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
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 crate::common;
@ -75,16 +75,15 @@ impl DropMarkDirection {
}
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,
let bs = (dimension / 4.0).floor();
if bs > 8.0 {
8.0
} else {
bs
}
}
// 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(
element_geometry: &LogicalRect,
position: LogicalPoint,
@ -171,7 +170,6 @@ impl DropMarkDirection {
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,
@ -260,14 +258,13 @@ fn insert_position_at_end(
fn drop_target_element_node(
component_instance: &ComponentInstance,
x: f32,
y: f32,
position: LogicalPoint,
component_type: &str,
) -> (Option<common::ElementRcNode>, Option<common::ElementRcNode>) {
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) {
for sc in &element_selection::collect_all_element_nodes_covering(position, component_instance) {
let Some(en) = sc.as_element_node() else {
continue;
};
@ -318,34 +315,15 @@ fn extract_element(node: SyntaxNode) -> Option<i_slint_compiler::parser::syntax_
fn examine_target_element_node(
context: &str,
component_instance: &ComponentInstance,
x: f32,
y: f32,
position: LogicalPoint,
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 geometry = target_element_node.geometry_at(component_instance, position);
// 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()) {
@ -357,46 +335,90 @@ fn examine_target_element_node(
} else {
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(
component_instance: &ComponentInstance,
x: f32,
y: f32,
position: LogicalPoint,
component_type: &str,
) -> Option<DropInformation> {
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?;
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 {
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)?;
Some(DropInformation {
target_element_node: drop_element_node,
insert_info,
drop_mark: Some(DropMark {
start: LogicalPoint::new(x - 10.0, y - 10.0),
end: LogicalPoint::new(x + 10.0, y + 10.0),
}),
})
if drop_element_node.layout_kind() != ui::LayoutKind::None {
drop_into_layout(component_instance, drop_element_node, None, position)
} else {
drop_into_element(
component_instance,
drop_element_node,
drop_surrounding_element_node,
position,
)
}
}
/// 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 {
let component_type = component.name.to_string();
if let Some(dm) =
&super::component_instance().and_then(|ci| find_drop_location(&ci, x, y, &component_type))
let position = LogicalPoint::new(x, y);
if let Some(dm) = &super::component_instance()
.and_then(|ci| find_drop_location(&ci, position, &component_type))
{
super::set_drop_mark(&dm.drop_mark);
true
@ -424,10 +446,11 @@ pub fn drop_at(
y: f32,
component: &common::ComponentInformation,
) -> Option<(lsp_types::WorkspaceEdit, DropData)> {
let position = LogicalPoint::new(x, y);
let component_type = &component.name;
let component_instance = preview::component_instance()?;
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 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
&& !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("y", format!("{}px", y - area.origin.y)));
@ -508,13 +532,15 @@ mod tests {
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;
assert!(
bs >= (expected - 0.05) && bs < (expected + 0.05)
|| (bs >= (expected + 0.95) && bs < (expected + 1.05))
);
assert!((bs * 3.0) <= dimension); // this makes sure the first bs is 0.0
expected = bs.round();
}
// The maximum border size is 4px:
assert!(expected <= 4.05);
assert!(expected <= 8.05);
}
#[test]
@ -666,342 +692,218 @@ mod tests {
);
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:?}");
let pos = LogicalPoint::new(50.0 + (width / 2.0), 50.0 + (height / 2.0));
assert_eq!(
DropMarkDirection::for_element(
&rect,
pos,
ui::LayoutKind::None
),
DropMarkDirection::for_element(&rect, pos, ui::LayoutKind::None),
DropMarkDirection::None
);
assert_eq!(
DropMarkDirection::for_element(
&rect,
pos,
ui::LayoutKind::Horizontal
),
DropMarkDirection::for_element(&rect, pos, ui::LayoutKind::Horizontal),
DropMarkDirection::None
);
assert_eq!(
DropMarkDirection::for_element(
&rect,
pos,
ui::LayoutKind::Vertical
),
DropMarkDirection::for_element(&rect, pos, ui::LayoutKind::Vertical),
DropMarkDirection::None
);
assert_eq!(
DropMarkDirection::for_element(
&rect,
pos,
ui::LayoutKind::Grid
),
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::for_element(&rect, pos, ui::LayoutKind::None),
DropMarkDirection::None
);
assert_eq!(
DropMarkDirection::for_element(
&rect,
pos,
ui::LayoutKind::Horizontal
),
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 }
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 }
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::for_element(&rect, pos, ui::LayoutKind::None),
DropMarkDirection::None
);
assert_eq!(
DropMarkDirection::for_element(
&rect,
pos,
ui::LayoutKind::Horizontal
),
DropMarkDirection::for_element(&rect, pos, ui::LayoutKind::Horizontal),
DropMarkDirection::None
);
assert_eq!(
DropMarkDirection::for_element(
&rect,
pos,
ui::LayoutKind::Vertical
),
DropMarkDirection::for_element(&rect, pos, ui::LayoutKind::Vertical),
DropMarkDirection::None
);
assert_eq!(
DropMarkDirection::for_element(
&rect,
pos,
ui::LayoutKind::Grid
),
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::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 }
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::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 }
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::for_element(&rect, pos, ui::LayoutKind::None),
DropMarkDirection::None
);
assert_eq!(
DropMarkDirection::for_element(
&rect,
pos,
ui::LayoutKind::Horizontal
),
DropMarkDirection::for_element(&rect, pos, ui::LayoutKind::Horizontal),
DropMarkDirection::None
);
assert_eq!(
DropMarkDirection::for_element(
&rect,
pos,
ui::LayoutKind::Vertical
),
DropMarkDirection::for_element(&rect, pos, ui::LayoutKind::Vertical),
DropMarkDirection::None
);
assert_eq!(
DropMarkDirection::for_element(
&rect,
pos,
ui::LayoutKind::Grid
),
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::for_element(&rect, pos, ui::LayoutKind::None),
DropMarkDirection::None
);
assert_eq!(
DropMarkDirection::for_element(
&rect,
pos,
ui::LayoutKind::Horizontal
),
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 }
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 }
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::for_element(&rect, pos, ui::LayoutKind::None),
DropMarkDirection::None
);
assert_eq!(
DropMarkDirection::for_element(
&rect,
pos,
ui::LayoutKind::Horizontal
),
DropMarkDirection::for_element(&rect, pos, ui::LayoutKind::Horizontal),
DropMarkDirection::None
);
assert_eq!(
DropMarkDirection::for_element(
&rect,
pos,
ui::LayoutKind::Vertical
),
DropMarkDirection::for_element(&rect, pos, ui::LayoutKind::Vertical),
DropMarkDirection::None
);
assert_eq!(
DropMarkDirection::for_element(
&rect,
pos,
ui::LayoutKind::Grid
),
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::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 }
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::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 }
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::for_element(&rect, pos, ui::LayoutKind::None),
DropMarkDirection::None
);
assert_eq!(
DropMarkDirection::for_element(
&rect,
pos,
ui::LayoutKind::Horizontal
),
DropMarkDirection::for_element(&rect, pos, ui::LayoutKind::Horizontal),
DropMarkDirection::None
);
assert_eq!(
DropMarkDirection::for_element(
&rect,
pos,
ui::LayoutKind::Vertical
),
DropMarkDirection::for_element(&rect, pos, ui::LayoutKind::Vertical),
DropMarkDirection::None
);
assert_eq!(
DropMarkDirection::for_element(
&rect,
pos,
ui::LayoutKind::Grid
),
DropMarkDirection::for_element(&rect, pos, ui::LayoutKind::Grid),
DropMarkDirection::None
);
}
}
}

View file

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

View file

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