yakui_core/dom/
mod.rs

1//! Defines yakui's DOM, which holds the hierarchy of widgets and their
2//! implementation details.
3
4mod debug;
5mod dummy;
6mod root;
7
8use std::any::{type_name, TypeId};
9use std::cell::{Ref, RefCell, RefMut};
10use std::collections::VecDeque;
11use std::mem::replace;
12use std::rc::Rc;
13
14use anymap::AnyMap;
15use thunderdome::Arena;
16
17use crate::id::WidgetId;
18use crate::input::InputState;
19use crate::response::Response;
20use crate::widget::{ErasedWidget, Widget};
21
22use self::dummy::DummyWidget;
23use self::root::RootWidget;
24
25/// The DOM that contains the tree of active widgets.
26pub struct Dom {
27    inner: Rc<DomInner>,
28}
29
30struct DomInner {
31    nodes: RefCell<Arena<DomNode>>,
32    stack: RefCell<Vec<WidgetId>>,
33    removed_nodes: RefCell<Vec<WidgetId>>,
34    root: WidgetId,
35    globals: RefCell<AnyMap>,
36    pending_focus_request: RefCell<Option<WidgetId>>,
37}
38
39/// A node in the [`Dom`].
40pub struct DomNode {
41    /// The widget implementation. Only a subset of the methods from [`Widget`]
42    /// are available without downcasting the widget first.
43    pub widget: Box<dyn ErasedWidget>,
44
45    /// The parent of this node, if it has one.
46    pub parent: Option<WidgetId>,
47
48    /// All of this node's children.
49    pub children: Vec<WidgetId>,
50
51    /// Used when building the tree. The index of the next child if a new child
52    /// starts being built.
53    next_child: usize,
54}
55
56impl Dom {
57    /// Create a new, empty DOM.
58    pub fn new() -> Self {
59        Self {
60            inner: Rc::new(DomInner::new()),
61        }
62    }
63
64    pub(crate) fn clone(&self) -> Self {
65        Self {
66            inner: self.inner.clone(),
67        }
68    }
69
70    /// Start the build phase for the DOM and bind it to the current thread.
71    pub fn start(&self) {
72        log::debug!("Dom::start()");
73
74        let mut nodes = self.inner.nodes.borrow_mut();
75        let root = nodes.get_mut(self.inner.root.index()).unwrap();
76        root.next_child = 0;
77    }
78
79    /// End the DOM's build phase.
80    pub fn finish(&self, input: &InputState) {
81        log::debug!("Dom::finish()");
82
83        let mut nodes = self.inner.nodes.borrow_mut();
84        let mut removed_nodes = self.inner.removed_nodes.borrow_mut();
85        let root = self.inner.root;
86        trim_children(&mut nodes, &mut removed_nodes, root);
87
88        if let Some(widget_id) = self.inner.pending_focus_request.borrow_mut().take() {
89            input.set_selection(Some(widget_id));
90        }
91    }
92
93    /// Tells how many nodes are currently in the DOM.
94    pub fn len(&self) -> usize {
95        self.inner.nodes.borrow().len()
96    }
97
98    /// Tells whether the DOM is empty.
99    pub fn is_empty(&self) -> bool {
100        self.inner.nodes.borrow().is_empty()
101    }
102
103    /// Gives the root widget in the DOM. This widget will always exist.
104    pub fn root(&self) -> WidgetId {
105        self.inner.root
106    }
107
108    /// Request focus for the given widget id
109    pub fn request_focus(&self, id: WidgetId) {
110        *self.inner.pending_focus_request.borrow_mut() = Some(id);
111    }
112
113    /// Gives a list of all of the nodes that were removed in the last update.
114    /// This is used for synchronizing state with the primary DOM storage.
115    pub(crate) fn removed_nodes(&self) -> Ref<'_, [WidgetId]> {
116        let vec = self.inner.removed_nodes.borrow();
117        Ref::map(vec, AsRef::as_ref)
118    }
119
120    /// Enter the context of the given widget, pushing it onto the stack so that
121    /// [`Dom::current`] will report the correct widget.
122    pub(crate) fn enter(&self, id: WidgetId) {
123        self.inner.stack.borrow_mut().push(id);
124    }
125
126    /// Pop the given widget off of the traversal stack. Panics if the widget on
127    /// top of the stack is not the one with the given ID.
128    pub(crate) fn exit(&self, id: WidgetId) {
129        assert_eq!(self.inner.stack.borrow_mut().pop(), Some(id));
130    }
131
132    /// If the DOM is being built, tells which widget is currently being built.
133    ///
134    /// This method only gives valid results when called from inside a
135    /// [`Widget`] lifecycle method.
136    pub fn current(&self) -> WidgetId {
137        let stack = self.inner.stack.borrow();
138        stack.last().copied().unwrap_or(self.inner.root)
139    }
140
141    /// Returns a reference to the current DOM node. See [`Dom::current`].
142    pub fn get_current(&self) -> Ref<'_, DomNode> {
143        let nodes = self.inner.nodes.borrow();
144        let index = self.current().index();
145
146        Ref::map(nodes, |nodes| nodes.get(index).unwrap())
147    }
148
149    /// Get the node with the given widget ID.
150    pub fn get(&self, id: WidgetId) -> Option<Ref<'_, DomNode>> {
151        let nodes = self.inner.nodes.borrow();
152        let index = id.index();
153
154        if nodes.contains(index) {
155            Some(Ref::map(nodes, |nodes| nodes.get(index).unwrap()))
156        } else {
157            None
158        }
159    }
160
161    /// Get a mutable reference to the node with the given widget ID.
162    pub fn get_mut(&self, id: WidgetId) -> Option<RefMut<'_, DomNode>> {
163        let nodes = self.inner.nodes.borrow_mut();
164        let index = id.index();
165
166        if nodes.contains(index) {
167            Some(RefMut::map(nodes, |nodes| nodes.get_mut(index).unwrap()))
168        } else {
169            None
170        }
171    }
172
173    /// Get a piece of DOM-global state or initialize it with the given
174    /// function.
175    ///
176    /// This is intended for any state that is global. It's not a perfect fit
177    /// for scoped state like themes.
178    pub fn get_global_or_init<T, F>(&self, init: F) -> T
179    where
180        T: 'static + Clone,
181        F: FnOnce() -> T,
182    {
183        let mut globals = self.inner.globals.borrow_mut();
184        globals.entry::<T>().or_insert_with(init).clone()
185    }
186
187    /// Convenience method for calling [`Dom::begin_widget`] immediately
188    /// followed by [`Dom::end_widget`].
189    pub fn do_widget<T: Widget>(&self, props: T::Props<'_>) -> Response<T::Response> {
190        let response = self.begin_widget::<T>(props);
191        self.end_widget::<T>(response.id);
192        response
193    }
194
195    /// Begin building a widget with the given type and props.
196    ///
197    /// After calling this method, children can be added to this widget.
198    pub fn begin_widget<T: Widget>(&self, props: T::Props<'_>) -> Response<T::Response> {
199        log::trace!("begin_widget::<{}>({props:#?}", type_name::<T>());
200
201        let (id, mut widget) = {
202            let mut nodes = self.inner.nodes.borrow_mut();
203            let id = next_widget(&mut nodes, self.current());
204            self.inner.stack.borrow_mut().push(id);
205
206            // Component::update needs mutable access to both the widget and the
207            // DOM, so we need to rip the widget out of the tree so we can
208            // release our lock.
209            let node = nodes.get_mut(id.index()).unwrap();
210            let widget = replace(&mut node.widget, Box::new(DummyWidget));
211
212            node.next_child = 0;
213            (id, widget)
214        };
215
216        // Potentially recreate the widget, then update it.
217        let response = {
218            if widget.as_ref().type_id() != TypeId::of::<T>() {
219                widget = Box::new(T::new());
220            }
221
222            let widget = widget.downcast_mut::<T>().unwrap();
223            widget.update(props)
224        };
225
226        // Quick! Put the widget back, before anyone notices!
227        {
228            let mut nodes = self.inner.nodes.borrow_mut();
229            let node = nodes.get_mut(id.index()).unwrap();
230            node.widget = widget;
231        }
232
233        Response::new(id, response)
234    }
235
236    /// Finish building the widget with the given ID. Must be the top of the
237    /// stack, with no other widgets pending.
238    pub fn end_widget<T: Widget>(&self, id: WidgetId) {
239        log::trace!("end_widget::<{}>({id:?})", type_name::<T>());
240
241        let old_top = self.inner.stack.borrow_mut().pop().unwrap_or_else(|| {
242            panic!("Cannot end_widget without an in-progress widget.");
243        });
244
245        assert!(
246            id == old_top,
247            "Dom::end_widget did not match the input widget."
248        );
249
250        let mut nodes = self.inner.nodes.borrow_mut();
251        let mut removed_nodes = self.inner.removed_nodes.borrow_mut();
252        trim_children(&mut nodes, &mut removed_nodes, id);
253    }
254}
255
256impl DomInner {
257    fn new() -> Self {
258        let mut nodes = Arena::new();
259        let root = nodes.insert(DomNode {
260            widget: Box::new(RootWidget),
261            parent: None,
262            children: Vec::new(),
263            next_child: 0,
264        });
265
266        Self {
267            globals: RefCell::new(AnyMap::new()),
268            nodes: RefCell::new(nodes),
269            removed_nodes: RefCell::new(Vec::new()),
270            stack: RefCell::new(Vec::new()),
271            root: WidgetId::new(root),
272            pending_focus_request: RefCell::new(None),
273        }
274    }
275}
276
277fn next_widget(nodes: &mut Arena<DomNode>, parent_id: WidgetId) -> WidgetId {
278    let parent = nodes.get_mut(parent_id.index()).unwrap();
279    if parent.next_child < parent.children.len() {
280        let id = parent.children[parent.next_child];
281        parent.next_child += 1;
282        id
283    } else {
284        let index = nodes.insert(DomNode {
285            widget: Box::new(DummyWidget),
286            parent: Some(parent_id),
287            children: Vec::new(),
288            next_child: 0,
289        });
290
291        let id = WidgetId::new(index);
292
293        let parent = nodes.get_mut(parent_id.index()).unwrap();
294        parent.children.push(id);
295        parent.next_child += 1;
296        id
297    }
298}
299
300/// Remove children from the given node that weren't present in the latest
301/// traversal through the tree.
302fn trim_children(nodes: &mut Arena<DomNode>, removed_nodes: &mut Vec<WidgetId>, id: WidgetId) {
303    let node = nodes.get_mut(id.index()).unwrap();
304
305    if node.next_child < node.children.len() {
306        let mut queue: VecDeque<WidgetId> = VecDeque::new();
307        let to_drop = &node.children[node.next_child..];
308        queue.extend(to_drop);
309        removed_nodes.extend_from_slice(to_drop);
310
311        node.children.truncate(node.next_child);
312
313        while let Some(child_id) = queue.pop_front() {
314            removed_nodes.push(child_id);
315            let child = nodes.remove(child_id.index()).unwrap();
316            queue.extend(child.children);
317        }
318    }
319}