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