live preview: Prefer LogicalPoint over providing x and y

This commit is contained in:
Tobias Hunger 2024-03-29 15:08:12 +01:00 committed by Tobias Hunger
parent 69b8e8d57c
commit 9b2a7fc694
3 changed files with 276 additions and 367 deletions

View file

@ -2,7 +2,7 @@
// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-1.2 OR LicenseRef-Slint-commercial // SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-1.2 OR LicenseRef-Slint-commercial
use i_slint_compiler::parser::{SyntaxKind, SyntaxNode}; use i_slint_compiler::parser::{SyntaxKind, SyntaxNode};
use i_slint_core::lengths::{LogicalPoint, LogicalRect}; use i_slint_core::lengths::{LogicalPoint, LogicalRect, LogicalSize};
use slint_interpreter::ComponentInstance; use slint_interpreter::ComponentInstance;
use crate::common; use crate::common;
@ -75,16 +75,15 @@ impl DropMarkDirection {
} }
fn border_size(dimension: f32) -> f32 { fn border_size(dimension: f32) -> f32 {
match dimension { let bs = (dimension / 4.0).floor();
d if d >= 16.0 => 4.0, if bs > 8.0 {
d if d >= 14.0 => 3.0, 8.0
d if d >= 12.0 => 2.0, } else {
d if d >= 6.0 => 1.0, bs
_ => 0.0,
} }
} }
// Does this hit the center of an element that accepty drops? // Does this hit the center area of an element that accept drops?
fn element_accepts( fn element_accepts(
element_geometry: &LogicalRect, element_geometry: &LogicalRect,
position: LogicalPoint, position: LogicalPoint,
@ -171,7 +170,6 @@ impl DropMarkDirection {
let ne_half = y <= ascend * x; let ne_half = y <= ascend * x;
let nw_half = y <= (x * -ascend) + element_geometry.size.height; 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) { match (ne_half, nw_half) {
(false, false) => DropMarkDirection::S, (false, false) => DropMarkDirection::S,
@ -260,14 +258,13 @@ fn insert_position_at_end(
fn drop_target_element_node( fn drop_target_element_node(
component_instance: &ComponentInstance, component_instance: &ComponentInstance,
x: f32, position: LogicalPoint,
y: f32,
component_type: &str, component_type: &str,
) -> (Option<common::ElementRcNode>, Option<common::ElementRcNode>) { ) -> (Option<common::ElementRcNode>, Option<common::ElementRcNode>) {
let mut self_node = None; let mut self_node = None;
let mut surrounding_node = None; let mut surrounding_node = None;
let tl = component_instance.definition().type_loader(); let tl = component_instance.definition().type_loader();
for sc in &element_selection::collect_all_element_nodes_covering(x, y, component_instance) { for sc in &element_selection::collect_all_element_nodes_covering(position, component_instance) {
let Some(en) = sc.as_element_node() else { let Some(en) = sc.as_element_node() else {
continue; continue;
}; };
@ -318,34 +315,15 @@ fn extract_element(node: SyntaxNode) -> Option<i_slint_compiler::parser::syntax_
fn examine_target_element_node( fn examine_target_element_node(
context: &str, context: &str,
component_instance: &ComponentInstance, component_instance: &ComponentInstance,
x: f32, position: LogicalPoint,
y: f32,
target_element_node: &common::ElementRcNode, target_element_node: &common::ElementRcNode,
) { ) {
let geometry = target_element_node.geometry_at(component_instance, x, y); let geometry = target_element_node.geometry_at(component_instance, position);
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(); // let mut result = Vec::new();
if let Some(geometry) = geometry { 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() { for (i, c) in target_element_node.element.borrow().children.iter().enumerate() {
let c = common::ElementRcNode::new(c.clone(), 0).unwrap(); 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| { target_element_node.with_element_node(|node| {
for (i, c) in node.children().enumerate() { for (i, c) in node.children().enumerate() {
let element_data = if let Some(c_element) = extract_element(c.clone()) { let element_data = if let Some(c_element) = extract_element(c.clone()) {
@ -357,46 +335,90 @@ fn examine_target_element_node(
} else { } else {
String::new() String::new()
}; };
eprintln!(" {i}: {:?}<{:?}>{element_data}", c.text_range(), c.kind());
} }
}); });
} }
} }
fn drop_into_layout(
component_instance: &ComponentInstance,
element_node: common::ElementRcNode,
insert_position: Option<(DropMarkDirection, common::ElementRcNode)>,
position: LogicalPoint,
) -> Option<DropInformation> {
let geometry = element_node.geometry_at(component_instance, position)?;
let insert_info = insert_position_at_end(&element_node)?;
Some(DropInformation {
target_element_node: element_node,
insert_info,
drop_mark: Some(DropMark {
start: geometry.origin + LogicalSize::new(0.0, geometry.size.height - 1.0),
end: geometry.origin + geometry.size,
}),
})
}
fn drop_into_element(
component_instance: &ComponentInstance,
element_node: common::ElementRcNode,
surround_node: Option<common::ElementRcNode>,
position: LogicalPoint,
) -> Option<DropInformation> {
let geometry = element_node.geometry_at(component_instance, position)?;
let drop_mark_direction = DropMarkDirection::for_element(
&geometry,
position,
surround_node.as_ref().map(|n| n.layout_kind()).unwrap_or(ui::LayoutKind::None),
);
if drop_mark_direction == DropMarkDirection::None {
let insert_info = insert_position_at_end(&element_node)?;
Some(DropInformation { target_element_node: element_node, insert_info, drop_mark: None })
} else {
drop_into_layout(
component_instance,
surround_node.unwrap(),
Some((drop_mark_direction, element_node)),
position,
)
}
}
fn find_drop_location( fn find_drop_location(
component_instance: &ComponentInstance, component_instance: &ComponentInstance,
x: f32, position: LogicalPoint,
y: f32,
component_type: &str, component_type: &str,
) -> Option<DropInformation> { ) -> Option<DropInformation> {
let (drop_element_node, drop_surrounding_element_node) = let (drop_element_node, drop_surrounding_element_node) =
drop_target_element_node(component_instance, x, y, component_type); drop_target_element_node(component_instance, position, component_type);
let drop_element_node = drop_element_node?; let drop_element_node = drop_element_node?;
examine_target_element_node("Drop target", component_instance, x, y, &drop_element_node); examine_target_element_node("Drop target", component_instance, position, &drop_element_node);
if let Some(sn) = &drop_surrounding_element_node { if let Some(sn) = &drop_surrounding_element_node {
examine_target_element_node("Surrounding", component_instance, x, y, sn); examine_target_element_node("Surrounding", component_instance, position, sn);
} }
let insert_info = insert_position_at_end(&drop_element_node)?; if drop_element_node.layout_kind() != ui::LayoutKind::None {
drop_into_layout(component_instance, drop_element_node, None, position)
Some(DropInformation { } else {
target_element_node: drop_element_node, drop_into_element(
insert_info, component_instance,
drop_mark: Some(DropMark { drop_element_node,
start: LogicalPoint::new(x - 10.0, y - 10.0), drop_surrounding_element_node,
end: LogicalPoint::new(x + 10.0, y + 10.0), position,
}), )
}) }
} }
/// Find the Element to insert into. None means we can not insert at this point. /// Find the Element to insert into. None means we can not insert at this point.
pub fn can_drop_at(x: f32, y: f32, component: &common::ComponentInformation) -> bool { pub fn can_drop_at(x: f32, y: f32, component: &common::ComponentInformation) -> bool {
let component_type = component.name.to_string(); let component_type = component.name.to_string();
if let Some(dm) = let position = LogicalPoint::new(x, y);
&super::component_instance().and_then(|ci| find_drop_location(&ci, x, y, &component_type)) if let Some(dm) = &super::component_instance()
.and_then(|ci| find_drop_location(&ci, position, &component_type))
{ {
super::set_drop_mark(&dm.drop_mark); super::set_drop_mark(&dm.drop_mark);
true true
@ -424,10 +446,11 @@ pub fn drop_at(
y: f32, y: f32,
component: &common::ComponentInformation, component: &common::ComponentInformation,
) -> Option<(lsp_types::WorkspaceEdit, DropData)> { ) -> Option<(lsp_types::WorkspaceEdit, DropData)> {
let position = LogicalPoint::new(x, y);
let component_type = &component.name; let component_type = &component.name;
let component_instance = preview::component_instance()?; let component_instance = preview::component_instance()?;
let tl = component_instance.definition().type_loader(); let tl = component_instance.definition().type_loader();
let drop_info = find_drop_location(&component_instance, x, y, component_type)?; let drop_info = find_drop_location(&component_instance, position, component_type)?;
let properties = { let properties = {
let mut props = component.default_properties.clone(); let mut props = component.default_properties.clone();
@ -435,7 +458,8 @@ pub fn drop_at(
if drop_info.target_element_node.layout_kind() == ui::LayoutKind::None if drop_info.target_element_node.layout_kind() == ui::LayoutKind::None
&& !component.fills_parent && !component.fills_parent
{ {
if let Some(area) = drop_info.target_element_node.geometry_at(&component_instance, x, y) if let Some(area) =
drop_info.target_element_node.geometry_at(&component_instance, position)
{ {
props.push(common::PropertyChange::new("x", format!("{}px", x - area.origin.x))); props.push(common::PropertyChange::new("x", format!("{}px", x - area.origin.x)));
props.push(common::PropertyChange::new("y", format!("{}px", y - area.origin.y))); props.push(common::PropertyChange::new("y", format!("{}px", y - area.origin.y)));
@ -508,13 +532,15 @@ mod tests {
for i in 0_u16..100 { for i in 0_u16..100 {
let dimension = f32::from(i) / 10.0; let dimension = f32::from(i) / 10.0;
let bs = DropMarkDirection::border_size(dimension); let bs = DropMarkDirection::border_size(dimension);
assert!(bs >= (expected - 0.05)); assert!(
assert!(bs <= (expected + 1.05)); bs >= (expected - 0.05) && bs < (expected + 0.05)
assert!((bs * 2.0) <= dimension); // this makes sure the first bs is 0.0 || (bs >= (expected + 0.95) && bs < (expected + 1.05))
expected = bs; );
assert!((bs * 3.0) <= dimension); // this makes sure the first bs is 0.0
expected = bs.round();
} }
// The maximum border size is 4px: // The maximum border size is 4px:
assert!(expected <= 4.05); assert!(expected <= 8.05);
} }
#[test] #[test]
@ -666,342 +692,218 @@ mod tests {
); );
let bs_h = DropMarkDirection::border_size(rect.size.width); let bs_h = DropMarkDirection::border_size(rect.size.width);
let bs_v = DropMarkDirection::border_size(rect.size.height); 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: // Center: Drop into self, no drop mark ever:
let pos = let pos = LogicalPoint::new(50.0 + (width / 2.0), 50.0 + (height / 2.0));
LogicalPoint::new(50.0 + (width / 2.0), 50.0 + (height / 2.0));
eprintln!(" Testing: {pos:?}");
assert_eq!( assert_eq!(
DropMarkDirection::for_element( DropMarkDirection::for_element(&rect, pos, ui::LayoutKind::None),
&rect,
pos,
ui::LayoutKind::None
),
DropMarkDirection::None DropMarkDirection::None
); );
assert_eq!( assert_eq!(
DropMarkDirection::for_element( DropMarkDirection::for_element(&rect, pos, ui::LayoutKind::Horizontal),
&rect,
pos,
ui::LayoutKind::Horizontal
),
DropMarkDirection::None DropMarkDirection::None
); );
assert_eq!( assert_eq!(
DropMarkDirection::for_element( DropMarkDirection::for_element(&rect, pos, ui::LayoutKind::Vertical),
&rect,
pos,
ui::LayoutKind::Vertical
),
DropMarkDirection::None DropMarkDirection::None
); );
assert_eq!( assert_eq!(
DropMarkDirection::for_element( DropMarkDirection::for_element(&rect, pos, ui::LayoutKind::Grid),
&rect,
pos,
ui::LayoutKind::Grid
),
DropMarkDirection::None DropMarkDirection::None
); );
// N-side (in border): // N-side (in border):
let pos = LogicalPoint::new(50.0 + (width / 2.0), 49.0 + bs_v); let pos = LogicalPoint::new(50.0 + (width / 2.0), 49.0 + bs_v);
eprintln!(" Testing: {pos:?}");
assert_eq!( assert_eq!(
DropMarkDirection::for_element( DropMarkDirection::for_element(&rect, pos, ui::LayoutKind::None),
&rect,
pos,
ui::LayoutKind::None
),
DropMarkDirection::None DropMarkDirection::None
); );
assert_eq!( assert_eq!(
DropMarkDirection::for_element( DropMarkDirection::for_element(&rect, pos, ui::LayoutKind::Horizontal),
&rect,
pos,
ui::LayoutKind::Horizontal
),
DropMarkDirection::None DropMarkDirection::None
); );
assert_eq!( assert_eq!(
DropMarkDirection::for_element( DropMarkDirection::for_element(&rect, pos, ui::LayoutKind::Vertical),
&rect, if bs_h > 0.9 && bs_v > 0.9 {
pos, DropMarkDirection::N
ui::LayoutKind::Vertical } else {
), DropMarkDirection::None
if bs_h > 0.9 && bs_v > 0.9 { DropMarkDirection::N } else { DropMarkDirection::None } }
); );
assert_eq!( assert_eq!(
DropMarkDirection::for_element( DropMarkDirection::for_element(&rect, pos, ui::LayoutKind::Grid),
&rect, if bs_h > 0.9 && bs_v > 0.9 {
pos, DropMarkDirection::N
ui::LayoutKind::Grid } else {
), DropMarkDirection::None
if bs_h > 0.9 && bs_v > 0.9 { DropMarkDirection::N } else { DropMarkDirection::None } }
); );
// N-side (outside border): // N-side (outside border):
let pos = LogicalPoint::new(50.0 + (width / 2.0), 50.0 + bs_v); let pos = LogicalPoint::new(50.0 + (width / 2.0), 50.0 + bs_v);
eprintln!(" Testing: {pos:?}");
assert_eq!( assert_eq!(
DropMarkDirection::for_element( DropMarkDirection::for_element(&rect, pos, ui::LayoutKind::None),
&rect,
pos,
ui::LayoutKind::None
),
DropMarkDirection::None DropMarkDirection::None
); );
assert_eq!( assert_eq!(
DropMarkDirection::for_element( DropMarkDirection::for_element(&rect, pos, ui::LayoutKind::Horizontal),
&rect,
pos,
ui::LayoutKind::Horizontal
),
DropMarkDirection::None DropMarkDirection::None
); );
assert_eq!( assert_eq!(
DropMarkDirection::for_element( DropMarkDirection::for_element(&rect, pos, ui::LayoutKind::Vertical),
&rect,
pos,
ui::LayoutKind::Vertical
),
DropMarkDirection::None DropMarkDirection::None
); );
assert_eq!( assert_eq!(
DropMarkDirection::for_element( DropMarkDirection::for_element(&rect, pos, ui::LayoutKind::Grid),
&rect,
pos,
ui::LayoutKind::Grid
),
DropMarkDirection::None DropMarkDirection::None
); );
// E-side (inside border): // E-side (inside border):
let pos = LogicalPoint::new(50.0 + width - bs_h, 50.0 + (height / 2.0)); let pos = LogicalPoint::new(50.0 + width - bs_h, 50.0 + (height / 2.0));
eprintln!(" Testing: {pos:?}");
assert_eq!( assert_eq!(
DropMarkDirection::for_element( DropMarkDirection::for_element(&rect, pos, ui::LayoutKind::None),
&rect,
pos,
ui::LayoutKind::None
),
DropMarkDirection::None DropMarkDirection::None
); );
assert_eq!( assert_eq!(
DropMarkDirection::for_element( DropMarkDirection::for_element(&rect, pos, ui::LayoutKind::Horizontal),
&rect, if bs_h > 0.9 && bs_v > 0.9 {
pos, DropMarkDirection::E
ui::LayoutKind::Horizontal } else {
), DropMarkDirection::None
if bs_h > 0.9 && bs_v > 0.9 { DropMarkDirection::E } else { DropMarkDirection::None } }
); );
assert_eq!( assert_eq!(
DropMarkDirection::for_element( DropMarkDirection::for_element(&rect, pos, ui::LayoutKind::Vertical),
&rect,
pos,
ui::LayoutKind::Vertical
),
DropMarkDirection::None DropMarkDirection::None
); );
assert_eq!( assert_eq!(
DropMarkDirection::for_element( DropMarkDirection::for_element(&rect, pos, ui::LayoutKind::Grid),
&rect, if bs_h > 0.9 && bs_v > 0.9 {
pos, DropMarkDirection::E
ui::LayoutKind::Grid } else {
), DropMarkDirection::None
if bs_h > 0.9 && bs_v > 0.9 { DropMarkDirection::E } else { DropMarkDirection::None } }
); );
// E-side (outside border): // E-side (outside border):
let pos = LogicalPoint::new(49.0 + width - bs_h, 50.0 + (height / 2.0)); let pos = LogicalPoint::new(49.0 + width - bs_h, 50.0 + (height / 2.0));
eprintln!(" Testing: {pos:?}");
assert_eq!( assert_eq!(
DropMarkDirection::for_element( DropMarkDirection::for_element(&rect, pos, ui::LayoutKind::None),
&rect,
pos,
ui::LayoutKind::None
),
DropMarkDirection::None DropMarkDirection::None
); );
assert_eq!( assert_eq!(
DropMarkDirection::for_element( DropMarkDirection::for_element(&rect, pos, ui::LayoutKind::Horizontal),
&rect,
pos,
ui::LayoutKind::Horizontal
),
DropMarkDirection::None DropMarkDirection::None
); );
assert_eq!( assert_eq!(
DropMarkDirection::for_element( DropMarkDirection::for_element(&rect, pos, ui::LayoutKind::Vertical),
&rect,
pos,
ui::LayoutKind::Vertical
),
DropMarkDirection::None DropMarkDirection::None
); );
assert_eq!( assert_eq!(
DropMarkDirection::for_element( DropMarkDirection::for_element(&rect, pos, ui::LayoutKind::Grid),
&rect,
pos,
ui::LayoutKind::Grid
),
DropMarkDirection::None DropMarkDirection::None
); );
// S-side (in border): // S-side (in border):
let pos = LogicalPoint::new(50.0 + (width / 2.0), 50.0 + height - bs_v); let pos = LogicalPoint::new(50.0 + (width / 2.0), 50.0 + height - bs_v);
eprintln!(" Testing: {pos:?}");
assert_eq!( assert_eq!(
DropMarkDirection::for_element( DropMarkDirection::for_element(&rect, pos, ui::LayoutKind::None),
&rect,
pos,
ui::LayoutKind::None
),
DropMarkDirection::None DropMarkDirection::None
); );
assert_eq!( assert_eq!(
DropMarkDirection::for_element( DropMarkDirection::for_element(&rect, pos, ui::LayoutKind::Horizontal),
&rect,
pos,
ui::LayoutKind::Horizontal
),
DropMarkDirection::None DropMarkDirection::None
); );
assert_eq!( assert_eq!(
DropMarkDirection::for_element( DropMarkDirection::for_element(&rect, pos, ui::LayoutKind::Vertical),
&rect, if bs_h > 0.9 && bs_v > 0.9 {
pos, DropMarkDirection::S
ui::LayoutKind::Vertical } else {
), DropMarkDirection::None
if bs_h > 0.9 && bs_v > 0.9 { DropMarkDirection::S } else { DropMarkDirection::None } }
); );
assert_eq!( assert_eq!(
DropMarkDirection::for_element( DropMarkDirection::for_element(&rect, pos, ui::LayoutKind::Grid),
&rect, if bs_h > 0.9 && bs_v > 0.9 {
pos, DropMarkDirection::S
ui::LayoutKind::Grid } else {
), DropMarkDirection::None
if bs_h > 0.9 && bs_v > 0.9 { DropMarkDirection::S } else { DropMarkDirection::None } }
); );
// S-side (outside border): // S-side (outside border):
let pos = LogicalPoint::new(50.0 + (width / 2.0), 49.0 + height - bs_v); let pos = LogicalPoint::new(50.0 + (width / 2.0), 49.0 + height - bs_v);
eprintln!(" Testing: {pos:?}");
assert_eq!( assert_eq!(
DropMarkDirection::for_element( DropMarkDirection::for_element(&rect, pos, ui::LayoutKind::None),
&rect,
pos,
ui::LayoutKind::None
),
DropMarkDirection::None DropMarkDirection::None
); );
assert_eq!( assert_eq!(
DropMarkDirection::for_element( DropMarkDirection::for_element(&rect, pos, ui::LayoutKind::Horizontal),
&rect,
pos,
ui::LayoutKind::Horizontal
),
DropMarkDirection::None DropMarkDirection::None
); );
assert_eq!( assert_eq!(
DropMarkDirection::for_element( DropMarkDirection::for_element(&rect, pos, ui::LayoutKind::Vertical),
&rect,
pos,
ui::LayoutKind::Vertical
),
DropMarkDirection::None DropMarkDirection::None
); );
assert_eq!( assert_eq!(
DropMarkDirection::for_element( DropMarkDirection::for_element(&rect, pos, ui::LayoutKind::Grid),
&rect,
pos,
ui::LayoutKind::Grid
),
DropMarkDirection::None DropMarkDirection::None
); );
// W-side (inside border): // W-side (inside border):
let pos = LogicalPoint::new(49.0 + bs_h, 50.0 + (height / 2.0)); let pos = LogicalPoint::new(49.0 + bs_h, 50.0 + (height / 2.0));
eprintln!(" Testing: {pos:?}");
assert_eq!( assert_eq!(
DropMarkDirection::for_element( DropMarkDirection::for_element(&rect, pos, ui::LayoutKind::None),
&rect,
pos,
ui::LayoutKind::None
),
DropMarkDirection::None DropMarkDirection::None
); );
assert_eq!( assert_eq!(
DropMarkDirection::for_element( DropMarkDirection::for_element(&rect, pos, ui::LayoutKind::Horizontal),
&rect, if bs_h > 0.9 && bs_v > 0.9 {
pos, DropMarkDirection::W
ui::LayoutKind::Horizontal } else {
), DropMarkDirection::None
if bs_h > 0.9 && bs_v > 0.9 { DropMarkDirection::W } else { DropMarkDirection::None } }
); );
assert_eq!( assert_eq!(
DropMarkDirection::for_element( DropMarkDirection::for_element(&rect, pos, ui::LayoutKind::Vertical),
&rect,
pos,
ui::LayoutKind::Vertical
),
DropMarkDirection::None DropMarkDirection::None
); );
assert_eq!( assert_eq!(
DropMarkDirection::for_element( DropMarkDirection::for_element(&rect, pos, ui::LayoutKind::Grid),
&rect, if bs_h > 0.9 && bs_v > 0.9 {
pos, DropMarkDirection::W
ui::LayoutKind::Grid } else {
), DropMarkDirection::None
if bs_h > 0.9 && bs_v > 0.9 { DropMarkDirection::W } else { DropMarkDirection::None } }
); );
// W-side (outside border): // W-side (outside border):
let pos = LogicalPoint::new(50.0 + bs_h, 50.0 + (height / 2.0)); let pos = LogicalPoint::new(50.0 + bs_h, 50.0 + (height / 2.0));
eprintln!(" Testing: {pos:?}");
assert_eq!( assert_eq!(
DropMarkDirection::for_element( DropMarkDirection::for_element(&rect, pos, ui::LayoutKind::None),
&rect,
pos,
ui::LayoutKind::None
),
DropMarkDirection::None DropMarkDirection::None
); );
assert_eq!( assert_eq!(
DropMarkDirection::for_element( DropMarkDirection::for_element(&rect, pos, ui::LayoutKind::Horizontal),
&rect,
pos,
ui::LayoutKind::Horizontal
),
DropMarkDirection::None DropMarkDirection::None
); );
assert_eq!( assert_eq!(
DropMarkDirection::for_element( DropMarkDirection::for_element(&rect, pos, ui::LayoutKind::Vertical),
&rect,
pos,
ui::LayoutKind::Vertical
),
DropMarkDirection::None DropMarkDirection::None
); );
assert_eq!( assert_eq!(
DropMarkDirection::for_element( DropMarkDirection::for_element(&rect, pos, ui::LayoutKind::Grid),
&rect,
pos,
ui::LayoutKind::Grid
),
DropMarkDirection::None DropMarkDirection::None
); );
} }
} }
} }

View file

@ -5,7 +5,7 @@ use std::{path::PathBuf, rc::Rc};
use i_slint_compiler::diagnostics::SourceFile; use i_slint_compiler::diagnostics::SourceFile;
use i_slint_compiler::object_tree::{Component, ElementRc}; use i_slint_compiler::object_tree::{Component, ElementRc};
use i_slint_core::lengths::{LogicalLength, LogicalPoint}; use i_slint_core::lengths::LogicalPoint;
use rowan::TextRange; use rowan::TextRange;
use slint_interpreter::ComponentInstance; use slint_interpreter::ComponentInstance;
@ -73,17 +73,11 @@ fn lsp_element_node_position(element: &ElementRcNode) -> Option<(String, lsp_typ
} }
fn element_covers_point( fn element_covers_point(
x: f32, position: LogicalPoint,
y: f32,
component_instance: &ComponentInstance, component_instance: &ComponentInstance,
selected_element: &ElementRc, selected_element: &ElementRc,
) -> bool { ) -> bool {
let click_position = LogicalPoint::from_lengths(LogicalLength::new(x), LogicalLength::new(y)); component_instance.element_positions(selected_element).iter().any(|p| p.contains(position))
component_instance
.element_positions(selected_element)
.iter()
.any(|p| p.contains(click_position))
} }
pub fn unselect_element() { pub fn unselect_element() {
@ -197,8 +191,7 @@ impl std::fmt::Debug for SelectionCandidate {
// Traverse the element tree in reverse render order and collect information on // Traverse the element tree in reverse render order and collect information on
// all elements that "render" at the given x and y coordinates // all elements that "render" at the given x and y coordinates
fn collect_all_element_nodes_covering_impl( fn collect_all_element_nodes_covering_impl(
x: f32, position: LogicalPoint,
y: f32,
component_instance: &ComponentInstance, component_instance: &ComponentInstance,
current_element: &ElementRc, current_element: &ElementRc,
component_stack: &Vec<Rc<Component>>, component_stack: &Vec<Rc<Component>>,
@ -223,8 +216,7 @@ fn collect_all_element_nodes_covering_impl(
for c in ce.borrow().children.iter().rev() { for c in ce.borrow().children.iter().rev() {
collect_all_element_nodes_covering_impl( collect_all_element_nodes_covering_impl(
x, position,
y,
component_instance, component_instance,
c, c,
children_component_stack, children_component_stack,
@ -232,7 +224,7 @@ fn collect_all_element_nodes_covering_impl(
); );
} }
if element_covers_point(x, y, component_instance, &ce) { if element_covers_point(position, component_instance, &ce) {
for (i, _) in ce.borrow().debug.iter().enumerate().rev() { for (i, _) in ce.borrow().debug.iter().enumerate().rev() {
// All nodes have the same geometry // All nodes have the same geometry
let text_range = element_node_source_range(&ce, i); let text_range = element_node_source_range(&ce, i);
@ -247,15 +239,13 @@ fn collect_all_element_nodes_covering_impl(
} }
pub fn collect_all_element_nodes_covering( pub fn collect_all_element_nodes_covering(
x: f32, position: LogicalPoint,
y: f32,
component_instance: &ComponentInstance, component_instance: &ComponentInstance,
) -> Vec<SelectionCandidate> { ) -> Vec<SelectionCandidate> {
let root_element = root_element(component_instance); let root_element = root_element(component_instance);
let mut elements = Vec::new(); let mut elements = Vec::new();
collect_all_element_nodes_covering_impl( collect_all_element_nodes_covering_impl(
x, position,
y,
component_instance, component_instance,
&root_element, &root_element,
&vec![], &vec![],
@ -300,11 +290,10 @@ pub fn is_same_file_as_root_node(
fn select_element_at_impl( fn select_element_at_impl(
component_instance: &ComponentInstance, component_instance: &ComponentInstance,
x: f32, position: LogicalPoint,
y: f32,
enter_component: bool, enter_component: bool,
) -> Option<ElementRcNode> { ) -> Option<ElementRcNode> {
for sc in &collect_all_element_nodes_covering(x, y, component_instance) { for sc in &collect_all_element_nodes_covering(position, component_instance) {
if let Some(en) = filter_nodes_for_selection(component_instance, sc, enter_component) { if let Some(en) = filter_nodes_for_selection(component_instance, sc, enter_component) {
return Some(en); return Some(en);
} }
@ -317,20 +306,22 @@ pub fn select_element_at(x: f32, y: f32, enter_component: bool) {
return; return;
}; };
let position = LogicalPoint::new(x, y);
if let Some(se) = super::selected_element() { if let Some(se) = super::selected_element() {
if let Some(element) = se.as_element() { if let Some(element) = se.as_element() {
if element_covers_point(x, y, &component_instance, &element) { if element_covers_point(position, &component_instance, &element) {
// We clicked on the already selected element: Do nothing! // We clicked on the already selected element: Do nothing!
return; return;
} }
} }
} }
let Some(en) = select_element_at_impl(&component_instance, x, y, enter_component) else { let Some(en) = select_element_at_impl(&component_instance, position, enter_component) else {
return; return;
}; };
select_element_node(&component_instance, &en, Some(LogicalPoint::new(x, y))); select_element_node(&component_instance, &en, Some(position));
} }
pub fn is_element_node_in_layout(element: &ElementRcNode) -> bool { pub fn is_element_node_in_layout(element: &ElementRcNode) -> bool {
@ -381,12 +372,11 @@ fn filter_nodes_for_selection(
pub fn select_element_behind_impl( pub fn select_element_behind_impl(
component_instance: &ComponentInstance, component_instance: &ComponentInstance,
selected_element_node: &ElementRcNode, selected_element_node: &ElementRcNode,
x: f32, position: LogicalPoint,
y: f32,
enter_component: bool, enter_component: bool,
reverse: bool, reverse: bool,
) -> Option<ElementRcNode> { ) -> Option<ElementRcNode> {
let elements = collect_all_element_nodes_covering(x, y, component_instance); let elements = collect_all_element_nodes_covering(position, component_instance);
let current_selection_position = let current_selection_position =
elements.iter().position(|sc| sc.is_selected_element_node(selected_element_node))?; elements.iter().position(|sc| sc.is_selected_element_node(selected_element_node))?;
@ -422,6 +412,7 @@ pub fn select_element_behind(x: f32, y: f32, enter_component: bool, reverse: boo
let Some(component_instance) = super::component_instance() else { let Some(component_instance) = super::component_instance() else {
return; return;
}; };
let position = LogicalPoint::new(x, y);
let Some(selected_element_node) = let Some(selected_element_node) =
super::selected_element().and_then(|sel| sel.as_element_node()) super::selected_element().and_then(|sel| sel.as_element_node())
else { else {
@ -431,15 +422,14 @@ pub fn select_element_behind(x: f32, y: f32, enter_component: bool, reverse: boo
let Some(en) = select_element_behind_impl( let Some(en) = select_element_behind_impl(
&component_instance, &component_instance,
&selected_element_node, &selected_element_node,
x, position,
y,
enter_component, enter_component,
reverse, reverse,
) else { ) else {
return; return;
}; };
select_element_node(&component_instance, &en, Some(LogicalPoint::new(x, y))); select_element_node(&component_instance, &en, Some(position));
} }
// Called from UI thread! // Called from UI thread!
@ -459,6 +449,7 @@ pub fn reselect_element() {
mod tests { mod tests {
use std::path::PathBuf; use std::path::PathBuf;
use i_slint_core::lengths::LogicalPoint;
use slint_interpreter::ComponentInstance; use slint_interpreter::ComponentInstance;
fn demo_app() -> ComponentInstance { fn demo_app() -> ComponentInstance {
@ -494,8 +485,10 @@ export component Entry inherits Main { /* @lsp:ignore-node */ } // 401
fn test_find_covering_elements() { fn test_find_covering_elements() {
let component_instance = demo_app(); let component_instance = demo_app();
let mut covers_center = let mut covers_center = super::collect_all_element_nodes_covering(
super::collect_all_element_nodes_covering(100.0, 100.0, &component_instance); LogicalPoint::new(100.0, 100.0),
&component_instance,
);
// Remove the "button" implenmentation details. They must be at the start: // Remove the "button" implenmentation details. They must be at the start:
let button_path = PathBuf::from("builtin:/fluent-base/button.slint"); let button_path = PathBuf::from("builtin:/fluent-base/button.slint");
@ -518,8 +511,10 @@ export component Entry inherits Main { /* @lsp:ignore-node */ } // 401
assert_eq!(offset, *expected_offset); assert_eq!(offset, *expected_offset);
} }
let covers_below = let covers_below = super::collect_all_element_nodes_covering(
super::collect_all_element_nodes_covering(100.0, 180.0, &component_instance); LogicalPoint::new(100.0, 180.0),
&component_instance,
);
// All but the button itself as well as the SomeComponent (impl and use) // All but the button itself as well as the SomeComponent (impl and use)
assert_eq!(covers_below.len(), covers_center.len() - 3); assert_eq!(covers_below.len(), covers_center.len() - 3);
@ -537,26 +532,31 @@ export component Entry inherits Main { /* @lsp:ignore-node */ } // 401
let component_instance = demo_app(); let component_instance = demo_app();
let button_path = PathBuf::from("builtin:/fluent-base/button.slint"); let button_path = PathBuf::from("builtin:/fluent-base/button.slint");
let mut covers_center = let mut covers_center = super::collect_all_element_nodes_covering(
super::collect_all_element_nodes_covering(100.0, 100.0, &component_instance) LogicalPoint::new(100.0, 100.0),
.iter() &component_instance,
.flat_map(|sc| sc.as_element_node()) )
.map(|en| en.path_and_offset()) .iter()
.collect::<Vec<_>>(); .flat_map(|sc| sc.as_element_node())
.map(|en| en.path_and_offset())
.collect::<Vec<_>>();
let first_non_button = covers_center.iter().position(|(p, _)| p != &button_path).unwrap(); let first_non_button = covers_center.iter().position(|(p, _)| p != &button_path).unwrap();
covers_center.drain(1..(first_non_button - 1)); // strip all but first/last of button covers_center.drain(1..(first_non_button - 1)); // strip all but first/last of button
// Select without crossing file boundries // Select without crossing file boundries
let select = let select = super::select_element_at_impl(
super::select_element_at_impl(&component_instance, 100.0, 100.0, false).unwrap(); &component_instance,
LogicalPoint::new(100.0, 100.0),
false,
)
.unwrap();
assert_eq!(&select.path_and_offset(), covers_center.get(2).unwrap()); assert_eq!(&select.path_and_offset(), covers_center.get(2).unwrap());
// Move deeper into the image: // Move deeper into the image:
let next = super::select_element_behind_impl( let next = super::select_element_behind_impl(
&component_instance, &component_instance,
&select, &select,
100.0, LogicalPoint::new(100.0, 100.0),
100.0,
false, false,
false, false,
) )
@ -565,8 +565,7 @@ export component Entry inherits Main { /* @lsp:ignore-node */ } // 401
let next = super::select_element_behind_impl( let next = super::select_element_behind_impl(
&component_instance, &component_instance,
&next, &next,
100.0, LogicalPoint::new(100.0, 100.0),
100.0,
false, false,
false, false,
) )
@ -575,8 +574,7 @@ export component Entry inherits Main { /* @lsp:ignore-node */ } // 401
let next = super::select_element_behind_impl( let next = super::select_element_behind_impl(
&component_instance, &component_instance,
&next, &next,
100.0, LogicalPoint::new(100.0, 100.0),
100.0,
false, false,
false, false,
) )
@ -585,8 +583,7 @@ export component Entry inherits Main { /* @lsp:ignore-node */ } // 401
let next = super::select_element_behind_impl( let next = super::select_element_behind_impl(
&component_instance, &component_instance,
&next, &next,
100.0, LogicalPoint::new(100.0, 100.0),
100.0,
false, false,
false, false,
) )
@ -595,8 +592,7 @@ export component Entry inherits Main { /* @lsp:ignore-node */ } // 401
assert!(super::select_element_behind_impl( assert!(super::select_element_behind_impl(
&component_instance, &component_instance,
&next, &next,
100.0, LogicalPoint::new(100.0, 100.0),
100.0,
false, false,
false false
) )
@ -606,8 +602,7 @@ export component Entry inherits Main { /* @lsp:ignore-node */ } // 401
let prev = super::select_element_behind_impl( let prev = super::select_element_behind_impl(
&component_instance, &component_instance,
&next, &next,
100.0, LogicalPoint::new(100.0, 100.0),
100.0,
false, false,
true, true,
) )
@ -616,8 +611,7 @@ export component Entry inherits Main { /* @lsp:ignore-node */ } // 401
let prev = super::select_element_behind_impl( let prev = super::select_element_behind_impl(
&component_instance, &component_instance,
&prev, &prev,
100.0, LogicalPoint::new(100.0, 100.0),
100.0,
false, false,
true, true,
) )
@ -626,8 +620,7 @@ export component Entry inherits Main { /* @lsp:ignore-node */ } // 401
let prev = super::select_element_behind_impl( let prev = super::select_element_behind_impl(
&component_instance, &component_instance,
&prev, &prev,
100.0, LogicalPoint::new(100.0, 100.0),
100.0,
false, false,
true, true,
) )
@ -636,8 +629,7 @@ export component Entry inherits Main { /* @lsp:ignore-node */ } // 401
let prev = super::select_element_behind_impl( let prev = super::select_element_behind_impl(
&component_instance, &component_instance,
&prev, &prev,
100.0, LogicalPoint::new(100.0, 100.0),
100.0,
false, false,
true, true,
) )
@ -646,8 +638,7 @@ export component Entry inherits Main { /* @lsp:ignore-node */ } // 401
assert!(super::select_element_behind_impl( assert!(super::select_element_behind_impl(
&component_instance, &component_instance,
&prev, &prev,
100.0, LogicalPoint::new(100.0, 100.0),
100.0,
false, false,
true true
) )
@ -657,8 +648,7 @@ export component Entry inherits Main { /* @lsp:ignore-node */ } // 401
super::select_element_behind_impl( super::select_element_behind_impl(
&component_instance, &component_instance,
&select, &select,
100.0, LogicalPoint::new(100.0, 100.0),
100.0,
false, false,
true true
), ),
@ -666,8 +656,12 @@ export component Entry inherits Main { /* @lsp:ignore-node */ } // 401
); );
// Select with crossing file boundries // Select with crossing file boundries
let select = let select = super::select_element_at_impl(
super::select_element_at_impl(&component_instance, 100.0, 100.0, true).unwrap(); &component_instance,
LogicalPoint::new(100.0, 100.0),
true,
)
.unwrap();
assert_eq!(&select.path_and_offset(), covers_center.get(0).unwrap()); assert_eq!(&select.path_and_offset(), covers_center.get(0).unwrap());
// move to the last in the button definition: // move to the last in the button definition:
@ -676,8 +670,7 @@ export component Entry inherits Main { /* @lsp:ignore-node */ } // 401
button = super::select_element_behind_impl( button = super::select_element_behind_impl(
&component_instance, &component_instance,
&button, &button,
100.0, LogicalPoint::new(100.0, 100.0),
100.0,
true, true,
false, false,
) )
@ -691,8 +684,7 @@ export component Entry inherits Main { /* @lsp:ignore-node */ } // 401
let next = super::select_element_behind_impl( let next = super::select_element_behind_impl(
&component_instance, &component_instance,
&button, &button,
100.0, LogicalPoint::new(100.0, 100.0),
100.0,
true, true,
false, false,
) )
@ -701,8 +693,7 @@ export component Entry inherits Main { /* @lsp:ignore-node */ } // 401
let next = super::select_element_behind_impl( let next = super::select_element_behind_impl(
&component_instance, &component_instance,
&next, &next,
100.0, LogicalPoint::new(100.0, 100.0),
100.0,
true, true,
false, false,
) )
@ -711,8 +702,7 @@ export component Entry inherits Main { /* @lsp:ignore-node */ } // 401
let next = super::select_element_behind_impl( let next = super::select_element_behind_impl(
&component_instance, &component_instance,
&next, &next,
100.0, LogicalPoint::new(100.0, 100.0),
100.0,
true, true,
false, false,
) )
@ -721,8 +711,7 @@ export component Entry inherits Main { /* @lsp:ignore-node */ } // 401
let next = super::select_element_behind_impl( let next = super::select_element_behind_impl(
&component_instance, &component_instance,
&next, &next,
100.0, LogicalPoint::new(100.0, 100.0),
100.0,
true, true,
false, false,
) )
@ -731,8 +720,7 @@ export component Entry inherits Main { /* @lsp:ignore-node */ } // 401
let next = super::select_element_behind_impl( let next = super::select_element_behind_impl(
&component_instance, &component_instance,
&next, &next,
100.0, LogicalPoint::new(100.0, 100.0),
100.0,
true, true,
false, false,
) )
@ -741,33 +729,57 @@ export component Entry inherits Main { /* @lsp:ignore-node */ } // 401
assert!(super::select_element_behind_impl( assert!(super::select_element_behind_impl(
&component_instance, &component_instance,
&next, &next,
100.0, LogicalPoint::new(100.0, 100.0),
100.0,
false, false,
false false
) )
.is_none()); .is_none());
// Move towards the viewer: // Move towards the viewer:
let prev = let prev = super::select_element_behind_impl(
super::select_element_behind_impl(&component_instance, &next, 100.0, 100.0, true, true) &component_instance,
.unwrap(); &next,
LogicalPoint::new(100.0, 100.0),
true,
true,
)
.unwrap();
assert_eq!(&prev.path_and_offset(), covers_center.get(5).unwrap()); assert_eq!(&prev.path_and_offset(), covers_center.get(5).unwrap());
let prev = let prev = super::select_element_behind_impl(
super::select_element_behind_impl(&component_instance, &prev, 100.0, 100.0, true, true) &component_instance,
.unwrap(); &prev,
LogicalPoint::new(100.0, 100.0),
true,
true,
)
.unwrap();
assert_eq!(&prev.path_and_offset(), covers_center.get(4).unwrap()); assert_eq!(&prev.path_and_offset(), covers_center.get(4).unwrap());
let prev = let prev = super::select_element_behind_impl(
super::select_element_behind_impl(&component_instance, &prev, 100.0, 100.0, true, true) &component_instance,
.unwrap(); &prev,
LogicalPoint::new(100.0, 100.0),
true,
true,
)
.unwrap();
assert_eq!(&prev.path_and_offset(), covers_center.get(3).unwrap()); assert_eq!(&prev.path_and_offset(), covers_center.get(3).unwrap());
let prev = let prev = super::select_element_behind_impl(
super::select_element_behind_impl(&component_instance, &prev, 100.0, 100.0, true, true) &component_instance,
.unwrap(); &prev,
LogicalPoint::new(100.0, 100.0),
true,
true,
)
.unwrap();
assert_eq!(&prev.path_and_offset(), covers_center.get(2).unwrap()); assert_eq!(&prev.path_and_offset(), covers_center.get(2).unwrap());
let prev = let prev = super::select_element_behind_impl(
super::select_element_behind_impl(&component_instance, &prev, 100.0, 100.0, true, true) &component_instance,
.unwrap(); &prev,
LogicalPoint::new(100.0, 100.0),
true,
true,
)
.unwrap();
assert_eq!(&prev.path_and_offset(), covers_center.get(1).unwrap()); assert_eq!(&prev.path_and_offset(), covers_center.get(1).unwrap());
button = prev; button = prev;
@ -775,8 +787,7 @@ export component Entry inherits Main { /* @lsp:ignore-node */ } // 401
button = super::select_element_behind_impl( button = super::select_element_behind_impl(
&component_instance, &component_instance,
&button, &button,
100.0, LogicalPoint::new(100.0, 100.0),
100.0,
true, true,
true, true,
) )
@ -789,8 +800,7 @@ export component Entry inherits Main { /* @lsp:ignore-node */ } // 401
assert!(super::select_element_behind_impl( assert!(super::select_element_behind_impl(
&component_instance, &component_instance,
&button, &button,
100.0, LogicalPoint::new(100.0, 100.0),
100.0,
true, true,
true true
) )

View file

@ -22,8 +22,7 @@ pub trait ElementRcNodeExt {
fn geometry_at( fn geometry_at(
&self, &self,
component_instance: &ComponentInstance, component_instance: &ComponentInstance,
x: f32, position: LogicalPoint,
y: f32,
) -> Option<i_slint_core::lengths::LogicalRect>; ) -> Option<i_slint_core::lengths::LogicalRect>;
/// Find the first geometry of ElementRcNode in `rect` /// Find the first geometry of ElementRcNode in `rect`
@ -60,11 +59,9 @@ impl ElementRcNodeExt for common::ElementRcNode {
fn geometry_at( fn geometry_at(
&self, &self,
component_instance: &ComponentInstance, component_instance: &ComponentInstance,
x: f32, position: LogicalPoint,
y: f32,
) -> Option<i_slint_core::lengths::LogicalRect> { ) -> Option<i_slint_core::lengths::LogicalRect> {
let click_position = LogicalPoint::new(x, y); self.geometries(component_instance).iter().find(|g| g.contains(position)).cloned()
self.geometries(component_instance).iter().find(|g| g.contains(click_position)).cloned()
} }
fn geometry_in( fn geometry_in(