diff --git a/packages/native-core/src/custom_element.rs b/packages/native-core/src/custom_element.rs new file mode 100644 index 000000000..8f6142076 --- /dev/null +++ b/packages/native-core/src/custom_element.rs @@ -0,0 +1,134 @@ +use std::{ + cell::RefCell, + sync::{Arc, RwLock}, +}; + +use rustc_hash::FxHashMap; +use shipyard::Component; + +use crate::{ + node::{FromAnyValue, NodeType}, + node_ref::AttributeMask, + prelude::{NodeImmutable, NodeMut, RealDom}, + real_dom::NodeTypeMut, + shadow_dom::ShadowDom, + NodeId, +}; + +#[derive(Default)] +pub(crate) struct CustomElementRegistry { + builders: FxHashMap<&'static str, CustomElementBuilder>, +} + +impl CustomElementRegistry { + pub fn register>(&mut self) { + self.builders.insert( + W::NAME, + CustomElementBuilder { + create: |dom, light_root_id| Box::new(W::create(dom, light_root_id)), + }, + ); + } + + pub fn add_shadow_dom(&self, mut node: NodeMut) { + let element_tag = if let NodeType::Element(el) = &*node.node_type() { + Some(el.tag.clone()) + } else { + None + }; + if let Some(element_tag) = element_tag { + if let Some(builder) = self.builders.get(element_tag.as_str()) { + let boxed_widget = { + let light_root_id = node.id(); + let dom = node.real_dom_mut(); + (builder.create)(dom, light_root_id) + }; + + let boxed_widget = CustomElementManager { + inner: Arc::new(RwLock::new(boxed_widget)), + }; + + let NodeTypeMut::Element(mut el) = node.node_type_mut()else{ + panic!("The type of the light element should not change when creating a shadow DOM") + }; + + *el.shadow_root_mut() = Some(ShadowDom::new(boxed_widget).into()); + } + } + } +} + +struct CustomElementBuilder { + create: fn(&mut RealDom, NodeId) -> Box>, +} + +/// A controlled element that renders to a shadow DOM +pub trait CustomElement: Send + Sync + 'static { + /// The tag the widget is registered under. + const NAME: &'static str; + + /// Create a new widget without mounting it. + fn create(dom: &mut RealDom, light_root_id: NodeId) -> Self; + + /// The root node of the widget. + fn root(&self) -> NodeId; + + /// Called when the attributes of the widget are changed. + fn attributes_changed(&mut self, _dom: &mut RealDom, _attributes: &AttributeMask); +} + +/// A factory for creating widgets +trait ElementFactory, V: FromAnyValue + Send + Sync = ()>: + Send + Sync + 'static +{ + /// The tag the widget is registered under. + const NAME: &'static str; + + /// Create a new widget. + fn create(dom: &mut RealDom, light_root_id: NodeId) -> W; +} + +impl, V: FromAnyValue + Send + Sync> ElementFactory for W { + const NAME: &'static str = W::NAME; + + fn create(dom: &mut RealDom, light_root_id: NodeId) -> Self { + Self::create(dom, light_root_id) + } +} + +/// A trait for updating widgets +trait CustomElementUpdater: Send + Sync + 'static { + /// Called when the attributes of the widget are changed. + fn attributes_changed(&mut self, _dom: &mut RealDom, _attributes: &AttributeMask); + + /// The root node of the widget. + fn root(&self) -> NodeId; +} + +impl, V: FromAnyValue + Send + Sync> CustomElementUpdater for W { + fn attributes_changed(&mut self, root: &mut RealDom, attributes: &AttributeMask) { + self.attributes_changed(root, attributes); + } + + fn root(&self) -> NodeId { + self.root() + } +} + +pub struct CustomElementManager { + inner: Arc>>>, +} + +impl CustomElementManager { + pub fn root(&self) -> NodeId { + self.inner.read().unwrap().root() + } +} + +impl Clone for CustomElementManager { + fn clone(&self) -> Self { + Self { + inner: self.inner.clone(), + } + } +} diff --git a/packages/native-core/src/dioxus.rs b/packages/native-core/src/dioxus.rs index 3e9ff9ee0..9d50b1224 100644 --- a/packages/native-core/src/dioxus.rs +++ b/packages/native-core/src/dioxus.rs @@ -262,6 +262,7 @@ fn create_template_node( }) .collect(), listeners: FxHashSet::default(), + shadow: None, }); let node_id = rdom.create_node(node).id(); for child in *children { diff --git a/packages/native-core/src/lib.rs b/packages/native-core/src/lib.rs index ca8e8e5ee..6dfeb81cd 100644 --- a/packages/native-core/src/lib.rs +++ b/packages/native-core/src/lib.rs @@ -7,6 +7,7 @@ use std::hash::BuildHasherDefault; use node_ref::NodeMask; use rustc_hash::FxHasher; +pub mod custom_element; #[cfg(feature = "dioxus")] pub mod dioxus; pub mod layout_attributes; @@ -15,8 +16,10 @@ pub mod node_ref; pub mod node_watcher; mod passes; pub mod real_dom; +mod shadow_dom; pub mod tree; pub mod utils; + pub use shipyard::EntityId as NodeId; pub mod exports { diff --git a/packages/native-core/src/node.rs b/packages/native-core/src/node.rs index 42ff8229f..2cbfa3fb3 100644 --- a/packages/native-core/src/node.rs +++ b/packages/native-core/src/node.rs @@ -7,6 +7,8 @@ use std::{ fmt::{Debug, Display}, }; +use crate::shadow_dom::ShadowDom; + /// A element node in the RealDom #[derive(Debug, Clone, Default)] pub struct ElementNode { @@ -18,6 +20,9 @@ pub struct ElementNode { pub attributes: FxHashMap>, /// The events the element is listening for pub listeners: FxHashSet, + /// The shadow dom of the element + // This is a rare property, so it is stored in an `Box` to save space + pub shadow: Option>>, } impl ElementNode { @@ -28,6 +33,7 @@ impl ElementNode { namespace: namespace.into(), attributes: Default::default(), listeners: Default::default(), + shadow: Default::default(), } } } diff --git a/packages/native-core/src/passes.rs b/packages/native-core/src/passes.rs index 2a84e6df4..8696b8a89 100644 --- a/packages/native-core/src/passes.rs +++ b/packages/native-core/src/passes.rs @@ -104,6 +104,9 @@ pub trait State: Any + Send + Sync { /// This is a mask of what aspects of the node are required to update this state const NODE_MASK: NodeMaskBuilder<'static>; + /// Does the state traverse into the shadow dom? + const TRAVERSE_SHADOW_DOM: bool = false; + /// Update this state in a node, returns if the state was updated fn update<'a>( &mut self, diff --git a/packages/native-core/src/real_dom.rs b/packages/native-core/src/real_dom.rs index 7d318ddc1..9e6146319 100644 --- a/packages/native-core/src/real_dom.rs +++ b/packages/native-core/src/real_dom.rs @@ -17,6 +17,7 @@ use crate::node_ref::{NodeMask, NodeMaskBuilder}; use crate::node_watcher::{AttributeWatcher, NodeWatcher}; use crate::passes::{DirtyNodeStates, TypeErasedState}; use crate::prelude::AttributeMaskBuilder; +use crate::shadow_dom::ShadowDom; use crate::tree::{TreeMut, TreeMutView, TreeRef, TreeRefView}; use crate::NodeId; use crate::{FxDashSet, SendAnyMap}; @@ -139,6 +140,7 @@ impl RealDom { namespace: Some("Root".to_string()), attributes: FxHashMap::default(), listeners: FxHashSet::default(), + shadow: None, }); let root_id = world.add_entity(root_node); { @@ -1056,6 +1058,16 @@ impl ElementNodeMut<'_, V> { pub fn listeners(&self) -> &FxHashSet { &self.element().listeners } + + /// Get the shadow root of the element + pub fn shadow_root(&self) -> Option<&ShadowDom> { + self.element().shadow.as_deref() + } + + /// Get the shadow root of the element + pub fn shadow_root_mut(&mut self) -> &mut Option>> { + &mut self.element_mut().shadow + } } // Create a workload from all of the passes. This orders the passes so that each pass will only run at most once. diff --git a/packages/native-core/src/shadow_dom.rs b/packages/native-core/src/shadow_dom.rs new file mode 100644 index 000000000..262d6fbb6 --- /dev/null +++ b/packages/native-core/src/shadow_dom.rs @@ -0,0 +1,26 @@ +use std::fmt::Debug; + +use crate::{custom_element::CustomElementManager, node::FromAnyValue, prelude::NodeRef, NodeId}; + +#[derive(Clone)] +pub struct ShadowDom { + shadow_root: NodeId, + updater: CustomElementManager, +} + +impl ShadowDom { + pub fn new(updater: CustomElementManager) -> Self { + Self { + shadow_root: updater.root(), + updater, + } + } +} + +impl Debug for ShadowDom { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("ShadowDom") + .field("shadow_root", &self.shadow_root) + .finish() + } +} diff --git a/packages/native-core/src/utils/mod.rs b/packages/native-core/src/utils/mod.rs index 95ccc35be..a78b5310b 100644 --- a/packages/native-core/src/utils/mod.rs +++ b/packages/native-core/src/utils/mod.rs @@ -5,4 +5,3 @@ mod persistant_iterator; pub use persistant_iterator::*; pub mod cursor; -pub mod widget_watcher; diff --git a/packages/native-core/src/utils/widget_watcher.rs b/packages/native-core/src/utils/widget_watcher.rs deleted file mode 100644 index c6836697b..000000000 --- a/packages/native-core/src/utils/widget_watcher.rs +++ /dev/null @@ -1,136 +0,0 @@ -//! Widget utilities for defining, registering and updating widgets - -use std::sync::{Arc, RwLock}; - -use rustc_hash::FxHashMap; -use shipyard::Component; - -use crate::{ - node::{FromAnyValue, NodeType}, - node_ref::AttributeMask, - node_watcher::{AttributeWatcher, NodeWatcher}, - prelude::{NodeImmutable, NodeMut, RealDom}, - NodeId, -}; - -/// A watcher that handlers registering and updating widgets -#[derive(Default, Clone)] -pub struct WidgetWatcher { - inner: Arc>>, -} - -impl NodeWatcher for WidgetWatcher { - fn on_node_added(&mut self, node: NodeMut) { - let mut inner = self.inner.write().unwrap(); - inner.on_node_added(node); - } - - fn on_node_removed(&mut self, node: NodeMut) { - let mut inner = self.inner.write().unwrap(); - inner.on_node_removed(node); - } -} - -impl WidgetWatcher { - /// Register a widget - pub fn register_widget + 'static, O: WidgetUpdater>(&mut self) { - let mut inner = self.inner.write().unwrap(); - inner.builders.insert( - W::NAME, - WidgetBuilder { - create: |mut node| Box::new(W::create(&mut node)), - }, - ); - } - - /// Attach the widget watcher to the RealDom - pub fn attach(&self, dom: &mut RealDom) { - dom.add_node_watcher(self.clone()); - dom.add_attribute_watcher(self.clone()); - } -} - -impl AttributeWatcher for WidgetWatcher { - fn on_attributes_changed(&self, node: NodeMut, attributes: &AttributeMask) { - let mut inner = self.inner.write().unwrap(); - if let Some(widget) = inner.widgets.get_mut(&node.id()) { - widget.dyn_widget.attributes_changed(node, attributes); - } - } -} - -#[derive(Default)] -struct WidgetWatcherInner { - builders: FxHashMap<&'static str, WidgetBuilder>, - widgets: FxHashMap>, -} - -impl NodeWatcher for WidgetWatcherInner { - fn on_node_added(&mut self, node: NodeMut) { - let node_type = node.node_type(); - if let NodeType::Element(el) = &*node_type { - if let Some(builder) = self.builders.get(el.tag.as_str()) { - drop(node_type); - let id = node.id(); - let widget = (builder.create)(node); - self.widgets.insert(id, BoxedWidget { dyn_widget: widget }); - } - } - } - - fn on_node_removed(&mut self, node: NodeMut) { - self.widgets.remove(&node.id()); - } -} - -#[derive(Component)] -struct BoxedWidget { - dyn_widget: Box>, -} - -struct WidgetBuilder { - create: fn(NodeMut) -> Box>, -} - -/// A controlled element (a.k.a. widget) -pub trait Widget: Send + Sync + 'static { - /// The tag the widget is registered under. - const NAME: &'static str; - - /// Create a new widget. - fn create(root: &mut NodeMut) -> Self; - - /// Called when the attributes of the widget are changed. - fn attributes_changed(&mut self, _root: NodeMut, _attributes: &AttributeMask); -} - -/// A factory for creating widgets -pub trait WidgetFactory, V: FromAnyValue + Send + Sync = ()>: - Send + Sync + 'static -{ - /// The tag the widget is registered under. - const NAME: &'static str; - - /// Create a new widget. - fn create(root: &mut NodeMut) -> W; -} - -impl, V: FromAnyValue + Send + Sync> WidgetFactory for W { - const NAME: &'static str = W::NAME; - - fn create(root: &mut NodeMut) -> Self { - W::create(root) - } -} - -/// A trait for updating widgets -pub trait WidgetUpdater: Send + Sync + 'static { - /// Called when the attributes of the widget are changed. - fn attributes_changed(&mut self, _root: NodeMut, _attributes: &AttributeMask); -} - -impl, V: FromAnyValue + Send + Sync> WidgetUpdater for W { - fn attributes_changed(&mut self, root: NodeMut, attributes: &AttributeMask) { - self.attributes_changed(root, attributes); - } -} diff --git a/packages/native-core/tests/passes.rs b/packages/native-core/tests/passes.rs index 368333fb5..9a0fbe51d 100644 --- a/packages/native-core/tests/passes.rs +++ b/packages/native-core/tests/passes.rs @@ -10,6 +10,7 @@ fn create_blank_element() -> NodeType { namespace: None, attributes: FxHashMap::default(), listeners: FxHashSet::default(), + shadow: None, }) }