diff --git a/Cargo.toml b/Cargo.toml index ae0a7490c..50a0bfbf6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,4 +1,3 @@ - [workspace] members = [ "packages/dioxus", @@ -12,17 +11,20 @@ members = [ "packages/desktop", "packages/mobile", "packages/interpreter", - # "packages/tui", "packages/fermi", "packages/liveview", "packages/autofmt", "packages/rsx", + "docs/guide", + # "packages/tui", # "packages/native-core", # "packages/native-core-macro", - # "packages/edit-stream", - "docs/guide", ] - +exclude = [ + "packages/tui", + "packages/native-core", + "packages/native-core-macro", +] # This is a "virtual package" # It is not meant to be published, but is used so "cargo run --example XYZ" works properly diff --git a/packages/autofmt/src/buffer.rs b/packages/autofmt/src/buffer.rs index 720802346..45799ca62 100644 --- a/packages/autofmt/src/buffer.rs +++ b/packages/autofmt/src/buffer.rs @@ -67,7 +67,7 @@ impl Buffer { match node { BodyNode::Element(el) => self.write_element(el), BodyNode::Component(component) => self.write_component(component), - BodyNode::DynamicText(text) => self.write_text(text), + BodyNode::Text(text) => self.write_text(text), BodyNode::RawExpr(exp) => self.write_raw_expr(exp), _ => Ok(()), } diff --git a/packages/autofmt/src/element.rs b/packages/autofmt/src/element.rs index 4f14249c9..fd4529766 100644 --- a/packages/autofmt/src/element.rs +++ b/packages/autofmt/src/element.rs @@ -280,7 +280,7 @@ impl Buffer { } match children { - [BodyNode::DynamicText(ref text)] => Some(text.source.as_ref().unwrap().value().len()), + [BodyNode::Text(ref text)] => Some(text.source.as_ref().unwrap().value().len()), [BodyNode::Component(ref comp)] => { let attr_len = self.field_len(&comp.fields, &comp.manual_props); diff --git a/packages/core/src/create.rs b/packages/core/src/create.rs index 2f11eff1f..c64763c5b 100644 --- a/packages/core/src/create.rs +++ b/packages/core/src/create.rs @@ -228,27 +228,29 @@ impl<'b> VirtualDom { self.create_static_node(node); } - self.mutations.template_mutations.push(SaveTemplate { + self.mutations.template_edits.push(SaveTemplate { name: template.template.id, m: template.template.roots.len(), }); } + // todo: we shouldn't have any of instructions for building templates - renderers should be able to work with the + // template type directly, right? pub(crate) fn create_static_node(&mut self, node: &'b TemplateNode<'static>) { match *node { // Todo: create the children's template TemplateNode::Dynamic(_) => self .mutations - .template_mutations + .template_edits .push(CreateStaticPlaceholder {}), TemplateNode::Text(value) => self .mutations - .template_mutations + .template_edits .push(CreateStaticText { value }), - TemplateNode::DynamicText { .. } => self - .mutations - .template_mutations - .push(CreateTextPlaceholder), + + TemplateNode::DynamicText { .. } => { + self.mutations.template_edits.push(CreateTextPlaceholder) + } TemplateNode::Element { attrs, @@ -257,21 +259,19 @@ impl<'b> VirtualDom { tag, .. } => { - if let Some(namespace) = namespace { - self.mutations - .template_mutations - .push(CreateElementNamespace { - name: tag, - namespace, - }); - } else { - self.mutations - .template_mutations - .push(CreateElement { name: tag }); + match namespace { + Some(namespace) => self.mutations.template_edits.push(CreateElementNamespace { + name: tag, + namespace, + }), + None => self + .mutations + .template_edits + .push(CreateElement { name: tag }), } self.mutations - .template_mutations + .template_edits .extend(attrs.iter().filter_map(|attr| match attr { TemplateAttribute::Static { name, @@ -295,7 +295,7 @@ impl<'b> VirtualDom { .for_each(|child| self.create_static_node(child)); self.mutations - .template_mutations + .template_edits .push(AppendChildren { m: children.len() }) } } @@ -404,7 +404,7 @@ impl<'b> VirtualDom { ) -> usize { // Keep track of how many mutations are in the buffer in case we need to split them out if a suspense boundary // is encountered - let mutations_to_this_point = self.mutations.len(); + let mutations_to_this_point = self.mutations.dom_edits.len(); // Create the component's root element let created = self.create_scope(scope, new); @@ -430,10 +430,10 @@ impl<'b> VirtualDom { // Note that we break off dynamic mutations only - since static mutations aren't rendered immediately let split_off = unsafe { std::mem::transmute::, Vec>( - self.mutations.split_off(mutations_to_this_point), + self.mutations.dom_edits.split_off(mutations_to_this_point), ) }; - boundary.mutations.borrow_mut().edits.extend(split_off); + boundary.mutations.borrow_mut().dom_edits.extend(split_off); boundary.created_on_stack.set(created); boundary .waiting_on diff --git a/packages/core/src/events.rs b/packages/core/src/events.rs index f511e73f9..c79cee549 100644 --- a/packages/core/src/events.rs +++ b/packages/core/src/events.rs @@ -1,57 +1,110 @@ -use bumpalo::boxed::Box as BumpBox; use std::{ - any::Any, cell::{Cell, RefCell}, - fmt::Debug, rc::Rc, }; -pub struct UiEvent { - pub(crate) bubbles: Rc>, +/// A wrapper around some generic data that handles the event's state +/// +/// +/// Prevent this event from continuing to bubble up the tree to parent elements. +/// +/// # Example +/// +/// ```rust, ignore +/// rsx! { +/// button { +/// onclick: move |evt: Event| { +/// evt.cancel_bubble(); +/// +/// } +/// } +/// } +/// ``` +pub struct Event { pub(crate) data: Rc, + pub(crate) propogates: Rc>, } -impl UiEvent { - pub fn downcast(self) -> Option> { - Some(UiEvent { - bubbles: self.bubbles, - data: self.data.downcast().ok()?, - }) +impl Event { + /// Prevent this event from continuing to bubble up the tree to parent elements. + /// + /// # Example + /// + /// ```rust, ignore + /// rsx! { + /// button { + /// onclick: move |evt: Event| { + /// evt.cancel_bubble(); + /// } + /// } + /// } + /// ``` + #[deprecated = "use stop_propogation instead"] + pub fn cancel_bubble(&self) { + self.propogates.set(false); + } + + /// Prevent this event from continuing to bubble up the tree to parent elements. + /// + /// # Example + /// + /// ```rust, ignore + /// rsx! { + /// button { + /// onclick: move |evt: Event| { + /// evt.cancel_bubble(); + /// } + /// } + /// } + /// ``` + pub fn stop_propogation(&self) { + self.propogates.set(false); + } + + /// Get a reference to the inner data from this event + /// + /// ```rust, ignore + /// rsx! { + /// button { + /// onclick: move |evt: Event| { + /// let data = evt.inner.clone(); + /// cx.spawn(async move { + /// println!("{:?}", data); + /// }); + /// } + /// } + /// } + /// ``` + pub fn inner(&self) -> &Rc { + &self.data } } -impl Clone for UiEvent { + +impl Clone for Event { fn clone(&self) -> Self { Self { - bubbles: self.bubbles.clone(), + propogates: self.propogates.clone(), data: self.data.clone(), } } } -impl UiEvent { - pub fn cancel_bubble(&self) { - self.bubbles.set(false); - } -} -impl std::ops::Deref for UiEvent { +impl std::ops::Deref for Event { type Target = Rc; - fn deref(&self) -> &Self::Target { &self.data } } -impl std::fmt::Debug for UiEvent { +impl std::fmt::Debug for Event { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("UiEvent") - .field("bubble_state", &self.bubbles) + .field("bubble_state", &self.propogates) .field("data", &self.data) .finish() } } -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. @@ -60,9 +113,8 @@ type ExternalListenerCallback<'bump, T> = BumpBox<'bump, dyn FnMut(T) + 'bump>; /// # Example /// /// ```rust, ignore -/// /// rsx!{ -/// MyComponent { onclick: move |evt| log::info!("clicked"), } +/// MyComponent { onclick: move |evt| log::info!("clicked") } /// } /// /// #[derive(Props)] @@ -79,22 +131,17 @@ type ExternalListenerCallback<'bump, T> = BumpBox<'bump, dyn FnMut(T) + 'bump>; /// } /// /// ``` +#[derive(Default)] 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>>, + pub(super) callback: RefCell>>, } -impl<'a, T> Default for EventHandler<'a, T> { - fn default() -> Self { - Self { - callback: RefCell::new(None), - } - } -} +type ExternalListenerCallback<'bump, T> = bumpalo::boxed::Box<'bump, dyn FnMut(T) + 'bump>; impl EventHandler<'_, T> { /// Call this event handler with the appropriate event type + /// + /// This borrows the event using a RefCell. Recursively calling a listener will cause a panic. pub fn call(&self, event: T) { if let Some(callback) = self.callback.borrow_mut().as_mut() { callback(event); @@ -102,6 +149,8 @@ impl EventHandler<'_, T> { } /// Forcibly drop the internal handler callback, releasing memory + /// + /// This will force any future calls to "call" to not doing anything pub fn release(&self) { self.callback.replace(None); } diff --git a/packages/core/src/factory.rs b/packages/core/src/factory.rs index 0423d521e..b4217184d 100644 --- a/packages/core/src/factory.rs +++ b/packages/core/src/factory.rs @@ -1,119 +1,21 @@ -use std::{ - cell::{Cell, RefCell}, - fmt::Arguments, -}; - +use crate::{innerlude::DynamicNode, AttributeValue, Element, LazyNodes, ScopeState, VNode}; use bumpalo::boxed::Box as BumpBox; use bumpalo::Bump; +use std::fmt::Arguments; use std::future::Future; -use crate::{ - any_props::{AnyProps, VProps}, - arena::ElementId, - innerlude::{DynamicNode, EventHandler, VComponent, VText}, - Attribute, AttributeValue, Element, LazyNodes, Properties, Scope, ScopeState, VNode, -}; - -impl ScopeState { - /// Create some text that's allocated along with the other vnodes - pub fn text<'a>(&'a self, args: Arguments) -> DynamicNode<'a> { - let (text, _) = self.raw_text(args); - DynamicNode::Text(VText { - id: Cell::new(ElementId(0)), - value: text, - }) - } - - pub fn raw_text_inline<'a>(&'a self, args: Arguments) -> &'a str { - self.raw_text(args).0 - } - - pub fn raw_text<'a>(&'a self, args: Arguments) -> (&'a str, bool) { - match args.as_str() { - Some(static_str) => (static_str, true), - None => { - use bumpalo::core_alloc::fmt::Write; - let mut str_buf = bumpalo::collections::String::new_in(self.bump()); - str_buf.write_fmt(args).unwrap(); - (str_buf.into_bump_str(), false) - } - } - } - - pub fn fragment_from_iter<'a, 'c, I>( - &'a self, - node_iter: impl IntoDynNode<'a, I> + 'c, - ) -> DynamicNode { - node_iter.into_vnode(self) - } - - /// Create a new [`Attribute`] - pub fn attr<'a>( - &'a self, - name: &'static str, - value: impl IntoAttributeValue<'a>, - namespace: Option<&'static str>, - volatile: bool, - ) -> Attribute<'a> { - Attribute { - name, - namespace, - volatile, - value: value.into_value(self.bump()), - mounted_element: Cell::new(ElementId(0)), - } - } - - /// Create a new [`VNode::Component`] - pub fn component<'a, P, A, F: ComponentReturn<'a, A>>( - &'a self, - component: fn(Scope<'a, P>) -> F, - props: P, - fn_name: &'static str, - ) -> DynamicNode<'a> - where - P: Properties + 'a, - { - let as_component = component; - let vcomp = VProps::new(as_component, P::memoize, props); - let as_dyn: Box> = Box::new(vcomp); - let extended: Box = unsafe { std::mem::transmute(as_dyn) }; - - // let as_dyn: &dyn AnyProps = self.bump().alloc(vcomp); - // todo: clean up borrowed props - // if !P::IS_STATIC { - // let vcomp = ex; - // let vcomp = unsafe { std::mem::transmute(vcomp) }; - // self.items.borrow_mut().borrowed_props.push(vcomp); - // } - - DynamicNode::Component(VComponent { - name: fn_name, - render_fn: component as *const (), - static_props: P::IS_STATIC, - props: Cell::new(Some(extended)), - scope: Cell::new(None), - }) - } - - /// Create a new [`EventHandler`] from an [`FnMut`] - pub fn event_handler<'a, T>(&'a self, f: impl FnMut(T) + 'a) -> EventHandler<'a, T> { - let handler: &mut dyn FnMut(T) = self.bump().alloc(f); - let caller = unsafe { BumpBox::from_raw(handler as *mut dyn FnMut(T)) }; - let callback = RefCell::new(Some(caller)); - EventHandler { callback } - } -} - +#[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 @@ -142,6 +44,7 @@ impl<'a> RenderReturn<'a> { } } +#[doc(hidden)] pub trait IntoDynNode<'a, A = ()> { fn into_vnode(self, cx: &'a ScopeState) -> DynamicNode<'a>; } @@ -191,19 +94,19 @@ impl<'a, 'b> IntoDynNode<'a> for LazyNodes<'a, 'b> { impl<'a> IntoDynNode<'_> for &'a str { fn into_vnode(self, cx: &ScopeState) -> DynamicNode { - cx.text(format_args!("{}", self)) + cx.text_node(format_args!("{}", self)) } } impl IntoDynNode<'_> for String { fn into_vnode(self, cx: &ScopeState) -> DynamicNode { - cx.text(format_args!("{}", self)) + cx.text_node(format_args!("{}", self)) } } impl<'b> IntoDynNode<'b> for Arguments<'_> { fn into_vnode(self, cx: &'b ScopeState) -> DynamicNode<'b> { - cx.text(self) + cx.text_node(self) } } @@ -235,6 +138,7 @@ impl<'a, 'b> IntoTemplate<'a> for LazyNodes<'a, 'b> { } // 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 diff --git a/packages/core/src/lazynodes.rs b/packages/core/src/lazynodes.rs index 76c25641f..35333ca40 100644 --- a/packages/core/src/lazynodes.rs +++ b/packages/core/src/lazynodes.rs @@ -3,7 +3,7 @@ //! This module provides support for a type called `LazyNodes` which is a micro-heap located on the stack to make calls //! to `rsx!` more efficient. //! -//! To support returning rsx! from branches in match statements, we need to use dynamic dispatch on [`NodeFactory`] closures. +//! To support returning rsx! from branches in match statements, we need to use dynamic dispatch on [`ScopeState`] closures. //! //! This can be done either through boxing directly, or by using dynamic-sized-types and a custom allocator. In our case, //! we build a tiny alloactor in the stack and allocate the closure into that. @@ -27,13 +27,11 @@ pub struct LazyNodes<'a, 'b> { inner: StackNodeStorage<'a, 'b>, } -pub type NodeFactory<'a> = &'a ScopeState; - type StackHeapSize = [usize; 16]; enum StackNodeStorage<'a, 'b> { Stack(LazyStack), - Heap(Box>) -> Option> + 'b>), + Heap(Box) -> Option> + 'b>), } impl<'a, 'b> LazyNodes<'a, 'b> { @@ -42,11 +40,11 @@ impl<'a, 'b> LazyNodes<'a, 'b> { /// If the closure cannot fit into the stack allocation (16 bytes), then it /// is placed on the heap. Most closures will fit into the stack, and is /// the most optimal way to use the creation function. - pub fn new(val: impl FnOnce(NodeFactory<'a>) -> VNode<'a> + 'b) -> Self { + pub fn new(val: impl FnOnce(&'a ScopeState) -> VNode<'a> + 'b) -> Self { // there's no way to call FnOnce without a box, so we need to store it in a slot and use static dispatch let mut slot = Some(val); - let val = move |fac: Option>| { + let val = move |fac: Option<&'a ScopeState>| { fac.map( slot.take() .expect("LazyNodes closure to be called only once"), @@ -67,13 +65,13 @@ impl<'a, 'b> LazyNodes<'a, 'b> { /// Create a new [`LazyNodes`] closure, but force it onto the heap. pub fn new_boxed(inner: F) -> Self where - F: FnOnce(NodeFactory<'a>) -> VNode<'a> + 'b, + F: FnOnce(&'a ScopeState) -> VNode<'a> + 'b, { // there's no way to call FnOnce without a box, so we need to store it in a slot and use static dispatch let mut slot = Some(inner); Self { - inner: StackNodeStorage::Heap(Box::new(move |fac: Option>| { + inner: StackNodeStorage::Heap(Box::new(move |fac: Option<&'a ScopeState>| { fac.map( slot.take() .expect("LazyNodes closure to be called only once"), @@ -84,9 +82,9 @@ impl<'a, 'b> LazyNodes<'a, 'b> { unsafe fn new_inner(val: F) -> Self where - F: FnMut(Option>) -> Option> + 'b, + F: FnMut(Option<&'a ScopeState>) -> Option> + 'b, { - let mut ptr: *const _ = &val as &dyn FnMut(Option>) -> Option>; + let mut ptr: *const _ = &val as &dyn FnMut(Option<&'a ScopeState>) -> Option>; assert_eq!( ptr as *const u8, &val as *const _ as *const u8, @@ -162,12 +160,10 @@ impl<'a, 'b> LazyNodes<'a, 'b> { /// ```rust, ignore /// let f = LazyNodes::new(move |f| f.element("div", [], [], [] None)); /// - /// let fac = NodeFactory::new(&cx); - /// /// let node = f.call(cac); /// ``` #[must_use] - pub fn call(self, f: NodeFactory<'a>) -> VNode<'a> { + pub fn call(self, f: &'a ScopeState) -> VNode<'a> { match self.inner { StackNodeStorage::Heap(mut lazy) => { lazy(Some(f)).expect("Closure should not be called twice") @@ -184,18 +180,18 @@ struct LazyStack { } impl LazyStack { - fn call<'a>(&mut self, f: NodeFactory<'a>) -> VNode<'a> { + fn call<'a>(&mut self, f: &'a ScopeState) -> VNode<'a> { let LazyStack { buf, .. } = self; let data = buf.as_ref(); let info_size = - mem::size_of::<*mut dyn FnMut(Option>) -> Option>>() + mem::size_of::<*mut dyn FnMut(Option<&'a ScopeState>) -> Option>>() / mem::size_of::() - 1; let info_ofs = data.len() - info_size; - let g: *mut dyn FnMut(Option>) -> Option> = + let g: *mut dyn FnMut(Option<&'a ScopeState>) -> Option> = unsafe { make_fat_ptr(data[..].as_ptr() as usize, &data[info_ofs..]) }; self.dropped = true; @@ -210,14 +206,14 @@ impl Drop for LazyStack { let LazyStack { buf, .. } = self; let data = buf.as_ref(); - let info_size = mem::size_of::< - *mut dyn FnMut(Option>) -> Option>, - >() / mem::size_of::() - - 1; + let info_size = + mem::size_of::<*mut dyn FnMut(Option<&ScopeState>) -> Option>>() + / mem::size_of::() + - 1; let info_ofs = data.len() - info_size; - let g: *mut dyn FnMut(Option>) -> Option> = + let g: *mut dyn FnMut(Option<&ScopeState>) -> Option> = unsafe { make_fat_ptr(data[..].as_ptr() as usize, &data[info_ofs..]) }; self.dropped = true; @@ -252,73 +248,3 @@ unsafe fn make_fat_ptr(data_ptr: usize, meta_vals: &[usize]) -> *mut fn round_to_words(len: usize) -> usize { (len + mem::size_of::() - 1) / mem::size_of::() } - -// #[cfg(test)] -// mod tests { -// use super::*; -// use crate::innerlude::{Element, Scope, VirtualDom}; - -// #[test] -// fn it_works() { -// fn app(cx: Scope<()>) -> Element { -// cx.render(LazyNodes::new(|f| f.text(format_args!("hello world!")))) -// } - -// let mut dom = VirtualDom::new(app); -// dom.rebuild(); - -// let g = dom.base_scope().root_node(); -// dbg!(g); -// } - -// #[test] -// fn it_drops() { -// use std::rc::Rc; - -// struct AppProps { -// inner: Rc, -// } - -// fn app(cx: Scope) -> Element { -// struct DropInner { -// id: i32, -// } -// impl Drop for DropInner { -// fn drop(&mut self) { -// eprintln!("dropping inner"); -// } -// } - -// let caller = { -// let it = (0..10).map(|i| { -// let val = cx.props.inner.clone(); -// LazyNodes::new(move |f| { -// eprintln!("hell closure"); -// let inner = DropInner { id: i }; -// f.text(format_args!("hello world {:?}, {:?}", inner.id, val)) -// }) -// }); - -// LazyNodes::new(|f| { -// eprintln!("main closure"); -// f.fragment_from_iter(it) -// }) -// }; - -// cx.render(caller) -// } - -// let inner = Rc::new(0); -// let mut dom = VirtualDom::new_with_props( -// app, -// AppProps { -// inner: inner.clone(), -// }, -// ); -// dom.rebuild(); - -// drop(dom); - -// assert_eq!(Rc::strong_count(&inner), 1); -// } -// } diff --git a/packages/core/src/lib.rs b/packages/core/src/lib.rs index 8f9aa8f33..c4d10493c 100644 --- a/packages/core/src/lib.rs +++ b/packages/core/src/lib.rs @@ -1,4 +1,5 @@ #![doc = include_str!("../README.md")] +#![warn(missing_docs)] mod any_props; mod arena; @@ -67,43 +68,13 @@ pub(crate) mod innerlude { /// ) /// ``` pub type Component

= fn(Scope

) -> Element; - - /// A list of attributes - pub type Attributes<'a> = Option<&'a [Attribute<'a>]>; } pub use crate::innerlude::{ - // AnyAttributeValue, AnyEvent, - fc_to_builder, - Attribute, - AttributeValue, - Attributes, - Component, - DynamicNode, - Element, - ElementId, - Fragment, - LazyNodes, - Mutation, - Mutations, - NodeFactory, - Properties, - RenderReturn, - Scope, - ScopeId, - ScopeState, - Scoped, - SuspenseBoundary, - SuspenseContext, - TaskId, - Template, - TemplateAttribute, - TemplateNode, - UiEvent, - VComponent, - VNode, - VText, - VirtualDom, + fc_to_builder, Attribute, AttributeValue, Component, DynamicNode, Element, ElementId, Event, + Fragment, LazyNodes, Mutation, Mutations, Properties, RenderReturn, Scope, ScopeId, ScopeState, + Scoped, SuspenseBoundary, SuspenseContext, TaskId, Template, TemplateAttribute, TemplateNode, + VComponent, VNode, VText, VirtualDom, }; /// The purpose of this module is to alleviate imports of many common types @@ -111,9 +82,9 @@ pub use crate::innerlude::{ /// This includes types like [`Scope`], [`Element`], and [`Component`]. pub mod prelude { pub use crate::innerlude::{ - fc_to_builder, Element, EventHandler, Fragment, LazyNodes, NodeFactory, Properties, Scope, - ScopeId, ScopeState, Scoped, TaskId, Template, TemplateAttribute, TemplateNode, UiEvent, - VNode, VirtualDom, + fc_to_builder, Element, Event, EventHandler, Fragment, LazyNodes, Properties, Scope, + ScopeId, ScopeState, Scoped, TaskId, Template, TemplateAttribute, TemplateNode, VNode, + VirtualDom, }; } @@ -121,28 +92,4 @@ pub mod exports { //! Important dependencies that are used by the rest of the library //! Feel free to just add the dependencies in your own Crates.toml pub use bumpalo; - pub use futures_channel; -} - -#[macro_export] -/// A helper macro for using hooks in async environements. -/// -/// # Usage -/// -/// -/// ```ignore -/// let (data) = use_ref(&cx, || {}); -/// -/// let handle_thing = move |_| { -/// to_owned![data] -/// cx.spawn(async move { -/// // do stuff -/// }); -/// } -/// ``` -macro_rules! to_owned { - ($($es:ident),+) => {$( - #[allow(unused_mut)] - let mut $es = $es.to_owned(); - )*} } diff --git a/packages/core/src/mutations.rs b/packages/core/src/mutations.rs index 750013703..208ec65ab 100644 --- a/packages/core/src/mutations.rs +++ b/packages/core/src/mutations.rs @@ -1,45 +1,50 @@ +use fxhash::FxHashSet; + use crate::{arena::ElementId, ScopeId}; +/// A container for all the relevant steps to modify the Real DOM +/// +/// This method returns "mutations" - IE the necessary changes to get the RealDOM to match the VirtualDOM. It also +/// includes a list of NodeRefs that need to be applied and effects that need to be triggered after the RealDOM has +/// applied the edits. +/// +/// Mutations are the only link between the RealDOM and the VirtualDOM. #[derive(Debug, Default)] #[must_use = "not handling edits can lead to visual inconsistencies in UI"] pub struct Mutations<'a> { + /// The ID of the subtree that these edits are targetting pub subtree: usize, - pub template_mutations: Vec>, - pub edits: Vec>, + + /// The list of Scopes that were diffed, created, and removed during the Diff process. + pub dirty_scopes: FxHashSet, + + /// Any mutations required to build the templates using [`Mutations`] + pub template_edits: Vec>, + + /// Any mutations required to patch the renderer to match the layout of the VirtualDom + pub dom_edits: Vec>, } impl<'a> Mutations<'a> { - /// A useful tool for testing mutations - /// /// Rewrites IDs to just be "template", so you can compare the mutations + /// + /// Used really only for testing pub fn santize(mut self) -> Self { - for edit in self - .template_mutations + self.template_edits .iter_mut() - .chain(self.edits.iter_mut()) - { - match edit { + .chain(self.dom_edits.iter_mut()) + .for_each(|edit| match edit { Mutation::LoadTemplate { name, .. } => *name = "template", Mutation::SaveTemplate { name, .. } => *name = "template", _ => {} - } - } + }); self } -} -impl<'a> std::ops::Deref for Mutations<'a> { - type Target = Vec>; - - fn deref(&self) -> &Self::Target { - &self.edits - } -} - -impl std::ops::DerefMut for Mutations<'_> { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.edits + /// Push a new mutation into the dom_edits list + pub(crate) fn push(&mut self, mutation: Mutation<'static>) { + self.dom_edits.push(mutation) } } @@ -54,7 +59,11 @@ each subtree has its own numbering scheme )] #[derive(Debug, PartialEq, Eq)] pub enum Mutation<'a> { + /// Pop the topmost node from our stack and append them to the node + /// at the top of the stack. AppendChildren { + /// How many nodes should be popped from the stack. + /// The node remaining on the stack will be the target for the append. m: usize, }, @@ -122,36 +131,61 @@ pub enum Mutation<'a> { m: usize, }, + /// Save the top m nodes as a placeholder SaveTemplate { + /// The name of the template that we're saving name: &'static str, + + /// How many nodes are being saved into this template m: usize, }, + /// Set the value of a node's attribute. SetAttribute { + /// The name of the attribute to set. name: &'a str, + /// The value of the attribute. value: &'a str, + + /// The ID of the node to set the attribute of. id: ElementId, - // value: &'bump str, /// The (optional) namespace of the attribute. /// For instance, "style" is in the "style" namespace. ns: Option<&'a str>, }, + /// Set the value of a node's attribute. SetStaticAttribute { + /// The name of the attribute to set. name: &'a str, + + /// The value of the attribute. value: &'a str, + + /// The (optional) namespace of the attribute. + /// For instance, "style" is in the "style" namespace. ns: Option<&'a str>, }, + /// Set the value of a node's attribute. SetBoolAttribute { + /// The name of the attribute to set. name: &'a str, + + /// The value of the attribute. value: bool, + + /// The ID of the node to set the attribute of. id: ElementId, }, + /// Set the textcontent of a node. SetText { + /// The textcontent of the node value: &'a str, + + /// The ID of the node to set the textcontent of. id: ElementId, }, @@ -175,11 +209,16 @@ pub enum Mutation<'a> { /// The ID of the node to remove. id: ElementId, }, + + /// Remove a particular node from the DOM Remove { + /// The ID of the node to remove. id: ElementId, }, + /// Push the given root node onto our stack. PushRoot { + /// The ID of the root node to push. id: ElementId, }, } diff --git a/packages/core/src/nodes.rs b/packages/core/src/nodes.rs index 19955be64..9c6eb6cd9 100644 --- a/packages/core/src/nodes.rs +++ b/packages/core/src/nodes.rs @@ -1,4 +1,4 @@ -use crate::{any_props::AnyProps, arena::ElementId, Element, ScopeId, ScopeState, UiEvent}; +use crate::{any_props::AnyProps, arena::ElementId, Element, Event, ScopeId, ScopeState}; use bumpalo::boxed::Box as BumpBox; use std::{ any::{Any, TypeId}, @@ -70,7 +70,6 @@ pub struct Template<'a> { pub attr_paths: &'a [&'a [u8]], } -/// A weird-ish variant of VNodes with way more limited types #[derive(Debug, Clone, Copy)] pub enum TemplateNode<'a> { Element { @@ -98,7 +97,7 @@ impl<'a> DynamicNode<'a> { matches!(self, DynamicNode::Component(_)) } pub fn placeholder() -> Self { - Self::Placeholder(Cell::new(ElementId(0))) + Self::Placeholder(Default::default()) } } @@ -156,18 +155,18 @@ pub enum AttributeValue<'a> { None, } -type ListenerCb<'a> = BumpBox<'a, dyn FnMut(UiEvent) + 'a>; +type ListenerCb<'a> = BumpBox<'a, dyn FnMut(Event) + 'a>; impl<'a> AttributeValue<'a> { pub fn new_listener( cx: &'a ScopeState, - mut callback: impl FnMut(UiEvent) + 'a, + mut callback: impl FnMut(Event) + 'a, ) -> AttributeValue<'a> { let boxed: BumpBox<'a, dyn FnMut(_) + 'a> = unsafe { - BumpBox::from_raw(cx.bump().alloc(move |event: UiEvent| { + BumpBox::from_raw(cx.bump().alloc(move |event: Event| { if let Ok(data) = event.data.downcast::() { - callback(UiEvent { - bubbles: event.bubbles, + callback(Event { + propogates: event.propogates, data, }) } diff --git a/packages/core/src/scheduler/suspense.rs b/packages/core/src/scheduler/suspense.rs index e73be438a..dde0acb63 100644 --- a/packages/core/src/scheduler/suspense.rs +++ b/packages/core/src/scheduler/suspense.rs @@ -15,25 +15,11 @@ pub type SuspenseContext = Rc; /// Essentially a fiber in React pub struct SuspenseBoundary { - pub id: ScopeId, - pub waiting_on: RefCell>, - pub mutations: RefCell>, - pub placeholder: Cell>, - - pub created_on_stack: Cell, - - // whenever the suspense resolves, we call this onresolve function - // this lets us do things like putting up a loading spinner - // - // todo: we need a way of controlling whether or not a component hides itself but still processes changes - // If we run into suspense, we perform a diff, so its important that the old elements are still around. - // - // When the timer expires, I imagine a container could hide the elements and show the spinner. This, however, - // can not be - pub onresolve: Option>, - - /// Called when - pub onstart: Option>, + pub(crate) id: ScopeId, + pub(crate) waiting_on: RefCell>, + pub(crate) mutations: RefCell>, + pub(crate) placeholder: Cell>, + pub(crate) created_on_stack: Cell, } impl SuspenseBoundary { @@ -44,8 +30,6 @@ impl SuspenseBoundary { mutations: RefCell::new(Mutations::default()), placeholder: Cell::new(None), created_on_stack: Cell::new(0), - onresolve: None, - onstart: None, } } } diff --git a/packages/core/src/scope_arena.rs b/packages/core/src/scope_arena.rs index 722621ec4..91fb0b8b2 100644 --- a/packages/core/src/scope_arena.rs +++ b/packages/core/src/scope_arena.rs @@ -49,7 +49,7 @@ impl VirtualDom { .and_then(|id| self.scopes.get_mut(id.0).map(|f| f.as_mut() as *mut _)) } - pub fn ensure_drop_safety(&self, scope: ScopeId) { + fn ensure_drop_safety(&self, scope: ScopeId) { let scope = &self.scopes[scope.0]; let node = unsafe { scope.previous_frame().try_load_node() }; diff --git a/packages/core/src/scopes.rs b/packages/core/src/scopes.rs index 776414563..f78fc7bf1 100644 --- a/packages/core/src/scopes.rs +++ b/packages/core/src/scopes.rs @@ -1,18 +1,21 @@ use crate::{ any_props::AnyProps, + any_props::VProps, arena::ElementId, bump_frame::BumpFrame, - factory::RenderReturn, + factory::{ComponentReturn, IntoAttributeValue, IntoDynNode, RenderReturn}, + innerlude::{DynamicNode, EventHandler, VComponent, VText}, innerlude::{Scheduler, SchedulerMsg}, lazynodes::LazyNodes, - Element, TaskId, + Attribute, Element, Properties, TaskId, }; -use bumpalo::Bump; -use std::future::Future; +use bumpalo::{boxed::Box as BumpBox, Bump}; use std::{ any::{Any, TypeId}, cell::{Cell, RefCell}, collections::{HashMap, HashSet}, + fmt::Arguments, + future::Future, rc::Rc, sync::Arc, }; @@ -59,9 +62,9 @@ impl<'a, T> std::ops::Deref for Scoped<'a, T> { #[derive(Copy, Clone, PartialEq, Eq, Hash, Debug, PartialOrd, Ord)] pub struct ScopeId(pub usize); -/// A component's state. +/// A component's state separate from its props. /// -/// This struct stores all the important information about a component's state without the props. +/// This struct exists to provide a common interface for all scopes without relying on generics. pub struct ScopeState { pub(crate) render_cnt: Cell, @@ -86,7 +89,7 @@ pub struct ScopeState { pub(crate) placeholder: Cell>, } -impl ScopeState { +impl<'src> ScopeState { pub(crate) fn current_frame(&self) -> &BumpFrame { match self.render_cnt.get() % 2 { 0 => &self.node_arena_1, @@ -359,10 +362,101 @@ impl ScopeState { /// cx.render(lazy_tree) /// } ///``` - pub fn render<'src>(&'src self, rsx: LazyNodes<'src, '_>) -> Element<'src> { + pub fn render(&'src self, rsx: LazyNodes<'src, '_>) -> Element<'src> { Ok(rsx.call(self)) } + /// Create a dynamic text node using [`Arguments`] and the [`ScopeState`]'s internal [`Bump`] allocator + pub fn text_node(&'src self, args: Arguments) -> DynamicNode<'src> { + DynamicNode::Text(VText { + value: self.raw_text(args), + id: Default::default(), + }) + } + + /// Allocate some text inside the [`ScopeState`] from [`Arguments`] + /// + /// Uses the currently active [`Bump`] allocator + pub fn raw_text(&'src self, args: Arguments) -> &'src str { + args.as_str().unwrap_or_else(|| { + use bumpalo::core_alloc::fmt::Write; + let mut str_buf = bumpalo::collections::String::new_in(self.bump()); + str_buf.write_fmt(args).unwrap(); + str_buf.into_bump_str() + }) + } + + /// Convert any item that implements [`IntoDynNode`] into a [`DynamicNode`] using the internal [`Bump`] allocator + pub fn make_node<'c, I>(&'src self, into: impl IntoDynNode<'src, I> + 'c) -> DynamicNode { + into.into_vnode(self) + } + + /// Create a new [`Attribute`] from a name, value, namespace, and volatile bool + /// + /// "Volatile" referes to whether or not Dioxus should always override the value. This helps prevent the UI in + /// some renderers stay in sync with the VirtualDom's understanding of the world + pub fn attr( + &'src self, + name: &'static str, + value: impl IntoAttributeValue<'src>, + namespace: Option<&'static str>, + volatile: bool, + ) -> Attribute<'src> { + Attribute { + name, + namespace, + volatile, + mounted_element: Default::default(), + value: value.into_value(self.bump()), + } + } + + /// Create a new [`DynamicNode::Component`] variant + /// + /// + /// The given component can be any of four signatures. Remember that an [`Element`] is really a [`Result`]. + /// + /// ```rust, ignore + /// // Without explicit props + /// fn(Scope) -> Element; + /// async fn(Scope<'_>) -> Element; + /// + /// // With explicit props + /// fn(Scope) -> Element; + /// async fn(Scope>) -> Element; + /// ``` + pub fn component>( + &'src self, + component: fn(Scope<'src, P>) -> F, + props: P, + fn_name: &'static str, + ) -> DynamicNode<'src> + where + P: Properties + 'src, + { + let vcomp = VProps::new(component, P::memoize, props); + + // cast off the lifetime of the render return + let as_dyn: Box + '_> = Box::new(vcomp); + let extended: Box + 'src> = unsafe { std::mem::transmute(as_dyn) }; + + DynamicNode::Component(VComponent { + name: fn_name, + render_fn: component as *const (), + static_props: P::IS_STATIC, + props: Cell::new(Some(extended)), + scope: Cell::new(None), + }) + } + + /// Create a new [`EventHandler`] from an [`FnMut`] + pub fn event_handler(&'src self, f: impl FnMut(T) + 'src) -> EventHandler<'src, T> { + let handler: &mut dyn FnMut(T) = self.bump().alloc(f); + let caller = unsafe { BumpBox::from_raw(handler as *mut dyn FnMut(T)) }; + let callback = RefCell::new(Some(caller)); + EventHandler { callback } + } + /// Store a value between renders. The foundational hook for all other hooks. /// /// Accepts an `initializer` closure, which is run on the first use of the hook (typically the initial render). The return value of this closure is stored for the lifetime of the component, and a mutable reference to it is provided on every render as the return value of `use_hook`. diff --git a/packages/core/src/virtual_dom.rs b/packages/core/src/virtual_dom.rs index 77619e674..41a54d391 100644 --- a/packages/core/src/virtual_dom.rs +++ b/packages/core/src/virtual_dom.rs @@ -11,7 +11,7 @@ use crate::{ nodes::{Template, TemplateId}, scheduler::{SuspenseBoundary, SuspenseId}, scopes::{ScopeId, ScopeState}, - AttributeValue, Element, Scope, SuspenseContext, UiEvent, + AttributeValue, Element, Event, Scope, SuspenseContext, }; use futures_util::{pin_mut, StreamExt}; use slab::Slab; @@ -350,8 +350,8 @@ impl VirtualDom { let mut listeners = vec![]; // We will clone this later. The data itself is wrapped in RC to be used in callbacks if required - let uievent = UiEvent { - bubbles: Rc::new(Cell::new(bubbles)), + let uievent = Event { + propogates: Rc::new(Cell::new(bubbles)), data, }; @@ -395,7 +395,7 @@ impl VirtualDom { cb(uievent.clone()); } - if !uievent.bubbles.get() { + if !uievent.propogates.get() { return; } } @@ -534,9 +534,12 @@ impl VirtualDom { let context = scope.has_context::().unwrap(); self.mutations - .extend(context.mutations.borrow_mut().template_mutations.drain(..)); + .template_edits + .extend(context.mutations.borrow_mut().template_edits.drain(..)); + self.mutations - .extend(context.mutations.borrow_mut().drain(..)); + .dom_edits + .extend(context.mutations.borrow_mut().dom_edits.drain(..)); // TODO: count how many nodes are on the stack? self.mutations.push(Mutation::ReplaceWith { @@ -556,7 +559,7 @@ impl VirtualDom { } // Save the current mutations length so we can split them into boundary - let mutations_to_this_point = self.mutations.len(); + let mutations_to_this_point = self.mutations.dom_edits.len(); // Run the scope and get the mutations self.run_scope(dirty.id); @@ -575,7 +578,8 @@ impl VirtualDom { boundary_mut .mutations .borrow_mut() - .extend(self.mutations.split_off(mutations_to_this_point)); + .dom_edits + .extend(self.mutations.dom_edits.split_off(mutations_to_this_point)); // Attach suspended leaves boundary diff --git a/packages/core/tests/README.md b/packages/core/tests/README.md index 63293c741..eea7cec3a 100644 --- a/packages/core/tests/README.md +++ b/packages/core/tests/README.md @@ -1,9 +1,6 @@ # Testing of Dioxus core -NodeFactory -- [] rsx, html, NodeFactory generate the same structures - Diffing - [x] create elements - [x] create text @@ -19,15 +16,13 @@ Diffing - [x] keyed diffing - [x] keyed diffing out of order - [x] keyed diffing with prefix/suffix -- [x] suspended nodes work +- [x] suspended nodes work Lifecycle - [] Components mount properly - [] Components create new child components - [] Replaced components unmount old components and mount new - [] Post-render effects are called -- [] - Shared Context - [] Shared context propagates downwards @@ -37,7 +32,7 @@ Suspense - [] use_suspense generates suspended nodes -Hooks +Hooks - [] Drop order is maintained - [] Shared hook state is okay - [] use_hook works diff --git a/packages/core/tests/attr_cleanup.rs b/packages/core/tests/attr_cleanup.rs index 99ce9a01b..d3e98769a 100644 --- a/packages/core/tests/attr_cleanup.rs +++ b/packages/core/tests/attr_cleanup.rs @@ -23,7 +23,7 @@ fn attrs_cycle() { }); assert_eq!( - dom.rebuild().santize().edits, + dom.rebuild().santize().dom_edits, [ LoadTemplate { name: "template", index: 0, id: ElementId(1,) }, AppendChildren { m: 1 }, @@ -32,7 +32,7 @@ fn attrs_cycle() { dom.mark_dirty(ScopeId(0)); assert_eq!( - dom.render_immediate().santize().edits, + dom.render_immediate().santize().dom_edits, [ LoadTemplate { name: "template", index: 0, id: ElementId(2,) }, AssignId { path: &[0,], id: ElementId(3,) }, @@ -44,7 +44,7 @@ fn attrs_cycle() { dom.mark_dirty(ScopeId(0)); assert_eq!( - dom.render_immediate().santize().edits, + dom.render_immediate().santize().dom_edits, [ LoadTemplate { name: "template", index: 0, id: ElementId(3) }, ReplaceWith { id: ElementId(2), m: 1 } @@ -53,7 +53,7 @@ fn attrs_cycle() { dom.mark_dirty(ScopeId(0)); assert_eq!( - dom.render_immediate().santize().edits, + dom.render_immediate().santize().dom_edits, [ LoadTemplate { name: "template", index: 0, id: ElementId(2) }, AssignId { path: &[0], id: ElementId(1) }, @@ -66,7 +66,7 @@ fn attrs_cycle() { // we take the node taken by attributes since we reused it dom.mark_dirty(ScopeId(0)); assert_eq!( - dom.render_immediate().santize().edits, + dom.render_immediate().santize().dom_edits, [ LoadTemplate { name: "template", index: 0, id: ElementId(1) }, ReplaceWith { id: ElementId(2), m: 1 } diff --git a/packages/core/tests/boolattrs.rs b/packages/core/tests/boolattrs.rs index 5187e00f9..7688e5a49 100644 --- a/packages/core/tests/boolattrs.rs +++ b/packages/core/tests/boolattrs.rs @@ -5,7 +5,7 @@ use dioxus::prelude::*; fn bool_test() { let mut app = VirtualDom::new(|cx| cx.render(rsx!(div { hidden: false }))); assert_eq!( - app.rebuild().santize().edits, + app.rebuild().santize().dom_edits, [ LoadTemplate { name: "template", index: 0, id: ElementId(1) }, SetBoolAttribute { name: "hidden", value: false, id: ElementId(1,) }, diff --git a/packages/core/tests/borrowedstate.rs b/packages/core/tests/borrowedstate.rs index 3da5c8d94..1f1214189 100644 --- a/packages/core/tests/borrowedstate.rs +++ b/packages/core/tests/borrowedstate.rs @@ -8,7 +8,7 @@ fn test_borrowed_state() { let mut dom = VirtualDom::new(Parent); assert_eq!( - dom.rebuild().santize().edits, + dom.rebuild().santize().dom_edits, [ LoadTemplate { name: "template", index: 0, id: ElementId(1,) }, LoadTemplate { name: "template", index: 0, id: ElementId(2,) }, diff --git a/packages/core/tests/context_api.rs b/packages/core/tests/context_api.rs index 2ec1ea18b..b16b2433d 100644 --- a/packages/core/tests/context_api.rs +++ b/packages/core/tests/context_api.rs @@ -20,7 +20,7 @@ fn state_shares() { let mut dom = VirtualDom::new(app); assert_eq!( - dom.rebuild().santize().edits, + dom.rebuild().santize().dom_edits, [ CreateTextNode { value: "Value is 0", id: ElementId(1,) }, AppendChildren { m: 1 }, @@ -37,7 +37,7 @@ fn state_shares() { dom.mark_dirty(ScopeId(2)); assert_eq!( - dom.render_immediate().santize().edits, + dom.render_immediate().santize().dom_edits, [SetText { value: "Value is 2", id: ElementId(1,) },] ); @@ -45,7 +45,7 @@ fn state_shares() { dom.mark_dirty(ScopeId(2)); let edits = dom.render_immediate(); assert_eq!( - edits.santize().edits, + edits.santize().dom_edits, [SetText { value: "Value is 3", id: ElementId(1,) },] ); } diff --git a/packages/core/tests/create_dom.rs b/packages/core/tests/create_dom.rs index 98e7706bd..8eda5783b 100644 --- a/packages/core/tests/create_dom.rs +++ b/packages/core/tests/create_dom.rs @@ -23,7 +23,7 @@ fn test_original_diff() { let edits = dom.rebuild().santize(); assert_eq!( - edits.template_mutations, + edits.template_edits, [ // create template CreateElement { name: "div" }, @@ -36,7 +36,7 @@ fn test_original_diff() { ); assert_eq!( - edits.edits, + edits.dom_edits, [ // add to root LoadTemplate { name: "template", index: 0, id: ElementId(1) }, @@ -67,7 +67,7 @@ fn create() { let edits = dom.rebuild().santize(); assert_eq!( - edits.template_mutations, + edits.template_edits, [ // create template CreateElement { name: "div" }, @@ -99,7 +99,7 @@ fn create_list() { let edits = dom.rebuild().santize(); assert_eq!( - edits.template_mutations, + edits.template_edits, [ // create template CreateElement { name: "div" }, @@ -123,7 +123,7 @@ fn create_simple() { let edits = dom.rebuild().santize(); assert_eq!( - edits.template_mutations, + edits.template_edits, [ // create template CreateElement { name: "div" }, @@ -160,7 +160,7 @@ fn create_components() { let edits = dom.rebuild().santize(); assert_eq!( - edits.template_mutations, + edits.template_edits, [ // The "child" template CreateElement { name: "h1" }, @@ -196,7 +196,7 @@ fn anchors() { // note that the template under "false" doesn't show up since it's not loaded let edits = dom.rebuild().santize(); assert_eq!( - edits.template_mutations, + edits.template_edits, [ // create each template CreateElement { name: "div" }, @@ -207,7 +207,7 @@ fn anchors() { ); assert_eq!( - edits.edits, + edits.dom_edits, [ LoadTemplate { name: "template", index: 0, id: ElementId(1) }, CreatePlaceholder { id: ElementId(2) }, diff --git a/packages/core/tests/create_element.rs b/packages/core/tests/create_element.rs index ff9d485b6..e6331f9ac 100644 --- a/packages/core/tests/create_element.rs +++ b/packages/core/tests/create_element.rs @@ -12,7 +12,7 @@ fn multiroot() { }); assert_eq!( - dom.rebuild().santize().template_mutations, + dom.rebuild().santize().template_edits, [ CreateElement { name: "div" }, CreateStaticText { value: "Hello a" }, diff --git a/packages/core/tests/create_fragments.rs b/packages/core/tests/create_fragments.rs index 5b67711a8..33bba1eaa 100644 --- a/packages/core/tests/create_fragments.rs +++ b/packages/core/tests/create_fragments.rs @@ -14,7 +14,7 @@ fn empty_fragment_creates_nothing() { let edits = vdom.rebuild(); assert_eq!( - edits.edits, + edits.dom_edits, [ CreatePlaceholder { id: ElementId(1) }, AppendChildren { m: 1 } @@ -32,7 +32,7 @@ fn root_fragments_work() { }); assert_eq!( - vdom.rebuild().edits.last().unwrap(), + vdom.rebuild().dom_edits.last().unwrap(), &AppendChildren { m: 2 } ); } @@ -59,7 +59,7 @@ fn fragments_nested() { }); assert_eq!( - vdom.rebuild().edits.last().unwrap(), + vdom.rebuild().dom_edits.last().unwrap(), &AppendChildren { m: 8 } ); } @@ -84,7 +84,7 @@ fn fragments_across_components() { } assert_eq!( - VirtualDom::new(app).rebuild().edits.last().unwrap(), + VirtualDom::new(app).rebuild().dom_edits.last().unwrap(), &AppendChildren { m: 8 } ); } @@ -98,7 +98,7 @@ fn list_fragments() { )) } assert_eq!( - VirtualDom::new(app).rebuild().edits.last().unwrap(), + VirtualDom::new(app).rebuild().dom_edits.last().unwrap(), &AppendChildren { m: 7 } ); } diff --git a/packages/core/tests/create_lists.rs b/packages/core/tests/create_lists.rs index 27540d9f0..e29044514 100644 --- a/packages/core/tests/create_lists.rs +++ b/packages/core/tests/create_lists.rs @@ -28,7 +28,7 @@ fn list_renders() { let edits = dom.rebuild().santize(); assert_eq!( - edits.template_mutations, + edits.template_edits, [ // Create the outer div CreateElement { name: "div" }, @@ -51,7 +51,7 @@ fn list_renders() { ); assert_eq!( - edits.edits, + edits.dom_edits, [ // Load the outer div LoadTemplate { name: "template", index: 0, id: ElementId(1) }, diff --git a/packages/core/tests/create_passthru.rs b/packages/core/tests/create_passthru.rs index b8916156b..2ffdf0065 100644 --- a/packages/core/tests/create_passthru.rs +++ b/packages/core/tests/create_passthru.rs @@ -26,7 +26,7 @@ fn nested_passthru_creates() { let edits = dom.rebuild().santize(); assert_eq!( - edits.edits, + edits.dom_edits, [ LoadTemplate { name: "template", index: 0, id: ElementId(1) }, AppendChildren { m: 1 }, @@ -64,7 +64,7 @@ fn nested_passthru_creates_add() { let mut dom = VirtualDom::new(app); assert_eq!( - dom.rebuild().santize().edits, + dom.rebuild().santize().dom_edits, [ // load 1 LoadTemplate { name: "template", index: 0, id: ElementId(1) }, @@ -92,11 +92,11 @@ fn dynamic_node_as_root() { let edits = dom.rebuild().santize(); // Since the roots were all dynamic, they should not cause any template muations - assert_eq!(edits.template_mutations, []); + assert_eq!(edits.template_edits, []); // The root node is text, so we just create it on the spot assert_eq!( - edits.edits, + edits.dom_edits, [ CreateTextNode { value: "123", id: ElementId(1) }, CreateTextNode { value: "456", id: ElementId(2) }, diff --git a/packages/core/tests/cycle.rs b/packages/core/tests/cycle.rs index 72e1a028d..87ede2271 100644 --- a/packages/core/tests/cycle.rs +++ b/packages/core/tests/cycle.rs @@ -14,7 +14,7 @@ fn cycling_elements() { let edits = dom.rebuild().santize(); assert_eq!( - edits.edits, + edits.dom_edits, [ LoadTemplate { name: "template", index: 0, id: ElementId(1,) }, AppendChildren { m: 1 }, @@ -23,7 +23,7 @@ fn cycling_elements() { dom.mark_dirty(ScopeId(0)); assert_eq!( - dom.render_immediate().santize().edits, + dom.render_immediate().santize().dom_edits, [ LoadTemplate { name: "template", index: 0, id: ElementId(2,) }, ReplaceWith { id: ElementId(1,), m: 1 }, @@ -33,7 +33,7 @@ fn cycling_elements() { // notice that the IDs cycle back to ElementId(1), preserving a minimal memory footprint dom.mark_dirty(ScopeId(0)); assert_eq!( - dom.render_immediate().santize().edits, + dom.render_immediate().santize().dom_edits, [ LoadTemplate { name: "template", index: 0, id: ElementId(1,) }, ReplaceWith { id: ElementId(2,), m: 1 }, @@ -42,7 +42,7 @@ fn cycling_elements() { dom.mark_dirty(ScopeId(0)); assert_eq!( - dom.render_immediate().santize().edits, + dom.render_immediate().santize().dom_edits, [ LoadTemplate { name: "template", index: 0, id: ElementId(2,) }, ReplaceWith { id: ElementId(1,), m: 1 }, diff --git a/packages/core/tests/diff_component.rs b/packages/core/tests/diff_component.rs index 6e72f04cd..0f6ec615c 100644 --- a/packages/core/tests/diff_component.rs +++ b/packages/core/tests/diff_component.rs @@ -61,7 +61,7 @@ fn component_swap() { let mut dom = VirtualDom::new(app); let edits = dom.rebuild().santize(); assert_eq!( - edits.edits, + edits.dom_edits, [ LoadTemplate { name: "template", index: 0, id: ElementId(1) }, LoadTemplate { name: "template", index: 0, id: ElementId(2) }, @@ -75,7 +75,7 @@ fn component_swap() { dom.mark_dirty(ScopeId(0)); assert_eq!( - dom.render_immediate().santize().edits, + dom.render_immediate().santize().dom_edits, [ LoadTemplate { name: "template", index: 0, id: ElementId(6) }, ReplaceWith { id: ElementId(5), m: 1 } @@ -84,7 +84,7 @@ fn component_swap() { dom.mark_dirty(ScopeId(0)); assert_eq!( - dom.render_immediate().santize().edits, + dom.render_immediate().santize().dom_edits, [ LoadTemplate { name: "template", index: 0, id: ElementId(5) }, ReplaceWith { id: ElementId(6), m: 1 } @@ -93,7 +93,7 @@ fn component_swap() { dom.mark_dirty(ScopeId(0)); assert_eq!( - dom.render_immediate().santize().edits, + dom.render_immediate().santize().dom_edits, [ LoadTemplate { name: "template", index: 0, id: ElementId(6) }, ReplaceWith { id: ElementId(5), m: 1 } diff --git a/packages/core/tests/diff_element.rs b/packages/core/tests/diff_element.rs index 3ab9b284a..4ca97dcdb 100644 --- a/packages/core/tests/diff_element.rs +++ b/packages/core/tests/diff_element.rs @@ -14,19 +14,19 @@ fn text_diff() { vdom.mark_dirty(ScopeId(0)); assert_eq!( - vdom.render_immediate().edits, + vdom.render_immediate().dom_edits, [SetText { value: "hello 1", id: ElementId(2) }] ); vdom.mark_dirty(ScopeId(0)); assert_eq!( - vdom.render_immediate().edits, + vdom.render_immediate().dom_edits, [SetText { value: "hello 2", id: ElementId(2) }] ); vdom.mark_dirty(ScopeId(0)); assert_eq!( - vdom.render_immediate().edits, + vdom.render_immediate().dom_edits, [SetText { value: "hello 3", id: ElementId(2) }] ); } @@ -48,7 +48,7 @@ fn element_swap() { vdom.mark_dirty(ScopeId(0)); assert_eq!( - vdom.render_immediate().santize().edits, + vdom.render_immediate().santize().dom_edits, [ LoadTemplate { name: "template", index: 0, id: ElementId(2,) }, ReplaceWith { id: ElementId(1,), m: 1 }, @@ -57,7 +57,7 @@ fn element_swap() { vdom.mark_dirty(ScopeId(0)); assert_eq!( - vdom.render_immediate().santize().edits, + vdom.render_immediate().santize().dom_edits, [ LoadTemplate { name: "template", index: 0, id: ElementId(1,) }, ReplaceWith { id: ElementId(2,), m: 1 }, @@ -66,7 +66,7 @@ fn element_swap() { vdom.mark_dirty(ScopeId(0)); assert_eq!( - vdom.render_immediate().santize().edits, + vdom.render_immediate().santize().dom_edits, [ LoadTemplate { name: "template", index: 0, id: ElementId(2,) }, ReplaceWith { id: ElementId(1,), m: 1 }, @@ -75,7 +75,7 @@ fn element_swap() { vdom.mark_dirty(ScopeId(0)); assert_eq!( - vdom.render_immediate().santize().edits, + vdom.render_immediate().santize().dom_edits, [ LoadTemplate { name: "template", index: 0, id: ElementId(1,) }, ReplaceWith { id: ElementId(2,), m: 1 }, diff --git a/packages/core/tests/diff_keyed_list.rs b/packages/core/tests/diff_keyed_list.rs index 6acbaebbb..a2c00e439 100644 --- a/packages/core/tests/diff_keyed_list.rs +++ b/packages/core/tests/diff_keyed_list.rs @@ -21,7 +21,7 @@ fn keyed_diffing_out_of_order() { }); assert_eq!( - dom.rebuild().santize().edits, + dom.rebuild().santize().dom_edits, [ LoadTemplate { name: "template", index: 0, id: ElementId(1,) }, LoadTemplate { name: "template", index: 0, id: ElementId(2,) }, @@ -39,7 +39,7 @@ fn keyed_diffing_out_of_order() { dom.mark_dirty(ScopeId(0)); assert_eq!( - dom.render_immediate().edits, + dom.render_immediate().dom_edits, [ PushRoot { id: ElementId(7,) }, InsertBefore { id: ElementId(5,), m: 1 }, @@ -64,7 +64,7 @@ fn keyed_diffing_out_of_order_adds() { dom.mark_dirty(ScopeId(0)); assert_eq!( - dom.render_immediate().edits, + dom.render_immediate().dom_edits, [ PushRoot { id: ElementId(5,) }, PushRoot { id: ElementId(4,) }, @@ -90,7 +90,7 @@ fn keyed_diffing_out_of_order_adds_3() { dom.mark_dirty(ScopeId(0)); assert_eq!( - dom.render_immediate().edits, + dom.render_immediate().dom_edits, [ PushRoot { id: ElementId(5,) }, PushRoot { id: ElementId(4,) }, @@ -116,7 +116,7 @@ fn keyed_diffing_out_of_order_adds_4() { dom.mark_dirty(ScopeId(0)); assert_eq!( - dom.render_immediate().edits, + dom.render_immediate().dom_edits, [ PushRoot { id: ElementId(5,) }, PushRoot { id: ElementId(4,) }, @@ -142,7 +142,7 @@ fn keyed_diffing_out_of_order_adds_5() { dom.mark_dirty(ScopeId(0)); assert_eq!( - dom.render_immediate().edits, + dom.render_immediate().dom_edits, [ PushRoot { id: ElementId(5,) }, InsertBefore { id: ElementId(4,), m: 1 }, @@ -167,7 +167,7 @@ fn keyed_diffing_additions() { dom.mark_dirty(ScopeId(0)); assert_eq!( - dom.render_immediate().santize().edits, + dom.render_immediate().santize().dom_edits, [ LoadTemplate { name: "template", index: 0, id: ElementId(6) }, LoadTemplate { name: "template", index: 0, id: ElementId(7) }, @@ -192,7 +192,7 @@ fn keyed_diffing_additions_and_moves_on_ends() { dom.mark_dirty(ScopeId(0)); assert_eq!( - dom.render_immediate().santize().edits, + dom.render_immediate().santize().dom_edits, [ // create 11, 12 LoadTemplate { name: "template", index: 0, id: ElementId(5) }, @@ -222,7 +222,7 @@ fn keyed_diffing_additions_and_moves_in_middle() { // LIS: 4, 5, 6 dom.mark_dirty(ScopeId(0)); assert_eq!( - dom.render_immediate().santize().edits, + dom.render_immediate().santize().dom_edits, [ // create 5, 6 LoadTemplate { name: "template", index: 0, id: ElementId(5) }, @@ -256,7 +256,7 @@ fn controlled_keyed_diffing_out_of_order() { // LIS: 5, 6 dom.mark_dirty(ScopeId(0)); assert_eq!( - dom.render_immediate().santize().edits, + dom.render_immediate().santize().dom_edits, [ // remove 7 Remove { id: ElementId(4,) }, @@ -289,7 +289,7 @@ fn controlled_keyed_diffing_out_of_order_max_test() { dom.mark_dirty(ScopeId(0)); assert_eq!( - dom.render_immediate().santize().edits, + dom.render_immediate().santize().dom_edits, [ Remove { id: ElementId(5,) }, LoadTemplate { name: "template", index: 0, id: ElementId(5) }, @@ -318,7 +318,7 @@ fn remove_list() { dom.mark_dirty(ScopeId(0)); assert_eq!( - dom.render_immediate().santize().edits, + dom.render_immediate().santize().dom_edits, [ Remove { id: ElementId(3) }, Remove { id: ElementId(4) }, @@ -343,7 +343,7 @@ fn no_common_keys() { dom.mark_dirty(ScopeId(0)); assert_eq!( - dom.render_immediate().santize().edits, + dom.render_immediate().santize().dom_edits, [ Remove { id: ElementId(2) }, Remove { id: ElementId(3) }, diff --git a/packages/core/tests/diff_unkeyed_list.rs b/packages/core/tests/diff_unkeyed_list.rs index 5252d1ece..75c12aec0 100644 --- a/packages/core/tests/diff_unkeyed_list.rs +++ b/packages/core/tests/diff_unkeyed_list.rs @@ -17,7 +17,7 @@ fn list_creates_one_by_one() { // load the div and then assign the empty fragment as a placeholder assert_eq!( - dom.rebuild().santize().edits, + dom.rebuild().santize().dom_edits, [ LoadTemplate { name: "template", index: 0, id: ElementId(1,) }, AssignId { path: &[0], id: ElementId(2,) }, @@ -28,7 +28,7 @@ fn list_creates_one_by_one() { // Rendering the first item should replace the placeholder with an element dom.mark_dirty(ScopeId(0)); assert_eq!( - dom.render_immediate().santize().edits, + dom.render_immediate().santize().dom_edits, [ LoadTemplate { name: "template", index: 0, id: ElementId(3,) }, HydrateText { path: &[0], value: "0", id: ElementId(4,) }, @@ -39,7 +39,7 @@ fn list_creates_one_by_one() { // Rendering the next item should insert after the previous dom.mark_dirty(ScopeId(0)); assert_eq!( - dom.render_immediate().santize().edits, + dom.render_immediate().santize().dom_edits, [ LoadTemplate { name: "template", index: 0, id: ElementId(2,) }, HydrateText { path: &[0], value: "1", id: ElementId(5,) }, @@ -50,7 +50,7 @@ fn list_creates_one_by_one() { // ... and again! dom.mark_dirty(ScopeId(0)); assert_eq!( - dom.render_immediate().santize().edits, + dom.render_immediate().santize().dom_edits, [ LoadTemplate { name: "template", index: 0, id: ElementId(6,) }, HydrateText { path: &[0], value: "2", id: ElementId(7,) }, @@ -61,7 +61,7 @@ fn list_creates_one_by_one() { // once more dom.mark_dirty(ScopeId(0)); assert_eq!( - dom.render_immediate().santize().edits, + dom.render_immediate().santize().dom_edits, [ LoadTemplate { name: "template", index: 0, id: ElementId(8,) }, HydrateText { path: &[0], value: "3", id: ElementId(9,) }, @@ -86,7 +86,7 @@ fn removes_one_by_one() { // load the div and then assign the empty fragment as a placeholder assert_eq!( - dom.rebuild().santize().edits, + dom.rebuild().santize().dom_edits, [ // The container LoadTemplate { name: "template", index: 0, id: ElementId(1) }, @@ -108,14 +108,14 @@ fn removes_one_by_one() { // Rendering the first item should replace the placeholder with an element dom.mark_dirty(ScopeId(0)); assert_eq!( - dom.render_immediate().santize().edits, + dom.render_immediate().santize().dom_edits, [Remove { id: ElementId(6) }] ); // Remove div(2) dom.mark_dirty(ScopeId(0)); assert_eq!( - dom.render_immediate().santize().edits, + dom.render_immediate().santize().dom_edits, [Remove { id: ElementId(4) }] ); @@ -123,7 +123,7 @@ fn removes_one_by_one() { // todo: this should just be a remove with no placeholder dom.mark_dirty(ScopeId(0)); assert_eq!( - dom.render_immediate().santize().edits, + dom.render_immediate().santize().dom_edits, [ CreatePlaceholder { id: ElementId(3) }, ReplaceWith { id: ElementId(2), m: 1 } @@ -134,7 +134,7 @@ fn removes_one_by_one() { // todo: this should actually be append to, but replace placeholder is fine for now dom.mark_dirty(ScopeId(0)); assert_eq!( - dom.render_immediate().santize().edits, + dom.render_immediate().santize().dom_edits, [ LoadTemplate { name: "template", index: 0, id: ElementId(2) }, HydrateText { path: &[0], value: "0", id: ElementId(4) }, @@ -161,7 +161,7 @@ fn list_shrink_multiroot() { }); assert_eq!( - dom.rebuild().santize().edits, + dom.rebuild().santize().dom_edits, [ LoadTemplate { name: "template", index: 0, id: ElementId(1,) }, AssignId { path: &[0,], id: ElementId(2,) }, @@ -171,7 +171,7 @@ fn list_shrink_multiroot() { dom.mark_dirty(ScopeId(0)); assert_eq!( - dom.render_immediate().santize().edits, + dom.render_immediate().santize().dom_edits, [ LoadTemplate { name: "template", index: 0, id: ElementId(3) }, HydrateText { path: &[0], value: "0", id: ElementId(4) }, @@ -183,7 +183,7 @@ fn list_shrink_multiroot() { dom.mark_dirty(ScopeId(0)); assert_eq!( - dom.render_immediate().santize().edits, + dom.render_immediate().santize().dom_edits, [ LoadTemplate { name: "template", index: 0, id: ElementId(2) }, HydrateText { path: &[0], value: "1", id: ElementId(7) }, @@ -195,7 +195,7 @@ fn list_shrink_multiroot() { dom.mark_dirty(ScopeId(0)); assert_eq!( - dom.render_immediate().santize().edits, + dom.render_immediate().santize().dom_edits, [ LoadTemplate { name: "template", index: 0, id: ElementId(10) }, HydrateText { path: &[0], value: "2", id: ElementId(11) }, @@ -223,7 +223,7 @@ fn removes_one_by_one_multiroot() { // load the div and then assign the empty fragment as a placeholder assert_eq!( - dom.rebuild().santize().edits, + dom.rebuild().santize().dom_edits, [ LoadTemplate { name: "template", index: 0, id: ElementId(1) }, // @@ -250,19 +250,19 @@ fn removes_one_by_one_multiroot() { dom.mark_dirty(ScopeId(0)); assert_eq!( - dom.render_immediate().santize().edits, + dom.render_immediate().santize().dom_edits, [Remove { id: ElementId(10) }, Remove { id: ElementId(12) }] ); dom.mark_dirty(ScopeId(0)); assert_eq!( - dom.render_immediate().santize().edits, + dom.render_immediate().santize().dom_edits, [Remove { id: ElementId(6) }, Remove { id: ElementId(8) }] ); dom.mark_dirty(ScopeId(0)); assert_eq!( - dom.render_immediate().santize().edits, + dom.render_immediate().santize().dom_edits, [ Remove { id: ElementId(4) }, CreatePlaceholder { id: ElementId(5) }, @@ -282,7 +282,7 @@ fn two_equal_fragments_are_equal_static() { }); _ = dom.rebuild(); - assert!(dom.render_immediate().edits.is_empty()); + assert!(dom.render_immediate().dom_edits.is_empty()); } #[test] @@ -296,7 +296,7 @@ fn two_equal_fragments_are_equal() { }); _ = dom.rebuild(); - assert!(dom.render_immediate().edits.is_empty()); + assert!(dom.render_immediate().dom_edits.is_empty()); } #[test] @@ -315,9 +315,9 @@ fn remove_many() { }); let edits = dom.rebuild().santize(); - assert!(edits.template_mutations.is_empty()); + assert!(edits.template_edits.is_empty()); assert_eq!( - edits.edits, + edits.dom_edits, [ CreatePlaceholder { id: ElementId(1,) }, AppendChildren { m: 1 }, @@ -327,7 +327,7 @@ fn remove_many() { dom.mark_dirty(ScopeId(0)); let edits = dom.render_immediate().santize(); assert_eq!( - edits.edits, + edits.dom_edits, [ LoadTemplate { name: "template", index: 0, id: ElementId(2,) }, HydrateText { path: &[0,], value: "hello 0", id: ElementId(3,) }, @@ -338,7 +338,7 @@ fn remove_many() { dom.mark_dirty(ScopeId(0)); let edits = dom.render_immediate().santize(); assert_eq!( - edits.edits, + edits.dom_edits, [ LoadTemplate { name: "template", index: 0, id: ElementId(1,) }, HydrateText { path: &[0,], value: "hello 1", id: ElementId(4,) }, @@ -355,7 +355,7 @@ fn remove_many() { dom.mark_dirty(ScopeId(0)); let edits = dom.render_immediate().santize(); assert_eq!( - edits.edits, + edits.dom_edits, [ Remove { id: ElementId(1,) }, Remove { id: ElementId(5,) }, @@ -369,7 +369,7 @@ fn remove_many() { dom.mark_dirty(ScopeId(0)); let edits = dom.render_immediate().santize(); assert_eq!( - edits.edits, + edits.dom_edits, [ LoadTemplate { name: "template", index: 0, id: ElementId(2,) }, HydrateText { path: &[0,], value: "hello 0", id: ElementId(10,) }, diff --git a/packages/core/tests/kitchen_sink.rs b/packages/core/tests/kitchen_sink.rs index 7f719f772..737980938 100644 --- a/packages/core/tests/kitchen_sink.rs +++ b/packages/core/tests/kitchen_sink.rs @@ -30,7 +30,7 @@ fn dual_stream() { use Mutation::*; assert_eq!( - edits.template_mutations, + edits.template_edits, [ CreateElement { name: "div" }, SetStaticAttribute { name: "class", value: "asd", ns: None }, @@ -66,7 +66,7 @@ fn dual_stream() { ); assert_eq!( - edits.edits, + edits.dom_edits, [ LoadTemplate { name: "template", index: 0, id: ElementId(1) }, SetAttribute { name: "class", value: "123", id: ElementId(1), ns: None }, diff --git a/packages/core/tests/lifecycle.rs b/packages/core/tests/lifecycle.rs index ab7be8793..bdceb2332 100644 --- a/packages/core/tests/lifecycle.rs +++ b/packages/core/tests/lifecycle.rs @@ -28,7 +28,7 @@ fn manual_diffing() { *value.lock().unwrap() = "goodbye"; assert_eq!( - dom.rebuild().santize().edits, + dom.rebuild().santize().dom_edits, [ LoadTemplate { name: "template", index: 0, id: ElementId(3) }, HydrateText { path: &[0], value: "goodbye", id: ElementId(4) }, @@ -62,7 +62,7 @@ fn events_generate() { let edits = dom.render_immediate(); assert_eq!( - edits.edits, + edits.dom_edits, [ CreatePlaceholder { id: ElementId(2) }, ReplaceWith { id: ElementId(1), m: 1 } diff --git a/packages/core/tests/suspense.rs b/packages/core/tests/suspense.rs index 481ade2e3..4e70ec807 100644 --- a/packages/core/tests/suspense.rs +++ b/packages/core/tests/suspense.rs @@ -13,7 +13,7 @@ async fn it_works() { // We should at least get the top-level template in before pausing for the children assert_eq!( - mutations.template_mutations, + mutations.template_edits, [ CreateElement { name: "div" }, CreateStaticText { value: "Waiting for child..." }, @@ -25,7 +25,7 @@ async fn it_works() { // And we should load it in and assign the placeholder properly assert_eq!( - mutations.edits, + mutations.dom_edits, [ LoadTemplate { name: "template", index: 0, id: ElementId(1) }, // hmmmmmmmmm.... with suspense how do we guarantee that IDs increase linearly? diff --git a/packages/desktop/src/controller.rs b/packages/desktop/src/controller.rs index 19ac205b4..4eca36db1 100644 --- a/packages/desktop/src/controller.rs +++ b/packages/desktop/src/controller.rs @@ -54,8 +54,8 @@ impl DesktopController { { let edits = dom.rebuild(); let mut queue = edit_queue.lock().unwrap(); - queue.push(serde_json::to_string(&edits.template_mutations).unwrap()); - queue.push(serde_json::to_string(&edits.edits).unwrap()); + queue.push(serde_json::to_string(&edits.template_edits).unwrap()); + queue.push(serde_json::to_string(&edits.dom_edits).unwrap()); proxy.send_event(UserWindowEvent::EditsReady).unwrap(); } @@ -79,8 +79,8 @@ impl DesktopController { { let mut queue = edit_queue.lock().unwrap(); - queue.push(serde_json::to_string(&muts.template_mutations).unwrap()); - queue.push(serde_json::to_string(&muts.edits).unwrap()); + queue.push(serde_json::to_string(&muts.template_edits).unwrap()); + queue.push(serde_json::to_string(&muts.dom_edits).unwrap()); let _ = proxy.send_event(UserWindowEvent::EditsReady); } } diff --git a/packages/dioxus/benches/jsframework.rs b/packages/dioxus/benches/jsframework.rs index 24ca2fe69..281d36904 100644 --- a/packages/dioxus/benches/jsframework.rs +++ b/packages/dioxus/benches/jsframework.rs @@ -46,7 +46,7 @@ fn create_rows(c: &mut Criterion) { b.iter(|| { let g = dom.rebuild(); - assert!(g.edits.len() > 1); + assert!(g.dom_edits.len() > 1); }) }); } diff --git a/packages/hooks/src/lib.rs b/packages/hooks/src/lib.rs index 1332975ea..4eadea1fd 100644 --- a/packages/hooks/src/lib.rs +++ b/packages/hooks/src/lib.rs @@ -1,5 +1,25 @@ -// #![deny(missing_docs)] -//! Useful foundational hooks for Dioxus +#[macro_export] +/// A helper macro for using hooks in async environements. +/// +/// # Usage +/// +/// +/// ```ignore +/// let (data) = use_ref(&cx, || {}); +/// +/// let handle_thing = move |_| { +/// to_owned![data] +/// cx.spawn(async move { +/// // do stuff +/// }); +/// } +/// ``` +macro_rules! to_owned { + ($($es:ident),+) => {$( + #[allow(unused_mut)] + let mut $es = $es.to_owned(); + )*} +} mod usestate; pub use usestate::{use_state, UseState}; diff --git a/packages/hooks/src/usestate.rs b/packages/hooks/src/usestate.rs index 15ba5fd3d..a2d0242d8 100644 --- a/packages/hooks/src/usestate.rs +++ b/packages/hooks/src/usestate.rs @@ -39,7 +39,7 @@ pub fn use_state( let update_callback = cx.schedule_update(); let slot = Rc::new(RefCell::new(current_val.clone())); let setter = Rc::new({ - dioxus_core::to_owned![update_callback, slot]; + to_owned![update_callback, slot]; move |new| { { let mut slot = slot.borrow_mut(); diff --git a/packages/html/src/events.rs b/packages/html/src/events.rs index 5107e2cda..c5b8af9e1 100644 --- a/packages/html/src/events.rs +++ b/packages/html/src/events.rs @@ -11,7 +11,7 @@ macro_rules! impl_event { ) => { $( $( #[$attr] )* - pub fn $name<'a>(_cx: &'a ::dioxus_core::ScopeState, _f: impl FnMut(::dioxus_core::UiEvent<$data>) + 'a) -> ::dioxus_core::Attribute<'a> { + pub fn $name<'a>(_cx: &'a ::dioxus_core::ScopeState, _f: impl FnMut(::dioxus_core::Event<$data>) + 'a) -> ::dioxus_core::Attribute<'a> { ::dioxus_core::Attribute { name: stringify!($name), value: ::dioxus_core::AttributeValue::new_listener(_cx, _f), diff --git a/packages/html/src/events/animation.rs b/packages/html/src/events/animation.rs index 2e576e578..fb6dc64ca 100644 --- a/packages/html/src/events/animation.rs +++ b/packages/html/src/events/animation.rs @@ -1,6 +1,6 @@ -use dioxus_core::UiEvent; +use dioxus_core::Event; -pub type AnimationEvent = UiEvent; +pub type AnimationEvent = Event; #[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))] #[derive(Debug, Clone)] diff --git a/packages/html/src/events/clipboard.rs b/packages/html/src/events/clipboard.rs index 9b8365401..e7031d061 100644 --- a/packages/html/src/events/clipboard.rs +++ b/packages/html/src/events/clipboard.rs @@ -1,6 +1,6 @@ -use dioxus_core::UiEvent; +use dioxus_core::Event; -pub type ClipboardEvent = UiEvent; +pub type ClipboardEvent = Event; #[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))] #[derive(Debug, Clone)] pub struct ClipboardData { diff --git a/packages/html/src/events/composition.rs b/packages/html/src/events/composition.rs index d29171787..59009c1f0 100644 --- a/packages/html/src/events/composition.rs +++ b/packages/html/src/events/composition.rs @@ -1,6 +1,6 @@ -use dioxus_core::UiEvent; +use dioxus_core::Event; -pub type CompositionEvent = UiEvent; +pub type CompositionEvent = Event; #[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))] #[derive(Debug, Clone)] pub struct CompositionData { diff --git a/packages/html/src/events/drag.rs b/packages/html/src/events/drag.rs index f0fc8774a..5dd7f5c71 100644 --- a/packages/html/src/events/drag.rs +++ b/packages/html/src/events/drag.rs @@ -1,10 +1,10 @@ use std::any::Any; -use dioxus_core::UiEvent; +use dioxus_core::Event; use crate::MouseData; -pub type DragEvent = UiEvent; +pub type DragEvent = Event; /// The DragEvent interface is a DOM event that represents a drag and drop interaction. The user initiates a drag by /// placing a pointer device (such as a mouse) on the touch surface and then dragging the pointer to a new location diff --git a/packages/html/src/events/focus.rs b/packages/html/src/events/focus.rs index d402684fd..b2a4f5205 100644 --- a/packages/html/src/events/focus.rs +++ b/packages/html/src/events/focus.rs @@ -1,6 +1,6 @@ -use dioxus_core::UiEvent; +use dioxus_core::Event; -pub type FocusEvent = UiEvent; +pub type FocusEvent = Event; #[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))] #[derive(Debug, Clone)] diff --git a/packages/html/src/events/form.rs b/packages/html/src/events/form.rs index b003e24c7..5e0b6a17e 100644 --- a/packages/html/src/events/form.rs +++ b/packages/html/src/events/form.rs @@ -1,8 +1,8 @@ use std::{collections::HashMap, fmt::Debug, sync::Arc}; -use dioxus_core::UiEvent; +use dioxus_core::Event; -pub type FormEvent = UiEvent; +pub type FormEvent = Event; /* DOMEvent: Send + SyncTarget relatedTarget */ #[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))] diff --git a/packages/html/src/events/image.rs b/packages/html/src/events/image.rs index deb6ae48c..06430c4c2 100644 --- a/packages/html/src/events/image.rs +++ b/packages/html/src/events/image.rs @@ -1,6 +1,6 @@ -use dioxus_core::UiEvent; +use dioxus_core::Event; -pub type ImageEvent = UiEvent; +pub type ImageEvent = Event; #[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))] #[derive(Debug, Clone)] pub struct ImageData { diff --git a/packages/html/src/events/keyboard.rs b/packages/html/src/events/keyboard.rs index 040b2658e..ddf720770 100644 --- a/packages/html/src/events/keyboard.rs +++ b/packages/html/src/events/keyboard.rs @@ -1,11 +1,11 @@ use crate::input_data::{decode_key_location, encode_key_location}; -use dioxus_core::UiEvent; +use dioxus_core::Event; use keyboard_types::{Code, Key, Location, Modifiers}; use std::convert::TryInto; use std::fmt::{Debug, Formatter}; use std::str::FromStr; -pub type KeyboardEvent = UiEvent; +pub type KeyboardEvent = Event; #[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))] #[derive(Clone)] pub struct KeyboardData { diff --git a/packages/html/src/events/media.rs b/packages/html/src/events/media.rs index a3433131f..0b2fe70e0 100644 --- a/packages/html/src/events/media.rs +++ b/packages/html/src/events/media.rs @@ -1,6 +1,6 @@ -use dioxus_core::UiEvent; +use dioxus_core::Event; -pub type MediaEvent = UiEvent; +pub type MediaEvent = Event; #[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))] #[derive(Debug, Clone)] pub struct MediaData {} diff --git a/packages/html/src/events/mouse.rs b/packages/html/src/events/mouse.rs index 9738fb80f..a0fda7be6 100644 --- a/packages/html/src/events/mouse.rs +++ b/packages/html/src/events/mouse.rs @@ -2,11 +2,11 @@ use crate::geometry::{ClientPoint, Coordinates, ElementPoint, PagePoint, ScreenP use crate::input_data::{ decode_mouse_button_set, encode_mouse_button_set, MouseButton, MouseButtonSet, }; -use dioxus_core::UiEvent; +use dioxus_core::Event; use keyboard_types::Modifiers; use std::fmt::{Debug, Formatter}; -pub type MouseEvent = UiEvent; +pub type MouseEvent = Event; /// A synthetic event that wraps a web-style [`MouseEvent`](https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent) #[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))] diff --git a/packages/html/src/events/pointer.rs b/packages/html/src/events/pointer.rs index a87dbe9a5..23b0aba71 100644 --- a/packages/html/src/events/pointer.rs +++ b/packages/html/src/events/pointer.rs @@ -1,6 +1,6 @@ -use dioxus_core::UiEvent; +use dioxus_core::Event; -pub type PointerEvent = UiEvent; +pub type PointerEvent = Event; #[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))] #[derive(Debug, Clone)] pub struct PointerData { diff --git a/packages/html/src/events/scroll.rs b/packages/html/src/events/scroll.rs index e4b62e50a..0c2dd3143 100644 --- a/packages/html/src/events/scroll.rs +++ b/packages/html/src/events/scroll.rs @@ -1,6 +1,6 @@ -use dioxus_core::UiEvent; +use dioxus_core::Event; -pub type ScrollEvent = UiEvent; +pub type ScrollEvent = Event; #[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))] #[derive(Debug, Clone)] pub struct ScrollData {} diff --git a/packages/html/src/events/selection.rs b/packages/html/src/events/selection.rs index e28eefcf9..ff1496c96 100644 --- a/packages/html/src/events/selection.rs +++ b/packages/html/src/events/selection.rs @@ -1,6 +1,6 @@ -use dioxus_core::UiEvent; +use dioxus_core::Event; -pub type SelectionEvent = UiEvent; +pub type SelectionEvent = Event; #[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))] #[derive(Debug, Clone)] pub struct SelectionData {} diff --git a/packages/html/src/events/toggle.rs b/packages/html/src/events/toggle.rs index 448df05d1..1f0d3f6e7 100644 --- a/packages/html/src/events/toggle.rs +++ b/packages/html/src/events/toggle.rs @@ -1,6 +1,6 @@ -use dioxus_core::UiEvent; +use dioxus_core::Event; -pub type ToggleEvent = UiEvent; +pub type ToggleEvent = Event; #[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))] #[derive(Debug, Clone)] pub struct ToggleData {} diff --git a/packages/html/src/events/touch.rs b/packages/html/src/events/touch.rs index 30855814a..1f219b22f 100644 --- a/packages/html/src/events/touch.rs +++ b/packages/html/src/events/touch.rs @@ -1,6 +1,6 @@ -use dioxus_core::UiEvent; +use dioxus_core::Event; -pub type TouchEvent = UiEvent; +pub type TouchEvent = Event; #[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))] #[derive(Debug, Clone)] pub struct TouchData { diff --git a/packages/html/src/events/transition.rs b/packages/html/src/events/transition.rs index fe574c1ab..095496664 100644 --- a/packages/html/src/events/transition.rs +++ b/packages/html/src/events/transition.rs @@ -1,6 +1,6 @@ -use dioxus_core::UiEvent; +use dioxus_core::Event; -pub type TransitionEvent = UiEvent; +pub type TransitionEvent = Event; #[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))] #[derive(Debug, Clone)] pub struct TransitionData { diff --git a/packages/html/src/events/wheel.rs b/packages/html/src/events/wheel.rs index 344d9179c..e6e06501b 100644 --- a/packages/html/src/events/wheel.rs +++ b/packages/html/src/events/wheel.rs @@ -1,10 +1,10 @@ -use dioxus_core::UiEvent; +use dioxus_core::Event; use euclid::UnknownUnit; use std::fmt::{Debug, Formatter}; use crate::geometry::{LinesVector, PagesVector, PixelsVector, WheelDelta}; -pub type WheelEvent = UiEvent; +pub type WheelEvent = Event; #[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))] #[derive(Clone)] pub struct WheelData { diff --git a/packages/liveview/src/events.rs b/packages/liveview/src/events.rs index 841bdeeff..874d8081a 100644 --- a/packages/liveview/src/events.rs +++ b/packages/liveview/src/events.rs @@ -6,9 +6,8 @@ use std::any::Any; use std::sync::Arc; use dioxus_core::ElementId; -use dioxus_core::{EventPriority, UserEvent}; use dioxus_html::event_bubbles; -use dioxus_html::on::*; +use dioxus_html::events::*; #[derive(serde::Serialize, serde::Deserialize)] pub(crate) struct IpcMessage { @@ -30,29 +29,29 @@ struct ImEvent { contents: serde_json::Value, } -pub fn trigger_from_serialized(val: serde_json::Value) -> UserEvent { - let ImEvent { - event, - mounted_dom_id, - contents, - } = serde_json::from_value(val).unwrap(); +pub fn trigger_from_serialized(val: serde_json::Value) -> () { + todo!() + // let ImEvent { + // event, + // mounted_dom_id, + // contents, + // } = serde_json::from_value(val).unwrap(); - let mounted_dom_id = Some(mounted_dom_id); + // let mounted_dom_id = Some(mounted_dom_id); - let name = event_name_from_type(&event); - let event = make_synthetic_event(&event, contents); + // let name = event_name_from_type(&event); + // let event = make_synthetic_event(&event, contents); - UserEvent { - name, - priority: EventPriority::Low, - scope_id: None, - element: mounted_dom_id, - data: event, - bubbles: event_bubbles(name), - } + // UserEvent { + // name, + // scope_id: None, + // element: mounted_dom_id, + // data: event, + // bubbles: event_bubbles(name), + // } } -fn make_synthetic_event(name: &str, val: serde_json::Value) -> Arc { +fn make_synthetic_event(name: &str, val: serde_json::Value) -> Arc { match name { "copy" | "cut" | "paste" => { // diff --git a/packages/native-core/src/real_dom.rs b/packages/native-core/src/real_dom.rs index 20a055778..a0f071a15 100644 --- a/packages/native-core/src/real_dom.rs +++ b/packages/native-core/src/real_dom.rs @@ -67,7 +67,7 @@ impl RealDom { let mut nodes_updated = Vec::new(); nodes_updated.push((RealNodeId::ElementId(ElementId(0)), NodeMask::ALL)); for mutations in mutations_vec { - for e in mutations.edits { + for e in mutations.dom_edits { use dioxus_core::DomEdit::*; match e { AppendChildren { root, children } => { diff --git a/packages/native-core/src/utils.rs b/packages/native-core/src/utils.rs index aa61807a4..66e9906a2 100644 --- a/packages/native-core/src/utils.rs +++ b/packages/native-core/src/utils.rs @@ -72,7 +72,7 @@ impl PersistantElementIter { pub fn prune(&mut self, mutations: &Mutations, rdom: &RealDom) -> bool { let mut changed = false; let ids_removed: Vec<_> = mutations - .edits + .dom_edits .iter() .filter_map(|e| { // nodes within templates will never be removed @@ -97,7 +97,7 @@ impl PersistantElementIter { for (el_id, child_idx) in self.stack.iter_mut() { if let NodePosition::InChild(child_idx) = child_idx { if let NodeType::Element { children, .. } = &rdom[*el_id].node_data.node_type { - for m in &mutations.edits { + for m in &mutations.dom_edits { match m { DomEdit::Remove { root } => { let id = rdom.resolve_maybe_id(*root); diff --git a/packages/rsx/src/component.rs b/packages/rsx/src/component.rs index d1132fdae..090f7382d 100644 --- a/packages/rsx/src/component.rs +++ b/packages/rsx/src/component.rs @@ -219,7 +219,7 @@ impl ToTokens for ContentField { match self { ContentField::ManExpr(e) => e.to_tokens(tokens), ContentField::Formatted(s) => tokens.append_all(quote! { - __cx.raw_text(#s).0 + __cx.raw_text(#s) }), ContentField::OnHandlerRaw(e) => tokens.append_all(quote! { __cx.event_handler(#e) diff --git a/packages/rsx/src/lib.rs b/packages/rsx/src/lib.rs index 8d5a9da59..6cd5102de 100644 --- a/packages/rsx/src/lib.rs +++ b/packages/rsx/src/lib.rs @@ -77,7 +77,7 @@ impl ToTokens for CallBody { }) } else { out_tokens.append_all(quote! { - ::dioxus::core::LazyNodes::new( move | __cx: ::dioxus::core::NodeFactory| -> ::dioxus::core::VNode { + ::dioxus::core::LazyNodes::new( move | __cx: &::dioxus::core::ScopeState| -> ::dioxus::core::VNode { #body }) }) @@ -106,7 +106,7 @@ impl<'a> ToTokens for TemplateRenderer<'a> { }; let key_tokens = match key { - Some(tok) => quote! { Some( __cx.raw_text_inline(#tok) ) }, + Some(tok) => quote! { Some( __cx.raw_text(#tok) ) }, None => quote! { None }, }; diff --git a/packages/rsx/src/node.rs b/packages/rsx/src/node.rs index 8788e52d2..11ec1a0b2 100644 --- a/packages/rsx/src/node.rs +++ b/packages/rsx/src/node.rs @@ -131,10 +131,10 @@ impl ToTokens for BodyNode { BodyNode::Element(el) => el.to_tokens(tokens), BodyNode::Component(comp) => comp.to_tokens(tokens), BodyNode::Text(txt) => tokens.append_all(quote! { - __cx.text(#txt) + __cx.text_node(#txt) }), BodyNode::RawExpr(exp) => tokens.append_all(quote! { - __cx.fragment_from_iter(#exp) + __cx.make_node(#exp) }), BodyNode::ForLoop(exp) => { let ForLoop { @@ -144,7 +144,7 @@ impl ToTokens for BodyNode { let renderer = TemplateRenderer { roots: &body }; tokens.append_all(quote! { - __cx.fragment_from_iter( + __cx.make_node( (#expr).into_iter().map(|#pat| { #renderer }) ) }) @@ -152,7 +152,7 @@ impl ToTokens for BodyNode { BodyNode::IfChain(chain) => { if is_if_chain_terminated(chain) { tokens.append_all(quote! { - __cx.fragment_from_iter(#chain) + __cx.make_node(#chain) }); } else { let ExprIf { @@ -206,7 +206,7 @@ impl ToTokens for BodyNode { }); tokens.append_all(quote! { - __cx.fragment_from_iter(#body) + __cx.make_node(#body) }); } } diff --git a/packages/tui/src/focus.rs b/packages/tui/src/focus.rs index 9c070bc00..4a21df431 100644 --- a/packages/tui/src/focus.rs +++ b/packages/tui/src/focus.rs @@ -256,7 +256,7 @@ impl FocusState { if self.focus_iter.prune(mutations, rdom) { self.dirty = true; } - for m in &mutations.edits { + for m in &mutations.dom_edits { match m { dioxus_core::DomEdit::ReplaceWith { root, .. } => remove_children( &mut [&mut self.last_focused_id], diff --git a/packages/web/src/dom.rs b/packages/web/src/dom.rs index e211188c9..17b7978a2 100644 --- a/packages/web/src/dom.rs +++ b/packages/web/src/dom.rs @@ -19,36 +19,14 @@ use web_sys::{Document, Element, Event, HtmlElement}; use crate::Config; pub struct WebsysDom { - pub interpreter: Interpreter, - - pub(crate) root: Element, - - pub handler: Closure, + interpreter: Interpreter, + handler: Closure, + root: Element, } impl WebsysDom { pub fn new(cfg: Config, event_channel: mpsc::UnboundedSender) -> Self { // eventually, we just want to let the interpreter do all the work of decoding events into our event type - let callback: Box = Box::new(move |event: &web_sys::Event| { - _ = event_channel.unbounded_send(event.clone()); - - // if let Ok(synthetic_event) = decoded { - // // Try to prevent default if the attribute is set - // if let Some(node) = target.dyn_ref::() { - // if let Some(name) = node.get_attribute("dioxus-prevent-default") { - // if name == synthetic_event.name - // || name.trim_start_matches("on") == synthetic_event.name - // { - // log::trace!("Preventing default"); - // event.prevent_default(); - // } - // } - // } - - // sender_callback.as_ref()(SchedulerMsg::Event(synthetic_event)) - // } - }); - // a match here in order to avoid some error during runtime browser test let document = load_document(); let root = match document.get_element_by_id(&cfg.rootname) { @@ -58,8 +36,10 @@ impl WebsysDom { Self { interpreter: Interpreter::new(root.clone()), - handler: Closure::wrap(callback), root, + handler: Closure::wrap(Box::new(move |event: &web_sys::Event| { + let _ = event_channel.unbounded_send(event.clone()); + })), } } @@ -99,11 +79,10 @@ impl WebsysDom { } SetText { value, id } => i.SetText(id.0 as u32, value.into()), NewEventListener { name, scope, id } => { - let handler: &Function = self.handler.as_ref().unchecked_ref(); self.interpreter.NewEventListener( name, id.0 as u32, - handler, + self.handler.as_ref().unchecked_ref(), event_bubbles(&name[2..]), ); } diff --git a/packages/web/src/lib.rs b/packages/web/src/lib.rs index 68ccf3cf5..443791ec1 100644 --- a/packages/web/src/lib.rs +++ b/packages/web/src/lib.rs @@ -192,8 +192,8 @@ pub async fn run_with_props(root: fn(Scope) -> Element, root_prop if should_hydrate { } else { let edits = dom.rebuild(); - websys_dom.apply_edits(edits.template_mutations); - websys_dom.apply_edits(edits.edits); + websys_dom.apply_edits(edits.template_edits); + websys_dom.apply_edits(edits.dom_edits); } let mut work_loop = ric_raf::RafLoop::new(); @@ -247,8 +247,8 @@ pub async fn run_with_props(root: fn(Scope) -> Element, root_prop log::debug!("edits {:#?}", edits); - websys_dom.apply_edits(edits.template_mutations); - websys_dom.apply_edits(edits.edits); + websys_dom.apply_edits(edits.template_edits); + websys_dom.apply_edits(edits.dom_edits); } } diff --git a/packages/web/tests/hydrate.rs b/packages/web/tests/hydrate.rs index d0b5acb9c..f295037bb 100644 --- a/packages/web/tests/hydrate.rs +++ b/packages/web/tests/hydrate.rs @@ -23,7 +23,7 @@ fn makes_tree() { let mut dom = VirtualDom::new(app); let muts = dom.rebuild(); - dbg!(muts.edits); + dbg!(muts.dom_edits); } #[wasm_bindgen_test]