فهرست منبع

Merge remote-tracking branch 'origin/master' into jk/rsx-refactor

Evan Almloff 3 سال پیش
والد
کامیت
f9713b2fe9
83فایلهای تغییر یافته به همراه6892 افزوده شده و 2313 حذف شده
  1. 1 1
      .github/FUNDING.yml
  2. 1 2
      .github/workflows/docs.yml
  3. 4 4
      .github/workflows/main.yml
  4. 3 1
      .gitignore
  5. 24 14
      Cargo.toml
  6. 0 1
      docs/cli/.gitignore
  7. 0 6
      docs/cli/book.toml
  8. 0 11
      docs/cli/src/SUMMARY.md
  9. 0 26
      docs/cli/src/cmd/README.md
  10. 0 47
      docs/cli/src/cmd/build.md
  11. 0 18
      docs/cli/src/cmd/clean.md
  12. 0 49
      docs/cli/src/cmd/serve.md
  13. 0 57
      docs/cli/src/cmd/translate.md
  14. 0 147
      docs/cli/src/configure.md
  15. 0 38
      docs/cli/src/creating.md
  16. 0 24
      docs/cli/src/installation.md
  17. 0 21
      docs/cli/src/introduction.md
  18. 1 1
      docs/guide/src/elements/lists.md
  19. 484 0
      docs/reference/src/guide/custom_renderer.md
  20. 3 5
      docs/reference/src/platforms/tui.md
  21. 30 0
      examples/custom_element.rs
  22. 26 0
      examples/fermi.rs
  23. 56 0
      examples/mouse_event.rs
  24. 29 2
      examples/tui_hover.rs
  25. 9 7
      examples/tui_keys.rs
  26. 23 0
      examples/window_zoom.rs
  27. 1 1
      packages/core-macro/Cargo.toml
  28. 1 1
      packages/core/Cargo.toml
  29. 239 0
      packages/core/src/arbitrary_value.rs
  30. 1 1
      packages/core/src/events.rs
  31. 6 4
      packages/core/src/lib.rs
  32. 4 3
      packages/core/src/mutations.rs
  33. 20 2
      packages/core/src/nodes.rs
  34. 6 10
      packages/desktop/Cargo.toml
  35. 28 4
      packages/desktop/src/desktop_context.rs
  36. 1 1
      packages/desktop/src/lib.rs
  37. 2 2
      packages/fermi/Cargo.toml
  38. 38 14
      packages/fermi/src/hooks/atom_ref.rs
  39. 3 3
      packages/hooks/Cargo.toml
  40. 3 0
      packages/hooks/src/lib.rs
  41. 1 1
      packages/hooks/src/useref.rs
  42. 26 2
      packages/html/Cargo.toml
  43. 190 15
      packages/html/src/events.rs
  44. 73 0
      packages/html/src/geometry.rs
  45. 120 0
      packages/html/src/input_data.rs
  46. 4 0
      packages/html/src/lib.rs
  47. 176 0
      packages/html/src/web_sys_bind/events.rs
  48. 1 0
      packages/html/src/web_sys_bind/mod.rs
  49. 1 1
      packages/interpreter/Cargo.toml
  50. 4 0
      packages/interpreter/src/interpreter.js
  51. 6 3
      packages/liveview/Cargo.toml
  52. 16 4
      packages/liveview/src/adapters/axum_adapter.rs
  53. 20 4
      packages/liveview/src/adapters/warp_adapter.rs
  54. 4 0
      packages/liveview/src/interpreter.js
  55. 1 1
      packages/mobile/Cargo.toml
  56. 29 0
      packages/native-core-macro/Cargo.toml
  57. 703 0
      packages/native-core-macro/src/lib.rs
  58. 28 0
      packages/native-core-macro/src/sorted_slice.rs
  59. 158 0
      packages/native-core-macro/tests/change_nodes.rs
  60. 129 0
      packages/native-core-macro/tests/initial_build.rs
  61. 534 0
      packages/native-core-macro/tests/update_state.rs
  62. 24 0
      packages/native-core/Cargo.toml
  63. 636 0
      packages/native-core/src/layout_attributes.rs
  64. 4 0
      packages/native-core/src/lib.rs
  65. 249 0
      packages/native-core/src/node_ref.rs
  66. 774 0
      packages/native-core/src/real_dom.rs
  67. 202 0
      packages/native-core/src/state.rs
  68. 4 4
      packages/router/Cargo.toml
  69. 2 2
      packages/ssr/Cargo.toml
  70. 4 2
      packages/ssr/src/lib.rs
  71. 9 5
      packages/tui/Cargo.toml
  72. 0 968
      packages/tui/src/attributes.rs
  73. 2 2
      packages/tui/src/config.rs
  74. 446 344
      packages/tui/src/hooks.rs
  75. 248 102
      packages/tui/src/layout.rs
  76. 111 120
      packages/tui/src/lib.rs
  77. 35 61
      packages/tui/src/render.rs
  78. 46 42
      packages/tui/src/style.rs
  79. 797 0
      packages/tui/src/style_attributes.rs
  80. 8 4
      packages/tui/src/widget.rs
  81. 5 5
      packages/web/Cargo.toml
  82. 8 91
      packages/web/src/dom.rs
  83. 7 2
      src/lib.rs

+ 1 - 1
.github/FUNDING.yml

@@ -1,4 +1,4 @@
 # These are supported funding model platforms
 
-github: jkelleyrtp # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
+github: DioxusLabs # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
 open_collective: dioxus-labs # Replace with a single Open Collective username

+ 1 - 2
.github/workflows/docs.yml

@@ -24,8 +24,7 @@ jobs:
         run: cd docs &&
           cd guide && mdbook build -d ../nightly/guide && cd .. &&
           cd reference && mdbook build -d ../nightly/reference && cd .. &&
-          cd router && mdbook build -d ../nightly/router  && cd .. &&
-          cd cli && mdbook build -d ../nightly/cli  && cd ..
+          cd router && mdbook build -d ../nightly/router  && cd ..
 
       - name: Deploy 🚀
         uses: JamesIves/github-pages-deploy-action@v4.2.3

+ 4 - 4
.github/workflows/main.yml

@@ -38,7 +38,7 @@ jobs:
           override: true
       - uses: Swatinem/rust-cache@v1
       - run: sudo apt-get update
-      - run: sudo apt install libwebkit2gtk-4.0-dev libappindicator3-dev libgtk-3-dev
+      - run: sudo apt install libwebkit2gtk-4.0-dev libgtk-3-dev libayatana-appindicator3-dev
       - uses: actions-rs/cargo@v1
         with:
           command: check
@@ -56,7 +56,7 @@ jobs:
           override: true
       - uses: Swatinem/rust-cache@v1
       - run: sudo apt-get update
-      - run: sudo apt install libwebkit2gtk-4.0-dev libappindicator3-dev libgtk-3-dev
+      - run: sudo apt install libwebkit2gtk-4.0-dev libgtk-3-dev libayatana-appindicator3-dev
       - uses: davidB/rust-cargo-make@v1
       - uses: browser-actions/setup-firefox@latest
       - uses: jetli/wasm-pack-action@v0.3.0
@@ -96,7 +96,7 @@ jobs:
           override: true
       - uses: Swatinem/rust-cache@v1
       - run: sudo apt-get update
-      - run: sudo apt install libwebkit2gtk-4.0-dev libappindicator3-dev libgtk-3-dev
+      - run: sudo apt install libwebkit2gtk-4.0-dev libgtk-3-dev libayatana-appindicator3-dev
       - run: rustup component add clippy
       - uses: actions-rs/cargo@v1
         with:
@@ -117,7 +117,7 @@ jobs:
   #       run: |
   #         apt-get update &&\
   #         apt-get install build-essential &&\
-  #         apt install libwebkit2gtk-4.0-dev libappindicator3-dev libgtk-3-dev -y &&\
+  #         apt install libwebkit2gtk-4.0-dev libgtk-3-dev libayatana-appindicator3-dev -y &&\
   #         cargo +nightly tarpaulin --verbose --all-features --workspace --timeout 120 --out Xml
   #     - name: Upload to codecov.io
   #       uses: codecov/codecov-action@v2

+ 3 - 1
.gitignore

@@ -9,4 +9,6 @@ Cargo.lock
 !.vscode/launch.json
 !.vscode/extensions.json
 tarpaulin-report.html
-.idea
+
+# Jetbrain
+.idea/

+ 24 - 14
Cargo.toml

@@ -1,6 +1,6 @@
 [package]
 name = "dioxus"
-version = "0.2.3"
+version = "0.2.4"
 authors = ["Jonathan Kelley"]
 edition = "2021"
 description = "Core functionality for Dioxus - a concurrent renderer-agnostic Virtual DOM for interactive user experiences"
@@ -12,23 +12,30 @@ keywords = ["dom", "ui", "gui", "react", "wasm"]
 rust-version = "1.56.0"
 
 [dependencies]
-dioxus-core = { path = "./packages/core", version = "^0.2.0" }
-dioxus-html = { path = "./packages/html", version = "^0.2.0", optional = true }
-dioxus-core-macro = { path = "./packages/core-macro", version = "^0.2.0", optional = true }
-dioxus-hooks = { path = "./packages/hooks", version = "^0.2.0", optional = true }
-fermi = { path = "./packages/fermi", version = "^0.2.0", optional = true }
+dioxus-core = { path = "./packages/core", version = "^0.2.1" }
+dioxus-html = { path = "./packages/html", version = "^0.2.1", optional = true }
+dioxus-core-macro = { path = "./packages/core-macro", version = "^0.2.1", optional = true }
+dioxus-hooks = { path = "./packages/hooks", version = "^0.2.1", optional = true }
+fermi = { path = "./packages/fermi", version = "^0.2.1", optional = true }
 
-dioxus-web = { path = "./packages/web", version = "^0.2.0", optional = true }
-dioxus-desktop = { path = "./packages/desktop", version = "^0.2.2", optional = true }
-dioxus-ssr = { path = "./packages/ssr", version = "^0.2.0", optional = true }
+dioxus-web = { path = "./packages/web", version = "^0.2.1", optional = true }
+dioxus-desktop = { path = "./packages/desktop", version = "^0.2.3", optional = true }
+dioxus-ssr = { path = "./packages/ssr", version = "^0.2.1", optional = true }
 
-dioxus-router = { path = "./packages/router", version = "^0.2.1", optional = true }
-dioxus-interpreter-js = { path = "./packages/interpreter", version = "^0.2.0", optional = true }
-dioxus-tui = { path = "./packages/tui", version = "^0.2.0", optional = true }
+dioxus-router = { path = "./packages/router", version = "^0.2.3", optional = true }
+dioxus-interpreter-js = { path = "./packages/interpreter", version = "^0.2.1", optional = true }
+dioxus-tui = { path = "./packages/tui", version = "^0.2.2", optional = true }
 
-dioxus-liveview = { path = "./packages/liveview", optional = true }
 dioxus-rsx = { path = "./packages/rsx", optional = true }
 dioxus-rsx-interperter = { path = "./packages/rsx_interperter", optional = true }
+dioxus-liveview = { path = "./packages/liveview", version = "^0.1.0", optional = true }
+
+dioxus-native-core = { path = "./packages/native-core", version = "^0.2.0", optional = true }
+dioxus-native-core-macro = { path = "./packages/native-core-macro", version = "^0.2.0", optional = true }
+
+# dioxus-mobile = { path = "./packages/mobile", version = "^0.2.0", optional = true }
+# dioxus-rsx = { path = "./packages/rsx", optional = true }
+# macro = ["dioxus-core-macro", "dioxus-rsx"]
 
 [features]
 default = ["macro", "hooks", "html"]
@@ -39,11 +46,12 @@ html = ["dioxus-html"]
 ssr = ["dioxus-ssr"]
 web = ["dioxus-web", "dioxus-router/web"]
 desktop = ["dioxus-desktop"]
-ayatana = ["dioxus-desktop/ayatana"]
 router = ["dioxus-router"]
 tui = ["dioxus-tui"]
 liveview = ["dioxus-liveview"]
 hot_reload = ["dioxus-core-macro/hot_reload", "dioxus-rsx-interperter"]
+native-core = ["dioxus-native-core", "dioxus-native-core-macro"]
+
 
 [workspace]
 members = [
@@ -61,6 +69,8 @@ members = [
     "packages/liveview",
     "packages/rsx",
     "packages/rsx_interperter",
+    "packages/native-core",
+    "packages/native-core-macro",
 ]
 
 [dev-dependencies]

+ 0 - 1
docs/cli/.gitignore

@@ -1 +0,0 @@
-book

+ 0 - 6
docs/cli/book.toml

@@ -1,6 +0,0 @@
-[book]
-authors = ["YuKun Liu"]
-language = "en"
-multilingual = false
-src = "src"
-title = "Dioxus Cli"

+ 0 - 11
docs/cli/src/SUMMARY.md

@@ -1,11 +0,0 @@
-# Summary
-
-- [Introduction](./introduction.md)
-- [Installation](./installation.md)
-- [Create a Project](./creating.md)
-- [Configure Project](./configure.md)
-- [Commands](./cmd/README.md)
-  - [Build](./cmd/build.md)
-  - [Serve](./cmd/serve.md)
-  - [Clean](./cmd/clean.md)
-  - [Translate](./cmd/translate.md)

+ 0 - 26
docs/cli/src/cmd/README.md

@@ -1,26 +0,0 @@
-# Commands
-
-In this chapter we will introduce all `dioxus-cli` commands.
-
-> you can also use `dioxus --help` to get cli help info.
-
-```
-dioxus 
-Build, bundle, & ship your Dioxus app
-
-USAGE:
-    dioxus [OPTIONS] <SUBCOMMAND>
-
-OPTIONS:
-    -h, --help    Print help information
-    -v            Enable verbose logging
-
-SUBCOMMANDS:
-    build        Build the Rust WASM app and all of its assets
-    clean        Clean output artifacts
-    config       Dioxus config file controls
-    create       Init a new project for Dioxus
-    help         Print this message or the help of the given subcommand(s)
-    serve        Build, watch & serve the Rust WASM app and all of its assets
-    translate    Translate some source file into Dioxus code
-```

+ 0 - 47
docs/cli/src/cmd/build.md

@@ -1,47 +0,0 @@
-# Build
-
-The `dioxus build` command can help you `pack & build` a dioxus project.
-
-```
-dioxus-build 
-Build the Rust WASM app and all of its assets
-
-USAGE:
-    dioxus build [OPTIONS]
-
-OPTIONS:
-        --example <EXAMPLE>      [default: ""]
-        --platform <PLATFORM>    [default: "default_platform"]
-        --release                [default: false]
-```
-
-You can use this command to build project to `out_dir` :
-
-```
-dioxus build --release
-```
-
-## Target platform
-
-Use option `platform` choose build target platform:
-
-```
-# for desktop project
-dioxus build --platform desktop
-```
-
-`platform` only supports `desktop` & `web`.
-
-```
-# for web project
-dioxus build --platform web
-```
-
-## Build Example
-
-You can use `--example {name}` to build a example code.
-
-```
-# build `example/test` code
-dioxus build --exmaple test
-```

+ 0 - 18
docs/cli/src/cmd/clean.md

@@ -1,18 +0,0 @@
-# Clean
-
-`dioxus clean` will call `target clean` and remove `out_dir` directory.
-
-```
-dioxus-clean 
-Clean output artifacts
-
-USAGE:
-    dioxus clean
-```
-
-you can use this command to clean all build cache and the `out_dir` content.
-
-```
-dioxus clean
-```
-

+ 0 - 49
docs/cli/src/cmd/serve.md

@@ -1,49 +0,0 @@
-# Serve
-
-The `dioxus serve` can start a dev server (include hot-reload tool) to run the project.
-
-```
-dioxus-serve 
-Build, watch & serve the Rust WASM app and all of its assets
-
-USAGE:
-    dioxus serve [OPTIONS]
-
-OPTIONS:
-        --example <EXAMPLE>      [default: ""]
-        --platform <PLATFORM>    [default: "default_platform"]
-        --release                [default: false]
-```
-
-You can use this command to build project and start a `dev server` :
-
-```
-dioxus serve
-```
-
-## Target platform
-
-Use option `platform` choose build target platform:
-
-```
-# for desktop project
-dioxus serve --platform desktop
-```
-
-`platform` only supports `desktop` & `web`.
-
-`dev-server` only for `web` project.
-
-```
-# for web project
-dioxus serve --platform web
-```
-
-## Serve Example
-
-You can use `--example {name}` to start a example code.
-
-```
-# build `example/test` code
-dioxus serve --exmaple test
-```

+ 0 - 57
docs/cli/src/cmd/translate.md

@@ -1,57 +0,0 @@
-# Translate
-
-`dioxus translate` can translate some source file into Dioxus code.
-
-```
-dioxus-translate 
-Translate some source file into Dioxus code
-
-USAGE:
-    dioxus translate [OPTIONS] [OUTPUT]
-
-ARGS:
-    <OUTPUT>    Output file, stdout if not present
-
-OPTIONS:
-    -c, --component      Activate debug mode
-    -f, --file <FILE>    Input file
-```
-
-## Translate HTML to stdout
-
-```
-dioxus transtale --file ./index.html
-```
-
-## Output in a file
-
-```
-dioxus translate --component --file ./index.html component.rsx
-```
-
-set `component` flag will wrap `dioxus rsx` code in a component function.
-
-## Example
-
-```html
-<div>
-    <h1> Hello World </h1>
-    <a href="https://dioxuslabs.com/">Link</a>
-</div>
-```
-
-Translate HTML to Dioxus component code.
-
-```rust
-fn component(cx: Scope) -> Element {
-    cx.render(rsx! {
-        div {
-            h1 { "Hello World" },
-            a {
-                href: "https://dioxuslabs.com/",
-                "Link"
-            }
-        }
-    })
-}
-```

+ 0 - 147
docs/cli/src/configure.md

@@ -1,147 +0,0 @@
-# Configure Project
-
-This chapter will introduce `Dioxus.toml` and anatomy the config file.
-
-## Structure
-
-We use `toml` to define some info for `dioxus` project.
-
-### Application
-
-1. ***name*** - project name & title
-2. ***default_platform*** - which platform target for this project.
-   ```
-   # current support: web, desktop
-   # default: web
-   default_platform = "web"
-   ```
-   change this to `desktop`, the `dioxus build & serve` will default build desktop app.
-3. ***out_dir*** - which directory to put the output file; use `dioxus build & service`, the output will put at this directory, and the `assets` will be also copy to here.
-    ```
-    out_dir = "dist"
-    ```
-4. ***asset_dir*** - which direcotry to put your `static, assets` file, cli will automatic copy all file to `out_dir`, so you can put some resource file in there, like `CSS, JS, Image` file.
-   ```
-   asset_dir = "public"
-   ```
-
-### Web.App
-
-Web platform application config:
-
-1. ***title*** - this value will display on the web page title. like `<title></title>` tag.
-   ```
-   # HTML title tag content
-   title = "dioxus app | ⛺"
-   ```
-
-### Web.Watcher
-
-Web platform `dev-server` watcher config:
-
-1. ***reload_html*** - a boolean value; when watcher trigger, regenerate `index.html` file.
-   ```
-   # when watcher trigger, regenerate the `index.html`
-   reload_html = true
-   ```
-2. ***watch_path*** - which files & directories will be watcher monitoring.
-   ```
-   watch_path = ["src", "public"]
-   ```
-
-### Web.Resource
-
-Include some `CSS Javascript` resources into html file.
-
-1. ***style*** - include some style(CSS) file into html.
-   ```
-   style = [
-      # include from public_dir.
-      "./assets/style.css",
-      # or some asset from online cdn.
-      "https://cdn.jsdelivr.net/npm/bootstrap/dist/css/bootstrap.css"
-   ]
-   ```
-2. ***script*** - include some script(JS) file into html.
-    ```
-    style = [
-        # include from public_dir.
-        "./assets/index.js",
-        # or some asset from online cdn.
-        "https://cdn.jsdelivr.net/npm/bootstrap/dist/js/bootstrap.js"
-    ]
-   ```
-
-### Web.Resource.Dev
-
-Only include resources at `Dev` mode.
-
-1. ***style*** - include some style(CSS) file into html.
-   ```
-   style = [
-      # include from public_dir.
-      "./assets/style.css",
-      # or some asset from online cdn.
-      "https://cdn.jsdelivr.net/npm/bootstrap/dist/css/bootstrap.css"
-   ]
-   ```
-2. ***script*** - include some script(JS) file into html.
-    ```
-    style = [
-        # include from public_dir.
-        "./assets/index.js",
-        # or some asset from online cdn.
-        "https://cdn.jsdelivr.net/npm/bootstrap/dist/js/bootstrap.js"
-    ]
-   ```
-
-## Config example
-
-```toml
-[application]
-
-# App (Project) Name
-name = "{{project-name}}"
-
-# Dioxus App Default Platform
-# desktop, web, mobile, ssr
-default_platform = "web"
-
-# `build` & `serve` dist path
-out_dir = "dist"
-
-# resource (public) file folder
-asset_dir = "public"
-
-[web.app]
-
-# HTML title tag content
-title = "dioxus | ⛺"
-
-[web.watcher]
-
-# when watcher trigger, regenerate the `index.html`
-reload_html = true
-
-# which files or dirs will be watcher monitoring
-watch_path = ["src", "public"]
-
-# include `assets` in web platform
-[web.resource]
-
-# CSS style file
-style = []
-
-# Javascript code file
-script = []
-
-[web.resource.dev]
-
-# serve: [dev-server] only
-
-# CSS style file
-style = []
-
-# Javascript code file
-script = []
-```

+ 0 - 38
docs/cli/src/creating.md

@@ -1,38 +0,0 @@
-# Create a Project
-
-Once you have the Dioxus CLI tool installed, you can use it to create dioxus project.
-
-## Initializing a default project
-
-The `dioxus create` command will create a new directory containing a project template.
-```
-dioxus create hello-dioxus
-```
-
-It will clone a default template from github template: [DioxusLabs/dioxus-template](https://github.com/DioxusLabs/dioxus-template)
-
-> This default template is use for `web` platform application.
-
-then you can change the current directory in to the project:
-
-```
-cd hello-dioxus
-```
-
-> Make sure `wasm32 target` is installed before running the Web project.
-
-now we can create a `dev server` to display the project:
-
-```
-dioxus serve
-```
-
-by default, the dioxus dev server will running at: [`http://127.0.0.1:8080/`](http://127.0.0.1:8080/)
-
-## Initalizing from other repository
-
-you can assign which repository you want to create:
-
-```
-dioxus init hello-dioxus --template=gh:dioxuslabs/dioxus-template
-```

+ 0 - 24
docs/cli/src/installation.md

@@ -1,24 +0,0 @@
-# Installation
-
-There are multiple ways to install the Dioxus CLI tool. Choose any one of the methods below that best suit your needs.
-
-## Install from latest master version
-
-We suggest you use `github master` version to install it now.
-
-We have not yet released the latest version to `crates.io`
-
-```
-cargo install --git https://github.com/Dioxuslabs/cli
-```
-
-This will automatically download `Dioxus-CLI` source from github master branch,
-and install it in Cargo's global binary directory (`~/.cargo/bin/` by default).
-
-## Install from `crates.io` version
-
-```
-cargo install dioxus-cli
-```
-
-Make sure to add the Cargo bin directory to your `PATH`.

+ 0 - 21
docs/cli/src/introduction.md

@@ -1,21 +0,0 @@
-# Introduction
-
-📦✨ **Dioxus-Cli** is a tool to help get dioxus projects off the ground.
-
-![dioxus-logo](https://dioxuslabs.com/guide/images/dioxuslogo_full.png)
-
-It includes `dev server`, `hot reload` and some `quick command` to help you use dioxus.
-
-## Features
-
-- [x] `html` to `rsx` conversion tool
-- [x] hot reload for `web` platform
-- [x] create dioxus project from `git` repo
-- [x] build & pack dioxus project
-- [ ] autoformat dioxus `rsx` code
-
-## Contributors
-
-Contributors to this guide:
-
-- [mrxiaozhuox](https://github.com/mrxiaozhuox)

+ 1 - 1
docs/guide/src/elements/lists.md

@@ -102,7 +102,7 @@ For keen Rustaceans: notice how we don't actually call `collect` on the name lis
 
 ## Keeping list items in order with `key`
 
-The examples above demonstrate the power of iterators in `rsx!` but all share the same issue: if your array items move (e.g. due to sorting), get inserted, or get deleted, Dioxus has no way of knowing what happened. This can cause Elements to be unnecessarily removed, changed and rebuilt when all that was needed was to change their position – this is inneficient.
+The examples above demonstrate the power of iterators in `rsx!` but all share the same issue: if your array items move (e.g. due to sorting), get inserted, or get deleted, Dioxus has no way of knowing what happened. This can cause Elements to be unnecessarily removed, changed and rebuilt when all that was needed was to change their position – this is inefficient.
 
 To solve this problem, each item in the list must be **uniquely identifiable**. You can achieve this by giving it a unique, fixed "key". In Dioxus, a key is a string that identifies an item among others in the list.
 

+ 484 - 0
docs/reference/src/guide/custom_renderer.md

@@ -1 +1,485 @@
 # Custom Renderer
+
+Dioxus is an incredibly portable framework for UI development. The lessons, knowledge, hooks, and components you acquire over time can always be used for future projects. However, sometimes those projects cannot leverage a supported renderer or you need to implement your own better renderer.
+
+Great news: the design of the renderer is entirely up to you! We provide suggestions and inspiration with the 1st party renderers, but only really require processing `DomEdits` and sending `UserEvents`.
+
+## The specifics:
+
+Implementing the renderer is fairly straightforward. The renderer needs to:
+
+1. Handle the stream of edits generated by updates to the virtual DOM
+2. Register listeners and pass events into the virtual DOM's event system
+
+Essentially, your renderer needs to implement the `RealDom` trait and generate `EventTrigger` objects to update the VirtualDOM. From there, you'll have everything needed to render the VirtualDOM to the screen.
+
+Internally, Dioxus handles the tree relationship, diffing, memory management, and the event system, leaving as little as possible required for renderers to implement themselves.
+
+For reference, check out the javascript interperter or tui renderer as a starting point for your custom renderer.
+
+## DomEdits
+
+The "DomEdit" type is a serialized enum that represents an atomic operation occurring on the RealDom. The variants roughly follow this set:
+
+```rust
+enum DomEdit {
+    PushRoot,
+    AppendChildren,
+    ReplaceWith,
+    InsertAfter,
+    InsertBefore,
+    Remove,
+    CreateTextNode,
+    CreateElement,
+    CreateElementNs,
+    CreatePlaceholder,
+    NewEventListener,
+    RemoveEventListener,
+    SetText,
+    SetAttribute,
+    RemoveAttribute,
+    PopRoot,
+}
+```
+
+The Dioxus diffing mechanism operates as a [stack machine](https://en.wikipedia.org/wiki/Stack_machine) where the "push_root" method pushes a new "real" DOM node onto the stack and "append_child" and "replace_with" both remove nodes from the stack.
+
+
+### An example
+
+For the sake of understanding, lets consider this example - a very simple UI declaration:
+
+```rust
+rsx!( h1 {"hello world"} )
+```
+
+To get things started, Dioxus must first navigate to the container of this h1 tag. To "navigate" here, the internal diffing algorithm generates the DomEdit `PushRoot` where the ID of the root is the container.
+
+When the renderer receives this instruction, it pushes the actual Node onto its own stack. The real renderer's stack will look like this:
+
+```rust
+instructions: [
+    PushRoot(Container)
+]
+stack: [
+    ContainerNode,
+]
+```
+
+Next, Dioxus will encounter the h1 node. The diff algorithm decides that this node needs to be created, so Dioxus will generate the DomEdit `CreateElement`. When the renderer receives this instruction, it will create an unmounted node and push into its own stack:
+
+```rust
+instructions: [
+    PushRoot(Container),
+    CreateElement(h1),
+]
+stack: [
+    ContainerNode,
+    h1,
+]
+```
+Next, Dioxus sees the text node, and generates the `CreateTextNode` DomEdit:
+```rust
+instructions: [
+    PushRoot(Container),
+    CreateElement(h1),
+    CreateTextNode("hello world")
+]
+stack: [
+    ContainerNode,
+    h1,
+    "hello world"
+]
+```
+Remember, the text node is not attached to anything (it is unmounted) so Dioxus needs to generate an Edit that connects the text node to the h1 element. It depends on the situation, but in this case we use `AppendChildren`. This pops the text node off the stack, leaving the h1 element as the next element in line.
+
+```rust
+instructions: [
+    PushRoot(Container),
+    CreateElement(h1),
+    CreateTextNode("hello world"),
+    AppendChildren(1)
+]
+stack: [
+    ContainerNode,
+    h1
+]
+```
+We call `AppendChildren` again, popping off the h1 node and attaching it to the parent:
+```rust
+instructions: [
+    PushRoot(Container),
+    CreateElement(h1),
+    CreateTextNode("hello world"),
+    AppendChildren(1),
+    AppendChildren(1)
+]
+stack: [
+    ContainerNode,
+]
+```
+Finally, the container is popped since we don't need it anymore.
+```rust
+instructions: [
+    PushRoot(Container),
+    CreateElement(h1),
+    CreateTextNode("hello world"),
+    AppendChildren(1),
+    AppendChildren(1),
+    PopRoot
+]
+stack: []
+```
+Over time, our stack looked like this:
+```rust
+[]
+[Container]
+[Container, h1]
+[Container, h1, "hello world"]
+[Container, h1]
+[Container]
+[]
+```
+
+Notice how our stack is empty once UI has been mounted. Conveniently, this approach completely separates the VirtualDOM and the Real DOM. Additionally, these edits are serializable, meaning we can even manage UIs across a network connection. This little stack machine and serialized edits makes Dioxus independent of platform specifics.
+
+Dioxus is also really fast. Because Dioxus splits the diff and patch phase, it's able to make all the edits to the RealDOM in a very short amount of time (less than a single frame) making rendering very snappy. It also allows Dioxus to cancel large diffing operations if higher priority work comes in while it's diffing.
+
+It's important to note that there _is_ one layer of connectedness between Dioxus and the renderer. Dioxus saves and loads elements (the PushRoot edit) with an ID. Inside the VirtualDOM, this is just tracked as a u64.
+
+Whenever a `CreateElement` edit is generated during diffing, Dioxus increments its node counter and assigns that new element its current NodeCount. The RealDom is responsible for remembering this ID and pushing the correct node when PushRoot(ID) is generated. Dioxus reclaims IDs of elements when removed. To stay in sync with Dioxus you can use a sparce Vec (Vec<Option<T>>) with possibly unoccupied items. You can use the ids as indexes into the Vec for elements, and grow the Vec when a id does not exist.
+
+This little demo serves to show exactly how a Renderer would need to process an edit stream to build UIs. A set of serialized DomEditss for various demos is available for you to test your custom renderer against.
+
+## Event loop
+
+Like most GUIs, Dioxus relies on an event loop to progress the VirtualDOM. The VirtualDOM itself can produce events as well, so it's important that your custom renderer can handle those too.
+
+The code for the WebSys implementation is straightforward, so we'll add it here to demonstrate how simple an event loop is:
+
+```rust
+pub async fn run(&mut self) -> dioxus_core::error::Result<()> {
+    // Push the body element onto the WebsysDom's stack machine
+    let mut websys_dom = crate::new::WebsysDom::new(prepare_websys_dom());
+    websys_dom.stack.push(root_node);
+
+    // Rebuild or hydrate the virtualdom
+    let mutations = self.internal_dom.rebuild();
+    websys_dom.apply_mutations(mutations);
+
+    // Wait for updates from the real dom and progress the virtual dom
+    loop {
+        let user_input_future = websys_dom.wait_for_event();
+        let internal_event_future = self.internal_dom.wait_for_work();
+
+        match select(user_input_future, internal_event_future).await {
+            Either::Left((_, _)) => {
+                let mutations = self.internal_dom.work_with_deadline(|| false);
+                websys_dom.apply_mutations(mutations);
+            },
+            Either::Right((event, _)) => websys_dom.handle_event(event),
+        }
+
+        // render
+    }
+}
+```
+
+It's important that you decode the real events from your event system into Dioxus' synthetic event system (synthetic meaning abstracted). This simply means matching your event type and creating a Dioxus `UserEvent` type. Right now, the VirtualEvent system is modeled almost entirely around the HTML spec, but we are interested in slimming it down.
+
+```rust
+fn virtual_event_from_websys_event(event: &web_sys::Event) -> VirtualEvent {
+    match event.type_().as_str() {
+        "keydown" => {
+            let event: web_sys::KeyboardEvent = event.clone().dyn_into().unwrap();
+            UserEvent::KeyboardEvent(UserEvent {
+                scope_id: None,
+                priority: EventPriority::Medium,
+                name: "keydown",
+                // This should be whatever element is focused
+                element: Some(ElementId(0)),
+                data: Arc::new(KeyboardData{
+                    char_code: event.char_code(),
+                    key: event.key(),
+                    key_code: event.key_code(),
+                    alt_key: event.alt_key(),
+                    ctrl_key: event.ctrl_key(),
+                    meta_key: event.meta_key(),
+                    shift_key: event.shift_key(),
+                    locale: "".to_string(),
+                    location: event.location(),
+                    repeat: event.repeat(),
+                    which: event.which(),
+                })
+            })
+        }
+        _ => todo!()
+    }
+}
+```
+
+## Custom raw elements
+
+If you need to go as far as relying on custom elements for your renderer - you totally can. This still enables you to use Dioxus' reactive nature, component system, shared state, and other features, but will ultimately generate different nodes. All attributes and listeners for the HTML and SVG namespace are shuttled through helper structs that essentially compile away (pose no runtime overhead). You can drop in your own elements any time you want, with little hassle. However, you must be absolutely sure your renderer can handle the new type, or it will crash and burn.
+
+These custom elements are defined as unit structs with trait implementations.
+
+For example, the `div` element is (approximately!) defined as such:
+
+```rust
+struct div;
+impl div {
+    /// Some glorious documentation about the class property.
+    const TAG_NAME: &'static str = "div";
+    const NAME_SPACE: Option<&'static str> = None;
+    // define the class attribute
+    pub fn class<'a>(&self, cx: NodeFactory<'a>, val: Arguments) -> Attribute<'a> {
+        cx.attr("class", val, None, false)
+    }
+    // more attributes
+}
+```
+
+You've probably noticed that many elements in the `rsx!` macros support on-hover documentation. The approach we take to custom elements means that the unit struct is created immediately where the element is used in the macro. When the macro is expanded, the doc comments still apply to the unit struct, giving tons of in-editor feedback, even inside a proc macro.
+
+# Native Core
+
+Renderers take a lot of work. If you are creating a renderer in rust, native core provides some utilites to implement a renderer. It provides an abstraction over DomEdits and handles layout for you.
+
+## RealDom
+
+The `RealDom` is a higher level abstraction over updating the Dom. It updates with `DomEdits` and provides a way to lazily update the state of nodes based on what attributes change.
+
+### Example
+
+Let's build a toy renderer with borders, size, and text color.
+Before we start lets take a look at an exaple element we can render:
+```rust
+cx.render(rsx!{
+    div{
+        color: "red",
+        p{
+            border: "1px solid black",
+            "hello world"
+        }
+    }
+})
+```
+
+In this tree the color depends on the parent's color. The size depends on the childrens size, the current text, and a text size. The border depends on only the current node.
+
+```mermaid
+flowchart TB
+    subgraph context
+        text_width(text width)
+    end
+    subgraph div
+        state1(state)-->color1(color)
+        state1(state)-->border1(border)
+        border1-.->text_width
+        linkStyle 2 stroke:#5555ff,stroke-width:4px;
+        state1(state)-->layout_width1(layout width)
+    end
+    subgraph p
+        state2(state)-->color2(color)
+        color2-.->color1(color)
+        linkStyle 5 stroke:#0000ff,stroke-width:4px;
+        state2(state)-->border2(border)
+        border2-.->text_width
+        linkStyle 7 stroke:#5555ff,stroke-width:4px;
+        state2(state)-->layout_width2(layout width)
+        layout_width1-.->layout_width2
+        linkStyle 9 stroke:#aaaaff,stroke-width:4px;
+    end
+    subgraph hello world
+        state3(state)-->color3(color)
+        color3-.->color2(color)
+        linkStyle 11 stroke:#0000ff,stroke-width:4px;
+        state3(state)-->border3(border)
+        border3-.->text_width
+        linkStyle 13 stroke:#5555ff,stroke-width:4px;
+        state3(state)-->layout_width3(layout width)
+        layout_width2-.->layout_width3
+        linkStyle 15 stroke:#aaaaff,stroke-width:4px;
+    end
+```
+
+To help in building a Dom, native core provides four traits: State, ChildDepState, ParentDepState, and NodeDepState and a RealDom struct.
+
+```rust
+use dioxus_native_core::node_ref::*;
+use dioxus_native_core::state::{ChildDepState, NodeDepState, ParentDepState, State};
+use dioxus_native_core_macro::{sorted_str_slice, State};
+
+#[derive(Default, Copy, Clone)]
+struct Size(f32, f32);
+// Size only depends on the current node and its children, so it implements ChildDepState
+impl ChildDepState for Size {
+    // Size accepts a font size context
+    type Ctx = f32;
+    // Size depends on the Size part of each child
+    type DepState = Self;
+    // Size only cares about the width, height, and text parts of the current node
+    const NODE_MASK: NodeMask =
+        NodeMask::new_with_attrs(AttributeMask::Static(&sorted_str_slice!(["width", "height"]))).with_text();
+    fn reduce<'a>(
+        &mut self,
+        node: NodeView,
+        children: impl Iterator<Item = &'a Self::DepState>,
+        ctx: &Self::Ctx,
+    ) -> bool
+    where
+        Self::DepState: 'a,
+    {
+        let mut width;
+        let mut height;
+        if let Some(text) = node.text() {
+            // if the node has text, use the text to size our object
+            width = text.len() as f32 * ctx;
+            height = ctx;
+        } else {
+            // otherwise, the size is the maximum size of the children
+            width = *children
+                .reduce(|accum, item| if accum >= item.0 { accum } else { item.0 })
+                .unwrap_or(0.0));
+            height = *children
+                .reduce(|accum, item| if accum >= item.1 { accum } else { item.1 })
+                .unwrap_or(&0.0);
+        }
+        // if the node contains a width or height attribute it overrides the other size
+        for a in node.attibutes(){
+            match a.name{
+                "width" => width = a.value.parse().unwrap(),
+                "height" => height = a.value.parse().unwrap(),
+                // because Size only depends on the width and height, no other attributes will be passed to the member
+                _ => panic!()
+            }
+        }
+        // to determine what other parts of the dom need to be updated we return a boolean that marks if this member changed
+        let changed = (width != self.0) || (height != self.1);
+        *self = Self(width, height);
+        changed
+    }
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Default)]
+struct TextColor {
+    r: u8,
+    g: u8,
+    b: u8,
+}
+// TextColor only depends on the current node and its parent, so it implements ParentDepState
+impl ParentDepState for TextColor {
+    type Ctx = ();
+    // TextColor depends on the TextColor part of the parent
+    type DepState = Self;
+    // TextColor only cares about the color attribute of the current node
+    const NODE_MASK: NodeMask = NodeMask::new_with_attrs(AttributeMask::Static(&["color"]));
+    fn reduce(
+        &mut self,
+        node: NodeView,
+        parent: Option<&Self::DepState>,
+        _ctx: &Self::Ctx,
+    ) -> bool {
+        // TextColor only depends on the color tag, so getting the first tag is equivilent to looking through all tags
+        let new = match node.attributes().next() {
+            // if there is a color tag, translate it
+            Some("red") => TextColor { r: 255, g: 0, b: 0 },
+            Some("green") => TextColor { r: 0, g: 255, b: 0 },
+            Some("blue") => TextColor { r: 0, g: 0, b: 255 },
+            Some(_) => panic!("unknown color"),
+            // otherwise check if the node has a parent and inherit that color
+            None => match parent {
+                Some(parent) => *parent,
+                None => Self::default(),
+            },
+        };
+        // check if the member has changed
+        let changed = new != *self;
+        *self = new;
+        changed
+    }
+}
+
+#[derive(Debug, Clone, PartialEq, Default)]
+struct Border(bool);
+// TextColor only depends on the current node, so it implements NodeDepState
+impl NodeDepState for Border {
+    type Ctx = ();
+    // Border does not depended on any other member in the current node
+    type DepState = ();
+    // Border does not depended on any other member in the current node
+    const NODE_MASK: NodeMask =
+        NodeMask::new_with_attrs(AttributeMask::Static(&["border"]));
+    fn reduce(&mut self, node: NodeView, _sibling: &Self::DepState, _ctx: &Self::Ctx) -> bool {
+        // check if the node contians a border attribute
+        let new = Self(node.attributes().next().map(|a| a.name == "border").is_some());
+        // check if the member has changed
+        let changed = new != *self;
+        *self = new;
+        changed
+    }
+}
+
+// State provides a derive macro, but anotations on the members are needed in the form #[dep_type(dep_member, CtxType)]
+#[derive(State, Default, Clone)]
+struct ToyState {
+    // the color member of it's parent and no context
+    #[parent_dep_state(color)]
+    color: TextColor,
+    // depends on the node, and no context
+    #[node_dep_state()]
+    border: Border,
+    // depends on the layout_width member of children and f32 context (for text size)
+    #[child_dep_state(size, f32)]
+    size: Size,
+}
+```
+
+Now that we have our state, we can put it to use in our dom. Re can update the dom with update_state to update the structure of the dom (adding, removing, and chaning properties of nodes) and then apply_mutations to update the ToyState for each of the nodes that changed.
+```rust
+fn main(){
+    fn app(cx: Scope) -> Element {
+        cx.render(rsx!{
+            div{
+                color: "red",
+                "hello world"
+            }
+        })
+    }
+    let vdom = VirtualDom::new(app);
+    let rdom: RealDom<ToyState> = RealDom::new();
+
+    let mutations = dom.rebuild();
+    // update the structure of the real_dom tree
+    let to_update = rdom.apply_mutations(vec![mutations]);
+    let mut ctx = AnyMap::new();
+    // set the font size to 3.3
+    ctx.insert(3.3);
+    // update the ToyState for nodes in the real_dom tree
+    let _to_rerender = rdom.update_state(&dom, to_update, ctx).unwrap();
+
+    // we need to run the vdom in a async runtime
+    tokio::runtime::Builder::new_current_thread()
+        .enable_all()
+        .build()?
+        .block_on(async {
+            loop{
+                let wait = vdom.wait_for_work();
+                let mutations = vdom.work_with_deadline(|| false);
+                let to_update = rdom.apply_mutations(mutations);
+                let mut ctx = AnyMap::new();
+                ctx.insert(3.3);
+                let _to_rerender = rdom.update_state(vdom, to_update, ctx).unwrap();
+
+                // render...
+            }
+        })
+}
+```
+
+## Layout
+For most platforms the layout of the Elements will stay the same. The layout_attributes module provides a way to apply html attributes to a stretch layout style.
+
+## Conclusion
+That should be it! You should have nearly all the knowledge required on how to implement your own renderer. We're super interested in seeing Dioxus apps brought to custom desktop renderers, mobile renderer, video game UI, and even augmented reality! If you're interesting in contributing to any of the these projects, don't be afraid to reach out or join the community.

+ 3 - 5
docs/reference/src/platforms/tui.md

@@ -53,7 +53,6 @@ $ cargo run
 Press "ctrl-c" to close the app. To switch from "ctrl-c" to  just "q" to quit you can launch the app with a configuration to disable the default quit and use the root TuiContext to quit on your own.
 
 ```rust
-//  main
 use dioxus::events::{KeyCode, KeyboardEvent};
 use dioxus::prelude::*;
 use dioxus::tui::TuiContext;
@@ -61,11 +60,10 @@ use dioxus::tui::TuiContext;
 fn main() {
     dioxus::tui::launch_cfg(
         app,
-        dioxus::tui::Config {
-            ctrl_c_quit: false,
+        dioxus::tui::Config::new()
+            .without_ctrl_c_quit()
             // Some older terminals only support 16 colors or ANSI colors if your terminal is one of these change this to BaseColors or ANSI
-            rendering_mode: dioxus::tui::RenderingMode::Rgb,
-        },
+            .with_rendering_mode(dioxus::tui::RenderingMode::Rgb),
     );
 }
 

+ 30 - 0
examples/custom_element.rs

@@ -0,0 +1,30 @@
+//! This example shows to wrap a webcomponent / custom element with a component.
+//!
+//! Oftentimes, a third party library will provide a webcomponent that you want
+//! to use in your application. This example shows how to create that custom element
+//! directly with the raw_element method on NodeFactory.
+
+use dioxus::prelude::*;
+
+fn main() {
+    let mut dom = VirtualDom::new(app);
+    let _ = dom.rebuild();
+
+    let output = dioxus::ssr::render_vdom(&dom);
+
+    println!("{}", output);
+}
+
+fn app(cx: Scope) -> Element {
+    let nf = NodeFactory::new(&cx);
+
+    let mut attrs = dioxus::core::exports::bumpalo::collections::Vec::new_in(nf.bump());
+
+    attrs.push(nf.attr("client-id", format_args!("abc123"), None, false));
+
+    attrs.push(nf.attr("name", format_args!("bob"), None, false));
+
+    attrs.push(nf.attr("age", format_args!("47"), None, false));
+
+    Some(nf.raw_element("my-element", None, &[], attrs.into_bump_slice(), &[], None))
+}

+ 26 - 0
examples/fermi.rs

@@ -14,6 +14,7 @@ fn app(cx: Scope) -> Element {
     cx.render(rsx! {
         div { "hello {name}!" }
         Child {}
+        ChildWithRef{}
     })
 }
 
@@ -27,3 +28,28 @@ fn Child(cx: Scope) -> Element {
         }
     })
 }
+
+static NAMES: AtomRef<Vec<String>> = |_| vec!["world".to_string()];
+
+fn ChildWithRef(cx: Scope) -> Element {
+    let names = use_atom_ref(&cx, NAMES);
+
+    cx.render(rsx! {
+        div {
+            ul {
+                names.read().iter().map(|f| rsx!{
+                    li { "hello: {f}" }
+                })
+            }
+            button {
+                onclick: move |_| {
+                    let names = names.clone();
+                    cx.spawn(async move {
+                        names.write().push("asd".to_string());
+                    })
+                },
+                "Add name"
+            }
+        }
+    })
+}

+ 56 - 0
examples/mouse_event.rs

@@ -0,0 +1,56 @@
+use dioxus::prelude::*;
+use dioxus_core::UiEvent;
+use dioxus_html::on::MouseData;
+
+fn main() {
+    dioxus::desktop::launch(app);
+}
+
+fn app(cx: Scope) -> Element {
+    let page_coordinates = use_state(&cx, || "".to_string());
+    let screen_coordinates = use_state(&cx, || "".to_string());
+    let element_coordinates = use_state(&cx, || "".to_string());
+    let buttons = use_state(&cx, || "".to_string());
+    let modifiers = use_state(&cx, || "".to_string());
+
+    let container_style = r#"
+        display: flex;
+        flex-direction: column;
+        align-items: center;
+    "#;
+    let rect_style = r#"
+        background: deepskyblue;
+        height: 50vh;
+        width: 50vw;
+    "#;
+
+    let update_mouse_position = move |event: UiEvent<MouseData>| {
+        let mouse_data = event.data;
+
+        page_coordinates.set(format!("{:?}", mouse_data.page_coordinates()));
+        screen_coordinates.set(format!("{:?}", mouse_data.screen_coordinates()));
+        element_coordinates.set(format!("{:?}", mouse_data.element_coordinates()));
+
+        // Note: client coordinates are also available, but they would be the same as the page coordinates in this example, because there is no scrolling.
+
+        buttons.set(format!("{:?}", mouse_data.held_buttons()));
+        modifiers.set(format!("{:?}", mouse_data.modifiers()));
+    };
+
+    cx.render(rsx! (
+        div {
+            style: "{container_style}",
+            "Hover over to display coordinates:",
+            div {
+                style: "{rect_style}",
+                onmousemove: update_mouse_position,
+                prevent_default: "mousedown",
+            }
+            div {"Page coordinates: {page_coordinates}"},
+            div {"Screen coordinates: {screen_coordinates}"},
+            div {"Element coordinates: {element_coordinates}"},
+            div {"Buttons: {buttons}"},
+            div {"Modifiers: {modifiers}"},
+        }
+    ))
+}

+ 29 - 2
examples/tui_hover.rs

@@ -1,6 +1,7 @@
 use std::{convert::TryInto, sync::Arc};
 
 use dioxus::{events::MouseData, prelude::*};
+use dioxus_core::UiEvent;
 
 fn main() {
     dioxus::tui::launch(app);
@@ -12,7 +13,7 @@ fn app(cx: Scope) -> Element {
     }
 
     fn get_brightness(m: Arc<MouseData>) -> i32 {
-        let b: i32 = m.buttons.count_ones().try_into().unwrap();
+        let b: i32 = m.held_buttons().len().try_into().unwrap();
         127 * b
     }
 
@@ -26,6 +27,24 @@ fn app(cx: Scope) -> Element {
     let q3_color_str = to_str(q3_color);
     let q4_color_str = to_str(q4_color);
 
+    let page_coordinates = use_state(&cx, || "".to_string());
+    let element_coordinates = use_state(&cx, || "".to_string());
+    let buttons = use_state(&cx, || "".to_string());
+    let modifiers = use_state(&cx, || "".to_string());
+
+    let update_data = move |event: UiEvent<MouseData>| {
+        let mouse_data = event.data;
+
+        page_coordinates.set(format!("{:?}", mouse_data.page_coordinates()));
+        element_coordinates.set(format!("{:?}", mouse_data.element_coordinates()));
+
+        // Note: client coordinates are also available, but they would be the same as the page coordinates in this example, because there is no scrolling.
+        // There are also screen coordinates, but they are currently the same as client coordinates due to technical limitations
+
+        buttons.set(format!("{:?}", mouse_data.held_buttons()));
+        modifiers.set(format!("{:?}", mouse_data.modifiers()));
+    };
+
     cx.render(rsx! {
         div {
             width: "100%",
@@ -48,6 +67,7 @@ fn app(cx: Scope) -> Element {
                     onmouseup: move |m| q1_color.set([get_brightness(m.data), 0, 0]),
                     onwheel: move |w| q1_color.set([q1_color[0] + (10.0*w.delta_y) as i32, 0, 0]),
                     onmouseleave: move |_| q1_color.set([200; 3]),
+                    onmousemove: update_data,
                     "click me"
                 }
                 div {
@@ -61,6 +81,7 @@ fn app(cx: Scope) -> Element {
                     onmouseup: move |m| q2_color.set([get_brightness(m.data); 3]),
                     onwheel: move |w| q2_color.set([q2_color[0] + (10.0*w.delta_y) as i32;3]),
                     onmouseleave: move |_| q2_color.set([200; 3]),
+                    onmousemove: update_data,
                     "click me"
                 }
             }
@@ -80,6 +101,7 @@ fn app(cx: Scope) -> Element {
                     onmouseup: move |m| q3_color.set([0, get_brightness(m.data), 0]),
                     onwheel: move |w| q3_color.set([0, q3_color[1] + (10.0*w.delta_y) as i32, 0]),
                     onmouseleave: move |_| q3_color.set([200; 3]),
+                    onmousemove: update_data,
                     "click me"
                 }
                 div {
@@ -93,9 +115,14 @@ fn app(cx: Scope) -> Element {
                     onmouseup: move |m| q4_color.set([0, 0, get_brightness(m.data)]),
                     onwheel: move |w| q4_color.set([0, 0, q4_color[2] + (10.0*w.delta_y) as i32]),
                     onmouseleave: move |_| q4_color.set([200; 3]),
+                    onmousemove: update_data,
                     "click me"
                 }
-            }
+            },
+            div {"Page coordinates: {page_coordinates}"},
+            div {"Element coordinates: {element_coordinates}"},
+            div {"Buttons: {buttons}"},
+            div {"Modifiers: {modifiers}"},
         }
     })
 }

+ 9 - 7
examples/tui_keys.rs

@@ -1,5 +1,7 @@
 use dioxus::events::WheelEvent;
 use dioxus::prelude::*;
+use dioxus_html::geometry::ScreenPoint;
+use dioxus_html::input_data::MouseButtonSet;
 use dioxus_html::on::{KeyboardEvent, MouseEvent};
 use dioxus_html::KeyCode;
 
@@ -9,9 +11,9 @@ fn main() {
 
 fn app(cx: Scope) -> Element {
     let key = use_state(&cx, || "".to_string());
-    let mouse = use_state(&cx, || (0, 0));
+    let mouse = use_state(&cx, || ScreenPoint::zero());
     let count = use_state(&cx, || 0);
-    let buttons = use_state(&cx, || 0);
+    let buttons = use_state(&cx, || MouseButtonSet::empty());
     let mouse_clicked = use_state(&cx, || false);
 
     cx.render(rsx! {
@@ -36,21 +38,21 @@ fn app(cx: Scope) -> Element {
                 count.set(count + evt.data.delta_y as i64);
             },
             ondrag: move |evt: MouseEvent| {
-                mouse.set((evt.data.screen_x, evt.data.screen_y));
+                mouse.set(evt.data.screen_coordinates());
             },
             onmousedown: move |evt: MouseEvent| {
-                mouse.set((evt.data.screen_x, evt.data.screen_y));
-                buttons.set(evt.data.buttons);
+                mouse.set(evt.data.screen_coordinates());
+                buttons.set(evt.data.held_buttons());
                 mouse_clicked.set(true);
             },
             onmouseup: move |evt: MouseEvent| {
-                buttons.set(evt.data.buttons);
+                buttons.set(evt.data.held_buttons());
                 mouse_clicked.set(false);
             },
 
             "count: {count:?}",
             "key: {key}",
-            "mouse buttons: {buttons:b}",
+            "mouse buttons: {buttons:?}",
             "mouse pos: {mouse:?}",
             "mouse button pressed: {mouse_clicked}"
         }

+ 23 - 0
examples/window_zoom.rs

@@ -0,0 +1,23 @@
+use dioxus::desktop::use_window;
+use dioxus::prelude::*;
+
+fn main() {
+    dioxus::desktop::launch(app);
+}
+
+fn app(cx: Scope) -> Element {
+    let window = use_window(&cx);
+
+    let level = use_state(&cx, || 1.0);
+    cx.render(rsx! {
+        input {
+            r#type: "number",
+            value: "{level}",
+            oninput: |e| {
+                let num = e.value.parse::<f64>().unwrap_or(1.0);
+                level.set(num);
+                window.set_zoom_level(num);
+            }
+        }
+    })
+}

+ 1 - 1
packages/core-macro/Cargo.toml

@@ -1,6 +1,6 @@
 [package]
 name = "dioxus-core-macro"
-version = "0.2.0"
+version = "0.2.1"
 authors = ["Jonathan Kelley"]
 edition = "2021"
 description = "Core macro for Dioxus Virtual DOM"

+ 1 - 1
packages/core/Cargo.toml

@@ -1,6 +1,6 @@
 [package]
 name = "dioxus-core"
-version = "0.2.0"
+version = "0.2.1"
 authors = ["Jonathan Kelley"]
 edition = "2018"
 description = "Core functionality for Dioxus - a concurrent renderer-agnostic Virtual DOM for interactive user experiences"

+ 239 - 0
packages/core/src/arbitrary_value.rs

@@ -0,0 +1,239 @@
+use std::fmt::Formatter;
+
+/// Possible values for an attribute
+// trying to keep values at 3 bytes
+#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
+#[cfg_attr(feature = "serialize", serde(untagged))]
+#[derive(Clone, Debug, PartialEq)]
+#[allow(missing_docs)]
+pub enum AttributeValue<'a> {
+    Text(&'a str),
+    Float32(f32),
+    Float64(f64),
+    Int32(i32),
+    Int64(i64),
+    Uint32(u32),
+    Uint64(u64),
+    Bool(bool),
+
+    Vec3Float(f32, f32, f32),
+    Vec3Int(i32, i32, i32),
+    Vec3Uint(u32, u32, u32),
+
+    Vec4Float(f32, f32, f32, f32),
+    Vec4Int(i32, i32, i32, i32),
+    Vec4Uint(u32, u32, u32, u32),
+
+    Bytes(&'a [u8]),
+    Any(ArbitraryAttributeValue<'a>),
+}
+
+// todo
+#[allow(missing_docs)]
+impl<'a> AttributeValue<'a> {
+    pub fn is_truthy(&self) -> bool {
+        match self {
+            AttributeValue::Text(t) => *t == "true",
+            AttributeValue::Bool(t) => *t,
+            _ => false,
+        }
+    }
+
+    pub fn is_falsy(&self) -> bool {
+        match self {
+            AttributeValue::Text(t) => *t == "false",
+            AttributeValue::Bool(t) => !(*t),
+            _ => false,
+        }
+    }
+}
+
+impl<'a> std::fmt::Display for AttributeValue<'a> {
+    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
+        match self {
+            AttributeValue::Text(a) => write!(f, "{}", a),
+            AttributeValue::Float32(a) => write!(f, "{}", a),
+            AttributeValue::Float64(a) => write!(f, "{}", a),
+            AttributeValue::Int32(a) => write!(f, "{}", a),
+            AttributeValue::Int64(a) => write!(f, "{}", a),
+            AttributeValue::Uint32(a) => write!(f, "{}", a),
+            AttributeValue::Uint64(a) => write!(f, "{}", a),
+            AttributeValue::Bool(a) => write!(f, "{}", a),
+            AttributeValue::Vec3Float(_, _, _) => todo!(),
+            AttributeValue::Vec3Int(_, _, _) => todo!(),
+            AttributeValue::Vec3Uint(_, _, _) => todo!(),
+            AttributeValue::Vec4Float(_, _, _, _) => todo!(),
+            AttributeValue::Vec4Int(_, _, _, _) => todo!(),
+            AttributeValue::Vec4Uint(_, _, _, _) => todo!(),
+            AttributeValue::Bytes(_) => todo!(),
+            AttributeValue::Any(_) => todo!(),
+        }
+    }
+}
+
+#[derive(Clone, Copy)]
+pub struct ArbitraryAttributeValue<'a> {
+    pub value: &'a dyn std::any::Any,
+    pub cmp: fn(&'a dyn std::any::Any, &'a dyn std::any::Any) -> bool,
+}
+
+impl PartialEq for ArbitraryAttributeValue<'_> {
+    fn eq(&self, other: &Self) -> bool {
+        (self.cmp)(self.value, other.value)
+    }
+}
+
+impl std::fmt::Debug for ArbitraryAttributeValue<'_> {
+    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
+        f.debug_struct("ArbitraryAttributeValue").finish()
+    }
+}
+
+#[cfg(feature = "serialize")]
+impl<'a> serde::Serialize for ArbitraryAttributeValue<'a> {
+    fn serialize<S>(&self, _serializer: S) -> Result<S::Ok, S::Error>
+    where
+        S: serde::Serializer,
+    {
+        panic!("ArbitraryAttributeValue should not be serialized")
+    }
+}
+#[cfg(feature = "serialize")]
+impl<'de, 'a> serde::Deserialize<'de> for &'a ArbitraryAttributeValue<'a> {
+    fn deserialize<D>(_deserializer: D) -> Result<Self, D::Error>
+    where
+        D: serde::Deserializer<'de>,
+    {
+        panic!("ArbitraryAttributeValue is not deserializable!")
+    }
+}
+#[cfg(feature = "serialize")]
+impl<'de, 'a> serde::Deserialize<'de> for ArbitraryAttributeValue<'a> {
+    fn deserialize<D>(_deserializer: D) -> Result<Self, D::Error>
+    where
+        D: serde::Deserializer<'de>,
+    {
+        panic!("ArbitraryAttributeValue is not deserializable!")
+    }
+}
+
+// todo
+#[allow(missing_docs)]
+impl<'a> AttributeValue<'a> {
+    pub fn as_text(&self) -> Option<&'a str> {
+        match self {
+            AttributeValue::Text(s) => Some(s),
+            _ => None,
+        }
+    }
+
+    pub fn as_float32(&self) -> Option<f32> {
+        match self {
+            AttributeValue::Float32(f) => Some(*f),
+            _ => None,
+        }
+    }
+
+    pub fn as_float64(&self) -> Option<f64> {
+        match self {
+            AttributeValue::Float64(f) => Some(*f),
+            _ => None,
+        }
+    }
+
+    pub fn as_int32(&self) -> Option<i32> {
+        match self {
+            AttributeValue::Int32(i) => Some(*i),
+            _ => None,
+        }
+    }
+
+    pub fn as_int64(&self) -> Option<i64> {
+        match self {
+            AttributeValue::Int64(i) => Some(*i),
+            _ => None,
+        }
+    }
+
+    pub fn as_uint32(&self) -> Option<u32> {
+        match self {
+            AttributeValue::Uint32(i) => Some(*i),
+            _ => None,
+        }
+    }
+
+    pub fn as_uint64(&self) -> Option<u64> {
+        match self {
+            AttributeValue::Uint64(i) => Some(*i),
+            _ => None,
+        }
+    }
+
+    pub fn as_bool(&self) -> Option<bool> {
+        match self {
+            AttributeValue::Bool(b) => Some(*b),
+            _ => None,
+        }
+    }
+
+    pub fn as_vec3_float(&self) -> Option<(f32, f32, f32)> {
+        match self {
+            AttributeValue::Vec3Float(x, y, z) => Some((*x, *y, *z)),
+            _ => None,
+        }
+    }
+
+    pub fn as_vec3_int(&self) -> Option<(i32, i32, i32)> {
+        match self {
+            AttributeValue::Vec3Int(x, y, z) => Some((*x, *y, *z)),
+            _ => None,
+        }
+    }
+
+    pub fn as_vec3_uint(&self) -> Option<(u32, u32, u32)> {
+        match self {
+            AttributeValue::Vec3Uint(x, y, z) => Some((*x, *y, *z)),
+            _ => None,
+        }
+    }
+
+    pub fn as_vec4_float(&self) -> Option<(f32, f32, f32, f32)> {
+        match self {
+            AttributeValue::Vec4Float(x, y, z, w) => Some((*x, *y, *z, *w)),
+            _ => None,
+        }
+    }
+
+    pub fn as_vec4_int(&self) -> Option<(i32, i32, i32, i32)> {
+        match self {
+            AttributeValue::Vec4Int(x, y, z, w) => Some((*x, *y, *z, *w)),
+            _ => None,
+        }
+    }
+
+    pub fn as_vec4_uint(&self) -> Option<(u32, u32, u32, u32)> {
+        match self {
+            AttributeValue::Vec4Uint(x, y, z, w) => Some((*x, *y, *z, *w)),
+            _ => None,
+        }
+    }
+
+    pub fn as_bytes(&self) -> Option<&[u8]> {
+        match self {
+            AttributeValue::Bytes(b) => Some(b),
+            _ => None,
+        }
+    }
+
+    pub fn as_any(&self) -> Option<&'a ArbitraryAttributeValue> {
+        match self {
+            AttributeValue::Any(a) => Some(a),
+            _ => None,
+        }
+    }
+}
+
+// #[test]
+// fn test_attribute_value_size() {
+//     assert_eq!(std::mem::size_of::<AttributeValue<'_>>(), 24);
+// }

+ 1 - 1
packages/core/src/events.rs

@@ -49,7 +49,7 @@ impl BubbleState {
 ///     }
 /// )).unwrap();
 /// ```
-#[derive(Debug)]
+#[derive(Debug, Clone)]
 pub struct UserEvent {
     /// The originator of the event trigger if available
     pub scope_id: Option<ScopeId>,

+ 6 - 4
packages/core/src/lib.rs

@@ -2,6 +2,7 @@
 #![doc = include_str!("../README.md")]
 #![deny(missing_docs)]
 
+pub(crate) mod arbitrary_value;
 pub(crate) mod diff;
 pub(crate) mod events;
 pub(crate) mod lazynodes;
@@ -13,6 +14,7 @@ pub(crate) mod util;
 pub(crate) mod virtual_dom;
 
 pub(crate) mod innerlude {
+    pub use crate::arbitrary_value::*;
     pub use crate::events::*;
     pub use crate::lazynodes::*;
     pub use crate::mutations::*;
@@ -71,10 +73,10 @@ pub(crate) mod innerlude {
 }
 
 pub use crate::innerlude::{
-    AnyEvent, Attribute, Component, DioxusElement, DomEdit, Element, ElementId, ElementIdIterator,
-    EventHandler, EventPriority, IntoVNode, LazyNodes, Listener, Mutations, NodeFactory,
-    Properties, SchedulerMsg, Scope, ScopeId, ScopeState, TaskId, UiEvent, UserEvent, VComponent,
-    VElement, VFragment, VNode, VPlaceholder, VText, VirtualDom,
+    AnyEvent, Attribute, AttributeValue, Component, DioxusElement, DomEdit, Element, ElementId,
+    ElementIdIterator, EventHandler, EventPriority, IntoVNode, LazyNodes, Listener, Mutations,
+    NodeFactory, Properties, SchedulerMsg, Scope, ScopeId, ScopeState, TaskId, UiEvent, UserEvent,
+    VComponent, VElement, VFragment, VNode, VPlaceholder, VText, VirtualDom,
 };
 
 /// The purpose of this module is to alleviate imports of many common types

+ 4 - 3
packages/core/src/mutations.rs

@@ -167,8 +167,9 @@ pub enum DomEdit<'bump> {
         field: &'static str,
 
         /// The value of the attribute.
-        value: &'bump str,
+        value: AttributeValue<'bump>,
 
+        // value: &'bump str,
         /// The (optional) namespace of the attribute.
         /// For instance, "style" is in the "style" namespace.
         ns: Option<&'bump str>,
@@ -286,7 +287,7 @@ impl<'a> Mutations<'a> {
         self.edits.push(SetText { text, root });
     }
 
-    pub(crate) fn set_attribute(&mut self, attribute: &'a Attribute, root: u64) {
+    pub(crate) fn set_attribute(&mut self, attribute: &'a Attribute<'a>, root: u64) {
         let Attribute {
             name,
             value,
@@ -296,7 +297,7 @@ impl<'a> Mutations<'a> {
 
         self.edits.push(SetAttribute {
             field: name,
-            value,
+            value: value.clone(),
             ns: *namespace,
             root,
         });

+ 20 - 2
packages/core/src/nodes.rs

@@ -4,7 +4,7 @@
 //! cheap and *very* fast to construct - building a full tree should be quick.
 
 use crate::{
-    innerlude::{ComponentPtr, Element, Properties, Scope, ScopeId, ScopeState},
+    innerlude::{AttributeValue, ComponentPtr, Element, Properties, Scope, ScopeId, ScopeState},
     lazynodes::LazyNodes,
     AnyEvent, Component,
 };
@@ -339,7 +339,7 @@ pub struct Attribute<'a> {
     pub name: &'static str,
 
     /// The value of the attribute.
-    pub value: &'a str,
+    pub value: AttributeValue<'a>,
 
     /// An indication if this attribute can be ignored during diffing
     ///
@@ -610,6 +610,24 @@ impl<'a> NodeFactory<'a> {
         is_volatile: bool,
     ) -> Attribute<'a> {
         let (value, is_static) = self.raw_text(val);
+        Attribute {
+            name,
+            value: AttributeValue::Text(value),
+            is_static,
+            namespace,
+            is_volatile,
+        }
+    }
+
+    /// Create a new [`Attribute`] using non-arguments
+    pub fn custom_attr(
+        &self,
+        name: &'static str,
+        value: AttributeValue<'a>,
+        namespace: Option<&'static str>,
+        is_volatile: bool,
+        is_static: bool,
+    ) -> Attribute<'a> {
         Attribute {
             name,
             value,

+ 6 - 10
packages/desktop/Cargo.toml

@@ -1,6 +1,6 @@
 [package]
 name = "dioxus-desktop"
-version = "0.2.2"
+version = "0.2.3"
 authors = ["Jonathan Kelley"]
 edition = "2018"
 description = "Dioxus VirtualDOM renderer for a remote webview instance"
@@ -12,15 +12,15 @@ keywords = ["dom", "ui", "gui", "react", "wasm"]
 # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
 
 [dependencies]
-dioxus-core = { path = "../core", version = "^0.2.0", features = ["serialize"] }
-dioxus-html = { path = "../html", features = ["serialize"], version = "^0.2.0" }
-dioxus-interpreter-js = { path = "../interpreter", version = "^0.2.0" }
+dioxus-core = { path = "../core", version = "^0.2.1", features = ["serialize"] }
+dioxus-html = { path = "../html", features = ["serialize"], version = "^0.2.1" }
+dioxus-interpreter-js = { path = "../interpreter", version = "^0.2.1" }
 
 serde = "1.0.136"
 serde_json = "1.0.79"
 thiserror = "1.0.30"
 log = "0.4.14"
-wry = { version = "0.13.1" }
+wry = { version = "0.16.0" }
 futures-channel = "0.3.21"
 tokio = { version = "1.16.1", features = [
     "sync",
@@ -28,7 +28,7 @@ tokio = { version = "1.16.1", features = [
     "rt",
     "time",
 ], optional = true, default-features = false }
-webbrowser = "0.6.0"
+webbrowser = "0.7.1"
 mime_guess = "2.0.3"
 dunce = "1.0.2"
 
@@ -38,13 +38,9 @@ core-foundation = "0.9.3"
 [features]
 default = ["tokio_runtime"]
 tokio_runtime = ["tokio"]
-
-devtool = ["wry/devtool"]
 fullscreen = ["wry/fullscreen"]
 transparent = ["wry/transparent"]
-
 tray = ["wry/tray"]
-ayatana = ["wry/ayatana"]
 
 
 [dev-dependencies]

+ 28 - 4
packages/desktop/src/desktop_context.rs

@@ -115,6 +115,16 @@ impl DesktopContext {
         let _ = self.proxy.send_event(SetDecorations(decoration));
     }
 
+    /// set window zoom level
+    pub fn set_zoom_level(&self, scale_factor: f64) {
+        let _ = self.proxy.send_event(SetZoomLevel(scale_factor));
+    }
+
+    /// launch print modal
+    pub fn print(&self) {
+        let _ = self.proxy.send_event(Print);
+    }
+
     /// opens DevTool window
     pub fn devtool(&self) {
         let _ = self.proxy.send_event(DevTool);
@@ -148,6 +158,9 @@ pub enum UserWindowEvent {
     SetTitle(String),
     SetDecorations(bool),
 
+    SetZoomLevel(f64),
+
+    Print,
     DevTool,
 
     Eval(String),
@@ -191,11 +204,22 @@ pub(super) fn handler(
         SetTitle(content) => window.set_title(&content),
         SetDecorations(state) => window.set_decorations(state),
 
-        DevTool => webview.devtool(),
+        SetZoomLevel(scale_factor) => webview.zoom(scale_factor),
 
-        Eval(code) => webview
-            .evaluate_script(code.as_str())
-            .expect("eval shouldn't panic"),
+        Print => {
+            if let Err(e) = webview.print() {
+                // we can't panic this error.
+                log::warn!("Open print modal failed: {e}");
+            }
+        }
+        DevTool => webview.open_devtools(),
+
+        Eval(code) => {
+            if let Err(e) = webview.evaluate_script(code.as_str()) {
+                // we can't panic this error.
+                log::warn!("Eval script error: {e}");
+            }
+        }
     }
 }
 

+ 1 - 1
packages/desktop/src/lib.rs

@@ -200,7 +200,7 @@ pub fn launch_with_props<P: 'static + Send>(
                     )
                 } else {
                     // in debug, we are okay with the reload menu showing and dev tool
-                    webview = webview.with_dev_tool(true);
+                    webview = webview.with_devtools(true);
                 }
 
                 desktop.webviews.insert(window_id, webview.build().unwrap());

+ 2 - 2
packages/fermi/Cargo.toml

@@ -1,6 +1,6 @@
 [package]
 name = "fermi"
-version = "0.2.0"
+version = "0.2.1"
 authors = ["Jonathan Kelley"]
 edition = "2018"
 description = "Global state management for Dioxus"
@@ -13,7 +13,7 @@ keywords = ["dom", "ui", "gui", "react", "wasm"]
 # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
 
 [dependencies]
-dioxus-core = { path = "../core", version = "^0.2.0" }
+dioxus-core = { path = "../core", version = "^0.2.1" }
 im-rc = { version = "15.0.0", features = ["serde"] }
 log = "0.4.14"
 

+ 38 - 14
packages/fermi/src/hooks/atom_ref.rs

@@ -16,15 +16,35 @@ use std::{
 pub fn use_atom_ref<T: 'static>(cx: &ScopeState, atom: AtomRef<T>) -> &UseAtomRef<T> {
     let root = use_atom_root(cx);
 
-    cx.use_hook(|_| {
+    &cx.use_hook(|_| {
         root.initialize(atom);
-        UseAtomRef {
-            ptr: atom.unique_id(),
-            root: root.clone(),
-            scope_id: cx.scope_id(),
-            value: root.register(atom, cx.scope_id()),
-        }
+        (
+            UseAtomRef {
+                ptr: atom.unique_id(),
+                root: root.clone(),
+                scope_id: cx.scope_id(),
+                value: root.register(atom, cx.scope_id()),
+            },
+            AtomRefSubscription {
+                ptr: atom.unique_id(),
+                root: root.clone(),
+                scope_id: cx.scope_id(),
+            },
+        )
     })
+    .0
+}
+
+pub struct AtomRefSubscription {
+    ptr: AtomId,
+    root: Rc<AtomRoot>,
+    scope_id: ScopeId,
+}
+
+impl Drop for AtomRefSubscription {
+    fn drop(&mut self) {
+        self.root.unsubscribe(self.ptr, self.scope_id)
+    }
 }
 
 pub struct UseAtomRef<T> {
@@ -34,6 +54,17 @@ pub struct UseAtomRef<T> {
     scope_id: ScopeId,
 }
 
+impl<T> Clone for UseAtomRef<T> {
+    fn clone(&self) -> Self {
+        Self {
+            ptr: self.ptr,
+            value: self.value.clone(),
+            root: self.root.clone(),
+            scope_id: self.scope_id,
+        }
+    }
+}
+
 impl<T: 'static> UseAtomRef<T> {
     pub fn read(&self) -> Ref<T> {
         self.value.borrow()
@@ -45,7 +76,6 @@ impl<T: 'static> UseAtomRef<T> {
     }
 
     pub fn write_silent(&self) -> RefMut<T> {
-        self.root.force_update(self.ptr);
         self.value.borrow_mut()
     }
 
@@ -54,9 +84,3 @@ impl<T: 'static> UseAtomRef<T> {
         self.root.set(self.ptr, new);
     }
 }
-
-impl<T> Drop for UseAtomRef<T> {
-    fn drop(&mut self) {
-        self.root.unsubscribe(self.ptr, self.scope_id)
-    }
-}

+ 3 - 3
packages/hooks/Cargo.toml

@@ -1,6 +1,6 @@
 [package]
 name = "dioxus-hooks"
-version = "0.2.0"
+version = "0.2.1"
 authors = ["Jonathan Kelley"]
 edition = "2018"
 description = "Dioxus VirtualDOM renderer for a remote webview instance"
@@ -12,11 +12,11 @@ keywords = ["dom", "ui", "gui", "react", "wasm"]
 # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
 
 [dependencies]
-dioxus-core = { path = "../../packages/core", version = "^0.2.0" }
+dioxus-core = { path = "../../packages/core", version = "^0.2.1" }
 futures-channel = "0.3.21"
 log = { version = "0.4" }
 
 
 [dev-dependencies]
 futures-util = { version = "0.3", default-features = false }
-dioxus-core = { path = "../../packages/core", version = "^0.2.0" }
+dioxus-core = { path = "../../packages/core", version = "^0.2.1" }

+ 3 - 0
packages/hooks/src/lib.rs

@@ -16,5 +16,8 @@ pub use usecoroutine::*;
 mod usefuture;
 pub use usefuture::*;
 
+mod useeffect;
+pub use useeffect::*;
+
 // mod usesuspense;
 // pub use usesuspense::*;

+ 1 - 1
packages/hooks/src/useref.rs

@@ -24,7 +24,7 @@ use std::{
 /// val.write().insert(1, "hello".to_string());
 /// ```
 ///
-/// You can avoid this defualt behavior with `write_silent`
+/// You can avoid this default behavior with `write_silent`
 ///
 /// ```
 /// // with `write_silent`, the component will not be re-rendered

+ 26 - 2
packages/html/Cargo.toml

@@ -1,6 +1,6 @@
 [package]
 name = "dioxus-html"
-version = "0.2.0"
+version = "0.2.1"
 authors = ["Jonathan Kelley"]
 edition = "2018"
 description = "HTML Element pack for Dioxus - a concurrent renderer-agnostic Virtual DOM for interactive user experiences"
@@ -11,10 +11,34 @@ documentation = "https://docs.rs/dioxus"
 keywords = ["dom", "ui", "gui", "react", "wasm"]
 
 [dependencies]
-dioxus-core = { path = "../core", version = "^0.2.0" }
+dioxus-core = { path = "../core", version = "^0.2.1" }
 serde = { version = "1", features = ["derive"], optional = true }
 serde_repr = { version = "0.1", optional = true }
+wasm-bindgen = { version = "0.2.79", optional = true }
+euclid = "0.22.7"
+enumset = "1.0.11"
+keyboard-types = "0.6.2"
+
+[dependencies.web-sys]
+optional = true
+version = "0.3.56"
+features = [
+    "TouchEvent",
+    "MouseEvent",
+    "InputEvent",
+    "ClipboardEvent",
+    "KeyboardEvent",
+    "TouchEvent",
+    "WheelEvent",
+    "AnimationEvent",
+    "TransitionEvent",
+    "PointerEvent",
+    "FocusEvent",
+    "CompositionEvent",
+    "ClipboardEvent",
+]
 
 [features]
 default = []
 serialize = ["serde", "serde_repr"]
+wasm-bind = ["web-sys", "wasm-bindgen"]

+ 190 - 15
packages/html/src/events.rs

@@ -3,6 +3,13 @@ use dioxus_core::exports::bumpalo;
 use dioxus_core::*;
 
 pub mod on {
+    //! Input events and associated data
+
+    use crate::geometry::{ClientPoint, Coordinates, ElementPoint, PagePoint, ScreenPoint};
+    use crate::input_data::{
+        decode_mouse_button_set, encode_mouse_button_set, MouseButton, MouseButtonSet,
+    };
+    use keyboard_types::Modifiers;
     use std::collections::HashMap;
 
     use super::*;
@@ -398,21 +405,21 @@ pub mod on {
 
     pub type ClipboardEvent = UiEvent<ClipboardData>;
     #[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
-    #[derive(Debug)]
+    #[derive(Debug, Clone)]
     pub struct ClipboardData {
         // DOMDataTransfer clipboardData
     }
 
     pub type CompositionEvent = UiEvent<CompositionData>;
     #[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
-    #[derive(Debug)]
+    #[derive(Debug, Clone)]
     pub struct CompositionData {
         pub data: String,
     }
 
     pub type KeyboardEvent = UiEvent<KeyboardData>;
     #[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
-    #[derive(Debug)]
+    #[derive(Debug, Clone)]
     pub struct KeyboardData {
         pub char_code: u32,
 
@@ -481,12 +488,12 @@ pub mod on {
 
     pub type FocusEvent = UiEvent<FocusData>;
     #[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
-    #[derive(Debug)]
+    #[derive(Debug, Clone)]
     pub struct FocusData {/* DOMEventInner:  Send + SyncTarget relatedTarget */}
 
     pub type FormEvent = UiEvent<FormData>;
     #[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
-    #[derive(Debug)]
+    #[derive(Debug, Clone)]
     pub struct FormData {
         pub value: String,
         pub values: HashMap<String, String>,
@@ -495,26 +502,194 @@ pub mod on {
 
     pub type MouseEvent = UiEvent<MouseData>;
     #[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
-    #[derive(Debug)]
+    #[derive(Debug, Clone)]
+    /// Data associated with a mouse event
+    ///
+    /// Do not use the deprecated fields; they may change or become private in the future.
     pub struct MouseData {
+        /// True if the alt key was down when the mouse event was fired.
+        #[deprecated(since = "0.3.0", note = "use modifiers() instead")]
         pub alt_key: bool,
+        /// The button number that was pressed (if applicable) when the mouse event was fired.
+        #[deprecated(since = "0.3.0", note = "use trigger_button() instead")]
         pub button: i16,
+        /// Indicates which buttons are pressed on the mouse (or other input device) when a mouse event is triggered.
+        ///
+        /// Each button that can be pressed is represented by a given number (see below). If more than one button is pressed, the button values are added together to produce a new number. For example, if the secondary (2) and auxiliary (4) buttons are pressed simultaneously, the value is 6 (i.e., 2 + 4).
+        ///
+        /// - 1: Primary button (usually the left button)
+        /// - 2: Secondary button (usually the right button)
+        /// - 4: Auxiliary button (usually the mouse wheel button or middle button)
+        /// - 8: 4th button (typically the "Browser Back" button)
+        /// - 16 : 5th button (typically the "Browser Forward" button)
+        #[deprecated(since = "0.3.0", note = "use held_buttons() instead")]
         pub buttons: u16,
+        /// The horizontal coordinate within the application's viewport at which the event occurred (as opposed to the coordinate within the page).
+        ///
+        /// For example, clicking on the left edge of the viewport will always result in a mouse event with a clientX value of 0, regardless of whether the page is scrolled horizontally.
+        #[deprecated(since = "0.3.0", note = "use client_coordinates() instead")]
         pub client_x: i32,
+        /// The vertical coordinate within the application's viewport at which the event occurred (as opposed to the coordinate within the page).
+        ///
+        /// For example, clicking on the top edge of the viewport will always result in a mouse event with a clientY value of 0, regardless of whether the page is scrolled vertically.
+        #[deprecated(since = "0.3.0", note = "use client_coordinates() instead")]
         pub client_y: i32,
+        /// True if the control key was down when the mouse event was fired.
+        #[deprecated(since = "0.3.0", note = "use modifiers() instead")]
         pub ctrl_key: bool,
+        /// True if the meta key was down when the mouse event was fired.
+        #[deprecated(since = "0.3.0", note = "use modifiers() instead")]
         pub meta_key: bool,
+        /// The offset in the X coordinate of the mouse pointer between that event and the padding edge of the target node.
+        #[deprecated(since = "0.3.0", note = "use element_coordinates() instead")]
+        pub offset_x: i32,
+        /// The offset in the Y coordinate of the mouse pointer between that event and the padding edge of the target node.
+        #[deprecated(since = "0.3.0", note = "use element_coordinates() instead")]
+        pub offset_y: i32,
+        /// The X (horizontal) coordinate (in pixels) of the mouse, relative to the left edge of the entire document. This includes any portion of the document not currently visible.
+        ///
+        /// Being based on the edge of the document as it is, this property takes into account any horizontal scrolling of the page. For example, if the page is scrolled such that 200 pixels of the left side of the document are scrolled out of view, and the mouse is clicked 100 pixels inward from the left edge of the view, the value returned by pageX will be 300.
+        #[deprecated(since = "0.3.0", note = "use page_coordinates() instead")]
         pub page_x: i32,
+        /// The Y (vertical) coordinate in pixels of the event relative to the whole document.
+        ///
+        /// See `page_x`.
+        #[deprecated(since = "0.3.0", note = "use page_coordinates() instead")]
         pub page_y: i32,
+        /// The X coordinate of the mouse pointer in global (screen) coordinates.
+        #[deprecated(since = "0.3.0", note = "use screen_coordinates() instead")]
         pub screen_x: i32,
+        /// The Y coordinate of the mouse pointer in global (screen) coordinates.
+        #[deprecated(since = "0.3.0", note = "use screen_coordinates() instead")]
         pub screen_y: i32,
+        /// True if the shift key was down when the mouse event was fired.
+        #[deprecated(since = "0.3.0", note = "use modifiers() instead")]
         pub shift_key: bool,
         // fn get_modifier_state(&self, key_code: &str) -> bool;
     }
 
+    impl MouseData {
+        /// Construct MouseData with the specified properties
+        ///
+        /// Note: the current implementation truncates coordinates. In the future, when we change the internal representation, it may also support a fractional part.
+        pub fn new(
+            coordinates: Coordinates,
+            trigger_button: Option<MouseButton>,
+            held_buttons: MouseButtonSet,
+            modifiers: Modifiers,
+        ) -> Self {
+            let alt_key = modifiers.contains(Modifiers::ALT);
+            let ctrl_key = modifiers.contains(Modifiers::CONTROL);
+            let meta_key = modifiers.contains(Modifiers::META);
+            let shift_key = modifiers.contains(Modifiers::SHIFT);
+
+            let [client_x, client_y]: [i32; 2] = coordinates.client().cast().into();
+            let [offset_x, offset_y]: [i32; 2] = coordinates.element().cast().into();
+            let [page_x, page_y]: [i32; 2] = coordinates.page().cast().into();
+            let [screen_x, screen_y]: [i32; 2] = coordinates.screen().cast().into();
+
+            #[allow(deprecated)]
+            Self {
+                alt_key,
+                ctrl_key,
+                meta_key,
+                shift_key,
+
+                button: trigger_button.map_or(0, |b| b.into_web_code()),
+                buttons: encode_mouse_button_set(held_buttons),
+
+                client_x,
+                client_y,
+                offset_x,
+                offset_y,
+                page_x,
+                page_y,
+                screen_x,
+                screen_y,
+            }
+        }
+
+        /// The event's coordinates relative to the application's viewport (as opposed to the coordinate within the page).
+        ///
+        /// For example, clicking in the top left corner of the viewport will always result in a mouse event with client coordinates (0., 0.), regardless of whether the page is scrolled horizontally.
+        pub fn client_coordinates(&self) -> ClientPoint {
+            #[allow(deprecated)]
+            ClientPoint::new(self.client_x.into(), self.client_y.into())
+        }
+
+        /// The event's coordinates relative to the padding edge of the target element
+        ///
+        /// For example, clicking in the top left corner of an element will result in element coordinates (0., 0.)
+        pub fn element_coordinates(&self) -> ElementPoint {
+            #[allow(deprecated)]
+            ElementPoint::new(self.offset_x.into(), self.offset_y.into())
+        }
+
+        /// The event's coordinates relative to the entire document. This includes any portion of the document not currently visible.
+        ///
+        /// For example, if the page is scrolled 200 pixels to the right and 300 pixels down, clicking in the top left corner of the viewport would result in page coordinates (200., 300.)
+        pub fn page_coordinates(&self) -> PagePoint {
+            #[allow(deprecated)]
+            PagePoint::new(self.page_x.into(), self.page_y.into())
+        }
+
+        /// The event's coordinates relative to the entire screen. This takes into account the window's offset.
+        pub fn screen_coordinates(&self) -> ScreenPoint {
+            #[allow(deprecated)]
+            ScreenPoint::new(self.screen_x.into(), self.screen_y.into())
+        }
+
+        pub fn coordinates(&self) -> Coordinates {
+            Coordinates::new(
+                self.screen_coordinates(),
+                self.client_coordinates(),
+                self.element_coordinates(),
+                self.page_coordinates(),
+            )
+        }
+
+        /// The set of modifier keys which were pressed when the event occurred
+        pub fn modifiers(&self) -> Modifiers {
+            let mut modifiers = Modifiers::empty();
+
+            #[allow(deprecated)]
+            {
+                if self.alt_key {
+                    modifiers.insert(Modifiers::ALT);
+                }
+                if self.ctrl_key {
+                    modifiers.insert(Modifiers::CONTROL);
+                }
+                if self.meta_key {
+                    modifiers.insert(Modifiers::META);
+                }
+                if self.shift_key {
+                    modifiers.insert(Modifiers::SHIFT);
+                }
+            }
+
+            modifiers
+        }
+
+        /// The set of mouse buttons which were held when the event occurred.
+        pub fn held_buttons(&self) -> MouseButtonSet {
+            #[allow(deprecated)]
+            decode_mouse_button_set(self.buttons)
+        }
+
+        /// The mouse button that triggered the event
+        ///
+        // todo the following is kind of bad; should we just return None when the trigger_button is unreliable (and frankly irrelevant)? i guess we would need the event_type here
+        /// This is only guaranteed to indicate which button was pressed during events caused by pressing or releasing a button. As such, it is not reliable for events such as mouseenter, mouseleave, mouseover, mouseout, or mousemove. For example, a value of MouseButton::Primary may also indicate that no button was pressed.
+        pub fn trigger_button(&self) -> Option<MouseButton> {
+            #[allow(deprecated)]
+            Some(MouseButton::from_web_code(self.button))
+        }
+    }
+
     pub type PointerEvent = UiEvent<PointerData>;
     #[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
-    #[derive(Debug)]
+    #[derive(Debug, Clone)]
     pub struct PointerData {
         // Mouse only
         pub alt_key: bool,
@@ -544,12 +719,12 @@ pub mod on {
 
     pub type SelectionEvent = UiEvent<SelectionData>;
     #[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
-    #[derive(Debug)]
+    #[derive(Debug, Clone)]
     pub struct SelectionData {}
 
     pub type TouchEvent = UiEvent<TouchData>;
     #[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
-    #[derive(Debug)]
+    #[derive(Debug, Clone)]
     pub struct TouchData {
         pub alt_key: bool,
         pub ctrl_key: bool,
@@ -563,7 +738,7 @@ pub mod on {
 
     pub type WheelEvent = UiEvent<WheelData>;
     #[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
-    #[derive(Debug)]
+    #[derive(Debug, Clone)]
     pub struct WheelData {
         pub delta_mode: u32,
         pub delta_x: f64,
@@ -573,19 +748,19 @@ pub mod on {
 
     pub type MediaEvent = UiEvent<MediaData>;
     #[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
-    #[derive(Debug)]
+    #[derive(Debug, Clone)]
     pub struct MediaData {}
 
     pub type ImageEvent = UiEvent<ImageData>;
     #[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
-    #[derive(Debug)]
+    #[derive(Debug, Clone)]
     pub struct ImageData {
         pub load_error: bool,
     }
 
     pub type AnimationEvent = UiEvent<AnimationData>;
     #[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
-    #[derive(Debug)]
+    #[derive(Debug, Clone)]
     pub struct AnimationData {
         pub animation_name: String,
         pub pseudo_element: String,
@@ -594,7 +769,7 @@ pub mod on {
 
     pub type TransitionEvent = UiEvent<TransitionData>;
     #[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
-    #[derive(Debug)]
+    #[derive(Debug, Clone)]
     pub struct TransitionData {
         pub property_name: String,
         pub pseudo_element: String,
@@ -603,7 +778,7 @@ pub mod on {
 
     pub type ToggleEvent = UiEvent<ToggleData>;
     #[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
-    #[derive(Debug)]
+    #[derive(Debug, Clone)]
     pub struct ToggleData {}
 }
 

+ 73 - 0
packages/html/src/geometry.rs

@@ -0,0 +1,73 @@
+//! Geometry primitives for representing e.g. mouse events
+
+/// A re-export of euclid, which we use for geometry primitives
+pub use euclid;
+
+use euclid::*;
+
+/// Coordinate space relative to the screen
+pub struct ScreenSpace;
+/// A point in ScreenSpace
+pub type ScreenPoint = Point2D<f64, ScreenSpace>;
+
+/// Coordinate space relative to the viewport
+pub struct ClientSpace;
+/// A point in ClientSpace
+pub type ClientPoint = Point2D<f64, ClientSpace>;
+
+/// Coordinate space relative to an element
+pub struct ElementSpace;
+/// A point in ElementSpace
+pub type ElementPoint = Point2D<f64, ElementSpace>;
+
+/// Coordinate space relative to the page
+pub struct PageSpace;
+/// A point in PageSpace
+pub type PagePoint = Point2D<f64, PageSpace>;
+
+/// Coordinates of a point in the app's interface
+pub struct Coordinates {
+    screen: ScreenPoint,
+    client: ClientPoint,
+    element: ElementPoint,
+    page: PagePoint,
+}
+
+impl Coordinates {
+    /// Construct new coordinates with the specified screen-, client-, element- and page-relative points
+    pub fn new(
+        screen: ScreenPoint,
+        client: ClientPoint,
+        element: ElementPoint,
+        page: PagePoint,
+    ) -> Self {
+        Self {
+            screen,
+            client,
+            element,
+            page,
+        }
+    }
+    /// Coordinates relative to the entire screen. This takes into account the window's offset.
+    pub fn screen(&self) -> ScreenPoint {
+        self.screen
+    }
+    /// Coordinates relative to the application's viewport (as opposed to the coordinate within the page).
+    ///
+    /// For example, clicking in the top left corner of the viewport will always result in a mouse event with client coordinates (0., 0.), regardless of whether the page is scrolled horizontally.
+    pub fn client(&self) -> ClientPoint {
+        self.client
+    }
+    /// Coordinates relative to the padding edge of the target element
+    ///
+    /// For example, clicking in the top left corner of an element will result in element coordinates (0., 0.)
+    pub fn element(&self) -> ElementPoint {
+        self.element
+    }
+    /// Coordinates relative to the entire document. This includes any portion of the document not currently visible.
+    ///
+    /// For example, if the page is scrolled 200 pixels to the right and 300 pixels down, clicking in the top left corner of the viewport would result in page coordinates (200., 300.)
+    pub fn page(&self) -> PagePoint {
+        self.page
+    }
+}

+ 120 - 0
packages/html/src/input_data.rs

@@ -0,0 +1,120 @@
+//! Data structures representing user input, such as modifier keys and mouse buttons
+use enumset::{EnumSet, EnumSetType};
+
+/// A re-export of keyboard_types
+pub use keyboard_types;
+
+/// A mouse button type (such as Primary/Secondary)
+// note: EnumSetType also derives Copy and Clone for some reason
+#[derive(EnumSetType, Debug)]
+#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
+pub enum MouseButton {
+    /// Primary button (typically the left button)
+    Primary,
+    /// Secondary button (typically the right button)
+    Secondary,
+    /// Auxiliary button (typically the middle button)
+    Auxiliary,
+    /// Fourth button (typically the "Browser Back" button)
+    Fourth,
+    /// Fifth button (typically the "Browser Forward" button)
+    Fifth,
+    /// A button with an unknown code
+    Unknown,
+}
+
+impl MouseButton {
+    /// Constructs a MouseButton for the specified button code
+    ///
+    /// E.g. 0 => Primary; 1 => Auxiliary
+    ///
+    /// Unknown codes get mapped to MouseButton::Unknown.
+    pub fn from_web_code(code: i16) -> Self {
+        match code {
+            0 => MouseButton::Primary,
+            // not a typo; auxiliary and secondary are swapped unlike in the `buttons` field.
+            // https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/button
+            1 => MouseButton::Auxiliary,
+            2 => MouseButton::Secondary,
+            3 => MouseButton::Fourth,
+            4 => MouseButton::Fifth,
+            _ => MouseButton::Unknown,
+        }
+    }
+
+    /// Converts MouseButton into the corresponding button code
+    ///
+    /// MouseButton::Unknown will get mapped to -1
+    pub fn into_web_code(self) -> i16 {
+        match self {
+            MouseButton::Primary => 0,
+            // not a typo; auxiliary and secondary are swapped unlike in the `buttons` field.
+            // https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/button
+            MouseButton::Auxiliary => 1,
+            MouseButton::Secondary => 2,
+            MouseButton::Fourth => 3,
+            MouseButton::Fifth => 4,
+            MouseButton::Unknown => -1,
+        }
+    }
+}
+
+/// A set of mouse buttons
+pub type MouseButtonSet = EnumSet<MouseButton>;
+
+pub fn decode_mouse_button_set(code: u16) -> MouseButtonSet {
+    let mut set = EnumSet::empty();
+
+    // https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/buttons
+    #[allow(deprecated)]
+    {
+        if code & 0b1 != 0 {
+            set |= MouseButton::Primary;
+        }
+        if code & 0b10 != 0 {
+            set |= MouseButton::Secondary;
+        }
+        if code & 0b100 != 0 {
+            set |= MouseButton::Auxiliary;
+        }
+        if code & 0b1000 != 0 {
+            set |= MouseButton::Fourth;
+        }
+        if code & 0b10000 != 0 {
+            set |= MouseButton::Fifth;
+        }
+        if code & (!0b11111) != 0 {
+            set |= MouseButton::Unknown;
+        }
+    }
+
+    set
+}
+
+pub fn encode_mouse_button_set(set: MouseButtonSet) -> u16 {
+    let mut code = 0;
+
+    // https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/buttons
+    {
+        if set.contains(MouseButton::Primary) {
+            code |= 0b1;
+        }
+        if set.contains(MouseButton::Secondary) {
+            code |= 0b10;
+        }
+        if set.contains(MouseButton::Auxiliary) {
+            code |= 0b100;
+        }
+        if set.contains(MouseButton::Fourth) {
+            code |= 0b1000;
+        }
+        if set.contains(MouseButton::Fifth) {
+            code |= 0b10000;
+        }
+        if set.contains(MouseButton::Unknown) {
+            code |= 0b100000;
+        }
+    }
+
+    code
+}

+ 4 - 0
packages/html/src/lib.rs

@@ -15,7 +15,11 @@
 
 mod elements;
 mod events;
+pub mod geometry;
 mod global_attributes;
+pub mod input_data;
+#[cfg(feature = "wasm-bind")]
+mod web_sys_bind;
 
 pub use elements::*;
 pub use events::*;

+ 176 - 0
packages/html/src/web_sys_bind/events.rs

@@ -0,0 +1,176 @@
+use crate::geometry::{ClientPoint, Coordinates, ElementPoint, PagePoint, ScreenPoint};
+use crate::input_data::{decode_mouse_button_set, MouseButton};
+use crate::on::{
+    AnimationData, CompositionData, KeyboardData, MouseData, PointerData, TouchData,
+    TransitionData, WheelData,
+};
+use crate::KeyCode;
+use keyboard_types::Modifiers;
+use wasm_bindgen::JsCast;
+use web_sys::{
+    AnimationEvent, CompositionEvent, Event, KeyboardEvent, MouseEvent, PointerEvent, TouchEvent,
+    TransitionEvent, WheelEvent,
+};
+
+macro_rules! uncheck_convert {
+    ($t:ty, $d:ty) => {
+        impl From<Event> for $d {
+            #[inline]
+            fn from(e: Event) -> Self {
+                let e: $t = e.unchecked_into();
+                Self::from(&e)
+            }
+        }
+
+        impl From<&Event> for $d {
+            #[inline]
+            fn from(e: &Event) -> Self {
+                let e: &$t = e.unchecked_ref();
+                Self::from(e)
+            }
+        }
+    };
+    ($($t:ty => $d:ty),+ $(,)?) => {
+        $(uncheck_convert!($t, $d);)+
+    };
+}
+
+uncheck_convert![
+    CompositionEvent => CompositionData,
+    KeyboardEvent    => KeyboardData,
+    MouseEvent       => MouseData,
+    TouchEvent       => TouchData,
+    PointerEvent     => PointerData,
+    WheelEvent       => WheelData,
+    AnimationEvent   => AnimationData,
+    TransitionEvent  => TransitionData,
+];
+
+impl From<&CompositionEvent> for CompositionData {
+    fn from(e: &CompositionEvent) -> Self {
+        Self {
+            data: e.data().unwrap_or_default(),
+        }
+    }
+}
+
+impl From<&KeyboardEvent> for KeyboardData {
+    fn from(e: &KeyboardEvent) -> Self {
+        Self {
+            alt_key: e.alt_key(),
+            char_code: e.char_code(),
+            key: e.key(),
+            key_code: KeyCode::from_raw_code(e.key_code() as u8),
+            ctrl_key: e.ctrl_key(),
+            locale: "not implemented".to_string(),
+            location: e.location() as usize,
+            meta_key: e.meta_key(),
+            repeat: e.repeat(),
+            shift_key: e.shift_key(),
+            which: e.which() as usize,
+        }
+    }
+}
+
+impl From<&MouseEvent> for MouseData {
+    fn from(e: &MouseEvent) -> Self {
+        let mut modifiers = Modifiers::empty();
+
+        if e.alt_key() {
+            modifiers.insert(Modifiers::ALT);
+        }
+        if e.ctrl_key() {
+            modifiers.insert(Modifiers::CONTROL);
+        }
+        if e.meta_key() {
+            modifiers.insert(Modifiers::META);
+        }
+        if e.shift_key() {
+            modifiers.insert(Modifiers::SHIFT);
+        }
+
+        MouseData::new(
+            Coordinates::new(
+                ScreenPoint::new(e.screen_x().into(), e.screen_y().into()),
+                ClientPoint::new(e.client_x().into(), e.client_y().into()),
+                ElementPoint::new(e.offset_x().into(), e.offset_y().into()),
+                PagePoint::new(e.page_x().into(), e.page_y().into()),
+            ),
+            Some(MouseButton::from_web_code(e.button().into())),
+            decode_mouse_button_set(e.buttons()),
+            modifiers,
+        )
+    }
+}
+
+impl From<&TouchEvent> for TouchData {
+    fn from(e: &TouchEvent) -> Self {
+        Self {
+            alt_key: e.alt_key(),
+            ctrl_key: e.ctrl_key(),
+            meta_key: e.meta_key(),
+            shift_key: e.shift_key(),
+        }
+    }
+}
+
+impl From<&PointerEvent> for PointerData {
+    fn from(e: &PointerEvent) -> Self {
+        Self {
+            alt_key: e.alt_key(),
+            button: e.button(),
+            buttons: e.buttons(),
+            client_x: e.client_x(),
+            client_y: e.client_y(),
+            ctrl_key: e.ctrl_key(),
+            meta_key: e.meta_key(),
+            page_x: e.page_x(),
+            page_y: e.page_y(),
+            screen_x: e.screen_x(),
+            screen_y: e.screen_y(),
+            shift_key: e.shift_key(),
+            pointer_id: e.pointer_id(),
+            width: e.width(),
+            height: e.height(),
+            pressure: e.pressure(),
+            tangential_pressure: e.tangential_pressure(),
+            tilt_x: e.tilt_x(),
+            tilt_y: e.tilt_y(),
+            twist: e.twist(),
+            pointer_type: e.pointer_type(),
+            is_primary: e.is_primary(),
+            // get_modifier_state: evt.get_modifier_state(),
+        }
+    }
+}
+
+impl From<&WheelEvent> for WheelData {
+    fn from(e: &WheelEvent) -> Self {
+        Self {
+            delta_x: e.delta_x(),
+            delta_y: e.delta_y(),
+            delta_z: e.delta_z(),
+            delta_mode: e.delta_mode(),
+        }
+    }
+}
+
+impl From<&AnimationEvent> for AnimationData {
+    fn from(e: &AnimationEvent) -> Self {
+        Self {
+            elapsed_time: e.elapsed_time(),
+            animation_name: e.animation_name(),
+            pseudo_element: e.pseudo_element(),
+        }
+    }
+}
+
+impl From<&TransitionEvent> for TransitionData {
+    fn from(e: &TransitionEvent) -> Self {
+        Self {
+            elapsed_time: e.elapsed_time(),
+            property_name: e.property_name(),
+            pseudo_element: e.pseudo_element(),
+        }
+    }
+}

+ 1 - 0
packages/html/src/web_sys_bind/mod.rs

@@ -0,0 +1 @@
+mod events;

+ 1 - 1
packages/interpreter/Cargo.toml

@@ -1,6 +1,6 @@
 [package]
 name = "dioxus-interpreter-js"
-version = "0.2.0"
+version = "0.2.1"
 edition = "2018"
 authors = ["Jonathan Kelley"]
 description = "JS Intepreter for Dioxus - a concurrent renderer-agnostic Virtual DOM for interactive user experiences"

+ 4 - 0
packages/interpreter/src/interpreter.js

@@ -409,6 +409,8 @@ export function serialize_event(event) {
         clientY,
         ctrlKey,
         metaKey,
+        offsetX,
+        offsetY,
         pageX,
         pageY,
         screenX,
@@ -423,6 +425,8 @@ export function serialize_event(event) {
         client_y: clientY,
         ctrl_key: ctrlKey,
         meta_key: metaKey,
+        offset_x: offsetX,
+        offset_y: offsetY,
         page_x: pageX,
         page_y: pageY,
         screen_x: screenX,

+ 6 - 3
packages/liveview/Cargo.toml

@@ -6,6 +6,9 @@ repository = "https://github.com/DioxusLabs/dioxus/"
 homepage = "https://dioxuslabs.com"
 documentation = "https://dioxuslabs.com"
 keywords = ["dom", "ui", "gui", "react", "wasm"]
+description = "Build server-side apps with Dioxus"
+license = "MIT/Apache-2.0"
+
 
 
 # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
@@ -23,8 +26,8 @@ serde = { version = "1.0.136", features = ["derive"] }
 serde_json = "1.0.79"
 tokio-util = { version = "0.7.0", features = ["full"] }
 
-dioxus-html = { path = "../html", features = ["serialize"] }
-dioxus-core = { path = "../core", features = ["serialize"] }
+dioxus-html = { path = "../html", features = ["serialize"], version = "^0.2.1" }
+dioxus-core = { path = "../core", features = ["serialize"], version = "^0.2.1" }
 
 
 # warp
@@ -42,4 +45,4 @@ axum = { version = "0.5.1", features = ["ws"] }
 tower = "0.4.12"
 
 [features]
-default = []
+default = []

+ 16 - 4
packages/liveview/src/adapters/axum_adapter.rs

@@ -9,21 +9,33 @@ use tokio::sync::mpsc;
 use tokio_stream::wrappers::UnboundedReceiverStream;
 use tokio_util::task::LocalPoolHandle;
 
-#[cfg(feature = "axum")]
 impl crate::Liveview {
     pub async fn upgrade(&self, ws: WebSocket, app: fn(Scope) -> Element) {
-        connect(ws, self.pool.clone(), app).await;
+        connect(ws, self.pool.clone(), app, ()).await;
+    }
+    pub async fn upgrade_with_props<T>(&self, ws: WebSocket, app: fn(Scope<T>) -> Element, props: T)
+    where
+        T: Send + Sync + 'static,
+    {
+        connect(ws, self.pool.clone(), app, props).await;
     }
 }
 
-pub async fn connect(socket: WebSocket, pool: LocalPoolHandle, app: fn(Scope) -> Element) {
+pub async fn connect<T>(
+    socket: WebSocket,
+    pool: LocalPoolHandle,
+    app: fn(Scope<T>) -> Element,
+    props: T,
+) where
+    T: Send + Sync + 'static,
+{
     let (mut user_ws_tx, mut user_ws_rx) = socket.split();
     let (event_tx, event_rx) = mpsc::unbounded_channel();
     let (edits_tx, edits_rx) = mpsc::unbounded_channel();
     let mut edits_rx = UnboundedReceiverStream::new(edits_rx);
     let mut event_rx = UnboundedReceiverStream::new(event_rx);
     let vdom_fut = pool.clone().spawn_pinned(move || async move {
-        let mut vdom = VirtualDom::new(app);
+        let mut vdom = VirtualDom::new_with_props(app, props);
         let edits = vdom.rebuild();
         let serialized = serde_json::to_string(&edits.edits).unwrap();
         edits_tx.send(serialized).unwrap();

+ 20 - 4
packages/liveview/src/adapters/warp_adapter.rs

@@ -6,14 +6,30 @@ use tokio_stream::wrappers::UnboundedReceiverStream;
 use tokio_util::task::LocalPoolHandle;
 use warp::ws::{Message, WebSocket};
 
-#[cfg(feature = "warp")]
 impl crate::Liveview {
     pub async fn upgrade(&self, ws: warp::ws::WebSocket, app: fn(Scope) -> Element) {
-        connect(ws, self.pool.clone(), app).await;
+        connect(ws, self.pool.clone(), app, ()).await;
+    }
+    pub async fn upgrade_with_props<T>(
+        &self,
+        ws: warp::ws::WebSocket,
+        app: fn(Scope<T>) -> Element,
+        props: T,
+    ) where
+        T: Send + Sync + 'static,
+    {
+        connect(ws, self.pool.clone(), app, props).await;
     }
 }
 
-pub async fn connect(ws: WebSocket, pool: LocalPoolHandle, app: fn(Scope) -> Element) {
+pub async fn connect<T>(
+    ws: WebSocket,
+    pool: LocalPoolHandle,
+    app: fn(Scope<T>) -> Element,
+    props: T,
+) where
+    T: Send + Sync + 'static,
+{
     // Use a counter to assign a new unique ID for this user.
 
     // Split the socket into a sender and receive of messages.
@@ -26,7 +42,7 @@ pub async fn connect(ws: WebSocket, pool: LocalPoolHandle, app: fn(Scope) -> Ele
     let mut event_rx = UnboundedReceiverStream::new(event_rx);
 
     let vdom_fut = pool.spawn_pinned(move || async move {
-        let mut vdom = VirtualDom::new(app);
+        let mut vdom = VirtualDom::new_with_props(app, props);
 
         let edits = vdom.rebuild();
 

+ 4 - 0
packages/liveview/src/interpreter.js

@@ -418,6 +418,8 @@ function serialize_event(event) {
         clientY,
         ctrlKey,
         metaKey,
+        offsetX,
+        offsetY,
         pageX,
         pageY,
         screenX,
@@ -432,6 +434,8 @@ function serialize_event(event) {
         client_y: clientY,
         ctrl_key: ctrlKey,
         meta_key: metaKey,
+        offset_x: offsetX,
+        offset_y: offsetY,
         page_x: pageX,
         page_y: pageY,
         screen_x: screenX,

+ 1 - 1
packages/mobile/Cargo.toml

@@ -12,7 +12,7 @@ license = "MIT/Apache-2.0"
 # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
 
 [dependencies]
-dioxus-desktop = { path = "../desktop", version = "^0.2.0" }
+dioxus-desktop = { path = "../desktop", version = "^0.2.3" }
 
 [lib]
 doctest = false

+ 29 - 0
packages/native-core-macro/Cargo.toml

@@ -0,0 +1,29 @@
+[package]
+name = "dioxus-native-core-macro"
+version = "0.2.0"
+edition = "2021"
+description = "Build natively rendered apps with Dioxus"
+license = "MIT/Apache-2.0"
+repository = "https://github.com/DioxusLabs/dioxus/"
+homepage = "https://dioxuslabs.com"
+documentation = "https://dioxuslabs.com"
+keywords = ["dom", "ui", "gui", "react", "wasm"]
+
+
+[lib]
+proc-macro = true
+
+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+[dependencies]
+syn = { version = "1.0.11", features = ["extra-traits"] }
+quote = "1.0"
+dioxus-native-core = { path = "../native-core", version = "^0.2.0" }
+
+[dev-dependencies]
+dioxus-core = { path = "../core", version = "^0.2.1" }
+dioxus-html = { path = "../html", version = "^0.2.1" }
+dioxus-core-macro = { path = "../core-macro", version = "^0.2.1" }
+
+smallvec = "1.6"
+fxhash = "0.2"
+anymap = "0.12.1"

+ 703 - 0
packages/native-core-macro/src/lib.rs

@@ -0,0 +1,703 @@
+extern crate proc_macro;
+
+mod sorted_slice;
+
+use dioxus_native_core::state::MemberId;
+use proc_macro::TokenStream;
+use quote::format_ident;
+use quote::{quote, ToTokens, __private::Span};
+use sorted_slice::StrSlice;
+use syn::{
+    self,
+    parse::{Parse, ParseStream, Result},
+    parse_macro_input, parse_quote, Error, Field, Ident, Token, Type,
+};
+
+#[proc_macro]
+pub fn sorted_str_slice(input: TokenStream) -> TokenStream {
+    let slice: StrSlice = parse_macro_input!(input as StrSlice);
+    let strings = slice.map.values();
+    quote!([#(#strings, )*]).into()
+}
+
+#[derive(PartialEq, Debug, Clone)]
+enum DepKind {
+    Node,
+    Child,
+    Parent,
+}
+
+#[proc_macro_derive(
+    State,
+    attributes(node_dep_state, child_dep_state, parent_dep_state, state)
+)]
+pub fn state_macro_derive(input: TokenStream) -> TokenStream {
+    let ast = syn::parse(input).unwrap();
+    impl_derive_macro(&ast)
+}
+
+fn impl_derive_macro(ast: &syn::DeriveInput) -> TokenStream {
+    let type_name = &ast.ident;
+    let fields: Vec<_> = match &ast.data {
+        syn::Data::Struct(data) => match &data.fields {
+            syn::Fields::Named(e) => &e.named,
+            syn::Fields::Unnamed(_) => todo!("unnamed fields"),
+            syn::Fields::Unit => todo!("unit structs"),
+        }
+        .iter()
+        .collect(),
+        _ => unimplemented!(),
+    };
+    let strct = Struct::new(type_name.clone(), &fields);
+    match StateStruct::parse(&fields, &strct) {
+        Ok(state_strct) => {
+            let node_dep_state_fields = state_strct
+                .state_members
+                .iter()
+                .filter(|f| f.dep_kind == DepKind::Node)
+                .map(|f| f.reduce_self());
+            let child_dep_state_fields = state_strct
+                .state_members
+                .iter()
+                .filter(|f| f.dep_kind == DepKind::Child)
+                .map(|f| f.reduce_self());
+            let parent_dep_state_fields = state_strct
+                .state_members
+                .iter()
+                .filter(|f| f.dep_kind == DepKind::Parent)
+                .map(|f| f.reduce_self());
+
+            let node_iter = state_strct
+                .state_members
+                .iter()
+                .filter(|m| m.dep_kind == DepKind::Node);
+            let node_ids = node_iter.clone().map(|m| m.member_id.0);
+            let node_ids_clone = node_ids.clone();
+            let node_types = node_iter.map(|f| &f.mem.ty);
+
+            let child_iter = state_strct
+                .state_members
+                .iter()
+                .filter(|m| m.dep_kind == DepKind::Child);
+            let child_ids = child_iter.clone().map(|m| m.member_id.0);
+            let child_ids_clone = child_ids.clone();
+            let child_types = child_iter.map(|f| &f.mem.ty);
+
+            let parent_iter = state_strct
+                .state_members
+                .iter()
+                .filter(|m| m.dep_kind == DepKind::Parent);
+            let parent_ids = parent_iter.clone().map(|m| m.member_id.0);
+            let parent_ids_clone = parent_ids.clone();
+            let parent_types = parent_iter.map(|f| &f.mem.ty);
+
+            let type_name_str = type_name.to_string();
+
+            let child_states = &state_strct.child_states;
+
+            let member_size = state_strct.state_members.len();
+
+            let child_state_ty = child_states.iter().map(|m| &m.ty);
+            let child_state_idents: Vec<_> = child_states.iter().map(|m| &m.ident).collect();
+            let sum_const_declarations = child_state_ty.clone().enumerate().map(|(i, ty)| {
+                let ident = format_ident!("__{}_SUM_{}", i, type_name.to_string());
+                let ident_minus = format_ident!("__{}_SUM_{}_minus", i, type_name.to_string());
+                if i == 0 {
+                    quote!(const #ident_minus: usize = #member_size + #ty::SIZE - 1;
+                    const #ident: usize = #member_size + #ty::SIZE;)
+                } else {
+                    let prev_ident = format_ident!("__{}_SUM_{}", i - 1, type_name.to_string());
+                    quote!(const #ident_minus: usize = #prev_ident + #ty::SIZE - 1;
+                    const #ident: usize = #prev_ident + #ty::SIZE;)
+                }
+            });
+            let sum_idents: Vec<_> = std::iter::once(quote!(#member_size))
+                .chain((0..child_states.len()).map(|i| {
+                    let ident = format_ident!("__{}_SUM_{}", i, type_name.to_string());
+                    quote!(#ident)
+                }))
+                .collect();
+
+            let child_state_ranges: Vec<_> = (0..child_state_ty.len())
+                .map(|i| {
+                    let current = format_ident!("__{}_SUM_{}_minus", i, type_name.to_string());
+                    let previous = if i == 0 {
+                        quote!(#member_size)
+                    } else {
+                        let ident = format_ident!("__{}_SUM_{}", i - 1, type_name.to_string());
+                        quote!(#ident)
+                    };
+                    quote!(#previous..=#current)
+                })
+                .collect();
+
+            let gen = quote! {
+                #(
+                    #sum_const_declarations
+                )*
+                impl State for #type_name{
+                    const SIZE: usize = #member_size #( + #child_state_ty::SIZE)*;
+
+                    fn update_node_dep_state<'a>(
+                        &'a mut self,
+                        ty: dioxus_native_core::state::MemberId,
+                        node: &'a dioxus_core::VNode<'a>,
+                        vdom: &'a dioxus_core::VirtualDom,
+                        ctx: &anymap::AnyMap,
+                    ) -> Option<dioxus_native_core::state::NodeStatesChanged>{
+                        use dioxus_native_core::state::NodeDepState as _;
+                        use dioxus_native_core::state::State as _;
+                        match ty.0{
+                            #(
+                                #node_ids => #node_dep_state_fields,
+                            )*
+                            #(
+                                #child_state_ranges => {
+                                    self.#child_state_idents.update_node_dep_state(
+                                        ty - #sum_idents,
+                                        node,
+                                        vdom,
+                                        ctx,
+                                    ).map(|mut changed|{
+                                        for id in &mut changed.node_dep{
+                                            *id += #sum_idents;
+                                        }
+                                        changed
+                                    })
+                                }
+                            )*
+                            _ => panic!("{:?} not in {}", ty, #type_name_str),
+                        }
+                    }
+
+                    fn update_parent_dep_state<'a>(
+                        &'a mut self,
+                        ty: dioxus_native_core::state::MemberId,
+                        node: &'a dioxus_core::VNode<'a>,
+                        vdom: &'a dioxus_core::VirtualDom,
+                        parent: Option<&Self>,
+                        ctx: &anymap::AnyMap,
+                    ) -> Option<dioxus_native_core::state::ParentStatesChanged>{
+                        use dioxus_native_core::state::ParentDepState as _;
+                        match ty.0{
+                            #(
+                                #parent_ids => #parent_dep_state_fields,
+                            )*
+                            #(
+                                #child_state_ranges => {
+                                    self.#child_state_idents.update_parent_dep_state(
+                                        ty - #sum_idents,
+                                        node,
+                                        vdom,
+                                        parent.map(|p| &p.#child_state_idents),
+                                        ctx,
+                                    ).map(|mut changed|{
+                                        for id in &mut changed.node_dep{
+                                            *id += #sum_idents;
+                                        }
+                                        for id in &mut changed.parent_dep{
+                                            *id += #sum_idents;
+                                        }
+                                        changed
+                                    })
+                                }
+                            )*
+                            _ => panic!("{:?} not in {}", ty, #type_name_str),
+                        }
+                    }
+
+                    fn update_child_dep_state<'a>(
+                        &'a mut self,
+                        ty: dioxus_native_core::state::MemberId,
+                        node: &'a dioxus_core::VNode<'a>,
+                        vdom: &'a dioxus_core::VirtualDom,
+                        children: &Vec<&Self>,
+                        ctx: &anymap::AnyMap,
+                    ) -> Option<dioxus_native_core::state::ChildStatesChanged>{
+                        use dioxus_native_core::state::ChildDepState as _;
+                        match ty.0{
+                            #(
+                                #child_ids => #child_dep_state_fields,
+                            )*
+                            #(
+                                #child_state_ranges => {
+                                    self.#child_state_idents.update_child_dep_state(
+                                        ty - #sum_idents,
+                                        node,
+                                        vdom,
+                                        &children.iter().map(|p| &p.#child_state_idents).collect(),
+                                        ctx,
+                                    ).map(|mut changed|{
+                                        for id in &mut changed.node_dep{
+                                            *id += #sum_idents;
+                                        }
+                                        for id in &mut changed.child_dep{
+                                            *id += #sum_idents;
+                                        }
+                                        changed
+                                    })
+                                }
+                            )*
+                            _ => panic!("{:?} not in {}", ty, #type_name_str),
+                        }
+                    }
+
+                    fn child_dep_types(&self, mask: &dioxus_native_core::node_ref::NodeMask) -> Vec<dioxus_native_core::state::MemberId>{
+                        let mut dep_types = Vec::new();
+                        #(if #child_types::NODE_MASK.overlaps(mask) {
+                            dep_types.push(dioxus_native_core::state::MemberId(#child_ids_clone));
+                        })*
+                        #(
+                            dep_types.extend(self.#child_state_idents.child_dep_types(mask).into_iter().map(|id| id + #sum_idents));
+                        )*
+                        dep_types
+                    }
+
+                    fn parent_dep_types(&self, mask: &dioxus_native_core::node_ref::NodeMask) -> Vec<dioxus_native_core::state::MemberId>{
+                        let mut dep_types = Vec::new();
+                        #(if #parent_types::NODE_MASK.overlaps(mask) {
+                            dep_types.push(dioxus_native_core::state::MemberId(#parent_ids_clone));
+                        })*
+                        #(
+                            dep_types.extend(self.#child_state_idents.parent_dep_types(mask).into_iter().map(|id| id + #sum_idents));
+                        )*
+                        dep_types
+                    }
+
+                    fn node_dep_types(&self, mask: &dioxus_native_core::node_ref::NodeMask) -> Vec<dioxus_native_core::state::MemberId>{
+                        let mut dep_types = Vec::new();
+                        #(if #node_types::NODE_MASK.overlaps(mask) {
+                            dep_types.push(dioxus_native_core::state::MemberId(#node_ids_clone));
+                        })*
+                        #(
+                            dep_types.extend(self.#child_state_idents.node_dep_types(mask).into_iter().map(|id| id + #sum_idents));
+                        )*
+                        dep_types
+                    }
+                }
+            };
+            gen.into()
+        }
+        Err(e) => e.into_compile_error().into(),
+    }
+}
+
+struct Struct {
+    name: Ident,
+    members: Vec<Member>,
+}
+
+impl Struct {
+    fn new(name: Ident, fields: &[&Field]) -> Self {
+        let members = fields.iter().filter_map(|f| Member::parse(f)).collect();
+        Self { name, members }
+    }
+}
+
+struct StateStruct<'a> {
+    state_members: Vec<StateMember<'a>>,
+    child_states: Vec<&'a Member>,
+}
+
+impl<'a> StateStruct<'a> {
+    fn parse(fields: &[&'a Field], strct: &'a Struct) -> Result<Self> {
+        let mut parse_err = Ok(());
+        let state_members = strct
+            .members
+            .iter()
+            .zip(fields.iter())
+            .filter_map(|(m, f)| match StateMember::parse(f, m, strct) {
+                Ok(m) => m,
+                Err(err) => {
+                    parse_err = Err(err);
+                    None
+                }
+            });
+
+        let child_states = strct
+            .members
+            .iter()
+            .zip(fields.iter())
+            .filter(|(_, f)| {
+                f.attrs.iter().any(|a| {
+                    a.path
+                        .get_ident()
+                        .filter(|i| i.to_string().as_str() == "state")
+                        .is_some()
+                })
+            })
+            .map(|(m, _)| m);
+
+        #[derive(Debug, Clone)]
+        struct DepNode<'a> {
+            state_mem: StateMember<'a>,
+            depandants: Vec<DepNode<'a>>,
+        }
+        impl<'a> DepNode<'a> {
+            fn new(state_mem: StateMember<'a>) -> Self {
+                Self {
+                    state_mem,
+                    depandants: Vec::new(),
+                }
+            }
+
+            /// flattens the node in pre order
+            fn flatten(self) -> Vec<StateMember<'a>> {
+                let DepNode {
+                    state_mem,
+                    depandants,
+                } = self;
+                let mut flat = vec![state_mem];
+                for d in depandants {
+                    flat.append(&mut d.flatten());
+                }
+                flat
+            }
+
+            fn set_ids(&mut self, current_id: &mut usize) {
+                self.state_mem.member_id = dioxus_native_core::state::MemberId(*current_id);
+                // if the node depends on itself, we need to add the dependency seperately
+                if let Some(dep) = self.state_mem.dep_mem {
+                    if dep == self.state_mem.mem {
+                        self.state_mem
+                            .dependants
+                            .push((MemberId(*current_id), self.state_mem.dep_kind.clone()));
+                    }
+                }
+                *current_id += 1;
+                for d in &mut self.depandants {
+                    self.state_mem
+                        .dependants
+                        .push((MemberId(*current_id), d.state_mem.dep_kind.clone()));
+                    d.set_ids(current_id);
+                }
+            }
+
+            fn contains_member(&self, member: &Member) -> bool {
+                if self.state_mem.mem == member {
+                    true
+                } else {
+                    self.depandants.iter().any(|d| d.contains_member(member))
+                }
+            }
+
+            // check if there are any mixed child/parent dependancies
+            fn check(&self) -> Option<Error> {
+                self.kind().err()
+            }
+
+            fn kind(&self) -> Result<&DepKind> {
+                fn reduce_kind<'a>(dk1: &'a DepKind, dk2: &'a DepKind) -> Result<&'a DepKind> {
+                    match (dk1, dk2) {
+                        (DepKind::Child, DepKind::Parent) | (DepKind::Parent, DepKind::Child) => {
+                            Err(Error::new(
+                                Span::call_site(),
+                                "There is a ChildDepState that depends on a ParentDepState",
+                            ))
+                        }
+                        // node dep state takes the lowest priority
+                        (DepKind::Node, important) | (important, DepKind::Node) => Ok(important),
+                        // they are the same
+                        (fst, _) => Ok(fst),
+                    }
+                }
+                reduce_kind(
+                    self.depandants
+                        .iter()
+                        .try_fold(&DepKind::Node, |dk1, dk2| reduce_kind(dk1, dk2.kind()?))?,
+                    &self.state_mem.dep_kind,
+                )
+            }
+
+            fn insert_dependant(&mut self, other: DepNode<'a>) -> bool {
+                let dep = other.state_mem.dep_mem.unwrap();
+                if self.contains_member(dep) {
+                    if self.state_mem.mem == dep {
+                        self.depandants.push(other);
+                        true
+                    } else {
+                        self.depandants
+                            .iter_mut()
+                            .find(|d| d.contains_member(dep))
+                            .unwrap()
+                            .insert_dependant(other)
+                    }
+                } else {
+                    false
+                }
+            }
+        }
+
+        // members need to be sorted so that members are updated after the members they depend on
+        let mut roots: Vec<DepNode> = vec![];
+        for m in state_members {
+            if let Some(dep) = m.dep_mem {
+                let root_depends_on = roots
+                    .iter()
+                    .filter_map(|m| m.state_mem.dep_mem)
+                    .any(|d| m.mem == d);
+
+                if let Some(r) = roots.iter_mut().find(|r| r.contains_member(dep)) {
+                    let new = DepNode::new(m);
+                    if root_depends_on {
+                        return Err(Error::new(
+                            new.state_mem.mem.ident.span(),
+                            format!("{} has a circular dependancy", new.state_mem.mem.ident),
+                        ));
+                    }
+                    // return Err(Error::new(new.state_mem.mem.ident.span(), "stuff"));
+                    r.insert_dependant(new);
+                    continue;
+                }
+            }
+            let mut new = DepNode::new(m);
+            let mut i = 0;
+            while i < roots.len() {
+                if roots[i].state_mem.dep_mem == Some(new.state_mem.mem) {
+                    let child = roots.remove(i);
+                    new.insert_dependant(child);
+                } else {
+                    i += 1;
+                }
+            }
+            roots.push(new);
+        }
+        parse_err?;
+        let mut current_id = 0;
+        for r in &mut roots {
+            r.set_ids(&mut current_id);
+        }
+        if let Some(err) = roots.iter().find_map(DepNode::check) {
+            Err(err)
+        } else {
+            let state_members: Vec<_> = roots
+                .into_iter()
+                .flat_map(|r| r.flatten().into_iter())
+                .collect();
+
+            Ok(Self {
+                state_members,
+                child_states: child_states.collect(),
+            })
+        }
+    }
+}
+
+struct Dependancy {
+    ctx_ty: Option<Type>,
+    dep: Option<Ident>,
+}
+
+impl Parse for Dependancy {
+    fn parse(input: ParseStream) -> Result<Self> {
+        let dep = input
+            .parse()
+            .ok()
+            .filter(|i: &Ident| format!("{}", i) != "NONE");
+        let comma: Option<Token![,]> = input.parse().ok();
+        let ctx_ty = input.parse().ok();
+        Ok(Self {
+            ctx_ty: comma.and(ctx_ty),
+            dep,
+        })
+    }
+}
+
+#[derive(PartialEq, Debug)]
+struct Member {
+    ty: Type,
+    ident: Ident,
+}
+
+impl Member {
+    fn parse(field: &Field) -> Option<Self> {
+        Some(Self {
+            ty: field.ty.clone(),
+            ident: field.ident.as_ref()?.clone(),
+        })
+    }
+}
+
+#[derive(Debug, Clone)]
+struct StateMember<'a> {
+    mem: &'a Member,
+    dep_kind: DepKind,
+    dep_mem: Option<&'a Member>,
+    ctx_ty: Option<Type>,
+    dependants: Vec<(dioxus_native_core::state::MemberId, DepKind)>,
+    // This is just the index of the final order of the struct it is used to communicate which parts need updated and what order to update them in.
+    member_id: dioxus_native_core::state::MemberId,
+}
+
+impl<'a> StateMember<'a> {
+    fn parse(
+        field: &Field,
+        mem: &'a Member,
+        parent: &'a Struct,
+    ) -> Result<Option<StateMember<'a>>> {
+        let mut err = Ok(());
+        let member = field.attrs.iter().find_map(|a| {
+            let dep_kind = a
+                .path
+                .get_ident()
+                .and_then(|i| match i.to_string().as_str() {
+                    "node_dep_state" => Some(DepKind::Node),
+                    "child_dep_state" => Some(DepKind::Child),
+                    "parent_dep_state" => Some(DepKind::Parent),
+                    _ => None,
+                })?;
+            match a.parse_args::<Dependancy>() {
+                Ok(dependancy) => {
+                    let dep_mem = if let Some(name) = &dependancy.dep {
+                        if let Some(found) = parent.members.iter().find(|m| &m.ident == name) {
+                            Some(found)
+                        } else {
+                            err = Err(Error::new(
+                                name.span(),
+                                format!("{} not found in {}", name, parent.name),
+                            ));
+                            None
+                        }
+                    } else {
+                        None
+                    };
+                    Some(Self {
+                        mem,
+                        dep_kind,
+                        dep_mem,
+                        ctx_ty: dependancy.ctx_ty,
+                        dependants: Vec::new(),
+                        member_id: dioxus_native_core::state::MemberId(0),
+                    })
+                }
+                Err(e) => {
+                    err = Err(e);
+                    None
+                }
+            }
+        });
+        err?;
+        Ok(member)
+    }
+
+    fn reduce_self(&self) -> quote::__private::TokenStream {
+        let ident = &self.mem.ident;
+        let get_ctx = if let Some(ctx_ty) = &self.ctx_ty {
+            if ctx_ty == &parse_quote!(()) {
+                quote! {&()}
+            } else {
+                let msg = ctx_ty.to_token_stream().to_string() + " not found in context";
+                quote! {ctx.get().expect(#msg)}
+            }
+        } else {
+            quote! {&()}
+        };
+        let states_changed = {
+            let child_dep = self
+                .dependants
+                .iter()
+                .filter(|(_, kind)| kind == &DepKind::Child)
+                .map(|(id, _)| id.0);
+            let parent_dep = self
+                .dependants
+                .iter()
+                .filter(|(_, kind)| kind == &DepKind::Parent)
+                .map(|(id, _)| id.0);
+            let node_dep = self
+                .dependants
+                .iter()
+                .filter(|(_, kind)| kind == &DepKind::Node)
+                .map(|(id, _)| id.0);
+            match self.dep_kind {
+                DepKind::Node => {
+                    quote! {
+                        dioxus_native_core::state::NodeStatesChanged{
+                            node_dep: vec![#(dioxus_native_core::state::MemberId(#node_dep), )*],
+                        }
+                    }
+                }
+                DepKind::Child => {
+                    quote! {
+                        dioxus_native_core::state::ChildStatesChanged{
+                            node_dep: vec![#(dioxus_native_core::state::MemberId(#node_dep), )*],
+                            child_dep: vec![#(dioxus_native_core::state::MemberId(#child_dep), )*],
+                        }
+                    }
+                }
+                DepKind::Parent => {
+                    quote! {
+                        dioxus_native_core::state::ParentStatesChanged{
+                            node_dep: vec![#(dioxus_native_core::state::MemberId(#node_dep), )*],
+                            parent_dep: vec![#(dioxus_native_core::state::MemberId(#parent_dep), )*],
+                        }
+                    }
+                }
+            }
+        };
+
+        let ty = &self.mem.ty;
+        let node_view =
+            quote!(dioxus_native_core::node_ref::NodeView::new(node, #ty::NODE_MASK, vdom));
+        if let Some(dep_ident) = &self.dep_mem.map(|m| &m.ident) {
+            match self.dep_kind {
+                DepKind::Node => {
+                    quote!({
+                        if self.#ident.reduce(#node_view, &self.#dep_ident, #get_ctx){
+                            Some(#states_changed)
+                        } else{
+                            None
+                        }
+                    })
+                }
+                DepKind::Child => {
+                    quote!({
+                        if self.#ident.reduce(#node_view, children.iter().map(|s| &s.#dep_ident), #get_ctx){
+                            Some(#states_changed)
+                        } else{
+                            None
+                        }
+                    })
+                }
+                DepKind::Parent => {
+                    quote!({
+                        if self.#ident.reduce(#node_view, parent.as_ref().map(|p| &p.#dep_ident), #get_ctx){
+                            Some(#states_changed)
+                        } else{
+                            None
+                        }
+                    })
+                }
+            }
+        } else {
+            match self.dep_kind {
+                DepKind::Node => {
+                    quote!({
+                        if self.#ident.reduce(#node_view, &(), #get_ctx){
+                            Some(#states_changed)
+                        } else{
+                            None
+                        }
+                    })
+                }
+                DepKind::Child => {
+                    quote!({
+                        if self.#ident.reduce(#node_view, std::iter::empty(), #get_ctx){
+                            Some(#states_changed)
+                        } else{
+                            None
+                        }
+                    })
+                }
+                DepKind::Parent => {
+                    quote!({
+                        if self.#ident.reduce(#node_view, Some(&()), #get_ctx){
+                            Some(#states_changed)
+                        } else{
+                            None
+                        }
+                    })
+                }
+            }
+        }
+    }
+}

+ 28 - 0
packages/native-core-macro/src/sorted_slice.rs

@@ -0,0 +1,28 @@
+extern crate proc_macro;
+
+use std::collections::BTreeMap;
+
+use syn::{
+    self, bracketed,
+    parse::{Parse, ParseStream, Result},
+    LitStr, Token,
+};
+pub struct StrSlice {
+    pub map: BTreeMap<String, LitStr>,
+}
+
+impl Parse for StrSlice {
+    fn parse(input: ParseStream) -> Result<Self> {
+        let content;
+        bracketed!(content in input);
+        let mut map = BTreeMap::new();
+        while let Ok(s) = content.parse::<LitStr>() {
+            map.insert(s.value(), s);
+            #[allow(unused_must_use)]
+            {
+                content.parse::<Token![,]>();
+            }
+        }
+        Ok(StrSlice { map })
+    }
+}

+ 158 - 0
packages/native-core-macro/tests/change_nodes.rs

@@ -0,0 +1,158 @@
+use dioxus_core::VNode;
+use dioxus_core::*;
+use dioxus_core_macro::*;
+use dioxus_html as dioxus_elements;
+use dioxus_native_core::real_dom::RealDom;
+use dioxus_native_core::state::State;
+use dioxus_native_core_macro::State;
+use std::cell::Cell;
+
+#[derive(State, Default, Clone)]
+struct Empty {}
+
+#[test]
+fn remove_node() {
+    #[allow(non_snake_case)]
+    fn Base(cx: Scope) -> Element {
+        rsx!(cx, div {})
+    }
+
+    let vdom = VirtualDom::new(Base);
+
+    let mutations = vdom.create_vnodes(rsx! {
+        div{
+            div{}
+        }
+    });
+
+    let mut dom: RealDom<Empty> = RealDom::new();
+
+    let _to_update = dom.apply_mutations(vec![mutations]);
+    let child_div = VElement {
+        id: Cell::new(Some(ElementId(2))),
+        key: None,
+        tag: "div",
+        namespace: None,
+        parent: Cell::new(Some(ElementId(1))),
+        listeners: &[],
+        attributes: &[],
+        children: &[],
+    };
+    let child_div_el = VNode::Element(&child_div);
+    let root_div = VElement {
+        id: Cell::new(Some(ElementId(1))),
+        key: None,
+        tag: "div",
+        namespace: None,
+        parent: Cell::new(Some(ElementId(0))),
+        listeners: &[],
+        attributes: &[],
+        children: &[child_div_el],
+    };
+
+    assert_eq!(dom.size(), 2);
+    assert!(&dom.contains_node(&VNode::Element(&root_div)));
+    assert_eq!(dom[1].height, 1);
+    assert_eq!(dom[2].height, 2);
+
+    let vdom = VirtualDom::new(Base);
+    let mutations = vdom.diff_lazynodes(
+        rsx! {
+            div{
+                div{}
+            }
+        },
+        rsx! {
+            div{}
+        },
+    );
+    dom.apply_mutations(vec![mutations.1]);
+
+    let new_root_div = VElement {
+        id: Cell::new(Some(ElementId(1))),
+        key: None,
+        tag: "div",
+        namespace: None,
+        parent: Cell::new(Some(ElementId(0))),
+        listeners: &[],
+        attributes: &[],
+        children: &[],
+    };
+
+    assert_eq!(dom.size(), 1);
+    assert!(&dom.contains_node(&VNode::Element(&new_root_div)));
+    assert_eq!(dom[1].height, 1);
+}
+
+#[test]
+fn add_node() {
+    #[allow(non_snake_case)]
+    fn Base(cx: Scope) -> Element {
+        rsx!(cx, div {})
+    }
+
+    let vdom = VirtualDom::new(Base);
+
+    let mutations = vdom.create_vnodes(rsx! {
+        div{}
+    });
+
+    let mut dom: RealDom<Empty> = RealDom::new();
+
+    let _to_update = dom.apply_mutations(vec![mutations]);
+
+    let root_div = VElement {
+        id: Cell::new(Some(ElementId(1))),
+        key: None,
+        tag: "div",
+        namespace: None,
+        parent: Cell::new(Some(ElementId(0))),
+        listeners: &[],
+        attributes: &[],
+        children: &[],
+    };
+
+    assert_eq!(dom.size(), 1);
+    assert!(&dom.contains_node(&VNode::Element(&root_div)));
+    assert_eq!(dom[1].height, 1);
+
+    let vdom = VirtualDom::new(Base);
+    let mutations = vdom.diff_lazynodes(
+        rsx! {
+            div{}
+        },
+        rsx! {
+            div{
+                p{}
+            }
+        },
+    );
+    dom.apply_mutations(vec![mutations.1]);
+
+    let child_div = VElement {
+        id: Cell::new(Some(ElementId(2))),
+        key: None,
+        tag: "p",
+        namespace: None,
+        parent: Cell::new(Some(ElementId(1))),
+        listeners: &[],
+        attributes: &[],
+        children: &[],
+    };
+    let child_div_el = VNode::Element(&child_div);
+    let new_root_div = VElement {
+        id: Cell::new(Some(ElementId(1))),
+        key: None,
+        tag: "div",
+        namespace: None,
+        parent: Cell::new(Some(ElementId(0))),
+        listeners: &[],
+        attributes: &[],
+        children: &[child_div_el],
+    };
+
+    assert_eq!(dom.size(), 2);
+    assert!(&dom.contains_node(&VNode::Element(&new_root_div)));
+    assert_eq!(dom[1].height, 1);
+    assert_eq!(dom[2].height, 2);
+}

+ 129 - 0
packages/native-core-macro/tests/initial_build.rs

@@ -0,0 +1,129 @@
+use std::cell::Cell;
+
+use dioxus_core::VNode;
+use dioxus_core::*;
+use dioxus_core_macro::*;
+use dioxus_html as dioxus_elements;
+use dioxus_native_core::real_dom::RealDom;
+use dioxus_native_core::state::State;
+use dioxus_native_core_macro::State;
+
+#[derive(Default, Clone, State)]
+struct Empty {}
+
+#[test]
+fn initial_build_simple() {
+    use std::cell::Cell;
+
+    #[allow(non_snake_case)]
+    fn Base(cx: Scope) -> Element {
+        rsx!(cx, div {})
+    }
+
+    let vdom = VirtualDom::new(Base);
+
+    let mutations = vdom.create_vnodes(rsx! {
+        div{}
+    });
+
+    let mut dom: RealDom<Empty> = RealDom::new();
+
+    let _to_update = dom.apply_mutations(vec![mutations]);
+    let root_div = VElement {
+        id: Cell::new(Some(ElementId(1))),
+        key: None,
+        tag: "div",
+        namespace: None,
+        parent: Cell::new(Some(ElementId(0))),
+        listeners: &[],
+        attributes: &[],
+        children: &[],
+    };
+    assert_eq!(dom.size(), 1);
+    assert!(&dom.contains_node(&VNode::Element(&root_div)));
+    assert_eq!(dom[1].height, 1);
+}
+
+#[test]
+fn initial_build_with_children() {
+    #[allow(non_snake_case)]
+    fn Base(cx: Scope) -> Element {
+        rsx!(cx, div {})
+    }
+
+    let vdom = VirtualDom::new(Base);
+
+    let mutations = vdom.create_vnodes(rsx! {
+        div{
+            div{
+                "hello"
+                p{
+                    "world"
+                }
+                "hello world"
+            }
+        }
+    });
+
+    let mut dom: RealDom<Empty> = RealDom::new();
+
+    let _to_update = dom.apply_mutations(vec![mutations]);
+    let first_text = VText {
+        id: Cell::new(Some(ElementId(3))),
+        text: "hello",
+        is_static: true,
+    };
+    let first_text_node = VNode::Text(&first_text);
+    let child_text = VText {
+        id: Cell::new(Some(ElementId(5))),
+        text: "world",
+        is_static: true,
+    };
+    let child_text_node = VNode::Text(&child_text);
+    let child_p_el = VElement {
+        id: Cell::new(Some(ElementId(4))),
+        key: None,
+        tag: "p",
+        namespace: None,
+        parent: Cell::new(Some(ElementId(2))),
+        listeners: &[],
+        attributes: &[],
+        children: &[child_text_node],
+    };
+    let child_p_node = VNode::Element(&child_p_el);
+    let second_text = VText {
+        id: Cell::new(Some(ElementId(6))),
+        text: "hello world",
+        is_static: true,
+    };
+    let second_text_node = VNode::Text(&second_text);
+    let child_div_el = VElement {
+        id: Cell::new(Some(ElementId(2))),
+        key: None,
+        tag: "div",
+        namespace: None,
+        parent: Cell::new(Some(ElementId(1))),
+        listeners: &[],
+        attributes: &[],
+        children: &[first_text_node, child_p_node, second_text_node],
+    };
+    let child_div_node = VNode::Element(&child_div_el);
+    let root_div = VElement {
+        id: Cell::new(Some(ElementId(1))),
+        key: None,
+        tag: "div",
+        namespace: None,
+        parent: Cell::new(Some(ElementId(0))),
+        listeners: &[],
+        attributes: &[],
+        children: &[child_div_node],
+    };
+    assert_eq!(dom.size(), 6);
+    assert!(&dom.contains_node(&VNode::Element(&root_div)));
+    assert_eq!(dom[1].height, 1);
+    assert_eq!(dom[2].height, 2);
+    assert_eq!(dom[3].height, 3);
+    assert_eq!(dom[4].height, 3);
+    assert_eq!(dom[5].height, 4);
+    assert_eq!(dom[6].height, 3);
+}

+ 534 - 0
packages/native-core-macro/tests/update_state.rs

@@ -0,0 +1,534 @@
+use anymap::AnyMap;
+use dioxus_core::AttributeValue;
+use dioxus_core::VNode;
+use dioxus_core::*;
+use dioxus_core_macro::*;
+use dioxus_html as dioxus_elements;
+use dioxus_native_core::node_ref::*;
+use dioxus_native_core::real_dom::*;
+use dioxus_native_core::state::{ChildDepState, NodeDepState, ParentDepState, State};
+use dioxus_native_core_macro::State;
+
+#[derive(Debug, Clone, Default, State)]
+struct CallCounterStatePart1 {
+    #[child_dep_state(child_counter)]
+    child_counter: ChildDepCallCounter,
+}
+
+#[derive(Debug, Clone, Default, State)]
+struct CallCounterStatePart2 {
+    #[parent_dep_state(parent_counter)]
+    parent_counter: ParentDepCallCounter,
+}
+
+#[derive(Debug, Clone, Default, State)]
+struct CallCounterStatePart3 {
+    #[node_dep_state()]
+    node_counter: NodeDepCallCounter,
+}
+
+#[derive(Debug, Clone, Default, State)]
+struct CallCounterState {
+    #[child_dep_state(child_counter)]
+    child_counter: ChildDepCallCounter,
+    #[state]
+    part2: CallCounterStatePart2,
+    #[parent_dep_state(parent_counter)]
+    parent_counter: ParentDepCallCounter,
+    #[state]
+    part1: CallCounterStatePart1,
+    #[state]
+    part3: CallCounterStatePart3,
+    #[node_dep_state()]
+    node_counter: NodeDepCallCounter,
+}
+
+#[derive(Debug, Clone, Default)]
+struct ChildDepCallCounter(u32);
+impl ChildDepState for ChildDepCallCounter {
+    type Ctx = ();
+    type DepState = Self;
+    const NODE_MASK: NodeMask = NodeMask::ALL;
+    fn reduce<'a>(
+        &mut self,
+        _: NodeView,
+        _: impl Iterator<Item = &'a Self::DepState>,
+        _: &Self::Ctx,
+    ) -> bool
+    where
+        Self::DepState: 'a,
+    {
+        self.0 += 1;
+        true
+    }
+}
+
+#[derive(Debug, Clone, Default)]
+struct ParentDepCallCounter(u32);
+impl ParentDepState for ParentDepCallCounter {
+    type Ctx = ();
+    type DepState = Self;
+    const NODE_MASK: NodeMask = NodeMask::ALL;
+    fn reduce(
+        &mut self,
+        _node: NodeView,
+        _parent: Option<&Self::DepState>,
+        _ctx: &Self::Ctx,
+    ) -> bool {
+        self.0 += 1;
+        true
+    }
+}
+
+#[derive(Debug, Clone, Default)]
+struct NodeDepCallCounter(u32);
+impl NodeDepState for NodeDepCallCounter {
+    type Ctx = ();
+    type DepState = ();
+    const NODE_MASK: NodeMask = NodeMask::ALL;
+    fn reduce(&mut self, _node: NodeView, _sibling: &Self::DepState, _ctx: &Self::Ctx) -> bool {
+        self.0 += 1;
+        true
+    }
+}
+
+#[derive(Debug, Clone, PartialEq, Default)]
+struct BubbledUpStateTester(Option<String>, Vec<Box<BubbledUpStateTester>>);
+impl ChildDepState for BubbledUpStateTester {
+    type Ctx = u32;
+    type DepState = Self;
+    const NODE_MASK: NodeMask = NodeMask::new().with_tag();
+    fn reduce<'a>(
+        &mut self,
+        node: NodeView,
+        children: impl Iterator<Item = &'a Self::DepState>,
+        ctx: &Self::Ctx,
+    ) -> bool
+    where
+        Self::DepState: 'a,
+    {
+        assert_eq!(*ctx, 42);
+        *self = BubbledUpStateTester(
+            node.tag().map(|s| s.to_string()),
+            children.into_iter().map(|c| Box::new(c.clone())).collect(),
+        );
+        true
+    }
+}
+
+#[derive(Debug, Clone, PartialEq, Default)]
+struct PushedDownStateTester(Option<String>, Option<Box<PushedDownStateTester>>);
+impl ParentDepState for PushedDownStateTester {
+    type Ctx = u32;
+    type DepState = Self;
+    const NODE_MASK: NodeMask = NodeMask::new().with_tag();
+    fn reduce(&mut self, node: NodeView, parent: Option<&Self::DepState>, ctx: &Self::Ctx) -> bool {
+        assert_eq!(*ctx, 42);
+        *self = PushedDownStateTester(
+            node.tag().map(|s| s.to_string()),
+            parent.map(|c| Box::new(c.clone())),
+        );
+        true
+    }
+}
+
+#[derive(Debug, Clone, PartialEq, Default)]
+struct NodeStateTester(Option<String>, Vec<(String, String)>);
+impl NodeDepState for NodeStateTester {
+    type Ctx = u32;
+    type DepState = ();
+    const NODE_MASK: NodeMask = NodeMask::new_with_attrs(AttributeMask::All).with_tag();
+    fn reduce(&mut self, node: NodeView, _sibling: &Self::DepState, ctx: &Self::Ctx) -> bool {
+        assert_eq!(*ctx, 42);
+        *self = NodeStateTester(
+            node.tag().map(|s| s.to_string()),
+            node.attributes()
+                .map(|a| (a.name.to_string(), a.value.to_string()))
+                .collect(),
+        );
+        true
+    }
+}
+
+#[derive(State, Clone, Default, Debug)]
+struct StateTester {
+    #[child_dep_state(bubbled, u32)]
+    bubbled: BubbledUpStateTester,
+    #[parent_dep_state(pushed, u32)]
+    pushed: PushedDownStateTester,
+    #[node_dep_state(NONE, u32)]
+    node: NodeStateTester,
+}
+
+#[test]
+fn state_initial() {
+    #[allow(non_snake_case)]
+    fn Base(cx: Scope) -> Element {
+        rsx!(cx, div {
+            p{}
+            h1{}
+        })
+    }
+
+    let vdom = VirtualDom::new(Base);
+
+    let mutations = vdom.create_vnodes(rsx! {
+        div {
+            p{
+                color: "red"
+            }
+            h1{}
+        }
+    });
+
+    let mut dom: RealDom<StateTester> = RealDom::new();
+
+    let nodes_updated = dom.apply_mutations(vec![mutations]);
+    let mut ctx = AnyMap::new();
+    ctx.insert(42u32);
+    let _to_rerender = dom.update_state(&vdom, nodes_updated, ctx);
+
+    let root_div = &dom[1];
+    assert_eq!(root_div.state.bubbled.0, Some("div".to_string()));
+    assert_eq!(
+        root_div.state.bubbled.1,
+        vec![
+            Box::new(BubbledUpStateTester(Some("p".to_string()), Vec::new())),
+            Box::new(BubbledUpStateTester(Some("h1".to_string()), Vec::new()))
+        ]
+    );
+    assert_eq!(root_div.state.pushed.0, Some("div".to_string()));
+    assert_eq!(
+        root_div.state.pushed.1,
+        Some(Box::new(PushedDownStateTester(None, None)))
+    );
+    assert_eq!(root_div.state.node.0, Some("div".to_string()));
+    assert_eq!(root_div.state.node.1, vec![]);
+
+    let child_p = &dom[2];
+    assert_eq!(child_p.state.bubbled.0, Some("p".to_string()));
+    assert_eq!(child_p.state.bubbled.1, Vec::new());
+    assert_eq!(child_p.state.pushed.0, Some("p".to_string()));
+    assert_eq!(
+        child_p.state.pushed.1,
+        Some(Box::new(PushedDownStateTester(
+            Some("div".to_string()),
+            Some(Box::new(PushedDownStateTester(None, None)))
+        )))
+    );
+    assert_eq!(child_p.state.node.0, Some("p".to_string()));
+    assert_eq!(
+        child_p.state.node.1,
+        vec![("color".to_string(), "red".to_string())]
+    );
+
+    let child_h1 = &dom[3];
+    assert_eq!(child_h1.state.bubbled.0, Some("h1".to_string()));
+    assert_eq!(child_h1.state.bubbled.1, Vec::new());
+    assert_eq!(child_h1.state.pushed.0, Some("h1".to_string()));
+    assert_eq!(
+        child_h1.state.pushed.1,
+        Some(Box::new(PushedDownStateTester(
+            Some("div".to_string()),
+            Some(Box::new(PushedDownStateTester(None, None)))
+        )))
+    );
+    assert_eq!(child_h1.state.node.0, Some("h1".to_string()));
+    assert_eq!(child_h1.state.node.1, vec![]);
+}
+
+#[test]
+fn state_reduce_initally_called_minimally() {
+    #[allow(non_snake_case)]
+    fn Base(cx: Scope) -> Element {
+        rsx!(cx, div {
+            div{
+                div{
+                    p{}
+                }
+                p{
+                    "hello"
+                }
+                div{
+                    h1{}
+                }
+                p{
+                    "world"
+                }
+            }
+        })
+    }
+
+    let vdom = VirtualDom::new(Base);
+
+    let mutations = vdom.create_vnodes(rsx! {
+        div {
+            div{
+                div{
+                    p{}
+                }
+                p{
+                    "hello"
+                }
+                div{
+                    h1{}
+                }
+                p{
+                    "world"
+                }
+            }
+        }
+    });
+
+    let mut dom: RealDom<CallCounterState> = RealDom::new();
+
+    let nodes_updated = dom.apply_mutations(vec![mutations]);
+    let _to_rerender = dom.update_state(&vdom, nodes_updated, AnyMap::new());
+
+    dom.traverse_depth_first(|n| {
+        assert_eq!(n.state.part1.child_counter.0, 1);
+        assert_eq!(n.state.child_counter.0, 1);
+        assert_eq!(n.state.part2.parent_counter.0, 1);
+        assert_eq!(n.state.parent_counter.0, 1);
+        assert_eq!(n.state.part3.node_counter.0, 1);
+        assert_eq!(n.state.node_counter.0, 1);
+    });
+}
+
+#[test]
+fn state_reduce_parent_called_minimally_on_update() {
+    #[allow(non_snake_case)]
+    fn Base(cx: Scope) -> Element {
+        rsx!(cx, div {
+            width: "100%",
+            div{
+                div{
+                    p{}
+                }
+                p{
+                    "hello"
+                }
+                div{
+                    h1{}
+                }
+                p{
+                    "world"
+                }
+            }
+        })
+    }
+
+    let vdom = VirtualDom::new(Base);
+
+    let mutations = vdom.create_vnodes(rsx! {
+        div {
+            width: "100%",
+            div{
+                div{
+                    p{}
+                }
+                p{
+                    "hello"
+                }
+                div{
+                    h1{}
+                }
+                p{
+                    "world"
+                }
+            }
+        }
+    });
+
+    let mut dom: RealDom<CallCounterState> = RealDom::new();
+
+    let nodes_updated = dom.apply_mutations(vec![mutations]);
+    let _to_rerender = dom.update_state(&vdom, nodes_updated, AnyMap::new());
+    let nodes_updated = dom.apply_mutations(vec![Mutations {
+        edits: vec![DomEdit::SetAttribute {
+            root: 1,
+            field: "width",
+            value: AttributeValue::Text("99%"),
+            ns: Some("style"),
+        }],
+        dirty_scopes: fxhash::FxHashSet::default(),
+        refs: Vec::new(),
+    }]);
+    let _to_rerender = dom.update_state(&vdom, nodes_updated, AnyMap::new());
+
+    dom.traverse_depth_first(|n| {
+        assert_eq!(n.state.part2.parent_counter.0, 2);
+        assert_eq!(n.state.parent_counter.0, 2);
+    });
+}
+
+#[test]
+fn state_reduce_child_called_minimally_on_update() {
+    #[allow(non_snake_case)]
+    fn Base(cx: Scope) -> Element {
+        rsx!(cx, div {
+            div{
+                div{
+                    p{
+                        width: "100%",
+                    }
+                }
+                p{
+                    "hello"
+                }
+                div{
+                    h1{}
+                }
+                p{
+                    "world"
+                }
+            }
+        })
+    }
+
+    let vdom = VirtualDom::new(Base);
+
+    let mutations = vdom.create_vnodes(rsx! {
+        div {
+            div{
+                div{
+                    p{
+                        width: "100%",
+                    }
+                }
+                p{
+                    "hello"
+                }
+                div{
+                    h1{}
+                }
+                p{
+                    "world"
+                }
+            }
+        }
+    });
+
+    let mut dom: RealDom<CallCounterState> = RealDom::new();
+
+    let nodes_updated = dom.apply_mutations(vec![mutations]);
+    let _to_rerender = dom.update_state(&vdom, nodes_updated, AnyMap::new());
+    let nodes_updated = dom.apply_mutations(vec![Mutations {
+        edits: vec![DomEdit::SetAttribute {
+            root: 4,
+            field: "width",
+            value: AttributeValue::Text("99%"),
+            ns: Some("style"),
+        }],
+        dirty_scopes: fxhash::FxHashSet::default(),
+        refs: Vec::new(),
+    }]);
+    let _to_rerender = dom.update_state(&vdom, nodes_updated, AnyMap::new());
+
+    dom.traverse_depth_first(|n| {
+        println!("{:?}", n);
+        assert_eq!(
+            n.state.part1.child_counter.0,
+            if n.id.0 > 4 { 1 } else { 2 }
+        );
+        assert_eq!(n.state.child_counter.0, if n.id.0 > 4 { 1 } else { 2 });
+    });
+}
+
+#[derive(Debug, Clone, Default, State)]
+struct UnorderedDependanciesState {
+    #[node_dep_state(c)]
+    b: BDepCallCounter,
+    #[node_dep_state()]
+    c: CDepCallCounter,
+    #[node_dep_state(b)]
+    a: ADepCallCounter,
+}
+
+#[derive(Debug, Clone, Default, PartialEq)]
+struct ADepCallCounter(usize, BDepCallCounter);
+impl NodeDepState for ADepCallCounter {
+    type Ctx = ();
+    type DepState = BDepCallCounter;
+    const NODE_MASK: NodeMask = NodeMask::NONE;
+    fn reduce(&mut self, _node: NodeView, sibling: &Self::DepState, _ctx: &Self::Ctx) -> bool {
+        self.0 += 1;
+        self.1 = sibling.clone();
+        true
+    }
+}
+
+#[derive(Debug, Clone, Default, PartialEq)]
+struct BDepCallCounter(usize, CDepCallCounter);
+impl NodeDepState for BDepCallCounter {
+    type Ctx = ();
+    type DepState = CDepCallCounter;
+    const NODE_MASK: NodeMask = NodeMask::NONE;
+    fn reduce(&mut self, _node: NodeView, sibling: &Self::DepState, _ctx: &Self::Ctx) -> bool {
+        self.0 += 1;
+        self.1 = sibling.clone();
+        true
+    }
+}
+
+#[derive(Debug, Clone, Default, PartialEq)]
+struct CDepCallCounter(usize);
+impl NodeDepState for CDepCallCounter {
+    type Ctx = ();
+    type DepState = ();
+    const NODE_MASK: NodeMask = NodeMask::ALL;
+    fn reduce(&mut self, _node: NodeView, _sibling: &Self::DepState, _ctx: &Self::Ctx) -> bool {
+        self.0 += 1;
+        true
+    }
+}
+
+#[test]
+fn dependancies_order_independant() {
+    #[allow(non_snake_case)]
+    fn Base(cx: Scope) -> Element {
+        rsx!(cx, div {
+            width: "100%",
+            p{
+                "hello"
+            }
+        })
+    }
+
+    let vdom = VirtualDom::new(Base);
+
+    let mutations = vdom.create_vnodes(rsx! {
+        div {
+            width: "100%",
+            p{
+                "hello"
+            }
+        }
+    });
+
+    let mut dom: RealDom<UnorderedDependanciesState> = RealDom::new();
+
+    let nodes_updated = dom.apply_mutations(vec![mutations]);
+    let _to_rerender = dom.update_state(&vdom, nodes_updated, AnyMap::new());
+
+    let c = CDepCallCounter(1);
+    let b = BDepCallCounter(1, c.clone());
+    let a = ADepCallCounter(1, b.clone());
+    dom.traverse_depth_first(|n| {
+        assert_eq!(&n.state.a, &a);
+        assert_eq!(&n.state.b, &b);
+        assert_eq!(&n.state.c, &c);
+    });
+}
+
+#[derive(Clone, Default, State)]
+struct DependanciesStateTest {
+    #[node_dep_state(c)]
+    b: BDepCallCounter,
+    #[node_dep_state()]
+    c: CDepCallCounter,
+    #[node_dep_state(b)]
+    a: ADepCallCounter,
+    #[state]
+    child: UnorderedDependanciesState,
+}

+ 24 - 0
packages/native-core/Cargo.toml

@@ -0,0 +1,24 @@
+[package]
+name = "dioxus-native-core"
+version = "0.2.0"
+edition = "2021"
+license = "MIT/Apache-2.0"
+repository = "https://github.com/DioxusLabs/dioxus/"
+homepage = "https://dioxuslabs.com"
+description = "Build natively rendered apps with Dioxus"
+documentation = "https://dioxuslabs.com"
+keywords = ["dom", "ui", "gui", "react", "wasm"]
+
+
+[dependencies]
+dioxus-core = { path = "../core", version = "^0.2.1" }
+dioxus-html = { path = "../html", version = "^0.2.1" }
+dioxus-core-macro = { path = "../core-macro", version = "^0.2.1" }
+
+stretch2 = "0.4.2"
+smallvec = "1.6"
+fxhash = "0.2"
+anymap = "0.12.1"
+
+[dev-dependencies]
+rand = "0.8.5"

+ 636 - 0
packages/native-core/src/layout_attributes.rs

@@ -0,0 +1,636 @@
+/*
+- [ ] pub display: Display,
+- [x] pub position_type: PositionType,  --> kinda, stretch doesnt support everything
+- [ ] pub direction: Direction,
+
+- [x] pub flex_direction: FlexDirection,
+- [x] pub flex_wrap: FlexWrap,
+- [x] pub flex_grow: f32,
+- [x] pub flex_shrink: f32,
+- [x] pub flex_basis: Dimension,
+
+- [x] pub overflow: Overflow, ---> kinda implemented... stretch doesnt have support for directional overflow
+
+- [x] pub align_items: AlignItems,
+- [x] pub align_self: AlignSelf,
+- [x] pub align_content: AlignContent,
+
+- [x] pub margin: Rect<Dimension>,
+- [x] pub padding: Rect<Dimension>,
+
+- [x] pub justify_content: JustifyContent,
+- [ ] pub position: Rect<Dimension>,
+- [x] pub border: Rect<Dimension>,
+
+- [ ] pub size: Size<Dimension>, ----> ??? seems to only be relevant for input?
+- [ ] pub min_size: Size<Dimension>,
+- [ ] pub max_size: Size<Dimension>,
+
+- [ ] pub aspect_ratio: Number,
+*/
+
+use stretch2::{prelude::*, style::PositionType};
+
+/// applies the entire html namespace defined in dioxus-html
+pub fn apply_layout_attributes(name: &str, value: &str, style: &mut Style) {
+    match name {
+        "align-content"
+        | "align-items"
+        | "align-self" => apply_align(name, value, style),
+
+        "animation"
+        | "animation-delay"
+        | "animation-direction"
+        | "animation-duration"
+        | "animation-fill-mode"
+        | "animation-iteration-count"
+        | "animation-name"
+        | "animation-play-state"
+        | "animation-timing-function" => apply_animation(name, value, style),
+
+        "backface-visibility" => {}
+
+        "border"
+        | "border-bottom"
+        | "border-bottom-color"
+        | "border-bottom-left-radius"
+        | "border-bottom-right-radius"
+        | "border-bottom-style"
+        | "border-bottom-width"
+        | "border-collapse"
+        | "border-color"
+        | "border-image"
+        | "border-image-outset"
+        | "border-image-repeat"
+        | "border-image-slice"
+        | "border-image-source"
+        | "border-image-width"
+        | "border-left"
+        | "border-left-color"
+        | "border-left-style"
+        | "border-left-width"
+        | "border-radius"
+        | "border-right"
+        | "border-right-color"
+        | "border-right-style"
+        | "border-right-width"
+        | "border-spacing"
+        | "border-style"
+        | "border-top"
+        | "border-top-color"
+        | "border-top-left-radius"
+        | "border-top-right-radius"
+        | "border-top-style"
+        | "border-top-width"
+        | "border-width" => apply_border(name, value, style),
+
+        "bottom" => {}
+        "box-shadow" => {}
+        "box-sizing" => {}
+        "caption-side" => {}
+        "clear" => {}
+        "clip" => {}
+
+        "column-count"
+        | "column-fill"
+        | "column-gap"
+        | "column-rule"
+        | "column-rule-color"
+        | "column-rule-style"
+        | "column-rule-width"
+        | "column-span"
+        // add column-width
+        | "column-width" => apply_column(name, value, style),
+
+        "columns" => {}
+
+        "content" => {}
+        "counter-increment" => {}
+        "counter-reset" => {}
+
+        "cursor" => {}
+        "direction" => {
+            match value {
+                "ltr" => style.direction = Direction::LTR,
+                "rtl" => style.direction = Direction::RTL,
+                _ => {}
+            }
+        }
+
+        "display" => apply_display(name, value, style),
+
+        "empty-cells" => {}
+
+        "flex"
+        | "flex-basis"
+        | "flex-direction"
+        | "flex-flow"
+        | "flex-grow"
+        | "flex-shrink"
+        | "flex-wrap" => apply_flex(name, value, style),
+
+        "float" => {}
+
+        "height" => {
+            if let Some(v) = parse_value(value){
+                style.size.height = match v {
+                    UnitSystem::Percent(v)=> Dimension::Percent(v/100.0),
+                    UnitSystem::Point(v)=> Dimension::Points(v),
+                };
+            }
+        }
+        "justify-content" => {
+            use JustifyContent::*;
+            style.justify_content = match value {
+                "flex-start" => FlexStart,
+                "flex-end" => FlexEnd,
+                "center" => Center,
+                "space-between" => SpaceBetween,
+                "space-around" => SpaceAround,
+                "space-evenly" => SpaceEvenly,
+                _ => FlexStart,
+            };
+        }
+        "left" => {}
+        "letter-spacing" => {}
+        "line-height" => {}
+
+        "list-style"
+        | "list-style-image"
+        | "list-style-position"
+        | "list-style-type" => {}
+
+        "margin"
+        | "margin-bottom"
+        | "margin-left"
+        | "margin-right"
+        | "margin-top" => apply_margin(name, value, style),
+
+        "max-height" => {}
+        "max-width" => {}
+        "min-height" => {}
+        "min-width" => {}
+
+        "opacity" => {}
+        "order" => {}
+        "outline" => {}
+
+        "outline-color"
+        | "outline-offset"
+        | "outline-style"
+        | "outline-width" => {}
+
+        "overflow"
+        | "overflow-x"
+        | "overflow-y" => apply_overflow(name, value, style),
+
+        "padding"
+        | "padding-bottom"
+        | "padding-left"
+        | "padding-right"
+        | "padding-top" => apply_padding(name, value, style),
+
+        "page-break-after"
+        | "page-break-before"
+        | "page-break-inside" => {}
+
+        "perspective"
+        | "perspective-origin" => {}
+
+        "position" => {
+            match value {
+                "static" => {}
+                "relative" => style.position_type = PositionType::Relative,
+                "fixed" => {}
+                "absolute" => style.position_type = PositionType::Absolute,
+                "sticky" => {}
+                _ => {}
+            }
+
+        }
+
+        "pointer-events" => {}
+
+        "quotes" => {}
+        "resize" => {}
+        "right" => {}
+        "tab-size" => {}
+        "table-layout" => {}
+
+        "top" => {}
+
+        "transform"
+        | "transform-origin"
+        | "transform-style" => apply_transform(name, value, style),
+
+        "transition"
+        | "transition-delay"
+        | "transition-duration"
+        | "transition-property"
+        | "transition-timing-function" => apply_transition(name, value, style),
+
+        "vertical-align" => {}
+        "visibility" => {}
+        "white-space" => {}
+        "width" => {
+            if let Some(v) = parse_value(value){
+                style.size.width = match v {
+                    UnitSystem::Percent(v)=> Dimension::Percent(v/100.0),
+                    UnitSystem::Point(v)=> Dimension::Points(v),
+                };
+            }
+        }
+        "word-break" => {}
+        "word-spacing" => {}
+        "word-wrap" => {}
+        "z-index" => {}
+        _ => {}
+    }
+}
+
+/// a relative or absolute size
+#[derive(Clone, Copy, PartialEq, Debug)]
+pub enum UnitSystem {
+    Percent(f32),
+    Point(f32),
+}
+
+impl From<UnitSystem> for Dimension {
+    fn from(other: UnitSystem) -> Dimension {
+        match other {
+            UnitSystem::Percent(v) => Dimension::Percent(v),
+            UnitSystem::Point(v) => Dimension::Points(v),
+        }
+    }
+}
+
+/// parse relative or absolute value
+pub fn parse_value(value: &str) -> Option<UnitSystem> {
+    if value.ends_with("px") {
+        if let Ok(px) = value.trim_end_matches("px").parse::<f32>() {
+            Some(UnitSystem::Point(px))
+        } else {
+            None
+        }
+    } else if value.ends_with('%') {
+        if let Ok(pct) = value.trim_end_matches('%').parse::<f32>() {
+            Some(UnitSystem::Percent(pct))
+        } else {
+            None
+        }
+    } else {
+        None
+    }
+}
+
+fn apply_overflow(name: &str, value: &str, style: &mut Style) {
+    match name {
+        // todo: add more overflow support to stretch2
+        "overflow" | "overflow-x" | "overflow-y" => {
+            style.overflow = match value {
+                "auto" => Overflow::Visible,
+                "hidden" => Overflow::Hidden,
+                "scroll" => Overflow::Scroll,
+                "visible" => Overflow::Visible,
+                _ => Overflow::Visible,
+            };
+        }
+        _ => {}
+    }
+}
+
+fn apply_display(_name: &str, value: &str, style: &mut Style) {
+    style.display = match value {
+        "flex" => Display::Flex,
+        "block" => Display::None,
+        _ => Display::Flex,
+    }
+
+    // TODO: there are way more variants
+    // stretch needs to be updated to handle them
+    //
+    // "block" => Display::Block,
+    // "inline" => Display::Inline,
+    // "inline-block" => Display::InlineBlock,
+    // "inline-table" => Display::InlineTable,
+    // "list-item" => Display::ListItem,
+    // "run-in" => Display::RunIn,
+    // "table" => Display::Table,
+    // "table-caption" => Display::TableCaption,
+    // "table-cell" => Display::TableCell,
+    // "table-column" => Display::TableColumn,
+    // "table-column-group" => Display::TableColumnGroup,
+    // "table-footer-group" => Display::TableFooterGroup,
+    // "table-header-group" => Display::TableHeaderGroup,
+    // "table-row" => Display::TableRow,
+    // "table-row-group" => Display::TableRowGroup,
+    // "none" => Display::None,
+    // _ => Display::Inline,
+}
+
+fn apply_border(name: &str, value: &str, style: &mut Style) {
+    match name {
+        "border" => {}
+        "border-bottom" => {}
+        "border-bottom-color" => {}
+        "border-bottom-left-radius" => {}
+        "border-bottom-right-radius" => {}
+        "border-bottom-style" => {
+            if style.border.bottom == Dimension::default() {
+                let v = Dimension::Points(1.0);
+                style.border.bottom = v;
+            }
+        }
+        "border-bottom-width" => {
+            if let Some(v) = parse_value(value) {
+                style.border.bottom = v.into();
+            }
+        }
+        "border-collapse" => {}
+        "border-color" => {}
+        "border-image" => {}
+        "border-image-outset" => {}
+        "border-image-repeat" => {}
+        "border-image-slice" => {}
+        "border-image-source" => {}
+        "border-image-width" => {}
+        "border-left" => {}
+        "border-left-color" => {}
+        "border-left-style" => {
+            if style.border.start == Dimension::default() {
+                let v = Dimension::Points(1.0);
+                style.border.start = v;
+            }
+        }
+        "border-left-width" => {
+            if let Some(v) = parse_value(value) {
+                style.border.start = v.into();
+            }
+        }
+        "border-radius" => {}
+        "border-right" => {}
+        "border-right-color" => {}
+        "border-right-style" => {
+            let v = Dimension::Points(1.0);
+            style.border.end = v;
+        }
+        "border-right-width" => {
+            if let Some(v) = parse_value(value) {
+                style.border.end = v.into();
+            }
+        }
+        "border-spacing" => {}
+        "border-style" => {
+            if style.border.top == Dimension::default() {
+                let v = Dimension::Points(1.0);
+                style.border.top = v;
+            }
+            if style.border.bottom == Dimension::default() {
+                let v = Dimension::Points(1.0);
+                style.border.bottom = v;
+            }
+            if style.border.start == Dimension::default() {
+                let v = Dimension::Points(1.0);
+                style.border.start = v;
+            }
+            if style.border.end == Dimension::default() {
+                let v = Dimension::Points(1.0);
+                style.border.end = v;
+            }
+        }
+        "border-top" => {}
+        "border-top-color" => {}
+        "border-top-left-radius" => {}
+        "border-top-right-radius" => {}
+        "border-top-style" => {
+            if style.border.top == Dimension::default() {
+                let v = Dimension::Points(1.0);
+                style.border.top = v;
+            }
+        }
+        "border-top-width" => {
+            if let Some(v) = parse_value(value) {
+                style.border.top = v.into();
+            }
+        }
+        "border-width" => {
+            let values: Vec<_> = value.split(' ').collect();
+            if values.len() == 1 {
+                if let Some(w) = parse_value(values[0]) {
+                    style.border.top = w.into();
+                    style.border.bottom = w.into();
+                    style.border.start = w.into();
+                    style.border.end = w.into();
+                }
+            } else {
+                let border_widths = [
+                    &mut style.border.top,
+                    &mut style.border.bottom,
+                    &mut style.border.start,
+                    &mut style.border.end,
+                ];
+                for (v, width) in values.into_iter().zip(border_widths) {
+                    if let Some(w) = parse_value(v) {
+                        *width = w.into();
+                    }
+                }
+            }
+        }
+        _ => (),
+    }
+}
+
+fn apply_animation(name: &str, _value: &str, _style: &mut Style) {
+    match name {
+        "animation" => {}
+        "animation-delay" => {}
+        "animation-direction =>{}" => {}
+        "animation-duration" => {}
+        "animation-fill-mode" => {}
+        "animation-itera =>{}tion-count" => {}
+        "animation-name" => {}
+        "animation-play-state" => {}
+        "animation-timing-function" => {}
+        _ => {}
+    }
+}
+
+fn apply_column(name: &str, _value: &str, _style: &mut Style) {
+    match name {
+        "column-count" => {}
+        "column-fill" => {}
+        "column-gap" => {}
+        "column-rule" => {}
+        "column-rule-color" => {}
+        "column-rule-style" => {}
+        "column-rule-width" => {}
+        "column-span" => {}
+        "column-width" => {}
+        _ => {}
+    }
+}
+
+fn apply_flex(name: &str, value: &str, style: &mut Style) {
+    // - [x] pub flex_direction: FlexDirection,
+    // - [x] pub flex_wrap: FlexWrap,
+    // - [x] pub flex_grow: f32,
+    // - [x] pub flex_shrink: f32,
+    // - [x] pub flex_basis: Dimension,
+
+    match name {
+        "flex" => {}
+        "flex-direction" => {
+            use FlexDirection::*;
+            style.flex_direction = match value {
+                "row" => Row,
+                "row-reverse" => RowReverse,
+                "column" => Column,
+                "column-reverse" => ColumnReverse,
+                _ => Row,
+            };
+        }
+        "flex-basis" => {
+            if let Some(v) = parse_value(value) {
+                style.flex_basis = match v {
+                    UnitSystem::Percent(v) => Dimension::Percent(v / 100.0),
+                    UnitSystem::Point(v) => Dimension::Points(v),
+                };
+            }
+        }
+        "flex-flow" => {}
+        "flex-grow" => {
+            if let Ok(val) = value.parse::<f32>() {
+                style.flex_grow = val;
+            }
+        }
+        "flex-shrink" => {
+            if let Ok(px) = value.parse::<f32>() {
+                style.flex_shrink = px;
+            }
+        }
+        "flex-wrap" => {
+            use FlexWrap::*;
+            style.flex_wrap = match value {
+                "nowrap" => NoWrap,
+                "wrap" => Wrap,
+                "wrap-reverse" => WrapReverse,
+                _ => NoWrap,
+            };
+        }
+        _ => {}
+    }
+}
+
+fn apply_padding(name: &str, value: &str, style: &mut Style) {
+    match parse_value(value) {
+        Some(UnitSystem::Percent(v)) => match name {
+            "padding" => {
+                let v = Dimension::Percent(v / 100.0);
+                style.padding.top = v;
+                style.padding.bottom = v;
+                style.padding.start = v;
+                style.padding.end = v;
+            }
+            "padding-bottom" => style.padding.bottom = Dimension::Percent(v / 100.0),
+            "padding-left" => style.padding.start = Dimension::Percent(v / 100.0),
+            "padding-right" => style.padding.end = Dimension::Percent(v / 100.0),
+            "padding-top" => style.padding.top = Dimension::Percent(v / 100.0),
+            _ => {}
+        },
+        Some(UnitSystem::Point(v)) => match name {
+            "padding" => {
+                style.padding.top = Dimension::Points(v);
+                style.padding.bottom = Dimension::Points(v);
+                style.padding.start = Dimension::Points(v);
+                style.padding.end = Dimension::Points(v);
+            }
+            "padding-bottom" => style.padding.bottom = Dimension::Points(v),
+            "padding-left" => style.padding.start = Dimension::Points(v),
+            "padding-right" => style.padding.end = Dimension::Points(v),
+            "padding-top" => style.padding.top = Dimension::Points(v),
+            _ => {}
+        },
+        None => {}
+    }
+}
+
+fn apply_transform(_name: &str, _value: &str, _style: &mut Style) {
+    todo!()
+}
+
+fn apply_transition(_name: &str, _value: &str, _style: &mut Style) {
+    todo!()
+}
+
+fn apply_align(name: &str, value: &str, style: &mut Style) {
+    match name {
+        "align-items" => {
+            use AlignItems::*;
+            style.align_items = match value {
+                "flex-start" => FlexStart,
+                "flex-end" => FlexEnd,
+                "center" => Center,
+                "baseline" => Baseline,
+                "stretch" => Stretch,
+                _ => FlexStart,
+            };
+        }
+        "align-content" => {
+            use AlignContent::*;
+            style.align_content = match value {
+                "flex-start" => FlexStart,
+                "flex-end" => FlexEnd,
+                "center" => Center,
+                "space-between" => SpaceBetween,
+                "space-around" => SpaceAround,
+                _ => FlexStart,
+            };
+        }
+        "align-self" => {
+            use AlignSelf::*;
+            style.align_self = match value {
+                "auto" => Auto,
+                "flex-start" => FlexStart,
+                "flex-end" => FlexEnd,
+                "center" => Center,
+                "baseline" => Baseline,
+                "stretch" => Stretch,
+                _ => Auto,
+            };
+        }
+        _ => {}
+    }
+}
+
+fn apply_margin(name: &str, value: &str, style: &mut Style) {
+    match parse_value(value) {
+        Some(UnitSystem::Percent(v)) => match name {
+            "margin" => {
+                let v = Dimension::Percent(v / 100.0);
+                style.margin.top = v;
+                style.margin.bottom = v;
+                style.margin.start = v;
+                style.margin.end = v;
+            }
+            "margin-top" => style.margin.top = Dimension::Percent(v / 100.0),
+            "margin-bottom" => style.margin.bottom = Dimension::Percent(v / 100.0),
+            "margin-left" => style.margin.start = Dimension::Percent(v / 100.0),
+            "margin-right" => style.margin.end = Dimension::Percent(v / 100.0),
+            _ => {}
+        },
+        Some(UnitSystem::Point(v)) => match name {
+            "margin" => {
+                style.margin.top = Dimension::Points(v);
+                style.margin.bottom = Dimension::Points(v);
+                style.margin.start = Dimension::Points(v);
+                style.margin.end = Dimension::Points(v);
+            }
+            "margin-top" => style.margin.top = Dimension::Points(v),
+            "margin-bottom" => style.margin.bottom = Dimension::Points(v),
+            "margin-left" => style.margin.start = Dimension::Points(v),
+            "margin-right" => style.margin.end = Dimension::Points(v),
+            _ => {}
+        },
+        None => {}
+    }
+}

+ 4 - 0
packages/native-core/src/lib.rs

@@ -0,0 +1,4 @@
+pub mod layout_attributes;
+pub mod node_ref;
+pub mod real_dom;
+pub mod state;

+ 249 - 0
packages/native-core/src/node_ref.rs

@@ -0,0 +1,249 @@
+use dioxus_core::*;
+
+use crate::state::union_ordered_iter;
+
+#[derive(Debug)]
+pub struct NodeView<'a> {
+    inner: &'a VNode<'a>,
+    mask: NodeMask,
+}
+impl<'a> NodeView<'a> {
+    pub fn new(mut vnode: &'a VNode<'a>, view: NodeMask, vdom: &'a VirtualDom) -> Self {
+        if let VNode::Component(sc) = vnode {
+            let scope = vdom.get_scope(sc.scope.get().unwrap()).unwrap();
+            vnode = scope.root_node();
+        }
+        Self {
+            inner: vnode,
+            mask: view,
+        }
+    }
+
+    pub fn id(&self) -> ElementId {
+        self.inner.mounted_id()
+    }
+
+    pub fn tag(&self) -> Option<&'a str> {
+        self.mask.tag.then(|| self.el().map(|el| el.tag)).flatten()
+    }
+
+    pub fn namespace(&self) -> Option<&'a str> {
+        self.mask
+            .namespace
+            .then(|| self.el().and_then(|el| el.namespace))
+            .flatten()
+    }
+
+    pub fn attributes(&self) -> impl Iterator<Item = &Attribute<'a>> {
+        self.el()
+            .map(|el| el.attributes)
+            .unwrap_or_default()
+            .iter()
+            .filter(|a| self.mask.attritutes.contains_attribute(a.name))
+    }
+
+    pub fn text(&self) -> Option<&str> {
+        self.mask
+            .text
+            .then(|| self.txt().map(|txt| txt.text))
+            .flatten()
+    }
+
+    pub fn listeners(&self) -> &'a [Listener<'a>] {
+        self.el().map(|el| el.listeners).unwrap_or_default()
+    }
+
+    fn el(&self) -> Option<&'a VElement<'a>> {
+        if let VNode::Element(el) = &self.inner {
+            Some(el)
+        } else {
+            None
+        }
+    }
+
+    fn txt(&self) -> Option<&'a VText<'a>> {
+        if let VNode::Text(txt) = &self.inner {
+            Some(txt)
+        } else {
+            None
+        }
+    }
+}
+
+#[derive(PartialEq, Clone, Debug)]
+pub enum AttributeMask {
+    All,
+    Dynamic(Vec<&'static str>),
+    Static(&'static [&'static str]),
+}
+
+impl AttributeMask {
+    pub const NONE: Self = Self::Static(&[]);
+
+    fn contains_attribute(&self, attr: &'static str) -> bool {
+        match self {
+            AttributeMask::All => true,
+            AttributeMask::Dynamic(l) => l.binary_search(&attr).is_ok(),
+            AttributeMask::Static(l) => l.binary_search(&attr).is_ok(),
+        }
+    }
+
+    pub fn single(new: &'static str) -> Self {
+        Self::Dynamic(vec![new])
+    }
+
+    pub fn verify(&self) {
+        match &self {
+            AttributeMask::Static(attrs) => debug_assert!(
+                attrs.windows(2).all(|w| w[0] < w[1]),
+                "attritutes must be increasing"
+            ),
+            AttributeMask::Dynamic(attrs) => debug_assert!(
+                attrs.windows(2).all(|w| w[0] < w[1]),
+                "attritutes must be increasing"
+            ),
+            _ => (),
+        }
+    }
+
+    pub fn union(&self, other: &Self) -> Self {
+        let new = match (self, other) {
+            (AttributeMask::Dynamic(s), AttributeMask::Dynamic(o)) => AttributeMask::Dynamic(
+                union_ordered_iter(s.iter().copied(), o.iter().copied(), s.len() + o.len()),
+            ),
+            (AttributeMask::Static(s), AttributeMask::Dynamic(o)) => AttributeMask::Dynamic(
+                union_ordered_iter(s.iter().copied(), o.iter().copied(), s.len() + o.len()),
+            ),
+            (AttributeMask::Dynamic(s), AttributeMask::Static(o)) => AttributeMask::Dynamic(
+                union_ordered_iter(s.iter().copied(), o.iter().copied(), s.len() + o.len()),
+            ),
+            (AttributeMask::Static(s), AttributeMask::Static(o)) => AttributeMask::Dynamic(
+                union_ordered_iter(s.iter().copied(), o.iter().copied(), s.len() + o.len()),
+            ),
+            _ => AttributeMask::All,
+        };
+        new.verify();
+        new
+    }
+
+    fn overlaps(&self, other: &Self) -> bool {
+        fn overlaps_iter(
+            self_iter: impl Iterator<Item = &'static str>,
+            mut other_iter: impl Iterator<Item = &'static str>,
+        ) -> bool {
+            if let Some(mut other_attr) = other_iter.next() {
+                for self_attr in self_iter {
+                    while other_attr < self_attr {
+                        if let Some(attr) = other_iter.next() {
+                            other_attr = attr;
+                        } else {
+                            return false;
+                        }
+                    }
+                    if other_attr == self_attr {
+                        return true;
+                    }
+                }
+            }
+            false
+        }
+        match (self, other) {
+            (AttributeMask::All, AttributeMask::All) => true,
+            (AttributeMask::All, AttributeMask::Dynamic(v)) => !v.is_empty(),
+            (AttributeMask::All, AttributeMask::Static(s)) => !s.is_empty(),
+            (AttributeMask::Dynamic(v), AttributeMask::All) => !v.is_empty(),
+            (AttributeMask::Static(s), AttributeMask::All) => !s.is_empty(),
+            (AttributeMask::Dynamic(v1), AttributeMask::Dynamic(v2)) => {
+                overlaps_iter(v1.iter().copied(), v2.iter().copied())
+            }
+            (AttributeMask::Dynamic(v), AttributeMask::Static(s)) => {
+                overlaps_iter(v.iter().copied(), s.iter().copied())
+            }
+            (AttributeMask::Static(s), AttributeMask::Dynamic(v)) => {
+                overlaps_iter(v.iter().copied(), s.iter().copied())
+            }
+            (AttributeMask::Static(s1), AttributeMask::Static(s2)) => {
+                overlaps_iter(s1.iter().copied(), s2.iter().copied())
+            }
+        }
+    }
+}
+
+impl Default for AttributeMask {
+    fn default() -> Self {
+        AttributeMask::Static(&[])
+    }
+}
+
+#[derive(Default, PartialEq, Clone, Debug)]
+pub struct NodeMask {
+    // must be sorted
+    attritutes: AttributeMask,
+    tag: bool,
+    namespace: bool,
+    text: bool,
+    listeners: bool,
+}
+
+impl NodeMask {
+    pub const NONE: Self = Self::new();
+    pub const ALL: Self = Self::new_with_attrs(AttributeMask::All)
+        .with_text()
+        .with_element();
+
+    pub fn overlaps(&self, other: &Self) -> bool {
+        (self.tag && other.tag)
+            || (self.namespace && other.namespace)
+            || self.attritutes.overlaps(&other.attritutes)
+            || (self.text && other.text)
+            || (self.listeners && other.listeners)
+    }
+
+    pub fn union(&self, other: &Self) -> Self {
+        Self {
+            attritutes: self.attritutes.union(&other.attritutes),
+            tag: self.tag | other.tag,
+            namespace: self.namespace | other.namespace,
+            text: self.text | other.text,
+            listeners: self.listeners | other.listeners,
+        }
+    }
+
+    pub const fn new_with_attrs(attritutes: AttributeMask) -> Self {
+        Self {
+            attritutes,
+            tag: false,
+            namespace: false,
+            text: false,
+            listeners: false,
+        }
+    }
+
+    pub const fn new() -> Self {
+        Self::new_with_attrs(AttributeMask::NONE)
+    }
+
+    pub const fn with_tag(mut self) -> Self {
+        self.tag = true;
+        self
+    }
+
+    pub const fn with_namespace(mut self) -> Self {
+        self.namespace = true;
+        self
+    }
+
+    pub const fn with_element(self) -> Self {
+        self.with_namespace().with_tag()
+    }
+
+    pub const fn with_text(mut self) -> Self {
+        self.text = true;
+        self
+    }
+
+    pub const fn with_listeners(mut self) -> Self {
+        self.listeners = true;
+        self
+    }
+}

+ 774 - 0
packages/native-core/src/real_dom.rs

@@ -0,0 +1,774 @@
+use anymap::AnyMap;
+use fxhash::{FxHashMap, FxHashSet};
+use std::{
+    collections::VecDeque,
+    ops::{Index, IndexMut},
+};
+
+use dioxus_core::{ElementId, Mutations, VNode, VirtualDom};
+
+use crate::state::{union_ordered_iter, State};
+use crate::{
+    node_ref::{AttributeMask, NodeMask},
+    state::MemberId,
+};
+
+/// A Dom that can sync with the VirtualDom mutations intended for use in lazy renderers.
+/// The render state passes from parent to children and or accumulates state from children to parents.
+/// To get started implement [PushedDownState] and or [BubbledUpState] and call [RealDom::apply_mutations] to update the dom and [RealDom::update_state] to update the state of the nodes.
+#[derive(Debug)]
+pub struct RealDom<S: State> {
+    root: usize,
+    nodes: Vec<Option<Node<S>>>,
+    nodes_listening: FxHashMap<&'static str, FxHashSet<usize>>,
+    node_stack: smallvec::SmallVec<[usize; 10]>,
+}
+
+impl<S: State> Default for RealDom<S> {
+    fn default() -> Self {
+        Self::new()
+    }
+}
+
+impl<S: State> RealDom<S> {
+    pub fn new() -> RealDom<S> {
+        RealDom {
+            root: 0,
+            nodes: {
+                let v = vec![Some(Node::new(
+                    0,
+                    NodeType::Element {
+                        tag: "Root".to_string(),
+                        namespace: Some("Root"),
+                        children: Vec::new(),
+                    },
+                ))];
+                v
+            },
+            nodes_listening: FxHashMap::default(),
+            node_stack: smallvec::SmallVec::new(),
+        }
+    }
+
+    /// Updates the dom, up and down state and return a set of nodes that were updated pass this to update_state.
+    pub fn apply_mutations(&mut self, mutations_vec: Vec<Mutations>) -> Vec<(usize, NodeMask)> {
+        let mut nodes_updated = Vec::new();
+        for mutations in mutations_vec {
+            for e in mutations.edits {
+                use dioxus_core::DomEdit::*;
+                match e {
+                    PushRoot { root } => self.node_stack.push(root as usize),
+                    AppendChildren { many } => {
+                        let target = if self.node_stack.len() > many as usize {
+                            *self
+                                .node_stack
+                                .get(self.node_stack.len() - (many as usize + 1))
+                                .unwrap()
+                        } else {
+                            0
+                        };
+                        let drained: Vec<_> = self
+                            .node_stack
+                            .drain(self.node_stack.len() - many as usize..)
+                            .collect();
+                        for ns in drained {
+                            self.link_child(ns, target).unwrap();
+                            nodes_updated.push((ns, NodeMask::ALL));
+                        }
+                    }
+                    ReplaceWith { root, m } => {
+                        let root = self.remove(root as usize).unwrap();
+                        let target = root.parent.unwrap().0;
+                        let drained: Vec<_> = self.node_stack.drain(0..m as usize).collect();
+                        for ns in drained {
+                            nodes_updated.push((ns, NodeMask::ALL));
+                            self.link_child(ns, target).unwrap();
+                        }
+                    }
+                    InsertAfter { root, n } => {
+                        let target = self[root as usize].parent.unwrap().0;
+                        let drained: Vec<_> = self.node_stack.drain(0..n as usize).collect();
+                        for ns in drained {
+                            nodes_updated.push((ns, NodeMask::ALL));
+                            self.link_child(ns, target).unwrap();
+                        }
+                    }
+                    InsertBefore { root, n } => {
+                        let target = self[root as usize].parent.unwrap().0;
+                        let drained: Vec<_> = self.node_stack.drain(0..n as usize).collect();
+                        for ns in drained {
+                            nodes_updated.push((ns, NodeMask::ALL));
+                            self.link_child(ns, target).unwrap();
+                        }
+                    }
+                    Remove { root } => {
+                        if let Some(parent) = self[root as usize].parent {
+                            nodes_updated.push((parent.0, NodeMask::NONE));
+                        }
+                        self.remove(root as usize).unwrap();
+                    }
+                    CreateTextNode { root, text } => {
+                        let n = Node::new(
+                            root,
+                            NodeType::Text {
+                                text: text.to_string(),
+                            },
+                        );
+                        self.insert(n);
+                        self.node_stack.push(root as usize)
+                    }
+                    CreateElement { root, tag } => {
+                        let n = Node::new(
+                            root,
+                            NodeType::Element {
+                                tag: tag.to_string(),
+                                namespace: None,
+                                children: Vec::new(),
+                            },
+                        );
+                        self.insert(n);
+                        self.node_stack.push(root as usize)
+                    }
+                    CreateElementNs { root, tag, ns } => {
+                        let n = Node::new(
+                            root,
+                            NodeType::Element {
+                                tag: tag.to_string(),
+                                namespace: Some(ns),
+                                children: Vec::new(),
+                            },
+                        );
+                        self.insert(n);
+                        self.node_stack.push(root as usize)
+                    }
+                    CreatePlaceholder { root } => {
+                        let n = Node::new(root, NodeType::Placeholder);
+                        self.insert(n);
+                        self.node_stack.push(root as usize)
+                    }
+
+                    NewEventListener {
+                        event_name,
+                        scope: _,
+                        root,
+                    } => {
+                        nodes_updated.push((root as usize, NodeMask::new().with_listeners()));
+                        if let Some(v) = self.nodes_listening.get_mut(event_name) {
+                            v.insert(root as usize);
+                        } else {
+                            let mut hs = FxHashSet::default();
+                            hs.insert(root as usize);
+                            self.nodes_listening.insert(event_name, hs);
+                        }
+                    }
+                    RemoveEventListener { root, event } => {
+                        nodes_updated.push((root as usize, NodeMask::new().with_listeners()));
+                        let v = self.nodes_listening.get_mut(event).unwrap();
+                        v.remove(&(root as usize));
+                    }
+                    SetText {
+                        root,
+                        text: new_text,
+                    } => {
+                        let target = &mut self[root as usize];
+                        nodes_updated.push((root as usize, NodeMask::new().with_text()));
+                        match &mut target.node_type {
+                            NodeType::Text { text } => {
+                                *text = new_text.to_string();
+                            }
+                            _ => unreachable!(),
+                        }
+                    }
+                    SetAttribute { root, field, .. } => {
+                        nodes_updated.push((
+                            root as usize,
+                            NodeMask::new_with_attrs(AttributeMask::single(field)),
+                        ));
+                    }
+                    RemoveAttribute {
+                        root, name: field, ..
+                    } => {
+                        nodes_updated.push((
+                            root as usize,
+                            NodeMask::new_with_attrs(AttributeMask::single(field)),
+                        ));
+                    }
+                    PopRoot {} => {
+                        self.node_stack.pop();
+                    }
+                }
+            }
+        }
+
+        nodes_updated
+    }
+
+    /// Seperated from apply_mutations because Mutations require a mutable reference to the VirtualDom.
+    pub fn update_state(
+        &mut self,
+        vdom: &VirtualDom,
+        nodes_updated: Vec<(usize, NodeMask)>,
+        ctx: AnyMap,
+    ) -> Option<FxHashSet<usize>> {
+        #[derive(PartialEq, Clone, Debug)]
+        enum StatesToCheck {
+            All,
+            Some(Vec<MemberId>),
+        }
+        impl StatesToCheck {
+            fn union(&self, other: &Self) -> Self {
+                match (self, other) {
+                    (Self::Some(s), Self::Some(o)) => Self::Some(union_ordered_iter(
+                        s.iter().copied(),
+                        o.iter().copied(),
+                        s.len() + o.len(),
+                    )),
+                    _ => Self::All,
+                }
+            }
+        }
+
+        #[derive(Debug, Clone)]
+        struct NodeRef {
+            id: usize,
+            height: u16,
+            node_mask: NodeMask,
+            to_check: StatesToCheck,
+        }
+        impl NodeRef {
+            fn union_with(&mut self, other: &Self) {
+                self.node_mask = self.node_mask.union(&other.node_mask);
+                self.to_check = self.to_check.union(&other.to_check);
+            }
+        }
+        impl Eq for NodeRef {}
+        impl PartialEq for NodeRef {
+            fn eq(&self, other: &Self) -> bool {
+                self.id == other.id && self.height == other.height
+            }
+        }
+        impl Ord for NodeRef {
+            fn cmp(&self, other: &Self) -> std::cmp::Ordering {
+                self.partial_cmp(other).unwrap()
+            }
+        }
+        impl PartialOrd for NodeRef {
+            fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
+                // Sort nodes first by height, then if the height is the same id.
+                // The order of the id does not matter it just helps with binary search later
+                Some(self.height.cmp(&other.height).then(self.id.cmp(&other.id)))
+            }
+        }
+
+        let mut to_rerender = FxHashSet::default();
+        to_rerender.extend(nodes_updated.iter().map(|(id, _)| id));
+        let mut nodes_updated: Vec<_> = nodes_updated
+            .into_iter()
+            .map(|(id, mask)| NodeRef {
+                id,
+                height: self[id].height,
+                node_mask: mask,
+                to_check: StatesToCheck::All,
+            })
+            .collect();
+        nodes_updated.sort();
+
+        // Combine mutations that affect the same node.
+        let mut last_node: Option<NodeRef> = None;
+        let mut new_nodes_updated = VecDeque::new();
+        for current in nodes_updated.into_iter() {
+            if let Some(node) = &mut last_node {
+                if *node == current {
+                    node.union_with(&current);
+                } else {
+                    new_nodes_updated.push_back(last_node.replace(current).unwrap());
+                }
+            } else {
+                last_node = Some(current);
+            }
+        }
+        if let Some(node) = last_node {
+            new_nodes_updated.push_back(node);
+        }
+        let nodes_updated = new_nodes_updated;
+
+        // update the state that only depends on nodes. The order does not matter.
+        for node_ref in &nodes_updated {
+            let mut changed = false;
+            let node = &mut self[node_ref.id];
+            let mut ids = match &node_ref.to_check {
+                StatesToCheck::All => node.state.node_dep_types(&node_ref.node_mask),
+                // this should only be triggered from the current node, so all members will need to be checked
+                StatesToCheck::Some(_) => unreachable!(),
+            };
+            let mut i = 0;
+            while i < ids.len() {
+                let id = ids[i];
+                let node = &mut self[node_ref.id];
+                let vnode = node.element(vdom);
+                if let Some(members_effected) =
+                    node.state.update_node_dep_state(id, vnode, vdom, &ctx)
+                {
+                    debug_assert!(members_effected.node_dep.iter().all(|i| i >= &id));
+                    for m in &members_effected.node_dep {
+                        if let Err(idx) = ids.binary_search(m) {
+                            ids.insert(idx, *m);
+                        }
+                    }
+                    changed = true;
+                }
+                i += 1;
+            }
+            if changed {
+                to_rerender.insert(node_ref.id);
+            }
+        }
+
+        // bubble up state. To avoid calling reduce more times than nessisary start from the bottom and go up.
+        let mut to_bubble = nodes_updated.clone();
+        while let Some(node_ref) = to_bubble.pop_back() {
+            let NodeRef {
+                id,
+                height,
+                node_mask,
+                to_check,
+            } = node_ref;
+            let (node, children) = self.get_node_children_mut(id).unwrap();
+            let children_state: Vec<_> = children.iter().map(|c| &c.state).collect();
+            let mut ids = match to_check {
+                StatesToCheck::All => node.state.child_dep_types(&node_mask),
+                StatesToCheck::Some(ids) => ids,
+            };
+            let mut changed = Vec::new();
+            let mut i = 0;
+            while i < ids.len() {
+                let id = ids[i];
+                let vnode = node.element(vdom);
+                if let Some(members_effected) =
+                    node.state
+                        .update_child_dep_state(id, vnode, vdom, &children_state, &ctx)
+                {
+                    debug_assert!(members_effected.node_dep.iter().all(|i| i >= &id));
+                    for m in members_effected.node_dep {
+                        if let Err(idx) = ids.binary_search(&m) {
+                            ids.insert(idx, m);
+                        }
+                    }
+                    for m in members_effected.child_dep {
+                        changed.push(m);
+                    }
+                }
+                i += 1;
+            }
+            if let Some(parent_id) = node.parent {
+                if !changed.is_empty() {
+                    to_rerender.insert(id);
+                    let i = to_bubble.partition_point(
+                        |NodeRef {
+                             id: other_id,
+                             height: h,
+                             ..
+                         }| {
+                            *h < height - 1 || (*h == height - 1 && *other_id < parent_id.0)
+                        },
+                    );
+                    // make sure the parent is not already queued
+                    if i < to_bubble.len() && to_bubble[i].id == parent_id.0 {
+                        to_bubble[i].to_check =
+                            to_bubble[i].to_check.union(&StatesToCheck::Some(changed));
+                    } else {
+                        to_bubble.insert(
+                            i,
+                            NodeRef {
+                                id: parent_id.0,
+                                height: height - 1,
+                                node_mask: NodeMask::NONE,
+                                to_check: StatesToCheck::Some(changed),
+                            },
+                        );
+                    }
+                }
+            }
+        }
+
+        // push down state. To avoid calling reduce more times than nessisary start from the top and go down.
+        let mut to_push = nodes_updated;
+        while let Some(node_ref) = to_push.pop_front() {
+            let NodeRef {
+                id,
+                height,
+                node_mask,
+                to_check,
+            } = node_ref;
+            let node = &self[id];
+            let mut ids = match to_check {
+                StatesToCheck::All => node.state.parent_dep_types(&node_mask),
+                StatesToCheck::Some(ids) => ids,
+            };
+            let mut changed = Vec::new();
+            let (node, parent) = self.get_node_parent_mut(id).unwrap();
+            let mut i = 0;
+            while i < ids.len() {
+                let id = ids[i];
+                let vnode = node.element(vdom);
+                let parent = parent.as_deref();
+                let state = &mut node.state;
+                if let Some(members_effected) =
+                    state.update_parent_dep_state(id, vnode, vdom, parent.map(|n| &n.state), &ctx)
+                {
+                    debug_assert!(members_effected.node_dep.iter().all(|i| i >= &id));
+                    for m in members_effected.node_dep {
+                        if let Err(idx) = ids.binary_search(&m) {
+                            ids.insert(idx, m);
+                        }
+                    }
+                    for m in members_effected.parent_dep {
+                        changed.push(m);
+                    }
+                }
+                i += 1;
+            }
+
+            to_rerender.insert(id);
+            if !changed.is_empty() {
+                let node = &self[id];
+                if let NodeType::Element { children, .. } = &node.node_type {
+                    for c in children {
+                        let i = to_push.partition_point(
+                            |NodeRef {
+                                 id: other_id,
+                                 height: h,
+                                 ..
+                             }| {
+                                *h < height + 1 || (*h == height + 1 && *other_id < c.0)
+                            },
+                        );
+                        if i < to_push.len() && to_push[i].id == c.0 {
+                            to_push[i].to_check = to_push[i]
+                                .to_check
+                                .union(&StatesToCheck::Some(changed.clone()));
+                        } else {
+                            to_push.insert(
+                                i,
+                                NodeRef {
+                                    id: c.0,
+                                    height: height + 1,
+                                    node_mask: NodeMask::NONE,
+                                    to_check: StatesToCheck::Some(changed.clone()),
+                                },
+                            );
+                        }
+                    }
+                }
+            }
+        }
+
+        Some(to_rerender)
+    }
+
+    fn link_child(&mut self, child_id: usize, parent_id: usize) -> Option<()> {
+        debug_assert_ne!(child_id, parent_id);
+        let parent = &mut self[parent_id];
+        parent.add_child(ElementId(child_id));
+        let parent_height = parent.height + 1;
+        self[child_id].set_parent(ElementId(parent_id));
+        self.increase_height(child_id, parent_height);
+        Some(())
+    }
+
+    fn increase_height(&mut self, id: usize, amount: u16) {
+        let n = &mut self[id];
+        n.height += amount;
+        if let NodeType::Element { children, .. } = &n.node_type {
+            for c in children.clone() {
+                self.increase_height(c.0, amount);
+            }
+        }
+    }
+
+    // remove a node and it's children from the dom.
+    fn remove(&mut self, id: usize) -> Option<Node<S>> {
+        // We do not need to remove the node from the parent's children list for children.
+        fn inner<S: State>(dom: &mut RealDom<S>, id: usize) -> Option<Node<S>> {
+            let mut node = dom.nodes[id as usize].take()?;
+            if let NodeType::Element { children, .. } = &mut node.node_type {
+                for c in children {
+                    inner(dom, c.0)?;
+                }
+            }
+            Some(node)
+        }
+        let mut node = self.nodes[id as usize].take()?;
+        if let Some(parent) = node.parent {
+            let parent = &mut self[parent];
+            parent.remove_child(ElementId(id));
+        }
+        if let NodeType::Element { children, .. } = &mut node.node_type {
+            for c in children {
+                inner(self, c.0)?;
+            }
+        }
+        Some(node)
+    }
+
+    fn insert(&mut self, node: Node<S>) {
+        let current_len = self.nodes.len();
+        let id = node.id.0;
+        if current_len - 1 < node.id.0 {
+            // self.nodes.reserve(1 + id - current_len);
+            self.nodes.extend((0..1 + id - current_len).map(|_| None));
+        }
+        self.nodes[id] = Some(node);
+    }
+
+    pub fn get(&self, id: usize) -> Option<&Node<S>> {
+        self.nodes.get(id)?.as_ref()
+    }
+
+    pub fn get_mut(&mut self, id: usize) -> Option<&mut Node<S>> {
+        self.nodes.get_mut(id)?.as_mut()
+    }
+
+    // this is safe because no node will have itself as a child
+    pub fn get_node_children_mut(
+        &mut self,
+        id: usize,
+    ) -> Option<(&mut Node<S>, Vec<&mut Node<S>>)> {
+        let ptr = self.nodes.as_mut_ptr();
+        unsafe {
+            if id >= self.nodes.len() {
+                None
+            } else {
+                let node = &mut *ptr.add(id);
+                if let Some(node) = node.as_mut() {
+                    let children = match &node.node_type {
+                        NodeType::Element { children, .. } => children
+                            .iter()
+                            .map(|id| (&mut *ptr.add(id.0)).as_mut().unwrap())
+                            .collect(),
+                        _ => Vec::new(),
+                    };
+                    Some((node, children))
+                } else {
+                    None
+                }
+            }
+        }
+    }
+
+    // this is safe because no node will have itself as a parent
+    pub fn get_node_parent_mut(
+        &mut self,
+        id: usize,
+    ) -> Option<(&mut Node<S>, Option<&mut Node<S>>)> {
+        let ptr = self.nodes.as_mut_ptr();
+        unsafe {
+            let node = &mut *ptr.add(id);
+            if id >= self.nodes.len() {
+                None
+            } else if let Some(node) = node.as_mut() {
+                let parent = node
+                    .parent
+                    .map(|id| (&mut *ptr.add(id.0)).as_mut().unwrap());
+                Some((node, parent))
+            } else {
+                None
+            }
+        }
+    }
+
+    pub fn get_listening_sorted(&self, event: &'static str) -> Vec<&Node<S>> {
+        if let Some(nodes) = self.nodes_listening.get(event) {
+            let mut listening: Vec<_> = nodes.iter().map(|id| &self[*id]).collect();
+            listening.sort_by(|n1, n2| (n1.height).cmp(&n2.height).reverse());
+            listening
+        } else {
+            Vec::new()
+        }
+    }
+
+    /// Check if the dom contains a node and its children.
+    pub fn contains_node(&self, node: &VNode) -> bool {
+        match node {
+            VNode::Component(_) => {
+                todo!()
+            }
+            VNode::Element(e) => {
+                if let Some(id) = e.id.get() {
+                    let dom_node = &self[id];
+                    match &dom_node.node_type {
+                        NodeType::Element {
+                            tag,
+                            namespace,
+                            children,
+                        } => {
+                            tag == e.tag
+                                && namespace == &e.namespace
+                                && children.iter().copied().collect::<FxHashSet<_>>()
+                                    == e.children
+                                        .iter()
+                                        .map(|c| c.mounted_id())
+                                        .collect::<FxHashSet<_>>()
+                                && e.children.iter().all(|c| {
+                                    self.contains_node(c)
+                                        && self[c.mounted_id()].parent == e.id.get()
+                                })
+                        }
+                        _ => false,
+                    }
+                } else {
+                    true
+                }
+            }
+            VNode::Fragment(f) => f.children.iter().all(|c| self.contains_node(c)),
+            VNode::Placeholder(_) => true,
+            VNode::Text(t) => {
+                if let Some(id) = t.id.get() {
+                    let dom_node = &self[id];
+                    match &dom_node.node_type {
+                        NodeType::Text { text } => t.text == text,
+                        _ => false,
+                    }
+                } else {
+                    true
+                }
+            }
+        }
+    }
+
+    /// Return the number of nodes in the dom.
+    pub fn size(&self) -> usize {
+        // The dom has a root node, ignore it.
+        self.nodes.iter().filter(|n| n.is_some()).count() - 1
+    }
+
+    /// Returns the id of the root node.
+    pub fn root_id(&self) -> usize {
+        self.root
+    }
+
+    /// Call a function for each node in the dom, depth first.
+    pub fn traverse_depth_first(&self, mut f: impl FnMut(&Node<S>)) {
+        fn inner<S: State>(dom: &RealDom<S>, id: ElementId, f: &mut impl FnMut(&Node<S>)) {
+            let node = &dom[id];
+            f(node);
+            if let NodeType::Element { children, .. } = &node.node_type {
+                for c in children {
+                    inner(dom, *c, f);
+                }
+            }
+        }
+        if let NodeType::Element { children, .. } = &self[self.root].node_type {
+            for c in children {
+                inner(self, *c, &mut f);
+            }
+        }
+    }
+
+    /// Call a function for each node in the dom, depth first.
+    pub fn traverse_depth_first_mut(&mut self, mut f: impl FnMut(&mut Node<S>)) {
+        fn inner<S: State>(dom: &mut RealDom<S>, id: ElementId, f: &mut impl FnMut(&mut Node<S>)) {
+            let node = &mut dom[id];
+            f(node);
+            if let NodeType::Element { children, .. } = &mut node.node_type {
+                for c in children.clone() {
+                    inner(dom, c, f);
+                }
+            }
+        }
+        let root = self.root;
+        if let NodeType::Element { children, .. } = &mut self[root].node_type {
+            for c in children.clone() {
+                inner(self, c, &mut f);
+            }
+        }
+    }
+}
+
+impl<S: State> Index<usize> for RealDom<S> {
+    type Output = Node<S>;
+
+    fn index(&self, idx: usize) -> &Self::Output {
+        self.get(idx).expect("Node does not exist")
+    }
+}
+
+impl<S: State> Index<ElementId> for RealDom<S> {
+    type Output = Node<S>;
+
+    fn index(&self, idx: ElementId) -> &Self::Output {
+        &self[idx.0]
+    }
+}
+
+impl<S: State> IndexMut<usize> for RealDom<S> {
+    fn index_mut(&mut self, idx: usize) -> &mut Self::Output {
+        self.get_mut(idx).expect("Node does not exist")
+    }
+}
+impl<S: State> IndexMut<ElementId> for RealDom<S> {
+    fn index_mut(&mut self, idx: ElementId) -> &mut Self::Output {
+        &mut self[idx.0]
+    }
+}
+
+/// The node is stored client side and stores only basic data about the node. For more complete information about the node see [`domNode::element`].
+#[derive(Debug, Clone)]
+pub struct Node<S: State> {
+    /// The id of the node this node was created from.
+    pub id: ElementId,
+    /// The parent id of the node.
+    pub parent: Option<ElementId>,
+    /// State of the node.
+    pub state: S,
+    /// Additional inforation specific to the node type
+    pub node_type: NodeType,
+    /// The number of parents before the root node. The root node has height 1.
+    pub height: u16,
+}
+
+#[derive(Debug, Clone)]
+pub enum NodeType {
+    Text {
+        text: String,
+    },
+    Element {
+        tag: String,
+        namespace: Option<&'static str>,
+        children: Vec<ElementId>,
+    },
+    Placeholder,
+}
+
+impl<S: State> Node<S> {
+    fn new(id: u64, node_type: NodeType) -> Self {
+        Node {
+            id: ElementId(id as usize),
+            parent: None,
+            node_type,
+            state: S::default(),
+            height: 0,
+        }
+    }
+
+    /// Returns a reference to the element that this node refrences.
+    pub fn element<'b>(&self, vdom: &'b VirtualDom) -> &'b VNode<'b> {
+        vdom.get_element(self.id).unwrap()
+    }
+
+    fn add_child(&mut self, child: ElementId) {
+        if let NodeType::Element { children, .. } = &mut self.node_type {
+            children.push(child);
+        }
+    }
+
+    fn remove_child(&mut self, child: ElementId) {
+        if let NodeType::Element { children, .. } = &mut self.node_type {
+            children.retain(|c| c != &child);
+        }
+    }
+
+    fn set_parent(&mut self, parent: ElementId) {
+        self.parent = Some(parent);
+    }
+}

+ 202 - 0
packages/native-core/src/state.rs

@@ -0,0 +1,202 @@
+use std::{
+    cmp::Ordering,
+    fmt::Debug,
+    ops::{Add, AddAssign, Sub, SubAssign},
+};
+
+use anymap::AnyMap;
+use dioxus_core::VNode;
+
+use crate::node_ref::{NodeMask, NodeView};
+
+pub(crate) fn union_ordered_iter<T: Ord + Debug>(
+    s_iter: impl Iterator<Item = T>,
+    o_iter: impl Iterator<Item = T>,
+    new_len_guess: usize,
+) -> Vec<T> {
+    let mut s_peekable = s_iter.peekable();
+    let mut o_peekable = o_iter.peekable();
+    let mut v = Vec::with_capacity(new_len_guess);
+    while let Some(s_i) = s_peekable.peek() {
+        while let Some(o_i) = o_peekable.peek() {
+            match o_i.cmp(s_i) {
+                Ordering::Greater => {
+                    break;
+                }
+                Ordering::Less => {
+                    v.push(o_peekable.next().unwrap());
+                }
+                Ordering::Equal => {
+                    o_peekable.next();
+                    break;
+                }
+            }
+        }
+        v.push(s_peekable.next().unwrap());
+    }
+    for o_i in o_peekable {
+        v.push(o_i);
+    }
+    for w in v.windows(2) {
+        debug_assert!(w[1] > w[0]);
+    }
+    v
+}
+
+/// This state is derived from children. For example a node's size could be derived from the size of children.
+/// Called when the current node's node properties are modified, a child's [BubbledUpState] is modified or a child is removed.
+/// Called at most once per update.
+pub trait ChildDepState {
+    /// The context is passed to the [PushedDownState::reduce] when it is pushed down.
+    /// This is sometimes nessisary for lifetime purposes.
+    type Ctx;
+    /// This must be either a [ChildDepState] or [NodeDepState]
+    type DepState;
+    const NODE_MASK: NodeMask = NodeMask::NONE;
+    fn reduce<'a>(
+        &mut self,
+        node: NodeView,
+        children: impl Iterator<Item = &'a Self::DepState>,
+        ctx: &Self::Ctx,
+    ) -> bool
+    where
+        Self::DepState: 'a;
+}
+
+/// This state that is passed down to children. For example text properties (`<b>` `<i>` `<u>`) would be passed to children.
+/// Called when the current node's node properties are modified or a parrent's [PushedDownState] is modified.
+/// Called at most once per update.
+pub trait ParentDepState {
+    /// The context is passed to the [PushedDownState::reduce] when it is pushed down.
+    /// This is sometimes nessisary for lifetime purposes.
+    type Ctx;
+    /// This must be either a [ParentDepState] or [NodeDepState]
+    type DepState;
+    const NODE_MASK: NodeMask = NodeMask::NONE;
+    fn reduce(&mut self, node: NodeView, parent: Option<&Self::DepState>, ctx: &Self::Ctx) -> bool;
+}
+
+/// This state that is upadated lazily. For example any propertys that do not effect other parts of the dom like bg-color.
+/// Called when the current node's node properties are modified or a parrent's [PushedDownState] is modified.
+/// Called at most once per update.
+pub trait NodeDepState {
+    type Ctx;
+    type DepState: NodeDepState;
+    const NODE_MASK: NodeMask = NodeMask::NONE;
+    fn reduce(&mut self, node: NodeView, sibling: &Self::DepState, ctx: &Self::Ctx) -> bool;
+}
+
+#[derive(Debug)]
+pub struct ChildStatesChanged {
+    pub node_dep: Vec<MemberId>,
+    pub child_dep: Vec<MemberId>,
+}
+
+#[derive(Debug)]
+pub struct ParentStatesChanged {
+    pub node_dep: Vec<MemberId>,
+    pub parent_dep: Vec<MemberId>,
+}
+
+#[derive(Debug)]
+pub struct NodeStatesChanged {
+    pub node_dep: Vec<MemberId>,
+}
+
+pub trait State: Default + Clone {
+    const SIZE: usize;
+
+    fn update_node_dep_state<'a>(
+        &'a mut self,
+        ty: MemberId,
+        node: &'a VNode<'a>,
+        vdom: &'a dioxus_core::VirtualDom,
+        ctx: &AnyMap,
+    ) -> Option<NodeStatesChanged>;
+    /// This must be a valid resolution order. (no nodes updated before a state they rely on)
+    fn child_dep_types(&self, mask: &NodeMask) -> Vec<MemberId>;
+
+    fn update_parent_dep_state<'a>(
+        &'a mut self,
+        ty: MemberId,
+        node: &'a VNode<'a>,
+        vdom: &'a dioxus_core::VirtualDom,
+        parent: Option<&Self>,
+        ctx: &AnyMap,
+    ) -> Option<ParentStatesChanged>;
+    /// This must be a valid resolution order. (no nodes updated before a state they rely on)
+    fn parent_dep_types(&self, mask: &NodeMask) -> Vec<MemberId>;
+
+    fn update_child_dep_state<'a>(
+        &'a mut self,
+        ty: MemberId,
+        node: &'a VNode<'a>,
+        vdom: &'a dioxus_core::VirtualDom,
+        children: &Vec<&Self>,
+        ctx: &AnyMap,
+    ) -> Option<ChildStatesChanged>;
+    /// This must be a valid resolution order. (no nodes updated before a state they rely on)
+    fn node_dep_types(&self, mask: &NodeMask) -> Vec<MemberId>;
+}
+
+// Todo: once GATs land we can model multable dependencies
+impl ChildDepState for () {
+    type Ctx = ();
+    type DepState = ();
+    fn reduce<'a>(
+        &mut self,
+        _: NodeView,
+        _: impl Iterator<Item = &'a Self::DepState>,
+        _: &Self::Ctx,
+    ) -> bool
+    where
+        Self::DepState: 'a,
+    {
+        false
+    }
+}
+
+impl ParentDepState for () {
+    type Ctx = ();
+    type DepState = ();
+    fn reduce(&mut self, _: NodeView, _: Option<&Self::DepState>, _: &Self::Ctx) -> bool {
+        false
+    }
+}
+
+impl NodeDepState for () {
+    type Ctx = ();
+    type DepState = ();
+    fn reduce(&mut self, _: NodeView, _sibling: &Self::DepState, _: &Self::Ctx) -> bool {
+        false
+    }
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
+pub struct MemberId(pub usize);
+
+impl Sub<usize> for MemberId {
+    type Output = MemberId;
+    fn sub(self, rhs: usize) -> Self::Output {
+        MemberId(self.0 - rhs)
+    }
+}
+
+impl Add<usize> for MemberId {
+    type Output = MemberId;
+    fn add(self, rhs: usize) -> Self::Output {
+        MemberId(self.0 + rhs)
+    }
+}
+
+impl SubAssign<usize> for MemberId {
+    fn sub_assign(&mut self, rhs: usize) {
+        *self = *self - rhs;
+    }
+}
+
+impl AddAssign<usize> for MemberId {
+    fn add_assign(&mut self, rhs: usize) {
+        *self = *self + rhs;
+    }
+}

+ 4 - 4
packages/router/Cargo.toml

@@ -1,6 +1,6 @@
 [package]
 name = "dioxus-router"
-version = "0.2.2"
+version = "0.2.3"
 edition = "2018"
 description = "Cross-platform router for Dioxus apps"
 license = "MIT/Apache-2.0"
@@ -11,9 +11,9 @@ keywords = ["dom", "ui", "gui", "react", "wasm"]
 # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
 
 [dependencies]
-dioxus-core = { path = "../core", version = "^0.2.0", default-features = false }
-dioxus-html = { path = "../html", version = "^0.2.0", default-features = false }
-dioxus-core-macro = { path = "../core-macro", version = "^0.2.0" }
+dioxus-core = { path = "../core", version = "^0.2.1", default-features = false }
+dioxus-html = { path = "../html", version = "^0.2.1", default-features = false }
+dioxus-core-macro = { path = "../core-macro", version = "^0.2.1" }
 futures-channel = "0.3.21"
 url = { version = "2.2.2", default-features = false }
 

+ 2 - 2
packages/ssr/Cargo.toml

@@ -1,6 +1,6 @@
 [package]
 name = "dioxus-ssr"
-version = "0.2.0"
+version = "0.2.1"
 authors = ["Jonathan Kelley"]
 edition = "2018"
 description = "Dioxus render-to-string"
@@ -13,7 +13,7 @@ keywords = ["dom", "ui", "gui", "react", "wasm"]
 # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
 
 [dependencies]
-dioxus-core = { path = "../core", version = "^0.2.0", features = ["serialize"] }
+dioxus-core = { path = "../core", version = "^0.2.1", features = ["serialize"] }
 
 
 [dev-dependencies]

+ 4 - 2
packages/ssr/src/lib.rs

@@ -186,7 +186,9 @@ impl<'a> TextRenderer<'a, '_> {
                 while let Some(attr) = attr_iter.next() {
                     match attr.namespace {
                         None => match attr.name {
-                            "dangerous_inner_html" => inner_html = Some(attr.value),
+                            "dangerous_inner_html" => {
+                                inner_html = Some(attr.value.as_text().unwrap())
+                            }
                             "allowfullscreen"
                             | "allowpaymentrequest"
                             | "async"
@@ -213,7 +215,7 @@ impl<'a> TextRenderer<'a, '_> {
                             | "reversed"
                             | "selected"
                             | "truespeed" => {
-                                if attr.value != "false" {
+                                if attr.value.is_truthy() {
                                     write!(f, " {}=\"{}\"", attr.name, attr.value)?
                                 }
                             }

+ 9 - 5
packages/tui/Cargo.toml

@@ -1,6 +1,6 @@
 [package]
 name = "dioxus-tui"
-version = "0.2.1"
+version = "0.2.2"
 authors = ["Jonathan Kelley, @dementhos"]
 edition = "2021"
 description = "TUI-based renderer for Dioxus"
@@ -13,13 +13,17 @@ license = "MIT/Apache-2.0"
 # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
 
 [dependencies]
-dioxus-core = { path = "../core", version = "^0.2.0" }
-dioxus-html = { path = "../html", version = "^0.2.0" }
+dioxus-core = { path = "../core", version = "^0.2.1" }
+dioxus-html = { path = "../html", version = "^0.2.1" }
+dioxus-native-core = { path = "../native-core", version = "^0.2.0" }
+dioxus-native-core-macro = { path = "../native-core-macro", version = "^0.2.0" }
 
 tui = "0.17.0"
 crossterm = "0.23.0"
 anyhow = "1.0.42"
 tokio = { version = "1.15.0", features = ["full"] }
 futures = "0.3.19"
-stretch2 = "0.4.1"
-
+stretch2 = "0.4.2"
+smallvec = "1.6"
+fxhash = "0.2"
+anymap = "0.12.1"

+ 0 - 968
packages/tui/src/attributes.rs

@@ -1,968 +0,0 @@
-/*
-- [ ] pub display: Display,
-- [x] pub position_type: PositionType,  --> kinda, stretch doesnt support everything
-- [ ] pub direction: Direction,
-
-- [x] pub flex_direction: FlexDirection,
-- [x] pub flex_wrap: FlexWrap,
-- [x] pub flex_grow: f32,
-- [x] pub flex_shrink: f32,
-- [x] pub flex_basis: Dimension,
-
-- [x] pub overflow: Overflow, ---> kinda implemented... stretch doesnt have support for directional overflow
-
-- [x] pub align_items: AlignItems,
-- [x] pub align_self: AlignSelf,
-- [x] pub align_content: AlignContent,
-
-- [x] pub margin: Rect<Dimension>,
-- [x] pub padding: Rect<Dimension>,
-
-- [x] pub justify_content: JustifyContent,
-- [ ] pub position: Rect<Dimension>,
-- [ ] pub border: Rect<Dimension>,
-
-- [ ] pub size: Size<Dimension>, ----> ??? seems to only be relevant for input?
-- [ ] pub min_size: Size<Dimension>,
-- [ ] pub max_size: Size<Dimension>,
-
-- [ ] pub aspect_ratio: Number,
-*/
-
-use stretch2::{prelude::*, style::PositionType, style::Style};
-
-use crate::style::{RinkColor, RinkStyle};
-
-pub struct StyleModifer {
-    pub style: Style,
-    pub tui_style: RinkStyle,
-    pub tui_modifier: TuiModifier,
-}
-
-#[derive(Default)]
-pub struct TuiModifier {
-    pub borders: Borders,
-}
-
-#[derive(Default)]
-pub struct Borders {
-    pub top: BorderEdge,
-    pub right: BorderEdge,
-    pub bottom: BorderEdge,
-    pub left: BorderEdge,
-}
-
-impl Borders {
-    fn slice(&mut self) -> [&mut BorderEdge; 4] {
-        [
-            &mut self.top,
-            &mut self.right,
-            &mut self.bottom,
-            &mut self.left,
-        ]
-    }
-}
-
-pub struct BorderEdge {
-    pub color: Option<RinkColor>,
-    pub style: BorderStyle,
-    pub width: UnitSystem,
-    pub radius: UnitSystem,
-}
-
-impl Default for BorderEdge {
-    fn default() -> Self {
-        Self {
-            color: None,
-            style: BorderStyle::NONE,
-            width: UnitSystem::Point(0.0),
-            radius: UnitSystem::Point(0.0),
-        }
-    }
-}
-
-#[derive(Clone, Copy)]
-pub enum BorderStyle {
-    DOTTED,
-    DASHED,
-    SOLID,
-    DOUBLE,
-    GROOVE,
-    RIDGE,
-    INSET,
-    OUTSET,
-    HIDDEN,
-    NONE,
-}
-
-impl BorderStyle {
-    pub fn symbol_set(&self) -> Option<tui::symbols::line::Set> {
-        use tui::symbols::line::*;
-        const DASHED: Set = Set {
-            horizontal: "╌",
-            vertical: "╎",
-            ..NORMAL
-        };
-        const DOTTED: Set = Set {
-            horizontal: "┈",
-            vertical: "┊",
-            ..NORMAL
-        };
-        match self {
-            BorderStyle::DOTTED => Some(DOTTED),
-            BorderStyle::DASHED => Some(DASHED),
-            BorderStyle::SOLID => Some(NORMAL),
-            BorderStyle::DOUBLE => Some(DOUBLE),
-            BorderStyle::GROOVE => Some(NORMAL),
-            BorderStyle::RIDGE => Some(NORMAL),
-            BorderStyle::INSET => Some(NORMAL),
-            BorderStyle::OUTSET => Some(NORMAL),
-            BorderStyle::HIDDEN => None,
-            BorderStyle::NONE => None,
-        }
-    }
-}
-
-/// applies the entire html namespace defined in dioxus-html
-pub fn apply_attributes(
-    //
-    name: &str,
-    value: &str,
-    style: &mut StyleModifer,
-) {
-    match name {
-        "align-content"
-        | "align-items"
-        | "align-self" => apply_align(name, value, style),
-
-        "animation"
-        | "animation-delay"
-        | "animation-direction"
-        | "animation-duration"
-        | "animation-fill-mode"
-        | "animation-iteration-count"
-        | "animation-name"
-        | "animation-play-state"
-        | "animation-timing-function" => apply_animation(name, value, style),
-
-        "backface-visibility" => {}
-
-        "background"
-        | "background-attachment"
-        | "background-clip"
-        | "background-color"
-        | "background-image"
-        | "background-origin"
-        | "background-position"
-        | "background-repeat"
-        | "background-size" => apply_background(name, value, style),
-
-        "border"
-        | "border-bottom"
-        | "border-bottom-color"
-        | "border-bottom-left-radius"
-        | "border-bottom-right-radius"
-        | "border-bottom-style"
-        | "border-bottom-width"
-        | "border-collapse"
-        | "border-color"
-        | "border-image"
-        | "border-image-outset"
-        | "border-image-repeat"
-        | "border-image-slice"
-        | "border-image-source"
-        | "border-image-width"
-        | "border-left"
-        | "border-left-color"
-        | "border-left-style"
-        | "border-left-width"
-        | "border-radius"
-        | "border-right"
-        | "border-right-color"
-        | "border-right-style"
-        | "border-right-width"
-        | "border-spacing"
-        | "border-style"
-        | "border-top"
-        | "border-top-color"
-        | "border-top-left-radius"
-        | "border-top-right-radius"
-        | "border-top-style"
-        | "border-top-width"
-        | "border-width" => apply_border(name, value, style),
-
-        "bottom" => {}
-        "box-shadow" => {}
-        "box-sizing" => {}
-        "caption-side" => {}
-        "clear" => {}
-        "clip" => {}
-
-        "color" => {
-            if let Ok(c) = value.parse() {
-                style.tui_style.fg.replace(c);
-            }
-        }
-
-        "column-count"
-        | "column-fill"
-        | "column-gap"
-        | "column-rule"
-        | "column-rule-color"
-        | "column-rule-style"
-        | "column-rule-width"
-        | "column-span"
-        // add column-width
-        | "column-width" => apply_column(name, value, style),
-
-        "columns" => {}
-
-        "content" => {}
-        "counter-increment" => {}
-        "counter-reset" => {}
-
-        "cursor" => {}
-        "direction" => {
-            match value {
-                "ltr" => style.style.direction = Direction::LTR,
-                "rtl" => style.style.direction = Direction::RTL,
-                _ => {}
-            }
-        }
-
-        "display" => apply_display(name, value, style),
-
-        "empty-cells" => {}
-
-        "flex"
-        | "flex-basis"
-        | "flex-direction"
-        | "flex-flow"
-        | "flex-grow"
-        | "flex-shrink"
-        | "flex-wrap" => apply_flex(name, value, style),
-
-        "float" => {}
-
-        "font"
-        | "font-family"
-        | "font-size"
-        | "font-size-adjust"
-        | "font-stretch"
-        | "font-style"
-        | "font-variant"
-        | "font-weight" => apply_font(name, value, style),
-
-        "height" => {
-            if let Some(v) = parse_value(value){
-                style.style.size.height = match v {
-                    UnitSystem::Percent(v)=> Dimension::Percent(v/100.0),
-                    UnitSystem::Point(v)=> Dimension::Points(v),
-                };
-            }
-        }
-        "justify-content" => {
-            use JustifyContent::*;
-            style.style.justify_content = match value {
-                "flex-start" => FlexStart,
-                "flex-end" => FlexEnd,
-                "center" => Center,
-                "space-between" => SpaceBetween,
-                "space-around" => SpaceAround,
-                "space-evenly" => SpaceEvenly,
-                _ => FlexStart,
-            };
-        }
-        "left" => {}
-        "letter-spacing" => {}
-        "line-height" => {}
-
-        "list-style"
-        | "list-style-image"
-        | "list-style-position"
-        | "list-style-type" => {}
-
-        "margin"
-        | "margin-bottom"
-        | "margin-left"
-        | "margin-right"
-        | "margin-top" => apply_margin(name, value, style),
-
-        "max-height" => {}
-        "max-width" => {}
-        "min-height" => {}
-        "min-width" => {}
-
-        "opacity" => {}
-        "order" => {}
-        "outline" => {}
-
-        "outline-color"
-        | "outline-offset"
-        | "outline-style"
-        | "outline-width" => {}
-
-        "overflow"
-        | "overflow-x"
-        | "overflow-y" => apply_overflow(name, value, style),
-
-        "padding"
-        | "padding-bottom"
-        | "padding-left"
-        | "padding-right"
-        | "padding-top" => apply_padding(name, value, style),
-
-        "page-break-after"
-        | "page-break-before"
-        | "page-break-inside" => {}
-
-        "perspective"
-        | "perspective-origin" => {}
-
-        "position" => {
-            match value {
-                "static" => {}
-                "relative" => style.style.position_type = PositionType::Relative,
-                "fixed" => {}
-                "absolute" => style.style.position_type = PositionType::Absolute,
-                "sticky" => {}
-                _ => {}
-            }
-
-        }
-
-        "pointer-events" => {}
-
-        "quotes" => {}
-        "resize" => {}
-        "right" => {}
-        "tab-size" => {}
-        "table-layout" => {}
-
-        "text-align"
-        | "text-align-last"
-        | "text-decoration"
-        | "text-decoration-color"
-        | "text-decoration-line"
-        | "text-decoration-style"
-        | "text-indent"
-        | "text-justify"
-        | "text-overflow"
-        | "text-shadow"
-        | "text-transform" => apply_text(name, value, style),
-
-        "top" => {}
-
-        "transform"
-        | "transform-origin"
-        | "transform-style" => apply_transform(name, value, style),
-
-        "transition"
-        | "transition-delay"
-        | "transition-duration"
-        | "transition-property"
-        | "transition-timing-function" => apply_transition(name, value, style),
-
-        "vertical-align" => {}
-        "visibility" => {}
-        "white-space" => {}
-        "width" => {
-            if let Some(v) = parse_value(value){
-                style.style.size.width = match v {
-                    UnitSystem::Percent(v)=> Dimension::Percent(v/100.0),
-                    UnitSystem::Point(v)=> Dimension::Points(v),
-                };
-            }
-        }
-        "word-break" => {}
-        "word-spacing" => {}
-        "word-wrap" => {}
-        "z-index" => {}
-        _ => {}
-    }
-}
-
-#[derive(Clone, Copy)]
-pub enum UnitSystem {
-    Percent(f32),
-    Point(f32),
-}
-
-impl Into<Dimension> for UnitSystem {
-    fn into(self) -> Dimension {
-        match self {
-            Self::Percent(v) => Dimension::Percent(v),
-            Self::Point(v) => Dimension::Points(v),
-        }
-    }
-}
-
-fn parse_value(value: &str) -> Option<UnitSystem> {
-    if value.ends_with("px") {
-        if let Ok(px) = value.trim_end_matches("px").parse::<f32>() {
-            Some(UnitSystem::Point(px))
-        } else {
-            None
-        }
-    } else if value.ends_with('%') {
-        if let Ok(pct) = value.trim_end_matches('%').parse::<f32>() {
-            Some(UnitSystem::Percent(pct))
-        } else {
-            None
-        }
-    } else {
-        None
-    }
-}
-
-fn apply_overflow(name: &str, value: &str, style: &mut StyleModifer) {
-    match name {
-        // todo: add more overflow support to stretch2
-        "overflow" | "overflow-x" | "overflow-y" => {
-            style.style.overflow = match value {
-                "auto" => Overflow::Visible,
-                "hidden" => Overflow::Hidden,
-                "scroll" => Overflow::Scroll,
-                "visible" => Overflow::Visible,
-                _ => Overflow::Visible,
-            };
-        }
-        _ => {}
-    }
-}
-
-fn apply_display(_name: &str, value: &str, style: &mut StyleModifer) {
-    style.style.display = match value {
-        "flex" => Display::Flex,
-        "block" => Display::None,
-        _ => Display::Flex,
-    }
-
-    // TODO: there are way more variants
-    // stretch needs to be updated to handle them
-    //
-    // "block" => Display::Block,
-    // "inline" => Display::Inline,
-    // "inline-block" => Display::InlineBlock,
-    // "inline-table" => Display::InlineTable,
-    // "list-item" => Display::ListItem,
-    // "run-in" => Display::RunIn,
-    // "table" => Display::Table,
-    // "table-caption" => Display::TableCaption,
-    // "table-cell" => Display::TableCell,
-    // "table-column" => Display::TableColumn,
-    // "table-column-group" => Display::TableColumnGroup,
-    // "table-footer-group" => Display::TableFooterGroup,
-    // "table-header-group" => Display::TableHeaderGroup,
-    // "table-row" => Display::TableRow,
-    // "table-row-group" => Display::TableRowGroup,
-    // "none" => Display::None,
-    // _ => Display::Inline,
-}
-
-fn apply_background(name: &str, value: &str, style: &mut StyleModifer) {
-    match name {
-        "background-color" => {
-            if let Ok(c) = value.parse() {
-                style.tui_style.bg.replace(c);
-            }
-        }
-        "background" => {}
-        "background-attachment" => {}
-        "background-clip" => {}
-        "background-image" => {}
-        "background-origin" => {}
-        "background-position" => {}
-        "background-repeat" => {}
-        "background-size" => {}
-        _ => {}
-    }
-}
-
-fn apply_border(name: &str, value: &str, style: &mut StyleModifer) {
-    fn parse_border_style(v: &str) -> BorderStyle {
-        match v {
-            "dotted" => BorderStyle::DOTTED,
-            "dashed" => BorderStyle::DASHED,
-            "solid" => BorderStyle::SOLID,
-            "double" => BorderStyle::DOUBLE,
-            "groove" => BorderStyle::GROOVE,
-            "ridge" => BorderStyle::RIDGE,
-            "inset" => BorderStyle::INSET,
-            "outset" => BorderStyle::OUTSET,
-            "none" => BorderStyle::NONE,
-            "hidden" => BorderStyle::HIDDEN,
-            _ => todo!(),
-        }
-    }
-    match name {
-        "border" => {}
-        "border-bottom" => {}
-        "border-bottom-color" => {
-            if let Ok(c) = value.parse() {
-                style.tui_modifier.borders.bottom.color = Some(c);
-            }
-        }
-        "border-bottom-left-radius" => {
-            if let Some(v) = parse_value(value) {
-                style.tui_modifier.borders.left.radius = v;
-            }
-        }
-        "border-bottom-right-radius" => {
-            if let Some(v) = parse_value(value) {
-                style.tui_modifier.borders.right.radius = v;
-            }
-        }
-        "border-bottom-style" => {
-            if style.style.border.bottom == Dimension::default() {
-                let v = Dimension::Points(1.0);
-                style.style.border.bottom = v;
-            }
-            style.tui_modifier.borders.bottom.style = parse_border_style(value)
-        }
-        "border-bottom-width" => {
-            if let Some(v) = parse_value(value) {
-                style.tui_modifier.borders.bottom.width = v;
-                style.style.border.bottom = v.into();
-            }
-        }
-        "border-collapse" => {}
-        "border-color" => {
-            let values: Vec<_> = value.split(' ').collect();
-            if values.len() == 1 {
-                if let Ok(c) = values[0].parse() {
-                    style
-                        .tui_modifier
-                        .borders
-                        .slice()
-                        .iter_mut()
-                        .for_each(|b| b.color = Some(c));
-                }
-            } else {
-                for (v, b) in values
-                    .into_iter()
-                    .zip(style.tui_modifier.borders.slice().iter_mut())
-                {
-                    if let Ok(c) = v.parse() {
-                        b.color = Some(c);
-                    }
-                }
-            }
-        }
-        "border-image" => {}
-        "border-image-outset" => {}
-        "border-image-repeat" => {}
-        "border-image-slice" => {}
-        "border-image-source" => {}
-        "border-image-width" => {}
-        "border-left" => {}
-        "border-left-color" => {
-            if let Ok(c) = value.parse() {
-                style.tui_modifier.borders.left.color = Some(c);
-            }
-        }
-        "border-left-style" => {
-            if style.style.border.start == Dimension::default() {
-                let v = Dimension::Points(1.0);
-                style.style.border.start = v;
-            }
-            style.tui_modifier.borders.left.style = parse_border_style(value)
-        }
-        "border-left-width" => {
-            if let Some(v) = parse_value(value) {
-                style.tui_modifier.borders.left.width = v;
-                style.style.border.start = v.into();
-            }
-        }
-        "border-radius" => {
-            let values: Vec<_> = value.split(' ').collect();
-            if values.len() == 1 {
-                if let Some(r) = parse_value(values[0]) {
-                    style
-                        .tui_modifier
-                        .borders
-                        .slice()
-                        .iter_mut()
-                        .for_each(|b| b.radius = r);
-                }
-            } else {
-                for (v, b) in values
-                    .into_iter()
-                    .zip(style.tui_modifier.borders.slice().iter_mut())
-                {
-                    if let Some(r) = parse_value(v) {
-                        b.radius = r;
-                    }
-                }
-            }
-        }
-        "border-right" => {}
-        "border-right-color" => {
-            if let Ok(c) = value.parse() {
-                style.tui_modifier.borders.right.color = Some(c);
-            }
-        }
-        "border-right-style" => {
-            let v = Dimension::Points(1.0);
-            style.style.border.end = v;
-            style.tui_modifier.borders.right.style = parse_border_style(value)
-        }
-        "border-right-width" => {
-            if let Some(v) = parse_value(value) {
-                style.tui_modifier.borders.right.width = v;
-            }
-        }
-        "border-spacing" => {}
-        "border-style" => {
-            let values: Vec<_> = value.split(' ').collect();
-            if style.style.border.top == Dimension::default() {
-                let v = Dimension::Points(1.0);
-                style.style.border.top = v;
-            }
-            if style.style.border.bottom == Dimension::default() {
-                let v = Dimension::Points(1.0);
-                style.style.border.bottom = v;
-            }
-            if style.style.border.start == Dimension::default() {
-                let v = Dimension::Points(1.0);
-                style.style.border.start = v;
-            }
-            if style.style.border.end == Dimension::default() {
-                let v = Dimension::Points(1.0);
-                style.style.border.end = v;
-            }
-            if values.len() == 1 {
-                let border_style = parse_border_style(values[0]);
-                style
-                    .tui_modifier
-                    .borders
-                    .slice()
-                    .iter_mut()
-                    .for_each(|b| b.style = border_style);
-            } else {
-                for (v, b) in values
-                    .into_iter()
-                    .zip(style.tui_modifier.borders.slice().iter_mut())
-                {
-                    b.style = parse_border_style(v);
-                }
-            }
-        }
-        "border-top" => {}
-        "border-top-color" => {
-            if let Ok(c) = value.parse() {
-                style.tui_modifier.borders.top.color = Some(c);
-            }
-        }
-        "border-top-left-radius" => {
-            if let Some(v) = parse_value(value) {
-                style.tui_modifier.borders.left.radius = v;
-            }
-        }
-        "border-top-right-radius" => {
-            if let Some(v) = parse_value(value) {
-                style.tui_modifier.borders.right.radius = v;
-            }
-        }
-        "border-top-style" => {
-            if style.style.border.top == Dimension::default() {
-                let v = Dimension::Points(1.0);
-                style.style.border.top = v;
-            }
-            style.tui_modifier.borders.top.style = parse_border_style(value)
-        }
-        "border-top-width" => {
-            if let Some(v) = parse_value(value) {
-                style.style.border.top = v.into();
-                style.tui_modifier.borders.top.width = v;
-            }
-        }
-        "border-width" => {
-            let values: Vec<_> = value.split(' ').collect();
-            if values.len() == 1 {
-                if let Some(w) = parse_value(values[0]) {
-                    style.style.border.top = w.into();
-                    style.style.border.bottom = w.into();
-                    style.style.border.start = w.into();
-                    style.style.border.end = w.into();
-                    style
-                        .tui_modifier
-                        .borders
-                        .slice()
-                        .iter_mut()
-                        .for_each(|b| b.width = w);
-                }
-            } else {
-                let border_widths = [
-                    &mut style.style.border.top,
-                    &mut style.style.border.bottom,
-                    &mut style.style.border.start,
-                    &mut style.style.border.end,
-                ];
-                for ((v, b), width) in values
-                    .into_iter()
-                    .zip(style.tui_modifier.borders.slice().iter_mut())
-                    .zip(border_widths)
-                {
-                    if let Some(w) = parse_value(v) {
-                        *width = w.into();
-                        b.width = w;
-                    }
-                }
-            }
-        }
-        _ => (),
-    }
-}
-
-fn apply_animation(name: &str, _value: &str, _style: &mut StyleModifer) {
-    match name {
-        "animation" => {}
-        "animation-delay" => {}
-        "animation-direction =>{}" => {}
-        "animation-duration" => {}
-        "animation-fill-mode" => {}
-        "animation-itera =>{}tion-count" => {}
-        "animation-name" => {}
-        "animation-play-state" => {}
-        "animation-timing-function" => {}
-        _ => {}
-    }
-}
-
-fn apply_column(name: &str, _value: &str, _style: &mut StyleModifer) {
-    match name {
-        "column-count" => {}
-        "column-fill" => {}
-        "column-gap" => {}
-        "column-rule" => {}
-        "column-rule-color" => {}
-        "column-rule-style" => {}
-        "column-rule-width" => {}
-        "column-span" => {}
-        "column-width" => {}
-        _ => {}
-    }
-}
-
-fn apply_flex(name: &str, value: &str, style: &mut StyleModifer) {
-    // - [x] pub flex_direction: FlexDirection,
-    // - [x] pub flex_wrap: FlexWrap,
-    // - [x] pub flex_grow: f32,
-    // - [x] pub flex_shrink: f32,
-    // - [x] pub flex_basis: Dimension,
-
-    match name {
-        "flex" => {}
-        "flex-direction" => {
-            use FlexDirection::*;
-            style.style.flex_direction = match value {
-                "row" => Row,
-                "row-reverse" => RowReverse,
-                "column" => Column,
-                "column-reverse" => ColumnReverse,
-                _ => Row,
-            };
-        }
-        "flex-basis" => {
-            if let Some(v) = parse_value(value) {
-                style.style.flex_basis = match v {
-                    UnitSystem::Percent(v) => Dimension::Percent(v / 100.0),
-                    UnitSystem::Point(v) => Dimension::Points(v),
-                };
-            }
-        }
-        "flex-flow" => {}
-        "flex-grow" => {
-            if let Ok(val) = value.parse::<f32>() {
-                style.style.flex_grow = val;
-            }
-        }
-        "flex-shrink" => {
-            if let Ok(px) = value.parse::<f32>() {
-                style.style.flex_shrink = px;
-            }
-        }
-        "flex-wrap" => {
-            use FlexWrap::*;
-            style.style.flex_wrap = match value {
-                "nowrap" => NoWrap,
-                "wrap" => Wrap,
-                "wrap-reverse" => WrapReverse,
-                _ => NoWrap,
-            };
-        }
-        _ => {}
-    }
-}
-
-fn apply_font(name: &str, value: &str, style: &mut StyleModifer) {
-    use tui::style::Modifier;
-    match name {
-        "font" => (),
-        "font-family" => (),
-        "font-size" => (),
-        "font-size-adjust" => (),
-        "font-stretch" => (),
-        "font-style" => match value {
-            "italic" => style.tui_style = style.tui_style.add_modifier(Modifier::ITALIC),
-            "oblique" => style.tui_style = style.tui_style.add_modifier(Modifier::ITALIC),
-            _ => (),
-        },
-        "font-variant" => todo!(),
-        "font-weight" => match value {
-            "bold" => style.tui_style = style.tui_style.add_modifier(Modifier::BOLD),
-            "normal" => style.tui_style = style.tui_style.remove_modifier(Modifier::BOLD),
-            _ => (),
-        },
-        _ => (),
-    }
-}
-
-fn apply_padding(name: &str, value: &str, style: &mut StyleModifer) {
-    match parse_value(value) {
-        Some(UnitSystem::Percent(v)) => match name {
-            "padding" => {
-                let v = Dimension::Percent(v / 100.0);
-                style.style.padding.top = v;
-                style.style.padding.bottom = v;
-                style.style.padding.start = v;
-                style.style.padding.end = v;
-            }
-            "padding-bottom" => style.style.padding.bottom = Dimension::Percent(v / 100.0),
-            "padding-left" => style.style.padding.start = Dimension::Percent(v / 100.0),
-            "padding-right" => style.style.padding.end = Dimension::Percent(v / 100.0),
-            "padding-top" => style.style.padding.top = Dimension::Percent(v / 100.0),
-            _ => {}
-        },
-        Some(UnitSystem::Point(v)) => match name {
-            "padding" => {
-                style.style.padding.top = Dimension::Points(v);
-                style.style.padding.bottom = Dimension::Points(v);
-                style.style.padding.start = Dimension::Points(v);
-                style.style.padding.end = Dimension::Points(v);
-            }
-            "padding-bottom" => style.style.padding.bottom = Dimension::Points(v),
-            "padding-left" => style.style.padding.start = Dimension::Points(v),
-            "padding-right" => style.style.padding.end = Dimension::Points(v),
-            "padding-top" => style.style.padding.top = Dimension::Points(v),
-            _ => {}
-        },
-        None => {}
-    }
-}
-
-fn apply_text(name: &str, value: &str, style: &mut StyleModifer) {
-    use tui::style::Modifier;
-
-    match name {
-        "text-align" => todo!(),
-        "text-align-last" => todo!(),
-        "text-decoration" | "text-decoration-line" => {
-            for v in value.split(' ') {
-                match v {
-                    "line-through" => {
-                        style.tui_style = style.tui_style.add_modifier(Modifier::CROSSED_OUT)
-                    }
-                    "underline" => {
-                        style.tui_style = style.tui_style.add_modifier(Modifier::UNDERLINED)
-                    }
-                    _ => (),
-                }
-            }
-        }
-        "text-decoration-color" => todo!(),
-        "text-decoration-style" => todo!(),
-        "text-indent" => todo!(),
-        "text-justify" => todo!(),
-        "text-overflow" => todo!(),
-        "text-shadow" => todo!(),
-        "text-transform" => todo!(),
-        _ => todo!(),
-    }
-}
-
-fn apply_transform(_name: &str, _value: &str, _style: &mut StyleModifer) {
-    todo!()
-}
-
-fn apply_transition(_name: &str, _value: &str, _style: &mut StyleModifer) {
-    todo!()
-}
-
-fn apply_align(name: &str, value: &str, style: &mut StyleModifer) {
-    match name {
-        "align-items" => {
-            use AlignItems::*;
-            style.style.align_items = match value {
-                "flex-start" => FlexStart,
-                "flex-end" => FlexEnd,
-                "center" => Center,
-                "baseline" => Baseline,
-                "stretch" => Stretch,
-                _ => FlexStart,
-            };
-        }
-        "align-content" => {
-            use AlignContent::*;
-            style.style.align_content = match value {
-                "flex-start" => FlexStart,
-                "flex-end" => FlexEnd,
-                "center" => Center,
-                "space-between" => SpaceBetween,
-                "space-around" => SpaceAround,
-                _ => FlexStart,
-            };
-        }
-        "align-self" => {
-            use AlignSelf::*;
-            style.style.align_self = match value {
-                "auto" => Auto,
-                "flex-start" => FlexStart,
-                "flex-end" => FlexEnd,
-                "center" => Center,
-                "baseline" => Baseline,
-                "stretch" => Stretch,
-                _ => Auto,
-            };
-        }
-        _ => {}
-    }
-}
-
-pub fn apply_size(_name: &str, _value: &str, _style: &mut StyleModifer) {
-    //
-}
-
-pub fn apply_margin(name: &str, value: &str, style: &mut StyleModifer) {
-    match parse_value(value) {
-        Some(UnitSystem::Percent(v)) => match name {
-            "margin" => {
-                let v = Dimension::Percent(v / 100.0);
-                style.style.margin.top = v;
-                style.style.margin.bottom = v;
-                style.style.margin.start = v;
-                style.style.margin.end = v;
-            }
-            "margin-top" => style.style.margin.top = Dimension::Percent(v / 100.0),
-            "margin-bottom" => style.style.margin.bottom = Dimension::Percent(v / 100.0),
-            "margin-left" => style.style.margin.start = Dimension::Percent(v / 100.0),
-            "margin-right" => style.style.margin.end = Dimension::Percent(v / 100.0),
-            _ => {}
-        },
-        Some(UnitSystem::Point(v)) => match name {
-            "margin" => {
-                style.style.margin.top = Dimension::Points(v);
-                style.style.margin.bottom = Dimension::Points(v);
-                style.style.margin.start = Dimension::Points(v);
-                style.style.margin.end = Dimension::Points(v);
-            }
-            "margin-top" => style.style.margin.top = Dimension::Points(v),
-            "margin-bottom" => style.style.margin.bottom = Dimension::Points(v),
-            "margin-left" => style.style.margin.start = Dimension::Points(v),
-            "margin-right" => style.style.margin.end = Dimension::Points(v),
-            _ => {}
-        },
-        None => {}
-    }
-}

+ 2 - 2
packages/tui/src/config.rs

@@ -21,9 +21,9 @@ impl Config {
         }
     }
 
-    pub fn with_ctrl_c_quit(self) -> Self {
+    pub fn without_ctrl_c_quit(self) -> Self {
         Self {
-            ctrl_c_quit: true,
+            ctrl_c_quit: false,
             ..self
         }
     }

+ 446 - 344
packages/tui/src/hooks.rs

@@ -2,20 +2,25 @@ use crossterm::event::{
     Event as TermEvent, KeyCode as TermKeyCode, KeyModifiers, MouseButton, MouseEventKind,
 };
 use dioxus_core::*;
+use fxhash::{FxHashMap, FxHashSet};
 
+use dioxus_html::geometry::euclid::{Point2D, Rect, Size2D};
+use dioxus_html::geometry::{ClientPoint, Coordinates, ElementPoint, PagePoint, ScreenPoint};
+use dioxus_html::input_data::keyboard_types::Modifiers;
+use dioxus_html::input_data::MouseButtonSet as DioxusMouseButtons;
+use dioxus_html::input_data::{MouseButton as DioxusMouseButton, MouseButtonSet};
 use dioxus_html::{on::*, KeyCode};
-use futures::{channel::mpsc::UnboundedReceiver, StreamExt};
 use std::{
     any::Any,
     cell::RefCell,
-    collections::{HashMap, HashSet},
     rc::Rc,
     sync::Arc,
     time::{Duration, Instant},
 };
+use stretch2::geometry::{Point, Size};
 use stretch2::{prelude::Layout, Stretch};
 
-use crate::TuiNode;
+use crate::{Dom, Node};
 
 // a wrapper around the input state for easier access
 // todo: fix loop
@@ -31,12 +36,12 @@ use crate::TuiNode;
 
 //     pub fn mouse(&self) -> Option<MouseData> {
 //         let data = (**self.0).borrow();
-//         data.mouse.as_ref().map(|m| clone_mouse_data(m))
+//         data.mouse.as_ref().map(|m| m.clone())
 //     }
 
 //     pub fn wheel(&self) -> Option<WheelData> {
 //         let data = (**self.0).borrow();
-//         data.wheel.as_ref().map(|w| clone_wheel_data(w))
+//         data.wheel.as_ref().map(|w| w.clone())
 //     }
 
 //     pub fn screen(&self) -> Option<(u16, u16)> {
@@ -48,7 +53,7 @@ use crate::TuiNode;
 //         let data = (**self.0).borrow();
 //         data.last_key_pressed
 //             .as_ref()
-//             .map(|k| clone_keyboard_data(&k.0))
+//             .map(|k| &k.0.clone())
 //     }
 // }
 
@@ -75,7 +80,7 @@ impl EventData {
 const MAX_REPEAT_TIME: Duration = Duration::from_millis(100);
 
 pub struct InnerInputState {
-    mouse: Option<(MouseData, Vec<u16>)>,
+    mouse: Option<MouseData>,
     wheel: Option<WheelData>,
     last_key_pressed: Option<(KeyboardData, Instant)>,
     screen: Option<(u16, u16)>,
@@ -97,55 +102,39 @@ impl InnerInputState {
     fn apply_event(&mut self, evt: &mut EventCore) {
         match evt.1 {
             // limitations: only two buttons may be held at once
-            EventData::Mouse(ref mut m) => match &mut self.mouse {
-                Some(state) => {
-                    let mut buttons = state.0.buttons;
-                    state.0 = clone_mouse_data(m);
-                    match evt.0 {
-                        // this code only runs when there are no buttons down
-                        "mouseup" => {
-                            buttons = 0;
-                            state.1 = Vec::new();
-                        }
-                        "mousedown" => {
-                            if state.1.contains(&m.buttons) {
-                                // if we already pressed a button and there is another button released the button crossterm sends is the button remaining
-                                if state.1.len() > 1 {
-                                    evt.0 = "mouseup";
-                                    state.1 = vec![m.buttons];
-                                }
-                                // otherwise some other button was pressed. In testing it was consistantly this mapping
-                                else {
-                                    match m.buttons {
-                                        0x01 => state.1.push(0x02),
-                                        0x02 => state.1.push(0x01),
-                                        0x04 => state.1.push(0x01),
-                                        _ => (),
-                                    }
-                                }
-                            } else {
-                                state.1.push(m.buttons);
-                            }
+            EventData::Mouse(ref mut m) => {
+                let mut held_buttons = match &self.mouse {
+                    Some(previous_data) => previous_data.held_buttons(),
+                    None => MouseButtonSet::empty(),
+                };
 
-                            buttons = state.1.iter().copied().reduce(|a, b| a | b).unwrap();
-                        }
-                        _ => (),
+                match evt.0 {
+                    "mousedown" => {
+                        held_buttons.insert(
+                            m.trigger_button()
+                                .expect("No trigger button for mousedown event"),
+                        );
                     }
-                    state.0.buttons = buttons;
-                    m.buttons = buttons;
-                }
-                None => {
-                    self.mouse = Some((
-                        clone_mouse_data(m),
-                        if m.buttons == 0 {
-                            Vec::new()
-                        } else {
-                            vec![m.buttons]
-                        },
-                    ));
+                    "mouseup" => {
+                        held_buttons.remove(
+                            m.trigger_button()
+                                .expect("No trigger button for mouseup event"),
+                        );
+                    }
+                    _ => {}
                 }
-            },
-            EventData::Wheel(ref w) => self.wheel = Some(clone_wheel_data(w)),
+
+                let new_mouse_data = MouseData::new(
+                    m.coordinates(),
+                    m.trigger_button(),
+                    held_buttons,
+                    m.modifiers(),
+                );
+
+                self.mouse = Some(new_mouse_data.clone());
+                *m = new_mouse_data;
+            }
+            EventData::Wheel(ref w) => self.wheel = Some(w.clone()),
             EventData::Screen(ref s) => self.screen = Some(*s),
             EventData::Keyboard(ref mut k) => {
                 let repeat = self
@@ -154,195 +143,343 @@ impl InnerInputState {
                     .filter(|k2| k2.0.key == k.key && k2.1.elapsed() < MAX_REPEAT_TIME)
                     .is_some();
                 k.repeat = repeat;
-                let new = clone_keyboard_data(k);
+                let new = k.clone();
                 self.last_key_pressed = Some((new, Instant::now()));
             }
         }
     }
 
-    fn update<'a>(
+    fn update(
         &mut self,
-        dom: &'a VirtualDom,
-        evts: &mut Vec<EventCore>,
+        evts: &mut [EventCore],
         resolved_events: &mut Vec<UserEvent>,
         layout: &Stretch,
-        layouts: &mut HashMap<ElementId, TuiNode<'a>>,
-        node: &'a VNode<'a>,
+        dom: &mut Dom,
     ) {
-        struct Data<'b> {
-            new_pos: (i32, i32),
-            old_pos: Option<(i32, i32)>,
-            clicked: bool,
-            released: bool,
-            wheel_delta: f64,
-            mouse_data: &'b MouseData,
-            wheel_data: &'b Option<WheelData>,
+        let previous_mouse = self.mouse.clone();
+
+        self.wheel = None;
+
+        for e in evts.iter_mut() {
+            self.apply_event(e);
         }
 
-        fn layout_contains_point(layout: &Layout, point: (i32, i32)) -> bool {
-            layout.location.x as i32 <= point.0
-                && layout.location.x as i32 + layout.size.width as i32 >= point.0
-                && layout.location.y as i32 <= point.1
-                && layout.location.y as i32 + layout.size.height as i32 >= point.1
+        self.resolve_mouse_events(previous_mouse, resolved_events, layout, dom);
+
+        // for s in &self.subscribers {
+        //     s();
+        // }
+    }
+
+    fn resolve_mouse_events(
+        &self,
+        previous_mouse: Option<MouseData>,
+        resolved_events: &mut Vec<UserEvent>,
+        layout: &Stretch,
+        dom: &mut Dom,
+    ) {
+        fn layout_contains_point(layout: &Layout, point: ScreenPoint) -> bool {
+            let Point { x, y } = layout.location;
+            let Size { width, height } = layout.size;
+
+            let layout_rect = Rect::new(Point2D::new(x, y), Size2D::new(width, height));
+            layout_rect.contains(point.cast())
         }
 
-        fn get_mouse_events<'c, 'd>(
-            dom: &'c VirtualDom,
+        fn try_create_event(
+            name: &'static str,
+            data: Arc<dyn Any + Send + Sync>,
+            will_bubble: &mut FxHashSet<ElementId>,
             resolved_events: &mut Vec<UserEvent>,
-            layout: &Stretch,
-            layouts: &HashMap<ElementId, TuiNode<'c>>,
-            node: &'c VNode<'c>,
-            data: &'d Data<'d>,
-        ) -> HashSet<&'static str> {
-            match node {
-                VNode::Fragment(f) => {
-                    let mut union = HashSet::new();
-                    for child in f.children {
-                        union = union
-                            .union(&get_mouse_events(
-                                dom,
+            node: &Node,
+            dom: &Dom,
+        ) {
+            // only trigger event if the event was not triggered already by a child
+            if will_bubble.insert(node.id) {
+                let mut parent = node.parent;
+                while let Some(parent_id) = parent {
+                    will_bubble.insert(parent_id);
+                    parent = dom[parent_id.0].parent;
+                }
+                resolved_events.push(UserEvent {
+                    scope_id: None,
+                    priority: EventPriority::Medium,
+                    name,
+                    element: Some(node.id),
+                    data,
+                })
+            }
+        }
+
+        fn prepare_mouse_data(mouse_data: &MouseData, layout: &Layout) -> MouseData {
+            let Point { x, y } = layout.location;
+            let node_origin = ClientPoint::new(x.into(), y.into());
+
+            let new_client_coordinates = (mouse_data.client_coordinates() - node_origin)
+                .to_point()
+                .cast_unit();
+
+            let coordinates = Coordinates::new(
+                mouse_data.screen_coordinates(),
+                mouse_data.client_coordinates(),
+                new_client_coordinates,
+                mouse_data.page_coordinates(),
+            );
+
+            MouseData::new(
+                coordinates,
+                mouse_data.trigger_button(),
+                mouse_data.held_buttons(),
+                mouse_data.modifiers(),
+            )
+        }
+
+        if let Some(mouse_data) = &self.mouse {
+            let new_pos = mouse_data.screen_coordinates();
+            let old_pos = previous_mouse.as_ref().map(|m| m.screen_coordinates());
+
+            // a mouse button is pressed if a button was not down and is now down
+            let previous_buttons = previous_mouse
+                .map_or(MouseButtonSet::empty(), |previous_data| {
+                    previous_data.held_buttons()
+                });
+            let was_pressed = !(mouse_data.held_buttons() - previous_buttons).is_empty();
+
+            // a mouse button is released if a button was down and is now not down
+            let was_released = !(previous_buttons - mouse_data.held_buttons()).is_empty();
+
+            let wheel_delta = self.wheel.as_ref().map_or(0.0, |w| w.delta_y);
+            let wheel_data = &self.wheel;
+
+            {
+                // mousemove
+                if old_pos != Some(new_pos) {
+                    let mut will_bubble = FxHashSet::default();
+                    for node in dom.get_listening_sorted("mousemove") {
+                        let node_layout = layout.layout(node.state.layout.node.unwrap()).unwrap();
+                        let previously_contained = old_pos
+                            .filter(|pos| layout_contains_point(node_layout, *pos))
+                            .is_some();
+                        let currently_contains = layout_contains_point(node_layout, new_pos);
+
+                        if currently_contains && previously_contained {
+                            try_create_event(
+                                "mousemove",
+                                Arc::new(prepare_mouse_data(mouse_data, node_layout)),
+                                &mut will_bubble,
                                 resolved_events,
-                                layout,
-                                layouts,
-                                child,
-                                data,
-                            ))
-                            .copied()
-                            .collect();
+                                node,
+                                dom,
+                            );
+                        }
                     }
-                    return union;
                 }
+            }
 
-                VNode::Component(vcomp) => {
-                    let idx = vcomp.scope.get().unwrap();
-                    let new_node = dom.get_scope(idx).unwrap().root_node();
-                    return get_mouse_events(dom, resolved_events, layout, layouts, new_node, data);
+            {
+                // mouseenter
+                let mut will_bubble = FxHashSet::default();
+                for node in dom.get_listening_sorted("mouseenter") {
+                    let node_layout = layout.layout(node.state.layout.node.unwrap()).unwrap();
+                    let previously_contained = old_pos
+                        .filter(|pos| layout_contains_point(node_layout, *pos))
+                        .is_some();
+                    let currently_contains = layout_contains_point(node_layout, new_pos);
+
+                    if currently_contains && !previously_contained {
+                        try_create_event(
+                            "mouseenter",
+                            Arc::new(mouse_data.clone()),
+                            &mut will_bubble,
+                            resolved_events,
+                            node,
+                            dom,
+                        );
+                    }
                 }
-
-                VNode::Placeholder(_) => return HashSet::new(),
-
-                VNode::Element(_) | VNode::Text(_) => {}
             }
 
-            let id = node.try_mounted_id().unwrap();
-            let node = layouts.get(&id).unwrap();
+            {
+                // mouseover
+                let mut will_bubble = FxHashSet::default();
+                for node in dom.get_listening_sorted("mouseover") {
+                    let node_layout = layout.layout(node.state.layout.node.unwrap()).unwrap();
+                    let previously_contained = old_pos
+                        .filter(|pos| layout_contains_point(node_layout, *pos))
+                        .is_some();
+                    let currently_contains = layout_contains_point(node_layout, new_pos);
+
+                    if currently_contains && !previously_contained {
+                        try_create_event(
+                            "mouseover",
+                            Arc::new(prepare_mouse_data(mouse_data, node_layout)),
+                            &mut will_bubble,
+                            resolved_events,
+                            node,
+                            dom,
+                        );
+                    }
+                }
+            }
 
-            let node_layout = layout.layout(node.layout).unwrap();
+            // mousedown
+            if was_pressed {
+                let mut will_bubble = FxHashSet::default();
+                for node in dom.get_listening_sorted("mousedown") {
+                    let node_layout = layout.layout(node.state.layout.node.unwrap()).unwrap();
+                    let currently_contains = layout_contains_point(node_layout, new_pos);
 
-            let previously_contained = data
-                .old_pos
-                .filter(|pos| layout_contains_point(node_layout, *pos))
-                .is_some();
-            let currently_contains = layout_contains_point(node_layout, data.new_pos);
+                    if currently_contains {
+                        try_create_event(
+                            "mousedown",
+                            Arc::new(prepare_mouse_data(mouse_data, node_layout)),
+                            &mut will_bubble,
+                            resolved_events,
+                            node,
+                            dom,
+                        );
+                    }
+                }
+            }
 
-            match node.node {
-                VNode::Element(el) => {
-                    let mut events = HashSet::new();
-                    if previously_contained || currently_contains {
-                        for c in el.children {
-                            events = events
-                                .union(&get_mouse_events(
-                                    dom,
-                                    resolved_events,
-                                    layout,
-                                    layouts,
-                                    c,
-                                    data,
-                                ))
-                                .copied()
-                                .collect();
+            {
+                // mouseup
+                if was_released {
+                    let mut will_bubble = FxHashSet::default();
+                    for node in dom.get_listening_sorted("mouseup") {
+                        let node_layout = layout.layout(node.state.layout.node.unwrap()).unwrap();
+                        let currently_contains = layout_contains_point(node_layout, new_pos);
+
+                        if currently_contains {
+                            try_create_event(
+                                "mouseup",
+                                Arc::new(prepare_mouse_data(mouse_data, node_layout)),
+                                &mut will_bubble,
+                                resolved_events,
+                                node,
+                                dom,
+                            );
                         }
                     }
-                    let mut try_create_event = |name| {
-                        // only trigger event if the event was not triggered already by a child
-                        if events.insert(name) {
-                            resolved_events.push(UserEvent {
-                                scope_id: None,
-                                priority: EventPriority::Medium,
-                                name,
-                                element: Some(el.id.get().unwrap()),
-                                data: Arc::new(clone_mouse_data(data.mouse_data)),
-                            })
-                        }
-                    };
-                    if currently_contains {
-                        if !previously_contained {
-                            try_create_event("mouseenter");
-                            try_create_event("mouseover");
-                        }
-                        if data.clicked {
-                            try_create_event("mousedown");
+                }
+            }
+
+            {
+                // click
+                if mouse_data.trigger_button() == Some(DioxusMouseButton::Primary) && was_released {
+                    let mut will_bubble = FxHashSet::default();
+                    for node in dom.get_listening_sorted("click") {
+                        let node_layout = layout.layout(node.state.layout.node.unwrap()).unwrap();
+                        let currently_contains = layout_contains_point(node_layout, new_pos);
+
+                        if currently_contains {
+                            try_create_event(
+                                "click",
+                                Arc::new(prepare_mouse_data(mouse_data, node_layout)),
+                                &mut will_bubble,
+                                resolved_events,
+                                node,
+                                dom,
+                            );
                         }
-                        if data.released {
-                            try_create_event("mouseup");
-                            match data.mouse_data.button {
-                                0 => try_create_event("click"),
-                                2 => try_create_event("contextmenu"),
-                                _ => (),
-                            }
+                    }
+                }
+            }
+
+            {
+                // contextmenu
+                if mouse_data.trigger_button() == Some(DioxusMouseButton::Secondary) && was_released
+                {
+                    let mut will_bubble = FxHashSet::default();
+                    for node in dom.get_listening_sorted("contextmenu") {
+                        let node_layout = layout.layout(node.state.layout.node.unwrap()).unwrap();
+                        let currently_contains = layout_contains_point(node_layout, new_pos);
+
+                        if currently_contains {
+                            try_create_event(
+                                "contextmenu",
+                                Arc::new(prepare_mouse_data(mouse_data, node_layout)),
+                                &mut will_bubble,
+                                resolved_events,
+                                node,
+                                dom,
+                            );
                         }
-                        if let Some(w) = data.wheel_data {
-                            if data.wheel_delta != 0.0 {
-                                resolved_events.push(UserEvent {
-                                    scope_id: None,
-                                    priority: EventPriority::Medium,
-                                    name: "wheel",
-                                    element: Some(el.id.get().unwrap()),
-                                    data: Arc::new(clone_wheel_data(w)),
-                                })
+                    }
+                }
+            }
+
+            {
+                // wheel
+                if let Some(w) = wheel_data {
+                    if wheel_delta != 0.0 {
+                        let mut will_bubble = FxHashSet::default();
+                        for node in dom.get_listening_sorted("wheel") {
+                            let node_layout =
+                                layout.layout(node.state.layout.node.unwrap()).unwrap();
+                            let currently_contains = layout_contains_point(node_layout, new_pos);
+
+                            if currently_contains {
+                                try_create_event(
+                                    "wheel",
+                                    Arc::new(w.clone()),
+                                    &mut will_bubble,
+                                    resolved_events,
+                                    node,
+                                    dom,
+                                );
                             }
                         }
-                    } else if previously_contained {
-                        try_create_event("mouseleave");
-                        try_create_event("mouseout");
                     }
-                    events
                 }
-                VNode::Text(_) => HashSet::new(),
-                _ => todo!(),
             }
-        }
-
-        let previous_mouse = self
-            .mouse
-            .as_ref()
-            .map(|m| (clone_mouse_data(&m.0), m.1.clone()));
-        // println!("{previous_mouse:?}");
-
-        self.wheel = None;
 
-        for e in evts.iter_mut() {
-            self.apply_event(e);
-        }
+            {
+                // mouseleave
+                let mut will_bubble = FxHashSet::default();
+                for node in dom.get_listening_sorted("mouseleave") {
+                    let node_layout = layout.layout(node.state.layout.node.unwrap()).unwrap();
+                    let previously_contained = old_pos
+                        .filter(|pos| layout_contains_point(node_layout, *pos))
+                        .is_some();
+                    let currently_contains = layout_contains_point(node_layout, new_pos);
+
+                    if !currently_contains && previously_contained {
+                        try_create_event(
+                            "mouseleave",
+                            Arc::new(prepare_mouse_data(mouse_data, node_layout)),
+                            &mut will_bubble,
+                            resolved_events,
+                            node,
+                            dom,
+                        );
+                    }
+                }
+            }
 
-        // resolve hover events
-        if let Some(mouse) = &self.mouse {
-            let new_pos = (mouse.0.screen_x, mouse.0.screen_y);
-            let old_pos = previous_mouse
-                .as_ref()
-                .map(|m| (m.0.screen_x, m.0.screen_y));
-            let clicked =
-                (!mouse.0.buttons & previous_mouse.as_ref().map(|m| m.0.buttons).unwrap_or(0)) > 0;
-            let released =
-                (mouse.0.buttons & !previous_mouse.map(|m| m.0.buttons).unwrap_or(0)) > 0;
-            let wheel_delta = self.wheel.as_ref().map_or(0.0, |w| w.delta_y);
-            let mouse_data = &mouse.0;
-            let wheel_data = &self.wheel;
-            let data = Data {
-                new_pos,
-                old_pos,
-                clicked,
-                released,
-                wheel_delta,
-                mouse_data,
-                wheel_data,
-            };
-            get_mouse_events(dom, resolved_events, layout, layouts, node, &data);
+            {
+                // mouseout
+                let mut will_bubble = FxHashSet::default();
+                for node in dom.get_listening_sorted("mouseout") {
+                    let node_layout = layout.layout(node.state.layout.node.unwrap()).unwrap();
+                    let previously_contained = old_pos
+                        .filter(|pos| layout_contains_point(node_layout, *pos))
+                        .is_some();
+                    let currently_contains = layout_contains_point(node_layout, new_pos);
+
+                    if !currently_contains && previously_contained {
+                        try_create_event(
+                            "mouseout",
+                            Arc::new(prepare_mouse_data(mouse_data, node_layout)),
+                            &mut will_bubble,
+                            resolved_events,
+                            node,
+                            dom,
+                        );
+                    }
+                }
+            }
         }
-
-        // for s in &self.subscribers {
-        //     s();
-        // }
     }
 
     // fn subscribe(&mut self, f: Rc<dyn Fn() + 'static>) {
@@ -358,24 +495,21 @@ pub struct RinkInputHandler {
 impl RinkInputHandler {
     /// global context that handles events
     /// limitations: GUI key modifier is never detected, key up events are not detected, and only two mouse buttons may be pressed at once
-    pub fn new(
-        mut receiver: UnboundedReceiver<TermEvent>,
-        cx: &ScopeState,
-    ) -> (Self, Rc<RefCell<InnerInputState>>) {
+    pub fn new() -> (
+        Self,
+        Rc<RefCell<InnerInputState>>,
+        impl FnMut(crossterm::event::Event),
+    ) {
         let queued_events = Rc::new(RefCell::new(Vec::new()));
-        let queued_events2 = Rc::<RefCell<std::vec::Vec<_>>>::downgrade(&queued_events);
-
-        cx.push_future(async move {
-            while let Some(evt) = receiver.next().await {
-                if let Some(evt) = get_event(evt) {
-                    if let Some(v) = queued_events2.upgrade() {
-                        (*v).borrow_mut().push(evt);
-                    } else {
-                        break;
-                    }
+        let queued_events2 = Rc::downgrade(&queued_events);
+
+        let regester_event = move |evt: crossterm::event::Event| {
+            if let Some(evt) = get_event(evt) {
+                if let Some(v) = queued_events2.upgrade() {
+                    (*v).borrow_mut().push(evt);
                 }
             }
-        });
+        };
 
         let state = Rc::new(RefCell::new(InnerInputState::new()));
 
@@ -385,73 +519,65 @@ impl RinkInputHandler {
                 queued_events,
             },
             state,
+            regester_event,
         )
     }
 
-    pub fn get_events<'a>(
-        &self,
-        dom: &'a VirtualDom,
-        layout: &Stretch,
-        layouts: &mut HashMap<ElementId, TuiNode<'a>>,
-        node: &'a VNode<'a>,
-    ) -> Vec<UserEvent> {
-        // todo: currently resolves events in all nodes, but once the focus system is added it should filter by focus
-        fn inner(
-            queue: &[(&'static str, Arc<dyn Any + Send + Sync>)],
-            resolved: &mut Vec<UserEvent>,
-            node: &VNode,
-        ) {
-            match node {
-                VNode::Fragment(frag) => {
-                    for c in frag.children {
-                        inner(queue, resolved, c);
-                    }
-                }
-                VNode::Element(el) => {
-                    for l in el.listeners {
-                        for (name, data) in queue.iter() {
-                            if *name == l.event {
-                                if let Some(id) = el.id.get() {
-                                    resolved.push(UserEvent {
-                                        scope_id: None,
-                                        priority: EventPriority::Medium,
-                                        name: *name,
-                                        element: Some(id),
-                                        data: data.clone(),
-                                    });
-                                }
-                            }
-                        }
-                    }
-                    for c in el.children {
-                        inner(queue, resolved, c);
-                    }
-                }
-                _ => (),
-            }
-        }
-
+    pub(crate) fn get_events(&self, layout: &Stretch, dom: &mut Dom) -> Vec<UserEvent> {
         let mut resolved_events = Vec::new();
 
         (*self.state).borrow_mut().update(
-            dom,
             &mut (*self.queued_events).borrow_mut(),
             &mut resolved_events,
             layout,
-            layouts,
-            node,
+            dom,
         );
 
-        let events: Vec<_> = self
+        let events = self
             .queued_events
             .replace(Vec::new())
             .into_iter()
             // these events were added in the update stage
-            .filter(|e| !["mousedown", "mouseup", "mousemove", "drag", "wheel"].contains(&e.0))
-            .map(|e| (e.0, e.1.into_any()))
-            .collect();
+            .filter(|e| {
+                ![
+                    "mouseenter",
+                    "mouseover",
+                    "mouseleave",
+                    "mouseout",
+                    "mousedown",
+                    "mouseup",
+                    "mousemove",
+                    "drag",
+                    "wheel",
+                    "click",
+                    "contextmenu",
+                ]
+                .contains(&e.0)
+            })
+            .map(|evt| (evt.0, evt.1.into_any()));
 
-        inner(&events, &mut resolved_events, node);
+        // todo: currently resolves events in all nodes, but once the focus system is added it should filter by focus
+        let mut hm: FxHashMap<&'static str, Vec<Arc<dyn Any + Send + Sync>>> = FxHashMap::default();
+        for (event, data) in events {
+            if let Some(v) = hm.get_mut(event) {
+                v.push(data);
+            } else {
+                hm.insert(event, vec![data]);
+            }
+        }
+        for (event, datas) in hm {
+            for node in dom.get_listening_sorted(event) {
+                for data in &datas {
+                    resolved_events.push(UserEvent {
+                        scope_id: None,
+                        priority: EventPriority::Medium,
+                        name: event,
+                        element: Some(node.id),
+                        data: data.clone(),
+                    });
+                }
+            }
+        }
 
         resolved_events
     }
@@ -468,34 +594,47 @@ fn get_event(evt: TermEvent) -> Option<(&'static str, EventData)> {
             let ctrl = m.modifiers.contains(KeyModifiers::CONTROL);
             let meta = false;
 
-            let get_mouse_data = |b| {
-                let buttons = match b {
-                    None => 0,
-                    Some(MouseButton::Left) => 1,
-                    Some(MouseButton::Right) => 2,
-                    Some(MouseButton::Middle) => 4,
-                };
-                let button_state = match b {
-                    None => 0,
-                    Some(MouseButton::Left) => 0,
-                    Some(MouseButton::Middle) => 1,
-                    Some(MouseButton::Right) => 2,
-                };
+            let get_mouse_data = |crossterm_button: Option<MouseButton>| {
+                let button = crossterm_button.map(|b| match b {
+                    MouseButton::Left => DioxusMouseButton::Primary,
+                    MouseButton::Right => DioxusMouseButton::Secondary,
+                    MouseButton::Middle => DioxusMouseButton::Auxiliary,
+                });
+
                 // from https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent
-                EventData::Mouse(MouseData {
-                    alt_key: alt,
-                    button: button_state,
-                    buttons,
-                    client_x: x,
-                    client_y: y,
-                    ctrl_key: ctrl,
-                    meta_key: meta,
-                    page_x: x,
-                    page_y: y,
-                    screen_x: x,
-                    screen_y: y,
-                    shift_key: shift,
-                })
+
+                // The `page` and `screen` coordinates are inconsistent with the MDN definition, as they are relative to the viewport (client), not the target element/page/screen, respectively.
+                // todo?
+                // But then, MDN defines them in terms of pixels, yet crossterm provides only row/column, and it might not be possible to get pixels. So we can't get 100% consistency anyway.
+                let coordinates = Coordinates::new(
+                    ScreenPoint::new(x, y),
+                    ClientPoint::new(x, y),
+                    // offset x/y are set when the origin of the event is assigned to an element
+                    ElementPoint::new(0., 0.),
+                    PagePoint::new(x, y),
+                );
+
+                let mut modifiers = Modifiers::empty();
+                if shift {
+                    modifiers.insert(Modifiers::SHIFT);
+                }
+                if ctrl {
+                    modifiers.insert(Modifiers::CONTROL);
+                }
+                if meta {
+                    modifiers.insert(Modifiers::META);
+                }
+                if alt {
+                    modifiers.insert(Modifiers::ALT);
+                }
+
+                // held mouse buttons get set later by maintaining state, as crossterm does not provide them
+                EventData::Mouse(MouseData::new(
+                    coordinates,
+                    button,
+                    DioxusMouseButtons::empty(),
+                    modifiers,
+                ))
             };
 
             let get_wheel_data = |up| {
@@ -525,6 +664,7 @@ fn get_event(evt: TermEvent) -> Option<(&'static str, EventData)> {
 
 fn translate_key_event(event: crossterm::event::KeyEvent) -> Option<EventData> {
     let (code, key_str);
+    let mut shift_key = event.modifiers.contains(KeyModifiers::SHIFT);
     if let TermKeyCode::Char(c) = event.code {
         code = match c {
             'A'..='Z' | 'a'..='z' => match c.to_ascii_uppercase() {
@@ -638,7 +778,11 @@ fn translate_key_event(event: crossterm::event::KeyEvent) -> Option<EventData> {
                 12 => KeyCode::F12,
                 _ => return None,
             },
-            TermKeyCode::BackTab => return None,
+            // backtab is Shift + Tab
+            TermKeyCode::BackTab => {
+                shift_key = true;
+                KeyCode::Tab
+            }
             TermKeyCode::Null => return None,
             _ => return None,
         };
@@ -651,57 +795,15 @@ fn translate_key_event(event: crossterm::event::KeyEvent) -> Option<EventData> {
     // from https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent
     Some(EventData::Keyboard(KeyboardData {
         char_code: code.raw_code(),
-        key: key_str.to_string(),
+        key: key_str,
         key_code: code,
         alt_key: event.modifiers.contains(KeyModifiers::ALT),
         ctrl_key: event.modifiers.contains(KeyModifiers::CONTROL),
         meta_key: false,
-        shift_key: event.modifiers.contains(KeyModifiers::SHIFT),
+        shift_key,
         locale: Default::default(),
         location: 0x00,
         repeat: Default::default(),
         which: Default::default(),
     }))
 }
-
-fn clone_mouse_data(m: &MouseData) -> MouseData {
-    MouseData {
-        client_x: m.client_x,
-        client_y: m.client_y,
-        page_x: m.page_x,
-        page_y: m.page_y,
-        screen_x: m.screen_x,
-        screen_y: m.screen_y,
-        alt_key: m.alt_key,
-        ctrl_key: m.ctrl_key,
-        meta_key: m.meta_key,
-        shift_key: m.shift_key,
-        button: m.button,
-        buttons: m.buttons,
-    }
-}
-
-fn clone_keyboard_data(k: &KeyboardData) -> KeyboardData {
-    KeyboardData {
-        char_code: k.char_code,
-        key: k.key.clone(),
-        key_code: k.key_code,
-        alt_key: k.alt_key,
-        ctrl_key: k.ctrl_key,
-        meta_key: k.meta_key,
-        shift_key: k.shift_key,
-        locale: k.locale.clone(),
-        location: k.location,
-        repeat: k.repeat,
-        which: k.which,
-    }
-}
-
-fn clone_wheel_data(w: &WheelData) -> WheelData {
-    WheelData {
-        delta_mode: w.delta_mode,
-        delta_x: w.delta_x,
-        delta_y: w.delta_y,
-        delta_z: w.delta_x,
-    }
-}

+ 248 - 102
packages/tui/src/layout.rs

@@ -1,32 +1,64 @@
+use std::cell::RefCell;
+use std::rc::Rc;
+
 use dioxus_core::*;
-use std::collections::HashMap;
+use dioxus_native_core::layout_attributes::apply_layout_attributes;
+use dioxus_native_core::node_ref::{AttributeMask, NodeMask, NodeView};
+use dioxus_native_core::state::ChildDepState;
+use dioxus_native_core_macro::sorted_str_slice;
+use stretch2::prelude::*;
 
-use crate::{
-    attributes::{apply_attributes, StyleModifer},
-    style::RinkStyle,
-    TuiModifier, TuiNode,
-};
+#[derive(Debug, Clone, Copy, PartialEq)]
+pub(crate) enum PossiblyUninitalized<T> {
+    Uninitalized,
+    Initialized(T),
+}
+impl<T> PossiblyUninitalized<T> {
+    pub fn unwrap(self) -> T {
+        match self {
+            Self::Initialized(i) => i,
+            _ => panic!(),
+        }
+    }
+}
+impl<T> Default for PossiblyUninitalized<T> {
+    fn default() -> Self {
+        Self::Uninitalized
+    }
+}
 
-/*
-The layout system uses the lineheight as one point.
+#[derive(Clone, PartialEq, Default, Debug)]
+pub(crate) struct StretchLayout {
+    pub style: Style,
+    pub node: PossiblyUninitalized<Node>,
+}
 
-stretch uses fractional points, so we can rasterize if we need too, but not with characters
-this means anything thats "1px" is 1 lineheight. Unfortunately, text cannot be smaller or bigger
-*/
-pub fn collect_layout<'a>(
-    layout: &mut stretch2::Stretch,
-    nodes: &mut HashMap<ElementId, TuiNode<'a>>,
-    vdom: &'a VirtualDom,
-    node: &'a VNode<'a>,
-) {
-    use stretch2::prelude::*;
+impl ChildDepState for StretchLayout {
+    type Ctx = Rc<RefCell<Stretch>>;
+    type DepState = Self;
+    // use tag to force this to be called when a node is built
+    const NODE_MASK: NodeMask =
+        NodeMask::new_with_attrs(AttributeMask::Static(SORTED_LAYOUT_ATTRS))
+            .with_text()
+            .with_tag();
 
-    match node {
-        VNode::Text(t) => {
-            let id = t.id.get().unwrap();
-            let char_len = t.text.chars().count();
+    /// Setup the layout
+    fn reduce<'a>(
+        &mut self,
+        node: NodeView,
+        children: impl Iterator<Item = &'a Self::DepState>,
+        ctx: &Self::Ctx,
+    ) -> bool
+    where
+        Self::DepState: 'a,
+    {
+        let mut changed = false;
+        let mut stretch = ctx.borrow_mut();
+        let mut style = Style::default();
+        if let Some(text) = node.text() {
+            let char_len = text.chars().count();
 
-            let style = Style {
+            style = Style {
                 size: Size {
                     // characters are 1 point tall
                     height: Dimension::Points(1.0),
@@ -36,96 +68,210 @@ pub fn collect_layout<'a>(
                 },
                 ..Default::default()
             };
-
-            nodes.insert(
-                id,
-                TuiNode {
-                    node,
-                    block_style: RinkStyle::default(),
-                    tui_modifier: TuiModifier::default(),
-                    layout: layout.new_node(style, &[]).unwrap(),
-                },
-            );
-        }
-        VNode::Element(el) => {
-            // gather up all the styles from the attribute list
-            let mut modifier = StyleModifer {
-                style: Style::default(),
-                tui_style: RinkStyle::default(),
-                tui_modifier: TuiModifier::default(),
-            };
-
-            // handle text modifier elements
-            if el.namespace.is_none() {
-                match el.tag {
-                    "b" => apply_attributes("font-weight", "bold", &mut modifier),
-                    "strong" => apply_attributes("font-weight", "bold", &mut modifier),
-                    "u" => apply_attributes("text-decoration", "underline", &mut modifier),
-                    "ins" => apply_attributes("text-decoration", "underline", &mut modifier),
-                    "del" => apply_attributes("text-decoration", "line-through", &mut modifier),
-                    "i" => apply_attributes("font-style", "italic", &mut modifier),
-                    "em" => apply_attributes("font-style", "italic", &mut modifier),
-                    "mark" => apply_attributes(
-                        "background-color",
-                        "rgba(241, 231, 64, 50%)",
-                        &mut modifier,
-                    ),
-                    _ => (),
+            if let PossiblyUninitalized::Initialized(n) = self.node {
+                if self.style != style {
+                    stretch.set_style(n, style).unwrap();
                 }
+            } else {
+                self.node =
+                    PossiblyUninitalized::Initialized(stretch.new_node(style, &[]).unwrap());
+                changed = true;
             }
-
-            for &Attribute { name, value, .. } in el.attributes {
-                apply_attributes(name, value, &mut modifier);
+        } else {
+            // gather up all the styles from the attribute list
+            for Attribute { name, value, .. } in node.attributes() {
+                assert!(SORTED_LAYOUT_ATTRS.binary_search(name).is_ok());
+                if let Some(text) = value.as_text() {
+                    apply_layout_attributes(name, text, &mut style);
+                }
             }
 
-            // Layout the children
-            for child in el.children {
-                collect_layout(layout, nodes, vdom, child);
+            // the root node fills the entire area
+            if node.id() == ElementId(0) {
+                apply_layout_attributes("width", "100%", &mut style);
+                apply_layout_attributes("height", "100%", &mut style);
             }
 
             // Set all direct nodes as our children
             let mut child_layout = vec![];
-            for el in el.children {
-                let ite = ElementIdIterator::new(vdom, el);
-                for node in ite {
-                    match node {
-                        VNode::Element(_) | VNode::Text(_) => {
-                            //
-                            child_layout.push(nodes[&node.mounted_id()].layout)
-                        }
-                        VNode::Placeholder(_) => {}
-                        VNode::Fragment(_) => todo!(),
-                        VNode::Component(_) => todo!(),
-                    }
-
-                    // child_layout.push(nodes[&node.mounted_id()].layout)
-                }
+            for l in children {
+                child_layout.push(l.node.unwrap());
             }
 
-            nodes.insert(
-                node.mounted_id(),
-                TuiNode {
-                    node,
-                    block_style: modifier.tui_style,
-                    tui_modifier: modifier.tui_modifier,
-                    layout: layout.new_node(modifier.style, &child_layout).unwrap(),
-                },
-            );
-        }
-        VNode::Fragment(el) => {
-            //
-            for child in el.children {
-                collect_layout(layout, nodes, vdom, child);
+            if let PossiblyUninitalized::Initialized(n) = self.node {
+                if self.style != style {
+                    stretch.set_style(n, style).unwrap();
+                }
+                if stretch.children(n).unwrap() != child_layout {
+                    stretch.set_children(n, &child_layout).unwrap();
+                }
+            } else {
+                self.node = PossiblyUninitalized::Initialized(
+                    stretch.new_node(style, &child_layout).unwrap(),
+                );
+                changed = true;
             }
         }
-        VNode::Component(sc) => {
-            //
-            let scope = vdom.get_scope(sc.scope.get().unwrap()).unwrap();
-            let root = scope.root_node();
-            collect_layout(layout, nodes, vdom, root);
+        if self.style != style {
+            changed = true;
+            self.style = style;
         }
-        VNode::Placeholder(_) => {
-            //
-        }
-    };
+        changed
+    }
 }
+
+// these are the attributes in layout_attiributes in native-core
+const SORTED_LAYOUT_ATTRS: &[&str] = &sorted_str_slice!([
+    "align-content",
+    "align-items",
+    "align-self",
+    "animation",
+    "animation-delay",
+    "animation-direction",
+    "animation-duration",
+    "animation-fill-mode",
+    "animation-iteration-count",
+    "animation-name",
+    "animation-play-state",
+    "animation-timing-function",
+    "backface-visibility",
+    "border",
+    "border-bottom",
+    "border-bottom-color",
+    "border-bottom-left-radius",
+    "border-bottom-right-radius",
+    "border-bottom-style",
+    "border-bottom-width",
+    "border-collapse",
+    "border-color",
+    "border-image",
+    "border-image-outset",
+    "border-image-repeat",
+    "border-image-slice",
+    "border-image-source",
+    "border-image-width",
+    "border-left",
+    "border-left-color",
+    "border-left-style",
+    "border-left-width",
+    "border-radius",
+    "border-right",
+    "border-right-color",
+    "border-right-style",
+    "border-right-width",
+    "border-spacing",
+    "border-style",
+    "border-top",
+    "border-top-color",
+    "border-top-left-radius",
+    "border-top-right-radius",
+    "border-top-style",
+    "border-top-width",
+    "border-width",
+    "bottom",
+    "box-shadow",
+    "box-sizing",
+    "caption-side",
+    "clear",
+    "clip",
+    "column-count",
+    "column-fill",
+    "column-gap",
+    "column-rule",
+    "column-rule-color",
+    "column-rule-style",
+    "column-rule-width",
+    "column-span",
+    "column-width",
+    "columns",
+    "content",
+    "counter-increment",
+    "counter-reset",
+    "cursor",
+    "direction",
+    "ltr",
+    "rtl",
+    "display",
+    "empty-cells",
+    "flex",
+    "flex-basis",
+    "flex-direction",
+    "flex-flow",
+    "flex-grow",
+    "flex-shrink",
+    "flex-wrap",
+    "float",
+    "height",
+    "justify-content",
+    "flex-start",
+    "flex-end",
+    "center",
+    "space-between",
+    "space-around",
+    "space-evenly",
+    "left",
+    "letter-spacing",
+    "line-height",
+    "list-style",
+    "list-style-image",
+    "list-style-position",
+    "list-style-type",
+    "margin",
+    "margin-bottom",
+    "margin-left",
+    "margin-right",
+    "margin-top",
+    "max-height",
+    "max-width",
+    "min-height",
+    "min-width",
+    "opacity",
+    "order",
+    "outline",
+    "outline-color",
+    "outline-offset",
+    "outline-style",
+    "outline-width",
+    "overflow",
+    "overflow-x",
+    "overflow-y",
+    "padding",
+    "padding-bottom",
+    "padding-left",
+    "padding-right",
+    "padding-top",
+    "page-break-after",
+    "page-break-before",
+    "page-break-inside",
+    "perspective",
+    "perspective-origin",
+    "position",
+    "static",
+    "relative",
+    "fixed",
+    "absolute",
+    "sticky",
+    "pointer-events",
+    "quotes",
+    "resize",
+    "right",
+    "tab-size",
+    "table-layout",
+    "top",
+    "transform",
+    "transform-origin",
+    "transform-style",
+    "transition",
+    "transition-delay",
+    "transition-duration",
+    "transition-property",
+    "transition-timing-function",
+    "vertical-align",
+    "visibility",
+    "white-space",
+    "width",
+    "word-break",
+    "word-spacing",
+    "word-wrap",
+    "z-index"
+]);

+ 111 - 120
packages/tui/src/lib.rs

@@ -1,4 +1,5 @@
 use anyhow::Result;
+use anymap::AnyMap;
 use crossterm::{
     event::{DisableMouseCapture, EnableMouseCapture, Event as TermEvent, KeyCode, KeyModifiers},
     execute,
@@ -6,35 +7,42 @@ use crossterm::{
 };
 use dioxus_core::exports::futures_channel::mpsc::unbounded;
 use dioxus_core::*;
+use dioxus_native_core::{real_dom::RealDom, state::*};
+use dioxus_native_core_macro::State;
 use futures::{
     channel::mpsc::{UnboundedReceiver, UnboundedSender},
     pin_mut, StreamExt,
 };
-use std::{
-    collections::HashMap,
-    io,
-    time::{Duration, Instant},
-};
-use stretch2::{
-    prelude::{Node, Size},
-    Stretch,
-};
-use style::RinkStyle;
-use tui::{backend::CrosstermBackend, Terminal};
+use layout::StretchLayout;
+use std::cell::RefCell;
+use std::rc::Rc;
+use std::{io, time::Duration};
+use stretch2::{prelude::Size, Stretch};
+use style_attributes::StyleModifier;
+use tui::{backend::CrosstermBackend, layout::Rect, Terminal};
 
-mod attributes;
 mod config;
 mod hooks;
 mod layout;
 mod render;
 mod style;
+mod style_attributes;
 mod widget;
 
-pub use attributes::*;
 pub use config::*;
 pub use hooks::*;
-pub use layout::*;
-pub use render::*;
+
+type Dom = RealDom<NodeState>;
+type Node = dioxus_native_core::real_dom::Node<NodeState>;
+
+#[derive(Debug, Clone, State, Default)]
+struct NodeState {
+    #[child_dep_state(layout, RefCell<Stretch>)]
+    layout: StretchLayout,
+    // depends on attributes, the C component of it's parent and a u8 context
+    #[parent_dep_state(style)]
+    style: StyleModifier,
+}
 
 #[derive(Clone)]
 pub struct TuiContext {
@@ -52,66 +60,64 @@ pub fn launch(app: Component<()>) {
 
 pub fn launch_cfg(app: Component<()>, cfg: Config) {
     let mut dom = VirtualDom::new(app);
-    let (tx, rx) = unbounded();
+
+    let (handler, state, register_event) = RinkInputHandler::new();
+
     // Setup input handling
     let (event_tx, event_rx) = unbounded();
     let event_tx_clone = event_tx.clone();
     if !cfg.headless {
         std::thread::spawn(move || {
-            let tick_rate = Duration::from_millis(100);
-            let mut last_tick = Instant::now();
+            let tick_rate = Duration::from_millis(1000);
             loop {
-                // poll for tick rate duration, if no events, sent tick event.
-                let timeout = tick_rate
-                    .checked_sub(last_tick.elapsed())
-                    .unwrap_or_else(|| Duration::from_secs(0));
-
-                if crossterm::event::poll(timeout).unwrap() {
+                if crossterm::event::poll(tick_rate).unwrap() {
+                    // if crossterm::event::poll(timeout).unwrap() {
                     let evt = crossterm::event::read().unwrap();
-                    event_tx.unbounded_send(InputEvent::UserInput(evt)).unwrap();
-                }
-
-                if last_tick.elapsed() >= tick_rate {
-                    event_tx.unbounded_send(InputEvent::Tick).unwrap();
-                    last_tick = Instant::now();
+                    if event_tx.unbounded_send(InputEvent::UserInput(evt)).is_err() {
+                        break;
+                    }
                 }
             }
         });
     }
 
     let cx = dom.base_scope();
-    cx.provide_root_context(TuiContext { tx: event_tx_clone });
-
-    let (handler, state) = RinkInputHandler::new(rx, cx);
-
     cx.provide_root_context(state);
+    cx.provide_root_context(TuiContext { tx: event_tx_clone });
 
-    dom.rebuild();
-
-    render_vdom(&mut dom, event_rx, tx, handler, cfg).unwrap();
-}
-
-pub struct TuiNode<'a> {
-    pub layout: stretch2::node::Node,
-    pub block_style: RinkStyle,
-    pub tui_modifier: TuiModifier,
-    pub node: &'a VNode<'a>,
+    let mut rdom: Dom = RealDom::new();
+    let mutations = dom.rebuild();
+    let to_update = rdom.apply_mutations(vec![mutations]);
+    let stretch = Rc::new(RefCell::new(Stretch::new()));
+    let mut any_map = AnyMap::new();
+    any_map.insert(stretch.clone());
+    let _to_rerender = rdom.update_state(&dom, to_update, any_map).unwrap();
+
+    render_vdom(
+        &mut dom,
+        event_rx,
+        handler,
+        cfg,
+        rdom,
+        stretch,
+        register_event,
+    )
+    .unwrap();
 }
 
 fn render_vdom(
     vdom: &mut VirtualDom,
     mut event_reciever: UnboundedReceiver<InputEvent>,
-    ctx: UnboundedSender<TermEvent>,
     handler: RinkInputHandler,
     cfg: Config,
+    mut rdom: Dom,
+    stretch: Rc<RefCell<Stretch>>,
+    mut register_event: impl FnMut(crossterm::event::Event),
 ) -> Result<()> {
     tokio::runtime::Builder::new_current_thread()
         .enable_all()
         .build()?
         .block_on(async {
-            /*
-            Get the terminal to calcualte the layout from
-            */
             let mut terminal = (!cfg.headless).then(|| {
                 enable_raw_mode().unwrap();
                 let mut stdout = std::io::stdout();
@@ -119,85 +125,61 @@ fn render_vdom(
                 let backend = CrosstermBackend::new(io::stdout());
                 Terminal::new(backend).unwrap()
             });
-
             if let Some(terminal) = &mut terminal {
                 terminal.clear().unwrap();
             }
 
+            let mut to_rerender: fxhash::FxHashSet<usize> = vec![0].into_iter().collect();
+            let mut resized = true;
+
             loop {
                 /*
-                -> collect all the nodes with their layout
-                -> solve their layout
+                -> render the nodes in the right place with tui/crossterm
+                -> wait for changes
                 -> resolve events
-                -> render the nodes in the right place with tui/crosstream
-                -> while rendering, apply styling
+                -> lazily update the layout and style based on nodes changed
 
                 use simd to compare lines for diffing?
 
-
-                todo: reuse the layout and node objects.
-                our work_with_deadline method can tell us which nodes are dirty.
-                */
-                let mut layout = Stretch::new();
-                let mut nodes = HashMap::new();
-
-                let root_node = vdom.base_scope().root_node();
-                layout::collect_layout(&mut layout, &mut nodes, vdom, root_node);
-                /*
-                Compute the layout given the terminal size
+                todo: lazy re-rendering
                 */
-                let node_id = root_node.try_mounted_id().unwrap();
-                let root_layout = nodes[&node_id].layout;
-                let mut events = Vec::new();
 
-                fn resize(dims: tui::layout::Rect, stretch: &mut Stretch, root_layout: Node) {
-                    let width = dims.width;
-                    let height = dims.height;
-
-                    stretch
-                        .compute_layout(
-                            root_layout,
-                            Size {
-                                width: stretch2::prelude::Number::Defined((width - 1) as f32),
-                                height: stretch2::prelude::Number::Defined((height - 1) as f32),
+                if !to_rerender.is_empty() || resized {
+                    resized = false;
+                    fn resize(dims: Rect, stretch: &mut Stretch, rdom: &Dom) {
+                        let width = dims.width;
+                        let height = dims.height;
+                        let root_node = rdom[0].state.layout.node.unwrap();
+
+                        stretch
+                            .compute_layout(
+                                root_node,
+                                Size {
+                                    width: stretch2::prelude::Number::Defined((width - 1) as f32),
+                                    height: stretch2::prelude::Number::Defined((height - 1) as f32),
+                                },
+                            )
+                            .unwrap();
+                    }
+                    if let Some(terminal) = &mut terminal {
+                        terminal.draw(|frame| {
+                            // size is guaranteed to not change when rendering
+                            resize(frame.size(), &mut stretch.borrow_mut(), &rdom);
+                            let root = &rdom[0];
+                            render::render_vnode(frame, &stretch.borrow(), &rdom, root, cfg);
+                        })?;
+                    } else {
+                        resize(
+                            Rect {
+                                x: 0,
+                                y: 0,
+                                width: 300,
+                                height: 300,
                             },
-                        )
-                        .unwrap();
-                }
-
-                if let Some(terminal) = &mut terminal {
-                    terminal.draw(|frame| {
-                        // size is guaranteed to not change when rendering
-                        resize(frame.size(), &mut layout, root_layout);
-
-                        // resolve events before rendering
-                        events = handler.get_events(vdom, &layout, &mut nodes, root_node);
-                        render::render_vnode(
-                            frame,
-                            &layout,
-                            &mut nodes,
-                            vdom,
-                            root_node,
-                            &RinkStyle::default(),
-                            cfg,
+                            &mut stretch.borrow_mut(),
+                            &rdom,
                         );
-                        assert!(nodes.is_empty());
-                    })?;
-                } else {
-                    resize(
-                        tui::layout::Rect {
-                            x: 0,
-                            y: 0,
-                            width: 100,
-                            height: 100,
-                        },
-                        &mut layout,
-                        root_layout,
-                    );
-                }
-
-                for e in events {
-                    vdom.handle_message(SchedulerMsg::Event(e));
+                    }
                 }
 
                 use futures::future::{select, Either};
@@ -220,20 +202,32 @@ fn render_vdom(
                                             break;
                                         }
                                     }
-                                    TermEvent::Resize(_, _) | TermEvent::Mouse(_) => {}
+                                    TermEvent::Resize(_, _) => resized = true,
+                                    TermEvent::Mouse(_) => {}
                                 },
-                                InputEvent::Tick => {} // tick
                                 InputEvent::Close => break,
                             };
 
                             if let InputEvent::UserInput(evt) = evt.unwrap() {
-                                ctx.unbounded_send(evt).unwrap();
+                                register_event(evt);
                             }
                         }
                     }
                 }
 
-                vdom.work_with_deadline(|| false);
+                {
+                    let evts = handler.get_events(&stretch.borrow(), &mut rdom);
+                    for e in evts {
+                        vdom.handle_message(SchedulerMsg::Event(e));
+                    }
+                    let mutations = vdom.work_with_deadline(|| false);
+                    // updates the dom's nodes
+                    let to_update = rdom.apply_mutations(mutations);
+                    // update the style and layout
+                    let mut any_map = AnyMap::new();
+                    any_map.insert(stretch.clone());
+                    to_rerender = rdom.update_state(vdom, to_update, any_map).unwrap();
+                }
             }
 
             if let Some(terminal) = &mut terminal {
@@ -252,8 +246,5 @@ fn render_vdom(
 
 enum InputEvent {
     UserInput(TermEvent),
-    Tick,
-
-    #[allow(dead_code)]
     Close,
 }

+ 35 - 61
packages/tui/src/render.rs

@@ -1,5 +1,5 @@
-use dioxus_core::*;
-use std::{collections::HashMap, io::Stdout};
+use dioxus_native_core::layout_attributes::UnitSystem;
+use std::io::Stdout;
 use stretch2::{
     geometry::Point,
     prelude::{Layout, Size},
@@ -9,52 +9,33 @@ use tui::{backend::CrosstermBackend, layout::Rect};
 
 use crate::{
     style::{RinkColor, RinkStyle},
+    style_attributes::{BorderEdge, BorderStyle},
     widget::{RinkBuffer, RinkCell, RinkWidget, WidgetWithContext},
-    BorderEdge, BorderStyle, Config, TuiNode, UnitSystem,
+    Config, Dom, Node,
 };
 
 const RADIUS_MULTIPLIER: [f32; 2] = [1.0, 0.5];
 
-pub fn render_vnode<'a>(
+pub(crate) fn render_vnode(
     frame: &mut tui::Frame<CrosstermBackend<Stdout>>,
     layout: &Stretch,
-    layouts: &mut HashMap<ElementId, TuiNode<'a>>,
-    vdom: &'a VirtualDom,
-    node: &'a VNode<'a>,
-    // this holds the accumulated syle state for styled text rendering
-    style: &RinkStyle,
+    rdom: &Dom,
+    node: &Node,
     cfg: Config,
 ) {
-    match node {
-        VNode::Fragment(f) => {
-            for child in f.children {
-                render_vnode(frame, layout, layouts, vdom, child, style, cfg);
-            }
-            return;
-        }
+    use dioxus_native_core::real_dom::NodeType;
 
-        VNode::Component(vcomp) => {
-            let idx = vcomp.scope.get().unwrap();
-            let new_node = vdom.get_scope(idx).unwrap().root_node();
-            render_vnode(frame, layout, layouts, vdom, new_node, style, cfg);
-            return;
-        }
-
-        VNode::Placeholder(_) => return,
-
-        VNode::Element(_) | VNode::Text(_) => {}
+    if let NodeType::Placeholder = &node.node_type {
+        return;
     }
 
-    let id = node.try_mounted_id().unwrap();
-    let mut node = layouts.remove(&id).unwrap();
-
-    let Layout { location, size, .. } = layout.layout(node.layout).unwrap();
+    let Layout { location, size, .. } = layout.layout(node.state.layout.node.unwrap()).unwrap();
 
     let Point { x, y } = location;
     let Size { width, height } = size;
 
-    match node.node {
-        VNode::Text(t) => {
+    match &node.node_type {
+        NodeType::Text { text } => {
             #[derive(Default)]
             struct Label<'a> {
                 text: &'a str,
@@ -67,14 +48,14 @@ pub fn render_vnode<'a>(
                         let mut new_cell = RinkCell::default();
                         new_cell.set_style(self.style);
                         new_cell.symbol = c.to_string();
-                        buf.set(area.left() + i as u16, area.top(), &new_cell);
+                        buf.set(area.left() + i as u16, area.top(), new_cell);
                     }
                 }
             }
 
             let label = Label {
-                text: t.text,
-                style: *style,
+                text,
+                style: node.state.style.core,
             };
             let area = Rect::new(*x as u16, *y as u16, *width as u16, *height as u16);
 
@@ -83,30 +64,23 @@ pub fn render_vnode<'a>(
                 frame.render_widget(WidgetWithContext::new(label, cfg), area);
             }
         }
-        VNode::Element(el) => {
+        NodeType::Element { children, .. } => {
             let area = Rect::new(*x as u16, *y as u16, *width as u16, *height as u16);
 
-            let mut new_style = node.block_style.merge(*style);
-            node.block_style = new_style;
-
             // the renderer will panic if a node is rendered out of range even if the size is zero
             if area.width > 0 && area.height > 0 {
                 frame.render_widget(WidgetWithContext::new(node, cfg), area);
             }
 
-            // do not pass background color to children
-            new_style.bg = None;
-            for el in el.children {
-                render_vnode(frame, layout, layouts, vdom, el, &new_style, cfg);
+            for c in children {
+                render_vnode(frame, layout, rdom, &rdom[c.0], cfg);
             }
         }
-        VNode::Fragment(_) => todo!(),
-        VNode::Component(_) => todo!(),
-        VNode::Placeholder(_) => todo!(),
+        NodeType::Placeholder => unreachable!(),
     }
 }
 
-impl<'a> RinkWidget for TuiNode<'a> {
+impl RinkWidget for &Node {
     fn render(self, area: Rect, mut buf: RinkBuffer<'_>) {
         use tui::symbols::line::*;
 
@@ -176,7 +150,7 @@ impl<'a> RinkWidget for TuiNode<'a> {
             buf.set(
                 (current[0] + pos[0] as i32) as u16,
                 (current[1] + pos[1] as i32) as u16,
-                &new_cell,
+                new_cell,
             );
         }
 
@@ -270,8 +244,8 @@ impl<'a> RinkWidget for TuiNode<'a> {
 
         fn get_radius(border: &BorderEdge, area: Rect) -> f32 {
             match border.style {
-                BorderStyle::HIDDEN => 0.0,
-                BorderStyle::NONE => 0.0,
+                BorderStyle::Hidden => 0.0,
+                BorderStyle::None => 0.0,
                 _ => match border.radius {
                     UnitSystem::Percent(p) => p * area.width as f32 / 100.0,
                     UnitSystem::Point(p) => p,
@@ -290,14 +264,14 @@ impl<'a> RinkWidget for TuiNode<'a> {
         for x in area.left()..area.right() {
             for y in area.top()..area.bottom() {
                 let mut new_cell = RinkCell::default();
-                if let Some(c) = self.block_style.bg {
+                if let Some(c) = self.state.style.core.bg {
                     new_cell.bg = c;
                 }
-                buf.set(x, y, &new_cell);
+                buf.set(x, y, new_cell);
             }
         }
 
-        let borders = self.tui_modifier.borders;
+        let borders = &self.state.style.modifier.borders;
 
         let last_edge = &borders.left;
         let current_edge = &borders.top;
@@ -314,14 +288,14 @@ impl<'a> RinkWidget for TuiNode<'a> {
                 (last_r * RADIUS_MULTIPLIER[0]) as u16,
                 (last_r * RADIUS_MULTIPLIER[1]) as u16,
             ];
-            let color = current_edge.color.or(self.block_style.fg);
+            let color = current_edge.color.or(self.state.style.core.fg);
             let mut new_cell = RinkCell::default();
             if let Some(c) = color {
                 new_cell.fg = c;
             }
             for x in (area.left() + last_radius[0] + 1)..(area.right() - radius[0]) {
                 new_cell.symbol = symbols.horizontal.to_string();
-                buf.set(x, area.top(), &new_cell);
+                buf.set(x, area.top(), new_cell.clone());
             }
             draw_arc(
                 [area.right() - radius[0] - 1, area.top() + radius[1]],
@@ -349,14 +323,14 @@ impl<'a> RinkWidget for TuiNode<'a> {
                 (last_r * RADIUS_MULTIPLIER[0]) as u16,
                 (last_r * RADIUS_MULTIPLIER[1]) as u16,
             ];
-            let color = current_edge.color.or(self.block_style.fg);
+            let color = current_edge.color.or(self.state.style.core.fg);
             let mut new_cell = RinkCell::default();
             if let Some(c) = color {
                 new_cell.fg = c;
             }
             for y in (area.top() + last_radius[1] + 1)..(area.bottom() - radius[1]) {
                 new_cell.symbol = symbols.vertical.to_string();
-                buf.set(area.right() - 1, y, &new_cell);
+                buf.set(area.right() - 1, y, new_cell.clone());
             }
             draw_arc(
                 [area.right() - radius[0] - 1, area.bottom() - radius[1] - 1],
@@ -384,14 +358,14 @@ impl<'a> RinkWidget for TuiNode<'a> {
                 (last_r * RADIUS_MULTIPLIER[0]) as u16,
                 (last_r * RADIUS_MULTIPLIER[1]) as u16,
             ];
-            let color = current_edge.color.or(self.block_style.fg);
+            let color = current_edge.color.or(self.state.style.core.fg);
             let mut new_cell = RinkCell::default();
             if let Some(c) = color {
                 new_cell.fg = c;
             }
             for x in (area.left() + radius[0])..(area.right() - last_radius[0] - 1) {
                 new_cell.symbol = symbols.horizontal.to_string();
-                buf.set(x, area.bottom() - 1, &new_cell);
+                buf.set(x, area.bottom() - 1, new_cell.clone());
             }
             draw_arc(
                 [area.left() + radius[0], area.bottom() - radius[1] - 1],
@@ -419,14 +393,14 @@ impl<'a> RinkWidget for TuiNode<'a> {
                 (last_r * RADIUS_MULTIPLIER[0]) as u16,
                 (last_r * RADIUS_MULTIPLIER[1]) as u16,
             ];
-            let color = current_edge.color.or(self.block_style.fg);
+            let color = current_edge.color.or(self.state.style.core.fg);
             let mut new_cell = RinkCell::default();
             if let Some(c) = color {
                 new_cell.fg = c;
             }
             for y in (area.top() + radius[1])..(area.bottom() - last_radius[1] - 1) {
                 new_cell.symbol = symbols.vertical.to_string();
-                buf.set(area.left(), y, &new_cell);
+                buf.set(area.left(), y, new_cell.clone());
             }
             draw_arc(
                 [area.left() + radius[0], area.top() + radius[1]],

+ 46 - 42
packages/tui/src/style.rs

@@ -4,17 +4,17 @@ use tui::style::{Color, Modifier, Style};
 
 use crate::RenderingMode;
 
-#[derive(Clone, Copy, Debug)]
+#[derive(Clone, Copy, Debug, PartialEq)]
 pub struct RinkColor {
     pub color: Color,
-    pub alpha: f32,
+    pub alpha: u8,
 }
 
 impl Default for RinkColor {
     fn default() -> Self {
         Self {
             color: Color::Black,
-            alpha: 0.0,
+            alpha: 0,
         }
     }
 }
@@ -23,22 +23,17 @@ impl RinkColor {
     pub fn blend(self, other: Color) -> Color {
         if self.color == Color::Reset {
             Color::Reset
-        } else if self.alpha == 0.0 {
+        } else if self.alpha == 0 {
             other
         } else {
-            let [sr, sg, sb] = to_rgb(self.color);
-            let [or, og, ob] = to_rgb(other);
-            let (sr, sg, sb, sa) = (
-                sr as f32 / 255.0,
-                sg as f32 / 255.0,
-                sb as f32 / 255.0,
-                self.alpha,
-            );
-            let (or, og, ob) = (or as f32 / 255.0, og as f32 / 255.0, ob as f32 / 255.0);
+            let [sr, sg, sb] = to_rgb(self.color).map(|e| e as u16);
+            let [or, og, ob] = to_rgb(other).map(|e| e as u16);
+            let sa = self.alpha as u16;
+            let rsa = 255 - sa;
             Color::Rgb(
-                (255.0 * (sr * sa + or * (1.0 - sa))) as u8,
-                (255.0 * (sg * sa + og * (1.0 - sa))) as u8,
-                (255.0 * (sb * sa + ob * (1.0 - sa))) as u8,
+                ((sr * sa + or * rsa) / 255) as u8,
+                ((sg * sa + og * rsa) / 255) as u8,
+                ((sb * sa + ob * rsa) / 255) as u8,
             )
         }
     }
@@ -151,75 +146,75 @@ impl FromStr for RinkColor {
         match color {
             "red" => Ok(RinkColor {
                 color: Color::Red,
-                alpha: 1.0,
+                alpha: 255,
             }),
             "black" => Ok(RinkColor {
                 color: Color::Black,
-                alpha: 1.0,
+                alpha: 255,
             }),
             "green" => Ok(RinkColor {
                 color: Color::Green,
-                alpha: 1.0,
+                alpha: 255,
             }),
             "yellow" => Ok(RinkColor {
                 color: Color::Yellow,
-                alpha: 1.0,
+                alpha: 255,
             }),
             "blue" => Ok(RinkColor {
                 color: Color::Blue,
-                alpha: 1.0,
+                alpha: 255,
             }),
             "magenta" => Ok(RinkColor {
                 color: Color::Magenta,
-                alpha: 1.0,
+                alpha: 255,
             }),
             "cyan" => Ok(RinkColor {
                 color: Color::Cyan,
-                alpha: 1.0,
+                alpha: 255,
             }),
             "gray" => Ok(RinkColor {
                 color: Color::Gray,
-                alpha: 1.0,
+                alpha: 255,
             }),
             "darkgray" => Ok(RinkColor {
                 color: Color::DarkGray,
-                alpha: 1.0,
+                alpha: 255,
             }),
             // light red does not exist
             "orangered" => Ok(RinkColor {
                 color: Color::LightRed,
-                alpha: 1.0,
+                alpha: 255,
             }),
             "lightgreen" => Ok(RinkColor {
                 color: Color::LightGreen,
-                alpha: 1.0,
+                alpha: 255,
             }),
             "lightyellow" => Ok(RinkColor {
                 color: Color::LightYellow,
-                alpha: 1.0,
+                alpha: 255,
             }),
             "lightblue" => Ok(RinkColor {
                 color: Color::LightBlue,
-                alpha: 1.0,
+                alpha: 255,
             }),
             // light magenta does not exist
             "orchid" => Ok(RinkColor {
                 color: Color::LightMagenta,
-                alpha: 1.0,
+                alpha: 255,
             }),
             "lightcyan" => Ok(RinkColor {
                 color: Color::LightCyan,
-                alpha: 1.0,
+                alpha: 255,
             }),
             "white" => Ok(RinkColor {
                 color: Color::White,
-                alpha: 1.0,
+                alpha: 255,
             }),
             _ => {
                 if color.len() == 7 && color.starts_with('#') {
                     parse_hex(color).map(|c| RinkColor {
                         color: c,
-                        alpha: 1.0,
+                        alpha: 255,
                     })
                 } else if let Some(stripped) = color.strip_prefix("rgb(") {
                     let color_values = stripped.trim_end_matches(')');
@@ -234,7 +229,7 @@ impl FromStr for RinkColor {
                     } else {
                         parse_rgb(color_values).map(|c| RinkColor {
                             color: c,
-                            alpha: 1.0,
+                            alpha: 255,
                         })
                     }
                 } else if let Some(stripped) = color.strip_prefix("rgba(") {
@@ -243,14 +238,17 @@ impl FromStr for RinkColor {
                         let (rgb_values, alpha) =
                             color_values.rsplit_once(',').ok_or(ParseColorError)?;
                         if let Ok(a) = parse_value(alpha, 1.0, 1.0) {
-                            parse_rgb(rgb_values).map(|c| RinkColor { color: c, alpha: a })
+                            parse_rgb(rgb_values).map(|c| RinkColor {
+                                color: c,
+                                alpha: (a * 255.0) as u8,
+                            })
                         } else {
                             Err(ParseColorError)
                         }
                     } else {
                         parse_rgb(color_values).map(|c| RinkColor {
                             color: c,
-                            alpha: 1.0,
+                            alpha: 255,
                         })
                     }
                 } else if let Some(stripped) = color.strip_prefix("hsl(") {
@@ -259,14 +257,17 @@ impl FromStr for RinkColor {
                         let (rgb_values, alpha) =
                             color_values.rsplit_once(',').ok_or(ParseColorError)?;
                         if let Ok(a) = parse_value(alpha, 1.0, 1.0) {
-                            parse_hsl(rgb_values).map(|c| RinkColor { color: c, alpha: a })
+                            parse_hsl(rgb_values).map(|c| RinkColor {
+                                color: c,
+                                alpha: (a * 255.0) as u8,
+                            })
                         } else {
                             Err(ParseColorError)
                         }
                     } else {
                         parse_hsl(color_values).map(|c| RinkColor {
                             color: c,
-                            alpha: 1.0,
+                            alpha: 255,
                         })
                     }
                 } else if let Some(stripped) = color.strip_prefix("hsla(") {
@@ -275,14 +276,17 @@ impl FromStr for RinkColor {
                         let (rgb_values, alpha) =
                             color_values.rsplit_once(',').ok_or(ParseColorError)?;
                         if let Ok(a) = parse_value(alpha, 1.0, 1.0) {
-                            parse_hsl(rgb_values).map(|c| RinkColor { color: c, alpha: a })
+                            parse_hsl(rgb_values).map(|c| RinkColor {
+                                color: c,
+                                alpha: (a * 255.0) as u8,
+                            })
                         } else {
                             Err(ParseColorError)
                         }
                     } else {
                         parse_hsl(color_values).map(|c| RinkColor {
                             color: c,
-                            alpha: 1.0,
+                            alpha: 255,
                         })
                     }
                 } else {
@@ -393,7 +397,7 @@ fn rgb_to_ansi() {
     }
 }
 
-#[derive(Clone, Copy)]
+#[derive(Clone, Copy, PartialEq, Debug)]
 pub struct RinkStyle {
     pub fg: Option<RinkColor>,
     pub bg: Option<RinkColor>,
@@ -406,7 +410,7 @@ impl Default for RinkStyle {
         Self {
             fg: Some(RinkColor {
                 color: Color::White,
-                alpha: 1.0,
+                alpha: 255,
             }),
             bg: None,
             add_modifier: Modifier::empty(),

+ 797 - 0
packages/tui/src/style_attributes.rs

@@ -0,0 +1,797 @@
+/*
+- [ ] pub display: Display,
+- [x] pub position_type: PositionType,  --> kinda, stretch doesnt support everything
+- [ ] pub direction: Direction,
+
+- [x] pub flex_direction: FlexDirection,
+- [x] pub flex_wrap: FlexWrap,
+- [x] pub flex_grow: f32,
+- [x] pub flex_shrink: f32,
+- [x] pub flex_basis: Dimension,
+
+- [x] pub overflow: Overflow, ---> kinda implemented... stretch doesnt have support for directional overflow
+
+- [x] pub align_items: AlignItems,
+- [x] pub align_self: AlignSelf,
+- [x] pub align_content: AlignContent,
+
+- [x] pub margin: Rect<Dimension>,
+- [x] pub padding: Rect<Dimension>,
+
+- [x] pub justify_content: JustifyContent,
+- [ ] pub position: Rect<Dimension>,
+- [x] pub border: Rect<Dimension>,
+
+- [ ] pub size: Size<Dimension>, ----> ??? seems to only be relevant for input?
+- [ ] pub min_size: Size<Dimension>,
+- [ ] pub max_size: Size<Dimension>,
+
+- [ ] pub aspect_ratio: Number,
+*/
+
+use dioxus_core::Attribute;
+use dioxus_native_core::{
+    layout_attributes::{parse_value, UnitSystem},
+    node_ref::{AttributeMask, NodeMask, NodeView},
+    state::ParentDepState,
+};
+use dioxus_native_core_macro::sorted_str_slice;
+
+use crate::style::{RinkColor, RinkStyle};
+
+#[derive(Default, Clone, PartialEq, Debug)]
+pub struct StyleModifier {
+    pub core: RinkStyle,
+    pub modifier: TuiModifier,
+}
+
+impl ParentDepState for StyleModifier {
+    type Ctx = ();
+    type DepState = Self;
+    // todo: seperate each attribute into it's own class
+    const NODE_MASK: NodeMask =
+        NodeMask::new_with_attrs(AttributeMask::Static(SORTED_STYLE_ATTRS)).with_element();
+
+    fn reduce(&mut self, node: NodeView, parent: Option<&Self::DepState>, _: &Self::Ctx) -> bool {
+        let mut new = StyleModifier::default();
+        if parent.is_some() {
+            new.core.fg = None;
+        }
+
+        // handle text modifier elements
+        if node.namespace().is_none() {
+            if let Some(tag) = node.tag() {
+                match tag {
+                    "b" => apply_style_attributes("font-weight", "bold", &mut new),
+                    "strong" => apply_style_attributes("font-weight", "bold", &mut new),
+                    "u" => apply_style_attributes("text-decoration", "underline", &mut new),
+                    "ins" => apply_style_attributes("text-decoration", "underline", &mut new),
+                    "del" => apply_style_attributes("text-decoration", "line-through", &mut new),
+                    "i" => apply_style_attributes("font-style", "italic", &mut new),
+                    "em" => apply_style_attributes("font-style", "italic", &mut new),
+                    "mark" => {
+                        apply_style_attributes("background-color", "rgba(241, 231, 64, 50%)", self)
+                    }
+                    _ => (),
+                }
+            }
+        }
+
+        // gather up all the styles from the attribute list
+        for Attribute { name, value, .. } in node.attributes() {
+            if let Some(text) = value.as_text() {
+                apply_style_attributes(name, text, &mut new);
+            }
+        }
+
+        // keep the text styling from the parent element
+        if let Some(parent) = parent {
+            let mut new_style = new.core.merge(parent.core);
+            new_style.bg = new.core.bg;
+            new.core = new_style;
+        }
+        if &mut new != self {
+            *self = new;
+            true
+        } else {
+            false
+        }
+    }
+}
+
+#[derive(Default, Clone, PartialEq, Debug)]
+pub struct TuiModifier {
+    pub borders: Borders,
+}
+
+#[derive(Default, Clone, PartialEq, Debug)]
+pub struct Borders {
+    pub top: BorderEdge,
+    pub right: BorderEdge,
+    pub bottom: BorderEdge,
+    pub left: BorderEdge,
+}
+
+impl Borders {
+    fn slice(&mut self) -> [&mut BorderEdge; 4] {
+        [
+            &mut self.top,
+            &mut self.right,
+            &mut self.bottom,
+            &mut self.left,
+        ]
+    }
+}
+
+#[derive(Clone, PartialEq, Debug)]
+pub struct BorderEdge {
+    pub color: Option<RinkColor>,
+    pub style: BorderStyle,
+    pub width: UnitSystem,
+    pub radius: UnitSystem,
+}
+
+impl Default for BorderEdge {
+    fn default() -> Self {
+        Self {
+            color: None,
+            style: BorderStyle::None,
+            width: UnitSystem::Point(0.0),
+            radius: UnitSystem::Point(0.0),
+        }
+    }
+}
+
+#[derive(Clone, Copy, PartialEq, Debug)]
+pub enum BorderStyle {
+    Dotted,
+    Dashed,
+    Solid,
+    Double,
+    Groove,
+    Ridge,
+    Inset,
+    Outset,
+    Hidden,
+    None,
+}
+
+impl BorderStyle {
+    pub fn symbol_set(&self) -> Option<tui::symbols::line::Set> {
+        use tui::symbols::line::*;
+        const DASHED: Set = Set {
+            horizontal: "╌",
+            vertical: "╎",
+            ..NORMAL
+        };
+        const DOTTED: Set = Set {
+            horizontal: "┈",
+            vertical: "┊",
+            ..NORMAL
+        };
+        match self {
+            BorderStyle::Dotted => Some(DOTTED),
+            BorderStyle::Dashed => Some(DASHED),
+            BorderStyle::Solid => Some(NORMAL),
+            BorderStyle::Double => Some(DOUBLE),
+            BorderStyle::Groove => Some(NORMAL),
+            BorderStyle::Ridge => Some(NORMAL),
+            BorderStyle::Inset => Some(NORMAL),
+            BorderStyle::Outset => Some(NORMAL),
+            BorderStyle::Hidden => None,
+            BorderStyle::None => None,
+        }
+    }
+}
+
+/// applies the entire html namespace defined in dioxus-html
+pub fn apply_style_attributes(
+    //
+    name: &str,
+    value: &str,
+    style: &mut StyleModifier,
+) {
+    match name {
+        "animation"
+        | "animation-delay"
+        | "animation-direction"
+        | "animation-duration"
+        | "animation-fill-mode"
+        | "animation-iteration-count"
+        | "animation-name"
+        | "animation-play-state"
+        | "animation-timing-function" => apply_animation(name, value, style),
+
+        "backface-visibility" => {}
+
+        "background"
+        | "background-attachment"
+        | "background-clip"
+        | "background-color"
+        | "background-image"
+        | "background-origin"
+        | "background-position"
+        | "background-repeat"
+        | "background-size" => apply_background(name, value, style),
+
+        "border"
+        | "border-bottom"
+        | "border-bottom-color"
+        | "border-bottom-left-radius"
+        | "border-bottom-right-radius"
+        | "border-bottom-style"
+        | "border-bottom-width"
+        | "border-collapse"
+        | "border-color"
+        | "border-image"
+        | "border-image-outset"
+        | "border-image-repeat"
+        | "border-image-slice"
+        | "border-image-source"
+        | "border-image-width"
+        | "border-left"
+        | "border-left-color"
+        | "border-left-style"
+        | "border-left-width"
+        | "border-radius"
+        | "border-right"
+        | "border-right-color"
+        | "border-right-style"
+        | "border-right-width"
+        | "border-spacing"
+        | "border-style"
+        | "border-top"
+        | "border-top-color"
+        | "border-top-left-radius"
+        | "border-top-right-radius"
+        | "border-top-style"
+        | "border-top-width"
+        | "border-width" => apply_border(name, value, style),
+
+        "bottom" => {}
+        "box-shadow" => {}
+        "box-sizing" => {}
+        "caption-side" => {}
+        "clear" => {}
+        "clip" => {}
+
+        "color" => {
+            if let Ok(c) = value.parse() {
+                style.core.fg.replace(c);
+            }
+        }
+
+        "columns" => {}
+
+        "content" => {}
+        "counter-increment" => {}
+        "counter-reset" => {}
+
+        "cursor" => {}
+
+        "empty-cells" => {}
+
+        "float" => {}
+
+        "font" | "font-family" | "font-size" | "font-size-adjust" | "font-stretch"
+        | "font-style" | "font-variant" | "font-weight" => apply_font(name, value, style),
+
+        "letter-spacing" => {}
+        "line-height" => {}
+
+        "list-style" | "list-style-image" | "list-style-position" | "list-style-type" => {}
+
+        "opacity" => {}
+        "order" => {}
+        "outline" => {}
+
+        "outline-color" | "outline-offset" | "outline-style" | "outline-width" => {}
+
+        "page-break-after" | "page-break-before" | "page-break-inside" => {}
+
+        "perspective" | "perspective-origin" => {}
+
+        "pointer-events" => {}
+
+        "quotes" => {}
+        "resize" => {}
+        "tab-size" => {}
+        "table-layout" => {}
+
+        "text-align"
+        | "text-align-last"
+        | "text-decoration"
+        | "text-decoration-color"
+        | "text-decoration-line"
+        | "text-decoration-style"
+        | "text-indent"
+        | "text-justify"
+        | "text-overflow"
+        | "text-shadow"
+        | "text-transform" => apply_text(name, value, style),
+
+        "transition"
+        | "transition-delay"
+        | "transition-duration"
+        | "transition-property"
+        | "transition-timing-function" => apply_transition(name, value, style),
+
+        "visibility" => {}
+        "white-space" => {}
+        _ => {}
+    }
+}
+
+fn apply_background(name: &str, value: &str, style: &mut StyleModifier) {
+    match name {
+        "background-color" => {
+            if let Ok(c) = value.parse() {
+                style.core.bg.replace(c);
+            }
+        }
+        "background" => {}
+        "background-attachment" => {}
+        "background-clip" => {}
+        "background-image" => {}
+        "background-origin" => {}
+        "background-position" => {}
+        "background-repeat" => {}
+        "background-size" => {}
+        _ => {}
+    }
+}
+
+fn apply_border(name: &str, value: &str, style: &mut StyleModifier) {
+    fn parse_border_style(v: &str) -> BorderStyle {
+        match v {
+            "dotted" => BorderStyle::Dotted,
+            "dashed" => BorderStyle::Dashed,
+            "solid" => BorderStyle::Solid,
+            "double" => BorderStyle::Double,
+            "groove" => BorderStyle::Groove,
+            "ridge" => BorderStyle::Ridge,
+            "inset" => BorderStyle::Inset,
+            "outset" => BorderStyle::Outset,
+            "none" => BorderStyle::None,
+            "hidden" => BorderStyle::Hidden,
+            _ => todo!(),
+        }
+    }
+    match name {
+        "border" => {}
+        "border-bottom" => {}
+        "border-bottom-color" => {
+            if let Ok(c) = value.parse() {
+                style.modifier.borders.bottom.color = Some(c);
+            }
+        }
+        "border-bottom-left-radius" => {
+            if let Some(v) = parse_value(value) {
+                style.modifier.borders.left.radius = v;
+            }
+        }
+        "border-bottom-right-radius" => {
+            if let Some(v) = parse_value(value) {
+                style.modifier.borders.right.radius = v;
+            }
+        }
+        "border-bottom-style" => style.modifier.borders.bottom.style = parse_border_style(value),
+        "border-bottom-width" => {
+            if let Some(v) = parse_value(value) {
+                style.modifier.borders.bottom.width = v;
+            }
+        }
+        "border-collapse" => {}
+        "border-color" => {
+            let values: Vec<_> = value.split(' ').collect();
+            if values.len() == 1 {
+                if let Ok(c) = values[0].parse() {
+                    style
+                        .modifier
+                        .borders
+                        .slice()
+                        .iter_mut()
+                        .for_each(|b| b.color = Some(c));
+                }
+            } else {
+                for (v, b) in values
+                    .into_iter()
+                    .zip(style.modifier.borders.slice().iter_mut())
+                {
+                    if let Ok(c) = v.parse() {
+                        b.color = Some(c);
+                    }
+                }
+            }
+        }
+        "border-image" => {}
+        "border-image-outset" => {}
+        "border-image-repeat" => {}
+        "border-image-slice" => {}
+        "border-image-source" => {}
+        "border-image-width" => {}
+        "border-left" => {}
+        "border-left-color" => {
+            if let Ok(c) = value.parse() {
+                style.modifier.borders.left.color = Some(c);
+            }
+        }
+        "border-left-style" => style.modifier.borders.left.style = parse_border_style(value),
+        "border-left-width" => {
+            if let Some(v) = parse_value(value) {
+                style.modifier.borders.left.width = v;
+            }
+        }
+        "border-radius" => {
+            let values: Vec<_> = value.split(' ').collect();
+            if values.len() == 1 {
+                if let Some(r) = parse_value(values[0]) {
+                    style
+                        .modifier
+                        .borders
+                        .slice()
+                        .iter_mut()
+                        .for_each(|b| b.radius = r);
+                }
+            } else {
+                for (v, b) in values
+                    .into_iter()
+                    .zip(style.modifier.borders.slice().iter_mut())
+                {
+                    if let Some(r) = parse_value(v) {
+                        b.radius = r;
+                    }
+                }
+            }
+        }
+        "border-right" => {}
+        "border-right-color" => {
+            if let Ok(c) = value.parse() {
+                style.modifier.borders.right.color = Some(c);
+            }
+        }
+        "border-right-style" => style.modifier.borders.right.style = parse_border_style(value),
+        "border-right-width" => {
+            if let Some(v) = parse_value(value) {
+                style.modifier.borders.right.width = v;
+            }
+        }
+        "border-spacing" => {}
+        "border-style" => {
+            let values: Vec<_> = value.split(' ').collect();
+            if values.len() == 1 {
+                let border_style = parse_border_style(values[0]);
+                style
+                    .modifier
+                    .borders
+                    .slice()
+                    .iter_mut()
+                    .for_each(|b| b.style = border_style);
+            } else {
+                for (v, b) in values
+                    .into_iter()
+                    .zip(style.modifier.borders.slice().iter_mut())
+                {
+                    b.style = parse_border_style(v);
+                }
+            }
+        }
+        "border-top" => {}
+        "border-top-color" => {
+            if let Ok(c) = value.parse() {
+                style.modifier.borders.top.color = Some(c);
+            }
+        }
+        "border-top-left-radius" => {
+            if let Some(v) = parse_value(value) {
+                style.modifier.borders.left.radius = v;
+            }
+        }
+        "border-top-right-radius" => {
+            if let Some(v) = parse_value(value) {
+                style.modifier.borders.right.radius = v;
+            }
+        }
+        "border-top-style" => style.modifier.borders.top.style = parse_border_style(value),
+        "border-top-width" => {
+            if let Some(v) = parse_value(value) {
+                style.modifier.borders.top.width = v;
+            }
+        }
+        "border-width" => {
+            let values: Vec<_> = value.split(' ').collect();
+            if values.len() == 1 {
+                if let Some(w) = parse_value(values[0]) {
+                    style
+                        .modifier
+                        .borders
+                        .slice()
+                        .iter_mut()
+                        .for_each(|b| b.width = w);
+                }
+            } else {
+                for (v, width) in values
+                    .into_iter()
+                    .zip(style.modifier.borders.slice().iter_mut())
+                {
+                    if let Some(w) = parse_value(v) {
+                        width.width = w;
+                    }
+                }
+            }
+        }
+        _ => (),
+    }
+}
+
+fn apply_animation(name: &str, _value: &str, _style: &mut StyleModifier) {
+    match name {
+        "animation" => {}
+        "animation-delay" => {}
+        "animation-direction =>{}" => {}
+        "animation-duration" => {}
+        "animation-fill-mode" => {}
+        "animation-itera =>{}tion-count" => {}
+        "animation-name" => {}
+        "animation-play-state" => {}
+        "animation-timing-function" => {}
+        _ => {}
+    }
+}
+
+fn apply_font(name: &str, value: &str, style: &mut StyleModifier) {
+    use tui::style::Modifier;
+    match name {
+        "font" => (),
+        "font-family" => (),
+        "font-size" => (),
+        "font-size-adjust" => (),
+        "font-stretch" => (),
+        "font-style" => match value {
+            "italic" => style.core = style.core.add_modifier(Modifier::ITALIC),
+            "oblique" => style.core = style.core.add_modifier(Modifier::ITALIC),
+            _ => (),
+        },
+        "font-variant" => todo!(),
+        "font-weight" => match value {
+            "bold" => style.core = style.core.add_modifier(Modifier::BOLD),
+            "normal" => style.core = style.core.remove_modifier(Modifier::BOLD),
+            _ => (),
+        },
+        _ => (),
+    }
+}
+
+fn apply_text(name: &str, value: &str, style: &mut StyleModifier) {
+    use tui::style::Modifier;
+
+    match name {
+        "text-align" => todo!(),
+        "text-align-last" => todo!(),
+        "text-decoration" | "text-decoration-line" => {
+            for v in value.split(' ') {
+                match v {
+                    "line-through" => style.core = style.core.add_modifier(Modifier::CROSSED_OUT),
+                    "underline" => style.core = style.core.add_modifier(Modifier::UNDERLINED),
+                    _ => (),
+                }
+            }
+        }
+        "text-decoration-color" => todo!(),
+        "text-decoration-style" => todo!(),
+        "text-indent" => todo!(),
+        "text-justify" => todo!(),
+        "text-overflow" => todo!(),
+        "text-shadow" => todo!(),
+        "text-transform" => todo!(),
+        _ => todo!(),
+    }
+}
+
+fn apply_transition(_name: &str, _value: &str, _style: &mut StyleModifier) {
+    todo!()
+}
+
+const SORTED_STYLE_ATTRS: &[&str] = &sorted_str_slice!([
+    "animation",
+    "animation-delay",
+    "animation-direction",
+    "animation-duration",
+    "animation-fill-mode",
+    "animation-iteration-count",
+    "animation-name",
+    "animation-play-state",
+    "animation-timing-function",
+    "backface-visibility",
+    "background",
+    "background-attachment",
+    "background-clip",
+    "background-color",
+    "background-image",
+    "background-origin",
+    "background-position",
+    "background-repeat",
+    "background-size",
+    "border",
+    "border-bottom",
+    "border-bottom-color",
+    "border-bottom-left-radius",
+    "border-bottom-right-radius",
+    "border-bottom-style",
+    "border-bottom-width",
+    "border-collapse",
+    "border-color",
+    "border-image",
+    "border-image-outset",
+    "border-image-repeat",
+    "border-image-slice",
+    "border-image-source",
+    "border-image-width",
+    "border-left",
+    "border-left-color",
+    "border-left-style",
+    "border-left-width",
+    "border-radius",
+    "border-right",
+    "border-right-color",
+    "border-right-style",
+    "border-right-width",
+    "border-spacing",
+    "border-style",
+    "border-top",
+    "border-top-color",
+    "border-top-left-radius",
+    "border-top-right-radius",
+    "border-top-style",
+    "border-top-width",
+    "border-width",
+    "bottom",
+    "box-shadow",
+    "box-sizing",
+    "caption-side",
+    "clear",
+    "clip",
+    "color",
+    "columns",
+    "content",
+    "counter-increment",
+    "counter-reset",
+    "cursor",
+    "empty-cells",
+    "float",
+    "font",
+    "font-family",
+    "font-size",
+    "font-size-adjust",
+    "font-stretch",
+    "font-style",
+    "font-variant",
+    "font-weight",
+    "letter-spacing",
+    "line-height",
+    "list-style",
+    "list-style-image",
+    "list-style-position",
+    "list-style-type",
+    "opacity",
+    "order",
+    "outline",
+    "outline-color",
+    "outline-offset",
+    "outline-style",
+    "outline-width",
+    "page-break-after",
+    "page-break-before",
+    "page-break-inside",
+    "perspective",
+    "perspective-origin",
+    "pointer-events",
+    "quotes",
+    "resize",
+    "tab-size",
+    "table-layout",
+    "text-align",
+    "text-align-last",
+    "text-decoration",
+    "text-decoration-color",
+    "text-decoration-line",
+    "text-decoration-style",
+    "text-indent",
+    "text-justify",
+    "text-overflow",
+    "text-shadow",
+    "text-transform",
+    "transition",
+    "transition-delay",
+    "transition-duration",
+    "transition-property",
+    "transition-timing-function",
+    "visibility",
+    "white-space",
+    "background-color",
+    "background",
+    "background-attachment",
+    "background-clip",
+    "background-image",
+    "background-origin",
+    "background-position",
+    "background-repeat",
+    "background-size",
+    "dotted",
+    "dashed",
+    "solid",
+    "double",
+    "groove",
+    "ridge",
+    "inset",
+    "outset",
+    "none",
+    "hidden",
+    "border",
+    "border-bottom",
+    "border-bottom-color",
+    "border-bottom-left-radius",
+    "border-bottom-right-radius",
+    "border-bottom-style",
+    "border-bottom-width",
+    "border-collapse",
+    "border-color",
+    "border-image",
+    "border-image-outset",
+    "border-image-repeat",
+    "border-image-slice",
+    "border-image-source",
+    "border-image-width",
+    "border-left",
+    "border-left-color",
+    "border-left-style",
+    "border-left-width",
+    "border-radius",
+    "border-right",
+    "border-right-color",
+    "border-right-style",
+    "border-right-width",
+    "border-spacing",
+    "border-style",
+    "border-top",
+    "border-top-color",
+    "border-top-left-radius",
+    "border-top-right-radius",
+    "border-top-style",
+    "border-top-width",
+    "border-width",
+    "animation",
+    "animation-delay",
+    "animation-direction",
+    "animation-duration",
+    "animation-fill-mode",
+    "animation-itera ",
+    "animation-name",
+    "animation-play-state",
+    "animation-timing-function",
+    "font",
+    "font-family",
+    "font-size",
+    "font-size-adjust",
+    "font-stretch",
+    "font-style",
+    "italic",
+    "oblique",
+    "font-variant",
+    "font-weight",
+    "bold",
+    "normal",
+    "text-align",
+    "text-align-last",
+    "text-decoration",
+    "text-decoration-line",
+    "line-through",
+    "underline",
+    "text-decoration-color",
+    "text-decoration-style",
+    "text-indent",
+    "text-justify",
+    "text-overflow",
+    "text-shadow",
+    "text-transform"
+]);

+ 8 - 4
packages/tui/src/widget.rs

@@ -20,7 +20,11 @@ impl<'a> RinkBuffer<'a> {
         Self { buf, cfg }
     }
 
-    pub fn set(&mut self, x: u16, y: u16, new: &RinkCell) {
+    pub fn set(&mut self, x: u16, y: u16, new: RinkCell) {
+        let area = self.buf.area();
+        if x < area.x || x > area.width || y < area.y || y > area.height {
+            panic!("({x}, {y}) is not in {area:?}");
+        }
         let mut cell = self.buf.get_mut(x, y);
         cell.bg = convert(self.cfg.rendering_mode, new.bg.blend(cell.bg));
         if new.symbol.is_empty() {
@@ -30,7 +34,7 @@ impl<'a> RinkBuffer<'a> {
             }
         } else {
             cell.modifier = new.modifier;
-            cell.symbol = new.symbol.clone();
+            cell.symbol = new.symbol;
             cell.fg = convert(self.cfg.rendering_mode, new.fg.blend(cell.bg));
         }
     }
@@ -71,11 +75,11 @@ impl Default for RinkCell {
             symbol: "".to_string(),
             fg: RinkColor {
                 color: Color::Rgb(0, 0, 0),
-                alpha: 0.0,
+                alpha: 0,
             },
             bg: RinkColor {
                 color: Color::Rgb(0, 0, 0),
-                alpha: 0.0,
+                alpha: 0,
             },
             modifier: Modifier::empty(),
         }

+ 5 - 5
packages/web/Cargo.toml

@@ -1,6 +1,6 @@
 [package]
 name = "dioxus-web"
-version = "0.2.0"
+version = "0.2.1"
 authors = ["Jonathan Kelley"]
 edition = "2018"
 description = "Dioxus VirtualDOM renderer for the web browser using websys"
@@ -11,10 +11,10 @@ documentation = "https://dioxuslabs.com"
 keywords = ["dom", "ui", "gui", "react", "wasm"]
 
 [dependencies]
-dioxus-core = { path = "../core", version = "^0.2.0" }
-dioxus-html = { path = "../html", version = "^0.2.0" }
-dioxus-interpreter-js = { path = "../interpreter", version = "^0.2.0", features = [
-    "web",
+dioxus-core = { path = "../core", version = "^0.2.1" }
+dioxus-html = { path = "../html", version = "^0.2.1", features = ["wasm-bind"] }
+dioxus-interpreter-js = { path = "../interpreter", version = "^0.2.1", features = [
+    "web"
 ] }
 
 js-sys = "0.3.56"

+ 8 - 91
packages/web/src/dom.rs

@@ -146,7 +146,7 @@ impl WebsysDom {
                     value,
                     ns,
                 } => {
-                    let value = serde_wasm_bindgen::to_value(value).unwrap();
+                    let value = serde_wasm_bindgen::to_value(&value).unwrap();
                     self.interpreter.SetAttribute(root, field, value, ns)
                 }
             }
@@ -168,7 +168,6 @@ fn virtual_event_from_websys_event(
     target: Element,
 ) -> Arc<dyn Any + Send + Sync> {
     use dioxus_html::on::*;
-    use dioxus_html::KeyCode;
 
     match event.type_().as_str() {
         "copy" | "cut" | "paste" => Arc::new(ClipboardData {}),
@@ -178,22 +177,7 @@ fn virtual_event_from_websys_event(
                 data: evt.data().unwrap_or_default(),
             })
         }
-        "keydown" | "keypress" | "keyup" => {
-            let evt: &web_sys::KeyboardEvent = event.dyn_ref().unwrap();
-            Arc::new(KeyboardData {
-                alt_key: evt.alt_key(),
-                char_code: evt.char_code(),
-                key: evt.key(),
-                key_code: KeyCode::from_raw_code(evt.key_code() as u8),
-                ctrl_key: evt.ctrl_key(),
-                locale: "not implemented".to_string(),
-                location: evt.location() as usize,
-                meta_key: evt.meta_key(),
-                repeat: evt.repeat(),
-                shift_key: evt.shift_key(),
-                which: evt.which() as usize,
-            })
-        }
+        "keydown" | "keypress" | "keyup" => Arc::new(KeyboardData::from(event)),
         "focus" | "blur" => Arc::new(FocusData {}),
 
         // todo: these handlers might get really slow if the input box gets large and allocation pressure is heavy
@@ -277,88 +261,21 @@ fn virtual_event_from_websys_event(
         "click" | "contextmenu" | "doubleclick" | "drag" | "dragend" | "dragenter" | "dragexit"
         | "dragleave" | "dragover" | "dragstart" | "drop" | "mousedown" | "mouseenter"
         | "mouseleave" | "mousemove" | "mouseout" | "mouseover" | "mouseup" => {
-            let evt: &web_sys::MouseEvent = event.dyn_ref().unwrap();
-            Arc::new(MouseData {
-                alt_key: evt.alt_key(),
-                button: evt.button(),
-                buttons: evt.buttons(),
-                client_x: evt.client_x(),
-                client_y: evt.client_y(),
-                ctrl_key: evt.ctrl_key(),
-                meta_key: evt.meta_key(),
-                screen_x: evt.screen_x(),
-                screen_y: evt.screen_y(),
-                shift_key: evt.shift_key(),
-                page_x: evt.page_x(),
-                page_y: evt.page_y(),
-            })
+            Arc::new(MouseData::from(event))
         }
         "pointerdown" | "pointermove" | "pointerup" | "pointercancel" | "gotpointercapture"
         | "lostpointercapture" | "pointerenter" | "pointerleave" | "pointerover" | "pointerout" => {
-            let evt: &web_sys::PointerEvent = event.dyn_ref().unwrap();
-            Arc::new(PointerData {
-                alt_key: evt.alt_key(),
-                button: evt.button(),
-                buttons: evt.buttons(),
-                client_x: evt.client_x(),
-                client_y: evt.client_y(),
-                ctrl_key: evt.ctrl_key(),
-                meta_key: evt.meta_key(),
-                page_x: evt.page_x(),
-                page_y: evt.page_y(),
-                screen_x: evt.screen_x(),
-                screen_y: evt.screen_y(),
-                shift_key: evt.shift_key(),
-                pointer_id: evt.pointer_id(),
-                width: evt.width(),
-                height: evt.height(),
-                pressure: evt.pressure(),
-                tangential_pressure: evt.tangential_pressure(),
-                tilt_x: evt.tilt_x(),
-                tilt_y: evt.tilt_y(),
-                twist: evt.twist(),
-                pointer_type: evt.pointer_type(),
-                is_primary: evt.is_primary(),
-                // get_modifier_state: evt.get_modifier_state(),
-            })
+            Arc::new(PointerData::from(event))
         }
         "select" => Arc::new(SelectionData {}),
-        "touchcancel" | "touchend" | "touchmove" | "touchstart" => {
-            let evt: &web_sys::TouchEvent = event.dyn_ref().unwrap();
-            Arc::new(TouchData {
-                alt_key: evt.alt_key(),
-                ctrl_key: evt.ctrl_key(),
-                meta_key: evt.meta_key(),
-                shift_key: evt.shift_key(),
-            })
-        }
+        "touchcancel" | "touchend" | "touchmove" | "touchstart" => Arc::new(TouchData::from(event)),
 
         "scroll" => Arc::new(()),
-        "wheel" => {
-            let evt: &web_sys::WheelEvent = event.dyn_ref().unwrap();
-            Arc::new(WheelData {
-                delta_x: evt.delta_x(),
-                delta_y: evt.delta_y(),
-                delta_z: evt.delta_z(),
-                delta_mode: evt.delta_mode(),
-            })
-        }
+        "wheel" => Arc::new(WheelData::from(event)),
         "animationstart" | "animationend" | "animationiteration" => {
-            let evt: &web_sys::AnimationEvent = event.dyn_ref().unwrap();
-            Arc::new(AnimationData {
-                elapsed_time: evt.elapsed_time(),
-                animation_name: evt.animation_name(),
-                pseudo_element: evt.pseudo_element(),
-            })
-        }
-        "transitionend" => {
-            let evt: &web_sys::TransitionEvent = event.dyn_ref().unwrap();
-            Arc::new(TransitionData {
-                elapsed_time: evt.elapsed_time(),
-                property_name: evt.property_name(),
-                pseudo_element: evt.pseudo_element(),
-            })
+            Arc::new(AnimationData::from(event))
         }
+        "transitionend" => Arc::new(TransitionData::from(event)),
         "abort" | "canplay" | "canplaythrough" | "durationchange" | "emptied" | "encrypted"
         | "ended" | "error" | "loadeddata" | "loadedmetadata" | "loadstart" | "pause" | "play"
         | "playing" | "progress" | "ratechange" | "seeked" | "seeking" | "stalled" | "suspend"

+ 7 - 2
src/lib.rs

@@ -6,10 +6,10 @@ pub mod hooks {
     #[cfg(feature = "hooks")]
     pub use dioxus_hooks::*;
 
-    #[cfg(all(target = "wasm", feature = "web"))]
+    #[cfg(all(target_arch = "wasm32", feature = "web"))]
     pub use dioxus_web::use_eval;
 
-    #[cfg(all(not(target = "wasm"), feature = "desktop"))]
+    #[cfg(all(not(target_arch = "wasm32"), feature = "desktop"))]
     pub use dioxus_desktop::use_eval;
 }
 
@@ -31,6 +31,11 @@ pub use dioxus_desktop as desktop;
 #[cfg(feature = "tui")]
 pub use dioxus_tui as tui;
 
+#[cfg(feature = "native-core")]
+pub use dioxus_native_core as native_core;
+#[cfg(feature = "native-core")]
+pub use dioxus_native_core_macro as native_core_macro;
+
 #[cfg(feature = "fermi")]
 pub use fermi;