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