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}
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, subscribing to changes if the underlying source supports it.
107    ///
108    /// Whether this actually subscribes depends on how the `Readable` was created:
109    /// - From [`State<T>`]: subscribes to state changes.
110    /// - From a `RadioSlice`: subscribes to radio channel changes.
111    /// - From a plain value ([`from_value`](Self::from_value)): no subscription, just returns the value.
112    pub fn read(&self) -> ReadableRef<T> {
113        (self.read_fn)()
114    }
115
116    /// Read the value without subscribing to changes.
117    pub fn peek(&self) -> ReadableRef<T> {
118        (self.peek_fn)()
119    }
120}
121
122pub trait IntoReadable<T: 'static> {
123    fn into_readable(self) -> Readable<T>;
124}
125
126impl<T: 'static> IntoReadable<T> for State<T> {
127    fn into_readable(self) -> Readable<T> {
128        Readable::from_state(self)
129    }
130}
131
132impl<T: 'static> IntoReadable<T> for T {
133    fn into_readable(self) -> Readable<T> {
134        Readable::from_value(self)
135    }
136}
137
138impl<T> From<State<T>> for Readable<T> {
139    fn from(value: State<T>) -> Self {
140        value.into_readable()
141    }
142}