diff --git a/.github/workflows/deploy_docs.yml b/.github/workflows/deploy_docs.yml
index 984b4617d..fea1e7590 100644
--- a/.github/workflows/deploy_docs.yml
+++ b/.github/workflows/deploy_docs.yml
@@ -20,7 +20,7 @@ jobs:
- name: Set up cargo cache
uses: Swatinem/rust-cache@v2
with:
- prefix-key: "2"
+ prefix-key: "3"
- name: cargo doc
run: cargo doc --no-deps --workspace
@@ -30,4 +30,4 @@ jobs:
with:
project: freya-docs
entrypoint: https://deno.land/std/http/file_server.ts
- root: target/doc
\ No newline at end of file
+ root: target/doc
diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml
index 8162790a8..c20a5e4cc 100644
--- a/.github/workflows/rust.yml
+++ b/.github/workflows/rust.yml
@@ -25,7 +25,7 @@ jobs:
- name: Set up cargo cache
uses: Swatinem/rust-cache@v2
with:
- prefix-key: "5"
+ prefix-key: "7"
- name: Install linux dependencies
if: runner.os == 'Linux'
run: |
@@ -51,7 +51,7 @@ jobs:
rustup component add llvm-tools-preview
curl -L https://github.com/mozilla/grcov/releases/latest/download/grcov-x86_64-unknown-linux-gnu.tar.bz2 | tar jxf -
./grcov . --binary-path ./target/debug/deps -s . -t lcov --branch --ignore-not-existing --ignore "../*" --ignore "/*" -o cov.lcov
- - uses: codecov/codecov-action@v3
+ - uses: codecov/codecov-action@v4
if: runner.os == 'Linux'
with:
token: ${{ secrets.CODECOV_TOKEN }}
diff --git a/Cargo.toml b/Cargo.toml
index c54e543ed..797a46e9c 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -26,18 +26,19 @@ freya-testing = { path = "crates/testing", version = "0.1" }
freya-engine = { path = "crates/engine", version = "0.1" }
torin = { path = "crates/torin", version = "0.1" }
-dioxus = { git = "https://github.com/DioxusLabs/dioxus", rev = "53380c9956c7dda54d9251d3bc48eaa0ec4e89be", default-features = false, features = ["macro", "signals", "hooks", "hot-reload", "html"]}
-dioxus-native-core-macro = { git = "https://github.com/DioxusLabs/dioxus", rev = "53380c9956c7dda54d9251d3bc48eaa0ec4e89be" }
-dioxus-rsx = { git = "https://github.com/DioxusLabs/dioxus", rev = "53380c9956c7dda54d9251d3bc48eaa0ec4e89be", features = ["hot_reload"] }
-dioxus-native-core = { git = "https://github.com/DioxusLabs/dioxus", rev = "53380c9956c7dda54d9251d3bc48eaa0ec4e89be", features = ["dioxus"] }
-dioxus-core-macro = { git = "https://github.com/DioxusLabs/dioxus", rev = "53380c9956c7dda54d9251d3bc48eaa0ec4e89be" }
-dioxus-hooks = { git = "https://github.com/DioxusLabs/dioxus", rev = "53380c9956c7dda54d9251d3bc48eaa0ec4e89be" }
-dioxus-signals = { git = "https://github.com/DioxusLabs/dioxus", rev = "53380c9956c7dda54d9251d3bc48eaa0ec4e89be" }
-dioxus-core = { git = "https://github.com/DioxusLabs/dioxus", rev = "53380c9956c7dda54d9251d3bc48eaa0ec4e89be" }
-dioxus-hot-reload = { git = "https://github.com/DioxusLabs/dioxus", rev = "53380c9956c7dda54d9251d3bc48eaa0ec4e89be", features = ["file_watcher"], default-features = false }
-dioxus-router = { git = "https://github.com/DioxusLabs/dioxus", rev = "53380c9956c7dda54d9251d3bc48eaa0ec4e89be", default-features = false }
+dioxus = { git = "https://github.com/DioxusLabs/dioxus", rev = "a454f2c7ac239933bb4d5cd184c8a549190070f1", default-features = false, features = ["macro", "signals", "hooks", "hot-reload", "html"]}
+dioxus-native-core-macro = { git = "https://github.com/DioxusLabs/dioxus", rev = "a454f2c7ac239933bb4d5cd184c8a549190070f1" }
+dioxus-rsx = { git = "https://github.com/DioxusLabs/dioxus", rev = "a454f2c7ac239933bb4d5cd184c8a549190070f1", features = ["hot_reload"] }
+dioxus-native-core = { git = "https://github.com/DioxusLabs/dioxus", rev = "a454f2c7ac239933bb4d5cd184c8a549190070f1", features = ["dioxus"] }
+dioxus-core-macro = { git = "https://github.com/DioxusLabs/dioxus", rev = "a454f2c7ac239933bb4d5cd184c8a549190070f1" }
+dioxus-hooks = { git = "https://github.com/DioxusLabs/dioxus", rev = "a454f2c7ac239933bb4d5cd184c8a549190070f1" }
+dioxus-signals = { git = "https://github.com/DioxusLabs/dioxus", rev = "a454f2c7ac239933bb4d5cd184c8a549190070f1" }
+dioxus-core = { git = "https://github.com/DioxusLabs/dioxus", rev = "a454f2c7ac239933bb4d5cd184c8a549190070f1" }
+dioxus-hot-reload = { git = "https://github.com/DioxusLabs/dioxus", rev = "a454f2c7ac239933bb4d5cd184c8a549190070f1", features = ["file_watcher"], default-features = false }
+dioxus-router = { git = "https://github.com/DioxusLabs/dioxus", rev = "a454f2c7ac239933bb4d5cd184c8a549190070f1", default-features = false }
+dioxus-std = { git = "https://github.com/marc2332/dioxus-std", rev = "2e111cda95f816fe4232bae5e75515a58afa4a1e", features = ["clipboard"] }
-skia-safe = { version = "0.67.0", features = ["gl", "textlayout", "svg"] }
+skia-safe = { version = "0.70.0", features = ["gl", "textlayout", "svg"] }
gl = "0.14.0"
glutin = "0.31.2"
@@ -47,12 +48,11 @@ winit = "0.29.9"
tokio = { version = "1.33.0", features = ["sync", "rt-multi-thread", "time", "macros"] }
accesskit = { version = "0.12.2", features = ["serde"]}
accesskit_winit = "0.18.0"
-zbus = "3.14.1"
euclid = "0.22.9"
uuid = { version = "1.4.1", features = ["v4"]}
-futures = "0.3.28"
-anymap = "0.12.1"
+futures-util = "0.3.30"
+futures-task = "0.3.30"
tracing = "0.1"
tracing-subscriber = "0.3.17"
rustc-hash = "1.1.0"
@@ -69,7 +69,7 @@ freya-core = { workspace = true }
reqwest = { version = "0.11.22", features = ["json"] }
serde = "1.0.189"
tracing-subscriber = "0.3.17"
-dioxus-std = { git = "https://github.com/DioxusLabs/dioxus-std", rev = "137b4149dc86a648119eef8f331e3a682c2c6b62", features = ["i18n"] }
+dioxus-std = { git = "https://github.com/marc2332/dioxus-std", rev = "2e111cda95f816fe4232bae5e75515a58afa4a1e", features = ["i18n"] }
rand = "0.8.5"
dioxus-router = { workspace = true }
itertools = "0.11.0"
diff --git a/README.md b/README.md
index bc49e1b2f..98902c637 100644
--- a/README.md
+++ b/README.md
@@ -20,10 +20,10 @@
```rust, no_run
-fn app() -> Element {
- let mut count = use_signal(|| 0);
+fn app(cx: Scope) -> Element {
+ let mut count = use_state(cx, || 0);
- rsx!(
+ render!(
rect {
height: "20%",
width: "100%",
@@ -59,10 +59,12 @@ fn app() -> Element {
Thanks to my sponsors for supporting this project! 😄
-![Alberto Mendez](https://github.com/piny4man.png)
+![Alberto Mendez](https://github.com/piny4man.png)
### Want to try it? 🤔
+Note: `main` branch currently depends on Dioxus 0.5.
+
⚠️ First, see [Environment setup](https://book.freyaui.dev/setup.html).
Clone this repo and run:
diff --git a/book/src/guides/animating.md b/book/src/guides/animating.md
index 08e24d37a..9afdf8808 100644
--- a/book/src/guides/animating.md
+++ b/book/src/guides/animating.md
@@ -18,16 +18,16 @@ fn main() {
launch(app);
}
- fn app() -> Element {
- let mut animation = use_animation(|| 0.0);
+ fn app(cx: Scope) -> Element {
+ let animation = use_animation(cx, || 0.0);
let progress = animation.value();
- use_hook(move || {
+ use_memo(cx, (), move |_| {
animation.start(Animation::new_linear(0.0..=100.0, 50));
- })
+ });
- rsx!(rect {
+ render!(rect {
width: "{progress}",
})
}
@@ -53,8 +53,8 @@ fn main() {
const TARGET: f64 = 500.0;
-fn app() -> Element {
- let mut animation = use_animation_transition(TransitionAnimation::new_sine_in_out(200), (), || {
+fn app(cx: Scope) -> Element {
+ let animation = use_animation_transition(cx, TransitionAnimation::new_sine_in_out(200), (), || {
vec![
Animate::new_size(0.0, TARGET),
Animate::new_color("rgb(33, 158, 188)", "white"),
@@ -72,7 +72,7 @@ fn app() -> Element {
}
};
- rsx!(
+ render!(
rect {
overflow: "clip",
background: "black",
@@ -83,7 +83,7 @@ fn app() -> Element {
height: "100%",
width: "200",
background: "{background}",
- onclick,
+ onclick: onclick,
}
}
)
diff --git a/book/src/guides/effects.md b/book/src/guides/effects.md
index 5f0c9703a..494fba317 100644
--- a/book/src/guides/effects.md
+++ b/book/src/guides/effects.md
@@ -11,8 +11,8 @@ The `rotate` attribute let's you rotate an element.
Example:
```rust, no_run
-fn app() -> Element {
- rsx!(
+fn app(cx: Scope) -> Element {
+ render!(
label {
rotate: "180deg",
"Hello, World!"
diff --git a/book/src/guides/elements.md b/book/src/guides/elements.md
index 660c50e20..eb7afd058 100644
--- a/book/src/guides/elements.md
+++ b/book/src/guides/elements.md
@@ -16,8 +16,8 @@ You can specify things like [`width`](/guides/layout.html#width), [`paddings`](/
Example:
```rust, no_run
-fn app() -> Element {
- rsx!(
+fn app(cx: Scope) -> Element {
+ render!(
rect {
direction: "vertical",
label { "Hi!" }
@@ -34,8 +34,8 @@ The `label` element simply shows some text.
Example:
```rust, no_run
-fn app() -> Element {
- rsx!(
+fn app(cx: Scope) -> Element {
+ render!(
label {
"Hello World"
}
@@ -53,9 +53,9 @@ Example:
static FERRIS: &[u8] = include_bytes!("./ferris.svg");
-fn app() -> Element {
- let ferris = bytes_to_data(FERRIS);
- rsx!(
+fn app(cx: Scope) -> Element {
+ let ferris = bytes_to_data(cx, FERRIS);
+ render!(
svg {
svg_data: ferris,
}
@@ -70,9 +70,9 @@ The `image` element, just like `svg` element, require you to pass the image byte
```rust, no_run
static RUST_LOGO: &[u8] = include_bytes!("./rust_logo.png");
-fn app() -> Element {
- let image_data = bytes_to_data(RUST_LOGO);
- rsx!(
+fn app(cx: Scope) -> Element {
+ let image_data = bytes_to_data(cx, RUST_LOGO);
+ render!(
image {
image_data: image_data,
width: "{size}",
@@ -87,8 +87,8 @@ fn app() -> Element {
Both `paragraph` and `text` elements are used together. They will let you build texts with different styles.
``` rust
-fn app() -> Element {
- rsx!(
+fn app(cx: Scope) -> Element {
+ render!(
paragraph {
text {
font_size: "15",
diff --git a/book/src/guides/font_style.md b/book/src/guides/font_style.md
index d63cf10dd..9d889b1e9 100644
--- a/book/src/guides/font_style.md
+++ b/book/src/guides/font_style.md
@@ -29,8 +29,8 @@ You can learn about the syntax of this attribute in [`Color Syntax`](/guides/sty
Example:
```rust, no_run
-fn app() -> Element {
- rsx!(
+fn app(cx: Scope) -> Element {
+ render!(
label {
color: "green",
"Hello, World!"
@@ -42,8 +42,8 @@ fn app() -> Element {
Another example showing [inheritance](#inheritance):
```rust, no_run
-fn app() -> Element {
- rsx!(
+fn app(cx: Scope) -> Element {
+ render!(
rect {
color: "blue",
label {
@@ -66,8 +66,8 @@ Limitation: Only fonts installed in the system are supported for now.
Example:
```rust, no_run
-fn app() -> Element {
- rsx!(
+fn app(cx: Scope) -> Element {
+ render!(
label {
font_family: "Inter",
"Hello, World!"
@@ -85,8 +85,8 @@ You can specify the size of the text using `font_size`.
Example:
```rust, no_run
-fn app() -> Element {
- rsx!(
+fn app(cx: Scope) -> Element {
+ render!(
label {
font_size: "50",
"Hellooooo!"
@@ -106,8 +106,8 @@ Accepted values: `center`, `end`, `justify`, `left`, `right`, `start`
Example
```rust, no_run
-fn app() -> Element {
- rsx!(
+fn app(cx: Scope) -> Element {
+ render!(
label {
align: "right",
"Hello, World!"
@@ -127,8 +127,8 @@ Accepted values: `upright` (default), `italic` and `oblique`.
Example:
```rust, no_run
-fn app() -> Element {
- rsx!(
+fn app(cx: Scope) -> Element {
+ render!(
label {
font_style: "italic",
"Hello, World!"
@@ -170,8 +170,8 @@ Accepted values:
Example:
```rust, no_run
-fn app() -> Element {
- rsx!(
+fn app(cx: Scope) -> Element {
+ render!(
label {
font_weight: "bold",
"Hello, World!"
@@ -199,8 +199,8 @@ Accepted values:
Example:
```rust, no_run
-fn app() -> Element {
- rsx!(
+fn app(cx: Scope) -> Element {
+ render!(
label {
font_weight: "bold",
"Hello, World!"
@@ -220,8 +220,8 @@ Specify the height of the lines of the text.
Example:
```rust, no_run
-fn app() -> Element {
- rsx!(
+fn app(cx: Scope) -> Element {
+ render!(
label {
lines_height: "3",
"Hello, World! \n Hello, again!"
@@ -237,13 +237,13 @@ Determines the amount of lines that the text can have. It has unlimited lines by
Example:
```rust, no_run
-fn app() -> Element {
- rsx!(
+fn app(cx: Scope) -> Element {
+ render!(
label {
"Hello, World! \n Hello, World! \n Hello, world!" // Will show all three lines
}
label {
- lines_height: "2",
+ max_lines: "2",
"Hello, World! \n Hello, World! \n Hello, world!" // Will only show two lines
}
)
@@ -259,8 +259,8 @@ Specify the spacing between characters of the text.
Example:
```rust, no_run
-fn app() -> Element {
- rsx!(
+fn app(cx: Scope) -> Element {
+ render!(
label {
letter_spacing: "10",
"Hello, World!"
@@ -278,8 +278,8 @@ Specify the spacing between words of the text.
Example:
```rust, no_run
-fn app() -> Element {
- rsx!(
+fn app(cx: Scope) -> Element {
+ render!(
label {
word_spacing: "10",
"Hello, World!"
@@ -302,8 +302,8 @@ Accpted values:
Example:
```rust, no_run
-fn app() -> Element {
- rsx!(
+fn app(cx: Scope) -> Element {
+ render!(
label {
decoration: "line-through",
"Hello, World!"
@@ -328,8 +328,8 @@ Accpted values:
Example:
```rust, no_run
-fn app() -> Element {
- rsx!(
+fn app(cx: Scope) -> Element {
+ render!(
label {
decoration: "line-through",
decoration_style: "dotted",
@@ -350,8 +350,8 @@ You can learn about the syntax of this attribute in [`Color Syntax`](/guides/sty
Example:
```rust, no_run
-fn app() -> Element {
- rsx!(
+fn app(cx: Scope) -> Element {
+ render!(
label {
decoration: "line-through",
decoration_color: "orange",
@@ -372,8 +372,8 @@ Syntax: ` `
Example:
```rust, no_run
-fn app() -> Element {
- rsx!(
+fn app(cx: Scope) -> Element {
+ render!(
label {
text_shadow: "0 18 12 rgb(0, 0, 0)",
"Hello, World!"
@@ -395,8 +395,8 @@ Accepted values:
Example:
```rust, no_run
-fn app() -> Element {
- rsx!(
+fn app(cx: Scope) -> Element {
+ render!(
label {
max_lines: "3",
text_overflow: "ellipsis",
diff --git a/book/src/guides/getting_started.md b/book/src/guides/getting_started.md
index 3a303f5e9..3ce82c631 100644
--- a/book/src/guides/getting_started.md
+++ b/book/src/guides/getting_started.md
@@ -43,10 +43,10 @@ fn main() {
launch(app);
}
-fn app() -> Element {
- let mut count = use_signal(|| 0);
+fn app(cx: Scope) -> Element {
+ let mut count = use_state(cx, || 0);
- rsx!(
+ render!(
rect {
height: "100%",
width: "100%",
diff --git a/book/src/guides/hot_reload.md b/book/src/guides/hot_reload.md
index a463f7bde..1d61f57dc 100644
--- a/book/src/guides/hot_reload.md
+++ b/book/src/guides/hot_reload.md
@@ -8,7 +8,7 @@ Before launching your app, you need to initialize the hot-reload context:
```rust, no_run
use freya::prelude::*;
-use freya::hot_reload::FreyaCtx;
+use freya::hotreload::FreyaCtx;
fn main() {
dioxus_hot_reload::hot_reload_init!(Config::::default());
diff --git a/book/src/guides/layout.md b/book/src/guides/layout.md
index bc5de08b3..ea49c7752 100644
--- a/book/src/guides/layout.md
+++ b/book/src/guides/layout.md
@@ -24,8 +24,8 @@ See syntax for [`Size Units`](#size-units).
##### Usage
```rust, no_run
-fn app() -> Element {
- rsx!(
+fn app(cx: Scope) -> Element {
+ render!(
rect {
background: "red",
width: "15",
@@ -44,8 +44,8 @@ See syntax for [`Size Units`](#size-units).
##### Usage
```rust, no_run
-fn app() -> Element {
- rsx!(
+fn app(cx: Scope) -> Element {
+ render!(
rect {
background: "red",
min_width: "100",
@@ -66,8 +66,8 @@ See syntax for [`Size Units`](#size-units).
##### Usage
```rust, no_run
-fn app() -> Element {
- rsx!(
+fn app(cx: Scope) -> Element {
+ render!(
rect {
background: "red",
max_width: "50%",
@@ -85,8 +85,8 @@ fn app() -> Element {
Will use it's inner children as size, so in this case, the `rect` width will be equivalent to the width of `label`:
```rust, no_run
-fn app() -> Element {
- rsx!(
+fn app(cx: Scope) -> Element {
+ render!(
rect {
width: "auto",
height: "33",
@@ -101,8 +101,8 @@ fn app() -> Element {
#### Logical pixels
```rust, no_run
-fn app() -> Element {
- rsx!(
+fn app(cx: Scope) -> Element {
+ render!(
rect {
width: "50",
height: "33"
@@ -115,8 +115,8 @@ fn app() -> Element {
Relative percentage to the parent equivalent value.
```rust, no_run
-fn app() -> Element {
- rsx!(
+fn app(cx: Scope) -> Element {
+ render!(
rect {
width: "50%", // Half the parent
height: "75%" // 3/4 the parent
@@ -130,8 +130,8 @@ fn app() -> Element {
For more complex logic you can use the `calc()` function.
```rust, no_run
-fn app() -> Element {
- rsx!(
+fn app(cx: Scope) -> Element {
+ render!(
rect {
width: "calc(33% - 60 + 15%)", // (1/3 of the parent minus 60) plus 15% of parent
height: "calc(100% - 10)" // 100% of the parent minus 10
@@ -147,8 +147,8 @@ Control how the inner elements will be stacked, possible values are `horizontal`
##### Usage
```rust, no_run
-fn app() -> Element {
- rsx!(
+fn app(cx: Scope) -> Element {
+ render!(
rect {
width: "100%",
height: "100%",
@@ -173,8 +173,8 @@ fn app() -> Element {
Specify the inner paddings of an element. You can do so by three different ways.
```rust, no_run
-fn app() -> Element {
- rsx!(
+fn app(cx: Scope) -> Element {
+ render!(
rect {
padding: "25" // 25 in all sides
padding: "100 50" // 100 in top and bottom, and 50 in left and right
@@ -190,8 +190,8 @@ fn app() -> Element {
Control how the inner elements are displayed, possible values are `normal` (default) or `center`.
```rust, no_run
-fn app() -> Element {
- rsx!(
+fn app(cx: Scope) -> Element {
+ render!(
rect {
width: "100%",
height: "100%",
@@ -212,8 +212,8 @@ fn app() -> Element {
Specify the margin of an element. You can do so by three different ways.
```rust, no_run
-fn app() -> Element {
- rsx!(
+fn app(cx: Scope) -> Element {
+ render!(
rect {
margin: "25" // 25 in all sides
margin: "100 50" // 100 in top and bottom, and 50 in left and right
diff --git a/book/src/guides/style.md b/book/src/guides/style.md
index ce9773c04..2053e54de 100644
--- a/book/src/guides/style.md
+++ b/book/src/guides/style.md
@@ -21,8 +21,8 @@ You can learn about the syntax of this attribute [here](#color-syntax).
Example:
```rust, no_run
-fn app() -> Element {
- rsx!(
+fn app(cx: Scope) -> Element {
+ render!(
rect {
background: "red"
}
@@ -42,8 +42,8 @@ Syntax: ` `
Example:
```rust, no_run
-fn app() -> Element {
- rsx!(
+fn app(cx: Scope) -> Element {
+ render!(
rect {
shadow: "0 0 25 2 rgb(0, 0, 0, 120)"
}
@@ -60,8 +60,8 @@ The `corner_radius` attribute let's you smooth the corners of the element, with
Example:
```rust, no_run
-fn app() -> Element {
- rsx!(
+fn app(cx: Scope) -> Element {
+ render!(
rect {
corner_radius: "10",
corner_smoothing: "75%"
@@ -82,8 +82,8 @@ You can add a border to an element using the `border` and `border_align` attribu
Example:
```rust, no_run
-fn app() -> Element {
- rsx!(
+fn app(cx: Scope) -> Element {
+ render!(
rect {
border: "2 solid black",
border_align: "inner"
@@ -103,8 +103,8 @@ Accepted values: `clip | none`.
Example:
```rust, no_run
-fn app() -> Element {
- rsx!(
+fn app(cx: Scope) -> Element {
+ render!(
rect {
overflow: "clip"
width: "100",
diff --git a/book/src/guides/testing.md b/book/src/guides/testing.md
index 296480191..0cf151773 100644
--- a/book/src/guides/testing.md
+++ b/book/src/guides/testing.md
@@ -12,8 +12,8 @@ For example, this will launch a state-less component and assert that it renders
```rust, no_run
#[tokio::test]
async fn test() {
- fn our_component() -> Element {
- rsx!(
+ fn our_component(cx: Scope) -> Element {
+ render!(
label {
"Hello World!"
}
@@ -41,12 +41,15 @@ Here, the component has a state that is `false` by default, but, once mounted it
```rust, no_run
#[tokio::test]
async fn dynamic_test() {
- fn dynamic_component() -> Element {
- let mut state = use_signal(|| false);
+ fn dynamic_component(cx: Scope) -> Element {
+ let state = use_state(cx, || false);
- use_hook(move || state.set(true));
+ use_effect(cx, (), |_| {
+ state.set(true);
+ async move { }
+ });
- rsx!(
+ render!(
label {
"Is enabled? {state}"
}
@@ -60,7 +63,7 @@ async fn dynamic_test() {
assert_eq!(label.get(0).text(), Some("Is enabled? false"));
- // This will run the `use_hook` and update the state.
+ // This will run the `use_effect` and update the state.
utils.wait_for_update().await;
assert_eq!(label.get(0).text(), Some("Is enabled? true"));
@@ -74,14 +77,14 @@ We can also simulate events on the component, for example, we can simulate a cli
```rust, no_run
#[tokio::test]
async fn event_test() {
- fn event_component() -> Element {
- let mut enabled = use_signal(|| false);
- rsx!(
+ fn event_component(cx: Scope) -> Element {
+ let enabled = use_state(cx, || false);
+ render!(
rect {
width: "100%",
height: "100%",
background: "red",
- onclick: move |_| {
+ onclick: |_| {
enabled.set(true);
},
label {
@@ -126,8 +129,8 @@ Here is an example of how to can set our custom window size:
```rust, no_run
#[tokio::test]
async fn test() {
- fn our_component() -> Element {
- rsx!(
+ fn our_component(cx: Scope) -> Element {
+ render!(
label {
"Hello World!"
}
diff --git a/book/src/guides/theming.md b/book/src/guides/theming.md
index a172b218f..e192dc95f 100644
--- a/book/src/guides/theming.md
+++ b/book/src/guides/theming.md
@@ -9,8 +9,8 @@ Freya has built-in support for Theming.
You can access the whole current theme via the `use_get_theme` hook.
```rust, no_run
-fn app() -> Element {
- rsx!(
+fn app(cx: Scope) -> Element {
+ render!(
ThemeProvider {
Component { }
}
@@ -18,12 +18,12 @@ fn app() -> Element {
}
#[allow(non_snake_case)]
-fn Component() -> Element {
- let theme = use_get_theme();
+fn Component(cx: Scope) -> Element {
+ let theme = use_get_theme(cx);
let button_theme = &theme.button;
- rsx!(
+ render!(
rect {
background: "{button_theme.background}",
}
@@ -35,8 +35,8 @@ fn Component() -> Element {
By default, the selected theme is `LIGHT_THEME`. You can use the alternative, `DARK_THEME`.
```rust, no_run
-fn app() -> Element {
- rsx!(
+fn app(cx: Scope) -> Element {
+ render!(
ThemeProvider {
theme: LIGHT_THEME,
Component { }
@@ -45,12 +45,12 @@ fn app() -> Element {
}
#[allow(non_snake_case)]
-fn Component() -> Element {
- let theme = use_get_theme();
+fn Component(cx: Scope) -> Element {
+ let theme = use_get_theme(cx);
let button_theme = &theme.button;
- rsx!(
+ render!(
rect {
background: "{button_theme.background}",
}
@@ -63,8 +63,8 @@ fn Component() -> Element {
Changing the selected theme at runtime is possible by using the `use_theme` hook.
```rust, no_run
-fn app() -> Element {
- rsx!(
+fn app(cx: Scope) -> Element {
+ render!(
ThemeProvider {
Component { }
}
@@ -72,16 +72,16 @@ fn app() -> Element {
}
#[allow(non_snake_case)]
-fn Component() -> Element {
- let mut theme = use_theme();
+fn Component(cx: Scope) -> Element {
+ let theme = use_theme(cx);
- let onclick = move |_| {
+ let onclick = |_| {
*theme.write() = LIGHT_THEME;
};
- rsx!(
+ render!(
Button {
- onclick,
+ onclick: onclick,
label {
"Use Light theme"
}
@@ -104,8 +104,8 @@ const CUSTOM_THEME: Theme = Theme {
..LIGHT_THEME
};
-fn app() -> Element {
- rsx!(
+fn app(cx: Scope) -> Element {
+ render!(
ThemeProvider {
theme: CUSTOM_THEME,
rect {
diff --git a/book/src/guides/virtualizing.md b/book/src/guides/virtualizing.md
index 5de8fd9f0..9a22020da 100644
--- a/book/src/guides/virtualizing.md
+++ b/book/src/guides/virtualizing.md
@@ -19,19 +19,19 @@ fn main() {
launch(app);
}
-fn app() -> Element {
- let values = use_signal(|| vec!["Hello World"].repeat(400));
+fn app(cx: Scope) -> Element {
+ let values = use_state(cx, || vec!["Hello World"].repeat(400));
- rsx!(
+ render!(
VirtualScrollView {
width: "100%",
height: "100%",
show_scrollbar: true,
direction: "vertical",
- length: values.read().len(),
+ length: values.get().len(),
item_size: 25.0,
- builder_values: values.read().clone(),
- builder: Rc::new(move |(key, index, values)| {
+ builder_values: values.get(),
+ builder: Box::new(move |(key, index, _cx, values)| {
let values = values.unwrap();
let value = values[index];
rsx! {
@@ -72,4 +72,4 @@ Used to calculate how many elements can be fit in the viewport.
Any data that you might need in the `builder` function
#### `builder`
-This is a function that dinamically creates an element for the given index in the list. It receives 4 arguments, a `key` for the element, the `index` of the element and the `builder_values` you previously passed.
\ No newline at end of file
+This is a function that dinamically creates an element for the given index in the list. It receives 4 arguments, a `key` for the element, the `index` of the element, the Scope (`cx`) and the `builder_values` you previously passed.
\ No newline at end of file
diff --git a/book/src/index.md b/book/src/index.md
index 1d352c2cc..6eea72d1d 100644
--- a/book/src/index.md
+++ b/book/src/index.md
@@ -16,10 +16,10 @@
```rust, no_run
-fn app() -> Element {
- let mut count = use_signal(|| 0);
+fn app(cx: Scope) -> Element {
+ let mut count = use_state(cx, || 0);
- rsx!(
+ render!(
rect {
height: "20%",
width: "100%",
diff --git a/crates/common/Cargo.toml b/crates/common/Cargo.toml
index fbb139936..f73056ed4 100644
--- a/crates/common/Cargo.toml
+++ b/crates/common/Cargo.toml
@@ -14,15 +14,10 @@ categories = ["gui", "asynchronous"]
[dependencies]
torin = { workspace = true }
-dioxus-rsx = { workspace = true }
-dioxus-native-core = { workspace = true }
-dioxus-core-macro = { workspace = true }
-dioxus-hooks = { workspace = true }
dioxus-core = { workspace = true }
accesskit = { workspace = true }
accesskit_winit = { workspace = true }
winit = { workspace = true }
-euclid = { workspace = true }
uuid = { workspace = true }
diff --git a/crates/common/src/event_messages.rs b/crates/common/src/event_messages.rs
index bc4ac3d76..d4ab3479d 100644
--- a/crates/common/src/event_messages.rs
+++ b/crates/common/src/event_messages.rs
@@ -11,12 +11,8 @@ pub enum EventMessage {
UpdateTemplate(Template),
/// Pull the VirtualDOM
PollVDOM,
- /// Request a layout recalculation
- RequestRelayout,
/// Request a rerender
RequestRerender,
- /// Request a redraw
- RequestRedraw,
/// Remeasure a text elements group
RemeasureTextGroup(Uuid),
/// Change the cursor icon
diff --git a/crates/components/Cargo.toml b/crates/components/Cargo.toml
index 7d7ba7c3d..9f0e8221f 100644
--- a/crates/components/Cargo.toml
+++ b/crates/components/Cargo.toml
@@ -22,13 +22,12 @@ freya-elements = { workspace = true }
freya-node-state = { workspace = true }
freya-hooks = { workspace = true }
freya-common = { workspace = true }
-freya-core = { path = "../core", version = "0.1" }
freya-engine = { path = "../engine", version = "0.1" }
torin = { workspace = true }
-dioxus = { workspace = true }
dioxus-router = { workspace = true }
-futures = { workspace = true }
+dioxus = { workspace = true }
+futures-util = { workspace = true }
winit = { workspace = true }
tokio = { workspace = true }
@@ -39,4 +38,4 @@ reqwest = { version = "0.11.22", features = ["json"] }
[dev-dependencies]
freya = { path = "../freya" }
-freya-testing = { path = "../testing" }
\ No newline at end of file
+freya-testing = { path = "../testing" }
diff --git a/crates/components/src/accordion.rs b/crates/components/src/accordion.rs
index 6f79a70ad..23e7c241b 100644
--- a/crates/components/src/accordion.rs
+++ b/crates/components/src/accordion.rs
@@ -3,8 +3,8 @@ use freya_elements::elements as dioxus_elements;
use freya_elements::events::MouseEvent;
use freya_hooks::{
- use_animation, use_applied_theme, use_node_signal, use_platform, AccordionTheme,
- AccordionThemeWith, Animation,
+ use_animation_with_dependencies, use_applied_theme, use_node, use_platform, AccordionTheme,
+ AccordionThemeWith, AnimNum,
};
use winit::window::CursorIcon;
@@ -40,54 +40,40 @@ pub struct AccordionProps {
#[allow(non_snake_case)]
pub fn Accordion(props: AccordionProps) -> Element {
let theme = use_applied_theme!(&props.theme, accordion);
- let mut animation = use_animation(|| 0.0);
let mut open = use_signal(|| false);
- let (node_ref, size) = use_node_signal();
+ let (node_ref, size) = use_node();
+
+ let animation = use_animation_with_dependencies(&size.area.height(), move |ctx, height| {
+ ctx.with(AnimNum::new(0., height).time(200))
+ });
let mut status = use_signal(AccordionStatus::default);
let platform = use_platform();
- let animation_value = animation.value();
+ let animation_value = animation.read().get().read().as_f32();
let AccordionTheme {
background,
color,
border_fill,
} = theme;
- // Adapt the accordion if the body size changes
- use_memo({
- to_owned![animation];
- move || {
- if (size().area.height() as f64) < animation.value() && !animation.is_animating() {
- animation.set_value(size().area.height() as f64);
- }
- }
- });
-
let onclick = move |_: MouseEvent| {
- let bodyHeight = size.peek().area.height() as f64;
+ open.toggle();
if *open.read() {
- animation.start(Animation::new_sine_in_out(bodyHeight..=0.0, 200));
+ animation.read().start();
} else {
- animation.start(Animation::new_sine_in_out(0.0..=bodyHeight, 200));
+ animation.read().reverse();
}
- open.toggle();
};
- use_drop({
- to_owned![status, platform];
- move || {
- if *status.read() == AccordionStatus::Hovering {
- platform.set_cursor(CursorIcon::default());
- }
+ use_drop(move || {
+ if *status.read() == AccordionStatus::Hovering {
+ platform.set_cursor(CursorIcon::default());
}
});
- let onmouseenter = {
- to_owned![status, platform];
- move |_| {
- platform.set_cursor(CursorIcon::Pointer);
- status.set(AccordionStatus::Hovering);
- }
+ let onmouseenter = move |_| {
+ platform.set_cursor(CursorIcon::Pointer);
+ status.set(AccordionStatus::Hovering);
};
let onmouseleave = move |_| {
@@ -162,3 +148,53 @@ pub fn AccordionBody(props: AccordionBodyProps) -> Element {
{props.children}
})
}
+
+#[cfg(test)]
+mod test {
+ use freya::prelude::*;
+ use freya_testing::{launch_test, EventName, PlatformEvent};
+ use winit::event::MouseButton;
+
+ #[tokio::test]
+ pub async fn accordion() {
+ fn accordion_app() -> Element {
+ rsx!(
+ Accordion {
+ summary: rsx!(AccordionSummary {
+ label {
+ "Accordion Summary"
+ }
+ }),
+ AccordionBody {
+ label {
+ "Accordion Body"
+ }
+ }
+ }
+ )
+ }
+
+ let mut utils = launch_test(accordion_app);
+
+ let root = utils.root();
+ let content = root.get(0).get(1).get(0);
+ let label = content.get(0);
+ utils.wait_for_update().await;
+ utils.wait_for_update().await;
+
+ // Accordion is closed, therefore label is hidden.
+ assert!(!label.is_visible());
+
+ // Click on the accordion
+ utils.push_event(PlatformEvent::Mouse {
+ name: EventName::Click,
+ cursor: (5., 5.).into(),
+ button: Some(MouseButton::Left),
+ });
+
+ utils.wait_for_update().await;
+
+ // Accordion is open, therefore label is visible.
+ assert!(label.is_visible());
+ }
+}
diff --git a/crates/components/src/button.rs b/crates/components/src/button.rs
index 070062a46..2fea81fe2 100644
--- a/crates/components/src/button.rs
+++ b/crates/components/src/button.rs
@@ -52,7 +52,7 @@ pub enum ButtonStatus {
///
#[allow(non_snake_case)]
pub fn Button(props: ButtonProps) -> Element {
- let focus = use_focus();
+ let mut focus = use_focus();
let mut status = use_signal(ButtonStatus::default);
let platform = use_platform();
@@ -73,7 +73,7 @@ pub fn Button(props: ButtonProps) -> Element {
} = use_applied_theme!(&props.theme, button);
let onclick = {
- to_owned![focus, click];
+ to_owned![click];
move |ev| {
focus.focus();
if let Some(onclick) = &click {
@@ -82,21 +82,15 @@ pub fn Button(props: ButtonProps) -> Element {
}
};
- use_drop({
- to_owned![status, platform];
- move || {
- if *status.read() == ButtonStatus::Hovering {
- platform.set_cursor(CursorIcon::default());
- }
+ use_drop(move || {
+ if *status.read() == ButtonStatus::Hovering {
+ platform.set_cursor(CursorIcon::default());
}
});
- let onmouseenter = {
- to_owned![status, platform];
- move |_| {
- platform.set_cursor(CursorIcon::Pointer);
- status.set(ButtonStatus::Hovering);
- }
+ let onmouseenter = move |_| {
+ platform.set_cursor(CursorIcon::Pointer);
+ status.set(ButtonStatus::Hovering);
};
let onmouseleave = move |_| {
@@ -104,13 +98,10 @@ pub fn Button(props: ButtonProps) -> Element {
status.set(ButtonStatus::default());
};
- let onkeydown = {
- to_owned![focus];
- move |e: KeyboardEvent| {
- if focus.validate_keydown(e) {
- if let Some(onclick) = &props.onclick {
- onclick.call(None)
- }
+ let onkeydown = move |e: KeyboardEvent| {
+ if focus.validate_keydown(e) {
+ if let Some(onclick) = &props.onclick {
+ onclick.call(None)
}
}
};
@@ -151,3 +142,43 @@ pub fn Button(props: ButtonProps) -> Element {
}
)
}
+
+#[cfg(test)]
+mod test {
+ use dioxus::prelude::use_signal;
+ use freya::prelude::*;
+ use freya_testing::*;
+
+ #[tokio::test]
+ pub async fn button() {
+ fn button_app() -> Element {
+ let mut state = use_signal(|| false);
+
+ rsx!(
+ Button {
+ onclick: move |_| state.toggle(),
+ label {
+ "{state}"
+ }
+ }
+ )
+ }
+
+ let mut utils = launch_test(button_app);
+ let root = utils.root();
+ let label = root.get(0).get(0);
+ utils.wait_for_update().await;
+
+ assert_eq!(label.get(0).text(), Some("false"));
+
+ utils.push_event(PlatformEvent::Mouse {
+ name: EventName::Click,
+ cursor: (5.0, 5.0).into(),
+ button: Some(MouseButton::Left),
+ });
+
+ utils.wait_for_update().await;
+
+ assert_eq!(label.get(0).text(), Some("true"));
+ }
+}
diff --git a/crates/components/src/cursor_area.rs b/crates/components/src/cursor_area.rs
index 6ec97df05..2c4873565 100644
--- a/crates/components/src/cursor_area.rs
+++ b/crates/components/src/cursor_area.rs
@@ -1,7 +1,7 @@
use dioxus::prelude::*;
use freya_elements::elements as dioxus_elements;
use freya_hooks::use_platform;
-use winit::window::CursorIcon;
+pub use winit::window::CursorIcon;
/// [`CursorArea`] component properties.
#[derive(Props, Clone, PartialEq)]
@@ -39,22 +39,16 @@ pub struct CursorAreaProps {
#[allow(non_snake_case)]
pub fn CursorArea(CursorAreaProps { children, icon }: CursorAreaProps) -> Element {
let platform = use_platform();
- let is_hovering = use_signal(|| false);
+ let mut is_hovering = use_signal(|| false);
- let onmouseover = {
- to_owned![platform, is_hovering];
- move |_| {
- *is_hovering.write() = true;
- platform.set_cursor(icon);
- }
+ let onmouseover = move |_| {
+ *is_hovering.write() = true;
+ platform.set_cursor(icon);
};
- let onmouseleave = {
- to_owned![platform];
- move |_| {
- *is_hovering.write() = false;
- platform.set_cursor(CursorIcon::default());
- }
+ let onmouseleave = move |_| {
+ *is_hovering.write() = false;
+ platform.set_cursor(CursorIcon::default());
};
use_drop(move || {
@@ -71,3 +65,70 @@ pub fn CursorArea(CursorAreaProps { children, icon }: CursorAreaProps) -> Elemen
}
)
}
+
+#[cfg(test)]
+mod test {
+ use freya::prelude::*;
+ use freya_testing::*;
+ use winit::{event::MouseButton, window::CursorIcon};
+
+ #[tokio::test]
+ pub async fn cursor_area() {
+ fn cursor_area_app() -> Element {
+ rsx!(
+ CursorArea {
+ icon: CursorIcon::Progress,
+ rect {
+ height: "50%",
+ width: "100%",
+ }
+ }
+ CursorArea {
+ icon: CursorIcon::Pointer,
+ rect {
+ height: "50%",
+ width: "100%",
+ }
+ }
+ )
+ }
+
+ let mut utils = launch_test(cursor_area_app);
+
+ // Initial cursor
+ assert_eq!(utils.cursor_icon(), CursorIcon::default());
+
+ utils.push_event(PlatformEvent::Mouse {
+ name: EventName::MouseOver,
+ cursor: (100., 100.).into(),
+ button: Some(MouseButton::Left),
+ });
+
+ utils.wait_for_update().await;
+
+ // Cursor after hovering the first half
+ assert_eq!(utils.cursor_icon(), CursorIcon::Progress);
+
+ utils.push_event(PlatformEvent::Mouse {
+ name: EventName::MouseOver,
+ cursor: (100., 300.).into(),
+ button: Some(MouseButton::Left),
+ });
+
+ utils.wait_for_update().await;
+
+ // Cursor after hovering the second half
+ assert_eq!(utils.cursor_icon(), CursorIcon::Pointer);
+
+ utils.push_event(PlatformEvent::Mouse {
+ name: EventName::MouseOver,
+ cursor: (-1., -1.).into(),
+ button: Some(MouseButton::Left),
+ });
+
+ utils.wait_for_update().await;
+
+ // Cursor after leaving the window
+ assert_eq!(utils.cursor_icon(), CursorIcon::default());
+ }
+}
diff --git a/crates/components/src/drag_drop.rs b/crates/components/src/drag_drop.rs
index 461534f2b..ae866464e 100644
--- a/crates/components/src/drag_drop.rs
+++ b/crates/components/src/drag_drop.rs
@@ -24,7 +24,7 @@ pub fn DragProvider(DragProviderProps { children }: DragProviderProp
/// [`DragZone`] component properties.
#[derive(Props, Clone, PartialEq)]
-pub struct DragZoneProps {
+pub struct DragZoneProps {
/// Element visible when dragging the element. This follows the cursor.
drag_element: Element,
/// Inner children for the DropZone.
@@ -39,14 +39,14 @@ pub struct DragZoneProps {
/// See [`DragZoneProps`].
///
#[allow(non_snake_case)]
-pub fn DragZone(
+pub fn DragZone(
DragZoneProps {
data,
children,
drag_element,
}: DragZoneProps,
) -> Element {
- let drags = use_context::>>();
+ let mut drags = use_context::>>();
let mut dragging = use_signal(|| false);
let mut pos = use_signal(CursorPoint::default);
let (node_reference, size) = use_node_signal();
@@ -108,31 +108,22 @@ pub fn DragZone(
}
/// [`DropZone`] component properties.
-#[derive(Props, PartialEq)]
-pub struct DropZoneProps {
+#[derive(Props, PartialEq, Clone)]
+pub struct DropZoneProps {
/// Inner children for the DropZone.
children: Element,
/// Handler for the `ondrop` event.
ondrop: EventHandler,
}
-impl Clone for DropZoneProps {
- fn clone(&self) -> Self {
- Self {
- children: self.children.clone(),
- ondrop: self.ondrop.clone(),
- }
- }
-}
-
/// Elements from [`DragZone`]s can be dropped here.
///
/// # Props
/// See [`DropZoneProps`].
///
#[allow(non_snake_case)]
-pub fn DropZone(props: DropZoneProps) -> Element {
- let drags = use_context::>>();
+pub fn DropZone(props: DropZoneProps) -> Element {
+ let mut drags = use_context::>>();
let onclick = move |_: MouseEvent| {
if let Some(current_drags) = &*drags.read() {
@@ -153,9 +144,8 @@ pub fn DropZone(props: DropZoneProps) -> Element {
#[cfg(test)]
mod test {
- use dioxus::signals::use_signal;
use freya::prelude::*;
- use freya_testing::{events::pointer::MouseButton, launch_test, FreyaEvent};
+ use freya_testing::{events::pointer::MouseButton, launch_test, EventName, PlatformEvent};
#[tokio::test]
pub async fn drag_drop() {
@@ -200,24 +190,24 @@ mod test {
let root = utils.root();
utils.wait_for_update().await;
- utils.push_event(FreyaEvent::Mouse {
- name: "mousedown".to_string(),
+ utils.push_event(PlatformEvent::Mouse {
+ name: EventName::MouseDown,
cursor: (5.0, 5.0).into(),
button: Some(MouseButton::Left),
});
utils.wait_for_update().await;
- utils.push_event(FreyaEvent::Mouse {
- name: "mouseover".to_string(),
+ utils.push_event(PlatformEvent::Mouse {
+ name: EventName::MouseOver,
cursor: (5.0, 5.0).into(),
button: Some(MouseButton::Left),
});
utils.wait_for_update().await;
- utils.push_event(FreyaEvent::Mouse {
- name: "mouseover".to_string(),
+ utils.push_event(PlatformEvent::Mouse {
+ name: EventName::MouseOver,
cursor: (5.0, 300.0).into(),
button: Some(MouseButton::Left),
});
@@ -226,8 +216,8 @@ mod test {
assert_eq!(root.get(0).get(0).get(0).get(0).text(), Some("Moving"));
- utils.push_event(FreyaEvent::Mouse {
- name: "click".to_string(),
+ utils.push_event(PlatformEvent::Mouse {
+ name: EventName::Click,
cursor: (5.0, 300.0).into(),
button: Some(MouseButton::Left),
});
diff --git a/crates/components/src/dropdown.rs b/crates/components/src/dropdown.rs
index 7dc3a8d91..ac823cd2d 100644
--- a/crates/components/src/dropdown.rs
+++ b/crates/components/src/dropdown.rs
@@ -14,7 +14,7 @@ use winit::window::CursorIcon;
/// [`DropdownItem`] component properties.
#[derive(Props, Clone, PartialEq)]
-pub struct DropdownItemProps {
+pub struct DropdownItemProps {
/// Theme override.
pub theme: Option,
/// Selectable items, like [`DropdownItem`]
@@ -72,21 +72,15 @@ where
};
let color = theme.font_theme.color;
- use_drop({
- to_owned![status, platform];
- move || {
- if *status.peek() == DropdownItemStatus::Hovering {
- platform.set_cursor(CursorIcon::default());
- }
+ use_drop(move || {
+ if *status.peek() == DropdownItemStatus::Hovering {
+ platform.set_cursor(CursorIcon::default());
}
});
- let onmouseenter = {
- to_owned![platform];
- move |_| {
- platform.set_cursor(CursorIcon::Pointer);
- status.set(DropdownItemStatus::Hovering);
- }
+ let onmouseenter = move |_| {
+ platform.set_cursor(CursorIcon::Pointer);
+ status.set(DropdownItemStatus::Hovering);
};
let onmouseleave = move |_| {
@@ -132,7 +126,7 @@ where
/// [`Dropdown`] component properties.
#[derive(Props, Clone, PartialEq)]
-pub struct DropdownProps {
+pub struct DropdownProps {
/// Theme override.
pub theme: Option,
/// Selectable items, like [`DropdownItem`]
@@ -188,7 +182,7 @@ pub fn Dropdown(props: DropdownProps) -> Element
where
T: PartialEq + Clone + Display + 'static,
{
- let selected = use_context_provider(|| Signal::new(props.value.clone()));
+ let mut selected = use_context_provider(|| Signal::new(props.value.clone()));
let theme = use_applied_theme!(&props.theme, dropdown);
let mut focus = use_focus();
let mut status = use_signal(DropdownStatus::default);
@@ -204,12 +198,9 @@ where
*selected.write() = value;
});
- use_drop({
- to_owned![status, platform];
- move || {
- if *status.peek() == DropdownStatus::Hovering {
- platform.set_cursor(CursorIcon::default());
- }
+ use_drop(move || {
+ if *status.peek() == DropdownStatus::Hovering {
+ platform.set_cursor(CursorIcon::default());
}
});
@@ -237,12 +228,9 @@ where
}
};
- let onmouseenter = {
- to_owned![status, platform];
- move |_| {
- platform.set_cursor(CursorIcon::Pointer);
- status.set(DropdownStatus::Hovering);
- }
+ let onmouseenter = move |_| {
+ platform.set_cursor(CursorIcon::Pointer);
+ status.set(DropdownStatus::Hovering);
};
let onmouseleave = move |_| {
@@ -315,3 +303,96 @@ where
}
)
}
+
+#[cfg(test)]
+mod test {
+ use freya::prelude::*;
+ use freya_testing::*;
+ use winit::event::MouseButton;
+
+ #[tokio::test]
+ pub async fn dropdown() {
+ fn dropdown_app() -> Element {
+ let values = use_hook(|| {
+ vec![
+ "Value A".to_string(),
+ "Value B".to_string(),
+ "Value C".to_string(),
+ ]
+ });
+ let mut selected_dropdown = use_signal(|| "Value A".to_string());
+
+ rsx!(
+ Dropdown {
+ value: selected_dropdown.read().clone(),
+ for ch in values {
+ DropdownItem {
+ value: ch.clone(),
+ onclick: {
+ to_owned![ch];
+ move |_| selected_dropdown.set(ch.clone())
+ },
+ label { "{ch}" }
+ }
+ }
+ }
+ )
+ }
+
+ let mut utils = launch_test(dropdown_app);
+ let root = utils.root();
+ let label = root.get(0).get(0);
+ utils.wait_for_update().await;
+
+ // Currently closed
+ let start_size = utils.sdom().get().layout().size();
+
+ // Default value
+ assert_eq!(label.get(0).text(), Some("Value A"));
+
+ // Open the dropdown
+ utils.push_event(PlatformEvent::Mouse {
+ name: EventName::Click,
+ cursor: (5.0, 5.0).into(),
+ button: Some(MouseButton::Left),
+ });
+ utils.wait_for_update().await;
+
+ // Now that the dropwdown is opened, there are more nodes in the layout
+ assert!(utils.sdom().get().layout().size() > start_size);
+
+ // Close the dropdown by clicking outside of it
+ utils.push_event(PlatformEvent::Mouse {
+ name: EventName::Click,
+ cursor: (200.0, 200.0).into(),
+ button: Some(MouseButton::Left),
+ });
+ utils.wait_for_update().await;
+
+ // Now the layout size is like in the begining
+ assert_eq!(utils.sdom().get().layout().size(), start_size);
+
+ // Open the dropdown again
+ utils.push_event(PlatformEvent::Mouse {
+ name: EventName::Click,
+ cursor: (5.0, 5.0).into(),
+ button: Some(MouseButton::Left),
+ });
+ utils.wait_for_update().await;
+
+ // Click on the second option
+ utils.push_event(PlatformEvent::Mouse {
+ name: EventName::Click,
+ cursor: (45.0, 100.0).into(),
+ button: Some(MouseButton::Left),
+ });
+ utils.wait_for_update().await;
+ utils.wait_for_update().await;
+
+ // Now the layout size is like in the begining, again
+ assert_eq!(utils.sdom().get().layout().size(), start_size);
+
+ // The second optio was selected
+ assert_eq!(label.get(0).text(), Some("Value B"));
+ }
+}
diff --git a/crates/components/src/external_link.rs b/crates/components/src/external_link.rs
deleted file mode 100644
index 408fa6f28..000000000
--- a/crates/components/src/external_link.rs
+++ /dev/null
@@ -1,102 +0,0 @@
-use dioxus::prelude::*;
-use freya_elements::elements as dioxus_elements;
-use freya_elements::events::MouseEvent;
-
-use freya_hooks::{use_applied_theme, ExternalLinkThemeWith};
-
-use crate::Tooltip;
-
-/// [`ExternalLink`] component properties.
-#[derive(Props, Clone, PartialEq)]
-pub struct ExternalLinkProps {
- /// Theme override.
- pub theme: Option,
- /// Inner children for the ExternalLink.
- pub children: Element,
- /// Handler for the `onerror` event.
- pub onerror: Option>,
- /// Whether to show a tooltip with the URL or not.
- #[props(optional, default = true)]
- pub show_tooltip: bool,
- /// The ExternalLink destination URL.
- #[props(into)]
- pub url: String,
-}
-
-/// `Link` for external locations, e.g websites.
-///
-/// # Props
-/// See [`ExternalLinkProps`].
-///
-/// # Styling
-/// Inherits the [`ExternalLinkTheme`](freya_hooks::ExternalLinkTheme) theme.
-///
-/// # Example
-///
-/// ```no_run
-/// # use freya::prelude::*;
-/// fn app() -> Element {
-/// rsx!(
-/// ExternalLink {
-/// url: "https://github.com",
-/// label {
-/// "GitHub"
-/// }
-/// }
-/// )
-/// }
-/// ```
-///
-#[allow(non_snake_case)]
-pub fn ExternalLink(props: ExternalLinkProps) -> Element {
- let theme = use_applied_theme!(&props.theme, external_link);
- let is_hovering = use_signal(|| false);
-
- let onmouseover = move |_: MouseEvent| {
- *is_hovering.write() = true;
- };
-
- let onmouseleave = move |_: MouseEvent| {
- *is_hovering.write() = false;
- };
-
- let onclick = {
- let url = props.url.clone();
- move |_: MouseEvent| {
- let res = open::that(url.clone());
- if let (Err(_), Some(onerror)) = (res, props.onerror.as_ref()) {
- onerror.call(());
- }
- // TODO(marc2332): Log unhandled errors
- }
- };
-
- let color = if *is_hovering.read() {
- theme.highlight_color.as_ref()
- } else {
- "inherit"
- };
-
- rsx!(
- rect {
- onmouseover,
- onmouseleave,
- onclick,
- color: "{color}",
- {props.children}
- }
- rect {
- height: "0",
- width: "0",
- layer: "-999",
- rect {
- width: "100v",
- if *is_hovering.read() && props.show_tooltip {
- Tooltip {
- url: props.url.clone()
- }
- }
- }
- }
- )
-}
diff --git a/crates/components/src/gesture_area.rs b/crates/components/src/gesture_area.rs
index 6fba5379b..5a5adbe3e 100644
--- a/crates/components/src/gesture_area.rs
+++ b/crates/components/src/gesture_area.rs
@@ -4,7 +4,7 @@ use std::time::Instant;
use dioxus::prelude::*;
use freya_elements::elements as dioxus_elements;
use freya_elements::events::{touch::TouchPhase, TouchEvent};
-use futures::StreamExt;
+use futures_util::StreamExt;
/// Distance between the first tap and the second tap in `DoubleTap` gesture.
const DOUBLE_TAP_DISTANCE: f64 = 100.0;
@@ -173,10 +173,8 @@ pub fn GestureArea(props: GestureAreaProps) -> Element {
mod test {
use std::time::Duration;
- use dioxus::signals::use_signal;
use freya::prelude::*;
- use freya_elements::events::touch::TouchPhase;
- use freya_testing::{launch_test, FreyaEvent};
+ use freya_testing::{events::touch::TouchPhase, launch_test, EventName, PlatformEvent};
use tokio::time::sleep;
use crate::gesture_area::DOUBLE_TAP_MIN;
@@ -198,8 +196,13 @@ mod test {
rsx!(
GestureArea {
ongesture,
- "{value}"
+ rect {
+ width: "100%",
+ height: "100%",
+
+ }
}
+ "{value}"
)
}
@@ -208,18 +211,18 @@ mod test {
// Initial state
utils.wait_for_update().await;
- assert_eq!(utils.root().get(0).get(0).text(), Some("EMPTY"));
+ assert_eq!(utils.root().get(1).text(), Some("EMPTY"));
- utils.push_event(FreyaEvent::Touch {
- name: "touchstart".to_string(),
+ utils.push_event(PlatformEvent::Touch {
+ name: EventName::TouchStart,
location: (1.0, 1.0).into(),
phase: TouchPhase::Started,
finger_id: 0,
force: None,
});
- utils.push_event(FreyaEvent::Touch {
- name: "touchend".to_string(),
+ utils.push_event(PlatformEvent::Touch {
+ name: EventName::TouchEnd,
location: (1.0, 1.0).into(),
phase: TouchPhase::Ended,
finger_id: 0,
@@ -231,8 +234,8 @@ mod test {
sleep(Duration::from_millis(DOUBLE_TAP_MIN as u64)).await;
- utils.push_event(FreyaEvent::Touch {
- name: "touchstart".to_string(),
+ utils.push_event(PlatformEvent::Touch {
+ name: EventName::TouchStart,
location: (1.0, 1.0).into(),
phase: TouchPhase::Started,
finger_id: 0,
@@ -242,7 +245,7 @@ mod test {
utils.wait_for_update().await;
utils.wait_for_update().await;
- assert_eq!(utils.root().get(0).get(0).text(), Some("DoubleTap"));
+ assert_eq!(utils.root().get(1).text(), Some("DoubleTap"));
}
/// Simulates `TapUp` and `TapDown` gestures.
@@ -258,8 +261,13 @@ mod test {
rsx!(
GestureArea {
ongesture,
- "{value}"
+ rect {
+ width: "100%",
+ height: "100%",
+
+ }
}
+ "{value}"
)
}
@@ -268,10 +276,10 @@ mod test {
// Initial state
utils.wait_for_update().await;
- assert_eq!(utils.root().get(0).get(0).text(), Some("EMPTY"));
+ assert_eq!(utils.root().get(1).text(), Some("EMPTY"));
- utils.push_event(FreyaEvent::Touch {
- name: "touchstart".to_string(),
+ utils.push_event(PlatformEvent::Touch {
+ name: EventName::TouchStart,
location: (1.0, 1.0).into(),
phase: TouchPhase::Started,
finger_id: 0,
@@ -281,10 +289,10 @@ mod test {
utils.wait_for_update().await;
utils.wait_for_update().await;
- assert_eq!(utils.root().get(0).get(0).text(), Some("TapDown"));
+ assert_eq!(utils.root().get(1).text(), Some("TapDown"));
- utils.push_event(FreyaEvent::Touch {
- name: "touchend".to_string(),
+ utils.push_event(PlatformEvent::Touch {
+ name: EventName::TouchEnd,
location: (1.0, 1.0).into(),
phase: TouchPhase::Ended,
finger_id: 0,
@@ -294,6 +302,6 @@ mod test {
utils.wait_for_update().await;
utils.wait_for_update().await;
- assert_eq!(utils.root().get(0).get(0).text(), Some("TapUp"));
+ assert_eq!(utils.root().get(1).text(), Some("TapUp"));
}
}
diff --git a/crates/components/src/graph.rs b/crates/components/src/graph.rs
index e5e730970..5796ef8c8 100644
--- a/crates/components/src/graph.rs
+++ b/crates/components/src/graph.rs
@@ -140,7 +140,8 @@ pub fn Graph(props: GraphProps) -> Element {
for (i, point) in x_labels.iter().enumerate() {
let x = (space_x * i as f32) + start_x;
- let mut paragrap_builder = ParagraphBuilder::new(¶graph_style, font_collection);
+ let mut paragrap_builder =
+ ParagraphBuilder::new(¶graph_style, font_collection.clone());
paragrap_builder.add_text(point);
let mut text = paragrap_builder.build();
diff --git a/crates/components/src/input.rs b/crates/components/src/input.rs
index e5862531f..7ab8d5604 100644
--- a/crates/components/src/input.rs
+++ b/crates/components/src/input.rs
@@ -88,13 +88,13 @@ pub fn Input(
}: InputProps,
) -> Element {
let platform = use_platform();
- let status = use_signal(InputStatus::default);
+ let mut status = use_signal(InputStatus::default);
let mut editable = use_editable(
|| EditableConfig::new(value.to_string()),
EditableMode::MultipleLinesSingleEditor,
);
let theme = use_applied_theme!(&theme, input);
- let focus = use_focus();
+ let mut focus = use_focus();
if &value != editable.editor().read().rope() {
editable.editor_mut().write().set(&value);
@@ -105,67 +105,46 @@ pub fn Input(
InputMode::Shown => value.clone(),
};
- use_drop({
- to_owned![status, platform];
- move || {
- if *status.peek() == InputStatus::Hovering {
- platform.set_cursor(CursorIcon::default());
- }
+ use_drop(move || {
+ if *status.peek() == InputStatus::Hovering {
+ platform.set_cursor(CursorIcon::default());
}
});
- let onkeydown = {
- to_owned![editable, focus];
- move |e: Event| {
- if focus.is_focused() && e.data.key != Key::Enter {
- editable.process_event(&EditableEvent::KeyDown(e.data));
- onchange.call(editable.editor().peek().to_string());
- }
+ let onkeydown = move |e: Event| {
+ if focus.is_focused() && e.data.key != Key::Enter {
+ editable.process_event(&EditableEvent::KeyDown(e.data));
+ onchange.call(editable.editor().peek().to_string());
}
};
- let onmousedown = {
- to_owned![editable, focus];
- move |e: MouseEvent| {
- editable.process_event(&EditableEvent::MouseDown(e.data, 0));
- focus.focus();
- }
+ let onmousedown = move |e: MouseEvent| {
+ editable.process_event(&EditableEvent::MouseDown(e.data, 0));
+ focus.focus();
};
- let onmouseover = {
- to_owned![editable];
- move |e: MouseEvent| {
- editable.process_event(&EditableEvent::MouseOver(e.data, 0));
- }
+ let onmouseover = move |e: MouseEvent| {
+ editable.process_event(&EditableEvent::MouseOver(e.data, 0));
};
- let onmouseenter = {
- to_owned![platform, status];
- move |_| {
- platform.set_cursor(CursorIcon::Text);
- *status.write() = InputStatus::Hovering;
- }
+ let onmouseenter = move |_| {
+ platform.set_cursor(CursorIcon::Text);
+ *status.write() = InputStatus::Hovering;
};
- let onmouseleave = {
- to_owned![platform, status];
- move |_| {
- platform.set_cursor(CursorIcon::default());
- *status.write() = InputStatus::default();
- }
+ let onmouseleave = move |_| {
+ platform.set_cursor(CursorIcon::default());
+ *status.write() = InputStatus::default();
};
- let onglobalclick = {
- to_owned![editable, focus];
- move |_| match *status.read() {
- InputStatus::Idle if focus.is_focused() => {
- focus.unfocus();
- }
- InputStatus::Hovering => {
- editable.process_event(&EditableEvent::Click);
- }
- _ => {}
+ let onglobalclick = move |_| match *status.read() {
+ InputStatus::Idle if focus.is_focused() => {
+ focus.unfocus();
}
+ InputStatus::Hovering => {
+ editable.process_event(&EditableEvent::Click);
+ }
+ _ => {}
};
let focus_id = focus.attribute();
@@ -184,6 +163,7 @@ pub fn Input(
border_fill,
width,
margin,
+ corner_radius,
font_theme: FontTheme { color },
..
} = theme;
@@ -195,11 +175,12 @@ pub fn Input(
color: "{color}",
background: "{background}",
border: "1 solid {border_fill}",
- shadow: "0 3 15 0 rgb(0, 0, 0, 0.3)",
- corner_radius: "10",
+ shadow: "0 4 5 0 rgb(0, 0, 0, 0.1)",
+ corner_radius: "{corner_radius}",
margin: "{margin}",
cursor_reference,
focus_id,
+ focusable: "true",
role: "textInput",
main_align: "center",
paragraph {
@@ -224,3 +205,56 @@ pub fn Input(
}
)
}
+
+#[cfg(test)]
+mod test {
+ use freya::prelude::*;
+ use freya_testing::*;
+
+ #[tokio::test]
+ pub async fn input() {
+ fn input_app() -> Element {
+ let mut value = use_signal(|| "Hello, Worl".to_string());
+
+ rsx!(Input {
+ value: value.read().clone(),
+ onchange: move |new_value| {
+ value.set(new_value);
+ }
+ },)
+ }
+
+ let mut utils = launch_test(input_app);
+ let root = utils.root();
+ let text = root.get(0).get(0).get(0);
+ utils.wait_for_update().await;
+
+ // Default value
+ assert_eq!(text.get(0).text(), Some("Hello, Worl"));
+
+ assert_eq!(utils.focus_id(), ACCESSIBILITY_ROOT_ID);
+
+ // Focus the input in the end of the text
+ utils.push_event(PlatformEvent::Mouse {
+ name: EventName::MouseDown,
+ cursor: (115., 10.).into(),
+ button: Some(MouseButton::Left),
+ });
+ utils.wait_for_update().await;
+ utils.wait_for_update().await;
+
+ assert_ne!(utils.focus_id(), ACCESSIBILITY_ROOT_ID);
+
+ // Write "d"
+ utils.push_event(PlatformEvent::Keyboard {
+ name: EventName::KeyDown,
+ key: Key::Character("d".to_string()),
+ code: Code::KeyD,
+ modifiers: Modifiers::default(),
+ });
+ utils.wait_for_update().await;
+
+ // Check that "d" has been written into the input.
+ assert_eq!(text.get(0).text(), Some("Hello, World"));
+ }
+}
diff --git a/crates/components/src/lib.rs b/crates/components/src/lib.rs
index e080e1ed5..7121cb937 100644
--- a/crates/components/src/lib.rs
+++ b/crates/components/src/lib.rs
@@ -8,15 +8,16 @@ mod canvas;
mod cursor_area;
mod drag_drop;
mod dropdown;
-mod external_link;
mod gesture_area;
mod graph;
mod icons;
mod input;
+mod link;
mod loader;
mod network_image;
mod progress_bar;
mod scroll_views;
+mod sidebar;
mod slider;
mod switch;
mod table;
@@ -31,15 +32,16 @@ pub use canvas::*;
pub use cursor_area::*;
pub use drag_drop::*;
pub use dropdown::*;
-pub use external_link::*;
pub use gesture_area::*;
pub use graph::*;
pub use icons::*;
pub use input::*;
+pub use link::*;
pub use loader::*;
pub use network_image::*;
pub use progress_bar::*;
pub use scroll_views::*;
+pub use sidebar::*;
pub use slider::*;
pub use switch::*;
pub use table::*;
diff --git a/crates/components/src/link.rs b/crates/components/src/link.rs
new file mode 100644
index 000000000..7cf69ebdf
--- /dev/null
+++ b/crates/components/src/link.rs
@@ -0,0 +1,286 @@
+use crate::Tooltip;
+use dioxus::prelude::*;
+use dioxus_router::prelude::{navigator, IntoRoutable};
+use freya_elements::elements as dioxus_elements;
+use freya_elements::events::MouseEvent;
+use freya_hooks::{use_applied_theme, LinkThemeWith};
+use std::borrow::Cow;
+use winit::event::MouseButton;
+
+#[derive(Clone, PartialEq)]
+pub enum LinkTooltip {
+ /// No tooltip at all.
+ None,
+ /// Default tooltip.
+ ///
+ /// - For a route, this is the same as [`None`](AnchorTooltip::None).
+ /// - For a URL, this is the value of that URL.
+ Default,
+ /// Custom tooltip to always show.
+ Custom(String),
+}
+
+/// Similar to [`Link`](dioxus_router::components::Link), but you can use it in Freya.
+/// Both internal routes (dioxus-router) and external links are supported. When using internal routes
+/// make sure the Link is descendant of a [`Router`](dioxus_router::components::Router) component.
+///
+/// # Styling
+///
+/// Inherits the [`LinkTheme`](freya_hooks::LinkTheme) theme.
+///
+/// # Example
+///
+/// With Dioxus Router:
+///
+/// ```rust
+/// # use dioxus::prelude::*;
+/// # use dioxus_router::prelude::*;
+/// # use freya_elements::elements as dioxus_elements;
+/// # use freya_components::Link;
+/// # #[derive(Routable, Clone)]
+/// # #[rustfmt::skip]
+/// # enum AppRouter {
+/// # #[route("/")]
+/// # Settings,
+/// # #[route("/..routes")]
+/// # NotFound
+/// # }
+/// # #[component]
+/// # fn Settings() -> Element { rsx!(rect { })}
+/// # #[component]
+/// # fn NotFound() -> Element { rsx!(rect { })}
+/// # fn link_example_good() -> Element {
+/// rsx! {
+/// Link {
+/// to: AppRouter::Settings,
+/// label { "App Settings" }
+/// }
+/// }
+/// # }
+/// ```
+///
+/// With external routes:
+///
+/// ```rust
+/// # use dioxus::prelude::*;
+/// # use freya_elements::elements as dioxus_elements;
+/// # use freya_components::Link;
+/// # fn link_example_good() -> Element {
+/// rsx! {
+/// Link {
+/// to: "https://crates.io/crates/freya",
+/// label { "Freya crates.io" }
+/// }
+/// }
+/// # }
+/// ```
+#[allow(non_snake_case)]
+#[component]
+pub fn Link(
+ /// Theme override.
+ #[props(optional)]
+ theme: Option,
+ /// The route or external URL string to navigate to.
+ #[props(into)]
+ to: IntoRoutable,
+ /// Inner children for the Link.
+ children: Element,
+ /// This event will be fired if opening an external link fails.
+ #[props(optional)]
+ onerror: Option>,
+ /// A little text hint to show when hovering over the anchor.
+ ///
+ /// Setting this to [`None`] is the same as [`LinkTooltip::Default`].
+ /// To remove the tooltip, set this to [`LinkTooltip::None`].
+ #[props(optional)]
+ tooltip: Option,
+) -> Element {
+ let theme = use_applied_theme!(&theme, link);
+ let mut is_hovering = use_signal(|| false);
+
+ let url = if let IntoRoutable::FromStr(ref url) = to {
+ Some(url.clone())
+ } else {
+ None
+ };
+
+ let onmouseenter = move |_: MouseEvent| {
+ is_hovering.set(true);
+ };
+
+ let onmouseleave = move |_: MouseEvent| {
+ is_hovering.set(false);
+ };
+
+ let onclick = {
+ to_owned![url, to];
+ move |event: MouseEvent| {
+ if !matches!(event.trigger_button, Some(MouseButton::Left)) {
+ return;
+ }
+
+ // Open the url if there is any
+ // otherwise change the dioxus router route
+ if let Some(url) = &url {
+ let res = open::that(url);
+
+ if let (Err(_), Some(onerror)) = (res, onerror.as_ref()) {
+ onerror.call(());
+ }
+
+ // TODO(marc2332): Log unhandled errors
+ } else {
+ let router = navigator();
+ router.push(to.clone());
+ }
+ }
+ };
+
+ let color = if *is_hovering.read() {
+ theme.highlight_color
+ } else {
+ Cow::Borrowed("inherit")
+ };
+
+ let tooltip = match tooltip {
+ None | Some(LinkTooltip::Default) => url.clone(),
+ Some(LinkTooltip::None) => None,
+ Some(LinkTooltip::Custom(str)) => Some(str),
+ };
+
+ let main_rect = rsx! {
+ rect {
+ onmouseenter,
+ onmouseleave,
+ onclick,
+ color: "{color}",
+ {children}
+ }
+ };
+
+ let Some(tooltip) = tooltip else {
+ return rsx!({ main_rect });
+ };
+
+ rsx! {
+ {main_rect}
+ rect {
+ height: "0",
+ layer: "-999",
+ rect {
+ width: "100v",
+ if *is_hovering.read() {
+ Tooltip {
+ url: tooltip
+ }
+ }
+ }
+ }
+ }
+}
+
+#[cfg(test)]
+mod test {
+ use dioxus_router::prelude::{Outlet, Routable, Router};
+ use freya::prelude::*;
+ use freya_testing::*;
+
+ #[tokio::test]
+ pub async fn link() {
+ #[derive(Routable, Clone)]
+ #[rustfmt::skip]
+ enum Route {
+ #[layout(Layout)]
+ #[route("/")]
+ Home,
+ #[route("/somewhere")]
+ Somewhere,
+ #[route("/..routes")]
+ NotFound
+ }
+
+ #[allow(non_snake_case)]
+ #[component]
+ fn NotFound() -> Element {
+ rsx! {
+ label {
+ "Not found"
+ }
+ }
+ }
+
+ #[allow(non_snake_case)]
+ #[component]
+ fn Home() -> Element {
+ rsx! {
+ label {
+ "Home"
+ }
+ }
+ }
+
+ #[allow(non_snake_case)]
+ #[component]
+ fn Somewhere() -> Element {
+ rsx! {
+ label {
+ "Somewhere"
+ }
+ }
+ }
+
+ #[allow(non_snake_case)]
+ #[component]
+ fn Layout() -> Element {
+ rsx!(
+ Link {
+ to: Route::Home,
+ Button {
+ label { "Home" }
+ }
+ }
+ Link {
+ to: Route::Somewhere,
+ Button {
+ label { "Somewhere" }
+ }
+ }
+ Outlet:: {}
+ )
+ }
+
+ fn link_app() -> Element {
+ rsx!(Router:: {})
+ }
+
+ let mut utils = launch_test(link_app);
+
+ utils.wait_for_update().await;
+ utils.wait_for_update().await;
+
+ // Check route is Home
+ assert_eq!(utils.root().get(2).get(0).text(), Some("Home"));
+
+ // Go to the "Somewhere" route
+ utils.push_event(PlatformEvent::Mouse {
+ name: EventName::Click,
+ cursor: (5., 70.).into(),
+ button: Some(MouseButton::Left),
+ });
+
+ utils.wait_for_update().await;
+ utils.wait_for_update().await;
+
+ // Check route is Somewhere
+ assert_eq!(utils.root().get(2).get(0).text(), Some("Somewhere"));
+
+ // Go to the "Home" route again
+ utils.push_event(PlatformEvent::Mouse {
+ name: EventName::Click,
+ cursor: (5., 5.).into(),
+ button: Some(MouseButton::Left),
+ });
+
+ utils.wait_for_update().await;
+ }
+}
diff --git a/crates/components/src/network_image.rs b/crates/components/src/network_image.rs
index b9b0962ab..9189c0f7c 100644
--- a/crates/components/src/network_image.rs
+++ b/crates/components/src/network_image.rs
@@ -58,8 +58,8 @@ pub enum ImageStatus {
#[allow(non_snake_case)]
pub fn NetworkImage(props: NetworkImageProps) -> Element {
let focus = use_focus();
- let status = use_signal(|| ImageStatus::Loading);
- let image_bytes = use_signal:: | |