move creation functions into the structs they are creating and unify user and system events
This commit is contained in:
1 parent
a4a9aa5224
commit
d90c71c508
8 files changed
+110
-319
No files matched your search
@@ -21,9 +21,6 @@ pub struct ElementRef {
|
|||||||
// the pathway of the real element inside the template
|
// the pathway of the real element inside the template
|
||||||
pub(crate) path: ElementPath,
|
pub(crate) path: ElementPath,
|
||||||
|
|
||||||
// the scope that this element belongs to
|
|
||||||
pub(crate) scope: ScopeId,
|
|
||||||
|
|
||||||
// The actual element
|
// The actual element
|
||||||
pub(crate) element: VNode,
|
pub(crate) element: VNode,
|
||||||
}
|
}
|
||||||
@@ -59,7 +56,7 @@ impl VirtualDom {
|
|||||||
// Note: This will not remove any ids from the arena
|
// Note: This will not remove any ids from the arena
|
||||||
pub(crate) fn drop_scope(&mut self, id: ScopeId) {
|
pub(crate) fn drop_scope(&mut self, id: ScopeId) {
|
||||||
self.dirty_scopes.remove(&DirtyScope {
|
self.dirty_scopes.remove(&DirtyScope {
|
||||||
height: self.scopes[id.0].height(),
|
height: self.scopes[id.0].context().height,
|
||||||
id,
|
id,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -199,7 +199,6 @@ impl VirtualDom {
|
|||||||
path: template.template.get().node_paths[idx],
|
path: template.template.get().node_paths[idx],
|
||||||
},
|
},
|
||||||
element: template.clone(),
|
element: template.clone(),
|
||||||
scope: self.runtime.current_scope_id().unwrap_or(ScopeId(0)),
|
|
||||||
};
|
};
|
||||||
self.create_dynamic_node(&template_ref, node, to)
|
self.create_dynamic_node(&template_ref, node, to)
|
||||||
}
|
}
|
||||||
@@ -209,7 +208,6 @@ impl VirtualDom {
|
|||||||
path: template.template.get().node_paths[idx],
|
path: template.template.get().node_paths[idx],
|
||||||
},
|
},
|
||||||
element: template.clone(),
|
element: template.clone(),
|
||||||
scope: self.runtime.current_scope_id().unwrap_or(ScopeId(0)),
|
|
||||||
};
|
};
|
||||||
*parent.borrow_mut() = Some(template_ref);
|
*parent.borrow_mut() = Some(template_ref);
|
||||||
let id = self.set_slot(id);
|
let id = self.set_slot(id);
|
||||||
@@ -297,7 +295,6 @@ impl VirtualDom {
|
|||||||
path: template.template.get().node_paths[idx],
|
path: template.template.get().node_paths[idx],
|
||||||
},
|
},
|
||||||
element: template.clone(),
|
element: template.clone(),
|
||||||
scope: self.runtime.current_scope_id().unwrap_or(ScopeId(0)),
|
|
||||||
};
|
};
|
||||||
let m = self.create_dynamic_node(&boundary_ref, &template.dynamic_nodes[idx], to);
|
let m = self.create_dynamic_node(&boundary_ref, &template.dynamic_nodes[idx], to);
|
||||||
if m > 0 {
|
if m > 0 {
|
||||||
@@ -353,7 +350,6 @@ impl VirtualDom {
|
|||||||
let element_ref = ElementRef {
|
let element_ref = ElementRef {
|
||||||
path: ElementPath { path },
|
path: ElementPath { path },
|
||||||
element: template.clone(),
|
element: template.clone(),
|
||||||
scope: self.runtime.current_scope_id().unwrap_or(ScopeId(0)),
|
|
||||||
};
|
};
|
||||||
self.elements[id.0] = Some(element_ref);
|
self.elements[id.0] = Some(element_ref);
|
||||||
to.create_event_listener(&unbounded_name[2..], id);
|
to.create_event_listener(&unbounded_name[2..], id);
|
||||||
|
|||||||
@@ -142,7 +142,6 @@ impl VirtualDom {
|
|||||||
path: ElementPath {
|
path: ElementPath {
|
||||||
path: left_template.template.get().node_paths[dyn_node_idx],
|
path: left_template.template.get().node_paths[dyn_node_idx],
|
||||||
},
|
},
|
||||||
scope: self.runtime.scope_stack.borrow().last().copied().unwrap(),
|
|
||||||
};
|
};
|
||||||
self.diff_dynamic_node(left_node, right_node, ¤t_ref, to);
|
self.diff_dynamic_node(left_node, right_node, ¤t_ref, to);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
use crate::{runtime::with_runtime, ScopeId};
|
use crate::{runtime::with_runtime, scope_context::current_scope_id, ScopeId};
|
||||||
use std::{
|
use std::{
|
||||||
cell::{Cell, RefCell},
|
cell::{Cell, RefCell},
|
||||||
rc::Rc,
|
rc::Rc,
|
||||||
@@ -149,7 +149,18 @@ impl<T> Default for EventHandler<T> {
|
|||||||
|
|
||||||
type ExternalListenerCallback<T> = Box<dyn FnMut(T)>;
|
type ExternalListenerCallback<T> = Box<dyn FnMut(T)>;
|
||||||
|
|
||||||
impl<T> EventHandler< T> {
|
impl<T> EventHandler<T> {
|
||||||
|
/// Create a new [`EventHandler`] from an [`FnMut`]
|
||||||
|
pub fn new(mut f: impl FnMut(T) + 'static) -> EventHandler<T> {
|
||||||
|
let callback = RefCell::new(Some(Box::new(move |event: T| {
|
||||||
|
f(event);
|
||||||
|
}) as Box<dyn FnMut(T)>));
|
||||||
|
EventHandler {
|
||||||
|
callback,
|
||||||
|
origin: current_scope_id().expect("to be in a dioxus runtime"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Call this event handler with the appropriate event type
|
/// Call this event handler with the appropriate event type
|
||||||
///
|
///
|
||||||
/// This borrows the event using a RefCell. Recursively calling a listener will cause a panic.
|
/// This borrows the event using a RefCell. Recursively calling a listener will cause a panic.
|
||||||
|
|||||||
+79
-11
@@ -1,5 +1,6 @@
|
|||||||
use crate::any_props::BoxedAnyProps;
|
use crate::any_props::{BoxedAnyProps, VProps};
|
||||||
use crate::innerlude::ElementRef;
|
use crate::innerlude::{ElementRef, EventHandler};
|
||||||
|
use crate::Properties;
|
||||||
use crate::{arena::ElementId, Element, Event, ScopeId};
|
use crate::{arena::ElementId, Element, Event, ScopeId};
|
||||||
use std::ops::Deref;
|
use std::ops::Deref;
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
@@ -336,6 +337,13 @@ pub enum DynamicNode {
|
|||||||
Fragment(Vec<VNode>),
|
Fragment(Vec<VNode>),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl DynamicNode {
|
||||||
|
/// Convert any item that implements [`IntoDynNode`] into a [`DynamicNode`]
|
||||||
|
pub fn make_node<'c, I>(into: impl IntoDynNode<I> + 'c) -> DynamicNode {
|
||||||
|
into.into_dyn_node()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Default for DynamicNode {
|
impl Default for DynamicNode {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self::Placeholder(Default::default())
|
Self::Placeholder(Default::default())
|
||||||
@@ -360,6 +368,35 @@ pub struct VComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl VComponent {
|
impl VComponent {
|
||||||
|
/// Create a new [`VComponent`] variant
|
||||||
|
///
|
||||||
|
///
|
||||||
|
/// The given component can be any of four signatures. Remember that an [`Element`] is really a [`Result<VNode>`].
|
||||||
|
///
|
||||||
|
/// ```rust, ignore
|
||||||
|
/// // Without explicit props
|
||||||
|
/// fn(Scope) -> Element;
|
||||||
|
/// async fn(Scope<'_>) -> Element;
|
||||||
|
///
|
||||||
|
/// // With explicit props
|
||||||
|
/// fn(Scope<Props>) -> Element;
|
||||||
|
/// async fn(Scope<Props<'_>>) -> Element;
|
||||||
|
/// ```
|
||||||
|
pub fn new<P>(&self, component: fn(P) -> Element, props: P, fn_name: &'static str) -> Self
|
||||||
|
where
|
||||||
|
// The properties must be valid until the next bump frame
|
||||||
|
P: Properties,
|
||||||
|
{
|
||||||
|
let vcomp = VProps::new(component, P::memoize, props, fn_name);
|
||||||
|
|
||||||
|
VComponent {
|
||||||
|
name: fn_name,
|
||||||
|
render_fn: component as *const (),
|
||||||
|
props: BoxedAnyProps::new(vcomp),
|
||||||
|
scope: Default::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Get the scope that this component is mounted to
|
/// Get the scope that this component is mounted to
|
||||||
pub fn mounted_scope(&self) -> Option<ScopeId> {
|
pub fn mounted_scope(&self) -> Option<ScopeId> {
|
||||||
self.scope.get()
|
self.scope.get()
|
||||||
@@ -400,6 +437,12 @@ impl VText {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<Arguments<'_>> for VText {
|
||||||
|
fn from(args: Arguments) -> Self {
|
||||||
|
Self::new(args.to_string())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// A placeholder node, used by suspense and fragments
|
/// A placeholder node, used by suspense and fragments
|
||||||
#[derive(Clone, Debug, Default)]
|
#[derive(Clone, Debug, Default)]
|
||||||
pub struct VPlaceholder {
|
pub struct VPlaceholder {
|
||||||
@@ -471,19 +514,23 @@ pub struct Attribute {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Attribute {
|
impl Attribute {
|
||||||
/// Create a new attribute
|
/// Create a new [`Attribute`] from a name, value, namespace, and volatile bool
|
||||||
pub fn new(
|
///
|
||||||
|
/// "Volatile" referes to whether or not Dioxus should always override the value. This helps prevent the UI in
|
||||||
|
/// some renderers stay in sync with the VirtualDom's understanding of the world
|
||||||
|
pub fn attr(
|
||||||
|
&self,
|
||||||
name: &'static str,
|
name: &'static str,
|
||||||
value: AttributeValue,
|
value: impl IntoAttributeValue,
|
||||||
namespace: Option<&'static str>,
|
namespace: Option<&'static str>,
|
||||||
volatile: bool,
|
volatile: bool,
|
||||||
) -> Self {
|
) -> Attribute {
|
||||||
Self {
|
Attribute {
|
||||||
name,
|
name,
|
||||||
value,
|
|
||||||
namespace,
|
namespace,
|
||||||
volatile,
|
volatile,
|
||||||
mounted_element: Cell::new(ElementId::default()),
|
mounted_element: Default::default(),
|
||||||
|
value: value.into_value(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -511,7 +558,7 @@ pub enum AttributeValue {
|
|||||||
Bool(bool),
|
Bool(bool),
|
||||||
|
|
||||||
/// A listener, like "onclick"
|
/// A listener, like "onclick"
|
||||||
Listener(RefCell<ListenerCb>),
|
Listener(ListenerCb),
|
||||||
|
|
||||||
/// An arbitrary value that implements PartialEq and is static
|
/// An arbitrary value that implements PartialEq and is static
|
||||||
Any(Box<dyn AnyValue>),
|
Any(Box<dyn AnyValue>),
|
||||||
@@ -520,7 +567,28 @@ pub enum AttributeValue {
|
|||||||
None,
|
None,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub type ListenerCb = Box<dyn FnMut(Event<dyn Any>)>;
|
impl AttributeValue {
|
||||||
|
/// Create a new [`AttributeValue`] with the listener variant from a callback
|
||||||
|
///
|
||||||
|
/// The callback must be confined to the lifetime of the ScopeState
|
||||||
|
pub fn listener<T: 'static>(mut callback: impl FnMut(Event<T>) + 'static) -> AttributeValue {
|
||||||
|
AttributeValue::Listener(EventHandler::new(move |event: Event<dyn Any>| {
|
||||||
|
if let Ok(data) = event.data.downcast::<T>() {
|
||||||
|
callback(Event {
|
||||||
|
propagates: event.propagates,
|
||||||
|
data,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a new [`AttributeValue`] with a value that implements [`AnyValue`]
|
||||||
|
pub fn any_value<T: AnyValue>(&self, value: T) -> AttributeValue {
|
||||||
|
AttributeValue::Any(Box::new(value))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub type ListenerCb = EventHandler<Event<dyn Any>>;
|
||||||
|
|
||||||
impl std::fmt::Debug for AttributeValue {
|
impl std::fmt::Debug for AttributeValue {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
|||||||
@@ -301,6 +301,11 @@ impl ScopeContext {
|
|||||||
pub fn generation(&self) -> usize {
|
pub fn generation(&self) -> usize {
|
||||||
self.render_count.get()
|
self.render_count.get()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get the height of this scope
|
||||||
|
pub fn height(&self) -> u32 {
|
||||||
|
self.height
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Drop for ScopeContext {
|
impl Drop for ScopeContext {
|
||||||
|
|||||||
+3
-283
@@ -1,19 +1,7 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
any_props::{BoxedAnyProps, VProps},
|
any_props::BoxedAnyProps, nodes::RenderReturn, runtime::Runtime, scope_context::ScopeContext,
|
||||||
innerlude::{DynamicNode, EventHandler, VComponent, VText},
|
|
||||||
nodes::{IntoAttributeValue, IntoDynNode, RenderReturn},
|
|
||||||
runtime::Runtime,
|
|
||||||
scope_context::ScopeContext,
|
|
||||||
AnyValue, Attribute, AttributeValue, Element, Event, Properties, TaskId,
|
|
||||||
};
|
|
||||||
use std::{
|
|
||||||
any::Any,
|
|
||||||
cell::{Ref, RefCell},
|
|
||||||
fmt::{Arguments, Debug},
|
|
||||||
future::Future,
|
|
||||||
rc::Rc,
|
|
||||||
sync::Arc,
|
|
||||||
};
|
};
|
||||||
|
use std::{cell::Ref, fmt::Debug, rc::Rc};
|
||||||
|
|
||||||
/// A component's unique identifier.
|
/// A component's unique identifier.
|
||||||
///
|
///
|
||||||
@@ -57,16 +45,11 @@ impl Drop for ScopeState {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'src> ScopeState {
|
impl ScopeState {
|
||||||
pub(crate) fn context(&self) -> Ref<'_, ScopeContext> {
|
pub(crate) fn context(&self) -> Ref<'_, ScopeContext> {
|
||||||
self.runtime.get_context(self.context_id).unwrap()
|
self.runtime.get_context(self.context_id).unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the name of this component
|
|
||||||
pub fn name(&self) -> &str {
|
|
||||||
self.context().name
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get a handle to the currently active head node arena for this Scope
|
/// Get a handle to the currently active head node arena for this Scope
|
||||||
///
|
///
|
||||||
/// This is useful for traversing the tree outside of the VirtualDom, such as in a custom renderer or in SSR.
|
/// This is useful for traversing the tree outside of the VirtualDom, such as in a custom renderer or in SSR.
|
||||||
@@ -85,267 +68,4 @@ impl<'src> ScopeState {
|
|||||||
pub fn try_root_node(&self) -> Option<&RenderReturn> {
|
pub fn try_root_node(&self) -> Option<&RenderReturn> {
|
||||||
self.last_rendered_node.as_ref()
|
self.last_rendered_node.as_ref()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the height of this Scope - IE the number of scopes above it.
|
|
||||||
///
|
|
||||||
/// A Scope with a height of `0` is the root scope - there are no other scopes above it.
|
|
||||||
///
|
|
||||||
/// # Example
|
|
||||||
///
|
|
||||||
/// ```rust, ignore
|
|
||||||
/// let mut dom = VirtualDom::new(|cx| cx.render(rsx!{ div {} }));
|
|
||||||
/// dom.rebuild();
|
|
||||||
///
|
|
||||||
/// let base = dom.base_scope();
|
|
||||||
///
|
|
||||||
/// assert_eq!(base.height(), 0);
|
|
||||||
/// ```
|
|
||||||
pub fn height(&self) -> u32 {
|
|
||||||
self.context().height
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get the Parent of this [`Scope`] within this Dioxus [`crate::VirtualDom`].
|
|
||||||
///
|
|
||||||
/// This ID is not unique across Dioxus [`crate::VirtualDom`]s or across time. IDs will be reused when components are unmounted.
|
|
||||||
///
|
|
||||||
/// The base component will not have a parent, and will return `None`.
|
|
||||||
///
|
|
||||||
/// # Example
|
|
||||||
///
|
|
||||||
/// ```rust, ignore
|
|
||||||
/// let mut dom = VirtualDom::new(|cx| cx.render(rsx!{ div {} }));
|
|
||||||
/// dom.rebuild();
|
|
||||||
///
|
|
||||||
/// let base = dom.base_scope();
|
|
||||||
///
|
|
||||||
/// assert_eq!(base.parent(), None);
|
|
||||||
/// ```
|
|
||||||
pub fn parent(&self) -> Option<ScopeId> {
|
|
||||||
// safety: the pointer to our parent is *always* valid thanks to the bump arena
|
|
||||||
self.context().parent_id()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get the ID of this Scope within this Dioxus [`crate::VirtualDom`].
|
|
||||||
///
|
|
||||||
/// This ID is not unique across Dioxus [`crate::VirtualDom`]s or across time. IDs will be reused when components are unmounted.
|
|
||||||
///
|
|
||||||
/// # Example
|
|
||||||
///
|
|
||||||
/// ```rust, ignore
|
|
||||||
/// let mut dom = VirtualDom::new(|cx| cx.render(rsx!{ div {} }));
|
|
||||||
/// dom.rebuild();
|
|
||||||
/// let base = dom.base_scope();
|
|
||||||
///
|
|
||||||
/// assert_eq!(base.scope_id(), 0);
|
|
||||||
/// ```
|
|
||||||
pub fn scope_id(&self) -> ScopeId {
|
|
||||||
self.context().scope_id()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Create a subscription that schedules a future render for the reference component
|
|
||||||
///
|
|
||||||
/// ## Notice: you should prefer using [`Self::schedule_update_any`] and [`Self::scope_id`]
|
|
||||||
pub fn schedule_update(&self) -> Arc<dyn Fn() + Send + Sync + 'static> {
|
|
||||||
self.context().schedule_update()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Schedule an update for any component given its [`ScopeId`].
|
|
||||||
///
|
|
||||||
/// A component's [`ScopeId`] can be obtained from `use_hook` or the [`ScopeState::scope_id`] method.
|
|
||||||
///
|
|
||||||
/// This method should be used when you want to schedule an update for a component
|
|
||||||
pub fn schedule_update_any(&self) -> Arc<dyn Fn(ScopeId) + Send + Sync> {
|
|
||||||
self.context().schedule_update_any()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Mark this scope as dirty, and schedule a render for it.
|
|
||||||
pub fn needs_update(&self) {
|
|
||||||
self.context().needs_update()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get the [`ScopeId`] of a mounted component.
|
|
||||||
///
|
|
||||||
/// `ScopeId` is not unique for the lifetime of the [`crate::VirtualDom`] - a [`ScopeId`] will be reused if a component is unmounted.
|
|
||||||
pub fn needs_update_any(&self, id: ScopeId) {
|
|
||||||
self.context().needs_update_any(id)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Return any context of type T if it exists on this scope
|
|
||||||
pub fn has_context<T: 'static + Clone>(&self) -> Option<T> {
|
|
||||||
self.context().has_context()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Try to retrieve a shared state with type `T` from any parent scope.
|
|
||||||
///
|
|
||||||
/// Clones the state if it exists.
|
|
||||||
pub fn consume_context<T: 'static + Clone>(&self) -> Option<T> {
|
|
||||||
self.context().consume_context()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Expose state to children further down the [`crate::VirtualDom`] Tree. Requires `Clone` on the context to allow getting values down the tree.
|
|
||||||
///
|
|
||||||
/// This is a "fundamental" operation and should only be called during initialization of a hook.
|
|
||||||
///
|
|
||||||
/// For a hook that provides the same functionality, use `use_provide_context` and `use_context` instead.
|
|
||||||
///
|
|
||||||
/// # Example
|
|
||||||
///
|
|
||||||
/// ```rust, ignore
|
|
||||||
/// struct SharedState(&'static str);
|
|
||||||
///
|
|
||||||
/// static App: Component = |cx| {
|
|
||||||
/// cx.use_hook(|| cx.provide_context(SharedState("world")));
|
|
||||||
/// render!(Child {})
|
|
||||||
/// }
|
|
||||||
///
|
|
||||||
/// static Child: Component = |cx| {
|
|
||||||
/// let state = cx.consume_state::<SharedState>();
|
|
||||||
/// render!(div { "hello {state.0}" })
|
|
||||||
/// }
|
|
||||||
/// ```
|
|
||||||
pub fn provide_context<T: 'static + Clone>(&self, value: T) -> T {
|
|
||||||
self.context().provide_context(value)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Provide a context to the root and then consume it
|
|
||||||
///
|
|
||||||
/// This is intended for "global" state management solutions that would rather be implicit for the entire app.
|
|
||||||
/// Things like signal runtimes and routers are examples of "singletons" that would benefit from lazy initialization.
|
|
||||||
///
|
|
||||||
/// Note that you should be checking if the context existed before trying to provide a new one. Providing a context
|
|
||||||
/// when a context already exists will swap the context out for the new one, which may not be what you want.
|
|
||||||
pub fn provide_root_context<T: 'static + Clone>(&self, context: T) -> T {
|
|
||||||
self.context().provide_root_context(context)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Pushes the future onto the poll queue to be polled after the component renders.
|
|
||||||
pub fn push_future(&self, fut: impl Future<Output = ()> + 'static) -> TaskId {
|
|
||||||
self.context().push_future(fut)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Spawns the future but does not return the [`TaskId`]
|
|
||||||
pub fn spawn(&self, fut: impl Future<Output = ()> + 'static) {
|
|
||||||
self.context().spawn(fut);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Spawn a future that Dioxus won't clean up when this component is unmounted
|
|
||||||
///
|
|
||||||
/// This is good for tasks that need to be run after the component has been dropped.
|
|
||||||
pub fn spawn_forever(&self, fut: impl Future<Output = ()> + 'static) -> TaskId {
|
|
||||||
self.context().spawn_forever(fut)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Informs the scheduler that this task is no longer needed and should be removed.
|
|
||||||
///
|
|
||||||
/// This drops the task immediately.
|
|
||||||
pub fn remove_future(&self, id: TaskId) {
|
|
||||||
self.context().remove_future(id);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Create a dynamic text node using [`Arguments`] and the [`ScopeState`]'s internal [`Bump`] allocator
|
|
||||||
pub fn text_node(&'src self, args: Arguments) -> DynamicNode {
|
|
||||||
DynamicNode::Text(VText {
|
|
||||||
value: args.to_string(),
|
|
||||||
id: Default::default(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Convert any item that implements [`IntoDynNode`] into a [`DynamicNode`] using the internal [`Bump`] allocator
|
|
||||||
pub fn make_node<'c, I>(&'src self, into: impl IntoDynNode<I> + 'c) -> DynamicNode {
|
|
||||||
into.into_dyn_node()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Create a new [`Attribute`] from a name, value, namespace, and volatile bool
|
|
||||||
///
|
|
||||||
/// "Volatile" referes to whether or not Dioxus should always override the value. This helps prevent the UI in
|
|
||||||
/// some renderers stay in sync with the VirtualDom's understanding of the world
|
|
||||||
pub fn attr(
|
|
||||||
&'src self,
|
|
||||||
name: &'static str,
|
|
||||||
value: impl IntoAttributeValue,
|
|
||||||
namespace: Option<&'static str>,
|
|
||||||
volatile: bool,
|
|
||||||
) -> Attribute {
|
|
||||||
Attribute {
|
|
||||||
name,
|
|
||||||
namespace,
|
|
||||||
volatile,
|
|
||||||
mounted_element: Default::default(),
|
|
||||||
value: value.into_value(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Create a new [`DynamicNode::Component`] variant
|
|
||||||
///
|
|
||||||
///
|
|
||||||
/// The given component can be any of four signatures. Remember that an [`Element`] is really a [`Result<VNode>`].
|
|
||||||
///
|
|
||||||
/// ```rust, ignore
|
|
||||||
/// // Without explicit props
|
|
||||||
/// fn(Scope) -> Element;
|
|
||||||
/// async fn(Scope<'_>) -> Element;
|
|
||||||
///
|
|
||||||
/// // With explicit props
|
|
||||||
/// fn(Scope<Props>) -> Element;
|
|
||||||
/// async fn(Scope<Props<'_>>) -> Element;
|
|
||||||
/// ```
|
|
||||||
pub fn component<P>(
|
|
||||||
&self,
|
|
||||||
component: fn(P) -> Element,
|
|
||||||
props: P,
|
|
||||||
fn_name: &'static str,
|
|
||||||
) -> DynamicNode
|
|
||||||
where
|
|
||||||
// The properties must be valid until the next bump frame
|
|
||||||
P: Properties,
|
|
||||||
{
|
|
||||||
let vcomp = VProps::new(component, P::memoize, props, fn_name);
|
|
||||||
|
|
||||||
DynamicNode::Component(VComponent {
|
|
||||||
name: fn_name,
|
|
||||||
render_fn: component as *const (),
|
|
||||||
props: BoxedAnyProps::new(vcomp),
|
|
||||||
scope: Default::default(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Create a new [`EventHandler`] from an [`FnMut`]
|
|
||||||
pub fn event_handler<T>(&self, mut f: impl FnMut(T) + 'static) -> EventHandler<T> {
|
|
||||||
let callback = RefCell::new(Some(Box::new(move |event: T| {
|
|
||||||
f(event);
|
|
||||||
}) as Box<dyn FnMut(T)>));
|
|
||||||
EventHandler {
|
|
||||||
callback,
|
|
||||||
origin: self.context().id,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Create a new [`AttributeValue`] with the listener variant from a callback
|
|
||||||
///
|
|
||||||
/// The callback must be confined to the lifetime of the ScopeState
|
|
||||||
pub fn listener<T: 'static>(
|
|
||||||
&self,
|
|
||||||
mut callback: impl FnMut(Event<T>) + 'static,
|
|
||||||
) -> AttributeValue {
|
|
||||||
AttributeValue::Listener(RefCell::new(Box::new(move |event: Event<dyn Any>| {
|
|
||||||
if let Ok(data) = event.data.downcast::<T>() {
|
|
||||||
callback(Event {
|
|
||||||
propagates: event.propagates,
|
|
||||||
data,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
})))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Create a new [`AttributeValue`] with a value that implements [`AnyValue`]
|
|
||||||
pub fn any_value<T: AnyValue>(&'src self, value: T) -> AttributeValue {
|
|
||||||
AttributeValue::Any(Box::new(value))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Mark this component as suspended and then return None
|
|
||||||
pub fn suspend(&self) -> Option<Element> {
|
|
||||||
let cx = self.context();
|
|
||||||
cx.suspend();
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -276,10 +276,11 @@ impl VirtualDom {
|
|||||||
);
|
);
|
||||||
|
|
||||||
// Unlike react, we provide a default error boundary that just renders the error as a string
|
// Unlike react, we provide a default error boundary that just renders the error as a string
|
||||||
root.provide_context(Rc::new(ErrorBoundary::new_in_scope(
|
root.context()
|
||||||
ScopeId::ROOT,
|
.provide_context(Rc::new(ErrorBoundary::new_in_scope(
|
||||||
Arc::new(|_| {}),
|
ScopeId::ROOT,
|
||||||
)));
|
Arc::new(|_| {}),
|
||||||
|
)));
|
||||||
|
|
||||||
// the root element is always given element ID 0 since it's the container for the entire tree
|
// the root element is always given element ID 0 since it's the container for the entire tree
|
||||||
dom.elements.insert(None);
|
dom.elements.insert(None);
|
||||||
@@ -305,7 +306,7 @@ impl VirtualDom {
|
|||||||
///
|
///
|
||||||
/// This is useful for what is essentially dependency injection when building the app
|
/// This is useful for what is essentially dependency injection when building the app
|
||||||
pub fn with_root_context<T: Clone + 'static>(self, context: T) -> Self {
|
pub fn with_root_context<T: Clone + 'static>(self, context: T) -> Self {
|
||||||
self.base_scope().provide_context(context);
|
self.base_scope().context().provide_context(context);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -314,7 +315,7 @@ impl VirtualDom {
|
|||||||
/// Whenever the Runtime "works", it will re-render this scope
|
/// Whenever the Runtime "works", it will re-render this scope
|
||||||
pub fn mark_dirty(&mut self, id: ScopeId) {
|
pub fn mark_dirty(&mut self, id: ScopeId) {
|
||||||
if let Some(scope) = self.get_scope(id) {
|
if let Some(scope) = self.get_scope(id) {
|
||||||
let height = scope.height();
|
let height = scope.context().height();
|
||||||
tracing::trace!("Marking scope {:?} ({}) as dirty", id, scope.context().name);
|
tracing::trace!("Marking scope {:?} ({}) as dirty", id, scope.context().name);
|
||||||
self.dirty_scopes.insert(DirtyScope { height, id });
|
self.dirty_scopes.insert(DirtyScope { height, id });
|
||||||
}
|
}
|
||||||
@@ -403,11 +404,8 @@ impl VirtualDom {
|
|||||||
// We check the bubble state between each call to see if the event has been stopped from bubbling
|
// We check the bubble state between each call to see if the event has been stopped from bubbling
|
||||||
for listener in listeners.into_iter().rev() {
|
for listener in listeners.into_iter().rev() {
|
||||||
if let AttributeValue::Listener(listener) = listener {
|
if let AttributeValue::Listener(listener) = listener {
|
||||||
let origin = path.scope;
|
|
||||||
self.runtime.scope_stack.borrow_mut().push(origin);
|
|
||||||
self.runtime.rendering.set(false);
|
self.runtime.rendering.set(false);
|
||||||
(listener.borrow_mut())(uievent.clone());
|
listener.call(uievent.clone());
|
||||||
self.runtime.scope_stack.borrow_mut().pop();
|
|
||||||
self.runtime.rendering.set(true);
|
self.runtime.rendering.set(true);
|
||||||
|
|
||||||
if !uievent.propagates.get() {
|
if !uievent.propagates.get() {
|
||||||
@@ -432,11 +430,8 @@ impl VirtualDom {
|
|||||||
// Only call the listener if this is the exact target element.
|
// Only call the listener if this is the exact target element.
|
||||||
if attr.name.trim_start_matches("on") == name && target_path == this_path {
|
if attr.name.trim_start_matches("on") == name && target_path == this_path {
|
||||||
if let AttributeValue::Listener(listener) = &attr.value {
|
if let AttributeValue::Listener(listener) = &attr.value {
|
||||||
let origin = path.scope;
|
|
||||||
self.runtime.scope_stack.borrow_mut().push(origin);
|
|
||||||
self.runtime.rendering.set(false);
|
self.runtime.rendering.set(false);
|
||||||
(listener.borrow_mut())(uievent.clone());
|
listener.call(uievent.clone());
|
||||||
self.runtime.scope_stack.borrow_mut().pop();
|
|
||||||
self.runtime.rendering.set(true);
|
self.runtime.rendering.set(true);
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
|||||||
Reference in new issue
Block a user