mirror of
https://github.com/DioxusLabs/dioxus
synced 2024-11-10 14:44:12 +00:00
wip: enable more diffing
This commit is contained in:
parent
b5e5ef171a
commit
e8f29a8f8a
26 changed files with 1326 additions and 367 deletions
|
@ -60,7 +60,7 @@ members = [
|
|||
"packages/html-namespace",
|
||||
"packages/web",
|
||||
# "packages/webview"
|
||||
# "packages/cli",
|
||||
"packages/cli",
|
||||
# "packages/atoms",
|
||||
# "packages/ssr",
|
||||
# "packages/docsite",
|
||||
|
|
136
examples/assets/calculator.css
Normal file
136
examples/assets/calculator.css
Normal file
|
@ -0,0 +1,136 @@
|
|||
html {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
*, *:before, *:after {
|
||||
box-sizing: inherit;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
font: 100 14px 'Roboto';
|
||||
}
|
||||
|
||||
button {
|
||||
display: block;
|
||||
background: none;
|
||||
border: none;
|
||||
padding: 0;
|
||||
font-family: inherit;
|
||||
user-select: none;
|
||||
cursor: pointer;
|
||||
outline: none;
|
||||
|
||||
-webkit-tap-highlight-color: rgba(0,0,0,0);
|
||||
}
|
||||
|
||||
button:active {
|
||||
box-shadow: inset 0px 0px 80px 0px rgba(0,0,0,0.25);
|
||||
}
|
||||
|
||||
#wrapper {
|
||||
height: 100vh;
|
||||
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
#app {
|
||||
width: 320px;
|
||||
height: 520px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.calculator {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: black;
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
#wrapper .calculator {
|
||||
box-shadow: 0px 0px 20px 0px #aaa;
|
||||
}
|
||||
|
||||
.calculator-display {
|
||||
color: white;
|
||||
background: #1c191c;
|
||||
line-height: 130px;
|
||||
font-size: 6em;
|
||||
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.auto-scaling-text {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.calculator-display .auto-scaling-text {
|
||||
padding: 0 30px;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
transform-origin: right;
|
||||
}
|
||||
|
||||
.calculator-keypad {
|
||||
height: 400px;
|
||||
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.calculator .input-keys {
|
||||
width: 240px;
|
||||
}
|
||||
|
||||
.calculator .function-keys {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.calculator .digit-keys {
|
||||
background: #e0e0e7;
|
||||
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap-reverse;
|
||||
}
|
||||
|
||||
.calculator-key {
|
||||
width: 80px;
|
||||
height: 80px;
|
||||
border-top: 1px solid #777;
|
||||
border-right: 1px solid #666;
|
||||
text-align: center;
|
||||
line-height: 80px;
|
||||
}
|
||||
.calculator .function-keys .calculator-key {
|
||||
font-size: 2em;
|
||||
}
|
||||
.calculator .function-keys .key-multiply {
|
||||
line-height: 50px;
|
||||
}
|
||||
.calculator .digit-keys .calculator-key {
|
||||
font-size: 2.25em;
|
||||
}
|
||||
.calculator .digit-keys .key-0 {
|
||||
width: 160px;
|
||||
text-align: left;
|
||||
padding-left: 32px;
|
||||
}
|
||||
.calculator .digit-keys .key-dot {
|
||||
padding-top: 1em;
|
||||
font-size: 0.75em;
|
||||
}
|
||||
.calculator .operator-keys .calculator-key {
|
||||
color: white;
|
||||
border-right: 0;
|
||||
font-size: 3em;
|
||||
}
|
||||
|
||||
.calculator .function-keys {
|
||||
background: linear-gradient(to bottom, rgba(202,202,204,1) 0%, rgba(196,194,204,1) 100%);
|
||||
}
|
||||
.calculator .operator-keys {
|
||||
background: linear-gradient(to bottom, rgba(252,156,23,1) 0%, rgba(247,126,27,1) 100%);
|
||||
}
|
|
@ -98,16 +98,16 @@ static App: FC<()> = |cx| {
|
|||
CalculatorKey { name: "key-0", onclick: move |_| input_digit(0), "0" }
|
||||
CalculatorKey { name: "key-dot", onclick: move |_| input_dot(), "●" }
|
||||
|
||||
{(1..9).map(|k| rsx!{
|
||||
{(1..9).map(move |k| rsx!{
|
||||
CalculatorKey { key: "{k}", name: "key-{k}", onclick: move |_| input_digit(k), "{k}" }
|
||||
})}
|
||||
}
|
||||
div { class: "operator-keys"
|
||||
CalculatorKey { name:"key-divide", onclick: move |_| operator.set(Some(Operator::Div)) "÷" }
|
||||
CalculatorKey { name:"key-multiply", onclick: move |_| operator.set(Some(Operator::Mul)) "×" }
|
||||
CalculatorKey { name:"key-subtract", onclick: move |_| operator.set(Some(Operator::Sub)) "−" }
|
||||
CalculatorKey { name:"key-add", onclick: move |_| operator.set(Some(Operator::Add)) "+" }
|
||||
CalculatorKey { name:"key-equals", onclick: move |_| perform_operation() "=" }
|
||||
CalculatorKey { name: "key-divide", onclick: move |_| operator.set(Some(Operator::Div)) "÷" }
|
||||
CalculatorKey { name: "key-multiply", onclick: move |_| operator.set(Some(Operator::Mul)) "×" }
|
||||
CalculatorKey { name: "key-subtract", onclick: move |_| operator.set(Some(Operator::Sub)) "−" }
|
||||
CalculatorKey { name: "key-add", onclick: move |_| operator.set(Some(Operator::Add)) "+" }
|
||||
CalculatorKey { name: "key-equals", onclick: move |_| perform_operation() "=" }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -116,7 +116,10 @@ static App: FC<()> = |cx| {
|
|||
|
||||
#[derive(Props)]
|
||||
struct CalculatorKeyProps<'a> {
|
||||
/// Name!
|
||||
name: &'static str,
|
||||
|
||||
/// Click!
|
||||
onclick: &'a dyn Fn(MouseEvent),
|
||||
}
|
||||
|
||||
|
|
60
examples/compose.rs
Normal file
60
examples/compose.rs
Normal file
|
@ -0,0 +1,60 @@
|
|||
fn main() {}
|
||||
|
||||
struct Title(String);
|
||||
|
||||
struct Position([f32; 3]);
|
||||
|
||||
struct Velocity([f32; 3]);
|
||||
|
||||
type Batch<T> = fn(&mut T) -> ();
|
||||
|
||||
static Atom: Batch<(Title, Position, Velocity)> = |_| {};
|
||||
|
||||
enum VNode<'a> {
|
||||
El(El<'a>),
|
||||
Text(&'a str),
|
||||
Fragment(&'a [VNode<'a>]),
|
||||
}
|
||||
struct El<'a> {
|
||||
name: &'static str,
|
||||
key: Option<&'a str>,
|
||||
attrs: &'a [(&'static str, AttrType<'a>)],
|
||||
children: &'a [El<'a>],
|
||||
}
|
||||
enum AttrType<'a> {
|
||||
Numeric(usize),
|
||||
Text(&'a str),
|
||||
}
|
||||
|
||||
fn example() {
|
||||
use AttrType::Numeric;
|
||||
let el = El {
|
||||
name: "div",
|
||||
attrs: &[("type", Numeric(10)), ("type", Numeric(10))],
|
||||
key: None,
|
||||
children: &[],
|
||||
};
|
||||
}
|
||||
|
||||
use dioxus::prelude::bumpalo::Bump;
|
||||
trait IntoVnode {
|
||||
fn into_vnode<'a>(self, b: &'a Bump) -> VNode<'a>;
|
||||
}
|
||||
|
||||
impl<'a> IntoIterator for VNode<'a> {
|
||||
type Item = VNode<'a>;
|
||||
type IntoIter = std::iter::Once<VNode<'a>>;
|
||||
|
||||
#[inline]
|
||||
fn into_iter(self) -> Self::IntoIter {
|
||||
std::iter::once(self)
|
||||
}
|
||||
}
|
||||
|
||||
fn take_iterable<F: IntoVnode>(f: impl IntoIterator<Item = F>) {
|
||||
let iter = f.into_iter();
|
||||
let b = Bump::new();
|
||||
for f in iter {
|
||||
let v = f.into_vnode(&b);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
use dioxus::prelude::*;
|
||||
fn main() {}
|
||||
|
||||
static Example: FC<()> = |cx| {
|
||||
let (g, set_g) = use_state(&cx, || 0);
|
||||
let v = (0..10).map(move |f| {
|
||||
rsx!(li {
|
||||
onclick: move |_| set_g(10)
|
||||
})
|
||||
});
|
||||
cx.render(rsx! {
|
||||
div {
|
||||
{v}
|
||||
}
|
||||
})
|
||||
};
|
|
@ -4,51 +4,54 @@
|
|||
//! Some components benefit through the use of "Models". Models are a single block of encapsulated state that allow mutative
|
||||
//! methods to be performed on them. Dioxus exposes the ability to use the model pattern through the "use_model" hook.
|
||||
//!
|
||||
//! Models are commonly used in the "Model-View-Component" approach for building UI state.
|
||||
//!
|
||||
//! `use_model` is basically just a fancy wrapper around set_state, but saves a "working copy" of the new state behind a
|
||||
//! RefCell. To modify the working copy, you need to call "modify" which returns the RefMut. This makes it easy to write
|
||||
//! RefCell. To modify the working copy, you need to call "get_mut" which returns the RefMut. This makes it easy to write
|
||||
//! fully encapsulated apps that retain a certain feel of native Rusty-ness. A calculator app is a good example of when this
|
||||
//! is useful.
|
||||
//!
|
||||
//! Do note that "get_mut" returns a `RefMut` (a lock over a RefCell). If two `RefMut`s are held at the same time (ie in a loop)
|
||||
//! the RefCell will panic and crash. You can use `try_get_mut` or `.modify` to avoid this problem, or just not hold two
|
||||
//! RefMuts at the same time.
|
||||
|
||||
use dioxus::events::on::*;
|
||||
use dioxus::prelude::*;
|
||||
use std::{
|
||||
cell::RefCell,
|
||||
ops::{Deref, DerefMut},
|
||||
};
|
||||
|
||||
fn main() {
|
||||
dioxus::webview::launch(App)
|
||||
}
|
||||
|
||||
static App: FC<()> = |cx| {
|
||||
let state = use_model(&cx, || CalculatorState::new());
|
||||
let calc = use_model(&cx, || Calculator::new());
|
||||
|
||||
let clear_display = state.display_value.eq("0");
|
||||
let clear_display = calc.display_value.eq("0");
|
||||
let clear_text = if clear_display { "C" } else { "AC" };
|
||||
let formatted = state.formatted();
|
||||
let formatted = calc.formatted();
|
||||
|
||||
cx.render(rsx! {
|
||||
div { class: "calculator", onkeydown: move |evt| state.modify().handle_keydown(evt),
|
||||
div { class: "calculator-display", "{formatted}"}
|
||||
div { class: "calculator", onkeydown: move |evt| calc.get_mut().handle_keydown(evt),
|
||||
div { class: "calculator-display","{formatted}"}
|
||||
div { class: "input-keys"
|
||||
div { class: "function-keys"
|
||||
CalculatorKey { name: "key-clear", onclick: move |_| state.modify().clear_display() "{clear_text}" }
|
||||
CalculatorKey { name: "key-sign", onclick: move |_| state.modify().toggle_sign(), "±"}
|
||||
CalculatorKey { name: "key-percent", onclick: move |_| state.modify().toggle_percent() "%"}
|
||||
// All there ways that the calculator may be modified:
|
||||
CalculatorKey { name: "key-clear", onclick: move |_| calc.get_mut().clear_display() "{clear_text}" }
|
||||
CalculatorKey { name: "key-sign", onclick: move |_| calc.modify(Calculator::toggle_sign), "±"}
|
||||
CalculatorKey { name: "key-percent", onclick: move |_| calc.modify(|f| f.toggle_percent()) "%"}
|
||||
}
|
||||
div { class: "digit-keys"
|
||||
CalculatorKey { name: "key-0", onclick: move |_| state.modify().input_digit(0), "0" }
|
||||
CalculatorKey { name: "key-dot", onclick: move |_| state.modify().input_dot(), "●" }
|
||||
{(1..9).map(|k| rsx!{
|
||||
CalculatorKey { key: "{k}", name: "key-{k}", onclick: move |_| state.modify().input_digit(k), "{k}" }
|
||||
CalculatorKey { name: "key-0", onclick: move |_| calc.get_mut().input_digit(0), "0" }
|
||||
CalculatorKey { name: "key-dot", onclick: move |_| calc.get_mut().input_dot(), "●" }
|
||||
{(1..9).map(move |k| rsx!{
|
||||
CalculatorKey { key: "{k}", name: "key-{k}", onclick: move |_| calc.get_mut().input_digit(k), "{k}" }
|
||||
})}
|
||||
}
|
||||
div { class: "operator-keys"
|
||||
CalculatorKey { name:"key-divide", onclick: move |_| state.modify().set_operator(Operator::Div) "÷" }
|
||||
CalculatorKey { name:"key-multiply", onclick: move |_| state.modify().set_operator(Operator::Mul) "×" }
|
||||
CalculatorKey { name:"key-subtract", onclick: move |_| state.modify().set_operator(Operator::Sub) "−" }
|
||||
CalculatorKey { name:"key-add", onclick: move |_| state.modify().set_operator(Operator::Add) "+" }
|
||||
CalculatorKey { name:"key-equals", onclick: move |_| state.modify().perform_operation() "=" }
|
||||
CalculatorKey { name:"key-divide", onclick: move |_| calc.get_mut().set_operator(Operator::Div) "÷" }
|
||||
CalculatorKey { name:"key-multiply", onclick: move |_| calc.get_mut().set_operator(Operator::Mul) "×" }
|
||||
CalculatorKey { name:"key-subtract", onclick: move |_| calc.get_mut().set_operator(Operator::Sub) "−" }
|
||||
CalculatorKey { name:"key-add", onclick: move |_| calc.get_mut().set_operator(Operator::Add) "+" }
|
||||
CalculatorKey { name:"key-equals", onclick: move |_| calc.get_mut().perform_operation() "=" }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -56,7 +59,7 @@ static App: FC<()> = |cx| {
|
|||
};
|
||||
|
||||
#[derive(Clone)]
|
||||
struct CalculatorState {
|
||||
struct Calculator {
|
||||
display_value: String,
|
||||
operator: Option<Operator>,
|
||||
cur_val: f64,
|
||||
|
@ -68,9 +71,10 @@ enum Operator {
|
|||
Mul,
|
||||
Div,
|
||||
}
|
||||
impl CalculatorState {
|
||||
|
||||
impl Calculator {
|
||||
fn new() -> Self {
|
||||
CalculatorState {
|
||||
Calculator {
|
||||
display_value: "0".to_string(),
|
||||
operator: None,
|
||||
cur_val: 0.0,
|
||||
|
@ -118,7 +122,9 @@ impl CalculatorState {
|
|||
self.display_value.pop();
|
||||
}
|
||||
}
|
||||
fn set_operator(&mut self, operator: Operator) {}
|
||||
fn set_operator(&mut self, operator: Operator) {
|
||||
self.operator = Some(operator)
|
||||
}
|
||||
fn handle_keydown(&mut self, evt: KeyboardEvent) {
|
||||
match evt.key_code() {
|
||||
KeyCode::Backspace => self.backspace(),
|
||||
|
@ -160,6 +166,10 @@ fn CalculatorKey<'a, 'r>(cx: Context<'a, CalculatorKeyProps<'r>>) -> VNode<'a> {
|
|||
use odl::use_model;
|
||||
mod odl {
|
||||
use super::*;
|
||||
use std::{
|
||||
cell::RefCell,
|
||||
ops::{Deref, DerefMut},
|
||||
};
|
||||
/// Use model makes it easy to use "models" as state for components. To modify the model, call "modify" and a clone of the
|
||||
/// current model will be made, with a RefMut lock on it. Dioxus will never run your components multithreaded, so you can
|
||||
/// be relatively sure that this won't fail in practice
|
||||
|
@ -208,9 +218,14 @@ mod odl {
|
|||
wip: RefCell::new(t),
|
||||
}
|
||||
}
|
||||
pub fn modify(&self) -> RefMut<'_, T> {
|
||||
pub fn get_mut(&self) -> RefMut<'_, T> {
|
||||
self.wip.borrow_mut()
|
||||
}
|
||||
pub fn modify(&self, f: impl FnOnce(&mut T)) {
|
||||
let mut g = self.get_mut();
|
||||
let r = g.deref_mut();
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
|
|
|
@ -2,9 +2,17 @@ use dioxus::prelude::*;
|
|||
fn main() {}
|
||||
|
||||
static Example: FC<()> = |cx| {
|
||||
let (g, set_g) = use_state(&cx, || 0);
|
||||
let v = (0..10).map(|f| {
|
||||
rsx! {
|
||||
li {
|
||||
onclick: move |_| set_g(10)
|
||||
}
|
||||
}
|
||||
});
|
||||
cx.render(rsx! {
|
||||
div {
|
||||
|
||||
{v}
|
||||
}
|
||||
})
|
||||
};
|
||||
|
|
7
examples/reference/main.rs
Normal file
7
examples/reference/main.rs
Normal file
|
@ -0,0 +1,7 @@
|
|||
// mod iterator;
|
||||
// mod memo;
|
||||
mod iterators;
|
||||
mod listener;
|
||||
mod memo;
|
||||
|
||||
fn main() {}
|
|
@ -1,2 +0,0 @@
|
|||
mod iterator;
|
||||
mod memo;
|
|
@ -70,15 +70,20 @@ pub fn build(config: &Config, _build_config: &BuildConfig) -> Result<()> {
|
|||
// [3] Bindgen the final binary for use easy linking
|
||||
let mut bindgen_builder = Bindgen::new();
|
||||
|
||||
let release_type = match config.release {
|
||||
true => "release",
|
||||
false => "debug",
|
||||
};
|
||||
|
||||
let input_path = match executable {
|
||||
ExecutableType::Binary(name) | ExecutableType::Lib(name) => target_dir
|
||||
// .join("wasm32-unknown-unknown/release")
|
||||
.join("wasm32-unknown-unknown/debug")
|
||||
.join(format!("wasm32-unknown-unknown/{}", release_type))
|
||||
.join(format!("{}.wasm", name)),
|
||||
|
||||
ExecutableType::Example(name) => target_dir
|
||||
// .join("wasm32-unknown-unknown/release/examples")
|
||||
.join("wasm32-unknown-unknown/debug/examples")
|
||||
.join(format!("wasm32-unknown-unknown/{}/examples", release_type))
|
||||
.join(format!("{}.wasm", name)),
|
||||
};
|
||||
|
||||
|
|
|
@ -68,19 +68,43 @@ impl ToTokens for Element {
|
|||
let name = &self.name.to_string();
|
||||
|
||||
tokens.append_all(quote! {
|
||||
dioxus::builder::ElementBuilder::new(__cx, #name)
|
||||
__cx.element(#name)
|
||||
});
|
||||
// dioxus::builder::ElementBuilder::new(__cx, #name)
|
||||
// dioxus::builder::ElementBuilder::new(__cx, #name)
|
||||
|
||||
// Add attributes
|
||||
// TODO: conver to the "attrs" syntax for compile-time known sizes
|
||||
for attr in self.attrs.iter() {
|
||||
attr.to_tokens(tokens);
|
||||
}
|
||||
|
||||
let mut children = self.children.iter();
|
||||
while let Some(child) = children.next() {
|
||||
let inner_toks = child.to_token_stream();
|
||||
// let mut children = self.children.iter();
|
||||
// while let Some(child) = children.next() {
|
||||
// let inner_toks = child.to_token_stream();
|
||||
// tokens.append_all(quote! {
|
||||
// .iter_child(#inner_toks)
|
||||
// })
|
||||
// }
|
||||
|
||||
let mut childs = quote! {};
|
||||
for child in &self.children {
|
||||
match child {
|
||||
Node::Text(e) => e.to_tokens(&mut childs),
|
||||
Node::Element(e) => e.to_tokens(&mut childs),
|
||||
Node::RawExpr(e) => quote! {
|
||||
__cx.fragment_from_iter(#e)
|
||||
}
|
||||
.to_tokens(&mut childs),
|
||||
}
|
||||
childs.append_all(quote! {,})
|
||||
}
|
||||
if self.children.len() > 0 {
|
||||
tokens.append_all(quote! {
|
||||
.iter_child(#inner_toks)
|
||||
})
|
||||
.children([
|
||||
#childs
|
||||
])
|
||||
});
|
||||
}
|
||||
|
||||
tokens.append_all(quote! {
|
||||
|
|
|
@ -99,14 +99,14 @@ impl ToTokens for RsxRender {
|
|||
// The `in cx` pattern allows directly rendering
|
||||
Some(ident) => out_tokens.append_all(quote! {
|
||||
#ident.render(dioxus::prelude::LazyNodes::new(move |__cx|{
|
||||
let bump = &__cx.bump();
|
||||
// let bump = &__cx.bump();
|
||||
#inner
|
||||
}))
|
||||
}),
|
||||
// Otherwise we just build the LazyNode wrapper
|
||||
None => out_tokens.append_all(quote! {
|
||||
dioxus::prelude::LazyNodes::new(move |__cx: &NodeFactory|{
|
||||
let bump = &__cx.bump();
|
||||
// let bump = &__cx.bump();
|
||||
#inner
|
||||
})
|
||||
}),
|
||||
|
|
|
@ -61,13 +61,7 @@ impl ToTokens for TextNode {
|
|||
// todo: use heuristics to see if we can promote to &static str
|
||||
let token_stream = &self.0.to_token_stream();
|
||||
tokens.append_all(quote! {
|
||||
{
|
||||
// use bumpalo::core_alloc::fmt::Write;
|
||||
// let mut s = bumpalo::collections::String::new_in(bump);
|
||||
// s.write_fmt(format_args_f!(#token_stream)).unwrap();
|
||||
dioxus::builder::text3(bump, format_args_f!(#token_stream))
|
||||
// dioxus::builder::text2(s)
|
||||
}
|
||||
__cx.text(format_args_f!(#token_stream))
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -105,7 +105,7 @@ static HTML_TAGS: Lazy<HashSet<&'static str>> = Lazy::new(|| {
|
|||
"source",
|
||||
"span",
|
||||
"strong",
|
||||
// "style",
|
||||
"style",
|
||||
"sub",
|
||||
"summary",
|
||||
"sup",
|
||||
|
|
|
@ -135,197 +135,357 @@ impl<'real, 'bump, Dom: RealDom<'bump>> DiffMachine<'real, 'bump, Dom> {
|
|||
Then, we diff and queue an edit event (via chagelist). s single trees - when components show up, we save that traversal and then re-enter later.
|
||||
When re-entering, we reuse the EditList in DiffState
|
||||
*/
|
||||
match old_node {
|
||||
VNode::Element(old) => match new_node {
|
||||
// New node is an element, old node was en element, need to investiage more deeply
|
||||
VNode::Element(new) => {
|
||||
// If the element type is completely different, the element needs to be re-rendered completely
|
||||
// This is an optimization React makes due to how users structure their code
|
||||
if new.tag_name != old.tag_name || new.namespace != old.namespace {
|
||||
self.create(new_node);
|
||||
self.dom.replace_with();
|
||||
return;
|
||||
}
|
||||
new.dom_id.set(old.dom_id.get());
|
||||
|
||||
self.diff_listeners(old.listeners, new.listeners);
|
||||
self.diff_attr(old.attributes, new.attributes, new.namespace);
|
||||
self.diff_children(old.children, new.children);
|
||||
}
|
||||
// New node is a text element, need to replace the element with a simple text node
|
||||
VNode::Text(_) => {
|
||||
self.create(new_node);
|
||||
self.dom.replace_with();
|
||||
}
|
||||
|
||||
// New node is a component
|
||||
// Make the component and replace our element on the stack with it
|
||||
VNode::Component(_) => {
|
||||
self.create(new_node);
|
||||
self.dom.replace_with();
|
||||
}
|
||||
|
||||
// New node is actually a sequence of nodes.
|
||||
// We need to replace this one node with a sequence of nodes
|
||||
// Not yet implement because it's kinda hairy
|
||||
VNode::Fragment(new) => {
|
||||
match new.children.len() {
|
||||
0 => {
|
||||
// remove
|
||||
}
|
||||
1 => {
|
||||
// replace
|
||||
self.create(&new.children[0]);
|
||||
self.dom.replace_with();
|
||||
}
|
||||
_ => {
|
||||
todo!()
|
||||
// remove and mount many
|
||||
// self.remove_self_and_next_siblings(old)
|
||||
//
|
||||
// let iter = ChildIterator::new(new_node, self.components).rev();
|
||||
// for child in iter {}
|
||||
// for child in new.children.iter().skip(1).rev() {
|
||||
// self.dom.remove();
|
||||
// }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// New Node is actually suspended. Todo
|
||||
VNode::Suspended { real } => todo!(),
|
||||
},
|
||||
|
||||
// Old element was text
|
||||
VNode::Text(old) => match new_node {
|
||||
VNode::Text(new) => {
|
||||
if old.text != new.text {
|
||||
log::debug!("Text has changed {}, {}", old.text, new.text);
|
||||
self.dom.set_text(new.text);
|
||||
}
|
||||
new.dom_id.set(old.dom_id.get());
|
||||
}
|
||||
VNode::Element(_) | VNode::Component(_) => {
|
||||
self.create(new_node);
|
||||
self.dom.replace_with();
|
||||
}
|
||||
|
||||
// TODO on handling these types
|
||||
VNode::Fragment(frag) => {
|
||||
if frag.children.len() == 0 {
|
||||
// do nothing
|
||||
} else {
|
||||
self.create(&frag.children[0]);
|
||||
self.dom.replace_with();
|
||||
for child in frag.children.iter().skip(1) {
|
||||
self.create(child);
|
||||
self.dom.append_child();
|
||||
}
|
||||
}
|
||||
}
|
||||
VNode::Suspended { real } => todo!(),
|
||||
},
|
||||
|
||||
// Old element was a component
|
||||
VNode::Component(old) => {
|
||||
match new_node {
|
||||
// It's something entirely different
|
||||
VNode::Element(_) | VNode::Text(_) => {
|
||||
self.create(new_node);
|
||||
self.dom.replace_with();
|
||||
}
|
||||
|
||||
// It's also a component
|
||||
VNode::Component(new) => {
|
||||
if old.user_fc == new.user_fc {
|
||||
// Make sure we're dealing with the same component (by function pointer)
|
||||
|
||||
// Make sure the new component vnode is referencing the right scope id
|
||||
let scope_id = old.ass_scope.get();
|
||||
new.ass_scope.set(scope_id);
|
||||
new.mounted_root.set(old.mounted_root.get());
|
||||
|
||||
// make sure the component's caller function is up to date
|
||||
self.components
|
||||
.with_scope(scope_id.unwrap(), |scope| {
|
||||
scope.caller = Rc::downgrade(&new.caller)
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
// React doesn't automatically memoize, but we do.
|
||||
// The cost is low enough to make it worth checking
|
||||
// Rust produces fairly performant comparison methods, sometimes SIMD accelerated
|
||||
let should_render = match old.comparator {
|
||||
Some(comparator) => comparator(new),
|
||||
None => true,
|
||||
};
|
||||
|
||||
if should_render {
|
||||
// // self.dom.commit_traversal();
|
||||
self.components
|
||||
.with_scope(scope_id.unwrap(), |f| {
|
||||
f.run_scope().unwrap();
|
||||
})
|
||||
.unwrap();
|
||||
// diff_machine.change_list.load_known_root(root_id);
|
||||
// run the scope
|
||||
//
|
||||
} else {
|
||||
// Component has memoized itself and doesn't need to be re-rendered.
|
||||
// We still need to make sure the child's props are up-to-date.
|
||||
// Don't commit traversal
|
||||
}
|
||||
} else {
|
||||
// It's an entirely different component
|
||||
|
||||
// A new component has shown up! We need to destroy the old node
|
||||
|
||||
// Wipe the old one and plant the new one
|
||||
// self.dom.commit_traversal();
|
||||
// self.dom.replace_node_with(old.dom_id, new.dom_id);
|
||||
// self.create(new_node);
|
||||
// self.dom.replace_with();
|
||||
self.create(new_node);
|
||||
// self.create_and_repalce(new_node, old.mounted_root.get());
|
||||
|
||||
// Now we need to remove the old scope and all of its descendents
|
||||
let old_scope = old.ass_scope.get().unwrap();
|
||||
self.destroy_scopes(old_scope);
|
||||
}
|
||||
}
|
||||
VNode::Fragment(_) => todo!(),
|
||||
VNode::Suspended { real } => todo!(),
|
||||
log::debug!("diffing...");
|
||||
match (old_node, new_node) {
|
||||
// Handle the "sane" cases first.
|
||||
// The rsx and html macros strongly discourage dynamic lists not encapsulated by a "Fragment".
|
||||
// So the sane (and fast!) cases are where the virtual structure stays the same and is easily diffable.
|
||||
(VNode::Text(old), VNode::Text(new)) => {
|
||||
if old.text != new.text {
|
||||
self.dom.push_root(old.dom_id.get());
|
||||
log::debug!("Text has changed {}, {}", old.text, new.text);
|
||||
self.dom.set_text(new.text);
|
||||
}
|
||||
new.dom_id.set(old.dom_id.get());
|
||||
}
|
||||
|
||||
VNode::Fragment(old) => {
|
||||
(VNode::Element(old), VNode::Element(new)) => {
|
||||
// If the element type is completely different, the element needs to be re-rendered completely
|
||||
// This is an optimization React makes due to how users structure their code
|
||||
//
|
||||
match new_node {
|
||||
VNode::Fragment(new) => todo!(),
|
||||
// In Dioxus, this is less likely to occur unless through a fragment
|
||||
if new.tag_name != old.tag_name || new.namespace != old.namespace {
|
||||
log::debug!("things have changed....?");
|
||||
self.dom.push_root(old.dom_id.get());
|
||||
self.create(new_node);
|
||||
self.dom.replace_with();
|
||||
return;
|
||||
}
|
||||
log::debug!("Important Things haven't changed!");
|
||||
new.dom_id.set(old.dom_id.get());
|
||||
|
||||
// going from fragment to element means we're going from many (or potentially none) to one
|
||||
VNode::Element(new) => {}
|
||||
VNode::Text(_) => todo!(),
|
||||
VNode::Suspended { real } => todo!(),
|
||||
VNode::Component(_) => todo!(),
|
||||
self.diff_listeners(old.listeners, new.listeners);
|
||||
self.diff_attr(old.attributes, new.attributes, new.namespace);
|
||||
self.diff_children(old.children, new.children);
|
||||
}
|
||||
|
||||
(VNode::Component(old), VNode::Component(new)) => {
|
||||
log::warn!("diffing components? {:#?}", new.user_fc);
|
||||
if old.user_fc == new.user_fc {
|
||||
// Make sure we're dealing with the same component (by function pointer)
|
||||
|
||||
// Make sure the new component vnode is referencing the right scope id
|
||||
let scope_id = old.ass_scope.get();
|
||||
new.ass_scope.set(scope_id);
|
||||
|
||||
// new.mounted_root.set(old.mounted_root.get());
|
||||
|
||||
// make sure the component's caller function is up to date
|
||||
let scope = self.components.try_get_mut(scope_id.unwrap()).unwrap();
|
||||
// .with_scope(scope_id.unwrap(), |scope| {
|
||||
scope.caller = Rc::downgrade(&new.caller);
|
||||
scope.update_children(new.children);
|
||||
// })
|
||||
// .unwrap();
|
||||
|
||||
// React doesn't automatically memoize, but we do.
|
||||
// The cost is low enough to make it worth checking
|
||||
// Rust produces fairly performant comparison methods, sometimes SIMD accelerated
|
||||
// let should_render = match old.comparator {
|
||||
// Some(comparator) => comparator(new),
|
||||
// None => true,
|
||||
// };
|
||||
|
||||
// if should_render {
|
||||
// // self.dom.commit_traversal();
|
||||
// let f = self.components.try_get_mut(scope_id.unwrap()).unwrap();
|
||||
// self.components
|
||||
// .with_scope(scope_id.unwrap(), |f| {
|
||||
log::debug!("running scope during diff {:#?}", scope_id);
|
||||
scope.run_scope().unwrap();
|
||||
self.diff_node(scope.old_frame(), scope.next_frame());
|
||||
log::debug!("scope completed {:#?}", scope_id);
|
||||
self.seen_nodes.insert(scope_id.unwrap());
|
||||
// })
|
||||
// .unwrap();
|
||||
|
||||
// diff_machine.change_list.load_known_root(root_id);
|
||||
// run the scope
|
||||
//
|
||||
// } else {
|
||||
// log::error!("Memoized componented");
|
||||
// // Component has memoized itself and doesn't need to be re-rendered.
|
||||
// // We still need to make sure the child's props are up-to-date.
|
||||
// // Don't commit traversal
|
||||
// }
|
||||
} else {
|
||||
// It's an entirely different component
|
||||
|
||||
// A new component has shown up! We need to destroy the old node
|
||||
|
||||
// Wipe the old one and plant the new one
|
||||
// self.dom.commit_traversal();
|
||||
// self.dom.replace_node_with(old.dom_id, new.dom_id);
|
||||
// self.create(new_node);
|
||||
log::warn!("creating and replacing...");
|
||||
self.create(new_node);
|
||||
|
||||
// self.dom.replace_with();
|
||||
// self.create_and_repalce(new_node, old.mounted_root.get());
|
||||
|
||||
// Now we need to remove the old scope and all of its descendents
|
||||
let old_scope = old.ass_scope.get().unwrap();
|
||||
self.destroy_scopes(old_scope);
|
||||
}
|
||||
}
|
||||
|
||||
// a suspended node will perform a mem-copy of the previous elements until it is ready
|
||||
// this means that event listeners will need to be disabled and removed
|
||||
// it also means that props will need to disabled - IE if the node "came out of hibernation" any props should be considered outdated
|
||||
VNode::Suspended { real: old_real } => {
|
||||
(VNode::Fragment(old), VNode::Fragment(new)) => {
|
||||
log::debug!("diffing as fragment");
|
||||
// This is the case where options or direct vnodes might be used.
|
||||
// In this case, it's faster to just skip ahead to their diff
|
||||
if old.children.len() == 1 && new.children.len() == 1 {
|
||||
self.diff_node(old.children.get(0).unwrap(), new.children.get(0).unwrap());
|
||||
return;
|
||||
}
|
||||
|
||||
// Diff using the approach where we're looking for added or removed nodes.
|
||||
if old.children.len() != new.children.len() {}
|
||||
|
||||
// Diff where we think the elements are the same
|
||||
if old.children.len() == new.children.len() {}
|
||||
|
||||
self.diff_children(old.children, new.children);
|
||||
// todo!()
|
||||
}
|
||||
|
||||
// Okay - these are the "insane" cases where the structure is entirely different.
|
||||
// The factory and rsx! APIs don't really produce structures like this, so we don't take any too complicated
|
||||
// code paths.
|
||||
|
||||
// in the case where the old node was a fragment but the new nodes are text,
|
||||
(VNode::Fragment(_) | VNode::Component(_), VNode::Element(_) | VNode::Text(_)) => {
|
||||
// find the first real element int the old node
|
||||
let mut iter = RealChildIterator::new(old_node, self.components);
|
||||
if let Some(first) = iter.next() {
|
||||
// replace the very first node with the creation of the element or text
|
||||
} else {
|
||||
// there are no real elements in the old fragment...
|
||||
// We need to load up the next real
|
||||
}
|
||||
if let VNode::Component(old) = old_node {
|
||||
// schedule this component's destructor to be run
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
// In the case where real nodes are being replaced by potentially
|
||||
(VNode::Element(_) | VNode::Text(_), VNode::Fragment(new)) => {
|
||||
//
|
||||
match new_node {
|
||||
VNode::Suspended { real: new_real } => {
|
||||
//
|
||||
}
|
||||
VNode::Element(_) => todo!(),
|
||||
VNode::Text(_) => todo!(),
|
||||
VNode::Fragment(_) => todo!(),
|
||||
VNode::Component(_) => todo!(),
|
||||
}
|
||||
}
|
||||
(VNode::Text(_), VNode::Element(_)) => {
|
||||
self.create(new_node);
|
||||
self.dom.replace_with();
|
||||
}
|
||||
(VNode::Element(_), VNode::Text(_)) => {
|
||||
self.create(new_node);
|
||||
self.dom.replace_with();
|
||||
}
|
||||
|
||||
_ => {
|
||||
//
|
||||
} /*
|
||||
|
||||
*/
|
||||
//
|
||||
// VNode::Element(old) => match new_node {
|
||||
// // New node is an element, old node was en element, need to investiage more deeply
|
||||
// VNode::Element(new) => {
|
||||
// // If the element type is completely different, the element needs to be re-rendered completely
|
||||
// // This is an optimization React makes due to how users structure their code
|
||||
// if new.tag_name != old.tag_name || new.namespace != old.namespace {
|
||||
// self.create(new_node);
|
||||
// self.dom.replace_with();
|
||||
// return;
|
||||
// }
|
||||
// new.dom_id.set(old.dom_id.get());
|
||||
|
||||
// self.diff_listeners(old.listeners, new.listeners);
|
||||
// self.diff_attr(old.attributes, new.attributes, new.namespace);
|
||||
// self.diff_children(old.children, new.children);
|
||||
// }
|
||||
// // New node is a text element, need to replace the element with a simple text node
|
||||
// VNode::Text(_) => {
|
||||
// self.create(new_node);
|
||||
// self.dom.replace_with();
|
||||
// }
|
||||
|
||||
// // New node is a component
|
||||
// // Make the component and replace our element on the stack with it
|
||||
// VNode::Component(_) => {
|
||||
// self.create(new_node);
|
||||
// self.dom.replace_with();
|
||||
// }
|
||||
|
||||
// // New node is actually a sequence of nodes.
|
||||
// // We need to replace this one node with a sequence of nodes
|
||||
// // Not yet implement because it's kinda hairy
|
||||
// VNode::Fragment(new) => {
|
||||
// match new.children.len() {
|
||||
// 0 => {
|
||||
// // remove
|
||||
// }
|
||||
// 1 => {
|
||||
// // replace
|
||||
// self.create(&new.children[0]);
|
||||
// self.dom.replace_with();
|
||||
// }
|
||||
// _ => {
|
||||
// todo!()
|
||||
// // remove and mount many
|
||||
// // self.remove_self_and_next_siblings(old)
|
||||
// //
|
||||
// // let iter = ChildIterator::new(new_node, self.components).rev();
|
||||
// // for child in iter {}
|
||||
// // for child in new.children.iter().skip(1).rev() {
|
||||
// // self.dom.remove();
|
||||
// // }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
// // New Node is actually suspended. Todo
|
||||
// VNode::Suspended { real } => todo!(),
|
||||
// },
|
||||
|
||||
// // Old element was text
|
||||
// VNode::Text(old) => match new_node {
|
||||
// VNode::Text(new) => {
|
||||
// if old.text != new.text {
|
||||
// log::debug!("Text has changed {}, {}", old.text, new.text);
|
||||
// self.dom.set_text(new.text);
|
||||
// }
|
||||
// new.dom_id.set(old.dom_id.get());
|
||||
// }
|
||||
// VNode::Element(_) | VNode::Component(_) => {
|
||||
// self.create(new_node);
|
||||
// self.dom.replace_with();
|
||||
// }
|
||||
|
||||
// // TODO on handling these types
|
||||
// VNode::Fragment(frag) => {
|
||||
// if frag.children.len() == 0 {
|
||||
// // do nothing
|
||||
// } else {
|
||||
// self.create(&frag.children[0]);
|
||||
// self.dom.replace_with();
|
||||
// for child in frag.children.iter().skip(1) {
|
||||
// self.create(child);
|
||||
// self.dom.append_child();
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// VNode::Suspended { real } => todo!(),
|
||||
// },
|
||||
|
||||
// // Old element was a component
|
||||
// VNode::Component(old) => {
|
||||
// match new_node {
|
||||
// // It's something entirely different
|
||||
// VNode::Element(_) | VNode::Text(_) => {
|
||||
// self.create(new_node);
|
||||
// self.dom.replace_with();
|
||||
// }
|
||||
|
||||
// // It's also a component
|
||||
// VNode::Component(new) => {
|
||||
// if old.user_fc == new.user_fc {
|
||||
// // Make sure we're dealing with the same component (by function pointer)
|
||||
|
||||
// // Make sure the new component vnode is referencing the right scope id
|
||||
// let scope_id = old.ass_scope.get();
|
||||
// new.ass_scope.set(scope_id);
|
||||
// new.mounted_root.set(old.mounted_root.get());
|
||||
|
||||
// // make sure the component's caller function is up to date
|
||||
// self.components
|
||||
// .with_scope(scope_id.unwrap(), |scope| {
|
||||
// scope.caller = Rc::downgrade(&new.caller)
|
||||
// })
|
||||
// .unwrap();
|
||||
|
||||
// // React doesn't automatically memoize, but we do.
|
||||
// // The cost is low enough to make it worth checking
|
||||
// // Rust produces fairly performant comparison methods, sometimes SIMD accelerated
|
||||
// let should_render = match old.comparator {
|
||||
// Some(comparator) => comparator(new),
|
||||
// None => true,
|
||||
// };
|
||||
|
||||
// if should_render {
|
||||
// // // self.dom.commit_traversal();
|
||||
// self.components
|
||||
// .with_scope(scope_id.unwrap(), |f| {
|
||||
// f.run_scope().unwrap();
|
||||
// })
|
||||
// .unwrap();
|
||||
// // diff_machine.change_list.load_known_root(root_id);
|
||||
// // run the scope
|
||||
// //
|
||||
// } else {
|
||||
// // Component has memoized itself and doesn't need to be re-rendered.
|
||||
// // We still need to make sure the child's props are up-to-date.
|
||||
// // Don't commit traversal
|
||||
// }
|
||||
// } else {
|
||||
// // It's an entirely different component
|
||||
|
||||
// // A new component has shown up! We need to destroy the old node
|
||||
|
||||
// // Wipe the old one and plant the new one
|
||||
// // self.dom.commit_traversal();
|
||||
// // self.dom.replace_node_with(old.dom_id, new.dom_id);
|
||||
// // self.create(new_node);
|
||||
// // self.dom.replace_with();
|
||||
// self.create(new_node);
|
||||
// // self.create_and_repalce(new_node, old.mounted_root.get());
|
||||
|
||||
// // Now we need to remove the old scope and all of its descendents
|
||||
// let old_scope = old.ass_scope.get().unwrap();
|
||||
// self.destroy_scopes(old_scope);
|
||||
// }
|
||||
// }
|
||||
// VNode::Fragment(_) => todo!(),
|
||||
// VNode::Suspended { real } => todo!(),
|
||||
// }
|
||||
// }
|
||||
|
||||
// VNode::Fragment(old) => {
|
||||
// //
|
||||
// match new_node {
|
||||
// VNode::Fragment(new) => todo!(),
|
||||
|
||||
// // going from fragment to element means we're going from many (or potentially none) to one
|
||||
// VNode::Element(new) => {}
|
||||
// VNode::Text(_) => todo!(),
|
||||
// VNode::Suspended { real } => todo!(),
|
||||
// VNode::Component(_) => todo!(),
|
||||
// }
|
||||
// }
|
||||
|
||||
// // a suspended node will perform a mem-copy of the previous elements until it is ready
|
||||
// // this means that event listeners will need to be disabled and removed
|
||||
// // it also means that props will need to disabled - IE if the node "came out of hibernation" any props should be considered outdated
|
||||
// VNode::Suspended { real: old_real } => {
|
||||
// //
|
||||
// match new_node {
|
||||
// VNode::Suspended { real: new_real } => {
|
||||
// //
|
||||
// }
|
||||
// VNode::Element(_) => todo!(),
|
||||
// VNode::Text(_) => todo!(),
|
||||
// VNode::Fragment(_) => todo!(),
|
||||
// VNode::Component(_) => todo!(),
|
||||
// }
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -340,6 +500,7 @@ impl<'real, 'bump, Dom: RealDom<'bump>> DiffMachine<'real, 'bump, Dom> {
|
|||
// [... node]
|
||||
fn create(&mut self, node: &'bump VNode<'bump>) {
|
||||
// debug_assert!(self.dom.traversal_is_committed());
|
||||
log::warn!("Creating node!");
|
||||
match node {
|
||||
VNode::Text(text) => {
|
||||
let real_id = self.dom.create_text_node(text.text);
|
||||
|
@ -402,7 +563,7 @@ impl<'real, 'bump, Dom: RealDom<'bump>> DiffMachine<'real, 'bump, Dom> {
|
|||
}
|
||||
|
||||
VNode::Component(component) => {
|
||||
let real_id = self.dom.create_placeholder();
|
||||
// let real_id = self.dom.create_placeholder();
|
||||
|
||||
// let root_id = next_id();
|
||||
// self.dom.save_known_root(root_id);
|
||||
|
@ -455,6 +616,7 @@ impl<'real, 'bump, Dom: RealDom<'bump>> DiffMachine<'real, 'bump, Dom> {
|
|||
new_component.run_scope().unwrap();
|
||||
|
||||
// And then run the diff algorithm
|
||||
let _real_id = self.dom.create_placeholder();
|
||||
self.diff_node(new_component.old_frame(), new_component.next_frame());
|
||||
|
||||
// Finally, insert this node as a seen node.
|
||||
|
@ -675,11 +837,14 @@ impl<'a, 'bump, Dom: RealDom<'bump>> DiffMachine<'a, 'bump, Dom> {
|
|||
);
|
||||
|
||||
if new_is_keyed && old_is_keyed {
|
||||
todo!("Not yet implemented a migration away from temporaries");
|
||||
log::warn!("using the wrong approach");
|
||||
self.diff_non_keyed_children(old, new);
|
||||
// todo!("Not yet implemented a migration away from temporaries");
|
||||
// let t = self.dom.next_temporary();
|
||||
// self.diff_keyed_children(old, new);
|
||||
// self.dom.set_next_temporary(t);
|
||||
} else {
|
||||
log::debug!("diffing non keyed children");
|
||||
self.diff_non_keyed_children(old, new);
|
||||
}
|
||||
}
|
||||
|
@ -1120,24 +1285,24 @@ impl<'a, 'bump, Dom: RealDom<'bump>> DiffMachine<'a, 'bump, Dom> {
|
|||
// self.dom.go_to_sibling(i);
|
||||
// [... parent this_child]
|
||||
|
||||
let did = old_child.get_mounted_id(self.components).unwrap();
|
||||
if did.0 == 0 {
|
||||
log::debug!("Root is bad: {:#?}", old_child);
|
||||
}
|
||||
self.dom.push_root(did);
|
||||
// let did = old_child.get_mounted_id(self.components).unwrap();
|
||||
// if did.0 == 0 {
|
||||
// log::debug!("Root is bad: {:#?}", old_child);
|
||||
// }
|
||||
// self.dom.push_root(did);
|
||||
self.diff_node(old_child, new_child);
|
||||
|
||||
let old_id = old_child.get_mounted_id(self.components).unwrap();
|
||||
let new_id = new_child.get_mounted_id(self.components).unwrap();
|
||||
// let old_id = old_child.get_mounted_id(self.components).unwrap();
|
||||
// let new_id = new_child.get_mounted_id(self.components).unwrap();
|
||||
|
||||
log::debug!(
|
||||
"pushed root. {:?}, {:?}",
|
||||
old_child.get_mounted_id(self.components).unwrap(),
|
||||
new_child.get_mounted_id(self.components).unwrap()
|
||||
);
|
||||
if old_id != new_id {
|
||||
log::debug!("Mismatch: {:?}", new_child);
|
||||
}
|
||||
// log::debug!(
|
||||
// "pushed root. {:?}, {:?}",
|
||||
// old_child.get_mounted_id(self.components).unwrap(),
|
||||
// new_child.get_mounted_id(self.components).unwrap()
|
||||
// );
|
||||
// if old_id != new_id {
|
||||
// log::debug!("Mismatch: {:?}", new_child);
|
||||
// }
|
||||
}
|
||||
|
||||
// match old.len().cmp(&new.len()) {
|
||||
|
@ -1256,7 +1421,11 @@ enum KeyedPrefixResult {
|
|||
MoreWorkToDo(usize),
|
||||
}
|
||||
|
||||
struct ChildIterator<'a> {
|
||||
/// This iterator iterates through a list of virtual children and only returns real children (Elements or Text).
|
||||
///
|
||||
/// This iterator is useful when it's important to load the next real root onto the top of the stack for operations like
|
||||
/// "InsertBefore".
|
||||
struct RealChildIterator<'a> {
|
||||
scopes: &'a ScopeArena,
|
||||
|
||||
// Heuristcally we should never bleed into 5 completely nested fragments/components
|
||||
|
@ -1264,7 +1433,7 @@ struct ChildIterator<'a> {
|
|||
stack: smallvec::SmallVec<[(u16, &'a VNode<'a>); 5]>,
|
||||
}
|
||||
|
||||
impl<'a> ChildIterator<'a> {
|
||||
impl<'a> RealChildIterator<'a> {
|
||||
fn new(starter: &'a VNode<'a>, scopes: &'a ScopeArena) -> Self {
|
||||
Self {
|
||||
scopes,
|
||||
|
@ -1279,7 +1448,7 @@ impl<'a> ChildIterator<'a> {
|
|||
// }
|
||||
// }
|
||||
|
||||
impl<'a> Iterator for ChildIterator<'a> {
|
||||
impl<'a> Iterator for RealChildIterator<'a> {
|
||||
type Item = &'a VNode<'a>;
|
||||
|
||||
fn next(&mut self) -> Option<&'a VNode<'a>> {
|
||||
|
@ -1402,7 +1571,7 @@ mod tests {
|
|||
let mut renderer = DebugDom::new();
|
||||
dom.rebuild(&mut renderer).unwrap();
|
||||
let starter = dom.base_scope().root();
|
||||
let ite = ChildIterator::new(starter, &dom.components);
|
||||
let ite = RealChildIterator::new(starter, &dom.components);
|
||||
for child in ite {
|
||||
match child {
|
||||
VNode::Element(el) => println!("Found: Element {}", el.tag_name),
|
||||
|
|
|
@ -10,14 +10,62 @@ use crate::{innerlude::ScopeIdx, virtual_dom::RealDomNode};
|
|||
|
||||
#[derive(Debug)]
|
||||
pub struct EventTrigger {
|
||||
///
|
||||
pub component_id: ScopeIdx,
|
||||
|
||||
///
|
||||
pub real_node_id: RealDomNode,
|
||||
|
||||
///
|
||||
pub event: VirtualEvent,
|
||||
|
||||
///
|
||||
pub priority: EventPriority,
|
||||
}
|
||||
|
||||
/// Priority of Event Triggers.
|
||||
///
|
||||
/// Internally, Dioxus will abort work that's taking too long if new, more important, work arrives. Unlike React, Dioxus
|
||||
/// won't be afraid to pause work or flush changes to the RealDOM. This is called "cooperative scheduling". Some Renderers
|
||||
/// implement this form of scheduling internally, however Dioxus will perform its own scheduling as well.
|
||||
///
|
||||
/// The ultimate goal of the scheduler is to manage latency of changes, prioritizing "flashier" changes over "subtler" changes.
|
||||
#[derive(Debug)]
|
||||
pub enum EventPriority {
|
||||
/// "Immediate" work will interrupt whatever work is currently being done and force its way through. This type of work
|
||||
/// is typically reserved for small changes to single elements.
|
||||
///
|
||||
/// The primary user of the "Immediate" priority is the `Signal` API which performs surgical mutations to the DOM.
|
||||
Immediate,
|
||||
|
||||
/// "High Priority" work will not interrupt other high priority work, but will interrupt long medium and low priority work.
|
||||
///
|
||||
///
|
||||
/// This is typically reserved for things like user interaction.
|
||||
High,
|
||||
|
||||
/// "Medium priority" work is generated by page events not triggered by the user. These types of events are less important
|
||||
/// than "High Priority" events and will take presedence over low priority events.
|
||||
///
|
||||
/// This is typically reserved for VirtualEvents that are not related to keyboard or mouse input.
|
||||
Medium,
|
||||
|
||||
/// "Low Priority" work will always be pre-empted unless the work is significantly delayed, in which case it will be
|
||||
/// advanced to the front of the work queue until completed.
|
||||
///
|
||||
/// The primary user of Low Priority work is the asynchronous work system (suspense).
|
||||
Low,
|
||||
}
|
||||
|
||||
impl EventTrigger {
|
||||
pub fn new(event: VirtualEvent, scope: ScopeIdx, mounted_dom_id: RealDomNode) -> Self {
|
||||
pub fn new(
|
||||
event: VirtualEvent,
|
||||
scope: ScopeIdx,
|
||||
mounted_dom_id: RealDomNode,
|
||||
priority: EventPriority,
|
||||
) -> Self {
|
||||
Self {
|
||||
priority,
|
||||
component_id: scope,
|
||||
real_node_id: mounted_dom_id,
|
||||
event,
|
||||
|
@ -78,7 +126,7 @@ pub mod on {
|
|||
( $( $eventdata:ident($wrapper:ident): [ $( $name:ident )* ]; )* ) => {
|
||||
$(
|
||||
#[derive(Debug)]
|
||||
pub struct $wrapper(Rc<dyn $eventdata>);
|
||||
pub struct $wrapper(pub Rc<dyn $eventdata>);
|
||||
impl Deref for $wrapper {
|
||||
type Target = Rc<dyn $eventdata>;
|
||||
fn deref(&self) -> &Self::Target {
|
||||
|
|
|
@ -9,6 +9,8 @@ use std::{
|
|||
u128,
|
||||
};
|
||||
|
||||
use bumpalo::Bump;
|
||||
|
||||
use crate::{
|
||||
events::VirtualEvent,
|
||||
innerlude::{Properties, VComponent, FC},
|
||||
|
@ -654,8 +656,9 @@ pub fn text3<'a>(bump: &'a bumpalo::Bump, args: std::fmt::Arguments) -> VNode<'a
|
|||
// we can just short-circuit to the &'static str version instead of having to allocate in the bump arena.
|
||||
//
|
||||
// In the most general case, this prevents the need for any string allocations for simple code IE:
|
||||
// div {"abc"}
|
||||
//
|
||||
// ```
|
||||
// div {"abc"}
|
||||
// ```
|
||||
match args.as_str() {
|
||||
Some(static_str) => VNode::text(static_str),
|
||||
None => {
|
||||
|
@ -735,13 +738,14 @@ impl<'a, 'b> ChildrenList<'a, 'b> {
|
|||
}
|
||||
}
|
||||
|
||||
// NodeFactory is used to build VNodes in the component's memory space.
|
||||
// This struct adds metadata to the final VNode about listeners, attributes, and children
|
||||
/// This struct provides an ergonomic API to quickly build VNodes.
|
||||
///
|
||||
/// NodeFactory is used to build VNodes in the component's memory space.
|
||||
/// This struct adds metadata to the final VNode about listeners, attributes, and children
|
||||
#[derive(Clone)]
|
||||
pub struct NodeFactory<'a> {
|
||||
pub scope_ref: &'a Scope,
|
||||
pub listener_id: Cell<usize>,
|
||||
// pub listener_id: RefCell<usize>,
|
||||
}
|
||||
|
||||
impl<'a> NodeFactory<'a> {
|
||||
|
@ -756,7 +760,7 @@ impl<'a> NodeFactory<'a> {
|
|||
}
|
||||
|
||||
/// Create an element builder
|
||||
pub fn element<'b, Listeners, Attributes, Children>(
|
||||
pub fn element<'b>(
|
||||
&'b self,
|
||||
tag_name: &'static str,
|
||||
) -> ElementBuilder<
|
||||
|
@ -787,6 +791,20 @@ impl<'a> NodeFactory<'a> {
|
|||
key: NodeKey::new_opt(key),
|
||||
}))
|
||||
}
|
||||
|
||||
pub fn fragment_from_iter(
|
||||
&self,
|
||||
node_iter: impl IntoIterator<Item = impl IntoVNode<'a>>,
|
||||
) -> VNode<'a> {
|
||||
let mut nodes = bumpalo::collections::Vec::new_in(self.bump());
|
||||
for node in node_iter.into_iter() {
|
||||
nodes.push(node.into_vnode(&self));
|
||||
}
|
||||
VNode::Fragment(
|
||||
self.bump()
|
||||
.alloc(VFragment::new(None, nodes.into_bump_slice())),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
use std::fmt::Debug;
|
||||
|
|
|
@ -29,7 +29,11 @@ pub enum VNode<'src> {
|
|||
/// A text node (node type `TEXT_NODE`).
|
||||
Text(VText<'src>),
|
||||
|
||||
/// A fragment is a "virtual position" in the DOM
|
||||
/// A fragment is a list of elements that might have a dynamic order.
|
||||
/// Normally, children will have a fixed order. However, Fragments allow a dynamic order and must be diffed differently.
|
||||
///
|
||||
/// Fragments don't have a single mount into the dom, so their position is characterized by the head and tail nodes.
|
||||
///
|
||||
/// Fragments may have children and keys
|
||||
Fragment(&'src VFragment<'src>),
|
||||
|
||||
|
|
|
@ -487,6 +487,14 @@ impl Scope {
|
|||
self.caller = broken_caller;
|
||||
}
|
||||
|
||||
pub fn update_children<'creator_node>(
|
||||
&mut self,
|
||||
child_nodes: &'creator_node [VNode<'creator_node>],
|
||||
) {
|
||||
let child_nodes = unsafe { std::mem::transmute(child_nodes) };
|
||||
self.child_nodes = child_nodes;
|
||||
}
|
||||
|
||||
/// Create a new context and run the component with references from the Virtual Dom
|
||||
/// This function downcasts the function pointer based on the stored props_type
|
||||
///
|
||||
|
@ -597,9 +605,9 @@ impl Scope {
|
|||
///
|
||||
/// }
|
||||
///
|
||||
/// fn example(cx: Context, props: &Props -> VNode {
|
||||
/// fn example(cx: Context<Props>) -> VNode {
|
||||
/// html! {
|
||||
/// <div> "Hello, {cx.cx.name}" </div>
|
||||
/// <div> "Hello, {cx.name}" </div>
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
|
|
|
@ -10,7 +10,7 @@ license = "MIT/Apache-2.0"
|
|||
[dependencies]
|
||||
dioxus-core = { path="../core", version="0.1.2" }
|
||||
js-sys = "0.3"
|
||||
wasm-bindgen = "0.2.71"
|
||||
wasm-bindgen = { version="0.2.71", features=["enable-interning"] }
|
||||
lazy_static = "1.4.0"
|
||||
wasm-bindgen-futures = "0.4.20"
|
||||
wasm-logger = "0.2.0"
|
||||
|
@ -31,6 +31,7 @@ slotmap = "1.0.3"
|
|||
version = "0.3.50"
|
||||
features = [
|
||||
"Comment",
|
||||
"Attr",
|
||||
"Document",
|
||||
"Element",
|
||||
"HtmlElement",
|
||||
|
@ -47,6 +48,7 @@ features = [
|
|||
"MouseEvent",
|
||||
"InputEvent",
|
||||
"ClipboardEvent",
|
||||
"NamedNodeMap",
|
||||
"KeyboardEvent",
|
||||
"TouchEvent",
|
||||
"WheelEvent",
|
||||
|
@ -70,4 +72,5 @@ crate-type = ["cdylib", "rlib"]
|
|||
[dev-dependencies]
|
||||
im-rc = "15.0.0"
|
||||
rand = { version="0.8.4", features=["small_rng"] }
|
||||
separator = "0.4.1"
|
||||
uuid = { version="0.8.2", features=["v4", "wasm-bindgen"] }
|
||||
|
|
|
@ -3,3 +3,9 @@
|
|||
Build interactive user experiences directly in the web browser!
|
||||
|
||||
Dioxus-web provides a `WebsysRenderer` for the Dioxus Virtual Dom that handles events, progresses components, and updates the actual DOM using web-sys methods.
|
||||
|
||||
|
||||
## Web-specific Optimizations
|
||||
- Uses string interning of all common node types
|
||||
- Optimistically interns short strings
|
||||
- Builds trees completely before mounting them
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
//! Basic example that renders a simple VNode to the browser.
|
||||
|
||||
use dioxus::events::on::MouseEvent;
|
||||
use dioxus_core as dioxus;
|
||||
use dioxus_core::prelude::*;
|
||||
use dioxus_web::*;
|
||||
|
@ -14,56 +15,42 @@ fn main() {
|
|||
}
|
||||
|
||||
static App: FC<()> = |cx| {
|
||||
let (state, set_state) = use_state(&cx, || 0);
|
||||
cx.render(rsx! {
|
||||
div {
|
||||
h1 {"hello"}
|
||||
C1 {}
|
||||
C2 {}
|
||||
section { class: "py-12 px-4 text-center"
|
||||
div { class: "w-full max-w-2xl mx-auto"
|
||||
span { class: "text-sm font-semibold"
|
||||
"count: {state}"
|
||||
}
|
||||
div {
|
||||
C1 {
|
||||
onclick: move |_| set_state(state + 1)
|
||||
"incr"
|
||||
}
|
||||
C1 {
|
||||
onclick: move |_| set_state(state - 1)
|
||||
"decr"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
};
|
||||
|
||||
static C1: FC<()> = |cx| {
|
||||
#[derive(Props)]
|
||||
struct IncrementerProps<'a> {
|
||||
onclick: &'a dyn Fn(MouseEvent),
|
||||
}
|
||||
|
||||
fn C1<'a, 'b>(cx: Context<'a, IncrementerProps<'b>>) -> VNode<'a> {
|
||||
cx.render(rsx! {
|
||||
button {
|
||||
"numba 1"
|
||||
class: "inline-block py-4 px-8 mr-6 leading-none text-white bg-indigo-600 hover:bg-indigo-900 font-semibold rounded shadow"
|
||||
onclick: {cx.onclick}
|
||||
"becr"
|
||||
{cx.children()}
|
||||
}
|
||||
})
|
||||
};
|
||||
|
||||
static C2: FC<()> = |cx| {
|
||||
cx.render(rsx! {
|
||||
button {
|
||||
"numba 2"
|
||||
}
|
||||
})
|
||||
};
|
||||
|
||||
static DocExamples: FC<()> = |cx| {
|
||||
//
|
||||
|
||||
let is_ready = false;
|
||||
|
||||
let items = (0..10).map(|i| rsx! { li {"{i}"} });
|
||||
let _ = rsx! {
|
||||
ul {
|
||||
{items}
|
||||
}
|
||||
};
|
||||
|
||||
// rsx! {
|
||||
// div {}
|
||||
// h1 {}
|
||||
// {""}
|
||||
// "asbasd"
|
||||
// dioxus::Fragment {
|
||||
// //
|
||||
// }
|
||||
// }
|
||||
|
||||
cx.render(rsx! {
|
||||
div {
|
||||
{ is_ready.then(|| rsx!{ h1 {"We are ready!"} }) }
|
||||
}
|
||||
})
|
||||
};
|
||||
}
|
||||
|
|
181
packages/web/examples/calculator.rs
Normal file
181
packages/web/examples/calculator.rs
Normal file
|
@ -0,0 +1,181 @@
|
|||
//! Example: Calculator
|
||||
//! -------------------------
|
||||
|
||||
// use dioxus::events::on::*;
|
||||
// use dioxus::prelude::*;
|
||||
|
||||
use dioxus_core as dioxus;
|
||||
use dioxus_web::WebsysRenderer;
|
||||
|
||||
const STYLE: &str = include_str!("../../../examples/assets/calculator.css");
|
||||
|
||||
fn main() {
|
||||
// Setup logging
|
||||
wasm_logger::init(wasm_logger::Config::new(log::Level::Debug));
|
||||
console_error_panic_hook::set_once();
|
||||
|
||||
// Run the app
|
||||
wasm_bindgen_futures::spawn_local(WebsysRenderer::start(App));
|
||||
}
|
||||
|
||||
use dioxus::events::on::*;
|
||||
use dioxus::prelude::*;
|
||||
|
||||
enum Operator {
|
||||
Add,
|
||||
Sub,
|
||||
Mul,
|
||||
Div,
|
||||
}
|
||||
|
||||
static App: FC<()> = |cx| {
|
||||
let (cur_val, set_cur_val) = use_state(&cx, || 0.0_f64);
|
||||
let (operator, set_operator) = use_state(&cx, || None as Option<Operator>);
|
||||
let (display_value, set_display_value) = use_state(&cx, || "".to_string());
|
||||
|
||||
let clear_display = display_value.eq("0");
|
||||
let clear_text = if clear_display { "C" } else { "AC" };
|
||||
|
||||
let input_digit =
|
||||
move |num: u8| {
|
||||
log::warn!("Inputting digit {:#?}", num);
|
||||
let mut new = display_value.clone();
|
||||
new.push_str(num.to_string().as_str());
|
||||
set_display_value(new);
|
||||
};
|
||||
|
||||
let input_dot = move || {
|
||||
let mut new = display_value.clone();
|
||||
new.push_str(".");
|
||||
set_display_value(new);
|
||||
};
|
||||
|
||||
let perform_operation = move || {
|
||||
if let Some(op) = operator.as_ref() {
|
||||
let rhs = display_value.parse::<f64>().unwrap();
|
||||
let new_val = match op {
|
||||
Operator::Add => *cur_val + rhs,
|
||||
Operator::Sub => *cur_val - rhs,
|
||||
Operator::Mul => *cur_val * rhs,
|
||||
Operator::Div => *cur_val / rhs,
|
||||
};
|
||||
set_cur_val(new_val);
|
||||
set_display_value(new_val.to_string());
|
||||
set_operator(None);
|
||||
}
|
||||
};
|
||||
|
||||
let toggle_sign = move |_| {
|
||||
if display_value.starts_with("-") {
|
||||
set_display_value(display_value.trim_start_matches("-").to_string())
|
||||
} else {
|
||||
set_display_value(format!("-{}", *display_value))
|
||||
}
|
||||
};
|
||||
let toggle_percent = move |_| todo!();
|
||||
|
||||
let clear_key = move |_| {
|
||||
set_display_value("0".to_string());
|
||||
if !clear_display {
|
||||
set_operator(None);
|
||||
set_cur_val(0.0);
|
||||
}
|
||||
};
|
||||
|
||||
let keydownhandler = move |evt: KeyboardEvent| match evt.key_code() {
|
||||
KeyCode::Backspace => {
|
||||
//
|
||||
let mut new = display_value.clone();
|
||||
if !new.as_str().eq("0") {
|
||||
new.pop();
|
||||
}
|
||||
set_display_value(new);
|
||||
|
||||
},
|
||||
KeyCode::_0 => input_digit(0),
|
||||
KeyCode::_1 => input_digit(1),
|
||||
KeyCode::_2 => input_digit(2),
|
||||
KeyCode::_3 => input_digit(3),
|
||||
KeyCode::_4 => input_digit(4),
|
||||
KeyCode::_5 => input_digit(5),
|
||||
KeyCode::_6 => input_digit(6),
|
||||
KeyCode::_7 => input_digit(7),
|
||||
KeyCode::_8 => input_digit(8),
|
||||
KeyCode::_9 => input_digit(9),
|
||||
KeyCode::Add => set_operator(Some(Operator::Add)),
|
||||
KeyCode::Subtract => set_operator(Some(Operator::Sub)),
|
||||
KeyCode::Divide => set_operator(Some(Operator::Div)),
|
||||
KeyCode::Multiply => set_operator(Some(Operator::Mul)),
|
||||
_ => {}
|
||||
};
|
||||
|
||||
cx.render(rsx! {
|
||||
div { class: "app"
|
||||
onkeydown: {keydownhandler}
|
||||
style {
|
||||
"{STYLE}"
|
||||
}
|
||||
div { class: "calculator",
|
||||
div { class: "input-keys"
|
||||
CalculatorDisplay { val: *cur_val }
|
||||
div { class: "function-keys"
|
||||
CalculatorKey { name: "key-clear", onclick: {clear_key} "{clear_text}" }
|
||||
CalculatorKey { name: "key-sign", onclick: {toggle_sign}, "±"}
|
||||
CalculatorKey { name: "key-percent", onclick: {toggle_percent} "%"}
|
||||
}
|
||||
div { class: "digit-keys"
|
||||
CalculatorKey { name: "key-0", onclick: move |_| input_digit(0), "0" }
|
||||
CalculatorKey { name: "key-dot", onclick: move |_| input_dot(), "●" }
|
||||
|
||||
{(1..9).map(move |k| rsx!{
|
||||
CalculatorKey { key: "{k}", name: "key-{k}", onclick: move |_| input_digit(k), "{k}" }
|
||||
})}
|
||||
}
|
||||
div { class: "operator-keys"
|
||||
CalculatorKey { name: "key-divide", onclick: move |_| set_operator(Some(Operator::Div)) "÷" }
|
||||
CalculatorKey { name: "key-multiply", onclick: move |_| set_operator(Some(Operator::Mul)) "×" }
|
||||
CalculatorKey { name: "key-subtract", onclick: move |_| set_operator(Some(Operator::Sub)) "−" }
|
||||
CalculatorKey { name: "key-add", onclick: move |_| set_operator(Some(Operator::Add)) "+" }
|
||||
CalculatorKey { name: "key-equals", onclick: move |_| perform_operation() "=" }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
};
|
||||
|
||||
#[derive(Props)]
|
||||
struct CalculatorKeyProps<'a> {
|
||||
/// Name!
|
||||
name: &'static str,
|
||||
|
||||
/// Click!
|
||||
onclick: &'a dyn Fn(MouseEvent),
|
||||
}
|
||||
|
||||
fn CalculatorKey<'a, 'r>(cx: Context<'a, CalculatorKeyProps<'r>>) -> VNode<'a> {
|
||||
cx.render(rsx! {
|
||||
button {
|
||||
class: "calculator-key {cx.name}"
|
||||
onclick: {cx.onclick}
|
||||
{cx.children()}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
#[derive(Props, PartialEq)]
|
||||
struct CalculatorDisplayProps {
|
||||
val: f64,
|
||||
}
|
||||
|
||||
fn CalculatorDisplay(cx: Context<CalculatorDisplayProps>) -> VNode {
|
||||
use separator::Separatable;
|
||||
// Todo, add float support to the num-format crate
|
||||
let formatted = cx.val.separated_string();
|
||||
// TODO: make it autoscaling with css
|
||||
cx.render(rsx! {
|
||||
div { class: "calculator-display"
|
||||
"{formatted}"
|
||||
}
|
||||
})
|
||||
}
|
|
@ -3,15 +3,17 @@ 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_logger::init(wasm_logger::Config::new(log::Level::Debug));
|
||||
// console_error_panic_hook::set_once();
|
||||
|
||||
// log::info!("hello world");
|
||||
dioxus_web::intern_cache();
|
||||
|
||||
log::info!("hello world");
|
||||
wasm_bindgen_futures::spawn_local(WebsysRenderer::start(Example));
|
||||
}
|
||||
|
||||
static Example: FC<()> = |cx| {
|
||||
let nodes = (0..15).map(|f| rsx! (li { key: "{f}", "{f}"}));
|
||||
let nodes = (0..500).map(|f| rsx! (li {"div"}));
|
||||
cx.render(rsx! {
|
||||
div {
|
||||
span {
|
||||
|
|
|
@ -77,49 +77,6 @@ impl WebsysRenderer {
|
|||
.progress_with_event(&mut websys_dom, trigger)?;
|
||||
}
|
||||
|
||||
// let edits = self.internal_dom.rebuild()?;
|
||||
// log::debug!("Received edits: {:#?}", edits);
|
||||
// edits.iter().for_each(|edit| {
|
||||
// log::debug!("patching with {:?}", edit);
|
||||
// patch_machine.handle_edit(edit);
|
||||
// });
|
||||
|
||||
// patch_machine.reset();
|
||||
// let root_node = body_element.first_child().unwrap();
|
||||
// patch_machine.stack.push(root_node.clone());
|
||||
|
||||
// log::debug!("patch stack size {:?}", patch_machine.stack);
|
||||
|
||||
// Event loop waits for the receiver to finish up
|
||||
// TODO! Connect the sender to the virtual dom's suspense system
|
||||
// Suspense is basically an external event that can force renders to specific nodes
|
||||
// while let Ok(event) = receiver.recv().await {
|
||||
// log::debug!("Stack before entrance {:#?}", patch_machine.stack.top());
|
||||
// log::debug!("patch stack size before {:#?}", patch_machine.stack);
|
||||
// patch_machine.reset();
|
||||
// patch_machine.stack.push(root_node.clone());
|
||||
// self.internal_dom
|
||||
// .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 {
|
||||
// // log::debug!("edit stream {:?}", edit);
|
||||
// // log::debug!("Stream stack {:#?}", patch_machine.stack.top());
|
||||
// patch_machine.handle_edit(edit);
|
||||
// }
|
||||
|
||||
// log::debug!("patch stack size after {:#?}", patch_machine.stack);
|
||||
// patch_machine.reset();
|
||||
// our root node reference gets invalidated
|
||||
// not sure why
|
||||
// for now, just select the first child again.
|
||||
// eventually, we'll just make our own root element instead of using body
|
||||
// or just use body directly IDEK
|
||||
// let root_node = body_element.first_child().unwrap();
|
||||
// patch_machine.stack.push(root_node.clone());
|
||||
// }
|
||||
|
||||
Ok(()) // should actually never return from this, should be an error, rustc just cant see it
|
||||
}
|
||||
}
|
||||
|
@ -135,7 +92,7 @@ fn prepare_websys_dom() -> Element {
|
|||
|
||||
// Build a dummy div
|
||||
let container: &Element = body.as_ref();
|
||||
container.set_inner_html("");
|
||||
// container.set_inner_html("");
|
||||
container
|
||||
.append_child(
|
||||
document
|
||||
|
@ -164,6 +121,299 @@ fn prepare_websys_dom() -> Element {
|
|||
// };
|
||||
// }
|
||||
|
||||
/// Wasm-bindgen has a performance option to intern commonly used phrases
|
||||
/// This saves the decoding cost, making the interaction of Rust<->JS more performant.
|
||||
/// We intern all the HTML tags and attributes, making most operations much faster.
|
||||
///
|
||||
/// Interning takes about 1ms at the start of the app, but saves a *ton* of time later on.
|
||||
pub fn intern_cache() {
|
||||
let cached_words = [
|
||||
// All the HTML Tags
|
||||
"a",
|
||||
"abbr",
|
||||
"address",
|
||||
"area",
|
||||
"article",
|
||||
"aside",
|
||||
"audio",
|
||||
"b",
|
||||
"base",
|
||||
"bdi",
|
||||
"bdo",
|
||||
"big",
|
||||
"blockquote",
|
||||
"body",
|
||||
"br",
|
||||
"button",
|
||||
"canvas",
|
||||
"caption",
|
||||
"cite",
|
||||
"code",
|
||||
"col",
|
||||
"colgroup",
|
||||
"command",
|
||||
"data",
|
||||
"datalist",
|
||||
"dd",
|
||||
"del",
|
||||
"details",
|
||||
"dfn",
|
||||
"dialog",
|
||||
"div",
|
||||
"dl",
|
||||
"dt",
|
||||
"em",
|
||||
"embed",
|
||||
"fieldset",
|
||||
"figcaption",
|
||||
"figure",
|
||||
"footer",
|
||||
"form",
|
||||
"h1",
|
||||
"h2",
|
||||
"h3",
|
||||
"h4",
|
||||
"h5",
|
||||
"h6",
|
||||
"head",
|
||||
"header",
|
||||
"hr",
|
||||
"html",
|
||||
"i",
|
||||
"iframe",
|
||||
"img",
|
||||
"input",
|
||||
"ins",
|
||||
"kbd",
|
||||
"keygen",
|
||||
"label",
|
||||
"legend",
|
||||
"li",
|
||||
"link",
|
||||
"main",
|
||||
"map",
|
||||
"mark",
|
||||
"menu",
|
||||
"menuitem",
|
||||
"meta",
|
||||
"meter",
|
||||
"nav",
|
||||
"noscript",
|
||||
"object",
|
||||
"ol",
|
||||
"optgroup",
|
||||
"option",
|
||||
"output",
|
||||
"p",
|
||||
"param",
|
||||
"picture",
|
||||
"pre",
|
||||
"progress",
|
||||
"q",
|
||||
"rp",
|
||||
"rt",
|
||||
"ruby",
|
||||
"s",
|
||||
"samp",
|
||||
"script",
|
||||
"section",
|
||||
"select",
|
||||
"small",
|
||||
"source",
|
||||
"span",
|
||||
"strong",
|
||||
"style",
|
||||
"sub",
|
||||
"summary",
|
||||
"sup",
|
||||
"table",
|
||||
"tbody",
|
||||
"td",
|
||||
"textarea",
|
||||
"tfoot",
|
||||
"th",
|
||||
"thead",
|
||||
"time",
|
||||
"title",
|
||||
"tr",
|
||||
"track",
|
||||
"u",
|
||||
"ul",
|
||||
"var",
|
||||
"video",
|
||||
"wbr",
|
||||
// All the event handlers
|
||||
"Attribute",
|
||||
"accept",
|
||||
"accept-charset",
|
||||
"accesskey",
|
||||
"action",
|
||||
"alt",
|
||||
"async",
|
||||
"autocomplete",
|
||||
"autofocus",
|
||||
"autoplay",
|
||||
"charset",
|
||||
"checked",
|
||||
"cite",
|
||||
"class",
|
||||
"cols",
|
||||
"colspan",
|
||||
"content",
|
||||
"contenteditable",
|
||||
"controls",
|
||||
"coords",
|
||||
"data",
|
||||
"data-*",
|
||||
"datetime",
|
||||
"default",
|
||||
"defer",
|
||||
"dir",
|
||||
"dirname",
|
||||
"disabled",
|
||||
"download",
|
||||
"draggable",
|
||||
"enctype",
|
||||
"for",
|
||||
"form",
|
||||
"formaction",
|
||||
"headers",
|
||||
"height",
|
||||
"hidden",
|
||||
"high",
|
||||
"href",
|
||||
"hreflang",
|
||||
"http-equiv",
|
||||
"id",
|
||||
"ismap",
|
||||
"kind",
|
||||
"label",
|
||||
"lang",
|
||||
"list",
|
||||
"loop",
|
||||
"low",
|
||||
"max",
|
||||
"maxlength",
|
||||
"media",
|
||||
"method",
|
||||
"min",
|
||||
"multiple",
|
||||
"muted",
|
||||
"name",
|
||||
"novalidate",
|
||||
"onabort",
|
||||
"onafterprint",
|
||||
"onbeforeprint",
|
||||
"onbeforeunload",
|
||||
"onblur",
|
||||
"oncanplay",
|
||||
"oncanplaythrough",
|
||||
"onchange",
|
||||
"onclick",
|
||||
"oncontextmenu",
|
||||
"oncopy",
|
||||
"oncuechange",
|
||||
"oncut",
|
||||
"ondblclick",
|
||||
"ondrag",
|
||||
"ondragend",
|
||||
"ondragenter",
|
||||
"ondragleave",
|
||||
"ondragover",
|
||||
"ondragstart",
|
||||
"ondrop",
|
||||
"ondurationchange",
|
||||
"onemptied",
|
||||
"onended",
|
||||
"onerror",
|
||||
"onfocus",
|
||||
"onhashchange",
|
||||
"oninput",
|
||||
"oninvalid",
|
||||
"onkeydown",
|
||||
"onkeypress",
|
||||
"onkeyup",
|
||||
"onload",
|
||||
"onloadeddata",
|
||||
"onloadedmetadata",
|
||||
"onloadstart",
|
||||
"onmousedown",
|
||||
"onmousemove",
|
||||
"onmouseout",
|
||||
"onmouseover",
|
||||
"onmouseup",
|
||||
"onmousewheel",
|
||||
"onoffline",
|
||||
"ononline",
|
||||
"<body>",
|
||||
"onpageshow",
|
||||
"onpaste",
|
||||
"onpause",
|
||||
"onplay",
|
||||
"onplaying",
|
||||
"<body>",
|
||||
"onprogress",
|
||||
"onratechange",
|
||||
"onreset",
|
||||
"onresize",
|
||||
"onscroll",
|
||||
"onsearch",
|
||||
"onseeked",
|
||||
"onseeking",
|
||||
"onselect",
|
||||
"onstalled",
|
||||
"<body>",
|
||||
"onsubmit",
|
||||
"onsuspend",
|
||||
"ontimeupdate",
|
||||
"ontoggle",
|
||||
"onunload",
|
||||
"onvolumechange",
|
||||
"onwaiting",
|
||||
"onwheel",
|
||||
"open",
|
||||
"optimum",
|
||||
"pattern",
|
||||
"placeholder",
|
||||
"poster",
|
||||
"preload",
|
||||
"readonly",
|
||||
"rel",
|
||||
"required",
|
||||
"reversed",
|
||||
"rows",
|
||||
"rowspan",
|
||||
"sandbox",
|
||||
"scope",
|
||||
"selected",
|
||||
"shape",
|
||||
"size",
|
||||
"sizes",
|
||||
"span",
|
||||
"spellcheck",
|
||||
"src",
|
||||
"srcdoc",
|
||||
"srclang",
|
||||
"srcset",
|
||||
"start",
|
||||
"step",
|
||||
"style",
|
||||
"tabindex",
|
||||
"target",
|
||||
"title",
|
||||
"translate",
|
||||
"type",
|
||||
"usemap",
|
||||
"value",
|
||||
"width",
|
||||
"wrap",
|
||||
];
|
||||
|
||||
for s in cached_words {
|
||||
wasm_bindgen::intern(s);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::env;
|
||||
|
|
|
@ -171,6 +171,7 @@ impl<'a> dioxus_core::diff::RealDom<'a> for WebsysDom {
|
|||
tag: &str,
|
||||
ns: Option<&'static str>,
|
||||
) -> dioxus_core::virtual_dom::RealDomNode {
|
||||
let tag = wasm_bindgen::intern(tag);
|
||||
let el = match ns {
|
||||
Some(ns) => self
|
||||
.document
|
||||
|
@ -200,6 +201,7 @@ impl<'a> dioxus_core::diff::RealDom<'a> for WebsysDom {
|
|||
el_id: usize,
|
||||
real_id: RealDomNode,
|
||||
) {
|
||||
let event = wasm_bindgen::intern(event);
|
||||
log::debug!(
|
||||
"Called [`new_event_listener`]: {}, {:?}, {}, {:?}",
|
||||
event,
|
||||
|
@ -237,7 +239,7 @@ impl<'a> dioxus_core::diff::RealDom<'a> for WebsysDom {
|
|||
// Instead, we just build and immediately execute a closure that returns result
|
||||
match decode_trigger(event) {
|
||||
Ok(synthetic_event) => trigger.as_ref()(synthetic_event),
|
||||
Err(_) => log::error!("Error decoding Dioxus event attribute."),
|
||||
Err(e) => log::error!("Error decoding Dioxus event attribute. {:#?}", e),
|
||||
};
|
||||
}) as Box<dyn FnMut(&Event)>);
|
||||
|
||||
|
@ -407,7 +409,7 @@ fn virtual_event_from_websys_event(event: &web_sys::Event) -> VirtualEvent {
|
|||
|
||||
#[derive(Debug)]
|
||||
pub struct CustomMouseEvent(web_sys::MouseEvent);
|
||||
impl dioxus_core::events::on::MouseEvent for CustomMouseEvent {
|
||||
impl dioxus_core::events::on::MouseEventInner for CustomMouseEvent {
|
||||
fn alt_key(&self) -> bool {
|
||||
self.0.alt_key()
|
||||
}
|
||||
|
@ -451,7 +453,7 @@ fn virtual_event_from_websys_event(event: &web_sys::Event) -> VirtualEvent {
|
|||
self.0.get_modifier_state(key_code)
|
||||
}
|
||||
}
|
||||
VirtualEvent::MouseEvent(Rc::new(CustomMouseEvent(evt)))
|
||||
VirtualEvent::MouseEvent(MouseEvent(Rc::new(CustomMouseEvent(evt))))
|
||||
// MouseEvent(Box::new(RawMouseEvent {
|
||||
// alt_key: evt.alt_key(),
|
||||
// button: evt.button() as i32,
|
||||
|
@ -547,25 +549,36 @@ fn decode_trigger(event: &web_sys::Event) -> anyhow::Result<EventTrigger> {
|
|||
|
||||
use anyhow::Context;
|
||||
|
||||
// for attr in {
|
||||
let attrs = target.attributes();
|
||||
for x in 0..attrs.length() {
|
||||
let attr = attrs.item(x).unwrap();
|
||||
log::debug!("attrs include: {:#?}", attr);
|
||||
}
|
||||
// }
|
||||
// for attr in target.attributes() {
|
||||
// log::debug!("attrs include: {:#?}", attr);
|
||||
// }
|
||||
|
||||
// The error handling here is not very descriptive and needs to be replaced with a zero-cost error system
|
||||
let val: String = target
|
||||
.get_attribute(&format!("dioxus-event-{}", typ))
|
||||
.context("")?;
|
||||
.context(format!("wrong format - received {:#?}", typ))?;
|
||||
|
||||
let mut fields = val.splitn(3, ".");
|
||||
|
||||
let gi_id = fields
|
||||
.next()
|
||||
.and_then(|f| f.parse::<u64>().ok())
|
||||
.context("")?;
|
||||
.context("failed to parse gi id")?;
|
||||
let el_id = fields
|
||||
.next()
|
||||
.and_then(|f| f.parse::<usize>().ok())
|
||||
.context("")?;
|
||||
.context("failed to parse el id")?;
|
||||
let real_id = fields
|
||||
.next()
|
||||
.and_then(|f| f.parse::<u64>().ok().map(RealDomNode::new))
|
||||
.context("")?;
|
||||
.context("failed to parse real id")?;
|
||||
|
||||
// Call the trigger
|
||||
log::debug!("decoded gi_id: {}, li_idx: {}", gi_id, el_id);
|
||||
|
@ -575,6 +588,7 @@ fn decode_trigger(event: &web_sys::Event) -> anyhow::Result<EventTrigger> {
|
|||
virtual_event_from_websys_event(event),
|
||||
triggered_scope,
|
||||
real_id,
|
||||
dioxus_core::events::EventPriority::High,
|
||||
))
|
||||
}
|
||||
|
||||
|
@ -584,3 +598,6 @@ impl ListenerMap {
|
|||
false
|
||||
}
|
||||
}
|
||||
|
||||
struct JsStringCache {}
|
||||
impl JsStringCache {}
|
||||
|
|
Loading…
Reference in a new issue