use std::collections::HashMap;
use crate::focus::WidgetId;
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct WidgetState {
pub focused: bool,
pub hovered: bool,
pub disabled: bool,
pub active: bool,
}
#[derive(Clone, Debug)]
pub struct WidgetNode {
pub id: WidgetId,
pub type_name: String,
pub classes: Vec<String>,
pub css_id: Option<String>,
pub state: WidgetState,
pub parent: Option<WidgetId>,
pub children: Vec<WidgetId>,
}
impl WidgetNode {
pub fn new(id: WidgetId, type_name: impl Into<String>) -> Self {
Self {
id,
type_name: type_name.into(),
classes: Vec::new(),
css_id: None,
state: WidgetState::default(),
parent: None,
children: Vec::new(),
}
}
pub fn with_class(mut self, class: impl Into<String>) -> Self {
self.classes.push(class.into());
self
}
pub fn with_id(mut self, css_id: impl Into<String>) -> Self {
self.css_id = Some(css_id.into());
self
}
pub fn has_class(&self, name: &str) -> bool {
self.classes.iter().any(|c| c == name)
}
}
pub struct WidgetTree {
nodes: HashMap<WidgetId, WidgetNode>,
root: Option<WidgetId>,
}
impl WidgetTree {
pub fn new() -> Self {
Self {
nodes: HashMap::new(),
root: None,
}
}
pub fn add_node(&mut self, node: WidgetNode) {
let id = node.id;
let parent_id = node.parent;
self.nodes.insert(id, node);
if let Some(pid) = parent_id {
if let Some(parent) = self.nodes.get_mut(&pid) {
parent.children.push(id);
}
} else if self.root.is_none() {
self.root = Some(id);
}
}
pub fn remove_node(&mut self, id: WidgetId) {
if let Some(node) = self.nodes.remove(&id) {
if let Some(pid) = node.parent
&& let Some(parent) = self.nodes.get_mut(&pid)
{
parent.children.retain(|&c| c != id);
}
if self.root == Some(id) {
self.root = None;
}
}
}
pub fn get(&self, id: WidgetId) -> Option<&WidgetNode> {
self.nodes.get(&id)
}
pub fn get_mut(&mut self, id: WidgetId) -> Option<&mut WidgetNode> {
self.nodes.get_mut(&id)
}
pub fn root(&self) -> Option<WidgetId> {
self.root
}
pub fn set_root(&mut self, id: WidgetId) {
self.root = Some(id);
}
pub fn parent(&self, id: WidgetId) -> Option<&WidgetNode> {
self.nodes
.get(&id)
.and_then(|n| n.parent)
.and_then(|pid| self.nodes.get(&pid))
}
pub fn children(&self, id: WidgetId) -> &[WidgetId] {
match self.nodes.get(&id) {
Some(node) => &node.children,
None => &[],
}
}
pub fn ancestors(&self, id: WidgetId) -> Vec<WidgetId> {
let mut result = Vec::new();
let mut current = self.nodes.get(&id).and_then(|n| n.parent);
while let Some(pid) = current {
result.push(pid);
current = self.nodes.get(&pid).and_then(|n| n.parent);
}
result
}
pub fn is_first_child(&self, id: WidgetId) -> bool {
let parent = match self.parent(id) {
Some(p) => p,
None => return false,
};
parent.children.first() == Some(&id)
}
pub fn is_last_child(&self, id: WidgetId) -> bool {
let parent = match self.parent(id) {
Some(p) => p,
None => return false,
};
parent.children.last() == Some(&id)
}
pub fn child_index(&self, id: WidgetId) -> Option<usize> {
let parent = self.parent(id)?;
parent.children.iter().position(|&c| c == id)
}
pub fn len(&self) -> usize {
self.nodes.len()
}
pub fn is_empty(&self) -> bool {
self.nodes.is_empty()
}
}
impl Default for WidgetTree {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn empty_tree() {
let tree = WidgetTree::new();
assert!(tree.is_empty());
assert_eq!(tree.len(), 0);
assert!(tree.root().is_none());
}
#[test]
fn add_root_node() {
let mut tree = WidgetTree::new();
tree.add_node(WidgetNode::new(1, "Container"));
assert_eq!(tree.len(), 1);
assert_eq!(tree.root(), Some(1));
assert!(tree.get(1).is_some());
}
#[test]
fn add_child_node() {
let mut tree = WidgetTree::new();
tree.add_node(WidgetNode::new(1, "Container"));
let mut child = WidgetNode::new(2, "Label");
child.parent = Some(1);
tree.add_node(child);
assert_eq!(tree.len(), 2);
assert_eq!(tree.children(1), &[2]);
let parent = tree.parent(2);
assert!(parent.is_some());
let parent = match parent {
Some(p) => p,
None => unreachable!(),
};
assert_eq!(parent.id, 1);
}
#[test]
fn remove_node() {
let mut tree = WidgetTree::new();
tree.add_node(WidgetNode::new(1, "Container"));
let mut child = WidgetNode::new(2, "Label");
child.parent = Some(1);
tree.add_node(child);
tree.remove_node(2);
assert_eq!(tree.len(), 1);
assert!(tree.get(2).is_none());
assert!(tree.children(1).is_empty());
}
#[test]
fn ancestors() {
let mut tree = WidgetTree::new();
tree.add_node(WidgetNode::new(1, "Root"));
let mut mid = WidgetNode::new(2, "Middle");
mid.parent = Some(1);
tree.add_node(mid);
let mut leaf = WidgetNode::new(3, "Leaf");
leaf.parent = Some(2);
tree.add_node(leaf);
let anc = tree.ancestors(3);
assert_eq!(anc, vec![2, 1]);
}
#[test]
fn is_first_last_child() {
let mut tree = WidgetTree::new();
tree.add_node(WidgetNode::new(1, "Root"));
for id in 2..=4 {
let mut child = WidgetNode::new(id, "Child");
child.parent = Some(1);
tree.add_node(child);
}
assert!(tree.is_first_child(2));
assert!(!tree.is_first_child(3));
assert!(!tree.is_first_child(4));
assert!(!tree.is_last_child(2));
assert!(!tree.is_last_child(3));
assert!(tree.is_last_child(4));
}
#[test]
fn child_index() {
let mut tree = WidgetTree::new();
tree.add_node(WidgetNode::new(1, "Root"));
for id in 2..=4 {
let mut child = WidgetNode::new(id, "Child");
child.parent = Some(1);
tree.add_node(child);
}
assert_eq!(tree.child_index(2), Some(0));
assert_eq!(tree.child_index(3), Some(1));
assert_eq!(tree.child_index(4), Some(2));
assert_eq!(tree.child_index(1), None);
}
#[test]
fn widget_node_builder() {
let node = WidgetNode::new(1, "Label")
.with_class("error")
.with_class("bold")
.with_id("main-title");
assert_eq!(node.type_name, "Label");
assert!(node.has_class("error"));
assert!(node.has_class("bold"));
assert!(!node.has_class("hidden"));
assert_eq!(node.css_id.as_deref(), Some("main-title"));
}
#[test]
fn widget_state_default() {
let state = WidgetState::default();
assert!(!state.focused);
assert!(!state.hovered);
assert!(!state.disabled);
assert!(!state.active);
}
}