From 62df5f7097f5968455a0b25eebb0540d958d2fd3 Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Sun, 20 Feb 2022 17:37:46 -0500 Subject: [PATCH 1/5] 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/5] 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/5] 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 56517ef5de3ce7998f4938d17958912db9e3009c Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Sat, 26 Feb 2022 17:35:23 -0500 Subject: [PATCH 4/5] 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 5/5] 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 }) } }