Renderers are now packages, not features. (#387)

* feat: use synchronous router design

* feat: function to get router out of dom

* chore: restructure workspace to use renderers as packages, not features
This commit is contained in:
Jon Kelley authored and GitHub committed 2022-07-09 15:15:20 -04:00
1 parent c97ca7dff6
commit d9546d9504
181 files changed
+487 -1463

No files matched your search

+1 -1
View File
@@ -6,10 +6,10 @@ edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
dioxus-rsx = { path = "../rsx" }
proc-macro2 = { version = "1.0.6", features = ["span-locations"] }
quote = "1.0"
syn = { version = "1.0.11", features = ["full", "extra-traits"] }
dioxus-rsx = { path = "../rsx" }
triple_accel = "0.4.0"
serde = { version = "1.0.136", features = ["derive"] }
prettyplease = { git = "https://github.com/DioxusLabs/prettyplease-macro-fmt.git", features = [
+2 -2
View File
@@ -143,8 +143,8 @@ impl Buffer {
let mut total = 0;
for attr in attributes {
if self.current_span_is_primary(attr.attr.start()) {
'line: for line in self.src[..attr.attr.start().start().line - 1].iter().rev() {
if self.current_span_is_primary(attr.attr.flart()) {
'line: for line in self.src[..attr.attr.flart().start().line - 1].iter().rev() {
match (line.trim().starts_with("//"), line.is_empty()) {
(true, _) => return 100000,
(_, true) => continue 'line,
+1 -1
View File
@@ -153,7 +153,7 @@ impl Buffer {
while let Some(attr) = attr_iter.next() {
self.indent += 1;
if !sameline {
self.write_comments(attr.attr.start())?;
self.write_comments(attr.attr.flart())?;
}
self.indent -= 1;
+2 -2
View File
@@ -29,7 +29,7 @@ pub fn derive_typed_builder(input: proc_macro::TokenStream) -> proc_macro::Token
/// The rsx! macro makes it easy for developers to write jsx-style markup in their components.
///
/// ## Complete Reference Guide:
/// ```
/// ```ignore
#[doc = include_str!("../../../examples/rsx_usage.rs")]
/// ```
#[proc_macro]
@@ -81,7 +81,7 @@ pub fn rsx(s: TokenStream) -> TokenStream {
/// you would be repeating a lot of the usual Rust boilerplate.
///
/// # Example
/// ```
/// ```ignore
/// #[inline_props]
/// fn app(cx: Scope, bob: String) -> Element {
/// cx.render(rsx!("hello, {bob}"))
+1 -1
View File
@@ -101,7 +101,7 @@ pub(crate) mod unsafe_utils {
/// # Usage
///
///
/// ```
/// ```ignore
/// let (data) = use_ref(&cx, || {});
///
/// let handle_thing = move |_| {
+2 -2
View File
@@ -24,7 +24,7 @@ pub fn use_window(cx: &ScopeState) -> &DesktopContext {
///
/// you can use `cx.consume_context::<DesktopContext>` to get this context
///
/// ```rust
/// ```rust, ignore
/// let desktop = cx.consume_context::<DesktopContext>().unwrap();
/// ```
#[derive(Clone)]
@@ -43,7 +43,7 @@ impl DesktopContext {
/// Moves the window with the left mouse button until the button is released.
///
/// you need use it in `onmousedown` event:
/// ```rust
/// ```rust, ignore
/// onmousedown: move |_| { desktop.drag_window(); }
/// ```
pub fn drag(&self) {
+1 -1
View File
@@ -4,7 +4,7 @@ use std::fmt::{self, Write};
///
/// # Example
///
/// ```rust,no_run
/// ```rust,ignore
/// # use web_view::WebView;
/// # use std::mem;
/// #
+6 -6
View File
@@ -33,11 +33,11 @@ use wry::webview::WebViewBuilder;
///
/// This function will start a multithreaded Tokio runtime as well the WebView event loop.
///
/// ```rust
/// ```rust, ignore
/// use dioxus::prelude::*;
///
/// fn main() {
/// dioxus::desktop::launch(app);
/// dioxus_desktop::launch(app);
/// }
///
/// fn app(cx: Scope) -> Element {
@@ -56,11 +56,11 @@ pub fn launch(root: Component) {
///
/// You can configure the WebView window with a configuration closure
///
/// ```rust
/// ```rust, ignore
/// use dioxus::prelude::*;
///
/// fn main() {
/// dioxus::desktop::launch_cfg(app, |c| c.with_window(|w| w.with_title("My App")));
/// dioxus_desktop::launch_cfg(app, |c| c.with_window(|w| w.with_title("My App")));
/// }
///
/// fn app(cx: Scope) -> Element {
@@ -82,11 +82,11 @@ pub fn launch_cfg(
///
/// You can configure the WebView window with a configuration closure
///
/// ```rust
/// ```rust, ignore
/// use dioxus::prelude::*;
///
/// fn main() {
/// dioxus::desktop::launch_cfg(app, AppProps { name: "asd" }, |c| c);
/// dioxus_desktop::launch_cfg(app, AppProps { name: "asd" }, |c| c);
/// }
///
/// struct AppProps {
+2 -2
View File
@@ -26,12 +26,12 @@ $ cargo add dioxus --features desktop
Edit your `main.rs`:
```rust
```rust, ignore
// main.rs
use dioxus::prelude::*;
fn main() {
dioxus::desktop::launch(app);
dioxus_desktop::launch(app);
}
fn app(cx: Scope) -> Element {
+51
View File
@@ -0,0 +1,51 @@
[package]
name = "dioxus"
version = "0.2.4"
authors = ["Jonathan Kelley"]
edition = "2021"
description = "Core functionality for Dioxus - a concurrent renderer-agnostic Virtual DOM for interactive user experiences"
license = "MIT OR Apache-2.0"
repository = "https://github.com/DioxusLabs/dioxus/"
homepage = "https://dioxuslabs.com"
documentation = "https://dioxuslabs.com"
keywords = ["dom", "ui", "gui", "react", "wasm"]
rust-version = "1.60.0"
[dependencies]
dioxus-core = { path = "../core", version = "^0.2.1" }
dioxus-html = { path = "../html", version = "^0.2.1", optional = true }
dioxus-core-macro = { path = "../core-macro", version = "^0.2.1", optional = true }
dioxus-hooks = { path = "../hooks", version = "^0.2.1", optional = true }
dioxus-rsx = { path = "../rsx", optional = true }
dioxus-rsx-interpreter = { path = "../rsx_interpreter", optional = true }
# dioxus-interpreter-js = { path = "./packages/interpreter", version = "^0.2.1", optional = true }
# dioxus-native-core = { path = "./packages/native-core", version = "^0.2.0", optional = true }
# dioxus-native-core-macro = { path = "./packages/native-core-macro", version = "^0.2.0", optional = true }
[features]
default = ["macro", "hooks", "html"]
macro = ["dioxus-core-macro", "dioxus-rsx"]
html = ["dioxus-html"]
hooks = ["dioxus-hooks"]
hot-reload = [
"dioxus-core-macro/hot-reload",
"dioxus-rsx-interpreter",
]
[dev-dependencies]
futures-util = "0.3.21"
log = "0.4.14"
rand = { version = "0.8.4", features = ["small_rng"] }
criterion = "0.3.5"
thiserror = "1.0.30"
env_logger = "0.9.0"
[[bench]]
name = "create"
harness = false
[[bench]]
name = "jsframework"
harness = false
+1
View File
@@ -0,0 +1 @@
fn main() {}
+120
View File
@@ -0,0 +1,120 @@
#![allow(non_snake_case, non_upper_case_globals)]
//! This benchmark tests just the overhead of Dioxus itself.
//!
//! For the JS Framework Benchmark, both the framework and the browser is benchmarked together. Dioxus prepares changes
//! to be made, but the change application phase will be just as performant as the vanilla wasm_bindgen code. In essence,
//! we are measuring the overhead of Dioxus, not the performance of the "apply" phase.
//!
//! On my MBP 2019:
//! - Dioxus takes 3ms to create 1_000 rows
//! - Dioxus takes 30ms to create 10_000 rows
//!
//! As pure "overhead", these are amazing good numbers, mostly slowed down by hitting the global allocator.
//! These numbers don't represent Dioxus with the heuristic engine installed, so I assume it'll be even faster.
use criterion::{criterion_group, criterion_main, Criterion};
use dioxus::prelude::*;
use rand::prelude::*;
criterion_group!(mbenches, create_rows);
criterion_main!(mbenches);
fn create_rows(c: &mut Criterion) {
static App: Component = |cx| {
let mut rng = SmallRng::from_entropy();
rsx!(cx, table {
tbody {
(0..10_000_usize).map(|f| {
let label = Label::new(&mut rng);
rsx!(Row {
row_id: f,
label: label
})
})
}
})
};
c.bench_function("create rows", |b| {
b.iter(|| {
let mut dom = VirtualDom::new(App);
let g = dom.rebuild();
assert!(g.edits.len() > 1);
})
});
}
#[derive(PartialEq, Props)]
struct RowProps {
row_id: usize,
label: Label,
}
fn Row(cx: Scope<RowProps>) -> Element {
let [adj, col, noun] = cx.props.label.0;
cx.render(rsx! {
tr {
td { class:"col-md-1", "{cx.props.row_id}" }
td { class:"col-md-1", onclick: move |_| { /* run onselect */ },
a { class: "lbl", "{adj}" "{col}" "{noun}" }
}
td { class: "col-md-1",
a { class: "remove", onclick: move |_| {/* remove */},
span { class: "glyphicon glyphicon-remove remove", aria_hidden: "true" }
}
}
td { class: "col-md-6" }
}
})
}
#[derive(PartialEq)]
struct Label([&'static str; 3]);
impl Label {
fn new(rng: &mut SmallRng) -> Self {
Label([
ADJECTIVES.choose(rng).unwrap(),
COLOURS.choose(rng).unwrap(),
NOUNS.choose(rng).unwrap(),
])
}
}
static ADJECTIVES: &[&str] = &[
"pretty",
"large",
"big",
"small",
"tall",
"short",
"long",
"handsome",
"plain",
"quaint",
"clean",
"elegant",
"easy",
"angry",
"crazy",
"helpful",
"mushy",
"odd",
"unsightly",
"adorable",
"important",
"inexpensive",
"cheap",
"expensive",
"fancy",
];
static COLOURS: &[&str] = &[
"red", "yellow", "blue", "green", "pink", "brown", "purple", "brown", "white", "black",
"orange",
];
static NOUNS: &[&str] = &[
"table", "chair", "house", "bbq", "desk", "car", "pony", "cookie", "sandwich", "burger",
"pizza", "mouse", "keyboard",
];
@@ -1,27 +1,39 @@
//! This package is meant for internal use within dioxus. It provides a prelude that enables basic components to work.
#![doc = include_str!("../../../notes/README.md")]
pub use dioxus_core as core;
pub mod hooks {
pub use dioxus_hooks::*;
}
pub use hooks::*;
#[cfg(feature = "hooks")]
pub use dioxus_hooks as hooks;
pub mod events {
#[cfg(feature = "html")]
pub use dioxus_html::{on::*, KeyCode};
}
#[cfg(feature = "hot-reload")]
pub use dioxus_rsx_interpreter as rsx_interpreter;
#[cfg(feature = "html")]
pub use dioxus_html as html;
#[cfg(feature = "macro")]
pub use dioxus_rsx as rsx;
#[cfg(feature = "macro")]
pub use dioxus_core_macro as core_macro;
pub mod prelude {
#[cfg(feature = "hooks")]
pub use crate::hooks::*;
pub use dioxus_core::prelude::*;
#[cfg(feature = "macro")]
pub use dioxus_core_macro::{format_args_f, inline_props, rsx, Props};
pub use dioxus_elements::{GlobalAttributes, SvgAttributes};
#[cfg(feature = "html")]
pub use dioxus_html as dioxus_elements;
#[cfg(feature = "html")]
pub use dioxus_elements::{GlobalAttributes, SvgAttributes};
#[cfg(feature = "hot-reload")]
pub use dioxus_rsx_interpreter::{
captuered_context::{CapturedContext, FormattedArg, IfmtArgs},
+1
View File
@@ -0,0 +1 @@
struct_lit_width = 80
+53
View File
@@ -0,0 +1,53 @@
# Testing of Dioxus core
NodeFactory
- [] rsx, html, NodeFactory generate the same structures
Diffing
- [x] create elements
- [x] create text
- [x] create fragments
- [x] create empty fragments (placeholders)
- [x] diff elements
- [x] diff from element/text to fragment
- [x] diff from element/text to empty fragment
- [x] diff to element with children works too
- [x] replace with works forward
- [x] replace with works backward
- [x] un-keyed diffing
- [x] keyed diffing
- [x] keyed diffing out of order
- [x] keyed diffing with prefix/suffix
- [x] suspended nodes work
Lifecycle
- [] Components mount properly
- [] Components create new child components
- [] Replaced components unmount old components and mount new
- [] Post-render effects are called
- []
Shared Context
- [] Shared context propagates downwards
- [] unwrapping shared context if it doesn't exist works too
Suspense
- [] use_suspense generates suspended nodes
Hooks
- [] Drop order is maintained
- [] Shared hook state is okay
- [] use_hook works
- [] use_ref works
- [] use_noderef works
- [] use_provide_state
- [] use_consume_state
VirtualDOM API
- [] work
- [] rebuild
- [] change props
+46
View File
@@ -0,0 +1,46 @@
#![allow(non_snake_case)]
use dioxus::prelude::*;
#[test]
fn test_borrowed_state() {
let _ = VirtualDom::new(Parent);
}
fn Parent(cx: Scope) -> Element {
let value = cx.use_hook(|_| String::new());
cx.render(rsx! {
div {
Child { name: value }
Child { name: value }
Child { name: value }
Child { name: value }
}
})
}
#[derive(Props)]
struct ChildProps<'a> {
name: &'a str,
}
fn Child<'a>(cx: Scope<'a, ChildProps<'a>>) -> Element {
cx.render(rsx! {
div {
h1 { "it's nested" }
Child2 { name: cx.props.name }
}
})
}
#[derive(Props)]
struct Grandchild<'a> {
name: &'a str,
}
fn Child2<'a>(cx: Scope<'a, Grandchild<'a>>) -> Element {
cx.render(rsx! {
div { "Hello {cx.props.name}!" }
})
}
+211
View File
@@ -0,0 +1,211 @@
#![allow(unused, non_upper_case_globals, non_snake_case)]
//! Prove that the dom works normally through virtualdom methods.
//!
//! This methods all use "rebuild" which completely bypasses the scheduler.
//! Hard rebuilds don't consume any events from the event queue.
use dioxus::prelude::*;
use dioxus_core::DomEdit::*;
fn new_dom<P: 'static + Send>(app: Component<P>, props: P) -> VirtualDom {
VirtualDom::new_with_props(app, props)
}
#[test]
fn test_original_diff() {
static APP: Component = |cx| {
cx.render(rsx! {
div {
div {
"Hello, world!"
}
}
})
};
let mut dom = new_dom(APP, ());
let mutations = dom.rebuild();
assert_eq!(
mutations.edits,
[
CreateElement { root: 1, tag: "div" },
CreateElement { root: 2, tag: "div" },
CreateTextNode { root: 3, text: "Hello, world!" },
AppendChildren { many: 1 },
AppendChildren { many: 1 },
AppendChildren { many: 1 },
]
);
}
#[test]
fn create() {
static APP: Component = |cx| {
cx.render(rsx! {
div {
div {
"Hello, world!"
div {
div {
Fragment {
"hello"
"world"
}
}
}
}
}
})
};
let mut dom = new_dom(APP, ());
let mutations = dom.rebuild();
assert_eq!(
mutations.edits,
[
CreateElement { root: 1, tag: "div" },
CreateElement { root: 2, tag: "div" },
CreateTextNode { root: 3, text: "Hello, world!" },
CreateElement { root: 4, tag: "div" },
CreateElement { root: 5, tag: "div" },
CreateTextNode { root: 6, text: "hello" },
CreateTextNode { root: 7, text: "world" },
AppendChildren { many: 2 },
AppendChildren { many: 1 },
AppendChildren { many: 2 },
AppendChildren { many: 1 },
AppendChildren { many: 1 },
]
);
}
#[test]
fn create_list() {
static APP: Component = |cx| {
cx.render(rsx! {
{(0..3).map(|f| rsx!{ div {
"hello"
}})}
})
};
let mut dom = new_dom(APP, ());
let mutations = dom.rebuild();
// copilot wrote this test :P
assert_eq!(
mutations.edits,
[
CreateElement { root: 1, tag: "div" },
CreateTextNode { root: 2, text: "hello" },
AppendChildren { many: 1 },
CreateElement { root: 3, tag: "div" },
CreateTextNode { root: 4, text: "hello" },
AppendChildren { many: 1 },
CreateElement { root: 5, tag: "div" },
CreateTextNode { root: 6, text: "hello" },
AppendChildren { many: 1 },
AppendChildren { many: 3 },
]
);
}
#[test]
fn create_simple() {
static APP: Component = |cx| {
cx.render(rsx! {
div {}
div {}
div {}
div {}
})
};
let mut dom = new_dom(APP, ());
let mutations = dom.rebuild();
// copilot wrote this test :P
assert_eq!(
mutations.edits,
[
CreateElement { root: 1, tag: "div" },
CreateElement { root: 2, tag: "div" },
CreateElement { root: 3, tag: "div" },
CreateElement { root: 4, tag: "div" },
AppendChildren { many: 4 },
]
);
}
#[test]
fn create_components() {
static App: Component = |cx| {
cx.render(rsx! {
Child { "abc1" }
Child { "abc2" }
Child { "abc3" }
})
};
#[derive(Props)]
struct ChildProps<'a> {
children: Element<'a>,
}
fn Child<'a>(cx: Scope<'a, ChildProps<'a>>) -> Element {
cx.render(rsx! {
h1 {}
div { {&cx.props.children} }
p {}
})
}
let mut dom = new_dom(App, ());
let mutations = dom.rebuild();
assert_eq!(
mutations.edits,
[
CreateElement { root: 1, tag: "h1" },
CreateElement { root: 2, tag: "div" },
CreateTextNode { root: 3, text: "abc1" },
AppendChildren { many: 1 },
CreateElement { root: 4, tag: "p" },
CreateElement { root: 5, tag: "h1" },
CreateElement { root: 6, tag: "div" },
CreateTextNode { root: 7, text: "abc2" },
AppendChildren { many: 1 },
CreateElement { root: 8, tag: "p" },
CreateElement { root: 9, tag: "h1" },
CreateElement { root: 10, tag: "div" },
CreateTextNode { root: 11, text: "abc3" },
AppendChildren { many: 1 },
CreateElement { root: 12, tag: "p" },
AppendChildren { many: 9 },
]
);
}
#[test]
fn anchors() {
static App: Component = |cx| {
cx.render(rsx! {
{true.then(|| rsx!{ div { "hello" } })}
{false.then(|| rsx!{ div { "goodbye" } })}
})
};
let mut dom = new_dom(App, ());
let mutations = dom.rebuild();
assert_eq!(
mutations.edits,
[
CreateElement { root: 1, tag: "div" },
CreateTextNode { root: 2, text: "hello" },
AppendChildren { many: 1 },
CreatePlaceholder { root: 3 },
AppendChildren { many: 2 },
]
);
}
+830
View File
@@ -0,0 +1,830 @@
#![allow(unused, non_upper_case_globals)]
//! Diffing Tests
//!
//! These tests only verify that the diffing algorithm works properly for single components.
//!
//! It does not validated that component lifecycles work properly. This is done in another test file.
use dioxus::prelude::*;
fn new_dom() -> VirtualDom {
VirtualDom::new(|cx| rsx!(cx, "hi"))
}
use dioxus_core::DomEdit::*;
/// Should push the text node onto the stack and modify it
#[test]
fn html_and_rsx_generate_the_same_output() {
let dom = new_dom();
let (create, change) = dom.diff_lazynodes(
rsx! ( div { "Hello world" } ),
rsx! ( div { "Goodbye world" } ),
);
assert_eq!(
create.edits,
[
CreateElement { root: 1, tag: "div" },
CreateTextNode { root: 2, text: "Hello world" },
AppendChildren { many: 1 },
AppendChildren { many: 1 },
]
);
assert_eq!(change.edits, [SetText { text: "Goodbye world", root: 2 },]);
}
/// Should result in 3 elements on the stack
#[test]
fn fragments_create_properly() {
let dom = new_dom();
let create = dom.create_vnodes(rsx! {
div { "Hello a" }
div { "Hello b" }
div { "Hello c" }
});
assert_eq!(
create.edits,
[
CreateElement { root: 1, tag: "div" },
CreateTextNode { root: 2, text: "Hello a" },
AppendChildren { many: 1 },
CreateElement { root: 3, tag: "div" },
CreateTextNode { root: 4, text: "Hello b" },
AppendChildren { many: 1 },
CreateElement { root: 5, tag: "div" },
CreateTextNode { root: 6, text: "Hello c" },
AppendChildren { many: 1 },
AppendChildren { many: 3 },
]
);
}
/// Should result in the creation of an anchor (placeholder) and then a replacewith
#[test]
fn empty_fragments_create_anchors() {
let dom = new_dom();
let left = rsx!({ (0..0).map(|_f| rsx! { div {}}) });
let right = rsx!({ (0..1).map(|_f| rsx! { div {}}) });
let (create, change) = dom.diff_lazynodes(left, right);
assert_eq!(
create.edits,
[CreatePlaceholder { root: 1 }, AppendChildren { many: 1 }]
);
assert_eq!(
change.edits,
[
CreateElement { root: 2, tag: "div" },
ReplaceWith { m: 1, root: 1 }
]
);
}
/// Should result in the creation of an anchor (placeholder) and then a replacewith m=5
#[test]
fn empty_fragments_create_many_anchors() {
let dom = new_dom();
let left = rsx!({ (0..0).map(|_f| rsx! { div {}}) });
let right = rsx!({ (0..5).map(|_f| rsx! { div {}}) });
let (create, change) = dom.diff_lazynodes(left, right);
assert_eq!(
create.edits,
[CreatePlaceholder { root: 1 }, AppendChildren { many: 1 }]
);
assert_eq!(
change.edits,
[
CreateElement { root: 2, tag: "div" },
CreateElement { root: 3, tag: "div" },
CreateElement { root: 4, tag: "div" },
CreateElement { root: 5, tag: "div" },
CreateElement { root: 6, tag: "div" },
ReplaceWith { m: 5, root: 1 }
]
);
}
/// Should result in the creation of an anchor (placeholder) and then a replacewith
/// Includes child nodes inside the fragment
#[test]
fn empty_fragments_create_anchors_with_many_children() {
let dom = new_dom();
let left = rsx!({ (0..0).map(|_| rsx! { div {} }) });
let right = rsx!({
(0..3).map(|f| {
rsx! { div { "hello: {f}" }}
})
});
let (create, change) = dom.diff_lazynodes(left, right);
assert_eq!(
create.edits,
[CreatePlaceholder { root: 1 }, AppendChildren { many: 1 }]
);
assert_eq!(
change.edits,
[
CreateElement { tag: "div", root: 2 },
CreateTextNode { text: "hello: 0", root: 3 },
AppendChildren { many: 1 },
CreateElement { tag: "div", root: 4 },
CreateTextNode { text: "hello: 1", root: 5 },
AppendChildren { many: 1 },
CreateElement { tag: "div", root: 6 },
CreateTextNode { text: "hello: 2", root: 7 },
AppendChildren { many: 1 },
ReplaceWith { root: 1, m: 3 }
]
);
}
/// Should result in every node being pushed and then replaced with an anchor
#[test]
fn many_items_become_fragment() {
let dom = new_dom();
let left = rsx!({
(0..2).map(|_| {
rsx! { div { "hello" }}
})
});
let right = rsx!({ (0..0).map(|_| rsx! { div {} }) });
let (create, change) = dom.diff_lazynodes(left, right);
assert_eq!(
create.edits,
[
CreateElement { root: 1, tag: "div" },
CreateTextNode { text: "hello", root: 2 },
AppendChildren { many: 1 },
CreateElement { root: 3, tag: "div" },
CreateTextNode { text: "hello", root: 4 },
AppendChildren { many: 1 },
AppendChildren { many: 2 },
]
);
assert_eq!(
change.edits,
[
CreatePlaceholder { root: 5 },
ReplaceWith { root: 1, m: 1 },
Remove { root: 3 },
]
);
}
/// Should result in no edits
#[test]
fn two_equal_fragments_are_equal() {
let dom = new_dom();
let left = rsx!({
(0..2).map(|_| {
rsx! { div { "hello" }}
})
});
let right = rsx!({
(0..2).map(|_| {
rsx! { div { "hello" }}
})
});
let (_create, change) = dom.diff_lazynodes(left, right);
assert!(change.edits.is_empty());
}
/// Should result the creation of more nodes appended after the old last node
#[test]
fn two_fragments_with_differrent_elements_are_differet() {
let dom = new_dom();
let left = rsx!(
{ (0..2).map(|_| rsx! { div { }} ) }
p {}
);
let right = rsx!(
{ (0..5).map(|_| rsx! (h1 { }) ) }
p {}
);
let (_create, changes) = dom.diff_lazynodes(left, right);
println!("{:#?}", &changes);
assert_eq!(
changes.edits,
[
// create the new h1s
CreateElement { tag: "h1", root: 4 },
CreateElement { tag: "h1", root: 5 },
CreateElement { tag: "h1", root: 6 },
InsertAfter { root: 2, n: 3 },
// replace the divs with new h1s
CreateElement { tag: "h1", root: 7 },
ReplaceWith { root: 1, m: 1 },
CreateElement { tag: "h1", root: 1 }, // notice how 1 gets re-used
ReplaceWith { root: 2, m: 1 },
]
);
}
/// Should result in multiple nodes destroyed - with changes to the first nodes
#[test]
fn two_fragments_with_differrent_elements_are_differet_shorter() {
let dom = new_dom();
let left = rsx!(
{(0..5).map(|f| {rsx! { div { }}})}
p {}
);
let right = rsx!(
{(0..2).map(|f| {rsx! { h1 { }}})}
p {}
);
let (create, change) = dom.diff_lazynodes(left, right);
assert_eq!(
create.edits,
[
CreateElement { root: 1, tag: "div" },
CreateElement { root: 2, tag: "div" },
CreateElement { root: 3, tag: "div" },
CreateElement { root: 4, tag: "div" },
CreateElement { root: 5, tag: "div" },
CreateElement { root: 6, tag: "p" },
AppendChildren { many: 6 },
]
);
// note: key reuse is always the last node that got used
// slab maintains a linked list, essentially
assert_eq!(
change.edits,
[
Remove { root: 3 },
Remove { root: 4 },
Remove { root: 5 },
CreateElement { root: 5, tag: "h1" }, // 3 gets reused
ReplaceWith { root: 1, m: 1 }, // 1 gets deleted
CreateElement { root: 1, tag: "h1" }, // 1 gets reused
ReplaceWith { root: 2, m: 1 },
]
);
}
/// Should result in multiple nodes destroyed - with no changes
#[test]
fn two_fragments_with_same_elements_are_differet() {
let dom = new_dom();
let left = rsx!(
{(0..2).map(|f| {rsx! { div { }}})}
p {}
);
let right = rsx!(
{(0..5).map(|f| {rsx! { div { }}})}
p {}
);
let (create, change) = dom.diff_lazynodes(left, right);
assert_eq!(
create.edits,
[
CreateElement { root: 1, tag: "div" },
CreateElement { root: 2, tag: "div" },
CreateElement { root: 3, tag: "p" },
AppendChildren { many: 3 },
]
);
assert_eq!(
change.edits,
[
CreateElement { root: 4, tag: "div" },
CreateElement { root: 5, tag: "div" },
CreateElement { root: 6, tag: "div" },
InsertAfter { root: 2, n: 3 },
]
);
}
/// should result in the removal of elements
#[test]
fn keyed_diffing_order() {
let dom = new_dom();
let left = rsx!(
{(0..5).map(|f| {rsx! { div { key: "{f}" }}})}
p {"e"}
);
let right = rsx!(
{(0..2).map(|f| rsx! { div { key: "{f}" }})}
p {"e"}
);
let (create, change) = dom.diff_lazynodes(left, right);
assert_eq!(
change.edits,
[Remove { root: 3 }, Remove { root: 4 }, Remove { root: 5 },]
);
}
/// Should result in moves, but not removals or additions
#[test]
fn keyed_diffing_out_of_order() {
let dom = new_dom();
let left = rsx!({
[0, 1, 2, 3, /**/ 4, 5, 6, /**/ 7, 8, 9].iter().map(|f| {
rsx! { div { key: "{f}" }}
})
});
let right = rsx!({
[0, 1, 2, 3, /**/ 6, 4, 5, /**/ 7, 8, 9].iter().map(|f| {
rsx! { div { key: "{f}" }}
})
});
let (_, changes) = dom.diff_lazynodes(left, right);
println!("{:?}", &changes);
assert_eq!(
changes.edits,
[PushRoot { root: 7 }, InsertBefore { root: 5, n: 1 }]
);
}
/// Should result in moves only
#[test]
fn keyed_diffing_out_of_order_adds() {
let dom = new_dom();
let left = rsx!({
[/**/ 4, 5, 6, 7, 8 /**/].iter().map(|f| {
rsx! { div { key: "{f}" }}
})
});
let right = rsx!({
[/**/ 8, 7, 4, 5, 6 /**/].iter().map(|f| {
rsx! { div { key: "{f}" }}
})
});
let (_, change) = dom.diff_lazynodes(left, right);
assert_eq!(
change.edits,
[
PushRoot { root: 5 },
PushRoot { root: 4 },
InsertBefore { n: 2, root: 1 }
]
);
}
/// Should result in moves onl
#[test]
fn keyed_diffing_out_of_order_adds_2() {
let dom = new_dom();
let left = rsx!({
[/**/ 4, 5, 6, 7, 8 /**/].iter().map(|f| {
rsx! { div { key: "{f}" }}
})
});
let right = rsx!({
[/**/ 7, 8, 4, 5, 6 /**/].iter().map(|f| {
rsx! { div { key: "{f}" }}
})
});
let (_, change) = dom.diff_lazynodes(left, right);
assert_eq!(
change.edits,
[
PushRoot { root: 4 },
PushRoot { root: 5 },
InsertBefore { n: 2, root: 1 }
]
);
}
/// Should result in moves onl
#[test]
fn keyed_diffing_out_of_order_adds_3() {
let dom = new_dom();
let left = rsx!({
[/**/ 4, 5, 6, 7, 8 /**/].iter().map(|f| {
rsx! { div { key: "{f}" }}
})
});
let right = rsx!({
[/**/ 4, 8, 7, 5, 6 /**/].iter().map(|f| {
rsx! { div { key: "{f}" }}
})
});
let (_, change) = dom.diff_lazynodes(left, right);
assert_eq!(
change.edits,
[
PushRoot { root: 5 },
PushRoot { root: 4 },
InsertBefore { n: 2, root: 2 }
]
);
}
/// Should result in moves onl
#[test]
fn keyed_diffing_out_of_order_adds_4() {
let dom = new_dom();
let left = rsx!({
[/**/ 4, 5, 6, 7, 8 /**/].iter().map(|f| {
rsx! { div { key: "{f}" }}
})
});
let right = rsx!({
[/**/ 4, 5, 8, 7, 6 /**/].iter().map(|f| {
rsx! { div { key: "{f}" }}
})
});
let (_, change) = dom.diff_lazynodes(left, right);
assert_eq!(
change.edits,
[
PushRoot { root: 5 },
PushRoot { root: 4 },
InsertBefore { n: 2, root: 3 }
]
);
}
/// Should result in moves onl
#[test]
fn keyed_diffing_out_of_order_adds_5() {
let dom = new_dom();
let left = rsx!({
[/**/ 4, 5, 6, 7, 8 /**/].iter().map(|f| {
rsx! { div { key: "{f}" }}
})
});
let right = rsx!({
[/**/ 4, 5, 6, 8, 7 /**/].iter().map(|f| {
rsx! { div { key: "{f}" }}
})
});
let (_, change) = dom.diff_lazynodes(left, right);
assert_eq!(
change.edits,
[PushRoot { root: 5 }, InsertBefore { n: 1, root: 4 }]
);
}
#[test]
fn keyed_diffing_additions() {
let dom = new_dom();
let left = rsx!({
[/**/ 4, 5, 6, 7, 8 /**/].iter().map(|f| {
rsx! { div { key: "{f}" }}
})
});
let right = rsx!({
[/**/ 4, 5, 6, 7, 8, 9, 10 /**/].iter().map(|f| {
rsx! { div { key: "{f}" }}
})
});
let (_, change) = dom.diff_lazynodes(left, right);
assert_eq!(
change.edits,
[
CreateElement { root: 6, tag: "div" },
CreateElement { root: 7, tag: "div" },
InsertAfter { n: 2, root: 5 }
]
);
}
#[test]
fn keyed_diffing_additions_and_moves_on_ends() {
let dom = new_dom();
let left = rsx!({
[/**/ 4, 5, 6, 7 /**/].iter().map(|f| {
rsx! { div { key: "{f}" }}
})
});
let right = rsx!({
[/**/ 7, 4, 5, 6, 11, 12 /**/].iter().map(|f| {
rsx! { div { key: "{f}" }}
})
});
let (_, change) = dom.diff_lazynodes(left, right);
println!("{:?}", change);
assert_eq!(
change.edits,
[
// create 11, 12
CreateElement { tag: "div", root: 5 },
CreateElement { tag: "div", root: 6 },
InsertAfter { root: 3, n: 2 },
// move 7 to the front
PushRoot { root: 4 },
InsertBefore { root: 1, n: 1 }
]
);
}
#[test]
fn keyed_diffing_additions_and_moves_in_middle() {
let dom = new_dom();
let left = rsx!({
[/**/ 1, 2, 3, 4 /**/].iter().map(|f| {
rsx! { div { key: "{f}" }}
})
});
let right = rsx!({
[/**/ 4, 1, 7, 8, 2, 5, 6, 3 /**/].iter().map(|f| {
rsx! { div { key: "{f}" }}
})
});
// LIS: 4, 5, 6
let (_, change) = dom.diff_lazynodes(left, right);
println!("{:#?}", change);
assert_eq!(
change.edits,
[
// create 5, 6
CreateElement { tag: "div", root: 5 },
CreateElement { tag: "div", root: 6 },
InsertBefore { root: 3, n: 2 },
// create 7, 8
CreateElement { tag: "div", root: 7 },
CreateElement { tag: "div", root: 8 },
InsertBefore { root: 2, n: 2 },
// move 7
PushRoot { root: 4 },
InsertBefore { root: 1, n: 1 }
]
);
}
#[test]
fn controlled_keyed_diffing_out_of_order() {
let dom = new_dom();
let left = rsx!({
[4, 5, 6, 7].iter().map(|f| {
rsx! { div { key: "{f}" }}
})
});
let right = rsx!({
[0, 5, 9, 6, 4].iter().map(|f| {
rsx! { div { key: "{f}" }}
})
});
// LIS: 5, 6
let (_, changes) = dom.diff_lazynodes(left, right);
println!("{:#?}", &changes);
assert_eq!(
changes.edits,
[
Remove { root: 4 },
// move 4 to after 6
PushRoot { root: 1 },
InsertAfter { n: 1, root: 3 },
// remove 7
// create 9 and insert before 6
CreateElement { root: 4, tag: "div" },
InsertBefore { n: 1, root: 3 },
// create 0 and insert before 5
CreateElement { root: 5, tag: "div" },
InsertBefore { n: 1, root: 2 },
]
);
}
#[test]
fn controlled_keyed_diffing_out_of_order_max_test() {
let dom = new_dom();
let left = rsx!({
[0, 1, 2, 3, 4].iter().map(|f| {
rsx! { div { key: "{f}" }}
})
});
let right = rsx!({
[3, 0, 1, 10, 2].iter().map(|f| {
rsx! { div { key: "{f}" }}
})
});
let (_, changes) = dom.diff_lazynodes(left, right);
println!("{:#?}", &changes);
assert_eq!(
changes.edits,
[
Remove { root: 5 },
CreateElement { root: 5, tag: "div" },
InsertBefore { n: 1, root: 3 },
PushRoot { root: 4 },
InsertBefore { n: 1, root: 1 },
]
);
}
// noticed some weird behavior in the desktop interpreter
// just making sure it doesnt happen in the core implementation
#[test]
fn remove_list() {
let dom = new_dom();
let left = rsx!({
(0..10).rev().take(5).map(|i| {
rsx! { Fragment { key: "{i}", "{i}" }}
})
});
let right = rsx!({
(0..10).rev().take(2).map(|i| {
rsx! { Fragment { key: "{i}", "{i}" }}
})
});
let (create, changes) = dom.diff_lazynodes(left, right);
// dbg!(create);
// dbg!(changes);
assert_eq!(
changes.edits,
[
// remove 5, 4, 3
Remove { root: 3 },
Remove { root: 4 },
Remove { root: 5 },
]
);
}
// noticed some weird behavior in the desktop interpreter
// just making sure it doesnt happen in the core implementation
#[test]
fn remove_list_nokeyed() {
let dom = new_dom();
let left = rsx!({
(0..10).rev().take(5).map(|i| {
rsx! { Fragment { "{i}" }}
})
});
let right = rsx!({
(0..10).rev().take(2).map(|i| {
rsx! { Fragment { "{i}" }}
})
});
let (create, changes) = dom.diff_lazynodes(left, right);
assert_eq!(
changes.edits,
[
// remove 5, 4, 3
Remove { root: 3 },
Remove { root: 4 },
Remove { root: 5 },
]
);
}
#[test]
fn add_nested_elements() {
let vdom = new_dom();
let (_create, change) = vdom.diff_lazynodes(
rsx! {
div{}
},
rsx! {
div{
div{}
}
},
);
assert_eq!(
change.edits,
[
PushRoot { root: 1 },
CreateElement { root: 2, tag: "div" },
AppendChildren { many: 1 },
PopRoot {},
]
);
}
#[test]
fn add_listeners() {
let vdom = new_dom();
let (_create, change) = vdom.diff_lazynodes(
rsx! {
div{}
},
rsx! {
div{
onkeyup: |_| {},
onkeydown: |_| {},
}
},
);
assert_eq!(
change.edits,
[
NewEventListener { event_name: "keyup", scope: ScopeId(0), root: 1 },
NewEventListener { event_name: "keydown", scope: ScopeId(0), root: 1 },
]
);
}
#[test]
fn remove_listeners() {
let vdom = new_dom();
let (_create, change) = vdom.diff_lazynodes(
rsx! {
div{
onkeyup: |_| {},
onkeydown: |_| {},
}
},
rsx! {
div{}
},
);
assert_eq!(
change.edits,
[
RemoveEventListener { event: "keyup", root: 1 },
RemoveEventListener { event: "keydown", root: 1 },
]
);
}
#[test]
fn diff_listeners() {
let vdom = new_dom();
let (_create, change) = vdom.diff_lazynodes(
rsx! {
div{
onkeydown: |_| {},
}
},
rsx! {
div{
onkeyup: |_| {},
}
},
);
assert_eq!(
change.edits,
[
RemoveEventListener { root: 1, event: "keydown" },
NewEventListener { event_name: "keyup", scope: ScopeId(0), root: 1 }
]
);
}
+63
View File
@@ -0,0 +1,63 @@
#![allow(unused, non_upper_case_globals, non_snake_case)]
//! Prove that the dom works normally through virtualdom methods.
//!
//! This methods all use "rebuild" which completely bypasses the scheduler.
//! Hard rebuilds don't consume any events from the event queue.
use dioxus::prelude::*;
use dioxus_core::{DomEdit::*, ScopeId};
const IS_LOGGING_ENABLED: bool = false;
fn new_dom<P: 'static + Send>(app: Component<P>, props: P) -> VirtualDom {
VirtualDom::new_with_props(app, props)
}
/// This test ensures that if a component aborts early, it is replaced with a placeholder.
/// In debug, this should also toss a warning.
#[test]
fn test_early_abort() {
const app: Component = |cx| {
let val = cx.use_hook(|_| 0);
*val += 1;
if *val == 2 {
return None;
}
rsx!(cx, div { "Hello, world!" })
};
let mut dom = new_dom(app, ());
let edits = dom.rebuild();
assert_eq!(
edits.edits,
[
CreateElement { tag: "div", root: 1 },
CreateTextNode { text: "Hello, world!", root: 2 },
AppendChildren { many: 1 },
AppendChildren { many: 1 },
]
);
let edits = dom.hard_diff(ScopeId(0));
assert_eq!(
edits.edits,
[CreatePlaceholder { root: 3 }, ReplaceWith { root: 1, m: 1 },],
);
let edits = dom.hard_diff(ScopeId(0));
assert_eq!(
edits.edits,
[
CreateElement { tag: "div", root: 1 }, // keys get reused
CreateTextNode { text: "Hello, world!", root: 2 }, // keys get reused
AppendChildren { many: 1 },
ReplaceWith { root: 3, m: 1 },
]
);
}
+261
View File
@@ -0,0 +1,261 @@
#![allow(unused, non_upper_case_globals)]
#![allow(non_snake_case)]
//! Tests for the lifecycle of components.
use dioxus::prelude::*;
use dioxus_core::DomEdit::*;
use std::sync::{Arc, Mutex};
type Shared<T> = Arc<Mutex<T>>;
#[test]
fn manual_diffing() {
struct AppProps {
value: Shared<&'static str>,
}
static App: Component<AppProps> = |cx| {
let val = cx.props.value.lock().unwrap();
cx.render(rsx! { div { "{val}" } })
};
let value = Arc::new(Mutex::new("Hello"));
let mut dom = VirtualDom::new_with_props(App, AppProps { value: value.clone() });
let _ = dom.rebuild();
*value.lock().unwrap() = "goodbye";
let edits = dom.rebuild();
println!("edits: {:?}", edits);
}
#[test]
fn events_generate() {
fn app(cx: Scope) -> Element {
let count = cx.use_hook(|_| 0);
let inner = match *count {
0 => {
rsx! {
div {
onclick: move |_| *count += 1,
div {
"nested"
}
"Click me!"
}
}
}
_ => todo!(),
};
cx.render(inner)
};
let mut dom = VirtualDom::new(app);
let mut channel = dom.get_scheduler_channel();
assert!(dom.has_work());
let edits = dom.rebuild();
assert_eq!(
edits.edits,
[
CreateElement { tag: "div", root: 1 },
NewEventListener { event_name: "click", scope: ScopeId(0), root: 1 },
CreateElement { tag: "div", root: 2 },
CreateTextNode { text: "nested", root: 3 },
AppendChildren { many: 1 },
CreateTextNode { text: "Click me!", root: 4 },
AppendChildren { many: 2 },
AppendChildren { many: 1 },
]
)
}
#[test]
fn components_generate() {
fn app(cx: Scope) -> Element {
let render_phase = cx.use_hook(|_| 0);
*render_phase += 1;
cx.render(match *render_phase {
1 => rsx!("Text0"),
2 => rsx!(div {}),
3 => rsx!("Text2"),
4 => rsx!(Child {}),
5 => rsx!({ None as Option<()> }),
6 => rsx!("text 3"),
7 => rsx!({ (0..2).map(|f| rsx!("text {f}")) }),
8 => rsx!(Child {}),
_ => todo!(),
})
};
fn Child(cx: Scope) -> Element {
println!("Running child");
cx.render(rsx! {
h1 {}
})
}
let mut dom = VirtualDom::new(app);
let edits = dom.rebuild();
assert_eq!(
edits.edits,
[
CreateTextNode { text: "Text0", root: 1 },
AppendChildren { many: 1 },
]
);
assert_eq!(
dom.hard_diff(ScopeId(0)).edits,
[
CreateElement { tag: "div", root: 2 },
ReplaceWith { root: 1, m: 1 },
]
);
assert_eq!(
dom.hard_diff(ScopeId(0)).edits,
[
CreateTextNode { text: "Text2", root: 1 },
ReplaceWith { root: 2, m: 1 },
]
);
// child {}
assert_eq!(
dom.hard_diff(ScopeId(0)).edits,
[
CreateElement { tag: "h1", root: 2 },
ReplaceWith { root: 1, m: 1 },
]
);
// placeholder
assert_eq!(
dom.hard_diff(ScopeId(0)).edits,
[CreatePlaceholder { root: 1 }, ReplaceWith { root: 2, m: 1 },]
);
assert_eq!(
dom.hard_diff(ScopeId(0)).edits,
[
CreateTextNode { text: "text 3", root: 2 },
ReplaceWith { root: 1, m: 1 },
]
);
assert_eq!(
dom.hard_diff(ScopeId(0)).edits,
[
CreateTextNode { text: "text 0", root: 1 },
CreateTextNode { text: "text 1", root: 3 },
ReplaceWith { root: 2, m: 2 },
]
);
assert_eq!(
dom.hard_diff(ScopeId(0)).edits,
[
CreateElement { tag: "h1", root: 2 },
ReplaceWith { root: 1, m: 1 },
Remove { root: 3 },
]
);
}
#[test]
fn component_swap() {
fn app(cx: Scope) -> Element {
let render_phase = cx.use_hook(|_| 0);
*render_phase += 1;
cx.render(match *render_phase {
0 => rsx!(
div {
NavBar {}
Dashboard {}
}
),
1 => rsx!(
div {
NavBar {}
Results {}
}
),
2 => rsx!(
div {
NavBar {}
Dashboard {}
}
),
3 => rsx!(
div {
NavBar {}
Results {}
}
),
4 => rsx!(
div {
NavBar {}
Dashboard {}
}
),
_ => rsx!("blah"),
})
};
static NavBar: Component = |cx| {
println!("running navbar");
cx.render(rsx! {
h1 {
"NavBar"
{(0..3).map(|f| rsx!(NavLink {}))}
}
})
};
static NavLink: Component = |cx| {
println!("running navlink");
cx.render(rsx! {
h1 {
"NavLink"
}
})
};
static Dashboard: Component = |cx| {
println!("running dashboard");
cx.render(rsx! {
div {
"dashboard"
}
})
};
static Results: Component = |cx| {
println!("running results");
cx.render(rsx! {
div {
"results"
}
})
};
let mut dom = VirtualDom::new(app);
let edits = dom.rebuild();
dbg!(&edits);
let edits = dom.work_with_deadline(|| false);
dbg!(&edits);
let edits = dom.work_with_deadline(|| false);
dbg!(&edits);
let edits = dom.work_with_deadline(|| false);
dbg!(&edits);
let edits = dom.work_with_deadline(|| false);
dbg!(&edits);
}
+413
View File
@@ -0,0 +1,413 @@
#![allow(non_snake_case)]
/*
Stress Miri as much as possible.
Prove that we don't leak memory and that our methods are safe.
Specifically:
- [ ] VirtualDom drops memory safely
- [ ] Borrowed components don't expose invalid pointers
- [ ] Async isn't busted
*/
use dioxus::prelude::*;
use dioxus_core::SchedulerMsg;
fn new_dom<P: 'static + Send>(app: Component<P>, props: P) -> VirtualDom {
VirtualDom::new_with_props(app, props)
}
/// This test ensures that if a component aborts early, it is replaced with a placeholder.
/// In debug, this should also toss a warning.
#[test]
fn test_memory_leak() {
fn app(cx: Scope) -> Element {
let val = cx.use_hook(|_| 0);
*val += 1;
if *val == 2 || *val == 4 {
return None;
}
let name = cx.use_hook(|_| String::from("asd"));
cx.render(rsx!(
div { "Hello, world!" }
child()
child()
child()
child()
child()
child()
borrowed_child(na: name)
borrowed_child(na: name)
borrowed_child(na: name)
borrowed_child(na: name)
borrowed_child(na: name)
))
}
#[derive(Props)]
struct BorrowedProps<'a> {
na: &'a str,
}
fn borrowed_child<'a>(cx: Scope<'a, BorrowedProps<'a>>) -> Element {
rsx!(cx, div {
"goodbye {cx.props.na}"
child()
child()
})
}
fn child(cx: Scope) -> Element {
rsx!(cx, div { "goodbye world" })
}
let mut dom = new_dom(app, ());
dom.rebuild();
dom.hard_diff(ScopeId(0));
dom.hard_diff(ScopeId(0));
dom.hard_diff(ScopeId(0));
dom.hard_diff(ScopeId(0));
dom.hard_diff(ScopeId(0));
dom.hard_diff(ScopeId(0));
}
#[test]
fn memo_works_properly() {
fn app(cx: Scope) -> Element {
let val = cx.use_hook(|_| 0);
*val += 1;
if *val == 2 || *val == 4 {
return None;
}
let name = cx.use_hook(|_| String::from("asd"));
cx.render(rsx!(
div { "Hello, world! {name}" }
child(na: "asdfg".to_string() )
))
}
#[derive(PartialEq, Props)]
struct ChildProps {
na: String,
}
fn child(cx: Scope<ChildProps>) -> Element {
rsx!(cx, div { "goodbye world" })
}
let mut dom = new_dom(app, ());
dom.rebuild();
dom.hard_diff(ScopeId(0));
dom.hard_diff(ScopeId(0));
dom.hard_diff(ScopeId(0));
dom.hard_diff(ScopeId(0));
dom.hard_diff(ScopeId(0));
dom.hard_diff(ScopeId(0));
dom.hard_diff(ScopeId(0));
}
#[test]
fn free_works_on_root_props() {
fn app(cx: Scope<Custom>) -> Element {
cx.render(rsx! {
child(a: "alpha")
child(a: "beta")
child(a: "gamma")
child(a: "delta")
})
}
#[derive(Props, PartialEq)]
struct ChildProps {
a: &'static str,
}
fn child(cx: Scope<ChildProps>) -> Element {
rsx!(cx, "child {cx.props.a}")
}
struct Custom {
val: String,
}
impl Drop for Custom {
fn drop(&mut self) {
dbg!("dropped! {}", &self.val);
}
}
let mut dom = new_dom(app, Custom { val: String::from("asd") });
dom.rebuild();
}
#[test]
fn free_works_on_borrowed() {
fn app(cx: Scope) -> Element {
cx.render(rsx! {
child(a: "alpha", b: "asd".to_string())
})
}
#[derive(Props)]
struct ChildProps<'a> {
a: &'a str,
b: String,
}
fn child<'a>(cx: Scope<'a, ChildProps<'a>>) -> Element {
dbg!("rendering child");
rsx!(cx, "child {cx.props.a}, {cx.props.b}")
}
impl Drop for ChildProps<'_> {
fn drop(&mut self) {
dbg!("dropped child!");
}
}
let mut dom = new_dom(app, ());
let _ = dom.rebuild();
}
#[test]
fn free_works_on_root_hooks() {
/*
On Drop, scopearena drops all the hook contents.
*/
struct Droppable<T>(T);
impl<T> Drop for Droppable<T> {
fn drop(&mut self) {
dbg!("dropping droppable");
}
}
fn app(cx: Scope) -> Element {
let name = cx.use_hook(|_| Droppable(String::from("asd")));
rsx!(cx, div { "{name.0}" })
}
let mut dom = new_dom(app, ());
let _ = dom.rebuild();
}
#[test]
fn old_props_arent_stale() {
fn app(cx: Scope) -> Element {
dbg!("rendering parent");
let cnt = cx.use_hook(|_| 0);
*cnt += 1;
if *cnt == 1 {
rsx!(cx, div { child(a: "abcdef".to_string()) })
} else {
rsx!(cx, div { child(a: "abcdef".to_string()) })
}
}
#[derive(Props, PartialEq)]
struct ChildProps {
a: String,
}
fn child(cx: Scope<ChildProps>) -> Element {
dbg!("rendering child", &cx.props.a);
rsx!(cx, div { "child {cx.props.a}" })
}
let mut dom = new_dom(app, ());
let _ = dom.rebuild();
dom.handle_message(SchedulerMsg::Immediate(ScopeId(0)));
dom.work_with_deadline(|| false);
dom.handle_message(SchedulerMsg::Immediate(ScopeId(0)));
dom.work_with_deadline(|| false);
dom.handle_message(SchedulerMsg::Immediate(ScopeId(0)));
dom.work_with_deadline(|| false);
dbg!("forcing update to child");
dom.handle_message(SchedulerMsg::Immediate(ScopeId(1)));
dom.work_with_deadline(|| false);
dom.handle_message(SchedulerMsg::Immediate(ScopeId(1)));
dom.work_with_deadline(|| false);
dom.handle_message(SchedulerMsg::Immediate(ScopeId(1)));
dom.work_with_deadline(|| false);
}
#[test]
fn basic() {
fn app(cx: Scope) -> Element {
rsx!(cx, div {
child(a: "abcdef".to_string())
})
}
#[derive(Props, PartialEq)]
struct ChildProps {
a: String,
}
fn child(cx: Scope<ChildProps>) -> Element {
dbg!("rendering child", &cx.props.a);
rsx!(cx, div { "child {cx.props.a}" })
}
let mut dom = new_dom(app, ());
let _ = dom.rebuild();
dom.handle_message(SchedulerMsg::Immediate(ScopeId(0)));
dom.work_with_deadline(|| false);
dom.handle_message(SchedulerMsg::Immediate(ScopeId(0)));
dom.work_with_deadline(|| false);
}
#[test]
fn leak_thru_children() {
fn app(cx: Scope) -> Element {
cx.render(rsx! {
Child {
name: "asd".to_string(),
}
});
cx.render(rsx! {
div {}
})
}
#[inline_props]
fn Child(cx: Scope, name: String) -> Element {
rsx!(cx, div { "child {name}" })
}
let mut dom = new_dom(app, ());
let _ = dom.rebuild();
dom.handle_message(SchedulerMsg::Immediate(ScopeId(0)));
dom.work_with_deadline(|| false);
dom.handle_message(SchedulerMsg::Immediate(ScopeId(0)));
dom.work_with_deadline(|| false);
}
#[test]
fn test_pass_thru() {
#[inline_props]
fn NavContainer<'a>(cx: Scope, children: Element<'a>) -> Element {
cx.render(rsx! {
header {
nav { children }
}
})
}
fn NavMenu(cx: Scope) -> Element {
rsx!(cx,
NavBrand {}
div {
NavStart {}
NavEnd {}
}
)
}
fn NavBrand(cx: Scope) -> Element {
rsx!(cx, div {})
}
fn NavStart(cx: Scope) -> Element {
rsx!(cx, div {})
}
fn NavEnd(cx: Scope) -> Element {
rsx!(cx, div {})
}
#[inline_props]
fn MainContainer<'a>(
cx: Scope,
nav: Element<'a>,
body: Element<'a>,
footer: Element<'a>,
) -> Element {
cx.render(rsx! {
div {
class: "columns is-mobile",
div {
class: "column is-full",
nav,
body,
footer,
}
}
})
}
fn app(cx: Scope) -> Element {
let nav = cx.render(rsx! {
NavContainer {
NavMenu {}
}
});
let body = cx.render(rsx! {
div {}
});
let footer = cx.render(rsx! {
div {}
});
cx.render(rsx! {
MainContainer {
nav: nav,
body: body,
footer: footer,
}
})
}
let mut dom = new_dom(app, ());
let _ = dom.rebuild();
for _ in 0..40 {
dom.handle_message(SchedulerMsg::Immediate(ScopeId(0)));
dom.work_with_deadline(|| false);
dom.handle_message(SchedulerMsg::Immediate(ScopeId(0)));
dom.work_with_deadline(|| false);
dom.handle_message(SchedulerMsg::Immediate(ScopeId(0)));
dom.work_with_deadline(|| false);
dom.handle_message(SchedulerMsg::Immediate(ScopeId(1)));
dom.work_with_deadline(|| false);
dom.handle_message(SchedulerMsg::Immediate(ScopeId(1)));
dom.work_with_deadline(|| false);
dom.handle_message(SchedulerMsg::Immediate(ScopeId(1)));
dom.work_with_deadline(|| false);
dom.handle_message(SchedulerMsg::Immediate(ScopeId(2)));
dom.work_with_deadline(|| false);
dom.handle_message(SchedulerMsg::Immediate(ScopeId(2)));
dom.work_with_deadline(|| false);
dom.handle_message(SchedulerMsg::Immediate(ScopeId(2)));
dom.work_with_deadline(|| false);
dom.handle_message(SchedulerMsg::Immediate(ScopeId(3)));
dom.work_with_deadline(|| false);
dom.handle_message(SchedulerMsg::Immediate(ScopeId(3)));
dom.work_with_deadline(|| false);
dom.handle_message(SchedulerMsg::Immediate(ScopeId(3)));
dom.work_with_deadline(|| false);
}
}
+100
View File
@@ -0,0 +1,100 @@
#![allow(unused, non_upper_case_globals)]
#![allow(non_snake_case)]
//! Diffing Tests
//!
//! These tests only verify that the diffing algorithm works properly for single components.
//!
//! It does not validated that component lifecycles work properly. This is done in another test file.
use dioxus::prelude::*;
fn new_dom() -> VirtualDom {
VirtualDom::new(|cx| rsx!(cx, "hi"))
}
use dioxus_core::DomEdit::*;
/// Should push the text node onto the stack and modify it
#[test]
fn nested_passthru_creates() {
fn app(cx: Scope) -> Element {
cx.render(rsx! {
Child {
Child {
Child {
div {
"hi"
}
}
}
}
})
};
#[inline_props]
fn Child<'a>(cx: Scope, children: Element<'a>) -> Element {
cx.render(rsx! { children })
};
let mut dom = VirtualDom::new(app);
let mut channel = dom.get_scheduler_channel();
assert!(dom.has_work());
let edits = dom.rebuild();
assert_eq!(
edits.edits,
[
CreateElement { tag: "div", root: 1 },
CreateTextNode { text: "hi", root: 2 },
AppendChildren { many: 1 },
AppendChildren { many: 1 },
]
)
}
/// Should push the text node onto the stack and modify it
#[test]
fn nested_passthru_creates_add() {
fn app(cx: Scope) -> Element {
cx.render(rsx! {
Child {
"1"
Child {
"2"
Child {
"3"
div {
"hi"
}
}
}
}
})
};
#[inline_props]
fn Child<'a>(cx: Scope, children: Element<'a>) -> Element {
cx.render(rsx! {
children
})
};
let mut dom = VirtualDom::new(app);
let mut channel = dom.get_scheduler_channel();
assert!(dom.has_work());
let edits = dom.rebuild();
assert_eq!(
edits.edits,
[
CreateTextNode { text: "1", root: 1 },
CreateTextNode { text: "2", root: 2 },
CreateTextNode { text: "3", root: 3 },
CreateElement { tag: "div", root: 4 },
CreateTextNode { text: "hi", root: 5 },
AppendChildren { many: 1 },
AppendChildren { many: 4 },
]
)
}
+6
View File
@@ -0,0 +1,6 @@
//! Tests for the scheduler.
//!
//! TODO
//! - priority lanes
//! - periodic checking
//!
+126
View File
@@ -0,0 +1,126 @@
#![allow(unused, non_upper_case_globals, non_snake_case)]
use dioxus::prelude::*;
use dioxus_core::{DomEdit, Mutations, SchedulerMsg, ScopeId};
use std::rc::Rc;
use DomEdit::*;
#[test]
fn shared_state_test() {
struct MySharedState(&'static str);
static App: Component = |cx| {
cx.provide_context(Rc::new(MySharedState("world!")));
cx.render(rsx!(Child {}))
};
static Child: Component = |cx| {
let shared = cx.consume_context::<Rc<MySharedState>>()?;
cx.render(rsx!("Hello, {shared.0}"))
};
let mut dom = VirtualDom::new(App);
let Mutations { edits, .. } = dom.rebuild();
assert_eq!(
edits,
[
CreateTextNode { root: 1, text: "Hello, world!" },
AppendChildren { many: 1 },
]
);
}
#[test]
fn swap_test() {
struct MySharedState(&'static str);
fn app(cx: Scope) -> Element {
let val = cx.use_hook(|_| 0);
*val += 1;
cx.provide_context(Rc::new(MySharedState("world!")));
let child = match *val % 2 {
0 => rsx!(
Child1 {
Child1 { }
Child2 { }
}
),
_ => rsx!(
Child2 {
Child2 { }
Child2 { }
}
),
};
cx.render(rsx!(
Router {
div { child }
}
))
}
#[inline_props]
fn Router<'a>(cx: Scope, children: Element<'a>) -> Element<'a> {
cx.render(rsx!(div { children }))
}
#[inline_props]
fn Child1<'a>(cx: Scope, children: Element<'a>) -> Element {
let shared = cx.consume_context::<Rc<MySharedState>>().unwrap();
println!("Child1: {}", shared.0);
cx.render(rsx! {
div {
"{shared.0}",
children
}
})
}
#[inline_props]
fn Child2<'a>(cx: Scope, children: Element<'a>) -> Element {
let shared = cx.consume_context::<Rc<MySharedState>>().unwrap();
println!("Child2: {}", shared.0);
cx.render(rsx! {
h1 {
"{shared.0}",
children
}
})
}
let mut dom = VirtualDom::new(app);
let Mutations { edits, .. } = dom.rebuild();
dom.handle_message(SchedulerMsg::Immediate(ScopeId(0)));
dom.work_with_deadline(|| false);
dom.handle_message(SchedulerMsg::Immediate(ScopeId(0)));
dom.work_with_deadline(|| false);
dom.handle_message(SchedulerMsg::Immediate(ScopeId(0)));
dom.work_with_deadline(|| false);
dom.handle_message(SchedulerMsg::Immediate(ScopeId(0)));
dom.work_with_deadline(|| false);
dom.handle_message(SchedulerMsg::Immediate(ScopeId(0)));
dom.work_with_deadline(|| false);
dom.handle_message(SchedulerMsg::Immediate(ScopeId(0)));
dom.work_with_deadline(|| false);
// dom.handle_message(SchedulerMsg::Immediate(ScopeId(1)));
// dom.work_with_deadline(|| false);
// dom.handle_message(SchedulerMsg::Immediate(ScopeId(2)));
// dom.work_with_deadline(|| false);
// dom.handle_message(SchedulerMsg::Immediate(ScopeId(3)));
// dom.work_with_deadline(|| false);
// dom.handle_message(SchedulerMsg::Immediate(ScopeId(0)));
// dom.work_with_deadline(|| false);
// dom.handle_message(SchedulerMsg::Immediate(ScopeId(0)));
// dom.work_with_deadline(|| false);
// dom.handle_message(SchedulerMsg::Immediate(ScopeId(0)));
// dom.work_with_deadline(|| false);
}
+1
View File
@@ -0,0 +1 @@
fn main() {}
+95
View File
@@ -0,0 +1,95 @@
#![allow(unused, non_upper_case_globals)]
//! Rebuilding tests
//! ----------------
//!
//! This tests module ensures that the initial build of the virtualdom is correct.
//! This does not include dynamic tests or the diffing algorithm itself.
//!
//! It does prove that mounting works properly and the correct edit streams are generated.
//!
//! Don't have a good way to validate, everything is done manually ATM
use dioxus::prelude::*;
use dioxus_core::DomEdit::*;
#[test]
fn app_runs() {
static App: Component = |cx| rsx!(cx, div{"hello"} );
let mut vdom = VirtualDom::new(App);
let edits = vdom.rebuild();
}
#[test]
fn fragments_work() {
static App: Component = |cx| {
cx.render(rsx!(
div{"hello"}
div{"goodbye"}
))
};
let mut vdom = VirtualDom::new(App);
let edits = vdom.rebuild();
// should result in a final "appendchildren n=2"
dbg!(edits);
}
#[test]
fn lists_work() {
static App: Component = |cx| {
cx.render(rsx!(
h1 {"hello"}
(0..6).map(|f| rsx!(span{ "{f}" }))
))
};
let mut vdom = VirtualDom::new(App);
let edits = vdom.rebuild();
dbg!(edits);
}
#[test]
fn conditional_rendering() {
static App: Component = |cx| {
cx.render(rsx!(
h1 {"hello"}
{true.then(|| rsx!(span{ "a" }))}
{false.then(|| rsx!(span{ "b" }))}
))
};
let mut vdom = VirtualDom::new(App);
let mutations = vdom.rebuild();
assert_eq!(
mutations.edits,
[
CreateElement { root: 1, tag: "h1" },
CreateTextNode { root: 2, text: "hello" },
AppendChildren { many: 1 },
CreateElement { root: 3, tag: "span" },
CreateTextNode { root: 4, text: "a" },
AppendChildren { many: 1 },
CreatePlaceholder { root: 5 },
AppendChildren { many: 3 },
]
)
}
#[test]
fn child_components() {
static App: Component = |cx| {
cx.render(rsx!(
{true.then(|| rsx!(Child { }))}
{false.then(|| rsx!(Child { }))}
))
};
static Child: Component = |cx| {
cx.render(rsx!(
h1 {"hello"}
h1 {"goodbye"}
))
};
let mut vdom = VirtualDom::new(App);
let edits = vdom.rebuild();
dbg!(edits);
}
+7 -7
View File
@@ -36,14 +36,14 @@ Fermi is a global state management solution for Dioxus that's as easy as `use_st
Inspired by atom-based state management solutions, all state in Fermi starts as an `atom`:
```rust
```rust, ignore
static NAME: Atom<&str> = |_| "Dioxus";
```
From anywhere in our app, we can read our the value of our atom:
```rust
fn NameCard(cx: Scope) -> Element {
```rust, ignore
fn NameCard(cx: Scope) -> Element {
let name = use_read(&cx, NAME);
cx.render(rsx!{ h1 { "Hello, {name}"} })
}
@@ -51,8 +51,8 @@ fn NameCard(cx: Scope) -> Element {
We can also set the value of our atom, also from anywhere in our app:
```rust
fn NameCard(cx: Scope) -> Element {
```rust, ignore
fn NameCard(cx: Scope) -> Element {
let set_name = use_set(&cx, NAME);
cx.render(rsx!{
button {
@@ -68,7 +68,7 @@ It's that simple!
## Installation
Fermi is currently under construction, so you have to use the `master` branch to get started.
```rust
```toml
[dependencies]
fermi = { git = "https://github.com/dioxuslabs/fermi" }
```
@@ -89,4 +89,4 @@ Broadly our feature set to required to be released includes:
- [ ] Support for Atom Families
- [ ] Support for memoized Selectors
- [ ] Support for memoized SelectorFamilies
- [ ] Support for UseFermiCallback for access to fermi from async
- [ ] Support for UseFermiCallback for access to fermi from async
+3 -3
View File
@@ -124,7 +124,7 @@ impl<T: 'static> AtomState<T> {
/// # Examples
///
/// Basic usage:
/// ```rust
/// ```rust, ignore
/// # use dioxus_core::prelude::*;
/// # use dioxus_hooks::*;
/// fn component(cx: Scope) -> Element {
@@ -219,7 +219,7 @@ impl<T: Clone> AtomState<T> {
///
/// # Examples
///
/// ```
/// ```ignore
/// let val = use_state(&cx, || 0);
///
/// val.with_mut(|v| *v = 1);
@@ -242,7 +242,7 @@ impl<T: Clone> AtomState<T> {
///
/// # Examples
///
/// ```
/// ```ignore
/// let val = use_state(&cx, || 0);
///
/// *val.make_mut() += 1;
+1 -1
View File
@@ -36,7 +36,7 @@ use std::future::Future;
///
/// ## Example
///
/// ```rust
/// ```rust, ignore
/// enum Action {
/// Start,
/// Stop,
+10 -10
View File
@@ -16,7 +16,7 @@ use std::{
/// writes through the `write` method. Whenever `write` is called, the component
/// that initialized the hook will be marked as "dirty".
///
/// ```rust
/// ```rust, ignore
/// let val = use_ref(|| HashMap::<u32, String>::new());
///
/// // using `write` will give us a `RefMut` to the inner value, which we can call methods on
@@ -26,7 +26,7 @@ use std::{
///
/// You can avoid this default behavior with `write_silent`
///
/// ```
/// ```ignore
/// // with `write_silent`, the component will not be re-rendered
/// val.write_silent().insert(2, "goodbye".to_string());
/// ```
@@ -35,7 +35,7 @@ use std::{
///
/// To read values out of the refcell, you can use the `read` method which will retrun a `Ref`.
///
/// ```rust
/// ```rust, ignore
/// let map: Ref<_> = val.read();
///
/// let item = map.get(&1);
@@ -43,7 +43,7 @@ use std::{
///
/// To get an &T out of the RefCell, you need to "reborrow" through the Ref:
///
/// ```rust
/// ```rust, ignore
/// let read = val.read();
/// let map = &*read;
/// ```
@@ -54,7 +54,7 @@ use std::{
/// Typically this will be a collection like a HashMap or a Vec. To create new
/// elements from the collection, we can use `read()` directly in our rsx!.
///
/// ```rust
/// ```rust, ignore
/// rsx!{
/// val.read().iter().map(|(k, v)| {
/// rsx!{ key: "{k}", value: "{v}" }
@@ -66,7 +66,7 @@ use std::{
/// "render" inside the iterator. For some cases you might need to collect into
/// a temporary Vec.
///
/// ```rust
/// ```rust, ignore
/// let items = val.read().iter().map(|(k, v)| {
/// cx.render(rsx!{ key: "{k}", value: "{v}" })
/// });
@@ -82,7 +82,7 @@ use std::{
/// from the current scope's lifetime, making it a `'static` value. This is done
/// by simply calling `ToOnwed` or `Clone`.
///
/// ```rust
/// ```rust, ignore
/// let val = use_ref(|| HashMap::<u32, String>::new());
///
/// cx.spawn({
@@ -97,7 +97,7 @@ use std::{
/// If you're working with lots of values like UseState and UseRef, you can use the
/// `clone!` macro to make it easier to write the above code.
///
/// ```rust
/// ```rust, ignore
/// let val1 = use_ref(|| HashMap::<u32, String>::new());
/// let val2 = use_ref(|| HashMap::<u32, String>::new());
/// let val3 = use_ref(|| HashMap::<u32, String>::new());
@@ -194,7 +194,7 @@ impl<T> UseRef<T> {
/// Note: You can always "reborrow" the value through the RefCell.
/// This method just does it for you automatically.
///
/// ```rust
/// ```rust, ignore
/// let val = use_ref(|| HashMap::<u32, String>::new());
///
///
@@ -214,7 +214,7 @@ impl<T> UseRef<T> {
/// Note: You can always "reborrow" the value through the RefCell.
/// This method just does it for you automatically.
///
/// ```rust
/// ```rust, ignore
/// let val = use_ref(|| HashMap::<u32, String>::new());
///
///
+3 -3
View File
@@ -140,7 +140,7 @@ impl<T: 'static> UseState<T> {
/// # Examples
///
/// Basic usage:
/// ```rust
/// ```rust, ignore
/// # use dioxus_core::prelude::*;
/// # use dioxus_hooks::*;
/// fn component(cx: Scope) -> Element {
@@ -236,7 +236,7 @@ impl<T: Clone> UseState<T> {
///
/// # Examples
///
/// ```
/// ```rust, ignore
/// let val = use_state(&cx, || 0);
///
/// val.with_mut(|v| *v = 1);
@@ -268,7 +268,7 @@ impl<T: Clone> UseState<T> {
///
/// # Examples
///
/// ```
/// ```rust, ignore
/// let val = use_state(&cx, || 0);
///
/// *val.make_mut() += 1;
+3 -3
View File
@@ -181,7 +181,7 @@ builder_constructors! {
///
/// # Usage
///
/// ```
/// ```rust, ignore
/// html!(<h1> A header element </h1>)
/// rsx!(h1 { "A header element" })
/// LazyNodes::new(|f| f.el(h1).children([f.text("A header element")]).finish())
@@ -201,7 +201,7 @@ builder_constructors! {
/// - The `<h2>` heading is usually a large bolded font.
///
/// # Usage
/// ```
/// ```rust, ignore
/// html!(<h2> A header element </h2>)
/// rsx!(h2 { "A header element" })
/// LazyNodes::new(|f| f.el(h2).children([f.text("A header element")]).finish())
@@ -274,7 +274,7 @@ builder_constructors! {
/// Note: By default, browsers always place a line break before and after the <div> element.
///
/// ## Usage
/// ```
/// ```rust, ignore
/// html!(<div> A header element </div>)
/// rsx!(div { "A header element" })
/// LazyNodes::new(|f| f.element(div, &[], &[], &[], None))
+1 -1
View File
@@ -193,7 +193,7 @@ pub mod on {
/// `click` fires after both the `mousedown` and `mouseup` events have fired, in that order.
///
/// ## Example
/// ```
/// ```rust, ignore
/// rsx!( button { "click me", onclick: move |_| log::info!("Clicked!`") } )
/// ```
///
+1 -1
View File
@@ -39,7 +39,7 @@ tower = { version = "0.4.12", optional = true }
[dev-dependencies]
tokio = { version = "1", features = ["full"] }
dioxus = { path = "../../" }
dioxus = { path = "../dioxus" }
warp = "0.3"
axum = { version = "0.5.1", features = ["ws"] }
tower = "0.4.12"
+9 -8
View File
@@ -1,12 +1,17 @@
#![cfg(feature = "axum")]
use axum::{extract::ws::WebSocketUpgrade, response::Html, routing::get, Router};
use dioxus_core::{Element, LazyNodes, Scope};
#[cfg(not(feature = "axum"))]
fn main() {}
#[cfg(feature = "axum")]
#[tokio::main]
async fn main() {
use axum::{extract::ws::WebSocketUpgrade, response::Html, routing::get, Router};
use dioxus_core::{Element, LazyNodes, Scope};
pretty_env_logger::init();
fn app(cx: Scope) -> Element {
cx.render(LazyNodes::new(|f| f.text(format_args!("hello world!"))))
}
let addr: std::net::SocketAddr = ([127, 0, 0, 1], 3030).into();
let view = dioxus_liveview::new(addr);
@@ -27,7 +32,3 @@ async fn main() {
.await
.unwrap();
}
fn app(cx: Scope) -> Element {
cx.render(LazyNodes::new(|f| f.text(format_args!("hello world!"))))
}
+12 -10
View File
@@ -1,12 +1,18 @@
#![cfg(feature = "warp")]
use dioxus_core::{Element, LazyNodes, Scope};
use dioxus_liveview as liveview;
use warp::ws::Ws;
use warp::Filter;
#[cfg(not(feature = "warp"))]
fn main() {}
#[cfg(feature = "warp")]
#[tokio::main]
async fn main() {
use dioxus_core::{Element, LazyNodes, Scope};
use dioxus_liveview as liveview;
use warp::ws::Ws;
use warp::Filter;
fn app(cx: Scope) -> Element {
cx.render(LazyNodes::new(|f| f.text(format_args!("hello world!"))))
}
pretty_env_logger::init();
let addr = ([127, 0, 0, 1], 3030);
@@ -27,7 +33,3 @@ async fn main() {
}));
warp::serve(routes).run(addr).await;
}
fn app(cx: Scope) -> Element {
cx.render(LazyNodes::new(|f| f.text(format_args!("hello world!"))))
}
+1 -1
View File
@@ -59,7 +59,7 @@ Edit your `lib.rs`:
use dioxus::prelude::*;
fn main() {
dioxus::mobile::launch(app);
dioxus_mobile::launch(app);
}
fn app(cx: Scope) -> Element {
+1 -1
View File
@@ -20,7 +20,7 @@ quote = "1.0"
dioxus-native-core = { path = "../native-core", version = "^0.2.0" }
[dev-dependencies]
dioxus = { path = "../rsx-prelude", version = "^0.1.0", package = "rsx-prelude" }
dioxus = { path = "../dioxus" }
smallvec = "1.6"
fxhash = "0.2"
+1 -1
View File
@@ -83,7 +83,7 @@ pub trait ParentDepState {
/// Called when the current node's node properties are modified or a sibling's [NodeDepState] is modified.
/// Called at most once per update.
/// NodeDepState is the only state that can accept multiple dependancies, but only from the current node.
/// ```rust
/// ```rust, ignore
/// impl<'a, 'b> NodeDepState<(&'a TextWrap, &'b ChildLayout)> for Layout {
/// type Ctx = LayoutCache;
/// const NODE_MASK: NodeMask =
+3 -6
View File
@@ -11,7 +11,7 @@ keywords = ["dom", "ui", "gui", "react", "wasm"]
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
dioxus = { path = "../rsx-prelude", version = "^0.1.0", package = "rsx-prelude" }
dioxus = { path = "../dioxus" }
futures-channel = "0.3.21"
url = { version = "2.2.2", default-features = false }
@@ -26,7 +26,7 @@ web-sys = { version = "0.3", features = [
"Url",
"UrlSearchParams",
"Window",
"Location"
"Location",
], optional = true }
wasm-bindgen = { version = "0.2", optional = true }
js-sys = { version = "0.3", optional = true }
@@ -46,14 +46,11 @@ hot-reload = ["dioxus/hot-reload"]
[dev-dependencies]
console_error_panic_hook = "0.1.7"
dioxus-web = { path = "../web" }
log = "0.4.14"
wasm-logger = "0.2.0"
wasm-bindgen-test = "0.3"
gloo-utils = "0.1.2"
dioxus-ssr = { path = "../ssr" }
dioxus-router = { path = ".", default-features = false }
dioxus-web = { path = "../web" }
[target.wasm32-unknown-unknown.dev-dependencies]
dioxus-router = { path = ".", features = ["web"] }
-2
View File
@@ -4,8 +4,6 @@ use dioxus::prelude::*;
use dioxus_router::*;
fn main() {
console_error_panic_hook::set_once();
wasm_logger::init(wasm_logger::Config::new(log::Level::Debug));
dioxus_web::launch(app);
}
+3 -3
View File
@@ -120,15 +120,15 @@ pub fn Link<'a>(cx: Scope<'a, LinkProps<'a>>) -> Element {
id: format_args!("{}", id.unwrap_or("")),
title: format_args!("{}", title.unwrap_or("")),
prevent_default: "{prevent_default}",
target: format_args!("{}", if *new_tab { "_blank" } else { "" }),
target: format_args!("{}", if * new_tab { "_blank" } else { "" }),
onclick: move |_| {
if !outerlink {
if let Some(service) = svc {
service.push_route(to, cx.props.title.map(|f| f.to_string()), None);
} else {
log::error!(
"Attempted to create a Link to {} outside of a Router context",
cx.props.to,
"Attempted to create a Link to {} outside of a Router context", cx.props
.to,
);
}
}
+4 -74
View File
@@ -1,7 +1,5 @@
use crate::ParsedRoute;
use crate::{cfg::RouterCfg, RouteEvent, RouterCore};
use crate::{cfg::RouterCfg, RouterCore};
use dioxus::prelude::*;
use futures_util::stream::StreamExt;
use std::sync::Arc;
/// The props for the [`Router`](fn.Router.html) component.
@@ -40,81 +38,13 @@ pub struct RouterProps<'a> {
#[allow(non_snake_case)]
pub fn Router<'a>(cx: Scope<'a, RouterProps<'a>>) -> Element {
let svc = cx.use_hook(|_| {
let (tx, mut rx) = futures_channel::mpsc::unbounded::<RouteEvent>();
let svc = RouterCore::new(
tx,
cx.provide_context(RouterCore::new(
&cx,
RouterCfg {
base_url: cx.props.base_url.map(|s| s.to_string()),
active_class: cx.props.active_class.map(|s| s.to_string()),
},
);
cx.spawn({
let svc = svc.clone();
let regen_route = cx.schedule_update_any();
let router_id = cx.scope_id();
async move {
while let Some(msg) = rx.next().await {
match msg {
RouteEvent::Push {
route,
serialized_state,
title,
} => {
let new_route = Arc::new(ParsedRoute {
url: svc.current_location().url.join(&route).ok().unwrap(),
title,
serialized_state,
});
svc.history.push(&new_route);
svc.stack.borrow_mut().push(new_route);
}
RouteEvent::Replace {
route,
title,
serialized_state,
} => {
let new_route = Arc::new(ParsedRoute {
url: svc.current_location().url.join(&route).ok().unwrap(),
title,
serialized_state,
});
svc.history.replace(&new_route);
*svc.stack.borrow_mut().last_mut().unwrap() = new_route;
}
RouteEvent::Pop => {
let mut stack = svc.stack.borrow_mut();
if stack.len() == 1 {
continue;
}
stack.pop();
}
}
svc.route_found.set(None);
regen_route(router_id);
for listener in svc.onchange_listeners.borrow().iter() {
regen_route(*listener);
}
for route in svc.ordering.borrow().iter().rev() {
regen_route(*route);
}
}
}
});
cx.provide_context(svc)
))
});
// next time we run the rout_found will be filled
+98 -57
View File
@@ -2,9 +2,9 @@
// does each window have its own router? probably, lol
use crate::cfg::RouterCfg;
use dioxus::core::ScopeId;
use futures_channel::mpsc::UnboundedSender;
use dioxus::core::{ScopeId, ScopeState, VirtualDom};
use std::any::Any;
use std::sync::Weak;
use std::{
cell::{Cell, RefCell},
collections::{HashMap, HashSet},
@@ -45,8 +45,6 @@ pub struct RouterCore {
pub(crate) stack: RefCell<Vec<Arc<ParsedRoute>>>,
pub(crate) tx: UnboundedSender<RouteEvent>,
pub(crate) slots: Rc<RefCell<HashMap<ScopeId, String>>>,
pub(crate) ordering: Rc<RefCell<Vec<ScopeId>>>,
@@ -55,6 +53,10 @@ pub struct RouterCore {
pub(crate) history: Box<dyn RouterProvider>,
pub(crate) regen_any_route: Arc<dyn Fn(ScopeId)>,
pub(crate) router_id: ScopeId,
pub(crate) cfg: RouterCfg,
}
@@ -74,41 +76,38 @@ pub struct ParsedRoute {
pub serialized_state: Option<String>,
}
#[derive(Debug)]
pub(crate) enum RouteEvent {
Push {
route: String,
title: Option<String>,
serialized_state: Option<String>,
},
Replace {
route: String,
title: Option<String>,
serialized_state: Option<String>,
},
Pop,
}
impl RouterCore {
pub(crate) fn new(tx: UnboundedSender<RouteEvent>, cfg: RouterCfg) -> Arc<Self> {
pub(crate) fn new(cx: &ScopeState, cfg: RouterCfg) -> Arc<Self> {
#[cfg(feature = "web")]
let history = Box::new(web::new(tx.clone()));
let history = Box::new(web::new());
#[cfg(not(feature = "web"))]
let history = Box::new(hash::new());
let route = Arc::new(history.init_location());
Arc::new(Self {
let svc = Arc::new(Self {
cfg,
tx,
regen_any_route: cx.schedule_update_any(),
router_id: cx.scope_id(),
route_found: Cell::new(None),
stack: RefCell::new(vec![route]),
ordering: Default::default(),
slots: Default::default(),
onchange_listeners: Default::default(),
history,
})
});
svc.history.attach_listeners(Arc::downgrade(&svc));
svc
}
/// Push a new route with no custom title or serialized state.
///
/// This is a convenience method for easily navigating.
pub fn navigate_to(&self, route: &str) {
self.push_route(route, None, None);
}
/// Push a new route to the history.
@@ -117,16 +116,16 @@ impl RouterCore {
///
/// This does not modify the current route
pub fn push_route(&self, route: &str, title: Option<String>, serialized_state: Option<String>) {
let _ = self.tx.unbounded_send(RouteEvent::Push {
route: route.to_string(),
let new_route = Arc::new(ParsedRoute {
url: self.current_location().url.join(route).ok().unwrap(),
title,
serialized_state,
});
}
/// Pop the current route from the history.
pub fn pop_route(&self) {
let _ = self.tx.unbounded_send(RouteEvent::Pop);
self.history.push(&new_route);
self.stack.borrow_mut().push(new_route);
self.regen_routes();
}
/// Instead of pushing a new route, replaces the current route.
@@ -136,11 +135,44 @@ impl RouterCore {
title: Option<String>,
serialized_state: Option<String>,
) {
let _ = self.tx.unbounded_send(RouteEvent::Replace {
route: route.to_string(),
let new_route = Arc::new(ParsedRoute {
url: self.current_location().url.join(route).ok().unwrap(),
title,
serialized_state,
});
self.history.replace(&new_route);
*self.stack.borrow_mut().last_mut().unwrap() = new_route;
self.regen_routes();
}
/// Pop the current route from the history.
pub fn pop_route(&self) {
let mut stack = self.stack.borrow_mut();
if stack.len() > 1 {
stack.pop();
}
self.regen_routes();
}
/// Regenerate any routes that need to be regenerated, discarding the currently found route
///
/// You probably don't need this method
pub fn regen_routes(&self) {
self.route_found.set(None);
(self.regen_any_route)(self.router_id);
for listener in self.onchange_listeners.borrow().iter() {
(self.regen_any_route)(*listener);
}
for route in self.ordering.borrow().iter().rev() {
(self.regen_any_route)(*route);
}
}
/// Get the current location of the Router
@@ -196,6 +228,20 @@ impl RouterCore {
}
}
/// Get the router service from an existing VirtualDom.
///
/// Takes an optional target_scope parameter to specify the scope to use if ScopeId is not the component
/// that owns the router.
///
/// This might change in the future.
pub fn get_router_from_vdom(
dom: &VirtualDom,
target_scope: Option<ScopeId>,
) -> Option<Arc<RouterCore>> {
dom.get_scope(target_scope.unwrap_or(ScopeId(0)))
.and_then(|scope| scope.consume_context::<Arc<RouterCore>>())
}
fn clean_route(route: String) -> String {
if route.as_str() == "/" {
return route;
@@ -236,8 +282,6 @@ fn route_matches_path(cur: &Url, attempt: &str, base_url: Option<&String>) -> bo
let attempt_pieces = clean_path(attempt).split('/').collect::<Vec<_>>();
log::trace!("Comparing {:?} to {:?}", cur_pieces, attempt_pieces);
if attempt_pieces.len() != cur_pieces.len() {
return false;
}
@@ -262,6 +306,7 @@ pub(crate) trait RouterProvider {
fn replace(&self, route: &ParsedRoute);
fn native_location(&self) -> Box<dyn Any>;
fn init_location(&self) -> ParsedRoute;
fn attach_listeners(&self, svc: Weak<RouterCore>);
}
#[cfg(not(feature = "web"))]
@@ -291,22 +336,23 @@ mod hash {
}
fn replace(&self, _route: &ParsedRoute) {}
fn attach_listeners(&self, _svc: Weak<RouterCore>) {}
}
}
#[cfg(feature = "web")]
mod web {
use super::RouterProvider;
use crate::{ParsedRoute, RouteEvent};
use crate::ParsedRoute;
use futures_channel::mpsc::UnboundedSender;
use gloo_events::EventListener;
use std::any::Any;
use std::{any::Any, cell::Cell};
use web_sys::History;
pub struct WebRouter {
// keep it around so it drops when the router is dropped
_listener: gloo_events::EventListener,
_listener: Cell<Option<gloo_events::EventListener>>,
window: web_sys::Window,
history: History,
@@ -358,30 +404,25 @@ mod web {
serialized_state: None,
}
}
fn attach_listeners(&self, svc: std::sync::Weak<crate::RouterCore>) {
self._listener.set(Some(EventListener::new(
&web_sys::window().unwrap(),
"popstate",
move |_| {
if let Some(svc) = svc.upgrade() {
svc.pop_route();
}
},
)));
}
}
pub(crate) fn new(tx: UnboundedSender<RouteEvent>) -> WebRouter {
pub(crate) fn new() -> WebRouter {
WebRouter {
history: web_sys::window().unwrap().history().unwrap(),
window: web_sys::window().unwrap(),
_listener: EventListener::new(&web_sys::window().unwrap(), "popstate", move |_| {
let _ = tx.unbounded_send(RouteEvent::Pop);
}),
_listener: Cell::new(None),
}
}
}
/*
Route { to: "/blog/:id" }
Route { to: "/blog/:id" }
Route { to: "/blog/:id" }
Route { to: "/blog/:id" }
Route { to: "/blog/:id" }
*/
-28
View File
@@ -1,28 +0,0 @@
#![allow(non_snake_case)]
use dioxus::prelude::*;
use dioxus_router::*;
#[test]
fn generates_without_error() {
let mut app = VirtualDom::new(app);
app.rebuild();
let out = dioxus_ssr::render_vdom(&app);
assert_eq!(out, "<nav>navbar</nav>default<!--placeholder-->");
}
fn app(cx: Scope) -> Element {
cx.render(rsx! {
Router {
nav { "navbar" }
Route { to: "/", "default" }
Route { to: "/home", Home {} }
}
})
}
fn Home(cx: Scope) -> Element {
cx.render(rsx! { h1 { "Home" } })
}
+3 -3
View File
@@ -73,10 +73,10 @@ If the active class is given on both, the `Router` and the `Link`, the one on th
Router {
active_class: "custom-active", // All active links in this router get this class.
Link { to: "/", "Home" },
Link {
Link {
to: "/blog",
active_class: "is-active", // Only for this Link. Overwrites "custom-active" from Router.
"Blog"
"Blog"
},
}
```
@@ -207,7 +207,7 @@ let mut prerendered_pages = dom
let _ = dom.rebuild();
// render the page and insert it into our map
(route, dioxus::ssr::render_vdom(&dom))
(route, dioxus_ssr::render_vdom(&dom))
})
.collect::<HashMap<_, _>>();
```
-22
View File
@@ -1,22 +0,0 @@
[package]
name = "rsx-prelude"
version = "0.1.0"
authors = ["Jonathan Kelley"]
edition = "2021"
description = "Basic functionality for the dioxus rsx macro."
license = "MIT OR Apache-2.0"
repository = "https://github.com/DioxusLabs/dioxus/"
homepage = "https://dioxuslabs.com"
documentation = "https://dioxuslabs.com"
keywords = ["dom", "ui", "gui", "react", "wasm"]
rust-version = "1.60.0"
[dependencies]
dioxus-core-macro = { path = "../core-macro", version = "^0.2.1" }
dioxus-core = { path = "../core", version = "^0.2.1" }
dioxus-html = { path = "../html", version = "^0.2.1" }
dioxus-rsx-interpreter = { path = "../rsx_interpreter", version = "^0.1.0", optional = true }
dioxus-hooks = { path = "../hooks", version = "^0.2.1" }
[features]
hot-reload = ["dioxus-core-macro/hot-reload", "dioxus-rsx-interpreter"]
+1 -1
View File
@@ -211,7 +211,7 @@ pub enum ElementAttr {
}
impl ElementAttr {
pub fn start(&self) -> Span {
pub fn flart(&self) -> Span {
match self {
ElementAttr::AttrText { name, .. } => name.span(),
ElementAttr::AttrExpression { name, .. } => name.span(),
+1 -1
View File
@@ -21,4 +21,4 @@ dioxus-hooks = { path = "../hooks"}
[dev-dependencies]
dioxus-core-macro = { path = "../core-macro" }
bumpalo = { version = "3.6", features = ["collections", "boxed"] }
dioxus = { path = "../rsx-prelude", version = "^0.1.0", package = "rsx-prelude", features = ["hot-reload"] }
dioxus = { path = "../dioxus", features = ["hot-reload"]}
+3 -3
View File
@@ -190,7 +190,7 @@ builder_constructors! {
///
/// # Usage
///
/// ```
/// ```rust, ignore
/// html!(<h1> A header element </h1>)
/// rsx!(h1 { "A header element" })
/// LazyNodes::new(|f| f.el(h1).children([f.text("A header element")]).finish())
@@ -210,7 +210,7 @@ builder_constructors! {
/// - The `<h2>` heading is usually a large bolded font.
///
/// # Usage
/// ```
/// ```rust, ignore
/// html!(<h2> A header element </h2>)
/// rsx!(h2 { "A header element" })
/// LazyNodes::new(|f| f.el(h2).children([f.text("A header element")]).finish())
@@ -283,7 +283,7 @@ builder_constructors! {
/// Note: By default, browsers always place a line break before and after the <div> element.
///
/// ## Usage
/// ```
/// ```rust, ignore
/// html!(<div> A header element </div>)
/// rsx!(div { "A header element" })
/// LazyNodes::new(|f| f.element(div, &[], &[], &[], None))
+1 -1
View File
@@ -16,7 +16,7 @@ keywords = ["dom", "ui", "gui", "react", "wasm"]
dioxus-core = { path = "../core", version = "^0.2.1", features = ["serialize"] }
[dev-dependencies]
dioxus = { path = "../rsx-prelude", version = "^0.1.0", package = "rsx-prelude" }
dioxus = { path = "../dioxus" }
thiserror = "1.0.23"
log = "0.4.13"
fern = { version = "0.6.0", features = ["colored"] }
+8 -8
View File
@@ -23,7 +23,7 @@ let app: Component = |cx| cx.render(rsx!(div {"hello world!"}));
let mut vdom = VirtualDom::new(app);
let _ = vdom.rebuild();
let text = dioxus::ssr::render_vdom(&vdom);
let text = dioxus_ssr::render_vdom(&vdom);
assert_eq!(text, "<div>hello world!</div>")
```
@@ -33,7 +33,7 @@ assert_eq!(text, "<div>hello world!</div>")
The simplest example is to simply render some `rsx!` nodes to html. This can be done with the [`render_lazy`] api.
```rust, ignore
let content = dioxus::ssr::render(rsx!{
let content = dioxus_ssr::render(rsx!{
div {
(0..5).map(|i| rsx!(
"Number: {i}"
@@ -48,14 +48,14 @@ let content = dioxus::ssr::render(rsx!{
let mut dom = VirtualDom::new(app);
let _ = dom.rebuild();
let content = dioxus::ssr::render_vdom(&dom);
let content = dioxus_ssr::render_vdom(&dom);
```
## Configuring output
It's possible to configure the output of the generated HTML.
```rust, ignore
let content = dioxus::ssr::render_vdom(&dom, |config| config.pretty(true).prerender(true));
let content = dioxus_ssr::render_vdom(&dom, |config| config.pretty(true).prerender(true));
```
## Usage as a writer
@@ -70,7 +70,7 @@ let mut buf = String::new();
let dom = VirtualDom::new(app);
let _ = dom.rebuild();
let args = dioxus::ssr::formatter(dom, |config| config);
let args = dioxus_ssr::formatter(dom, |config| config);
buf.write_fmt!(format_args!("{}", args));
```
@@ -95,7 +95,7 @@ To enable pre-rendering, simply configure the `SsrConfig` with pre-rendering ena
```rust, ignore
let dom = VirtualDom::new(App);
let text = dioxus::ssr::render_vdom(App, |cfg| cfg.pre_render(true));
let text = dioxus_ssr::render_vdom(App, |cfg| cfg.pre_render(true));
```
## Usage in server-side rendering
@@ -103,7 +103,7 @@ let text = dioxus::ssr::render_vdom(App, |cfg| cfg.pre_render(true));
Dioxus SSR can also be to render on the server. Obviously, you can just render the VirtualDOM to a string and send that down.
```rust, ignore
let text = dioxus::ssr::render_vdom(&vdom);
let text = dioxus_ssr::render_vdom(&vdom);
assert_eq!(text, "<div>hello world!</div>")
```
@@ -114,7 +114,7 @@ The rest of the space - IE doing this more efficiently, caching the virtualdom,
Dioxus SSR needs an arena to allocate from - whether it be the VirtualDom or a dedicated Bump allocator. To render `rsx!` directly to a string, you'll want to create an `SsrRenderer` and call `render_lazy`.
```rust, ignore
let text = dioxus::ssr::SsrRenderer::new().render_lazy(rsx!{
let text = dioxus_ssr::SsrRenderer::new().render_lazy(rsx!{
div { "hello world" }
});
assert_eq!(text, "<div>hello world!</div>")
+7 -2
View File
@@ -29,5 +29,10 @@ fxhash = "0.2"
anymap = "0.12.1"
[dev-dependencies]
dioxus = { path = "../rsx-prelude", version = "^0.1.0", package = "rsx-prelude" }
tokio = { version = "1" }
dioxus = { path = "../dioxus" }
tokio = { version = "1" }
criterion = "0.3.5"
[[bench]]
name = "update"
harness = false
+220
View File
@@ -0,0 +1,220 @@
use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion};
use dioxus::prelude::*;
use dioxus_tui::{Config, TuiContext};
criterion_group!(mbenches, tui_update);
criterion_main!(mbenches);
/// This benchmarks the cache performance of the TUI for small edits by changing one box at a time.
fn tui_update(c: &mut Criterion) {
let mut group = c.benchmark_group("Update boxes");
// We can also use loops to define multiple benchmarks, even over multiple dimensions.
for size in 1..=8u32 {
let parameter_string = format!("{}", (3 * size).pow(2));
group.bench_with_input(
BenchmarkId::new("size", parameter_string),
&size,
|b, size| {
b.iter(|| match size {
1 => dioxus_tui::launch_cfg(app3, Config::default().with_headless()),
2 => dioxus_tui::launch_cfg(app6, Config::default().with_headless()),
3 => dioxus_tui::launch_cfg(app9, Config::default().with_headless()),
4 => dioxus_tui::launch_cfg(app12, Config::default().with_headless()),
5 => dioxus_tui::launch_cfg(app15, Config::default().with_headless()),
6 => dioxus_tui::launch_cfg(app18, Config::default().with_headless()),
7 => dioxus_tui::launch_cfg(app21, Config::default().with_headless()),
8 => dioxus_tui::launch_cfg(app24, Config::default().with_headless()),
_ => (),
})
},
);
}
}
#[derive(Props, PartialEq)]
struct BoxProps {
x: usize,
y: usize,
hue: f32,
alpha: f32,
}
#[allow(non_snake_case)]
fn Box(cx: Scope<BoxProps>) -> Element {
let count = use_state(&cx, || 0);
let x = cx.props.x * 2;
let y = cx.props.y * 2;
let hue = cx.props.hue;
let display_hue = cx.props.hue as u32 / 10;
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{"{display_hue:03}"}
}
})
}
#[derive(Props, PartialEq)]
struct GridProps {
size: usize,
}
#[allow(non_snake_case)]
fn Grid(cx: Scope<GridProps>) -> Element {
let size = cx.props.size;
let count = use_state(&cx, || 0);
let counts = use_ref(&cx, || vec![0; size * size]);
let ctx: TuiContext = cx.consume_context().unwrap();
if *count.get() + 1 >= (size * size) {
ctx.quit();
} else {
counts.with_mut(|c| {
let i = *count.current();
c[i] += 1;
c[i] %= 360;
});
count.with_mut(|i| {
*i += 1;
*i %= size * size;
});
}
cx.render(rsx! {
div{
width: "100%",
height: "100%",
flex_direction: "column",
(0..size).map(|x|
{
cx.render(rsx! {
div{
width: "100%",
height: "100%",
flex_direction: "row",
(0..size).map(|y|
{
let alpha = y as f32*100.0/size as f32 + counts.read()[x*size + y] as f32;
let key = format!("{}-{}", x, y);
cx.render(rsx! {
Box{
x: x,
y: y,
alpha: 100.0,
hue: alpha,
key: "{key}",
}
})
}
)
}
})
}
)
}
})
}
fn app3(cx: Scope) -> Element {
cx.render(rsx! {
div{
width: "100%",
height: "100%",
Grid{
size: 3,
}
}
})
}
fn app6(cx: Scope) -> Element {
cx.render(rsx! {
div{
width: "100%",
height: "100%",
Grid{
size: 6,
}
}
})
}
fn app9(cx: Scope) -> Element {
cx.render(rsx! {
div{
width: "100%",
height: "100%",
Grid{
size: 9,
}
}
})
}
fn app12(cx: Scope) -> Element {
cx.render(rsx! {
div{
width: "100%",
height: "100%",
Grid{
size: 12,
}
}
})
}
fn app15(cx: Scope) -> Element {
cx.render(rsx! {
div{
width: "100%",
height: "100%",
Grid{
size: 15,
}
}
})
}
fn app18(cx: Scope) -> Element {
cx.render(rsx! {
div{
width: "100%",
height: "100%",
Grid{
size: 18,
}
}
})
}
fn app21(cx: Scope) -> Element {
cx.render(rsx! {
div{
width: "100%",
height: "100%",
Grid{
size: 21,
}
}
})
}
fn app24(cx: Scope) -> Element {
cx.render(rsx! {
div{
width: "100%",
height: "100%",
Grid{
size: 24,
}
}
})
}
+85
View File
@@ -0,0 +1,85 @@
use dioxus::prelude::*;
use dioxus_html::on::{FocusData, KeyboardData, MouseData, WheelData};
use std::sync::Arc;
fn main() {
dioxus_tui::launch(app);
}
#[derive(Debug)]
enum Event {
MouseMove(Arc<MouseData>),
MouseClick(Arc<MouseData>),
MouseDoubleClick(Arc<MouseData>),
MouseDown(Arc<MouseData>),
MouseUp(Arc<MouseData>),
Wheel(Arc<WheelData>),
KeyDown(Arc<KeyboardData>),
KeyUp(Arc<KeyboardData>),
KeyPress(Arc<KeyboardData>),
FocusIn(Arc<FocusData>),
FocusOut(Arc<FocusData>),
}
const MAX_EVENTS: usize = 8;
fn app(cx: Scope) -> Element {
let events = use_ref(&cx, Vec::new);
let events_lock = events.read();
let first_index = events_lock.len().saturating_sub(MAX_EVENTS);
let events_rendered = events_lock[first_index..].iter().map(|event| {
// TUI panics if text overflows (https://github.com/DioxusLabs/dioxus/issues/371)
// temporary hack: just trim the strings (and make sure viewport is big enough)
// todo: remove
let mut trimmed = format!("{event:?}");
trimmed.truncate(200);
cx.render(rsx!(p { "{trimmed}" }))
});
let log_event = move |event: Event| {
events.write().push(event);
};
cx.render(rsx! {
div {
width: "100%",
height: "100%",
flex_direction: "column",
div {
width: "80%",
height: "50%",
border_width: "1px",
justify_content: "center",
align_items: "center",
background_color: "hsl(248, 53%, 58%)",
onmousemove: move |event| log_event(Event::MouseMove(event.data)),
onclick: move |event| log_event(Event::MouseClick(event.data)),
ondblclick: move |event| log_event(Event::MouseDoubleClick(event.data)),
onmousedown: move |event| log_event(Event::MouseDown(event.data)),
onmouseup: move |event| log_event(Event::MouseUp(event.data)),
onwheel: move |event| log_event(Event::Wheel(event.data)),
onkeydown: move |event| log_event(Event::KeyDown(event.data)),
onkeyup: move |event| log_event(Event::KeyUp(event.data)),
onkeypress: move |event| log_event(Event::KeyPress(event.data)),
onfocusin: move |event| log_event(Event::FocusIn(event.data)),
onfocusout: move |event| log_event(Event::FocusOut(event.data)),
"Hover, click, type or scroll to see the info down below"
},
div {
width: "80%",
height: "50%",
flex_direction: "column",
events_rendered,
},
},
})
}
+27
View File
@@ -0,0 +1,27 @@
use dioxus::prelude::*;
fn main() {
dioxus_tui::launch(app);
}
fn app(cx: Scope) -> Element {
let radius = use_state(&cx, || 0);
cx.render(rsx! {
div {
width: "100%",
height: "100%",
justify_content: "center",
align_items: "center",
background_color: "hsl(248, 53%, 58%)",
onwheel: move |w| radius.modify(|r| (r + w.delta().strip_units().y as i8).abs()),
border_style: "solid none solid double",
border_width: "thick",
border_radius: "{radius}px",
border_color: "#0000FF #FF00FF #FF0000 #00FF00",
"{radius}"
}
})
}
+94
View File
@@ -0,0 +1,94 @@
use dioxus::prelude::*;
use dioxus_html::input_data::keyboard_types::Code;
fn main() {
dioxus_tui::launch(app);
}
#[derive(PartialEq, Props)]
struct ButtonProps {
color_offset: u32,
layer: u16,
}
#[allow(non_snake_case)]
fn Button(cx: Scope<ButtonProps>) -> Element {
let toggle = use_state(&cx, || false);
let hovered = use_state(&cx, || false);
let hue = cx.props.color_offset % 255;
let saturation = if *toggle.get() { 50 } else { 25 } + if *hovered.get() { 50 } else { 25 };
let brightness = saturation / 2;
let color = format!("hsl({hue}, {saturation}, {brightness})");
cx.render(rsx! {
div{
width: "100%",
height: "100%",
background_color: "{color}",
tabindex: "{cx.props.layer}",
onkeydown: |e| {
if let Code::Space = e.data.code() {
toggle.modify(|f| !f);
}
},
onclick: |_| {
toggle.modify(|f| !f);
},
onmouseenter: |_|{
hovered.set(true);
},
onmouseleave: |_|{
hovered.set(false);
},
justify_content: "center",
align_items: "center",
display: "flex",
flex_direction: "column",
p{"tabindex: {cx.props.layer}"}
}
})
}
fn app(cx: Scope) -> Element {
cx.render(rsx! {
div {
display: "flex",
flex_direction: "column",
width: "100%",
height: "100%",
(1..8).map(|y|
cx.render(rsx!{
div{
display: "flex",
flex_direction: "row",
width: "100%",
height: "100%",
(1..8).map(|x|{
if (x + y) % 2 == 0{
cx.render(rsx!{
div{
width: "100%",
height: "100%",
background_color: "rgb(100, 100, 100)",
}
})
}
else{
let layer = (x + y) % 3;
cx.render(rsx!{
Button{
color_offset: x * y,
layer: layer as u16,
}
})
}
})
}
})
)
}
})
}
+45
View File
@@ -0,0 +1,45 @@
use dioxus::prelude::*;
fn main() {
dioxus_tui::launch_cfg(
app,
dioxus_tui::Config::default().with_rendering_mode(dioxus_tui::RenderingMode::Ansi),
);
}
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! {
div {
left: "{x}px",
top: "{y}px",
width: "10%",
height: "100%",
background_color: "hsl({hue}, 100%, 50%, {alpha}%)",
}
})
}
)
}
})
}
)
}
})
}
+27
View File
@@ -0,0 +1,27 @@
use dioxus::prelude::*;
use dioxus_tui::query::Query;
use dioxus_tui::Size;
fn main() {
dioxus_tui::launch(app);
}
fn app(cx: Scope) -> Element {
let hue = use_state(&cx, || 0.0);
let brightness = use_state(&cx, || 0.0);
let tui_query: Query = cx.consume_context().unwrap();
cx.render(rsx! {
div{
width: "100%",
background_color: "hsl({hue}, 70%, {brightness}%)",
onmousemove: move |evt| {
let node = tui_query.get(cx.root_node().mounted_id());
let Size{width, height} = node.size().unwrap();
let pos = evt.data.element_coordinates();
hue.set((pos.x as f32/width as f32)*255.0);
brightness.set((pos.y as f32/height as f32)*100.0);
},
"hsl({hue}, 70%, {brightness}%)",
}
})
}
+66
View File
@@ -0,0 +1,66 @@
#![allow(non_snake_case)]
use dioxus::prelude::*;
fn main() {
dioxus_tui::launch(app);
}
#[derive(Props, PartialEq)]
struct QuadrentProps {
color: String,
text: String,
}
fn Quadrant(cx: Scope<QuadrentProps>) -> Element {
cx.render(rsx! {
div {
border_width: "1px",
width: "50%",
height: "100%",
background_color: "{cx.props.color}",
justify_content: "center",
align_items: "center",
"{cx.props.text}"
}
})
}
fn app(cx: Scope) -> Element {
cx.render(rsx! {
div {
width: "100%",
height: "100%",
flex_direction: "column",
div {
width: "100%",
height: "50%",
flex_direction: "row",
Quadrant{
color: "red".to_string(),
text: "[A]".to_string()
},
Quadrant{
color: "black".to_string(),
text: "[B]".to_string()
}
}
div {
width: "100%",
height: "50%",
flex_direction: "row",
Quadrant{
color: "green".to_string(),
text: "[C]".to_string()
},
Quadrant{
color: "blue".to_string(),
text: "[D]".to_string()
}
}
}
})
}
+82
View File
@@ -0,0 +1,82 @@
use dioxus::prelude::*;
fn main() {
dioxus_tui::launch(app);
}
fn app(cx: Scope) -> Element {
cx.render(rsx! {
div {
width: "100%",
height: "100%",
flex_direction: "column",
// justify_content: "center",
// align_items: "center",
// flex_direction: "row",
// background_color: "red",
p {
background_color: "black",
flex_direction: "column",
justify_content: "center",
align_items: "center",
// height: "10%",
"hi"
"hi"
"hi"
}
li {
background_color: "red",
flex_direction: "column",
justify_content: "center",
align_items: "center",
// height: "10%",
"bib"
"bib"
"bib"
"bib"
"bib"
"bib"
"bib"
"bib"
}
li {
background_color: "blue",
flex_direction: "column",
justify_content: "center",
align_items: "center",
// height: "10%",
"zib"
"zib"
"zib"
"zib"
"zib"
"zib"
"zib"
"zib"
"zib"
"zib"
"zib"
"zib"
"zib"
}
p {
background_color: "yellow",
"asd"
}
p {
background_color: "green",
"asd"
}
p {
background_color: "white",
"asd"
}
p {
background_color: "cyan",
"asd"
}
}
})
}
+128
View File
@@ -0,0 +1,128 @@
use std::{convert::TryInto, sync::Arc};
use dioxus::{events::MouseData, prelude::*};
use dioxus_core::UiEvent;
fn main() {
dioxus_tui::launch(app);
}
fn app(cx: Scope) -> Element {
fn to_str(c: &[i32; 3]) -> String {
"#".to_string() + &c.iter().map(|c| format!("{c:02X?}")).collect::<String>()
}
fn get_brightness(m: Arc<MouseData>) -> i32 {
let b: i32 = m.held_buttons().len().try_into().unwrap();
127 * b
}
let q1_color = use_state(&cx, || [200; 3]);
let q2_color = use_state(&cx, || [200; 3]);
let q3_color = use_state(&cx, || [200; 3]);
let q4_color = use_state(&cx, || [200; 3]);
let q1_color_str = to_str(q1_color);
let q2_color_str = to_str(q2_color);
let q3_color_str = to_str(q3_color);
let q4_color_str = to_str(q4_color);
let page_coordinates = use_state(&cx, || "".to_string());
let element_coordinates = use_state(&cx, || "".to_string());
let buttons = use_state(&cx, || "".to_string());
let modifiers = use_state(&cx, || "".to_string());
let update_data = move |event: UiEvent<MouseData>| {
let mouse_data = event.data;
page_coordinates.set(format!("{:?}", mouse_data.page_coordinates()));
element_coordinates.set(format!("{:?}", mouse_data.element_coordinates()));
// Note: client coordinates are also available, but they would be the same as the page coordinates in this example, because there is no scrolling.
// There are also screen coordinates, but they are currently the same as client coordinates due to technical limitations
buttons.set(format!("{:?}", mouse_data.held_buttons()));
modifiers.set(format!("{:?}", mouse_data.modifiers()));
};
cx.render(rsx! {
div {
width: "100%",
height: "100%",
flex_direction: "column",
div {
width: "100%",
height: "50%",
flex_direction: "row",
div {
border_width: "1px",
width: "50%",
height: "100%",
justify_content: "center",
align_items: "center",
background_color: "{q1_color_str}",
onmouseenter: move |m| q1_color.set([get_brightness(m.data), 0, 0]),
onmousedown: move |m| q1_color.set([get_brightness(m.data), 0, 0]),
onmouseup: move |m| q1_color.set([get_brightness(m.data), 0, 0]),
onwheel: move |w| q1_color.set([q1_color[0] + (10.0*w.delta().strip_units().y) as i32, 0, 0]),
onmouseleave: move |_| q1_color.set([200; 3]),
onmousemove: update_data,
"click me"
}
div {
width: "50%",
height: "100%",
justify_content: "center",
align_items: "center",
background_color: "{q2_color_str}",
onmouseenter: move |m| q2_color.set([get_brightness(m.data); 3]),
onmousedown: move |m| q2_color.set([get_brightness(m.data); 3]),
onmouseup: move |m| q2_color.set([get_brightness(m.data); 3]),
onwheel: move |w| q2_color.set([q2_color[0] + (10.0*w.delta().strip_units().y) as i32;3]),
onmouseleave: move |_| q2_color.set([200; 3]),
onmousemove: update_data,
"click me"
}
}
div {
width: "100%",
height: "50%",
flex_direction: "row",
div {
width: "50%",
height: "100%",
justify_content: "center",
align_items: "center",
background_color: "{q3_color_str}",
onmouseenter: move |m| q3_color.set([0, get_brightness(m.data), 0]),
onmousedown: move |m| q3_color.set([0, get_brightness(m.data), 0]),
onmouseup: move |m| q3_color.set([0, get_brightness(m.data), 0]),
onwheel: move |w| q3_color.set([0, q3_color[1] + (10.0*w.delta().strip_units().y) as i32, 0]),
onmouseleave: move |_| q3_color.set([200; 3]),
onmousemove: update_data,
"click me"
}
div {
width: "50%",
height: "100%",
justify_content: "center",
align_items: "center",
background_color: "{q4_color_str}",
onmouseenter: move |m| q4_color.set([0, 0, get_brightness(m.data)]),
onmousedown: move |m| q4_color.set([0, 0, get_brightness(m.data)]),
onmouseup: move |m| q4_color.set([0, 0, get_brightness(m.data)]),
onwheel: move |w| q4_color.set([0, 0, q4_color[2] + (10.0*w.delta().strip_units().y) as i32]),
onmouseleave: move |_| q4_color.set([200; 3]),
onmousemove: update_data,
"click me"
}
},
div {"Page coordinates: {page_coordinates}"},
div {"Element coordinates: {element_coordinates}"},
div {"Buttons: {buttons}"},
div {"Modifiers: {modifiers}"},
}
})
}
+66
View File
@@ -0,0 +1,66 @@
use dioxus::events::WheelEvent;
use dioxus::prelude::*;
use dioxus_html::geometry::ScreenPoint;
use dioxus_html::input_data::keyboard_types::Code;
use dioxus_html::input_data::MouseButtonSet;
use dioxus_html::on::{KeyboardEvent, MouseEvent};
fn main() {
dioxus_tui::launch(app);
}
fn app(cx: Scope) -> Element {
let key = use_state(&cx, || "".to_string());
let mouse = use_state(&cx, ScreenPoint::zero);
let count = use_state(&cx, || 0);
let buttons = use_state(&cx, MouseButtonSet::empty);
let mouse_clicked = use_state(&cx, || false);
let key_down_handler = move |evt: KeyboardEvent| {
match evt.data.code() {
Code::ArrowLeft => count.set(count + 1),
Code::ArrowRight => count.set(count - 1),
Code::ArrowUp => count.set(count + 10),
Code::ArrowDown => count.set(count - 10),
_ => {}
}
key.set(format!(
"{:?} repeating: {:?}",
evt.key(),
evt.is_auto_repeating()
));
};
cx.render(rsx! {
div {
width: "100%",
height: "10px",
background_color: "red",
justify_content: "center",
align_items: "center",
flex_direction: "column",
onkeydown: key_down_handler,
onwheel: move |evt: WheelEvent| {
count.set(count + evt.data.delta().strip_units().y as i64);
},
ondrag: move |evt: MouseEvent| {
mouse.set(evt.data.screen_coordinates());
},
onmousedown: move |evt: MouseEvent| {
mouse.set(evt.data.screen_coordinates());
buttons.set(evt.data.held_buttons());
mouse_clicked.set(true);
},
onmouseup: move |evt: MouseEvent| {
buttons.set(evt.data.held_buttons());
mouse_clicked.set(false);
},
"count: {count:?}",
"key: {key}",
"mouse buttons: {buttons:?}",
"mouse pos: {mouse:?}",
"mouse button pressed: {mouse_clicked}"
}
})
}
+28
View File
@@ -0,0 +1,28 @@
use dioxus::prelude::*;
fn main() {
dioxus_tui::launch(app);
}
fn app(cx: Scope) -> Element {
cx.render(rsx! {
div {
width: "100%",
height: "100%",
flex_direction: "column",
border_width: "1px",
h1 { height: "2px", color: "green",
"that's awesome!"
}
ul {
flex_direction: "column",
padding_left: "3px",
(0..10).map(|i| rsx!(
"> hello {i}"
))
}
}
})
}
+50
View File
@@ -0,0 +1,50 @@
use dioxus::prelude::*;
fn main() {
dioxus_tui::launch(app);
}
fn app(cx: Scope) -> Element {
cx.render(rsx! {
div {
width: "100%",
height: "100%",
flex_direction: "column",
background_color: "black",
// margin_right: "10px",
div {
width: "70%",
height: "70%",
background_color: "green",
// margin_left: "4px",
div {
width: "100%",
height: "100%",
margin_top: "2px",
margin_bottom: "2px",
margin_left: "2px",
margin_right: "2px",
flex_shrink: "0",
background_color: "red",
justify_content: "center",
align_items: "center",
flex_direction: "column",
// padding_top: "2px",
// padding_bottom: "2px",
// padding_left: "4px",
// padding_right: "4px",
"[A]"
"[A]"
"[A]"
"[A]"
}
}
}
})
}
+60
View File
@@ -0,0 +1,60 @@
use dioxus::prelude::*;
fn main() {
dioxus_tui::launch(app);
}
fn app(cx: Scope) -> Element {
cx.render(rsx! {
div {
width: "100%",
height: "100%",
flex_direction: "column",
div {
width: "100%",
height: "50%",
flex_direction: "row",
div {
border_width: "1px",
width: "50%",
height: "100%",
background_color: "red",
justify_content: "center",
align_items: "center",
"[A]"
}
div {
width: "50%",
height: "100%",
background_color: "black",
justify_content: "center",
align_items: "center",
"[B]"
}
}
div {
width: "100%",
height: "50%",
flex_direction: "row",
div {
width: "50%",
height: "100%",
background_color: "green",
justify_content: "center",
align_items: "center",
"[C]"
}
div {
width: "50%",
height: "100%",
background_color: "blue",
justify_content: "center",
align_items: "center",
"[D]"
}
}
}
})
}
+19
View File
@@ -0,0 +1,19 @@
use dioxus::prelude::*;
fn main() {
dioxus_tui::launch(app);
}
fn app(cx: Scope) -> Element {
cx.render(rsx! {
div {
width: "100%",
height: "10px",
background_color: "red",
justify_content: "center",
align_items: "center",
"Hello world!"
}
})
}
+32
View File
@@ -0,0 +1,32 @@
use dioxus::prelude::*;
fn main() {
dioxus_tui::launch(app);
}
fn app(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(|f| *f += 1);
tokio::time::sleep(std::time::Duration::from_millis(1000)).await;
update();
}
}
});
cx.render(rsx! {
div { width: "100%",
div { width: "50%", height: "5px", background_color: "blue", justify_content: "center", align_items: "center",
"Hello {count}!"
}
div { width: "50%", height: "10px", background_color: "red", justify_content: "center", align_items: "center",
"Hello {count}!"
}
}
})
}
+113
View File
@@ -0,0 +1,113 @@
use dioxus::prelude::*;
fn main() {
dioxus_tui::launch(app);
}
fn app(cx: Scope) -> Element {
let alpha = use_state(&cx, || 100);
cx.render(rsx! {
div {
width: "100%",
height: "100%",
flex_direction: "column",
onwheel: move |evt| alpha.set((**alpha + evt.data.delta().strip_units().y as i64).min(100).max(0)),
p {
background_color: "black",
flex_direction: "column",
justify_content: "center",
align_items: "center",
color: "green",
"hi"
"hi"
"hi"
}
li {
background_color: "red",
flex_direction: "column",
justify_content: "center",
align_items: "center",
"bib"
"bib"
"bib"
"bib"
"bib"
"bib"
"bib"
"bib"
}
li {
background_color: "blue",
flex_direction: "column",
justify_content: "center",
align_items: "center",
"zib"
"zib"
"zib"
"zib"
"zib"
"zib"
}
p {
background_color: "yellow",
"asd"
}
p {
background_color: "green",
"asd"
}
p {
background_color: "white",
"asd"
}
p {
background_color: "cyan",
"asd"
}
div {
font_weight: "bold",
color: "#666666",
p {
"bold"
}
p {
font_weight: "normal",
" normal"
}
}
p {
font_style: "italic",
color: "red",
"italic"
}
p {
text_decoration: "underline",
color: "rgba(255, 255, 255)",
"underline"
}
p {
text_decoration: "line-through",
color: "hsla(10, 100%, 70%)",
"line-through"
}
div{
position: "absolute",
top: "1px",
background_color: "rgba(255, 0, 0, 50%)",
width: "100%",
p {
color: "rgba(255, 255, 255, {alpha}%)",
background_color: "rgba(100, 100, 100, {alpha}%)",
"rgba(255, 255, 255, {alpha}%)"
}
p {
color: "rgba(255, 255, 255, 100%)",
"rgba(255, 255, 255, 100%)"
}
}
}
})
}
-5
View File
@@ -1,5 +0,0 @@
div {
div {
}
}
+3 -3
View File
@@ -17,11 +17,11 @@ use crate::Dom;
/// # Example
/// ```rust, ignore
/// use dioxus::prelude::*;
/// use dioxus::tui::query::Query;
/// use dioxus::tui::Size;
/// use dioxus_tui::query::Query;
/// use dioxus_tui::Size;
///
/// fn main() {
/// dioxus::tui::launch(app);
/// dioxus_tui::launch(app);
/// }
///
/// fn app(cx: Scope) -> Element {
+1 -1
View File
@@ -80,7 +80,7 @@ panic_hook = ["console_error_panic_hook"]
hot-reload = ["dioxus-rsx-interpreter", "web-sys/WebSocket", "web-sys/Location", "web-sys/MessageEvent", "web-sys/console", "serde_json"]
[dev-dependencies]
dioxus = { path = "../rsx-prelude", version = "^0.1.0", package = "rsx-prelude" }
dioxus = { path = "../dioxus" }
wasm-bindgen-test = "0.3.29"
dioxus-ssr = { path = "../ssr" }
wasm-logger = "0.2.0"
+1 -1
View File
@@ -5,7 +5,7 @@
/// # Example
///
/// ```rust, ignore
/// dioxus::web::launch(App, |cfg| cfg.hydrate(true).root_name("myroot"))
/// dioxus_web::launch(App, |cfg| cfg.hydrate(true).root_name("myroot"))
/// ```
pub struct WebConfig {
pub(crate) hydrate: bool,
+1 -1
View File
@@ -102,7 +102,7 @@ pub fn launch(root_component: Component) {
///
/// You can configure the WebView window with a configuration closure
///
/// ```rust
/// ```rust, ignore
/// use dioxus::prelude::*;
///
/// fn main() {