mirror of
https://github.com/slint-ui/slint.git
synced 2025-10-02 06:41:14 +00:00
live preview: Implement drop marker mapping for elements
... so we know what to do when dropping near tghe border of elements that accept drops in a layout.
This commit is contained in:
parent
1bb0a8638c
commit
69b8e8d57c
4 changed files with 824 additions and 51 deletions
|
@ -75,6 +75,10 @@ impl ElementRcNode {
|
||||||
(n.source_file.path().to_owned(), u32::from(n.text_range().start()))
|
(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(
|
pub fn create_workspace_edit(
|
||||||
|
|
|
@ -220,10 +220,8 @@ fn change_geometry_of_selected_element(x: f32, y: f32, width: f32, height: f32)
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
let Some(geometry) = component_instance
|
let Some(geometry) =
|
||||||
.element_positions(&selected_element_node.element)
|
selected_element_node.geometries(&component_instance).get(selected.instance_index).cloned()
|
||||||
.get(selected.instance_index)
|
|
||||||
.cloned()
|
|
||||||
else {
|
else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
// Copyright © SixtyFPS GmbH <info@slint.dev>
|
// Copyright © SixtyFPS GmbH <info@slint.dev>
|
||||||
// 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;
|
use i_slint_compiler::parser::{SyntaxKind, SyntaxNode};
|
||||||
use i_slint_core::lengths::{LogicalLength, LogicalPoint};
|
use i_slint_core::lengths::{LogicalPoint, LogicalRect};
|
||||||
use slint_interpreter::ComponentInstance;
|
use slint_interpreter::ComponentInstance;
|
||||||
|
|
||||||
use crate::common;
|
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 struct DropInformation {
|
||||||
pub target_element_node: common::ElementRcNode,
|
pub target_element_node: common::ElementRcNode,
|
||||||
pub insert_info: InsertInformation,
|
pub insert_info: InsertInformation,
|
||||||
|
@ -71,7 +204,7 @@ pub struct DropMark {
|
||||||
pub end: i_slint_core::lengths::LogicalPoint,
|
pub end: i_slint_core::lengths::LogicalPoint,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn find_insert_position_at_end(
|
fn insert_position_at_end(
|
||||||
target_element_node: &common::ElementRcNode,
|
target_element_node: &common::ElementRcNode,
|
||||||
) -> Option<InsertInformation> {
|
) -> Option<InsertInformation> {
|
||||||
target_element_node.with_element_node(|node| {
|
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<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) {
|
||||||
|
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<i_slint_compiler::parser::syntax_nodes::Element> {
|
||||||
|
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(
|
fn find_drop_location(
|
||||||
component_instance: &ComponentInstance,
|
component_instance: &ComponentInstance,
|
||||||
x: f32,
|
x: f32,
|
||||||
y: f32,
|
y: f32,
|
||||||
component_type: &str,
|
component_type: &str,
|
||||||
) -> Option<DropInformation> {
|
) -> Option<DropInformation> {
|
||||||
let target_element_node = {
|
let (drop_element_node, drop_surrounding_element_node) =
|
||||||
let mut result = None;
|
drop_target_element_node(component_instance, x, y, component_type);
|
||||||
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) {
|
let drop_element_node = drop_element_node?;
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
let (path, _) = en.path_and_offset();
|
examine_target_element_node("Drop target", component_instance, x, y, &drop_element_node);
|
||||||
let Some(doc) = tl.get_document(&path) else {
|
if let Some(sn) = &drop_surrounding_element_node {
|
||||||
continue;
|
examine_target_element_node("Surrounding", component_instance, x, y, sn);
|
||||||
};
|
}
|
||||||
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) {
|
let insert_info = insert_position_at_end(&drop_element_node)?;
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
result = Some(en);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
result
|
|
||||||
}?;
|
|
||||||
|
|
||||||
let insert_info = find_insert_position_at_end(&target_element_node)?;
|
|
||||||
|
|
||||||
Some(DropInformation {
|
Some(DropInformation {
|
||||||
target_element_node,
|
target_element_node: drop_element_node,
|
||||||
insert_info,
|
insert_info,
|
||||||
drop_mark: Some(DropMark {
|
drop_mark: Some(DropMark {
|
||||||
start: LogicalPoint::new(x - 10.0, y - 10.0),
|
start: LogicalPoint::new(x - 10.0, y - 10.0),
|
||||||
|
@ -221,16 +432,10 @@ pub fn drop_at(
|
||||||
let properties = {
|
let properties = {
|
||||||
let mut props = component.default_properties.clone();
|
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
|
if drop_info.target_element_node.layout_kind() == ui::LayoutKind::None
|
||||||
&& !component.fills_parent
|
&& !component.fills_parent
|
||||||
{
|
{
|
||||||
if let Some(area) = component_instance
|
if let Some(area) = drop_info.target_element_node.geometry_at(&component_instance, x, y)
|
||||||
.element_positions(&drop_info.target_element_node.element)
|
|
||||||
.iter()
|
|
||||||
.find(|p| p.contains(click_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)));
|
||||||
|
@ -288,3 +493,516 @@ pub fn drop_at(
|
||||||
DropData { selection_offset, path },
|
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
|
||||||
|
);
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -2,12 +2,36 @@
|
||||||
// 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::layout;
|
use i_slint_compiler::layout;
|
||||||
|
use i_slint_core::lengths::{LogicalPoint, LogicalRect};
|
||||||
|
|
||||||
use crate::common;
|
use crate::common;
|
||||||
use crate::preview::ui;
|
use crate::preview::ui;
|
||||||
|
|
||||||
|
use slint_interpreter::ComponentInstance;
|
||||||
|
|
||||||
pub trait ElementRcNodeExt {
|
pub trait ElementRcNodeExt {
|
||||||
fn layout_kind(&self) -> crate::preview::ui::LayoutKind;
|
fn layout_kind(&self) -> crate::preview::ui::LayoutKind;
|
||||||
|
|
||||||
|
/// Find all geometries for the given `ElementRcNode`
|
||||||
|
fn geometries(
|
||||||
|
&self,
|
||||||
|
component_instance: &ComponentInstance,
|
||||||
|
) -> Vec<i_slint_core::lengths::LogicalRect>;
|
||||||
|
|
||||||
|
/// Find the first geometry of `ElementRcNode` that includes the point `x`, `y`
|
||||||
|
fn geometry_at(
|
||||||
|
&self,
|
||||||
|
component_instance: &ComponentInstance,
|
||||||
|
x: f32,
|
||||||
|
y: f32,
|
||||||
|
) -> Option<i_slint_core::lengths::LogicalRect>;
|
||||||
|
|
||||||
|
/// Find the first geometry of ElementRcNode in `rect`
|
||||||
|
fn geometry_in(
|
||||||
|
&self,
|
||||||
|
component_instance: &ComponentInstance,
|
||||||
|
rect: &LogicalRect,
|
||||||
|
) -> Vec<i_slint_core::lengths::LogicalRect>;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ElementRcNodeExt for common::ElementRcNode {
|
impl ElementRcNodeExt for common::ElementRcNode {
|
||||||
|
@ -25,4 +49,33 @@ impl ElementRcNodeExt for common::ElementRcNode {
|
||||||
_ => ui::LayoutKind::None,
|
_ => ui::LayoutKind::None,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn geometries(
|
||||||
|
&self,
|
||||||
|
component_instance: &ComponentInstance,
|
||||||
|
) -> Vec<i_slint_core::lengths::LogicalRect> {
|
||||||
|
component_instance.element_positions(&self.as_element())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn geometry_at(
|
||||||
|
&self,
|
||||||
|
component_instance: &ComponentInstance,
|
||||||
|
x: f32,
|
||||||
|
y: f32,
|
||||||
|
) -> 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()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn geometry_in(
|
||||||
|
&self,
|
||||||
|
component_instance: &ComponentInstance,
|
||||||
|
rect: &LogicalRect,
|
||||||
|
) -> Vec<i_slint_core::lengths::LogicalRect> {
|
||||||
|
self.geometries(component_instance)
|
||||||
|
.iter()
|
||||||
|
.filter(|g| rect.contains_rect(g))
|
||||||
|
.cloned()
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue