Skip to main content

freya_core/lifecycle/
readable.rs

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