diff --git a/.vscode/spellright.dict b/.vscode/spellright.dict index efbfd1aa3..476f28f10 100644 --- a/.vscode/spellright.dict +++ b/.vscode/spellright.dict @@ -46,3 +46,5 @@ impl destructured linting lodash +crates.io +utilties diff --git a/Cargo.toml b/Cargo.toml index 624473388..cd6fed8b1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,11 +5,11 @@ members = [ "packages/core", "packages/web", "packages/dioxus", - "packages/recoil", - "packages/docsite", "packages/ssr", - "packages/webview", ] +# "packages/recoil", +# "packages/docsite", +# "packages/webview", # "packages/cli", # "packages/webview", diff --git a/README.md b/README.md index 7210abde4..c38143995 100644 --- a/README.md +++ b/README.md @@ -8,15 +8,13 @@ Dioxus is a portable, performant, and ergonomic framework for building cross-platform user experiences in Rust. ```rust -fn Example(ctx: Context<()>) -> VNode { +fn Example(ctx: Context<()>) -> VNodes { let (selection, set_selection) = use_state(&ctx, move || "..?"); ctx.render(rsx! { - div { - h1 { "Hello, {selection}" } - button { "?", onclick: move |_| set_selection("world!")} - button { "?", onclick: move |_| set_selection("Dioxus 🎉")} - } + h1 { "Hello, {selection}" } + button { "?", onclick: move |_| set_selection("world!")} + button { "?", onclick: move |_| set_selection("Dioxus 🎉")} }) }; ``` diff --git a/docs/assets/01-setup-helloworld.png b/docs/assets/01-setup-helloworld.png new file mode 100644 index 000000000..9da58b932 Binary files /dev/null and b/docs/assets/01-setup-helloworld.png differ diff --git a/docs/main-concepts/00-index.md b/docs/main-concepts/00-index.md new file mode 100644 index 000000000..b4d938cae --- /dev/null +++ b/docs/main-concepts/00-index.md @@ -0,0 +1,3 @@ +# Getting Started + +This page is an overview of the Dioxus documentation. diff --git a/docs/main-concepts/01-setup.md b/docs/main-concepts/01-setup.md new file mode 100644 index 000000000..861d5a73f --- /dev/null +++ b/docs/main-concepts/01-setup.md @@ -0,0 +1,57 @@ +# Overview + +Dioxus aims to provide a fast, friendly, and portable toolkit for building user interfaces with Rust. + +This guide assumes you'll be building a SPA (single page application) for the web. The process for building desktop apps, server-rendered apps, static sites, and mobile apps, is more-or-less the same. You can check out the [Platform Specific Guides](../platforms/00-index.md) for more information on setting up Dioxus for any of the various targets you are building for. + +# Setting up Dioxus + +Dioxus requires a few main things to get up and running: + +- The [Rust compiler](https://www.rust-lang.org) and associated build tooling + +- An editor of your choice, with the [Rust-Analyzer LSP plugin](https://rust-analyzer.github.io) + +Dioxus integrates very well with the Rust-Analyzer IDE plugin which will provide appropriate syntax highlighting, code navigation, folding, and more. + +We also recommend installing the Dioxus CLI. The Dioxus CLI automates building and packaging for various targets and integrates with simulators, development servers, and app deployment. It'll be our one-stop-shop for anything related to building and sharing our Dioxus Apps. To install the CLI, you'll need cargo (should be automatically installed with Rust): + +``` +$ cargo install dioxus-cli +``` + +You can update the dioxus-cli at any time with: + +``` +$ cargo install --force dioxus-cli +``` + +If your version of the CLI is out of date, it'll remind you to update whenever a new version is uploaded to Rust's package manager [crates.io](https://crates.io). We use a dedicated 1st-party CLI to save you from having to run potentially untrusted code every time you add a crate to your project - as is standard in the NPM ecosystem. You can vet the source of the Dioxus-CLI yourself at its [GitHub repo](https://github.com/jkelleyrtp/dioxus/tree/master/packages/cli). + +If you want to keep your traditional `npm install XXX` workflow for adding packages, you might want to install `cargo-edit` and a few other fun `cargo` extensions: + +- [cargo edit](https://github.com/killercup/cargo-edit) for adding dependencies from the CLI +- [cargo-expand](https://github.com/dtolnay/cargo-expand) for expanding macro calls +- `cargo tree` - an integrated cargo command that lets you inspect your dependency tree + +That's it! We won't need to touch NPM/WebPack/Babel/Parcel, etc. + +# Important tools + +With Rust, things like benchmarking, testing, and documentation are included in the language. We strongly recommend going through the official Rust book _completely_. However, our hope is that a Dioxus app can serve as a great first project. With Dioxus you'll learn about: + +- Error handling +- Structs, Functions, Enums +- Closures +- Macros + +We've put a lot of care into making Dioxus syntax familiar and easy to understand, so you won't need knowledge on async, lifetimes, and smart pointers until you really start building complex Dioxus apps. + +We strongly encourage exploring the guides for more information on how to work with the integrated tooling: + +- [Testing](Testing.md) +- [Documentation](Documentation.md) +- [Benchmarking](Benchmarking.md) +- [Building](Building.md) +- [Modules](Modules.md) +- [Crates](Crates.md) diff --git a/docs/main-concepts/02-hello.md b/docs/main-concepts/02-hello.md new file mode 100644 index 000000000..a2e9deed5 --- /dev/null +++ b/docs/main-concepts/02-hello.md @@ -0,0 +1,142 @@ +# Hello World + +Let's put together a simple "hello world" to get acquainted with Dioxus. The Dioxus-CLI has an equivalent to "create-react-app" built-in, but setting up Dioxus apps is simple enough to not need additional tooling. + +First, let's start a new project. Rust has the concept of executables and libraries. Executables have a `main.rs` and libraries have `lib.rs`. A project may have both. Our `hello world` will be an executable - we expect our app to launch when we run it! Cargo provides this for us: + +```shell +$ cargo new --bin hello-dioxus +``` + +Now, we can `cd` into our project and poke around: + +```shell +$ cd hello-dioxus +$ tree +. +├── Cargo.toml +├── .git +├── .gitignore +└── src + └── main.rs +``` + +We are greeted with a pre-initialized git repository, our code folder (`src`) and our project file (`Cargo.toml`). + +Our `src` folder holds our code. Our `main.rs` file holds our `fn main` which will be executed when our app is ran. + +```shell +$ more src/main.rs +``` + +```rust +fn main() { + println!("Hello, world!"); +} +``` + +Right now, whenever our app is launched, "Hello world" will be echoed to the terminal. + +```shell +$ cargo run + Compiling hello-dioxus v0.1.0 + Finished dev [unoptimized + debuginfo] target(s) in 0.41s + Running `target/debug/hello-dioxus` +Hello, world! +``` + +Our `Cargo.toml` file holds our dependencies and project flags. + +```shell +$ cat Cargo.toml +``` + +```toml +[package] +name = "hello-dioxus" +version = "0.1.0" +edition = "2018" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] + +``` + +To use the Dioxus library, we'll want to add the most recent version of `Dioxus` to our crate. If you have `cargo edit` installed, simply call: + +```shell +$ cargo add dioxus --features web +``` + +It's very important to add `dioxus` with the `web` feature for this example. The `dioxus` crate is a batteries-include crate that combines a bunch of utility crates together, ensuring compatibility of the most important parts of the ecosystem. Under the hood, the `dioxus` crate configures various renderers, hooks, debug tooling, and more. The `web` feature ensures the we only depend on the smallest set of required crates to compile. + +If you plan to develop extensions for the `Dioxus` ecosystem, please use `dioxus-core` crate. The `dioxus` crate re-exports `dioxus-core` alongside other utilties. As a result, `dioxus-core` is much more stable than the batteries-included `dioxus` crate. + +Now, let's edit our `main.rs` file: + +```rust +use diouxs::prelude::*; + +fn main() { + dioxus::web::start(App) +} + +fn App(ctx: Context<()>) -> VNode { + ctx.render(rsx! { + div { "Hello, world!" } + }) +} +``` + +Let's dissect our example a bit. + +This bit of code imports everything from the the `prelude` module. This brings into scope the right traits, types, and macros needed for working with Dioxus. + +```rust +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. + +```rust +fn main() { + dioxus::web::start(App) +} +``` + +Finally, our app. Every component in Dioxus is a function that takes in a `Context` object and returns a `VNode`. + +```rust +fn App(ctx: Context<()>) -> VNode { + ctx.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. + +The `rsx!` macro is lazy: it does not immediately produce elements or allocates, but rather builds a closure which can be rendered with `ctx.render`. + +Now, let's launch our app in a development server: + +```shell +$ dioxus develop +``` + +Huzzah! We have a simple app. + +![Hello world](../assets/01-setup-helloworld.png) + +If we wanted to golf a bit, we can shrink our hello-world even smaller: + +```rust +fn main() { + dioxus::web::start(|ctx| ctx.render(diouxs::rsx!( div { "Hello, World!" } )) +} +``` + +Anyways, let's move on to something a bit more complex. diff --git a/docs/main-concepts/03-rsx.md b/docs/main-concepts/03-rsx.md new file mode 100644 index 000000000..c82e0802e --- /dev/null +++ b/docs/main-concepts/03-rsx.md @@ -0,0 +1,156 @@ +# VNode Macros + +Dioxus comes preloaded with two macros for creating VNodes. + +## html! macro + +The html! macro supports a limited subset of the html standard. This macro will happily accept a copy-paste from something like tailwind builder. However, writing HTML by hand is a bit tedious - IDE tools for Rust don't support linting/autocomplete/syntax highlighting. RSX is much more natural for Rust programs and _does_ integrate well with Rust IDE tools. + +There is also limited support for dynamic handlers, but it will function similarly to JSX. + +You'll want to write RSX where you can, and in a future release we'll have a tool that automatically converts HTML to RSX. + +```rust +#[fc] +fn Example(ctx: Context, name: &str, pending: bool, count: i32 ) -> VNode { + ctx.render(html! { +
+

"Hello, {name}!"

+

"Status: {pending}!"

+

"Count {count}!"

