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::graphics::euclid;
9use i_slint_core::items::ItemRc;
10use i_slint_core::lengths::{LogicalPoint, LogicalRect};
11use smol_str::SmolStr;
12use std::cell::RefCell;
13use std::path::Path;
14use std::rc::Rc;
15use vtable::VRc;
16
17fn normalize_repeated_element(element: ElementRc) -> ElementRc {
18    if element.borrow().repeated.is_some() {
19        if let i_slint_compiler::langtype::ElementType::Component(base) =
20            &element.borrow().base_type
21        {
22            if base.parent_element.upgrade().is_some() {
23                return base.root_element.clone();
24            }
25        }
26    }
27
28    element
29}
30
31/// The rectangle of an element, which may be rotated around its center
32#[derive(Clone, Copy, Debug, Default)]
33pub struct HighlightedRect {
34    /// The element's geometry
35    pub rect: LogicalRect,
36    /// In degrees, around the center of the element
37    pub angle: f32,
38}
39impl HighlightedRect {
40    /// return true if the point is inside the (potentially rotated) rectangle
41    pub fn contains(&self, position: LogicalPoint) -> bool {
42        let center = self.rect.center();
43        let rotation = euclid::Rotation2D::radians((-self.angle).to_radians());
44        let transformed = center + rotation.transform_vector(position - center);
45        self.rect.contains(transformed)
46    }
47}
48
49fn collect_highlight_data(
50    component: &DynamicComponentVRc,
51    elements: &[std::rc::Weak<RefCell<Element>>],
52) -> Vec<HighlightedRect> {
53    let component_instance = VRc::downgrade(component);
54    let component_instance = component_instance.upgrade().unwrap();
55    generativity::make_guard!(guard);
56    let c = component_instance.unerase(guard);
57    let mut values = Vec::new();
58    for element in elements.iter().filter_map(|e| e.upgrade()) {
59        let element = normalize_repeated_element(element);
60        if let Some(repeater_path) = repeater_path(&element) {
61            fill_highlight_data(
62                &repeater_path,
63                &element,
64                &c,
65                &c,
66                ElementPositionFilter::IncludeClipped,
67                &mut values,
68            );
69        }
70    }
71    values
72}
73
74pub(crate) fn component_positions(
75    component_instance: &DynamicComponentVRc,
76    path: &Path,
77    offset: u32,
78) -> Vec<HighlightedRect> {
79    generativity::make_guard!(guard);
80    let c = component_instance.unerase(guard);
81
82    let elements =
83        find_element_node_at_source_code_position(&c.description().original, path, offset);
84    collect_highlight_data(
85        component_instance,
86        &elements.into_iter().map(|(e, _)| Rc::downgrade(&e)).collect::<Vec<_>>(),
87    )
88}
89
90/// Argument to filter the elements in the [`element_positions`] function
91#[derive(Copy, Clone, Eq, PartialEq)]
92pub enum ElementPositionFilter {
93    /// Include all elements
94    IncludeClipped,
95    /// Exclude elements that are not visible because they are clipped
96    ExcludeClipped,
97}
98
99/// Return the positions of all instances of a specific element
100pub fn element_positions(
101    component_instance: &DynamicComponentVRc,
102    element: &ElementRc,
103    filter_clipped: ElementPositionFilter,
104) -> Vec<HighlightedRect> {
105    generativity::make_guard!(guard);
106    let c = component_instance.unerase(guard);
107
108    let mut values = Vec::new();
109
110    let element = normalize_repeated_element(element.clone());
111    if let Some(repeater_path) = repeater_path(&element) {
112        fill_highlight_data(&repeater_path, &element, &c, &c, filter_clipped, &mut values);
113    }
114    values
115}
116
117pub(crate) fn element_node_at_source_code_position(
118    component_instance: &DynamicComponentVRc,
119    path: &Path,
120    offset: u32,
121) -> Vec<(ElementRc, usize)> {
122    generativity::make_guard!(guard);
123    let c = component_instance.unerase(guard);
124
125    find_element_node_at_source_code_position(&c.description().original, path, offset)
126}
127
128fn fill_highlight_data(
129    repeater_path: &[SmolStr],
130    element: &ElementRc,
131    component_instance: &ItemTreeBox,
132    root_component_instance: &ItemTreeBox,
133    filter_clipped: ElementPositionFilter,
134    values: &mut Vec<HighlightedRect>,
135) {
136    if element.borrow().repeated.is_some() {
137        // avoid a panic
138        return;
139    }
140
141    if let [first, rest @ ..] = repeater_path {
142        generativity::make_guard!(guard);
143        let rep = crate::dynamic_item_tree::get_repeater_by_name(
144            component_instance.borrow_instance(),
145            first.as_str(),
146            guard,
147        );
148        for idx in rep.0.range() {
149            if let Some(c) = rep.0.instance_at(idx) {
150                generativity::make_guard!(guard);
151                fill_highlight_data(
152                    rest,
153                    element,
154                    &c.unerase(guard),
155                    root_component_instance,
156                    filter_clipped,
157                    values,
158                );
159            }
160        }
161    } else {
162        let vrc = VRc::into_dyn(
163            component_instance.borrow_instance().self_weak().get().unwrap().upgrade().unwrap(),
164        );
165        let root_vrc = VRc::into_dyn(
166            root_component_instance.borrow_instance().self_weak().get().unwrap().upgrade().unwrap(),
167        );
168        let index = element.borrow().item_index.get().copied().unwrap();
169        let item_rc = ItemRc::new(vrc.clone(), index);
170        if filter_clipped == ElementPositionFilter::IncludeClipped || item_rc.is_visible() {
171            let geometry = item_rc.geometry();
172            if geometry.size.is_empty() {
173                return;
174            }
175            let origin = item_rc.map_to_item_tree(geometry.origin, &root_vrc);
176            let top_right = item_rc.map_to_item_tree(
177                geometry.origin + euclid::vec2(geometry.size.width, 0.),
178                &root_vrc,
179            );
180            let delta = top_right - origin;
181            let width = delta.length();
182            let height = geometry.size.height * width / geometry.size.width;
183            // Compute the angle between the origin(top-right) and top-left corner
184            let angle_rad = delta.y.atan2(delta.x);
185            let (sin, cos) = angle_rad.sin_cos();
186            let center = euclid::point2(
187                origin.x + (width / 2.0) * cos - (height / 2.0) * sin,
188                origin.y + (width / 2.0) * sin + (height / 2.0) * cos,
189            );
190            values.push(HighlightedRect {
191                rect: LogicalRect {
192                    origin: center - euclid::vec2(width / 2.0, height / 2.0),
193                    size: euclid::size2(width, height),
194                },
195                angle: angle_rad.to_degrees(),
196            });
197        }
198    }
199}
200
201// Go over all elements in original to find the one that is highlighted
202fn find_element_node_at_source_code_position(
203    component: &Rc<Component>,
204    path: &Path,
205    offset: u32,
206) -> Vec<(ElementRc, usize)> {
207    let mut result = Vec::new();
208    i_slint_compiler::object_tree::recurse_elem_including_sub_components(
209        component,
210        &(),
211        &mut |elem, &()| {
212            if elem.borrow().repeated.is_some() {
213                return;
214            }
215            for (index, node_path, node_range) in
216                elem.borrow().debug.iter().enumerate().map(|(i, n)| {
217                    let text_range = n
218                        .node
219                        .QualifiedName()
220                        .map(|n| n.text_range())
221                        .or_else(|| {
222                            n.node
223                                .child_token(i_slint_compiler::parser::SyntaxKind::LBrace)
224                                .map(|n| n.text_range())
225                        })
226                        .expect("A Element must contain a LBrace somewhere pretty early");
227
228                    (i, n.node.source_file.path(), text_range)
229                })
230            {
231                if node_path == path && node_range.contains(offset.into()) {
232                    result.push((elem.clone(), index));
233                }
234            }
235        },
236    );
237    result
238}
239
240fn repeater_path(elem: &ElementRc) -> Option<Vec<SmolStr>> {
241    let enclosing = elem.borrow().enclosing_component.upgrade().unwrap();
242    if let Some(parent) = enclosing.parent_element.upgrade() {
243        // This is not a repeater, it might be a popup menu which is not supported ATM
244        parent.borrow().repeated.as_ref()?;
245
246        let mut r = repeater_path(&parent)?;
247        r.push(parent.borrow().id.clone());
248        Some(r)
249    } else {
250        Some(vec![])
251    }
252}