slint/internal/interpreter/eval.rs
Olivier Goffart a8ef4a05e6
Saner codegen for debugging enum
The previous code would create many many lambda and if conditions which
would cause nesting to be way too high for big enums.

Also improve the C++ codegen for condition to use the ternary operator
when possible

Fixes #9954
2025-11-04 16:13:37 +01:00

2047 lines
92 KiB
Rust

// Copyright © SixtyFPS GmbH <info@slint.dev>
// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0
use crate::api::{SetPropertyError, Struct, Value};
use crate::dynamic_item_tree::{CallbackHandler, InstanceRef};
use core::pin::Pin;
use corelib::graphics::{
ConicGradientBrush, GradientStop, LinearGradientBrush, PathElement, RadialGradientBrush,
};
use corelib::input::FocusReason;
use corelib::items::{ColorScheme, ItemRc, ItemRef, PropertyAnimation, WindowItem};
use corelib::menus::{Menu, MenuFromItemTree};
use corelib::model::{Model, ModelExt, ModelRc, VecModel};
use corelib::rtti::AnimatedBindingKind;
use corelib::window::WindowInner;
use corelib::{Brush, Color, PathData, SharedString, SharedVector};
use i_slint_compiler::expression_tree::{
BuiltinFunction, Callable, EasingCurve, Expression, MinMaxOp, Path as ExprPath,
PathElement as ExprPathElement,
};
use i_slint_compiler::langtype::Type;
use i_slint_compiler::namedreference::NamedReference;
use i_slint_compiler::object_tree::ElementRc;
use i_slint_core as corelib;
use smol_str::SmolStr;
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 ());
fn prepare_for_two_way_binding(&self, item: Pin<ItemRef>) -> Pin<Rc<corelib::Property<Value>>>;
fn link_two_way_with_map(
&self,
item: Pin<ItemRef>,
property2: Pin<Rc<corelib::Property<Value>>>,
map: Option<Rc<dyn corelib::rtti::TwoWayBindingMapping<Value>>>,
);
}
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)
}
fn prepare_for_two_way_binding(&self, item: Pin<ItemRef>) -> Pin<Rc<corelib::Property<Value>>> {
(*self).prepare_for_two_way_binding(ItemRef::downcast_pin(item).unwrap())
}
fn link_two_way_with_map(
&self,
item: Pin<ItemRef>,
property2: Pin<Rc<corelib::Property<Value>>>,
map: Option<Rc<dyn corelib::rtti::TwoWayBindingMapping<Value>>>,
) {
(*self).link_two_way_with_map(ItemRef::downcast_pin(item).unwrap(), property2, map)
}
}
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(Clone)]
pub(crate) enum ComponentInstance<'a, 'id> {
InstanceRef(InstanceRef<'a, 'id>),
GlobalComponent(Pin<Rc<dyn crate::global_component::GlobalComponent>>),
}
/// The local variable needed for binding evaluation
pub struct EvalLocalContext<'a, 'id> {
local_variables: HashMap<SmolStr, Value>,
function_arguments: Vec<Value>,
pub(crate) component_instance: InstanceRef<'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: 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: 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.as_str().into()),
Expression::NumberLiteral(n, unit) => Value::Number(unit.normalize(*n)),
Expression::BoolLiteral(b) => Value::Bool(*b),
Expression::ElementReference(_) => todo!("Element references are only supported in the context of built-in function calls at the moment"),
Expression::PropertyReference(nr) => {
load_property_helper(&ComponentInstance::InstanceRef(local_context.component_instance), &nr.element(), nr.name()).unwrap()
}
Expression::RepeaterIndexReference { element } => load_property_helper(&ComponentInstance::InstanceRef(local_context.component_instance),
&element.upgrade().unwrap().borrow().base_type.as_component().root_element,
crate::dynamic_item_tree::SPECIAL_PROPERTY_INDEX,
)
.unwrap(),
Expression::RepeaterModelReference { element } => {
let value = load_property_helper(&ComponentInstance::InstanceRef(local_context.component_instance),
&element.upgrade().unwrap().borrow().base_type.as_component().root_element,
crate::dynamic_item_tree::SPECIAL_PROPERTY_MODEL_DATA,
)
.unwrap();
if matches!(value, Value::Void) {
// Uninitialized model data (because the model returned None) should still be initialized to the default value of the type
default_value_for_type(&expression.ty())
} else {
value
}
},
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)) => {
model.row_data_tracked(index as isize as usize).unwrap_or_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.trunc()),
(Value::Number(n), Type::String) => {
Value::String(i_slint_core::string::shared_string_from_number(n))
}
(Value::Number(n), Type::Color) => Color::from_argb_encoded(n as u32).into(),
(Value::Brush(brush), Type::Color) => brush.color().into(),
(Value::EnumerationValue(_, val), Type::String) => Value::String(val.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 {
Callable::Function(nr) => {
let is_item_member = nr.element().borrow().native_class().is_some_and(|n| n.properties.contains_key(nr.name()));
if is_item_member {
call_item_member_function(nr, local_context)
} else {
let args = arguments.iter().map(|e| eval_expression(e, local_context)).collect::<Vec<_>>();
call_function(&ComponentInstance::InstanceRef(local_context.component_instance), &nr.element(), nr.name(), args).unwrap()
}
}
Callable::Callback(nr) => {
let args = arguments.iter().map(|e| eval_expression(e, local_context)).collect::<Vec<_>>();
invoke_callback(&ComponentInstance::InstanceRef(local_context.component_instance), &nr.element(), nr.name(), &args).unwrap()
}
Callable::Builtin(f) => call_builtin_function(f.clone(), arguments, local_context, source_location),
}
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, nine_slice, .. } => {
let mut image = match resource_ref {
i_slint_compiler::expression_tree::ImageReference::None => {
Ok(Default::default())
}
i_slint_compiler::expression_tree::ImageReference::AbsolutePath(path) => {
let path = std::path::Path::new(path);
if path.starts_with("builtin:/") {
i_slint_compiler::fileaccess::load_file(path).and_then(|virtual_file| virtual_file.builtin_contents).map(|virtual_file| {
let extension = path.extension().unwrap().to_str().unwrap();
corelib::graphics::load_image_from_embedded_data(
corelib::slice::Slice::from_slice(virtual_file),
corelib::slice::Slice::from_slice(extension.as_bytes())
)
}).ok_or_else(Default::default)
} else {
corelib::graphics::Image::load_from_path(path)
}
}
i_slint_compiler::expression_tree::ImageReference::EmbeddedData { .. } => {
todo!()
}
i_slint_compiler::expression_tree::ImageReference::EmbeddedTexture { .. } => {
todo!()
}
}.unwrap_or_else(|_| {
eprintln!("Could not load image {resource_ref:?}" );
Default::default()
});
if let Some(n) = nine_slice {
image.set_nine_slice_edges(n[0], n[1], n[2], n[3]);
}
Value::Image(image)
}
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.to_string(), 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::EaseInElastic => corelib::animations::EasingCurve::EaseInElastic,
EasingCurve::EaseOutElastic => corelib::animations::EasingCurve::EaseOutElastic,
EasingCurve::EaseInOutElastic => corelib::animations::EasingCurve::EaseInOutElastic,
EasingCurve::EaseInBounce => corelib::animations::EasingCurve::EaseInBounce,
EasingCurve::EaseOutBounce => corelib::animations::EasingCurve::EaseOutBounce,
EasingCurve::EaseInOutBounce => corelib::animations::EasingCurve::EaseInOutBounce,
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::RadialGradient{stops} => {
Value::Brush(Brush::RadialGradient(RadialGradientBrush::new_circle(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::ConicGradient{stops} => {
Value::Brush(Brush::ConicGradient(ConicGradientBrush::new(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.to_string(), 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(&ComponentInstance::InstanceRef(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.get((cache[*index] as usize) + offset * 2).copied().unwrap_or(0.).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),
Expression::MinMax { ty: _, op, lhs, rhs } => {
let Value::Number(lhs) = eval_expression(lhs, local_context) else {
return local_context.return_value.clone().expect("minmax lhs expression did not evaluate to number");
};
let Value::Number(rhs) = eval_expression(rhs, local_context) else {
return local_context.return_value.clone().expect("minmax rhs expression did not evaluate to number");
};
match op {
MinMaxOp::Min => Value::Number(lhs.min(rhs)),
MinMaxOp::Max => Value::Number(lhs.max(rhs)),
}
}
Expression::EmptyComponentFactory => Value::ComponentFactory(Default::default()),
Expression::DebugHook { expression, .. } => eval_expression(expression, local_context),
}
}
fn call_builtin_function(
f: BuiltinFunction,
arguments: &[Expression],
local_context: &mut EvalLocalContext,
source_location: &Option<i_slint_compiler::diagnostics::SourceLocation>,
) -> Value {
match f {
BuiltinFunction::GetWindowScaleFactor => Value::Number(
local_context.component_instance.access_window(|window| window.scale_factor()) as _,
),
BuiltinFunction::GetWindowDefaultFontSize => Value::Number({
let component = local_context.component_instance;
let item_comp = component.self_weak().get().unwrap().upgrade().unwrap();
WindowItem::resolved_default_font_size(vtable::VRc::into_dyn(item_comp)).get() as _
}),
BuiltinFunction::AnimationTick => {
Value::Number(i_slint_core::animations::animation_tick() as f64)
}
BuiltinFunction::Debug => {
let to_print: SharedString =
eval_expression(&arguments[0], local_context).try_into().unwrap();
local_context.component_instance.description.debug_handler.borrow()(
source_location.as_ref(),
&to_print,
);
Value::Void
}
BuiltinFunction::Mod => {
let mut to_num = |e| -> f64 { eval_expression(e, local_context).try_into().unwrap() };
Value::Number(to_num(&arguments[0]).rem_euclid(to_num(&arguments[1])))
}
BuiltinFunction::Round => {
let x: f64 = eval_expression(&arguments[0], local_context).try_into().unwrap();
Value::Number(x.round())
}
BuiltinFunction::Ceil => {
let x: f64 = eval_expression(&arguments[0], local_context).try_into().unwrap();
Value::Number(x.ceil())
}
BuiltinFunction::Floor => {
let x: f64 = eval_expression(&arguments[0], local_context).try_into().unwrap();
Value::Number(x.floor())
}
BuiltinFunction::Sqrt => {
let x: f64 = eval_expression(&arguments[0], local_context).try_into().unwrap();
Value::Number(x.sqrt())
}
BuiltinFunction::Abs => {
let x: f64 = eval_expression(&arguments[0], local_context).try_into().unwrap();
Value::Number(x.abs())
}
BuiltinFunction::Sin => {
let x: f64 = eval_expression(&arguments[0], local_context).try_into().unwrap();
Value::Number(x.to_radians().sin())
}
BuiltinFunction::Cos => {
let x: f64 = eval_expression(&arguments[0], local_context).try_into().unwrap();
Value::Number(x.to_radians().cos())
}
BuiltinFunction::Tan => {
let x: f64 = eval_expression(&arguments[0], local_context).try_into().unwrap();
Value::Number(x.to_radians().tan())
}
BuiltinFunction::ASin => {
let x: f64 = eval_expression(&arguments[0], local_context).try_into().unwrap();
Value::Number(x.asin().to_degrees())
}
BuiltinFunction::ACos => {
let x: f64 = eval_expression(&arguments[0], local_context).try_into().unwrap();
Value::Number(x.acos().to_degrees())
}
BuiltinFunction::ATan => {
let x: f64 = eval_expression(&arguments[0], local_context).try_into().unwrap();
Value::Number(x.atan().to_degrees())
}
BuiltinFunction::ATan2 => {
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.atan2(y).to_degrees())
}
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))
}
BuiltinFunction::Ln => {
let x: f64 = eval_expression(&arguments[0], local_context).try_into().unwrap();
Value::Number(x.ln())
}
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))
}
BuiltinFunction::Exp => {
let x: f64 = eval_expression(&arguments[0], local_context).try_into().unwrap();
Value::Number(x.exp())
}
BuiltinFunction::ToFixed => {
let n: f64 = eval_expression(&arguments[0], local_context).try_into().unwrap();
let digits: i32 = eval_expression(&arguments[1], local_context).try_into().unwrap();
let digits: usize = digits.max(0) as usize;
Value::String(i_slint_core::string::shared_string_from_number_fixed(n, digits))
}
BuiltinFunction::ToPrecision => {
let n: f64 = eval_expression(&arguments[0], local_context).try_into().unwrap();
let precision: i32 = eval_expression(&arguments[1], local_context).try_into().unwrap();
let precision: usize = precision.max(0) as usize;
Value::String(i_slint_core::string::shared_string_from_number_precision(n, precision))
}
BuiltinFunction::SetFocusItem => {
if arguments.len() != 1 {
panic!("internal error: incorrect argument count to SetFocusItem")
}
let component = local_context.component_instance;
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 description = enclosing_component.description;
let item_info = &description.items[focus_item.borrow().id.as_str()];
let focus_item_comp =
enclosing_component.self_weak().get().unwrap().upgrade().unwrap();
component.access_window(|window| {
window.set_focus_item(
&corelib::items::ItemRc::new(
vtable::VRc::into_dyn(focus_item_comp),
item_info.item_index(),
),
true,
FocusReason::Programmatic,
)
});
Value::Void
} else {
panic!("internal error: argument to SetFocusItem must be an element")
}
}
BuiltinFunction::ClearFocusItem => {
if arguments.len() != 1 {
panic!("internal error: incorrect argument count to SetFocusItem")
}
let component = local_context.component_instance;
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 description = enclosing_component.description;
let item_info = &description.items[focus_item.borrow().id.as_str()];
let focus_item_comp =
enclosing_component.self_weak().get().unwrap().upgrade().unwrap();
component.access_window(|window| {
window.set_focus_item(
&corelib::items::ItemRc::new(
vtable::VRc::into_dyn(focus_item_comp),
item_info.item_index(),
),
false,
FocusReason::Programmatic,
)
});
Value::Void
} else {
panic!("internal error: argument to ClearFocusItem must be an element")
}
}
BuiltinFunction::ShowPopupWindow => {
if arguments.len() != 1 {
panic!("internal error: incorrect argument count to ShowPopupWindow")
}
let component = local_context.component_instance;
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();
generativity::make_guard!(guard);
let enclosing_component =
enclosing_component_for_element(&popup.parent_element, component, guard);
let parent_item_info = &enclosing_component.description.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(),
);
let close_policy = Value::EnumerationValue(
popup.close_policy.enumeration.name.to_string(),
popup.close_policy.to_string(),
)
.try_into()
.expect("Invalid internal enumeration representation for close policy");
crate::dynamic_item_tree::show_popup(
popup_window,
component,
popup,
|instance_ref| {
let comp = ComponentInstance::InstanceRef(instance_ref);
let x = load_property_helper(&comp, &popup.x.element(), popup.x.name())
.unwrap();
let y = load_property_helper(&comp, &popup.y.element(), popup.y.name())
.unwrap();
corelib::api::LogicalPosition::new(
x.try_into().unwrap(),
y.try_into().unwrap(),
)
},
close_policy,
enclosing_component.self_weak().get().unwrap().clone(),
component.window_adapter(),
&parent_item,
);
Value::Void
} else {
panic!("internal error: argument to ShowPopupWindow must be an element")
}
}
BuiltinFunction::ClosePopupWindow => {
let component = local_context.component_instance;
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();
generativity::make_guard!(guard);
let enclosing_component =
enclosing_component_for_element(&popup.parent_element, component, guard);
crate::dynamic_item_tree::close_popup(
popup_window,
enclosing_component,
enclosing_component.window_adapter(),
);
Value::Void
} else {
panic!("internal error: argument to ClosePopupWindow must be an element")
}
}
BuiltinFunction::ShowPopupMenu | BuiltinFunction::ShowPopupMenuInternal => {
let [Expression::ElementReference(element), entries, position] = arguments else {
panic!("internal error: incorrect argument count to ShowPopupMenu")
};
let position = eval_expression(position, local_context)
.try_into()
.expect("internal error: popup menu position argument should be a point");
let component = local_context.component_instance;
let elem = element.upgrade().unwrap();
generativity::make_guard!(guard);
let enclosing_component = enclosing_component_for_element(&elem, component, guard);
let description = enclosing_component.description;
let item_info = &description.items[elem.borrow().id.as_str()];
let item_comp = enclosing_component.self_weak().get().unwrap().upgrade().unwrap();
let item_tree = vtable::VRc::into_dyn(item_comp);
let item_rc = corelib::items::ItemRc::new(item_tree.clone(), item_info.item_index());
generativity::make_guard!(guard);
let compiled = enclosing_component.description.popup_menu_description.unerase(guard);
let inst = crate::dynamic_item_tree::instantiate(
compiled.clone(),
Some(enclosing_component.self_weak().get().unwrap().clone()),
None,
Some(&crate::dynamic_item_tree::WindowOptions::UseExistingWindow(
component.window_adapter(),
)),
Default::default(),
);
generativity::make_guard!(guard);
let inst_ref = inst.unerase(guard);
if let Expression::ElementReference(e) = entries {
let menu_item_tree =
e.upgrade().unwrap().borrow().enclosing_component.upgrade().unwrap();
let menu_item_tree = crate::dynamic_item_tree::make_menu_item_tree(
&menu_item_tree,
&enclosing_component,
None,
);
if component.access_window(|window| {
window.show_native_popup_menu(
vtable::VRc::into_dyn(menu_item_tree.clone()),
position,
&item_rc,
)
}) {
return Value::Void;
}
let (entries, sub_menu, activated) = menu_item_tree_properties(menu_item_tree);
compiled.set_binding(inst_ref.borrow(), "entries", entries).unwrap();
compiled.set_callback_handler(inst_ref.borrow(), "sub-menu", sub_menu).unwrap();
compiled.set_callback_handler(inst_ref.borrow(), "activated", activated).unwrap();
} else {
let entries = eval_expression(entries, local_context);
compiled.set_property(inst_ref.borrow(), "entries", entries).unwrap();
let item_weak = item_rc.downgrade();
compiled
.set_callback_handler(
inst_ref.borrow(),
"sub-menu",
Box::new(move |args: &[Value]| -> Value {
item_weak
.upgrade()
.unwrap()
.downcast::<corelib::items::ContextMenu>()
.unwrap()
.sub_menu
.call(&(args[0].clone().try_into().unwrap(),))
.into()
}),
)
.unwrap();
let item_weak = item_rc.downgrade();
compiled
.set_callback_handler(
inst_ref.borrow(),
"activated",
Box::new(move |args: &[Value]| -> Value {
item_weak
.upgrade()
.unwrap()
.downcast::<corelib::items::ContextMenu>()
.unwrap()
.activated
.call(&(args[0].clone().try_into().unwrap(),));
Value::Void
}),
)
.unwrap();
}
let item_weak = item_rc.downgrade();
compiled
.set_callback_handler(
inst_ref.borrow(),
"close",
Box::new(move |_args: &[Value]| -> Value {
let Some(item_rc) = item_weak.upgrade() else { return Value::Void };
if let Some(id) = item_rc
.downcast::<corelib::items::ContextMenu>()
.unwrap()
.popup_id
.take()
{
WindowInner::from_pub(item_rc.window_adapter().unwrap().window())
.close_popup(id);
}
Value::Void
}),
)
.unwrap();
component.access_window(|window| {
let context_menu_elem = item_rc.downcast::<corelib::items::ContextMenu>().unwrap();
if let Some(old_id) = context_menu_elem.popup_id.take() {
window.close_popup(old_id)
}
let id = window.show_popup(
&vtable::VRc::into_dyn(inst.clone()),
position,
corelib::items::PopupClosePolicy::CloseOnClickOutside,
&item_rc,
true,
);
context_menu_elem.popup_id.set(Some(id));
});
inst.run_setup_code();
Value::Void
}
BuiltinFunction::SetSelectionOffsets => {
if arguments.len() != 3 {
panic!("internal error: incorrect argument count to select range function call")
}
let component = local_context.component_instance;
if let Expression::ElementReference(element) = &arguments[0] {
generativity::make_guard!(guard);
let elem = element.upgrade().unwrap();
let enclosing_component = enclosing_component_for_element(&elem, component, guard);
let description = enclosing_component.description;
let item_info = &description.items[elem.borrow().id.as_str()];
let item_ref =
unsafe { item_info.item_from_item_tree(enclosing_component.as_ptr()) };
let item_comp = enclosing_component.self_weak().get().unwrap().upgrade().unwrap();
let item_rc = corelib::items::ItemRc::new(
vtable::VRc::into_dyn(item_comp),
item_info.item_index(),
);
let window_adapter = component.window_adapter();
// TODO: Make this generic through RTTI
if let Some(textinput) =
ItemRef::downcast_pin::<corelib::items::TextInput>(item_ref)
{
let start: i32 =
eval_expression(&arguments[1], local_context).try_into().expect(
"internal error: second argument to set-selection-offsets must be an integer",
);
let end: i32 = eval_expression(&arguments[2], local_context).try_into().expect(
"internal error: third argument to set-selection-offsets must be an integer",
);
textinput.set_selection_offsets(&window_adapter, &item_rc, start, end);
} else {
panic!(
"internal error: member function called on element that doesn't have it: {}",
elem.borrow().original_name()
)
}
Value::Void
} else {
panic!("internal error: first argument to set-selection-offsets must be an element")
}
}
BuiltinFunction::ItemFontMetrics => {
if arguments.len() != 1 {
panic!(
"internal error: incorrect argument count to item font metrics function call"
)
}
let component = local_context.component_instance;
if let Expression::ElementReference(element) = &arguments[0] {
generativity::make_guard!(guard);
let elem = element.upgrade().unwrap();
let enclosing_component = enclosing_component_for_element(&elem, component, guard);
let description = enclosing_component.description;
let item_info = &description.items[elem.borrow().id.as_str()];
let item_ref =
unsafe { item_info.item_from_item_tree(enclosing_component.as_ptr()) };
let item_comp = enclosing_component.self_weak().get().unwrap().upgrade().unwrap();
let item_rc = corelib::items::ItemRc::new(
vtable::VRc::into_dyn(item_comp),
item_info.item_index(),
);
let window_adapter = component.window_adapter();
let metrics = i_slint_core::items::slint_text_item_fontmetrics(
&window_adapter,
item_ref,
&item_rc,
);
metrics.into()
} else {
panic!("internal error: argument to item-font-metrics must be an element")
}
}
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");
}
}
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");
}
}
BuiltinFunction::StringIsEmpty => {
if arguments.len() != 1 {
panic!("internal error: incorrect argument count to StringIsEmpty")
}
if let Value::String(s) = eval_expression(&arguments[0], local_context) {
Value::Bool(s.is_empty())
} else {
panic!("Argument not a string");
}
}
BuiltinFunction::StringCharacterCount => {
if arguments.len() != 1 {
panic!("internal error: incorrect argument count to StringCharacterCount")
}
if let Value::String(s) = eval_expression(&arguments[0], local_context) {
Value::Number(
unicode_segmentation::UnicodeSegmentation::graphemes(s.as_str(), true).count()
as f64,
)
} else {
panic!("Argument not a string");
}
}
BuiltinFunction::StringToLowercase => {
if arguments.len() != 1 {
panic!("internal error: incorrect argument count to StringToLowercase")
}
if let Value::String(s) = eval_expression(&arguments[0], local_context) {
Value::String(s.to_lowercase().into())
} else {
panic!("Argument not a string");
}
}
BuiltinFunction::StringToUppercase => {
if arguments.len() != 1 {
panic!("internal error: incorrect argument count to StringToUppercase")
}
if let Value::String(s) = eval_expression(&arguments[0], local_context) {
Value::String(s.to_uppercase().into())
} else {
panic!("Argument not a string");
}
}
BuiltinFunction::ColorRgbaStruct => {
if arguments.len() != 1 {
panic!("internal error: incorrect argument count to ColorRGBAComponents")
}
if let Value::Brush(brush) = eval_expression(&arguments[0], local_context) {
let color = brush.color();
let values = IntoIterator::into_iter([
("red".to_string(), Value::Number(color.red().into())),
("green".to_string(), Value::Number(color.green().into())),
("blue".to_string(), Value::Number(color.blue().into())),
("alpha".to_string(), Value::Number(color.alpha().into())),
])
.collect();
Value::Struct(values)
} else {
panic!("First argument not a color");
}
}
BuiltinFunction::ColorHsvaStruct => {
if arguments.len() != 1 {
panic!("internal error: incorrect argument count to ColorHSVAComponents")
}
if let Value::Brush(brush) = eval_expression(&arguments[0], local_context) {
let color = brush.color().to_hsva();
let values = IntoIterator::into_iter([
("hue".to_string(), Value::Number(color.hue.into())),
("saturation".to_string(), Value::Number(color.saturation.into())),
("value".to_string(), Value::Number(color.value.into())),
("alpha".to_string(), Value::Number(color.alpha.into())),
])
.collect();
Value::Struct(values)
} else {
panic!("First argument not a color");
}
}
BuiltinFunction::ColorBrighter => {
if arguments.len() != 2 {
panic!("internal error: incorrect argument count to ColorBrighter")
}
if let Value::Brush(brush) = eval_expression(&arguments[0], local_context) {
if let Value::Number(factor) = eval_expression(&arguments[1], local_context) {
brush.brighter(factor as _).into()
} else {
panic!("Second argument not a number");
}
} else {
panic!("First argument not a color");
}
}
BuiltinFunction::ColorDarker => {
if arguments.len() != 2 {
panic!("internal error: incorrect argument count to ColorDarker")
}
if let Value::Brush(brush) = eval_expression(&arguments[0], local_context) {
if let Value::Number(factor) = eval_expression(&arguments[1], local_context) {
brush.darker(factor as _).into()
} else {
panic!("Second argument not a number");
}
} else {
panic!("First argument not a color");
}
}
BuiltinFunction::ColorTransparentize => {
if arguments.len() != 2 {
panic!("internal error: incorrect argument count to ColorFaded")
}
if let Value::Brush(brush) = eval_expression(&arguments[0], local_context) {
if let Value::Number(factor) = eval_expression(&arguments[1], local_context) {
brush.transparentize(factor as _).into()
} else {
panic!("Second argument not a number");
}
} else {
panic!("First argument not a color");
}
}
BuiltinFunction::ColorMix => {
if arguments.len() != 3 {
panic!("internal error: incorrect argument count to ColorMix")
}
let arg0 = eval_expression(&arguments[0], local_context);
let arg1 = eval_expression(&arguments[1], local_context);
let arg2 = eval_expression(&arguments[2], local_context);
if !matches!(arg0, Value::Brush(Brush::SolidColor(_))) {
panic!("First argument not a color");
}
if !matches!(arg1, Value::Brush(Brush::SolidColor(_))) {
panic!("Second argument not a color");
}
if !matches!(arg2, Value::Number(_)) {
panic!("Third argument not a number");
}
let (
Value::Brush(Brush::SolidColor(color_a)),
Value::Brush(Brush::SolidColor(color_b)),
Value::Number(factor),
) = (arg0, arg1, arg2)
else {
unreachable!()
};
color_a.mix(&color_b, factor as _).into()
}
BuiltinFunction::ColorWithAlpha => {
if arguments.len() != 2 {
panic!("internal error: incorrect argument count to ColorWithAlpha")
}
if let Value::Brush(brush) = eval_expression(&arguments[0], local_context) {
if let Value::Number(factor) = eval_expression(&arguments[1], local_context) {
brush.with_alpha(factor as _).into()
} else {
panic!("Second argument not a number");
}
} else {
panic!("First argument not a color");
}
}
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");
}
}
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");
}
}
}
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.clamp(0, 255) as u8;
let g: u8 = g.clamp(0, 255) as u8;
let b: u8 = b.clamp(0, 255) as u8;
let a: u8 = (255. * a).clamp(0., 255.) as u8;
Value::Brush(Brush::SolidColor(Color::from_argb_u8(a, r, g, b)))
}
BuiltinFunction::Hsv => {
let h: f32 = eval_expression(&arguments[0], local_context).try_into().unwrap();
let s: f32 = eval_expression(&arguments[1], local_context).try_into().unwrap();
let v: f32 = eval_expression(&arguments[2], local_context).try_into().unwrap();
let a: f32 = eval_expression(&arguments[3], local_context).try_into().unwrap();
let a = (1. * a).clamp(0., 1.);
Value::Brush(Brush::SolidColor(Color::from_hsva(h, s, v, a)))
}
BuiltinFunction::ColorScheme => local_context
.component_instance
.window_adapter()
.internal(corelib::InternalToken)
.map_or(ColorScheme::Unknown, |x| x.color_scheme())
.into(),
BuiltinFunction::SupportsNativeMenuBar => local_context
.component_instance
.window_adapter()
.internal(corelib::InternalToken)
.is_some_and(|x| x.supports_native_menu_bar())
.into(),
BuiltinFunction::SetupMenuBar => {
let component = local_context.component_instance;
let [Expression::PropertyReference(entries_nr), Expression::PropertyReference(sub_menu_nr), Expression::PropertyReference(activated_nr), Expression::ElementReference(item_tree_root), Expression::BoolLiteral(no_native), rest @ ..] =
arguments
else {
panic!("internal error: incorrect argument count to SetupMenuBar")
};
let menu_item_tree =
item_tree_root.upgrade().unwrap().borrow().enclosing_component.upgrade().unwrap();
let menu_item_tree = crate::dynamic_item_tree::make_menu_item_tree(
&menu_item_tree,
&component,
rest.first(),
);
if let Some(w) = component.window_adapter().internal(i_slint_core::InternalToken) {
if !no_native && w.supports_native_menu_bar() {
let menubar = vtable::VRc::into_dyn(menu_item_tree);
w.setup_menubar(menubar);
return Value::Void;
}
}
let (entries, sub_menu, activated) = menu_item_tree_properties(menu_item_tree);
assert_eq!(
entries_nr.element().borrow().id,
component.description.original.root_element.borrow().id,
"entries need to be in the main element"
);
local_context
.component_instance
.description
.set_binding(component.borrow(), entries_nr.name(), entries)
.unwrap();
let i = &ComponentInstance::InstanceRef(local_context.component_instance);
set_callback_handler(i, &sub_menu_nr.element(), sub_menu_nr.name(), sub_menu).unwrap();
set_callback_handler(i, &activated_nr.element(), activated_nr.name(), activated)
.unwrap();
return Value::Void;
}
BuiltinFunction::MonthDayCount => {
let m: u32 = eval_expression(&arguments[0], local_context).try_into().unwrap();
let y: i32 = eval_expression(&arguments[1], local_context).try_into().unwrap();
Value::Number(i_slint_core::date_time::month_day_count(m, y).unwrap_or(0) as f64)
}
BuiltinFunction::MonthOffset => {
let m: u32 = eval_expression(&arguments[0], local_context).try_into().unwrap();
let y: i32 = eval_expression(&arguments[1], local_context).try_into().unwrap();
Value::Number(i_slint_core::date_time::month_offset(m, y) as f64)
}
BuiltinFunction::FormatDate => {
let f: SharedString = eval_expression(&arguments[0], local_context).try_into().unwrap();
let d: u32 = eval_expression(&arguments[1], local_context).try_into().unwrap();
let m: u32 = eval_expression(&arguments[2], local_context).try_into().unwrap();
let y: i32 = eval_expression(&arguments[3], local_context).try_into().unwrap();
Value::String(i_slint_core::date_time::format_date(&f, d, m, y))
}
BuiltinFunction::DateNow => Value::Model(ModelRc::new(VecModel::from(
i_slint_core::date_time::date_now()
.into_iter()
.map(|x| Value::Number(x as f64))
.collect::<Vec<_>>(),
))),
BuiltinFunction::ValidDate => {
let d: SharedString = eval_expression(&arguments[0], local_context).try_into().unwrap();
let f: SharedString = eval_expression(&arguments[1], local_context).try_into().unwrap();
Value::Bool(i_slint_core::date_time::parse_date(d.as_str(), f.as_str()).is_some())
}
BuiltinFunction::ParseDate => {
let d: SharedString = eval_expression(&arguments[0], local_context).try_into().unwrap();
let f: SharedString = eval_expression(&arguments[1], local_context).try_into().unwrap();
Value::Model(ModelRc::new(
i_slint_core::date_time::parse_date(d.as_str(), f.as_str())
.map(|x| {
VecModel::from(
x.into_iter().map(|x| Value::Number(x as f64)).collect::<Vec<_>>(),
)
})
.unwrap_or_default(),
))
}
BuiltinFunction::TextInputFocused => Value::Bool(
local_context.component_instance.access_window(|window| window.text_input_focused())
as _,
),
BuiltinFunction::SetTextInputFocused => {
local_context.component_instance.access_window(|window| {
window.set_text_input_focused(
eval_expression(&arguments[0], local_context).try_into().unwrap(),
)
});
Value::Void
}
BuiltinFunction::ImplicitLayoutInfo(orient) => {
let component = local_context.component_instance;
if let [Expression::ElementReference(item)] = arguments {
generativity::make_guard!(guard);
let item = item.upgrade().unwrap();
let enclosing_component = enclosing_component_for_element(&item, component, guard);
let description = enclosing_component.description;
let item_info = &description.items[item.borrow().id.as_str()];
let item_ref =
unsafe { item_info.item_from_item_tree(enclosing_component.as_ptr()) };
let item_comp = enclosing_component.self_weak().get().unwrap().upgrade().unwrap();
let window_adapter = component.window_adapter();
item_ref
.as_ref()
.layout_info(
crate::eval_layout::to_runtime(orient),
&window_adapter,
&ItemRc::new(vtable::VRc::into_dyn(item_comp), item_info.item_index()),
)
.into()
} else {
panic!("internal error: incorrect arguments to ImplicitLayoutInfo {arguments:?}");
}
}
BuiltinFunction::ItemAbsolutePosition => {
if arguments.len() != 1 {
panic!("internal error: incorrect argument count to ItemAbsolutePosition")
}
let component = local_context.component_instance;
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 description = enclosing_component.description;
let item_info = &description.items[item.borrow().id.as_str()];
let item_comp = enclosing_component.self_weak().get().unwrap().upgrade().unwrap();
let item_rc = corelib::items::ItemRc::new(
vtable::VRc::into_dyn(item_comp),
item_info.item_index(),
);
item_rc.map_to_window(Default::default()).to_untyped().into()
} else {
panic!("internal error: argument to SetFocusItem must be an element")
}
}
BuiltinFunction::RegisterCustomFontByPath => {
if arguments.len() != 1 {
panic!("internal error: incorrect argument count to RegisterCustomFontByPath")
}
let component = local_context.component_instance;
if let Value::String(s) = eval_expression(&arguments[0], local_context) {
if let Some(err) = component
.window_adapter()
.renderer()
.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");
}
}
BuiltinFunction::RegisterCustomFontByMemory | BuiltinFunction::RegisterBitmapFont => {
unimplemented!()
}
BuiltinFunction::Translate => {
let original: SharedString =
eval_expression(&arguments[0], local_context).try_into().unwrap();
let context: SharedString =
eval_expression(&arguments[1], local_context).try_into().unwrap();
let domain: SharedString =
eval_expression(&arguments[2], local_context).try_into().unwrap();
let args = eval_expression(&arguments[3], local_context);
let Value::Model(args) = args else { panic!("Args to translate not a model {args:?}") };
struct StringModelWrapper(ModelRc<Value>);
impl corelib::translations::FormatArgs for StringModelWrapper {
type Output<'a> = SharedString;
fn from_index(&self, index: usize) -> Option<SharedString> {
self.0.row_data(index).map(|x| x.try_into().unwrap())
}
}
Value::String(corelib::translations::translate(
&original,
&context,
&domain,
&StringModelWrapper(args),
eval_expression(&arguments[4], local_context).try_into().unwrap(),
&SharedString::try_from(eval_expression(&arguments[5], local_context)).unwrap(),
))
}
BuiltinFunction::Use24HourFormat => Value::Bool(corelib::date_time::use_24_hour_format()),
BuiltinFunction::UpdateTimers => {
crate::dynamic_item_tree::update_timers(local_context.component_instance);
Value::Void
}
BuiltinFunction::DetectOperatingSystem => i_slint_core::detect_operating_system().into(),
// start and stop are unreachable because they are lowered to simple assignment of running
BuiltinFunction::StartTimer => unreachable!(),
BuiltinFunction::StopTimer => unreachable!(),
BuiltinFunction::RestartTimer => {
if let [Expression::ElementReference(timer_element)] = arguments {
crate::dynamic_item_tree::restart_timer(
timer_element.clone(),
local_context.component_instance,
);
Value::Void
} else {
panic!("internal error: argument to RestartTimer must be an element")
}
}
BuiltinFunction::OpenUrl => {
let url: SharedString =
eval_expression(&arguments[0], local_context).try_into().unwrap();
corelib::open_url(&url);
Value::Void
}
}
}
fn call_item_member_function(nr: &NamedReference, local_context: &mut EvalLocalContext) -> Value {
let component = local_context.component_instance;
let elem = nr.element();
let name = nr.name().as_str();
generativity::make_guard!(guard);
let enclosing_component = enclosing_component_for_element(&elem, component, guard);
let description = enclosing_component.description;
let item_info = &description.items[elem.borrow().id.as_str()];
let item_ref = unsafe { item_info.item_from_item_tree(enclosing_component.as_ptr()) };
let item_comp = enclosing_component.self_weak().get().unwrap().upgrade().unwrap();
let item_rc =
corelib::items::ItemRc::new(vtable::VRc::into_dyn(item_comp), item_info.item_index());
let window_adapter = component.window_adapter();
// TODO: Make this generic through RTTI
if let Some(textinput) = ItemRef::downcast_pin::<corelib::items::TextInput>(item_ref) {
match name {
"select-all" => textinput.select_all(&window_adapter, &item_rc),
"clear-selection" => textinput.clear_selection(&window_adapter, &item_rc),
"cut" => textinput.cut(&window_adapter, &item_rc),
"copy" => textinput.copy(&window_adapter, &item_rc),
"paste" => textinput.paste(&window_adapter, &item_rc),
_ => panic!("internal: Unknown member function {name} called on TextInput"),
}
} else if let Some(s) = ItemRef::downcast_pin::<corelib::items::SwipeGestureHandler>(item_ref) {
match name {
"cancel" => s.cancel(&window_adapter, &item_rc),
_ => panic!("internal: Unknown member function {name} called on SwipeGestureHandler"),
}
} else if let Some(s) = ItemRef::downcast_pin::<corelib::items::ContextMenu>(item_ref) {
match name {
"close" => s.close(&window_adapter, &item_rc),
"is-open" => return Value::Bool(s.is_open(&window_adapter, &item_rc)),
_ => {
panic!("internal: Unknown member function {name} called on ContextMenu")
}
}
} else {
panic!(
"internal error: member function {name} called on element that doesn't have it: {}",
elem.borrow().original_name()
)
}
Value::Void
}
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,
&ComponentInstance::InstanceRef(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.description.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.description.items[element.borrow().id.as_str()];
let item =
unsafe { item_info.item_from_item_tree(enclosing_component.as_ptr()) };
let p = &item_info.rtti.properties[nr.name().as_str()];
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_string(), r);
eval_assignment(base, '=', Value::Struct(o), local_context)
}
}
Expression::RepeaterModelReference { element } => {
let element = element.upgrade().unwrap();
let component_instance = local_context.component_instance;
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_item_tree::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)) => {
if index >= 0. && (index as usize) < model.row_count() {
let index = index as usize;
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.description.custom_properties.get(name) {
return unsafe {
x.prop.get(Pin::new_unchecked(&*enclosing_component.as_ptr().add(x.offset)))
};
} else if enclosing_component.description.original.is_global() {
return Err(());
}
};
let item_info = enclosing_component
.description
.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_item_tree(enclosing_component.as_ptr()) };
Ok(item_info.rtti.properties.get(name).ok_or(())?.get(item))
}
ComponentInstance::GlobalComponent(glob) => glob.as_ref().get_property(name),
}
}
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_item_tree::animation_for_property(
enclosing_component,
&b.borrow().animation,
),
None => {
crate::dynamic_item_tree::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.description.custom_properties.get(name) {
if let Some(orig_decl) = enclosing_component
.description
.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.description.original.is_global() {
return Err(SetPropertyError::NoSuchProperty);
}
};
let item_info = &enclosing_component.description.items[element.borrow().id.as_str()];
let item = unsafe { item_info.item_from_item_tree(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::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::Rem
| 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(s) => {
matches!(value, Value::Struct(str) if str.iter().all(|(k, v)| s.fields.get(k).is_some_and(|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(_)),
Type::ComponentFactory => matches!(value, Value::ComponentFactory(_)),
}
}
pub(crate) fn invoke_callback(
component_instance: &ComponentInstance,
element: &ElementRc,
callback_name: &SmolStr,
args: &[Value],
) -> Option<Value> {
generativity::make_guard!(guard);
match enclosing_component_instance_for_element(element, component_instance, guard) {
ComponentInstance::InstanceRef(enclosing_component) => {
let description = enclosing_component.description;
let element = element.borrow();
if element.id == element.enclosing_component.upgrade().unwrap().root_element.borrow().id
{
if let Some(callback_offset) = description.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(callback)) = description
.original
.root_element
.borrow()
.property_declarations
.get(callback_name)
.map(|d| &d.property_type)
{
// If the callback was not set, 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(&callback.return_type)
} else {
res
});
} else if enclosing_component.description.original.is_global() {
return None;
}
};
let item_info = &description.items[element.id.as_str()];
let item = unsafe { item_info.item_from_item_tree(enclosing_component.as_ptr()) };
item_info
.rtti
.callbacks
.get(callback_name.as_str())
.map(|callback| callback.call(item, args))
}
ComponentInstance::GlobalComponent(global) => {
Some(global.as_ref().invoke_callback(callback_name, args).unwrap())
}
}
}
pub(crate) fn set_callback_handler(
component_instance: &ComponentInstance,
element: &ElementRc,
callback_name: &str,
handler: CallbackHandler,
) -> Result<(), ()> {
generativity::make_guard!(guard);
match enclosing_component_instance_for_element(element, component_instance, guard) {
ComponentInstance::InstanceRef(enclosing_component) => {
let description = enclosing_component.description;
let element = element.borrow();
if element.id == element.enclosing_component.upgrade().unwrap().root_element.borrow().id
{
if let Some(callback_offset) = description.custom_callbacks.get(callback_name) {
let callback = callback_offset.apply(&*enclosing_component.instance);
callback.set_handler(handler);
return Ok(());
} else if enclosing_component.description.original.is_global() {
return Err(());
}
};
let item_info = &description.items[element.id.as_str()];
let item = unsafe { item_info.item_from_item_tree(enclosing_component.as_ptr()) };
if let Some(callback) = item_info.rtti.callbacks.get(callback_name) {
callback.set_handler(item, handler);
Ok(())
} else {
Err(())
}
}
ComponentInstance::GlobalComponent(global) => {
global.as_ref().set_callback_handler(callback_name, handler)
}
}
}
/// Invoke the function.
///
/// Return None if the function don't exist
pub(crate) fn call_function(
component_instance: &ComponentInstance,
element: &ElementRc,
function_name: &str,
args: Vec<Value>,
) -> Option<Value> {
generativity::make_guard!(guard);
match enclosing_component_instance_for_element(element, component_instance, guard) {
ComponentInstance::InstanceRef(c) => {
let mut ctx = EvalLocalContext::from_function_arguments(c, args);
eval_expression(
&element.borrow().bindings.get(function_name)?.borrow().expression,
&mut ctx,
)
.into()
}
ComponentInstance::GlobalComponent(g) => g.as_ref().eval_function(function_name, args).ok(),
}
}
/// 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.description.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());
// Safety: this is the only place we use this 'static lifetime in this function and nothing is returned with it
// For some reason we can't make a new guard here because the compiler thinks we are returning that
// (it assumes that the 'id must outlive 'a , which is not true)
let static_guard = unsafe { generativity::Guard::new(generativity::Id::<'static>::new()) };
let parent_instance = component.parent_instance(static_guard).unwrap();
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, 'new_id>(
element: &'a ElementRc,
component_instance: &ComponentInstance<'a, '_>,
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() && !Rc::ptr_eq(enclosing, &component.description.original) {
let root = component.toplevel_instance(guard);
ComponentInstance::GlobalComponent(
root.description
.extra_data_offset
.apply(root.instance.get_ref())
.globals
.get()
.unwrap()
.get(enclosing.root_element.borrow().id.as_str())
.unwrap(),
)
} else {
ComponentInstance::InstanceRef(enclosing_component_for_element(
element, *component, guard,
))
}
}
ComponentInstance::GlobalComponent(global) => {
//assert!(Rc::ptr_eq(enclosing, &global.component));
ComponentInstance::GlobalComponent(global.clone())
}
}
}
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 evaluate 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 | Type::Rem => {
Value::Number(0.)
}
Type::Image => Value::Image(Default::default()),
Type::Bool => Value::Bool(false),
Type::Callback { .. } => Value::Void,
Type::Struct(s) => Value::Struct(
s.fields
.iter()
.map(|(n, t)| (n.to_string(), default_value_for_type(t)))
.collect::<Struct>(),
),
Type::Array(_) | Type::Model => Value::Model(Default::default()),
Type::Percent => Value::Number(0.),
Type::Enumeration(e) => Value::EnumerationValue(
e.name.to_string(),
e.values.get(e.default_value).unwrap().to_string(),
),
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::ComponentFactory => Value::ComponentFactory(Default::default()),
Type::InferredProperty
| Type::InferredCallback
| Type::ElementReference
| Type::Function { .. } => {
panic!("There can't be such property")
}
}
}
fn menu_item_tree_properties(
context_menu_item_tree: vtable::VRc<i_slint_core::menus::MenuVTable, MenuFromItemTree>,
) -> (Box<dyn Fn() -> Value>, CallbackHandler, CallbackHandler) {
let context_menu_item_tree_ = context_menu_item_tree.clone();
let entries = Box::new(move || {
let mut entries = SharedVector::default();
context_menu_item_tree_.sub_menu(None, &mut entries);
Value::Model(ModelRc::new(VecModel::from(
entries.into_iter().map(Value::from).collect::<Vec<_>>(),
)))
});
let context_menu_item_tree_ = context_menu_item_tree.clone();
let sub_menu = Box::new(move |args: &[Value]| -> Value {
let mut entries = SharedVector::default();
context_menu_item_tree_.sub_menu(Some(&args[0].clone().try_into().unwrap()), &mut entries);
Value::Model(ModelRc::new(VecModel::from(
entries.into_iter().map(Value::from).collect::<Vec<_>>(),
)))
});
let activated = Box::new(move |args: &[Value]| -> Value {
context_menu_item_tree.activate(&args[0].clone().try_into().unwrap());
Value::Void
});
(entries, sub_menu, activated)
}