+
+ }) +} +``` + +## rsx! macro + +The rsx! macro is a VNode builder macro designed especially for Rust programs. Writing these should feel very natural, much like assembling a struct. VSCode also supports these with code folding, bracket-tabbing, bracket highlighting, and section selecting. + +The Dioxus VSCode extension will eventually provide a macro to convert a selection of html! template and turn it into rsx!, so you'll never need to transcribe templates by hand. + +It's also a bit easier on the eyes 🙂. + +```rust +#[fc] +fn Example(ctx: Context, name: &str, pending: bool, count: i32 ) -> VNode { + ctx.render(rsx! { + div { + p {"Hello, {name}!"} + p {"Status: {pending}!"} + p {"Count {count}!"} + } + }) +} + +``` + +Each element takes a comma-separated list of expressions to build the node. Roughly, here's how they work: + +- `name: value` sets the property on this element. +- `"text"` adds a new text element +- `tag {}` adds a new child element +- `CustomTag {}` adds a new child component +- `{expr}` pastes the `expr` tokens literally. They must be IntoCtx to work properly + +Commas are entirely optional, but might be useful to delineate between elements and attributes. + +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. + +```rust +static Example: FC<()> = |ctx| { + + let text = "example"; + + let g = async { + wait(10).await; + "hello" + }; + + let user_name = use_read_async(ctx, USERNAME); + let title = ctx.suspend(user_name, |user_name| rsx!{ h1 { "Welcome back, {user_name}" } }); + + + ctx.render(rsx!{ + div { + h1 { "Example" }, + + {title} + + // fstring interpolation + "{text}" + + p { + // Attributes + tag: "type", + + // Anything that implements display can be an attribute + abc: 123, + enabled: true, + + // attributes also supports interpolation + // `class` is not a restricted keyword unlike JS and ClassName + class: "big small wide short {text}", + + // bool-based classnames + classes: [("big", true), ("small", false)] + + // Bool-based props + // *must* be in the tuple form, cannot enter as a variable + tag: ("type", false) + + tag: {"these tokens are placed directly"} + + // Children + a { "abcder" }, + + // Children with attributes + h2 { "hello", class: "abc-123" }, + + // Child components + CustomComponent { a: 123, b: 456, key: "1" }, + + // Child components with paths + crate::components::CustomComponent { a: 123, b: 456, key: "1" }, + + // Iterators + { 0..3.map(|i| rsx!( h1 {"{:i}"} )) }, + + // More rsx!, or even html! + { rsx! { div { } } }, + { html! {
} }, + + // Matching + // Requires rendering the nodes first. + // rsx! is lazy, and the underlying closures cannot have the same type + // Rendering produces the VNode type + {match rand::gen_range::(1..3) { + 1 => rsx!(in ctx, h1 { "big" }) + 2 => rsx!(in ctx, h2 { "medium" }) + _ => rsx!(in ctx, h3 { "small" }) + }} + + // Optionals + {true.and_then(|f| rsx!{ h1 {"Conditional Rendering"} })} + + // Bool options + {(rsx!{ h1 {"Conditional Rendering"}, true)} + + // Child nodes + // Returns &[VNode] + {ctx.children()} + + // Duplicating nodes + // Clones the nodes by reference, so they are literally identical + {{ + let node = rsx!(in ctx, h1{ "TopNode" }); + (0..10).map(|_| node.clone()) + }} + + // Any expression that is `IntoVNode` + {expr} + } + } + }) +} +``` diff --git a/docs/main-concepts/testing.md b/docs/main-concepts/testing.md new file mode 100644 index 000000000..42f9af1d8 --- /dev/null +++ b/docs/main-concepts/testing.md @@ -0,0 +1,29 @@ +## Testing + +To test your Rust code, you can annotate any function with the `#[test]` block. In VSCode with RA, this will provide a lens to click and run the test. + +```rust +#[test] +fn component_runs() { + assert!(true) +} +``` + +This will test your Rust code _without_ going through the browser. This is ideal for squashing logic bugs and ensuring components render appropriately when the browsers's DOM is not needed. If you need to run tests in the browser, you can annotate your blocks with the `#[dioxus::test]` block. + +```rust +#[dioxus::test] +fn runs_in_browser() { + // ... +} +``` + +Then, when you run + +``` +$ dioxus test --chrome +``` + +Dioxus will build and test your code using the Chrome browser as a harness. + +There's a lot more to testing if you dive in, so check out the [Testing]() guide for more information diff --git a/docs/guides/00-index.md b/docs/platforms/00-index.md similarity index 98% rename from docs/guides/00-index.md rename to docs/platforms/00-index.md index 22548e951..551aa3523 100644 --- a/docs/guides/00-index.md +++ b/docs/platforms/00-index.md @@ -1,4 +1,7 @@ +# Welcome to Dioxus! + ## Running Examples + We use the dedicated `dioxus-cli` to build and test dioxus web-apps. This can run examples, tests, build web workers, launch development servers, bundle, and more. It's general purpose, but currently very tailored to Dioxus for liveview and bundling. If you've not used it before, `cargo install --path pacakages/dioxus-cli` will get it installed. This CLI tool should feel like using `cargo` but with 1st party support for assets, bundling, and other important dioxus-specific features. Alternatively, `trunk` works but can't run examples. diff --git a/docs/guides/01-ssr.md b/docs/platforms/01-ssr.md similarity index 100% rename from docs/guides/01-ssr.md rename to docs/platforms/01-ssr.md diff --git a/docs/guides/02-wasm.md b/docs/platforms/02-wasm.md similarity index 100% rename from docs/guides/02-wasm.md rename to docs/platforms/02-wasm.md diff --git a/docs/guides/03-desktop.md b/docs/platforms/03-desktop.md similarity index 100% rename from docs/guides/03-desktop.md rename to docs/platforms/03-desktop.md diff --git a/docs/guides/04-concurrency.md b/docs/platforms/04-concurrency.md similarity index 100% rename from docs/guides/04-concurrency.md rename to docs/platforms/04-concurrency.md diff --git a/docs/guides/05-liveview.md b/docs/platforms/05-liveview.md similarity index 100% rename from docs/guides/05-liveview.md rename to docs/platforms/05-liveview.md diff --git a/docs/guides/06-components.md b/docs/platforms/06-components.md similarity index 100% rename from docs/guides/06-components.md rename to docs/platforms/06-components.md diff --git a/docs/rocketsrchrome_files/cloud-0.png b/docs/rocketsrchrome_files/cloud-0.png deleted file mode 100644 index 9a3377af2..000000000 Binary files a/docs/rocketsrchrome_files/cloud-0.png and /dev/null differ diff --git a/docs/rocketsrchrome_files/cloud-1.png b/docs/rocketsrchrome_files/cloud-1.png deleted file mode 100644 index efe778e38..000000000 Binary files a/docs/rocketsrchrome_files/cloud-1.png and /dev/null differ diff --git a/docs/rocketsrchrome_files/cloud-2.png b/docs/rocketsrchrome_files/cloud-2.png deleted file mode 100644 index 2a7eef37b..000000000 Binary files a/docs/rocketsrchrome_files/cloud-2.png and /dev/null differ diff --git a/docs/rocketsrchrome_files/config-icon.svg b/docs/rocketsrchrome_files/config-icon.svg deleted file mode 100644 index 845f5932a..000000000 --- a/docs/rocketsrchrome_files/config-icon.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/docs/rocketsrchrome_files/cookies-icon.svg b/docs/rocketsrchrome_files/cookies-icon.svg deleted file mode 100644 index 0ba4c3593..000000000 --- a/docs/rocketsrchrome_files/cookies-icon.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/docs/rocketsrchrome_files/css b/docs/rocketsrchrome_files/css deleted file mode 100644 index a0fdfcc25..000000000 --- a/docs/rocketsrchrome_files/css +++ /dev/null @@ -1,32 +0,0 @@ -/* latin-ext */ -@font-face { - font-family: 'Proza Libre'; - font-style: normal; - font-weight: 400; - src: url(https://fonts.gstatic.com/s/prozalibre/v5/LYjGdGHgj0k1DIQRyUEyyEomdNr1Wpyyzzc.woff2) format('woff2'); - unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF; -} -/* latin */ -@font-face { - font-family: 'Proza Libre'; - font-style: normal; - font-weight: 400; - src: url(https://fonts.gstatic.com/s/prozalibre/v5/LYjGdGHgj0k1DIQRyUEyyEoodNr1Wpyy.woff2) format('woff2'); - unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; -} -/* latin-ext */ -@font-face { - font-family: 'Proza Libre'; - font-style: normal; - font-weight: 500; - src: url(https://fonts.gstatic.com/s/prozalibre/v5/LYjbdGHgj0k1DIQRyUEyyELbV8_WeJGY9T6Mh1k.woff2) format('woff2'); - unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF; -} -/* latin */ -@font-face { - font-family: 'Proza Libre'; - font-style: normal; - font-weight: 500; - src: url(https://fonts.gstatic.com/s/prozalibre/v5/LYjbdGHgj0k1DIQRyUEyyELbV8_YeJGY9T6M.woff2) format('woff2'); - unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; -} diff --git a/docs/rocketsrchrome_files/helmet.svg b/docs/rocketsrchrome_files/helmet.svg deleted file mode 100644 index 480458326..000000000 --- a/docs/rocketsrchrome_files/helmet.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/docs/rocketsrchrome_files/logo-small.svg b/docs/rocketsrchrome_files/logo-small.svg deleted file mode 100644 index b2e134820..000000000 --- a/docs/rocketsrchrome_files/logo-small.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/docs/rocketsrchrome_files/robot-free.svg b/docs/rocketsrchrome_files/robot-free.svg deleted file mode 100644 index 275e2e5dd..000000000 --- a/docs/rocketsrchrome_files/robot-free.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/docs/rocketsrchrome_files/ship-icon.svg b/docs/rocketsrchrome_files/ship-icon.svg deleted file mode 100644 index 581322d8c..000000000 --- a/docs/rocketsrchrome_files/ship-icon.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/docs/rocketsrchrome_files/streams-icon.svg b/docs/rocketsrchrome_files/streams-icon.svg deleted file mode 100644 index c65454527..000000000 --- a/docs/rocketsrchrome_files/streams-icon.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/docs/rocketsrchrome_files/style.css b/docs/rocketsrchrome_files/style.css deleted file mode 100644 index 4415ba380..000000000 --- a/docs/rocketsrchrome_files/style.css +++ /dev/null @@ -1,2033 +0,0 @@ -@keyframes octocat-wave { - 0%, - to { - transform: rotate(0); - } - 20%, - 60% { - transform: rotate(-25deg); - } - 40%, - 80% { - transform: rotate(10deg); - } -} -html { - line-height: 1.15; - -ms-text-size-adjust: 100%; - -webkit-text-size-adjust: 100%; -} -.features .col.tight .button, -.features .col.tight button, -body { - margin: 0; -} -article, -aside, -details, -figcaption, -figure, -footer, -header, -header nav.version-select:hover options, -main, -menu, -nav, -section { - display: block; -} -h1 { - margin: 0.67em 0; - font-size: 3em; -} -figure { - margin: 1em 40px; -} -hr { - box-sizing: content-box; - overflow: visible; - border: 0; - height: 1px; - background-color: rgba(255, 255, 255, 0.25); -} -kbd, -pre, -samp { - font-family: monospace, monospace; - font-size: 1em; -} -a { - background-color: transparent; - -webkit-text-decoration-skip: objects; - color: #fff; - text-decoration: none; -} -a:active, -a:hover { - outline-width: 0; -} -abbr[title] { - border-bottom: none; - text-decoration: underline dotted; -} -b, -strong { - font-weight: bolder; -} -dfn { - font-style: italic; -} -mark { - background-color: #ff0; - color: #000; -} -small { - font-size: 80%; -} -sub, -sup { - font-size: 75%; - line-height: 0; - position: relative; - vertical-align: baseline; -} -sub { - bottom: -0.25em; -} -sup { - top: -0.5em; -} -audio, -canvas, -video { - display: inline-block; -} -audio:not([controls]) { - display: none; - height: 0; -} -img { - border-style: none; -} -svg:not(:root) { - overflow: hidden; -} -button { - line-height: 1.15; -} -input, -optgroup, -select, -textarea { - font-family: sans-serif; - line-height: 1.15; - margin: 0; -} -optgroup { - font-size: 100%; -} -button, -input { - overflow: visible; -} -button, -select { - text-transform: none; -} -[type="reset"], -[type="submit"], -button, -html [type="button"] { - -webkit-appearance: button; -} -[type="button"]::-moz-focus-inner, -[type="reset"]::-moz-focus-inner, -[type="submit"]::-moz-focus-inner, -button::-moz-focus-inner { - border-style: none; - padding: 0; -} -[type="button"]:-moz-focusring, -[type="reset"]:-moz-focusring, -[type="submit"]:-moz-focusring, -button:-moz-focusring { - outline: 1px dotted ButtonText; -} -fieldset { - border: 1px solid silver; - margin: 0 2px; - padding: 0.35em 0.625em 0.75em; -} -legend { - color: inherit; - display: table; - max-width: 100%; - white-space: normal; -} -progress { - display: inline-block; - vertical-align: baseline; -} -textarea { - overflow: auto; -} -[type="checkbox"], -[type="radio"], -legend { - box-sizing: border-box; - padding: 0; -} -[type="number"]::-webkit-inner-spin-button, -[type="number"]::-webkit-outer-spin-button { - height: auto; -} -[type="search"] { - -webkit-appearance: textfield; - outline-offset: -2px; -} -[type="search"]::-webkit-search-cancel-button, -[type="search"]::-webkit-search-decoration { - -webkit-appearance: none; -} -::-webkit-file-upload-button { - -webkit-appearance: button; - font: inherit; -} -summary { - display: list-item; -} -[hidden], -template { - display: none; -} -body, -html { - -webkit-font-smoothing: antialiased; - background-color: black; - /* background-color: #d33847; */ -} -html { - color: #fff; -} -a.button, -body, -button, -html { - font-family: "Helvetica Neue", Helvetica, Arial, "Lucida Grande", sans-serif; -} -body { - font-size: 16px; -} -body, -input, -select, -textarea { - color: #fff; -} -.code { - font-family: Consolas, "Liberation Mono", Menlo, Monaco, Lucida Console, - Liberation Mono, DejaVu Sans Mono, Bitstream Vera Sans Mono, Courier New, - monospace, serif; -} -.container { - margin: 0 20px; - display: inherit; - position: relative; - width: calc(100% - 40px); -} -@media (min-width: 1310px) { - .container { - margin: auto; - width: 1270px; - } -} -h2 { - font-size: 2.5em; -} -h3 { - font-size: 1.6em; -} -h4 { - font-size: 1.2em; -} -.addon, -input, -select, -textarea { - outline: 0; - font-size: 14px; -} -ul.columns { - list-style-position: inside; - columns: 1; -} -@media (min-width: 650px) { - ul.columns { - columns: 2; - } -} -@media (min-width: 800px) { - ul.columns { - columns: 3; - } -} -a.button, -button { - font-size: 1.125em; - background: #999; - border-radius: 5px; - border: 0; - cursor: pointer; - display: inline-block; - padding: 8px 34px; - background: #fff; - color: #f03b4d; - font-weight: 500; - box-shadow: 0 0 5px 0 rgba(0, 0, 0, 0.15); - margin: 15px; - text-shadow: none; -} -a.button.small, -button.small { - font-size: 0.875em; - padding: 8px 26px; -} -a.button:hover, -button:hover { - background: #ffd1d6; - background: linear-gradient(#fff, #ffd1d6); - border: 0; - outline: 0; -} -a.button:active, -a.button:focus, -button:active, -button:focus { - background: #ffd1d6; - background: linear-gradient(#ffd1d6, #fff); - border: 0; - outline: 0; -} -a.button.inverted, -button.inverted { - background: 0 0; - color: #fff; - padding: 6px 30px; - border: 2px solid #fff; - font-weight: 500; -} -a.button.inverted.small, -button.inverted.small { - padding: 6px 24px; -} -a.button.inverted:hover, -button.inverted:hover { - background-color: rgba(255, 255, 255, 0.15); -} -a.button.inverted:active, -a.button.inverted:focus, -button.inverted:active, -button.inverted:focus { - background-color: rgba(0, 0, 0, 0.25); -} -a.button.inverted.blue, -button.inverted.blue { - border-color: #5daced; -} -a.button.inverted.purple, -button.inverted.purple { - border-color: #df76ff; -} -a.button.inverted.red, -button.inverted.red { - border-color: #ff6170; -} -a.button.inverted.yellow, -button.inverted.yellow { - border-color: #fff776; -} -a.button.inverted.orange, -button.inverted.orange { - border-color: #ff853e; -} -a.button.inverted.green, -button.inverted.green { - border-color: #15dc98; -} -.table-container { - overflow-x: auto; -} -table:not(.code) { - border-collapse: collapse; - border-spacing: 0; - font-family: "Helvetica Neue", Helvetica, Arial, "Lucida Grande", sans-serif; - text-shadow: none; - box-shadow: 0 2px 2px rgba(0, 0, 0, 0.25); - border-bottom: 1px solid #111; - border-top: 1px solid #111; -} -table:not(.code) thead { - background-color: #131313; - text-align: left; - border-bottom: 1px solid #111; -} -table:not(.code) td, -table:not(.code) th { - border-left: 1px solid #111; - padding: 0.5em 1.25em; -} -table:not(.code) td:last-child, -table:not(.code) th:last-child { - border-right: 1px solid #111; -} -table:not(.code) tbody > tr { - background: #303133; -} -table:not(.code) tbody > tr:nth-child(2n) { - background: #202020; -} -.grid { - display: flex; -} -@media (max-width: 870px) { - .grid { - display: block; - } -} -.col { - box-sizing: border-box; - flex: 1; -} -.col.center { - align-self: center; -} -.hl, -.hl-dark { - color: #abb2bf; -} -.hl .c, -.hl-dark .c { - color: #5c6370; - font-style: italic; -} -.hl .err, -.hl-dark .err { - color: #960050; - background-color: #1e0010; -} -.hl .k, -.hl-dark .k { - color: #c678dd; -} -.hl .l, -.hl-dark .l { - color: #98c379; -} -.hl .n, -.hl .o, -.hl .p, -.hl-dark .n, -.hl-dark .o, -.hl-dark .p { - color: #abb2bf; -} -.hl .cm, -.hl-dark .cm { - color: #5c6370; - font-style: italic; -} -.hl .cp, -.hl-dark .cp { - color: #61afef; -} -.hl .c1, -.hl .cs, -.hl-dark .c1, -.hl-dark .cs { - color: #5c6370; - font-style: italic; -} -.hl .ge, -.hl-dark .ge { - font-style: italic; -} -.hl .gs, -.hl-dark .gs { - font-weight: 700; -} -.hl .kc, -.hl .kd, -.hl .kn, -.hl-dark .kc, -.hl-dark .kd, -.hl-dark .kn { - color: #c678dd; -} -.hl .kp, -.hl-dark .kp { - color: #d19a66; -} -.hl .kr, -.hl-dark .kr { - color: #c678dd; -} -.hl .kt, -.hl-dark .kt { - color: #e06c75; -} -.hl .ld, -.hl-dark .ld { - color: #98c379; -} -.hl .m, -.hl-dark .m { - color: #d19a66; -} -.hl .s, -.hl-dark .s { - color: #98c379; -} -.hl .na, -.hl-dark .na { - color: #d19a66; -} -.hl .nb, -.hl .nc, -.hl .nd, -.hl .ne, -.hl .ni, -.hl .no, -.hl-dark .nb, -.hl-dark .nc, -.hl-dark .nd, -.hl-dark .ne, -.hl-dark .ni, -.hl-dark .no { - color: #e5c07b; -} -.hl .nf, -.hl-dark .nf { - color: #61afef; -} -.hl .nl, -.hl-dark .nl { - color: #e5c07b; -} -.hl .nn, -.hl .nx, -.hl-dark .nn, -.hl-dark .nx { - color: #abb2bf; -} -.hl .py, -.hl-dark .py { - color: #e5c07b; -} -.hl .nt, -.hl-dark .nt { - color: #e06c75; -} -.hl .nv, -.hl-dark .nv { - color: #e5c07b; -} -.hl .ow, -.hl-dark .ow { - font-weight: 700; -} -.hl .w, -.hl-dark .w { - color: #f8f8f2; -} -.hl .mf, -.hl .mh, -.hl .mi, -.hl .mo, -.hl-dark .mf, -.hl-dark .mh, -.hl-dark .mi, -.hl-dark .mo { - color: #d19a66; -} -.hl .s2, -.hl .sb, -.hl .sc, -.hl .sd, -.hl .se, -.hl .sh, -.hl .si, -.hl .sx, -.hl-dark .s2, -.hl-dark .sb, -.hl-dark .sc, -.hl-dark .sd, -.hl-dark .se, -.hl-dark .sh, -.hl-dark .si, -.hl-dark .sx { - color: #98c379; -} -.hl .sr, -.hl-dark .sr { - color: #56b6c2; -} -.hl .s1, -.hl-dark .s1 { - color: #98c379; -} -.hl .ss, -.hl-dark .ss { - color: #56b6c2; -} -.hl .bp, -.hl .vc, -.hl .vg, -.hl-dark .bp, -.hl-dark .vc, -.hl-dark .vg { - color: #e5c07b; -} -.hl .vi, -.hl-dark .vi { - color: #e06c75; -} -.hl .il, -.hl-dark .il { - color: #d19a66; -} -.hl .gu, -.hl-dark .gu { - color: #75715e; -} -.hl .gd, -.hl-dark .gd { - color: #f92672; -} -.hl .gi, -.hl-dark .gi { - color: #a6e22e; -} -.hl-light { - color: #383a42; -} -.hl-light .c { - color: #a0a1a7; - font-style: italic; -} -.hl-light .err { - color: #960050; - background-color: #1e0010; -} -.hl-light .k { - color: #a626a4; -} -.hl-light .l { - color: #50a14f; -} -.hl-light .n, -.hl-light .o, -.hl-light .p { - color: #383a42; -} -.hl-light .cm { - color: #a0a1a7; - font-style: italic; -} -.hl-light .cp { - color: #4078f2; -} -.hl-light .c1, -.hl-light .cs { - color: #a0a1a7; - font-style: italic; -} -.hl-light .ge { - font-style: italic; -} -.hl-light .gs { - font-weight: 700; -} -.hl-light .kc, -.hl-light .kd, -.hl-light .kn { - color: #a626a4; -} -.hl-light .kp { - color: #986801; -} -.hl-light .kr { - color: #a626a4; -} -.hl-light .kt { - color: #e45649; -} -.hl-light .ld { - color: #50a14f; -} -.hl-light .m { - color: #986801; -} -.hl-light .s { - color: #50a14f; -} -.hl-light .na { - color: #986801; -} -.hl-light .nb, -.hl-light .nc, -.hl-light .nd, -.hl-light .ne, -.hl-light .ni, -.hl-light .no { - color: #c18401; -} -.hl-light .nf { - color: #4078f2; -} -.hl-light .nl { - color: #c18401; -} -.hl-light .nn, -.hl-light .nx { - color: #383a42; -} -.hl-light .py { - color: #c18401; -} -.hl-light .nt { - color: #e45649; -} -.hl-light .nv { - color: #c18401; -} -.hl-light .ow { - font-weight: 700; -} -.hl-light .w { - color: #f8f8f2; -} -.hl-light .mf, -.hl-light .mh, -.hl-light .mi, -.hl-light .mo { - color: #986801; -} -.hl-light .s2, -.hl-light .sb, -.hl-light .sc, -.hl-light .sd, -.hl-light .se, -.hl-light .sh, -.hl-light .si, -.hl-light .sx { - color: #50a14f; -} -.hl-light .sr { - color: #0184bc; -} -.hl-light .s1 { - color: #50a14f; -} -.hl-light .ss { - color: #0184bc; -} -.hl-light .bp, -.hl-light .vc, -.hl-light .vg { - color: #c18401; -} -.hl-light .vi { - color: #e45649; -} -.hl-light .il { - color: #986801; -} -.hl-light .gu { - color: #75715e; -} -.hl-light .gd { - color: #f92672; -} -.hl-light .gi { - color: #a6e22e; -} -header { - background-color: #d33847; - position: relative; - overflow: hidden; -} -header :not(.cloud) { - position: relative; - z-index: 10; -} -header h1, -header h2, -header h3, -header h4 { - font-family: "Proza Libre", "Helvetica Neue", Helvetica, Arial, - "Lucida Grande", sans-serif; - text-shadow: 0 2px 0 rgba(0, 0, 0, 0.25); -} -header h1 { - font-size: 42px; - margin: 30px 0 0; - font-weight: 600; -} -header h1 a:active, -header h1 a:focus, -header h1 a:hover { - color: #ffbec4; - border: 0; -} -header h4 { - font-size: 16px; - line-height: 18px; - font-weight: 500; - margin: 0; - padding: 10px 0 70px; - color: #ffdada; -} -header .banner { - margin: 30px 0 50px; -} -header .banner h1, -header .banner h2, -header .banner h3, -header .banner h4 { - font-family: "Helvetica Neue", Helvetica, Arial, "Lucida Grande", sans-serif; -} -header .banner h1 { - font-size: 36px; - font-weight: 500; - margin: 17px 0; -} -header .banner h2 { - font-size: 25px; - font-weight: 300; - line-height: 36px; - margin: 10px auto 15px; - max-width: 580px; -} -.cloud { - position: absolute; - top: 0; - left: 0; - width: 100%; - height: 100%; - display: block; - -webkit-backface-visibility: hidden; - -moz-backface-visibility: hidden; - -ms-backface-visibility: hidden; - backface-visibility: hidden; - -webkit-perspective: 1000; - -moz-perspective: 1000; - -ms-perspective: 1000; - perspective: 1000; - transform: translateZ(0); -} -.cloud.back { - z-index: 1; - opacity: 0.03; -} -.cloud.front { - z-index: 2; - opacity: 0.06; -} -.features img { - margin-bottom: 15px; -} -.features h2 { - font-size: 36px; - color: #fff; - text-shadow: 0 2px 0 rgba(0, 0, 0, 0.15); - font-weight: 500; - line-height: 36px; - margin-top: 0; - margin-bottom: 10px; -} -.features figcaption, -.features h2, -.features h3, -.features h4 { - font-family: "Proza Libre", "Helvetica Neue", Helvetica, Arial, - "Lucida Grande", sans-serif; -} -.features h3 { - font-size: 20px; - line-height: 20px; - text-shadow: 0 1px 0 rgba(0, 0, 0, 0.2); - margin: 0 auto; - font-weight: 500; -} -.features h3.blue { - color: #5daced; -} -.features h3.purple { - color: #df76ff; -} -.features h3.red { - color: #ff6170; -} -.features h3.yellow { - color: #fff776; -} -.features h3.orange { - color: #ff853e; -} -.features h3.green { - color: #15dc98; -} -.features figcaption, -.features h4 { - font-weight: 400; -} -.features h4 { - margin-top: 0; - margin-bottom: 35px; - font-size: 18px; - color: #a2a2a2; - line-height: 24px; -} -.features figcaption { - color: #ffd5d8; - font-size: 15px; - line-height: 25px; - max-width: 220px; - margin: 10px auto 0; -} -.features figcaption.wide { - margin-bottom: 15px; - max-width: 300px; - font-weight: 300; - color: #fff; - padding: 0 10px; -} -.features .col { - border-right: 1px solid rgba(255, 255, 255, 0.25); - margin: 40px 0; -} -@media (max-width: 870px) { - .features .col { - border-right: none; - } -} -.features .col.tight:last-child, -.features .col:last-child { - border-right: none; -} -@media (max-width: 870px) { - .features .col:last-child { - margin-bottom: 40px; - } -} -.features .col.tight { - padding: 35px 0; - margin: 10px 0; - border-right: 1px solid rgba(255, 255, 255, 0.1); -} -@media (max-width: 870px) { - .features .col.tight { - border-right: none; - } -} -@media (max-width: 870px) { - .features .col.tight:last-child { - margin-bottom: 0; - } -} -.features hr { - background-color: rgba(255, 255, 255, 0.1); - margin: 30px 0; -} -a:hover { - border-bottom: 1px dashed #fff; -} -a:active, -a:focus { - border-bottom: 1px solid #fff; - outline: 0; -} -#pagination ul, -.centered, -.circle-container h4 { - text-align: center; -} -.pull-right { - float: right; -} -.text-right { - text-align: right; -} -table { - width: 100%; -} -table.code { - display: inline-block; - color: #a5abb8; - font-size: 14px; - text-shadow: none; - padding: 0; - border-spacing: 0; - box-shadow: 0 2px 5px 0 rgba(0, 0, 0, 0.5); - border-radius: 5px; - width: 100%; - overflow-x: auto; -} -table.code * { - font-family: Consolas, "Liberation Mono", Menlo, Monaco, Lucida Console, - Liberation Mono, DejaVu Sans Mono, Bitstream Vera Sans Mono, Courier New, - monospace, serif; - line-height: 18px; - margin: 0; - -webkit-font-smoothing: subpixel-antialiased; -} -table.code td.linenos { - background-color: #3a3a3a; - padding: 15px 10px 15px 18px; - color: #666; - text-align: right; - border-top-left-radius: 5px; - border-bottom-left-radius: 5px; - border: 1px solid #4b4b4b; - border-right: 1px solid #1b1d22; -} -table.code td.code { - padding-left: 15px; - background: #323232; - border-top-right-radius: 5px; - border-bottom-right-radius: 5px; - border: 1px solid #4b4b4b; - border-left: none; - width: 100%; -} -footer, -section { - text-shadow: 0 2px 0 rgba(0, 0, 0, 0.25); - background-color: #272727; -} -section { - padding: 70px 0; -} -footer.tight, -section.tight { - padding: 0; -} -footer.dark, -section.dark { - background-color: #1d1d1d; -} -footer h3, -section h3 { - font-size: 36px; - font-weight: 500; - margin: 20px 0 0; - line-height: 48px; -} -footer .col.left-main, -section .col.left-main { - line-height: 22px; - align-self: center; -} -footer .col.left-main h3, -footer .col.left-main p, -section .col.left-main h3, -section .col.left-main p { - padding: 0 20px; -} -footer .col.right-main, -section .col.right-main { - overflow-x: auto; - align-self: center; -} -@media (max-width: 870px) { - footer h3, - section h3 { - margin: -10px 0 0; - text-align: center; - } - footer span.callout, - section span.callout { - display: block; - text-align: center; - } - footer .col.left-main, - section .col.left-main { - padding-bottom: 10px; - } -} -@media (min-width: 870px) { - footer:nth-child(even) .col.left-main, - section:nth-child(even) .col.left-main { - padding-left: 20px; - } - footer:nth-child(even) .col.right-main, - section:nth-child(even) .col.right-main { - padding-right: 20px; - order: -1; - } - footer:nth-child(odd) .col.left-main, - section:nth-child(odd) .col.left-main { - padding-right: 20px; - } - footer:nth-child(odd) .col.right-main, - section:nth-child(odd) .col.right-main { - padding-left: 20px; - } -} -footer:nth-child(odd), -section:nth-child(odd) { - background-color: #d33847; -} -footer:nth-child(odd) table.code, -section:nth-child(odd) table.code { - border-color: #a0a0a0; - color: #222; -} -footer:nth-child(odd) table.code td.linenos, -section:nth-child(odd) table.code td.linenos { - background-color: #efefef; - border: 1px solid #a0a0a0; - border-right: 1px solid #d5d5d5; - color: #a9a9a9; -} -footer:nth-child(odd) table.code td.code, -section:nth-child(odd) table.code td.code { - background: #fff; - border: 1px solid #a0a0a0; - border-left: none; -} -footer #sidebar, -section #sidebar { - background-color: #1d1d1d; - width: calc(280px + (100% - 1310px) / 2); - min-width: 280px; -} -@media (max-width: 870px) { - footer #sidebar, - section #sidebar { - display: none; - } -} -footer #search-results, -footer article, -section #search-results, -section article { - width: calc(100% - 280px); - max-width: 1060px; - padding: 40px; -} -@media (max-width: 870px) { - footer #search-results, - footer article, - section #search-results, - section article { - width: 100%; - padding: 35px 30px; - } -} -footer nav, -section nav { - padding: 40px 35px; - box-sizing: border-box; - font-family: "Proza Libre", "Helvetica Neue", Helvetica, Arial, - "Lucida Grande", sans-serif; - font-weight: 500; - position: relative; - min-width: 250px; - float: right; - display: inline-block; - overflow: auto; -} -footer nav .affix, -section nav .affix { - position: fixed; - top: 40px; - overflow: auto; - height: 100%; -} -footer nav .affix-bottom, -section nav .affix-bottom { - position: relative; - bottom: 40px; - overflow: auto; - height: 100%; -} -footer nav li, -section nav li { - list-style: none; -} -footer nav .toc ul, -section nav .toc ul { - padding: 10px 0 0; -} -footer nav .toc ul:first-child, -section nav .toc ul:first-child { - padding: 0; - margin: 0; -} -footer nav .toc ul.small, -section nav .toc ul.small { - padding: 0; -} -footer nav .toc ul.small a, -section nav .toc ul.small a { - font-size: 14px; -} -footer nav .toc > ul > li, -section nav .toc > ul > li { - font-size: 20px; - line-height: 20px; - margin: 20px 0; -} -footer nav .toc > ul > li:first-child, -section nav .toc > ul > li:first-child { - margin: 0; -} -footer nav .toc > ul > li ul li, -section nav .toc > ul > li ul li { - border-left: 1px solid #424242; - padding: 10px 0 10px 17px; - font-size: 14px; - line-height: 14px; -} -footer nav .toc > ul > li ul li a, -section nav .toc > ul > li ul li a { - color: #c5c5c5; -} -article.news a > h2, -footer nav .toc > ul > li ul li a.active, -footer nav .toc > ul > li ul li a:hover, -section nav .toc > ul > li ul li a.active, -section nav .toc > ul > li ul li a:hover { - color: #fff; -} -footer nav .toc > ul > li ul li a:active, -footer nav .toc > ul > li ul li a:focus, -section nav .toc > ul > li ul li a:active, -section nav .toc > ul > li ul li a:focus { - color: #444; -} -footer nav a:hover, -section nav a:hover { - border: 0; - text-decoration: underline; - color: #fff; -} -footer nav a:active, -footer nav a:focus, -section nav a:active, -section nav a:focus { - border: 0; - color: #444; -} -footer nav a.active, -section nav a.active { - font-weight: 700; -} -#search-results, -article, -main { - box-sizing: border-box; - padding: 40px 10px; - font-size: 16px; - font-family: Helvetica, Arial, "Lucida Grande", sans-serif; - line-height: 24px; -} -#search-results p, -article p, -main p { - margin: 15px 0; -} -#search-results h2, -#search-results h3, -#search-results h4, -#search-results h5, -article h2, -article h3, -article h4, -article h5, -main h2, -main h3, -main h4, -main h5 { - font-family: "Proza Libre", "Helvetica Neue", Helvetica, Arial, - "Lucida Grande", sans-serif; - font-weight: 600; - text-align: left; -} -#search-results h2 + p, -#search-results h3 + p, -#search-results h4 + p, -#search-results h5 + p, -article h2 + p, -article h3 + p, -article h4 + p, -article h5 + p, -main h2 + p, -main h3 + p, -main h4 + p, -main h5 + p { - margin-top: 0; -} -#search-results h2:first-child, -#search-results h3:first-child, -#search-results h4:first-child, -#search-results h5:first-child, -article h2:first-child, -article h3:first-child, -article h4:first-child, -article h5:first-child, -main h2:first-child, -main h3:first-child, -main h4:first-child, -main h5:first-child { - padding-top: 0; - margin-top: 0; -} -#pagination li, -#search-results h2:hover > a, -#search-results h3:hover > a, -#search-results h4:hover > a, -#search-results h5:hover > a, -article h2:hover > a, -article h3:hover > a, -article h4:hover > a, -article h5:hover > a, -main h2:hover > a, -main h3:hover > a, -main h4:hover > a, -main h5:hover > a { - display: inline; -} -#search-results h2, -article h2, -main h2 { - padding: 20px 0 10px; - margin: 15px 0; - font-size: 30px; - line-height: 32px; -} -#search-results h3, -article h3, -main h3 { - padding: 10px 0; - margin: 20px 0 10px; - font-size: 22px; - line-height: 24px; -} -#search-results h4, -#search-results h5, -article h4, -article h5, -main h4, -main h5 { - padding: 10px 0; - margin: 10px 0 5px; -} -#search-results h4, -article h4, -main h4 { - font-size: 18px; - line-height: 20px; -} -#search-results h5, -article h5, -article.news h5, -main h5 { - font-size: 16px; - line-height: 20px; -} -#search-results a.headerlink, -article a.headerlink, -main a.headerlink { - color: #5e5e5e; - font-size: 0.8em; - line-height: 1em; - margin-left: 5px; - border-bottom: none; -} -@media (min-width: 870px) { - #search-results a.headerlink, - article a.headerlink, - main a.headerlink { - display: none; - } -} -#search-results a.headerlink:active, -#search-results a.headerlink:focus, -#search-results a.headerlink:hover, -article a.headerlink:active, -article a.headerlink:focus, -article a.headerlink:hover, -main a.headerlink:active, -main a.headerlink:focus, -main a.headerlink:hover { - border: 0; -} -#search-results a.headerlink:hover, -article a.headerlink:hover, -main a.headerlink:hover { - color: #ccc; -} -#search-results a.headerlink:active, -#search-results a.headerlink:focus, -article a.headerlink:active, -article a.headerlink:focus, -main a.headerlink:active, -main a.headerlink:focus { - color: #fff; -} -#search-results blockquote, -article blockquote, -main blockquote { - background-color: rgba(255, 255, 255, 0.05); - margin: 0; - padding: 5px 20px 5px 30px; - border-left: 10px solid rgba(255, 255, 255, 0.1); - box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.5); - text-shadow: 0 2px 0 rgba(0, 0, 0, 0.25); -} -article.news { - font-size: 18px; - font-family: Helvetica, Arial, "Lucida Grande", sans-serif; - line-height: 30px; - max-width: 900px; - margin: 0 auto; - overflow-x: auto; -} -@media (max-width: 870px) { - article.news { - padding: 10px 5px; - } -} -article.news p:not(:nth-child(2)) { - margin-left: auto; - margin-right: auto; -} -article.news p.metadata { - margin: -10px 0 20px; - font-size: 16px; - color: #bbb; -} -article.news blockquote p:not(:last-child) { - color: #bbb; -} -article.news a > h2:hover { - text-decoration: none; -} -#tabs-panel .panel, -#tabs-panel input, -article.news h2 a, -article.news h2:hover > a { - display: none; -} -article.news h2 { - margin-top: 20px; - font-size: 30px; - line-height: 42px; - padding: 0; -} -article.news h3 { - font-size: 22px; - line-height: 26px; -} -article.news h4 { - font-size: 18px; - line-height: 22px; -} -#search-results a, -article a { - font-weight: 700; - color: #8ab4ff; -} -#search-results a:hover, -article a:hover { - color: inherit; - border-bottom: none; - text-decoration: underline; -} -#search-results a:hover code, -article a:hover code { - text-decoration: underline; -} -#search-results a:active, -#search-results a:focus, -article a:active, -article a:focus { - border-bottom: none; - text-decoration: underline; -} -#search-results h2, -article h2 { - border-bottom: 1px solid rgba(255, 255, 255, 0.15); -} -#search-results h3, -article h3 { - border-bottom: 1px solid rgba(255, 255, 255, 0.07); -} -main h2, -main h3 { - margin-bottom: 0; -} -main h2:not(:first-child) { - margin-top: 30px; -} -code, -footer { - text-shadow: none; -} -code { - display: inline-block; - background: rgba(0, 0, 0, 0.4); - font-size: 0.87em; - border-radius: 5px; - font-family: Consolas, "Liberation Mono", Menlo, Monaco, Lucida Console, - Liberation Mono, DejaVu Sans Mono, Bitstream Vera Sans Mono, Courier New, - monospace, serif; - -webkit-font-smoothing: subpixel-antialiased; - padding: 1px 4px; - line-height: 1.4em; -} -span.orange, -span.purple { - color: #f68500; - font-weight: 400; - font-size: 0.95em; -} -span.purple { - color: #9a64c0; -} -span.callout { - display: inline-block; - background: #fff; - color: #000; - padding: 2px 15px; - font-family: "Helvetica Neue", Helvetica, Arial, "Lucida Grande", sans-serif; - font-size: 14px; - font-weight: 700; - border-radius: 5px; - border: 1px solid #000; - box-shadow: 0 2px 3px 0 rgba(0, 0, 0, 0.5); - margin: 0; - text-shadow: none; -} -footer { - padding: 40px 0 30px; - line-height: 32px; -} -footer, -footer a { - font-size: 14px; - color: #ffb9b9; -} -footer h2 { - font-size: 36px; - font-family: "Proza Libre", "Helvetica Neue", Helvetica, Arial, - "Lucida Grande", sans-serif; -} -footer h4 { - font-size: 18px; -} -#tabs-panel { - border: 1px solid #3d3d3d; - text-shadow: none; - box-shadow: 0 2px 5px 0 rgba(0, 0, 0, 0.5); -} -#tabs-panel p:first-child { - margin-top: 0; -} -#tabs-panel p:last-child { - margin-bottom: 0; -} -#tabs-panel .tab { - display: block; - padding: 17px 20px 17px 45px; - font: 500 18px "Proza Libre", "Helvetica Neue", Helvetica, Arial, - "Lucida Grande", sans-serif; - cursor: pointer; - color: #464646; - background-color: #212121; - border-right: 1px solid #3d3d3d; - position: relative; - margin-right: -1px; - box-shadow: inset -1px 1px 3px rgba(0, 0, 0, 0.25); -} -#tabs-panel .tab img { - margin: auto 0; - position: absolute; - left: 17px; - bottom: 0; - right: 0; - top: 0; - opacity: 0.15; - pointer-events: none; -} -#tabs-panel .tab:not(:first-child) { - border-top: 1px solid #2f2f2f; -} -#tabs-panel .tab:hover { - background-color: #272727; - color: #aaa; -} -#tabs-panel .tab:hover img { - opacity: 0.5; -} -#tabs-panel .tab:focus { - background-color: #111; -} -#tabs-panel .tab:active { - background-color: #2c2c2c; -} -#tabs-panel .tabs { - flex: 1 1 300px; - background-color: #212121; - min-width: 220px; -} -#tabs-panel .panels { - background-color: #323232; - width: 100%; -} -#tabs-panel .panel { - padding: 15px 25px; - font: 16px Helvetica, Arial, "Lucida Grande", sans-serif; - color: #e0e0e0; - line-height: 26px; -} -#tabs-panel #dynamic-params-tab img { - top: -1px; -} -#tabs-panel #handling-data-tab img { - left: 18px; - top: -1px; -} -#tabs-panel #request-guards-tab img { - left: 19px; - top: -3px; -} -#tabs-panel #responders-tab img { - top: -3px; -} -#tabs-panel #launching-tab img { - left: 21px; - top: -3px; -} -#tabs-panel #dynamic-params:checked ~ .tabs #dynamic-params-tab, -#tabs-panel #handling-data:checked ~ .tabs #handling-data-tab, -#tabs-panel #launching:checked ~ .tabs #launching-tab, -#tabs-panel #request-guards:checked ~ .tabs #request-guards-tab, -#tabs-panel #responders:checked ~ .tabs #responders-tab, -#tabs-panel #routing:checked ~ .tabs #routing-tab { - text-shadow: 0 2px 0 rgba(0, 0, 0, 0.25); - background-color: #323232; - cursor: default; - color: #fff; - border-right: none; - box-shadow: none; -} -#tabs-panel #dynamic-params:checked ~ .tabs #dynamic-params-tab img, -#tabs-panel #handling-data:checked ~ .tabs #handling-data-tab img, -#tabs-panel #launching:checked ~ .tabs #launching-tab img, -#tabs-panel #request-guards:checked ~ .tabs #request-guards-tab img, -#tabs-panel #responders:checked ~ .tabs #responders-tab img, -#tabs-panel #routing:checked ~ .tabs #routing-tab img { - opacity: 1; -} -#tabs-panel #dynamic-params:checked ~ .panels #dynamic-params-panel, -#tabs-panel #handling-data:checked ~ .panels #handling-data-panel, -#tabs-panel #launching:checked ~ .panels #launching-panel, -#tabs-panel #request-guards:checked ~ .panels #request-guards-panel, -#tabs-panel #responders:checked ~ .panels #responders-panel, -#tabs-panel #routing:checked ~ .panels #routing-panel { - display: block; -} -#logo span, -#toggle { - display: none; -} -header .container { - padding-top: 35px; - z-index: 10; -} -header nav.version-select { - position: relative; - display: inline-block; - width: 70px; - height: 35px; - line-height: 35px; - text-align: center; - font-size: 15px; - font-family: "Proza Libre", "Helvetica Neue", Helvetica, Arial, - "Lucida Grande", sans-serif; - font-weight: 700; - z-index: 20; -} -header nav.version-select i { - border: solid #fff; - border-width: 0 3px 3px 0; - display: inline-block; - padding: 3px; - transform: rotate(45deg); - margin-left: 3px; - top: -2px; -} -header nav.version-select options { - display: none; - position: absolute; - box-shadow: 0 2px 0 rgba(0, 0, 0, 0.25); - left: -1px; - top: 35px; - z-index: 30; -} -header nav.version-select options a, -header nav.version-select options a:hover { - border: 1px solid rgba(255, 255, 255, 0.8); -} -header nav.version-select options a:hover:not(:first-child), -header nav.version-select options a:not(:first-child) { - border-top: none; -} -header nav.version-select options a { - color: #fff; - font-size: 15px; - font-weight: 700; - display: block; - background-color: rgba(255, 255, 255, 0.1); - width: 70px; - height: 35px; -} -header nav.version-select options a:hover { - background-color: rgba(255, 255, 255, 0.8); - color: #d33847; -} -@media (max-width: 870px) { - header nav.version-select options a { - background-color: #d44756; - } - header nav.version-select options a:hover { - background-color: #dcdcdc; - } -} -header .hamburger, -header nav.main { - float: right; -} -.circle-container .circle + h4, -header nav a { - font-family: "Proza Libre", "Helvetica Neue", Helvetica, Arial, - "Lucida Grande", sans-serif; -} -header nav a { - font-size: 18px; - font-weight: 500; -} -a#logo:active, -a#logo:hover { - border: 0; - text-decoration: none; -} -@media (min-width: 870px) { - header nav.main { - margin-top: 20px; - } - header nav.main a { - margin-left: 35px; - } - #logo { - height: 60px; - width: 180px; - background-repeat: no-repeat; - display: inline-block; - vertical-align: middle; - } -} -@media (max-width: 870px) { - @-webkit-keyframes bugfix { - 0%, - to { - padding: 0; - } - } - body { - -webkit-animation: bugfix infinite 1s; - } - #logo { - height: 30px; - width: 140px; - background-repeat: no-repeat; - display: inline-block; - vertical-align: middle; - } - header .banner { - margin-top: 20px; - } - header .banner h1 { - margin-top: 0; - } - header nav.main { - display: block; - color: #000; - width: 100%; - background-color: rgba(0, 0, 0, 0.2); - max-height: 0; - transition: max-height 0.25s ease-in-out; - margin: 20px 0 0; - overflow: hidden; - float: none; - } - header nav.main a { - display: block; - margin: 0; - padding: 15px 35px; - } - header nav.main a:first-child { - padding-top: 30px; - } - header nav.main a:last-child { - padding-bottom: 30px; - } - header nav.main a:hover { - border: 0; - background-color: rgba(0, 0, 0, 0.2); - } - header nav.main a:active, - header nav.main a:focus { - border: 0; - background-color: rgba(0, 0, 0, 0.4); - } - .hamburger, - .hamburger span { - display: block; - transform: rotate(0deg); - } - .hamburger { - float: right; - cursor: pointer; - user-select: none; - background: rgba(0, 0, 0, 0.2); - width: 38px; - height: 30px; - position: relative; - } - .hamburger span { - position: absolute; - height: 14%; - width: 60%; - background: #fff; - border-radius: 9px; - opacity: 1; - left: 20%; - transition: 0.25s ease-in-out; - } - .hamburger span:nth-child(1) { - top: 20%; - } - #toggle:checked ~ .hamburger span:nth-child(1) { - top: 43%; - transform: rotate(135deg); - } - .hamburger span:nth-child(2) { - top: 43%; - } - #toggle:checked ~ .hamburger span:nth-child(2) { - opacity: 0; - left: -60px; - } - .hamburger span:nth-child(3) { - top: 66%; - } - #toggle:checked ~ .hamburger span:nth-child(3) { - top: 43%; - transform: rotate(-135deg); - } - #toggle:checked ~ nav { - max-height: 300px; - } - .mobile-hide { - display: none; - } -} -.circle-container { - flex: 0 0 180px; -} -.circle-container .circle { - border-radius: 50%; - text-shadow: none; - width: 60px; - height: 60px; - font-family: "Helvetica Neue", Helvetica, Arial, "Lucida Grande", sans-serif; - font-size: 42px; - font-weight: 700; - color: #1e1e1e; - text-align: center; - line-height: 60px; - margin: 0 auto; -} -.circle-container .circle.blue { - background: #5daced; -} -.circle-container .circle.purple { - background: #da74f9; -} -.circle-container .circle.red { - background: #ff6170; -} -.circle-container .circle + h4 { - margin: 12px 0 0; -} -.work-step { - border-top: 1px solid #464646; - padding: 25px 0; -} -.blocky-code table.code td.linenos { - border-radius: 0; - border: 1px solid #414141; - border-right: none; - background-color: #323232; - padding-left: 20px; -} -.blocky-code table.code td.code { - border-radius: 0; - border: 1px solid #414141; - border-left: none; - background-color: #323232; - padding-left: 10px; -} -.lighter-code table.code { - box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.25); -} -.lighter-code table.code td.linenos { - background-color: #434343; - border: 1px solid #494949; - border-right: none; - color: #8e8e8e; - padding: 12px; -} -.lighter-code table.code td.code { - border: 1px solid #494949; - border-left: none; - background-color: #393939; -} -@media (max-width: 870px) { - .github-corner { - display: none; - } -} -.github-corner:hover .octo-arm { - animation: octocat-wave 560ms ease-in-out; -} -input#search-input { - max-width: 180px; - color: #fff; - font-weight: 600; - border: 0; - margin-bottom: 25px; - box-sizing: border-box; - padding: 10px 15px 10px 35px; - background-color: #000; - background-size: 15px 15px; - background-repeat: no-repeat; - background-position: 10px 11px; -} -#hits { - color: inherit; -} -#hits a { - font-size: 22px; -} -#hits span { - color: #ccc; - font-weight: 400; - font-size: 14px; -} -#hits .hit { - margin-bottom: 30px; -} -#hits hr { - margin: 5px 0; -} -#hits p { - font-weight: 400; - margin: 5px 0 0; -} -#hits em { - background-color: rgba(117, 97, 0, 0.24); - color: #ffc900; - font-style: normal; - font-weight: 700; -} -#pagination .ais-pagination--item__disabled { - display: none; -} -#hits p, -#pagination .ais-pagination--item__active a { - color: #fff; -} -#pagination li a { - padding: 6px; - background: rgba(0, 0, 0, 0.5); - border: 1px solid #000; - margin: 0 5px; -} -#pagination li a:hover { - background: rgba(0, 0, 0, 0.2); - border-bottom: 1px solid #000; -} -.cancel-search { - display: inline-block; - width: 20px; - height: 20px; - cursor: pointer; - margin-bottom: -4px; - opacity: 0.6; - margin-right: 4px; -} -.cancel-search:hover { - opacity: 1; -} -.admonition { - box-shadow: 0 2px 5px 0 rgba(0, 0, 0, 0.5); - margin: 15px 0; - padding: 0 20px; - border-radius: 5px; - overflow: auto; - position: relative; -} -.admonition .title { - display: block; - margin: 0 -20px; - padding: 10px 20px 6px 43px; - background-repeat: no-repeat; - background-position: 20px 55%; - background-size: 17px; -} -@media (max-width: 870px) { - .admonition .title { - padding-left: 55px; - background-position: 20px 50%; - } -} -.admonition.note { - border-left: 8px solid #448aff; - background-color: rgba(68, 138, 255, 0.05); -} -.admonition.note .title { - background-color: rgba(68, 138, 255, 0.25); - border-bottom: 0.1rem solid rgba(68, 138, 255, 0.1); - background-position: 20px 50%; -} -.admonition.tip { - border-left: 8px solid #00bfa5; - background-color: rgba(0, 191, 165, 0.05); -} -.admonition.tip .title { - background-color: rgba(0, 191, 165, 0.25); - border-bottom: 0.1rem solid rgba(0, 191, 165, 0.1); -} -.admonition.warning { - border-left: 8px solid #ff9100; - background-color: rgba(255, 145, 0, 0.05); -} -.admonition.warning .title { - background-color: rgba(255, 145, 0, 0.25); - border-bottom: 0.1rem solid rgba(255, 145, 0, 0.1); -} diff --git a/docs/rocketsrchrome_files/sun.svg b/docs/rocketsrchrome_files/sun.svg deleted file mode 100644 index 435993c5e..000000000 --- a/docs/rocketsrchrome_files/sun.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/docs/rocketsrchrome_files/telescope.svg b/docs/rocketsrchrome_files/telescope.svg deleted file mode 100644 index 62e24291c..000000000 --- a/docs/rocketsrchrome_files/telescope.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/docs/rocketsrchrome_files/templating-icon.svg b/docs/rocketsrchrome_files/templating-icon.svg deleted file mode 100644 index 29049bc2c..000000000 --- a/docs/rocketsrchrome_files/templating-icon.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/docs/rocketsrchrome_files/testing-icon.svg b/docs/rocketsrchrome_files/testing-icon.svg deleted file mode 100644 index 0a4e9acec..000000000 --- a/docs/rocketsrchrome_files/testing-icon.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/notes/Parity.md b/notes/Parity.md index 1fb0b6f28..f87ac7b06 100644 --- a/notes/Parity.md +++ b/notes/Parity.md @@ -2,41 +2,41 @@ Sorted by priority -| Feature | Dioxus | React | Notes | -| ---------------------- | ------ | ----- | ------------------------------------ | -| ----- Phase 1 ----- | ----- | ----- | ----- | -| Conditional Rendering | ✅ | ✅ | if/then to hide/show component | -| Map, Iterator | ✅ | ✅ | map/filter/reduce rsx! | -| Keyed Components | ✅ | ✅ | advanced diffing with keys | -| Web | ✅ | ✅ | renderer for web browser | -| Desktop (webview) | ✅ | ✅ | renderer for desktop | -| Context | ✅ | ✅ | share state through the tree | -| Hook | ✅ | ✅ | memory cells in components | -| SSR | ✅ | ✅ | render directly to string | -| Runs natively | ✅ | 👀 | runs as a sharable binary | -| Null components | 👀 | ✅ | allow returning None | -| Fragments | 👀 | ✅ | no requirement on main root | -| Component Children | 👀 | ✅ | ctx.children() as a list of nodes | -| NodeRef | 👀 | ✅ | gain direct access to nodes | -| Controlled Inputs | 👀 | ✅ | stateful wrappers around inputs | -| No-div components | 👀 | ✅ | components that render components | -| CSS/Inline Styles | 🛠 | ✅ | syntax for inline/conditional styles | -| 1st class global state | 🛠 | ✅ | redux/recoil/mobx on top of context | -| ----- Phase 2 ----- | ----- | ----- | ----- | -| 1st class router | 👀 | ✅ | Hook built on top of history | -| Assets | 👀 | ✅ | include css/svg/img url statically | -| Integrated classnames | 🛠 | 👀 | built-in `classnames` | -| Suspense | 👀 | 👀 | schedule future render from future | -| Transition | 👀 | 👀 | High-level control over suspense | -| Animation | 👀 | ✅ | Spring-style animations | -| Mobile | 👀 | ✅ | Render with cacao | -| Desktop (native) | 👀 | ✅ | Render with native desktop | -| 3D Renderer | 👀 | ✅ | react-three-fiber | -| ----- Phase 3 ----- | ----- | ----- | ----- | -| Portal | 👀 | ✅ | cast elements through tree | -| Error/Panic boundary | 👀 | ✅ | catch panics and display BSOD | -| Code-splitting | 👀 | ✅ | Make bundle smaller/lazy | -| LiveView | 👀 | 👀 | Example for SSR + WASM apps | +| Feature | Dioxus | React | Notes | +| ---------------------- | ------ | ----- | ------------------------------------------------ | +| ----- Phase 1 ----- | ----- | ----- | ----- | +| Conditional Rendering | ✅ | ✅ | if/then to hide/show component | +| Map, Iterator | ✅ | ✅ | map/filter/reduce rsx! | +| Keyed Components | ✅ | ✅ | advanced diffing with keys | +| Web | ✅ | ✅ | renderer for web browser | +| Desktop (webview) | ✅ | ✅ | renderer for desktop | +| Context | ✅ | ✅ | share state through the tree | +| Hook | ✅ | ✅ | memory cells in components | +| SSR | ✅ | ✅ | render directly to string | +| Runs natively | ✅ | 👀 | runs as a sharable binary | +| Component Children | ✅ | ✅ | ctx.children() as a list of nodes | +| Null components | ✅ | ✅ | allow returning no components | +| No-div components | ✅ | ✅ | components that render components | +| Fragments | ✅ | ✅ | rsx! can return multiple elements without a root | +| NodeRef | 👀 | ✅ | gain direct access to nodes | +| Controlled Inputs | 👀 | ✅ | stateful wrappers around inputs | +| CSS/Inline Styles | 🛠 | ✅ | syntax for inline/conditional styles | +| 1st class global state | 🛠 | ✅ | redux/recoil/mobx on top of context | +| ----- Phase 2 ----- | ----- | ----- | ----- | +| 1st class router | 👀 | ✅ | Hook built on top of history | +| Assets | 👀 | ✅ | include css/svg/img url statically | +| Integrated classnames | 🛠 | 👀 | built-in `classnames` | +| Suspense | 👀 | 👀 | schedule future render from future | +| Transition | 👀 | 👀 | High-level control over suspense | +| Animation | 👀 | ✅ | Spring-style animations | +| Mobile | 👀 | ✅ | Render with cacao | +| Desktop (native) | 👀 | ✅ | Render with native desktop | +| 3D Renderer | 👀 | ✅ | react-three-fiber | +| ----- Phase 3 ----- | ----- | ----- | ----- | +| Portal | 👀 | ✅ | cast elements through tree | +| Error/Panic boundary | 👀 | ✅ | catch panics and display BSOD | +| Code-splitting | 👀 | ✅ | Make bundle smaller/lazy | +| LiveView | 👀 | 👀 | Example for SSR + WASM apps | ## Required services: diff --git a/packages/core-macro/examples/prop_test.rs b/packages/core-macro/examples/prop_test.rs index aa42daef2..da4d7e961 100644 --- a/packages/core-macro/examples/prop_test.rs +++ b/packages/core-macro/examples/prop_test.rs @@ -2,8 +2,9 @@ fn main() {} pub mod dioxus { pub mod prelude { - pub trait Properties { + pub unsafe trait Properties { type Builder; + const CAN_BE_MEMOIZED: bool; fn builder() -> Self::Builder; } } diff --git a/packages/core-macro/src/props/mod.rs b/packages/core-macro/src/props/mod.rs index ef706043a..0301c68bd 100644 --- a/packages/core-macro/src/props/mod.rs +++ b/packages/core-macro/src/props/mod.rs @@ -679,8 +679,9 @@ Finally, call `.build()` to create the instance of `{name}`. } } - impl #impl_generics dioxus::prelude::Properties for #name #ty_generics{ + unsafe impl #impl_generics dioxus::prelude::Properties for #name #ty_generics{ type Builder = #builder_name #generics_with_empty; + const CAN_BE_MEMOIZED: bool = true; fn builder() -> Self::Builder { #name::builder() } diff --git a/packages/core-macro/src/rsx/ambiguous.rs b/packages/core-macro/src/rsx/ambiguous.rs index caaf7b0bc..82b95d2b5 100644 --- a/packages/core-macro/src/rsx/ambiguous.rs +++ b/packages/core-macro/src/rsx/ambiguous.rs @@ -50,14 +50,13 @@ impl Parse for AmbiguousElement { false => { let first_char = name_str.chars().next().unwrap(); if first_char.is_ascii_uppercase() { - if name_str == "Fragment" { - input + match name_str.as_str() { + "Fragment" => input .parse::() - .map(|c| AmbiguousElement::Fragment(c)) - } else { - input + .map(|c| AmbiguousElement::Fragment(c)), + _ => input .parse::() - .map(|c| AmbiguousElement::Component(c)) + .map(|c| AmbiguousElement::Component(c)), } } else { let name = input.parse::().unwrap(); diff --git a/packages/core-macro/src/rsx/component.rs b/packages/core-macro/src/rsx/component.rs index 4269df013..3d10aede0 100644 --- a/packages/core-macro/src/rsx/component.rs +++ b/packages/core-macro/src/rsx/component.rs @@ -26,7 +26,7 @@ pub struct Component { // accept any path-like argument name: syn::Path, body: Vec, - _children: Vec, + children: Vec, } impl Parse for Component { @@ -41,7 +41,7 @@ impl Parse for Component { syn::braced!(content in s); let mut body: Vec = Vec::new(); - let _children: Vec = Vec::new(); + let mut children: Vec = Vec::new(); 'parsing: loop { // [1] Break if empty @@ -49,7 +49,7 @@ impl Parse for Component { break 'parsing; } - if content.peek(token::Brace) { + if content.peek(token::Brace) && content.peek2(Token![...]) { let inner: ParseBuffer; syn::braced!(inner in content); if inner.peek(Token![...]) { @@ -57,7 +57,11 @@ impl Parse for Component { } } - body.push(content.parse::()?); + if content.peek(Ident) && content.peek2(Token![:]) { + body.push(content.parse::()?); + } else { + children.push(content.parse::()?); + } // consume comma if it exists // we don't actually care if there *are* commas between attrs @@ -66,13 +70,10 @@ impl Parse for Component { } } - // todo: add support for children - let children: Vec = vec![]; - Ok(Self { name, body, - _children: children, + children, }) } } @@ -109,9 +110,22 @@ impl ToTokens for Component { None => quote! {None}, }; - let _toks = tokens.append_all(quote! { - dioxus::builder::virtual_child(__ctx, #name, #builder, #key_token) - }); + let childs = &self.children; + let children = quote! { + ChildrenList::new(__ctx) + #( .add_child(#childs) )* + .finish() + }; + + tokens.append_all(quote! { + dioxus::builder::virtual_child( + __ctx, + #name, + #builder, + #key_token, + #children + ) + }) } } diff --git a/packages/core-macro/src/rsx/element.rs b/packages/core-macro/src/rsx/element.rs index f4ef28f02..23b545cb9 100644 --- a/packages/core-macro/src/rsx/element.rs +++ b/packages/core-macro/src/rsx/element.rs @@ -133,6 +133,10 @@ impl Parse for ElementAttr { } } else { match name_str.as_str() { + "key" => { + // todo: better error here + AttrType::BumpText(s.parse::()?) + } "style" => { // todo!("inline style not yet supported") @@ -196,10 +200,23 @@ impl ToTokens for ElementAttr { let name = self.name.to_string(); let nameident = &self.name; let _attr_stream = TokenStream2::new(); + match &self.ty { - AttrType::BumpText(value) => { + AttrType::BumpText(value) => match name.as_str() { + "key" => { + tokens.append_all(quote! { + .key2(format_args_f!(#value)) + }); + } + _ => { + tokens.append_all(quote! { + .attr(#name, format_args_f!(#value)) + }); + } + }, + AttrType::FieldTokens(exp) => { tokens.append_all(quote! { - .attr(#name, format_args_f!(#value)) + .attr(#name, #exp) }); } AttrType::Event(event) => { @@ -207,16 +224,11 @@ impl ToTokens for ElementAttr { .add_listener(dioxus::events::on::#nameident(__ctx, #event)) }); } - AttrType::FieldTokens(exp) => { - tokens.append_all(quote! { - .attr(#name, #exp) - }); - } AttrType::EventTokens(event) => { // tokens.append_all(quote! { .add_listener(dioxus::events::on::#nameident(__ctx, #event)) - }) + }); } } } diff --git a/packages/core-macro/src/rsx/fragment.rs b/packages/core-macro/src/rsx/fragment.rs index 7a5d12014..840b14797 100644 --- a/packages/core-macro/src/rsx/fragment.rs +++ b/packages/core-macro/src/rsx/fragment.rs @@ -8,6 +8,10 @@ //! - [ ] Children //! - [ ] Keys +use syn::parse::ParseBuffer; + +use super::AmbiguousElement; + use { proc_macro::TokenStream, proc_macro2::{Span, TokenStream as TokenStream2}, @@ -19,15 +23,44 @@ use { }, }; -pub struct Fragment {} +pub struct Fragment { + children: Vec, +} impl Parse for Fragment { fn parse(input: ParseStream) -> Result { - todo!() + input.parse::()?; + + let children = Vec::new(); + + // parse the guts + let content: ParseBuffer; + syn::braced!(content in input); + while !content.is_empty() { + content.parse::()?; + + if content.peek(Token![,]) { + let _ = content.parse::(); + } + } + Ok(Self { children }) } } impl ToTokens for Fragment { fn to_tokens(&self, tokens: &mut TokenStream2) { - todo!() + let childs = &self.children; + let children = quote! { + ChildrenList::new(__ctx) + #( .add_child(#childs) )* + .finish() + }; + tokens.append_all(quote! { + // #key_token, + dioxus::builder::vfragment( + __ctx, + None, + #children + ) + }) } } diff --git a/packages/core-macro/src/rsx/mod.rs b/packages/core-macro/src/rsx/mod.rs index a96796c1c..6eb6d90db 100644 --- a/packages/core-macro/src/rsx/mod.rs +++ b/packages/core-macro/src/rsx/mod.rs @@ -26,7 +26,7 @@ pub use node::*; use crate::util::is_valid_tag; use proc_macro2::TokenStream as TokenStream2; -use quote::{quote, ToTokens}; +use quote::{quote, ToTokens, TokenStreamExt}; use syn::{ parse::{Parse, ParseStream}, Error, Ident, LitStr, Result, Token, @@ -34,7 +34,7 @@ use syn::{ pub struct RsxRender { custom_context: Option, - root: AmbiguousElement, + roots: Vec, } impl Parse for RsxRender { @@ -61,16 +61,13 @@ impl Parse for RsxRender { None }; - let root = { input.parse::() }?; - if !input.is_empty() { - return Err(Error::new( - input.span(), - "Currently only one element is allowed per component. Try wrapping your list of components in a `Fragment` tag", - )); + let mut roots = Vec::new(); + while !input.is_empty() { + roots.push(input.parse::()?); } Ok(Self { - root, + roots, custom_context, }) } @@ -78,27 +75,41 @@ impl Parse for RsxRender { impl ToTokens for RsxRender { fn to_tokens(&self, out_tokens: &mut TokenStream2) { - let inner = &self.root; - let output = match &self.custom_context { - // The `in ctx` pattern allows directly rendering - Some(ident) => { - quote! { - #ident.render(dioxus::prelude::LazyNodes::new(move |__ctx|{ - let bump = &__ctx.bump(); - #inner - })) - } - } - // Otherwise we just build the LazyNode wrapper - None => { - quote! { - dioxus::prelude::LazyNodes::new(move |__ctx|{ - let bump = &__ctx.bump(); - #inner - }) - } + let inner = if self.roots.len() == 1 { + let inner = &self.roots[0]; + quote! {#inner} + } else { + let childs = &self.roots; + let children = quote! { + ChildrenList::new(__ctx) + #( .add_child(#childs) )* + .finish() + }; + quote! { + // #key_token, + dioxus::builder::vfragment( + __ctx, + None, + #children + ) } }; - output.to_tokens(out_tokens) + + match &self.custom_context { + // The `in ctx` pattern allows directly rendering + Some(ident) => out_tokens.append_all(quote! { + #ident.render(dioxus::prelude::LazyNodes::new(move |__ctx|{ + let bump = &__ctx.bump(); + #inner + })) + }), + // Otherwise we just build the LazyNode wrapper + None => out_tokens.append_all(quote! { + dioxus::prelude::LazyNodes::new(move |__ctx|{ + let bump = &__ctx.bump(); + #inner + }) + }), + }; } } diff --git a/packages/core/.vscode/settings.json b/packages/core/.vscode/settings.json index 80f51cff8..2f418701b 100644 --- a/packages/core/.vscode/settings.json +++ b/packages/core/.vscode/settings.json @@ -1,3 +1,3 @@ { - "rust-analyzer.inlayHints.enable": false + "rust-analyzer.inlayHints.enable": true } \ No newline at end of file diff --git a/packages/core/examples/borrowed.rs b/packages/core/examples/borrowed.rs index 328889715..e8ad5243f 100644 --- a/packages/core/examples/borrowed.rs +++ b/packages/core/examples/borrowed.rs @@ -39,6 +39,7 @@ fn app(ctx: Context) -> VNode { .item_handler(Callback(set_val.clone())) .build(), None, + &[], )); } root.finish() diff --git a/packages/core/examples/nested.rs b/packages/core/examples/nested.rs index 92c11f7b3..0a4e278cf 100644 --- a/packages/core/examples/nested.rs +++ b/packages/core/examples/nested.rs @@ -15,9 +15,11 @@ static Header: FC<()> = |ctx| { ctx.render(dioxus::prelude::LazyNodes::new(|nodectx| { builder::ElementBuilder::new(nodectx, "div") .child(VNode::Component(nodectx.bump().alloc(VComponent::new( + nodectx, Bottom, (), None, + &[], )))) .finish() })) diff --git a/packages/core/src/arena.rs b/packages/core/src/arena.rs index bca37a866..210a04c4e 100644 --- a/packages/core/src/arena.rs +++ b/packages/core/src/arena.rs @@ -9,9 +9,9 @@ use generational_arena::Arena; use crate::innerlude::*; #[derive(Clone)] -pub struct ScopeArena(Rc>); +pub struct ScopeArena(pub Rc>); -struct ScopeArenaInner { +pub struct ScopeArenaInner { pub(crate) arena: UnsafeCell>, locks: HashMap, } @@ -61,6 +61,31 @@ impl ScopeArena { // todo!() } + pub fn with_scope<'b, O: 'static>( + &'b self, + id: ScopeIdx, + f: impl FnOnce(&'b mut Scope) -> O, + ) -> Result { + todo!() + } + + // return a bumpframe with a lifetime attached to the arena borrow + // this is useful for merging lifetimes + pub fn with_scope_vnode<'b>( + &self, + id: ScopeIdx, + f: impl FnOnce(&mut Scope) -> &VNode<'b>, + ) -> Result<&VNode<'b>> { + todo!() + } + + pub fn try_remove(&mut self, id: ScopeIdx) -> Result { + let inner = unsafe { &mut *self.0.borrow().arena.get() }; + inner + .remove(id) + .ok_or_else(|| Error::FatalInternal("Scope not found")) + } + unsafe fn inner_unchecked<'s>() -> &'s mut Arena { todo!() } diff --git a/packages/core/src/component.rs b/packages/core/src/component.rs index 22ad2106f..487e6e483 100644 --- a/packages/core/src/component.rs +++ b/packages/core/src/component.rs @@ -7,21 +7,14 @@ use crate::innerlude::FC; -pub type ScopeIdx = generational_arena::Index; - -pub trait Properties: PartialEq { +pub unsafe trait Properties: PartialEq + Sized { type Builder; + const CAN_BE_MEMOIZED: bool; fn builder() -> Self::Builder; } -pub struct EmptyBuilder; -impl EmptyBuilder { - pub fn build(self) -> () { - () - } -} - -impl Properties for () { +unsafe impl Properties for () { + const CAN_BE_MEMOIZED: bool = true; type Builder = EmptyBuilder; fn builder() -> Self::Builder { @@ -29,112 +22,14 @@ impl Properties for () { } } -pub fn fc_to_builder(_f: FC) -> T::Builder { +pub struct EmptyBuilder; +impl EmptyBuilder { + #[inline] + pub fn build(self) -> () { + () + } +} + +pub fn fc_to_builder(_: FC) -> T::Builder { T::builder() } - -mod testing { - use std::any::Any; - - // trait PossibleProps { - // type POut: PartialEq; - // fn as_partial_eq(&self) -> Option<&Self::POut> { - // None - // } - // } - - // impl PossibleProps for T { - // type POut = Self; - // } - - // struct SomeProps2<'a> { - // inner: &'a str, - // } - - // Fallback trait for to all types to default to `false`. - trait NotEq { - const IS_EQ: bool = false; - } - impl NotEq for T {} - - // Concrete wrapper type where `IS_COPY` becomes `true` if `T: Copy`. - struct IsEq(std::marker::PhantomData<(G, T)>); - - impl> IsEq { - // Because this is implemented directly on `IsCopy`, it has priority over - // the `NotCopy` trait impl. - // - // Note: this is a *totally different* associated constant from that in - // `NotCopy`. This does not specialize the `NotCopy` trait impl on `IsCopy`. - const IS_EQ: bool = true; - } - - #[derive(PartialEq)] - struct SomeProps { - inner: &'static str, - } - - struct SomeProps2 { - inner: &'static str, - } - - #[test] - fn test() { - let g = IsEq::::IS_EQ; - - // let g = IsEq::>::IS_COPY; - // let g = IsEq::::IS_COPY; - // dbg!(g); - - // let props = SomeProps { inner: "asd" }; - - // let intermediate: Box> = Box::new(props); - // let as_any: Box = Box::new(intermediate); - - // let as_partialeq = as_any - // .downcast_ref::>>() - // .unwrap(); - } - - // struct blah {} - // #[reorder_args] - pub fn blah(a: i32, b: &str, c: &str) {} - - // pub mod blah { - // pub const a: u8 = 0; - // pub const b: u8 = 1; - // } - - trait Eat {} - impl Eat for fn() {} - impl Eat for fn(T) {} - impl Eat for fn(T, K) {} - - mod other { - use super::blah; - fn test2() { - // rsx!{ - // div { - // Ele { - // a: 10, - // b: "asd" - // c: impl Fn() -> () - // } - // } - // } - - // becomes - - // const reorder: fn(_, _) = |a, b| {}; - // blah::META; - // let a = 10; - // let b = "asd"; - // let g = [10, 10.0]; - // let c = g.a; - - // blah(10, "asd"); - } - } -} - -mod style {} diff --git a/packages/core/src/diff.rs b/packages/core/src/diff.rs index d13793d6f..c4adbfb34 100644 --- a/packages/core/src/diff.rs +++ b/packages/core/src/diff.rs @@ -32,7 +32,7 @@ //! //! More info on how to improve this diffing algorithm: //! - https://hacks.mozilla.org/2019/03/fast-bump-allocated-virtual-doms-with-rust-and-wasm/ -use crate::innerlude::*; +use crate::{arena::ScopeArena, innerlude::*}; use bumpalo::Bump; use fxhash::{FxHashMap, FxHashSet}; use generational_arena::Arena; @@ -59,50 +59,29 @@ use std::{ /// subscriptions and props changes. pub struct DiffMachine<'a> { pub create_diffs: bool, + pub cur_idx: ScopeIdx, pub change_list: EditMachine<'a>, pub diffed: FxHashSet, - pub lifecycle_events: VecDeque>, -} -pub enum LifeCycleEvent<'a> { - Mount { - caller: Weak VNode + 'a>, - stable_scope_addr: Weak, - root_id: u32, - }, - PropsChanged { - caller: Weak VNode + 'a>, - stable_scope_addr: Weak, - root_id: u32, - }, - SameProps { - caller: Weak VNode + 'a>, - stable_scope_addr: Weak, - root_id: u32, - }, - Replace { - caller: Weak VNode + 'a>, - old_scope: Weak, - new_scope: Weak, - root_id: u32, - }, - Remove { - stable_scope_addr: Weak, - root_id: u32, - }, + pub components: ScopeArena, + pub event_queue: EventQueue, + pub seen_nodes: FxHashSet, } static COUNTER: AtomicU32 = AtomicU32::new(1); -fn get_id() -> u32 { +fn next_id() -> u32 { COUNTER.fetch_add(1, std::sync::atomic::Ordering::Relaxed) } impl<'a> DiffMachine<'a> { - pub fn new() -> Self { + pub fn new(components: ScopeArena, cur_idx: ScopeIdx, event_queue: EventQueue) -> Self { Self { + components, + cur_idx, + event_queue, create_diffs: true, - lifecycle_events: VecDeque::new(), change_list: EditMachine::new(), diffed: FxHashSet::default(), + seen_nodes: FxHashSet::default(), } } @@ -110,15 +89,15 @@ impl<'a> DiffMachine<'a> { self.change_list.emitter } - pub fn diff_node(&mut self, old: &VNode<'a>, new: &VNode<'a>) { + pub fn diff_node(&mut self, old_node: &VNode<'a>, new_node: &VNode<'a>) { // pub fn diff_node(&mut self, old: &VNode<'a>, new: &VNode<'a>) { /* For each valid case, we "commit traversal", meaning we save this current position in the tree. Then, we diff and queue an edit event (via chagelist). s single trees - when components show up, we save that traversal and then re-enter later. When re-entering, we reuse the EditList in DiffState */ - match (old, new) { - (VNode::Text(VText { text: old_text }), VNode::Text(VText { text: new_text })) => { + match (old_node, new_node) { + (VNode::Text(old_text), VNode::Text(new_text)) => { if old_text != new_text { self.change_list.commit_traversal(); self.change_list.set_text(new_text); @@ -127,13 +106,13 @@ impl<'a> DiffMachine<'a> { (VNode::Text(_), VNode::Element(_)) => { self.change_list.commit_traversal(); - self.create(new); + self.create(new_node); self.change_list.replace_with(); } (VNode::Element(_), VNode::Text(_)) => { self.change_list.commit_traversal(); - self.create(new); + self.create(new_node); self.change_list.replace_with(); } @@ -151,55 +130,86 @@ impl<'a> DiffMachine<'a> { } (VNode::Component(cold), VNode::Component(cnew)) => { - // todo!("should not happen") - // self.change_list.commit_traversal(); + // Make sure we're dealing with the same component (by function pointer) if cold.user_fc == cnew.user_fc { - // todo: create a stable addr - let caller = Rc::downgrade(&cnew.caller); - let id = cold.stable_addr.borrow().unwrap(); - *cnew.stable_addr.borrow_mut() = Some(id); - *cnew.ass_scope.borrow_mut() = *cold.ass_scope.borrow(); + // Make sure the new component vnode is referencing the right scope id + let scope_id = cold.ass_scope.borrow().clone(); + *cnew.ass_scope.borrow_mut() = scope_id; - let scope = Rc::downgrade(&cold.ass_scope); - self.lifecycle_events - .push_back(LifeCycleEvent::PropsChanged { - caller, - root_id: id, - stable_scope_addr: scope, - }); + // make sure the component's caller function is up to date + self.components + .with_scope(scope_id.unwrap(), |scope| { + scope.caller = Rc::downgrade(&cnew.caller) + }) + .unwrap(); + + // React doesn't automatically memoize, but we do. + // The cost is low enough to make it worth checking + let should_render = match cold.comparator { + Some(comparator) => comparator(cnew), + None => true, + }; + + if should_render { + self.change_list.commit_traversal(); + self.components + .with_scope(scope_id.unwrap(), |f| { + f.run_scope().unwrap(); + }) + .unwrap(); + // diff_machine.change_list.load_known_root(root_id); + // run the scope + // + } else { + // Component has memoized itself and doesn't need to be re-rendered. + // We still need to make sure the child's props are up-to-date. + // Don't commit traversal + } } else { - let caller = Rc::downgrade(&cnew.caller); - let id = cold.stable_addr.borrow().unwrap(); - let old_scope = Rc::downgrade(&cold.ass_scope); - let new_scope = Rc::downgrade(&cnew.ass_scope); + // A new component has shown up! We need to destroy the old node - self.lifecycle_events.push_back(LifeCycleEvent::Replace { - caller, - root_id: id, - old_scope, - new_scope, - }); + // Wipe the old one and plant the new one + self.change_list.commit_traversal(); + self.create(new_node); + self.change_list.replace_with(); + + // Now we need to remove the old scope and all of its descendents + let old_scope = cold.ass_scope.borrow().as_ref().unwrap().clone(); + self.destroy_scopes(old_scope); } } // todo: knock out any listeners - (_, VNode::Component(_new)) => { + (_, VNode::Component(_)) => { self.change_list.commit_traversal(); + self.create(new_node); + self.change_list.replace_with(); } + // A component is being torn down in favor of a non-component node (VNode::Component(_old), _) => { - todo!("Usage of component VNode not currently supported"); + self.change_list.commit_traversal(); + self.create(new_node); + self.change_list.replace_with(); + + // Destroy the original scope and any of its children + self.destroy_scopes(_old.ass_scope.borrow().unwrap()); } + // Anything suspended is not enabled ATM (VNode::Suspended, _) | (_, VNode::Suspended) => { todo!("Suspended components not currently available") } - (VNode::Fragment(_), VNode::Fragment(_)) => { + // Fragments are special + // we actually have to remove a bunch of nodes + (VNode::Fragment(_), _) => { + todo!("Fragments not currently supported in diffing") + } + + (_, VNode::Fragment(_)) => { todo!("Fragments not currently supported in diffing") } - (_, VNode::Fragment(_)) => todo!("Fragments not currently supported in diffing"), - (VNode::Fragment(_), _) => todo!("Fragments not currently supported in diffing"), } } @@ -215,11 +225,11 @@ impl<'a> DiffMachine<'a> { fn create(&mut self, node: &VNode<'a>) { debug_assert!(self.change_list.traversal_is_committed()); match node { - VNode::Text(VText { text }) => { + VNode::Text(text) => { self.change_list.create_text_node(text); } VNode::Element(&VElement { - key: _, + key, tag_name, listeners, attributes, @@ -250,7 +260,7 @@ impl<'a> DiffMachine<'a> { // text content, and finally (3) append the text node to this // parent. if children.len() == 1 { - if let VNode::Text(VText { text }) = children[0] { + if let VNode::Text(text) = children[0] { self.change_list.set_text(text); return; } @@ -258,35 +268,121 @@ impl<'a> DiffMachine<'a> { for child in children { self.create(child); - self.change_list.append_child(); + if let VNode::Fragment(_) = child { + // do nothing + // fragments append themselves + } else { + self.change_list.append_child(); + } } } - /* - todo: integrate re-entrace - */ VNode::Component(component) => { self.change_list .create_text_node("placeholder for vcomponent"); - let id = get_id(); - *component.stable_addr.as_ref().borrow_mut() = Some(id); - self.change_list.save_known_root(id); - let scope = Rc::downgrade(&component.ass_scope); - self.lifecycle_events.push_back(LifeCycleEvent::Mount { - caller: Rc::downgrade(&component.caller), - root_id: id, - stable_scope_addr: scope, - }); + let root_id = next_id(); + self.change_list.save_known_root(root_id); + + log::debug!("Mounting a new component"); + let caller: Weak = Rc::downgrade(&component.caller); + + // We're modifying the component arena while holding onto references into the assoiated bump arenas of its children + // those references are stable, even if the component arena moves around in memory, thanks to the bump arenas. + // However, there is no way to convey this to rust, so we need to use unsafe to pierce through the lifetime. + + let parent_idx = self.cur_idx; + + // Insert a new scope into our component list + let idx = self + .components + .with(|components| { + components.insert_with(|new_idx| { + let parent_scope = self.components.try_get(parent_idx).unwrap(); + let height = parent_scope.height + 1; + Scope::new( + caller, + new_idx, + Some(parent_idx), + height, + self.event_queue.new_channel(height, new_idx), + self.components.clone(), + component.children, + ) + }) + }) + .unwrap(); + + { + let cur_component = self.components.try_get_mut(idx).unwrap(); + let mut ch = cur_component.descendents.borrow_mut(); + ch.insert(idx); + std::mem::drop(ch); + } + + // yaaaaay lifetimes out of thin air + // really tho, we're merging the frame lifetimes together + let inner: &'a mut _ = unsafe { &mut *self.components.0.borrow().arena.get() }; + let new_component = inner.get_mut(idx).unwrap(); + + // Actually initialize the caller's slot with the right address + *component.ass_scope.borrow_mut() = Some(idx); + + // Run the scope for one iteration to initialize it + new_component.run_scope().unwrap(); + + // And then run the diff algorithm + self.diff_node(new_component.old_frame(), new_component.next_frame()); + + // Finally, insert this node as a seen node. + self.seen_nodes.insert(idx); } + + // we go the the "known root" but only operate on a sibling basis + VNode::Fragment(frag) => { + // create the children directly in the space + for child in frag.children { + self.create(child); + self.change_list.append_child(); + } + } + VNode::Suspended => { todo!("Creation of VNode::Suspended not yet supported") } - VNode::Fragment(frag) => { - // - todo!("Cannot current create fragments") + } + } + + /// Destroy a scope and all of its descendents. + /// + /// Calling this will run the destuctors on all hooks in the tree. + /// It will also add the destroyed nodes to the `seen_nodes` cache to prevent them from being renderered. + fn destroy_scopes(&mut self, old_scope: ScopeIdx) { + let mut nodes_to_delete = vec![old_scope]; + let mut scopes_to_explore = vec![old_scope]; + + // explore the scope tree breadth first + while let Some(scope_id) = scopes_to_explore.pop() { + // If we're planning on deleting this node, then we don't need to both rendering it + self.seen_nodes.insert(scope_id); + let scope = self.components.try_get(scope_id).unwrap(); + for child in scope.descendents.borrow().iter() { + // Add this node to be explored + scopes_to_explore.push(child.clone()); + + // Also add it for deletion + nodes_to_delete.push(child.clone()); } } + + // Delete all scopes that we found as part of this subtree + for node in nodes_to_delete { + log::debug!("Removing scope {:#?}", node); + let _scope = self.components.try_remove(node).unwrap(); + // do anything we need to do to delete the scope + // I think we need to run the destructors on the hooks + // TODO + } } // Diff event listeners between `old` and `new`. @@ -297,7 +393,6 @@ impl<'a> DiffMachine<'a> { // // The change list stack is left unchanged. fn diff_listeners(&mut self, old: &[Listener<'_>], new: &[Listener<'_>]) { - // fn diff_listeners(&mut self, old: &[Listener<'a>], new: &[Listener<'a>]) { if !old.is_empty() || !new.is_empty() { self.change_list.commit_traversal(); } @@ -319,26 +414,15 @@ impl<'a> DiffMachine<'a> { .update_event_listener(event_type, new_l.scope, new_l.id) } - // if let Some(scope) = self.current_idx { - // let cb = CbIdx::from_gi_index(scope, l_idx); - // self.change_list - // .update_event_listener(event_type, new_l.scope, new_l.id); - // } - continue 'outer1; } } - // if let Some(scope) = self.current_idx { - // let cb = CbIdx::from_gi_index(scope, l_idx); self.change_list .new_event_listener(event_type, new_l.scope, new_l.id); - // } } 'outer2: for old_l in old { - // registry.remove(old_l); - for new_l in new { if new_l.event == old_l.event { continue 'outer2; @@ -363,6 +447,9 @@ impl<'a> DiffMachine<'a> { ) { // Do O(n^2) passes to add/update and remove attributes, since // there are almost always very few attributes. + // + // The "fast" path is when the list of attributes name is identical and in the same order + // With the Rsx and Html macros, this will almost always be the case 'outer: for new_attr in new { if new_attr.is_volatile() { self.change_list.commit_traversal(); @@ -380,6 +467,8 @@ impl<'a> DiffMachine<'a> { ); } continue 'outer; + } else { + // names are different, a varying order of attributes has arrived } } @@ -420,22 +509,17 @@ impl<'a> DiffMachine<'a> { if new.len() == 1 { match (old.first(), &new[0]) { - ( - Some(&VNode::Text(VText { text: old_text })), - &VNode::Text(VText { text: new_text }), - ) if old_text == new_text => { + (Some(&VNode::Text(old_text)), &VNode::Text(new_text)) if old_text == new_text => { // Don't take this fast path... } - (_, &VNode::Text(VText { text })) => { + (_, &VNode::Text(text)) => { self.change_list.commit_traversal(); self.change_list.set_text(text); - // for o in old { - // registry.remove_subtree(o); - // } return; } + // todo: any more optimizations (_, _) => {} } } @@ -462,6 +546,7 @@ impl<'a> DiffMachine<'a> { if new_is_keyed && old_is_keyed { let t = self.change_list.next_temporary(); + self.diff_keyed_children(old, new); self.change_list.set_next_temporary(t); } else { self.diff_non_keyed_children(old, new); @@ -977,11 +1062,12 @@ impl<'a> DiffMachine<'a> { // self.change_list // .create_text_node("placeholder for vcomponent"); - let root_id = vcomp.stable_addr.as_ref().borrow().unwrap(); - self.lifecycle_events.push_back(LifeCycleEvent::Remove { - root_id, - stable_scope_addr: Rc::downgrade(&vcomp.ass_scope), - }) + todo!() + // let root_id = vcomp.stable_addr.as_ref().borrow().unwrap(); + // self.lifecycle_events.push_back(LifeCycleEvent::Remove { + // root_id, + // stable_scope_addr: Rc::downgrade(&vcomp.ass_scope), + // }) // let id = get_id(); // *component.stable_addr.as_ref().borrow_mut() = Some(id); // self.change_list.save_known_root(id); diff --git a/packages/core/src/lib.rs b/packages/core/src/lib.rs index 83ab31c98..7299286a5 100644 --- a/packages/core/src/lib.rs +++ b/packages/core/src/lib.rs @@ -120,14 +120,12 @@ pub mod prelude { pub use crate::nodebuilder::LazyNodes; + pub use crate::nodebuilder::ChildrenList; pub use crate::virtual_dom::NodeCtx; // pub use nodes::iterables::IterableNodes; /// This type alias is an internal way of abstracting over the static functions that represent components. pub use crate::innerlude::FC; - // TODO @Jon, fix this - // hack the VNode type until VirtualNode is fixed in the macro crate - // expose our bumpalo type pub use bumpalo; pub use bumpalo::Bump; @@ -139,19 +137,9 @@ pub mod prelude { pub use dioxus_core_macro::{format_args_f, html, rsx, Props}; - pub use crate::component::ScopeIdx; pub use crate::diff::DiffMachine; + pub use crate::virtual_dom::ScopeIdx; pub use crate::debug_renderer::DebugRenderer; - pub use crate::dioxus_main; pub use crate::hooks::*; } - -#[macro_export] -macro_rules! dioxus_main { - ($i:ident) => { - fn main() { - todo!("this macro is a placeholder for launching a dioxus app on different platforms. \nYou probably don't want to use this, but it's okay for small apps.") - } - }; -} diff --git a/packages/core/src/nodebuilder.rs b/packages/core/src/nodebuilder.rs index aa1a0ac57..0819c2da0 100644 --- a/packages/core/src/nodebuilder.rs +++ b/packages/core/src/nodebuilder.rs @@ -1,12 +1,12 @@ //! Helpers for building virtual DOM VNodes. -use std::{any::Any, borrow::BorrowMut, intrinsics::transmute, u128}; +use std::{any::Any, borrow::BorrowMut, fmt::Arguments, intrinsics::transmute, u128}; use crate::{ events::VirtualEvent, innerlude::{Properties, VComponent, FC}, nodes::{Attribute, Listener, NodeKey, VNode}, - prelude::VElement, + prelude::{VElement, VFragment}, virtual_dom::NodeCtx, }; @@ -269,6 +269,20 @@ where self } + pub fn key2(mut self, args: Arguments) -> Self { + let key = match args.as_str() { + Some(static_str) => static_str, + None => { + use bumpalo::core_alloc::fmt::Write; + let mut s = bumpalo::collections::String::new_in(self.ctx.bump()); + s.write_fmt(args).unwrap(); + s.into_bump_str() + } + }; + self.key = NodeKey(Some(key)); + self + } + /// Create the virtual DOM VNode described by this builder. /// /// # Example @@ -650,17 +664,54 @@ pub fn attr<'a>(name: &'static str, value: &'a str) -> Attribute<'a> { Attribute { name, value } } -pub fn virtual_child<'a, T: Properties>( +pub fn virtual_child<'a, T: Properties + 'a>( ctx: &NodeCtx<'a>, f: FC, props: T, key: Option<&'a str>, // key: NodeKey<'a>, + children: &'a [VNode<'a>], ) -> VNode<'a> { // currently concerned about if props have a custom drop implementation // might override it with the props macro - todo!() - // VNode::Component( - // ctx.bump() - // .alloc(crate::nodes::VComponent::new(f, props, key)), - // ) + // todo!() + VNode::Component( + ctx.bump() + .alloc(crate::nodes::VComponent::new(ctx, f, props, key, children)), + // ctx.bump() + // .alloc(crate::nodes::VComponent::new(f, props, key)), + ) +} + +pub fn vfragment<'a>( + ctx: &NodeCtx<'a>, + key: Option<&'a str>, // key: NodeKey<'a>, + children: &'a [VNode<'a>], +) -> VNode<'a> { + VNode::Fragment(ctx.bump().alloc(VFragment::new(key, children))) +} + +pub struct ChildrenList<'a, 'b> { + ctx: &'b NodeCtx<'a>, + children: bumpalo::collections::Vec<'a, VNode<'a>>, +} + +impl<'a, 'b> ChildrenList<'a, 'b> { + pub fn new(ctx: &'b NodeCtx<'a>) -> Self { + Self { + ctx, + children: bumpalo::collections::Vec::new_in(ctx.bump()), + } + } + + pub fn add_child(mut self, nodes: impl IntoIterator>) -> Self { + for item in nodes { + let child = item.into_vnode(&self.ctx); + self.children.push(child); + } + self + } + + pub fn finish(self) -> &'a [VNode<'a>] { + self.children.into_bump_slice() + } } diff --git a/packages/core/src/nodes.rs b/packages/core/src/nodes.rs index 388e8ae74..dde21a7f7 100644 --- a/packages/core/src/nodes.rs +++ b/packages/core/src/nodes.rs @@ -7,6 +7,8 @@ use crate::{ events::VirtualEvent, innerlude::{Context, Properties, Scope, ScopeIdx, FC}, nodebuilder::text3, + virtual_dom::NodeCtx, + // support::NodeCtx, }; use bumpalo::Bump; use std::{ @@ -27,7 +29,7 @@ pub enum VNode<'src> { Element(&'src VElement<'src>), /// A text node (node type `TEXT_NODE`). - Text(VText<'src>), + Text(&'src str), /// A fragment is a "virtual position" in the DOM /// Fragments may have children and keys @@ -46,11 +48,11 @@ pub enum VNode<'src> { impl<'a> Clone for VNode<'a> { fn clone(&self) -> Self { match self { - VNode::Element(el) => VNode::Element(el), - VNode::Text(origi) => VNode::Text(VText { text: origi.text }), - VNode::Fragment(frag) => VNode::Fragment(frag), + VNode::Element(element) => VNode::Element(element), + VNode::Text(text) => VNode::Text(text), + VNode::Fragment(fragment) => VNode::Fragment(fragment), + VNode::Component(component) => VNode::Component(component), VNode::Suspended => VNode::Suspended, - VNode::Component(c) => VNode::Component(c), } } } @@ -86,7 +88,7 @@ impl<'a> VNode<'a> { /// Construct a new text node with the given text. #[inline] pub fn text(text: &'a str) -> VNode<'a> { - VNode::Text(VText { text }) + VNode::Text(text) } pub fn text_args(bump: &'a Bump, args: Arguments) -> VNode<'a> { @@ -210,51 +212,34 @@ impl<'a> NodeKey<'a> { } } -#[derive(Debug, PartialEq)] -pub struct VText<'bump> { - pub text: &'bump str, -} - -impl<'b> Clone for VText<'b> { - fn clone(&self) -> Self { - Self { text: self.text } - } -} - -impl<'a> VText<'a> { - // / Create an new `VText` instance with the specified text. - pub fn new(text: &'a str) -> Self { - VText { text: text.into() } - } -} - // ============================== // Custom components // ============================== /// Virtual Components for custom user-defined components /// Only supports the functional syntax -pub type StableScopeAddres = RefCell>; -pub type VCompAssociatedScope = RefCell>; +pub type StableScopeAddres = Option; +pub type VCompAssociatedScope = Option; pub struct VComponent<'src> { pub key: NodeKey<'src>, - pub stable_addr: Rc, - pub ass_scope: Rc, + pub stable_addr: RefCell, + pub ass_scope: RefCell, // pub comparator: Rc bool + 'src>, - pub caller: Rc VNode + 'src>, + pub caller: Rc VNode>, pub children: &'src [VNode<'src>], + pub comparator: Option<&'src dyn Fn(&VComponent) -> bool>, + // a pointer into the bump arena (given by the 'src lifetime) // raw_props: Box, - // raw_props: *const (), + raw_props: *const (), // a pointer to the raw fn typ pub user_fc: *const (), - _p: PhantomData<&'src ()>, } impl<'a> VComponent<'a> { @@ -264,70 +249,123 @@ impl<'a> VComponent<'a> { // TODO: lift the requirement that props need to be static // we want them to borrow references... maybe force implementing a "to_static_unsafe" trait - pub fn new( + pub fn new( + // bump: &'a Bump, + ctx: &NodeCtx<'a>, component: FC

