mirror of
https://github.com/DioxusLabs/dioxus
synced 2024-11-10 22:54:12 +00:00
Merge branch 'master' into jk/coroutine-coroutineoverhaul
This commit is contained in:
commit
d512a6758f
22 changed files with 456 additions and 194 deletions
|
@ -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
|
||||
})
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -119,24 +119,41 @@ 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<BubbleState>,
|
||||
pub(crate) data: Arc<dyn Any + Send + Sync>,
|
||||
}
|
||||
|
||||
impl AnyEvent {
|
||||
/// Convert this AnyEvent into a specific UiEvent with EventData.
|
||||
///
|
||||
/// ```rust, ignore
|
||||
/// let evt: FormEvent = evvt.downcast().unwrap();
|
||||
/// ```
|
||||
pub fn downcast<T: Send + Sync + 'static>(self) -> Option<UiEvent<T>> {
|
||||
let AnyEvent { data, bubble_state } = self;
|
||||
|
||||
if let Ok(data) = data.downcast::<T>() {
|
||||
Some(UiEvent { bubble_state, data })
|
||||
} else {
|
||||
None
|
||||
}
|
||||
data.downcast::<T>()
|
||||
.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<T> generic type.
|
||||
///
|
||||
/// For the HTML crate, this would include [`MouseEvent`], [`FormEvent`] etc.
|
||||
pub struct UiEvent<T> {
|
||||
/// The internal data of the event
|
||||
/// This is wrapped in an Arc so that it can be sent across threads
|
||||
pub data: Arc<T>,
|
||||
|
||||
#[allow(unused)]
|
||||
|
|
|
@ -35,14 +35,32 @@ enum StackNodeStorage<'a, 'b> {
|
|||
}
|
||||
|
||||
impl<'a, 'b> LazyNodes<'a, 'b> {
|
||||
pub fn new_some<F>(_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<NodeFactory<'a>>| {
|
||||
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<F>(_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<NodeFactory<'a>>| {
|
||||
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<F>(val: F) -> Self
|
||||
where
|
||||
F: FnMut(Option<NodeFactory<'a>>) -> Option<VNode<'a>> + '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::<Vec<_>>();
|
||||
|
||||
LazyNodes::new_some(|f| {
|
||||
LazyNodes::new(|f| {
|
||||
log::debug!("main closure");
|
||||
f.fragment_from_iter(it)
|
||||
})
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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<DomEdit<'a>>,
|
||||
|
||||
/// The list of Scopes that were diffed, created, and removed during the Diff process.
|
||||
pub dirty_scopes: FxHashSet<ScopeId>,
|
||||
|
||||
/// The list of nodes to connect to the RealDOM.
|
||||
pub refs: Vec<NodeRefMutation<'a>>,
|
||||
}
|
||||
|
||||
|
@ -39,74 +44,145 @@ 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,
|
||||
|
||||
/// The namespace of the attribute.
|
||||
ns: Option<&'bump str>,
|
||||
},
|
||||
}
|
||||
|
|
|
@ -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<Option<ElementId>> {
|
|||
|
||||
/// A placeholder node only generated when Fragments don't have any children.
|
||||
pub struct VPlaceholder {
|
||||
/// The [`ElementId`] of the placeholder.
|
||||
pub id: Cell<Option<ElementId>>,
|
||||
}
|
||||
|
||||
/// A bump-allocated string slice and metadata.
|
||||
pub struct VText<'src> {
|
||||
pub text: &'src str,
|
||||
/// The [`ElementId`] of the VText.
|
||||
pub id: Cell<Option<ElementId>>,
|
||||
|
||||
/// 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<Option<ElementId>>,
|
||||
|
||||
/// 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<Option<ElementId>>,
|
||||
|
||||
/// 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<Option<ExternalListenerCallback<'bump, T>>>,
|
||||
}
|
||||
|
||||
|
@ -381,12 +428,23 @@ impl<T> 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<Option<ScopeId>>,
|
||||
|
||||
/// 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<Option<Box<dyn AnyProps + 'src>>>,
|
||||
}
|
||||
|
||||
|
@ -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<P>(
|
||||
&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<Item = impl IntoVNode<'a> + '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<Item = impl IntoVNode<'a> + '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<Item = impl IntoVNode<'a>>,
|
||||
|
@ -679,6 +744,7 @@ impl<'a> NodeFactory<'a> {
|
|||
}
|
||||
}
|
||||
|
||||
/// Create a new [`EventHandler`] from an [`FnMut`]
|
||||
pub fn event_handler<T>(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>;
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
|
||||
|
@ -750,8 +753,9 @@ impl ScopeState {
|
|||
self.push_future(fut);
|
||||
}
|
||||
|
||||
// todo: attach some state to the future to know if we should poll it
|
||||
pub fn cancel_future(&self, id: TaskId) {
|
||||
/// 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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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) => {
|
||||
|
|
|
@ -29,7 +29,9 @@ 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]
|
||||
default = ["tokio_runtime"]
|
||||
|
|
|
@ -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<String>,
|
||||
pub(crate) event_handler: Option<Box<DynEventHandlerFn>>,
|
||||
pub(crate) disable_context_menu: bool,
|
||||
pub(crate) resource_dir: Option<PathBuf>,
|
||||
}
|
||||
|
||||
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<PathBuf>) -> 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
|
||||
|
|
|
@ -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},
|
||||
|
|
|
@ -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<UserWindowEvent>;
|
||||
pub type ProxyType = EventLoopProxy<UserWindowEvent>;
|
||||
|
||||
/// Desktop-Window handle api context
|
||||
/// Get an imperative handle to the current window
|
||||
pub fn use_window(cx: &ScopeState) -> &Rc<DesktopContext> {
|
||||
cx.use_hook(|_| cx.consume_context::<DesktopContext>())
|
||||
.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::<DesktopContext>` to get this context
|
||||
///
|
||||
|
@ -19,7 +28,8 @@ type ProxyType = EventLoopProxy<UserWindowEvent>;
|
|||
/// ```
|
||||
#[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,74 @@ impl DesktopContext {
|
|||
}
|
||||
}
|
||||
|
||||
/// use this function can get the `DesktopContext` context.
|
||||
pub fn use_window(cx: &ScopeState) -> &Rc<DesktopContext> {
|
||||
cx.use_hook(|_| cx.consume_context::<DesktopContext>())
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
use wry::application::event_loop::ControlFlow;
|
||||
use wry::application::window::Fullscreen as WryFullscreen;
|
||||
|
||||
use crate::controller::DesktopController;
|
||||
|
||||
#[derive(Debug)]
|
||||
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(),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
|
@ -122,8 +123,11 @@ pub fn launch_with_props<P: 'static + Send>(
|
|||
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)
|
||||
|
@ -139,8 +143,7 @@ pub fn launch_with_props<P: 'static + Send>(
|
|||
}
|
||||
"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" => {
|
||||
let data = message.params();
|
||||
|
@ -160,7 +163,9 @@ pub fn launch_with_props<P: 'static + Send>(
|
|||
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()
|
||||
|
@ -213,7 +218,7 @@ pub fn launch_with_props<P: 'static + Send>(
|
|||
},
|
||||
|
||||
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 => {}
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
use std::path::Path;
|
||||
use std::path::{Path, PathBuf};
|
||||
use wry::{
|
||||
http::{status::StatusCode, Request, Response, ResponseBuilder},
|
||||
Result,
|
||||
};
|
||||
|
||||
pub(super) fn desktop_handler(request: &Request) -> Result<Response> {
|
||||
pub(super) fn desktop_handler(request: &Request, asset_root: Option<PathBuf>) -> Result<Response> {
|
||||
// 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,27 +21,62 @@ pub(super) fn desktop_handler(request: &Request) -> Result<Response> {
|
|||
.mimetype("text/javascript")
|
||||
.body(dioxus_interpreter_js::INTERPRETER_JS.as_bytes().to_vec())
|
||||
} else {
|
||||
let path_buf = Path::new(trimmed).canonicalize()?;
|
||||
let cur_path = Path::new(".").canonicalize()?;
|
||||
let asset_root = asset_root
|
||||
.unwrap_or_else(|| get_asset_root().unwrap_or_else(|| Path::new(".").to_path_buf()));
|
||||
|
||||
if !path_buf.starts_with(cur_path) {
|
||||
let asset = asset_root.join(trimmed).canonicalize()?;
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(unreachable_code)]
|
||||
fn get_asset_root() -> Option<PathBuf> {
|
||||
/*
|
||||
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 = 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);
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
|
|
@ -1,73 +0,0 @@
|
|||
use wry::application::event_loop::ControlFlow;
|
||||
use wry::application::window::Fullscreen as WryFullscreen;
|
||||
|
||||
use crate::controller::DesktopController;
|
||||
|
||||
#[derive(Debug)]
|
||||
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(),
|
||||
}
|
||||
}
|
|
@ -1,3 +1,6 @@
|
|||
// #![deny(missing_docs)]
|
||||
//! Useful foundational hooks for Dioxus
|
||||
|
||||
mod usestate;
|
||||
pub use usestate::{use_state, UseState};
|
||||
|
||||
|
|
|
@ -1,19 +1,30 @@
|
|||
#![allow(missing_docs)]
|
||||
use dioxus_core::{ScopeState, TaskId};
|
||||
use std::{any::Any, cell::Cell, future::Future, rc::Rc, sync::Arc};
|
||||
|
||||
/// A hook that provides a future that will resolve to a value.
|
||||
/// 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.
|
||||
///
|
||||
/// Whenever the hooks dependencies change, the future will be re-evaluated.
|
||||
/// If a future is pending when the dependencies change, the previous future
|
||||
/// will be allowed to continue
|
||||
///
|
||||
///
|
||||
/// - dependencies: a tuple of references to values that are PartialEq + Clone
|
||||
pub fn use_future<'a, T: 'static, F: Future<Output = T> + 'static, D: UseFutureDep>(
|
||||
cx: &'a ScopeState,
|
||||
pub fn use_future<T, F, D>(
|
||||
cx: &ScopeState,
|
||||
dependencies: D,
|
||||
future: impl FnOnce(D::Out) -> F,
|
||||
) -> &'a UseFuture<T> {
|
||||
) -> &UseFuture<T>
|
||||
where
|
||||
T: 'static,
|
||||
F: Future<Output = T> + 'static,
|
||||
D: UseFutureDep,
|
||||
{
|
||||
let state = cx.use_hook(move |_| UseFuture {
|
||||
update: cx.schedule_update(),
|
||||
needs_regen: Cell::new(true),
|
||||
|
@ -41,7 +52,7 @@ pub fn use_future<'a, T: 'static, F: Future<Output = T> + 'static, D: UseFutureD
|
|||
|
||||
// Cancel the current future
|
||||
if let Some(current) = state.task.take() {
|
||||
cx.cancel_future(current);
|
||||
cx.remove_future(current);
|
||||
}
|
||||
|
||||
state.task.set(Some(cx.push_future(async move {
|
||||
|
@ -54,6 +65,12 @@ pub fn use_future<'a, T: 'static, F: Future<Output = T> + 'static, D: UseFutureD
|
|||
state
|
||||
}
|
||||
|
||||
pub enum FutureState<'a, T> {
|
||||
Pending,
|
||||
Complete(&'a T),
|
||||
Regenerating(&'a T), // the old value
|
||||
}
|
||||
|
||||
pub struct UseFuture<T> {
|
||||
update: Arc<dyn Fn()>,
|
||||
needs_regen: Cell<bool>,
|
||||
|
@ -82,7 +99,7 @@ impl<T> UseFuture<T> {
|
|||
/// Forcefully cancel a future
|
||||
pub fn cancel(&self, cx: &ScopeState) {
|
||||
if let Some(task) = self.task.take() {
|
||||
cx.cancel_future(task);
|
||||
cx.remove_future(task);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -219,6 +236,8 @@ impl_dep!(A = a, B = b, C = c,);
|
|||
impl_dep!(A = a, B = b, C = c, D = d,);
|
||||
impl_dep!(A = a, B = b, C = c, D = d, E = e,);
|
||||
impl_dep!(A = a, B = b, C = c, D = d, E = e, F = f,);
|
||||
impl_dep!(A = a, B = b, C = c, D = d, E = e, F = f, G = g,);
|
||||
impl_dep!(A = a, B = b, C = c, D = d, E = e, F = f, G = g, H = h,);
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
|
|
|
@ -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
|
||||
})
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
#![deny(missing_docs)]
|
||||
|
||||
//! Dioxus WebSys
|
||||
//!
|
||||
//! ## Overview
|
||||
|
|
Loading…
Reference in a new issue