Skip to main content

slint_interpreter/
item_tree_vtable.rs

1// Copyright © SixtyFPS GmbH <info@slint.dev>
2// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0
3
4//! `ItemTreeVTable` implementation for [`Instance`].
5//!
6//! A single static vtable serves every runtime `Instance`; vtable calls
7//! walk the instance's sub-component tree on demand rather than through
8//! a precomputed offset table.
9
10use crate::instance::Instance;
11use i_slint_core::SharedString;
12use i_slint_core::accessibility::{
13    AccessibilityAction, AccessibleStringProperty, SupportedAccessibilityAction,
14};
15use i_slint_core::item_tree::{
16    IndexRange, ItemTree, ItemTreeNode, ItemTreeVTable, ItemVisitorVTable, ItemWeak,
17    TraversalOrder, VisitChildrenResult,
18};
19use i_slint_core::items::{AccessibleRole, ItemVTable};
20use i_slint_core::layout::{LayoutInfo, Orientation};
21use i_slint_core::lengths::LogicalRect;
22use i_slint_core::slice::Slice;
23use i_slint_core::window::WindowAdapterRc;
24use std::pin::Pin;
25use vtable::{VRef, VRefMut, VWeak};
26
27i_slint_core::ItemTreeVTable_static!(static INTERPRETER_INSTANCE_VT for Instance);
28
29/// Find the `sub_component_path` (sequence of `SubComponentInstanceIdx`)
30/// from the parent instance's root to the given sub-component. Used by
31/// `parent_node` to match entries in the parent's `dynamic_table`.
32pub(crate) fn sub_component_path_of(
33    target: &crate::instance::SubComponentInstance,
34    parent_root: &Instance,
35) -> Vec<i_slint_compiler::llr::SubComponentInstanceIdx> {
36    fn walk(
37        current: &crate::instance::SubComponentInstance,
38        target_ptr: *const crate::instance::SubComponentInstance,
39        path: &mut Vec<i_slint_compiler::llr::SubComponentInstanceIdx>,
40    ) -> bool {
41        if std::ptr::eq(current as *const _, target_ptr) {
42            return true;
43        }
44        for (idx, nested) in current.sub_components.iter().enumerate() {
45            path.push(idx.into());
46            if walk(nested, target_ptr, path) {
47                return true;
48            }
49            path.pop();
50        }
51        false
52    }
53    let mut path = Vec::new();
54    walk(&parent_root.root_sub_component, target as *const _, &mut path);
55    path
56}
57
58impl i_slint_core::item_tree::ItemTree for Instance {
59    fn visit_children_item(
60        self: Pin<&Self>,
61        index: isize,
62        order: TraversalOrder,
63        visitor: VRefMut<'_, ItemVisitorVTable>,
64    ) -> VisitChildrenResult {
65        let this = self.get_ref();
66        let weak = this.self_weak.get().unwrap().clone();
67        if index >= 0 && this.z_sort_table.get(index as usize).is_some_and(|e| e.is_some()) {
68            i_slint_core::item_tree::visit_item_tree_z_sorted(
69                &vtable::VRc::into_dyn(weak.upgrade().unwrap()),
70                &this.tree_nodes[..],
71                index,
72                order,
73                visitor,
74                &mut |order, visitor, dyn_index| {
75                    self.visit_dynamic_children(dyn_index, order, visitor)
76                },
77                &mut |push| self.collect_z_sorted_children(index, push),
78            )
79        } else {
80            i_slint_core::item_tree::visit_item_tree(
81                &vtable::VRc::into_dyn(weak.upgrade().unwrap()),
82                &this.tree_nodes[..],
83                index,
84                order,
85                visitor,
86                &mut |order, visitor, dyn_index| {
87                    self.visit_dynamic_children(dyn_index, order, visitor)
88                },
89            )
90        }
91    }
92
93    fn get_item_ref(self: Pin<&Self>, index: u32) -> Pin<VRef<'_, ItemVTable>> {
94        // The item_table is indexed by flat tree index (same ordering as
95        // `tree_nodes`), pointing at the sub-component path + item slot
96        // that backs each static item node.
97        let this = self.get_ref();
98        let entry = this
99            .item_table
100            .get(index as usize)
101            .and_then(Option::as_ref)
102            .expect("get_item_ref: tree index is not a static item");
103        // Walk the path by borrowing — every intermediate sub-component
104        // is owned by its parent via `sub_components`, so a reference
105        // to the leaf is valid for the lifetime of `self`.
106        let mut current: &crate::instance::SubComponentInstance = &this.root_sub_component;
107        for &sub_idx in entry.0.iter() {
108            current = &current.sub_components[sub_idx];
109        }
110        Pin::as_ref(&current.items[entry.1]).as_item_ref()
111    }
112
113    fn ensure_instantiated(self: Pin<&Self>) -> bool {
114        self.get_ref().ensure_instantiated()
115    }
116
117    fn get_subtree_range(self: Pin<&Self>, index: u32) -> IndexRange {
118        let Some((sub, rep_idx)) = self.get_ref().dynamic_at(index) else {
119            return IndexRange { start: 0, end: 0 };
120        };
121        // Trigger lazy instantiation: for a regular repeater this fills
122        // the model rows; for a `ComponentContainer` it evaluates the
123        // factory and stores the embedded tree on the container item.
124        self.get_ref().ensure_updated(index);
125        if let Some(cc) = crate::instance::component_container_item(&sub, rep_idx) {
126            return cc.subtree_range();
127        }
128        let repeater = &sub.repeaters[rep_idx];
129        let range = repeater.range();
130        IndexRange { start: range.start, end: range.end }
131    }
132
133    fn get_subtree(
134        self: Pin<&Self>,
135        index: u32,
136        subindex: usize,
137        result: &mut VWeak<ItemTreeVTable, vtable::Dyn>,
138    ) {
139        self.get_ref().ensure_updated(index);
140        let Some((sub, rep_idx)) = self.get_ref().dynamic_at(index) else {
141            return;
142        };
143        if let Some(cc) = crate::instance::component_container_item(&sub, rep_idx) {
144            if subindex == 0 {
145                *result = cc.subtree_component();
146            }
147            return;
148        }
149        let repeater = &sub.repeaters[rep_idx];
150        if let Some(instance) = repeater.instance_at(subindex) {
151            *result = vtable::VRc::downgrade(&vtable::VRc::into_dyn(instance));
152        }
153    }
154
155    fn get_item_tree(self: Pin<&Self>) -> Slice<'_, ItemTreeNode> {
156        Slice::from(&*self.get_ref().tree_nodes)
157    }
158
159    fn parent_node(self: Pin<&Self>, result: &mut ItemWeak) {
160        // If this is a repeated sub-tree, point at the repeater's placeholder
161        // in the parent instance. For a popup (parented but not repeated),
162        // point at the parent instance's root item.
163        let this = self.get_ref();
164        // `embedded_in` records where in the outer item tree this instance
165        // lives. Return that as the parent; the core walks back through it
166        // the same way as a repeated DynamicTree node.
167        if let Some((outer_weak, outer_index)) = this.embedded_in.get()
168            && let Some(outer) = outer_weak.upgrade()
169        {
170            *result = i_slint_core::items::ItemRc::new(outer, *outer_index).downgrade();
171            return;
172        }
173        let Some(parent_sub) = this.parent_instance.upgrade() else { return };
174        let Some(parent_root_vrc) = parent_sub.root.get().and_then(|w| w.upgrade()) else {
175            return;
176        };
177        let parent_dyn = vtable::VRc::into_dyn(parent_root_vrc.clone());
178        if let Some((_, repeater_idx)) = this.root_sub_component.repeated_in.get() {
179            // Return the DynamicTree node itself in the parent's flat tree.
180            // `parent_item` in i_slint_core detects that the returned parent
181            // is a DynamicTree and walks one more level up to its parent
182            // item. Returning the DynamicTree's own parent here skips that
183            // adjustment and gives the caller the wrong node.
184            let rep_idx = *repeater_idx;
185            let parent_path = sub_component_path_of(&parent_sub, &parent_root_vrc);
186            for (flat, entry) in parent_root_vrc.dynamic_table.iter().enumerate() {
187                if let Some((path, idx)) = entry.as_ref()
188                    && path.as_ref() == parent_path.as_slice()
189                    && *idx == rep_idx
190                {
191                    *result = i_slint_core::items::ItemRc::new(parent_dyn, flat as u32).downgrade();
192                    return;
193                }
194            }
195        } else {
196            // Popup case: ItemRc::new_root on the parent instance, which the
197            // caller uses to traverse up to the window.
198            *result = i_slint_core::items::ItemRc::new(parent_dyn, 0).downgrade();
199        }
200    }
201
202    fn embed_component(
203        self: Pin<&Self>,
204        parent: &VWeak<ItemTreeVTable>,
205        parent_item_tree_index: u32,
206    ) -> bool {
207        // Stash the outer item tree handle so `parent_node` can point at
208        // the ComponentContainer slot that substitutes this instance in.
209        let this = self.get_ref();
210        this.embedded_in.set((parent.clone(), parent_item_tree_index)).is_ok()
211    }
212
213    fn subtree_index(self: Pin<&Self>) -> usize {
214        // For repeated instances, return the model index so tab-focus
215        // traversal can step to the next sibling via get_subtree(idx+1).
216        let this = self.get_ref();
217        let sc = &this.root_sub_component.compilation_unit.sub_components
218            [this.root_sub_component.sub_component_idx];
219        for (idx, prop) in sc.properties.iter_enumerated() {
220            if prop.name == "model_index"
221                && let crate::Value::Number(n) =
222                    Pin::as_ref(&this.root_sub_component.properties[idx]).get()
223            {
224                return n as usize;
225            }
226        }
227        // Conditional: only one instance, index 0.
228        0
229    }
230
231    fn layout_info(self: Pin<&Self>, orientation: Orientation) -> LayoutInfo {
232        let this = self.get_ref();
233        let sc_idx = this.root_sub_component.sub_component_idx;
234        let cu = &this.root_sub_component.compilation_unit;
235        let sc = &cu.sub_components[sc_idx];
236        let expr = match orientation {
237            Orientation::Horizontal => sc.layout_info_h.borrow(),
238            Orientation::Vertical => sc.layout_info_v.borrow(),
239        };
240        let mut ctx = crate::eval::EvalContext::new(this.root_sub_component.clone());
241        crate::eval::eval_expression(&mut ctx, &expr).try_into().unwrap_or_default()
242    }
243
244    fn item_geometry(self: Pin<&Self>, item_index: u32) -> LogicalRect {
245        // `item_index` is the flat tree index. Resolve it via `item_table`
246        // into the owning sub-component, then look up the geometry by
247        // the item's `index_in_tree`. `sc.geometries` is keyed by the
248        // sub-component-local tree index (set by `generate_item_indices`),
249        // not by the raw `ItemInstanceIdx` slot.
250        let this = self.get_ref();
251        let Some(entry) = this.item_table.get(item_index as usize).and_then(Option::as_ref) else {
252            return LogicalRect::default();
253        };
254        let mut owner_rc = this.root_sub_component.clone();
255        for &sub_idx in entry.0.iter() {
256            owner_rc = owner_rc.sub_components[sub_idx].clone();
257        }
258        let cu = owner_rc.compilation_unit.clone();
259        let sc = &cu.sub_components[owner_rc.sub_component_idx];
260        let item = &sc.items[entry.1];
261        // When the flat tree crosses into a sub-component (non-empty path)
262        // and lands on its root element (local tree index 0), the inner
263        // root's geometry can duplicate the parent's placement: the
264        // compiler's `adjust_geometry_for_injected_parent` pass hoists the
265        // original position into an injected wrapper item, and the inner
266        // root applies the same offset again via its `y: root-1_y`
267        // binding. So read the wrapper's geometry from the *parent*
268        // sub-component at the placement slot and never query the inner
269        // root. Fall through when the parent
270        // has no entry (the sub-component was placed directly with no
271        // wrapper, e.g. `box := SpinBox {}` inside a Window — then the
272        // inner root's own geometry is the correct placement).
273        let parent_placement = if !entry.0.is_empty() && item.index_in_tree == 0 {
274            let mut parent_rc = this.root_sub_component.clone();
275            for &sub_idx in &entry.0[..entry.0.len() - 1] {
276                parent_rc = parent_rc.sub_components[sub_idx].clone();
277            }
278            let placement = entry.0[entry.0.len() - 1];
279            let parent_sc = &cu.sub_components[parent_rc.sub_component_idx];
280            let placement_idx = parent_sc.sub_components[placement].index_in_tree as usize;
281            parent_sc
282                .geometries
283                .get(placement_idx)
284                .and_then(|g| g.clone())
285                .map(|expr| (expr, parent_rc))
286        } else {
287            None
288        };
289        let (expr_cell, ctx_owner) = if let Some(pair) = parent_placement {
290            pair
291        } else {
292            let tree_local_idx = item.index_in_tree as usize;
293            match sc.geometries.get(tree_local_idx) {
294                Some(Some(expr)) => (expr.clone(), owner_rc),
295                _ => return LogicalRect::default(),
296            }
297        };
298        let expr = expr_cell.borrow();
299        let mut ctx = crate::eval::EvalContext::new(ctx_owner);
300        let crate::Value::Struct(s) = crate::eval::eval_expression(&mut ctx, &expr) else {
301            return LogicalRect::default();
302        };
303        let as_f32 = |name: &str| -> f32 {
304            match s.get_field(name) {
305                Some(crate::Value::Number(n)) => *n as f32,
306                _ => 0.0,
307            }
308        };
309        LogicalRect::new(
310            i_slint_core::lengths::LogicalPoint::new(as_f32("x"), as_f32("y")),
311            i_slint_core::lengths::LogicalSize::new(as_f32("width"), as_f32("height")),
312        )
313    }
314
315    fn accessible_role(self: Pin<&Self>, item_index: u32) -> AccessibleRole {
316        let Some((owner, local_idx)) = resolve_accessible_item(self.get_ref(), item_index) else {
317            return AccessibleRole::default();
318        };
319        let cu = owner.compilation_unit.clone();
320        let sc = &cu.sub_components[owner.sub_component_idx];
321        let Some(expr) = sc.accessible_prop.get(&(local_idx, "Role".to_string())) else {
322            return AccessibleRole::default();
323        };
324        let mut ctx = crate::eval::EvalContext::new(owner);
325        crate::eval::eval_expression(&mut ctx, &expr.borrow()).try_into().unwrap_or_default()
326    }
327
328    fn accessible_string_property(
329        self: Pin<&Self>,
330        item_index: u32,
331        what: AccessibleStringProperty,
332        result: &mut SharedString,
333    ) -> bool {
334        let what_str = accessible_string_property_name(what);
335        for (owner, local_idx) in resolve_accessible_candidates(self.get_ref(), item_index) {
336            let cu = owner.compilation_unit.clone();
337            let sc = &cu.sub_components[owner.sub_component_idx];
338            if let Some(expr) = sc.accessible_prop.get(&(local_idx, what_str.clone())) {
339                let mut ctx = crate::eval::EvalContext::new(owner);
340                if let crate::Value::String(s) =
341                    crate::eval::eval_expression(&mut ctx, &expr.borrow())
342                {
343                    *result = s;
344                    return true;
345                }
346            }
347        }
348        false
349    }
350
351    fn accessibility_action(self: Pin<&Self>, item_index: u32, action: &AccessibilityAction) {
352        let what = format!("Action{}", accessibility_action_name(action));
353        for (owner, local_idx) in resolve_accessible_candidates(self.get_ref(), item_index) {
354            let cu = owner.compilation_unit.clone();
355            let sc = &cu.sub_components[owner.sub_component_idx];
356            if let Some(expr) = sc.accessible_prop.get(&(local_idx, what.clone())) {
357                let args = accessibility_action_args(action);
358                let mut ctx = crate::eval::EvalContext::with_arguments(owner, args);
359                crate::eval::eval_expression(&mut ctx, &expr.borrow());
360                return;
361            }
362        }
363    }
364
365    fn supported_accessibility_actions(
366        self: Pin<&Self>,
367        item_index: u32,
368    ) -> SupportedAccessibilityAction {
369        let mut actions = SupportedAccessibilityAction::default();
370        for (owner, local_idx) in resolve_accessible_candidates(self.get_ref(), item_index) {
371            let cu = owner.compilation_unit.clone();
372            let sc = &cu.sub_components[owner.sub_component_idx];
373            for (idx, key) in sc.accessible_prop.keys() {
374                if *idx == local_idx
375                    && let Some(action_name) = key.strip_prefix("Action")
376                {
377                    actions |= SupportedAccessibilityAction::from_name(action_name)
378                        .unwrap_or_else(|| panic!("Not an accessible action: {action_name:?}"));
379                }
380            }
381        }
382        actions
383    }
384
385    fn item_element_infos(self: Pin<&Self>, item_index: u32, result: &mut SharedString) -> bool {
386        let this = self.get_ref();
387        let Some(entry) = this.item_table.get(item_index as usize).and_then(Option::as_ref) else {
388            return false;
389        };
390        let cu = &this.root_sub_component.compilation_unit;
391        // The compiler stores `element_infos` per sub-component, keyed by
392        // the element's tree index *within that sub-component*. Walk the
393        // sub_component_path from the root, translating the flat index
394        // into each sub-component's local tree space.
395        //
396        // A native item's info lives on the leaf sub-component; a
397        // component-instance declaration's (`Switch { }`) lives on the
398        // *parent* of the leaf, keyed by the instance's `index_in_tree`.
399        // Check each level before descending — first match wins.
400        let mut owner_sc_idx = this.root_sub_component.sub_component_idx;
401        let mut local_idx = item_index;
402        for &sub_step in entry.0.iter() {
403            let owner_sc = &cu.sub_components[owner_sc_idx];
404            if let Some(info) = owner_sc.element_infos.get(&local_idx) {
405                *result = info.as_str().into();
406                return true;
407            }
408            let nested = &owner_sc.sub_components[sub_step];
409            // Translate `local_idx` into `nested`'s tree.
410            if local_idx == nested.index_in_tree {
411                local_idx = 0;
412            } else if nested.index_of_first_child_in_tree > 0 {
413                local_idx = local_idx + 1 - nested.index_of_first_child_in_tree;
414            }
415            owner_sc_idx = nested.ty;
416        }
417        let owner_sc = &cu.sub_components[owner_sc_idx];
418        let item_local_idx = owner_sc.items[entry.1].index_in_tree;
419        if let Some(infos) = owner_sc.element_infos.get(&item_local_idx) {
420            *result = infos.as_str().into();
421            true
422        } else {
423            false
424        }
425    }
426
427    fn element_declared_properties(
428        self: Pin<&Self>,
429        item_index: u32,
430        result: &mut SharedString,
431    ) -> bool {
432        let this = self.get_ref();
433        let cu = this.root_sub_component.compilation_unit.clone();
434        if !cu.has_debug_info {
435            return false;
436        }
437        if let Some((owner, local_idx)) = resolve_element_properties_owner(this, item_index) {
438            let sc = &cu.sub_components[owner.sub_component_idx];
439            if let Some(props) = sc.element_properties.get(&local_idx) {
440                let mut encoded = String::new();
441                for p in props {
442                    use std::fmt::Write;
443                    writeln!(encoded, "{}:{}", p.name, p.ty).unwrap();
444                }
445                *result = encoded.as_str().into();
446            }
447        }
448        true
449    }
450
451    fn element_property_value(
452        self: Pin<&Self>,
453        item_index: u32,
454        property_name: Slice<u8>,
455        result: &mut SharedString,
456    ) -> bool {
457        let this = self.get_ref();
458        let cu = this.root_sub_component.compilation_unit.clone();
459        if !cu.has_debug_info {
460            return false;
461        }
462        let Ok(name) = core::str::from_utf8(property_name.as_slice()) else {
463            return false;
464        };
465        let Some((owner, local_idx)) = resolve_element_properties_owner(this, item_index) else {
466            return false;
467        };
468        let sc = &cu.sub_components[owner.sub_component_idx];
469        let Some(prop) = sc
470            .element_properties
471            .get(&local_idx)
472            .and_then(|props| props.iter().find(|p| p.name == name))
473        else {
474            return false;
475        };
476        let ctx = crate::eval::EvalContext::new(owner);
477        let value = crate::eval::load_property(&ctx, &prop.prop);
478        format_element_property_value(&value, &prop.ty, result)
479    }
480
481    fn window_adapter(self: Pin<&Self>, do_create: bool, result: &mut Option<WindowAdapterRc>) {
482        // A repeated instance's own `window_adapter` is unset; walk up via
483        // `parent_instance` to the root `Instance` and read its adapter.
484        let this = self.get_ref();
485        if let Some(adapter) = this.window_adapter.get() {
486            *result = Some(adapter.clone());
487            return;
488        }
489        let mut parent_sub = this.parent_instance.upgrade();
490        while let Some(sub) = parent_sub {
491            let Some(root_vrc) = sub.root.get().and_then(|w| w.upgrade()) else { break };
492            if let Some(adapter) = root_vrc.window_adapter.get() {
493                *result = Some(adapter.clone());
494                return;
495            }
496            parent_sub = root_vrc.parent_instance.upgrade();
497        }
498        if do_create {
499            *result = this.window_adapter_or_default();
500        }
501    }
502}
503
504/// Resolve a flat tree index to (owning sub-component, local index_in_tree)
505/// for accessibility lookups.
506fn resolve_accessible_item(
507    instance: &Instance,
508    item_index: u32,
509) -> Option<(Pin<std::rc::Rc<crate::instance::SubComponentInstance>>, u32)> {
510    let entry = instance.item_table.get(item_index as usize).and_then(Option::as_ref)?;
511    let mut owner = instance.root_sub_component.clone();
512    for &sub_idx in entry.0.iter() {
513        let next = owner.sub_components[sub_idx].clone();
514        owner = next;
515    }
516    let cu = &owner.compilation_unit;
517    let sc = &cu.sub_components[owner.sub_component_idx];
518    let local_idx = sc.items[entry.1].index_in_tree;
519    Some((owner, local_idx))
520}
521
522/// Resolve a flat tree index to the sub-component instance whose `element_properties`
523/// table has an entry for it, plus the local key.
524/// Walks like `item_element_infos`; first match wins.
525fn resolve_element_properties_owner(
526    instance: &Instance,
527    item_index: u32,
528) -> Option<(Pin<std::rc::Rc<crate::instance::SubComponentInstance>>, u32)> {
529    let entry = instance.item_table.get(item_index as usize).and_then(Option::as_ref)?;
530    let cu = instance.root_sub_component.compilation_unit.clone();
531    let mut owner = instance.root_sub_component.clone();
532    let mut local_idx = item_index;
533    for &sub_step in entry.0.iter() {
534        let sc = &cu.sub_components[owner.sub_component_idx];
535        if sc.element_properties.contains_key(&local_idx) {
536            return Some((owner, local_idx));
537        }
538        let nested = &sc.sub_components[sub_step];
539        // Translate `local_idx` into the nested sub-component's tree.
540        if local_idx == nested.index_in_tree {
541            local_idx = 0;
542        } else if nested.index_of_first_child_in_tree > 0 {
543            local_idx = local_idx + 1 - nested.index_of_first_child_in_tree;
544        }
545        let next = owner.sub_components[sub_step].clone();
546        owner = next;
547    }
548    let sc = &cu.sub_components[owner.sub_component_idx];
549    let item_local_idx = sc.items[entry.1].index_in_tree;
550    sc.element_properties.contains_key(&item_local_idx).then_some((owner, item_local_idx))
551}
552
553/// Encode `value` per [`i_slint_core::debug_info`],
554/// dispatching on the declared type exactly like the generated code does,
555/// so both runtimes produce the same string.
556fn format_element_property_value(
557    value: &crate::Value,
558    ty: &i_slint_compiler::langtype::Type,
559    result: &mut SharedString,
560) -> bool {
561    use i_slint_compiler::langtype::Type;
562    use i_slint_core::debug_info;
563    match ty {
564        Type::Bool => {
565            let crate::Value::Bool(b) = value else { return false };
566            *result = debug_info::format_bool(*b);
567        }
568        Type::Int32 => {
569            let crate::Value::Number(n) = value else { return false };
570            *result = debug_info::format_integer(*n as i32 as i64);
571        }
572        Type::Duration => {
573            let crate::Value::Number(n) = value else { return false };
574            *result = debug_info::format_integer(*n as i64);
575        }
576        Type::Float32
577        | Type::Angle
578        | Type::Percent
579        | Type::PhysicalLength
580        | Type::LogicalLength
581        | Type::Rem => {
582            let crate::Value::Number(n) = value else { return false };
583            *result = debug_info::format_float(*n as f32);
584        }
585        Type::String => {
586            let crate::Value::String(s) = value else { return false };
587            *result = s.clone();
588        }
589        Type::Color | Type::Brush => {
590            let crate::Value::Brush(b) = value else { return false };
591            let Some(formatted) = debug_info::format_brush(b) else { return false };
592            *result = formatted;
593        }
594        Type::Enumeration(_) => {
595            let crate::Value::EnumerationValue(_, v) = value else { return false };
596            *result = v.replace('_', "-").as_str().into();
597        }
598        _ => return false,
599    }
600    true
601}
602
603/// Returns the candidates to look up an accessible property for a given
604/// flat tree index. The first candidate is the wrapping sub-component
605/// reference at the root level (if applicable); the second is the
606/// deepest item itself, so an outer-element query wins over the inner
607/// sub-component root's own accessible properties.
608fn resolve_accessible_candidates(
609    instance: &Instance,
610    item_index: u32,
611) -> Vec<(Pin<std::rc::Rc<crate::instance::SubComponentInstance>>, u32)> {
612    let mut out = Vec::new();
613    let Some(entry) = instance.item_table.get(item_index as usize).and_then(Option::as_ref) else {
614        return out;
615    };
616    // First candidate: a wrapping sub-component reference at the root.
617    // Its accessible_prop entry is keyed by the root-local flat index.
618    if !entry.0.is_empty() {
619        out.push((instance.root_sub_component.clone(), item_index));
620    }
621    // Second candidate: the deepest item itself.
622    let mut owner = instance.root_sub_component.clone();
623    for &sub_idx in entry.0.iter() {
624        let next = owner.sub_components[sub_idx].clone();
625        owner = next;
626    }
627    let cu = &owner.compilation_unit;
628    let sc = &cu.sub_components[owner.sub_component_idx];
629    let local_idx = sc.items[entry.1].index_in_tree;
630    out.push((owner, local_idx));
631    out
632}
633
634/// The `accessible_prop` map key for a string property — the same
635/// PascalCase form the lowering derives from the enum's kebab-case
636/// `Display` (see `lower_to_item_tree`).
637fn accessible_string_property_name(what: AccessibleStringProperty) -> String {
638    i_slint_compiler::generator::to_pascal_case(&what.to_string())
639}
640
641fn accessibility_action_name(action: &AccessibilityAction) -> &'static str {
642    match action {
643        AccessibilityAction::Default => "Default",
644        AccessibilityAction::Decrement => "Decrement",
645        AccessibilityAction::Increment => "Increment",
646        AccessibilityAction::Expand => "Expand",
647        AccessibilityAction::ReplaceSelectedText(_) => "ReplaceSelectedText",
648        AccessibilityAction::SetValue(_) => "SetValue",
649        AccessibilityAction::SetSelectionOffsets(..) => "SetSelectionOffsets",
650    }
651}
652
653fn accessibility_action_args(action: &AccessibilityAction) -> Vec<crate::Value> {
654    match action {
655        AccessibilityAction::ReplaceSelectedText(s) | AccessibilityAction::SetValue(s) => {
656            vec![crate::Value::String(s.clone())]
657        }
658        AccessibilityAction::SetSelectionOffsets(anchor, focus) => {
659            vec![crate::Value::Number(*anchor as f64), crate::Value::Number(*focus as f64)]
660        }
661        _ => Vec::new(),
662    }
663}