From 7f4e25775781b04df3371dec5c71dfef3e657aea Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Sat, 26 Mar 2022 20:10:15 -0500 Subject: [PATCH] bugfixes, testing and refactoring --- .vscode/launch.json | 2541 +++++++++++++++++++ examples/tui_color_test.rs | 56 +- examples/tui_readme.rs | 17 +- examples/tui_stress_test.rs | 88 + examples/tui_text.rs | 2 +- packages/core/src/diff.rs | 1 + packages/native-core/Cargo.toml | 9 +- packages/native-core/src/client_tree.rs | 580 +++++ packages/native-core/src/layout.rs | 5 +- packages/native-core/src/lib.rs | 526 +--- packages/native-core/tests/change_nodes.rs | 153 ++ packages/native-core/tests/initial_build.rs | 124 + packages/native-core/tests/state.rs | 320 +++ packages/tui/Cargo.toml | 4 +- packages/tui/src/hooks.rs | 41 +- packages/tui/src/lib.rs | 44 +- packages/tui/src/render.rs | 20 +- packages/tui/src/style_attributes.rs | 6 +- packages/tui/src/utils.rs | 83 - packages/tui/tests/margin.rs | 7 + 20 files changed, 3888 insertions(+), 739 deletions(-) create mode 100644 .vscode/launch.json create mode 100644 examples/tui_stress_test.rs create mode 100644 packages/native-core/src/client_tree.rs create mode 100644 packages/native-core/tests/change_nodes.rs create mode 100644 packages/native-core/tests/initial_build.rs create mode 100644 packages/native-core/tests/state.rs delete mode 100644 packages/tui/src/utils.rs diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 000000000..fd829da72 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,2541 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "type": "lldb", + "request": "launch", + "name": "Debug unit tests in library 'dioxus-core'", + "cargo": { + "args": [ + "test", + "--no-run", + "--lib", + "--package=dioxus-core" + ], + "filter": { + "name": "dioxus-core", + "kind": "lib" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug unit tests in library 'dioxus-rsx'", + "cargo": { + "args": [ + "test", + "--no-run", + "--lib", + "--package=dioxus-rsx" + ], + "filter": { + "name": "dioxus-rsx", + "kind": "lib" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug unit tests in library 'dioxus-html'", + "cargo": { + "args": [ + "test", + "--no-run", + "--lib", + "--package=dioxus-html" + ], + "filter": { + "name": "dioxus-html", + "kind": "lib" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug unit tests in library 'dioxus-hooks'", + "cargo": { + "args": [ + "test", + "--no-run", + "--lib", + "--package=dioxus-hooks" + ], + "filter": { + "name": "dioxus-hooks", + "kind": "lib" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug unit tests in library 'dioxus-web'", + "cargo": { + "args": [ + "test", + "--no-run", + "--lib", + "--package=dioxus-web" + ], + "filter": { + "name": "dioxus-web", + "kind": "lib" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug example 'hydrate'", + "cargo": { + "args": [ + "build", + "--example=hydrate", + "--package=dioxus-web" + ], + "filter": { + "name": "hydrate", + "kind": "example" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug unit tests in example 'hydrate'", + "cargo": { + "args": [ + "test", + "--no-run", + "--example=hydrate", + "--package=dioxus-web" + ], + "filter": { + "name": "hydrate", + "kind": "example" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug integration test 'hydrate'", + "cargo": { + "args": [ + "test", + "--no-run", + "--test=hydrate", + "--package=dioxus-web" + ], + "filter": { + "name": "hydrate", + "kind": "test" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug unit tests in library 'dioxus-interpreter-js'", + "cargo": { + "args": [ + "test", + "--no-run", + "--lib", + "--package=dioxus-interpreter-js" + ], + "filter": { + "name": "dioxus-interpreter-js", + "kind": "lib" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug unit tests in library 'dioxus-ssr'", + "cargo": { + "args": [ + "test", + "--no-run", + "--lib", + "--package=dioxus-ssr" + ], + "filter": { + "name": "dioxus-ssr", + "kind": "lib" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug integration test 'renders'", + "cargo": { + "args": [ + "test", + "--no-run", + "--test=renders", + "--package=dioxus-ssr" + ], + "filter": { + "name": "renders", + "kind": "test" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug unit tests in library 'dioxus-desktop'", + "cargo": { + "args": [ + "test", + "--no-run", + "--lib", + "--package=dioxus-desktop" + ], + "filter": { + "name": "dioxus-desktop", + "kind": "lib" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug unit tests in library 'dioxus-mobile'", + "cargo": { + "args": [ + "test", + "--no-run", + "--lib", + "--package=dioxus-mobile" + ], + "filter": { + "name": "dioxus-mobile", + "kind": "lib" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug unit tests in library 'fermi'", + "cargo": { + "args": [ + "test", + "--no-run", + "--lib", + "--package=fermi" + ], + "filter": { + "name": "fermi", + "kind": "lib" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug unit tests in library 'dioxus-tui'", + "cargo": { + "args": [ + "test", + "--no-run", + "--lib", + "--package=dioxus-tui" + ], + "filter": { + "name": "dioxus-tui", + "kind": "lib" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug integration test 'margin'", + "cargo": { + "args": [ + "test", + "--no-run", + "--test=margin", + "--package=dioxus-tui" + ], + "filter": { + "name": "margin", + "kind": "test" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug unit tests in library 'dioxus-native-core'", + "cargo": { + "args": [ + "test", + "--no-run", + "--lib", + "--package=dioxus-native-core" + ], + "filter": { + "name": "dioxus-native-core", + "kind": "lib" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug unit tests in library 'dioxus'", + "cargo": { + "args": [ + "test", + "--no-run", + "--lib", + "--package=dioxus" + ], + "filter": { + "name": "dioxus", + "kind": "lib" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug example 'all_css'", + "cargo": { + "args": [ + "build", + "--example=all_css", + "--package=dioxus" + ], + "filter": { + "name": "all_css", + "kind": "example" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug unit tests in example 'all_css'", + "cargo": { + "args": [ + "test", + "--no-run", + "--example=all_css", + "--package=dioxus" + ], + "filter": { + "name": "all_css", + "kind": "example" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug example 'borrowed'", + "cargo": { + "args": [ + "build", + "--example=borrowed", + "--package=dioxus" + ], + "filter": { + "name": "borrowed", + "kind": "example" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug unit tests in example 'borrowed'", + "cargo": { + "args": [ + "test", + "--no-run", + "--example=borrowed", + "--package=dioxus" + ], + "filter": { + "name": "borrowed", + "kind": "example" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug example 'calculator'", + "cargo": { + "args": [ + "build", + "--example=calculator", + "--package=dioxus" + ], + "filter": { + "name": "calculator", + "kind": "example" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug unit tests in example 'calculator'", + "cargo": { + "args": [ + "test", + "--no-run", + "--example=calculator", + "--package=dioxus" + ], + "filter": { + "name": "calculator", + "kind": "example" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug example 'crm'", + "cargo": { + "args": [ + "build", + "--example=crm", + "--package=dioxus" + ], + "filter": { + "name": "crm", + "kind": "example" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug unit tests in example 'crm'", + "cargo": { + "args": [ + "test", + "--no-run", + "--example=crm", + "--package=dioxus" + ], + "filter": { + "name": "crm", + "kind": "example" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug example 'custom_assets'", + "cargo": { + "args": [ + "build", + "--example=custom_assets", + "--package=dioxus" + ], + "filter": { + "name": "custom_assets", + "kind": "example" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug unit tests in example 'custom_assets'", + "cargo": { + "args": [ + "test", + "--no-run", + "--example=custom_assets", + "--package=dioxus" + ], + "filter": { + "name": "custom_assets", + "kind": "example" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug example 'disabled'", + "cargo": { + "args": [ + "build", + "--example=disabled", + "--package=dioxus" + ], + "filter": { + "name": "disabled", + "kind": "example" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug unit tests in example 'disabled'", + "cargo": { + "args": [ + "test", + "--no-run", + "--example=disabled", + "--package=dioxus" + ], + "filter": { + "name": "disabled", + "kind": "example" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug example 'dog_app'", + "cargo": { + "args": [ + "build", + "--example=dog_app", + "--package=dioxus" + ], + "filter": { + "name": "dog_app", + "kind": "example" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug unit tests in example 'dog_app'", + "cargo": { + "args": [ + "test", + "--no-run", + "--example=dog_app", + "--package=dioxus" + ], + "filter": { + "name": "dog_app", + "kind": "example" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug example 'error_handle'", + "cargo": { + "args": [ + "build", + "--example=error_handle", + "--package=dioxus" + ], + "filter": { + "name": "error_handle", + "kind": "example" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug unit tests in example 'error_handle'", + "cargo": { + "args": [ + "test", + "--no-run", + "--example=error_handle", + "--package=dioxus" + ], + "filter": { + "name": "error_handle", + "kind": "example" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug example 'fermi'", + "cargo": { + "args": [ + "build", + "--example=fermi", + "--package=dioxus" + ], + "filter": { + "name": "fermi", + "kind": "example" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug unit tests in example 'fermi'", + "cargo": { + "args": [ + "test", + "--no-run", + "--example=fermi", + "--package=dioxus" + ], + "filter": { + "name": "fermi", + "kind": "example" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug example 'filedragdrop'", + "cargo": { + "args": [ + "build", + "--example=filedragdrop", + "--package=dioxus" + ], + "filter": { + "name": "filedragdrop", + "kind": "example" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug unit tests in example 'filedragdrop'", + "cargo": { + "args": [ + "test", + "--no-run", + "--example=filedragdrop", + "--package=dioxus" + ], + "filter": { + "name": "filedragdrop", + "kind": "example" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug example 'file_explorer'", + "cargo": { + "args": [ + "build", + "--example=file_explorer", + "--package=dioxus" + ], + "filter": { + "name": "file_explorer", + "kind": "example" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug unit tests in example 'file_explorer'", + "cargo": { + "args": [ + "test", + "--no-run", + "--example=file_explorer", + "--package=dioxus" + ], + "filter": { + "name": "file_explorer", + "kind": "example" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug example 'flat_router'", + "cargo": { + "args": [ + "build", + "--example=flat_router", + "--package=dioxus" + ], + "filter": { + "name": "flat_router", + "kind": "example" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug unit tests in example 'flat_router'", + "cargo": { + "args": [ + "test", + "--no-run", + "--example=flat_router", + "--package=dioxus" + ], + "filter": { + "name": "flat_router", + "kind": "example" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug example 'form'", + "cargo": { + "args": [ + "build", + "--example=form", + "--package=dioxus" + ], + "filter": { + "name": "form", + "kind": "example" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug unit tests in example 'form'", + "cargo": { + "args": [ + "test", + "--no-run", + "--example=form", + "--package=dioxus" + ], + "filter": { + "name": "form", + "kind": "example" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug example 'framework_benchmark'", + "cargo": { + "args": [ + "build", + "--example=framework_benchmark", + "--package=dioxus" + ], + "filter": { + "name": "framework_benchmark", + "kind": "example" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug unit tests in example 'framework_benchmark'", + "cargo": { + "args": [ + "test", + "--no-run", + "--example=framework_benchmark", + "--package=dioxus" + ], + "filter": { + "name": "framework_benchmark", + "kind": "example" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug example 'heavy_compute'", + "cargo": { + "args": [ + "build", + "--example=heavy_compute", + "--package=dioxus" + ], + "filter": { + "name": "heavy_compute", + "kind": "example" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug unit tests in example 'heavy_compute'", + "cargo": { + "args": [ + "test", + "--no-run", + "--example=heavy_compute", + "--package=dioxus" + ], + "filter": { + "name": "heavy_compute", + "kind": "example" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug example 'hello_world'", + "cargo": { + "args": [ + "build", + "--example=hello_world", + "--package=dioxus" + ], + "filter": { + "name": "hello_world", + "kind": "example" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug unit tests in example 'hello_world'", + "cargo": { + "args": [ + "test", + "--no-run", + "--example=hello_world", + "--package=dioxus" + ], + "filter": { + "name": "hello_world", + "kind": "example" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug example 'hydration'", + "cargo": { + "args": [ + "build", + "--example=hydration", + "--package=dioxus" + ], + "filter": { + "name": "hydration", + "kind": "example" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug unit tests in example 'hydration'", + "cargo": { + "args": [ + "test", + "--no-run", + "--example=hydration", + "--package=dioxus" + ], + "filter": { + "name": "hydration", + "kind": "example" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug example 'inputs'", + "cargo": { + "args": [ + "build", + "--example=inputs", + "--package=dioxus" + ], + "filter": { + "name": "inputs", + "kind": "example" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug unit tests in example 'inputs'", + "cargo": { + "args": [ + "test", + "--no-run", + "--example=inputs", + "--package=dioxus" + ], + "filter": { + "name": "inputs", + "kind": "example" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug example 'link'", + "cargo": { + "args": [ + "build", + "--example=link", + "--package=dioxus" + ], + "filter": { + "name": "link", + "kind": "example" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug unit tests in example 'link'", + "cargo": { + "args": [ + "test", + "--no-run", + "--example=link", + "--package=dioxus" + ], + "filter": { + "name": "link", + "kind": "example" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug example 'login_form'", + "cargo": { + "args": [ + "build", + "--example=login_form", + "--package=dioxus" + ], + "filter": { + "name": "login_form", + "kind": "example" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug unit tests in example 'login_form'", + "cargo": { + "args": [ + "test", + "--no-run", + "--example=login_form", + "--package=dioxus" + ], + "filter": { + "name": "login_form", + "kind": "example" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug example 'nested_listeners'", + "cargo": { + "args": [ + "build", + "--example=nested_listeners", + "--package=dioxus" + ], + "filter": { + "name": "nested_listeners", + "kind": "example" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug unit tests in example 'nested_listeners'", + "cargo": { + "args": [ + "test", + "--no-run", + "--example=nested_listeners", + "--package=dioxus" + ], + "filter": { + "name": "nested_listeners", + "kind": "example" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug example 'optional_props'", + "cargo": { + "args": [ + "build", + "--example=optional_props", + "--package=dioxus" + ], + "filter": { + "name": "optional_props", + "kind": "example" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug unit tests in example 'optional_props'", + "cargo": { + "args": [ + "test", + "--no-run", + "--example=optional_props", + "--package=dioxus" + ], + "filter": { + "name": "optional_props", + "kind": "example" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug example 'pattern_model'", + "cargo": { + "args": [ + "build", + "--example=pattern_model", + "--package=dioxus" + ], + "filter": { + "name": "pattern_model", + "kind": "example" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug unit tests in example 'pattern_model'", + "cargo": { + "args": [ + "test", + "--no-run", + "--example=pattern_model", + "--package=dioxus" + ], + "filter": { + "name": "pattern_model", + "kind": "example" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug example 'pattern_reducer'", + "cargo": { + "args": [ + "build", + "--example=pattern_reducer", + "--package=dioxus" + ], + "filter": { + "name": "pattern_reducer", + "kind": "example" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug unit tests in example 'pattern_reducer'", + "cargo": { + "args": [ + "test", + "--no-run", + "--example=pattern_reducer", + "--package=dioxus" + ], + "filter": { + "name": "pattern_reducer", + "kind": "example" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug example 'readme'", + "cargo": { + "args": [ + "build", + "--example=readme", + "--package=dioxus" + ], + "filter": { + "name": "readme", + "kind": "example" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug unit tests in example 'readme'", + "cargo": { + "args": [ + "test", + "--no-run", + "--example=readme", + "--package=dioxus" + ], + "filter": { + "name": "readme", + "kind": "example" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug example 'router'", + "cargo": { + "args": [ + "build", + "--example=router", + "--package=dioxus" + ], + "filter": { + "name": "router", + "kind": "example" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug unit tests in example 'router'", + "cargo": { + "args": [ + "test", + "--no-run", + "--example=router", + "--package=dioxus" + ], + "filter": { + "name": "router", + "kind": "example" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug example 'rsx_compile_fail'", + "cargo": { + "args": [ + "build", + "--example=rsx_compile_fail", + "--package=dioxus" + ], + "filter": { + "name": "rsx_compile_fail", + "kind": "example" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug unit tests in example 'rsx_compile_fail'", + "cargo": { + "args": [ + "test", + "--no-run", + "--example=rsx_compile_fail", + "--package=dioxus" + ], + "filter": { + "name": "rsx_compile_fail", + "kind": "example" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug example 'rsx_usage'", + "cargo": { + "args": [ + "build", + "--example=rsx_usage", + "--package=dioxus" + ], + "filter": { + "name": "rsx_usage", + "kind": "example" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug unit tests in example 'rsx_usage'", + "cargo": { + "args": [ + "test", + "--no-run", + "--example=rsx_usage", + "--package=dioxus" + ], + "filter": { + "name": "rsx_usage", + "kind": "example" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug example 'ssr'", + "cargo": { + "args": [ + "build", + "--example=ssr", + "--package=dioxus" + ], + "filter": { + "name": "ssr", + "kind": "example" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug unit tests in example 'ssr'", + "cargo": { + "args": [ + "test", + "--no-run", + "--example=ssr", + "--package=dioxus" + ], + "filter": { + "name": "ssr", + "kind": "example" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug example 'suspense'", + "cargo": { + "args": [ + "build", + "--example=suspense", + "--package=dioxus" + ], + "filter": { + "name": "suspense", + "kind": "example" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug unit tests in example 'suspense'", + "cargo": { + "args": [ + "test", + "--no-run", + "--example=suspense", + "--package=dioxus" + ], + "filter": { + "name": "suspense", + "kind": "example" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug example 'svg'", + "cargo": { + "args": [ + "build", + "--example=svg", + "--package=dioxus" + ], + "filter": { + "name": "svg", + "kind": "example" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug unit tests in example 'svg'", + "cargo": { + "args": [ + "test", + "--no-run", + "--example=svg", + "--package=dioxus" + ], + "filter": { + "name": "svg", + "kind": "example" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug example 'tailwind'", + "cargo": { + "args": [ + "build", + "--example=tailwind", + "--package=dioxus" + ], + "filter": { + "name": "tailwind", + "kind": "example" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug unit tests in example 'tailwind'", + "cargo": { + "args": [ + "test", + "--no-run", + "--example=tailwind", + "--package=dioxus" + ], + "filter": { + "name": "tailwind", + "kind": "example" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug example 'tasks'", + "cargo": { + "args": [ + "build", + "--example=tasks", + "--package=dioxus" + ], + "filter": { + "name": "tasks", + "kind": "example" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug unit tests in example 'tasks'", + "cargo": { + "args": [ + "test", + "--no-run", + "--example=tasks", + "--package=dioxus" + ], + "filter": { + "name": "tasks", + "kind": "example" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug example 'textarea'", + "cargo": { + "args": [ + "build", + "--example=textarea", + "--package=dioxus" + ], + "filter": { + "name": "textarea", + "kind": "example" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug unit tests in example 'textarea'", + "cargo": { + "args": [ + "test", + "--no-run", + "--example=textarea", + "--package=dioxus" + ], + "filter": { + "name": "textarea", + "kind": "example" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug example 'todomvc'", + "cargo": { + "args": [ + "build", + "--example=todomvc", + "--package=dioxus" + ], + "filter": { + "name": "todomvc", + "kind": "example" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug unit tests in example 'todomvc'", + "cargo": { + "args": [ + "test", + "--no-run", + "--example=todomvc", + "--package=dioxus" + ], + "filter": { + "name": "todomvc", + "kind": "example" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug example 'tui_border'", + "cargo": { + "args": [ + "build", + "--example=tui_border", + "--package=dioxus" + ], + "filter": { + "name": "tui_border", + "kind": "example" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug unit tests in example 'tui_border'", + "cargo": { + "args": [ + "test", + "--no-run", + "--example=tui_border", + "--package=dioxus" + ], + "filter": { + "name": "tui_border", + "kind": "example" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug example 'tui_color_test'", + "cargo": { + "args": [ + "build", + "--example=tui_color_test", + "--package=dioxus" + ], + "filter": { + "name": "tui_color_test", + "kind": "example" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug unit tests in example 'tui_color_test'", + "cargo": { + "args": [ + "test", + "--no-run", + "--example=tui_color_test", + "--package=dioxus" + ], + "filter": { + "name": "tui_color_test", + "kind": "example" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug example 'tui_components'", + "cargo": { + "args": [ + "build", + "--example=tui_components", + "--package=dioxus" + ], + "filter": { + "name": "tui_components", + "kind": "example" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug unit tests in example 'tui_components'", + "cargo": { + "args": [ + "test", + "--no-run", + "--example=tui_components", + "--package=dioxus" + ], + "filter": { + "name": "tui_components", + "kind": "example" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug example 'tui_frame'", + "cargo": { + "args": [ + "build", + "--example=tui_frame", + "--package=dioxus" + ], + "filter": { + "name": "tui_frame", + "kind": "example" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug unit tests in example 'tui_frame'", + "cargo": { + "args": [ + "test", + "--no-run", + "--example=tui_frame", + "--package=dioxus" + ], + "filter": { + "name": "tui_frame", + "kind": "example" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug example 'tui_hover'", + "cargo": { + "args": [ + "build", + "--example=tui_hover", + "--package=dioxus" + ], + "filter": { + "name": "tui_hover", + "kind": "example" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug unit tests in example 'tui_hover'", + "cargo": { + "args": [ + "test", + "--no-run", + "--example=tui_hover", + "--package=dioxus" + ], + "filter": { + "name": "tui_hover", + "kind": "example" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug example 'tui_keys'", + "cargo": { + "args": [ + "build", + "--example=tui_keys", + "--package=dioxus" + ], + "filter": { + "name": "tui_keys", + "kind": "example" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug unit tests in example 'tui_keys'", + "cargo": { + "args": [ + "test", + "--no-run", + "--example=tui_keys", + "--package=dioxus" + ], + "filter": { + "name": "tui_keys", + "kind": "example" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug example 'tui_list'", + "cargo": { + "args": [ + "build", + "--example=tui_list", + "--package=dioxus" + ], + "filter": { + "name": "tui_list", + "kind": "example" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug unit tests in example 'tui_list'", + "cargo": { + "args": [ + "test", + "--no-run", + "--example=tui_list", + "--package=dioxus" + ], + "filter": { + "name": "tui_list", + "kind": "example" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug example 'tui_margin'", + "cargo": { + "args": [ + "build", + "--example=tui_margin", + "--package=dioxus" + ], + "filter": { + "name": "tui_margin", + "kind": "example" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug unit tests in example 'tui_margin'", + "cargo": { + "args": [ + "test", + "--no-run", + "--example=tui_margin", + "--package=dioxus" + ], + "filter": { + "name": "tui_margin", + "kind": "example" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug example 'tui_quadrants'", + "cargo": { + "args": [ + "build", + "--example=tui_quadrants", + "--package=dioxus" + ], + "filter": { + "name": "tui_quadrants", + "kind": "example" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug unit tests in example 'tui_quadrants'", + "cargo": { + "args": [ + "test", + "--no-run", + "--example=tui_quadrants", + "--package=dioxus" + ], + "filter": { + "name": "tui_quadrants", + "kind": "example" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug example 'tui_readme'", + "cargo": { + "args": [ + "build", + "--example=tui_readme", + "--package=dioxus" + ], + "filter": { + "name": "tui_readme", + "kind": "example" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug unit tests in example 'tui_readme'", + "cargo": { + "args": [ + "test", + "--no-run", + "--example=tui_readme", + "--package=dioxus" + ], + "filter": { + "name": "tui_readme", + "kind": "example" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug example 'tui_task'", + "cargo": { + "args": [ + "build", + "--example=tui_task", + "--package=dioxus" + ], + "filter": { + "name": "tui_task", + "kind": "example" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug unit tests in example 'tui_task'", + "cargo": { + "args": [ + "test", + "--no-run", + "--example=tui_task", + "--package=dioxus" + ], + "filter": { + "name": "tui_task", + "kind": "example" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug example 'tui_text'", + "cargo": { + "args": [ + "build", + "--example=tui_text", + "--package=dioxus" + ], + "filter": { + "name": "tui_text", + "kind": "example" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug unit tests in example 'tui_text'", + "cargo": { + "args": [ + "test", + "--no-run", + "--example=tui_text", + "--package=dioxus" + ], + "filter": { + "name": "tui_text", + "kind": "example" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug example 'window_event'", + "cargo": { + "args": [ + "build", + "--example=window_event", + "--package=dioxus" + ], + "filter": { + "name": "window_event", + "kind": "example" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug unit tests in example 'window_event'", + "cargo": { + "args": [ + "test", + "--no-run", + "--example=window_event", + "--package=dioxus" + ], + "filter": { + "name": "window_event", + "kind": "example" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug example 'xss_safety'", + "cargo": { + "args": [ + "build", + "--example=xss_safety", + "--package=dioxus" + ], + "filter": { + "name": "xss_safety", + "kind": "example" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug unit tests in example 'xss_safety'", + "cargo": { + "args": [ + "test", + "--no-run", + "--example=xss_safety", + "--package=dioxus" + ], + "filter": { + "name": "xss_safety", + "kind": "example" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug integration test 'borrowedstate'", + "cargo": { + "args": [ + "test", + "--no-run", + "--test=borrowedstate", + "--package=dioxus" + ], + "filter": { + "name": "borrowedstate", + "kind": "test" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug integration test 'create_dom'", + "cargo": { + "args": [ + "test", + "--no-run", + "--test=create_dom", + "--package=dioxus" + ], + "filter": { + "name": "create_dom", + "kind": "test" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug integration test 'diffing'", + "cargo": { + "args": [ + "test", + "--no-run", + "--test=diffing", + "--package=dioxus" + ], + "filter": { + "name": "diffing", + "kind": "test" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug integration test 'earlyabort'", + "cargo": { + "args": [ + "test", + "--no-run", + "--test=earlyabort", + "--package=dioxus" + ], + "filter": { + "name": "earlyabort", + "kind": "test" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug integration test 'fermi'", + "cargo": { + "args": [ + "test", + "--no-run", + "--test=fermi", + "--package=dioxus" + ], + "filter": { + "name": "fermi", + "kind": "test" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug integration test 'lifecycle'", + "cargo": { + "args": [ + "test", + "--no-run", + "--test=lifecycle", + "--package=dioxus" + ], + "filter": { + "name": "lifecycle", + "kind": "test" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug integration test 'miri_stress'", + "cargo": { + "args": [ + "test", + "--no-run", + "--test=miri_stress", + "--package=dioxus" + ], + "filter": { + "name": "miri_stress", + "kind": "test" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug integration test 'passthru'", + "cargo": { + "args": [ + "test", + "--no-run", + "--test=passthru", + "--package=dioxus" + ], + "filter": { + "name": "passthru", + "kind": "test" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug integration test 'scheduler'", + "cargo": { + "args": [ + "test", + "--no-run", + "--test=scheduler", + "--package=dioxus" + ], + "filter": { + "name": "scheduler", + "kind": "test" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug integration test 'sharedstate'", + "cargo": { + "args": [ + "test", + "--no-run", + "--test=sharedstate", + "--package=dioxus" + ], + "filter": { + "name": "sharedstate", + "kind": "test" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug integration test 'task'", + "cargo": { + "args": [ + "test", + "--no-run", + "--test=task", + "--package=dioxus" + ], + "filter": { + "name": "task", + "kind": "test" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug integration test 'test_logging'", + "cargo": { + "args": [ + "test", + "--no-run", + "--test=test_logging", + "--package=dioxus" + ], + "filter": { + "name": "test_logging", + "kind": "test" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug integration test 'vdom_rebuild'", + "cargo": { + "args": [ + "test", + "--no-run", + "--test=vdom_rebuild", + "--package=dioxus" + ], + "filter": { + "name": "vdom_rebuild", + "kind": "test" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug benchmark 'create'", + "cargo": { + "args": [ + "test", + "--no-run", + "--bench=create", + "--package=dioxus" + ], + "filter": { + "name": "create", + "kind": "bench" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug benchmark 'jsframework'", + "cargo": { + "args": [ + "test", + "--no-run", + "--bench=jsframework", + "--package=dioxus" + ], + "filter": { + "name": "jsframework", + "kind": "bench" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug unit tests in library 'dioxus-router'", + "cargo": { + "args": [ + "test", + "--no-run", + "--lib", + "--package=dioxus-router" + ], + "filter": { + "name": "dioxus-router", + "kind": "lib" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug example 'simple'", + "cargo": { + "args": [ + "build", + "--example=simple", + "--package=dioxus-router" + ], + "filter": { + "name": "simple", + "kind": "example" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug unit tests in example 'simple'", + "cargo": { + "args": [ + "test", + "--no-run", + "--example=simple", + "--package=dioxus-router" + ], + "filter": { + "name": "simple", + "kind": "example" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug integration test 'ssr_router'", + "cargo": { + "args": [ + "test", + "--no-run", + "--test=ssr_router", + "--package=dioxus-router" + ], + "filter": { + "name": "ssr_router", + "kind": "test" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug integration test 'web_router'", + "cargo": { + "args": [ + "test", + "--no-run", + "--test=web_router", + "--package=dioxus-router" + ], + "filter": { + "name": "web_router", + "kind": "test" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + } + ] +} \ No newline at end of file diff --git a/examples/tui_color_test.rs b/examples/tui_color_test.rs index b110ecef0..358fae1f6 100644 --- a/examples/tui_color_test.rs +++ b/examples/tui_color_test.rs @@ -4,54 +4,11 @@ fn main() { dioxus::tui::launch_cfg( app, dioxus::tui::Config { - rendering_mode: dioxus::tui::RenderingMode::Rgb, + rendering_mode: dioxus::tui::RenderingMode::Ansi, }, ); } -#[derive(Props, PartialEq)] -struct BoxProps { - x: i32, - y: i32, - hue: f32, - alpha: f32, -} -fn Box(cx: Scope) -> Element { - let painted = use_state(&cx, || true); - - // use_future(&cx, (), move |_| { - // let count = count.to_owned(); - // let update = cx.schedule_update(); - // async move { - // loop { - // count.with_mut(|i| *i += 1); - // tokio::time::sleep(std::time::Duration::from_millis(800)).await; - // update(); - // } - // } - // }); - - let x = cx.props.x; - let y = cx.props.y; - let hue = cx.props.hue; - let current_painted = painted.get(); - let alpha = cx.props.alpha + if *current_painted { 100.0 } else { 0.0 }; - - cx.render(rsx! { - div { - left: "{x}px", - top: "{y}px", - width: "100%", - height: "100%", - background_color: "hsl({hue}, 100%, 50%, {alpha}%)", - align_items: "center", - onkeydown: |_| painted.with_mut(|i| *i = !*i), - onmouseenter: |_| painted.with_mut(|i| *i = !*i), - p{" "} - } - }) -} - fn app(cx: Scope) -> Element { let steps = 50; cx.render(rsx! { @@ -71,11 +28,12 @@ fn app(cx: Scope) -> Element { { let alpha = y as f32*100.0/steps as f32; cx.render(rsx! { - Box{ - x: x, - y: y, - alpha: alpha, - hue: hue, + div { + left: "{x}px", + top: "{y}px", + width: "10%", + height: "100%", + background_color: "hsl({hue}, 100%, 50%, {alpha}%)", } }) } diff --git a/examples/tui_readme.rs b/examples/tui_readme.rs index 21aa6c996..3e15cf7e3 100644 --- a/examples/tui_readme.rs +++ b/examples/tui_readme.rs @@ -5,26 +5,15 @@ fn main() { } fn app(cx: Scope) -> Element { - let alpha = use_state(&cx, || 100); - cx.render(rsx! { div { - onwheel: move |evt| alpha.set((**alpha + evt.data.delta_y as i64).min(100).max(0)), - width: "100%", height: "10px", background_color: "red", - // justify_content: "center", - // align_items: "center", + justify_content: "center", + align_items: "center", - p{ - color: "rgba(0, 255, 0, {alpha}%)", - "Hello world!" - } - p{ - "{alpha}" - } - // p{"Hi"} + "Hello world!" } }) } diff --git a/examples/tui_stress_test.rs b/examples/tui_stress_test.rs new file mode 100644 index 000000000..72e72273d --- /dev/null +++ b/examples/tui_stress_test.rs @@ -0,0 +1,88 @@ +use dioxus::prelude::*; + +fn main() { + dioxus::tui::launch_cfg( + app, + dioxus::tui::Config { + rendering_mode: dioxus::tui::RenderingMode::Rgb, + }, + ); +} + +#[derive(Props, PartialEq)] +struct BoxProps { + x: i32, + y: i32, + hue: f32, + alpha: f32, +} +#[allow(non_snake_case)] +fn Box(cx: Scope) -> Element { + let count = use_state(&cx, || 0); + + use_future(&cx, (), move |_| { + let count = count.to_owned(); + let update = cx.schedule_update(); + async move { + loop { + count.with_mut(|i| *i += 1); + tokio::time::sleep(std::time::Duration::from_millis(800)).await; + update(); + } + } + }); + + let x = cx.props.x * 2; + let y = cx.props.y * 2; + let hue = cx.props.hue; + let count = count.get(); + let alpha = cx.props.alpha + (count % 100) as f32; + + cx.render(rsx! { + div { + left: "{x}%", + top: "{y}%", + width: "100%", + height: "100%", + background_color: "hsl({hue}, 100%, 50%, {alpha}%)", + align_items: "center", + p{"{count}"} + } + }) +} + +fn app(cx: Scope) -> Element { + let steps = 50; + cx.render(rsx! { + div{ + width: "100%", + height: "100%", + flex_direction: "column", + (0..=steps).map(|x| + { + let hue = x as f32*360.0/steps as f32; + cx.render(rsx! { + div{ + width: "100%", + height: "100%", + flex_direction: "row", + (0..=steps).map(|y| + { + let alpha = y as f32*100.0/steps as f32; + cx.render(rsx! { + Box{ + x: x, + y: y, + alpha: alpha, + hue: hue, + } + }) + } + ) + } + }) + } + ) + } + }) +} diff --git a/examples/tui_text.rs b/examples/tui_text.rs index 95f9864a9..b8f04d99a 100644 --- a/examples/tui_text.rs +++ b/examples/tui_text.rs @@ -15,7 +15,7 @@ fn app(cx: Scope) -> Element { onwheel: move |evt| alpha.set((**alpha + evt.data.delta_y as i64).min(100).max(0)), p { - // background_color: "black", + background_color: "black", flex_direction: "column", justify_content: "center", align_items: "center", diff --git a/packages/core/src/diff.rs b/packages/core/src/diff.rs index 5682772be..a0be7fd3c 100644 --- a/packages/core/src/diff.rs +++ b/packages/core/src/diff.rs @@ -423,6 +423,7 @@ impl<'b> DiffState<'b> { match (old.children.len(), new.children.len()) { (0, 0) => {} (0, _) => { + self.mutations.push_root(root); let created = self.create_children(new.children); self.mutations.append_children(created as u32); } diff --git a/packages/native-core/Cargo.toml b/packages/native-core/Cargo.toml index 05ed52a06..c3816dac7 100644 --- a/packages/native-core/Cargo.toml +++ b/packages/native-core/Cargo.toml @@ -3,8 +3,6 @@ name = "dioxus-native-core" version = "0.2.0" edition = "2021" license = "MIT/Apache-2.0" -authors = ["@dementhos"] -description = "TUI-based renderer for Dioxus" repository = "https://github.com/DioxusLabs/dioxus/" homepage = "https://dioxuslabs.com" @@ -13,5 +11,10 @@ dioxus-core = { path = "../core", version = "^0.2.0" } dioxus-html = { path = "../html", version = "^0.2.0" } dioxus-core-macro = { path = "../core-macro", version = "^0.2.0" } -stretch2 = "0.4.1" +# stretch2 = "0.4.1" +stretch2 = { git = "https://github.com/Demonthos/stretch.git" } smallvec = "1.6" +fxhash = "0.2" + +[dev-dependencies] +rand = "0.8.5" \ No newline at end of file diff --git a/packages/native-core/src/client_tree.rs b/packages/native-core/src/client_tree.rs new file mode 100644 index 000000000..6b4017e8f --- /dev/null +++ b/packages/native-core/src/client_tree.rs @@ -0,0 +1,580 @@ +use fxhash::{FxHashMap, FxHashSet}; +use std::{ + collections::VecDeque, + ops::{Index, IndexMut}, +}; + +use dioxus_core::{ElementId, Mutations, VNode, VirtualDom}; + +/// A tree that can sync with dioxus mutations backed by a hashmap. +/// Intended for use in lazy native renderers with a state that passes from parrent to children and or accumulates state from children to parrents. +/// To get started implement [PushedDownState] and or [BubbledUpState] and call [Tree::apply_mutations] and [Tree::update_state]. +#[derive(Debug)] +pub struct ClientTree { + root: usize, + nodes: Vec>>, + nodes_listening: FxHashMap<&'static str, FxHashSet>, + node_stack: smallvec::SmallVec<[usize; 10]>, +} + +impl ClientTree { + pub fn new() -> ClientTree { + ClientTree { + root: 0, + nodes: { + let mut v = Vec::new(); + v.push(Some(TreeNode::new( + 0, + TreeNodeType::Element { + tag: "Root".to_string(), + namespace: Some("Root"), + children: Vec::new(), + }, + ))); + v + }, + nodes_listening: FxHashMap::default(), + node_stack: smallvec::SmallVec::new(), + } + } + + /// Updates the tree, 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) -> Vec { + 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 + 1 { + *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); + } + } + 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); + 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); + 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); + self.link_child(ns, target).unwrap(); + } + } + Remove { root } => { + if let Some(parent) = self[root as usize].parent { + nodes_updated.push(parent.0); + } + self.remove(root as usize).unwrap(); + } + CreateTextNode { root, text } => { + let n = TreeNode::new( + root, + TreeNodeType::Text { + text: text.to_string(), + }, + ); + self.insert(n); + self.node_stack.push(root as usize) + } + CreateElement { root, tag } => { + let n = TreeNode::new( + root, + TreeNodeType::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 = TreeNode::new( + root, + TreeNodeType::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 = TreeNode::new(root, TreeNodeType::Placeholder); + self.insert(n); + self.node_stack.push(root as usize) + } + + NewEventListener { + event_name, + scope: _, + root, + } => { + 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 } => { + 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); + match &mut target.node_type { + TreeNodeType::Text { text } => { + *text = new_text.to_string(); + } + _ => unreachable!(), + } + } + SetAttribute { root, .. } => { + nodes_updated.push(root as usize); + } + RemoveAttribute { root, .. } => { + nodes_updated.push(root as usize); + } + } + } + } + + 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, + us_ctx: &mut US::Ctx, + ds_ctx: &mut DS::Ctx, + ) -> Option> { + let mut to_rerender = FxHashSet::default(); + let mut nodes_updated: Vec<_> = nodes_updated + .into_iter() + .map(|id| (id, self[id].height)) + .collect(); + // Sort nodes first by height, then if the height is the same id. + nodes_updated.sort_by(|fst, snd| fst.1.cmp(&snd.1).then(fst.0.cmp(&snd.0))); + nodes_updated.dedup(); + + // bubble up state. To avoid calling reduce more times than nessisary start from the bottom and go up. + let mut to_bubble: VecDeque<_> = nodes_updated.clone().into(); + while let Some((id, height)) = to_bubble.pop_back() { + let node = &mut self[id as usize]; + let vnode = node.element(vdom); + let node_type = &node.node_type; + let up_state = &mut node.up_state; + let children = match node_type { + TreeNodeType::Element { children, .. } => Some(children), + _ => None, + }; + // todo: reduce cloning state + let old = up_state.clone(); + let mut new = up_state.clone(); + let parent = node.parent.clone(); + new.reduce( + children + .unwrap_or(&Vec::new()) + .clone() + .iter() + .map(|c| &self[*c].up_state), + vnode, + us_ctx, + ); + if new != old { + to_rerender.insert(id); + if let Some(p) = parent { + let i = to_bubble.partition_point(|(other_id, h)| { + *h < height - 1 || (*h == height - 1 && *other_id < p.0) + }); + // make sure the parent is not already queued + if i >= to_bubble.len() || to_bubble[i].0 != p.0 { + to_bubble.insert(i, (p.0, height - 1)); + } + } + let node = &mut self[id as usize]; + node.up_state = new; + } + } + + // push down state. To avoid calling reduce more times than nessisary start from the top and go down. + let mut to_push: VecDeque<_> = nodes_updated.clone().into(); + while let Some((id, height)) = to_push.pop_front() { + let node = &self[id as usize]; + // todo: reduce cloning state + let old = node.down_state.clone(); + let mut new = node.down_state.clone(); + let vnode = node.element(vdom); + new.reduce( + node.parent + .filter(|e| e.0 != 0) + .map(|e| &self[e].down_state), + vnode, + ds_ctx, + ); + if new != old { + to_rerender.insert(id); + let node = &mut self[id as usize]; + match &node.node_type { + TreeNodeType::Element { children, .. } => { + for c in children { + let i = to_push.partition_point(|(other_id, h)| { + *h < height + 1 || (*h == height + 1 && *other_id < c.0) + }); + if i >= to_push.len() || to_push[i].0 != c.0 { + to_push.insert(i, (c.0, height + 1)); + } + } + } + _ => (), + }; + node.down_state = new; + } + } + + 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; + match &n.node_type { + TreeNodeType::Element { children, .. } => { + for c in children.clone() { + self.increase_height(c.0, amount); + } + } + _ => (), + } + } + + fn remove(&mut self, id: usize) -> Option> { + // We do not need to remove the node from the parent's children list for children. + fn inner( + tree: &mut ClientTree, + id: usize, + ) -> Option> { + let mut node = tree.nodes[id as usize].take()?; + match &mut node.node_type { + TreeNodeType::Element { children, .. } => { + for c in children { + inner(tree, 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)); + } + match &mut node.node_type { + TreeNodeType::Element { children, .. } => { + for c in children { + inner(self, c.0)?; + } + } + _ => (), + } + Some(node) + } + + fn insert(&mut self, node: TreeNode) { + 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<&TreeNode> { + self.nodes.get(id)?.as_ref() + } + + pub fn get_mut(&mut self, id: usize) -> Option<&mut TreeNode> { + self.nodes.get_mut(id)?.as_mut() + } + + pub fn get_listening_sorted(&self, event: &'static str) -> Vec<&TreeNode> { + 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 tree 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 tree_node = &self[id]; + match &tree_node.node_type { + TreeNodeType::Element { + tag, + namespace, + children, + } => { + tag == e.tag + && namespace == &e.namespace + && children.iter().copied().collect::>() + == e.children + .iter() + .map(|c| c.mounted_id()) + .collect::>() + && 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 tree_node = &self[id]; + match &tree_node.node_type { + TreeNodeType::Text { text } => t.text == text, + _ => false, + } + } else { + true + } + } + } + } + + /// Return the number of nodes in the tree. + pub fn size(&self) -> usize { + // The tree 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 tree, depth first. + pub fn traverse(&self, mut f: impl FnMut(&TreeNode)) { + fn inner( + tree: &ClientTree, + id: ElementId, + f: &mut impl FnMut(&TreeNode), + ) { + let node = &tree[id]; + f(node); + match &node.node_type { + TreeNodeType::Element { children, .. } => { + for c in children { + inner(tree, *c, f); + } + } + _ => (), + } + } + match &self[self.root].node_type { + TreeNodeType::Element { children, .. } => { + for c in children { + inner(self, *c, &mut f); + } + } + _ => (), + } + } +} + +impl Index for ClientTree { + type Output = TreeNode; + + fn index(&self, idx: usize) -> &Self::Output { + self.get(idx).expect("Node does not exist") + } +} + +impl Index for ClientTree { + type Output = TreeNode; + + fn index(&self, idx: ElementId) -> &Self::Output { + &self[idx.0] + } +} + +impl IndexMut for ClientTree { + fn index_mut(&mut self, idx: usize) -> &mut Self::Output { + self.get_mut(idx).expect("Node does not exist") + } +} +impl IndexMut for ClientTree { + 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 [`TreeNode::element`]. +#[derive(Debug, Clone)] +pub struct TreeNode { + /// The id of the node this node was created from. + pub id: ElementId, + /// The parent id of the node. + pub parent: Option, + /// State of the node that is bubbled up to its parents. The state must depend only on the node and its children. + pub up_state: US, + /// State of the node that is pushed down to the children. The state must depend only on the node itself and its parent. + pub down_state: DS, + /// Additional inforation specific to the node type + pub node_type: TreeNodeType, + /// The number of parents before the root node. The root node has height 1. + pub height: u16, +} + +#[derive(Debug, Clone)] +pub enum TreeNodeType { + Text { + text: String, + }, + Element { + tag: String, + namespace: Option<&'static str>, + children: Vec, + }, + Placeholder, +} + +impl TreeNode { + fn new(id: u64, node_type: TreeNodeType) -> Self { + TreeNode { + id: ElementId(id as usize), + parent: None, + node_type, + down_state: DS::default(), + up_state: US::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) { + match &mut self.node_type { + TreeNodeType::Element { children, .. } => { + children.push(child); + } + _ => (), + } + } + + fn remove_child(&mut self, child: ElementId) { + match &mut self.node_type { + TreeNodeType::Element { children, .. } => { + children.retain(|c| c != &child); + } + _ => (), + } + } + + fn set_parent(&mut self, parent: ElementId) { + self.parent = Some(parent); + } +} + +/// This state that is passed down to children. For example text properties (`` `` ``) 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 PushedDownState: Default + PartialEq + Clone { + /// The context is passed to the [PushedDownState::reduce] when it is pushed down. + /// This is sometimes nessisary for lifetime purposes. + type Ctx; + fn reduce(&mut self, parent: Option<&Self>, vnode: &VNode, ctx: &mut Self::Ctx); +} +impl PushedDownState for () { + type Ctx = (); + fn reduce(&mut self, _parent: Option<&Self>, _vnode: &VNode, _ctx: &mut Self::Ctx) {} +} + +/// This state is derived from children. For example a non-flexbox div'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 BubbledUpState: Default + PartialEq + Clone { + /// The context is passed to the [BubbledUpState::reduce] when it is bubbled up. + /// This is sometimes nessisary for lifetime purposes. + type Ctx; + fn reduce<'a, I>(&mut self, children: I, vnode: &VNode, ctx: &mut Self::Ctx) + where + I: Iterator, + Self: 'a; +} +impl BubbledUpState for () { + type Ctx = (); + fn reduce<'a, I>(&mut self, _children: I, _vnode: &VNode, _ctx: &mut Self::Ctx) + where + I: Iterator, + Self: 'a, + { + } +} diff --git a/packages/native-core/src/layout.rs b/packages/native-core/src/layout.rs index 9e6dd90ea..5bfdb238b 100644 --- a/packages/native-core/src/layout.rs +++ b/packages/native-core/src/layout.rs @@ -1,4 +1,4 @@ -use crate::BubbledUpState; +use crate::client_tree::BubbledUpState; use dioxus_core::*; use stretch2::prelude::*; @@ -58,7 +58,7 @@ impl BubbledUpState for StretchLayout { apply_layout_attributes(name, value, &mut style); } - // todo: move + // the root node fills the entire area if el.id.get() == Some(ElementId(0)) { apply_layout_attributes("width", "100%", &mut style); apply_layout_attributes("height", "100%", &mut style); @@ -69,7 +69,6 @@ impl BubbledUpState for StretchLayout { for l in children { child_layout.push(l.node.unwrap()); } - child_layout.reverse(); if let Some(n) = self.node { if &stretch.children(n).unwrap() != &child_layout { diff --git a/packages/native-core/src/lib.rs b/packages/native-core/src/lib.rs index 67e888500..1dc8ad1c7 100644 --- a/packages/native-core/src/lib.rs +++ b/packages/native-core/src/lib.rs @@ -1,527 +1,3 @@ -use std::collections::{HashMap, HashSet, VecDeque}; - -use dioxus_core::{ElementId, Mutations, VNode, VirtualDom}; +pub mod client_tree; pub mod layout; pub mod layout_attributes; - -/// A tree that can sync with dioxus mutations backed by a hashmap. -/// Intended for use in lazy native renderers with a state that passes from parrent to children and or accumulates state from children to parrents. -/// To get started implement [PushedDownState] and or [BubbledUpState] and call [Tree::apply_mutations] and [Tree::update_state]. -#[derive(Debug)] - -pub struct Tree { - pub root: usize, - pub nodes: Vec>>, - pub nodes_listening: HashMap<&'static str, HashSet>, -} - -impl Tree { - pub fn new() -> Tree { - Tree { - root: 0, - nodes: { - let mut v = Vec::new(); - v.push(Some(TreeNode::new( - 0, - TreeNodeType::Element { - tag: "Root".to_string(), - namespace: Some("Root"), - children: Vec::new(), - }, - ))); - v - }, - nodes_listening: HashMap::new(), - } - } - - /// Updates the tree, up and down state and return a set of nodes that were updated - pub fn apply_mutations(&mut self, mutations_vec: Vec) -> Vec { - let mut nodes_updated = Vec::new(); - for mutations in mutations_vec { - let mut node_stack: smallvec::SmallVec<[usize; 5]> = smallvec::SmallVec::new(); - for e in mutations.edits { - use dioxus_core::DomEdit::*; - match e { - PushRoot { root } => node_stack.push(root as usize), - AppendChildren { many } => { - let target = if node_stack.len() >= many as usize + 1 { - *node_stack - .get(node_stack.len() - (many as usize + 1)) - .unwrap() - } else { - 0 - }; - for ns in node_stack.drain(node_stack.len() - many as usize..).rev() { - self.link_child(ns, target).unwrap(); - nodes_updated.push(ns); - } - } - ReplaceWith { root, m } => { - let root = self.remove(root as usize).unwrap(); - let target = root.parent.unwrap().0; - for ns in node_stack.drain(0..m as usize) { - nodes_updated.push(ns); - self.link_child(ns, target).unwrap(); - } - } - InsertAfter { root, n } => { - let target = self.get(root as usize).parent.unwrap().0; - for ns in node_stack.drain(0..n as usize) { - nodes_updated.push(ns); - self.link_child(ns, target).unwrap(); - } - } - InsertBefore { root, n } => { - let target = self.get(root as usize).parent.unwrap().0; - for ns in node_stack.drain(0..n as usize) { - nodes_updated.push(ns); - self.link_child(ns, target).unwrap(); - } - } - Remove { root } => { - if let Some(parent) = self.get(root as usize).parent { - nodes_updated.push(parent.0); - } - self.remove(root as usize).unwrap(); - } - CreateTextNode { root, text } => { - let n = TreeNode::new( - root, - TreeNodeType::Text { - text: text.to_string(), - }, - ); - self.insert(n); - node_stack.push(root as usize) - } - CreateElement { root, tag } => { - let n = TreeNode::new( - root, - TreeNodeType::Element { - tag: tag.to_string(), - namespace: None, - children: Vec::new(), - }, - ); - self.insert(n); - node_stack.push(root as usize) - } - CreateElementNs { root, tag, ns } => { - let n = TreeNode::new( - root, - TreeNodeType::Element { - tag: tag.to_string(), - namespace: Some(ns), - children: Vec::new(), - }, - ); - self.insert(n); - node_stack.push(root as usize) - } - CreatePlaceholder { root } => { - let n = TreeNode::new(root, TreeNodeType::Placeholder); - self.insert(n); - node_stack.push(root as usize) - } - - NewEventListener { - event_name, - scope: _, - root, - } => { - if let Some(v) = self.nodes_listening.get_mut(event_name) { - v.insert(root as usize); - } else { - let mut hs = HashSet::new(); - hs.insert(root as usize); - self.nodes_listening.insert(event_name, hs); - } - } - RemoveEventListener { root, event } => { - let v = self.nodes_listening.get_mut(event).unwrap(); - v.remove(&(root as usize)); - } - SetText { - root, - text: new_text, - } => { - let target = self.get_mut(root as usize); - nodes_updated.push(root as usize); - match &mut target.node_type { - TreeNodeType::Text { text } => { - *text = new_text.to_string(); - } - _ => unreachable!(), - } - } - SetAttribute { root, .. } => { - nodes_updated.push(root as usize); - } - RemoveAttribute { root, .. } => { - nodes_updated.push(root as usize); - } - } - } - } - - nodes_updated - } - - pub fn update_state( - &mut self, - vdom: &VirtualDom, - nodes_updated: Vec, - us_ctx: &mut US::Ctx, - ds_ctx: &mut DS::Ctx, - ) -> Option> { - let mut to_rerender = HashSet::new(); - let mut nodes_updated: Vec<_> = nodes_updated - .into_iter() - .map(|id| (id, self.get(id).height)) - .collect(); - nodes_updated.dedup(); - nodes_updated.sort_by_key(|(_, h)| *h); - - // bubble up state. To avoid calling reduce more times than nessisary start from the bottom and go up. - // todo: this is called multable times per element? - let mut to_bubble: VecDeque<_> = nodes_updated.clone().into(); - while let Some((id, height)) = to_bubble.pop_back() { - let node = self.get_mut(id as usize); - let vnode = node.element(vdom); - let node_type = &node.node_type; - let up_state = &mut node.up_state; - let children = match node_type { - TreeNodeType::Element { children, .. } => Some(children), - _ => None, - }; - // todo: reduce cloning state - let old = up_state.clone(); - let mut new = up_state.clone(); - let parent = node.parent.clone(); - new.reduce( - children - .unwrap_or(&Vec::new()) - .clone() - .iter() - .map(|c| &self.get(c.0).up_state), - vnode, - us_ctx, - ); - if new != old { - to_rerender.insert(id); - if let Some(p) = parent { - let i = to_bubble.partition_point(|(_, h)| *h < height - 1); - // make sure the parent is not already queued - if i >= to_bubble.len() || to_bubble.get(i).unwrap().0 != p.0 { - to_bubble.insert(i, (p.0, height - 1)); - } - } - let node = self.get_mut(id as usize); - node.up_state = new; - } - } - - // push down state. To avoid calling reduce more times than nessisary start from the top and go down. - let mut to_push: VecDeque<_> = nodes_updated.clone().into(); - while let Some((id, height)) = to_push.pop_front() { - let node = self.get_mut(id as usize); - // todo: reduce cloning state - let old = node.down_state.clone(); - let mut new = node.down_state.clone(); - let vnode = node.element(vdom); - new.reduce( - node.parent.map(|e| &self.get(e.0).down_state), - vnode, - ds_ctx, - ); - if new != old { - to_rerender.insert(id); - let node = self.get_mut(id as usize); - match &node.node_type { - TreeNodeType::Element { children, .. } => { - for c in children { - let i = to_bubble.partition_point(|(_, h)| *h < height + 1); - to_bubble.insert(i, (c.0, height + 1)); - } - } - _ => (), - }; - node.down_state = new; - } - } - - Some(to_rerender) - } - - fn link_child(&mut self, child_id: usize, parent_id: usize) -> Option<()> { - debug_assert_ne!(child_id, parent_id); - let parent = self.get_mut(parent_id); - parent.add_child(ElementId(child_id)); - let parent_height = parent.height + 1; - self.get_mut(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 = self.get_mut(id); - n.height += amount; - match &n.node_type { - TreeNodeType::Element { children, .. } => { - for c in children.clone() { - self.increase_height(c.0, amount); - } - } - _ => (), - } - } - - fn remove(&mut self, id: usize) -> Option> { - let mut node = self.nodes.get_mut(id as usize).unwrap().take().unwrap(); - match &mut node.node_type { - TreeNodeType::Element { children, .. } => { - for c in children { - self.remove(c.0).unwrap(); - } - } - _ => (), - } - Some(node) - } - - fn insert(&mut self, node: TreeNode) { - 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) -> &TreeNode { - self.nodes.get(id).unwrap().as_ref().unwrap() - } - - fn get_mut(&mut self, id: usize) -> &mut TreeNode { - self.nodes.get_mut(id).unwrap().as_mut().unwrap() - } - - pub fn get_listening_sorted(&self, event: &'static str) -> Vec<&TreeNode> { - if let Some(nodes) = self.nodes_listening.get(event) { - let mut listening: Vec<_> = nodes.iter().map(|id| self.get(*id)).collect(); - listening.sort_by(|n1, n2| (n1.height).cmp(&n2.height).reverse()); - listening - } else { - Vec::new() - } - } -} - -/// The node is stored client side and stores render data -#[derive(Debug, Clone)] -pub struct TreeNode { - pub id: ElementId, - pub parent: Option, - pub up_state: US, - pub down_state: DS, - pub node_type: TreeNodeType, - pub height: u16, -} - -#[derive(Debug, Clone)] -pub enum TreeNodeType { - Text { - text: String, - }, - Element { - tag: String, - namespace: Option<&'static str>, - children: Vec, - }, - Placeholder, -} - -impl TreeNode { - fn new(id: u64, node_type: TreeNodeType) -> Self { - TreeNode { - id: ElementId(id as usize), - parent: None, - node_type, - down_state: DS::default(), - up_state: US::default(), - height: 0, - } - } - - fn element<'b>(&self, vdom: &'b VirtualDom) -> &'b VNode<'b> { - vdom.get_element(self.id).unwrap() - } - - fn add_child(&mut self, child: ElementId) { - match &mut self.node_type { - TreeNodeType::Element { children, .. } => { - children.push(child); - } - _ => (), - } - } - - fn set_parent(&mut self, parent: ElementId) { - self.parent = Some(parent); - } -} - -/// This state that is passed down to children. For example text properties (`` `` ``) 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 PushedDownState: Default + PartialEq + Clone { - type Ctx; - fn reduce(&mut self, parent: Option<&Self>, vnode: &VNode, ctx: &mut Self::Ctx); -} -impl PushedDownState for () { - type Ctx = (); - fn reduce(&mut self, _parent: Option<&Self>, _vnode: &VNode, _ctx: &mut Self::Ctx) {} -} - -/// This state is derived from children. For example a non-flexbox div'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 BubbledUpState: Default + PartialEq + Clone { - type Ctx; - fn reduce<'a, I>(&mut self, children: I, vnode: &VNode, ctx: &mut Self::Ctx) - where - I: Iterator, - Self: 'a; -} -impl BubbledUpState for () { - type Ctx = (); - fn reduce<'a, I>(&mut self, _children: I, _vnode: &VNode, _ctx: &mut Self::Ctx) - where - I: Iterator, - Self: 'a, - { - } -} - -// /// The nodes that need to be updated after updating a state. -// pub struct Update { -// children: bool, -// parent: bool, -// } - -// /// This state is derived from children and parents. -// /// Called when the current node's node properties are modified or a parent or child's [State] is modified. -// /// Unlike [BubbledUpState] and [PushedDownState] this may be called mulable times per update. Prefer those over this. -// pub trait State: Default + PartialEq + Clone { -// fn reduce<'a, I>(&mut self, parent: Option<&Self>, children: I, vnode: &VNode) -> Update -// where -// I: Iterator, -// Self: 'a; -// } -// impl State for () { -// fn reduce<'a, I>(&mut self, _parent: Option<&Self>, _children: I, _vnode: &VNode) -> Update -// where -// I: Iterator, -// Self: 'a, -// { -// Update { -// children: false, -// parent: false, -// } -// } -// } - -#[test] -fn test_insert() { - use dioxus_core::*; - use dioxus_core_macro::*; - use dioxus_html as dioxus_elements; - - #[derive(Debug, Default, PartialEq, Clone)] - struct Rect { - x: u16, - y: u16, - width: u16, - height: u16, - } - - impl BubbledUpState for Rect { - type Ctx = (); - - fn reduce<'a, I>(&mut self, children: I, vnode: &VNode, _ctx: &mut Self::Ctx) - where - I: Iterator, - Self: 'a, - { - match vnode { - VNode::Text(t) => { - *self = Rect { - x: 0, - y: 0, - width: t.text.len().try_into().unwrap(), - height: 1, - }; - return; - } - _ => (), - } - self.width = 2; - self.height = 2; - for c in children { - println!("\t{c:?}"); - self.width = self.width.max(c.width); - self.height += c.height; - } - } - } - - #[allow(non_snake_case)] - fn Base(cx: Scope) -> Element { - rsx!(cx, div {}) - } - - let vdom = VirtualDom::new(Base); - let node_1 = rsx! { - div{ - div{ - "hello" - "hello world" - } - } - }; - let node_2 = rsx! { - div{ - div{ - "hello" - "hello world" - } - } - }; - let mutations = vdom.diff_lazynodes(node_1, node_2); - - let mut tree: Tree = Tree { - root: 0, - nodes: { - let mut v = Vec::new(); - v.push(Some(TreeNode::new( - 0, - TreeNodeType::Element { - tag: "Root".to_string(), - namespace: Some("Root"), - children: Vec::new(), - }, - ))); - v - }, - nodes_listening: HashMap::new(), - }; - println!("{:?}", mutations); - let to_update = tree.apply_mutations(vec![mutations.0]); - let to_rerender = tree - .update_state(&vdom, to_update, &mut (), &mut ()) - .unwrap(); - println!("{to_rerender:?}"); - panic!("{}", format!("{:?}", &tree.nodes[1..]).replace("\\", "")); -} diff --git a/packages/native-core/tests/change_nodes.rs b/packages/native-core/tests/change_nodes.rs new file mode 100644 index 000000000..bb15fd47d --- /dev/null +++ b/packages/native-core/tests/change_nodes.rs @@ -0,0 +1,153 @@ +use dioxus_core::VNode; +use dioxus_core::*; +use dioxus_core_macro::*; +use dioxus_html as dioxus_elements; +use dioxus_native_core::client_tree::ClientTree; +use std::cell::Cell; + +#[test] +fn tree_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 tree: ClientTree<(), ()> = ClientTree::new(); + + let _to_update = tree.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!(tree.size(), 2); + assert!(&tree.contains_node(&VNode::Element(&root_div))); + assert_eq!(tree[1].height, 1); + assert_eq!(tree[2].height, 2); + + let vdom = VirtualDom::new(Base); + let mutations = vdom.diff_lazynodes( + rsx! { + div{ + div{} + } + }, + rsx! { + div{} + }, + ); + tree.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!(tree.size(), 1); + assert!(&tree.contains_node(&VNode::Element(&new_root_div))); + assert_eq!(tree[1].height, 1); +} + +#[test] +fn tree_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 tree: ClientTree<(), ()> = ClientTree::new(); + + let _to_update = tree.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!(tree.size(), 1); + assert!(&tree.contains_node(&VNode::Element(&root_div))); + assert_eq!(tree[1].height, 1); + + let vdom = VirtualDom::new(Base); + let mutations = vdom.diff_lazynodes( + rsx! { + div{} + }, + rsx! { + div{ + p{} + } + }, + ); + tree.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!(tree.size(), 2); + assert!(&tree.contains_node(&VNode::Element(&new_root_div))); + assert_eq!(tree[1].height, 1); + assert_eq!(tree[2].height, 2); +} diff --git a/packages/native-core/tests/initial_build.rs b/packages/native-core/tests/initial_build.rs new file mode 100644 index 000000000..74bc809c7 --- /dev/null +++ b/packages/native-core/tests/initial_build.rs @@ -0,0 +1,124 @@ +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::client_tree::ClientTree; + +#[test] +fn tree_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 tree: ClientTree<(), ()> = ClientTree::new(); + + let _to_update = tree.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!(tree.size(), 1); + assert!(&tree.contains_node(&VNode::Element(&root_div))); + assert_eq!(tree[1].height, 1); +} + +#[test] +fn tree_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 tree: ClientTree<(), ()> = ClientTree::new(); + + let _to_update = tree.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!(tree.size(), 6); + assert!(&tree.contains_node(&VNode::Element(&root_div))); + assert_eq!(tree[1].height, 1); + assert_eq!(tree[2].height, 2); + assert_eq!(tree[3].height, 3); + assert_eq!(tree[4].height, 3); + assert_eq!(tree[5].height, 4); + assert_eq!(tree[6].height, 3); +} diff --git a/packages/native-core/tests/state.rs b/packages/native-core/tests/state.rs new file mode 100644 index 000000000..e738dc70d --- /dev/null +++ b/packages/native-core/tests/state.rs @@ -0,0 +1,320 @@ +use dioxus_core::VNode; +use dioxus_core::*; +use dioxus_core_macro::*; +use dioxus_html as dioxus_elements; +use dioxus_native_core::client_tree::*; + +#[derive(Debug, Clone, PartialEq, Default)] +struct CallCounter(u32); +impl BubbledUpState for CallCounter { + type Ctx = (); + + fn reduce<'a, I>(&mut self, _children: I, _vnode: &VNode, _ctx: &mut Self::Ctx) + where + I: Iterator, + Self: 'a, + { + self.0 += 1; + } +} + +impl PushedDownState for CallCounter { + type Ctx = (); + + fn reduce(&mut self, _parent: Option<&Self>, _vnode: &VNode, _ctx: &mut Self::Ctx) { + self.0 += 1; + } +} + +#[derive(Debug, Clone, PartialEq, Default)] +struct BubbledUpStateTester(String, Vec>); +impl BubbledUpState for BubbledUpStateTester { + type Ctx = u32; + + fn reduce<'a, I>(&mut self, children: I, vnode: &VNode, ctx: &mut Self::Ctx) + where + I: Iterator, + Self: 'a, + { + assert_eq!(*ctx, 42); + *self = BubbledUpStateTester( + vnode.mounted_id().to_string(), + children.map(|c| Box::new(c.clone())).collect(), + ); + } +} + +#[derive(Debug, Clone, PartialEq, Default)] +struct PushedDownStateTester(String, Option>); +impl PushedDownState for PushedDownStateTester { + type Ctx = u32; + + fn reduce(&mut self, parent: Option<&Self>, vnode: &VNode, ctx: &mut Self::Ctx) { + assert_eq!(*ctx, 42); + *self = PushedDownStateTester( + vnode.mounted_id().to_string(), + parent.map(|c| Box::new(c.clone())), + ); + } +} + +#[test] +fn tree_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{} + h1{} + } + }); + + let mut tree: ClientTree = ClientTree::new(); + + let nodes_updated = tree.apply_mutations(vec![mutations]); + let _to_rerender = tree.update_state(&vdom, nodes_updated, &mut 42, &mut 42); + + let root_div = &tree[1]; + assert_eq!(root_div.up_state.0, "1"); + assert_eq!( + root_div.up_state.1, + vec![ + Box::new(BubbledUpStateTester("2".to_string(), Vec::new())), + Box::new(BubbledUpStateTester("3".to_string(), Vec::new())) + ] + ); + assert_eq!(root_div.down_state.0, "1"); + assert_eq!(root_div.down_state.1, None); + + let child_p = &tree[2]; + assert_eq!(child_p.up_state.0, "2"); + assert_eq!(child_p.up_state.1, Vec::new()); + assert_eq!(child_p.down_state.0, "2"); + assert_eq!( + child_p.down_state.1, + Some(Box::new(PushedDownStateTester("1".to_string(), None))) + ); + + let child_h1 = &tree[3]; + assert_eq!(child_h1.up_state.0, "3"); + assert_eq!(child_h1.up_state.1, Vec::new()); + assert_eq!(child_h1.down_state.0, "3"); + assert_eq!( + child_h1.down_state.1, + Some(Box::new(PushedDownStateTester("1".to_string(), None))) + ); +} + +#[test] +fn tree_state_reduce_initally_called_minimally() { + #[derive(Debug, Clone, PartialEq, Default)] + struct CallCounter(u32); + impl BubbledUpState for CallCounter { + type Ctx = (); + + fn reduce<'a, I>(&mut self, _children: I, _vnode: &VNode, _ctx: &mut Self::Ctx) + where + I: Iterator, + Self: 'a, + { + self.0 += 1; + } + } + + impl PushedDownState for CallCounter { + type Ctx = (); + + fn reduce(&mut self, _parent: Option<&Self>, _vnode: &VNode, _ctx: &mut Self::Ctx) { + self.0 += 1; + } + } + + #[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 tree: ClientTree = ClientTree::new(); + + let nodes_updated = tree.apply_mutations(vec![mutations]); + let _to_rerender = tree.update_state(&vdom, nodes_updated, &mut (), &mut ()); + + tree.traverse(|n| { + assert_eq!(n.up_state.0, 1); + assert_eq!(n.down_state.0, 1); + }); +} + +#[test] +fn tree_state_reduce_down_called_minimally_on_update() { + #[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 { + width: "100%", + div{ + div{ + p{} + } + p{ + "hello" + } + div{ + h1{} + } + p{ + "world" + } + } + } + }); + + let mut tree: ClientTree = ClientTree::new(); + + let nodes_updated = tree.apply_mutations(vec![mutations]); + let _to_rerender = tree.update_state(&vdom, nodes_updated, &mut (), &mut ()); + let nodes_updated = tree.apply_mutations(vec![Mutations { + edits: vec![DomEdit::SetAttribute { + root: 1, + field: "width", + value: "99%", + ns: Some("style"), + }], + dirty_scopes: fxhash::FxHashSet::default(), + refs: Vec::new(), + }]); + let _to_rerender = tree.update_state(&vdom, nodes_updated, &mut (), &mut ()); + + tree.traverse(|n| { + assert_eq!(n.down_state.0, 2); + }); +} + +#[test] +fn tree_state_reduce_up_called_minimally_on_update() { + #[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 { + width: "100%", + div{ + div{ + p{} + } + p{ + "hello" + } + div{ + h1{} + } + p{ + "world" + } + } + } + }); + + let mut tree: ClientTree = ClientTree::new(); + + let nodes_updated = tree.apply_mutations(vec![mutations]); + let _to_rerender = tree.update_state(&vdom, nodes_updated, &mut (), &mut ()); + let nodes_updated = tree.apply_mutations(vec![Mutations { + edits: vec![DomEdit::SetAttribute { + root: 4, + field: "width", + value: "99%", + ns: Some("style"), + }], + dirty_scopes: fxhash::FxHashSet::default(), + refs: Vec::new(), + }]); + let _to_rerender = tree.update_state(&vdom, nodes_updated, &mut (), &mut ()); + + tree.traverse(|n| { + assert_eq!(n.up_state.0, if n.id.0 > 4 { 1 } else { 2 }); + }); +} diff --git a/packages/tui/Cargo.toml b/packages/tui/Cargo.toml index fcbfa053b..92c915e76 100644 --- a/packages/tui/Cargo.toml +++ b/packages/tui/Cargo.toml @@ -22,5 +22,7 @@ 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.1" +stretch2 = { git = "https://github.com/Demonthos/stretch.git" } smallvec = "1.6" +fxhash = "0.2" diff --git a/packages/tui/src/hooks.rs b/packages/tui/src/hooks.rs index 2184dcd06..866d1fc85 100644 --- a/packages/tui/src/hooks.rs +++ b/packages/tui/src/hooks.rs @@ -2,14 +2,17 @@ use crossterm::event::{ Event as TermEvent, KeyCode as TermKeyCode, KeyModifiers, MouseButton, MouseEventKind, }; use dioxus_core::*; +use fxhash::{FxHashMap, FxHashSet}; use dioxus_html::{on::*, KeyCode}; -use dioxus_native_core::{layout::StretchLayout, Tree, TreeNode}; +use dioxus_native_core::{ + client_tree::{ClientTree, TreeNode}, + layout::StretchLayout, +}; use futures::{channel::mpsc::UnboundedReceiver, StreamExt}; use std::{ any::Any, cell::RefCell, - collections::{HashMap, HashSet}, rc::Rc, sync::Arc, time::{Duration, Instant}, @@ -166,7 +169,7 @@ impl InnerInputState { evts: &mut Vec, resolved_events: &mut Vec, layout: &Stretch, - tree: &mut Tree, + tree: &mut ClientTree, ) { let previous_mouse = self .mouse @@ -191,7 +194,7 @@ impl InnerInputState { previous_mouse: Option<(MouseData, Vec)>, resolved_events: &mut Vec, layout: &Stretch, - tree: &mut Tree, + tree: &mut ClientTree, ) { struct Data<'b> { new_pos: (i32, i32), @@ -213,17 +216,17 @@ impl InnerInputState { fn try_create_event( name: &'static str, data: Arc, - will_bubble: &mut HashSet, + will_bubble: &mut FxHashSet, resolved_events: &mut Vec, node: &TreeNode, - tree: &Tree, + tree: &ClientTree, ) { // 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 = tree.get(parent_id.0).parent; + parent = tree[parent_id.0].parent; } resolved_events.push(UserEvent { scope_id: None, @@ -259,7 +262,7 @@ impl InnerInputState { { // mousemove - let mut will_bubble = HashSet::new(); + let mut will_bubble = FxHashSet::default(); for node in tree.get_listening_sorted("mousemove") { let node_layout = layout.layout(node.up_state.node.unwrap()).unwrap(); let previously_contained = data @@ -285,7 +288,7 @@ impl InnerInputState { { // mouseenter - let mut will_bubble = HashSet::new(); + let mut will_bubble = FxHashSet::default(); for node in tree.get_listening_sorted("mouseenter") { let node_layout = layout.layout(node.up_state.node.unwrap()).unwrap(); let previously_contained = data @@ -311,7 +314,7 @@ impl InnerInputState { { // mouseover - let mut will_bubble = HashSet::new(); + let mut will_bubble = FxHashSet::default(); for node in tree.get_listening_sorted("mouseover") { let node_layout = layout.layout(node.up_state.node.unwrap()).unwrap(); let previously_contained = data @@ -337,7 +340,7 @@ impl InnerInputState { { // mousedown - let mut will_bubble = HashSet::new(); + let mut will_bubble = FxHashSet::default(); for node in tree.get_listening_sorted("mousedown") { let node_layout = layout.layout(node.up_state.node.unwrap()).unwrap(); let currently_contains = layout_contains_point(node_layout, data.new_pos); @@ -359,7 +362,7 @@ impl InnerInputState { { // mouseup - let mut will_bubble = HashSet::new(); + let mut will_bubble = FxHashSet::default(); for node in tree.get_listening_sorted("mouseup") { let node_layout = layout.layout(node.up_state.node.unwrap()).unwrap(); let currently_contains = layout_contains_point(node_layout, data.new_pos); @@ -381,7 +384,7 @@ impl InnerInputState { { // click - let mut will_bubble = HashSet::new(); + let mut will_bubble = FxHashSet::default(); for node in tree.get_listening_sorted("click") { let node_layout = layout.layout(node.up_state.node.unwrap()).unwrap(); let currently_contains = layout_contains_point(node_layout, data.new_pos); @@ -403,7 +406,7 @@ impl InnerInputState { { // contextmenu - let mut will_bubble = HashSet::new(); + let mut will_bubble = FxHashSet::default(); for node in tree.get_listening_sorted("contextmenu") { let node_layout = layout.layout(node.up_state.node.unwrap()).unwrap(); let currently_contains = layout_contains_point(node_layout, data.new_pos); @@ -425,7 +428,7 @@ impl InnerInputState { { // wheel - let mut will_bubble = HashSet::new(); + let mut will_bubble = FxHashSet::default(); for node in tree.get_listening_sorted("wheel") { let node_layout = layout.layout(node.up_state.node.unwrap()).unwrap(); let currently_contains = layout_contains_point(node_layout, data.new_pos); @@ -449,7 +452,7 @@ impl InnerInputState { { // mouseleave - let mut will_bubble = HashSet::new(); + let mut will_bubble = FxHashSet::default(); for node in tree.get_listening_sorted("mouseleave") { let node_layout = layout.layout(node.up_state.node.unwrap()).unwrap(); let previously_contained = data @@ -473,7 +476,7 @@ impl InnerInputState { { // mouseout - let mut will_bubble = HashSet::new(); + let mut will_bubble = FxHashSet::default(); for node in tree.get_listening_sorted("mouseout") { let node_layout = layout.layout(node.up_state.node.unwrap()).unwrap(); let previously_contained = data @@ -543,7 +546,7 @@ impl RinkInputHandler { pub fn get_events<'a>( &self, layout: &Stretch, - tree: &mut Tree, + tree: &mut ClientTree, ) -> Vec { let mut resolved_events = Vec::new(); @@ -578,7 +581,7 @@ impl RinkInputHandler { .map(|evt| (evt.0, evt.1.into_any())); // todo: currently resolves events in all nodes, but once the focus system is added it should filter by focus - let mut hm: HashMap<&'static str, Vec>> = HashMap::new(); + let mut hm: FxHashMap<&'static str, Vec>> = FxHashMap::default(); for (event, data) in events { if let Some(v) = hm.get_mut(event) { v.push(data); diff --git a/packages/tui/src/lib.rs b/packages/tui/src/lib.rs index f3828aac1..0fda78071 100644 --- a/packages/tui/src/lib.rs +++ b/packages/tui/src/lib.rs @@ -9,10 +9,8 @@ use crossterm::{ }; use dioxus_core::exports::futures_channel::mpsc::unbounded; use dioxus_core::*; -use dioxus_html::on::{KeyboardData, MouseData, PointerData, TouchData, WheelData}; -use dioxus_native_core::{layout::StretchLayout, Tree}; +use dioxus_native_core::{client_tree::ClientTree, layout::StretchLayout}; use futures::{channel::mpsc::UnboundedSender, pin_mut, StreamExt}; -use std::collections::HashSet; use std::{io, time::Duration}; use stretch2::{prelude::Size, Stretch}; use style_attributes::StyleModifier; @@ -44,7 +42,7 @@ pub fn launch_cfg(app: Component<()>, cfg: Config) { cx.provide_root_context(state); - let mut tree: Tree = Tree::new(); + let mut tree: ClientTree = ClientTree::new(); let mutations = dom.rebuild(); let to_update = tree.apply_mutations(vec![mutations]); let mut stretch = Stretch::new(); @@ -60,7 +58,7 @@ pub fn render_vdom( ctx: UnboundedSender, handler: RinkInputHandler, cfg: Config, - mut tree: Tree, + mut tree: ClientTree, mut stretch: Stretch, ) -> Result<()> { // Setup input handling @@ -100,12 +98,8 @@ pub fn render_vdom( let mut terminal = Terminal::new(backend).unwrap(); terminal.clear().unwrap(); - let mut to_rerender: HashSet = tree - .nodes - .iter() - .filter_map(|n| n.as_ref()) - .map(|n| n.id.0) - .collect(); + let mut to_rerender: fxhash::FxHashSet = vec![0].into_iter().collect(); + let mut redraw = true; loop { /* @@ -120,15 +114,16 @@ pub fn render_vdom( todo: lazy re-rendering */ - if !to_rerender.is_empty() { + if !to_rerender.is_empty() || redraw { + redraw = false; terminal.draw(|frame| { // size is guaranteed to not change when rendering let dims = frame.size(); // println!("{dims:?}"); let width = dims.width; let height = dims.height; - let root_id = tree.root; - let root_node = tree.get(root_id).up_state.node.unwrap(); + let root_id = tree.root_id(); + let root_node = tree[root_id].up_state.node.unwrap(); stretch .compute_layout( root_node, @@ -138,7 +133,7 @@ pub fn render_vdom( }, ) .unwrap(); - let root = tree.get(tree.root); + let root = &tree[tree.root_id()]; render::render_vnode(frame, &stretch, &tree, &root, cfg); })?; } @@ -146,22 +141,6 @@ pub fn render_vdom( // resolve events before rendering // todo: events do not trigger update? for e in handler.get_events(&stretch, &mut tree) { - // let tname = if e.data.is::() { - // "PointerData" - // } else if e.data.is::() { - // "WheelData" - // } else if e.data.is::() { - // "MouseData" - // } else if e.data.is::() { - // "KeyboardData" - // } else if e.data.is::() { - // "TouchData" - // } else if e.data.is::<(u16, u16)>() { - // "(u16, u16)" - // } else { - // panic!() - // }; - // println!("{tname}: {e:?}"); vdom.handle_message(SchedulerMsg::Event(e)); } @@ -184,7 +163,8 @@ pub fn render_vdom( break; } } - TermEvent::Resize(_, _) | TermEvent::Mouse(_) => {} + TermEvent::Resize(_, _) => redraw = true, + TermEvent::Mouse(_) => {} }, InputEvent::Tick => {} // tick InputEvent::Close => break, diff --git a/packages/tui/src/render.rs b/packages/tui/src/render.rs index 7be17ff82..fe21ba8ae 100644 --- a/packages/tui/src/render.rs +++ b/packages/tui/src/render.rs @@ -1,4 +1,8 @@ -use dioxus_native_core::{layout::StretchLayout, layout_attributes::UnitSystem, Tree, TreeNode}; +use dioxus_native_core::{ + client_tree::{ClientTree, TreeNode}, + layout::StretchLayout, + layout_attributes::UnitSystem, +}; use std::io::Stdout; use stretch2::{ geometry::Point, @@ -19,12 +23,14 @@ const RADIUS_MULTIPLIER: [f32; 2] = [1.0, 0.5]; pub fn render_vnode<'a>( frame: &mut tui::Frame>, layout: &Stretch, - tree: &Tree, + tree: &ClientTree, node: &TreeNode, cfg: Config, ) { + use dioxus_native_core::client_tree::TreeNodeType; + match &node.node_type { - dioxus_native_core::TreeNodeType::Placeholder => return, + TreeNodeType::Placeholder => return, _ => (), } @@ -34,7 +40,7 @@ pub fn render_vnode<'a>( let Size { width, height } = size; match &node.node_type { - dioxus_native_core::TreeNodeType::Text { text } => { + TreeNodeType::Text { text } => { #[derive(Default)] struct Label<'a> { text: &'a str, @@ -64,7 +70,7 @@ pub fn render_vnode<'a>( frame.render_widget(WidgetWithContext::new(label, cfg), area); } } - dioxus_native_core::TreeNodeType::Element { children, .. } => { + TreeNodeType::Element { children, .. } => { let area = Rect::new(*x as u16, *y as u16, *width as u16, *height as u16); // the renderer will panic if a node is rendered out of range even if the size is zero @@ -73,10 +79,10 @@ pub fn render_vnode<'a>( } for c in children { - render_vnode(frame, layout, tree, tree.get(c.0), cfg); + render_vnode(frame, layout, tree, &tree[c.0], cfg); } } - dioxus_native_core::TreeNodeType::Placeholder => unreachable!(), + TreeNodeType::Placeholder => unreachable!(), } } diff --git a/packages/tui/src/style_attributes.rs b/packages/tui/src/style_attributes.rs index 907a066de..90cebe47d 100644 --- a/packages/tui/src/style_attributes.rs +++ b/packages/tui/src/style_attributes.rs @@ -31,8 +31,8 @@ use dioxus_core::{Attribute, VNode}; use dioxus_native_core::{ + client_tree::PushedDownState, layout_attributes::{parse_value, UnitSystem}, - PushedDownState, }; use crate::style::{RinkColor, RinkStyle}; @@ -48,7 +48,9 @@ impl PushedDownState for StyleModifier { fn reduce(&mut self, parent: Option<&Self>, vnode: &VNode, _ctx: &mut Self::Ctx) { *self = StyleModifier::default(); - self.style.fg = None; + if parent.is_some() { + self.style.fg = None; + } match vnode { VNode::Element(el) => { // handle text modifier elements diff --git a/packages/tui/src/utils.rs b/packages/tui/src/utils.rs deleted file mode 100644 index f89e3180c..000000000 --- a/packages/tui/src/utils.rs +++ /dev/null @@ -1,83 +0,0 @@ -// use dioxus_core::{Element, ElementId, Mutations, VNode, VirtualDom, DomEdit}; - -// /// The focus system needs a iterator that can persist through changes in the [VirtualDom]. Iterate through it with [ElementIter::next], and update it with [ElementIter::update] (with data from [`VirtualDom::work_with_deadline`]). -// pub(crate) struct ElementIter { -// // stack of elements and fragments -// stack: smallvec::SmallVec<[(ElementId, usize); 5]>, -// } - -// impl ElementIter { -// pub(crate) fn new(initial: ElementId) -> Self { -// ElementIter { -// stack: smallvec::smallvec![(initial, 0)], -// } -// } -// /// remove stale element refreneces -// pub(crate) fn update(&mut self, mutations: &Mutations, vdom: &VirtualDom) { -// let ids_removed: Vec<_> = mutations.edits.iter().filter_map(|e| if let DomEdit::Remove{root: }).collect(); -// for node in self.stack { - -// match node.0 { -// VNode::Fragment(f) => { - -// } - -// VNode::Element(_) => {} - -// _ => unreachable!(), -// } -// } -// } -// pub(crate) fn next<'a>(&mut self, vdom: &'a VirtualDom) -> Option<&'a VNode<'a>> { -// let last = self.stack.last()?.0; -// let node = vdom.get_element(last)?; -// match node { -// VNode::Fragment(f) => { -// let mut last_mut = self.stack.last_mut()?; -// if last_mut.1 + 1 >= f.children.len() { -// self.stack.pop(); -// self.next(vdom) -// } else { -// last_mut.1 += 1; -// let new_node = &f.children[last_mut.1]; -// if matches!(new_node, VNode::Fragment(_) | VNode::Element(_)) { -// self.stack.push((new_node.mounted_id(), 0)); -// } -// self.next(vdom) -// } -// } - -// VNode::Component(vcomp) => { -// let idx = vcomp.scope.get().unwrap(); -// let new_node = vdom.get_scope(idx).unwrap().root_node(); -// *self.stack.last_mut()? = (new_node.mounted_id(), 0); -// self.next(vdom) -// } - -// VNode::Placeholder(_) | VNode::Text(_) => { -// self.stack.pop(); -// self.next(vdom) -// } - -// VNode::Element(e) => { -// let mut last_mut = self.stack.last_mut()?; -// if last_mut.1 + 1 >= e.children.len() { -// self.stack.pop(); -// self.next(vdom); -// } else { -// last_mut.1 += 1; -// let new_node = &e.children[last_mut.1]; -// if matches!(new_node, VNode::Fragment(_) | VNode::Element(_)) { -// self.stack.push((new_node.mounted_id(), 0)); -// } -// self.next(vdom); -// } -// Some(node) -// } -// } -// } - -// pub(crate) fn peak(&self) -> Option<&ElementId> { -// self.stack.last().map(|(e, c)| e) -// } -// } diff --git a/packages/tui/tests/margin.rs b/packages/tui/tests/margin.rs index 53e8978e5..44f909d69 100644 --- a/packages/tui/tests/margin.rs +++ b/packages/tui/tests/margin.rs @@ -90,4 +90,11 @@ fn margin_and_flex_row2() { assert_eq!(stretch.layout(node).unwrap().size.height, 100f32); assert_eq!(stretch.layout(node).unwrap().location.x, 0f32); assert_eq!(stretch.layout(node).unwrap().location.y, 0f32); + + dbg!(stretch.layout(node0)).unwrap(); + + // assert_eq!(stretch.layout(node0).unwrap().size.width, 80f32); + // assert_eq!(stretch.layout(node0).unwrap().size.height, 100f32); + // assert_eq!(stretch.layout(node0).unwrap().location.x, 10f32); + // assert_eq!(stretch.layout(node0).unwrap().location.y, 0f32); }