1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
//! Tree storage: one node per widget, addressed by a generational key.
use alloc::vec::Vec;
use denise::Rect;
use crate::anchor::{Anchors, Dock};
use crate::widget::{BoxedWidget, VisualState};
slotmap::new_key_type! {
/// Identifies a node for exactly as long as that node exists.
///
/// The generation in the key is the point: an application that keeps an id
/// after removing the node gets `None` back, not somebody else's widget. That
/// is also why the tree stores ids rather than references — parent-linked
/// component graphs are what forced `Rc<RefCell<_>>` on CoreCanvas, and this
/// is the replacement.
pub struct NodeId;
}
impl NodeId {
/// The key as a plain `u64`, for carrying across the C ABI in M5.
#[inline]
pub fn as_ffi(self) -> u64 {
use slotmap::Key as _;
self.data().as_ffi()
}
/// Rebuilds a key from [`NodeId::as_ffi`]. A value that never came from there
/// simply fails to resolve.
#[inline]
pub fn from_ffi(value: u64) -> Self {
use slotmap::KeyData;
NodeId::from(KeyData::from_ffi(value))
}
}
pub(crate) struct Node<M> {
pub(crate) widget: BoxedWidget<M>,
/// Position and extent relative to the parent's origin.
pub(crate) layout: Rect,
/// Absolute bounds, recomputed when the node or an ancestor moves.
pub(crate) bounds: Rect,
/// [`Node::bounds`] intersected with every ancestor's bounds: what the widget
/// may actually paint into, and what damage from this node covers.
pub(crate) clip: Rect,
/// Sort key among siblings. Ties keep insertion order.
pub(crate) z: i32,
pub(crate) visible: bool,
pub(crate) enabled: bool,
pub(crate) parent: Option<NodeId>,
pub(crate) children: Vec<NodeId>,
/// Which scene this node belongs to, as an index into the stack.
pub(crate) scene: usize,
pub(crate) state: VisualState,
/// Whether the tree may scroll this node's content on wheel, page keys and
/// reveal requests. Explicit rather than inferred from overflowing content,
/// so a panel with a decoratively clipped child does not start moving under
/// the wheel.
pub(crate) scrollable: bool,
/// Text shown after the pointer rests on this node. See
/// [`Ui::set_tooltip`](crate::Ui::set_tooltip) — the tree owns everything
/// about a tooltip except the string.
pub(crate) tooltip: Option<alloc::string::String>,
/// How far this node's content is scrolled: children are shifted up and
/// left by this much. Applied in `reflow`, which is the single place that
/// turns layouts into absolute bounds — so paint, clip and hit testing
/// cannot disagree about where a scrolled child is.
pub(crate) scroll: denise::Point,
/// `Some(spacing)` makes this node a vertical stack: its visible children
/// are placed top-to-bottom in order, each at the running y. Applied in
/// `reflow`, like scrolling and for the same reason — one place turns
/// layouts into bounds, so nothing can disagree about where a moved
/// sibling is. See [`Ui::set_stack`](crate::Ui::set_stack).
pub(crate) stack: Option<i32>,
/// Which of the parent's edges this node keeps its distance from.
///
/// [`Anchors::TOP_LEFT`] by default, which derives the rectangle the node
/// already had — so a tree that never mentions anchoring behaves exactly as
/// it did before anchoring existed.
pub(crate) anchors: Anchors,
/// An edge of the parent this node takes entirely, if any. Applied in
/// `reflow`, before the anchored children and before any stack, so a docked
/// bar shrinks the box the rest are placed in.
pub(crate) dock: Option<Dock>,
/// The size of the box this node was placed in when its layout was last set,
/// against which [`Node::anchors`] measures the parent's growth.
///
/// `None` until the first reflow places it, and captured there rather than
/// at insertion, because the box a child is placed in is not knowable until
/// its docked siblings have taken their edges. Reset by
/// [`Ui::set_layout`](crate::Ui::set_layout) — a new layout is a new design,
/// stated against whatever the parent is now — but *not* by a layout tween,
/// which would otherwise re-baseline on every frame and stand still.
pub(crate) anchor_base: Option<denise::Size>,
}
impl<M> Node<M> {
pub(crate) fn new(widget: BoxedWidget<M>, layout: Rect, scene: usize) -> Self {
Self {
widget,
layout,
bounds: layout,
clip: layout,
z: 0,
visible: true,
enabled: true,
parent: None,
children: Vec::new(),
scene,
state: VisualState::NONE,
scrollable: false,
tooltip: None,
scroll: denise::Point::ZERO,
stack: None,
anchors: Anchors::TOP_LEFT,
dock: None,
anchor_base: None,
}
}
/// Returns `true` if this node can be painted at all.
#[inline]
pub(crate) fn paintable(&self) -> bool {
self.visible && !self.clip.is_empty()
}
}
/// One layer of the scene stack.
///
/// The stack is how CoreCanvas did dialogs and it is how Denise does them: a modal
/// is not a widget inside the page, it is a scene pushed on top of it.
#[derive(Clone, Copy, Debug)]
pub(crate) struct Scene {
pub(crate) root: NodeId,
/// Alpha of the backdrop painted under this scene, `0` for none.
pub(crate) dim: u8,
/// Set when this scene is a popup: the node the popup is anchored to and
/// the container holding its content. What makes a popup a popup is not the
/// scene — input capture comes from scene-ness itself — but the dismissal
/// rules: a press outside the container closes it, Escape closes it, and
/// focus goes back to the anchor.
pub(crate) popup: Option<Popup>,
}
/// The dismissal bookkeeping of a popup scene.
#[derive(Clone, Copy, Debug)]
pub(crate) struct Popup {
/// The node the popup is attached to, and where focus returns on close.
pub(crate) anchor: NodeId,
/// The positioned container the caller fills. A press outside its bounds
/// dismisses the popup.
pub(crate) container: NodeId,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn ffi_round_trip_preserves_identity() {
use slotmap::SlotMap;
let mut map: SlotMap<NodeId, u32> = SlotMap::with_key();
let a = map.insert(1);
assert_eq!(NodeId::from_ffi(a.as_ffi()), a);
}
#[test]
fn a_stale_id_does_not_resolve_to_the_next_node() {
use slotmap::SlotMap;
let mut map: SlotMap<NodeId, u32> = SlotMap::with_key();
let a = map.insert(1);
map.remove(a);
let b = map.insert(2);
assert_ne!(a, b);
assert_eq!(map.get(a), None);
}
}