freya_core/lifecycle/
state.rs

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