use crate::{ any_props::AnyProps, arena::ElementId, Element, Event, LazyNodes, ScopeId, ScopeState, }; use bumpalo::boxed::Box as BumpBox; use bumpalo::Bump; use std::{ any::{Any, TypeId}, cell::{Cell, RefCell}, fmt::Arguments, future::Future, }; pub type TemplateId = &'static str; /// The actual state of the component's most recent computation /// /// Because Dioxus accepts components in the form of `async fn(Scope) -> Result`, we need to support both /// sync and async versions. /// /// Dioxus will do its best to immediately resolve any async components into a regular Element, but as an implementor /// you might need to handle the case where there's no node immediately ready. pub enum RenderReturn<'a> { /// A currently-available element Sync(Element<'a>), /// An ongoing future that will resolve to a [`Element`] Async(BumpBox<'a, dyn Future> + 'a>), } /// A reference to a template along with any context needed to hydrate it /// /// The dynamic parts of the template are stored separately from the static parts. This allows faster diffing by skipping /// static parts of the template. #[derive(Debug, Clone)] pub struct VNode<'a> { /// The key given to the root of this template. /// /// In fragments, this is the key of the first child. In other cases, it is the key of the root. pub key: Option<&'a str>, /// When rendered, this template will be linked to its parent manually pub parent: Option, /// The static nodes and static descriptor of the template pub template: Template<'static>, /// The IDs for the roots of this template - to be used when moving the template around and removing it from /// the actual Dom pub root_ids: &'a [Cell>], /// The dynamic parts of the template pub dynamic_nodes: &'a [DynamicNode<'a>], /// The dynamic parts of the template pub dynamic_attrs: &'a [Attribute<'a>], } impl<'a> VNode<'a> { /// Create a template with no nodes that will be skipped over during diffing pub fn empty() -> Element<'a> { Some(VNode { key: None, parent: None, root_ids: &[], dynamic_nodes: &[], dynamic_attrs: &[], template: Template { name: "dioxus-empty", roots: &[], node_paths: &[], attr_paths: &[], }, }) } /// Load a dynamic root at the given index /// /// Returns [`None`] if the root is actually a static node (Element/Text) pub fn dynamic_root(&self, idx: usize) -> Option<&'a DynamicNode<'a>> { match &self.template.roots[idx] { TemplateNode::Element { .. } | TemplateNode::Text { text: _ } => None, TemplateNode::Dynamic { id } | TemplateNode::DynamicText { id } => { Some(&self.dynamic_nodes[*id]) } } } pub(crate) fn clear_listeners(&self) { for attr in self.dynamic_attrs { if let AttributeValue::Listener(l) = &attr.value { l.0.borrow_mut().take(); } } } } /// A static layout of a UI tree that describes a set of dynamic and static nodes. /// /// This is the core innovation in Dioxus. Most UIs are made of static nodes, yet participate in diffing like any /// dynamic node. This struct can be created at compile time. It promises that its name is unique, allow Dioxus to use /// its static description of the UI to skip immediately to the dynamic nodes during diffing. /// /// For this to work properly, the [`Template::name`] *must* be unique across your entire project. This can be done via variety of /// ways, with the suggested approach being the unique code location (file, line, col, etc). #[cfg_attr(feature = "serialize", derive(serde::Serialize))] #[derive(Debug, Clone, Copy, PartialEq, Hash, Eq, PartialOrd, Ord)] pub struct Template<'a> { /// The name of the template. This must be unique across your entire program for template diffing to work properly /// /// If two templates have the same name, it's likely that Dioxus will panic when diffing. pub name: &'a str, /// The list of template nodes that make up the template /// /// Unlike react, calls to `rsx!` can have multiple roots. This list supports that paradigm. pub roots: &'a [TemplateNode<'a>], /// The paths of each node relative to the root of the template. /// /// These will be one segment shorter than the path sent to the renderer since those paths are relative to the /// topmost element, not the `roots` field. pub node_paths: &'a [&'a [u8]], /// The paths of each dynamic attribute relative to the root of the template /// /// These will be one segment shorter than the path sent to the renderer since those paths are relative to the /// topmost element, not the `roots` field. pub attr_paths: &'a [&'a [u8]], } impl<'a> Template<'a> { /// Is this template worth caching at all, since it's completely runtime? /// /// There's no point in saving templates that are completely dynamic, since they'll be recreated every time anyway. pub fn is_completely_dynamic(&self) -> bool { use TemplateNode::*; self.roots .iter() .all(|root| matches!(root, Dynamic { .. } | DynamicText { .. })) } } /// A statically known node in a layout. /// /// This can be created at compile time, saving the VirtualDom time when diffing the tree #[derive(Debug, Clone, Copy, PartialEq, Hash, Eq, PartialOrd, Ord)] #[cfg_attr(feature = "serialize", derive(serde::Serialize), serde(tag = "type"))] pub enum TemplateNode<'a> { /// An statically known element in the dom. /// /// In HTML this would be something like `
` Element { /// The name of the element /// /// IE for a div, it would be the string "div" tag: &'a str, /// The namespace of the element /// /// In HTML, this would be a valid URI that defines a namespace for all elements below it /// SVG is an example of this namespace namespace: Option<&'a str>, /// A list of possibly dynamic attribues for this element /// /// An attribute on a DOM node, such as `id="my-thing"` or `href="https://example.com"`. attrs: &'a [TemplateAttribute<'a>], /// A list of template nodes that define another set of template nodes children: &'a [TemplateNode<'a>], }, /// This template node is just a piece of static text Text { /// The actual text text: &'a str, }, /// This template node is unknown, and needs to be created at runtime. Dynamic { /// The index of the dynamic node in the VNode's dynamic_nodes list id: usize, }, /// This template node is known to be some text, but needs to be created at runtime /// /// This is separate from the pure Dynamic variant for various optimizations DynamicText { /// The index of the dynamic node in the VNode's dynamic_nodes list id: usize, }, } /// A node created at runtime /// /// This node's index in the DynamicNode list on VNode should match its repsective `Dynamic` index #[derive(Debug)] pub enum DynamicNode<'a> { /// A component node /// /// Most of the time, Dioxus will actually know which component this is as compile time, but the props and /// assigned scope are dynamic. /// /// The actual VComponent can be dynamic between two VNodes, though, allowing implementations to swap /// the render function at runtime Component(VComponent<'a>), /// A text node Text(VText<'a>), /// A placeholder /// /// Used by suspense when a node isn't ready and by fragments that don't render anything /// /// In code, this is just an ElementId whose initial value is set to 0 upon creation Placeholder(VPlaceholder), /// A list of VNodes. /// /// Note that this is not a list of dynamic nodes. These must be VNodes and created through conditional rendering /// or iterators. Fragment(&'a [VNode<'a>]), } impl Default for DynamicNode<'_> { fn default() -> Self { Self::Placeholder(Default::default()) } } /// An instance of a child component pub struct VComponent<'a> { /// The name of this component pub name: &'static str, /// Are the props valid for the 'static lifetime? /// /// Internally, this is used as a guarantee. Externally, this might be incorrect, so don't count on it. /// /// This flag is assumed by the [`crate::Properties`] trait which is unsafe to implement pub static_props: bool, /// The assigned Scope for this component pub scope: Cell>, /// The function pointer of the component, known at compile time /// /// It is possible that components get folded at comppile time, so these shouldn't be really used as a key pub render_fn: *const (), pub(crate) props: RefCell + 'a>>>, } impl<'a> std::fmt::Debug for VComponent<'a> { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("VComponent") .field("name", &self.name) .field("static_props", &self.static_props) .field("scope", &self.scope) .finish() } } /// An instance of some text, mounted to the DOM #[derive(Debug)] pub struct VText<'a> { /// The actual text itself pub value: &'a str, /// The ID of this node in the real DOM pub id: Cell>, } /// A placeholder node, used by suspense and fragments #[derive(Debug, Default)] pub struct VPlaceholder { /// The ID of this node in the real DOM pub id: Cell>, } /// An attribute of the TemplateNode, created at compile time #[derive(Debug, PartialEq, Hash, Eq, PartialOrd, Ord)] #[cfg_attr( feature = "serialize", derive(serde::Serialize, serde::Deserialize), serde(tag = "type") )] pub enum TemplateAttribute<'a> { /// This attribute is entirely known at compile time, enabling Static { /// The name of this attribute. /// /// For example, the `href` attribute in `href="https://example.com"`, would have the name "href" name: &'a str, /// The value of this attribute, known at compile time /// /// Currently this only accepts &str, so values, even if they're known at compile time, are not known value: &'a str, /// The namespace of this attribute. Does not exist in the HTML spec namespace: Option<&'a str>, }, /// The attribute in this position is actually determined dynamically at runtime /// /// This is the index into the dynamic_attributes field on the container VNode Dynamic { /// The index id: usize, }, } /// An attribute on a DOM node, such as `id="my-thing"` or `href="https://example.com"` #[derive(Debug)] pub struct Attribute<'a> { /// The name of the attribute. pub name: &'a str, /// The value of the attribute pub value: AttributeValue<'a>, /// The namespace of the attribute. /// /// Doesn’t exist in the html spec. Used in Dioxus to denote “style” tags and other attribute groups. pub namespace: Option<&'static str>, /// The element in the DOM that this attribute belongs to pub mounted_element: Cell, /// An indication of we should always try and set the attribute. Used in controlled components to ensure changes are propagated pub volatile: bool, } /// Any of the built-in values that the Dioxus VirtualDom supports as dynamic attributes on elements /// /// These are built-in to be faster during the diffing process. To use a custom value, use the [`AttributeValue::Any`] /// variant. #[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))] #[cfg_attr(feature = "serialize", serde(untagged))] #[derive(Clone)] pub enum AttributeValue<'a> { /// Text attribute Text(&'a str), /// A float Float(f64), /// Signed integer Int(i64), /// Boolean Bool(bool), /// A listener, like "onclick" Listener(ListenerCb<'a>), /// An arbitrary value that implements PartialEq and is static Any(AnyValueContainer), /// A "none" value, resulting in the removal of an attribute from the dom None, } pub type ListenerCbInner<'a> = RefCell) + 'a>>>; pub struct ListenerCb<'a>(pub ListenerCbInner<'a>); impl Clone for ListenerCb<'_> { fn clone(&self) -> Self { panic!("ListenerCb cannot be cloned") } } #[cfg(feature = "serialize")] impl<'a> serde::Serialize for ListenerCb<'a> { fn serialize(&self, _: S) -> Result where S: serde::Serializer, { panic!("ListenerCb cannot be serialized") } } #[cfg(feature = "serialize")] impl<'de, 'a> serde::Deserialize<'de> for ListenerCb<'a> { fn deserialize(_: D) -> Result where D: serde::Deserializer<'de>, { panic!("ListenerCb cannot be deserialized") } } /// A boxed value that implements PartialEq and Any #[derive(Clone)] #[cfg(not(feature = "sync_attributes"))] pub struct AnyValueContainer(pub std::rc::Rc); #[derive(Clone)] #[cfg(feature = "sync_attributes")] /// A boxed value that implements PartialEq, Any, Sync, and Send pub struct AnyValueContainer(pub std::sync::Arc); impl PartialEq for AnyValueContainer { fn eq(&self, other: &Self) -> bool { self.0.any_cmp(other.0.as_ref()) } } impl AnyValueContainer { /// Create a new AnyValueContainer containing the specified data. pub fn new(value: T) -> Self { #[cfg(feature = "sync_attributes")] return Self(std::sync::Arc::new(value)); #[cfg(not(feature = "sync_attributes"))] return Self(std::rc::Rc::new(value)); } /// Returns a reference to the inner value without checking the type. /// /// # Safety /// The caller must ensure that the type of the inner value is `T`. pub unsafe fn downcast_ref_unchecked(&self) -> &T { unsafe { &*(self.0.as_ref() as *const _ as *const T) } } /// Returns a reference to the inner value. pub fn downcast_ref(&self) -> Option<&T> { if self.0.our_typeid() == TypeId::of::() { Some(unsafe { self.downcast_ref_unchecked() }) } else { None } } /// Checks if the inner value is of type `T`. pub fn is(&self) -> bool { self.0.our_typeid() == TypeId::of::() } } #[test] fn test_any_value_rc() { let a = AnyValueContainer::new(1i32); assert_eq!(a.downcast_ref::(), Some(&1i32)); assert_eq!(a.downcast_ref::(), None); assert!(a.is::()); assert!(!a.is::()); unsafe { assert_eq!(a.downcast_ref_unchecked::(), &1i32); } } #[cfg(feature = "serialize")] impl serde::Serialize for AnyValueContainer { fn serialize(&self, _: S) -> Result where S: serde::Serializer, { panic!("AnyValueBox cannot be serialized") } } #[cfg(feature = "serialize")] impl<'de> serde::Deserialize<'de> for AnyValueContainer { fn deserialize(_: D) -> Result where D: serde::Deserializer<'de>, { panic!("AnyValueBox cannot be deserialized") } } impl<'a> std::fmt::Debug for AttributeValue<'a> { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { Self::Text(arg0) => f.debug_tuple("Text").field(arg0).finish(), Self::Float(arg0) => f.debug_tuple("Float").field(arg0).finish(), Self::Int(arg0) => f.debug_tuple("Int").field(arg0).finish(), Self::Bool(arg0) => f.debug_tuple("Bool").field(arg0).finish(), Self::Listener(_) => f.debug_tuple("Listener").finish(), Self::Any(_) => f.debug_tuple("Any").finish(), Self::None => write!(f, "None"), } } } impl<'a> PartialEq for AttributeValue<'a> { fn eq(&self, other: &Self) -> bool { match (self, other) { (Self::Text(l0), Self::Text(r0)) => l0 == r0, (Self::Float(l0), Self::Float(r0)) => l0 == r0, (Self::Int(l0), Self::Int(r0)) => l0 == r0, (Self::Bool(l0), Self::Bool(r0)) => l0 == r0, (Self::Listener(_), Self::Listener(_)) => true, (Self::Any(l0), Self::Any(r0)) => l0 == r0, _ => false, } } } // Some renderers need attributes to be sync and send. The rest of the attributes are already sync and send, but there is no way to force Any values to be sync and send on the renderer side. // The sync_attributes flag restricts any valuse to be sync and send. #[doc(hidden)] #[cfg(feature = "sync_attributes")] pub trait AnyValue: Sync + Send { fn any_cmp(&self, other: &dyn AnyValue) -> bool; fn our_typeid(&self) -> TypeId; } #[cfg(feature = "sync_attributes")] impl AnyValue for T { fn any_cmp(&self, other: &dyn AnyValue) -> bool { if self.our_typeid() != other.our_typeid() { return false; } self == unsafe { &*(other as *const _ as *const T) } } fn our_typeid(&self) -> TypeId { self.type_id() } } #[doc(hidden)] #[cfg(not(feature = "sync_attributes"))] pub trait AnyValue { fn any_cmp(&self, other: &dyn AnyValue) -> bool; fn our_typeid(&self) -> TypeId; } #[cfg(not(feature = "sync_attributes"))] impl AnyValue for T { fn any_cmp(&self, other: &dyn AnyValue) -> bool { if self.our_typeid() != other.our_typeid() { return false; } self == unsafe { &*(other as *const _ as *const T) } } fn our_typeid(&self) -> TypeId { self.type_id() } } #[doc(hidden)] pub trait ComponentReturn<'a, A = ()> { fn into_return(self, cx: &'a ScopeState) -> RenderReturn<'a>; } impl<'a> ComponentReturn<'a> for Element<'a> { fn into_return(self, _cx: &ScopeState) -> RenderReturn<'a> { RenderReturn::Sync(self) } } #[doc(hidden)] pub struct AsyncMarker; impl<'a, F> ComponentReturn<'a, AsyncMarker> for F where F: Future> + 'a, { fn into_return(self, cx: &'a ScopeState) -> RenderReturn<'a> { let f: &mut dyn Future> = cx.bump().alloc(self); RenderReturn::Async(unsafe { BumpBox::from_raw(f) }) } } impl<'a> RenderReturn<'a> { pub(crate) unsafe fn extend_lifetime_ref<'c>(&self) -> &'c RenderReturn<'c> { unsafe { std::mem::transmute(self) } } pub(crate) unsafe fn extend_lifetime<'c>(self) -> RenderReturn<'c> { unsafe { std::mem::transmute(self) } } } /// A trait that allows various items to be converted into a dynamic node for the rsx macro pub trait IntoDynNode<'a, A = ()> { /// Consume this item along with a scopestate and produce a DynamicNode /// /// You can use the bump alloactor of the scopestate to creat the dynamic node fn into_vnode(self, cx: &'a ScopeState) -> DynamicNode<'a>; } impl<'a> IntoDynNode<'a> for () { fn into_vnode(self, _cx: &'a ScopeState) -> DynamicNode<'a> { DynamicNode::default() } } impl<'a> IntoDynNode<'a> for VNode<'a> { fn into_vnode(self, _cx: &'a ScopeState) -> DynamicNode<'a> { DynamicNode::Fragment(_cx.bump().alloc([self])) } } impl<'a> IntoDynNode<'a> for DynamicNode<'a> { fn into_vnode(self, _cx: &'a ScopeState) -> DynamicNode<'a> { self } } impl<'a, T: IntoDynNode<'a>> IntoDynNode<'a> for Option { fn into_vnode(self, _cx: &'a ScopeState) -> DynamicNode<'a> { match self { Some(val) => val.into_vnode(_cx), None => DynamicNode::default(), } } } impl<'a> IntoDynNode<'a> for &Element<'a> { fn into_vnode(self, _cx: &'a ScopeState) -> DynamicNode<'a> { match self.as_ref() { Some(val) => val.clone().into_vnode(_cx), _ => DynamicNode::default(), } } } impl<'a, 'b> IntoDynNode<'a> for LazyNodes<'a, 'b> { fn into_vnode(self, cx: &'a ScopeState) -> DynamicNode<'a> { DynamicNode::Fragment(cx.bump().alloc([self.call(cx)])) } } impl<'a> IntoDynNode<'_> for &'a str { fn into_vnode(self, cx: &ScopeState) -> DynamicNode { cx.text_node(format_args!("{}", self)) } } impl IntoDynNode<'_> for String { fn into_vnode(self, cx: &ScopeState) -> DynamicNode { cx.text_node(format_args!("{}", self)) } } impl<'b> IntoDynNode<'b> for Arguments<'_> { fn into_vnode(self, cx: &'b ScopeState) -> DynamicNode<'b> { cx.text_node(self) } } impl<'a> IntoDynNode<'a> for &'a VNode<'a> { fn into_vnode(self, _cx: &'a ScopeState) -> DynamicNode<'a> { DynamicNode::Fragment(_cx.bump().alloc([VNode { parent: self.parent, template: self.template, root_ids: self.root_ids, key: self.key, dynamic_nodes: self.dynamic_nodes, dynamic_attrs: self.dynamic_attrs, }])) } } pub trait IntoTemplate<'a> { fn into_template(self, _cx: &'a ScopeState) -> VNode<'a>; } impl<'a> IntoTemplate<'a> for VNode<'a> { fn into_template(self, _cx: &'a ScopeState) -> VNode<'a> { self } } impl<'a> IntoTemplate<'a> for Element<'a> { fn into_template(self, _cx: &'a ScopeState) -> VNode<'a> { match self { Some(val) => val.into_template(_cx), _ => VNode::empty().unwrap(), } } } impl<'a, 'b> IntoTemplate<'a> for LazyNodes<'a, 'b> { fn into_template(self, cx: &'a ScopeState) -> VNode<'a> { self.call(cx) } } // Note that we're using the E as a generic but this is never crafted anyways. #[doc(hidden)] pub struct FromNodeIterator; impl<'a, T, I> IntoDynNode<'a, FromNodeIterator> for T where T: Iterator, I: IntoTemplate<'a>, { fn into_vnode(self, cx: &'a ScopeState) -> DynamicNode<'a> { let mut nodes = bumpalo::collections::Vec::new_in(cx.bump()); nodes.extend(self.into_iter().map(|node| node.into_template(cx))); match nodes.into_bump_slice() { children if children.is_empty() => DynamicNode::default(), children => DynamicNode::Fragment(children), } } } /// A value that can be converted into an attribute value pub trait IntoAttributeValue<'a> { /// Convert into an attribute value fn into_value(self, bump: &'a Bump) -> AttributeValue<'a>; } impl<'a> IntoAttributeValue<'a> for &'a str { fn into_value(self, _: &'a Bump) -> AttributeValue<'a> { AttributeValue::Text(self) } } impl<'a> IntoAttributeValue<'a> for f64 { fn into_value(self, _: &'a Bump) -> AttributeValue<'a> { AttributeValue::Float(self) } } impl<'a> IntoAttributeValue<'a> for i64 { fn into_value(self, _: &'a Bump) -> AttributeValue<'a> { AttributeValue::Int(self) } } impl<'a> IntoAttributeValue<'a> for bool { fn into_value(self, _: &'a Bump) -> AttributeValue<'a> { AttributeValue::Bool(self) } } impl<'a> IntoAttributeValue<'a> for Arguments<'_> { fn into_value(self, bump: &'a Bump) -> AttributeValue<'a> { use bumpalo::core_alloc::fmt::Write; let mut str_buf = bumpalo::collections::String::new_in(bump); str_buf.write_fmt(self).unwrap(); AttributeValue::Text(str_buf.into_bump_str()) } } impl<'a> IntoAttributeValue<'a> for AnyValueContainer { fn into_value(self, _: &'a Bump) -> AttributeValue<'a> { AttributeValue::Any(self) } }