slint/internal/interpreter/eval.rs
Tobias Hunger de4e195280
Rename internal crates and add a README.md to them
The README.md contains the warning that used to be in lib.rs.

Add README.md files to all internal crates

... pointing to the official public crate to use instead.

Rename internal crates

fixup: README files

fixup rename
2022-02-07 13:12:48 +01:00

1182 lines
56 KiB
Rust

// Copyright © SixtyFPS GmbH <info@sixtyfps.io>
// SPDX-License-Identifier: (GPL-3.0-only OR LicenseRef-SixtyFPS-commercial)
use crate::api::{SetPropertyError, Struct, Value};
use crate::dynamic_component::InstanceRef;
use core::convert::TryInto;
use core::pin::Pin;
use corelib::graphics::{GradientStop, LinearGradientBrush, PathElement};
use corelib::items::{ItemRef, PropertyAnimation};
use corelib::model::{Model, ModelRc};
use corelib::rtti::AnimatedBindingKind;
use corelib::window::{WindowHandleAccess, WindowRc};
use corelib::{Brush, Color, PathData, SharedString, SharedVector};
use i_slint_compiler::expression_tree::{
BuiltinFunction, EasingCurve, Expression, Path as ExprPath, PathElement as ExprPathElement,
};
use i_slint_compiler::langtype::Type;
use i_slint_compiler::object_tree::ElementRc;
use i_slint_core as corelib;
use std::collections::HashMap;
use std::rc::Rc;
pub trait ErasedPropertyInfo {
fn get(&self, item: Pin<ItemRef>) -> Value;
fn set(
&self,
item: Pin<ItemRef>,
value: Value,
animation: Option<PropertyAnimation>,
) -> Result<(), ()>;
fn set_binding(
&self,
item: Pin<ItemRef>,
binding: Box<dyn Fn() -> Value>,
animation: AnimatedBindingKind,
);
fn offset(&self) -> usize;
/// Safety: Property2 must be a (pinned) pointer to a `Property<T>`
/// where T is the same T as the one represented by this property.
unsafe fn link_two_ways(&self, item: Pin<ItemRef>, property2: *const ());
}
impl<Item: vtable::HasStaticVTable<corelib::items::ItemVTable>> ErasedPropertyInfo
for &'static dyn corelib::rtti::PropertyInfo<Item, Value>
{
fn get(&self, item: Pin<ItemRef>) -> Value {
(*self).get(ItemRef::downcast_pin(item).unwrap()).unwrap()
}
fn set(
&self,
item: Pin<ItemRef>,
value: Value,
animation: Option<PropertyAnimation>,
) -> Result<(), ()> {
(*self).set(ItemRef::downcast_pin(item).unwrap(), value, animation)
}
fn set_binding(
&self,
item: Pin<ItemRef>,
binding: Box<dyn Fn() -> 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<ItemRef>, 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<ItemRef>, args: &[Value]) -> Value;
fn set_handler(&self, item: Pin<ItemRef>, handler: Box<dyn Fn(&[Value]) -> Value>);
}
impl<Item: vtable::HasStaticVTable<corelib::items::ItemVTable>> ErasedCallbackInfo
for &'static dyn corelib::rtti::CallbackInfo<Item, Value>
{
fn call(&self, item: Pin<ItemRef>, args: &[Value]) -> Value {
(*self).call(ItemRef::downcast_pin(item).unwrap(), args).unwrap()
}
fn set_handler(&self, item: Pin<ItemRef>, handler: Box<dyn Fn(&[Value]) -> Value>) {
(*self).set_handler(ItemRef::downcast_pin(item).unwrap(), handler).unwrap()
}
}
impl corelib::rtti::ValueType for Value {}
#[derive(Copy, Clone)]
pub(crate) enum ComponentInstance<'a, 'id> {
InstanceRef(InstanceRef<'a, 'id>),
GlobalComponent(&'a Pin<Rc<dyn crate::global_component::GlobalComponent>>),
}
/// The local variable needed for binding evaluation
pub struct EvalLocalContext<'a, 'id> {
local_variables: HashMap<String, Value>,
function_arguments: Vec<Value>,
pub(crate) component_instance: ComponentInstance<'a, 'id>,
/// When Some, a return statement was executed and one must stop evaluating
return_value: Option<Value>,
}
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<Value>,
) -> Self {
Self {
component_instance: ComponentInstance::InstanceRef(component),
function_arguments,
local_variables: Default::default(),
return_value: None,
}
}
}
/// Evaluate an expression and return a Value as the result of this expression
pub fn eval_expression(expression: &Expression, local_context: &mut EvalLocalContext) -> Value {
if let Some(r) = &local_context.return_value {
return r.clone();
}
match expression {
Expression::Invalid => panic!("invalid expression while evaluating"),
Expression::Uncompiled(_) => panic!("uncompiled 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(nr) => {
load_property_helper(local_context.component_instance, &nr.element(), nr.name()).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::StructFieldAccess { base, name } => {
if let Value::Struct(o) = eval_expression(base, local_context) {
o.get_field(name).cloned().unwrap_or(Value::Void)
} else {
Value::Void
}
}
Expression::ArrayIndex { array, index } => {
let array = eval_expression(array, local_context);
let index = eval_expression(index, local_context);
match (array, index) {
(Value::Model(model), Value::Number(index)) => {
if (index as usize) < model.row_count() {
model.model_tracker().track_row_data_changes(index as usize);
model.row_data(index as usize).unwrap_or_else(|| default_value_for_type(&expression.ty()))
} else {
default_value_for_type(&expression.ty())
}
}
_ => {
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(nr) => {
let args = arguments.iter().map(|e| eval_expression(e, local_context)).collect::<Vec<_>>();
invoke_callback(local_context.component_instance, &nr.element(), nr.name(), &args).unwrap()
}
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 to_print: SharedString = eval_expression(&arguments[0], local_context).try_into().unwrap();
corelib::debug_log!("{}", to_print);
Value::Void
}
Expression::BuiltinFunctionReference(BuiltinFunction::Mod, _) => {
let mut to_int = |e| -> i32 { eval_expression(e, local_context).try_into().unwrap() };
Value::Number((to_int(&arguments[0]) % to_int(&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::Abs, _) => {
let x: f64 = eval_expression(&arguments[0], local_context).try_into().unwrap();
Value::Number(x.abs())
}
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::Log, _) => {
let x: f64 = eval_expression(&arguments[0], local_context).try_into().unwrap();
let y: f64 = eval_expression(&arguments[1], local_context).try_into().unwrap();
Value::Number(x.log(y))
}
Expression::BuiltinFunctionReference(BuiltinFunction::Pow, _) => {
let x: f64 = eval_expression(&arguments[0], local_context).try_into().unwrap();
let y: f64 = eval_expression(&arguments[1], local_context).try_into().unwrap();
Value::Number(x.powf(y))
}
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().clone().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(), popup.x.name()).unwrap();
let y = load_property_helper(local_context.component_instance, &popup.y.element(), popup.y.name()).unwrap();
generativity::make_guard!(guard);
let enclosing_component =
enclosing_component_for_element(&popup.parent_element, component, guard);
let parent_item_info = &enclosing_component.component_type.items[popup.parent_element.borrow().id.as_str()];
let parent_item_comp = enclosing_component.self_weak().get().unwrap().upgrade().unwrap();
let parent_item = corelib::items::ItemRc::new(vtable::VRc::into_dyn(parent_item_comp), parent_item_info.item_index());
crate::dynamic_component::show_popup(
popup,
i_slint_core::graphics::Point::new(x.try_into().unwrap(), y.try_into().unwrap()),
component.borrow(),
window_ref(component).unwrap(),
&parent_item);
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(<f64 as core::str::FromStr>::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::ImageSize, _) => {
if arguments.len() != 1 {
panic!("internal error: incorrect argument count to ImageSize")
}
if let Value::Image(img) = eval_expression(&arguments[0], local_context) {
let size = img.size();
let values = IntoIterator::into_iter([
("width".to_string(), Value::Number(size.width as f64)),
("height".to_string(), Value::Number(size.height as f64)),
]).collect();
Value::Struct(values)
} else {
panic!("First argument not an image");
}
}
Expression::BuiltinFunctionReference(BuiltinFunction::ArrayLength, _) => {
if arguments.len() != 1 {
panic!("internal error: incorrect argument count to ArrayLength")
}
match eval_expression(&arguments[0], local_context) {
Value::Model(model) => {
model.model_tracker().track_row_count_changes();
Value::Number(model.row_count() as f64)
}
_ => {
panic!("First argument not an array");
}
}
}
Expression::BuiltinFunctionReference(BuiltinFunction::Rgb, _) => {
let r: i32 = eval_expression(&arguments[0], local_context).try_into().unwrap();
let g: i32 = eval_expression(&arguments[1], local_context).try_into().unwrap();
let b: i32 = eval_expression(&arguments[2], local_context).try_into().unwrap();
let a: f32 = eval_expression(&arguments[3], local_context).try_into().unwrap();
let r: u8 = r.max(0).min(255) as u8;
let g: u8 = g.max(0).min(255) as u8;
let b: u8 = b.max(0).min(255) as u8;
let a: u8 = (255. * a).max(0.).min(255.) as u8;
Value::Brush(Brush::SolidColor(Color::from_argb_u8(a, r, g, b)))
}
Expression::BuiltinFunctionReference(BuiltinFunction::ImplicitLayoutInfo(orient), _) => {
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.as_slice() {
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();
item_ref.as_ref().layout_info(crate::eval_layout::to_runtime(*orient), window).into()
} else {
panic!("internal error: incorrect arguments to ImplicitLayoutInfo {:?}", arguments);
}
}
Expression::BuiltinFunctionReference(BuiltinFunction::RegisterCustomFontByPath, _) => {
if arguments.len() != 1 {
panic!("internal error: incorrect argument count to RegisterCustomFontByPath")
}
if let Value::String(s) = eval_expression(&arguments[0], local_context) {
if let Some(err) = crate::register_font_from_path(&std::path::PathBuf::from(s.as_str())).err() {
corelib::debug_log!("Error loading custom font {}: {}", s.as_str(), err);
}
Value::Void
} else {
panic!("Argument not a string");
}
}
_ => panic!("call of something not a callback"),
}
Expression::SelfAssignment { lhs, rhs, op } => {
let rhs = eval_expression(&**rhs, local_context);
eval_assignment(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),
('+', a @ Value::Struct(_), b @ Value::Struct(_)) => {
let a : Option<corelib::layout::LayoutInfo> = a.try_into().ok();
let b : Option<corelib::layout::LayoutInfo> = b.try_into().ok();
if let (Some(a), Some(b)) = (a, b) {
a.merge(&b).into()
} else {
panic!("unsupported {:?} {} {:?}", a, op, 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, .. } => {
Value::Image(match resource_ref {
i_slint_compiler::expression_tree::ImageReference::None => {
Ok(Default::default())
}
i_slint_compiler::expression_tree::ImageReference::AbsolutePath(path) => {
corelib::graphics::Image::load_from_path(std::path::Path::new(path))
}
i_slint_compiler::expression_tree::ImageReference::EmbeddedData { resource_id, extension } => {
let toplevel_instance = match local_context.component_instance {
ComponentInstance::InstanceRef(instance) => instance.toplevel_instance(),
ComponentInstance::GlobalComponent(_) => unimplemented!(),
};
let extra_data = toplevel_instance.component_type.extra_data_offset.apply(toplevel_instance.as_ref());
let path = extra_data.embedded_file_resources.get(resource_id).expect("internal error: invalid resource id");
let virtual_file = i_slint_compiler::fileaccess::load_file(std::path::Path::new(path)).unwrap(); // embedding pass ensured that the file exists
if let (std::borrow::Cow::Borrowed(static_path), Some(static_data)) = (virtual_file.path, virtual_file.builtin_contents) {
let virtual_file_extension = std::path::Path::new(static_path).extension().unwrap().to_str().unwrap();
debug_assert_eq!(virtual_file_extension, extension);
Ok(corelib::graphics::Image::from(
corelib::graphics::ImageInner::EmbeddedData {
data: corelib::slice::Slice::from_slice(static_data),
format: corelib::slice::Slice::from_slice(virtual_file_extension.as_bytes())
}
))
} else {
corelib::debug_log!("Cannot embed images from disk {}", path);
Ok(corelib::graphics::Image::default())
}
}
i_slint_compiler::expression_tree::ImageReference::EmbeddedTexture { .. } => {
todo!()
}
}.unwrap_or_else(|_| {
eprintln!("Could not load image {:?}",resource_ref );
Default::default()
}))
}
Expression::Condition { condition, true_expr, false_expr } => {
match eval_expression(&**condition, local_context).try_into()
as Result<bool, _>
{
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::Model(
ModelRc::new(corelib::model::SharedVectorModel::from(
values.iter().map(|e| eval_expression(e, local_context)).collect::<SharedVector<_>>()
)
)),
Expression::Struct { values, .. } => Value::Struct(
values
.iter()
.map(|(k, v)| (k.clone(), eval_expression(v, local_context)))
.collect(),
),
Expression::PathData(data) => {
Value::PathData(convert_path(data, 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()
}
Expression::LayoutCacheAccess { layout_cache_prop, index, repeater_index } => {
let cache = load_property_helper(local_context.component_instance, &layout_cache_prop.element(), layout_cache_prop.name()).unwrap();
if let Value::LayoutCache(cache) = cache {
if let Some(ri) = repeater_index {
let offset : usize = eval_expression(ri, local_context).try_into().unwrap();
Value::Number(cache[(cache[*index] as usize) + offset * 2].into())
} else {
Value::Number(cache[*index].into())
}
} else {
panic!("invalid layout cache")
}
}
Expression::ComputeLayoutInfo(lay, o) => crate::eval_layout::compute_layout_info(lay, *o, local_context),
Expression::SolveLayout(lay, o) => crate::eval_layout::solve_layout(lay, *o, local_context),
}
}
fn eval_assignment(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(nr) => {
let element = nr.element();
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, nr.name(), 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(nr.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[nr.name()];
p.set(item, eval(p.get(item)), None).unwrap();
}
ComponentInstance::GlobalComponent(global) => {
let val = if op == '=' {
rhs
} else {
eval(global.as_ref().get_property(nr.name()).unwrap())
};
global.as_ref().set_property(nr.name(), val).unwrap();
}
}
}
Expression::StructFieldAccess { base, name } => {
if let Value::Struct(mut o) = eval_expression(base, local_context) {
let mut r = o.get_field(name).unwrap().clone();
r = if op == '=' { rhs } else { eval(std::mem::take(&mut r)) };
o.set_field(name.to_owned(), r);
eval_assignment(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,
))
},
)
}
Expression::ArrayIndex { array, index } => {
let array = eval_expression(array, local_context);
let index = eval_expression(index, local_context);
match (array, index) {
(Value::Model(model), Value::Number(index)) => {
let index = index as usize;
if (index) < model.row_count() {
if op == '=' {
model.set_row_data(index, rhs);
} else {
model.set_row_data(
index,
eval(
model
.row_data(index)
.unwrap_or_else(|| default_value_for_type(&lhs.ty())),
),
);
}
}
}
_ => {
eprintln!("Attempting to write into an array that cannot be written");
}
}
}
_ => panic!("typechecking should make sure this was a PropertyReference"),
}
}
pub fn load_property(component: InstanceRef, element: &ElementRc, name: &str) -> Result<Value, ()> {
load_property_helper(ComponentInstance::InstanceRef(component), element, name)
}
fn load_property_helper(
component_instance: ComponentInstance,
element: &ElementRc,
name: &str,
) -> Result<Value, ()> {
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)))
};
} else if enclosing_component.component_type.original.is_global() {
return Err(());
}
};
let item_info = enclosing_component
.component_type
.items
.get(element.id.as_str())
.unwrap_or_else(|| panic!("Unknown 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).unwrap()),
}
}
pub fn store_property(
component_instance: InstanceRef,
element: &ElementRc,
name: &str,
value: Value,
) -> Result<(), SetPropertyError> {
generativity::make_guard!(guard);
match enclosing_component_instance_for_element(
element,
ComponentInstance::InstanceRef(component_instance),
guard,
) {
ComponentInstance::InstanceRef(enclosing_component) => {
let maybe_animation = match element.borrow().bindings.get(name) {
Some(b) => crate::dynamic_component::animation_for_property(
enclosing_component,
&b.borrow().animation,
),
None => {
crate::dynamic_component::animation_for_property(enclosing_component, &None)
}
};
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) {
if let Some(orig_decl) = enclosing_component
.component_type
.original
.root_element
.borrow()
.property_declarations
.get(name)
{
// Do an extra type checking because PropertyInfo::set won't do it for custom structures or array
if !check_value_type(&value, &orig_decl.property_type) {
return Err(SetPropertyError::WrongType);
}
}
unsafe {
let p = Pin::new_unchecked(&*enclosing_component.as_ptr().add(x.offset));
return x
.prop
.set(p, value, maybe_animation.as_animation())
.map_err(|()| SetPropertyError::WrongType);
}
} else if enclosing_component.component_type.original.is_global() {
return Err(SetPropertyError::NoSuchProperty);
}
};
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(SetPropertyError::NoSuchProperty)?;
p.set(item, value, maybe_animation.as_animation())
.map_err(|()| SetPropertyError::WrongType)?;
}
ComponentInstance::GlobalComponent(glob) => {
glob.as_ref().set_property(name, value)?;
}
}
Ok(())
}
/// Return true if the Value can be used for a property of the given type
fn check_value_type(value: &Value, ty: &Type) -> bool {
match ty {
Type::Void => true,
Type::Invalid
| Type::InferredProperty
| Type::InferredCallback
| Type::Component(_)
| Type::Builtin(_)
| Type::Native(_)
| Type::Callback { .. }
| Type::Function { .. }
| Type::ElementReference => panic!("not valid property type"),
Type::Float32 => matches!(value, Value::Number(_)),
Type::Int32 => matches!(value, Value::Number(_)),
Type::String => matches!(value, Value::String(_)),
Type::Color => matches!(value, Value::Brush(_)),
Type::UnitProduct(_)
| Type::Duration
| Type::PhysicalLength
| Type::LogicalLength
| Type::Angle
| Type::Percent => matches!(value, Value::Number(_)),
Type::Image => matches!(value, Value::Image(_)),
Type::Bool => matches!(value, Value::Bool(_)),
Type::Model => {
matches!(value, Value::Model(_) | Value::Bool(_) | Value::Number(_))
}
Type::PathData => matches!(value, Value::PathData(_)),
Type::Easing => matches!(value, Value::EasingCurve(_)),
Type::Brush => matches!(value, Value::Brush(_)),
Type::Array(inner) => {
matches!(value, Value::Model(m) if m.iter().all(|v| check_value_type(&v, inner)))
}
Type::Struct { fields, .. } => {
matches!(value, Value::Struct(str) if str.iter().all(|(k, v)| fields.get(k).map_or(false, |ty| check_value_type(v, ty))))
}
Type::Enumeration(en) => {
matches!(value, Value::EnumerationValue(name, _) if name == en.name.as_str())
}
Type::LayoutCache => matches!(value, Value::LayoutCache(_)),
}
}
pub(crate) fn invoke_callback(
component_instance: ComponentInstance,
element: &ElementRc,
callback_name: &str,
args: &[Value],
) -> Option<Value> {
generativity::make_guard!(guard);
match enclosing_component_instance_for_element(element, component_instance, guard) {
ComponentInstance::InstanceRef(enclosing_component) => {
let component_type = enclosing_component.component_type;
let element = element.borrow();
if element.id == element.enclosing_component.upgrade().unwrap().root_element.borrow().id
{
if let Some(callback_offset) = component_type.custom_callbacks.get(callback_name) {
let callback = callback_offset.apply(&*enclosing_component.instance);
let res = callback.call(args);
return Some(if res != Value::Void {
res
} else if let Some(Type::Callback { return_type: Some(rt), .. }) =
component_type
.original
.root_element
.borrow()
.property_declarations
.get(callback_name)
.map(|d| &d.property_type)
{
// If the callback was not se, the return value will be Value::Void, but we need
// to make sure that the value is actually of the right type as returned by the
// callback, otherwise we will get panics later
default_value_for_type(&*rt)
} else {
res
});
} else if enclosing_component.component_type.original.is_global() {
return None;
}
};
let item_info = &component_type.items[element.id.as_str()];
let item = unsafe { item_info.item_from_component(enclosing_component.as_ptr()) };
item_info.rtti.callbacks.get(callback_name).map(|callback| callback.call(item, args))
}
ComponentInstance::GlobalComponent(global) => {
Some(global.as_ref().invoke_callback(callback_name, args).unwrap())
}
}
}
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, 'old_id>, InstanceRef<'a, 'new_id>>(component)
}
}
}
pub fn window_ref<'a>(component: InstanceRef<'a, '_>) -> Option<&'a WindowRc> {
component
.component_type
.window_offset
.apply(component.instance.get_ref())
.as_ref()
.map(|window| window.window_handle())
}
/// 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();
if Rc::ptr_eq(enclosing, &component.component_type.original) {
// Safety: new_id is an unique id
unsafe {
std::mem::transmute::<InstanceRef<'a, 'old_id>, InstanceRef<'a, 'new_id>>(component)
}
} else {
assert!(!enclosing.is_global());
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::<InstanceRef, InstanceRef<'a, 'static>>(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 takes the GlobalComponent into account.
pub(crate) 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() && !component.component_type.original.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.root_element.borrow().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 + corelib::rtti::BuiltinItem>(
bindings: &i_slint_compiler::object_tree::BindingsMap,
local_context: &mut EvalLocalContext,
) -> ElementType {
let mut element = ElementType::default();
for (prop, info) in ElementType::fields::<Value>().into_iter() {
if let Some(binding) = &bindings.get(prop) {
let value = eval_expression(&binding.borrow(), local_context);
info.set_field(&mut element, value).unwrap();
}
}
element
}
fn convert_from_lyon_path<'a>(
events_it: impl IntoIterator<Item = &'a i_slint_compiler::expression_tree::Expression>,
points_it: impl IntoIterator<Item = &'a i_slint_compiler::expression_tree::Expression>,
local_context: &mut EvalLocalContext,
) -> PathData {
let events = events_it
.into_iter()
.map(|event_expr| eval_expression(event_expr, local_context).try_into().unwrap())
.collect::<SharedVector<_>>();
let points = points_it
.into_iter()
.map(|point_expr| {
let point_value = eval_expression(point_expr, local_context);
let point_struct: Struct = point_value.try_into().unwrap();
let mut point = i_slint_core::graphics::Point::default();
let x: f64 = point_struct.get_field("x").unwrap().clone().try_into().unwrap();
let y: f64 = point_struct.get_field("y").unwrap().clone().try_into().unwrap();
point.x = x as _;
point.y = y as _;
point
})
.collect::<SharedVector<_>>();
PathData::Events(events, points)
}
pub fn convert_path(path: &ExprPath, local_context: &mut EvalLocalContext) -> PathData {
match path {
ExprPath::Elements(elements) => PathData::Elements(
elements
.iter()
.map(|element| convert_path_element(element, local_context))
.collect::<SharedVector<PathElement>>(),
),
ExprPath::Events(events, points) => {
convert_from_lyon_path(events.iter(), points.iter(), local_context)
}
ExprPath::Commands(commands) => {
if let Value::String(commands) = eval_expression(commands, local_context) {
PathData::Commands(commands)
} else {
panic!("binding to path commands does not evalutate to string");
}
}
}
}
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
),
}
}
/// Create a value suitable as the default value of a given type
pub fn default_value_for_type(ty: &Type) -> Value {
match ty {
Type::Float32 | Type::Int32 => Value::Number(0.),
Type::String => Value::String(Default::default()),
Type::Color | Type::Brush => Value::Brush(Default::default()),
Type::Duration | Type::Angle | Type::PhysicalLength | Type::LogicalLength => {
Value::Number(0.)
}
Type::Image => Value::Image(Default::default()),
Type::Bool => Value::Bool(false),
Type::Callback { .. } => Value::Void,
Type::Struct { fields, .. } => Value::Struct(
fields.iter().map(|(n, t)| (n.clone(), default_value_for_type(t))).collect::<Struct>(),
),
Type::Array(_) | Type::Model => Value::Void,
Type::Percent => Value::Number(0.),
Type::Enumeration(e) => {
Value::EnumerationValue(e.name.clone(), e.values.get(e.default_value).unwrap().clone())
}
Type::Easing => Value::EasingCurve(Default::default()),
Type::Void | Type::Invalid => Value::Void,
Type::UnitProduct(_) => Value::Number(0.),
Type::PathData => Value::PathData(Default::default()),
Type::LayoutCache => Value::LayoutCache(Default::default()),
Type::InferredProperty
| Type::InferredCallback
| Type::ElementReference
| Type::Builtin(_)
| Type::Component(_)
| Type::Native(_)
| Type::Function { .. } => {
panic!("There can't be such property")
}
}
}