1use crate::instance::{Instance, SubComponentInstance};
12use i_slint_compiler::llr::{ItemInstanceIdx, SubComponentIdx, SubComponentInstanceIdx};
13use i_slint_compiler::object_tree::ElementRc;
14use i_slint_core::graphics::euclid;
15use i_slint_core::item_tree::ItemTreeVTable;
16use i_slint_core::items::ItemRc;
17use i_slint_core::lengths::{LogicalPoint, LogicalRect};
18use std::path::Path;
19use std::pin::Pin;
20use std::rc::Rc;
21use vtable::VRc;
22
23#[derive(Clone, Copy, Debug, Default)]
25pub struct HighlightedRect {
26 pub rect: LogicalRect,
28 pub angle: f32,
30}
31impl HighlightedRect {
32 pub fn contains(&self, position: LogicalPoint) -> bool {
34 let center = self.rect.center();
35 let rotation = euclid::Rotation2D::radians((-self.angle).to_radians());
36 let transformed = center + rotation.transform_vector(position - center);
37 self.rect.contains(transformed)
38 }
39}
40
41#[derive(Copy, Clone, Eq, PartialEq)]
43pub enum ElementPositionFilter {
44 IncludeClipped,
46 ExcludeClipped,
48}
49
50pub fn element_positions(
55 instance: &VRc<ItemTreeVTable, Instance>,
56 element: &ElementRc,
57 filter: ElementPositionFilter,
58) -> Vec<HighlightedRect> {
59 let target = walk_to_native_root(element);
65 let Some(target_loc) = source_location_of(&target) else {
66 return Vec::new();
67 };
68 let use_site = if Rc::ptr_eq(&target, element) { None } else { source_location_of(element) };
73 positions_by_source(
74 instance,
75 &target_loc.0,
76 target_loc.1,
77 use_site.as_ref().map(|(p, o)| (p.as_path(), *o)),
78 filter,
79 )
80}
81
82fn source_location_of(element: &ElementRc) -> Option<(std::path::PathBuf, u32)> {
86 use i_slint_compiler::diagnostics::Spanned;
87 let e = element.borrow();
88 let path = e.source_file()?.path().to_path_buf();
89 Some((path, e.span().offset as u32))
90}
91
92fn walk_to_native_root(element: &ElementRc) -> ElementRc {
97 let mut current = element.clone();
98 loop {
99 let next = {
100 let b = current.borrow();
101 if let i_slint_compiler::langtype::ElementType::Component(c) = &b.base_type {
102 Some(c.root_element.clone())
103 } else {
104 None
105 }
106 };
107 match next {
108 Some(n) => current = n,
109 None => return current,
110 }
111 }
112}
113
114pub(crate) fn component_positions(
117 instance: &VRc<ItemTreeVTable, Instance>,
118 path: &Path,
119 offset: u32,
120) -> Vec<HighlightedRect> {
121 element_node_at_source_code_position(instance, path, offset)
122 .into_iter()
123 .flat_map(|(element, _)| {
124 element_positions(instance, &element, ElementPositionFilter::IncludeClipped)
125 })
126 .collect()
127}
128
129pub(crate) fn element_node_at_source_code_position(
133 instance: &VRc<ItemTreeVTable, Instance>,
134 path: &Path,
135 offset: u32,
136) -> Vec<(ElementRc, usize)> {
137 let Some(type_loader) = instance.type_loaders.type_loader.as_ref() else {
138 return Vec::new();
139 };
140 let Some(doc) = type_loader.get_document(path) else {
141 return Vec::new();
142 };
143 let mut result = Vec::new();
144 for component in &doc.inner_components {
147 visit_element_for_position(&component.root_element, path, offset, &mut result);
148 }
149 result
150}
151
152fn visit_element_for_position(
153 element: &ElementRc,
154 path: &Path,
155 offset: u32,
156 result: &mut Vec<(ElementRc, usize)>,
157) {
158 if element.borrow().repeated.is_some() {
159 let base = match &element.borrow().base_type {
164 i_slint_compiler::langtype::ElementType::Component(c) => Some(c.root_element.clone()),
165 _ => None,
166 };
167 if let Some(root) = base {
168 visit_element_for_position(&root, path, offset, result);
169 }
170 return;
171 }
172 for (index, node_path, node_range) in element.borrow().debug.iter().enumerate().map(|(i, n)| {
173 let text_range = n
174 .node
175 .QualifiedName()
176 .map(|n| n.text_range())
177 .or_else(|| {
178 n.node
179 .child_token(i_slint_compiler::parser::SyntaxKind::LBrace)
180 .map(|n| n.text_range())
181 })
182 .expect("An Element must contain a LBrace somewhere");
183 (i, n.node.source_file.path(), text_range)
184 }) {
185 if node_path == path && node_range.contains(offset.into()) {
186 result.push((element.clone(), index));
187 }
188 }
189 let children = element.borrow().children.clone();
190 for child in &children {
191 visit_element_for_position(child, path, offset, result);
192 }
193}
194
195fn find_flat_indices_for_item(
200 instance: &VRc<ItemTreeVTable, Instance>,
201 target_sc_idx: SubComponentIdx,
202 target_local: ItemInstanceIdx,
203 use_site: Option<(&Path, u32)>,
204) -> Vec<usize> {
205 let cu = &instance.root_sub_component.compilation_unit;
206 let root_ty = instance.root_sub_component.sub_component_idx;
207 let mut out = Vec::new();
208 for (flat, entry) in instance.item_table.iter().enumerate() {
209 let Some((path, local_idx)) = entry.as_ref() else { continue };
210 if *local_idx != target_local {
211 continue;
212 }
213 if sub_component_idx_at_path(cu, root_ty, path) != target_sc_idx {
214 continue;
215 }
216 if let Some((us_path, us_offset)) = use_site
217 && !path_passes_use_site(cu, root_ty, path, us_path, us_offset)
218 {
219 continue;
220 }
221 out.push(flat);
222 }
223 out
224}
225
226fn path_passes_use_site(
229 cu: &i_slint_compiler::llr::CompilationUnit,
230 mut current: SubComponentIdx,
231 path: &[SubComponentInstanceIdx],
232 us_path: &Path,
233 us_offset: u32,
234) -> bool {
235 for &instance_idx in path {
236 if let Some(debug) = cu.sub_components[current].debug_info.as_ref()
237 && let Some(loc) = debug.sub_component_use_sites.get(instance_idx)
238 && loc.source_file.as_ref().is_some_and(|f| f.path() == us_path)
239 && loc.span.offset as u32 == us_offset
240 {
241 return true;
242 }
243 current = cu.sub_components[current].sub_components[instance_idx].ty;
244 }
245 false
246}
247
248fn all_instances(root: &VRc<ItemTreeVTable, Instance>) -> Vec<VRc<ItemTreeVTable, Instance>> {
251 let mut out = Vec::new();
252 collect_instances(root, &mut out);
253 out
254}
255
256fn collect_instances(
257 inst: &VRc<ItemTreeVTable, Instance>,
258 out: &mut Vec<VRc<ItemTreeVTable, Instance>>,
259) {
260 out.push(inst.clone());
261 collect_row_instances(&inst.root_sub_component, out);
262}
263
264fn collect_row_instances(
265 sub: &Pin<Rc<SubComponentInstance>>,
266 out: &mut Vec<VRc<ItemTreeVTable, Instance>>,
267) {
268 for rep in sub.repeaters.iter() {
269 for row in rep.instances_vec() {
270 collect_instances(&row, out);
271 }
272 }
273 for nested in sub.sub_components.iter() {
274 collect_row_instances(nested, out);
275 }
276}
277
278fn sub_component_idx_at_path(
281 cu: &i_slint_compiler::llr::CompilationUnit,
282 root_idx: SubComponentIdx,
283 path: &[SubComponentInstanceIdx],
284) -> SubComponentIdx {
285 let mut current = root_idx;
286 for &instance_idx in path {
287 let nested = &cu.sub_components[current].sub_components[instance_idx];
288 current = nested.ty;
289 }
290 current
291}
292
293fn item_flat_index_to_rect(
294 instance: &VRc<ItemTreeVTable, Instance>,
295 root: &VRc<ItemTreeVTable, Instance>,
296 flat_idx: usize,
297) -> Option<HighlightedRect> {
298 let vrc = VRc::into_dyn(instance.clone());
299 let root_vrc = VRc::into_dyn(root.clone());
300 let item_rc = ItemRc::new(vrc, flat_idx as u32);
301 let geometry = item_rc.geometry();
302 if geometry.size.is_empty() {
303 return None;
304 }
305 let origin = item_rc.map_to_item_tree(geometry.origin, &root_vrc);
306 let top_right = item_rc
307 .map_to_item_tree(geometry.origin + euclid::vec2(geometry.size.width, 0.), &root_vrc);
308 let delta = top_right - origin;
309 let width = delta.length();
310 let height = if geometry.size.width == 0.0 {
311 0.0
312 } else {
313 geometry.size.height * width / geometry.size.width
314 };
315 let angle_rad = delta.y.atan2(delta.x);
316 let (sin, cos) = angle_rad.sin_cos();
317 let center = euclid::point2(
318 origin.x + (width / 2.0) * cos - (height / 2.0) * sin,
319 origin.y + (width / 2.0) * sin + (height / 2.0) * cos,
320 );
321 Some(HighlightedRect {
322 rect: LogicalRect {
323 origin: center - euclid::vec2(width / 2.0, height / 2.0),
324 size: euclid::size2(width, height),
325 },
326 angle: angle_rad.to_degrees(),
327 })
328}
329
330fn positions_by_source(
331 root: &VRc<ItemTreeVTable, Instance>,
332 target_path: &Path,
333 target_offset: u32,
334 use_site: Option<(&Path, u32)>,
335 filter: ElementPositionFilter,
336) -> Vec<HighlightedRect> {
337 let cu = root.root_sub_component.compilation_unit.clone();
338 let mut results = Vec::new();
339 for instance in all_instances(root) {
343 for sc_idx in 0..cu.sub_components.len() {
344 let sc_idx: SubComponentIdx = sc_idx.into();
345 let sc = &cu.sub_components[sc_idx];
346 let Some(debug) = sc.debug_info.as_ref() else { continue };
347 for (local_idx, item_dbg) in debug.items.iter_enumerated() {
348 let Some(source_file) = item_dbg.source_location.source_file.as_ref() else {
349 continue;
350 };
351 if source_file.path() != target_path {
352 continue;
353 }
354 if item_dbg.source_location.span.offset as u32 != target_offset {
355 continue;
356 }
357 for flat_idx in find_flat_indices_for_item(&instance, sc_idx, local_idx, use_site) {
358 if filter == ElementPositionFilter::ExcludeClipped {
359 let dyn_rc = vtable::VRc::into_dyn(instance.clone());
360 let item_rc = i_slint_core::items::ItemRc::new(dyn_rc, flat_idx as u32);
361 if !item_rc.is_visible() {
362 continue;
363 }
364 }
365 if let Some(rect) = item_flat_index_to_rect(&instance, root, flat_idx) {
366 results.push(rect);
367 }
368 }
369 }
370 }
371 }
372 results
373}