1use crate::Value;
11use crate::globals::{GlobalInstance, GlobalStorage};
12use crate::instance::SubComponentInstance;
13use i_slint_compiler::diagnostics::SourceLocation;
14use i_slint_compiler::expression_tree::{BuiltinFunction, MinMaxOp};
15use i_slint_compiler::langtype::{ConstantExpression, Type};
16use i_slint_compiler::llr::{self, Expression, LocalMemberIndex, MemberReference};
17use i_slint_core::graphics::{
18 Brush, ConicGradientBrush, GradientStop, LinearGradientBrush, RadialGradientBrush,
19};
20use i_slint_core::model::{Model, ModelExt, ModelRc, SharedVectorModel};
21use i_slint_core::{Color, SharedString, SharedVector};
22use smol_str::SmolStr;
23use std::collections::HashMap;
24use std::pin::Pin;
25use std::rc::{Rc, Weak};
26
27pub struct EvalContext {
29 pub current: Option<Pin<Rc<SubComponentInstance>>>,
32 pub compilation_unit: Rc<llr::CompilationUnit>,
35 pub globals: Weak<GlobalStorage>,
37 pub locals: HashMap<SmolStr, Value>,
39 pub function_arguments: Vec<Value>,
41 pub function_arg_types: Vec<Type>,
44 pub return_value: Option<Value>,
46}
47
48impl EvalContext {
49 pub fn new(current: Pin<Rc<SubComponentInstance>>) -> Self {
52 let globals = current
53 .root
54 .get()
55 .and_then(|w| w.upgrade())
56 .map(|inst| Rc::downgrade(&inst.globals))
57 .unwrap_or_default();
58 Self {
59 compilation_unit: current.compilation_unit.clone(),
60 current: Some(current),
61 globals,
62 locals: HashMap::new(),
63 function_arguments: Vec::new(),
64 function_arg_types: Vec::new(),
65 return_value: None,
66 }
67 }
68
69 pub fn for_global(globals: Weak<GlobalStorage>, cu: Rc<llr::CompilationUnit>) -> Self {
71 Self {
72 current: None,
73 compilation_unit: cu,
74 globals,
75 locals: HashMap::new(),
76 function_arguments: Vec::new(),
77 function_arg_types: Vec::new(),
78 return_value: None,
79 }
80 }
81
82 pub fn with_arguments(current: Pin<Rc<SubComponentInstance>>, args: Vec<Value>) -> Self {
83 let mut ctx = Self::new(current);
84 ctx.function_arguments = args;
85 ctx
86 }
87}
88
89fn root_instance(
92 ctx: &EvalContext,
93) -> Option<vtable::VRc<i_slint_core::item_tree::ItemTreeVTable, crate::instance::Instance>> {
94 match ctx.current.as_ref() {
95 Some(c) => c.root.get()?.upgrade(),
96 None => ctx.globals.upgrade()?.root.get()?.upgrade(),
97 }
98}
99
100pub(crate) fn try_walk_parent(
106 start: &Pin<Rc<SubComponentInstance>>,
107 level: usize,
108) -> Option<Pin<Rc<SubComponentInstance>>> {
109 let mut current = start.clone();
110 for _ in 0..level {
111 current = Pin::new(current.parent.upgrade()?);
112 }
113 Some(current)
114}
115
116pub(crate) fn walk_parent(
120 start: &Pin<Rc<SubComponentInstance>>,
121 level: usize,
122) -> Pin<Rc<SubComponentInstance>> {
123 try_walk_parent(start, level).expect("parent vanished during evaluation")
124}
125
126impl i_slint_compiler::llr::TypeResolutionContext for EvalContext {
127 fn property_ty(&self, mr: &MemberReference) -> &Type {
128 let cu = &self.compilation_unit;
129 match mr {
130 MemberReference::Global { global_index, member } => {
131 let g = &cu.globals[*global_index];
132 match member {
133 LocalMemberIndex::Property(idx) => &g.properties[*idx].ty,
134 LocalMemberIndex::Function(idx) => &g.functions[*idx].ret_ty,
135 LocalMemberIndex::Callback(idx) => &g.callbacks[*idx].ty,
138 LocalMemberIndex::Native { .. } | LocalMemberIndex::Timer(_) => &Type::Invalid,
139 }
140 }
141 MemberReference::Relative { parent_level, local_reference } => {
142 let current =
143 self.current.as_ref().expect("property_ty needs a sub-component context");
144 let Some(sub) = try_walk_parent(current, *parent_level) else {
148 return &Type::Invalid;
149 };
150 let mut sc_idx = sub.sub_component_idx;
151 for i in &local_reference.sub_component_path {
152 sc_idx = cu.sub_components[sc_idx].sub_components[*i].ty;
153 }
154 let sc = &cu.sub_components[sc_idx];
155 match &local_reference.reference {
156 LocalMemberIndex::Property(idx) => &sc.properties[*idx].ty,
157 LocalMemberIndex::Function(idx) => &sc.functions[*idx].ret_ty,
158 LocalMemberIndex::Callback(idx) => &sc.callbacks[*idx].ty,
159 LocalMemberIndex::Timer(_) => &Type::Invalid,
161 LocalMemberIndex::Native { item_index, prop_name, .. } => {
162 if prop_name == "elements" {
163 return &Type::PathData;
165 }
166 sc.items[*item_index]
167 .ty
168 .lookup_property(prop_name)
169 .unwrap_or(&Type::Invalid)
170 }
171 }
172 }
173 }
174 }
175
176 fn arg_type(&self, index: usize) -> &Type {
177 self.function_arg_types.get(index).unwrap_or(&Type::Invalid)
178 }
179}
180
181pub(crate) fn walk_sub_path(
183 mut current: Pin<Rc<SubComponentInstance>>,
184 path: &[llr::SubComponentInstanceIdx],
185) -> Pin<Rc<SubComponentInstance>> {
186 for &idx in path {
187 let next = current.sub_components[idx].clone();
188 current = next;
189 }
190 current
191}
192
193pub(crate) fn try_walk_to(
197 ctx: &EvalContext,
198 parent_level: usize,
199 local_reference: &llr::LocalMemberReference,
200) -> Option<Pin<Rc<SubComponentInstance>>> {
201 let base = try_walk_parent(ctx.current.as_ref()?, parent_level)?;
202 Some(walk_sub_path(base, &local_reference.sub_component_path))
203}
204
205pub(crate) fn find_flat_item_index(
207 item_table: &[Option<(
208 Box<[i_slint_compiler::llr::SubComponentInstanceIdx]>,
209 i_slint_compiler::llr::ItemInstanceIdx,
210 )>],
211 path: &[i_slint_compiler::llr::SubComponentInstanceIdx],
212 item_index: i_slint_compiler::llr::ItemInstanceIdx,
213) -> Option<usize> {
214 item_table.iter().position(|entry| {
215 entry.as_ref().is_some_and(|(p, i)| p.as_ref() == path && *i == item_index)
216 })
217}
218
219fn load_local(instance: &SubComponentInstance, member: &LocalMemberIndex) -> Value {
220 match member {
221 LocalMemberIndex::Property(idx) => Pin::as_ref(&instance.properties[*idx]).get(),
222 LocalMemberIndex::Native { item_index, prop_name, .. } => {
223 Pin::as_ref(&instance.items[*item_index]).get_property(prop_name).unwrap_or(Value::Void)
224 }
225 LocalMemberIndex::Callback(_)
226 | LocalMemberIndex::Function(_)
227 | LocalMemberIndex::Timer(_) => {
228 panic!("load_local called on callback/function/timer reference")
229 }
230 }
231}
232
233fn eval_array_row_predicate(
239 arg_name: &SmolStr,
240 predicate: &Expression,
241 ctx: &mut EvalContext,
242 row_value: Value,
243) -> bool {
244 let previous = ctx.locals.insert(arg_name.clone(), row_value);
245 let result = eval_expression(ctx, predicate).try_into().unwrap();
246 match previous {
247 Some(prev) => {
248 ctx.locals.insert(arg_name.clone(), prev);
249 }
250 None => {
251 ctx.locals.remove(arg_name);
252 }
253 }
254 result
255}
256
257fn set_maybe_animated(
259 prop: Pin<&i_slint_core::Property<Value>>,
260 ty: &Type,
261 value: Value,
262 animation: Option<i_slint_core::items::PropertyAnimation>,
263) {
264 match animation {
265 Some(anim) => match crate::bindings::animated_value_map(ty) {
266 Some(map) => prop.set_animated_value_with_map(value, anim, map),
267 None => prop.set_animated_value(value, anim),
268 },
269 None => prop.set(value),
270 }
271}
272
273fn store_local(
274 instance: &SubComponentInstance,
275 member: &LocalMemberIndex,
276 value: Value,
277 animation: Option<i_slint_core::items::PropertyAnimation>,
278) {
279 match member {
280 LocalMemberIndex::Property(idx) => {
281 let sc = &instance.compilation_unit.sub_components[instance.sub_component_idx];
282 set_maybe_animated(
283 Pin::as_ref(&instance.properties[*idx]),
284 &sc.properties[*idx].ty,
285 value,
286 animation,
287 );
288 }
289 LocalMemberIndex::Native { item_index, prop_name, .. } => {
290 let _ =
291 Pin::as_ref(&instance.items[*item_index]).set_property(prop_name, value, animation);
292 }
293 LocalMemberIndex::Callback(_)
294 | LocalMemberIndex::Function(_)
295 | LocalMemberIndex::Timer(_) => {
296 panic!("store_local called on callback/function/timer reference")
297 }
298 }
299}
300
301fn walk_to_target_with_animation(
308 start: Pin<Rc<SubComponentInstance>>,
309 local_reference: &llr::LocalMemberReference,
310) -> (Pin<Rc<SubComponentInstance>>, Option<i_slint_core::items::PropertyAnimation>) {
311 let cu = start.compilation_unit.clone();
312 let path = &local_reference.sub_component_path;
313 let mut animation = None;
314 let mut owner = start;
315 for depth in 0..=path.len() {
316 if animation.is_none() {
317 let sc = &cu.sub_components[owner.sub_component_idx];
318 if !sc.animations.is_empty() {
319 let key = llr::LocalMemberReference {
320 sub_component_path: path[depth..].to_vec(),
321 reference: local_reference.reference.clone(),
322 };
323 if let Some(expr) = sc.animations.get(&key) {
324 animation = Some((owner.clone(), expr.clone()));
325 }
326 }
327 }
328 if let Some(&idx) = path.get(depth) {
329 let next = owner.sub_components[idx].clone();
330 owner = next;
331 }
332 }
333 let animation = animation.map(|(scope, expr)| {
334 let mut ctx = EvalContext::new(scope);
335 crate::bindings::value_to_property_animation(eval_expression(&mut ctx, &expr))
336 });
337 (owner, animation)
338}
339
340pub fn load_property(ctx: &EvalContext, mr: &MemberReference) -> Value {
341 match mr {
342 MemberReference::Global { global_index, member } => {
343 let Some(storage) = ctx.globals.upgrade() else { return Value::Void };
344 let Some(global) = storage.get(*global_index) else { return Value::Void };
345 load_global(global, member)
346 }
347 MemberReference::Relative { parent_level, local_reference } => {
348 let Some(instance) = try_walk_to(ctx, *parent_level, local_reference) else {
349 return Value::Void;
350 };
351 load_local(&instance, &local_reference.reference)
352 }
353 }
354}
355
356pub fn store_property(ctx: &EvalContext, mr: &MemberReference, value: Value) {
357 match mr {
358 MemberReference::Global { global_index, member } => {
359 let Some(storage) = ctx.globals.upgrade() else { return };
360 let Some(global) = storage.get(*global_index) else { return };
361 store_global(global, member, value);
362 }
363 MemberReference::Relative { parent_level, local_reference } => {
364 let Some(base) =
365 ctx.current.as_ref().and_then(|start| try_walk_parent(start, *parent_level))
366 else {
367 return;
368 };
369 let (instance, animation) = walk_to_target_with_animation(base, local_reference);
370 store_local(&instance, &local_reference.reference, value, animation);
371 }
372 }
373}
374
375pub fn invoke_callback(ctx: &EvalContext, mr: &MemberReference, args: &[Value]) -> Value {
376 match mr {
377 MemberReference::Global { global_index, member } => {
378 let Some(storage) = ctx.globals.upgrade() else { return Value::Void };
379 let Some(global) = storage.get(*global_index) else { return Value::Void };
380 let LocalMemberIndex::Callback(idx) = member else {
381 panic!("invoke_callback on non-callback global reference")
382 };
383 let cb = &global.compilation_unit.globals[global.global_idx].callbacks[*idx];
384 if let Some(native) = &global.native {
385 let res = native.as_ref().invoke_callback(&cb.name, args).unwrap_or(Value::Void);
386 return ensure_typed_default(res, &cb.ret_ty);
387 }
388 if let Some(tracker) = global.callback_trackers[*idx].as_ref() {
391 Pin::as_ref(tracker).get();
392 }
393 let res = Pin::as_ref(&global.callbacks[*idx]).call(args);
394 ensure_typed_default(res, &cb.ret_ty)
395 }
396 MemberReference::Relative { parent_level, local_reference } => {
397 let Some(instance) = try_walk_to(ctx, *parent_level, local_reference) else {
398 return Value::Void;
399 };
400 match &local_reference.reference {
401 LocalMemberIndex::Callback(idx) => {
402 if let Some(tracker) = instance.callback_trackers[*idx].as_ref() {
406 Pin::as_ref(tracker).get();
407 }
408 let res = Pin::as_ref(&instance.callbacks[*idx]).call(args);
409 let ret_ty = instance.compilation_unit.sub_components
410 [instance.sub_component_idx]
411 .callbacks[*idx]
412 .ret_ty
413 .clone();
414 ensure_typed_default(res, &ret_ty)
415 }
416 LocalMemberIndex::Native { item_index, prop_name, .. } => {
417 Pin::as_ref(&instance.items[*item_index])
418 .call_callback(prop_name, args)
419 .unwrap_or(Value::Void)
420 }
421 _ => panic!("invoke_callback on non-callback reference: {mr:?}"),
422 }
423 }
424 }
425}
426
427pub(crate) fn ensure_typed_default(value: Value, ret_ty: &Type) -> Value {
430 if matches!(value, Value::Void) { default_value_for_type(ret_ty) } else { value }
431}
432
433pub fn invoke_function(ctx: &EvalContext, mr: &MemberReference, args: Vec<Value>) -> Value {
434 match mr {
435 MemberReference::Global { global_index, member } => {
436 let Some(storage) = ctx.globals.upgrade() else { return Value::Void };
437 let Some(global) = storage.get(*global_index) else { return Value::Void };
438 let LocalMemberIndex::Function(idx) = member else {
439 panic!("invoke_function on non-function global reference")
440 };
441 let function = &global.compilation_unit.globals[global.global_idx].functions[*idx];
442 let code = function.code.borrow().clone();
443 let mut inner_ctx =
444 EvalContext::for_global(ctx.globals.clone(), global.compilation_unit.clone());
445 inner_ctx.function_arg_types = function.args.clone();
446 inner_ctx.function_arguments = args;
447 eval_expression(&mut inner_ctx, &code)
448 }
449 MemberReference::Relative { parent_level, local_reference } => {
450 let Some(instance) = try_walk_to(ctx, *parent_level, local_reference) else {
451 return Value::Void;
452 };
453 let LocalMemberIndex::Function(idx) = &local_reference.reference else {
454 panic!("invoke_function on non-function reference")
455 };
456 let sc = &instance.compilation_unit.sub_components[instance.sub_component_idx];
457 let function = &sc.functions[*idx];
458 let code = function.code.borrow().clone();
459 let mut inner_ctx = EvalContext::with_arguments(instance.clone(), args);
460 inner_ctx.function_arg_types = function.args.clone();
461 eval_expression(&mut inner_ctx, &code)
462 }
463 }
464}
465
466fn load_global(global: &Rc<GlobalInstance>, member: &LocalMemberIndex) -> Value {
467 match member {
468 LocalMemberIndex::Property(idx) => {
469 if let Some(native) = &global.native {
470 let g = &global.compilation_unit.globals[global.global_idx];
471 return native
472 .as_ref()
473 .get_property(&g.properties[*idx].name)
474 .unwrap_or(Value::Void);
475 }
476 Pin::as_ref(&global.properties[*idx]).get()
477 }
478 _ => panic!("load_global called on non-property"),
479 }
480}
481
482pub(crate) fn store_global(global: &Rc<GlobalInstance>, member: &LocalMemberIndex, value: Value) {
483 if let LocalMemberIndex::Property(idx) = member {
484 let g = &global.compilation_unit.globals[global.global_idx];
485 if let Some(native) = &global.native {
487 let _ = native.as_ref().set_property(&g.properties[*idx].name, value, None);
488 return;
489 }
490 set_maybe_animated(
491 Pin::as_ref(&global.properties[*idx]),
492 &g.properties[*idx].ty,
493 value,
494 None,
495 );
496 }
497}
498
499fn cast_to_path_data(ctx: &mut EvalContext, from: &Expression) -> Value {
509 use i_slint_core::graphics::PathData;
510 use i_slint_core::items::PathEvent;
511
512 match from {
513 Expression::Array { values, .. } => {
514 let elements: SharedVector<i_slint_core::graphics::PathElement> =
515 values.iter().filter_map(|e| path_element_from_expression(ctx, e)).collect();
516 Value::PathData(PathData::Elements(elements))
517 }
518 Expression::Struct { values, .. }
519 if values.contains_key("events") && values.contains_key("points") =>
520 {
521 let events_value = eval_expression(ctx, &values["events"]);
522 let points_value = eval_expression(ctx, &values["points"]);
523 let events: SharedVector<PathEvent> = match events_value {
528 Value::Model(m) => {
529 (0..m.row_count()).filter_map(|i| m.row_data(i)?.try_into().ok()).collect()
530 }
531 _ => SharedVector::default(),
532 };
533 let points: SharedVector<lyon_path::math::Point> = match points_value {
534 Value::Model(m) => {
535 (0..m.row_count()).filter_map(|i| m.row_data(i)?.try_into().ok()).collect()
536 }
537 _ => SharedVector::default(),
538 };
539 Value::PathData(PathData::Events(events, points))
540 }
541 _ => match eval_expression(ctx, from) {
542 Value::String(s) => Value::PathData(PathData::Commands(s)),
543 _ => Value::PathData(PathData::None),
544 },
545 }
546}
547
548fn path_element_from_expression(
552 ctx: &mut EvalContext,
553 expr: &Expression,
554) -> Option<i_slint_core::graphics::PathElement> {
555 use i_slint_compiler::langtype::{BuiltinStruct, StructName};
556 use i_slint_core::graphics::{
557 PathArcTo, PathCubicTo, PathElement, PathLineTo, PathMoveTo, PathQuadraticTo,
558 };
559 let Expression::Struct { ty, values } = expr else { return None };
560 let StructName::Builtin(bs) = &ty.name else { return None };
561 let get_f32 = |field: &str, ctx: &mut EvalContext| -> f32 {
562 values
563 .get(field)
564 .map(|e| eval_expression(ctx, e))
565 .and_then(|v| f64::try_from(v).ok())
566 .unwrap_or(0.0) as f32
567 };
568 let get_bool = |field: &str, ctx: &mut EvalContext| -> bool {
569 values
570 .get(field)
571 .map(|e| eval_expression(ctx, e))
572 .map(|v| matches!(v, Value::Bool(true)))
573 .unwrap_or(false)
574 };
575 Some(match bs {
576 BuiltinStruct::PathMoveTo => {
577 PathElement::MoveTo(PathMoveTo { x: get_f32("x", ctx), y: get_f32("y", ctx) })
578 }
579 BuiltinStruct::PathLineTo => {
580 PathElement::LineTo(PathLineTo { x: get_f32("x", ctx), y: get_f32("y", ctx) })
581 }
582 BuiltinStruct::PathArcTo => PathElement::ArcTo(PathArcTo {
583 x: get_f32("x", ctx),
584 y: get_f32("y", ctx),
585 radius_x: get_f32("radius-x", ctx),
586 radius_y: get_f32("radius-y", ctx),
587 x_rotation: get_f32("x-rotation", ctx),
588 large_arc: get_bool("large-arc", ctx),
589 sweep: get_bool("sweep", ctx),
590 }),
591 BuiltinStruct::PathCubicTo => PathElement::CubicTo(PathCubicTo {
592 x: get_f32("x", ctx),
593 y: get_f32("y", ctx),
594 control_1_x: get_f32("control-1-x", ctx),
595 control_1_y: get_f32("control-1-y", ctx),
596 control_2_x: get_f32("control-2-x", ctx),
597 control_2_y: get_f32("control-2-y", ctx),
598 }),
599 BuiltinStruct::PathQuadraticTo => PathElement::QuadraticTo(PathQuadraticTo {
600 x: get_f32("x", ctx),
601 y: get_f32("y", ctx),
602 control_x: get_f32("control-x", ctx),
603 control_y: get_f32("control-y", ctx),
604 }),
605 BuiltinStruct::PathClose => PathElement::Close,
606 _ => return None,
607 })
608}
609
610pub fn default_value_for_type(ty: &Type) -> Value {
613 match ty {
614 Type::Float32
615 | Type::Int32
616 | Type::Duration
617 | Type::Angle
618 | Type::PhysicalLength
619 | Type::LogicalLength
620 | Type::Rem
621 | Type::Percent
622 | Type::UnitProduct(_) => Value::Number(0.),
623 Type::String => Value::String(Default::default()),
624 Type::Color | Type::Brush => Value::Brush(Brush::default()),
625 Type::Bool => Value::Bool(false),
626 Type::Image => Value::Image(Default::default()),
627 Type::Struct(s) => Value::Struct(
628 s.fields
629 .keys()
630 .map(|k| (k.to_string(), default_value_for_struct_field(s, k)))
631 .collect(),
632 ),
633 Type::Array(_) | Type::Model => Value::Model(ModelRc::default()),
634 Type::Keys => Value::Keys(Default::default()),
635 Type::DataTransfer => Value::DataTransfer(Default::default()),
636 Type::StyledText => Value::StyledText(Default::default()),
637 Type::Enumeration(en) => {
638 let default = en.clone().default_value();
639 Value::EnumerationValue(en.name.to_string(), default.to_string())
640 }
641 Type::ComponentFactory => Value::ComponentFactory(Default::default()),
642 Type::MouseCursor => Value::MouseCursorInner(Default::default()),
643 Type::Void => Value::Void,
644 Type::Invalid
647 | Type::InferredProperty
648 | Type::InferredCallback
649 | Type::Callback(_)
650 | Type::Function(_)
651 | Type::PathData
652 | Type::Easing
653 | Type::ElementReference
654 | Type::ArrayOfU16
655 | Type::LayoutCache
656 | Type::Closure => Value::Void,
657 }
658}
659
660pub fn default_value_for_struct_field(
664 s: &i_slint_compiler::langtype::Struct,
665 field_name: &str,
666) -> Value {
667 match s.field_defaults.get(field_name) {
668 Some(expr) => eval_constant_expression(expr),
669 None => default_value_for_type(
670 s.fields.get(field_name).expect("default value requested for unknown struct field"),
671 ),
672 }
673}
674
675fn eval_constant_expression(expr: &ConstantExpression) -> Value {
678 match expr {
679 ConstantExpression::StringLiteral(s) => Value::String(s.as_str().into()),
680 ConstantExpression::NumberLiteral(n, _unit) => Value::Number(*n),
681 ConstantExpression::BoolLiteral(b) => Value::Bool(*b),
682 ConstantExpression::EnumerationValue(value) => {
683 Value::EnumerationValue(value.enumeration.name.to_string(), value.to_string())
684 }
685 ConstantExpression::Cast { from, to } => {
686 cast_constant_value(eval_constant_expression(from), to)
687 }
688 ConstantExpression::UnaryOp { sub, op } => {
689 match (eval_constant_expression(sub), op) {
691 (Value::Number(a), '+') => Value::Number(a),
692 (Value::Number(a), '-') => Value::Number(-a),
693 (Value::Bool(a), '!') => Value::Bool(!a),
694 (sub, _) => panic!("unsupported {op} {sub:?}"),
695 }
696 }
697 ConstantExpression::Struct { values, .. } => Value::Struct(
698 values
699 .iter()
700 .map(|(k, v)| (k.to_string(), eval_constant_expression(v)))
701 .collect::<crate::api::Struct>(),
702 ),
703 ConstantExpression::Array { values, .. } => {
704 Value::Model(ModelRc::new(SharedVectorModel::from(
705 values.iter().map(eval_constant_expression).collect::<SharedVector<_>>(),
706 )))
707 }
708 }
709}
710
711fn cast_constant_value(value: Value, to: &Type) -> Value {
713 match (value, to) {
714 (Value::Number(n), Type::Int32) => Value::Number(n.trunc()),
715 (Value::Number(n), Type::String) => {
716 Value::String(i_slint_core::string::shared_string_from_number(n))
717 }
718 (Value::Number(n), Type::Color) => Color::from_argb_encoded(n as u32).into(),
719 (Value::Brush(brush), Type::Color) => brush.color().into(),
720 (Value::EnumerationValue(_, val), Type::String) => Value::String(val.into()),
721 (v, _) => v,
722 }
723}
724
725pub fn eval_expression(ctx: &mut EvalContext, expression: &Expression) -> Value {
726 if let Some(r) = &ctx.return_value {
727 return r.clone();
728 }
729 match expression {
730 Expression::StringLiteral(s) => Value::String(s.as_str().into()),
731 Expression::NumberLiteral(n) => Value::Number(*n),
732 Expression::BoolLiteral(b) => Value::Bool(*b),
733 Expression::KeysLiteral(ks) => Value::Keys({
734 let mut modifiers = i_slint_core::input::KeyboardModifiers::default();
735 modifiers.alt = ks.modifiers.alt;
736 modifiers.control = ks.modifiers.control;
737 modifiers.shift = ks.modifiers.shift;
738 modifiers.meta = ks.modifiers.meta;
739 i_slint_core::input::make_keys(
740 SharedString::from(&*ks.key),
741 modifiers,
742 ks.ignore_shift,
743 ks.ignore_alt,
744 )
745 }),
746 Expression::PropertyReference(mr) => load_property(ctx, mr),
747 Expression::FunctionParameterReference { index } => ctx.function_arguments[*index].clone(),
748 Expression::StoreLocalVariable { name, value } => {
749 let v = eval_expression(ctx, value);
750 ctx.locals.insert(name.clone(), v);
751 Value::Void
752 }
753 Expression::ReadLocalVariable { name, .. } => {
754 ctx.locals.get(name).cloned().unwrap_or(Value::Void)
755 }
756 Expression::StructFieldAccess { base, name } => {
757 if let Value::Struct(s) = eval_expression(ctx, base) {
758 s.get_field(name).cloned().unwrap_or(Value::Void)
759 } else {
760 Value::Void
761 }
762 }
763 Expression::ArrayIndex { array, index } => {
764 let array_v = eval_expression(ctx, array);
765 let index = eval_expression(ctx, index);
766 match (array_v, index) {
767 (Value::Model(m), Value::Number(i)) => {
768 let idx = i as isize as usize;
769 m.row_data_tracked(idx).unwrap_or_else(|| {
770 default_value_for_type(&expression.ty(&*ctx))
773 })
774 }
775 _ => Value::Void,
776 }
777 }
778 Expression::Cast { from, to } => {
779 if matches!(to, Type::PathData) {
783 return cast_to_path_data(ctx, from);
784 }
785 let v = eval_expression(ctx, from);
786 match (v, to) {
787 (Value::Number(n), Type::Int32) => Value::Number(n.trunc()),
788 (Value::Number(n), Type::String) => {
789 Value::String(i_slint_core::string::shared_string_from_number(n))
790 }
791 (Value::Number(n), Type::Color) => Color::from_argb_encoded(n as u32).into(),
792 (Value::Brush(brush), Type::Color) => brush.color().into(),
793 (Value::EnumerationValue(_, val), Type::String) => Value::String(val.into()),
794 (v, _) => v,
795 }
796 }
797 Expression::CodeBlock(sub) => {
798 let mut v = Value::Void;
799 for e in sub {
800 v = eval_expression(ctx, e);
801 if let Some(r) = &ctx.return_value {
802 return r.clone();
803 }
804 }
805 v
806 }
807 Expression::BuiltinFunctionCall { function, arguments, source_location } => {
808 call_builtin_function(ctx, function.clone(), arguments, source_location)
809 }
810 Expression::CallBackCall { callback, arguments } => {
811 let args: Vec<Value> = arguments.iter().map(|e| eval_expression(ctx, e)).collect();
812 invoke_callback(ctx, callback, &args)
813 }
814 Expression::FunctionCall { function, arguments } => {
815 let args: Vec<Value> = arguments.iter().map(|e| eval_expression(ctx, e)).collect();
816 invoke_function(ctx, function, args)
817 }
818 Expression::ItemMemberFunctionCall { function } => call_item_member_function(ctx, function),
819 Expression::ExtraBuiltinFunctionCall { function, arguments, .. } => {
820 crate::eval_layout::call_extra_builtin(ctx, function, arguments)
821 }
822 Expression::PropertyAssignment { property, value } => {
823 let v = eval_expression(ctx, value);
824 store_property(ctx, property, v);
825 Value::Void
826 }
827 Expression::ModelDataAssignment { level, value } => {
828 let new_value = eval_expression(ctx, value);
829 if let Some(current) = ctx.current.as_ref() {
830 let Some(walker) = try_walk_parent(current, *level) else { return Value::Void };
831 if let Some((parent_weak, repeater_idx)) = walker.repeated_in.get()
832 && let Some(parent) = parent_weak.upgrade()
833 {
834 let row = walker.compilation_unit.sub_components[walker.sub_component_idx]
837 .properties
838 .iter_enumerated()
839 .find(|(_, p)| p.name.as_str() == "model_index")
840 .map(|(idx, _)| {
841 let v = std::pin::Pin::as_ref(&walker.properties[idx]).get();
842 f64::try_from(v).unwrap_or(0.) as usize
843 })
844 .unwrap_or(0);
845 let parent_pinned = std::pin::Pin::new(parent);
846 let repeater = &parent_pinned.repeaters[*repeater_idx];
847 repeater.model_set_row_data(row, new_value);
848 }
849 }
850 Value::Void
851 }
852 Expression::ArrayIndexAssignment { array, index, value } => {
853 let value = eval_expression(ctx, value);
854 let array = eval_expression(ctx, array);
855 let index = eval_expression(ctx, index);
856 if let (Value::Model(m), Value::Number(i)) = (array, index)
857 && i >= 0.0
858 {
859 let i = i.trunc() as usize;
860 if i < m.row_count() {
861 m.set_row_data(i, value);
862 }
863 }
864 Value::Void
865 }
866 Expression::SliceIndexAssignment { slice_name, index, value } => {
867 let value = eval_expression(ctx, value);
868 match ctx.locals.get_mut(slice_name.as_str()) {
869 Some(Value::ArrayOfU16(vec)) => {
870 if let Value::Number(n) = value
871 && *index < vec.len()
872 {
873 vec.make_mut_slice()[*index] = n as u16;
874 }
875 }
876 Some(Value::Model(m)) if *index < m.row_count() => {
877 m.set_row_data(*index, value);
878 }
879 _ => {}
880 }
881 Value::Void
882 }
883 Expression::BinaryExpression { lhs, rhs, op, .. } => {
884 let lhs = eval_expression(ctx, lhs);
885 match (op, &lhs) {
888 ('&', Value::Bool(false)) => return Value::Bool(false),
889 ('|', Value::Bool(true)) => return Value::Bool(true),
890 _ => {}
891 }
892 let rhs = eval_expression(ctx, rhs);
893 binary_op(*op, lhs, rhs)
894 }
895 Expression::UnaryOp { sub, op } => {
896 let sub = eval_expression(ctx, sub);
897 match (sub, op) {
898 (Value::Number(a), '+') => Value::Number(a),
899 (Value::Number(a), '-') => Value::Number(-a),
900 (Value::Bool(a), '!') => Value::Bool(!a),
901 (Value::Void, '+' | '-') => Value::Number(0.0),
904 (Value::Void, '!') => Value::Bool(true),
905 (s, o) => panic!("unsupported {o} {s:?}"),
906 }
907 }
908 Expression::ImageReference { resource_ref, nine_slice } => {
909 let mut image = load_image_reference(resource_ref);
910 if let Some(n) = nine_slice {
911 image.set_nine_slice_edges(n[0], n[1], n[2], n[3]);
912 }
913 Value::Image(image)
914 }
915 Expression::Condition { condition, true_expr, false_expr, .. } => {
916 match eval_expression(ctx, condition) {
917 Value::Bool(true) => eval_expression(ctx, true_expr),
918 Value::Bool(false) => eval_expression(ctx, false_expr),
919 _ => Value::Void,
920 }
921 }
922 Expression::Array { values, .. } => Value::Model(ModelRc::new(SharedVectorModel::from(
923 values.iter().map(|e| eval_expression(ctx, e)).collect::<SharedVector<_>>(),
924 ))),
925 Expression::Struct { values, .. } => Value::Struct(
926 values.iter().map(|(k, v)| (k.to_string(), eval_expression(ctx, v))).collect(),
927 ),
928 Expression::EasingCurve(curve) => {
929 use i_slint_compiler::expression_tree::EasingCurve as EC;
930 use i_slint_core::animations::EasingCurve as Core;
931 Value::EasingCurve(match curve {
932 EC::Linear => Core::Linear,
933 EC::EaseInElastic => Core::EaseInElastic,
934 EC::EaseOutElastic => Core::EaseOutElastic,
935 EC::EaseInOutElastic => Core::EaseInOutElastic,
936 EC::EaseInBounce => Core::EaseInBounce,
937 EC::EaseOutBounce => Core::EaseOutBounce,
938 EC::EaseInOutBounce => Core::EaseInOutBounce,
939 EC::CubicBezier(a, b, c, d) => Core::CubicBezier([*a, *b, *c, *d]),
940 EC::Spring(bounce) => Core::Spring(*bounce),
941 })
942 }
943 Expression::MouseCursor(cursor) => {
944 use i_slint_compiler::expression_tree::MouseCursorInner as Expr;
945 use i_slint_core::cursor::MouseCursorInner as Core;
946 Value::MouseCursorInner(match cursor {
947 Expr::BuiltIn(cursor) => {
948 Core::BuiltIn(eval_expression(ctx, cursor).try_into().unwrap_or_default())
949 }
950 Expr::CustomMouseCursor { image, hotspot_x, hotspot_y } => {
951 Core::CustomMouseCursor {
952 image: eval_expression(ctx, image).try_into().unwrap_or_default(),
953 hotspot_x: eval_expression(ctx, hotspot_x).try_into().unwrap_or_default(),
954 hotspot_y: eval_expression(ctx, hotspot_y).try_into().unwrap_or_default(),
955 }
956 }
957 })
958 }
959 Expression::LinearGradient { angle, stops } => {
960 let angle: f32 = eval_expression(ctx, angle).try_into().unwrap_or_default();
961 Value::Brush(Brush::LinearGradient(LinearGradientBrush::new(
962 angle,
963 eval_stops(ctx, stops),
964 )))
965 }
966 Expression::RadialGradient { stops, center, radius } => {
967 let mut g = RadialGradientBrush::new_circle(eval_stops(ctx, stops));
968 if let Some((cx, cy)) = center {
969 let cx: f32 = eval_expression(ctx, cx).try_into().unwrap_or_default();
970 let cy: f32 = eval_expression(ctx, cy).try_into().unwrap_or_default();
971 g = g.with_center(cx, cy);
972 }
973 if let Some(r) = radius {
974 let r: f32 = eval_expression(ctx, r).try_into().unwrap_or_default();
975 g = g.with_radius(r);
976 }
977 Value::Brush(Brush::RadialGradient(g))
978 }
979 Expression::ConicGradient { from_angle, stops, center } => {
980 let from_angle: f32 = eval_expression(ctx, from_angle).try_into().unwrap_or_default();
981 let mut g = ConicGradientBrush::new(from_angle, eval_stops(ctx, stops));
982 if let Some((cx, cy)) = center {
983 let cx: f32 = eval_expression(ctx, cx).try_into().unwrap_or_default();
984 let cy: f32 = eval_expression(ctx, cy).try_into().unwrap_or_default();
985 g = g.with_center(cx, cy);
986 }
987 Value::Brush(Brush::ConicGradient(g))
988 }
989 Expression::EnumerationValue(value) => {
990 Value::EnumerationValue(value.enumeration.name.to_string(), value.to_string())
991 }
992 Expression::LayoutCacheAccess {
993 layout_cache_prop,
994 index,
995 repeater_index,
996 entries_per_item,
997 } => {
998 let cache = load_property(ctx, layout_cache_prop);
999 layout_cache_access(ctx, cache, *index, repeater_index.as_deref(), *entries_per_item)
1000 }
1001 Expression::GridRepeaterCacheAccess {
1002 layout_cache_prop,
1003 index,
1004 repeater_index,
1005 stride,
1006 child_offset,
1007 inner_repeater_index,
1008 entries_per_item,
1009 } => {
1010 let cache = load_property(ctx, layout_cache_prop);
1011 let offset: usize = eval_expression(ctx, repeater_index).try_into().unwrap_or_default();
1012 let stride_val: usize = eval_expression(ctx, stride).try_into().unwrap_or_default();
1013 let inner_offset: usize = inner_repeater_index
1014 .as_deref()
1015 .map(|e| {
1016 let i: usize = eval_expression(ctx, e).try_into().unwrap_or_default();
1017 i * *entries_per_item
1018 })
1019 .unwrap_or(0);
1020 grid_repeater_cache_access(
1021 cache,
1022 *index,
1023 offset,
1024 stride_val,
1025 *child_offset,
1026 inner_offset,
1027 )
1028 }
1029 Expression::WithLayoutItemInfo {
1030 cells_variable,
1031 elements,
1032 orientation,
1033 repeated_cross_size,
1034 sub_expression,
1035 ..
1036 } => with_layout_item_info(
1037 ctx,
1038 cells_variable,
1039 elements,
1040 *orientation,
1041 repeated_cross_size.as_deref(),
1042 sub_expression,
1043 ),
1044 Expression::WithFlexboxLayoutItemInfo {
1045 cells_h_variable,
1046 cells_v_variable,
1047 flex_props_variable,
1048 elements,
1049 repeated_cross_width,
1050 sub_expression,
1051 ..
1052 } => with_flexbox_layout_item_info(
1053 ctx,
1054 cells_h_variable,
1055 cells_v_variable,
1056 flex_props_variable.as_deref(),
1057 elements,
1058 repeated_cross_width.as_deref(),
1059 sub_expression,
1060 ),
1061 Expression::WithGridInputData { cells_variable, elements, sub_expression, .. } => {
1062 with_grid_input_data(ctx, cells_variable, elements, sub_expression)
1063 }
1064 Expression::MinMax { ty: _, op, lhs, rhs } => {
1065 let Value::Number(lhs) = eval_expression(ctx, lhs) else { return Value::Void };
1066 let Value::Number(rhs) = eval_expression(ctx, rhs) else { return Value::Void };
1067 match op {
1068 MinMaxOp::Min => Value::Number(lhs.min(rhs)),
1069 MinMaxOp::Max => Value::Number(lhs.max(rhs)),
1070 }
1071 }
1072 Expression::EmptyComponentFactory => Value::ComponentFactory(Default::default()),
1073 Expression::EmptyDataTransfer => Value::DataTransfer(Default::default()),
1074 Expression::SolveFlexboxLayoutWithMeasure { .. } => {
1075 crate::eval_layout::solve_flexbox_layout_with_measure(ctx, expression)
1076 }
1077 Expression::FlexboxLayoutInfoCrossAxisWithMeasure { .. } => {
1078 crate::eval_layout::flexbox_layout_info_cross_axis_with_measure(ctx, expression)
1079 }
1080 Expression::BoxLayoutInfoOrthoWithMeasure { .. } => {
1081 crate::eval_layout::box_layout_info_ortho_with_measure(ctx, expression)
1082 }
1083 Expression::TranslationReference { .. } => {
1084 Value::String(Default::default())
1088 }
1089 Expression::Closure { .. } => unreachable!(
1090 "closures are dispatched by their consuming builtin and should not go through eval_expression"
1091 ),
1092 Expression::DebugHook { expression, id } => {
1093 if let Some(hook_value) = crate::debug_hook::trigger_debug_hook(ctx, id) {
1094 return hook_value;
1095 }
1096 eval_expression(ctx, expression)
1097 }
1098 }
1099}
1100
1101fn with_layout_item_info(
1102 ctx: &mut EvalContext,
1103 cells_variable: &str,
1104 elements: &[itertools::Either<Expression, i_slint_compiler::llr::LayoutRepeatedElement>],
1105 orientation: i_slint_compiler::layout::Orientation,
1106 repeated_cross_size: Option<&Expression>,
1107 sub_expression: &Expression,
1108) -> Value {
1109 let cross_size: Option<f32> =
1114 repeated_cross_size.and_then(|e| eval_expression(ctx, e).try_into().ok());
1115 let mut cells: Vec<Value> = Vec::with_capacity(elements.len());
1116 let mut repeated_indices: Vec<u32> = Vec::new();
1117 let mut repeater_steps: Vec<u32> = Vec::new();
1118 for el in elements {
1119 match el {
1120 itertools::Either::Left(expr) => cells.push(eval_expression(ctx, expr)),
1121 itertools::Either::Right(repeater) => {
1122 let offset = cells.len() as u32;
1123 let (instances, step) = push_repeater_layout_items(
1124 ctx,
1125 repeater.repeater_index,
1126 repeater.row_child_templates.as_deref(),
1127 orientation,
1128 cross_size,
1129 repeater.cross_width.as_ref(),
1130 &mut cells,
1131 );
1132 repeated_indices.push(offset);
1133 repeated_indices.push(instances);
1134 repeater_steps.push(step);
1135 }
1136 }
1137 }
1138 let prev_cells =
1139 ctx.locals.insert(SmolStr::from(cells_variable), Value::Model(model_from_vec(cells)));
1140 let prev_ri = ctx.locals.insert(
1141 SmolStr::new_static("repeated_indices"),
1142 Value::Model(model_from_vec(
1143 repeated_indices.into_iter().map(|i| Value::Number(i as f64)).collect(),
1144 )),
1145 );
1146 let prev_rs = ctx.locals.insert(
1147 SmolStr::new_static("repeater_steps"),
1148 Value::Model(model_from_vec(
1149 repeater_steps.into_iter().map(|i| Value::Number(i as f64)).collect(),
1150 )),
1151 );
1152 let result = eval_expression(ctx, sub_expression);
1153 restore_local(ctx, cells_variable, prev_cells);
1154 restore_local(ctx, "repeated_indices", prev_ri);
1155 restore_local(ctx, "repeater_steps", prev_rs);
1156 result
1157}
1158
1159fn push_repeater_layout_items(
1160 ctx: &mut EvalContext,
1161 repeater_idx: i_slint_compiler::llr::RepeatedElementIdx,
1162 row_child_templates: Option<&[i_slint_compiler::llr::RowChildTemplateInfo]>,
1163 orientation: i_slint_compiler::layout::Orientation,
1164 cross_size: Option<f32>,
1165 grid_cross_width: Option<&Expression>,
1166 cells: &mut Vec<Value>,
1167) -> (u32, u32) {
1168 use i_slint_core::model::RepeatedItemTree;
1169 let Some(current) = ctx.current.as_ref() else { return (0, 0) };
1170 let repeater = ¤t.repeaters[repeater_idx];
1171 repeater.track_instance_changes();
1172 let instances = repeater.instances_vec();
1173 let core_orientation = llr_to_core_orientation(orientation);
1174 let push_cell = |cells: &mut Vec<Value>, info: i_slint_core::layout::LayoutItemInfo| {
1175 let mut struct_value = crate::api::Struct::default();
1176 struct_value.set_field("constraint".to_string(), info.constraint.into());
1177 if info.cross_axis_self_alignment != i_slint_core::items::CrossAxisAlignment::Auto {
1180 struct_value.set_field(
1181 "cross-axis-self-alignment".to_string(),
1182 Value::EnumerationValue(
1183 "CrossAxisAlignment".to_string(),
1184 info.cross_axis_self_alignment.to_string(),
1185 ),
1186 );
1187 }
1188 if info.layout_order != 0 {
1190 struct_value
1191 .set_field("layout-order".to_string(), Value::Number(info.layout_order as f64));
1192 }
1193 cells.push(Value::Struct(struct_value));
1194 };
1195 let step = match row_child_templates {
1196 None => {
1197 for (i, instance) in instances.iter().enumerate() {
1201 let info = match (cross_size, core_orientation) {
1202 (Some(cs), i_slint_core::items::Orientation::Vertical) => {
1203 RepeatedItemTree::layout_item_info_at_cross_width(instance.as_pin_ref(), cs)
1204 }
1205 (Some(_), i_slint_core::items::Orientation::Horizontal) => {
1206 unreachable!("a horizontal main pass forwards no cross size")
1207 }
1208 (None, _) => {
1211 match grid_cross_width.and_then(|e| eval_grid_measure_width(ctx, e, i)) {
1212 Some(w) => RepeatedItemTree::layout_item_info_at_cross_width(
1213 instance.as_pin_ref(),
1214 w,
1215 ),
1216 None => RepeatedItemTree::layout_item_info(
1217 instance.as_pin_ref(),
1218 core_orientation,
1219 None,
1220 ),
1221 }
1222 }
1223 };
1224 push_cell(cells, info);
1225 }
1226 1
1227 }
1228 Some(templates) => {
1229 debug_assert!(cross_size.is_none());
1232 let max_total = instances
1236 .iter()
1237 .map(|inst| total_row_child_count(&inst.root_sub_component, templates))
1238 .max()
1239 .unwrap_or(i_slint_compiler::llr::static_child_count(templates));
1240 for instance in &instances {
1241 for child_idx in 0..max_total {
1242 let info = RepeatedItemTree::layout_item_info(
1243 instance.as_pin_ref(),
1244 core_orientation,
1245 Some(child_idx),
1246 );
1247 push_cell(cells, info);
1248 }
1249 }
1250 max_total as u32
1251 }
1252 };
1253 (instances.len() as u32, step)
1254}
1255
1256fn eval_grid_measure_width(ctx: &mut EvalContext, expr: &Expression, index: usize) -> Option<f32> {
1260 use i_slint_compiler::llr::lower_layout_expression::GRID_MEASURE_REPEATER_INDEX_LOCAL;
1261 let prev = ctx.locals.insert(
1262 SmolStr::new_static(GRID_MEASURE_REPEATER_INDEX_LOCAL),
1263 Value::Number(index as f64),
1264 );
1265 let value = eval_expression(ctx, expr);
1266 restore_local(ctx, GRID_MEASURE_REPEATER_INDEX_LOCAL, prev);
1267 value.try_into().ok()
1268}
1269
1270fn total_row_child_count(
1271 sub: &Pin<std::rc::Rc<crate::instance::SubComponentInstance>>,
1272 templates: &[i_slint_compiler::llr::RowChildTemplateInfo],
1273) -> usize {
1274 use i_slint_compiler::llr::{RowChildTemplateInfo, static_child_count};
1275 let mut total = static_child_count(templates);
1276 for entry in templates {
1277 if let RowChildTemplateInfo::Repeated { repeater_index, .. } = entry {
1278 let repeater = &sub.repeaters[*repeater_index];
1279 repeater.track_instance_changes();
1280 total += repeater.range().len();
1281 }
1282 }
1283 total
1284}
1285
1286pub(crate) fn llr_to_core_orientation(
1287 o: i_slint_compiler::layout::Orientation,
1288) -> i_slint_core::items::Orientation {
1289 match o {
1290 i_slint_compiler::layout::Orientation::Horizontal => {
1291 i_slint_core::items::Orientation::Horizontal
1292 }
1293 i_slint_compiler::layout::Orientation::Vertical => {
1294 i_slint_core::items::Orientation::Vertical
1295 }
1296 }
1297}
1298
1299fn with_flexbox_layout_item_info(
1300 ctx: &mut EvalContext,
1301 cells_h_variable: &str,
1302 cells_v_variable: &str,
1303 flex_props_variable: Option<&str>,
1304 elements: &[itertools::Either<
1305 (Expression, Expression, Expression),
1306 i_slint_compiler::llr::LayoutRepeatedElement,
1307 >],
1308 repeated_cross_width: Option<&Expression>,
1309 sub_expression: &Expression,
1310) -> Value {
1311 let cross_width =
1314 repeated_cross_width.map(|e| eval_expression(ctx, e).try_into().unwrap_or_default());
1315 let mut cells_h: Vec<Value> = Vec::with_capacity(elements.len());
1316 let mut cells_v: Vec<Value> = Vec::with_capacity(elements.len());
1317 let mut flex_props: Vec<Value> = Vec::with_capacity(elements.len());
1318 let mut repeated_indices: Vec<u32> = Vec::new();
1319 for el in elements {
1320 match el {
1321 itertools::Either::Left((h, v, props)) => {
1322 cells_h.push(eval_expression(ctx, h));
1323 cells_v.push(eval_expression(ctx, v));
1324 if flex_props_variable.is_some() {
1328 flex_props.push(eval_expression(ctx, props));
1329 }
1330 }
1331 itertools::Either::Right(repeater) => {
1332 let offset = cells_h.len() as u32;
1333 let instances = push_repeater_flexbox_items(
1334 ctx,
1335 repeater.repeater_index,
1336 cross_width,
1337 &mut cells_h,
1338 &mut cells_v,
1339 flex_props_variable.is_some().then_some(&mut flex_props),
1340 );
1341 repeated_indices.push(offset);
1342 repeated_indices.push(instances);
1343 }
1344 }
1345 }
1346 let prev_h =
1347 ctx.locals.insert(SmolStr::from(cells_h_variable), Value::Model(model_from_vec(cells_h)));
1348 let prev_v =
1349 ctx.locals.insert(SmolStr::from(cells_v_variable), Value::Model(model_from_vec(cells_v)));
1350 let prev_fp = flex_props_variable.map(|name| {
1351 ctx.locals.insert(SmolStr::from(name), Value::Model(model_from_vec(flex_props)))
1352 });
1353 let prev_ri = ctx.locals.insert(
1354 SmolStr::new_static("repeated_indices"),
1355 Value::Model(model_from_vec(
1356 repeated_indices.into_iter().map(|i| Value::Number(i as f64)).collect(),
1357 )),
1358 );
1359 let result = eval_expression(ctx, sub_expression);
1360 restore_local(ctx, cells_h_variable, prev_h);
1361 restore_local(ctx, cells_v_variable, prev_v);
1362 if let Some(name) = flex_props_variable {
1363 restore_local(ctx, name, prev_fp.flatten());
1364 }
1365 restore_local(ctx, "repeated_indices", prev_ri);
1366 result
1367}
1368
1369fn push_repeater_flexbox_items(
1370 ctx: &mut EvalContext,
1371 repeater_idx: i_slint_compiler::llr::RepeatedElementIdx,
1372 cross_width: Option<f32>,
1373 cells_h: &mut Vec<Value>,
1374 cells_v: &mut Vec<Value>,
1375 mut flex_props: Option<&mut Vec<Value>>,
1376) -> u32 {
1377 use i_slint_core::items::Orientation;
1378 use i_slint_core::model::RepeatedItemTree;
1379 let Some(current) = ctx.current.as_ref() else { return 0 };
1380 let repeater = ¤t.repeaters[repeater_idx];
1381 repeater.track_instance_changes();
1382 let instances = repeater.instances_vec();
1383 let instance_count = instances.len() as u32;
1384 for instance in instances {
1385 let info_h = RepeatedItemTree::flexbox_layout_item_info(
1389 instance.as_pin_ref(),
1390 Orientation::Horizontal,
1391 None,
1392 );
1393 let info_v = match cross_width {
1396 Some(w) => instance.as_pin_ref().flexbox_layout_item_info_at_cross_width(w),
1397 None => RepeatedItemTree::flexbox_layout_item_info(
1398 instance.as_pin_ref(),
1399 Orientation::Vertical,
1400 None,
1401 ),
1402 };
1403 if let Some(fp) = flex_props.as_mut() {
1406 fp.push(flex_props_to_value(info_h.props));
1407 }
1408 cells_h.push(layout_item_info_to_value(info_h.constraint));
1409 cells_v.push(layout_item_info_to_value(info_v.constraint));
1410 }
1411 instance_count
1412}
1413
1414fn layout_item_info_to_value(constraint: i_slint_core::layout::LayoutInfo) -> Value {
1415 let mut s = crate::api::Struct::default();
1416 s.set_field("constraint".to_string(), constraint.into());
1417 Value::Struct(s)
1418}
1419
1420fn flex_props_to_value(props: i_slint_core::layout::FlexItemProps) -> Value {
1421 let mut s = crate::api::Struct::default();
1422 s.set_field(
1423 "cross-axis-self-alignment".to_string(),
1424 Value::EnumerationValue(
1425 "CrossAxisAlignment".to_string(),
1426 format!("{:?}", props.cross_axis_self_alignment).to_lowercase(),
1427 ),
1428 );
1429 s.set_field("layout-order".to_string(), Value::Number(props.layout_order as f64));
1430 Value::Struct(s)
1431}
1432
1433fn with_grid_input_data(
1434 ctx: &mut EvalContext,
1435 cells_variable: &str,
1436 elements: &[itertools::Either<Expression, i_slint_compiler::llr::GridLayoutRepeatedElement>],
1437 sub_expression: &Expression,
1438) -> Value {
1439 let saved_new_row = ctx.locals.remove("new_row");
1446 let mut cells: Vec<Value> = Vec::with_capacity(elements.len());
1447 let mut repeated_indices: Vec<u32> = Vec::new();
1448 let mut repeater_steps: Vec<u32> = Vec::new();
1449
1450 for el in elements {
1451 match el {
1452 itertools::Either::Left(expr) => cells.push(eval_expression(ctx, expr)),
1453 itertools::Either::Right(repeater) => {
1454 ctx.locals.insert(SmolStr::new_static("new_row"), Value::Bool(repeater.new_row));
1455 let offset = cells.len() as u32;
1456 let is_row_repeater = repeater.row_child_templates.is_some();
1457 let (instances, step) = push_repeater_grid_input_data(
1458 ctx,
1459 repeater.repeater_index,
1460 repeater.new_row,
1461 repeater.row_child_templates.as_deref(),
1462 &mut cells,
1463 );
1464 if !is_row_repeater && instances > 0 {
1465 ctx.locals.insert(SmolStr::new_static("new_row"), Value::Bool(false));
1466 }
1467 repeated_indices.push(offset);
1468 repeated_indices.push(instances);
1469 repeater_steps.push(step);
1470 }
1471 }
1472 }
1473 restore_local(ctx, "new_row", saved_new_row);
1474
1475 let prev_cells =
1476 ctx.locals.insert(SmolStr::from(cells_variable), Value::Model(model_from_vec(cells)));
1477 let prev_ri = ctx.locals.insert(
1478 SmolStr::new_static("repeated_indices"),
1479 Value::Model(model_from_vec(
1480 repeated_indices.into_iter().map(|i| Value::Number(i as f64)).collect(),
1481 )),
1482 );
1483 let prev_rs = ctx.locals.insert(
1484 SmolStr::new_static("repeater_steps"),
1485 Value::Model(model_from_vec(
1486 repeater_steps.into_iter().map(|i| Value::Number(i as f64)).collect(),
1487 )),
1488 );
1489
1490 let result = eval_expression(ctx, sub_expression);
1491
1492 restore_local(ctx, cells_variable, prev_cells);
1493 restore_local(ctx, "repeated_indices", prev_ri);
1494 restore_local(ctx, "repeater_steps", prev_rs);
1495 result
1496}
1497
1498pub(crate) fn restore_local(ctx: &mut EvalContext, name: &str, prev: Option<Value>) {
1499 if let Some(prev) = prev {
1500 ctx.locals.insert(SmolStr::from(name), prev);
1501 } else {
1502 ctx.locals.remove(name);
1503 }
1504}
1505
1506fn push_repeater_grid_input_data(
1507 ctx: &mut EvalContext,
1508 repeater_idx: i_slint_compiler::llr::RepeatedElementIdx,
1509 new_row: bool,
1510 row_child_templates: Option<&[i_slint_compiler::llr::RowChildTemplateInfo]>,
1511 cells: &mut Vec<Value>,
1512) -> (u32, u32) {
1513 use i_slint_compiler::llr::RowChildTemplateInfo;
1514 use i_slint_core::model::VecModel;
1515 use std::rc::Rc;
1516 let Some(current) = ctx.current.as_ref() else { return (0, 0) };
1517 let repeater = ¤t.repeaters[repeater_idx];
1518 repeater.track_instance_changes();
1519
1520 let is_row_repeater = row_child_templates.is_some();
1521 let static_count =
1522 row_child_templates.map(i_slint_compiler::llr::static_child_count).unwrap_or(1);
1523
1524 let instances = repeater.instances_vec();
1525 let instance_count = instances.len() as u32;
1526
1527 let step = if let Some(templates) = row_child_templates {
1531 instances
1532 .iter()
1533 .map(|inst| total_row_child_count(&inst.root_sub_component, templates))
1534 .max()
1535 .unwrap_or(static_count)
1536 } else {
1537 1
1538 };
1539
1540 let mut current_new_row = new_row;
1541
1542 for instance in &instances {
1543 let inner_sub = instance.root_sub_component.clone();
1544 let cu = inner_sub.compilation_unit.clone();
1545 let sc = &cu.sub_components[inner_sub.sub_component_idx];
1546
1547 let mut statics: Vec<Value> = vec![Value::Void; static_count];
1551 if let Some(expr) = &sc.grid_layout_input_for_repeated {
1552 let expr = expr.borrow();
1553 let mut inner_ctx = EvalContext::new(inner_sub.clone());
1554 let result_model: Rc<VecModel<Value>> = Rc::new(VecModel::default());
1555 for _ in 0..static_count {
1556 result_model.push(Value::Void);
1557 }
1558 inner_ctx.locals.insert(
1559 SmolStr::new_static("result"),
1560 Value::Model(i_slint_core::model::ModelRc::from(result_model.clone())),
1561 );
1562 inner_ctx.locals.insert(SmolStr::new_static("new_row"), Value::Bool(current_new_row));
1563 eval_expression(&mut inner_ctx, &expr);
1564 for (slot, i) in statics.iter_mut().zip(0..result_model.row_count()) {
1565 if let Some(v) = result_model.row_data(i) {
1566 *slot = v;
1567 }
1568 }
1569 }
1570
1571 if let Some(templates) = row_child_templates {
1572 let mut written = 0usize;
1576 let mut static_idx = 0usize;
1577 for entry in templates {
1578 if written >= step {
1579 break;
1580 }
1581 match entry {
1582 RowChildTemplateInfo::Static { .. } => {
1583 let mut v = statics.get(static_idx).cloned().unwrap_or(Value::Void);
1584 static_idx += 1;
1585 override_new_row(&mut v, written == 0 && current_new_row);
1586 cells.push(v);
1587 written += 1;
1588 }
1589 RowChildTemplateInfo::Repeated { repeater_index, .. } => {
1590 let inner_rep = &inner_sub.repeaters[*repeater_index];
1591 inner_rep.track_instance_changes();
1592 for inner_inst in inner_rep.instances_vec() {
1596 if written >= step {
1597 break;
1598 }
1599 for mut v in eval_grid_input_for_repeated(
1600 &inner_inst.root_sub_component,
1601 written == 0 && current_new_row,
1602 ) {
1603 if written >= step {
1604 break;
1605 }
1606 override_new_row(&mut v, written == 0 && current_new_row);
1607 cells.push(v);
1608 written += 1;
1609 }
1610 }
1611 }
1612 }
1613 }
1614 while written < step {
1615 cells.push(auto_grid_input_data());
1616 written += 1;
1617 }
1618 } else {
1619 cells.push(statics.pop().unwrap_or_else(auto_grid_input_data));
1621 }
1622
1623 if !is_row_repeater {
1624 current_new_row = false;
1625 }
1626 }
1627 (instance_count, step as u32)
1628}
1629
1630fn eval_grid_input_for_repeated(
1635 sub: &Pin<Rc<crate::instance::SubComponentInstance>>,
1636 new_row: bool,
1637) -> Vec<Value> {
1638 use i_slint_core::model::{Model, VecModel};
1639 let cu = sub.compilation_unit.clone();
1640 let sc = &cu.sub_components[sub.sub_component_idx];
1641 let count = sc
1642 .row_child_templates
1643 .as_ref()
1644 .map(|t| i_slint_compiler::llr::static_child_count(t))
1645 .unwrap_or(1)
1646 .max(1);
1647 let Some(expr) = &sc.grid_layout_input_for_repeated else {
1648 return vec![auto_grid_input_data()];
1649 };
1650 let expr = expr.borrow();
1651 let mut ctx = EvalContext::new(sub.clone());
1652 let result_model: Rc<VecModel<Value>> = Rc::new(VecModel::default());
1653 for _ in 0..count {
1654 result_model.push(Value::Void);
1655 }
1656 ctx.locals.insert(
1657 SmolStr::new_static("result"),
1658 Value::Model(i_slint_core::model::ModelRc::from(result_model.clone())),
1659 );
1660 ctx.locals.insert(SmolStr::new_static("new_row"), Value::Bool(new_row));
1661 eval_expression(&mut ctx, &expr);
1662 (0..result_model.row_count())
1663 .map(|i| result_model.row_data(i).unwrap_or_else(auto_grid_input_data))
1664 .collect()
1665}
1666
1667fn auto_grid_input_data() -> Value {
1670 let mut s = crate::api::Struct::default();
1671 s.set_field("new-row".into(), Value::Bool(false));
1672 s.set_field("row".into(), Value::Number(i_slint_common::ROW_COL_AUTO as f64));
1673 s.set_field("col".into(), Value::Number(i_slint_common::ROW_COL_AUTO as f64));
1674 s.set_field("rowspan".into(), Value::Number(1.0));
1675 s.set_field("colspan".into(), Value::Number(1.0));
1676 Value::Struct(s)
1677}
1678
1679fn override_new_row(v: &mut Value, new_row: bool) {
1680 if let Value::Struct(s) = v {
1681 s.set_field("new-row".into(), Value::Bool(new_row));
1682 }
1683}
1684
1685fn model_from_vec(values: Vec<Value>) -> ModelRc<Value> {
1686 ModelRc::new(SharedVectorModel::from(values.into_iter().collect::<SharedVector<_>>()))
1687}
1688
1689fn binary_op(op: char, lhs: Value, rhs: Value) -> Value {
1690 let (lhs, rhs) = match (lhs, rhs) {
1693 (Value::Void, Value::Number(b)) => (Value::Number(0.), Value::Number(b)),
1694 (Value::Number(a), Value::Void) => (Value::Number(a), Value::Number(0.)),
1695 (Value::Void, Value::Bool(b)) => (Value::Bool(false), Value::Bool(b)),
1696 (Value::Bool(a), Value::Void) => (Value::Bool(a), Value::Bool(false)),
1697 (Value::Void, Value::String(b)) => (Value::String(Default::default()), Value::String(b)),
1698 (Value::String(a), Value::Void) => (Value::String(a), Value::String(Default::default())),
1699 (Value::Void, Value::Void) if matches!(op, '&' | '|') => {
1701 (Value::Bool(false), Value::Bool(false))
1702 }
1703 (Value::Void, Value::Void) => (Value::Number(0.), Value::Number(0.)),
1704 (a, b) => (a, b),
1705 };
1706 match (op, lhs, rhs) {
1707 ('+', Value::String(mut a), Value::String(b)) => {
1708 a.push_str(b.as_str());
1709 Value::String(a)
1710 }
1711 ('+', Value::Number(a), Value::Number(b)) => Value::Number(a + b),
1712 ('+', a @ Value::Struct(_), b @ Value::Struct(_)) => {
1713 let la: Option<i_slint_core::layout::LayoutInfo> = a.try_into().ok();
1714 let lb: Option<i_slint_core::layout::LayoutInfo> = b.try_into().ok();
1715 if let (Some(a), Some(b)) = (la, lb) {
1716 a.merge(&b).into()
1717 } else {
1718 panic!("unsupported struct + struct");
1719 }
1720 }
1721 ('-', Value::Number(a), Value::Number(b)) => Value::Number(a - b),
1722 ('/', Value::Number(a), Value::Number(b)) => Value::Number(a / b),
1723 ('*', Value::Number(a), Value::Number(b)) => Value::Number(a * b),
1724 ('<', Value::Number(a), Value::Number(b)) => Value::Bool(a < b),
1725 ('>', Value::Number(a), Value::Number(b)) => Value::Bool(a > b),
1726 ('≤', Value::Number(a), Value::Number(b)) => Value::Bool(a <= b),
1727 ('≥', Value::Number(a), Value::Number(b)) => Value::Bool(a >= b),
1728 ('<', Value::String(a), Value::String(b)) => Value::Bool(a < b),
1729 ('>', Value::String(a), Value::String(b)) => Value::Bool(a > b),
1730 ('≤', Value::String(a), Value::String(b)) => Value::Bool(a <= b),
1731 ('≥', Value::String(a), Value::String(b)) => Value::Bool(a >= b),
1732 ('=', a, b) => Value::Bool(a == b),
1733 ('!', a, b) => Value::Bool(a != b),
1734 ('&', Value::Bool(a), Value::Bool(b)) => Value::Bool(a && b),
1735 ('|', Value::Bool(a), Value::Bool(b)) => Value::Bool(a || b),
1736 (op, a, b) => panic!("unsupported {a:?} {op} {b:?}"),
1737 }
1738}
1739
1740fn eval_stops(ctx: &mut EvalContext, stops: &[(Expression, Expression)]) -> Vec<GradientStop> {
1741 stops
1742 .iter()
1743 .map(|(color, stop)| GradientStop {
1744 color: eval_expression(ctx, color).try_into().unwrap_or_default(),
1745 position: eval_expression(ctx, stop).try_into().unwrap_or_default(),
1746 })
1747 .collect()
1748}
1749
1750fn load_image_reference(
1751 resource_ref: &i_slint_compiler::expression_tree::ImageReference,
1752) -> i_slint_core::graphics::Image {
1753 use i_slint_compiler::expression_tree::ImageReference as Ref;
1754 let image = match resource_ref {
1755 Ref::None => Ok(Default::default()),
1756 Ref::DataUri(data_uri) => i_slint_compiler::data_uri::decode_data_uri(data_uri)
1757 .ok()
1758 .and_then(|(data, extension)| {
1759 i_slint_core::graphics::load_image_from_data_uri(data_uri, &data, &extension).ok()
1760 })
1761 .ok_or_else(Default::default),
1762 Ref::Url(url) if url.scheme() == "builtin" => {
1763 let path = std::path::Path::new(url.as_str());
1767 i_slint_compiler::fileaccess::load_file(path)
1768 .and_then(|virtual_file| virtual_file.builtin_contents)
1769 .map(|contents| {
1770 let extension = path.extension().unwrap().to_str().unwrap();
1771 i_slint_core::graphics::load_image_from_embedded_data(
1772 i_slint_core::slice::Slice::from_slice(contents),
1773 i_slint_core::slice::Slice::from_slice(extension.as_bytes()),
1774 )
1775 })
1776 .ok_or_else(Default::default)
1777 }
1778 Ref::Path(path) => {
1779 i_slint_core::graphics::Image::load_from_path(std::path::Path::new(path.as_str()))
1780 }
1781 Ref::Url(url) => {
1782 #[cfg(target_arch = "wasm32")]
1783 {
1784 i_slint_core::graphics::load_as_html_image(url.as_str())
1785 }
1786 #[cfg(not(target_arch = "wasm32"))]
1788 {
1789 let _ = url;
1790 Err(Default::default())
1791 }
1792 }
1793 Ref::EmbeddedData { .. } | Ref::EmbeddedTexture { .. } => Ok(Default::default()),
1794 };
1795 image.unwrap_or_else(|_| {
1796 eprintln!("Could not load image {resource_ref:?}");
1797 Default::default()
1798 })
1799}
1800
1801fn layout_cache_access(
1802 ctx: &mut EvalContext,
1803 cache: Value,
1804 index: usize,
1805 repeater_index: Option<&Expression>,
1806 entries_per_item: usize,
1807) -> Value {
1808 match cache {
1809 Value::LayoutCache(cache) => {
1810 if let Some(ri) = repeater_index {
1811 let offset: usize = eval_expression(ctx, ri).try_into().unwrap_or_default();
1812 Value::Number(
1813 cache
1814 .get((cache[index] as usize) + offset * entries_per_item)
1815 .copied()
1816 .unwrap_or(0.)
1817 .into(),
1818 )
1819 } else {
1820 Value::Number(cache[index].into())
1821 }
1822 }
1823 Value::ArrayOfU16(cache) => {
1824 if let Some(ri) = repeater_index {
1825 let offset: usize = eval_expression(ctx, ri).try_into().unwrap_or_default();
1826 Value::Number(
1827 cache
1828 .get((cache[index] as usize) + offset * entries_per_item)
1829 .copied()
1830 .unwrap_or(0)
1831 .into(),
1832 )
1833 } else {
1834 Value::Number(cache[index].into())
1835 }
1836 }
1837 _ => Value::Number(0.),
1838 }
1839}
1840
1841fn grid_repeater_cache_access(
1846 cache: Value,
1847 index: usize,
1848 repeater_index: usize,
1849 stride: usize,
1850 child_offset: usize,
1851 inner_offset: usize,
1852) -> Value {
1853 let get = |data_idx: usize, slice_len: usize, read: &dyn Fn(usize) -> f64| {
1854 if data_idx < slice_len { Value::Number(read(data_idx)) } else { Value::Number(0.) }
1855 };
1856 match cache {
1857 Value::LayoutCache(cache) => {
1858 let base = cache.get(index).copied().unwrap_or(0.) as usize;
1859 let data_idx = base + repeater_index * stride + child_offset + inner_offset;
1860 get(data_idx, cache.len(), &|i| cache[i] as f64)
1861 }
1862 Value::ArrayOfU16(cache) => {
1863 let base = cache.get(index).copied().unwrap_or(0) as usize;
1864 let data_idx = base + repeater_index * stride + child_offset + inner_offset;
1865 get(data_idx, cache.len(), &|i| cache[i] as f64)
1866 }
1867 _ => Value::Number(0.),
1868 }
1869}
1870
1871fn log_message_location(
1875 source_location: &Option<SourceLocation>,
1876) -> Option<i_slint_core::debug_log::LogMessageLocation<'_>> {
1877 let location = source_location.as_ref()?;
1878 let source_file = location.source_file.as_ref()?;
1879 let (line, column) = source_file
1880 .line_column(location.span.offset, i_slint_compiler::diagnostics::ByteFormat::Utf8);
1881 Some(i_slint_core::debug_log::LogMessageLocation {
1882 path: source_file.path().to_str()?,
1883 line,
1884 column,
1885 })
1886}
1887
1888fn call_builtin_function(
1889 ctx: &mut EvalContext,
1890 f: BuiltinFunction,
1891 arguments: &[Expression],
1892 source_location: &Option<SourceLocation>,
1893) -> Value {
1894 let to_num = |ctx: &mut EvalContext, e: &Expression| -> f64 {
1895 eval_expression(ctx, e).try_into().unwrap_or_default()
1896 };
1897 let to_string = |ctx: &mut EvalContext, e: &Expression| -> SharedString {
1898 eval_expression(ctx, e).try_into().unwrap_or_default()
1899 };
1900
1901 match f {
1902 BuiltinFunction::Mod => {
1903 Value::Number(to_num(ctx, &arguments[0]).rem_euclid(to_num(ctx, &arguments[1])))
1904 }
1905 BuiltinFunction::Round => Value::Number(to_num(ctx, &arguments[0]).round()),
1906 BuiltinFunction::Ceil => Value::Number(to_num(ctx, &arguments[0]).ceil()),
1907 BuiltinFunction::Floor => Value::Number(to_num(ctx, &arguments[0]).floor()),
1908 BuiltinFunction::Sqrt => Value::Number(to_num(ctx, &arguments[0]).sqrt()),
1909 BuiltinFunction::Abs => Value::Number(to_num(ctx, &arguments[0]).abs()),
1910 BuiltinFunction::Sin => Value::Number(to_num(ctx, &arguments[0]).to_radians().sin()),
1911 BuiltinFunction::Cos => Value::Number(to_num(ctx, &arguments[0]).to_radians().cos()),
1912 BuiltinFunction::Tan => Value::Number(to_num(ctx, &arguments[0]).to_radians().tan()),
1913 BuiltinFunction::ASin => Value::Number(to_num(ctx, &arguments[0]).asin().to_degrees()),
1914 BuiltinFunction::ACos => Value::Number(to_num(ctx, &arguments[0]).acos().to_degrees()),
1915 BuiltinFunction::ATan => Value::Number(to_num(ctx, &arguments[0]).atan().to_degrees()),
1916 BuiltinFunction::ATan2 => {
1917 Value::Number(to_num(ctx, &arguments[0]).atan2(to_num(ctx, &arguments[1])).to_degrees())
1918 }
1919 BuiltinFunction::Log => {
1920 Value::Number(to_num(ctx, &arguments[0]).log(to_num(ctx, &arguments[1])))
1921 }
1922 BuiltinFunction::Ln => Value::Number(to_num(ctx, &arguments[0]).ln()),
1923 BuiltinFunction::Pow => {
1924 Value::Number(to_num(ctx, &arguments[0]).powf(to_num(ctx, &arguments[1])))
1925 }
1926 BuiltinFunction::Exp => Value::Number(to_num(ctx, &arguments[0]).exp()),
1927 BuiltinFunction::ToFixed => {
1928 let n = to_num(ctx, &arguments[0]);
1929 let digits: i32 = eval_expression(ctx, &arguments[1]).try_into().unwrap_or_default();
1930 Value::String(i_slint_core::string::shared_string_from_number_fixed(
1931 n,
1932 digits.max(0) as usize,
1933 ))
1934 }
1935 BuiltinFunction::ToPrecision => {
1936 let n = to_num(ctx, &arguments[0]);
1937 let p: i32 = eval_expression(ctx, &arguments[1]).try_into().unwrap_or_default();
1938 Value::String(i_slint_core::string::shared_string_from_number_precision(
1939 n,
1940 p.max(0) as usize,
1941 ))
1942 }
1943 BuiltinFunction::StringStartsWith => Value::Bool(
1944 to_string(ctx, &arguments[0])
1945 .as_str()
1946 .starts_with(to_string(ctx, &arguments[1]).as_str()),
1947 ),
1948 BuiltinFunction::StringEndsWith => Value::Bool(
1949 to_string(ctx, &arguments[0])
1950 .as_str()
1951 .ends_with(to_string(ctx, &arguments[1]).as_str()),
1952 ),
1953 BuiltinFunction::ToStringUnlocalized => {
1954 let n = to_num(ctx, &arguments[0]);
1955 Value::String(i_slint_core::string::shared_string_from_number_unlocalized(n))
1956 }
1957 BuiltinFunction::DefaultWindowTitle => {
1958 Value::String(i_slint_core::window::default_window_title())
1959 }
1960 BuiltinFunction::DecimalSeparator => Value::String(
1961 find_window_adapter(ctx)
1962 .map(|adapter| {
1963 i_slint_core::window::WindowInner::from_pub(adapter.window())
1964 .context()
1965 .locale_decimal_separator()
1966 })
1967 .unwrap_or_default()
1968 .into(),
1969 ),
1970 BuiltinFunction::MacosBringAllWindowsToFront => {
1971 i_slint_core::macos_bring_all_windows_to_front();
1972 Value::Void
1973 }
1974 BuiltinFunction::ColorToStyledText => {
1975 let color: i_slint_core::Color =
1976 eval_expression(ctx, &arguments[0]).try_into().unwrap_or_default();
1977 Value::StyledText(i_slint_core::styled_text::color_to_styled_text(color))
1978 }
1979 BuiltinFunction::SetupSystemTrayIcon => {
1980 crate::popup::setup_system_tray_icon(ctx, arguments)
1981 }
1982 BuiltinFunction::StringIsFloat => Value::Bool(
1983 <f64 as core::str::FromStr>::from_str(to_string(ctx, &arguments[0]).as_str()).is_ok(),
1984 ),
1985 BuiltinFunction::StringToFloat => Value::Number(
1986 core::str::FromStr::from_str(to_string(ctx, &arguments[0]).as_str()).unwrap_or(0.),
1987 ),
1988 BuiltinFunction::StringIsEmpty => Value::Bool(to_string(ctx, &arguments[0]).is_empty()),
1989 BuiltinFunction::StringCharacterCount => Value::Number(
1990 unicode_segmentation::UnicodeSegmentation::graphemes(
1991 to_string(ctx, &arguments[0]).as_str(),
1992 true,
1993 )
1994 .count() as f64,
1995 ),
1996 BuiltinFunction::StringToLowercase => {
1997 Value::String(to_string(ctx, &arguments[0]).to_lowercase().into())
1998 }
1999 BuiltinFunction::StringToUppercase => {
2000 Value::String(to_string(ctx, &arguments[0]).to_uppercase().into())
2001 }
2002 BuiltinFunction::StringReplaceAll => {
2003 if arguments.len() != 3 {
2004 panic!("internal error: incorrect argument count to StringReplaceAll")
2005 }
2006
2007 if let (Value::String(s), Value::String(from), Value::String(to)) = (
2008 eval_expression(ctx, &arguments[0]),
2009 eval_expression(ctx, &arguments[1]),
2010 eval_expression(ctx, &arguments[2]),
2011 ) {
2012 Value::String(i_slint_core::string::shared_string_replace_all(
2013 &s,
2014 from.as_str(),
2015 to.as_str(),
2016 ))
2017 } else {
2018 panic!("Not all arguments are strings");
2019 }
2020 }
2021 BuiltinFunction::ColorRgbaStruct => {
2022 if let Value::Brush(brush) = eval_expression(ctx, &arguments[0]) {
2023 let color = brush.color();
2024 let values = [
2025 ("red".to_string(), Value::Number(color.red().into())),
2026 ("green".to_string(), Value::Number(color.green().into())),
2027 ("blue".to_string(), Value::Number(color.blue().into())),
2028 ("alpha".to_string(), Value::Number(color.alpha().into())),
2029 ]
2030 .into_iter()
2031 .collect();
2032 Value::Struct(values)
2033 } else {
2034 Value::Void
2035 }
2036 }
2037 BuiltinFunction::ColorHsvaStruct => {
2038 if let Value::Brush(brush) = eval_expression(ctx, &arguments[0]) {
2039 let color = brush.color().to_hsva();
2040 let values = [
2041 ("hue".to_string(), Value::Number(color.hue.into())),
2042 ("saturation".to_string(), Value::Number(color.saturation.into())),
2043 ("value".to_string(), Value::Number(color.value.into())),
2044 ("alpha".to_string(), Value::Number(color.alpha.into())),
2045 ]
2046 .into_iter()
2047 .collect();
2048 Value::Struct(values)
2049 } else {
2050 Value::Void
2051 }
2052 }
2053 BuiltinFunction::ColorOklchStruct => {
2054 if let Value::Brush(brush) = eval_expression(ctx, &arguments[0]) {
2055 let color = brush.color().to_oklch();
2056 let values = [
2057 ("lightness".to_string(), Value::Number(color.lightness.into())),
2058 ("chroma".to_string(), Value::Number(color.chroma.into())),
2059 ("hue".to_string(), Value::Number(color.hue.into())),
2060 ("alpha".to_string(), Value::Number(color.alpha.into())),
2061 ]
2062 .into_iter()
2063 .collect();
2064 Value::Struct(values)
2065 } else {
2066 Value::Void
2067 }
2068 }
2069 BuiltinFunction::ColorBrighter => {
2070 if let Value::Brush(brush) = eval_expression(ctx, &arguments[0]) {
2071 brush.brighter(to_num(ctx, &arguments[1]) as f32).into()
2072 } else {
2073 Value::Void
2074 }
2075 }
2076 BuiltinFunction::ColorDarker => {
2077 if let Value::Brush(brush) = eval_expression(ctx, &arguments[0]) {
2078 brush.darker(to_num(ctx, &arguments[1]) as f32).into()
2079 } else {
2080 Value::Void
2081 }
2082 }
2083 BuiltinFunction::ColorTransparentize => {
2084 if let Value::Brush(brush) = eval_expression(ctx, &arguments[0]) {
2085 brush.transparentize(to_num(ctx, &arguments[1]) as f32).into()
2086 } else {
2087 Value::Void
2088 }
2089 }
2090 BuiltinFunction::ColorWithAlpha => {
2091 if let Value::Brush(brush) = eval_expression(ctx, &arguments[0]) {
2092 brush.with_alpha(to_num(ctx, &arguments[1]) as f32).into()
2093 } else {
2094 Value::Void
2095 }
2096 }
2097 BuiltinFunction::ColorMix => {
2098 let a = eval_expression(ctx, &arguments[0]);
2099 let b = eval_expression(ctx, &arguments[1]);
2100 let factor = to_num(ctx, &arguments[2]) as f32;
2101 if let (
2102 Value::Brush(i_slint_core::Brush::SolidColor(ca)),
2103 Value::Brush(i_slint_core::Brush::SolidColor(cb)),
2104 ) = (a, b)
2105 {
2106 ca.mix(&cb, factor).into()
2107 } else {
2108 Value::Void
2109 }
2110 }
2111 BuiltinFunction::ArrayPush => {
2112 if arguments.len() != 2 {
2113 panic!("internal error: incorrect argument count to ArrayPush")
2114 }
2115
2116 let model = match eval_expression(ctx, &arguments[0]) {
2117 Value::Model(m) => m,
2118 _ => panic!("First argument not an array: {:?}", arguments[0]),
2119 };
2120 let value = eval_expression(ctx, &arguments[1]);
2121
2122 i_slint_core::model::report_model_error(
2123 "push",
2124 log_message_location(source_location),
2125 model.push_row(value),
2126 );
2127
2128 Value::Void
2129 }
2130 BuiltinFunction::ArrayRemove => {
2131 if arguments.len() != 2 {
2132 panic!("internal error: incorrect argument count to ArrayRemove")
2133 }
2134
2135 let model = match eval_expression(ctx, &arguments[0]) {
2136 Value::Model(m) => m,
2137 _ => panic!("First argument not an array: {:?}", arguments[0]),
2138 };
2139 let index = match eval_expression(ctx, &arguments[1]) {
2140 Value::Number(i) => i,
2141 _ => panic!("Second argument not an integer: {:?}", arguments[1]),
2142 };
2143
2144 let result = match usize::try_from(index as i64) {
2145 Ok(index) => model.remove_row(index),
2146 Err(_) => Err(i_slint_core::model::ModelError::out_of_bounds(model.row_count())),
2147 };
2148 i_slint_core::model::report_model_error(
2149 "remove",
2150 log_message_location(source_location),
2151 result,
2152 );
2153
2154 Value::Void
2155 }
2156
2157 BuiltinFunction::ArrayInsert => {
2158 if arguments.len() != 3 {
2159 panic!("internal error: incorrect argument count to ArrayInsert")
2160 }
2161
2162 let model = match eval_expression(ctx, &arguments[0]) {
2163 Value::Model(m) => m,
2164 _ => panic!("First argument not an array: {:?}", arguments[0]),
2165 };
2166 let index = match eval_expression(ctx, &arguments[1]) {
2167 Value::Number(i) => i,
2168 _ => panic!("Second argument not an integer: {:?}", arguments[1]),
2169 };
2170
2171 let value = eval_expression(ctx, &arguments[2]);
2172 let result = match usize::try_from(index as i64) {
2173 Ok(index) => model.insert_row(index, value),
2174 Err(_) => Err(i_slint_core::model::ModelError::out_of_bounds(model.row_count())),
2175 };
2176 i_slint_core::model::report_model_error(
2177 "insert",
2178 log_message_location(source_location),
2179 result,
2180 );
2181
2182 Value::Void
2183 }
2184 BuiltinFunction::Rgb => {
2185 let r: i32 = eval_expression(ctx, &arguments[0]).try_into().unwrap_or(0);
2186 let g: i32 = eval_expression(ctx, &arguments[1]).try_into().unwrap_or(0);
2187 let b: i32 = eval_expression(ctx, &arguments[2]).try_into().unwrap_or(0);
2188 let a: f32 = eval_expression(ctx, &arguments[3]).try_into().unwrap_or(1.0);
2189 let r: u8 = r.clamp(0, 255) as u8;
2190 let g: u8 = g.clamp(0, 255) as u8;
2191 let b: u8 = b.clamp(0, 255) as u8;
2192 let a: u8 = (255. * a).clamp(0., 255.) as u8;
2193 Value::Brush(i_slint_core::Brush::SolidColor(i_slint_core::Color::from_argb_u8(
2194 a, r, g, b,
2195 )))
2196 }
2197 BuiltinFunction::Hsv => {
2198 let h: f32 = eval_expression(ctx, &arguments[0]).try_into().unwrap_or(0.0);
2199 let s: f32 = eval_expression(ctx, &arguments[1]).try_into().unwrap_or(0.0);
2200 let v: f32 = eval_expression(ctx, &arguments[2]).try_into().unwrap_or(0.0);
2201 let a: f32 = eval_expression(ctx, &arguments[3]).try_into().unwrap_or(1.0);
2202 let a = a.clamp(0., 1.);
2203 Value::Brush(i_slint_core::Brush::SolidColor(i_slint_core::Color::from_hsva(
2204 h, s, v, a,
2205 )))
2206 }
2207 BuiltinFunction::Oklch => {
2208 let l: f32 = eval_expression(ctx, &arguments[0]).try_into().unwrap_or(0.0);
2209 let c: f32 = eval_expression(ctx, &arguments[1]).try_into().unwrap_or(0.0);
2210 let h: f32 = eval_expression(ctx, &arguments[2]).try_into().unwrap_or(0.0);
2211 let a: f32 = eval_expression(ctx, &arguments[3]).try_into().unwrap_or(1.0);
2212 Value::Brush(i_slint_core::Brush::SolidColor(i_slint_core::Color::from_oklch(
2213 l.clamp(0.0, 1.0),
2214 c,
2215 h,
2216 a.clamp(0.0, 1.0),
2217 )))
2218 }
2219 BuiltinFunction::AnimationTick => {
2220 Value::Number(i_slint_core::animations::animation_tick() as f64)
2221 }
2222 BuiltinFunction::GetWindowScaleFactor => {
2223 let factor = root_instance(ctx)
2224 .and_then(|inst| inst.window_adapter_or_default())
2225 .map(|adapter| {
2226 i_slint_core::window::WindowInner::from_pub(adapter.window()).scale_factor()
2227 as f64
2228 })
2229 .unwrap_or(1.0);
2230 Value::Number(factor)
2231 }
2232 BuiltinFunction::GetWindowDefaultFontSize => {
2233 let size = root_instance(ctx)
2239 .map(|inst| {
2240 i_slint_core::items::WindowItem::resolved_default_font_size(
2241 vtable::VRc::into_dyn(inst),
2242 )
2243 .get() as f64
2244 })
2245 .unwrap_or(12.0);
2246 Value::Number(size)
2247 }
2248 BuiltinFunction::DetectOperatingSystem => i_slint_core::detect_operating_system().into(),
2249 BuiltinFunction::Use24HourFormat => {
2250 Value::Bool(i_slint_core::date_time::use_24_hour_format())
2251 }
2252 BuiltinFunction::ColorScheme => {
2253 let scheme = root_instance(ctx)
2254 .map(vtable::VRc::into_dyn)
2255 .and_then(|root| {
2256 i_slint_core::window::context_for_root(&root)
2257 .map(|ctx| ctx.color_scheme(Some(&root)))
2258 })
2259 .unwrap_or(i_slint_core::items::ColorScheme::Unknown);
2260 scheme.into()
2261 }
2262 BuiltinFunction::AccentColor => {
2263 let color = root_instance(ctx)
2264 .map(vtable::VRc::into_dyn)
2265 .map(|root| i_slint_core::window::accent_color(&root))
2266 .unwrap_or_default();
2267 Value::Brush(i_slint_core::Brush::SolidColor(color))
2268 }
2269 BuiltinFunction::SupportsNativeMenuBar => {
2270 let supports = find_window_adapter(ctx).is_some_and(|a| {
2271 a.internal(i_slint_core::InternalToken)
2272 .is_some_and(|x| x.supports_native_menu_bar())
2273 });
2274 Value::Bool(supports)
2275 }
2276 BuiltinFunction::TextInputFocused => {
2277 let focused = ctx
2278 .current
2279 .as_ref()
2280 .and_then(|c| c.root.get())
2281 .and_then(|w| w.upgrade())
2282 .and_then(|inst| inst.window_adapter_or_default())
2283 .map(|adapter| {
2284 i_slint_core::window::WindowInner::from_pub(adapter.window())
2285 .text_input_focused()
2286 })
2287 .unwrap_or(false);
2288 Value::Bool(focused)
2289 }
2290 BuiltinFunction::SetTextInputFocused => {
2291 let value = arguments
2292 .first()
2293 .map(|e| eval_expression(ctx, e))
2294 .and_then(|v| bool::try_from(v).ok())
2295 .unwrap_or(false);
2296 if let Some(adapter) = ctx
2297 .current
2298 .as_ref()
2299 .and_then(|c| c.root.get())
2300 .and_then(|w| w.upgrade())
2301 .and_then(|inst| inst.window_adapter_or_default())
2302 {
2303 i_slint_core::window::WindowInner::from_pub(adapter.window())
2304 .set_text_input_focused(value);
2305 }
2306 Value::Void
2307 }
2308 BuiltinFunction::UpdateTimers => {
2309 Value::Void
2312 }
2313 BuiltinFunction::RestartTimer => {
2314 if let [
2319 Expression::PropertyReference(MemberReference::Relative {
2320 parent_level,
2321 local_reference,
2322 }),
2323 ] = arguments
2324 && let LocalMemberIndex::Timer(timer_idx) = &local_reference.reference
2325 && let Some(instance) = try_walk_to(ctx, *parent_level, local_reference)
2326 && let Some(timer) = instance.timers.get(usize::from(*timer_idx))
2327 {
2328 timer.restart();
2329 }
2330 Value::Void
2331 }
2332 BuiltinFunction::KeysToString => {
2333 let v = arguments.first().map(|e| eval_expression(ctx, e));
2334 if let Some(Value::Keys(keys)) = v {
2335 Value::String(keys.to_string().into())
2336 } else {
2337 Value::String(Default::default())
2338 }
2339 }
2340 BuiltinFunction::SetSelectionOffsets => {
2341 use i_slint_core::items::TextInput;
2343 let [Expression::PropertyReference(mr), anchor_expr, focus_expr] = arguments else {
2344 return Value::Void;
2345 };
2346 let anchor: i32 = eval_expression(ctx, anchor_expr).try_into().unwrap_or(0);
2347 let focus: i32 = eval_expression(ctx, focus_expr).try_into().unwrap_or(0);
2348 let Some((parent_inst, flat_idx)) = resolve_item_rc_from_ref(ctx, mr) else {
2349 return Value::Void;
2350 };
2351 let Some(adapter) = parent_inst.window_adapter_or_default() else {
2352 return Value::Void;
2353 };
2354 let parent_dyn = vtable::VRc::into_dyn(parent_inst);
2355 let item_rc = i_slint_core::items::ItemRc::new(parent_dyn, flat_idx as u32);
2356 if let Some(text_input) = vtable::VRef::downcast_pin::<TextInput>(item_rc.borrow()) {
2357 text_input.set_selection_offsets(&adapter, &item_rc, anchor, focus);
2358 }
2359 Value::Void
2360 }
2361 BuiltinFunction::RegisterCustomFontByPath => {
2362 if let Value::String(s) = eval_expression(ctx, &arguments[0])
2363 && let Some(root) = find_root_instance(ctx)
2364 {
2365 let result =
2368 root.try_window_adapter().map_err(|e| e.to_string()).and_then(|adapter| {
2369 adapter
2370 .renderer()
2371 .register_font_from_path(&std::path::PathBuf::from(s.as_str()))
2372 .map_err(|e| format!("Cannot load custom font {}: {e}", s.as_str()))
2373 });
2374 if let Err(err) = result {
2375 i_slint_core::debug_log!("{err}");
2376 }
2377 }
2378 Value::Void
2379 }
2380 BuiltinFunction::SetupMenuBar => crate::popup::setup_menubar(ctx, arguments),
2381 BuiltinFunction::ItemFontMetrics => {
2382 if let Some(Expression::PropertyReference(mr)) = arguments.first()
2383 && let Some((inst, flat_idx)) = resolve_item_rc_from_ref(ctx, mr)
2384 && let Some(adapter) = inst.window_adapter_or_default()
2385 {
2386 let item_rc =
2387 i_slint_core::items::ItemRc::new(vtable::VRc::into_dyn(inst), flat_idx as u32);
2388 let metrics = i_slint_core::items::slint_text_item_fontmetrics(
2389 &adapter,
2390 item_rc.borrow(),
2391 &item_rc,
2392 );
2393 return metrics.into();
2394 }
2395 i_slint_core::items::FontMetrics::default().into()
2396 }
2397 BuiltinFunction::ItemAbsolutePosition => {
2398 if let Some(Expression::PropertyReference(mr)) = arguments.first()
2399 && let Some((inst, flat_idx)) = resolve_item_rc_from_ref(ctx, mr)
2400 {
2401 let item_rc =
2402 i_slint_core::items::ItemRc::new(vtable::VRc::into_dyn(inst), flat_idx as u32);
2403 return item_rc.map_to_window(item_rc.geometry().origin).to_untyped().into();
2407 }
2408 i_slint_core::api::LogicalPosition::default().into()
2409 }
2410 BuiltinFunction::PathPointAt => {
2411 if let Some(Expression::PropertyReference(mr)) = arguments.first()
2412 && let Some((inst, flat_idx)) = resolve_item_rc_from_ref(ctx, mr)
2413 {
2414 let item_rc =
2415 i_slint_core::items::ItemRc::new(vtable::VRc::into_dyn(inst), flat_idx as u32);
2416 let t: f32 = eval_expression(ctx, &arguments[1]).try_into().unwrap_or_default();
2417 return item_rc
2418 .downcast::<i_slint_core::items::Path>()
2419 .unwrap()
2420 .as_pin_ref()
2421 .point_at(&item_rc, t)
2422 .to_untyped()
2423 .into();
2424 }
2425 panic!("internal error: argument to PathPointAt must be an element")
2426 }
2427 BuiltinFunction::PathAngleAt => {
2428 if let Some(Expression::PropertyReference(mr)) = arguments.first()
2429 && let Some((inst, flat_idx)) = resolve_item_rc_from_ref(ctx, mr)
2430 {
2431 let item_rc =
2432 i_slint_core::items::ItemRc::new(vtable::VRc::into_dyn(inst), flat_idx as u32);
2433 let t: f32 = eval_expression(ctx, &arguments[1]).try_into().unwrap_or_default();
2434 return item_rc
2435 .downcast::<i_slint_core::items::Path>()
2436 .unwrap()
2437 .as_pin_ref()
2438 .angle_at(&item_rc, t)
2439 .into();
2440 }
2441 panic!("internal error: argument to PathAngleAt must be an element")
2442 }
2443 BuiltinFunction::ArrayAny | BuiltinFunction::ArrayAll => {
2444 let is_all = matches!(f, BuiltinFunction::ArrayAll);
2445 let model: i_slint_core::model::ModelRc<Value> =
2446 eval_expression(ctx, &arguments[0]).try_into().unwrap();
2447 let Expression::Closure { arg_name, expression } = &arguments[1] else {
2448 panic!("internal error: Array.any/all expects a closure as second argument")
2449 };
2450 let mut predicate =
2451 |row_value| eval_array_row_predicate(arg_name, expression, ctx, row_value);
2452 Value::Bool(if is_all {
2453 i_slint_core::model::model_all(&model, &mut predicate)
2454 } else {
2455 i_slint_core::model::model_any(&model, &mut predicate)
2456 })
2457 }
2458 BuiltinFunction::ArrayFindIndex => {
2459 let model: i_slint_core::model::ModelRc<Value> =
2460 eval_expression(ctx, &arguments[0]).try_into().unwrap();
2461 let Expression::Closure { arg_name, expression } = &arguments[1] else {
2462 panic!("internal error: Array.find-index expects a closure as second argument")
2463 };
2464 Value::Number(i_slint_core::model::model_find_index(&model, |row_value| {
2465 eval_array_row_predicate(arg_name, expression, ctx, row_value)
2466 }) as f64)
2467 }
2468 BuiltinFunction::ImplicitLayoutInfo(orient) => {
2469 let constraint: f32 = arguments
2473 .get(1)
2474 .map(|e| eval_expression(ctx, e).try_into().unwrap_or(-1.))
2475 .unwrap_or(-1.);
2476 if let Some(Expression::PropertyReference(mr)) = arguments.first()
2477 && let Some((inst, flat_idx)) = resolve_item_rc_from_ref(ctx, mr)
2478 && let Some(adapter) = inst.window_adapter_or_default()
2479 {
2480 let item_rc =
2481 i_slint_core::items::ItemRc::new(vtable::VRc::into_dyn(inst), flat_idx as u32);
2482 return item_rc
2483 .borrow()
2484 .as_ref()
2485 .layout_info(
2486 llr_to_core_orientation(orient),
2487 constraint as _,
2488 &adapter,
2489 &item_rc,
2490 )
2491 .into();
2492 }
2493 i_slint_core::layout::LayoutInfo::default().into()
2494 }
2495 BuiltinFunction::Debug => {
2496 use i_slint_core::debug_log::*;
2497 let msg = to_string(ctx, &arguments[0]);
2498 let root = ctx
2499 .current
2500 .as_ref()
2501 .and_then(|c| c.root.get())
2502 .and_then(|w| w.upgrade())
2503 .map(vtable::VRc::into_dyn);
2504 if let Some(context) = root.as_ref().and_then(i_slint_core::window::context_for_root) {
2505 context.dispatch_log_message(LogMessage::new(
2506 LogMessageSource::SlintCode,
2507 log_message_location(source_location),
2508 format_args!("{msg}"),
2509 ));
2510 } else {
2511 log_message(LogMessage::new(
2512 LogMessageSource::SlintCode,
2513 log_message_location(source_location),
2514 format_args!("{msg}"),
2515 ));
2516 }
2517 Value::Void
2518 }
2519 BuiltinFunction::ArrayLength => match eval_expression(ctx, &arguments[0]) {
2520 Value::Model(m) => {
2523 m.model_tracker().track_row_count_changes();
2524 Value::Number(m.row_count() as f64)
2525 }
2526 _ => Value::Number(0.),
2527 },
2528 BuiltinFunction::ImageSize => {
2529 if let Value::Image(img) = eval_expression(ctx, &arguments[0]) {
2530 let size = img.size();
2531 let mut s = crate::api::Struct::default();
2532 s.set_field("width".to_string(), Value::Number(size.width as f64));
2533 s.set_field("height".to_string(), Value::Number(size.height as f64));
2534 Value::Struct(s)
2535 } else {
2536 Value::Void
2537 }
2538 }
2539 BuiltinFunction::ParseMarkdown => {
2540 let format_string: SharedString =
2541 eval_expression(ctx, &arguments[0]).try_into().unwrap_or_default();
2542 let args = eval_expression(ctx, &arguments[1]);
2543 let args: Vec<i_slint_core::styled_text::StyledText> = if let Value::Model(m) = args {
2544 (0..m.row_count())
2545 .filter_map(|i| match m.row_data(i)? {
2546 Value::StyledText(t) => Some(t),
2547 _ => None,
2548 })
2549 .collect()
2550 } else {
2551 Vec::new()
2552 };
2553 Value::StyledText(i_slint_core::styled_text::parse_markdown(&format_string, &args))
2554 }
2555 BuiltinFunction::StringToStyledText => {
2556 let string: SharedString =
2557 eval_expression(ctx, &arguments[0]).try_into().unwrap_or_default();
2558 Value::StyledText(i_slint_core::styled_text::string_to_styled_text(string.to_string()))
2559 }
2560 BuiltinFunction::Translate => {
2561 let original: SharedString = to_string(ctx, &arguments[0]);
2562 let context: SharedString = to_string(ctx, &arguments[1]);
2563 let domain: SharedString = to_string(ctx, &arguments[2]);
2564 let args = eval_expression(ctx, &arguments[3]);
2565 let Value::Model(args) = args else {
2566 return Value::String(original);
2567 };
2568 struct StringModelWrapper(ModelRc<Value>);
2569 impl i_slint_core::translations::FormatArgs for StringModelWrapper {
2570 type Output<'a> = SharedString;
2571 fn from_index(&self, index: usize) -> Option<SharedString> {
2572 self.0.row_data(index).and_then(|v| v.try_into().ok())
2573 }
2574 }
2575 let n: i32 = eval_expression(ctx, &arguments[4]).try_into().unwrap_or(0);
2576 let plural: SharedString = to_string(ctx, &arguments[5]);
2577 Value::String(i_slint_core::translations::translate(
2578 &original,
2579 &context,
2580 &domain,
2581 &StringModelWrapper(args),
2582 n,
2583 &plural,
2584 ))
2585 }
2586 BuiltinFunction::ShowPopupWindow => crate::popup::show_popup_window(ctx, arguments),
2587 BuiltinFunction::ClosePopupWindow => crate::popup::close_popup_window(ctx, arguments),
2588 BuiltinFunction::SetFocusItem => {
2589 if let Some(Expression::PropertyReference(mr)) = arguments.first()
2590 && let Some((inst, flat_idx)) = resolve_item_rc_from_ref(ctx, mr)
2591 && let Some(adapter) = find_window_adapter(ctx)
2592 {
2593 let dyn_rc = vtable::VRc::into_dyn(inst);
2594 let item_rc = i_slint_core::items::ItemRc::new(dyn_rc, flat_idx as u32);
2595 i_slint_core::window::WindowInner::from_pub(adapter.window()).set_focus_item(
2596 &item_rc,
2597 true,
2598 i_slint_core::input::FocusReason::Programmatic,
2599 );
2600 }
2601 Value::Void
2602 }
2603 BuiltinFunction::ClearFocusItem => {
2604 if let Some(Expression::PropertyReference(mr)) = arguments.first()
2605 && let Some((inst, flat_idx)) = resolve_item_rc_from_ref(ctx, mr)
2606 && let Some(adapter) = find_window_adapter(ctx)
2607 {
2608 let dyn_rc = vtable::VRc::into_dyn(inst);
2609 let item_rc = i_slint_core::items::ItemRc::new(dyn_rc, flat_idx as u32);
2610 i_slint_core::window::WindowInner::from_pub(adapter.window()).set_focus_item(
2611 &item_rc,
2612 false,
2613 i_slint_core::input::FocusReason::Programmatic,
2614 );
2615 }
2616 Value::Void
2617 }
2618 BuiltinFunction::MonthDayCount => {
2619 let m: u32 = eval_expression(ctx, &arguments[0]).try_into().unwrap_or(0);
2620 let y: i32 = eval_expression(ctx, &arguments[1]).try_into().unwrap_or(0);
2621 Value::Number(i_slint_core::date_time::month_day_count(m, y).unwrap_or(0) as f64)
2622 }
2623 BuiltinFunction::MonthOffset => {
2624 let m: u32 = eval_expression(ctx, &arguments[0]).try_into().unwrap_or(0);
2625 let y: i32 = eval_expression(ctx, &arguments[1]).try_into().unwrap_or(0);
2626 Value::Number(i_slint_core::date_time::month_offset(m, y) as f64)
2627 }
2628 BuiltinFunction::FormatDate => {
2629 let f: SharedString = to_string(ctx, &arguments[0]);
2630 let d: u32 = eval_expression(ctx, &arguments[1]).try_into().unwrap_or(0);
2631 let m: u32 = eval_expression(ctx, &arguments[2]).try_into().unwrap_or(0);
2632 let y: i32 = eval_expression(ctx, &arguments[3]).try_into().unwrap_or(0);
2633 Value::String(i_slint_core::date_time::format_date(&f, d, m, y))
2634 }
2635 BuiltinFunction::DateNow => {
2636 Value::Model(i_slint_core::model::ModelRc::new(i_slint_core::model::VecModel::from(
2637 i_slint_core::date_time::date_now()
2638 .into_iter()
2639 .map(|x| Value::Number(x as f64))
2640 .collect::<Vec<_>>(),
2641 )))
2642 }
2643 BuiltinFunction::ValidDate => {
2644 let d: SharedString = to_string(ctx, &arguments[0]);
2645 let f: SharedString = to_string(ctx, &arguments[1]);
2646 Value::Bool(i_slint_core::date_time::parse_date(d.as_str(), f.as_str()).is_some())
2647 }
2648 BuiltinFunction::ParseDate => {
2649 let d: SharedString = to_string(ctx, &arguments[0]);
2650 let f: SharedString = to_string(ctx, &arguments[1]);
2651 Value::Model(i_slint_core::model::ModelRc::new(i_slint_core::model::VecModel::from(
2652 i_slint_core::date_time::parse_date(d.as_str(), f.as_str())
2653 .map(|v| v.into_iter().map(|x| Value::Number(x as f64)).collect::<Vec<_>>())
2654 .unwrap_or_default(),
2655 )))
2656 }
2657 BuiltinFunction::ShowPopupMenu | BuiltinFunction::ShowPopupMenuInternal => {
2658 crate::popup::show_popup_menu(ctx, arguments)
2659 }
2660 BuiltinFunction::OpenUrl => {
2661 let url = to_string(ctx, &arguments[0]);
2662 let result = find_window_adapter(ctx)
2663 .map(|adapter| i_slint_core::open_url(&url, adapter.window()).is_ok())
2664 .unwrap_or(false);
2665 Value::Bool(result)
2666 }
2667 BuiltinFunction::RegisterCustomFontByMemory | BuiltinFunction::RegisterBitmapFont => {
2668 Value::Void
2670 }
2671 BuiltinFunction::StartTimer | BuiltinFunction::StopTimer => {
2672 Value::Void
2674 }
2675 }
2676}
2677
2678pub(crate) fn resolve_item_rc_from_ref(
2682 ctx: &EvalContext,
2683 mr: &MemberReference,
2684) -> Option<(vtable::VRc<i_slint_core::item_tree::ItemTreeVTable, crate::instance::Instance>, usize)>
2685{
2686 let MemberReference::Relative { parent_level, local_reference } = mr else { return None };
2687 let LocalMemberIndex::Native { item_index, .. } = &local_reference.reference else {
2688 return None;
2689 };
2690 let owner = try_walk_to(ctx, *parent_level, local_reference)?;
2691 let parent_inst = owner.root.get().and_then(|w| w.upgrade())?;
2692 let full_path = crate::item_tree_vtable::sub_component_path_of(&owner, &parent_inst);
2693 let flat_idx = find_flat_item_index(&parent_inst.item_table, &full_path, *item_index)?;
2694 Some((parent_inst, flat_idx))
2695}
2696
2697pub(crate) fn find_root_instance(
2701 ctx: &EvalContext,
2702) -> Option<vtable::VRc<i_slint_core::item_tree::ItemTreeVTable, crate::instance::Instance>> {
2703 let current = ctx.current.as_ref()?;
2704 let mut sub = current.clone();
2705 loop {
2706 if let Some(root) = sub.root.get()
2707 && let Some(inst) = root.upgrade()
2708 && inst.public_component_index.is_some()
2709 {
2710 return Some(inst);
2711 }
2712 let parent = sub.parent.upgrade()?;
2713 sub = Pin::new(parent);
2714 }
2715}
2716
2717pub(crate) fn find_window_adapter(
2719 ctx: &EvalContext,
2720) -> Option<i_slint_core::window::WindowAdapterRc> {
2721 find_root_instance(ctx)?.window_adapter_or_default()
2722}
2723
2724fn call_item_member_function(ctx: &EvalContext, function: &MemberReference) -> Value {
2728 use i_slint_core::items::{ContextMenu, SwipeGestureHandler, TextInput, WindowItem};
2729 let MemberReference::Relative { local_reference, .. } = function else {
2730 return Value::Void;
2731 };
2732 let LocalMemberIndex::Native { prop_name, .. } = &local_reference.reference else {
2733 return Value::Void;
2734 };
2735 let Some((parent_inst, flat_idx)) = resolve_item_rc_from_ref(ctx, function) else {
2736 return Value::Void;
2737 };
2738 let Some(adapter) = parent_inst.window_adapter_or_default() else { return Value::Void };
2739 let parent_dyn = vtable::VRc::into_dyn(parent_inst);
2740 let item_rc = i_slint_core::items::ItemRc::new(parent_dyn, flat_idx as u32);
2741 let item_ref = item_rc.borrow();
2742
2743 macro_rules! dispatch {
2746 ($item:expr, $name:expr; $($slint_name:literal => $rust_method:ident $(=> $into:ty)?),* $(,)?) => {
2747 match $name {
2748 $(
2749 $slint_name => {
2750 let res = $item.$rust_method(&adapter, &item_rc);
2751 $(let res: $into = res.into();)?
2752 return res.into();
2753 }
2754 )*
2755 _ => {}
2756 }
2757 };
2758 }
2759
2760 if let Some(text_input) = vtable::VRef::downcast_pin::<TextInput>(item_ref) {
2761 dispatch!(text_input, prop_name.as_str();
2762 "select-all" => select_all => (),
2763 "clear-selection" => clear_selection => (),
2764 "select-word" => select_word => (),
2765 "cut" => cut => (),
2766 "copy" => copy => (),
2767 "paste" => paste => (),
2768 "undo" => undo => (),
2769 "redo" => redo => (),
2770 );
2771 }
2772 if let Some(swipe) = vtable::VRef::downcast_pin::<SwipeGestureHandler>(item_rc.borrow()) {
2773 dispatch!(swipe, prop_name.as_str();
2774 "cancel" => cancel => (),
2775 );
2776 }
2777 if let Some(menu) = vtable::VRef::downcast_pin::<ContextMenu>(item_rc.borrow()) {
2778 dispatch!(menu, prop_name.as_str();
2779 "close" => close => (),
2780 "is-open" => is_open,
2781 );
2782 }
2783 if let Some(window) = vtable::VRef::downcast_pin::<WindowItem>(item_rc.borrow()) {
2784 match prop_name.as_str() {
2785 "hide" => {
2786 window.hide(&adapter, &item_rc);
2787 return Value::Void;
2788 }
2789 "close" => return Value::Bool(window.close(&adapter, &item_rc)),
2790 _ => {}
2791 }
2792 }
2793 unimplemented!("ItemMemberFunctionCall `{prop_name}`")
2794}