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
4use std::{path::PathBuf, rc::Rc};
5
6use i_slint_compiler::{
7 object_tree::ElementRc,
8 parser::{SyntaxKind, TextSize},
9};
10use i_slint_core::lengths::{LogicalPoint, LogicalRect};
11use slint_interpreter::{ComponentHandle, ComponentInstance};
12
13use crate::common;
14
15use crate::preview::{ext::ElementRcNodeExt, ui, SelectionNotification};
16
17#[derive(Clone, Debug)]
18pub struct ElementSelection {
19 pub path: PathBuf,
20 pub offset: TextSize,
21 pub instance_index: usize,
22}
23
24impl ElementSelection {
25 pub fn as_element(&self) -> Option<ElementRc> {
26 let component_instance: ComponentInstance = super::component_instance()?;
27
28 let elements: Vec<(Rc>, …)> =
29 component_instance.element_node_at_source_code_position(&self.path, self.offset.into());
30 elements.get(self.instance_index).or_else(|| elements.first()).map(|(e: &Rc>, _)| e.clone())
31 }
32
33 pub fn as_element_node(&self) -> Option<common::ElementRcNode> {
34 let element: Rc> = self.as_element()?;
35
36 let debug_index: Option = {
37 let e: Ref<'_, Element> = element.borrow();
38 e.debug.iter().position(|d: &ElementDebugInfo| {
39 d.node.source_file.path() == self.path && d.node.text_range().start() == self.offset
40 })
41 };
42
43 debug_index.map(|i: usize| common::ElementRcNode { element, debug_index: i })
44 }
45}
46
47// Look at an element and if it is a sub component, jump to its root_element()
48fn self_or_embedded_component_root(element: &ElementRc) -> ElementRc {
49 let elem: Ref<'_, Element> = element.borrow();
50 if elem.repeated.is_some() {
51 if let i_slint_compiler::langtype::ElementType::Component(base: &Rc) = &elem.base_type {
52 return base.root_element.clone();
53 }
54 }
55
56 element.clone()
57}
58
59fn lsp_element_node_position(
60 element: &common::ElementRcNode,
61) -> Option<(String, lsp_types::Range)> {
62 let location = element.with_element_node(|n: &Element| {
63 nFilter<{unknown}, impl FnMut(…) -> …>.parent()
64 .filter(|p: &{unknown}| p.kind() == i_slint_compiler::parser::SyntaxKind::SubElement)
65 .map_or_else(
66 || Some(n.source_file.text_size_to_file_line_column(n.text_range().start())),
67 |p| Some(p.source_file.text_size_to_file_line_column(p.text_range().start())),
68 )
69 });
70 location.map(|(f, sl: u32, sc: u32, el: u32, ec: u32)| {
71 use lsp_types::{Position, Range};
72 let start: Position = Position::new((sl as u32).saturating_sub(1), (sc as u32).saturating_sub(1));
73 let end: Position = Position::new((el as u32).saturating_sub(1), (ec as u32).saturating_sub(1));
74
75 (f, Range::new(start, end))
76 })
77}
78
79fn element_covers_point(
80 position: LogicalPoint,
81 component_instance: &ComponentInstance,
82 selected_element: &ElementRc,
83) -> Option<LogicalRect> {
84 slint_interpreterOption<&Rect>::highlight::element_positions(
85 &component_instance.clone_strong().into(),
86 selected_element,
87 filter_clipped:slint_interpreter::highlight::ElementPositionFilter::ExcludeClipped,
88 )
89 .iter()
90 .find(|p: &&Rect| p.contains(position))
91 .copied()
92}
93
94pub fn unselect_element() {
95 super::set_selected_element(selection:None, &[], editor_notification:SelectionNotification::Never);
96}
97
98pub fn select_element_at_source_code_position(
99 path: PathBuf,
100 offset: TextSize,
101 position: Option<LogicalPoint>,
102 editor_notification: crate::preview::SelectionNotification,
103) {
104 let Some(component_instance: ComponentInstance) = super::component_instance() else {
105 return;
106 };
107 select_element_at_source_code_position_impl(
108 &component_instance,
109 path,
110 offset,
111 position,
112 editor_notification,
113 )
114}
115
116fn select_element_at_source_code_position_impl(
117 component_instance: &ComponentInstance,
118 path: PathBuf,
119 offset: TextSize,
120 position: Option<LogicalPoint>,
121 editor_notification: SelectionNotification,
122) {
123 let positions: Vec> = component_instance.component_positions(&path, offset.into());
124
125 let instance_index: usize = positionOption
126 .and_then(|p: Point2D| positions.iter().enumerate().find_map(|(i: usize, g: &Rect)| g.contains(p).then_some(i)))
127 .unwrap_or_default();
128
129 super::set_selected_element(
130 selection:Some(ElementSelection { path, offset, instance_index }),
131 &positions,
132 editor_notification,
133 );
134}
135
136fn select_element_node(
137 component_instance: &ComponentInstance,
138 selected_element: &common::ElementRcNode,
139 position: Option<LogicalPoint>,
140) {
141 let (path: PathBuf, offset: TextSize) = selected_element.path_and_offset();
142
143 select_element_at_source_code_position_impl(
144 component_instance,
145 path,
146 offset,
147 position,
148 editor_notification:SelectionNotification::Never, // We update directly;-)
149 );
150
151 if let Some(document_position: (String, Range)) = lsp_element_node_position(selected_element) {
152 super::ask_editor_to_show_document(&document_position.0, selection:document_position.1, take_focus:false);
153 }
154}
155
156// Return the real root element, skipping the WindowElement that might got added
157pub fn root_element(component_instance: &ComponentInstance) -> ElementRc {
158 let root_element: Rc> = component_instance.definition().root_component().root_element.clone();
159 if root_element.borrow().debug.is_empty() {
160 // The root element has no debug set if it is a window inserted by the compiler.
161 // That window will have one child -- the "real root", but it might
162 // have a few more compiler-generated nodes in front or behind the "real root"!
163 let child: Option>> =
164 root_element.borrow().children.iter().find(|c: &&Rc>| !c.borrow().debug.is_empty()).cloned();
165 child.unwrap_or(default:root_element)
166 } else {
167 root_element
168 }
169}
170
171#[derive(Clone)]
172pub struct SelectionCandidate {
173 pub element: ElementRc,
174 pub debug_index: usize,
175 pub geometry: LogicalRect,
176 pub is_in_root_component: bool,
177}
178
179impl SelectionCandidate {
180 pub fn is_selected_element_node(&self, selection: &common::ElementRcNode) -> bool {
181 self.as_element_node().map(|en: ElementRcNode| en.path_and_offset()) == Some(selection.path_and_offset())
182 }
183
184 pub fn as_element_node(&self) -> Option<common::ElementRcNode> {
185 common::ElementRcNode::new(self.element.clone(), self.debug_index)
186 }
187}
188
189impl std::fmt::Debug for SelectionCandidate {
190 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
191 write!(f, "SelectionCandidate {{ {:?} }}@({:?})", self.as_element_node(), self.geometry)
192 }
193}
194
195// Traverse the element tree in reverse render order and collect information on
196// all elements that "render" at the given x and y coordinates
197fn collect_all_element_nodes_covering_impl(
198 position: LogicalPoint,
199 component_instance: &ComponentInstance,
200 current_element: &ElementRc,
201 result: &mut Vec<SelectionCandidate>,
202) {
203 let ce: Rc> = self_or_embedded_component_root(current_element);
204
205 for c: &Rc> in ce.borrow().children.iter().rev() {
206 collect_all_element_nodes_covering_impl(position, component_instance, current_element:c, result);
207 }
208
209 if let Some(geometry: Rect) = element_covers_point(position, component_instance, selected_element:current_element) {
210 for (i: usize, d: &ElementDebugInfo) in ce.borrow().debug.iter().enumerate().rev() {
211 if !common::is_element_node_ignored(&d.node)
212 && !d.node.source_file.path().starts_with("builtin:/")
213 {
214 // All nodes have the same geometry
215 result.push(SelectionCandidate {
216 element: ce.clone(),
217 debug_index: i,
218 is_in_root_component: false,
219 geometry,
220 });
221 }
222 }
223 }
224}
225
226fn assign_is_in_root_component(candidates: &mut Vec<SelectionCandidate>) {
227 let mut root_text_range: Option<i_slint_compiler::parser::TextRange> = None;
228 for sc: &mut SelectionCandidate in candidates.iter_mut().rev() {
229 let Some(en: ElementRcNode) = sc.as_element_node() else {
230 continue;
231 };
232
233 let node_text_range: TextRange = en.with_element_node(|n: &Element| n.text_range());
234 if let Some(rtr: TextRange) = root_text_range {
235 sc.is_in_root_component = rtr.contains_range(node_text_range);
236 } else {
237 root_text_range = Some(node_text_range);
238 sc.is_in_root_component = true;
239 }
240 }
241}
242
243pub fn collect_all_element_nodes_covering(
244 position: LogicalPoint,
245 component_instance: &ComponentInstance,
246) -> Vec<SelectionCandidate> {
247 let root_element: Rc> = root_element(component_instance);
248 let mut elements: Vec = Vec::new();
249 collect_all_element_nodes_covering_impl(
250 position,
251 component_instance,
252 &root_element,
253 &mut elements,
254 );
255
256 assign_is_in_root_component(&mut elements);
257
258 elements
259}
260
261fn select_element_at_impl(
262 component_instance: &ComponentInstance,
263 position: LogicalPoint,
264 enter_component: bool,
265) -> Option<common::ElementRcNode> {
266 for sc: &SelectionCandidate in &collect_all_element_nodes_covering(position, component_instance) {
267 if let Some(en: ElementRcNode) = filter_nodes_for_selection(selection_candidate:sc, enter_component) {
268 return Some(en);
269 }
270 }
271 None
272}
273
274pub fn select_element_at(x: f32, y: f32, enter_component: bool) {
275 let Some(component_instance: ComponentInstance) = super::component_instance() else {
276 return;
277 };
278
279 let position: Point2D = LogicalPoint::new(x, y);
280
281 let Some(en: ElementRcNode) = select_element_at_impl(&component_instance, position, enter_component) else {
282 return;
283 };
284
285 select_element_node(&component_instance, &en, position:Some(position));
286}
287
288pub fn selection_stack_at(
289 x: f32,
290 y: f32,
291) -> slint::ModelRc<crate::preview::ui::SelectionStackFrame> {
292 let Some(component_instance) = &super::component_instance() else {
293 return Default::default();
294 };
295 let root_element = root_element(component_instance);
296 let Some(root_geometry) = component_instance.element_positions(&root_element).first().cloned()
297 else {
298 return Default::default();
299 };
300
301 let position = LogicalPoint::new(x, y);
302
303 let (known_components, mut selected) = crate::preview::PREVIEW_STATE.with(|preview_state| {
304 let preview_state = preview_state.borrow();
305
306 let known_components = preview_state.known_components.clone();
307 let selected =
308 preview_state.selected.as_ref().and_then(|s| s.as_element_node()).filter(|en| {
309 en.geometries(component_instance).iter().any(|gr| gr.contains(position))
310 });
311
312 (known_components, selected)
313 });
314
315 let mut longest_path_prefix = PathBuf::new();
316
317 let mut result = collect_all_element_nodes_covering(position, component_instance)
318 .iter()
319 .filter(|sn| filter_nodes_for_selection(sn, true).is_some())
320 .map(|sc| {
321 let (type_name, id, is_layout, is_selected, path, offset) = sc
322 .as_element_node()
323 .map(|en| {
324 let (path, offset) = en.path_and_offset();
325 let offset: u32 = offset.into();
326
327 let is_selected = if selected.is_none() {
328 select_element_node(component_instance, &en, Some(position));
329 selected = Some(en.clone());
330 true
331 } else {
332 selected.as_ref() == Some(&en)
333 };
334
335 let (type_name, id, is_layout) = en.with_element_debug(|di| {
336 let id = di
337 .node
338 .parent()
339 .and_then(|p| {
340 if p.kind() == SyntaxKind::SubElement {
341 p.child_token(SyntaxKind::Identifier)
342 .map(|t| t.text().to_string())
343 } else {
344 None
345 }
346 })
347 .unwrap_or_default();
348
349 let type_name = {
350 di.node
351 .parent()
352 .and_then(|p| {
353 if p.kind() == SyntaxKind::Component {
354 p.child_node(SyntaxKind::DeclaredIdentifier)
355 .map(|t| t.text().to_string())
356 } else {
357 None
358 }
359 })
360 .or_else(|| {
361 di.node
362 .QualifiedName()
363 .map(|qn| qn.text().to_string().trim().to_string())
364 })
365 .unwrap_or_default()
366 .trim()
367 .to_string()
368 };
369
370 (type_name, id, di.layout.is_some())
371 });
372
373 (type_name, id, is_layout, is_selected, path, offset)
374 })
375 .unwrap_or_default();
376
377 if path.strip_prefix("/@").is_err() && path != PathBuf::new() {
378 if longest_path_prefix == PathBuf::new() {
379 longest_path_prefix = path.clone();
380 } else {
381 longest_path_prefix =
382 std::iter::zip(longest_path_prefix.components(), path.components())
383 .take_while(|(l, p)| l == p)
384 .map(|(l, _)| l)
385 .collect();
386 }
387 }
388
389 let width = (sc.geometry.size.width / root_geometry.size.width) * 100.0;
390 let height = (sc.geometry.size.height / root_geometry.size.height) * 100.0;
391 let x = ((sc.geometry.origin.x + root_geometry.origin.x) / root_geometry.size.width)
392 * 100.0;
393 let y = ((sc.geometry.origin.y + root_geometry.origin.y) / root_geometry.size.height)
394 * 100.0;
395
396 let is_interactive = known_components
397 .iter()
398 .position(|kc| kc.name.as_str() == type_name.as_str())
399 .map(|index| known_components.get(index).unwrap().is_interactive)
400 .unwrap_or_default();
401
402 crate::preview::ui::SelectionStackFrame {
403 width,
404 height,
405 x,
406 y,
407 is_in_root_component: sc.is_in_root_component,
408 is_selected,
409 is_layout,
410 is_interactive,
411 type_name: type_name.into(),
412 file_name: path.to_string_lossy().to_string().into(),
413 element_path: path.to_string_lossy().to_string().into(),
414 element_offset: offset as i32,
415 id: id.into(),
416 }
417 })
418 .collect::<Vec<_>>();
419
420 for frame in result.iter_mut() {
421 let file_name = PathBuf::from(frame.file_name.to_string());
422 let new_file_name = {
423 if let Some(library) = file_name.to_string_lossy().strip_prefix("/@") {
424 format!("@{library:?}")
425 } else if file_name == longest_path_prefix {
426 file_name.file_name().unwrap_or_default().to_string_lossy().to_string()
427 } else {
428 file_name
429 .strip_prefix(&longest_path_prefix)
430 .unwrap_or(&file_name)
431 .to_string_lossy()
432 .to_string()
433 }
434 };
435 frame.file_name = new_file_name.into();
436 }
437
438 Rc::new(slint::VecModel::from(result)).into()
439}
440
441pub fn filter_sort_selection_stack(
442 model: slint::ModelRc<crate::preview::ui::SelectionStackFrame>,
443 filter_text: slint::SharedString,
444 filter: crate::preview::ui::SelectionStackFilter,
445) -> slint::ModelRc<crate::preview::ui::SelectionStackFrame> {
446 use crate::preview::ui::{SelectionStackFilter, SelectionStackFrame};
447 use slint::ModelExt;
448
449 fn filter_fn(frame: &SelectionStackFrame, filter: SelectionStackFilter) -> bool {
450 match filter {
451 SelectionStackFilter::Nothing => false,
452 SelectionStackFilter::Layouts => frame.is_layout,
453 SelectionStackFilter::Interactive => frame.is_interactive,
454 SelectionStackFilter::Others => !frame.is_interactive && !frame.is_layout,
455 SelectionStackFilter::LayoutsAndInteractive => frame.is_layout || frame.is_interactive,
456 SelectionStackFilter::LayoutsAndOthers => {
457 frame.is_layout || (!frame.is_layout && !frame.is_interactive)
458 }
459 SelectionStackFilter::InteractiveAndOthers => {
460 frame.is_interactive || (!frame.is_layout && !frame.is_interactive)
461 }
462 SelectionStackFilter::Everything => true,
463 }
464 }
465
466 let filter_text = filter_text.to_string();
467
468 if filter_text.is_empty() && filter == SelectionStackFilter::Everything {
469 model
470 } else if filter_text.as_str().chars().any(|c| !c.is_lowercase()) {
471 Rc::new(model.filter(move |frame| {
472 filter_fn(frame, filter)
473 && (frame.id.contains(&filter_text)
474 || frame.type_name.contains(&filter_text)
475 || frame.file_name.contains(&filter_text))
476 }))
477 .into()
478 } else {
479 Rc::new(model.filter(move |frame| {
480 filter_fn(frame, filter)
481 && (frame.id.to_lowercase().contains(&filter_text)
482 || frame.type_name.to_lowercase().contains(&filter_text)
483 || frame.file_name.to_lowercase().contains(&filter_text))
484 }))
485 .into()
486 }
487}
488
489pub fn parent_layout_kind(element: &common::ElementRcNode) -> ui::LayoutKind {
490 element.parent().map(|p| p.layout_kind()).unwrap_or(default:ui::LayoutKind::None)
491}
492
493fn filter_nodes_for_selection(
494 selection_candidate: &SelectionCandidate,
495 enter_component: bool,
496) -> Option<common::ElementRcNode> {
497 if !selection_candidate.is_in_root_component && !enter_component {
498 return None;
499 }
500
501 selection_candidate.as_element_node().filter(|en: &ElementRcNode| {
502 en.with_element_node(|n: &Element| n.parent().map_or(true, |p| p.kind() != SyntaxKind::Component))
503 })
504}
505
506pub fn select_element_behind_impl(
507 component_instance: &ComponentInstance,
508 selected_element_node: &common::ElementRcNode,
509 position: LogicalPoint,
510 enter_component: bool,
511 reverse: bool,
512) -> Option<common::ElementRcNode> {
513 let elements = collect_all_element_nodes_covering(position, component_instance);
514 let current_selection_position =
515 elements.iter().position(|sc| sc.is_selected_element_node(selected_element_node))?;
516
517 let (start_position, iterations) = if reverse {
518 let start_position = current_selection_position.saturating_sub(1);
519 (start_position, current_selection_position)
520 } else {
521 let start_position = current_selection_position + 1;
522 (start_position, elements.len().saturating_sub(current_selection_position + 1))
523 };
524
525 for i in 0..iterations {
526 let mapped_index = if reverse {
527 assert!(i <= start_position);
528 start_position - i
529 } else {
530 assert!(i + start_position < elements.len());
531 start_position + i
532 };
533 if let Some(en) =
534 filter_nodes_for_selection(elements.get(mapped_index).unwrap(), enter_component)
535 {
536 return Some(en);
537 }
538 }
539
540 None
541}
542
543pub fn select_element_behind(x: f32, y: f32, enter_component: bool, reverse: bool) {
544 let Some(component_instance: ComponentInstance) = super::component_instance() else {
545 return;
546 };
547 let position: Point2D = LogicalPoint::new(x, y);
548 let Some(selected_element_node: ElementRcNode) =
549 super::selected_element().and_then(|sel: ElementSelection| sel.as_element_node())
550 else {
551 return;
552 };
553
554 let Some(en: ElementRcNode) = select_element_behind_impl(
555 &component_instance,
556 &selected_element_node,
557 position,
558 enter_component,
559 reverse,
560 ) else {
561 return;
562 };
563
564 select_element_node(&component_instance, &en, position:Some(position));
565}
566
567// Called from UI thread!
568pub fn reselect_element() {
569 let Some(selected: ElementSelection) = super::selected_element() else {
570 super::set_selected_element(selection:None, &[], editor_notification:SelectionNotification::Never);
571 return;
572 };
573 let Some(component_instance: ComponentInstance) = super::component_instance() else {
574 return;
575 };
576 let positions: Vec> = component_instance.component_positions(&selected.path, selected.offset.into());
577
578 super::set_selected_element(selection:Some(selected), &positions, editor_notification:SelectionNotification::Never);
579}
580
581#[cfg(test)]
582mod tests {
583 use crate::common::test;
584
585 use std::path::PathBuf;
586
587 use i_slint_core::lengths::LogicalPoint;
588 use slint_interpreter::ComponentInstance;
589
590 fn demo_app() -> ComponentInstance {
591 crate::preview::test::interpret_test(
592 "fluent",
593 r#"import { Button } from "std-widgets.slint";
594
595component SomeComponent { // 69
596 @children
597}
598
599component Main { // 109
600 width: 200px;
601 height: 200px;
602
603 HorizontalLayout { // 160
604 Rectangle { // 194
605 SomeComponent { // 225
606 Button { // 264
607 text: "Press me";
608 }
609 }
610 }
611 }
612}
613
614export component Entry inherits Main { /* @lsp:ignore-node */ } // 401
615"#,
616 )
617 }
618
619 #[test]
620 fn test_find_covering_elements() {
621 let type_loader = demo_app();
622
623 let mut covers_center = super::collect_all_element_nodes_covering(
624 LogicalPoint::new(100.0, 100.0),
625 &type_loader,
626 );
627
628 // Remove the "button" implementation details. They must be at the start:
629 let button_path = PathBuf::from("builtin:/fluent/button.slint");
630 let first_non_button = covers_center
631 .iter()
632 .position(|sc| {
633 sc.as_element_node().map(|en| en.path_and_offset().0).as_ref() != Some(&button_path)
634 })
635 .unwrap();
636 covers_center.drain(0..first_non_button);
637
638 let test_file = test::test_file_name("test_data.slint");
639
640 let expected_offsets = [264_u32, 69, 225, 194, 160, 109];
641 assert_eq!(covers_center.len(), expected_offsets.len());
642
643 for (candidate, expected_offset) in covers_center.iter().zip(&expected_offsets) {
644 let (path, offset) = candidate.as_element_node().unwrap().path_and_offset();
645 assert_eq!(&path, &test_file);
646 assert_eq!(offset, (*expected_offset).into());
647 }
648
649 let covers_below = super::collect_all_element_nodes_covering(
650 LogicalPoint::new(100.0, 180.0),
651 &type_loader,
652 );
653
654 // All but the button itself as well as the SomeComponent (impl and use)
655 assert_eq!(covers_below.len(), covers_center.len() - 3);
656
657 for (below, center) in covers_below.iter().zip(&covers_center[3..]) {
658 assert_eq!(
659 below.as_element_node().map(|en| en.path_and_offset()),
660 center.as_element_node().map(|en| en.path_and_offset())
661 );
662 }
663 }
664
665 #[test]
666 fn test_element_selection() {
667 let component_instance = demo_app();
668
669 let covers_center = super::collect_all_element_nodes_covering(
670 LogicalPoint::new(100.0, 100.0),
671 &component_instance,
672 )
673 .iter()
674 .flat_map(|sc| sc.as_element_node())
675 .map(|en| en.path_and_offset())
676 .collect::<Vec<_>>();
677
678 eprintln!("Covers:");
679 for (i, (p, ts)) in covers_center.iter().enumerate() {
680 println!(" {i}: {p:?}:{ts:?}");
681 }
682 eprintln!("Done");
683
684 // Select without crossing boundaries
685 // --------------------------------------------------------------------
686 let select = super::select_element_at_impl(
687 &component_instance,
688 LogicalPoint::new(100.0, 100.0),
689 false,
690 )
691 .unwrap();
692 assert_eq!(&select.path_and_offset(), covers_center.first().unwrap());
693
694 // Try to move towards the viewer:
695 assert!(super::select_element_behind_impl(
696 &component_instance,
697 &select,
698 LogicalPoint::new(100.0, 100.0),
699 false,
700 true
701 )
702 .is_none());
703
704 // Move deeper into the image:
705 let next = super::select_element_behind_impl(
706 &component_instance,
707 &select,
708 LogicalPoint::new(100.0, 100.0),
709 false,
710 false,
711 )
712 .unwrap();
713 assert_eq!(&next.path_and_offset(), covers_center.get(2).unwrap());
714 let next = super::select_element_behind_impl(
715 &component_instance,
716 &next,
717 LogicalPoint::new(100.0, 100.0),
718 false,
719 false,
720 )
721 .unwrap();
722 assert_eq!(&next.path_and_offset(), covers_center.get(3).unwrap());
723 let next = super::select_element_behind_impl(
724 &component_instance,
725 &next,
726 LogicalPoint::new(100.0, 100.0),
727 false,
728 false,
729 )
730 .unwrap();
731 assert_eq!(&next.path_and_offset(), covers_center.get(4).unwrap());
732
733 assert!(super::select_element_behind_impl(
734 &component_instance,
735 &next,
736 LogicalPoint::new(100.0, 100.0),
737 false,
738 false
739 )
740 .is_none());
741
742 // Move towards the viewer:
743 let prev = super::select_element_behind_impl(
744 &component_instance,
745 &next,
746 LogicalPoint::new(100.0, 100.0),
747 false,
748 true,
749 )
750 .unwrap();
751 assert_eq!(&prev.path_and_offset(), covers_center.get(3).unwrap());
752 let prev = super::select_element_behind_impl(
753 &component_instance,
754 &prev,
755 LogicalPoint::new(100.0, 100.0),
756 false,
757 true,
758 )
759 .unwrap();
760 assert_eq!(&prev.path_and_offset(), covers_center.get(2).unwrap());
761 let prev = super::select_element_behind_impl(
762 &component_instance,
763 &prev,
764 LogicalPoint::new(100.0, 100.0),
765 false,
766 true,
767 )
768 .unwrap();
769 assert_eq!(&prev.path_and_offset(), covers_center.first().unwrap());
770
771 // Select with crossing component boundaries
772 // --------------------------------------------------------------------
773 let select = super::select_element_at_impl(
774 &component_instance,
775 LogicalPoint::new(100.0, 100.0),
776 true,
777 )
778 .unwrap();
779 assert_eq!(&select.path_and_offset(), covers_center.first().unwrap());
780
781 // Move deeper into the image:
782 let next = super::select_element_behind_impl(
783 &component_instance,
784 &select,
785 LogicalPoint::new(100.0, 100.0),
786 true,
787 false,
788 )
789 .unwrap();
790 assert_eq!(&next.path_and_offset(), covers_center.get(2).unwrap());
791 let next = super::select_element_behind_impl(
792 &component_instance,
793 &next,
794 LogicalPoint::new(100.0, 100.0),
795 true,
796 false,
797 )
798 .unwrap();
799 assert_eq!(&next.path_and_offset(), covers_center.get(3).unwrap());
800 let next = super::select_element_behind_impl(
801 &component_instance,
802 &next,
803 LogicalPoint::new(100.0, 100.0),
804 true,
805 false,
806 )
807 .unwrap();
808 assert_eq!(&next.path_and_offset(), covers_center.get(4).unwrap());
809
810 assert!(super::select_element_behind_impl(
811 &component_instance,
812 &next,
813 LogicalPoint::new(100.0, 100.0),
814 true,
815 false
816 )
817 .is_none());
818
819 // Move towards the viewer:
820 let prev = super::select_element_behind_impl(
821 &component_instance,
822 &next,
823 LogicalPoint::new(100.0, 100.0),
824 true,
825 true,
826 )
827 .unwrap();
828 assert_eq!(&prev.path_and_offset(), covers_center.get(3).unwrap());
829 let prev = super::select_element_behind_impl(
830 &component_instance,
831 &prev,
832 LogicalPoint::new(100.0, 100.0),
833 true,
834 true,
835 )
836 .unwrap();
837 assert_eq!(&prev.path_and_offset(), covers_center.get(2).unwrap());
838 let prev = super::select_element_behind_impl(
839 &component_instance,
840 &prev,
841 LogicalPoint::new(100.0, 100.0),
842 true,
843 true,
844 )
845 .unwrap();
846 assert_eq!(&prev.path_and_offset(), covers_center.first().unwrap());
847
848 assert!(super::select_element_behind_impl(
849 &component_instance,
850 &prev,
851 LogicalPoint::new(100.0, 100.0),
852 true,
853 true
854 )
855 .is_none());
856 }
857}
858