use std::cell::{RefCell, RefMut};
use std::rc::{Rc, Weak};
use crabshell_reactive::Signal;
use taffy::TaffyTree;
use crate::cx::{Anchor, Cx};
use crate::geometry::{Rect, Size};
use crate::input::{Handlers, Pointer};
use crate::keyed::KeyedSlot;
use crate::prop::{Prop, PropKind};
use crate::style::{Padding, Style};
use crate::text::TextNode;
use crate::widgets::{Align, Element, ElementChild, ElementKind, IconSource, Justify};
use crate::window::{Anchors, Layer, Output, PanelWindow, Window};
pub(crate) type NodeId = taffy::NodeId;
pub(crate) struct Tree {
pub(crate) this: Weak<RefCell<Tree>>,
pub(crate) taffy: TaffyTree<Node>,
pub(crate) root: NodeId,
pub(crate) output: Output,
pub(crate) surface: Surface,
pub(crate) size: Size,
pub(crate) needs_layout: bool,
pub(crate) damage: Vec<Rect>,
pub(crate) pointer: Pointer,
pub(crate) popups: Vec<Window>,
pub(crate) popup: Option<PopupState>,
}
pub(crate) struct PopupState {
pub(crate) opener: Rc<Anchor>,
pub(crate) close: bool,
pub(crate) alive: Signal<()>,
}
pub(crate) struct Node {
pub(crate) name: &'static str,
pub(crate) path: Rc<str>,
pub(crate) cx: Cx,
pub(crate) style: Style,
pub(crate) kind: NodeKind,
pub(crate) groups: Vec<Vec<NodeId>>,
pub(crate) next_child: usize,
pub(crate) rect: Option<Rect>,
pub(crate) content: Rect,
pub(crate) painted: Rect,
pub(crate) bounds: Rect,
pub(crate) drawn: Rect,
pub(crate) changed: bool,
}
pub(crate) enum NodeKind {
Root,
Flex(Flex),
Stack,
Spacer,
Text(TextNode),
Icon(IconNode),
Button(ButtonNode),
}
pub(crate) struct Flex {
pub(crate) column: bool,
pub(crate) grow: bool,
pub(crate) gap: u32,
pub(crate) padding_vertical: Option<u32>,
pub(crate) padding_horizontal: Option<u32>,
pub(crate) align: Align,
pub(crate) justify: Justify,
}
pub(crate) struct IconNode {
pub(crate) source: Option<IconSource>,
pub(crate) size: u32,
}
pub(crate) struct ButtonNode {
pub(crate) hover_style: Style,
pub(crate) pressed_style: Style,
pub(crate) handlers: Signal<Handlers>,
pub(crate) hovered: bool,
pub(crate) pressed: bool,
}
#[derive(Default)]
pub(crate) struct Surface {
pub(crate) anchors: Anchors,
pub(crate) width: u32,
pub(crate) height: u32,
pub(crate) exclusive_zone: Option<i32>,
pub(crate) layer: Layer,
pub(crate) changed: bool,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(crate) enum Change {
None,
Paint,
Layout,
}
pub(crate) fn borrow(tree: &RefCell<Tree>) -> RefMut<'_, Tree> {
tree
.try_borrow_mut()
.expect("invariant: a window's tree is not borrowed while effects or handlers run")
}
impl Node {
fn new(name: &'static str, path: Rc<str>, cx: Cx, kind: NodeKind) -> Node {
Node {
name,
path,
cx,
style: Style::new(),
kind,
groups: Vec::new(),
next_child: 0,
rect: None,
content: Rect::default(),
painted: Rect::default(),
bounds: Rect::default(),
drawn: Rect::default(),
changed: true,
}
}
pub(crate) fn effective_style(&self) -> Style {
match &self.kind {
NodeKind::Button(button) if button.hovered => {
let hovered = self.style.merge(button.hover_style);
if button.pressed {
hovered.merge(button.pressed_style)
} else {
hovered
}
}
_ => self.style,
}
}
pub(crate) fn draws(&self) -> bool {
let style = self.effective_style();
matches!(
self.kind,
NodeKind::Root | NodeKind::Text(_) | NodeKind::Icon(_)
) || style.background.is_some()
|| style.border.is_some()
}
pub(crate) fn padding(&self) -> Padding {
let style = self.effective_style().padding.unwrap_or_default();
match &self.kind {
NodeKind::Flex(flex) => Padding {
vertical: flex.padding_vertical.unwrap_or(style.vertical),
horizontal: flex.padding_horizontal.unwrap_or(style.horizontal),
},
_ => style,
}
}
}
impl Tree {
pub(crate) fn new(
this: Weak<RefCell<Tree>>,
name: &'static str,
output: Output,
cx: Cx,
path: Rc<str>,
) -> Tree {
let mut taffy = TaffyTree::new();
let anchor = cx.anchor.clone();
let root = taffy
.new_leaf_with_context(
taffy::Style::default(),
Node::new(name, path, cx, NodeKind::Root),
)
.expect("invariant: adding a leaf to taffy never fails");
if let Some(anchor) = anchor {
anchor.set(this.clone(), root);
}
Tree {
this,
taffy,
root,
output,
surface: Surface::default(),
size: Size::default(),
needs_layout: true,
damage: Vec::new(),
pointer: Pointer::default(),
popups: Vec::new(),
popup: None,
}
}
pub(crate) fn node(&self, id: NodeId) -> Option<&Node> {
self.taffy.get_node_context(id)
}
pub(crate) fn node_mut(&mut self, id: NodeId) -> Option<&mut Node> {
self.taffy.get_node_context_mut(id)
}
pub(crate) fn mount_panel(&mut self, panel: PanelWindow, child: Option<Element>) {
let root = self.root;
let PanelWindow {
output: _,
anchors,
width,
height,
exclusive_zone,
layer,
style,
child: _,
} = panel;
self.bind_opt(root, anchors, "anchors", |tree, _, value| {
tree.surface.set(|s| &mut s.anchors, value);
});
self.bind_opt(root, width, "width", |tree, _, value| {
tree.surface.set(|s| &mut s.width, value);
});
self.bind_opt(root, height, "height", |tree, _, value| {
tree.surface.set(|s| &mut s.height, value);
});
self.bind_opt(root, exclusive_zone, "exclusive_zone", |tree, _, value| {
tree.surface.set(|s| &mut s.exclusive_zone, Some(value));
});
self.bind_opt(root, layer, "layer", |tree, _, value| {
tree.surface.set(|s| &mut s.layer, value);
});
self.mount_root(style, child);
self.surface.changed = true;
}
pub(crate) fn mount_root(&mut self, style: Option<Prop<Style>>, child: Option<Element>) {
let root = self.root;
self.bind_opt(root, style, "style", set_style);
if let Some(child) = child {
let cx = self.node(root).map(|node| node.cx.clone());
let cx = cx.expect("invariant: the root node exists while the tree does");
let child = self.mount(child, &cx, root);
self.set_group(root, 0, vec![child]);
}
}
pub(crate) fn mount(&mut self, element: Element, parent_cx: &Cx, parent: NodeId) -> NodeId {
let Element {
name,
style,
mut kind,
children,
} = element;
let index = self.next_index(parent);
let parent_path = self.node(parent).map_or("", |node| &node.path);
let path: Rc<str> = match index {
Some(index) => format!("{parent_path}/{name}[{index}]"),
None => format!("{parent_path}/{name}"),
}
.into();
let anchor = Rc::new(Anchor::default());
let cx = parent_cx
.child()
.named(path.to_string())
.at(Rc::clone(&anchor));
let node_kind = match &mut kind {
ElementKind::Flex(props) => NodeKind::Flex(Flex {
column: props.column,
grow: props.grow,
gap: 0,
padding_vertical: None,
padding_horizontal: None,
align: Align::default(),
justify: Justify::default(),
}),
ElementKind::Stack => NodeKind::Stack,
ElementKind::Spacer => NodeKind::Spacer,
ElementKind::Text(_) => NodeKind::Text(TextNode::new()),
ElementKind::Icon(_) => NodeKind::Icon(IconNode {
source: None,
size: crate::Icon::DEFAULT_SIZE,
}),
ElementKind::Button(props) => NodeKind::Button(ButtonNode {
hover_style: Style::new(),
pressed_style: Style::new(),
handlers: cx.signal(std::mem::take(&mut props.handlers)),
hovered: false,
pressed: false,
}),
};
let id = self
.taffy
.new_leaf_with_context(
taffy::Style::default(),
Node::new(name, Rc::clone(&path), cx.clone(), node_kind),
)
.expect("invariant: adding a leaf to taffy never fails");
anchor.set(self.this.clone(), id);
self.bind_opt(id, style, "style", set_style);
self.bind_kind(id, kind);
self.mount_children(id, children, &cx, &path);
id
}
fn bind_kind(&mut self, id: NodeId, kind: ElementKind) {
match kind {
ElementKind::Flex(props) => {
self.bind_opt(id, props.gap, "gap", |tree, id, value| {
tree.update_flex(id, |flex| replace(&mut flex.gap, value))
});
self.bind_opt(id, props.padding_vertical, "padding", |tree, id, value| {
tree.update_flex(id, |flex| replace(&mut flex.padding_vertical, Some(value)))
});
self.bind_opt(
id,
props.padding_horizontal,
"padding",
|tree, id, value| {
tree.update_flex(id, |flex| {
replace(&mut flex.padding_horizontal, Some(value))
})
},
);
self.bind_opt(id, props.align, "align", |tree, id, value| {
tree.update_flex(id, |flex| replace(&mut flex.align, value))
});
self.bind_opt(id, props.justify, "justify", |tree, id, value| {
tree.update_flex(id, |flex| replace(&mut flex.justify, value))
});
}
ElementKind::Text(props) => {
self.bind(id, props.text, "text", |tree, id, value| {
tree.update_text(id, |text| text.set_content(value))
});
self.bind_opt(id, props.ellipsis, "ellipsis", |tree, id, value| {
tree.update_text(id, |text| text.set_ellipsis(value))
});
self.bind_opt(id, props.max_lines, "max_lines", |tree, id, value| {
tree.update_text(id, |text| text.set_max_lines(value))
});
}
ElementKind::Icon(props) => {
self.bind(id, props.source, "icon", |tree, id, value| {
tree.update_icon(id, |icon| match replace(&mut icon.source, Some(value)) {
Change::None => Change::None,
_ => Change::Paint,
})
});
self.bind_opt(id, props.size, "size", |tree, id, value| {
tree.update_icon(id, |icon| replace(&mut icon.size, value))
});
}
ElementKind::Button(props) => {
self.bind_opt(id, props.hover_style, "hover_style", |tree, id, value| {
tree.update_button_style(id, |button| button.hover_style = value);
});
self.bind_opt(
id,
props.pressed_style,
"pressed_style",
|tree, id, value| {
tree.update_button_style(id, |button| button.pressed_style = value);
},
);
}
ElementKind::Stack | ElementKind::Spacer => {}
}
}
fn mount_children(&mut self, id: NodeId, children: Vec<ElementChild>, cx: &Cx, path: &Rc<str>) {
for child in children {
let Some(node) = self.node(id) else {
return;
};
let group = node.groups.len();
match child {
ElementChild::One(element) => {
let child = self.mount(*element, cx, id);
self.set_group(id, group, vec![child]);
}
ElementChild::Keyed(mount) => {
self.set_group(id, group, Vec::new());
mount(KeyedSlot {
tree: self.this.clone(),
container: id,
group,
cx: cx.clone(),
path: Rc::clone(path),
});
}
}
}
}
fn next_index(&mut self, parent: NodeId) -> Option<usize> {
let node = self.node_mut(parent)?;
if !matches!(node.kind, NodeKind::Flex(_) | NodeKind::Stack) {
return None;
}
let index = node.next_child;
node.next_child += 1;
Some(index)
}
pub(crate) fn set_group(&mut self, parent: NodeId, group: usize, nodes: Vec<NodeId>) {
let Some(node) = self.node_mut(parent) else {
return;
};
if group == node.groups.len() {
node.groups.push(nodes);
} else if let Some(slot) = node.groups.get_mut(group) {
*slot = nodes;
}
let children: Vec<NodeId> = node.groups.iter().flatten().copied().collect();
self
.taffy
.set_children(parent, &children)
.expect("invariant: every node in a child group is in the tree");
self.needs_layout = true;
}
pub(crate) fn unmount(&mut self, id: NodeId) {
if let Some(node) = self.taffy.get_node_context(id)
&& node.rect.is_some()
{
add_damage(&mut self.damage, node.bounds);
}
let mut removed = Vec::new();
let mut pending = vec![id];
while let Some(next) = pending.pop() {
removed.push(next);
if let Ok(children) = self.taffy.children(next) {
pending.extend(children);
}
}
self.pointer.forget(&removed);
for node in removed.into_iter().rev() {
let _ = self.taffy.remove(node);
}
self.needs_layout = true;
}
pub(crate) fn bind<T: 'static>(
&mut self,
id: NodeId,
prop: Prop<T>,
name: &str,
apply: fn(&mut Tree, NodeId, T),
) {
match prop.0 {
PropKind::Value(value) => apply(self, id, value),
PropKind::Binding(mut compute) => {
let Some(node) = self.node(id) else {
return;
};
let label = format!("{}.{name}", node.path);
let tree = self.this.clone();
node
.cx
.effect(move || {
let value = compute();
if let Some(tree) = tree.upgrade() {
apply(&mut borrow(&tree), id, value);
}
})
.named(label);
}
}
}
fn bind_opt<T: 'static>(
&mut self,
id: NodeId,
prop: Option<Prop<T>>,
name: &str,
apply: fn(&mut Tree, NodeId, T),
) {
if let Some(prop) = prop {
self.bind(id, prop, name, apply);
}
}
pub(crate) fn update(&mut self, id: NodeId, f: impl FnOnce(&mut Node) -> Change) -> Change {
let Some(node) = self.node_mut(id) else {
return Change::None;
};
let change = f(node);
self.invalidate(id, change);
change
}
pub(crate) fn update_style(&mut self, id: NodeId, f: impl FnOnce(&mut Node)) -> Change {
self.update(id, |node| {
let before = node.effective_style();
f(node);
let after = node.effective_style();
if before == after {
Change::None
} else if before.same_layout(&after) {
Change::Paint
} else {
Change::Layout
}
})
}
fn update_flex(&mut self, id: NodeId, f: impl FnOnce(&mut Flex) -> Change) {
self.update(id, |node| match &mut node.kind {
NodeKind::Flex(flex) => f(flex),
_ => Change::None,
});
}
fn update_text(&mut self, id: NodeId, f: impl FnOnce(&mut TextNode) -> bool) {
self.update(id, |node| match &mut node.kind {
NodeKind::Text(text) => {
if f(text) {
Change::Layout
} else {
Change::None
}
}
_ => Change::None,
});
}
fn update_icon(&mut self, id: NodeId, f: impl FnOnce(&mut IconNode) -> Change) {
self.update(id, |node| match &mut node.kind {
NodeKind::Icon(icon) => f(icon),
_ => Change::None,
});
}
pub(crate) fn update_button_style(
&mut self,
id: NodeId,
f: impl FnOnce(&mut ButtonNode),
) -> Change {
self.update_style(id, |node| {
if let NodeKind::Button(button) = &mut node.kind {
f(button);
}
})
}
pub(crate) fn invalidate(&mut self, id: NodeId, change: Change) {
match change {
Change::None => {}
Change::Paint => {
if let Some(node) = self.taffy.get_node_context_mut(id)
&& node.rect.is_some()
{
add_damage(&mut self.damage, node.bounds);
node.drawn = node.painted;
}
}
Change::Layout => {
if let Some(node) = self.node_mut(id) {
node.changed = true;
}
self.needs_layout = true;
}
}
}
}
impl Surface {
fn set<T: PartialEq>(&mut self, field: impl FnOnce(&mut Surface) -> &mut T, value: T) {
let slot = field(self);
if *slot != value {
*slot = value;
self.changed = true;
}
}
}
fn set_style(tree: &mut Tree, id: NodeId, style: Style) {
tree.update_style(id, |node| node.style = style);
}
pub(crate) fn icon_rect(content: Rect, size: u32) -> Rect {
let side = size as f32;
Rect::new(
content.x + ((content.width - side) / 2.0).round(),
content.y + ((content.height - side) / 2.0).round(),
side,
side,
)
}
pub(crate) fn add_damage(damage: &mut Vec<Rect>, rect: Rect) {
if !rect.is_empty() && !damage.contains(&rect) {
damage.push(rect);
}
}
pub(crate) fn replace<T: PartialEq>(slot: &mut T, value: T) -> Change {
if *slot == value {
Change::None
} else {
*slot = value;
Change::Layout
}
}