feat: props memoization is more powerful
This commit solves the memoization , properly memoizing properties that don't have any generic parameters. This is a rough heuristic to prevent non-static lifetimes from creeping into props and breaking our minual lifetime management. Props that have a generic parameter are opted-out of the `partialeq` requirement and props *without* lifetimes must implement partialeq. We're going to leave manual disabling of memoization for future work.
This commit is contained in:
parent
7102fe5f98
commit
73047fe956
|
@ -52,7 +52,8 @@ members = [
|
||||||
"packages/core-macro",
|
"packages/core-macro",
|
||||||
"packages/core",
|
"packages/core",
|
||||||
"packages/html-namespace",
|
"packages/html-namespace",
|
||||||
# "packages/web",
|
"packages/web",
|
||||||
|
"packages/cli",
|
||||||
# "packages/atoms",
|
# "packages/atoms",
|
||||||
# "packages/ssr",
|
# "packages/ssr",
|
||||||
# "packages/docsite",
|
# "packages/docsite",
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
- [] Transition away from names and towards compile-time safe tags
|
- [] Transition away from names and towards compile-time safe tags
|
||||||
- [] Fix diffing of fragments
|
- [] Fix diffing of fragments
|
||||||
- [] Properly integrate memoization to prevent safety issues with children
|
- [] Properly integrate memoization to prevent safety issues with children
|
||||||
- [] Understand the issue with callbacks (outdated generations)
|
- [x] Understand the issue with callbacks (outdated generations)
|
||||||
- [] Fix examples for core, web, ssr, and general
|
- [] Fix examples for core, web, ssr, and general
|
||||||
- [] Finish up documentation
|
- [] Finish up documentation
|
||||||
- [] Polish the Recoil (Dirac?) API
|
- [] Polish the Recoil (Dirac?) API
|
||||||
|
|
|
@ -11,13 +11,13 @@ description = "CLI tool for developing, testing, and publishing Dioxus apps"
|
||||||
[dependencies]
|
[dependencies]
|
||||||
thiserror = "1.0.23"
|
thiserror = "1.0.23"
|
||||||
log = "0.4.13"
|
log = "0.4.13"
|
||||||
fern = { version = "0.6.0", features = ["colored"] }
|
fern = { version="0.6.0", features=["colored"] }
|
||||||
wasm-bindgen-cli-support = "0.2.73"
|
wasm-bindgen-cli-support = "0.2.73"
|
||||||
anyhow = "1.0.38"
|
anyhow = "1.0.38"
|
||||||
argh = "0.1.4"
|
argh = "0.1.4"
|
||||||
serde = "1.0.120"
|
serde = "1.0.120"
|
||||||
serde_json = "1.0.61"
|
serde_json = "1.0.61"
|
||||||
async-std = { version = "1.9.0", features = ["attributes"] }
|
async-std = { version="1.9.0", features=["attributes"] }
|
||||||
tide = "0.15.0"
|
tide = "0.15.0"
|
||||||
fs_extra = "1.2.0"
|
fs_extra = "1.2.0"
|
||||||
|
|
||||||
|
|
|
@ -131,7 +131,7 @@ fn gen_page(module: &str) -> String {
|
||||||
<!-- Note the usage of `type=module` here as this is an ES6 module -->
|
<!-- Note the usage of `type=module` here as this is an ES6 module -->
|
||||||
<script type="module">
|
<script type="module">
|
||||||
import init from "{}";
|
import init from "{}";
|
||||||
init();
|
init("./wasm/module_bg.wasm");
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
@ -502,6 +502,7 @@ mod field_info {
|
||||||
|
|
||||||
mod struct_info {
|
mod struct_info {
|
||||||
use proc_macro2::TokenStream;
|
use proc_macro2::TokenStream;
|
||||||
|
use quote::__private::ext::RepToTokensExt;
|
||||||
use quote::quote;
|
use quote::quote;
|
||||||
use syn::parse::Error;
|
use syn::parse::Error;
|
||||||
|
|
||||||
|
@ -569,6 +570,13 @@ mod struct_info {
|
||||||
ref builder_name,
|
ref builder_name,
|
||||||
..
|
..
|
||||||
} = *self;
|
} = *self;
|
||||||
|
|
||||||
|
// we're generating stuff that goes into unsafe code here
|
||||||
|
// we use the heuristic: are there *any* generic parameters?
|
||||||
|
// If so, then they might have non-static lifetimes and we can't compare two generic things that *might borrow*
|
||||||
|
// Therefore, we will generate code that shortcircuits the "comparison" in memoization
|
||||||
|
let are_there_generics = self.generics.params.len() > 0;
|
||||||
|
|
||||||
let (impl_generics, ty_generics, where_clause) = self.generics.split_for_impl();
|
let (impl_generics, ty_generics, where_clause) = self.generics.split_for_impl();
|
||||||
let all_fields_param = syn::GenericParam::Type(
|
let all_fields_param = syn::GenericParam::Type(
|
||||||
syn::Ident::new("TypedBuilderFields", proc_macro2::Span::call_site()).into(),
|
syn::Ident::new("TypedBuilderFields", proc_macro2::Span::call_site()).into(),
|
||||||
|
@ -650,6 +658,11 @@ Finally, call `.build()` to create the instance of `{name}`.
|
||||||
.extend(predicates.predicates.clone());
|
.extend(predicates.predicates.clone());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let can_memoize = match are_there_generics {
|
||||||
|
true => quote! { false },
|
||||||
|
false => quote! { self == other },
|
||||||
|
};
|
||||||
|
|
||||||
Ok(quote! {
|
Ok(quote! {
|
||||||
impl #impl_generics #name #ty_generics #where_clause {
|
impl #impl_generics #name #ty_generics #where_clause {
|
||||||
#[doc = #builder_method_doc]
|
#[doc = #builder_method_doc]
|
||||||
|
@ -679,12 +692,14 @@ Finally, call `.build()` to create the instance of `{name}`.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
unsafe impl #impl_generics dioxus::prelude::Properties for #name #ty_generics{
|
impl #impl_generics dioxus::prelude::Properties for #name #ty_generics{
|
||||||
type Builder = #builder_name #generics_with_empty;
|
type Builder = #builder_name #generics_with_empty;
|
||||||
const CAN_BE_MEMOIZED: bool = true;
|
|
||||||
fn builder() -> Self::Builder {
|
fn builder() -> Self::Builder {
|
||||||
#name::builder()
|
#name::builder()
|
||||||
}
|
}
|
||||||
|
unsafe fn memoize(&self, other: &Self) -> bool {
|
||||||
|
#can_memoize
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,3 +1,3 @@
|
||||||
{
|
{
|
||||||
"rust-analyzer.inlayHints.enable": true
|
"rust-analyzer.inlayHints.enable": false
|
||||||
}
|
}
|
|
@ -63,10 +63,12 @@ impl PartialEq for ChildProps {
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
unsafe impl Properties for ChildProps {
|
impl Properties for ChildProps {
|
||||||
type Builder = ();
|
type Builder = ();
|
||||||
const CAN_BE_MEMOIZED: bool = false;
|
|
||||||
fn builder() -> Self::Builder {
|
fn builder() -> Self::Builder {
|
||||||
()
|
()
|
||||||
}
|
}
|
||||||
|
unsafe fn memoize(&self, other: &Self) -> bool {
|
||||||
|
self == other
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,21 +7,27 @@
|
||||||
|
|
||||||
use crate::innerlude::FC;
|
use crate::innerlude::FC;
|
||||||
|
|
||||||
pub unsafe trait Properties: PartialEq + Sized {
|
pub trait Properties: Sized {
|
||||||
type Builder;
|
type Builder;
|
||||||
const CAN_BE_MEMOIZED: bool;
|
|
||||||
fn builder() -> Self::Builder;
|
fn builder() -> Self::Builder;
|
||||||
|
|
||||||
|
/// Memoization can only happen if the props are 'static
|
||||||
|
/// The user must know if their props are static, but if they make a mistake, UB happens
|
||||||
|
/// Therefore it's unsafe to memeoize.
|
||||||
|
unsafe fn memoize(&self, other: &Self) -> bool;
|
||||||
}
|
}
|
||||||
|
|
||||||
unsafe impl Properties for () {
|
impl Properties for () {
|
||||||
const CAN_BE_MEMOIZED: bool = true;
|
|
||||||
type Builder = EmptyBuilder;
|
type Builder = EmptyBuilder;
|
||||||
|
|
||||||
fn builder() -> Self::Builder {
|
fn builder() -> Self::Builder {
|
||||||
EmptyBuilder {}
|
EmptyBuilder {}
|
||||||
}
|
}
|
||||||
|
unsafe fn memoize(&self, _other: &Self) -> bool {
|
||||||
|
true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
// We allow components to use the () generic parameter if they have no props. This impl enables the "build" method
|
||||||
|
// that the macros use to anonymously complete prop construction.
|
||||||
pub struct EmptyBuilder;
|
pub struct EmptyBuilder;
|
||||||
impl EmptyBuilder {
|
impl EmptyBuilder {
|
||||||
#[inline]
|
#[inline]
|
||||||
|
@ -30,6 +36,8 @@ impl EmptyBuilder {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// This utility function launches the builder method so rsx! and html! macros can use the typed-builder pattern
|
||||||
|
/// to initialize a component's props.
|
||||||
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()
|
||||||
}
|
}
|
||||||
|
@ -39,9 +47,10 @@ pub fn fc_to_builder<T: Properties>(_: FC<T>) -> T::Builder {
|
||||||
///
|
///
|
||||||
/// Fragments capture a series of children without rendering extra nodes.
|
/// 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.
|
||||||
pub static Fragment: FC<()> = |ctx| {
|
#[allow(non_upper_case_globals)]
|
||||||
|
pub const Fragment: FC<()> = |ctx| {
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
ctx.render(LazyNodes::new(move |c| {
|
ctx.render(LazyNodes::new(move |c| {
|
||||||
crate::nodebuilder::vfragment(c, None, ctx.children())
|
crate::nodebuilder::vfragment(c, None, ctx.children())
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
//!
|
//!
|
||||||
//! Renderers don't actually need to own the virtual dom (it's up to the implementer).
|
//! Renderers don't actually need to own the virtual dom (it's up to the implementer).
|
||||||
|
|
||||||
|
use crate::innerlude::RealDom;
|
||||||
use crate::{events::EventTrigger, virtual_dom::VirtualDom};
|
use crate::{events::EventTrigger, virtual_dom::VirtualDom};
|
||||||
use crate::{innerlude::Result, prelude::*};
|
use crate::{innerlude::Result, prelude::*};
|
||||||
|
|
||||||
|
@ -40,7 +41,7 @@ impl DebugRenderer {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn step(&mut self, machine: &mut DiffMachine) -> Result<()> {
|
pub fn step<Dom: RealDom>(&mut self, machine: &mut DiffMachine<Dom>) -> Result<()> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -70,6 +71,27 @@ impl DebugRenderer {
|
||||||
pub fn trigger_listener(&mut self, id: usize) -> Result<()> {
|
pub fn trigger_listener(&mut self, id: usize) -> Result<()> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn render_nodes<'a, F>(&self, other: LazyNodes<'a, F>) -> Result<()>
|
||||||
|
where
|
||||||
|
F: for<'b> FnOnce(&'b NodeCtx<'a>) -> VNode<'a> + 'a,
|
||||||
|
{
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct DebugVNodeSource {
|
||||||
|
bump: Bump,
|
||||||
|
}
|
||||||
|
impl DebugVNodeSource {
|
||||||
|
fn new() -> Self {
|
||||||
|
Self { bump: Bump::new() }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render_nodes(&self) -> VNode {
|
||||||
|
// let ctx = NodeCtx
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
|
|
@ -44,31 +44,30 @@ use std::{
|
||||||
/// single node
|
/// single node
|
||||||
pub trait RealDom {
|
pub trait RealDom {
|
||||||
// Navigation
|
// Navigation
|
||||||
fn push_root(&self, root: RealDomNode);
|
fn push_root(&mut self, root: RealDomNode);
|
||||||
fn pop(&self);
|
|
||||||
|
|
||||||
// Add Nodes to the dom
|
// Add Nodes to the dom
|
||||||
fn append_child(&self);
|
fn append_child(&mut self);
|
||||||
fn replace_with(&self);
|
fn replace_with(&mut self);
|
||||||
|
|
||||||
// Remove Nodesfrom the dom
|
// Remove Nodesfrom the dom
|
||||||
fn remove(&self);
|
fn remove(&mut self);
|
||||||
fn remove_all_children(&self);
|
fn remove_all_children(&mut self);
|
||||||
|
|
||||||
// Create
|
// Create
|
||||||
fn create_text_node(&self, text: &str) -> RealDomNode;
|
fn create_text_node(&mut self, text: &str) -> RealDomNode;
|
||||||
fn create_element(&self, tag: &str) -> RealDomNode;
|
fn create_element(&mut self, tag: &str) -> RealDomNode;
|
||||||
fn create_element_ns(&self, tag: &str, namespace: &str) -> RealDomNode;
|
fn create_element_ns(&mut self, tag: &str, namespace: &str) -> RealDomNode;
|
||||||
|
|
||||||
// events
|
// events
|
||||||
fn new_event_listener(&self, event: &str, scope: ScopeIdx, id: usize);
|
fn new_event_listener(&mut self, event: &str, scope: ScopeIdx, id: usize);
|
||||||
// fn new_event_listener(&self, event: &str);
|
// fn new_event_listener(&mut self, event: &str);
|
||||||
fn remove_event_listener(&self, event: &str);
|
fn remove_event_listener(&mut self, event: &str);
|
||||||
|
|
||||||
// modify
|
// modify
|
||||||
fn set_text(&self, text: &str);
|
fn set_text(&mut self, text: &str);
|
||||||
fn set_attribute(&self, name: &str, value: &str, is_namespaced: bool);
|
fn set_attribute(&mut self, name: &str, value: &str, is_namespaced: bool);
|
||||||
fn remove_attribute(&self, name: &str);
|
fn remove_attribute(&mut self, name: &str);
|
||||||
|
|
||||||
// node ref
|
// node ref
|
||||||
fn raw_node_as_any_mut(&self) -> &mut dyn Any;
|
fn raw_node_as_any_mut(&self) -> &mut dyn Any;
|
||||||
|
@ -470,7 +469,7 @@ impl<'a, Dom: RealDom> DiffMachine<'a, Dom> {
|
||||||
// [... node]
|
// [... node]
|
||||||
//
|
//
|
||||||
// The change list stack is left unchanged.
|
// The change list stack is left unchanged.
|
||||||
fn diff_listeners(&self, old: &[Listener<'_>], new: &[Listener<'_>]) {
|
fn diff_listeners(&mut self, old: &[Listener<'_>], new: &[Listener<'_>]) {
|
||||||
if !old.is_empty() || !new.is_empty() {
|
if !old.is_empty() || !new.is_empty() {
|
||||||
// self.dom.commit_traversal();
|
// self.dom.commit_traversal();
|
||||||
}
|
}
|
||||||
|
@ -518,7 +517,12 @@ impl<'a, Dom: RealDom> DiffMachine<'a, Dom> {
|
||||||
// [... node]
|
// [... node]
|
||||||
//
|
//
|
||||||
// The change list stack is left unchanged.
|
// The change list stack is left unchanged.
|
||||||
fn diff_attr(&self, old: &'a [Attribute<'a>], new: &'a [Attribute<'a>], is_namespaced: bool) {
|
fn diff_attr(
|
||||||
|
&mut self,
|
||||||
|
old: &'a [Attribute<'a>],
|
||||||
|
new: &'a [Attribute<'a>],
|
||||||
|
is_namespaced: bool,
|
||||||
|
) {
|
||||||
// Do O(n^2) passes to add/update and remove attributes, since
|
// Do O(n^2) passes to add/update and remove attributes, since
|
||||||
// there are almost always very few attributes.
|
// there are almost always very few attributes.
|
||||||
//
|
//
|
||||||
|
|
|
@ -11,10 +11,9 @@
|
||||||
pub mod arena;
|
pub mod arena;
|
||||||
pub mod component; // Logic for extending FC
|
pub mod component; // Logic for extending FC
|
||||||
|
|
||||||
// pub mod debug_renderer;
|
pub mod debug_renderer;
|
||||||
pub mod diff;
|
pub mod diff;
|
||||||
pub mod patch; // An "edit phase" described by transitions and edit operations // Test harness for validating that lifecycles and diffs work appropriately
|
|
||||||
// the diffing algorithm that builds the ChangeList
|
|
||||||
pub mod error; // Error type we expose to the renderers
|
pub mod error; // Error type we expose to the renderers
|
||||||
pub mod events; // Manages the synthetic event API
|
pub mod events; // Manages the synthetic event API
|
||||||
pub mod hooks; // Built-in hooks
|
pub mod hooks; // Built-in hooks
|
||||||
|
@ -36,7 +35,6 @@ pub(crate) mod innerlude {
|
||||||
pub use crate::hooks::*;
|
pub use crate::hooks::*;
|
||||||
pub use crate::nodebuilder::*;
|
pub use crate::nodebuilder::*;
|
||||||
pub use crate::nodes::*;
|
pub use crate::nodes::*;
|
||||||
pub use crate::patch::*;
|
|
||||||
pub use crate::virtual_dom::*;
|
pub use crate::virtual_dom::*;
|
||||||
|
|
||||||
pub type FC<P> = fn(Context<P>) -> VNode;
|
pub type FC<P> = fn(Context<P>) -> VNode;
|
||||||
|
|
|
@ -494,10 +494,22 @@ where
|
||||||
/// .finish();
|
/// .finish();
|
||||||
/// ```
|
/// ```
|
||||||
pub fn iter_child(mut self, nodes: impl IntoIterator<Item = impl IntoVNode<'a>>) -> Self {
|
pub fn iter_child(mut self, nodes: impl IntoIterator<Item = impl IntoVNode<'a>>) -> Self {
|
||||||
|
let len_before = self.children.len();
|
||||||
for item in nodes {
|
for item in nodes {
|
||||||
let child = item.into_vnode(&self.ctx);
|
let child = item.into_vnode(&self.ctx);
|
||||||
self.children.push(child);
|
self.children.push(child);
|
||||||
}
|
}
|
||||||
|
let len_after = self.children.len();
|
||||||
|
if len_after > len_before {
|
||||||
|
let last_child = self.children.last().unwrap();
|
||||||
|
if last_child.key().is_none() {
|
||||||
|
// TODO: Somehow get the name of the component when NodeCtx is being made
|
||||||
|
const ERR_MSG: &str = r#"Warning: Each child in an array or iterator should have a unique "key" prop.
|
||||||
|
Check the render method of XXXX.
|
||||||
|
See fb.me/react-warning-keys for more information. "#;
|
||||||
|
log::error!("{}", ERR_MSG);
|
||||||
|
}
|
||||||
|
}
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -111,9 +111,29 @@ impl<'a> VNode<'a> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_child(&self, id: u32) -> Option<VNode<'a>> {
|
fn get_child(&self, id: u32) -> Option<&'a VNode<'a>> {
|
||||||
todo!()
|
todo!()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn is_real(&self) -> bool {
|
||||||
|
match self {
|
||||||
|
VNode::Element(_) => true,
|
||||||
|
VNode::Text(_) => true,
|
||||||
|
VNode::Fragment(_) => false,
|
||||||
|
VNode::Suspended => false,
|
||||||
|
VNode::Component(_) => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_mounted_id(&self) -> Option<RealDomNode> {
|
||||||
|
match self {
|
||||||
|
VNode::Element(_) => todo!(),
|
||||||
|
VNode::Text(_) => todo!(),
|
||||||
|
VNode::Fragment(_) => todo!(),
|
||||||
|
VNode::Suspended => todo!(),
|
||||||
|
VNode::Component(_) => todo!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
|
@ -257,88 +277,68 @@ pub struct VComponent<'src> {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> VComponent<'a> {
|
impl<'a> VComponent<'a> {
|
||||||
// use the type parameter on props creation and move it into a portable context
|
/// When the rsx! macro is called, it will check if the CanMemo flag is set to true (from the Props impl)
|
||||||
// this lets us keep scope generic *and* downcast its props when we need to:
|
/// If it is set to true, then this method will be called which implements automatic memoization.
|
||||||
// - perform comparisons when diffing (memoization)
|
///
|
||||||
// TODO: lift the requirement that props need to be static
|
/// If the CanMemo is `false`, then the macro will call the backup method which always defaults to "false"
|
||||||
// we want them to borrow references... maybe force implementing a "to_static_unsafe" trait
|
|
||||||
|
|
||||||
pub fn new<P: Properties + 'a>(
|
pub fn new<P: Properties + 'a>(
|
||||||
// bump: &'a Bump,
|
|
||||||
ctx: &NodeCtx<'a>,
|
ctx: &NodeCtx<'a>,
|
||||||
component: FC<P>,
|
component: FC<P>,
|
||||||
// props: bumpalo::boxed::Box<'a, P>,
|
|
||||||
props: P,
|
props: P,
|
||||||
key: Option<&'a str>,
|
key: Option<&'a str>,
|
||||||
children: &'a [VNode<'a>],
|
children: &'a [VNode<'a>],
|
||||||
) -> Self {
|
) -> Self {
|
||||||
// pub fn new<P: Properties + 'a>(component: FC<P>, props: P, key: Option<&'a str>) -> Self {
|
|
||||||
// let bad_props = unsafe { transmogrify(props) };
|
|
||||||
let bump = ctx.bump();
|
let bump = ctx.bump();
|
||||||
let caller_ref = component as *const ();
|
let user_fc = component as *const ();
|
||||||
let props = bump.alloc(props);
|
|
||||||
|
|
||||||
|
let props = bump.alloc(props);
|
||||||
let raw_props = props as *const P as *const ();
|
let raw_props = props as *const P as *const ();
|
||||||
|
|
||||||
let comparator: Option<&dyn Fn(&VComponent) -> bool> = {
|
let comparator: Option<&dyn Fn(&VComponent) -> bool> = {
|
||||||
if P::CAN_BE_MEMOIZED {
|
Some(bump.alloc(move |other: &VComponent| {
|
||||||
Some(bump.alloc(move |other: &VComponent| {
|
// Safety:
|
||||||
// Safety:
|
// ------
|
||||||
// We are guaranteed that the props will be of the same type because
|
//
|
||||||
// there is no way to create a VComponent other than this `new` method.
|
// Invariants:
|
||||||
//
|
// - Component function pointers are the same
|
||||||
// Therefore, if the render functions are identical (by address), then so will be
|
// - Generic properties on the same function pointer are the same
|
||||||
// props type paramter (because it is the same render function). Therefore, we can be
|
// - Lifetime of P borrows from its parent
|
||||||
// sure
|
// - The parent scope still exists when method is called
|
||||||
if caller_ref == other.user_fc {
|
// - Casting from T to *const () is portable
|
||||||
// let g = other.raw_ctx.downcast_ref::<P>().unwrap();
|
//
|
||||||
let real_other = unsafe { &*(other.raw_props as *const _ as *const P) };
|
// Explanation:
|
||||||
&props == &real_other
|
// We are guaranteed that the props will be of the same type because
|
||||||
} else {
|
// there is no way to create a VComponent other than this `new` method.
|
||||||
false
|
//
|
||||||
|
// Therefore, if the render functions are identical (by address), then so will be
|
||||||
|
// props type paramter (because it is the same render function). Therefore, we can be
|
||||||
|
// sure that it is safe to interperet the previous props raw pointer as the same props
|
||||||
|
// type. From there, we can call the props' "memoize" method to see if we can
|
||||||
|
// avoid re-rendering the component.
|
||||||
|
if user_fc == other.user_fc {
|
||||||
|
let real_other = unsafe { &*(other.raw_props as *const _ as *const P) };
|
||||||
|
let props_memoized = unsafe { props.memoize(&real_other) };
|
||||||
|
match (props_memoized, children.len() == 0) {
|
||||||
|
(true, true) => true,
|
||||||
|
_ => false,
|
||||||
}
|
}
|
||||||
}))
|
} else {
|
||||||
} else {
|
false
|
||||||
None
|
}
|
||||||
}
|
}))
|
||||||
};
|
};
|
||||||
|
|
||||||
// let prref: &'a P = props.as_ref();
|
|
||||||
|
|
||||||
// let r = create_closure(component, raw_props);
|
|
||||||
// let caller: Rc<dyn for<'g> Fn(&'g Scope) -> VNode<'g>> = Rc::new(move |scope| {
|
|
||||||
// // r(scope);
|
|
||||||
// //
|
|
||||||
// // let props2 = bad_props;
|
|
||||||
// // props.as_ref();
|
|
||||||
// // let ctx = Context {
|
|
||||||
// // props: prref,
|
|
||||||
// // scope,
|
|
||||||
// // };
|
|
||||||
// // let ctx: Context<'g, P> = todo!();
|
|
||||||
// // todo!()
|
|
||||||
// // let r = component(ctx);
|
|
||||||
// todo!()
|
|
||||||
// });
|
|
||||||
let caller = create_closure(component, raw_props);
|
|
||||||
|
|
||||||
// let caller: Rc<dyn Fn(&Scope) -> VNode> = Rc::new(create_closure(component, raw_props));
|
|
||||||
|
|
||||||
let key = match key {
|
|
||||||
Some(key) => NodeKey::new(key),
|
|
||||||
None => NodeKey(None),
|
|
||||||
};
|
|
||||||
|
|
||||||
// raw_props: Box::new(props),
|
|
||||||
// comparator: Rc::new(props_comparator),
|
|
||||||
Self {
|
Self {
|
||||||
key,
|
user_fc,
|
||||||
ass_scope: RefCell::new(None),
|
|
||||||
user_fc: caller_ref,
|
|
||||||
comparator,
|
comparator,
|
||||||
raw_props,
|
raw_props,
|
||||||
children,
|
children,
|
||||||
caller,
|
ass_scope: RefCell::new(None),
|
||||||
|
key: match key {
|
||||||
|
Some(key) => NodeKey::new(key),
|
||||||
|
None => NodeKey(None),
|
||||||
|
},
|
||||||
|
caller: create_closure(component, raw_props),
|
||||||
mounted_root: Cell::new(RealDomNode::empty()),
|
mounted_root: Cell::new(RealDomNode::empty()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -346,7 +346,7 @@ impl<'a> VComponent<'a> {
|
||||||
|
|
||||||
type Captured<'a> = Rc<dyn for<'r> Fn(&'r Scope) -> VNode<'r> + 'a>;
|
type Captured<'a> = Rc<dyn for<'r> Fn(&'r Scope) -> VNode<'r> + 'a>;
|
||||||
|
|
||||||
fn create_closure<'a, P: Properties + 'a>(
|
fn create_closure<'a, P: 'a>(
|
||||||
component: FC<P>,
|
component: FC<P>,
|
||||||
raw_props: *const (),
|
raw_props: *const (),
|
||||||
) -> Rc<dyn for<'r> Fn(&'r Scope) -> VNode<'r>> {
|
) -> Rc<dyn for<'r> Fn(&'r Scope) -> VNode<'r>> {
|
||||||
|
@ -385,7 +385,9 @@ impl<'a> VFragment<'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// This method converts a list of nested real/virtual nodes into a stream of nodes that are definitely associated
|
/// This method converts a list of nested real/virtual nodes into a stream of nodes that are definitely associated
|
||||||
/// with the real dom.
|
/// with the real dom. The only types of nodes that may be returned are text, elemets, and components.
|
||||||
|
///
|
||||||
|
/// Components *are* considered virtual, but this iterator can't necessarily handle them without the scope arena.
|
||||||
///
|
///
|
||||||
/// Why?
|
/// Why?
|
||||||
/// ---
|
/// ---
|
||||||
|
@ -401,47 +403,80 @@ pub fn iterate_real_nodes<'a>(nodes: &'a [VNode<'a>]) -> RealNodeIterator<'a> {
|
||||||
RealNodeIterator::new(nodes)
|
RealNodeIterator::new(nodes)
|
||||||
}
|
}
|
||||||
|
|
||||||
struct RealNodeIterator<'a> {
|
pub struct RealNodeIterator<'a> {
|
||||||
nodes: &'a [VNode<'a>],
|
nodes: &'a [VNode<'a>],
|
||||||
|
|
||||||
// an idx for each level of nesting
|
// this node is always a "real" node
|
||||||
// it's highly highly unlikely to hit 4 levels of nested fragments
|
// the index is "what sibling # is it"
|
||||||
// so... we just don't support it
|
// IE in a list of children on a fragment, the node will be a text node that's the 5th sibling
|
||||||
nesting_idxs: [Option<u32>; 3],
|
node_stack: Vec<(&'a VNode<'a>, u32)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> RealNodeIterator<'a> {
|
impl<'a> RealNodeIterator<'a> {
|
||||||
|
// We immediately descend to the first real node we can find
|
||||||
fn new(nodes: &'a [VNode<'a>]) -> Self {
|
fn new(nodes: &'a [VNode<'a>]) -> Self {
|
||||||
Self {
|
let mut node_stack = Vec::new();
|
||||||
nodes,
|
if nodes.len() > 0 {
|
||||||
nesting_idxs: [None, None, None],
|
let mut cur_node = nodes.get(0).unwrap();
|
||||||
|
loop {
|
||||||
|
node_stack.push((cur_node, 0_u32));
|
||||||
|
if !cur_node.is_real() {
|
||||||
|
cur_node = cur_node.get_child(0).unwrap();
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Self { nodes, node_stack }
|
||||||
}
|
}
|
||||||
|
|
||||||
// advances the cursor to the next element, panicing if we're on the 3rd level and still finding fragments
|
// // advances the cursor to the next element, panicing if we're on the 3rd level and still finding fragments
|
||||||
fn advance_cursor(&mut self) {
|
// fn advance_cursor(&mut self) {
|
||||||
match self.nesting_idxs {
|
// let (mut cur_node, mut cur_id) = self.node_stack.last().unwrap();
|
||||||
[None, ..] => {}
|
|
||||||
|
// while !cur_node.is_real() {
|
||||||
|
// match cur_node {
|
||||||
|
// VNode::Element(_) | VNode::Text(_) => todo!(),
|
||||||
|
// VNode::Suspended => todo!(),
|
||||||
|
// VNode::Component(_) => todo!(),
|
||||||
|
// VNode::Fragment(frag) => {
|
||||||
|
// let p = frag.children;
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
fn next_node(&mut self) -> bool {
|
||||||
|
let (mut cur_node, cur_id) = self.node_stack.last_mut().unwrap();
|
||||||
|
|
||||||
|
match cur_node {
|
||||||
|
VNode::Fragment(frag) => {
|
||||||
|
//
|
||||||
|
if *cur_id + 1 > frag.children.len() as u32 {
|
||||||
|
self.node_stack.pop();
|
||||||
|
let next = self.node_stack.last_mut();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
*cur_id += 1;
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
VNode::Element(_) => todo!(),
|
||||||
|
VNode::Text(_) => todo!(),
|
||||||
|
VNode::Suspended => todo!(),
|
||||||
|
VNode::Component(_) => todo!(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_current_node(&self) -> Option<&VNode<'a>> {
|
fn get_current_node(&self) -> Option<&VNode<'a>> {
|
||||||
match self.nesting_idxs {
|
self.node_stack.last().map(|(node, id)| match node {
|
||||||
[None, None, None] => None,
|
VNode::Element(_) => todo!(),
|
||||||
[Some(a), None, None] => Some(&self.nodes[a as usize]),
|
VNode::Text(_) => todo!(),
|
||||||
[Some(a), Some(b), None] => {
|
VNode::Fragment(_) => todo!(),
|
||||||
//
|
VNode::Suspended => todo!(),
|
||||||
*&self.nodes[a as usize].get_child(b).as_ref()
|
VNode::Component(_) => todo!(),
|
||||||
}
|
})
|
||||||
[Some(a), Some(b), Some(c)] => {
|
|
||||||
//
|
|
||||||
*&self.nodes[a as usize]
|
|
||||||
.get_child(b)
|
|
||||||
.unwrap()
|
|
||||||
.get_child(c)
|
|
||||||
.as_ref()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -485,12 +520,26 @@ impl<'a> Iterator for RealNodeIterator<'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
mod tests {
|
mod tests {
|
||||||
|
use crate::debug_renderer::DebugRenderer;
|
||||||
use crate::nodebuilder::LazyNodes;
|
use crate::nodebuilder::LazyNodes;
|
||||||
|
|
||||||
|
use crate as dioxus;
|
||||||
|
use dioxus::prelude::*;
|
||||||
#[test]
|
#[test]
|
||||||
fn iterate_nodes() {
|
fn iterate_nodes() {
|
||||||
// let t1 = LazyNodes::new(|b| {
|
let rs = rsx! {
|
||||||
// //
|
Fragment {
|
||||||
// });
|
Fragment {
|
||||||
|
Fragment {
|
||||||
|
Fragment {
|
||||||
|
h1 {"abc1"}
|
||||||
|
}
|
||||||
|
h2 {"abc2"}
|
||||||
|
}
|
||||||
|
h3 {"abc3"}
|
||||||
|
}
|
||||||
|
h4 {"abc4"}
|
||||||
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,695 +0,0 @@
|
||||||
//! Changelist
|
|
||||||
//! ----------
|
|
||||||
//!
|
|
||||||
//! This module exposes the "changelist" object which allows 3rd party implementors to handle diffs to the virtual dom.
|
|
||||||
//!
|
|
||||||
//! # Design
|
|
||||||
//! ---
|
|
||||||
//! In essence, the changelist object connects a diff of two vdoms to the actual edits required to update the output renderer.
|
|
||||||
//!
|
|
||||||
//! This abstraction relies on the assumption that the final renderer accepts a tree of elements. For most target platforms,
|
|
||||||
//! this is an appropriate abstraction .
|
|
||||||
//!
|
|
||||||
//! During the diff phase, the change list is built. Once the diff phase is over, the change list is finished and returned back
|
|
||||||
//! to the renderer. The renderer is responsible for propogating the updates to the final display.
|
|
||||||
//!
|
|
||||||
//! Because the change list references data internal to the vdom, it needs to be consumed by the renderer before the vdom
|
|
||||||
//! can continue to work. This means once a change list is generated, it should be consumed as fast as possible, otherwise the
|
|
||||||
//! dom will be blocked from progressing. This is enforced by lifetimes on the returend changelist object.
|
|
||||||
//!
|
|
||||||
//! # Known Issues
|
|
||||||
//! ----
|
|
||||||
//! - stack machine approach does not work when 3rd party extensions inject elements (breaking our view of the dom) - solvable by the renderer
|
|
||||||
|
|
||||||
use crate::innerlude::ScopeIdx;
|
|
||||||
|
|
||||||
pub type EditList<'src> = Vec<Edit<'src>>;
|
|
||||||
|
|
||||||
/// The `Edit` represents a single modifcation of the renderer tree.
|
|
||||||
/// todo @jon, go through and make certain fields static. tag names should be known at compile time
|
|
||||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
|
||||||
#[cfg_attr(feature = "serde", serde(tag = "type"))]
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub enum Edit<'src_bump> {
|
|
||||||
// ========================================================
|
|
||||||
// Common Ops: The most common operation types
|
|
||||||
// ========================================================
|
|
||||||
SetText {
|
|
||||||
text: &'src_bump str,
|
|
||||||
},
|
|
||||||
SetClass {
|
|
||||||
class_name: &'src_bump str,
|
|
||||||
},
|
|
||||||
CreateTextNode {
|
|
||||||
text: &'src_bump str,
|
|
||||||
},
|
|
||||||
CreateElement {
|
|
||||||
// todo - make static?
|
|
||||||
tag_name: &'src_bump str,
|
|
||||||
},
|
|
||||||
CreateElementNs {
|
|
||||||
// todo - make static?
|
|
||||||
tag_name: &'src_bump str,
|
|
||||||
// todo - make static?
|
|
||||||
ns: &'src_bump str,
|
|
||||||
},
|
|
||||||
|
|
||||||
// ========================================================
|
|
||||||
// Attributes
|
|
||||||
// ========================================================
|
|
||||||
SetAttribute {
|
|
||||||
name: &'src_bump str,
|
|
||||||
value: &'src_bump str,
|
|
||||||
},
|
|
||||||
RemoveAttribute {
|
|
||||||
name: &'src_bump str,
|
|
||||||
},
|
|
||||||
RemoveChild {
|
|
||||||
n: u32,
|
|
||||||
},
|
|
||||||
|
|
||||||
// ============================================================
|
|
||||||
// Event Listeners: Event types and IDs used to update the VDOM
|
|
||||||
// ============================================================
|
|
||||||
NewListener {
|
|
||||||
// todo - make static?
|
|
||||||
event: &'src_bump str,
|
|
||||||
scope: ScopeIdx,
|
|
||||||
id: usize,
|
|
||||||
},
|
|
||||||
UpdateListener {
|
|
||||||
// todo - make static?
|
|
||||||
event: &'src_bump str,
|
|
||||||
scope: ScopeIdx,
|
|
||||||
id: usize,
|
|
||||||
},
|
|
||||||
RemoveListener {
|
|
||||||
// todo - make static?
|
|
||||||
event: &'src_bump str,
|
|
||||||
},
|
|
||||||
|
|
||||||
// ========================================================
|
|
||||||
// Cached Roots: The mount point for individual components
|
|
||||||
// Allows quick traversal to cached entrypoints
|
|
||||||
// ========================================================
|
|
||||||
// push a known node on to the stack
|
|
||||||
TraverseToKnown {
|
|
||||||
node: u32,
|
|
||||||
// node: ScopeIdx,
|
|
||||||
},
|
|
||||||
// Add the current top of the stack to the known nodes
|
|
||||||
MakeKnown {
|
|
||||||
node: u32,
|
|
||||||
// node: ScopeIdx,
|
|
||||||
},
|
|
||||||
// Remove the current top of the stack from the known nodes
|
|
||||||
RemoveKnown,
|
|
||||||
|
|
||||||
// ========================================================
|
|
||||||
// Stack OPs: Operations for manipulating the stack machine
|
|
||||||
// ========================================================
|
|
||||||
PushReverseChild {
|
|
||||||
n: u32,
|
|
||||||
},
|
|
||||||
PopPushChild {
|
|
||||||
n: u32,
|
|
||||||
},
|
|
||||||
Pop,
|
|
||||||
AppendChild,
|
|
||||||
RemoveSelfAndNextSiblings {},
|
|
||||||
ReplaceWith,
|
|
||||||
SaveChildrenToTemporaries {
|
|
||||||
temp: u32,
|
|
||||||
start: u32,
|
|
||||||
end: u32,
|
|
||||||
},
|
|
||||||
PushChild {
|
|
||||||
n: u32,
|
|
||||||
},
|
|
||||||
PushTemporary {
|
|
||||||
temp: u32,
|
|
||||||
},
|
|
||||||
InsertBefore,
|
|
||||||
PopPushReverseChild {
|
|
||||||
n: u32,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The edit machine represents a stream of differences between two component trees.
|
|
||||||
///
|
|
||||||
/// This struct is interesting in that it keeps track of differences by borrowing
|
|
||||||
/// from the source rather than writing to a new buffer. This means that the virtual dom
|
|
||||||
/// *cannot* be updated while this machine is in existence without "unsafe".
|
|
||||||
///
|
|
||||||
/// This unsafety is handled by methods on the virtual dom and is not exposed via lib code.
|
|
||||||
pub struct EditMachine<'lock> {
|
|
||||||
pub traversal: Traversal,
|
|
||||||
next_temporary: u32,
|
|
||||||
forcing_new_listeners: bool,
|
|
||||||
pub cur_height: u32,
|
|
||||||
|
|
||||||
// // if the current node is a "known" node
|
|
||||||
// // any actions that modify this node should update the mapping
|
|
||||||
// current_known: Option<u32>,
|
|
||||||
pub emitter: EditList<'lock>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'lock> EditMachine<'lock> {
|
|
||||||
pub fn new() -> Self {
|
|
||||||
Self {
|
|
||||||
// current_known: None,
|
|
||||||
traversal: Traversal::new(),
|
|
||||||
cur_height: 0,
|
|
||||||
next_temporary: 0,
|
|
||||||
forcing_new_listeners: false,
|
|
||||||
emitter: EditList::<'lock>::default(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ===================================
|
|
||||||
// Traversal Methods
|
|
||||||
// ===================================
|
|
||||||
impl<'src> EditMachine<'src> {
|
|
||||||
pub fn go_down(&mut self) {
|
|
||||||
self.traversal.down();
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn go_down_to_child(&mut self, index: usize) {
|
|
||||||
self.traversal.down();
|
|
||||||
self.traversal.sibling(index);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn go_down_to_reverse_child(&mut self, index: usize) {
|
|
||||||
self.traversal.down();
|
|
||||||
self.traversal.reverse_sibling(index);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn go_up(&mut self) {
|
|
||||||
self.traversal.up();
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn go_to_sibling(&mut self, index: usize) {
|
|
||||||
self.traversal.sibling(index);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn go_to_temp_sibling(&mut self, temp: u32) {
|
|
||||||
self.traversal.up();
|
|
||||||
self.traversal.down_to_temp(temp);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn go_down_to_temp_child(&mut self, temp: u32) {
|
|
||||||
self.traversal.down_to_temp(temp);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn commit_traversal(&mut self) {
|
|
||||||
if self.traversal.is_committed() {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
for mv in self.traversal.commit() {
|
|
||||||
match mv {
|
|
||||||
MoveTo::Parent => self.emitter.push(Edit::Pop {}),
|
|
||||||
MoveTo::Child(n) => self.emitter.push(Edit::PushChild { n }),
|
|
||||||
MoveTo::ReverseChild(n) => self.emitter.push(Edit::PushReverseChild { n }),
|
|
||||||
MoveTo::Sibling(n) => self.emitter.push(Edit::PopPushChild { n }),
|
|
||||||
MoveTo::ReverseSibling(n) => self.emitter.push(Edit::PopPushReverseChild { n }),
|
|
||||||
MoveTo::TempChild(temp) => self.emitter.push(Edit::PushTemporary { temp }),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn traversal_is_committed(&self) -> bool {
|
|
||||||
self.traversal.is_committed()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ===================================
|
|
||||||
// Stack methods
|
|
||||||
// ===================================
|
|
||||||
impl<'a> EditMachine<'a> {
|
|
||||||
pub fn next_temporary(&self) -> u32 {
|
|
||||||
self.next_temporary
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_next_temporary(&mut self, next_temporary: u32) {
|
|
||||||
self.next_temporary = next_temporary;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn save_children_to_temporaries(&mut self, start: usize, end: usize) -> u32 {
|
|
||||||
debug_assert!(self.traversal_is_committed());
|
|
||||||
debug_assert!(start < end);
|
|
||||||
let temp_base = self.next_temporary;
|
|
||||||
self.next_temporary = temp_base + (end - start) as u32;
|
|
||||||
self.emitter.push(Edit::SaveChildrenToTemporaries {
|
|
||||||
temp: temp_base,
|
|
||||||
start: start as u32,
|
|
||||||
end: end as u32,
|
|
||||||
});
|
|
||||||
temp_base
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn push_temporary(&mut self, temp: u32) {
|
|
||||||
debug_assert!(self.traversal_is_committed());
|
|
||||||
|
|
||||||
self.emitter.push(Edit::PushTemporary { temp });
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn remove_child(&mut self, child: usize) {
|
|
||||||
debug_assert!(self.traversal_is_committed());
|
|
||||||
|
|
||||||
self.emitter.push(Edit::RemoveChild { n: child as u32 })
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn insert_before(&mut self) {
|
|
||||||
debug_assert!(self.traversal_is_committed());
|
|
||||||
|
|
||||||
self.emitter.push(Edit::InsertBefore {})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_text(&mut self, text: &'a str) {
|
|
||||||
debug_assert!(self.traversal_is_committed());
|
|
||||||
self.emitter.push(Edit::SetText { text });
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn remove_self_and_next_siblings(&mut self) {
|
|
||||||
debug_assert!(self.traversal_is_committed());
|
|
||||||
self.emitter.push(Edit::RemoveSelfAndNextSiblings {});
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn replace_with(&mut self) {
|
|
||||||
debug_assert!(self.traversal_is_committed());
|
|
||||||
// debug!("emit: replace_with()");
|
|
||||||
// if let Some(id) = self.current_known {
|
|
||||||
// // update mapping
|
|
||||||
// self.emitter.push(Edit::MakeKnown{node: id});
|
|
||||||
// self.current_known = None;
|
|
||||||
// }
|
|
||||||
// self.emitter.replace_with();
|
|
||||||
self.emitter.push(Edit::ReplaceWith {});
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_attribute(&mut self, name: &'a str, value: &'a str, is_namespaced: bool) {
|
|
||||||
debug_assert!(self.traversal_is_committed());
|
|
||||||
|
|
||||||
if name == "class" && !is_namespaced {
|
|
||||||
self.emitter.push(Edit::SetClass { class_name: value });
|
|
||||||
} else {
|
|
||||||
self.emitter.push(Edit::SetAttribute { name, value });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn remove_attribute(&mut self, name: &'a str) {
|
|
||||||
self.emitter.push(Edit::RemoveAttribute { name });
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn append_child(&mut self) {
|
|
||||||
debug_assert!(self.traversal_is_committed());
|
|
||||||
self.emitter.push(Edit::AppendChild {});
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn create_text_node(&mut self, text: &'a str) {
|
|
||||||
debug_assert!(self.traversal_is_committed());
|
|
||||||
self.emitter.push(Edit::CreateTextNode { text });
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn create_element(&mut self, tag_name: &'a str) {
|
|
||||||
debug_assert!(self.traversal_is_committed());
|
|
||||||
self.emitter.push(Edit::CreateElement { tag_name });
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn create_element_ns(&mut self, tag_name: &'a str, ns: &'a str) {
|
|
||||||
debug_assert!(self.traversal_is_committed());
|
|
||||||
self.emitter.push(Edit::CreateElementNs { tag_name, ns });
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn push_force_new_listeners(&mut self) -> bool {
|
|
||||||
let old = self.forcing_new_listeners;
|
|
||||||
self.forcing_new_listeners = true;
|
|
||||||
old
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn pop_force_new_listeners(&mut self, previous: bool) {
|
|
||||||
debug_assert!(self.forcing_new_listeners);
|
|
||||||
self.forcing_new_listeners = previous;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn new_event_listener(&mut self, event: &'a str, scope: ScopeIdx, id: usize) {
|
|
||||||
debug_assert!(self.traversal_is_committed());
|
|
||||||
self.emitter.push(Edit::NewListener { event, scope, id });
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn update_event_listener(&mut self, event: &'a str, scope: ScopeIdx, id: usize) {
|
|
||||||
debug_assert!(self.traversal_is_committed());
|
|
||||||
if self.forcing_new_listeners {
|
|
||||||
self.new_event_listener(event, scope, id);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
self.emitter.push(Edit::NewListener { event, scope, id });
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn remove_event_listener(&mut self, event: &'a str) {
|
|
||||||
debug_assert!(self.traversal_is_committed());
|
|
||||||
self.emitter.push(Edit::RemoveListener { event });
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn save_known_root(&mut self, id: u32) {
|
|
||||||
log::debug!("emit: save_known_root({:?})", id);
|
|
||||||
self.emitter.push(Edit::MakeKnown { node: id })
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn load_known_root(&mut self, id: u32) {
|
|
||||||
log::debug!("emit: TraverseToKnown({:?})", id);
|
|
||||||
self.emitter.push(Edit::TraverseToKnown { node: id })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Keeps track of where we are moving in a DOM tree, and shortens traversal
|
|
||||||
// paths between mutations to their minimal number of operations.
|
|
||||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
|
||||||
pub enum MoveTo {
|
|
||||||
/// Move from the current node up to its parent.
|
|
||||||
Parent,
|
|
||||||
|
|
||||||
/// Move to the current node's n^th child.
|
|
||||||
Child(u32),
|
|
||||||
|
|
||||||
/// Move to the current node's n^th from last child.
|
|
||||||
ReverseChild(u32),
|
|
||||||
|
|
||||||
/// Move to the n^th sibling. Not relative from the current
|
|
||||||
/// location. Absolute indexed within all of the current siblings.
|
|
||||||
Sibling(u32),
|
|
||||||
|
|
||||||
/// Move to the n^th from last sibling. Not relative from the current
|
|
||||||
/// location. Absolute indexed within all of the current siblings.
|
|
||||||
ReverseSibling(u32),
|
|
||||||
|
|
||||||
/// Move down to the given saved temporary child.
|
|
||||||
TempChild(u32),
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct Traversal {
|
|
||||||
uncommitted: Vec<MoveTo>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Traversal {
|
|
||||||
/// Construct a new `Traversal` with its internal storage backed by the
|
|
||||||
/// given bump arena.
|
|
||||||
pub fn new() -> Traversal {
|
|
||||||
Traversal {
|
|
||||||
uncommitted: Vec::with_capacity(32),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Move the traversal up in the tree.
|
|
||||||
pub fn up(&mut self) {
|
|
||||||
match self.uncommitted.last() {
|
|
||||||
Some(MoveTo::Sibling(_)) | Some(MoveTo::ReverseSibling(_)) => {
|
|
||||||
self.uncommitted.pop();
|
|
||||||
self.uncommitted.push(MoveTo::Parent);
|
|
||||||
}
|
|
||||||
Some(MoveTo::TempChild(_)) | Some(MoveTo::Child(_)) | Some(MoveTo::ReverseChild(_)) => {
|
|
||||||
self.uncommitted.pop();
|
|
||||||
// And we're back at the parent.
|
|
||||||
}
|
|
||||||
_ => {
|
|
||||||
self.uncommitted.push(MoveTo::Parent);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Move the traversal down in the tree to the first child of the current
|
|
||||||
/// node.
|
|
||||||
pub fn down(&mut self) {
|
|
||||||
if let Some(&MoveTo::Parent) = self.uncommitted.last() {
|
|
||||||
self.uncommitted.pop();
|
|
||||||
self.sibling(0);
|
|
||||||
} else {
|
|
||||||
self.uncommitted.push(MoveTo::Child(0));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Move the traversal to the n^th sibling.
|
|
||||||
pub fn sibling(&mut self, index: usize) {
|
|
||||||
let index = index as u32;
|
|
||||||
match self.uncommitted.last_mut() {
|
|
||||||
Some(MoveTo::Sibling(ref mut n)) | Some(MoveTo::Child(ref mut n)) => {
|
|
||||||
*n = index;
|
|
||||||
}
|
|
||||||
Some(MoveTo::ReverseSibling(_)) => {
|
|
||||||
self.uncommitted.pop();
|
|
||||||
self.uncommitted.push(MoveTo::Sibling(index));
|
|
||||||
}
|
|
||||||
Some(MoveTo::TempChild(_)) | Some(MoveTo::ReverseChild(_)) => {
|
|
||||||
self.uncommitted.pop();
|
|
||||||
self.uncommitted.push(MoveTo::Child(index))
|
|
||||||
}
|
|
||||||
_ => {
|
|
||||||
self.uncommitted.push(MoveTo::Sibling(index));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Move the the n^th from last sibling.
|
|
||||||
pub fn reverse_sibling(&mut self, index: usize) {
|
|
||||||
let index = index as u32;
|
|
||||||
match self.uncommitted.last_mut() {
|
|
||||||
Some(MoveTo::ReverseSibling(ref mut n)) | Some(MoveTo::ReverseChild(ref mut n)) => {
|
|
||||||
*n = index;
|
|
||||||
}
|
|
||||||
Some(MoveTo::Sibling(_)) => {
|
|
||||||
self.uncommitted.pop();
|
|
||||||
self.uncommitted.push(MoveTo::ReverseSibling(index));
|
|
||||||
}
|
|
||||||
Some(MoveTo::TempChild(_)) | Some(MoveTo::Child(_)) => {
|
|
||||||
self.uncommitted.pop();
|
|
||||||
self.uncommitted.push(MoveTo::ReverseChild(index))
|
|
||||||
}
|
|
||||||
_ => {
|
|
||||||
self.uncommitted.push(MoveTo::ReverseSibling(index));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Go to the given saved temporary.
|
|
||||||
pub fn down_to_temp(&mut self, temp: u32) {
|
|
||||||
match self.uncommitted.last() {
|
|
||||||
Some(MoveTo::Sibling(_)) | Some(MoveTo::ReverseSibling(_)) => {
|
|
||||||
self.uncommitted.pop();
|
|
||||||
}
|
|
||||||
Some(MoveTo::Parent)
|
|
||||||
| Some(MoveTo::TempChild(_))
|
|
||||||
| Some(MoveTo::Child(_))
|
|
||||||
| Some(MoveTo::ReverseChild(_))
|
|
||||||
| None => {
|
|
||||||
// Can't remove moves to parents since we rely on their stack
|
|
||||||
// pops.
|
|
||||||
}
|
|
||||||
}
|
|
||||||
self.uncommitted.push(MoveTo::TempChild(temp));
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Are all the traversal's moves committed? That is, are there no moves
|
|
||||||
/// that have *not* been committed yet?
|
|
||||||
#[inline]
|
|
||||||
pub fn is_committed(&self) -> bool {
|
|
||||||
self.uncommitted.is_empty()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Commit this traversals moves and return the optimized path from the last
|
|
||||||
/// commit.
|
|
||||||
#[inline]
|
|
||||||
pub fn commit(&mut self) -> std::vec::Drain<'_, MoveTo> {
|
|
||||||
self.uncommitted.drain(..)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn reset(&mut self) {
|
|
||||||
self.uncommitted.clear();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use super::*;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_traversal() {
|
|
||||||
fn t<F>(f: F) -> Box<dyn FnMut(&mut Traversal)>
|
|
||||||
where
|
|
||||||
F: 'static + FnMut(&mut Traversal),
|
|
||||||
{
|
|
||||||
Box::new(f) as _
|
|
||||||
}
|
|
||||||
|
|
||||||
for (mut traverse, expected_moves) in vec![
|
|
||||||
(
|
|
||||||
t(|t| {
|
|
||||||
t.down();
|
|
||||||
}),
|
|
||||||
vec![MoveTo::Child(0)],
|
|
||||||
),
|
|
||||||
(
|
|
||||||
t(|t| {
|
|
||||||
t.up();
|
|
||||||
}),
|
|
||||||
vec![MoveTo::Parent],
|
|
||||||
),
|
|
||||||
(
|
|
||||||
t(|t| {
|
|
||||||
t.sibling(42);
|
|
||||||
}),
|
|
||||||
vec![MoveTo::Sibling(42)],
|
|
||||||
),
|
|
||||||
(
|
|
||||||
t(|t| {
|
|
||||||
t.down();
|
|
||||||
t.up();
|
|
||||||
}),
|
|
||||||
vec![],
|
|
||||||
),
|
|
||||||
(
|
|
||||||
t(|t| {
|
|
||||||
t.down();
|
|
||||||
t.sibling(2);
|
|
||||||
t.up();
|
|
||||||
}),
|
|
||||||
vec![],
|
|
||||||
),
|
|
||||||
(
|
|
||||||
t(|t| {
|
|
||||||
t.down();
|
|
||||||
t.sibling(3);
|
|
||||||
}),
|
|
||||||
vec![MoveTo::Child(3)],
|
|
||||||
),
|
|
||||||
(
|
|
||||||
t(|t| {
|
|
||||||
t.down();
|
|
||||||
t.sibling(4);
|
|
||||||
t.sibling(8);
|
|
||||||
}),
|
|
||||||
vec![MoveTo::Child(8)],
|
|
||||||
),
|
|
||||||
(
|
|
||||||
t(|t| {
|
|
||||||
t.sibling(1);
|
|
||||||
t.sibling(1);
|
|
||||||
}),
|
|
||||||
vec![MoveTo::Sibling(1)],
|
|
||||||
),
|
|
||||||
(
|
|
||||||
t(|t| {
|
|
||||||
t.reverse_sibling(3);
|
|
||||||
}),
|
|
||||||
vec![MoveTo::ReverseSibling(3)],
|
|
||||||
),
|
|
||||||
(
|
|
||||||
t(|t| {
|
|
||||||
t.down();
|
|
||||||
t.reverse_sibling(3);
|
|
||||||
}),
|
|
||||||
vec![MoveTo::ReverseChild(3)],
|
|
||||||
),
|
|
||||||
(
|
|
||||||
t(|t| {
|
|
||||||
t.down();
|
|
||||||
t.reverse_sibling(3);
|
|
||||||
t.up();
|
|
||||||
}),
|
|
||||||
vec![],
|
|
||||||
),
|
|
||||||
(
|
|
||||||
t(|t| {
|
|
||||||
t.down();
|
|
||||||
t.reverse_sibling(3);
|
|
||||||
t.reverse_sibling(6);
|
|
||||||
}),
|
|
||||||
vec![MoveTo::ReverseChild(6)],
|
|
||||||
),
|
|
||||||
(
|
|
||||||
t(|t| {
|
|
||||||
t.up();
|
|
||||||
t.reverse_sibling(3);
|
|
||||||
t.reverse_sibling(6);
|
|
||||||
}),
|
|
||||||
vec![MoveTo::Parent, MoveTo::ReverseSibling(6)],
|
|
||||||
),
|
|
||||||
(
|
|
||||||
t(|t| {
|
|
||||||
t.up();
|
|
||||||
t.sibling(3);
|
|
||||||
t.sibling(6);
|
|
||||||
}),
|
|
||||||
vec![MoveTo::Parent, MoveTo::Sibling(6)],
|
|
||||||
),
|
|
||||||
(
|
|
||||||
t(|t| {
|
|
||||||
t.sibling(3);
|
|
||||||
t.sibling(6);
|
|
||||||
t.up();
|
|
||||||
}),
|
|
||||||
vec![MoveTo::Parent],
|
|
||||||
),
|
|
||||||
(
|
|
||||||
t(|t| {
|
|
||||||
t.reverse_sibling(3);
|
|
||||||
t.reverse_sibling(6);
|
|
||||||
t.up();
|
|
||||||
}),
|
|
||||||
vec![MoveTo::Parent],
|
|
||||||
),
|
|
||||||
(
|
|
||||||
t(|t| {
|
|
||||||
t.down();
|
|
||||||
t.down_to_temp(3);
|
|
||||||
}),
|
|
||||||
vec![MoveTo::Child(0), MoveTo::TempChild(3)],
|
|
||||||
),
|
|
||||||
(
|
|
||||||
t(|t| {
|
|
||||||
t.down_to_temp(3);
|
|
||||||
t.sibling(5);
|
|
||||||
}),
|
|
||||||
vec![MoveTo::Child(5)],
|
|
||||||
),
|
|
||||||
(
|
|
||||||
t(|t| {
|
|
||||||
t.down_to_temp(3);
|
|
||||||
t.reverse_sibling(5);
|
|
||||||
}),
|
|
||||||
vec![MoveTo::ReverseChild(5)],
|
|
||||||
),
|
|
||||||
(
|
|
||||||
t(|t| {
|
|
||||||
t.down_to_temp(3);
|
|
||||||
t.up();
|
|
||||||
}),
|
|
||||||
vec![],
|
|
||||||
),
|
|
||||||
(
|
|
||||||
t(|t| {
|
|
||||||
t.sibling(2);
|
|
||||||
t.up();
|
|
||||||
t.down_to_temp(3);
|
|
||||||
}),
|
|
||||||
vec![MoveTo::Parent, MoveTo::TempChild(3)],
|
|
||||||
),
|
|
||||||
(
|
|
||||||
t(|t| {
|
|
||||||
t.up();
|
|
||||||
t.down_to_temp(3);
|
|
||||||
}),
|
|
||||||
vec![MoveTo::Parent, MoveTo::TempChild(3)],
|
|
||||||
),
|
|
||||||
] {
|
|
||||||
let mut traversal = Traversal::new();
|
|
||||||
traverse(&mut traversal);
|
|
||||||
let actual_moves: Vec<_> = traversal.commit().collect();
|
|
||||||
assert_eq!(actual_moves, expected_moves);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -64,8 +64,11 @@ pub struct VirtualDom {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy)]
|
#[derive(Clone, Copy)]
|
||||||
pub struct RealDomNode(u32);
|
pub struct RealDomNode(pub u32);
|
||||||
impl RealDomNode {
|
impl RealDomNode {
|
||||||
|
pub fn new(id: u32) -> Self {
|
||||||
|
Self(id)
|
||||||
|
}
|
||||||
pub fn empty() -> Self {
|
pub fn empty() -> Self {
|
||||||
Self(u32::MIN)
|
Self(u32::MIN)
|
||||||
}
|
}
|
||||||
|
@ -318,7 +321,8 @@ impl VirtualDom {
|
||||||
cur_component.run_scope()?;
|
cur_component.run_scope()?;
|
||||||
// diff_machine.change_list.load_known_root(1);
|
// diff_machine.change_list.load_known_root(1);
|
||||||
|
|
||||||
let (old, new) = cur_component.get_frames_mut();
|
let (old, new) = (cur_component.old_frame(), cur_component.next_frame());
|
||||||
|
// let (old, new) = cur_component.get_frames_mut();
|
||||||
diff_machine.diff_node(old, new);
|
diff_machine.diff_node(old, new);
|
||||||
|
|
||||||
// cur_height = cur_component.height;
|
// cur_height = cur_component.height;
|
||||||
|
@ -527,7 +531,8 @@ impl Scope {
|
||||||
let EventTrigger {
|
let EventTrigger {
|
||||||
listener_id, event, ..
|
listener_id, event, ..
|
||||||
} = trigger;
|
} = trigger;
|
||||||
//
|
|
||||||
|
// todo: implement scanning for outdated events
|
||||||
unsafe {
|
unsafe {
|
||||||
// Convert the raw ptr into an actual object
|
// Convert the raw ptr into an actual object
|
||||||
// This operation is assumed to be safe
|
// This operation is assumed to be safe
|
||||||
|
@ -547,12 +552,6 @@ impl Scope {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_frames_mut<'bump>(
|
|
||||||
&'bump mut self,
|
|
||||||
) -> (&'bump mut VNode<'bump>, &'bump mut VNode<'bump>) {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn next_frame<'bump>(&'bump self) -> &'bump VNode<'bump> {
|
pub fn next_frame<'bump>(&'bump self) -> &'bump VNode<'bump> {
|
||||||
self.frames.current_head_node()
|
self.frames.current_head_node()
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,8 +27,8 @@ atoms = { path="../atoms" }
|
||||||
# futures = "0.3.12"
|
# futures = "0.3.12"
|
||||||
# html-validation = { path = "../html-validation", version = "0.1.1" }
|
# html-validation = { path = "../html-validation", version = "0.1.1" }
|
||||||
|
|
||||||
async-channel = "1.6.1"
|
# async-channel = "1.6.1"
|
||||||
wee_alloc = "0.4.5"
|
nohash-hasher = "0.2.0"
|
||||||
# futures-lite = "1.11.3"
|
# futures-lite = "1.11.3"
|
||||||
|
|
||||||
[dependencies.web-sys]
|
[dependencies.web-sys]
|
||||||
|
@ -79,6 +79,8 @@ opt-level = 's'
|
||||||
crate-type = ["cdylib", "rlib"]
|
crate-type = ["cdylib", "rlib"]
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
|
im-rc = "15.0.0"
|
||||||
|
rand = { version="0.8.4", features=["small_rng"] }
|
||||||
uuid = { version="0.8.2", features=["v4", "wasm-bindgen"] }
|
uuid = { version="0.8.2", features=["v4", "wasm-bindgen"] }
|
||||||
|
|
||||||
[[example]]
|
[[example]]
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
//! Basic example that renders a simple VNode to the browser.
|
//! Basic example that renders a simple VNode to the browser.
|
||||||
|
|
||||||
|
use dioxus_core as dioxus;
|
||||||
use dioxus_core::prelude::*;
|
use dioxus_core::prelude::*;
|
||||||
use dioxus_web::*;
|
use dioxus_web::*;
|
||||||
|
|
||||||
|
@ -50,15 +51,15 @@ static DocExamples: FC<()> = |ctx| {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
rsx! {
|
// rsx! {
|
||||||
div {}
|
// div {}
|
||||||
h1 {}
|
// h1 {}
|
||||||
{""}
|
// {""}
|
||||||
"asbasd"
|
// "asbasd"
|
||||||
dioxus::Fragment {
|
// dioxus::Fragment {
|
||||||
//
|
// //
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
ctx.render(rsx! {
|
ctx.render(rsx! {
|
||||||
div {
|
div {
|
||||||
|
|
|
@ -2,14 +2,14 @@
|
||||||
//! --------------------
|
//! --------------------
|
||||||
//! This example demonstrates how to use the raw context api for sharing state throughout the VirtualDOM Tree.
|
//! This example demonstrates how to use the raw context api for sharing state throughout the VirtualDOM Tree.
|
||||||
//! A custom context must be its own unique type - otherwise use_context will fail. A context may be c
|
//! A custom context must be its own unique type - otherwise use_context will fail. A context may be c
|
||||||
//!
|
//!
|
||||||
//!
|
//!
|
||||||
//!
|
//!
|
||||||
//!
|
//!
|
||||||
//!
|
//!
|
||||||
//!
|
//!
|
||||||
//!
|
//!
|
||||||
//!
|
//!
|
||||||
|
|
||||||
use dioxus_core::prelude::*;
|
use dioxus_core::prelude::*;
|
||||||
use dioxus_web::WebsysRenderer;
|
use dioxus_web::WebsysRenderer;
|
||||||
|
@ -20,7 +20,6 @@ fn main() {
|
||||||
wasm_bindgen_futures::spawn_local(WebsysRenderer::start(Example));
|
wasm_bindgen_futures::spawn_local(WebsysRenderer::start(Example));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
struct CustomContext([&'static str; 3]);
|
struct CustomContext([&'static str; 3]);
|
||||||
|
|
||||||
|
@ -46,7 +45,6 @@ static Example: FC<()> = |ctx| {
|
||||||
})
|
})
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
#[derive(Props, PartialEq)]
|
#[derive(Props, PartialEq)]
|
||||||
struct ButtonProps {
|
struct ButtonProps {
|
||||||
id: u8,
|
id: u8,
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
|
|
||||||
|
use dioxus_core as dioxus;
|
||||||
use dioxus_web::{dioxus::prelude::*, WebsysRenderer};
|
use dioxus_web::{dioxus::prelude::*, WebsysRenderer};
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
use dioxus_core as dioxus;
|
||||||
use dioxus_web::{dioxus::prelude::*, WebsysRenderer};
|
use dioxus_web::{dioxus::prelude::*, WebsysRenderer};
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
|
|
|
@ -8,10 +8,6 @@ fn main() {
|
||||||
wasm_bindgen_futures::spawn_local(WebsysRenderer::start(App));
|
wasm_bindgen_futures::spawn_local(WebsysRenderer::start(App));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Use `wee_alloc` as the global allocator.
|
|
||||||
#[global_allocator]
|
|
||||||
static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT;
|
|
||||||
|
|
||||||
fn App(ctx: Context<()>) -> VNode {
|
fn App(ctx: Context<()>) -> VNode {
|
||||||
let cansee = use_state_new(&ctx, || false);
|
let cansee = use_state_new(&ctx, || false);
|
||||||
rsx! { in ctx,
|
rsx! { in ctx,
|
||||||
|
|
|
@ -5,17 +5,17 @@ use dioxus_core::prelude::*;
|
||||||
fn main() {}
|
fn main() {}
|
||||||
|
|
||||||
fn autocomplete() {
|
fn autocomplete() {
|
||||||
let handler = move |evt| {
|
// let handler = move |evt| {
|
||||||
let r = evt.alt_key();
|
// let r = evt.alt_key();
|
||||||
if evt.alt_key() {}
|
// if evt.alt_key() {}
|
||||||
};
|
// };
|
||||||
|
|
||||||
let g = rsx! {
|
// let g = rsx! {
|
||||||
button {
|
// button {
|
||||||
button {
|
// button {
|
||||||
onclick: {handler}
|
// onclick: {handler}
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
};
|
// };
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,184 @@
|
||||||
|
//! JS Framework Benchmark
|
||||||
|
//! ----------------------
|
||||||
|
//!
|
||||||
|
//! This example is used in the JS framework benchmarking tool to compare Dioxus' performance with other frontend frameworks.
|
||||||
|
//!
|
||||||
|
//!
|
||||||
|
//!
|
||||||
|
|
||||||
|
use std::rc::Rc;
|
||||||
|
|
||||||
|
use dioxus::events::on::MouseEvent;
|
||||||
|
use dioxus_core as dioxus;
|
||||||
|
use dioxus_core::prelude::*;
|
||||||
|
use dioxus_web::WebsysRenderer;
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
wasm_logger::init(wasm_logger::Config::new(log::Level::Debug));
|
||||||
|
console_error_panic_hook::set_once();
|
||||||
|
wasm_bindgen_futures::spawn_local(WebsysRenderer::start(App));
|
||||||
|
}
|
||||||
|
|
||||||
|
// We use a special immutable hashmap to make hashmap operations efficient
|
||||||
|
type RowList = im_rc::HashMap<usize, Rc<str>, nohash_hasher::BuildNoHashHasher<usize>>;
|
||||||
|
|
||||||
|
static App: FC<()> = |cx| {
|
||||||
|
let (items, set_items) = use_state(&cx, || RowList::default());
|
||||||
|
let (selection, set_selection) = use_state(&cx, || None as Option<usize>);
|
||||||
|
|
||||||
|
let create_rendered_rows = move |from, num| move |_| set_items(create_row_list(from, num));
|
||||||
|
|
||||||
|
let append_1_000_rows =
|
||||||
|
move |_| set_items(create_row_list(items.len(), 1000).union(items.clone()));
|
||||||
|
|
||||||
|
let update_every_10th_row = move |_| {
|
||||||
|
let mut new_items = items.clone();
|
||||||
|
let mut small_rng = SmallRng::from_entropy();
|
||||||
|
new_items
|
||||||
|
.iter_mut()
|
||||||
|
.step_by(10)
|
||||||
|
.for_each(|(_, val)| *val = create_new_row_label(&mut small_rng));
|
||||||
|
set_items(new_items);
|
||||||
|
};
|
||||||
|
let clear_rows = move |_| set_items(RowList::default());
|
||||||
|
|
||||||
|
let swap_rows = move |_| {
|
||||||
|
// this looks a bit ugly because we're using a hashmap instead of a vec
|
||||||
|
if items.len() > 998 {
|
||||||
|
let mut new_items = items.clone();
|
||||||
|
let a = new_items.get(&0).unwrap().clone();
|
||||||
|
*new_items.get_mut(&0).unwrap() = new_items.get(&998).unwrap().clone();
|
||||||
|
*new_items.get_mut(&998).unwrap() = a;
|
||||||
|
set_items(new_items);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let rows = items.iter().map(|(key, value)| {
|
||||||
|
rsx!(Row {
|
||||||
|
key: "{key}",
|
||||||
|
row_id: *key as usize,
|
||||||
|
label: value.clone(),
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
cx.render(rsx! {
|
||||||
|
div { class: "container"
|
||||||
|
div { class: "jumbotron"
|
||||||
|
div { class: "row"
|
||||||
|
div { class: "col-md-6", h1 { "Dioxus" } }
|
||||||
|
div { class: "col-md-6"
|
||||||
|
div { class: "row"
|
||||||
|
ActionButton { name: "Create 1,000 rows", id: "run", action: create_rendered_rows(0, 1_000) }
|
||||||
|
ActionButton { name: "Create 10,000 rows", id: "runlots", action: create_rendered_rows(0, 10_000) }
|
||||||
|
ActionButton { name: "Append 1,000 rows", id: "add", action: append_1_000_rows }
|
||||||
|
ActionButton { name: "Update every 10th row", id: "update", action: update_every_10th_row, }
|
||||||
|
ActionButton { name: "Clear", id: "clear", action: clear_rows }
|
||||||
|
ActionButton { name: "Swap rows", id: "swaprows", action: swap_rows }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
table {
|
||||||
|
tbody {
|
||||||
|
{rows}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
span {}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Props)]
|
||||||
|
struct ActionButtonProps<F: Fn(Rc<dyn MouseEvent>)> {
|
||||||
|
name: &'static str,
|
||||||
|
id: &'static str,
|
||||||
|
action: F,
|
||||||
|
}
|
||||||
|
fn ActionButton<F: Fn(Rc<dyn MouseEvent>)>(cx: Context<ActionButtonProps<F>>) -> VNode {
|
||||||
|
cx.render(rsx! {
|
||||||
|
div { class: "col-sm-6 smallpad"
|
||||||
|
button {class:"btn btn-primary btn-block", type: "button", id: "{cx.id}", onclick: {&cx.action},
|
||||||
|
"{cx.name}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#[derive(PartialEq, Props)]
|
||||||
|
struct RowProps {
|
||||||
|
row_id: usize,
|
||||||
|
label: Rc<str>,
|
||||||
|
}
|
||||||
|
fn Row<'a>(cx: Context<'a, RowProps>) -> VNode {
|
||||||
|
cx.render(rsx! {
|
||||||
|
tr {
|
||||||
|
td { class:"col-md-1", "{cx.row_id}" }
|
||||||
|
td { class:"col-md-1", onclick: move |_| { /* run onselect */ }
|
||||||
|
a { class: "lbl", "{cx.label}" }
|
||||||
|
}
|
||||||
|
td { class: "col-md-1"
|
||||||
|
a { class: "remove", onclick: move |_| {/* remove */}
|
||||||
|
span { class: "glyphicon glyphicon-remove remove" aria_hidden: "true" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
td { class: "col-md-6" }
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
use rand::prelude::*;
|
||||||
|
fn create_new_row_label(rng: &mut SmallRng) -> Rc<str> {
|
||||||
|
let mut label = String::new();
|
||||||
|
label.push_str(ADJECTIVES.choose(rng).unwrap());
|
||||||
|
label.push(' ');
|
||||||
|
label.push_str(COLOURS.choose(rng).unwrap());
|
||||||
|
label.push(' ');
|
||||||
|
label.push_str(NOUNS.choose(rng).unwrap());
|
||||||
|
Rc::from(label)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_row_list(from: usize, num: usize) -> RowList {
|
||||||
|
let mut small_rng = SmallRng::from_entropy();
|
||||||
|
(from..num + from)
|
||||||
|
.map(|f| (f, create_new_row_label(&mut small_rng)))
|
||||||
|
.collect::<RowList>()
|
||||||
|
}
|
||||||
|
|
||||||
|
static ADJECTIVES: &[&str] = &[
|
||||||
|
"pretty",
|
||||||
|
"large",
|
||||||
|
"big",
|
||||||
|
"small",
|
||||||
|
"tall",
|
||||||
|
"short",
|
||||||
|
"long",
|
||||||
|
"handsome",
|
||||||
|
"plain",
|
||||||
|
"quaint",
|
||||||
|
"clean",
|
||||||
|
"elegant",
|
||||||
|
"easy",
|
||||||
|
"angry",
|
||||||
|
"crazy",
|
||||||
|
"helpful",
|
||||||
|
"mushy",
|
||||||
|
"odd",
|
||||||
|
"unsightly",
|
||||||
|
"adorable",
|
||||||
|
"important",
|
||||||
|
"inexpensive",
|
||||||
|
"cheap",
|
||||||
|
"expensive",
|
||||||
|
"fancy",
|
||||||
|
];
|
||||||
|
|
||||||
|
static COLOURS: &[&str] = &[
|
||||||
|
"red", "yellow", "blue", "green", "pink", "brown", "purple", "brown", "white", "black",
|
||||||
|
"orange",
|
||||||
|
];
|
||||||
|
|
||||||
|
static NOUNS: &[&str] = &[
|
||||||
|
"table", "chair", "house", "bbq", "desk", "car", "pony", "cookie", "sandwich", "burger",
|
||||||
|
"pizza", "mouse", "keyboard",
|
||||||
|
];
|
|
@ -1,3 +1,4 @@
|
||||||
|
use dioxus_core as dioxus;
|
||||||
use dioxus_core::prelude::*;
|
use dioxus_core::prelude::*;
|
||||||
use dioxus_web::WebsysRenderer;
|
use dioxus_web::WebsysRenderer;
|
||||||
|
|
||||||
|
@ -10,12 +11,24 @@ fn main() {
|
||||||
}
|
}
|
||||||
|
|
||||||
static Example: FC<()> = |ctx| {
|
static Example: FC<()> = |ctx| {
|
||||||
|
let nodes = (0..5).map(|f| {
|
||||||
|
rsx! {
|
||||||
|
li {"{f}"}
|
||||||
|
}
|
||||||
|
});
|
||||||
ctx.render(rsx! {
|
ctx.render(rsx! {
|
||||||
div {
|
div {
|
||||||
span {
|
span {
|
||||||
class: "px-2 py-1 flex w-36 mt-4 items-center text-xs rounded-md font-semibold text-yellow-500 bg-yellow-100"
|
class: "px-2 py-1 flex w-36 mt-4 items-center text-xs rounded-md font-semibold text-yellow-500 bg-yellow-100"
|
||||||
"DUE DATE : 18 JUN"
|
"DUE DATE : 189 JUN"
|
||||||
}
|
}
|
||||||
|
p {
|
||||||
|
"these"
|
||||||
|
"are"
|
||||||
|
"text"
|
||||||
|
"nodes"
|
||||||
|
}
|
||||||
|
{nodes}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
use dioxus_core as dioxus;
|
||||||
use dioxus_web::prelude::*;
|
use dioxus_web::prelude::*;
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
use dioxus_core as dioxus;
|
||||||
use dioxus_core::{events::on::MouseEvent, prelude::*};
|
use dioxus_core::{events::on::MouseEvent, prelude::*};
|
||||||
use dioxus_web::WebsysRenderer;
|
use dioxus_web::WebsysRenderer;
|
||||||
|
|
||||||
|
@ -14,7 +15,7 @@ fn main() {
|
||||||
static Example: FC<()> = |ctx| {
|
static Example: FC<()> = |ctx| {
|
||||||
let (event, set_event) = use_state(&ctx, || None);
|
let (event, set_event) = use_state(&ctx, || None);
|
||||||
|
|
||||||
let handler = move |evt: MouseEvent| {
|
let handler = move |evt| {
|
||||||
set_event(Some(evt));
|
set_event(Some(evt));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -42,17 +43,15 @@ static Example: FC<()> = |ctx| {
|
||||||
})
|
})
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Props)]
|
#[derive(Debug, PartialEq, Props)]
|
||||||
struct ExampleProps {
|
struct ExampleProps {
|
||||||
name: String
|
name: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
static Example2: FC<ExampleProps> = |ctx| {
|
static Example2: FC<ExampleProps> = |ctx| {
|
||||||
ctx.render(rsx!{
|
ctx.render(rsx! {
|
||||||
div {
|
div {
|
||||||
h1 {"hello {ctx.name}"}
|
h1 {"hello {ctx.name}"}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -26,25 +26,22 @@ static App: FC<()> = |ctx| {
|
||||||
id: "username"
|
id: "username"
|
||||||
type: "text"
|
type: "text"
|
||||||
value: "{val}"
|
value: "{val}"
|
||||||
oninput: move |evet| {
|
oninput: move |evet| set_val(evet.value())
|
||||||
log::debug!("Value is {:#?}", evet);
|
|
||||||
set_val(evet.value);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
p { "Val is: {val}" }
|
p { "Val is: {val}" }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
};
|
};
|
||||||
|
|
||||||
static Example: FC<()> = |ctx| {
|
static Example: FC<()> = |ctx| {
|
||||||
ctx.render(rsx! {
|
ctx.render(rsx! {
|
||||||
div { class: "max-w-lg max-w-xs bg-blue-800 shadow-2xl rounded-lg mx-auto text-center py-12 mt-4 rounded-xl"
|
div { class: "max-w-lg max-w-xs bg-blue-800 shadow-2xl rounded-lg mx-auto text-center py-12 mt-4 rounded-xl"
|
||||||
div { class: "container py-5 max-w-md mx-auto"
|
div { class: "container py-5 max-w-md mx-auto"
|
||||||
h1 { class: "text-gray-200 text-center font-extrabold -mt-3 text-3xl",
|
h1 { class: "text-gray-200 text-center font-extrabold -mt-3 text-3xl",
|
||||||
"Text Input Example"
|
"Text Input Example"
|
||||||
}
|
}
|
||||||
UserInput {}
|
UserInput {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -54,19 +51,15 @@ static Example: FC<()> = |ctx| {
|
||||||
static UserInput: FC<()> = |ctx| {
|
static UserInput: FC<()> = |ctx| {
|
||||||
let (val, set_val) = use_state(&ctx, || "asd".to_string());
|
let (val, set_val) = use_state(&ctx, || "asd".to_string());
|
||||||
|
|
||||||
rsx!{ in ctx,
|
rsx! { in ctx,
|
||||||
div { class: "mb-4"
|
div { class: "mb-4"
|
||||||
input { class: "shadow appearance-none rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline"
|
input { class: "shadow appearance-none rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline"
|
||||||
placeholder: "Username"
|
placeholder: "Username"
|
||||||
id: "username"
|
id: "username"
|
||||||
type: "text"
|
type: "text"
|
||||||
oninput: move |evet| {
|
oninput: move |evet| set_val(evet.value())
|
||||||
log::debug!("Value is {:#?}", evet);
|
|
||||||
set_val(evet.value);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
p { "Val is: {val}" }
|
p { "Val is: {val}" }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
|
@ -6,7 +6,7 @@ use dioxus_core::prelude::*;
|
||||||
use dioxus_web::*;
|
use dioxus_web::*;
|
||||||
fn main() {
|
fn main() {
|
||||||
// Setup logging
|
// Setup logging
|
||||||
wasm_logger::init(wasm_logger::Config::new(log::Level::Debug));
|
// wasm_logger::init(wasm_logger::Config::new(log::Level::Debug));
|
||||||
console_error_panic_hook::set_once();
|
console_error_panic_hook::set_once();
|
||||||
// Run the app
|
// Run the app
|
||||||
wasm_bindgen_futures::spawn_local(WebsysRenderer::start(App));
|
wasm_bindgen_futures::spawn_local(WebsysRenderer::start(App));
|
||||||
|
|
|
@ -53,7 +53,7 @@ static App: FC<()> = |ctx| {
|
||||||
input {
|
input {
|
||||||
class: "new-todo"
|
class: "new-todo"
|
||||||
placeholder: "What needs to be done?"
|
placeholder: "What needs to be done?"
|
||||||
oninput: move |evt| set_draft(evt.value)
|
oninput: move |evt| set_draft(evt.value())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
use dioxus_core as dioxus;
|
||||||
use dioxus_core::prelude::*;
|
use dioxus_core::prelude::*;
|
||||||
use dioxus_web::WebsysRenderer;
|
use dioxus_web::WebsysRenderer;
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,13 @@
|
||||||
|
use dioxus_core as dioxus;
|
||||||
|
use dioxus_core::prelude::*;
|
||||||
|
|
||||||
|
#[derive(Props)]
|
||||||
|
struct MyProps<'a> {
|
||||||
|
blah: u128,
|
||||||
|
b: &'a (),
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
// let p = unsafe { MyProps {}.memoize(&MyProps {}) };
|
||||||
|
// dbg!(p);
|
||||||
|
}
|
|
@ -1,14 +1,18 @@
|
||||||
#![allow(non_snake_case)]
|
#![allow(non_snake_case)]
|
||||||
use dioxus_core as dioxus;
|
use std::rc::Rc;
|
||||||
|
|
||||||
use dioxus::{events::on::MouseEvent, prelude::*};
|
use dioxus::{events::on::MouseEvent, prelude::*};
|
||||||
|
use dioxus_core as dioxus;
|
||||||
use dioxus_web::WebsysRenderer;
|
use dioxus_web::WebsysRenderer;
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
wasm_logger::init(wasm_logger::Config::new(log::Level::Trace));
|
wasm_logger::init(wasm_logger::Config::new(log::Level::Trace));
|
||||||
console_error_panic_hook::set_once();
|
console_error_panic_hook::set_once();
|
||||||
|
|
||||||
wasm_bindgen_futures::spawn_local(async {
|
wasm_bindgen_futures::spawn_local(async {
|
||||||
let props = ExampleProps { initial_name: "..?"};
|
let props = ExampleProps {
|
||||||
|
initial_name: "..?",
|
||||||
|
};
|
||||||
WebsysRenderer::new_with_props(Example, props)
|
WebsysRenderer::new_with_props(Example, props)
|
||||||
.run()
|
.run()
|
||||||
.await
|
.await
|
||||||
|
@ -25,17 +29,17 @@ static Example: FC<ExampleProps> = |ctx| {
|
||||||
let name = use_state_new(&ctx, move || ctx.initial_name);
|
let name = use_state_new(&ctx, move || ctx.initial_name);
|
||||||
|
|
||||||
ctx.render(rsx! {
|
ctx.render(rsx! {
|
||||||
div {
|
div {
|
||||||
class: "py-12 px-4 text-center w-full max-w-2xl mx-auto"
|
class: "py-12 px-4 text-center w-full max-w-2xl mx-auto"
|
||||||
span {
|
span {
|
||||||
class: "text-sm font-semibold"
|
class: "text-sm font-semibold"
|
||||||
"Dioxus Example: Jack and Jill"
|
"Dioxus Example: Jack and Jill"
|
||||||
}
|
}
|
||||||
h2 {
|
h2 {
|
||||||
class: "text-5xl mt-2 mb-6 leading-tight font-semibold font-heading"
|
class: "text-5xl mt-2 mb-6 leading-tight font-semibold font-heading"
|
||||||
"Hello, {name}"
|
"Hello, {name}"
|
||||||
}
|
}
|
||||||
|
|
||||||
CustomButton { name: "Jack!", handler: move |_| name.set("Jack") }
|
CustomButton { name: "Jack!", handler: move |_| name.set("Jack") }
|
||||||
CustomButton { name: "Jill!", handler: move |_| name.set("Jill") }
|
CustomButton { name: "Jill!", handler: move |_| name.set("Jill") }
|
||||||
CustomButton { name: "Bob!", handler: move |_| name.set("Bob")}
|
CustomButton { name: "Bob!", handler: move |_| name.set("Bob")}
|
||||||
|
@ -45,12 +49,10 @@ static Example: FC<ExampleProps> = |ctx| {
|
||||||
})
|
})
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#[derive(Props)]
|
#[derive(Props)]
|
||||||
struct ButtonProps<'src, F: Fn(MouseEvent)> {
|
struct ButtonProps<'src, F: Fn(Rc<dyn MouseEvent>)> {
|
||||||
name: &'src str,
|
name: &'src str,
|
||||||
handler: F
|
handler: F,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn CustomButton<'a, F: Fn(MouseEvent)>(ctx: Context<'a, ButtonProps<'a, F>>) -> VNode {
|
fn CustomButton<'a, F: Fn(MouseEvent)>(ctx: Context<'a, ButtonProps<'a, F>>) -> VNode {
|
||||||
|
@ -69,13 +71,12 @@ impl<F: Fn(MouseEvent)> PartialEq for ButtonProps<'_, F> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#[derive(Props, PartialEq)]
|
#[derive(Props, PartialEq)]
|
||||||
struct PlaceholderProps {
|
struct PlaceholderProps {
|
||||||
val: &'static str
|
val: &'static str,
|
||||||
}
|
}
|
||||||
fn Placeholder(ctx: Context<PlaceholderProps>) -> VNode {
|
fn Placeholder(ctx: Context<PlaceholderProps>) -> VNode {
|
||||||
ctx.render(rsx!{
|
ctx.render(rsx! {
|
||||||
div {
|
div {
|
||||||
"child: {ctx.val}"
|
"child: {ctx.val}"
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,10 +1,11 @@
|
||||||
|
use dioxus_core as dioxus;
|
||||||
use dioxus_web::{prelude::*, WebsysRenderer};
|
use dioxus_web::{prelude::*, WebsysRenderer};
|
||||||
|
|
||||||
mod filtertoggles;
|
// mod filtertoggles;
|
||||||
mod recoil;
|
// mod recoil;
|
||||||
mod state;
|
// mod state;
|
||||||
mod todoitem;
|
// mod todoitem;
|
||||||
mod todolist;
|
// mod todolist;
|
||||||
|
|
||||||
static APP_STYLE: &'static str = include_str!("./style.css");
|
static APP_STYLE: &'static str = include_str!("./style.css");
|
||||||
|
|
||||||
|
@ -13,10 +14,10 @@ fn main() {
|
||||||
ctx.render(rsx! {
|
ctx.render(rsx! {
|
||||||
div {
|
div {
|
||||||
id: "app"
|
id: "app"
|
||||||
style { "{APP_STYLE}" }
|
// style { "{APP_STYLE}" }
|
||||||
|
|
||||||
// list
|
// list
|
||||||
todolist::TodoList {}
|
// todolist::TodoList {}
|
||||||
|
|
||||||
// footer
|
// footer
|
||||||
footer {
|
footer {
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
use std::{collections::HashMap, rc::Rc};
|
use std::{collections::HashMap, rc::Rc};
|
||||||
|
|
||||||
|
use dioxus_core as dioxus;
|
||||||
use dioxus_core::prelude::*;
|
use dioxus_core::prelude::*;
|
||||||
use dioxus_web::WebsysRenderer;
|
use dioxus_web::WebsysRenderer;
|
||||||
|
|
||||||
|
@ -65,7 +66,7 @@ pub fn TodoList(ctx: Context<()>) -> VNode {
|
||||||
class: "new-todo"
|
class: "new-todo"
|
||||||
placeholder: "What needs to be done?"
|
placeholder: "What needs to be done?"
|
||||||
value: "{draft}"
|
value: "{draft}"
|
||||||
oninput: move |evt| set_draft(evt.value)
|
oninput: move |evt| set_draft(evt.value())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -78,7 +79,7 @@ pub fn TodoList(ctx: Context<()>) -> VNode {
|
||||||
FilterState::Completed => item.checked,
|
FilterState::Completed => item.checked,
|
||||||
})
|
})
|
||||||
.map(|(id, item)| {
|
.map(|(id, item)| {
|
||||||
TodoEntry!();
|
// TodoEntry!();
|
||||||
todo!()
|
todo!()
|
||||||
// rsx!(TodoEntry {
|
// rsx!(TodoEntry {
|
||||||
// key: "{order}",
|
// key: "{order}",
|
||||||
|
@ -100,17 +101,14 @@ pub struct TodoEntryProps {
|
||||||
item: Rc<TodoItem>,
|
item: Rc<TodoItem>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn TodoEntry(ctx: Context, props: &TodoEntryProps) -> VNode {
|
pub fn TodoEntry(ctx: Context<TodoEntryProps>) -> VNode {
|
||||||
// #[inline_props]
|
|
||||||
pub fn TodoEntry(
|
|
||||||
ctx: Context,
|
|
||||||
baller: &impl Fn() -> (),
|
|
||||||
caller: &impl Fn() -> (),
|
|
||||||
todo: &Rc<TodoItem>,
|
|
||||||
) -> VNode {
|
|
||||||
// pub fn TodoEntry(ctx: Context, todo: &Rc<TodoItem>) -> VNode {
|
|
||||||
let (is_editing, set_is_editing) = use_state(&ctx, || false);
|
let (is_editing, set_is_editing) = use_state(&ctx, || false);
|
||||||
// let todo = &ctx.item;
|
let contents = "";
|
||||||
|
let todo = TodoItem {
|
||||||
|
checked: false,
|
||||||
|
contents: "asd".to_string(),
|
||||||
|
id: uuid::Uuid::new_v4(),
|
||||||
|
};
|
||||||
|
|
||||||
ctx.render(rsx! (
|
ctx.render(rsx! (
|
||||||
li {
|
li {
|
||||||
|
|
|
@ -9,7 +9,7 @@
|
||||||
//! Here, we show to use Dioxus' Recoil state management solution to simplify app logic
|
//! Here, we show to use Dioxus' Recoil state management solution to simplify app logic
|
||||||
#![allow(non_snake_case)]
|
#![allow(non_snake_case)]
|
||||||
use dioxus_web::dioxus::prelude::*;
|
use dioxus_web::dioxus::prelude::*;
|
||||||
use recoil::*;
|
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
|
|
|
@ -63,62 +63,70 @@ impl WebsysRenderer {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn run(&mut self) -> dioxus_core::error::Result<()> {
|
pub async fn run(&mut self) -> dioxus_core::error::Result<()> {
|
||||||
let (sender, mut receiver) = async_channel::unbounded::<EventTrigger>();
|
// let (sender, mut receiver) = async_channel::unbounded::<EventTrigger>();
|
||||||
|
|
||||||
let body_element = prepare_websys_dom();
|
let body_element = prepare_websys_dom();
|
||||||
|
|
||||||
let mut patch_machine = interpreter::PatchMachine::new(body_element.clone(), move |ev| {
|
// let mut patch_machine = interpreter::PatchMachine::new(body_element.clone(), move |ev| {
|
||||||
log::debug!("Event trigger! {:#?}", ev);
|
// log::debug!("Event trigger! {:#?}", ev);
|
||||||
let mut c = sender.clone();
|
// let mut c = sender.clone();
|
||||||
wasm_bindgen_futures::spawn_local(async move {
|
// wasm_bindgen_futures::spawn_local(async move {
|
||||||
c.send(ev).await.unwrap();
|
// c.send(ev).await.unwrap();
|
||||||
});
|
// });
|
||||||
});
|
// });
|
||||||
|
|
||||||
let root_node = body_element.first_child().unwrap();
|
let root_node = body_element.first_child().unwrap();
|
||||||
patch_machine.stack.push(root_node.clone());
|
|
||||||
|
let mut websys_dom = crate::new::WebsysDom::new(body_element);
|
||||||
|
|
||||||
|
websys_dom.stack.push(root_node);
|
||||||
|
// patch_machine.stack.push(root_node.clone());
|
||||||
|
|
||||||
// todo: initialize the event registry properly on the root
|
// todo: initialize the event registry properly on the root
|
||||||
|
|
||||||
let edits = self.internal_dom.rebuild()?;
|
self.internal_dom.rebuild(&mut websys_dom)?;
|
||||||
log::debug!("Received edits: {:#?}", edits);
|
// let edits = self.internal_dom.rebuild()?;
|
||||||
edits.iter().for_each(|edit| {
|
// log::debug!("Received edits: {:#?}", edits);
|
||||||
log::debug!("patching with {:?}", edit);
|
// edits.iter().for_each(|edit| {
|
||||||
patch_machine.handle_edit(edit);
|
// log::debug!("patching with {:?}", edit);
|
||||||
});
|
// patch_machine.handle_edit(edit);
|
||||||
|
// });
|
||||||
|
|
||||||
patch_machine.reset();
|
// patch_machine.reset();
|
||||||
let root_node = body_element.first_child().unwrap();
|
// let root_node = body_element.first_child().unwrap();
|
||||||
patch_machine.stack.push(root_node.clone());
|
// patch_machine.stack.push(root_node.clone());
|
||||||
|
|
||||||
// log::debug!("patch stack size {:?}", patch_machine.stack);
|
// log::debug!("patch stack size {:?}", patch_machine.stack);
|
||||||
|
|
||||||
// Event loop waits for the receiver to finish up
|
// Event loop waits for the receiver to finish up
|
||||||
// TODO! Connect the sender to the virtual dom's suspense system
|
// TODO! Connect the sender to the virtual dom's suspense system
|
||||||
// Suspense is basically an external event that can force renders to specific nodes
|
// Suspense is basically an external event that can force renders to specific nodes
|
||||||
while let Ok(event) = receiver.recv().await {
|
// while let Ok(event) = receiver.recv().await {
|
||||||
log::debug!("Stack before entrance {:#?}", patch_machine.stack.top());
|
// log::debug!("Stack before entrance {:#?}", patch_machine.stack.top());
|
||||||
// log::debug!("patch stack size before {:#?}", patch_machine.stack);
|
// log::debug!("patch stack size before {:#?}", patch_machine.stack);
|
||||||
// patch_machine.reset();
|
// patch_machine.reset();
|
||||||
// patch_machine.stack.push(root_node.clone());
|
// patch_machine.stack.push(root_node.clone());
|
||||||
let edits = self.internal_dom.progress_with_event(event)?;
|
// self.internal_dom
|
||||||
log::debug!("Received edits: {:#?}", edits);
|
// .progress_with_event(&mut websys_dom, event)?;
|
||||||
|
// let edits = self.internal_dom.progress_with_event(event)?;
|
||||||
|
// log::debug!("Received edits: {:#?}", edits);
|
||||||
|
|
||||||
for edit in &edits {
|
// for edit in &edits {
|
||||||
// log::debug!("edit stream {:?}", edit);
|
// // log::debug!("edit stream {:?}", edit);
|
||||||
// log::debug!("Stream stack {:#?}", patch_machine.stack.top());
|
// // log::debug!("Stream stack {:#?}", patch_machine.stack.top());
|
||||||
patch_machine.handle_edit(edit);
|
// patch_machine.handle_edit(edit);
|
||||||
}
|
// }
|
||||||
|
|
||||||
// log::debug!("patch stack size after {:#?}", patch_machine.stack);
|
// log::debug!("patch stack size after {:#?}", patch_machine.stack);
|
||||||
patch_machine.reset();
|
// patch_machine.reset();
|
||||||
// our root node reference gets invalidated
|
// our root node reference gets invalidated
|
||||||
// not sure why
|
// not sure why
|
||||||
// for now, just select the first child again.
|
// for now, just select the first child again.
|
||||||
// eventually, we'll just make our own root element instead of using body
|
// eventually, we'll just make our own root element instead of using body
|
||||||
// or just use body directly IDEK
|
// or just use body directly IDEK
|
||||||
let root_node = body_element.first_child().unwrap();
|
// let root_node = body_element.first_child().unwrap();
|
||||||
patch_machine.stack.push(root_node.clone());
|
// patch_machine.stack.push(root_node.clone());
|
||||||
}
|
// }
|
||||||
|
|
||||||
Ok(()) // should actually never return from this, should be an error, rustc just cant see it
|
Ok(()) // should actually never return from this, should be an error, rustc just cant see it
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,2 +1,275 @@
|
||||||
pub struct WebsysDom {}
|
use std::collections::HashMap;
|
||||||
impl WebsysDom {}
|
|
||||||
|
use dioxus_core::virtual_dom::RealDomNode;
|
||||||
|
use nohash_hasher::IntMap;
|
||||||
|
use wasm_bindgen::{closure::Closure, JsCast};
|
||||||
|
use web_sys::{
|
||||||
|
window, Document, Element, Event, HtmlElement, HtmlInputElement, HtmlOptionElement, Node,
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::interpreter::Stack;
|
||||||
|
pub struct WebsysDom {
|
||||||
|
pub stack: Stack,
|
||||||
|
nodes: IntMap<u32, Node>,
|
||||||
|
document: Document,
|
||||||
|
root: Element,
|
||||||
|
|
||||||
|
// We need to make sure to add comments between text nodes
|
||||||
|
// We ensure that the text siblings are patched by preventing the browser from merging
|
||||||
|
// neighboring text nodes. Originally inspired by some of React's work from 2016.
|
||||||
|
// -> https://reactjs.org/blog/2016/04/07/react-v15.html#major-changes
|
||||||
|
// -> https://github.com/facebook/react/pull/5753
|
||||||
|
//
|
||||||
|
// `ptns` = Percy text node separator
|
||||||
|
// TODO
|
||||||
|
last_node_was_text: bool,
|
||||||
|
|
||||||
|
node_counter: Counter,
|
||||||
|
}
|
||||||
|
impl WebsysDom {
|
||||||
|
pub fn new(root: Element) -> Self {
|
||||||
|
let document = window()
|
||||||
|
.expect("must have access to the window")
|
||||||
|
.document()
|
||||||
|
.expect("must have access to the Document");
|
||||||
|
|
||||||
|
Self {
|
||||||
|
stack: Stack::with_capacity(10),
|
||||||
|
nodes: HashMap::with_capacity_and_hasher(
|
||||||
|
1000,
|
||||||
|
nohash_hasher::BuildNoHashHasher::default(),
|
||||||
|
),
|
||||||
|
document,
|
||||||
|
root,
|
||||||
|
last_node_was_text: false,
|
||||||
|
node_counter: Counter(0),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Counter(u32);
|
||||||
|
impl Counter {
|
||||||
|
fn next(&mut self) -> u32 {
|
||||||
|
self.0 += 1;
|
||||||
|
self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl dioxus_core::diff::RealDom for WebsysDom {
|
||||||
|
fn push_root(&mut self, root: dioxus_core::virtual_dom::RealDomNode) {
|
||||||
|
let domnode = self.nodes.get(&root.0).expect("Failed to pop know root");
|
||||||
|
self.stack.push(domnode.clone());
|
||||||
|
}
|
||||||
|
|
||||||
|
fn append_child(&mut self) {
|
||||||
|
let child = self.stack.pop();
|
||||||
|
|
||||||
|
if child.dyn_ref::<web_sys::Text>().is_some() {
|
||||||
|
if self.last_node_was_text {
|
||||||
|
let comment_node = self
|
||||||
|
.document
|
||||||
|
.create_comment("dioxus")
|
||||||
|
.dyn_into::<Node>()
|
||||||
|
.unwrap();
|
||||||
|
self.stack.top().append_child(&comment_node).unwrap();
|
||||||
|
}
|
||||||
|
self.last_node_was_text = true;
|
||||||
|
} else {
|
||||||
|
self.last_node_was_text = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.stack.top().append_child(&child).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn replace_with(&mut self) {
|
||||||
|
let new_node = self.stack.pop();
|
||||||
|
let old_node = self.stack.pop();
|
||||||
|
|
||||||
|
if old_node.has_type::<Element>() {
|
||||||
|
old_node
|
||||||
|
.dyn_ref::<Element>()
|
||||||
|
.unwrap()
|
||||||
|
.replace_with_with_node_1(&new_node)
|
||||||
|
.unwrap();
|
||||||
|
} else if old_node.has_type::<web_sys::CharacterData>() {
|
||||||
|
old_node
|
||||||
|
.dyn_ref::<web_sys::CharacterData>()
|
||||||
|
.unwrap()
|
||||||
|
.replace_with_with_node_1(&new_node)
|
||||||
|
.unwrap();
|
||||||
|
} else if old_node.has_type::<web_sys::DocumentType>() {
|
||||||
|
old_node
|
||||||
|
.dyn_ref::<web_sys::DocumentType>()
|
||||||
|
.unwrap()
|
||||||
|
.replace_with_with_node_1(&new_node)
|
||||||
|
.unwrap();
|
||||||
|
} else {
|
||||||
|
panic!("Cannot replace node: {:?}", old_node);
|
||||||
|
}
|
||||||
|
|
||||||
|
// // poc to see if this is a valid solution
|
||||||
|
// if let Some(id) = self.current_known {
|
||||||
|
// // update mapping
|
||||||
|
// self.known_roots.insert(id, new_node.clone());
|
||||||
|
// self.current_known = None;
|
||||||
|
// }
|
||||||
|
|
||||||
|
self.stack.push(new_node);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn remove(&mut self) {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn remove_all_children(&mut self) {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_text_node(&mut self, text: &str) -> dioxus_core::virtual_dom::RealDomNode {
|
||||||
|
let nid = self.node_counter.next();
|
||||||
|
let textnode = self
|
||||||
|
.document
|
||||||
|
.create_text_node(text)
|
||||||
|
.dyn_into::<Node>()
|
||||||
|
.unwrap();
|
||||||
|
self.stack.push(textnode.clone());
|
||||||
|
self.nodes.insert(nid, textnode);
|
||||||
|
|
||||||
|
RealDomNode::new(nid)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_element(&mut self, tag: &str) -> dioxus_core::virtual_dom::RealDomNode {
|
||||||
|
let el = self
|
||||||
|
.document
|
||||||
|
.create_element(tag)
|
||||||
|
.unwrap()
|
||||||
|
.dyn_into::<Node>()
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
self.stack.push(el.clone());
|
||||||
|
let nid = self.node_counter.next();
|
||||||
|
self.nodes.insert(nid, el);
|
||||||
|
RealDomNode::new(nid)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_element_ns(
|
||||||
|
&mut self,
|
||||||
|
tag: &str,
|
||||||
|
namespace: &str,
|
||||||
|
) -> dioxus_core::virtual_dom::RealDomNode {
|
||||||
|
let el = self
|
||||||
|
.document
|
||||||
|
.create_element_ns(Some(namespace), tag)
|
||||||
|
.unwrap()
|
||||||
|
.dyn_into::<Node>()
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
self.stack.push(el.clone());
|
||||||
|
let nid = self.node_counter.next();
|
||||||
|
self.nodes.insert(nid, el);
|
||||||
|
RealDomNode::new(nid)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn new_event_listener(
|
||||||
|
&mut self,
|
||||||
|
event: &str,
|
||||||
|
scope: dioxus_core::prelude::ScopeIdx,
|
||||||
|
id: usize,
|
||||||
|
) {
|
||||||
|
// if let Some(entry) = self.listeners.get_mut(event) {
|
||||||
|
// entry.0 += 1;
|
||||||
|
// } else {
|
||||||
|
// let trigger = self.trigger.clone();
|
||||||
|
// let handler = Closure::wrap(Box::new(move |event: &web_sys::Event| {
|
||||||
|
// log::debug!("Handling event!");
|
||||||
|
|
||||||
|
// let target = event
|
||||||
|
// .target()
|
||||||
|
// .expect("missing target")
|
||||||
|
// .dyn_into::<Element>()
|
||||||
|
// .expect("not a valid element");
|
||||||
|
|
||||||
|
// let typ = event.type_();
|
||||||
|
|
||||||
|
// let gi_id: Option<usize> = target
|
||||||
|
// .get_attribute(&format!("dioxus-giid-{}", typ))
|
||||||
|
// .and_then(|v| v.parse().ok());
|
||||||
|
|
||||||
|
// let gi_gen: Option<u64> = target
|
||||||
|
// .get_attribute(&format!("dioxus-gigen-{}", typ))
|
||||||
|
// .and_then(|v| v.parse().ok());
|
||||||
|
|
||||||
|
// let li_idx: Option<usize> = target
|
||||||
|
// .get_attribute(&format!("dioxus-lidx-{}", typ))
|
||||||
|
// .and_then(|v| v.parse().ok());
|
||||||
|
|
||||||
|
// if let (Some(gi_id), Some(gi_gen), Some(li_idx)) = (gi_id, gi_gen, li_idx) {
|
||||||
|
// // Call the trigger
|
||||||
|
// log::debug!(
|
||||||
|
// "decoded gi_id: {}, gi_gen: {}, li_idx: {}",
|
||||||
|
// gi_id,
|
||||||
|
// gi_gen,
|
||||||
|
// li_idx
|
||||||
|
// );
|
||||||
|
|
||||||
|
// let triggered_scope = ScopeIdx::from_raw_parts(gi_id, gi_gen);
|
||||||
|
// trigger.0.as_ref()(EventTrigger::new(
|
||||||
|
// virtual_event_from_websys_event(event),
|
||||||
|
// triggered_scope,
|
||||||
|
// // scope,
|
||||||
|
// li_idx,
|
||||||
|
// ));
|
||||||
|
// }
|
||||||
|
// }) as Box<dyn FnMut(&Event)>);
|
||||||
|
|
||||||
|
// self.root
|
||||||
|
// .add_event_listener_with_callback(event, (&handler).as_ref().unchecked_ref())
|
||||||
|
// .unwrap();
|
||||||
|
|
||||||
|
// // Increment the listeners
|
||||||
|
// self.listeners.insert(event.into(), (1, handler));
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn remove_event_listener(&mut self, event: &str) {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_text(&mut self, text: &str) {
|
||||||
|
self.stack.top().set_text_content(Some(text))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_attribute(&mut self, name: &str, value: &str, is_namespaced: bool) {
|
||||||
|
if name == "class" {
|
||||||
|
if let Some(el) = self.stack.top().dyn_ref::<Element>() {
|
||||||
|
el.set_class_name(value);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn remove_attribute(&mut self, name: &str) {
|
||||||
|
let node = self.stack.top();
|
||||||
|
if let Some(node) = node.dyn_ref::<web_sys::Element>() {
|
||||||
|
node.remove_attribute(name).unwrap();
|
||||||
|
}
|
||||||
|
if let Some(node) = node.dyn_ref::<HtmlInputElement>() {
|
||||||
|
// Some attributes are "volatile" and don't work through `removeAttribute`.
|
||||||
|
if name == "value" {
|
||||||
|
node.set_value("");
|
||||||
|
}
|
||||||
|
if name == "checked" {
|
||||||
|
node.set_checked(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(node) = node.dyn_ref::<HtmlOptionElement>() {
|
||||||
|
if name == "selected" {
|
||||||
|
node.set_selected(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn raw_node_as_any_mut(&self) -> &mut dyn std::any::Any {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue