diff --git a/tools/lsp/common.rs b/tools/lsp/common.rs index 88ce189aa..8c11c4f1d 100644 --- a/tools/lsp/common.rs +++ b/tools/lsp/common.rs @@ -75,6 +75,10 @@ impl ElementRcNode { (n.source_file.path().to_owned(), u32::from(n.text_range().start())) }) } + + pub fn as_element(&self) -> &ElementRc { + &self.element + } } pub fn create_workspace_edit( diff --git a/tools/lsp/preview.rs b/tools/lsp/preview.rs index f15dc568e..9ca076bd2 100644 --- a/tools/lsp/preview.rs +++ b/tools/lsp/preview.rs @@ -220,10 +220,8 @@ fn change_geometry_of_selected_element(x: f32, y: f32, width: f32, height: f32) return; }; - let Some(geometry) = component_instance - .element_positions(&selected_element_node.element) - .get(selected.instance_index) - .cloned() + let Some(geometry) = + selected_element_node.geometries(&component_instance).get(selected.instance_index).cloned() else { return; }; diff --git a/tools/lsp/preview/drop_location.rs b/tools/lsp/preview/drop_location.rs index a2dfd4144..49149038c 100644 --- a/tools/lsp/preview/drop_location.rs +++ b/tools/lsp/preview/drop_location.rs @@ -1,8 +1,8 @@ // Copyright © SixtyFPS GmbH // SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-1.2 OR LicenseRef-Slint-commercial -use i_slint_compiler::parser::SyntaxKind; -use i_slint_core::lengths::{LogicalLength, LogicalPoint}; +use i_slint_compiler::parser::{SyntaxKind, SyntaxNode}; +use i_slint_core::lengths::{LogicalPoint, LogicalRect}; use slint_interpreter::ComponentInstance; use crate::common; @@ -51,6 +51,139 @@ impl TextOffsetAdjustment { } } +#[derive(Debug, Eq, PartialEq)] +enum DropMarkDirection { + None, + N, + E, + S, + W, +} + +impl DropMarkDirection { + /// Create `DropMarkSetup` for a 'live' element (accpting the drop) + pub fn for_element( + element_geometry: &LogicalRect, + position: LogicalPoint, + element_is_in_layout: ui::LayoutKind, + ) -> Self { + if Self::element_accepts(element_geometry, position, element_is_in_layout) { + DropMarkDirection::None + } else { + Self::element_direction(element_geometry, position, element_is_in_layout) + } + } + + fn border_size(dimension: f32) -> f32 { + match dimension { + d if d >= 16.0 => 4.0, + d if d >= 14.0 => 3.0, + d if d >= 12.0 => 2.0, + d if d >= 6.0 => 1.0, + _ => 0.0, + } + } + + // Does this hit the center of an element that accepty drops? + fn element_accepts( + element_geometry: &LogicalRect, + position: LogicalPoint, + element_is_in_layout: ui::LayoutKind, + ) -> bool { + if !element_geometry.contains(position) { + return false; + } + + let mut inner = element_geometry.clone(); + + let bs_h = Self::border_size(element_geometry.size.width); + let bs_v = Self::border_size(element_geometry.size.height); + + if bs_h < 0.9 || bs_v < 0.9 { + // To small to sub-divide into individual drop-zones:-/ + return true; + } + + match element_is_in_layout { + ui::LayoutKind::None => {} + ui::LayoutKind::Horizontal => { + inner.origin.x += bs_h; + inner.size.width -= 2.0 * bs_h; + } + ui::LayoutKind::Vertical => { + inner.origin.y += bs_v; + inner.size.height -= 2.0 * bs_v; + } + ui::LayoutKind::Grid => { + inner.origin.x += bs_h; + inner.size.width -= 2.0 * bs_h; + inner.origin.y += bs_v; + inner.size.height -= 2.0 * bs_v; + } + }; + + inner.contains(position) + } + + /// Create `DropMarkSetup` for an element just based on layout info and ignoring + /// the element itself as a drop target (which is handled by `element_accepts`). + fn element_direction( + element_geometry: &LogicalRect, + position: LogicalPoint, + element_is_in_layout: ui::LayoutKind, + ) -> Self { + if !element_geometry.contains(position) { + return DropMarkDirection::None; + } + + if Self::border_size(element_geometry.size.width) <= 0.9 + || Self::border_size(element_geometry.size.height) <= 0.9 + { + // Geometry is too small to sub-divide: + return match element_is_in_layout { + ui::LayoutKind::None => DropMarkDirection::None, + ui::LayoutKind::Horizontal => DropMarkDirection::E, + ui::LayoutKind::Vertical => DropMarkDirection::S, + ui::LayoutKind::Grid => DropMarkDirection::E, + }; + } + + match element_is_in_layout { + ui::LayoutKind::None => DropMarkDirection::None, + ui::LayoutKind::Horizontal => { + if position.x <= element_geometry.origin.x + (element_geometry.size.width / 2.0) { + DropMarkDirection::W + } else { + DropMarkDirection::E + } + } + ui::LayoutKind::Vertical => { + if position.y <= element_geometry.origin.y + (element_geometry.size.height / 2.0) { + DropMarkDirection::N + } else { + DropMarkDirection::S + } + } + ui::LayoutKind::Grid => { + let x = position.x - element_geometry.origin.x; + let y = position.y - element_geometry.origin.y; + let ascend = element_geometry.size.height / element_geometry.size.width; + + let ne_half = y <= ascend * x; + let nw_half = y <= (x * -ascend) + element_geometry.size.height; + eprintln!("GRID: {position:?} in {element_geometry:?}: x: {x}, y: {y}, ascend: {ascend} => NE-half: {} => {ne_half}, NW-half: {} => {nw_half}", -ascend * x, (x * ascend) - element_geometry.size.height); + + match (ne_half, nw_half) { + (false, false) => DropMarkDirection::S, + (false, true) => DropMarkDirection::W, + (true, false) => DropMarkDirection::E, + (true, true) => DropMarkDirection::N, + } + } + } + } +} + pub struct DropInformation { pub target_element_node: common::ElementRcNode, pub insert_info: InsertInformation, @@ -71,7 +204,7 @@ pub struct DropMark { pub end: i_slint_core::lengths::LogicalPoint, } -fn find_insert_position_at_end( +fn insert_position_at_end( target_element_node: &common::ElementRcNode, ) -> Option { target_element_node.with_element_node(|node| { @@ -125,54 +258,132 @@ fn find_insert_position_at_end( }) } +fn drop_target_element_node( + component_instance: &ComponentInstance, + x: f32, + y: f32, + component_type: &str, +) -> (Option, Option) { + let mut self_node = None; + let mut surrounding_node = None; + let tl = component_instance.definition().type_loader(); + for sc in &element_selection::collect_all_element_nodes_covering(x, y, component_instance) { + let Some(en) = sc.as_element_node() else { + continue; + }; + + if en.with_element_node(preview::is_element_node_ignored) { + continue; + } + + let (path, _) = en.path_and_offset(); + let Some(doc) = tl.get_document(&path) else { + continue; + }; + if let Some(element_type) = en.with_element_node(|node| { + util::lookup_current_element_type((node.clone()).into(), &doc.local_registry) + }) { + if en.layout_kind() == ui::LayoutKind::None + && element_type.accepts_child_element(component_type, &doc.local_registry).is_err() + { + break; + } + } + + if !element_selection::is_same_file_as_root_node(component_instance, &en) { + continue; + } + + if self_node.is_some() { + surrounding_node = Some(en); + break; + } else { + self_node = Some(en); + } + } + (self_node, surrounding_node) +} + +fn extract_element(node: SyntaxNode) -> Option { + match node.kind() { + SyntaxKind::Element => Some(node.into()), + SyntaxKind::SubElement => extract_element(node.child_node(SyntaxKind::Element)?), + SyntaxKind::ConditionalElement | SyntaxKind::RepeatedElement => { + extract_element(node.child_node(SyntaxKind::SubElement)?) + } + _ => None, + } +} + +fn examine_target_element_node( + context: &str, + component_instance: &ComponentInstance, + x: f32, + y: f32, + target_element_node: &common::ElementRcNode, +) { + let geometry = target_element_node.geometry_at(component_instance, x, y); + eprintln!( + "{context} :: {target_element_node:?}<{:?}> [{:?}] @ {x}, {y}", + target_element_node.with_element_node(|n| n.kind()), + target_element_node.layout_kind() + ); + // let mut result = Vec::new(); + if let Some(geometry) = geometry { + eprintln!( + " Element : children count: {}, Geometry: {geometry:?}", + target_element_node.element.borrow().children.len() + ); + for (i, c) in target_element_node.element.borrow().children.iter().enumerate() { + let c = common::ElementRcNode::new(c.clone(), 0).unwrap(); + eprintln!( + " {i}: {c:?}<{:?}>, Geometry: {:?}", + c.with_element_node(|n| n.kind()), + c.geometry_in(component_instance, &geometry) + ); + } + eprintln!( + " Node : children count: {}", + target_element_node.with_element_node(|node| node.children().count()) + ); + target_element_node.with_element_node(|node| { + for (i, c) in node.children().enumerate() { + let element_data = if let Some(c_element) = extract_element(c.clone()) { + format!( + "\n {:?}:{:?}", + c_element.source_file.path(), + c_element.text_range().start() + ) + } else { + String::new() + }; + + eprintln!(" {i}: {:?}<{:?}>{element_data}", c.text_range(), c.kind()); + } + }); + } +} + fn find_drop_location( component_instance: &ComponentInstance, x: f32, y: f32, component_type: &str, ) -> Option { - let target_element_node = { - let mut result = None; - let tl = component_instance.definition().type_loader(); - for sc in &element_selection::collect_all_element_nodes_covering(x, y, component_instance) { - let Some(en) = sc.as_element_node() else { - continue; - }; + let (drop_element_node, drop_surrounding_element_node) = + drop_target_element_node(component_instance, x, y, component_type); - if en.with_element_node(preview::is_element_node_ignored) { - continue; - } + let drop_element_node = drop_element_node?; - let (path, _) = en.path_and_offset(); - let Some(doc) = tl.get_document(&path) else { - continue; - }; - if let Some(element_type) = en.with_element_node(|node| { - util::lookup_current_element_type((node.clone()).into(), &doc.local_registry) - }) { - if en.layout_kind() == ui::LayoutKind::None - && element_type - .accepts_child_element(component_type, &doc.local_registry) - .is_err() - { - break; - } - } + examine_target_element_node("Drop target", component_instance, x, y, &drop_element_node); + if let Some(sn) = &drop_surrounding_element_node { + examine_target_element_node("Surrounding", component_instance, x, y, sn); + } - if !element_selection::is_same_file_as_root_node(component_instance, &en) { - continue; - } - - result = Some(en); - break; - } - result - }?; - - let insert_info = find_insert_position_at_end(&target_element_node)?; + let insert_info = insert_position_at_end(&drop_element_node)?; Some(DropInformation { - target_element_node, + target_element_node: drop_element_node, insert_info, drop_mark: Some(DropMark { start: LogicalPoint::new(x - 10.0, y - 10.0), @@ -221,16 +432,10 @@ pub fn drop_at( let properties = { let mut props = component.default_properties.clone(); - let click_position = - LogicalPoint::from_lengths(LogicalLength::new(x), LogicalLength::new(y)); - if drop_info.target_element_node.layout_kind() == ui::LayoutKind::None && !component.fills_parent { - if let Some(area) = component_instance - .element_positions(&drop_info.target_element_node.element) - .iter() - .find(|p| p.contains(click_position)) + if let Some(area) = drop_info.target_element_node.geometry_at(&component_instance, x, y) { props.push(common::PropertyChange::new("x", format!("{}px", x - area.origin.x))); props.push(common::PropertyChange::new("y", format!("{}px", y - area.origin.y))); @@ -288,3 +493,516 @@ pub fn drop_at( DropData { selection_offset, path }, )) } + +#[cfg(test)] +mod tests { + use i_slint_core::lengths::{LogicalPoint, LogicalRect, LogicalSize}; + + use crate::preview::{drop_location::DropMarkDirection, ui}; + + #[test] + fn test_drop_mark_direction_border_size() { + // The border size starts at 0.0 and grows by 1px from there + // Two borders always fit into the dimension passed into border_size + let mut expected = 0.0; + for i in 0_u16..100 { + let dimension = f32::from(i) / 10.0; + let bs = DropMarkDirection::border_size(dimension); + assert!(bs >= (expected - 0.05)); + assert!(bs <= (expected + 1.05)); + assert!((bs * 2.0) <= dimension); // this makes sure the first bs is 0.0 + expected = bs; + } + // The maximum border size is 4px: + assert!(expected <= 4.05); + } + + #[test] + fn test_drop_mark_direction_no_area() { + let rect = LogicalRect::new(LogicalPoint::new(50.0, 50.0), LogicalSize::new(0.0, 0.0)); + let position = LogicalPoint::new(50.0, 50.0); + assert_eq!( + DropMarkDirection::for_element(&rect, position, ui::LayoutKind::None), + DropMarkDirection::None + ); + assert_eq!( + DropMarkDirection::for_element(&rect, position, ui::LayoutKind::Horizontal), + DropMarkDirection::None + ); + assert_eq!( + DropMarkDirection::for_element(&rect, position, ui::LayoutKind::Vertical), + DropMarkDirection::None + ); + assert_eq!( + DropMarkDirection::for_element(&rect, position, ui::LayoutKind::Grid), + DropMarkDirection::None + ); + + let rect = LogicalRect::new(LogicalPoint::new(50.0, 50.0), LogicalSize::new(100.0, 0.0)); + let position = LogicalPoint::new(50.0, 50.0); + assert_eq!( + DropMarkDirection::for_element(&rect, position, ui::LayoutKind::None), + DropMarkDirection::None + ); + assert_eq!( + DropMarkDirection::for_element(&rect, position, ui::LayoutKind::Horizontal), + DropMarkDirection::None + ); + assert_eq!( + DropMarkDirection::for_element(&rect, position, ui::LayoutKind::Vertical), + DropMarkDirection::None + ); + assert_eq!( + DropMarkDirection::for_element(&rect, position, ui::LayoutKind::Grid), + DropMarkDirection::None + ); + + let rect = LogicalRect::new(LogicalPoint::new(50.0, 50.0), LogicalSize::new(0.0, 100.0)); + let position = LogicalPoint::new(50.0, 50.0); + assert_eq!( + DropMarkDirection::for_element(&rect, position, ui::LayoutKind::None), + DropMarkDirection::None + ); + assert_eq!( + DropMarkDirection::for_element(&rect, position, ui::LayoutKind::Horizontal), + DropMarkDirection::None + ); + assert_eq!( + DropMarkDirection::for_element(&rect, position, ui::LayoutKind::Vertical), + DropMarkDirection::None + ); + assert_eq!( + DropMarkDirection::for_element(&rect, position, ui::LayoutKind::Grid), + DropMarkDirection::None + ); + } + + #[test] + fn test_drop_mark_direction_outside_position() { + let rect = LogicalRect::new(LogicalPoint::new(50.0, 50.0), LogicalSize::new(50.0, 50.0)); + let position = LogicalPoint::new(45.0, 75.0); + assert_eq!( + DropMarkDirection::for_element(&rect, position, ui::LayoutKind::None), + DropMarkDirection::None + ); + assert_eq!( + DropMarkDirection::for_element(&rect, position, ui::LayoutKind::Horizontal), + DropMarkDirection::None + ); + assert_eq!( + DropMarkDirection::for_element(&rect, position, ui::LayoutKind::Vertical), + DropMarkDirection::None + ); + assert_eq!( + DropMarkDirection::for_element(&rect, position, ui::LayoutKind::Grid), + DropMarkDirection::None + ); + + let position = LogicalPoint::new(105.0, 75.0); + assert_eq!( + DropMarkDirection::for_element(&rect, position, ui::LayoutKind::None), + DropMarkDirection::None + ); + assert_eq!( + DropMarkDirection::for_element(&rect, position, ui::LayoutKind::Horizontal), + DropMarkDirection::None + ); + assert_eq!( + DropMarkDirection::for_element(&rect, position, ui::LayoutKind::Vertical), + DropMarkDirection::None + ); + assert_eq!( + DropMarkDirection::for_element(&rect, position, ui::LayoutKind::Grid), + DropMarkDirection::None + ); + + let position = LogicalPoint::new(75.0, 45.0); + assert_eq!( + DropMarkDirection::for_element(&rect, position, ui::LayoutKind::None), + DropMarkDirection::None + ); + assert_eq!( + DropMarkDirection::for_element(&rect, position, ui::LayoutKind::Horizontal), + DropMarkDirection::None + ); + assert_eq!( + DropMarkDirection::for_element(&rect, position, ui::LayoutKind::Vertical), + DropMarkDirection::None + ); + assert_eq!( + DropMarkDirection::for_element(&rect, position, ui::LayoutKind::Grid), + DropMarkDirection::None + ); + + let position = LogicalPoint::new(75.0, 105.0); + assert_eq!( + DropMarkDirection::for_element(&rect, position, ui::LayoutKind::None), + DropMarkDirection::None + ); + assert_eq!( + DropMarkDirection::for_element(&rect, position, ui::LayoutKind::Horizontal), + DropMarkDirection::None + ); + assert_eq!( + DropMarkDirection::for_element(&rect, position, ui::LayoutKind::Vertical), + DropMarkDirection::None + ); + assert_eq!( + DropMarkDirection::for_element(&rect, position, ui::LayoutKind::Grid), + DropMarkDirection::None + ); + } + + #[test] + fn test_drop_mark_direction_valid_position() { + for width in 1_u16..50 { + for height in 1_u16..50 { + let width = f32::from(width); + let height = f32::from(height); + + let rect = LogicalRect::new( + LogicalPoint::new(50.0, 50.0), + LogicalSize::new(width, height), + ); + let bs_h = DropMarkDirection::border_size(rect.size.width); + let bs_v = DropMarkDirection::border_size(rect.size.height); + eprintln!("width: {width}, height: {height} => border-h: {bs_h}, border-v: {bs_v}"); + + // Center: Drop into self, no drop mark ever: + let pos = + LogicalPoint::new(50.0 + (width / 2.0), 50.0 + (height / 2.0)); + eprintln!(" Testing: {pos:?}"); + + assert_eq!( + DropMarkDirection::for_element( + &rect, + pos, + ui::LayoutKind::None + ), + DropMarkDirection::None + ); + assert_eq!( + DropMarkDirection::for_element( + &rect, + pos, + ui::LayoutKind::Horizontal + ), + DropMarkDirection::None + ); + assert_eq!( + DropMarkDirection::for_element( + &rect, + pos, + ui::LayoutKind::Vertical + ), + DropMarkDirection::None + ); + assert_eq!( + DropMarkDirection::for_element( + &rect, + pos, + ui::LayoutKind::Grid + ), + DropMarkDirection::None + ); + + // N-side (in border): + let pos = LogicalPoint::new(50.0 + (width / 2.0), 49.0 + bs_v); + eprintln!(" Testing: {pos:?}"); + + assert_eq!( + DropMarkDirection::for_element( + &rect, + pos, + ui::LayoutKind::None + ), + DropMarkDirection::None + ); + assert_eq!( + DropMarkDirection::for_element( + &rect, + pos, + ui::LayoutKind::Horizontal + ), + DropMarkDirection::None + ); + assert_eq!( + DropMarkDirection::for_element( + &rect, + pos, + ui::LayoutKind::Vertical + ), + if bs_h > 0.9 && bs_v > 0.9 { DropMarkDirection::N } else { DropMarkDirection::None } + ); + assert_eq!( + DropMarkDirection::for_element( + &rect, + pos, + ui::LayoutKind::Grid + ), + if bs_h > 0.9 && bs_v > 0.9 { DropMarkDirection::N } else { DropMarkDirection::None } + ); + + // N-side (outside border): + let pos = LogicalPoint::new(50.0 + (width / 2.0), 50.0 + bs_v); + eprintln!(" Testing: {pos:?}"); + + assert_eq!( + DropMarkDirection::for_element( + &rect, + pos, + ui::LayoutKind::None + ), + DropMarkDirection::None + ); + assert_eq!( + DropMarkDirection::for_element( + &rect, + pos, + ui::LayoutKind::Horizontal + ), + DropMarkDirection::None + ); + assert_eq!( + DropMarkDirection::for_element( + &rect, + pos, + ui::LayoutKind::Vertical + ), + DropMarkDirection::None + ); + assert_eq!( + DropMarkDirection::for_element( + &rect, + pos, + ui::LayoutKind::Grid + ), + DropMarkDirection::None + ); + + // E-side (inside border): + let pos = LogicalPoint::new(50.0 + width - bs_h, 50.0 + (height / 2.0)); + eprintln!(" Testing: {pos:?}"); + + assert_eq!( + DropMarkDirection::for_element( + &rect, + pos, + ui::LayoutKind::None + ), + DropMarkDirection::None + ); + assert_eq!( + DropMarkDirection::for_element( + &rect, + pos, + ui::LayoutKind::Horizontal + ), + if bs_h > 0.9 && bs_v > 0.9 { DropMarkDirection::E } else { DropMarkDirection::None } + ); + assert_eq!( + DropMarkDirection::for_element( + &rect, + pos, + ui::LayoutKind::Vertical + ), + DropMarkDirection::None + ); + assert_eq!( + DropMarkDirection::for_element( + &rect, + pos, + ui::LayoutKind::Grid + ), + if bs_h > 0.9 && bs_v > 0.9 { DropMarkDirection::E } else { DropMarkDirection::None } + ); + + // E-side (outside border): + let pos = LogicalPoint::new(49.0 + width - bs_h, 50.0 + (height / 2.0)); + eprintln!(" Testing: {pos:?}"); + + assert_eq!( + DropMarkDirection::for_element( + &rect, + pos, + ui::LayoutKind::None + ), + DropMarkDirection::None + ); + assert_eq!( + DropMarkDirection::for_element( + &rect, + pos, + ui::LayoutKind::Horizontal + ), + DropMarkDirection::None + ); + assert_eq!( + DropMarkDirection::for_element( + &rect, + pos, + ui::LayoutKind::Vertical + ), + DropMarkDirection::None + ); + assert_eq!( + DropMarkDirection::for_element( + &rect, + pos, + ui::LayoutKind::Grid + ), + DropMarkDirection::None + ); + + // S-side (in border): + let pos = LogicalPoint::new(50.0 + (width / 2.0), 50.0 + height - bs_v); + eprintln!(" Testing: {pos:?}"); + + assert_eq!( + DropMarkDirection::for_element( + &rect, + pos, + ui::LayoutKind::None + ), + DropMarkDirection::None + ); + assert_eq!( + DropMarkDirection::for_element( + &rect, + pos, + ui::LayoutKind::Horizontal + ), + DropMarkDirection::None + ); + assert_eq!( + DropMarkDirection::for_element( + &rect, + pos, + ui::LayoutKind::Vertical + ), + if bs_h > 0.9 && bs_v > 0.9 { DropMarkDirection::S } else { DropMarkDirection::None } + ); + assert_eq!( + DropMarkDirection::for_element( + &rect, + pos, + ui::LayoutKind::Grid + ), + if bs_h > 0.9 && bs_v > 0.9 { DropMarkDirection::S } else { DropMarkDirection::None } + ); + + // S-side (outside border): + let pos = LogicalPoint::new(50.0 + (width / 2.0), 49.0 + height - bs_v); + eprintln!(" Testing: {pos:?}"); + + assert_eq!( + DropMarkDirection::for_element( + &rect, + pos, + ui::LayoutKind::None + ), + DropMarkDirection::None + ); + assert_eq!( + DropMarkDirection::for_element( + &rect, + pos, + ui::LayoutKind::Horizontal + ), + DropMarkDirection::None + ); + assert_eq!( + DropMarkDirection::for_element( + &rect, + pos, + ui::LayoutKind::Vertical + ), + DropMarkDirection::None + ); + assert_eq!( + DropMarkDirection::for_element( + &rect, + pos, + ui::LayoutKind::Grid + ), + DropMarkDirection::None + ); + + // W-side (inside border): + let pos = LogicalPoint::new(49.0 + bs_h, 50.0 + (height / 2.0)); + eprintln!(" Testing: {pos:?}"); + + assert_eq!( + DropMarkDirection::for_element( + &rect, + pos, + ui::LayoutKind::None + ), + DropMarkDirection::None + ); + assert_eq!( + DropMarkDirection::for_element( + &rect, + pos, + ui::LayoutKind::Horizontal + ), + if bs_h > 0.9 && bs_v > 0.9 { DropMarkDirection::W } else { DropMarkDirection::None } + ); + assert_eq!( + DropMarkDirection::for_element( + &rect, + pos, + ui::LayoutKind::Vertical + ), + DropMarkDirection::None + ); + assert_eq!( + DropMarkDirection::for_element( + &rect, + pos, + ui::LayoutKind::Grid + ), + if bs_h > 0.9 && bs_v > 0.9 { DropMarkDirection::W } else { DropMarkDirection::None } + ); + + // W-side (outside border): + let pos = LogicalPoint::new(50.0 + bs_h, 50.0 + (height / 2.0)); + eprintln!(" Testing: {pos:?}"); + + assert_eq!( + DropMarkDirection::for_element( + &rect, + pos, + ui::LayoutKind::None + ), + DropMarkDirection::None + ); + assert_eq!( + DropMarkDirection::for_element( + &rect, + pos, + ui::LayoutKind::Horizontal + ), + DropMarkDirection::None + ); + assert_eq!( + DropMarkDirection::for_element( + &rect, + pos, + ui::LayoutKind::Vertical + ), + DropMarkDirection::None + ); + assert_eq!( + DropMarkDirection::for_element( + &rect, + pos, + ui::LayoutKind::Grid + ), + DropMarkDirection::None + ); + + } + } + } +} diff --git a/tools/lsp/preview/ext.rs b/tools/lsp/preview/ext.rs index e111d7d3d..196f7a6a8 100644 --- a/tools/lsp/preview/ext.rs +++ b/tools/lsp/preview/ext.rs @@ -2,12 +2,36 @@ // SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-1.2 OR LicenseRef-Slint-commercial use i_slint_compiler::layout; +use i_slint_core::lengths::{LogicalPoint, LogicalRect}; use crate::common; use crate::preview::ui; +use slint_interpreter::ComponentInstance; + pub trait ElementRcNodeExt { fn layout_kind(&self) -> crate::preview::ui::LayoutKind; + + /// Find all geometries for the given `ElementRcNode` + fn geometries( + &self, + component_instance: &ComponentInstance, + ) -> Vec; + + /// Find the first geometry of `ElementRcNode` that includes the point `x`, `y` + fn geometry_at( + &self, + component_instance: &ComponentInstance, + x: f32, + y: f32, + ) -> Option; + + /// Find the first geometry of ElementRcNode in `rect` + fn geometry_in( + &self, + component_instance: &ComponentInstance, + rect: &LogicalRect, + ) -> Vec; } impl ElementRcNodeExt for common::ElementRcNode { @@ -25,4 +49,33 @@ impl ElementRcNodeExt for common::ElementRcNode { _ => ui::LayoutKind::None, }) } + + fn geometries( + &self, + component_instance: &ComponentInstance, + ) -> Vec { + component_instance.element_positions(&self.as_element()) + } + + fn geometry_at( + &self, + component_instance: &ComponentInstance, + x: f32, + y: f32, + ) -> Option { + let click_position = LogicalPoint::new(x, y); + self.geometries(component_instance).iter().find(|g| g.contains(click_position)).cloned() + } + + fn geometry_in( + &self, + component_instance: &ComponentInstance, + rect: &LogicalRect, + ) -> Vec { + self.geometries(component_instance) + .iter() + .filter(|g| rect.contains_rect(g)) + .cloned() + .collect() + } }