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 window_adapter(self: Pin<&Self>, do_create: bool, result: &mut Option<WindowAdapterRc>) {
428        // A repeated instance's own `window_adapter` is unset; walk up via
429        // `parent_instance` to the root `Instance` and read its adapter.
430        let this = self.get_ref();
431        if let Some(adapter) = this.window_adapter.get() {
432            *result = Some(adapter.clone());
433            return;
434        }
435        let mut parent_sub = this.parent_instance.upgrade();
436        while let Some(sub) = parent_sub {
437            let Some(root_vrc) = sub.root.get().and_then(|w| w.upgrade()) else { break };
438            if let Some(adapter) = root_vrc.window_adapter.get() {
439                *result = Some(adapter.clone());
440                return;
441            }
442            parent_sub = root_vrc.parent_instance.upgrade();
443        }
444        if do_create {
445            *result = this.window_adapter_or_default();
446        }
447    }
448}
449
450/// Resolve a flat tree index to (owning sub-component, local index_in_tree)
451/// for accessibility lookups.
452fn resolve_accessible_item(
453    instance: &Instance,
454    item_index: u32,
455) -> Option<(Pin<std::rc::Rc<crate::instance::SubComponentInstance>>, u32)> {
456    let entry = instance.item_table.get(item_index as usize).and_then(Option::as_ref)?;
457    let mut owner = instance.root_sub_component.clone();
458    for &sub_idx in entry.0.iter() {
459        let next = owner.sub_components[sub_idx].clone();
460        owner = next;
461    }
462    let cu = &owner.compilation_unit;
463    let sc = &cu.sub_components[owner.sub_component_idx];
464    let local_idx = sc.items[entry.1].index_in_tree;
465    Some((owner, local_idx))
466}
467
468/// Returns the candidates to look up an accessible property for a given
469/// flat tree index. The first candidate is the wrapping sub-component
470/// reference at the root level (if applicable); the second is the
471/// deepest item itself, so an outer-element query wins over the inner
472/// sub-component root's own accessible properties.
473fn resolve_accessible_candidates(
474    instance: &Instance,
475    item_index: u32,
476) -> Vec<(Pin<std::rc::Rc<crate::instance::SubComponentInstance>>, u32)> {
477    let mut out = Vec::new();
478    let Some(entry) = instance.item_table.get(item_index as usize).and_then(Option::as_ref) else {
479        return out;
480    };
481    // First candidate: a wrapping sub-component reference at the root.
482    // Its accessible_prop entry is keyed by the root-local flat index.
483    if !entry.0.is_empty() {
484        out.push((instance.root_sub_component.clone(), item_index));
485    }
486    // Second candidate: the deepest item itself.
487    let mut owner = instance.root_sub_component.clone();
488    for &sub_idx in entry.0.iter() {
489        let next = owner.sub_components[sub_idx].clone();
490        owner = next;
491    }
492    let cu = &owner.compilation_unit;
493    let sc = &cu.sub_components[owner.sub_component_idx];
494    let local_idx = sc.items[entry.1].index_in_tree;
495    out.push((owner, local_idx));
496    out
497}
498
499/// The `accessible_prop` map key for a string property — the same
500/// PascalCase form the lowering derives from the enum's kebab-case
501/// `Display` (see `lower_to_item_tree`).
502fn accessible_string_property_name(what: AccessibleStringProperty) -> String {
503    i_slint_compiler::generator::to_pascal_case(&what.to_string())
504}
505
506fn accessibility_action_name(action: &AccessibilityAction) -> &'static str {
507    match action {
508        AccessibilityAction::Default => "Default",
509        AccessibilityAction::Decrement => "Decrement",
510        AccessibilityAction::Increment => "Increment",
511        AccessibilityAction::Expand => "Expand",
512        AccessibilityAction::ReplaceSelectedText(_) => "ReplaceSelectedText",
513        AccessibilityAction::SetValue(_) => "SetValue",
514        AccessibilityAction::SetSelectionOffsets(..) => "SetSelectionOffsets",
515    }
516}
517
518fn accessibility_action_args(action: &AccessibilityAction) -> Vec<crate::Value> {
519    match action {
520        AccessibilityAction::ReplaceSelectedText(s) | AccessibilityAction::SetValue(s) => {
521            vec![crate::Value::String(s.clone())]
522        }
523        AccessibilityAction::SetSelectionOffsets(anchor, focus) => {
524            vec![crate::Value::Number(*anchor as f64), crate::Value::Number(*focus as f64)]
525        }
526        _ => Vec::new(),
527    }
528}