slint_interpreter/
highlight.rs

1// Copyright © SixtyFPS GmbH <[email protected]>
2// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0
3
4//! This module contains the code for the highlight of some elements
5
6use crate::dynamic_item_tree::{DynamicComponentVRc, ItemTreeBox};
7use i_slint_compiler::object_tree::{Component, Element, ElementRc};
8use i_slint_core::items::ItemRc;
9use i_slint_core::lengths::LogicalRect;
10use smol_str::SmolStr;
11use std::cell::RefCell;
12use std::path::Path;
13use std::rc::Rc;
14use vtable::VRc;
15
16fn normalize_repeated_element(element: ElementRc) -> ElementRc {
17    if element.borrow().repeated.is_some() {
18        if let i_slint_compiler::langtype::ElementType::Component(base) =
19            &element.borrow().base_type
20        {
21            if base.parent_element.upgrade().is_some() {
22                return base.root_element.clone();
23            }
24        }
25    }
26
27    element
28}
29
30fn collect_highlight_data(
31    component: &DynamicComponentVRc,
32    elements: &[std::rc::Weak<RefCell<Element>>],
33) -> Vec<i_slint_core::lengths::LogicalRect> {
34    let component_instance = VRc::downgrade(component);
35    let component_instance = component_instance.upgrade().unwrap();
36    generativity::make_guard!(guard);
37    let c = component_instance.unerase(guard);
38    let mut values = Vec::new();
39    for element in elements.iter().filter_map(|e| e.upgrade()) {
40        let element = normalize_repeated_element(element);
41        if let Some(repeater_path) = repeater_path(&element) {
42            fill_highlight_data(
43                &repeater_path,
44                &element,
45                &c,
46                &c,
47                ElementPositionFilter::IncludeClipped,
48                &mut values,
49            );
50        }
51    }
52    values
53}
54
55pub(crate) fn component_positions(
56    component_instance: &DynamicComponentVRc,
57    path: &Path,
58    offset: u32,
59) -> Vec<i_slint_core::lengths::LogicalRect> {
60    generativity::make_guard!(guard);
61    let c = component_instance.unerase(guard);
62
63    let elements =
64        find_element_node_at_source_code_position(&c.description().original, path, offset);
65    collect_highlight_data(
66        component_instance,
67        &elements.into_iter().map(|(e, _)| Rc::downgrade(&e)).collect::<Vec<_>>(),
68    )
69}
70
71/// Argument to filter the elements in the [`element_positions`] function
72#[derive(Copy, Clone, Eq, PartialEq)]
73pub enum ElementPositionFilter {
74    /// Include all elements
75    IncludeClipped,
76    /// Exclude elements that are not visible because they are clipped
77    ExcludeClipped,
78}
79
80/// Return the positions of all instances of a specific element
81pub fn element_positions(
82    component_instance: &DynamicComponentVRc,
83    element: &ElementRc,
84    filter_clipped: ElementPositionFilter,
85) -> Vec<LogicalRect> {
86    generativity::make_guard!(guard);
87    let c = component_instance.unerase(guard);
88
89    let mut values = Vec::new();
90
91    let element = normalize_repeated_element(element.clone());
92    if let Some(repeater_path) = repeater_path(&element) {
93        fill_highlight_data(&repeater_path, &element, &c, &c, filter_clipped, &mut values);
94    }
95    values
96}
97
98pub(crate) fn element_node_at_source_code_position(
99    component_instance: &DynamicComponentVRc,
100    path: &Path,
101    offset: u32,
102) -> Vec<(ElementRc, usize)> {
103    generativity::make_guard!(guard);
104    let c = component_instance.unerase(guard);
105
106    find_element_node_at_source_code_position(&c.description().original, path, offset)
107}
108
109fn fill_highlight_data(
110    repeater_path: &[SmolStr],
111    element: &ElementRc,
112    component_instance: &ItemTreeBox,
113    root_component_instance: &ItemTreeBox,
114    filter_clipped: ElementPositionFilter,
115    values: &mut Vec<i_slint_core::lengths::LogicalRect>,
116) {
117    if element.borrow().repeated.is_some() {
118        // avoid a panic
119        return;
120    }
121
122    if let [first, rest @ ..] = repeater_path {
123        generativity::make_guard!(guard);
124        let rep = crate::dynamic_item_tree::get_repeater_by_name(
125            component_instance.borrow_instance(),
126            first.as_str(),
127            guard,
128        );
129        for idx in rep.0.range() {
130            if let Some(c) = rep.0.instance_at(idx) {
131                generativity::make_guard!(guard);
132                fill_highlight_data(
133                    rest,
134                    element,
135                    &c.unerase(guard),
136                    root_component_instance,
137                    filter_clipped,
138                    values,
139                );
140            }
141        }
142    } else {
143        let vrc = VRc::into_dyn(
144            component_instance.borrow_instance().self_weak().get().unwrap().upgrade().unwrap(),
145        );
146        let root_vrc = VRc::into_dyn(
147            root_component_instance.borrow_instance().self_weak().get().unwrap().upgrade().unwrap(),
148        );
149        let index = element.borrow().item_index.get().copied().unwrap();
150        let item_rc = ItemRc::new(vrc.clone(), index);
151        if filter_clipped == ElementPositionFilter::IncludeClipped || item_rc.is_visible() {
152            let geometry = item_rc.geometry();
153            let origin = item_rc.map_to_item_tree(geometry.origin, &root_vrc);
154            let size = geometry.size;
155            values.push(LogicalRect { origin, size });
156        }
157    }
158}
159
160// Go over all elements in original to find the one that is highlighted
161fn find_element_node_at_source_code_position(
162    component: &Rc<Component>,
163    path: &Path,
164    offset: u32,
165) -> Vec<(ElementRc, usize)> {
166    let mut result = Vec::new();
167    i_slint_compiler::object_tree::recurse_elem_including_sub_components(
168        component,
169        &(),
170        &mut |elem, &()| {
171            if elem.borrow().repeated.is_some() {
172                return;
173            }
174            for (index, node_path, node_range) in
175                elem.borrow().debug.iter().enumerate().map(|(i, n)| {
176                    let text_range = n
177                        .node
178                        .QualifiedName()
179                        .map(|n| n.text_range())
180                        .or_else(|| {
181                            n.node
182                                .child_token(i_slint_compiler::parser::SyntaxKind::LBrace)
183                                .map(|n| n.text_range())
184                        })
185                        .expect("A Element must contain a LBrace somewhere pretty early");
186
187                    (i, n.node.source_file.path(), text_range)
188                })
189            {
190                if node_path == path && node_range.contains(offset.into()) {
191                    result.push((elem.clone(), index));
192                }
193            }
194        },
195    );
196    result
197}
198
199fn repeater_path(elem: &ElementRc) -> Option<Vec<SmolStr>> {
200    let enclosing = elem.borrow().enclosing_component.upgrade().unwrap();
201    if let Some(parent) = enclosing.parent_element.upgrade() {
202        // This is not a repeater, it might be a popup menu which is not supported ATM
203        parent.borrow().repeated.as_ref()?;
204
205        let mut r = repeater_path(&parent)?;
206        r.push(parent.borrow().id.clone());
207        Some(r)
208    } else {
209        Some(vec![])
210    }
211}