use std::borrow::Cow;
use std::collections::{HashMap, HashSet};
use kurbo::{Point, Rect, Size};
use tree_arena::{ArenaMut, ArenaRef, TreeArena};
use crate::view::{ChangeFlags, WidgetId};
use crate::widget::{ChildPod, Widget};
pub struct WidgetPod {
id: WidgetId,
widget: Box<dyn Widget>,
origin: Point,
size: Size,
flags: ChangeFlags,
type_name: &'static str,
debug_label: Option<Cow<'static, str>>,
}
impl WidgetPod {
pub const ERASED_TYPE_NAME: &'static str = "<erased dyn Widget>";
pub fn new(id: WidgetId, widget: Box<dyn Widget>) -> Self {
Self::with_type_name(id, widget, Self::ERASED_TYPE_NAME)
}
pub fn new_typed<W: Widget>(id: WidgetId, widget: W) -> Self {
Self::with_type_name(id, Box::new(widget), core::any::type_name::<W>())
}
fn with_type_name(id: WidgetId, widget: Box<dyn Widget>, type_name: &'static str) -> Self {
Self {
id,
widget,
origin: Point::ZERO,
size: Size::ZERO,
flags: ChangeFlags::LAYOUT | ChangeFlags::PAINT,
type_name,
debug_label: None,
}
}
pub fn id(&self) -> WidgetId {
self.id
}
pub fn type_name(&self) -> &'static str {
self.type_name
}
pub fn debug_label(&self) -> Option<&str> {
self.debug_label.as_deref()
}
pub fn set_debug_label(&mut self, label: impl Into<Cow<'static, str>>) {
self.debug_label = Some(label.into());
}
pub fn clear_debug_label(&mut self) {
self.debug_label = None;
}
pub fn widget(&self) -> &dyn Widget {
&*self.widget
}
pub fn widget_mut(&mut self) -> &mut dyn Widget {
&mut *self.widget
}
pub fn origin(&self) -> Point {
self.origin
}
pub fn size(&self) -> Size {
self.size
}
pub fn set_layout(&mut self, origin: Point, size: Size) {
self.origin = origin;
self.size = size;
}
pub fn flags(&self) -> ChangeFlags {
self.flags
}
pub fn merge_flags(&mut self, flags: ChangeFlags) {
self.flags |= flags;
}
pub fn clear_flags(&mut self) {
self.flags = ChangeFlags::NONE;
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct InspectNode {
pub id: WidgetId,
pub parent: Option<WidgetId>,
pub type_name: &'static str,
pub debug_label: Option<Cow<'static, str>>,
pub bounds: Rect,
pub children: Vec<WidgetId>,
pub depth: usize,
}
pub struct WidgetTree {
arena: TreeArena<WidgetPod>,
root_order: Vec<WidgetId>,
child_order: HashMap<WidgetId, Vec<WidgetId>>,
}
impl WidgetTree {
pub fn new() -> Self {
Self {
arena: TreeArena::new(),
root_order: Vec::new(),
child_order: HashMap::new(),
}
}
pub fn insert_root(&mut self, pod: WidgetPod) -> WidgetId {
let id = pod.id();
self.arena.roots_mut().insert(id.0, pod);
self.root_order.push(id);
id
}
pub fn insert_child(&mut self, parent: WidgetId, pod: WidgetPod) -> Option<WidgetId> {
let id = pod.id();
let mut parent_mut: ArenaMut<'_, WidgetPod> = self.arena.find_mut(parent.0)?;
parent_mut.children.insert(id.0, pod);
self.child_order.entry(parent).or_default().push(id);
Some(id)
}
pub fn pod(&self, id: WidgetId) -> Option<&WidgetPod> {
self.arena.find(id.0).map(|node| node.item)
}
pub fn pod_mut(&mut self, id: WidgetId) -> Option<&mut WidgetPod> {
self.arena.find_mut(id.0).map(|node| node.item)
}
pub fn roots(&self) -> Vec<WidgetId> {
let live: HashSet<u64> = self.arena.root_ids().collect();
order_against(&self.root_order, &live)
}
pub fn children(&self, id: WidgetId) -> Vec<WidgetId> {
match self.arena.find(id.0) {
Some(node) => self.ordered_children(id, node),
None => Vec::new(),
}
}
pub fn inspect(&self) -> Vec<InspectNode> {
let mut out = Vec::new();
let roots = self.arena.roots();
for id in self.roots() {
if let Some(node) = roots.into_item(id.0) {
self.inspect_node(node, id, None, Point::ZERO, 0, &mut out);
}
}
out
}
fn inspect_node(
&self,
node: ArenaRef<'_, WidgetPod>,
id: WidgetId,
parent: Option<WidgetId>,
parent_origin: Point,
depth: usize,
out: &mut Vec<InspectNode>,
) {
let pod = node.item;
let origin = parent_origin + pod.origin().to_vec2();
let arena_children = self.ordered_children(id, node);
let slot = out.len();
out.push(InspectNode {
id,
parent,
type_name: pod.type_name(),
debug_label: pod.debug_label.clone(),
bounds: Rect::from_origin_size(origin, pod.size()),
children: Vec::new(),
depth,
});
let mut children = Vec::with_capacity(arena_children.len());
for child_id in arena_children {
if let Some(child) = node.children.into_item(child_id.0) {
children.push(child_id);
self.inspect_node(child, child_id, Some(id), origin, depth + 1, out);
}
}
Self::inspect_pods(pod.widget(), id, origin, depth, &mut children, out);
out[slot].children = children;
}
fn inspect_pods(
widget: &dyn Widget,
parent: WidgetId,
parent_origin: Point,
parent_depth: usize,
children: &mut Vec<WidgetId>,
out: &mut Vec<InspectNode>,
) {
widget.visit_children(&mut |child| {
let child_id = child.inspect_id();
children.push(child_id);
Self::inspect_child(
child,
child_id,
parent,
parent_origin,
parent_depth + 1,
out,
);
});
}
fn inspect_child(
child: &ChildPod,
id: WidgetId,
parent: WidgetId,
parent_origin: Point,
depth: usize,
out: &mut Vec<InspectNode>,
) {
let origin = parent_origin + child.origin().to_vec2();
let slot = out.len();
out.push(InspectNode {
id,
parent: Some(parent),
type_name: child.type_name(),
debug_label: child.debug_label_cow(),
bounds: Rect::from_origin_size(origin, child.size()),
children: Vec::new(),
depth,
});
let mut grandchildren = Vec::new();
Self::inspect_pods(child.widget(), id, origin, depth, &mut grandchildren, out);
out[slot].children = grandchildren;
}
fn ordered_children(&self, id: WidgetId, node: ArenaRef<'_, WidgetPod>) -> Vec<WidgetId> {
let live: HashSet<u64> = node.child_ids().into_iter().collect();
let recorded = self.child_order.get(&id).map_or(&[][..], Vec::as_slice);
order_against(recorded, &live)
}
}
fn order_against(recorded: &[WidgetId], live: &HashSet<u64>) -> Vec<WidgetId> {
let mut ordered: Vec<WidgetId> = recorded
.iter()
.copied()
.filter(|id| live.contains(&id.0))
.collect();
if ordered.len() != live.len() {
let seen: HashSet<u64> = ordered.iter().map(|id| id.0).collect();
let mut unrecorded: Vec<u64> = live.difference(&seen).copied().collect();
unrecorded.sort_unstable();
ordered.extend(unrecorded.into_iter().map(WidgetId));
}
ordered
}
impl Default for WidgetTree {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::layout::BoxConstraints;
use crate::widget::{LayoutCtx, PaintCtx, PaintScene};
struct Leaf;
impl Widget for Leaf {
fn layout(&mut self, _ctx: &mut LayoutCtx, bc: &BoxConstraints) -> Size {
bc.max()
}
fn paint(&mut self, _ctx: &mut PaintCtx, _scene: &mut dyn PaintScene) {}
}
struct Container {
children: Vec<ChildPod>,
}
impl Container {
fn new(children: Vec<(Point, Size, Box<dyn Widget>)>) -> Self {
Self {
children: children
.into_iter()
.map(|(origin, size, widget)| {
let mut pod = ChildPod::new(widget);
pod.set_origin(origin);
pod.layout_child(&mut LayoutCtx::new(), &BoxConstraints::new(size, size));
pod
})
.collect(),
}
}
}
impl Widget for Container {
fn layout(&mut self, _ctx: &mut LayoutCtx, bc: &BoxConstraints) -> Size {
bc.max()
}
fn paint(&mut self, _ctx: &mut PaintCtx, _scene: &mut dyn PaintScene) {}
fn visit_children(&self, visitor: &mut dyn FnMut(&ChildPod)) {
for child in &self.children {
visitor(child);
}
}
}
fn pod(id: u64) -> WidgetPod {
WidgetPod::new(WidgetId(id), Box::new(Leaf))
}
#[test]
fn insert_root_and_find() {
let mut tree = WidgetTree::new();
let id = tree.insert_root(pod(1));
assert_eq!(id, WidgetId(1));
assert!(tree.pod(id).is_some());
assert!(tree.pod_mut(id).is_some());
assert!(tree.pod(WidgetId(999)).is_none());
}
#[test]
fn insert_child_traverses_arena() {
let mut tree = WidgetTree::new();
let root = tree.insert_root(pod(1));
let child = tree.insert_child(root, pod(2)).expect("child inserted");
assert_eq!(child, WidgetId(2));
assert!(tree.pod(child).is_some());
assert!(tree.insert_child(WidgetId(42), pod(3)).is_none());
}
#[test]
fn roots_and_children_follow_insertion_order() {
let mut tree = WidgetTree::new();
let root_b = tree.insert_root(pod(20));
let root_a = tree.insert_root(pod(10));
assert_eq!(tree.roots(), vec![root_b, root_a]);
let c2 = tree.insert_child(root_b, pod(22)).unwrap();
let c1 = tree.insert_child(root_b, pod(21)).unwrap();
let grandchild = tree.insert_child(c2, pod(30)).unwrap();
assert_eq!(tree.children(root_b), vec![c2, c1]);
assert_eq!(tree.children(c2), vec![grandchild]);
assert!(tree.children(c1).is_empty());
assert!(tree.children(WidgetId(999)).is_empty());
}
#[test]
fn pod_records_type_name_and_optional_label() {
let mut tree = WidgetTree::new();
let erased = tree.insert_root(WidgetPod::new(WidgetId(1), Box::new(Leaf)));
assert_eq!(
tree.pod(erased).unwrap().type_name(),
WidgetPod::ERASED_TYPE_NAME
);
let typed = tree
.insert_child(erased, WidgetPod::new_typed(WidgetId(2), Leaf))
.unwrap();
let pod = tree.pod_mut(typed).unwrap();
assert!(pod.type_name().ends_with("Leaf"), "{}", pod.type_name());
assert_eq!(pod.debug_label(), None);
pod.set_debug_label("the-leaf");
assert_eq!(pod.debug_label(), Some("the-leaf"));
pod.clear_debug_label();
assert_eq!(pod.debug_label(), None);
}
#[test]
fn inspect_walks_pre_order_with_absolute_bounds() {
let mut tree = WidgetTree::new();
let root = tree.insert_root(WidgetPod::new_typed(WidgetId(1), Leaf));
let a = tree
.insert_child(root, WidgetPod::new_typed(WidgetId(2), Leaf))
.unwrap();
let a_child = tree
.insert_child(a, WidgetPod::new_typed(WidgetId(3), Leaf))
.unwrap();
let b = tree
.insert_child(root, WidgetPod::new_typed(WidgetId(4), Leaf))
.unwrap();
tree.pod_mut(root)
.unwrap()
.set_layout(Point::ZERO, Size::new(100.0, 100.0));
tree.pod_mut(a)
.unwrap()
.set_layout(Point::new(10.0, 5.0), Size::new(50.0, 40.0));
tree.pod_mut(a_child)
.unwrap()
.set_layout(Point::new(2.0, 3.0), Size::new(10.0, 10.0));
tree.pod_mut(b)
.unwrap()
.set_layout(Point::new(0.0, 60.0), Size::new(20.0, 20.0));
tree.pod_mut(a).unwrap().set_debug_label("branch-a");
let nodes = tree.inspect();
let ids: Vec<WidgetId> = nodes.iter().map(|n| n.id).collect();
assert_eq!(ids, vec![root, a, a_child, b]);
assert_eq!(
nodes.iter().map(|n| n.depth).collect::<Vec<_>>(),
vec![0, 1, 2, 1]
);
assert_eq!(
nodes.iter().map(|n| n.parent).collect::<Vec<_>>(),
vec![None, Some(root), Some(a), Some(root)]
);
assert_eq!(nodes[0].children, vec![a, b]);
assert_eq!(nodes[2].children, Vec::new());
assert_eq!(nodes[1].bounds, Rect::new(10.0, 5.0, 60.0, 45.0));
assert_eq!(nodes[2].bounds, Rect::new(12.0, 8.0, 22.0, 18.0));
assert!(nodes[0].type_name.ends_with("Leaf"));
assert_eq!(nodes[1].debug_label.as_deref(), Some("branch-a"));
assert!(nodes[0].debug_label.is_none());
assert!(nodes[3].debug_label.is_none());
}
#[test]
fn inspect_of_an_unlaid_out_tree_reports_zero_rects() {
let mut tree = WidgetTree::new();
let root = tree.insert_root(WidgetPod::new_typed(WidgetId(1), Leaf));
tree.insert_child(root, WidgetPod::new_typed(WidgetId(2), Leaf))
.unwrap();
let nodes = tree.inspect();
assert_eq!(nodes.len(), 2);
assert!(nodes.iter().all(|n| n.bounds == Rect::ZERO));
}
#[test]
fn traversal_follows_the_arena_not_the_order_index() {
let mut tree = WidgetTree::new();
let root = tree.insert_root(pod(1));
let kept = tree.insert_child(root, pod(2)).unwrap();
tree.child_order
.get_mut(&root)
.unwrap()
.push(WidgetId(1234));
assert_eq!(tree.children(root), vec![kept]);
assert_eq!(tree.inspect().len(), 2);
tree.child_order.remove(&root);
assert_eq!(tree.children(root), vec![kept]);
tree.root_order.clear();
assert_eq!(tree.roots(), vec![root]);
}
#[test]
fn inspect_descends_through_child_pods() {
let inner = Container::new(vec![(
Point::new(1.0, 1.0),
Size::new(5.0, 5.0),
Box::new(Leaf),
)]);
let outer = Container::new(vec![
(Point::new(10.0, 5.0), Size::new(50.0, 40.0), Box::new(Leaf)),
(
Point::new(0.0, 60.0),
Size::new(20.0, 20.0),
Box::new(inner) as Box<dyn Widget>,
),
]);
let mut tree = WidgetTree::new();
let root = tree.insert_root(WidgetPod::new_typed(WidgetId(1), outer));
tree.pod_mut(root)
.unwrap()
.set_layout(Point::new(2.0, 3.0), Size::new(100.0, 100.0));
let nodes = tree.inspect();
assert_eq!(nodes.len(), 4, "root + two pods + one grandchild pod");
assert_eq!(
nodes.iter().map(|n| n.depth).collect::<Vec<_>>(),
vec![0, 1, 1, 2],
"a pod's depth continues from its owning node's"
);
assert_eq!(nodes[0].parent, None);
assert_eq!(nodes[1].parent, Some(nodes[0].id));
assert_eq!(nodes[2].parent, Some(nodes[0].id));
assert_eq!(nodes[3].parent, Some(nodes[2].id));
assert_eq!(nodes[0].children, vec![nodes[1].id, nodes[2].id]);
assert_eq!(nodes[2].children, vec![nodes[3].id]);
assert!(nodes[1].children.is_empty(), "a leaf publishes no children");
assert_eq!(nodes[0].bounds, Rect::new(2.0, 3.0, 102.0, 103.0));
assert_eq!(nodes[1].bounds, Rect::new(12.0, 8.0, 62.0, 48.0));
assert_eq!(nodes[2].bounds, Rect::new(2.0, 63.0, 22.0, 83.0));
assert_eq!(nodes[3].bounds, Rect::new(3.0, 64.0, 8.0, 69.0));
assert!(
nodes[0].type_name.ends_with("Container"),
"{}",
nodes[0].type_name
);
assert!(
nodes[1..]
.iter()
.all(|n| n.type_name.ends_with("Leaf") || n.type_name.ends_with("Container"))
);
let ids: HashSet<u64> = nodes.iter().map(|n| n.id.0).collect();
assert_eq!(ids.len(), nodes.len(), "ids are unique across a snapshot");
assert!(
nodes[1..]
.iter()
.all(|n| n.id.0 >= crate::widget::ChildPod::INSPECT_ID_BASE)
);
}
#[test]
fn a_pods_tooling_id_and_label_survive_the_next_walk() {
let mut tree = WidgetTree::new();
let root = tree.insert_root(WidgetPod::new_typed(
WidgetId(1),
Container::new(vec![(Point::ZERO, Size::new(4.0, 4.0), Box::new(Leaf))]),
));
let first = tree.inspect();
let second = tree.inspect();
assert_eq!(first[1].id, second[1].id);
let pod = tree.pod_mut(root).unwrap();
let container = pod
.widget_mut()
.downcast_mut::<Container>()
.expect("the root widget is the container");
container.children[0].set_debug_label("the-child");
assert_eq!(tree.inspect()[1].debug_label.as_deref(), Some("the-child"));
let container = tree
.pod_mut(root)
.unwrap()
.widget_mut()
.downcast_mut::<Container>()
.expect("the root widget is the container");
container.children[0].clear_debug_label();
assert_eq!(tree.inspect()[1].debug_label, None);
}
#[test]
fn a_widget_that_ignores_the_seam_reads_as_a_leaf() {
struct Opaque {
_child: ChildPod,
}
impl Widget for Opaque {
fn layout(&mut self, _ctx: &mut LayoutCtx, bc: &BoxConstraints) -> Size {
bc.max()
}
fn paint(&mut self, _ctx: &mut PaintCtx, _scene: &mut dyn PaintScene) {}
}
let mut tree = WidgetTree::new();
tree.insert_root(WidgetPod::new_typed(
WidgetId(1),
Opaque {
_child: ChildPod::new(Box::new(Leaf)),
},
));
assert_eq!(tree.inspect().len(), 1);
}
#[test]
fn pod_stores_layout_geometry() {
let mut tree = WidgetTree::new();
let id = tree.insert_root(pod(1));
let p = tree.pod_mut(id).unwrap();
assert!(p.flags().needs_layout());
p.set_layout(Point::new(2.0, 3.0), Size::new(10.0, 20.0));
p.clear_flags();
assert_eq!(p.origin(), Point::new(2.0, 3.0));
assert_eq!(p.size(), Size::new(10.0, 20.0));
assert!(p.flags().is_empty());
}
}