Merge branch 'upstream' into add-fuzzing-to-core

This commit is contained in:
Evan Almloff 2023-01-12 15:17:20 -06:00
commit 75932a43e0
124 changed files with 2138 additions and 1620 deletions

View file

@ -23,7 +23,7 @@ jobs:
# NOTE: Delete when the previous one is enabled
- name: Setup mdBook
run: |
cargo install mdbook --git https://github.com/Ruin0x11/mdBook.git --branch localization --rev e74fdb1
cargo install mdbook --git https://github.com/Demonthos/mdBook.git --branch master
- uses: actions/checkout@v3
- name: Build

View file

@ -104,7 +104,7 @@ jobs:
- uses: actions-rs/cargo@v1
with:
command: clippy
args: --workspace -- -D warnings
args: --workspace --examples --tests -- -D warnings
# Coverage is disabled until we can fix it
# coverage:

123
.github/workflows/miri.yml vendored Normal file
View file

@ -0,0 +1,123 @@
name: Miri Tests
on:
push:
# Run in PRs and for bors, but not on master.
branches:
- 'auto'
- 'try'
pull_request:
branches:
- 'master'
schedule:
- cron: '6 6 * * *' # At 6:06 UTC every day.
env:
CARGO_UNSTABLE_SPARSE_REGISTRY: 'true'
RUSTFLAGS: -Dwarnings
RUST_BACKTRACE: 1
# Change to specific Rust release to pin
rust_stable: stable
rust_nightly: nightly-2022-11-03
rust_clippy: 1.65.0
# When updating this, also update:
# - README.md
# - tokio/README.md
# - CONTRIBUTING.md
# - tokio/Cargo.toml
# - tokio-util/Cargo.toml
# - tokio-test/Cargo.toml
# - tokio-stream/Cargo.toml
# rust_min: 1.49.0
jobs:
test:
runs-on: ${{ matrix.os }}
env:
RUST_BACKTRACE: 1
HOST_TARGET: ${{ matrix.host_target }}
strategy:
fail-fast: false
matrix:
include:
- os: ubuntu-latest
host_target: x86_64-unknown-linux-gnu
# - os: macos-latest
# host_target: x86_64-apple-darwin
# - os: windows-latest
# host_target: i686-pc-windows-msvc
# - os: windows-latest
# host_target: i686-pc-windows-msvc
# - os: windows-latest
# host_target: i686-pc-windows-msvc
# - os: windows-latest
# host_target: i686-pc-windows-msvc
steps:
- name: Set the tag GC interval to 1 on linux
if: runner.os == 'Linux'
run: echo "MIRIFLAGS=-Zmiri-tag-gc=1" >> $GITHUB_ENV
- uses: actions/checkout@v3
- name: Install Rust ${{ env.rust_nightly }}
uses: dtolnay/rust-toolchain@master
with:
toolchain: ${{ env.rust_nightly }}
components: miri
- uses: Swatinem/rust-cache@v2
- name: miri
# Many of tests in tokio/tests and doctests use #[tokio::test] or
# #[tokio::main] that calls epoll_create1 that Miri does not support.
# run: cargo miri test --features full --lib --no-fail-fast
run: |
cargo miri test --package dioxus-core --test miri_stress -- --exact --nocapture
cargo miri test --package dioxus-native-core --test miri_native -- --exact --nocapture
# working-directory: tokio
env:
# todo: disable memory leaks ignore
MIRIFLAGS: -Zmiri-disable-isolation -Zmiri-strict-provenance -Zmiri-retag-fields -Zmiri-ignore-leaks
PROPTEST_CASES: 10
# Cache the global cargo directory, but NOT the local `target` directory which
# we cannot reuse anyway when the nightly changes (and it grows quite large
# over time).
# - name: Add cache for cargo
# id: cache
# uses: actions/cache@v3
# with:
# path: |
# # Taken from <https://doc.rust-lang.org/nightly/cargo/guide/cargo-home.html#caching-the-cargo-home-in-ci>.
# ~/.cargo/bin
# ~/.cargo/registry/index
# ~/.cargo/registry/cache
# ~/.cargo/git/db
# # contains package information of crates installed via `cargo install`.
# ~/.cargo/.crates.toml
# ~/.cargo/.crates2.json
# key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
# restore-keys: ${{ runner.os }}-cargo
# - name: Install rustup-toolchain-install-master
# if: ${{ steps.cache.outputs.cache-hit != 'true' }}
# shell: bash
# run: |
# cargo install -f rustup-toolchain-install-master
# - name: Install "master" toolchain
# shell: bash
# run: |
# if [[ ${{ github.event_name }} == 'schedule' ]]; then
# echo "Building against latest rustc git version"
# git ls-remote https://github.com/rust-lang/rust/ HEAD | cut -f 1 > rust-version
# fi
# toolchain --host ${{ matrix.host_target }}
# - name: Show Rust version
# run: |
# rustup show
# rustc -Vv
# cargo -V
# - name: Test
# run: |
# MIRIFLAGS="-Zmiri-disable-isolation -Zmiri-ignore-leaks" cargo +nightly miri test --package dioxus-core --test miri_stress -- --exact --nocapture
# MIRIFLAGS="-Zmiri-disable-isolation -Zmiri-ignore-leaks" cargo +nightly miri test --package dioxus-native-core --test miri_native -- --exact --nocapture

View file

@ -11,6 +11,7 @@ dioxus-desktop = { path = "../../packages/desktop" }
dioxus-web = { path = "../../packages/web" }
dioxus-ssr = { path = "../../packages/ssr" }
dioxus-router = { path = "../../packages/router" }
dioxus-liveview = { path = "../../packages/liveview", features = ["axum"] }
dioxus-tui = { path = "../../packages/tui" }
fermi = { path = "../../packages/fermi" }
@ -19,3 +20,4 @@ fermi = { path = "../../packages/fermi" }
serde = { version = "1.0.138", features=["derive"] }
reqwest = { version = "0.11.11", features = ["json"] }
tokio = { version = "1.19.2" , features=[]}
axum = { version = "0.6.1", features = ["ws"] }

View file

@ -24,6 +24,8 @@ edit-url-template = "https://github.com/DioxusLabs/dioxus/edit/master/docs/guide
[output.html.playground]
editable = true
line-numbers = true
# running examples will not work because dioxus is not a included in the playground
runnable = false
[output.html.search]
limit-results = 20

View file

@ -5,13 +5,14 @@ fn main() {
dioxus_desktop::launch(App);
}
#[rustfmt::skip]
fn App(cx: Scope) -> Element {
// ANCHOR: boolean_attribute
cx.render(rsx! {
div {
hidden: "false",
"hello"
}
})
cx.render(rsx! {
div {
hidden: "false",
"hello"
}
})
// ANCHOR_END: boolean_attribute
}

View file

@ -5,49 +5,50 @@ fn main() {
dioxus_desktop::launch(App);
}
#[rustfmt::skip]
fn App(cx: Scope) -> Element {
cx.render(rsx! {
// ANCHOR: OptionalProps_usage
Title {
title: "Some Title",
},
Title {
title: "Some Title",
subtitle: "Some Subtitle",
},
// Providing an Option explicitly won't compile though:
// Title {
// title: "Some Title",
// subtitle: None,
// },
Title {
title: "Some Title",
},
Title {
title: "Some Title",
subtitle: "Some Subtitle",
},
// Providing an Option explicitly won't compile though:
// Title {
// title: "Some Title",
// subtitle: None,
// },
// ANCHOR_END: OptionalProps_usage
// ANCHOR: ExplicitOption_usage
ExplicitOption {
title: "Some Title",
subtitle: None,
},
ExplicitOption {
title: "Some Title",
subtitle: Some("Some Title"),
},
// This won't compile:
// ExplicitOption {
// title: "Some Title",
// },
ExplicitOption {
title: "Some Title",
subtitle: None,
},
ExplicitOption {
title: "Some Title",
subtitle: Some("Some Title"),
},
// This won't compile:
// ExplicitOption {
// title: "Some Title",
// },
// ANCHOR_END: ExplicitOption_usage
// ANCHOR: DefaultComponent_usage
DefaultComponent {
number: 5,
},
DefaultComponent {},
DefaultComponent {
number: 5,
},
DefaultComponent {},
// ANCHOR_END: DefaultComponent_usage
// ANCHOR: IntoComponent_usage
IntoComponent {
string: "some &str",
},
IntoComponent {
string: "some &str",
},
// ANCHOR_END: IntoComponent_usage
})
}

View file

@ -1,3 +1,4 @@
#![allow(unused)]
#![allow(non_snake_case)]
use dioxus::prelude::*;
@ -16,6 +17,7 @@ pub fn App(cx: Scope) -> Element {
}
#[inline_props]
#[rustfmt::skip]
fn LogIn<'a>(
cx: Scope<'a>,
is_logged_in: bool,
@ -23,38 +25,73 @@ fn LogIn<'a>(
on_log_out: EventHandler<'a>,
) -> Element<'a> {
// ANCHOR: if_else
if *is_logged_in {
cx.render(rsx! {
div {
"Welcome!",
button {
onclick: move |_| on_log_out.call(()),
"Log Out",
}
}
})
} else {
cx.render(rsx! {
button {
onclick: move |_| on_log_in.call(()),
"Log In",
}
})
}
if *is_logged_in {
cx.render(rsx! {
"Welcome!"
button {
onclick: move |_| on_log_out.call(()),
"Log Out",
}
})
} else {
cx.render(rsx! {
button {
onclick: move |_| on_log_in.call(()),
"Log In",
}
})
}
// ANCHOR_END: if_else
}
#[inline_props]
#[rustfmt::skip]
fn LogInImproved<'a>(
cx: Scope<'a>,
is_logged_in: bool,
on_log_in: EventHandler<'a>,
on_log_out: EventHandler<'a>,
) -> Element<'a> {
// ANCHOR: if_else_improved
cx.render(rsx! {
// We only render the welcome message if we are logged in
// You can use if statements in the middle of a render block to conditionally render elements
if *is_logged_in {
// Notice the body of this if statment is rsx code, not an expression
"Welcome!"
}
button {
// depending on the value of `is_logged_in`, we will call a different event handler
onclick: move |_| if *is_logged_in {
on_log_in.call(())
}
else{
on_log_out.call(())
},
if *is_logged_in {
// if we are logged in, the button should say "Log Out"
"Log Out"
} else {
// if we are not logged in, the button should say "Log In"
"Log In"
}
}
})
// ANCHOR_END: if_else_improved
}
#[inline_props]
#[rustfmt::skip]
fn LogInWarning(cx: Scope, is_logged_in: bool) -> Element {
// ANCHOR: conditional_none
if *is_logged_in {
return cx.render(rsx!(()));
}
if *is_logged_in {
return None;
}
cx.render(rsx! {
a {
"You must be logged in to comment"
}
})
cx.render(rsx! {
a {
"You must be logged in to comment"
}
})
// ANCHOR_END: conditional_none
}

View file

@ -5,15 +5,16 @@ fn main() {
dioxus_desktop::launch(App);
}
#[rustfmt::skip]
fn App(cx: Scope) -> Element {
// ANCHOR: dangerous_inner_html
// this should come from a trusted source
let contents = "live <b>dangerously</b>";
// this should come from a trusted source
let contents = "live <b>dangerously</b>";
cx.render(rsx! {
div {
dangerous_inner_html: "{contents}",
}
})
cx.render(rsx! {
div {
dangerous_inner_html: "{contents}",
}
})
// ANCHOR_END: dangerous_inner_html
}

View file

@ -5,13 +5,14 @@ fn main() {
dioxus_desktop::launch(App);
}
#[rustfmt::skip]
fn App(cx: Scope) -> Element {
// ANCHOR: rsx
cx.render(rsx! {
button {
onclick: move |event| println!("Clicked! Event: {event:?}"),
"click me!"
}
})
cx.render(rsx! {
button {
onclick: move |event| println!("Clicked! Event: {event:?}"),
"click me!"
}
})
// ANCHOR_END: rsx
}

View file

