Skip to main content

freya_radio/
slice.rs

1use std::{
2    cell::{
3        Ref,
4        RefMut,
5    },
6    marker::PhantomData,
7    rc::Rc,
8};
9
10use freya_core::prelude::*;
11
12use crate::hooks::{
13    Radio,
14    RadioChannel,
15    RadioStation,
16};
17
18/// A read-only slice of a portion of the global radio state.
19///
20/// Components using a slice only re-render when that specific portion changes,
21/// as determined by the slice's channel.
22///
23/// # Example
24///
25/// ```rust, ignore
26/// let count_slice = radio.slice(AppChannel::Count, |state| &state.count);
27/// child_component(count_slice)
28/// ```
29pub struct RadioSlice<Value, SliceValue, Channel>
30where
31    Channel: RadioChannel<Value>,
32    Value: 'static,
33    SliceValue: 'static,
34{
35    channel: Channel,
36    station: RadioStation<Value, Channel>,
37    selector: Rc<dyn Fn(&Value) -> &SliceValue>,
38    _marker: PhantomData<SliceValue>,
39}
40
41impl<Value, SliceValue, Channel> Clone for RadioSlice<Value, SliceValue, Channel>
42where
43    Channel: RadioChannel<Value>,
44    SliceValue: 'static,
45{
46    fn clone(&self) -> Self {
47        Self {
48            channel: self.channel.clone(),
49            station: self.station,
50            selector: self.selector.clone(),
51            _marker: PhantomData,
52        }
53    }
54}
55
56impl<Value, SliceValue, Channel> PartialEq for RadioSlice<Value, SliceValue, Channel>
57where
58    Channel: RadioChannel<Value>,
59    SliceValue: 'static,
60{
61    fn eq(&self, other: &Self) -> bool {
62        self.channel == other.channel
63    }
64}
65
66impl<Value, SliceValue, Channel> RadioSlice<Value, SliceValue, Channel>
67where
68    Channel: RadioChannel<Value>,
69    SliceValue: 'static,
70{
71    pub(crate) fn new(
72        channel: Channel,
73        station: RadioStation<Value, Channel>,
74        selector: impl Fn(&Value) -> &SliceValue + 'static,
75    ) -> RadioSlice<Value, SliceValue, Channel> {
76        RadioSlice {
77            channel,
78            station,
79            selector: Rc::new(selector),
80            _marker: PhantomData,
81        }
82    }
83
84    pub(crate) fn subscribe_if_not(&self) {
85        if let Some(rc) = ReactiveContext::try_current() {
86            let is_listening = self.station.is_listening(&self.channel, &rc);
87
88            if !is_listening {
89                self.station.listen(self.channel.clone(), rc);
90            }
91        }
92    }
93
94    /// Read the slice value and subscribe to changes.
95    #[track_caller]
96    #[allow(invalid_reference_casting)]
97    pub fn read(&'_ self) -> ReadRef<'_, SliceValue> {
98        self.subscribe_if_not();
99        self.peek()
100    }
101
102    /// Read the slice value and subscribe to changes, with 'static lifetime.
103    #[track_caller]
104    #[allow(invalid_reference_casting)]
105    pub fn read_unchecked(&'_ self) -> ReadRef<'static, SliceValue> {
106        self.subscribe_if_not();
107        self.peek_unchecked()
108    }
109
110    /// Read the slice value without subscribing.
111    #[track_caller]
112    #[allow(invalid_reference_casting)]
113    pub fn peek(&'_ self) -> ReadRef<'_, SliceValue> {
114        self.peek_unchecked()
115    }
116
117    /// Read the slice value without subscribing, with 'static lifetime.
118    #[track_caller]
119    #[allow(invalid_reference_casting)]
120    pub fn peek_unchecked(&'_ self) -> ReadRef<'static, SliceValue> {
121        let inner = self.station.peek_unchecked();
122        inner.map(|v| {
123            Ref::map(v, |v| {
124                (self.selector)(unsafe { &mut *(v as *const Value as *mut Value) })
125            })
126        })
127    }
128}
129
130/// A mutable slice of a portion of the global radio state.
131///
132/// Like `RadioSlice`, components only re-render when the specific portion changes.
133///
134/// # Example
135///
136/// ```rust, ignore
137/// let mut count_slice = radio.slice_mut(AppChannel::Count, |state| &mut state.count);
138/// child_component(count_slice)
139/// ```
140pub struct RadioSliceMut<Value, SliceValue, Channel>
141where
142    Channel: RadioChannel<Value>,
143    Value: 'static,
144    SliceValue: 'static,
145{
146    channel: Channel,
147    pub(crate) station: RadioStation<Value, Channel>,
148    selector: Rc<dyn Fn(&mut Value) -> &mut SliceValue>,
149    _marker: PhantomData<SliceValue>,
150}
151
152impl<Value, SliceValue, Channel> Clone for RadioSliceMut<Value, SliceValue, Channel>
153where
154    Channel: RadioChannel<Value>,
155    SliceValue: 'static,
156{
157    fn clone(&self) -> Self {
158        Self {
159            channel: self.channel.clone(),
160            station: self.station,
161            selector: self.selector.clone(),
162            _marker: PhantomData,
163        }
164    }
165}
166
167impl<Value, SliceValue, Channel> PartialEq for RadioSliceMut<Value, SliceValue, Channel>
168where
169    Channel: RadioChannel<Value>,
170    SliceValue: 'static,
171{
172    fn eq(&self, other: &Self) -> bool {
173        self.channel == other.channel
174    }
175}
176
177impl<Value, SliceValue, Channel> RadioSliceMut<Value, SliceValue, Channel>
178where
179    Channel: RadioChannel<Value>,
180    SliceValue: 'static,
181{
182    pub(crate) fn new(
183        channel: Channel,
184        station: RadioStation<Value, Channel>,
185        selector: impl Fn(&mut Value) -> &mut SliceValue + 'static,
186    ) -> RadioSliceMut<Value, SliceValue, Channel> {
187        RadioSliceMut {
188            channel,
189            station,
190            selector: Rc::new(selector),
191            _marker: PhantomData,
192        }
193    }
194
195    pub(crate) fn subscribe_if_not(&self) {
196        if let Some(rc) = ReactiveContext::try_current() {
197            let is_listening = self.station.is_listening(&self.channel, &rc);
198
199            if !is_listening {
200                self.station.listen(self.channel.clone(), rc);
201            }
202        }
203    }
204
205    /// Read the slice value and subscribe to changes.
206    #[track_caller]
207    #[allow(invalid_reference_casting)]
208    pub fn read(&'_ self) -> ReadRef<'_, SliceValue> {
209        self.subscribe_if_not();
210        self.peek()
211    }
212
213    /// Read the slice value and subscribe to changes, with 'static lifetime.
214    #[track_caller]
215    #[allow(invalid_reference_casting)]
216    pub fn read_unchecked(&'_ self) -> ReadRef<'static, SliceValue> {
217        self.subscribe_if_not();
218        self.peek_unchecked()
219    }
220
221    /// Read the slice value without subscribing.
222    #[track_caller]
223    #[allow(invalid_reference_casting)]
224    pub fn peek(&'_ self) -> ReadRef<'_, SliceValue> {
225        self.peek_unchecked()
226    }
227
228    /// Read the slice value without subscribing, with 'static lifetime.
229    #[track_caller]
230    #[allow(invalid_reference_casting)]
231    pub fn peek_unchecked(&'_ self) -> ReadRef<'static, SliceValue> {
232        let inner = self.station.peek_unchecked();
233        inner.map(|v| {
234            Ref::map(v, |v| {
235                (self.selector)(unsafe { &mut *(v as *const Value as *mut Value) })
236            })
237        })
238    }
239
240    /// Write the slice value, with 'static lifetime.
241    #[track_caller]
242    pub fn write_unchecked(&'_ self) -> WriteRef<'static, SliceValue> {
243        self.notify();
244        self.write_silently()
245    }
246
247    /// Write the slice value without notifying.
248    #[track_caller]
249    pub fn write_silently(&'_ self) -> WriteRef<'static, SliceValue> {
250        let value = self.station.value.write_unchecked();
251        let selector = self.selector.clone();
252        value.map(|v| RefMut::map(v, |v| selector(v)))
253    }
254
255    /// Notify listeners for this slice's channel.
256    pub fn notify(&self) {
257        let value = self.station.peek_unchecked();
258        for channel in self.channel.clone().derive_channel(&value) {
259            self.station.notify_listeners(&channel)
260        }
261        self.station.cleanup();
262    }
263
264    /// Write the slice value.
265    #[track_caller]
266    pub fn write(&'_ mut self) -> WriteRef<'_, SliceValue> {
267        self.write_unchecked()
268    }
269}
270
271impl<Value, SliceValue, Channel> WritableUtils<SliceValue>
272    for RadioSliceMut<Value, SliceValue, Channel>
273where
274    Channel: RadioChannel<Value>,
275    Value: 'static,
276    SliceValue: 'static,
277{
278    fn write_state(&mut self) -> WriteRef<'static, SliceValue> {
279        self.write_unchecked()
280    }
281
282    fn peek_state(&self) -> ReadRef<'static, SliceValue> {
283        self.peek_unchecked()
284    }
285}
286
287impl<Value, Channel> Radio<Value, Channel>
288where
289    Channel: RadioChannel<Value>,
290    Value: 'static,
291{
292    /// Create a read-only slice of a specific portion of the state.
293    ///
294    /// # Example
295    ///
296    /// ```rust, ignore
297    /// let count_slice = radio.slice(AppChannel::Count, |state| &state.count);
298    /// ```
299    pub fn slice<SliceValue>(
300        &self,
301        channel: Channel,
302        selector: impl Fn(&Value) -> &SliceValue + 'static,
303    ) -> RadioSlice<Value, SliceValue, Channel> {
304        let station = self.antenna.peek().station;
305        RadioSlice::new(channel, station, selector)
306    }
307
308    /// Create a read-only slice using the current radio's channel.
309    ///
310    /// # Example
311    ///
312    /// ```rust, ignore
313    /// let count_slice = radio.slice_current(|state| &state.count);
314    /// ```
315    pub fn slice_current<SliceValue>(
316        &self,
317        selector: impl Fn(&Value) -> &SliceValue + 'static,
318    ) -> RadioSlice<Value, SliceValue, Channel> {
319        let channel = self.antenna.peek().channel.clone();
320        self.slice(channel, selector)
321    }
322
323    /// Create a mutable slice of a specific portion of the state.
324    ///
325    /// # Example
326    ///
327    /// ```rust, ignore
328    /// let mut count_slice = radio.slice_mut(AppChannel::Count, |state| &mut state.count);
329    /// ```
330    pub fn slice_mut<SliceValue>(
331        &self,
332        channel: Channel,
333        selector: impl Fn(&mut Value) -> &mut SliceValue + 'static,
334    ) -> RadioSliceMut<Value, SliceValue, Channel> {
335        let station = self.antenna.peek().station;
336        RadioSliceMut::new(channel, station, selector)
337    }
338
339    /// Create a mutable slice using the current radio's channel.
340    ///
341    /// # Example
342    ///
343    /// ```rust, ignore
344    /// let mut count_slice = radio.slice_mut_current(|state| &mut state.count);
345    /// ```
346    pub fn slice_mut_current<SliceValue>(
347        &self,
348        selector: impl Fn(&mut Value) -> &mut SliceValue + 'static,
349    ) -> RadioSliceMut<Value, SliceValue, Channel> {
350        let channel = self.antenna.peek().channel.clone();
351        self.slice_mut(channel, selector)
352    }
353}