azul_core/
dom.rs

1use std::{
2    fmt,
3    hash::{Hash, Hasher},
4    sync::atomic::{AtomicUsize, Ordering},
5    cmp::Ordering as CmpOrdering,
6    iter::FromIterator,
7    ffi::c_void,
8};
9use crate::{
10    callbacks::{
11        Callback, CallbackType,
12        IFrameCallback, IFrameCallbackType,
13        RefAny,
14    },
15    app_resources::{ImageId, TextId},
16    id_tree::{Arena, NodeDataContainer},
17};
18#[cfg(feature = "opengl")]
19use crate::callbacks::{GlCallback, GlCallbackType};
20use azul_css::{NodeTypePath, CssProperty};
21pub use crate::id_tree::{NodeHierarchy, Node, NodeId};
22
23static TAG_ID: AtomicUsize = AtomicUsize::new(1);
24
25/// Unique Ttag" that is used to annotate which rectangles are relevant for hit-testing
26#[derive(Copy, Clone, PartialEq, Eq, Hash, Ord, PartialOrd)]
27pub struct TagId(pub u64);
28
29impl ::std::fmt::Display for TagId {
30    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
31        write!(f, "ScrollTagId({})", self.0)
32    }
33}
34
35impl ::std::fmt::Debug for TagId {
36    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
37        write!(f, "{}", self)
38    }
39}
40
41
42/// Same as the `TagId`, but only for scrollable nodes
43#[derive(Copy, Clone, PartialEq, Eq, Hash, Ord, PartialOrd)]
44pub struct ScrollTagId(pub TagId);
45
46impl ::std::fmt::Display for ScrollTagId {
47    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
48        write!(f, "ScrollTagId({})", (self.0).0)
49    }
50}
51
52impl ::std::fmt::Debug for ScrollTagId {
53    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
54        write!(f, "{}", self)
55    }
56}
57
58impl TagId {
59    pub fn new() -> Self {
60        TagId(TAG_ID.fetch_add(1, Ordering::SeqCst) as u64)
61    }
62    pub fn reset() {
63        TAG_ID.swap(1, Ordering::SeqCst);
64    }
65}
66
67impl ScrollTagId {
68    pub fn new() -> ScrollTagId {
69        ScrollTagId(TagId::new())
70    }
71}
72
73static DOM_ID: AtomicUsize = AtomicUsize::new(1);
74
75/// DomID - used for identifying different DOMs (for example IFrameCallbacks)
76/// have a different DomId than the root DOM
77#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
78pub struct DomId {
79    /// Unique ID for this DOM
80    id: usize,
81    /// If this DOM was generated from an IFrameCallback, stores the parents
82    /// DomId + the NodeId (from the parent DOM) which the IFrameCallback
83    /// was attached to.
84    parent: Option<(Box<DomId>, NodeId)>,
85}
86
87impl DomId {
88
89    /// ID for the top-level DOM (of a window)
90    pub const ROOT_ID: DomId = Self { id: 0, parent: None };
91
92    /// Creates a new, unique DOM ID.
93    #[inline(always)]
94    pub fn new(parent: Option<(DomId, NodeId)>) -> DomId {
95        DomId {
96            id: DOM_ID.fetch_add(1, Ordering::SeqCst),
97            parent: parent.map(|(p, node_id)| (Box::new(p), node_id)),
98        }
99    }
100
101    /// Reset the DOM ID to 0, usually done once-per-frame for the root DOM
102    #[inline(always)]
103    pub fn reset() {
104        DOM_ID.swap(0, Ordering::SeqCst);
105    }
106
107    /// Returns if this is the root node
108    #[inline(always)]
109    pub fn is_root(&self) -> bool {
110        *self == Self::ROOT_ID
111    }
112}
113
114/// Calculated hash of a DOM node, used for querying attributes of the DOM node
115#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq, Ord, PartialOrd)]
116pub struct DomHash(pub u64);
117
118/// List of core DOM node types built-into by `azul`.
119#[derive(Debug, Clone, PartialEq, Hash, Eq)]
120pub enum NodeType {
121    /// Regular div with no particular type of data attached
122    Div,
123    /// Same as div, but only for the root node
124    Body,
125    /// A small label that can be (optionally) be selectable with the mouse
126    Label(DomString),
127    /// Larger amount of text, that has to be cached
128    Text(TextId),
129    /// An image that is rendered by WebRender. The id is acquired by the
130    /// `AppState::add_image()` function
131    Image(ImageId),
132    /// OpenGL texture. The `Svg` widget deserizalizes itself into a texture
133    /// Equality and Hash values are only checked by the OpenGl texture ID,
134    /// Azul does not check that the contents of two textures are the same
135    #[cfg(feature = "opengl")]
136    GlTexture((GlCallback, RefAny)),
137    /// DOM that gets passed its width / height during the layout
138    IFrame((IFrameCallback, RefAny)),
139}
140
141impl NodeType {
142    fn get_text_content(&self) -> Option<String> {
143        use self::NodeType::*;
144        match self {
145            Div | Body => None,
146            Label(s) => Some(format!("{}", s)),
147            Image(id) => Some(format!("image({:?})", id)),
148            Text(t) => Some(format!("textid({:?})", t)),
149            #[cfg(feature = "opengl")]
150            GlTexture(g) => Some(format!("gltexture({:?})", g)),
151            IFrame(i) => Some(format!("iframe({:?})", i)),
152        }
153    }
154}
155
156impl NodeType {
157    #[inline]
158    pub fn get_path(&self) -> NodeTypePath {
159        use self::NodeType::*;
160        match self {
161            Div => NodeTypePath::Div,
162            Body => NodeTypePath::Body,
163            Label(_) | Text(_) => NodeTypePath::P,
164            Image(_) => NodeTypePath::Img,
165            #[cfg(feature = "opengl")]
166            GlTexture(_) => NodeTypePath::Texture,
167            IFrame(_) => NodeTypePath::IFrame,
168        }
169    }
170}
171
172/// When to call a callback action - `On::MouseOver`, `On::MouseOut`, etc.
173#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
174pub enum On {
175    /// Mouse cursor is hovering over the element
176    MouseOver,
177    /// Mouse cursor has is over element and is pressed
178    /// (not good for "click" events - use `MouseUp` instead)
179    MouseDown,
180    /// (Specialization of `MouseDown`). Fires only if the left mouse button
181    /// has been pressed while cursor was over the element
182    LeftMouseDown,
183    /// (Specialization of `MouseDown`). Fires only if the middle mouse button
184    /// has been pressed while cursor was over the element
185    MiddleMouseDown,
186    /// (Specialization of `MouseDown`). Fires only if the right mouse button
187    /// has been pressed while cursor was over the element
188    RightMouseDown,
189    /// Mouse button has been released while cursor was over the element
190    MouseUp,
191    /// (Specialization of `MouseUp`). Fires only if the left mouse button has
192    /// been released while cursor was over the element
193    LeftMouseUp,
194    /// (Specialization of `MouseUp`). Fires only if the middle mouse button has
195    /// been released while cursor was over the element
196    MiddleMouseUp,
197    /// (Specialization of `MouseUp`). Fires only if the right mouse button has
198    /// been released while cursor was over the element
199    RightMouseUp,
200    /// Mouse cursor has entered the element
201    MouseEnter,
202    /// Mouse cursor has left the element
203    MouseLeave,
204    /// Mousewheel / touchpad scrolling
205    Scroll,
206    /// The window received a unicode character (also respects the system locale).
207    /// Check `keyboard_state.current_char` to get the current pressed character.
208    TextInput,
209    /// A **virtual keycode** was pressed. Note: This is only the virtual keycode,
210    /// not the actual char. If you want to get the character, use `TextInput` instead.
211    /// A virtual key does not have to map to a printable character.
212    ///
213    /// You can get all currently pressed virtual keycodes in the `keyboard_state.current_virtual_keycodes`
214    /// and / or just the last keycode in the `keyboard_state.latest_virtual_keycode`.
215    VirtualKeyDown,
216    /// A **virtual keycode** was release. See `VirtualKeyDown` for more info.
217    VirtualKeyUp,
218    /// A file has been dropped on the element
219    HoveredFile,
220    /// A file is being hovered on the element
221    DroppedFile,
222    /// A file was hovered, but has exited the window
223    HoveredFileCancelled,
224    /// Equivalent to `onfocus`
225    FocusReceived,
226    /// Equivalent to `onblur`
227    FocusLost,
228}
229
230/// Sets the target for what events can reach the callbacks specifically.
231///
232/// Filtering events can happen on several layers, depending on
233/// if a DOM node is hovered over or actively focused. For example,
234/// for text input, you wouldn't want to use hovering, because that
235/// would mean that the user needs to hold the mouse over the text input
236/// in order to enter text. To solve this, the DOM needs to fire events
237/// for elements that are currently not part of the hit-test.
238/// `EventFilter` implements `From<On>` as a shorthand (so that you can opt-in
239/// to a more specific event) and use
240///
241#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
242pub enum EventFilter {
243    /// Calls the attached callback when the mouse is actively over the
244    /// given element.
245    Hover(HoverEventFilter),
246    /// Inverse of `Hover` - calls the attached callback if the mouse is **not**
247    /// over the given element. This is particularly useful for popover menus
248    /// where you want to close the menu when the user clicks anywhere else but
249    /// the menu itself.
250    Not(NotEventFilter),
251    /// Calls the attached callback when the element is currently focused.
252    Focus(FocusEventFilter),
253    /// Calls the callback when anything related to the window is happening.
254    /// The "hit item" will be the root item of the DOM.
255    /// For example, this can be useful for tracking the mouse position
256    /// (in relation to the window). In difference to `Desktop`, this only
257    /// fires when the window is focused.
258    ///
259    /// This can also be good for capturing controller input, touch input
260    /// (i.e. global gestures that aren't attached to any component, but rather
261    /// the "window" itself).
262    Window(WindowEventFilter),
263}
264
265/// Creates a function inside an impl <enum type> block that returns a single
266/// variant if the enum is that variant.
267///
268/// ```rust
269/// enum A {
270///    Abc(AbcType),
271/// }
272///
273/// struct AbcType { }
274///
275/// impl A {
276///     // fn as_abc_type(&self) -> Option<AbcType>
277///     get_single_enum_type!(as_abc_type, A::Abc(AbcType));
278/// }
279/// ```
280macro_rules! get_single_enum_type {
281    ($fn_name:ident, $enum_name:ident::$variant:ident($return_type:ty)) => (
282        pub fn $fn_name(&self) -> Option<$return_type> {
283            use self::$enum_name::*;
284            match self {
285                $variant(e) => Some(*e),
286                _ => None,
287            }
288        }
289    )
290}
291
292impl EventFilter {
293    get_single_enum_type!(as_hover_event_filter, EventFilter::Hover(HoverEventFilter));
294    get_single_enum_type!(as_focus_event_filter, EventFilter::Focus(FocusEventFilter));
295    get_single_enum_type!(as_not_event_filter, EventFilter::Not(NotEventFilter));
296    get_single_enum_type!(as_window_event_filter, EventFilter::Window(WindowEventFilter));
297}
298
299impl From<On> for EventFilter {
300    fn from(input: On) -> EventFilter {
301        use self::On::*;
302        match input {
303            MouseOver            => EventFilter::Hover(HoverEventFilter::MouseOver),
304            MouseDown            => EventFilter::Hover(HoverEventFilter::MouseDown),
305            LeftMouseDown        => EventFilter::Hover(HoverEventFilter::LeftMouseDown),
306            MiddleMouseDown      => EventFilter::Hover(HoverEventFilter::MiddleMouseDown),
307            RightMouseDown       => EventFilter::Hover(HoverEventFilter::RightMouseDown),
308            MouseUp              => EventFilter::Hover(HoverEventFilter::MouseUp),
309            LeftMouseUp          => EventFilter::Hover(HoverEventFilter::LeftMouseUp),
310            MiddleMouseUp        => EventFilter::Hover(HoverEventFilter::MiddleMouseUp),
311            RightMouseUp         => EventFilter::Hover(HoverEventFilter::RightMouseUp),
312
313            MouseEnter           => EventFilter::Hover(HoverEventFilter::MouseEnter),
314            MouseLeave           => EventFilter::Hover(HoverEventFilter::MouseLeave),
315            Scroll               => EventFilter::Hover(HoverEventFilter::Scroll),
316            TextInput            => EventFilter::Focus(FocusEventFilter::TextInput),            // focus!
317            VirtualKeyDown       => EventFilter::Window(WindowEventFilter::VirtualKeyDown),     // window!
318            VirtualKeyUp         => EventFilter::Window(WindowEventFilter::VirtualKeyUp),       // window!
319            HoveredFile          => EventFilter::Hover(HoverEventFilter::HoveredFile),
320            DroppedFile          => EventFilter::Hover(HoverEventFilter::DroppedFile),
321            HoveredFileCancelled => EventFilter::Hover(HoverEventFilter::HoveredFileCancelled),
322            FocusReceived        => EventFilter::Focus(FocusEventFilter::FocusReceived),        // focus!
323            FocusLost            => EventFilter::Focus(FocusEventFilter::FocusLost),            // focus!
324        }
325    }
326}
327
328/// Event filter that only fires when an element is hovered over
329#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
330pub enum HoverEventFilter {
331    MouseOver,
332    MouseDown,
333    LeftMouseDown,
334    RightMouseDown,
335    MiddleMouseDown,
336    MouseUp,
337    LeftMouseUp,
338    RightMouseUp,
339    MiddleMouseUp,
340    MouseEnter,
341    MouseLeave,
342    Scroll,
343    ScrollStart,
344    ScrollEnd,
345    TextInput,
346    VirtualKeyDown,
347    VirtualKeyUp,
348    HoveredFile,
349    DroppedFile,
350    HoveredFileCancelled,
351}
352
353impl HoverEventFilter {
354    pub fn to_focus_event_filter(&self) -> Option<FocusEventFilter> {
355        use self::HoverEventFilter::*;
356        match self {
357            MouseOver => Some(FocusEventFilter::MouseOver),
358            MouseDown => Some(FocusEventFilter::MouseDown),
359            LeftMouseDown => Some(FocusEventFilter::LeftMouseDown),
360            RightMouseDown => Some(FocusEventFilter::RightMouseDown),
361            MiddleMouseDown => Some(FocusEventFilter::MiddleMouseDown),
362            MouseUp => Some(FocusEventFilter::MouseUp),
363            LeftMouseUp => Some(FocusEventFilter::LeftMouseUp),
364            RightMouseUp => Some(FocusEventFilter::RightMouseUp),
365            MiddleMouseUp => Some(FocusEventFilter::MiddleMouseUp),
366            MouseEnter => Some(FocusEventFilter::MouseEnter),
367            MouseLeave => Some(FocusEventFilter::MouseLeave),
368            Scroll => Some(FocusEventFilter::Scroll),
369            ScrollStart => Some(FocusEventFilter::ScrollStart),
370            ScrollEnd => Some(FocusEventFilter::ScrollEnd),
371            TextInput => Some(FocusEventFilter::TextInput),
372            VirtualKeyDown => Some(FocusEventFilter::VirtualKeyDown),
373            VirtualKeyUp => Some(FocusEventFilter::VirtualKeyDown),
374            HoveredFile => None,
375            DroppedFile => None,
376            HoveredFileCancelled => None,
377        }
378    }
379}
380
381/// The inverse of an `onclick` event filter, fires when an item is *not* hovered / focused.
382/// This is useful for cleanly implementing things like popover dialogs or dropdown boxes that
383/// want to close when the user clicks any where *but* the item itself.
384#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
385pub enum NotEventFilter {
386    Hover(HoverEventFilter),
387    Focus(FocusEventFilter),
388}
389
390/// Event filter similar to `HoverEventFilter` that only fires when the element is focused
391///
392/// **Important**: In order for this to fire, the item must have a `tabindex` attribute
393/// (to indicate that the item is focus-able).
394#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
395pub enum FocusEventFilter {
396    MouseOver,
397    MouseDown,
398    LeftMouseDown,
399    RightMouseDown,
400    MiddleMouseDown,
401    MouseUp,
402    LeftMouseUp,
403    RightMouseUp,
404    MiddleMouseUp,
405    MouseEnter,
406    MouseLeave,
407    Scroll,
408    ScrollStart,
409    ScrollEnd,
410    TextInput,
411    VirtualKeyDown,
412    VirtualKeyUp,
413    FocusReceived,
414    FocusLost,
415}
416
417/// Event filter that fires when any action fires on the entire window
418/// (regardless of whether any element is hovered or focused over).
419#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
420pub enum WindowEventFilter {
421    MouseOver,
422    MouseDown,
423    LeftMouseDown,
424    RightMouseDown,
425    MiddleMouseDown,
426    MouseUp,
427    LeftMouseUp,
428    RightMouseUp,
429    MiddleMouseUp,
430    MouseEnter,
431    MouseLeave,
432    Scroll,
433    ScrollStart,
434    ScrollEnd,
435    TextInput,
436    VirtualKeyDown,
437    VirtualKeyUp,
438    HoveredFile,
439    DroppedFile,
440    HoveredFileCancelled,
441}
442
443impl WindowEventFilter {
444    pub fn to_hover_event_filter(&self) -> Option<HoverEventFilter> {
445        use self::WindowEventFilter::*;
446        match self {
447            MouseOver => Some(HoverEventFilter::MouseOver),
448            MouseDown => Some(HoverEventFilter::MouseDown),
449            LeftMouseDown => Some(HoverEventFilter::LeftMouseDown),
450            RightMouseDown => Some(HoverEventFilter::RightMouseDown),
451            MiddleMouseDown => Some(HoverEventFilter::MiddleMouseDown),
452            MouseUp => Some(HoverEventFilter::MouseUp),
453            LeftMouseUp => Some(HoverEventFilter::LeftMouseUp),
454            RightMouseUp => Some(HoverEventFilter::RightMouseUp),
455            MiddleMouseUp => Some(HoverEventFilter::MiddleMouseUp),
456            Scroll => Some(HoverEventFilter::Scroll),
457            ScrollStart => Some(HoverEventFilter::ScrollStart),
458            ScrollEnd => Some(HoverEventFilter::ScrollEnd),
459            TextInput => Some(HoverEventFilter::TextInput),
460            VirtualKeyDown => Some(HoverEventFilter::VirtualKeyDown),
461            VirtualKeyUp => Some(HoverEventFilter::VirtualKeyDown),
462            HoveredFile => Some(HoverEventFilter::HoveredFile),
463            DroppedFile => Some(HoverEventFilter::DroppedFile),
464            HoveredFileCancelled => Some(HoverEventFilter::HoveredFileCancelled),
465            // MouseEnter and MouseLeave on the **window** - does not mean a mouseenter
466            // and a mouseleave on the hovered element
467            MouseEnter => None,
468            MouseLeave => None,
469        }
470    }
471}
472
473/// Represents one single DOM node (node type, classes, ids and callbacks are stored here)
474pub struct NodeData {
475    /// `div`
476    node_type: NodeType,
477    /// `#main #something`
478    ids: Vec<DomString>,
479    /// `.myclass .otherclass`
480    classes: Vec<DomString>,
481    /// `On::MouseUp` -> `Callback(my_button_click_handler)`
482    callbacks: Vec<(EventFilter, (Callback, RefAny))>,
483    /// Override certain dynamic styling properties in this frame. For this,
484    /// these properties have to have a name (the ID).
485    ///
486    /// For example, in the CSS stylesheet:
487    ///
488    /// ```css,ignore
489    /// #my_item { width: [[ my_custom_width | 200px ]] }
490    /// ```
491    ///
492    /// ```rust,ignore
493    /// let node = NodeData {
494    ///     id: Some("my_item".into()),
495    ///     dynamic_css_overrides: vec![("my_custom_width".into(), CssProperty::Width(LayoutWidth::px(500.0)))]
496    /// }
497    /// ```
498    dynamic_css_overrides: Vec<(DomString, CssProperty)>,
499    /// Whether this div can be dragged or not, similar to `draggable = "true"` in HTML, .
500    ///
501    /// **TODO**: Currently doesn't do anything, since the drag & drop implementation is missing, API stub.
502    is_draggable: bool,
503    /// Whether this div can be focused, and if yes, in what default to `None` (not focusable).
504    /// Note that without this, there can be no `On::FocusReceived` (equivalent to onfocus),
505    /// `On::FocusLost` (equivalent to onblur), etc. events.
506    tab_index: Option<TabIndex>,
507}
508
509#[derive(Debug, Copy, Clone, PartialEq, Eq, Ord, PartialOrd, Hash)]
510pub enum TabIndex {
511    /// Automatic tab index, similar to simply setting `focusable = "true"` or `tabindex = 0`
512    /// (both have the effect of making the element focusable).
513    ///
514    /// Sidenote: See https://www.w3.org/TR/html5/editing.html#sequential-focus-navigation-and-the-tabindex-attribute
515    /// for interesting notes on tabindex and accessibility
516    Auto,
517    /// Set the tab index in relation to its parent element. I.e. if you have a list of elements,
518    /// the focusing order is restricted to the current parent.
519    ///
520    /// Ex. a div might have:
521    ///
522    /// ```no_run,ignore
523    /// div (Auto)
524    /// |- element1 (OverrideInParent 0) <- current focus
525    /// |- element2 (OverrideInParent 5)
526    /// |- element3 (OverrideInParent 2)
527    /// |- element4 (Global 5)
528    /// ```
529    ///
530    /// When pressing tab repeatedly, the focusing order will be
531    /// "element3, element2, element4, div", since OverrideInParent elements
532    /// take precedence among global order.
533    OverrideInParent(usize),
534    /// Elements can be focused in callbacks, but are not accessible via
535    /// keyboard / tab navigation (-1)
536    NoKeyboardFocus,
537}
538
539impl TabIndex {
540    /// Returns the HTML-compatible number of the `tabindex` element
541    pub fn get_index(&self) -> isize {
542        use self::TabIndex::*;
543        match self {
544            Auto => 0,
545            OverrideInParent(x) => *x as isize,
546            NoKeyboardFocus => -1,
547        }
548    }
549}
550impl Default for TabIndex {
551    fn default() -> Self {
552        TabIndex::Auto
553    }
554}
555
556impl PartialEq for NodeData {
557    fn eq(&self, other: &Self) -> bool {
558        self.node_type == other.node_type &&
559        self.ids == other.ids &&
560        self.classes == other.classes &&
561        self.callbacks == other.callbacks &&
562        self.dynamic_css_overrides == other.dynamic_css_overrides &&
563        self.is_draggable == other.is_draggable &&
564        self.tab_index == other.tab_index
565    }
566}
567
568impl Eq for NodeData { }
569
570impl Default for NodeData {
571    fn default() -> Self {
572        NodeData::new(NodeType::Div)
573    }
574}
575
576impl Hash for NodeData {
577    fn hash<H: Hasher>(&self, state: &mut H) {
578        self.node_type.hash(state);
579        for id in &self.ids {
580            id.hash(state);
581        }
582        for class in &self.classes {
583            class.hash(state);
584        }
585        for callback in &self.callbacks {
586            callback.hash(state);
587        }
588        for dynamic_css_override in &self.dynamic_css_overrides {
589            dynamic_css_override.hash(state);
590        }
591        self.is_draggable.hash(state);
592        self.tab_index.hash(state);
593    }
594}
595
596impl Clone for NodeData {
597    fn clone(&self) -> Self {
598        Self {
599            node_type: self.node_type.clone(),
600            ids: self.ids.clone(),
601            classes: self.classes.clone(),
602            callbacks: self.callbacks.clone(),
603            dynamic_css_overrides: self.dynamic_css_overrides.clone(),
604            is_draggable: self.is_draggable.clone(),
605            tab_index: self.tab_index.clone(),
606        }
607    }
608}
609
610impl fmt::Display for NodeData {
611    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
612
613        let html_type = self.node_type.get_path();
614        let attributes_string = node_data_to_string(&self);
615
616        match self.node_type.get_text_content() {
617            Some(content) => write!(f, "<{}{}>{}</{}>", html_type, attributes_string, content, html_type),
618            None => write!(f, "<{}{}/>", html_type, attributes_string)
619        }
620    }
621}
622
623impl NodeData {
624    pub fn debug_print_start(&self, close_self: bool) -> String {
625        let html_type = self.node_type.get_path();
626        let attributes_string = node_data_to_string(&self);
627        format!("<{}{}{}>", html_type, attributes_string, if close_self { " /" } else { "" })
628    }
629
630    pub fn debug_print_end(&self) -> String {
631        let html_type = self.node_type.get_path();
632        format!("</{}>", html_type)
633    }
634}
635
636fn node_data_to_string(node_data: &NodeData) -> String {
637
638    let id_string = if node_data.ids.is_empty() {
639        String::new()
640    } else {
641        format!(" id=\"{}\"", node_data.ids.iter().map(|s| s.as_str().to_string()).collect::<Vec<String>>().join(" "))
642    };
643
644    let class_string = if node_data.classes.is_empty() {
645        String::new()
646    } else {
647        format!(" class=\"{}\"", node_data.classes.iter().map(|s| s.as_str().to_string()).collect::<Vec<String>>().join(" "))
648    };
649
650    let draggable = if node_data.is_draggable {
651        format!(" draggable=\"true\"")
652    } else {
653        String::new()
654    };
655
656    let tabindex = if let Some(tab_index) = node_data.tab_index {
657        format!(" tabindex=\"{}\"", tab_index.get_index())
658    } else {
659        String::new()
660    };
661
662    let callbacks = if node_data.callbacks.is_empty() {
663        String::new()
664    } else {
665        format!(" callbacks=\"{}\"", node_data.callbacks.iter().map(|(evt, cb)| format!("({:?}={:?})", evt, cb)).collect::<Vec<String>>().join(" "))
666    };
667
668    let css_overrides = if node_data.dynamic_css_overrides.is_empty() {
669        String::new()
670    } else {
671        format!(" css-overrides=\"{}\"", node_data.dynamic_css_overrides.iter().map(|(id, prop)| format!("{}={:?};", id, prop)).collect::<Vec<String>>().join(" "))
672    };
673
674    format!("{}{}{}{}{}{}", id_string, class_string, tabindex, draggable, callbacks, css_overrides)
675}
676
677impl fmt::Debug for NodeData {
678    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
679        write!(f, "NodeData {{")?;
680        write!(f, "\tnode_type: {:?}", self.node_type)?;
681
682        if !self.ids.is_empty() { write!(f, "\tids: {:?}", self.ids)?; }
683        if !self.classes.is_empty() { write!(f, "\tclasses: {:?}", self.classes)?; }
684        if !self.callbacks.is_empty() { write!(f, "\tcallbacks: {:?}", self.callbacks)?; }
685        if !self.dynamic_css_overrides.is_empty() { write!(f, "\tdynamic_css_overrides: {:?}", self.dynamic_css_overrides)?; }
686        if self.is_draggable { write!(f, "\tis_draggable: {:?}", self.is_draggable)?; }
687        if let Some(t) = self.tab_index { write!(f, "\ttab_index: {:?}", t)?; }
688        write!(f, "}}")?;
689        Ok(())
690    }
691}
692
693impl NodeData {
694
695    /// Creates a new `NodeData` instance from a given `NodeType`
696    #[inline]
697    pub const fn new(node_type: NodeType) -> Self {
698        Self {
699            node_type,
700            ids: Vec::new(),
701            classes: Vec::new(),
702            callbacks: Vec::new(),
703            dynamic_css_overrides: Vec::new(),
704            is_draggable: false,
705            tab_index: None,
706        }
707    }
708
709    /// Checks whether this node is of the given node type (div, image, text)
710    #[inline]
711    pub fn is_node_type(&self, searched_type: NodeType) -> bool {
712        self.node_type == searched_type
713    }
714
715    /// Checks whether this node has the searched ID attached
716    pub fn has_id(&self, id: &str) -> bool {
717        self.ids.iter().any(|self_id| self_id.equals_str(id))
718    }
719
720    /// Checks whether this node has the searched class attached
721    pub fn has_class(&self, class: &str) -> bool {
722        self.classes.iter().any(|self_class| self_class.equals_str(class))
723    }
724
725    pub fn calculate_node_data_hash(&self) -> DomHash {
726
727        use std::collections::hash_map::DefaultHasher as HashAlgorithm;
728
729        let mut hasher = HashAlgorithm::default();
730        self.hash(&mut hasher);
731
732        DomHash(hasher.finish())
733    }
734
735    /// Shorthand for `NodeData::new(NodeType::Body)`.
736    #[inline(always)]
737    pub const fn body() -> Self {
738        Self::new(NodeType::Body)
739    }
740
741    /// Shorthand for `NodeData::new(NodeType::Div)`.
742    #[inline(always)]
743    pub const fn div() -> Self {
744        Self::new(NodeType::Div)
745    }
746
747    /// Shorthand for `NodeData::new(NodeType::Label(value.into()))`
748    #[inline(always)]
749    pub fn label<S: Into<DomString>>(value: S) -> Self {
750        Self::new(NodeType::Label(value.into()))
751    }
752
753    /// Shorthand for `NodeData::new(NodeType::Text(text_id))`
754    #[inline(always)]
755    pub const fn text_id(text_id: TextId) -> Self {
756        Self::new(NodeType::Text(text_id))
757    }
758
759    /// Shorthand for `NodeData::new(NodeType::Image(image_id))`
760    #[inline(always)]
761    pub const fn image(image: ImageId) -> Self {
762        Self::new(NodeType::Image(image))
763    }
764
765    /// Shorthand for `NodeData::new(NodeType::GlTexture((callback, ptr)))`
766    #[inline(always)]
767    #[cfg(feature = "opengl")]
768    pub fn gl_texture(callback: GlCallbackType, ptr: RefAny) -> Self {
769        Self::new(NodeType::GlTexture((GlCallback(callback), ptr)))
770    }
771
772    /// Shorthand for `NodeData::new(NodeType::IFrame((callback, ptr)))`
773    #[inline(always)]
774    pub fn iframe(callback: IFrameCallbackType, ptr: RefAny) -> Self {
775        Self::new(NodeType::IFrame((IFrameCallback(callback), ptr)))
776    }
777
778    // NOTE: Getters are used here in order to allow changing the memory allocator for the NodeData
779    // in the future (which is why the fields are all private).
780
781    #[inline(always)]
782    pub const fn get_node_type(&self) -> &NodeType { &self.node_type }
783    #[inline(always)]
784    pub const fn get_ids(&self) -> &Vec<DomString> { &self.ids }
785    #[inline(always)]
786    pub const fn get_classes(&self) -> &Vec<DomString> { &self.classes }
787    #[inline(always)]
788    pub const fn get_callbacks(&self) -> &Vec<(EventFilter, (Callback, RefAny))> { &self.callbacks }
789    #[inline(always)]
790    pub const fn get_dynamic_css_overrides(&self) -> &Vec<(DomString, CssProperty)> { &self.dynamic_css_overrides }
791    #[inline(always)]
792    pub const fn get_is_draggable(&self) -> bool { self.is_draggable }
793    #[inline(always)]
794    pub const fn get_tab_index(&self) -> Option<TabIndex> { self.tab_index }
795
796    #[inline(always)]
797    pub fn set_node_type(&mut self, node_type: NodeType) { self.node_type = node_type; }
798    #[inline(always)]
799    pub fn set_ids(&mut self, ids: Vec<DomString>) { self.ids = ids; }
800    #[inline(always)]
801    pub fn set_classes(&mut self, classes: Vec<DomString>) { self.classes = classes; }
802    #[inline(always)]
803    pub fn set_callbacks(&mut self, callbacks: Vec<(EventFilter, (Callback, RefAny))>) { self.callbacks = callbacks; }
804    #[inline(always)]
805    pub fn set_dynamic_css_overrides(&mut self, dynamic_css_overrides: Vec<(DomString, CssProperty)>) { self.dynamic_css_overrides = dynamic_css_overrides; }
806    #[inline(always)]
807    pub fn set_is_draggable(&mut self, is_draggable: bool) { self.is_draggable = is_draggable; }
808    #[inline(always)]
809    pub fn set_tab_index(&mut self, tab_index: Option<TabIndex>) { self.tab_index = tab_index; }
810
811    #[inline(always)]
812    pub fn with_node_type(self, node_type: NodeType) -> Self { Self { node_type, .. self } }
813    #[inline(always)]
814    pub fn with_ids(self, ids: Vec<DomString>) -> Self { Self { ids, .. self } }
815    #[inline(always)]
816    pub fn with_classes(self, classes: Vec<DomString>) -> Self { Self { classes, .. self } }
817    #[inline(always)]
818    pub fn with_callbacks(self, callbacks: Vec<(EventFilter, (Callback, RefAny))>) -> Self { Self { callbacks, .. self } }
819    #[inline(always)]
820    pub fn with_dynamic_css_overrides(self, dynamic_css_overrides: Vec<(DomString, CssProperty)>) -> Self { Self { dynamic_css_overrides, .. self } }
821    #[inline(always)]
822    pub fn is_draggable(self, is_draggable: bool) -> Self { Self { is_draggable, .. self } }
823    #[inline(always)]
824    pub fn with_tab_index(self, tab_index: Option<TabIndex>) -> Self { Self { tab_index, .. self } }
825}
826
827/// Most strings are known at compile time, spares a bit of
828/// heap allocations - for `&'static str`, simply stores the pointer,
829/// instead of converting it into a String. This is good for class names
830/// or IDs, whose content rarely changes.
831#[derive(Clone)]
832pub enum DomString {
833    Static(&'static str),
834    Heap(String),
835}
836
837impl Eq for DomString { }
838
839impl PartialEq for DomString {
840    fn eq(&self, other: &Self) -> bool {
841        self.as_str() == other.as_str()
842    }
843}
844
845impl PartialOrd for DomString {
846    fn partial_cmp(&self, other: &Self) -> Option<CmpOrdering> {
847        Some(self.as_str().cmp(other.as_str()))
848    }
849}
850
851impl Ord for DomString {
852    fn cmp(&self, other: &Self) -> CmpOrdering {
853        self.as_str().cmp(other.as_str())
854    }
855}
856
857impl Hash for DomString {
858    fn hash<H: Hasher>(&self, state: &mut H) {
859        self.as_str().hash(state);
860    }
861}
862
863impl DomString {
864
865    pub fn equals_str(&self, target: &str) -> bool {
866        use self::DomString::*;
867        match &self {
868            Static(s) => *s == target,
869            Heap(h) => h == target,
870        }
871    }
872
873    pub fn as_str(&self) -> &str {
874        use self::DomString::*;
875        match &self {
876            Static(s) => s,
877            Heap(h) => h,
878        }
879    }
880}
881
882impl fmt::Debug for DomString {
883    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
884        use self::DomString::*;
885        match &self {
886            Static(s) => write!(f, "\"{}\"", s),
887            Heap(h) => write!(f, "\"{}\"", h),
888        }
889    }
890}
891
892impl fmt::Display for DomString {
893    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
894        use self::DomString::*;
895        match &self {
896            Static(s) => write!(f, "{}", s),
897            Heap(h) => write!(f, "{}", h),
898        }
899    }
900}
901
902impl From<String> for DomString {
903    fn from(e: String) -> Self {
904        DomString::Heap(e)
905    }
906}
907
908impl From<&'static str> for DomString {
909    fn from(e: &'static str) -> Self {
910        DomString::Static(e)
911    }
912}
913
914/// The document model, similar to HTML. This is a create-only structure, you don't actually read anything back
915pub struct Dom {
916    pub root: NodeData,
917    pub children: Vec<Dom>,
918    // Tracks the number of sub-children of the current children, so that
919    // the `Dom` can be converted into a `CompactDom`
920    estimated_total_children: usize,
921}
922
923/// Pointer to rust-allocated `Box<Dom<*mut c_void>>` struct
924#[no_mangle] #[repr(C)] pub struct DomPtr { pub ptr: *mut c_void }
925
926impl Clone for Dom {
927    fn clone(&self) -> Self {
928        Dom {
929            root: self.root.clone(),
930            children: self.children.clone(),
931            estimated_total_children: self.estimated_total_children,
932        }
933    }
934}
935
936fn compare_dom(a: &Dom, b: &Dom) -> bool {
937    a.root == b.root &&
938    a.estimated_total_children == b.estimated_total_children &&
939    a.children.len() == b.children.len() &&
940    a.children.iter().zip(b.children.iter()).all(|(a, b)| compare_dom(a, b))
941}
942
943impl PartialEq for Dom {
944    fn eq(&self, rhs: &Self) -> bool {
945        compare_dom(self, rhs)
946    }
947}
948
949impl Eq for Dom { }
950
951fn print_dom(d: &Dom, f: &mut fmt::Formatter) -> fmt::Result {
952    write!(f, "Dom {{\r\n")?;
953    write!(f, "\troot: {:#?}", d.root)?;
954    write!(f, "\testimated_total_children: {:#?}", d.estimated_total_children)?;
955    write!(f, "\tchildren: [")?;
956    for c in &d.children {
957        print_dom(c, f)?;
958    }
959    write!(f, "\t]")?;
960    write!(f, "}}")?;
961    Ok(())
962}
963
964impl fmt::Debug for Dom {
965    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
966        print_dom(self, f)
967    }
968}
969
970impl FromIterator<Dom> for Dom {
971    fn from_iter<I: IntoIterator<Item=Dom>>(iter: I) -> Self {
972
973        let mut estimated_total_children = 0;
974        let children = iter.into_iter().map(|c| {
975            estimated_total_children += c.estimated_total_children + 1;
976            c
977        }).collect();
978
979        Dom {
980            root: NodeData::div(),
981            children,
982            estimated_total_children,
983        }
984    }
985}
986
987impl FromIterator<NodeData> for Dom {
988    fn from_iter<I: IntoIterator<Item=NodeData>>(iter: I) -> Self {
989
990        let children = iter.into_iter().map(|c| Dom { root: c, children: Vec::new(), estimated_total_children: 0 }).collect::<Vec<_>>();
991        let estimated_total_children = children.len();
992
993        Dom {
994            root: NodeData::div(),
995            children,
996            estimated_total_children,
997        }
998    }
999}
1000
1001impl FromIterator<NodeType> for Dom {
1002    fn from_iter<I: IntoIterator<Item=NodeType>>(iter: I) -> Self {
1003        iter.into_iter().map(|i| NodeData { node_type: i, .. Default::default() }).collect()
1004    }
1005}
1006
1007pub(crate) fn convert_dom_into_compact_dom(dom: Dom) -> CompactDom {
1008
1009    // Pre-allocate all nodes (+ 1 root node)
1010    let default_node_data = NodeData::div();
1011
1012    let mut arena = Arena {
1013        node_hierarchy: NodeHierarchy { internal: vec![Node::ROOT; dom.estimated_total_children + 1] },
1014        node_data: NodeDataContainer { internal: vec![default_node_data; dom.estimated_total_children + 1] },
1015    };
1016
1017    let root_node_id = NodeId::ZERO;
1018    let mut cur_node_id = 0;
1019    let root_node = Node {
1020        parent: None,
1021        previous_sibling: None,
1022        next_sibling: None,
1023        first_child: if dom.children.is_empty() { None } else { Some(root_node_id + 1) },
1024        last_child: if dom.children.is_empty() { None } else { Some(root_node_id + dom.estimated_total_children) },
1025    };
1026
1027    convert_dom_into_compact_dom_internal(dom, &mut arena, root_node_id, root_node, &mut cur_node_id);
1028
1029    CompactDom {
1030        arena,
1031        root: root_node_id,
1032    }
1033}
1034
1035// note: somehow convert this into a non-recursive form later on!
1036fn convert_dom_into_compact_dom_internal(
1037    dom: Dom,
1038    arena: &mut Arena<NodeData>,
1039    parent_node_id: NodeId,
1040    node: Node,
1041    cur_node_id: &mut usize
1042) {
1043
1044    // - parent [0]
1045    //    - child [1]
1046    //    - child [2]
1047    //        - child of child 2 [2]
1048    //        - child of child 2 [4]
1049    //    - child [5]
1050    //    - child [6]
1051    //        - child of child 4 [7]
1052
1053    // Write node into the arena here!
1054    arena.node_hierarchy[parent_node_id] = node;
1055    arena.node_data[parent_node_id] = dom.root;
1056    *cur_node_id += 1;
1057
1058    let mut previous_sibling_id = None;
1059    let children_len = dom.children.len();
1060    for (child_index, child_dom) in dom.children.into_iter().enumerate() {
1061        let child_node_id = NodeId::new(*cur_node_id);
1062        let is_last_child = (child_index + 1) == children_len;
1063        let child_dom_is_empty = child_dom.children.is_empty();
1064        let child_node = Node {
1065            parent: Some(parent_node_id),
1066            previous_sibling: previous_sibling_id,
1067            next_sibling: if is_last_child { None } else { Some(child_node_id + child_dom.estimated_total_children + 1) },
1068            first_child: if child_dom_is_empty { None } else { Some(child_node_id + 1) },
1069            last_child: if child_dom_is_empty { None } else { Some(child_node_id + child_dom.estimated_total_children) },
1070        };
1071        previous_sibling_id = Some(child_node_id);
1072        // recurse BEFORE adding the next child
1073        convert_dom_into_compact_dom_internal(child_dom, arena, child_node_id, child_node, cur_node_id);
1074    }
1075}
1076
1077#[test]
1078fn test_compact_dom_conversion() {
1079
1080    use crate::dom::DomString::Static;
1081
1082    struct Dummy;
1083
1084    let dom: Dom<Dummy> = Dom::body()
1085        .with_child(Dom::div().with_class("class1"))
1086        .with_child(Dom::div().with_class("class1")
1087            .with_child(Dom::div().with_id("child_2"))
1088        )
1089        .with_child(Dom::div().with_class("class1"));
1090
1091    let expected_dom: CompactDom<Dummy> = CompactDom {
1092        root: NodeId::ZERO,
1093        arena: Arena {
1094            node_hierarchy: NodeHierarchy { internal: vec![
1095                Node /* 0 */ {
1096                    parent: None,
1097                    previous_sibling: None,
1098                    next_sibling: None,
1099                    first_child: Some(NodeId::new(1)),
1100                    last_child: Some(NodeId::new(4)),
1101                },
1102                Node /* 1 */ {
1103                    parent: Some(NodeId::new(0)),
1104                    previous_sibling: None,
1105                    next_sibling: Some(NodeId::new(2)),
1106                    first_child: None,
1107                    last_child: None,
1108                },
1109                Node /* 2 */ {
1110                    parent: Some(NodeId::new(0)),
1111                    previous_sibling: Some(NodeId::new(1)),
1112                    next_sibling: Some(NodeId::new(4)),
1113                    first_child: Some(NodeId::new(3)),
1114                    last_child: Some(NodeId::new(3)),
1115                },
1116                Node /* 3 */ {
1117                    parent: Some(NodeId::new(2)),
1118                    previous_sibling: None,
1119                    next_sibling: None,
1120                    first_child: None,
1121                    last_child: None,
1122                },
1123                Node /* 4 */ {
1124                    parent: Some(NodeId::new(0)),
1125                    previous_sibling: Some(NodeId::new(2)),
1126                    next_sibling: None,
1127                    first_child: None,
1128                    last_child: None,
1129                },
1130            ]},
1131            node_data: NodeDataContainer { internal: vec![
1132                /* 0 */    NodeData::body(),
1133                /* 1 */    NodeData::div().with_classes(vec![Static("class1")]),
1134                /* 2 */    NodeData::div().with_classes(vec![Static("class1")]),
1135                /* 3 */    NodeData::div().with_ids(vec![Static("child_2")]),
1136                /* 4 */    NodeData::div().with_classes(vec![Static("class1")]),
1137            ]},
1138        },
1139    };
1140
1141    let got_dom = convert_dom_into_compact_dom(dom);
1142    if got_dom != expected_dom {
1143        panic!("{}", format!("expected compact dom: ----\r\n{:#?}\r\n\r\ngot compact dom: ----\r\n{:#?}\r\n", expected_dom, got_dom));
1144    }
1145}
1146
1147/// Same as `Dom`, but arena-based for more efficient memory layout
1148pub struct CompactDom {
1149    pub arena: Arena<NodeData>,
1150    pub root: NodeId,
1151}
1152
1153impl From<Dom> for CompactDom {
1154    fn from(dom: Dom) -> Self {
1155        convert_dom_into_compact_dom(dom)
1156    }
1157}
1158
1159impl CompactDom {
1160    /// Returns the number of nodes in this DOM
1161    #[inline(always)]
1162    pub fn len(&self) -> usize {
1163        self.arena.len()
1164    }
1165}
1166
1167impl Clone for CompactDom {
1168    fn clone(&self) -> Self {
1169        CompactDom {
1170            arena: self.arena.clone(),
1171            root: self.root,
1172        }
1173    }
1174}
1175
1176impl PartialEq for CompactDom {
1177    fn eq(&self, rhs: &Self) -> bool {
1178        self.arena == rhs.arena &&
1179        self.root == rhs.root
1180    }
1181}
1182
1183impl Eq for CompactDom { }
1184
1185impl fmt::Debug for CompactDom {
1186    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1187        write!(f, "CompactDom {{ arena: {:?}, root: {:?} }}", self.arena, self.root)
1188    }
1189}
1190
1191impl Dom {
1192
1193    /// Creates an empty DOM with a give `NodeType`. Note: This is a `const fn` and
1194    /// doesn't allocate, it only allocates once you add at least one child node.
1195    #[inline]
1196    pub const fn new(node_type: NodeType) -> Self {
1197        Self {
1198            root: NodeData::new(node_type),
1199            children: Vec::new(),
1200            estimated_total_children: 0,
1201        }
1202    }
1203
1204    /// Creates an empty DOM with space reserved for `cap` nodes
1205    #[inline]
1206    pub fn with_capacity(node_type: NodeType, cap: usize) -> Self {
1207        Self {
1208            root: NodeData::new(node_type),
1209            children: Vec::with_capacity(cap),
1210            estimated_total_children: 0,
1211        }
1212    }
1213
1214    /// Shorthand for `Dom::new(NodeType::Div)`.
1215    #[inline(always)]
1216    pub const fn div() -> Self {
1217        Self::new(NodeType::Div)
1218    }
1219
1220    /// Shorthand for `Dom::new(NodeType::Body)`.
1221    #[inline(always)]
1222    pub const fn body() -> Self {
1223        Self::new(NodeType::Body)
1224    }
1225
1226    /// Shorthand for `Dom::new(NodeType::Label(value.into()))`
1227    #[inline(always)]
1228    pub fn label<S: Into<DomString>>(value: S) -> Self {
1229        Self::new(NodeType::Label(value.into()))
1230    }
1231
1232    /// Shorthand for `Dom::new(NodeType::Text(text_id))`
1233    #[inline(always)]
1234    pub const fn text_id(text_id: TextId) -> Self {
1235        Self::new(NodeType::Text(text_id))
1236    }
1237
1238    /// Shorthand for `Dom::new(NodeType::Image(image_id))`
1239    #[inline(always)]
1240    pub const fn image(image: ImageId) -> Self {
1241        Self::new(NodeType::Image(image))
1242    }
1243
1244    /// Shorthand for `Dom::new(NodeType::GlTexture((callback, ptr)))`
1245    #[inline(always)]
1246    #[cfg(feature = "opengl")]
1247    pub fn gl_texture<I: Into<RefAny>>(callback: GlCallbackType, ptr: I) -> Self {
1248        Self::new(NodeType::GlTexture((GlCallback(callback), ptr.into())))
1249    }
1250
1251    /// Shorthand for `Dom::new(NodeType::IFrame((callback, ptr)))`
1252    #[inline(always)]
1253    pub fn iframe<I: Into<RefAny>>(callback: IFrameCallbackType, ptr: I) -> Self {
1254        Self::new(NodeType::IFrame((IFrameCallback(callback), ptr.into())))
1255    }
1256
1257    /// Adds a child DOM to the current DOM
1258    #[inline]
1259    pub fn add_child(&mut self, child: Self) {
1260        self.estimated_total_children += child.estimated_total_children;
1261        self.estimated_total_children += 1;
1262        self.children.push(child);
1263    }
1264
1265    /// Same as `id`, but easier to use for method chaining in a builder-style pattern
1266    #[inline]
1267    pub fn with_id<S: Into<DomString>>(mut self, id: S) -> Self {
1268        self.add_id(id);
1269        self
1270    }
1271
1272    /// Same as `id`, but easier to use for method chaining in a builder-style pattern
1273    #[inline]
1274    pub fn with_class<S: Into<DomString>>(mut self, class: S) -> Self {
1275        self.add_class(class);
1276        self
1277    }
1278
1279    /// Same as `event`, but easier to use for method chaining in a builder-style pattern
1280    #[inline]
1281    pub fn with_callback<O: Into<EventFilter>>(mut self, on: O, callback: CallbackType, ptr: RefAny) -> Self {
1282        self.add_callback(on, callback, ptr);
1283        self
1284    }
1285
1286    #[inline]
1287    pub fn with_child(mut self, child: Self) -> Self {
1288        self.add_child(child);
1289        self
1290    }
1291
1292    #[inline]
1293    pub fn with_css_override<S: Into<DomString>>(mut self, id: S, property: CssProperty) -> Self {
1294        self.add_css_override(id, property);
1295        self
1296    }
1297
1298    #[inline]
1299    pub fn with_tab_index(mut self, tab_index: TabIndex) -> Self {
1300        self.set_tab_index(tab_index);
1301        self
1302    }
1303
1304    #[inline]
1305    pub fn is_draggable(mut self, draggable: bool) -> Self {
1306        self.set_draggable(draggable);
1307        self
1308    }
1309
1310    #[inline]
1311    pub fn add_id<S: Into<DomString>>(&mut self, id: S) {
1312        self.root.ids.push(id.into());
1313    }
1314
1315    #[inline]
1316    pub fn add_class<S: Into<DomString>>(&mut self, class: S) {
1317        self.root.classes.push(class.into());
1318    }
1319
1320    #[inline]
1321    pub fn add_callback<O: Into<EventFilter>>(&mut self, on: O, callback: CallbackType, ptr: RefAny) {
1322        self.root.callbacks.push((on.into(), (Callback(callback), ptr)));
1323    }
1324
1325    #[inline]
1326    pub fn add_css_override<S: Into<DomString>, P: Into<CssProperty>>(&mut self, override_id: S, property: P) {
1327        self.root.dynamic_css_overrides.push((override_id.into(), property.into()));
1328    }
1329
1330    #[inline]
1331    pub fn set_tab_index(&mut self, tab_index: TabIndex) {
1332        self.root.tab_index = Some(tab_index);
1333    }
1334
1335    #[inline]
1336    pub fn set_draggable(&mut self, draggable: bool) {
1337        self.root.is_draggable = draggable;
1338    }
1339
1340    /// Returns a HTML-formatted version of the DOM for easier debugging, i.e.
1341    ///
1342    /// ```rust,no_run,ignore
1343    /// Dom::div().with_id("hello")
1344    ///     .with_child(Dom::div().with_id("test"))
1345    /// ```
1346    ///
1347    /// will return:
1348    ///
1349    /// ```xml,no_run,ignore
1350    /// <div id="hello">
1351    ///      <div id="test" />
1352    /// </div>
1353    /// ```
1354    pub fn get_html_string(&self) -> String {
1355        let mut output = String::new();
1356        get_html_string_inner(self, &mut output, 0);
1357        output.trim().to_string()
1358    }
1359}
1360
1361fn get_html_string_inner(dom: &Dom, output: &mut String, indent: usize) {
1362    let tabs = String::from("    ").repeat(indent);
1363
1364    let content = dom.root.node_type.get_text_content();
1365    let print_self_closing_tag = dom.children.is_empty() && content.is_none();
1366
1367    output.push_str("\r\n");
1368    output.push_str(&tabs);
1369    output.push_str(&dom.root.debug_print_start(print_self_closing_tag));
1370
1371    if let Some(content) = &content {
1372        output.push_str("\r\n");
1373        output.push_str(&tabs);
1374        output.push_str(&"    ");
1375        output.push_str(content);
1376    }
1377
1378    if !print_self_closing_tag {
1379
1380        for c in &dom.children {
1381            get_html_string_inner(c, output, indent + 1);
1382        }
1383
1384        output.push_str("\r\n");
1385        output.push_str(&tabs);
1386        output.push_str(&dom.root.debug_print_end());
1387    }
1388}
1389
1390#[test]
1391fn test_dom_sibling_1() {
1392
1393    struct TestLayout;
1394
1395    let dom: Dom<TestLayout> =
1396        Dom::div()
1397            .with_child(
1398                Dom::div()
1399                .with_id("sibling-1")
1400                .with_child(Dom::div()
1401                    .with_id("sibling-1-child-1")))
1402            .with_child(Dom::div()
1403                .with_id("sibling-2")
1404                .with_child(Dom::div()
1405                    .with_id("sibling-2-child-1")));
1406
1407    let dom = convert_dom_into_compact_dom(dom);
1408
1409    let arena = &dom.arena;
1410
1411    assert_eq!(NodeId::new(0), dom.root);
1412
1413    assert_eq!(vec![DomString::Static("sibling-1")],
1414        arena.node_data[
1415            arena.node_hierarchy[dom.root]
1416            .first_child.expect("root has no first child")
1417        ].ids);
1418
1419    assert_eq!(vec![DomString::Static("sibling-2")],
1420        arena.node_data[
1421            arena.node_hierarchy[
1422                arena.node_hierarchy[dom.root]
1423                .first_child.expect("root has no first child")
1424            ].next_sibling.expect("root has no second sibling")
1425        ].ids);
1426
1427    assert_eq!(vec![DomString::Static("sibling-1-child-1")],
1428        arena.node_data[
1429            arena.node_hierarchy[
1430                arena.node_hierarchy[dom.root]
1431                .first_child.expect("root has no first child")
1432            ].first_child.expect("first child has no first child")
1433        ].ids);
1434
1435    assert_eq!(vec![DomString::Static("sibling-2-child-1")],
1436        arena.node_data[
1437            arena.node_hierarchy[
1438                arena.node_hierarchy[
1439                    arena.node_hierarchy[dom.root]
1440                    .first_child.expect("root has no first child")
1441                ].next_sibling.expect("first child has no second sibling")
1442            ].first_child.expect("second sibling has no first child")
1443        ].ids);
1444}
1445
1446#[test]
1447fn test_dom_from_iter_1() {
1448
1449    use crate::id_tree::Node;
1450
1451    struct TestLayout;
1452
1453    let dom: Dom<TestLayout> = (0..5).map(|e| NodeData::new(NodeType::Label(format!("{}", e + 1).into()))).collect();
1454    let dom = convert_dom_into_compact_dom(dom);
1455
1456    let arena = &dom.arena;
1457
1458    // We need to have 6 nodes:
1459    //
1460    // root                 NodeId(0)
1461    //   |-> 1              NodeId(1)
1462    //   |-> 2              NodeId(2)
1463    //   |-> 3              NodeId(3)
1464    //   |-> 4              NodeId(4)
1465    //   '-> 5              NodeId(5)
1466
1467    assert_eq!(arena.len(), 6);
1468
1469    // Check root node
1470    assert_eq!(arena.node_hierarchy.get(NodeId::new(0)), Some(&Node {
1471        parent: None,
1472        previous_sibling: None,
1473        next_sibling: None,
1474        first_child: Some(NodeId::new(1)),
1475        last_child: Some(NodeId::new(5)),
1476    }));
1477    assert_eq!(arena.node_data.get(NodeId::new(0)), Some(&NodeData::new(NodeType::Div)));
1478
1479    assert_eq!(arena.node_hierarchy.get(NodeId::new(arena.node_hierarchy.len() - 1)), Some(&Node {
1480        parent: Some(NodeId::new(0)),
1481        previous_sibling: Some(NodeId::new(4)),
1482        next_sibling: None,
1483        first_child: None,
1484        last_child: None,
1485    }));
1486
1487    assert_eq!(arena.node_data.get(NodeId::new(arena.node_data.len() - 1)), Some(&NodeData {
1488        node_type: NodeType::Label(DomString::Heap(String::from("5"))),
1489        .. Default::default()
1490    }));
1491}
1492
1493/// Test that there shouldn't be a DOM that has 0 nodes
1494#[test]
1495fn test_zero_size_dom() {
1496
1497    struct TestLayout;
1498
1499    let null_dom: Dom<TestLayout> = (0..0).map(|_| NodeData::default()).collect();
1500    let null_dom = convert_dom_into_compact_dom(null_dom);
1501
1502    assert!(null_dom.arena.len() == 1);
1503}