diff --git a/.github/workflows/deploy_docs.yml b/.github/workflows/deploy_docs.yml index fea1e7590..b09cb8734 100644 --- a/.github/workflows/deploy_docs.yml +++ b/.github/workflows/deploy_docs.yml @@ -23,7 +23,7 @@ jobs: prefix-key: "3" - name: cargo doc - run: cargo doc --no-deps --workspace + run: cargo doc --no-deps --workspace --features docs - name: Upload to Deno Deploy uses: denoland/deployctl@v1 diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 3c73ae9d9..ae33afa9e 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -50,7 +50,7 @@ jobs: if: runner.os != 'Linux' run: cargo nextest run --workspace --exclude examples - name: Run doctests - run: cargo test --workspace --doc + run: cargo test --workspace --doc --features docs - name: Run coverage if: runner.os == 'Linux' run: | diff --git a/Cargo.toml b/Cargo.toml index 0deac8d48..4f33c7d24 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,6 +15,7 @@ custom-tokio-rt = ["freya/custom-tokio-rt"] performance-overlay = ["freya/performance-overlay"] fade-cached-incremental-areas = ["freya/fade-cached-incremental-areas"] disable-zoom-shortcuts = ["freya/disable-zoom-shortcuts"] +docs = ["freya/docs"] [patch.crates-io] # dioxus = { git = "https://github.com/DioxusLabs/dioxus", rev = "7beacdf9c76ae5412d3c2bcd55f7c5d87f486a0f" } @@ -52,6 +53,7 @@ dioxus-core = { version = "0.5" } dioxus-hot-reload = { version = "0.5", features = ["file_watcher"], default-features = false } dioxus-router = { version = "0.5", default-features = false } dioxus-clipboard = "0.1" +dioxus-i18n = "0.3" skia-safe = { version = "0.80.0", features = ["gl", "textlayout", "svg"] } @@ -84,7 +86,7 @@ freya-core = { workspace = true } freya-testing = { workspace = true } reqwest = { version = "0.12.0", features = ["json"] } serde = "1.0.189" -dioxus-i18n = "0.2" +dioxus-i18n = { workspace = true } rand = "0.8.5" dioxus-router = { workspace = true } itertools = "0.13.0" diff --git a/book/src/SUMMARY.md b/book/src/SUMMARY.md index b1906f236..2855a0574 100644 --- a/book/src/SUMMARY.md +++ b/book/src/SUMMARY.md @@ -1,5 +1,3 @@ -# Summary - # Introduction - [Overview](./index.md) @@ -14,4 +12,4 @@ # Other - [Differences with Dioxus](differences_with_dioxus.md) - [FAQ](./faq.md) -- [Contact](./contact.md) +- [Contact](./contact.md) \ No newline at end of file diff --git a/book/src/guides/devtools.md b/book/src/guides/devtools.md index 38248d76d..ac1282197 100644 --- a/book/src/guides/devtools.md +++ b/book/src/guides/devtools.md @@ -1,12 +1,12 @@ -# Devtools - -Devtools can be enabled by enabling the `devtools` to Freya. - -```toml -// Cargo.toml - -[dependencies] -freya = { version = "0.2", features = ["devtools"] } - -``` - +# Devtools + +Devtools can be enabled by enabling the `devtools` to Freya. + +```toml +// Cargo.toml + +[dependencies] +freya = { version = "0.2", features = ["devtools"] } + +``` + diff --git a/crates/components/src/image.rs b/crates/components/src/image.rs index bc4257e4d..e291b2d0f 100644 --- a/crates/components/src/image.rs +++ b/crates/components/src/image.rs @@ -1,6 +1,6 @@ /// Generate a Dioxus component rendering the specified image. /// -/// Example: +/// ### Example /// /// ```no_run /// # use freya::prelude::*; diff --git a/crates/components/src/svg.rs b/crates/components/src/svg.rs index 70036bb60..90661f4ac 100644 --- a/crates/components/src/svg.rs +++ b/crates/components/src/svg.rs @@ -1,6 +1,6 @@ /// Generate a Dioxus component rendering the specified SVG. /// -/// Example: +/// ### Example /// /// ```no_run /// # use freya::prelude::*; diff --git a/crates/components/src/tabs.rs b/crates/components/src/tabs.rs index bbcb7d8ef..9887bf9c6 100644 --- a/crates/components/src/tabs.rs +++ b/crates/components/src/tabs.rs @@ -34,7 +34,7 @@ pub enum TabStatus { Hovering, } -/// Clickable Tab. Usually used in combination with [`Tabsbar`], [`Link`] and [`ActivableRoute`]. +/// Clickable Tab. Usually used in combination with [`Tabsbar`], [`crate::Link`] and [`crate::ActivableRoute`]. /// /// # Styling /// Inherits the [`TabTheme`](freya_hooks::TabTheme) theme. @@ -163,7 +163,7 @@ pub fn Tab( } /// Clickable BottomTab. Same thing as Tab but designed to be placed in the bottom of your app, -/// usually used in combination with [`Tabsbar`], [`Link`] and [`ActivableRoute`]. +/// usually used in combination with [`Tabsbar`], [`crate::Link`] and [`crate::ActivableRoute`]. /// /// # Styling /// Inherits the [`BottomTabTheme`](freya_hooks::BottomTabTheme) theme. diff --git a/crates/core/src/elements/utils.rs b/crates/core/src/elements/utils.rs index 4575e653b..1848223fb 100644 --- a/crates/core/src/elements/utils.rs +++ b/crates/core/src/elements/utils.rs @@ -110,7 +110,7 @@ pub trait ElementUtils { /// Check if this element requires any kind of special caching. /// Mainly used for text-like elements with shadows. - /// See [crate::compositor::CompositorCache]. + /// See [crate::render::CompositorCache]. /// Default to `false`. #[inline] fn element_needs_cached_area(&self, _node_ref: &DioxusNode) -> bool { diff --git a/crates/freya/Cargo.toml b/crates/freya/Cargo.toml index 8797c7eda..516f8b815 100644 --- a/crates/freya/Cargo.toml +++ b/crates/freya/Cargo.toml @@ -12,7 +12,7 @@ keywords = ["gui", "ui", "desktop", "skia", "dioxus"] categories = ["gui", "asynchronous"] [package.metadata.docs.rs] -features = ["freya-engine/mocked-engine"] +features = ["freya-engine/mocked-engine", "docs"] no-default-features = true [features] @@ -27,6 +27,7 @@ default = ["skia"] performance-overlay = [] fade-cached-incremental-areas = ["freya-core/fade-cached-incremental-areas"] disable-zoom-shortcuts = ["freya-renderer/disable-zoom-shortcuts"] +docs = ["dep:freya-testing", "dep:dioxus-i18n", "dep:dioxus-router"] [dependencies] freya-devtools = { workspace = true, optional = true } @@ -40,6 +41,11 @@ freya-components = { workspace = true } freya-engine = { workspace = true } torin = { workspace = true } + +freya-testing = { workspace = true, optional = true } +dioxus-i18n = { workspace = true, optional = true } +dioxus-router = { workspace = true, optional = true } + dioxus = { workspace = true } dioxus-core-macro = { workspace = true } dioxus-hooks = { workspace = true } diff --git a/crates/freya/src/_docs/accessibility.rs b/crates/freya/src/_docs/accessibility.rs new file mode 100644 index 000000000..4b3e716f2 --- /dev/null +++ b/crates/freya/src/_docs/accessibility.rs @@ -0,0 +1,24 @@ +//! # Accessibility +//! +//! TODO +//! +//! ### `use_focus` hook +//! +//! ```rust +//! # use freya::prelude::*; +//! fn app() -> Element { +//! let mut my_focus = use_focus(); +//! +//! rsx!( +//! rect { +//! width: "100%", +//! height: "100%", +//! a11y_id: my_focus.attribute(), +//! onclick: move |_| my_focus.focus(), +//! label { +//! "{my_focus.is_focused()}" +//! } +//! } +//! ) +//! } +//! ``` diff --git a/crates/freya/src/_docs/async_tasks.rs b/crates/freya/src/_docs/async_tasks.rs new file mode 100644 index 000000000..3b506ee0c --- /dev/null +++ b/crates/freya/src/_docs/async_tasks.rs @@ -0,0 +1,33 @@ +//! # Async +//! +//! You may run asynchronous code through the different APIs Dioxus provide. You can use other libraries such as tokio to spawn tasks but it's not always the recommended approach as these will not work with the lifecycling of the components. +//! +//! +//! ### `spawn` +//! +//! Simple function to spawn an **async task**, this is primarily targeted for custom hooks or when you wanted to run some async code dynamically from an event listener. +//! +//! Important to know: Tasks spawned with `spawn` will be cancelled when the component their were created is dropped. If you want to have an async tasks not attached to the component you may use `spawn_forever`. +//! +//! ```rust +//! # use freya::prelude::*; +//! fn app() -> Element { +//! rsx!(Button { +//! onclick: |_| { +//! if 1 == 1 { +//! spawn(async move { +//! println!("Hello, World fom an async task!"); +//! }); +//! } +//! } +//! }) +//! } +//! ``` +//! +//! ### `use_future` +//! +//! TODO +//! +//! ### `use_resource` +//! +//! TODO diff --git a/crates/freya/src/_docs/components_and_props.rs b/crates/freya/src/_docs/components_and_props.rs new file mode 100644 index 000000000..29302f750 --- /dev/null +++ b/crates/freya/src/_docs/components_and_props.rs @@ -0,0 +1,105 @@ +//! # Components +//! +//! Freya apps will usually be composed of different components. +//! Components are defined in the form functions that might receive some input as **Props** and return the UI as **Element**. +//! +//! > You can learn more about how the UI is defined in the [UI](./ui.md) chapter. +//! +//! This is how a simple root component looks like: +//! +//! ```rust +//! # use freya::prelude::*; +//! // Usually, the root component of a Freya app is named `app`, +//! // but it is not a requirement +//! fn app() -> Element { +//! rsx!( +//! label { +//! "Hello, World!" +//! } +//! ) +//! } +//! ``` +//! +//! This is obviously fine, but the moment our app grows in size and complexity we might want to split +//! things out in order to maintain a certain level of modularity and reusability. We can do this by spliting the UI in different components +//! +//! For example, lets create a reusable component: +//! +//! ```rust +//! # use freya::prelude::*; +//! fn app() -> Element { +//! rsx!( +//! // By declaring this element using `TextLabel` +//! // we are creating an instance of that component +//! TextLabel { +//! text: "Number 1" +//! } +//! label { +//! "Number 2" +//! } +//! // Another instance of the same component +//! TextLabel { +//! text: "Number 3" +//! } +//! ) +//! } +//! +//! // Reusable component that we might call as many times we want +//! #[component] +//! fn TextLabel(text: String) -> Element { +//! rsx!( +//! label { +//! "{text}" +//! } +//! ) +//! } +//! ``` +//! +//! Notice how we anotate our `TextLabel` component with the macro `#[component]`, this will transform every argument of the function (just `text: String` in this case) to a component prop, so we can later use the component prop in a declarative way in the RSX. +//! +//! For more complex components you might want to put the props in an external struct intead of using the `#[components]` macro: +//! +//! ```rust +//! # use freya::prelude::*; +//! #[derive(Props, PartialEq, Clone)] +//! struct TextLabelProps { +//! text: String +//! } +//! +//! fn TextLabel(TextLabelProps { text }: TextLabelProps) -> Element { +//! rsx!( +//! label { +//! "{text}" +//! } +//! ) +//! } +//! ``` +//! +//! ## Renders +//! +//! Components renders are just when a component function runs, which might be because it is subscribed to a signal and that signal got mutated, or because its props changed. +//! +//! > Even though the naming might give you the impression that it means the app will effectively rerender again, it has nothing to do with it, in fact, a component might render a thousand times but it it doesn't generate a new UI Freya will not rerender it. +//! +//! Consider this simple component: +//! +//! ```rust +//! # use freya::prelude::*; +//! #[component] +//! fn CoolComp() -> Element { +//! let mut count = use_signal(|| 0); +//! +//! // 1 run of this function means 1 render of this component +//! // So, everytime the `count` signal is mutated, the component rerenders/is recalled. +//! +//! rsx!( +//! label { +//! // Update the signal value +//! onclick: move |_| count += 1, +//! +//! // By embedding the count in this text the component is subscried to any change in the `count` siganal +//! "Increase {count}" +//! } +//! ) +//! } +//! ``` diff --git a/crates/freya/src/_docs/development_setup.rs b/crates/freya/src/_docs/development_setup.rs new file mode 100644 index 000000000..7f1e6cf86 --- /dev/null +++ b/crates/freya/src/_docs/development_setup.rs @@ -0,0 +1,50 @@ +//! # Setup +//! +//! Make sure you have [Rust](https://www.rust-lang.org/) and your OS dependencies installed. +//! +//! ### Windows +//! +//! You will need C++ build tools which you can get through Visual Studio 2022, learn more [here](https://learn.microsoft.com/en-us/windows/dev-environment/rust/setup#install-visual-studio-recommended-or-the-microsoft-c-build-tools). +//! +//! ### Linux +//! +//! #### Debian-based (Ubuntu, PopOS, etc) +//! +//! Install these packages: +//! ```sh +//! sudo apt install build-essential libssl-dev pkg-config cmake libgtk-3-dev libclang-dev +//! ``` +//! +//! #### Arch Linux +//! +//! Install these packages: +//! ```sh +//! sudo pacman -S base-devel openssl cmake gtk3 clang +//! ``` +//! +//! #### Fedora +//! +//! Install these packages: +//! +//! ```sh +//! sudo dnf install openssl-devel pkgconf cmake gtk3-devel clang-devel -y +//! sudo dnf groupinstall "Development Tools" "C Development Tools and Libraries" -y +//! ``` +//! +//! #### NixOS +//! +//! Copy this [flake.nix](https://github.com/kuba375/freya-flake) into your project root. Then you can enter a dev shell by `nix develop`. +//! +//! Don't hesitate to contribute so other distros can be added here. +//! +//! ### MacOS +//! +//! No setup required. But feel free to add more if we miss something. +//! +//! ## Custom Linkers +//! +//! The following custom linkers are not supported at the moment: +//! +//! - `mold` +//! +//! If there is another one not supported don't hesitate to add it here. diff --git a/crates/freya/src/_docs/dioxus_fundamentals.rs b/crates/freya/src/_docs/dioxus_fundamentals.rs new file mode 100644 index 000000000..8ea9fa943 --- /dev/null +++ b/crates/freya/src/_docs/dioxus_fundamentals.rs @@ -0,0 +1,15 @@ +//! +//! # Dioxus Fundamentals +//! +//! As you should know by now, Freya runs on 🧬 [Dioxus](https://dioxuslabs.com), a renderer-agnostic UI library for Rust. +//! +//! - [UI](crate::_docs::ui) +//! - [Components](crate::_docs::components_and_props) +//! - [Hooks](crate::_docs::hooks) +//! - [State Management](crate::_docs::state_management) +//! - [Signals](crate::_docs::state_management::signals) +//! - [Global Signals](crate::_docs::state_management::global_signals) +//! - [Lifecycle](crate::_docs::state_management::lifecycle) +//! - [Context](crate::_docs::state_management::context) +//! - [Memoization](crate::_docs::state_management::memoization) +//! - [Async Tasks](crate::_docs::async_tasks) diff --git a/crates/freya/src/_docs/elements.rs b/crates/freya/src/_docs/elements.rs new file mode 100644 index 000000000..1ea08e8e7 --- /dev/null +++ b/crates/freya/src/_docs/elements.rs @@ -0,0 +1,108 @@ +//! # Elements +//! +//! This is an overview of the elements supported in Freya. +//! +//! > For more info check the [API Reference](freya_elements::elements#structs). +//! +//! ### `rect` +//! +//! [`rect`](freya_elements::elements::rect) is a generic element that acts as a container for other elements. +//! +//! You can specify things like **width**, **padding** or even in what **direction** the inner elements are stacked. +//! +//! Example: +//! +//! ```rust +//! # use freya::prelude::*; +//! fn app() -> Element { +//! rsx!( +//! rect { +//! direction: "vertical", +//! label { "Hi!" } +//! } +//! ) +//! } +//! ``` +//! +//! ### `label` +//! +//! [`label`](freya_elements::elements::label) simply let's you display some text. +//! +//! Example: +//! +//! ```rust +//! # use freya::prelude::*; +//! fn app() -> Element { +//! rsx!( +//! label { +//! "Hello World" +//! } +//! ) +//! } +//! ``` +//! +//! ### `paragraph` +//! +//! [`paragraph`](freya_elements::elements::paragraph) element let's you build texts with different styles. +//! +//! This used used with the `text` element. +//! +//! Example: +//! +//! ```rust +//! # use freya::prelude::*; +//! fn app() -> Element { +//! rsx!( +//! paragraph { +//! text { +//! font_size: "15", +//! "Hello, " +//! } +//! text { +//! font_size: "30", +//! "World!" +//! } +//! } +//! ) +//! } +//! ``` +//! +//! ### `image` +//! +//! [`image`](freya_elements::elements::image) element let's you show an image. +//! +//! Example: +//! +//! ```rust +//! # use freya::prelude::*; +//! static RUST_LOGO: &[u8] = include_bytes!("./rust_logo.png"); +//! +//! fn app() -> Element { +//! let image_data = static_bytes(RUST_LOGO); +//! rsx!(image { +//! image_data: image_data, +//! width: "100%", +//! height: "100%", +//! }) +//! } +//! ``` +//! +//! ### `svg` +//! +//! [`svg`](freya_elements::elements::svg) element let's you display an SVG. +//! +//! Example: +//! +//! ```rust +//! # use freya::prelude::*; +//! static FERRIS: &[u8] = include_bytes!("./ferris.svg"); +//! +//! fn app() -> Element { +//! let ferris = static_bytes(FERRIS); +//! rsx!(svg { +//! svg_data: ferris, +//! width: "100%", +//! height: "100%", +//! }) +//! } +//! ``` diff --git a/crates/freya/src/_docs/en-US.ftl b/crates/freya/src/_docs/en-US.ftl new file mode 100644 index 000000000..622c932eb --- /dev/null +++ b/crates/freya/src/_docs/en-US.ftl @@ -0,0 +1,3 @@ +hello_world = Hello, World! + +hello = Hello, {$name}! \ No newline at end of file diff --git a/crates/freya/src/_docs/es-ES.ftl b/crates/freya/src/_docs/es-ES.ftl new file mode 100644 index 000000000..86c1da396 --- /dev/null +++ b/crates/freya/src/_docs/es-ES.ftl @@ -0,0 +1,3 @@ +hello_world = Hola, Mundo! + +hello = Hola, {$name}! \ No newline at end of file diff --git a/crates/freya/src/_docs/ferris.svg b/crates/freya/src/_docs/ferris.svg new file mode 100644 index 000000000..0d8a78b37 --- /dev/null +++ b/crates/freya/src/_docs/ferris.svg @@ -0,0 +1,47 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/crates/freya/src/_docs/hooks.rs b/crates/freya/src/_docs/hooks.rs new file mode 100644 index 000000000..113b2c660 --- /dev/null +++ b/crates/freya/src/_docs/hooks.rs @@ -0,0 +1,110 @@ +//! # Hooks +//! +//! Hooks are special functions to be used inside of Components that lets you handle different things like the state or lifecycle of your component. They are usually prefixed with `use`, e.g `use_signal`, `use_effect`, `use_memo`, etc. +//! +//! # Rules of Hooks +//! +//! Even though hooks appear to be normal functions they are in fact special so you cannot just call them however you want. +//! +//! ## 1. They cannot be called conditionally +//! +//! You cannot do the following because hooks need to maintain their order. So, if one component is calling 3 different hooks in one render, and then in another render if just calls 2, it would be breaking this rule. +//! +//! ❌: +//! ```rust +//! # use freya::prelude::*; +//! #[component] +//! fn MyComponent(value: bool) -> Element { +//! let is_enabled = if value { +//! // This should be moved out of the conditional +//! let state = use_signal(|| value); +//! +//! state() +//! } else { +//! true +//! }; +//! +//! None +//! } +//! ``` +//! +//! ✅: +//! ```rust +//! # use freya::prelude::*; +//! #[component] +//! fn MyComponent(initial_value: bool) -> Element { +//! let is_enabled = use_signal(move || initial_value); +//! +//! None +//! } +//! ``` +//! +//! ## 2. They can only be called inside of Component functions +//! +//! You cannot call them inside of event handlers, futures, etc. +//! +//! ❌: +//! ```rust +//! # use freya::prelude::*; +//! #[component] +//! fn MyComponent() -> Element { +//! let onclick = |_| { +//! let state = use_signal(|| false); +//! }; +//! +//! rsx!( +//! label { +//! onclick, +//! "Hello, World!" +//! } +//! ) +//! } +//! ``` +//! +//! ✅: +//! ```rust +//! # use freya::prelude::*; +//! #[component] +//! fn MyComponent() -> Element { +//! let mut state = use_signal(|| false); +//! +//! let onclick = move |_| { +//! state.set(true); +//! }; +//! +//! rsx!( +//! label { +//! onclick, +//! "Hello, World!" +//! } +//! ) +//! } +//! ``` +//! +//! ## 3. They cannot be called in loops +//! +//! Hooks cannot be called in loops as the numbers of iterations might change between renders. +//! +//! ❌: +//! ```rust +//! # use freya::prelude::*; +//! #[component] +//! fn MyComponent() -> Element { +//! for i in 0..5 { +//! let state = use_signal(|| i); +//! } +//! +//! None +//! } +//! ``` +//! +//! ✅: +//! ```rust +//! # use freya::prelude::*; +//! #[component] +//! fn MyComponent() -> Element { +//! let state = use_signal(|| (0..5).into_iter().collect::>()); +//! +//! None +//! } +//! ``` diff --git a/crates/freya/src/_docs/hot_reload.rs b/crates/freya/src/_docs/hot_reload.rs deleted file mode 100644 index a38a24e41..000000000 --- a/crates/freya/src/_docs/hot_reload.rs +++ /dev/null @@ -1,26 +0,0 @@ -//! # Hot reload -//! -//! Freya supports Dioxus hot reload, which means that you can update the `layout` and `styling` of your app on the fly, without having to recompile your project. -//! -//! ## Setup -//! -//! Before launching your app, you need to initialize the hot-reload context: -//! -//! ```rust, no_run -//! use freya::{ -//! hotreload::FreyaCtx, -//! prelude::*, -//! }; -//! -//! fn main() { -//! dioxus_hot_reload::hot_reload_init!(Config::::default()); -//! -//! launch(app); -//! } -//! -//! # fn app() -> Element { -//! # None -//! # } -//! ``` -//! -//! That is it! diff --git a/crates/freya/src/_docs/i18n.rs b/crates/freya/src/_docs/i18n.rs new file mode 100644 index 000000000..466f1b2f9 --- /dev/null +++ b/crates/freya/src/_docs/i18n.rs @@ -0,0 +1,83 @@ +//! # i18n +//! +//! You may add i18n (localization) support to your Freya app by using the [**dioxus-i18n**](https://github.com/dioxus-community/dioxus-i18n) crate. +//! +//! ```fluent +//! # en-US.ftl +//! +//! hello_world = Hello, World! +//! hello = Hello, {$name}! +//! ``` +//! +//! +//! ```fluent +//! # es-ES.ftl +//! +//! hello_world = Hola, Mundo! +//! hello = Hola, {$name}! +//! ``` +//! +//! ```rust +//! # use freya::prelude::*; +//! # use dioxus_i18n::{prelude::*, t}; +//! # use dioxus_i18n::unic_langid::langid; +//! +//! // main.rs +//! +//! fn main() { +//! # return; +//! launch(app); +//! } +//! +//! #[allow(non_snake_case)] +//! fn Body() -> Element { +//! // Access to the i18n state +//! let mut i18n = i18n(); +//! +//! // Update the current language +//! let change_to_english = move |_| i18n.set_language(langid!("en-US")); +//! let change_to_spanish = move |_| i18n.set_language(langid!("es-ES")); +//! +//! rsx!( +//! rect { +//! rect { +//! direction: "horizontal", +//! Button { +//! onclick: change_to_english, +//! label { +//! "English" +//! } +//! } +//! Button { +//! onclick: change_to_spanish, +//! label { +//! "Spanish" +//! } +//! } +//! } +//! +//! // Get and subscribe to these messages +//! label { { t!("hello_world") } } +//! label { { t!("hello", name: "Dioxus") } } +//! } +//! ) +//! } +//! +//! fn app() -> Element { +//! // Initialize our i18n config +//! use_init_i18n(|| { +//! # return I18nConfig::new(langid!("en-US")); +//! I18nConfig::new(langid!("en-US")) +//! .with_locale(Locale::new_static( +//! langid!("en-US"), +//! include_str!("./en-US.ftl"), +//! )) +//! .with_locale(Locale::new_dynamic( +//! langid!("es-ES"), +//! "./es-ES.ftl", +//! )) +//! }); +//! +//! rsx!(Body {}) +//! } +//! ``` diff --git a/crates/freya/src/_docs/introduction.rs b/crates/freya/src/_docs/introduction.rs new file mode 100644 index 000000000..ce6364a3e --- /dev/null +++ b/crates/freya/src/_docs/introduction.rs @@ -0,0 +1,40 @@ +//! # Introduction +//! +//! **Freya** is a Rust 🦀 library to make GUI applications that are **cross-platform**, which means they will run the same way in Windows, macOS and Linux. +//! +//! It uses a model where you compose the app by splitting the UI in different components that return pieces of UI or call other components, this is because +//! Freya runs on 🧬 [Dioxus](https://dioxuslabs.com), a renderer-agnostic UI library inspired by ReactJS. +//! +//! Even though you might have seen that Dioxus renders to HTML, that it uses WASM, or that it uses CSS. this does not apply to Freya. In fact, Freya only uses some of the core +//! crates of Dioxus, which means that you will be writing Dioxus components and using some of its APIs but, the elements, attributes, styling, layout, events, and more things +//! will be provided by Freya. +//! +//! Freya uses 🎨 [Skia](https://skia.org/) as rendering engine because its a very battle tested library and has great support for a lot of features. +//! +//! #### Example +//! +//! ```rust, no_run +//! # use freya::prelude::*; +//! fn main() { +//! // **Start** your app by specifying the root component and some config parameters +//! launch_with_props(app, "Counter", (400.0, 350.0)); +//! } +//! +//! fn app() -> Element { +//! // Define a **state** +//! let mut count = use_signal(|| 0); +//! +//! // Declare the **UI** +//! rsx!( +//! rect { +//! height: "100%", +//! width: "100%", +//! background: "rgb(35, 35, 35)", +//! color: "white", +//! padding: "12", +//! onclick: move |_| count += 1, // **Update** the state +//! label { "Click to increase -> {count}" } // Display the **state** +//! } +//! ) +//! } +//! ``` diff --git a/crates/freya/src/_docs/mod.rs b/crates/freya/src/_docs/mod.rs index dae281b3f..4a0da78e8 100644 --- a/crates/freya/src/_docs/mod.rs +++ b/crates/freya/src/_docs/mod.rs @@ -1,3 +1,16 @@ +pub mod accessibility; +pub mod async_tasks; +pub mod components_and_props; +pub mod development_setup; pub mod devtools; -pub mod hot_reload; +pub mod dioxus_fundamentals; +pub mod elements; +pub mod hooks; +pub mod i18n; +pub mod introduction; +pub mod performance; +pub mod router; +pub mod state_management; pub mod theming; +pub mod third_party_state; +pub mod ui; diff --git a/crates/freya/src/_docs/performance.rs b/crates/freya/src/_docs/performance.rs new file mode 100644 index 000000000..637988702 --- /dev/null +++ b/crates/freya/src/_docs/performance.rs @@ -0,0 +1,86 @@ +//! # Performance +//! +//! Collection of things to avoid and improve to have a better performance. +//! +//! ### 1. Using use_effect to synchronize state +//! The `use_effect` hook is sometimes missused as a synchronization between states +//! +//! ```rust +//! # use freya::prelude::*; +//! fn app() -> Element { +//! let mut state = use_signal(|| 1); +//! let mut double_state = use_signal(|| 1); +//! +//! use_effect(move || { +//! // Update double_state whenever `state` changes +//! double_state.set(state() * 2) +//! }); +//! +//! rsx!( +//! label { +//! onclick: move |_| state += 1, +//! "{state} * 2 = {double_state}" +//! } +//! ) +//! } +//! ``` +//! +//! This is bad because we are storing a derived value (double_state) in an unnecessary reactive wrapper (signal). +//! The flow would have been: +//! ```ignore +//! (initial) -> state: 0 , double_state: 0 +//! (state gets updated) -> state: 1 , double_state: 0 +//! (effect runs and updates double_state) -> state: 1 , double_state: 1 +//! ``` +//! +//! +//! #### Manual signal derivation +//! +//! We can simply create a temporary variable in which to store the derived value from the signal. +//! Because we are reading `double_state`, whenever `state` changes this component function will reerun, so `double_state` will always be up to date. +//! +//! ```rust +//! # use freya::prelude::*; +//! fn app() -> Element { +//! let mut state = use_signal(|| 1); +//! let double_state = state() * 2; +//! +//! rsx!( +//! label { +//! onclick: move |_| state += 1, +//! "{state} * 2 = {double_state}" +//! } +//! ) +//! } +//! ``` +//! +//! Now, the flow would be: +//! ```ignore +//! (initial) -> state: 0 , double_state: 0 +//! (state gets updated and double_state derived) -> state: 1 , double_state: 1 +//! ``` +//! +//! ### Reactive signal derivation +//! +//! We can also use `use_memo` to memoize derived values. This is very useful for values that are expensive to compute (which isn't the case with simple numeric operation) +//! +//! ```rust +//! # use freya::prelude::*; +//! fn app() -> Element { +//! let mut state = use_signal(|| 1); +//! let double_state = use_memo(move || state() * 2); +//! +//! rsx!( +//! label { +//! onclick: move |_| state += 1, +//! "{state} * 2 = {double_state}" +//! } +//! ) +//! } +//! ``` +//! +//! The flow would be: +//! ```ignore +//! (initial) -> state: 0 , double_state: 0 +//! (state gets updated and double_state memo run synchronously) -> state: 1 , double_state: 1 +//! ``` diff --git a/crates/freya/src/_docs/router.rs b/crates/freya/src/_docs/router.rs new file mode 100644 index 000000000..7d70bf773 --- /dev/null +++ b/crates/freya/src/_docs/router.rs @@ -0,0 +1,97 @@ +//! # Router +//! +//! Freya supports the official [Dioxus Router](https://docs.rs/dioxus-router/latest/dioxus_router/), which means you can declare different pages for your app. The only difference is that you will need to use Freya's custom `Link` component. +//! +//! ### Example +//! +//! ```rust +//! # use freya::prelude::*; +//! # use dioxus_router::prelude::{ +//! # Outlet, +//! # Routable, +//! # Router, +//! # }; +//! fn app() -> Element { +//! /// We place the router renderer in the root component +//! rsx!(Router:: {}) +//! } +//! +//! // Declare your Routes tree as an enum +//! // Every route must have a component with the same name +//! // So for example, `Home` needs to have a `fn Home(...` component +//! // the `Routable` macro will pick it up automatically +//! // so it must be in the scope. +//! #[derive(Routable, Clone, PartialEq)] +//! #[rustfmt::skip] +//! pub enum Route { +//! #[layout(AppSidebar)] +//! #[route("/")] +//! Home, +//! #[route("/other")] +//! Other, +//! #[end_layout] +//! #[route("/..route")] +//! PageNotFound { }, // Handle 404 routes. +//! } +//! +//! // This component is used as container for the router (as it was marked with `#[layout(AppSidebar)]`), which means +//! // That we can render something here that will be rendered no matter what route you are in +//! // Useful for navigation bars. +//! fn AppSidebar() -> Element { +//! rsx!( +//! Body { +//! Link { +//! // Specify to what destination you want this Link you point when clicking. +//! to: Route::Home, +//! label { +//! "Home" +//! } +//! }, +//! Link { +//! to: Route::Other, +//! label { +//! "Other" +//! } +//! }, +//! rect { +//! main_align: "center", +//! cross_align: "center", +//! width: "100%", +//! height: "100%", +//! // This is the place where the routes content will actually be showed. +//! Outlet:: { } +//! } +//! } +//! ) +//! } +//! +//! #[component] +//! fn Home() -> Element { +//! rsx!( +//! label { +//! "Home Page" +//! } +//! ) +//! } +//! +//! #[component] +//! fn Other() -> Element { +//! rsx!( +//! label { +//! "Other Page" +//! } +//! ) +//! } +//! +//! #[component] +//! fn PageNotFound() -> Element { +//! rsx!( +//! label { +//! "404" +//! } +//! ) +//! } +//! ``` + +pub mod animated_transitions; +pub mod native_router; diff --git a/crates/freya/src/_docs/router/animated_transitions.rs b/crates/freya/src/_docs/router/animated_transitions.rs new file mode 100644 index 000000000..d3f5a12fa --- /dev/null +++ b/crates/freya/src/_docs/router/animated_transitions.rs @@ -0,0 +1 @@ + diff --git a/crates/freya/src/_docs/router/native_router.rs b/crates/freya/src/_docs/router/native_router.rs new file mode 100644 index 000000000..761b63924 --- /dev/null +++ b/crates/freya/src/_docs/router/native_router.rs @@ -0,0 +1,32 @@ +//! # Native Router +//! +//! Even though Freya supports Dioxus Router, there are certain integrations that it does not provide, such as as back and forward navigation with the mouse buttons. +//! For things like this exists `NativeRouter`, a thin wrapper component that adds these missing integrations. +//! +//! You simply need to wrap your `Router` content inside the `NativeRouter` component. +//! +//! Example (based on the example from [router](crate::_docs::router)): +//! ```rust, ignore +//! #[allow(non_snake_case)] +//! fn AppSidebar() -> Element { +//! rsx!( +//! NativeRouter { +//! Body { +//! Link { +//! to: Route::Home, +//! label { +//! "Home" +//! } +//! }, +//! Link { +//! to: Route::Other, +//! label { +//! "Other" +//! } +//! }, +//! // Rest of app +//! } +//! } +//! ) +//! } +//! ``` diff --git a/crates/freya/src/_docs/rust_logo.png b/crates/freya/src/_docs/rust_logo.png new file mode 100644 index 000000000..1793f8198 Binary files /dev/null and b/crates/freya/src/_docs/rust_logo.png differ diff --git a/crates/freya/src/_docs/state_management.rs b/crates/freya/src/_docs/state_management.rs new file mode 100644 index 000000000..136422a86 --- /dev/null +++ b/crates/freya/src/_docs/state_management.rs @@ -0,0 +1,17 @@ +//! # State Management +//! +//! Dioxus and Freya apps, have multiple ways of state management. +//! +//! See the different features: +//! +//! - [Signals](crate::_docs::state_management::signals) +//! - [Global Signals](crate::_docs::state_management::global_signals) +//! - [Lifecycle](crate::_docs::state_management::lifecycle) +//! - [Context](crate::_docs::state_management::context) +//! - [Memoization](crate::_docs::state_management::memoization) + +pub mod context; +pub mod global_signals; +pub mod lifecycle; +pub mod memoization; +pub mod signals; diff --git a/crates/freya/src/_docs/state_management/context.rs b/crates/freya/src/_docs/state_management/context.rs new file mode 100644 index 000000000..f407aed62 --- /dev/null +++ b/crates/freya/src/_docs/state_management/context.rs @@ -0,0 +1,163 @@ +//! # Context +//! +//! Dioxus offers a way to pass data from parent components to its descendents in a way to avoid [**Prop Drilling**](#prop-drilling). +//! +//! ## Prop Drilling +//! +//! **Prop drilling** is when you want to pass a certain data from one parent component to some nested component, and you start to declare the same prop in each one of the components in between the parent and the target component. This causes a huge unnecessary boilerplate that can be used by using the Context API. +//! +//! ```rust +//! # use freya::prelude::*; +//! // Parent component +//! #[component] +//! fn CompA() -> Element { +//! rsx!( +//! CompB { +//! value: 2 +//! } +//! ) +//! } +//! +//! // This component needs the value just so it can pass it to the next component +//! #[component] +//! fn CompB(value: usize) -> Element { +//! rsx!( +//! CompC { +//! value +//! } +//! ) +//! } +//! +//! // Same as CompB +//! #[component] +//! fn CompC(value: usize) -> Element { +//! rsx!( +//! CompD { +//! value +//! } +//! ) +//! } +//! +//! // Finally ! Our target component +//! #[component] +//! fn CompD(value: usize) -> Element { +//! rsx!( +//! label { +//! "{value}" +//! } +//! ) +//! } +//! ``` +//! +//! ## Context +//! +//! You can initialize a context that will be identified by its type and be allowed to be accessed from any desdendent from where you intialized it. +//! +//! ```rust +//! # use freya::prelude::*; +//! // Parent component +//! #[component] +//! fn CompA() -> Element { +//! // Initialize the context with `1` usize as value +//! // Any component desdendant of `CompA` will be allowed to access this component +//! use_context_provider(|| 1); +//! +//! rsx!( +//! CompB { } +//! ) +//! } +//! +//! #[component] +//! fn CompB() -> Element { +//! rsx!( +//! CompC { } +//! ) +//! } +//! +//! #[component] +//! fn CompC() -> Element { +//! rsx!( +//! CompD { } +//! ) +//! } +//! +//! // Our target component +//! #[component] +//! fn CompD() -> Element { +//! // Retrieve our context as we know its type +//! let value = use_context::(); +//! +//! rsx!( +//! label { +//! "{value}" +//! } +//! ) +//! } +//! ``` +//! +//! ### Avoid having context with same type +//! +//! Because Context are identified by their type, you cannot do the following: +//! +//! ```rust +//! # use freya::prelude::*; +//! // Parent component +//! #[component] +//! fn CompA() -> Element { +//! use_context_provider(|| 1); +//! use_context_provider(|| 2); +//! use_context_provider(|| 3); +//! +//! // All these 3 contexts share the same type, `usize`, so each context will replace any other context defined previously, which means that only the third context will actually be accessible +//! +//! rsx!( +//! CompB { } +//! ) +//! } +//! +//! # #[component] +//! # fn CompB() -> Element { +//! # None +//! # } +//! ``` +//! +//! If you really need to the tree contexts split you can wrap them in different types so each one gets an unique type instead of just `usize`. +//! +//! ```rust +//! # use freya::prelude::*; +//! +//! #[derive(Clone)] +//! struct ValueA(pub usize); +//! +//! #[derive(Clone)] +//! struct ValueB(pub usize); +//! +//! #[derive(Clone)] +//! struct ValueC(pub usize); +//! +//! // Parent component +//! #[component] +//! fn CompA() -> Element { +//! use_context_provider(|| ValueA(1)); +//! use_context_provider(|| ValueB(2)); +//! use_context_provider(|| ValueC(3)); +//! +//! // All these 3 contexts share the same type, `usize`, so each context will replace any other context defined previously, which means that only the third context will actually be accessible +//! +//! rsx!( +//! CompB { } +//! ) +//! } +//! +//! #[component] +//! fn CompB() -> Element { +//! let value_a = use_context::(); +//! let value_b = use_context::(); +//! let value_c = use_context::(); +//! rsx!( +//! label { +//! "{value_a.0} {value_b.0} {value_c.0}" +//! } +//! ) +//! } +//! ``` diff --git a/crates/freya/src/_docs/state_management/global_signals.rs b/crates/freya/src/_docs/state_management/global_signals.rs new file mode 100644 index 000000000..3a395f32c --- /dev/null +++ b/crates/freya/src/_docs/state_management/global_signals.rs @@ -0,0 +1,34 @@ +//! # Global Signals +//! +//! Global Signals behave like Signals but you declare them statitically and you don't need to pass them through props or context as you can just import it. +//! Main use case is for apps, not libraries. +//! +//! ### Example +//! +//! ```rust +//! # use freya::prelude::*; +//! static COUNT: GlobalSignal = Signal::global(|| 0); +//! +//! fn app() -> Element { +//! let onclick = move |_| { +//! *COUNT.write() += 1; // Modify the global signal, as if it was a normal signal +//! }; +//! +//! rsx!( +//! label { +//! onclick, +//! "{COUNT}" // Read the global signal +//! } +//! SomeOtherComp {} +//! ) +//! } +//! +//! #[component] +//! fn SomeOtherComp() -> Element { +//! rsx!( +//! label { +//! "{COUNT}" // We can use the global signal here again +//! } +//! ) +//! } +//! ``` diff --git a/crates/freya/src/_docs/state_management/lifecycle.rs b/crates/freya/src/_docs/state_management/lifecycle.rs new file mode 100644 index 000000000..54163e164 --- /dev/null +++ b/crates/freya/src/_docs/state_management/lifecycle.rs @@ -0,0 +1,79 @@ +//! # Lifecycle +//! +//! Dioxus components can use hooks to manage certain lifecycle situations. +//! +//! ## Component creation +//! You can run certain logic when the component is created for the first time by using the `use_hook` hook. +//! +//! ```rust +//! # use freya::prelude::*; +//! fn app() -> Element { +//! use_hook(|| { +//! println!("Component running for the first time!"); +//! }); +//! +//! None +//! } +//! ``` +//! +//! ## Component destroyment +//! +//! Run some logic when the component is being destroyed. +//! +//! ```rust +//! # use freya::prelude::*; +//! fn app() -> Element { +//! use_drop(|| { +//! println!("Component is being dropped."); +//! }); +//! +//! None +//! } +//! ``` +//! +//! ## Side effects +//! +//! Run some logic when a signal is changed. +//! +//! ```rust +//! # use freya::prelude::*; +//! fn app() -> Element { +//! let mut signal = use_signal(|| 1); +//! +//! use_effect(move || { +//! // Because we are reading this signal +//! // now the effect is subscribed to any change +//! let value = signal(); +//! println!("Value of signal is {value}"); +//! }); +//! +//! None +//! } +//! ``` +//! +//! ## Side effects with dependencies +//! +//! Run some logic when some values change. +//! +//! ```rust +//! # use freya::prelude::*; +//! fn app() -> Element { +//! let mut signal = use_signal(|| 1); +//! let mut other_signal = use_signal(|| 1); +//! +//! // Manually specify non-signal values that we might want to react to +//! use_effect(use_reactive(&signal, |value| { +//! println!("Value of signal is {value}"); +//! })); +//! +//! // When you need multiple values you can pass a tuple +//! use_effect(use_reactive( +//! &(signal, other_signal), +//! |(value, other_signal)| { +//! println!("Value of signals are {value} and {other_signal}"); +//! }, +//! )); +//! +//! None +//! } +//! ``` diff --git a/crates/freya/src/_docs/state_management/memoization.rs b/crates/freya/src/_docs/state_management/memoization.rs new file mode 100644 index 000000000..b6cc7c15b --- /dev/null +++ b/crates/freya/src/_docs/state_management/memoization.rs @@ -0,0 +1,24 @@ +//! # Memoization +//! +//! You can memoize values by using the `use_memo` hook. This can be useful to have reactive derived across components or to cache expensive value to compute. +//! +//! ```rust +//! # use freya::prelude::*; +//! fn app() -> Element { +//! let mut state = use_signal(|| 1); +//! // `use_memo` returns a `ReadOnlySignal`, as the name says it is a Signal +//! // that you can read and subscribe to but you cannot mutate +//! // as its value can only be changed when the memo runs +//! let double_state = use_memo(move || { +//! // Just like `use_effect`, whenever a signal that is read in here is changed, the memo will rerun. +//! state() * 2 +//! }); +//! +//! rsx!( +//! label { +//! onclick: move |_| state += 1, +//! "{double_state}" +//! } +//! ) +//! } +//! ``` diff --git a/crates/freya/src/_docs/state_management/signals.rs b/crates/freya/src/_docs/state_management/signals.rs new file mode 100644 index 000000000..1bfbe6b4b --- /dev/null +++ b/crates/freya/src/_docs/state_management/signals.rs @@ -0,0 +1,40 @@ +//! # Signals +//! +//! Signals are a state management solution built-in into Dioxus. They are simple reactive value containers that simplify the mutation and reading of state, even across components. +//! +//! They are usually created by using the `use_signal` hook. +//! +//! ### Example +//! +//! ```rust +//! # use freya::prelude::*; +//! fn app() -> Element { +//! let mut count = use_signal(|| 0); +//! // The closure passed to `use_signal` will be called only +//! // the first time this component renders, +//! // it will return the initial value for the Signal. +//! // This closure is to prevent having to create the initial value +//! // every time the component runs again, as it is only needed the first time. +//! +//! let onclick = move |_| { +//! count += 1; // Shorthand for count.write() += 1; +//! // The moment the signal is mutated it will notify +//! // all the components that have a read subscription +//! // to this signal (in this case, only `app`) +//! // that there has been a change. +//! // When that happens they will renders again +//! // and thus producing the new UI. +//! }; +//! +//! rsx!( +//! label { +//! onclick, +//! "{count}" +//! // Because the signal is being read here, +//! // everytime that it gets mutated, this component +//! // will rerender as it has a read subscription. +//! // "{count}" is the same as using "{count.read()}". +//! } +//! ) +//! } +//! ``` diff --git a/crates/freya/src/_docs/theming.rs b/crates/freya/src/_docs/theming.rs index 711474aff..6a379a477 100644 --- a/crates/freya/src/_docs/theming.rs +++ b/crates/freya/src/_docs/theming.rs @@ -1,156 +1,16 @@ -//! # Theming +//! # Themes //! -//!
⚠️ As of 2023-12-19, extending the base theme is not supported.
+//! All the built-in components of Freya support themes, so if you find yourself wanting to tweak a certain style attribute of a component you might want to see if it can be changed through a theme. //! -//! Freya has built-in support for Theming. +//! ## ThemeProvider //! -//! ### Accessing the current theme +//! You can pass a ThemeProvider to your whole app or maybe just a part of it by using the ThemeProvider component. //! -//! You can access the current theme via the `use_get_theme` hook. +//! ### Example //! -//! ```rust, no_run -//! # use freya::prelude::*; -//! fn app() -> Element { -//! rsx!( -//! ThemeProvider { -//! Component { } -//! } -//! ) -//! } -//! -//! #[allow(non_snake_case)] -//! fn Component() -> Element { -//! let theme = use_get_theme(); -//! -//! let button_theme = &theme.button; -//! -//! rsx!( -//! rect { -//! background: "{button_theme.background}", -//! } -//! ) -//! } -//! ``` -//! -//! ## Custom default theme -//! -//! By default, the selected theme is `LIGHT_THEME`. You can use the alternative, `DARK_THEME`. -//! -//! ```rust, no_run -//! # use freya::prelude::*; -//! fn app() -> Element { -//! rsx!( -//! ThemeProvider { -//! theme: LIGHT_THEME, -//! Component { } -//! } -//! ) -//! } -//! -//! #[allow(non_snake_case)] -//! fn Component() -> Element { -//! let theme = use_get_theme(); -//! -//! let button_theme = &theme.button; -//! -//! rsx!( -//! rect { -//! background: "{button_theme.background}", -//! } -//! ) -//! } -//! ``` -//! -//! ## Change the theme -//! -//! Changing the selected theme at runtime is possible by using the `use_theme` hook. -//! -//! ```rust, no_run -//! # use freya::prelude::*; -//! fn app() -> Element { -//! rsx!( -//! ThemeProvider { -//! Component { } -//! } -//! ) -//! } -//! -//! #[allow(non_snake_case)] -//! fn Component() -> Element { -//! let mut theme = use_theme(); -//! -//! let onpress = move |_| { -//! *theme.write() = LIGHT_THEME; -//! }; -//! -//! rsx!( -//! Button { -//! onpress, -//! label { -//! "Use Light theme" -//! } -//! } -//! ) -//! } -//! ``` -//! -//! ## Change theme for an individual component -//! -//! Most built-in components have their own theme "override." -//! You can specify values to override like this: -//! -//! ```rust,no_run -//! # use freya::prelude::*; -//! fn app() -> Element { -//! rsx! { -//! Button { -//! theme: ButtonThemeWith { -//! background: Some("blue".into()), -//! font_theme: Some(FontThemeWith { -//! color: Some("white".into()), -//! ..Default::default() -//! }), -//! ..Default::default() -//! }, -//! label { "I'm blue now" } -//! } -//! } -//! } -//! ``` -//! -//! You need to use a different "type" of theme. -//! In the "ThemeWith" structs, each field is optional, so that the component knows what to override and -//! what to keep. -//! Also, you need to spread `..Default::default`, to make all the other fields `None`. -//! -//! To make this less verbose, you can use the `theme_with!` macro: -//! -//! ```rust,no_run -//! # use freya::prelude::*; -//! fn app() -> Element { -//! rsx! { -//! Button { -//! theme: theme_with!(ButtonTheme { -//! background: "blue".into(), -//! font_theme: theme_with!(FontTheme { -//! color: "white".into(), -//! }), -//! }), -//! label { "I'm blue now" } -//! } -//! } -//! } -//! ``` -//! -//! As you can see, it removes the need for the "With" suffix, because that is already in the macro name. -//! More importantly, though, it wraps each field in a `Some`, and adds the spread. -//! -//! ## Custom theme -//! -//! You can build themes from scratch or extended from others, like here with `LIGHT_THEME`: -//! -//! ```rust, no_run +//! ```rust //! # use freya::prelude::*; +//! // A custom theme based on the Light Theme that simply tweaks some parts of the Button theme. //! const CUSTOM_THEME: Theme = Theme { //! button: ButtonTheme { //! background: Cow::Borrowed("rgb(230, 0, 0)"), @@ -165,6 +25,8 @@ //! //! fn app() -> Element { //! rsx!( +//! // All the components descendant of this ThemeProvider will inherit the Custom Theme +//! // Again, this could be your whole app or maybe just a small part. //! ThemeProvider { //! theme: CUSTOM_THEME, //! rect { @@ -172,7 +34,7 @@ //! height: "100%", //! Button { //! label { -//! "Report" +//! "Cancel" //! } //! } //! } @@ -180,3 +42,27 @@ //! ) //! } //! ``` +//! +//! ## `theme` prop +//! +//! Most of the components also support being tweaked via their `theme` prop and with the help of the `theme_with` macro. +//! +//! ### Example +//! +//! ```rust +//! # use freya::prelude::*; +//! fn app() -> Element { +//! rsx!( +//! Button { +//! // You could pass the whole theme or maybe just a part of it +//! theme: theme_with!(ButtonTheme { +//! background: "red".into(), +//! width: "200".into(), +//! }), +//! label { +//! "Cancel" +//! } +//! } +//! ) +//! } +//! ``` diff --git a/crates/freya/src/_docs/third_party_state.rs b/crates/freya/src/_docs/third_party_state.rs new file mode 100644 index 000000000..11b41b5e9 --- /dev/null +++ b/crates/freya/src/_docs/third_party_state.rs @@ -0,0 +1,6 @@ +//! # Third Party +//! +//! There are other community crates that offer different approaches to state management. +//! +//! - [dioxus-query](https://github.com/marc2332/dioxus-query) +//! - [dioxus-radio](https://github.com/dioxus-community/dioxus-radio) diff --git a/crates/freya/src/_docs/ui.rs b/crates/freya/src/_docs/ui.rs new file mode 100644 index 000000000..a1ebcc931 --- /dev/null +++ b/crates/freya/src/_docs/ui.rs @@ -0,0 +1,116 @@ +//! # UI +//! +//! Freya uses a declarive model for the UI, which means that you do not use imperative APIs but instead you write in a semi-markup language (integrated with Rust) by using the `rsx!()` macro from Dioxus. +//! +//! For example, this is how a simple component would look like in Freya: +//! +//! ```rust, no_run +//! # use freya::prelude::*; +//! fn app() -> Element { +//! rsx!( +//! rect { +//! background: "red", +//! width: "100%", +//! onclick: |_| println!("Clicked!"), +//! label { +//! "Hello, World!" +//! } +//! } +//! ) +//! } +//! ``` +//! +//! Notice that the `app` component is returning an [`Element`](dioxus_core::Element) created by the [`rsx!()`](dioxus_core_macro::rsx!()) macro. So, in other words, the [`Element`](dioxus_core::Element) contains the UI of that component. +//! Every time the component reruns the [`rsx!()`](dioxus_core_macro::rsx!()) will be called again and thus generate a new UI. +//! +//! ### [`rsx!()`](dioxus_core_macro::rsx!()) +//! +//! This macro is not a standalone-language or anything like that. It is simply a macro to easily declare how we want the UI to look like. You can still use normal Rust code inside. +//! +//! The structure for RSX looks like this: +//! +//! ```rust, no_run +//! # use freya::prelude::*; +//! +//! # { rsx!( +//! // Element, always in lower case +//! rect { +//! // Attribute for the element `rect` +//! background: "red", +//! // Attribute for the element `rect` +//! width: "100%", +//! // Event handler for the element `rect`, can be a function or a closure +//! onclick: |_| println!("Clicked!"), +//! // Element child of `rect` +//! label { +//! // Text Element for the element `label` +//! "Hello, World!" +//! } +//! // Component child of `rect`, always in PascalCase +//! CoolComp { +//! // Prop for the component `CoolComp` +//! prop: 123 +//! } +//! } +//! # )}; +//! +//! # #[component] +//! # fn CoolComp(prop: i32) -> Element { None } +//! ``` +//! +//! You can reference variables inside the RSX as well: +//! +//! ```rust, no_run +//! # use freya::prelude::*; +//! let onclick = |_| { +//! println!("Clicked"); +//! }; +//! +//! let width = "100%"; +//! let name = "World"; +//! +//! # { +//! rsx!( +//! rect { +//! background: "red", +//! width, +//! onclick, +//! label { +//! "Hello, {name}!" +//! } +//! label { +//! "{1 + 1} is 2" +//! } +//! } +//! ) +//! # }; +//! ``` +//! +//! Or just use if, for-loops, etc.. Inside of the RSX: +//! +//! ```rust, no_run +//! # use freya::prelude::*; +//! let show_text = false; +//! +//! # { +//! rsx!( +//! rect { +//! for i in 0..5 { +//! label { +//! // Looped elements must have an unique ID specified through +//! // the `key` attribute so Dioxus is able to identify them +//! key: "{i}", +//! "Value -> {i}" +//! } +//! } +//! // When this condition is not met the inner element will +//! // simply not be rendered +//! if show_text { +//! label { +//! "Hello, World!" +//! } +//! } +//! } +//! ) +//! # }; +//! ``` diff --git a/crates/freya/src/lib.rs b/crates/freya/src/lib.rs index 641caff6c..121d794b1 100644 --- a/crates/freya/src/lib.rs +++ b/crates/freya/src/lib.rs @@ -4,44 +4,46 @@ )] //! # Freya //! -//! Build native & cross-platform GUI applications using 🦀 Rust. +//! **Freya** is a declarative, cross-platform GUI Rust library, powered by 🧬 [Dioxus](https://dioxuslabs.com) and 🎨 [Skia](https://skia.org/). //! -//! Powered by [🧬 Dioxus](https://dioxuslabs.com) and [🎨 Skia](https://skia.org/). +//! **It does not use any web tech**, check the [Differences with Dioxus](https://book.freyaui.dev/differences_with_dioxus.html). //! -//! - [Elements API reference](freya_elements::elements#structs) -//! - [Events API reference](freya_elements::elements#functions) -//! - [Elements guides](freya_elements::_docs) -//! - [Components](freya_components) -//! - [Hooks](freya_hooks) +//! ### Basics +//! - [Introduction](self::_docs::introduction) +//! - [Dioxus Fundamentals](self::_docs::dioxus_fundamentals) +//! - [UI](self::_docs::ui) +//! - [Components](self::_docs::components_and_props) +//! - [Hooks](self::_docs::hooks) +//! - [State Management](self::_docs::state_management) +//! - [Signals](self::_docs::state_management::signals) +//! - [Global Signals](self::_docs::state_management::global_signals) +//! - [Lifecycle](self::_docs::state_management::lifecycle) +//! - [Context](self::_docs::state_management::context) +//! - [Memoization](self::_docs::state_management::memoization) +//! - [Async Tasks](self::_docs::async_tasks) +//! +//! ### Learn +//! - [Development Setup](self::_docs::development_setup) +//! - [Elements Overview](self::_docs::elements) //! - [Theming](self::_docs::theming) -//! - [Hot reload](self::_docs::hot_reload) -//! - [Testing](freya_testing) -//! - [Animating](freya_hooks::use_animation) +//! - [i18n](self::_docs::i18n) +//! - [Accessibility](self::_docs::accessibility) +//! - [Text Editing](self::_docs) +//! - [Animations](self::_docs) +//! - [Router](self::_docs::router) +//! - [Native Router](self::_docs::router::native_router) +//! - [Animated transitions](self::_docs::router::animated_transitions) +//! - [Native Menus](self::_docs) +//! - [Third Party State Managemement](self::_docs::third_party_state) +//! - [Unit Testing for Components](freya_testing) //! - [Devtools](self::_docs::devtools) +//! - [Performance Tips](self::_docs::performance) //! -//! ```rust,no_run -//! use freya::prelude::*; -//! -//! fn main(){ -//! launch(app); -//! } -//! -//! fn app() -> Element { -//! let mut count = use_signal(|| 0); -//! -//! rsx!( -//! rect { -//! height: "100%", -//! width: "100%", -//! background: "rgb(35, 35, 35)", -//! color: "white", -//! padding: "12", -//! onclick: move |_| count += 1, -//! label { "Click to increase -> {count}" } -//! } -//! ) -//! } -//! ``` +//! ### API References +//! - [Elements and attributes](freya_elements::elements#structs) +//! - [Events](freya_elements::elements#functions) +//! - [Built-in Components](freya_components) +//! - [Built-in Hooks](freya_hooks) //! //! ## Features flags //!