freya_core/lifecycle/
writable.rs

1//! Type-erased writable state that hides generic type parameters.
2
3use std::rc::Rc;
4
5use crate::prelude::*;
6
7/// A type-erased writable state that only exposes the value type `T`.
8///
9/// This abstraction allows components to accept state from any source without knowing
10/// whether it comes from local state ([`use_state`]) or
11/// global state (Freya Radio). It hides the implementation details, providing a
12/// uniform interface for reading and writing values.
13///
14/// # Sources
15///
16/// `Writable` can be created from:
17/// - [`State<T>`] via [`From`] or [`IntoWritable`]
18/// - `RadioSliceMut` via `IntoWritable`
19///
20/// # Example
21///
22/// ```rust, ignore
23/// #[derive(PartialEq)]
24/// struct NameInput {
25///     name: Writable<String>,
26/// }
27///
28/// impl Component for NameInput {
29///     fn render(&self) -> impl IntoElement {
30///         // The component doesn't care if this is local or global state
31///         Input::new(self.name.clone())
32///     }
33/// }
34///
35/// fn app() -> impl IntoElement {
36///     let local = use_state(|| "Alice".to_string());
37///     let radio = use_radio(AppChannel::Name);
38///     let slice = radio.slice_mut_current(|s| &mut s.name);
39///
40///     rect()
41///         // Pass local state
42///         .child(NameInput { name: local.into_writable() })
43///         // Pass global radio slice
44///         .child(NameInput { name: slice.into_writable() })
45/// }
46/// ```
47pub struct Writable<T: 'static> {
48    pub(crate) peek_fn: Rc<dyn Fn() -> ReadRef<'static, T>>,
49    pub(crate) write_fn: Rc<dyn Fn() -> WriteRef<'static, T>>,
50    pub(crate) subscribe_fn: Rc<dyn Fn()>,
51    pub(crate) notify_fn: Rc<dyn Fn()>,
52}
53
54impl<T: 'static> PartialEq for Writable<T> {
55    fn eq(&self, _other: &Self) -> bool {
56        true
57    }
58}
59
60impl<T: 'static> Clone for Writable<T> {
61    fn clone(&self) -> Self {
62        Self {
63            peek_fn: self.peek_fn.clone(),
64            write_fn: self.write_fn.clone(),
65            subscribe_fn: self.subscribe_fn.clone(),
66            notify_fn: self.notify_fn.clone(),
67        }
68    }
69}
70
71impl<T: 'static> Writable<T> {
72    /// Create from local `State<T>`.
73    pub fn from_state(state: State<T>) -> Self {
74        Self {
75            peek_fn: Rc::new(move || state.peek()),
76            write_fn: Rc::new(move || state.write_unchecked()),
77            subscribe_fn: Rc::new(move || state.subscribe()),
78            notify_fn: Rc::new(move || state.notify()),
79        }
80    }
81
82    /// Create a new `Writable` with custom peek, write, subscribe, and notify functions.
83    pub fn new(
84        peek_fn: Box<dyn Fn() -> ReadRef<'static, T>>,
85        write_fn: Box<dyn Fn() -> WriteRef<'static, T>>,
86        subscribe_fn: Box<dyn Fn()>,
87        notify_fn: Box<dyn Fn()>,
88    ) -> Self {
89        Self {
90            peek_fn: Rc::from(peek_fn),
91            write_fn: Rc::from(write_fn),
92            subscribe_fn: Rc::from(subscribe_fn),
93            notify_fn: Rc::from(notify_fn),
94        }
95    }
96
97    /// Read the value and subscribe to changes.
98    pub fn read(&self) -> ReadRef<'static, T> {
99        self.subscribe();
100        self.peek()
101    }
102
103    /// Read the value without subscribing.
104    pub fn peek(&self) -> ReadRef<'static, T> {
105        (self.peek_fn)()
106    }
107
108    /// Write the value and notify subscribers.
109    pub fn write(&mut self) -> WriteRef<'static, T> {
110        self.notify();
111        (self.write_fn)()
112    }
113
114    pub fn write_if(&mut self, with: impl FnOnce(WriteRef<'static, T>) -> bool) {
115        let changed = with(self.write());
116        if changed {
117            self.notify();
118        }
119    }
120
121    /// Subscribe to changes.
122    fn subscribe(&self) {
123        (self.subscribe_fn)()
124    }
125
126    /// Notify subscribers.
127    fn notify(&self) {
128        (self.notify_fn)()
129    }
130}
131
132pub trait IntoWritable<T: 'static> {
133    fn into_writable(self) -> Writable<T>;
134}
135
136impl<T: 'static> IntoWritable<T> for State<T> {
137    fn into_writable(self) -> Writable<T> {
138        Writable::from_state(self)
139    }
140}
141
142impl<T> From<State<T>> for Writable<T> {
143    fn from(value: State<T>) -> Self {
144        value.into_writable()
145    }
146}
147
148impl<T> From<Writable<T>> for Readable<T> {
149    fn from(value: Writable<T>) -> Self {
150        Readable {
151            read_fn: Rc::new({
152                let value = value.clone();
153                move || {
154                    value.subscribe();
155                    ReadableRef::Ref((value.peek_fn)())
156                }
157            }),
158            peek_fn: Rc::new(move || ReadableRef::Ref((value.peek_fn)())),
159        }
160    }
161}