diff --git a/examples/inputs.rs b/examples/inputs.rs index 9b6ec104f..4b3dea56a 100644 --- a/examples/inputs.rs +++ b/examples/inputs.rs @@ -2,7 +2,8 @@ //! //! There is some conversion happening when input types are checkbox/radio/select/textarea etc. -use dioxus::{events::FormEvent, prelude::*}; +use dioxus::events::FormEvent; +use dioxus::prelude::*; fn main() { dioxus_desktop::launch(app); diff --git a/packages/core/src/arena.rs b/packages/core/src/arena.rs index fbfd9cd8f..827e0d903 100644 --- a/packages/core/src/arena.rs +++ b/packages/core/src/arena.rs @@ -1,5 +1,6 @@ use crate::{nodes::VNode, virtual_dom::VirtualDom}; +#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))] #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Default)] pub struct ElementId(pub usize); diff --git a/packages/core/src/create.rs b/packages/core/src/create.rs index ce28d53a3..ccbdce71c 100644 --- a/packages/core/src/create.rs +++ b/packages/core/src/create.rs @@ -87,10 +87,14 @@ impl VirtualDom { value, id, }), + AttributeValue::Bool(value) => mutations.push(SetBoolAttribute { + name: attribute.name, + value, + id, + }), AttributeValue::Listener(_) => todo!("create listener attributes"), AttributeValue::Float(_) => todo!(), AttributeValue::Int(_) => todo!(), - AttributeValue::Bool(_) => todo!(), AttributeValue::Any(_) => todo!(), AttributeValue::None => todo!(), } @@ -138,11 +142,13 @@ impl VirtualDom { TemplateNode::DynamicText { .. } => mutations.push(CreateText { value: "placeholder", }), + TemplateNode::Element { attrs, children, namespace, tag, + inner_opt, } => { let id = self.next_element(template); @@ -159,6 +165,10 @@ impl VirtualDom { _ => None, })); + if children.is_empty() && inner_opt { + return; + } + children .into_iter() .for_each(|child| self.create_static_node(mutations, template, child)); @@ -176,7 +186,7 @@ impl VirtualDom { idx: usize, ) -> usize { match &node { - DynamicNode::Text { id, value } => { + DynamicNode::Text { id, value, inner } => { let new_id = self.next_element(template); id.set(new_id); mutations.push(HydrateText { @@ -188,11 +198,17 @@ impl VirtualDom { } DynamicNode::Component { - props, placeholder, .. + props, + placeholder, + scope: scope_slot, + .. } => { let scope = self .new_scope(unsafe { std::mem::transmute(props.get()) }) .id; + + scope_slot.set(Some(scope)); + let return_nodes = unsafe { self.run_scope(scope).extend_lifetime_ref() }; match return_nodes { @@ -247,7 +263,7 @@ impl VirtualDom { } } - DynamicNode::Fragment(children) => children + DynamicNode::Fragment { nodes, .. } => nodes .iter() .fold(0, |acc, child| acc + self.create(mutations, child)), diff --git a/packages/core/src/diff.rs b/packages/core/src/diff.rs index 3b7a10106..7e9e10e44 100644 --- a/packages/core/src/diff.rs +++ b/packages/core/src/diff.rs @@ -177,7 +177,7 @@ impl<'b> VirtualDom { todo!() }, - (DynamicNode::Text { id: lid, value: lvalue }, DynamicNode::Text { id: rid, value: rvalue }) => { + (DynamicNode::Text { id: lid, value: lvalue, .. }, DynamicNode::Text { id: rid, value: rvalue, .. }) => { rid.set(lid.get()); if lvalue != rvalue { muts.push(Mutation::SetText { @@ -196,7 +196,7 @@ impl<'b> VirtualDom { (DynamicNode::Placeholder(_), _) => todo!(), - (DynamicNode::Fragment (l), DynamicNode::Fragment (r)) => { + (DynamicNode::Fragment { nodes: lnodes, ..}, DynamicNode::Fragment { nodes: rnodes, ..}) => { // match (old, new) { diff --git a/packages/core/src/events.rs b/packages/core/src/events.rs index 6cb098e60..074a93e34 100644 --- a/packages/core/src/events.rs +++ b/packages/core/src/events.rs @@ -1,50 +1,5 @@ -use crate::{arena::ElementId, virtual_dom::VirtualDom, Attribute, AttributeValue}; -use std::cell::Cell; - -/// User Events are events that are shuttled from the renderer into the [`VirtualDom`] through the scheduler channel. -/// -/// These events will be passed to the appropriate Element given by `mounted_dom_id` and then bubbled up through the tree -/// where each listener is checked and fired if the event name matches. -/// -/// It is the expectation that the event name matches the corresponding event listener, otherwise Dioxus will panic in -/// attempting to downcast the event data. -/// -/// Because Event Data is sent across threads, it must be `Send + Sync`. We are hoping to lift the `Sync` restriction but -/// `Send` will not be lifted. The entire `UserEvent` must also be `Send + Sync` due to its use in the scheduler channel. -/// -/// # Example -/// ```rust, ignore -/// fn App(cx: Scope) -> Element { -/// render!(div { -/// onclick: move |_| println!("Clicked!") -/// }) -/// } -/// -/// let mut dom = VirtualDom::new(App); -/// let mut scheduler = dom.get_scheduler_channel(); -/// scheduler.unbounded_send(SchedulerMsg::UiEvent( -/// UserEvent { -/// scope_id: None, -/// priority: EventPriority::Medium, -/// name: "click", -/// element: Some(ElementId(0)), -/// data: Arc::new(ClickEvent { .. }) -/// } -/// )).unwrap(); -/// ``` -#[derive(Debug)] -pub struct UiEvent { - /// The priority of the event to be scheduled around ongoing work - pub priority: EventPriority, - - /// The optional real node associated with the trigger - pub element: ElementId, - - /// The event type IE "onclick" or "onmouseover" - pub name: &'static str, - pub bubble: Cell, - pub event: T, -} +use bumpalo::boxed::Box as BumpBox; +use std::{any::Any, cell::RefCell}; /// Priority of Event Triggers. /// @@ -108,6 +63,65 @@ pub fn is_path_ascendant(small: &[u8], big: &[u8]) -> bool { small == &big[..small.len()] } +pub type InternalHandler<'bump> = &'bump RefCell>>; +type InternalListenerCallback<'bump> = BumpBox<'bump, dyn FnMut(&'bump dyn Any) + 'bump>; +type ExternalListenerCallback<'bump, T> = BumpBox<'bump, dyn FnMut(T) + 'bump>; + +/// The callback type generated by the `rsx!` macro when an `on` field is specified for components. +/// +/// This makes it possible to pass `move |evt| {}` style closures into components as property fields. +/// +/// +/// # Example +/// +/// ```rust, ignore +/// +/// rsx!{ +/// MyComponent { onclick: move |evt| log::info!("clicked"), } +/// } +/// +/// #[derive(Props)] +/// struct MyProps<'a> { +/// onclick: EventHandler<'a, MouseEvent>, +/// } +/// +/// fn MyComponent(cx: Scope<'a, MyProps<'a>>) -> Element { +/// cx.render(rsx!{ +/// button { +/// onclick: move |evt| cx.props.onclick.call(evt), +/// } +/// }) +/// } +/// +/// ``` +pub struct EventHandler<'bump, T = ()> { + /// The (optional) callback that the user specified + /// Uses a `RefCell` to allow for interior mutability, and FnMut closures. + pub callback: RefCell>>, +} + +impl<'a, T> Default for EventHandler<'a, T> { + fn default() -> Self { + Self { + callback: RefCell::new(None), + } + } +} + +impl EventHandler<'_, T> { + /// Call this event handler with the appropriate event type + pub fn call(&self, event: T) { + if let Some(callback) = self.callback.borrow_mut().as_mut() { + callback(event); + } + } + + /// Forcibly drop the internal handler callback, releasing memory + pub fn release(&self) { + self.callback.replace(None); + } +} + #[test] fn matches_slice() { let left = &[1, 2, 3]; diff --git a/packages/core/src/factory.rs b/packages/core/src/factory.rs index fbe6bdf57..3996b123e 100644 --- a/packages/core/src/factory.rs +++ b/packages/core/src/factory.rs @@ -18,6 +18,7 @@ impl ScopeState { DynamicNode::Text { id: Cell::new(ElementId(0)), value: text, + inner: false, } } @@ -37,27 +38,32 @@ impl ScopeState { } } - pub fn fragment_from_iter<'a, I, F: IntoVnode<'a, I>>( + pub fn fragment_from_iter<'a, 'c, I, J>( &'a self, - it: impl IntoIterator, + node_iter: impl IntoVNode<'a, I, J> + 'c, ) -> DynamicNode { - let mut bump_vec = bumpalo::vec![in self.bump();]; + node_iter.into_vnode(self) - for item in it { - bump_vec.push(item.into_dynamic_node(self)); - } + // let mut bump_vec = bumpalo::vec![in self.bump();]; - match bump_vec.len() { - 0 => DynamicNode::Placeholder(Cell::new(ElementId(0))), - _ => DynamicNode::Fragment(bump_vec.into_bump_slice()), - } + // for item in it { + // bump_vec.push(item.into_vnode(self)); + // } + + // match bump_vec.len() { + // 0 => DynamicNode::Placeholder(Cell::new(ElementId(0))), + // _ => DynamicNode::Fragment { + // inner: false, + // nodes: bump_vec.into_bump_slice(), + // }, + // } } /// Create a new [`Attribute`] pub fn attr<'a>( &'a self, name: &'static str, - val: impl IntoAttributeValue<'a>, + value: impl IntoAttributeValue<'a>, namespace: Option<&'static str>, volatile: bool, ) -> Attribute<'a> { @@ -65,7 +71,7 @@ impl ScopeState { name, namespace, volatile, - value: val.into_value(self.bump()), + value: value.into_value(self.bump()), mounted_element: Cell::new(ElementId(0)), } } @@ -97,6 +103,7 @@ impl ScopeState { static_props: P::IS_STATIC, props: Cell::new(detached_dyn), placeholder: Cell::new(None), + scope: Cell::new(None), } } } @@ -137,39 +144,100 @@ impl<'a> RenderReturn<'a> { } } -pub trait IntoVnode<'a, A = ()> { - fn into_dynamic_node(self, cx: &'a ScopeState) -> VNode<'a>; +pub trait IntoVNode<'a, A = (), J = ()> { + fn into_vnode(self, cx: &'a ScopeState) -> DynamicNode<'a>; } -impl<'a, 'b> IntoVnode<'a> for LazyNodes<'a, 'b> { - fn into_dynamic_node(self, cx: &'a ScopeState) -> VNode<'a> { - self.call(cx) +impl<'a, 'b> IntoVNode<'a> for VNode<'a> { + fn into_vnode(self, _cx: &'a ScopeState) -> DynamicNode<'a> { + todo!() + // self } } -impl<'a, 'b> IntoVnode<'a> for VNode<'a> { - fn into_dynamic_node(self, _cx: &'a ScopeState) -> VNode<'a> { - self +impl<'a, 'b> IntoVNode<'a> for LazyNodes<'a, 'b> { + fn into_vnode(self, cx: &'a ScopeState) -> DynamicNode<'a> { + todo!() + // self.call(cx) } } -impl<'a, 'b> IntoVnode<'a> for Option> { - fn into_dynamic_node(self, _cx: &'a ScopeState) -> VNode<'a> { - self.expect("please allow optional nodes in") +impl<'b> IntoVNode<'_> for &'b str { + fn into_vnode(self, cx: &ScopeState) -> DynamicNode { + // cx.text(format_args!("{}", self)) + todo!() } } -impl<'a, 'b> IntoVnode<'a> for &'a VNode<'a> { - fn into_dynamic_node(self, _cx: &'a ScopeState) -> VNode<'a> { - VNode { - node_id: self.node_id.clone(), - parent: self.parent, - template: self.template, - root_ids: self.root_ids, - key: self.key, - dynamic_nodes: self.dynamic_nodes, - dynamic_attrs: self.dynamic_attrs, +impl IntoVNode<'_> for String { + fn into_vnode(self, cx: &ScopeState) -> DynamicNode { + // cx.text(format_args!("{}", self)) + todo!() + } +} + +impl<'b> IntoVNode<'b> for Arguments<'_> { + fn into_vnode(self, cx: &'b ScopeState) -> DynamicNode<'b> { + // cx.text(self) + todo!() + } +} + +impl<'a, 'b> IntoVNode<'a> for &VNode<'a> { + fn into_vnode(self, _cx: &'a ScopeState) -> DynamicNode<'a> { + todo!() + // VNode { + // node_id: self.node_id.clone(), + // parent: self.parent, + // template: self.template, + // root_ids: self.root_ids, + // key: self.key, + // dynamic_nodes: self.dynamic_nodes, + // dynamic_attrs: self.dynamic_attrs, + // } + } +} + +// Note that we're using the E as a generic but this is never crafted anyways. +pub struct FromNodeIterator; +impl<'a, T, I, E> IntoVNode<'a, FromNodeIterator, E> for T +where + T: IntoIterator, + I: IntoVNode<'a, E>, +{ + fn into_vnode(self, cx: &'a ScopeState) -> DynamicNode<'a> { + let mut nodes = bumpalo::collections::Vec::new_in(cx.bump()); + + for node in self { + nodes.push(node.into_vnode(cx)); } + + let children = nodes.into_bump_slice(); + + // if cfg!(debug_assertions) && children.len() > 1 && children.last().unwrap().key().is_none() + // { + // let bt = backtrace::Backtrace::new(); + // let bt = "no backtrace available"; + + // // todo: make the backtrace prettier or remove it altogether + // log::error!( + // r#" + // Warning: Each child in an array or iterator should have a unique "key" prop. + // Not providing a key will lead to poor performance with lists. + // See docs.rs/dioxus for more information. + // ------------- + // {:?} + // "#, + // bt + // ); + // } + + todo!() + // VNode::Fragment(cx.bump.alloc(VFragment { + // children, + // placeholder: Default::default(), + // key: None, + // })) } } diff --git a/packages/core/src/garbage.rs b/packages/core/src/garbage.rs index 2f77dba8d..5f8c422f0 100644 --- a/packages/core/src/garbage.rs +++ b/packages/core/src/garbage.rs @@ -25,8 +25,9 @@ impl<'b> VirtualDom { todo!() } - DynamicNode::Fragment(children) => {} + DynamicNode::Fragment { inner, nodes } => {} DynamicNode::Placeholder(_) => todo!(), + _ => todo!(), } } } diff --git a/packages/core/src/lib.rs b/packages/core/src/lib.rs index 77f9146e4..dd5c44103 100644 --- a/packages/core/src/lib.rs +++ b/packages/core/src/lib.rs @@ -92,7 +92,6 @@ pub use crate::innerlude::{ Template, TemplateAttribute, TemplateNode, - UiEvent, VNode, VirtualDom, }; @@ -102,9 +101,9 @@ pub use crate::innerlude::{ /// This includes types like [`Scope`], [`Element`], and [`Component`]. pub mod prelude { pub use crate::innerlude::{ - fc_to_builder, Element, EventPriority, Fragment, LazyNodes, NodeFactory, Properties, Scope, - ScopeId, ScopeState, Scoped, TaskId, Template, TemplateAttribute, TemplateNode, UiEvent, - VNode, VirtualDom, + fc_to_builder, Element, EventHandler, EventPriority, Fragment, LazyNodes, NodeFactory, + Properties, Scope, ScopeId, ScopeState, Scoped, TaskId, Template, TemplateAttribute, + TemplateNode, VNode, VirtualDom, }; } diff --git a/packages/core/src/mutations.rs b/packages/core/src/mutations.rs index 8e8affae0..4d0c10079 100644 --- a/packages/core/src/mutations.rs +++ b/packages/core/src/mutations.rs @@ -35,6 +35,7 @@ impl std::ops::DerefMut for Mutations<'_> { each subtree has its own numbering scheme */ +#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))] #[derive(Debug, PartialEq, Eq)] pub enum Mutation<'a> { SetAttribute { @@ -43,6 +44,12 @@ pub enum Mutation<'a> { id: ElementId, }, + SetBoolAttribute { + name: &'a str, + value: bool, + id: ElementId, + }, + LoadTemplate { name: &'static str, index: usize, @@ -86,6 +93,10 @@ pub enum Mutation<'a> { id: ElementId, }, + SetInnerText { + value: &'a str, + }, + CreateText { value: &'a str, }, diff --git a/packages/core/src/nodes.rs b/packages/core/src/nodes.rs index 3388dff97..8fc38e34e 100644 --- a/packages/core/src/nodes.rs +++ b/packages/core/src/nodes.rs @@ -1,4 +1,4 @@ -use crate::{any_props::AnyProps, arena::ElementId, ScopeState}; +use crate::{any_props::AnyProps, arena::ElementId, ScopeId, ScopeState}; use std::{ any::{Any, TypeId}, cell::{Cell, RefCell}, @@ -15,7 +15,7 @@ pub struct VNode<'a> { pub key: Option<&'a str>, // When rendered, this template will be linked to its parent manually - pub parent: Option<(*mut VNode<'static>, usize)>, + pub parent: Option<*const DynamicNode<'a>>, pub template: Template<'static>, @@ -98,6 +98,7 @@ pub enum TemplateNode<'a> { namespace: Option<&'a str>, attrs: &'a [TemplateAttribute<'a>], children: &'a [TemplateNode<'a>], + inner_opt: bool, }, Text(&'a str), Dynamic(usize), @@ -110,12 +111,17 @@ pub enum DynamicNode<'a> { static_props: bool, props: Cell<*mut dyn AnyProps<'a>>, placeholder: Cell>, + scope: Cell>, }, Text { id: Cell, value: &'a str, + inner: bool, + }, + Fragment { + nodes: &'a [VNode<'a>], + inner: bool, }, - Fragment(&'a [VNode<'a>]), Placeholder(Cell), } @@ -215,3 +221,54 @@ fn what_are_the_sizes() { dbg!(std::mem::size_of::