live preview: Do not drop on elements that can not have children

Slpit up `lookup_type_for_child_elemnt` (in the compiler) into the
part that looks at the element itself only (called `accepts_child_element`)
and the part that looks at the child.

When deciding whether we can drop something into another element in
the live preview, we know the child type is going to be OK, even when
the `TypeRegister` does not contain it yet (as it is not imported yet).
This commit is contained in:
Tobias Hunger 2024-03-01 16:27:06 +01:00 committed by Tobias Hunger
parent 9eb52fb14a
commit d0fc025bc8
4 changed files with 68 additions and 16 deletions

View file

@ -481,23 +481,43 @@ impl ElementType {
}
}
pub fn lookup_type_for_child_element(
pub fn can_have_child(&self, name: &str, tr: &TypeRegister) -> bool {
match self {
Self::Component(component) if component.child_insertion_point.borrow().is_none() => {
let base_type = component.root_element.borrow().base_type.clone();
if base_type == tr.empty_type() {
false
} else {
base_type.can_have_child(name, tr)
}
}
Self::Builtin(builtin) => {
if builtin.additional_accepted_child_types.contains_key(name) {
true
} else {
!builtin.disallow_global_types_as_child_elements
}
}
_ => true,
}
}
pub fn accepts_child_element(
&self,
name: &str,
tr: &TypeRegister,
) -> Result<ElementType, String> {
) -> Result<Option<ElementType>, String> {
match self {
Self::Component(component) if component.child_insertion_point.borrow().is_none() => {
let base_type = component.root_element.borrow().base_type.clone();
if base_type == tr.empty_type() {
return Err(format!("'{}' cannot have children. Only components with @children can have children", component.id));
} else {
return base_type.lookup_type_for_child_element(name, tr);
}
return base_type.accepts_child_element(name, tr);
}
Self::Builtin(builtin) => {
if let Some(child_type) = builtin.additional_accepted_child_types.get(name) {
return Ok(child_type.clone());
return Ok(Some(child_type.clone()));
}
if builtin.disallow_global_types_as_child_elements {
let mut valid_children: Vec<_> =
@ -514,6 +534,18 @@ impl ElementType {
}
_ => {}
};
Ok(None)
}
pub fn lookup_type_for_child_element(
&self,
name: &str,
tr: &TypeRegister,
) -> Result<ElementType, String> {
if let Some(ct) = self.accepts_child_element(name, tr)? {
return Ok(ct);
}
tr.lookup_element(name).and_then(|t| {
if !tr.expose_internal_types && matches!(&t, Self::Builtin(e) if e.is_internal) {
Err(format!("Unknown type {}. (The type exist as an internal type, but cannot be accessed in this scope)", name))

View file

@ -16,12 +16,19 @@ pub type UrlVersion = Option<i32>;
#[cfg(target_arch = "wasm32")]
use crate::wasm_prelude::*;
#[derive(Clone, Debug)]
#[derive(Clone)]
pub struct ElementRcNode {
pub element: ElementRc,
pub debug_index: usize,
}
impl std::fmt::Debug for ElementRcNode {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let (path, offset) = self.path_and_offset();
write!(f, "ElementNode {{ {path:?}:{offset} }}")
}
}
impl ElementRcNode {
pub fn find_in(element: ElementRc, path: &std::path::Path, offset: u32) -> Option<Self> {
let debug_index = element.borrow().debug.iter().position(|(n, _)| {

View file

@ -107,8 +107,8 @@ fn search_for_parent_element(root: &ElementRc, child: &ElementRc) -> Option<Elem
}
// triggered from the UI, running in UI thread
fn can_drop_component(_component_name: slint::SharedString, x: f32, y: f32) -> bool {
drop_location::can_drop_at(x, y)
fn can_drop_component(component_name: slint::SharedString, x: f32, y: f32) -> bool {
drop_location::can_drop_at(x, y, component_name.into())
}
// triggered from the UI, running in UI thread

View file

@ -57,9 +57,11 @@ fn find_drop_location(
component_instance: &ComponentInstance,
x: f32,
y: f32,
component_type: &str,
) -> Option<DropInformation> {
let target_element_node = {
let mut result = None;
let tl = component_instance.definition().type_loader();
for sc in &element_selection::collect_all_element_nodes_covering(x, y, &component_instance)
{
let Some(en) = sc.as_element_node() else {
@ -70,14 +72,23 @@ fn find_drop_location(
continue;
}
if !element_selection::is_same_file_as_root_node(&component_instance, &en) {
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.is_layout()
&& element_type
.accepts_child_element(component_type, &doc.local_registry)
.is_err()
{
break;
}
}
if en.with_element_node(|n| {
n.child_node(i_slint_compiler::parser::SyntaxKind::ChildrenPlaceholder).is_some()
}) && !element_selection::is_root_element_node(&component_instance, &en)
{
if !element_selection::is_same_file_as_root_node(&component_instance, &en) {
continue;
}
@ -105,8 +116,10 @@ fn find_drop_location(
}
/// Find the Element to insert into. None means we can not insert at this point.
pub fn can_drop_at(x: f32, y: f32) -> bool {
super::component_instance().and_then(|ci| find_drop_location(&ci, x, y)).is_some()
pub fn can_drop_at(x: f32, y: f32, component_type: String) -> bool {
super::component_instance()
.and_then(|ci| find_drop_location(&ci, x, y, &component_type))
.is_some()
}
/// Extra data on an added Element, relevant to the Preview side only.
@ -130,7 +143,7 @@ pub fn drop_at(
) -> Option<(lsp_types::WorkspaceEdit, DropData)> {
let component_instance = preview::component_instance()?;
let tl = component_instance.definition().type_loader();
let drop_info = find_drop_location(&component_instance, x, y)?;
let drop_info = find_drop_location(&component_instance, x, y, &component_type)?;
let properties = {
let click_position =