Skip to main content

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_silently()),
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: impl Fn() -> ReadRef<'static, T> + 'static,
85        write_fn: impl Fn() -> WriteRef<'static, T> + 'static,
86        subscribe_fn: impl Fn() + 'static,
87        notify_fn: impl Fn() + 'static,
88    ) -> Self {
89        Self {
90            peek_fn: Rc::new(peek_fn),
91            write_fn: Rc::new(write_fn),
92            subscribe_fn: Rc::new(subscribe_fn),
93            notify_fn: Rc::new(notify_fn),
94        }
95    }
96
97    /// Read the value and subscribe to changes.
98    #[track_caller]
99    pub fn read(&self) -> ReadRef<'static, T> {
100        self.subscribe();
101        self.peek()
102    }
103
104    /// Read the value without subscribing.
105    #[track_caller]
106    pub fn peek(&self) -> ReadRef<'static, T> {
107        (self.peek_fn)()
108    }
109
110    /// Write the value and notify subscribers.
111    #[track_caller]
112    pub fn write(&mut self) -> WriteRef<'static, T> {
113        self.notify();
114        (self.write_fn)()
115    }
116
117    #[track_caller]
118    pub fn write_if(&mut self, with: impl FnOnce(WriteRef<'static, T>) -> bool) -> bool {
119        let changed = with((self.write_fn)());
120        if changed {
121            self.notify();
122        }
123        changed
124    }
125
126    /// Subscribe to changes.
127    fn subscribe(&self) {
128        (self.subscribe_fn)()
129    }
130
131    /// Notify subscribers.
132    fn notify(&self) {
133        (self.notify_fn)()
134    }
135
136    /// Derive a new `Writable` that reads and writes only a part of the value.
137    ///
138    /// # Example
139    ///
140    /// ```rust, ignore
141    /// let user = use_state(|| (String::from("Alice"), 30));
142    /// let writable: Writable<(String, u32)> = user.into_writable();
143    ///
144    /// let name = writable.map(
145    ///     move || user.peek().map(|user| Ref::map(user, |user| &user.0)),
146    ///     move || user.write_silently().map(|user| RefMut::map(user, |user| &mut user.0)),
147    /// );
148    /// ```
149    pub fn map<O>(
150        &self,
151        peek_fn: impl Fn() -> ReadRef<'static, O> + 'static,
152        write_fn: impl Fn() -> WriteRef<'static, O> + 'static,
153    ) -> Writable<O> {
154        Writable {
155            peek_fn: Rc::new(peek_fn),
156            write_fn: Rc::new(write_fn),
157            subscribe_fn: self.subscribe_fn.clone(),
158            notify_fn: self.notify_fn.clone(),
159        }
160    }
161}
162
163pub trait IntoWritable<T: 'static> {
164    fn into_writable(self) -> Writable<T>;
165}
166
167impl<T: 'static> IntoWritable<T> for State<T> {
168    fn into_writable(self) -> Writable<T> {
169        Writable::from_state(self)
170    }
171}
172
173impl<T> From<State<T>> for Writable<T> {
174    fn from(value: State<T>) -> Self {
175        value.into_writable()
176    }
177}
178
179impl<T> From<Writable<T>> for Readable<T> {
180    fn from(value: Writable<T>) -> Self {
181        Readable {
182            read_fn: Rc::new({
183                let value = value.clone();
184                move || {
185                    value.subscribe();
186                    ReadableRef::Ref((value.peek_fn)())
187                }
188            }),
189            peek_fn: Rc::new(move || ReadableRef::Ref((value.peek_fn)())),
190            equal_fn: Rc::new(move |_| true),
191        }
192    }
193}