Skip to main content

freya_core/lifecycle/
readable.rs

1//! Type-erased readable state that hides generic type parameters.
2
3use std::{
4    cell::Ref,
5    rc::Rc,
6};
7
8use crate::prelude::*;
9
10/// A type-erased readable state that only exposes the value type `T`.
11///
12/// This abstraction allows components to accept read-only state from any source without
13/// knowing whether it comes from local state ([`use_state`]) or
14/// global state (Freya Radio). Unlike [`Writable`], this only
15/// provides read access to the underlying value.
16///
17/// # Sources
18///
19/// `Readable` can be created from:
20/// - [`State<T>`] via [`From`] or [`IntoReadable`]
21/// - `RadioSlice` via `IntoReadable`
22/// - [`Writable<T>`] via [`From<Writable<T>>`]
23///
24/// # Example
25///
26/// ```rust, ignore
27/// #[derive(PartialEq)]
28/// struct Counter {
29///     count: Readable<i32>,
30/// }
31///
32/// impl Component for Counter {
33///     fn render(&self) -> impl IntoElement {
34///         // The component only reads the value, never modifies it
35///         format!("Count: {}", self.count.read())
36///     }
37/// }
38///
39/// fn app() -> impl IntoElement {
40///     let local = use_state(|| 0);
41///     let radio = use_radio(AppChannel::Count);
42///     let slice = radio.slice_current(|s| &s.count);
43///
44///     rect()
45///         // Pass local state as Readable
46///         .child(Counter { count: local.into_readable() })
47///         // Pass global radio slice as Readable
48///         .child(Counter { count: slice.into_readable() })
49/// }
50/// ```
51pub struct Readable<T: 'static> {
52    pub(crate) read_fn: Rc<dyn Fn() -> ReadableRef<T>>,
53    pub(crate) peek_fn: Rc<dyn Fn() -> ReadableRef<T>>,
54    pub(crate) equal_fn: Rc<dyn Fn(&T) -> bool>,
55}
56
57impl<T: 'static> Clone for Readable<T> {
58    fn clone(&self) -> Self {
59        Self {
60            read_fn: self.read_fn.clone(),
61            peek_fn: self.peek_fn.clone(),
62            equal_fn: self.equal_fn.clone(),
63        }
64    }
65}
66
67impl<T: 'static> PartialEq for Readable<T> {
68    fn eq(&self, other: &Self) -> bool {
69        (self.equal_fn)(&*other.peek())
70    }
71}
72
73impl<T: PartialEq> From<T> for Readable<T> {
74    fn from(value: T) -> Self {
75        Readable::from_value(value)
76    }
77}
78
79impl<T: 'static + PartialEq> Readable<T> {
80    /// Create from an owned value.
81    pub fn from_value(value: T) -> Self {
82        let value = Rc::new(value);
83        Self {
84            read_fn: Rc::new({
85                let value = value.clone();
86                move || ReadableRef::Borrowed(value.clone())
87            }),
88            peek_fn: Rc::new({
89                let value = value.clone();
90                move || ReadableRef::Borrowed(value.clone())
91            }),
92            equal_fn: Rc::new(move |other| other == &*value),
93        }
94    }
95}
96impl<T: 'static> Readable<T> {
97    /// Create from local `State<T>`.
98    pub fn from_state(state: State<T>) -> Self {
99        Self {
100            read_fn: Rc::new(move || ReadableRef::Ref(state.read())),
101            peek_fn: Rc::new(move || ReadableRef::Ref(state.peek())),
102            equal_fn: Rc::new(move |_| true),
103        }
104    }
105
106    /// Create a new `Readable` with custom read and peek functions.
107    pub fn new(
108        read_fn: impl Fn() -> ReadableRef<T> + 'static,
109        peek_fn: impl Fn() -> ReadableRef<T> + 'static,
110        equal_fn: impl Fn(&T) -> bool + 'static,
111    ) -> Self {
112        Self {
113            read_fn: Rc::new(read_fn),
114            peek_fn: Rc::new(peek_fn),
115            equal_fn: Rc::new(equal_fn),
116        }
117    }
118
119    /// Read the value, subscribing to changes if the underlying source supports it.
120    ///
121    /// Whether this actually subscribes depends on how the `Readable` was created:
122    /// - From [`State<T>`]: subscribes to state changes.
123    /// - From a `RadioSlice`: subscribes to radio channel changes.
124    /// - From a plain value ([`from_value`](Self::from_value)): no subscription, just returns the value.
125    pub fn read(&self) -> ReadableRef<T> {
126        (self.read_fn)()
127    }
128
129    /// Read the value without subscribing to changes.
130    pub fn peek(&self) -> ReadableRef<T> {
131        (self.peek_fn)()
132    }
133
134    /// Derive a new `Readable` that exposes only a part of the value.
135    ///
136    /// # Example
137    ///
138    /// ```rust, ignore
139    /// let user = use_state(|| (String::from("Alice"), 30));
140    /// let user: Readable<(String, u32)> = user.into_readable();
141    ///
142    /// let name = user.map(|user| &user.0, |name| name == "Alice");
143    /// ```
144    pub fn map<O>(
145        &self,
146        map_fn: impl Fn(&T) -> &O + 'static,
147        equal_fn: impl Fn(&O) -> bool + 'static,
148    ) -> Readable<O> {
149        let readable = self.clone();
150        let map_fn = Rc::new(map_fn);
151        Readable {
152            read_fn: Rc::new({
153                let map_fn = map_fn.clone();
154                let readable = readable.clone();
155                move || {
156                    let ReadableRef::Ref(r) = readable.read() else {
157                        unreachable!("Unsupported")
158                    };
159
160                    ReadableRef::Ref(r.map(|r| Ref::map(r, |v| (map_fn)(v))))
161                }
162            }),
163            peek_fn: Rc::new(move || {
164                let ReadableRef::Ref(r) = readable.peek() else {
165                    unreachable!("Unsupported")
166                };
167
168                ReadableRef::Ref(r.map(|r| Ref::map(r, |v| (map_fn)(v))))
169            }),
170            equal_fn: Rc::new(equal_fn),
171        }
172    }
173}
174
175pub trait IntoReadable<T: 'static> {
176    fn into_readable(self) -> Readable<T>;
177}
178
179impl<T: 'static> IntoReadable<T> for State<T> {
180    fn into_readable(self) -> Readable<T> {
181        Readable::from_state(self)
182    }
183}
184
185impl<T: 'static + PartialEq> IntoReadable<T> for T {
186    fn into_readable(self) -> Readable<T> {
187        Readable::from_value(self)
188    }
189}
190
191impl<T: 'static> IntoReadable<T> for Memo<T> {
192    fn into_readable(self) -> Readable<T> {
193        Readable::from_state(self.state)
194    }
195}
196
197impl<T> From<State<T>> for Readable<T> {
198    fn from(value: State<T>) -> Self {
199        value.into_readable()
200    }
201}
202
203impl<T> From<Memo<T>> for Readable<T> {
204    fn from(value: Memo<T>) -> Self {
205        value.into_readable()
206    }
207}