feat: bubbling
This commit is contained in:
1 parent
9bd56ee499
commit
19df1bda10
12 files changed
+398
-292
No files matched your search
+14
-3
@@ -2,6 +2,8 @@
|
|||||||
//!
|
//!
|
||||||
//! The example from the README.md.
|
//! The example from the README.md.
|
||||||
|
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
use dioxus::prelude::*;
|
use dioxus::prelude::*;
|
||||||
fn main() {
|
fn main() {
|
||||||
dioxus::desktop::launch(App, |c| c);
|
dioxus::desktop::launch(App, |c| c);
|
||||||
@@ -10,14 +12,23 @@ fn main() {
|
|||||||
static App: FC<()> = |cx, props| {
|
static App: FC<()> = |cx, props| {
|
||||||
let mut count = use_state(cx, || 0);
|
let mut count = use_state(cx, || 0);
|
||||||
|
|
||||||
cx.push_task(async {
|
cx.push_task(async move {
|
||||||
panic!("polled future");
|
tokio::time::sleep(Duration::from_millis(100)).await;
|
||||||
//
|
println!("setting count");
|
||||||
|
count += 1;
|
||||||
|
// count.set(10);
|
||||||
|
// *count += 1;
|
||||||
|
// let c = count.get() + 1;
|
||||||
|
// count.set(c);
|
||||||
});
|
});
|
||||||
|
|
||||||
cx.render(rsx! {
|
cx.render(rsx! {
|
||||||
div {
|
div {
|
||||||
h1 { "High-Five counter: {count}" }
|
h1 { "High-Five counter: {count}" }
|
||||||
|
// button {
|
||||||
|
// onclick: move |_| count +=1 ,
|
||||||
|
// "Click me!"
|
||||||
|
// }
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
};
|
};
|
||||||
@@ -1344,13 +1344,14 @@ impl<'bump> DiffState<'bump> {
|
|||||||
.current_scope()
|
.current_scope()
|
||||||
.and_then(|id| self.scopes.get_scope(&id))
|
.and_then(|id| self.scopes.get_scope(&id))
|
||||||
{
|
{
|
||||||
// safety: this lifetime is managed by the logic on scope
|
todo!()
|
||||||
let extended = unsafe { std::mem::transmute(suspended) };
|
// // safety: this lifetime is managed by the logic on scope
|
||||||
scope
|
// let extended = unsafe { std::mem::transmute(suspended) };
|
||||||
.items
|
// scope
|
||||||
.borrow_mut()
|
// .items
|
||||||
.suspended_nodes
|
// .borrow_mut()
|
||||||
.insert(suspended.task_id, extended);
|
// .suspended_nodes
|
||||||
|
// .insert(suspended.task_id, extended);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+87
-102
@@ -5,28 +5,111 @@
|
|||||||
use crate::innerlude::*;
|
use crate::innerlude::*;
|
||||||
use std::{any::Any, fmt::Debug};
|
use std::{any::Any, fmt::Debug};
|
||||||
|
|
||||||
|
/// ## Mutations
|
||||||
|
///
|
||||||
|
/// This method returns "mutations" - IE the necessary changes to get the RealDOM to match the VirtualDOM. It also
|
||||||
|
/// includes a list of NodeRefs that need to be applied and effects that need to be triggered after the RealDOM has
|
||||||
|
/// applied the edits.
|
||||||
|
///
|
||||||
|
/// Mutations are the only link between the RealDOM and the VirtualDOM.
|
||||||
pub struct Mutations<'a> {
|
pub struct Mutations<'a> {
|
||||||
pub edits: Vec<DomEdit<'a>>,
|
pub edits: Vec<DomEdit<'a>>,
|
||||||
pub noderefs: Vec<NodeRefMutation<'a>>,
|
pub refs: Vec<NodeRefMutation<'a>>,
|
||||||
pub effects: Vec<&'a dyn FnMut()>,
|
pub effects: Vec<&'a dyn FnMut()>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Debug for Mutations<'_> {
|
impl Debug for Mutations<'_> {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
f.debug_struct("Mutations")
|
f.debug_struct("Mutations")
|
||||||
.field("edits", &self.edits)
|
.field("edits", &self.edits)
|
||||||
.field("noderefs", &self.noderefs)
|
.field("noderefs", &self.refs)
|
||||||
// .field("effects", &self.effects)
|
|
||||||
.finish()
|
.finish()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A `DomEdit` represents a serialized form of the VirtualDom's trait-based API. This allows streaming edits across the
|
||||||
|
/// network or through FFI boundaries.
|
||||||
|
#[derive(Debug, PartialEq)]
|
||||||
|
#[cfg_attr(
|
||||||
|
feature = "serialize",
|
||||||
|
derive(serde::Serialize, serde::Deserialize),
|
||||||
|
serde(tag = "type")
|
||||||
|
)]
|
||||||
|
pub enum DomEdit<'bump> {
|
||||||
|
PushRoot {
|
||||||
|
root: u64,
|
||||||
|
},
|
||||||
|
PopRoot,
|
||||||
|
|
||||||
|
AppendChildren {
|
||||||
|
many: u32,
|
||||||
|
},
|
||||||
|
|
||||||
|
// "Root" refers to the item directly
|
||||||
|
// it's a waste of an instruction to push the root directly
|
||||||
|
ReplaceWith {
|
||||||
|
root: u64,
|
||||||
|
m: u32,
|
||||||
|
},
|
||||||
|
InsertAfter {
|
||||||
|
root: u64,
|
||||||
|
n: u32,
|
||||||
|
},
|
||||||
|
InsertBefore {
|
||||||
|
root: u64,
|
||||||
|
n: u32,
|
||||||
|
},
|
||||||
|
Remove {
|
||||||
|
root: u64,
|
||||||
|
},
|
||||||
|
CreateTextNode {
|
||||||
|
text: &'bump str,
|
||||||
|
root: u64,
|
||||||
|
},
|
||||||
|
CreateElement {
|
||||||
|
tag: &'bump str,
|
||||||
|
root: u64,
|
||||||
|
},
|
||||||
|
CreateElementNs {
|
||||||
|
tag: &'bump str,
|
||||||
|
root: u64,
|
||||||
|
ns: &'static str,
|
||||||
|
},
|
||||||
|
CreatePlaceholder {
|
||||||
|
root: u64,
|
||||||
|
},
|
||||||
|
NewEventListener {
|
||||||
|
event_name: &'static str,
|
||||||
|
scope: ScopeId,
|
||||||
|
root: u64,
|
||||||
|
},
|
||||||
|
RemoveEventListener {
|
||||||
|
root: u64,
|
||||||
|
event: &'static str,
|
||||||
|
},
|
||||||
|
SetText {
|
||||||
|
root: u64,
|
||||||
|
text: &'bump str,
|
||||||
|
},
|
||||||
|
SetAttribute {
|
||||||
|
root: u64,
|
||||||
|
field: &'static str,
|
||||||
|
value: &'bump str,
|
||||||
|
ns: Option<&'bump str>,
|
||||||
|
},
|
||||||
|
RemoveAttribute {
|
||||||
|
root: u64,
|
||||||
|
name: &'static str,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
use DomEdit::*;
|
use DomEdit::*;
|
||||||
|
|
||||||
impl<'a> Mutations<'a> {
|
impl<'a> Mutations<'a> {
|
||||||
pub(crate) fn new() -> Self {
|
pub(crate) fn new() -> Self {
|
||||||
Self {
|
Self {
|
||||||
edits: Vec::new(),
|
edits: Vec::new(),
|
||||||
noderefs: Vec::new(),
|
refs: Vec::new(),
|
||||||
effects: Vec::new(),
|
effects: Vec::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -156,101 +239,3 @@ impl<'a> NodeRefMutation<'a> {
|
|||||||
.and_then(|f| f.downcast_mut::<T>())
|
.and_then(|f| f.downcast_mut::<T>())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A `DomEdit` represents a serialized form of the VirtualDom's trait-based API. This allows streaming edits across the
|
|
||||||
/// network or through FFI boundaries.
|
|
||||||
#[derive(Debug, PartialEq)]
|
|
||||||
#[cfg_attr(
|
|
||||||
feature = "serialize",
|
|
||||||
derive(serde::Serialize, serde::Deserialize),
|
|
||||||
serde(tag = "type")
|
|
||||||
)]
|
|
||||||
pub enum DomEdit<'bump> {
|
|
||||||
PushRoot {
|
|
||||||
root: u64,
|
|
||||||
},
|
|
||||||
PopRoot,
|
|
||||||
|
|
||||||
AppendChildren {
|
|
||||||
many: u32,
|
|
||||||
},
|
|
||||||
|
|
||||||
// "Root" refers to the item directly
|
|
||||||
// it's a waste of an instruction to push the root directly
|
|
||||||
ReplaceWith {
|
|
||||||
root: u64,
|
|
||||||
m: u32,
|
|
||||||
},
|
|
||||||
InsertAfter {
|
|
||||||
root: u64,
|
|
||||||
n: u32,
|
|
||||||
},
|
|
||||||
InsertBefore {
|
|
||||||
root: u64,
|
|
||||||
n: u32,
|
|
||||||
},
|
|
||||||
Remove {
|
|
||||||
root: u64,
|
|
||||||
},
|
|
||||||
CreateTextNode {
|
|
||||||
text: &'bump str,
|
|
||||||
root: u64,
|
|
||||||
},
|
|
||||||
CreateElement {
|
|
||||||
tag: &'bump str,
|
|
||||||
root: u64,
|
|
||||||
},
|
|
||||||
CreateElementNs {
|
|
||||||
tag: &'bump str,
|
|
||||||
root: u64,
|
|
||||||
ns: &'static str,
|
|
||||||
},
|
|
||||||
CreatePlaceholder {
|
|
||||||
root: u64,
|
|
||||||
},
|
|
||||||
NewEventListener {
|
|
||||||
event_name: &'static str,
|
|
||||||
scope: ScopeId,
|
|
||||||
root: u64,
|
|
||||||
},
|
|
||||||
RemoveEventListener {
|
|
||||||
root: u64,
|
|
||||||
event: &'static str,
|
|
||||||
},
|
|
||||||
SetText {
|
|
||||||
root: u64,
|
|
||||||
text: &'bump str,
|
|
||||||
},
|
|
||||||
SetAttribute {
|
|
||||||
root: u64,
|
|
||||||
field: &'static str,
|
|
||||||
value: &'bump str,
|
|
||||||
ns: Option<&'bump str>,
|
|
||||||
},
|
|
||||||
RemoveAttribute {
|
|
||||||
root: u64,
|
|
||||||
name: &'static str,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
impl DomEdit<'_> {
|
|
||||||
pub fn is(&self, id: &'static str) -> bool {
|
|
||||||
match self {
|
|
||||||
DomEdit::InsertAfter { .. } => id == "InsertAfter",
|
|
||||||
DomEdit::InsertBefore { .. } => id == "InsertBefore",
|
|
||||||
DomEdit::PushRoot { .. } => id == "PushRoot",
|
|
||||||
DomEdit::PopRoot => id == "PopRoot",
|
|
||||||
DomEdit::AppendChildren { .. } => id == "AppendChildren",
|
|
||||||
DomEdit::ReplaceWith { .. } => id == "ReplaceWith",
|
|
||||||
DomEdit::Remove { .. } => id == "Remove",
|
|
||||||
DomEdit::CreateTextNode { .. } => id == "CreateTextNode",
|
|
||||||
DomEdit::CreateElement { .. } => id == "CreateElement",
|
|
||||||
DomEdit::CreateElementNs { .. } => id == "CreateElementNs",
|
|
||||||
DomEdit::CreatePlaceholder { .. } => id == "CreatePlaceholder",
|
|
||||||
DomEdit::NewEventListener { .. } => id == "NewEventListener",
|
|
||||||
DomEdit::RemoveEventListener { .. } => id == "RemoveEventListener",
|
|
||||||
DomEdit::SetText { .. } => id == "SetText",
|
|
||||||
DomEdit::SetAttribute { .. } => id == "SetAttribute",
|
|
||||||
DomEdit::RemoveAttribute { .. } => id == "RemoveAttribute",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -105,7 +105,7 @@ pub enum VNode<'src> {
|
|||||||
///
|
///
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
Suspended(&'src VSuspended<'src>),
|
Suspended(&'src VSuspended),
|
||||||
|
|
||||||
/// Anchors are a type of placeholder VNode used when fragments don't contain any children.
|
/// Anchors are a type of placeholder VNode used when fragments don't contain any children.
|
||||||
///
|
///
|
||||||
@@ -387,12 +387,11 @@ pub struct VComponent<'src> {
|
|||||||
pub(crate) drop_props: RefCell<Option<BumpBox<'src, dyn FnMut()>>>,
|
pub(crate) drop_props: RefCell<Option<BumpBox<'src, dyn FnMut()>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct VSuspended<'a> {
|
pub struct VSuspended {
|
||||||
pub task_id: u64,
|
pub task_id: usize,
|
||||||
|
pub scope: Cell<Option<ScopeId>>,
|
||||||
pub dom_id: Cell<Option<ElementId>>,
|
pub dom_id: Cell<Option<ElementId>>,
|
||||||
|
pub parent: Cell<Option<ElementId>>,
|
||||||
#[allow(clippy::type_complexity)]
|
|
||||||
pub callback: RefCell<Option<BumpBox<'a, dyn FnMut() -> Element + 'a>>>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A cached node is a "pointer" to a "rendered" node in a particular scope
|
/// A cached node is a "pointer" to a "rendered" node in a particular scope
|
||||||
|
|||||||
+122
-111
@@ -72,7 +72,7 @@ pub struct Scope {
|
|||||||
pub struct SelfReferentialItems<'a> {
|
pub struct SelfReferentialItems<'a> {
|
||||||
pub(crate) listeners: Vec<&'a Listener<'a>>,
|
pub(crate) listeners: Vec<&'a Listener<'a>>,
|
||||||
pub(crate) borrowed_props: Vec<&'a VComponent<'a>>,
|
pub(crate) borrowed_props: Vec<&'a VComponent<'a>>,
|
||||||
pub(crate) suspended_nodes: FxHashMap<u64, &'a VSuspended<'a>>,
|
pub(crate) suspended_nodes: FxHashMap<u64, &'a VSuspended>,
|
||||||
pub(crate) tasks: Vec<BumpBox<'a, dyn Future<Output = ()>>>,
|
pub(crate) tasks: Vec<BumpBox<'a, dyn Future<Output = ()>>>,
|
||||||
pub(crate) pending_effects: Vec<BumpBox<'a, dyn FnMut()>>,
|
pub(crate) pending_effects: Vec<BumpBox<'a, dyn FnMut()>>,
|
||||||
}
|
}
|
||||||
@@ -107,6 +107,32 @@ impl Scope {
|
|||||||
self.subtree.get()
|
self.subtree.get()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Create a new subtree with this scope as the root of the subtree.
|
||||||
|
///
|
||||||
|
/// Each component has its own subtree ID - the root subtree has an ID of 0. This ID is used by the renderer to route
|
||||||
|
/// the mutations to the correct window/portal/subtree.
|
||||||
|
///
|
||||||
|
/// This method
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
///
|
||||||
|
/// ```rust, ignore
|
||||||
|
/// fn App(cx: Context, props: &()) -> Element {
|
||||||
|
/// todo!();
|
||||||
|
/// rsx!(cx, div { "Subtree {id}"})
|
||||||
|
/// };
|
||||||
|
/// ```
|
||||||
|
pub fn create_subtree(&self) -> Option<u32> {
|
||||||
|
if self.is_subtree_root.get() {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
todo!()
|
||||||
|
// let cur = self.subtree().get();
|
||||||
|
// self.shared.cur_subtree.set(cur + 1);
|
||||||
|
// Some(cur)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Get the height of this Scope - IE the number of scopes above it.
|
/// 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.
|
/// A Scope with a height of `0` is the root scope - there are no other scopes above it.
|
||||||
@@ -207,75 +233,6 @@ impl Scope {
|
|||||||
&self.wip_frame().bump
|
&self.wip_frame().bump
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Take a lazy VNode structure and actually build it with the context of the VDom's efficient VNode allocator.
|
|
||||||
///
|
|
||||||
/// This function consumes the context and absorb the lifetime, so these VNodes *must* be returned.
|
|
||||||
///
|
|
||||||
/// ## Example
|
|
||||||
///
|
|
||||||
/// ```ignore
|
|
||||||
/// fn Component(cx: Scope, props: &Props) -> Element {
|
|
||||||
/// // Lazy assemble the VNode tree
|
|
||||||
/// let lazy_nodes = rsx!("hello world");
|
|
||||||
///
|
|
||||||
/// // Actually build the tree and allocate it
|
|
||||||
/// cx.render(lazy_tree)
|
|
||||||
/// }
|
|
||||||
///```
|
|
||||||
pub fn render<'src>(&'src self, lazy_nodes: Option<LazyNodes<'src, '_>>) -> Option<NodeLink> {
|
|
||||||
let frame = self.wip_frame();
|
|
||||||
let bump = &frame.bump;
|
|
||||||
let factory = NodeFactory { bump };
|
|
||||||
let node = lazy_nodes.map(|f| f.call(factory))?;
|
|
||||||
let node = bump.alloc(node);
|
|
||||||
|
|
||||||
let node_ptr = node as *mut _;
|
|
||||||
let node_ptr = unsafe { std::mem::transmute(node_ptr) };
|
|
||||||
|
|
||||||
let link = NodeLink {
|
|
||||||
scope_id: Cell::new(Some(self.our_arena_idx)),
|
|
||||||
link_idx: Cell::new(0),
|
|
||||||
node: node_ptr,
|
|
||||||
};
|
|
||||||
|
|
||||||
Some(link)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Push an effect to be ran after the component has been successfully mounted to the dom
|
|
||||||
/// Returns the effect's position in the stack
|
|
||||||
pub fn push_effect<'src>(&'src self, effect: impl FnOnce() + 'src) -> usize {
|
|
||||||
// this is some tricker to get around not being able to actually call fnonces
|
|
||||||
let mut slot = Some(effect);
|
|
||||||
let fut: &mut dyn FnMut() = self.bump().alloc(move || slot.take().unwrap()());
|
|
||||||
|
|
||||||
// wrap it in a type that will actually drop the contents
|
|
||||||
let boxed_fut = unsafe { BumpBox::from_raw(fut) };
|
|
||||||
|
|
||||||
// erase the 'src lifetime for self-referential storage
|
|
||||||
let self_ref_fut = unsafe { std::mem::transmute(boxed_fut) };
|
|
||||||
|
|
||||||
let mut items = self.items.borrow_mut();
|
|
||||||
items.pending_effects.push(self_ref_fut);
|
|
||||||
items.pending_effects.len() - 1
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Pushes the future onto the poll queue to be polled
|
|
||||||
/// The future is forcibly dropped if the component is not ready by the next render
|
|
||||||
pub fn push_task<'src>(&'src self, fut: impl Future<Output = ()> + 'src) -> usize {
|
|
||||||
// allocate the future
|
|
||||||
let fut: &mut dyn Future<Output = ()> = self.bump().alloc(fut);
|
|
||||||
|
|
||||||
// wrap it in a type that will actually drop the contents
|
|
||||||
let boxed_fut: BumpBox<dyn Future<Output = ()>> = unsafe { BumpBox::from_raw(fut) };
|
|
||||||
|
|
||||||
// erase the 'src lifetime for self-referential storage
|
|
||||||
let self_ref_fut = unsafe { std::mem::transmute(boxed_fut) };
|
|
||||||
|
|
||||||
let mut items = self.items.borrow_mut();
|
|
||||||
items.tasks.push(self_ref_fut);
|
|
||||||
items.tasks.len() - 1
|
|
||||||
}
|
|
||||||
|
|
||||||
/// This method enables the ability to expose state to children further down the VirtualDOM Tree.
|
/// This method enables the ability to expose state to children further down the VirtualDOM Tree.
|
||||||
///
|
///
|
||||||
/// This is a "fundamental" operation and should only be called during initialization of a hook.
|
/// This is a "fundamental" operation and should only be called during initialization of a hook.
|
||||||
@@ -326,47 +283,102 @@ impl Scope {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create a new subtree with this scope as the root of the subtree.
|
/// Push an effect to be ran after the component has been successfully mounted to the dom
|
||||||
///
|
/// Returns the effect's position in the stack
|
||||||
/// Each component has its own subtree ID - the root subtree has an ID of 0. This ID is used by the renderer to route
|
pub fn push_effect<'src>(&'src self, effect: impl FnOnce() + 'src) -> usize {
|
||||||
/// the mutations to the correct window/portal/subtree.
|
// this is some tricker to get around not being able to actually call fnonces
|
||||||
///
|
let mut slot = Some(effect);
|
||||||
/// This method
|
let fut: &mut dyn FnMut() = self.bump().alloc(move || slot.take().unwrap()());
|
||||||
///
|
|
||||||
/// # Example
|
// wrap it in a type that will actually drop the contents
|
||||||
///
|
let boxed_fut = unsafe { BumpBox::from_raw(fut) };
|
||||||
/// ```rust, ignore
|
|
||||||
/// fn App(cx: Context, props: &()) -> Element {
|
// erase the 'src lifetime for self-referential storage
|
||||||
/// todo!();
|
let self_ref_fut = unsafe { std::mem::transmute(boxed_fut) };
|
||||||
/// rsx!(cx, div { "Subtree {id}"})
|
|
||||||
/// };
|
let mut items = self.items.borrow_mut();
|
||||||
/// ```
|
items.pending_effects.push(self_ref_fut);
|
||||||
pub fn create_subtree(&self) -> Option<u32> {
|
items.pending_effects.len() - 1
|
||||||
if self.is_subtree_root.get() {
|
|
||||||
None
|
|
||||||
} else {
|
|
||||||
todo!()
|
|
||||||
// let cur = self.subtree().get();
|
|
||||||
// self.shared.cur_subtree.set(cur + 1);
|
|
||||||
// Some(cur)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the subtree ID that this scope belongs to.
|
/// Pushes the future onto the poll queue to be polled
|
||||||
|
/// The future is forcibly dropped if the component is not ready by the next render
|
||||||
|
pub fn push_task<'src, F: Future<Output = ()> + 'src>(
|
||||||
|
&'src self,
|
||||||
|
mut fut: impl FnOnce() -> F + 'src,
|
||||||
|
) -> usize {
|
||||||
|
// allocate the future
|
||||||
|
let fut = fut();
|
||||||
|
let fut: &mut dyn Future<Output = ()> = self.bump().alloc(fut);
|
||||||
|
|
||||||
|
// wrap it in a type that will actually drop the contents
|
||||||
|
let boxed_fut: BumpBox<dyn Future<Output = ()>> = unsafe { BumpBox::from_raw(fut) };
|
||||||
|
|
||||||
|
// erase the 'src lifetime for self-referential storage
|
||||||
|
let self_ref_fut = unsafe { std::mem::transmute(boxed_fut) };
|
||||||
|
|
||||||
|
let mut items = self.items.borrow_mut();
|
||||||
|
items.tasks.push(self_ref_fut);
|
||||||
|
items.tasks.len() - 1
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Take a lazy VNode structure and actually build it with the context of the VDom's efficient VNode allocator.
|
||||||
///
|
///
|
||||||
/// Each component has its own subtree ID - the root subtree has an ID of 0. This ID is used by the renderer to route
|
/// This function consumes the context and absorb the lifetime, so these VNodes *must* be returned.
|
||||||
/// the mutations to the correct window/portal/subtree.
|
|
||||||
///
|
///
|
||||||
/// # Example
|
/// ## Example
|
||||||
///
|
///
|
||||||
/// ```rust, ignore
|
/// ```ignore
|
||||||
/// fn App(cx: Context, props: &()) -> Element {
|
/// fn Component(cx: Scope, props: &Props) -> Element {
|
||||||
/// let id = cx.get_current_subtree();
|
/// // Lazy assemble the VNode tree
|
||||||
/// rsx!(cx, div { "Subtree {id}"})
|
/// let lazy_nodes = rsx!("hello world");
|
||||||
/// };
|
///
|
||||||
/// ```
|
/// // Actually build the tree and allocate it
|
||||||
pub fn get_current_subtree(&self) -> u32 {
|
/// cx.render(lazy_tree)
|
||||||
self.subtree()
|
/// }
|
||||||
|
///```
|
||||||
|
pub fn render<'src>(&'src self, lazy_nodes: Option<LazyNodes<'src, '_>>) -> Option<NodeLink> {
|
||||||
|
let frame = self.wip_frame();
|
||||||
|
let bump = &frame.bump;
|
||||||
|
let factory = NodeFactory { bump };
|
||||||
|
let node = lazy_nodes.map(|f| f.call(factory))?;
|
||||||
|
let node = bump.alloc(node);
|
||||||
|
|
||||||
|
let node_ptr = node as *mut _;
|
||||||
|
let node_ptr = unsafe { std::mem::transmute(node_ptr) };
|
||||||
|
|
||||||
|
let link = NodeLink {
|
||||||
|
scope_id: Cell::new(Some(self.our_arena_idx)),
|
||||||
|
link_idx: Cell::new(0),
|
||||||
|
node: node_ptr,
|
||||||
|
};
|
||||||
|
|
||||||
|
Some(link)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn suspend<'src, F: Future<Output = Element> + 'src>(
|
||||||
|
&'src self,
|
||||||
|
mut fut: impl FnMut() -> F,
|
||||||
|
) -> Option<VNode> {
|
||||||
|
let channel = self.sender.clone();
|
||||||
|
let node_fut = fut();
|
||||||
|
|
||||||
|
let scope = self.scope_id();
|
||||||
|
|
||||||
|
// self.push_task(move || {
|
||||||
|
//
|
||||||
|
// async move {
|
||||||
|
// //
|
||||||
|
// let r = node_fut.await;
|
||||||
|
// if let Some(node) = r {
|
||||||
|
// channel
|
||||||
|
// .unbounded_send(SchedulerMsg::Suspended { node, scope })
|
||||||
|
// .unwrap();
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// });
|
||||||
|
|
||||||
|
todo!()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Store a value between renders
|
/// Store a value between renders
|
||||||
@@ -455,10 +467,9 @@ impl Scope {
|
|||||||
let mut nodes = &mut self.items.get_mut().suspended_nodes;
|
let mut nodes = &mut self.items.get_mut().suspended_nodes;
|
||||||
|
|
||||||
if let Some(suspended) = nodes.remove(&task_id) {
|
if let Some(suspended) = nodes.remove(&task_id) {
|
||||||
let sus: &'a VSuspended<'static> = suspended;
|
let sus: &'a VSuspended = suspended;
|
||||||
let sus: &'a VSuspended<'a> = unsafe { std::mem::transmute(sus) };
|
// let mut boxed = sus.callback.borrow_mut().take().unwrap();
|
||||||
let mut boxed = sus.callback.borrow_mut().take().unwrap();
|
// let new_node: Element = boxed();
|
||||||
let new_node: Element = boxed();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -469,7 +480,7 @@ impl Scope {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn root_node<'a>(&'a self) -> &'a VNode<'a> {
|
pub fn root_node(&self) -> &VNode {
|
||||||
let node = *self.wip_frame().nodes.borrow().get(0).unwrap();
|
let node = *self.wip_frame().nodes.borrow().get(0).unwrap();
|
||||||
unsafe { std::mem::transmute(&*node) }
|
unsafe { std::mem::transmute(&*node) }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ pub struct Heuristic {
|
|||||||
// has an internal heuristics engine to pre-allocate arenas to the right size
|
// has an internal heuristics engine to pre-allocate arenas to the right size
|
||||||
pub(crate) struct ScopeArena {
|
pub(crate) struct ScopeArena {
|
||||||
bump: Bump,
|
bump: Bump,
|
||||||
pub pending_futures: FxHashSet<ScopeId>,
|
pub pending_futures: RefCell<FxHashSet<ScopeId>>,
|
||||||
scope_counter: Cell<usize>,
|
scope_counter: Cell<usize>,
|
||||||
pub scopes: RefCell<FxHashMap<ScopeId, *mut Scope>>,
|
pub scopes: RefCell<FxHashMap<ScopeId, *mut Scope>>,
|
||||||
pub heuristics: RefCell<FxHashMap<FcSlot, Heuristic>>,
|
pub heuristics: RefCell<FxHashMap<FcSlot, Heuristic>>,
|
||||||
@@ -56,7 +56,7 @@ impl ScopeArena {
|
|||||||
Self {
|
Self {
|
||||||
scope_counter: Cell::new(0),
|
scope_counter: Cell::new(0),
|
||||||
bump,
|
bump,
|
||||||
pending_futures: FxHashSet::default(),
|
pending_futures: RefCell::new(FxHashSet::default()),
|
||||||
scopes: RefCell::new(FxHashMap::default()),
|
scopes: RefCell::new(FxHashMap::default()),
|
||||||
heuristics: RefCell::new(FxHashMap::default()),
|
heuristics: RefCell::new(FxHashMap::default()),
|
||||||
free_scopes: RefCell::new(Vec::new()),
|
free_scopes: RefCell::new(Vec::new()),
|
||||||
@@ -356,7 +356,7 @@ impl ScopeArena {
|
|||||||
debug_assert_eq!(scope.wip_frame().nodes.borrow().len(), 1);
|
debug_assert_eq!(scope.wip_frame().nodes.borrow().len(), 1);
|
||||||
|
|
||||||
if !scope.items.borrow().tasks.is_empty() {
|
if !scope.items.borrow().tasks.is_empty() {
|
||||||
//
|
self.pending_futures.borrow_mut().insert(*id);
|
||||||
}
|
}
|
||||||
|
|
||||||
// make the "wip frame" contents the "finished frame"
|
// make the "wip frame" contents the "finished frame"
|
||||||
@@ -381,7 +381,7 @@ impl ScopeArena {
|
|||||||
if listener.event == event.name {
|
if listener.event == event.name {
|
||||||
let mut cb = listener.callback.borrow_mut();
|
let mut cb = listener.callback.borrow_mut();
|
||||||
if let Some(cb) = cb.as_mut() {
|
if let Some(cb) = cb.as_mut() {
|
||||||
(cb)(event.event.clone());
|
(cb)(event.data.clone());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -263,7 +263,7 @@ impl VirtualDom {
|
|||||||
// todo: poll the events once even if there is work to do to prevent starvation
|
// todo: poll the events once even if there is work to do to prevent starvation
|
||||||
|
|
||||||
// if there's no futures in the virtualdom, just wait for a scheduler message and put it into the queue to be processed
|
// if there's no futures in the virtualdom, just wait for a scheduler message and put it into the queue to be processed
|
||||||
if self.scopes.pending_futures.is_empty() {
|
if self.scopes.pending_futures.borrow().is_empty() {
|
||||||
self.pending_messages
|
self.pending_messages
|
||||||
.push_front(self.receiver.next().await.unwrap());
|
.push_front(self.receiver.next().await.unwrap());
|
||||||
} else {
|
} else {
|
||||||
@@ -275,7 +275,7 @@ impl VirtualDom {
|
|||||||
type Output = ();
|
type Output = ();
|
||||||
|
|
||||||
fn poll(
|
fn poll(
|
||||||
mut self: Pin<&mut Self>,
|
self: Pin<&mut Self>,
|
||||||
cx: &mut std::task::Context<'_>,
|
cx: &mut std::task::Context<'_>,
|
||||||
) -> Poll<Self::Output> {
|
) -> Poll<Self::Output> {
|
||||||
let mut all_pending = true;
|
let mut all_pending = true;
|
||||||
@@ -284,7 +284,7 @@ impl VirtualDom {
|
|||||||
let mut scopes_to_clear: SmallVec<[_; 10]> = smallvec::smallvec![];
|
let mut scopes_to_clear: SmallVec<[_; 10]> = smallvec::smallvec![];
|
||||||
|
|
||||||
// Poll every scope manually
|
// Poll every scope manually
|
||||||
for fut in self.scopes.pending_futures.iter() {
|
for fut in self.scopes.pending_futures.borrow().iter() {
|
||||||
let scope = self
|
let scope = self
|
||||||
.scopes
|
.scopes
|
||||||
.get_scope(fut)
|
.get_scope(fut)
|
||||||
@@ -316,7 +316,7 @@ impl VirtualDom {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for scope in scopes_to_clear {
|
for scope in scopes_to_clear {
|
||||||
self.scopes.pending_futures.remove(&scope);
|
self.scopes.pending_futures.borrow_mut().remove(&scope);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Resolve the future if any singular task is ready
|
// Resolve the future if any singular task is ready
|
||||||
@@ -347,28 +347,31 @@ impl VirtualDom {
|
|||||||
|
|
||||||
/// Run the virtualdom with a deadline.
|
/// Run the virtualdom with a deadline.
|
||||||
///
|
///
|
||||||
/// This method will progress async tasks until the deadline is reached. If tasks are completed before the deadline,
|
/// This method will perform any outstanding diffing work and try to return as many mutations as possible before the
|
||||||
/// and no tasks are pending, this method will return immediately. If tasks are still pending, then this method will
|
/// deadline is reached. This method accepts a closure that returns `true` if the deadline has been reached. To wrap
|
||||||
/// exhaust the deadline working on them.
|
/// your future into a deadline, consider the `now_or_never` method from `future_utils`.
|
||||||
|
///
|
||||||
|
/// ```rust, ignore
|
||||||
|
/// let mut vdom = VirtualDom::new(App);
|
||||||
|
///
|
||||||
|
/// let timeout = TimeoutFuture::from_ms(16);
|
||||||
|
/// let deadline = || (&mut timeout).now_or_never();
|
||||||
|
///
|
||||||
|
/// let mutations = vdom.work_with_deadline(deadline);
|
||||||
|
/// ```
|
||||||
///
|
///
|
||||||
/// This method is useful when needing to schedule the virtualdom around other tasks on the main thread to prevent
|
/// This method is useful when needing to schedule the virtualdom around other tasks on the main thread to prevent
|
||||||
/// "jank". It will try to finish whatever work it has by the deadline to free up time for other work.
|
/// "jank". It will try to finish whatever work it has by the deadline to free up time for other work.
|
||||||
///
|
///
|
||||||
/// Due to platform differences in how time is handled, this method accepts a future that resolves when the deadline
|
/// If the work is not finished by the deadline, Dioxus will store it for later and return when work_with_deadline
|
||||||
/// is exceeded. However, the deadline won't be met precisely, so you might want to build some wiggle room into the
|
/// is called again. This means you can ensure some level of free time on the VirtualDom's thread during the work phase.
|
||||||
/// deadline closure manually.
|
|
||||||
///
|
|
||||||
/// The deadline is polled before starting to diff components. This strikes a balance between the overhead of checking
|
|
||||||
/// the deadline and just completing the work. However, if an individual component takes more than 16ms to render, then
|
|
||||||
/// the screen will "jank" up. In debug, this will trigger an alert.
|
|
||||||
///
|
|
||||||
/// If there are no in-flight fibers when this method is called, it will await any possible tasks, aborting early if
|
|
||||||
/// the provided deadline future resolves.
|
|
||||||
///
|
///
|
||||||
/// For use in the web, it is expected that this method will be called to be executed during "idle times" and the
|
/// For use in the web, it is expected that this method will be called to be executed during "idle times" and the
|
||||||
/// mutations to be applied during the "paint times" IE "animation frames". With this strategy, it is possible to craft
|
/// mutations to be applied during the "paint times" IE "animation frames". With this strategy, it is possible to craft
|
||||||
/// entirely jank-free applications that perform a ton of work.
|
/// entirely jank-free applications that perform a ton of work.
|
||||||
///
|
///
|
||||||
|
/// In general use, Dioxus is plenty fast enough to not need to worry about this.
|
||||||
|
///
|
||||||
/// # Example
|
/// # Example
|
||||||
///
|
///
|
||||||
/// ```rust, ignore
|
/// ```rust, ignore
|
||||||
@@ -387,15 +390,6 @@ impl VirtualDom {
|
|||||||
/// apply_mutations(mutations);
|
/// apply_mutations(mutations);
|
||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
///
|
|
||||||
/// ## Mutations
|
|
||||||
///
|
|
||||||
/// This method returns "mutations" - IE the necessary changes to get the RealDOM to match the VirtualDOM. It also
|
|
||||||
/// includes a list of NodeRefs that need to be applied and effects that need to be triggered after the RealDOM has
|
|
||||||
/// applied the edits.
|
|
||||||
///
|
|
||||||
/// Mutations are the only link between the RealDOM and the VirtualDOM.
|
|
||||||
///
|
|
||||||
pub fn work_with_deadline(&mut self, mut deadline: impl FnMut() -> bool) -> Vec<Mutations> {
|
pub fn work_with_deadline(&mut self, mut deadline: impl FnMut() -> bool) -> Vec<Mutations> {
|
||||||
let mut committed_mutations = vec![];
|
let mut committed_mutations = vec![];
|
||||||
|
|
||||||
@@ -410,16 +404,18 @@ impl VirtualDom {
|
|||||||
SchedulerMsg::Immediate(id) => {
|
SchedulerMsg::Immediate(id) => {
|
||||||
self.dirty_scopes.insert(id);
|
self.dirty_scopes.insert(id);
|
||||||
}
|
}
|
||||||
|
SchedulerMsg::Suspended { node, scope } => {
|
||||||
|
todo!("should wire up suspense")
|
||||||
|
// self.suspense_scopes.insert(id);
|
||||||
|
}
|
||||||
SchedulerMsg::UiEvent(event) => {
|
SchedulerMsg::UiEvent(event) => {
|
||||||
if let Some(element) = event.mounted_dom_id {
|
if let Some(element) = event.element {
|
||||||
log::info!("Calling listener {:?}, {:?}", event.scope_id, element);
|
log::info!("Calling listener {:?}, {:?}", event.scope_id, element);
|
||||||
|
|
||||||
// if let Some(scope) = self.scopes.get_scope(&event.scope_id) {
|
|
||||||
self.scopes.call_listener_with_bubbling(event, element);
|
self.scopes.call_listener_with_bubbling(event, element);
|
||||||
while let Ok(Some(dirty_scope)) = self.receiver.try_next() {
|
while let Ok(Some(dirty_scope)) = self.receiver.try_next() {
|
||||||
self.pending_messages.push_front(dirty_scope);
|
self.pending_messages.push_front(dirty_scope);
|
||||||
}
|
}
|
||||||
// }
|
|
||||||
} else {
|
} else {
|
||||||
log::debug!("User event without a targetted ElementId. Not currently supported.\nUnsure how to proceed. {:?}", event);
|
log::debug!("User event without a targetted ElementId. Not currently supported.\nUnsure how to proceed. {:?}", event);
|
||||||
}
|
}
|
||||||
@@ -432,12 +428,9 @@ impl VirtualDom {
|
|||||||
|
|
||||||
let mut ran_scopes = FxHashSet::default();
|
let mut ran_scopes = FxHashSet::default();
|
||||||
|
|
||||||
// todo: the 2021 version of rust will let us not have to force the borrow
|
|
||||||
// let scopes = &self.scopes;
|
|
||||||
// Sort the scopes by height. Theoretically, we'll de-duplicate scopes by height
|
// Sort the scopes by height. Theoretically, we'll de-duplicate scopes by height
|
||||||
self.dirty_scopes
|
self.dirty_scopes
|
||||||
.retain(|id| scopes.get_scope(id).is_some());
|
.retain(|id| scopes.get_scope(id).is_some());
|
||||||
|
|
||||||
self.dirty_scopes.sort_by(|a, b| {
|
self.dirty_scopes.sort_by(|a, b| {
|
||||||
let h1 = scopes.get_scope(a).unwrap().height;
|
let h1 = scopes.get_scope(a).unwrap().height;
|
||||||
let h2 = scopes.get_scope(b).unwrap().height;
|
let h2 = scopes.get_scope(b).unwrap().height;
|
||||||
@@ -445,13 +438,9 @@ impl VirtualDom {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if let Some(scopeid) = self.dirty_scopes.pop() {
|
if let Some(scopeid) = self.dirty_scopes.pop() {
|
||||||
log::info!("handling dirty scope {:?}", scopeid);
|
|
||||||
|
|
||||||
if !ran_scopes.contains(&scopeid) {
|
if !ran_scopes.contains(&scopeid) {
|
||||||
ran_scopes.insert(scopeid);
|
ran_scopes.insert(scopeid);
|
||||||
|
|
||||||
log::debug!("about to run scope {:?}", scopeid);
|
|
||||||
|
|
||||||
if self.scopes.run_scope(&scopeid) {
|
if self.scopes.run_scope(&scopeid) {
|
||||||
let (old, new) = (
|
let (old, new) = (
|
||||||
self.scopes.wip_head(&scopeid),
|
self.scopes.wip_head(&scopeid),
|
||||||
@@ -480,7 +469,6 @@ impl VirtualDom {
|
|||||||
committed_mutations.push(mutations);
|
committed_mutations.push(mutations);
|
||||||
} else {
|
} else {
|
||||||
// leave the work in an incomplete state
|
// leave the work in an incomplete state
|
||||||
log::debug!("don't have a mechanism to pause work (yet)");
|
|
||||||
return committed_mutations;
|
return committed_mutations;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -530,7 +518,6 @@ impl VirtualDom {
|
|||||||
/// In this case, every component will be diffed, even if their props are memoized. This method is intended to be used
|
/// In this case, every component will be diffed, even if their props are memoized. This method is intended to be used
|
||||||
/// to force an update of the DOM when the state of the app is changed outside of the app.
|
/// to force an update of the DOM when the state of the app is changed outside of the app.
|
||||||
///
|
///
|
||||||
///
|
|
||||||
/// # Example
|
/// # Example
|
||||||
/// ```rust, ignore
|
/// ```rust, ignore
|
||||||
/// #[derive(PartialEq, Props)]
|
/// #[derive(PartialEq, Props)]
|
||||||
@@ -544,12 +531,7 @@ impl VirtualDom {
|
|||||||
/// };
|
/// };
|
||||||
///
|
///
|
||||||
/// let value = Rc::new(RefCell::new("Hello"));
|
/// let value = Rc::new(RefCell::new("Hello"));
|
||||||
/// let mut dom = VirtualDom::new_with_props(
|
/// let mut dom = VirtualDom::new_with_props(App, AppProps { value: value.clone(), });
|
||||||
/// App,
|
|
||||||
/// AppProps {
|
|
||||||
/// value: value.clone(),
|
|
||||||
/// },
|
|
||||||
/// );
|
|
||||||
///
|
///
|
||||||
/// let _ = dom.rebuild();
|
/// let _ = dom.rebuild();
|
||||||
///
|
///
|
||||||
@@ -633,8 +615,41 @@ pub enum SchedulerMsg {
|
|||||||
|
|
||||||
// setstate
|
// setstate
|
||||||
Immediate(ScopeId),
|
Immediate(ScopeId),
|
||||||
|
|
||||||
|
Suspended { scope: ScopeId, node: NodeLink },
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// User Events are events that are shuttled from the renderer into the VirtualDom trhough the scheduler channel.
|
||||||
|
///
|
||||||
|
/// These events will be passed to the appropriate Element given by `mounted_dom_id` and then bubbled up through the tree
|
||||||
|
/// where each listener is checked and fired if the event name matches.
|
||||||
|
///
|
||||||
|
/// It is the expectation that the event name matches the corresponding event listener, otherwise Dioxus will panic in
|
||||||
|
/// attempting to downcast the event data.
|
||||||
|
///
|
||||||
|
/// Because Event Data is sent across threads, it must be `Send + Sync`. We are hoping to lift the `Sync` restriction but
|
||||||
|
/// `Send` will not be lifted. The entire `UserEvent` must also be `Send + Sync` due to its use in the scheduler channel.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
/// ```rust
|
||||||
|
/// fn App(cx: Context, props: &()) -> Element {
|
||||||
|
/// rsx!(cx, div {
|
||||||
|
/// onclick: move |_| println!("Clicked!")
|
||||||
|
/// })
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// let mut dom = VirtualDom::new(App);
|
||||||
|
/// let mut scheduler = dom.get_scheduler_channel();
|
||||||
|
/// scheduler.unbounded_send(SchedulerMsg::UiEvent(
|
||||||
|
/// UserEvent {
|
||||||
|
/// scope_id: None,
|
||||||
|
/// priority: EventPriority::Medium,
|
||||||
|
/// name: "click",
|
||||||
|
/// element: Some(ElementId(0)),
|
||||||
|
/// data: Arc::new(ClickEvent { .. })
|
||||||
|
/// }
|
||||||
|
/// )).unwrap();
|
||||||
|
/// ```
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct UserEvent {
|
pub struct UserEvent {
|
||||||
/// The originator of the event trigger
|
/// The originator of the event trigger
|
||||||
@@ -643,7 +658,7 @@ pub struct UserEvent {
|
|||||||
pub priority: EventPriority,
|
pub priority: EventPriority,
|
||||||
|
|
||||||
/// The optional real node associated with the trigger
|
/// The optional real node associated with the trigger
|
||||||
pub mounted_dom_id: Option<ElementId>,
|
pub element: Option<ElementId>,
|
||||||
|
|
||||||
/// The event type IE "onclick" or "onmouseover"
|
/// The event type IE "onclick" or "onmouseover"
|
||||||
///
|
///
|
||||||
@@ -651,7 +666,7 @@ pub struct UserEvent {
|
|||||||
pub name: &'static str,
|
pub name: &'static str,
|
||||||
|
|
||||||
/// Event Data
|
/// Event Data
|
||||||
pub event: Arc<dyn Any + Send + Sync>,
|
pub data: Arc<dyn Any + Send + Sync>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Priority of Event Triggers.
|
/// Priority of Event Triggers.
|
||||||
|
|||||||
@@ -11,19 +11,18 @@
|
|||||||
//! Don't have a good way to validate, everything is done manually ATM
|
//! Don't have a good way to validate, everything is done manually ATM
|
||||||
|
|
||||||
use dioxus::prelude::*;
|
use dioxus::prelude::*;
|
||||||
|
use dioxus::DomEdit;
|
||||||
use dioxus_core as dioxus;
|
use dioxus_core as dioxus;
|
||||||
use dioxus_core_macro::*;
|
use dioxus_core_macro::*;
|
||||||
use dioxus_html as dioxus_elements;
|
use dioxus_html as dioxus_elements;
|
||||||
|
use DomEdit::*;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn app_runs() {
|
fn app_runs() {
|
||||||
static App: FC<()> = |cx, props| {
|
static App: FC<()> = |cx, props| rsx!(cx, div{"hello"} );
|
||||||
//
|
|
||||||
cx.render(rsx!( div{"hello"} ))
|
|
||||||
};
|
|
||||||
let mut vdom = VirtualDom::new(App);
|
let mut vdom = VirtualDom::new(App);
|
||||||
let edits = vdom.rebuild();
|
let edits = vdom.rebuild();
|
||||||
dbg!(edits);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -65,9 +64,25 @@ fn conditional_rendering() {
|
|||||||
let mut vdom = VirtualDom::new(App);
|
let mut vdom = VirtualDom::new(App);
|
||||||
|
|
||||||
let mutations = vdom.rebuild();
|
let mutations = vdom.rebuild();
|
||||||
dbg!(&mutations);
|
assert_eq!(
|
||||||
// the "false" fragment should generate an empty placeholder to re-visit
|
mutations.edits,
|
||||||
assert!(mutations.edits[mutations.edits.len() - 2].is("CreatePlaceholder"));
|
[
|
||||||
|
CreateElement { root: 1, tag: "h1" },
|
||||||
|
CreateTextNode {
|
||||||
|
root: 2,
|
||||||
|
text: "hello"
|
||||||
|
},
|
||||||
|
AppendChildren { many: 1 },
|
||||||
|
CreateElement {
|
||||||
|
root: 3,
|
||||||
|
tag: "span"
|
||||||
|
},
|
||||||
|
CreateTextNode { root: 4, text: "a" },
|
||||||
|
AppendChildren { many: 1 },
|
||||||
|
CreatePlaceholder { root: 5 },
|
||||||
|
AppendChildren { many: 3 },
|
||||||
|
]
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|||||||
@@ -34,8 +34,8 @@ pub fn trigger_from_serialized(val: serde_json::Value) -> UserEvent {
|
|||||||
name,
|
name,
|
||||||
priority: EventPriority::Low,
|
priority: EventPriority::Low,
|
||||||
scope_id: None,
|
scope_id: None,
|
||||||
mounted_dom_id,
|
element: mounted_dom_id,
|
||||||
event,
|
data: event,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,29 @@
|
|||||||
|
//! Example: README.md showcase
|
||||||
|
//!
|
||||||
|
//! The example from the README.md.
|
||||||
|
|
||||||
|
use dioxus::prelude::*;
|
||||||
|
use dioxus_core as dioxus;
|
||||||
|
use dioxus_core_macro::*;
|
||||||
|
use dioxus_hooks::use_state;
|
||||||
|
use dioxus_html as dioxus_elements;
|
||||||
|
use dioxus_web;
|
||||||
|
use gloo_timers::future::TimeoutFuture;
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
dioxus_web::launch(App, |c| c);
|
||||||
|
}
|
||||||
|
|
||||||
|
static App: FC<()> = |cx, props| {
|
||||||
|
let mut count = use_state(cx, || 0);
|
||||||
|
|
||||||
|
cx.push_task(|| async move {
|
||||||
|
TimeoutFuture::new(100).await;
|
||||||
|
count += 1;
|
||||||
|
});
|
||||||
|
|
||||||
|
rsx!(cx, div {
|
||||||
|
h3 { "High-Five counter: {count}" }
|
||||||
|
button { onclick: move |_| count.set(0), "Reset!" }
|
||||||
|
})
|
||||||
|
};
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
#![allow(non_upper_case_globals)]
|
||||||
|
|
||||||
|
//! Example: README.md showcase
|
||||||
|
//!
|
||||||
|
//! The example from the README.md.
|
||||||
|
|
||||||
|
use dioxus::prelude::*;
|
||||||
|
use dioxus_core as dioxus;
|
||||||
|
use dioxus_core_macro::*;
|
||||||
|
use dioxus_html as dioxus_elements;
|
||||||
|
use dioxus_web;
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
dioxus_web::launch(App, |c| c);
|
||||||
|
}
|
||||||
|
|
||||||
|
static App: FC<()> = |cx, _| {
|
||||||
|
let doggo = cx.suspend(|| async move {
|
||||||
|
#[derive(serde::Deserialize)]
|
||||||
|
struct Doggo {
|
||||||
|
message: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
let src = reqwest::get("https://dog.ceo/api/breeds/image/random")
|
||||||
|
.await
|
||||||
|
.expect("Failed to fetch doggo")
|
||||||
|
.json::<Doggo>()
|
||||||
|
.await
|
||||||
|
.expect("Failed to parse doggo")
|
||||||
|
.message;
|
||||||
|
|
||||||
|
rsx!(cx, img { src: "{src}" })
|
||||||
|
});
|
||||||
|
|
||||||
|
rsx!(cx, div {
|
||||||
|
h1 {"One doggo coming right up"}
|
||||||
|
button { onclick: move |_| cx.needs_update(), "Get a new doggo" }
|
||||||
|
{doggo}
|
||||||
|
})
|
||||||
|
};
|
||||||
@@ -703,9 +703,9 @@ fn decode_trigger(event: &web_sys::Event) -> anyhow::Result<UserEvent> {
|
|||||||
|
|
||||||
Ok(UserEvent {
|
Ok(UserEvent {
|
||||||
name: event_name_from_typ(&typ),
|
name: event_name_from_typ(&typ),
|
||||||
event: virtual_event_from_websys_event(event.clone()),
|
data: virtual_event_from_websys_event(event.clone()),
|
||||||
mounted_dom_id: Some(ElementId(real_id as usize)),
|
element: Some(ElementId(real_id as usize)),
|
||||||
scope_id: ScopeId(triggered_scope as usize),
|
scope_id: Some(ScopeId(triggered_scope as usize)),
|
||||||
priority: dioxus_core::EventPriority::Medium,
|
priority: dioxus_core::EventPriority::Medium,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in new issue
Block a user