From 1a6b2388299bf005006e23b75c65a09449049f8d Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Fri, 3 Sep 2021 11:28:09 -0400 Subject: [PATCH 01/35] wip: naming --- packages/core/src/scope.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/core/src/scope.rs b/packages/core/src/scope.rs index 0f5cae2c7..df5a99f89 100644 --- a/packages/core/src/scope.rs +++ b/packages/core/src/scope.rs @@ -231,7 +231,7 @@ impl Scope { scope: &self, }, }; - let n: DomTree<'a> = (_cb)(cx); + let new_node: DomTree<'a> = (_cb)(cx); } } From 3f68be79ae326f6d169ee771bf86490aed4d7885 Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Tue, 7 Sep 2021 18:25:57 -0400 Subject: [PATCH 02/35] wip: documentation and badges --- README.md | 20 ++++++++++---------- packages/core/src/lib.rs | 2 +- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index f1a6e02a1..e09ce1868 100644 --- a/README.md +++ b/README.md @@ -7,38 +7,38 @@
- - + Crates.io version - - + Download - + docs.rs docs - - + CI status

- + API Docs | - + Website | - + Examples

diff --git a/packages/core/src/lib.rs b/packages/core/src/lib.rs index b588215b3..ff600f6c9 100644 --- a/packages/core/src/lib.rs +++ b/packages/core/src/lib.rs @@ -4,7 +4,7 @@ /* Navigating this crate: - virtual_dom: the primary entrypoint for the crate -- scheduler: the core interior logic called by virtual_dom +- scheduler: the core interior logic called by the [`VirtualDom`] - nodes: the definition of VNodes, listeners, etc. - diff: the stackmachine-based diffing algorithm - hooks: foundational hooks that require crate-private APIs From ee46a834c4985e413b0e56f0de7552d38250eb66 Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Tue, 7 Sep 2021 18:27:10 -0400 Subject: [PATCH 03/35] docs: more docs --- README.md | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index e09ce1868..709f1d6fa 100644 --- a/README.md +++ b/README.md @@ -99,9 +99,9 @@ If you know React, then you already know Dioxus. Tools - + ## Why? From 0896f34493a730691ed1e07950e2e30fc1df0b0b Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Tue, 7 Sep 2021 18:32:47 -0400 Subject: [PATCH 04/35] feat: cool example liust --- README.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/README.md b/README.md index 709f1d6fa..aa8c7abeb 100644 --- a/README.md +++ b/README.md @@ -99,6 +99,16 @@ If you know React, then you already know Dioxus. Tools + +## Fun Examples: + +- File navigator (desktop app) +- Bluetooth scanner (desktop app, possibly works on iOS) +- Shopping app (liveview web and mobile app) +- TODO MVC (works on all platforms) +- Sierpinski's triangle (web SPA) +- Doxie Documentation Library (Web SPA with Hydration) + - - + CI status
@@ -52,30 +52,18 @@ Dioxus is a portable, performant, and ergonomic framework for building cross-pla fn App(cx: Context<()>) -> DomTree { let mut count = use_state(cx, || 0); - cx.render(rsx! { + cx.render(rsx!( h1 { "High-Five counter: {count}" } button { onclick: move |_| count += 1, "Up high!" } button { onclick: move |_| count -= 1, "Down low!" } - }) + )) }; ``` -Dioxus can be used to deliver webapps, desktop apps, static pages, liveview apps, eventually mobile apps (WIP), and more. At its core, Dioxus is entirely renderer agnostic and has great documentation for creating new renderers for any platform. +Dioxus can be used to deliver webapps, desktop apps, static sites, liveview apps, mobile apps (WIP), and more. At its core, Dioxus is entirely renderer agnostic and has great documentation for creating new renderers for any platform. If you know React, then you already know Dioxus. -### **Things you'll love ❤️:** - -- Ergonomic design -- Minimal boilerplate -- Simple build, test, and deploy -- Compile-time correct templating -- Support for fine-grained reactivity -- Support for html! and rsx! templates -- SSR, WASM, desktop, and mobile support -- Support for asynchronous batched rendering -- Powerful and simple integrated state management -- Rust! (enums, static types, modules, efficiency) ### Unique features: - Incredible inline documentation. Supports hover and guides for all HTML elements, listeners, and events. @@ -100,14 +88,26 @@ If you know React, then you already know Dioxus. -## Fun Examples: +## Examples: + +| File Navigator (Desktop) | Bluetooth scanner (Desktop) | TodoMVC (All platforms) | Widget Gallery | +| ---------------------------------------------------------------- | ---------------------------------------------------------------- | ---------------------------------------------------------------- | ---------------------------------------------------------------- | +| ![asd](https://sixtyfps.io/resources/printerdemo_screenshot.png) | ![asd](https://sixtyfps.io/resources/printerdemo_screenshot.png) | ![asd](https://sixtyfps.io/resources/printerdemo_screenshot.png) | ![asd](https://sixtyfps.io/resources/printerdemo_screenshot.png) | + + + +| E-Commerce Site | Doxie Documentation Generator | Chatroom | Widget Gallery | +| ---------------------------------------------------------------- | ---------------------------------------------------------------- | ---------------------------------------------------------------- | ---------------------------------------------------------------- | +| ![asd](https://sixtyfps.io/resources/printerdemo_screenshot.png) | ![asd](https://sixtyfps.io/resources/printerdemo_screenshot.png) | ![asd](https://sixtyfps.io/resources/printerdemo_screenshot.png) | ![asd](https://sixtyfps.io/resources/printerdemo_screenshot.png) | + + + +| Printer Demo | Slide Puzzle | Todo | Widget Gallery | +| ---------------------------------------------------------------- | ---------------------------------------------------------------- | ---------------------------------------------------------------- | ---------------------------------------------------------------- | +| ![asd](https://sixtyfps.io/resources/printerdemo_screenshot.png) | ![asd](https://sixtyfps.io/resources/printerdemo_screenshot.png) | ![asd](https://sixtyfps.io/resources/printerdemo_screenshot.png) | ![asd](https://sixtyfps.io/resources/printerdemo_screenshot.png) | + + -- File navigator (desktop app) -- Bluetooth scanner (desktop app, possibly works on iOS) -- Shopping app (liveview web and mobile app) -- TODO MVC (works on all platforms) -- Sierpinski's triangle (web SPA) -- Doxie Documentation Library (Web SPA with Hydration) See the awesome-dioxus page for a curated list of content in the Dioxus Ecosystem. @@ -156,7 +156,6 @@ And much more. Dioxus makes Rust apps just as fast to write as React apps, but a Dioxus is heavily inspired by React, but we want your transition to feel like an upgrade. Dioxus is _most_ of the way there, but missing a few key features. This parity table does not necessarily include important ecosystem crates like code blocks, markdown, resizing hooks, etc. -### Phase 1: The Basics | Feature | Dioxus | React | Notes for Dioxus | | ------------------------- | ------ | ----- | -------------------------------------------------------------------- | @@ -189,34 +188,22 @@ Dioxus is heavily inspired by React, but we want your transition to feel like an | Fine-grained reactivity | 🛠 | ❓ | Skip diffing for fine-grain updates | | Effects | 🛠 | ✅ | Run effects after a component has been committed to render | - -### Phase 2: Advanced Toolkits - -| Feature | Dioxus | React | Notes for Dioxus | -| --------------------- | ------ | ----- | ---------------------------------- | -| 1st class router | 👀 | ✅ | Hook built on top of history | -| Assets | 👀 | ✅ | include css/svg/img url statically | -| Integrated classnames | 🛠 | ❓ | built-in `classnames` | -| Transition | 👀 | 🛠 | High-level control over suspense | -| Animation | 👀 | ✅ | Spring-style animations | -| Native Mobile | 👀 | ✅ | Render with cacao | -| Native Desktop | 👀 | ✅ | Render with native desktop | -| 3D Renderer | 👀 | ✅ | react-three-fiber | - -### Phase 3: Additional Complexity - -| Feature | Dioxus | React | Notes for Dioxus | -| -------------------- | ------ | ----- | ------------------------------------ | -| Portal | ❓ | ✅ | cast elements through tree | -| Error/Panic boundary | 👀 | ✅ | catch panics and display custom BSOD | -| Code-splitting | 👀 | ✅ | Make bundle smaller/lazy | -| LiveView | 👀 | ❓ | Example for SSR + WASM apps | - - ✅ = implemented and working - 🛠 = actively being worked on - 👀 = not yet implemented or being worked on - ❓ = not sure if will or can implement +### Coming up... + +- 1st class router +- Assets +- Integrated classnames +- Transition +- Animation +- Native Mobile +- Native Desktop +- 3D Renderer +- LiveView ## FAQ: @@ -228,9 +215,9 @@ Remember: Dioxus is a library - not a compiler like Svelte. Plus, the inner Virt The overhead layer between WASM and JS APIs is extremely poorly understood. Rust web benchmarks typically suffer from differences in how Rust and JS cache strings. In Dioxus, we solve most of these issues and our JS Framework Benchmark actually beats the WASM Bindgen benchmark in many cases. Compared to a "pure vanilla JS" solution, Dioxus adds less than 5% of overhead and takes advantage of batched DOM manipulation. ### Aren't WASM binaries too huge to deploy in production? -WASM binary sizes are another poorly understood characteristic of Rust web apps. 50kb of WASM and 50kb of JS are not made equally. In JS, the code must be downloaded _first_ and _then_ JIT-ted. Just-in-time compiling 50kb of JavaScript takes some time which is why 50kb of JavaScript sounds like a lot! However, with WASM, the code is downloaded and JIT-ted _simultaneously_ through the magic of streaming compilation. By the time the 50kb of Rust is finished downloading, it is already ready to go. Again, Dioxus beats out many benchmarks with time-to-interactivity. +WASM binary sizes are another poorly understood characteristic of Rust web apps. 50kb of WASM and 50kb of JS are _not_ made equally. In JS, the code must be downloaded _first_ and _then_ JIT-ted. Just-in-time compiling 50kb of JavaScript takes a while which is why 50kb of JavaScript sounds like a lot! However, with WASM, the code is downloaded and JIT-ted _simultaneously_ through the magic of streaming compilation. By the time the 50kb of Rust is finished downloading, it is already ready to go. Again, Dioxus beats out many benchmarks with time-to-interactivity. -For reference, Dioxus `hello-world` gzipped clocks in at around 60kb. +For reference, Dioxus `hello-world` clocks in at around 70kb gzip or 60kb brotli, and Dioxus supports SSR. ### Why hooks? Why not MVC, classes, traits, messages, etc? There are plenty Rust Elm-like frameworks in the world - we were not interested in making another! Instead, we borrowed hooks from React. JS and Rust share many structural similarities, so if you're comfortable with React, then you'll be plenty comfortable with Dioxus. diff --git a/packages/core/src/diff.rs b/packages/core/src/diff.rs index 19e2f365c..2da69817d 100644 --- a/packages/core/src/diff.rs +++ b/packages/core/src/diff.rs @@ -329,6 +329,9 @@ impl<'bump> DiffMachine<'bump> { } if children.len() > 0 { + self.stack.element_id_stack.push(real_id);; + // push our element_id onto the stack + // drop our element off the stack self.stack.create_children(children, MountType::Append); } } diff --git a/packages/core/src/hooks.rs b/packages/core/src/hooks.rs index 85cf782ff..8121e958a 100644 --- a/packages/core/src/hooks.rs +++ b/packages/core/src/hooks.rs @@ -170,8 +170,6 @@ pub(crate) struct SuspenseHook { pub value: Rc>>>, } -type SuspendedCallback = Box Fn(SuspendedContext<'a>) -> DomTree<'a>>; - pub struct SuspendedContext<'a> { pub(crate) inner: Context<'a, ()>, } diff --git a/packages/web/Cargo.toml b/packages/web/Cargo.toml index 78d5de6b3..7dc49bec9 100644 --- a/packages/web/Cargo.toml +++ b/packages/web/Cargo.toml @@ -18,7 +18,6 @@ log = "0.4.14" fxhash = "0.2.1" wasm-logger = "0.2.0" console_error_panic_hook = "0.1.6" -generational-arena = "0.2.8" wasm-bindgen-test = "0.3.21" once_cell = "1.8" async-channel = "1.6.1" From 4fbe58116f7282dbdf9dee759854b45339f850c2 Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Mon, 20 Sep 2021 17:08:37 -0400 Subject: [PATCH 19/35] wip; more changes --- README.md | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/README.md b/README.md index f59eed707..ff170a40d 100644 --- a/README.md +++ b/README.md @@ -193,18 +193,6 @@ Dioxus is heavily inspired by React, but we want your transition to feel like an - 👀 = not yet implemented or being worked on - ❓ = not sure if will or can implement -### Coming up... - -- 1st class router -- Assets -- Integrated classnames -- Transition -- Animation -- Native Mobile -- Native Desktop -- 3D Renderer -- LiveView - ## FAQ: ### Aren't VDOMs just pure overhead? Why not something like Solid or Svelte? @@ -246,7 +234,6 @@ You shouldn't use Dioxus if: - You want to support browsers where WASM or asm.js are not supported. - ## License This project is licensed under the [MIT license]. From adf202eab9201c69ec455e621c755500329815fe Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Mon, 20 Sep 2021 17:36:39 -0400 Subject: [PATCH 20/35] wip: more doc --- packages/core/src/events.rs | 26 ++++++++++++++++++- packages/html/src/lib.rs | 51 +++++++++++++++++++++---------------- 2 files changed, 54 insertions(+), 23 deletions(-) diff --git a/packages/core/src/events.rs b/packages/core/src/events.rs index cce39cd4c..0d9c2442d 100644 --- a/packages/core/src/events.rs +++ b/packages/core/src/events.rs @@ -271,8 +271,10 @@ pub mod on { CompositionEventInner(CompositionEvent): [ /// oncompositionend oncompositionend + /// oncompositionstart oncompositionstart + /// oncompositionupdate oncompositionupdate ]; @@ -280,8 +282,10 @@ pub mod on { KeyboardEventInner(KeyboardEvent): [ /// onkeydown onkeydown + /// onkeypress onkeypress + /// onkeyup onkeyup ]; @@ -289,6 +293,7 @@ pub mod on { FocusEventInner(FocusEvent): [ /// onfocus onfocus + /// onblur onblur ]; @@ -297,12 +302,16 @@ pub mod on { FormEventInner(FormEvent): [ /// onchange onchange + /// oninput oninput + /// oninvalid oninvalid + /// onreset onreset + /// onsubmit onsubmit ]; @@ -373,36 +382,50 @@ pub mod on { /// ## Reference /// - https://www.w3schools.com/tags/ev_onclick.asp /// - https://developer.mozilla.org/en-US/docs/Web/API/Element/click_event - /// onclick + /// oncontextmenu oncontextmenu + /// ondoubleclick ondoubleclick + /// ondrag ondrag + /// ondragend ondragend + /// ondragenter ondragenter + /// ondragexit ondragexit + /// ondragleave ondragleave + /// ondragover ondragover + /// ondragstart ondragstart + /// ondrop ondrop + /// onmousedown onmousedown + /// onmouseenter onmouseenter + /// onmouseleave onmouseleave + /// onmousemove onmousemove + /// onmouseout onmouseout @@ -413,6 +436,7 @@ pub mod on { /// /// Triggered when the users's mouse hovers over an element. onmouseover + /// onmouseup onmouseup ]; diff --git a/packages/html/src/lib.rs b/packages/html/src/lib.rs index b726d34a5..e9f427bd4 100644 --- a/packages/html/src/lib.rs +++ b/packages/html/src/lib.rs @@ -43,6 +43,7 @@ macro_rules! style_trait_methods { )* ) => { $( + #[inline] $(#[$attr])* fn $name<'a>(&self, cx: NodeFactory<'a>, val: Arguments) -> Attribute<'a> { cx.attr($lit, val, Some("style"), false) @@ -112,6 +113,13 @@ pub trait GlobalAttributes { title; translate; } + + // This macro creates an explicit method call for each of the style attributes. + // + // The left token specifies the name of the attribute in the rsx! macro, and the right string literal specifies the + // actual name of the attribute generated. + // + // This roughly follows the html spec style_trait_methods! { /// Specifies the alignment of flexible container's items within the flex container. align_content: "align-content", @@ -152,34 +160,33 @@ pub trait GlobalAttributes { /// Specifies whether or not the "back" side of a transformed element is visible when facing the user. backface_visibility: "backface-visibility", - /// Defines a variety of background properties within one declaration. background: "background", /// Specify whether the background image is fixed in the viewport or scrolls. background_attachment: "background-attachment", + /// Specifies the painting area of the background. background_clip: "background-clip", - /// Defines an element's background color. background_color: "background-color", /// Defines an element's background image. background_image: "background-image", + /// Specifies the positioning area of the background images. background_origin: "background-origin", - /// Defines the origin of a background image. background_position: "background-position", /// Specify whether/how the background image is tiled. background_repeat: "background-repeat", + /// Specifies the size of the background images. background_size: "background-size", - /// Sets the width, style, and color for all four sides of an element's border. border: "border", @@ -188,13 +195,13 @@ pub trait GlobalAttributes { /// Sets the color of the bottom border of an element. border_bottom_color: "border-bottom-color", + /// Defines the shape of the bottom_left border corner of an element. border_bottom_left_radius: "border-bottom-left-radius", /// Defines the shape of the bottom_right border corner of an element. border_bottom_right_radius: "border-bottom-right-radius", - /// Sets the style of the bottom border of an element. border_bottom_style: "border-bottom-style", @@ -206,6 +213,7 @@ pub trait GlobalAttributes { /// Sets the color of the border on all the four sides of an element. border_color: "border-color", + /// Specifies how an image is to be used in place of the border styles. border_image: "border-image", @@ -224,7 +232,6 @@ pub trait GlobalAttributes { /// Specifies the width of the image_border. border_image_width: "border-image-width", - /// Sets the width, style, and color of the left border of an element. border_left: "border-left", @@ -236,10 +243,10 @@ pub trait GlobalAttributes { /// Sets the width of the left border of an element. border_left_width: "border-left-width", + /// Defines the shape of the border corners of an element. border_radius: "border-radius", - /// Sets the width, style, and color of the right border of an element. border_right: "border-right", @@ -263,13 +270,13 @@ pub trait GlobalAttributes { /// Sets the color of the top border of an element. border_top_color: "border-top-color", + /// Defines the shape of the top_left border corner of an element. border_top_left_radius: "border-top-left-radius", /// Defines the shape of the top_right border corner of an element. border_top_right_radius: "border-top-right-radius", - /// Sets the style of the top border of an element. border_top_style: "border-top-style", @@ -281,13 +288,13 @@ pub trait GlobalAttributes { /// Specify the location of the bottom edge of the positioned element. bottom: "bottom", + /// Applies one or more drop_shadows to the element's box. box_shadow: "box-shadow", /// Alter the default CSS box model. box_sizing: "box-sizing", - /// Specify the position of table's caption. caption_side: "caption-side", @@ -299,6 +306,7 @@ pub trait GlobalAttributes { /// Specify the color of the text of an element. color: "color", + /// Specifies the number of columns in a multi_column element. column_count: "column-count", @@ -329,7 +337,6 @@ pub trait GlobalAttributes { /// A shorthand property for setting column_width and column_count properties. columns: "columns", - /// Inserts generated content. content: "content", @@ -350,6 +357,7 @@ pub trait GlobalAttributes { /// Show or hide borders and backgrounds of empty table cells. empty_cells: "empty-cells", + /// Specifies the components of a flexible length. flex: "flex", @@ -371,7 +379,6 @@ pub trait GlobalAttributes { /// Specifies whether the flexible items should wrap or not. flex_wrap: "flex-wrap", - /// Specifies whether or not a box should float. float: "float", @@ -383,13 +390,13 @@ pub trait GlobalAttributes { /// Defines the font size for the text. font_size: "font-size", + /// Preserves the readability of text when font fallback occurs. font_size_adjust: "font-size-adjust", /// Selects a normal, condensed, or expanded face from a font. font_stretch: "font-stretch", - /// Defines the font style for the text. font_style: "font-style", @@ -401,10 +408,10 @@ pub trait GlobalAttributes { /// Specify the height of an element. height: "height", + /// Specifies how flex items are aligned along the main axis of the flex container after any flexible lengths and auto margins have been resolved. justify_content: "auto margins have been resolved.", - /// Specify the location of the left edge of the positioned element. left: "left", @@ -452,22 +459,22 @@ pub trait GlobalAttributes { /// Specify the minimum width of an element. min_width: "min-width", + /// Specifies the transparency of an element. opacity: "opacity", /// Specifies the order in which a flex items are displayed and laid out within a flex container. order: "order", - /// Sets the width, style, and color for all four sides of an element's outline. outline: "outline", /// Sets the color of the outline. outline_color: "outline-color", + /// Set the space between an outline and the border edge of an element. outline_offset: "outline-offset", - /// Sets a style for an outline. outline_style: "outline-style", @@ -482,7 +489,6 @@ pub trait GlobalAttributes { /// Specifies the treatment of content that overflows the element's box vertically. overflow_y: "overflow-y", - /// Sets the padding on all four sides of the element. padding: "padding", @@ -506,24 +512,25 @@ pub trait GlobalAttributes { /// Insert a page breaks inside an element. page_break_inside: "page-break-inside", + /// Defines the perspective from which all child elements of the object are viewed. perspective: "perspective", /// Defines the origin (the vanishing point for the 3D space) for the perspective property. perspective_origin: "perspective-origin", - /// Specifies how an element is positioned. position: "position", /// Specifies quotation marks for embedded quotations. quotes: "quotes", + /// Specifies whether or not an element is resizable by the user. resize: "resize", - /// Specify the location of the right edge of the positioned element. right: "right", + /// Specifies the length of the tab character. tab_size: "tab-size", @@ -546,9 +553,9 @@ pub trait GlobalAttributes { /// Specifies the style of the lines specified by the text_decoration_line property text_decoration_style: "text-decoration-style", - /// Indent the first line of text. text_indent: "text-indent", + /// Specifies the justification method to use when the text_align property is set to justify. text_justify: "text-justify", @@ -558,12 +565,12 @@ pub trait GlobalAttributes { /// Applies one or more shadows to the text content of an element. text_shadow: "text-shadow", - /// Transforms the case of the text. text_transform: "text-transform", /// Specify the location of the top edge of the positioned element. top: "top", + /// Applies a 2D or 3D transformation to an element. transform: "transform", @@ -588,7 +595,6 @@ pub trait GlobalAttributes { /// Specifies the speed curve of the transition effect. transition_timing_function: "transition-timing-function", - /// Sets the vertical positioning of an element relative to the current text baseline. vertical_align: "vertical-align", @@ -600,12 +606,13 @@ pub trait GlobalAttributes { /// Specify the width of an element. width: "width", + /// Specifies how to break lines within words. word_break: "word-break", - /// Sets the spacing between words. word_spacing: "word-spacing", + /// Specifies whether to break words when the content overflows the boundaries of its container. word_wrap: "word-wrap", From f1cff845ce11231cb4b2dd4857f9ca9f0265b925 Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Tue, 21 Sep 2021 12:11:04 -0400 Subject: [PATCH 21/35] wip: examples --- examples/calculator.rs | 6 +++--- examples/file_explorer.rs | 6 +++--- examples/model.rs | 24 ++++++++++----------- examples/reducer.rs | 4 ++-- examples/slideshow.rs | 4 ++-- examples/webview.rs | 4 ++-- packages/core/src/events.rs | 2 ++ packages/hooks/src/lib.rs | 5 ++++- packages/hooks/src/useref.rs | 38 ++++++++++++++++++++++++++++++++++ packages/hooks/src/usestate.rs | 13 +++++++++++- 10 files changed, 80 insertions(+), 26 deletions(-) diff --git a/examples/calculator.rs b/examples/calculator.rs index 2dbbde02b..be96de93c 100644 --- a/examples/calculator.rs +++ b/examples/calculator.rs @@ -27,9 +27,9 @@ const App: FC<()> = |cx| { let clear_display = display_value == "0"; let clear_text = if clear_display { "C" } else { "AC" }; - let input_digit = move |num: u8| display_value.get_mut().push_str(num.to_string().as_str()); + let input_digit = move |num: u8| display_value.modify().push_str(num.to_string().as_str()); - let input_dot = move || display_value.get_mut().push_str("."); + let input_dot = move || display_value.modify().push_str("."); let perform_operation = move || { if let Some(op) = operator.as_ref() { @@ -67,7 +67,7 @@ const App: FC<()> = |cx| { let keydownhandler = move |evt: KeyboardEvent| match evt.key_code() { KeyCode::Backspace => { if !display_value.as_str().eq("0") { - display_value.get_mut().pop(); + display_value.modify().pop(); } } KeyCode::_0 => input_digit(0), diff --git a/examples/file_explorer.rs b/examples/file_explorer.rs index bedb3d23d..5d4a89b32 100644 --- a/examples/file_explorer.rs +++ b/examples/file_explorer.rs @@ -25,7 +25,7 @@ static App: FC<()> = |cx| { let file_list = files.path_names.iter().enumerate().map(|(dir_id, path)| { rsx! ( - li { a {"{path}", onclick: move |_| files.get_mut().enter_dir(dir_id), href: "#"} } + li { a {"{path}", onclick: move |_| files.modify().enter_dir(dir_id), href: "#"} } ) }); @@ -33,7 +33,7 @@ static App: FC<()> = |cx| { rsx! { div { code {"{err}"} - button {"x", onclick: move |_| files.get_mut().clear_err() } + button {"x", onclick: move |_| files.modify().clear_err() } } } }); @@ -43,7 +43,7 @@ static App: FC<()> = |cx| { div { h1 {"Files: "} h3 {"Cur dir: {cur}"} - button { "go up", onclick: move |_| files.get_mut().go_up() } + button { "go up", onclick: move |_| files.modify().go_up() } ol { {file_list} } {err_disp} } diff --git a/examples/model.rs b/examples/model.rs index f65355a8a..ac294fd76 100644 --- a/examples/model.rs +++ b/examples/model.rs @@ -42,29 +42,29 @@ static App: FC<()> = |cx| { cx.render(rsx! { div { id: "wrapper" div { class: "app", style { "{STYLE}" } - div { class: "calculator", onkeypress: move |evt| state.get_mut().handle_keydown(evt), + div { class: "calculator", onkeypress: move |evt| state.modify().handle_keydown(evt), div { class: "calculator-display", "{formatted}"} div { class: "calculator-keypad" div { class: "input-keys" div { class: "function-keys" - CalculatorKey { name: "key-clear", onclick: move |_| state.get_mut().clear_display(), "{clear_text}" } - CalculatorKey { name: "key-sign", onclick: move |_| state.get_mut().toggle_sign(), "±"} - CalculatorKey { name: "key-percent", onclick: move |_| state.get_mut().toggle_percent(), "%"} + 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(), "%"} } div { class: "digit-keys" - CalculatorKey { name: "key-0", onclick: move |_| state.get_mut().input_digit(0), "0" } - CalculatorKey { name: "key-dot", onclick: move |_| state.get_mut().input_dot(), "●" } + CalculatorKey { name: "key-0", onclick: move |_| state.modify().input_digit(0), "0" } + CalculatorKey { name: "key-dot", onclick: move |_| state.modify().input_dot(), "●" } {(1..10).map(move |k| rsx!{ - CalculatorKey { key: "{k}", name: "key-{k}", onclick: move |_| state.get_mut().input_digit(k), "{k}" } + CalculatorKey { key: "{k}", name: "key-{k}", onclick: move |_| state.modify().input_digit(k), "{k}" } })} } } div { class: "operator-keys" - CalculatorKey { name:"key-divide", onclick: move |_| state.get_mut().set_operator(Operator::Div), "÷" } - CalculatorKey { name:"key-multiply", onclick: move |_| state.get_mut().set_operator(Operator::Mul), "×" } - CalculatorKey { name:"key-subtract", onclick: move |_| state.get_mut().set_operator(Operator::Sub), "−" } - CalculatorKey { name:"key-add", onclick: move |_| state.get_mut().set_operator(Operator::Add), "+" } - CalculatorKey { name:"key-equals", onclick: move |_| state.get_mut().perform_operation(), "=" } + 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(), "=" } } } } diff --git a/examples/reducer.rs b/examples/reducer.rs index 2f93beeee..7a2bf7de2 100644 --- a/examples/reducer.rs +++ b/examples/reducer.rs @@ -22,11 +22,11 @@ pub static App: FC<()> = |cx| { h3 {"The radio is... {is_playing}!"} button { "Pause" - onclick: move |_| state.get_mut().reduce(PlayerAction::Pause) + onclick: move |_| state.modify().reduce(PlayerAction::Pause) } button { "Play" - onclick: move |_| state.get_mut().reduce(PlayerAction::Play) + onclick: move |_| state.modify().reduce(PlayerAction::Play) } } }) diff --git a/examples/slideshow.rs b/examples/slideshow.rs index 7aa09b7af..97391c033 100644 --- a/examples/slideshow.rs +++ b/examples/slideshow.rs @@ -34,9 +34,9 @@ static App: FC<()> = |cx| { div { div { h1 {"my awesome slideshow"} } div { - button {"<-", onclick: move |_| slides.get_mut().go_forward()} + button {"<-", onclick: move |_| slides.modify().go_forward()} h3 { "{slides.slide_id}" } - button {"->" onclick: move |_| slides.get_mut().go_backward()} + button {"->" onclick: move |_| slides.modify().go_backward()} } } {slide} diff --git a/examples/webview.rs b/examples/webview.rs index 8759fc733..8178684de 100644 --- a/examples/webview.rs +++ b/examples/webview.rs @@ -25,8 +25,8 @@ static App: FC<()> = |cx| { cx.render(rsx! { div { h1 {"{state}"} - CalculatorKey { name: "key-clear", onclick: move |_| state.get_mut().push_str("hello"), "{clear_text}" } - CalculatorKey { name: "key-sign", onclick: move |_| { state.get_mut().pop(); }, "±"} + CalculatorKey { name: "key-clear", onclick: move |_| state.modify().push_str("hello"), "{clear_text}" } + CalculatorKey { name: "key-sign", onclick: move |_| { state.modify().pop(); }, "±"} } }) }; diff --git a/packages/core/src/events.rs b/packages/core/src/events.rs index 0d9c2442d..7cc51dab4 100644 --- a/packages/core/src/events.rs +++ b/packages/core/src/events.rs @@ -188,6 +188,8 @@ pub(crate) fn event_meta(event: &UserEvent) -> (bool, EventPriority) { } } +pub use on::KeyCode; + pub mod on { //! This module defines the synthetic events that all Dioxus apps enable. No matter the platform, every dioxus renderer //! will implement the same events and same behavior (bubbling, cancelation, etc). diff --git a/packages/hooks/src/lib.rs b/packages/hooks/src/lib.rs index 97c759b33..6a0722d95 100644 --- a/packages/hooks/src/lib.rs +++ b/packages/hooks/src/lib.rs @@ -1,2 +1,5 @@ mod usestate; -pub use usestate::use_state; +pub use usestate::{use_state, AsyncUseState, UseState}; + +mod useref; +pub use useref::*; diff --git a/packages/hooks/src/useref.rs b/packages/hooks/src/useref.rs index e69de29bb..4254c2e52 100644 --- a/packages/hooks/src/useref.rs +++ b/packages/hooks/src/useref.rs @@ -0,0 +1,38 @@ +use std::cell::{Ref, RefCell, RefMut}; + +use dioxus_core::Context; + +pub struct UseRef<'a, T> { + inner: &'a RefCell, +} + +impl<'a, T> UseRef<'a, T> { + pub fn read(&self) -> Ref<'_, T> { + self.inner.borrow() + } + + pub fn read_write(&self) -> (Ref<'_, T>, &Self) { + (self.read(), self) + } + + /// Calling "write" will force the component to re-render + pub fn write(&self) -> RefMut<'_, T> { + self.inner.borrow_mut() + } + + /// Allows the ability to write the value without forcing a re-render + pub fn write_silent(&self) -> RefMut<'_, T> { + self.inner.borrow_mut() + } +} + +impl Clone for UseRef<'_, T> { + fn clone(&self) -> Self { + Self { inner: self.inner } + } +} +impl Copy for UseRef<'_, T> {} + +pub fn use_ref(cx: Context

, f: impl FnOnce() -> T) -> UseRef { + cx.use_hook(|_| RefCell::new(f()), |f| UseRef { inner: f }, |_| {}) +} diff --git a/packages/hooks/src/usestate.rs b/packages/hooks/src/usestate.rs index f0d3abc43..ea0040be9 100644 --- a/packages/hooks/src/usestate.rs +++ b/packages/hooks/src/usestate.rs @@ -82,6 +82,7 @@ struct UseStateInner { pub struct UseState<'a, T: 'static> { inner: &'a UseStateInner, } + impl Copy for UseState<'_, T> {} impl<'a, T> Clone for UseState<'a, T> where @@ -115,6 +116,11 @@ impl<'a, T: 'static> UseState<'a, T> { self.inner.wip.borrow() } + /// Get the current status of the work-in-progress data + pub fn get_wip_mut(&self) -> RefMut> { + self.inner.wip.borrow_mut() + } + pub fn classic(self) -> (&'a T, &'a Rc) { todo!() } @@ -136,7 +142,12 @@ impl<'a, T: 'static> UseState<'a, T> { } impl<'a, T: 'static + ToOwned> UseState<'a, T> { - pub fn get_mut(self) -> RefMut<'a, T> { + /// Gain mutable access to the new value. This method is only available when the value is a `ToOwned` type. + /// + /// Mutable access is derived by calling "ToOwned" (IE cloning) on the current value. + /// + /// To get a reference to the current value, use `.get()` + pub fn modify(self) -> RefMut<'a, T> { // make sure we get processed self.needs_update(); From bfdcb20437c385116fd640e46b6361d09d8d5ff8 Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Tue, 21 Sep 2021 13:13:15 -0400 Subject: [PATCH 22/35] feat: transition to cx, props --- packages/core/src/component.rs | 2 +- packages/core/src/context.rs | 17 +++++------ packages/core/src/hooks.rs | 17 +++++------ packages/core/src/lib.rs | 2 +- packages/core/src/nodes.rs | 2 +- packages/core/src/scope.rs | 5 +--- packages/core/src/virtual_dom.rs | 4 +-- packages/core/tests/borrowedstate.rs | 45 ++++++++++++++++++++++++++++ packages/core/tests/create_dom.rs | 16 +++++----- packages/core/tests/display_vdom.rs | 4 +-- packages/core/tests/eventsystem.rs | 2 +- packages/core/tests/lifecycle.rs | 4 +-- packages/core/tests/sharedstate.rs | 4 +-- packages/core/tests/vdom_rebuild.rs | 14 ++++----- 14 files changed, 88 insertions(+), 50 deletions(-) create mode 100644 packages/core/tests/borrowedstate.rs diff --git a/packages/core/src/component.rs b/packages/core/src/component.rs index 36cf908e8..d19ec6581 100644 --- a/packages/core/src/component.rs +++ b/packages/core/src/component.rs @@ -29,7 +29,7 @@ use crate::innerlude::{Context, DomTree, LazyNodes, FC}; /// You want to use this free-function when your fragment needs a key and simply returning multiple nodes from rsx! won't cut it. /// #[allow(non_upper_case_globals, non_snake_case)] -pub fn Fragment<'a>(cx: Context<'a, ()>) -> DomTree<'a> { +pub fn Fragment<'a>(cx: Context<'a>, props: &'a ()) -> DomTree<'a> { cx.render(LazyNodes::new(|f| f.fragment_from_iter(cx.children()))) } diff --git a/packages/core/src/context.rs b/packages/core/src/context.rs index 7915601ca..65b3d9710 100644 --- a/packages/core/src/context.rs +++ b/packages/core/src/context.rs @@ -27,16 +27,15 @@ use std::{any::TypeId, ops::Deref, rc::Rc}; /// } /// } /// ``` -pub struct Context<'src, T> { - pub props: &'src T, +pub struct Context<'src> { pub scope: &'src Scope, } -impl<'src, T> Copy for Context<'src, T> {} -impl<'src, T> Clone for Context<'src, T> { +impl<'src> Copy for Context<'src> {} +impl<'src> Clone for Context<'src> { fn clone(&self) -> Self { Self { - props: self.props, + // props: self.props, scope: self.scope, } } @@ -44,14 +43,14 @@ impl<'src, T> Clone for Context<'src, T> { // We currently deref to props, but it might make more sense to deref to Scope? // This allows for code that takes cx.xyz instead of cx.props.xyz -impl<'a, T> Deref for Context<'a, T> { - type Target = &'a T; +impl<'a> Deref for Context<'a> { + type Target = &'a Scope; fn deref(&self) -> &Self::Target { - &self.props + &self.scope } } -impl<'src, P> Context<'src, P> { +impl<'src> Context<'src> { /// Access the children elements passed into the component /// /// This enables patterns where a component is passed children from its parent. diff --git a/packages/core/src/hooks.rs b/packages/core/src/hooks.rs index 8121e958a..fcbf640d1 100644 --- a/packages/core/src/hooks.rs +++ b/packages/core/src/hooks.rs @@ -24,8 +24,8 @@ use std::{ /// /// /// -pub fn use_task<'src, Out, Fut, Init, P>( - cx: Context<'src, P>, +pub fn use_task<'src, Out, Fut, Init>( + cx: Context<'src>, task_initializer: Init, ) -> (&'src TaskHandle, &'src Option) where @@ -83,8 +83,8 @@ where /// # Example /// /// -pub fn use_suspense<'src, Out, Fut, Cb, P>( - cx: Context<'src, P>, +pub fn use_suspense<'src, Out, Fut, Cb>( + cx: Context<'src>, task_initializer: impl FnOnce() -> Fut, user_callback: Cb, ) -> DomTree<'src> @@ -123,10 +123,7 @@ where Some(value) => { let out = value.downcast_ref::().unwrap(); let sus = SuspendedContext { - inner: Context { - props: &(), - scope: cx.scope, - }, + inner: Context { scope: cx.scope }, }; user_callback(sus, out) } @@ -171,7 +168,7 @@ pub(crate) struct SuspenseHook { } pub struct SuspendedContext<'a> { - pub(crate) inner: Context<'a, ()>, + pub(crate) inner: Context<'a>, } impl<'src> SuspendedContext<'src> { @@ -196,7 +193,7 @@ impl<'a, T> Deref for NodeRef<'a, T> { } } -pub fn use_node_ref(cx: Context

) -> NodeRef { +pub fn use_node_ref(cx: Context) -> NodeRef { cx.use_hook( |f| {}, |f| { diff --git a/packages/core/src/lib.rs b/packages/core/src/lib.rs index 6c6923204..33aa21ede 100644 --- a/packages/core/src/lib.rs +++ b/packages/core/src/lib.rs @@ -54,7 +54,7 @@ pub(crate) mod innerlude { pub use crate::virtual_dom::*; pub type DomTree<'a> = Option>; - pub type FC

= fn(Context

) -> DomTree; + pub type FC

= for<'a> fn(Context<'a>, &'a P) -> DomTree<'a>; pub use dioxus_core_macro::{format_args_f, html, rsx}; } diff --git a/packages/core/src/nodes.rs b/packages/core/src/nodes.rs index daf264fc6..555a9c285 100644 --- a/packages/core/src/nodes.rs +++ b/packages/core/src/nodes.rs @@ -511,7 +511,7 @@ impl<'a> NodeFactory<'a> { let caller: &'a mut dyn for<'b> Fn(&'b Scope) -> DomTree<'b> = bump.alloc(move |scope: &Scope| -> DomTree { let props: &'_ P = unsafe { &*(raw_props as *const P) }; - let res = component(Context { props, scope }); + let res = component(Context { scope }, props); unsafe { std::mem::transmute(res) } }); diff --git a/packages/core/src/scope.rs b/packages/core/src/scope.rs index 357cf2e2e..ff5535607 100644 --- a/packages/core/src/scope.rs +++ b/packages/core/src/scope.rs @@ -275,10 +275,7 @@ impl Scope { let sus: &'a VSuspended<'a> = unsafe { std::mem::transmute(sus) }; let cx: SuspendedContext<'a> = SuspendedContext { - inner: Context { - props: &(), - scope: self, - }, + inner: Context { scope: self }, }; let mut cb = sus.callback.borrow_mut().take().unwrap(); diff --git a/packages/core/src/virtual_dom.rs b/packages/core/src/virtual_dom.rs index e331fe26d..9f83138da 100644 --- a/packages/core/src/virtual_dom.rs +++ b/packages/core/src/virtual_dom.rs @@ -130,7 +130,7 @@ impl VirtualDom { // Safety: this callback is only valid for the lifetime of the root props let root_caller: Box DomTree> = Box::new(move |scope: &Scope| unsafe { let props: &'_ P = &*(props_ptr as *const P); - std::mem::transmute(root(Context { props, scope })) + std::mem::transmute(root(Context { scope }, props)) }); let scheduler = Scheduler::new(); @@ -203,7 +203,7 @@ impl VirtualDom { let root_caller: Box DomTree> = Box::new(move |scope: &Scope| unsafe { let props: &'_ P = &*(props_ptr as *const P); - std::mem::transmute(root(Context { props, scope })) + std::mem::transmute(root(Context { scope }, props)) }); root_scope.update_scope_dependencies(&root_caller, ScopeChildren(&[])); diff --git a/packages/core/tests/borrowedstate.rs b/packages/core/tests/borrowedstate.rs new file mode 100644 index 000000000..1c40ce7ca --- /dev/null +++ b/packages/core/tests/borrowedstate.rs @@ -0,0 +1,45 @@ +use dioxus::{nodes::VSuspended, prelude::*, DomEdit, TestDom}; +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! { + div { + Child { name: value } + Child { name: value } + Child { name: value } + Child { name: value } + } + }) +}; + +#[derive(Props)] +struct ChildProps<'a> { + name: &'a String, +} + +fn Child<'a>(cx: Context<'a>, props: &'a ChildProps) -> DomTree<'a> { + cx.render(rsx! { + div { + h1 { "it's nested" } + Child2 { name: props.name } + } + }) +} + +#[derive(Props)] +struct Grandchild<'a> { + name: &'a String, +} + +fn Child2<'a>(cx: Context<'a>, props: &Grandchild) -> DomTree<'a> { + cx.render(rsx! { + div { "Hello {props.name}!" } + }) +} + +#[test] +fn test_borrowed_state() {} diff --git a/packages/core/tests/create_dom.rs b/packages/core/tests/create_dom.rs index 124c87d66..b1b6639b8 100644 --- a/packages/core/tests/create_dom.rs +++ b/packages/core/tests/create_dom.rs @@ -18,7 +18,7 @@ fn new_dom(app: FC

, props: P) -> VirtualDom { #[test] fn test_original_diff() { - static APP: FC<()> = |cx| { + static APP: FC<()> = |cx, props| { cx.render(rsx! { div { div { @@ -48,7 +48,7 @@ fn test_original_diff() { #[test] fn create() { - static APP: FC<()> = |cx| { + static APP: FC<()> = |cx, props| { cx.render(rsx! { div { div { @@ -99,7 +99,7 @@ fn create() { #[test] fn create_list() { - static APP: FC<()> = |cx| { + static APP: FC<()> = |cx, props| { cx.render(rsx! { {(0..3).map(|f| rsx!{ div { "hello" @@ -139,7 +139,7 @@ fn create_list() { #[test] fn create_simple() { - static APP: FC<()> = |cx| { + static APP: FC<()> = |cx, props| { cx.render(rsx! { div {} div {} @@ -165,7 +165,7 @@ fn create_simple() { } #[test] fn create_components() { - static App: FC<()> = |cx| { + static App: FC<()> = |cx, props| { cx.render(rsx! { Child { "abc1" } Child { "abc2" } @@ -173,7 +173,7 @@ fn create_components() { }) }; - static Child: FC<()> = |cx| { + static Child: FC<()> = |cx, props| { cx.render(rsx! { h1 {} div { {cx.children()} } @@ -217,7 +217,7 @@ fn create_components() { } #[test] fn anchors() { - static App: FC<()> = |cx| { + static App: FC<()> = |cx, props| { cx.render(rsx! { {true.then(|| rsx!{ div { "hello" } })} {false.then(|| rsx!{ div { "goodbye" } })} @@ -243,7 +243,7 @@ fn anchors() { #[test] fn suspended() { - static App: FC<()> = |cx| { + static App: FC<()> = |cx, props| { let val = use_suspense( cx, || async { diff --git a/packages/core/tests/display_vdom.rs b/packages/core/tests/display_vdom.rs index e2321585b..461a9068b 100644 --- a/packages/core/tests/display_vdom.rs +++ b/packages/core/tests/display_vdom.rs @@ -15,7 +15,7 @@ const IS_LOGGING_ENABLED: bool = true; #[test] fn please_work() { - static App: FC<()> = |cx| { + static App: FC<()> = |cx, props| { cx.render(rsx! { div { hidden: "true" @@ -29,7 +29,7 @@ fn please_work() { }) }; - static Child: FC<()> = |cx| { + static Child: FC<()> = |cx, props| { cx.render(rsx! { div { "child" } }) diff --git a/packages/core/tests/eventsystem.rs b/packages/core/tests/eventsystem.rs index 9c7748e44..ccdd03810 100644 --- a/packages/core/tests/eventsystem.rs +++ b/packages/core/tests/eventsystem.rs @@ -7,7 +7,7 @@ use dioxus_html as dioxus_elements; #[async_std::test] async fn event_queue_works() { - static App: FC<()> = |cx| { + static App: FC<()> = |cx, props| { cx.render(rsx! { div { "hello world" } }) diff --git a/packages/core/tests/lifecycle.rs b/packages/core/tests/lifecycle.rs index ad5a8b0f4..6faf8d9e4 100644 --- a/packages/core/tests/lifecycle.rs +++ b/packages/core/tests/lifecycle.rs @@ -20,8 +20,8 @@ fn manual_diffing() { value: Shared<&'static str>, } - static App: FC = |cx| { - let val = cx.value.borrow(); + static App: FC = |cx, props| { + let val = props.value.borrow(); cx.render(rsx! { div { "{val}" } }) }; diff --git a/packages/core/tests/sharedstate.rs b/packages/core/tests/sharedstate.rs index a5e2b1dad..b34980b64 100644 --- a/packages/core/tests/sharedstate.rs +++ b/packages/core/tests/sharedstate.rs @@ -15,12 +15,12 @@ fn new_dom() -> TestDom { fn shared_state_test() { struct MySharedState(&'static str); - static App: FC<()> = |cx| { + static App: FC<()> = |cx, props| { cx.use_provide_state(|| MySharedState("world!")); rsx!(cx, Child {}) }; - static Child: FC<()> = |cx| { + static Child: FC<()> = |cx, props| { let shared = cx.use_consume_state::()?; rsx!(cx, "Hello, {shared.0}") }; diff --git a/packages/core/tests/vdom_rebuild.rs b/packages/core/tests/vdom_rebuild.rs index 3cd985f11..13f30a0eb 100644 --- a/packages/core/tests/vdom_rebuild.rs +++ b/packages/core/tests/vdom_rebuild.rs @@ -14,7 +14,7 @@ use dioxus_html as dioxus_elements; #[test] fn app_runs() { - static App: FC<()> = |cx| { + static App: FC<()> = |cx, props| { // cx.render(rsx!( div{"hello"} )) }; @@ -25,7 +25,7 @@ fn app_runs() { #[test] fn fragments_work() { - static App: FC<()> = |cx| { + static App: FC<()> = |cx, props| { cx.render(rsx!( div{"hello"} div{"goodbye"} @@ -39,7 +39,7 @@ fn fragments_work() { #[test] fn lists_work() { - static App: FC<()> = |cx| { + static App: FC<()> = |cx, props| { cx.render(rsx!( h1 {"hello"} {(0..6).map(|f| rsx!(span{ "{f}" }))} @@ -52,7 +52,7 @@ fn lists_work() { #[test] fn conditional_rendering() { - static App: FC<()> = |cx| { + static App: FC<()> = |cx, props| { cx.render(rsx!( h1 {"hello"} {true.then(|| rsx!(span{ "a" }))} @@ -69,13 +69,13 @@ fn conditional_rendering() { #[test] fn child_components() { - static App: FC<()> = |cx| { + static App: FC<()> = |cx, props| { cx.render(rsx!( {true.then(|| rsx!(Child { }))} {false.then(|| rsx!(Child { }))} )) }; - static Child: FC<()> = |cx| { + static Child: FC<()> = |cx, props| { cx.render(rsx!( h1 {"hello"} h1 {"goodbye"} @@ -88,7 +88,7 @@ fn child_components() { #[test] fn suspended_works() { - static App: FC<()> = |cx| { + static App: FC<()> = |cx, props| { let title = use_suspense(cx, || async { "bob" }, |cx, f| cx.render(rsx! { "{f}"})); cx.render(rsx!("hello" { title })) }; From a2c7d17b0595769f60bc1c2bbf7cbe32cec37486 Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Tue, 21 Sep 2021 13:42:52 -0400 Subject: [PATCH 23/35] feat: mvoe away from compound context --- docs/main-concepts/02-hello.md | 29 +++++++++++++----- docs/main-concepts/03-rsx.md | 22 ++++++-------- docs/main-concepts/04-hooks.md | 4 +-- docs/main-concepts/10-concurrent-mode.md | 4 +-- docs/main-concepts/11-arena-memo.md | 6 ++-- docs/main-concepts/12-signals.md | 6 ++-- examples/_examples/basic.rs | 2 +- examples/_examples/calculator.rs | 2 +- examples/_examples/children.rs | 4 +-- examples/_examples/context.rs | 2 +- examples/_examples/fragment.rs | 2 +- examples/_examples/hello.rs | 2 +- examples/_examples/infer.rs | 4 +-- examples/_examples/input.rs | 6 ++-- examples/_examples/jackjill.rs | 2 +- examples/_examples/jackjillrsx.rs | 2 +- examples/_examples/landingpage.rs | 2 +- examples/_examples/landingpage2.rs | 2 +- examples/_examples/list.rs | 2 +- examples/_examples/many.rs | 2 +- examples/_examples/model.rs | 2 +- examples/_examples/rsxt.rs | 2 +- examples/_examples/todomvc/main.rs | 2 +- examples/_examples/weather.rs | 2 +- examples/async.rs | 2 +- examples/borrowed.rs | 14 ++++----- examples/calculator.rs | 32 ++++++++++---------- examples/coroutine.rs | 4 +-- examples/examples/calculator.rs | 2 +- examples/examples/demo.rs | 4 +-- examples/examples/hifive.rs | 2 +- examples/file_explorer.rs | 2 +- examples/framework_benchmark.rs | 14 ++++----- examples/hydration.rs | 2 +- examples/manually.rs | 2 +- examples/model.rs | 28 ++++++++--------- examples/readme.rs | 2 +- examples/reducer.rs | 2 +- examples/reference/antipatterns.rs | 22 +++++++------- examples/reference/basics.rs | 6 ++-- examples/reference/children.rs | 4 +-- examples/reference/conditional_rendering.rs | 18 +++++------ examples/reference/controlled_inputs.rs | 6 ++-- examples/reference/custom_elements.rs | 2 +- examples/reference/empty.rs | 2 +- examples/reference/errorhandling.rs | 12 ++++---- examples/reference/fragments.rs | 8 ++--- examples/reference/global_css.rs | 2 +- examples/reference/inline_styles.rs | 4 +-- examples/reference/iterators.rs | 2 +- examples/reference/listener.rs | 8 ++--- examples/reference/memo.rs | 18 +++++------ examples/reference/noderefs.rs | 2 +- examples/reference/signals.rs | 2 +- examples/reference/spreadpattern.rs | 10 +++--- examples/reference/statemanagement.rs | 2 +- examples/reference/suspense.rs | 9 ++++-- examples/reference/task.rs | 2 +- examples/reference/testing.rs | 2 +- examples/reference/tostring.rs | 4 +-- examples/rsx_usage.rs | 7 +++-- examples/showcase.rs | 10 +++--- examples/slideshow.rs | 12 ++++---- examples/ssr.rs | 4 +-- examples/tailwind.rs | 12 ++++---- examples/testbed.rs | 6 ++-- examples/todomvc.rs | 5 ++- examples/web_tick.rs | 8 ++--- examples/webview.rs | 8 ++--- examples/webview_web.rs | 2 +- notes/Parity.md | 2 +- notes/SOLVEDPROBLEMS.md | 10 +++--- packages/core-macro/src/lib.rs | 2 +- packages/core-macro/src/rsxtemplate.rs | 2 +- packages/core/benches/jsframework.rs | 8 ++--- packages/core/examples/alternative.rs | 2 +- packages/core/examples/async.rs | 6 ++-- packages/core/examples/borrowed.rs | 4 +-- packages/core/examples/contextapi.rs | 2 +- packages/core/examples/fragment_from_iter.rs | 2 +- packages/core/examples/jsframework.rs | 8 ++--- packages/core/examples/vdom_usage.rs | 2 +- packages/core/src/context.rs | 8 ++--- packages/core/src/scope.rs | 8 ++--- packages/core/src/virtual_dom.rs | 10 +++--- packages/core/tests/hooks.rs | 2 +- packages/desktop/README.md | 2 +- packages/desktop/examples/test.rs | 2 +- packages/hooks/examples/lifecycle.rs | 2 +- packages/hooks/src/usecollection.rs | 2 +- packages/hooks/src/useref.rs | 2 +- packages/hooks/src/usestate.rs | 6 ++-- packages/ssr/README.md | 2 +- packages/ssr/examples/basic.rs | 2 +- packages/ssr/examples/tide.rs | 4 +-- packages/ssr/examples/tofile.rs | 12 ++++---- packages/ssr/src/lib.rs | 12 ++++---- packages/web/examples/async_web.rs | 2 +- packages/web/examples/basic.rs | 4 +-- packages/web/examples/blah.rs | 2 +- packages/web/examples/btns.rs | 4 +-- packages/web/examples/demo.rs | 12 ++++---- src/lib.rs | 4 +-- 103 files changed, 309 insertions(+), 293 deletions(-) diff --git a/docs/main-concepts/02-hello.md b/docs/main-concepts/02-hello.md index ad91849c9..c33e115e7 100644 --- a/docs/main-concepts/02-hello.md +++ b/docs/main-concepts/02-hello.md @@ -78,15 +78,16 @@ Now, let's edit our `main.rs` file: ```rust use diouxs::prelude::*; + fn main() { - dioxus::web::start(App) + dioxus::web::start(App, |c| c).unwrap(); } -fn App(cx: Context<()>) -> DomTree { +static App: FC<()> = |cx, props| { cx.render(rsx! { div { "Hello, world!" } }) -} +}; ``` Let's dissect our example a bit. @@ -97,24 +98,36 @@ This bit of code imports everything from the the `prelude` module. This brings i use diouxs::prelude::*; ``` -This bit of code starts the WASM renderer as a future (JS Promise) and then awaits it. This is very similar to the `ReactDOM.render()` method you use for a React app. We pass in the `App` function as a our app. +This bit of code starts the WASM renderer as a future (JS Promise) and then awaits it. This is very similar to the `ReactDOM.render()` method you use for a React app. We pass in the `App` function as a our app. The `|c| c` closure provides a way to configure the WebSys app, enabling things like hydration, pre-rendering, and other tweaks. The simplest configuration is just the defaults. + +Calling "start" will launch Dioxus on the web's main thread, running `wasm_bindgen_futures::block_on`. It's possible to "launch" the WebSys renderer as an async function - check the documentation for more details. ```rust fn main() { - dioxus::web::start(App) + dioxus::web::start(App, |c| c).unwrap(); } ``` -Finally, our app. Every component in Dioxus is a function that takes in a `Context` object and returns a `VNode`. +Finally, our app. Every component in Dioxus is a function that takes in `Context` and `Props` and returns an `Option`. ```rust -fn App(cx: Context<()>) -> DomTree { +static App: FC<()> = |cx, props| { cx.render(rsx! { div { "Hello, world!" } }) }; ``` +The closure `FC<()>` syntax is identical to the function syntax, but with lifetimes managed for you. In cases where props need to borrow from their parent, you will need to specify lifetimes using the function syntax: + +```rust +fn App<'a>(cx: Context<'a>, props: &()) -> DomTree<'a> { + cx.render(rsx! { + div { "Hello, world!" } + }) +} +``` + In React, you'll save data between renders with hooks. However, hooks rely on global variables which make them difficult to integrate in multi-tenant systems like server-rendering. In Dioxus, you are given an explicit `Context` object to control how the component renders and stores data. Next, we're greeted with the `rsx!` macro. This lets us add a custom DSL for declaratively building the structure of our app. The semantics of this macro are similar to that of JSX and HTML, though with a familiar Rust-y interface. The `html!` macro is also available for writing components with a JSX/HTML syntax. @@ -135,7 +148,7 @@ If we wanted to golf a bit, we can shrink our hello-world even smaller: ```rust fn main() { - dioxus::web::start(|cx| cx.render(diouxs::rsx!( div { "Hello, World!" } )) + dioxus::web::start(|cx, _| cx.render(diouxs::rsx!( div { "Hello, World!" } )) } ``` diff --git a/docs/main-concepts/03-rsx.md b/docs/main-concepts/03-rsx.md index a615eb986..c3b51baaa 100644 --- a/docs/main-concepts/03-rsx.md +++ b/docs/main-concepts/03-rsx.md @@ -12,15 +12,14 @@ You'll want to write RSX where you can, and in a future release we'll have a too ```rust #[derive(PartialEq, Props)] -struct ExampleProps { name: &str, pending: bool, count: i32 } +struct ExampleProps { name: &'static str, pending: bool, count: i32 } -fn Example(cx: Context ) -> DomTree { - let ExampleProps { name, pending, count } = cx.props; +static Example: FC = |cx, props| { cx.render(html! {

-

"Hello, {name}!"

-

"Status: {pending}!"

-

"Count {count}!"

+

"Hello, {props.name}!"

+

"Status: {props.pending}!"

+

"Count {props.count}!"

}) } @@ -35,13 +34,12 @@ The Dioxus VSCode extension will eventually provide a macro to convert a selecti It's also a bit easier on the eyes 🙂 than HTML. ```rust -fn Example(cx: Context) -> DomTree { +static Example: FC = |cx, props| { cx.render(rsx! { div { - // cx derefs to props so you can access fields directly - p {"Hello, {cx.name}!"} - p {"Status: {cx.pending}!"} - p {"Count {cx.count}!"} + p {"Hello, {props.name}!"} + p {"Status: {props.pending}!"} + p {"Count {props.count}!"} } }) } @@ -60,7 +58,7 @@ Commas are entirely optional, but might be useful to delineate between elements The `render` function provides an **extremely efficient** allocator for VNodes and text, so try not to use the `format!` macro in your components. Rust's default `ToString` methods pass through the global allocator, but all text in components is allocated inside a manually-managed Bump arena. To push you in the right direction, all text-based attributes take `std::fmt::Arguments` directly, so you'll want to reach for `format_args!` when the built-in `f-string` interpolation just doesn't cut it. ```rust -pub static Example: FC<()> = |cx| { +pub static Example: FC<()> = |cx, props|{ let text = "example"; diff --git a/docs/main-concepts/04-hooks.md b/docs/main-concepts/04-hooks.md index c8741a9fd..7979ccc6e 100644 --- a/docs/main-concepts/04-hooks.md +++ b/docs/main-concepts/04-hooks.md @@ -1,5 +1,5 @@ ```rust -fn Example(cx: &mut Context<()>) -> DomTree { +fn Example<'a>(cx: Context<'a>, props: &()) -> DomTree<'a> { let service = use_combubulator(cx); let Status { name, pending, count } = service.info(); html! { @@ -7,7 +7,7 @@ fn Example(cx: &mut Context<()>) -> DomTree {

"Hello, {name}!"

"Status: {pending}!"

"Count {count}!"

- diff --git a/docs/main-concepts/10-concurrent-mode.md b/docs/main-concepts/10-concurrent-mode.md index c14d66c34..46b3f8e52 100644 --- a/docs/main-concepts/10-concurrent-mode.md +++ b/docs/main-concepts/10-concurrent-mode.md @@ -50,8 +50,8 @@ async fn ExampleLoader(cx: Context<()>) -> Vnode { This API stores the result on the Context object, so the loaded data is taken as reference. */ let name: &Result = use_fetch_data("http://example.com/json", ()) - .place_holder(|cx| rsx!{
"loading..."
}) - .delayed_place_holder(1000, |cx| rsx!{
"still loading..."
}) + .place_holder(|cx, props|rsx!{
"loading..."
}) + .delayed_place_holder(1000, |cx, props|rsx!{
"still loading..."
}) .await; match name { diff --git a/docs/main-concepts/11-arena-memo.md b/docs/main-concepts/11-arena-memo.md index 4d6417a8b..01b01c1e1 100644 --- a/docs/main-concepts/11-arena-memo.md +++ b/docs/main-concepts/11-arena-memo.md @@ -21,9 +21,9 @@ fn test() -> DomTree { } } -static TestComponent: FC<()> = |cx| html!{
"Hello world"
}; +static TestComponent: FC<()> = |cx, props|html!{
"Hello world"
}; -static TestComponent: FC<()> = |cx| { +static TestComponent: FC<()> = |cx, props|{ let g = "BLAH"; html! {
"Hello world"
@@ -31,7 +31,7 @@ static TestComponent: FC<()> = |cx| { }; #[functional_component] -static TestComponent: FC<{ name: String }> = |cx| html! {
"Hello {name}"
}; +static TestComponent: FC<{ name: String }> = |cx, props|html! {
"Hello {name}"
}; ``` ## Why this behavior? diff --git a/docs/main-concepts/12-signals.md b/docs/main-concepts/12-signals.md index bfe6e9c3c..5000faebc 100644 --- a/docs/main-concepts/12-signals.md +++ b/docs/main-concepts/12-signals.md @@ -96,7 +96,7 @@ Sometimes you want a signal to propagate across your app, either through far-awa ```rust const TITLE: Atom = || "".to_string(); -const Provider: FC<()> = |cx| { +const Provider: FC<()> = |cx, props|{ let title = use_signal(&cx, &TITLE); rsx!(cx, input { value: title }) }; @@ -105,7 +105,7 @@ const Provider: FC<()> = |cx| { If we use the `TITLE` atom in another component, we can cause updates to flow between components without calling render or diffing either component trees: ```rust -const Receiver: FC<()> = |cx| { +const Receiver: FC<()> = |cx, props|{ let title = use_signal(&cx, &TITLE); log::info!("This will only be called once!"); rsx!(cx, @@ -132,7 +132,7 @@ Dioxus automatically understands how to use your signals when mixed with iterato ```rust const DICT: AtomFamily = |_| {}; -const List: FC<()> = |cx| { +const List: FC<()> = |cx, props|{ let dict = use_signal(&cx, &DICT); cx.render(rsx!( ul { diff --git a/examples/_examples/basic.rs b/examples/_examples/basic.rs index e70d3de5a..d1f943ec5 100644 --- a/examples/_examples/basic.rs +++ b/examples/_examples/basic.rs @@ -15,7 +15,7 @@ fn main() { wasm_bindgen_futures::spawn_local(WebsysRenderer::start(App)); } -static App: FC<()> = |cx| { +static App: FC<()> = |cx, props| { let (state, set_state) = use_state_classic(cx, || 0); cx.render(rsx! { div { diff --git a/examples/_examples/calculator.rs b/examples/_examples/calculator.rs index 0ba9ad7f8..fee49b504 100644 --- a/examples/_examples/calculator.rs +++ b/examples/_examples/calculator.rs @@ -27,7 +27,7 @@ enum Operator { Div, } -static App: FC<()> = |cx| { +static App: FC<()> = |cx, props| { let (cur_val, set_cur_val) = use_state_classic(cx, || 0.0_f64); let (operator, set_operator) = use_state_classic(cx, || None as Option); let (display_value, set_display_value) = use_state_classic(cx, || "0".to_string()); diff --git a/examples/_examples/children.rs b/examples/_examples/children.rs index 17d63df35..c30d1fd80 100644 --- a/examples/_examples/children.rs +++ b/examples/_examples/children.rs @@ -13,7 +13,7 @@ fn main() { wasm_bindgen_futures::spawn_local(WebsysRenderer::start(App)); } -static App: FC<()> = |cx| { +static App: FC<()> = |cx, props| { cx.render(rsx! { Calcier { h2 {"abc 1"} @@ -25,7 +25,7 @@ static App: FC<()> = |cx| { }) }; -static Calcier: FC<()> = |cx| { +static Calcier: FC<()> = |cx, props| { cx.render(rsx! { div { h1 { diff --git a/examples/_examples/context.rs b/examples/_examples/context.rs index dbed8057a..0658aa711 100644 --- a/examples/_examples/context.rs +++ b/examples/_examples/context.rs @@ -25,7 +25,7 @@ fn main() { #[derive(Debug)] struct CustomContext([&'static str; 3]); -pub static Example: FC<()> = |cx| { +pub static Example: FC<()> = |cx, props|{ cx.use_create_context(|| CustomContext(["Jack", "Jill", "Bob"])); cx.render(rsx! { diff --git a/examples/_examples/fragment.rs b/examples/_examples/fragment.rs index 059f57431..958fa102c 100644 --- a/examples/_examples/fragment.rs +++ b/examples/_examples/fragment.rs @@ -13,7 +13,7 @@ fn main() { wasm_bindgen_futures::spawn_local(WebsysRenderer::start(App)); } -static App: FC<()> = |cx| { +static App: FC<()> = |cx, props| { cx.render(rsx! { h2 { "abc 1" } div { diff --git a/examples/_examples/hello.rs b/examples/_examples/hello.rs index ac13aa19f..72b89a3f4 100644 --- a/examples/_examples/hello.rs +++ b/examples/_examples/hello.rs @@ -13,7 +13,7 @@ fn main() { wasm_bindgen_futures::spawn_local(WebsysRenderer::start(Example)); } -pub static Example: FC<()> = |cx| { +pub static Example: FC<()> = |cx, props| { let nodes = (0..500).map(|f| rsx! (li {"div"})); cx.render(rsx! { div { diff --git a/examples/_examples/infer.rs b/examples/_examples/infer.rs index 08de6deed..8d61a3345 100644 --- a/examples/_examples/infer.rs +++ b/examples/_examples/infer.rs @@ -14,7 +14,7 @@ fn main() { } // this is a component -pub static Example: FC<()> = |cx| { +pub static Example: FC<()> = |cx, props|{ let (event, set_event) = use_state_classic(cx, || None); let handler = move |evt| { @@ -50,7 +50,7 @@ struct ExampleProps { name: String, } -pub static Example2: FC = |cx| { +pub static Example2: FC = |cx, props|{ cx.render(rsx! { div { h1 {"hello {cx.name}"} diff --git a/examples/_examples/input.rs b/examples/_examples/input.rs index 83423c8cb..66a0fab7a 100644 --- a/examples/_examples/input.rs +++ b/examples/_examples/input.rs @@ -12,7 +12,7 @@ fn main() { wasm_bindgen_futures::spawn_local(WebsysRenderer::start(App)); } -static App: FC<()> = |cx| { +static App: FC<()> = |cx, props|{ let (val, set_val) = use_state_classic(cx, || "asd".to_string()); cx.render(rsx! { @@ -37,7 +37,7 @@ static App: FC<()> = |cx| { }) }; -pub static Example: FC<()> = |cx| { +pub static Example: FC<()> = |cx, props|{ cx.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: "container py-5 max-w-md mx-auto" @@ -50,7 +50,7 @@ pub static Example: FC<()> = |cx| { }) }; -static UserInput: FC<()> = |cx| { +static UserInput: FC<()> = |cx, props|{ let (val, set_val) = use_state_classic(cx, || "asd".to_string()); rsx! { in cx, diff --git a/examples/_examples/jackjill.rs b/examples/_examples/jackjill.rs index 932d17fdb..cb4e95c78 100644 --- a/examples/_examples/jackjill.rs +++ b/examples/_examples/jackjill.rs @@ -9,7 +9,7 @@ fn main() { wasm_bindgen_futures::spawn_local(WebsysRenderer::start(Example)) } -pub static Example: FC<()> = |cx| { +pub static Example: FC<()> = |cx, props| { let (name, set_name) = use_state_classic(cx, || "...?"); log::debug!("Running component...."); diff --git a/examples/_examples/jackjillrsx.rs b/examples/_examples/jackjillrsx.rs index fa84a03dc..fcf7c790f 100644 --- a/examples/_examples/jackjillrsx.rs +++ b/examples/_examples/jackjillrsx.rs @@ -9,7 +9,7 @@ fn main() { wasm_bindgen_futures::spawn_local(WebsysRenderer::start(Example)) } -pub static Example: FC<()> = |cx| { +pub static Example: FC<()> = |cx, props| { let (name, set_name) = use_state_classic(cx, || "...?"); cx.render(rsx! { section { class: "py-12 px-4 text-center" diff --git a/examples/_examples/landingpage.rs b/examples/_examples/landingpage.rs index a6589d7ab..2cd24f7c7 100644 --- a/examples/_examples/landingpage.rs +++ b/examples/_examples/landingpage.rs @@ -14,7 +14,7 @@ fn main() { wasm_bindgen_futures::spawn_local(WebsysRenderer::start(App)); } -static App: FC<()> = |cx| { +static App: FC<()> = |cx, props| { let (contents, set_contents) = use_state_classic(cx, || "asd"); cx.render(rsx! { diff --git a/examples/_examples/landingpage2.rs b/examples/_examples/landingpage2.rs index bf47c9f97..53ba2acdd 100644 --- a/examples/_examples/landingpage2.rs +++ b/examples/_examples/landingpage2.rs @@ -13,7 +13,7 @@ fn main() { wasm_bindgen_futures::spawn_local(WebsysRenderer::start(App)); } -static App: FC<()> = |cx| { +static App: FC<()> = |cx, props| { let (contents, set_contents) = use_state_classic(cx, || "asd"); cx.render(rsx! { diff --git a/examples/_examples/list.rs b/examples/_examples/list.rs index f8415902a..4232194b5 100644 --- a/examples/_examples/list.rs +++ b/examples/_examples/list.rs @@ -26,7 +26,7 @@ pub struct TodoItem { pub contents: String, } -static App: FC<()> = |cx| { +static App: FC<()> = |cx, props| { let (draft, set_draft) = use_state_classic(cx, || "".to_string()); let (filter, set_filter) = use_state_classic(cx, || FilterState::All); let todos = use_state(cx, || BTreeMap::::new()); diff --git a/examples/_examples/many.rs b/examples/_examples/many.rs index be4973acd..cd2027b59 100644 --- a/examples/_examples/many.rs +++ b/examples/_examples/many.rs @@ -11,7 +11,7 @@ fn main() { wasm_bindgen_futures::spawn_local(WebsysRenderer::start(Example)); } -pub static Example: FC<()> = |cx| { +pub static Example: FC<()> = |cx, props| { cx.render(rsx! { div { span { diff --git a/examples/_examples/model.rs b/examples/_examples/model.rs index 0b70003dd..41906ce92 100644 --- a/examples/_examples/model.rs +++ b/examples/_examples/model.rs @@ -31,7 +31,7 @@ fn main() { wasm_bindgen_futures::spawn_local(WebsysRenderer::start(App)); } -static App: FC<()> = |cx| { +static App: FC<()> = |cx, props| { let state = use_model(&cx, || Calculator::new()); let clear_display = state.display_value.eq("0"); diff --git a/examples/_examples/rsxt.rs b/examples/_examples/rsxt.rs index 6fb139278..f800b7e58 100644 --- a/examples/_examples/rsxt.rs +++ b/examples/_examples/rsxt.rs @@ -27,7 +27,7 @@ struct ExampleProps { initial_name: &'static str, } -pub static Example: FC = |cx| { +pub static Example: FC = |cx, props|{ let name = use_state(cx, move || cx.initial_name); cx.render(rsx! { diff --git a/examples/_examples/todomvc/main.rs b/examples/_examples/todomvc/main.rs index 7b637fcc8..56bd3789d 100644 --- a/examples/_examples/todomvc/main.rs +++ b/examples/_examples/todomvc/main.rs @@ -11,7 +11,7 @@ use dioxus_web::{prelude::*, WebsysRenderer}; static APP_STYLE: &'static str = include_str!("./style.css"); fn main() { - wasm_bindgen_futures::spawn_local(WebsysRenderer::start(|cx| { + wasm_bindgen_futures::spawn_local(WebsysRenderer::start(|cx, props| { cx.render(rsx! { div { id: "app" diff --git a/examples/_examples/weather.rs b/examples/_examples/weather.rs index f7c25c822..9a2d0b4fc 100644 --- a/examples/_examples/weather.rs +++ b/examples/_examples/weather.rs @@ -9,7 +9,7 @@ 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(|cx| { + wasm_bindgen_futures::spawn_local(WebsysRenderer::start(|cx, props| { cx.render(html! {
diff --git a/examples/async.rs b/examples/async.rs index 111102921..3a9d317c9 100644 --- a/examples/async.rs +++ b/examples/async.rs @@ -7,7 +7,7 @@ fn main() { dioxus::desktop::launch(App, |c| c).expect("faield to launch"); } -pub static App: FC<()> = |cx| { +pub static App: FC<()> = |cx, props| { let count = use_state(cx, || 0); let mut direction = use_state(cx, || 1); diff --git a/examples/borrowed.rs b/examples/borrowed.rs index 0be0c668b..912999b6a 100644 --- a/examples/borrowed.rs +++ b/examples/borrowed.rs @@ -22,7 +22,7 @@ fn main() { dioxus::desktop::launch(App, |c| c); } -fn App<'a>(cx: Context<'a, ()>) -> DomTree<'a> { +fn App<'a>(cx: Context<'a>, props: &()) -> DomTree<'a> { let text: &'a mut Vec = cx.use_hook(|_| vec![String::from("abc=def")], |f| f, |_| {}); let first = text.get_mut(0).unwrap(); @@ -45,8 +45,8 @@ impl<'a> Drop for C1Props<'a> { fn drop(&mut self) {} } -fn Child1<'a>(cx: Context<'a, C1Props>) -> DomTree<'a> { - let (left, right) = cx.text.split_once("=").unwrap(); +fn Child1<'a>(cx: Context<'a>, props: &'a C1Props) -> DomTree<'a> { + let (left, right) = props.text.split_once("=").unwrap(); cx.render(rsx! { div { @@ -66,10 +66,10 @@ impl<'a> Drop for C2Props<'a> { } } -fn Child2<'a>(cx: Context<'a, C2Props>) -> DomTree<'a> { +fn Child2<'a>(cx: Context<'a>, props: &'a C2Props) -> DomTree<'a> { cx.render(rsx! { Child3 { - text: cx.text + text: props.text } }) } @@ -79,8 +79,8 @@ struct C3Props<'a> { text: &'a str, } -fn Child3<'a>(cx: Context<'a, C3Props>) -> DomTree<'a> { +fn Child3<'a>(cx: Context<'a>, props: &C3Props) -> DomTree<'a> { cx.render(rsx! { - div { "{cx.text}"} + div { "{props.text}"} }) } diff --git a/examples/calculator.rs b/examples/calculator.rs index be96de93c..eb1adc645 100644 --- a/examples/calculator.rs +++ b/examples/calculator.rs @@ -19,7 +19,7 @@ enum Operator { Div, } -const App: FC<()> = |cx| { +const App: FC<()> = |cx, props| { 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()); @@ -70,16 +70,16 @@ const App: FC<()> = |cx| { display_value.modify().pop(); } } - 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::Num0 => input_digit(0), + KeyCode::Num1 => input_digit(1), + KeyCode::Num2 => input_digit(2), + KeyCode::Num3 => input_digit(3), + KeyCode::Num4 => input_digit(4), + KeyCode::Num5 => input_digit(5), + KeyCode::Num6 => input_digit(6), + 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)), @@ -124,11 +124,11 @@ struct CalculatorKeyProps<'a> { onclick: &'a dyn Fn(MouseEvent), } -fn CalculatorKey<'a, 'r>(cx: Context<'a, CalculatorKeyProps<'r>>) -> DomTree<'a> { +fn CalculatorKey<'a>(cx: Context<'a>, props: &'a CalculatorKeyProps) -> DomTree<'a> { cx.render(rsx! { button { - class: "calculator-key {cx.name}" - onclick: {cx.onclick} + class: "calculator-key {props.name}" + onclick: {props.onclick} {cx.children()} } }) @@ -139,10 +139,10 @@ struct CalculatorDisplayProps { val: f64, } -fn CalculatorDisplay(cx: Context) -> DomTree { +fn CalculatorDisplay<'a>(cx: Context<'a>, props: &CalculatorDisplayProps) -> DomTree<'a> { use separator::Separatable; // Todo, add float support to the num-format crate - let formatted = cx.val.separated_string(); + let formatted = props.val.separated_string(); // TODO: make it autoscaling with css cx.render(rsx! { div { class: "calculator-display" diff --git a/examples/coroutine.rs b/examples/coroutine.rs index 26386055c..ef2dfc9b9 100644 --- a/examples/coroutine.rs +++ b/examples/coroutine.rs @@ -27,7 +27,7 @@ fn main() { use dioxus::prelude::*; -static App: FC<()> = |cx| { +static App: FC<()> = |cx, props| { let p1 = use_state(cx, || 0); let p2 = use_state(cx, || 0); @@ -59,7 +59,7 @@ struct HorseyProps { pos: i32, } -static Horsey: FC = |cx| { +static Horsey: FC = |cx, props| { cx.render(rsx! { div { button { "pause" } diff --git a/examples/examples/calculator.rs b/examples/examples/calculator.rs index ebebf218b..107794164 100644 --- a/examples/examples/calculator.rs +++ b/examples/examples/calculator.rs @@ -19,7 +19,7 @@ fn main() { .expect("Webview finished"); } -static App: FC<()> = |cx| { +static App: FC<()> = |cx, props| { let state = use_model(&cx, || Calculator::new()); let clear_display = state.display_value.eq("0"); diff --git a/examples/examples/demo.rs b/examples/examples/demo.rs index 44017d2e0..bcd898055 100644 --- a/examples/examples/demo.rs +++ b/examples/examples/demo.rs @@ -17,7 +17,7 @@ fn main() { .expect("Webview finished"); } -// pub static Example: FC<()> = |cx| { +// pub static Example: FC<()> = |cx, props|{ // cx.render(html! { //
// // }) // }; -pub static Example: FC<()> = |cx| { +pub static Example: FC<()> = |cx, props| { cx.render(rsx! { div { class: "flex items-center justify-center flex-col" diff --git a/examples/examples/hifive.rs b/examples/examples/hifive.rs index 6b44ec7cf..972eff4ff 100644 --- a/examples/examples/hifive.rs +++ b/examples/examples/hifive.rs @@ -17,7 +17,7 @@ fn main() { .expect("Webview finished"); } -static App: FC<()> = |cx| { +static App: FC<()> = |cx, props| { let hifives = use_model(&cx, || 0); cx.render(rsx! { div { diff --git a/examples/file_explorer.rs b/examples/file_explorer.rs index 5d4a89b32..bc0c289d4 100644 --- a/examples/file_explorer.rs +++ b/examples/file_explorer.rs @@ -20,7 +20,7 @@ fn main() { .unwrap(); } -static App: FC<()> = |cx| { +static App: FC<()> = |cx, props| { let files = use_state(cx, || Files::new()); let file_list = files.path_names.iter().enumerate().map(|(dir_id, path)| { diff --git a/examples/framework_benchmark.rs b/examples/framework_benchmark.rs index a7ee8588d..8927198a9 100644 --- a/examples/framework_benchmark.rs +++ b/examples/framework_benchmark.rs @@ -19,7 +19,7 @@ fn main() { // We use a special immutable hashmap to make hashmap operations efficient type RowList = im_rc::HashMap, FxBuildHasher>; -static App: FC<()> = |cx| { +static App: FC<()> = |cx, props| { let items = use_state(cx, || RowList::default()); let create_rendered_rows = move |from, num| move |_| items.set(create_row_list(from, num)); @@ -92,14 +92,14 @@ struct ActionButtonProps { id: &'static str, action: F, } -fn ActionButton(cx: Context>) -> DomTree +fn ActionButton<'a, F>(cx: Context<'a>, props: &'a ActionButtonProps) -> DomTree<'a> where F: Fn(MouseEvent), { cx.render(rsx! { div { class: "col-sm-6 smallpad" - button { class:"btn btn-primary btn-block", r#type: "button", id: "{cx.id}", onclick: {&cx.action}, - "{cx.name}" + button { class:"btn btn-primary btn-block", r#type: "button", id: "{props.id}", onclick: {&props.action}, + "{props.name}" } } }) @@ -110,12 +110,12 @@ struct RowProps { row_id: usize, label: Rc, } -fn Row<'a>(cx: Context<'a, RowProps>) -> DomTree { +fn Row<'a>(cx: Context<'a>, props: &'a RowProps) -> DomTree<'a> { cx.render(rsx! { tr { - td { class:"col-md-1", "{cx.row_id}" } + td { class:"col-md-1", "{props.row_id}" } td { class:"col-md-1", onclick: move |_| { /* run onselect */ } - a { class: "lbl", "{cx.label}" } + a { class: "lbl", "{props.label}" } } td { class: "col-md-1" a { class: "remove", onclick: move |_| {/* remove */} diff --git a/examples/hydration.rs b/examples/hydration.rs index 0f083d933..a661ae842 100644 --- a/examples/hydration.rs +++ b/examples/hydration.rs @@ -19,7 +19,7 @@ fn main() { dioxus::desktop::launch(App, |c| c.with_prerendered(content)).unwrap(); } -static App: FC<()> = |cx| { +static App: FC<()> = |cx, props| { let mut val = use_state(cx, || 0); cx.render(rsx! { div { diff --git a/examples/manually.rs b/examples/manually.rs index 4e6999d96..6bf3fb85d 100644 --- a/examples/manually.rs +++ b/examples/manually.rs @@ -20,4 +20,4 @@ fn main() { dioxus_desktop::WebviewRenderer::run_with_edits(App, (), |c| c, Some(edits)).expect("failed"); } -const App: FC<()> = |cx| todo!(); +const App: FC<()> = |cx, props| todo!(); diff --git a/examples/model.rs b/examples/model.rs index ac294fd76..ee137ab1a 100644 --- a/examples/model.rs +++ b/examples/model.rs @@ -32,7 +32,7 @@ fn main() { .expect("failed to launch dioxus app"); } -static App: FC<()> = |cx| { +static App: FC<()> = |cx, props| { let state = use_state(cx, || Calculator::new()); let clear_display = state.display_value.eq("0"); @@ -79,11 +79,11 @@ struct CalculatorKeyProps<'a> { onclick: &'a dyn Fn(MouseEvent), } -fn CalculatorKey<'a, 'r>(cx: Context<'a, CalculatorKeyProps<'r>>) -> DomTree<'a> { +fn CalculatorKey<'a, 'r>(cx: Context<'a>, props: &'a CalculatorKeyProps) -> DomTree<'a> { cx.render(rsx! { button { - class: "calculator-key {cx.name}" - onclick: {cx.onclick} + class: "calculator-key {props.name}" + onclick: {props.onclick} {cx.children()} } }) @@ -174,16 +174,16 @@ impl Calculator { fn handle_keydown(&mut self, evt: KeyboardEvent) { match evt.key_code() { KeyCode::Backspace => self.backspace(), - KeyCode::_0 => self.input_digit(0), - KeyCode::_1 => self.input_digit(1), - KeyCode::_2 => self.input_digit(2), - KeyCode::_3 => self.input_digit(3), - KeyCode::_4 => self.input_digit(4), - KeyCode::_5 => self.input_digit(5), - KeyCode::_6 => self.input_digit(6), - KeyCode::_7 => self.input_digit(7), - KeyCode::_8 => self.input_digit(8), - KeyCode::_9 => self.input_digit(9), + KeyCode::Num0 => self.input_digit(0), + KeyCode::Num1 => self.input_digit(1), + KeyCode::Num2 => self.input_digit(2), + KeyCode::Num3 => self.input_digit(3), + KeyCode::Num4 => self.input_digit(4), + KeyCode::Num5 => self.input_digit(5), + KeyCode::Num6 => self.input_digit(6), + KeyCode::Num7 => self.input_digit(7), + KeyCode::Num8 => self.input_digit(8), + KeyCode::Num9 => self.input_digit(9), KeyCode::Add => self.operator = Some(Operator::Add), KeyCode::Subtract => self.operator = Some(Operator::Sub), KeyCode::Divide => self.operator = Some(Operator::Div), diff --git a/examples/readme.rs b/examples/readme.rs index 82520b163..8eb8b098f 100644 --- a/examples/readme.rs +++ b/examples/readme.rs @@ -7,7 +7,7 @@ fn main() { dioxus::desktop::launch(App, |c| c); } -static App: FC<()> = |cx| { +static App: FC<()> = |cx, props| { let mut count = use_state(cx, || 0); cx.render(rsx! { diff --git a/examples/reducer.rs b/examples/reducer.rs index 7a2bf7de2..52e3e9a92 100644 --- a/examples/reducer.rs +++ b/examples/reducer.rs @@ -11,7 +11,7 @@ fn main() { dioxus::desktop::launch(App, |c| c); } -pub static App: FC<()> = |cx| { +pub static App: FC<()> = |cx, props| { let state = use_state(cx, PlayerState::new); let is_playing = state.is_playing(); diff --git a/examples/reference/antipatterns.rs b/examples/reference/antipatterns.rs index e6eff4df7..39d70bd9f 100644 --- a/examples/reference/antipatterns.rs +++ b/examples/reference/antipatterns.rs @@ -32,14 +32,14 @@ use dioxus::prelude::*; struct NoKeysProps { data: std::collections::HashMap, } -static AntipatternNoKeys: FC = |cx| { +static AntipatternNoKeys: FC = |cx, props| { // WRONG: Make sure to add keys! rsx!(cx, ul { - {cx.data.iter().map(|(k, v)| rsx!(li { "List item: {v}" }))} + {props.data.iter().map(|(k, v)| rsx!(li { "List item: {v}" }))} }); // RIGHT: Like this: rsx!(cx, ul { - {cx.data.iter().map(|(k, v)| rsx!(li { key: "{k}", "List item: {v}" }))} + {props.data.iter().map(|(k, v)| rsx!(li { key: "{k}", "List item: {v}" }))} }) }; @@ -54,7 +54,7 @@ static AntipatternNoKeys: FC = |cx| { /// /// Only Component and Fragment nodes are susceptible to this issue. Dioxus mitigates this with components by providing /// an API for registering shared state without the ContextProvider pattern. -static AntipatternNestedFragments: FC<()> = |cx| { +static AntipatternNestedFragments: FC<()> = |cx, props| { // Try to avoid heavily nesting fragments rsx!(cx, Fragment { @@ -82,7 +82,7 @@ static AntipatternNestedFragments: FC<()> = |cx| { /// However, calling set_state will *not* update the current version of state in the component. This should be easy to /// recognize from the function signature, but Dioxus will not update the "live" version of state. Calling `set_state` /// merely places a new value in the queue and schedules the component for a future update. -static AntipaternRelyingOnSetState: FC<()> = |cx| { +static AntipaternRelyingOnSetState: FC<()> = |cx, props| { let (state, set_state) = use_state(cx, || "Hello world").classic(); set_state("New state"); // This will return false! `state` will *still* be "Hello world" @@ -99,7 +99,7 @@ static AntipaternRelyingOnSetState: FC<()> = |cx| { /// - All components must start with an uppercase character /// /// IE: the following component will be rejected when attempted to be used in the rsx! macro -static antipattern_component: FC<()> = |cx| todo!(); +static antipattern_component: FC<()> = |cx, props| todo!(); /// Antipattern: Misusing hooks /// --------------------------- @@ -120,8 +120,8 @@ static antipattern_component: FC<()> = |cx| todo!(); struct MisuedHooksProps { should_render_state: bool, } -static AntipatternMisusedHooks: FC = |cx| { - if cx.should_render_state { +static AntipatternMisusedHooks: FC = |cx, props| { + if props.should_render_state { // do not place a hook in the conditional! // prefer to move it out of the conditional let (state, set_state) = use_state(cx, || "hello world").classic(); @@ -153,7 +153,7 @@ static AntipatternMisusedHooks: FC = |cx| { /// } /// } /// }) -static _example: FC<()> = |cx| todo!(); +static _example: FC<()> = |cx, props| todo!(); /// Antipattern: publishing components and hooks with all features enabled /// ---------------------------------------------------------------------- @@ -171,9 +171,9 @@ static _example: FC<()> = |cx| todo!(); /// /// This will only include the `core` dioxus crate which is relatively slim and fast to compile and avoids target-specific /// libraries. -static __example: FC<()> = |cx| todo!(); +static __example: FC<()> = |cx, props| todo!(); -pub static Example: FC<()> = |cx| { +pub static Example: FC<()> = |cx, props| { cx.render(rsx! { AntipatternNoKeys { data: std::collections::HashMap::new() } AntipatternNestedFragments {} diff --git a/examples/reference/basics.rs b/examples/reference/basics.rs index aefc10300..f4c945c7c 100644 --- a/examples/reference/basics.rs +++ b/examples/reference/basics.rs @@ -9,7 +9,7 @@ use dioxus::prelude::*; -pub static Example: FC<()> = |cx| { +pub static Example: FC<()> = |cx, props| { cx.render(rsx! { div { Greeting { @@ -25,10 +25,10 @@ struct GreetingProps { name: &'static str, } -static Greeting: FC = |cx| { +static Greeting: FC = |cx, props| { cx.render(rsx! { div { - h1 { "Hello, {cx.name}!" } + h1 { "Hello, {props.name}!" } p { "Welcome to the Diouxs framework" } br {} {cx.children()} diff --git a/examples/reference/children.rs b/examples/reference/children.rs index dc4e67838..16e89e52e 100644 --- a/examples/reference/children.rs +++ b/examples/reference/children.rs @@ -18,7 +18,7 @@ use dioxus::prelude::*; -pub static Example: FC<()> = |cx| { +pub static Example: FC<()> = |cx, props| { cx.render(rsx! { div { Banner { @@ -31,7 +31,7 @@ pub static Example: FC<()> = |cx| { }) }; -pub static Banner: FC<()> = |cx| { +pub static Banner: FC<()> = |cx, props| { cx.render(rsx! { div { h1 { "This is a great banner!" } diff --git a/examples/reference/conditional_rendering.rs b/examples/reference/conditional_rendering.rs index 0a5360826..80be8094a 100644 --- a/examples/reference/conditional_rendering.rs +++ b/examples/reference/conditional_rendering.rs @@ -16,10 +16,10 @@ use dioxus::prelude::*; pub struct MyProps { should_show: bool, } -pub static Example0: FC = |cx| { +pub static Example0: FC = |cx, props| { cx.render(rsx! { div { - {cx.should_show.then(|| rsx!{ + {props.should_show.then(|| rsx!{ h1 { "showing the title!" } })} } @@ -39,17 +39,17 @@ pub static Example0: FC = |cx| { pub struct MyProps1 { should_show: bool, } -pub static Example1: FC = |cx| { +pub static Example1: FC = |cx, props| { cx.render(rsx! { div { // With matching - {match cx.should_show { + {match props.should_show { true => cx.render(rsx!(div {"it is true!"})), false => rsx!(cx, div {"it is false!"}), }} // or with just regular conditions - {if cx.should_show { + {if props.should_show { rsx!(cx, div {"it is true!"}) } else { rsx!(cx, div {"it is false!"}) @@ -57,7 +57,7 @@ pub static Example1: FC = |cx| { // or with optional chaining { - cx.should_show + props.should_show .then(|| rsx!(cx, div {"it is false!"})) .unwrap_or_else(|| rsx!(cx, div {"it is false!"})) } @@ -77,10 +77,10 @@ pub enum Color { pub struct MyProps2 { color: Color, } -pub static Example2: FC = |cx| { +pub static Example2: FC = |cx, props| { cx.render(rsx! { div { - {match cx.color { + {match props.color { Color::Green => rsx!(cx, div {"it is Green!"}), Color::Yellow => rsx!(cx, div {"it is Yellow!"}), Color::Red => rsx!(cx, div {"it is Red!"}), @@ -89,7 +89,7 @@ pub static Example2: FC = |cx| { }) }; -pub static Example: FC<()> = |cx| { +pub static Example: FC<()> = |cx, props| { let should_show = use_state(cx, || false); let mut color_index = use_state(cx, || 0); let color = match *color_index % 2 { diff --git a/examples/reference/controlled_inputs.rs b/examples/reference/controlled_inputs.rs index 16e37e491..bdabe83d2 100644 --- a/examples/reference/controlled_inputs.rs +++ b/examples/reference/controlled_inputs.rs @@ -1,7 +1,7 @@ use dioxus::prelude::*; fn main() {} -pub static Example: FC<()> = |cx| { +pub static Example: FC<()> = |cx, props| { cx.render(rsx! { div { @@ -10,7 +10,7 @@ pub static Example: FC<()> = |cx| { }; // A controlled component: -static ControlledSelect: FC<()> = |cx| { +static ControlledSelect: FC<()> = |cx, props| { let value = use_state(cx, || String::from("Grapefruit")); cx.render(rsx! { select { value: "{value}", onchange: move |evt| value.set(evt.value()), @@ -23,7 +23,7 @@ static ControlledSelect: FC<()> = |cx| { }; // TODO - how do uncontrolled things work? -static UncontrolledSelect: FC<()> = |cx| { +static UncontrolledSelect: FC<()> = |cx, props| { let value = use_state(cx, || String::new()); cx.render(rsx! { diff --git a/examples/reference/custom_elements.rs b/examples/reference/custom_elements.rs index 7b86430d1..053b3aea5 100644 --- a/examples/reference/custom_elements.rs +++ b/examples/reference/custom_elements.rs @@ -11,7 +11,7 @@ use dioxus::prelude::*; -pub static Example: FC<()> = |cx| { +pub static Example: FC<()> = |cx, props| { cx.render(rsx! { div { custom_element { diff --git a/examples/reference/empty.rs b/examples/reference/empty.rs index d852b5bb2..28914bd6b 100644 --- a/examples/reference/empty.rs +++ b/examples/reference/empty.rs @@ -5,4 +5,4 @@ use dioxus::prelude::*; -pub static Example: FC<()> = |cx| cx.render(rsx! { Fragment {} }); +pub static Example: FC<()> = |cx, props| cx.render(rsx! { Fragment {} }); diff --git a/examples/reference/errorhandling.rs b/examples/reference/errorhandling.rs index ba0f21a87..a79153272 100644 --- a/examples/reference/errorhandling.rs +++ b/examples/reference/errorhandling.rs @@ -23,14 +23,14 @@ fn main() {} /// This is one way to go about error handling (just toss things away with unwrap). /// However, if you get it wrong, the whole app will crash. /// This is pretty flimsy. -static App: FC<()> = |cx| { +static App: FC<()> = |cx, props| { let data = get_data().unwrap(); cx.render(rsx!( div { "{data}" } )) }; /// This is a pretty verbose way of error handling /// However, it's still pretty good since we don't panic, just fail to render anything -static App1: FC<()> = |cx| { +static App1: FC<()> = |cx, props| { let data = match get_data() { Some(data) => data, None => return None, @@ -46,7 +46,7 @@ static App1: FC<()> = |cx| { /// a user is logged in. /// /// Dioxus will throw an error in the console if the None-path is ever taken. -static App2: FC<()> = |cx| { +static App2: FC<()> = |cx, props| { let data = get_data()?; cx.render(rsx!( div { "{data}" } )) }; @@ -54,14 +54,14 @@ static App2: FC<()> = |cx| { /// This is top-tier error handling since it displays a failure state. /// /// However, the error is lacking in context. -static App3: FC<()> = |cx| match get_data() { +static App3: FC<()> = |cx, props| match get_data() { Some(data) => cx.render(rsx!( div { "{data}" } )), None => cx.render(rsx!( div { "Failed to load data :(" } )), }; /// For errors that return results, it's possible short-circuit the match-based error handling with `.ok()` which converts /// a Result into an Option and lets you -static App4: FC<()> = |cx| { +static App4: FC<()> = |cx, props| { let data = get_data_err().ok()?; cx.render(rsx!( div { "{data}" } )) }; @@ -69,7 +69,7 @@ static App4: FC<()> = |cx| { /// This is great error handling since it displays a failure state... with context! /// /// Hopefully you never need to disply a screen like this. It's rather bad taste -static App5: FC<()> = |cx| match get_data_err() { +static App5: FC<()> = |cx, props| match get_data_err() { Ok(data) => cx.render(rsx!( div { "{data}" } )), Err(c) => cx.render(rsx!( div { "Failed to load data: {c}" } )), }; diff --git a/examples/reference/fragments.rs b/examples/reference/fragments.rs index 7ebde4229..88c9dcaea 100644 --- a/examples/reference/fragments.rs +++ b/examples/reference/fragments.rs @@ -11,7 +11,7 @@ use dioxus::prelude::*; // Returning multiple elements with rsx! or html! -static App1: FC<()> = |cx| { +static App1: FC<()> = |cx, props| { cx.render(rsx! { h1 { } h2 { } @@ -20,7 +20,7 @@ static App1: FC<()> = |cx| { }; // Using the Fragment component -static App2: FC<()> = |cx| { +static App2: FC<()> = |cx, props| { cx.render(rsx! { Fragment { div {} @@ -31,7 +31,7 @@ static App2: FC<()> = |cx| { }; // Using the `fragment` method on the NodeFactory -static App3: FC<()> = |cx| { +static App3: FC<()> = |cx, props| { cx.render(LazyNodes::new(move |fac| { fac.fragment_from_iter([ fac.text(format_args!("A")), @@ -42,7 +42,7 @@ static App3: FC<()> = |cx| { })) }; -pub static Example: FC<()> = |cx| { +pub static Example: FC<()> = |cx, props| { cx.render(rsx! { App1 {} App2 {} diff --git a/examples/reference/global_css.rs b/examples/reference/global_css.rs index eb3cb73be..5e7c75a8c 100644 --- a/examples/reference/global_css.rs +++ b/examples/reference/global_css.rs @@ -19,7 +19,7 @@ h1 {color: blue;} p {color: red;} "#; -pub static Example: FC<()> = |cx| { +pub static Example: FC<()> = |cx, props| { cx.render(rsx! { head { style { "{STYLE}" } } body { diff --git a/examples/reference/inline_styles.rs b/examples/reference/inline_styles.rs index f75e2ac13..272e220c2 100644 --- a/examples/reference/inline_styles.rs +++ b/examples/reference/inline_styles.rs @@ -10,7 +10,7 @@ use dioxus::prelude::*; -pub static Example: FC<()> = |cx| { +pub static Example: FC<()> = |cx, props| { cx.render(rsx! { head { style: { background_color: "powderblue" } @@ -29,7 +29,7 @@ pub static Example: FC<()> = |cx| { // .... technically the rsx! macro is slightly broken at the moment and alows styles not wrapped in style {} // I haven't noticed any name collisions yet, and am tentatively leaving this behavior in.. // Don't rely on it. -static Example2: FC<()> = |cx| { +static Example2: FC<()> = |cx, props| { cx.render(rsx! { div { color: "red" "hello world!" diff --git a/examples/reference/iterators.rs b/examples/reference/iterators.rs index 9067bb46c..ed4fdfd13 100644 --- a/examples/reference/iterators.rs +++ b/examples/reference/iterators.rs @@ -12,7 +12,7 @@ use dioxus::prelude::*; -pub static Example: FC<()> = |cx| { +pub static Example: FC<()> = |cx, props| { let example_data = use_state(cx, || 0); let v = (0..10).map(|f| { diff --git a/examples/reference/listener.rs b/examples/reference/listener.rs index 269811a02..59d180263 100644 --- a/examples/reference/listener.rs +++ b/examples/reference/listener.rs @@ -7,7 +7,7 @@ use dioxus::prelude::*; -pub static Example: FC<()> = |cx| { +pub static Example: FC<()> = |cx, props| { cx.render(rsx! { ButtonList {} NonUpdatingEvents {} @@ -16,7 +16,7 @@ pub static Example: FC<()> = |cx| { }; /// We can use `set_name` in multiple closures; the closures automatically *copy* the reference to set_name. -static ButtonList: FC<()> = |cx| { +static ButtonList: FC<()> = |cx, props| { let name = use_state(cx, || "...?"); let names = ["jack", "jill", "john", "jane"] @@ -33,7 +33,7 @@ static ButtonList: FC<()> = |cx| { /// This shows how listeners may be without a visible change in the display. /// Check the console. -static NonUpdatingEvents: FC<()> = |cx| { +static NonUpdatingEvents: FC<()> = |cx, props| { rsx!(cx, div { button { onclick: move |_| log::info!("Did not cause any updates!") @@ -42,7 +42,7 @@ static NonUpdatingEvents: FC<()> = |cx| { }) }; -static DisablePropogation: FC<()> = |cx| { +static DisablePropogation: FC<()> = |cx, props| { rsx!(cx, div { onclick: move |_| log::info!("event propogated to the div!") diff --git a/examples/reference/memo.rs b/examples/reference/memo.rs index b9b1985d5..40aa54191 100644 --- a/examples/reference/memo.rs +++ b/examples/reference/memo.rs @@ -21,7 +21,7 @@ use dioxus::prelude::*; // By default, components with no props are always memoized. // A props of () is considered empty. -pub static Example: FC<()> = |cx| { +pub static Example: FC<()> = |cx, props| { cx.render(rsx! { div { "100% memoized!" } }) @@ -35,9 +35,9 @@ pub struct MyProps1 { name: String, } -pub static Example1: FC = |cx| { +pub static Example1: FC = |cx, props| { cx.render(rsx! { - div { "100% memoized! {cx.name}" } + div { "100% memoized! {props.name}" } }) }; @@ -49,9 +49,9 @@ pub struct MyProps2 { name: std::rc::Rc, } -pub static Example2: FC = |cx| { +pub static Example2: FC = |cx, props| { cx.render(rsx! { - div { "100% memoized! {cx.name}" } + div { "100% memoized! {props.name}" } }) }; @@ -63,9 +63,9 @@ pub struct MyProps3<'a> { // We need to manually specify a lifetime that ensures props and scope (the component's state) share the same lifetime. // Using the `pub static Example: FC<()>` pattern _will_ specify a lifetime, but that lifetime will be static which might // not exactly be what you want -fn Example3<'a>(cx: Context<'a, MyProps3<'a>>) -> DomTree { +fn Example3<'a>(cx: Context<'a>, props: &'a MyProps3) -> DomTree<'a> { cx.render(rsx! { - div { "Not memoized! {cx.name}" } + div { "Not memoized! {props.name}" } }) } @@ -77,8 +77,8 @@ pub struct MyProps4<'a> { } // We need to manually specify a lifetime that ensures props and scope (the component's state) share the same lifetime. -fn Example4<'a>(cx: Context<'a, MyProps4<'a>>) -> DomTree { +fn Example4<'a>(cx: Context<'a>, props: &'a MyProps4) -> DomTree<'a> { cx.render(rsx! { - div { "Not memoized!", onclick: move |_| (cx.onhandle)() } + div { "Not memoized!", onclick: move |_| (props.onhandle)() } }) } diff --git a/examples/reference/noderefs.rs b/examples/reference/noderefs.rs index 195210cfd..8d22ee5b4 100644 --- a/examples/reference/noderefs.rs +++ b/examples/reference/noderefs.rs @@ -1,7 +1,7 @@ use dioxus::prelude::*; fn main() {} -pub static Example: FC<()> = |cx| { +pub static Example: FC<()> = |cx, props| { let p = 10; cx.render(rsx! { diff --git a/examples/reference/signals.rs b/examples/reference/signals.rs index fdab747b6..2e69d8e66 100644 --- a/examples/reference/signals.rs +++ b/examples/reference/signals.rs @@ -1,7 +1,7 @@ use dioxus::prelude::*; fn main() {} -pub static Example: FC<()> = |cx| { +pub static Example: FC<()> = |cx, props| { cx.render(rsx! { div { diff --git a/examples/reference/spreadpattern.rs b/examples/reference/spreadpattern.rs index 4faddf900..770cf427c 100644 --- a/examples/reference/spreadpattern.rs +++ b/examples/reference/spreadpattern.rs @@ -9,7 +9,7 @@ use dioxus::prelude::*; -pub static Example: FC<()> = |cx| { +pub static Example: FC<()> = |cx, props| { let props = MyProps { count: 0, live: true, @@ -27,12 +27,12 @@ pub struct MyProps { name: &'static str, } -pub static Example1: FC = |cx| { +pub static Example1: FC = |cx, MyProps { count, live, name }| { cx.render(rsx! { div { - h1 { "Hello, {cx.name}"} - h3 {"Are we alive? {cx.live}"} - p {"Count is {cx.count}"} + h1 { "Hello, {name}"} + h3 {"Are we alive? {live}"} + p {"Count is {count}"} { cx.children() } } }) diff --git a/examples/reference/statemanagement.rs b/examples/reference/statemanagement.rs index fdab747b6..2e69d8e66 100644 --- a/examples/reference/statemanagement.rs +++ b/examples/reference/statemanagement.rs @@ -1,7 +1,7 @@ use dioxus::prelude::*; fn main() {} -pub static Example: FC<()> = |cx| { +pub static Example: FC<()> = |cx, props| { cx.render(rsx! { div { diff --git a/examples/reference/suspense.rs b/examples/reference/suspense.rs index c0d8d54ec..f5431edf6 100644 --- a/examples/reference/suspense.rs +++ b/examples/reference/suspense.rs @@ -14,12 +14,17 @@ struct DogApi { } const ENDPOINT: &str = "https://dog.ceo/api/breeds/image/random"; -pub static Example: FC<()> = |cx| { +pub static Example: FC<()> = |cx, props| { let doggo = use_suspense( cx, || surf::get(ENDPOINT).recv_json::(), |cx, res| match res { - Ok(res) => rsx!(cx, img { src: "{res.message}" }), + Ok(res) => rsx!( + cx, + img { + src: "{res.message}" + } + ), Err(_) => rsx!(cx, div { "No doggos for you :(" }), }, ); diff --git a/examples/reference/task.rs b/examples/reference/task.rs index 8f1e61e0a..a5e877bc6 100644 --- a/examples/reference/task.rs +++ b/examples/reference/task.rs @@ -24,7 +24,7 @@ use dioxus::prelude::*; -pub static Example: FC<()> = |cx| { +pub static Example: FC<()> = |cx, props| { let count = use_state(cx, || 0); let mut direction = use_state(cx, || 1); diff --git a/examples/reference/testing.rs b/examples/reference/testing.rs index d59e9619a..587ce19cc 100644 --- a/examples/reference/testing.rs +++ b/examples/reference/testing.rs @@ -1,6 +1,6 @@ use dioxus::prelude::*; -pub static Example: FC<()> = |cx| { +pub static Example: FC<()> = |cx, props| { cx.render(rsx! { div { diff --git a/examples/reference/tostring.rs b/examples/reference/tostring.rs index 02b1daf81..a04a165da 100644 --- a/examples/reference/tostring.rs +++ b/examples/reference/tostring.rs @@ -1,7 +1,7 @@ use dioxus::prelude::*; use dioxus::ssr; -pub static Example: FC<()> = |cx| { +pub static Example: FC<()> = |cx, props| { let as_string = use_state(cx, || { // Currently, SSR is only supported for whole VirtualDOMs // This is an easy/low hanging fruit to improve upon @@ -15,7 +15,7 @@ pub static Example: FC<()> = |cx| { }) }; -static SomeApp: FC<()> = |cx| { +static SomeApp: FC<()> = |cx, props| { cx.render(rsx! { div { style: {background_color: "blue"} h1 {"Some amazing app or component"} diff --git a/examples/rsx_usage.rs b/examples/rsx_usage.rs index 5a1227f75..0c2569880 100644 --- a/examples/rsx_usage.rs +++ b/examples/rsx_usage.rs @@ -49,7 +49,7 @@ const NONE_ELEMENT: Option<()> = None; use baller::Baller; use dioxus::prelude::*; -pub static Example: FC<()> = |cx| { +pub static Example: FC<()> = |cx, props| { let formatting = "formatting!"; let formatting_tuple = ("a", "b"); let lazy_fmt = format_args!("lazily formatted text"); @@ -180,10 +180,11 @@ pub static Example: FC<()> = |cx| { mod baller { use super::*; + #[derive(PartialEq, Props)] pub struct BallerProps {} /// This component totally balls - pub fn Baller(cx: Context<()>) -> DomTree { + pub fn Baller<'a>(cx: Context<'a>, props: &BallerProps) -> DomTree<'a> { todo!() } } @@ -194,7 +195,7 @@ pub struct TallerProps { } /// This component is taller than most :) -pub fn Taller(cx: Context) -> DomTree { +pub fn Taller<'a>(cx: Context<'a>, props: &'a TallerProps) -> DomTree<'a> { let b = true; todo!() } diff --git a/examples/showcase.rs b/examples/showcase.rs index dd9370fd6..ca3cc469c 100644 --- a/examples/showcase.rs +++ b/examples/showcase.rs @@ -11,7 +11,7 @@ fn main() {} use dioxus::prelude::*; use std::rc::Rc; -static App: FC<()> = |cx| { +static App: FC<()> = |cx, props| { let (selection, set_selection) = use_state(cx, || None as Option).classic(); let body = match selection { @@ -33,7 +33,7 @@ struct ScrollSelectorProps<'a> { onselect: &'a dyn Fn(Option), } -fn ScrollSelector<'a>(cx: Context<'a, ScrollSelectorProps>) -> DomTree<'a> { +fn ScrollSelector<'a>(cx: Context<'a>, props: &'a ScrollSelectorProps) -> DomTree<'a> { let selection_list = (&REFERENCES).iter().enumerate().map(|(id, _)| { rsx! { li { @@ -47,7 +47,7 @@ fn ScrollSelector<'a>(cx: Context<'a, ScrollSelectorProps>) -> DomTree<'a> { ul { {selection_list} button { - onclick: move |_| (cx.onselect)(Some(10)) + onclick: move |_| (props.onselect)(Some(10)) } } } @@ -59,8 +59,8 @@ struct ReferenceItemProps { selected: usize, } -static ReferenceItem: FC = |cx| { - let (caller, name, code) = REFERENCES[cx.selected]; +static ReferenceItem: FC = |cx, props| { + let (caller, name, code) = REFERENCES[props.selected]; // Create the component using the factory API directly let caller_node = LazyNodes::new(move |f| f.component(caller, (), None, &[])); diff --git a/examples/slideshow.rs b/examples/slideshow.rs index 97391c033..80154b4ac 100644 --- a/examples/slideshow.rs +++ b/examples/slideshow.rs @@ -15,7 +15,7 @@ fn main() { dioxus::desktop::launch(App, |c| c); } -static App: FC<()> = |cx| { +static App: FC<()> = |cx, props| { let slides = use_state(cx, SlideController::new); let slide = match slides.slide_id { @@ -70,7 +70,7 @@ impl SlideController { } } -const Title: FC<()> = |cx| { +const Title: FC<()> = |cx, props| { cx.render(rsx! { div { h1 { "Title" } @@ -78,7 +78,7 @@ const Title: FC<()> = |cx| { } }) }; -const Slide1: FC<()> = |cx| { +const Slide1: FC<()> = |cx, props| { cx.render(rsx! { div { h1 { "Slide1" } @@ -86,7 +86,7 @@ const Slide1: FC<()> = |cx| { } }) }; -const Slide2: FC<()> = |cx| { +const Slide2: FC<()> = |cx, props| { cx.render(rsx! { div { h1 { "Slide2" } @@ -94,7 +94,7 @@ const Slide2: FC<()> = |cx| { } }) }; -const Slide3: FC<()> = |cx| { +const Slide3: FC<()> = |cx, props| { cx.render(rsx! { div { h1 { "Slide3" } @@ -102,7 +102,7 @@ const Slide3: FC<()> = |cx| { } }) }; -const End: FC<()> = |cx| { +const End: FC<()> = |cx, props| { cx.render(rsx! { div { h1 { "End" } diff --git a/examples/ssr.rs b/examples/ssr.rs index 9a74031d4..b5d29844d 100644 --- a/examples/ssr.rs +++ b/examples/ssr.rs @@ -9,7 +9,7 @@ fn main() { println!("{}", ssr::render_vdom(&vdom, |c| c)); } -static App: FC<()> = |cx| { +static App: FC<()> = |cx, props| { cx.render(rsx!( div { h1 { "Title" } @@ -21,6 +21,6 @@ static App: FC<()> = |cx| { struct MyProps<'a> { text: &'a str, } -fn App2<'a>(cx: Context<'a, MyProps>) -> DomTree<'a> { +fn App2<'a>(cx: Context<'a>, props: &'a MyProps) -> DomTree<'a> { None } diff --git a/examples/tailwind.rs b/examples/tailwind.rs index c4149faeb..406f56613 100644 --- a/examples/tailwind.rs +++ b/examples/tailwind.rs @@ -14,7 +14,7 @@ fn main() { const STYLE: &str = "body {overflow:hidden;}"; -pub static App: FC<()> = |cx| { +pub static App: FC<()> = |cx, props| { cx.render(rsx!( div { class: "overflow-hidden" style { "{STYLE}" } @@ -30,7 +30,7 @@ pub static App: FC<()> = |cx| { )) }; -pub static Header: FC<()> = |cx| { +pub static Header: FC<()> = |cx, props| { cx.render(rsx! { div { header { class: "text-gray-400 bg-gray-900 body-font" @@ -56,7 +56,7 @@ pub static Header: FC<()> = |cx| { }) }; -pub static Hero: FC<()> = |cx| { +pub static Hero: FC<()> = |cx, props| { // cx.render(rsx! { section{ class: "text-gray-400 bg-gray-900 body-font" @@ -94,7 +94,7 @@ pub static Hero: FC<()> = |cx| { } }) }; -pub static Entry: FC<()> = |cx| { +pub static Entry: FC<()> = |cx, props| { // cx.render(rsx! { section{ class: "text-gray-400 bg-gray-900 body-font" @@ -107,7 +107,7 @@ pub static Entry: FC<()> = |cx| { }) }; -pub static StacksIcon: FC<()> = |cx| { +pub static StacksIcon: FC<()> = |cx, props| { cx.render(rsx!( svg { // xmlns: "http://www.w3.org/2000/svg" @@ -122,7 +122,7 @@ pub static StacksIcon: FC<()> = |cx| { } )) }; -pub static RightArrowIcon: FC<()> = |cx| { +pub static RightArrowIcon: FC<()> = |cx, props| { cx.render(rsx!( svg { fill: "none" diff --git a/examples/testbed.rs b/examples/testbed.rs index 1eadcac53..63d76659c 100644 --- a/examples/testbed.rs +++ b/examples/testbed.rs @@ -17,7 +17,7 @@ h1 {color: blue;} p {color: red;} "#; -const Example: FC<()> = |cx| { +const Example: FC<()> = |cx, props| { cx.render(rsx! { Fragment { Fragment { @@ -38,7 +38,7 @@ const Example: FC<()> = |cx| { }) }; -const Child: FC<()> = |cx| { +const Child: FC<()> = |cx, props| { cx.render(rsx!( h1 {"1" } h1 {"2" } @@ -48,7 +48,7 @@ const Child: FC<()> = |cx| { }; // this is a bad case that hurts our subtree memoization :( -const AbTest: FC<()> = |cx| { +const AbTest: FC<()> = |cx, props| { if 1 == 2 { cx.render(rsx!( h1 {"1"} diff --git a/examples/todomvc.rs b/examples/todomvc.rs index 5add6a446..d5d961ba0 100644 --- a/examples/todomvc.rs +++ b/examples/todomvc.rs @@ -22,7 +22,7 @@ pub struct TodoItem { } const STYLE: &str = include_str!("./_examples/todomvc/style.css"); -const App: FC<()> = |cx| { +const App: FC<()> = |cx, props| { let draft = use_state(cx, || "".to_string()); let todos = use_state(cx, || HashMap::>::new()); let filter = use_state(cx, || FilterState::All); @@ -87,8 +87,7 @@ pub struct TodoEntryProps { todo: std::rc::Rc, } -pub fn TodoEntry(cx: Context) -> DomTree { - let TodoEntryProps { todo } = *cx; +pub fn TodoEntry<'a>(cx: Context<'a>, TodoEntryProps { todo }: &'a TodoEntryProps) -> DomTree<'a> { let is_editing = use_state(cx, || false); let contents = ""; diff --git a/examples/web_tick.rs b/examples/web_tick.rs index e82f131ec..82fe47db4 100644 --- a/examples/web_tick.rs +++ b/examples/web_tick.rs @@ -19,7 +19,7 @@ fn main() { dioxus::web::launch(App, |c| c); } -static App: FC<()> = |cx| { +static App: FC<()> = |cx, props| { let mut rng = SmallRng::from_entropy(); let rows = (0..1_000).map(|f| { let label = Label::new(&mut rng); @@ -45,11 +45,11 @@ struct RowProps { row_id: usize, label: Label, } -fn Row<'a>(cx: Context<'a, RowProps>) -> DomTree { - let [adj, col, noun] = cx.label.0; +fn Row<'a>(cx: Context<'a>, props: &'a RowProps) -> DomTree<'a> { + let [adj, col, noun] = props.label.0; cx.render(rsx! { tr { - td { class:"col-md-1", "{cx.row_id}" } + td { class:"col-md-1", "{props.row_id}" } td { class:"col-md-1", onclick: move |_| { /* run onselect */ } a { class: "lbl", "{adj}" "{col}" "{noun}" } } diff --git a/examples/webview.rs b/examples/webview.rs index 8178684de..ee74970dc 100644 --- a/examples/webview.rs +++ b/examples/webview.rs @@ -17,7 +17,7 @@ fn main() -> anyhow::Result<()> { dioxus::desktop::launch(App, |c| c) } -static App: FC<()> = |cx| { +static App: FC<()> = |cx, props| { let state = use_state(cx, || String::from("hello")); let clear_text = state == "hello"; @@ -37,11 +37,11 @@ struct CalculatorKeyProps<'a> { onclick: &'a dyn Fn(MouseEvent), } -fn CalculatorKey<'a, 'r>(cx: Context<'a, CalculatorKeyProps<'r>>) -> DomTree<'a> { +fn CalculatorKey<'a>(cx: Context<'a>, props: &'a CalculatorKeyProps) -> DomTree<'a> { cx.render(rsx! { button { - class: "calculator-key {cx.name}" - onclick: {cx.onclick} + class: "calculator-key {props.name}" + onclick: {props.onclick} {cx.children()} } }) diff --git a/examples/webview_web.rs b/examples/webview_web.rs index b897ffb5a..9ff57388d 100644 --- a/examples/webview_web.rs +++ b/examples/webview_web.rs @@ -16,7 +16,7 @@ fn main() { dioxus::web::launch(App, |c| c); } -static App: FC<()> = |cx| { +static App: FC<()> = |cx, props| { let mut count = use_state(cx, || 0); cx.render(rsx! { diff --git a/notes/Parity.md b/notes/Parity.md index 11655c2e7..5cc0851c9 100644 --- a/notes/Parity.md +++ b/notes/Parity.md @@ -12,7 +12,7 @@ https://github.com/rustwasm/gloo For example, resize observer would function like this: ```rust -pub static Example: FC<()> = |cx| { +pub static Example: FC<()> = |cx, props|{ let observer = use_resize_observer(); cx.render(rsx!( diff --git a/notes/SOLVEDPROBLEMS.md b/notes/SOLVEDPROBLEMS.md index 7f501822e..377d6f221 100644 --- a/notes/SOLVEDPROBLEMS.md +++ b/notes/SOLVEDPROBLEMS.md @@ -153,13 +153,13 @@ Notice that LiveComponent receivers (the client-side interpretation of a LiveCom The `VNodeTree` type is a very special type that allows VNodes to be created using a pluggable allocator. The html! macro creates something that looks like: ```rust -pub static Example: FC<()> = |cx| { +pub static Example: FC<()> = |cx, props|{ html! {
"blah"
} }; // expands to... -pub static Example: FC<()> = |cx| { +pub static Example: FC<()> = |cx, props|{ // This function converts a Fn(allocator) -> DomTree closure to a VNode struct that will later be evaluated. html_macro_to_vnodetree(move |allocator| { let mut node0 = allocator.alloc(VElement::div); @@ -313,7 +313,7 @@ Here's how react does it: Any "dirty" node causes an entire subtree render. Calling "setState" at the very top will cascade all the way down. This is particularly bad for this component design: ```rust -static APP: FC<()> = |cx| { +static APP: FC<()> = |cx, props|{ let title = use_context(Title); cx.render(html!{
@@ -334,7 +334,7 @@ static APP: FC<()> = |cx| {
}) }; -static HEAVY_LIST: FC<()> = |cx| { +static HEAVY_LIST: FC<()> = |cx, props|{ cx.render({ {0.100.map(i => )} }) @@ -378,7 +378,7 @@ struct Props { } -static Component: FC = |cx| { +static Component: FC = |cx, props|{ } ``` diff --git a/packages/core-macro/src/lib.rs b/packages/core-macro/src/lib.rs index 6e3c1f537..f4763009d 100644 --- a/packages/core-macro/src/lib.rs +++ b/packages/core-macro/src/lib.rs @@ -30,7 +30,7 @@ pub fn derive_typed_builder(input: proc_macro::TokenStream) -> proc_macro::Token /// /// ## Complete Reference Guide: /// ``` -/// const Example: FC<()> = |cx| { +/// const Example: FC<()> = |cx, props|{ /// let formatting = "formatting!"; /// let formatting_tuple = ("a", "b"); /// let lazy_fmt = format_args!("lazily formatted text"); diff --git a/packages/core-macro/src/rsxtemplate.rs b/packages/core-macro/src/rsxtemplate.rs index 9222d7117..1f712997d 100644 --- a/packages/core-macro/src/rsxtemplate.rs +++ b/packages/core-macro/src/rsxtemplate.rs @@ -72,7 +72,7 @@ impl ToTokens for RsxTemplate { // // create a lazy tree that accepts a bump allocator // let final_tokens = quote! { - // dioxus::prelude::LazyNodes::new(move |cx| { + // dioxus::prelude::LazyNodes::new(move |cx, props|{ // let bump = &cx.bump(); // #new_toks diff --git a/packages/core/benches/jsframework.rs b/packages/core/benches/jsframework.rs index 294a9a952..f744e6070 100644 --- a/packages/core/benches/jsframework.rs +++ b/packages/core/benches/jsframework.rs @@ -22,7 +22,7 @@ criterion_group!(mbenches, create_rows); criterion_main!(mbenches); fn create_rows(c: &mut Criterion) { - static App: FC<()> = |cx| { + static App: FC<()> = |cx, props| { let mut rng = SmallRng::from_entropy(); let rows = (0..10_000).map(|f| { let label = Label::new(&mut rng); @@ -56,11 +56,11 @@ struct RowProps { row_id: usize, label: Label, } -fn Row<'a>(cx: Context<'a, RowProps>) -> DomTree { - let [adj, col, noun] = cx.label.0; +fn Row<'a>(cx: Context<'a>, props: &RowProps) -> DomTree<'a> { + let [adj, col, noun] = props.label.0; cx.render(rsx! { tr { - td { class:"col-md-1", "{cx.row_id}" } + td { class:"col-md-1", "{props.row_id}" } td { class:"col-md-1", onclick: move |_| { /* run onselect */ } a { class: "lbl", "{adj}" "{col}" "{noun}" } } diff --git a/packages/core/examples/alternative.rs b/packages/core/examples/alternative.rs index 95a8469ff..02bebb43d 100644 --- a/packages/core/examples/alternative.rs +++ b/packages/core/examples/alternative.rs @@ -2,7 +2,7 @@ use dioxus_core::prelude::*; fn main() {} -pub static Example: FC<()> = |cx| { +pub static Example: FC<()> = |cx, props| { let list = (0..10).map(|_f| LazyNodes::new(move |_f| todo!())); cx.render(LazyNodes::new(move |cx| { diff --git a/packages/core/examples/async.rs b/packages/core/examples/async.rs index 73a83b456..a60dfb0f4 100644 --- a/packages/core/examples/async.rs +++ b/packages/core/examples/async.rs @@ -2,7 +2,7 @@ use dioxus_core::prelude::*; fn main() {} -const App: FC<()> = |cx| { +const App: FC<()> = |cx, props| { // create a new future let _fut = cx.use_hook( |_| { @@ -20,7 +20,7 @@ const App: FC<()> = |cx| { todo!() }; -const Task: FC<()> = |cx| { +const Task: FC<()> = |cx, props| { let (_task, _res) = use_task(cx, || async { true }); // task.pause(); // task.restart(); @@ -34,6 +34,6 @@ const Task: FC<()> = |cx| { todo!() }; -fn use_mut(_cx: Context

, _f: impl FnOnce() -> T) -> &mut T { +fn use_mut(_cx: Context, _f: impl FnOnce() -> T) -> &mut T { todo!() } diff --git a/packages/core/examples/borrowed.rs b/packages/core/examples/borrowed.rs index e30447548..a11f55225 100644 --- a/packages/core/examples/borrowed.rs +++ b/packages/core/examples/borrowed.rs @@ -20,7 +20,7 @@ struct ListItem { age: u32, } -fn app(cx: Context) -> DomTree { +fn app<'a>(cx: Context<'a>, props: &AppProps) -> DomTree<'a> { // let (val, set_val) = use_state_classic(cx, || 0); cx.render(LazyNodes::new(move |_nodecx| { @@ -56,7 +56,7 @@ struct ChildProps { item_handler: Rc, } -fn ChildItem<'a>(cx: Context<'a, ChildProps>) -> DomTree { +fn ChildItem<'a>(cx: Context<'a>, props: &'a ChildProps) -> DomTree<'a> { cx.render(LazyNodes::new(move |__cx| todo!())) } diff --git a/packages/core/examples/contextapi.rs b/packages/core/examples/contextapi.rs index 61dc79837..6c90a60c7 100644 --- a/packages/core/examples/contextapi.rs +++ b/packages/core/examples/contextapi.rs @@ -6,7 +6,7 @@ struct SomeContext { } #[allow(unused)] -static Example: FC<()> = |cpt| { +static Example: FC<()> = |cx, props| { todo!() // let value = cx.use_context(|c: &SomeContext| c.items.last().unwrap()); diff --git a/packages/core/examples/fragment_from_iter.rs b/packages/core/examples/fragment_from_iter.rs index 58c4df70f..ae7d761e8 100644 --- a/packages/core/examples/fragment_from_iter.rs +++ b/packages/core/examples/fragment_from_iter.rs @@ -2,7 +2,7 @@ use dioxus_core::prelude::*; fn main() {} -fn app(cx: Context<()>) -> DomTree { +fn app<'a>(cx: Context<'a>, props: &()) -> DomTree<'a> { let vak = use_suspense( cx, || async {}, diff --git a/packages/core/examples/jsframework.rs b/packages/core/examples/jsframework.rs index 79400afd1..902869f43 100644 --- a/packages/core/examples/jsframework.rs +++ b/packages/core/examples/jsframework.rs @@ -10,7 +10,7 @@ fn main() { assert!(g.edits.len() > 1); } -static App: FC<()> = |cx| { +static App: FC<()> = |cx, props| { let mut rng = SmallRng::from_entropy(); let rows = (0..10_000).map(|f| { let label = Label::new(&mut rng); @@ -35,12 +35,12 @@ struct RowProps { row_id: usize, label: Label, } -fn Row<'a>(cx: Context<'a, RowProps>) -> DomTree { +fn Row<'a>(cx: Context<'a>, props: &'a RowProps) -> DomTree<'a> { cx.render(rsx! { tr { - td { class:"col-md-1", "{cx.row_id}" } + td { class:"col-md-1", "{props.row_id}" } td { class:"col-md-1", onclick: move |_| { /* run onselect */ } - a { class: "lbl", "{cx.label}" } + a { class: "lbl", "{props.label}" } } td { class: "col-md-1" a { class: "remove", onclick: move |_| {/* remove */} diff --git a/packages/core/examples/vdom_usage.rs b/packages/core/examples/vdom_usage.rs index c5d643df5..1793b030b 100644 --- a/packages/core/examples/vdom_usage.rs +++ b/packages/core/examples/vdom_usage.rs @@ -4,7 +4,7 @@ use dioxus_core::prelude::*; #[async_std::main] async fn main() { - static App: FC<()> = |cx| cx.render(LazyNodes::new(|f| f.text(format_args!("hello")))); + static App: FC<()> = |cx, props|cx.render(LazyNodes::new(|f| f.text(format_args!("hello")))); let mut dom = VirtualDom::new(App); diff --git a/packages/core/src/context.rs b/packages/core/src/context.rs index 65b3d9710..991352c5f 100644 --- a/packages/core/src/context.rs +++ b/packages/core/src/context.rs @@ -68,7 +68,7 @@ impl<'src> Context<'src> { /// ## Example /// /// ```rust - /// const App: FC<()> = |cx| { + /// const App: FC<()> = |cx, props|{ /// cx.render(rsx!{ /// CustomCard { /// h1 {} @@ -77,7 +77,7 @@ impl<'src> Context<'src> { /// }) /// } /// - /// const CustomCard: FC<()> = |cx| { + /// const CustomCard: FC<()> = |cx, props|{ /// cx.render(rsx!{ /// div { /// h1 {"Title card"} @@ -176,12 +176,12 @@ impl<'src> Context<'src> { /// ``` /// struct SharedState(&'static str); /// - /// static App: FC<()> = |cx| { + /// static App: FC<()> = |cx, props|{ /// cx.use_provide_state(|| SharedState("world")); /// rsx!(cx, Child {}) /// } /// - /// static Child: FC<()> = |cx| { + /// static Child: FC<()> = |cx, props|{ /// let state = cx.use_consume_state::(); /// rsx!(cx, div { "hello {state.0}" }) /// } diff --git a/packages/core/src/scope.rs b/packages/core/src/scope.rs index ff5535607..b696a96cd 100644 --- a/packages/core/src/scope.rs +++ b/packages/core/src/scope.rs @@ -58,7 +58,7 @@ impl Scope { /// /// # Example /// ```rust - /// let mut dom = VirtualDom::new(|cx| cx.render(rsx!{ div {} })); + /// let mut dom = VirtualDom::new(|cx, props|cx.render(rsx!{ div {} })); /// dom.rebuild(); /// /// let base = dom.base_scope(); @@ -78,7 +78,7 @@ impl Scope { /// # Example /// /// ```rust - /// let mut dom = VirtualDom::new(|cx| cx.render(rsx!{ div {} })); + /// let mut dom = VirtualDom::new(|cx, props|cx.render(rsx!{ div {} })); /// dom.rebuild(); /// /// let base = dom.base_scope(); @@ -98,7 +98,7 @@ impl Scope { /// # Example /// /// ```rust - /// let mut dom = VirtualDom::new(|cx| cx.render(rsx!{ div {} })); + /// let mut dom = VirtualDom::new(|cx, props|cx.render(rsx!{ div {} })); /// dom.rebuild(); /// /// let base = dom.base_scope(); @@ -116,7 +116,7 @@ impl Scope { /// # Example /// /// ```rust - /// let mut dom = VirtualDom::new(|cx| cx.render(rsx!{ div {} })); + /// let mut dom = VirtualDom::new(|cx, props|cx.render(rsx!{ div {} })); /// dom.rebuild(); /// let base = dom.base_scope(); /// diff --git a/packages/core/src/virtual_dom.rs b/packages/core/src/virtual_dom.rs index 9f83138da..849b1f7cf 100644 --- a/packages/core/src/virtual_dom.rs +++ b/packages/core/src/virtual_dom.rs @@ -31,7 +31,7 @@ use std::any::Any; /// /// Example /// ```rust -/// static App: FC<()> = |cx| { +/// static App: FC<()> = |cx, props|{ /// cx.render(rsx!{ /// div { /// "Hello World" @@ -180,7 +180,7 @@ impl VirtualDom { /// struct AppProps { /// route: &'static str /// } - /// static App: FC = |cx| cx.render(rsx!{ "route is {cx.route}" }); + /// static App: FC = |cx, props|cx.render(rsx!{ "route is {cx.route}" }); /// /// let mut dom = VirtualDom::new_with_props(App, AppProps { route: "start" }); /// @@ -227,7 +227,7 @@ impl VirtualDom { /// /// # Example /// ``` - /// static App: FC<()> = |cx| cx.render(rsx!{ "hello world" }); + /// static App: FC<()> = |cx, props|cx.render(rsx!{ "hello world" }); /// let mut dom = VirtualDom::new(); /// let edits = dom.rebuild(); /// @@ -252,7 +252,7 @@ impl VirtualDom { /// value: Shared<&'static str>, /// } /// - /// static App: FC = |cx| { + /// static App: FC = |cx, props|{ /// let val = cx.value.borrow(); /// cx.render(rsx! { div { "{val}" } }) /// }; @@ -314,7 +314,7 @@ impl VirtualDom { /// # Example /// /// ```no_run - /// static App: FC<()> = |cx| rsx!(cx, div {"hello"} ); + /// static App: FC<()> = |cx, props|rsx!(cx, div {"hello"} ); /// let mut dom = VirtualDom::new(App); /// loop { /// let deadline = TimeoutFuture::from_ms(16); diff --git a/packages/core/tests/hooks.rs b/packages/core/tests/hooks.rs index a7161cf43..a12f9a570 100644 --- a/packages/core/tests/hooks.rs +++ b/packages/core/tests/hooks.rs @@ -10,7 +10,7 @@ type Shared = Rc>; #[test] fn sample_refs() { - // static App: FC<()> = |cx| { + // static App: FC<()> = |cx, props|{ // let div_ref = use_node_ref::(cx); // cx.render(rsx! { diff --git a/packages/desktop/README.md b/packages/desktop/README.md index 56817e0b6..dc840b09d 100644 --- a/packages/desktop/README.md +++ b/packages/desktop/README.md @@ -8,7 +8,7 @@ Dioxus-webview is an attempt at making a simpler "Tauri" where creating desktop // main.rs #[async_std::main] async fn main() { - dioxus_desktop::new(|cx| { + dioxus_desktop::new(|cx, props|{ let (count, set_count) = use_state(cx, || 0); cx.render(html! {

diff --git a/packages/desktop/examples/test.rs b/packages/desktop/examples/test.rs index cebd522b5..b327b1c33 100644 --- a/packages/desktop/examples/test.rs +++ b/packages/desktop/examples/test.rs @@ -6,7 +6,7 @@ fn main() { dioxus_desktop::launch(App, |f| f.with_window(|w| w.with_maximized(true))).expect("Failed"); } -static App: FC<()> = |cx| { +static App: FC<()> = |cx, props|{ // cx.render(rsx!( div { diff --git a/packages/hooks/examples/lifecycle.rs b/packages/hooks/examples/lifecycle.rs index 87c4e5559..0398f7b16 100644 --- a/packages/hooks/examples/lifecycle.rs +++ b/packages/hooks/examples/lifecycle.rs @@ -10,7 +10,7 @@ fn main() {} // Remvoe, // } -// static Component: FC<()> = |cx| { +// static Component: FC<()> = |cx, props|{ // let (tasks, dispatch) = use_reducer( // cx, // || CompState { tasks: Vec::new() }, diff --git a/packages/hooks/src/usecollection.rs b/packages/hooks/src/usecollection.rs index cb696a214..d0b3b71e7 100644 --- a/packages/hooks/src/usecollection.rs +++ b/packages/hooks/src/usecollection.rs @@ -36,7 +36,7 @@ uses the same memoization on top of the use_context API. Here's a fully-functional todo app using the use_map API: ```rust -static TodoList: FC<()> = |cx| { +static TodoList: FC<()> = |cx, props|{ let todos = use_map(cx, || HashMap::new()); let input = use_ref(|| None); diff --git a/packages/hooks/src/useref.rs b/packages/hooks/src/useref.rs index 4254c2e52..1639628e0 100644 --- a/packages/hooks/src/useref.rs +++ b/packages/hooks/src/useref.rs @@ -33,6 +33,6 @@ impl Clone for UseRef<'_, T> { } impl Copy for UseRef<'_, T> {} -pub fn use_ref(cx: Context

, f: impl FnOnce() -> T) -> UseRef { +pub fn use_ref(cx: Context, f: impl FnOnce() -> T) -> UseRef { cx.use_hook(|_| RefCell::new(f()), |f| UseRef { inner: f }, |_| {}) } diff --git a/packages/hooks/src/usestate.rs b/packages/hooks/src/usestate.rs index ea0040be9..bb5ac85ca 100644 --- a/packages/hooks/src/usestate.rs +++ b/packages/hooks/src/usestate.rs @@ -35,7 +35,7 @@ use std::{ /// /// Usage: /// ```ignore -/// const Example: FC<()> = |cx| { +/// const Example: FC<()> = |cx, props|{ /// let counter = use_state(cx, || 0); /// let increment = |_| counter += 1; /// let decrement = |_| counter += 1; @@ -49,8 +49,8 @@ use std::{ /// } /// } /// ``` -pub fn use_state<'a, 'c, T: 'static, F: FnOnce() -> T, P>( - cx: Context<'a, P>, +pub fn use_state<'a, 'c, T: 'static, F: FnOnce() -> T>( + cx: Context<'a>, initial_state_fn: F, ) -> UseState<'a, T> { cx.use_hook( diff --git a/packages/ssr/README.md b/packages/ssr/README.md index 77fbc97e1..44ace9c77 100644 --- a/packages/ssr/README.md +++ b/packages/ssr/README.md @@ -4,7 +4,7 @@ Render a Dioxus VirtualDOM to a string. ```rust // Our app: -const App: FC<()> = |cx| cx.render(rsx!(div {"hello world!"})); +const App: FC<()> = |cx, props|cx.render(rsx!(div {"hello world!"})); // Build the virtualdom from our app let mut vdom = VirtualDOM::new(App); diff --git a/packages/ssr/examples/basic.rs b/packages/ssr/examples/basic.rs index ece077759..5934f2cc8 100644 --- a/packages/ssr/examples/basic.rs +++ b/packages/ssr/examples/basic.rs @@ -12,7 +12,7 @@ fn main() { ) } -pub static App: FC<()> = |cx| { +pub static App: FC<()> = |cx, props|{ cx.render(rsx!( div { class: "overflow-hidden" diff --git a/packages/ssr/examples/tide.rs b/packages/ssr/examples/tide.rs index 9fc58d2be..ae1805622 100644 --- a/packages/ssr/examples/tide.rs +++ b/packages/ssr/examples/tide.rs @@ -40,8 +40,8 @@ struct ExampleProps { initial_name: String, } -static Example: FC = |cx| { - let dispaly_name = use_state(cx, move || cx.initial_name.clone()); +static Example: FC = |cx, props| { + let dispaly_name = use_state(cx, move || props.initial_name.clone()); cx.render(rsx! { div { class: "py-12 px-4 text-center w-full max-w-2xl mx-auto", diff --git a/packages/ssr/examples/tofile.rs b/packages/ssr/examples/tofile.rs index ec72532d0..6a14b4f85 100644 --- a/packages/ssr/examples/tofile.rs +++ b/packages/ssr/examples/tofile.rs @@ -23,7 +23,7 @@ fn main() { .unwrap(); } -pub static App: FC<()> = |cx| { +pub static App: FC<()> = |cx, props|{ cx.render(rsx!( div { class: "overflow-hidden" link { href:"https://unpkg.com/tailwindcss@^2/dist/tailwind.min.css" rel:"stylesheet" } @@ -38,7 +38,7 @@ pub static App: FC<()> = |cx| { )) }; -pub static Header: FC<()> = |cx| { +pub static Header: FC<()> = |cx, props|{ cx.render(rsx! { div { header { class: "text-gray-400 bg-gray-900 body-font" @@ -64,7 +64,7 @@ pub static Header: FC<()> = |cx| { }) }; -pub static Hero: FC<()> = |cx| { +pub static Hero: FC<()> = |cx, props|{ // cx.render(rsx! { section{ class: "text-gray-400 bg-gray-900 body-font" @@ -102,7 +102,7 @@ pub static Hero: FC<()> = |cx| { } }) }; -pub static Entry: FC<()> = |cx| { +pub static Entry: FC<()> = |cx, props|{ // cx.render(rsx! { section{ class: "text-gray-400 bg-gray-900 body-font" @@ -115,7 +115,7 @@ pub static Entry: FC<()> = |cx| { }) }; -pub static StacksIcon: FC<()> = |cx| { +pub static StacksIcon: FC<()> = |cx, props|{ cx.render(rsx!( svg { // xmlns: "http://www.w3.org/2000/svg" @@ -130,7 +130,7 @@ pub static StacksIcon: FC<()> = |cx| { } )) }; -pub static RightArrowIcon: FC<()> = |cx| { +pub static RightArrowIcon: FC<()> = |cx, props|{ cx.render(rsx!( svg { fill: "none" diff --git a/packages/ssr/src/lib.rs b/packages/ssr/src/lib.rs index 57d9d11df..eb51883fc 100644 --- a/packages/ssr/src/lib.rs +++ b/packages/ssr/src/lib.rs @@ -44,7 +44,7 @@ pub fn render_vdom_scope(vdom: &VirtualDom, scope: ScopeId) -> Option { /// /// ## Example /// ```ignore -/// static App: FC<()> = |cx| cx.render(rsx!(div { "hello world" })); +/// static App: FC<()> = |cx, props|cx.render(rsx!(div { "hello world" })); /// let mut vdom = VirtualDom::new(App); /// vdom.rebuild(); /// @@ -242,13 +242,13 @@ mod tests { use dioxus_core::prelude::*; use dioxus_html as dioxus_elements; - static SIMPLE_APP: FC<()> = |cx| { + static SIMPLE_APP: FC<()> = |cx, props|{ cx.render(rsx!(div { "hello world!" })) }; - static SLIGHTLY_MORE_COMPLEX: FC<()> = |cx| { + static SLIGHTLY_MORE_COMPLEX: FC<()> = |cx, props|{ cx.render(rsx! { div { title: "About W3Schools" @@ -267,14 +267,14 @@ mod tests { }) }; - static NESTED_APP: FC<()> = |cx| { + static NESTED_APP: FC<()> = |cx, props|{ cx.render(rsx!( div { SIMPLE_APP {} } )) }; - static FRAGMENT_APP: FC<()> = |cx| { + static FRAGMENT_APP: FC<()> = |cx, props|{ cx.render(rsx!( div { "f1" } div { "f2" } @@ -330,7 +330,7 @@ mod tests { #[test] fn styles() { - static STLYE_APP: FC<()> = |cx| { + static STLYE_APP: FC<()> = |cx, props|{ cx.render(rsx! { div { style: { color: "blue", font_size: "46px" } } }) diff --git a/packages/web/examples/async_web.rs b/packages/web/examples/async_web.rs index e1d1d49e6..d0dac5440 100644 --- a/packages/web/examples/async_web.rs +++ b/packages/web/examples/async_web.rs @@ -28,7 +28,7 @@ struct DogApi { const ENDPOINT: &str = "https://dog.ceo/api/breeds/image/random/"; -static App: FC<()> = |cx| { +static App: FC<()> = |cx, props|{ let state = use_state(cx, || 0); let dog_node = use_suspense( diff --git a/packages/web/examples/basic.rs b/packages/web/examples/basic.rs index 7561d2784..56eb1f74d 100644 --- a/packages/web/examples/basic.rs +++ b/packages/web/examples/basic.rs @@ -22,7 +22,7 @@ fn main() { dioxus_web::launch(APP, |c| c) } -static APP: FC<()> = |cx| { +static APP: FC<()> = |cx, props|{ let mut count = use_state(cx, || 3); cx.render(rsx! { @@ -44,7 +44,7 @@ static APP: FC<()> = |cx| { }) }; -static Child: FC<()> = |cx| { +static Child: FC<()> = |cx, props|{ cx.render(rsx! { div { div { diff --git a/packages/web/examples/blah.rs b/packages/web/examples/blah.rs index a12ec2bb0..de33f9b62 100644 --- a/packages/web/examples/blah.rs +++ b/packages/web/examples/blah.rs @@ -24,7 +24,7 @@ fn main() { dioxus_web::launch(App, |c| c) } -static App: FC<()> = |cx| { +static App: FC<()> = |cx, props|{ let mut state = use_state(cx, || 0); cx.render(rsx! { div { diff --git a/packages/web/examples/btns.rs b/packages/web/examples/btns.rs index 1dfca2a4b..c354fea5b 100644 --- a/packages/web/examples/btns.rs +++ b/packages/web/examples/btns.rs @@ -22,7 +22,7 @@ fn main() { // dioxus::web::launch(App, |c| c); } -static App: FC<()> = |cx| { +static App: FC<()> = |cx, props|{ dbg!("rednering parent"); cx.render(rsx! { div { @@ -39,7 +39,7 @@ static App: FC<()> = |cx| { }) }; -static But: FC<()> = |cx| { +static But: FC<()> = |cx, props|{ let mut count = use_state(cx, || 0); // let d = Dropper { name: "asd" }; diff --git a/packages/web/examples/demo.rs b/packages/web/examples/demo.rs index 95bda2267..304715377 100644 --- a/packages/web/examples/demo.rs +++ b/packages/web/examples/demo.rs @@ -25,7 +25,7 @@ fn main() { dioxus_web::launch(App, |c| c) } -pub static App: FC<()> = |cx| { +pub static App: FC<()> = |cx, props|{ cx.render(rsx!( div { class: "overflow-hidden" link { href:"https://unpkg.com/tailwindcss@^2/dist/tailwind.min.css" rel:"stylesheet" } @@ -40,7 +40,7 @@ pub static App: FC<()> = |cx| { )) }; -pub static Header: FC<()> = |cx| { +pub static Header: FC<()> = |cx, props|{ cx.render(rsx! { div { header { class: "text-gray-400 bg-gray-900 body-font" @@ -66,7 +66,7 @@ pub static Header: FC<()> = |cx| { }) }; -pub static Hero: FC<()> = |cx| { +pub static Hero: FC<()> = |cx, props|{ // cx.render(rsx! { section{ class: "text-gray-400 bg-gray-900 body-font" @@ -104,7 +104,7 @@ pub static Hero: FC<()> = |cx| { } }) }; -pub static Entry: FC<()> = |cx| { +pub static Entry: FC<()> = |cx, props|{ // cx.render(rsx! { section{ class: "text-gray-400 bg-gray-900 body-font" @@ -117,7 +117,7 @@ pub static Entry: FC<()> = |cx| { }) }; -pub static StacksIcon: FC<()> = |cx| { +pub static StacksIcon: FC<()> = |cx, props|{ cx.render(rsx!( svg { // xmlns: "http://www.w3.org/2000/svg" @@ -132,7 +132,7 @@ pub static StacksIcon: FC<()> = |cx| { } )) }; -pub static RightArrowIcon: FC<()> = |cx| { +pub static RightArrowIcon: FC<()> = |cx, props|{ cx.render(rsx!( svg { fill: "none" diff --git a/src/lib.rs b/src/lib.rs index 7c9f6c3f3..0248ea19e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -93,7 +93,7 @@ //! Dioxus uses hooks for state management. Hooks are a form of state persisted between calls of the function component. //! //! ``` -//! pub pub static Example: FC<()> = |cx| { +//! pub pub static Example: FC<()> = |cx, props|{ //! let (val, set_val) = use_state(cx, || 0); //! cx.render(rsx!( //! button { onclick: move |_| set_val(val + 1) } @@ -156,7 +156,7 @@ //! diouxs::web::launch(Example); //! } //! -//! pub pub static Example: FC<()> = |cx| { +//! pub pub static Example: FC<()> = |cx, props|{ //! cx.render(rsx! { //! div { "Hello World!" } //! }) From e881b86d2e0548412f030988ddf8e9ed2da75e6c Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Tue, 21 Sep 2021 17:02:37 -0400 Subject: [PATCH 24/35] docs: update readme --- README.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/README.md b/README.md index ff170a40d..f811045d6 100644 --- a/README.md +++ b/README.md @@ -49,7 +49,7 @@ Dioxus is a portable, performant, and ergonomic framework for building cross-platform user experiences in Rust. ```rust -fn App(cx: Context<()>) -> DomTree { +static App: FC<()> = |cx, props| { let mut count = use_state(cx, || 0); cx.render(rsx!( @@ -64,7 +64,6 @@ Dioxus can be used to deliver webapps, desktop apps, static sites, liveview apps If you know React, then you already know Dioxus. - ### Unique features: - Incredible inline documentation. Supports hover and guides for all HTML elements, listeners, and events. - Templates are "constified" at compile time. Nodes that don't change will won't be diffed. From b32e2611e37b17c2371ffb10cf1ac647f017d917 Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Tue, 21 Sep 2021 18:13:09 -0400 Subject: [PATCH 25/35] polish: clean up warnings --- .vscode/.gitignore | 1 - .vscode/settings.json | 4 ++ examples/calculator.rs | 11 ++--- examples/manually.rs | 1 + packages/core-macro/src/ifmt.rs | 4 +- packages/core-macro/src/rsx/ambiguous.rs | 4 +- packages/core-macro/src/rsx/element.rs | 2 +- packages/core/src/childiter.rs | 2 +- packages/core/src/component.rs | 6 +-- packages/core/src/context.rs | 36 ++++++++------- packages/core/src/diff.rs | 56 ++++++++++++++---------- packages/core/src/diff_stack.rs | 6 +-- packages/core/src/hooklist.rs | 5 ++- packages/core/src/hooks.rs | 26 ++--------- packages/core/src/nodes.rs | 20 ++++----- packages/core/src/scheduler.rs | 11 ++--- packages/core/src/scope.rs | 19 ++++---- packages/core/src/test_dom.rs | 8 +++- packages/core/src/util.rs | 2 - packages/core/src/virtual_dom.rs | 25 +++++------ packages/desktop/src/lib.rs | 6 ++- 21 files changed, 126 insertions(+), 129 deletions(-) create mode 100644 .vscode/settings.json diff --git a/.vscode/.gitignore b/.vscode/.gitignore index e38da2008..e69de29bb 100644 --- a/.vscode/.gitignore +++ b/.vscode/.gitignore @@ -1 +0,0 @@ -settings.json diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 000000000..a3b8a0b79 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,4 @@ +{ + "rust-analyzer.inlayHints.enable": false, + "rust-analyzer.cargo.allFeatures": true +} diff --git a/examples/calculator.rs b/examples/calculator.rs index eb1adc645..13b1cc865 100644 --- a/examples/calculator.rs +++ b/examples/calculator.rs @@ -1,12 +1,9 @@ //! Example: Calculator //! ------------------------- -// use dioxus::events::on::*; -// use dioxus::prelude::*; - fn main() { env_logger::init(); - dioxus::desktop::launch(App, |cfg| cfg); + dioxus::desktop::launch(App, |cfg| cfg).unwrap(); } use dioxus::events::on::*; @@ -97,10 +94,10 @@ const App: FC<()> = |cx, props| { } div { class: "digit-keys" CalculatorKey { name: "key-0", onclick: move |_| input_digit(0), "0" } - CalculatorKey { name: "key-dot", onclick: move |_| input_dot(), "●" } + 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}" } + {(1..9).map(|k| rsx!{ + CalculatorKey { key: "{k}", name: "key-{k}", onclick: move |_| input_digit(k), "{k}" } })} } div { class: "operator-keys" diff --git a/examples/manually.rs b/examples/manually.rs index 6bf3fb85d..9dd760bf7 100644 --- a/examples/manually.rs +++ b/examples/manually.rs @@ -17,6 +17,7 @@ fn main() { AppendChildren { many: 1 }, // ReplaceWith { many: 1 }, ]; + dioxus_desktop::WebviewRenderer::run_with_edits(App, (), |c| c, Some(edits)).expect("failed"); } diff --git a/packages/core-macro/src/ifmt.rs b/packages/core-macro/src/ifmt.rs index 816c1f400..5bac539d2 100644 --- a/packages/core-macro/src/ifmt.rs +++ b/packages/core-macro/src/ifmt.rs @@ -237,8 +237,8 @@ where // # Safety // // - This is the canonical example of using `ManuallyDrop`. - let value = ManuallyDrop::into_inner(ptr::read(&mut self.0)); - let drop = ManuallyDrop::into_inner(ptr::read(&mut self.1)); + let value = ManuallyDrop::into_inner(ptr::read(&self.0)); + let drop = ManuallyDrop::into_inner(ptr::read(&self.1)); drop(value); } } diff --git a/packages/core-macro/src/rsx/ambiguous.rs b/packages/core-macro/src/rsx/ambiguous.rs index bfcfbc374..ad535ef31 100644 --- a/packages/core-macro/src/rsx/ambiguous.rs +++ b/packages/core-macro/src/rsx/ambiguous.rs @@ -86,11 +86,11 @@ impl Parse for AmbiguousElement { if first_char.is_ascii_uppercase() { input .parse::>() - .map(|c| AmbiguousElement::Component(c)) + .map(AmbiguousElement::Component) } else { input .parse::>() - .map(|c| AmbiguousElement::Element(c)) + .map(AmbiguousElement::Element) } } else { Err(Error::new(input.span(), "Not a valid Html tag")) diff --git a/packages/core-macro/src/rsx/element.rs b/packages/core-macro/src/rsx/element.rs index 4d9a2f09e..451a3d1f9 100644 --- a/packages/core-macro/src/rsx/element.rs +++ b/packages/core-macro/src/rsx/element.rs @@ -270,7 +270,7 @@ fn parse_rsx_element_field( name, value: ty, namespace: None, - element_name: element_name.clone(), + element_name, }); return Ok(()); } diff --git a/packages/core/src/childiter.rs b/packages/core/src/childiter.rs index db3b19b62..e7032e939 100644 --- a/packages/core/src/childiter.rs +++ b/packages/core/src/childiter.rs @@ -39,7 +39,7 @@ impl<'a> Iterator for RealChildIterator<'a> { // We've recursed INTO an element/text // We need to recurse *out* of it and move forward to the next should_pop = true; - returned_node = Some(&*node); + returned_node = Some(node); } // If we get a fragment we push the next child diff --git a/packages/core/src/component.rs b/packages/core/src/component.rs index d19ec6581..0ec306229 100644 --- a/packages/core/src/component.rs +++ b/packages/core/src/component.rs @@ -29,7 +29,7 @@ use crate::innerlude::{Context, DomTree, LazyNodes, FC}; /// You want to use this free-function when your fragment needs a key and simply returning multiple nodes from rsx! won't cut it. /// #[allow(non_upper_case_globals, non_snake_case)] -pub fn Fragment<'a>(cx: Context<'a>, props: &'a ()) -> DomTree<'a> { +pub fn Fragment<'a>(cx: Context<'a>, _: &'a ()) -> DomTree<'a> { cx.render(LazyNodes::new(|f| f.fragment_from_iter(cx.children()))) } @@ -91,9 +91,7 @@ impl Properties for () { pub struct EmptyBuilder; impl EmptyBuilder { #[inline] - pub fn build(self) -> () { - () - } + pub fn build(self) {} } /// This utility function launches the builder method so rsx! and html! macros can use the typed-builder pattern diff --git a/packages/core/src/context.rs b/packages/core/src/context.rs index 991352c5f..9d16c9c01 100644 --- a/packages/core/src/context.rs +++ b/packages/core/src/context.rs @@ -34,10 +34,7 @@ pub struct Context<'src> { impl<'src> Copy for Context<'src> {} impl<'src> Clone for Context<'src> { fn clone(&self) -> Self { - Self { - // props: self.props, - scope: self.scope, - } + Self { scope: self.scope } } } @@ -202,16 +199,18 @@ impl<'src> Context<'src> { ); if !is_initialized { - self.scope - .shared_contexts - .borrow_mut() - .insert(TypeId::of::(), Rc::new(init())) - .map(|_| { + let existing = self + .scope + .shared_contexts + .borrow_mut() + .insert(TypeId::of::(), Rc::new(init())); + + if existing.is_some() { log::warn!( "A shared state was replaced with itself. \ This is does not result in a panic, but is probably not what you are trying to do" ); - }); + } } self.use_consume_state().unwrap() @@ -244,12 +243,15 @@ impl<'src> Context<'src> { /// - Runner: closure used to output a value every time the hook is used /// - Cleanup: closure used to teardown the hook once the dom is cleaned up /// + /// + /// # Example + /// /// ```ignore /// // use_ref is the simplest way of storing a value between renders - /// pub fn use_ref(initial_value: impl FnOnce() -> T + 'static) -> Rc> { + /// fn use_ref(initial_value: impl FnOnce() -> T) -> &RefCell { /// use_hook( /// || Rc::new(RefCell::new(initial_value())), - /// |state| state.clone(), + /// |state| state, /// |_| {}, /// ) /// } @@ -275,14 +277,14 @@ impl<'src> Context<'src> { ); } - const ERR_MSG: &str = r###" + runner(self.scope.hooks.next::().expect(HOOK_ERR_MSG)) + } +} + +const HOOK_ERR_MSG: &str = r###" Unable to retrive the hook that was initialized in this index. Consult the `rules of hooks` to understand how to use hooks properly. You likely used the hook in a conditional. Hooks rely on consistent ordering between renders. Any function prefixed with "use" should not be called conditionally. "###; - - runner(self.scope.hooks.next::().expect(ERR_MSG)) - } -} diff --git a/packages/core/src/diff.rs b/packages/core/src/diff.rs index 2da69817d..9dea0c71a 100644 --- a/packages/core/src/diff.rs +++ b/packages/core/src/diff.rs @@ -328,8 +328,8 @@ impl<'bump> DiffMachine<'bump> { self.mutations.set_attribute(attr); } - if children.len() > 0 { - self.stack.element_id_stack.push(real_id);; + if children.is_empty() { + self.stack.element_id_stack.push(real_id); // push our element_id onto the stack // drop our element off the stack self.stack.create_children(children, MountType::Append); @@ -341,7 +341,7 @@ impl<'bump> DiffMachine<'bump> { } fn create_component_node(&mut self, vcomponent: &'bump VComponent<'bump>) { - let caller = vcomponent.caller.clone(); + let caller = vcomponent.caller; let parent_idx = self.stack.current_scope().unwrap(); @@ -546,13 +546,15 @@ impl<'bump> DiffMachine<'bump> { // make sure the component's caller function is up to date let scope = self.vdom.get_scope_mut(scope_addr).unwrap(); - scope.update_scope_dependencies(new.caller.clone(), ScopeChildren(new.children)); + scope.update_scope_dependencies(new.caller, ScopeChildren(new.children)); // React doesn't automatically memoize, but we do. let props_are_the_same = old.comparator.unwrap(); if self.cfg.force_diff || !props_are_the_same(new) { - if scope.run_scope(self.vdom) { + let succeeded = scope.run_scope(self.vdom); + + if succeeded { self.diff_node(scope.frames.wip_head(), scope.frames.fin_head()); } } @@ -664,16 +666,20 @@ impl<'bump> DiffMachine<'bump> { self.stack.push(DiffInstruction::Diff { new, old }); } - if old.len() > new.len() { - self.remove_nodes(&old[new.len()..]); - } else if new.len() > old.len() { - log::debug!("Calling create children on array differences"); - self.stack.create_children( - &new[old.len()..], - MountType::InsertAfter { - other_node: old.last().unwrap(), - }, - ); + use std::cmp::Ordering; + match old.len().cmp(&new.len()) { + Ordering::Greater => self.remove_nodes(&old[new.len()..]), + Ordering::Less => { + self.stack.create_children( + &new[old.len()..], + MountType::InsertAfter { + other_node: old.last().unwrap(), + }, + ); + } + Ordering::Equal => { + // nothing - they're the same size + } } } @@ -922,7 +928,7 @@ impl<'bump> DiffMachine<'bump> { // for each spacing, generate a mount instruction let mut lis_iter = lis_sequence.iter().rev(); let mut last = *lis_iter.next().unwrap(); - while let Some(&next) = lis_iter.next() { + for next in lis_iter { if last - next > 1 { self.stack.push_nodes_created(0); self.stack.push(DiffInstruction::Mount { @@ -934,7 +940,7 @@ impl<'bump> DiffMachine<'bump> { apply(idx + next + 1, new_node, &mut self.stack); } } - last = next; + last = *next; } // add mount instruction for the first items not covered by the lis @@ -1036,25 +1042,27 @@ impl<'bump> DiffMachine<'bump> { for node in nodes { match node { VNode::Text(t) => { - t.dom_id.get().map(|id| { + if let Some(id) = t.dom_id.get() { self.mutations.remove(id.as_u64()); self.vdom.collect_garbage(id); - }); + } } VNode::Suspended(s) => { - s.dom_id.get().map(|id| { + if let Some(id) = s.dom_id.get() { self.mutations.remove(id.as_u64()); self.vdom.collect_garbage(id); - }); + } } VNode::Anchor(a) => { - a.dom_id.get().map(|id| { + if let Some(id) = a.dom_id.get() { self.mutations.remove(id.as_u64()); self.vdom.collect_garbage(id); - }); + } } VNode::Element(e) => { - e.dom_id.get().map(|id| self.mutations.remove(id.as_u64())); + if let Some(id) = e.dom_id.get() { + self.mutations.remove(id.as_u64()); + } } VNode::Fragment(f) => { self.remove_nodes(f.children); diff --git a/packages/core/src/diff_stack.rs b/packages/core/src/diff_stack.rs index 2e6479b2b..aa9716cb6 100644 --- a/packages/core/src/diff_stack.rs +++ b/packages/core/src/diff_stack.rs @@ -72,7 +72,7 @@ impl<'bump> DiffStack<'bump> { self.nodes_created_stack.push(0); self.instructions.push(DiffInstruction::Mount { and }); - for child in children.into_iter().rev() { + for child in children.iter().rev() { self.instructions .push(DiffInstruction::Create { node: child }); } @@ -82,7 +82,7 @@ impl<'bump> DiffStack<'bump> { self.nodes_created_stack.push(count); } - pub fn push_element_id(&mut self, id: ElementId) { + pub fn _push_element_id(&mut self, id: ElementId) { self.element_id_stack.push(id); } @@ -101,7 +101,7 @@ impl<'bump> DiffStack<'bump> { } pub fn current_scope(&self) -> Option { - self.scope_stack.last().map(|f| f.clone()) + self.scope_stack.last().copied() } pub fn create_component(&mut self, idx: ScopeId, node: &'bump VNode<'bump>) { diff --git a/packages/core/src/hooklist.rs b/packages/core/src/hooklist.rs index bea11a82a..2f68be3b8 100644 --- a/packages/core/src/hooklist.rs +++ b/packages/core/src/hooklist.rs @@ -52,8 +52,11 @@ impl HookList { pub(crate) fn at_end(&self) -> bool { self.cur_idx() >= self.len() } +} - pub(crate) fn cleanup_hooks(&mut self) { +// When the scope is dropped, we want to call the cleanup function for each of the hooks +impl Drop for HookList { + fn drop(&mut self) { self.vals .borrow_mut() .drain(..) diff --git a/packages/core/src/hooks.rs b/packages/core/src/hooks.rs index fcbf640d1..a0d12609e 100644 --- a/packages/core/src/hooks.rs +++ b/packages/core/src/hooks.rs @@ -11,13 +11,7 @@ use crate::innerlude::*; use futures_util::FutureExt; -use std::{ - any::{Any, TypeId}, - cell::RefCell, - future::Future, - ops::Deref, - rc::Rc, -}; +use std::{any::Any, cell::RefCell, future::Future, ops::Deref, rc::Rc}; /// Awaits the given task, forcing the component to re-render when the value is ready. /// @@ -104,11 +98,10 @@ where - if it doesn't, then we render a suspended node along with with the callback and task id */ cx.use_hook( - move |hook_idx| { + move |_| { let value = Rc::new(RefCell::new(None)); let slot = value.clone(); - - let originator = cx.scope.our_arena_idx.clone(); + let originator = cx.scope.our_arena_idx; let handle = cx.submit_task(Box::pin(task_initializer().then( move |output| async move { @@ -176,9 +169,7 @@ impl<'src> SuspendedContext<'src> { self, lazy_nodes: LazyNodes<'src, F>, ) -> DomTree<'src> { - let scope_ref = self.inner.scope; let bump = &self.inner.scope.frames.wip_frame().bump; - Some(lazy_nodes.into_vnode(NodeFactory { bump })) } } @@ -194,14 +185,5 @@ impl<'a, T> Deref for NodeRef<'a, T> { } pub fn use_node_ref(cx: Context) -> NodeRef { - cx.use_hook( - |f| {}, - |f| { - // - todo!() - }, - |f| { - // - }, - ) + cx.use_hook(|_| RefCell::new(None), |f| NodeRef { 0: f }, |_| {}) } diff --git a/packages/core/src/nodes.rs b/packages/core/src/nodes.rs index 555a9c285..926a36d46 100644 --- a/packages/core/src/nodes.rs +++ b/packages/core/src/nodes.rs @@ -307,7 +307,7 @@ impl<'a> NodeFactory<'a> { #[inline] pub fn bump(&self) -> &'a bumpalo::Bump { - &self.bump + self.bump } pub fn render_directly(&self, lazy_nodes: LazyNodes<'a, F>) -> DomTree<'a> @@ -466,12 +466,12 @@ impl<'a> NodeFactory<'a> { // - This comparator is only called on a corresponding set of bumpframes let props_memoized = unsafe { let real_other: &P = &*(other.raw_props as *const _ as *const P); - props.memoize(&real_other) + props.memoize(real_other) }; // It's only okay to memoize if there are no children and the props can be memoized // Implementing memoize is unsafe and done automatically with the props trait - match (props_memoized, children.len() == 0) { + match (props_memoized, children.is_empty()) { (true, true) => true, _ => false, } @@ -504,7 +504,7 @@ impl<'a> NodeFactory<'a> { RefCell::new(Some(drop_props)) }; - let is_static = children.len() == 0 && P::IS_STATIC && key.is_none(); + let is_static = children.is_empty() && P::IS_STATIC && key.is_none(); let key = key.map(|f| self.raw_text(f).0); @@ -515,7 +515,7 @@ impl<'a> NodeFactory<'a> { unsafe { std::mem::transmute(res) } }); - let can_memoize = children.len() == 0 && P::IS_STATIC; + let can_memoize = children.is_empty() && P::IS_STATIC; VNode::Component(bump.alloc(VComponent { user_fc, @@ -596,7 +596,7 @@ where nodes.push(node.into_vnode(cx)); } - if nodes.len() == 0 { + if nodes.is_empty() { nodes.push(VNode::Anchor(VAnchor { dom_id: empty_cell(), })); @@ -716,14 +716,14 @@ where // Conveniently, we also support "null" (nothing) passed in impl IntoVNode<'_> for () { - fn into_vnode<'a>(self, cx: NodeFactory<'a>) -> VNode<'a> { + fn into_vnode(self, cx: NodeFactory) -> VNode { cx.fragment_from_iter(None as Option) } } // Conveniently, we also support "None" impl IntoVNode<'_> for Option<()> { - fn into_vnode<'a>(self, cx: NodeFactory<'a>) -> VNode<'a> { + fn into_vnode(self, cx: NodeFactory) -> VNode { cx.fragment_from_iter(None as Option) } } @@ -738,12 +738,12 @@ impl<'a> IntoVNode<'a> for Option> { } impl IntoVNode<'_> for &'static str { - fn into_vnode<'a>(self, cx: NodeFactory<'a>) -> VNode<'a> { + fn into_vnode(self, cx: NodeFactory) -> VNode { cx.static_text(self) } } impl IntoVNode<'_> for Arguments<'_> { - fn into_vnode<'a>(self, cx: NodeFactory<'a>) -> VNode<'a> { + fn into_vnode(self, cx: NodeFactory) -> VNode { cx.text(self) } } diff --git a/packages/core/src/scheduler.rs b/packages/core/src/scheduler.rs index 8c69e081b..542b28e01 100644 --- a/packages/core/src/scheduler.rs +++ b/packages/core/src/scheduler.rs @@ -88,9 +88,11 @@ pub(crate) struct EventChannel { pub sender: UnboundedSender, pub schedule_any_immediate: Rc, pub submit_task: Rc TaskHandle>, - pub get_shared_context: Rc Option>>, + pub get_shared_context: GetSharedContext, } +pub type GetSharedContext = Rc Option>>; + pub enum SchedulerMsg { // events from the host UiEvent(UserEvent), @@ -175,7 +177,6 @@ impl Scheduler { Rc::new(move |id| sender.unbounded_send(SchedulerMsg::Immediate(id)).unwrap()) }, submit_task: { - let sender = sender.clone(); Rc::new(move |fiber_task| { let task_id = task_counter.get(); task_counter.set(task_id + 1); @@ -206,7 +207,7 @@ impl Scheduler { }; let pool = ResourcePool { - components: components.clone(), + components, raw_elements, channel, }; @@ -295,7 +296,7 @@ impl Scheduler { } pub fn has_pending_events(&self) -> bool { - self.ui_events.len() > 0 + !self.ui_events.is_empty() } /// re-balance the work lanes, ensuring high-priority work properly bumps away low priority work @@ -550,6 +551,6 @@ impl PriorityLane { } fn has_work(&self) -> bool { - self.dirty_scopes.len() > 0 || self.in_progress == true + !self.dirty_scopes.is_empty() || self.in_progress } } diff --git a/packages/core/src/scope.rs b/packages/core/src/scope.rs index b696a96cd..4ae5d0d1a 100644 --- a/packages/core/src/scope.rs +++ b/packages/core/src/scope.rs @@ -190,7 +190,7 @@ impl Scope { self.child_nodes = child_nodes; } - pub(crate) fn child_nodes<'a>(&'a self) -> ScopeChildren { + pub(crate) fn child_nodes(&self) -> ScopeChildren { unsafe { self.child_nodes.shorten_lifetime() } } @@ -246,10 +246,7 @@ impl Scope { let search_id = search.mounted_node.get(); // this assumes the node might not be mounted - should we assume that though? - match search_id.map(|f| f == element) { - Some(same) => same, - None => false, - } + search_id.map(|f| f == element).unwrap_or(false) }); if let Some(raw_listener) = raw_listener { @@ -356,11 +353,11 @@ impl<'a> ScopeRenderer<'a> { match &node { VNode::Text(text) => { write_indent(f, il); - write!(f, "\"{}\"\n", text.text)? + writeln!(f, "\"{}\"", text.text)? } VNode::Anchor(_anchor) => { write_indent(f, il); - write!(f, "Anchor {{}}\n")?; + writeln!(f, "Anchor {{}}")?; } VNode::Element(el) => { write_indent(f, il); @@ -373,7 +370,7 @@ impl<'a> ScopeRenderer<'a> { None => { // write_indent(f, il + 1); - write!(f, "{}: \"{}\"\n", attr.name, attr.value)? + writeln!(f, "{}: \"{}\"", attr.name, attr.value)? } Some(ns) => { @@ -401,17 +398,17 @@ impl<'a> ScopeRenderer<'a> { } write_indent(f, il); - write!(f, "}}\n")?; + writeln!(f, "}}")?; } VNode::Fragment(frag) => { if self.show_fragments { write_indent(f, il); - write!(f, "Fragment {{\n")?; + writeln!(f, "Fragment {{")?; for child in frag.children { self.render(vdom, child, f, il + 1)?; } write_indent(f, il); - write!(f, "}}\n")?; + writeln!(f, "}}")?; } else { for child in frag.children { self.render(vdom, child, f, il)?; diff --git a/packages/core/src/test_dom.rs b/packages/core/src/test_dom.rs index 6e89e817f..60d2de9b8 100644 --- a/packages/core/src/test_dom.rs +++ b/packages/core/src/test_dom.rs @@ -16,7 +16,7 @@ impl TestDom { TestDom { bump, scheduler } } - pub fn new_factory<'a>(&'a self) -> NodeFactory<'a> { + pub fn new_factory(&self) -> NodeFactory { NodeFactory::new(&self.bump) } @@ -87,6 +87,12 @@ impl TestDom { } } +impl Default for TestDom { + fn default() -> Self { + Self::new() + } +} + impl VirtualDom { pub fn simulate(&mut self) { // diff --git a/packages/core/src/util.rs b/packages/core/src/util.rs index d516761db..63962d792 100644 --- a/packages/core/src/util.rs +++ b/packages/core/src/util.rs @@ -1,8 +1,6 @@ use std::cell::Cell; use std::fmt::Display; -use crate::innerlude::*; - // create a cell with a "none" value #[inline] pub fn empty_cell() -> Cell> { diff --git a/packages/core/src/virtual_dom.rs b/packages/core/src/virtual_dom.rs index 849b1f7cf..6c2233f13 100644 --- a/packages/core/src/virtual_dom.rs +++ b/packages/core/src/virtual_dom.rs @@ -20,6 +20,7 @@ //! Additional functionality is defined in the respective files. use crate::innerlude::*; +use futures_util::Future; use std::any::Any; /// An integrated virtual node system that progresses events and diffs UI trees. @@ -186,7 +187,10 @@ impl VirtualDom { /// /// let mutations = dom.update_root_props(AppProps { route: "end" }).unwrap(); /// ``` - pub fn update_root_props<'s, P: 'static>(&'s mut self, root_props: P) -> Option> { + pub fn update_root_props

(&mut self, root_props: P) -> Option + where + P: 'static, + { let root_scope = self.scheduler.pool.get_scope_mut(self.base_scope).unwrap(); // Pre-emptively drop any downstream references of the old props @@ -233,7 +237,7 @@ impl VirtualDom { /// /// apply_edits(edits); /// ``` - pub fn rebuild<'s>(&'s mut self) -> Mutations<'s> { + pub fn rebuild(&mut self) -> Mutations { self.scheduler.rebuild(self.base_scope) } @@ -271,7 +275,7 @@ impl VirtualDom { /// /// let edits = dom.diff(); /// ``` - pub fn diff<'s>(&'s mut self) -> Mutations<'s> { + pub fn diff(&mut self) -> Mutations { self.scheduler.hard_diff(self.base_scope) } @@ -279,7 +283,7 @@ impl VirtualDom { /// /// This method will not wait for any suspended nodes to complete. If there is no pending work, then this method will /// return "None" - pub fn run_immediate<'s>(&'s mut self) -> Option>> { + pub fn run_immediate(&mut self) -> Option> { if self.scheduler.has_any_work() { Some(self.scheduler.work_sync()) } else { @@ -330,10 +334,10 @@ impl VirtualDom { /// applied the edits. /// /// Mutations are the only link between the RealDOM and the VirtualDOM. - pub async fn run_with_deadline<'s>( - &'s mut self, - deadline: impl std::future::Future, - ) -> Vec> { + pub async fn run_with_deadline( + &mut self, + deadline: impl Future, + ) -> Vec> { self.scheduler.work_with_deadline(deadline).await } @@ -380,8 +384,3 @@ impl std::fmt::Display for VirtualDom { renderer.render(self, root, f, 0) } } - -pub struct VirtualDomConfig { - component_slab_size: usize, - element_slab_size: usize, -} diff --git a/packages/desktop/src/lib.rs b/packages/desktop/src/lib.rs index c9c7153b4..436103a70 100644 --- a/packages/desktop/src/lib.rs +++ b/packages/desktop/src/lib.rs @@ -64,10 +64,12 @@ impl WebviewRenderer { Self::run_with_edits(root, props, user_builder, None) } - pub fn run_with_edits( + pub fn run_with_edits< + F: for<'a, 'b> FnOnce(&'a mut DesktopConfig<'b>) -> &'a mut DesktopConfig<'b>, + >( root: FC, props: T, - user_builder: impl for<'a, 'b> FnOnce(&'a mut DesktopConfig<'b>) -> &'a mut DesktopConfig<'b>, + user_builder: F, redits: Option>>, ) -> anyhow::Result<()> { log::info!("hello edits"); From 098d3821ed89ad38d99077a6556b48a7e91fc3fc Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Wed, 22 Sep 2021 01:25:28 -0400 Subject: [PATCH 26/35] wip: changes to scheduler --- .vscode/settings.json | 2 +- packages/core-macro/examples/fc.rs | 1 - packages/core-macro/examples/prop_test.rs | 2 +- packages/core-macro/examples/rsxt.rs | 125 --------------- packages/core/benches/jsframework.rs | 2 +- packages/core/examples/alternative.rs | 8 +- packages/core/examples/async.rs | 38 +---- packages/core/src/diff.rs | 2 +- packages/core/src/hooks.rs | 14 +- packages/core/src/scheduler.rs | 179 ++++++++++------------ packages/core/src/tasks.rs | 8 +- packages/core/src/virtual_dom.rs | 43 ++++-- packages/web/src/lib.rs | 5 +- packages/web/src/ric_raf.rs | 4 +- 14 files changed, 151 insertions(+), 282 deletions(-) delete mode 100644 packages/core-macro/examples/fc.rs delete mode 100644 packages/core-macro/examples/rsxt.rs diff --git a/.vscode/settings.json b/.vscode/settings.json index a3b8a0b79..c7ce540f2 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,4 +1,4 @@ { - "rust-analyzer.inlayHints.enable": false, + "rust-analyzer.inlayHints.enable": true, "rust-analyzer.cargo.allFeatures": true } diff --git a/packages/core-macro/examples/fc.rs b/packages/core-macro/examples/fc.rs deleted file mode 100644 index f328e4d9d..000000000 --- a/packages/core-macro/examples/fc.rs +++ /dev/null @@ -1 +0,0 @@ -fn main() {} diff --git a/packages/core-macro/examples/prop_test.rs b/packages/core-macro/examples/prop_test.rs index d80c0cc35..525b3c8b0 100644 --- a/packages/core-macro/examples/prop_test.rs +++ b/packages/core-macro/examples/prop_test.rs @@ -20,5 +20,5 @@ struct SomeProps { /// This implementation does not require a "PartialEq" because it does not memoize #[derive(dioxus_core_macro::Props)] struct SomePropsTwo<'a> { - a: &'a str, + _a: &'a str, } diff --git a/packages/core-macro/examples/rsxt.rs b/packages/core-macro/examples/rsxt.rs deleted file mode 100644 index 2a6434b5d..000000000 --- a/packages/core-macro/examples/rsxt.rs +++ /dev/null @@ -1,125 +0,0 @@ -pub mod dioxus { - pub mod builder { - pub struct Builder; - - struct AttrVal; - - impl Into for &'static str { - fn into(self) -> AttrVal { - todo!() - } - } - - impl Into for String { - fn into(self) -> AttrVal { - todo!() - } - } - // impl From for AttrVal { - // fn from(_: T) -> Self { - // todo!() - // } - // } - - impl Builder { - // fn attr(mut self, key: &str, value: impl Into) -> Self { - pub fn attr(self, _key: &str, _value: T) -> Self { - Self - } - - pub fn on(self, _key: &str, _value: T) -> Self { - Self - } - - pub fn finish(self) { - // Self - } - } - - pub struct Bump; - pub fn div(_bump: &Bump) -> Builder { - todo!() - } - pub fn h1(_bump: &Bump) -> Builder { - todo!() - } - pub fn h2(_bump: &Bump) -> Builder { - todo!() - } - } -} - -pub fn main() { - // render(rsx! { - // div { // we can actually support just a list of nodes too - // h1 {"Hello Dioxus"} - // p {"This is a beautful app you're building"} - // section { - // "custom section to the rescue", - // class: "abc123" - // } - // span { - // class: "abc123" - // "Try backwards too." - // "Anything goes!" - // "As long as it's within the rules" - // {0..10.map(|f| rsx!{ - // div { - // h3 {"totally okay to drop in iterators and expressions"} - // p {"however, debug information is lost"} - // } - // })} - // } - // span { - // "Feel free" - // class: "abc123" - // "To mix to your heart's content" - // } - // span { class: "some-very-long-and-tedious-class-name-is-now-separated" - // "Very ergonomic" - // } - // span { "Innovative design 🛠" - // class: "some-very-long-and-tedious-class-name-is-now-separated" - // } - // } - // }); - - let _g = String::from("asd"); - - // let lazy = rsx! { - // div { - // a: "asd", - // a: "asd", - // a: "asd", - // a: "asd", - // a: "asd", - // // a: {rsx!{ h1 {"hello world"} }}, // include - // a: {&g}, - // b: {1 + 2}, - // onclick: {move |e: ()| { - // println!("hello world!") - // }}, - // div { - // a: "asd" - // div { - // div { - // div { - - // } - // } - // } - // } - // h1 { - - // } - // h2 { - // "child" - // } - // "Childnode" - // } - // }; - - // render(lazy); -} - -fn render(_f: impl Fn(&dioxus::builder::Bump)) {} diff --git a/packages/core/benches/jsframework.rs b/packages/core/benches/jsframework.rs index f744e6070..e69fd9511 100644 --- a/packages/core/benches/jsframework.rs +++ b/packages/core/benches/jsframework.rs @@ -22,7 +22,7 @@ criterion_group!(mbenches, create_rows); criterion_main!(mbenches); fn create_rows(c: &mut Criterion) { - static App: FC<()> = |cx, props| { + static App: FC<()> = |cx, _| { let mut rng = SmallRng::from_entropy(); let rows = (0..10_000).map(|f| { let label = Label::new(&mut rng); diff --git a/packages/core/examples/alternative.rs b/packages/core/examples/alternative.rs index 02bebb43d..bb170873e 100644 --- a/packages/core/examples/alternative.rs +++ b/packages/core/examples/alternative.rs @@ -1,8 +1,12 @@ use dioxus_core::prelude::*; -fn main() {} +fn main() { + let mut dom = VirtualDom::new(EXAMPLE); + dom.rebuild(); + println!("{}", dom); +} -pub static Example: FC<()> = |cx, props| { +pub static EXAMPLE: FC<()> = |cx, _| { let list = (0..10).map(|_f| LazyNodes::new(move |_f| todo!())); cx.render(LazyNodes::new(move |cx| { diff --git a/packages/core/examples/async.rs b/packages/core/examples/async.rs index a60dfb0f4..6575afe5f 100644 --- a/packages/core/examples/async.rs +++ b/packages/core/examples/async.rs @@ -1,39 +1,15 @@ use dioxus_core::prelude::*; -fn main() {} +fn main() { + let mut dom = VirtualDom::new(App); + dom.rebuild(); +} const App: FC<()> = |cx, props| { - // create a new future - let _fut = cx.use_hook( - |_| { - // - async { loop {} } - // Box::pin(async { loop {} }) as Pin>> - }, - |f| f, - |_| {}, - ); - // let g = unsafe { Pin::new_unchecked(fut) }; + let id = cx.scope_id(); + cx.submit_task(Box::pin(async move { id })); - // cx.submit_task(fut); + let (handle, contents) = use_task(cx, || async { "hello world".to_string() }); todo!() }; - -const Task: FC<()> = |cx, props| { - let (_task, _res) = use_task(cx, || async { true }); - // task.pause(); - // task.restart(); - // task.stop(); - // task.drop(); - - // - - let _s = use_task(cx, || async { "hello world".to_string() }); - - todo!() -}; - -fn use_mut(_cx: Context, _f: impl FnOnce() -> T) -> &mut T { - todo!() -} diff --git a/packages/core/src/diff.rs b/packages/core/src/diff.rs index 9dea0c71a..34ec66644 100644 --- a/packages/core/src/diff.rs +++ b/packages/core/src/diff.rs @@ -137,7 +137,7 @@ impl<'a> SavedDiffWork<'a> { std::mem::transmute(self) } - pub unsafe fn promote<'b>(self, vdom: &'b mut ResourcePool) -> DiffMachine<'b> { + pub unsafe fn promote<'b>(self, vdom: &'b ResourcePool) -> DiffMachine<'b> { let extended: SavedDiffWork<'b> = std::mem::transmute(self); DiffMachine { vdom, diff --git a/packages/core/src/hooks.rs b/packages/core/src/hooks.rs index a0d12609e..f90f42d59 100644 --- a/packages/core/src/hooks.rs +++ b/packages/core/src/hooks.rs @@ -15,9 +15,21 @@ use std::{any::Any, cell::RefCell, future::Future, ops::Deref, rc::Rc}; /// Awaits the given task, forcing the component to re-render when the value is ready. /// +/// Returns the handle to the task and the value (if it is ready, else None). /// +/// ``` +/// static Example: FC<()> = |cx, props| { +/// let (task, value) = use_task(|| async { +/// timer::sleep(Duration::from_secs(1)).await; +/// "Hello World" +/// }); /// -/// +/// match contents { +/// Some(contents) => rsx!(cx, div { "{title}" }), +/// None => rsx!(cx, div { "Loading..." }), +/// } +/// }; +/// ``` pub fn use_task<'src, Out, Fut, Init>( cx: Context<'src>, task_initializer: Init, diff --git a/packages/core/src/scheduler.rs b/packages/core/src/scheduler.rs index 542b28e01..922cfa2fa 100644 --- a/packages/core/src/scheduler.rs +++ b/packages/core/src/scheduler.rs @@ -101,6 +101,10 @@ pub enum SchedulerMsg { Immediate(ScopeId), // tasks + Task(TaskMsg), +} + +pub enum TaskMsg { SubmitTask(FiberTask, u64), ToggleTask(u64), PauseTask(u64), @@ -152,7 +156,9 @@ pub(crate) struct Scheduler { pub garbage_scopes: HashSet, - pub lane: PriorityLane, + pub dirty_scopes: IndexSet, + pub saved_state: Option>, + pub in_progress: bool, } impl Scheduler { @@ -181,7 +187,9 @@ impl Scheduler { let task_id = task_counter.get(); task_counter.set(task_id + 1); sender - .unbounded_send(SchedulerMsg::SubmitTask(fiber_task, task_id)) + .unbounded_send(SchedulerMsg::Task(TaskMsg::SubmitTask( + fiber_task, task_id, + ))) .unwrap(); TaskHandle { our_id: task_id, @@ -214,6 +222,12 @@ impl Scheduler { let async_tasks = FuturesUnordered::new(); + // push a task that would never resolve - prevents us from immediately aborting the scheduler + async_tasks.push(Box::pin(async { + std::future::pending::<()>().await; + ScopeId(0) + }) as FiberTask); + Self { pool, @@ -235,24 +249,9 @@ impl Scheduler { garbage_scopes: HashSet::new(), - // current_priority: EventPriority::Low, - lane: PriorityLane::new(), - } - } - - pub fn manually_poll_events(&mut self) { - while let Ok(Some(msg)) = self.receiver.try_next() { - match msg { - SchedulerMsg::UiEvent(event) => self.ui_events.push_front(event), - SchedulerMsg::Immediate(im) => self.pending_immediates.push_front(im), - - // task stuff - SchedulerMsg::SubmitTask(_, _) => todo!(), - SchedulerMsg::ToggleTask(_) => todo!(), - SchedulerMsg::PauseTask(_) => todo!(), - SchedulerMsg::ResumeTask(_) => todo!(), - SchedulerMsg::DropTask(_) => todo!(), - } + dirty_scopes: Default::default(), + saved_state: None, + in_progress: false, } } @@ -272,31 +271,27 @@ impl Scheduler { } } - use EventPriority::*; + // use EventPriority::*; - match priority { - Immediate => todo!(), - High => todo!(), - Medium => todo!(), - Low => todo!(), - } + // match priority { + // Immediate => todo!(), + // High => todo!(), + // Medium => todo!(), + // Low => todo!(), + // } discrete } fn prepare_work(&mut self) { - while let Some(trigger) = self.ui_events.pop_back() { - if let Some(scope) = self.pool.get_scope_mut(trigger.scope) {} - } + // while let Some(trigger) = self.ui_events.pop_back() { + // if let Some(scope) = self.pool.get_scope_mut(trigger.scope) {} + // } } // nothing to do, no events on channels, no work pub fn has_any_work(&self) -> bool { - self.lane.has_work() || self.has_pending_events() - } - - pub fn has_pending_events(&self) -> bool { - !self.ui_events.is_empty() + !(self.dirty_scopes.is_empty() && self.ui_events.is_empty()) } /// re-balance the work lanes, ensuring high-priority work properly bumps away low priority work @@ -304,35 +299,39 @@ impl Scheduler { fn save_work(&mut self, lane: SavedDiffWork) { let saved: SavedDiffWork<'static> = unsafe { std::mem::transmute(lane) }; - self.lane.saved_state = Some(saved); + self.saved_state = Some(saved); } - fn load_work(&mut self) -> SavedDiffWork<'static> { - // match self.current_priority { - // EventPriority::Immediate => todo!(), - // EventPriority::High => todo!(), - // EventPriority::Medium => todo!(), - // EventPriority::Low => todo!(), - // } - unsafe { self.lane.saved_state.take().unwrap().extend() } + unsafe fn load_work(&mut self) -> SavedDiffWork<'static> { + self.saved_state.take().unwrap().extend() } pub fn handle_channel_msg(&mut self, msg: SchedulerMsg) { match msg { + // + SchedulerMsg::Task(msg) => todo!(), + SchedulerMsg::Immediate(_) => todo!(), SchedulerMsg::UiEvent(event) => { // - self.handle_ui_event(event); - } + let (discrete, priority) = event_meta(&event); - // - SchedulerMsg::SubmitTask(_, _) => todo!(), - SchedulerMsg::ToggleTask(_) => todo!(), - SchedulerMsg::PauseTask(_) => todo!(), - SchedulerMsg::ResumeTask(_) => todo!(), - SchedulerMsg::DropTask(_) => todo!(), + if let Some(scope) = self.pool.get_scope_mut(event.scope) { + if let Some(element) = event.mounted_dom_id { + // TODO: bubble properly here + scope.call_listener(event.event, element); + + while let Ok(Some(dirty_scope)) = self.receiver.try_next() { + // + // self.add_dirty_scope(dirty_scope, trigger.priority) + } + } + } + + discrete; + } } } @@ -346,22 +345,22 @@ impl Scheduler { ) -> bool { // Work through the current subtree, and commit the results when it finishes // When the deadline expires, give back the work - let saved_state = self.load_work(); + let saved_state = unsafe { self.load_work() }; // We have to split away some parts of ourself - current lane is borrowed mutably - let mut shared = self.pool.clone(); - let mut machine = unsafe { saved_state.promote(&mut shared) }; + let shared = self.pool.clone(); + let mut machine = unsafe { saved_state.promote(&shared) }; if machine.stack.is_empty() { let shared = self.pool.clone(); - self.lane.dirty_scopes.sort_by(|a, b| { + self.dirty_scopes.sort_by(|a, b| { let h1 = shared.get_scope(*a).unwrap().height; let h2 = shared.get_scope(*b).unwrap().height; h1.cmp(&h2) }); - if let Some(scope) = self.lane.dirty_scopes.pop() { + if let Some(scope) = self.dirty_scopes.pop() { let component = self.pool.get_scope(scope).unwrap(); let (old, new) = (component.frames.wip_head(), component.frames.fin_head()); machine.stack.push(DiffInstruction::Diff { new, old }); @@ -378,7 +377,7 @@ impl Scheduler { false } else { for node in saved.seen_scopes.drain() { - self.lane.dirty_scopes.remove(&node); + self.dirty_scopes.remove(&node); } let mut new_mutations = Mutations::new(); @@ -395,9 +394,9 @@ impl Scheduler { /// Uses some fairly complex logic to schedule what work should be produced. /// /// Returns a list of successful mutations. - pub async fn work_with_deadline<'a>( + pub fn work_with_deadline<'a>( &'a mut self, - deadline: impl Future, + mut deadline: impl FnMut() -> bool, ) -> Vec> { /* Strategy: @@ -429,33 +428,29 @@ impl Scheduler { - but if we received both - then we don't need to diff, do we? run as many as we can and then finally diff? */ let mut committed_mutations = Vec::>::new(); - pin_mut!(deadline); loop { - // Move work out of the scheduler message receiver and into dedicated message lanes - self.manually_poll_events(); + // switch our priority, pop off any work + for event in self.ui_events.drain(..) { + if let Some(scope) = self.pool.get_scope_mut(event.scope) { + if let Some(element) = event.mounted_dom_id { + // TODO: bubble properly here + scope.call_listener(event.event, element); - // Wait for any new events if we have nothing to do - // todo: poll the events once even if there is work to do to prevent starvation - if !self.has_any_work() { - futures_util::select! { - // todo: find a solution for the exhausted queue problem - // msg = self.async_tasks.next() => {} - msg = self.receiver.next() => { - log::debug!("received work in scheduler"); - self.handle_channel_msg(msg.unwrap()) - }, - _ = (&mut deadline).fuse() => return committed_mutations, + while let Ok(Some(dirty_scope)) = self.receiver.try_next() { + match dirty_scope { + SchedulerMsg::Immediate(im) => { + self.dirty_scopes.insert(im); + } + _ => todo!(), + } + } + } } } - // switch our priority, pop off any work - self.prepare_work(); - - let mut deadline_reached = || (&mut deadline).now_or_never().is_some(); - let finished_before_deadline = - self.work_on_current_lane(&mut deadline_reached, &mut committed_mutations); + self.work_on_current_lane(&mut deadline, &mut committed_mutations); if !finished_before_deadline { break; @@ -471,7 +466,9 @@ impl Scheduler { pub fn work_sync<'a>(&'a mut self) -> Vec> { let mut committed_mutations = Vec::new(); - self.manually_poll_events(); + while let Ok(Some(msg)) = self.receiver.try_next() { + self.handle_channel_msg(msg); + } if !self.has_any_work() { return committed_mutations; @@ -534,23 +531,3 @@ impl Scheduler { } } } - -pub(crate) struct PriorityLane { - pub dirty_scopes: IndexSet, - pub saved_state: Option>, - pub in_progress: bool, -} - -impl PriorityLane { - pub fn new() -> Self { - Self { - saved_state: None, - dirty_scopes: Default::default(), - in_progress: false, - } - } - - fn has_work(&self) -> bool { - !self.dirty_scopes.is_empty() || self.in_progress - } -} diff --git a/packages/core/src/tasks.rs b/packages/core/src/tasks.rs index 8306eb529..95540d153 100644 --- a/packages/core/src/tasks.rs +++ b/packages/core/src/tasks.rs @@ -12,28 +12,28 @@ impl TaskHandle { /// This method is not synchronous - your task will not stop immediately. pub fn toggle(&self) { self.sender - .unbounded_send(SchedulerMsg::ToggleTask(self.our_id)) + .unbounded_send(SchedulerMsg::Task(TaskMsg::ToggleTask(self.our_id))) .unwrap() } /// This method is not synchronous - your task will not stop immediately. pub fn resume(&self) { self.sender - .unbounded_send(SchedulerMsg::ResumeTask(self.our_id)) + .unbounded_send(SchedulerMsg::Task(TaskMsg::ResumeTask(self.our_id))) .unwrap() } /// This method is not synchronous - your task will not stop immediately. pub fn stop(&self) { self.sender - .unbounded_send(SchedulerMsg::ToggleTask(self.our_id)) + .unbounded_send(SchedulerMsg::Task(TaskMsg::ToggleTask(self.our_id))) .unwrap() } /// This method is not synchronous - your task will not stop immediately. pub fn restart(&self) { self.sender - .unbounded_send(SchedulerMsg::ToggleTask(self.our_id)) + .unbounded_send(SchedulerMsg::Task(TaskMsg::ToggleTask(self.our_id))) .unwrap() } } diff --git a/packages/core/src/virtual_dom.rs b/packages/core/src/virtual_dom.rs index 6c2233f13..1c732d862 100644 --- a/packages/core/src/virtual_dom.rs +++ b/packages/core/src/virtual_dom.rs @@ -334,11 +334,8 @@ impl VirtualDom { /// applied the edits. /// /// Mutations are the only link between the RealDOM and the VirtualDOM. - pub async fn run_with_deadline( - &mut self, - deadline: impl Future, - ) -> Vec> { - self.scheduler.work_with_deadline(deadline).await + pub fn run_with_deadline(&mut self, deadline: impl FnMut() -> bool) -> Vec> { + self.scheduler.work_with_deadline(deadline) } pub fn get_event_sender(&self) -> futures_channel::mpsc::UnboundedSender { @@ -355,12 +352,38 @@ impl VirtualDom { log::debug!("No active work.... waiting for some..."); use futures_util::StreamExt; + + // right now this won't poll events if there is ongoing work + // in the future we want to prioritize some events over ongoing work + // this is coming in the "priorities" PR + + // Wait for any new events if we have nothing to do + // todo: poll the events once even if there is work to do to prevent starvation futures_util::select! { - // // hmm - will this resolve to none if there are no async tasks? - // _ = self.scheduler.async_tasks.next() => { - // log::debug!("async task completed!"); - // } - msg = self.scheduler.receiver.next() => self.scheduler.handle_channel_msg(msg.unwrap()), + _ = self.scheduler.async_tasks.next() => {} + msg = self.scheduler.receiver.next() => { + match msg.unwrap() { + SchedulerMsg::Task(t) => todo!(), + SchedulerMsg::Immediate(im) => { + self.scheduler.dirty_scopes.insert(im); + } + SchedulerMsg::UiEvent(evt) => { + self.scheduler.ui_events.push_back(evt); + } + } + }, + } + + while let Ok(Some(msg)) = self.scheduler.receiver.try_next() { + match msg { + SchedulerMsg::Task(t) => todo!(), + SchedulerMsg::Immediate(im) => { + self.scheduler.dirty_scopes.insert(im); + } + SchedulerMsg::UiEvent(evt) => { + self.scheduler.ui_events.push_back(evt); + } + } } } } diff --git a/packages/web/src/lib.rs b/packages/web/src/lib.rs index a6588f07a..dc4b2373c 100644 --- a/packages/web/src/lib.rs +++ b/packages/web/src/lib.rs @@ -61,6 +61,7 @@ use dioxus::prelude::Properties; use dioxus::virtual_dom::VirtualDom; pub use dioxus_core as dioxus; use dioxus_core::prelude::FC; +use futures_util::FutureExt; mod cache; mod cfg; @@ -145,10 +146,10 @@ pub async fn run_with_props(root: FC, root_props: T, dom.wait_for_work().await; // wait for the mainthread to schedule us in - let deadline = work_loop.wait_for_idle_time().await; + let mut deadline = work_loop.wait_for_idle_time().await; // run the virtualdom work phase until the frame deadline is reached - let mutations = dom.run_with_deadline(deadline).await; + let mutations = dom.run_with_deadline(|| (&mut deadline).now_or_never().is_some()); // wait for the animation frame to fire so we can apply our changes work_loop.wait_for_raf().await; diff --git a/packages/web/src/ric_raf.rs b/packages/web/src/ric_raf.rs index 0a914540c..08a3b89f5 100644 --- a/packages/web/src/ric_raf.rs +++ b/packages/web/src/ric_raf.rs @@ -33,8 +33,10 @@ impl RafLoop { .call0(&JsValue::NULL) .unwrap(); + let window = web_sys::window().unwrap(); + Self { - window: web_sys::window().unwrap(), + window, raf_receiver, raf_closure, ric_receiver, From f15e1fa892070f9cf1fc627eb9c36fbaa0dc309b Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Wed, 22 Sep 2021 01:29:14 -0400 Subject: [PATCH 27/35] wip: broken diff --- packages/web/src/lib.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/web/src/lib.rs b/packages/web/src/lib.rs index dc4b2373c..6c4054b77 100644 --- a/packages/web/src/lib.rs +++ b/packages/web/src/lib.rs @@ -133,6 +133,7 @@ pub async fn run_with_props(root: FC, root_props: T, // hydrating is simply running the dom for a single render. If the page is already written, then the corresponding // ElementIds should already line up because the web_sys dom has already loaded elements with the DioxusID into memory if !should_hydrate { + log::info!("Applying rebuild edits..., {:?}", mutations); websys_dom.process_edits(&mut mutations.edits); } else { // websys dom processed the config and hydrated the dom already From c7d2d9d050548f06244caeeedc2c0e0e52eb329f Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Wed, 22 Sep 2021 01:37:45 -0400 Subject: [PATCH 28/35] wip: fix invalid is_empty bug --- packages/core/src/diff.rs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/packages/core/src/diff.rs b/packages/core/src/diff.rs index 34ec66644..838359e82 100644 --- a/packages/core/src/diff.rs +++ b/packages/core/src/diff.rs @@ -328,8 +328,8 @@ impl<'bump> DiffMachine<'bump> { self.mutations.set_attribute(attr); } - if children.is_empty() { - self.stack.element_id_stack.push(real_id); + if !children.is_empty() { + // self.stack.element_id_stack.push(real_id); // push our element_id onto the stack // drop our element off the stack self.stack.create_children(children, MountType::Append); @@ -522,6 +522,10 @@ impl<'bump> DiffMachine<'bump> { } } + if has_comitted { + self.mutations.pop(); + } + self.diff_children(old.children, new.children); } From d7940aa2ac2017316d62e0f2eac0701dc6ad1f09 Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Wed, 22 Sep 2021 02:44:01 -0400 Subject: [PATCH 29/35] feat: proper handling of events --- packages/core/examples/vdom_usage.rs | 5 +- packages/core/src/diff.rs | 2 + packages/core/src/scheduler.rs | 78 +++++++++++++++++++++------- packages/core/src/scope.rs | 14 ++--- packages/web/examples/basic.rs | 59 +++++++++++---------- packages/web/src/dom.rs | 18 +++---- packages/web/src/lib.rs | 2 + 7 files changed, 116 insertions(+), 62 deletions(-) diff --git a/packages/core/examples/vdom_usage.rs b/packages/core/examples/vdom_usage.rs index 1793b030b..7175a167d 100644 --- a/packages/core/examples/vdom_usage.rs +++ b/packages/core/examples/vdom_usage.rs @@ -4,12 +4,13 @@ use dioxus_core::prelude::*; #[async_std::main] async fn main() { - static App: FC<()> = |cx, props|cx.render(LazyNodes::new(|f| f.text(format_args!("hello")))); + static App: FC<()> = |cx, props| cx.render(LazyNodes::new(|f| f.text(format_args!("hello")))); let mut dom = VirtualDom::new(App); dom.rebuild(); let deadline = async_std::task::sleep(Duration::from_millis(50)); - let _fut = dom.run_with_deadline(deadline); + + // let _fut = dom.run_with_deadline(|| deadline.); } diff --git a/packages/core/src/diff.rs b/packages/core/src/diff.rs index 838359e82..b360cba35 100644 --- a/packages/core/src/diff.rs +++ b/packages/core/src/diff.rs @@ -185,6 +185,7 @@ impl<'bump> DiffMachine<'bump> { /// Returns a `bool` indicating that the work completed properly. pub fn work(&mut self, mut deadline_expired: impl FnMut() -> bool) -> bool { while let Some(instruction) = self.stack.pop() { + log::debug!("working {:?}", instruction); match instruction { DiffInstruction::Diff { old, new } => self.diff_node(old, new), DiffInstruction::Create { node } => self.create_node(node), @@ -194,6 +195,7 @@ impl<'bump> DiffMachine<'bump> { }; if deadline_expired() { + log::debug!("Deadling expired before we could finished!"); return false; } } diff --git a/packages/core/src/scheduler.rs b/packages/core/src/scheduler.rs index 922cfa2fa..588b496a4 100644 --- a/packages/core/src/scheduler.rs +++ b/packages/core/src/scheduler.rs @@ -228,6 +228,12 @@ impl Scheduler { ScopeId(0) }) as FiberTask); + let saved_state = SavedDiffWork { + mutations: Mutations::new(), + stack: DiffStack::new(), + seen_scopes: Default::default(), + }; + Self { pool, @@ -250,7 +256,7 @@ impl Scheduler { garbage_scopes: HashSet::new(), dirty_scopes: Default::default(), - saved_state: None, + saved_state: Some(saved_state), in_progress: false, } } @@ -262,7 +268,7 @@ impl Scheduler { if let Some(scope) = self.pool.get_scope_mut(event.scope) { if let Some(element) = event.mounted_dom_id { // TODO: bubble properly here - scope.call_listener(event.event, element); + scope.call_listener(event, element); while let Ok(Some(dirty_scope)) = self.receiver.try_next() { // @@ -321,7 +327,7 @@ impl Scheduler { if let Some(scope) = self.pool.get_scope_mut(event.scope) { if let Some(element) = event.mounted_dom_id { // TODO: bubble properly here - scope.call_listener(event.event, element); + scope.call_listener(event, element); while let Ok(Some(dirty_scope)) = self.receiver.try_next() { // @@ -360,31 +366,55 @@ impl Scheduler { h1.cmp(&h2) }); - if let Some(scope) = self.dirty_scopes.pop() { - let component = self.pool.get_scope(scope).unwrap(); + 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 }); } } - let deadline_expired = machine.work(deadline_reached); + let work_completed = machine.work(deadline_reached); - let machine: DiffMachine<'static> = unsafe { std::mem::transmute(machine) }; - let mut saved = machine.save(); + log::debug!("Working finished? {:?}", work_completed); - if deadline_expired { - self.save_work(saved); - false - } else { - for node in saved.seen_scopes.drain() { + log::debug!("raw edits {:?}", machine.mutations.edits); + + let mut machine: DiffMachine<'static> = unsafe { std::mem::transmute(machine) }; + // let mut saved = machine.save(); + + if work_completed { + for node in machine.seen_scopes.drain() { + // self.dirty_scopes.clear(); + // self.ui_events.clear(); self.dirty_scopes.remove(&node); + // self.dirty_scopes.remove(&node); } let mut new_mutations = Mutations::new(); - std::mem::swap(&mut new_mutations, &mut saved.mutations); + + for edit in machine.mutations.edits.drain(..) { + new_mutations.edits.push(edit); + } + + // for edit in saved.edits.drain(..) { + // new_mutations.edits.push(edit); + // } + + // std::mem::swap(&mut new_mutations, &mut saved.mutations); mutations.push(new_mutations); + + log::debug!("saved edits {:?}", mutations); + + let mut saved = machine.save(); self.save_work(saved); + false + + // self.save_work(saved); + // false + } else { true } } @@ -429,18 +459,28 @@ impl Scheduler { */ let mut committed_mutations = Vec::>::new(); - loop { + while self.has_any_work() { // switch our priority, pop off any work for event in self.ui_events.drain(..) { if let Some(scope) = self.pool.get_scope_mut(event.scope) { if let Some(element) = event.mounted_dom_id { + log::info!("Calling listener {:?}, {:?}", event.scope, element); + // TODO: bubble properly here - scope.call_listener(event.event, element); + scope.call_listener(event, element); while let Ok(Some(dirty_scope)) = self.receiver.try_next() { match dirty_scope { SchedulerMsg::Immediate(im) => { - self.dirty_scopes.insert(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!() + } + } } _ => todo!(), } @@ -449,10 +489,10 @@ impl Scheduler { } } - let finished_before_deadline = + let deadline_expired = self.work_on_current_lane(&mut deadline, &mut committed_mutations); - if !finished_before_deadline { + if deadline_expired { break; } } diff --git a/packages/core/src/scope.rs b/packages/core/src/scope.rs index 4ae5d0d1a..19006042e 100644 --- a/packages/core/src/scope.rs +++ b/packages/core/src/scope.rs @@ -238,22 +238,24 @@ impl Scope { } /// A safe wrapper around calling listeners - pub(crate) fn call_listener(&mut self, event: SyntheticEvent, element: ElementId) { + pub(crate) fn call_listener(&mut self, event: UserEvent, element: ElementId) { let listners = self.listeners.borrow_mut(); let raw_listener = listners.iter().find(|lis| { let search = unsafe { &***lis }; - let search_id = search.mounted_node.get(); - - // this assumes the node might not be mounted - should we assume that though? - search_id.map(|f| f == element).unwrap_or(false) + if search.event == event.name { + let search_id = search.mounted_node.get(); + search_id.map(|f| f == element).unwrap_or(false) + } else { + false + } }); if let Some(raw_listener) = raw_listener { let listener = unsafe { &**raw_listener }; let mut cb = listener.callback.borrow_mut(); if let Some(cb) = cb.as_mut() { - (cb)(event); + (cb)(event.event); } } else { log::warn!("An event was triggered but there was no listener to handle it"); diff --git a/packages/web/examples/basic.rs b/packages/web/examples/basic.rs index 56eb1f74d..2badc320a 100644 --- a/packages/web/examples/basic.rs +++ b/packages/web/examples/basic.rs @@ -22,36 +22,43 @@ fn main() { dioxus_web::launch(APP, |c| c) } -static APP: FC<()> = |cx, props|{ +static APP: FC<()> = |cx, props| { let mut count = use_state(cx, || 3); cx.render(rsx! { - div { - button { - onclick: move |_| count += 1, - "Click to add." - "Current count: {count}" - } - ul { - {(0..*count).map(|f| rsx!{ - li { "a - {f}" } - li { "b - {f}" } - li { "c - {f}" } - })} - } - Child {} + button { + // onclick: move |_| count += 1, + onmouseover: move |_| count += 5, + onmouseout: move |_| count -= 5, + "Click to add." + "Current count: {count}" } + // div { + // button { + // onclick: move |_| count += 1, + // "Click to add." + // "Current count: {count}" + // } + // ul { + // {(0..*count).map(|f| rsx!{ + // li { "a - {f}" } + // li { "b - {f}" } + // li { "c - {f}" } + // })} + // } + // Child {} + // } }) }; -static Child: FC<()> = |cx, props|{ - cx.render(rsx! { - div { - div { - div { - "hello child" - } - } - } - }) -}; +// static Child: FC<()> = |cx, props| { +// cx.render(rsx! { +// div { +// div { +// div { +// "hello child" +// } +// } +// } +// }) +// }; diff --git a/packages/web/src/dom.rs b/packages/web/src/dom.rs index 3957a1f74..80c87f967 100644 --- a/packages/web/src/dom.rs +++ b/packages/web/src/dom.rs @@ -7,9 +7,9 @@ use dioxus_core::{ DomEdit, ElementId, ScopeId, }; use fxhash::FxHashMap; -use wasm_bindgen::{closure::Closure, JsCast}; +use wasm_bindgen::{closure::Closure, JsCast, JsValue}; use web_sys::{ - window, CssStyleDeclaration, Document, Element, Event, HtmlElement, HtmlInputElement, + window, Attr, CssStyleDeclaration, Document, Element, Event, HtmlElement, HtmlInputElement, HtmlOptionElement, Node, NodeList, UiEvent, }; @@ -264,8 +264,8 @@ impl WebsysDom { ) .unwrap(); - el.set_attribute(&format!("dioxus-event"), &format!("{}", event)) - .unwrap(); + // el.set_attribute(&format!("dioxus-event"), &format!("{}", event)) + // .unwrap(); // Register the callback to decode @@ -532,11 +532,11 @@ fn decode_trigger(event: &web_sys::Event) -> anyhow::Result { log::debug!("Event type is {:?}", typ); - // let attrs = target.attributes(); - // for x in 0..attrs.length() { - // let attr = attrs.item(x).unwrap(); - // log::debug!("attrs include: {:#?}", attr); - // } + let attrs = target.attributes(); + for x in 0..attrs.length() { + let attr: Attr = attrs.item(x).unwrap(); + log::debug!("attrs include: {:#?}, {:#?}", attr.name(), attr.value()); + } use anyhow::Context; diff --git a/packages/web/src/lib.rs b/packages/web/src/lib.rs index 6c4054b77..c4ae4e9bf 100644 --- a/packages/web/src/lib.rs +++ b/packages/web/src/lib.rs @@ -152,6 +152,8 @@ pub async fn run_with_props(root: FC, root_props: T, // run the virtualdom work phase until the frame deadline is reached let mutations = dom.run_with_deadline(|| (&mut deadline).now_or_never().is_some()); + log::debug!("received mutations {:#?}", mutations); + // wait for the animation frame to fire so we can apply our changes work_loop.wait_for_raf().await; From 84b5ddded57238a64e966ec07334e6f9bd86ecf8 Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Wed, 22 Sep 2021 03:22:15 -0400 Subject: [PATCH 30/35] feat: select figured out --- packages/web/examples/basic.rs | 73 +++++++++++++++++++--------------- packages/web/src/dom.rs | 26 ++++++------ packages/web/src/events.rs | 8 +++- 3 files changed, 61 insertions(+), 46 deletions(-) diff --git a/packages/web/examples/basic.rs b/packages/web/examples/basic.rs index 2badc320a..c41b34d7d 100644 --- a/packages/web/examples/basic.rs +++ b/packages/web/examples/basic.rs @@ -26,39 +26,48 @@ static APP: FC<()> = |cx, props| { let mut count = use_state(cx, || 3); cx.render(rsx! { - button { - // onclick: move |_| count += 1, - onmouseover: move |_| count += 5, - onmouseout: move |_| count -= 5, - "Click to add." - "Current count: {count}" + div { + button { + onclick: move |_| count += 1, + "Click to add." + "Current count: {count}" + } + select { + name:"cars" + id:"cars" + oninput: move |ev| { + match ev.value().as_str() { + "h1" => count.set(0), + "h2" => count.set(5), + "h3" => count.set(10), + s => { + log::debug!("real value is {}", s); + } + } + }, + option { value: "h1", "h1" } + option { value: "h2", "h2" } + option { value: "h3", "h3" } + } + + ul { + {(0..*count).map(|f| rsx!{ + li { "a - {f}" } + li { "b - {f}" } + li { "c - {f}" } + })} + } + Child {} } - // div { - // button { - // onclick: move |_| count += 1, - // "Click to add." - // "Current count: {count}" - // } - // ul { - // {(0..*count).map(|f| rsx!{ - // li { "a - {f}" } - // li { "b - {f}" } - // li { "c - {f}" } - // })} - // } - // Child {} - // } }) }; -// static Child: FC<()> = |cx, props| { -// cx.render(rsx! { -// div { -// div { -// div { -// "hello child" -// } -// } -// } -// }) -// }; +static Child: FC<()> = |cx, props| { + cx.render(rsx! { + div { + div { + "hello child" + } + } + }) +}; diff --git a/packages/web/src/dom.rs b/packages/web/src/dom.rs index 80c87f967..56326b58d 100644 --- a/packages/web/src/dom.rs +++ b/packages/web/src/dom.rs @@ -445,7 +445,7 @@ fn virtual_event_from_websys_event(event: web_sys::Event) -> SyntheticEvent { SyntheticEvent::ClipboardEvent(ClipboardEvent(Rc::new(WebsysClipboardEvent(event)))) } "compositionend" | "compositionstart" | "compositionupdate" => { - let evt: web_sys::CompositionEvent = event.clone().dyn_into().unwrap(); + let evt: web_sys::CompositionEvent = event.dyn_into().unwrap(); SyntheticEvent::CompositionEvent(CompositionEvent(Rc::new(WebsysCompositionEvent(evt)))) } "keydown" | "keypress" | "keyup" => { @@ -461,57 +461,57 @@ fn virtual_event_from_websys_event(event: web_sys::Event) -> SyntheticEvent { SyntheticEvent::GenericEvent(GenericEvent(Rc::new(WebsysGenericUiEvent(evt)))) } "input" | "invalid" | "reset" | "submit" => { - let evt: web_sys::InputEvent = event.clone().dyn_into().unwrap(); + let evt: web_sys::Event = event.dyn_into().unwrap(); SyntheticEvent::FormEvent(FormEvent(Rc::new(WebsysFormEvent(evt)))) } "click" | "contextmenu" | "doubleclick" | "drag" | "dragend" | "dragenter" | "dragexit" | "dragleave" | "dragover" | "dragstart" | "drop" | "mousedown" | "mouseenter" | "mouseleave" | "mousemove" | "mouseout" | "mouseover" | "mouseup" => { - let evt: web_sys::MouseEvent = event.clone().dyn_into().unwrap(); + let evt: web_sys::MouseEvent = event.dyn_into().unwrap(); SyntheticEvent::MouseEvent(MouseEvent(Rc::new(WebsysMouseEvent(evt)))) } "pointerdown" | "pointermove" | "pointerup" | "pointercancel" | "gotpointercapture" | "lostpointercapture" | "pointerenter" | "pointerleave" | "pointerover" | "pointerout" => { - let evt: web_sys::PointerEvent = event.clone().dyn_into().unwrap(); + let evt: web_sys::PointerEvent = event.dyn_into().unwrap(); SyntheticEvent::PointerEvent(PointerEvent(Rc::new(WebsysPointerEvent(evt)))) } "select" => { - let evt: web_sys::UiEvent = event.clone().dyn_into().unwrap(); + let evt: web_sys::UiEvent = event.dyn_into().unwrap(); SyntheticEvent::SelectionEvent(SelectionEvent(Rc::new(WebsysGenericUiEvent(evt)))) } "touchcancel" | "touchend" | "touchmove" | "touchstart" => { - let evt: web_sys::TouchEvent = event.clone().dyn_into().unwrap(); + let evt: web_sys::TouchEvent = event.dyn_into().unwrap(); SyntheticEvent::TouchEvent(TouchEvent(Rc::new(WebsysTouchEvent(evt)))) } "scroll" => { - let evt: web_sys::UiEvent = event.clone().dyn_into().unwrap(); + let evt: web_sys::UiEvent = event.dyn_into().unwrap(); SyntheticEvent::GenericEvent(GenericEvent(Rc::new(WebsysGenericUiEvent(evt)))) } "wheel" => { - let evt: web_sys::WheelEvent = event.clone().dyn_into().unwrap(); + let evt: web_sys::WheelEvent = event.dyn_into().unwrap(); SyntheticEvent::WheelEvent(WheelEvent(Rc::new(WebsysWheelEvent(evt)))) } "animationstart" | "animationend" | "animationiteration" => { - let evt: web_sys::AnimationEvent = event.clone().dyn_into().unwrap(); + let evt: web_sys::AnimationEvent = event.dyn_into().unwrap(); SyntheticEvent::AnimationEvent(AnimationEvent(Rc::new(WebsysAnimationEvent(evt)))) } "transitionend" => { - let evt: web_sys::TransitionEvent = event.clone().dyn_into().unwrap(); + let evt: web_sys::TransitionEvent = event.dyn_into().unwrap(); SyntheticEvent::TransitionEvent(TransitionEvent(Rc::new(WebsysTransitionEvent(evt)))) } "abort" | "canplay" | "canplaythrough" | "durationchange" | "emptied" | "encrypted" | "ended" | "error" | "loadeddata" | "loadedmetadata" | "loadstart" | "pause" | "play" | "playing" | "progress" | "ratechange" | "seeked" | "seeking" | "stalled" | "suspend" | "timeupdate" | "volumechange" | "waiting" => { - let evt: web_sys::UiEvent = event.clone().dyn_into().unwrap(); + let evt: web_sys::UiEvent = event.dyn_into().unwrap(); SyntheticEvent::MediaEvent(MediaEvent(Rc::new(WebsysMediaEvent(evt)))) } "toggle" => { - let evt: web_sys::UiEvent = event.clone().dyn_into().unwrap(); + let evt: web_sys::UiEvent = event.dyn_into().unwrap(); SyntheticEvent::ToggleEvent(ToggleEvent(Rc::new(WebsysToggleEvent(evt)))) } _ => { - let evt: web_sys::UiEvent = event.clone().dyn_into().unwrap(); + let evt: web_sys::UiEvent = event.dyn_into().unwrap(); SyntheticEvent::GenericEvent(GenericEvent(Rc::new(WebsysGenericUiEvent(evt)))) } } diff --git a/packages/web/src/events.rs b/packages/web/src/events.rs index ff4ced474..7257fad2a 100644 --- a/packages/web/src/events.rs +++ b/packages/web/src/events.rs @@ -241,7 +241,7 @@ impl SelectionEventInner for WebsysGenericUiEvent {} pub struct WebsysFocusEvent(pub web_sys::FocusEvent); impl FocusEventInner for WebsysFocusEvent {} -pub struct WebsysFormEvent(pub web_sys::InputEvent); +pub struct WebsysFormEvent(pub web_sys::Event); impl FormEventInner for WebsysFormEvent { // technically a controlled component, so we need to manually grab out the target data fn value(&self) -> String { @@ -254,6 +254,12 @@ impl FormEventInner for WebsysFormEvent { .dyn_ref() .map(|input: &web_sys::HtmlTextAreaElement| input.value()) }) + // select elements are NOT input events - because - why woudn't they be?? + .or_else(|| { + this + .dyn_ref() + .map(|input: &web_sys::HtmlSelectElement| input.value()) + }) .or_else(|| { this .dyn_ref::() From 4b6ca05f2c3ad647842c858967da9c87f1915825 Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Wed, 22 Sep 2021 04:11:27 -0400 Subject: [PATCH 31/35] wip: performance looks good, needs more testing --- packages/core/src/diff.rs | 5 ++--- packages/core/src/nodes.rs | 5 +++++ packages/core/src/scheduler.rs | 20 +++++++++--------- packages/hooks/src/usestate.rs | 8 +++---- packages/web/Cargo.toml | 1 + packages/web/examples/basic.rs | 38 +++++++++++++++++++++------------- packages/web/src/dom.rs | 8 +++---- packages/web/src/lib.rs | 2 -- packages/web/src/ric_raf.rs | 11 ++++++---- 9 files changed, 56 insertions(+), 42 deletions(-) diff --git a/packages/core/src/diff.rs b/packages/core/src/diff.rs index b360cba35..3e4ecfcfc 100644 --- a/packages/core/src/diff.rs +++ b/packages/core/src/diff.rs @@ -185,7 +185,6 @@ impl<'bump> DiffMachine<'bump> { /// Returns a `bool` indicating that the work completed properly. pub fn work(&mut self, mut deadline_expired: impl FnMut() -> bool) -> bool { while let Some(instruction) = self.stack.pop() { - log::debug!("working {:?}", instruction); match instruction { DiffInstruction::Diff { old, new } => self.diff_node(old, new), DiffInstruction::Create { node } => self.create_node(node), @@ -351,6 +350,7 @@ impl<'bump> DiffMachine<'bump> { // Insert a new scope into our component list let parent_scope = self.vdom.get_scope(parent_idx).unwrap(); + let new_idx = self.vdom.insert_scope_with_key(|new_idx| { let height = parent_scope.height + 1; Scope::new( @@ -475,7 +475,7 @@ 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 { + if old_attr.value != new_attr.value || old_attr.is_volatile { please_commit(&mut self.mutations.edits); self.mutations.set_attribute(new_attr); } @@ -664,7 +664,6 @@ impl<'bump> DiffMachine<'bump> { // the change list stack is in the same state when this function returns. fn diff_non_keyed_children(&mut self, old: &'bump [VNode<'bump>], new: &'bump [VNode<'bump>]) { // Handled these cases in `diff_children` before calling this function. - log::debug!("diffing non-keyed case"); debug_assert!(!new.is_empty()); debug_assert!(!old.is_empty()); diff --git a/packages/core/src/nodes.rs b/packages/core/src/nodes.rs index 926a36d46..200bfa861 100644 --- a/packages/core/src/nodes.rs +++ b/packages/core/src/nodes.rs @@ -429,6 +429,11 @@ 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 588b496a4..d64b6c651 100644 --- a/packages/core/src/scheduler.rs +++ b/packages/core/src/scheduler.rs @@ -379,7 +379,7 @@ impl Scheduler { log::debug!("Working finished? {:?}", work_completed); - log::debug!("raw edits {:?}", machine.mutations.edits); + // log::debug!("raw edits {:?}", machine.mutations.edits); let mut machine: DiffMachine<'static> = unsafe { std::mem::transmute(machine) }; // let mut saved = machine.save(); @@ -406,16 +406,16 @@ impl Scheduler { mutations.push(new_mutations); - log::debug!("saved edits {:?}", mutations); + // log::debug!("saved edits {:?}", mutations); let mut saved = machine.save(); self.save_work(saved); - false + true // self.save_work(saved); // false } else { - true + false } } @@ -461,7 +461,7 @@ impl Scheduler { while self.has_any_work() { // switch our priority, pop off any work - for event in self.ui_events.drain(..) { + while let Some(event) = self.ui_events.pop_front() { if let Some(scope) = self.pool.get_scope_mut(event.scope) { if let Some(element) = event.mounted_dom_id { log::info!("Calling listener {:?}, {:?}", event.scope, element); @@ -482,18 +482,18 @@ impl Scheduler { } } } - _ => todo!(), + SchedulerMsg::UiEvent(e) => self.ui_events.push_back(e), + SchedulerMsg::Task(_) => todo!(), } } } } } - let deadline_expired = - self.work_on_current_lane(&mut deadline, &mut committed_mutations); + let work_complete = self.work_on_current_lane(&mut deadline, &mut committed_mutations); - if deadline_expired { - break; + if !work_complete { + return committed_mutations; } } diff --git a/packages/hooks/src/usestate.rs b/packages/hooks/src/usestate.rs index bb5ac85ca..bd67fd153 100644 --- a/packages/hooks/src/usestate.rs +++ b/packages/hooks/src/usestate.rs @@ -56,7 +56,7 @@ pub fn use_state<'a, 'c, T: 'static, F: FnOnce() -> T>( cx.use_hook( move |_| UseStateInner { current_val: initial_state_fn(), - callback: cx.schedule_update(), + update_callback: cx.schedule_update(), wip: Rc::new(RefCell::new(None)), update_scheuled: Cell::new(false), }, @@ -75,7 +75,7 @@ pub fn use_state<'a, 'c, T: 'static, F: FnOnce() -> T>( struct UseStateInner { current_val: T, update_scheuled: Cell, - callback: Rc, + update_callback: Rc, wip: Rc>>, } @@ -98,13 +98,13 @@ impl<'a, T: 'static> UseState<'a, T> { pub fn needs_update(&self) { if !self.inner.update_scheuled.get() { self.inner.update_scheuled.set(true); - (self.inner.callback)(); + (self.inner.update_callback)(); } } pub fn set(&self, new_val: T) { - self.needs_update(); *self.inner.wip.borrow_mut() = Some(new_val); + self.needs_update(); } pub fn get(&self) -> &'a T { diff --git a/packages/web/Cargo.toml b/packages/web/Cargo.toml index 7dc49bec9..9aa45c894 100644 --- a/packages/web/Cargo.toml +++ b/packages/web/Cargo.toml @@ -62,6 +62,7 @@ features = [ "SvgElement", "SvgAnimatedString", "HtmlOptionElement", + "IdleDeadline", ] [lib] diff --git a/packages/web/examples/basic.rs b/packages/web/examples/basic.rs index c41b34d7d..9539a2507 100644 --- a/packages/web/examples/basic.rs +++ b/packages/web/examples/basic.rs @@ -15,7 +15,7 @@ use std::{pin::Pin, time::Duration}; fn main() { // 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(); // Run the app @@ -25,26 +25,34 @@ fn main() { static APP: FC<()> = |cx, props| { let mut count = use_state(cx, || 3); + let mut content = use_state(cx, || String::new()); cx.render(rsx! { div { + input { + value: "{content}" + oninput: move |e| { + content.set(e.value()); + } + } button { onclick: move |_| count += 1, "Click to add." "Current count: {count}" } + select { - name:"cars" - id:"cars" + name: "cars" + id: "cars" + value: "h1" oninput: move |ev| { match ev.value().as_str() { "h1" => count.set(0), "h2" => count.set(5), "h3" => count.set(10), - s => { - log::debug!("real value is {}", s); - } + s => {} } }, + option { value: "h1", "h1" } option { value: "h2", "h2" } option { value: "h3", "h3" } @@ -56,18 +64,20 @@ static APP: FC<()> = |cx, props| { li { "b - {f}" } li { "c - {f}" } })} + } + + {render_bullets(cx)} + Child {} } }) }; -static Child: FC<()> = |cx, props| { - cx.render(rsx! { - div { - div { - "hello child" - } - } +fn render_bullets(cx: Context) -> DomTree { + rsx!(cx, div { + "bite me" }) -}; +} + +static Child: FC<()> = |cx, props| rsx!(cx, div {"hello child"}); diff --git a/packages/web/src/dom.rs b/packages/web/src/dom.rs index 56326b58d..97fc17132 100644 --- a/packages/web/src/dom.rs +++ b/packages/web/src/dom.rs @@ -520,8 +520,6 @@ fn virtual_event_from_websys_event(event: web_sys::Event) -> SyntheticEvent { /// This function decodes a websys event and produces an EventTrigger /// With the websys implementation, we attach a unique key to the nodes fn decode_trigger(event: &web_sys::Event) -> anyhow::Result { - log::debug!("Handling event!"); - let target = event .target() .expect("missing target") @@ -535,7 +533,7 @@ fn decode_trigger(event: &web_sys::Event) -> anyhow::Result { let attrs = target.attributes(); for x in 0..attrs.length() { let attr: Attr = attrs.item(x).unwrap(); - log::debug!("attrs include: {:#?}, {:#?}", attr.name(), attr.value()); + // log::debug!("attrs include: {:#?}, {:#?}", attr.name(), attr.value()); } use anyhow::Context; @@ -558,11 +556,11 @@ fn decode_trigger(event: &web_sys::Event) -> anyhow::Result { .context("failed to parse real id")?; // Call the trigger - log::debug!("decoded scope_id: {}, node_id: {:#?}", gi_id, real_id); + // log::debug!("decoded scope_id: {}, node_id: {:#?}", gi_id, real_id); let triggered_scope = gi_id; // let triggered_scope: ScopeId = KeyData::from_ffi(gi_id).into(); - log::debug!("Triggered scope is {:#?}", triggered_scope); + // log::debug!("Triggered scope is {:#?}", triggered_scope); Ok(UserEvent { name: event_name_from_typ(&typ), event: virtual_event_from_websys_event(event.clone()), diff --git a/packages/web/src/lib.rs b/packages/web/src/lib.rs index c4ae4e9bf..6c4054b77 100644 --- a/packages/web/src/lib.rs +++ b/packages/web/src/lib.rs @@ -152,8 +152,6 @@ pub async fn run_with_props(root: FC, root_props: T, // run the virtualdom work phase until the frame deadline is reached let mutations = dom.run_with_deadline(|| (&mut deadline).now_or_never().is_some()); - log::debug!("received mutations {:#?}", mutations); - // wait for the animation frame to fire so we can apply our changes work_loop.wait_for_raf().await; diff --git a/packages/web/src/ric_raf.rs b/packages/web/src/ric_raf.rs index 08a3b89f5..d472a9866 100644 --- a/packages/web/src/ric_raf.rs +++ b/packages/web/src/ric_raf.rs @@ -8,7 +8,7 @@ use web_sys::Window; pub struct RafLoop { window: Window, - ric_receiver: async_channel::Receiver<()>, + ric_receiver: async_channel::Receiver, raf_receiver: async_channel::Receiver<()>, ric_closure: Closure, raf_closure: Closure, @@ -25,7 +25,10 @@ impl RafLoop { let (ric_sender, ric_receiver) = async_channel::unbounded(); let ric_closure: Closure = Closure::wrap(Box::new(move |_v: JsValue| { - ric_sender.try_send(()).unwrap() + // + let deadline = _v.dyn_into::().unwrap(); + let time_remaining = deadline.time_remaining() as u32; + ric_sender.try_send(time_remaining).unwrap() })); // execute the polyfill for safari @@ -46,8 +49,8 @@ impl RafLoop { /// waits for some idle time and returns a timeout future that expires after the idle time has passed pub async fn wait_for_idle_time(&self) -> TimeoutFuture { let ric_fn = self.ric_closure.as_ref().dyn_ref::().unwrap(); - let deadline: u32 = self.window.request_idle_callback(ric_fn).unwrap(); - self.ric_receiver.recv().await.unwrap(); + let _cb_id: u32 = self.window.request_idle_callback(ric_fn).unwrap(); + let deadline = self.ric_receiver.recv().await.unwrap(); let deadline = TimeoutFuture::new(deadline); deadline } From 1a2f91ed91c13dae553ecde585462ab261b1b95d Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Fri, 24 Sep 2021 00:05:56 -0400 Subject: [PATCH 32/35] 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! { /// [`