@ -5,20 +5,21 @@ fn main() {
dioxus_desktop::launch(App);
}
#[rustfmt::skip]
fn App(cx: Scope) -> Element {
// ANCHOR: rsx
cx.render(rsx! {
div {
onclick: move |_event| {},
"outer",
button {
onclick: move |event| {
// now, outer won't be triggered
event.stop_propagation();
},
"inner"
}
cx.render(rsx! {
div {
onclick: move |_event| {},
"outer",
button {
onclick: move |event| {
// now, outer won't be triggered
event.stop_propagation();
},
"inner"
}
})
}
})
// ANCHOR_END: rsx
}

View file

@ -5,13 +5,14 @@ fn main() {
dioxus_desktop::launch(App);
}
#[rustfmt::skip]
fn App(cx: Scope) -> Element {
// ANCHOR: prevent_default
cx.render(rsx! {
input {
prevent_default: "oninput",
prevent_default: "onclick",
}
})
cx.render(rsx! {
input {
prevent_default: "oninput",
prevent_default: "onclick",
}
})
// ANCHOR_END: prevent_default
}

View file

@ -1,12 +1,15 @@
// ANCHOR: all
#![allow(non_snake_case)]
// import the prelude to get access to the `rsx!` macro and the `Scope` and `Element` types
use dioxus::prelude::*;
fn main() {
// launch the dioxus app in a webview
dioxus_desktop::launch(App);
}
// ANCHOR: component
// define a component that renders a div with the text "Hello, world!"
fn App(cx: Scope) -> Element {
cx.render(rsx! {
div {

View file

@ -0,0 +1,60 @@
// ANCHOR: all
use axum::{extract::ws::WebSocketUpgrade, response::Html, routing::get, Router};
use dioxus::prelude::*;
// ANCHOR: glue
#[tokio::main]
async fn main() {
let addr: std::net::SocketAddr = ([127, 0, 0, 1], 3030).into();
let view = dioxus_liveview::LiveViewPool::new();
let app = Router::new()
// The root route contains the glue code to connect to the WebSocket
.route(
"/",
get(move || async move {
Html(format!(
r#"
<!DOCTYPE html>
<html>
<head> <title>Dioxus LiveView with Axum</title> </head>
<body> <div id="main"></div> </body>
{glue}
</html>
"#,
// Create the glue code to connect to the WebSocket on the "/ws" route
glue = dioxus_liveview::interpreter_glue(&format!("ws://{addr}/ws"))
))
}),
)
// The WebSocket route is what Dioxus uses to communicate with the browser
.route(
"/ws",
get(move |ws: WebSocketUpgrade| async move {
ws.on_upgrade(move |socket| async move {
// When the WebSocket is upgraded, launch the LiveView with the app component
_ = view.launch(dioxus_liveview::axum_socket(socket), app).await;
})
}),
);
println!("Listening on http://{}", addr);
axum::Server::bind(&addr.to_string().parse().unwrap())
.serve(app.into_make_service())
.await
.unwrap();
}
// ANCHOR_END: glue
// ANCHOR: app
fn app(cx: Scope) -> Element {
cx.render(rsx! {
div {
"Hello, world!"
}
})
}
// ANCHOR_END: app
// ANCHOR_END: all

View file

@ -1,20 +1,17 @@
#![allow(non_snake_case)]
// import the prelude to get access to the `rsx!` macro and the `Scope` and `Element` types
use dioxus::prelude::*;
fn main() {
// launch the app in the terminal
dioxus_tui::launch(App);
}
// create a component that renders a div with the text "Hello, world!"
fn App(cx: Scope) -> Element {
cx.render(rsx! {
div {
width: "100%",
height: "10px",
background_color: "red",
justify_content: "center",
align_items: "center",
"Hello world!"
"Hello, world!"
}
})
}

View file

@ -1,10 +1,13 @@
#![allow(non_snake_case)]
// import the prelude to get access to the `rsx!` macro and the `Scope` and `Element` types
use dioxus::prelude::*;
fn main() {
// launch the web app
dioxus_web::launch(App);
}
// create a component that renders a div with the text "Hello, world!"
fn App(cx: Scope) -> Element {
cx.render(rsx! {
div {

View file

@ -7,60 +7,61 @@ fn main() {
dioxus_desktop::launch(App);
}
#[rustfmt::skip]
fn App(cx: Scope) -> Element {
let you_are_happy = true;
let you_know_it = false;
// ANCHOR: conditional
// ❌ don't call hooks in conditionals!
// We must ensure that the same hooks will be called every time
// But `if` statements only run if the conditional is true!
// So we might violate rule 2.
if you_are_happy && you_know_it {
let something = use_state(cx, || "hands");
println!("clap your {something}")
}
// ✅ instead, *always* call use_state
// You can put other stuff in the conditional though
// ❌ don't call hooks in conditionals!
// We must ensure that the same hooks will be called every time
// But `if` statements only run if the conditional is true!
// So we might violate rule 2.
if you_are_happy && you_know_it {
let something = use_state(cx, || "hands");
if you_are_happy && you_know_it {
println!("clap your {something}")
}
println!("clap your {something}")
}
// ✅ instead, *always* call use_state
// You can put other stuff in the conditional though
let something = use_state(cx, || "hands");
if you_are_happy && you_know_it {
println!("clap your {something}")
}
// ANCHOR_END: conditional
// ANCHOR: closure
// ❌ don't call hooks inside closures!
// We can't guarantee that the closure, if used, will be called at the same time every time
let _a = || {
let b = use_state(cx, || 0);
b.get()
};
// ✅ instead, move hook `b` outside
// ❌ don't call hooks inside closures!
// We can't guarantee that the closure, if used, will be called in the same order every time
let _a = || {
let b = use_state(cx, || 0);
let _a = || b.get();
b.get()
};
// ✅ instead, move hook `b` outside
let b = use_state(cx, || 0);
let _a = || b.get();
// ANCHOR_END: closure
let names: Vec<&str> = vec![];
// ANCHOR: loop
// `names` is a Vec<&str>
// `names` is a Vec<&str>
// ❌ Do not use hooks in loops!
// In this case, if the length of the Vec changes, we break rule 2
for _name in &names {
let is_selected = use_state(cx, || false);
println!("selected: {is_selected}");
}
// ❌ Do not use hooks in loops!
// In this case, if the length of the Vec changes, we break rule 2
for _name in &names {
let is_selected = use_state(cx, || false);
println!("selected: {is_selected}");
}
// ✅ Instead, use a hashmap with use_ref
let selection_map = use_ref(cx, HashMap::<&str, bool>::new);
// ✅ Instead, use a hashmap with use_ref
let selection_map = use_ref(cx, HashMap::<&str, bool>::new);
for name in &names {
let is_selected = selection_map.read()[name];
println!("selected: {is_selected}");
}
for name in &names {
let is_selected = selection_map.read()[name];
println!("selected: {is_selected}");
}
// ANCHOR_END: loop
cx.render(rsx!(()))

View file

@ -7,12 +7,25 @@ fn main() {
// ANCHOR: component
fn App(cx: Scope) -> Element {
// count will be initialized to 0 the first time the component is rendered
let mut count = use_state(cx, || 0);
cx.render(rsx!(
h1 { "High-Five counter: {count}" }
button { onclick: move |_| count += 1, "Up high!" }
button { onclick: move |_| count -= 1, "Down low!" }
button {
onclick: move |_| {
// changing the count will cause the component to re-render
count += 1
},
"Up high!"
}
button {
onclick: move |_| {
// changing the count will cause the component to re-render
count -= 1
},
"Down low!"
}
))
}
// ANCHOR_END: component

View file

@ -8,13 +8,12 @@ fn main() {
// ANCHOR: component
fn App(cx: Scope) -> Element {
let list = use_ref(cx, Vec::new);
let list_formatted = format!("{:?}", *list.read());
cx.render(rsx!(
p { "Current list: {list_formatted}" }
p { "Current list: {list.read():?}" }
button {
onclick: move |event| {
list.write().push(event)
list.with_mut(|list| list.push(event));
},
"Click me!"
}

View file

@ -11,9 +11,10 @@ fn main() {
struct DarkMode(bool);
// ANCHOR_END: DarkMode_struct
#[rustfmt::skip]
pub fn App(cx: Scope) -> Element {
// ANCHOR: context_provider
use_shared_state_provider(cx, || DarkMode(false));
use_shared_state_provider(cx, || DarkMode(false));
// ANCHOR_END: context_provider
let is_dark_mode = use_is_dark_mode(cx);
@ -34,9 +35,10 @@ pub fn App(cx: Scope) -> Element {
}))
}
#[rustfmt::skip]
pub fn use_is_dark_mode(cx: &ScopeState) -> bool {
// ANCHOR: use_context
let dark_mode_context = use_shared_state::<DarkMode>(cx);
let dark_mode_context = use_shared_state::<DarkMode>(cx);
// ANCHOR_END: use_context
dark_mode_context

View file

@ -12,42 +12,80 @@ struct Comment {
id: usize,
}
#[rustfmt::skip]
pub fn App(cx: Scope) -> Element {
// ANCHOR: render_list
let comment_field = use_state(cx, String::new);
let mut next_id = use_state(cx, || 0);
let comments = use_ref(cx, Vec::<Comment>::new);
let comment_field = use_state(cx, String::new);
let mut next_id = use_state(cx, || 0);
let comments = use_ref(cx, Vec::<Comment>::new);
let comments_lock = comments.read();
let comments_rendered = comments_lock.iter().map(|comment| {
cx.render(rsx!(CommentComponent {
let comments_lock = comments.read();
let comments_rendered = comments_lock.iter().map(|comment| {
rsx!(CommentComponent {
key: "{comment.id}",
comment: comment.clone(),
})
});
cx.render(rsx!(
form {
onsubmit: move |_| {
comments.write().push(Comment {
content: comment_field.get().clone(),
id: *next_id.get(),
});
next_id += 1;
comment_field.set(String::new());
},
input {
value: "{comment_field}",
oninput: |event| comment_field.set(event.value.clone()),
}
input {
r#type: "submit",
}
},
comments_rendered,
))
// ANCHOR_END: render_list
}
#[rustfmt::skip]
pub fn AppForLoop(cx: Scope) -> Element {
// ANCHOR: render_list_for_loop
let comment_field = use_state(cx, String::new);
let mut next_id = use_state(cx, || 0);
let comments = use_ref(cx, Vec::<Comment>::new);
cx.render(rsx!(
form {
onsubmit: move |_| {
comments.write().push(Comment {
content: comment_field.get().clone(),
id: *next_id.get(),
});
next_id += 1;
comment_field.set(String::new());
},
input {
value: "{comment_field}",
oninput: |event| comment_field.set(event.value.clone()),
}
input {
r#type: "submit",
}
},
for comment in &*comments.read() {
// Notice the body of this for loop is rsx code, not an expression
CommentComponent {
key: "{comment.id}",
comment: comment.clone(),
}))
});
cx.render(rsx!(
form {
onsubmit: move |_| {
comments.write().push(Comment {
content: comment_field.get().clone(),
id: *next_id.get(),
});
next_id += 1;
comment_field.set(String::new());
},
input {
value: "{comment_field}",
oninput: |event| comment_field.set(event.value.clone()),
}
input {
r#type: "submit",
}
},
comments_rendered,
))
// ANCHOR_END: render_list
}
}
))
// ANCHOR_END: render_list_for_loop
}
#[inline_props]

View file

@ -19,91 +19,151 @@ pub fn App(cx: Scope) -> Element {
))
}
#[rustfmt::skip]
pub fn Empty(cx: Scope) -> Element {
// ANCHOR: empty
cx.render(rsx!(div {}))
cx.render(rsx!(div {
// attributes / listeners
// children
}))
// ANCHOR_END: empty
}
#[rustfmt::skip]
pub fn Children(cx: Scope) -> Element {
// ANCHOR: children
cx.render(rsx!(ol {
li {"First Item"}
li {"Second Item"}
li {"Third Item"}
}))
cx.render(rsx!(ol {
li {"First Item"}
li {"Second Item"}
li {"Third Item"}
}))
// ANCHOR_END: children
}
#[rustfmt::skip]
pub fn Fragments(cx: Scope) -> Element {
// ANCHOR: fragments
cx.render(rsx!(
p {"First Item"},
p {"Second Item"},
Fragment {
span { "a group" },
span { "of three" },
span { "items" },
}
))
cx.render(rsx!(
p {"First Item"},
p {"Second Item"},
Fragment {
span { "a group" },
span { "of three" },
span { "items" },
}
))
// ANCHOR_END: fragments
}
#[rustfmt::skip]
pub fn ManyRoots(cx: Scope) -> Element {
// ANCHOR: manyroots
cx.render(rsx!(
p {"First Item"},
p {"Second Item"},
))
// ANCHOR_END: manyroots
}
#[rustfmt::skip]
pub fn Attributes(cx: Scope) -> Element {
// ANCHOR: attributes
cx.render(rsx!(a {
href: "https://www.youtube.com/watch?v=dQw4w9WgXcQ",
class: "primary_button",
"Log In"
}))
cx.render(rsx!(a {
href: "https://www.youtube.com/watch?v=dQw4w9WgXcQ",
class: "primary_button",
color: "red",
}))
// ANCHOR_END: attributes
}
#[rustfmt::skip]
pub fn VariableAttributes(cx: Scope) -> Element {
// ANCHOR: variable_attributes
let written_in_rust = true;
let button_type = "button";
cx.render(rsx!(button {
disabled: "{written_in_rust}",
class: "{button_type}",
"Rewrite it in rust"
}))
let written_in_rust = true;
let button_type = "button";
cx.render(rsx!(button {
disabled: "{written_in_rust}",
class: "{button_type}",
"Rewrite it in rust"
}))
// ANCHOR_END: variable_attributes
}
#[rustfmt::skip]
pub fn CustomAttributes(cx: Scope) -> Element {
// ANCHOR: custom_attributes
cx.render(rsx!(b {
"customAttribute": "value",
"Rust is Cool"
}))
// ANCHOR_END: custom_attributes
}
#[rustfmt::skip]
pub fn Formatting(cx: Scope) -> Element {
// ANCHOR: formatting
let coordinates = (42, 0);
let country = "es";
cx.render(rsx!(div {
class: "country-{country}",
"Coordinates: {coordinates:?}",
// arbitrary expressions are allowed,
// as long as they don't contain `{}`
div {
"{country.to_uppercase()}"
},
div {
"{7*6}"
},
}))
// ANCHOR_END: formatting
let coordinates = (42, 0);
let country = "es";
cx.render(rsx!(div {
class: "country-{country}",
"position": "{coordinates:?}",
// arbitrary expressions are allowed,
// as long as they don't contain `{}`
div {
"{country.to_uppercase()}"
},
div {
"{7*6}"
},
// {} can be escaped with {{}}
div {
"{{}}"
},
}))
// ANCHOR_END: formatting
}
#[rustfmt::skip]
pub fn Expression(cx: Scope) -> Element {
// ANCHOR: expression
let text = "Dioxus";
cx.render(rsx!(span {
text.to_uppercase()
}))
let text = "Dioxus";
cx.render(rsx!(span {
text.to_uppercase(),
// create a list of text from 0 to 9
(0..10).map(|i| rsx!{ i.to_string() })
}))
// ANCHOR_END: expression
}
#[rustfmt::skip]
pub fn Loops(cx: Scope) -> Element {
// ANCHOR: loops
cx.render(rsx!{
// use a for loop where the body itself is RSX
div {
// create a list of text from 0 to 9
for i in 0..3 {
// NOTE: the body of the loop is RSX not a rust statement
div {
"{i}"
}
}
}
// iterator equivalent
div {
(0..3).map(|i| rsx!{ div { "{i}" } })
}
})
// ANCHOR_END: loops
}
#[rustfmt::skip]
pub fn IfStatements(cx: Scope) -> Element {
// ANCHOR: ifstatements
cx.render(rsx!{
// use if statements without an else
if true {
rsx!(div { "true" })
}
})
// ANCHOR_END: ifstatements
}

View file

@ -12,49 +12,51 @@ struct ApiResponse {
image_url: String,
}
#[rustfmt::skip]
fn App(cx: Scope) -> Element {
// ANCHOR: use_future
let future = use_future(cx, (), |_| async move {
reqwest::get("https://dog.ceo/api/breeds/image/random")
.await
.unwrap()
.json::<ApiResponse>()
.await
});
// ANCHOR_END: use_future
// ANCHOR: use_future
let future = use_future(cx, (), |_| async move {
reqwest::get("https://dog.ceo/api/breeds/image/random")
.await
.unwrap()
.json::<ApiResponse>()
.await
});
// ANCHOR_END: use_future
// ANCHOR: render
cx.render(match future.value() {
Some(Ok(response)) => rsx! {
button {
onclick: move |_| future.restart(),
"Click to fetch another doggo"
// ANCHOR: render
cx.render(match future.value() {
Some(Ok(response)) => rsx! {
button {
onclick: move |_| future.restart(),
"Click to fetch another doggo"
}
div {
img {
max_width: "500px",
max_height: "500px",
src: "{response.image_url}",
}
div {
img {
max_width: "500px",
max_height: "500px",
src: "{response.image_url}",
}
}
},
Some(Err(_)) => rsx! { div { "Loading dogs failed" } },
None => rsx! { div { "Loading dogs..." } },
})
// ANCHOR_END: render
}
},
Some(Err(_)) => rsx! { div { "Loading dogs failed" } },
None => rsx! { div { "Loading dogs..." } },
})
// ANCHOR_END: render
}
#[rustfmt::skip]
#[inline_props]
fn RandomDog(cx: Scope, breed: String) -> Element {
// ANCHOR: dependency
let future = use_future(cx, (breed,), |(breed,)| async move {
reqwest::get(format!("https://dog.ceo/api/breed/{breed}/images/random"))
.await
.unwrap()
.json::<ApiResponse>()
.await
});
// ANCHOR_END: dependency
// ANCHOR: dependency
let future = use_future(cx, (breed,), |(breed,)| async move {
reqwest::get(format!("https://dog.ceo/api/breed/{breed}/images/random"))
.await
.unwrap()
.json::<ApiResponse>()
.await
});
// ANCHOR_END: dependency
cx.render(rsx!(()))
}

View file

@ -7,6 +7,7 @@
- [Web](getting_started/web.md)
- [Hot Reload](getting_started/hot_reload.md)
- [Server-Side Rendering](getting_started/ssr.md)
- [Liveview](getting_started/liveview.md)
- [Terminal UI](getting_started/tui.md)
- [Mobile](getting_started/mobile.md)
- [Describing the UI](describing_ui/index.md)

View file

@ -8,16 +8,6 @@ The `use_future` and `use_coroutine` hooks are useful if you want to uncondition
> Note: `spawn` will always spawn a *new* future. You most likely don't want to call it on every render.
The future must be `'static` so any values captured by the task cannot carry any references to `cx`, such as a `UseState`.
However, since you'll typically need a way to update the value of a hook, you can use `to_owned` to create a clone of the hook handle. You can then use that clone in the async closure.
To make this a bit less verbose, Dioxus exports the `to_owned!` macro which will create a binding as shown above, which can be quite helpful when dealing with many values.
```rust
{{#include ../../../examples/spawn.rs:to_owned_macro}}
```
Calling `spawn` will give you a `JoinHandle` which lets you cancel or pause the future.
## Spawning Tokio Tasks

View file

@ -1,12 +1,12 @@
# Coroutines
Another good tool to keep in your async toolbox are coroutines. Coroutines are futures that can be manually stopped, started, paused, and resumed.
Another tool in your async toolbox are coroutines. Coroutines are futures that can be manually stopped, started, paused, and resumed.
Like regular futures, code in a Dioxus coroutine will run until the next `await` point before yielding. This low-level control over asynchronous tasks is quite powerful, allowing for infinitely looping tasks like WebSocket polling, background timers, and other periodic actions.
Like regular futures, code in a coroutine will run until the next `await` point before yielding. This low-level control over asynchronous tasks is quite powerful, allowing for infinitely looping tasks like WebSocket polling, background timers, and other periodic actions.
## `use_coroutine`
The basic setup for coroutines is the `use_coroutine` hook. Most coroutines we write will be polling loops using async/await.
The `use_coroutine` hook allows you to create a coroutine. Most coroutines we write will be polling loops using async/await.
```rust
fn app(cx: Scope) -> Element {
@ -50,11 +50,45 @@ if sync.is_running() {
This pattern is where coroutines are extremely useful instead of writing all the complicated logic for pausing our async tasks like we would with JavaScript promises, the Rust model allows us to just not poll our future.
## Yielding Values
To yield values from a coroutine, simply bring in a `UseState` handle and set the value whenever your coroutine completes its work.
The future must be `'static` so any values captured by the task cannot carry any references to `cx`, such as a `UseState`.
You can use [to_owned](https://doc.rust-lang.org/std/borrow/trait.ToOwned.html#tymethod.to_owned) to create a clone of the hook handle which can be moved into the async closure.
```rust
let sync_status = use_state(cx, || Status::Launching);
let sync_task = use_coroutine(cx, |rx: UnboundedReceiver<SyncAction>| {
let sync_status = sync_status.to_owned();
async move {
loop {
delay_ms(1000).await;
sync_status.set(Status::Working);
}
}
})
```
To make this a bit less verbose, Dioxus exports the `to_owned!` macro which will create a binding as shown above, which can be quite helpful when dealing with many values.
```rust
let sync_status = use_state(cx, || Status::Launching);
let load_status = use_state(cx, || Status::Launching);
let sync_task = use_coroutine(cx, |rx: UnboundedReceiver<SyncAction>| {
to_owned![sync_status, load_status];
async move {
// ...
}
})
```
## Sending Values
You might've noticed the `use_coroutine` closure takes an argument called `rx`. What is that? Well, a common pattern in complex apps is to handle a bunch of async code at once. With libraries like Redux Toolkit, managing multiple promises at once can be challenging and a common source of bugs.
With Coroutines, we have the opportunity to centralize our async logic. The `rx` parameter is an Unbounded Channel for code external to the coroutine to send data *into* the coroutine. Instead of looping on an external service, we can loop on the channel itself, processing messages from within our app without needing to spawn a new future. To send data into the coroutine, we would call "send" on the handle.
With Coroutines, we can centralize our async logic. The `rx` parameter is an Channel that allows code external to the coroutine to send data *into* the coroutine. Instead of looping on an external service, we can loop on the channel itself, processing messages from within our app without needing to spawn a new future. To send data into the coroutine, we would call "send" on the handle.
```rust
@ -103,7 +137,7 @@ async fn editor_service(rx: UnboundedReceiver<EditorCommand>) {
}
```
We can combine coroutines with Fermi to emulate Redux Toolkit's Thunk system with much less headache. This lets us store all of our app's state *within* a task and then simply update the "view" values stored in Atoms. It cannot be understated how powerful this technique is: we get all the perks of native Rust tasks with the optimizations and ergonomics of global state. This means your *actual* state does not need to be tied up in a system like Fermi or Redux the only Atoms that need to exist are those that are used to drive the display/UI.
We can combine coroutines with [Fermi](https://docs.rs/fermi/latest/fermi/index.html) to emulate Redux Toolkit's Thunk system with much less headache. This lets us store all of our app's state *within* a task and then simply update the "view" values stored in Atoms. It cannot be understated how powerful this technique is: we get all the perks of native Rust tasks with the optimizations and ergonomics of global state. This means your *actual* state does not need to be tied up in a system like Fermi or Redux the only Atoms that need to exist are those that are used to drive the display/UI.
```rust
static USERNAME: Atom<String> = |_| "default".to_string();
@ -152,27 +186,9 @@ async fn sync_service(mut rx: UnboundedReceiver<SyncAction>, atoms: AtomRoot) {
}
```
## Yielding Values
To yield values from a coroutine, simply bring in a `UseState` handle and set the value whenever your coroutine completes its work.
```rust
let sync_status = use_state(cx, || Status::Launching);
let sync_task = use_coroutine(cx, |rx: UnboundedReceiver<SyncAction>| {
to_owned![sync_status];
async move {
loop {
delay_ms(1000).await;
sync_status.set(Status::Working);
}
}
})
```
## Automatic injection into the Context API
Coroutine handles are automatically injected through the context API. `use_coroutine_handle` with the message type as a generic can be used to fetch a handle.
Coroutine handles are automatically injected through the context API. You can use the `use_coroutine_handle` hook with the message type as a generic to fetch a handle.
```rust
fn Child(cx: Scope) -> Element {

View file

@ -2,7 +2,7 @@
[`use_future`](https://docs.rs/dioxus-hooks/latest/dioxus_hooks/fn.use_future.html) lets you run an async closure, and provides you with its result.
For example, we can make an API request inside `use_future`:
For example, we can make an API request (using [reqwest](https://docs.rs/reqwest/latest/reqwest/index.html)) inside `use_future`:
```rust
{{#include ../../../examples/use_future.rs:use_future}}
@ -25,7 +25,7 @@ The `UseFuture` handle provides a `restart` method. It can be used to execute th
## Dependencies
Often, you will need to run the future again every time some value (e.g. a prop) changes. Rather than `.restart`ing it manually, you can provide a tuple of "dependencies" to the hook. It will automatically re-run the future when any of those dependencies change. Example:
Often, you will need to run the future again every time some value (e.g. a prop) changes. Rather than calling `restart` manually, you can provide a tuple of "dependencies" to the hook. It will automatically re-run the future when any of those dependencies change. Example:
```rust

View file

@ -1,6 +1,6 @@
# Antipatterns
This example shows what not to do and provides a reason why a given pattern is considered an "AntiPattern". Most anti-patterns are considered wrong due performance reasons, or for harming code re-usability.
This example shows what not to do and provides a reason why a given pattern is considered an "AntiPattern". Most anti-patterns are considered wrong for performance or code re-usability reasons.
## Unnecessarily Nested Fragments
@ -14,7 +14,7 @@ Only Component and Fragment nodes are susceptible to this issue. Dioxus mitigate
## Incorrect Iterator Keys
As described in the conditional rendering chapter, list items must have unique keys that are associated with the same items across renders. This helps Dioxus associate state with the contained components, and ensures good diffing performance. Do not omit keys, unless you know that the list is static and will never change.
As described in the [dynamic rendering chapter](../interactivity/dynamic_rendering.md#the-key-attribute), list items must have unique keys that are associated with the same items across renders. This helps Dioxus associate state with the contained components and ensures good diffing performance. Do not omit keys, unless you know that the list will never change.
```rust
{{#include ../../../examples/anti_patterns.rs:iter_keys}}

View file

@ -1,6 +1,6 @@
# Contributing
Development happens in the [Dioxus GitHub repository](https://github.com/DioxusLabs/dioxus). If you've found a bug or have an idea for a feature, please submit an issue (check if someone hasn't [done it already](https://github.com/DioxusLabs/dioxus/issues) though).
Development happens in the [Dioxus GitHub repository](https://github.com/DioxusLabs/dioxus). If you've found a bug or have an idea for a feature, please submit an issue (but first check if someone hasn't [done it already](https://github.com/DioxusLabs/dioxus/issues)).
[GitHub discussions](https://github.com/DioxusLabs/dioxus/discussions) can be used as a place to ask for help or talk about features. You can also join [our Discord channel](https://discord.gg/XgGxMSkvUM) where some development discussion happens.
@ -10,11 +10,11 @@ If you'd like to improve the docs, PRs are welcome! Both Rust docs ([source](htt
## Working on the Ecosystem
Part of what makes React great is the rich ecosystem. We'd like the same for Dioxus! So if you have a library in mind that you'd like to write, and that you think many people would benefit from, it will be appreciated. You can [browse npm.js](https://www.npmjs.com/search?q=keywords:react-component) for inspiration.
Part of what makes React great is the rich ecosystem. We'd like the same for Dioxus! So if you have a library in mind that you'd like to write and many people would benefit from, it will be appreciated. You can [browse npm.js](https://www.npmjs.com/search?q=keywords:react-component) for inspiration.
## Bugs & Features
If you've fixed [an issue that's open](https://github.com/DioxusLabs/dioxus/issues), feel free to submit a PR! You can also take a look at [the roadmap](./roadmap.md) and work on something in there. Consider [reaching out](https://discord.gg/XgGxMSkvUM) to the team first to make sure everyone's on the same page, and you don't do useless work!
If you've fixed [an open issue](https://github.com/DioxusLabs/dioxus/issues), feel free to submit a PR! You can also take a look at [the roadmap](./roadmap.md) and work on something in there. Consider [reaching out](https://discord.gg/XgGxMSkvUM) to the team first to make sure everyone's on the same page, and you don't do useless work!
All pull requests (including those made by a team member) must be approved by at least one other team member.
Larger, more nuanced decisions about design, architecture, breaking changes, trade-offs, etc. are made by team consensus.

View file

@ -11,46 +11,50 @@ Implementing the renderer is fairly straightforward. The renderer needs to:
1. Handle the stream of edits generated by updates to the virtual DOM
2. Register listeners and pass events into the virtual DOM's event system
Essentially, your renderer needs to implement the `RealDom` trait and generate `EventTrigger` objects to update the VirtualDOM. From there, you'll have everything needed to render the VirtualDOM to the screen.
Essentially, your renderer needs to process edits and generate events to update the VirtualDOM. From there, you'll have everything needed to render the VirtualDOM to the screen.
Internally, Dioxus handles the tree relationship, diffing, memory management, and the event system, leaving as little as possible required for renderers to implement themselves.
For reference, check out the javascript interperter or tui renderer as a starting point for your custom renderer.
For reference, check out the [javascript interpreter](https://github.com/DioxusLabs/dioxus/tree/master/packages/interpreter) or [tui renderer](https://github.com/DioxusLabs/dioxus/tree/master/packages/tui) as a starting point for your custom renderer.
## DomEdits
## Templates
The "DomEdit" type is a serialized enum that represents an atomic operation occurring on the RealDom. The variants roughly follow this set:
Dioxus is built around the concept of [Templates](https://docs.rs/dioxus-core/latest/dioxus_core/prelude/struct.Template.html). Templates describe a UI tree known at compile time with dynamic parts filled at runtime. This is useful internally to make skip diffing static nodes, but it is also useful for the renderer to reuse parts of the UI tree. This can be useful for things like a list of items. Each item could contain some static parts and some dynamic parts. The renderer can use the template to create a static part of the UI once, clone it for each element in the list, and then fill in the dynamic parts.
## Mutations
The `Mutation` type is a serialized enum that represents an operation that should be applied to update the UI. The variants roughly follow this set:
```rust
enum DomEdit {
PushRoot,
enum Mutation {
AppendChildren,
AssignId,
CreatePlaceholder,
CreateTextNode,
HydrateText,
LoadTemplate,
ReplaceWith,
ReplacePlaceholder,
InsertAfter,
InsertBefore,
Remove,
CreateTextNode,
CreateElement,
CreateElementNs,
CreatePlaceholder,
SetAttribute,
SetText,
NewEventListener,
RemoveEventListener,
SetText,
SetAttribute,
RemoveAttribute,
PopRoot,
Remove,
PushRoot,
}
```
The Dioxus diffing mechanism operates as a [stack machine](https://en.wikipedia.org/wiki/Stack_machine) where the "push_root" method pushes a new "real" DOM node onto the stack and "append_child" and "replace_with" both remove nodes from the stack.
### An example
### An Example
For the sake of understanding, lets consider this example a very simple UI declaration:
For the sake of understanding, let's consider this example a very simple UI declaration:
```rust
rsx!( h1 {"hello world"} )
rsx!( h1 {"count {x}"} )
```
To get things started, Dioxus must first navigate to the container of this h1 tag. To "navigate" here, the internal diffing algorithm generates the DomEdit `PushRoot` where the ID of the root is the container.
@ -66,7 +70,7 @@ stack: [
]
```
Next, Dioxus will encounter the h1 node. The diff algorithm decides that this node needs to be created, so Dioxus will generate the DomEdit `CreateElement`. When the renderer receives this instruction, it will create an unmounted node and push into its own stack:
Next, Dioxus will encounter the h1 node. The diff algorithm decides that this node needs to be created, so Dioxus will generate the DomEdit `CreateElement`. When the renderer receives this instruction, it will create an unmounted node and push it into its own stack:
```rust
instructions: [
@ -91,7 +95,7 @@ stack: [
"hello world"
]
```
Remember, the text node is not attached to anything (it is unmounted) so Dioxus needs to generate an Edit that connects the text node to the h1 element. It depends on the situation, but in this case we use `AppendChildren`. This pops the text node off the stack, leaving the h1 element as the next element in line.
Remember, the text node is not attached to anything (it is unmounted) so Dioxus needs to generate an Edit that connects the text node to the h1 element. It depends on the situation, but in this case, we use `AppendChildren`. This pops the text node off the stack, leaving the h1 element as the next element in line.
```rust
instructions: [
@ -141,13 +145,13 @@ Over time, our stack looked like this:
[]
```
Notice how our stack is empty once UI has been mounted. Conveniently, this approach completely separates the VirtualDOM and the Real DOM. Additionally, these edits are serializable, meaning we can even manage UIs across a network connection. This little stack machine and serialized edits makes Dioxus independent of platform specifics.
Notice how our stack is empty once UI has been mounted. Conveniently, this approach completely separates the Virtual DOM and the Real DOM. Additionally, these edits are serializable, meaning we can even manage UIs across a network connection. This little stack machine and serialized edits make Dioxus independent of platform specifics.
Dioxus is also really fast. Because Dioxus splits the diff and patch phase, it's able to make all the edits to the RealDOM in a very short amount of time (less than a single frame) making rendering very snappy. It also allows Dioxus to cancel large diffing operations if higher priority work comes in while it's diffing.
It's important to note that there _is_ one layer of connectedness between Dioxus and the renderer. Dioxus saves and loads elements (the PushRoot edit) with an ID. Inside the VirtualDOM, this is just tracked as a u64.
Whenever a `CreateElement` edit is generated during diffing, Dioxus increments its node counter and assigns that new element its current NodeCount. The RealDom is responsible for remembering this ID and pushing the correct node when PushRoot(ID) is generated. Dioxus reclaims IDs of elements when removed. To stay in sync with Dioxus you can use a sparce Vec (Vec<Option<T>>) with possibly unoccupied items. You can use the ids as indexes into the Vec for elements, and grow the Vec when a id does not exist.
Whenever a `CreateElement` edit is generated during diffing, Dioxus increments its node counter and assigns that new element its current NodeCount. The RealDom is responsible for remembering this ID and pushing the correct node when PushRoot(ID) is generated. Dioxus reclaims the IDs of elements when removed. To stay in sync with Dioxus you can use a sparse Vec (Vec<Option<T>>) with possibly unoccupied items. You can use the ids as indexes into the Vec for elements, and grow the Vec when an id does not exist.
This little demo serves to show exactly how a Renderer would need to process an edit stream to build UIs. A set of serialized DomEditss for various demos is available for you to test your custom renderer against.
@ -243,16 +247,16 @@ You've probably noticed that many elements in the `rsx!` macros support on-hover
# Native Core
If you are creating a renderer in rust, native core provides some utilites to implement a renderer. It provides an abstraction over DomEdits and handles layout for you.
If you are creating a renderer in rust, native-core provides some utilities to implement a renderer. It provides an abstraction over DomEdits and handles the layout for you.
## RealDom
The `RealDom` is a higher level abstraction over updating the Dom. It updates with `DomEdits` and provides a way to incrementally update the state of nodes based on what attributes change.
The `RealDom` is a higher-level abstraction over updating the Dom. It updates with `DomEdits` and provides a way to incrementally update the state of nodes based on what attributes change.
### Example
Let's build a toy renderer with borders, size, and text color.
Before we start lets take a look at an example element we can render:
Before we start let's take a look at an example element we can render:
```rust
cx.render(rsx!{
div{
@ -265,7 +269,7 @@ cx.render(rsx!{
})
```
In this tree the color depends on the parent's color. The size depends on the childrens size, the current text, and a text size. The border depends on only the current node.
In this tree, the color depends on the parent's color. The size depends on the children's size, the current text, and the text size. The border depends on only the current node.
In the following diagram arrows represent dataflow:
@ -312,7 +316,7 @@ In the following diagram arrows represent dataflow:
[//]: # " end"
[//]: # " end"
To help in building a Dom, native core provides four traits: State, ChildDepState, ParentDepState, and NodeDepState and a RealDom struct. The ChildDepState, ParentDepState, and NodeDepState provide a way to describe how some information in a node relates to that of its relatives. By providing how to build a single node from its relations, native-core will derive a way to update the state of all nodes for you with ```#[derive(State)]```. Once you have a state you can provide it as a generic to RealDom. RealDom provides all of the methods to interact and update your new dom.
To help in building a Dom, native-core provides four traits: State, ChildDepState, ParentDepState, NodeDepState, and a RealDom struct. The ChildDepState, ParentDepState, and NodeDepState provide a way to describe how some information in a node relates to that of its relatives. By providing how to build a single node from its relations, native-core will derive a way to update the state of all nodes for you with ```#[derive(State)]```. Once you have a state you can provide it as a generic to RealDom. RealDom provides all of the methods to interact and update your new dom.
```rust
use dioxus_native_core::node_ref::*;
@ -447,7 +451,7 @@ struct ToyState {
}
```
Now that we have our state, we can put it to use in our dom. Re can update the dom with update_state to update the structure of the dom (adding, removing, and chaning properties of nodes) and then apply_mutations to update the ToyState for each of the nodes that changed.
Now that we have our state, we can put it to use in our dom. Re can update the dom with update_state to update the structure of the dom (adding, removing, and changing properties of nodes) and then apply_mutations to update the ToyState for each of the nodes that changed.
```rust
fn main(){
fn app(cx: Scope) -> Element {
@ -490,7 +494,7 @@ fn main(){
```
## Layout
For most platforms the layout of the Elements will stay the same. The layout_attributes module provides a way to apply html attributes to a stretch layout style.
For most platforms, the layout of the Elements will stay the same. The layout_attributes module provides a way to apply HTML attributes to a stretch layout style.
## Conclusion
That should be it! You should have nearly all the knowledge required on how to implement your own renderer. We're super interested in seeing Dioxus apps brought to custom desktop renderers, mobile renderer, video game UI, and even augmented reality! If you're interesting in contributing to any of the these projects, don't be afraid to reach out or join the community.
That should be it! You should have nearly all the knowledge required on how to implement your own renderer. We're super interested in seeing Dioxus apps brought to custom desktop renderers, mobile renderers, video game UI, and even augmented reality! If you're interested in contributing to any of these projects, don't be afraid to reach out or join the [community](https://discord.gg/XgGxMSkvUM).

View file

@ -9,7 +9,7 @@ Component props are a single struct annotated with `#[derive(Props)]`. For a com
There are 2 flavors of Props structs:
- Owned props:
- Don't have an associated lifetime
- Implement `PartialEq`, allowing for memoization (if the props don't change, Dioxus won't re-render the component)
- Implement `PartialEq`, allow for memoization (if the props don't change, Dioxus won't re-render the component)
- Borrowed props:
- [Borrow](https://doc.rust-lang.org/beta/rust-by-example/scope/borrow.html) from a parent component
- Cannot be memoized due to lifetime constraints
@ -32,7 +32,7 @@ You can then pass prop values to the component the same way you would pass attri
### Borrowed Props
Owning props works well if your props are easy to copy around like a single number. But what if we need to pass a larger data type, like a String from an `App` Component to a `TitleCard` subcomponent? A naive solution might be to [`.clone()`](https://doc.rust-lang.org/std/clone/trait.Clone.html) the String, creating a copy of it for the subcomponent but this would be inefficient, especially for larger Strings.
Owned props work well if your props are easy to copy around like a single number. But what if we need to pass a larger data type, like a String from an `App` Component to a `TitleCard` subcomponent? A naive solution might be to [`.clone()`](https://doc.rust-lang.org/std/clone/trait.Clone.html) the String, creating a copy of it for the subcomponent but this would be inefficient, especially for larger Strings.
Rust allows for something more efficient borrowing the String as a `&str` this is what Borrowed Props are for!
@ -47,6 +47,7 @@ We can then use the component like this:
```
![Screenshot: TitleCard component](./images/component_borrowed_props_screenshot.png)
Borrowed props can be very useful, but they do not allow for memorization so they will *always* rerun when the parent scope is rerendered. Because of this Borrowed Props should be reserved for components that are cheap to rerun or places where cloning data is an issue. Using Borrowed Props everywhere will result in large parts of your app rerunning every interaction.
## Prop Options

View file

@ -1,6 +1,6 @@
# Components
Just like you wouldn't want to write a complex program in a single, long, `main` function, you shouldn't build a complex UI in a single `App` function. Instead, it would be better to break down the functionality of an app in logical parts called components.
Just like you wouldn't want to write a complex program in a single, long, `main` function, you shouldn't build a complex UI in a single `App` function. Instead, you should break down the functionality of an app in logical parts called components.
A component is a Rust function, named in UpperCammelCase, that takes a `Scope` parameter and returns an `Element` describing the UI it wants to render. In fact, our `App` function is a component!
@ -8,7 +8,7 @@ A component is a Rust function, named in UpperCammelCase, that takes a `Scope` p
{{#include ../../../examples/hello_world_desktop.rs:component}}
```
> You'll probably want to add `#![allow(non_snake_case)]` to the top of your crate to avoid warnings about the function name
> You'll probably want to add `#![allow(non_snake_case)]` to the top of your crate to avoid warnings about UpperCammelCase component names
A Component is responsible for some rendering task typically, rendering an isolated part of the user interface. For example, you could have an `About` component that renders a short description of Dioxus Labs:

View file

@ -1,8 +1,8 @@
# Describing the UI
Dioxus is a *declarative* framework. This means that instead of telling Dioxus what to do (e.g. to "create an element" or "set color to red") we simply *declare* what we want the UI to look like using RSX.
Dioxus is a *declarative* framework. This means that instead of telling Dioxus what to do (e.g. to "create an element" or "set the color to red") we simply *declare* what we want the UI to look like using RSX.
You have already seen a simple example or RSX syntax in the "hello world" application:
You have already seen a simple example of RSX syntax in the "hello world" application:
```rust
{{#include ../../../examples/hello_world_desktop.rs:component}}
@ -21,9 +21,51 @@ RSX is very similar to HTML in that it describes elements with attributes and ch
<div></div>
```
### Attributes
Attributes (and [listeners](../interactivity/index.md)) modify the behavior or appearance of the element they are attached to. They are specified inside the `{}` brackets, using the `name: value` syntax. You can provide the value as a literal in the RSX:
```rust
{{#include ../../../examples/rsx_overview.rs:attributes}}
```
```html
<a href="https://www.youtube.com/watch?v=dQw4w9WgXcQ" class="primary_button" autofocus="true" style="color: red"></a>
```
> Note: All attributes defined in `dioxus-html` follow the snake_case naming convention. They transform their `snake_case` names to HTML's `camelCase` attributes.
> Note: Styles can be used directly outside of the `style:` attribute. In the above example, `color: "red"` is turned into `style="color: red"`.
#### Custom Attributes
Dioxus has a pre-configured set of attributes that you can use. RSX is validated at compile time to make sure you didn't specify an invalid attribute. If you want to override this behavior with a custom attribute name, specify the attribute in quotes:
```rust
{{#include ../../../examples/rsx_overview.rs:custom_attributes}}
```
```html
<b customAttribute="value">
</b>
```
### Interpolation
Similarly to how you can [format](https://doc.rust-lang.org/rust-by-example/hello/print/fmt.html) Rust strings, you can also interpolate in RSX text. Use `{variable}` to Display the value of a variable in a string, or `{variable:?}` to use the Debug representation:
```rust
{{#include ../../../examples/rsx_overview.rs:formatting}}
```
```html
<div class="country-es" position="(42, 0)">
<div>ES</div>
<div>42</div>
<div>{}</div>
</div>
```
### Children
To add children to an element, put them inside the `{}` brackets. They can be either other elements, or text. For example, you could have an `ol` (ordered list) element, containing 3 `li` (list item) elements, each of which contains some text:
To add children to an element, put them inside the `{}` brackets after all attributes and listeners in the element. They can be other elements, text, or [components](components.md). For example, you could have an `ol` (ordered list) element, containing 3 `li` (list item) elements, each of which contains some text:
```rust
{{#include ../../../examples/rsx_overview.rs:children}}
@ -38,69 +80,51 @@ To add children to an element, put them inside the `{}` brackets. They can be ei
### Fragments
You can also "group" elements by wrapping them in `Fragment {}`. This will not create any additional elements.
> Note: you can also render multiple elements at the top level of `rsx!` and they will be automatically grouped no need for an explicit `Fragment {}` there.
You can render multiple elements at the top level of `rsx!` and they will be automatically grouped.
```rust
{{#include ../../../examples/rsx_overview.rs:fragments}}
{{#include ../../../examples/rsx_overview.rs:manyroots}}
```
```html
<p>First Item</p>
<p>Second Item</p>
<span>a group</span>
<span>of three</span>
<span>items</span>
```
### Attributes
Attributes are also specified inside the `{}` brackets, using the `name: value` syntax. You can provide the value as a literal in the RSX:
```rust
{{#include ../../../examples/rsx_overview.rs:attributes}}
```
```html
<a href="https://www.youtube.com/watch?v=dQw4w9WgXcQ" class="primary_button" autofocus="true">Log In</a>
```
> Note: All attributes defined in `dioxus-html` follow the snake_case naming convention. They transform their `snake_case` names to HTML's `camelCase` attributes.
#### Custom Attributes
Dioxus has a pre-configured set of attributes that you can use. RSX is validated at compile time to make sure you didn't specify an invalid attribute. If you want to override this behavior with a custom attribute name, specify the attribute in quotes:
```rust
{{#include ../../../examples/rsx_overview.rs:custom_attributes}}
```
```html
<b customAttribute="value">
Rust is cool
</b>
```
### Interpolation
Similarly to how you can [format](https://doc.rust-lang.org/rust-by-example/hello/print/fmt.html) Rust strings, you can also interpolate in RSX text. Use `{variable}` to Display the value of a variable in a string, or `{variable:?}` to use the Debug representation:
```rust
{{#include ../../../examples/rsx_overview.rs:formatting}}
```
```html
<div class="country-es">Coordinates: (42, 0)
<div>ES</div>
<div>42</div>
</div>
```
### Expressions
You can include arbitrary Rust expressions within RSX, but you must escape them in `[]` brackets:
You can include arbitrary Rust expressions as children within RSX that implements [IntoDynNode](https://docs.rs/dioxus-core/0.3/dioxus_core/trait.IntoDynNode.html). This is useful for displaying data from an [iterator](https://doc.rust-lang.org/stable/book/ch13-02-iterators.html#processing-a-series-of-items-with-iterators):
```rust
{{#include ../../../examples/rsx_overview.rs:expression}}
```
```html
<span>DIOXUS</span>
<span>DIOXUS0123456789</span>
```
### Loops
In addition to iterators you can also use for loops directly within RSX:
```rust
{{#include ../../../examples/rsx_overview.rs:loops}}
```
```html
<div>0</div>
<div>1</div>
<div>2</div>
<div>0</div>
<div>1</div>
<div>2</div>
```
### If statements
You can also use if statements without an else branch within RSX:
```rust
{{#include ../../../examples/rsx_overview.rs:ifstatements}}
```
```html
<div>true</div>
```

View file

@ -1,21 +1,58 @@
# Desktop Application
# Desktop Overview
Build a standalone native desktop app that looks and feels the same across operating systems.
Apps built with Dioxus are typically <5mb in size and use existing system resources, so they won't hog extreme amounts of RAM or memory.
Examples:
- [File explorer](https://github.com/DioxusLabs/example-projects/blob/master/file-explorer)
- [WiFi scanner](https://github.com/DioxusLabs/example-projects/blob/master/wifi-scanner)
- [File Explorer](https://github.com/DioxusLabs/example-projects/blob/master/file-explorer)
- [WiFi Scanner](https://github.com/DioxusLabs/example-projects/blob/master/wifi-scanner)
[![File ExplorerExample](https://raw.githubusercontent.com/DioxusLabs/example-projects/master/file-explorer/image.png)](https://github.com/DioxusLabs/example-projects/tree/master/file-explorer)
## Support
The desktop is a powerful target for Dioxus, but is currently limited in capability when compared to the Web platform. Currently, desktop apps are rendered with the platform's WebView library, but your Rust code is running natively on a native thread. This means that browser APIs are *not* available, so rendering WebGL, Canvas, etc is not as easy as the Web. However, native system APIs *are* accessible, so streaming, WebSockets, filesystem, etc are all viable APIs. In the future, we plan to move to a custom webrenderer-based DOM renderer with WGPU integrations.
The desktop is a powerful target for Dioxus but is currently limited in capability when compared to the Web platform. Currently, desktop apps are rendered with the platform's WebView library, but your Rust code is running natively on a native thread. This means that browser APIs are *not* available, so rendering WebGL, Canvas, etc is not as easy as the Web. However, native system APIs *are* accessible, so streaming, WebSockets, filesystem, etc are all viable APIs. In the future, we plan to move to a custom web renderer-based DOM renderer with WGPU integrations.
Dioxus Desktop is built off [Tauri](https://tauri.app/). Right now there aren't any Dioxus abstractions over keyboard shortcuts, menubar, handling, etc, so you'll want to leverage Tauri mostly [Wry](http://github.com/tauri-apps/wry/) and [Tao](http://github.com/tauri-apps/tao)) directly.
# Getting started
## Platform-Specific Dependencies
Dioxus desktop renders through a web view. Depending on your platform, you might need to install some dependancies.
### Windows
Windows Desktop apps depend on WebView2 a library that should be installed in all modern Windows distributions. If you have Edge installed, then Dioxus will work fine. If you *don't* have Webview2, [then you can install it through Microsoft](https://developer.microsoft.com/en-us/microsoft-edge/webview2/). MS provides 3 options:
1. A tiny "evergreen" *bootstrapper* that fetches an installer from Microsoft's CDN
2. A tiny *installer* that fetches Webview2 from Microsoft's CDN
3. A statically linked version of Webview2 in your final binary for offline users
For development purposes, use Option 1.
### Linux
Webview Linux apps require WebkitGtk. When distributing, this can be part of your dependency tree in your `.rpm` or `.deb`. However, likely, your users will already have WebkitGtk.
```bash
sudo apt install libwebkit2gtk-4.0-dev libgtk-3-dev libappindicator3-dev
```
When using Debian/bullseye `libappindicator3-dev` is no longer available but replaced by `libayatana-appindicator3-dev`.
```bash
# on Debian/bullseye use:
sudo apt install libwebkit2gtk-4.0-dev libgtk-3-dev libayatana-appindicator3-dev
```
If you run into issues, make sure you have all the basics installed, as outlined in the [Tauri docs](https://tauri.studio/v1/guides/getting-started/prerequisites#setting-up-linux).
### MacOS
Currently everything for macOS is built right in! However, you might run into an issue if you're using nightly Rust due to some permissions issues in our Tao dependency (which have been resolved but not published).
## Creating a Project
Create a new crate:
@ -25,7 +62,7 @@ cargo new --bin demo
cd demo
```
Add Dioxus and the `desktop` renderer (this will edit `Cargo.toml`):
Add Dioxus and the desktop renderer as dependencies (this will edit your `Cargo.toml`):
```shell
cargo add dioxus

View file

@ -1,15 +1,12 @@
# Setting Up Hot Reload
1. Hot reloading allows much faster iteration times inside of rsx calls by interperting them and streaming the edits.
1. Hot reloading allows much faster iteration times inside of rsx calls by interpreting them and streaming the edits.
2. It is useful when changing the styling/layout of a program, but will not help with changing the logic of a program.
3. Currently the cli only implements hot reloading for the web renderer.
# Setup
Install [dioxus-cli](https://github.com/DioxusLabs/cli).
Enable the hot-reload feature on dioxus:
```toml
dioxus = { version = "*", features = ["hot-reload"] }
```
Hot reloading is automatically enabled when using the web renderer on debug builds.
# Usage
1. run:
@ -21,5 +18,5 @@ dioxus serve --hot-reload
4. save and watch the style change without recompiling
# Limitations
1. The interperter can only use expressions that existed on the last full recompile. If you introduce a new variable or expression to the rsx call, it will trigger a full recompile to capture the expression.
2. Components and Iterators can contain abritary rust code, and will trigger a full recompile when changed.
1. The interpreter can only use expressions that existed on the last full recompile. If you introduce a new variable or expression to the rsx call, it will trigger a full recompile to capture the expression.
2. Components and Iterators can contain arbitrary rust code and will trigger a full recompile when changed.

View file

@ -12,63 +12,23 @@ Dioxus integrates very well with the [Rust-Analyzer LSP plugin](https://rust-ana
Head over to [https://rust-lang.org](http://rust-lang.org) and install the Rust compiler.
We strongly recommend going through the [official Rust book](https://doc.rust-lang.org/book/ch01-00-getting-started.html) _completely_. However, our hope is that a Dioxus app can serve as a great first Rust project. With Dioxus, you'll learn about:
We strongly recommend going through the [official Rust book](https://doc.rust-lang.org/book/ch01-00-getting-started.html) _completely_. However, we hope that a Dioxus app can serve as a great first Rust project. With Dioxus, you'll learn about:
- Error handling
- Structs, Functions, Enums
- Closures
- Macros
We've put a lot of care into making Dioxus syntax familiar and easy to understand, so you won't need deep knowledge on async, lifetimes, or smart pointers until you really start building complex Dioxus apps.
### Platform-Specific Dependencies
#### Windows
Windows Desktop apps depend on WebView2 a library which should be installed in all modern Windows distributions. If you have Edge installed, then Dioxus will work fine. If you *don't* have Webview2, [then you can install it through Microsoft](https://developer.microsoft.com/en-us/microsoft-edge/webview2/). MS provides 3 options:
1. A tiny "evergreen" *bootstrapper* which will fetch an installer from Microsoft's CDN
2. A tiny *installer* which will fetch Webview2 from Microsoft's CDN
3. A statically linked version of Webview2 in your final binary for offline users
For development purposes, use Option 1.
#### Linux
Webview Linux apps require WebkitGtk. When distributing, this can be part of your dependency tree in your `.rpm` or `.deb`. However, it's very likely that your users will already have WebkitGtk.
```bash
sudo apt install libwebkit2gtk-4.0-dev libgtk-3-dev libappindicator3-dev
```
When using Debian/bullseye `libappindicator3-dev` is no longer available but replaced by `libayatana-appindicator3-dev`.
```bash
# on Debian/bullseye use:
sudo apt install libwebkit2gtk-4.0-dev libgtk-3-dev libayatana-appindicator3-dev
```
If you run into issues, make sure you have all the basics installed, as outlined in the [Tauri docs](https://tauri.studio/v1/guides/getting-started/prerequisites#setting-up-linux).
#### MacOS
Currently everything for macOS is built right in! However, you might run into an issue if you're using nightly Rust due to some permissions issues in our Tao dependency (which have been resolved but not published).
### Suggested Cargo Extensions
If you want to keep your traditional `npm install XXX` workflow for adding packages, you might want to install `cargo-edit` and a few other fun `cargo` extensions:
- [cargo-expand](https://github.com/dtolnay/cargo-expand) for expanding macro calls
- [cargo tree](https://doc.rust-lang.org/cargo/commands/cargo-tree.html) an integrated cargo command that lets you inspect your dependency tree
We've put a lot of care into making Dioxus syntax familiar and easy to understand, so you won't need deep knowledge of async, lifetimes, or smart pointers until you start building complex Dioxus apps.
## Setup Guides
Dioxus supports multiple platforms. Depending on what you want, the setup is a bit different.
Dioxus supports multiple platforms. Choose the platform you want to target below to get platform-specific setup instructions:
- [Web](web.md): running in the browser using WASM
- [Server Side Rendering](ssr.md): render Dioxus HTML as text
- [Desktop](desktop.md): a standalone app using webview
- [Mobile](mobile.md)
- [Terminal UI](tui.md): terminal text-based graphic interface
- [Web](web.md): runs in the browser through WebAssembly
- [Server Side Rendering](ssr.md): renders to HTML text on the server
- [Liveview](liveview.md): runs on the server, renders in the browser using WebSockets
- [Desktop](desktop.md): runs in a web view on desktop
- [Mobile](mobile.md): runs in a web view on mobile
- [Terminal UI](tui.md): renders text-based graphics in the terminal

View file

@ -0,0 +1,66 @@
# Liveview
Liveview allows apps to *run* on the server and *render* in the browser. It uses WebSockets to communicate between the server and the browser.
Examples:
- [Axum Example](https://github.com/DioxusLabs/dioxus/tree/master/packages/liveview/examples/axum.rs)
- [Salvo Example](https://github.com/DioxusLabs/dioxus/tree/master/packages/liveview/examples/salvo.rs)
- [Warp Example](https://github.com/DioxusLabs/dioxus/tree/master/packages/liveview/examples/warp.rs)
## Support
Liveview is currently limited in capability when compared to the Web platform. Liveview apps run on the server in a native thread. This means that browser APIs are not available, so rendering WebGL, Canvas, etc is not as easy as the Web. However, native system APIs are accessible, so streaming, WebSockets, filesystem, etc are all viable APIs.
## Setup
For this guide, we're going to show how to use Dioxus Liveview with [Axum](https://docs.rs/axum/latest/axum/).
Make sure you have Rust and Cargo installed, and then create a new project:
```shell
cargo new --bin demo
cd app
```
Add Dioxus and the liveview renderer with the Axum feature as dependencies:
```shell
cargo add dioxus
cargo add dioxus-liveview --features axum
```
Next, add all the Axum dependencies. This will be different if you're using a different Web Framework
```
cargo add tokio --features full
cargo add axum
```
Your dependencies should look roughly like this:
```toml
[dependencies]
axum = "0.4.5"
dioxus = { version = "*" }
dioxus-liveview = { version = "*", features = ["axum"] }
tokio = { version = "1.15.0", features = ["full"] }
```
Now, set up your Axum app to respond on an endpoint.
```rust
{{#include ../../../examples/hello_world_liveview.rs:glue}}
```
And then add our app component:
```rust
{{#include ../../../examples/hello_world_liveview.rs:app}}
```
And that's it!

View file

@ -5,12 +5,12 @@ Build a mobile app with Dioxus!
Example: [Todo App](https://github.com/DioxusLabs/example-projects/blob/master/ios_demo)
## Support
Mobile is currently the least-supported renderer target for Dioxus. Mobile apps are rendered with either the platform's WebView or experimentally through WGPU. WebView doesn't support animations, transparency, and native widgets.
Mobile is currently the least-supported renderer target for Dioxus. Mobile apps are rendered with either the platform's WebView or experimentally through [WGPU](https://github.com/DioxusLabs/blitz). WebView doesn't support animations, transparency, and native widgets.
Mobile support is currently best suited for CRUD-style apps, ideally for internal teams who need to develop quickly but don't care much about animations or native widgets.
This guide is primarily targetted for iOS apps, however, you can follow it while using the `android` guide in `cargo-mobile`.
This guide is primarily targeted at iOS apps, however, you can follow it while using the `android` guide in `cargo-mobile`.
## Getting Set up
@ -35,7 +35,7 @@ We're going to completely clear out the `dependencies` it generates for us, swap
[package]
name = "dioxus-ios-demo"
version = "0.1.0"
authors = ["Jonathan Kelley <jkelleyrtp@gmail.com>"]
authors = []
edition = "2018"

View file

@ -15,19 +15,7 @@ When working with web frameworks that require `Send`, it is possible to render a
## Setup
If you just want to render `rsx!` or a VirtualDom to HTML, check out the API docs. It's pretty simple:
```rust
// We can render VirtualDoms
let mut vdom = VirtualDom::new(app);
let _ = vdom.rebuild();
println!("{}", dioxus_ssr::render_vdom(&vdom));
// Or we can render rsx! calls directly
println!( "{}", dioxus_ssr::render_lazy(rsx! { h1 { "Hello, world!" } } );
```
However, for this guide, we're going to show how to use Dioxus SSR with `Axum`.
For this guide, we're going to show how to use Dioxus SSR with [Axum](https://docs.rs/axum/latest/axum/).
Make sure you have Rust and Cargo installed, and then create a new project:
@ -36,7 +24,7 @@ cargo new --bin demo
cd app
```
Add Dioxus and the `ssr` renderer feature:
Add Dioxus and the ssr renderer as dependencies:
```shell
cargo add dioxus
@ -86,8 +74,9 @@ And then add our endpoint. We can either render `rsx!` directly:
```rust
async fn app_endpoint() -> Html<String> {
// render the rsx! macro to HTML
Html(dioxus_ssr::render_lazy(rsx! {
h1 { "hello world!" }
div { "hello world!" }
}))
}
```
@ -96,12 +85,16 @@ Or we can render VirtualDoms.
```rust
async fn app_endpoint() -> Html<String> {
// create a component that renders a div with the text "hello world"
fn app(cx: Scope) -> Element {
cx.render(rsx!(h1 { "hello world" }))
cx.render(rsx!(div { "hello world" }))
}
// create a VirtualDom with the app component
let mut app = VirtualDom::new(app);
// rebuild the VirtualDom before rendering
let _ = app.rebuild();
// render the VirtualDom to HTML
Html(dioxus_ssr::render_vdom(&app))
}
```

View file

@ -8,12 +8,19 @@ You can build a text-based interface that will run in the terminal using Dioxus.
## Support
TUI support is currently quite experimental. Even the project name will change. But, if you're willing to venture into the realm of the unknown, this guide will get you started.
TUI support is currently quite experimental. But, if you're willing to venture into the realm of the unknown, this guide will get you started.
- It uses flexbox for the layout
- It only supports a subset of the attributes and elements
- Regular widgets will not work in the tui render, but the tui renderer has its own widget components that start with a capital letter. See the [widgets example](https://github.com/DioxusLabs/dioxus/blob/master/packages/tui/examples/tui_widgets.rs)
- 1px is one character line height. Your regular CSS px does not translate
- If your app panics, your terminal is wrecked. This will be fixed eventually
## Getting Set up
Start by making a new package and adding our TUI renderer.
Start by making a new package and adding Dioxus and the TUI renderer as dependancies.
```shell
cargo new --bin demo
@ -34,15 +41,8 @@ To run our app:
cargo run
```
Press "ctrl-c" to close the app. To switch from "ctrl-c" to just "q" to quit you can launch the app with a configuration to disable the default quit and use the root TuiContext to quit on your own.
Press "ctrl-c" to close the app. To switch from "ctrl-c" to just "q" to quit you can launch the app with a configuration to disable the default quit and use the root TuiContext to quit on your own.
```rust
{{#include ../../../examples/hello_world_tui_no_ctrl_c.rs}}
```
## Notes
- Our TUI package uses flexbox for layout
- Regular widgets will not work in the tui render, but the tui renderer has it's own widget components (see [the widgets example](https://github.com/DioxusLabs/dioxus/blob/master/packages/tui/examples/tui_widgets.rs)).
- 1px is one character lineheight. Your regular CSS px does not translate.
- If your app panics, your terminal is wrecked. This will be fixed eventually.

View file

@ -1,8 +1,8 @@
# Web
Build single-page applications that run in the browser with Dioxus. To run on the Web, your app must be compiled to WebAssembly and depend on the `dioxus` crate with the `web` feature enabled.
Build single-page applications that run in the browser with Dioxus. To run on the Web, your app must be compiled to WebAssembly and depend on the `dioxus` and `dioxus-web` crates.
A build of Dioxus for the web will be roughly equivalent to the size of a React build (70kb vs 65kb), but will load significantly faster due to [WebAssembly's StreamingCompile](https://hacks.mozilla.org/2018/01/making-webassembly-even-faster-firefoxs-new-streaming-and-tiering-compiler/) option.
A build of Dioxus for the web will be roughly equivalent to the size of a React build (70kb vs 65kb) but it will load significantly faster because [WebAssembly can be compiled as it is streamed](https://hacks.mozilla.org/2018/01/making-webassembly-even-faster-firefoxs-new-streaming-and-tiering-compiler/).
Examples:
- [TodoMVC](https://github.com/DioxusLabs/example-projects/tree/master/todomvc)
@ -10,21 +10,24 @@ Examples:
[![TodoMVC example](https://github.com/DioxusLabs/example-projects/raw/master/todomvc/example.png)](https://github.com/DioxusLabs/example-projects/blob/master/todomvc)
> Note: Because of the limitations of Wasm, not every crate will work with your web apps, so you'll need to make sure that your crates work without native system calls (timers, IO, etc).
> Note: Because of the limitations of Wasm, [not every crate will work](https://rustwasm.github.io/docs/book/reference/which-crates-work-with-wasm.html) with your web apps, so you'll need to make sure that your crates work without native system calls (timers, IO, etc).
## Support
The Web is the best-supported target platform for Dioxus.
- Because your app will be compiled to WASM you have access to browser APIs through [wasm-bingen](https://rustwasm.github.io/docs/wasm-bindgen/introduction.html).
- Dioxus provides hydration to resume apps that are rendered on the server. See the [hydration example](https://github.com/DioxusLabs/dioxus/blob/master/packages/web/examples/hydrate.rs) for more details.
## Tooling
To develop your Dioxus app for the web, you'll need a tool to build and serve your assets. We recommend using [trunk](https://trunkrs.dev) which includes a build system, Wasm optimization, a dev server, and support for SASS/CSS:
To develop your Dioxus app for the web, you'll need a tool to build and serve your assets. We recommend using [dioxus-cli](https://github.com/DioxusLabs/cli) which includes a build system, Wasm optimization, a dev server, and support hot reloading:
```shell
cargo install trunk
cargo install dioxus-cli
```
Make sure the `wasm32-unknown-unknown` target is installed:
Make sure the `wasm32-unknown-unknown` target for rust is installed:
```shell
rustup target add wasm32-unknown-unknown
```
@ -38,28 +41,13 @@ cargo new --bin demo
cd demo
```
Add Dioxus as a dependency and add the web renderer:
Add Dioxus and the web renderer as dependencies (this will edit your `Cargo.toml`):
```bash
cargo add dioxus
cargo add dioxus-web
```
Add an `index.html` for Trunk to use. Make sure your "mount point" element has an ID of "main":
```html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
</head>
<body>
<div id="main"> </div>
</body>
</html>
```
Edit your `main.rs`:
```rust
{{#include ../../../examples/hello_world_web.rs}}
@ -69,5 +57,5 @@ Edit your `main.rs`:
And to serve our app:
```bash
trunk serve
dioxus serve
```

View file

@ -27,7 +27,7 @@ Dioxus is heavily inspired by React. If you know React, getting started with Dio
- Comprehensive inline documentation hover and guides for all HTML elements, listeners, and events.
- Extremely memory efficient 0 global allocations for steady-state components.
- Multi-channel asynchronous scheduler for first-class async support.
- And more! Read the [full release post](https://dioxuslabs.com/blog/introducing-dioxus/.
- And more! Read the [full release post](https://dioxuslabs.com/blog/introducing-dioxus/).
### Multiplatform

View file

@ -12,10 +12,19 @@ To render different elements based on a condition, you could use an `if-else` st
> You could also use `match` statements, or any Rust function to conditionally render different things.
### Improving the `if-else` Example
You may have noticed some repeated code in the `if-else` example above. Repeating code like this is both bad for maintainability and performance. Dioxus will skip diffing static elements like the button, but when switching between multiple `rsx` calls it cannot perform this optimization. For this example either approach is fine, but for components with large parts that are reused between conditionals, it can be more of an issue.
We can improve this example by splitting up the dynamic parts and inserting them where they are needed.
```rust
{{#include ../../../examples/conditional_rendering.rs:if_else_improved}}
```
### Inspecting `Element` props
Since `Element` is a `Option<VNode>`, components accepting `Element` as a prop can actually inspect its contents, and render different things based on that. Example:
Since `Element` is a `Option<VNode>`, components accepting `Element` as a prop can inspect its contents, and render different things based on that. Example:
```rust
{{#include ../../../examples/component_children_inspect.rs:Clickable}}
@ -43,9 +52,9 @@ Often, you'll want to render a collection of components. For example, you might
For this, Dioxus accepts iterators that produce `Element`s. So we need to:
- Get an iterator over all of our items (e.g., if you have a `Vec` of comments, iterate over it with `iter()`)
- `.map` the iterator to convert each item into a rendered `Element` using `cx.render(rsx!(...))`
- `.map` the iterator to convert each item into a `LazyNode` using `rsx!(...)`
- Add a unique `key` attribute to each iterator item
- Include this iterator in the final RSX
- Include this iterator in the final RSX (or use it inline)
Example: suppose you have a list of comments you want to render. Then, you can render them like this:
@ -53,21 +62,26 @@ Example: suppose you have a list of comments you want to render. Then, you can r
{{#include ../../../examples/rendering_lists.rs:render_list}}
```
### Inline for loops
Because of how common it is to render a list of items, Dioxus provides a shorthand for this. Instead of using `.iter, `.map`, and `rsx`, you can use a `for` loop with a body of rsx code:
```rust
{{#include ../../../examples/rendering_lists.rs:render_list_for_loop}}
```
### The `key` Attribute
Every time you re-render your list, Dioxus needs to keep track of which item went where, because the order of items in a list might change items might be added, removed or swapped. Despite that, Dioxus needs to:
- Keep track of component state
- Efficiently figure out what updates need to be made to the UI
Every time you re-render your list, Dioxus needs to keep track of which items go where to determine what updates need to be made to the UI.
For example, suppose the `CommentComponent` had some state e.g. a field where the user typed in a reply. If the order of comments suddenly changes, Dioxus needs to correctly associate that state with the same comment otherwise, the user will end up replying to a different comment!
To help Dioxus keep track of list items, we need to associate each item with a unique key. In the example above, we dynamically generated the unique key. In real applications, it's more likely that the key will come from e.g. a database ID. It doesn't really matter where you get the key from, as long as it meets the requirements
To help Dioxus keep track of list items, we need to associate each item with a unique key. In the example above, we dynamically generated the unique key. In real applications, it's more likely that the key will come from e.g. a database ID. It doesn't matter where you get the key from, as long as it meets the requirements:
- Keys must be unique in a list
- The same item should always get associated with the same key
- Keys should be relatively small (i.e. converting the entire Comment structure to a String would be a pretty bad key) so they can be compared efficiently
You might be tempted to use an item's index in the list as its key. In fact, thats what Dioxus will use if you dont specify a key at all. This is only acceptable if you can guarantee that the list is constant i.e., no re-ordering, additions or deletions.
You might be tempted to use an item's index in the list as its key. Thats what Dioxus will use if you dont specify a key at all. This is only acceptable if you can guarantee that the list is constant i.e., no re-ordering, additions, or deletions.
> Note that if you pass the key to a component you've made, it won't receive the key as a prop. Its only used as a hint by Dioxus itself. If your component needs an ID, you have to pass it as a separate prop.

View file

@ -1,10 +1,10 @@
# Event Handlers
Events are interesting things that happen, usually related to something the user has done. For example, some events happen when the user clicks, scrolls, moves the mouse, or types a character. To respond to these actions and make the UI interactive, we need to handle those events.
Event handlers are used to respond to user actions. For example, an event handler could be triggered when the user clicks, scrolls, moves the mouse, or types a character.
Events are associated with elements. For example, we usually don't care about all the clicks that happen within an app, only those on a particular button. To handle events that happen on an element, we must attach the desired event handler to it.
Event handlers are attached to elements. For example, we usually don't care about all the clicks that happen within an app, only those on a particular button.
Event handlers are similar to regular attributes, but their name usually starts with `on`- and they accept closures as values. The closure will be called whenever the event is triggered, and will be passed that event.
Event handlers are similar to regular attributes, but their name usually starts with `on`- and they accept closures as values. The closure will be called whenever the event it listens for is triggered and will be passed that event.
For example, to handle clicks on an element, we can specify an `onclick` handler:
@ -14,20 +14,24 @@ For example, to handle clicks on an element, we can specify an `onclick` handler
## The `Event` object
Event handlers receive an [`UiEvent`](https://docs.rs/dioxus-core/latest/dioxus_core/struct.UiEvent.html) object containing information about the event. Different types of events contain different types of data. For example, mouse-related events contain [`MouseData`](https://docs.rs/dioxus/latest/dioxus/events/struct.MouseData.html), which tells you things like where the mouse was clicked and what mouse buttons were used.
Event handlers receive an [`Event`](https://docs.rs/dioxus-core/latest/dioxus_core/struct.Event.html) object containing information about the event. Different types of events contain different types of data. For example, mouse-related events contain [`MouseData`](https://docs.rs/dioxus/latest/dioxus/events/struct.MouseData.html), which tells you things like where the mouse was clicked and what mouse buttons were used.
In the example above, this event data was logged to the terminal:
```
Clicked! Event: UiEvent { data: MouseData { coordinates: Coordinates { screen: (468.0, 109.0), client: (73.0, 25.0), element: (63.0, 15.0), page: (73.0, 25.0) }, modifiers: (empty), held_buttons: EnumSet(), trigger_button: Some(Primary) } }
Clicked! Event: UiEvent { data: MouseData { coordinates: Coordinates { screen: (468.0, 109.0), client: (73.0, 25.0), element: (63.0, 15.0), page: (73.0, 25.0) }, modifiers: (empty), held_buttons: EnumSet(), trigger_button: Some(Primary) } }
Clicked! Event: UiEvent { bubble_state: Cell { value: true }, data: MouseData { coordinates: Coordinates { screen: (242.0, 256.0), client: (26.0, 17.0), element: (16.0, 7.0), page: (26.0, 17.0) }, modifiers: (empty), held_buttons: EnumSet(), trigger_button: Some(Primary) } }
Clicked! Event: UiEvent { bubble_state: Cell { value: true }, data: MouseData { coordinates: Coordinates { screen: (242.0, 256.0), client: (26.0, 17.0), element: (16.0, 7.0), page: (26.0, 17.0) }, modifiers: (empty), held_buttons: EnumSet(), trigger_button: Some(Primary) } }
```
To learn what the different event types provide, read the [events module docs](https://docs.rs/dioxus/latest/dioxus/events/index.html).
To learn what the different event types for HTML provide, read the [events module docs](https://docs.rs/dioxus-html/latest/dioxus_html/events/index.html).
### Stopping propagation
### Event propagation
When you have e.g. a `button` inside a `div`, any click on the `button` is also a click on the `div`. For this reason, Dioxus propagates the click event: first, it is triggered on the target element, then on parent elements. If you want to prevent this behavior, you can call `cancel_bubble()` on the event:
Some events will trigger first on the element the event originated at upward. For example, a click event on a `button` inside a `div` would first trigger the button's event listener and then the div's event listener.
> For more information about event propigation see [the mdn docs on event bubling](https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Building_blocks/Events#event_bubbling)
If you want to prevent this behavior, you can call `stop_propogation()` on the event:
```rust
{{#include ../../../examples/event_nested.rs:rsx}}
@ -45,7 +49,7 @@ In some instances, might want to avoid this default behavior. For this, you can
Any event handlers will still be called.
> Normally, in React or JavaScript, you'd call "preventDefault" on the event in the callback. Dioxus does *not* currently support this behavior. Note: this means you cannot conditionally prevent default behavior.
> Normally, in React or JavaScript, you'd call "preventDefault" on the event in the callback. Dioxus does *not* currently support this behavior. Note: this means you cannot conditionally prevent default behavior based on the data in the event.
## Handler Props

View file

@ -1,12 +1,12 @@
# Hooks and Component State
So far our components, being Rust functions, had no state they were always rendering the same thing. However, in a UI component, it is often useful to have stateful functionality to build user interactions. For example, you might want to track whether the user has openend a drop-down, and render different things accordingly.
So far our components have had no state like a normal rust functions. However, in a UI component, it is often useful to have stateful functionality to build user interactions. For example, you might want to track whether the user has opened a drop-down, and render different things accordingly.
For stateful logic, you can use hooks. Hooks are Rust functions that take a reference to `ScopeState` (in a component, you can pass `&cx`), and provide you with functionality and state.
Hooks allow us to create state in our components. Hooks are Rust functions that take a reference to `ScopeState` (in a component, you can pass `cx`), and provide you with functionality and state.
## `use_state` Hook
[`use_state`](https://docs.rs/dioxus/latest/dioxus/hooks/fn.use_state.html) is one of the simplest hooks.
[`use_state`](https://docs.rs/dioxus/latest/dioxus/prelude/fn.use_state.html) is one of the simplest hooks.
- You provide a closure that determines the initial value
- `use_state` gives you the current value, and a way to update it by setting it to something else
@ -21,7 +21,7 @@ For example, you might have seen the counter example, in which state (a number)
Every time the component's state changes, it re-renders, and the component function is called, so you can describe what you want the new UI to look like. You don't have to worry about "changing" anything just describe what you want in terms of the state, and Dioxus will take care of the rest!
> `use_state` returns your value wrapped in a smart pointer of type [`UseState`](https://docs.rs/dioxus/latest/dioxus/hooks/struct.UseState.html). This is why you can both read the value and update it, even within a handler.
> `use_state` returns your value wrapped in a smart pointer of type [`UseState`](https://docs.rs/dioxus/latest/dioxus/prelude/struct.UseState.html). This is why you can both read the value and update it, even within an event handler.
You can use multiple hooks in the same component if you want:
@ -40,13 +40,13 @@ But how can Dioxus differentiate between multiple hooks in the same component? A
{{#include ../../../examples/hooks_counter_two_state.rs:use_state_calls}}
```
This is only possible because the two hooks are always called in the same order, so Dioxus knows which is which. So the order you call hooks matters, which is why you must follow certain rules when using hooks:
This is only possible because the two hooks are always called in the same order, so Dioxus knows which is which. Because the order you call hooks matters, you must follow certain rules when using hooks:
1. Hooks may be only used in components or other hooks (we'll get to that later)
2. On every call to the component function
1. The same hooks must be called
2. In the same order
3. Hooks name's must start with `use_` so you don't accidentally confuse them with regular functions
3. Hooks name's should start with `use_` so you don't accidentally confuse them with regular functions
These rules mean that there are certain things you can't do with hooks:
@ -73,11 +73,11 @@ For example, suppose we want to maintain a `Vec` of values. If we stored it with
Thankfully, there is another hook for that, `use_ref`! It is similar to `use_state`, but it lets you get a mutable reference to the contained data.
Here's a simple example that keeps a list of events in a `use_ref`. We can acquire write access to the state with `.write()`, and then just `.push` a new value to the state:
Here's a simple example that keeps a list of events in a `use_ref`. We can acquire write access to the state with `.with_mut()`, and then just `.push` a new value to the state:
```rust
{{#include ../../../examples/hooks_use_ref.rs:component}}
```
> The return values of `use_state` and `use_ref`, (`UseState` and `UseRef`, respectively) are in some ways similar to [`Cell`](https://doc.rust-lang.org/std/cell/) and [`RefCell`](https://doc.rust-lang.org/std/cell/struct.RefCell.html) they provide interior mutability. However, these Dioxus wrappers also ensure that the component gets re-rendered whenever you change the state.
> The return values of `use_state` and `use_ref` (`UseState` and `UseRef`, respectively) are in some ways similar to [`Cell`](https://doc.rust-lang.org/std/cell/) and [`RefCell`](https://doc.rust-lang.org/std/cell/struct.RefCell.html) they provide interior mutability. However, these Dioxus wrappers also ensure that the component gets re-rendered whenever you change the state.

View file

@ -1,26 +1,23 @@
# Router
In many of your apps, you'll want to have different "scenes". For a webpage, these scenes might be the different webpages with their own content.
In many of your apps, you'll want to have different "scenes". For a webpage, these scenes might be the different webpages with their own content. For a desktop app, these scenes might be different views in your app.
You could write your own scene management solution quite simply too. However, to save you the effort, Dioxus supports a first-party solution for scene management called Dioxus Router.
To unify these platforms, Dioxus provides a first-party solution for scene management called Dioxus Router.
## What is it?
For an app like the Dioxus landing page (https://dioxuslabs.com), we want to have different pages. A quick sketch of an app would be something like:
For an app like the Dioxus landing page (https://dioxuslabs.com), we want to have several different scenes:
- Homepage
- Blog
- Example showcase
Each of these scenes is independent we don't want to render both the homepage and blog at the same time.
This is where the router crates come in handy. To make sure we're using the router, simply add the `"router"` feature to your dioxus dependency.
The Dioxus router makes it easy to create these scenes. To make sure we're using the router, add the `dioxus-router` package to your `Cargo.toml`.
```toml
[dependencies]
dioxus = { version = "*" }
dioxus-router = { version = "*" }
```shell
cargo add dioxus-router
```
@ -30,8 +27,11 @@ Unlike other routers in the Rust ecosystem, our router is built declaratively. T
```rust
rsx!{
// All of our routes will be rendered inside this Router component
Router {
// if the current location is "/home", render the Home component
Route { to: "/home", Home {} }
// if the current location is "/blog", render the Blog component
Route { to: "/blog", Blog {} }
}
}
@ -48,6 +48,7 @@ rsx!{
Router {
Route { to: "/home", Home {} }
Route { to: "/blog", Blog {} }
// if the current location doesn't match any of the above routes, render the NotFound component
Route { to: "", NotFound {} }
}
}
@ -61,6 +62,7 @@ rsx!{
Router {
Route { to: "/home", Home {} }
Route { to: "/blog", Blog {} }
// if the current location doesn't match any of the above routes, redirect to "/home"
Redirect { from: "", to: "/home" }
}
}
@ -82,6 +84,4 @@ rsx!{
## More reading
This page is just meant to be a very brief overview of the router to show you that there's a powerful solution already built for a very common problem. For more information about the router, definitely check out its book or check out some of the examples.
The router has its own documentation! [Available here](https://dioxuslabs.com/router/guide/).
This page is just a very brief overview of the router. For more information, check out [the router book](https://dioxuslabs.com/router/guide/) or some of [the router examples](https://github.com/DioxusLabs/dioxus/blob/master/examples/router.rs).

View file

@ -6,9 +6,9 @@ Often, multiple components need to access the same state. Depending on your need
One approach to share state between components is to "lift" it up to the nearest common ancestor. This means putting the `use_state` hook in a parent component, and passing the needed values down as props.
For example, suppose we want to build a meme editor. We want to have an input to edit the meme caption, but also a preview of the meme with the caption. Logically, the meme and the input are 2 separate components, but they need access to the same state (the current caption).
Suppose we want to build a meme editor. We want to have an input to edit the meme caption, but also a preview of the meme with the caption. Logically, the meme and the input are 2 separate components, but they need access to the same state (the current caption).
> Of course, in this simple example, we could write everything in one component but it is better to split everything out in smaller components to make the code more reusable and easier to maintain (this is even more important for larger, complex apps).
> Of course, in this simple example, we could write everything in one component but it is better to split everything out in smaller components to make the code more reusable, maintainable, and performant (this is even more important for larger, complex apps).
We start with a `Meme` component, responsible for rendering a meme with a given caption:
```rust
@ -57,7 +57,7 @@ As a result, any child component of `App` (direct or not), can access the `DarkM
{{#include ../../../examples/meme_editor_dark_mode.rs:use_context}}
```
> `use_context` returns `Option<UseSharedState<DarkMode>>` here. If the context has been provided, the value is `Some(UseSharedState)`, which you can call `.read` or `.write` on, similarly to `UseRef`. Otherwise, the value is `None`.
> `use_context` returns `Option<UseSharedState<DarkMode>>` here. If the context has been provided, the value is `Some(UseSharedState<DarkMode>)`, which you can call `.read` or `.write` on, similarly to `UseRef`. Otherwise, the value is `None`.
For example, here's how we would implement the dark mode toggle, which both reads the context (to determine what color it should render) and writes to it (to toggle dark mode):
```rust

View file

@ -19,7 +19,7 @@ Notice the flexibility you can:
## Uncontrolled Inputs
As an alternative to controlled inputs, you can simply let the platform keep track of the input values. If we don't tell a HTML input what content it should have, it be editable anyway (this is built into the webview). This approach can be more performant, but less flexible. For example, it's harder to keep the input in sync with another element.
As an alternative to controlled inputs, you can simply let the platform keep track of the input values. If we don't tell a HTML input what content it should have, it will be editable anyway (this is built into the browser). This approach can be more performant, but less flexible. For example, it's harder to keep the input in sync with another element.
Since you don't necessarily have the current value of the uncontrolled input in state, you can access it either by listening to `oninput` events (similarly to controlled components), or, if the input is part of a form, you can access the form data in the form events (e.g. `oninput` or `onsubmit`):

View file

@ -125,8 +125,10 @@ We are currently working on our own build tool called [Dioxus CLI](https://githu
- ability to publish to github/netlify/vercel
- bundling for iOS/Desktop/etc
### LiveView / Server Component Support
### Server Component Support
The internal architecture of Dioxus was designed from day one to support the `LiveView` use-case, where a web server hosts a running app for each connected user. As of today, there is no first-class LiveView support you'll need to wire this up yourself.
While not currently fully implemented, the expectation is that LiveView apps can be a hybrid between Wasm and server-rendered where only portions of a page are "live" and the rest of the page is either server-rendered, statically generated, or handled by the host SPA.
While not currently fully implemented, the expectation is that LiveView apps can be a hybrid between Wasm and server-rendered where only portions of a page are "live" and the rest of the page is either server-rendered, statically generated, or handled by the host SPA.
### Native rendering
We are currently working on a native renderer for Dioxus using WGPU called [Blitz](https://github.com/DioxusLabs/blitz/). This will allow you to build apps that are rendered natively for iOS, Android, and Desktop.

View file

@ -0,0 +1 @@
# Roteamento

View file

@ -1 +0,0 @@
this directory is for deploying into

View file

@ -10,7 +10,7 @@ fn main() {
fn app(cx: Scope) -> Element {
let window = use_window(cx);
let emails_sent = use_ref(cx, || vec![]);
let emails_sent = use_ref(cx, Vec::new);
let tx = use_coroutine(cx, |mut rx: UnboundedReceiver<String>| {
to_owned![emails_sent];
@ -56,7 +56,7 @@ struct ComposeProps {
}
fn compose(cx: Scope<ComposeProps>) -> Element {
let user_input = use_state(cx, || String::new());
let user_input = use_state(cx, String::new);
let window = use_window(cx);
cx.render(rsx! {

View file

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

View file

@ -10,6 +10,7 @@ fn main() {
static NAME: Atom<String> = |_| "world".to_string();
fn app(cx: Scope) -> Element {
use_init_atom_root(cx);
let name = use_read(cx, NAME);
cx.render(rsx! {

View file

@ -1,12 +1,11 @@
use dioxus::prelude::*;
use dioxus_desktop::{use_window, WindowBuilder};
fn main() {
dioxus_desktop::launch(app);
}
fn app(cx: Scope) -> Element {
let window = use_window(cx);
let window = dioxus_desktop::use_window(cx);
cx.render(rsx! {
div {

View file

@ -1,5 +1,5 @@
use dioxus::prelude::*;
use dioxus_desktop::{tao::dpi::PhysicalPosition, use_window, Config, LogicalSize, WindowBuilder};
use dioxus_desktop::{tao::dpi::PhysicalPosition, use_window, LogicalSize, WindowBuilder};
fn main() {
dioxus_desktop::launch_cfg(app, make_config());

View file

@ -1,3 +1,5 @@
#![allow(non_snake_case)]
use dioxus::prelude::*;
use dioxus_router::*;

View file

@ -20,7 +20,7 @@
[Website](https://dioxuslabs.com) |
[Guides](https://dioxuslabs.com/guide/) |
[API Docs](https://docs.rs/dioxus-autofmt/latest/dioxus-autofmt) |
[API Docs](https://docs.rs/dioxus-autofmt/latest/dioxus_autofmt) |
[Chat](https://discord.gg/XgGxMSkvUM)
@ -45,5 +45,5 @@ This project is licensed under the [MIT license].
[mit license]: https://github.com/DioxusLabs/dioxus/blob/master/LICENSE-MIT
Unless you explicitly state otherwise, any contribution intentionally submitted
for inclusion in Dioxus by you, shall be licensed as MIT, without any additional
for inclusion in Dioxus by you shall be licensed as MIT without any additional
terms or conditions.

View file

@ -1,33 +1,18 @@
use std::{
collections::{HashMap, VecDeque},
fmt::{Result, Write},
};
//! The output buffer that supports some helpful methods
//! These are separate from the input so we can lend references between the two
//!
//!
//!
use dioxus_rsx::{BodyNode, ElementAttr, ElementAttrNamed, IfmtInput};
use proc_macro2::{LineColumn, Span};
use syn::{spanned::Spanned, Expr};
use std::fmt::{Result, Write};
#[derive(Default, Debug)]
use dioxus_rsx::IfmtInput;
/// The output buffer that tracks indent and string
#[derive(Debug, Default)]
pub struct Buffer {
pub src: Vec<String>,
pub cached_formats: HashMap<Location, String>,
pub buf: String,
pub indent: usize,
pub comments: VecDeque<usize>,
}
#[derive(Clone, Copy, Hash, PartialEq, Eq, Debug)]
pub struct Location {
pub line: usize,
pub col: usize,
}
impl Location {
pub fn new(start: LineColumn) -> Self {
Self {
line: start.line,
col: start.column,
}
}
}
impl Buffer {
@ -62,160 +47,14 @@ impl Buffer {
writeln!(self.buf)
}
// Expects to be written directly into place
pub fn write_ident(&mut self, node: &BodyNode) -> Result {
match node {
BodyNode::Element(el) => self.write_element(el),
BodyNode::Component(component) => self.write_component(component),
BodyNode::Text(text) => self.write_text(text),
BodyNode::RawExpr(exp) => self.write_raw_expr(exp),
_ => Ok(()),
}
}
pub fn write_text(&mut self, text: &IfmtInput) -> Result {
write!(self.buf, "\"{}\"", text.source.as_ref().unwrap().value())
}
pub fn consume(self) -> Option<String> {
Some(self.buf)
}
pub fn write_comments(&mut self, child: Span) -> Result {
// collect all comments upwards
let start = child.start();
let line_start = start.line - 1;
for (id, line) in self.src[..line_start].iter().enumerate().rev() {
if line.trim().starts_with("//") || line.is_empty() {
if id != 0 {
self.comments.push_front(id);
}
} else {
break;
}
}
let mut last_was_empty = false;
while let Some(comment_line) = self.comments.pop_front() {
let line = &self.src[comment_line];
if line.is_empty() {
if !last_was_empty {
self.new_line()?;
}
last_was_empty = true;
} else {
last_was_empty = false;
self.tabbed_line()?;
write!(self.buf, "{}", self.src[comment_line].trim())?;
}
}
Ok(())
}
// Push out the indent level and write each component, line by line
pub fn write_body_indented(&mut self, children: &[BodyNode]) -> Result {
self.indent += 1;
self.write_body_no_indent(children)?;
self.indent -= 1;
Ok(())
}
pub fn write_body_no_indent(&mut self, children: &[BodyNode]) -> Result {
let last_child = children.len();
for (idx, child) in children.iter().enumerate() {
match child {
// check if the expr is a short
BodyNode::RawExpr { .. } => {
self.tabbed_line()?;
self.write_ident(child)?;
if idx != last_child - 1 {
write!(self.buf, ",")?;
}
}
_ => {
if self.current_span_is_primary(child.span()) {
self.write_comments(child.span())?;
}
self.tabbed_line()?;
self.write_ident(child)?;
}
}
}
Ok(())
}
pub(crate) fn is_short_attrs(&mut self, attributes: &[ElementAttrNamed]) -> usize {
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() {
match (line.trim().starts_with("//"), line.is_empty()) {
(true, _) => return 100000,
(_, true) => continue 'line,
_ => break 'line,
}
}
}
total += match &attr.attr {
ElementAttr::AttrText { value, name } => {
value.source.as_ref().unwrap().value().len() + name.span().line_length() + 3
}
ElementAttr::AttrExpression { name, value } => {
value.span().line_length() + name.span().line_length() + 3
}
ElementAttr::CustomAttrText { value, name } => {
value.source.as_ref().unwrap().value().len() + name.value().len() + 3
}
ElementAttr::CustomAttrExpression { name, value } => {
name.value().len() + value.span().line_length() + 3
}
ElementAttr::EventTokens { tokens, name } => {
let location = Location::new(tokens.span().start());
let len = if let std::collections::hash_map::Entry::Vacant(e) =
self.cached_formats.entry(location)
{
let formatted = prettyplease::unparse_expr(tokens);
let len = if formatted.contains('\n') {
10000
} else {
formatted.len()
};
e.insert(formatted);
len
} else {
self.cached_formats[&location].len()
};
len + name.span().line_length() + 3
}
};
}
total
}
pub fn retrieve_formatted_expr(&mut self, expr: &Expr) -> &str {
self.cached_formats
.entry(Location::new(expr.span().start()))
.or_insert_with(|| prettyplease::unparse_expr(expr))
.as_str()
}
}
trait SpanLength {
fn line_length(&self) -> usize;
}
impl SpanLength for Span {
fn line_length(&self) -> usize {
self.end().line - self.start().line
impl std::fmt::Write for Buffer {
fn write_str(&mut self, s: &str) -> std::fmt::Result {
self.buf.push_str(s);
Ok(())
}
}

View file

@ -1,4 +1,4 @@
use crate::{buffer::Location, Buffer};
use crate::{writer::Location, Writer};
use dioxus_rsx::*;
use quote::ToTokens;
use std::fmt::{Result, Write};
@ -19,7 +19,7 @@ enum ShortOptimization {
NoOpt,
}
impl Buffer {
impl Writer {
pub fn write_component(
&mut self,
Component {
@ -28,6 +28,7 @@ impl Buffer {
children,
manual_props,
prop_gen_args,
..
}: &Component,
) -> Result {
self.write_component_name(name, prop_gen_args)?;
@ -82,46 +83,46 @@ impl Buffer {
match opt_level {
ShortOptimization::Empty => {}
ShortOptimization::Oneliner => {
write!(self.buf, " ")?;
write!(self.out, " ")?;
self.write_component_fields(fields, manual_props, true)?;
if !children.is_empty() && !fields.is_empty() {
write!(self.buf, ", ")?;
write!(self.out, ", ")?;
}
for child in children {
self.write_ident(child)?;
}
write!(self.buf, " ")?;
write!(self.out, " ")?;
}
ShortOptimization::PropsOnTop => {
write!(self.buf, " ")?;
write!(self.out, " ")?;
self.write_component_fields(fields, manual_props, true)?;
if !children.is_empty() && !fields.is_empty() {
write!(self.buf, ",")?;
write!(self.out, ",")?;
}
self.write_body_indented(children)?;
self.tabbed_line()?;
self.out.tabbed_line()?;
}
ShortOptimization::NoOpt => {
self.write_component_fields(fields, manual_props, false)?;
if !children.is_empty() && !fields.is_empty() {
write!(self.buf, ",")?;
write!(self.out, ",")?;
}
self.write_body_indented(children)?;
self.tabbed_line()?;
self.out.tabbed_line()?;
}
}
write!(self.buf, "}}")?;
write!(self.out, "}}")?;
Ok(())
}
@ -133,16 +134,16 @@ impl Buffer {
let mut name = name.to_token_stream().to_string();
name.retain(|c| !c.is_whitespace());
write!(self.buf, "{name}")?;
write!(self.out, "{name}")?;
if let Some(generics) = generics {
let mut written = generics.to_token_stream().to_string();
written.retain(|c| !c.is_whitespace());
write!(self.buf, "{}", written)?;
write!(self.out, "{}", written)?;
}
write!(self.buf, " {{")?;
write!(self.out, " {{")?;
Ok(())
}
@ -157,18 +158,18 @@ impl Buffer {
while let Some(field) = field_iter.next() {
if !sameline {
self.indented_tabbed_line()?;
self.out.indented_tabbed_line()?;
}
let name = &field.name;
match &field.content {
ContentField::ManExpr(exp) => {
let out = prettyplease::unparse_expr(exp);
write!(self.buf, "{}: {}", name, out)?;
write!(self.out, "{}: {}", name, out)?;
}
ContentField::Formatted(s) => {
write!(
self.buf,
self.out,
"{}: \"{}\"",
name,
s.source.as_ref().unwrap().value()
@ -178,27 +179,27 @@ impl Buffer {
let out = prettyplease::unparse_expr(exp);
let mut lines = out.split('\n').peekable();
let first = lines.next().unwrap();
write!(self.buf, "{}: {}", name, first)?;
write!(self.out, "{}: {}", name, first)?;
for line in lines {
self.new_line()?;
self.indented_tab()?;
write!(self.buf, "{}", line)?;
self.out.new_line()?;
self.out.indented_tab()?;
write!(self.out, "{}", line)?;
}
}
}
if field_iter.peek().is_some() || manual_props.is_some() {
write!(self.buf, ",")?;
write!(self.out, ",")?;
if sameline {
write!(self.buf, " ")?;
write!(self.out, " ")?;
}
}
}
if let Some(exp) = manual_props {
if !sameline {
self.indented_tabbed_line()?;
self.out.indented_tabbed_line()?;
}
self.write_manual_props(exp)?;
}
@ -258,10 +259,10 @@ impl Buffer {
let first_line = lines.next().unwrap();
write!(self.buf, "..{first_line}")?;
write!(self.out, "..{first_line}")?;
for line in lines {
self.indented_tabbed_line()?;
write!(self.buf, "{line}")?;
self.out.indented_tabbed_line()?;
write!(self.out, "{line}")?;
}
Ok(())

View file

@ -1,35 +1,56 @@
use crate::Buffer;
use crate::Writer;
use dioxus_rsx::*;
use proc_macro2::Span;
use std::{fmt::Result, fmt::Write};
use syn::{spanned::Spanned, Expr};
use std::{
fmt::Result,
fmt::{self, Write},
};
use syn::{spanned::Spanned, token::Brace, Expr};
#[derive(Debug)]
enum ShortOptimization {
// Special because we want to print the closing bracket immediately
/// Special because we want to print the closing bracket immediately
///
/// IE
/// `div {}` instead of `div { }`
Empty,
// Special optimization to put everything on the same line
/// Special optimization to put everything on the same line and add some buffer spaces
///
/// IE
///
/// `div { "asdasd" }` instead of a multiline variant
Oneliner,
// Optimization where children flow but props remain fixed on top
/// Optimization where children flow but props remain fixed on top
PropsOnTop,
// The noisiest optimization where everything flows
/// The noisiest optimization where everything flows
NoOpt,
}
impl Buffer {
pub fn write_element(
&mut self,
Element {
/*
// whitespace
div {
// some whitespace
class: "asdasd"
// whjiot
asdasd // whitespace
}
*/
impl Writer {
pub fn write_element(&mut self, el: &Element) -> Result {
let Element {
name,
key,
attributes,
children,
_is_static,
}: &Element,
) -> Result {
brace,
} = el;
/*
1. Write the tag
2. Write the key
@ -37,7 +58,7 @@ impl Buffer {
4. Write the children
*/
write!(self.buf, "{name} {{")?;
write!(self.out, "{name} {{")?;
// decide if we have any special optimizations
// Default with none, opt the cases in one-by-one
@ -70,6 +91,9 @@ impl Buffer {
// If there's nothing at all, empty optimization
if attributes.is_empty() && children.is_empty() && key.is_none() {
opt_level = ShortOptimization::Empty;
// Write comments if they exist
self.write_todo_body(brace)?;
}
// multiline handlers bump everything down
@ -80,55 +104,56 @@ impl Buffer {
match opt_level {
ShortOptimization::Empty => {}
ShortOptimization::Oneliner => {
write!(self.buf, " ")?;
write!(self.out, " ")?;
self.write_attributes(attributes, key, true)?;
if !children.is_empty() && (!attributes.is_empty() || key.is_some()) {
write!(self.buf, ", ")?;
write!(self.out, ", ")?;
}
for (id, child) in children.iter().enumerate() {
self.write_ident(child)?;
if id != children.len() - 1 && children.len() > 1 {
write!(self.buf, ", ")?;
write!(self.out, ", ")?;
}
}
write!(self.buf, " ")?;
write!(self.out, " ")?;
}
ShortOptimization::PropsOnTop => {
if !attributes.is_empty() || key.is_some() {
write!(self.buf, " ")?;
write!(self.out, " ")?;
}
self.write_attributes(attributes, key, true)?;
if !children.is_empty() && (!attributes.is_empty() || key.is_some()) {
write!(self.buf, ",")?;
write!(self.out, ",")?;
}
if !children.is_empty() {
self.write_body_indented(children)?;
}
self.tabbed_line()?;
self.out.tabbed_line()?;
}
ShortOptimization::NoOpt => {
self.write_attributes(attributes, key, false)?;
if !children.is_empty() && (!attributes.is_empty() || key.is_some()) {
write!(self.buf, ",")?;
write!(self.out, ",")?;
}
if !children.is_empty() {
self.write_body_indented(children)?;
}
self.tabbed_line()?;
self.out.tabbed_line()?;
}
}
write!(self.buf, "}}")?;
write!(self.out, "}}")?;
Ok(())
}
@ -143,39 +168,39 @@ impl Buffer {
if let Some(key) = key {
if !sameline {
self.indented_tabbed_line()?;
self.out.indented_tabbed_line()?;
}
write!(
self.buf,
self.out,
"key: \"{}\"",
key.source.as_ref().unwrap().value()
)?;
if !attributes.is_empty() {
write!(self.buf, ",")?;
write!(self.out, ",")?;
if sameline {
write!(self.buf, " ")?;
write!(self.out, " ")?;
}
}
}
while let Some(attr) = attr_iter.next() {
self.indent += 1;
self.out.indent += 1;
if !sameline {
self.write_comments(attr.attr.start())?;
}
self.indent -= 1;
self.out.indent -= 1;
if !sameline {
self.indented_tabbed_line()?;
self.out.indented_tabbed_line()?;
}
self.write_attribute(attr)?;
if attr_iter.peek().is_some() {
write!(self.buf, ",")?;
write!(self.out, ",")?;
if sameline {
write!(self.buf, " ")?;
write!(self.out, " ")?;
}
}
}
@ -187,19 +212,19 @@ impl Buffer {
match &attr.attr {
ElementAttr::AttrText { name, value } => {
write!(
self.buf,
self.out,
"{name}: \"{value}\"",
value = value.source.as_ref().unwrap().value()
)?;
}
ElementAttr::AttrExpression { name, value } => {
let out = prettyplease::unparse_expr(value);
write!(self.buf, "{}: {}", name, out)?;
write!(self.out, "{}: {}", name, out)?;
}
ElementAttr::CustomAttrText { name, value } => {
write!(
self.buf,
self.out,
"\"{name}\": \"{value}\"",
name = name.value(),
value = value.source.as_ref().unwrap().value()
@ -208,7 +233,7 @@ impl Buffer {
ElementAttr::CustomAttrExpression { name, value } => {
let out = prettyplease::unparse_expr(value);
write!(self.buf, "\"{}\": {}", name.value(), out)?;
write!(self.out, "\"{}\": {}", name.value(), out)?;
}
ElementAttr::EventTokens { name, tokens } => {
@ -220,17 +245,17 @@ impl Buffer {
// a one-liner for whatever reason
// Does not need a new line
if lines.peek().is_none() {
write!(self.buf, "{}: {}", name, first)?;
write!(self.out, "{}: {}", name, first)?;
} else {
writeln!(self.buf, "{}: {}", name, first)?;
writeln!(self.out, "{}: {}", name, first)?;
while let Some(line) = lines.next() {
self.indented_tab()?;
write!(self.buf, "{}", line)?;
self.out.indented_tab()?;
write!(self.out, "{}", line)?;
if lines.peek().is_none() {
write!(self.buf, "")?;
write!(self.out, "")?;
} else {
writeln!(self.buf)?;
writeln!(self.out)?;
}
}
}
@ -254,8 +279,6 @@ impl Buffer {
""
};
// dbg!(beginning);
beginning.is_empty()
}
@ -268,6 +291,10 @@ impl Buffer {
if children.is_empty() {
// todo: allow elements with comments but no children
// like div { /* comment */ }
// or
// div {
// // some helpful
// }
return Some(0);
}
@ -342,6 +369,35 @@ impl Buffer {
}
}
}
/// empty everything except for some comments
fn write_todo_body(&mut self, brace: &Brace) -> fmt::Result {
let span = brace.span.span();
let start = span.start();
let end = span.end();
if start.line == end.line {
return Ok(());
}
writeln!(self.out)?;
for idx in start.line..end.line {
let line = &self.src[idx];
if line.trim().starts_with("//") {
for _ in 0..self.out.indent + 1 {
write!(self.out, " ")?
}
writeln!(self.out, "{}", line.trim()).unwrap();
}
}
for _ in 0..self.out.indent {
write!(self.out, " ")?
}
Ok(())
}
}
fn get_expr_length(expr: &Expr) -> Option<usize> {

View file

@ -1,45 +1,23 @@
//! pretty printer for rsx!
use std::fmt::{Result, Write};
use crate::Buffer;
use crate::Writer;
impl Buffer {
impl Writer {
pub fn write_raw_expr(&mut self, exp: &syn::Expr) -> Result {
/*
We want to normalize the expr to the appropriate indent level.
*/
// in a perfect world, just fire up the rust pretty printer
// pretty_print_rust_code_as_if_it_were_rustfmt()
use syn::spanned::Spanned;
let placement = exp.span();
let start = placement.start();
let end = placement.end();
// let num_spaces_desired = (self.indent * 4) as isize;
// print comments
// let mut queued_comments = vec![];
// let mut offset = 2;
// loop {
// let line = &self.src[start.line - offset];
// if line.trim_start().starts_with("//") {
// queued_comments.push(line);
// } else {
// break;
// }
// offset += 1;
// }
// let had_comments = !queued_comments.is_empty();
// for comment in queued_comments.into_iter().rev() {
// writeln!(self.buf, "{}", comment)?;
// }
// if the expr is on one line, just write it directly
if start.line == end.line {
write!(
self.buf,
self.out,
"{}",
&self.src[start.line - 1][start.column - 1..end.column].trim()
)?;
@ -50,7 +28,7 @@ impl Buffer {
// This involves unshifting the first line if it's aligned
let first_line = &self.src[start.line - 1];
write!(
self.buf,
self.out,
"{}",
&first_line[start.column - 1..first_line.len()].trim()
)?;
@ -66,7 +44,7 @@ impl Buffer {
};
for (id, line) in self.src[start.line..end.line].iter().enumerate() {
writeln!(self.buf)?;
writeln!(self.out)?;
// trim the leading whitespace
let line = match id {
x if x == (end.line - start.line) - 1 => &line[..end.column],
@ -75,52 +53,17 @@ impl Buffer {
if offset < 0 {
for _ in 0..-offset {
write!(self.buf, " ")?;
write!(self.out, " ")?;
}
write!(self.buf, "{}", line)?;
write!(self.out, "{}", line)?;
} else {
let offset = offset as usize;
let right = &line[offset..];
write!(self.buf, "{}", right)?;
write!(self.out, "{}", right)?;
}
}
// let first = &self.src[start.line - 1];
// let num_spaces_real = first.chars().take_while(|c| c.is_whitespace()).count() as isize;
// let offset = num_spaces_real - num_spaces_desired;
// for (row, line) in self.src[start.line - 1..end.line].iter().enumerate() {
// let line = match row {
// 0 => &line[start.column - 1..],
// a if a == (end.line - start.line) => &line[..end.column - 1],
// _ => line,
// };
// writeln!(self.buf)?;
// // trim the leading whitespace
// if offset < 0 {
// for _ in 0..-offset {
// write!(self.buf, " ")?;
// }
// write!(self.buf, "{}", line)?;
// } else {
// let offset = offset as usize;
// let right = &line[offset..];
// write!(self.buf, "{}", right)?;
// }
// }
Ok(())
}
}
// :(
// fn pretty_print_rust_code_as_if_it_were_rustfmt(code: &str) -> String {
// let formatted = prettyplease::unparse_expr(exp);
// for line in formatted.lines() {
// write!(self.buf, "{}", line)?;
// self.new_line()?;
// }
// }

View file

@ -1,13 +1,14 @@
use dioxus_rsx::CallBody;
use crate::buffer::*;
use crate::util::*;
use crate::writer::*;
mod buffer;
mod component;
mod element;
mod expr;
mod util;
mod writer;
/// A modification to the original file to be applied by an IDE
///
@ -101,10 +102,9 @@ pub fn fmt_file(contents: &str) -> Vec<FormattedBlock> {
}
pub fn write_block_out(body: CallBody) -> Option<String> {
let mut buf = Buffer {
let mut buf = Writer {
src: vec!["".to_string()],
indent: 0,
..Buffer::default()
..Writer::default()
};
// Oneliner optimization
@ -120,12 +120,13 @@ pub fn write_block_out(body: CallBody) -> Option<String> {
pub fn fmt_block(block: &str, indent_level: usize) -> Option<String> {
let body = syn::parse_str::<dioxus_rsx::CallBody>(block).ok()?;
let mut buf = Buffer {
let mut buf = Writer {
src: block.lines().map(|f| f.to_string()).collect(),
indent: indent_level,
..Buffer::default()
..Writer::default()
};
buf.out.indent = indent_level;
// Oneliner optimization
if buf.is_short_children(&body.roots).is_some() {
buf.write_ident(&body.roots[0]).unwrap();
@ -134,8 +135,8 @@ pub fn fmt_block(block: &str, indent_level: usize) -> Option<String> {
}
// writing idents leaves the final line ended at the end of the last ident
if buf.buf.contains('\n') {
buf.new_line().unwrap();
if buf.out.buf.contains('\n') {
buf.out.new_line().unwrap();
}
buf.consume()

View file

@ -0,0 +1,188 @@
use dioxus_rsx::{BodyNode, ElementAttr, ElementAttrNamed};
use proc_macro2::{LineColumn, Span};
use std::{
collections::{HashMap, VecDeque},
fmt::{Result, Write},
};
use syn::{spanned::Spanned, Expr};
use crate::buffer::Buffer;
#[derive(Debug, Default)]
pub struct Writer {
pub src: Vec<String>,
pub cached_formats: HashMap<Location, String>,
pub comments: VecDeque<usize>,
pub out: Buffer,
}
#[derive(Clone, Copy, Hash, PartialEq, Eq, Debug)]
pub struct Location {
pub line: usize,
pub col: usize,
}
impl Location {
pub fn new(start: LineColumn) -> Self {
Self {
line: start.line,
col: start.column,
}
}
}
impl Writer {
// Expects to be written directly into place
pub fn write_ident(&mut self, node: &BodyNode) -> Result {
match node {
BodyNode::Element(el) => self.write_element(el),
BodyNode::Component(component) => self.write_component(component),
BodyNode::Text(text) => self.out.write_text(text),
BodyNode::RawExpr(exp) => self.write_raw_expr(exp),
_ => Ok(()),
}
}
pub fn consume(self) -> Option<String> {
Some(self.out.buf)
}
pub fn write_comments(&mut self, child: Span) -> Result {
// collect all comments upwards
let start = child.start();
let line_start = start.line - 1;
for (id, line) in self.src[..line_start].iter().enumerate().rev() {
if line.trim().starts_with("//") || line.is_empty() {
if id != 0 {
self.comments.push_front(id);
}
} else {
break;
}
}
let mut last_was_empty = false;
while let Some(comment_line) = self.comments.pop_front() {
let line = &self.src[comment_line];
if line.is_empty() {
if !last_was_empty {
self.out.new_line()?;
}
last_was_empty = true;
} else {
last_was_empty = false;
self.out.tabbed_line()?;
write!(self.out, "{}", self.src[comment_line].trim())?;
}
}
Ok(())
}
// Push out the indent level and write each component, line by line
pub fn write_body_indented(&mut self, children: &[BodyNode]) -> Result {
self.out.indent += 1;
self.write_body_no_indent(children)?;
self.out.indent -= 1;
Ok(())
}
pub fn write_body_no_indent(&mut self, children: &[BodyNode]) -> Result {
let last_child = children.len();
let iter = children.iter().peekable().enumerate();
for (idx, child) in iter {
if self.current_span_is_primary(child.span()) {
self.write_comments(child.span())?;
}
match child {
// check if the expr is a short
BodyNode::RawExpr { .. } => {
self.out.tabbed_line()?;
self.write_ident(child)?;
if idx != last_child - 1 {
write!(self.out, ",")?;
}
}
_ => {
self.out.tabbed_line()?;
self.write_ident(child)?;
}
}
}
Ok(())
}
pub(crate) fn is_short_attrs(&mut self, attributes: &[ElementAttrNamed]) -> usize {
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() {
match (line.trim().starts_with("//"), line.is_empty()) {
(true, _) => return 100000,
(_, true) => continue 'line,
_ => break 'line,
}
}
}
total += match &attr.attr {
ElementAttr::AttrText { value, name } => {
value.source.as_ref().unwrap().value().len() + name.span().line_length() + 3
}
ElementAttr::AttrExpression { name, value } => {
value.span().line_length() + name.span().line_length() + 3
}
ElementAttr::CustomAttrText { value, name } => {
value.source.as_ref().unwrap().value().len() + name.value().len() + 3
}
ElementAttr::CustomAttrExpression { name, value } => {
name.value().len() + value.span().line_length() + 3
}
ElementAttr::EventTokens { tokens, name } => {
let location = Location::new(tokens.span().start());
let len = if let std::collections::hash_map::Entry::Vacant(e) =
self.cached_formats.entry(location)
{
let formatted = prettyplease::unparse_expr(tokens);
let len = if formatted.contains('\n') {
10000
} else {
formatted.len()
};
e.insert(formatted);
len
} else {
self.cached_formats[&location].len()
};
len + name.span().line_length() + 3
}
};
}
total
}
pub fn retrieve_formatted_expr(&mut self, expr: &Expr) -> &str {
self.cached_formats
.entry(Location::new(expr.span().start()))
.or_insert_with(|| prettyplease::unparse_expr(expr))
.as_str()
}
}
trait SpanLength {
fn line_length(&self) -> usize;
}
impl SpanLength for Span {
fn line_length(&self) -> usize {
self.end().line - self.start().line
}
}

View file

@ -30,3 +30,6 @@ twoway! ("key" => key);
// Disabled because we can't handle comments on exprs yet
twoway! ("multirsx" => multirsx);
// Disabled because we can't handle comments on exprs yet
twoway! ("commentshard" => commentshard);

View file

@ -0,0 +1,42 @@
rsx! {
// Comments
div {
// Comments
class: "asdasd",
// Comments
"hello world"
// Comments
expr1,
// Comments
expr2,
// Comments
// Comments
// Comments
// Comments
// Comments
expr3,
div {
// todo some work in here
}
div {
// todo some work in here
// todo some work in here
//
// todo some work in here
}
div {
// todo some work in here
class: "hello world",
// todo some work in here
class: "hello world"
}
}
}

View file

@ -8,7 +8,7 @@ license = "MIT/Apache-2.0"
repository = "https://github.com/DioxusLabs/dioxus/"
homepage = "https://dioxuslabs.com"
documentation = "https://dioxuslabs.com"
keywords = ["dom", "ui", "gui", "react", "wasm"]
keywords = ["dom", "ui", "gui", "react"]
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[lib]

View file

@ -20,7 +20,7 @@
[Website](https://dioxuslabs.com) |
[Guides](https://dioxuslabs.com/guide/) |
[API Docs](https://docs.rs/dioxus-core-macro/latest/dioxus-core-macro) |
[API Docs](https://docs.rs/dioxus-core-macro/latest/dioxus_core_macro) |
[Chat](https://discord.gg/XgGxMSkvUM)
@ -29,7 +29,7 @@
`dioxus-core-macro` provides a handful of helpful macros used by the `dioxus` crate. These include:
- The `rsx!` macro that underpins templates and node creation
- The `inline_props` that transforms function arguments into an auto-derived struct
- The `inline_props` macro transforms function arguments into an auto-derived struct
- The `format_args_f` macro which allows f-string formatting with support for expressions
@ -44,5 +44,5 @@ This project is licensed under the [MIT license].
[mit license]: https://github.com/DioxusLabs/dioxus/blob/master/LICENSE-MIT
Unless you explicitly state otherwise, any contribution intentionally submitted
for inclusion in Dioxus by you, shall be licensed as MIT, without any additional
for inclusion in Dioxus by you shall be licensed as MIT without any additional
terms or conditions.

3
packages/core/.vscode/settings.json vendored Normal file
View file

@ -0,0 +1,3 @@
{
"rust-analyzer.checkOnSave.allTargets": false
}

View file

@ -8,7 +8,7 @@ license = "MIT/Apache-2.0"
repository = "https://github.com/DioxusLabs/dioxus/"
homepage = "https://dioxuslabs.com"
documentation = "https://dioxuslabs.com"
keywords = ["dom", "ui", "gui", "react", "wasm"]
keywords = ["dom", "ui", "gui", "react"]
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

View file

@ -13,7 +13,7 @@ dioxus-core is a fast and featureful VirtualDom implementation written in and fo
- Error boundaries through the `anyhow` crate
- Customizable memoization
If just starting out, check out the Guides first.
If you are just starting, check out the Guides first.
# General Theory
@ -21,7 +21,7 @@ The dioxus-core `VirtualDom` object is built around the concept of a `Template`.
Each component in the VirtualDom works as a dedicated render loop where re-renders are triggered by events external to the VirtualDom, or from the components themselves.
When each component re-renders, it must return an `Element`. In Dioxus, the `Element` type is an alias for `Result<VNode>`. Between two renders, Dioxus compares the inner `VNode` object, and calculates the differences of the dynamic portions of each internal `Template`. If any attributes or elements are different between the old layout and new layout, Dioxus will write modifications to the `Mutations` object.
When each component re-renders, it must return an `Element`. In Dioxus, the `Element` type is an alias for `Result<VNode>`. Between two renders, Dioxus compares the inner `VNode` object and calculates the differences between the dynamic portions of each internal `Template`. If any attributes or elements are different between the old layout and the new layout, Dioxus will write modifications to the `Mutations` object.
Dioxus expects the target renderer to save its nodes in a list. Each element is given a numerical ID which can be used to directly index into that list for O(1) lookups.
@ -33,37 +33,48 @@ The `dioxus` crate exports the `rsx` macro which transforms a helpful, simpler s
First, start with your app:
```rust, ignore
```rust
# use dioxus::core::Mutations;
use dioxus::prelude::*;
// First, declare a root component
fn app(cx: Scope) -> Element {
cx.render(rsx!( div { "hello world" } ))
cx.render(rsx!{
div { "hello world" }
})
}
fn main() {
// Next, create a new VirtualDom using this app as the root component.
let mut dom = VirtualDom::new(app);
// The initial render of the dom will generate a stream of edits for the real dom to apply
let mutations = dom.rebuild();
// Somehow, you can apply these edits to the real dom
apply_edits_to_real_dom(mutations);
}
# fn apply_edits_to_real_dom(mutations: Mutations) {}
```
Then, we'll want to create a new VirtualDom using this app as the root component.
```rust, ignore
let mut dom = VirtualDom::new(app);
```
To build the app into a stream of mutations, we'll use [`VirtualDom::rebuild`]:
```rust, ignore
let mutations = dom.rebuild();
apply_edits_to_real_dom(mutations);
```
We can then wait for any asynchronous components or pending futures using the `wait_for_work()` method. If we have a deadline, then we can use render_with_deadline instead:
```rust
# #![allow(unused)]
# use dioxus::prelude::*;
```rust, ignore
# use std::time::Duration;
# async fn wait(mut dom: VirtualDom) {
// Wait for the dom to be marked dirty internally
dom.wait_for_work().await;
// Or wait for a deadline and then collect edits
dom.render_with_deadline(tokio::time::sleep(Duration::from_millis(16)));
let mutations = dom.render_with_deadline(tokio::time::sleep(Duration::from_millis(16)));
# }
```
If an event occurs from outside the virtualdom while waiting for work, then we can cancel the wait using a `select!` block and inject the event.
If an event occurs from outside the VirtualDom while waiting for work, then we can cancel the wait using a `select!` block and inject the event.
```rust, ignore
loop {
@ -88,18 +99,18 @@ Dioxus-core builds off the many frameworks that came before it. Notably, Dioxus
- React: hooks, concurrency, suspense
- Dodrio: bump allocation, double buffering, and some diffing architecture
Dioxus-core leverages some really cool techniques and hits a very high level of parity with mature frameworks. However, Dioxus also brings some new unique features:
Dioxus-core hits a very high level of parity with mature frameworks. However, Dioxus also brings some new unique features:
- managed lifetimes for borrowed data
- placeholder approach for suspended vnodes
- fiber/interruptible diffing algorithm
- custom memory allocator for vnodes and all text content
- custom memory allocator for VNodes and all text content
- support for fragments w/ lazy normalization
- slab allocator for scopes
- mirrored-slab approach for remote vdoms
- mirrored-slab approach for remote VirtualDoms
- dedicated subtrees for rendering into separate contexts from the same app
There's certainly more to the story, but these optimizations make Dioxus memory use and allocation count extremely minimal. For an average application, it is possible that zero allocations will need to be performed once the app has been loaded. Only when new components are added to the dom will allocations occur. For a given component, the space of old VNodes is dynamically recycled as new nodes are added. Additionally, Dioxus tracks the average memory footprint of previous components to estimate how much memory allocate for future components.
There's certainly more to the story, but these optimizations make Dioxus memory use and allocation count extremely minimal. For an average application, no allocations may be needed once the app has been loaded. Only when new components are added to the dom will allocations occur. For a given component, the space of old VNodes is dynamically recycled as new nodes are added. Additionally, Dioxus tracks the average memory footprint of previous components to estimate how much memory allocate for future components.
All in all, Dioxus treats memory as a valuable resource. Combined with the memory-efficient footprint of Wasm compilation, Dioxus apps can scale to thousands of components and still stay snappy.
@ -112,5 +123,5 @@ The final implementation of Dioxus must:
- Be concurrent. Components should be able to pause rendering to let the screen paint the next frame.
- Be disconnected from a specific renderer (no WebSys dependency in the core crate).
- Support server-side-rendering (SSR). VNodes should render to a string that can be served via a web server.
- Be "live". Components should be able to be both server rendered and client rendered without needing frontend APIs.
- Be modular. Components and hooks should be work anywhere without worrying about target platform.
- Be "live". Components should be able to be both server-rendered and client rendered without needing frontend APIs.
- Be modular. Components and hooks should work anywhere without worrying about the target platform.

View file

@ -1,9 +1,9 @@
use crate::nodes::RenderReturn;
use bumpalo::Bump;
use std::cell::Cell;
use std::cell::{Cell, UnsafeCell};
pub(crate) struct BumpFrame {
pub bump: Bump,
pub bump: UnsafeCell<Bump>,
pub node: Cell<*const RenderReturn<'static>>,
}
@ -11,7 +11,7 @@ impl BumpFrame {
pub(crate) fn new(capacity: usize) -> Self {
let bump = Bump::with_capacity(capacity);
Self {
bump,
bump: UnsafeCell::new(bump),
node: Cell::new(std::ptr::null()),
}
}
@ -26,4 +26,13 @@ impl BumpFrame {
unsafe { std::mem::transmute(&*node) }
}
pub(crate) fn bump(&self) -> &Bump {
unsafe { &*self.bump.get() }
}
#[allow(clippy::mut_from_ref)]
pub(crate) unsafe fn bump_mut(&self) -> &mut Bump {
unsafe { &mut *self.bump.get() }
}
}

View file

@ -81,9 +81,7 @@ impl<'b> VirtualDom {
// The best renderers will have templates prehydrated and registered
// Just in case, let's create the template using instructions anyways
if !self.templates.contains_key(&node.template.get().name) {
self.register_template(node.template.get());
}
self.register_template(node.template.get());
// we know that this will generate at least one mutation per node
self.mutations
@ -389,30 +387,36 @@ impl<'b> VirtualDom {
/// Insert a new template into the VirtualDom's template registry
pub(crate) fn register_template(&mut self, mut template: Template<'static>) {
// First, make sure we mark the template as seen, regardless if we process it
let (path, byte_index) = template.name.rsplit_once(':').unwrap();
let byte_index = byte_index.parse::<usize>().unwrap();
// if hot reloading is enabled, then we need to check for a template that has overriten this one
#[cfg(debug_assertions)]
if let Some(mut new_template) = self
// First, check if we've already seen this template
if self
.templates
.get_mut(path)
.and_then(|map| map.remove(&usize::MAX))
.get(&path)
.filter(|set| set.contains_key(&byte_index))
.is_none()
{
// the byte index of the hot reloaded template could be different
new_template.name = template.name;
template = new_template;
}
// if hot reloading is enabled, then we need to check for a template that has overriten this one
#[cfg(debug_assertions)]
if let Some(mut new_template) = self
.templates
.get_mut(path)
.and_then(|map| map.remove(&usize::MAX))
{
// the byte index of the hot reloaded template could be different
new_template.name = template.name;
template = new_template;
}
self.templates
.entry(path)
.or_default()
.insert(byte_index, template);
self.templates
.entry(path)
.or_default()
.insert(byte_index, template);
// If it's all dynamic nodes, then we don't need to register it
if !template.is_completely_dynamic() {
self.mutations.templates.push(template);
// If it's all dynamic nodes, then we don't need to register it
if !template.is_completely_dynamic() {
self.mutations.templates.push(template);
}
}
}

View file

@ -11,6 +11,7 @@
//! The logic for this was borrowed from <https://docs.rs/stack_dst/0.6.1/stack_dst/>. Unfortunately, this crate does not
//! support non-static closures, so we've implemented the core logic of `ValueA` in this module.
#[allow(unused_imports)]
use smallbox::{smallbox, space::S16, SmallBox};
use crate::{innerlude::VNode, ScopeState};

View file

@ -819,7 +819,6 @@ impl<'a, 'b> IntoTemplate<'a> for LazyNodes<'a, 'b> {
}
// Note that we're using the E as a generic but this is never crafted anyways.
#[doc(hidden)]
pub struct FromNodeIterator;
impl<'a, T, I> IntoDynNode<'a, FromNodeIterator> for T
where

View file

@ -31,7 +31,8 @@ impl VirtualDom {
// If the task completes...
if task.task.borrow_mut().as_mut().poll(&mut cx).is_ready() {
// Remove it from the scope so we dont try to double drop it when the scope dropes
self.scopes[task.scope.0].spawned_tasks.remove(&id);
let scope = &self.scopes[task.scope.0];
scope.spawned_tasks.borrow_mut().remove(&id);
// Remove it from the scheduler
tasks.try_remove(id.0);
@ -63,10 +64,10 @@ impl VirtualDom {
if let Poll::Ready(new_nodes) = as_pinned_mut.poll_unpin(&mut cx) {
let fiber = self.acquire_suspense_boundary(leaf.scope_id);
let scope = &mut self.scopes[scope_id.0];
let scope = &self.scopes[scope_id.0];
let arena = scope.current_frame();
let ret = arena.bump.alloc(match new_nodes {
let ret = arena.bump().alloc(match new_nodes {
Some(new) => RenderReturn::Ready(new),
None => RenderReturn::default(),
});

View file

@ -7,7 +7,6 @@ use crate::{
scopes::{ScopeId, ScopeState},
virtual_dom::VirtualDom,
};
use bumpalo::Bump;
use futures_util::FutureExt;
use std::{
mem,
@ -48,11 +47,10 @@ impl VirtualDom {
}))
}
fn acquire_current_scope_raw(&mut self) -> Option<*mut ScopeState> {
self.scope_stack
.last()
.copied()
.and_then(|id| self.scopes.get_mut(id.0).map(|f| f.as_mut() as *mut _))
fn acquire_current_scope_raw(&self) -> Option<*const ScopeState> {
let id = self.scope_stack.last().copied()?;
let scope = self.scopes.get(id.0)?;
Some(scope.as_ref())
}
pub(crate) fn run_scope(&mut self, scope_id: ScopeId) -> &RenderReturn {
@ -62,17 +60,10 @@ impl VirtualDom {
self.ensure_drop_safety(scope_id);
let mut new_nodes = unsafe {
let scope = self.scopes[scope_id.0].as_mut();
self.scopes[scope_id.0].previous_frame().bump_mut().reset();
// if this frame hasn't been intialized yet, we can guess the size of the next frame to be more efficient
if scope.previous_frame().bump.allocated_bytes() == 0 {
scope.previous_frame_mut().bump =
Bump::with_capacity(scope.current_frame().bump.allocated_bytes());
} else {
scope.previous_frame_mut().bump.reset();
}
let scope = &self.scopes[scope_id.0];
// Make sure to reset the hook counter so we give out hooks in the right order
scope.hook_idx.set(0);
// safety: due to how we traverse the tree, we know that the scope is not currently aliased
@ -142,7 +133,7 @@ impl VirtualDom {
let frame = scope.previous_frame();
// set the new head of the bump frame
let allocated = &*frame.bump.alloc(new_nodes);
let allocated = &*frame.bump().alloc(new_nodes);
frame.node.set(allocated);
// And move the render generation forward by one

View file

@ -73,7 +73,7 @@ pub struct ScopeState {
pub(crate) node_arena_1: BumpFrame,
pub(crate) node_arena_2: BumpFrame,
pub(crate) parent: Option<*mut ScopeState>,
pub(crate) parent: Option<*const ScopeState>,
pub(crate) id: ScopeId,
pub(crate) height: u32,
@ -85,7 +85,7 @@ pub struct ScopeState {
pub(crate) shared_contexts: RefCell<FxHashMap<TypeId, Box<dyn Any>>>,
pub(crate) tasks: Rc<Scheduler>,
pub(crate) spawned_tasks: FxHashSet<TaskId>,
pub(crate) spawned_tasks: RefCell<FxHashSet<TaskId>>,
pub(crate) borrowed_props: RefCell<Vec<*const VComponent<'static>>>,
pub(crate) attributes_to_drop: RefCell<Vec<*const Attribute<'static>>>,
@ -111,14 +111,6 @@ impl<'src> ScopeState {
}
}
pub(crate) fn previous_frame_mut(&mut self) -> &mut BumpFrame {
match self.render_cnt.get() % 2 {
1 => &mut self.node_arena_1,
0 => &mut self.node_arena_2,
_ => unreachable!(),
}
}
/// Get the name of this component
pub fn name(&self) -> &str {
self.name
@ -139,7 +131,7 @@ impl<'src> ScopeState {
/// If you need to allocate items that need to be dropped, use bumpalo's box.
pub fn bump(&self) -> &Bump {
// note that this is actually the previous frame since we use that as scratch space while the component is rendering
&self.previous_frame().bump
self.previous_frame().bump()
}
/// Get a handle to the currently active head node arena for this Scope
@ -556,7 +548,7 @@ impl<'src> ScopeState {
#[allow(clippy::mut_from_ref)]
pub fn use_hook<State: 'static>(&self, initializer: impl FnOnce() -> State) -> &mut State {
let cur_hook = self.hook_idx.get();
let mut hook_list = self.hook_list.borrow_mut();
let mut hook_list = self.hook_list.try_borrow_mut().expect("The hook list is already borrowed: This error is likely caused by trying to use a hook inside a hook which violates the rules of hooks.");
if cur_hook >= hook_list.len() {
hook_list.push(self.hook_arena.alloc(initializer()));

View file

@ -24,7 +24,9 @@ use std::{any::Any, borrow::BorrowMut, cell::Cell, collections::BTreeSet, future
///
/// Components are defined as simple functions that take [`Scope`] and return an [`Element`].
///
/// ```rust, ignore
/// ```rust
/// # use dioxus::prelude::*;
///
/// #[derive(Props, PartialEq)]
/// struct AppProps {
/// title: String
@ -39,7 +41,17 @@ use std::{any::Any, borrow::BorrowMut, cell::Cell, collections::BTreeSet, future
///
/// Components may be composed to make complex apps.
///
/// ```rust, ignore
/// ```rust
/// # #![allow(unused)]
/// # use dioxus::prelude::*;
///
/// # #[derive(Props, PartialEq)]
/// # struct AppProps {
/// # title: String
/// # }
///
/// static ROUTES: &str = "";
///
/// fn App(cx: Scope<AppProps>) -> Element {
/// cx.render(rsx!(
/// NavBar { routes: ROUTES }
@ -47,12 +59,33 @@ use std::{any::Any, borrow::BorrowMut, cell::Cell, collections::BTreeSet, future
/// Footer {}
/// ))
/// }
///
/// #[inline_props]
/// fn NavBar(cx: Scope, routes: &'static str) -> Element {
/// cx.render(rsx! {
/// div { "Routes: {routes}" }
/// })
/// }
///
/// fn Footer(cx: Scope) -> Element {
/// cx.render(rsx! { div { "Footer" } })
/// }
///
/// #[inline_props]
/// fn Title<'a>(cx: Scope<'a>, children: Element<'a>) -> Element {
/// cx.render(rsx! {
/// div { id: "title", children }
/// })
/// }
/// ```
///
/// To start an app, create a [`VirtualDom`] and call [`VirtualDom::rebuild`] to get the list of edits required to
/// draw the UI.
///
/// ```rust, ignore
/// ```rust
/// # use dioxus::prelude::*;
/// # fn App(cx: Scope) -> Element { cx.render(rsx! { div {} }) }
///
/// let mut vdom = VirtualDom::new(App);
/// let edits = vdom.rebuild();
/// ```
@ -549,7 +582,7 @@ impl VirtualDom {
loop {
// first, unload any complete suspense trees
for finished_fiber in self.finished_fibers.drain(..) {
let scope = &mut self.scopes[finished_fiber.0];
let scope = &self.scopes[finished_fiber.0];
let context = scope.has_context::<Rc<SuspenseContext>>().unwrap();
self.mutations

View file

@ -5,7 +5,7 @@
use bumpalo::Bump;
use dioxus::core::{ElementId, Mutation::*};
use dioxus::prelude::*;
use dioxus_core::{AttributeValue, BorrowedAttributeValue};
use dioxus_core::BorrowedAttributeValue;
#[test]
fn attrs_cycle() {

View file

@ -1,13 +1,11 @@
#![allow(non_snake_case)]
use dioxus::prelude::*;
use futures_util::Future;
#[test]
fn catches_panic() {
let mut dom = VirtualDom::new(app);
let a = dom.rebuild();
dbg!(a);
_ = dom.rebuild();
}
fn app(cx: Scope) -> Element {
@ -16,43 +14,21 @@ fn app(cx: Scope) -> Element {
h1 { "Title" }
NoneChild {}
ThrowChild {}
}
})
}
fn NoneChild(cx: Scope) -> Element {
fn NoneChild(_cx: Scope) -> Element {
None
}
fn PanicChild(cx: Scope) -> Element {
panic!("Rendering panicked for whatever reason");
cx.render(rsx! {
h1 { "It works!" }
})
}
fn ThrowChild(cx: Scope) -> Element {
cx.throw(std::io::Error::new(std::io::ErrorKind::AddrInUse, "asd"))?;
let g: i32 = "123123".parse().throw(cx)?;
let _g: i32 = "123123".parse().throw(cx)?;
cx.render(rsx! {
div {}
})
}
fn custom_allocator(cx: Scope) -> Element {
let g = String::new();
let p = g.as_str();
let g2 = cx.use_hook(|| 123);
// cx.spawn(async move {
// //
// // println!("Thig is {p}");
// });
None
}

View file

@ -137,213 +137,78 @@ fn free_works_on_root_hooks() {
assert_eq!(Rc::strong_count(&ptr), 1);
}
// #[test]
// fn old_props_arent_stale() {
// fn app(cx: Scope) -> Element {
// dbg!("rendering parent");
// let cnt = cx.use_hook(|| 0);
// *cnt += 1;
#[test]
fn supports_async() {
use std::time::Duration;
use tokio::time::sleep;
// if *cnt == 1 {
// render!(div { Child { a: "abcdef".to_string() } })
// } else {
// render!(div { Child { a: "abcdef".to_string() } })
// }
// }
fn app(cx: Scope) -> Element {
let colors = use_state(cx, || vec!["green", "blue", "red"]);
let padding = use_state(cx, || 10);
// #[derive(Props, PartialEq)]
// struct ChildProps {
// a: String,
// }
// fn Child(cx: Scope<ChildProps>) -> Element {
// dbg!("rendering child", &cx.props.a);
// render!(div { "child {cx.props.a}" })
// }
use_effect(cx, colors, |colors| async move {
sleep(Duration::from_millis(1000)).await;
colors.with_mut(|colors| colors.reverse());
});
// let mut dom = new_dom(app, ());
// let _ = dom.rebuild();
use_effect(cx, padding, |padding| async move {
sleep(Duration::from_millis(10)).await;
padding.with_mut(|padding| {
if *padding < 65 {
*padding += 1;
} else {
*padding = 5;
}
});
});
// dom.handle_message(SchedulerMsg::Immediate(ScopeId(0)));
// dom.work_with_deadline(|| false);
let big = colors[0];
let mid = colors[1];
let small = colors[2];
// dom.handle_message(SchedulerMsg::Immediate(ScopeId(0)));
// dom.work_with_deadline(|| false);
cx.render(rsx! {
div {
background: "{big}",
height: "stretch",
width: "stretch",
padding: "50",
label {
"hello",
}
div {
background: "{mid}",
height: "auto",
width: "stretch",
padding: "{padding}",
label {
"World",
}
div {
background: "{small}",
height: "auto",
width: "stretch",
padding: "20",
label {
"ddddddd",
}
}
},
}
})
}
// dom.handle_message(SchedulerMsg::Immediate(ScopeId(0)));
// dom.work_with_deadline(|| false);
let rt = tokio::runtime::Builder::new_current_thread()
.enable_time()
.build()
.unwrap();
// dbg!("forcing update to child");
rt.block_on(async {
let mut dom = VirtualDom::new(app);
let _ = dom.rebuild();
// 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 {
// render!(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);
// render!(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 {
// render!(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 {
// render!( NavBrand {}
// div {
// NavStart {}
// NavEnd {}
// }
// )
// }
// fn NavBrand(cx: Scope) -> Element {
// render!(div {})
// }
// fn NavStart(cx: Scope) -> Element {
// render!(div {})
// }
// fn NavEnd(cx: Scope) -> Element {
// render!(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);
// }
// }
for _ in 0..10 {
dom.wait_for_work().await;
let _edits = dom.render_immediate();
}
});
}

View file

@ -5,42 +5,48 @@ use std::future::IntoFuture;
use std::rc::Rc;
use std::time::Duration;
#[tokio::test]
async fn it_works() {
let mut dom = VirtualDom::new(app);
{
let mutations = dom.rebuild().santize();
// We should at least get the top-level template in before pausing for the children
// note: we dont test template edits anymore
// assert_eq!(
// mutations.templates,
// [
// CreateElement { name: "div" },
// CreateStaticText { value: "Waiting for child..." },
// CreateStaticPlaceholder,
// AppendChildren { m: 2 },
// SaveTemplate { name: "template", m: 1 }
// ]
// );
// And we should load it in and assign the placeholder properly
assert_eq!(
mutations.edits,
[
LoadTemplate { name: "template", index: 0, id: ElementId(1) },
// hmmmmmmmmm.... with suspense how do we guarantee that IDs increase linearly?
// can we even?
AssignId { path: &[1], id: ElementId(3) },
AppendChildren { m: 1, id: ElementId(0) },
]
);
}
#[test]
fn it_works() {
// wait just a moment, not enough time for the boundary to resolve
dom.wait_for_work().await;
tokio::runtime::Builder::new_current_thread()
.enable_time()
.build()
.unwrap()
.block_on(async {
let mut dom = VirtualDom::new(app);
{
let mutations = dom.rebuild().santize();
// We should at least get the top-level template in before pausing for the children
// note: we dont test template edits anymore
// assert_eq!(
// mutations.templates,
// [
// CreateElement { name: "div" },
// CreateStaticText { value: "Waiting for child..." },
// CreateStaticPlaceholder,
// AppendChildren { m: 2 },
// SaveTemplate { name: "template", m: 1 }
// ]
// );
// And we should load it in and assign the placeholder properly
assert_eq!(
mutations.edits,
[
LoadTemplate { name: "template", index: 0, id: ElementId(1) },
// hmmmmmmmmm.... with suspense how do we guarantee that IDs increase linearly?
// can we even?
AssignId { path: &[1], id: ElementId(3) },
AppendChildren { m: 1, id: ElementId(0) },
]
);
}
dom.wait_for_work().await;
});
}
fn app(cx: Scope) -> Element {

View file

@ -8,7 +8,7 @@ license = "MIT/Apache-2.0"
repository = "https://github.com/DioxusLabs/dioxus/"
homepage = "https://dioxuslabs.com"
documentation = "https://dioxuslabs.com"
keywords = ["dom", "ui", "gui", "react", "wasm"]
keywords = ["dom", "ui", "gui", "react"]
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]

View file

@ -20,7 +20,7 @@
[Website](https://dioxuslabs.com) |
[Guides](https://dioxuslabs.com/guide/) |
[API Docs](https://docs.rs/dioxus-desktop/latest/dioxus-desktop) |
[API Docs](https://docs.rs/dioxus-desktop/latest/dioxus_desktop) |
[Chat](https://discord.gg/XgGxMSkvUM)
@ -37,10 +37,10 @@ This requires that webview is installed on the target system. WebView is install
## Features
- Simple, one-line launch for desktop apps
- Dioxus virtualdom running on a native thread
- Dioxus VirtualDom running on a native thread
- Full HTML/CSS support via `wry` and `tao`
- Exposed `window` and `Proxy` types from tao for direct window manipulation
- Helpful hooks for
- Helpful hooks for accessing the window, WebView, and running javascript.
## Contributing
@ -53,5 +53,5 @@ This project is licensed under the [MIT license].
[mit license]: https://github.com/DioxusLabs/dioxus/blob/master/LICENSE-MIT
Unless you explicitly state otherwise, any contribution intentionally submitted
for inclusion in Dioxus by you, shall be licensed as MIT, without any additional
for inclusion in Dioxus by you shall be licensed as MIT without any additional
terms or conditions.

View file

@ -18,10 +18,11 @@ $ cargo new --bin demo
$ cd app
```
Add Dioxus with the `desktop` feature:
Add Dioxus and the `desktop` renderer feature:
```shell
$ cargo add dioxus --features desktop
$ cargo add dioxus
$ cargo add dioxus-desktop
```
Edit your `main.rs`:

View file

@ -9,7 +9,7 @@ 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"
rust-version = "1.65.0"
[dependencies]
dioxus-core = { path = "../core", version = "^0.3.0" }

View file

@ -229,7 +229,7 @@ of logic. Hooks provide us a way of retrieving state from the `Scope` and using
it to render UI elements.
By convention, all hooks are functions that should start with `use_`. We can
use hooks to define state and modify it from within listeners.
use hooks to define the state and modify it from within listeners.
```rust, ignore
fn app(cx: Scope) -> Element {
@ -249,7 +249,7 @@ In a sense, hooks let us add a field of state to our component without declaring
an explicit state struct. However, this means we need to "load" the struct in the right
order. If that order is wrong, then the hook will pick the wrong state and panic.
Most hooks you'll write are simply composition of other hooks:
Most hooks you'll write are simply compositions of other hooks:
```rust, ignore
fn use_username(cx: &ScopeState, id: Uuid) -> bool {
@ -309,7 +309,7 @@ Beyond this overview, Dioxus supports:
Good luck!
## Inspiration, Resources, Alternatives and Credits
## Inspiration, Resources, Alternatives, and Credits
Dioxus is inspired by:
- React: for its hooks, concurrency, suspense
@ -318,7 +318,7 @@ Dioxus is inspired by:
Alternatives to Dioxus include:
- Yew: supports function components and web, but no SSR, borrowed data, or bump allocation. Rather slow at times.
- Percy: supports function components, web, ssr, but lacks state management
- Sycamore: supports function components, web, ssr, but closer to SolidJS than React
- Sycamore: supports function components, web, ssr, but is closer to SolidJS than React
- MoonZoom/Seed: opinionated frameworks based on the Elm model (message, update) - no hooks
We've put a lot of work into making Dioxus ergonomic and *familiar*.

View file

@ -1,6 +1,6 @@
[package]
name = "fermi"
version = "0.2.1"
version = "0.3.0"
authors = ["Jonathan Kelley"]
edition = "2018"
description = "Global state management for Dioxus"
@ -8,7 +8,7 @@ license = "MIT/Apache-2.0"
repository = "https://github.com/DioxusLabs/dioxus/"
homepage = "https://dioxuslabs.com"
documentation = "https://dioxuslabs.com"
keywords = ["dom", "ui", "gui", "react", "wasm"]
keywords = ["dom", "ui", "gui", "react", "state-management"]
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

View file

@ -40,9 +40,9 @@ Inspired by atom-based state management solutions, all state in Fermi starts as
static NAME: Atom<&str> = |_| "Dioxus";
```
From anywhere in our app, we can read our the value of our atom:
From anywhere in our app, we can read the value of our atom:
```rust, ignore
```rust, ignores
fn NameCard(cx: Scope) -> Element {
let name = use_read(cx, NAME);
cx.render(rsx!{ h1 { "Hello, {name}"} })
@ -83,9 +83,9 @@ $ cargo run --example fermi
## Features
Broadly our feature set to required to be released includes:
Broadly our feature set required to be released includes:
- [x] Support for Atoms
- [x] Support for AtomRef (for values that aren't clone)
- [x] Support for AtomRef (for values that aren't `Clone`)
- [ ] Support for Atom Families
- [ ] Support for memoized Selectors
- [ ] Support for memoized SelectorFamilies

View file

@ -8,7 +8,7 @@ license = "MIT/Apache-2.0"
repository = "https://github.com/DioxusLabs/dioxus/"
homepage = "https://dioxuslabs.com"
documentation = "https://dioxuslabs.com"
keywords = ["dom", "ui", "gui", "react", "wasm"]
keywords = ["dom", "ui", "gui", "react"]
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]

View file

@ -20,7 +20,7 @@
[Website](https://dioxuslabs.com) |
[Guides](https://dioxuslabs.com/guide/) |
[API Docs](https://docs.rs/dioxus-hooks/latest/dioxus-hooks) |
[API Docs](https://docs.rs/dioxus-hooks/latest/dioxus_hooks) |
[Chat](https://discord.gg/XgGxMSkvUM)
@ -54,5 +54,5 @@ This project is licensed under the [MIT license].
[mit license]: https://github.com/DioxusLabs/dioxus/blob/master/LICENSE-MIT
Unless you explicitly state otherwise, any contribution intentionally submitted
for inclusion in Dioxus by you, shall be licensed as MIT, without any additional
for inclusion in Dioxus by you shall be licensed as MIT without any additional
terms or conditions.

View file

@ -7,8 +7,8 @@ description = "HTML Element pack for Dioxus - a concurrent renderer-agnostic Vir
license = "MIT/Apache-2.0"
repository = "https://github.com/DioxusLabs/dioxus/"
homepage = "https://dioxuslabs.com"
documentation = "https://docs.rs/dioxus"
keywords = ["dom", "ui", "gui", "react", "wasm"]
documentation = "https://dioxuslabs.com"
keywords = ["dom", "ui", "gui", "react"]
[dependencies]
dioxus-core = { path = "../core", version = "^0.3.0" }

View file

@ -20,7 +20,7 @@
[Website](https://dioxuslabs.com) |
[Guides](https://dioxuslabs.com/guide/) |
[API Docs](https://docs.rs/dioxus-html/latest/dioxus-html) |
[API Docs](https://docs.rs/dioxus-html/latest/dioxus_html) |
[Chat](https://discord.gg/XgGxMSkvUM)
@ -61,7 +61,7 @@ impl DioxusElement for div {
All elements should be defined as a zero-sized-struct (also known as unit struct). These structs are zero-cost and just provide the type-level trickery to Rust for compile-time correct templates.
Attributes would then be implemented as methods on these unit structs.
Attributes would then be implemented as constants on these unit structs.
The HTML namespace is defined mostly with macros. However, the expanded form would look something like this:
```rust
@ -71,14 +71,8 @@ impl DioxusElement for base {
const NAME_SPACE: Option<&'static str> = None;
}
impl base {
#[inline]
fn href<'a>(&self, f: NodeFactory<'a>, v: Arguments) -> Attribute<'a> {
f.attr("href", v, None, false)
}
#[inline]
fn target<'a>(&self, f: NodeFactory<'a>, v: Arguments) -> Attribute<'a> {
f.attr("target", v, None, false)
}
const href: (&'static str, Option<'static str>, bool) = ("href", None, false);
const target: (&'static str, Option<'static str>, bool) = ("target", None, false);
}
```
Because attributes are defined as methods on the unit struct, they guard the attribute creation behind a compile-time correct interface.
@ -114,5 +108,5 @@ This project is licensed under the [MIT license].
[mit license]: https://github.com/DioxusLabs/dioxus/blob/master/LICENSE-MIT
Unless you explicitly state otherwise, any contribution intentionally submitted
for inclusion in Dioxus by you, shall be licensed as MIT, without any additional
for inclusion in Dioxus by you shall be licensed as MIT without any additional
terms or conditions.

View file

@ -294,7 +294,7 @@ builder_constructors! {
rel: LinkType DEFAULT,
sizes: String DEFAULT, // FIXME
title: String DEFAULT, // FIXME
r#type: Mime DEFAULT,
r#type: Mime "type",
integrity: String DEFAULT,
};
@ -312,7 +312,7 @@ builder_constructors! {
/// [`<style>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/style)
/// element.
style None {
r#type: Mime DEFAULT,
r#type: Mime "type",
media: String DEFAULT, // FIXME media query
nonce: Nonce DEFAULT,
title: String DEFAULT, // FIXME
@ -517,7 +517,7 @@ builder_constructors! {
ol None {
reversed: Bool DEFAULT,
start: isize DEFAULT,
r#type: OrderedListType DEFAULT,
r#type: OrderedListType "type",
};
/// Build a
@ -546,7 +546,7 @@ builder_constructors! {
href: Uri DEFAULT,
hreflang: LanguageTag DEFAULT,
target: Target DEFAULT,
r#type: Mime DEFAULT,
r#type: Mime "type",
// ping: SpacedList<Uri>,
// rel: SpacedList<LinkType>,
ping: SpacedList DEFAULT,
@ -737,7 +737,7 @@ builder_constructors! {
muted: Bool DEFAULT,
preload: Preload DEFAULT,
src: Uri DEFAULT,
r#loop: Bool DEFAULT,
r#loop: Bool "loop",
};
/// Build a
@ -783,7 +783,7 @@ builder_constructors! {
controls: Bool DEFAULT,
crossorigin: CrossOrigin DEFAULT,
height: usize DEFAULT,
r#loop: Bool DEFAULT,
r#loop: Bool "loop",
muted: Bool DEFAULT,
preload: Preload DEFAULT,
playsinline: Bool DEFAULT,
@ -801,7 +801,7 @@ builder_constructors! {
embed None {
height: usize DEFAULT,
src: Uri DEFAULT,
r#type: Mime DEFAULT,
r#type: Mime "type",
width: usize DEFAULT,
};
@ -819,13 +819,13 @@ builder_constructors! {
srcdoc: Uri DEFAULT,
width: usize DEFAULT,
marginWidth: String DEFAULT,
margin_width: String "marginWidth",
align: String DEFAULT,
longdesc: String DEFAULT,
scrolling: String DEFAULT,
marginHeight: String DEFAULT,
frameBorder: String DEFAULT,
margin_height: String "marginHeight",
frame_border: String "frameBorder",
// sandbox: SpacedSet<Sandbox>,
};
@ -837,7 +837,7 @@ builder_constructors! {
form: Id DEFAULT,
height: usize DEFAULT,
name: Id DEFAULT,
r#type: Mime DEFAULT,
r#type: Mime "type",
typemustmatch: Bool DEFAULT,
usemap: String DEFAULT, // TODO should be a fragment starting with '#'
width: usize DEFAULT,
@ -861,7 +861,7 @@ builder_constructors! {
/// element.
source None {
src: Uri DEFAULT,
r#type: Mime DEFAULT,
r#type: Mime "type",
};

View file

@ -1622,22 +1622,22 @@ trait_methods! {
ascent: "ascent";
/// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/attributeName>
attributeName: "attributeName";
attribute_name: "attributeName";
/// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/attributeType>
attributeType: "attributeType";
attribute_type: "attributeType";
/// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/azimuth>
azimuth: "azimuth";
/// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/baseFrequency>
baseFrequency: "baseFrequency";
base_frequency: "baseFrequency";
/// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/baseline-shift>
baseline_shift: "baseline-shift";
/// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/baseProfile>
baseProfile: "baseProfile";
base_profile: "baseProfile";
/// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/bbox>
bbox: "bbox";
@ -1652,7 +1652,7 @@ trait_methods! {
by: "by";
/// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/calcMode>
calcMode: "calcMode";
calc_mode: "calcMode";
/// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/cap-height>
cap_height: "cap-height";
@ -1664,7 +1664,7 @@ trait_methods! {
clip: "clip";
/// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/clipPathUnits>
clipPathUnits: "clipPathUnits";
clip_path_units: "clipPathUnits";
/// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/clip-path>
clip_path: "clip-path";
@ -1688,10 +1688,10 @@ trait_methods! {
color_rendering: "color-rendering";
/// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/contentScriptType>
contentScriptType: "contentScriptType";
content_script_type: "contentScriptType";
/// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/contentStyleType>
contentStyleType: "contentStyleType";
content_style_type: "contentStyleType";
/// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/crossorigin>
crossorigin: "crossorigin";
@ -1715,7 +1715,7 @@ trait_methods! {
descent: "descent";
/// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/diffuseConstant>
diffuseConstant: "diffuseConstant";
diffuse_constant: "diffuseConstant";
/// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/direction>
direction: "direction";
@ -1739,7 +1739,7 @@ trait_methods! {
dy: "dy";
/// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/edgeMode>
edgeMode: "edgeMode";
edge_mode: "edgeMode";
/// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/elevation>
elevation: "elevation";
@ -1829,13 +1829,13 @@ trait_methods! {
glyph_orientation_vertical: "glyph-orientation-vertical";
/// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/glyphRef>
glyphRef: "glyphRef";
glyph_ref: "glyphRef";
/// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/gradientTransform>
gradientTransform: "gradientTransform";
gradient_transform: "gradientTransform";
/// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/gradientUnits>
gradientUnits: "gradientUnits";
gradient_units: "gradientUnits";
/// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/hanging>
hanging: "hanging";
@ -1889,28 +1889,28 @@ trait_methods! {
k4: "k4";
/// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/kernelMatrix>
kernelMatrix: "kernelMatrix";
kernel_matrix: "kernelMatrix";
/// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/kernelUnitLength>
kernelUnitLength: "kernelUnitLength";
kernel_unit_length: "kernelUnitLength";
/// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/kerning>
kerning: "kerning";
/// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/keyPoints>
keyPoints: "keyPoints";
key_points: "keyPoints";
/// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/keySplines>
keySplines: "keySplines";
key_splines: "keySplines";
/// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/keyTimes>
keyTimes: "keyTimes";
key_times: "keyTimes";
/// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/lang>
lang: "lang";
/// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/lengthAdjust>
lengthAdjust: "lengthAdjust";
length_adjust: "lengthAdjust";
/// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/letter-spacing>
letter_spacing: "letter-spacing";
@ -1919,7 +1919,7 @@ trait_methods! {
lighting_color: "lighting-color";
/// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/limitingConeAngle>
limitingConeAngle: "limitingConeAngle";
limiting_cone_angle: "limitingConeAngle";
/// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/local>
local: "local";
@ -1930,26 +1930,26 @@ trait_methods! {
/// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/marker-mid>
marker_mid: "marker-mid";
/// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/marker_start>
marker_start: "marker_start";
/// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/marker-start>
marker_start: "marker-start";
/// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/markerHeight>
markerHeight: "markerHeight";
marker_height: "markerHeight";
/// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/markerUnits>
markerUnits: "markerUnits";
marker_units: "markerUnits";
/// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/markerWidth>
markerWidth: "markerWidth";
marker_width: "markerWidth";
/// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/mask>
mask: "mask";
/// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/maskContentUnits>
maskContentUnits: "maskContentUnits";
mask_content_units: "maskContentUnits";
/// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/maskUnits>
maskUnits: "maskUnits";
mask_units: "maskUnits";
/// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/mathematical>
mathematical: "mathematical";
@ -1973,7 +1973,7 @@ trait_methods! {
name: "name";
/// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/numOctaves>
numOctaves: "numOctaves";
num_octaves: "numOctaves";
/// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/offset>
offset: "offset";
@ -2015,16 +2015,16 @@ trait_methods! {
path: "path";
/// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/pathLength>
pathLength: "pathLength";
path_length: "pathLength";
/// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/patternContentUnits>
patternContentUnits: "patternContentUnits";
pattern_content_units: "patternContentUnits";
/// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/patternTransform>
patternTransform: "patternTransform";
pattern_transform: "patternTransform";
/// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/patternUnits>
patternUnits: "patternUnits";
pattern_units: "patternUnits";
/// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/ping>
ping: "ping";
@ -2036,22 +2036,22 @@ trait_methods! {
points: "points";
/// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/pointsAtX>
pointsAtX: "pointsAtX";
points_at_x: "pointsAtX";
/// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/pointsAtY>
pointsAtY: "pointsAtY";
points_at_y: "pointsAtY";
/// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/pointsAtZ>
pointsAtZ: "pointsAtZ";
points_at_z: "pointsAtZ";
/// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/preserveAlpha>
preserveAlpha: "preserveAlpha";
preserve_alpha: "preserveAlpha";
/// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/preserveAspectRatio>
preserveAspectRatio: "preserveAspectRatio";
preserve_aspect_ratio: "preserveAspectRatio";
/// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/primitiveUnits>
primitiveUnits: "primitiveUnits";
primitive_units: "primitiveUnits";
/// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/r>
r: "r";
@ -2060,13 +2060,13 @@ trait_methods! {
radius: "radius";
/// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/referrerPolicy>
referrerPolicy: "referrerPolicy";
referrer_policy: "referrerPolicy";
/// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/refX>
refX: "refX";
ref_x: "refX";
/// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/refY>
refY: "refY";
ref_y: "refY";
/// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/rel>
rel: "rel";
@ -2075,16 +2075,16 @@ trait_methods! {
rendering_intent: "rendering-intent";
/// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/repeatCount>
repeatCount: "repeatCount";
repeat_count: "repeatCount";
/// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/repeatDur>
repeatDur: "repeatDur";
repeat_dur: "repeatDur";
/// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/requiredExtensions>
requiredExtensions: "requiredExtensions";
required_extensions: "requiredExtensions";
/// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/requiredFeatures>
requiredFeatures: "requiredFeatures";
required_features: "requiredFeatures";
/// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/restart>
restart: "restart";
@ -2120,22 +2120,22 @@ trait_methods! {
spacing: "spacing";
/// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/specularConstant>
specularConstant: "specularConstant";
specular_constant: "specularConstant";
/// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/specularExponent>
specularExponent: "specularExponent";
specular_exponent: "specularExponent";
/// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/speed>
speed: "speed";
/// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/spreadMethod>
spreadMethod: "spreadMethod";
spread_method: "spreadMethod";
/// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/startOffset>
startOffset: "startOffset";
start_offset: "startOffset";
/// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/stdDeviation>
stdDeviation: "stdDeviation";
std_deviation: "stdDeviation";
/// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/stemh>
stemh: "stemh";
@ -2144,13 +2144,13 @@ trait_methods! {
stemv: "stemv";
/// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/stitchTiles>
stitchTiles: "stitchTiles";
stitch_tiles: "stitchTiles";
/// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/stop_color>
stop_color: "stop_color";
/// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/stop-color>
stop_color: "stop-color";
/// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/stop_opacity>
stop_opacity: "stop_opacity";
/// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/stop-opacity>
stop_opacity: "stop-opacity";
/// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/strikethrough-position>
strikethrough_position: "strikethrough-position";
@ -2189,25 +2189,25 @@ trait_methods! {
style: "style";
/// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/surfaceScale>
surfaceScale: "surfaceScale";
surface_scale: "surfaceScale";
/// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/systemLanguage>
systemLanguage: "systemLanguage";
system_language: "systemLanguage";
/// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/tabindex>
tabindex: "tabindex";
/// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/tableValues>
tableValues: "tableValues";
table_values: "tableValues";
/// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/target>
target: "target";
/// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/targetX>
targetX: "targetX";
target_x: "targetX";
/// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/targetY>
targetY: "targetY";
target_y: "targetY";
/// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/text-anchor>
text_anchor: "text-anchor";
@ -2219,7 +2219,7 @@ trait_methods! {
text_rendering: "text-rendering";
/// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/textLength>
textLength: "textLength";
text_length: "textLength";
/// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/to>
to: "to";
@ -2342,6 +2342,6 @@ trait_methods! {
z: "z";
/// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/zoomAndPan>
zoomAndPan: "zoomAndPan";
zoom_and_pan: "zoomAndPan";
}

View file

@ -4,6 +4,7 @@ use crate::events::{
};
use crate::geometry::{ClientPoint, Coordinates, ElementPoint, PagePoint, ScreenPoint};
use crate::input_data::{decode_key_location, decode_mouse_button_set, MouseButton};
use crate::DragData;
use keyboard_types::{Code, Key, Modifiers};
use std::convert::TryInto;
use std::str::FromStr;
@ -40,6 +41,7 @@ uncheck_convert![
CompositionEvent => CompositionData,
KeyboardEvent => KeyboardData,
MouseEvent => MouseData,
MouseEvent => DragData,
TouchEvent => TouchData,
PointerEvent => PointerData,
WheelEvent => WheelData,
@ -117,6 +119,14 @@ impl From<&MouseEvent> for MouseData {
}
}
impl From<&MouseEvent> for DragData {
fn from(value: &MouseEvent) -> Self {
Self {
mouse: MouseData::from(value),
}
}
}
impl From<&TouchEvent> for TouchData {
fn from(e: &TouchEvent) -> Self {
Self {

View file

@ -20,7 +20,7 @@
[Website](https://dioxuslabs.com) |
[Guides](https://dioxuslabs.com/guide/) |
[API Docs](https://docs.rs/dioxus-interpreter-js/latest/dioxus-interpreter-js) |
[API Docs](https://docs.rs/dioxus-interpreter-js/latest/dioxus_interpreter_js) |
[Chat](https://discord.gg/XgGxMSkvUM)
@ -42,5 +42,5 @@ This project is licensed under the [MIT license].
[mit license]: https://github.com/DioxusLabs/dioxus/blob/master/LICENSE-MIT
Unless you explicitly state otherwise, any contribution intentionally submitted
for inclusion in Dioxus by you, shall be licensed as MIT, without any additional
for inclusion in Dioxus by you shall be licensed as MIT without any additional
terms or conditions.

View file

@ -144,8 +144,6 @@ class Interpreter {
this.nodes[root].textContent = text;
}
SetAttribute(id, field, value, ns) {
console.log("set attribute", id, field, value, ns);
if (value === null) {
this.RemoveAttribute(id, field, ns);
}
@ -228,7 +226,6 @@ class Interpreter {
}
MakeTemplateNode(node) {
console.log("making template node", node);
switch (node.type) {
case "Text":
return document.createTextNode(node.text);

View file

@ -5,7 +5,7 @@ edition = "2021"
repository = "https://github.com/DioxusLabs/dioxus/"
homepage = "https://dioxuslabs.com"
documentation = "https://dioxuslabs.com"
keywords = ["dom", "ui", "gui", "react", "wasm"]
keywords = ["dom", "ui", "gui", "react", "liveview"]
description = "Build server-side apps with Dioxus"
license = "MIT/Apache-2.0"

Some files were not shown because too many files have changed in this diff Show more