Skip to main content

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