mod builder;
mod cache;
mod node;
mod retained;
pub use builder::{NodeOptions, WidgetTreeBuilder};
pub use cache::{NodeFrameState, NodeInteraction, NodeLayout, WidgetTreeCache};
pub use node::{NodeId, Policy, WidgetTree, WidgetTreeNode};
pub use retained::{widget_handle, WidgetHandle};
pub(crate) use node::WidgetTreeNodeKind;
pub(crate) use retained::{erased_widget_state, TreeCustomRender, WidgetStateHandleDyn};
#[cfg(test)]
mod tests {
use crate::{AtlasHandle, AtlasSource, Button, CharEntry, Container, ContainerHandle, FontEntry, Input, Recti, SizePolicy, SourceFormat, Style, Vec2i};
use std::{cell::RefCell, rc::Rc};
use super::*;
const ICON_NAMES: [&str; 6] = ["white", "close", "expand", "collapse", "check", "expand_down"];
fn make_test_atlas() -> AtlasHandle {
let pixels: [u8; 4] = [0xFF, 0xFF, 0xFF, 0xFF];
let icons: Vec<(&str, Recti)> = ICON_NAMES.iter().map(|name| (*name, Recti::new(0, 0, 1, 1))).collect();
let entries = vec![
(
'_',
CharEntry {
offset: Vec2i::new(0, 0),
advance: Vec2i::new(8, 0),
rect: Recti::new(0, 0, 1, 1),
},
),
(
'a',
CharEntry {
offset: Vec2i::new(0, 0),
advance: Vec2i::new(8, 0),
rect: Recti::new(0, 0, 1, 1),
},
),
(
'b',
CharEntry {
offset: Vec2i::new(0, 0),
advance: Vec2i::new(8, 0),
rect: Recti::new(0, 0, 1, 1),
},
),
];
let fonts = vec![(
"default",
FontEntry {
line_size: 10,
baseline: 8,
font_size: 10,
entries: &entries,
},
)];
let source = AtlasSource {
width: 1,
height: 1,
pixels: &pixels,
icons: &icons,
fonts: &fonts,
format: SourceFormat::Raw,
slots: &[],
};
AtlasHandle::from(&source)
}
#[test]
fn unkeyed_widget_ids_are_stable_for_same_shape() {
let button_a = widget_handle(Button::new("A"));
let button_b = widget_handle(Button::new("B"));
let tree_a = WidgetTreeBuilder::build(|builder| {
builder.widget(button_a.clone());
builder.widget(button_b.clone());
});
let tree_a_ids: Vec<NodeId> = tree_a.roots().iter().map(WidgetTreeNode::id).collect();
let tree_b = WidgetTreeBuilder::build(|builder| {
builder.widget(button_a.clone());
builder.widget(button_b.clone());
});
let tree_b_ids: Vec<NodeId> = tree_b.roots().iter().map(WidgetTreeNode::id).collect();
assert_eq!(tree_a_ids[0], tree_b_ids[0]);
assert_eq!(tree_a_ids[1], tree_b_ids[1]);
}
#[test]
fn keyed_widgets_keep_ids_across_reorder() {
let button_a = widget_handle(Button::new("A"));
let button_b = widget_handle(Button::new("B"));
let tree_a = WidgetTreeBuilder::build(|builder| {
builder.widget_with(NodeOptions::keyed("a"), button_a.clone());
builder.widget_with(NodeOptions::keyed("b"), button_b.clone());
});
let ids_a: Vec<NodeId> = tree_a.roots().iter().map(WidgetTreeNode::id).collect();
let tree_b = WidgetTreeBuilder::build(|builder| {
builder.widget_with(NodeOptions::keyed("b"), button_b.clone());
builder.widget_with(NodeOptions::keyed("a"), button_a.clone());
});
let ids_b: Vec<NodeId> = tree_b.roots().iter().map(WidgetTreeNode::id).collect();
assert_eq!(ids_a[0], ids_b[1]);
assert_eq!(ids_a[1], ids_b[0]);
}
#[test]
fn row_nodes_capture_children_and_track_policy() {
let button_a = widget_handle(Button::new("A"));
let button_b = widget_handle(Button::new("B"));
let tree = WidgetTreeBuilder::build(|builder| {
builder.row_with(
NodeOptions::with_policy(Policy::fill()),
&[SizePolicy::Fixed(40), SizePolicy::Remainder(0)],
SizePolicy::Fixed(24),
|builder| {
builder.widget(button_a.clone());
builder.widget(button_b.clone());
},
);
});
let row = &tree.roots()[0];
assert_eq!(row.policy(), Policy::fill());
assert_eq!(row.children().len(), 2);
match row.kind() {
WidgetTreeNodeKind::Row { widths, height } => {
assert_eq!(widths, &[SizePolicy::Fixed(40), SizePolicy::Remainder(0)]);
assert_eq!(*height, SizePolicy::Fixed(24));
}
_ => panic!("expected row node"),
}
}
#[test]
fn container_nodes_store_handle_and_children() {
let atlas = make_test_atlas();
let input = Rc::new(RefCell::new(Input::default()));
let handle = ContainerHandle::new(Container::new("panel", atlas, Rc::new(Style::default()), input));
let leaf = widget_handle((crate::WidgetOption::NONE, crate::WidgetBehaviourOption::NONE));
let tree = WidgetTreeBuilder::build(|builder| {
builder.container_with(
NodeOptions::with_policy(Policy::fill()),
handle.clone(),
crate::ContainerOption::NONE,
crate::WidgetBehaviourOption::NONE,
|builder| {
builder.widget(leaf.clone());
},
);
});
let node = &tree.roots()[0];
assert_eq!(node.children().len(), 1);
match node.kind() {
WidgetTreeNodeKind::Container { .. } => {}
_ => panic!("expected container node"),
}
}
#[test]
fn text_nodes_are_recorded_as_widgets() {
let tree = WidgetTreeBuilder::build(|builder| {
builder.text("hello");
});
assert_eq!(tree.roots().len(), 1);
match tree.roots()[0].kind() {
WidgetTreeNodeKind::Widget { .. } => {}
_ => panic!("expected retained text widget node"),
}
}
}