Skip to main content

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