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}