diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 8162790a8..71cebd48a 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: "6" - 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 a99aebcd1..dc095e769 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -26,27 +26,27 @@ 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 = "ef101dd876ee8ecdd702a7e22addfd47f2ebd892", default-features = false, features = ["macro", "signals", "hooks", "hot-reload", "html"]} +dioxus-native-core-macro = { git = "https://github.com/DioxusLabs/dioxus", rev = "ef101dd876ee8ecdd702a7e22addfd47f2ebd892" } +dioxus-rsx = { git = "https://github.com/DioxusLabs/dioxus", rev = "ef101dd876ee8ecdd702a7e22addfd47f2ebd892", features = ["hot_reload"] } +dioxus-native-core = { git = "https://github.com/DioxusLabs/dioxus", rev = "ef101dd876ee8ecdd702a7e22addfd47f2ebd892", features = ["dioxus"] } +dioxus-core-macro = { git = "https://github.com/DioxusLabs/dioxus", rev = "ef101dd876ee8ecdd702a7e22addfd47f2ebd892" } +dioxus-hooks = { git = "https://github.com/DioxusLabs/dioxus", rev = "ef101dd876ee8ecdd702a7e22addfd47f2ebd892" } +dioxus-signals = { git = "https://github.com/DioxusLabs/dioxus", rev = "ef101dd876ee8ecdd702a7e22addfd47f2ebd892" } +dioxus-core = { git = "https://github.com/DioxusLabs/dioxus", rev = "ef101dd876ee8ecdd702a7e22addfd47f2ebd892" } +dioxus-hot-reload = { git = "https://github.com/DioxusLabs/dioxus", rev = "ef101dd876ee8ecdd702a7e22addfd47f2ebd892", features = ["file_watcher"], default-features = false } +dioxus-router = { git = "https://github.com/DioxusLabs/dioxus", rev = "ef101dd876ee8ecdd702a7e22addfd47f2ebd892", default-features = false } skia-safe = { version = "0.67.0", features = ["gl", "textlayout", "svg"] } gl = "0.14.0" -glutin = "0.30.10" -glutin-winit = "0.3.0" -raw-window-handle = "0.5.1" -winit = "0.28.7" +glutin = "0.31.2" +glutin-winit = "0.4.2" +raw-window-handle = "0.5.2" +winit = "0.29.9" tokio = { version = "1.33.0", features = ["sync", "rt-multi-thread", "time", "macros"] } -accesskit = { version = "0.11.0", features = ["serde"]} -accesskit_winit = "0.14.1" +accesskit = { version = "0.12.2", features = ["serde"]} +accesskit_winit = "0.18.0" zbus = "3.14.1" euclid = "0.22.9" 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 MendezStephen Andary +Alberto MendezAndar1an ### 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..3fd67d1e4 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,8 +237,8 @@ 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 } @@ -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 c23c2c3bc..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%", @@ -170,11 +170,11 @@ fn app() -> Element { ### padding -Specify the inner paddings of an element. You can do so by three different ways, just like in CSS. +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%", @@ -209,11 +209,11 @@ fn app() -> Element { ### margin -Specify the margin of an element. You can do so by three different ways, just like in CSS. +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 4af7a359c..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", @@ -126,7 +126,7 @@ Compatible elements: [`rect`](/guides/elements.html#rect) The attributes that have colors as values can use the following syntax: #### Static colors -- `rect` +- `red` - `blue` - `green` - `yellow` diff --git a/book/src/guides/testing.md b/book/src/guides/testing.md index 0df423cbe..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!" } @@ -136,7 +139,7 @@ async fn test() { let mut utils = launch_test_with_config( our_component, - TestingConfig::default().with_size((500.0, 800.0).into()), + *TestingConfig::default().with_size((500.0, 800.0).into()), ); let root = utils.root(); 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/components/src/accordion.rs b/crates/components/src/accordion.rs index 27802ab48..6f79a70ad 100644 --- a/crates/components/src/accordion.rs +++ b/crates/components/src/accordion.rs @@ -85,7 +85,7 @@ pub fn Accordion(props: AccordionProps) -> Element { let onmouseenter = { to_owned![status, platform]; move |_| { - platform.set_cursor(CursorIcon::Hand); + platform.set_cursor(CursorIcon::Pointer); status.set(AccordionStatus::Hovering); } }; diff --git a/crates/components/src/button.rs b/crates/components/src/button.rs index 94c436733..070062a46 100644 --- a/crates/components/src/button.rs +++ b/crates/components/src/button.rs @@ -94,7 +94,7 @@ pub fn Button(props: ButtonProps) -> Element { let onmouseenter = { to_owned![status, platform]; move |_| { - platform.set_cursor(CursorIcon::Hand); + platform.set_cursor(CursorIcon::Pointer); status.set(ButtonStatus::Hovering); } }; diff --git a/crates/components/src/cursor_area.rs b/crates/components/src/cursor_area.rs index 6ec97df05..39b6c3c92 100644 --- a/crates/components/src/cursor_area.rs +++ b/crates/components/src/cursor_area.rs @@ -39,7 +39,7 @@ 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]; diff --git a/crates/components/src/drag_drop.rs b/crates/components/src/drag_drop.rs index 461534f2b..8c224092f 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,7 +144,6 @@ 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}; diff --git a/crates/components/src/dropdown.rs b/crates/components/src/dropdown.rs index e13bdbea9..71e6ca875 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`] @@ -84,7 +84,7 @@ where let onmouseenter = { to_owned![platform]; move |_| { - platform.set_cursor(CursorIcon::Hand); + platform.set_cursor(CursorIcon::Pointer); status.set(DropdownItemStatus::Hovering); } }; @@ -132,7 +132,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 +188,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); @@ -240,7 +240,7 @@ where let onmouseenter = { to_owned![status, platform]; move |_| { - platform.set_cursor(CursorIcon::Hand); + platform.set_cursor(CursorIcon::Pointer); status.set(DropdownStatus::Hovering); } }; diff --git a/crates/components/src/external_link.rs b/crates/components/src/external_link.rs index 408fa6f28..6c56a9a59 100644 --- a/crates/components/src/external_link.rs +++ b/crates/components/src/external_link.rs @@ -50,7 +50,7 @@ pub struct ExternalLinkProps { #[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 mut is_hovering = use_signal(|| false); let onmouseover = move |_: MouseEvent| { *is_hovering.write() = true; diff --git a/crates/components/src/gesture_area.rs b/crates/components/src/gesture_area.rs index 6fba5379b..1fa83505d 100644 --- a/crates/components/src/gesture_area.rs +++ b/crates/components/src/gesture_area.rs @@ -173,7 +173,6 @@ 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}; diff --git a/crates/components/src/input.rs b/crates/components/src/input.rs index 1ef0a52c8..e5862531f 100644 --- a/crates/components/src/input.rs +++ b/crates/components/src/input.rs @@ -168,6 +168,7 @@ pub fn Input( } }; + let focus_id = focus.attribute(); let cursor_reference = editable.cursor_attr(); let highlights = editable.highlights_attr(0); @@ -198,6 +199,8 @@ pub fn Input( corner_radius: "10", margin: "{margin}", cursor_reference, + focus_id, + role: "textInput", main_align: "center", paragraph { margin: "8 12", diff --git a/crates/components/src/scroll_views/scroll_view.rs b/crates/components/src/scroll_views/scroll_view.rs index 27246a7df..bc1733b2d 100644 --- a/crates/components/src/scroll_views/scroll_view.rs +++ b/crates/components/src/scroll_views/scroll_view.rs @@ -56,7 +56,7 @@ pub struct ScrollViewProps { /// #[allow(non_snake_case)] pub fn ScrollView(props: ScrollViewProps) -> Element { - let clicking_scrollbar = use_signal::>(|| None); + let mut clicking_scrollbar = use_signal::>(|| None); let mut clicking_shift = use_signal(|| false); let mut clicking_alt = use_signal(|| false); let mut scrolled_y = use_signal(|| 0); @@ -116,7 +116,13 @@ pub fn ScrollView(props: ScrollViewProps) -> Element { corrected_scrolled_y, ); - *scrolled_y.write() = scroll_position_y; + // Only scroll when there is still area to scroll + if *scrolled_y.peek() != scroll_position_y { + e.stop_propagation(); + *scrolled_y.write() = scroll_position_y; + } else { + return; + } } let wheel_x = if *clicking_shift.read() { @@ -132,7 +138,13 @@ pub fn ScrollView(props: ScrollViewProps) -> Element { corrected_scrolled_x, ); - *scrolled_x.write() = scroll_position_x; + // Only scroll when there is still area to scroll + if *scrolled_x.peek() != scroll_position_x { + e.stop_propagation(); + *scrolled_x.write() = scroll_position_x; + } else { + return; + } focus.focus(); } diff --git a/crates/components/src/scroll_views/virtual_scroll_view.rs b/crates/components/src/scroll_views/virtual_scroll_view.rs index 45940be5b..71a197237 100644 --- a/crates/components/src/scroll_views/virtual_scroll_view.rs +++ b/crates/components/src/scroll_views/virtual_scroll_view.rs @@ -39,6 +39,18 @@ pub struct VirtualScrollViewProps { pub scroll_with_arrows: bool, } +impl PartialEq for VirtualScrollViewProps { + fn eq(&self, other: &Self) -> bool { + self.theme == other.theme + && self.length == other.length + && self.item_size == other.item_size + && Rc::ptr_eq(&self.builder, &other.builder) + && self.direction == other.direction + && self.show_scrollbar == other.show_scrollbar + && self.scroll_with_arrows == other.scroll_with_arrows + } +} + fn get_render_range( viewport_size: f32, scroll_position: f32, @@ -91,7 +103,7 @@ fn get_render_range( /// ``` #[allow(non_snake_case)] pub fn VirtualScrollView(props: VirtualScrollViewProps) -> Element { - let clicking_scrollbar = use_signal::>(|| None); + let mut clicking_scrollbar = use_signal::>(|| None); let mut clicking_shift = use_signal(|| false); let mut clicking_alt = use_signal(|| false); let mut scrolled_y = use_signal(|| 0); @@ -149,7 +161,13 @@ pub fn VirtualScrollView(props: VirtualScrollViewProps) -> Element corrected_scrolled_y, ); - *scrolled_y.write() = scroll_position_y; + // Only scroll when there is still area to scroll + if *scrolled_y.peek() != scroll_position_y { + e.stop_propagation(); + *scrolled_y.write() = scroll_position_y; + } else { + return; + } } let wheel_x = if *clicking_shift.peek() { @@ -165,7 +183,13 @@ pub fn VirtualScrollView(props: VirtualScrollViewProps) -> Element corrected_scrolled_x, ); - *scrolled_x.write() = scroll_position_x; + // Only scroll when there is still area to scroll + if *scrolled_x.peek() != scroll_position_x { + e.stop_propagation(); + *scrolled_x.write() = scroll_position_x; + } else { + return; + } focus.focus(); } diff --git a/crates/components/src/slider.rs b/crates/components/src/slider.rs index d07e862ae..10cb3f2f4 100644 --- a/crates/components/src/slider.rs +++ b/crates/components/src/slider.rs @@ -104,7 +104,8 @@ pub fn Slider( let onmouseleave = { to_owned![platform, status]; - move |_: MouseEvent| { + move |e: MouseEvent| { + e.stop_propagation(); *status.write() = SliderStatus::Idle; platform.set_cursor(CursorIcon::default()); } @@ -112,15 +113,17 @@ pub fn Slider( let onmouseenter = { to_owned![status]; - move |_: MouseEvent| { + move |e: MouseEvent| { + e.stop_propagation(); *status.write() = SliderStatus::Hovering; - platform.set_cursor(CursorIcon::Hand); + platform.set_cursor(CursorIcon::Pointer); } }; let onmouseover = { to_owned![clicking, onmoved]; move |e: MouseEvent| { + e.stop_propagation(); if *clicking.peek() { let coordinates = e.get_element_coordinates(); let x = coordinates.x - size.area.min_x() as f64 - 6.0; @@ -135,6 +138,7 @@ pub fn Slider( let onmousedown = { to_owned![clicking, onmoved, focus]; move |e: MouseEvent| { + e.stop_propagation(); focus.focus(); clicking.set(true); let coordinates = e.get_element_coordinates(); @@ -151,6 +155,7 @@ pub fn Slider( }; let onwheel = move |e: WheelEvent| { + e.stop_propagation(); let wheel_y = e.get_delta_y().clamp(-1.0, 1.0); let percentage = value + (wheel_y * 2.0); let percentage = percentage.clamp(0.0, 100.0); diff --git a/crates/components/src/switch.rs b/crates/components/src/switch.rs index 75aaf003b..cfbe4e3bb 100644 --- a/crates/components/src/switch.rs +++ b/crates/components/src/switch.rs @@ -59,7 +59,7 @@ pub fn Switch(props: SwitchProps) -> Element { let mut animation = use_animation(|| 0.0); let theme = use_applied_theme!(&props.theme, switch); let platform = use_platform(); - let status = use_signal(SwitchStatus::default); + let mut status = use_signal(SwitchStatus::default); let focus = use_focus(); let focus_id = focus.attribute(); @@ -73,23 +73,30 @@ pub fn Switch(props: SwitchProps) -> Element { } }); + let onmousedown = |e: MouseEvent| { + e.stop_propagation(); + }; + let onmouseleave = { to_owned![platform]; - move |_: MouseEvent| { + move |e: MouseEvent| { + e.stop_propagation(); *status.write() = SwitchStatus::Idle; platform.set_cursor(CursorIcon::default()); } }; - let onmouseenter = move |_: MouseEvent| { + let onmouseenter = move |e: MouseEvent| { + e.stop_propagation(); *status.write() = SwitchStatus::Hovering; - platform.set_cursor(CursorIcon::Hand); + platform.set_cursor(CursorIcon::Pointer); }; let onclick = { let ontoggled = props.ontoggled.clone(); to_owned![focus]; - move |_: MouseEvent| { + move |e: MouseEvent| { + e.stop_propagation(); focus.focus(); ontoggled.call(()); } @@ -142,7 +149,7 @@ pub fn Switch(props: SwitchProps) -> Element { corner_radius: "50", background: "{background}", border: "{border}", - onmousedown: |_| {}, + onmousedown, onmouseenter, onmouseleave, onkeydown, diff --git a/crates/core/src/accessibility/accessibility_manager.rs b/crates/core/src/accessibility/accessibility_manager.rs new file mode 100644 index 000000000..bbe14ec96 --- /dev/null +++ b/crates/core/src/accessibility/accessibility_manager.rs @@ -0,0 +1,203 @@ +use crate::accessibility::*; +use accesskit::{ + Action, DefaultActionVerb, Node, NodeBuilder, NodeClassSet, NodeId as AccessibilityId, Rect, + Role, Tree, TreeUpdate, +}; +use freya_dom::prelude::DioxusNode; +use freya_node_state::AccessibilityNodeState; +use std::sync::{Arc, Mutex}; +use torin::prelude::NodeAreas; + +pub type SharedAccessibilityManager = Arc>; + +pub const ACCESSIBILITY_ROOT_ID: AccessibilityId = AccessibilityId(0); + +/// Manages the Accessibility integration. +pub struct AccessibilityManager { + /// Accessibility Nodes + pub nodes: Vec<(AccessibilityId, Node)>, + /// Accessibility tree + pub node_classes: NodeClassSet, + /// Current focused Accessibility Node. + pub focused_id: AccessibilityId, +} + +impl AccessibilityManager { + pub fn new(focused_id: AccessibilityId) -> Self { + Self { + focused_id, + node_classes: NodeClassSet::default(), + nodes: Vec::default(), + } + } + + /// Wrap it in a `Arc>`. + pub fn wrap(self) -> SharedAccessibilityManager { + Arc::new(Mutex::new(self)) + } + + /// Clear the Accessibility Nodes. + pub fn clear(&mut self) { + self.nodes.clear(); + } + + pub fn push_node(&mut self, id: AccessibilityId, node: Node) { + self.nodes.push((id, node)) + } + + /// Add a Node to the Accessibility Tree. + pub fn add_node( + &mut self, + dioxus_node: &DioxusNode, + node_areas: &NodeAreas, + accessibility_id: AccessibilityId, + node_accessibility: &AccessibilityNodeState, + ) { + let mut builder = NodeBuilder::new(Role::Unknown); + + // Set children + let children = dioxus_node.get_accessibility_children(); + if !children.is_empty() { + builder.set_children(children); + } + + // Set text value + if let Some(alt) = &node_accessibility.alt { + builder.set_value(alt.to_owned()); + } else if let Some(value) = dioxus_node.get_inner_texts() { + builder.set_value(value); + } + + // Set name + if let Some(name) = &node_accessibility.name { + builder.set_name(name.to_owned()); + } + + // Set role + if let Some(role) = node_accessibility.role { + builder.set_role(role); + } + + // Set the area + let area = node_areas.area.to_f64(); + builder.set_bounds(Rect { + x0: area.min_x(), + x1: area.max_x(), + y0: area.min_y(), + y1: area.max_y(), + }); + + // Set focusable action + if node_accessibility.focusable { + builder.add_action(Action::Focus); + } else { + builder.add_action(Action::Default); + builder.set_default_action_verb(DefaultActionVerb::Focus); + } + + // Insert the node into the Tree + let node = builder.build(&mut self.node_classes); + self.push_node(accessibility_id, node); + } + + /// Update the focused Node ID and generate a TreeUpdate if necessary. + pub fn set_focus_with_update(&mut self, new_focus_id: AccessibilityId) -> Option { + self.focused_id = new_focus_id; + + // Only focus the element if it exists + let node_focused_exists = self.nodes.iter().any(|node| node.0 == new_focus_id); + if node_focused_exists { + Some(TreeUpdate { + nodes: Vec::new(), + tree: None, + focus: self.focused_id, + }) + } else { + None + } + } + + /// Create the root Accessibility Node. + pub fn build_root(&mut self, root_name: &str) -> Node { + let mut builder = NodeBuilder::new(Role::Window); + builder.set_name(root_name.to_string()); + builder.set_children( + self.nodes + .iter() + .map(|(id, _)| *id) + .collect::>(), + ); + + builder.build(&mut self.node_classes) + } + + /// Process the Nodes accessibility Tree + pub fn process(&mut self, root_id: AccessibilityId, root_name: &str) -> TreeUpdate { + let root = self.build_root(root_name); + let mut nodes = vec![(root_id, root)]; + nodes.extend(self.nodes.clone()); + nodes.reverse(); + + let focus = self + .nodes + .iter() + .find_map(|node| { + if node.0 == self.focused_id { + Some(node.0) + } else { + None + } + }) + .unwrap_or(ACCESSIBILITY_ROOT_ID); + + TreeUpdate { + nodes, + tree: Some(Tree::new(root_id)), + focus, + } + } + + /// Focus the next/previous Node starting from the currently focused Node. + pub fn set_focus_on_next_node(&mut self, direction: AccessibilityFocusDirection) -> TreeUpdate { + let node_index = self + .nodes + .iter() + .enumerate() + .find(|(_, node)| node.0 == self.focused_id) + .map(|(i, _)| i); + + let target_node = if direction == AccessibilityFocusDirection::Forward { + // Find the next Node + if let Some(node_index) = node_index { + if node_index == self.nodes.len() - 1 { + self.nodes.first() + } else { + self.nodes.get(node_index + 1) + } + } else { + self.nodes.first() + } + } else { + // Find the previous Node + if let Some(node_index) = node_index { + if node_index == 0 { + self.nodes.get(node_index - 1) + } else { + self.nodes.last() + } + } else { + self.nodes.last() + } + }; + + self.focused_id = target_node + .map(|(id, _)| *id) + .unwrap_or(ACCESSIBILITY_ROOT_ID); + + TreeUpdate { + nodes: Vec::new(), + tree: None, + focus: self.focused_id, + } + } +} diff --git a/crates/core/src/accessibility/accessibility_provider.rs b/crates/core/src/accessibility/accessibility_provider.rs deleted file mode 100644 index 3349ee164..000000000 --- a/crates/core/src/accessibility/accessibility_provider.rs +++ /dev/null @@ -1,265 +0,0 @@ -use accesskit::{ - Action, DefaultActionVerb, Node, NodeBuilder, NodeClassSet, NodeId as AccessibilityId, Rect, - Role, Tree, TreeUpdate, -}; -use dioxus_native_core::{ - prelude::{NodeType, TextNode}, - real_dom::NodeImmutable, - NodeId, -}; -use freya_dom::prelude::{DioxusDOM, DioxusNode}; -use freya_node_state::AccessibilityState; -use std::slice::Iter; -use tokio::sync::watch; -use torin::{prelude::NodeAreas, torin::Torin}; - -use crate::layout::*; - -/// Direction for the next Accessibility Node to be focused. -#[derive(PartialEq)] -pub enum AccessibilityFocusDirection { - Forward, - Backward, -} - -pub trait AccessibilityProvider { - /// Add a Node to the Accessibility Tree. - fn add_node( - &mut self, - dioxus_node: &DioxusNode, - node_areas: &NodeAreas, - accessibility_id: AccessibilityId, - node_accessibility: &AccessibilityState, - ) { - let mut builder = NodeBuilder::new(Role::Unknown); - - // Set children - let children = dioxus_node.get_accessibility_children(); - if !children.is_empty() { - builder.set_children(children); - } - - // Set text value - if let Some(alt) = &node_accessibility.alt { - builder.set_value(alt.to_owned()); - } else if let Some(value) = dioxus_node.get_inner_texts() { - builder.set_value(value); - } - - // Set name - if let Some(name) = &node_accessibility.name { - builder.set_name(name.to_owned()); - } - - // Set role - if let Some(role) = node_accessibility.role { - builder.set_role(role); - } - - // Set the area - let area = node_areas.area.to_f64(); - builder.set_bounds(Rect { - x0: area.min_x(), - x1: area.max_x(), - y0: area.min_y(), - y1: area.max_y(), - }); - - // Set focusable action - if node_accessibility.focusable { - builder.add_action(Action::Focus); - } else { - builder.add_action(Action::Default); - builder.set_default_action_verb(DefaultActionVerb::Focus); - } - - // Insert the node into the Tree - let node = builder.build(self.node_classes()); - self.push_node(accessibility_id, node); - } - - /// Push a Node into the Accesibility Tree. - fn push_node(&mut self, id: AccessibilityId, node: Node); - - /// Mutable reference to the NodeClassSet. - fn node_classes(&mut self) -> &mut NodeClassSet; - - /// Iterator over the Accessibility Tree of Nodes. - fn nodes(&self) -> Iter<(AccessibilityId, Node)>; - - /// Get the currently focused Node's ID. - fn focus_id(&self) -> Option; - - /// Update the focused Node ID. - fn set_focus(&mut self, new_focus_id: Option); - - /// Update the focused Node ID and generate a TreeUpdate if necessary. - fn set_focus_with_update( - &mut self, - new_focus_id: Option, - ) -> Option { - self.set_focus(new_focus_id); - - // Only focus the element if it exists - let node_focused_exists = self.nodes().any(|node| Some(node.0) == new_focus_id); - if node_focused_exists { - Some(TreeUpdate { - nodes: Vec::new(), - tree: None, - focus: self.focus_id(), - }) - } else { - None - } - } - - /// Create the root Accessibility Node. - fn build_root(&mut self, root_name: &str) -> Node { - let mut builder = NodeBuilder::new(Role::Window); - builder.set_name(root_name.to_string()); - builder.set_children( - self.nodes() - .map(|(id, _)| *id) - .collect::>(), - ); - - builder.build(self.node_classes()) - } - - /// Process the Nodes accessibility Tree - fn process(&mut self, root_id: AccessibilityId, root_name: &str) -> TreeUpdate { - let root = self.build_root(root_name); - let mut nodes = vec![(root_id, root)]; - nodes.extend(self.nodes().cloned()); - nodes.reverse(); - - let focus = self.nodes().find_map(|node| { - if Some(node.0) == self.focus_id() { - Some(node.0) - } else { - None - } - }); - - TreeUpdate { - nodes, - tree: Some(Tree::new(root_id)), - focus, - } - } - - /// Focus the next/previous Node starting from the currently focused Node. - fn set_focus_on_next_node( - &mut self, - direction: AccessibilityFocusDirection, - focus_sender: &watch::Sender>, - ) -> Option { - // Start from the focused node or from the first registered node - let focused_node_id = self.focus_id().or(self.nodes().nth(0).map(|node| node.0)); - if let Some(focused_node_id) = focused_node_id { - let current_node = self - .nodes() - .enumerate() - .find(|(_, node)| node.0 == focused_node_id) - .map(|(i, _)| i); - - if let Some(node_index) = current_node { - let target_node_index = if direction == AccessibilityFocusDirection::Forward { - // Find the next Node - if node_index == self.nodes().len() - 1 { - 0 - } else { - node_index + 1 - } - } else { - // Find the previous Node - if node_index == 0 { - self.nodes().len() - 1 - } else { - node_index - 1 - } - }; - - let target_node = self - .nodes() - .enumerate() - .find(|(i, _)| *i == target_node_index) - .map(|(_, node)| node.0); - - self.set_focus(target_node); - } else { - // Select the first Node - self.set_focus(self.nodes().next().map(|(id, _)| *id)) - } - - focus_sender.send(self.focus_id()).ok(); - - Some(TreeUpdate { - nodes: Vec::new(), - tree: None, - focus: self.focus_id(), - }) - } else { - None - } - } -} - -/// Shortcut functions to retrieve Acessibility info from a Dioxus Node -trait NodeAccessibility { - /// Return the first TextNode from this Node - fn get_inner_texts(&self) -> Option; - - /// Collect all the AccessibilityIDs from a Node's children - fn get_accessibility_children(&self) -> Vec; -} - -impl NodeAccessibility for DioxusNode<'_> { - /// Return the first TextNode from this Node - fn get_inner_texts(&self) -> Option { - let children = self.children(); - let first_child = children.first()?; - let node_type = first_child.node_type(); - if let NodeType::Text(TextNode { text, .. }) = &*node_type { - Some(text.to_owned()) - } else { - None - } - } - - /// Collect all the AccessibilityIDs from a Node's children - fn get_accessibility_children(&self) -> Vec { - self.children() - .iter() - .filter_map(|child| { - let node_accessibility = &*child.get::().unwrap(); - node_accessibility.focus_id - }) - .collect::>() - } -} - -pub fn process_accessibility( - layers: &Layers, - layout: &Torin, - rdom: &DioxusDOM, - access_provider: &mut impl AccessibilityProvider, -) { - for layer in layers.layers.values() { - for node_id in layer { - let node_areas = layout.get(*node_id).unwrap(); - let dioxus_node = rdom.get(*node_id); - if let Some(dioxus_node) = dioxus_node { - let node_accessibility = &*dioxus_node.get::().unwrap(); - if let Some(accessibility_id) = node_accessibility.focus_id { - access_provider.add_node( - &dioxus_node, - node_areas, - accessibility_id, - node_accessibility, - ); - } - } - } - } -} diff --git a/crates/core/src/accessibility/accessibility_state.rs b/crates/core/src/accessibility/accessibility_state.rs deleted file mode 100644 index c8c6b3a88..000000000 --- a/crates/core/src/accessibility/accessibility_state.rs +++ /dev/null @@ -1,61 +0,0 @@ -use crate::accessibility::*; -use accesskit::{Node, NodeClassSet, NodeId as AccessibilityId}; -use std::{ - num::NonZeroU128, - sync::{Arc, Mutex}, -}; - -pub type SharedAccessibilityState = Arc>; - -pub const ROOT_ID: AccessibilityId = AccessibilityId(unsafe { NonZeroU128::new_unchecked(1) }); - -/// Manages the Accessibility integration. -#[derive(Default)] -pub struct AccessibilityState { - /// Accessibility Nodes - pub nodes: Vec<(AccessibilityId, Node)>, - - /// Accessibility tree - pub node_classes: NodeClassSet, - - /// Current focused Accessibility Node. - pub focus: Option, -} - -impl AccessibilityState { - pub fn new() -> Self { - Self::default() - } - - /// Wrap it in a `Arc>`. - pub fn wrap(self) -> SharedAccessibilityState { - Arc::new(Mutex::new(self)) - } - - /// Clear the Accessibility Nodes. - pub fn clear(&mut self) { - self.nodes.clear(); - } -} - -impl AccessibilityProvider for AccessibilityState { - fn node_classes(&mut self) -> &mut NodeClassSet { - &mut self.node_classes - } - - fn nodes(&self) -> std::slice::Iter<(AccessibilityId, Node)> { - self.nodes.iter() - } - - fn focus_id(&self) -> Option { - self.focus - } - - fn set_focus(&mut self, new_focus_id: Option) { - self.focus = new_focus_id; - } - - fn push_node(&mut self, id: AccessibilityId, node: Node) { - self.nodes.push((id, node)) - } -} diff --git a/crates/core/src/accessibility/mod.rs b/crates/core/src/accessibility/mod.rs index 0ef7059dd..b202b16ed 100644 --- a/crates/core/src/accessibility/mod.rs +++ b/crates/core/src/accessibility/mod.rs @@ -1,5 +1,80 @@ -pub mod accessibility_provider; -pub mod accessibility_state; +pub mod accessibility_manager; +pub use accessibility_manager::*; -pub use accessibility_provider::*; -pub use accessibility_state::*; +use accesskit::NodeId as AccessibilityId; +use dioxus_native_core::{ + node::{NodeType, TextNode}, + real_dom::NodeImmutable, + NodeId, +}; +use freya_dom::dom::{DioxusDOM, DioxusNode}; +use freya_node_state::AccessibilityNodeState; +use torin::torin::Torin; + +use crate::layout::Layers; + +/// Direction for the next Accessibility Node to be focused. +#[derive(PartialEq)] +pub enum AccessibilityFocusDirection { + Forward, + Backward, +} + +/// Shortcut functions to retrieve Acessibility info from a Dioxus Node +trait NodeAccessibility { + /// Return the first TextNode from this Node + fn get_inner_texts(&self) -> Option; + + /// Collect all the AccessibilityIDs from a Node's children + fn get_accessibility_children(&self) -> Vec; +} + +impl NodeAccessibility for DioxusNode<'_> { + /// Return the first TextNode from this Node + fn get_inner_texts(&self) -> Option { + let children = self.children(); + let first_child = children.first()?; + let node_type = first_child.node_type(); + if let NodeType::Text(TextNode { text, .. }) = &*node_type { + Some(text.to_owned()) + } else { + None + } + } + + /// Collect all the AccessibilityIDs from a Node's children + fn get_accessibility_children(&self) -> Vec { + self.children() + .iter() + .filter_map(|child| { + let node_accessibility = &*child.get::().unwrap(); + node_accessibility.accessibility_id + }) + .collect::>() + } +} + +pub fn process_accessibility( + layers: &Layers, + layout: &Torin, + rdom: &DioxusDOM, + accessibility_manager: &mut AccessibilityManager, +) { + for layer in layers.layers.values() { + for node_id in layer { + let node_areas = layout.get(*node_id).unwrap(); + let dioxus_node = rdom.get(*node_id); + if let Some(dioxus_node) = dioxus_node { + let node_accessibility = &*dioxus_node.get::().unwrap(); + if let Some(accessibility_id) = node_accessibility.accessibility_id { + accessibility_manager.add_node( + &dioxus_node, + node_areas, + accessibility_id, + node_accessibility, + ); + } + } + } + } +} diff --git a/crates/core/src/events/events_measurer.rs b/crates/core/src/events/events_measurer.rs index 2361d3600..b0f83fbc6 100644 --- a/crates/core/src/events/events_measurer.rs +++ b/crates/core/src/events/events_measurer.rs @@ -1,8 +1,8 @@ use crate::layout::{Layers, Viewports}; -use dioxus_native_core::prelude::NodeImmutableDioxusExt; use dioxus_native_core::real_dom::NodeImmutable; use dioxus_native_core::NodeId; -use freya_dom::prelude::FreyaDOM; +use dioxus_native_core::{prelude::NodeImmutableDioxusExt, tree::TreeRef}; +use freya_dom::{dom::DioxusDOM, prelude::FreyaDOM}; use freya_engine::prelude::*; use freya_node_state::{Fill, Style}; @@ -10,7 +10,7 @@ use rustc_hash::FxHashMap; pub use crate::events::{DomEvent, ElementsState, FreyaEvent}; -use crate::types::{EventEmitter, EventsQueue, NodesEvents}; +use crate::types::{EventEmitter, EventsQueue, PotentialEvents}; /// Process the events and emit them to the VirtualDOM pub fn process_events( @@ -26,18 +26,18 @@ pub fn process_events( let global_events = measure_global_events(events); // 2. Get potential events that could be emitted based on the elements layout and viewports - let mut potential_events = measure_potential_event_listeners(layers, events, viewports, dom); + let potential_events = measure_potential_event_listeners(layers, events, viewports, dom); // 3. Get what events can be actually emitted based on what elements are listening - let dom_events = measure_dom_events(&mut potential_events, dom, scale_factor); + let dom_events = measure_dom_events(potential_events, dom, scale_factor); // 4. Filter the dom events and get potential derived events, e.g mouseover -> mouseenter - let (mut potential_colateral_events, mut to_emit_dom_events) = + let (potential_colateral_events, mut to_emit_dom_events) = elements_state.process_events(&dom_events, events); // 5. Get what derived events can actually be emitted let to_emit_dom_colateral_events = - measure_dom_events(&mut potential_colateral_events, dom, scale_factor); + measure_dom_events(potential_colateral_events, dom, scale_factor); // 6. Join both the dom and colateral dom events and sort them to_emit_dom_events.extend(to_emit_dom_colateral_events); @@ -80,7 +80,7 @@ pub fn measure_potential_event_listeners( events: &EventsQueue, viewports: &Viewports, fdom: &FreyaDOM, -) -> NodesEvents { +) -> PotentialEvents { let mut potential_events = FxHashMap::default(); let layout = fdom.layout(); @@ -161,76 +161,87 @@ fn get_derivated_events(event_name: &str) -> Vec<&str> { } } -const STACKED_EVENTS: [&str; 13] = [ - "mouseover", - "mouseenter", - "mouseleave", - "click", - "keydown", - "keyup", - "touchcancel", - "touchend", - "touchmove", - "touchstart", - "pointerover", - "pointerenter", - "pointerleave", -]; - -const FIRST_CAPTURED_EVENTS: [&str; 1] = ["wheel"]; - -const LAST_CAPTURED_EVENTS: [&str; 3] = ["click", "touchstart", "touchend"]; +fn is_node_parent_of(rdom: &DioxusDOM, node: NodeId, parent_node: NodeId) -> bool { + let mut stack = vec![parent_node]; + while let Some(id) = stack.pop() { + let tree = rdom.tree_ref(); + let mut children = tree.children_ids(id); + drop(tree); + if children.contains(&node) { + return true; + } + + if rdom.contains(id) { + children.reverse(); + stack.extend(children.iter()); + } + } + + false +} /// Measure what DOM events could be emited fn measure_dom_events( - potential_events: &mut NodesEvents, + potential_events: PotentialEvents, fdom: &FreyaDOM, scale_factor: f64, ) -> Vec { let mut new_events = Vec::new(); let rdom = fdom.rdom(); - for (event_name, event_nodes) in potential_events.iter_mut() { + // Iterate over all the events + for (event_name, event_nodes) in potential_events { let derivated_events = get_derivated_events(event_name.as_str()); let mut found_nodes: Vec<(&NodeId, FreyaEvent)> = Vec::new(); - for derivated_event_name in derivated_events { + + // Iterate over the derivated event (including the source) + 'event: for derivated_event_name in derivated_events.iter() { + let mut child_node: Option = None; + let listeners = rdom.get_listening_sorted(derivated_event_name); - 'event_nodes: for (node_id, request) in event_nodes.iter() { + + // Iterate over the event nodes + for (node_id, event) in event_nodes.iter().rev() { + let Some(node) = rdom.get(*node_id) else { + continue; + }; + + // Iterate over the event listeners for listener in &listeners { if listener.id() == *node_id { - let Style { background, .. } = &*listener.get:: + \ No newline at end of file