, // props: bumpalo::boxed::Box<'a, P>, props: P, key: Option<&'a str>, + children: &'a [VNode<'a>], ) -> Self { // pub fn new(component: FC

, props: P, key: Option<&'a str>) -> Self { // let bad_props = unsafe { transmogrify(props) }; + let bump = ctx.bump(); let caller_ref = component as *const (); + let props = bump.alloc(props); - // let raw_props = props as *const P as *const (); + let raw_props = props as *const P as *const (); - // let props_comparator = move |other: &VComponent| { - // // Safety: - // // We are guaranteed that the props will be of the same type because - // // there is no way to create a VComponent other than this `new` method. - // // - // // Therefore, if the render functions are identical (by address), then so will be - // // props type paramter (because it is the same render function). Therefore, we can be - // // sure - // if caller_ref == other.user_fc { - // let g = other.raw_ctx.downcast_ref::

().unwrap(); - // // let real_other = unsafe { &*(other.raw_props as *const _ as *const P) }; - // &props == g - // } else { - // false - // } - // }; + let comparator: Option<&dyn Fn(&VComponent) -> bool> = { + if P::CAN_BE_MEMOIZED { + Some(bump.alloc(move |other: &VComponent| { + // Safety: + // We are guaranteed that the props will be of the same type because + // there is no way to create a VComponent other than this `new` method. + // + // Therefore, if the render functions are identical (by address), then so will be + // props type paramter (because it is the same render function). Therefore, we can be + // sure + if caller_ref == other.user_fc { + // let g = other.raw_ctx.downcast_ref::

