mirror of
https://github.com/DioxusLabs/dioxus
synced 2024-11-10 06:34:20 +00:00
Merge branch 'master' into pr/Dangerised/1902
This commit is contained in:
commit
65d4f922b4
528 changed files with 17445 additions and 20909 deletions
12
.github/free_space.sh
vendored
Normal file
12
.github/free_space.sh
vendored
Normal file
|
@ -0,0 +1,12 @@
|
|||
df -h
|
||||
sudo rm -rf ${GITHUB_WORKSPACE}/.git
|
||||
sudo rm -rf "$AGENT_TOOLSDIRECTORY"
|
||||
sudo rm -rf /usr/share/dotnet
|
||||
sudo apt-get remove -y '^ghc-8.*'
|
||||
sudo apt-get remove -y '^dotnet-.*'
|
||||
sudo apt-get remove -y '^llvm-.*'
|
||||
sudo apt-get remove -y 'php.*'
|
||||
sudo apt-get remove -y azure-cli google-cloud-sdk hhvm google-chrome-stable firefox powershell mono-devel
|
||||
sudo apt-get autoremove -y
|
||||
sudo apt-get clean
|
||||
df -h
|
115
.github/workflows/main.yml
vendored
115
.github/workflows/main.yml
vendored
|
@ -13,7 +13,6 @@ on:
|
|||
- lib.rs
|
||||
- Cargo.toml
|
||||
- Makefile.toml
|
||||
- playwright-tests/**
|
||||
|
||||
pull_request:
|
||||
types: [opened, synchronize, reopened, ready_for_review]
|
||||
|
@ -70,12 +69,13 @@ jobs:
|
|||
- uses: davidB/rust-cargo-make@v1
|
||||
- uses: browser-actions/setup-firefox@latest
|
||||
- uses: jetli/wasm-pack-action@v0.4.0
|
||||
- run: sudo rm -rf /usr/share/dotnet
|
||||
- run: sudo rm -rf "$AGENT_TOOLSDIRECTORY"
|
||||
- run: |
|
||||
df -h
|
||||
sudo rm -rf ${GITHUB_WORKSPACE}/.git
|
||||
df -h
|
||||
- name: Free Disk Space (Ubuntu)
|
||||
uses: jlumbroso/free-disk-space@v1.3.1
|
||||
with: # speed things up a bit
|
||||
large-packages: false
|
||||
docker-images: false
|
||||
swap-storage: false
|
||||
|
||||
- run: cargo make tests
|
||||
|
||||
fmt:
|
||||
|
@ -112,42 +112,43 @@ jobs:
|
|||
save-if: ${{ github.ref == 'refs/heads/master' }}
|
||||
- run: cargo clippy --workspace --examples --tests -- -D warnings
|
||||
|
||||
miri:
|
||||
if: github.event.pull_request.draft == false
|
||||
name: Miri
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
CARGO_UNSTABLE_SPARSE_REGISTRY: 'true'
|
||||
RUSTFLAGS: -Dwarnings
|
||||
RUST_BACKTRACE: 1
|
||||
MIRIFLAGS: -Zmiri-tag-gc=1
|
||||
# Change to specific Rust release to pin
|
||||
rust_stable: stable
|
||||
rust_nightly: nightly-2023-11-16
|
||||
rust_clippy: 1.70.0
|
||||
# We removed most unsafe that we can, and using nightly doubles our cache size
|
||||
# miri:
|
||||
# if: github.event.pull_request.draft == false
|
||||
# name: Miri
|
||||
# runs-on: ubuntu-latest
|
||||
# env:
|
||||
# CARGO_UNSTABLE_SPARSE_REGISTRY: 'true'
|
||||
# RUSTFLAGS: -Dwarnings
|
||||
# RUST_BACKTRACE: 1
|
||||
# MIRIFLAGS: -Zmiri-tag-gc=1
|
||||
# # Change to specific Rust release to pin
|
||||
# rust_stable: stable
|
||||
# rust_nightly: nightly-2023-11-16
|
||||
# rust_clippy: 1.70.0
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: ilammy/setup-nasm@v1
|
||||
- name: Install Rust ${{ env.rust_nightly }}
|
||||
uses: dtolnay/rust-toolchain@master
|
||||
with:
|
||||
toolchain: ${{ env.rust_nightly }}
|
||||
components: miri
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
with:
|
||||
cache-all-crates: "true"
|
||||
save-if: ${{ github.ref == 'refs/heads/master' }}
|
||||
- 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 -- --exact --nocapture
|
||||
cargo miri test --package dioxus-native-core --test miri_native -- --exact --nocapture
|
||||
env:
|
||||
MIRIFLAGS: -Zmiri-disable-isolation -Zmiri-strict-provenance -Zmiri-retag-fields
|
||||
PROPTEST_CASES: 10
|
||||
# steps:
|
||||
# - uses: actions/checkout@v4
|
||||
# - uses: ilammy/setup-nasm@v1
|
||||
# - name: Install Rust ${{ env.rust_nightly }}
|
||||
# uses: dtolnay/rust-toolchain@master
|
||||
# with:
|
||||
# toolchain: ${{ env.rust_nightly }}
|
||||
# components: miri
|
||||
# - uses: Swatinem/rust-cache@v2
|
||||
# with:
|
||||
# cache-all-crates: "true"
|
||||
# save-if: ${{ github.ref == 'refs/heads/master' }}
|
||||
# - 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 -- --exact --nocapture
|
||||
# cargo miri test --package dioxus-native-core --test miri_native -- --exact --nocapture
|
||||
# env:
|
||||
# MIRIFLAGS: -Zmiri-disable-isolation -Zmiri-strict-provenance -Zmiri-retag-fields
|
||||
# PROPTEST_CASES: 10
|
||||
|
||||
playwright:
|
||||
if: github.event.pull_request.draft == false
|
||||
|
@ -160,6 +161,12 @@ jobs:
|
|||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 16
|
||||
- name: Free Disk Space (Ubuntu)
|
||||
uses: jlumbroso/free-disk-space@v1.3.1
|
||||
with: # speed things up a bit
|
||||
large-packages: false
|
||||
docker-images: false
|
||||
swap-storage: false
|
||||
- name: Install Rust
|
||||
uses: dtolnay/rust-toolchain@master
|
||||
with:
|
||||
|
@ -172,19 +179,19 @@ jobs:
|
|||
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
working-directory: ./playwright-tests
|
||||
working-directory: ./packages/playwright-tests
|
||||
|
||||
- name: Install Playwright
|
||||
run: npm install -D @playwright/test
|
||||
working-directory: ./playwright-tests
|
||||
working-directory: ./packages/playwright-tests
|
||||
|
||||
- name: Install Playwright Browsers
|
||||
run: npx playwright install --with-deps
|
||||
working-directory: ./playwright-tests
|
||||
working-directory: ./packages/playwright-tests
|
||||
|
||||
- name: Run Playwright tests
|
||||
run: npx playwright test
|
||||
working-directory: ./playwright-tests
|
||||
working-directory: ./packages/playwright-tests
|
||||
|
||||
- uses: actions/upload-artifact@v4
|
||||
if: always()
|
||||
|
@ -205,7 +212,7 @@ jobs:
|
|||
- {
|
||||
target: x86_64-pc-windows-msvc,
|
||||
os: windows-latest,
|
||||
toolchain: "1.70.0",
|
||||
toolchain: "1.75.0",
|
||||
cross: false,
|
||||
command: "test",
|
||||
args: "--all --tests",
|
||||
|
@ -213,7 +220,7 @@ jobs:
|
|||
- {
|
||||
target: x86_64-apple-darwin,
|
||||
os: macos-latest,
|
||||
toolchain: "1.70.0",
|
||||
toolchain: "1.75.0",
|
||||
cross: false,
|
||||
command: "test",
|
||||
args: "--all --tests",
|
||||
|
@ -221,7 +228,7 @@ jobs:
|
|||
- {
|
||||
target: aarch64-apple-ios,
|
||||
os: macos-latest,
|
||||
toolchain: "1.70.0",
|
||||
toolchain: "1.75.0",
|
||||
cross: false,
|
||||
command: "build",
|
||||
args: "--package dioxus-mobile",
|
||||
|
@ -229,7 +236,7 @@ jobs:
|
|||
- {
|
||||
target: aarch64-linux-android,
|
||||
os: ubuntu-latest,
|
||||
toolchain: "1.70.0",
|
||||
toolchain: "1.75.0",
|
||||
cross: true,
|
||||
command: "build",
|
||||
args: "--package dioxus-mobile",
|
||||
|
@ -250,6 +257,14 @@ jobs:
|
|||
|
||||
uses: taiki-e/install-action@cross
|
||||
|
||||
- name: Free Disk Space (Ubuntu)
|
||||
if: ${{ matrix.platform.os == 'ubuntu-latest' }}
|
||||
uses: jlumbroso/free-disk-space@v1.3.1
|
||||
with: # speed things up a bit
|
||||
large-packages: false
|
||||
docker-images: false
|
||||
swap-storage: false
|
||||
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
with:
|
||||
key: "${{ matrix.platform.target }}"
|
||||
|
|
8
.gitignore
vendored
8
.gitignore
vendored
|
@ -1,6 +1,6 @@
|
|||
/target
|
||||
/playwright-tests/web/dist
|
||||
/playwright-tests/fullstack/dist
|
||||
/packages/playwright-tests/web/dist
|
||||
/packages/playwright-tests/fullstack/dist
|
||||
/dist
|
||||
.DS_Store
|
||||
/examples/assets/test_video.mp4
|
||||
|
@ -19,5 +19,5 @@ tarpaulin-report.html
|
|||
.idea/
|
||||
node_modules/
|
||||
/test-results/
|
||||
/playwright-report/
|
||||
/playwright/.cache/
|
||||
/packages/playwright-report/
|
||||
/packages/playwright/.cache/
|
||||
|
|
1393
Cargo.lock
generated
1393
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
103
Cargo.toml
103
Cargo.toml
|
@ -2,10 +2,12 @@
|
|||
resolver = "2"
|
||||
members = [
|
||||
"packages/dioxus",
|
||||
"packages/dioxus-lib",
|
||||
"packages/core",
|
||||
"packages/cli",
|
||||
"packages/cli-config",
|
||||
"packages/core-macro",
|
||||
"packages/config-macro",
|
||||
"packages/router-macro",
|
||||
"packages/extension",
|
||||
"packages/router",
|
||||
|
@ -17,13 +19,12 @@ members = [
|
|||
"packages/desktop",
|
||||
"packages/mobile",
|
||||
"packages/interpreter",
|
||||
"packages/fermi",
|
||||
"packages/liveview",
|
||||
"packages/autofmt",
|
||||
"packages/check",
|
||||
"packages/rsx",
|
||||
"packages/dioxus-tui",
|
||||
"packages/rink",
|
||||
"packages/plasmo",
|
||||
"packages/native-core",
|
||||
"packages/native-core-macro",
|
||||
"packages/rsx-rosetta",
|
||||
|
@ -42,14 +43,13 @@ members = [
|
|||
# Full project examples
|
||||
"examples/tailwind",
|
||||
"examples/PWA-example",
|
||||
"examples/query_segments_demo",
|
||||
"examples/openid_connect_demo",
|
||||
# "examples/openid_connect_demo",
|
||||
# Playwright tests
|
||||
"playwright-tests/liveview",
|
||||
"playwright-tests/web",
|
||||
"playwright-tests/fullstack",
|
||||
"packages/playwright-tests/liveview",
|
||||
"packages/playwright-tests/web",
|
||||
"packages/playwright-tests/fullstack",
|
||||
]
|
||||
exclude = ["examples/mobile_demo"]
|
||||
exclude = ["examples/mobile_demo", "examples/openid_connect_demo",]
|
||||
|
||||
[workspace.package]
|
||||
version = "0.4.3"
|
||||
|
@ -57,25 +57,26 @@ version = "0.4.3"
|
|||
# dependencies that are shared across packages
|
||||
[workspace.dependencies]
|
||||
dioxus = { path = "packages/dioxus", version = "0.4.0" }
|
||||
dioxus-lib = { path = "packages/dioxus-lib", version = "0.4.0" }
|
||||
dioxus-core = { path = "packages/core", version = "0.4.2" }
|
||||
dioxus-core-macro = { path = "packages/core-macro", version = "0.4.0" }
|
||||
dioxus-router = { path = "packages/router", version = "0.4.1" }
|
||||
dioxus-config-macro = { path = "packages/config-macro", version = "0.4.0" }
|
||||
dioxus-router = { path = "packages/router", version = "0.4.1" }
|
||||
dioxus-router-macro = { path = "packages/router-macro", version = "0.4.1" }
|
||||
dioxus-html = { path = "packages/html", default-features = false, version = "0.4.0" }
|
||||
dioxus-html-internal-macro = { path = "packages/html-internal-macro", version = "0.4.0" }
|
||||
dioxus-html = { path = "packages/html", version = "0.4.0" }
|
||||
dioxus-html-internal-macro = { path = "packages/html-internal-macro", version = "0.4.0" }
|
||||
dioxus-hooks = { path = "packages/hooks", version = "0.4.0" }
|
||||
dioxus-web = { path = "packages/web", version = "0.4.0" }
|
||||
dioxus-ssr = { path = "packages/ssr", version = "0.4.0" }
|
||||
dioxus-ssr = { path = "packages/ssr", version = "0.4.0", default-features = false }
|
||||
dioxus-desktop = { path = "packages/desktop", version = "0.4.0" }
|
||||
dioxus-mobile = { path = "packages/mobile", version = "0.4.0" }
|
||||
dioxus-mobile = { path = "packages/mobile", version = "0.4.0" }
|
||||
dioxus-interpreter-js = { path = "packages/interpreter", version = "0.4.0" }
|
||||
fermi = { path = "packages/fermi", version = "0.4.0" }
|
||||
dioxus-liveview = { path = "packages/liveview", version = "0.4.0" }
|
||||
dioxus-autofmt = { path = "packages/autofmt", version = "0.4.0" }
|
||||
dioxus-check = { path = "packages/check", version = "0.4.0" }
|
||||
dioxus-rsx = { path = "packages/rsx", version = "0.4.0" }
|
||||
dioxus-tui = { path = "packages/dioxus-tui", version = "0.4.0" }
|
||||
plasmo = { path = "packages/rink", version = "0.4.0" }
|
||||
dioxus-liveview = { path = "packages/liveview", version = "0.4.0" }
|
||||
dioxus-autofmt = { path = "packages/autofmt", version = "0.4.0" }
|
||||
dioxus-check = { path = "packages/check", version = "0.4.0" }
|
||||
dioxus-rsx = { path = "packages/rsx", version = "0.4.0" }
|
||||
dioxus-tui = { path = "packages/dioxus-tui", version = "0.4.0" }
|
||||
plasmo = { path = "packages/plasmo", version = "0.4.0" }
|
||||
dioxus-native-core = { path = "packages/native-core", version = "0.4.0" }
|
||||
dioxus-native-core-macro = { path = "packages/native-core-macro", version = "0.4.0" }
|
||||
rsx-rosetta = { path = "packages/rsx-rosetta", version = "0.4.0" }
|
||||
|
@ -98,11 +99,11 @@ thiserror = "1.0.40"
|
|||
prettyplease = { package = "prettier-please", version = "0.2", features = [
|
||||
"verbatim",
|
||||
] }
|
||||
manganis-cli-support = { git = "https://github.com/DioxusLabs/collect-assets", rev = "e0093a4", features = [
|
||||
manganis-cli-support = { git = "https://github.com/DioxusLabs/collect-assets", rev = "f982698", features = [
|
||||
"webp",
|
||||
"html",
|
||||
] }
|
||||
manganis = { git = "https://github.com/DioxusLabs/collect-assets", rev = "e0093a4" }
|
||||
manganis = { git = "https://github.com/DioxusLabs/collect-assets", rev = "f982698" }
|
||||
|
||||
# This is a "virtual package"
|
||||
# It is not meant to be published, but is used so "cargo run --example XYZ" works properly
|
||||
|
@ -120,27 +121,53 @@ keywords = ["dom", "ui", "gui", "react", "wasm"]
|
|||
rust-version = "1.60.0"
|
||||
publish = false
|
||||
|
||||
[dependencies]
|
||||
manganis = { workspace = true, optional = true}
|
||||
reqwest = { version = "0.11.9", features = ["json"], optional = true}
|
||||
http-range = {version = "0.1.5", optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
dioxus = { workspace = true }
|
||||
dioxus-desktop = { workspace = true, features = ["transparent"] }
|
||||
dioxus = { workspace = true, features = ["router"] }
|
||||
dioxus-ssr = { workspace = true }
|
||||
dioxus-router = { workspace = true }
|
||||
dioxus-signals = { workspace = true }
|
||||
fermi = { workspace = true }
|
||||
futures-util = "0.3.21"
|
||||
log = "0.4.14"
|
||||
num-format = "0.4.0"
|
||||
separator = "0.4.1"
|
||||
serde = { version = "1.0.136", features = ["derive"] }
|
||||
im-rc = "15.0.0"
|
||||
anyhow = "1.0.53"
|
||||
serde_json = "1.0.79"
|
||||
rand = { version = "0.8.4", features = ["small_rng"] }
|
||||
form_urlencoded = "1.2.0"
|
||||
|
||||
[target.'cfg(target_arch = "wasm32")'.dev-dependencies]
|
||||
getrandom = { version = "0.2.12", features = ["js"] }
|
||||
|
||||
[target.'cfg(not(target_arch = "wasm32"))'.dev-dependencies]
|
||||
tokio = { version = "1.16.1", features = ["full"] }
|
||||
reqwest = { version = "0.11.9", features = ["json"] }
|
||||
env_logger = "0.10.0"
|
||||
simple_logger = "4.0.0"
|
||||
thiserror = { workspace = true }
|
||||
manganis = { workspace = true }
|
||||
tracing-subscriber = "0.3.17"
|
||||
http-range = "0.1.5"
|
||||
|
||||
# To make most examples faster to compile, we split out assets and http-related stuff
|
||||
# This trims off like 270 dependencies, leading to a significant speedup in compilation time
|
||||
[features]
|
||||
liveview = ["dioxus/liveview"]
|
||||
fullstack = ["dioxus/fullstack"]
|
||||
axum = ["dioxus/axum"]
|
||||
salvo = ["dioxus/salvo"]
|
||||
rocket = ["dioxus/rocket"]
|
||||
server = ["dioxus/axum"]
|
||||
default = ["dioxus/desktop"]
|
||||
web = ["dioxus/web"]
|
||||
collect-assets = ["manganis"]
|
||||
http = ["reqwest", "http-range"]
|
||||
|
||||
[[example]]
|
||||
name = "login_form"
|
||||
required-features = ["http"]
|
||||
|
||||
[[example]]
|
||||
name = "dog_app"
|
||||
required-features = ["http"]
|
||||
|
||||
[[example]]
|
||||
name = "video_stream"
|
||||
required-features = ["http"]
|
||||
|
||||
[[example]]
|
||||
name = "suspense"
|
||||
required-features = ["http"]
|
||||
|
|
11
README.md
11
README.md
|
@ -52,17 +52,20 @@
|
|||
|
||||
<br/>
|
||||
|
||||
> [!WARNING]
|
||||
> Dioxus 0.5 (currently in master) contains massive breaking changes and is not compatible with Dioxus 0.4
|
||||
|
||||
Dioxus is a portable, performant, and ergonomic framework for building cross-platform user interfaces in Rust.
|
||||
|
||||
```rust
|
||||
fn app(cx: Scope) -> Element {
|
||||
let mut count = use_state(cx, || 0);
|
||||
fn app() -> Element {
|
||||
let mut count = use_signal(|| 0);
|
||||
|
||||
cx.render(rsx! {
|
||||
rsx! {
|
||||
h1 { "High-Five counter: {count}" }
|
||||
button { onclick: move |_| count += 1, "Up high!" }
|
||||
button { onclick: move |_| count -= 1, "Down low!" }
|
||||
})
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
|
|
@ -5,16 +5,17 @@ fn main() {
|
|||
wasm_logger::init(wasm_logger::Config::default());
|
||||
console_error_panic_hook::set_once();
|
||||
|
||||
dioxus_web::launch(app);
|
||||
launch(app);
|
||||
}
|
||||
|
||||
fn app(cx: Scope) -> Element {
|
||||
cx.render(rsx! (
|
||||
div {
|
||||
style: "text-align: center;",
|
||||
fn app() -> Element {
|
||||
rsx! (
|
||||
div { style: "text-align: center;",
|
||||
h1 { "🌗 Dioxus 🚀" }
|
||||
h3 { "Frontend that scales." }
|
||||
p { "Dioxus is a portable, performant, and ergonomic framework for building cross-platform user interfaces in Rust." }
|
||||
p {
|
||||
"Dioxus is a portable, performant, and ergonomic framework for building cross-platform user interfaces in Rust."
|
||||
}
|
||||
}
|
||||
))
|
||||
)
|
||||
}
|
||||
|
|
|
@ -1,413 +0,0 @@
|
|||
use dioxus::prelude::*;
|
||||
|
||||
fn main() {
|
||||
dioxus_desktop::launch(app);
|
||||
}
|
||||
|
||||
fn app(cx: Scope) -> Element {
|
||||
cx.render(rsx! {
|
||||
div {
|
||||
align_content: "a",
|
||||
align_items: "a",
|
||||
align_self: "a",
|
||||
alignment_adjust: "a",
|
||||
alignment_baseline: "a",
|
||||
all: "a",
|
||||
alt: "a",
|
||||
animation: "a",
|
||||
animation_delay: "a",
|
||||
animation_direction: "a",
|
||||
animation_duration: "a",
|
||||
animation_fill_mode: "a",
|
||||
animation_iteration_count: "a",
|
||||
animation_name: "a",
|
||||
animation_play_state: "a",
|
||||
animation_timing_function: "a",
|
||||
azimuth: "a",
|
||||
backface_visibility: "a",
|
||||
background: "a",
|
||||
background_attachment: "a",
|
||||
background_clip: "a",
|
||||
background_color: "a",
|
||||
background_image: "a",
|
||||
background_origin: "a",
|
||||
background_position: "a",
|
||||
background_repeat: "a",
|
||||
background_size: "a",
|
||||
background_blend_mode: "a",
|
||||
baseline_shift: "a",
|
||||
bleed: "a",
|
||||
bookmark_label: "a",
|
||||
bookmark_level: "a",
|
||||
bookmark_state: "a",
|
||||
border: "a",
|
||||
border_color: "a",
|
||||
border_style: "a",
|
||||
border_width: "a",
|
||||
border_bottom: "a",
|
||||
border_bottom_color: "a",
|
||||
border_bottom_style: "a",
|
||||
border_bottom_width: "a",
|
||||
border_left: "a",
|
||||
border_left_color: "a",
|
||||
border_left_style: "a",
|
||||
border_left_width: "a",
|
||||
border_right: "a",
|
||||
border_right_color: "a",
|
||||
border_right_style: "a",
|
||||
border_right_width: "a",
|
||||
border_top: "a",
|
||||
border_top_color: "a",
|
||||
border_top_style: "a",
|
||||
border_top_width: "a",
|
||||
border_collapse: "a",
|
||||
border_image: "a",
|
||||
border_image_outset: "a",
|
||||
border_image_repeat: "a",
|
||||
border_image_slice: "a",
|
||||
border_image_source: "a",
|
||||
border_image_width: "a",
|
||||
border_radius: "a",
|
||||
border_bottom_left_radius: "a",
|
||||
border_bottom_right_radius: "a",
|
||||
border_top_left_radius: "a",
|
||||
border_top_right_radius: "a",
|
||||
border_spacing: "a",
|
||||
bottom: "a",
|
||||
box_decoration_break: "a",
|
||||
box_shadow: "a",
|
||||
box_sizing: "a",
|
||||
box_snap: "a",
|
||||
break_after: "a",
|
||||
break_before: "a",
|
||||
break_inside: "a",
|
||||
buffered_rendering: "a",
|
||||
caption_side: "a",
|
||||
clear: "a",
|
||||
clear_side: "a",
|
||||
clip: "a",
|
||||
clip_path: "a",
|
||||
clip_rule: "a",
|
||||
color: "a",
|
||||
color_adjust: "a",
|
||||
color_correction: "a",
|
||||
color_interpolation: "a",
|
||||
color_interpolation_filters: "a",
|
||||
color_profile: "a",
|
||||
color_rendering: "a",
|
||||
column_fill: "a",
|
||||
column_gap: "a",
|
||||
column_rule: "a",
|
||||
column_rule_color: "a",
|
||||
column_rule_style: "a",
|
||||
column_rule_width: "a",
|
||||
column_span: "a",
|
||||
columns: "a",
|
||||
column_count: "a",
|
||||
column_width: "a",
|
||||
contain: "a",
|
||||
content: "a",
|
||||
counter_increment: "a",
|
||||
counter_reset: "a",
|
||||
counter_set: "a",
|
||||
cue: "a",
|
||||
cue_after: "a",
|
||||
cue_before: "a",
|
||||
cursor: "a",
|
||||
direction: "a",
|
||||
display: "a",
|
||||
display_inside: "a",
|
||||
display_outside: "a",
|
||||
display_extras: "a",
|
||||
display_box: "a",
|
||||
dominant_baseline: "a",
|
||||
elevation: "a",
|
||||
empty_cells: "a",
|
||||
enable_background: "a",
|
||||
fill: "a",
|
||||
fill_opacity: "a",
|
||||
fill_rule: "a",
|
||||
filter: "a",
|
||||
float: "a",
|
||||
float_defer_column: "a",
|
||||
float_defer_page: "a",
|
||||
float_offset: "a",
|
||||
float_wrap: "a",
|
||||
flow_into: "a",
|
||||
flow_from: "a",
|
||||
flex: "a",
|
||||
flex_basis: "a",
|
||||
flex_grow: "a",
|
||||
flex_shrink: "a",
|
||||
flex_flow: "a",
|
||||
flex_direction: "a",
|
||||
flex_wrap: "a",
|
||||
flood_color: "a",
|
||||
flood_opacity: "a",
|
||||
font: "a",
|
||||
font_family: "a",
|
||||
font_size: "a",
|
||||
font_stretch: "a",
|
||||
font_style: "a",
|
||||
font_weight: "a",
|
||||
font_feature_settings: "a",
|
||||
font_kerning: "a",
|
||||
font_language_override: "a",
|
||||
font_size_adjust: "a",
|
||||
font_synthesis: "a",
|
||||
font_variant: "a",
|
||||
font_variant_alternates: "a",
|
||||
font_variant_caps: "a",
|
||||
font_variant_east_asian: "a",
|
||||
font_variant_ligatures: "a",
|
||||
font_variant_numeric: "a",
|
||||
font_variant_position: "a",
|
||||
footnote_policy: "a",
|
||||
glyph_orientation_horizontal: "a",
|
||||
glyph_orientation_vertical: "a",
|
||||
grid: "a",
|
||||
grid_auto_flow: "a",
|
||||
grid_auto_columns: "a",
|
||||
grid_auto_rows: "a",
|
||||
grid_template: "a",
|
||||
grid_template_areas: "a",
|
||||
grid_template_columns: "a",
|
||||
grid_template_rows: "a",
|
||||
grid_area: "a",
|
||||
grid_column: "a",
|
||||
grid_column_start: "a",
|
||||
grid_column_end: "a",
|
||||
grid_row: "a",
|
||||
grid_row_start: "a",
|
||||
grid_row_end: "a",
|
||||
hanging_punctuation: "a",
|
||||
height: "a",
|
||||
hyphenate_character: "a",
|
||||
hyphenate_limit_chars: "a",
|
||||
hyphenate_limit_last: "a",
|
||||
hyphenate_limit_lines: "a",
|
||||
hyphenate_limit_zone: "a",
|
||||
hyphens: "a",
|
||||
icon: "a",
|
||||
image_orientation: "a",
|
||||
image_resolution: "a",
|
||||
image_rendering: "a",
|
||||
ime: "a",
|
||||
ime_align: "a",
|
||||
ime_mode: "a",
|
||||
ime_offset: "a",
|
||||
ime_width: "a",
|
||||
initial_letters: "a",
|
||||
inline_box_align: "a",
|
||||
isolation: "a",
|
||||
justify_content: "a",
|
||||
justify_items: "a",
|
||||
justify_self: "a",
|
||||
kerning: "a",
|
||||
left: "a",
|
||||
letter_spacing: "a",
|
||||
lighting_color: "a",
|
||||
line_box_contain: "a",
|
||||
line_break: "a",
|
||||
line_grid: "a",
|
||||
line_height: "a",
|
||||
line_slack: "a",
|
||||
line_snap: "a",
|
||||
list_style: "a",
|
||||
list_style_image: "a",
|
||||
list_style_position: "a",
|
||||
list_style_type: "a",
|
||||
margin: "a",
|
||||
margin_bottom: "a",
|
||||
margin_left: "a",
|
||||
margin_right: "a",
|
||||
margin_top: "a",
|
||||
marker: "a",
|
||||
marker_end: "a",
|
||||
marker_mid: "a",
|
||||
marker_pattern: "a",
|
||||
marker_segment: "a",
|
||||
marker_start: "a",
|
||||
marker_knockout_left: "a",
|
||||
marker_knockout_right: "a",
|
||||
marker_side: "a",
|
||||
marks: "a",
|
||||
marquee_direction: "a",
|
||||
marquee_play_count: "a",
|
||||
marquee_speed: "a",
|
||||
marquee_style: "a",
|
||||
mask: "a",
|
||||
mask_image: "a",
|
||||
mask_repeat: "a",
|
||||
mask_position: "a",
|
||||
mask_clip: "a",
|
||||
mask_origin: "a",
|
||||
mask_size: "a",
|
||||
mask_box: "a",
|
||||
mask_box_outset: "a",
|
||||
mask_box_repeat: "a",
|
||||
mask_box_slice: "a",
|
||||
mask_box_source: "a",
|
||||
mask_box_width: "a",
|
||||
mask_type: "a",
|
||||
max_height: "a",
|
||||
max_lines: "a",
|
||||
max_width: "a",
|
||||
min_height: "a",
|
||||
min_width: "a",
|
||||
mix_blend_mode: "a",
|
||||
nav_down: "a",
|
||||
nav_index: "a",
|
||||
nav_left: "a",
|
||||
nav_right: "a",
|
||||
nav_up: "a",
|
||||
object_fit: "a",
|
||||
object_position: "a",
|
||||
offset_after: "a",
|
||||
offset_before: "a",
|
||||
offset_end: "a",
|
||||
offset_start: "a",
|
||||
opacity: "a",
|
||||
order: "a",
|
||||
orphans: "a",
|
||||
outline: "a",
|
||||
outline_color: "a",
|
||||
outline_style: "a",
|
||||
outline_width: "a",
|
||||
outline_offset: "a",
|
||||
overflow: "a",
|
||||
overflow_x: "a",
|
||||
overflow_y: "a",
|
||||
overflow_style: "a",
|
||||
overflow_wrap: "a",
|
||||
padding: "a",
|
||||
padding_bottom: "a",
|
||||
padding_left: "a",
|
||||
padding_right: "a",
|
||||
padding_top: "a",
|
||||
page: "a",
|
||||
page_break_after: "a",
|
||||
page_break_before: "a",
|
||||
page_break_inside: "a",
|
||||
paint_order: "a",
|
||||
pause: "a",
|
||||
pause_after: "a",
|
||||
pause_before: "a",
|
||||
perspective: "a",
|
||||
perspective_origin: "a",
|
||||
pitch: "a",
|
||||
pitch_range: "a",
|
||||
play_during: "a",
|
||||
pointer_events: "a",
|
||||
position: "a",
|
||||
quotes: "a",
|
||||
region_fragment: "a",
|
||||
resize: "a",
|
||||
rest: "a",
|
||||
rest_after: "a",
|
||||
rest_before: "a",
|
||||
richness: "a",
|
||||
right: "a",
|
||||
ruby_align: "a",
|
||||
ruby_merge: "a",
|
||||
ruby_position: "a",
|
||||
scroll_behavior: "a",
|
||||
scroll_snap_coordinate: "a",
|
||||
scroll_snap_destination: "a",
|
||||
scroll_snap_points_x: "a",
|
||||
scroll_snap_points_y: "a",
|
||||
scroll_snap_type: "a",
|
||||
shape_image_threshold: "a",
|
||||
shape_inside: "a",
|
||||
shape_margin: "a",
|
||||
shape_outside: "a",
|
||||
shape_padding: "a",
|
||||
shape_rendering: "a",
|
||||
size: "a",
|
||||
speak: "a",
|
||||
speak_as: "a",
|
||||
speak_header: "a",
|
||||
speak_numeral: "a",
|
||||
speak_punctuation: "a",
|
||||
speech_rate: "a",
|
||||
stop_color: "a",
|
||||
stop_opacity: "a",
|
||||
stress: "a",
|
||||
string_set: "a",
|
||||
stroke: "a",
|
||||
stroke_dasharray: "a",
|
||||
stroke_dashoffset: "a",
|
||||
stroke_linecap: "a",
|
||||
stroke_linejoin: "a",
|
||||
stroke_miterlimit: "a",
|
||||
stroke_opacity: "a",
|
||||
stroke_width: "a",
|
||||
tab_size: "a",
|
||||
table_layout: "a",
|
||||
text_align: "a",
|
||||
text_align_all: "a",
|
||||
text_align_last: "a",
|
||||
text_anchor: "a",
|
||||
text_combine_upright: "a",
|
||||
text_decoration: "a",
|
||||
text_decoration_color: "a",
|
||||
text_decoration_line: "a",
|
||||
text_decoration_style: "a",
|
||||
text_decoration_skip: "a",
|
||||
text_emphasis: "a",
|
||||
text_emphasis_color: "a",
|
||||
text_emphasis_style: "a",
|
||||
text_emphasis_position: "a",
|
||||
text_emphasis_skip: "a",
|
||||
text_height: "a",
|
||||
text_indent: "a",
|
||||
text_justify: "a",
|
||||
text_orientation: "a",
|
||||
text_overflow: "a",
|
||||
text_rendering: "a",
|
||||
text_shadow: "a",
|
||||
text_size_adjust: "a",
|
||||
text_space_collapse: "a",
|
||||
text_spacing: "a",
|
||||
text_transform: "a",
|
||||
text_underline_position: "a",
|
||||
text_wrap: "a",
|
||||
top: "a",
|
||||
touch_action: "a",
|
||||
transform: "a",
|
||||
transform_box: "a",
|
||||
transform_origin: "a",
|
||||
transform_style: "a",
|
||||
transition: "a",
|
||||
transition_delay: "a",
|
||||
transition_duration: "a",
|
||||
transition_property: "a",
|
||||
unicode_bidi: "a",
|
||||
vector_effect: "a",
|
||||
vertical_align: "a",
|
||||
visibility: "a",
|
||||
voice_balance: "a",
|
||||
voice_duration: "a",
|
||||
voice_family: "a",
|
||||
voice_pitch: "a",
|
||||
voice_range: "a",
|
||||
voice_rate: "a",
|
||||
voice_stress: "a",
|
||||
voice_volumn: "a",
|
||||
volume: "a",
|
||||
white_space: "a",
|
||||
widows: "a",
|
||||
width: "a",
|
||||
will_change: "a",
|
||||
word_break: "a",
|
||||
word_spacing: "a",
|
||||
word_wrap: "a",
|
||||
wrap_flow: "a",
|
||||
wrap_through: "a",
|
||||
writing_mode: "a",
|
||||
z_index: "a",
|
||||
|
||||
"This example isn't quite useful yet"
|
||||
}
|
||||
})
|
||||
}
|
|
@ -1,78 +1,59 @@
|
|||
use dioxus::{events::*, html::MouseEvent, prelude::*};
|
||||
use dioxus::prelude::*;
|
||||
use std::{collections::VecDeque, fmt::Debug, rc::Rc};
|
||||
|
||||
fn main() {
|
||||
dioxus_desktop::launch(app);
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
enum Event {
|
||||
MouseMove(MouseEvent),
|
||||
MouseClick(MouseEvent),
|
||||
MouseDoubleClick(MouseEvent),
|
||||
MouseDown(MouseEvent),
|
||||
MouseUp(MouseEvent),
|
||||
|
||||
Wheel(WheelEvent),
|
||||
|
||||
KeyDown(KeyboardEvent),
|
||||
KeyUp(KeyboardEvent),
|
||||
KeyPress(KeyboardEvent),
|
||||
|
||||
FocusIn(FocusEvent),
|
||||
FocusOut(FocusEvent),
|
||||
launch(app);
|
||||
}
|
||||
|
||||
const MAX_EVENTS: usize = 8;
|
||||
|
||||
const CONTAINER_STYLE: &str = r#"
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
"#;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
"#;
|
||||
|
||||
const RECT_STYLE: &str = r#"
|
||||
background: deepskyblue;
|
||||
height: 50vh;
|
||||
width: 50vw;
|
||||
color: white;
|
||||
padding: 20px;
|
||||
margin: 20px;
|
||||
text-aligh: center;
|
||||
"#;
|
||||
background: deepskyblue;
|
||||
height: 50vh;
|
||||
width: 50vw;
|
||||
color: white;
|
||||
padding: 20px;
|
||||
margin: 20px;
|
||||
text-aligh: center;
|
||||
"#;
|
||||
|
||||
fn app(cx: Scope) -> Element {
|
||||
let events = use_ref(cx, std::collections::VecDeque::new);
|
||||
fn app() -> Element {
|
||||
let mut events = use_signal(|| VecDeque::new() as VecDeque<Rc<dyn Debug>>);
|
||||
|
||||
let log_event = move |event: Event| {
|
||||
let mut log_event = move |event: Rc<dyn Debug>| {
|
||||
let mut events = events.write();
|
||||
|
||||
if events.len() >= MAX_EVENTS {
|
||||
events.pop_front();
|
||||
}
|
||||
|
||||
events.push_back(event);
|
||||
};
|
||||
|
||||
cx.render(rsx! (
|
||||
rsx! {
|
||||
div { style: "{CONTAINER_STYLE}",
|
||||
div {
|
||||
style: "{RECT_STYLE}",
|
||||
// focusing is necessary to catch keyboard events
|
||||
tabindex: "0",
|
||||
// focusing is necessary to catch keyboard events
|
||||
div { style: "{RECT_STYLE}", tabindex: 0,
|
||||
onmousemove: move |event| log_event(event.data()),
|
||||
onclick: move |event| log_event(event.data()),
|
||||
ondoubleclick: move |event| log_event(event.data()),
|
||||
onmousedown: move |event| log_event(event.data()),
|
||||
onmouseup: move |event| log_event(event.data()),
|
||||
|
||||
onmousemove: move |event| log_event(Event::MouseMove(event)),
|
||||
onclick: move |event| log_event(Event::MouseClick(event)),
|
||||
ondoubleclick: move |event| log_event(Event::MouseDoubleClick(event)),
|
||||
onmousedown: move |event| log_event(Event::MouseDown(event)),
|
||||
onmouseup: move |event| log_event(Event::MouseUp(event)),
|
||||
onwheel: move |event| log_event(event.data()),
|
||||
|
||||
onwheel: move |event| log_event(Event::Wheel(event)),
|
||||
onkeydown: move |event| log_event(event.data()),
|
||||
onkeyup: move |event| log_event(event.data()),
|
||||
onkeypress: move |event| log_event(event.data()),
|
||||
|
||||
onkeydown: move |event| log_event(Event::KeyDown(event)),
|
||||
onkeyup: move |event| log_event(Event::KeyUp(event)),
|
||||
onkeypress: move |event| log_event(Event::KeyPress(event)),
|
||||
|
||||
onfocusin: move |event| log_event(Event::FocusIn(event)),
|
||||
onfocusout: move |event| log_event(Event::FocusOut(event)),
|
||||
onfocusin: move |event| log_event(event.data()),
|
||||
onfocusout: move |event| log_event(event.data()),
|
||||
|
||||
"Hover, click, type or scroll to see the info down below"
|
||||
}
|
||||
|
@ -82,5 +63,5 @@ fn app(cx: Scope) -> Element {
|
|||
}
|
||||
}
|
||||
}
|
||||
))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,13 +1,17 @@
|
|||
html {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
*, *:before, *:after {
|
||||
|
||||
*,
|
||||
*:before,
|
||||
*:after {
|
||||
box-sizing: inherit;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
font: 100 14px 'Roboto';
|
||||
font-family: Arial;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
|
@ -20,18 +24,18 @@ button {
|
|||
user-select: none;
|
||||
cursor: pointer;
|
||||
outline: none;
|
||||
|
||||
-webkit-tap-highlight-color: rgba(0,0,0,0);
|
||||
|
||||
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
|
||||
}
|
||||
|
||||
button:active {
|
||||
box-shadow: inset 0px 0px 80px 0px rgba(0,0,0,0.25);
|
||||
box-shadow: inset 0px 0px 80px 0px rgba(0, 0, 0, 0.25);
|
||||
}
|
||||
|
||||
#wrapper {
|
||||
/* height: 100vh; */
|
||||
height: max-content;
|
||||
|
||||
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
@ -47,7 +51,7 @@ button:active {
|
|||
width: 100%;
|
||||
height: 100%;
|
||||
background: black;
|
||||
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
@ -61,14 +65,14 @@ button:active {
|
|||
background: #1c191c;
|
||||
line-height: 130px;
|
||||
/* font-size: 6em; */
|
||||
font-size: 16px;
|
||||
font-size: 4vw;
|
||||
font-size: 16px;
|
||||
font-size: 4vw;
|
||||
|
||||
|
||||
max-height: 160px;
|
||||
padding: 0 30px;
|
||||
/* height: 80px; */
|
||||
|
||||
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
|
@ -85,7 +89,7 @@ button:active {
|
|||
|
||||
.calculator-keypad {
|
||||
height: 400px;
|
||||
|
||||
|
||||
display: flex;
|
||||
}
|
||||
|
||||
|
@ -99,7 +103,7 @@ button:active {
|
|||
|
||||
.calculator .digit-keys {
|
||||
background: #e0e0e7;
|
||||
|
||||
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap-reverse;
|
||||
|
@ -109,28 +113,34 @@ button:active {
|
|||
width: 80px;
|
||||
height: 80px;
|
||||
border-top: 1px solid #777;
|
||||
border-right: 1px solid #666;
|
||||
border-right: 1px solid #666;
|
||||
text-align: center;
|
||||
line-height: 80px;
|
||||
}
|
||||
|
||||
.calculator .function-keys .calculator-key {
|
||||
font-size: 2em;
|
||||
}
|
||||
|
||||
.calculator .function-keys .key-multiply {
|
||||
line-height: 50px;
|
||||
}
|
||||
|
||||
.calculator .digit-keys .calculator-key {
|
||||
font-size: 2.25em;
|
||||
}
|
||||
|
||||
.calculator .digit-keys .key-0 {
|
||||
width: 160px;
|
||||
text-align: left;
|
||||
padding-left: 32px;
|
||||
}
|
||||
|
||||
.calculator .digit-keys .key-dot {
|
||||
padding-top: 1em;
|
||||
font-size: 0.75em;
|
||||
}
|
||||
|
||||
.calculator .operator-keys .calculator-key {
|
||||
color: white;
|
||||
border-right: 0;
|
||||
|
@ -138,8 +148,9 @@ button:active {
|
|||
}
|
||||
|
||||
.calculator .function-keys {
|
||||
background: linear-gradient(to bottom, rgba(202,202,204,1) 0%, rgba(196,194,204,1) 100%);
|
||||
background: linear-gradient(to bottom, rgba(202, 202, 204, 1) 0%, rgba(196, 194, 204, 1) 100%);
|
||||
}
|
||||
|
||||
.calculator .operator-keys {
|
||||
background: linear-gradient(to bottom, rgba(252,156,23,1) 0%, rgba(247,126,27,1) 100%);
|
||||
background: linear-gradient(to bottom, rgba(252, 156, 23, 1) 0%, rgba(247, 126, 27, 1) 100%);
|
||||
}
|
||||
|
|
55
examples/backgrounded_futures.rs
Normal file
55
examples/backgrounded_futures.rs
Normal file
|
@ -0,0 +1,55 @@
|
|||
use dioxus::prelude::*;
|
||||
|
||||
fn main() {
|
||||
launch_desktop(app);
|
||||
}
|
||||
|
||||
fn app() -> Element {
|
||||
let mut show_child = use_signal(|| true);
|
||||
let mut count = use_signal(|| 0);
|
||||
|
||||
let child = use_memo(move || {
|
||||
rsx! {
|
||||
Child {
|
||||
count
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
rsx! {
|
||||
button { onclick: move |_| show_child.toggle(), "Toggle child" }
|
||||
button { onclick: move |_| count += 1, "Increment count" }
|
||||
if show_child() {
|
||||
{child.cloned()}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[component]
|
||||
fn Child(count: Signal<i32>) -> Element {
|
||||
let mut early_return = use_signal(|| false);
|
||||
|
||||
let early = rsx! {
|
||||
button { onclick: move |_| early_return.toggle(), "Toggle {early_return} early return" }
|
||||
};
|
||||
|
||||
if early_return() {
|
||||
return early;
|
||||
}
|
||||
|
||||
use_future(move || async move {
|
||||
loop {
|
||||
tokio::time::sleep(std::time::Duration::from_millis(100)).await;
|
||||
println!("Child")
|
||||
}
|
||||
});
|
||||
|
||||
use_effect(move || {
|
||||
println!("Child count: {}", count());
|
||||
});
|
||||
|
||||
rsx! {
|
||||
"hellO!"
|
||||
{early}
|
||||
}
|
||||
}
|
|
@ -1,73 +0,0 @@
|
|||
#![allow(non_snake_case)]
|
||||
|
||||
/*
|
||||
Dioxus manages borrow lifetimes for you. This means any child may borrow from its parent. However, it is not possible
|
||||
to hand out an &mut T to children - all props are consumed by &P, so you'd only get an &&mut T.
|
||||
|
||||
How does it work?
|
||||
|
||||
Dioxus will manually drop closures and props - things that borrow data before the component is ran again. This is done
|
||||
"bottom up" from the lowest child all the way to the initiating parent. As it traverses each listener and prop, the
|
||||
drop implementation is manually called, freeing any memory and ensuring that memory is not leaked.
|
||||
|
||||
We cannot drop from the parent to the children - if the drop implementation modifies the data, downstream references
|
||||
might be broken since we take an &mut T and and &T to the data. Instead, we work bottom up, making sure to remove any
|
||||
potential references to the data before finally giving out an &mut T. This prevents us from mutably aliasing the data,
|
||||
and is proven to be safe with MIRI.
|
||||
*/
|
||||
|
||||
use dioxus::prelude::*;
|
||||
|
||||
fn main() {
|
||||
dioxus_desktop::launch(app);
|
||||
}
|
||||
|
||||
fn app(cx: Scope) -> Element {
|
||||
let text = cx.use_hook(|| vec![String::from("abc=def")]);
|
||||
|
||||
let first = text.get_mut(0).unwrap();
|
||||
|
||||
cx.render(rsx! {
|
||||
div {
|
||||
Child1 { text: first }
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
#[derive(Props)]
|
||||
struct C1Props<'a> {
|
||||
text: &'a mut String,
|
||||
}
|
||||
|
||||
fn Child1<'a>(cx: Scope<'a, C1Props<'a>>) -> Element {
|
||||
let (left, right) = cx.props.text.split_once('=').unwrap();
|
||||
|
||||
cx.render(rsx! {
|
||||
div {
|
||||
Child2 { text: left }
|
||||
Child2 { text: right }
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
#[derive(Props)]
|
||||
struct C2Props<'a> {
|
||||
text: &'a str,
|
||||
}
|
||||
|
||||
fn Child2<'a>(cx: Scope<'a, C2Props<'a>>) -> Element {
|
||||
cx.render(rsx! {
|
||||
Child3 { text: cx.props.text }
|
||||
})
|
||||
}
|
||||
|
||||
#[derive(Props)]
|
||||
struct C3Props<'a> {
|
||||
text: &'a str,
|
||||
}
|
||||
|
||||
fn Child3<'a>(cx: Scope<'a, C3Props<'a>>) -> Element {
|
||||
cx.render(rsx! {
|
||||
div { "{cx.props.text}"}
|
||||
})
|
||||
}
|
|
@ -6,65 +6,58 @@ This calculator version uses React-style state management. All state is held as
|
|||
use dioxus::events::*;
|
||||
use dioxus::html::input_data::keyboard_types::Key;
|
||||
use dioxus::prelude::*;
|
||||
use dioxus_desktop::{Config, LogicalSize, WindowBuilder};
|
||||
|
||||
fn main() {
|
||||
let config = Config::new().with_window(
|
||||
WindowBuilder::default()
|
||||
.with_title("Calculator")
|
||||
.with_inner_size(LogicalSize::new(300.0, 500.0)),
|
||||
);
|
||||
|
||||
dioxus_desktop::launch_cfg(app, config);
|
||||
LaunchBuilder::new()
|
||||
.with_cfg(desktop!({
|
||||
use dioxus::desktop::{Config, LogicalSize, WindowBuilder};
|
||||
Config::new().with_window(
|
||||
WindowBuilder::default()
|
||||
.with_title("Calculator")
|
||||
.with_inner_size(LogicalSize::new(300.0, 525.0)),
|
||||
)
|
||||
}))
|
||||
.launch(app);
|
||||
}
|
||||
|
||||
fn app(cx: Scope) -> Element {
|
||||
let val = use_state(cx, || String::from("0"));
|
||||
fn app() -> Element {
|
||||
let mut val = use_signal(|| String::from("0"));
|
||||
|
||||
let input_digit = move |num: u8| {
|
||||
if val.get() == "0" {
|
||||
let mut input_digit = move |num: String| {
|
||||
if val() == "0" {
|
||||
val.set(String::new());
|
||||
}
|
||||
|
||||
val.make_mut().push_str(num.to_string().as_str());
|
||||
val.write().push_str(num.as_str());
|
||||
};
|
||||
|
||||
let input_operator = move |key: &str| val.make_mut().push_str(key);
|
||||
let mut input_operator = move |key: &str| val.write().push_str(key);
|
||||
|
||||
let handle_key_down_event = move |evt: KeyboardEvent| match evt.key() {
|
||||
Key::Backspace => {
|
||||
if !val.len() != 0 {
|
||||
val.make_mut().pop();
|
||||
if !val().is_empty() {
|
||||
val.write().pop();
|
||||
}
|
||||
}
|
||||
Key::Character(character) => match character.as_str() {
|
||||
"+" => input_operator("+"),
|
||||
"-" => input_operator("-"),
|
||||
"/" => input_operator("/"),
|
||||
"*" => input_operator("*"),
|
||||
"0" => input_digit(0),
|
||||
"1" => input_digit(1),
|
||||
"2" => input_digit(2),
|
||||
"3" => input_digit(3),
|
||||
"4" => input_digit(4),
|
||||
"5" => input_digit(5),
|
||||
"6" => input_digit(6),
|
||||
"7" => input_digit(7),
|
||||
"8" => input_digit(8),
|
||||
"9" => input_digit(9),
|
||||
"+" | "-" | "/" | "*" => input_operator(&character),
|
||||
"0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9" => input_digit(character),
|
||||
_ => {}
|
||||
},
|
||||
_ => {}
|
||||
};
|
||||
|
||||
cx.render(rsx!(
|
||||
rsx! {
|
||||
style { {include_str!("./assets/calculator.css")} }
|
||||
div { id: "wrapper",
|
||||
div { class: "app",
|
||||
div { class: "calculator",
|
||||
tabindex: "0",
|
||||
onkeydown: handle_key_down_event,
|
||||
div { class: "calculator-display", "{val}" }
|
||||
div { class: "calculator", tabindex: "0", onkeydown: handle_key_down_event,
|
||||
div { class: "calculator-display",
|
||||
if val().is_empty() {
|
||||
"0"
|
||||
} else {
|
||||
"{val}"
|
||||
}
|
||||
}
|
||||
div { class: "calculator-keypad",
|
||||
div { class: "input-keys",
|
||||
div { class: "function-keys",
|
||||
|
@ -72,55 +65,62 @@ fn app(cx: Scope) -> Element {
|
|||
class: "calculator-key key-clear",
|
||||
onclick: move |_| {
|
||||
val.set(String::new());
|
||||
if !val.is_empty(){
|
||||
if !val.cloned().is_empty() {
|
||||
val.set("0".into());
|
||||
}
|
||||
},
|
||||
if val.is_empty() { "C" } else { "AC" }
|
||||
if val.cloned().is_empty() { "C" } else { "AC" }
|
||||
}
|
||||
button {
|
||||
class: "calculator-key key-sign",
|
||||
onclick: move |_| {
|
||||
let temp = calc_val(val.as_str());
|
||||
if temp > 0.0 {
|
||||
val.set(format!("-{temp}"));
|
||||
let new_val = calc_val(val.cloned().as_str());
|
||||
if new_val > 0.0 {
|
||||
val.set(format!("-{new_val}"));
|
||||
} else {
|
||||
val.set(format!("{}", temp.abs()));
|
||||
val.set(format!("{}", new_val.abs()));
|
||||
}
|
||||
},
|
||||
"±"
|
||||
}
|
||||
button {
|
||||
class: "calculator-key key-percent",
|
||||
onclick: move |_| {
|
||||
val.set(
|
||||
format!("{}", calc_val(val.as_str()) / 100.0)
|
||||
);
|
||||
},
|
||||
onclick: move |_| val.set(format!("{}", calc_val(val.cloned().as_str()) / 100.0)),
|
||||
"%"
|
||||
}
|
||||
}
|
||||
div { class: "digit-keys",
|
||||
button { class: "calculator-key key-0", onclick: move |_| input_digit(0), "0" }
|
||||
button { class: "calculator-key key-dot", onclick: move |_| val.make_mut().push('.'), "●" }
|
||||
button {
|
||||
class: "calculator-key key-0",
|
||||
onclick: move |_| input_digit(0.to_string()),
|
||||
"0"
|
||||
}
|
||||
button {
|
||||
class: "calculator-key key-dot",
|
||||
onclick: move |_| val.write().push('.'),
|
||||
"●"
|
||||
}
|
||||
for k in 1..10 {
|
||||
button {
|
||||
class: "calculator-key {k}",
|
||||
name: "key-{k}",
|
||||
onclick: move |_| input_digit(k),
|
||||
onclick: move |_| input_digit(k.to_string()),
|
||||
"{k}"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
div { class: "operator-keys",
|
||||
button { class: "calculator-key key-divide", onclick: move |_| input_operator("/"), "÷" }
|
||||
button { class: "calculator-key key-multiply", onclick: move |_| input_operator("*"), "×" }
|
||||
button { class: "calculator-key key-subtract", onclick: move |_| input_operator("-"), "−" }
|
||||
button { class: "calculator-key key-add", onclick: move |_| input_operator("+"), "+" }
|
||||
for (key, class) in [("/", "key-divide"), ("*", "key-multiply"), ("-", "key-subtract"), ("+", "key-add")] {
|
||||
button {
|
||||
class: "calculator-key {class}",
|
||||
onclick: move |_| input_operator(key),
|
||||
"{key}"
|
||||
}
|
||||
}
|
||||
button {
|
||||
class: "calculator-key key-equals",
|
||||
onclick: move |_| val.set(format!("{}", calc_val(val.as_str()))),
|
||||
onclick: move |_| val.set(format!("{}", calc_val(val.cloned().as_str()))),
|
||||
"="
|
||||
}
|
||||
}
|
||||
|
@ -128,8 +128,7 @@ fn app(cx: Scope) -> Element {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
fn calc_val(val: &str) -> f64 {
|
||||
|
|
|
@ -17,12 +17,12 @@
|
|||
//! the RefCell will panic and crash. You can use `try_get_mut` or `.modify` to avoid this problem, or just not hold two
|
||||
//! RefMuts at the same time.
|
||||
|
||||
use dioxus::desktop::tao::dpi::LogicalSize;
|
||||
use dioxus::desktop::{Config, WindowBuilder};
|
||||
use dioxus::events::*;
|
||||
use dioxus::html::input_data::keyboard_types::Key;
|
||||
use dioxus::html::MouseEvent;
|
||||
use dioxus::prelude::*;
|
||||
use dioxus_desktop::tao::dpi::LogicalSize;
|
||||
use dioxus_desktop::{Config, WindowBuilder};
|
||||
|
||||
fn main() {
|
||||
let cfg = Config::new().with_window(
|
||||
|
@ -32,50 +32,34 @@ fn main() {
|
|||
.with_inner_size(LogicalSize::new(320.0, 530.0)),
|
||||
);
|
||||
|
||||
dioxus_desktop::launch_cfg(app, cfg);
|
||||
LaunchBuilder::desktop().with_cfg(cfg).launch(app);
|
||||
}
|
||||
|
||||
const STYLE: &str = include_str!("./assets/calculator.css");
|
||||
|
||||
fn app(cx: Scope) -> Element {
|
||||
let state = use_ref(cx, Calculator::new);
|
||||
fn app() -> Element {
|
||||
let mut state = use_signal(Calculator::new);
|
||||
|
||||
cx.render(rsx! {
|
||||
rsx! {
|
||||
style { {STYLE} }
|
||||
div { id: "wrapper",
|
||||
div { class: "app",
|
||||
div { class: "calculator", onkeypress: move |evt| state.write().handle_keydown(evt),
|
||||
div {
|
||||
class: "calculator",
|
||||
onkeypress: move |evt| state.write().handle_keydown(evt),
|
||||
div { class: "calculator-display", {state.read().formatted_display()} }
|
||||
div { class: "calculator-keypad",
|
||||
div { class: "input-keys",
|
||||
div { class: "function-keys",
|
||||
CalculatorKey {
|
||||
name: "key-clear",
|
||||
onclick: move |_| state.write().clear_display(),
|
||||
CalculatorKey { name: "key-clear", onclick: move |_| state.write().clear_display(),
|
||||
if state.read().display_value == "0" { "C" } else { "AC" }
|
||||
}
|
||||
CalculatorKey {
|
||||
name: "key-sign",
|
||||
onclick: move |_| state.write().toggle_sign(),
|
||||
"±"
|
||||
}
|
||||
CalculatorKey {
|
||||
name: "key-percent",
|
||||
onclick: move |_| state.write().toggle_percent(),
|
||||
"%"
|
||||
}
|
||||
CalculatorKey { name: "key-sign", onclick: move |_| state.write().toggle_sign(), "±" }
|
||||
CalculatorKey { name: "key-percent", onclick: move |_| state.write().toggle_percent(), "%" }
|
||||
}
|
||||
div { class: "digit-keys",
|
||||
CalculatorKey {
|
||||
name: "key-0",
|
||||
onclick: move |_| state.write().input_digit(0),
|
||||
"0"
|
||||
}
|
||||
CalculatorKey {
|
||||
name: "key-dot",
|
||||
onclick: move |_| state.write().input_dot(),
|
||||
"●"
|
||||
}
|
||||
CalculatorKey { name: "key-0", onclick: move |_| state.write().input_digit(0), "0" }
|
||||
CalculatorKey { name: "key-dot", onclick: move |_| state.write().input_dot(), "●" }
|
||||
for k in 1..10 {
|
||||
CalculatorKey {
|
||||
key: "{k}",
|
||||
|
@ -102,39 +86,21 @@ fn app(cx: Scope) -> Element {
|
|||
onclick: move |_| state.write().set_operator(Operator::Sub),
|
||||
"−"
|
||||
}
|
||||
CalculatorKey {
|
||||
name: "key-add",
|
||||
onclick: move |_| state.write().set_operator(Operator::Add),
|
||||
"+"
|
||||
}
|
||||
CalculatorKey {
|
||||
name: "key-equals",
|
||||
onclick: move |_| state.write().perform_operation(),
|
||||
"="
|
||||
}
|
||||
CalculatorKey { name: "key-add", onclick: move |_| state.write().set_operator(Operator::Add), "+" }
|
||||
CalculatorKey { name: "key-equals", onclick: move |_| state.write().perform_operation(), "=" }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Props)]
|
||||
struct CalculatorKeyProps<'a> {
|
||||
name: &'a str,
|
||||
onclick: EventHandler<'a, MouseEvent>,
|
||||
children: Element<'a>,
|
||||
}
|
||||
|
||||
fn CalculatorKey<'a>(cx: Scope<'a, CalculatorKeyProps<'a>>) -> Element {
|
||||
cx.render(rsx! {
|
||||
button {
|
||||
class: "calculator-key {cx.props.name}",
|
||||
onclick: move |e| cx.props.onclick.call(e),
|
||||
{&cx.props.children}
|
||||
}
|
||||
})
|
||||
#[component]
|
||||
fn CalculatorKey(name: String, onclick: EventHandler<MouseEvent>, children: Element) -> Element {
|
||||
rsx! {
|
||||
button { class: "calculator-key {name}", onclick: move |e| onclick.call(e), {&children} }
|
||||
}
|
||||
}
|
||||
|
||||
struct Calculator {
|
|
@ -1,22 +0,0 @@
|
|||
use dioxus::prelude::*;
|
||||
|
||||
fn main() {
|
||||
dioxus_desktop::launch(app);
|
||||
}
|
||||
|
||||
fn app(cx: Scope) -> Element {
|
||||
let login = use_callback!(cx, move |_| async move {
|
||||
let res = reqwest::get("https://dog.ceo/api/breeds/list/all")
|
||||
.await
|
||||
.unwrap()
|
||||
.text()
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
println!("{res:#?}, ");
|
||||
});
|
||||
|
||||
cx.render(rsx! {
|
||||
button { onclick: login, "Click me!" }
|
||||
})
|
||||
}
|
|
@ -1,22 +1,24 @@
|
|||
use dioxus::prelude::*;
|
||||
use dioxus_signals::use_signal;
|
||||
|
||||
fn main() {
|
||||
dioxus_desktop::launch(app);
|
||||
launch_desktop(app);
|
||||
}
|
||||
|
||||
fn app(cx: Scope) -> Element {
|
||||
let mut count = use_signal(cx, || 0);
|
||||
fn app() -> Element {
|
||||
let mut count = use_signal(|| 0);
|
||||
|
||||
use_future!(cx, || async move {
|
||||
use_future(move || async move {
|
||||
loop {
|
||||
tokio::time::sleep(std::time::Duration::from_millis(10)).await;
|
||||
count += 1;
|
||||
println!("current: {count}");
|
||||
}
|
||||
});
|
||||
|
||||
cx.render(rsx! {
|
||||
use_effect(move || {
|
||||
println!("High-Five counter: {}", count());
|
||||
});
|
||||
|
||||
rsx! {
|
||||
div { "High-Five counter: {count}" }
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,68 +1,62 @@
|
|||
//! This example shows how to create a popup window and send data back to the parent window.
|
||||
|
||||
use std::rc::Rc;
|
||||
|
||||
use dioxus::prelude::*;
|
||||
use futures_util::StreamExt;
|
||||
|
||||
fn main() {
|
||||
dioxus_desktop::launch(app);
|
||||
launch_desktop(app);
|
||||
}
|
||||
|
||||
fn app(cx: Scope) -> Element {
|
||||
let emails_sent = use_ref(cx, Vec::new);
|
||||
fn app() -> Element {
|
||||
let mut emails_sent = use_signal(|| Vec::new() as Vec<String>);
|
||||
|
||||
let tx = use_coroutine(cx, |mut rx: UnboundedReceiver<String>| {
|
||||
to_owned![emails_sent];
|
||||
async move {
|
||||
while let Some(message) = rx.next().await {
|
||||
emails_sent.write().push(message);
|
||||
}
|
||||
// Wait for responses to the compose channel, and then push them to the emails_sent signal.
|
||||
let handle = use_coroutine(|mut rx: UnboundedReceiver<String>| async move {
|
||||
while let Some(message) = rx.next().await {
|
||||
emails_sent.write().push(message);
|
||||
}
|
||||
});
|
||||
|
||||
cx.render(rsx! {
|
||||
div {
|
||||
h1 { "This is your email" }
|
||||
let open_compose_window = move |_evt: MouseEvent| {
|
||||
let tx = handle.tx();
|
||||
dioxus::desktop::window().new_window(
|
||||
VirtualDom::new_with_props(compose, Rc::new(move |s| tx.unbounded_send(s).unwrap())),
|
||||
Default::default(),
|
||||
);
|
||||
};
|
||||
|
||||
button {
|
||||
onclick: move |_| {
|
||||
let dom = VirtualDom::new_with_props(compose, ComposeProps { app_tx: tx.clone() });
|
||||
dioxus_desktop::window().new_window(dom, Default::default());
|
||||
},
|
||||
"Click to compose a new email"
|
||||
}
|
||||
|
||||
ul {
|
||||
for message in emails_sent.read().iter() {
|
||||
li {
|
||||
h3 { "email" }
|
||||
span {"{message}"}
|
||||
}
|
||||
rsx! {
|
||||
h1 { "This is your email" }
|
||||
button { onclick: open_compose_window, "Click to compose a new email" }
|
||||
ul {
|
||||
for message in emails_sent.read().iter() {
|
||||
li {
|
||||
h3 { "email" }
|
||||
span { "{message}" }
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
struct ComposeProps {
|
||||
app_tx: Coroutine<String>,
|
||||
}
|
||||
fn compose(send: Rc<dyn Fn(String)>) -> Element {
|
||||
let mut user_input = use_signal(String::new);
|
||||
|
||||
fn compose(cx: Scope<ComposeProps>) -> Element {
|
||||
let user_input = use_state(cx, String::new);
|
||||
|
||||
cx.render(rsx! {
|
||||
rsx! {
|
||||
div {
|
||||
h1 { "Compose a new email" }
|
||||
|
||||
button {
|
||||
onclick: move |_| {
|
||||
cx.props.app_tx.send(user_input.get().clone());
|
||||
dioxus_desktop::window().close();
|
||||
send(user_input.cloned());
|
||||
dioxus::desktop::window().close();
|
||||
},
|
||||
"Click to send"
|
||||
}
|
||||
|
||||
input { oninput: move |e| user_input.set(e.value()), value: "{user_input}" }
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,36 +3,41 @@ use std::rc::Rc;
|
|||
use dioxus::prelude::*;
|
||||
|
||||
fn main() {
|
||||
dioxus_desktop::launch(app);
|
||||
launch_desktop(app);
|
||||
}
|
||||
|
||||
fn app(cx: Scope) -> Element {
|
||||
let elements: &UseRef<Vec<Rc<MountedData>>> = use_ref(cx, Vec::new);
|
||||
let running = use_state(cx, || true);
|
||||
fn app() -> Element {
|
||||
let mut elements = use_signal(Vec::<Rc<MountedData>>::new);
|
||||
let mut running = use_signal(|| true);
|
||||
|
||||
use_future!(cx, |(elements, running)| async move {
|
||||
use_future(move || async move {
|
||||
let mut focused = 0;
|
||||
if *running.current() {
|
||||
loop {
|
||||
tokio::time::sleep(std::time::Duration::from_millis(10)).await;
|
||||
if let Some(element) = elements.with(|f| f.get(focused).cloned()) {
|
||||
_ = element.set_focus(true).await;
|
||||
} else {
|
||||
focused = 0;
|
||||
}
|
||||
focused += 1;
|
||||
|
||||
loop {
|
||||
tokio::time::sleep(std::time::Duration::from_millis(10)).await;
|
||||
|
||||
if !running() {
|
||||
continue;
|
||||
}
|
||||
|
||||
if let Some(element) = elements.with(|f| f.get(focused).cloned()) {
|
||||
_ = element.set_focus(true).await;
|
||||
} else {
|
||||
focused = 0;
|
||||
}
|
||||
|
||||
focused += 1;
|
||||
}
|
||||
});
|
||||
|
||||
cx.render(rsx!(
|
||||
rsx! {
|
||||
div {
|
||||
h1 { "Input Roulette" }
|
||||
for i in 0..100 {
|
||||
input {
|
||||
value: "{i}",
|
||||
onmounted: move |cx| {
|
||||
elements.write().push(cx.inner().clone());
|
||||
elements.write().push(cx.data());
|
||||
},
|
||||
oninput: move |_| {
|
||||
running.set(false);
|
||||
|
@ -40,5 +45,5 @@ fn app(cx: Scope) -> Element {
|
|||
}
|
||||
}
|
||||
}
|
||||
))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,32 +4,49 @@
|
|||
use dioxus::prelude::*;
|
||||
|
||||
fn main() {
|
||||
dioxus_desktop::launch(app);
|
||||
launch(app);
|
||||
}
|
||||
|
||||
fn app(cx: Scope) -> Element {
|
||||
let counters = use_state(cx, || vec![0, 0, 0]);
|
||||
let sum: usize = counters.iter().copied().sum();
|
||||
fn app() -> Element {
|
||||
let mut counters = use_signal(|| vec![0, 0, 0]);
|
||||
let sum = use_memo(move || counters.read().iter().copied().sum::<i32>());
|
||||
|
||||
render! {
|
||||
rsx! {
|
||||
div {
|
||||
button { onclick: move |_| counters.make_mut().push(0), "Add counter" }
|
||||
button { onclick: move |_| { counters.make_mut().pop(); }, "Remove counter" }
|
||||
button { onclick: move |_| counters.write().push(0), "Add counter" }
|
||||
button {
|
||||
onclick: move |_| {
|
||||
counters.write().pop();
|
||||
},
|
||||
"Remove counter"
|
||||
}
|
||||
p { "Total: {sum}" }
|
||||
for (i, counter) in counters.iter().enumerate() {
|
||||
li {
|
||||
button { onclick: move |_| counters.make_mut()[i] -= 1, "-1" }
|
||||
input {
|
||||
value: "{counter}",
|
||||
oninput: move |e| {
|
||||
if let Ok(value) = e.value().parse::<usize>() {
|
||||
counters.make_mut()[i] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
button { onclick: move |_| counters.make_mut()[i] += 1, "+1" }
|
||||
button { onclick: move |_| { counters.make_mut().remove(i); }, "x" }
|
||||
}
|
||||
for i in 0..counters.len() {
|
||||
Child { i, counters }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[component]
|
||||
fn Child(counters: Signal<Vec<i32>>, i: usize) -> Element {
|
||||
rsx! {
|
||||
li {
|
||||
button { onclick: move |_| counters.write()[i] -= 1, "-1" }
|
||||
input {
|
||||
value: "{counters.read()[i]}",
|
||||
oninput: move |e| {
|
||||
if let Ok(value) = e.value().parse::<i32>() {
|
||||
counters.write()[i] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
button { onclick: move |_| counters.write()[i] += 1, "+1" }
|
||||
button {
|
||||
onclick: move |_| {
|
||||
counters.write().remove(i);
|
||||
},
|
||||
"x"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
168
examples/crm.rs
168
examples/crm.rs
|
@ -1,112 +1,100 @@
|
|||
//! Tiny CRM: A port of the Yew CRM example to Dioxus.
|
||||
use dioxus::prelude::*;
|
||||
use dioxus_router::prelude::*;
|
||||
|
||||
fn main() {
|
||||
dioxus_desktop::launch(App);
|
||||
LaunchBuilder::new()
|
||||
.with_cfg(desktop!({
|
||||
use dioxus::desktop::{LogicalSize, WindowBuilder};
|
||||
dioxus::desktop::Config::default()
|
||||
.with_window(WindowBuilder::new().with_inner_size(LogicalSize::new(800, 600)))
|
||||
}))
|
||||
.launch(|| {
|
||||
rsx! {
|
||||
link {
|
||||
rel: "stylesheet",
|
||||
href: "https://unpkg.com/purecss@2.0.6/build/pure-min.css",
|
||||
integrity: "sha384-Uu6IeWbM+gzNVXJcM9XV3SohHtmWE+3VGi496jvgX1jyvDTXfdK+rfZc8C1Aehk5",
|
||||
crossorigin: "anonymous"
|
||||
}
|
||||
style { {r#" .red { background-color: rgb(202, 60, 60) !important; } "#} }
|
||||
h1 { "Dioxus CRM Example" }
|
||||
Router::<Route> {}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// We only have one list of clients for the whole app, so we can use a global signal.
|
||||
static CLIENTS: GlobalSignal<Vec<Client>> = Signal::global(Vec::new);
|
||||
|
||||
struct Client {
|
||||
first_name: String,
|
||||
last_name: String,
|
||||
description: String,
|
||||
}
|
||||
|
||||
#[derive(Routable, Clone)]
|
||||
#[rustfmt::skip]
|
||||
enum Route {
|
||||
#[route("/")]
|
||||
ClientList {},
|
||||
ClientList,
|
||||
|
||||
#[route("/new")]
|
||||
ClientAdd {},
|
||||
ClientAdd,
|
||||
|
||||
#[route("/settings")]
|
||||
Settings {},
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct Client {
|
||||
pub first_name: String,
|
||||
pub last_name: String,
|
||||
pub description: String,
|
||||
}
|
||||
|
||||
type ClientContext = Vec<Client>;
|
||||
|
||||
#[component]
|
||||
fn App(cx: Scope) -> Element {
|
||||
use_shared_state_provider::<ClientContext>(cx, Default::default);
|
||||
|
||||
render! {
|
||||
link {
|
||||
rel: "stylesheet",
|
||||
href: "https://unpkg.com/purecss@2.0.6/build/pure-min.css",
|
||||
integrity: "sha384-Uu6IeWbM+gzNVXJcM9XV3SohHtmWE+3VGi496jvgX1jyvDTXfdK+rfZc8C1Aehk5",
|
||||
crossorigin: "anonymous"
|
||||
}
|
||||
|
||||
style {
|
||||
"
|
||||
.red {{
|
||||
background-color: rgb(202, 60, 60) !important;
|
||||
}}
|
||||
"
|
||||
}
|
||||
|
||||
h1 { "Dioxus CRM Example" }
|
||||
|
||||
Router::<Route> {}
|
||||
}
|
||||
Settings,
|
||||
}
|
||||
|
||||
#[component]
|
||||
fn ClientList(cx: Scope) -> Element {
|
||||
let clients = use_shared_state::<ClientContext>(cx).unwrap();
|
||||
|
||||
cx.render(rsx! {
|
||||
fn ClientList() -> Element {
|
||||
rsx! {
|
||||
h2 { "List of Clients" }
|
||||
|
||||
Link { to: Route::ClientAdd {}, class: "pure-button pure-button-primary", "Add Client" }
|
||||
Link { to: Route::Settings {}, class: "pure-button", "Settings" }
|
||||
|
||||
for client in clients.read().iter() {
|
||||
div {
|
||||
class: "client",
|
||||
style: "margin-bottom: 50px",
|
||||
|
||||
Link { to: Route::ClientAdd, class: "pure-button pure-button-primary", "Add Client" }
|
||||
Link { to: Route::Settings, class: "pure-button", "Settings" }
|
||||
for client in CLIENTS.read().iter() {
|
||||
div { class: "client", style: "margin-bottom: 50px",
|
||||
p { "Name: {client.first_name} {client.last_name}" }
|
||||
p { "Description: {client.description}" }
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[component]
|
||||
fn ClientAdd(cx: Scope) -> Element {
|
||||
let clients = use_shared_state::<ClientContext>(cx).unwrap();
|
||||
let first_name = use_state(cx, String::new);
|
||||
let last_name = use_state(cx, String::new);
|
||||
let description = use_state(cx, String::new);
|
||||
fn ClientAdd() -> Element {
|
||||
let mut first_name = use_signal(String::new);
|
||||
let mut last_name = use_signal(String::new);
|
||||
let mut description = use_signal(String::new);
|
||||
|
||||
cx.render(rsx! {
|
||||
let submit_client = move |_: FormEvent| {
|
||||
// Write the client
|
||||
CLIENTS.write().push(Client {
|
||||
first_name: first_name(),
|
||||
last_name: last_name(),
|
||||
description: description(),
|
||||
});
|
||||
|
||||
// And then navigate back to the client list
|
||||
dioxus::router::router().push(Route::ClientList);
|
||||
};
|
||||
|
||||
rsx! {
|
||||
h2 { "Add new Client" }
|
||||
|
||||
form {
|
||||
class: "pure-form pure-form-aligned",
|
||||
onsubmit: move |_| {
|
||||
let mut clients = clients.write();
|
||||
clients
|
||||
.push(Client {
|
||||
first_name: first_name.to_string(),
|
||||
last_name: last_name.to_string(),
|
||||
description: description.to_string(),
|
||||
});
|
||||
dioxus_router::router().push(Route::ClientList {});
|
||||
},
|
||||
|
||||
form { class: "pure-form pure-form-aligned", onsubmit: submit_client,
|
||||
fieldset {
|
||||
div { class: "pure-control-group",
|
||||
label { "for": "first_name", "First Name" }
|
||||
label { r#for: "first_name", "First Name" }
|
||||
input {
|
||||
id: "first_name",
|
||||
"type": "text",
|
||||
r#type: "text",
|
||||
placeholder: "First Name…",
|
||||
required: "",
|
||||
value: "{first_name}",
|
||||
oninput: move |e| first_name.set(e.value())
|
||||
oninput: move |e| first_name.set(e.value()),
|
||||
|
||||
// when the form mounts, focus the first name input
|
||||
onmounted: move |e| async move {
|
||||
_ = e.set_focus(true).await;
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -114,7 +102,7 @@ fn ClientAdd(cx: Scope) -> Element {
|
|||
label { "for": "last_name", "Last Name" }
|
||||
input {
|
||||
id: "last_name",
|
||||
"type": "text",
|
||||
r#type: "text",
|
||||
placeholder: "Last Name…",
|
||||
required: "",
|
||||
value: "{last_name}",
|
||||
|
@ -133,30 +121,26 @@ fn ClientAdd(cx: Scope) -> Element {
|
|||
}
|
||||
|
||||
div { class: "pure-controls",
|
||||
button { "type": "submit", class: "pure-button pure-button-primary", "Save" }
|
||||
Link { to: Route::ClientList {}, class: "pure-button pure-button-primary red", "Cancel" }
|
||||
button { r#type: "submit", class: "pure-button pure-button-primary", "Save" }
|
||||
Link { to: Route::ClientList, class: "pure-button pure-button-primary red", "Cancel" }
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[component]
|
||||
fn Settings(cx: Scope) -> Element {
|
||||
let clients = use_shared_state::<ClientContext>(cx).unwrap();
|
||||
|
||||
cx.render(rsx! {
|
||||
fn Settings() -> Element {
|
||||
rsx! {
|
||||
h2 { "Settings" }
|
||||
|
||||
button {
|
||||
class: "pure-button pure-button-primary red",
|
||||
onclick: move |_| {
|
||||
let mut clients = clients.write();
|
||||
clients.clear();
|
||||
CLIENTS.write().clear();
|
||||
dioxus::router::router().push(Route::ClientList);
|
||||
},
|
||||
"Remove all Clients"
|
||||
}
|
||||
|
||||
Link { to: Route::ClientList {}, class: "pure-button", "Go back" }
|
||||
})
|
||||
Link { to: Route::ClientList, class: "pure-button", "Go back" }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,16 +1,21 @@
|
|||
use dioxus::prelude::*;
|
||||
|
||||
#[cfg(not(feature = "collect-assets"))]
|
||||
static ASSET_PATH: &str = "examples/assets/logo.png";
|
||||
|
||||
#[cfg(feature = "collect-assets")]
|
||||
static ASSET_PATH: &str =
|
||||
manganis::mg!(image("examples/assets/logo.png").format(ImageType::Avif)).path();
|
||||
|
||||
fn main() {
|
||||
dioxus_desktop::launch(app);
|
||||
launch(app);
|
||||
}
|
||||
|
||||
fn app(cx: Scope) -> Element {
|
||||
cx.render(rsx! {
|
||||
fn app() -> Element {
|
||||
rsx! {
|
||||
div {
|
||||
p {
|
||||
"This should show an image:"
|
||||
}
|
||||
img { src: manganis::mg!(image("examples/assets/logo.png").format(ImageType::Avif)).to_string() }
|
||||
p { "This should show an image:" }
|
||||
img { src: ASSET_PATH.to_string() }
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,19 +1,20 @@
|
|||
//! This example shows how to use a custom index.html and custom <HEAD> extensions
|
||||
//! to add things like stylesheets, scripts, and third-party JS libraries.
|
||||
|
||||
use dioxus::desktop::Config;
|
||||
use dioxus::prelude::*;
|
||||
use dioxus_desktop::Config;
|
||||
|
||||
fn main() {
|
||||
dioxus_desktop::launch_cfg(
|
||||
app,
|
||||
Config::new().with_custom_head("<style>body { background-color: red; }</style>".into()),
|
||||
);
|
||||
LaunchBuilder::desktop()
|
||||
.with_cfg(
|
||||
Config::new().with_custom_head("<style>body { background-color: red; }</style>".into()),
|
||||
)
|
||||
.launch(app);
|
||||
|
||||
dioxus_desktop::launch_cfg(
|
||||
app,
|
||||
Config::new().with_custom_index(
|
||||
r#"
|
||||
LaunchBuilder::desktop()
|
||||
.with_cfg(
|
||||
Config::new().with_custom_index(
|
||||
r#"
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
|
@ -26,15 +27,14 @@ fn main() {
|
|||
</body>
|
||||
</html>
|
||||
"#
|
||||
.into(),
|
||||
),
|
||||
);
|
||||
.into(),
|
||||
),
|
||||
)
|
||||
.launch(app);
|
||||
}
|
||||
|
||||
fn app(cx: Scope) -> Element {
|
||||
cx.render(rsx! {
|
||||
div {
|
||||
h1 {"hello world!"}
|
||||
}
|
||||
})
|
||||
fn app() -> Element {
|
||||
rsx! {
|
||||
div { h1 { "hello world!" } }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,25 +1,21 @@
|
|||
use dioxus::prelude::*;
|
||||
|
||||
fn main() {
|
||||
dioxus_desktop::launch(app);
|
||||
launch(app);
|
||||
}
|
||||
|
||||
fn app(cx: Scope) -> Element {
|
||||
let disabled = use_state(cx, || false);
|
||||
fn app() -> Element {
|
||||
let mut disabled = use_signal(|| false);
|
||||
|
||||
cx.render(rsx! {
|
||||
rsx! {
|
||||
div {
|
||||
button {
|
||||
onclick: move |_| disabled.set(!disabled),
|
||||
button { onclick: move |_| disabled.toggle(),
|
||||
"click to "
|
||||
if disabled == true { "enable" } else { "disable" }
|
||||
if disabled() { "enable" } else { "disable" }
|
||||
" the lower button"
|
||||
}
|
||||
|
||||
button {
|
||||
disabled: "{disabled}",
|
||||
"lower button"
|
||||
}
|
||||
button { disabled, "lower button" }
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,57 +2,49 @@ use dioxus::prelude::*;
|
|||
use std::collections::HashMap;
|
||||
|
||||
fn main() {
|
||||
dioxus_desktop::launch(|cx| render!(AppRoot {}));
|
||||
launch(app);
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, serde::Deserialize)]
|
||||
struct ListBreeds {
|
||||
message: HashMap<String, Vec<String>>,
|
||||
}
|
||||
|
||||
#[component]
|
||||
fn AppRoot(cx: Scope<'_>) -> Element {
|
||||
let breed = use_state(cx, || "deerhound".to_string());
|
||||
|
||||
let breeds = use_future!(cx, || async move {
|
||||
reqwest::get("https://dog.ceo/api/breeds/list/all")
|
||||
fn app() -> Element {
|
||||
let mut breed = use_signal(|| "deerhound".to_string());
|
||||
let breed_list = use_resource(move || async move {
|
||||
let list = reqwest::get("https://dog.ceo/api/breeds/list/all")
|
||||
.await
|
||||
.unwrap()
|
||||
.json::<ListBreeds>()
|
||||
.await
|
||||
});
|
||||
.await;
|
||||
|
||||
match breeds.value()? {
|
||||
Ok(breed_list) => cx.render(rsx! {
|
||||
div { height: "500px",
|
||||
h1 { "Select a dog breed!" }
|
||||
div { display: "flex",
|
||||
ul { flex: "50%",
|
||||
for cur_breed in breed_list.message.keys().take(10) {
|
||||
li { key: "{cur_breed}",
|
||||
button {
|
||||
onclick: move |_| breed.set(cur_breed.clone()),
|
||||
"{cur_breed}"
|
||||
}
|
||||
}
|
||||
}
|
||||
let Ok(breeds) = list else {
|
||||
return rsx! { "error fetching breeds" };
|
||||
};
|
||||
|
||||
rsx! {
|
||||
for cur_breed in breeds.message.keys().take(10).cloned() {
|
||||
li { key: "{cur_breed}",
|
||||
button { onclick: move |_| breed.set(cur_breed.clone()),
|
||||
"{cur_breed}"
|
||||
}
|
||||
div { flex: "50%", BreedPic { breed: breed.to_string() } }
|
||||
}
|
||||
}
|
||||
}),
|
||||
Err(_e) => cx.render(rsx! { div { "Error fetching breeds" } }),
|
||||
}
|
||||
});
|
||||
|
||||
let Some(breed_list) = breed_list() else {
|
||||
return rsx! { "loading breeds..." };
|
||||
};
|
||||
|
||||
rsx! {
|
||||
h1 { "Select a dog breed!" }
|
||||
div { height: "500px", display: "flex",
|
||||
ul { flex: "50%", {breed_list} }
|
||||
div { flex: "50%", BreedPic { breed } }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(serde::Deserialize, Debug)]
|
||||
struct DogApi {
|
||||
message: String,
|
||||
}
|
||||
|
||||
#[component]
|
||||
fn BreedPic(cx: Scope, breed: String) -> Element {
|
||||
let fut = use_future!(cx, |breed| async move {
|
||||
fn BreedPic(breed: Signal<String>) -> Element {
|
||||
let mut fut = use_resource(move || async move {
|
||||
reqwest::get(format!("https://dog.ceo/api/breed/{breed}/images/random"))
|
||||
.await
|
||||
.unwrap()
|
||||
|
@ -60,23 +52,22 @@ fn BreedPic(cx: Scope, breed: String) -> Element {
|
|||
.await
|
||||
});
|
||||
|
||||
match fut.value()? {
|
||||
Ok(resp) => render! {
|
||||
div {
|
||||
button {
|
||||
onclick: move |_| {
|
||||
println!("clicked");
|
||||
fut.restart()
|
||||
},
|
||||
"Click to fetch another doggo"
|
||||
}
|
||||
img {
|
||||
src: "{resp.message}",
|
||||
max_width: "500px",
|
||||
max_height: "500px",
|
||||
}
|
||||
}
|
||||
match fut.read().as_ref() {
|
||||
Some(Ok(resp)) => rsx! {
|
||||
button { onclick: move |_| fut.restart(), "Click to fetch another doggo" }
|
||||
img { max_width: "500px", max_height: "500px", src: "{resp.message}" }
|
||||
},
|
||||
Err(_) => render! { div { "loading dogs failed" } },
|
||||
Some(Err(_)) => rsx! { "loading image failed" },
|
||||
None => rsx! { "loading image..." },
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, serde::Deserialize)]
|
||||
struct ListBreeds {
|
||||
message: HashMap<String, Vec<String>>,
|
||||
}
|
||||
|
||||
#[derive(serde::Deserialize, Debug)]
|
||||
struct DogApi {
|
||||
message: String,
|
||||
}
|
||||
|
|
|
@ -1,36 +0,0 @@
|
|||
use dioxus::prelude::*;
|
||||
|
||||
fn main() {
|
||||
dioxus_desktop::launch(app);
|
||||
}
|
||||
|
||||
fn app(cx: Scope) -> Element {
|
||||
let count = if cx.generation() % 2 == 0 { 10 } else { 0 };
|
||||
|
||||
println!("Generation: {}", cx.generation());
|
||||
|
||||
if cx.generation() < 10 {
|
||||
cx.needs_update();
|
||||
}
|
||||
|
||||
render! {
|
||||
for _ in 0..count {
|
||||
drop_child {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn drop_child(cx: Scope) -> Element {
|
||||
cx.use_hook(|| Drops);
|
||||
render! {
|
||||
div{}
|
||||
}
|
||||
}
|
||||
|
||||
struct Drops;
|
||||
|
||||
impl Drop for Drops {
|
||||
fn drop(&mut self) {
|
||||
println!("Dropped!");
|
||||
}
|
||||
}
|
|
@ -1,23 +1,21 @@
|
|||
use dioxus::desktop::{use_asset_handler, wry::http::Response};
|
||||
use dioxus::prelude::*;
|
||||
use dioxus_desktop::{use_asset_handler, wry::http::Response};
|
||||
|
||||
fn main() {
|
||||
dioxus_desktop::launch(app);
|
||||
launch_desktop(app);
|
||||
}
|
||||
|
||||
fn app(cx: Scope) -> Element {
|
||||
use_asset_handler(cx, "logos", |request, response| {
|
||||
// Note that the "logos" prefix is stripped from the URI
|
||||
//
|
||||
// However, the asset is absolute to its "virtual folder" - meaning it starts with a leading slash
|
||||
if request.uri().path() != "/logo.png" {
|
||||
fn app() -> Element {
|
||||
use_asset_handler("logos", |request, response| {
|
||||
// We get the original path - make sure you handle that!
|
||||
if request.uri().path() != "/logos/logo.png" {
|
||||
return;
|
||||
}
|
||||
|
||||
response.respond(Response::new(include_bytes!("./assets/logo.png").to_vec()));
|
||||
});
|
||||
|
||||
render! {
|
||||
rsx! {
|
||||
div {
|
||||
img { src: "/logos/logo.png" }
|
||||
}
|
||||
|
|
|
@ -1,28 +1,25 @@
|
|||
use dioxus::{core::CapturedError, prelude::*};
|
||||
use dioxus::{dioxus_core::CapturedError, prelude::*};
|
||||
|
||||
fn main() {
|
||||
dioxus_desktop::launch(App);
|
||||
launch_desktop(app);
|
||||
}
|
||||
|
||||
#[component]
|
||||
fn App(cx: Scope) -> Element {
|
||||
cx.render(rsx! {
|
||||
fn app() -> Element {
|
||||
rsx! {
|
||||
ErrorBoundary {
|
||||
handle_error: |error: CapturedError| rsx! {"Found error {error}"},
|
||||
DemoC {
|
||||
x: 1
|
||||
}
|
||||
DemoC { x: 1 }
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[component]
|
||||
fn DemoC(cx: Scope, x: i32) -> Element {
|
||||
fn DemoC(x: i32) -> Element {
|
||||
let result = Err("Error");
|
||||
|
||||
result.throw()?;
|
||||
|
||||
render! {
|
||||
rsx! {
|
||||
h1 { "{x}" }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
use dioxus::prelude::*;
|
||||
|
||||
fn main() {
|
||||
dioxus_desktop::launch(app);
|
||||
launch(app);
|
||||
}
|
||||
|
||||
fn app(cx: Scope) -> Element {
|
||||
let future = use_future(cx, (), |_| async move {
|
||||
let eval = eval(
|
||||
fn app() -> Element {
|
||||
let future = use_resource(move || async move {
|
||||
let mut eval = eval(
|
||||
r#"
|
||||
dioxus.send("Hi from JS!");
|
||||
let msg = await dioxus.recv();
|
||||
|
@ -22,12 +22,8 @@ fn app(cx: Scope) -> Element {
|
|||
res
|
||||
});
|
||||
|
||||
match future.value() {
|
||||
Some(v) => cx.render(rsx!(
|
||||
p { "{v}" }
|
||||
)),
|
||||
_ => cx.render(rsx!(
|
||||
p { "hello" }
|
||||
)),
|
||||
match future.value().as_ref() {
|
||||
Some(v) => rsx!( p { "{v}" } ),
|
||||
_ => rsx!( p { "waiting.." } ),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,57 +0,0 @@
|
|||
#![allow(non_snake_case)]
|
||||
|
||||
use dioxus::prelude::*;
|
||||
use fermi::*;
|
||||
|
||||
fn main() {
|
||||
dioxus_desktop::launch(app)
|
||||
}
|
||||
|
||||
static NAME: Atom<String> = Atom(|_| "world".to_string());
|
||||
|
||||
fn app(cx: Scope) -> Element {
|
||||
use_init_atom_root(cx);
|
||||
let name = use_read(cx, &NAME);
|
||||
|
||||
cx.render(rsx! {
|
||||
div { "hello {name}!" }
|
||||
Child {}
|
||||
ChildWithRef {}
|
||||
})
|
||||
}
|
||||
|
||||
fn Child(cx: Scope) -> Element {
|
||||
let set_name = use_set(cx, &NAME);
|
||||
|
||||
cx.render(rsx! {
|
||||
button {
|
||||
onclick: move |_| set_name("dioxus".to_string()),
|
||||
"reset name"
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
static NAMES: AtomRef<Vec<String>> = AtomRef(|_| vec!["world".to_string()]);
|
||||
|
||||
fn ChildWithRef(cx: Scope) -> Element {
|
||||
let names = use_atom_ref(cx, &NAMES);
|
||||
|
||||
cx.render(rsx! {
|
||||
div {
|
||||
ul {
|
||||
for name in names.read().iter() {
|
||||
li { "hello: {name}" }
|
||||
}
|
||||
}
|
||||
button {
|
||||
onclick: move |_| {
|
||||
let names = names.clone();
|
||||
cx.spawn(async move {
|
||||
names.write().push("asd".to_string());
|
||||
})
|
||||
},
|
||||
"Add name"
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
|
@ -8,30 +8,34 @@
|
|||
//! It also uses `use_ref` to maintain a model, rather than `use_state`. That way,
|
||||
//! we dont need to clutter our code with `read` commands.
|
||||
|
||||
use dioxus::desktop::{Config, WindowBuilder};
|
||||
use dioxus::prelude::*;
|
||||
use dioxus_desktop::{Config, WindowBuilder};
|
||||
|
||||
fn main() {
|
||||
dioxus_desktop::launch_cfg(
|
||||
app,
|
||||
Config::new().with_window(WindowBuilder::new().with_resizable(true)),
|
||||
);
|
||||
LaunchBuilder::desktop()
|
||||
.with_cfg(Config::new().with_window(WindowBuilder::new().with_resizable(true)))
|
||||
.launch(app)
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "collect-assets"))]
|
||||
const _STYLE: &str = include_str!("../examples/assets/fileexplorer.css");
|
||||
|
||||
#[cfg(feature = "collect-assets")]
|
||||
const _STYLE: &str = manganis::mg!(file("./examples/assets/fileexplorer.css"));
|
||||
|
||||
fn app(cx: Scope) -> Element {
|
||||
let files = use_ref(cx, Files::new);
|
||||
fn app() -> Element {
|
||||
let mut files = use_signal(Files::new);
|
||||
|
||||
cx.render(rsx! {
|
||||
rsx! {
|
||||
div {
|
||||
link { href:"https://fonts.googleapis.com/icon?family=Material+Icons", rel:"stylesheet", }
|
||||
link { href:"https://fonts.googleapis.com/icon?family=Material+Icons", rel:"stylesheet" }
|
||||
header {
|
||||
i { class: "material-icons icon-menu", "menu" }
|
||||
h1 { "Files: ", {files.read().current()} }
|
||||
span { }
|
||||
i { class: "material-icons", onclick: move |_| files.write().go_up(), "logout" }
|
||||
}
|
||||
style { "{_STYLE}" }
|
||||
main {
|
||||
{files.read().path_names.iter().enumerate().map(|(dir_id, path)| {
|
||||
let path_end = path.split('/').last().unwrap_or(path.as_str());
|
||||
|
@ -60,7 +64,7 @@ fn app(cx: Scope) -> Element {
|
|||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
struct Files {
|
||||
|
|
|
@ -4,21 +4,38 @@ use dioxus::prelude::*;
|
|||
use tokio::time::sleep;
|
||||
|
||||
fn main() {
|
||||
dioxus_desktop::launch(App);
|
||||
launch(App);
|
||||
}
|
||||
|
||||
fn App(cx: Scope) -> Element {
|
||||
let enable_directory_upload = use_state(cx, || false);
|
||||
let files_uploaded: &UseRef<Vec<String>> = use_ref(cx, Vec::new);
|
||||
fn App() -> Element {
|
||||
let mut enable_directory_upload = use_signal(|| false);
|
||||
let mut files_uploaded = use_signal(|| Vec::new() as Vec<String>);
|
||||
|
||||
cx.render(rsx! {
|
||||
let upload_files = move |evt: FormEvent| async move {
|
||||
for file_name in evt.files().unwrap().files() {
|
||||
// no files on form inputs?
|
||||
sleep(std::time::Duration::from_secs(1)).await;
|
||||
files_uploaded.write().push(file_name);
|
||||
}
|
||||
};
|
||||
|
||||
let handle_file_drop = move |evt: DragEvent| async move {
|
||||
if let Some(file_engine) = &evt.files() {
|
||||
let files = file_engine.files();
|
||||
for file_name in &files {
|
||||
if let Some(file) = file_engine.read_file_to_string(file_name).await {
|
||||
files_uploaded.write().push(file);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
rsx! {
|
||||
label {
|
||||
input {
|
||||
r#type: "checkbox",
|
||||
checked: "{enable_directory_upload}",
|
||||
oninput: move |evt| {
|
||||
enable_directory_upload.set(evt.value().parse().unwrap());
|
||||
},
|
||||
checked: enable_directory_upload,
|
||||
oninput: move |evt| enable_directory_upload.set(evt.checked()),
|
||||
},
|
||||
"Enable directory upload"
|
||||
}
|
||||
|
@ -27,41 +44,16 @@ fn App(cx: Scope) -> Element {
|
|||
r#type: "file",
|
||||
accept: ".txt,.rs",
|
||||
multiple: true,
|
||||
directory: **enable_directory_upload,
|
||||
onchange: |evt| {
|
||||
to_owned![files_uploaded];
|
||||
async move {
|
||||
if let Some(file_engine) = &evt.files() {
|
||||
let files = file_engine.files();
|
||||
for file_name in files {
|
||||
sleep(std::time::Duration::from_secs(1)).await;
|
||||
files_uploaded.write().push(file_name);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
directory: enable_directory_upload,
|
||||
onchange: upload_files,
|
||||
}
|
||||
div {
|
||||
width: "100px",
|
||||
height: "100px",
|
||||
border: "1px solid black",
|
||||
prevent_default: "ondrop dragover dragenter",
|
||||
ondrop: move |evt| {
|
||||
to_owned![files_uploaded];
|
||||
async move {
|
||||
if let Some(file_engine) = &evt.files() {
|
||||
let files = file_engine.files();
|
||||
for file_name in &files {
|
||||
if let Some(file) = file_engine.read_file_to_string(file_name).await{
|
||||
files_uploaded.write().push(file);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
ondragover: move |event: DragEvent| {
|
||||
event.stop_propagation();
|
||||
},
|
||||
ondrop: handle_file_drop,
|
||||
ondragover: move |event| event.stop_propagation(),
|
||||
"Drop files here"
|
||||
}
|
||||
|
||||
|
@ -70,5 +62,5 @@ fn App(cx: Scope) -> Element {
|
|||
li { "{file}" }
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,19 +1,17 @@
|
|||
use dioxus::desktop::Config;
|
||||
use dioxus::prelude::*;
|
||||
use dioxus_desktop::Config;
|
||||
|
||||
fn main() {
|
||||
let cfg = Config::new().with_file_drop_handler(|_w, e| {
|
||||
println!("{e:?}");
|
||||
true
|
||||
});
|
||||
|
||||
dioxus_desktop::launch_with_props(app, (), cfg);
|
||||
LaunchBuilder::desktop()
|
||||
.with_cfg(Config::new().with_file_drop_handler(|_w, e| {
|
||||
println!("{e:?}");
|
||||
true
|
||||
}))
|
||||
.launch(app)
|
||||
}
|
||||
|
||||
fn app(cx: Scope) -> Element {
|
||||
cx.render(rsx!(
|
||||
div {
|
||||
h1 { "drag a file here and check your console" }
|
||||
}
|
||||
))
|
||||
fn app() -> Element {
|
||||
rsx!(
|
||||
div { h1 { "drag a file here and check your console" } }
|
||||
)
|
||||
}
|
||||
|
|
|
@ -1,24 +1,11 @@
|
|||
use dioxus::prelude::*;
|
||||
use dioxus_desktop::{tao::dpi::LogicalSize, Config, WindowBuilder};
|
||||
use dioxus_router::prelude::*;
|
||||
|
||||
fn main() {
|
||||
env_logger::init();
|
||||
|
||||
let cfg = Config::new().with_window(
|
||||
WindowBuilder::new()
|
||||
.with_inner_size(LogicalSize::new(600, 1000))
|
||||
.with_resizable(false),
|
||||
);
|
||||
|
||||
dioxus_desktop::launch_cfg(App, cfg)
|
||||
}
|
||||
|
||||
#[component]
|
||||
fn App(cx: Scope) -> Element {
|
||||
render! {
|
||||
Router::<Route> {}
|
||||
}
|
||||
launch(|| {
|
||||
rsx! {
|
||||
Router::<Route> {}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
#[derive(Routable, Clone)]
|
||||
|
@ -27,52 +14,59 @@ enum Route {
|
|||
#[layout(Footer)]
|
||||
#[route("/")]
|
||||
Home {},
|
||||
|
||||
#[route("/games")]
|
||||
Games {},
|
||||
|
||||
#[route("/play")]
|
||||
Play {},
|
||||
|
||||
#[route("/settings")]
|
||||
Settings {},
|
||||
}
|
||||
|
||||
#[component]
|
||||
fn Footer(cx: Scope) -> Element {
|
||||
render! {
|
||||
div {
|
||||
Outlet::<Route> { }
|
||||
|
||||
p {
|
||||
"----"
|
||||
}
|
||||
|
||||
nav {
|
||||
ul {
|
||||
li { Link { to: Route::Home {}, "Home" } }
|
||||
li { Link { to: Route::Games {}, "Games" } }
|
||||
li { Link { to: Route::Play {}, "Play" } }
|
||||
li { Link { to: Route::Settings {}, "Settings" } }
|
||||
}
|
||||
}
|
||||
fn Footer() -> Element {
|
||||
rsx! {
|
||||
Outlet::<Route> {}
|
||||
p { "----" }
|
||||
nav {
|
||||
style { {STYLE} }
|
||||
Link { to: Route::Home {}, class: "nav-btn", "Home" }
|
||||
Link { to: Route::Games {}, class: "nav-btn", "Games" }
|
||||
Link { to: Route::Play {}, class: "nav-btn", "Play" }
|
||||
Link { to: Route::Settings {}, class: "nav-btn", "Settings" }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[component]
|
||||
fn Home(cx: Scope) -> Element {
|
||||
render!("Home")
|
||||
fn Home() -> Element {
|
||||
rsx!("Home")
|
||||
}
|
||||
|
||||
#[component]
|
||||
fn Games(cx: Scope) -> Element {
|
||||
render!("Games")
|
||||
fn Games() -> Element {
|
||||
rsx!("Games")
|
||||
}
|
||||
|
||||
#[component]
|
||||
fn Play(cx: Scope) -> Element {
|
||||
render!("Play")
|
||||
fn Play() -> Element {
|
||||
rsx!("Play")
|
||||
}
|
||||
|
||||
#[component]
|
||||
fn Settings(cx: Scope) -> Element {
|
||||
render!("Settings")
|
||||
fn Settings() -> Element {
|
||||
rsx!("Settings")
|
||||
}
|
||||
|
||||
const STYLE: &str = r#"
|
||||
nav {
|
||||
display: flex;
|
||||
justify-content: space-around;
|
||||
}
|
||||
.nav-btn {
|
||||
text-decoration: none;
|
||||
color: black;
|
||||
}
|
||||
"#;
|
||||
|
|
|
@ -6,11 +6,11 @@
|
|||
use dioxus::prelude::*;
|
||||
|
||||
fn main() {
|
||||
dioxus_desktop::launch(app);
|
||||
launch_desktop(app);
|
||||
}
|
||||
|
||||
fn app(cx: Scope) -> Element {
|
||||
cx.render(rsx! {
|
||||
fn app() -> Element {
|
||||
rsx! {
|
||||
div {
|
||||
h1 { "Form" }
|
||||
form {
|
||||
|
@ -24,5 +24,5 @@ fn app(cx: Scope) -> Element {
|
|||
button { r#type: "submit", value: "Submit", "Submit the form" }
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,152 +0,0 @@
|
|||
#![allow(non_snake_case)]
|
||||
|
||||
use dioxus::prelude::*;
|
||||
use rand::prelude::*;
|
||||
|
||||
fn main() {
|
||||
dioxus_desktop::launch(app);
|
||||
}
|
||||
|
||||
#[derive(Clone, PartialEq)]
|
||||
struct Label {
|
||||
key: usize,
|
||||
labels: [&'static str; 3],
|
||||
}
|
||||
|
||||
impl Label {
|
||||
fn new_list(num: usize) -> Vec<Self> {
|
||||
let mut rng = SmallRng::from_entropy();
|
||||
let mut labels = Vec::with_capacity(num);
|
||||
for x in 0..num {
|
||||
labels.push(Label {
|
||||
key: x,
|
||||
labels: [
|
||||
ADJECTIVES.choose(&mut rng).unwrap(),
|
||||
COLOURS.choose(&mut rng).unwrap(),
|
||||
NOUNS.choose(&mut rng).unwrap(),
|
||||
],
|
||||
});
|
||||
}
|
||||
labels
|
||||
}
|
||||
}
|
||||
|
||||
fn app(cx: Scope) -> Element {
|
||||
let items = use_ref(cx, Vec::new);
|
||||
let selected = use_state(cx, || None);
|
||||
|
||||
cx.render(rsx! {
|
||||
div { class: "container",
|
||||
div { class: "jumbotron",
|
||||
div { class: "row",
|
||||
div { class: "col-md-6", h1 { "Dioxus" } }
|
||||
div { class: "col-md-6",
|
||||
div { class: "row",
|
||||
ActionButton { name: "Create 1,000 rows", id: "run",
|
||||
onclick: move |_| items.set(Label::new_list(1_000)),
|
||||
}
|
||||
ActionButton { name: "Create 10,000 rows", id: "runlots",
|
||||
onclick: move |_| items.set(Label::new_list(10_000)),
|
||||
}
|
||||
ActionButton { name: "Append 1,000 rows", id: "add",
|
||||
onclick: move |_| items.write().extend(Label::new_list(1_000)),
|
||||
}
|
||||
ActionButton { name: "Update every 10th row", id: "update",
|
||||
onclick: move |_| items.write().iter_mut().step_by(10).for_each(|item| item.labels[2] = "!!!"),
|
||||
}
|
||||
ActionButton { name: "Clear", id: "clear",
|
||||
onclick: move |_| items.write().clear(),
|
||||
}
|
||||
ActionButton { name: "Swap rows", id: "swaprows",
|
||||
onclick: move |_| items.write().swap(0, 998),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
table {
|
||||
tbody {
|
||||
for (id, item) in items.read().iter().enumerate() {
|
||||
tr {
|
||||
class: if (*selected).map(|s| s == id).unwrap_or(false) { "danger" },
|
||||
td { class:"col-md-1" }
|
||||
td { class:"col-md-1", "{item.key}" }
|
||||
td { class:"col-md-1", onclick: move |_| selected.set(Some(id)),
|
||||
a { class: "lbl", "{item.labels[0]}{item.labels[1]}{item.labels[2]}" }
|
||||
}
|
||||
td { class: "col-md-1",
|
||||
a { class: "remove", onclick: move |_| { items.write().remove(id); },
|
||||
span { class: "glyphicon glyphicon-remove remove", aria_hidden: "true" }
|
||||
}
|
||||
}
|
||||
td { class: "col-md-6" }
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
span { class: "preloadicon glyphicon glyphicon-remove", aria_hidden: "true" }
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
#[derive(Props)]
|
||||
struct ActionButtonProps<'a> {
|
||||
name: &'a str,
|
||||
id: &'a str,
|
||||
onclick: EventHandler<'a>,
|
||||
}
|
||||
|
||||
fn ActionButton<'a>(cx: Scope<'a, ActionButtonProps<'a>>) -> Element {
|
||||
cx.render(rsx! {
|
||||
div {
|
||||
class: "col-sm-6 smallpad",
|
||||
button {
|
||||
class:"btn btn-primary btn-block",
|
||||
r#type: "button",
|
||||
id: "{cx.props.id}",
|
||||
onclick: move |_| cx.props.onclick.call(()),
|
||||
|
||||
"{cx.props.name}"
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
static ADJECTIVES: &[&str] = &[
|
||||
"pretty",
|
||||
"large",
|
||||
"big",
|
||||
"small",
|
||||
"tall",
|
||||
"short",
|
||||
"long",
|
||||
"handsome",
|
||||
"plain",
|
||||
"quaint",
|
||||
"clean",
|
||||
"elegant",
|
||||
"easy",
|
||||
"angry",
|
||||
"crazy",
|
||||
"helpful",
|
||||
"mushy",
|
||||
"odd",
|
||||
"unsightly",
|
||||
"adorable",
|
||||
"important",
|
||||
"inexpensive",
|
||||
"cheap",
|
||||
"expensive",
|
||||
"fancy",
|
||||
];
|
||||
|
||||
static COLOURS: &[&str] = &[
|
||||
"red", "yellow", "blue", "green", "pink", "brown", "purple", "brown", "white", "black",
|
||||
"orange",
|
||||
];
|
||||
|
||||
static NOUNS: &[&str] = &[
|
||||
"table", "chair", "house", "bbq", "desk", "car", "pony", "cookie", "sandwich", "burger",
|
||||
"pizza", "mouse", "keyboard",
|
||||
];
|
|
@ -3,22 +3,22 @@ use std::fmt::Display;
|
|||
use dioxus::prelude::*;
|
||||
|
||||
fn main() {
|
||||
dioxus_desktop::launch(app);
|
||||
launch_desktop(app);
|
||||
}
|
||||
|
||||
fn app(cx: Scope) -> Element {
|
||||
render! {
|
||||
fn app() -> Element {
|
||||
rsx! {
|
||||
generic_child { data: 0 }
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Props)]
|
||||
struct GenericChildProps<T: Display + PartialEq> {
|
||||
#[derive(PartialEq, Props, Clone)]
|
||||
struct GenericChildProps<T: Display + PartialEq + Clone + 'static> {
|
||||
data: T,
|
||||
}
|
||||
|
||||
fn generic_child<T: Display + PartialEq>(cx: Scope<GenericChildProps<T>>) -> Element {
|
||||
render! {
|
||||
div { "{&cx.props.data}" }
|
||||
fn generic_child<T: Display + PartialEq + Clone>(props: GenericChildProps<T>) -> Element {
|
||||
rsx! {
|
||||
div { "{props.data}" }
|
||||
}
|
||||
}
|
||||
|
|
20
examples/global.rs
Normal file
20
examples/global.rs
Normal file
|
@ -0,0 +1,20 @@
|
|||
//! Example: README.md showcase
|
||||
//!
|
||||
//! The example from the README.md.
|
||||
|
||||
use dioxus::prelude::*;
|
||||
|
||||
fn main() {
|
||||
launch(app);
|
||||
}
|
||||
|
||||
static COUNT: GlobalSignal<i32> = Signal::global(|| 0);
|
||||
static DOUBLED_COUNT: GlobalMemo<i32> = Signal::global_memo(|| COUNT() * 2);
|
||||
|
||||
fn app() -> Element {
|
||||
rsx! {
|
||||
h1 { "{COUNT} x 2 = {DOUBLED_COUNT}" }
|
||||
button { onclick: move |_| *COUNT.write() += 1, "Up high!" }
|
||||
button { onclick: move |_| *COUNT.write() -= 1, "Down low!" }
|
||||
}
|
||||
}
|
|
@ -1,28 +0,0 @@
|
|||
//! This example shows that you can place heavy work on the main thread, and then
|
||||
//!
|
||||
//! You *should* be using `tokio::spawn_blocking` instead.
|
||||
//!
|
||||
//! Your app runs in an async runtime (Tokio), so you should avoid blocking
|
||||
//! the rendering of the VirtualDom.
|
||||
//!
|
||||
//!
|
||||
|
||||
use dioxus::prelude::*;
|
||||
|
||||
fn main() {
|
||||
dioxus_desktop::launch(app);
|
||||
}
|
||||
|
||||
fn app(cx: Scope) -> Element {
|
||||
// This is discouraged
|
||||
std::thread::sleep(std::time::Duration::from_millis(2_000));
|
||||
|
||||
// This is suggested
|
||||
tokio::task::spawn_blocking(move || {
|
||||
std::thread::sleep(std::time::Duration::from_millis(2_000));
|
||||
});
|
||||
|
||||
cx.render(rsx! {
|
||||
div { "Hello, world!" }
|
||||
})
|
||||
}
|
|
@ -1,11 +1,11 @@
|
|||
use dioxus::prelude::*;
|
||||
|
||||
fn main() {
|
||||
dioxus_desktop::launch(app);
|
||||
launch(app);
|
||||
}
|
||||
|
||||
fn app(cx: Scope) -> Element {
|
||||
render! {
|
||||
fn app() -> Element {
|
||||
rsx! {
|
||||
div { "Hello, world!" }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,27 +9,28 @@
|
|||
//! In this example, we pre-render the page to HTML and then pass it into the desktop configuration. This serves as a
|
||||
//! proof-of-concept for the hydration feature, but you'll probably only want to use hydration for the web.
|
||||
|
||||
use dioxus::desktop::Config;
|
||||
use dioxus::prelude::*;
|
||||
use dioxus_desktop::Config;
|
||||
|
||||
fn main() {
|
||||
let mut vdom = VirtualDom::new(app);
|
||||
let _ = vdom.rebuild();
|
||||
let content = dioxus_ssr::pre_render(&vdom);
|
||||
LaunchBuilder::desktop()
|
||||
.with_cfg(Config::new().with_prerendered({
|
||||
// We build the dom a first time, then pre-render it to HTML
|
||||
let pre_rendered_dom = VirtualDom::prebuilt(app);
|
||||
|
||||
dioxus_desktop::launch_cfg(app, Config::new().with_prerendered(content));
|
||||
// We then launch the app with the pre-rendered HTML
|
||||
dioxus_ssr::pre_render(&pre_rendered_dom)
|
||||
}))
|
||||
.launch(app)
|
||||
}
|
||||
|
||||
fn app(cx: Scope) -> Element {
|
||||
let val = use_state(cx, || 0);
|
||||
fn app() -> Element {
|
||||
let mut val = use_signal(|| 0);
|
||||
|
||||
cx.render(rsx! {
|
||||
rsx! {
|
||||
div {
|
||||
h1 { "hello world. Count: {val}" }
|
||||
button {
|
||||
onclick: move |_| *val.make_mut() += 1,
|
||||
"click to increment"
|
||||
}
|
||||
button { onclick: move |_| val += 1, "click to increment" }
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,43 +0,0 @@
|
|||
//! Run with `cargo-expand` to see what each one expands to.
|
||||
//! This file is named `inlineprops.rs`, because there used to be a `#[inline_props]` macro to
|
||||
//! do this. However, it's now deprecated (and will likely be removed in a future major version),
|
||||
//! so please use `#[component]` instead!
|
||||
use dioxus::prelude::*;
|
||||
|
||||
#[component]
|
||||
fn Thing1<T>(cx: Scope, _a: T) -> Element {
|
||||
cx.render(rsx! { "" })
|
||||
}
|
||||
|
||||
#[component]
|
||||
fn Thing2(cx: Scope, _a: u32) -> Element<'a> {
|
||||
cx.render(rsx! { "" })
|
||||
}
|
||||
|
||||
#[component]
|
||||
fn Thing3<'a, T>(cx: Scope<'a>, _a: &'a T) -> Element<'a> {
|
||||
cx.render(rsx! { "" })
|
||||
}
|
||||
|
||||
#[component]
|
||||
fn Thing4<'a>(cx: Scope<'a>, _a: &'a u32) -> Element<'a> {
|
||||
cx.render(rsx! { "" })
|
||||
}
|
||||
|
||||
fn main() {
|
||||
dioxus_desktop::launch(App);
|
||||
}
|
||||
|
||||
#[component]
|
||||
fn App(cx: Scope) -> Element {
|
||||
let state = use_state(cx, || 1);
|
||||
|
||||
cx.render(rsx! {
|
||||
div {
|
||||
Thing1 { _a: 1 },
|
||||
Thing2 { _a: 1 },
|
||||
Thing3 { _a: state },
|
||||
Thing4 { _a: state },
|
||||
}
|
||||
})
|
||||
}
|
|
@ -5,7 +5,7 @@
|
|||
use dioxus::prelude::*;
|
||||
|
||||
fn main() {
|
||||
dioxus_desktop::launch(app);
|
||||
launch_desktop(app);
|
||||
}
|
||||
|
||||
const FIELDS: &[(&str, &str)] = &[
|
||||
|
@ -34,23 +34,21 @@ const FIELDS: &[(&str, &str)] = &[
|
|||
("week", ""), // degrades to text most of the time
|
||||
];
|
||||
|
||||
fn app(cx: Scope) -> Element {
|
||||
cx.render(rsx! {
|
||||
fn app() -> Element {
|
||||
rsx! {
|
||||
div { margin_left: "30px",
|
||||
{select_example(cx)},
|
||||
{select_example()},
|
||||
div {
|
||||
// handling inputs on divs will catch all input events below
|
||||
// so the value of our input event will be either huey, dewey, louie, or true/false (because of the checkboxe)
|
||||
// be mindful in grouping inputs together, as they will all be handled by the same event handler
|
||||
oninput: move |evt| {
|
||||
println!("{evt:?}");
|
||||
},
|
||||
oninput: move |evt| println!("{evt:?}"),
|
||||
div {
|
||||
input {
|
||||
id: "huey",
|
||||
r#type: "radio",
|
||||
value: "huey",
|
||||
checked: "",
|
||||
checked: true,
|
||||
name: "drone",
|
||||
}
|
||||
label {
|
||||
|
@ -65,10 +63,7 @@ fn app(cx: Scope) -> Element {
|
|||
value: "dewey",
|
||||
name: "drone",
|
||||
}
|
||||
label {
|
||||
r#for: "dewey",
|
||||
"Dewey"
|
||||
}
|
||||
label { r#for: "dewey", "Dewey" }
|
||||
}
|
||||
div {
|
||||
input {
|
||||
|
@ -133,36 +128,35 @@ fn app(cx: Scope) -> Element {
|
|||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn select_example(cx: Scope) -> Element {
|
||||
cx.render(rsx! {
|
||||
div {
|
||||
select {
|
||||
id: "selection",
|
||||
name: "selection",
|
||||
multiple: true,
|
||||
oninput: move |evt| {
|
||||
println!("{evt:?}");
|
||||
},
|
||||
option {
|
||||
value : "Option 1",
|
||||
label : "Option 1",
|
||||
fn select_example() -> Element {
|
||||
rsx! {
|
||||
div {
|
||||
select {
|
||||
id: "selection",
|
||||
name: "selection",
|
||||
multiple: true,
|
||||
oninput: move |evt| println!("{evt:?}"),
|
||||
option {
|
||||
value: "Option 1",
|
||||
label: "Option 1",
|
||||
}
|
||||
option {
|
||||
value: "Option 2",
|
||||
label: "Option 2",
|
||||
selected: true,
|
||||
},
|
||||
option {
|
||||
value: "Option 3",
|
||||
label: "Option 3",
|
||||
}
|
||||
}
|
||||
option {
|
||||
value : "Option 2",
|
||||
label : "Option 2",
|
||||
selected : true,
|
||||
},
|
||||
option {
|
||||
value : "Option 3",
|
||||
label : "Option 3",
|
||||
label {
|
||||
r#for: "selection",
|
||||
"select element"
|
||||
}
|
||||
}
|
||||
label {
|
||||
r#for: "selection",
|
||||
"select element"
|
||||
}
|
||||
}})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,30 +1,25 @@
|
|||
use dioxus::prelude::*;
|
||||
use dioxus_router::prelude::*;
|
||||
|
||||
fn main() {
|
||||
dioxus_desktop::launch(App);
|
||||
launch_desktop(App);
|
||||
}
|
||||
|
||||
#[component]
|
||||
fn App(cx: Scope) -> Element {
|
||||
cx.render(rsx! (
|
||||
fn App() -> Element {
|
||||
rsx! (
|
||||
div {
|
||||
p {
|
||||
a { href: "http://dioxuslabs.com/", "Default link - links outside of your app" }
|
||||
}
|
||||
p { a { href: "http://dioxuslabs.com/", "Default link - links outside of your app" } }
|
||||
p {
|
||||
a {
|
||||
href: "http://dioxuslabs.com/",
|
||||
prevent_default: "onclick",
|
||||
onclick: |_| println!("Hello Dioxus"),
|
||||
"Custom event link - links inside of your app",
|
||||
"Custom event link - links inside of your app"
|
||||
}
|
||||
}
|
||||
}
|
||||
div {
|
||||
Router::<Route> {}
|
||||
}
|
||||
))
|
||||
div { Router::<Route> {} }
|
||||
)
|
||||
}
|
||||
|
||||
#[derive(Routable, Clone)]
|
||||
|
@ -38,23 +33,27 @@ enum Route {
|
|||
}
|
||||
|
||||
#[component]
|
||||
fn Header(cx: Scope) -> Element {
|
||||
render! {
|
||||
fn Header() -> Element {
|
||||
rsx! {
|
||||
h1 { "Your app here" }
|
||||
ul {
|
||||
li { Link { to: Route::Home {}, "home" } }
|
||||
li { Link { to: Route::Settings {}, "settings" } }
|
||||
li {
|
||||
Link { to: Route::Home {}, "home" }
|
||||
}
|
||||
li {
|
||||
Link { to: Route::Settings {}, "settings" }
|
||||
}
|
||||
}
|
||||
Outlet::<Route> {}
|
||||
}
|
||||
}
|
||||
|
||||
#[component]
|
||||
fn Home(cx: Scope) -> Element {
|
||||
render!(h1 { "Home" })
|
||||
fn Home() -> Element {
|
||||
rsx!( h1 { "Home" } )
|
||||
}
|
||||
|
||||
#[component]
|
||||
fn Settings(cx: Scope) -> Element {
|
||||
render!(h1 { "Settings" })
|
||||
fn Settings() -> Element {
|
||||
rsx!( h1 { "Settings" } )
|
||||
}
|
||||
|
|
|
@ -4,10 +4,10 @@
|
|||
use dioxus::prelude::*;
|
||||
|
||||
fn main() {
|
||||
dioxus_desktop::launch(app);
|
||||
launch_desktop(app);
|
||||
}
|
||||
|
||||
fn app(cx: Scope) -> Element {
|
||||
fn app() -> Element {
|
||||
let onsubmit = move |evt: FormEvent| async move {
|
||||
let resp = reqwest::Client::new()
|
||||
.post("http://localhost:8080/login")
|
||||
|
@ -29,9 +29,9 @@ fn app(cx: Scope) -> Element {
|
|||
}
|
||||
};
|
||||
|
||||
cx.render(rsx! {
|
||||
rsx! {
|
||||
h1 { "Login" }
|
||||
form { onsubmit: onsubmit,
|
||||
form { onsubmit,
|
||||
input { r#type: "text", id: "username", name: "username" }
|
||||
label { "Username" }
|
||||
br {}
|
||||
|
@ -40,5 +40,5 @@ fn app(cx: Scope) -> Element {
|
|||
br {}
|
||||
button { "Login" }
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
44
examples/memo_chain.rs
Normal file
44
examples/memo_chain.rs
Normal file
|
@ -0,0 +1,44 @@
|
|||
use dioxus::prelude::*;
|
||||
|
||||
fn main() {
|
||||
launch_desktop(app);
|
||||
}
|
||||
|
||||
fn app() -> Element {
|
||||
let mut value = use_signal(|| 0);
|
||||
let mut depth = use_signal(|| 0_usize);
|
||||
let items = use_memo(move || (0..depth()).map(|f| f as _).collect::<Vec<isize>>());
|
||||
let state = use_memo(move || value() + 1);
|
||||
|
||||
println!("rendering app");
|
||||
|
||||
rsx! {
|
||||
button { onclick: move |_| value += 1, "Increment" }
|
||||
button { onclick: move |_| depth += 1, "Add depth" }
|
||||
button { onclick: move |_| depth -= 1, "Remove depth" }
|
||||
Child { depth, items, state }
|
||||
}
|
||||
}
|
||||
|
||||
#[component]
|
||||
fn Child(
|
||||
state: ReadOnlySignal<isize>,
|
||||
items: ReadOnlySignal<Vec<isize>>,
|
||||
depth: ReadOnlySignal<usize>,
|
||||
) -> Element {
|
||||
if depth() == 0 {
|
||||
return None;
|
||||
}
|
||||
|
||||
// These memos don't get re-computed when early returns happen
|
||||
let state = use_memo(move || state() + 1);
|
||||
let item = use_memo(move || items()[depth()]);
|
||||
let depth = use_memo(move || depth() - 1);
|
||||
|
||||
println!("rendering child: {}", depth());
|
||||
|
||||
rsx! {
|
||||
h3 { "Depth({depth})-Item({item}): {state}"}
|
||||
Child { depth, state, items }
|
||||
}
|
||||
}
|
|
@ -2,7 +2,7 @@
|
|||
name = "mobile-demo"
|
||||
version = "0.1.0"
|
||||
authors = ["Jonathan Kelley <jkelleyrtp@gmail.com>"]
|
||||
edition = "2018"
|
||||
edition = "2021"
|
||||
|
||||
[lib]
|
||||
crate-type = ["staticlib", "cdylib", "rlib"]
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use anyhow::Result;
|
||||
use dioxus::desktop::Config;
|
||||
use dioxus::prelude::*;
|
||||
use dioxus_desktop::Config;
|
||||
#[cfg(target_os = "android")]
|
||||
use wry::android_binding;
|
||||
|
||||
|
@ -49,8 +49,7 @@ pub fn main() -> Result<()> {
|
|||
|
||||
// Right now we're going through dioxus-desktop but we'd like to go through dioxus-mobile
|
||||
// That will seed the index.html with some fixes that prevent the page from scrolling/zooming etc
|
||||
dioxus_desktop::launch_cfg(
|
||||
app,
|
||||
LaunchBuilder::new().cfg(
|
||||
// Note that we have to disable the viewport goofiness of the browser.
|
||||
// Dioxus_mobile should do this for us
|
||||
Config::default().with_custom_index(include_str!("index.html").to_string()),
|
||||
|
@ -59,17 +58,22 @@ pub fn main() -> Result<()> {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
fn app(cx: Scope) -> Element {
|
||||
fn app() -> Element {
|
||||
let items = cx.use_hook(|| vec![1, 2, 3]);
|
||||
|
||||
log::debug!("Hello from the app");
|
||||
|
||||
render! {
|
||||
rsx! {
|
||||
div {
|
||||
h1 { "Hello, Mobile"}
|
||||
div { margin_left: "auto", margin_right: "auto", width: "200px", padding: "10px", border: "1px solid black",
|
||||
h1 { "Hello, Mobile" }
|
||||
div {
|
||||
margin_left: "auto",
|
||||
margin_right: "auto",
|
||||
width: "200px",
|
||||
padding: "10px",
|
||||
border: "1px solid black",
|
||||
button {
|
||||
onclick: move|_| {
|
||||
onclick: move |_| {
|
||||
println!("Clicked!");
|
||||
items.push(items.len());
|
||||
cx.needs_update_any(ScopeId::ROOT);
|
||||
|
|
|
@ -1,25 +1,22 @@
|
|||
use dioxus::prelude::*;
|
||||
|
||||
fn main() {
|
||||
dioxus_desktop::launch(app);
|
||||
launch_desktop(app);
|
||||
}
|
||||
|
||||
fn app(cx: Scope) -> Element {
|
||||
cx.render(rsx! {
|
||||
div {
|
||||
button {
|
||||
onclick: move |_| {
|
||||
let dom = VirtualDom::new(popup);
|
||||
dioxus_desktop::window().new_window(dom, Default::default());
|
||||
},
|
||||
"New Window"
|
||||
}
|
||||
}
|
||||
})
|
||||
fn app() -> Element {
|
||||
let onclick = move |_| {
|
||||
let dom = VirtualDom::new(popup);
|
||||
dioxus::desktop::window().new_window(dom, Default::default());
|
||||
};
|
||||
|
||||
rsx! {
|
||||
button { onclick, "New Window" }
|
||||
}
|
||||
}
|
||||
|
||||
fn popup(cx: Scope) -> Element {
|
||||
cx.render(rsx! {
|
||||
div { "This is a popup!" }
|
||||
})
|
||||
fn popup() -> Element {
|
||||
rsx! {
|
||||
div { "This is a popup window!" }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,11 +7,11 @@
|
|||
use dioxus::prelude::*;
|
||||
|
||||
fn main() {
|
||||
dioxus_desktop::launch(app);
|
||||
launch(app);
|
||||
}
|
||||
|
||||
fn app(cx: Scope) -> Element {
|
||||
cx.render(rsx! {
|
||||
fn app() -> Element {
|
||||
rsx! {
|
||||
div {
|
||||
onclick: move |_| println!("clicked! top"),
|
||||
"- div"
|
||||
|
@ -30,5 +30,5 @@ fn app(cx: Scope) -> Element {
|
|||
"Does not handle clicks - only propagate"
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -14,8 +14,6 @@ pub(crate) mod views;
|
|||
use oidc::{AuthRequestState, AuthTokenState};
|
||||
use router::Route;
|
||||
|
||||
use dioxus_router::prelude::*;
|
||||
|
||||
use crate::{
|
||||
constants::{DIOXUS_FRONT_AUTH_REQUEST, DIOXUS_FRONT_AUTH_TOKEN},
|
||||
oidc::ClientState,
|
||||
|
@ -30,14 +28,14 @@ pub static DIOXUS_FRONT_ISSUER_URL: &str = env!("DIOXUS_FRONT_ISSUER_URL");
|
|||
pub static DIOXUS_FRONT_CLIENT_ID: &str = env!("DIOXUS_FRONT_CLIENT_ID");
|
||||
pub static DIOXUS_FRONT_URL: &str = env!("DIOXUS_FRONT_URL");
|
||||
|
||||
fn App(cx: Scope) -> Element {
|
||||
fn App() -> Element {
|
||||
use_init_atom_root(cx);
|
||||
|
||||
// Retrieve the value stored in the browser's storage
|
||||
let stored_auth_token = LocalStorage::get(DIOXUS_FRONT_AUTH_TOKEN)
|
||||
.ok()
|
||||
.unwrap_or(AuthTokenState::default());
|
||||
let fermi_auth_token = use_atom_ref(cx, &FERMI_AUTH_TOKEN);
|
||||
let fermi_auth_token = use_atom_ref(&FERMI_AUTH_TOKEN);
|
||||
if fermi_auth_token.read().is_none() {
|
||||
*fermi_auth_token.write() = Some(stored_auth_token);
|
||||
}
|
||||
|
@ -45,11 +43,11 @@ fn App(cx: Scope) -> Element {
|
|||
let stored_auth_request = LocalStorage::get(DIOXUS_FRONT_AUTH_REQUEST)
|
||||
.ok()
|
||||
.unwrap_or(AuthRequestState::default());
|
||||
let fermi_auth_request = use_atom_ref(cx, &FERMI_AUTH_REQUEST);
|
||||
let fermi_auth_request = use_atom_ref(&FERMI_AUTH_REQUEST);
|
||||
if fermi_auth_request.read().is_none() {
|
||||
*fermi_auth_request.write() = Some(stored_auth_request);
|
||||
}
|
||||
render! { Router::<Route> {} }
|
||||
rsx! { Router::<Route> {} }
|
||||
}
|
||||
|
||||
fn main() {
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
use crate::views::{header::AuthHeader, home::Home, login::Login, not_found::NotFound};
|
||||
use dioxus::prelude::*;
|
||||
use dioxus_router::prelude::*;
|
||||
|
||||
#[derive(Routable, Clone)]
|
||||
pub enum Route {
|
||||
|
|
|
@ -9,15 +9,15 @@ use crate::{
|
|||
FERMI_AUTH_REQUEST, FERMI_AUTH_TOKEN, FERMI_CLIENT,
|
||||
};
|
||||
use dioxus::prelude::*;
|
||||
use dioxus_router::prelude::{Link, Outlet};
|
||||
use dioxus::router::prelude::{Link, Outlet};
|
||||
use fermi::*;
|
||||
use openidconnect::{url::Url, OAuth2TokenResponse, TokenResponse};
|
||||
|
||||
#[component]
|
||||
pub fn LogOut(cx: Scope<ClientProps>) -> Element {
|
||||
let fermi_auth_token = use_atom_ref(cx, &FERMI_AUTH_TOKEN);
|
||||
let fermi_auth_token = use_atom_ref(&FERMI_AUTH_TOKEN);
|
||||
let fermi_auth_token_read = fermi_auth_token.read().clone();
|
||||
let log_out_url_state = use_state(cx, || None::<Option<Result<Url, crate::errors::Error>>>);
|
||||
let log_out_url_state = use_signal(|| None::<Option<Result<Url, crate::errors::Error>>>);
|
||||
cx.render(match fermi_auth_token_read {
|
||||
Some(fermi_auth_token_read) => match fermi_auth_token_read.id_token.clone() {
|
||||
Some(id_token) => match log_out_url_state.get() {
|
||||
|
@ -40,9 +40,7 @@ pub fn LogOut(cx: Scope<ClientProps>) -> Element {
|
|||
}
|
||||
}
|
||||
Err(error) => {
|
||||
rsx! {
|
||||
div { "Failed to load disconnection url: {error:?}" }
|
||||
}
|
||||
rsx! { div { "Failed to load disconnection url: {error:?}" } }
|
||||
}
|
||||
},
|
||||
None => {
|
||||
|
@ -61,7 +59,7 @@ pub fn LogOut(cx: Scope<ClientProps>) -> Element {
|
|||
})
|
||||
};
|
||||
logout_url_task();
|
||||
rsx! { div{"Loading log out url... Please wait"}}
|
||||
rsx! { div { "Loading log out url... Please wait" } }
|
||||
}
|
||||
},
|
||||
None => {
|
||||
|
@ -76,8 +74,8 @@ pub fn LogOut(cx: Scope<ClientProps>) -> Element {
|
|||
|
||||
#[component]
|
||||
pub fn RefreshToken(cx: Scope<ClientProps>) -> Element {
|
||||
let fermi_auth_token = use_atom_ref(cx, &FERMI_AUTH_TOKEN);
|
||||
let fermi_auth_request = use_atom_ref(cx, &FERMI_AUTH_REQUEST);
|
||||
let fermi_auth_token = use_atom_ref(&FERMI_AUTH_TOKEN);
|
||||
let fermi_auth_request = use_atom_ref(&FERMI_AUTH_REQUEST);
|
||||
let fermi_auth_token_read = fermi_auth_token.read().clone();
|
||||
cx.render(match fermi_auth_token_read {
|
||||
Some(fermi_auth_client_read) => match fermi_auth_client_read.refresh_token {
|
||||
|
@ -128,9 +126,9 @@ pub fn RefreshToken(cx: Scope<ClientProps>) -> Element {
|
|||
}
|
||||
|
||||
#[component]
|
||||
pub fn LoadClient(cx: Scope) -> Element {
|
||||
let init_client_future = use_future(cx, (), |_| async move { init_oidc_client().await });
|
||||
let fermi_client: &UseAtomRef<ClientState> = use_atom_ref(cx, &FERMI_CLIENT);
|
||||
pub fn LoadClient() -> Element {
|
||||
let init_client_future = use_future(move || async move { init_oidc_client().await });
|
||||
let fermi_client: &UseAtomRef<ClientState> = use_atom_ref(&FERMI_CLIENT);
|
||||
cx.render(match init_client_future.value() {
|
||||
Some(client_props) => match client_props {
|
||||
Ok((client_id, client)) => {
|
||||
|
@ -162,10 +160,10 @@ pub fn LoadClient(cx: Scope) -> Element {
|
|||
}
|
||||
|
||||
#[component]
|
||||
pub fn AuthHeader(cx: Scope) -> Element {
|
||||
let auth_token = use_atom_ref(cx, &FERMI_AUTH_TOKEN);
|
||||
let fermi_auth_request = use_atom_ref(cx, &FERMI_AUTH_REQUEST);
|
||||
let fermi_client: &UseAtomRef<ClientState> = use_atom_ref(cx, &FERMI_CLIENT);
|
||||
pub fn AuthHeader() -> Element {
|
||||
let auth_token = use_atom_ref(&FERMI_AUTH_TOKEN);
|
||||
let fermi_auth_request = use_atom_ref(&FERMI_AUTH_REQUEST);
|
||||
let fermi_client: &UseAtomRef<ClientState> = use_atom_ref(&FERMI_CLIENT);
|
||||
let client = fermi_client.read().oidc_client.clone();
|
||||
let auth_request_read = fermi_auth_request.read().clone();
|
||||
let auth_token_read = auth_token.read().clone();
|
||||
|
@ -197,7 +195,7 @@ pub fn AuthHeader(cx: Scope) -> Element {
|
|||
log::info!("Token expired");
|
||||
rsx! {
|
||||
div {
|
||||
RefreshToken {client_id: client_props.client_id, client: client_props.client}
|
||||
RefreshToken { client_id: client_props.client_id, client: client_props.client }
|
||||
Outlet::<Route> {}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
use dioxus::prelude::*;
|
||||
|
||||
pub fn Home(cx: Scope) -> Element {
|
||||
render! { div { "Hello world" } }
|
||||
pub fn Home() -> Element {
|
||||
rsx! { div { "Hello world" } }
|
||||
}
|
||||
|
|
|
@ -5,16 +5,16 @@ use crate::{
|
|||
DIOXUS_FRONT_URL, FERMI_AUTH_REQUEST, FERMI_AUTH_TOKEN, FERMI_CLIENT,
|
||||
};
|
||||
use dioxus::prelude::*;
|
||||
use dioxus_router::prelude::{Link, NavigationTarget};
|
||||
use dioxus::router::prelude::{Link, NavigationTarget};
|
||||
use fermi::*;
|
||||
use openidconnect::{OAuth2TokenResponse, TokenResponse};
|
||||
|
||||
#[component]
|
||||
pub fn Login(cx: Scope, query_string: String) -> Element {
|
||||
let fermi_client = use_atom_ref(cx, &FERMI_CLIENT);
|
||||
let fermi_auth_token = use_atom_ref(cx, &FERMI_AUTH_TOKEN);
|
||||
pub fn Login(query_string: String) -> Element {
|
||||
let fermi_client = use_atom_ref(&FERMI_CLIENT);
|
||||
let fermi_auth_token = use_atom_ref(&FERMI_AUTH_TOKEN);
|
||||
let home_url: NavigationTarget<Route> = DIOXUS_FRONT_URL.parse().unwrap();
|
||||
let fermi_auth_request = use_atom_ref(cx, &FERMI_AUTH_REQUEST);
|
||||
let fermi_auth_request = use_atom_ref(&FERMI_AUTH_REQUEST);
|
||||
let client = fermi_client.read().oidc_client.clone();
|
||||
let auth_token_read = fermi_auth_token.read().clone();
|
||||
cx.render(match (client, auth_token_read) {
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
use dioxus::prelude::*;
|
||||
|
||||
#[component]
|
||||
pub fn NotFound(cx: Scope, route: Vec<String>) -> Element {
|
||||
render! {
|
||||
pub fn NotFound(route: Vec<String>) -> Element {
|
||||
rsx! {
|
||||
div{
|
||||
{route.join("")}
|
||||
}
|
||||
|
|
|
@ -7,11 +7,11 @@
|
|||
use dioxus::prelude::*;
|
||||
|
||||
fn main() {
|
||||
dioxus_desktop::launch(app);
|
||||
launch(app);
|
||||
}
|
||||
|
||||
fn app(cx: Scope) -> Element {
|
||||
cx.render(rsx! {
|
||||
fn app() -> Element {
|
||||
rsx! {
|
||||
Button {
|
||||
a: "asd".to_string(),
|
||||
c: "asd".to_string(),
|
||||
|
@ -30,12 +30,10 @@ fn app(cx: Scope) -> Element {
|
|||
c: "asd".to_string(),
|
||||
d: Some("asd".to_string()),
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
type SthElse<T> = Option<T>;
|
||||
|
||||
#[derive(Props, PartialEq)]
|
||||
#[derive(Props, PartialEq, Clone)]
|
||||
struct ButtonProps {
|
||||
a: String,
|
||||
|
||||
|
@ -51,14 +49,16 @@ struct ButtonProps {
|
|||
e: SthElse<String>,
|
||||
}
|
||||
|
||||
fn Button(cx: Scope<ButtonProps>) -> Element {
|
||||
cx.render(rsx! {
|
||||
type SthElse<T> = Option<T>;
|
||||
|
||||
fn Button(props: ButtonProps) -> Element {
|
||||
rsx! {
|
||||
button {
|
||||
"{cx.props.a} | "
|
||||
"{cx.props.b:?} | "
|
||||
"{cx.props.c:?} | "
|
||||
"{cx.props.d:?} | "
|
||||
"{cx.props.e:?}"
|
||||
"{props.a} | "
|
||||
"{props.b:?} | "
|
||||
"{props.c:?} | "
|
||||
"{props.d:?} | "
|
||||
"{props.e:?}"
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
use dioxus::desktop::{tao::dpi::PhysicalPosition, LogicalSize, WindowBuilder};
|
||||
use dioxus::prelude::*;
|
||||
use dioxus_desktop::{tao::dpi::PhysicalPosition, LogicalSize, WindowBuilder};
|
||||
|
||||
fn main() {
|
||||
dioxus_desktop::launch_cfg(app, make_config());
|
||||
LaunchBuilder::desktop().with_cfg(make_config()).launch(app);
|
||||
}
|
||||
|
||||
fn app(cx: Scope) -> Element {
|
||||
cx.render(rsx! {
|
||||
fn app() -> Element {
|
||||
rsx! {
|
||||
div {
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
|
@ -17,16 +17,16 @@ fn app(cx: Scope) -> Element {
|
|||
width: "100%",
|
||||
height: "10px",
|
||||
background_color: "black",
|
||||
onmousedown: move |_| dioxus_desktop::window().drag(),
|
||||
onmousedown: move |_| dioxus::desktop::window().drag(),
|
||||
}
|
||||
|
||||
"This is an overlay!"
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn make_config() -> dioxus_desktop::Config {
|
||||
dioxus_desktop::Config::default()
|
||||
fn make_config() -> dioxus::desktop::Config {
|
||||
dioxus::desktop::Config::default()
|
||||
.with_window(make_window())
|
||||
.with_custom_head(
|
||||
r#"
|
||||
|
|
|
@ -1,16 +1,12 @@
|
|||
#![allow(unused)]
|
||||
//! Example: Url query segments usage
|
||||
//! ------------------------------------
|
||||
//!
|
||||
//! This example shows how to access and use multiple query segments present in an url on the web.
|
||||
//!
|
||||
//! Run `dx serve` and navigate to `http://localhost:8080/blog?name=John&surname=Doe`
|
||||
use dioxus::prelude::*;
|
||||
use std::fmt::Display;
|
||||
|
||||
use dioxus::prelude::*;
|
||||
use dioxus_router::prelude::*;
|
||||
|
||||
// ANCHOR: route
|
||||
#[derive(Routable, Clone)]
|
||||
#[rustfmt::skip]
|
||||
enum Route {
|
||||
|
@ -20,6 +16,7 @@ enum Route {
|
|||
// You must include query segments in child variants
|
||||
query_params: ManualBlogQuerySegments,
|
||||
},
|
||||
|
||||
// segments that follow the ?:field&:other_field syntax are query segments that follow the standard url query syntax
|
||||
#[route("/autoblog?:name&:surname")]
|
||||
AutomaticBlogPost {
|
||||
|
@ -41,7 +38,7 @@ impl Display for ManualBlogQuerySegments {
|
|||
}
|
||||
}
|
||||
|
||||
/// The query segment is anything that implements <https://docs.rs/dioxus-router/latest/dioxus_router/routable/trait.FromQuery.html>. You can implement that trait for a struct if you want to parse multiple query parameters.
|
||||
/// The query segment is anything that implements <https://docs.rs/dioxus-router/latest/dioxus::router/routable/trait.FromQuery.html>. You can implement that trait for a struct if you want to parse multiple query parameters.
|
||||
impl FromQuery for ManualBlogQuerySegments {
|
||||
fn from_query(query: &str) -> Self {
|
||||
let mut name = None;
|
||||
|
@ -63,26 +60,26 @@ impl FromQuery for ManualBlogQuerySegments {
|
|||
}
|
||||
|
||||
#[component]
|
||||
fn BlogPost(cx: Scope, query_params: ManualBlogQuerySegments) -> Element {
|
||||
render! {
|
||||
div{"This is your blogpost with a query segment:"}
|
||||
div{ "{query_params:?}" }
|
||||
fn BlogPost(query_params: ManualBlogQuerySegments) -> Element {
|
||||
rsx! {
|
||||
div { "This is your blogpost with a query segment:" }
|
||||
div { "{query_params:?}" }
|
||||
}
|
||||
}
|
||||
|
||||
#[component]
|
||||
fn AutomaticBlogPost(cx: Scope, name: String, surname: String) -> Element {
|
||||
render! {
|
||||
div{"This is your blogpost with a query segment:"}
|
||||
div{ "name={name}&surname={surname}" }
|
||||
fn AutomaticBlogPost(name: String, surname: String) -> Element {
|
||||
rsx! {
|
||||
div { "This is your blogpost with a query segment:" }
|
||||
div { "name={name}&surname={surname}" }
|
||||
}
|
||||
}
|
||||
|
||||
#[component]
|
||||
fn App(cx: Scope) -> Element {
|
||||
render! { Router::<Route>{} }
|
||||
fn App() -> Element {
|
||||
rsx! { Router::<Route> {} }
|
||||
}
|
||||
|
||||
fn main() {
|
||||
dioxus_web::launch(App);
|
||||
launch(App);
|
||||
}
|
|
@ -1,13 +0,0 @@
|
|||
[package]
|
||||
name = "query_segments_demo"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
publish = false
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
dioxus = { path = "../../packages/dioxus", version = "*" }
|
||||
dioxus-router = { path = "../../packages/router", version = "*" }
|
||||
dioxus-web = { path = "../../packages/web", version = "*" }
|
||||
form_urlencoded = "1.2.0"
|
|
@ -4,10 +4,10 @@ use std::rc::Rc;
|
|||
use dioxus::{html::geometry::euclid::Rect, prelude::*};
|
||||
|
||||
fn main() {
|
||||
dioxus_desktop::launch_cfg(
|
||||
app,
|
||||
dioxus_desktop::Config::default().with_custom_head(
|
||||
r#"
|
||||
LaunchBuilder::desktop()
|
||||
.with_cfg(
|
||||
dioxus::desktop::Config::default().with_custom_head(
|
||||
r#"
|
||||
<style type="text/css">
|
||||
html, body {
|
||||
height: 100%;
|
||||
|
@ -20,41 +20,35 @@ fn main() {
|
|||
}
|
||||
</style>
|
||||
"#
|
||||
.to_owned(),
|
||||
),
|
||||
);
|
||||
.to_owned(),
|
||||
),
|
||||
)
|
||||
.launch(app);
|
||||
}
|
||||
|
||||
fn app(cx: Scope) -> Element {
|
||||
let div_element: &UseRef<Option<Rc<MountedData>>> = use_ref(cx, || None);
|
||||
fn app() -> Element {
|
||||
let mut div_element = use_signal(|| None as Option<Rc<MountedData>>);
|
||||
let mut dimensions = use_signal(Rect::zero);
|
||||
|
||||
let dimentions = use_ref(cx, Rect::zero);
|
||||
let read_dims = move |_| async move {
|
||||
let read = div_element.read();
|
||||
let client_rect = read.as_ref().map(|el| el.get_client_rect());
|
||||
if let Some(client_rect) = client_rect {
|
||||
if let Ok(rect) = client_rect.await {
|
||||
dimensions.set(rect);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
cx.render(rsx!(
|
||||
rsx!(
|
||||
div {
|
||||
width: "50%",
|
||||
height: "50%",
|
||||
background_color: "red",
|
||||
onmounted: move |cx| {
|
||||
div_element.set(Some(cx.inner().clone()));
|
||||
},
|
||||
"This element is {dimentions.read():?}"
|
||||
onmounted: move |cx| div_element.set(Some(cx.data())),
|
||||
"This element is {dimensions():?}"
|
||||
}
|
||||
|
||||
button {
|
||||
onclick: move |_| {
|
||||
to_owned![div_element, dimentions];
|
||||
async move {
|
||||
let read = div_element.read();
|
||||
let client_rect = read.as_ref().map(|el| el.get_client_rect());
|
||||
if let Some(client_rect) = client_rect {
|
||||
if let Ok(rect) = client_rect.await {
|
||||
dimentions.set(rect);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Read dimentions"
|
||||
}
|
||||
))
|
||||
button { onclick: read_dims, "Read dimensions" }
|
||||
)
|
||||
}
|
||||
|
|
|
@ -1,19 +1,15 @@
|
|||
//! Example: README.md showcase
|
||||
//!
|
||||
//! The example from the README.md.
|
||||
|
||||
use dioxus::prelude::*;
|
||||
|
||||
fn main() {
|
||||
dioxus_desktop::launch(app);
|
||||
launch(app);
|
||||
}
|
||||
|
||||
fn app(cx: Scope) -> Element {
|
||||
let mut count = use_state(cx, || 0);
|
||||
fn app() -> Element {
|
||||
let mut count = use_signal(|| 0);
|
||||
|
||||
cx.render(rsx! {
|
||||
rsx! {
|
||||
h1 { "High-Five counter: {count}" }
|
||||
button { onclick: move |_| count += 1, "Up high!" }
|
||||
button { onclick: move |_| count -= 1, "Down low!" }
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,31 +4,24 @@
|
|||
//! This example shows how to encapsulate state in dioxus components with the reducer pattern.
|
||||
//! This pattern is very useful when a single component can handle many types of input that can
|
||||
//! be represented by an enum.
|
||||
//!
|
||||
//! Currently we don't have a reducer pattern hook. If you'd like to add it,
|
||||
//! feel free to make a PR.
|
||||
|
||||
use dioxus::prelude::*;
|
||||
|
||||
fn main() {
|
||||
dioxus_desktop::launch(app);
|
||||
launch(app);
|
||||
}
|
||||
|
||||
fn app(cx: Scope) -> Element {
|
||||
let state = use_state(cx, PlayerState::new);
|
||||
fn app() -> Element {
|
||||
let mut state = use_signal(|| PlayerState { is_playing: false });
|
||||
|
||||
cx.render(rsx!(
|
||||
rsx!(
|
||||
div {
|
||||
h1 {"Select an option"}
|
||||
h3 { "The radio is... ", {state.is_playing()}, "!" }
|
||||
button { onclick: move |_| state.make_mut().reduce(PlayerAction::Pause),
|
||||
"Pause"
|
||||
}
|
||||
button { onclick: move |_| state.make_mut().reduce(PlayerAction::Play),
|
||||
"Play"
|
||||
}
|
||||
h3 { "The radio is... ", {state.read().is_playing()}, "!" }
|
||||
button { onclick: move |_| state.write().reduce(PlayerAction::Pause), "Pause" }
|
||||
button { onclick: move |_| state.write().reduce(PlayerAction::Play), "Play" }
|
||||
}
|
||||
))
|
||||
)
|
||||
}
|
||||
|
||||
enum PlayerAction {
|
||||
|
@ -42,9 +35,6 @@ struct PlayerState {
|
|||
}
|
||||
|
||||
impl PlayerState {
|
||||
fn new() -> Self {
|
||||
Self { is_playing: false }
|
||||
}
|
||||
fn reduce(&mut self, action: PlayerAction) {
|
||||
match action {
|
||||
PlayerAction::Pause => self.is_playing = false,
|
|
@ -1,15 +1,14 @@
|
|||
use dioxus::prelude::*;
|
||||
use dioxus_router::prelude::*;
|
||||
|
||||
fn main() {
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
dioxus_web::launch(App);
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
dioxus_desktop::launch(App);
|
||||
launch_desktop(|| {
|
||||
rsx! {
|
||||
Router::<Route> {}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// ANCHOR: router
|
||||
#[derive(Routable, Clone)]
|
||||
#[derive(Routable, Clone, Debug, PartialEq)]
|
||||
#[rustfmt::skip]
|
||||
enum Route {
|
||||
#[layout(NavBar)]
|
||||
|
@ -19,7 +18,7 @@ enum Route {
|
|||
#[layout(Blog)]
|
||||
#[route("/")]
|
||||
BlogList {},
|
||||
#[route("/blog/:name")]
|
||||
#[route("/:name")]
|
||||
BlogPost { name: String },
|
||||
#[end_layout]
|
||||
#[end_nest]
|
||||
|
@ -33,22 +32,18 @@ enum Route {
|
|||
route: Vec<String>,
|
||||
},
|
||||
}
|
||||
// ANCHOR_END: router
|
||||
|
||||
#[component]
|
||||
fn App(cx: Scope) -> Element {
|
||||
render! {
|
||||
Router::<Route> {}
|
||||
}
|
||||
}
|
||||
|
||||
#[component]
|
||||
fn NavBar(cx: Scope) -> Element {
|
||||
render! {
|
||||
fn NavBar() -> Element {
|
||||
rsx! {
|
||||
nav {
|
||||
ul {
|
||||
li { Link { to: Route::Home {}, "Home" } }
|
||||
li { Link { to: Route::BlogList {}, "Blog" } }
|
||||
li {
|
||||
Link { to: Route::Home {}, "Home" }
|
||||
}
|
||||
li {
|
||||
Link { to: Route::BlogList {}, "Blog" }
|
||||
}
|
||||
}
|
||||
}
|
||||
Outlet::<Route> {}
|
||||
|
@ -56,34 +51,36 @@ fn NavBar(cx: Scope) -> Element {
|
|||
}
|
||||
|
||||
#[component]
|
||||
fn Home(cx: Scope) -> Element {
|
||||
render! {
|
||||
h1 { "Welcome to the Dioxus Blog!" }
|
||||
}
|
||||
fn Home() -> Element {
|
||||
rsx! { h1 { "Welcome to the Dioxus Blog!" } }
|
||||
}
|
||||
|
||||
#[component]
|
||||
fn Blog(cx: Scope) -> Element {
|
||||
render! {
|
||||
fn Blog() -> Element {
|
||||
rsx! {
|
||||
h1 { "Blog" }
|
||||
Outlet::<Route> {}
|
||||
}
|
||||
}
|
||||
|
||||
#[component]
|
||||
fn BlogList(cx: Scope) -> Element {
|
||||
render! {
|
||||
fn BlogList() -> Element {
|
||||
rsx! {
|
||||
h2 { "Choose a post" }
|
||||
ul {
|
||||
li {
|
||||
Link {
|
||||
to: Route::BlogPost { name: "Blog post 1".into() },
|
||||
to: Route::BlogPost {
|
||||
name: "Blog post 1".into(),
|
||||
},
|
||||
"Read the first blog post"
|
||||
}
|
||||
}
|
||||
li {
|
||||
Link {
|
||||
to: Route::BlogPost { name: "Blog post 2".into() },
|
||||
to: Route::BlogPost {
|
||||
name: "Blog post 2".into(),
|
||||
},
|
||||
"Read the second blog post"
|
||||
}
|
||||
}
|
||||
|
@ -92,20 +89,15 @@ fn BlogList(cx: Scope) -> Element {
|
|||
}
|
||||
|
||||
#[component]
|
||||
fn BlogPost(cx: Scope, name: String) -> Element {
|
||||
render! {
|
||||
h2 { "Blog Post: {name}"}
|
||||
}
|
||||
fn BlogPost(name: String) -> Element {
|
||||
rsx! { h2 { "Blog Post: {name}" } }
|
||||
}
|
||||
|
||||
#[component]
|
||||
fn PageNotFound(cx: Scope, route: Vec<String>) -> Element {
|
||||
render! {
|
||||
fn PageNotFound(route: Vec<String>) -> Element {
|
||||
rsx! {
|
||||
h1 { "Page not found" }
|
||||
p { "We are terribly sorry, but the page you requested doesn't exist." }
|
||||
pre {
|
||||
color: "red",
|
||||
"log:\nattemped to navigate to: {route:?}"
|
||||
}
|
||||
pre { color: "red", "log:\nattemped to navigate to: {route:?}" }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,58 +0,0 @@
|
|||
//! This example just flexes the ability to use arbitrary expressions within RSX.
|
||||
//! It also proves that lifetimes work properly, especially when used with use_ref
|
||||
|
||||
use dioxus::prelude::*;
|
||||
|
||||
fn main() {
|
||||
let mut vdom = VirtualDom::new(example);
|
||||
_ = vdom.rebuild();
|
||||
|
||||
let mut renderer = dioxus_ssr::Renderer::new();
|
||||
renderer.pretty = true;
|
||||
renderer.render(&vdom);
|
||||
}
|
||||
|
||||
fn example(cx: Scope) -> Element {
|
||||
let items = use_state(cx, || {
|
||||
vec![Thing {
|
||||
a: "asd".to_string(),
|
||||
b: 10,
|
||||
}]
|
||||
});
|
||||
|
||||
let things = use_ref(cx, || {
|
||||
vec![Thing {
|
||||
a: "asd".to_string(),
|
||||
b: 10,
|
||||
}]
|
||||
});
|
||||
let things_list = things.read();
|
||||
|
||||
let mything = use_ref(cx, || Some(String::from("asd")));
|
||||
let mything_read = mything.read();
|
||||
|
||||
cx.render(rsx!(
|
||||
div {
|
||||
div { id: "asd",
|
||||
"your neighborhood spiderman"
|
||||
|
||||
for item in items.iter().cycle().take(5) {
|
||||
div { "{item.a}" }
|
||||
}
|
||||
|
||||
for thing in things_list.iter() {
|
||||
div { "{thing.a}" "{thing.b}" }
|
||||
}
|
||||
|
||||
if let Some(f) = mything_read.as_ref() {
|
||||
div { "{f}" }
|
||||
}
|
||||
}
|
||||
}
|
||||
))
|
||||
}
|
||||
|
||||
struct Thing {
|
||||
a: String,
|
||||
b: u32,
|
||||
}
|
|
@ -39,269 +39,263 @@
|
|||
//! - Allow top-level fragments
|
||||
|
||||
fn main() {
|
||||
dioxus_desktop::launch(App);
|
||||
}
|
||||
|
||||
use core::{fmt, str::FromStr};
|
||||
use std::fmt::Display;
|
||||
|
||||
use baller::Baller;
|
||||
use dioxus::prelude::*;
|
||||
|
||||
#[component]
|
||||
fn App(cx: Scope) -> Element {
|
||||
let formatting = "formatting!";
|
||||
let formatting_tuple = ("a", "b");
|
||||
let lazy_fmt = format_args!("lazily formatted text");
|
||||
let asd = 123;
|
||||
cx.render(rsx! {
|
||||
div {
|
||||
// Elements
|
||||
div {}
|
||||
h1 {"Some text"}
|
||||
h1 {"Some text with {formatting}"}
|
||||
h1 {"Formatting basic expressions {formatting_tuple.0} and {formatting_tuple.1}"}
|
||||
h1 {"Formatting without interpolation " {formatting_tuple.0} "and" {formatting_tuple.1} }
|
||||
h2 {
|
||||
"Multiple"
|
||||
"Text"
|
||||
"Blocks"
|
||||
"Use comments as separators in html"
|
||||
}
|
||||
div {
|
||||
h1 {"multiple"}
|
||||
h2 {"nested"}
|
||||
h3 {"elements"}
|
||||
}
|
||||
div {
|
||||
class: "my special div",
|
||||
h1 {"Headers and attributes!"}
|
||||
}
|
||||
div {
|
||||
// pass simple rust expressions in
|
||||
class: lazy_fmt,
|
||||
id: format_args!("attributes can be passed lazily with std::fmt::Arguments"),
|
||||
class: "asd",
|
||||
class: "{asd}",
|
||||
// if statements can be used to conditionally render attributes
|
||||
class: if formatting.contains("form") { "{asd}" },
|
||||
div {
|
||||
class: {
|
||||
const WORD: &str = "expressions";
|
||||
format_args!("Arguments can be passed in through curly braces for complex {WORD}")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Expressions can be used in element position too:
|
||||
{rsx!(p { "More templating!" })},
|
||||
|
||||
// Iterators
|
||||
{(0..10).map(|i| rsx!(li { "{i}" }))},
|
||||
|
||||
// Iterators within expressions
|
||||
{
|
||||
let data = std::collections::HashMap::<&'static str, &'static str>::new();
|
||||
// Iterators *should* have keys when you can provide them.
|
||||
// Keys make your app run faster. Make sure your keys are stable, unique, and predictable.
|
||||
// Using an "ID" associated with your data is a good idea.
|
||||
data.into_iter().map(|(k, v)| rsx!(li { key: "{k}", "{v}" }))
|
||||
}
|
||||
|
||||
// Matching
|
||||
match true {
|
||||
true => rsx!( h1 {"Top text"}),
|
||||
false => rsx!( h1 {"Bottom text"})
|
||||
}
|
||||
|
||||
// Conditional rendering
|
||||
// Dioxus conditional rendering is based around None/Some. We have no special syntax for conditionals.
|
||||
// You can convert a bool condition to rsx! with .then and .or
|
||||
{true.then(|| rsx!(div {}))},
|
||||
|
||||
// Alternatively, you can use the "if" syntax - but both branches must be resolve to Element
|
||||
if false {
|
||||
h1 {"Top text"}
|
||||
} else {
|
||||
h1 {"Bottom text"}
|
||||
}
|
||||
|
||||
// Using optionals for diverging branches
|
||||
// Note that since this is wrapped in curlies, it's interpreted as an expression
|
||||
{if true {
|
||||
Some(rsx!(h1 {"Top text"}))
|
||||
} else {
|
||||
None
|
||||
}}
|
||||
|
||||
// returning "None" without a diverging branch is a bit noisy... but rare in practice
|
||||
{None as Option<()>},
|
||||
|
||||
// can also just use empty fragments
|
||||
Fragment {}
|
||||
|
||||
// Fragments let you insert groups of nodes without a parent.
|
||||
// This lets you make components that insert elements as siblings without a container.
|
||||
div {"A"}
|
||||
Fragment {
|
||||
div {"B"}
|
||||
div {"C"}
|
||||
Fragment {
|
||||
"D"
|
||||
Fragment {
|
||||
"E"
|
||||
"F"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Components
|
||||
// Can accept any paths
|
||||
// Notice how you still get syntax highlighting and IDE support :)
|
||||
Baller {}
|
||||
baller::Baller {}
|
||||
crate::baller::Baller {}
|
||||
|
||||
// Can take properties
|
||||
Taller { a: "asd" }
|
||||
|
||||
// Can take optional properties
|
||||
Taller { a: "asd" }
|
||||
|
||||
// Can pass in props directly as an expression
|
||||
{
|
||||
let props = TallerProps {a: "hello", children: None };
|
||||
rsx!(Taller { ..props })
|
||||
}
|
||||
|
||||
// Spreading can also be overridden manually
|
||||
Taller {
|
||||
..TallerProps { a: "ballin!", children: None },
|
||||
a: "not ballin!"
|
||||
}
|
||||
|
||||
// Can take children too!
|
||||
Taller { a: "asd", div {"hello world!"} }
|
||||
|
||||
// This component's props are defined *inline* with the `inline_props` macro
|
||||
WithInline { text: "using functionc all syntax" }
|
||||
|
||||
// Components can be generic too
|
||||
// This component takes i32 type to give you typed input
|
||||
TypedInput::<i32> {}
|
||||
|
||||
// Type inference can be used too
|
||||
TypedInput { initial: 10.0 }
|
||||
|
||||
// geneircs with the `inline_props` macro
|
||||
Label { text: "hello geneirc world!" }
|
||||
Label { text: 99.9 }
|
||||
|
||||
// Lowercase components work too, as long as they are access using a path
|
||||
baller::lowercase_component {}
|
||||
|
||||
// For in-scope lowercase components, use the `self` keyword
|
||||
self::lowercase_helper {}
|
||||
|
||||
// helper functions
|
||||
// Anything that implements IntoVnode can be dropped directly into Rsx
|
||||
{helper(cx, "hello world!")}
|
||||
|
||||
// Strings can be supplied directly
|
||||
{String::from("Hello world!")}
|
||||
|
||||
// So can format_args
|
||||
{format_args!("Hello {}!", "world")}
|
||||
|
||||
// Or we can shell out to a helper function
|
||||
{format_dollars(10, 50)}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn format_dollars(dollars: u32, cents: u32) -> String {
|
||||
format!("${dollars}.{cents:02}")
|
||||
}
|
||||
|
||||
fn helper<'a>(cx: &'a ScopeState, text: &'a str) -> Element<'a> {
|
||||
cx.render(rsx! {
|
||||
p { "{text}" }
|
||||
})
|
||||
}
|
||||
|
||||
// no_case_check disables PascalCase checking if you *really* want a snake_case component.
|
||||
// This will likely be deprecated/removed in a future update that will introduce a more polished linting system,
|
||||
// something like Clippy.
|
||||
#[component(no_case_check)]
|
||||
fn lowercase_helper(cx: Scope) -> Element {
|
||||
cx.render(rsx! {
|
||||
"asd"
|
||||
})
|
||||
}
|
||||
|
||||
mod baller {
|
||||
use super::*;
|
||||
#[derive(Props, PartialEq, Eq)]
|
||||
pub struct BallerProps {}
|
||||
|
||||
#[component]
|
||||
/// This component totally balls
|
||||
pub fn Baller(_cx: Scope<BallerProps>) -> Element {
|
||||
todo!()
|
||||
}
|
||||
|
||||
// no_case_check disables PascalCase checking if you *really* want a snake_case component.
|
||||
// This will likely be deprecated/removed in a future update that will introduce a more polished linting system,
|
||||
// something like Clippy.
|
||||
#[component(no_case_check)]
|
||||
pub fn lowercase_component(cx: Scope) -> Element {
|
||||
cx.render(rsx! { "look ma, no uppercase" })
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Props)]
|
||||
pub struct TallerProps<'a> {
|
||||
/// Fields are documented and accessible in rsx!
|
||||
a: &'static str,
|
||||
children: Element<'a>,
|
||||
}
|
||||
|
||||
/// Documention for this component is visible within the rsx macro
|
||||
#[component]
|
||||
pub fn Taller<'a>(cx: Scope<'a, TallerProps<'a>>) -> Element {
|
||||
cx.render(rsx! {
|
||||
{&cx.props.children}
|
||||
})
|
||||
}
|
||||
|
||||
#[derive(Props, PartialEq, Eq)]
|
||||
pub struct TypedInputProps<T> {
|
||||
#[props(optional, default)]
|
||||
initial: Option<T>,
|
||||
}
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
pub fn TypedInput<T>(_: Scope<TypedInputProps<T>>) -> Element
|
||||
where
|
||||
T: FromStr + fmt::Display,
|
||||
<T as FromStr>::Err: std::fmt::Display,
|
||||
{
|
||||
todo!()
|
||||
//launch_desktop(App);
|
||||
}
|
||||
|
||||
#[component]
|
||||
fn WithInline<'a>(cx: Scope<'a>, text: &'a str) -> Element {
|
||||
cx.render(rsx! {
|
||||
p { "{text}" }
|
||||
})
|
||||
}
|
||||
// use core::{fmt, str::FromStr};
|
||||
// use std::fmt::Display;
|
||||
|
||||
#[component]
|
||||
fn Label<T>(cx: Scope, text: T) -> Element
|
||||
where
|
||||
T: Display,
|
||||
{
|
||||
cx.render(rsx! {
|
||||
p { "{text}" }
|
||||
})
|
||||
}
|
||||
// use baller::Baller;
|
||||
// use dioxus::prelude::*;
|
||||
|
||||
// #[component]
|
||||
// fn App() -> Element {
|
||||
// let formatting = "formatting!";
|
||||
// let formatting_tuple = ("a", "b");
|
||||
// let lazy_fmt = format_args!("lazily formatted text");
|
||||
// let asd = 123;
|
||||
// rsx! {
|
||||
// div {
|
||||
// // Elements
|
||||
// div {}
|
||||
// h1 {"Some text"}
|
||||
// h1 {"Some text with {formatting}"}
|
||||
// h1 {"Formatting basic expressions {formatting_tuple.0} and {formatting_tuple.1}"}
|
||||
// h1 {"Formatting without interpolation " {formatting_tuple.0} "and" {formatting_tuple.1} }
|
||||
// h2 {
|
||||
// "Multiple"
|
||||
// "Text"
|
||||
// "Blocks"
|
||||
// "Use comments as separators in html"
|
||||
// }
|
||||
// div {
|
||||
// h1 {"multiple"}
|
||||
// h2 {"nested"}
|
||||
// h3 {"elements"}
|
||||
// }
|
||||
// div {
|
||||
// class: "my special div",
|
||||
// h1 {"Headers and attributes!"}
|
||||
// }
|
||||
// div {
|
||||
// // pass simple rust expressions in
|
||||
// class: lazy_fmt,
|
||||
// id: format_args!("attributes can be passed lazily with std::fmt::Arguments"),
|
||||
// class: "asd",
|
||||
// class: "{asd}",
|
||||
// // if statements can be used to conditionally render attributes
|
||||
// class: if formatting.contains("form") { "{asd}" },
|
||||
// div {
|
||||
// class: {
|
||||
// const WORD: &str = "expressions";
|
||||
// format_args!("Arguments can be passed in through curly braces for complex {WORD}")
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
// // Expressions can be used in element position too:
|
||||
// {rsx!(p { "More templating!" })},
|
||||
|
||||
// // Iterators
|
||||
// {(0..10).map(|i| rsx!(li { "{i}" }))},
|
||||
|
||||
// // Iterators within expressions
|
||||
// {
|
||||
// let data = std::collections::HashMap::<&'static str, &'static str>::new();
|
||||
// // Iterators *should* have keys when you can provide them.
|
||||
// // Keys make your app run faster. Make sure your keys are stable, unique, and predictable.
|
||||
// // Using an "ID" associated with your data is a good idea.
|
||||
// data.into_iter().map(|(k, v)| rsx!(li { key: "{k}", "{v}" }))
|
||||
// }
|
||||
|
||||
// // Matching
|
||||
// match true {
|
||||
// true => rsx!( h1 {"Top text"}),
|
||||
// false => rsx!( h1 {"Bottom text"})
|
||||
// }
|
||||
|
||||
// // Conditional rendering
|
||||
// // Dioxus conditional rendering is based around None/Some. We have no special syntax for conditionals.
|
||||
// // You can convert a bool condition to rsx! with .then and .or
|
||||
// {true.then(|| rsx!(div {}))},
|
||||
|
||||
// // Alternatively, you can use the "if" syntax - but both branches must be resolve to Element
|
||||
// if false {
|
||||
// h1 {"Top text"}
|
||||
// } else {
|
||||
// h1 {"Bottom text"}
|
||||
// }
|
||||
|
||||
// // Using optionals for diverging branches
|
||||
// // Note that since this is wrapped in curlies, it's interpreted as an expression
|
||||
// {if true {
|
||||
// Some(rsx!(h1 {"Top text"}))
|
||||
// } else {
|
||||
// None
|
||||
// }}
|
||||
|
||||
// // returning "None" without a diverging branch is a bit noisy... but rare in practice
|
||||
// {None as Option<()>},
|
||||
|
||||
// // can also just use empty fragments
|
||||
// Fragment {}
|
||||
|
||||
// // Fragments let you insert groups of nodes without a parent.
|
||||
// // This lets you make components that insert elements as siblings without a container.
|
||||
// div {"A"}
|
||||
// Fragment {
|
||||
// div {"B"}
|
||||
// div {"C"}
|
||||
// Fragment {
|
||||
// "D"
|
||||
// Fragment {
|
||||
// "E"
|
||||
// "F"
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
// // Components
|
||||
// // Can accept any paths
|
||||
// // Notice how you still get syntax highlighting and IDE support :)
|
||||
// Baller {}
|
||||
// baller::Baller {}
|
||||
// crate::baller::Baller {}
|
||||
|
||||
// // Can take properties
|
||||
// Taller { a: "asd" }
|
||||
|
||||
// // Can take optional properties
|
||||
// Taller { a: "asd" }
|
||||
|
||||
// // Can pass in props directly as an expression
|
||||
// {
|
||||
// let props = TallerProps {a: "hello", children: None };
|
||||
// rsx!(Taller { ..props })
|
||||
// }
|
||||
|
||||
// // Spreading can also be overridden manually
|
||||
// Taller {
|
||||
// ..TallerProps { a: "ballin!", children: None },
|
||||
// a: "not ballin!"
|
||||
// }
|
||||
|
||||
// // Can take children too!
|
||||
// Taller { a: "asd", div {"hello world!"} }
|
||||
|
||||
// // This component's props are defined *inline* with the `inline_props` macro
|
||||
// WithInline { text: "using functionc all syntax" }
|
||||
|
||||
// // Components can be generic too
|
||||
// // This component takes i32 type to give you typed input
|
||||
// TypedInput::<i32> {}
|
||||
|
||||
// // Type inference can be used too
|
||||
// TypedInput { initial: 10.0 }
|
||||
|
||||
// // geneircs with the `inline_props` macro
|
||||
// Label { text: "hello geneirc world!" }
|
||||
// Label { text: 99.9 }
|
||||
|
||||
// // Lowercase components work too, as long as they are access using a path
|
||||
// baller::lowercase_component {}
|
||||
|
||||
// // For in-scope lowercase components, use the `self` keyword
|
||||
// self::lowercase_helper {}
|
||||
|
||||
// // helper functions
|
||||
// // Anything that implements IntoVnode can be dropped directly into Rsx
|
||||
// {helper("hello world!")}
|
||||
|
||||
// // Strings can be supplied directly
|
||||
// {String::from("Hello world!")}
|
||||
|
||||
// // So can format_args
|
||||
// {format_args!("Hello {}!", "world")}
|
||||
|
||||
// // Or we can shell out to a helper function
|
||||
// {format_dollars(10, 50)}
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
// fn format_dollars(dollars: u32, cents: u32) -> String {
|
||||
// format!("${dollars}.{cents:02}")
|
||||
// }
|
||||
|
||||
// fn helper<'a>(cx: &'a ScopeState, text: &'a str) -> Element {
|
||||
// rsx! {
|
||||
// p { "{text}" }
|
||||
// }
|
||||
// }
|
||||
|
||||
// // no_case_check disables PascalCase checking if you *really* want a snake_case component.
|
||||
// // This will likely be deprecated/removed in a future update that will introduce a more polished linting system,
|
||||
// // something like Clippy.
|
||||
// #[component(no_case_check)]
|
||||
// fn lowercase_helper() -> Element {
|
||||
// rsx! {
|
||||
// "asd"
|
||||
// }
|
||||
// }
|
||||
|
||||
// mod baller {
|
||||
// use super::*;
|
||||
|
||||
// #[component]
|
||||
// /// This component totally balls
|
||||
// pub fn Baller() -> Element {
|
||||
// todo!()
|
||||
// }
|
||||
|
||||
// // no_case_check disables PascalCase checking if you *really* want a snake_case component.
|
||||
// // This will likely be deprecated/removed in a future update that will introduce a more polished linting system,
|
||||
// // something like Clippy.
|
||||
// #[component(no_case_check)]
|
||||
// pub fn lowercase_component() -> Element {
|
||||
// rsx! { "look ma, no uppercase" }
|
||||
// }
|
||||
// }
|
||||
|
||||
// /// Documention for this component is visible within the rsx macro
|
||||
// #[component]
|
||||
// pub fn Taller(
|
||||
// /// Fields are documented and accessible in rsx!
|
||||
// a: &'static str,
|
||||
// children: Element,
|
||||
// ) -> Element {
|
||||
// rsx! { {&children} }
|
||||
// }
|
||||
|
||||
// #[derive(Props, PartialEq, Eq)]
|
||||
// pub struct TypedInputProps<T> {
|
||||
// #[props(optional, default)]
|
||||
// initial: Option<T>,
|
||||
// }
|
||||
|
||||
// #[allow(non_snake_case)]
|
||||
// pub fn TypedInput<T>(_: Scope<TypedInputProps<T>>) -> Element
|
||||
// where
|
||||
// T: FromStr + fmt::Display,
|
||||
// <T as FromStr>::Err: std::fmt::Display,
|
||||
// {
|
||||
// todo!()
|
||||
// }
|
||||
|
||||
// #[component]
|
||||
// fn WithInline(cx: Scope<'a>, text: &'a str) -> Element {
|
||||
// rsx! {
|
||||
// p { "{text}" }
|
||||
// }
|
||||
// }
|
||||
|
||||
// #[component]
|
||||
// fn Label<T: Clone + PartialEq>(text: T) -> Element
|
||||
// where
|
||||
// T: Display,
|
||||
// {
|
||||
// rsx! {
|
||||
// p { "{text}" }
|
||||
// }
|
||||
// }
|
||||
|
|
|
@ -1,18 +1,16 @@
|
|||
use dioxus::prelude::*;
|
||||
|
||||
fn main() {
|
||||
dioxus_desktop::launch(app);
|
||||
launch_desktop(app);
|
||||
}
|
||||
|
||||
fn app(cx: Scope) -> Element {
|
||||
let header_element = use_ref(cx, || None);
|
||||
fn app() -> Element {
|
||||
let mut header_element = use_signal(|| None);
|
||||
|
||||
cx.render(rsx!(
|
||||
rsx! {
|
||||
div {
|
||||
h1 {
|
||||
onmounted: move |cx| {
|
||||
header_element.set(Some(cx.inner().clone()));
|
||||
},
|
||||
onmounted: move |cx| header_element.set(Some(cx.data())),
|
||||
"Scroll to top example"
|
||||
}
|
||||
|
||||
|
@ -21,15 +19,13 @@ fn app(cx: Scope) -> Element {
|
|||
}
|
||||
|
||||
button {
|
||||
onclick: move |_| {
|
||||
if let Some(header) = header_element.read().as_ref().cloned() {
|
||||
cx.spawn(async move {
|
||||
let _ = header.scroll_to(ScrollBehavior::Smooth).await;
|
||||
});
|
||||
onclick: move |_| async move {
|
||||
if let Some(header) = header_element.cloned() {
|
||||
let _ = header.scroll_to(ScrollBehavior::Smooth).await;
|
||||
}
|
||||
},
|
||||
"Scroll to top"
|
||||
}
|
||||
}
|
||||
))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,73 +0,0 @@
|
|||
use std::collections::HashMap;
|
||||
|
||||
use dioxus::prelude::*;
|
||||
|
||||
fn main() {
|
||||
dioxus_desktop::launch(App);
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct CoolData {
|
||||
data: HashMap<usize, String>,
|
||||
}
|
||||
|
||||
impl CoolData {
|
||||
pub fn new(data: HashMap<usize, String>) -> Self {
|
||||
Self { data }
|
||||
}
|
||||
|
||||
pub fn view(&self, id: &usize) -> Option<&String> {
|
||||
self.data.get(id)
|
||||
}
|
||||
|
||||
pub fn set(&mut self, id: usize, data: String) {
|
||||
self.data.insert(id, data);
|
||||
}
|
||||
}
|
||||
|
||||
#[component]
|
||||
#[rustfmt::skip]
|
||||
pub fn App(cx: Scope) -> Element {
|
||||
use_shared_state_provider(cx, || CoolData::new(HashMap::from([
|
||||
(0, "Hello, World!".to_string()),
|
||||
(1, "Dioxus is amazing!".to_string())
|
||||
])));
|
||||
|
||||
render!(
|
||||
DataEditor {
|
||||
id: 0
|
||||
}
|
||||
DataEditor {
|
||||
id: 1
|
||||
}
|
||||
DataView {
|
||||
id: 0
|
||||
}
|
||||
DataView {
|
||||
id: 1
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
#[component]
|
||||
fn DataEditor(cx: Scope, id: usize) -> Element {
|
||||
let data = use_shared_state::<CoolData>(cx)?;
|
||||
|
||||
render! {
|
||||
p {
|
||||
{data.read().view(id)?}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[component]
|
||||
fn DataView(cx: Scope, id: usize) -> Element {
|
||||
let data = use_shared_state::<CoolData>(cx)?;
|
||||
|
||||
render! {
|
||||
input {
|
||||
oninput: move |e: FormEvent| data.write().set(*id, e.value()),
|
||||
value: data.read().view(id)?
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,17 +1,14 @@
|
|||
use dioxus::desktop::use_global_shortcut;
|
||||
use dioxus::prelude::*;
|
||||
use dioxus_desktop::use_global_shortcut;
|
||||
|
||||
fn main() {
|
||||
dioxus_desktop::launch(app);
|
||||
launch_desktop(app);
|
||||
}
|
||||
|
||||
fn app(cx: Scope) -> Element {
|
||||
let toggled = use_state(cx, || false);
|
||||
fn app() -> Element {
|
||||
let mut toggled = use_signal(|| false);
|
||||
|
||||
use_global_shortcut(cx, "ctrl+s", {
|
||||
to_owned![toggled];
|
||||
move || toggled.modify(|t| !*t)
|
||||
});
|
||||
_ = use_global_shortcut("ctrl+s", move || toggled.toggle());
|
||||
|
||||
cx.render(rsx!("toggle: {toggled.get()}"))
|
||||
rsx!("toggle: {toggled}")
|
||||
}
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
use dioxus::prelude::*;
|
||||
|
||||
fn main() {
|
||||
dioxus_desktop::launch(app);
|
||||
launch_desktop(app);
|
||||
}
|
||||
|
||||
fn app(cx: Scope) -> Element {
|
||||
fn app() -> Element {
|
||||
let a = 123;
|
||||
let b = 456;
|
||||
let c = 789;
|
||||
|
@ -14,10 +14,10 @@ fn app(cx: Scope) -> Element {
|
|||
// todo: i'd like it for children on elements to be inferred as the children of the element
|
||||
// also should shorthands understand references/dereferences?
|
||||
// ie **a, *a, &a, &mut a, etc
|
||||
let children = render! { "Child" };
|
||||
let children = rsx! { "Child" };
|
||||
let onclick = move |_| println!("Clicked!");
|
||||
|
||||
render! {
|
||||
rsx! {
|
||||
div { class, id, {&children} }
|
||||
Component { a, b, c, children, onclick }
|
||||
Component { a, ..ComponentProps { a: 1, b: 2, c: 3, children: None, onclick: Default::default() } }
|
||||
|
@ -25,21 +25,12 @@ fn app(cx: Scope) -> Element {
|
|||
}
|
||||
|
||||
#[component]
|
||||
fn Component<'a>(
|
||||
cx: Scope<'a>,
|
||||
a: i32,
|
||||
b: i32,
|
||||
c: i32,
|
||||
children: Element<'a>,
|
||||
onclick: EventHandler<'a, ()>,
|
||||
) -> Element {
|
||||
render! {
|
||||
fn Component(a: i32, b: i32, c: i32, children: Element, onclick: EventHandler) -> Element {
|
||||
rsx! {
|
||||
div { "{a}" }
|
||||
div { "{b}" }
|
||||
div { "{c}" }
|
||||
div { {children} }
|
||||
div {
|
||||
onclick: move |_| onclick.call(()),
|
||||
}
|
||||
div { onclick: move |_| onclick.call(()) }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,40 +2,60 @@ use dioxus::prelude::*;
|
|||
use std::time::Duration;
|
||||
|
||||
fn main() {
|
||||
dioxus_desktop::launch(app);
|
||||
launch_desktop(app);
|
||||
}
|
||||
|
||||
fn app(cx: Scope) -> Element {
|
||||
let running = dioxus_signals::use_signal(cx, || true);
|
||||
let mut count = dioxus_signals::use_signal(cx, || 0);
|
||||
let saved_values = dioxus_signals::use_signal(cx, || vec![0.to_string()]);
|
||||
fn app() -> Element {
|
||||
let mut running = use_signal(|| true);
|
||||
let mut count = use_signal(|| 0);
|
||||
let mut saved_values = use_signal(|| vec![0.to_string()]);
|
||||
|
||||
// Signals can be used in async functions without an explicit clone since they're 'static and Copy
|
||||
// Signals are backed by a runtime that is designed to deeply integrate with Dioxus apps
|
||||
use_future!(cx, || async move {
|
||||
// use_memo will recompute the value of the signal whenever the captured signals change
|
||||
let doubled_count = use_memo(move || count() * 2);
|
||||
|
||||
// use_effect will subscribe to any changes in the signal values it captures
|
||||
// effects will always run after first mount and then whenever the signal values change
|
||||
use_effect(move || println!("Count changed to {count}"));
|
||||
|
||||
// We can do early returns and conditional rendering which will pause all futures that haven't been polled
|
||||
if count() > 30 {
|
||||
return rsx! {
|
||||
h1 { "Count is too high!" }
|
||||
button { onclick: move |_| count.set(0), "Press to reset" }
|
||||
};
|
||||
}
|
||||
|
||||
// use_future will spawn an infinitely running future that can be started and stopped
|
||||
use_future(move || async move {
|
||||
loop {
|
||||
if running.value() {
|
||||
if running() {
|
||||
count += 1;
|
||||
}
|
||||
tokio::time::sleep(Duration::from_millis(400)).await;
|
||||
}
|
||||
});
|
||||
|
||||
cx.render(rsx! {
|
||||
// use_resource will spawn a future that resolves to a value
|
||||
let _slow_count = use_resource(move || async move {
|
||||
tokio::time::sleep(Duration::from_millis(200)).await;
|
||||
count() * 2
|
||||
});
|
||||
|
||||
rsx! {
|
||||
h1 { "High-Five counter: {count}" }
|
||||
button { onclick: move |_| count += 1, "Up high!" }
|
||||
button { onclick: move |_| count -= 1, "Down low!" }
|
||||
button { onclick: move |_| running.toggle(), "Toggle counter" }
|
||||
button { onclick: move |_| saved_values.push(count.value().to_string()), "Save this value" }
|
||||
button { onclick: move |_| saved_values.write().clear(), "Clear saved values" }
|
||||
button { onclick: move |_| saved_values.push(count.to_string()), "Save this value" }
|
||||
button { onclick: move |_| saved_values.clear(), "Clear saved values" }
|
||||
|
||||
// We can do boolean operations on the current signal value
|
||||
if count.value() > 5 {
|
||||
if count() > 5 {
|
||||
h2 { "High five!" }
|
||||
}
|
||||
|
||||
// We can cleanly map signals with iterators
|
||||
for value in saved_values.read().iter() {
|
||||
for value in saved_values.iter() {
|
||||
h3 { "Saved value: {value}" }
|
||||
}
|
||||
|
||||
|
@ -45,5 +65,18 @@ fn app(cx: Scope) -> Element {
|
|||
} else {
|
||||
"No saved values"
|
||||
}
|
||||
})
|
||||
|
||||
// You can pass a value directly to any prop that accepts a signal
|
||||
Child { count: doubled_count() }
|
||||
Child { count: doubled_count }
|
||||
}
|
||||
}
|
||||
|
||||
#[component]
|
||||
fn Child(mut count: ReadOnlySignal<i32>) -> Element {
|
||||
println!("rendering child with count {count}");
|
||||
|
||||
rsx! {
|
||||
h1 { "{count}" }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,82 +0,0 @@
|
|||
#![allow(non_snake_case)]
|
||||
|
||||
use dioxus::prelude::*;
|
||||
use dioxus_router::prelude::*;
|
||||
|
||||
fn main() {
|
||||
simple_logger::SimpleLogger::new()
|
||||
.with_level(log::LevelFilter::Debug)
|
||||
.with_module_level("dioxus", log::LevelFilter::Trace)
|
||||
.init()
|
||||
.unwrap();
|
||||
dioxus_desktop::launch(App);
|
||||
}
|
||||
|
||||
#[component]
|
||||
fn App(cx: Scope) -> Element {
|
||||
render! {
|
||||
Router::<Route> {}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Routable, Clone)]
|
||||
#[rustfmt::skip]
|
||||
enum Route {
|
||||
#[layout(NavBar)]
|
||||
#[route("/")]
|
||||
Home {},
|
||||
#[nest("/new")]
|
||||
#[route("/")]
|
||||
BlogList {},
|
||||
#[route("/:post")]
|
||||
BlogPost {
|
||||
post: String,
|
||||
},
|
||||
#[end_nest]
|
||||
#[route("/oranges")]
|
||||
Oranges {},
|
||||
}
|
||||
|
||||
#[component]
|
||||
fn NavBar(cx: Scope) -> Element {
|
||||
render! {
|
||||
h1 { "Your app here" }
|
||||
ul {
|
||||
li { Link { to: Route::Home {}, "home" } }
|
||||
li { Link { to: Route::BlogList {}, "blog" } }
|
||||
li { Link { to: Route::BlogPost { post: "tim".into() }, "tims' blog" } }
|
||||
li { Link { to: Route::BlogPost { post: "bill".into() }, "bills' blog" } }
|
||||
li { Link { to: Route::BlogPost { post: "james".into() }, "james amazing' blog" } }
|
||||
}
|
||||
Outlet::<Route> {}
|
||||
}
|
||||
}
|
||||
|
||||
#[component]
|
||||
fn Home(cx: Scope) -> Element {
|
||||
log::debug!("rendering home {:?}", cx.scope_id());
|
||||
render! { h1 { "Home" } }
|
||||
}
|
||||
|
||||
#[component]
|
||||
fn BlogList(cx: Scope) -> Element {
|
||||
log::debug!("rendering blog list {:?}", cx.scope_id());
|
||||
render! { div { "Blog List" } }
|
||||
}
|
||||
|
||||
#[component]
|
||||
fn BlogPost(cx: Scope, post: String) -> Element {
|
||||
log::debug!("rendering blog post {}", post);
|
||||
|
||||
render! {
|
||||
div {
|
||||
h3 { "blog post: {post}" }
|
||||
Link { to: Route::BlogList {}, "back to blog list" }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[component]
|
||||
fn Oranges(cx: Scope) -> Element {
|
||||
render!("Oranges are not apples!")
|
||||
}
|
|
@ -1,11 +1,11 @@
|
|||
use dioxus::prelude::*;
|
||||
|
||||
fn main() {
|
||||
dioxus_desktop::launch(app);
|
||||
launch_desktop(app);
|
||||
}
|
||||
|
||||
fn app(cx: Scope) -> Element {
|
||||
cx.render(rsx!(
|
||||
fn app() -> Element {
|
||||
rsx!(
|
||||
div {
|
||||
// Use Map directly to lazily pull elements
|
||||
{(0..10).map(|f| rsx! { "{f}" })},
|
||||
|
@ -22,7 +22,7 @@ fn app(cx: Scope) -> Element {
|
|||
|
||||
// use a for loop where the body itself is RSX
|
||||
for name in 0..10 {
|
||||
div {"{name}"}
|
||||
div { "{name}" }
|
||||
}
|
||||
|
||||
// Or even use an unterminated conditional
|
||||
|
@ -30,5 +30,5 @@ fn app(cx: Scope) -> Element {
|
|||
"hello world!"
|
||||
}
|
||||
}
|
||||
))
|
||||
)
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
#![allow(non_snake_case)]
|
||||
|
||||
use dioxus::prelude::*;
|
||||
use dioxus_router::prelude::*;
|
||||
|
||||
#[derive(Routable, Clone, PartialEq)]
|
||||
enum Route {
|
||||
|
@ -13,30 +13,46 @@ enum Route {
|
|||
}
|
||||
|
||||
#[component]
|
||||
fn Homepage(cx: Scope) -> Element {
|
||||
render! { h1 { "Welcome home" } }
|
||||
fn Homepage() -> Element {
|
||||
rsx! { h1 { "Welcome home" } }
|
||||
}
|
||||
|
||||
#[component]
|
||||
fn Blog(cx: Scope, id: String) -> Element {
|
||||
render! {
|
||||
fn Blog(id: String) -> Element {
|
||||
rsx! {
|
||||
h1 { "How to make: " }
|
||||
p { "{id}" }
|
||||
}
|
||||
}
|
||||
|
||||
#[component]
|
||||
fn Nav(cx: Scope) -> Element {
|
||||
render! {
|
||||
fn Nav() -> Element {
|
||||
rsx! {
|
||||
nav {
|
||||
li { Link { to: Route::Homepage { }, "Go home" } }
|
||||
li { Link { to: Route::Blog { id: "Brownies".to_string() }, "Learn Brownies" } }
|
||||
li { Link { to: Route::Blog { id: "Cookies".to_string() }, "Learn Cookies" } }
|
||||
li {
|
||||
Link { to: Route::Homepage {}, "Go home" }
|
||||
}
|
||||
li {
|
||||
Link {
|
||||
to: Route::Blog {
|
||||
id: "Brownies".to_string(),
|
||||
},
|
||||
"Learn Brownies"
|
||||
}
|
||||
}
|
||||
li {
|
||||
Link {
|
||||
to: Route::Blog {
|
||||
id: "Cookies".to_string(),
|
||||
},
|
||||
"Learn Cookies"
|
||||
}
|
||||
}
|
||||
}
|
||||
div { Outlet::<Route> {} }
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
dioxus_desktop::launch(|cx| render!(Router::<Route> {}));
|
||||
launch_desktop(|| rsx! { Router::<Route> {} });
|
||||
}
|
||||
|
|
|
@ -1,16 +1,15 @@
|
|||
use dioxus::prelude::*;
|
||||
|
||||
fn main() {
|
||||
let mut dom = VirtualDom::new(app);
|
||||
let _ = dom.rebuild();
|
||||
let dom = VirtualDom::prebuilt(app);
|
||||
let html = dioxus_ssr::render(&dom);
|
||||
|
||||
println!("{}", html);
|
||||
}
|
||||
|
||||
fn app(cx: Scope) -> Element {
|
||||
render! {
|
||||
Component {
|
||||
fn app() -> Element {
|
||||
rsx! {
|
||||
spreadable_component {
|
||||
width: "10px",
|
||||
extra_data: "hello{1}",
|
||||
extra_data2: "hello{2}",
|
||||
|
@ -20,17 +19,18 @@ fn app(cx: Scope) -> Element {
|
|||
}
|
||||
}
|
||||
|
||||
#[component]
|
||||
fn Component<'a>(cx: Scope<'a, Props<'a>>) -> Element<'a> {
|
||||
render! {
|
||||
audio { ..cx.props.attributes, "1: {cx.props.extra_data}\n2: {cx.props.extra_data2}" }
|
||||
}
|
||||
#[derive(Props, PartialEq, Clone)]
|
||||
struct Props {
|
||||
#[props(extends = GlobalAttributes)]
|
||||
attributes: Vec<Attribute>,
|
||||
|
||||
extra_data: String,
|
||||
|
||||
extra_data2: String,
|
||||
}
|
||||
|
||||
#[derive(Props)]
|
||||
struct Props<'a> {
|
||||
#[props(extends = GlobalAttributes)]
|
||||
attributes: Vec<Attribute<'a>>,
|
||||
extra_data: &'a str,
|
||||
extra_data2: &'a str,
|
||||
fn spreadable_component(props: Props) -> Element {
|
||||
rsx! {
|
||||
audio { ..props.attributes, "1: {props.extra_data}\n2: {props.extra_data2}" }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,14 +6,13 @@ use dioxus::prelude::*;
|
|||
|
||||
fn main() {
|
||||
// We can render VirtualDoms
|
||||
let mut vdom = VirtualDom::new(app);
|
||||
let _ = vdom.rebuild();
|
||||
let vdom = VirtualDom::prebuilt(app);
|
||||
println!("{}", dioxus_ssr::render(&vdom));
|
||||
|
||||
// Or we can render rsx! calls themselves
|
||||
println!(
|
||||
"{}",
|
||||
dioxus_ssr::render_lazy(rsx! {
|
||||
dioxus_ssr::render_element(rsx! {
|
||||
div {
|
||||
h1 { "Hello, world!" }
|
||||
}
|
||||
|
@ -30,11 +29,11 @@ fn main() {
|
|||
println!("{file}");
|
||||
}
|
||||
|
||||
fn app(cx: Scope) -> Element {
|
||||
cx.render(rsx!(
|
||||
fn app() -> Element {
|
||||
rsx!(
|
||||
div {
|
||||
h1 { "Title" }
|
||||
p { "Body" }
|
||||
}
|
||||
))
|
||||
)
|
||||
}
|
||||
|
|
36
examples/stale_memo.rs
Normal file
36
examples/stale_memo.rs
Normal file
|
@ -0,0 +1,36 @@
|
|||
use dioxus::prelude::*;
|
||||
|
||||
fn main() {
|
||||
launch_desktop(app);
|
||||
}
|
||||
|
||||
fn app() -> Element {
|
||||
let mut state = use_signal(|| 0);
|
||||
let mut depth = use_signal(|| 1_usize);
|
||||
|
||||
if depth() == 5 {
|
||||
return rsx! {
|
||||
div { "Max depth reached" }
|
||||
button { onclick: move |_| depth -= 1, "Remove depth" }
|
||||
};
|
||||
}
|
||||
|
||||
let items = use_memo(move || (0..depth()).map(|f| f as _).collect::<Vec<isize>>());
|
||||
|
||||
rsx! {
|
||||
button { onclick: move |_| state += 1, "Increment" }
|
||||
button { onclick: move |_| depth += 1, "Add depth" }
|
||||
button {
|
||||
onclick: move |_| async move {
|
||||
depth += 1;
|
||||
tokio::time::sleep(std::time::Duration::from_millis(100)).await;
|
||||
dbg!(items.read());
|
||||
// if depth() is 5, this will be the old since the memo hasn't been re-computed
|
||||
// use_memos are only re-computed when the signals they capture change
|
||||
// *and* they are used in the current render
|
||||
// If the use_memo isn't used, it can't be re-computed!
|
||||
},
|
||||
"Add depth with sleep"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,16 +1,15 @@
|
|||
use dioxus::prelude::*;
|
||||
use dioxus_signals::use_signal;
|
||||
use futures_util::{future, stream, Stream, StreamExt};
|
||||
use std::time::Duration;
|
||||
|
||||
fn main() {
|
||||
dioxus_desktop::launch(app);
|
||||
launch_desktop(app);
|
||||
}
|
||||
|
||||
fn app(cx: Scope) -> Element {
|
||||
let count = use_signal(cx, || 10);
|
||||
fn app() -> Element {
|
||||
let mut count = use_signal(|| 10);
|
||||
|
||||
use_future(cx, (), |_| async move {
|
||||
use_future(move || async move {
|
||||
let mut stream = some_stream();
|
||||
|
||||
while let Some(second) = stream.next().await {
|
||||
|
@ -18,9 +17,9 @@ fn app(cx: Scope) -> Element {
|
|||
}
|
||||
});
|
||||
|
||||
cx.render(rsx! {
|
||||
rsx! {
|
||||
h1 { "High-Five counter: {count}" }
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn some_stream() -> std::pin::Pin<Box<dyn Stream<Item = i32>>> {
|
||||
|
|
|
@ -13,28 +13,25 @@
|
|||
//! We can achieve the majority of suspense functionality by composing "suspenseful"
|
||||
//! primitives in our own custom components.
|
||||
|
||||
use dioxus::desktop::{Config, LogicalSize, WindowBuilder};
|
||||
use dioxus::prelude::*;
|
||||
use dioxus_desktop::{Config, LogicalSize, WindowBuilder};
|
||||
|
||||
fn main() {
|
||||
let cfg = Config::new().with_window(
|
||||
WindowBuilder::new()
|
||||
.with_title("Doggo Fetcher")
|
||||
.with_inner_size(LogicalSize::new(600.0, 800.0)),
|
||||
);
|
||||
|
||||
dioxus_desktop::launch_cfg(app, cfg);
|
||||
LaunchBuilder::desktop()
|
||||
.with_cfg(
|
||||
Config::new().with_window(
|
||||
WindowBuilder::new()
|
||||
.with_title("Doggo Fetcher")
|
||||
.with_inner_size(LogicalSize::new(600.0, 800.0)),
|
||||
),
|
||||
)
|
||||
.launch(app)
|
||||
}
|
||||
|
||||
#[derive(serde::Deserialize)]
|
||||
struct DogApi {
|
||||
message: String,
|
||||
}
|
||||
|
||||
fn app(cx: Scope) -> Element {
|
||||
cx.render(rsx! {
|
||||
fn app() -> Element {
|
||||
rsx! {
|
||||
div {
|
||||
h1 {"Dogs are very important"}
|
||||
h1 { "Dogs are very important" }
|
||||
p {
|
||||
"The dog or domestic dog (Canis familiaris[4][5] or Canis lupus familiaris[5])"
|
||||
"is a domesticated descendant of the wolf which is characterized by an upturning tail."
|
||||
|
@ -44,16 +41,21 @@ fn app(cx: Scope) -> Element {
|
|||
}
|
||||
|
||||
h3 { "Illustrious Dog Photo" }
|
||||
Doggo { }
|
||||
Doggo {}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// This component will re-render when the future has finished
|
||||
/// Suspense is achieved my moving the future into only the component that
|
||||
/// actually renders the data.
|
||||
fn Doggo(cx: Scope) -> Element {
|
||||
let fut = use_future(cx, (), |_| async move {
|
||||
fn Doggo() -> Element {
|
||||
let mut fut = use_resource(move || async move {
|
||||
#[derive(serde::Deserialize)]
|
||||
struct DogApi {
|
||||
message: String,
|
||||
}
|
||||
|
||||
reqwest::get("https://dog.ceo/api/breeds/image/random/")
|
||||
.await
|
||||
.unwrap()
|
||||
|
@ -61,21 +63,12 @@ fn Doggo(cx: Scope) -> Element {
|
|||
.await
|
||||
});
|
||||
|
||||
cx.render(match fut.value() {
|
||||
match fut.read().as_ref() {
|
||||
Some(Ok(resp)) => rsx! {
|
||||
button {
|
||||
onclick: move |_| fut.restart(),
|
||||
"Click to fetch another doggo"
|
||||
}
|
||||
div {
|
||||
img {
|
||||
max_width: "500px",
|
||||
max_height: "500px",
|
||||
src: "{resp.message}",
|
||||
}
|
||||
}
|
||||
button { onclick: move |_| fut.restart(), "Click to fetch another doggo" }
|
||||
div { img { max_width: "500px", max_height: "500px", src: "{resp.message}" } }
|
||||
},
|
||||
Some(Err(_)) => rsx! { div { "loading dogs failed" } },
|
||||
None => rsx! { div { "loading dogs..." } },
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
119
examples/svg.rs
119
examples/svg.rs
|
@ -1,103 +1,50 @@
|
|||
// Thanks to @japsu and their project https://github.com/japsu/jatsi for the example!
|
||||
|
||||
use dioxus::prelude::*;
|
||||
use rand::{thread_rng, Rng};
|
||||
|
||||
fn main() {
|
||||
dioxus_desktop::launch(app);
|
||||
}
|
||||
|
||||
fn app(cx: Scope) -> Element {
|
||||
let val = use_state(cx, || 5);
|
||||
|
||||
cx.render(rsx! {
|
||||
div {
|
||||
user_select: "none",
|
||||
webkit_user_select: "none",
|
||||
margin_left: "10%",
|
||||
margin_right: "10%",
|
||||
h1 { "Click die to generate a new value" }
|
||||
div {
|
||||
cursor: "pointer",
|
||||
height: "80%",
|
||||
width: "80%",
|
||||
Die {
|
||||
value: **val,
|
||||
keep: true,
|
||||
onclick: move |_| {
|
||||
use rand::Rng;
|
||||
let mut rng = rand::thread_rng();
|
||||
val.set(rng.gen_range(1..=6));
|
||||
}
|
||||
}
|
||||
launch(|| {
|
||||
rsx! {
|
||||
div { user_select: "none", webkit_user_select: "none", margin_left: "10%", margin_right: "10%",
|
||||
h1 { "Click die to generate a new value" }
|
||||
div { cursor: "pointer", height: "100%", width: "100%", Dice {} }
|
||||
}
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
#[derive(Props)]
|
||||
pub struct DieProps<'a> {
|
||||
pub value: u64,
|
||||
pub keep: bool,
|
||||
pub onclick: EventHandler<'a, MouseEvent>,
|
||||
}
|
||||
#[component]
|
||||
fn Dice() -> Element {
|
||||
const Y: bool = true;
|
||||
const N: bool = false;
|
||||
const DOTS: [(i64, i64); 7] = [(-1, -1), (-1, -0), (-1, 1), (1, -1), (1, 0), (1, 1), (0, 0)];
|
||||
const DOTS_FOR_VALUE: [[bool; 7]; 6] = [
|
||||
[N, N, N, N, N, N, Y],
|
||||
[N, N, Y, Y, N, N, N],
|
||||
[N, N, Y, Y, N, N, Y],
|
||||
[Y, N, Y, Y, N, Y, N],
|
||||
[Y, N, Y, Y, N, Y, Y],
|
||||
[Y, Y, Y, Y, Y, Y, N],
|
||||
];
|
||||
|
||||
const DOTS: [(i64, i64); 7] = [(-1, -1), (-1, -0), (-1, 1), (1, -1), (1, 0), (1, 1), (0, 0)];
|
||||
const DOTS_FOR_VALUE: [[bool; 7]; 6] = [
|
||||
[false, false, false, false, false, false, true],
|
||||
[false, false, true, true, false, false, false],
|
||||
[false, false, true, true, false, false, true],
|
||||
[true, false, true, true, false, true, false],
|
||||
[true, false, true, true, false, true, true],
|
||||
[true, true, true, true, true, true, false],
|
||||
];
|
||||
let mut value = use_signal(|| 5);
|
||||
let active_dots = use_memo(move || &DOTS_FOR_VALUE[(value() - 1) as usize]);
|
||||
|
||||
const OFFSET: i64 = 600;
|
||||
const DOT_RADIUS: &str = "200";
|
||||
const HELD_COLOR: &str = "#aaa";
|
||||
const UNHELD_COLOR: &str = "#ddd";
|
||||
|
||||
// A six-sided die (D6) with dots.
|
||||
#[allow(non_snake_case)]
|
||||
pub fn Die<'a>(cx: Scope<'a, DieProps<'a>>) -> Element {
|
||||
let &DieProps { value, keep, .. } = cx.props;
|
||||
|
||||
let active_dots = &DOTS_FOR_VALUE[(value - 1) as usize];
|
||||
let fill = if keep { HELD_COLOR } else { UNHELD_COLOR };
|
||||
let dots = DOTS
|
||||
.iter()
|
||||
.zip(active_dots.iter())
|
||||
.filter(|(_, &active)| active)
|
||||
.map(|((x, y), _)| {
|
||||
let dcx = x * OFFSET;
|
||||
let dcy = y * OFFSET;
|
||||
|
||||
rsx! {
|
||||
rsx! {
|
||||
svg {
|
||||
view_box: "-1000 -1000 2000 2000",
|
||||
prevent_default: "onclick",
|
||||
onclick: move |_| value.set(thread_rng().gen_range(1..=6)),
|
||||
rect { x: -1000, y: -1000, width: 2000, height: 2000, rx: 200, fill: "#aaa" }
|
||||
for ((x, y), _) in DOTS.iter().zip(active_dots.read().iter()).filter(|(_, &active)| active) {
|
||||
circle {
|
||||
cx: "{dcx}",
|
||||
cy: "{dcy}",
|
||||
r: "{DOT_RADIUS}",
|
||||
cx: *x * 600,
|
||||
cy: *y * 600,
|
||||
r: 200,
|
||||
fill: "#333"
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
cx.render(rsx! {
|
||||
svg {
|
||||
onclick: move |e| cx.props.onclick.call(e),
|
||||
prevent_default: "onclick",
|
||||
class: "die",
|
||||
view_box: "-1000 -1000 2000 2000",
|
||||
|
||||
rect {
|
||||
x: "-1000",
|
||||
y: "-1000",
|
||||
width: "2000",
|
||||
height: "2000",
|
||||
rx: "{DOT_RADIUS}",
|
||||
fill: "{fill}",
|
||||
}
|
||||
|
||||
{dots}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,81 +0,0 @@
|
|||
use dioxus::prelude::*;
|
||||
|
||||
fn app(cx: Scope) -> Element {
|
||||
cx.render(rsx!( svg {
|
||||
width: "200",
|
||||
height: "250",
|
||||
xmlns: "http://www.w3.org/2000/svg",
|
||||
version: "1.1",
|
||||
rect {
|
||||
x: "10",
|
||||
y: "10",
|
||||
width: "30",
|
||||
height: "30",
|
||||
stroke: "black",
|
||||
fill: "transparent",
|
||||
stroke_width: "5",
|
||||
}
|
||||
rect {
|
||||
x: "60",
|
||||
y: "10",
|
||||
width: "30",
|
||||
height: "30",
|
||||
stroke: "black",
|
||||
fill: "transparent",
|
||||
stroke_width: "5",
|
||||
}
|
||||
circle {
|
||||
cx: "25",
|
||||
cy: "75",
|
||||
r: "20",
|
||||
stroke: "red",
|
||||
fill: "transparent",
|
||||
stroke_width: "5",
|
||||
}
|
||||
ellipse {
|
||||
cx: "75",
|
||||
cy: "75",
|
||||
rx: "20",
|
||||
ry: "5",
|
||||
stroke: "red",
|
||||
fill: "transparent",
|
||||
stroke_width: "5",
|
||||
}
|
||||
line {
|
||||
x1: "10",
|
||||
x2: "50",
|
||||
y1: "110",
|
||||
y2: "150",
|
||||
stroke: "orange",
|
||||
stroke_width: "5",
|
||||
}
|
||||
polyline {
|
||||
points: "60 110 65 120 70 115 75 130 80 125 85 140 90 135 95 150 100 145",
|
||||
stroke: "orange",
|
||||
fill: "transparent",
|
||||
stroke_width: "5",
|
||||
}
|
||||
polygon {
|
||||
points: "50 160 55 180 70 180 60 190 65 205 50 195 35 205 40 190 30 180 45 180",
|
||||
stroke: "green",
|
||||
fill: "transparent",
|
||||
stroke_width: "5",
|
||||
}
|
||||
path {
|
||||
d: "M20,230 Q40,205 50,230 T90,230",
|
||||
fill: "none",
|
||||
stroke: "blue",
|
||||
stroke_width: "5",
|
||||
}
|
||||
path {
|
||||
d: "M9.00001 9C9 62 103.5 124 103.5 178",
|
||||
stroke: "#3CC4DC",
|
||||
"stroke-linecap": "square",
|
||||
"stroke-width": "square",
|
||||
}
|
||||
}))
|
||||
}
|
||||
|
||||
fn main() {
|
||||
dioxus_desktop::launch(app);
|
||||
}
|
|
@ -12,11 +12,10 @@ rust-version = "1.60.0"
|
|||
publish = false
|
||||
|
||||
[dependencies]
|
||||
dioxus = { path = "../../packages/dioxus" }
|
||||
manganis = { workspace = true }
|
||||
|
||||
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
|
||||
dioxus-desktop = { path = "../../packages/desktop" }
|
||||
dioxus = { path = "../../packages/dioxus", features = ["desktop"] }
|
||||
|
||||
[target.'cfg(target_arch = "wasm32")'.dependencies]
|
||||
dioxus-web = { path = "../../packages/web" }
|
||||
dioxus = { path = "../../packages/dioxus", features = ["web"] }
|
||||
|
|
File diff suppressed because one or more lines are too long
|
@ -2,36 +2,34 @@
|
|||
|
||||
use dioxus::prelude::*;
|
||||
|
||||
const _STYLE: &str = manganis::mg!(file("./public/tailwind.css"));
|
||||
const _STYLE: &str = manganis::mg!(file("public/tailwind.css"));
|
||||
|
||||
fn main() {
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
dioxus_desktop::launch(app);
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
dioxus_web::launch(app);
|
||||
launch(app);
|
||||
}
|
||||
|
||||
pub fn app(cx: Scope) -> Element {
|
||||
pub fn app() -> Element {
|
||||
let grey_background = true;
|
||||
cx.render(rsx!(
|
||||
rsx!(
|
||||
div {
|
||||
header {
|
||||
class: "text-gray-400 body-font",
|
||||
// you can use optional attributes to optionally apply a tailwind class
|
||||
class: if grey_background { "bg-gray-900" },
|
||||
class: if grey_background {
|
||||
"bg-gray-900"
|
||||
},
|
||||
div { class: "container mx-auto flex flex-wrap p-5 flex-col md:flex-row items-center",
|
||||
a { class: "flex title-font font-medium items-center text-white mb-4 md:mb-0",
|
||||
StacksIcon {}
|
||||
span { class: "ml-3 text-xl", "Hello Dioxus!"}
|
||||
span { class: "ml-3 text-xl", "Hello Dioxus!" }
|
||||
}
|
||||
nav { class: "md:ml-auto flex flex-wrap items-center text-base justify-center",
|
||||
a { class: "mr-5 hover:text-white", "First Link"}
|
||||
a { class: "mr-5 hover:text-white", "Second Link"}
|
||||
a { class: "mr-5 hover:text-white", "Third Link"}
|
||||
a { class: "mr-5 hover:text-white", "Fourth Link"}
|
||||
a { class: "mr-5 hover:text-white", "First Link" }
|
||||
a { class: "mr-5 hover:text-white", "Second Link" }
|
||||
a { class: "mr-5 hover:text-white", "Third Link" }
|
||||
a { class: "mr-5 hover:text-white", "Fourth Link" }
|
||||
}
|
||||
button {
|
||||
class: "inline-flex items-center bg-gray-800 border-0 py-1 px-3 focus:outline-none hover:bg-gray-700 rounded text-base mt-4 md:mt-0",
|
||||
button { class: "inline-flex items-center bg-gray-800 border-0 py-1 px-3 focus:outline-none hover:bg-gray-700 rounded text-base mt-4 md:mt-0",
|
||||
"Button"
|
||||
RightArrowIcon {}
|
||||
}
|
||||
|
@ -45,21 +43,17 @@ pub fn app(cx: Scope) -> Element {
|
|||
br { class: "hidden lg:inline-block" }
|
||||
"Dioxus Sneak Peek"
|
||||
}
|
||||
p {
|
||||
class: "mb-8 leading-relaxed",
|
||||
p { class: "mb-8 leading-relaxed",
|
||||
|
||||
"Dioxus is a new UI framework that makes it easy and simple to write cross-platform apps using web
|
||||
technologies! It is functional, fast, and portable. Dioxus can run on the web, on the desktop, and
|
||||
on mobile and embedded platforms."
|
||||
|
||||
}
|
||||
div { class: "flex justify-center",
|
||||
button {
|
||||
class: "inline-flex text-white bg-indigo-500 border-0 py-2 px-6 focus:outline-none hover:bg-indigo-600 rounded text-lg",
|
||||
button { class: "inline-flex text-white bg-indigo-500 border-0 py-2 px-6 focus:outline-none hover:bg-indigo-600 rounded text-lg",
|
||||
"Learn more"
|
||||
}
|
||||
button {
|
||||
class: "ml-4 inline-flex text-gray-400 bg-gray-800 border-0 py-2 px-6 focus:outline-none hover:bg-gray-700 hover:text-white rounded text-lg",
|
||||
button { class: "ml-4 inline-flex text-gray-400 bg-gray-800 border-0 py-2 px-6 focus:outline-none hover:bg-gray-700 hover:text-white rounded text-lg",
|
||||
"Build an app"
|
||||
}
|
||||
}
|
||||
|
@ -68,18 +62,18 @@ pub fn app(cx: Scope) -> Element {
|
|||
img {
|
||||
class: "object-cover object-center rounded",
|
||||
src: "https://i.imgur.com/oK6BLtw.png",
|
||||
referrerpolicy:"no-referrer",
|
||||
alt: "hero",
|
||||
referrerpolicy: "no-referrer",
|
||||
alt: "hero"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
))
|
||||
)
|
||||
}
|
||||
|
||||
pub fn StacksIcon(cx: Scope) -> Element {
|
||||
cx.render(rsx!(
|
||||
pub fn StacksIcon() -> Element {
|
||||
rsx!(
|
||||
svg {
|
||||
fill: "none",
|
||||
stroke: "currentColor",
|
||||
|
@ -88,13 +82,13 @@ pub fn StacksIcon(cx: Scope) -> Element {
|
|||
stroke_width: "2",
|
||||
class: "w-10 h-10 text-white p-2 bg-indigo-500 rounded-full",
|
||||
view_box: "0 0 24 24",
|
||||
path { d: "M12 2L2 7l10 5 10-5-10-5zM2 17l10 5 10-5M2 12l10 5 10-5"}
|
||||
path { d: "M12 2L2 7l10 5 10-5-10-5zM2 17l10 5 10-5M2 12l10 5 10-5" }
|
||||
}
|
||||
))
|
||||
)
|
||||
}
|
||||
|
||||
pub fn RightArrowIcon(cx: Scope) -> Element {
|
||||
cx.render(rsx!(
|
||||
pub fn RightArrowIcon() -> Element {
|
||||
rsx!(
|
||||
svg {
|
||||
fill: "none",
|
||||
stroke: "currentColor",
|
||||
|
@ -103,7 +97,7 @@ pub fn RightArrowIcon(cx: Scope) -> Element {
|
|||
stroke_width: "2",
|
||||
class: "w-4 h-4 ml-1",
|
||||
view_box: "0 0 24 24",
|
||||
path { d: "M5 12h14M12 5l7 7-7 7"}
|
||||
path { d: "M5 12h14M12 5l7 7-7 7" }
|
||||
}
|
||||
))
|
||||
)
|
||||
}
|
||||
|
|
|
@ -6,29 +6,23 @@ use dioxus::prelude::*;
|
|||
use std::time::Duration;
|
||||
|
||||
fn main() {
|
||||
dioxus_desktop::launch(app);
|
||||
launch_desktop(app);
|
||||
}
|
||||
|
||||
fn app(cx: Scope) -> Element {
|
||||
let count = use_state(cx, || 0);
|
||||
fn app() -> Element {
|
||||
let mut count = use_signal(|| 0);
|
||||
|
||||
use_future(cx, (), move |_| {
|
||||
let mut count = count.clone();
|
||||
async move {
|
||||
loop {
|
||||
tokio::time::sleep(Duration::from_millis(1000)).await;
|
||||
count += 1;
|
||||
}
|
||||
use_future(move || async move {
|
||||
loop {
|
||||
tokio::time::sleep(Duration::from_millis(1000)).await;
|
||||
count += 1;
|
||||
}
|
||||
});
|
||||
|
||||
cx.render(rsx! {
|
||||
rsx! {
|
||||
div {
|
||||
h1 { "Current count: {count}" }
|
||||
button {
|
||||
onclick: move |_| count.set(0),
|
||||
"Reset the count"
|
||||
}
|
||||
button { onclick: move |_| count.set(0), "Reset the count" }
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,15 +3,13 @@
|
|||
use dioxus::prelude::*;
|
||||
|
||||
fn main() {
|
||||
dioxus_desktop::launch(app);
|
||||
launch_desktop(app);
|
||||
}
|
||||
|
||||
fn app(cx: Scope) -> Element {
|
||||
let model = use_state(cx, || String::from("asd"));
|
||||
fn app() -> Element {
|
||||
let mut model = use_signal(|| String::from("asd"));
|
||||
|
||||
println!("{model}");
|
||||
|
||||
cx.render(rsx! {
|
||||
rsx! {
|
||||
textarea {
|
||||
class: "border",
|
||||
rows: "10",
|
||||
|
@ -19,5 +17,5 @@ fn app(cx: Scope) -> Element {
|
|||
value: "{model}",
|
||||
oninput: move |e| model.set(e.value().clone()),
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,104 +1,108 @@
|
|||
#![allow(non_snake_case)]
|
||||
|
||||
use dioxus::prelude::*;
|
||||
use dioxus_elements::input_data::keyboard_types::Key;
|
||||
use std::collections::HashMap;
|
||||
|
||||
fn main() {
|
||||
dioxus_desktop::launch(app);
|
||||
launch(app);
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Eq, Clone, Copy)]
|
||||
pub enum FilterState {
|
||||
enum FilterState {
|
||||
All,
|
||||
Active,
|
||||
Completed,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Clone)]
|
||||
pub struct TodoItem {
|
||||
pub id: u32,
|
||||
pub checked: bool,
|
||||
pub contents: String,
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
struct TodoItem {
|
||||
id: u32,
|
||||
checked: bool,
|
||||
contents: String,
|
||||
}
|
||||
|
||||
pub fn app(cx: Scope<()>) -> Element {
|
||||
let todos = use_state(cx, im_rc::HashMap::<u32, TodoItem>::default);
|
||||
let filter = use_state(cx, || FilterState::All);
|
||||
const STYLE: &str = include_str!("./assets/todomvc.css");
|
||||
|
||||
// Filter the todos based on the filter state
|
||||
let mut filtered_todos = todos
|
||||
.iter()
|
||||
.filter(|(_, item)| match **filter {
|
||||
FilterState::All => true,
|
||||
FilterState::Active => !item.checked,
|
||||
FilterState::Completed => item.checked,
|
||||
})
|
||||
.map(|f| *f.0)
|
||||
.collect::<Vec<_>>();
|
||||
filtered_todos.sort_unstable();
|
||||
fn app() -> Element {
|
||||
let mut todos = use_signal(HashMap::<u32, TodoItem>::new);
|
||||
let filter = use_signal(|| FilterState::All);
|
||||
|
||||
let active_todo_count = todos.values().filter(|item| !item.checked).count();
|
||||
let active_todo_text = match active_todo_count {
|
||||
1 => "item",
|
||||
_ => "items",
|
||||
let active_todo_count =
|
||||
use_memo(move || todos.read().values().filter(|item| !item.checked).count());
|
||||
|
||||
let filtered_todos = use_memo(move || {
|
||||
let mut filtered_todos = todos
|
||||
.read()
|
||||
.iter()
|
||||
.filter(|(_, item)| match filter() {
|
||||
FilterState::All => true,
|
||||
FilterState::Active => !item.checked,
|
||||
FilterState::Completed => item.checked,
|
||||
})
|
||||
.map(|f| *f.0)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
filtered_todos.sort_unstable();
|
||||
|
||||
filtered_todos
|
||||
});
|
||||
|
||||
let toggle_all = move |_| {
|
||||
let check = active_todo_count() != 0;
|
||||
for (_, item) in todos.write().iter_mut() {
|
||||
item.checked = check;
|
||||
}
|
||||
};
|
||||
|
||||
let show_clear_completed = todos.values().any(|todo| todo.checked);
|
||||
|
||||
cx.render(rsx! {
|
||||
rsx! {
|
||||
section { class: "todoapp",
|
||||
style { {include_str!("./assets/todomvc.css")} }
|
||||
TodoHeader { todos: todos }
|
||||
style { {STYLE} }
|
||||
TodoHeader { todos }
|
||||
section { class: "main",
|
||||
if !todos.is_empty() {
|
||||
if !todos.read().is_empty() {
|
||||
input {
|
||||
id: "toggle-all",
|
||||
class: "toggle-all",
|
||||
r#type: "checkbox",
|
||||
onchange: move |_| {
|
||||
let check = active_todo_count != 0;
|
||||
for (_, item) in todos.make_mut().iter_mut() {
|
||||
item.checked = check;
|
||||
}
|
||||
},
|
||||
checked: if active_todo_count == 0 { "true" } else { "false" },
|
||||
onchange: toggle_all,
|
||||
checked: active_todo_count() == 0,
|
||||
}
|
||||
label { r#for: "toggle-all" }
|
||||
}
|
||||
ul { class: "todo-list",
|
||||
for id in filtered_todos.iter() {
|
||||
TodoEntry {
|
||||
key: "{id}",
|
||||
id: *id,
|
||||
todos: todos,
|
||||
}
|
||||
for id in filtered_todos() {
|
||||
TodoEntry { key: "{id}", id, todos }
|
||||
}
|
||||
}
|
||||
if !todos.is_empty() {
|
||||
ListFooter {
|
||||
active_todo_count: active_todo_count,
|
||||
active_todo_text: active_todo_text,
|
||||
show_clear_completed: show_clear_completed,
|
||||
todos: todos,
|
||||
filter: filter,
|
||||
}
|
||||
if !todos.read().is_empty() {
|
||||
ListFooter { active_todo_count, todos, filter }
|
||||
}
|
||||
}
|
||||
}
|
||||
PageFooter {}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Props)]
|
||||
pub struct TodoHeaderProps<'a> {
|
||||
todos: &'a UseState<im_rc::HashMap<u32, TodoItem>>,
|
||||
}
|
||||
#[component]
|
||||
fn TodoHeader(mut todos: Signal<HashMap<u32, TodoItem>>) -> Element {
|
||||
let mut draft = use_signal(|| "".to_string());
|
||||
let mut todo_id = use_signal(|| 0);
|
||||
|
||||
pub fn TodoHeader<'a>(cx: Scope<'a, TodoHeaderProps<'a>>) -> Element {
|
||||
let draft = use_state(cx, || "".to_string());
|
||||
let todo_id = use_state(cx, || 0);
|
||||
let onkeydown = move |evt: KeyboardEvent| {
|
||||
if evt.key() == Key::Enter && !draft.read().is_empty() {
|
||||
let id = todo_id();
|
||||
let todo = TodoItem {
|
||||
id,
|
||||
checked: false,
|
||||
contents: draft.to_string(),
|
||||
};
|
||||
todos.write().insert(id, todo);
|
||||
todo_id += 1;
|
||||
draft.set("".to_string());
|
||||
}
|
||||
};
|
||||
|
||||
cx.render(rsx! {
|
||||
rsx! {
|
||||
header { class: "header",
|
||||
h1 { "todos" }
|
||||
input {
|
||||
|
@ -106,76 +110,46 @@ pub fn TodoHeader<'a>(cx: Scope<'a, TodoHeaderProps<'a>>) -> Element {
|
|||
placeholder: "What needs to be done?",
|
||||
value: "{draft}",
|
||||
autofocus: "true",
|
||||
oninput: move |evt| {
|
||||
draft.set(evt.value().clone());
|
||||
},
|
||||
onkeydown: move |evt| {
|
||||
if evt.key() == Key::Enter && !draft.is_empty() {
|
||||
cx.props
|
||||
.todos
|
||||
.make_mut()
|
||||
.insert(
|
||||
**todo_id,
|
||||
TodoItem {
|
||||
id: **todo_id,
|
||||
checked: false,
|
||||
contents: draft.to_string(),
|
||||
},
|
||||
);
|
||||
*todo_id.make_mut() += 1;
|
||||
draft.set("".to_string());
|
||||
}
|
||||
}
|
||||
oninput: move |evt| draft.set(evt.value().clone()),
|
||||
onkeydown,
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Props)]
|
||||
pub struct TodoEntryProps<'a> {
|
||||
todos: &'a UseState<im_rc::HashMap<u32, TodoItem>>,
|
||||
id: u32,
|
||||
}
|
||||
#[component]
|
||||
fn TodoEntry(mut todos: Signal<HashMap<u32, TodoItem>>, id: u32) -> Element {
|
||||
let mut is_editing = use_signal(|| false);
|
||||
let checked = use_memo(move || todos.read().get(&id).unwrap().checked);
|
||||
let contents = use_memo(move || todos.read().get(&id).unwrap().contents.clone());
|
||||
|
||||
pub fn TodoEntry<'a>(cx: Scope<'a, TodoEntryProps<'a>>) -> Element {
|
||||
let is_editing = use_state(cx, || false);
|
||||
|
||||
let todos = cx.props.todos.get();
|
||||
let todo = &todos[&cx.props.id];
|
||||
let completed = if todo.checked { "completed" } else { "" };
|
||||
let editing = if **is_editing { "editing" } else { "" };
|
||||
|
||||
cx.render(rsx!{
|
||||
li { class: "{completed} {editing}",
|
||||
rsx! {
|
||||
li { class: if checked() { "completed" }, class: if is_editing() { "editing" },
|
||||
div { class: "view",
|
||||
input {
|
||||
class: "toggle",
|
||||
r#type: "checkbox",
|
||||
id: "cbg-{todo.id}",
|
||||
checked: "{todo.checked}",
|
||||
oninput: move |evt| {
|
||||
cx.props.todos.make_mut()[&cx.props.id].checked = evt.value().parse().unwrap();
|
||||
}
|
||||
id: "cbg-{id}",
|
||||
checked: "{checked}",
|
||||
oninput: move |evt| todos.write().get_mut(&id).unwrap().checked = evt.value().parse().unwrap(),
|
||||
}
|
||||
label {
|
||||
r#for: "cbg-{todo.id}",
|
||||
r#for: "cbg-{id}",
|
||||
ondoubleclick: move |_| is_editing.set(true),
|
||||
prevent_default: "onclick",
|
||||
"{todo.contents}"
|
||||
"{contents}"
|
||||
}
|
||||
button {
|
||||
class: "destroy",
|
||||
onclick: move |_| {
|
||||
cx.props.todos.make_mut().remove(&todo.id);
|
||||
},
|
||||
onclick: move |_| { todos.write().remove(&id); },
|
||||
prevent_default: "onclick"
|
||||
}
|
||||
}
|
||||
if **is_editing {
|
||||
if is_editing() {
|
||||
input {
|
||||
class: "edit",
|
||||
value: "{todo.contents}",
|
||||
oninput: move |evt| cx.props.todos.make_mut()[&cx.props.id].contents = evt.value(),
|
||||
value: "{contents}",
|
||||
oninput: move |evt| todos.write().get_mut(&id).unwrap().contents = evt.value(),
|
||||
autofocus: "true",
|
||||
onfocusout: move |_| is_editing.set(false),
|
||||
onkeydown: move |evt| {
|
||||
|
@ -183,39 +157,32 @@ pub fn TodoEntry<'a>(cx: Scope<'a, TodoEntryProps<'a>>) -> Element {
|
|||
Key::Enter | Key::Escape | Key::Tab => is_editing.set(false),
|
||||
_ => {}
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Props)]
|
||||
pub struct ListFooterProps<'a> {
|
||||
todos: &'a UseState<im_rc::HashMap<u32, TodoItem>>,
|
||||
active_todo_count: usize,
|
||||
active_todo_text: &'a str,
|
||||
show_clear_completed: bool,
|
||||
filter: &'a UseState<FilterState>,
|
||||
}
|
||||
#[component]
|
||||
fn ListFooter(
|
||||
mut todos: Signal<HashMap<u32, TodoItem>>,
|
||||
active_todo_count: ReadOnlySignal<usize>,
|
||||
mut filter: Signal<FilterState>,
|
||||
) -> Element {
|
||||
let show_clear_completed = use_memo(move || todos.read().values().any(|todo| todo.checked));
|
||||
|
||||
pub fn ListFooter<'a>(cx: Scope<'a, ListFooterProps<'a>>) -> Element {
|
||||
let active_todo_count = cx.props.active_todo_count;
|
||||
let active_todo_text = cx.props.active_todo_text;
|
||||
|
||||
let selected = |state| {
|
||||
if *cx.props.filter == state {
|
||||
"selected"
|
||||
} else {
|
||||
"false"
|
||||
}
|
||||
};
|
||||
|
||||
cx.render(rsx! {
|
||||
rsx! {
|
||||
footer { class: "footer",
|
||||
span { class: "todo-count",
|
||||
strong { "{active_todo_count} " }
|
||||
span { "{active_todo_text} left" }
|
||||
span {
|
||||
match active_todo_count() {
|
||||
1 => "item",
|
||||
_ => "items",
|
||||
}
|
||||
" left"
|
||||
}
|
||||
}
|
||||
ul { class: "filters",
|
||||
for (state , state_text , url) in [
|
||||
|
@ -226,27 +193,27 @@ pub fn ListFooter<'a>(cx: Scope<'a, ListFooterProps<'a>>) -> Element {
|
|||
li {
|
||||
a {
|
||||
href: url,
|
||||
class: selected(state),
|
||||
onclick: move |_| cx.props.filter.set(state),
|
||||
class: if filter() == state { "selected" },
|
||||
onclick: move |_| filter.set(state),
|
||||
prevent_default: "onclick",
|
||||
{state_text}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if cx.props.show_clear_completed {
|
||||
if show_clear_completed() {
|
||||
button {
|
||||
class: "clear-completed",
|
||||
onclick: move |_| cx.props.todos.make_mut().retain(|_, todo| !todo.checked),
|
||||
onclick: move |_| todos.write().retain(|_, todo| !todo.checked),
|
||||
"Clear completed"
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub fn PageFooter(cx: Scope) -> Element {
|
||||
cx.render(rsx! {
|
||||
fn PageFooter() -> Element {
|
||||
rsx! {
|
||||
footer { class: "info",
|
||||
p { "Double-click to edit a todo" }
|
||||
p {
|
||||
|
@ -258,5 +225,5 @@ pub fn PageFooter(cx: Scope) -> Element {
|
|||
a { href: "http://todomvc.com", "TodoMVC" }
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
use dioxus::desktop::wry::http;
|
||||
use dioxus::desktop::wry::http::Response;
|
||||
use dioxus::desktop::{use_asset_handler, AssetRequest};
|
||||
use dioxus::prelude::*;
|
||||
use dioxus_desktop::wry::http;
|
||||
use dioxus_desktop::wry::http::Response;
|
||||
use dioxus_desktop::{use_asset_handler, AssetRequest};
|
||||
use http::{header::*, response::Builder as ResponseBuilder, status::StatusCode};
|
||||
use std::{io::SeekFrom, path::PathBuf};
|
||||
use tokio::io::AsyncReadExt;
|
||||
|
@ -26,11 +26,11 @@ fn main() {
|
|||
}
|
||||
});
|
||||
}
|
||||
dioxus_desktop::launch(app);
|
||||
launch_desktop(app);
|
||||
}
|
||||
|
||||
fn app(cx: Scope) -> Element {
|
||||
use_asset_handler(cx, "videos", move |request, responder| {
|
||||
fn app() -> Element {
|
||||
use_asset_handler("videos", move |request, responder| {
|
||||
// Using dioxus::spawn works, but is slower than a dedicated thread
|
||||
tokio::task::spawn(async move {
|
||||
let video_file = PathBuf::from(VIDEO_PATH);
|
||||
|
@ -43,7 +43,7 @@ fn app(cx: Scope) -> Element {
|
|||
});
|
||||
});
|
||||
|
||||
render! {
|
||||
rsx! {
|
||||
div {
|
||||
video {
|
||||
src: "/videos/test_video.mp4",
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
use dioxus::prelude::*;
|
||||
|
||||
fn main() {
|
||||
dioxus_desktop::launch(app);
|
||||
launch(app);
|
||||
}
|
||||
|
||||
fn app(cx: Scope) -> Element {
|
||||
cx.render(rsx! {
|
||||
fn app() -> Element {
|
||||
rsx! {
|
||||
web-component {
|
||||
"my-prop": "5%",
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,75 +1,70 @@
|
|||
use dioxus::desktop::{window, Config, WindowBuilder};
|
||||
use dioxus::prelude::*;
|
||||
use dioxus_desktop::{Config, WindowBuilder};
|
||||
|
||||
fn main() {
|
||||
let cfg = Config::new().with_window(
|
||||
WindowBuilder::new()
|
||||
.with_title("Borderless Window")
|
||||
.with_decorations(false),
|
||||
);
|
||||
|
||||
dioxus_desktop::launch_cfg(app, cfg);
|
||||
LaunchBuilder::desktop()
|
||||
.with_cfg(
|
||||
Config::new().with_window(
|
||||
WindowBuilder::new()
|
||||
.with_title("Borderless Window")
|
||||
.with_decorations(false),
|
||||
),
|
||||
)
|
||||
.launch(app)
|
||||
}
|
||||
|
||||
fn app(cx: Scope) -> Element {
|
||||
let window = dioxus_desktop::use_window(cx);
|
||||
fn app() -> Element {
|
||||
let mut fullscreen = use_signal(|| false);
|
||||
let mut always_on_top = use_signal(|| false);
|
||||
let mut decorations = use_signal(|| false);
|
||||
|
||||
// if you want to make window fullscreen, you need close the resizable.
|
||||
// window.set_fullscreen(true);
|
||||
// window.set_resizable(false);
|
||||
|
||||
let fullscreen = use_state(cx, || false);
|
||||
let always_on_top = use_state(cx, || false);
|
||||
let decorations = use_state(cx, || false);
|
||||
|
||||
cx.render(rsx!(
|
||||
link { href:"https://unpkg.com/tailwindcss@^2/dist/tailwind.min.css", rel:"stylesheet" }
|
||||
rsx!(
|
||||
link {
|
||||
href: "https://unpkg.com/tailwindcss@^2/dist/tailwind.min.css",
|
||||
rel: "stylesheet"
|
||||
}
|
||||
header {
|
||||
class: "text-gray-400 bg-gray-900 body-font",
|
||||
onmousedown: move |_| window.drag(),
|
||||
div {
|
||||
class: "container mx-auto flex flex-wrap p-5 flex-col md:flex-row items-center",
|
||||
onmousedown: move |_| window().drag(),
|
||||
div { class: "container mx-auto flex flex-wrap p-5 flex-col md:flex-row items-center",
|
||||
a { class: "flex title-font font-medium items-center text-white mb-4 md:mb-0",
|
||||
span { class: "ml-3 text-xl", "Dioxus"}
|
||||
span { class: "ml-3 text-xl", "Dioxus" }
|
||||
}
|
||||
nav { class: "md:ml-auto flex flex-wrap items-center text-base justify-center" }
|
||||
button {
|
||||
class: "inline-flex items-center bg-gray-800 border-0 py-1 px-3 focus:outline-none hover:bg-gray-700 rounded text-base mt-4 md:mt-0",
|
||||
onmousedown: |evt| evt.stop_propagation(),
|
||||
onclick: move |_| window.set_minimized(true),
|
||||
onclick: move |_| window().set_minimized(true),
|
||||
"Minimize"
|
||||
}
|
||||
button {
|
||||
class: "inline-flex items-center bg-gray-800 border-0 py-1 px-3 focus:outline-none hover:bg-gray-700 rounded text-base mt-4 md:mt-0",
|
||||
onmousedown: |evt| evt.stop_propagation(),
|
||||
onclick: move |_| {
|
||||
|
||||
window.set_fullscreen(!**fullscreen);
|
||||
window.set_resizable(**fullscreen);
|
||||
fullscreen.modify(|f| !*f);
|
||||
window().set_fullscreen(!fullscreen());
|
||||
window().set_resizable(fullscreen());
|
||||
fullscreen.toggle();
|
||||
},
|
||||
"Fullscreen"
|
||||
}
|
||||
button {
|
||||
class: "inline-flex items-center bg-gray-800 border-0 py-1 px-3 focus:outline-none hover:bg-gray-700 rounded text-base mt-4 md:mt-0",
|
||||
onmousedown: |evt| evt.stop_propagation(),
|
||||
onclick: move |_| window.close(),
|
||||
onclick: move |_| window().close(),
|
||||
"Close"
|
||||
}
|
||||
}
|
||||
}
|
||||
br {}
|
||||
div {
|
||||
class: "container mx-auto",
|
||||
div {
|
||||
class: "grid grid-cols-5",
|
||||
div { class: "container mx-auto",
|
||||
div { class: "grid grid-cols-5",
|
||||
div {
|
||||
button {
|
||||
class: "inline-flex items-center text-white bg-green-500 border-0 py-1 px-3 hover:bg-green-700 rounded",
|
||||
onmousedown: |evt| evt.stop_propagation(),
|
||||
onclick: move |_| {
|
||||
window.set_always_on_top(!always_on_top);
|
||||
always_on_top.set(!always_on_top);
|
||||
window().set_always_on_top(!always_on_top());
|
||||
always_on_top.toggle();
|
||||
},
|
||||
"Always On Top"
|
||||
}
|
||||
|
@ -79,8 +74,8 @@ fn app(cx: Scope) -> Element {
|
|||
class: "inline-flex items-center text-white bg-blue-500 border-0 py-1 px-3 hover:bg-green-700 rounded",
|
||||
onmousedown: |evt| evt.stop_propagation(),
|
||||
onclick: move |_| {
|
||||
window.set_decorations(!decorations);
|
||||
decorations.set(!decorations);
|
||||
window().set_decorations(!decorations());
|
||||
decorations.toggle();
|
||||
},
|
||||
"Set Decorations"
|
||||
}
|
||||
|
@ -89,11 +84,11 @@ fn app(cx: Scope) -> Element {
|
|||
button {
|
||||
class: "inline-flex items-center text-white bg-blue-500 border-0 py-1 px-3 hover:bg-green-700 rounded",
|
||||
onmousedown: |evt| evt.stop_propagation(),
|
||||
onclick: move |_| window.set_title("Dioxus Application"),
|
||||
onclick: move |_| window().set_title("Dioxus Application"),
|
||||
"Change Title"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
))
|
||||
)
|
||||
}
|
||||
|
|
|
@ -1,45 +1,35 @@
|
|||
use dioxus::desktop::tao::event::Event as WryEvent;
|
||||
use dioxus::desktop::tao::event::WindowEvent;
|
||||
use dioxus::desktop::use_wry_event_handler;
|
||||
use dioxus::desktop::{Config, WindowCloseBehaviour};
|
||||
use dioxus::prelude::*;
|
||||
use dioxus_desktop::tao::event::Event as WryEvent;
|
||||
use dioxus_desktop::tao::event::WindowEvent;
|
||||
use dioxus_desktop::use_wry_event_handler;
|
||||
use dioxus_desktop::{Config, WindowCloseBehaviour};
|
||||
|
||||
fn main() {
|
||||
let cfg = Config::new().with_close_behaviour(WindowCloseBehaviour::CloseWindow);
|
||||
|
||||
dioxus_desktop::launch_cfg(app, cfg);
|
||||
LaunchBuilder::desktop()
|
||||
.with_cfg(Config::new().with_close_behaviour(WindowCloseBehaviour::CloseWindow))
|
||||
.launch(app)
|
||||
}
|
||||
|
||||
fn app(cx: Scope) -> Element {
|
||||
let focused = use_state(cx, || false);
|
||||
fn app() -> Element {
|
||||
let mut focused = use_signal(|| true);
|
||||
|
||||
use_wry_event_handler(cx, {
|
||||
to_owned![focused];
|
||||
move |event, _| {
|
||||
if let WryEvent::WindowEvent {
|
||||
event: WindowEvent::Focused(new_focused),
|
||||
..
|
||||
} = event
|
||||
{
|
||||
focused.set(*new_focused);
|
||||
}
|
||||
use_wry_event_handler(move |event, _| {
|
||||
if let WryEvent::WindowEvent {
|
||||
event: WindowEvent::Focused(new_focused),
|
||||
..
|
||||
} = event
|
||||
{
|
||||
focused.set(*new_focused)
|
||||
}
|
||||
});
|
||||
|
||||
cx.render(rsx! {
|
||||
div{
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
display: "flex",
|
||||
flex_direction: "column",
|
||||
align_items: "center",
|
||||
{
|
||||
if *focused.get() {
|
||||
"This window is focused!"
|
||||
} else {
|
||||
"This window is not focused!"
|
||||
}
|
||||
rsx! {
|
||||
div { width: "100%", height: "100%", display: "flex", flex_direction: "column", align_items: "center",
|
||||
if focused() {
|
||||
"This window is focused!"
|
||||
} else {
|
||||
"This window is not focused!"
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,22 +1,22 @@
|
|||
use dioxus::prelude::*;
|
||||
|
||||
fn main() {
|
||||
dioxus_desktop::launch(app);
|
||||
launch_desktop(app);
|
||||
}
|
||||
|
||||
fn app(cx: Scope) -> Element {
|
||||
let level = use_state(cx, || 1.0);
|
||||
fn app() -> Element {
|
||||
let mut level = use_signal(|| 1.0);
|
||||
|
||||
cx.render(rsx! {
|
||||
rsx! {
|
||||
input {
|
||||
r#type: "number",
|
||||
value: "{level}",
|
||||
oninput: |e| {
|
||||
oninput: move |e| {
|
||||
if let Ok(new_zoom) = e.value().parse::<f64>() {
|
||||
level.set(new_zoom);
|
||||
dioxus_desktop::window().webview.zoom(new_zoom);
|
||||
dioxus::desktop::window().webview.zoom(new_zoom);
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,15 +5,13 @@
|
|||
use dioxus::prelude::*;
|
||||
|
||||
fn main() {
|
||||
dioxus_desktop::launch(app);
|
||||
launch_desktop(app);
|
||||
}
|
||||
|
||||
fn app(cx: Scope) -> Element {
|
||||
let contents = use_state(cx, || {
|
||||
String::from("<script>alert(\"hello world\")</script>")
|
||||
});
|
||||
fn app() -> Element {
|
||||
let mut contents = use_signal(|| String::from("<script>alert(\"hello world\")</script>"));
|
||||
|
||||
cx.render(rsx! {
|
||||
rsx! {
|
||||
div {
|
||||
h1 {"Dioxus is XSS-Safe"}
|
||||
h3 { "{contents}" }
|
||||
|
@ -23,5 +21,5 @@ fn app(cx: Scope) -> Element {
|
|||
oninput: move |e| contents.set(e.value()),
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
fn it_works() {
|
||||
cx.render(rsx!({()}))
|
||||
rsx!({()})
|
||||
}
|
||||
|
||||
|
|
|
@ -5,8 +5,8 @@ pub fn Explainer<'a>(
|
|||
cx: Scope<'a>,
|
||||
invert: bool,
|
||||
title: &'static str,
|
||||
content: Element<'a>,
|
||||
flasher: Element<'a>,
|
||||
content: Element,
|
||||
flasher: Element,
|
||||
) -> Element {
|
||||
// pt-5 sm:pt-24 lg:pt-24
|
||||
|
||||
|
@ -32,10 +32,10 @@ pub fn Explainer<'a>(
|
|||
std::mem::swap(&mut left, &mut right);
|
||||
}
|
||||
|
||||
cx.render(rsx! {
|
||||
rsx! {
|
||||
div { class: "flex flex-wrap items-center dark:text-white py-16 border-t font-light",
|
||||
{left},
|
||||
{right}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
fn SaveClipboard(cx: Scope) -> Element {
|
||||
fn SaveClipboard() -> Element {
|
||||
rsx! {
|
||||
div { class: "relative w-1/2 {align} max-w-md leading-8",
|
||||
h2 { class: "mb-6 text-3xl leading-tight md:text-4xl md:leading-tight lg:text-3xl lg:leading-tight font-heading font-mono font-bold",
|
||||
|
@ -7,7 +7,7 @@ fn SaveClipboard(cx: Scope) -> Element {
|
|||
}
|
||||
};
|
||||
|
||||
cx.render(rsx! {
|
||||
rsx! {
|
||||
div { "hello world", "hello world", "hello world" }
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
pub static Icon3: Component<()> = |cx| {
|
||||
cx.render(rsx! {
|
||||
rsx! {
|
||||
svg {
|
||||
class: "w-6 h-6",
|
||||
stroke_linecap: "round",
|
||||
|
@ -11,5 +11,5 @@ pub static Icon3: Component<()> = |cx| {
|
|||
path { d: "M20 21v-2a4 4 0 00-4-4H8a4 4 0 00-4 4v2" }
|
||||
circle { cx: "12", cy: "7", r: "4" }
|
||||
}
|
||||
})
|
||||
}
|
||||
};
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
fn it_works() {
|
||||
cx.render(rsx! {
|
||||
rsx! {
|
||||
div {
|
||||
span { "Description: ", {package.description.as_deref().unwrap_or("❌❌❌❌ missing")} }
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
fn app(cx: Scope) -> Element {
|
||||
cx.render(rsx! { div { "hello world" } })
|
||||
fn app() -> Element {
|
||||
rsx! { div { "hello world" } }
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
fn app(cx: Scope) -> Element {
|
||||
cx.render(rsx! {
|
||||
fn app() -> Element {
|
||||
rsx! {
|
||||
div {"hello world" }
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
fn app(cx: Scope) -> Element {
|
||||
cx.render(rsx! { div { "hello world" } })
|
||||
fn app() -> Element {
|
||||
rsx! { div { "hello world" } }
|
||||
}
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue