use std::{
fmt,
hash::{Hash, Hasher},
sync::atomic::{AtomicUsize, Ordering},
cmp::Ordering as CmpOrdering,
iter::FromIterator,
ffi::c_void,
};
use crate::{
callbacks::{
Callback, CallbackType,
GlCallback, GlCallbackType,
IFrameCallback, IFrameCallbackType,
RefAny,
},
app_resources::{ImageId, TextId},
id_tree::{Arena, NodeDataContainer},
};
use azul_css::{NodeTypePath, CssProperty};
pub use crate::id_tree::{NodeHierarchy, Node, NodeId};
static TAG_ID: AtomicUsize = AtomicUsize::new(1);
#[derive(Copy, Clone, PartialEq, Eq, Hash, Ord, PartialOrd)]
pub struct TagId(pub u64);
impl ::std::fmt::Display for TagId {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "ScrollTagId({})", self.0)
}
}
impl ::std::fmt::Debug for TagId {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", self)
}
}
#[derive(Copy, Clone, PartialEq, Eq, Hash, Ord, PartialOrd)]
pub struct ScrollTagId(pub TagId);
impl ::std::fmt::Display for ScrollTagId {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "ScrollTagId({})", (self.0).0)
}
}
impl ::std::fmt::Debug for ScrollTagId {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", self)
}
}
impl TagId {
pub fn new() -> Self {
TagId(TAG_ID.fetch_add(1, Ordering::SeqCst) as u64)
}
pub fn reset() {
TAG_ID.swap(1, Ordering::SeqCst);
}
}
impl ScrollTagId {
pub fn new() -> ScrollTagId {
ScrollTagId(TagId::new())
}
}
static DOM_ID: AtomicUsize = AtomicUsize::new(1);
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct DomId {
id: usize,
parent: Option<(Box<DomId>, NodeId)>,
}
impl DomId {
pub const ROOT_ID: DomId = Self { id: 0, parent: None };
#[inline(always)]
pub fn new(parent: Option<(DomId, NodeId)>) -> DomId {
DomId {
id: DOM_ID.fetch_add(1, Ordering::SeqCst),
parent: parent.map(|(p, node_id)| (Box::new(p), node_id)),
}
}
#[inline(always)]
pub fn reset() {
DOM_ID.swap(0, Ordering::SeqCst);
}
#[inline(always)]
pub fn is_root(&self) -> bool {
*self == Self::ROOT_ID
}
}
#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq, Ord, PartialOrd)]
pub struct DomHash(pub u64);
pub enum NodeType {
Div,
Body,
Label(DomString),
Text(TextId),
Image(ImageId),
GlTexture((GlCallback, RefAny)),
IFrame((IFrameCallback, RefAny)),
}
impl NodeType {
fn get_text_content(&self) -> Option<String> {
use self::NodeType::*;
match self {
Div | Body => None,
Label(s) => Some(format!("{}", s)),
Image(id) => Some(format!("image({:?})", id)),
Text(t) => Some(format!("textid({:?})", t)),
GlTexture(g) => Some(format!("gltexture({:?})", g)),
IFrame(i) => Some(format!("iframe({:?})", i)),
}
}
}
impl fmt::Debug for NodeType {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
use self::NodeType::*;
match self {
Div => write!(f, "NodeType::Div"),
Body => write!(f, "NodeType::Body"),
Label(a) => write!(f, "NodeType::Label {{ {:?} }}", a),
Text(a) => write!(f, "NodeType::Text {{ {:?} }}", a),
Image(a) => write!(f, "NodeType::Image {{ {:?} }}", a),
GlTexture((ptr, cb)) => write!(f, "NodeType::GlTexture {{ ptr: {:?}, callback: {:?} }}", ptr, cb),
IFrame((ptr, cb)) => write!(f, "NodeType::IFrame {{ ptr: {:?}, callback: {:?} }}", ptr, cb),
}
}
}
impl Clone for NodeType {
fn clone(&self) -> Self {
use self::NodeType::*;
match self {
Div => Div,
Body => Body,
Label(a) => Label(a.clone()),
Text(a) => Text(a.clone()),
Image(a) => Image(a.clone()),
GlTexture((ptr, a)) => GlTexture((ptr.clone(), a.clone())),
IFrame((ptr, a)) => IFrame((ptr.clone(), a.clone())),
}
}
}
impl Hash for NodeType {
fn hash<H>(&self, state: &mut H) where H: Hasher {
use self::NodeType::*;
use std::mem;
mem::discriminant(&self).hash(state);
match self {
Div | Body => { },
Label(a) => a.hash(state),
Text(a) => a.hash(state),
Image(a) => a.hash(state),
GlTexture((ptr, a)) => {
ptr.hash(state);
a.hash(state);
},
IFrame((ptr, a)) => {
ptr.hash(state);
a.hash(state);
},
}
}
}
impl PartialEq for NodeType {
fn eq(&self, rhs: &Self) -> bool {
use self::NodeType::*;
match (self, rhs) {
(Div, Div) => true,
(Body, Body) => true,
(Label(a), Label(b)) => a == b,
(Text(a), Text(b)) => a == b,
(Image(a), Image(b)) => a == b,
(GlTexture((ptr_a, a)), GlTexture((ptr_b, b))) => {
a == b && ptr_a == ptr_b
},
(IFrame((ptr_a, a)), IFrame((ptr_b, b))) => {
a == b && ptr_a == ptr_b
},
_ => false,
}
}
}
impl Eq for NodeType { }
impl NodeType {
#[inline]
pub fn get_path(&self) -> NodeTypePath {
use self::NodeType::*;
match self {
Div => NodeTypePath::Div,
Body => NodeTypePath::Body,
Label(_) | Text(_) => NodeTypePath::P,
Image(_) => NodeTypePath::Img,
GlTexture(_) => NodeTypePath::Texture,
IFrame(_) => NodeTypePath::IFrame,
}
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub enum On {
MouseOver,
MouseDown,
LeftMouseDown,
MiddleMouseDown,
RightMouseDown,
MouseUp,
LeftMouseUp,
MiddleMouseUp,
RightMouseUp,
MouseEnter,
MouseLeave,
Scroll,
TextInput,
VirtualKeyDown,
VirtualKeyUp,
HoveredFile,
DroppedFile,
HoveredFileCancelled,
FocusReceived,
FocusLost,
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub enum EventFilter {
Hover(HoverEventFilter),
Not(NotEventFilter),
Focus(FocusEventFilter),
Window(WindowEventFilter),
}
macro_rules! get_single_enum_type {
($fn_name:ident, $enum_name:ident::$variant:ident($return_type:ty)) => (
pub fn $fn_name(&self) -> Option<$return_type> {
use self::$enum_name::*;
match self {
$variant(e) => Some(*e),
_ => None,
}
}
)
}
impl EventFilter {
get_single_enum_type!(as_hover_event_filter, EventFilter::Hover(HoverEventFilter));
get_single_enum_type!(as_focus_event_filter, EventFilter::Focus(FocusEventFilter));
get_single_enum_type!(as_not_event_filter, EventFilter::Not(NotEventFilter));
get_single_enum_type!(as_window_event_filter, EventFilter::Window(WindowEventFilter));
}
impl From<On> for EventFilter {
fn from(input: On) -> EventFilter {
use self::On::*;
match input {
MouseOver => EventFilter::Hover(HoverEventFilter::MouseOver),
MouseDown => EventFilter::Hover(HoverEventFilter::MouseDown),
LeftMouseDown => EventFilter::Hover(HoverEventFilter::LeftMouseDown),
MiddleMouseDown => EventFilter::Hover(HoverEventFilter::MiddleMouseDown),
RightMouseDown => EventFilter::Hover(HoverEventFilter::RightMouseDown),
MouseUp => EventFilter::Hover(HoverEventFilter::MouseUp),
LeftMouseUp => EventFilter::Hover(HoverEventFilter::LeftMouseUp),
MiddleMouseUp => EventFilter::Hover(HoverEventFilter::MiddleMouseUp),
RightMouseUp => EventFilter::Hover(HoverEventFilter::RightMouseUp),
MouseEnter => EventFilter::Hover(HoverEventFilter::MouseEnter),
MouseLeave => EventFilter::Hover(HoverEventFilter::MouseLeave),
Scroll => EventFilter::Hover(HoverEventFilter::Scroll),
TextInput => EventFilter::Focus(FocusEventFilter::TextInput),
VirtualKeyDown => EventFilter::Window(WindowEventFilter::VirtualKeyDown),
VirtualKeyUp => EventFilter::Window(WindowEventFilter::VirtualKeyUp),
HoveredFile => EventFilter::Hover(HoverEventFilter::HoveredFile),
DroppedFile => EventFilter::Hover(HoverEventFilter::DroppedFile),
HoveredFileCancelled => EventFilter::Hover(HoverEventFilter::HoveredFileCancelled),
FocusReceived => EventFilter::Focus(FocusEventFilter::FocusReceived),
FocusLost => EventFilter::Focus(FocusEventFilter::FocusLost),
}
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub enum HoverEventFilter {
MouseOver,
MouseDown,
LeftMouseDown,
RightMouseDown,
MiddleMouseDown,
MouseUp,
LeftMouseUp,
RightMouseUp,
MiddleMouseUp,
MouseEnter,
MouseLeave,
Scroll,
ScrollStart,
ScrollEnd,
TextInput,
VirtualKeyDown,
VirtualKeyUp,
HoveredFile,
DroppedFile,
HoveredFileCancelled,
}
impl HoverEventFilter {
pub fn to_focus_event_filter(&self) -> Option<FocusEventFilter> {
use self::HoverEventFilter::*;
match self {
MouseOver => Some(FocusEventFilter::MouseOver),
MouseDown => Some(FocusEventFilter::MouseDown),
LeftMouseDown => Some(FocusEventFilter::LeftMouseDown),
RightMouseDown => Some(FocusEventFilter::RightMouseDown),
MiddleMouseDown => Some(FocusEventFilter::MiddleMouseDown),
MouseUp => Some(FocusEventFilter::MouseUp),
LeftMouseUp => Some(FocusEventFilter::LeftMouseUp),
RightMouseUp => Some(FocusEventFilter::RightMouseUp),
MiddleMouseUp => Some(FocusEventFilter::MiddleMouseUp),
MouseEnter => Some(FocusEventFilter::MouseEnter),
MouseLeave => Some(FocusEventFilter::MouseLeave),
Scroll => Some(FocusEventFilter::Scroll),
ScrollStart => Some(FocusEventFilter::ScrollStart),
ScrollEnd => Some(FocusEventFilter::ScrollEnd),
TextInput => Some(FocusEventFilter::TextInput),
VirtualKeyDown => Some(FocusEventFilter::VirtualKeyDown),
VirtualKeyUp => Some(FocusEventFilter::VirtualKeyDown),
HoveredFile => None,
DroppedFile => None,
HoveredFileCancelled => None,
}
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub enum NotEventFilter {
Hover(HoverEventFilter),
Focus(FocusEventFilter),
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub enum FocusEventFilter {
MouseOver,
MouseDown,
LeftMouseDown,
RightMouseDown,
MiddleMouseDown,
MouseUp,
LeftMouseUp,
RightMouseUp,
MiddleMouseUp,
MouseEnter,
MouseLeave,
Scroll,
ScrollStart,
ScrollEnd,
TextInput,
VirtualKeyDown,
VirtualKeyUp,
FocusReceived,
FocusLost,
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub enum WindowEventFilter {
MouseOver,
MouseDown,
LeftMouseDown,
RightMouseDown,
MiddleMouseDown,
MouseUp,
LeftMouseUp,
RightMouseUp,
MiddleMouseUp,
MouseEnter,
MouseLeave,
Scroll,
ScrollStart,
ScrollEnd,
TextInput,
VirtualKeyDown,
VirtualKeyUp,
HoveredFile,
DroppedFile,
HoveredFileCancelled,
}
impl WindowEventFilter {
pub fn to_hover_event_filter(&self) -> Option<HoverEventFilter> {
use self::WindowEventFilter::*;
match self {
MouseOver => Some(HoverEventFilter::MouseOver),
MouseDown => Some(HoverEventFilter::MouseDown),
LeftMouseDown => Some(HoverEventFilter::LeftMouseDown),
RightMouseDown => Some(HoverEventFilter::RightMouseDown),
MiddleMouseDown => Some(HoverEventFilter::MiddleMouseDown),
MouseUp => Some(HoverEventFilter::MouseUp),
LeftMouseUp => Some(HoverEventFilter::LeftMouseUp),
RightMouseUp => Some(HoverEventFilter::RightMouseUp),
MiddleMouseUp => Some(HoverEventFilter::MiddleMouseUp),
Scroll => Some(HoverEventFilter::Scroll),
ScrollStart => Some(HoverEventFilter::ScrollStart),
ScrollEnd => Some(HoverEventFilter::ScrollEnd),
TextInput => Some(HoverEventFilter::TextInput),
VirtualKeyDown => Some(HoverEventFilter::VirtualKeyDown),
VirtualKeyUp => Some(HoverEventFilter::VirtualKeyDown),
HoveredFile => Some(HoverEventFilter::HoveredFile),
DroppedFile => Some(HoverEventFilter::DroppedFile),
HoveredFileCancelled => Some(HoverEventFilter::HoveredFileCancelled),
MouseEnter => None,
MouseLeave => None,
}
}
}
pub struct NodeData {
node_type: NodeType,
ids: Vec<DomString>,
classes: Vec<DomString>,
callbacks: Vec<(EventFilter, (Callback, RefAny))>,
dynamic_css_overrides: Vec<(DomString, CssProperty)>,
is_draggable: bool,
tab_index: Option<TabIndex>,
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, Ord, PartialOrd, Hash)]
pub enum TabIndex {
Auto,
OverrideInParent(usize),
NoKeyboardFocus,
}
impl TabIndex {
pub fn get_index(&self) -> isize {
use self::TabIndex::*;
match self {
Auto => 0,
OverrideInParent(x) => *x as isize,
NoKeyboardFocus => -1,
}
}
}
impl Default for TabIndex {
fn default() -> Self {
TabIndex::Auto
}
}
impl PartialEq for NodeData {
fn eq(&self, other: &Self) -> bool {
self.node_type == other.node_type &&
self.ids == other.ids &&
self.classes == other.classes &&
self.callbacks == other.callbacks &&
self.dynamic_css_overrides == other.dynamic_css_overrides &&
self.is_draggable == other.is_draggable &&
self.tab_index == other.tab_index
}
}
impl Eq for NodeData { }
impl Default for NodeData {
fn default() -> Self {
NodeData::new(NodeType::Div)
}
}
impl Hash for NodeData {
fn hash<H: Hasher>(&self, state: &mut H) {
self.node_type.hash(state);
for id in &self.ids {
id.hash(state);
}
for class in &self.classes {
class.hash(state);
}
for callback in &self.callbacks {
callback.hash(state);
}
for dynamic_css_override in &self.dynamic_css_overrides {
dynamic_css_override.hash(state);
}
self.is_draggable.hash(state);
self.tab_index.hash(state);
}
}
impl Clone for NodeData {
fn clone(&self) -> Self {
Self {
node_type: self.node_type.clone(),
ids: self.ids.clone(),
classes: self.classes.clone(),
callbacks: self.callbacks.clone(),
dynamic_css_overrides: self.dynamic_css_overrides.clone(),
is_draggable: self.is_draggable.clone(),
tab_index: self.tab_index.clone(),
}
}
}
impl fmt::Display for NodeData {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let html_type = self.node_type.get_path();
let attributes_string = node_data_to_string(&self);
match self.node_type.get_text_content() {
Some(content) => write!(f, "<{}{}>{}</{}>", html_type, attributes_string, content, html_type),
None => write!(f, "<{}{}/>", html_type, attributes_string)
}
}
}
impl NodeData {
pub fn debug_print_start(&self, close_self: bool) -> String {
let html_type = self.node_type.get_path();
let attributes_string = node_data_to_string(&self);
format!("<{}{}{}>", html_type, attributes_string, if close_self { " /" } else { "" })
}
pub fn debug_print_end(&self) -> String {
let html_type = self.node_type.get_path();
format!("</{}>", html_type)
}
}
fn node_data_to_string(node_data: &NodeData) -> String {
let id_string = if node_data.ids.is_empty() {
String::new()
} else {
format!(" id=\"{}\"", node_data.ids.iter().map(|s| s.as_str().to_string()).collect::<Vec<String>>().join(" "))
};
let class_string = if node_data.classes.is_empty() {
String::new()
} else {
format!(" class=\"{}\"", node_data.classes.iter().map(|s| s.as_str().to_string()).collect::<Vec<String>>().join(" "))
};
let draggable = if node_data.is_draggable {
format!(" draggable=\"true\"")
} else {
String::new()
};
let tabindex = if let Some(tab_index) = node_data.tab_index {
format!(" tabindex=\"{}\"", tab_index.get_index())
} else {
String::new()
};
let callbacks = if node_data.callbacks.is_empty() {
String::new()
} else {
format!(" callbacks=\"{}\"", node_data.callbacks.iter().map(|(evt, cb)| format!("({:?}={:?})", evt, cb)).collect::<Vec<String>>().join(" "))
};
let css_overrides = if node_data.dynamic_css_overrides.is_empty() {
String::new()
} else {
format!(" css-overrides=\"{}\"", node_data.dynamic_css_overrides.iter().map(|(id, prop)| format!("{}={:?};", id, prop)).collect::<Vec<String>>().join(" "))
};
format!("{}{}{}{}{}{}", id_string, class_string, tabindex, draggable, callbacks, css_overrides)
}
impl fmt::Debug for NodeData {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "NodeData {{")?;
write!(f, "\tnode_type: {:?}", self.node_type)?;
if !self.ids.is_empty() { write!(f, "\tids: {:?}", self.ids)?; }
if !self.classes.is_empty() { write!(f, "\tclasses: {:?}", self.classes)?; }
if !self.callbacks.is_empty() { write!(f, "\tcallbacks: {:?}", self.callbacks)?; }
if !self.dynamic_css_overrides.is_empty() { write!(f, "\tdynamic_css_overrides: {:?}", self.dynamic_css_overrides)?; }
if self.is_draggable { write!(f, "\tis_draggable: {:?}", self.is_draggable)?; }
if let Some(t) = self.tab_index { write!(f, "\ttab_index: {:?}", t)?; }
write!(f, "}}")?;
Ok(())
}
}
impl NodeData {
#[inline]
pub const fn new(node_type: NodeType) -> Self {
Self {
node_type,
ids: Vec::new(),
classes: Vec::new(),
callbacks: Vec::new(),
dynamic_css_overrides: Vec::new(),
is_draggable: false,
tab_index: None,
}
}
#[inline]
pub fn is_node_type(&self, searched_type: NodeType) -> bool {
self.node_type == searched_type
}
pub fn has_id(&self, id: &str) -> bool {
self.ids.iter().any(|self_id| self_id.equals_str(id))
}
pub fn has_class(&self, class: &str) -> bool {
self.classes.iter().any(|self_class| self_class.equals_str(class))
}
pub fn calculate_node_data_hash(&self) -> DomHash {
use std::collections::hash_map::DefaultHasher as HashAlgorithm;
let mut hasher = HashAlgorithm::default();
self.hash(&mut hasher);
DomHash(hasher.finish())
}
#[inline(always)]
pub const fn body() -> Self {
Self::new(NodeType::Body)
}
#[inline(always)]
pub const fn div() -> Self {
Self::new(NodeType::Div)
}
#[inline(always)]
pub fn label<S: Into<DomString>>(value: S) -> Self {
Self::new(NodeType::Label(value.into()))
}
#[inline(always)]
pub const fn text_id(text_id: TextId) -> Self {
Self::new(NodeType::Text(text_id))
}
#[inline(always)]
pub const fn image(image: ImageId) -> Self {
Self::new(NodeType::Image(image))
}
#[inline(always)]
pub fn gl_texture(callback: GlCallbackType, ptr: RefAny) -> Self {
Self::new(NodeType::GlTexture((GlCallback(callback), ptr)))
}
#[inline(always)]
pub fn iframe(callback: IFrameCallbackType, ptr: RefAny) -> Self {
Self::new(NodeType::IFrame((IFrameCallback(callback), ptr)))
}
#[inline(always)]
pub const fn get_node_type(&self) -> &NodeType { &self.node_type }
#[inline(always)]
pub const fn get_ids(&self) -> &Vec<DomString> { &self.ids }
#[inline(always)]
pub const fn get_classes(&self) -> &Vec<DomString> { &self.classes }
#[inline(always)]
pub const fn get_callbacks(&self) -> &Vec<(EventFilter, (Callback, RefAny))> { &self.callbacks }
#[inline(always)]
pub const fn get_dynamic_css_overrides(&self) -> &Vec<(DomString, CssProperty)> { &self.dynamic_css_overrides }
#[inline(always)]
pub const fn get_is_draggable(&self) -> bool { self.is_draggable }
#[inline(always)]
pub const fn get_tab_index(&self) -> Option<TabIndex> { self.tab_index }
#[inline(always)]
pub fn set_node_type(&mut self, node_type: NodeType) { self.node_type = node_type; }
#[inline(always)]
pub fn set_ids(&mut self, ids: Vec<DomString>) { self.ids = ids; }
#[inline(always)]
pub fn set_classes(&mut self, classes: Vec<DomString>) { self.classes = classes; }
#[inline(always)]
pub fn set_callbacks(&mut self, callbacks: Vec<(EventFilter, (Callback, RefAny))>) { self.callbacks = callbacks; }
#[inline(always)]
pub fn set_dynamic_css_overrides(&mut self, dynamic_css_overrides: Vec<(DomString, CssProperty)>) { self.dynamic_css_overrides = dynamic_css_overrides; }
#[inline(always)]
pub fn set_is_draggable(&mut self, is_draggable: bool) { self.is_draggable = is_draggable; }
#[inline(always)]
pub fn set_tab_index(&mut self, tab_index: Option<TabIndex>) { self.tab_index = tab_index; }
#[inline(always)]
pub fn with_node_type(self, node_type: NodeType) -> Self { Self { node_type, .. self } }
#[inline(always)]
pub fn with_ids(self, ids: Vec<DomString>) -> Self { Self { ids, .. self } }
#[inline(always)]
pub fn with_classes(self, classes: Vec<DomString>) -> Self { Self { classes, .. self } }
#[inline(always)]
pub fn with_callbacks(self, callbacks: Vec<(EventFilter, (Callback, RefAny))>) -> Self { Self { callbacks, .. self } }
#[inline(always)]
pub fn with_dynamic_css_overrides(self, dynamic_css_overrides: Vec<(DomString, CssProperty)>) -> Self { Self { dynamic_css_overrides, .. self } }
#[inline(always)]
pub fn is_draggable(self, is_draggable: bool) -> Self { Self { is_draggable, .. self } }
#[inline(always)]
pub fn with_tab_index(self, tab_index: Option<TabIndex>) -> Self { Self { tab_index, .. self } }
}
#[derive(Clone)]
pub enum DomString {
Static(&'static str),
Heap(String),
}
impl Eq for DomString { }
impl PartialEq for DomString {
fn eq(&self, other: &Self) -> bool {
self.as_str() == other.as_str()
}
}
impl PartialOrd for DomString {
fn partial_cmp(&self, other: &Self) -> Option<CmpOrdering> {
Some(self.as_str().cmp(other.as_str()))
}
}
impl Ord for DomString {
fn cmp(&self, other: &Self) -> CmpOrdering {
self.as_str().cmp(other.as_str())
}
}
impl Hash for DomString {
fn hash<H: Hasher>(&self, state: &mut H) {
self.as_str().hash(state);
}
}
impl DomString {
pub fn equals_str(&self, target: &str) -> bool {
use self::DomString::*;
match &self {
Static(s) => *s == target,
Heap(h) => h == target,
}
}
pub fn as_str(&self) -> &str {
use self::DomString::*;
match &self {
Static(s) => s,
Heap(h) => h,
}
}
}
impl fmt::Debug for DomString {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
use self::DomString::*;
match &self {
Static(s) => write!(f, "\"{}\"", s),
Heap(h) => write!(f, "\"{}\"", h),
}
}
}
impl fmt::Display for DomString {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
use self::DomString::*;
match &self {
Static(s) => write!(f, "{}", s),
Heap(h) => write!(f, "{}", h),
}
}
}
impl From<String> for DomString {
fn from(e: String) -> Self {
DomString::Heap(e)
}
}
impl From<&'static str> for DomString {
fn from(e: &'static str) -> Self {
DomString::Static(e)
}
}
pub struct Dom {
pub root: NodeData,
pub children: Vec<Dom>,
estimated_total_children: usize,
}
#[no_mangle] #[repr(C)] pub struct DomPtr { pub ptr: *mut c_void }
impl Clone for Dom {
fn clone(&self) -> Self {
Dom {
root: self.root.clone(),
children: self.children.clone(),
estimated_total_children: self.estimated_total_children,
}
}
}
fn compare_dom(a: &Dom, b: &Dom) -> bool {
a.root == b.root &&
a.estimated_total_children == b.estimated_total_children &&
a.children.len() == b.children.len() &&
a.children.iter().zip(b.children.iter()).all(|(a, b)| compare_dom(a, b))
}
impl PartialEq for Dom {
fn eq(&self, rhs: &Self) -> bool {
compare_dom(self, rhs)
}
}
impl Eq for Dom { }
fn print_dom(d: &Dom, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "Dom {{\r\n")?;
write!(f, "\troot: {:#?}", d.root)?;
write!(f, "\testimated_total_children: {:#?}", d.estimated_total_children)?;
write!(f, "\tchildren: [")?;
for c in &d.children {
print_dom(c, f)?;
}
write!(f, "\t]")?;
write!(f, "}}")?;
Ok(())
}
impl fmt::Debug for Dom {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
print_dom(self, f)
}
}
impl FromIterator<Dom> for Dom {
fn from_iter<I: IntoIterator<Item=Dom>>(iter: I) -> Self {
let mut estimated_total_children = 0;
let children = iter.into_iter().map(|c| {
estimated_total_children += c.estimated_total_children + 1;
c
}).collect();
Dom {
root: NodeData::div(),
children,
estimated_total_children,
}
}
}
impl FromIterator<NodeData> for Dom {
fn from_iter<I: IntoIterator<Item=NodeData>>(iter: I) -> Self {
let children = iter.into_iter().map(|c| Dom { root: c, children: Vec::new(), estimated_total_children: 0 }).collect::<Vec<_>>();
let estimated_total_children = children.len();
Dom {
root: NodeData::div(),
children,
estimated_total_children,
}
}
}
impl FromIterator<NodeType> for Dom {
fn from_iter<I: IntoIterator<Item=NodeType>>(iter: I) -> Self {
iter.into_iter().map(|i| NodeData { node_type: i, .. Default::default() }).collect()
}
}
pub(crate) fn convert_dom_into_compact_dom(dom: Dom) -> CompactDom {
let default_node_data = NodeData::div();
let mut arena = Arena {
node_hierarchy: NodeHierarchy { internal: vec![Node::ROOT; dom.estimated_total_children + 1] },
node_data: NodeDataContainer { internal: vec![default_node_data; dom.estimated_total_children + 1] },
};
let root_node_id = NodeId::ZERO;
let mut cur_node_id = 0;
let root_node = Node {
parent: None,
previous_sibling: None,
next_sibling: None,
first_child: if dom.children.is_empty() { None } else { Some(root_node_id + 1) },
last_child: if dom.children.is_empty() { None } else { Some(root_node_id + dom.estimated_total_children) },
};
convert_dom_into_compact_dom_internal(dom, &mut arena, root_node_id, root_node, &mut cur_node_id);
CompactDom {
arena,
root: root_node_id,
}
}
fn convert_dom_into_compact_dom_internal(
dom: Dom,
arena: &mut Arena<NodeData>,
parent_node_id: NodeId,
node: Node,
cur_node_id: &mut usize
) {
arena.node_hierarchy[parent_node_id] = node;
arena.node_data[parent_node_id] = dom.root;
*cur_node_id += 1;
let mut previous_sibling_id = None;
let children_len = dom.children.len();
for (child_index, child_dom) in dom.children.into_iter().enumerate() {
let child_node_id = NodeId::new(*cur_node_id);
let is_last_child = (child_index + 1) == children_len;
let child_dom_is_empty = child_dom.children.is_empty();
let child_node = Node {
parent: Some(parent_node_id),
previous_sibling: previous_sibling_id,
next_sibling: if is_last_child { None } else { Some(child_node_id + child_dom.estimated_total_children + 1) },
first_child: if child_dom_is_empty { None } else { Some(child_node_id + 1) },
last_child: if child_dom_is_empty { None } else { Some(child_node_id + child_dom.estimated_total_children) },
};
previous_sibling_id = Some(child_node_id);
convert_dom_into_compact_dom_internal(child_dom, arena, child_node_id, child_node, cur_node_id);
}
}
#[test]
fn test_compact_dom_conversion() {
use crate::dom::DomString::Static;
struct Dummy;
let dom: Dom<Dummy> = Dom::body()
.with_child(Dom::div().with_class("class1"))
.with_child(Dom::div().with_class("class1")
.with_child(Dom::div().with_id("child_2"))
)
.with_child(Dom::div().with_class("class1"));
let expected_dom: CompactDom<Dummy> = CompactDom {
root: NodeId::ZERO,
arena: Arena {
node_hierarchy: NodeHierarchy { internal: vec![
Node {
parent: None,
previous_sibling: None,
next_sibling: None,
first_child: Some(NodeId::new(1)),
last_child: Some(NodeId::new(4)),
},
Node {
parent: Some(NodeId::new(0)),
previous_sibling: None,
next_sibling: Some(NodeId::new(2)),
first_child: None,
last_child: None,
},
Node {
parent: Some(NodeId::new(0)),
previous_sibling: Some(NodeId::new(1)),
next_sibling: Some(NodeId::new(4)),
first_child: Some(NodeId::new(3)),
last_child: Some(NodeId::new(3)),
},
Node {
parent: Some(NodeId::new(2)),
previous_sibling: None,
next_sibling: None,
first_child: None,
last_child: None,
},
Node {
parent: Some(NodeId::new(0)),
previous_sibling: Some(NodeId::new(2)),
next_sibling: None,
first_child: None,
last_child: None,
},
]},
node_data: NodeDataContainer { internal: vec![
NodeData::body(),
NodeData::div().with_classes(vec![Static("class1")]),
NodeData::div().with_classes(vec![Static("class1")]),
NodeData::div().with_ids(vec![Static("child_2")]),
NodeData::div().with_classes(vec![Static("class1")]),
]},
},
};
let got_dom = convert_dom_into_compact_dom(dom);
if got_dom != expected_dom {
panic!("{}", format!("expected compact dom: ----\r\n{:#?}\r\n\r\ngot compact dom: ----\r\n{:#?}\r\n", expected_dom, got_dom));
}
}
pub struct CompactDom {
pub arena: Arena<NodeData>,
pub root: NodeId,
}
impl From<Dom> for CompactDom {
fn from(dom: Dom) -> Self {
convert_dom_into_compact_dom(dom)
}
}
impl CompactDom {
#[inline(always)]
pub fn len(&self) -> usize {
self.arena.len()
}
}
impl Clone for CompactDom {
fn clone(&self) -> Self {
CompactDom {
arena: self.arena.clone(),
root: self.root,
}
}
}
impl PartialEq for CompactDom {
fn eq(&self, rhs: &Self) -> bool {
self.arena == rhs.arena &&
self.root == rhs.root
}
}
impl Eq for CompactDom { }
impl fmt::Debug for CompactDom {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "CompactDom {{ arena: {:?}, root: {:?} }}", self.arena, self.root)
}
}
impl Dom {
#[inline]
pub const fn new(node_type: NodeType) -> Self {
Self {
root: NodeData::new(node_type),
children: Vec::new(),
estimated_total_children: 0,
}
}
#[inline]
pub fn with_capacity(node_type: NodeType, cap: usize) -> Self {
Self {
root: NodeData::new(node_type),
children: Vec::with_capacity(cap),
estimated_total_children: 0,
}
}
#[inline(always)]
pub const fn div() -> Self {
Self::new(NodeType::Div)
}
#[inline(always)]
pub const fn body() -> Self {
Self::new(NodeType::Body)
}
#[inline(always)]
pub fn label<S: Into<DomString>>(value: S) -> Self {
Self::new(NodeType::Label(value.into()))
}
#[inline(always)]
pub const fn text_id(text_id: TextId) -> Self {
Self::new(NodeType::Text(text_id))
}
#[inline(always)]
pub const fn image(image: ImageId) -> Self {
Self::new(NodeType::Image(image))
}
#[inline(always)]
pub fn gl_texture<I: Into<RefAny>>(callback: GlCallbackType, ptr: I) -> Self {
Self::new(NodeType::GlTexture((GlCallback(callback), ptr.into())))
}
#[inline(always)]
pub fn iframe<I: Into<RefAny>>(callback: IFrameCallbackType, ptr: I) -> Self {
Self::new(NodeType::IFrame((IFrameCallback(callback), ptr.into())))
}
#[inline]
pub fn add_child(&mut self, child: Self) {
self.estimated_total_children += child.estimated_total_children;
self.estimated_total_children += 1;
self.children.push(child);
}
#[inline]
pub fn with_id<S: Into<DomString>>(mut self, id: S) -> Self {
self.add_id(id);
self
}
#[inline]
pub fn with_class<S: Into<DomString>>(mut self, class: S) -> Self {
self.add_class(class);
self
}
#[inline]
pub fn with_callback<O: Into<EventFilter>>(mut self, on: O, callback: CallbackType, ptr: RefAny) -> Self {
self.add_callback(on, callback, ptr);
self
}
#[inline]
pub fn with_child(mut self, child: Self) -> Self {
self.add_child(child);
self
}
#[inline]
pub fn with_css_override<S: Into<DomString>>(mut self, id: S, property: CssProperty) -> Self {
self.add_css_override(id, property);
self
}
#[inline]
pub fn with_tab_index(mut self, tab_index: TabIndex) -> Self {
self.set_tab_index(tab_index);
self
}
#[inline]
pub fn is_draggable(mut self, draggable: bool) -> Self {
self.set_draggable(draggable);
self
}
#[inline]
pub fn add_id<S: Into<DomString>>(&mut self, id: S) {
self.root.ids.push(id.into());
}
#[inline]
pub fn add_class<S: Into<DomString>>(&mut self, class: S) {
self.root.classes.push(class.into());
}
#[inline]
pub fn add_callback<O: Into<EventFilter>>(&mut self, on: O, callback: CallbackType, ptr: RefAny) {
self.root.callbacks.push((on.into(), (Callback(callback), ptr)));
}
#[inline]
pub fn add_css_override<S: Into<DomString>, P: Into<CssProperty>>(&mut self, override_id: S, property: P) {
self.root.dynamic_css_overrides.push((override_id.into(), property.into()));
}
#[inline]
pub fn set_tab_index(&mut self, tab_index: TabIndex) {
self.root.tab_index = Some(tab_index);
}
#[inline]
pub fn set_draggable(&mut self, draggable: bool) {
self.root.is_draggable = draggable;
}
pub fn get_html_string(&self) -> String {
let mut output = String::new();
get_html_string_inner(self, &mut output, 0);
output.trim().to_string()
}
}
fn get_html_string_inner(dom: &Dom, output: &mut String, indent: usize) {
let tabs = String::from(" ").repeat(indent);
let content = dom.root.node_type.get_text_content();
let print_self_closing_tag = dom.children.is_empty() && content.is_none();
output.push_str("\r\n");
output.push_str(&tabs);
output.push_str(&dom.root.debug_print_start(print_self_closing_tag));
if let Some(content) = &content {
output.push_str("\r\n");
output.push_str(&tabs);
output.push_str(&" ");
output.push_str(content);
}
if !print_self_closing_tag {
for c in &dom.children {
get_html_string_inner(c, output, indent + 1);
}
output.push_str("\r\n");
output.push_str(&tabs);
output.push_str(&dom.root.debug_print_end());
}
}
#[test]
fn test_dom_sibling_1() {
struct TestLayout;
let dom: Dom<TestLayout> =
Dom::div()
.with_child(
Dom::div()
.with_id("sibling-1")
.with_child(Dom::div()
.with_id("sibling-1-child-1")))
.with_child(Dom::div()
.with_id("sibling-2")
.with_child(Dom::div()
.with_id("sibling-2-child-1")));
let dom = convert_dom_into_compact_dom(dom);
let arena = &dom.arena;
assert_eq!(NodeId::new(0), dom.root);
assert_eq!(vec![DomString::Static("sibling-1")],
arena.node_data[
arena.node_hierarchy[dom.root]
.first_child.expect("root has no first child")
].ids);
assert_eq!(vec![DomString::Static("sibling-2")],
arena.node_data[
arena.node_hierarchy[
arena.node_hierarchy[dom.root]
.first_child.expect("root has no first child")
].next_sibling.expect("root has no second sibling")
].ids);
assert_eq!(vec![DomString::Static("sibling-1-child-1")],
arena.node_data[
arena.node_hierarchy[
arena.node_hierarchy[dom.root]
.first_child.expect("root has no first child")
].first_child.expect("first child has no first child")
].ids);
assert_eq!(vec![DomString::Static("sibling-2-child-1")],
arena.node_data[
arena.node_hierarchy[
arena.node_hierarchy[
arena.node_hierarchy[dom.root]
.first_child.expect("root has no first child")
].next_sibling.expect("first child has no second sibling")
].first_child.expect("second sibling has no first child")
].ids);
}
#[test]
fn test_dom_from_iter_1() {
use crate::id_tree::Node;
struct TestLayout;
let dom: Dom<TestLayout> = (0..5).map(|e| NodeData::new(NodeType::Label(format!("{}", e + 1).into()))).collect();
let dom = convert_dom_into_compact_dom(dom);
let arena = &dom.arena;
assert_eq!(arena.len(), 6);
assert_eq!(arena.node_hierarchy.get(NodeId::new(0)), Some(&Node {
parent: None,
previous_sibling: None,
next_sibling: None,
first_child: Some(NodeId::new(1)),
last_child: Some(NodeId::new(5)),
}));
assert_eq!(arena.node_data.get(NodeId::new(0)), Some(&NodeData::new(NodeType::Div)));
assert_eq!(arena.node_hierarchy.get(NodeId::new(arena.node_hierarchy.len() - 1)), Some(&Node {
parent: Some(NodeId::new(0)),
previous_sibling: Some(NodeId::new(4)),
next_sibling: None,
first_child: None,
last_child: None,
}));
assert_eq!(arena.node_data.get(NodeId::new(arena.node_data.len() - 1)), Some(&NodeData {
node_type: NodeType::Label(DomString::Heap(String::from("5"))),
.. Default::default()
}));
}
#[test]
fn test_zero_size_dom() {
struct TestLayout;
let null_dom: Dom<TestLayout> = (0..0).map(|_| NodeData::default()).collect();
let null_dom = convert_dom_into_compact_dom(null_dom);
assert!(null_dom.arena.len() == 1);
}