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}