wip: book documentation
This commit is contained in:
parent
7dedad4c26
commit
16dbf4a6f8
10
Cargo.toml
10
Cargo.toml
|
@ -8,7 +8,7 @@ description = "Core functionality for Dioxus - a concurrent renderer-agnostic Vi
|
|||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
dioxus-core = { path = "./packages/core" }
|
||||
dioxus-core = { path = "./packages/core", version = "0.1.3" }
|
||||
dioxus-core-macro = { path = "./packages/core-macro", optional = true }
|
||||
dioxus-html = { path = "./packages/html", optional = true }
|
||||
dioxus-web = { path = "./packages/web", optional = true }
|
||||
|
@ -63,14 +63,6 @@ console_error_panic_hook = "0.1.6"
|
|||
rand = { version = "0.8.4", features = ["small_rng"] }
|
||||
wasm-bindgen = { version = "0.2.71", features = ["enable-interning"] }
|
||||
|
||||
# surf = { version = "2.3.1", default-features = false, features = [
|
||||
# "wasm-client",
|
||||
# ] }
|
||||
# surf = { version = "2.2.0", default-features = false, features = [
|
||||
# "wasm-client",
|
||||
# ], git = "https://github.com/jkelleyrtp/surf/", branch = "jk/fix-the-wasm" }
|
||||
|
||||
|
||||
[dev-dependencies.getrandom]
|
||||
version = "0.2"
|
||||
features = ["js"]
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
book/
|
|
@ -0,0 +1,30 @@
|
|||
[book]
|
||||
title = "Dioxus Documentation"
|
||||
description = "Create book from markdown files. Like Gitbook but implemented in Rust"
|
||||
authors = ["Jonathan Kelley"]
|
||||
language = "en"
|
||||
|
||||
[rust]
|
||||
edition = "2018"
|
||||
|
||||
[output.html]
|
||||
mathjax-support = true
|
||||
site-url = "/mdBook/"
|
||||
git-repository-url = "https://github.com/rust-lang/mdBook/tree/master/guide"
|
||||
edit-url-template = "https://github.com/rust-lang/mdBook/edit/master/guide/{path}"
|
||||
|
||||
[output.html.playground]
|
||||
editable = true
|
||||
line-numbers = true
|
||||
|
||||
[output.html.search]
|
||||
limit-results = 20
|
||||
use-boolean-and = true
|
||||
boost-title = 2
|
||||
boost-hierarchy = 2
|
||||
boost-paragraph = 1
|
||||
expand = true
|
||||
heading-split-level = 2
|
||||
|
||||
[output.html.redirect]
|
||||
"/format/config.html" = "configuration/index.html"
|
|
@ -1,40 +0,0 @@
|
|||
# Dioxus Chapter 0 - Intro + Index
|
||||
|
||||
## Guides
|
||||
|
||||
---
|
||||
|
||||
| Chapter | Description |
|
||||
| ------------------ | ------------------------------------------ |
|
||||
| 1-hello-world | Intro to Dioxus |
|
||||
| 2-utilities | Tools to make writing apps easier |
|
||||
| 3-vnode-macros | VNodes and the html! macro |
|
||||
| 4-hooks | Renderer + event integration using web_sys |
|
||||
| 5-context-api | Virtual DOM, patching/diffing |
|
||||
| 6-subscription API | Standard bundle of useful hooks |
|
||||
| 7-hello-world | Html + functional_component macro |
|
||||
| 8-hello-world | Blessed state management solutions |
|
||||
| 9-hello-world | Renderer + event integration using web_sys |
|
||||
| 10-hello-world | Renderer + event integration using web_sys |
|
||||
|
||||
## Development
|
||||
|
||||
---
|
||||
|
||||
| Package | Use |
|
||||
| ------- | ------------------------------------------------ |
|
||||
| full | Batteries-included entrypoint with feature flags |
|
||||
| core | Virtual DOM, diffing, patching, and events |
|
||||
| hooks | Standard hooks |
|
||||
| macro | Html + functional_component macro |
|
||||
| web | Renderer + event integration using web_sys |
|
||||
| live | Dedicated isomorphic framework |
|
||||
| recoil | Data-flow-graph state management |
|
||||
| redux | Reducer-style state management |
|
||||
| bearly | Simple and idiomatic state management |
|
||||
|
||||
## Books
|
||||
|
||||
---
|
||||
|
||||
Book of patterns
|
|
@ -1,139 +0,0 @@
|
|||
# VNode Macros
|
||||
|
||||
Dioxus comes preloaded with two macros for creating VNodes.
|
||||
|
||||
## html! macro
|
||||
|
||||
The html! macro supports a limited subset of the html standard. This macro will happily accept a copy-paste from something like tailwind builder. However, writing HTML by hand is a bit tedious - IDE tools for Rust don't support linting/autocomplete/syntax highlighting. RSX is much more natural for Rust programs and _does_ integrate well with Rust IDE tools.
|
||||
|
||||
There is also limited support for dynamic handlers, but it will function similarly to JSX.
|
||||
|
||||
You'll want to write RSX where you can, and in a future release we'll have a tool that automatically converts HTML to RSX.
|
||||
|
||||
```rust
|
||||
#[derive(PartialEq, Props)]
|
||||
struct ExampleProps { name: &'static str, pending: bool, count: i32 }
|
||||
|
||||
static Example: FC<ExampleProps> = |cx, props| {
|
||||
cx.render(html! {
|
||||
<div>
|
||||
<p> "Hello, {props.name}!" </p>
|
||||
<p> "Status: {props.pending}!" </p>
|
||||
<p> "Count {props.count}!" </p>
|
||||
</div>
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
## rsx! macro
|
||||
|
||||
The rsx! macro is a VNode builder macro designed especially for Rust programs. Writing these should feel very natural, much like assembling a struct. VSCode also supports these with code folding, bracket-tabbing, bracket highlighting, section selecting, inline documentation, and GOTO definition (no rename support yet 😔 ).
|
||||
|
||||
The Dioxus VSCode extension will eventually provide a macro to convert a selection of html! template and turn it into rsx!, so you'll never need to transcribe templates by hand.
|
||||
|
||||
It's also a bit easier on the eyes 🙂 than HTML.
|
||||
|
||||
```rust
|
||||
static Example: FC<ExampleProps> = |cx, props| {
|
||||
cx.render(rsx! {
|
||||
div {
|
||||
p {"Hello, {props.name}!"}
|
||||
p {"Status: {props.pending}!"}
|
||||
p {"Count {props.count}!"}
|
||||
}
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
Each element takes a comma-separated list of expressions to build the node. Roughly, here's how they work:
|
||||
|
||||
- `name: value` sets a property on this element.
|
||||
- `"text"` adds a new text element
|
||||
- `tag {}` adds a new child element
|
||||
- `CustomTag {}` adds a new child component
|
||||
- `{expr}` pastes the `expr` tokens literally. They must be `IntoIterator<T> where T: IntoVnode` to work properly
|
||||
|
||||
Commas are entirely optional, but might be useful to delineate between elements and attributes.
|
||||
|
||||
The `render` function provides an **extremely efficient** allocator for VNodes and text, so try not to use the `format!` macro in your components. Rust's default `ToString` methods pass through the global allocator, but all text in components is allocated inside a manually-managed Bump arena. To push you in the right direction, all text-based attributes take `std::fmt::Arguments` directly, so you'll want to reach for `format_args!` when the built-in `f-string` interpolation just doesn't cut it.
|
||||
|
||||
```rust
|
||||
pub static Example: FC<()> = |cx, props|{
|
||||
|
||||
let text = "example";
|
||||
|
||||
cx.render(rsx!{
|
||||
div {
|
||||
h1 { "Example" },
|
||||
|
||||
{title}
|
||||
|
||||
// fstring interpolation
|
||||
"{text}"
|
||||
|
||||
p {
|
||||
// Attributes
|
||||
tag: "type",
|
||||
|
||||
// Anything that implements display can be an attribute
|
||||
abc: 123,
|
||||
enabled: true,
|
||||
|
||||
// attributes also supports interpolation
|
||||
// `class` is not a restricted keyword unlike JS and ClassName
|
||||
class: "big small wide short {text}",
|
||||
|
||||
class: format_args!("attributes take fmt::Arguments. {}", 99)
|
||||
|
||||
tag: {"these tokens are placed directly"}
|
||||
|
||||
// Children
|
||||
a { "abcder" },
|
||||
|
||||
// Children with attributes
|
||||
h2 { "hello", class: "abc-123" },
|
||||
|
||||
// Child components
|
||||
CustomComponent { a: 123, b: 456, key: "1" },
|
||||
|
||||
// Child components with paths
|
||||
crate::components::CustomComponent { a: 123, b: 456, key: "1" },
|
||||
|
||||
// Iterators
|
||||
{ 0..3.map(|i| rsx!( h1 {"{:i}"} )) },
|
||||
|
||||
// More rsx!, or even html!
|
||||
{ rsx! { div { } } },
|
||||
{ html! { <div> </div> } },
|
||||
|
||||
// Matching
|
||||
// Requires rendering the nodes first.
|
||||
// rsx! is lazy, and the underlying closures cannot have the same type
|
||||
// Rendering produces the VNode type
|
||||
{match rand::gen_range::<i32>(1..3) {
|
||||
1 => rsx!(cx, h1 { "big" })
|
||||
2 => rsx!(cx, h2 { "medium" })
|
||||
_ => rsx!(cx, h3 { "small" })
|
||||
}}
|
||||
|
||||
// Optionals
|
||||
{true.and_then(|f| rsx!{ h1 {"Conditional Rendering"} })}
|
||||
|
||||
// Child nodes
|
||||
// Returns &[VNode]
|
||||
{cx.children()}
|
||||
|
||||
// Duplicating nodes
|
||||
// Clones the nodes by reference, so they are literally identical
|
||||
{{
|
||||
let node = rsx!(cx, h1{ "TopNode" });
|
||||
(0..10).map(|_| node.clone())
|
||||
}}
|
||||
|
||||
// Any expression that is `IntoVNode`
|
||||
{expr}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
```
|
File diff suppressed because one or more lines are too long
|
@ -0,0 +1,101 @@
|
|||
# Introduction
|
||||
|
||||
![dioxuslogo](./images/dioxuslogo_full.png)
|
||||
|
||||
**Dioxus** is a framework and ecosystem for building fast, scalable, and robust user interfaces with the Rust programming language. This guide will help you get up-and-running with Dioxus running on the Web, Desktop, Mobile, and more.
|
||||
|
||||
```rust, ignore
|
||||
// An example Dioxus app - closely resembles React
|
||||
static App: FC<()> = |cx, props| {
|
||||
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!" }
|
||||
))
|
||||
};
|
||||
```
|
||||
|
||||
The Dioxus API and patterns closely resemble React - if this guide is lacking in any general concept or an error message is confusing, we recommend substituting "React" for "Dioxus" in your web search terms. A major goal of Dioxus is to provide a familiar toolkit for UI in Rust, so we've chosen to follow in the footsteps of popular UI frameworks (React, Redux, etc) - if you know React, then you already know Dioxus. If you don't know either, this guide will still help you!
|
||||
|
||||
|
||||
|
||||
### Web Support
|
||||
---
|
||||
|
||||
The Web is the most-supported target platform for Dioxus. To run on the Web, your app must be compiled to WebAssembly and depend on the `dioxus` crate with the `web` feature enabled. Because of the WASM limitation, 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).
|
||||
|
||||
Because the web is a fairly mature platform, we expect there to be very little API churn for web-based features.
|
||||
|
||||
[Jump to the getting started guide for the web.]()
|
||||
|
||||
Examples:
|
||||
- [TodoMVC](https://github.com/dioxusLabs/todomvc/)
|
||||
- [ECommerce]()
|
||||
- [Photo Editor]()
|
||||
|
||||
[![todomvc](https://github.com/DioxusLabs/todomvc/raw/master/example.png)](https://github.com/dioxusLabs/todomvc/)
|
||||
|
||||
### SSR Support
|
||||
---
|
||||
Dioxus supports server-side rendering! In a pinch, you can literally "debug" the VirtualDom:
|
||||
|
||||
```rust
|
||||
let dom = VirtualDom::new(App);
|
||||
println!("{:?}, dom");
|
||||
```
|
||||
|
||||
For rendering statically to an `.html` file or from a WebServer, then you'll want to make sure the `ssr` feature is enabled in the `dioxus` crate and use the `dioxus::ssr` API. We don't expect the SSR API to change drastically in the future.
|
||||
|
||||
```rust
|
||||
let contents = dioxus::ssr::render_vdom(&dom, |c| c);
|
||||
```
|
||||
|
||||
|
||||
[Jump to the getting started guide for SSR.]()
|
||||
|
||||
Examples:
|
||||
- [Example DocSite]()
|
||||
- [Tide WebServer]()
|
||||
- [Markdown to fancy HTML generator]()
|
||||
|
||||
### Desktop 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.
|
||||
|
||||
Desktop APIs will likely be in flux as we figure out better patterns than our ElectronJS counterpart.
|
||||
|
||||
[Jump to the getting started guide for Desktop.]()
|
||||
|
||||
Examples:
|
||||
- [File explorer]()
|
||||
- [Bluetooth scanner]()
|
||||
- [Device Viewer]()
|
||||
|
||||
[![FileExplorerExample](https://github.com/DioxusLabs/file-explorer-example/raw/master/image.png)](https://github.com/dioxusLabs/file-explorer/)
|
||||
|
||||
### Mobile Support
|
||||
---
|
||||
Mobile is currently the least-supported renderer target for Dioxus. Mobile apps are rendered with the platform's WebView, meaning that animations, transparency, and native widgets are not currently achievable. In addition, iOS is the only supported Mobile Platform. It is possible to get Dioxus running on Android and rendered with WebView, but the Rust windowing library that Dioxus uses - tao - does not currently supported Android.
|
||||
|
||||
[Jump to the getting started guide for Mobile.]()
|
||||
|
||||
Examples:
|
||||
- [Todo App]()
|
||||
- [Chat App]()
|
||||
|
||||
### LiveView 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 out-of-the-box 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.
|
||||
|
||||
|
||||
|
||||
### Multithreaded Support
|
||||
---
|
||||
The Dioxus VirtualDom, sadly, is not `Send.` This means you can't easily use Dioxus with most web frameworks like Tide, Rocket, Axum, etc. Currently, your two options include:
|
||||
- Actix: Actix supports `!Send` handlers
|
||||
- VirtualDomPool: A thread-per-core VirtualDom pool that uses message passing and serialization
|
||||
|
||||
When working with web frameworks that require `Send`, it is possible to render a VirtualDom immediately to a String - but you cannot hold the VirtualDom across an await point. For retained-state SSR (essentially LiveView), you'll need to create a VirtualDomPool which solves the `Send` problem.
|
|
@ -0,0 +1,62 @@
|
|||
# Summary
|
||||
|
||||
- [Introduction](README.md)
|
||||
- [Getting Setup](setup.md)
|
||||
- [Hello, World!](hello_world.md)
|
||||
- [Core Topics](concepts/00-index.md)
|
||||
- [Intro to VNodes](concepts/vnodes.md)
|
||||
- [VNodes with RSX, HTML, and NodeFactory](concepts/rsx.md)
|
||||
- [RSX in Depth](concepts/rsx_in_depth.md)
|
||||
- [Components and Props](concepts/components.md)
|
||||
- [Memoization](concepts/memoization.md)
|
||||
- [Hooks and Internal State](concepts/hooks.md)
|
||||
- [Event handlers](concepts/event_handlers.md)
|
||||
- [Global State](concepts/sharedstate.md)
|
||||
- [User Input and Controlled Components](concepts/errorhandling.md)
|
||||
- [Error handling](concepts/errorhandling.md)
|
||||
- [Reference Guide]()
|
||||
- [Advanced Guides]()
|
||||
- [Custom Renderer]()
|
||||
- [LiveView Support]()
|
||||
- [Bundling and Distributing]()
|
||||
- [Web]()
|
||||
- [Getting Started]()
|
||||
- [Down-casting Nodes]()
|
||||
- [Wrapping Web APIs]()
|
||||
- [SSR]()
|
||||
- [Wrapping Web APIs]()
|
||||
- [Desktop]()
|
||||
- [Wrapping Web APIs]()
|
||||
- [Mobile]()
|
||||
- [Wrapping Web APIs]()
|
||||
|
||||
|
||||
<!-- - [Command Line Tool](cli/README.md)
|
||||
- [init](cli/init.md)
|
||||
- [build](cli/build.md)
|
||||
- [watch](cli/watch.md)
|
||||
- [serve](cli/serve.md)
|
||||
- [test](cli/test.md)
|
||||
- [clean](cli/clean.md)
|
||||
- [Format](format/README.md)
|
||||
- [SUMMARY.md](format/summary.md)
|
||||
- [Draft chapter]()
|
||||
- [Configuration](format/configuration/README.md)
|
||||
- [General](format/configuration/general.md)
|
||||
- [Preprocessors](format/configuration/preprocessors.md)
|
||||
- [Renderers](format/configuration/renderers.md)
|
||||
- [Environment Variables](format/configuration/environment-variables.md)
|
||||
- [Theme](format/theme/README.md)
|
||||
- [index.hbs](format/theme/index-hbs.md)
|
||||
- [Syntax highlighting](format/theme/syntax-highlighting.md)
|
||||
- [Editor](format/theme/editor.md)
|
||||
- [MathJax Support](format/mathjax.md)
|
||||
- [mdBook-specific features](format/mdbook.md)
|
||||
- [Continuous Integration](continuous-integration.md)
|
||||
- [For Developers](for_developers/README.md)
|
||||
- [Preprocessors](for_developers/preprocessors.md)
|
||||
- [Alternative Backends](for_developers/backends.md) -->
|
||||
|
||||
-----------
|
||||
|
||||
[Contributors](misc/contributors.md)
|
|
@ -0,0 +1 @@
|
|||
# Command Line Tool
|
|
@ -0,0 +1 @@
|
|||
# build
|
|
@ -0,0 +1 @@
|
|||
# clean
|
|
@ -0,0 +1 @@
|
|||
# init
|
|
@ -0,0 +1 @@
|
|||
# serve
|
|
@ -0,0 +1 @@
|
|||
# test
|
|
@ -0,0 +1 @@
|
|||
# watch
|
|
@ -0,0 +1,73 @@
|
|||
# Core Topics
|
||||
|
||||
In this chapter of the book, we'll cover some core topics on how Dioxus works and how to best leverage the features to build a beautiful, reactive app.
|
||||
|
||||
At a very high level, Dioxus is simply a Rust framework for _declaring_ user interfaces and _reacting_ to changes.
|
||||
|
||||
1) We declare what we want our user interface to look like given a state using Rust-based logic and control flow.
|
||||
2) We declare how we want our state to change when the user triggers an event.
|
||||
|
||||
|
||||
## Declarative UI
|
||||
|
||||
Dioxus is a *declarative* framework. This means that instead of manually writing calls to "create element" and "set element background to red," we simply *declare* what we want the element to look like and let Dioxus handle the differences.
|
||||
|
||||
Let's pretend that we have a stoplight we need to control - it has a color state with red, yellow, and green as options.
|
||||
|
||||
|
||||
Using an imperative approach, we would have to manually declare each element and then handlers for advancing the stoplight.
|
||||
|
||||
```rust
|
||||
let container = Container::new();
|
||||
|
||||
let green_light = Light::new().color("green").enabled(true);
|
||||
let yellow_light = Light::new().color("yellow").enabled(false);
|
||||
let red_light = Light::new().color("red").enabled(false);
|
||||
container.push(green_light);
|
||||
container.push(yellow_light);
|
||||
container.push(red_light);
|
||||
|
||||
container.onclick(move |_| {
|
||||
if red_light.enabled() {
|
||||
red_light.set_enabled(false);
|
||||
green_light.set_enabled(true);
|
||||
} else if yellow_light.enabled() {
|
||||
yellow_light.set_enabled(false);
|
||||
red_light.set_enabled(true);
|
||||
} else if green_light.enabled() {
|
||||
green_light.set_enabled(false);
|
||||
yellow_light.set_enabled(true);
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
As the UI grows in scale, our logic to keep each element in the proper state would grow exponentially. This can become very unwieldy and lead to out-of-sync UIs that harm user experience.
|
||||
|
||||
Instead, with Dioxus, we *declare* what we want our UI to look like:
|
||||
|
||||
```rust
|
||||
let state = "red";
|
||||
|
||||
rsx!(
|
||||
Container {
|
||||
Light { color: "red", enabled: {state == "red"} }
|
||||
Light { color: "yellow", enabled: {state == "yellow"} }
|
||||
Light { color: "green", enabled: {state == "green"} }
|
||||
onclick: |_| {
|
||||
state = match state {
|
||||
"green" => "yellow",
|
||||
"yellow" => "red",
|
||||
"red" => "green",
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
```
|
||||
|
||||
Remember: this concept is not new! Many frameworks are declarative - with React being the most popular. Declarative frameworks tend to be much more enjoyable to work with than imperative frameworks.
|
||||
|
||||
Here's some reading about declaring UI in React:
|
||||
|
||||
- [https://stackoverflow.com/questions/33655534/difference-between-declarative-and-imperative-in-react-js](https://stackoverflow.com/questions/33655534/difference-between-declarative-and-imperative-in-react-js)
|
||||
|
||||
- [https://medium.com/@myung.kim287/declarative-vs-imperative-251ce99c6c44](https://medium.com/@myung.kim287/declarative-vs-imperative-251ce99c6c44)
|
|
@ -0,0 +1,18 @@
|
|||
# Components and Props
|
||||
|
||||
Dioxus should look and feel just like writing functional React components. In Dioxus, there are no class components with lifecycles. All state management is done via hooks. This encourages logic reusability and lessens the burden on Dioxus to maintain a non-breaking lifecycle API.
|
||||
|
||||
```rust
|
||||
#[derive(Properties, PartialEq)]
|
||||
struct MyProps {
|
||||
name: String
|
||||
}
|
||||
|
||||
fn Example(cx: Context<MyProps>) -> DomTree {
|
||||
html! { <div> "Hello {cx.cx.name}!" </div> }
|
||||
}
|
||||
```
|
||||
|
||||
Here, the `Context` object is used to access hook state, create subscriptions, and interact with the built-in context API. Props, children, and component APIs are accessible via the `Context` object. The functional component macro makes life more productive by inlining props directly as function arguments, similar to how Rocket parses URIs.
|
||||
|
||||
The final output of components must be a tree of VNodes. We provide an html macro for using JSX-style syntax to write these, though, you could use any macro, DSL, templating engine, or the constructors directly.
|
|
@ -0,0 +1 @@
|
|||
# Error handling
|
|
@ -0,0 +1 @@
|
|||
# Event handlers
|
|
@ -0,0 +1 @@
|
|||
# Hooks and Internal State
|
|
@ -0,0 +1 @@
|
|||
# Conditional Lists and Keys
|
|
@ -0,0 +1 @@
|
|||
# Memoization
|
|
@ -0,0 +1,63 @@
|
|||
# VNodes with RSX, HTML, and NodeFactory
|
||||
|
||||
Many modern frameworks provide a domain-specific-language for declaring user-interfaces. In the case of React, this language extension is called JSX and must be handled through additional dependencies and pre/post processors to transform your source code. With Rust, we can simply provide a procedural macro in the Dioxus dependency itself that mimics the JSX language.
|
||||
|
||||
With Dioxus, we actually ship two different macros - a macro that mimics JSX (the `html!` macro) and a macro that mimics Rust's native nested-struct syntax (the `rsx!` macro). These macros simply transform their inputs into NodeFactory calls.
|
||||
|
||||
For instance, this html! call:
|
||||
```rust
|
||||
html!(<div> "hello world" </div>)
|
||||
```
|
||||
becomes this NodeFactory call:
|
||||
```rust
|
||||
|f| f.element(
|
||||
dioxus_elements::div, // tag
|
||||
[], // listeners
|
||||
[], // attributes
|
||||
[f.static_text("hello world")], // children
|
||||
None // key
|
||||
)
|
||||
```
|
||||
The NodeFactory API is fairly ergonomic, making it a viable option to use directly. The NodeFactory API is also compile-time correct and has incredible syntax highlighting support. We use what Rust calls a "unit type" - the `dioxus_elements::div` and associated methods to ensure that a `div` can only have attributes associated with `div`s. This lets us tack on relevant documentation, autocomplete support, and jump-to-definition for methods and attributes.
|
||||
|
||||
![Compile time correct syntax](../images/compiletimecorrect.png)
|
||||
|
||||
## html! macro
|
||||
|
||||
The html! macro supports a limited subset of the html standard. Rust's macro parsing tools are somewhat limited, so all text between tags _must be quoted_.
|
||||
|
||||
However, writing HTML by hand is a bit tedious - IDE tools for Rust don't support linting/autocomplete/syntax highlighting. We suggest using RSX - it's more natural for Rust programs and _does_ integrate well with Rust IDE tools.
|
||||
|
||||
```rust
|
||||
let name = "jane";
|
||||
let pending = false;
|
||||
let count = 10;
|
||||
|
||||
dioxus::ssr::render_lazy(html! {
|
||||
<div>
|
||||
<p> "Hello, {name}!" </p>
|
||||
<p> "Status: {pending}!" </p>
|
||||
<p> "Count {count}!" </p>
|
||||
</div>
|
||||
});
|
||||
```
|
||||
|
||||
## rsx! macro
|
||||
|
||||
The rsx! macro is a VNode builder macro designed especially for Rust programs. Writing these should feel very natural, much like assembling a struct. VSCode also supports these with code folding, bracket-tabbing, bracket highlighting, section selecting, inline documentation, GOTO definition, and refactoring support.
|
||||
|
||||
When helpful, the Dioxus VSCode extension provides a way of converting a selection of HTML directly to RSX, so you can import templates from the web directly into your existing app.
|
||||
|
||||
It's also a bit easier on the eyes than HTML.
|
||||
|
||||
```rust
|
||||
dioxus::ssr::render_lazy(rsx! {
|
||||
div {
|
||||
p {"Hello, {name}!"}
|
||||
p {"Status: {pending}!"}
|
||||
p {"Count {count}!"}
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
In the next section, we'll cover the `rsx!` macro in more depth.
|
|
@ -0,0 +1,229 @@
|
|||
# RSX in Depth
|
||||
|
||||
The RSX macro makes it very easy to assemble complex UIs with a very natural Rust syntax:
|
||||
|
||||
|
||||
```rust
|
||||
rsx!(div {
|
||||
button {
|
||||
"Add todo",
|
||||
onclick: move |e| todos.write().new_todo()
|
||||
}
|
||||
ul {
|
||||
class: "todo-list"
|
||||
(todos.iter().map(|(key, todo)| rsx!(
|
||||
li {
|
||||
class: "beautiful-todo"
|
||||
key: "f"
|
||||
h3 { "{todo.title}" }
|
||||
p { "{todo.contents}"}
|
||||
}
|
||||
)))
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
In this section, we'll cover the `rsx!` macro in depth. If you prefer to learn through examples, the `reference` guide has plenty of examples on how to use `rsx!` effectively.
|
||||
|
||||
|
||||
|
||||
### Element structure
|
||||
|
||||
```rust
|
||||
div {
|
||||
hidden: false,
|
||||
"some text"
|
||||
child {}
|
||||
Component {}
|
||||
{/* literal tokens that resolve to vnodes */}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
Each element takes a comma-separated list of expressions to build the node. Roughly, here's how they work:
|
||||
|
||||
- `name: value` sets a property on this element.
|
||||
- `"text"` adds a new text element
|
||||
- `tag {}` adds a new child element
|
||||
- `CustomTag {}` adds a new child component
|
||||
- `{expr}` pastes the `expr` tokens literally. They must be `IntoIterator<T> where T: IntoVnode` to work properly
|
||||
|
||||
Commas are entirely optional, but might be useful to delineate between elements and attributes.
|
||||
|
||||
The `render` function provides an **extremely efficient** allocator for VNodes and text, so try not to use the `format!` macro in your components. Rust's default `ToString` methods pass through the global allocator, but all text in components is allocated inside a manually-managed Bump arena. To push you in the right direction, all text-based attributes take `std::fmt::Arguments` directly, so you'll want to reach for `format_args!` when the built-in `f-string` interpolation just doesn't cut it.
|
||||
|
||||
|
||||
### Ignoring `cx.render` with `rsx!(cx, ...)`
|
||||
|
||||
Sometimes, writing `cx.render` is a hassle. The `rsx! macro will accept any token followed by a comma as the target to call "render" on:
|
||||
|
||||
```rust
|
||||
cx.render(rsx!( div {} ))
|
||||
// becomes
|
||||
rsx!(cx, div {})
|
||||
```
|
||||
|
||||
|
||||
|
||||
### Conditional Rendering
|
||||
|
||||
Sometimes, you might not want to render an element given a condition. The rsx! macro will accept any tokens directly contained with curly braces, provided they resolve to a type that implements `IntoIterator<VNode>`. This lets us write any Rust expression that resolves to a VNode:
|
||||
|
||||
|
||||
```rust
|
||||
rsx!({
|
||||
if enabled {
|
||||
rsx!(cx, div {"enabled"})
|
||||
} else {
|
||||
rsx!(cx, li {"disabled"})
|
||||
}
|
||||
})
|
||||
```
|
||||
A convenient way of hiding/showing an element is returning an `Option<VNode>`. When combined with `and_then`, we can succinctly control the display state given some boolean:
|
||||
|
||||
```rust
|
||||
rsx!({
|
||||
a.and_then(rsx!(div {"enabled"}))
|
||||
})
|
||||
```
|
||||
|
||||
It's important to note that the expression `rsx!()` is typically lazy - this expression must be _rendered_ to produce a VNode. When using match statements, we must render every arm as to avoid the `no two closures are identical` rule that Rust imposes:
|
||||
|
||||
```rust
|
||||
// this will not compile!
|
||||
match case {
|
||||
true => rsx!(div {}),
|
||||
false => rsx!(div {})
|
||||
}
|
||||
|
||||
// the nodes must be rendered first
|
||||
match case {
|
||||
true => rsx!(cx, div {}),
|
||||
false => rsx!(cx, div {})
|
||||
}
|
||||
```
|
||||
|
||||
### Lists
|
||||
|
||||
Again, because anything that implements `IntoIterator<VNode>` is valid, we can use lists directly in our `rsx!`:
|
||||
|
||||
```rust
|
||||
let items = vec!["a", "b", "c"];
|
||||
|
||||
cx.render(rsx!{
|
||||
ul {
|
||||
{items.iter().map(|f| rsx!(li { "a" }))}
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
Sometimes, it makes sense to render VNodes into a list:
|
||||
|
||||
```rust
|
||||
let mut items = vec![];
|
||||
|
||||
for _ in 0..5 {
|
||||
items.push(rsx!(cx, li {} ))
|
||||
}
|
||||
|
||||
rsx!(cx, {items} )
|
||||
```
|
||||
|
||||
#### Lists and Keys
|
||||
|
||||
When rendering the VirtualDom to the screen, Dioxus needs to know which elements have been added and which have been removed. These changes are determined through a process called "diffing" - an old set of elements is compared to a new set of elements. If an element is removed, then it won't show up in the new elements, and Dioxus knows to remove it.
|
||||
|
||||
However, with lists, Dioxus does not exactly know how to determine which elements have been added or removed if the order changes or if an element is added or removed from the middle of the list.
|
||||
|
||||
In these cases, it is vitally important to specify a "key" alongside the element. Keys should be persistent between renders.
|
||||
|
||||
```rust
|
||||
fn render_list(cx: Context, items: HashMap<String, Todo>) -> DomTree {
|
||||
rsx!(cx, ul {
|
||||
{items.iter().map(|key, item| {
|
||||
li {
|
||||
key: key,
|
||||
h2 { "{todo.title}" }
|
||||
p { "{todo.contents}" }
|
||||
}
|
||||
})}
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
There have been many guides made for keys in React, so we recommend reading up to understand their importance:
|
||||
|
||||
- [React guide on keys](https://reactjs.org/docs/lists-and-keys.html)
|
||||
- [Importance of keys (Medium)](https://kentcdodds.com/blog/understanding-reacts-key-prop)
|
||||
|
||||
### Complete Reference
|
||||
```rust
|
||||
let text = "example";
|
||||
|
||||
cx.render(rsx!{
|
||||
div {
|
||||
h1 { "Example" },
|
||||
|
||||
{title}
|
||||
|
||||
// fstring interpolation
|
||||
"{text}"
|
||||
|
||||
p {
|
||||
// Attributes
|
||||
tag: "type",
|
||||
|
||||
// Anything that implements display can be an attribute
|
||||
abc: 123,
|
||||
|
||||
enabled: true,
|
||||
|
||||
// attributes also supports interpolation
|
||||
// `class` is not a restricted keyword unlike JS and ClassName
|
||||
class: "big small wide short {text}",
|
||||
|
||||
class: format_args!("attributes take fmt::Arguments. {}", 99),
|
||||
|
||||
tag: {"these tokens are placed directly"}
|
||||
|
||||
// Children
|
||||
a { "abcder" },
|
||||
|
||||
// Children with attributes
|
||||
h2 { "hello", class: "abc-123" },
|
||||
|
||||
// Child components
|
||||
CustomComponent { a: 123, b: 456, key: "1" },
|
||||
|
||||
// Child components with paths
|
||||
crate::components::CustomComponent { a: 123, b: 456, key: "1" },
|
||||
|
||||
// Iterators
|
||||
{ (0..3).map(|i| rsx!( h1 {"{:i}"} )) },
|
||||
|
||||
// More rsx!, or even html!
|
||||
{ rsx! { div { } } },
|
||||
{ html! { <div> </div> } },
|
||||
|
||||
// Matching
|
||||
// Requires rendering the nodes first.
|
||||
// rsx! is lazy, and the underlying closures cannot have the same type
|
||||
// Rendering produces the VNode type
|
||||
{match rand::gen_range::<i32>(1..3) {
|
||||
1 => rsx!(cx, h1 { "big" })
|
||||
2 => rsx!(cx, h2 { "medium" })
|
||||
_ => rsx!(cx, h3 { "small" })
|
||||
}}
|
||||
|
||||
// Optionals
|
||||
{true.and_then(|f| rsx!( h1 {"Conditional Rendering"} ))}
|
||||
|
||||
// Child nodes
|
||||
{cx.children()}
|
||||
|
||||
// Any expression that is `IntoVNode`
|
||||
{expr}
|
||||
}
|
||||
}
|
||||
})
|
||||
```
|
|
@ -0,0 +1 @@
|
|||
# Global State
|
|
@ -0,0 +1,64 @@
|
|||
# VNodes and Elements
|
||||
|
||||
At the heart of Dioxus is the concept of an "element" - a container that can have children, properties, event handlers, and other important attributes. Dioxus only knows how to render the `VNode` datastructure - an Enum variant of an Element, Text, Components, Fragments, and Anchors.
|
||||
|
||||
Because Dioxus is meant for the Web and uses WebView as a desktop and mobile renderer, almost all elements in Dioxus share properties with their HTML counterpart. When we declare our elements, we'll do so using HTML semantics:
|
||||
|
||||
```rust
|
||||
rsx!(
|
||||
div {
|
||||
"hello world"
|
||||
}
|
||||
)
|
||||
```
|
||||
|
||||
As you would expect, this snippet would generate a simple hello-world div. In fact, we can render these nodes directly with the SSR crate:
|
||||
|
||||
```rust
|
||||
dioxus::ssr::render_lazy(rsx!(
|
||||
div {
|
||||
"hello world"
|
||||
}
|
||||
))
|
||||
```
|
||||
|
||||
And produce the corresponding html structure:
|
||||
```html
|
||||
<div>hello world</div>
|
||||
```
|
||||
|
||||
Our structure declared above is made of two variants of the `VNode` datastructure:
|
||||
- A VElement with a tagname of `div`
|
||||
- A VText with contents of `"hello world"`
|
||||
|
||||
## All the VNode types
|
||||
|
||||
VNodes can be any of:
|
||||
- **Element**: a container with a tag name, namespace, attributes, children, and event listeners
|
||||
- **Text**: bump allocated text derived from string formatting
|
||||
- **Fragments**: a container of elements with no parent
|
||||
- **Suspended**: a container for nodes that aren't yet ready to be rendered
|
||||
- **Anchor**: a special type of node that is only available when fragments have no children
|
||||
|
||||
In practice, only elements and text can be initialized directly while other node types can only be created through hooks or NodeFactory methods.
|
||||
|
||||
## Bump Arena Allocation
|
||||
|
||||
To speed up the process of building our elements and text, Dioxus uses a special type of memory allocator tuned for large batches of small allocations called a Bump Arena. We use the `bumpalo` allocator which was initially developed for Dioxus' spiritual predecessor: `Dodrio.`
|
||||
|
||||
- Bumpalo: [https://github.com/fitzgen/bumpalo](https://github.com/fitzgen/bumpalo)
|
||||
- Dodrio: [https://github.com/fitzgen/dodrio](https://github.com/fitzgen/dodrio)
|
||||
|
||||
In other frontend frameworks for Rust, nearly every string is allocated using the global allocator. This means that strings in Rust do not benefit from the immutable string interning optimizations that JavaScript engines employ. By using a smaller, faster, more limited allocator, we can increase framework performance, bypassing even the naive WasmBindgen benchmarks for very quick renders.
|
||||
|
||||
It's important to note that VNodes are not `'static` - the VNode definition has a lifetime attached to it:
|
||||
|
||||
```rust, ignore
|
||||
enum VNode<'bump> {
|
||||
VElement { tag: &'static str, children: &'bump [VNode<'bump>] },
|
||||
VText { content: &'bump str },
|
||||
// other VNodes ....
|
||||
}
|
||||
```
|
||||
|
||||
Because VNodes use a bump allocator as their memory backing, they can only be created through the `NodeFactory` API - which we'll cover in the next chapter. This particular detail is important to understand because "rendering" VNodes produces a lifetime attached to the bump arena - which must be explicitly declared when dealing with components that borrow data from their parents.
|
|
@ -0,0 +1 @@
|
|||
# For Developers
|
|
@ -0,0 +1 @@
|
|||
# Alternative Backends
|
|
@ -0,0 +1 @@
|
|||
# Preprocessors
|
|
@ -0,0 +1 @@
|
|||
# Format
|
|
@ -0,0 +1 @@
|
|||
# Configuration
|
|
@ -0,0 +1 @@
|
|||
# Environment Variables
|
|
@ -0,0 +1 @@
|
|||
# General
|
|
@ -0,0 +1 @@
|
|||
# Preprocessors
|
|
@ -0,0 +1 @@
|
|||
# Renderers
|
|
@ -0,0 +1 @@
|
|||
# MathJax Support
|
|
@ -0,0 +1 @@
|
|||
# mdBook-specific features
|
|
@ -0,0 +1 @@
|
|||
# SUMMARY.md
|
|
@ -0,0 +1 @@
|
|||
# Theme
|
|
@ -0,0 +1 @@
|
|||
# Editor
|
|
@ -0,0 +1 @@
|
|||
# index.hbs
|
|
@ -0,0 +1 @@
|
|||
# Syntax highlighting
|
|
@ -2,6 +2,8 @@
|
|||
|
||||
Let's put together a simple "hello world" to get acquainted with Dioxus. The Dioxus-CLI has an equivalent to "create-react-app" built-in, but setting up Dioxus apps is simple enough to not need additional tooling.
|
||||
|
||||
This demo will build a simple desktop app. Check out the platform-specific setup guides on how to port your app to different targets.
|
||||
|
||||
First, let's start a new project. Rust has the concept of executables and libraries. Executables have a `main.rs` and libraries have `lib.rs`. A project may have both. Our `hello world` will be an executable - we expect our app to launch when we run it! Cargo provides this for us:
|
||||
|
||||
```shell
|
||||
|
@ -66,12 +68,12 @@ edition = "2018"
|
|||
To use the Dioxus library, we'll want to add the most recent version of `Dioxus` to our crate. If you have `cargo edit` installed, simply call:
|
||||
|
||||
```shell
|
||||
$ cargo add dioxus --features web
|
||||
$ cargo add dioxus --features desktop
|
||||
```
|
||||
|
||||
It's very important to add `dioxus` with the `web` feature for this example. The `dioxus` crate is a batteries-include crate that combines a bunch of utility crates together, ensuring compatibility of the most important parts of the ecosystem. Under the hood, the `dioxus` crate configures various renderers, hooks, debug tooling, and more. The `web` feature ensures the we only depend on the smallest set of required crates to compile.
|
||||
It's very important to add `dioxus` with the `desktop` feature for this example. The `dioxus` crate is a batteries-include crate that combines a bunch of utility crates together, ensuring compatibility of the most important parts of the ecosystem. Under the hood, the `dioxus` crate configures various renderers, hooks, debug tooling, and more. The `desktop` feature ensures the we only depend on the smallest set of required crates to compile and render.
|
||||
|
||||
If you plan to develop extensions for the `Dioxus` ecosystem, please use `dioxus-core` crate. The `dioxus` crate re-exports `dioxus-core` alongside other utilties. As a result, `dioxus-core` is much more stable than the batteries-included `dioxus` crate.
|
||||
If you plan to develop extensions for the `Dioxus` ecosystem, please use the `dioxus` crate with the `core` feature to limit the amount of dependencies your project brings in.
|
||||
|
||||
Now, let's edit our `main.rs` file:
|
||||
|
||||
|
@ -80,13 +82,13 @@ use diouxs::prelude::*;
|
|||
|
||||
|
||||
fn main() {
|
||||
dioxus::web::start(App, |c| c).unwrap();
|
||||
dioxus::desktop::start(App, |c| c);
|
||||
}
|
||||
|
||||
static App: FC<()> = |cx, props| {
|
||||
cx.render(rsx! {
|
||||
cx.render(rsx! (
|
||||
div { "Hello, world!" }
|
||||
})
|
||||
))
|
||||
};
|
||||
```
|
||||
|
||||
|
@ -98,13 +100,11 @@ This bit of code imports everything from the the `prelude` module. This brings i
|
|||
use diouxs::prelude::*;
|
||||
```
|
||||
|
||||
This bit of code starts the WASM renderer as a future (JS Promise) and then awaits it. This is very similar to the `ReactDOM.render()` method you use for a React app. We pass in the `App` function as a our app. The `|c| c` closure provides a way to configure the WebSys app, enabling things like hydration, pre-rendering, and other tweaks. The simplest configuration is just the defaults.
|
||||
|
||||
Calling "start" will launch Dioxus on the web's main thread, running `wasm_bindgen_futures::block_on`. It's possible to "launch" the WebSys renderer as an async function - check the documentation for more details.
|
||||
This initialization code launches a Tokio runtime on a helper thread - where your code will run, and then the WebView on the main-thread. Due to platform requirements, the main thread is blocked by this call.
|
||||
|
||||
```rust
|
||||
fn main() {
|
||||
dioxus::web::start(App, |c| c).unwrap();
|
||||
dioxus::desktop::start(App, |c| c);
|
||||
}
|
||||
```
|
||||
|
||||
|
@ -121,35 +121,35 @@ static App: FC<()> = |cx, props| {
|
|||
The closure `FC<()>` syntax is identical to the function syntax, but with lifetimes managed for you. In cases where props need to borrow from their parent, you will need to specify lifetimes using the function syntax:
|
||||
|
||||
```rust
|
||||
fn App<'a>(cx: Context<'a>, props: &()) -> DomTree<'a> {
|
||||
fn App<'a>(cx: Context<'a>, props: &'a ()) -> DomTree<'a> {
|
||||
cx.render(rsx! {
|
||||
div { "Hello, world!" }
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
In React, you'll save data between renders with hooks. However, hooks rely on global variables which make them difficult to integrate in multi-tenant systems like server-rendering. In Dioxus, you are given an explicit `Context` object to control how the component renders and stores data.
|
||||
|
||||
Next, we're greeted with the `rsx!` macro. This lets us add a custom DSL for declaratively building the structure of our app. The semantics of this macro are similar to that of JSX and HTML, though with a familiar Rust-y interface. The `html!` macro is also available for writing components with a JSX/HTML syntax.
|
||||
|
||||
The `rsx!` macro is lazy: it does not immediately produce elements or allocates, but rather builds a closure which can be rendered with `cx.render`.
|
||||
|
||||
Now, let's launch our app in a development server:
|
||||
Now, let's launch our app:
|
||||
|
||||
```shell
|
||||
$ dioxus develop
|
||||
$ cargo run
|
||||
```
|
||||
|
||||
Huzzah! We have a simple app.
|
||||
|
||||
![Hello world](../assets/01-setup-helloworld.png)
|
||||
![Hello world](images/01-setup-helloworld.png)
|
||||
|
||||
If we wanted to golf a bit, we can shrink our hello-world even smaller:
|
||||
|
||||
```rust
|
||||
fn main() {
|
||||
dioxus::web::start(|cx, _| cx.render(diouxs::rsx!( div { "Hello, World!" } ))
|
||||
static App: FC<()> = |cx, props| rsx!(cx, div {"hello world!"});
|
||||
dioxus::web::start(App, |c| c);
|
||||
}
|
||||
```
|
||||
|
||||
Anyways, let's move on to something a bit more complex.
|
Before Width: | Height: | Size: 30 KiB After Width: | Height: | Size: 30 KiB |
Binary file not shown.
After Width: | Height: | Size: 192 KiB |
Binary file not shown.
After Width: | Height: | Size: 58 KiB |
|
@ -0,0 +1 @@
|
|||
# Contributors
|
|
@ -8,7 +8,7 @@ use gloo_timers::future::TimeoutFuture;
|
|||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
dioxus::desktop::launch(App, |c| c).await;
|
||||
dioxus::desktop::launch(App, |c| c);
|
||||
}
|
||||
|
||||
pub static App: FC<()> = |cx, _| {
|
||||
|
|
|
@ -7,7 +7,7 @@ use dioxus::events::*;
|
|||
use dioxus::prelude::*;
|
||||
|
||||
fn main() {
|
||||
dioxus::desktop::launch(APP, |cfg| cfg).unwrap();
|
||||
dioxus::desktop::launch(APP, |cfg| cfg);
|
||||
}
|
||||
|
||||
const APP: FC<()> = |cx, _| {
|
||||
|
|
|
@ -3,7 +3,7 @@ use fxhash::FxBuildHasher;
|
|||
use std::rc::Rc;
|
||||
|
||||
fn main() {
|
||||
dioxus::desktop::launch(App, |c| c).unwrap();
|
||||
dioxus::desktop::launch(App, |c| c);
|
||||
}
|
||||
|
||||
// We use a special immutable hashmap to make hashmap operations efficient
|
||||
|
|
|
@ -16,7 +16,7 @@ fn main() {
|
|||
let vdom = VirtualDom::new(App);
|
||||
let content = ssr::render_vdom(&vdom, |f| f.pre_render(true));
|
||||
|
||||
dioxus::desktop::launch(App, |c| c.with_prerendered(content)).unwrap();
|
||||
dioxus::desktop::launch(App, |c| c.with_prerendered(content));
|
||||
}
|
||||
|
||||
static App: FC<()> = |cx, props| {
|
||||
|
|
|
@ -28,8 +28,7 @@ fn main() {
|
|||
.with_resizable(false)
|
||||
.with_inner_size(LogicalSize::new(320.0, 530.0))
|
||||
})
|
||||
})
|
||||
.expect("failed to launch dioxus app");
|
||||
});
|
||||
}
|
||||
|
||||
static App: FC<()> = |cx, props| {
|
||||
|
|
|
@ -21,7 +21,7 @@ pub struct TodoItem {
|
|||
pub contents: String,
|
||||
}
|
||||
|
||||
const STYLE: &str = include_str!("./_examples/todomvc/style.css");
|
||||
const STYLE: &str = include_str!("./assets/todomvc.css");
|
||||
const App: FC<()> = |cx, props| {
|
||||
let draft = use_state(cx, || "".to_string());
|
||||
let todos = use_state(cx, || HashMap::<u32, Rc<TodoItem>>::new());
|
||||
|
|
|
@ -262,7 +262,7 @@ mod field_info {
|
|||
pub setter: SetterSettings,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Clone)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct SetterSettings {
|
||||
pub doc: Option<syn::Expr>,
|
||||
pub skip: bool,
|
||||
|
@ -270,6 +270,17 @@ mod field_info {
|
|||
pub strip_option: bool,
|
||||
}
|
||||
|
||||
impl Default for SetterSettings {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
doc: Default::default(),
|
||||
skip: false,
|
||||
auto_into: false,
|
||||
strip_option: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FieldBuilderAttr {
|
||||
pub fn with(mut self, attrs: &[syn::Attribute]) -> Result<Self, Error> {
|
||||
let mut skip_tokens = None;
|
||||
|
|
|
@ -33,10 +33,12 @@ futures-channel = "0.3.16"
|
|||
# used for noderefs
|
||||
once_cell = "1.8.0"
|
||||
|
||||
indexmap = "1.7.0"
|
||||
|
||||
# # Serialize the Edits for use in Webview/Liveview instances
|
||||
serde = { version = "1", features = ["derive"], optional = true }
|
||||
|
||||
indexmap = "1.7.0"
|
||||
serde_repr = { version = "0.1.7", optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
anyhow = "1.0.42"
|
||||
|
@ -50,7 +52,7 @@ dioxus-core-macro = { path = "../core-macro", version = "0.1.2" }
|
|||
|
||||
[features]
|
||||
default = ["serialize"]
|
||||
serialize = ["serde"]
|
||||
serialize = ["serde", "serde_repr"]
|
||||
|
||||
[[bench]]
|
||||
name = "create"
|
||||
|
|
|
@ -25,7 +25,7 @@ criterion_main!(mbenches);
|
|||
fn create_rows(c: &mut Criterion) {
|
||||
static App: FC<()> = |cx, _| {
|
||||
let mut rng = SmallRng::from_entropy();
|
||||
let rows = (0..10_000).map(|f| {
|
||||
let rows = (0..10_000_usize).map(|f| {
|
||||
let label = Label::new(&mut rng);
|
||||
rsx! {
|
||||
Row {
|
||||
|
|
|
@ -14,7 +14,7 @@ fn main() {
|
|||
|
||||
static App: FC<()> = |cx, props| {
|
||||
let mut rng = SmallRng::from_entropy();
|
||||
let rows = (0..10_000).map(|f| {
|
||||
let rows = (0..10_000_usize).map(|f| {
|
||||
let label = Label::new(&mut rng);
|
||||
rsx! {
|
||||
Row {
|
||||
|
|
|
@ -769,18 +769,211 @@ pub mod on {
|
|||
pub struct ToggleEvent {}
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
|
||||
#[cfg_attr(
|
||||
feature = "serialize",
|
||||
derive(serde_repr::Serialize_repr, serde_repr::Deserialize_repr)
|
||||
)]
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
#[repr(u8)]
|
||||
pub enum KeyCode {
|
||||
// That key has no keycode, = 0
|
||||
// break, = 3
|
||||
// backspace / delete, = 8
|
||||
// tab, = 9
|
||||
// clear, = 12
|
||||
// enter, = 13
|
||||
// shift, = 16
|
||||
// ctrl, = 17
|
||||
// alt, = 18
|
||||
// pause/break, = 19
|
||||
// caps lock, = 20
|
||||
// hangul, = 21
|
||||
// hanja, = 25
|
||||
// escape, = 27
|
||||
// conversion, = 28
|
||||
// non-conversion, = 29
|
||||
// spacebar, = 32
|
||||
// page up, = 33
|
||||
// page down, = 34
|
||||
// end, = 35
|
||||
// home, = 36
|
||||
// left arrow, = 37
|
||||
// up arrow, = 38
|
||||
// right arrow, = 39
|
||||
// down arrow, = 40
|
||||
// select, = 41
|
||||
// print, = 42
|
||||
// execute, = 43
|
||||
// Print Screen, = 44
|
||||
// insert, = 45
|
||||
// delete, = 46
|
||||
// help, = 47
|
||||
// 0, = 48
|
||||
// 1, = 49
|
||||
// 2, = 50
|
||||
// 3, = 51
|
||||
// 4, = 52
|
||||
// 5, = 53
|
||||
// 6, = 54
|
||||
// 7, = 55
|
||||
// 8, = 56
|
||||
// 9, = 57
|
||||
// :, = 58
|
||||
// semicolon (firefox), equals, = 59
|
||||
// <, = 60
|
||||
// equals (firefox), = 61
|
||||
// ß, = 63
|
||||
// @ (firefox), = 64
|
||||
// a, = 65
|
||||
// b, = 66
|
||||
// c, = 67
|
||||
// d, = 68
|
||||
// e, = 69
|
||||
// f, = 70
|
||||
// g, = 71
|
||||
// h, = 72
|
||||
// i, = 73
|
||||
// j, = 74
|
||||
// k, = 75
|
||||
// l, = 76
|
||||
// m, = 77
|
||||
// n, = 78
|
||||
// o, = 79
|
||||
// p, = 80
|
||||
// q, = 81
|
||||
// r, = 82
|
||||
// s, = 83
|
||||
// t, = 84
|
||||
// u, = 85
|
||||
// v, = 86
|
||||
// w, = 87
|
||||
// x, = 88
|
||||
// y, = 89
|
||||
// z, = 90
|
||||
// Windows Key / Left ⌘ / Chromebook Search key, = 91
|
||||
// right window key, = 92
|
||||
// Windows Menu / Right ⌘, = 93
|
||||
// sleep, = 95
|
||||
// numpad 0, = 96
|
||||
// numpad 1, = 97
|
||||
// numpad 2, = 98
|
||||
// numpad 3, = 99
|
||||
// numpad 4, = 100
|
||||
// numpad 5, = 101
|
||||
// numpad 6, = 102
|
||||
// numpad 7, = 103
|
||||
// numpad 8, = 104
|
||||
// numpad 9, = 105
|
||||
// multiply, = 106
|
||||
// add, = 107
|
||||
// numpad period (firefox), = 108
|
||||
// subtract, = 109
|
||||
// decimal point, = 110
|
||||
// divide, = 111
|
||||
// f1, = 112
|
||||
// f2, = 113
|
||||
// f3, = 114
|
||||
// f4, = 115
|
||||
// f5, = 116
|
||||
// f6, = 117
|
||||
// f7, = 118
|
||||
// f8, = 119
|
||||
// f9, = 120
|
||||
// f10, = 121
|
||||
// f11, = 122
|
||||
// f12, = 123
|
||||
// f13, = 124
|
||||
// f14, = 125
|
||||
// f15, = 126
|
||||
// f16, = 127
|
||||
// f17, = 128
|
||||
// f18, = 129
|
||||
// f19, = 130
|
||||
// f20, = 131
|
||||
// f21, = 132
|
||||
// f22, = 133
|
||||
// f23, = 134
|
||||
// f24, = 135
|
||||
// f25, = 136
|
||||
// f26, = 137
|
||||
// f27, = 138
|
||||
// f28, = 139
|
||||
// f29, = 140
|
||||
// f30, = 141
|
||||
// f31, = 142
|
||||
// f32, = 143
|
||||
// num lock, = 144
|
||||
// scroll lock, = 145
|
||||
// airplane mode, = 151
|
||||
// ^, = 160
|
||||
// !, = 161
|
||||
// ؛ (arabic semicolon), = 162
|
||||
// #, = 163
|
||||
// $, = 164
|
||||
// ù, = 165
|
||||
// page backward, = 166
|
||||
// page forward, = 167
|
||||
// refresh, = 168
|
||||
// closing paren (AZERTY), = 169
|
||||
// *, = 170
|
||||
// ~ + * key, = 171
|
||||
// home key, = 172
|
||||
// minus (firefox), mute/unmute, = 173
|
||||
// decrease volume level, = 174
|
||||
// increase volume level, = 175
|
||||
// next, = 176
|
||||
// previous, = 177
|
||||
// stop, = 178
|
||||
// play/pause, = 179
|
||||
// e-mail, = 180
|
||||
// mute/unmute (firefox), = 181
|
||||
// decrease volume level (firefox), = 182
|
||||
// increase volume level (firefox), = 183
|
||||
// semi-colon / ñ, = 186
|
||||
// equal sign, = 187
|
||||
// comma, = 188
|
||||
// dash, = 189
|
||||
// period, = 190
|
||||
// forward slash / ç, = 191
|
||||
// grave accent / ñ / æ / ö, = 192
|
||||
// ?, / or °, = 193
|
||||
// numpad period (chrome), = 194
|
||||
// open bracket, = 219
|
||||
// back slash, = 220
|
||||
// close bracket / å, = 221
|
||||
// single quote / ø / ä, = 222
|
||||
// `, = 223
|
||||
// left or right ⌘ key (firefox), = 224
|
||||
// altgr, = 225
|
||||
// < /git >, left back slash, = 226
|
||||
// GNOME Compose Key, = 230
|
||||
// ç, = 231
|
||||
// XF86Forward, = 233
|
||||
// XF86Back, = 234
|
||||
// non-conversion, = 235
|
||||
// alphanumeric, = 240
|
||||
// hiragana/katakana, = 242
|
||||
// half-width/full-width, = 243
|
||||
// kanji, = 244
|
||||
// unlock trackpad (Chrome/Edge), = 251
|
||||
// toggle touchpad, = 255
|
||||
NA = 0,
|
||||
Break = 3,
|
||||
Backspace = 8,
|
||||
Tab = 9,
|
||||
Clear = 12,
|
||||
Enter = 13,
|
||||
Shift = 16,
|
||||
Ctrl = 17,
|
||||
Alt = 18,
|
||||
Pause = 19,
|
||||
CapsLock = 20,
|
||||
// hangul, = 21
|
||||
// hanja, = 25
|
||||
Escape = 27,
|
||||
// conversion, = 28
|
||||
// non-conversion, = 29
|
||||
Space = 32,
|
||||
PageUp = 33,
|
||||
PageDown = 34,
|
||||
End = 35,
|
||||
|
@ -789,8 +982,13 @@ pub enum KeyCode {
|
|||
UpArrow = 38,
|
||||
RightArrow = 39,
|
||||
DownArrow = 40,
|
||||
// select, = 41
|
||||
// print, = 42
|
||||
// execute, = 43
|
||||
// Print Screen, = 44
|
||||
Insert = 45,
|
||||
Delete = 46,
|
||||
// help, = 47
|
||||
Num0 = 48,
|
||||
Num1 = 49,
|
||||
Num2 = 50,
|
||||
|
@ -801,6 +999,12 @@ pub enum KeyCode {
|
|||
Num7 = 55,
|
||||
Num8 = 56,
|
||||
Num9 = 57,
|
||||
// :, = 58
|
||||
// semicolon (firefox), equals, = 59
|
||||
// <, = 60
|
||||
// equals (firefox), = 61
|
||||
// ß, = 63
|
||||
// @ (firefox), = 64
|
||||
A = 65,
|
||||
B = 66,
|
||||
C = 67,
|
||||
|
@ -857,8 +1061,53 @@ pub enum KeyCode {
|
|||
F10 = 121,
|
||||
F11 = 122,
|
||||
F12 = 123,
|
||||
// f13, = 124
|
||||
// f14, = 125
|
||||
// f15, = 126
|
||||
// f16, = 127
|
||||
// f17, = 128
|
||||
// f18, = 129
|
||||
// f19, = 130
|
||||
// f20, = 131
|
||||
// f21, = 132
|
||||
// f22, = 133
|
||||
// f23, = 134
|
||||
// f24, = 135
|
||||
// f25, = 136
|
||||
// f26, = 137
|
||||
// f27, = 138
|
||||
// f28, = 139
|
||||
// f29, = 140
|
||||
// f30, = 141
|
||||
// f31, = 142
|
||||
// f32, = 143
|
||||
NumLock = 144,
|
||||
ScrollLock = 145,
|
||||
// airplane mode, = 151
|
||||
// ^, = 160
|
||||
// !, = 161
|
||||
// ؛ (arabic semicolon), = 162
|
||||
// #, = 163
|
||||
// $, = 164
|
||||
// ù, = 165
|
||||
// page backward, = 166
|
||||
// page forward, = 167
|
||||
// refresh, = 168
|
||||
// closing paren (AZERTY), = 169
|
||||
// *, = 170
|
||||
// ~ + * key, = 171
|
||||
// home key, = 172
|
||||
// minus (firefox), mute/unmute, = 173
|
||||
// decrease volume level, = 174
|
||||
// increase volume level, = 175
|
||||
// next, = 176
|
||||
// previous, = 177
|
||||
// stop, = 178
|
||||
// play/pause, = 179
|
||||
// e-mail, = 180
|
||||
// mute/unmute (firefox), = 181
|
||||
// decrease volume level (firefox), = 182
|
||||
// increase volume level (firefox), = 183
|
||||
Semicolon = 186,
|
||||
EqualSign = 187,
|
||||
Comma = 188,
|
||||
|
@ -866,10 +1115,27 @@ pub enum KeyCode {
|
|||
Period = 190,
|
||||
ForwardSlash = 191,
|
||||
GraveAccent = 192,
|
||||
// ?, / or °, = 193
|
||||
// numpad period (chrome), = 194
|
||||
OpenBracket = 219,
|
||||
BackSlash = 220,
|
||||
CloseBraket = 221,
|
||||
SingleQuote = 222,
|
||||
// `, = 223
|
||||
// left or right ⌘ key (firefox), = 224
|
||||
// altgr, = 225
|
||||
// < /git >, left back slash, = 226
|
||||
// GNOME Compose Key, = 230
|
||||
// ç, = 231
|
||||
// XF86Forward, = 233
|
||||
// XF86Back, = 234
|
||||
// non-conversion, = 235
|
||||
// alphanumeric, = 240
|
||||
// hiragana/katakana, = 242
|
||||
// half-width/full-width, = 243
|
||||
// kanji, = 244
|
||||
// unlock trackpad (Chrome/Edge), = 251
|
||||
// toggle touchpad, = 255
|
||||
Unknown,
|
||||
}
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@ use dioxus_core_macro::*;
|
|||
use dioxus_html as dioxus_elements;
|
||||
|
||||
fn main() {
|
||||
dioxus_desktop::launch(App, |c| c).unwrap();
|
||||
dioxus_desktop::launch(App, |c| c);
|
||||
}
|
||||
|
||||
static App: FC<()> = |cx, props| {
|
||||
|
|
|
@ -49,7 +49,9 @@ fn make_synthetic_event(name: &str, val: serde_json::Value) -> Box<dyn Any + Sen
|
|||
Box::new(serde_json::from_value::<CompositionEvent>(val).unwrap())
|
||||
}
|
||||
"keydown" | "keypress" | "keyup" => {
|
||||
Box::new(serde_json::from_value::<KeyboardEvent>(val).unwrap())
|
||||
dbg!(&val);
|
||||
let evt = serde_json::from_value::<KeyboardEvent>(val).unwrap();
|
||||
Box::new(evt)
|
||||
}
|
||||
"focus" | "blur" => {
|
||||
//
|
||||
|
|
|
@ -1,10 +1,14 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<!-- <meta name="viewport" content="width=device-width, initial-scale=1.0 maximum-scale=1.0 user-scalable=no"
|
||||
charset="utf-8" /> -->
|
||||
</head>
|
||||
|
||||
|
||||
<body>
|
||||
<div id="_dioxusroot">
|
||||
</div>
|
||||
|
|
|
@ -244,7 +244,8 @@ const SerializeMap = {
|
|||
"focus": serialize_focus,
|
||||
"blur": serialize_focus,
|
||||
|
||||
"change": serialize_change,
|
||||
"change": serialize_form,
|
||||
// "change": serialize_change,
|
||||
|
||||
"input": serialize_form,
|
||||
"invalid": serialize_form,
|
||||
|
@ -335,16 +336,17 @@ function serialize_composition(event) {
|
|||
}
|
||||
function serialize_keyboard(event) {
|
||||
return {
|
||||
alt_key: event.altKey,
|
||||
char_code: event.charCode,
|
||||
key: event.key,
|
||||
key_code: event.keyCode,
|
||||
alt_key: event.altKey,
|
||||
ctrl_key: event.ctrlKey,
|
||||
locale: event.locale,
|
||||
location: event.location,
|
||||
meta_key: event.metaKey,
|
||||
repeat: event.repeat,
|
||||
key_code: event.keyCode,
|
||||
shift_key: event.shiftKey,
|
||||
locale: "locale",
|
||||
// locale: event.locale,
|
||||
location: event.location,
|
||||
repeat: event.repeat,
|
||||
which: event.which,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -185,7 +185,6 @@ impl<T: 'static + Send> WebviewRenderer<T> {
|
|||
});
|
||||
}
|
||||
}
|
||||
// brad johnson go chat
|
||||
|
||||
fn main() {
|
||||
init_logging();
|
||||
|
|
Loading…
Reference in New Issue