slint/internal/interpreter/eval.rs
Simon Hausmann 9504a4f090 Fix popupwindow.close() not working for the interpreter when called from other component
The instance passed to close_popup() was not the same as the one passed
to show_popup(), when called from another component. Fix this by
determining the instance in BuiltinFunction::ClosePopupWindow the same
way as in BuiltinFunction::ShowPopupWindow.

This issue was covered by popup_window_close.slint's JS test, but it was
commented out because we used to run the Node.js tests on Linux with Qt,
for which the popup testing doesn't work the same way in terms of
synthetic event delivery.

We decided to change that, also in light of the nodejs binaries we
upload to the NPM registry also being built without Qt support anyway.

This permits running additional popup window tests, providing test
coverage for the interpreter, some of which needed light syntax fixes.

Co-authored-by: Olivier Goffart <olivier.goffart@slint.dev>

Fixes #7318
2025-01-10 18:27:14 +01:00

1916 lines
86 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::InstanceRef;
use core::pin::Pin;
use corelib::graphics::{GradientStop, LinearGradientBrush, PathElement, RadialGradientBrush};
use corelib::items::{ColorScheme, ItemRef, MenuEntry, PropertyAnimation};
use corelib::model::{Model, ModelExt, ModelRc, VecModel};
use corelib::rtti::AnimatedBindingKind;
use corelib::window::{Menu, MenuVTable};
use corelib::{Brush, Color, PathData, SharedString, SharedVector};
use i_slint_compiler::expression_tree::{
BuiltinFunction, 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 ());
}
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(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: 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.as_str().into()),
Expression::NumberLiteral(n, unit) => Value::Number(unit.normalize(*n)),
Expression::BoolLiteral(b) => Value::Bool(*b),
Expression::CallbackReference { .. } => panic!("callback in expression"),
Expression::FunctionReference { .. } => panic!("function 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,
crate::dynamic_item_tree::SPECIAL_PROPERTY_INDEX,
)
.unwrap(),
Expression::RepeaterModelReference { element } => load_property_helper(&local_context.component_instance,
&element.upgrade().unwrap().borrow().base_type.as_component().root_element,
crate::dynamic_item_tree::SPECIAL_PROPERTY_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)) => {
model.row_data_tracked(index 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::format!("{}", n as f32))
}
(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::FunctionReference(nr, _) => {
let args = arguments.iter().map(|e| eval_expression(e, local_context)).collect::<Vec<_>>();
call_function(&local_context.component_instance, &nr.element(), nr.name(), args).unwrap()
}
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(f, _) => call_builtin_function(f.clone(), arguments, local_context),
_ => panic!("call of something not a callback: {function:?}"),
}
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::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(&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())
}
}
fn call_builtin_function(
f: BuiltinFunction,
arguments: &[Expression],
local_context: &mut EvalLocalContext,
) -> Value {
match f {
BuiltinFunction::GetWindowScaleFactor => match local_context.component_instance {
ComponentInstance::InstanceRef(component) => {
Value::Number(component.access_window(|window| window.scale_factor()) as _)
}
ComponentInstance::GlobalComponent(_) => {
panic!("Cannot get the window from a global component")
}
},
BuiltinFunction::GetWindowDefaultFontSize => match local_context.component_instance {
ComponentInstance::InstanceRef(component) => {
Value::Number(component.access_window(|window| {
window.window_item().unwrap().as_pin_ref().default_font_size().get()
}) as _)
}
ComponentInstance::GlobalComponent(_) => {
panic!("Cannot get the window from a global component")
}
},
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();
corelib::debug_log!("{}", 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::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::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 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,
)
});
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 = 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 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,
)
});
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 = 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();
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 = 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();
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 => {
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 = match local_context.component_instance {
ComponentInstance::InstanceRef(c) => c,
ComponentInstance::GlobalComponent(_) => {
panic!("Cannot show popup from a global component")
}
};
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_rc = corelib::items::ItemRc::new(
vtable::VRc::into_dyn(item_comp),
item_info.item_index(),
);
if component.access_window(|window| window.show_native_popup_menu(&item_rc, position)) {
return Value::Void;
}
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(),
);
inst.run_setup_code();
generativity::make_guard!(guard);
let inst_ref = inst.unerase(guard);
let entries = eval_expression(entries, local_context);
compiled.set_property(inst_ref.borrow(), "entries", entries).unwrap();
let item_rc_ = item_rc.clone();
compiled
.set_callback_handler(
inst_ref.borrow(),
"sub-menu",
Box::new(move |args: &[Value]| -> Value {
item_rc_
.downcast::<corelib::items::ContextMenu>()
.unwrap()
.sub_menu
.call(&(args[0].clone().try_into().unwrap(),))
.into()
}),
)
.unwrap();
let item_rc_ = item_rc.clone();
compiled
.set_callback_handler(
inst_ref.borrow(),
"activated",
Box::new(move |args: &[Value]| -> Value {
item_rc_
.downcast::<corelib::items::ContextMenu>()
.unwrap()
.activated
.call(&(args[0].clone().try_into().unwrap(),))
.into()
}),
)
.unwrap();
component.access_window(|window| {
window.show_popup(
&vtable::VRc::into_dyn(inst),
position,
corelib::items::PopupClosePolicy::CloseOnClickOutside,
&item_rc,
)
});
Value::Void
}
BuiltinFunction::SetSelectionOffsets => {
if arguments.len() != 3 {
panic!("internal error: incorrect argument count to select range function call")
}
let component = match local_context.component_instance {
ComponentInstance::InstanceRef(c) => c,
ComponentInstance::GlobalComponent(_) => {
panic!("Cannot invoke member function on item from a global component")
}
};
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::ItemMemberFunction(name) => {
if arguments.len() != 1 {
panic!("internal error: incorrect argument count to item member function call")
}
let component = match local_context.component_instance {
ComponentInstance::InstanceRef(c) => c,
ComponentInstance::GlobalComponent(_) => {
panic!("Cannot invoke member function on item from a global component")
}
};
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)
{
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 {
panic!(
"internal error: member function {name} called on element that doesn't have it: {}",
elem.borrow().original_name()
)
}
Value::Void
} else {
panic!("internal error: argument to set-selection-offsetsAll must be an element")
}
}
BuiltinFunction::ItemFontMetrics => {
if arguments.len() != 1 {
panic!(
"internal error: incorrect argument count to item font metrics function call"
)
}
let component = match local_context.component_instance {
ComponentInstance::InstanceRef(c) => c,
ComponentInstance::GlobalComponent(_) => {
panic!(
"Cannot invoke item font metrics function on item from a global component"
)
}
};
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 window_adapter = component.window_adapter();
let metrics =
i_slint_core::items::slint_text_item_fontmetrics(&window_adapter, item_ref);
metrics.into()
} else {
panic!("internal error: argument to set-selection-offsetsAll 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::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 => match local_context.component_instance {
ComponentInstance::InstanceRef(component) => component
.window_adapter()
.internal(corelib::InternalToken)
.map_or(ColorScheme::Unknown, |x| x.color_scheme())
.into(),
ComponentInstance::GlobalComponent(_) => {
panic!("Cannot get the window from a global component")
}
},
BuiltinFunction::SupportsNativeMenuBar => match local_context.component_instance {
ComponentInstance::InstanceRef(component) => component
.window_adapter()
.internal(corelib::InternalToken)
.map_or(false, |x| x.supports_native_menu_bar())
.into(),
ComponentInstance::GlobalComponent(_) => {
panic!("Cannot get the window from a global component")
}
},
BuiltinFunction::SetupNativeMenuBar => {
let ComponentInstance::InstanceRef(component) = local_context.component_instance else {
panic!("SetupNativeMenuBar from a global");
};
let [entries, Expression::CallbackReference(sub_menu, _), Expression::CallbackReference(activated, _)] =
arguments
else {
panic!("internal error: incorrect arguments to SetupNativeMenuBar")
};
if let Some(w) = component.window_adapter().internal(i_slint_core::InternalToken) {
if w.supports_native_menu_bar() {
w.setup_menubar(vtable::VBox::new(MenuWrapper {
entries: entries.clone(),
sub_menu: sub_menu.clone(),
activated: activated.clone(),
item_tree: component.self_weak().get().unwrap().clone(),
}));
}
}
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 => match local_context.component_instance {
ComponentInstance::InstanceRef(component) => {
Value::Bool(component.access_window(|window| window.text_input_focused()) as _)
}
ComponentInstance::GlobalComponent(_) => {
panic!("Cannot get the window from a global component")
}
},
BuiltinFunction::SetTextInputFocused => match local_context.component_instance {
ComponentInstance::InstanceRef(component) => {
component.access_window(|window| {
window.set_text_input_focused(
eval_expression(&arguments[0], local_context).try_into().unwrap(),
)
});
Value::Void
}
ComponentInstance::GlobalComponent(_) => {
panic!("Cannot get the window from a global component")
}
},
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 {
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 window_adapter = component.window_adapter();
item_ref
.as_ref()
.layout_info(crate::eval_layout::to_runtime(orient), &window_adapter)
.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 = match local_context.component_instance {
ComponentInstance::InstanceRef(c) => c,
ComponentInstance::GlobalComponent(_) => {
panic!("Cannot access the implicit item size from a global component")
}
};
if let Expression::ElementReference(item) = &arguments[0] {
generativity::make_guard!(guard);
let item = item.upgrade().unwrap();
let enclosing_component = enclosing_component_for_element(&item, component, guard);
let 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 = match local_context.component_instance {
ComponentInstance::InstanceRef(c) => c,
ComponentInstance::GlobalComponent(_) => {
panic!("Cannot access the implicit item size from a global component")
}
};
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 => match local_context.component_instance {
ComponentInstance::InstanceRef(component) => {
crate::dynamic_item_tree::update_timers(component);
Value::Void
}
ComponentInstance::GlobalComponent(_) => {
panic!("timer in global?")
}
},
}
}
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.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 = 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_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)) => {
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.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) => 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_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).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(_)),
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())
}
}
}
/// 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")
}
}
}
pub struct MenuWrapper {
entries: Expression,
sub_menu: NamedReference,
activated: NamedReference,
item_tree: crate::dynamic_item_tree::ErasedItemTreeBoxWeak,
}
i_slint_core::MenuVTable_static!(static MENU_WRAPPER_VTABLE for MenuWrapper);
impl i_slint_core::window::Menu for MenuWrapper {
fn sub_menu(&self, parent: Option<&MenuEntry>, result: &mut SharedVector<MenuEntry>) {
let Some(s) = self.item_tree.upgrade() else { return };
generativity::make_guard!(guard);
let compo_box = s.unerase(guard);
let instance_ref = compo_box.borrow_instance();
let res = match parent {
None => eval_expression(
&self.entries,
&mut EvalLocalContext::from_component_instance(instance_ref),
),
Some(parent) => {
let instance_ref = ComponentInstance::InstanceRef(instance_ref);
invoke_callback(
&instance_ref,
&self.sub_menu.element(),
self.sub_menu.name(),
&[parent.clone().into()],
)
.unwrap()
}
};
let Value::Model(model) = res else { panic!("Not a model of menu entries {res:?}") };
*result = model.iter().map(|v| v.try_into().unwrap()).collect();
}
fn activate(&self, entry: &MenuEntry) {
let Some(s) = self.item_tree.upgrade() else { return };
generativity::make_guard!(guard);
let compo_box = s.unerase(guard);
let instance_ref = compo_box.borrow_instance();
let instance_ref = ComponentInstance::InstanceRef(instance_ref);
invoke_callback(
&instance_ref,
&self.activated.element(),
self.activated.name(),
&[entry.clone().into()],
)
.unwrap();
}
}