From 62df5f7097f5968455a0b25eebb0540d958d2fd3 Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Sun, 20 Feb 2022 17:37:46 -0500 Subject: [PATCH 1/8] docs: document the entire core crate --- packages/core/src/diff.rs | 10 +--- packages/core/src/events.rs | 25 +++++++-- packages/core/src/lazynodes.rs | 66 ++++++++++++---------- packages/core/src/lib.rs | 4 ++ packages/core/src/mutations.rs | 94 +++++++++++++++++++++++++++---- packages/core/src/nodes.rs | 95 +++++++++++++++++++++++++++----- packages/core/src/properties.rs | 6 ++ packages/core/src/scopes.rs | 6 +- packages/core/src/util.rs | 5 ++ packages/core/src/virtual_dom.rs | 12 +++- packages/rsx/src/lib.rs | 4 +- 11 files changed, 253 insertions(+), 74 deletions(-) diff --git a/packages/core/src/diff.rs b/packages/core/src/diff.rs index d7189a710..80635bbd2 100644 --- a/packages/core/src/diff.rs +++ b/packages/core/src/diff.rs @@ -257,11 +257,10 @@ impl<'b> DiffState<'b> { vcomponent.scope.set(Some(new_idx)); log::trace!( - "created component \"{}\", id: {:?} parent {:?} orig: {:?}", + "created component \"{}\", id: {:?} parent {:?}", vcomponent.fn_name, new_idx, parent_idx, - vcomponent.originator ); // if vcomponent.can_memoize { @@ -957,13 +956,6 @@ impl<'b> DiffState<'b> { let props = scope.props.take().unwrap(); c.props.borrow_mut().replace(props); self.scopes.try_remove(scope_id).unwrap(); - - // // we can only remove components if they are actively being diffed - // if self.scope_stack.contains(&c.originator) { - // log::trace!("Removing component {:?}", old); - - // self.scopes.try_remove(scope_id).unwrap(); - // } } self.leave_scope(); } diff --git a/packages/core/src/events.rs b/packages/core/src/events.rs index 8386cedd8..e92ead8cc 100644 --- a/packages/core/src/events.rs +++ b/packages/core/src/events.rs @@ -119,24 +119,39 @@ pub enum EventPriority { Low = 0, } +/// The internal Dioxus type that carries any event data to the relevant handler. +/// +/// +/// +/// +/// pub struct AnyEvent { pub(crate) bubble_state: Rc, pub(crate) data: Arc, } impl AnyEvent { + /// Convert this AnyEvent into a specific UiEvent with EventData. + /// + /// ```rust, ignore + /// let evt: FormEvent = evvt.downcast().unwrap(); + /// ``` pub fn downcast(self) -> Option> { let AnyEvent { data, bubble_state } = self; - if let Ok(data) = data.downcast::() { - Some(UiEvent { bubble_state, data }) - } else { - None - } + data.downcast::().ok().map(|data| UiEvent { bubble_state, data }) } } +/// A UiEvent is a type that wraps various EventData. +/// +/// You should prefer to use the name of the event directly, rather than +/// the UiEvent generic type. +/// +/// For the HTML crate, this would include [`MouseEvent`], [`FormEvent`] etc. pub struct UiEvent { + /// The internal data of the event + /// This is wrapped in an Arc so that it can be sent across threads pub data: Arc, #[allow(unused)] diff --git a/packages/core/src/lazynodes.rs b/packages/core/src/lazynodes.rs index 1340c916c..5fac7b194 100644 --- a/packages/core/src/lazynodes.rs +++ b/packages/core/src/lazynodes.rs @@ -35,14 +35,32 @@ enum StackNodeStorage<'a, 'b> { } impl<'a, 'b> LazyNodes<'a, 'b> { - pub fn new_some(_val: F) -> Self - where - F: FnOnce(NodeFactory<'a>) -> VNode<'a> + 'b, - { - Self::new(_val) + /// Create a new LazyNodes closure, optimistically placing it onto the stack. + /// + /// 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 { + // 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 inner = slot.take().unwrap(); + fac.map(inner) + }; + + // miri does not know how to work with mucking directly into bytes + // just use a heap allocated type when miri is running + if cfg!(miri) { + Self { + inner: StackNodeStorage::Heap(Box::new(val)), + } + } else { + unsafe { LazyNodes::new_inner(val) } + } } - /// force this call onto the stack + /// Create a new LazyNodes closure, but force it onto the heap. pub fn new_boxed(_val: F) -> Self where F: FnOnce(NodeFactory<'a>) -> VNode<'a> + 'b, @@ -60,25 +78,6 @@ impl<'a, 'b> LazyNodes<'a, 'b> { } } - pub fn new(_val: impl FnOnce(NodeFactory<'a>) -> 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 inner = slot.take().unwrap(); - fac.map(inner) - }; - - // miri does not know how to work with mucking directly into bytes - if cfg!(miri) { - Self { - inner: StackNodeStorage::Heap(Box::new(val)), - } - } else { - unsafe { LazyNodes::new_inner(val) } - } - } - unsafe fn new_inner(val: F) -> Self where F: FnMut(Option>) -> Option> + 'b, @@ -153,6 +152,15 @@ impl<'a, 'b> LazyNodes<'a, 'b> { } } + /// Call the closure with the given factory to produce real VNodes. + /// + /// ```rust, ignore + /// let f = LazyNodes::new(move |f| f.element("div", [], [], [] None)); + /// + /// let fac = NodeFactory::new(&cx); + /// + /// let node = f.call(cac); + /// ``` pub fn call(self, f: NodeFactory<'a>) -> VNode<'a> { match self.inner { StackNodeStorage::Heap(mut lazy) => lazy(Some(f)).unwrap(), @@ -245,9 +253,7 @@ mod tests { #[test] fn it_works() { fn app(cx: Scope<()>) -> Element { - cx.render(LazyNodes::new_some(|f| { - f.text(format_args!("hello world!")) - })) + cx.render(LazyNodes::new(|f| f.text(format_args!("hello world!")))) } let mut dom = VirtualDom::new(app); @@ -280,7 +286,7 @@ mod tests { .map(|i| { let val = cx.props.inner.clone(); - LazyNodes::new_some(move |f| { + LazyNodes::new(move |f| { log::debug!("hell closure"); let inner = DropInner { id: i }; f.text(format_args!("hello world {:?}, {:?}", inner.id, val)) @@ -288,7 +294,7 @@ mod tests { }) .collect::>(); - LazyNodes::new_some(|f| { + LazyNodes::new(|f| { log::debug!("main closure"); f.fragment_from_iter(it) }) diff --git a/packages/core/src/lib.rs b/packages/core/src/lib.rs index e81ddca87..07fb5ce79 100644 --- a/packages/core/src/lib.rs +++ b/packages/core/src/lib.rs @@ -1,5 +1,6 @@ #![allow(non_snake_case)] #![doc = include_str!("../README.md")] +#![deny(missing_docs)] pub(crate) mod diff; pub(crate) mod events; @@ -76,6 +77,9 @@ pub use crate::innerlude::{ VElement, VFragment, VNode, VPlaceholder, VText, VirtualDom, }; +/// The purpose of this module is to alleviate imports of many common types +/// +/// This includes types like [`Scope`], [`Element`], and [`Component`]. pub mod prelude { pub use crate::innerlude::{ fc_to_builder, Attributes, Component, DioxusElement, Element, EventHandler, Fragment, diff --git a/packages/core/src/mutations.rs b/packages/core/src/mutations.rs index bd94d0b71..324b83cb6 100644 --- a/packages/core/src/mutations.rs +++ b/packages/core/src/mutations.rs @@ -16,8 +16,13 @@ use std::{any::Any, fmt::Debug}; /// /// Mutations are the only link between the RealDOM and the VirtualDOM. pub struct Mutations<'a> { + /// The list of edits that need to be applied for the RealDOM to match the VirtualDOM. pub edits: Vec>, + + /// The list of Scopes that were diffed, created, and removed during the Diff process. pub dirty_scopes: FxHashSet, + + /// The list of nodes to connect to the RealDOM. pub refs: Vec>, } @@ -39,73 +44,142 @@ impl Debug for Mutations<'_> { serde(tag = "type") )] pub enum DomEdit<'bump> { + /// Push the given root node onto our stack. PushRoot { + /// The ID of the root node to push. root: u64, }, + /// 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. many: u32, }, - // // save a possibly-fragment node as a template - // SaveAsTemplate { - // many: u32, - // }, - - // "Root" refers to the item directly - // it's a waste of an instruction to push the root directly + /// Replace a given (single) node with a handful of nodes currently on the stack. ReplaceWith { + /// The ID of the node to be replaced. root: u64, + + /// How many nodes should be popped from the stack to replace the target node. m: u32, }, + + /// Insert a number of nodes after a given node. InsertAfter { + /// The ID of the node to insert after. root: u64, + + /// How many nodes should be popped from the stack to insert after the target node. n: u32, }, + + /// Insert a number of nodes before a given node. InsertBefore { + /// The ID of the node to insert before. root: u64, + + /// How many nodes should be popped from the stack to insert before the target node. n: u32, }, + + /// Remove a particular node from the DOM Remove { + /// The ID of the node to remove. root: u64, }, + + /// Create a new purely-text node CreateTextNode { + /// The ID the new node should have. + root: u64, + + /// The textcontent of the node text: &'bump str, - root: u64, }, + + /// Create a new purely-element node CreateElement { - tag: &'bump str, + /// The ID the new node should have. root: u64, + + /// The tagname of the node + tag: &'bump str, }, + + /// Create a new purely-comment node with a given namespace CreateElementNs { - tag: &'bump str, + /// The ID the new node should have. root: u64, + + /// The namespace of the node + tag: &'bump str, + + /// The namespace of the node (like `SVG`) ns: &'static str, }, + + /// Create a new placeholder node. + /// In most implementations, this will either be a hidden div or a comment node. CreatePlaceholder { + /// The ID the new node should have. root: u64, }, + + /// Create a new Event Listener. NewEventListener { + /// The name of the event to listen for. event_name: &'static str, + + /// The ID of the node to attach the listener to. scope: ScopeId, + + /// The ID of the node to attach the listener to. root: u64, }, + + /// Remove an existing Event Listener. RemoveEventListener { + /// The ID of the node to remove. root: u64, + + /// The name of the event to remove. event: &'static str, }, + + /// Set the textcontent of a node. SetText { + /// The ID of the node to set the textcontent of. root: u64, + + /// The textcontent of the node text: &'bump str, }, + + /// Set the value of a node's attribute. SetAttribute { + /// The ID of the node to set the attribute of. root: u64, + + /// The name of the attribute to set. field: &'static str, + + /// The value of the attribute. value: &'bump str, + + /// The (optional) namespace of the attribute. + /// For instance, "style" is in the "style" namespace. ns: Option<&'bump str>, }, + + /// Remove an attribute from a node. RemoveAttribute { + /// The ID of the node to remove. root: u64, + + /// The name of the attribute to remove. name: &'static str, }, } diff --git a/packages/core/src/nodes.rs b/packages/core/src/nodes.rs index 7a36f58ce..094af2281 100644 --- a/packages/core/src/nodes.rs +++ b/packages/core/src/nodes.rs @@ -148,7 +148,7 @@ impl<'src> VNode<'src> { } // Create an "owned" version of the vnode. - pub fn decouple(&self) -> VNode<'src> { + pub(crate) fn decouple(&self) -> VNode<'src> { match *self { VNode::Text(t) => VNode::Text(t), VNode::Element(e) => VNode::Element(e), @@ -181,7 +181,6 @@ impl Debug for VNode<'_> { .field("fnptr", &comp.user_fc) .field("key", &comp.key) .field("scope", &comp.scope) - .field("originator", &comp.originator) .finish(), } } @@ -200,6 +199,7 @@ impl std::fmt::Display for ElementId { } impl ElementId { + /// Convertt the ElementId to a `u64`. pub fn as_u64(self) -> u64 { self.0 as u64 } @@ -211,18 +211,26 @@ fn empty_cell() -> Cell> { /// A placeholder node only generated when Fragments don't have any children. pub struct VPlaceholder { + /// The [`ElementId`] of the placeholder. pub id: Cell>, } /// A bump-allocated string slice and metadata. pub struct VText<'src> { - pub text: &'src str, + /// The [`ElementId`] of the VText. pub id: Cell>, + + /// The text of the VText. + pub text: &'src str, + + /// An indiciation if this VText can be ignored during diffing + /// Is usually only when there are no strings to be formatted (so the text is &'static str) pub is_static: bool, } /// A list of VNodes with no single root. pub struct VFragment<'src> { + /// The key of the fragment to be used during keyed diffing. pub key: Option<&'src str>, /// Fragments can never have zero children. Enforced by NodeFactory. @@ -233,13 +241,34 @@ pub struct VFragment<'src> { /// An element like a "div" with children, listeners, and attributes. pub struct VElement<'a> { - pub tag: &'static str, - pub namespace: Option<&'static str>, - pub key: Option<&'a str>, + /// The [`ElementId`] of the VText. pub id: Cell>, + + /// The key of the element to be used during keyed diffing. + pub key: Option<&'a str>, + + /// The tag name of the element. + /// + /// IE "div" + pub tag: &'static str, + + /// The namespace of the VElement + /// + /// IE "svg" + pub namespace: Option<&'static str>, + + /// The parent of the Element (if any). + /// + /// Used when bubbling events pub parent: Cell>, + + /// The Listeners of the VElement. pub listeners: &'a [Listener<'a>], + + /// The attributes of the VElement. pub attributes: &'a [Attribute<'a>], + + /// The children of the VElement. pub children: &'a [VNode<'a>], } @@ -275,12 +304,19 @@ impl Debug for VElement<'_> { /// }; /// ``` pub trait DioxusElement { + /// The tag name of the element. const TAG_NAME: &'static str; + + /// The namespace of the element. const NAME_SPACE: Option<&'static str>; + + /// The tag name of the element. #[inline] fn tag_name(&self) -> &'static str { Self::TAG_NAME } + + /// The namespace of the element. #[inline] fn namespace(&self) -> Option<&'static str> { Self::NAME_SPACE @@ -291,16 +327,25 @@ pub trait DioxusElement { /// `href="https://example.com"`. #[derive(Clone, Debug)] pub struct Attribute<'a> { + /// The name of the attribute. pub name: &'static str, + /// The value of the attribute. pub value: &'a str, + /// An indication if this attribute can be ignored during diffing + /// + /// Usually only when there are no strings to be formatted (so the value is &'static str) pub is_static: bool, + /// An indication of we should always try and set the attribute. + /// Used in controlled components to ensure changes are propagated. pub is_volatile: bool, - // Doesn't exist in the html spec. - // Used in Dioxus to denote "style" tags. + /// The namespace of the attribute. + /// + /// Doesn't exist in the html spec. + /// Used in Dioxus to denote "style" tags and other attribute groups. pub namespace: Option<&'static str>, } @@ -353,6 +398,8 @@ type ExternalListenerCallback<'bump, T> = BumpBox<'bump, dyn FnMut(T) + 'bump>; /// /// ``` 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>>, } @@ -381,12 +428,23 @@ impl EventHandler<'_, T> { /// Virtual Components for custom user-defined components /// Only supports the functional syntax pub struct VComponent<'src> { + /// The key of the component to be used during keyed diffing. pub key: Option<&'src str>, - pub originator: ScopeId, + + /// The ID of the component. + /// Will not be assigned until after the component has been initialized. pub scope: Cell>, + + /// An indication if the component is static (can be memozied) pub can_memoize: bool, + + /// The function pointer to the component's render function. pub user_fc: ComponentPtr, + + /// The actual name of the component. pub fn_name: &'static str, + + /// The props of the component. pub props: RefCell>>, } @@ -434,6 +492,7 @@ pub struct NodeFactory<'a> { } impl<'a> NodeFactory<'a> { + /// Create a new [`NodeFactory`] from a [`Scope`] or [`ScopeState`] pub fn new(scope: &'a ScopeState) -> NodeFactory<'a> { NodeFactory { scope, @@ -441,6 +500,7 @@ impl<'a> NodeFactory<'a> { } } + /// Get the custom alloactor for this component #[inline] pub fn bump(&self) -> &'a bumpalo::Bump { self.bump @@ -482,6 +542,7 @@ impl<'a> NodeFactory<'a> { })) } + /// Create a new [`VNode::VElement`] pub fn element( &self, el: impl DioxusElement, @@ -500,6 +561,9 @@ impl<'a> NodeFactory<'a> { ) } + /// Create a new [`VNode::VElement`] without the trait bound + /// + /// IE pass in "div" instead of `div` pub fn raw_element( &self, tag_name: &'static str, @@ -529,6 +593,7 @@ impl<'a> NodeFactory<'a> { })) } + /// Create a new [`Attribute`] pub fn attr( &self, name: &'static str, @@ -546,6 +611,7 @@ impl<'a> NodeFactory<'a> { } } + /// Create a new [`VNode::VComponent`] pub fn component

( &self, component: fn(Scope<'a, P>) -> Element, @@ -561,11 +627,8 @@ impl<'a> NodeFactory<'a> { scope: Default::default(), can_memoize: P::IS_STATIC, user_fc: component as ComponentPtr, - originator: self.scope.scope_id(), fn_name, props: RefCell::new(Some(Box::new(VComponentProps { - // local_props: RefCell::new(Some(props)), - // heap_props: RefCell::new(None), props, memo: P::memoize, // smuggle the memoization function across borders @@ -586,6 +649,7 @@ impl<'a> NodeFactory<'a> { VNode::Component(vcomp) } + /// Create a new [`Listener`] pub fn listener(self, event: &'static str, callback: InternalHandler<'a>) -> Listener<'a> { Listener { event, @@ -594,6 +658,7 @@ impl<'a> NodeFactory<'a> { } } + /// Create a new [`VNode::VFragment`] from a root of the rsx! call pub fn fragment_root<'b, 'c>( self, node_iter: impl IntoIterator + 'c> + 'b, @@ -614,6 +679,7 @@ impl<'a> NodeFactory<'a> { } } + /// Create a new [`VNode::VFragment`] from any iterator pub fn fragment_from_iter<'b, 'c>( self, node_iter: impl IntoIterator + 'c> + 'b, @@ -653,8 +719,7 @@ impl<'a> NodeFactory<'a> { } } - // this isn't quite feasible yet - // I think we need some form of interior mutability or state on nodefactory that stores which subtree was created + /// Create a new [`VNode`] from any iterator of children pub fn create_children( self, node_iter: impl IntoIterator>, @@ -679,6 +744,7 @@ impl<'a> NodeFactory<'a> { } } + /// Create a new [`EventHandler`] from an [`FnMut`] pub fn event_handler(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)) }; @@ -709,6 +775,7 @@ impl Debug for NodeFactory<'_> { /// As such, all node creation must go through the factory, which is only available in the component context. /// These strict requirements make it possible to manage lifetimes and state. pub trait IntoVNode<'a> { + /// Convert this into a [`VNode`], using the [`NodeFactory`] as a source of allocation fn into_vnode(self, cx: NodeFactory<'a>) -> VNode<'a>; } diff --git a/packages/core/src/properties.rs b/packages/core/src/properties.rs index f42bc36ba..e81fc52a2 100644 --- a/packages/core/src/properties.rs +++ b/packages/core/src/properties.rs @@ -128,8 +128,14 @@ pub fn Fragment<'a>(cx: Scope<'a, FragmentProps<'a>>) -> Element { /// } /// ``` pub trait Properties: Sized { + /// The type of the builder for this component. + /// Used to create "in-progress" versions of the props. type Builder; + + /// An indication if these props are can be memoized automatically. const IS_STATIC: bool; + + /// Create a builder for this component. fn builder() -> Self::Builder; /// Memoization can only happen if the props are valid for the 'static lifetime diff --git a/packages/core/src/scopes.rs b/packages/core/src/scopes.rs index 00c3024bf..bc592fc4a 100644 --- a/packages/core/src/scopes.rs +++ b/packages/core/src/scopes.rs @@ -396,7 +396,10 @@ impl ScopeArena { /// } /// ``` pub struct Scope<'a, P = ()> { + /// The internal ScopeState for this component pub scope: &'a ScopeState, + + /// The props for this component pub props: &'a P, } @@ -747,7 +750,8 @@ impl ScopeState { self.push_future(fut); } - // todo: attach some state to the future to know if we should poll it + /// Informs the scheduler that this task is no longer needed and should be removed + /// on next poll. pub fn remove_future(&self, id: TaskId) { self.tasks.remove_fut(id); } diff --git a/packages/core/src/util.rs b/packages/core/src/util.rs index b9640792d..a6a524e27 100644 --- a/packages/core/src/util.rs +++ b/packages/core/src/util.rs @@ -1,5 +1,7 @@ use crate::innerlude::*; +/// An iterator that only yields "real" [`Element`]s. IE only Elements that are +/// not [`VNode::VComponent`] or [`VNode::VFragment`], . pub struct ElementIdIterator<'a> { vdom: &'a VirtualDom, @@ -9,6 +11,9 @@ pub struct ElementIdIterator<'a> { } impl<'a> ElementIdIterator<'a> { + /// Create a new iterator from the given [`VirtualDom`] and [`VNode`] + /// + /// This will allow you to iterate through all the real childrne of the [`VNode`]. pub fn new(vdom: &'a VirtualDom, node: &'a VNode<'a>) -> Self { Self { vdom, diff --git a/packages/core/src/virtual_dom.rs b/packages/core/src/virtual_dom.rs index a7092104c..6d1678773 100644 --- a/packages/core/src/virtual_dom.rs +++ b/packages/core/src/virtual_dom.rs @@ -114,15 +114,18 @@ pub struct VirtualDom { ), } +/// The type of message that can be sent to the scheduler. +/// +/// These messages control how the scheduler will process updates to the UI. #[derive(Debug)] pub enum SchedulerMsg { - // events from the host + /// Events from the Renderer Event(UserEvent), - // setstate + /// Immediate updates from Components that mark them as dirty Immediate(ScopeId), - // an async task pushed from an event handler (or just spawned) + /// New tasks from components that should be polled when the next poll is ready NewTask(ScopeId), } @@ -388,6 +391,9 @@ impl VirtualDom { } } + /// Handle an individual message for the scheduler. + /// + /// This will either call an event listener or mark a component as dirty. pub fn process_message(&mut self, msg: SchedulerMsg) { match msg { SchedulerMsg::NewTask(_id) => { diff --git a/packages/rsx/src/lib.rs b/packages/rsx/src/lib.rs index e20d80dec..858f07c5c 100644 --- a/packages/rsx/src/lib.rs +++ b/packages/rsx/src/lib.rs @@ -79,7 +79,7 @@ impl ToTokens for CallBody { match &self.custom_context { // The `in cx` pattern allows directly rendering Some(ident) => out_tokens.append_all(quote! { - #ident.render(LazyNodes::new_some(move |__cx: NodeFactory| -> VNode { + #ident.render(LazyNodes::new(move |__cx: NodeFactory| -> VNode { use dioxus_elements::{GlobalAttributes, SvgAttributes}; #inner })) @@ -87,7 +87,7 @@ impl ToTokens for CallBody { // Otherwise we just build the LazyNode wrapper None => out_tokens.append_all(quote! { - LazyNodes::new_some(move |__cx: NodeFactory| -> VNode { + LazyNodes::new(move |__cx: NodeFactory| -> VNode { use dioxus_elements::{GlobalAttributes, SvgAttributes}; #inner }) From b2a4d387c7e031485126efb4919ef31532597b1e Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Sun, 20 Feb 2022 23:52:36 -0500 Subject: [PATCH 2/8] docs: document all of desktop crate --- packages/desktop/src/controller.rs | 3 +- packages/desktop/src/desktop_context.rs | 98 +++++++++++++++++++--- packages/desktop/src/lib.rs | 26 +++--- packages/desktop/src/user_window_events.rs | 72 ---------------- packages/hooks/src/lib.rs | 3 + packages/hooks/src/usefuture.rs | 42 ++++++++-- packages/web/src/lib.rs | 2 + 7 files changed, 138 insertions(+), 108 deletions(-) delete mode 100644 packages/desktop/src/user_window_events.rs diff --git a/packages/desktop/src/controller.rs b/packages/desktop/src/controller.rs index 8497a42e9..06dfd3e9c 100644 --- a/packages/desktop/src/controller.rs +++ b/packages/desktop/src/controller.rs @@ -1,5 +1,4 @@ -use crate::desktop_context::DesktopContext; -use crate::user_window_events::UserWindowEvent; +use crate::desktop_context::{DesktopContext, UserWindowEvent}; use dioxus_core::*; use std::{ collections::{HashMap, VecDeque}, diff --git a/packages/desktop/src/desktop_context.rs b/packages/desktop/src/desktop_context.rs index e1b726d73..1376110f1 100644 --- a/packages/desktop/src/desktop_context.rs +++ b/packages/desktop/src/desktop_context.rs @@ -3,14 +3,23 @@ use std::rc::Rc; use dioxus_core::ScopeState; use wry::application::event_loop::EventLoopProxy; -use crate::user_window_events::UserWindowEvent; use UserWindowEvent::*; -type ProxyType = EventLoopProxy; +pub type ProxyType = EventLoopProxy; -/// Desktop-Window handle api context +/// Get an imperative handle to the current window +pub fn use_window(cx: &ScopeState) -> &Rc { + cx.use_hook(|_| cx.consume_context::()) + .as_ref() + .unwrap() +} + +/// An imperative interface to the current window. /// -/// you can use this context control some window event +/// To get a handle to the current window, use the [`use_window`] hook. +/// +/// +/// # Example /// /// you can use `cx.consume_context::` to get this context /// @@ -19,7 +28,8 @@ type ProxyType = EventLoopProxy; /// ``` #[derive(Clone)] pub struct DesktopContext { - proxy: ProxyType, + /// The wry/tao proxy to the current window + pub proxy: ProxyType, } impl DesktopContext { @@ -84,12 +94,12 @@ impl DesktopContext { let _ = self.proxy.send_event(AlwaysOnTop(top)); } - // set cursor visible or not + /// set cursor visible or not pub fn set_cursor_visible(&self, visible: bool) { let _ = self.proxy.send_event(CursorVisible(visible)); } - // set cursor grab + /// set cursor grab pub fn set_cursor_grab(&self, grab: bool) { let _ = self.proxy.send_event(CursorGrab(grab)); } @@ -110,9 +120,73 @@ impl DesktopContext { } } -/// use this function can get the `DesktopContext` context. -pub fn use_window(cx: &ScopeState) -> &Rc { - cx.use_hook(|_| cx.consume_context::()) - .as_ref() - .unwrap() +use wry::application::event_loop::ControlFlow; +use wry::application::window::Fullscreen as WryFullscreen; + +use crate::controller::DesktopController; + +pub enum UserWindowEvent { + Update, + + CloseWindow, + DragWindow, + FocusWindow, + + Visible(bool), + Minimize(bool), + Maximize(bool), + MaximizeToggle, + Resizable(bool), + AlwaysOnTop(bool), + Fullscreen(bool), + + CursorVisible(bool), + CursorGrab(bool), + + SetTitle(String), + SetDecorations(bool), + + DevTool, +} + +pub(super) fn handler( + user_event: UserWindowEvent, + desktop: &mut DesktopController, + control_flow: &mut ControlFlow, +) { + // currently dioxus-desktop supports a single window only, + // so we can grab the only webview from the map; + let webview = desktop.webviews.values().next().unwrap(); + let window = webview.window(); + + match user_event { + Update => desktop.try_load_ready_webviews(), + CloseWindow => *control_flow = ControlFlow::Exit, + DragWindow => { + // if the drag_window has any errors, we don't do anything + window.fullscreen().is_none().then(|| window.drag_window()); + } + Visible(state) => window.set_visible(state), + Minimize(state) => window.set_minimized(state), + Maximize(state) => window.set_maximized(state), + MaximizeToggle => window.set_maximized(!window.is_maximized()), + Fullscreen(state) => { + if let Some(handle) = window.current_monitor() { + window.set_fullscreen(state.then(|| WryFullscreen::Borderless(Some(handle)))); + } + } + FocusWindow => window.set_focus(), + Resizable(state) => window.set_resizable(state), + AlwaysOnTop(state) => window.set_always_on_top(state), + + CursorVisible(state) => window.set_cursor_visible(state), + CursorGrab(state) => { + let _ = window.set_cursor_grab(state); + } + + SetTitle(content) => window.set_title(&content), + SetDecorations(state) => window.set_decorations(state), + + DevTool => webview.devtool(), + } } diff --git a/packages/desktop/src/lib.rs b/packages/desktop/src/lib.rs index 4d204a378..cd8099e3c 100644 --- a/packages/desktop/src/lib.rs +++ b/packages/desktop/src/lib.rs @@ -1,18 +1,23 @@ #![doc = include_str!("readme.md")] #![doc(html_logo_url = "https://avatars.githubusercontent.com/u/79236386")] #![doc(html_favicon_url = "https://avatars.githubusercontent.com/u/79236386")] +#![deny(missing_docs)] -pub mod cfg; +mod cfg; mod controller; -pub mod desktop_context; -pub mod escape; -pub mod events; +mod desktop_context; +mod escape; +mod events; mod protocol; -mod user_window_events; +use desktop_context::UserWindowEvent; +pub use desktop_context::{use_window, DesktopContext}; +pub use wry; +pub use wry::application as tao; + +use crate::events::trigger_from_serialized; use cfg::DesktopConfig; use controller::DesktopController; -pub use desktop_context::use_window; use dioxus_core::*; use events::parse_ipc_message; use tao::{ @@ -20,12 +25,8 @@ use tao::{ event_loop::{ControlFlow, EventLoop}, window::Window, }; -pub use wry; -pub use wry::application as tao; use wry::webview::WebViewBuilder; -use crate::events::trigger_from_serialized; - /// Launch the WebView and run the event loop. /// /// This function will start a multithreaded Tokio runtime as well the WebView event loop. @@ -139,8 +140,7 @@ pub fn launch_with_props( } "initialize" => { is_ready.store(true, std::sync::atomic::Ordering::Relaxed); - let _ = proxy - .send_event(user_window_events::UserWindowEvent::Update); + let _ = proxy.send_event(UserWindowEvent::Update); } "browser_open" => { println!("browser_open"); @@ -214,7 +214,7 @@ pub fn launch_with_props( }, Event::UserEvent(user_event) => { - user_window_events::handler(user_event, &mut desktop, control_flow) + desktop_context::handler(user_event, &mut desktop, control_flow) } Event::MainEventsCleared => {} Event::Resumed => {} diff --git a/packages/desktop/src/user_window_events.rs b/packages/desktop/src/user_window_events.rs deleted file mode 100644 index aab14561b..000000000 --- a/packages/desktop/src/user_window_events.rs +++ /dev/null @@ -1,72 +0,0 @@ -use wry::application::event_loop::ControlFlow; -use wry::application::window::Fullscreen as WryFullscreen; - -use crate::controller::DesktopController; - -pub(crate) enum UserWindowEvent { - Update, - - CloseWindow, - DragWindow, - FocusWindow, - - Visible(bool), - Minimize(bool), - Maximize(bool), - MaximizeToggle, - Resizable(bool), - AlwaysOnTop(bool), - Fullscreen(bool), - - CursorVisible(bool), - CursorGrab(bool), - - SetTitle(String), - SetDecorations(bool), - - DevTool, -} - -use UserWindowEvent::*; - -pub(super) fn handler( - user_event: UserWindowEvent, - desktop: &mut DesktopController, - control_flow: &mut ControlFlow, -) { - // currently dioxus-desktop supports a single window only, - // so we can grab the only webview from the map; - let webview = desktop.webviews.values().next().unwrap(); - let window = webview.window(); - - match user_event { - Update => desktop.try_load_ready_webviews(), - CloseWindow => *control_flow = ControlFlow::Exit, - DragWindow => { - // if the drag_window has any errors, we don't do anything - window.fullscreen().is_none().then(|| window.drag_window()); - } - Visible(state) => window.set_visible(state), - Minimize(state) => window.set_minimized(state), - Maximize(state) => window.set_maximized(state), - MaximizeToggle => window.set_maximized(!window.is_maximized()), - Fullscreen(state) => { - if let Some(handle) = window.current_monitor() { - window.set_fullscreen(state.then(|| WryFullscreen::Borderless(Some(handle)))); - } - } - FocusWindow => window.set_focus(), - Resizable(state) => window.set_resizable(state), - AlwaysOnTop(state) => window.set_always_on_top(state), - - CursorVisible(state) => window.set_cursor_visible(state), - CursorGrab(state) => { - let _ = window.set_cursor_grab(state); - } - - SetTitle(content) => window.set_title(&content), - SetDecorations(state) => window.set_decorations(state), - - DevTool => webview.devtool(), - } -} diff --git a/packages/hooks/src/lib.rs b/packages/hooks/src/lib.rs index cb4afd297..2960832c6 100644 --- a/packages/hooks/src/lib.rs +++ b/packages/hooks/src/lib.rs @@ -1,3 +1,6 @@ +#![deny(missing_docs)] +//! Useful foundational hooks for Dioxus + mod usestate; pub use usestate::{use_state, UseState}; diff --git a/packages/hooks/src/usefuture.rs b/packages/hooks/src/usefuture.rs index 58f350124..32b359204 100644 --- a/packages/hooks/src/usefuture.rs +++ b/packages/hooks/src/usefuture.rs @@ -1,19 +1,30 @@ +#![allow(missing_docs)] use dioxus_core::{ScopeState, TaskId}; use std::{cell::Cell, future::Future, rc::Rc}; +/// A future that resolves to a value. +/// +/// This runs the future only once - though the future may be regenerated +/// through the [`UseFuture::restart`] method. +/// +/// This is commonly used for components that cannot be rendered until some +/// asynchronous operation has completed. +/// +/// +/// +/// +/// pub fn use_future<'a, T: 'static, F: Future + 'static>( cx: &'a ScopeState, new_fut: impl FnOnce() -> F, ) -> &'a UseFuture { - let state = cx.use_hook(move |_| { - // - UseFuture { - update: cx.schedule_update(), - needs_regen: Cell::new(true), - slot: Rc::new(Cell::new(None)), - value: None, - task: None, - } + let state = cx.use_hook(move |_| UseFuture { + update: cx.schedule_update(), + needs_regen: Cell::new(true), + slot: Rc::new(Cell::new(None)), + value: None, + task: None, + pending: true, }); if let Some(value) = state.slot.take() { @@ -24,6 +35,7 @@ pub fn use_future<'a, T: 'static, F: Future + 'static>( if state.needs_regen.get() { // We don't need regen anymore state.needs_regen.set(false); + state.pending = false; // Create the new future let fut = new_fut(); @@ -42,10 +54,17 @@ pub fn use_future<'a, T: 'static, F: Future + 'static>( state } +pub enum FutureState<'a, T> { + Pending, + Complete(&'a T), + Regenerating(&'a T), // the old value +} + pub struct UseFuture { update: Rc, needs_regen: Cell, value: Option, + pending: bool, slot: Rc>>, task: Option, } @@ -72,4 +91,9 @@ impl UseFuture { pub fn value(&self) -> Option<&T> { self.value.as_ref() } + + pub fn state(&self) -> FutureState { + // self.value.as_ref() + FutureState::Pending + } } diff --git a/packages/web/src/lib.rs b/packages/web/src/lib.rs index 90fe84d04..c82dc4975 100644 --- a/packages/web/src/lib.rs +++ b/packages/web/src/lib.rs @@ -1,3 +1,5 @@ +#![deny(missing_docs)] + //! Dioxus WebSys //! //! ## Overview From fb661e5519f04d9efe29968ad359d7a4e54c99c6 Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Sun, 20 Feb 2022 23:52:54 -0500 Subject: [PATCH 3/8] wip: disable hook documentation --- packages/hooks/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/hooks/src/lib.rs b/packages/hooks/src/lib.rs index 2960832c6..cb0c43fb9 100644 --- a/packages/hooks/src/lib.rs +++ b/packages/hooks/src/lib.rs @@ -1,4 +1,4 @@ -#![deny(missing_docs)] +// #![deny(missing_docs)] //! Useful foundational hooks for Dioxus mod usestate; From 24743e44e92def5384b2a8e1f9b5bcac7bfc00ca Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Wed, 23 Feb 2022 13:53:45 -0500 Subject: [PATCH 4/8] feat: canoncialize assets for macOS --- packages/desktop/Cargo.toml | 5 ++++ packages/desktop/src/protocol.rs | 41 ++++++++++++++++++++++++++++++-- 2 files changed, 44 insertions(+), 2 deletions(-) diff --git a/packages/desktop/Cargo.toml b/packages/desktop/Cargo.toml index 6fd277ba4..f90d00000 100644 --- a/packages/desktop/Cargo.toml +++ b/packages/desktop/Cargo.toml @@ -29,6 +29,11 @@ dioxus-html = { path = "../html", features = ["serialize"], version = "^0.1.6" } webbrowser = "0.5.5" mime_guess = "2.0.3" dioxus-interpreter-js = { path = "../interpreter", version = "^0.0.0" } +dunce = "1.0.2" + + +[target.'cfg(target_os = "macos")'.dependencies] +core-foundation = "0.9.3" [features] diff --git a/packages/desktop/src/protocol.rs b/packages/desktop/src/protocol.rs index c8f007c1d..6cac600f2 100644 --- a/packages/desktop/src/protocol.rs +++ b/packages/desktop/src/protocol.rs @@ -1,4 +1,4 @@ -use std::path::Path; +use std::path::{Path, PathBuf}; use wry::{ http::{status::StatusCode, Request, Response, ResponseBuilder}, Result, @@ -21,8 +21,13 @@ pub(super) fn desktop_handler(request: &Request) -> Result { .mimetype("text/javascript") .body(dioxus_interpreter_js::INTERPRETER_JS.as_bytes().to_vec()) } else { + // the path of the asset specified without any relative paths let path_buf = Path::new(trimmed).canonicalize()?; - let cur_path = Path::new(".").canonicalize()?; + + // the current path of the bundle + let cur_path = get_asset_root() + .unwrap_or_else(|| Path::new(".").to_path_buf()) + .canonicalize()?; if !path_buf.starts_with(cur_path) { return ResponseBuilder::new() @@ -45,3 +50,35 @@ pub(super) fn desktop_handler(request: &Request) -> Result { ResponseBuilder::new().mimetype(&meta).body(data) } } + +#[allow(unreachable_code)] +fn get_asset_root() -> Option { + /* + We're matching exactly how cargo-bundle works. + + - [x] macOS + - [ ] Windows + - [ ] Linux (rpm) + - [ ] Linux (deb) + - [ ] iOS + - [ ] Android + + */ + + if std::env::var_os("CARGO").is_some() { + return None; + } + + // TODO: support for other platforms + #[cfg(target_os = "macos")] + { + let bundle = core_foundation::bundle::CFBundle::main_bundle(); + let bundle_path = bundle.path()?; + let resources_path = bundle.resources_path()?; + let absolute_resources_root = bundle_path.join(resources_path); + let canonical_resources_root = dunce::canonicalize(absolute_resources_root).ok()?; + return Some(canonical_resources_root); + } + + None +} From c12bcd8150371ff7655ffa8b1b1ec80eecd9a777 Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Wed, 23 Feb 2022 14:19:00 -0500 Subject: [PATCH 5/8] fix: protocol works on in both cargo and bundle This commit enables assets to be served locally through cargo run or through a bundle strategy. We use cargo-bundle's bundle strategy. This PR checks only for macOS targets. --- packages/desktop/src/protocol.rs | 26 +++++++++++--------------- 1 file changed, 11 insertions(+), 15 deletions(-) diff --git a/packages/desktop/src/protocol.rs b/packages/desktop/src/protocol.rs index 6cac600f2..b7daba550 100644 --- a/packages/desktop/src/protocol.rs +++ b/packages/desktop/src/protocol.rs @@ -21,30 +21,25 @@ pub(super) fn desktop_handler(request: &Request) -> Result { .mimetype("text/javascript") .body(dioxus_interpreter_js::INTERPRETER_JS.as_bytes().to_vec()) } else { - // the path of the asset specified without any relative paths - let path_buf = Path::new(trimmed).canonicalize()?; + let asset_root = get_asset_root().unwrap_or_else(|| Path::new(".").to_path_buf()); + let asset = asset_root.join(trimmed).canonicalize()?; - // the current path of the bundle - let cur_path = get_asset_root() - .unwrap_or_else(|| Path::new(".").to_path_buf()) - .canonicalize()?; - - if !path_buf.starts_with(cur_path) { + if !asset.starts_with(asset_root) { return ResponseBuilder::new() .status(StatusCode::FORBIDDEN) .body(String::from("Forbidden").into_bytes()); } - if !path_buf.exists() { + if !asset.exists() { return ResponseBuilder::new() .status(StatusCode::NOT_FOUND) .body(String::from("Not Found").into_bytes()); } - let mime = mime_guess::from_path(&path_buf).first_or_octet_stream(); + let mime = mime_guess::from_path(&asset).first_or_octet_stream(); // do not let path searching to go two layers beyond the caller level - let data = std::fs::read(path_buf)?; + let data = std::fs::read(asset)?; let meta = format!("{}", mime); ResponseBuilder::new().mimetype(&meta).body(data) @@ -73,10 +68,11 @@ fn get_asset_root() -> Option { #[cfg(target_os = "macos")] { let bundle = core_foundation::bundle::CFBundle::main_bundle(); - let bundle_path = bundle.path()?; - let resources_path = bundle.resources_path()?; - let absolute_resources_root = bundle_path.join(resources_path); - let canonical_resources_root = dunce::canonicalize(absolute_resources_root).ok()?; + let bundle_path = dbg!(bundle.path()?); + let resources_path = dbg!(bundle.resources_path()?); + let absolute_resources_root = dbg!(bundle_path.join(resources_path)); + let canonical_resources_root = dbg!(dunce::canonicalize(absolute_resources_root).ok()?); + return Some(canonical_resources_root); } From ccb3aa7977c016546bb310ca3f86d26a521ec223 Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Wed, 23 Feb 2022 15:11:57 -0500 Subject: [PATCH 6/8] feat: allow custom root directories --- packages/desktop/Cargo.toml | 3 --- packages/desktop/src/cfg.rs | 12 ++++++++++++ packages/desktop/src/lib.rs | 7 ++++++- packages/desktop/src/protocol.rs | 6 ++++-- 4 files changed, 22 insertions(+), 6 deletions(-) diff --git a/packages/desktop/Cargo.toml b/packages/desktop/Cargo.toml index f90d00000..017d399c1 100644 --- a/packages/desktop/Cargo.toml +++ b/packages/desktop/Cargo.toml @@ -30,12 +30,9 @@ webbrowser = "0.5.5" mime_guess = "2.0.3" dioxus-interpreter-js = { path = "../interpreter", version = "^0.0.0" } dunce = "1.0.2" - - [target.'cfg(target_os = "macos")'.dependencies] core-foundation = "0.9.3" - [features] default = ["tokio_runtime"] tokio_runtime = ["tokio"] diff --git a/packages/desktop/src/cfg.rs b/packages/desktop/src/cfg.rs index 161a574b1..6028706bc 100644 --- a/packages/desktop/src/cfg.rs +++ b/packages/desktop/src/cfg.rs @@ -1,3 +1,5 @@ +use std::path::PathBuf; + use wry::application::window::Icon; use wry::{ application::{ @@ -18,6 +20,7 @@ pub struct DesktopConfig { pub(crate) pre_rendered: Option, pub(crate) event_handler: Option>, pub(crate) disable_context_menu: bool, + pub(crate) resource_dir: Option, } pub(crate) type WryProtocol = ( @@ -38,14 +41,23 @@ impl DesktopConfig { file_drop_handler: None, pre_rendered: None, disable_context_menu: !cfg!(debug_assertions), + resource_dir: None, } } + /// set the directory from which assets will be searched in release mode + pub fn with_resource_directory(mut self, path: impl Into) -> Self { + self.resource_dir = Some(path.into()); + self + } + + /// Set whether or not the right-click context menu should be disabled. pub fn with_disable_context_menu(&mut self, disable: bool) -> &mut Self { self.disable_context_menu = disable; self } + /// With pre-rendered HTML content pub fn with_prerendered(&mut self, content: String) -> &mut Self { self.pre_rendered = Some(content); self diff --git a/packages/desktop/src/lib.rs b/packages/desktop/src/lib.rs index 268639597..5ddb0583a 100644 --- a/packages/desktop/src/lib.rs +++ b/packages/desktop/src/lib.rs @@ -122,8 +122,11 @@ pub fn launch_with_props( let (is_ready, sender) = (desktop.is_ready.clone(), desktop.sender.clone()); let proxy = proxy.clone(); + let file_handler = cfg.file_drop_handler.take(); + let resource_dir = cfg.resource_dir.clone(); + let mut webview = WebViewBuilder::new(window) .unwrap() .with_transparent(cfg.window.window.transparent) @@ -160,7 +163,9 @@ pub fn launch_with_props( log::warn!("invalid IPC message received"); }); }) - .with_custom_protocol(String::from("dioxus"), protocol::desktop_handler) + .with_custom_protocol(String::from("dioxus"), move |r| { + protocol::desktop_handler(r, resource_dir.clone()) + }) .with_file_drop_handler(move |window, evet| { file_handler .as_ref() diff --git a/packages/desktop/src/protocol.rs b/packages/desktop/src/protocol.rs index b7daba550..9422ec157 100644 --- a/packages/desktop/src/protocol.rs +++ b/packages/desktop/src/protocol.rs @@ -4,7 +4,7 @@ use wry::{ Result, }; -pub(super) fn desktop_handler(request: &Request) -> Result { +pub(super) fn desktop_handler(request: &Request, asset_root: Option) -> Result { // Any content that uses the `dioxus://` scheme will be shuttled through this handler as a "special case". // For now, we only serve two pieces of content which get included as bytes into the final binary. let path = request.uri().replace("dioxus://", ""); @@ -21,7 +21,9 @@ pub(super) fn desktop_handler(request: &Request) -> Result { .mimetype("text/javascript") .body(dioxus_interpreter_js::INTERPRETER_JS.as_bytes().to_vec()) } else { - let asset_root = get_asset_root().unwrap_or_else(|| Path::new(".").to_path_buf()); + let asset_root = asset_root + .unwrap_or_else(|| get_asset_root().unwrap_or_else(|| Path::new(".").to_path_buf())); + let asset = asset_root.join(trimmed).canonicalize()?; if !asset.starts_with(asset_root) { From 56517ef5de3ce7998f4938d17958912db9e3009c Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Sat, 26 Feb 2022 17:35:23 -0500 Subject: [PATCH 7/8] fix: merge conflicts --- packages/core-macro/src/rsx/mod.rs | 4 ++-- packages/core/src/mutations.rs | 2 ++ packages/desktop/src/desktop_context.rs | 1 + 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/packages/core-macro/src/rsx/mod.rs b/packages/core-macro/src/rsx/mod.rs index e20d80dec..858f07c5c 100644 --- a/packages/core-macro/src/rsx/mod.rs +++ b/packages/core-macro/src/rsx/mod.rs @@ -79,7 +79,7 @@ impl ToTokens for CallBody { match &self.custom_context { // The `in cx` pattern allows directly rendering Some(ident) => out_tokens.append_all(quote! { - #ident.render(LazyNodes::new_some(move |__cx: NodeFactory| -> VNode { + #ident.render(LazyNodes::new(move |__cx: NodeFactory| -> VNode { use dioxus_elements::{GlobalAttributes, SvgAttributes}; #inner })) @@ -87,7 +87,7 @@ impl ToTokens for CallBody { // Otherwise we just build the LazyNode wrapper None => out_tokens.append_all(quote! { - LazyNodes::new_some(move |__cx: NodeFactory| -> VNode { + LazyNodes::new(move |__cx: NodeFactory| -> VNode { use dioxus_elements::{GlobalAttributes, SvgAttributes}; #inner }) diff --git a/packages/core/src/mutations.rs b/packages/core/src/mutations.rs index 1e84fe059..2d3a4505f 100644 --- a/packages/core/src/mutations.rs +++ b/packages/core/src/mutations.rs @@ -181,6 +181,8 @@ pub enum DomEdit<'bump> { /// The name of the attribute to remove. name: &'static str, + + /// The namespace of the attribute. ns: Option<&'bump str>, }, } diff --git a/packages/desktop/src/desktop_context.rs b/packages/desktop/src/desktop_context.rs index 1376110f1..eaff13750 100644 --- a/packages/desktop/src/desktop_context.rs +++ b/packages/desktop/src/desktop_context.rs @@ -125,6 +125,7 @@ use wry::application::window::Fullscreen as WryFullscreen; use crate::controller::DesktopController; +#[derive(Debug)] pub enum UserWindowEvent { Update, From f77f8c85000239460c8c67e67be3acf08ac8a400 Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Sat, 26 Feb 2022 17:38:05 -0500 Subject: [PATCH 8/8] fmt --- packages/core/src/events.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/core/src/events.rs b/packages/core/src/events.rs index e92ead8cc..1a9899e4a 100644 --- a/packages/core/src/events.rs +++ b/packages/core/src/events.rs @@ -139,7 +139,9 @@ impl AnyEvent { pub fn downcast(self) -> Option> { let AnyEvent { data, bubble_state } = self; - data.downcast::().ok().map(|data| UiEvent { bubble_state, data }) + data.downcast::() + .ok() + .map(|data| UiEvent { bubble_state, data }) } }