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