/* 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::api::Struct; use crate::dynamic_component::InstanceRef; use core::convert::TryInto; use core::iter::FromIterator; use core::pin::Pin; use corelib::graphics::{GradientStop, LinearGradientBrush, PathElement}; use corelib::items::{ItemRef, PropertyAnimation}; use corelib::rtti::AnimatedBindingKind; use corelib::window::ComponentWindow; use corelib::{Brush, Color, ImageReference, PathData, SharedString, SharedVector}; use sixtyfps_compilerlib::expression_tree::{ BindingExpression, BuiltinFunction, EasingCurve, Expression, NamedReference, Path as ExprPath, PathElement as ExprPathElement, }; use sixtyfps_compilerlib::langtype::Type; use sixtyfps_compilerlib::object_tree::ElementRc; use sixtyfps_corelib as corelib; use std::collections::HashMap; use std::rc::Rc; pub trait ErasedPropertyInfo { fn get(&self, item: Pin) -> Value; fn set(&self, item: Pin, value: Value, animation: Option); fn set_binding( &self, item: Pin, binding: Box Value>, animation: AnimatedBindingKind, ); fn offset(&self) -> usize; /// Safety: Property2 must be a (pinned) pointer to a `Property` /// where T is the same T as the one represented by this property. unsafe fn link_two_ways(&self, item: Pin, property2: *const ()); } impl> ErasedPropertyInfo for &'static dyn corelib::rtti::PropertyInfo { fn get(&self, item: Pin) -> Value { (*self).get(ItemRef::downcast_pin(item).unwrap()).unwrap() } fn set(&self, item: Pin, value: Value, animation: Option) { (*self).set(ItemRef::downcast_pin(item).unwrap(), value, animation).unwrap() } fn set_binding( &self, item: Pin, binding: Box Value>, animation: AnimatedBindingKind, ) { (*self).set_binding(ItemRef::downcast_pin(item).unwrap(), binding, animation).unwrap(); } fn offset(&self) -> usize { (*self).offset() } unsafe fn link_two_ways(&self, item: Pin, property2: *const ()) { // Safety: ErasedPropertyInfo::link_two_ways and PropertyInfo::link_two_ways have the same safety requirement (*self).link_two_ways(ItemRef::downcast_pin(item).unwrap(), property2) } } pub trait ErasedCallbackInfo { fn call(&self, item: Pin, args: &[Value]) -> Value; fn set_handler(&self, item: Pin, handler: Box Value>); } impl> ErasedCallbackInfo for &'static dyn corelib::rtti::CallbackInfo { fn call(&self, item: Pin, args: &[Value]) -> Value { (*self).call(ItemRef::downcast_pin(item).unwrap(), args).unwrap() } fn set_handler(&self, item: Pin, handler: Box Value>) { (*self).set_handler(ItemRef::downcast_pin(item).unwrap(), handler).unwrap() } } /// This is a dynamically typed Value used in the interpreter, it need to be able /// to be converted from and to anything that can be stored in a Property #[derive(Clone)] #[non_exhaustive] pub enum Value { /// There is nothing in this value. That's the default. /// For example, a function that do not return a result would return a Value::Void Void, /// An `int` or a `float` (this is also used for unit based type such as `length` or `angle`) Number(f64), /// Correspond to the `string` type in .60 String(SharedString), /// Correspond to the `bool` type in .60 Bool(bool), /// Correspond to the `image` type in .60 Image(ImageReference), /// An Array in the .60 language. Array(SharedVector), /// A more complex model which is not created by the interpreter itself (Value::Array can also be used for model) Model(Rc>), /// An object Struct(Struct), /// Corresespond to `brush` or `color` type in .60. For color, this is then a [`Brush::SolidColor`] Brush(Brush), #[doc(hidden)] /// The elements of a path PathElements(PathData), #[doc(hidden)] /// An easing curve EasingCurve(corelib::animations::EasingCurve), #[doc(hidden)] /// An enumation, like TextHorizontalAlignment::align_center EnumerationValue(String, String), } impl Default for Value { fn default() -> Self { Value::Void } } impl PartialEq for Value { fn eq(&self, other: &Self) -> bool { match self { Value::Void => matches!(other, Value::Void), Value::Number(lhs) => matches!(other, Value::Number(rhs) if lhs == rhs), Value::String(lhs) => matches!(other, Value::String(rhs) if lhs == rhs), Value::Bool(lhs) => matches!(other, Value::Bool(rhs) if lhs == rhs), Value::Image(lhs) => matches!(other, Value::Image(rhs) if lhs == rhs), Value::Array(lhs) => matches!(other, Value::Array(rhs) if lhs == rhs), Value::Model(lhs) => matches!(other, Value::Model(rhs) if Rc::ptr_eq(lhs, rhs)), Value::Struct(lhs) => matches!(other, Value::Struct(rhs) if lhs == rhs), Value::Brush(lhs) => matches!(other, Value::Brush(rhs) if lhs == rhs), Value::PathElements(lhs) => matches!(other, Value::PathElements(rhs) if lhs == rhs), Value::EasingCurve(lhs) => matches!(other, Value::EasingCurve(rhs) if lhs == rhs), Value::EnumerationValue(lhs_name, lhs_value) => { matches!(other, Value::EnumerationValue(rhs_name, rhs_value) if lhs_name == rhs_name && lhs_value == rhs_value) } } } } impl std::fmt::Debug for Value { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { Value::Void => write!(f, "Value::Void"), Value::Number(n) => write!(f, "Value::Number({:?})", n), Value::String(s) => write!(f, "Value::String({:?})", s), Value::Bool(b) => write!(f, "Value::Bool({:?})", b), Value::Image(i) => write!(f, "Value::Image({:?})", i), Value::Array(a) => write!(f, "Value::Array({:?})", a), Value::Model(_) => write!(f, "Value::Model()"), Value::Struct(s) => write!(f, "Value::Struct({:?})", s), Value::Brush(b) => write!(f, "Value::Brush({:?})", b), Value::PathElements(e) => write!(f, "Value::PathElements({:?})", e), Value::EasingCurve(c) => write!(f, "Value::EasingCurve({:?})", c), Value::EnumerationValue(n, v) => write!(f, "Value::EnumerationValue({:?}, {:?})", n, v), } } } impl corelib::rtti::ValueType for Value {} /// Helper macro to implement the From / TryInto for Value /// /// For example /// `declare_value_conversion!(Number => [u32, u64, i32, i64, f32, f64] );` /// means that `Value::Number` can be converted to / from each of the said rust types /// /// For `Value::Object` mapping to a rust `struct`, one can use [`declare_value_struct_conversion!`] /// And for `Value::EnumerationValue` which maps to a rust `enum`, one can use [`declare_value_struct_conversion!`] macro_rules! declare_value_conversion { ( $value:ident => [$($ty:ty),*] ) => { $( impl From<$ty> for Value { fn from(v: $ty) -> Self { Value::$value(v as _) } } impl TryInto<$ty> for Value { type Error = Value; fn try_into(self) -> Result<$ty, Value> { match self { //Self::$value(x) => x.try_into().map_err(|_|()), Self::$value(x) => Ok(x as _), _ => Err(self) } } } )* }; } declare_value_conversion!(Number => [u32, u64, i32, i64, f32, f64, usize, isize] ); declare_value_conversion!(String => [SharedString] ); declare_value_conversion!(Bool => [bool] ); declare_value_conversion!(Image => [ImageReference] ); declare_value_conversion!(Struct => [Struct] ); declare_value_conversion!(Brush => [Brush] ); declare_value_conversion!(PathElements => [PathData]); declare_value_conversion!(EasingCurve => [corelib::animations::EasingCurve]); /// Implement From / TryInto for Value that convert a `struct` to/from `Value::Object` macro_rules! declare_value_struct_conversion { (struct $name:path { $($field:ident),* $(,)? }) => { impl From<$name> for Value { fn from($name { $($field),* }: $name) -> Self { let mut struct_ = Struct::default(); $(struct_.set_property(stringify!($field).into(), crate::api::Value($field.into()));)* Value::Struct(struct_) } } impl TryInto<$name> for Value { type Error = (); fn try_into(self) -> Result<$name, ()> { match self { Self::Struct(x) => { type Ty = $name; Ok(Ty { $($field: x.get_property(stringify!($field)).ok_or(())?.0.clone().try_into().map_err(|_|())?),* }) } _ => Err(()), } } } }; } declare_value_struct_conversion!(struct corelib::model::StandardListViewItem { text }); declare_value_struct_conversion!(struct corelib::properties::StateInfo { current_state, previous_state, change_time }); declare_value_struct_conversion!(struct corelib::input::KeyboardModifiers { control, alt, shift, meta }); declare_value_struct_conversion!(struct corelib::input::KeyEvent { event_type, text, modifiers }); /// Implement From / TryInto for Value that convert an `enum` to/from `Value::EnumerationValue` /// /// The `enum` must derive `Display` and `FromStr` /// (can be done with `strum_macros::EnumString`, `strum_macros::Display` derive macro) macro_rules! declare_value_enum_conversion { ($ty:ty, $n:ident) => { impl From<$ty> for Value { fn from(v: $ty) -> Self { Value::EnumerationValue(stringify!($n).to_owned(), v.to_string()) } } impl TryInto<$ty> for Value { type Error = (); fn try_into(self) -> Result<$ty, ()> { use std::str::FromStr; match self { Self::EnumerationValue(enumeration, value) => { if enumeration != stringify!($n) { return Err(()); } <$ty>::from_str(value.as_str()).map_err(|_| ()) } _ => Err(()), } } } }; } declare_value_enum_conversion!(corelib::items::TextHorizontalAlignment, TextHorizontalAlignment); declare_value_enum_conversion!(corelib::items::TextVerticalAlignment, TextVerticalAlignment); declare_value_enum_conversion!(corelib::items::TextOverflow, TextOverflow); declare_value_enum_conversion!(corelib::items::TextWrap, TextWrap); declare_value_enum_conversion!(corelib::layout::LayoutAlignment, LayoutAlignment); declare_value_enum_conversion!(corelib::items::ImageFit, ImageFit); declare_value_enum_conversion!(corelib::input::KeyEventType, KeyEventType); declare_value_enum_conversion!(corelib::items::EventResult, EventResult); declare_value_enum_conversion!(corelib::items::FillRule, FillRule); impl From for Value { fn from(value: corelib::animations::Instant) -> Self { Value::Number(value.0 as _) } } impl TryInto for Value { type Error = (); fn try_into(self) -> Result { match self { Value::Number(x) => Ok(corelib::animations::Instant(x as _)), _ => Err(()), } } } impl From<()> for Value { #[inline] fn from(_: ()) -> Self { Value::Void } } impl TryInto<()> for Value { type Error = (); #[inline] fn try_into(self) -> Result<(), ()> { Ok(()) } } impl From for Value { #[inline] fn from(c: Color) -> Self { Value::Brush(Brush::SolidColor(c)) } } impl TryInto for Value { type Error = Value; #[inline] fn try_into(self) -> Result { match self { Value::Brush(Brush::SolidColor(c)) => Ok(c), _ => Err(self), } } } #[derive(Copy, Clone)] enum ComponentInstance<'a, 'id> { InstanceRef(InstanceRef<'a, 'id>), GlobalComponent(&'a Pin>), } /// The local variable needed for binding evaluation pub struct EvalLocalContext<'a, 'id> { local_variables: HashMap, function_arguments: Vec, component_instance: ComponentInstance<'a, 'id>, /// When Some, a return statement was executed and one must stop evaluating return_value: Option, } impl<'a, 'id> EvalLocalContext<'a, 'id> { pub fn from_component_instance(component: InstanceRef<'a, 'id>) -> Self { Self { local_variables: Default::default(), function_arguments: Default::default(), component_instance: ComponentInstance::InstanceRef(component), return_value: None, } } /// Create a context for a function and passing the arguments pub fn from_function_arguments( component: InstanceRef<'a, 'id>, function_arguments: Vec, ) -> Self { Self { component_instance: ComponentInstance::InstanceRef(component), function_arguments, local_variables: Default::default(), return_value: None, } } pub fn from_global(global: &'a Pin>) -> Self { Self { local_variables: Default::default(), function_arguments: Default::default(), component_instance: ComponentInstance::GlobalComponent(&global), return_value: None, } } } /// Evaluate an expression and return a Value as the result of this expression pub fn eval_expression(e: &Expression, local_context: &mut EvalLocalContext) -> Value { if let Some(r) = &local_context.return_value { return r.clone(); } match e { Expression::Invalid => panic!("invalid expression while evaluating"), Expression::Uncompiled(_) => panic!("uncompiled expression while evaluating"), Expression::TwoWayBinding(..) => panic!("invalid expression while evaluating"), Expression::StringLiteral(s) => Value::String(s.into()), Expression::NumberLiteral(n, unit) => Value::Number(unit.normalize(*n)), Expression::BoolLiteral(b) => Value::Bool(*b), Expression::CallbackReference { .. } => panic!("callback in expression"), Expression::BuiltinFunctionReference(_) => panic!( "naked builtin function reference not allowed, should be handled by function call" ), Expression::ElementReference(_) => todo!("Element references are only supported in the context of built-in function calls at the moment"), Expression::MemberFunction { .. } => panic!("member function expressions must not appear in the code generator anymore"), Expression::BuiltinMacroReference { .. } => panic!("macro expressions must not appear in the code generator anymore"), Expression::PropertyReference(NamedReference { element, name }) => { load_property_helper(local_context.component_instance, &element.upgrade().unwrap(), name.as_ref()).unwrap() } Expression::RepeaterIndexReference { element } => load_property_helper(local_context.component_instance, &element.upgrade().unwrap().borrow().base_type.as_component().root_element, "index", ) .unwrap(), Expression::RepeaterModelReference { element } => load_property_helper(local_context.component_instance, &element.upgrade().unwrap().borrow().base_type.as_component().root_element, "model_data", ) .unwrap(), Expression::FunctionParameterReference { index, .. } => { local_context.function_arguments[*index].clone() } Expression::ObjectAccess { base, name } => { if let Value::Struct(o) = eval_expression(base, local_context) { o.get_property(name).map_or(Value::Void, |public_value| public_value.0) } else { Value::Void } } Expression::Cast { from, to } => { let v = eval_expression(&*from, local_context); match (v, to) { (Value::Number(n), Type::Int32) => Value::Number(n.round()), (Value::Number(n), Type::String) => { Value::String(SharedString::from(format!("{}", n).as_str())) } (Value::Number(n), Type::Color) => Color::from_argb_encoded(n as u32).into(), (Value::Brush(brush), Type::Color) => brush.color().into(), (v, _) => v, } } Expression::CodeBlock(sub) => { let mut v = Value::Void; for e in sub { v = eval_expression(e, local_context); if let Some(r) = &local_context.return_value { return r.clone(); } } v } Expression::FunctionCall { function, arguments, source_location: _ } => match &**function { Expression::CallbackReference(NamedReference { element, name }) => { let element = element.upgrade().unwrap(); generativity::make_guard!(guard); match enclosing_component_instance_for_element(&element, local_context.component_instance, guard) { ComponentInstance::InstanceRef(enclosing_component) => { let component_type = enclosing_component.component_type; let item_info = &component_type.items[element.borrow().id.as_str()]; let item = unsafe { item_info.item_from_component(enclosing_component.as_ptr()) }; let args = arguments.iter().map(|e| eval_expression(e, local_context)).collect::>(); if let Some(callback) = item_info.rtti.callbacks.get(name.as_str()) { callback.call(item, args.as_slice()) } else if let Some(callback_offset) = component_type.custom_callbacks.get(name.as_str()) { let callback = callback_offset.apply(&*enclosing_component.instance); callback.call(args.as_slice()) } else { panic!("unkown callback {}", name) } } ComponentInstance::GlobalComponent(global) => { let args = arguments.iter().map(|e| eval_expression(e, local_context)); global.as_ref().call_callback(name.as_ref(), args.collect::>().as_slice()) } } } Expression::BuiltinFunctionReference(BuiltinFunction::GetWindowScaleFactor) => { match local_context.component_instance { ComponentInstance::InstanceRef(component) => Value::Number(window_ref(component).unwrap().scale_factor() as _), ComponentInstance::GlobalComponent(_) => panic!("Cannot get the window from a global component"), } } Expression::BuiltinFunctionReference(BuiltinFunction::Debug) => { let a = arguments.iter().map(|e| eval_expression(e, local_context)); println!("{:?}", a.collect::>()); Value::Void } Expression::BuiltinFunctionReference(BuiltinFunction::Mod) => { let mut toint = |e| -> i32 { eval_expression(e, local_context).try_into().unwrap() }; Value::Number((toint(&arguments[0]) % toint(&arguments[1])) as _) } Expression::BuiltinFunctionReference(BuiltinFunction::Round) => { let x: f64 = eval_expression(&arguments[0], local_context).try_into().unwrap(); Value::Number(x.round()) } Expression::BuiltinFunctionReference(BuiltinFunction::Ceil) => { let x: f64 = eval_expression(&arguments[0], local_context).try_into().unwrap(); Value::Number(x.ceil()) } Expression::BuiltinFunctionReference(BuiltinFunction::Floor) => { let x: f64 = eval_expression(&arguments[0], local_context).try_into().unwrap(); Value::Number(x.floor()) } Expression::BuiltinFunctionReference(BuiltinFunction::Sqrt) => { let x: f64 = eval_expression(&arguments[0], local_context).try_into().unwrap(); Value::Number(x.sqrt()) } Expression::BuiltinFunctionReference(BuiltinFunction::Sin) => { let x: f64 = eval_expression(&arguments[0], local_context).try_into().unwrap(); Value::Number(x.to_radians().sin()) } Expression::BuiltinFunctionReference(BuiltinFunction::Cos) => { let x: f64 = eval_expression(&arguments[0], local_context).try_into().unwrap(); Value::Number(x.to_radians().cos()) } Expression::BuiltinFunctionReference(BuiltinFunction::Tan) => { let x: f64 = eval_expression(&arguments[0], local_context).try_into().unwrap(); Value::Number(x.to_radians().tan()) } Expression::BuiltinFunctionReference(BuiltinFunction::ASin) => { let x: f64 = eval_expression(&arguments[0], local_context).try_into().unwrap(); Value::Number(x.asin().to_degrees()) } Expression::BuiltinFunctionReference(BuiltinFunction::ACos) => { let x: f64 = eval_expression(&arguments[0], local_context).try_into().unwrap(); Value::Number(x.acos().to_degrees()) } Expression::BuiltinFunctionReference(BuiltinFunction::ATan) => { let x: f64 = eval_expression(&arguments[0], local_context).try_into().unwrap(); Value::Number(x.atan().to_degrees()) } Expression::BuiltinFunctionReference(BuiltinFunction::SetFocusItem) => { if arguments.len() != 1 { panic!("internal error: incorrect argument count to SetFocusItem") } let component = match local_context.component_instance { ComponentInstance::InstanceRef(c) => c, ComponentInstance::GlobalComponent(_) => panic!("Cannot access the focus item from a global component") }; if let Expression::ElementReference(focus_item) = &arguments[0] { generativity::make_guard!(guard); let focus_item = focus_item.upgrade().unwrap(); let enclosing_component = enclosing_component_for_element(&focus_item, component, guard); let component_type = enclosing_component.component_type; let item_info = &component_type.items[focus_item.borrow().id.as_str()]; let focus_item_comp = enclosing_component.self_weak().get().unwrap().upgrade().unwrap(); window_ref(component).unwrap().set_focus_item(&corelib::items::ItemRc::new(vtable::VRc::into_dyn(focus_item_comp), item_info.item_index())); Value::Void } else { panic!("internal error: argument to SetFocusItem must be an element") } } Expression::BuiltinFunctionReference(BuiltinFunction::ShowPopupWindow) => { if arguments.len() != 1 { panic!("internal error: incorrect argument count to ShowPopupWindow") } let component = match local_context.component_instance { ComponentInstance::InstanceRef(c) => c, ComponentInstance::GlobalComponent(_) => panic!("Cannot show popup from a global component") }; if let Expression::ElementReference(popup_window) = &arguments[0] { let popup_window = popup_window.upgrade().unwrap(); let pop_comp = popup_window.borrow().enclosing_component.upgrade().unwrap(); let parent_component = pop_comp.parent_element.upgrade().unwrap().borrow().enclosing_component.upgrade().unwrap(); let popup_list = parent_component.popup_windows.borrow(); let popup = popup_list.iter().find(|p| Rc::ptr_eq(&p.component, &pop_comp)).unwrap(); let x = load_property_helper(local_context.component_instance, &popup.x.element.upgrade().unwrap(), &popup.x.name).unwrap(); let y = load_property_helper(local_context.component_instance, &popup.y.element.upgrade().unwrap(), &popup.y.name).unwrap(); crate::dynamic_component::show_popup(popup, x.try_into().unwrap(), y.try_into().unwrap(), component.borrow(), window_ref(component).unwrap()); Value::Void } else { panic!("internal error: argument to SetFocusItem must be an element") } } Expression::BuiltinFunctionReference(BuiltinFunction::StringIsFloat) => { if arguments.len() != 1 { panic!("internal error: incorrect argument count to StringIsFloat") } if let Value::String(s) = eval_expression(&arguments[0], local_context) { Value::Bool(::from_str(s.as_str()).is_ok()) } else { panic!("Argument not a string"); } } Expression::BuiltinFunctionReference(BuiltinFunction::StringToFloat) => { if arguments.len() != 1 { panic!("internal error: incorrect argument count to StringToFloat") } if let Value::String(s) = eval_expression(&arguments[0], local_context) { Value::Number(core::str::FromStr::from_str(s.as_str()).unwrap_or(0.)) } else { panic!("Argument not a string"); } } Expression::BuiltinFunctionReference(BuiltinFunction::ColorBrighter) => { if arguments.len() != 2 { panic!("internal error: incorrect argument count to ColorBrighter") } if let Value::Brush(Brush::SolidColor(col)) = eval_expression(&arguments[0], local_context) { if let Value::Number(factor) = eval_expression(&arguments[1], local_context) { col.brighter(factor as _).into() } else { panic!("Second argument not a number"); } } else { panic!("First argument not a color"); } } Expression::BuiltinFunctionReference(BuiltinFunction::ColorDarker) => { if arguments.len() != 2 { panic!("internal error: incorrect argument count to ColorDarker") } if let Value::Brush(Brush::SolidColor(col)) = eval_expression(&arguments[0], local_context) { if let Value::Number(factor) = eval_expression(&arguments[1], local_context) { col.darker(factor as _).into() } else { panic!("Second argument not a number"); } } else { panic!("First argument not a color"); } } Expression::BuiltinFunctionReference(BuiltinFunction::ImplicitItemSize) => { if arguments.len() != 1 { panic!("internal error: incorrect argument count to ImplicitItemSize") } let component = match local_context.component_instance { ComponentInstance::InstanceRef(c) => c, ComponentInstance::GlobalComponent(_) => panic!("Cannot access the implicit item size from a global component") }; if let Expression::ElementReference(item) = &arguments[0] { generativity::make_guard!(guard); let item = item.upgrade().unwrap(); let enclosing_component = enclosing_component_for_element(&item, component, guard); let component_type = enclosing_component.component_type; let item_info = &component_type.items[item.borrow().id.as_str()]; let item_ref = unsafe { item_info.item_from_component(enclosing_component.as_ptr()) }; let window = window_ref(component).unwrap(); let size = item_ref.as_ref().implicit_size(&window); let values = [ ("width".to_string(), Value::Number(size.width as f64)), ("height".to_string(), Value::Number(size.height as f64)), ] .iter() .map(|(name, value)| (name.clone(), crate::api::Value(value.clone()))).collect(); Value::Struct(values) } else { panic!("internal error: argument to ImplicitItemWidth must be an element") } } _ => panic!("call of something not a callback"), } Expression::SelfAssignment { lhs, rhs, op } => { let rhs = eval_expression(&**rhs, local_context); eval_assignement(lhs, *op, rhs, local_context); Value::Void } Expression::BinaryExpression { lhs, rhs, op } => { let lhs = eval_expression(&**lhs, local_context); let rhs = eval_expression(&**rhs, local_context); match (op, lhs, rhs) { ('+', Value::String(mut a), Value::String(b)) => { a.push_str(b.as_str()); Value::String(a) }, ('+', Value::Number(a), Value::Number(b)) => Value::Number(a + b), ('-', Value::Number(a), Value::Number(b)) => Value::Number(a - b), ('/', Value::Number(a), Value::Number(b)) => Value::Number(a / b), ('*', Value::Number(a), Value::Number(b)) => Value::Number(a * b), ('<', Value::Number(a), Value::Number(b)) => Value::Bool(a < b), ('>', Value::Number(a), Value::Number(b)) => Value::Bool(a > b), ('≤', Value::Number(a), Value::Number(b)) => Value::Bool(a <= b), ('≥', Value::Number(a), Value::Number(b)) => Value::Bool(a >= b), ('<', Value::String(a), Value::String(b)) => Value::Bool(a < b), ('>', Value::String(a), Value::String(b)) => Value::Bool(a > b), ('≤', Value::String(a), Value::String(b)) => Value::Bool(a <= b), ('≥', Value::String(a), Value::String(b)) => Value::Bool(a >= b), ('=', a, b) => Value::Bool(a == b), ('!', a, b) => Value::Bool(a != b), ('&', Value::Bool(a), Value::Bool(b)) => Value::Bool(a && b), ('|', Value::Bool(a), Value::Bool(b)) => Value::Bool(a || b), (op, lhs, rhs) => panic!("unsupported {:?} {} {:?}", lhs, op, rhs), } } Expression::UnaryOp { sub, op } => { let sub = eval_expression(&**sub, local_context); match (sub, op) { (Value::Number(a), '+') => Value::Number(a), (Value::Number(a), '-') => Value::Number(-a), (Value::Bool(a), '!') => Value::Bool(!a), (sub, op) => panic!("unsupported {} {:?}", op, sub), } } Expression::ImageReference(resource_ref) => { match resource_ref { sixtyfps_compilerlib::expression_tree::ImageReference::None => { Value::Image(ImageReference::None) } sixtyfps_compilerlib::expression_tree::ImageReference::AbsolutePath(path) => { Value::Image(ImageReference::AbsoluteFilePath(path.into())) } sixtyfps_compilerlib::expression_tree::ImageReference::EmbeddedData(_) => panic!("Resource embedding is not supported by the interpreter") } } Expression::Condition { condition, true_expr, false_expr } => { match eval_expression(&**condition, local_context).try_into() as Result { Ok(true) => eval_expression(&**true_expr, local_context), Ok(false) => eval_expression(&**false_expr, local_context), _ => local_context.return_value.clone().expect("conditional expression did not evaluate to boolean"), } } Expression::Array { values, .. } => Value::Array( values.iter().map(|e| eval_expression(e, local_context)).collect(), ), Expression::Object { values, .. } => Value::Struct( values .iter() .map(|(k, v)| (k.clone(), crate::api::Value(eval_expression(v, local_context)))) .collect(), ), Expression::PathElements { elements } => { Value::PathElements(convert_path(elements, local_context)) } Expression::StoreLocalVariable { name, value } => { let value = eval_expression(value, local_context); local_context.local_variables.insert(name.clone(), value); Value::Void } Expression::ReadLocalVariable { name, .. } => { local_context.local_variables.get(name).unwrap().clone() } Expression::EasingCurve(curve) => Value::EasingCurve(match curve { EasingCurve::Linear => corelib::animations::EasingCurve::Linear, EasingCurve::CubicBezier(a, b, c, d) => { corelib::animations::EasingCurve::CubicBezier([*a, *b, *c, *d]) } }), Expression::LinearGradient{angle, stops} => { let angle = eval_expression(angle, local_context); Value::Brush(Brush::LinearGradient(LinearGradientBrush::new(angle.try_into().unwrap(), stops.iter().map(|(color, stop)| { let color = eval_expression(color, local_context).try_into().unwrap(); let position = eval_expression(stop, local_context).try_into().unwrap(); GradientStop{ color, position } })))) } Expression::EnumerationValue(value) => { Value::EnumerationValue(value.enumeration.name.clone(), value.to_string()) } Expression::ReturnStatement(x) => { let val = x.as_ref().map_or(Value::Void, |x| eval_expression(&x, local_context)); if local_context.return_value.is_none() { local_context.return_value = Some(val); } local_context.return_value.clone().unwrap() } } } fn eval_assignement(lhs: &Expression, op: char, rhs: Value, local_context: &mut EvalLocalContext) { let eval = |lhs| match (lhs, &rhs, op) { (Value::String(ref mut a), Value::String(b), '+') => { a.push_str(b.as_str()); Value::String(a.clone()) } (Value::Number(a), Value::Number(b), '+') => Value::Number(a + b), (Value::Number(a), Value::Number(b), '-') => Value::Number(a - b), (Value::Number(a), Value::Number(b), '/') => Value::Number(a / b), (Value::Number(a), Value::Number(b), '*') => Value::Number(a * b), (lhs, rhs, op) => panic!("unsupported {:?} {} {:?}", lhs, op, rhs), }; match lhs { Expression::PropertyReference(NamedReference { element, name }) => { let element = element.upgrade().unwrap(); generativity::make_guard!(guard); let enclosing_component = enclosing_component_instance_for_element( &element, local_context.component_instance, guard, ); match enclosing_component { ComponentInstance::InstanceRef(enclosing_component) => { if op == '=' { store_property(enclosing_component, &element, name.as_ref(), rhs).unwrap(); return; } let component = element.borrow().enclosing_component.upgrade().unwrap(); if element.borrow().id == component.root_element.borrow().id { if let Some(x) = enclosing_component.component_type.custom_properties.get(name) { unsafe { let p = Pin::new_unchecked( &*enclosing_component.as_ptr().add(x.offset), ); x.prop.set(p, eval(x.prop.get(p).unwrap()), None).unwrap(); } return; } }; let item_info = &enclosing_component.component_type.items[element.borrow().id.as_str()]; let item = unsafe { item_info.item_from_component(enclosing_component.as_ptr()) }; let p = &item_info.rtti.properties[name.as_str()]; p.set(item, eval(p.get(item)), None); } ComponentInstance::GlobalComponent(global) => { let val = if op == '=' { rhs } else { eval(global.as_ref().get_property(name.as_str())) }; global.as_ref().set_property(name.as_str(), val); } } } Expression::ObjectAccess { base, name } => { if let Value::Struct(mut o) = eval_expression(base, local_context) { let mut r = o.get_property(name).unwrap().0; r = if op == '=' { rhs } else { eval(std::mem::take(&mut r)) }; o.set_property(name.to_owned(), crate::api::Value(r)); eval_assignement(base, '=', Value::Struct(o), local_context) } } Expression::RepeaterModelReference { element } => { let element = element.upgrade().unwrap(); let component_instance = match local_context.component_instance { ComponentInstance::InstanceRef(i) => i, ComponentInstance::GlobalComponent(_) => panic!("can't have repeater in global"), }; generativity::make_guard!(g1); let enclosing_component = enclosing_component_for_element(&element, component_instance, g1); // we need a 'static Repeater component in order to call model_set_row_data, so get it. // Safety: This is the only 'static Id in scope. let static_guard = unsafe { generativity::Guard::new(generativity::Id::<'static>::new()) }; let repeater = crate::dynamic_component::get_repeater_by_name( enclosing_component, element.borrow().id.as_str(), static_guard, ); repeater.0.model_set_row_data( eval_expression( &Expression::RepeaterIndexReference { element: Rc::downgrade(&element) }, local_context, ) .try_into() .unwrap(), if op == '=' { rhs } else { eval(eval_expression( &Expression::RepeaterModelReference { element: Rc::downgrade(&element) }, local_context, )) }, ) } _ => panic!("typechecking should make sure this was a PropertyReference"), } } pub fn load_property(component: InstanceRef, element: &ElementRc, name: &str) -> Result { load_property_helper(ComponentInstance::InstanceRef(component), element, name) } fn load_property_helper( component_instance: ComponentInstance, element: &ElementRc, name: &str, ) -> Result { generativity::make_guard!(guard); match enclosing_component_instance_for_element(&element, component_instance, guard) { ComponentInstance::InstanceRef(enclosing_component) => { 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(name) { return unsafe { x.prop.get(Pin::new_unchecked(&*enclosing_component.as_ptr().add(x.offset))) }; } }; let item_info = enclosing_component .component_type .items .get(element.id.as_str()) .unwrap_or_else(|| panic!("Unkown element for {}.{}", element.id, name)); core::mem::drop(element); let item = unsafe { item_info.item_from_component(enclosing_component.as_ptr()) }; Ok(item_info.rtti.properties.get(name).ok_or(())?.get(item)) } ComponentInstance::GlobalComponent(glob) => Ok(glob.as_ref().get_property(name)), } } pub fn store_property( component_instance: InstanceRef, element: &ElementRc, name: &str, value: Value, ) -> Result<(), ()> { generativity::make_guard!(guard); let enclosing_component = enclosing_component_for_element(&element, component_instance, guard); let maybe_animation = crate::dynamic_component::animation_for_property( enclosing_component, &element.borrow(), name, ); let component = element.borrow().enclosing_component.upgrade().unwrap(); if element.borrow().id == component.root_element.borrow().id { if let Some(x) = enclosing_component.component_type.custom_properties.get(name) { unsafe { let p = Pin::new_unchecked(&*enclosing_component.as_ptr().add(x.offset)); return x.prop.set(p, value, maybe_animation.as_animation()); } } }; let item_info = &enclosing_component.component_type.items[element.borrow().id.as_str()]; let item = unsafe { item_info.item_from_component(enclosing_component.as_ptr()) }; let p = &item_info.rtti.properties.get(name).ok_or(())?; p.set(item, value, maybe_animation.as_animation()); Ok(()) } fn root_component_instance<'a, 'old_id, 'new_id>( component: InstanceRef<'a, 'old_id>, guard: generativity::Guard<'new_id>, ) -> InstanceRef<'a, 'new_id> { if let Some(parent_offset) = component.component_type.parent_component_offset { let parent_component = if let Some(parent) = parent_offset.apply(&*component.instance.get_ref()) { *parent } else { panic!("invalid parent ptr"); }; // we need a 'static guard in order to be able to re-borrow with lifetime 'a. // Safety: This is the only 'static Id in scope. let static_guard = unsafe { generativity::Guard::new(generativity::Id::<'static>::new()) }; root_component_instance( unsafe { InstanceRef::from_pin_ref(parent_component, static_guard) }, guard, ) } else { // Safety: new_id is an unique id unsafe { std::mem::transmute::, InstanceRef<'a, 'new_id>>(component) } } } pub fn window_ref(component: InstanceRef) -> Option { component.component_type.window_offset.apply(&*component.instance.get_ref()).clone() } /// Return the component instance which hold the given element. /// Does not take in account the global component. pub fn enclosing_component_for_element<'a, 'old_id, 'new_id>( element: &'a ElementRc, component: InstanceRef<'a, 'old_id>, guard: generativity::Guard<'new_id>, ) -> InstanceRef<'a, 'new_id> { let enclosing = &element.borrow().enclosing_component.upgrade().unwrap(); assert!(!enclosing.is_global()); if Rc::ptr_eq(enclosing, &component.component_type.original) { // Safety: new_id is an unique id unsafe { std::mem::transmute::, InstanceRef<'a, 'new_id>>(component) } } else { let parent_component = component .component_type .parent_component_offset .unwrap() .apply(component.as_ref()) .unwrap(); generativity::make_guard!(new_guard); let parent_instance = unsafe { InstanceRef::from_pin_ref(parent_component, new_guard) }; let parent_instance = unsafe { core::mem::transmute::>(parent_instance) }; enclosing_component_for_element(element, parent_instance, guard) } } /// Return the component instance which hold the given element. /// The difference with enclosing_component_for_element is that it taked in account the GlobalComponent. fn enclosing_component_instance_for_element<'a, 'old_id, 'new_id>( element: &'a ElementRc, component_instance: ComponentInstance<'a, 'old_id>, guard: generativity::Guard<'new_id>, ) -> ComponentInstance<'a, 'new_id> { let enclosing = &element.borrow().enclosing_component.upgrade().unwrap(); match component_instance { ComponentInstance::InstanceRef(component) => { if enclosing.is_global() { // we need a 'static guard in order to be able to borrow from `root` otherwise it does not work because of variance. // Safety: This is the only 'static Id in scope. let static_guard = unsafe { generativity::Guard::new(generativity::Id::<'static>::new()) }; let root = root_component_instance(component, static_guard); ComponentInstance::GlobalComponent( &root.component_type.extra_data_offset.apply(&*root.instance.get_ref()).globals [enclosing.id.as_str()], ) } else { ComponentInstance::InstanceRef(enclosing_component_for_element( element, component, guard, )) } } ComponentInstance::GlobalComponent(global) => { //assert!(Rc::ptr_eq(enclosing, &global.component)); ComponentInstance::GlobalComponent(global) } } } pub fn new_struct_with_bindings< ElementType: 'static + Default + sixtyfps_corelib::rtti::BuiltinItem, >( bindings: &HashMap, local_context: &mut EvalLocalContext, ) -> ElementType { let mut element = ElementType::default(); for (prop, info) in ElementType::fields::().into_iter() { if let Some(binding) = &bindings.get(prop) { let value = eval_expression(&binding, local_context); info.set_field(&mut element, value).unwrap(); } } element } fn convert_from_lyon_path<'a>( it: impl IntoIterator>, ) -> PathData { use lyon_path::Event; use sixtyfps_corelib::graphics::PathEvent; let mut coordinates = Vec::new(); let events = it .into_iter() .map(|event| match event { Event::Begin { at } => { coordinates.push(at); PathEvent::Begin } Event::Line { from, to } => { coordinates.push(from); coordinates.push(to); PathEvent::Line } Event::Quadratic { from, ctrl, to } => { coordinates.push(from); coordinates.push(ctrl); coordinates.push(to); PathEvent::Quadratic } Event::Cubic { from, ctrl1, ctrl2, to } => { coordinates.push(from); coordinates.push(ctrl1); coordinates.push(ctrl2); coordinates.push(to); PathEvent::Cubic } Event::End { close, .. } => { if *close { PathEvent::EndClosed } else { PathEvent::EndOpen } } }) .collect::>(); PathData::Events( SharedVector::from(events.as_slice()), SharedVector::from_iter(coordinates.into_iter().cloned()), ) } pub fn convert_path(path: &ExprPath, local_context: &mut EvalLocalContext) -> PathData { match path { ExprPath::Elements(elements) => PathData::Elements(SharedVector::::from_iter( elements.iter().map(|element| convert_path_element(element, local_context)), )), ExprPath::Events(events) => convert_from_lyon_path(events.iter()), } } fn convert_path_element( expr_element: &ExprPathElement, local_context: &mut EvalLocalContext, ) -> PathElement { match expr_element.element_type.native_class.class_name.as_str() { "MoveTo" => { PathElement::MoveTo(new_struct_with_bindings(&expr_element.bindings, local_context)) } "LineTo" => { PathElement::LineTo(new_struct_with_bindings(&expr_element.bindings, local_context)) } "ArcTo" => { PathElement::ArcTo(new_struct_with_bindings(&expr_element.bindings, local_context)) } "CubicTo" => { PathElement::CubicTo(new_struct_with_bindings(&expr_element.bindings, local_context)) } "QuadraticTo" => PathElement::QuadraticTo(new_struct_with_bindings( &expr_element.bindings, local_context, )), "Close" => PathElement::Close, _ => panic!( "Cannot create unsupported path element {}", expr_element.element_type.native_class.class_name ), } }