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 for (k, field_ty) in s.fields.iter() {
130 if str_value.get_field(k.as_str()).is_none() {
131 str_value
132 .set_field(k.to_string(), crate::eval::default_value_for_type(field_ty));
133 }
134 }
135 true
136 }
137 Type::Enumeration(en) => {
138 matches!(value, Value::EnumerationValue(name, _) if name == en.name.as_str())
139 }
140 Type::Keys => matches!(value, Value::Keys(_)),
141 Type::LayoutCache => matches!(value, Value::LayoutCache(_)),
142 Type::ArrayOfU16 => matches!(value, Value::ArrayOfU16(_)),
143 Type::ComponentFactory => matches!(value, Value::ComponentFactory(_)),
144 Type::StyledText => matches!(value, Value::StyledText(_)),
145 }
146}
147
148pub fn invoke(
150 instance: &VRc<ItemTreeVTable, Instance>,
151 name: &str,
152 args: &[Value],
153) -> Option<Value> {
154 use i_slint_compiler::langtype::Type;
155 let (public, sub) = resolve(instance)?;
156 let prop = find_public_property(public, name)?;
157 if !matches!(&prop.ty, Type::Callback(_) | Type::Function(_)) {
160 return None;
161 }
162 let ctx = EvalContext::new(sub);
163 Some(if matches!(&prop.ty, Type::Function(_)) || prop.prop.is_function() {
164 invoke_function(&ctx, &prop.prop, args.to_vec())
165 } else {
166 invoke_callback(&ctx, &prop.prop, args)
167 })
168}
169
170pub fn set_callback(
176 instance: &VRc<ItemTreeVTable, Instance>,
177 name: &str,
178 handler: Box<dyn Fn(&[Value]) -> Value>,
179) -> Result<(), ()> {
180 let (public, sub) = resolve(instance).ok_or(())?;
181 let prop = find_public_property(public, name).ok_or(())?;
182 match &prop.prop {
183 MemberReference::Relative { parent_level, local_reference } => {
184 let target = walk_to(sub, *parent_level, &local_reference.sub_component_path);
185 match &local_reference.reference {
186 i_slint_compiler::llr::LocalMemberIndex::Callback(idx) => {
187 let cb = Pin::as_ref(&target.callbacks[*idx]);
188 cb.set_handler(handler);
189 if let Some(tracker) = target.callback_trackers[*idx].as_ref() {
190 Pin::as_ref(tracker).mark_dirty();
191 }
192 Ok(())
193 }
194 i_slint_compiler::llr::LocalMemberIndex::Native {
195 item_index, prop_name, ..
196 } => {
197 Pin::as_ref(&target.items[*item_index]).set_callback_handler(prop_name, handler)
198 }
199 _ => Err(()),
200 }
201 }
202 MemberReference::Global { global_index, member } => {
203 let global_inst = instance.globals.get(*global_index).ok_or(())?;
207 let i_slint_compiler::llr::LocalMemberIndex::Callback(idx) = member else {
208 return Err(());
209 };
210 let cb = Pin::as_ref(&global_inst.callbacks[*idx]);
211 cb.set_handler(handler);
212 if let Some(tracker) = global_inst.callback_trackers[*idx].as_ref() {
213 Pin::as_ref(tracker).mark_dirty();
214 }
215 Ok(())
216 }
217 }
218}
219
220fn resolve(
221 instance: &VRc<ItemTreeVTable, Instance>,
222) -> Option<(&PublicComponent, Pin<Rc<SubComponentInstance>>)> {
223 let cu = &instance.root_sub_component.compilation_unit;
224 let public_index = instance.public_component_index?;
225 let public = cu.public_components.get(public_index)?;
226 Some((public, instance.root_sub_component.clone()))
227}
228
229fn resolve_global<'a>(
232 instance: &'a VRc<ItemTreeVTable, Instance>,
233 global_name: &str,
234 prop_name: &str,
235) -> Option<(&'a PublicProperty, Rc<crate::globals::GlobalInstance>)> {
236 let cu = &instance.root_sub_component.compilation_unit;
237 let (_global, global_instance) = instance.globals.find_by_name(cu, global_name)?;
238 let global_instance = global_instance.clone();
239 let needle = i_slint_compiler::parser::normalize_identifier(prop_name);
240 let global = &cu.globals[global_instance.global_idx];
241 let prop = global.public_properties.get(needle.as_str())?;
242 Some((prop, global_instance))
243}
244
245fn resolve_global_property(
251 instance: &VRc<ItemTreeVTable, Instance>,
252 source_inst: Rc<crate::globals::GlobalInstance>,
253 prop: &PublicProperty,
254) -> Option<(Rc<crate::globals::GlobalInstance>, i_slint_compiler::llr::LocalMemberIndex)> {
255 match &prop.prop {
256 MemberReference::Global { global_index, member } => {
257 let target = instance.globals.get(*global_index)?.clone();
258 Some((target, member.clone()))
259 }
260 MemberReference::Relative { local_reference, .. } => {
261 Some((source_inst, local_reference.reference.clone()))
262 }
263 }
264}
265
266pub fn get_global(
268 instance: &VRc<ItemTreeVTable, Instance>,
269 global_name: &str,
270 prop_name: &str,
271) -> Option<Value> {
272 let (prop, source_inst) = resolve_global(instance, global_name, prop_name)?;
273 let (target_inst, member) = resolve_global_property(instance, source_inst, prop)?;
274 match member {
275 i_slint_compiler::llr::LocalMemberIndex::Property(idx) => {
276 Some(Pin::as_ref(&target_inst.properties[idx]).get())
277 }
278 _ => None,
279 }
280}
281
282pub fn set_global(
284 instance: &VRc<ItemTreeVTable, Instance>,
285 global_name: &str,
286 prop_name: &str,
287 mut value: Value,
288) -> Result<(), SetPropertyError> {
289 let (prop, source_inst) =
290 resolve_global(instance, global_name, prop_name).ok_or(SetPropertyError::NoSuchProperty)?;
291 if prop.read_only() {
292 return Err(SetPropertyError::AccessDenied);
293 }
294 if !check_and_coerce(&mut value, &prop.ty) {
295 return Err(SetPropertyError::WrongType);
296 }
297 let (target_inst, member) = resolve_global_property(instance, source_inst, prop)
298 .ok_or(SetPropertyError::NoSuchProperty)?;
299 match member {
300 i_slint_compiler::llr::LocalMemberIndex::Property(_) => {
301 crate::eval::store_global(&target_inst, &member, value);
302 Ok(())
303 }
304 _ => Err(SetPropertyError::NoSuchProperty),
305 }
306}
307
308pub fn set_global_callback(
311 instance: &VRc<ItemTreeVTable, Instance>,
312 global_name: &str,
313 callback_name: &str,
314 handler: Box<dyn Fn(&[Value]) -> Value>,
315) -> Result<(), ()> {
316 let (prop, source_inst) = resolve_global(instance, global_name, callback_name).ok_or(())?;
317 let (target_inst, member) = resolve_global_property(instance, source_inst, prop).ok_or(())?;
318 match member {
319 i_slint_compiler::llr::LocalMemberIndex::Callback(idx) => {
320 if let Some(native) = &target_inst.native {
321 let g = &target_inst.compilation_unit.globals[target_inst.global_idx];
322 return native.as_ref().set_callback_handler(&g.callbacks[idx].name, handler);
323 }
324 let cb = Pin::as_ref(&target_inst.callbacks[idx]);
325 cb.set_handler(handler);
326 if let Some(tracker) = target_inst.callback_trackers[idx].as_ref() {
327 Pin::as_ref(tracker).mark_dirty();
328 }
329 Ok(())
330 }
331 _ => Err(()),
332 }
333}
334
335pub fn invoke_global(
337 instance: &VRc<ItemTreeVTable, Instance>,
338 global_name: &str,
339 name: &str,
340 args: &[Value],
341) -> Option<Value> {
342 use i_slint_compiler::llr::LocalMemberIndex;
343 let (prop, source_inst) = resolve_global(instance, global_name, name)?;
344 let (target_inst, member) = resolve_global_property(instance, source_inst, prop)?;
345 match member {
346 LocalMemberIndex::Callback(idx) => {
347 let cu = &target_inst.compilation_unit;
348 let cb_decl = &cu.globals[target_inst.global_idx].callbacks[idx];
349 if let Some(native) = &target_inst.native {
350 let res =
351 native.as_ref().invoke_callback(&cb_decl.name, args).unwrap_or(Value::Void);
352 return Some(crate::eval::ensure_typed_default(res, &cb_decl.ret_ty));
353 }
354 let cb = Pin::as_ref(&target_inst.callbacks[idx]);
355 Some(crate::eval::ensure_typed_default(cb.call(args), &cb_decl.ret_ty))
356 }
357 LocalMemberIndex::Function(fn_idx) => {
358 let cu = &instance.root_sub_component.compilation_unit;
359 let global = &cu.globals[target_inst.global_idx];
360 let function = &global.functions[fn_idx];
361 let expr = function.code.borrow().clone();
362 let mut ctx = crate::eval::EvalContext::for_global(
363 std::rc::Rc::downgrade(&instance.globals),
364 cu.clone(),
365 );
366 ctx.function_arg_types = function.args.clone();
367 ctx.function_arguments = args.to_vec();
368 Some(crate::eval::eval_expression(&mut ctx, &expr))
369 }
370 _ => None,
371 }
372}
373
374fn walk_to(
375 start: Pin<Rc<SubComponentInstance>>,
376 parent_level: usize,
377 path: &[i_slint_compiler::llr::SubComponentInstanceIdx],
378) -> Pin<Rc<SubComponentInstance>> {
379 crate::eval::walk_sub_path(crate::eval::walk_parent(&start, parent_level), path)
380}