use thiserror::Error;
#[non_exhaustive]
#[derive(Debug, Error)]
pub enum NodeTreeViewError {
#[error("Could not find node matching pattern: {0}")]
NodeNotFound(String),
}
pub trait NodeTreeView {
fn from_node<T: godot::obj::Inherits<godot::classes::Node>>(
node: godot::obj::Gd<T>,
) -> Result<Self, NodeTreeViewError>
where
Self: Sized;
}
pub fn find_node_by_pattern(
base_node: &godot::obj::Gd<godot::classes::Node>,
pattern: &str,
) -> Option<godot::obj::Gd<godot::classes::Node>> {
let (search_root, pattern_parts) = if let Some(stripped) = pattern.strip_prefix('/') {
let scene_tree = base_node.get_tree()?;
let root = scene_tree.get_root()?;
let root_as_node = root.upcast::<godot::classes::Node>();
let mut parts: Vec<&str> = stripped.split('/').filter(|s| !s.is_empty()).collect();
if !parts.is_empty() && parts[0] == "root" {
parts.remove(0);
}
(root_as_node, parts)
} else {
let parts: Vec<&str> = pattern.split('/').filter(|s| !s.is_empty()).collect();
(base_node.clone(), parts)
};
find_node_recursive(&search_root, &pattern_parts, 0)
}
fn find_node_recursive(
current_node: &godot::obj::Gd<godot::classes::Node>,
pattern_parts: &[&str],
depth: usize,
) -> Option<godot::obj::Gd<godot::classes::Node>> {
if depth >= pattern_parts.len() {
return Some(current_node.clone());
}
let pattern_part = pattern_parts[depth];
if pattern_part == "*" {
for i in 0..current_node.get_child_count() {
if let Some(child) = current_node.get_child(i)
&& let Some(result) = find_node_recursive(&child, pattern_parts, depth + 1)
{
return Some(result);
}
}
} else if pattern_part.contains('*') {
for i in 0..current_node.get_child_count() {
if let Some(child) = current_node.get_child(i) {
let child_name = child.get_name().to_string();
if matches_wildcard_pattern(&child_name, pattern_part)
&& let Some(result) = find_node_recursive(&child, pattern_parts, depth + 1)
{
return Some(result);
}
}
}
} else {
if current_node.has_node(pattern_part) {
let child = current_node.get_node_as::<godot::classes::Node>(pattern_part);
if let Some(result) = find_node_recursive(&child, pattern_parts, depth + 1) {
return Some(result);
}
}
}
None
}
fn matches_wildcard_pattern(text: &str, pattern: &str) -> bool {
if pattern == "*" {
return true;
}
if pattern.starts_with('*') && pattern.len() > 1 {
let suffix = &pattern[1..];
return text.ends_with(suffix);
}
if pattern.ends_with('*') && pattern.len() > 1 {
let prefix = &pattern[..pattern.len() - 1];
return text.starts_with(prefix);
}
if let Some(star_pos) = pattern.find('*') {
let prefix = &pattern[..star_pos];
let suffix = &pattern[star_pos + 1..];
return text.starts_with(prefix)
&& text.ends_with(suffix)
&& text.len() >= prefix.len() + suffix.len();
}
text == pattern
}