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: 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    #[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
137pub trait IntoWritable<T: 'static> {
138    fn into_writable(self) -> Writable<T>;
139}
140
141impl<T: 'static> IntoWritable<T> for State<T> {
142    fn into_writable(self) -> Writable<T> {
143        Writable::from_state(self)
144    }
145}
146
147impl<T> From<State<T>> for Writable<T> {
148    fn from(value: State<T>) -> Self {
149        value.into_writable()
150    }
151}
152
153impl<T> From<Writable<T>> for Readable<T> {
154    fn from(value: Writable<T>) -> Self {
155        Readable {
156            read_fn: Rc::new({
157                let value = value.clone();
158                move || {
159                    value.subscribe();
160                    ReadableRef::Ref((value.peek_fn)())
161                }
162            }),
163            peek_fn: Rc::new(move || ReadableRef::Ref((value.peek_fn)())),
164        }
165    }
166}