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 let Some(value) = self.try_read() else {
308 panic!("Reading the State failed because it is already borrowed or it was dropped.")
309 };
310 value
311 }
312
313 /// Read the current value and subscribe the current component to changes, or [None] if
314 /// the state is already borrowed or the scope owning it was dropped.
315 ///
316 /// # Example
317 ///
318 /// ```rust,no_run
319 /// # use freya::prelude::*;
320 /// let count = use_state(|| 0);
321 ///
322 /// if let Some(count) = count.try_read() {
323 /// println!("{count}");
324 /// }
325 /// ```
326 pub fn try_read(&self) -> Option<ReadRef<'static, T>> {
327 if let Some(mut rc) = ReactiveContext::try_current() {
328 let subscribers = self.subscribers.try_read().ok()?;
329 rc.subscribe(&subscribers);
330 }
331 self.key.try_read().ok()
332 }
333
334 /// Read the current value without subscribing to changes.
335 ///
336 /// This method provides access to the current state value without registering the current
337 /// component as a subscriber. The component will **not** re-render if the state changes.
338 ///
339 /// # When to Use
340 ///
341 /// Use `peek()` when you need to read the state value for a one-off operation where
342 /// reactivity is not needed, such as:
343 /// - Comparisons for conditional updates
344 /// - Debugging/logging
345 /// - Initial value checks
346 ///
347 /// # Example
348 ///
349 /// ```rust,no_run
350 /// # use freya::prelude::*;
351 /// let count = use_state(|| 0);
352 ///
353 /// // Check if count is zero without subscribing
354 /// if *count.peek() == 0 {
355 /// println!("Count is still zero");
356 /// }
357 ///
358 /// // For reactive reading, use `read()` instead:
359 /// let display_text = format!("Count: {}", count.read());
360 /// ```
361 ///
362 /// # Performance Note
363 ///
364 /// Prefer `read()` over `peek()` unless you specifically need non-reactive access.
365 #[track_caller]
366 pub fn peek(&self) -> ReadRef<'static, T> {
367 let Some(value) = self.try_peek() else {
368 panic!("Peeking the State failed because it is already borrowed or it was dropped.")
369 };
370 value
371 }
372
373 /// Read the current value without subscribing to changes, or [None] if the state is
374 /// already borrowed or the scope owning it was dropped.
375 ///
376 /// # Example
377 ///
378 /// ```rust,no_run
379 /// # use freya::prelude::*;
380 /// let count = use_state(|| 0);
381 ///
382 /// if let Some(count) = count.try_peek() {
383 /// println!("{count}");
384 /// }
385 /// ```
386 pub fn try_peek(&self) -> Option<ReadRef<'static, T>> {
387 self.key.try_read().ok()
388 }
389
390 /// Get a mutable reference to the state value and notify subscribers.
391 ///
392 /// This method returns a `WriteRef<T>` that allows direct mutation of the state value.
393 /// All subscribed components will be notified and will re-render on the next frame.
394 ///
395 /// # Example
396 ///
397 /// ```rust,no_run
398 /// # use freya::prelude::*;
399 /// let mut count = use_state(|| 0);
400 ///
401 /// // Direct mutation
402 /// *count.write() += 1;
403 ///
404 /// // Multiple operations
405 /// {
406 /// let mut value = count.write();
407 /// *value *= 2;
408 /// *value += 10;
409 /// } // Subscribers notified here
410 /// ```
411 ///
412 /// # See Also
413 ///
414 /// - `with_mut()` for closure-based mutations
415 /// - `set()` for replacing the entire value
416 #[track_caller]
417 pub fn write(&mut self) -> WriteRef<'static, T> {
418 let Some(value) = self.try_write() else {
419 panic!("Writing to the State failed because it is already borrowed or it was dropped.")
420 };
421 value
422 }
423
424 /// Get a mutable reference to the state value and notify subscribers, or [None] if the
425 /// state is already borrowed or the scope owning it was dropped.
426 ///
427 /// Useful in callbacks that can outlive the component they were created in.
428 ///
429 /// # Example
430 ///
431 /// ```rust,no_run
432 /// # use freya::prelude::*;
433 /// let mut count = use_state(|| 0);
434 ///
435 /// if let Some(mut count) = count.try_write() {
436 /// *count += 1;
437 /// }
438 /// ```
439 pub fn try_write(&mut self) -> Option<WriteRef<'static, T>> {
440 self.try_write_unchecked()
441 }
442
443 /// Same as [State::try_write] but without requiring a mutable borrow of the State.
444 ///
445 /// Prefer [State::try_write], here conflicting borrows are only detected at runtime.
446 pub fn try_write_unchecked(&self) -> Option<WriteRef<'static, T>> {
447 let subscribers = self.subscribers.try_write().ok()?;
448 subscribers.borrow_mut().retain(|s| s.notify());
449 self.key.try_write().ok()
450 }
451
452 /// Same as [State::write] but without requiring a mutable borrow of the State.
453 ///
454 /// Reach for it only when you cannot get a mutable reference to the `State` itself,
455 /// here conflicting borrows are only detected at runtime.
456 #[track_caller]
457 pub fn write_unchecked(&self) -> WriteRef<'static, T> {
458 let Some(value) = self.try_write_unchecked() else {
459 panic!(
460 "Writing (unchecked) to the State failed because it is already borrowed or it was dropped."
461 )
462 };
463 value
464 }
465
466 /// Get a mutable reference without notifying subscribers.
467 ///
468 /// This method provides write access without triggering any re-renders.
469 /// The caller is responsible for calling `notify()` if subscribers should be notified.
470 ///
471 /// This is primarily used internally by `Writable::write_if()` to enable conditional
472 /// notifications based on whether the value actually changed.
473 #[track_caller]
474 pub(crate) fn write_silently(&self) -> WriteRef<'static, T> {
475 let Some(value) = self.key.try_write().ok() else {
476 panic!(
477 "Silently writing to the State failed because it is already borrowed or it was dropped."
478 )
479 };
480 value
481 }
482
483 /// Create a new State attached to the current component's scope.
484 ///
485 /// This method creates a reactive state value that will be automatically cleaned up
486 /// when the current component unmounts.
487 ///
488 /// # Example
489 ///
490 /// ```rust,no_run
491 /// # use freya::prelude::*;
492 /// // Usually used through use_state() hook instead:
493 /// let count = use_state(|| 0);
494 ///
495 /// // Direct creation (rare):
496 /// let state = State::create(42);
497 /// ```
498 ///
499 /// # See Also
500 ///
501 /// - `use_state()` - The recommended way to create state in components
502 /// - `create_global()` - For application-wide state
503 pub fn create(value: T) -> Self
504 where
505 T: 'static, // TODO: Move this lifetime bound to impl
506 {
507 Self::create_in_scope(value, None)
508 }
509
510 /// Create a State attached to a specific scope.
511 ///
512 /// Advanced method for creating state in a different scope than the current one.
513 /// Pass `None` to attach to the current scope (same as `create()`).
514 ///
515 /// # Parameters
516 ///
517 /// - `value`: The initial value for the state
518 /// - `scope_id`: The scope to attach to, or `None` for current scope
519 ///
520 /// # Use Cases
521 ///
522 /// - Creating state in parent scopes
523 /// - Advanced component patterns
524 /// - Testing utilities
525 pub fn create_in_scope(value: T, scope_id: impl Into<Option<ScopeId>>) -> Self
526 where
527 T: 'static,
528 {
529 // TODO: Move this lifetime bound to impl
530 let owner = CurrentContext::with(|context| {
531 let scopes_storages = context.scopes_storages.borrow_mut();
532
533 let scopes_storage = scopes_storages.get(&scope_id.into().unwrap_or(context.scope_id));
534 scopes_storage.unwrap().owner.clone()
535 });
536 let key = owner.insert(value);
537 let subscribers = owner.insert(Rc::default());
538 State { key, subscribers }
539 }
540
541 /// Create a global [`State`] that lives for the entire application lifetime.
542 /// This is useful for sharing state across multiple windows.
543 ///
544 /// This is **not** a hook, do not use it inside components like you would [`use_state`].
545 /// You would usually want to call this in your `main` function, not anywhere else.
546 ///
547 /// # Example
548 ///
549 /// ```rust, ignore
550 /// # use freya::prelude::*;
551 ///
552 /// fn main() {
553 /// let count = State::create_global(0);
554 ///
555 /// launch(
556 /// LaunchConfig::new()
557 /// .with_window(WindowConfig::new(Window1 { count }))
558 /// .with_window(WindowConfig::new(Window2 { count })),
559 /// );
560 /// }
561 /// ```
562 /// # Memory Management
563 ///
564 /// Global state is leaked using `Box::leak()` and will not be automatically cleaned up.
565 /// Ensure global state contains lightweight data or implement manual cleanup if needed.
566 pub fn create_global(value: T) -> Self
567 where
568 T: 'static,
569 {
570 let owner = UnsyncStorage::owner();
571 Box::leak(Box::new(owner.clone()));
572 let key = owner.insert(value);
573 let subscribers = owner.insert(Rc::default());
574 State { key, subscribers }
575 }
576
577 /// Subscribe the current reactive context to this state's changes.
578 #[track_caller]
579 pub(crate) fn subscribe(&self) {
580 if let Some(mut rc) = ReactiveContext::try_current() {
581 rc.subscribe(&self.subscribers.read());
582 }
583 }
584
585 /// Notify all subscribers that the state has changed.
586 #[track_caller]
587 pub(crate) fn notify(&self) {
588 self.subscribers.write().borrow_mut().retain(|s| s.notify());
589 }
590}
591
592impl<T> Clone for State<T> {
593 fn clone(&self) -> Self {
594 *self
595 }
596}
597
598impl<T> Copy for State<T> {}
599
600impl<T> State<Option<T>> {
601 /// Take ownership of the contained value, leaving `None` in its place.
602 ///
603 /// This method is only available for `State<Option<T>>` and moves the value
604 /// out of the state, replacing it with `None`.
605 ///
606 /// # Example
607 ///
608 /// ```rust,no_run
609 /// # use freya::prelude::*;
610 /// let mut maybe_value = use_state(|| Some("hello".to_string()));
611 ///
612 /// // Take the value, state becomes None
613 /// let taken = maybe_value.take(); // Some("hello")
614 /// assert_eq!(*maybe_value.read(), None);
615 /// ```
616 ///
617 /// # Use Cases
618 ///
619 /// - Moving values out of reactive state
620 /// - One-time consumption of optional state
621 /// - State transitions where the value is no longer needed
622 #[track_caller]
623 pub fn take(&mut self) -> Option<T>
624 where
625 T: 'static,
626 {
627 self.write().take()
628 }
629}
630/// Creates a reactive state value initialized with the returned value of the `init` callback.
631///
632/// This hook creates a `State<T>` that is automatically scoped to the current component.
633/// The state will be cleaned up when the component unmounts.
634///
635/// # Parameters
636///
637/// - `init`: A closure that returns the initial value for the state
638///
639/// # Type Requirements
640///
641/// The type `T` must be `'static` (no borrowed references).
642///
643/// # Example
644///
645/// ```rust,no_run
646/// # use freya::prelude::*;
647/// fn counter() -> impl IntoElement {
648/// let mut count = use_state(|| 0);
649///
650/// rect().child(format!("Count: {}", count.read())).child(
651/// Button::new()
652/// .child("Increment")
653/// .on_press(move |_| *count.write() += 1),
654/// )
655/// }
656/// ```
657///
658/// # See Also
659///
660/// - [`State`] for the reactive state type
661/// - `freya-radio` crate for global state management
662pub fn use_state<T: 'static>(init: impl FnOnce() -> T) -> State<T> {
663 use_hook(|| State::create(init()))
664}