Skip to main content

freya_core/lifecycle/
writable.rs

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