().unwrap(); + let real_other = unsafe { &*(other.raw_props as *const _ as *const P) }; + &props == &real_other + } else { + false + } + })) + } else { + None + } + }; // let prref: &'a P = props.as_ref(); - let caller: Rc VNode> = Rc::new(move |scope| { - // - // let props2 = bad_props; - // props.as_ref() - // let ctx = Context { - // props: prref, - // scope, - // }; - todo!() - // component(ctx) - }); - // let caller = Rc::new(create_closure(component, raw_props)); + // let r = create_closure(component, raw_props); + // let caller: Rc Fn(&'g Scope) -> VNode<'g>> = Rc::new(move |scope| { + // // r(scope); + // // + // // let props2 = bad_props; + // // props.as_ref(); + // // let ctx = Context { + // // props: prref, + // // scope, + // // }; + // // let ctx: Context<'g, P> = todo!(); + // // todo!() + // // let r = component(ctx); + // todo!() + // }); + let caller = create_closure(component, raw_props); + + // let caller: Rc VNode> = Rc::new(create_closure(component, raw_props)); let key = match key { Some(key) => NodeKey::new(key), None => NodeKey(None), }; + // raw_props: Box::new(props), + // comparator: Rc::new(props_comparator), Self { key, - ass_scope: Rc::new(RefCell::new(None)), + ass_scope: RefCell::new(None), user_fc: caller_ref, - // raw_props: Box::new(props), - _p: PhantomData, - children: &[], + comparator, + raw_props, + children, caller, - // comparator: Rc::new(props_comparator), - stable_addr: Rc::new(RefCell::new(None)), + stable_addr: RefCell::new(None), } } } +type Captured<'a> = Rc Fn(&'r Scope) -> VNode<'r> + 'a>; + +fn create_closure<'a, P: Properties + 'a>( + component: FC

