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