From 1a2f91ed91c13dae553ecde585462ab261b1b95d Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Fri, 24 Sep 2021 00:05:56 -0400 Subject: [PATCH] polish: examples --- Cargo.toml | 4 +- examples/async.rs | 50 ++++++----- examples/borrowed.rs | 32 ++++--- examples/calculator.rs | 126 ++++++++++++--------------- packages/core/src/diff.rs | 14 ++- packages/core/src/events.rs | 40 ++++++++- packages/core/src/nodes.rs | 20 +++-- packages/core/src/scheduler.rs | 30 ++++--- packages/core/tests/borrowedstate.rs | 1 - packages/hooks/src/usestate.rs | 4 +- packages/html/Cargo.toml | 2 +- packages/html/src/lib.rs | 67 +++++++++++++- packages/web/BUGS.md | 6 ++ packages/web/Cargo.toml | 12 +-- packages/web/examples/basic.rs | 64 +++++++++----- packages/web/examples/blah.rs | 2 +- packages/web/src/dom.rs | 14 ++- packages/web/src/lib.rs | 1 + packages/web/src/ric_raf.rs | 21 +++-- 19 files changed, 327 insertions(+), 183 deletions(-) create mode 100644 packages/web/BUGS.md diff --git a/Cargo.toml b/Cargo.toml index 043c7c802..3689966ff 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -51,8 +51,10 @@ argh = "0.1.5" env_logger = "*" async-std = { version = "1.9.0", features = ["attributes"] } rand = { version = "0.8.4", features = ["small_rng"] } -surf = { version = "2.2.0", git = "https://github.com/jkelleyrtp/surf/", branch = "jk/fix-the-wasm" } gloo-timers = "0.2.1" +# surf = { version = "2.3.1", default-features = false, features = [ +# "wasm-client", +# ] } [target.'cfg(target_arch = "wasm32")'.dev-dependencies] gloo-timers = "0.2.1" diff --git a/examples/async.rs b/examples/async.rs index 3a9d317c9..da7649776 100644 --- a/examples/async.rs +++ b/examples/async.rs @@ -1,41 +1,43 @@ -//! Example: README.md showcase -//! -//! The example from the README.md +/* +This example shows how to use async and loops to implement a coroutine in a component. Coroutines can be controlled via +the `TaskHandle` object. +*/ use dioxus::prelude::*; +use gloo_timers::future::TimeoutFuture; + fn main() { - dioxus::desktop::launch(App, |c| c).expect("faield to launch"); + dioxus::desktop::launch(App, |c| c).unwrap(); } -pub static App: FC<()> = |cx, props| { +pub static App: FC<()> = |cx, _| { let count = use_state(cx, || 0); let mut direction = use_state(cx, || 1); let (async_count, dir) = (count.for_async(), *direction); - let (task, _result) = use_task(cx, move || async move { + + let (task, _) = use_task(cx, move || async move { loop { - gloo_timers::future::TimeoutFuture::new(250).await; + TimeoutFuture::new(250).await; *async_count.get_mut() += dir; } }); - cx.render(rsx! { - div { - h1 {"count is {count}"} - button { - "Stop counting" - onclick: move |_| task.stop() - } - button { - "Start counting" - onclick: move |_| task.resume() - } - button { - "Switch counting direcion" - onclick: move |_| { - direction *= -1; - task.restart(); - } + rsx!(cx, div { + h1 {"count is {count}"} + button { + "Stop counting" + onclick: move |_| task.stop() + } + button { + "Start counting" + onclick: move |_| task.resume() + } + button { + "Switch counting direcion" + onclick: move |_| { + direction *= -1; + task.restart(); } } }) diff --git a/examples/borrowed.rs b/examples/borrowed.rs index 912999b6a..320d5faa4 100644 --- a/examples/borrowed.rs +++ b/examples/borrowed.rs @@ -1,20 +1,18 @@ -#![allow(non_snake_case)] -//! Example: Extremely nested borrowing -//! ----------------------------------- -//! -//! Dioxus manages borrow lifetimes for you. This means any child may borrow from its parent. However, it is not possible -//! to hand out an &mut T to children - all props are consumed by &P, so you'd only get an &&mut T. -//! -//! How does it work? -//! -//! Dioxus will manually drop closures and props - things that borrow data before the component is ran again. This is done -//! "bottom up" from the lowest child all the way to the initiating parent. As it traverses each listener and prop, the -//! drop implementation is manually called, freeing any memory and ensuring that memory is not leaked. -//! -//! We cannot drop from the parent to the children - if the drop implementation modifies the data, downstream references -//! might be broken since we take an &mut T and and &T to the data. Instead, we work bottom up, making sure to remove any -//! potential references to the data before finally giving out an &mut T. This prevents us from mutably aliasing the data, -//! and is proven to be safe with MIRI. +/* +Dioxus manages borrow lifetimes for you. This means any child may borrow from its parent. However, it is not possible +to hand out an &mut T to children - all props are consumed by &P, so you'd only get an &&mut T. + +How does it work? + +Dioxus will manually drop closures and props - things that borrow data before the component is ran again. This is done +"bottom up" from the lowest child all the way to the initiating parent. As it traverses each listener and prop, the +drop implementation is manually called, freeing any memory and ensuring that memory is not leaked. + +We cannot drop from the parent to the children - if the drop implementation modifies the data, downstream references +might be broken since we take an &mut T and and &T to the data. Instead, we work bottom up, making sure to remove any +potential references to the data before finally giving out an &mut T. This prevents us from mutably aliasing the data, +and is proven to be safe with MIRI. +*/ use dioxus::prelude::*; diff --git a/examples/calculator.rs b/examples/calculator.rs index 13b1cc865..3b86d1a31 100644 --- a/examples/calculator.rs +++ b/examples/calculator.rs @@ -1,25 +1,19 @@ -//! Example: Calculator -//! ------------------------- - -fn main() { - env_logger::init(); - dioxus::desktop::launch(App, |cfg| cfg).unwrap(); -} +/* +This example is a simple iOS-style calculator. This particular example can run any platform - Web, Mobile, Desktop. +This calculator version uses React-style state management. All state is held as individual use_states. +*/ use dioxus::events::on::*; use dioxus::prelude::*; -enum Operator { - Add, - Sub, - Mul, - Div, +fn main() { + dioxus::desktop::launch(APP, |cfg| cfg).unwrap(); } -const App: FC<()> = |cx, props| { +const APP: FC<()> = |cx, _| { let cur_val = use_state(cx, || 0.0_f64); - let operator = use_state(cx, || None as Option); - let display_value = use_state(cx, || "".to_string()); + let operator = use_state(cx, || None); + let display_value = use_state(cx, || String::from("")); let clear_display = display_value == "0"; let clear_text = if clear_display { "C" } else { "AC" }; @@ -62,11 +56,10 @@ const App: FC<()> = |cx, props| { }; let keydownhandler = move |evt: KeyboardEvent| match evt.key_code() { - KeyCode::Backspace => { - if !display_value.as_str().eq("0") { - display_value.modify().pop(); - } - } + KeyCode::Add => operator.set(Some(Operator::Add)), + KeyCode::Subtract => operator.set(Some(Operator::Sub)), + KeyCode::Divide => operator.set(Some(Operator::Div)), + KeyCode::Multiply => operator.set(Some(Operator::Mul)), KeyCode::Num0 => input_digit(0), KeyCode::Num1 => input_digit(1), KeyCode::Num2 => input_digit(2), @@ -77,73 +70,62 @@ const App: FC<()> = |cx, props| { KeyCode::Num7 => input_digit(7), KeyCode::Num8 => input_digit(8), KeyCode::Num9 => input_digit(9), - KeyCode::Add => operator.set(Some(Operator::Add)), - KeyCode::Subtract => operator.set(Some(Operator::Sub)), - KeyCode::Divide => operator.set(Some(Operator::Div)), - KeyCode::Multiply => operator.set(Some(Operator::Mul)), + KeyCode::Backspace => { + if !display_value.as_str().eq("0") { + display_value.modify().pop(); + } + } _ => {} }; - cx.render(rsx! { - div { class: "calculator", onkeydown: {keydownhandler} - div { class: "input-keys" - 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(), "●" } + use separator::Separatable; + let formatted_display = cur_val.separated_string(); - {(1..9).map(|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() "=" } - } + rsx!(cx, div { + class: "calculator", onkeydown: {keydownhandler} + div { class: "calculator-display", "{formatted_display}" } + div { class: "input-keys" + 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(|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() "=" } } } }) }; +enum Operator { + Add, + Sub, + Mul, + Div, +} + #[derive(Props)] struct CalculatorKeyProps<'a> { - /// Name! name: &'static str, - - /// Click! onclick: &'a dyn Fn(MouseEvent), } fn CalculatorKey<'a>(cx: Context<'a>, props: &'a CalculatorKeyProps) -> DomTree<'a> { - cx.render(rsx! { - button { - class: "calculator-key {props.name}" - onclick: {props.onclick} - {cx.children()} - } - }) -} - -#[derive(Props, PartialEq)] -struct CalculatorDisplayProps { - val: f64, -} - -fn CalculatorDisplay<'a>(cx: Context<'a>, props: &CalculatorDisplayProps) -> DomTree<'a> { - use separator::Separatable; - // Todo, add float support to the num-format crate - let formatted = props.val.separated_string(); - // TODO: make it autoscaling with css - cx.render(rsx! { - div { class: "calculator-display" - "{formatted}" - } + rsx!(cx, button { + class: "calculator-key {props.name}" + onclick: {props.onclick} + {cx.children()} }) } diff --git a/packages/core/src/diff.rs b/packages/core/src/diff.rs index 3e4ecfcfc..e29b5e18e 100644 --- a/packages/core/src/diff.rs +++ b/packages/core/src/diff.rs @@ -461,9 +461,10 @@ impl<'bump> DiffMachine<'bump> { let mut please_commit = |edits: &mut Vec| { if !has_comitted { has_comitted = true; - if let Some(root) = root { - edits.push(PushRoot { id: root.as_u64() }); - } + log::info!("planning on committing... {:#?}, {:#?}", old, new); + edits.push(PushRoot { + id: root.unwrap().as_u64(), + }); } }; @@ -475,7 +476,12 @@ impl<'bump> DiffMachine<'bump> { // TODO: take a more efficient path than this if old.attributes.len() == new.attributes.len() { for (old_attr, new_attr) in old.attributes.iter().zip(new.attributes.iter()) { - if old_attr.value != new_attr.value || old_attr.is_volatile { + log::info!("checking attribute"); + if old_attr.value != new_attr.value { + please_commit(&mut self.mutations.edits); + self.mutations.set_attribute(new_attr); + } else if new_attr.is_volatile { + log::debug!("setting due to volatile atttributes"); please_commit(&mut self.mutations.edits); self.mutations.set_attribute(new_attr); } diff --git a/packages/core/src/events.rs b/packages/core/src/events.rs index 7cc51dab4..e11127e43 100644 --- a/packages/core/src/events.rs +++ b/packages/core/src/events.rs @@ -264,8 +264,10 @@ pub mod on { ClipboardEventInner(ClipboardEvent): [ /// Called when "copy" oncopy + /// oncut oncut + /// onpaste onpaste ]; @@ -305,7 +307,7 @@ pub mod on { /// onchange onchange - /// oninput + /// oninput handler oninput /// oninvalid @@ -446,22 +448,31 @@ pub mod on { PointerEventInner(PointerEvent): [ /// pointerdown onpointerdown + /// pointermove onpointermove + /// pointerup onpointerup + /// pointercancel onpointercancel + /// gotpointercapture ongotpointercapture + /// lostpointercapture onlostpointercapture + /// pointerenter onpointerenter + /// pointerleave onpointerleave + /// pointerover onpointerover + /// pointerout onpointerout ]; @@ -474,10 +485,13 @@ pub mod on { TouchEventInner(TouchEvent): [ /// ontouchcancel ontouchcancel + /// ontouchend ontouchend + /// ontouchmove ontouchmove + /// ontouchstart ontouchstart ]; @@ -490,48 +504,70 @@ pub mod on { MediaEventInner(MediaEvent): [ ///abort onabort + ///canplay oncanplay + ///canplaythrough oncanplaythrough + ///durationchange ondurationchange + ///emptied onemptied + ///encrypted onencrypted + ///ended onended + ///error onerror + ///loadeddata onloadeddata + ///loadedmetadata onloadedmetadata + ///loadstart onloadstart + ///pause onpause + ///play onplay + ///playing onplaying + ///progress onprogress + ///ratechange onratechange + ///seeked onseeked + ///seeking onseeking + ///stalled onstalled + ///suspend onsuspend + ///timeupdate ontimeupdate + ///volumechange onvolumechange + ///waiting onwaiting ]; @@ -539,8 +575,10 @@ pub mod on { AnimationEventInner(AnimationEvent): [ /// onanimationstart onanimationstart + /// onanimationend onanimationend + /// onanimationiteration onanimationiteration ]; diff --git a/packages/core/src/nodes.rs b/packages/core/src/nodes.rs index 200bfa861..5346042a1 100644 --- a/packages/core/src/nodes.rs +++ b/packages/core/src/nodes.rs @@ -197,6 +197,21 @@ pub struct VElement<'a> { pub children: &'a [VNode<'a>], } +impl Debug for VElement<'_> { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + f.debug_struct("VElement") + .field("tag_name", &self.tag_name) + .field("namespace", &self.namespace) + .field("key", &self.key) + .field("dom_id", &self.dom_id) + .field("parent_id", &self.parent_id) + .field("listeners", &self.listeners.len()) + .field("attributes", &self.attributes) + .field("children", &self.children) + .finish() + } +} + /// A trait for any generic Dioxus Element. /// /// This trait provides the ability to use custom elements in the `rsx!` macro. @@ -429,11 +444,6 @@ impl<'a> NodeFactory<'a> { is_volatile: bool, ) -> Attribute<'a> { let (value, is_static) = self.raw_text(val); - let is_volatile = match name { - "value" | "checked" | "selected" => true, - _ => false, - }; - Attribute { name, value, diff --git a/packages/core/src/scheduler.rs b/packages/core/src/scheduler.rs index d64b6c651..dcb6acb33 100644 --- a/packages/core/src/scheduler.rs +++ b/packages/core/src/scheduler.rs @@ -72,6 +72,7 @@ use crate::heuristics::*; use crate::innerlude::*; use futures_channel::mpsc::{UnboundedReceiver, UnboundedSender}; use futures_util::{pin_mut, stream::FuturesUnordered, Future, FutureExt, StreamExt}; +use fxhash::FxHashMap; use fxhash::FxHashSet; use indexmap::IndexSet; use slab::Slab; @@ -357,6 +358,8 @@ impl Scheduler { let shared = self.pool.clone(); let mut machine = unsafe { saved_state.promote(&shared) }; + let mut ran_scopes = FxHashSet::default(); + if machine.stack.is_empty() { let shared = self.pool.clone(); @@ -367,11 +370,18 @@ impl Scheduler { }); if let Some(scopeid) = self.dirty_scopes.pop() { - let component = self.pool.get_scope(scopeid).unwrap(); - let (old, new) = (component.frames.wip_head(), component.frames.fin_head()); - // let (old, new) = (component.frames.wip_head(), component.frames.fin_head()); - machine.stack.scope_stack.push(scopeid); - machine.stack.push(DiffInstruction::Diff { new, old }); + log::info!("handlng dirty scope {:#?}", scopeid); + if !ran_scopes.contains(&scopeid) { + ran_scopes.insert(scopeid); + + let mut component = self.pool.get_scope_mut(scopeid).unwrap(); + if component.run_scope(&self.pool) { + let (old, new) = (component.frames.wip_head(), component.frames.fin_head()); + // let (old, new) = (component.frames.wip_head(), component.frames.fin_head()); + machine.stack.scope_stack.push(scopeid); + machine.stack.push(DiffInstruction::Diff { new, old }); + } + } } } @@ -472,15 +482,7 @@ impl Scheduler { while let Ok(Some(dirty_scope)) = self.receiver.try_next() { match dirty_scope { SchedulerMsg::Immediate(im) => { - log::debug!("Handling immediate {:?}", im); - - if let Some(scope) = self.pool.get_scope_mut(im) { - if scope.run_scope(&self.pool) { - self.dirty_scopes.insert(im); - } else { - todo!() - } - } + self.dirty_scopes.insert(im); } SchedulerMsg::UiEvent(e) => self.ui_events.push_back(e), SchedulerMsg::Task(_) => todo!(), diff --git a/packages/core/tests/borrowedstate.rs b/packages/core/tests/borrowedstate.rs index 1c40ce7ca..02fdd51c5 100644 --- a/packages/core/tests/borrowedstate.rs +++ b/packages/core/tests/borrowedstate.rs @@ -3,7 +3,6 @@ use dioxus_core as dioxus; use dioxus_html as dioxus_elements; static Parent: FC<()> = |cx, props| { - // let value = cx.use_hook(|_| String::new(), |f| &*f, |_| {}); cx.render(rsx! { diff --git a/packages/hooks/src/usestate.rs b/packages/hooks/src/usestate.rs index bd67fd153..10015a23c 100644 --- a/packages/hooks/src/usestate.rs +++ b/packages/hooks/src/usestate.rs @@ -49,9 +49,9 @@ use std::{ /// } /// } /// ``` -pub fn use_state<'a, 'c, T: 'static, F: FnOnce() -> T>( +pub fn use_state<'a, 'c, T: 'static>( cx: Context<'a>, - initial_state_fn: F, + initial_state_fn: impl FnOnce() -> T, ) -> UseState<'a, T> { cx.use_hook( move |_| UseStateInner { diff --git a/packages/html/Cargo.toml b/packages/html/Cargo.toml index 819a5d291..1d21ae0b7 100644 --- a/packages/html/Cargo.toml +++ b/packages/html/Cargo.toml @@ -6,7 +6,7 @@ edition = "2018" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -dioxus-core = { path="../core" } +dioxus-core = { path = "../core" } [dev-dependencies] scraper = "0.12.0" diff --git a/packages/html/src/lib.rs b/packages/html/src/lib.rs index e9f427bd4..294ce326b 100644 --- a/packages/html/src/lib.rs +++ b/packages/html/src/lib.rs @@ -681,7 +681,10 @@ macro_rules! builder_constructors { $( $(#[$attr:meta])* $name:ident { - $($fil:ident: $vil:ident,)* + $( + $(#[$attr_method:meta])* + $fil:ident: $vil:ident, + )* }; )* ) => { @@ -699,6 +702,7 @@ macro_rules! builder_constructors { impl $name { $( + $(#[$attr_method])* pub fn $fil<'a>(&self, cx: NodeFactory<'a>, val: Arguments) -> Attribute<'a> { cx.attr(stringify!($fil), val, None, false) } @@ -1525,7 +1529,10 @@ builder_constructors! { src: Uri, step: String, tabindex: usize, - r#type: InputType, + + // This has a manual implementation below + // r#type: InputType, + value: String, width: isize, }; @@ -1570,7 +1577,10 @@ builder_constructors! { option { disabled: Bool, label: String, - selected: Bool, + + // defined below + // selected: Bool, + value: String, }; @@ -1595,7 +1605,8 @@ builder_constructors! { /// [`