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        if let Some(cc) = crate::instance::component_container_item(&sub, rep_idx) {
122            return cc.subtree_range();
123        }
124        let repeater = &sub.repeaters[rep_idx];
125        repeater.track_instance_changes();
126        let range = repeater.range();
127        IndexRange { start: range.start, end: range.end }
128    }
129
130    fn get_subtree(
131        self: Pin<&Self>,
132        index: u32,
133        subindex: usize,
134        result: &mut VWeak<ItemTreeVTable, vtable::Dyn>,
135    ) {
136        let Some((sub, rep_idx)) = self.get_ref().dynamic_at(index) else {
137            return;
138        };
139        if let Some(cc) = crate::instance::component_container_item(&sub, rep_idx) {
140            if subindex == 0 {
141                *result = cc.subtree_component();
142            }
143            return;
144        }
145        let repeater = &sub.repeaters[rep_idx];
146        if let Some(instance) = repeater.instance_at(subindex) {
147            *result = vtable::VRc::downgrade(&vtable::VRc::into_dyn(instance));
148        }
149    }
150
151    fn get_item_tree(self: Pin<&Self>) -> Slice<'_, ItemTreeNode> {
152        Slice::from(&*self.get_ref().tree_nodes)
153    }
154
155    fn parent_node(self: Pin<&Self>, result: &mut ItemWeak) {
156        // If this is a repeated sub-tree, point at the repeater's placeholder
157        // in the parent instance. For a popup (parented but not repeated),
158        // point at the parent instance's root item.
159        let this = self.get_ref();
160        // `embedded_in` records where in the outer item tree this instance
161        // lives. Return that as the parent; the core walks back through it
162        // the same way as a repeated DynamicTree node.
163        if let Some((outer_weak, outer_index)) = this.embedded_in.get()
164            && let Some(outer) = outer_weak.upgrade()
165        {
166            *result = i_slint_core::items::ItemRc::new(outer, *outer_index).downgrade();
167            return;
168        }
169        let Some(parent_sub) = this.parent_instance.upgrade() else { return };
170        let Some(parent_root_vrc) = parent_sub.root.get().and_then(|w| w.upgrade()) else {
171            return;
172        };
173        let parent_dyn = vtable::VRc::into_dyn(parent_root_vrc.clone());
174        if let Some((_, repeater_idx)) = this.root_sub_component.repeated_in.get() {
175            // Return the DynamicTree node itself in the parent's flat tree.
176            // `parent_item` in i_slint_core detects that the returned parent
177            // is a DynamicTree and walks one more level up to its parent
178            // item. Returning the DynamicTree's own parent here skips that
179            // adjustment and gives the caller the wrong node.
180            let rep_idx = *repeater_idx;
181            let parent_path = sub_component_path_of(&parent_sub, &parent_root_vrc);
182            for (flat, entry) in parent_root_vrc.dynamic_table.iter().enumerate() {
183                if let Some((path, idx)) = entry.as_ref()
184                    && path.as_ref() == parent_path.as_slice()
185                    && *idx == rep_idx
186                {
187                    *result = i_slint_core::items::ItemRc::new(parent_dyn, flat as u32).downgrade();
188                    return;
189                }
190            }
191        } else {
192            // Popup case: ItemRc::new_root on the parent instance, which the
193            // caller uses to traverse up to the window.
194            *result = i_slint_core::items::ItemRc::new(parent_dyn, 0).downgrade();
195        }
196    }
197
198    fn embed_component(
199        self: Pin<&Self>,
200        parent: &VWeak<ItemTreeVTable>,
201        parent_item_tree_index: u32,
202    ) -> bool {
203        // Stash the outer item tree handle so `parent_node` can point at
204        // the ComponentContainer slot that substitutes this instance in.
205        let this = self.get_ref();
206        this.embedded_in.set((parent.clone(), parent_item_tree_index)).is_ok()
207    }
208
209    fn subtree_index(self: Pin<&Self>) -> usize {
210        // For repeated instances, return the model index so tab-focus
211        // traversal can step to the next sibling via get_subtree(idx+1).
212        let this = self.get_ref();
213        let sc = &this.root_sub_component.compilation_unit.sub_components
214            [this.root_sub_component.sub_component_idx];
215        for (idx, prop) in sc.properties.iter_enumerated() {
216            if prop.name == "model_index"
217                && let crate::Value::Number(n) =
218                    Pin::as_ref(&this.root_sub_component.properties[idx]).get()
219            {
220                return n as usize;
221            }
222        }
223        // Conditional: only one instance, index 0.
224        0
225    }
226
227    fn layout_info(self: Pin<&Self>, orientation: Orientation) -> LayoutInfo {
228        let this = self.get_ref();
229        let sc_idx = this.root_sub_component.sub_component_idx;
230        let cu = &this.root_sub_component.compilation_unit;
231        let sc = &cu.sub_components[sc_idx];
232        let expr = match orientation {
233            Orientation::Horizontal => sc.layout_info_h.borrow(),
234            Orientation::Vertical => sc.layout_info_v.borrow(),
235        };
236        let mut ctx = crate::eval::EvalContext::new(this.root_sub_component.clone());
237        crate::eval::eval_expression(&mut ctx, &expr).try_into().unwrap_or_default()
238    }
239
240    fn item_geometry(self: Pin<&Self>, item_index: u32) -> LogicalRect {
241        // `item_index` is the flat tree index. Resolve it via `item_table`
242        // into the owning sub-component, then look up the geometry by
243        // the item's `index_in_tree`. `sc.geometries` is keyed by the
244        // sub-component-local tree index (set by `generate_item_indices`),
245        // not by the raw `ItemInstanceIdx` slot.
246        let this = self.get_ref();
247        let Some(entry) = this.item_table.get(item_index as usize).and_then(Option::as_ref) else {
248            return LogicalRect::default();
249        };
250        let mut owner_rc = this.root_sub_component.clone();
251        for &sub_idx in entry.0.iter() {
252            owner_rc = owner_rc.sub_components[sub_idx].clone();
253        }
254        let cu = owner_rc.compilation_unit.clone();
255        let sc = &cu.sub_components[owner_rc.sub_component_idx];
256        let item = &sc.items[entry.1];
257        // When the flat tree crosses into a sub-component (non-empty path)
258        // and lands on its root element (local tree index 0), the inner
259        // root's geometry can duplicate the parent's placement: the
260        // compiler's `adjust_geometry_for_injected_parent` pass hoists the
261        // original position into an injected wrapper item, and the inner
262        // root applies the same offset again via its `y: root-1_y`
263        // binding. So read the wrapper's geometry from the *parent*
264        // sub-component at the placement slot and never query the inner
265        // root. Fall through when the parent
266        // has no entry (the sub-component was placed directly with no
267        // wrapper, e.g. `box := SpinBox {}` inside a Window — then the
268        // inner root's own geometry is the correct placement).
269        let parent_placement = if !entry.0.is_empty() && item.index_in_tree == 0 {
270            let mut parent_rc = this.root_sub_component.clone();
271            for &sub_idx in &entry.0[..entry.0.len() - 1] {
272                parent_rc = parent_rc.sub_components[sub_idx].clone();
273            }
274            let placement = entry.0[entry.0.len() - 1];
275            let parent_sc = &cu.sub_components[parent_rc.sub_component_idx];
276            let placement_idx = parent_sc.sub_components[placement].index_in_tree as usize;
277            parent_sc
278                .geometries
279                .get(placement_idx)
280                .and_then(|g| g.clone())
281                .map(|expr| (expr, parent_rc))
282        } else {
283            None
284        };
285        let (expr_cell, ctx_owner) = if let Some(pair) = parent_placement {
286            pair
287        } else {
288            let tree_local_idx = item.index_in_tree as usize;
289            match sc.geometries.get(tree_local_idx) {
290                Some(Some(expr)) => (expr.clone(), owner_rc),
291                _ => return LogicalRect::default(),
292            }
293        };
294        let expr = expr_cell.borrow();
295        let mut ctx = crate::eval::EvalContext::new(ctx_owner);
296        let crate::Value::Struct(s) = crate::eval::eval_expression(&mut ctx, &expr) else {
297            return LogicalRect::default();
298        };
299        let as_f32 = |name: &str| -> f32 {
300            match s.get_field(name) {
301                Some(crate::Value::Number(n)) => *n as f32,
302                _ => 0.0,
303            }
304        };
305        LogicalRect::new(
306            i_slint_core::lengths::LogicalPoint::new(as_f32("x"), as_f32("y")),
307            i_slint_core::lengths::LogicalSize::new(as_f32("width"), as_f32("height")),
308        )
309    }
310
311    fn accessible_role(self: Pin<&Self>, item_index: u32) -> AccessibleRole {
312        let Some((owner, local_idx)) = resolve_accessible_item(self.get_ref(), item_index) else {
313            return AccessibleRole::default();
314        };
315        let cu = owner.compilation_unit.clone();
316        let sc = &cu.sub_components[owner.sub_component_idx];
317        let Some(expr) = sc.accessible_prop.get(&(local_idx, "Role".to_string())) else {
318            return AccessibleRole::default();
319        };
320        let mut ctx = crate::eval::EvalContext::new(owner);
321        crate::eval::eval_expression(&mut ctx, &expr.borrow()).try_into().unwrap_or_default()
322    }
323
324    fn accessible_string_property(
325        self: Pin<&Self>,
326        item_index: u32,
327        what: AccessibleStringProperty,
328        result: &mut SharedString,
329    ) -> bool {
330        let what_str = accessible_string_property_name(what);
331        for (owner, local_idx) in resolve_accessible_candidates(self.get_ref(), item_index) {
332            let cu = owner.compilation_unit.clone();
333            let sc = &cu.sub_components[owner.sub_component_idx];
334            if let Some(expr) = sc.accessible_prop.get(&(local_idx, what_str.clone())) {
335                let mut ctx = crate::eval::EvalContext::new(owner);
336                if let crate::Value::String(s) =
337                    crate::eval::eval_expression(&mut ctx, &expr.borrow())
338                {
339                    *result = s;
340                    return true;
341                }
342            }
343        }
344        false
345    }
346
347    fn accessibility_action(self: Pin<&Self>, item_index: u32, action: &AccessibilityAction) {
348        let what = format!("Action{}", accessibility_action_name(action));
349        for (owner, local_idx) in resolve_accessible_candidates(self.get_ref(), item_index) {
350            let cu = owner.compilation_unit.clone();
351            let sc = &cu.sub_components[owner.sub_component_idx];
352            if let Some(expr) = sc.accessible_prop.get(&(local_idx, what.clone())) {
353                let args = accessibility_action_args(action);
354                let mut ctx = crate::eval::EvalContext::with_arguments(owner, args);
355                crate::eval::eval_expression(&mut ctx, &expr.borrow());
356                return;
357            }
358        }
359    }
360
361    fn supported_accessibility_actions(
362        self: Pin<&Self>,
363        item_index: u32,
364    ) -> SupportedAccessibilityAction {
365        let mut actions = SupportedAccessibilityAction::default();
366        for (owner, local_idx) in resolve_accessible_candidates(self.get_ref(), item_index) {
367            let cu = owner.compilation_unit.clone();
368            let sc = &cu.sub_components[owner.sub_component_idx];
369            for (idx, key) in sc.accessible_prop.keys() {
370                if *idx == local_idx
371                    && let Some(action_name) = key.strip_prefix("Action")
372                {
373                    actions |= SupportedAccessibilityAction::from_name(action_name)
374                        .unwrap_or_else(|| panic!("Not an accessible action: {action_name:?}"));
375                }
376            }
377        }
378        actions
379    }
380
381    fn item_element_infos(self: Pin<&Self>, item_index: u32, result: &mut SharedString) -> bool {
382        let this = self.get_ref();
383        let Some(entry) = this.item_table.get(item_index as usize).and_then(Option::as_ref) else {
384            return false;
385        };
386        let cu = &this.root_sub_component.compilation_unit;
387        // The compiler stores `element_infos` per sub-component, keyed by
388        // the element's tree index *within that sub-component*. Walk the
389        // sub_component_path from the root, translating the flat index
390        // into each sub-component's local tree space.
391        //
392        // A native item's info lives on the leaf sub-component; a
393        // component-instance declaration's (`Switch { }`) lives on the
394        // *parent* of the leaf, keyed by the instance's `index_in_tree`.
395        // Check each level before descending — first match wins.
396        let mut owner_sc_idx = this.root_sub_component.sub_component_idx;
397        let mut local_idx = item_index;
398        for &sub_step in entry.0.iter() {
399            let owner_sc = &cu.sub_components[owner_sc_idx];
400            if let Some(info) = owner_sc.element_infos.get(&local_idx) {
401                *result = info.as_str().into();
402                return true;
403            }
404            let nested = &owner_sc.sub_components[sub_step];
405            // Translate `local_idx` into `nested`'s tree.
406            if local_idx == nested.index_in_tree {
407                local_idx = 0;
408            } else if nested.index_of_first_child_in_tree > 0 {
409                local_idx = local_idx + 1 - nested.index_of_first_child_in_tree;
410            }
411            owner_sc_idx = nested.ty;
412        }
413        let owner_sc = &cu.sub_components[owner_sc_idx];
414        let item_local_idx = owner_sc.items[entry.1].index_in_tree;
415        if let Some(infos) = owner_sc.element_infos.get(&item_local_idx) {
416            *result = infos.as_str().into();
417            true
418        } else {
419            false
420        }
421    }
422
423    fn window_adapter(self: Pin<&Self>, do_create: bool, result: &mut Option<WindowAdapterRc>) {
424        // A repeated instance's own `window_adapter` is unset; walk up via
425        // `parent_instance` to the root `Instance` and read its adapter.
426        let this = self.get_ref();
427        if let Some(adapter) = this.window_adapter.get() {
428            *result = Some(adapter.clone());
429            return;
430        }
431        let mut parent_sub = this.parent_instance.upgrade();
432        while let Some(sub) = parent_sub {
433            let Some(root_vrc) = sub.root.get().and_then(|w| w.upgrade()) else { break };
434            if let Some(adapter) = root_vrc.window_adapter.get() {
435                *result = Some(adapter.clone());
436                return;
437            }
438            parent_sub = root_vrc.parent_instance.upgrade();
439        }
440        if do_create {
441            *result = this.window_adapter_or_default();
442        }
443    }
444}
445
446/// Resolve a flat tree index to (owning sub-component, local index_in_tree)
447/// for accessibility lookups.
448fn resolve_accessible_item(
449    instance: &Instance,
450    item_index: u32,
451) -> Option<(Pin<std::rc::Rc<crate::instance::SubComponentInstance>>, u32)> {
452    let entry = instance.item_table.get(item_index as usize).and_then(Option::as_ref)?;
453    let mut owner = instance.root_sub_component.clone();
454    for &sub_idx in entry.0.iter() {
455        let next = owner.sub_components[sub_idx].clone();
456        owner = next;
457    }
458    let cu = &owner.compilation_unit;
459    let sc = &cu.sub_components[owner.sub_component_idx];
460    let local_idx = sc.items[entry.1].index_in_tree;
461    Some((owner, local_idx))
462}
463
464/// Returns the candidates to look up an accessible property for a given
465/// flat tree index. The first candidate is the wrapping sub-component
466/// reference at the root level (if applicable); the second is the
467/// deepest item itself, so an outer-element query wins over the inner
468/// sub-component root's own accessible properties.
469fn resolve_accessible_candidates(
470    instance: &Instance,
471    item_index: u32,
472) -> Vec<(Pin<std::rc::Rc<crate::instance::SubComponentInstance>>, u32)> {
473    let mut out = Vec::new();
474    let Some(entry) = instance.item_table.get(item_index as usize).and_then(Option::as_ref) else {
475        return out;
476    };
477    // First candidate: a wrapping sub-component reference at the root.
478    // Its accessible_prop entry is keyed by the root-local flat index.
479    if !entry.0.is_empty() {
480        out.push((instance.root_sub_component.clone(), item_index));
481    }
482    // Second candidate: the deepest item itself.
483    let mut owner = instance.root_sub_component.clone();
484    for &sub_idx in entry.0.iter() {
485        let next = owner.sub_components[sub_idx].clone();
486        owner = next;
487    }
488    let cu = &owner.compilation_unit;
489    let sc = &cu.sub_components[owner.sub_component_idx];
490    let local_idx = sc.items[entry.1].index_in_tree;
491    out.push((owner, local_idx));
492    out
493}
494
495/// The `accessible_prop` map key for a string property — the same
496/// PascalCase form the lowering derives from the enum's kebab-case
497/// `Display` (see `lower_to_item_tree`).
498fn accessible_string_property_name(what: AccessibleStringProperty) -> String {
499    i_slint_compiler::generator::to_pascal_case(&what.to_string())
500}
501
502fn accessibility_action_name(action: &AccessibilityAction) -> &'static str {
503    match action {
504        AccessibilityAction::Default => "Default",
505        AccessibilityAction::Decrement => "Decrement",
506        AccessibilityAction::Increment => "Increment",
507        AccessibilityAction::Expand => "Expand",
508        AccessibilityAction::ReplaceSelectedText(_) => "ReplaceSelectedText",
509        AccessibilityAction::SetValue(_) => "SetValue",
510        AccessibilityAction::SetSelectionOffsets(..) => "SetSelectionOffsets",
511    }
512}
513
514fn accessibility_action_args(action: &AccessibilityAction) -> Vec<crate::Value> {
515    match action {
516        AccessibilityAction::ReplaceSelectedText(s) | AccessibilityAction::SetValue(s) => {
517            vec![crate::Value::String(s.clone())]
518        }
519        AccessibilityAction::SetSelectionOffsets(anchor, focus) => {
520            vec![crate::Value::Number(*anchor as f64), crate::Value::Number(*focus as f64)]
521        }
522        _ => Vec::new(),
523    }
524}