use crate::{
any_props::BoxedAnyProps,
arena::ElementId,
events::ListenerCallback,
innerlude::{ElementRef, MountId, ScopeState, VProps},
properties::ComponentFunction,
Element, Event, Properties, ScopeId, VirtualDom,
};
use dioxus_core_types::DioxusFormattable;
use std::ops::Deref;
use std::rc::Rc;
use std::vec;
use std::{
any::{Any, TypeId},
cell::Cell,
fmt::{Arguments, Debug},
};
#[derive(Debug)]
pub(crate) struct VNodeMount {
pub parent: Option<ElementRef>,
pub node: VNode,
pub root_ids: Box<[ElementId]>,
pub(crate) mounted_attributes: Box<[ElementId]>,
pub(crate) mounted_dynamic_nodes: Box<[usize]>,
}
#[derive(Debug)]
pub struct VNodeInner {
pub key: Option<String>,
pub template: Template,
pub dynamic_nodes: Box<[DynamicNode]>,
pub dynamic_attrs: Box<[Box<[Attribute]>]>,
}
#[derive(Debug, Clone)]
pub struct VNode {
vnode: Rc<VNodeInner>,
pub(crate) mount: Cell<MountId>,
}
impl Default for VNode {
fn default() -> Self {
Self::placeholder()
}
}
impl PartialEq for VNode {
fn eq(&self, other: &Self) -> bool {
Rc::ptr_eq(&self.vnode, &other.vnode)
}
}
impl Deref for VNode {
type Target = VNodeInner;
fn deref(&self) -> &Self::Target {
&self.vnode
}
}
impl VNode {
pub fn empty() -> Element {
Ok(Self::default())
}
pub fn placeholder() -> Self {
use std::cell::OnceCell;
thread_local! {
static PLACEHOLDER_VNODE: OnceCell<Rc<VNodeInner>> = const { OnceCell::new() };
}
let vnode = PLACEHOLDER_VNODE.with(|cell| {
cell.get_or_init(move || {
Rc::new(VNodeInner {
key: None,
dynamic_nodes: Box::new([DynamicNode::Placeholder(Default::default())]),
dynamic_attrs: Box::new([]),
template: Template {
roots: &[TemplateNode::Dynamic { id: 0 }],
node_paths: &[&[0]],
attr_paths: &[],
},
})
})
.clone()
});
Self {
vnode,
mount: Default::default(),
}
}
pub fn new(
key: Option<String>,
template: Template,
dynamic_nodes: Box<[DynamicNode]>,
dynamic_attrs: Box<[Box<[Attribute]>]>,
) -> Self {
Self {
vnode: Rc::new(VNodeInner {
key,
template,
dynamic_nodes,
dynamic_attrs,
}),
mount: Default::default(),
}
}
pub fn dynamic_root(&self, idx: usize) -> Option<&DynamicNode> {
self.template.roots[idx]
.dynamic_id()
.map(|id| &self.dynamic_nodes[id])
}
pub fn mounted_dynamic_node(
&self,
dynamic_node_idx: usize,
dom: &VirtualDom,
) -> Option<ElementId> {
let mount = self.mount.get().as_usize()?;
match &self.dynamic_nodes[dynamic_node_idx] {
DynamicNode::Text(_) | DynamicNode::Placeholder(_) => {
let mounts = dom.runtime.mounts.borrow();
mounts
.get(mount)?
.mounted_dynamic_nodes
.get(dynamic_node_idx)
.map(|id| ElementId(*id))
}
_ => None,
}
}
pub fn mounted_root(&self, root_idx: usize, dom: &VirtualDom) -> Option<ElementId> {
let mount = self.mount.get().as_usize()?;
let mounts = dom.runtime.mounts.borrow();
mounts.get(mount)?.root_ids.get(root_idx).copied()
}
pub fn mounted_dynamic_attribute(
&self,
dynamic_attribute_idx: usize,
dom: &VirtualDom,
) -> Option<ElementId> {
let mount = self.mount.get().as_usize()?;
let mounts = dom.runtime.mounts.borrow();
mounts
.get(mount)?
.mounted_attributes
.get(dynamic_attribute_idx)
.copied()
}
pub(crate) fn deep_clone(&self) -> Self {
Self {
vnode: Rc::new(VNodeInner {
key: self.vnode.key.clone(),
template: self.vnode.template,
dynamic_nodes: self
.vnode
.dynamic_nodes
.iter()
.map(|node| match node {
DynamicNode::Fragment(nodes) => DynamicNode::Fragment(
nodes.iter().map(|node| node.deep_clone()).collect(),
),
other => other.clone(),
})
.collect(),
dynamic_attrs: self
.vnode
.dynamic_attrs
.iter()
.map(|attr| {
attr.iter()
.map(|attribute| attribute.deep_clone())
.collect()
})
.collect(),
}),
mount: Default::default(),
}
}
}
type StaticStr = &'static str;
type StaticPathArray = &'static [&'static [u8]];
type StaticTemplateArray = &'static [TemplateNode];
type StaticTemplateAttributeArray = &'static [TemplateAttribute];
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug, Clone, Copy, Eq, PartialOrd, Ord)]
pub struct Template {
#[cfg_attr(feature = "serialize", serde(deserialize_with = "deserialize_leaky"))]
pub roots: StaticTemplateArray,
#[cfg_attr(
feature = "serialize",
serde(deserialize_with = "deserialize_bytes_leaky")
)]
pub node_paths: StaticPathArray,
#[cfg_attr(
feature = "serialize",
serde(deserialize_with = "deserialize_bytes_leaky", bound = "")
)]
pub attr_paths: StaticPathArray,
}
#[allow(unpredictable_function_pointer_comparisons)] fn static_items_merged() -> bool {
fn a() {}
fn b() {}
a as fn() == b as fn()
}
impl std::hash::Hash for Template {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
if static_items_merged() {
std::ptr::hash(self.roots as *const _, state);
std::ptr::hash(self.node_paths as *const _, state);
std::ptr::hash(self.attr_paths as *const _, state);
}
else {
self.roots.hash(state);
self.node_paths.hash(state);
self.attr_paths.hash(state);
}
}
}
impl PartialEq for Template {
fn eq(&self, other: &Self) -> bool {
if static_items_merged() {
std::ptr::eq(self.roots as *const _, other.roots as *const _)
&& std::ptr::eq(self.node_paths as *const _, other.node_paths as *const _)
&& std::ptr::eq(self.attr_paths as *const _, other.attr_paths as *const _)
}
else {
self.roots == other.roots
&& self.node_paths == other.node_paths
&& self.attr_paths == other.attr_paths
}
}
}
#[cfg(feature = "serialize")]
pub(crate) fn deserialize_string_leaky<'a, 'de, D>(
deserializer: D,
) -> Result<&'static str, D::Error>
where
D: serde::Deserializer<'de>,
{
use serde::Deserialize;
let deserialized = String::deserialize(deserializer)?;
Ok(&*Box::leak(deserialized.into_boxed_str()))
}
#[cfg(feature = "serialize")]
fn deserialize_bytes_leaky<'a, 'de, D>(
deserializer: D,
) -> Result<&'static [&'static [u8]], D::Error>
where
D: serde::Deserializer<'de>,
{
use serde::Deserialize;
let deserialized = Vec::<Vec<u8>>::deserialize(deserializer)?;
let deserialized = deserialized
.into_iter()
.map(|v| &*Box::leak(v.into_boxed_slice()))
.collect::<Vec<_>>();
Ok(&*Box::leak(deserialized.into_boxed_slice()))
}
#[cfg(feature = "serialize")]
pub(crate) fn deserialize_leaky<'a, 'de, T, D>(deserializer: D) -> Result<&'static [T], D::Error>
where
T: serde::Deserialize<'de>,
D: serde::Deserializer<'de>,
{
use serde::Deserialize;
let deserialized = Box::<[T]>::deserialize(deserializer)?;
Ok(&*Box::leak(deserialized))
}
#[cfg(feature = "serialize")]
pub(crate) fn deserialize_option_leaky<'a, 'de, D>(
deserializer: D,
) -> Result<Option<&'static str>, D::Error>
where
D: serde::Deserializer<'de>,
{
use serde::Deserialize;
let deserialized = Option::<String>::deserialize(deserializer)?;
Ok(deserialized.map(|deserialized| &*Box::leak(deserialized.into_boxed_str())))
}
impl Template {
pub fn is_completely_dynamic(&self) -> bool {
use TemplateNode::*;
self.roots.iter().all(|root| matches!(root, Dynamic { .. }))
}
}
#[derive(Debug, Clone, Copy, PartialEq, Hash, Eq, PartialOrd, Ord)]
#[cfg_attr(
feature = "serialize",
derive(serde::Serialize, serde::Deserialize),
serde(tag = "type")
)]
pub enum TemplateNode {
Element {
#[cfg_attr(
feature = "serialize",
serde(deserialize_with = "deserialize_string_leaky")
)]
tag: StaticStr,
#[cfg_attr(
feature = "serialize",
serde(deserialize_with = "deserialize_option_leaky")
)]
namespace: Option<StaticStr>,
#[cfg_attr(
feature = "serialize",
serde(deserialize_with = "deserialize_leaky", bound = "")
)]
attrs: StaticTemplateAttributeArray,
#[cfg_attr(feature = "serialize", serde(deserialize_with = "deserialize_leaky"))]
children: StaticTemplateArray,
},
Text {
#[cfg_attr(
feature = "serialize",
serde(deserialize_with = "deserialize_string_leaky", bound = "")
)]
text: StaticStr,
},
Dynamic {
id: usize,
},
}
impl TemplateNode {
pub fn dynamic_id(&self) -> Option<usize> {
use TemplateNode::*;
match self {
Dynamic { id } => Some(*id),
_ => None,
}
}
}
#[derive(Debug, Clone)]
pub enum DynamicNode {
Component(VComponent),
Text(VText),
Placeholder(VPlaceholder),
Fragment(Vec<VNode>),
}
impl DynamicNode {
pub fn make_node<'c, I>(into: impl IntoDynNode<I> + 'c) -> DynamicNode {
into.into_dyn_node()
}
}
impl Default for DynamicNode {
fn default() -> Self {
Self::Placeholder(Default::default())
}
}
pub struct VComponent {
pub name: &'static str,
pub(crate) render_fn: usize,
pub(crate) props: BoxedAnyProps,
}
impl Clone for VComponent {
fn clone(&self) -> Self {
Self {
name: self.name,
props: self.props.duplicate(),
render_fn: self.render_fn,
}
}
}
impl VComponent {
pub fn new<P, M: 'static>(
component: impl ComponentFunction<P, M>,
props: P,
fn_name: &'static str,
) -> Self
where
P: Properties + 'static,
{
let render_fn = component.fn_ptr();
let props = Box::new(VProps::new(
component,
<P as Properties>::memoize,
props,
fn_name,
));
VComponent {
render_fn,
name: fn_name,
props,
}
}
pub fn mounted_scope_id(
&self,
dynamic_node_index: usize,
vnode: &VNode,
dom: &VirtualDom,
) -> Option<ScopeId> {
let mount = vnode.mount.get().as_usize()?;
let mounts = dom.runtime.mounts.borrow();
let scope_id = mounts.get(mount)?.mounted_dynamic_nodes[dynamic_node_index];
Some(ScopeId(scope_id))
}
pub fn mounted_scope<'a>(
&self,
dynamic_node_index: usize,
vnode: &VNode,
dom: &'a VirtualDom,
) -> Option<&'a ScopeState> {
let mount = vnode.mount.get().as_usize()?;
let mounts = dom.runtime.mounts.borrow();
let scope_id = mounts.get(mount)?.mounted_dynamic_nodes[dynamic_node_index];
dom.scopes.get(scope_id)
}
}
impl std::fmt::Debug for VComponent {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("VComponent")
.field("name", &self.name)
.finish()
}
}
#[derive(Clone, Debug)]
pub struct VText {
pub value: String,
}
impl VText {
pub fn new(value: impl ToString) -> Self {
Self {
value: value.to_string(),
}
}
}
impl From<Arguments<'_>> for VText {
fn from(args: Arguments) -> Self {
Self::new(args.to_string())
}
}
#[derive(Clone, Debug, Default)]
#[non_exhaustive]
pub struct VPlaceholder {}
#[derive(Debug, PartialEq, Hash, Eq, PartialOrd, Ord)]
#[cfg_attr(
feature = "serialize",
derive(serde::Serialize, serde::Deserialize),
serde(tag = "type")
)]
pub enum TemplateAttribute {
Static {
#[cfg_attr(
feature = "serialize",
serde(deserialize_with = "deserialize_string_leaky", bound = "")
)]
name: StaticStr,
#[cfg_attr(
feature = "serialize",
serde(deserialize_with = "deserialize_string_leaky", bound = "")
)]
value: StaticStr,
#[cfg_attr(
feature = "serialize",
serde(deserialize_with = "deserialize_option_leaky", bound = "")
)]
namespace: Option<StaticStr>,
},
Dynamic {
id: usize,
},
}
#[derive(Debug, Clone, PartialEq)]
pub struct Attribute {
pub name: &'static str,
pub value: AttributeValue,
pub namespace: Option<&'static str>,
pub volatile: bool,
}
impl Attribute {
pub fn new<T>(
name: &'static str,
value: impl IntoAttributeValue<T>,
namespace: Option<&'static str>,
volatile: bool,
) -> Attribute {
Attribute {
name,
namespace,
volatile,
value: value.into_value(),
}
}
pub(crate) fn deep_clone(&self) -> Self {
Attribute {
name: self.name,
namespace: self.namespace,
volatile: self.volatile,
value: self.value.clone(),
}
}
}
#[derive(Clone)]
pub enum AttributeValue {
Text(String),
Float(f64),
Int(i64),
Bool(bool),
Listener(ListenerCallback),
Any(Rc<dyn AnyValue>),
None,
}
impl AttributeValue {
pub fn listener<T: 'static>(callback: impl FnMut(Event<T>) + 'static) -> AttributeValue {
AttributeValue::Listener(ListenerCallback::new(callback).erase())
}
pub fn any_value<T: AnyValue>(value: T) -> AttributeValue {
AttributeValue::Any(Rc::new(value))
}
}
impl std::fmt::Debug for AttributeValue {
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 PartialEq for AttributeValue {
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(l0), Self::Listener(r0)) => l0 == r0,
(Self::Any(l0), Self::Any(r0)) => l0.as_ref().any_cmp(r0.as_ref()),
(Self::None, Self::None) => true,
_ => false,
}
}
}
#[doc(hidden)]
pub trait AnyValue: 'static {
fn any_cmp(&self, other: &dyn AnyValue) -> bool;
fn as_any(&self) -> &dyn Any;
fn type_id(&self) -> TypeId {
self.as_any().type_id()
}
}
impl<T: Any + PartialEq + 'static> AnyValue for T {
fn any_cmp(&self, other: &dyn AnyValue) -> bool {
if let Some(other) = other.as_any().downcast_ref() {
self == other
} else {
false
}
}
fn as_any(&self) -> &dyn Any {
self
}
}
pub trait IntoDynNode<A = ()> {
fn into_dyn_node(self) -> DynamicNode;
}
impl IntoDynNode for () {
fn into_dyn_node(self) -> DynamicNode {
DynamicNode::default()
}
}
impl IntoDynNode for VNode {
fn into_dyn_node(self) -> DynamicNode {
DynamicNode::Fragment(vec![self])
}
}
impl IntoDynNode for DynamicNode {
fn into_dyn_node(self) -> DynamicNode {
self
}
}
impl<T: IntoDynNode> IntoDynNode for Option<T> {
fn into_dyn_node(self) -> DynamicNode {
match self {
Some(val) => val.into_dyn_node(),
None => DynamicNode::default(),
}
}
}
impl IntoDynNode for &Element {
fn into_dyn_node(self) -> DynamicNode {
match self.as_ref() {
Ok(val) => val.into_dyn_node(),
_ => DynamicNode::default(),
}
}
}
impl IntoDynNode for Element {
fn into_dyn_node(self) -> DynamicNode {
match self {
Ok(val) => val.into_dyn_node(),
_ => DynamicNode::default(),
}
}
}
impl IntoDynNode for &Option<VNode> {
fn into_dyn_node(self) -> DynamicNode {
match self.as_ref() {
Some(val) => val.clone().into_dyn_node(),
_ => DynamicNode::default(),
}
}
}
impl IntoDynNode for &str {
fn into_dyn_node(self) -> DynamicNode {
DynamicNode::Text(VText {
value: self.to_string(),
})
}
}
impl IntoDynNode for String {
fn into_dyn_node(self) -> DynamicNode {
DynamicNode::Text(VText { value: self })
}
}
impl IntoDynNode for Arguments<'_> {
fn into_dyn_node(self) -> DynamicNode {
DynamicNode::Text(VText {
value: self.to_string(),
})
}
}
impl IntoDynNode for &VNode {
fn into_dyn_node(self) -> DynamicNode {
DynamicNode::Fragment(vec![self.clone()])
}
}
pub trait IntoVNode {
fn into_vnode(self) -> VNode;
}
impl IntoVNode for VNode {
fn into_vnode(self) -> VNode {
self
}
}
impl IntoVNode for &VNode {
fn into_vnode(self) -> VNode {
self.clone()
}
}
impl IntoVNode for Element {
fn into_vnode(self) -> VNode {
match self {
Ok(val) => val.into_vnode(),
_ => VNode::default(),
}
}
}
impl IntoVNode for &Element {
fn into_vnode(self) -> VNode {
match self {
Ok(val) => val.into_vnode(),
_ => VNode::default(),
}
}
}
impl IntoVNode for Option<VNode> {
fn into_vnode(self) -> VNode {
match self {
Some(val) => val.into_vnode(),
_ => VNode::default(),
}
}
}
impl IntoVNode for &Option<VNode> {
fn into_vnode(self) -> VNode {
match self.as_ref() {
Some(val) => val.clone().into_vnode(),
_ => VNode::default(),
}
}
}
impl IntoVNode for Option<Element> {
fn into_vnode(self) -> VNode {
match self {
Some(val) => val.into_vnode(),
_ => VNode::default(),
}
}
}
impl IntoVNode for &Option<Element> {
fn into_vnode(self) -> VNode {
match self.as_ref() {
Some(val) => val.clone().into_vnode(),
_ => VNode::default(),
}
}
}
pub struct FromNodeIterator;
impl<T, I> IntoDynNode<FromNodeIterator> for T
where
T: Iterator<Item = I>,
I: IntoVNode,
{
fn into_dyn_node(self) -> DynamicNode {
let children: Vec<_> = self.into_iter().map(|node| node.into_vnode()).collect();
if children.is_empty() {
DynamicNode::default()
} else {
DynamicNode::Fragment(children)
}
}
}
pub trait IntoAttributeValue<T = ()> {
fn into_value(self) -> AttributeValue;
}
impl IntoAttributeValue for AttributeValue {
fn into_value(self) -> AttributeValue {
self
}
}
impl IntoAttributeValue for &str {
fn into_value(self) -> AttributeValue {
AttributeValue::Text(self.to_string())
}
}
impl IntoAttributeValue for String {
fn into_value(self) -> AttributeValue {
AttributeValue::Text(self)
}
}
impl IntoAttributeValue for f32 {
fn into_value(self) -> AttributeValue {
AttributeValue::Float(self as _)
}
}
impl IntoAttributeValue for f64 {
fn into_value(self) -> AttributeValue {
AttributeValue::Float(self)
}
}
impl IntoAttributeValue for i8 {
fn into_value(self) -> AttributeValue {
AttributeValue::Int(self as _)
}
}
impl IntoAttributeValue for i16 {
fn into_value(self) -> AttributeValue {
AttributeValue::Int(self as _)
}
}
impl IntoAttributeValue for i32 {
fn into_value(self) -> AttributeValue {
AttributeValue::Int(self as _)
}
}
impl IntoAttributeValue for i64 {
fn into_value(self) -> AttributeValue {
AttributeValue::Int(self)
}
}
impl IntoAttributeValue for isize {
fn into_value(self) -> AttributeValue {
AttributeValue::Int(self as _)
}
}
impl IntoAttributeValue for i128 {
fn into_value(self) -> AttributeValue {
AttributeValue::Int(self as _)
}
}
impl IntoAttributeValue for u8 {
fn into_value(self) -> AttributeValue {
AttributeValue::Int(self as _)
}
}
impl IntoAttributeValue for u16 {
fn into_value(self) -> AttributeValue {
AttributeValue::Int(self as _)
}
}
impl IntoAttributeValue for u32 {
fn into_value(self) -> AttributeValue {
AttributeValue::Int(self as _)
}
}
impl IntoAttributeValue for u64 {
fn into_value(self) -> AttributeValue {
AttributeValue::Int(self as _)
}
}
impl IntoAttributeValue for usize {
fn into_value(self) -> AttributeValue {
AttributeValue::Int(self as _)
}
}
impl IntoAttributeValue for u128 {
fn into_value(self) -> AttributeValue {
AttributeValue::Int(self as _)
}
}
impl IntoAttributeValue for bool {
fn into_value(self) -> AttributeValue {
AttributeValue::Bool(self)
}
}
impl IntoAttributeValue for Arguments<'_> {
fn into_value(self) -> AttributeValue {
AttributeValue::Text(self.to_string())
}
}
impl IntoAttributeValue for Rc<dyn AnyValue> {
fn into_value(self) -> AttributeValue {
AttributeValue::Any(self)
}
}
impl<T> IntoAttributeValue for ListenerCallback<T> {
fn into_value(self) -> AttributeValue {
AttributeValue::Listener(self.erase())
}
}
impl<T: IntoAttributeValue> IntoAttributeValue for Option<T> {
fn into_value(self) -> AttributeValue {
match self {
Some(val) => val.into_value(),
None => AttributeValue::None,
}
}
}
pub struct AnyFmtMarker;
impl<T> IntoAttributeValue<AnyFmtMarker> for T
where
T: DioxusFormattable,
{
fn into_value(self) -> AttributeValue {
AttributeValue::Text(self.format().to_string())
}
}
pub trait HasAttributes {
fn push_attribute<T>(
self,
name: &'static str,
ns: Option<&'static str>,
attr: impl IntoAttributeValue<T>,
volatile: bool,
) -> Self;
}