freya_core/lifecycle/state.rs
1use std::{
2 cell::RefCell,
3 fmt::{
4 Debug,
5 Display,
6 },
7 mem::MaybeUninit,
8 ops::Deref,
9 rc::Rc,
10};
11
12use generational_box::{
13 AnyStorage,
14 GenerationalBox,
15 UnsyncStorage,
16};
17use rustc_hash::FxHashSet;
18
19use crate::{
20 current_context::CurrentContext,
21 lifecycle::writable_utils::WritableUtils,
22 prelude::use_hook,
23 reactive_context::ReactiveContext,
24 scope_id::ScopeId,
25};
26
27/// A reactive state container that holds a value of type `T` and manages subscriptions to changes.
28///
29/// `State<T>` is the fundamental reactive primitive in Freya. It allows you to store mutable state
30/// that automatically triggers re-renders in components that read from it when the value changes.
31///
32/// # Key Features
33///
34/// - **Reactive**: Components automatically re-render when the state value changes.
35/// - **Copy**: `State<T>` implements `Copy`, making it cheap to pass around.
36/// - **Shared**: Multiple components can read from and write to the same state.
37/// - **Scoped**: State is automatically cleaned up when its owning component unmounts.
38///
39/// # Basic Usage
40///
41/// ```rust,no_run
42/// use freya::prelude::*;
43///
44/// fn counter() -> impl IntoElement {
45/// // Create reactive state
46/// let mut count = use_state(|| 0);
47///
48/// rect().child(format!("Count: {}", count.read())).child(
49/// Button::new()
50/// .child("Increment")
51/// .on_press(move |_| *count.write() += 1),
52/// )
53/// }
54/// ```
55///
56/// # Reading State
57///
58/// - `state.read()` - Reads the current value and subscribes the current component to changes.
59/// - `state.peek()` - Reads the current value without subscribing (rarely needed).
60///
61/// # Writing State
62///
63/// - `state.write()` - Gets a mutable reference to modify the value.
64/// - `state.set(new_value)` - Replaces the current value.
65/// - `state.with_mut(|mut_ref| { /* modify */ })` - Modifies using a closure.
66///
67/// # Advanced Patterns
68///
69/// ## Conditional Updates
70///
71/// ```rust,no_run
72/// # use freya::prelude::*;
73/// let mut count = use_state(|| 0);
74///
75/// // Only update if the new value is different
76/// count.set_if_modified(5);
77///
78/// // Update and run additional logic
79/// count.set_if_modified_and_then(10, || {
80/// println!("Count reached 10!");
81/// });
82/// ```
83///
84/// ## Working with Options
85///
86/// ```rust,no_run
87/// # use freya::prelude::*;
88/// let mut optional_value = use_state(|| Some(42));
89///
90/// // Take ownership of the contained value
91/// let taken_value = optional_value.take(); // Returns Option<i32>
92/// ```
93///
94/// ## Copy Types
95///
96/// For `Copy` types, you can call the state as a function to read:
97///
98/// ```rust,no_run
99/// # use freya::prelude::*;
100/// let count = use_state(|| 0);
101///
102/// // These are equivalent:
103/// let value1 = count.read().clone();
104/// let value2 = count(); // Only works for Copy types
105/// ```
106///
107/// # Global State
108///
109/// For state that needs to persist across the entire application lifetime (e.g. shared across
110/// multiple windows), create it in your `main` function using [`State::create_global`] and pass
111/// it to each window:
112///
113/// ```rust, ignore
114/// # use freya::prelude::*;
115/// fn main() {
116/// let count = State::create_global(0);
117///
118/// launch(
119/// LaunchConfig::new()
120/// .with_window(WindowConfig::new(Window1 { count }))
121/// .with_window(WindowConfig::new(Window2 { count })),
122/// );
123/// }
124/// ```
125///
126/// # Thread Safety
127///
128/// `State<T>` is not thread-safe and should only be used within the main UI thread.
129/// For cross-thread communication, consider using channels or other synchronization primitives.
130///
131/// # Performance Notes
132///
133/// - Reading state subscribes the current component, causing re-renders when it changes.
134/// - Use `peek()` only when you specifically don't want reactivity.
135/// - Prefer `set_if_modified()` over `set()` when the value might not have changed.
136pub struct State<T> {
137 key: GenerationalBox<T>,
138 subscribers: GenerationalBox<Rc<RefCell<FxHashSet<ReactiveContext>>>>,
139}
140
141impl<T: 'static> PartialEq for State<T> {
142 fn eq(&self, other: &Self) -> bool {
143 self.key.ptr_eq(&other.key)
144 }
145}
146
147impl<T: 'static> Eq for State<T> {}
148
149/// Allow calling the states as functions.
150/// Limited to `Copy` values only.
151impl<T: Copy + 'static> Deref for State<T> {
152 type Target = dyn Fn() -> T;
153
154 fn deref(&self) -> &Self::Target {
155 unsafe { State::deref_impl(self) }
156 }
157}
158
159impl<T> State<T> {
160 /// Adapted from https://github.com/DioxusLabs/dioxus/blob/a4aef33369894cd6872283d6d7d265303ae63913/packages/signals/src/read.rs#L246
161 /// SAFETY: You must call this function directly with `self` as the argument.
162 /// This function relies on the size of the object you return from the deref
163 /// being the same as the object you pass in
164 #[doc(hidden)]
165 unsafe fn deref_impl<'a>(state: &State<T>) -> &'a dyn Fn() -> T
166 where
167 Self: Sized + 'a,
168 T: Clone + 'static,
169 {
170 // https://github.com/dtolnay/case-studies/tree/master/callable-types
171
172 // First we create a closure that captures something with the Same in memory layout as Self (MaybeUninit<Self>).
173 let uninit_callable = MaybeUninit::<Self>::uninit();
174 // Then move that value into the closure. We assume that the closure now has a in memory layout of Self.
175 let uninit_closure = move || Self::read(unsafe { &*uninit_callable.as_ptr() }).clone();
176
177 // Check that the size of the closure is the same as the size of Self in case the compiler changed the layout of the closure.
178 let size_of_closure = std::mem::size_of_val(&uninit_closure);
179 assert_eq!(size_of_closure, std::mem::size_of::<Self>());
180
181 // Then cast the lifetime of the closure to the lifetime of &self.
182 fn cast_lifetime<'a, T>(_a: &T, b: &'a T) -> &'a T {
183 b
184 }
185 let reference_to_closure = cast_lifetime(
186 {
187 // The real closure that we will never use.
188 &uninit_closure
189 },
190 #[allow(clippy::missing_transmute_annotations)]
191 // We transmute self into a reference to the closure. This is safe because we know that the closure has the same memory layout as Self so &Closure == &Self.
192 unsafe {
193 std::mem::transmute(state)
194 },
195 );
196
197 // Cast the closure to a trait object.
198 reference_to_closure as &_
199 }
200}
201
202impl<T: std::ops::Not<Output = T> + Clone + 'static> State<T> {
203 /// Toggle the boolean-like value and return the new value.
204 ///
205 /// This method negates the current value using the `!` operator and returns
206 /// the new value after updating the state.
207 ///
208 /// # Requirements
209 ///
210 /// The type `T` must implement `std::ops::Not<Output = T> + Clone`.
211 ///
212 /// # Example
213 ///
214 /// ```rust,no_run
215 /// # use freya::prelude::*;
216 /// let mut flag = use_state(|| false);
217 ///
218 /// // Toggle and get the new value
219 /// let new_value = flag.toggled(); // false -> true, returns true
220 /// assert_eq!(new_value, true);
221 /// ```
222 ///
223 /// # Common Types
224 ///
225 /// Works with `bool`, custom enum types, etc.
226 #[track_caller]
227 pub fn toggled(&mut self) -> T {
228 let value = self.read().clone();
229 let neg_value = !value;
230 self.set(neg_value.clone());
231 neg_value
232 }
233
234 /// Toggle the boolean-like value without returning it.
235 ///
236 /// This is a convenience method that toggles the value but discards the result.
237 /// Equivalent to calling [toggled](Self::toggled) and ignoring the return value.
238 ///
239 /// # Example
240 ///
241 /// ```rust,no_run
242 /// # use freya::prelude::*;
243 /// let mut is_visible = use_state(|| false);
244 ///
245 /// // Toggle visibility
246 /// is_visible.toggle(); // false -> true
247 /// ```
248 #[track_caller]
249 pub fn toggle(&mut self) {
250 self.toggled();
251 }
252}
253
254pub enum ReadableRef<T: 'static> {
255 Ref(ReadRef<'static, T>),
256 Borrowed(Rc<T>),
257}
258
259impl<T: 'static + Debug> Debug for ReadableRef<T> {
260 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
261 match self {
262 Self::Ref(r) => r.fmt(f),
263 Self::Borrowed(r) => r.deref().fmt(f),
264 }
265 }
266}
267
268impl<T: 'static + Display> Display for ReadableRef<T> {
269 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
270 match self {
271 Self::Ref(r) => r.fmt(f),
272 Self::Borrowed(r) => r.deref().fmt(f),
273 }
274 }
275}
276
277impl<T> Deref for ReadableRef<T> {
278 type Target = T;
279 fn deref(&self) -> &Self::Target {
280 match self {
281 Self::Ref(r) => r.deref(),
282 Self::Borrowed(b) => b,
283 }
284 }
285}
286
287pub type ReadRef<'a, T> =
288 <generational_box::UnsyncStorage as generational_box::AnyStorage>::Ref<'a, T>;
289
290pub type WriteRef<'a, T> =
291 <generational_box::UnsyncStorage as generational_box::AnyStorage>::Mut<'a, T>;
292
293impl<T> State<T> {
294 /// Read the current value and subscribe the current component to changes.
295 ///
296 /// When the state value changes, any component or hook that has called `read()` will re-render.
297 ///
298 /// # Example
299 ///
300 /// ```rust,no_run
301 /// # use freya::prelude::*;
302 /// let count = use_state(|| 0);
303 /// let current_value = count.read();
304 /// ```
305 #[track_caller]
306 pub fn read(&self) -> ReadRef<'static, T> {
307 if let Some(mut rc) = ReactiveContext::try_current() {
308 rc.subscribe(&self.subscribers.read());
309 }
310 match self.key.try_read() {
311 Ok(val) => val,
312 Err(e) => {
313 panic!("Reading the State failed because it is already borrowed.\n{e}")
314 }
315 }
316 }
317
318 /// Read the current value without subscribing to changes.
319 ///
320 /// This method provides access to the current state value without registering the current
321 /// component as a subscriber. The component will **not** re-render if the state changes.
322 ///
323 /// # When to Use
324 ///
325 /// Use `peek()` when you need to read the state value for a one-off operation where
326 /// reactivity is not needed, such as:
327 /// - Comparisons for conditional updates
328 /// - Debugging/logging
329 /// - Initial value checks
330 ///
331 /// # Example
332 ///
333 /// ```rust,no_run
334 /// # use freya::prelude::*;
335 /// let count = use_state(|| 0);
336 ///
337 /// // Check if count is zero without subscribing
338 /// if *count.peek() == 0 {
339 /// println!("Count is still zero");
340 /// }
341 ///
342 /// // For reactive reading, use `read()` instead:
343 /// let display_text = format!("Count: {}", count.read());
344 /// ```
345 ///
346 /// # Performance Note
347 ///
348 /// Prefer `read()` over `peek()` unless you specifically need non-reactive access.
349 #[track_caller]
350 pub fn peek(&self) -> ReadRef<'static, T> {
351 match self.key.try_read() {
352 Ok(val) => val,
353 Err(e) => {
354 panic!("Peeking the State failed because it is already borrowed.\n{e}")
355 }
356 }
357 }
358
359 /// Get a mutable reference to the state value and notify subscribers.
360 ///
361 /// This method returns a `WriteRef<T>` that allows direct mutation of the state value.
362 /// All subscribed components will be notified and will re-render on the next frame.
363 ///
364 /// # Example
365 ///
366 /// ```rust,no_run
367 /// # use freya::prelude::*;
368 /// let mut count = use_state(|| 0);
369 ///
370 /// // Direct mutation
371 /// *count.write() += 1;
372 ///
373 /// // Multiple operations
374 /// {
375 /// let mut value = count.write();
376 /// *value *= 2;
377 /// *value += 10;
378 /// } // Subscribers notified here
379 /// ```
380 ///
381 /// # See Also
382 ///
383 /// - `with_mut()` for closure-based mutations
384 /// - `set()` for replacing the entire value
385 #[track_caller]
386 pub fn write(&mut self) -> WriteRef<'static, T> {
387 self.subscribers.write().borrow_mut().retain(|s| s.notify());
388 match self.key.try_write() {
389 Ok(val) => val,
390 Err(e) => {
391 panic!("Writing to the State failed because it is already borrowed.\n{e}")
392 }
393 }
394 }
395
396 /// Get a mutable reference without requiring a mutable borrow of the State.
397 ///
398 /// This is an advanced method that allows writing to the state without having
399 /// mutable access to the `State` itself. Use with caution as it bypasses Rust's
400 /// borrow checker guarantees.
401 ///
402 /// # Safety Considerations
403 ///
404 /// This method should only be used when you cannot obtain a mutable reference
405 /// to the `State` but still need to modify it. Prefer `write()` when possible.
406 #[track_caller]
407 pub fn write_unchecked(&self) -> WriteRef<'static, T> {
408 self.subscribers.write().borrow_mut().retain(|s| s.notify());
409 match self.key.try_write() {
410 Ok(val) => val,
411 Err(e) => {
412 panic!(
413 "Writing (unchecked) to the State failed because it is already borrowed.\n{e}"
414 )
415 }
416 }
417 }
418
419 /// Get a mutable reference without notifying subscribers.
420 ///
421 /// This method provides write access without triggering any re-renders.
422 /// The caller is responsible for calling `notify()` if subscribers should be notified.
423 ///
424 /// This is primarily used internally by `Writable::write_if()` to enable conditional
425 /// notifications based on whether the value actually changed.
426 #[track_caller]
427 pub(crate) fn write_silently(&self) -> WriteRef<'static, T> {
428 match self.key.try_write() {
429 Ok(val) => val,
430 Err(e) => {
431 panic!("Silently writing to the State failed because it is already borrowed.\n{e}")
432 }
433 }
434 }
435
436 /// Create a new State attached to the current component's scope.
437 ///
438 /// This method creates a reactive state value that will be automatically cleaned up
439 /// when the current component unmounts.
440 ///
441 /// # Example
442 ///
443 /// ```rust,no_run
444 /// # use freya::prelude::*;
445 /// // Usually used through use_state() hook instead:
446 /// let count = use_state(|| 0);
447 ///
448 /// // Direct creation (rare):
449 /// let state = State::create(42);
450 /// ```
451 ///
452 /// # See Also
453 ///
454 /// - `use_state()` - The recommended way to create state in components
455 /// - `create_global()` - For application-wide state
456 pub fn create(value: T) -> Self
457 where
458 T: 'static, // TODO: Move this lifetime bound to impl
459 {
460 Self::create_in_scope(value, None)
461 }
462
463 /// Create a State attached to a specific scope.
464 ///
465 /// Advanced method for creating state in a different scope than the current one.
466 /// Pass `None` to attach to the current scope (same as `create()`).
467 ///
468 /// # Parameters
469 ///
470 /// - `value`: The initial value for the state
471 /// - `scope_id`: The scope to attach to, or `None` for current scope
472 ///
473 /// # Use Cases
474 ///
475 /// - Creating state in parent scopes
476 /// - Advanced component patterns
477 /// - Testing utilities
478 pub fn create_in_scope(value: T, scope_id: impl Into<Option<ScopeId>>) -> Self
479 where
480 T: 'static,
481 {
482 // TODO: Move this lifetime bound to impl
483 let owner = CurrentContext::with(|context| {
484 let scopes_storages = context.scopes_storages.borrow_mut();
485
486 let scopes_storage = scopes_storages.get(&scope_id.into().unwrap_or(context.scope_id));
487 scopes_storage.unwrap().owner.clone()
488 });
489 let key = owner.insert(value);
490 let subscribers = owner.insert(Rc::default());
491 State { key, subscribers }
492 }
493
494 /// Create a global [`State`] that lives for the entire application lifetime.
495 /// This is useful for sharing state across multiple windows.
496 ///
497 /// This is **not** a hook, do not use it inside components like you would [`use_state`].
498 /// You would usually want to call this in your `main` function, not anywhere else.
499 ///
500 /// # Example
501 ///
502 /// ```rust, ignore
503 /// # use freya::prelude::*;
504 ///
505 /// fn main() {
506 /// let count = State::create_global(0);
507 ///
508 /// launch(
509 /// LaunchConfig::new()
510 /// .with_window(WindowConfig::new(Window1 { count }))
511 /// .with_window(WindowConfig::new(Window2 { count })),
512 /// );
513 /// }
514 /// ```
515 /// # Memory Management
516 ///
517 /// Global state is leaked using `Box::leak()` and will not be automatically cleaned up.
518 /// Ensure global state contains lightweight data or implement manual cleanup if needed.
519 pub fn create_global(value: T) -> Self
520 where
521 T: 'static,
522 {
523 let owner = UnsyncStorage::owner();
524 Box::leak(Box::new(owner.clone()));
525 let key = owner.insert(value);
526 let subscribers = owner.insert(Rc::default());
527 State { key, subscribers }
528 }
529
530 /// Subscribe the current reactive context to this state's changes.
531 #[track_caller]
532 pub(crate) fn subscribe(&self) {
533 if let Some(mut rc) = ReactiveContext::try_current() {
534 rc.subscribe(&self.subscribers.read());
535 }
536 }
537
538 /// Notify all subscribers that the state has changed.
539 #[track_caller]
540 pub(crate) fn notify(&self) {
541 self.subscribers.write().borrow_mut().retain(|s| s.notify());
542 }
543}
544
545impl<T> Clone for State<T> {
546 fn clone(&self) -> Self {
547 *self
548 }
549}
550
551impl<T> Copy for State<T> {}
552
553impl<T> State<Option<T>> {
554 /// Take ownership of the contained value, leaving `None` in its place.
555 ///
556 /// This method is only available for `State<Option<T>>` and moves the value
557 /// out of the state, replacing it with `None`.
558 ///
559 /// # Example
560 ///
561 /// ```rust,no_run
562 /// # use freya::prelude::*;
563 /// let mut maybe_value = use_state(|| Some("hello".to_string()));
564 ///
565 /// // Take the value, state becomes None
566 /// let taken = maybe_value.take(); // Some("hello")
567 /// assert_eq!(*maybe_value.read(), None);
568 /// ```
569 ///
570 /// # Use Cases
571 ///
572 /// - Moving values out of reactive state
573 /// - One-time consumption of optional state
574 /// - State transitions where the value is no longer needed
575 #[track_caller]
576 pub fn take(&mut self) -> Option<T>
577 where
578 T: 'static,
579 {
580 self.write().take()
581 }
582}
583/// Creates a reactive state value initialized with the returned value of the `init` callback.
584///
585/// This hook creates a `State<T>` that is automatically scoped to the current component.
586/// The state will be cleaned up when the component unmounts.
587///
588/// # Parameters
589///
590/// - `init`: A closure that returns the initial value for the state
591///
592/// # Type Requirements
593///
594/// The type `T` must be `'static` (no borrowed references).
595///
596/// # Example
597///
598/// ```rust,no_run
599/// # use freya::prelude::*;
600/// fn counter() -> impl IntoElement {
601/// let mut count = use_state(|| 0);
602///
603/// rect().child(format!("Count: {}", count.read())).child(
604/// Button::new()
605/// .child("Increment")
606/// .on_press(move |_| *count.write() += 1),
607/// )
608/// }
609/// ```
610///
611/// # See Also
612///
613/// - [`State`] for the reactive state type
614/// - `freya-radio` crate for global state management
615pub fn use_state<T: 'static>(init: impl FnOnce() -> T) -> State<T> {
616 use_hook(|| State::create(init()))
617}