polish: tests and documentation
This commit is contained in:
1 parent
0896f34493
commit
da8159190b
27 files changed
+467
-569
No files matched your search
Vendored
+1
@@ -66,3 +66,4 @@ constified
|
|||||||
SegVec
|
SegVec
|
||||||
contentful
|
contentful
|
||||||
Jank
|
Jank
|
||||||
|
noderef
|
||||||
@@ -109,6 +109,8 @@ If you know React, then you already know Dioxus.
|
|||||||
- Sierpinski's triangle (web SPA)
|
- Sierpinski's triangle (web SPA)
|
||||||
- Doxie Documentation Library (Web SPA with Hydration)
|
- Doxie Documentation Library (Web SPA with Hydration)
|
||||||
|
|
||||||
|
See the awesome-dioxus page for a curated list of content in the Dioxus Ecosystem.
|
||||||
|
|
||||||
<!--
|
<!--
|
||||||
currently commented out until we have more content on the website
|
currently commented out until we have more content on the website
|
||||||
## Explore
|
## Explore
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ pub(crate) struct BumpFrame {
|
|||||||
pub bump: Bump,
|
pub bump: Bump,
|
||||||
pub(crate) head_node: VNode<'static>,
|
pub(crate) head_node: VNode<'static>,
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
// used internally for debugging
|
// used internally for debugging
|
||||||
_name: &'static str,
|
_name: &'static str,
|
||||||
}
|
}
|
||||||
@@ -23,11 +24,15 @@ impl ActiveFrame {
|
|||||||
let frame_a = BumpFrame {
|
let frame_a = BumpFrame {
|
||||||
bump: Bump::new(),
|
bump: Bump::new(),
|
||||||
head_node: NodeFactory::unstable_place_holder(),
|
head_node: NodeFactory::unstable_place_holder(),
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
_name: "wip",
|
_name: "wip",
|
||||||
};
|
};
|
||||||
let frame_b = BumpFrame {
|
let frame_b = BumpFrame {
|
||||||
bump: Bump::new(),
|
bump: Bump::new(),
|
||||||
head_node: NodeFactory::unstable_place_holder(),
|
head_node: NodeFactory::unstable_place_holder(),
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
_name: "fin",
|
_name: "fin",
|
||||||
};
|
};
|
||||||
Self {
|
Self {
|
||||||
|
|||||||
@@ -64,7 +64,6 @@ impl<'a> Iterator for RealChildIterator<'a> {
|
|||||||
.scopes
|
.scopes
|
||||||
.get_scope(sc.associated_scope.get().unwrap())
|
.get_scope(sc.associated_scope.get().unwrap())
|
||||||
.unwrap();
|
.unwrap();
|
||||||
// let scope = self.scopes.get(sc.ass_scope.get().unwrap()).unwrap();
|
|
||||||
|
|
||||||
// Simply swap the current node on the stack with the root of the component
|
// Simply swap the current node on the stack with the root of the component
|
||||||
*node = scope.frames.fin_head();
|
*node = scope.frames.fin_head();
|
||||||
|
|||||||
@@ -7,6 +7,32 @@
|
|||||||
|
|
||||||
use crate::innerlude::{Context, DomTree, LazyNodes, FC};
|
use crate::innerlude::{Context, DomTree, LazyNodes, FC};
|
||||||
|
|
||||||
|
/// Create inline fragments using Component syntax.
|
||||||
|
///
|
||||||
|
/// Fragments capture a series of children without rendering extra nodes.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// rsx!{
|
||||||
|
/// Fragment { key: "abc" }
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// # Details
|
||||||
|
///
|
||||||
|
/// Fragments are incredibly useful when necessary, but *do* add cost in the diffing phase.
|
||||||
|
/// Try to avoid nesting fragments if you can. There is no protection against infinitely nested fragments.
|
||||||
|
///
|
||||||
|
/// This function defines a dedicated `Fragment` component that can be used to create inline fragments in the RSX macro.
|
||||||
|
///
|
||||||
|
/// You want to use this free-function when your fragment needs a key and simply returning multiple nodes from rsx! won't cut it.
|
||||||
|
///
|
||||||
|
#[allow(non_upper_case_globals, non_snake_case)]
|
||||||
|
pub fn Fragment<'a>(cx: Context<'a, ()>) -> DomTree<'a> {
|
||||||
|
cx.render(LazyNodes::new(|f| f.fragment_from_iter(cx.children())))
|
||||||
|
}
|
||||||
|
|
||||||
/// Every "Props" used for a component must implement the `Properties` trait. This trait gives some hints to Dioxus
|
/// Every "Props" used for a component must implement the `Properties` trait. This trait gives some hints to Dioxus
|
||||||
/// on how to memoize the props and some additional optimizations that can be made. We strongly encourage using the
|
/// on how to memoize the props and some additional optimizations that can be made. We strongly encourage using the
|
||||||
/// derive macro to implement the `Properties` trait automatically as guarantee that your memoization strategy is safe.
|
/// derive macro to implement the `Properties` trait automatically as guarantee that your memoization strategy is safe.
|
||||||
@@ -44,7 +70,7 @@ pub trait Properties: Sized {
|
|||||||
const IS_STATIC: bool;
|
const IS_STATIC: bool;
|
||||||
fn builder() -> Self::Builder;
|
fn builder() -> Self::Builder;
|
||||||
|
|
||||||
/// Memoization can only happen if the props are 'static
|
/// Memoization can only happen if the props are valid for the 'static lifetime
|
||||||
/// The user must know if their props are static, but if they make a mistake, UB happens
|
/// The user must know if their props are static, but if they make a mistake, UB happens
|
||||||
/// Therefore it's unsafe to memeoize.
|
/// Therefore it's unsafe to memeoize.
|
||||||
unsafe fn memoize(&self, other: &Self) -> bool;
|
unsafe fn memoize(&self, other: &Self) -> bool;
|
||||||
@@ -75,24 +101,3 @@ impl EmptyBuilder {
|
|||||||
pub fn fc_to_builder<T: Properties>(_: FC<T>) -> T::Builder {
|
pub fn fc_to_builder<T: Properties>(_: FC<T>) -> T::Builder {
|
||||||
T::builder()
|
T::builder()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create inline fragments
|
|
||||||
///
|
|
||||||
/// Fragments capture a series of children without rendering extra nodes.
|
|
||||||
///
|
|
||||||
/// Fragments are incredibly useful when necessary, but *do* add cost in the diffing phase.
|
|
||||||
/// Try to avoid nesting fragments if you can. Infinitely nested Fragments *will* cause diffing to crash.
|
|
||||||
///
|
|
||||||
/// This function defines a dedicated `Fragment` component that can be used to create inline fragments in the RSX macro.
|
|
||||||
///
|
|
||||||
/// You want to use this free-function when your fragment needs a key and simply returning multiple nodes from rsx! won't cut it.
|
|
||||||
///
|
|
||||||
/// ```rust
|
|
||||||
/// rsx!{
|
|
||||||
/// Fragment { key: "abc" }
|
|
||||||
/// }
|
|
||||||
/// ```
|
|
||||||
#[allow(non_upper_case_globals, non_snake_case)]
|
|
||||||
pub fn Fragment<'a>(cx: Context<'a, ()>) -> DomTree<'a> {
|
|
||||||
cx.render(LazyNodes::new(move |f| f.fragment_from_iter(cx.children())))
|
|
||||||
}
|
|
||||||
+59
-117
@@ -124,6 +124,7 @@ impl<'a> SavedDiffWork<'a> {
|
|||||||
pub unsafe fn extend(self: SavedDiffWork<'a>) -> SavedDiffWork<'static> {
|
pub unsafe fn extend(self: SavedDiffWork<'a>) -> SavedDiffWork<'static> {
|
||||||
std::mem::transmute(self)
|
std::mem::transmute(self)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub unsafe fn promote<'b>(self, vdom: &'b mut ResourcePool) -> DiffMachine<'b> {
|
pub unsafe fn promote<'b>(self, vdom: &'b mut ResourcePool) -> DiffMachine<'b> {
|
||||||
let extended: SavedDiffWork<'b> = std::mem::transmute(self);
|
let extended: SavedDiffWork<'b> = std::mem::transmute(self);
|
||||||
DiffMachine {
|
DiffMachine {
|
||||||
@@ -136,10 +137,10 @@ impl<'a> SavedDiffWork<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl<'bump> DiffMachine<'bump> {
|
impl<'bump> DiffMachine<'bump> {
|
||||||
pub(crate) fn new(edits: Mutations<'bump>, shared: &'bump ResourcePool) -> Self {
|
pub(crate) fn new(mutations: Mutations<'bump>, shared: &'bump ResourcePool) -> Self {
|
||||||
Self {
|
Self {
|
||||||
|
mutations,
|
||||||
stack: DiffStack::new(),
|
stack: DiffStack::new(),
|
||||||
mutations: edits,
|
|
||||||
vdom: shared,
|
vdom: shared,
|
||||||
seen_scopes: FxHashSet::default(),
|
seen_scopes: FxHashSet::default(),
|
||||||
}
|
}
|
||||||
@@ -156,7 +157,7 @@ impl<'bump> DiffMachine<'bump> {
|
|||||||
pub fn diff_scope(&mut self, id: ScopeId) {
|
pub fn diff_scope(&mut self, id: ScopeId) {
|
||||||
if let Some(component) = self.vdom.get_scope_mut(id) {
|
if let Some(component) = self.vdom.get_scope_mut(id) {
|
||||||
let (old, new) = (component.frames.wip_head(), component.frames.fin_head());
|
let (old, new) = (component.frames.wip_head(), component.frames.fin_head());
|
||||||
self.stack.push(DiffInstruction::DiffNode { new, old });
|
self.stack.push(DiffInstruction::Diff { new, old });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -167,24 +168,14 @@ impl<'bump> DiffMachine<'bump> {
|
|||||||
/// We do depth-first to maintain high cache locality (nodes were originally generated recursively).
|
/// We do depth-first to maintain high cache locality (nodes were originally generated recursively).
|
||||||
///
|
///
|
||||||
/// Returns a `bool` indicating that the work completed properly.
|
/// Returns a `bool` indicating that the work completed properly.
|
||||||
pub fn work(&mut self, deadline_expired: &mut impl FnMut() -> bool) -> bool {
|
pub fn work(&mut self, mut deadline_expired: impl FnMut() -> bool) -> bool {
|
||||||
while let Some(instruction) = self.stack.pop() {
|
while let Some(instruction) = self.stack.pop() {
|
||||||
// defer to individual functions so the compiler produces better code
|
|
||||||
// large functions tend to be difficult for the compiler to work with
|
|
||||||
match instruction {
|
match instruction {
|
||||||
DiffInstruction::PopScope => {
|
DiffInstruction::Diff { old, new } => self.diff_node(old, new),
|
||||||
self.stack.pop_scope();
|
|
||||||
}
|
|
||||||
|
|
||||||
DiffInstruction::DiffNode { old, new, .. } => self.diff_node(old, new),
|
|
||||||
|
|
||||||
DiffInstruction::DiffChildren { old, new } => self.diff_children(old, new),
|
|
||||||
|
|
||||||
DiffInstruction::Create { node } => self.create_node(node),
|
DiffInstruction::Create { node } => self.create_node(node),
|
||||||
|
|
||||||
DiffInstruction::Mount { and } => self.mount(and),
|
DiffInstruction::Mount { and } => self.mount(and),
|
||||||
|
DiffInstruction::PrepareMove { node } => self.prepare_move_node(node),
|
||||||
DiffInstruction::PrepareMoveNode { node } => self.prepare_move_node(node),
|
DiffInstruction::PopScope => self.stack.pop_off_scope(),
|
||||||
};
|
};
|
||||||
|
|
||||||
if deadline_expired() {
|
if deadline_expired() {
|
||||||
@@ -217,15 +208,21 @@ impl<'bump> DiffMachine<'bump> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
MountType::Replace { old } => {
|
MountType::Replace { old } => {
|
||||||
let mut iter = RealChildIterator::new(old, self.vdom);
|
if let Some(old_id) = old.try_mounted_id() {
|
||||||
let first = iter.next().unwrap();
|
self.mutations.replace_with(old_id, nodes_created as u32);
|
||||||
self.mutations
|
} else {
|
||||||
.replace_with(first.mounted_id(), nodes_created as u32);
|
let mut iter = RealChildIterator::new(old, self.vdom);
|
||||||
self.remove_nodes(iter);
|
let first = iter.next().unwrap();
|
||||||
|
self.mutations
|
||||||
|
.replace_with(first.mounted_id(), nodes_created as u32);
|
||||||
|
self.remove_nodes(iter);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
MountType::ReplaceByElementId { el: old } => {
|
MountType::ReplaceByElementId { el } => {
|
||||||
self.mutations.replace_with(old, nodes_created as u32);
|
if let Some(old) = el {
|
||||||
|
self.mutations.replace_with(old, nodes_created as u32);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
MountType::InsertAfter { other_node } => {
|
MountType::InsertAfter { other_node } => {
|
||||||
@@ -326,6 +323,7 @@ impl<'bump> DiffMachine<'bump> {
|
|||||||
let parent_idx = self.stack.current_scope().unwrap();
|
let parent_idx = self.stack.current_scope().unwrap();
|
||||||
|
|
||||||
let shared = self.vdom.channel.clone();
|
let shared = self.vdom.channel.clone();
|
||||||
|
|
||||||
// Insert a new scope into our component list
|
// Insert a new scope into our component list
|
||||||
let parent_scope = self.vdom.get_scope(parent_idx).unwrap();
|
let parent_scope = self.vdom.get_scope(parent_idx).unwrap();
|
||||||
let new_idx = self.vdom.insert_scope_with_key(|new_idx| {
|
let new_idx = self.vdom.insert_scope_with_key(|new_idx| {
|
||||||
@@ -376,7 +374,9 @@ impl<'bump> DiffMachine<'bump> {
|
|||||||
match (old_node, new_node) {
|
match (old_node, new_node) {
|
||||||
// Check the most common cases first
|
// Check the most common cases first
|
||||||
(Text(old), Text(new)) => self.diff_text_nodes(old, new),
|
(Text(old), Text(new)) => self.diff_text_nodes(old, new),
|
||||||
(Component(old), Component(new)) => self.diff_component_nodes(old, new),
|
(Component(old), Component(new)) => {
|
||||||
|
self.diff_component_nodes(old_node, new_node, old, new)
|
||||||
|
}
|
||||||
(Fragment(old), Fragment(new)) => self.diff_fragment_nodes(old, new),
|
(Fragment(old), Fragment(new)) => self.diff_fragment_nodes(old, new),
|
||||||
(Anchor(old), Anchor(new)) => new.dom_id.set(old.dom_id.get()),
|
(Anchor(old), Anchor(new)) => new.dom_id.set(old.dom_id.get()),
|
||||||
(Suspended(old), Suspended(new)) => new.dom_id.set(old.dom_id.get()),
|
(Suspended(old), Suspended(new)) => new.dom_id.set(old.dom_id.get()),
|
||||||
@@ -393,19 +393,19 @@ impl<'bump> DiffMachine<'bump> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn diff_text_nodes(&mut self, old: &'bump VText<'bump>, new: &'bump VText<'bump>) {
|
fn diff_text_nodes(&mut self, old: &'bump VText<'bump>, new: &'bump VText<'bump>) {
|
||||||
let root = old.dom_id.get().unwrap();
|
if let Some(root) = old.dom_id.get() {
|
||||||
|
if old.text != new.text {
|
||||||
|
self.mutations.push_root(root);
|
||||||
|
self.mutations.set_text(new.text);
|
||||||
|
self.mutations.pop();
|
||||||
|
}
|
||||||
|
|
||||||
if old.text != new.text {
|
new.dom_id.set(Some(root));
|
||||||
self.mutations.push_root(root);
|
|
||||||
self.mutations.set_text(new.text);
|
|
||||||
self.mutations.pop();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
new.dom_id.set(Some(root));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn diff_element_nodes(&mut self, old: &'bump VElement<'bump>, new: &'bump VElement<'bump>) {
|
fn diff_element_nodes(&mut self, old: &'bump VElement<'bump>, new: &'bump VElement<'bump>) {
|
||||||
let root = old.dom_id.get().unwrap();
|
let root = old.dom_id.get();
|
||||||
|
|
||||||
// If the element type is completely different, the element needs to be re-rendered completely
|
// If the element type is completely different, the element needs to be re-rendered completely
|
||||||
// This is an optimization React makes due to how users structure their code
|
// This is an optimization React makes due to how users structure their code
|
||||||
@@ -417,21 +417,23 @@ impl<'bump> DiffMachine<'bump> {
|
|||||||
self.stack.push_nodes_created(0);
|
self.stack.push_nodes_created(0);
|
||||||
self.stack.push(DiffInstruction::Mount {
|
self.stack.push(DiffInstruction::Mount {
|
||||||
and: MountType::ReplaceByElementId {
|
and: MountType::ReplaceByElementId {
|
||||||
el: old.dom_id.get().unwrap(),
|
el: old.dom_id.get(),
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
self.create_element_node(new);
|
self.create_element_node(new);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
new.dom_id.set(Some(root));
|
new.dom_id.set(root);
|
||||||
|
|
||||||
// Don't push the root if we don't have to
|
// Don't push the root if we don't have to
|
||||||
let mut has_comitted = false;
|
let mut has_comitted = false;
|
||||||
let mut please_commit = |edits: &mut Vec<DomEdit>| {
|
let mut please_commit = |edits: &mut Vec<DomEdit>| {
|
||||||
if !has_comitted {
|
if !has_comitted {
|
||||||
has_comitted = true;
|
has_comitted = true;
|
||||||
edits.push(PushRoot { id: root.as_u64() });
|
if let Some(root) = root {
|
||||||
|
edits.push(PushRoot { id: root.as_u64() });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -449,7 +451,6 @@ impl<'bump> DiffMachine<'bump> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// TODO: provide some sort of report on how "good" the diffing was
|
|
||||||
please_commit(&mut self.mutations.edits);
|
please_commit(&mut self.mutations.edits);
|
||||||
for attribute in old.attributes {
|
for attribute in old.attributes {
|
||||||
self.mutations.remove_attribute(attribute);
|
self.mutations.remove_attribute(attribute);
|
||||||
@@ -467,7 +468,6 @@ impl<'bump> DiffMachine<'bump> {
|
|||||||
// We also need to make sure that all listeners are properly attached to the parent scope (fix_listener)
|
// We also need to make sure that all listeners are properly attached to the parent scope (fix_listener)
|
||||||
//
|
//
|
||||||
// TODO: take a more efficient path than this
|
// TODO: take a more efficient path than this
|
||||||
|
|
||||||
if let Some(cur_scope_id) = self.stack.current_scope() {
|
if let Some(cur_scope_id) = self.stack.current_scope() {
|
||||||
let scope = self.vdom.get_scope(cur_scope_id).unwrap();
|
let scope = self.vdom.get_scope(cur_scope_id).unwrap();
|
||||||
|
|
||||||
@@ -487,22 +487,21 @@ impl<'bump> DiffMachine<'bump> {
|
|||||||
self.mutations.remove_event_listener(listener.event);
|
self.mutations.remove_event_listener(listener.event);
|
||||||
}
|
}
|
||||||
for listener in new.listeners {
|
for listener in new.listeners {
|
||||||
listener.mounted_node.set(Some(root));
|
listener.mounted_node.set(root);
|
||||||
self.mutations.new_event_listener(listener, cur_scope_id);
|
self.mutations.new_event_listener(listener, cur_scope_id);
|
||||||
self.attach_listener_to_scope(listener, scope);
|
self.attach_listener_to_scope(listener, scope);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if has_comitted {
|
|
||||||
self.mutations.pop();
|
|
||||||
}
|
|
||||||
|
|
||||||
self.diff_children(old.children, new.children);
|
self.diff_children(old.children, new.children);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn diff_component_nodes(
|
fn diff_component_nodes(
|
||||||
&mut self,
|
&mut self,
|
||||||
|
old_node: &'bump VNode<'bump>,
|
||||||
|
new_node: &'bump VNode<'bump>,
|
||||||
|
|
||||||
old: &'bump VComponent<'bump>,
|
old: &'bump VComponent<'bump>,
|
||||||
new: &'bump VComponent<'bump>,
|
new: &'bump VComponent<'bump>,
|
||||||
) {
|
) {
|
||||||
@@ -526,7 +525,7 @@ impl<'bump> DiffMachine<'bump> {
|
|||||||
|
|
||||||
match compare(new) {
|
match compare(new) {
|
||||||
true => {
|
true => {
|
||||||
// the props are the same...
|
// the props are the same... do nothing
|
||||||
}
|
}
|
||||||
false => {
|
false => {
|
||||||
// the props are different...
|
// the props are different...
|
||||||
@@ -540,25 +539,8 @@ impl<'bump> DiffMachine<'bump> {
|
|||||||
|
|
||||||
self.seen_scopes.insert(scope_addr);
|
self.seen_scopes.insert(scope_addr);
|
||||||
} else {
|
} else {
|
||||||
todo!();
|
self.stack
|
||||||
|
.create_node(new_node, MountType::Replace { old: old_node });
|
||||||
// let mut old_iter = RealChildIterator::new(old_node, &self.vdom);
|
|
||||||
// let first = old_iter
|
|
||||||
// .next()
|
|
||||||
// .expect("Components should generate a placeholder root");
|
|
||||||
|
|
||||||
// // remove any leftovers
|
|
||||||
// for to_remove in old_iter {
|
|
||||||
// self.mutations.push_root(to_remove.direct_id());
|
|
||||||
// self.mutations.remove();
|
|
||||||
// }
|
|
||||||
|
|
||||||
// // seems like we could combine this into a single instruction....
|
|
||||||
// self.replace_node_with_node(first.direct_id(), old_node, new_node);
|
|
||||||
|
|
||||||
// // Wipe the old one and plant the new one
|
|
||||||
// let old_scope = old.ass_scope.get().unwrap();
|
|
||||||
// self.destroy_scopes(old_scope);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -608,7 +590,7 @@ impl<'bump> DiffMachine<'bump> {
|
|||||||
old_anchor.dom_id.set(new_anchor.dom_id.get());
|
old_anchor.dom_id.set(new_anchor.dom_id.get());
|
||||||
}
|
}
|
||||||
([VNode::Anchor(anchor)], _) => {
|
([VNode::Anchor(anchor)], _) => {
|
||||||
let el = anchor.dom_id.get().unwrap();
|
let el = anchor.dom_id.get();
|
||||||
self.stack
|
self.stack
|
||||||
.create_children(new, MountType::ReplaceByElementId { el });
|
.create_children(new, MountType::ReplaceByElementId { el });
|
||||||
}
|
}
|
||||||
@@ -652,7 +634,7 @@ impl<'bump> DiffMachine<'bump> {
|
|||||||
debug_assert!(!old.is_empty());
|
debug_assert!(!old.is_empty());
|
||||||
|
|
||||||
for (new, old) in new.iter().zip(old.iter()).rev() {
|
for (new, old) in new.iter().zip(old.iter()).rev() {
|
||||||
self.stack.push(DiffInstruction::DiffNode { new, old });
|
self.stack.push(DiffInstruction::Diff { new, old });
|
||||||
}
|
}
|
||||||
|
|
||||||
if old.len() > new.len() {
|
if old.len() > new.len() {
|
||||||
@@ -748,7 +730,7 @@ impl<'bump> DiffMachine<'bump> {
|
|||||||
if old.key() != new.key() {
|
if old.key() != new.key() {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
self.stack.push(DiffInstruction::DiffNode { old, new });
|
self.stack.push(DiffInstruction::Diff { old, new });
|
||||||
left_offset += 1;
|
left_offset += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -887,8 +869,8 @@ impl<'bump> DiffMachine<'bump> {
|
|||||||
stack.create_node(new_node, MountType::Absorb);
|
stack.create_node(new_node, MountType::Absorb);
|
||||||
} else {
|
} else {
|
||||||
// this funciton should never take LIS indicies
|
// this funciton should never take LIS indicies
|
||||||
stack.push(DiffInstruction::PrepareMoveNode { node: new_node });
|
stack.push(DiffInstruction::PrepareMove { node: new_node });
|
||||||
stack.push(DiffInstruction::DiffNode {
|
stack.push(DiffInstruction::Diff {
|
||||||
new: new_node,
|
new: new_node,
|
||||||
old: &old[old_index],
|
old: &old[old_index],
|
||||||
});
|
});
|
||||||
@@ -943,7 +925,7 @@ impl<'bump> DiffMachine<'bump> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for idx in lis_sequence.iter().rev() {
|
for idx in lis_sequence.iter().rev() {
|
||||||
self.stack.push(DiffInstruction::DiffNode {
|
self.stack.push(DiffInstruction::Diff {
|
||||||
new: &new[*idx],
|
new: &new[*idx],
|
||||||
old: &old[new_index_to_old_index[*idx]],
|
old: &old[new_index_to_old_index[*idx]],
|
||||||
});
|
});
|
||||||
@@ -998,6 +980,14 @@ impl<'bump> DiffMachine<'bump> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn replace_and_create_one_with_many(
|
||||||
|
&mut self,
|
||||||
|
old: &'bump VNode<'bump>,
|
||||||
|
new: &'bump [VNode<'bump>],
|
||||||
|
) {
|
||||||
|
//
|
||||||
|
}
|
||||||
|
|
||||||
fn replace_and_create_many_with_one(
|
fn replace_and_create_many_with_one(
|
||||||
&mut self,
|
&mut self,
|
||||||
old: &'bump [VNode<'bump>],
|
old: &'bump [VNode<'bump>],
|
||||||
@@ -1070,60 +1060,12 @@ impl<'bump> DiffMachine<'bump> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn create_garbage(&mut self, node: &'bump VNode<'bump>) {
|
|
||||||
match self
|
|
||||||
.stack
|
|
||||||
.current_scope()
|
|
||||||
.and_then(|id| self.vdom.get_scope(id))
|
|
||||||
{
|
|
||||||
Some(scope) => {
|
|
||||||
let garbage: &'bump VNode<'static> = unsafe { std::mem::transmute(node) };
|
|
||||||
scope.pending_garbage.borrow_mut().push(garbage);
|
|
||||||
}
|
|
||||||
None => {
|
|
||||||
log::info!("No scope to collect garbage into")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Adds a listener closure to a scope during diff.
|
/// Adds a listener closure to a scope during diff.
|
||||||
fn attach_listener_to_scope<'a>(&mut self, listener: &'a Listener<'a>, scope: &Scope) {
|
fn attach_listener_to_scope<'a>(&mut self, listener: &'a Listener<'a>, scope: &Scope) {
|
||||||
let mut queue = scope.listeners.borrow_mut();
|
let mut queue = scope.listeners.borrow_mut();
|
||||||
let long_listener: &'a Listener<'static> = unsafe { std::mem::transmute(listener) };
|
let long_listener: &'a Listener<'static> = unsafe { std::mem::transmute(listener) };
|
||||||
queue.push(long_listener as *const _)
|
queue.push(long_listener as *const _)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Destroy a scope and all of its descendents.
|
|
||||||
///
|
|
||||||
/// Calling this will run the destuctors on all hooks in the tree.
|
|
||||||
/// It will also add the destroyed nodes to the `seen_nodes` cache to prevent them from being renderered.
|
|
||||||
fn destroy_scopes(&mut self, old_scope: ScopeId) {
|
|
||||||
let mut nodes_to_delete = vec![old_scope];
|
|
||||||
let mut scopes_to_explore = vec![old_scope];
|
|
||||||
|
|
||||||
// explore the scope tree breadth first
|
|
||||||
while let Some(scope_id) = scopes_to_explore.pop() {
|
|
||||||
// If we're planning on deleting this node, then we don't need to both rendering it
|
|
||||||
self.seen_scopes.insert(scope_id);
|
|
||||||
let scope = self.vdom.get_scope(scope_id).unwrap();
|
|
||||||
for child in scope.descendents.borrow().iter() {
|
|
||||||
// Add this node to be explored
|
|
||||||
scopes_to_explore.push(child.clone());
|
|
||||||
|
|
||||||
// Also add it for deletion
|
|
||||||
nodes_to_delete.push(child.clone());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Delete all scopes that we found as part of this subtree
|
|
||||||
for node in nodes_to_delete {
|
|
||||||
log::debug!("Removing scope {:#?}", node);
|
|
||||||
let _scope = self.vdom.try_remove(node).unwrap();
|
|
||||||
// do anything we need to do to delete the scope
|
|
||||||
// I think we need to run the destructors on the hooks
|
|
||||||
// TODO
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn compare_strs(a: &str, b: &str) -> bool {
|
fn compare_strs(a: &str, b: &str) -> bool {
|
||||||
|
|||||||
@@ -3,23 +3,18 @@ use smallvec::{smallvec, SmallVec};
|
|||||||
|
|
||||||
/// The stack instructions we use to diff and create new nodes.
|
/// The stack instructions we use to diff and create new nodes.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum DiffInstruction<'a> {
|
pub(crate) enum DiffInstruction<'a> {
|
||||||
DiffNode {
|
Diff {
|
||||||
old: &'a VNode<'a>,
|
old: &'a VNode<'a>,
|
||||||
new: &'a VNode<'a>,
|
new: &'a VNode<'a>,
|
||||||
},
|
},
|
||||||
|
|
||||||
DiffChildren {
|
|
||||||
old: &'a [VNode<'a>],
|
|
||||||
new: &'a [VNode<'a>],
|
|
||||||
},
|
|
||||||
|
|
||||||
Create {
|
Create {
|
||||||
node: &'a VNode<'a>,
|
node: &'a VNode<'a>,
|
||||||
},
|
},
|
||||||
|
|
||||||
/// pushes the node elements onto the stack for use in mount
|
/// pushes the node elements onto the stack for use in mount
|
||||||
PrepareMoveNode {
|
PrepareMove {
|
||||||
node: &'a VNode<'a>,
|
node: &'a VNode<'a>,
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -35,7 +30,7 @@ pub enum MountType<'a> {
|
|||||||
Absorb,
|
Absorb,
|
||||||
Append,
|
Append,
|
||||||
Replace { old: &'a VNode<'a> },
|
Replace { old: &'a VNode<'a> },
|
||||||
ReplaceByElementId { el: ElementId },
|
ReplaceByElementId { el: Option<ElementId> },
|
||||||
InsertAfter { other_node: &'a VNode<'a> },
|
InsertAfter { other_node: &'a VNode<'a> },
|
||||||
InsertBefore { other_node: &'a VNode<'a> },
|
InsertBefore { other_node: &'a VNode<'a> },
|
||||||
}
|
}
|
||||||
@@ -63,6 +58,10 @@ impl<'bump> DiffStack<'bump> {
|
|||||||
self.instructions.pop()
|
self.instructions.pop()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn pop_off_scope(&mut self) {
|
||||||
|
self.scope_stack.pop();
|
||||||
|
}
|
||||||
|
|
||||||
pub fn pop_scope(&mut self) -> Option<ScopeId> {
|
pub fn pop_scope(&mut self) -> Option<ScopeId> {
|
||||||
self.scope_stack.pop()
|
self.scope_stack.pop()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -125,8 +125,13 @@ pub mod on {
|
|||||||
|
|
||||||
let callback: BumpBox<dyn FnMut(SyntheticEvent) + 'a> = unsafe { BumpBox::from_raw(cb) };
|
let callback: BumpBox<dyn FnMut(SyntheticEvent) + 'a> = unsafe { BumpBox::from_raw(cb) };
|
||||||
|
|
||||||
|
|
||||||
|
// ie oncopy
|
||||||
let event_name = stringify!($name);
|
let event_name = stringify!($name);
|
||||||
|
|
||||||
|
// ie copy
|
||||||
let shortname: &'static str = &event_name[2..];
|
let shortname: &'static str = &event_name[2..];
|
||||||
|
|
||||||
Listener {
|
Listener {
|
||||||
event: shortname,
|
event: shortname,
|
||||||
mounted_node: Cell::new(None),
|
mounted_node: Cell::new(None),
|
||||||
|
|||||||
@@ -3,45 +3,28 @@ use std::{
|
|||||||
cell::{Cell, RefCell, UnsafeCell},
|
cell::{Cell, RefCell, UnsafeCell},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/// An abstraction over internally stored data using a hook-based memory layout.
|
||||||
|
///
|
||||||
|
/// Hooks are allocated using Boxes and then our stored references are given out.
|
||||||
|
///
|
||||||
|
/// It's unsafe to "reset" the hooklist, but it is safe to add hooks into it.
|
||||||
|
///
|
||||||
|
/// Todo: this could use its very own bump arena, but that might be a tad overkill
|
||||||
|
#[derive(Default)]
|
||||||
pub(crate) struct HookList {
|
pub(crate) struct HookList {
|
||||||
vals: RefCell<Vec<InnerHook<Box<dyn Any>>>>,
|
vals: RefCell<Vec<UnsafeCell<Box<dyn Any>>>>,
|
||||||
idx: Cell<usize>,
|
idx: Cell<usize>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for HookList {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self {
|
|
||||||
vals: Default::default(),
|
|
||||||
idx: Cell::new(0),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct InnerHook<T> {
|
|
||||||
cell: UnsafeCell<T>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T> InnerHook<T> {
|
|
||||||
fn new(new: T) -> Self {
|
|
||||||
Self {
|
|
||||||
cell: UnsafeCell::new(new),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl HookList {
|
impl HookList {
|
||||||
pub(crate) fn next<T: 'static>(&self) -> Option<&mut T> {
|
pub(crate) fn next<T: 'static>(&self) -> Option<&mut T> {
|
||||||
self.vals.borrow().get(self.idx.get()).and_then(|inn| {
|
self.vals.borrow().get(self.idx.get()).and_then(|inn| {
|
||||||
self.idx.set(self.idx.get() + 1);
|
self.idx.set(self.idx.get() + 1);
|
||||||
let raw_box = unsafe { &mut *inn.cell.get() };
|
let raw_box = unsafe { &mut *inn.get() };
|
||||||
raw_box.downcast_mut::<T>()
|
raw_box.downcast_mut::<T>()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn push<T: 'static>(&self, new: T) {
|
|
||||||
self.vals.borrow_mut().push(InnerHook::new(Box::new(new)))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// This resets the internal iterator count
|
/// This resets the internal iterator count
|
||||||
/// It's okay that we've given out each hook, but now we have the opportunity to give it out again
|
/// It's okay that we've given out each hook, but now we have the opportunity to give it out again
|
||||||
/// Therefore, resetting is cosudered unsafe
|
/// Therefore, resetting is cosudered unsafe
|
||||||
@@ -52,17 +35,18 @@ impl HookList {
|
|||||||
self.idx.set(0);
|
self.idx.set(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
pub(crate) fn push<T: 'static>(&self, new: T) {
|
||||||
|
self.vals.borrow_mut().push(UnsafeCell::new(Box::new(new)))
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) fn len(&self) -> usize {
|
pub(crate) fn len(&self) -> usize {
|
||||||
self.vals.borrow().len()
|
self.vals.borrow().len()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub(crate) fn cur_idx(&self) -> usize {
|
pub(crate) fn cur_idx(&self) -> usize {
|
||||||
self.idx.get()
|
self.idx.get()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub(crate) fn at_end(&self) -> bool {
|
pub(crate) fn at_end(&self) -> bool {
|
||||||
self.cur_idx() >= self.len()
|
self.cur_idx() >= self.len()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -56,9 +56,9 @@ pub(crate) mod innerlude {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub use crate::innerlude::{
|
pub use crate::innerlude::{
|
||||||
format_args_f, html, rsx, Context, DiffInstruction, DioxusElement, DomEdit, DomTree, ElementId,
|
format_args_f, html, rsx, Context, DioxusElement, DomEdit, DomTree, ElementId, EventPriority,
|
||||||
EventPriority, LazyNodes, MountType, Mutations, NodeFactory, Properties, ScopeId,
|
LazyNodes, MountType, Mutations, NodeFactory, Properties, ScopeId, SuspendedContext,
|
||||||
SuspendedContext, SyntheticEvent, TestDom, UserEvent, VNode, VirtualDom, FC,
|
SyntheticEvent, TestDom, UserEvent, VNode, VirtualDom, FC,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub mod prelude {
|
pub mod prelude {
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ pub enum VNode<'src> {
|
|||||||
///
|
///
|
||||||
/// # Example
|
/// # Example
|
||||||
///
|
///
|
||||||
|
/// ```
|
||||||
/// let node = cx.render(rsx!{ "hello" }).unwrap();
|
/// let node = cx.render(rsx!{ "hello" }).unwrap();
|
||||||
///
|
///
|
||||||
/// if let VNode::Text(vtext) = node {
|
/// if let VNode::Text(vtext) = node {
|
||||||
@@ -248,7 +249,7 @@ pub struct Listener<'bump> {
|
|||||||
|
|
||||||
/// The type of event to listen for.
|
/// The type of event to listen for.
|
||||||
///
|
///
|
||||||
/// IE "onclick" - whatever the renderer needs to attach the listener by name.
|
/// IE "click" - whatever the renderer needs to attach the listener by name.
|
||||||
pub event: &'static str,
|
pub event: &'static str,
|
||||||
|
|
||||||
pub(crate) callback: RefCell<Option<BumpBox<'bump, dyn FnMut(SyntheticEvent) + 'bump>>>,
|
pub(crate) callback: RefCell<Option<BumpBox<'bump, dyn FnMut(SyntheticEvent) + 'bump>>>,
|
||||||
@@ -283,8 +284,7 @@ pub struct VComponent<'src> {
|
|||||||
pub struct VSuspended<'a> {
|
pub struct VSuspended<'a> {
|
||||||
pub task_id: u64,
|
pub task_id: u64,
|
||||||
pub dom_id: Cell<Option<ElementId>>,
|
pub dom_id: Cell<Option<ElementId>>,
|
||||||
pub(crate) callback:
|
pub callback: RefCell<Option<BumpBox<'a, dyn FnMut(SuspendedContext<'a>) -> DomTree<'a>>>>,
|
||||||
RefCell<Option<BumpBox<'a, dyn FnMut(SuspendedContext<'a>) -> DomTree<'a>>>>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// This struct provides an ergonomic API to quickly build VNodes.
|
/// This struct provides an ergonomic API to quickly build VNodes.
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ Some essential reading:
|
|||||||
|
|
||||||
Dioxus is a framework for "user experience" - not just "user interfaces." Part of the "experience" is keeping the UI
|
Dioxus is a framework for "user experience" - not just "user interfaces." Part of the "experience" is keeping the UI
|
||||||
snappy and "jank free" even under heavy work loads. Dioxus already has the "speed" part figured out - but there's no
|
snappy and "jank free" even under heavy work loads. Dioxus already has the "speed" part figured out - but there's no
|
||||||
point if being "fast" if you can't also be "responsive."
|
point in being "fast" if you can't also be "responsive."
|
||||||
|
|
||||||
As such, Dioxus can manually decide on what work is most important at any given moment in time. With a properly tuned
|
As such, Dioxus can manually decide on what work is most important at any given moment in time. With a properly tuned
|
||||||
priority system, Dioxus can ensure that user interaction is prioritized and committed as soon as possible (sub 100ms).
|
priority system, Dioxus can ensure that user interaction is prioritized and committed as soon as possible (sub 100ms).
|
||||||
@@ -87,7 +87,7 @@ use std::{
|
|||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct EventChannel {
|
pub(crate) struct EventChannel {
|
||||||
pub task_counter: Rc<Cell<u64>>,
|
pub task_counter: Rc<Cell<u64>>,
|
||||||
pub sender: UnboundedSender<SchedulerMsg>,
|
pub sender: UnboundedSender<SchedulerMsg>,
|
||||||
pub schedule_any_immediate: Rc<dyn Fn(ScopeId)>,
|
pub schedule_any_immediate: Rc<dyn Fn(ScopeId)>,
|
||||||
@@ -96,8 +96,13 @@ pub struct EventChannel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub enum SchedulerMsg {
|
pub enum SchedulerMsg {
|
||||||
Immediate(ScopeId),
|
// events from the host
|
||||||
UiEvent(UserEvent),
|
UiEvent(UserEvent),
|
||||||
|
|
||||||
|
// setstate
|
||||||
|
Immediate(ScopeId),
|
||||||
|
|
||||||
|
// tasks
|
||||||
SubmitTask(FiberTask, u64),
|
SubmitTask(FiberTask, u64),
|
||||||
ToggleTask(u64),
|
ToggleTask(u64),
|
||||||
PauseTask(u64),
|
PauseTask(u64),
|
||||||
@@ -109,7 +114,7 @@ pub enum SchedulerMsg {
|
|||||||
///
|
///
|
||||||
/// Each scope has the ability to lightly interact with the scheduler (IE, schedule an update) but ultimately the scheduler calls the components.
|
/// Each scope has the ability to lightly interact with the scheduler (IE, schedule an update) but ultimately the scheduler calls the components.
|
||||||
///
|
///
|
||||||
/// In Dioxus, the scheduler provides 3 priority levels - each with their own "DiffMachine". The DiffMachine state can be saved if the deadline runs
|
/// In Dioxus, the scheduler provides 4 priority levels - each with their own "DiffMachine". The DiffMachine state can be saved if the deadline runs
|
||||||
/// out.
|
/// out.
|
||||||
///
|
///
|
||||||
/// Saved DiffMachine state can be self-referential, so we need to be careful about how we save it. All self-referential data is a link between
|
/// Saved DiffMachine state can be self-referential, so we need to be careful about how we save it. All self-referential data is a link between
|
||||||
@@ -211,6 +216,7 @@ impl Scheduler {
|
|||||||
|
|
||||||
Self {
|
Self {
|
||||||
pool,
|
pool,
|
||||||
|
|
||||||
receiver,
|
receiver,
|
||||||
|
|
||||||
async_tasks: FuturesUnordered::new(),
|
async_tasks: FuturesUnordered::new(),
|
||||||
@@ -219,7 +225,6 @@ impl Scheduler {
|
|||||||
|
|
||||||
heuristics,
|
heuristics,
|
||||||
|
|
||||||
// a storage for our receiver to dump into
|
|
||||||
ui_events: VecDeque::new(),
|
ui_events: VecDeque::new(),
|
||||||
|
|
||||||
pending_immediates: VecDeque::new(),
|
pending_immediates: VecDeque::new(),
|
||||||
@@ -230,7 +235,7 @@ impl Scheduler {
|
|||||||
|
|
||||||
current_priority: EventPriority::Low,
|
current_priority: EventPriority::Low,
|
||||||
|
|
||||||
// a dedicated fiber for each priority
|
// sorted high to low by priority (0 = immediate, 3 = low)
|
||||||
lanes: [
|
lanes: [
|
||||||
PriorityLane::new(),
|
PriorityLane::new(),
|
||||||
PriorityLane::new(),
|
PriorityLane::new(),
|
||||||
@@ -248,25 +253,69 @@ impl Scheduler {
|
|||||||
|
|
||||||
// Converts UI events into dirty scopes with various priorities
|
// Converts UI events into dirty scopes with various priorities
|
||||||
pub fn consume_pending_events(&mut self) {
|
pub fn consume_pending_events(&mut self) {
|
||||||
|
// consume all events that are "continuous" to be batched
|
||||||
|
// if we run into a discrete event, then bail early
|
||||||
|
|
||||||
while let Some(trigger) = self.ui_events.pop_back() {
|
while let Some(trigger) = self.ui_events.pop_back() {
|
||||||
if let Some(scope) = self.pool.get_scope_mut(trigger.scope) {
|
if let Some(scope) = self.pool.get_scope_mut(trigger.scope) {
|
||||||
if let Some(element) = trigger.mounted_dom_id {
|
if let Some(element) = trigger.mounted_dom_id {
|
||||||
let priority = match &trigger.event {
|
let priority = match trigger.name {
|
||||||
SyntheticEvent::ClipboardEvent(_) => {}
|
// clipboard
|
||||||
SyntheticEvent::CompositionEvent(_) => {}
|
"copy" | "cut" | "paste" => EventPriority::Medium,
|
||||||
SyntheticEvent::KeyboardEvent(_) => {}
|
|
||||||
SyntheticEvent::FocusEvent(_) => {}
|
// Composition
|
||||||
SyntheticEvent::FormEvent(_) => {}
|
"compositionend" | "compositionstart" | "compositionupdate" => {
|
||||||
SyntheticEvent::SelectionEvent(_) => {}
|
EventPriority::Low
|
||||||
SyntheticEvent::TouchEvent(_) => {}
|
}
|
||||||
SyntheticEvent::WheelEvent(_) => {}
|
|
||||||
SyntheticEvent::MediaEvent(_) => {}
|
// Keyboard
|
||||||
SyntheticEvent::AnimationEvent(_) => {}
|
"keydown" | "keypress" | "keyup" => EventPriority::Low,
|
||||||
SyntheticEvent::TransitionEvent(_) => {}
|
|
||||||
SyntheticEvent::ToggleEvent(_) => {}
|
// Focus
|
||||||
SyntheticEvent::MouseEvent(_) => {}
|
"focus" | "blur" => EventPriority::Low,
|
||||||
SyntheticEvent::PointerEvent(_) => {}
|
|
||||||
SyntheticEvent::GenericEvent(_) => {}
|
// Form
|
||||||
|
"change" | "input" | "invalid" | "reset" | "submit" => EventPriority::Low,
|
||||||
|
|
||||||
|
// Mouse
|
||||||
|
"click" | "contextmenu" | "doubleclick" | "drag" | "dragend"
|
||||||
|
| "dragenter" | "dragexit" | "dragleave" | "dragover" | "dragstart"
|
||||||
|
| "drop" | "mousedown" | "mouseenter" | "mouseleave" | "mousemove"
|
||||||
|
| "mouseout" | "mouseover" | "mouseup" => EventPriority::Low,
|
||||||
|
|
||||||
|
// Pointer
|
||||||
|
"pointerdown" | "pointermove" | "pointerup" | "pointercancel"
|
||||||
|
| "gotpointercapture" | "lostpointercapture" | "pointerenter"
|
||||||
|
| "pointerleave" | "pointerover" | "pointerout" => EventPriority::Low,
|
||||||
|
|
||||||
|
// Selection
|
||||||
|
"select" | "touchcancel" | "touchend" => EventPriority::Low,
|
||||||
|
|
||||||
|
// Touch
|
||||||
|
"touchmove" | "touchstart" => EventPriority::Low,
|
||||||
|
|
||||||
|
// Wheel
|
||||||
|
"scroll" | "wheel" => EventPriority::Low,
|
||||||
|
|
||||||
|
// Media
|
||||||
|
"abort" | "canplay" | "canplaythrough" | "durationchange" | "emptied"
|
||||||
|
| "encrypted" | "ended" | "error" | "loadeddata" | "loadedmetadata"
|
||||||
|
| "loadstart" | "pause" | "play" | "playing" | "progress"
|
||||||
|
| "ratechange" | "seeked" | "seeking" | "stalled" | "suspend"
|
||||||
|
| "timeupdate" | "volumechange" | "waiting" => EventPriority::Low,
|
||||||
|
|
||||||
|
// Animation
|
||||||
|
"animationstart" | "animationend" | "animationiteration" => {
|
||||||
|
EventPriority::Low
|
||||||
|
}
|
||||||
|
|
||||||
|
// Transition
|
||||||
|
"transitionend" => EventPriority::Low,
|
||||||
|
|
||||||
|
// Toggle
|
||||||
|
"toggle" => EventPriority::Low,
|
||||||
|
|
||||||
|
_ => EventPriority::Low,
|
||||||
};
|
};
|
||||||
|
|
||||||
scope.call_listener(trigger.event, element);
|
scope.call_listener(trigger.event, element);
|
||||||
@@ -341,7 +390,6 @@ impl Scheduler {
|
|||||||
self.manually_poll_events();
|
self.manually_poll_events();
|
||||||
|
|
||||||
if !self.has_any_work() {
|
if !self.has_any_work() {
|
||||||
self.clean_up_garbage();
|
|
||||||
return committed_mutations;
|
return committed_mutations;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -349,7 +397,7 @@ impl Scheduler {
|
|||||||
|
|
||||||
while self.has_any_work() {
|
while self.has_any_work() {
|
||||||
self.shift_priorities();
|
self.shift_priorities();
|
||||||
self.work_on_current_lane(&mut || false, &mut committed_mutations);
|
self.work_on_current_lane(|| false, &mut committed_mutations);
|
||||||
}
|
}
|
||||||
|
|
||||||
committed_mutations
|
committed_mutations
|
||||||
@@ -393,7 +441,6 @@ impl Scheduler {
|
|||||||
|
|
||||||
// Wait for any new events if we have nothing to do
|
// Wait for any new events if we have nothing to do
|
||||||
if !self.has_any_work() {
|
if !self.has_any_work() {
|
||||||
self.clean_up_garbage();
|
|
||||||
let deadline_expired = self.wait_for_any_trigger(&mut deadline_reached).await;
|
let deadline_expired = self.wait_for_any_trigger(&mut deadline_reached).await;
|
||||||
|
|
||||||
if deadline_expired {
|
if deadline_expired {
|
||||||
@@ -425,7 +472,7 @@ impl Scheduler {
|
|||||||
/// Returns true if the lane is finished before the deadline could be met.
|
/// Returns true if the lane is finished before the deadline could be met.
|
||||||
pub fn work_on_current_lane(
|
pub fn work_on_current_lane(
|
||||||
&mut self,
|
&mut self,
|
||||||
deadline_reached: &mut impl FnMut() -> bool,
|
deadline_reached: impl FnMut() -> bool,
|
||||||
mutations: &mut Vec<Mutations>,
|
mutations: &mut Vec<Mutations>,
|
||||||
) -> bool {
|
) -> bool {
|
||||||
// Work through the current subtree, and commit the results when it finishes
|
// Work through the current subtree, and commit the results when it finishes
|
||||||
@@ -448,7 +495,7 @@ impl Scheduler {
|
|||||||
if let Some(scope) = self.current_lane().dirty_scopes.pop() {
|
if let Some(scope) = self.current_lane().dirty_scopes.pop() {
|
||||||
let component = self.pool.get_scope(scope).unwrap();
|
let component = self.pool.get_scope(scope).unwrap();
|
||||||
let (old, new) = (component.frames.wip_head(), component.frames.fin_head());
|
let (old, new) = (component.frames.wip_head(), component.frames.fin_head());
|
||||||
machine.stack.push(DiffInstruction::DiffNode { new, old });
|
machine.stack.push(DiffInstruction::Diff { new, old });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -537,61 +584,6 @@ impl Scheduler {
|
|||||||
fn collect_garbage(&mut self, id: ElementId) {
|
fn collect_garbage(&mut self, id: ElementId) {
|
||||||
//
|
//
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn clean_up_garbage(&mut self) {
|
|
||||||
let mut scopes_to_kill = Vec::new();
|
|
||||||
let mut garbage_list = Vec::new();
|
|
||||||
|
|
||||||
for scope in self.garbage_scopes.drain() {
|
|
||||||
let scope = self.pool.get_scope_mut(scope).unwrap();
|
|
||||||
for node in scope.consume_garbage() {
|
|
||||||
garbage_list.push(node);
|
|
||||||
}
|
|
||||||
|
|
||||||
while let Some(node) = garbage_list.pop() {
|
|
||||||
match &node {
|
|
||||||
VNode::Text(_) => {
|
|
||||||
self.pool.collect_garbage(node.mounted_id());
|
|
||||||
}
|
|
||||||
VNode::Anchor(_) => {
|
|
||||||
self.pool.collect_garbage(node.mounted_id());
|
|
||||||
}
|
|
||||||
VNode::Suspended(_) => {
|
|
||||||
self.pool.collect_garbage(node.mounted_id());
|
|
||||||
}
|
|
||||||
|
|
||||||
VNode::Element(el) => {
|
|
||||||
self.pool.collect_garbage(node.mounted_id());
|
|
||||||
for child in el.children {
|
|
||||||
garbage_list.push(child);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
VNode::Fragment(frag) => {
|
|
||||||
for child in frag.children {
|
|
||||||
garbage_list.push(child);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
VNode::Component(comp) => {
|
|
||||||
// TODO: run the hook destructors and then even delete the scope
|
|
||||||
|
|
||||||
let scope_id = comp.associated_scope.get().unwrap();
|
|
||||||
let scope = self.pool.get_scope(scope_id).unwrap();
|
|
||||||
let root = scope.root();
|
|
||||||
|
|
||||||
garbage_list.push(root);
|
|
||||||
scopes_to_kill.push(scope_id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for scope in scopes_to_kill.drain(..) {
|
|
||||||
//
|
|
||||||
// kill em
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) struct PriorityLane {
|
pub(crate) struct PriorityLane {
|
||||||
@@ -619,8 +611,8 @@ impl PriorityLane {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub struct TaskHandle {
|
pub struct TaskHandle {
|
||||||
pub sender: UnboundedSender<SchedulerMsg>,
|
pub(crate) sender: UnboundedSender<SchedulerMsg>,
|
||||||
pub our_id: u64,
|
pub(crate) our_id: u64,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TaskHandle {
|
impl TaskHandle {
|
||||||
|
|||||||
@@ -31,7 +31,6 @@ pub struct Scope {
|
|||||||
pub(crate) frames: ActiveFrame,
|
pub(crate) frames: ActiveFrame,
|
||||||
pub(crate) caller: Rc<WrappedCaller>,
|
pub(crate) caller: Rc<WrappedCaller>,
|
||||||
pub(crate) child_nodes: ScopeChildren<'static>,
|
pub(crate) child_nodes: ScopeChildren<'static>,
|
||||||
pub(crate) pending_garbage: RefCell<Vec<*const VNode<'static>>>,
|
|
||||||
|
|
||||||
// Listeners
|
// Listeners
|
||||||
pub(crate) listeners: RefCell<Vec<*const Listener<'static>>>,
|
pub(crate) listeners: RefCell<Vec<*const Listener<'static>>>,
|
||||||
@@ -90,7 +89,6 @@ impl Scope {
|
|||||||
listeners: Default::default(),
|
listeners: Default::default(),
|
||||||
borrowed_props: Default::default(),
|
borrowed_props: Default::default(),
|
||||||
descendents: Default::default(),
|
descendents: Default::default(),
|
||||||
pending_garbage: Default::default(),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -110,7 +108,6 @@ impl Scope {
|
|||||||
// Cycle to the next frame and then reset it
|
// Cycle to the next frame and then reset it
|
||||||
// This breaks any latent references, invalidating every pointer referencing into it.
|
// This breaks any latent references, invalidating every pointer referencing into it.
|
||||||
// Remove all the outdated listeners
|
// Remove all the outdated listeners
|
||||||
|
|
||||||
self.ensure_drop_safety(pool);
|
self.ensure_drop_safety(pool);
|
||||||
|
|
||||||
// Safety:
|
// Safety:
|
||||||
@@ -146,12 +143,6 @@ impl Scope {
|
|||||||
/// Refrences to hook data can only be stored in listeners and component props. During diffing, we make sure to log
|
/// Refrences to hook data can only be stored in listeners and component props. During diffing, we make sure to log
|
||||||
/// all listeners and borrowed props so we can clear them here.
|
/// all listeners and borrowed props so we can clear them here.
|
||||||
pub(crate) fn ensure_drop_safety(&mut self, pool: &ResourcePool) {
|
pub(crate) fn ensure_drop_safety(&mut self, pool: &ResourcePool) {
|
||||||
// make sure all garabge is collected before trying to proceed with anything else
|
|
||||||
debug_assert!(
|
|
||||||
self.pending_garbage.borrow().is_empty(),
|
|
||||||
"clean up your garabge please"
|
|
||||||
);
|
|
||||||
|
|
||||||
// todo!("arch changes");
|
// todo!("arch changes");
|
||||||
|
|
||||||
// make sure we drop all borrowed props manually to guarantee that their drop implementation is called before we
|
// make sure we drop all borrowed props manually to guarantee that their drop implementation is called before we
|
||||||
@@ -235,19 +226,6 @@ impl Scope {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn consume_garbage(&self) -> Vec<&VNode> {
|
|
||||||
self.pending_garbage
|
|
||||||
.borrow_mut()
|
|
||||||
.drain(..)
|
|
||||||
.map(|node| {
|
|
||||||
// safety: scopes cannot cycle without their garbage being collected. these nodes are safe
|
|
||||||
let node: &VNode<'static> = unsafe { &*node };
|
|
||||||
let node: &VNode = unsafe { std::mem::transmute(node) };
|
|
||||||
node
|
|
||||||
})
|
|
||||||
.collect::<Vec<_>>()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn root(&self) -> &VNode {
|
pub fn root(&self) -> &VNode {
|
||||||
self.frames.fin_head()
|
self.frames.fin_head()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,17 +20,25 @@ impl TestDom {
|
|||||||
NodeFactory::new(&self.bump)
|
NodeFactory::new(&self.bump)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn render<'a, F>(&'a self, lazy_nodes: LazyNodes<'a, F>) -> VNode<'a>
|
pub fn render_direct<'a, F>(&'a self, lazy_nodes: LazyNodes<'a, F>) -> VNode<'a>
|
||||||
where
|
where
|
||||||
F: FnOnce(NodeFactory<'a>) -> VNode<'a>,
|
F: FnOnce(NodeFactory<'a>) -> VNode<'a>,
|
||||||
{
|
{
|
||||||
lazy_nodes.into_vnode(NodeFactory::new(&self.bump))
|
lazy_nodes.into_vnode(NodeFactory::new(&self.bump))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn render<'a, F>(&'a self, lazy_nodes: LazyNodes<'a, F>) -> &'a VNode<'a>
|
||||||
|
where
|
||||||
|
F: FnOnce(NodeFactory<'a>) -> VNode<'a>,
|
||||||
|
{
|
||||||
|
self.bump
|
||||||
|
.alloc(lazy_nodes.into_vnode(NodeFactory::new(&self.bump)))
|
||||||
|
}
|
||||||
|
|
||||||
pub fn diff<'a>(&'a self, old: &'a VNode<'a>, new: &'a VNode<'a>) -> Mutations<'a> {
|
pub fn diff<'a>(&'a self, old: &'a VNode<'a>, new: &'a VNode<'a>) -> Mutations<'a> {
|
||||||
let mutations = Mutations::new();
|
let mutations = Mutations::new();
|
||||||
let mut machine = DiffMachine::new(mutations, &self.scheduler.pool);
|
let mut machine = DiffMachine::new(mutations, &self.scheduler.pool);
|
||||||
machine.stack.push(DiffInstruction::DiffNode { new, old });
|
machine.stack.push(DiffInstruction::Diff { new, old });
|
||||||
machine.mutations
|
machine.mutations
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -38,7 +46,7 @@ impl TestDom {
|
|||||||
where
|
where
|
||||||
F1: FnOnce(NodeFactory<'a>) -> VNode<'a>,
|
F1: FnOnce(NodeFactory<'a>) -> VNode<'a>,
|
||||||
{
|
{
|
||||||
let old = self.bump.alloc(self.render(left));
|
let old = self.bump.alloc(self.render_direct(left));
|
||||||
|
|
||||||
let mut machine = DiffMachine::new(Mutations::new(), &self.scheduler.pool);
|
let mut machine = DiffMachine::new(Mutations::new(), &self.scheduler.pool);
|
||||||
|
|
||||||
@@ -58,20 +66,18 @@ impl TestDom {
|
|||||||
F1: FnOnce(NodeFactory<'a>) -> VNode<'a>,
|
F1: FnOnce(NodeFactory<'a>) -> VNode<'a>,
|
||||||
F2: FnOnce(NodeFactory<'a>) -> VNode<'a>,
|
F2: FnOnce(NodeFactory<'a>) -> VNode<'a>,
|
||||||
{
|
{
|
||||||
let old = self.bump.alloc(self.render(left));
|
let (old, new) = (self.render(left), self.render(right));
|
||||||
|
|
||||||
let new = self.bump.alloc(self.render(right));
|
|
||||||
|
|
||||||
let mut machine = DiffMachine::new(Mutations::new(), &self.scheduler.pool);
|
let mut machine = DiffMachine::new(Mutations::new(), &self.scheduler.pool);
|
||||||
|
|
||||||
machine.stack.create_node(old, MountType::Append);
|
machine.stack.create_node(old, MountType::Append);
|
||||||
|
|
||||||
machine.work(&mut || false);
|
machine.work(|| false);
|
||||||
let create_edits = machine.mutations;
|
let create_edits = machine.mutations;
|
||||||
|
|
||||||
let mut machine = DiffMachine::new(Mutations::new(), &self.scheduler.pool);
|
let mut machine = DiffMachine::new(Mutations::new(), &self.scheduler.pool);
|
||||||
|
|
||||||
machine.stack.push(DiffInstruction::DiffNode { old, new });
|
machine.stack.push(DiffInstruction::Diff { old, new });
|
||||||
|
|
||||||
machine.work(&mut || false);
|
machine.work(&mut || false);
|
||||||
|
|
||||||
@@ -80,3 +86,9 @@ impl TestDom {
|
|||||||
(create_edits, edits)
|
(create_edits, edits)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl VirtualDom {
|
||||||
|
pub fn simulate(&mut self) {
|
||||||
|
//
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
//! # VirtualDOM Implementation for Rust
|
//! # VirtualDOM Implementation for Rust
|
||||||
|
//!
|
||||||
//! This module provides the primary mechanics to create a hook-based, concurrent VDOM for Rust.
|
//! This module provides the primary mechanics to create a hook-based, concurrent VDOM for Rust.
|
||||||
//!
|
//!
|
||||||
//! In this file, multiple items are defined. This file is big, but should be documented well to
|
//! In this file, multiple items are defined. This file is big, but should be documented well to
|
||||||
@@ -20,21 +21,38 @@
|
|||||||
|
|
||||||
use crate::innerlude::*;
|
use crate::innerlude::*;
|
||||||
use futures_util::{Future, FutureExt};
|
use futures_util::{Future, FutureExt};
|
||||||
use std::{
|
use std::{any::Any, pin::Pin};
|
||||||
any::{Any, TypeId},
|
|
||||||
pin::Pin,
|
|
||||||
rc::Rc,
|
|
||||||
};
|
|
||||||
|
|
||||||
/// An integrated virtual node system that progresses events and diffs UI trees.
|
/// An integrated virtual node system that progresses events and diffs UI trees.
|
||||||
|
///
|
||||||
/// Differences are converted into patches which a renderer can use to draw the UI.
|
/// Differences are converted into patches which a renderer can use to draw the UI.
|
||||||
///
|
///
|
||||||
|
/// If you are building an App with Dioxus, you probably won't want to reach for this directly, instead opting to defer
|
||||||
|
/// to a particular crate's wrapper over the [`VirtualDom`] API.
|
||||||
///
|
///
|
||||||
|
/// Example
|
||||||
|
/// ```rust
|
||||||
|
/// static App: FC<()> = |cx| {
|
||||||
|
/// cx.render(rsx!{
|
||||||
|
/// div {
|
||||||
|
/// "Hello World"
|
||||||
|
/// }
|
||||||
|
/// })
|
||||||
|
/// }
|
||||||
///
|
///
|
||||||
|
/// async fn main() {
|
||||||
|
/// let mut dom = VirtualDom::new(App);
|
||||||
|
/// let mut inital_edits = dom.rebuild();
|
||||||
|
/// initialize_screen(inital_edits);
|
||||||
///
|
///
|
||||||
///
|
/// loop {
|
||||||
///
|
/// let next_frame = TimeoutFuture::new(Duration::from_millis(16));
|
||||||
///
|
/// let edits = dom.run_with_deadline(next_frame).await;
|
||||||
|
/// apply_edits(edits);
|
||||||
|
/// render_frame();
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
pub struct VirtualDom {
|
pub struct VirtualDom {
|
||||||
scheduler: Scheduler,
|
scheduler: Scheduler,
|
||||||
|
|
||||||
@@ -42,7 +60,7 @@ pub struct VirtualDom {
|
|||||||
|
|
||||||
root_fc: Box<dyn Any>,
|
root_fc: Box<dyn Any>,
|
||||||
|
|
||||||
root_props: Pin<Box<dyn std::any::Any>>,
|
root_props: Pin<Box<dyn Any>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl VirtualDom {
|
impl VirtualDom {
|
||||||
@@ -178,28 +196,22 @@ impl VirtualDom {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Performs a *full* rebuild of the virtual dom, returning every edit required to generate the actual dom rom scratch
|
/// Performs a *full* rebuild of the virtual dom, returning every edit required to generate the actual dom from scratch
|
||||||
///
|
///
|
||||||
/// The diff machine expects the RealDom's stack to be the root of the application
|
/// The diff machine expects the RealDom's stack to be the root of the application.
|
||||||
///
|
///
|
||||||
/// Events like garabge collection, application of refs, etc are not handled by this method and can only be progressed
|
/// Tasks will not be polled with this method, nor will any events be processed from the event queue. Instead, the
|
||||||
/// through "run". We completely avoid the task scheduler infrastructure.
|
/// root component will be ran once and then diffed. All updates will flow out as mutations.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
/// ```
|
||||||
|
/// static App: FC<()> = |cx| cx.render(rsx!{ "hello world" });
|
||||||
|
/// let mut dom = VirtualDom::new();
|
||||||
|
/// let edits = dom.rebuild();
|
||||||
|
///
|
||||||
|
/// apply_edits(edits);
|
||||||
|
/// ```
|
||||||
pub fn rebuild<'s>(&'s mut self) -> Mutations<'s> {
|
pub fn rebuild<'s>(&'s mut self) -> Mutations<'s> {
|
||||||
let mut fut = self.rebuild_async().boxed_local();
|
|
||||||
|
|
||||||
loop {
|
|
||||||
if let Some(edits) = (&mut fut).now_or_never() {
|
|
||||||
break edits;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Rebuild the dom from the ground up
|
|
||||||
///
|
|
||||||
/// This method is asynchronous to prevent the application from blocking while the dom is being rebuilt. Computing
|
|
||||||
/// the diff and creating nodes can be expensive, so we provide this method to avoid blocking the main thread. This
|
|
||||||
/// method can be useful when needing to perform some crucial periodic tasks.
|
|
||||||
pub async fn rebuild_async<'s>(&'s mut self) -> Mutations<'s> {
|
|
||||||
let mut shared = self.scheduler.pool.clone();
|
let mut shared = self.scheduler.pool.clone();
|
||||||
let mut diff_machine = DiffMachine::new(Mutations::new(), &mut shared);
|
let mut diff_machine = DiffMachine::new(Mutations::new(), &mut shared);
|
||||||
|
|
||||||
@@ -214,9 +226,10 @@ impl VirtualDom {
|
|||||||
diff_machine
|
diff_machine
|
||||||
.stack
|
.stack
|
||||||
.create_node(cur_component.frames.fin_head(), MountType::Append);
|
.create_node(cur_component.frames.fin_head(), MountType::Append);
|
||||||
|
|
||||||
diff_machine.stack.scope_stack.push(self.base_scope);
|
diff_machine.stack.scope_stack.push(self.base_scope);
|
||||||
|
|
||||||
// let completed = diff_machine.work();
|
diff_machine.work(|| false);
|
||||||
} else {
|
} else {
|
||||||
// todo: should this be a hard error?
|
// todo: should this be a hard error?
|
||||||
log::warn!(
|
log::warn!(
|
||||||
|
|||||||
@@ -0,0 +1,53 @@
|
|||||||
|
# Testing of Dioxus core
|
||||||
|
|
||||||
|
|
||||||
|
NodeFactory
|
||||||
|
- [] rsx, html, NodeFactory generate the same structures
|
||||||
|
|
||||||
|
Diffing
|
||||||
|
- [x] create elements
|
||||||
|
- [x] create text
|
||||||
|
- [x] create fragments
|
||||||
|
- [x] create empty fragments (placeholders)
|
||||||
|
- [x] diff elements
|
||||||
|
- [x] diff from element/text to fragment
|
||||||
|
- [x] diff from element/text to empty fragment
|
||||||
|
- [x] diff to element with children works too
|
||||||
|
- [x] replace with works forward
|
||||||
|
- [x] replace with works backward
|
||||||
|
- [x] un-keyed diffing
|
||||||
|
- [x] keyed diffing
|
||||||
|
- [x] keyed diffing out of order
|
||||||
|
- [x] keyed diffing with prefix/suffix
|
||||||
|
- [x] suspended nodes work
|
||||||
|
|
||||||
|
Lifecycle
|
||||||
|
- [] Components mount properly
|
||||||
|
- [] Components create new child components
|
||||||
|
- [] Replaced components unmount old components and mount new
|
||||||
|
- [] Post-render effects are called
|
||||||
|
- []
|
||||||
|
|
||||||
|
|
||||||
|
Shared Context
|
||||||
|
- [] Shared context propagates downwards
|
||||||
|
- [] unwrapping shared context if it doesn't exist works too
|
||||||
|
|
||||||
|
Suspense
|
||||||
|
- [] use_suspense generates suspended nodes
|
||||||
|
|
||||||
|
|
||||||
|
Hooks
|
||||||
|
- [] Drop order is maintained
|
||||||
|
- [] Shared hook state is okay
|
||||||
|
- [] use_hook works
|
||||||
|
- [] use_ref works
|
||||||
|
- [] use_noderef works
|
||||||
|
- [] use_provide_state
|
||||||
|
- [] use_consume_state
|
||||||
|
|
||||||
|
|
||||||
|
VirtualDOM API
|
||||||
|
- [] work
|
||||||
|
- [] rebuild
|
||||||
|
- [] change props
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
use futures_channel::mpsc::unbounded;
|
|
||||||
|
|
||||||
#[async_std::test]
|
|
||||||
async fn channels() {
|
|
||||||
let (sender, mut receiver) = unbounded::<u32>();
|
|
||||||
|
|
||||||
// drop(sender);
|
|
||||||
|
|
||||||
match receiver.try_next() {
|
|
||||||
Ok(a) => {
|
|
||||||
dbg!(a);
|
|
||||||
}
|
|
||||||
Err(no) => {
|
|
||||||
dbg!(no);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
sender.unbounded_send(1).unwrap();
|
|
||||||
}
|
|
||||||
@@ -1,17 +1,23 @@
|
|||||||
//! tests to prove that the iterative implementation works
|
//! Prove that the dom works normally through virtualdom methods.
|
||||||
|
//! This methods all use "rebuild" which completely bypasses the scheduler.
|
||||||
|
//! Hard rebuilds don't consume any events from the event queue.
|
||||||
|
|
||||||
use anyhow::{Context, Result};
|
use dioxus::{prelude::*, DomEdit};
|
||||||
use dioxus::{prelude::*, DomEdit, Mutations};
|
|
||||||
mod test_logging;
|
|
||||||
use dioxus_core as dioxus;
|
use dioxus_core as dioxus;
|
||||||
use dioxus_html as dioxus_elements;
|
use dioxus_html as dioxus_elements;
|
||||||
|
|
||||||
|
mod test_logging;
|
||||||
use DomEdit::*;
|
use DomEdit::*;
|
||||||
|
|
||||||
const LOGGING_ENABLED: bool = false;
|
fn new_dom<P: Properties + 'static>(app: FC<P>, props: P) -> VirtualDom {
|
||||||
|
const IS_LOGGING_ENABLED: bool = false;
|
||||||
|
test_logging::set_up_logging(IS_LOGGING_ENABLED);
|
||||||
|
VirtualDom::new_with_props(app, props)
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_original_diff() {
|
fn test_original_diff() {
|
||||||
static App: FC<()> = |cx| {
|
static APP: FC<()> = |cx| {
|
||||||
cx.render(rsx! {
|
cx.render(rsx! {
|
||||||
div {
|
div {
|
||||||
div {
|
div {
|
||||||
@@ -21,7 +27,7 @@ fn test_original_diff() {
|
|||||||
})
|
})
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut dom = VirtualDom::new(App);
|
let mut dom = new_dom(APP, ());
|
||||||
let mutations = dom.rebuild();
|
let mutations = dom.rebuild();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
mutations.edits,
|
mutations.edits,
|
||||||
@@ -41,7 +47,7 @@ fn test_original_diff() {
|
|||||||
|
|
||||||
#[async_std::test]
|
#[async_std::test]
|
||||||
async fn create() {
|
async fn create() {
|
||||||
static App: FC<()> = |cx| {
|
static APP: FC<()> = |cx| {
|
||||||
cx.render(rsx! {
|
cx.render(rsx! {
|
||||||
div {
|
div {
|
||||||
div {
|
div {
|
||||||
@@ -59,9 +65,9 @@ async fn create() {
|
|||||||
})
|
})
|
||||||
};
|
};
|
||||||
|
|
||||||
test_logging::set_up_logging(LOGGING_ENABLED);
|
let mut dom = new_dom(APP, ());
|
||||||
let mut dom = VirtualDom::new(App);
|
let mutations = dom.rebuild();
|
||||||
let mutations = dom.rebuild_async().await;
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
mutations.edits,
|
mutations.edits,
|
||||||
[
|
[
|
||||||
@@ -92,7 +98,7 @@ async fn create() {
|
|||||||
|
|
||||||
#[async_std::test]
|
#[async_std::test]
|
||||||
async fn create_list() {
|
async fn create_list() {
|
||||||
static App: FC<()> = |cx| {
|
static APP: FC<()> = |cx| {
|
||||||
cx.render(rsx! {
|
cx.render(rsx! {
|
||||||
{(0..3).map(|f| rsx!{ div {
|
{(0..3).map(|f| rsx!{ div {
|
||||||
"hello"
|
"hello"
|
||||||
@@ -100,10 +106,8 @@ async fn create_list() {
|
|||||||
})
|
})
|
||||||
};
|
};
|
||||||
|
|
||||||
test_logging::set_up_logging(LOGGING_ENABLED);
|
let mut dom = new_dom(APP, ());
|
||||||
|
let mutations = dom.rebuild();
|
||||||
let mut dom = VirtualDom::new(App);
|
|
||||||
let mutations = dom.rebuild_async().await;
|
|
||||||
|
|
||||||
// copilot wrote this test :P
|
// copilot wrote this test :P
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
@@ -134,7 +138,7 @@ async fn create_list() {
|
|||||||
|
|
||||||
#[async_std::test]
|
#[async_std::test]
|
||||||
async fn create_simple() {
|
async fn create_simple() {
|
||||||
static App: FC<()> = |cx| {
|
static APP: FC<()> = |cx| {
|
||||||
cx.render(rsx! {
|
cx.render(rsx! {
|
||||||
div {}
|
div {}
|
||||||
div {}
|
div {}
|
||||||
@@ -143,10 +147,8 @@ async fn create_simple() {
|
|||||||
})
|
})
|
||||||
};
|
};
|
||||||
|
|
||||||
test_logging::set_up_logging(LOGGING_ENABLED);
|
let mut dom = new_dom(APP, ());
|
||||||
|
let mutations = dom.rebuild();
|
||||||
let mut dom = VirtualDom::new(App);
|
|
||||||
let mutations = dom.rebuild_async().await;
|
|
||||||
|
|
||||||
// copilot wrote this test :P
|
// copilot wrote this test :P
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
@@ -179,10 +181,8 @@ async fn create_components() {
|
|||||||
})
|
})
|
||||||
};
|
};
|
||||||
|
|
||||||
test_logging::set_up_logging(LOGGING_ENABLED);
|
let mut dom = new_dom(App, ());
|
||||||
|
let mutations = dom.rebuild();
|
||||||
let mut dom = VirtualDom::new(App);
|
|
||||||
let mutations = dom.rebuild_async().await;
|
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
mutations.edits,
|
mutations.edits,
|
||||||
@@ -225,10 +225,8 @@ async fn anchors() {
|
|||||||
})
|
})
|
||||||
};
|
};
|
||||||
|
|
||||||
test_logging::set_up_logging(LOGGING_ENABLED);
|
let mut dom = new_dom(App, ());
|
||||||
|
let mutations = dom.rebuild();
|
||||||
let mut dom = VirtualDom::new(App);
|
|
||||||
let mutations = dom.rebuild_async().await;
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
mutations.edits,
|
mutations.edits,
|
||||||
[
|
[
|
||||||
@@ -247,14 +245,18 @@ async fn anchors() {
|
|||||||
#[async_std::test]
|
#[async_std::test]
|
||||||
async fn suspended() {
|
async fn suspended() {
|
||||||
static App: FC<()> = |cx| {
|
static App: FC<()> = |cx| {
|
||||||
let val = use_suspense(cx, || async {}, |cx, _| cx.render(rsx! { "hi "}));
|
let val = use_suspense(
|
||||||
|
cx,
|
||||||
|
|| async {
|
||||||
|
//
|
||||||
|
},
|
||||||
|
|cx, _| cx.render(rsx! { "hi "}),
|
||||||
|
);
|
||||||
cx.render(rsx! { {val} })
|
cx.render(rsx! { {val} })
|
||||||
};
|
};
|
||||||
|
|
||||||
test_logging::set_up_logging(LOGGING_ENABLED);
|
let mut dom = new_dom(App, ());
|
||||||
|
let mutations = dom.rebuild();
|
||||||
let mut dom = VirtualDom::new(App);
|
|
||||||
let mutations = dom.rebuild_async().await;
|
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
mutations.edits,
|
mutations.edits,
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
/// A virtualdom wrapper used for testing purposes.
|
|
||||||
pub struct DebugDiff {}
|
|
||||||
@@ -1,48 +0,0 @@
|
|||||||
//! tests to prove that the iterative implementation works
|
|
||||||
|
|
||||||
use dioxus::prelude::*;
|
|
||||||
|
|
||||||
mod test_logging;
|
|
||||||
use dioxus_core as dioxus;
|
|
||||||
use dioxus_html as dioxus_elements;
|
|
||||||
|
|
||||||
const LOGGING_ENABLED: bool = false;
|
|
||||||
|
|
||||||
#[async_std::test]
|
|
||||||
async fn test_iterative_create_components() {
|
|
||||||
static App: FC<()> = |cx| {
|
|
||||||
// test root fragments
|
|
||||||
cx.render(rsx! {
|
|
||||||
Child { "abc1" }
|
|
||||||
Child { "abc2" }
|
|
||||||
Child { "abc3" }
|
|
||||||
})
|
|
||||||
};
|
|
||||||
|
|
||||||
fn Child(cx: Context<()>) -> DomTree {
|
|
||||||
// test root fragments, anchors, and ChildNode type
|
|
||||||
cx.render(rsx! {
|
|
||||||
h1 {}
|
|
||||||
div { {cx.children()} }
|
|
||||||
Fragment {
|
|
||||||
Fragment {
|
|
||||||
Fragment {
|
|
||||||
"wozza"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
{(0..0).map(|_f| rsx!{ div { "walalla"}})}
|
|
||||||
p {}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
test_logging::set_up_logging(LOGGING_ENABLED);
|
|
||||||
|
|
||||||
let mut dom = VirtualDom::new(App);
|
|
||||||
|
|
||||||
let mutations = dom.rebuild_async().await;
|
|
||||||
dbg!(mutations);
|
|
||||||
|
|
||||||
let mutations = dom.diff();
|
|
||||||
dbg!(mutations);
|
|
||||||
}
|
|
||||||
@@ -4,24 +4,19 @@
|
|||||||
//!
|
//!
|
||||||
//! It does not validated that component lifecycles work properly. This is done in another test file.
|
//! It does not validated that component lifecycles work properly. This is done in another test file.
|
||||||
|
|
||||||
use dioxus::{prelude::*, DomEdit, TestDom};
|
use dioxus::{nodes::VSuspended, prelude::*, DomEdit, TestDom};
|
||||||
use dioxus_core as dioxus;
|
use dioxus_core as dioxus;
|
||||||
use dioxus_html as dioxus_elements;
|
use dioxus_html as dioxus_elements;
|
||||||
|
|
||||||
mod test_logging;
|
mod test_logging;
|
||||||
use DomEdit::*;
|
|
||||||
|
|
||||||
// logging is wired up to the test harness
|
|
||||||
// feel free to enable while debugging
|
|
||||||
const IS_LOGGING_ENABLED: bool = false;
|
|
||||||
|
|
||||||
fn new_dom() -> TestDom {
|
fn new_dom() -> TestDom {
|
||||||
|
const IS_LOGGING_ENABLED: bool = false;
|
||||||
test_logging::set_up_logging(IS_LOGGING_ENABLED);
|
test_logging::set_up_logging(IS_LOGGING_ENABLED);
|
||||||
TestDom::new()
|
TestDom::new()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
use DomEdit::*;
|
||||||
fn diffing_works() {}
|
|
||||||
|
|
||||||
/// Should push the text node onto the stack and modify it
|
/// Should push the text node onto the stack and modify it
|
||||||
#[test]
|
#[test]
|
||||||
@@ -98,8 +93,8 @@ fn fragments_create_properly() {
|
|||||||
fn empty_fragments_create_anchors() {
|
fn empty_fragments_create_anchors() {
|
||||||
let dom = new_dom();
|
let dom = new_dom();
|
||||||
|
|
||||||
let left = rsx!({ (0..0).map(|f| rsx! { div {}}) });
|
let left = rsx!({ (0..0).map(|_f| rsx! { div {}}) });
|
||||||
let right = rsx!({ (0..1).map(|f| rsx! { div {}}) });
|
let right = rsx!({ (0..1).map(|_f| rsx! { div {}}) });
|
||||||
|
|
||||||
let (create, change) = dom.lazy_diff(left, right);
|
let (create, change) = dom.lazy_diff(left, right);
|
||||||
|
|
||||||
@@ -121,8 +116,8 @@ fn empty_fragments_create_anchors() {
|
|||||||
fn empty_fragments_create_many_anchors() {
|
fn empty_fragments_create_many_anchors() {
|
||||||
let dom = new_dom();
|
let dom = new_dom();
|
||||||
|
|
||||||
let left = rsx!({ (0..0).map(|f| rsx! { div {}}) });
|
let left = rsx!({ (0..0).map(|_f| rsx! { div {}}) });
|
||||||
let right = rsx!({ (0..5).map(|f| rsx! { div {}}) });
|
let right = rsx!({ (0..5).map(|_f| rsx! { div {}}) });
|
||||||
|
|
||||||
let (create, change) = dom.lazy_diff(left, right);
|
let (create, change) = dom.lazy_diff(left, right);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
@@ -148,7 +143,7 @@ fn empty_fragments_create_many_anchors() {
|
|||||||
fn empty_fragments_create_anchors_with_many_children() {
|
fn empty_fragments_create_anchors_with_many_children() {
|
||||||
let dom = new_dom();
|
let dom = new_dom();
|
||||||
|
|
||||||
let left = rsx!({ (0..0).map(|f| rsx! { div {} }) });
|
let left = rsx!({ (0..0).map(|_| rsx! { div {} }) });
|
||||||
let right = rsx!({
|
let right = rsx!({
|
||||||
(0..3).map(|f| {
|
(0..3).map(|f| {
|
||||||
rsx! { div { "hello: {f}" }}
|
rsx! { div { "hello: {f}" }}
|
||||||
@@ -192,11 +187,11 @@ fn many_items_become_fragment() {
|
|||||||
let dom = new_dom();
|
let dom = new_dom();
|
||||||
|
|
||||||
let left = rsx!({
|
let left = rsx!({
|
||||||
(0..2).map(|f| {
|
(0..2).map(|_| {
|
||||||
rsx! { div { "hello" }}
|
rsx! { div { "hello" }}
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
let right = rsx!({ (0..0).map(|f| rsx! { div {} }) });
|
let right = rsx!({ (0..0).map(|_| rsx! { div {} }) });
|
||||||
|
|
||||||
let (create, change) = dom.lazy_diff(left, right);
|
let (create, change) = dom.lazy_diff(left, right);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
@@ -235,17 +230,17 @@ fn two_equal_fragments_are_equal() {
|
|||||||
let dom = new_dom();
|
let dom = new_dom();
|
||||||
|
|
||||||
let left = rsx!({
|
let left = rsx!({
|
||||||
(0..2).map(|f| {
|
(0..2).map(|_| {
|
||||||
rsx! { div { "hello" }}
|
rsx! { div { "hello" }}
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
let right = rsx!({
|
let right = rsx!({
|
||||||
(0..2).map(|f| {
|
(0..2).map(|_| {
|
||||||
rsx! { div { "hello" }}
|
rsx! { div { "hello" }}
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
let (create, change) = dom.lazy_diff(left, right);
|
let (_create, change) = dom.lazy_diff(left, right);
|
||||||
assert!(change.edits.is_empty());
|
assert!(change.edits.is_empty());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -263,7 +258,7 @@ fn two_fragments_with_differrent_elements_are_differet() {
|
|||||||
p {}
|
p {}
|
||||||
);
|
);
|
||||||
|
|
||||||
let (create, changes) = dom.lazy_diff(left, right);
|
let (_create, changes) = dom.lazy_diff(left, right);
|
||||||
log::debug!("{:#?}", &changes);
|
log::debug!("{:#?}", &changes);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
changes.edits,
|
changes.edits,
|
||||||
@@ -700,3 +695,21 @@ fn controlled_keyed_diffing_out_of_order_max_test() {
|
|||||||
]
|
]
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn suspense() {
|
||||||
|
let dom = new_dom();
|
||||||
|
|
||||||
|
let edits = dom.create(LazyNodes::new(|f| {
|
||||||
|
use std::cell::{Cell, RefCell};
|
||||||
|
VNode::Suspended(f.bump().alloc(VSuspended {
|
||||||
|
task_id: 0,
|
||||||
|
callback: RefCell::new(None),
|
||||||
|
dom_id: Cell::new(None),
|
||||||
|
}))
|
||||||
|
}));
|
||||||
|
assert_eq!(
|
||||||
|
edits.edits,
|
||||||
|
[CreatePlaceholder { id: 0 }, AppendChildren { many: 1 }]
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
|
|
||||||
// type VirtualNode = VNode;
|
|
||||||
|
|
||||||
/// Test a basic usage of a virtual dom + text renderer combo
|
|
||||||
#[test]
|
|
||||||
fn simple_integration() {}
|
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
//! Tests for the lifecycle of components.
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
//! Tests for the scheduler.
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
//! Diffing is interruptible, but uses yield_now which is loop-pollable
|
|
||||||
//!
|
|
||||||
//! This means you can actually call it synchronously if you want.
|
|
||||||
|
|
||||||
use anyhow::{Context, Result};
|
|
||||||
use dioxus::{prelude::*, scope::Scope};
|
|
||||||
use dioxus_core as dioxus;
|
|
||||||
use dioxus_html as dioxus_elements;
|
|
||||||
use futures_util::FutureExt;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn worksync() {
|
|
||||||
static App: FC<()> = |cx| {
|
|
||||||
cx.render(rsx! {
|
|
||||||
div {"hello"}
|
|
||||||
})
|
|
||||||
};
|
|
||||||
let mut dom = VirtualDom::new(App);
|
|
||||||
|
|
||||||
let mut fut = dom.rebuild_async().boxed_local();
|
|
||||||
|
|
||||||
let mutations = loop {
|
|
||||||
let g = (&mut fut).now_or_never();
|
|
||||||
if g.is_some() {
|
|
||||||
break g.unwrap();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
dbg!(mutations);
|
|
||||||
}
|
|
||||||
@@ -65,10 +65,6 @@ features = [
|
|||||||
"HtmlOptionElement",
|
"HtmlOptionElement",
|
||||||
]
|
]
|
||||||
|
|
||||||
[profile.release]
|
|
||||||
lto = true
|
|
||||||
opt-level = 's'
|
|
||||||
|
|
||||||
[lib]
|
[lib]
|
||||||
crate-type = ["cdylib", "rlib"]
|
crate-type = ["cdylib", "rlib"]
|
||||||
|
|
||||||
|
|||||||
+79
-79
@@ -582,85 +582,85 @@ pub fn load_document() -> Document {
|
|||||||
|
|
||||||
pub fn event_name_from_typ(typ: &str) -> &'static str {
|
pub fn event_name_from_typ(typ: &str) -> &'static str {
|
||||||
match typ {
|
match typ {
|
||||||
"copy" => "oncopy",
|
"copy" => "copy",
|
||||||
"cut" => "oncut",
|
"cut" => "cut",
|
||||||
"paste" => "onpaste",
|
"paste" => "paste",
|
||||||
"compositionend" => "oncompositionend",
|
"compositionend" => "compositionend",
|
||||||
"compositionstart" => "oncompositionstart",
|
"compositionstart" => "compositionstart",
|
||||||
"compositionupdate" => "oncompositionupdate",
|
"compositionupdate" => "compositionupdate",
|
||||||
"keydown" => "onkeydown",
|
"keydown" => "keydown",
|
||||||
"keypress" => "onkeypress",
|
"keypress" => "keypress",
|
||||||
"keyup" => "onkeyup",
|
"keyup" => "keyup",
|
||||||
"focus" => "onfocus",
|
"focus" => "focus",
|
||||||
"blur" => "onblur",
|
"blur" => "blur",
|
||||||
"change" => "onchange",
|
"change" => "change",
|
||||||
"input" => "oninput",
|
"input" => "input",
|
||||||
"invalid" => "oninvalid",
|
"invalid" => "invalid",
|
||||||
"reset" => "onreset",
|
"reset" => "reset",
|
||||||
"submit" => "onsubmit",
|
"submit" => "submit",
|
||||||
"click" => "onclick",
|
"click" => "click",
|
||||||
"contextmenu" => "oncontextmenu",
|
"contextmenu" => "contextmenu",
|
||||||
"doubleclick" => "ondoubleclick",
|
"doubleclick" => "doubleclick",
|
||||||
"drag" => "ondrag",
|
"drag" => "drag",
|
||||||
"dragend" => "ondragend",
|
"dragend" => "dragend",
|
||||||
"dragenter" => "ondragenter",
|
"dragenter" => "dragenter",
|
||||||
"dragexit" => "ondragexit",
|
"dragexit" => "dragexit",
|
||||||
"dragleave" => "ondragleave",
|
"dragleave" => "dragleave",
|
||||||
"dragover" => "ondragover",
|
"dragover" => "dragover",
|
||||||
"dragstart" => "ondragstart",
|
"dragstart" => "dragstart",
|
||||||
"drop" => "ondrop",
|
"drop" => "drop",
|
||||||
"mousedown" => "onmousedown",
|
"mousedown" => "mousedown",
|
||||||
"mouseenter" => "onmouseenter",
|
"mouseenter" => "mouseenter",
|
||||||
"mouseleave" => "onmouseleave",
|
"mouseleave" => "mouseleave",
|
||||||
"mousemove" => "onmousemove",
|
"mousemove" => "mousemove",
|
||||||
"mouseout" => "onmouseout",
|
"mouseout" => "mouseout",
|
||||||
"mouseover" => "onmouseover",
|
"mouseover" => "mouseover",
|
||||||
"mouseup" => "onmouseup",
|
"mouseup" => "mouseup",
|
||||||
"pointerdown" => "onpointerdown",
|
"pointerdown" => "pointerdown",
|
||||||
"pointermove" => "onpointermove",
|
"pointermove" => "pointermove",
|
||||||
"pointerup" => "onpointerup",
|
"pointerup" => "pointerup",
|
||||||
"pointercancel" => "onpointercancel",
|
"pointercancel" => "pointercancel",
|
||||||
"gotpointercapture" => "ongotpointercapture",
|
"gotpointercapture" => "gotpointercapture",
|
||||||
"lostpointercapture" => "onlostpointercapture",
|
"lostpointercapture" => "lostpointercapture",
|
||||||
"pointerenter" => "onpointerenter",
|
"pointerenter" => "pointerenter",
|
||||||
"pointerleave" => "onpointerleave",
|
"pointerleave" => "pointerleave",
|
||||||
"pointerover" => "onpointerover",
|
"pointerover" => "pointerover",
|
||||||
"pointerout" => "onpointerout",
|
"pointerout" => "pointerout",
|
||||||
"select" => "onselect",
|
"select" => "select",
|
||||||
"touchcancel" => "ontouchcancel",
|
"touchcancel" => "touchcancel",
|
||||||
"touchend" => "ontouchend",
|
"touchend" => "touchend",
|
||||||
"touchmove" => "ontouchmove",
|
"touchmove" => "touchmove",
|
||||||
"touchstart" => "ontouchstart",
|
"touchstart" => "touchstart",
|
||||||
"scroll" => "onscroll",
|
"scroll" => "scroll",
|
||||||
"wheel" => "onwheel",
|
"wheel" => "wheel",
|
||||||
"animationstart" => "onanimationstart",
|
"animationstart" => "animationstart",
|
||||||
"animationend" => "onanimationend",
|
"animationend" => "animationend",
|
||||||
"animationiteration" => "onanimationiteration",
|
"animationiteration" => "animationiteration",
|
||||||
"transitionend" => "ontransitionend",
|
"transitionend" => "transitionend",
|
||||||
"abort" => "onabort",
|
"abort" => "abort",
|
||||||
"canplay" => "oncanplay",
|
"canplay" => "canplay",
|
||||||
"canplaythrough" => "oncanplaythrough",
|
"canplaythrough" => "canplaythrough",
|
||||||
"durationchange" => "ondurationchange",
|
"durationchange" => "durationchange",
|
||||||
"emptied" => "onemptied",
|
"emptied" => "emptied",
|
||||||
"encrypted" => "onencrypted",
|
"encrypted" => "encrypted",
|
||||||
"ended" => "onended",
|
"ended" => "ended",
|
||||||
"error" => "onerror",
|
"error" => "error",
|
||||||
"loadeddata" => "onloadeddata",
|
"loadeddata" => "loadeddata",
|
||||||
"loadedmetadata" => "onloadedmetadata",
|
"loadedmetadata" => "loadedmetadata",
|
||||||
"loadstart" => "onloadstart",
|
"loadstart" => "loadstart",
|
||||||
"pause" => "onpause",
|
"pause" => "pause",
|
||||||
"play" => "onplay",
|
"play" => "play",
|
||||||
"playing" => "onplaying",
|
"playing" => "playing",
|
||||||
"progress" => "onprogress",
|
"progress" => "progress",
|
||||||
"ratechange" => "onratechange",
|
"ratechange" => "ratechange",
|
||||||
"seeked" => "onseeked",
|
"seeked" => "seeked",
|
||||||
"seeking" => "onseeking",
|
"seeking" => "seeking",
|
||||||
"stalled" => "onstalled",
|
"stalled" => "stalled",
|
||||||
"suspend" => "onsuspend",
|
"suspend" => "suspend",
|
||||||
"timeupdate" => "ontimeupdate",
|
"timeupdate" => "timeupdate",
|
||||||
"volumechange" => "onvolumechange",
|
"volumechange" => "volumechange",
|
||||||
"waiting" => "onwaiting",
|
"waiting" => "waiting",
|
||||||
"toggle" => "ontoggle",
|
"toggle" => "toggle",
|
||||||
_ => {
|
_ => {
|
||||||
panic!("unsupported event type")
|
panic!("unsupported event type")
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in new issue
Block a user