Merge branch 'master' into pr/Andrew15-5/1903

This commit is contained in:
Evan Almloff 2024-02-06 10:28:49 -06:00
commit 1909b05412
518 changed files with 17150 additions and 20690 deletions

12
.github/free_space.sh vendored Normal file
View 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

View file

@ -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
View file

@ -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/

1302
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -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-core-macro = { path = "packages/core-macro", version = "0.4.0" }
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 = { 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-desktop = { path = "packages/desktop", version = "0.4.0" }
dioxus-web = { path = "packages/web", 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-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" }
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" }
@ -120,28 +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"] }
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"
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"] }
# 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"]

View file

@ -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!" }
})
}
}
```

View file

@ -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."
}
}
))
)
}

View file

@ -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"
}
})
}

View file

@ -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 {
}
}
}
))
}
}

View file

@ -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%);
}

View 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}
}
}

View file

@ -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}"}
})
}

View file

@ -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 {

View file

@ -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 {

View file

@ -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!" }
})
}

View file

@ -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}" }
})
}
}

View file

@ -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}" }
}
})
}
}

View file

@ -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 {
}
}
}
))
}
}

View file

@ -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"
}
}
}

View file

@ -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" }
}
}

View file

@ -1,16 +1,20 @@
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));
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() }
}
})
}
}

View file

@ -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!" } }
}
}

View file

@ -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" }
}
})
}
}

View file

@ -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,
}

View file

@ -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!");
}
}

View file

@ -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" }
}

View file

@ -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}" }
}
}

View file

@ -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.." } ),
}
}

View file

@ -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"
}
}
})
}

View file

@ -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 {

View file

@ -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}" }
}
}
})
}
}

View 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" } }
)
}

View file

@ -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;
}
"#;

View file

@ -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" }
}
}
})
}
}

View file

@ -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",
];

View file

@ -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
View 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!" }
}
}

View file

@ -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!" }
})
}

View file

@ -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!" }
}
}

View file

@ -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" }
}
})
}
}

View file

@ -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 },
}
})
}

View file

@ -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"
}
}})
}
}

View file

@ -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" } )
}

View file

@ -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
View 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 }
}
}

View file

@ -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"]

View file

@ -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);

View file

@ -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!" }
}
}

View file

@ -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"
}
}
})
}
}

View file

@ -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() {

View file

@ -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 {

View file

@ -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> {}
}
}

View file

@ -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" } }
}

View file

@ -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) {

View file

@ -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("")}
}

View file

@ -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:?}"
}
})
}
}

View file

@ -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#"

View file

@ -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);
}

View file

@ -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"

View file

@ -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" }
)
}

View file

@ -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!" }
})
}
}

View file

@ -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,

View file

@ -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:?}" }
}
}

View file

@ -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,
}

View file

@ -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}" }
// }
// }

View file

@ -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"
}
}
))
}
}

View file

@ -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)?
}
}
}

View file

@ -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}")
}

View file

@ -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(()) }
}
}

View file

@ -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}" }
}
}

View file

@ -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!")
}

View file

@ -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!"
}
}
))
)
}

View file

@ -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> {} });
}

View file

@ -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}" }
}
}

View file

@ -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
View 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"
}
}
}

View file

@ -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>>> {

View file

@ -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..." } },
})
}
}

View file

@ -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}
}
})
}
}

View file

@ -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);
}

View file

@ -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"] }

View file

@ -5,33 +5,31 @@ use dioxus::prelude::*;
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" }
}
))
)
}

View file

@ -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" }
}
})
}
}

View file

@ -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()),
}
})
}
}

View file

@ -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" }
}
}
})
}
}

View file

@ -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",

View file

@ -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%",
}
})
}
}

View file

@ -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"
}
}
}
}
))
)
}

View file

@ -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!"
}
}
})
}
}

View file

@ -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);
}
}
}
})
}
}

View file

@ -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()),
}
}
})
}
}

View file

@ -1,4 +1,4 @@
fn it_works() {
cx.render(rsx!({()}))
rsx!({()})
}

View file

@ -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}
}
})
}
}

View file

@ -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" }
})
}
}

View file

@ -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" }
}
})
}
};

View file

@ -1,7 +1,7 @@
fn it_works() {
cx.render(rsx! {
rsx! {
div {
span { "Description: ", {package.description.as_deref().unwrap_or("❌❌❌❌ missing")} }
}
})
}
}

View file

@ -1,3 +1,3 @@
fn app(cx: Scope) -> Element {
cx.render(rsx! { div { "hello world" } })
fn app() -> Element {
rsx! { div { "hello world" } }
}

View file

@ -1,5 +1,5 @@
fn app(cx: Scope) -> Element {
cx.render(rsx! {
fn app() -> Element {
rsx! {
div {"hello world" }
})
}
}

View file

@ -1,3 +1,3 @@
fn app(cx: Scope) -> Element {
cx.render(rsx! { div { "hello world" } })
fn app() -> Element {
rsx! { div { "hello world" } }
}

View file

@ -1,5 +1,5 @@
fn app(cx: Scope) -> Element {
cx.render(rsx! {
fn app() -> Element {
rsx! {
div {"hello world" }
})
}
}

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