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}
52
53impl<T: 'static> Clone for Readable<T> {
54    fn clone(&self) -> Self {
55        Self {
56            read_fn: self.read_fn.clone(),
57            peek_fn: self.peek_fn.clone(),
58        }
59    }
60}
61
62impl<T: 'static> PartialEq for Readable<T> {
63    fn eq(&self, _other: &Self) -> bool {
64        true
65    }
66}
67
68impl<T> From<T> for Readable<T> {
69    fn from(value: T) -> Self {
70        Readable::from_value(value)
71    }
72}
73
74impl<T: 'static> Readable<T> {
75    /// Create from local `State<T>`.
76    pub fn from_state(state: State<T>) -> Self {
77        Self {
78            read_fn: Rc::new(move || ReadableRef::Ref(state.read())),
79            peek_fn: Rc::new(move || ReadableRef::Ref(state.peek())),
80        }
81    }
82
83    /// Create from an owned value.
84    pub fn from_value(value: T) -> Self {
85        let value = Rc::new(value);
86        Self {
87            read_fn: Rc::new({
88                let value = value.clone();
89                move || ReadableRef::Borrowed(value.clone())
90            }),
91            peek_fn: Rc::new(move || ReadableRef::Borrowed(value.clone())),
92        }
93    }
94
95    /// Create a new `Readable` with custom read and peek functions.
96    pub fn new(
97        read_fn: Box<dyn Fn() -> ReadableRef<T>>,
98        peek_fn: Box<dyn Fn() -> ReadableRef<T>>,
99    ) -> Self {
100        Self {
101            read_fn: Rc::from(read_fn),
102            peek_fn: Rc::from(peek_fn),
103        }
104    }
105
106    /// Read the value and subscribe to changes.
107    pub fn read(&self) -> ReadableRef<T> {
108        (self.read_fn)()
109    }
110
111    /// Read the value without subscribing.
112    pub fn peek(&self) -> ReadableRef<T> {
113        (self.peek_fn)()
114    }
115}
116
117pub trait IntoReadable<T: 'static> {
118    fn into_readable(self) -> Readable<T>;
119}
120
121impl<T: 'static> IntoReadable<T> for State<T> {
122    fn into_readable(self) -> Readable<T> {
123        Readable::from_state(self)
124    }
125}
126
127impl<T> From<State<T>> for Readable<T> {
128    fn from(value: State<T>) -> Self {
129        value.into_readable()
130    }
131}