, + raw_props: *const (), +) -> Rc Fn(&'r Scope) -> VNode<'r>> { + // ) -> impl for<'r> Fn(&'r Scope) -> VNode<'r> { + let g: Captured = Rc::new(move |scp: &Scope| -> VNode { + // cast back into the right lifetime + let safe_props: &'_ P = unsafe { &*(raw_props as *const P) }; + // let ctx: Context = todo!(); + let ctx: Context

= Context { + props: safe_props, + scope: scp, + }; + + let g = component(ctx); + let g2 = unsafe { std::mem::transmute(g) }; + g2 + }); + let r: Captured<'static> = unsafe { std::mem::transmute(g) }; + r +} + pub struct VFragment<'src> { pub key: NodeKey<'src>, pub children: &'src [VNode<'src>], } + +impl<'a> VFragment<'a> { + pub fn new(key: Option<&'a str>, children: &'a [VNode<'a>]) -> Self { + let key = match key { + Some(key) => NodeKey::new(key), + None => NodeKey(None), + }; + + Self { key, children } + } +} diff --git a/packages/core/src/patch.rs b/packages/core/src/patch.rs index 98d3959ba..6638ea1f2 100644 --- a/packages/core/src/patch.rs +++ b/packages/core/src/patch.rs @@ -146,6 +146,7 @@ pub struct EditMachine<'lock> { pub traversal: Traversal, next_temporary: u32, forcing_new_listeners: bool, + pub cur_height: u32, // // if the current node is a "known" node // // any actions that modify this node should update the mapping @@ -158,6 +159,7 @@ impl<'lock> EditMachine<'lock> { Self { // current_known: None, traversal: Traversal::new(), + cur_height: 0, next_temporary: 0, forcing_new_listeners: false, emitter: EditList::<'lock>::default(), diff --git a/packages/core/src/virtual_dom.rs b/packages/core/src/virtual_dom.rs index b84e79101..5d0d62de8 100644 --- a/packages/core/src/virtual_dom.rs +++ b/packages/core/src/virtual_dom.rs @@ -32,6 +32,7 @@ use std::{ pin::Pin, rc::{Rc, Weak}, }; +pub type ScopeIdx = generational_arena::Index; /// An integrated virtual node system that progresses events and diffs UI trees. /// Differences are converted into patches which a renderer can use to draw the UI. @@ -52,8 +53,8 @@ pub struct VirtualDom { /// a strong allocation to the "caller" for the original component and its props #[doc(hidden)] - _root_caller: Rc>, - + _root_caller: Rc, + // _root_caller: Rc>, /// Type of the original ctx. This is stored as TypeId so VirtualDom does not need to be generic. /// /// Whenver props need to be updated, an Error will be thrown if the new props do not @@ -137,7 +138,8 @@ impl VirtualDom { // Normally, a component would be passed as a child in the RSX macro which automatically produces OpaqueComponents // Here, we need to make it manually, using an RC to force the Weak reference to stick around for the main scope. - let _root_caller: Rc> = Rc::new(move |scope| { + let _root_caller: Rc = Rc::new(move |scope| { + // let _root_caller: Rc> = Rc::new(move |scope| { // the lifetime of this closure is just as long as the lifetime on the scope reference // this closure moves root props (which is static) into this closure let props = unsafe { &*(&root_props as *const _) }; @@ -154,10 +156,11 @@ impl VirtualDom { // Make the first scope // We don't run the component though, so renderers will need to call "rebuild" when they initialize their DOM let link = components.clone(); - let event_channel = Rc::new(move || {}); + let base_scope = components .with(|arena| { arena.insert_with(move |myidx| { + let event_channel = _event_queue.new_channel(0, myidx); Scope::new(caller_ref, myidx, None, 0, event_channel, link, &[]) }) }) @@ -175,7 +178,11 @@ impl VirtualDom { /// Performs a *full* rebuild of the virtual dom, returning every edit required to generate the actual dom rom scratch /// Currently this doesn't do what we want it to do pub fn rebuild<'s>(&'s mut self) -> Result> { - let mut diff_machine = DiffMachine::new(); + let mut diff_machine = DiffMachine::new( + self.components.clone(), + self.base_scope, + self.event_queue.clone(), + ); // Schedule an update and then immediately call it on the root component // This is akin to a hook being called from a listener and requring a re-render @@ -190,10 +197,6 @@ impl VirtualDom { Ok(diff_machine.consume()) } - - pub fn base_scope(&self) -> &Scope { - todo!() - } } // ====================================== @@ -248,7 +251,9 @@ impl VirtualDom { self.components.try_get_mut(id)?.call_listener(event)?; - let mut diff_machine = DiffMachine::new(); + let mut diff_machine = + DiffMachine::new(self.components.clone(), id, self.event_queue.clone()); + self.progress_completely(&mut diff_machine)?; Ok(diff_machine.consume()) @@ -263,212 +268,48 @@ impl VirtualDom { diff_machine: &'_ mut DiffMachine<'s>, ) -> Result<()> { // Add this component to the list of components that need to be difed - #[allow(unused_assignments)] - let mut cur_height: u32 = 0; + // #[allow(unused_assignments)] + // let mut cur_height: u32 = 0; // Now, there are events in the queue - let mut seen_nodes = HashSet::::new(); let mut updates = self.event_queue.0.as_ref().borrow_mut(); // Order the nodes by their height, we want the biggest nodes on the top // This prevents us from running the same component multiple times updates.sort_unstable(); + log::debug!("There are: {:#?} updates to be processed", updates.len()); + // Iterate through the triggered nodes (sorted by height) and begin to diff them for update in updates.drain(..) { + log::debug!("Running updates for: {:#?}", update); // Make sure this isn't a node we've already seen, we don't want to double-render anything // If we double-renderer something, this would cause memory safety issues - if seen_nodes.contains(&update.idx) { + if diff_machine.seen_nodes.contains(&update.idx) { continue; } // Now, all the "seen nodes" are nodes that got notified by running this listener - seen_nodes.insert(update.idx.clone()); + diff_machine.seen_nodes.insert(update.idx.clone()); // Start a new mutable borrow to components // We are guaranteeed that this scope is unique because we are tracking which nodes have modified let cur_component = self.components.try_get_mut(update.idx).unwrap(); + // let inner: &'s mut _ = unsafe { &mut *self.components.0.borrow().arena.get() }; + // let cur_component = inner.get_mut(update.idx).unwrap(); cur_component.run_scope()?; diff_machine.diff_node(cur_component.old_frame(), cur_component.next_frame()); - cur_height = cur_component.height; + // cur_height = cur_component.height; - log::debug!( - "Processing update: {:#?} with height {}", - &update.idx, - cur_height - ); - - // Now, the entire subtree has been invalidated. We need to descend depth-first and process - // any updates that the diff machine has proprogated into the component lifecycle queue - while let Some(event) = diff_machine.lifecycle_events.pop_front() { - match event { - // A new component has been computed from the diffing algorithm - // create a new component in the arena, run it, move the diffing machine to this new spot, and then diff it - // this will flood the lifecycle queue with new updates to build up the subtree - LifeCycleEvent::Mount { - caller, - root_id: id, - stable_scope_addr, - } => { - log::debug!("Mounting a new component"); - - // We're modifying the component arena while holding onto references into the assoiated bump arenas of its children - // those references are stable, even if the component arena moves around in memory, thanks to the bump arenas. - // However, there is no way to convey this to rust, so we need to use unsafe to pierce through the lifetime. - - // Insert a new scope into our component list - let idx = self.components.with(|components| { - components.insert_with(|new_idx| { - let height = cur_height + 1; - Scope::new( - caller, - new_idx, - Some(cur_component.arena_idx), - height, - self.event_queue.new_channel(height, new_idx), - self.components.clone(), - &[], - ) - }) - })?; - - { - let cur_component = self.components.try_get_mut(update.idx).unwrap(); - let mut ch = cur_component.descendents.borrow_mut(); - ch.insert(idx); - std::mem::drop(ch); - } - - // Grab out that component - let new_component = self.components.try_get_mut(idx).unwrap(); - - // Actually initialize the caller's slot with the right address - *stable_scope_addr.upgrade().unwrap().as_ref().borrow_mut() = Some(idx); - - // Run the scope for one iteration to initialize it - new_component.run_scope()?; - - // Navigate the diff machine to the right point in the output dom - diff_machine.change_list.load_known_root(id); - - // And then run the diff algorithm - diff_machine - .diff_node(new_component.old_frame(), new_component.next_frame()); - - // Finally, insert this node as a seen node. - seen_nodes.insert(idx); - } - - // A component has remained in the same location but its properties have changed - // We need to process this component and then dump the output lifecycle events into the queue - LifeCycleEvent::PropsChanged { - caller, - root_id, - stable_scope_addr, - } => { - log::debug!("Updating a component after its props have changed"); - - // Get the stable index to the target component - // This *should* exist due to guarantees in the diff algorithm - let idx = stable_scope_addr - .upgrade() - .unwrap() - .as_ref() - .borrow() - .unwrap(); - - // Grab out that component - let component = self.components.try_get_mut(idx).unwrap(); - - // We have to move the caller over or running the scope will fail - component.update_caller(caller); - - // Run the scope - component.run_scope()?; - - // Navigate the diff machine to the right point in the output dom - diff_machine.change_list.load_known_root(root_id); - - // And then run the diff algorithm - diff_machine.diff_node(component.old_frame(), component.next_frame()); - - // Finally, insert this node as a seen node. - seen_nodes.insert(idx); - } - - // A component's parent has updated, but its properties did not change. - // This means the caller ptr is invalidated and needs to be updated, but the component itself does not need to be re-ran - LifeCycleEvent::SameProps { - caller, - stable_scope_addr, - .. - } => { - // In this case, the parent made a new VNode that resulted in the same props for us - // However, since our caller is located in a Bump frame, we need to update the caller pointer (which is now invalid) - log::debug!("Received the same props"); - - // Get the stable index to the target component - // This *should* exist due to guarantees in the diff algorithm - let idx = stable_scope_addr - .upgrade() - .unwrap() - .as_ref() - .borrow() - .unwrap(); - - // Grab out that component - let component = self.components.try_get_mut(idx).unwrap(); - - // We have to move the caller over or running the scope will fail - component.update_caller(caller); - - // This time, we will not add it to our seen nodes since we did not actually run it - } - - LifeCycleEvent::Remove { - root_id, - stable_scope_addr, - } => { - let id = stable_scope_addr - .upgrade() - .unwrap() - .as_ref() - .borrow() - .unwrap(); - - log::warn!("Removing node {:#?}", id); - - // This would normally be recursive but makes sense to do linear to - let mut children_to_remove = VecDeque::new(); - children_to_remove.push_back(id); - - // Accumulate all the child components that need to be removed - while let Some(child_id) = children_to_remove.pop_back() { - let comp = self.components.try_get(child_id).unwrap(); - let children = comp.descendents.borrow(); - for child in children.iter() { - children_to_remove.push_front(*child); - } - log::debug!("Removing component: {:#?}", child_id); - self.components - .with(|components| components.remove(child_id).unwrap()) - .unwrap(); - } - } - - LifeCycleEvent::Replace { - caller, - root_id: id, - .. - } => { - unimplemented!("This feature (Replace) is unimplemented") - } - } - } + // log::debug!( + // "Processing update: {:#?} with height {}", + // &update.idx, + // cur_height + // ); } Ok(()) @@ -492,7 +333,7 @@ pub struct Scope { // IDs of children that this scope has created // This enables us to drop the children and their children when this scope is destroyed - descendents: RefCell>, + pub(crate) descendents: RefCell>, child_nodes: &'static [VNode<'static>], @@ -510,8 +351,8 @@ pub struct Scope { pub event_channel: Rc, // pub event_queue: EventQueue, - pub caller: Weak>, - + pub caller: Weak, + // pub caller: Weak>, pub hookidx: RefCell, // ========================== @@ -545,8 +386,9 @@ impl Scope { // // Scopes cannot be made anywhere else except for this file // Therefore, their lifetimes are connected exclusively to the virtual dom - fn new<'creator_node>( - caller: Weak>, + pub fn new<'creator_node>( + caller: Weak, + // caller: Weak>, arena_idx: ScopeIdx, parent: Option, height: u32, @@ -572,13 +414,17 @@ impl Scope { // Not the best solution, so TODO on removing this in favor of a dedicated resource abstraction. let caller = unsafe { std::mem::transmute::< - Weak>, - Weak>, + Weak, + Weak, + // Weak>, + // Weak>, >(caller) }; + let child_nodes = unsafe { std::mem::transmute(child_nodes) }; + Self { - child_nodes: &[], + child_nodes: child_nodes, caller, parent, arena_idx, @@ -594,11 +440,14 @@ impl Scope { } } - pub fn update_caller<'creator_node>(&mut self, caller: Weak>) { + pub fn update_caller<'creator_node>(&mut self, caller: Weak) { + // pub fn update_caller<'creator_node>(&mut self, caller: Weak>) { let broken_caller = unsafe { std::mem::transmute::< - Weak>, - Weak>, + Weak, + Weak, + // Weak>, + // Weak>, >(caller) }; @@ -630,8 +479,10 @@ impl Scope { .ok_or(Error::FatalInternal("Failed to get caller"))?; // Cast the caller ptr from static to one with our own reference - let c2: &OpaqueComponent<'static> = caller.as_ref(); - let c3: &OpaqueComponent<'sel> = unsafe { std::mem::transmute(c2) }; + let c2: &OpaqueComponent = caller.as_ref(); + let c3: &OpaqueComponent = unsafe { std::mem::transmute(c2) }; + // let c2: &OpaqueComponent<'static> = caller.as_ref(); + // let c3: &OpaqueComponent<'sel> = unsafe { std::mem::transmute(c2) }; let unsafe_head = unsafe { self.own_vnodes(c3) }; @@ -641,7 +492,7 @@ impl Scope { } // this is its own function so we can preciesly control how lifetimes flow - unsafe fn own_vnodes<'a>(&'a self, f: &OpaqueComponent<'a>) -> VNode<'static> { + unsafe fn own_vnodes<'a>(&'a self, f: &OpaqueComponent) -> VNode<'static> { let new_head: VNode<'a> = f(self); let out: VNode<'static> = std::mem::transmute(new_head); out @@ -959,22 +810,25 @@ Any function prefixed with "use" should not be called conditionally. // We actually allocate the properties for components in their parent's properties // We then expose a handle to use those props for render in the form of "OpaqueComponent" -pub(crate) type OpaqueComponent<'e> = dyn for<'b> Fn(&'b Scope) -> VNode<'b> + 'e; +pub type OpaqueComponent = dyn for<'b> Fn(&'b Scope) -> VNode<'b>; #[derive(PartialEq, Debug, Clone, Default)] -pub(crate) struct EventQueue(pub Rc>>); +pub struct EventQueue(pub Rc>>); impl EventQueue { pub fn new_channel(&self, height: u32, idx: ScopeIdx) -> Rc { let inner = self.clone(); let marker = HeightMarker { height, idx }; - Rc::new(move || inner.0.as_ref().borrow_mut().push(marker)) + Rc::new(move || { + log::debug!("channel updated {:#?}", marker); + inner.0.as_ref().borrow_mut().push(marker) + }) } } /// A helper type that lets scopes be ordered by their height #[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub(crate) struct HeightMarker { +pub struct HeightMarker { pub idx: ScopeIdx, pub height: u32, } @@ -1050,20 +904,20 @@ impl ActiveFrame { ) } - fn from_frames(a: BumpFrame, b: BumpFrame) -> Self { + pub fn from_frames(a: BumpFrame, b: BumpFrame) -> Self { Self { generation: 0.into(), frames: [a, b], } } - fn cur_frame(&self) -> &BumpFrame { + pub fn cur_frame(&self) -> &BumpFrame { match *self.generation.borrow() & 1 == 0 { true => &self.frames[0], false => &self.frames[1], } } - fn cur_frame_mut(&mut self) -> &mut BumpFrame { + pub fn cur_frame_mut(&mut self) -> &mut BumpFrame { match *self.generation.borrow() & 1 == 0 { true => &mut self.frames[0], false => &mut self.frames[1], @@ -1098,7 +952,7 @@ impl ActiveFrame { } } - fn next(&mut self) -> &mut BumpFrame { + pub fn next(&mut self) -> &mut BumpFrame { *self.generation.borrow_mut() += 1; if *self.generation.borrow() % 2 == 0 { diff --git a/packages/hooks/src/lib.rs b/packages/hooks/src/lib.rs index 847cf79e4..ddbe01699 100644 --- a/packages/hooks/src/lib.rs +++ b/packages/hooks/src/lib.rs @@ -84,7 +84,7 @@ fn use_map() {} // Elements are received as Rc in case the underlying collection is shuffled around // Setters/getters can be generated fn use_collection<'a, T: Collection>( - ctx: &Context<'a>, + ctx: &impl Scoped<'a>, f: impl Fn() -> T, ) -> CollectionHandle<'a, T> { ctx.use_hook( diff --git a/packages/recoil/examples/callback.rs b/packages/recoil/examples/callback.rs index 3d1c75b68..be67683b4 100644 --- a/packages/recoil/examples/callback.rs +++ b/packages/recoil/examples/callback.rs @@ -20,7 +20,7 @@ fn update_title(api: &RecoilApi) { } static App: FC<()> = |ctx| { - let title = use_read(ctx, &TITLE); + let title = use_read(&ctx, &TITLE); let next_light = use_recoil_api(ctx, |api| move |_| update_title(&api)); rsx! { in ctx, diff --git a/packages/recoil/src/lib.rs b/packages/recoil/src/lib.rs index e73428600..634d9442e 100644 --- a/packages/recoil/src/lib.rs +++ b/packages/recoil/src/lib.rs @@ -32,7 +32,7 @@ mod traits { // Atoms, selectors, and their family variants are readable pub trait Readable: Sized + Copy { fn use_read<'a, P: 'static>(self, ctx: Context<'a, P>) -> &'a T { - hooks::use_read(ctx, self) + hooks::use_read(&ctx, self) } // This returns a future of the value @@ -95,9 +95,9 @@ mod atoms { const EXAMPLE_ATOM: Atom = |_| 10; // ensure that atoms are both read and write - let _ = use_read(ctx, &EXAMPLE_ATOM); - let _ = use_read_write(ctx, &EXAMPLE_ATOM); - let _ = use_write(ctx, &EXAMPLE_ATOM); + let _ = use_read(&ctx, &EXAMPLE_ATOM); + let _ = use_read_write(&ctx, &EXAMPLE_ATOM); + let _ = use_write(&ctx, &EXAMPLE_ATOM); } } } @@ -161,7 +161,7 @@ mod atomfamily { fn test(ctx: Context<()>) { let title = Titles.select(&10).use_read(ctx); - let t2 = use_read(ctx, &Titles.select(&10)); + let t2 = use_read(&ctx, &Titles.select(&10)); } } } @@ -368,7 +368,7 @@ mod root { mod hooks { use super::*; - use dioxus_core::{hooks::use_ref, prelude::Context}; + use dioxus_core::{hooks::use_ref, prelude::Context, virtual_dom::Scoped}; pub fn use_init_recoil_root

