Skip to main content

freya/_docs/
ui_and_components.rs

1//! # UI and Components
2//!
3//! Freya uses a [declarative](https://en.wikipedia.org/wiki/Declarative_programming) model for the UI.
4//! This means that you dont instantiate e.g Buttons, you simply declare them and Freya will take care of running them and painting them on screen.
5//!
6//! Example of how the UI is declared:
7//!
8//! ```rust, no_run
9//! # use freya::prelude::*;
10//! # fn app() -> impl IntoElement {
11//! rect()
12//!     .background((255, 0, 0))
13//!     .width(Size::fill())
14//!     .height(Size::px(100.))
15//!     .on_mouse_up(|_| println!("Clicked!"))
16//! # }
17//! ```
18//!
19//! You can also split your UI in reusable pieces called **Components**.
20//!
21//! ### [Component](freya_core::prelude::Component) trait
22//!
23//! For normal components you may use the [Component](freya_core::prelude::Component) trait.
24//!
25//! ```rust
26//! # use freya::prelude::*;
27//! #[derive(PartialEq)]
28//! struct App;
29//!
30//! impl Component for App {
31//!     fn render(&self) -> impl IntoElement {
32//!         "Hello, World!"
33//!     }
34//! }
35//! ```
36//!
37//! ## App/Root Component
38//! The app/root component is the component passed to [WindowConfig](crate::prelude::WindowConfig).
39//!
40//! For convenience it can be a `Fn() -> Element` instead of a struct that implements [App](freya_core::prelude::App).
41//!
42//! ```rust
43//! # use freya::prelude::*;
44//! fn app() -> impl IntoElement {
45//!     "Hello, World!"
46//! }
47//! ```
48//!
49//! If you wanted to pass data from your **main** function to your **root** component you would need to make it use a struct that implements the [App](freya_core::prelude::App) trait, like this:
50//!
51//! ```rust, no_run
52//! # use freya::prelude::*;
53//! fn main() {
54//!     launch(LaunchConfig::new().with_window(WindowConfig::new_app(MyApp { number: 1 })))
55//! }
56//!
57//! struct MyApp {
58//!     number: u8,
59//! }
60//!
61//! impl App for MyApp {
62//!     fn render(&self) -> impl IntoElement {
63//!         label().text(self.number.to_string())
64//!     }
65//! }
66//! ```
67//!
68//! To separate the UI of the app you may create more components.
69//!
70//! ```rust
71//! # use freya::prelude::*;
72//! # use std::borrow::Cow;
73//! // Reusable component that we might call as many times we want
74//! #[derive(PartialEq)]
75//! struct TextLabel(Cow<'static, str>);
76//! impl Component for TextLabel {
77//!     fn render(&self) -> impl IntoElement {
78//!         label().text(self.0.clone())
79//!     }
80//! }
81//!
82//! fn app() -> impl IntoElement {
83//!     rect()
84//!         .child(TextLabel("Number 1".into()))
85//!         .child("Number 2")
86//!         .child(TextLabel("Number 3".into()))
87//! }
88//! ```
89//!
90//! Notice how all these component are returning an [`Element`](freya_core::prelude::Element), this is because `rect()` gives you a [`Rect`](freya_core::elements::rect::Rect) which implements `Into<Element>` / `IntoElement`, same happens for the rest of elements.
91//! So, in other words, the [`Element`](freya_core::prelude::Element) contains the UI of that component.
92//! Every time the component render function reruns a new UI is created and later diffed by Freya internally.
93//!
94//! ## Renders
95//!
96//! "Components renders" are simply when the component's `render` function runs, this can happen in multiple scenarios:
97//!
98//! 1. The component just got instantiated for the first time (also called mounted in other UI libraries)
99//! 2. A state that this component is reading (thus subscribed to), got mutated
100//! 3. The component data (also called props) changed (this is why `PartialEq` is required)
101//!
102//! > **Note:** The naming of `render` might give you the impression that it means the window canvas will effectively rerender again, it has nothing to do with it, in fact, a component might render (run its function) a thousand times but generate the exact same UI, if that was the case Freya would not render the canvas again.
103//!
104//! Consider this simple component:
105//!
106//! ```rust
107//! # use freya::prelude::*;
108//! #[derive(PartialEq)]
109//! struct CoolComp;
110//!
111//! impl Component for CoolComp {
112//!     // One run of this function is the same as saying one render of this component
113//!     fn render(&self) -> impl IntoElement {
114//!         let mut count = use_state(|| 0);
115//!
116//!         label()
117//!             .on_mouse_up(move |_| *count.write() += 1)
118//!             // Here we subscribe to `count` because we called .read() on it
119//!             .text(format!("Increase {}", count.read()))
120//!     }
121//! }
122//! ```
123//!
124//! ## Lists and Keys
125//!
126//! When you render a dynamic list, Freya needs to match each element to its previous version across
127//! renders. Without any hint it pairs them up by index, which is fine until items are inserted,
128//! removed or reordered. When that happens index-based matching can missassociate an element with the
129//! wrong previous one, making local state or layout jump to another item.
130//!
131//! The [`key`](freya_core::elements::extensions::KeyExt::key) method gives an element a stable
132//! identity so Freya can reconcile it correctly no matter where it ends up in the list.
133//!
134//! ```rust
135//! # use freya::prelude::*;
136//! fn app() -> impl IntoElement {
137//!     let items = use_state(|| Vec::<String>::new());
138//!
139//!     rect().children(
140//!         items
141//!             .read()
142//!             .iter()
143//!             .enumerate()
144//!             .map(|(index, item)| label().key(index).text(item.clone()).into()),
145//!     )
146//! }
147//! ```
148//!
149//! The key can be derived from any [`Hash`](std::hash::Hash) value. Whenever items can be inserted,
150//! removed or reordered, prefer a value that uniquely and stably identifies the item.
151//!
152//! A custom [`Component`](freya_core::prelude::Component) can expose the same `.key` method by
153//! implementing [`KeyExt`](freya_core::elements::extensions::KeyExt) over a stored
154//! [`DiffKey`](freya_core::prelude::DiffKey) and forwarding it from
155//! [`render_key`](freya_core::prelude::Component::render_key).
156//!
157//! ```rust
158//! # use freya::prelude::*;
159//! #[derive(PartialEq)]
160//! struct Task {
161//!     title: String,
162//!     key: DiffKey,
163//! }
164//!
165//! impl Task {
166//!     fn new(title: String) -> Self {
167//!         Self {
168//!             title,
169//!             key: DiffKey::None,
170//!         }
171//!     }
172//! }
173//!
174//! impl KeyExt for Task {
175//!     fn write_key(&mut self) -> &mut DiffKey {
176//!         &mut self.key
177//!     }
178//! }
179//!
180//! impl Component for Task {
181//!     fn render(&self) -> impl IntoElement {
182//!         label().text(self.title.clone())
183//!     }
184//!
185//!     // Use the key set through `.key(..)` and fall back to the default one when none was given.
186//!     fn render_key(&self) -> DiffKey {
187//!         self.key.clone().or(self.default_key())
188//!     }
189//! }
190//!
191//! fn app(ids: Vec<u64>) -> impl IntoElement {
192//!     rect().children(
193//!         ids.iter()
194//!             .map(|id| Task::new(format!("Task {id}")).key(*id).into()),
195//!     )
196//! }
197//! ```
198//!
199//! ## Components vs Utility Functions
200//!
201//! Not every piece of reusable UI needs to be a full [Component](freya_core::prelude::Component).
202//! Sometimes a plain Rust function is simpler and more appropriate.
203//!
204//! ### Plain utility functions
205//!
206//! When you just want to reuse or encapsulate a chunk of UI with no internal state, a plain
207//! function is the simplest option — no boilerplate, no trait to implement.
208//!
209//! ```rust
210//! # use freya::prelude::*;
211//! fn colored_label(color: Color, text: &str) -> impl IntoElement {
212//!     label().color(color).text(text.to_string())
213//! }
214//!
215//! fn app() -> impl IntoElement {
216//!     rect()
217//!         .child(colored_label(Color::RED, "Error"))
218//!         .child(colored_label(Color::GREEN, "Success"))
219//! }
220//! ```
221//!
222//! ### Components with state or render optimization
223//!
224//! Use a [Component](freya_core::prelude::Component) when you need local state, as hooks like
225//! `use_state` only work inside a component's `render` method. Components also enable render
226//! optimization: because [Component](freya_core::prelude::Component) requires [`PartialEq`],
227//! Freya can skip re-running `render` and diffing the entire subtree when the component's data
228//! hasn't changed, something a plain function cannot do.
229//!
230//!
231//! ```rust
232//! # use freya::prelude::*;
233//! // This cannot be a plain function: it owns local state via `use_state`.
234//! // Also, if `initial` doesn't change between parent renders, Freya skips re-rendering
235//! // this component and its entire subtree entirely.
236//! #[derive(PartialEq)]
237//! struct Counter {
238//!     initial: i32,
239//! }
240//!
241//! impl Component for Counter {
242//!     fn render(&self) -> impl IntoElement {
243//!         let mut count = use_state(|| self.initial);
244//!
245//!         label()
246//!             .on_mouse_up(move |_| *count.write() += 1)
247//!             .text(format!("Count: {}", count.read()))
248//!     }
249//! }
250//!
251//! fn app() -> impl IntoElement {
252//!     Counter { initial: 0 }
253//! }
254//! ```