dioxus/packages/core/src/nodes.rs

367 lines
11 KiB
Rust
Raw Normal View History

2021-02-03 07:26:04 +00:00
//! Virtual Node Support
//! VNodes represent lazily-constructed VDom trees that support diffing and event handlers.
//!
//! These VNodes should be *very* cheap and *very* fast to construct - building a full tree should be insanely quick.
use bumpalo::Bump;
pub use vcomponent::VComponent;
pub use velement::VElement;
pub use velement::{Attribute, Listener, NodeKey};
pub use vnode::VNode;
pub use vtext::VText;
2021-02-21 02:59:16 +00:00
/// A domtree represents the result of "Viewing" the context
/// It's a placeholder over vnodes, to make working with lifetimes easier
pub struct DomTree;
2021-02-03 07:26:04 +00:00
/// Tools for the base unit of the virtual dom - the VNode
/// VNodes are intended to be quickly-allocated, lightweight enum values.
///
/// Components will be generating a lot of these very quickly, so we want to
/// limit the amount of heap allocations / overly large enum sizes.
mod vnode {
use super::*;
2021-02-21 02:59:16 +00:00
#[derive(Debug)]
2021-02-03 07:26:04 +00:00
pub enum VNode<'src> {
/// An element node (node type `ELEMENT_NODE`).
2021-02-08 00:14:04 +00:00
Element(&'src VElement<'src>),
2021-02-03 07:26:04 +00:00
/// A text node (node type `TEXT_NODE`).
///
/// Note: This wraps a `VText` instead of a plain `String` in
/// order to enable custom methods like `create_text_node()` on the
/// wrapped type.
Text(VText<'src>),
/// A "suspended component"
/// This is a masqeurade over an underlying future that needs to complete
/// When the future is completed, the VNode will then trigger a render
Suspended,
/// A User-defined componen node (node type COMPONENT_NODE)
2021-02-12 05:29:46 +00:00
Component(VComponent<'src>),
2021-02-03 07:26:04 +00:00
}
2021-02-08 00:14:04 +00:00
impl<'a> VNode<'a> {
/// Low-level constructor for making a new `Node` of type element with given
/// parts.
2021-02-03 07:26:04 +00:00
///
2021-02-08 00:14:04 +00:00
/// This is primarily intended for JSX and templating proc-macros to compile
/// down into. If you are building nodes by-hand, prefer using the
/// `dodrio::builder::*` APIs.
#[inline]
pub fn element(
bump: &'a Bump,
key: NodeKey,
tag_name: &'a str,
listeners: &'a [Listener<'a>],
attributes: &'a [Attribute<'a>],
children: &'a [VNode<'a>],
namespace: Option<&'a str>,
) -> VNode<'a> {
let element = bump.alloc_with(|| VElement {
key,
tag_name,
listeners,
attributes,
children,
namespace,
});
VNode::Element(element)
2021-02-03 07:26:04 +00:00
}
/// Construct a new text node with the given text.
#[inline]
2021-02-08 00:14:04 +00:00
pub fn text(text: &'a str) -> VNode<'a> {
2021-02-03 07:26:04 +00:00
VNode::Text(VText { text })
}
2021-02-08 00:14:04 +00:00
#[inline]
pub(crate) fn key(&self) -> NodeKey {
match &self {
VNode::Text(_) => NodeKey::NONE,
VNode::Element(e) => e.key,
VNode::Suspended => {
todo!()
}
VNode::Component(_) => {
todo!()
}
}
}
2021-02-03 07:26:04 +00:00
}
}
mod velement {
2021-03-03 07:27:26 +00:00
// use crate::{events::VirtualEvent, innerlude::CbIdx};
use crate::{events::VirtualEvent, innerlude::ScopeIdx};
2021-02-03 07:26:04 +00:00
use super::*;
2021-02-26 17:58:03 +00:00
use std::fmt::Debug;
2021-02-03 07:26:04 +00:00
2021-02-21 02:59:16 +00:00
#[derive(Debug)]
2021-02-03 07:26:04 +00:00
pub struct VElement<'a> {
2021-02-08 00:14:04 +00:00
/// Elements have a tag name, zero or more attributes, and zero or more
pub key: NodeKey,
2021-02-03 07:26:04 +00:00
pub tag_name: &'a str,
2021-02-08 00:14:04 +00:00
pub listeners: &'a [Listener<'a>],
2021-02-03 07:26:04 +00:00
pub attributes: &'a [Attribute<'a>],
2021-02-08 00:14:04 +00:00
pub children: &'a [VNode<'a>],
pub namespace: Option<&'a str>,
// The HTML tag, such as "div"
// pub tag: &'a str,
// pub tag_name: &'a str,
// pub attributes: &'a [Attribute<'a>],
2021-02-03 07:26:04 +00:00
// todo: hook up listeners
// pub listeners: &'a [Listener<'a>],
// / HTML attributes such as id, class, style, etc
// pub attrs: HashMap<String, String>,
// TODO: @JON Get this to not heap allocate, but rather borrow
// pub attrs: HashMap<&'static str, &'static str>,
// TODO @Jon, re-enable "events"
//
// /// Events that will get added to your real DOM element via `.addEventListener`
// pub events: Events,
// pub events: HashMap<String, ()>,
// /// The children of this `VNode`. So a <div> <em></em> </div> structure would
// /// have a parent div and one child, em.
// pub children: Vec<VNode>,
}
impl<'a> VElement<'a> {
// The tag of a component MUST be known at compile time
2021-02-24 06:32:50 +00:00
pub fn new(_tag: &'a str) -> Self {
2021-02-03 07:26:04 +00:00
todo!()
// VElement {
// tag,
// attrs: HashMap::new(),
// events: HashMap::new(),
// // events: Events(HashMap::new()),
// children: vec![],
// }
}
}
/// An attribute on a DOM node, such as `id="my-thing"` or
/// `href="https://example.com"`.
2021-02-15 19:14:28 +00:00
#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
2021-02-03 07:26:04 +00:00
pub struct Attribute<'a> {
2021-02-15 04:39:46 +00:00
pub name: &'static str,
pub value: &'a str,
2021-02-03 07:26:04 +00:00
}
impl<'a> Attribute<'a> {
/// Get this attribute's name, such as `"id"` in `<div id="my-thing" />`.
#[inline]
pub fn name(&self) -> &'a str {
self.name
}
/// The attribute value, such as `"my-thing"` in `<div id="my-thing" />`.
#[inline]
pub fn value(&self) -> &'a str {
self.value
}
/// Certain attributes are considered "volatile" and can change via user
/// input that we can't see when diffing against the old virtual DOM. For
/// these attributes, we want to always re-set the attribute on the physical
/// DOM node, even if the old and new virtual DOM nodes have the same value.
#[inline]
pub(crate) fn is_volatile(&self) -> bool {
match self.name {
"value" | "checked" | "selected" => true,
_ => false,
}
}
}
2021-03-03 07:27:26 +00:00
pub struct ListenerHandle {
pub event: &'static str,
pub scope: ScopeIdx,
pub id: usize,
2021-03-03 07:27:26 +00:00
}
2021-02-03 07:26:04 +00:00
/// An event listener.
2021-02-12 21:11:33 +00:00
pub struct Listener<'bump> {
2021-02-03 07:26:04 +00:00
/// The type of event to listen for.
2021-02-12 21:11:33 +00:00
pub(crate) event: &'static str,
2021-02-15 19:14:28 +00:00
pub scope: ScopeIdx,
pub id: usize,
2021-02-15 19:14:28 +00:00
// pub(crate) _i: &'bump str,
// #[serde(skip_serializing, skip_deserializing, default="")]
// /// The callback to invoke when the event happens.
pub(crate) callback: &'bump (dyn Fn(VirtualEvent)),
2021-02-03 07:26:04 +00:00
}
2021-02-21 02:59:16 +00:00
impl Debug for Listener<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Listener")
.field("event", &self.event)
.finish()
}
}
pub(crate) type ListenerCallback<'a> = &'a (dyn Fn(VirtualEvent));
2021-02-26 17:58:03 +00:00
union CallbackFatPtr<'a> {
callback: ListenerCallback<'a>,
parts: (u32, u32),
}
impl Listener<'_> {
#[inline]
pub(crate) fn get_callback_parts(&self) -> (u32, u32) {
assert_eq!(
std::mem::size_of::<ListenerCallback>(),
std::mem::size_of::<CallbackFatPtr>()
);
unsafe {
let fat = CallbackFatPtr {
callback: self.callback,
};
let (a, b) = fat.parts;
debug_assert!(a != 0);
(a, b)
}
}
}
2021-02-03 07:26:04 +00:00
/// The key for keyed children.
///
/// Keys must be unique among siblings.
///
/// If any sibling is keyed, then they all must be keyed.
2021-02-17 15:53:55 +00:00
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
2021-02-03 07:26:04 +00:00
pub struct NodeKey(pub(crate) u32);
impl Default for NodeKey {
fn default() -> NodeKey {
NodeKey::NONE
}
}
impl NodeKey {
/// The default, lack of a key.
pub const NONE: NodeKey = NodeKey(u32::MAX);
/// Is this key `NodeKey::NONE`?
#[inline]
pub fn is_none(&self) -> bool {
*self == Self::NONE
}
/// Is this key not `NodeKey::NONE`?
#[inline]
pub fn is_some(&self) -> bool {
!self.is_none()
}
/// Create a new `NodeKey`.
///
/// `key` must not be `u32::MAX`.
#[inline]
pub fn new(key: u32) -> Self {
debug_assert_ne!(key, u32::MAX);
NodeKey(key)
}
}
// todo
// use zst enum for element type. Something like ValidElements::div
}
mod vtext {
2021-02-21 02:59:16 +00:00
#[derive(Debug, PartialEq, serde::Serialize, serde::Deserialize)]
2021-02-12 21:11:33 +00:00
pub struct VText<'bump> {
pub text: &'bump str,
2021-02-03 07:26:04 +00:00
}
impl<'a> VText<'a> {
// / Create an new `VText` instance with the specified text.
pub fn new(text: &'a str) -> Self
// pub fn new<S>(text: S) -> Self
2021-02-03 07:26:04 +00:00
// where
// S: Into<str>,
{
VText { text: text.into() }
}
2021-02-03 07:26:04 +00:00
}
}
/// Virtual Components for custom user-defined components
/// Only supports the functional syntax
mod vcomponent {
2021-03-05 20:02:36 +00:00
use crate::innerlude::{Context, ScopeIdx, FC};
use std::{any::TypeId, cell::RefCell, marker::PhantomData, rc::Rc};
2021-03-04 17:03:22 +00:00
use super::DomTree;
2021-02-12 05:29:46 +00:00
2021-03-05 20:02:36 +00:00
pub type StableScopeAddres = Rc<RefCell<Option<ScopeIdx>>>;
2021-02-21 02:59:16 +00:00
#[derive(Debug)]
2021-02-12 05:29:46 +00:00
pub struct VComponent<'src> {
_p: PhantomData<&'src ()>,
2021-03-04 17:03:22 +00:00
pub(crate) props: Box<dyn std::any::Any>,
pub(crate) props_type: TypeId,
pub(crate) comp: *const (),
pub(crate) caller: Caller,
2021-03-05 20:02:36 +00:00
// once a component gets mounted, its parent gets a stable address.
// this way we can carry the scope index from between renders
// genius, really!
pub assigned_scope: StableScopeAddres,
2021-03-04 17:03:22 +00:00
}
pub struct Caller(Box<dyn Fn(Context) -> DomTree>);
impl std::fmt::Debug for Caller {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
todo!()
}
2021-02-12 05:29:46 +00:00
}
impl<'a> VComponent<'a> {
2021-03-04 17:03:22 +00:00
// use the type parameter on props creation and move it into a portable context
// this lets us keep scope generic *and* downcast its props when we need to:
// - perform comparisons when diffing (memoization)
// -
pub fn new<P>(comp: FC<P>, props: P) -> Self {
2021-03-05 20:02:36 +00:00
// let caller = move |ctx: Context| {
// let t = comp(ctx, &props);
// t
// };
2021-03-04 17:03:22 +00:00
// let _caller = comp as *const ();
// let _props = Box::new(props);
// cast to static?
// how do we save the type of props ?
// do it in the caller function?
// something like
// the "true" entrpypoint
//
// |caller| caller.call(fn, Props {})
// fn call<P>(f, new_props) {
// let old_props = old.downcast_ref::<P>();
// if new_props == old_props {
// return;
// } else {
// // set the new props
// // call the fn
// }
// }
2021-02-21 02:59:16 +00:00
todo!()
// Self {
// _p: PhantomData {},
// props,
// caller,
// }
}
2021-02-03 07:26:04 +00:00
}
}