(ctx: Context

, cfg: impl Fn(())) { ctx.use_create_context(move || RefCell::new(RecoilRoot::new())) @@ -381,12 +381,12 @@ mod hooks { /// /// You can use this method to create controllers that perform much more complex actions than set/get /// However, be aware that "getting" values through this hook will not subscribe the component to any updates. - pub fn use_recoil_api<'a, P>(ctx: Context<'a, P>) -> &Rc { + pub fn use_recoil_api<'a>(ctx: &impl Scoped<'a>) -> &'a Rc { ctx.use_context::() } - pub fn use_write<'a, T: AtomValue, P>( - ctx: Context<'a, P>, + pub fn use_write<'a, T: AtomValue>( + ctx: &impl Scoped<'a>, // todo: this shouldn't need to be static writable: impl Writable, ) -> &'a Rc { @@ -412,10 +412,10 @@ mod hooks { /// Read the atom and get the Rc directly to the Atom's slot /// This is useful if you need the memoized Atom value. However, Rc is not as easy to /// work with as - pub fn use_read_raw<'a, T: AtomValue, P: 'static>( - ctx: Context<'a, P>, + pub fn use_read_raw<'a, T: AtomValue>( + ctx: &impl Scoped<'a>, readable: impl Readable, - ) -> &Rc { + ) -> &'a Rc { struct ReadHook { value: Rc, consumer_id: u32, @@ -449,10 +449,7 @@ mod hooks { } /// - pub fn use_read<'a, T: AtomValue, P: 'static>( - ctx: Context<'a, P>, - readable: impl Readable, - ) -> &'a T { + pub fn use_read<'a, T: AtomValue>(ctx: &impl Scoped<'a>, readable: impl Readable) -> &'a T { use_read_raw(ctx, readable).as_ref() } @@ -471,8 +468,8 @@ mod hooks { /// // equivalent to: /// let (title, set_title) = (use_read(ctx, &Title), use_write(ctx, &Title)); /// ``` - pub fn use_read_write<'a, T: AtomValue + 'static, P: 'static>( - ctx: Context<'a, P>, + pub fn use_read_write<'a, T: AtomValue + 'static>( + ctx: &impl Scoped<'a>, writable: impl Writable, ) -> (&'a T, &'a Rc) { (use_read(ctx, writable), use_write(ctx, writable)) diff --git a/packages/web/examples/children.rs b/packages/web/examples/children.rs new file mode 100644 index 000000000..0c7bebb7f --- /dev/null +++ b/packages/web/examples/children.rs @@ -0,0 +1,36 @@ +//! Basic example that renders a simple VNode to the browser. + +use dioxus_core::prelude::*; +use dioxus_web::*; + +fn main() { + // Setup logging + wasm_logger::init(wasm_logger::Config::new(log::Level::Debug)); + console_error_panic_hook::set_once(); + + // Run the app + wasm_bindgen_futures::spawn_local(WebsysRenderer::start(App)); +} + +static App: FC<()> = |ctx| { + ctx.render(rsx! { + Calcier { + h2 {"abc 1"} + h2 {"abc 2"} + h2 {"abc 3"} + h2 {"abc 4"} + h2 {"abc 5"} + } + }) +}; + +static Calcier: FC<()> = |ctx| { + ctx.render(rsx! { + div { + h1 { + "abc 0" + } + {ctx.children()} + } + }) +}; diff --git a/packages/web/examples/context.rs b/packages/web/examples/context.rs index 6653d61ca..17f2dea0e 100644 --- a/packages/web/examples/context.rs +++ b/packages/web/examples/context.rs @@ -52,7 +52,7 @@ struct ButtonProps { id: u8, } -fn CustomButton(ctx: Context, props: &ButtonProps) -> VNode { +fn CustomButton(ctx: Context) -> VNode { let names = ctx.use_context::(); let name = names.0[ctx.id as usize]; diff --git a/packages/web/examples/fragment.rs b/packages/web/examples/fragment.rs new file mode 100644 index 000000000..1f3e63049 --- /dev/null +++ b/packages/web/examples/fragment.rs @@ -0,0 +1,25 @@ +//! Basic example that renders a simple VNode to the browser. + +use dioxus_core::prelude::*; +use dioxus_web::*; + +fn main() { + // Setup logging + wasm_logger::init(wasm_logger::Config::new(log::Level::Debug)); + console_error_panic_hook::set_once(); + + // Run the app + wasm_bindgen_futures::spawn_local(WebsysRenderer::start(App)); +} + +static App: FC<()> = |ctx| { + ctx.render(rsx! { + h2 { "abc 1" } + div { + "hello world!" + } + Fragment { + h2 { "abc 2"} + } + }) +}; diff --git a/packages/web/examples/helloworld.rs b/packages/web/examples/helloworld.rs new file mode 100644 index 000000000..480adace4 --- /dev/null +++ b/packages/web/examples/helloworld.rs @@ -0,0 +1,11 @@ +use dioxus_web::prelude::*; + +fn main() { + wasm_bindgen_futures::spawn_local(dioxus_web::WebsysRenderer::start(App)) +} + +fn App(ctx: Context<()>) -> VNode { + ctx.render(rsx! { + div { "Hello, world!" } + }) +} diff --git a/packages/web/examples/many.rs b/packages/web/examples/many.rs new file mode 100644 index 000000000..d595468f1 --- /dev/null +++ b/packages/web/examples/many.rs @@ -0,0 +1,21 @@ +use dioxus_core::prelude::*; +use dioxus_web::WebsysRenderer; + +fn main() { + wasm_logger::init(wasm_logger::Config::new(log::Level::Debug)); + console_error_panic_hook::set_once(); + + log::info!("hello world"); + wasm_bindgen_futures::spawn_local(WebsysRenderer::start(Example)); +} + +static Example: FC<()> = |ctx| { + ctx.render(rsx! { + div { + span { + class: "px-2 py-1 flex w-36 mt-4 items-center text-xs rounded-md font-semibold text-yellow-500 bg-yellow-100" + "DUE DATE : 18 JUN" + } + } + }) +}; diff --git a/packages/web/examples/rsxt.rs b/packages/web/examples/rsxt.rs index dbe85d207..94fb9407d 100644 --- a/packages/web/examples/rsxt.rs +++ b/packages/web/examples/rsxt.rs @@ -53,7 +53,7 @@ struct ButtonProps<'src, F: Fn(MouseEvent)> { handler: F } -fn CustomButton<'b, 'a, F: Fn(MouseEvent)>(ctx: Context<'a>, props: &'b ButtonProps<'b, F>) -> VNode { +fn CustomButton<'a, F: Fn(MouseEvent)>(ctx: Context<'a, ButtonProps<'a, F>>) -> VNode { ctx.render(rsx!{ button { class: "inline-block py-4 px-8 mr-6 leading-none text-white bg-indigo-600 hover:bg-indigo-900 font-semibold rounded shadow" @@ -74,7 +74,7 @@ impl PartialEq for ButtonProps<'_, F> { struct PlaceholderProps { val: &'static str } -fn Placeholder(ctx: Context, props: &PlaceholderProps) -> VNode { +fn Placeholder(ctx: Context) -> VNode { ctx.render(rsx!{ div { "child: {ctx.val}" diff --git a/packages/web/examples/todomvcsingle.rs b/packages/web/examples/todomvcsingle.rs index a2f6eacbe..8320cd198 100644 --- a/packages/web/examples/todomvcsingle.rs +++ b/packages/web/examples/todomvcsingle.rs @@ -149,11 +149,11 @@ pub fn FilterToggles(ctx: Context<()>) -> VNode { .iter() .map(|(name, path, filter)| { rsx!( - li { - class: "{name}" - a { "{name}", + li { class: "{name}" + a { href: "{path}", onclick: move |_| reducer.set_filter(&filter), + "{name}", } } ) @@ -168,7 +168,7 @@ pub fn FilterToggles(ctx: Context<()>) -> VNode { footer { span { strong {"{items_left}"} - span {"{item_text} left"} + span { "{item_text} left" } } ul { class: "filters" @@ -180,8 +180,7 @@ pub fn FilterToggles(ctx: Context<()>) -> VNode { pub fn Footer(ctx: Context<()>) -> VNode { rsx! { in ctx, - footer { - class: "info" + footer { class: "info" p {"Double-click to edit a todo"} p { "Created by "