use crate::geometry::{Point, Rect};
use std::collections::HashMap;
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct WidgetId(pub u64);
impl WidgetId {
pub const ROOT: WidgetId = WidgetId(0);
}
#[derive(Debug)]
pub struct WidgetIdAllocator {
next: u64,
}
impl WidgetIdAllocator {
pub fn new() -> Self {
Self { next: 1 }
}
pub fn alloc(&mut self) -> WidgetId {
let id = WidgetId(self.next);
self.next += 1;
id
}
pub fn allocated(&self) -> u64 {
self.next - 1
}
}
impl Default for WidgetIdAllocator {
fn default() -> Self {
Self::new()
}
}
#[derive(Clone, Debug)]
pub struct WidgetNode {
pub id: WidgetId,
pub parent: Option<WidgetId>,
pub children: Vec<WidgetId>,
pub rect: Rect,
pub z_index: i32,
pub hit_testable: bool,
pub focusable: bool,
pub dirty: bool,
pub clip_rect: Option<Rect>,
pub label: Option<String>,
}
impl WidgetNode {
pub fn new(id: WidgetId, rect: Rect) -> Self {
Self {
id,
parent: None,
children: Vec::new(),
rect,
z_index: 0,
hit_testable: true,
focusable: false,
dirty: true,
clip_rect: None,
label: None,
}
}
pub fn with_clip(mut self, clip: Rect) -> Self {
self.clip_rect = Some(clip);
self
}
}
#[derive(Debug)]
pub struct WidgetTree {
nodes: Vec<WidgetNode>,
index: HashMap<WidgetId, usize>,
alloc: WidgetIdAllocator,
}
impl WidgetTree {
pub fn new(root_rect: Rect) -> Self {
let mut root = WidgetNode::new(WidgetId::ROOT, root_rect);
root.label = Some("root".to_owned());
let mut index = HashMap::new();
index.insert(WidgetId::ROOT, 0usize);
Self {
nodes: vec![root],
index,
alloc: WidgetIdAllocator::new(),
}
}
pub fn len(&self) -> usize {
self.nodes.len()
}
pub fn is_empty(&self) -> bool {
self.nodes.len() <= 1
}
fn index_of(&self, id: WidgetId) -> Option<usize> {
self.index.get(&id).copied()
}
pub fn get(&self, id: WidgetId) -> Option<&WidgetNode> {
self.index_of(id).map(|i| &self.nodes[i])
}
pub fn get_mut(&mut self, id: WidgetId) -> Option<&mut WidgetNode> {
self.index_of(id).map(move |i| &mut self.nodes[i])
}
pub fn insert(&mut self, parent: WidgetId, rect: Rect) -> Option<WidgetId> {
self.index_of(parent)?;
let id = self.alloc.alloc();
let new_idx = self.nodes.len();
let mut node = WidgetNode::new(id, rect);
node.parent = Some(parent);
self.nodes.push(node);
self.index.insert(id, new_idx);
if let Some(pi) = self.index_of(parent) {
self.nodes[pi].children.push(id);
}
Some(id)
}
pub fn remove(&mut self, id: WidgetId) -> usize {
if id == WidgetId::ROOT {
return 0;
}
let mut to_remove = Vec::new();
let mut stack = vec![id];
while let Some(cur) = stack.pop() {
if let Some(node) = self.get(cur) {
stack.extend(node.children.iter().copied());
to_remove.push(cur);
}
}
if let Some(parent) = self.get(id).and_then(|n| n.parent) {
if let Some(pi) = self.index_of(parent) {
self.nodes[pi].children.retain(|&c| c != id);
}
}
let removed = to_remove.len();
self.nodes.retain(|n| !to_remove.contains(&n.id));
for rid in &to_remove {
self.index.remove(rid);
}
for (slot, node) in self.nodes.iter().enumerate() {
self.index.insert(node.id, slot);
}
removed
}
pub fn reparent(&mut self, id: WidgetId, new_parent: WidgetId) -> bool {
if id == WidgetId::ROOT || self.get(id).is_none() || self.get(new_parent).is_none() {
return false;
}
if self.is_descendant(new_parent, id) {
return false;
}
let old_parent = self.get(id).and_then(|n| n.parent);
if let Some(op) = old_parent {
if let Some(oi) = self.index_of(op) {
self.nodes[oi].children.retain(|&c| c != id);
}
}
if let Some(ni) = self.index_of(new_parent) {
self.nodes[ni].children.push(id);
}
if let Some(i) = self.index_of(id) {
self.nodes[i].parent = Some(new_parent);
}
true
}
pub fn is_descendant(&self, maybe_descendant: WidgetId, ancestor: WidgetId) -> bool {
let mut cur = maybe_descendant;
while let Some(node) = self.get(cur) {
match node.parent {
Some(p) if p == ancestor => return true,
Some(p) => cur = p,
None => return false,
}
}
false
}
pub fn depth(&self, id: WidgetId) -> Option<usize> {
let mut depth = 0;
let mut cur = self.get(id)?;
while let Some(parent) = cur.parent {
depth += 1;
cur = self.get(parent)?;
}
Some(depth)
}
pub fn walk_dfs(&self, mut visit: impl FnMut(&WidgetNode, usize)) {
let mut stack = vec![(WidgetId::ROOT, 0usize)];
while let Some((id, depth)) = stack.pop() {
if let Some(node) = self.get(id) {
visit(node, depth);
for &child in node.children.iter().rev() {
stack.push((child, depth + 1));
}
}
}
}
pub fn effective_clip(&self, id: WidgetId) -> Option<Rect> {
let mut acc: Option<Rect> = None;
let mut cur = self.get(id);
while let Some(node) = cur {
if let Some(clip) = node.clip_rect {
acc = Some(match acc {
None => clip,
Some(existing) => existing.intersection(&clip).unwrap_or(Rect::ZERO),
});
}
cur = node.parent.and_then(|p| self.get(p));
}
acc
}
pub fn hit_test(&self, point: Point) -> Option<WidgetId> {
let mut best: Option<(WidgetId, usize, i32)> = None;
self.walk_dfs(|node, depth| {
if !node.hit_testable || !node.rect.contains(point) {
return;
}
if let Some(clip) = self.effective_clip(node.id) {
if !clip.contains(point) {
return;
}
}
let key = (depth, node.z_index);
match best {
Some((_, bd, bz)) if (bd, bz) >= key => {}
_ => best = Some((node.id, depth, node.z_index)),
}
});
best.map(|(id, _, _)| id)
}
pub fn focus_order(&self) -> Vec<WidgetId> {
let mut order = Vec::new();
self.walk_dfs(|node, _| {
if node.focusable {
order.push(node.id);
}
});
order
}
pub fn paint_order(&self) -> Vec<WidgetId> {
let mut entries: Vec<(usize, i32, usize, WidgetId)> = Vec::new();
let mut seq = 0usize;
self.walk_dfs(|node, depth| {
entries.push((depth, node.z_index, seq, node.id));
seq += 1;
});
entries.sort_by_key(|&(depth, z, seq, _)| (depth, z, seq));
entries.into_iter().map(|(_, _, _, id)| id).collect()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::geometry::Rect;
fn sample_tree() -> WidgetTree {
let mut t = WidgetTree::new(Rect::new(0.0, 0.0, 200.0, 200.0));
let a = t
.insert(WidgetId::ROOT, Rect::new(0.0, 0.0, 100.0, 100.0))
.expect("root exists");
let _b = t
.insert(WidgetId::ROOT, Rect::new(100.0, 0.0, 100.0, 100.0))
.expect("root exists");
let _a1 = t
.insert(a, Rect::new(10.0, 10.0, 30.0, 30.0))
.expect("a exists");
t
}
#[test]
fn id_allocator_is_monotonic_and_unique() {
let mut alloc = WidgetIdAllocator::new();
let a = alloc.alloc();
let b = alloc.alloc();
assert_ne!(a, b);
assert_eq!(a, WidgetId(1));
assert_eq!(b, WidgetId(2));
assert_eq!(alloc.allocated(), 2);
}
#[test]
fn insert_and_structure() {
let t = sample_tree();
assert_eq!(t.len(), 4); let root = t.get(WidgetId::ROOT).expect("root");
assert_eq!(root.children.len(), 2);
assert_eq!(t.depth(WidgetId(3)), Some(2)); }
#[test]
fn remove_subtree() {
let mut t = sample_tree();
let removed = t.remove(WidgetId(1)); assert_eq!(removed, 2);
assert_eq!(t.len(), 2); assert!(t.get(WidgetId(1)).is_none());
assert!(t.get(WidgetId(3)).is_none());
assert_eq!(t.remove(WidgetId::ROOT), 0);
}
#[test]
fn reparent_rejects_cycles() {
let mut t = sample_tree();
assert!(!t.reparent(WidgetId(1), WidgetId(3)));
assert!(t.reparent(WidgetId(3), WidgetId(2)));
assert_eq!(t.depth(WidgetId(3)), Some(2));
assert_eq!(t.get(WidgetId(3)).and_then(|n| n.parent), Some(WidgetId(2)));
}
#[test]
fn hit_test_front_most_wins() {
let mut t = WidgetTree::new(Rect::new(0.0, 0.0, 100.0, 100.0));
let back = t
.insert(WidgetId::ROOT, Rect::new(0.0, 0.0, 50.0, 50.0))
.expect("root");
let front = t
.insert(back, Rect::new(10.0, 10.0, 20.0, 20.0))
.expect("back");
let hit = t.hit_test(Point::new(15.0, 15.0));
assert_eq!(hit, Some(front));
assert_eq!(t.hit_test(Point::new(45.0, 45.0)), Some(back));
assert_eq!(t.hit_test(Point::new(90.0, 90.0)), Some(WidgetId::ROOT));
}
#[test]
fn hit_test_skips_non_hit_testable() {
let mut t = WidgetTree::new(Rect::new(0.0, 0.0, 100.0, 100.0));
let overlay = t
.insert(WidgetId::ROOT, Rect::new(0.0, 0.0, 100.0, 100.0))
.expect("root");
if let Some(n) = t.get_mut(overlay) {
n.hit_testable = false; }
assert_eq!(t.hit_test(Point::new(50.0, 50.0)), Some(WidgetId::ROOT));
}
#[test]
fn focus_order_dfs() {
let mut t = WidgetTree::new(Rect::ZERO);
let a = t.insert(WidgetId::ROOT, Rect::ZERO).expect("root");
let b = t.insert(WidgetId::ROOT, Rect::ZERO).expect("root");
for id in [a, b] {
if let Some(n) = t.get_mut(id) {
n.focusable = true;
}
}
assert_eq!(t.focus_order(), vec![a, b]);
}
#[test]
fn hit_test_respects_clip_rect() {
let mut t = WidgetTree::new(Rect::new(0.0, 0.0, 100.0, 100.0));
let container = t
.insert(WidgetId::ROOT, Rect::new(0.0, 0.0, 100.0, 100.0))
.expect("root");
if let Some(n) = t.get_mut(container) {
n.clip_rect = Some(Rect::new(0.0, 0.0, 50.0, 100.0));
}
let child = t
.insert(container, Rect::new(40.0, 0.0, 40.0, 20.0))
.expect("container");
assert_eq!(t.hit_test(Point::new(45.0, 5.0)), Some(child));
assert_eq!(t.hit_test(Point::new(70.0, 5.0)), Some(WidgetId::ROOT));
}
#[test]
fn effective_clip_intersects_ancestors() {
let mut t = WidgetTree::new(Rect::new(0.0, 0.0, 100.0, 100.0));
let outer = t
.insert(WidgetId::ROOT, Rect::new(0.0, 0.0, 100.0, 100.0))
.expect("root");
if let Some(n) = t.get_mut(outer) {
n.clip_rect = Some(Rect::new(0.0, 0.0, 60.0, 100.0));
}
let inner = t
.insert(outer, Rect::new(0.0, 0.0, 100.0, 100.0))
.expect("outer");
if let Some(n) = t.get_mut(inner) {
n.clip_rect = Some(Rect::new(40.0, 0.0, 60.0, 100.0));
}
assert_eq!(
t.effective_clip(inner),
Some(Rect::new(40.0, 0.0, 20.0, 100.0))
);
assert_eq!(t.effective_clip(WidgetId::ROOT), None);
}
#[test]
fn paint_order_back_to_front() {
let mut t = WidgetTree::new(Rect::ZERO);
let a = t.insert(WidgetId::ROOT, Rect::ZERO).expect("root");
let b = t.insert(WidgetId::ROOT, Rect::ZERO).expect("root");
let a1 = t.insert(a, Rect::ZERO).expect("a"); if let Some(n) = t.get_mut(a) {
n.z_index = 5;
}
if let Some(n) = t.get_mut(b) {
n.z_index = 1;
}
let order = t.paint_order();
assert_eq!(order[0], WidgetId::ROOT);
let pos_a = order.iter().position(|&x| x == a).expect("a present");
let pos_b = order.iter().position(|&x| x == b).expect("b present");
let pos_a1 = order.iter().position(|&x| x == a1).expect("a1 present");
assert!(pos_b < pos_a, "lower z_index paints first");
assert!(pos_a1 > pos_a && pos_a1 > pos_b);
}
}