1use crate::Value;
12use crate::api::SetPropertyError;
13use crate::eval::{EvalContext, invoke_callback, invoke_function, load_property, store_property};
14use crate::instance::{Instance, SubComponentInstance};
15use i_slint_compiler::langtype::Type;
16use i_slint_compiler::llr::{MemberReference, PublicComponent, PublicProperty};
17use i_slint_core::item_tree::ItemTreeVTable;
18use i_slint_core::model::Model;
19use std::pin::Pin;
20use std::rc::Rc;
21use vtable::VRc;
22
23pub fn find_public_property<'a>(
27 public: &'a PublicComponent,
28 name: &str,
29) -> Option<&'a PublicProperty> {
30 let normalized = i_slint_compiler::parser::normalize_identifier(name);
31 public.public_properties.get(normalized.as_str())
32}
33
34pub fn get(instance: &VRc<ItemTreeVTable, Instance>, name: &str) -> Option<Value> {
36 let (public, sub) = resolve(instance)?;
37 let prop = find_public_property(public, name)?;
38 if !prop.ty.is_property_type() {
39 return None;
40 }
41 let ctx = EvalContext::new(sub);
42 Some(load_property(&ctx, &prop.prop))
43}
44
45pub fn set(
47 instance: &VRc<ItemTreeVTable, Instance>,
48 name: &str,
49 mut value: Value,
50) -> Result<(), SetPropertyError> {
51 let (public, sub) = resolve(instance).ok_or(SetPropertyError::NoSuchProperty)?;
52 let prop = find_public_property(public, name).ok_or(SetPropertyError::NoSuchProperty)?;
53 if !prop.ty.is_property_type() {
54 return Err(SetPropertyError::NoSuchProperty);
55 }
56 if prop.read_only() {
57 return Err(SetPropertyError::AccessDenied);
58 }
59 if !check_and_coerce(&mut value, &prop.ty) {
60 return Err(SetPropertyError::WrongType);
61 }
62 let ctx = EvalContext::new(sub);
63 store_property(&ctx, &prop.prop, value);
64 Ok(())
65}
66
67pub(crate) fn check_and_coerce(value: &mut Value, ty: &Type) -> bool {
70 match ty {
71 Type::Void => true,
72 Type::Invalid
73 | Type::InferredProperty
74 | Type::InferredCallback
75 | Type::Callback(_)
76 | Type::Function(_)
77 | Type::ElementReference
78 | Type::Closure => false,
79 Type::Float32 | Type::Int32 => matches!(value, Value::Number(_)),
80 Type::String => matches!(value, Value::String(_)),
81 Type::Color | Type::Brush => matches!(value, Value::Brush(_)),
82 Type::UnitProduct(_)
83 | Type::Duration
84 | Type::PhysicalLength
85 | Type::LogicalLength
86 | Type::Rem
87 | Type::Angle
88 | Type::Percent => matches!(value, Value::Number(_)),
89 Type::Image => matches!(value, Value::Image(_)),
90 Type::Bool => matches!(value, Value::Bool(_)),
91 Type::Model => matches!(value, Value::Model(_) | Value::Bool(_) | Value::Number(_)),
92 Type::PathData => matches!(value, Value::PathData(_)),
93 Type::DataTransfer => matches!(value, Value::DataTransfer(_)),
94 Type::Easing => matches!(value, Value::EasingCurve(_)),
95 Type::MouseCursor => matches!(value, Value::MouseCursorInner(_)),
96 Type::Array(inner) => match value {
97 Value::Model(m) => {
98 let mut ok = true;
99 for i in 0..m.row_count() {
100 if let Some(mut v) = m.row_data(i)
101 && !check_and_coerce(&mut v, inner)
102 {
103 ok = false;
104 break;
105 }
106 }
107 ok
108 }
109 _ => false,
110 },
111 Type::Struct(s) => {
112 let Value::Struct(str_value) = value else { return false };
113 let keys: Vec<String> = str_value.iter().map(|(k, _)| k.to_string()).collect();
116 for k in keys {
117 let Some(field_ty) = s.fields.get(k.as_str()) else {
118 return false;
119 };
120 let Some(v) = str_value.get_field(&k).cloned() else { continue };
121 let mut v = v;
122 if !check_and_coerce(&mut v, field_ty) {
123 return false;
124 }
125 str_value.set_field(k, v);
126 }
127 crate::eval::fill_missing_struct_fields(str_value, s);
128 true
129 }
130 Type::Enumeration(en) => {
131 matches!(value, Value::EnumerationValue(name, _) if name == en.name.as_str())
132 }
133 Type::Keys => matches!(value, Value::Keys(_)),
134 Type::LayoutCache => matches!(value, Value::LayoutCache(_)),
135 Type::ArrayOfU16 => matches!(value, Value::ArrayOfU16(_)),
136 Type::ComponentFactory => matches!(value, Value::ComponentFactory(_)),
137 Type::StyledText => matches!(value, Value::StyledText(_)),
138 }
139}
140
141pub fn invoke(
143 instance: &VRc<ItemTreeVTable, Instance>,
144 name: &str,
145 args: &[Value],
146) -> Option<Value> {
147 use i_slint_compiler::langtype::Type;
148 let (public, sub) = resolve(instance)?;
149 let prop = find_public_property(public, name)?;
150 if !matches!(&prop.ty, Type::Callback(_) | Type::Function(_)) {
153 return None;
154 }
155 let ctx = EvalContext::new(sub);
156 Some(if matches!(&prop.ty, Type::Function(_)) || prop.prop.is_function() {
157 invoke_function(&ctx, &prop.prop, args.to_vec())
158 } else {
159 invoke_callback(&ctx, &prop.prop, args)
160 })
161}
162
163pub fn set_callback(
169 instance: &VRc<ItemTreeVTable, Instance>,
170 name: &str,
171 handler: Box<dyn Fn(&[Value]) -> Value>,
172) -> Result<(), ()> {
173 let (public, sub) = resolve(instance).ok_or(())?;
174 let prop = find_public_property(public, name).ok_or(())?;
175 match &prop.prop {
176 MemberReference::Relative { parent_level, local_reference } => {
177 let target = walk_to(sub, *parent_level, local_reference).ok_or(())?;
178 match &local_reference.reference {
179 i_slint_compiler::llr::LocalMemberIndex::Callback(idx) => {
180 let cb = Pin::as_ref(&target.callbacks[*idx]);
181 cb.set_handler(handler);
182 if let Some(tracker) = target.callback_trackers[*idx].as_ref() {
183 Pin::as_ref(tracker).mark_dirty();
184 }
185 Ok(())
186 }
187 i_slint_compiler::llr::LocalMemberIndex::Native {
188 item_index, prop_name, ..
189 } => {
190 Pin::as_ref(&target.items[*item_index]).set_callback_handler(prop_name, handler)
191 }
192 _ => Err(()),
193 }
194 }
195 MemberReference::Global { global_index, member } => {
196 let global_inst = instance.globals.get(*global_index).ok_or(())?;
200 let i_slint_compiler::llr::LocalMemberIndex::Callback(idx) = member else {
201 return Err(());
202 };
203 let cb = Pin::as_ref(&global_inst.callbacks[*idx]);
204 cb.set_handler(handler);
205 if let Some(tracker) = global_inst.callback_trackers[*idx].as_ref() {
206 Pin::as_ref(tracker).mark_dirty();
207 }
208 Ok(())
209 }
210 }
211}
212
213fn resolve(
214 instance: &VRc<ItemTreeVTable, Instance>,
215) -> Option<(&PublicComponent, Pin<Rc<SubComponentInstance>>)> {
216 let cu = &instance.root_sub_component.compilation_unit;
217 let public_index = instance.public_component_index?;
218 let public = cu.public_components.get(public_index)?;
219 Some((public, instance.root_sub_component.clone()))
220}
221
222fn resolve_global<'a>(
225 instance: &'a VRc<ItemTreeVTable, Instance>,
226 global_name: &str,
227 prop_name: &str,
228) -> Option<(&'a PublicProperty, Rc<crate::globals::GlobalInstance>)> {
229 let cu = &instance.root_sub_component.compilation_unit;
230 let (_global, global_instance) = instance.globals.find_by_name(cu, global_name)?;
231 let global_instance = global_instance.clone();
232 let needle = i_slint_compiler::parser::normalize_identifier(prop_name);
233 let global = &cu.globals[global_instance.global_idx];
234 let prop = global.public_properties.get(needle.as_str())?;
235 Some((prop, global_instance))
236}
237
238fn resolve_global_property(
244 instance: &VRc<ItemTreeVTable, Instance>,
245 source_inst: Rc<crate::globals::GlobalInstance>,
246 prop: &PublicProperty,
247) -> Option<(Rc<crate::globals::GlobalInstance>, i_slint_compiler::llr::LocalMemberIndex)> {
248 match &prop.prop {
249 MemberReference::Global { global_index, member } => {
250 let target = instance.globals.get(*global_index)?.clone();
251 Some((target, member.clone()))
252 }
253 MemberReference::Relative { local_reference, .. } => {
254 Some((source_inst, local_reference.reference.clone()))
255 }
256 }
257}
258
259pub fn get_global(
261 instance: &VRc<ItemTreeVTable, Instance>,
262 global_name: &str,
263 prop_name: &str,
264) -> Option<Value> {
265 let (prop, source_inst) = resolve_global(instance, global_name, prop_name)?;
266 let (target_inst, member) = resolve_global_property(instance, source_inst, prop)?;
267 match member {
268 i_slint_compiler::llr::LocalMemberIndex::Property(idx) => {
269 Some(Pin::as_ref(&target_inst.properties[idx]).get())
270 }
271 _ => None,
272 }
273}
274
275pub fn set_global(
277 instance: &VRc<ItemTreeVTable, Instance>,
278 global_name: &str,
279 prop_name: &str,
280 mut value: Value,
281) -> Result<(), SetPropertyError> {
282 let (prop, source_inst) =
283 resolve_global(instance, global_name, prop_name).ok_or(SetPropertyError::NoSuchProperty)?;
284 if prop.read_only() {
285 return Err(SetPropertyError::AccessDenied);
286 }
287 if !check_and_coerce(&mut value, &prop.ty) {
288 return Err(SetPropertyError::WrongType);
289 }
290 let (target_inst, member) = resolve_global_property(instance, source_inst, prop)
291 .ok_or(SetPropertyError::NoSuchProperty)?;
292 match member {
293 i_slint_compiler::llr::LocalMemberIndex::Property(_) => {
294 crate::eval::store_global(&target_inst, &member, value);
295 Ok(())
296 }
297 _ => Err(SetPropertyError::NoSuchProperty),
298 }
299}
300
301pub fn set_global_callback(
304 instance: &VRc<ItemTreeVTable, Instance>,
305 global_name: &str,
306 callback_name: &str,
307 handler: Box<dyn Fn(&[Value]) -> Value>,
308) -> Result<(), ()> {
309 let (prop, source_inst) = resolve_global(instance, global_name, callback_name).ok_or(())?;
310 let (target_inst, member) = resolve_global_property(instance, source_inst, prop).ok_or(())?;
311 match member {
312 i_slint_compiler::llr::LocalMemberIndex::Callback(idx) => {
313 if let Some(native) = &target_inst.native {
314 let g = &target_inst.compilation_unit.globals[target_inst.global_idx];
315 return native.as_ref().set_callback_handler(&g.callbacks[idx].name, handler);
316 }
317 let cb = Pin::as_ref(&target_inst.callbacks[idx]);
318 cb.set_handler(handler);
319 if let Some(tracker) = target_inst.callback_trackers[idx].as_ref() {
320 Pin::as_ref(tracker).mark_dirty();
321 }
322 Ok(())
323 }
324 _ => Err(()),
325 }
326}
327
328pub fn invoke_global(
330 instance: &VRc<ItemTreeVTable, Instance>,
331 global_name: &str,
332 name: &str,
333 args: &[Value],
334) -> Option<Value> {
335 use i_slint_compiler::llr::LocalMemberIndex;
336 let (prop, source_inst) = resolve_global(instance, global_name, name)?;
337 let (target_inst, member) = resolve_global_property(instance, source_inst, prop)?;
338 match member {
339 LocalMemberIndex::Callback(idx) => {
340 let cu = &target_inst.compilation_unit;
341 let cb_decl = &cu.globals[target_inst.global_idx].callbacks[idx];
342 if let Some(native) = &target_inst.native {
343 let res =
344 native.as_ref().invoke_callback(&cb_decl.name, args).unwrap_or(Value::Void);
345 return Some(crate::eval::ensure_typed_default(res, &cb_decl.ret_ty));
346 }
347 let cb = Pin::as_ref(&target_inst.callbacks[idx]);
348 Some(crate::eval::ensure_typed_default(cb.call(args), &cb_decl.ret_ty))
349 }
350 LocalMemberIndex::Function(fn_idx) => {
351 let cu = &instance.root_sub_component.compilation_unit;
352 let global = &cu.globals[target_inst.global_idx];
353 let function = &global.functions[fn_idx];
354 let expr = function.code.borrow().clone();
355 let mut ctx = crate::eval::EvalContext::for_global(
356 std::rc::Rc::downgrade(&instance.globals),
357 cu.clone(),
358 );
359 ctx.function_arg_types = function.args.clone();
360 ctx.function_arguments = args.to_vec();
361 Some(crate::eval::eval_expression(&mut ctx, &expr))
362 }
363 _ => None,
364 }
365}
366
367fn walk_to(
368 start: Pin<Rc<SubComponentInstance>>,
369 parent_level: usize,
370 local_reference: &i_slint_compiler::llr::LocalMemberReference,
371) -> Option<Pin<Rc<SubComponentInstance>>> {
372 let base = crate::eval::try_walk_parent(&start, parent_level)?;
373 Some(crate::eval::walk_sub_path(base, &local_reference.sub_component_path))
374}