Skip to main content

freya_radio/hooks/
use_radio.rs

1use std::{
2    cell::RefCell,
3    collections::HashMap,
4    hash::Hash,
5    ops::{
6        Deref,
7        DerefMut,
8    },
9    rc::Rc,
10};
11
12use freya_core::{
13    integration::FxHashSet,
14    prelude::*,
15};
16
17#[cfg(feature = "tracing")]
18pub trait RadioChannel<T>: 'static + PartialEq + Eq + Clone + Hash + std::fmt::Debug + Ord {
19    fn derive_channel(self, _radio: &T) -> Vec<Self> {
20        vec![self]
21    }
22}
23
24/// Defines a channel for radio communication.
25/// Channels are used to subscribe to specific changes in the global state.
26/// Each channel must implement this trait to be used with [`RadioStation`] and [`Radio`].
27///
28/// Channels allow fine-grained control over which components re-render when the state changes.
29/// Components only re-render when a channel they are subscribed to is notified.
30///
31/// # Example
32///
33/// ```rust, no_run
34/// # use freya::radio::*;
35///
36/// # struct Data;
37///
38/// #[derive(PartialEq, Eq, Clone, Debug, Copy, Hash)]
39/// pub enum DataChannel {
40///     ListCreation,
41///     SpecificListItemUpdate(usize),
42/// }
43///
44/// impl RadioChannel<Data> for DataChannel {}
45/// ```
46#[cfg(not(feature = "tracing"))]
47pub trait RadioChannel<T>: 'static + PartialEq + Eq + Clone + Hash {
48    /// Derive additional channels based on the current state value.
49    /// This allows a single write operation to notify multiple channels.
50    ///
51    /// By default, returns a vector containing only `self`.
52    ///
53    /// # Example
54    ///
55    /// ```rust, no_run
56    /// # use freya::radio::*;
57    ///
58    /// # struct Data;
59    ///
60    /// #[derive(PartialEq, Eq, Clone, Debug, Copy, Hash)]
61    /// pub enum DataChannel {
62    ///     All,
63    ///     Specific(usize),
64    /// }
65    ///
66    /// impl RadioChannel<Data> for DataChannel {
67    ///     fn derive_channel(self, _data: &Data) -> Vec<Self> {
68    ///         match self {
69    ///             DataChannel::All => vec![DataChannel::All],
70    ///             DataChannel::Specific(id) => vec![DataChannel::All, DataChannel::Specific(id)],
71    ///         }
72    ///     }
73    /// }
74    /// ```
75    fn derive_channel(self, _radio: &T) -> Vec<Self> {
76        vec![self]
77    }
78}
79
80/// The central hub for global state management in Freya applications.
81/// A `RadioStation` holds the global state value and manages subscriptions to different channels.
82/// Components can subscribe to specific channels to receive notifications when the state changes.
83///
84/// RadioStations can be shared across multiple windows or components using [`use_share_radio`].
85///
86/// # Examples
87///
88/// ## Basic usage
89///
90/// ```rust, no_run
91/// # use freya::prelude::*;
92/// # use freya::radio::*;
93///
94/// #[derive(Default)]
95/// struct AppState {
96///     count: i32,
97/// }
98///
99/// #[derive(PartialEq, Eq, Clone, Debug, Copy, Hash)]
100/// enum AppChannel {
101///     Count,
102/// }
103///
104/// impl RadioChannel<AppState> for AppChannel {}
105///
106/// fn app() -> impl IntoElement {
107///     // Create a radio station (scoped to this component tree)
108///     use_init_radio_station::<AppState, AppChannel>(AppState::default);
109///
110///     let mut radio = use_radio(AppChannel::Count);
111///
112///     rect()
113///         .child(label().text(format!("Count: {}", radio.read().count)))
114///         .child(
115///             Button::new()
116///                 .on_press(move |_| radio.write().count += 1)
117///                 .child("Increment"),
118///         )
119/// }
120/// ```
121///
122/// ## Global radio station for multi-window apps
123///
124/// ```rust, ignore
125/// # use freya::prelude::*;
126/// # use freya::radio::*;
127///
128/// let radio_station = RadioStation::create_global(AppState::default);
129///
130/// launch(
131///     LaunchConfig::new()
132///         .with_window(WindowConfig::new(Window1 { radio_station }))
133///         .with_window(WindowConfig::new(Window2 { radio_station })),
134/// );
135/// ```
136pub struct RadioStation<Value, Channel>
137where
138    Channel: RadioChannel<Value>,
139    Value: 'static,
140{
141    pub(crate) value: State<Value>,
142    listeners: State<HashMap<Channel, Rc<RefCell<FxHashSet<ReactiveContext>>>>>,
143}
144
145impl<Value, Channel> Clone for RadioStation<Value, Channel>
146where
147    Channel: RadioChannel<Value>,
148{
149    fn clone(&self) -> Self {
150        *self
151    }
152}
153
154impl<Value, Channel> Copy for RadioStation<Value, Channel> where Channel: RadioChannel<Value> {}
155
156impl<Value, Channel> RadioStation<Value, Channel>
157where
158    Channel: RadioChannel<Value>,
159{
160    pub(crate) fn create(init_value: Value) -> Self {
161        RadioStation {
162            value: State::create(init_value),
163            listeners: State::create(HashMap::default()),
164        }
165    }
166
167    /// Create a global `RadioStation` that lives for the entire application lifetime.
168    /// This is useful for sharing state across multiple windows.
169    ///
170    /// This is **not** a hook, do not use it inside components like you would [`use_radio`].
171    /// You would usually want to call this in your `main` function, not anywhere else.
172    ///
173    /// # Example
174    ///
175    /// ```rust, ignore
176    /// # use freya::prelude::*;
177    /// # use freya::radio::*;
178    ///
179    /// let radio_station = RadioStation::create_global(AppState::default);
180    ///
181    /// launch(
182    ///     LaunchConfig::new()
183    ///         .with_window(WindowConfig::new(Window1 { radio_station }))
184    ///         .with_window(WindowConfig::new(Window2 { radio_station })),
185    /// );
186    /// ```
187    pub fn create_global(init_value: Value) -> Self {
188        RadioStation {
189            value: State::create_global(init_value),
190            listeners: State::create_global(HashMap::default()),
191        }
192    }
193
194    pub(crate) fn is_listening(
195        &self,
196        channel: &Channel,
197        reactive_context: &ReactiveContext,
198    ) -> bool {
199        let listeners = self.listeners.peek();
200        listeners
201            .get(channel)
202            .map(|contexts| contexts.borrow().contains(reactive_context))
203            .unwrap_or_default()
204    }
205
206    pub(crate) fn listen(&self, channel: Channel, mut reactive_context: ReactiveContext) {
207        let mut listeners = self.listeners.write_unchecked();
208        let listeners = listeners.entry(channel).or_default();
209        reactive_context.subscribe(listeners);
210    }
211
212    pub(crate) fn notify_listeners(&self, channel: &Channel) {
213        let listeners = self.listeners.write_unchecked();
214
215        #[cfg(feature = "tracing")]
216        tracing::info!("Notifying {channel:?}");
217
218        for (listener_channel, listeners) in listeners.iter() {
219            if listener_channel == channel {
220                for reactive_context in listeners.borrow().iter() {
221                    reactive_context.notify();
222                }
223            }
224        }
225    }
226
227    /// Read the current state value and subscribe to all channel changes.
228    /// Any component calling this will re-render when any channel is notified.
229    ///
230    /// # Example
231    ///
232    /// ```rust, ignore
233    /// # use freya::radio::*;
234    /// let value = radio_station.read();
235    /// ```
236    #[track_caller]
237    pub fn read(&'_ self) -> ReadRef<'_, Value> {
238        self.value.read()
239    }
240
241    #[track_caller]
242    pub fn peek_unchecked(&self) -> ReadRef<'static, Value> {
243        self.value.peek()
244    }
245
246    /// Read the current state value without subscribing to changes.
247    /// Components using this will not re-render when the state changes.
248    ///
249    /// # Example
250    ///
251    /// ```rust, ignore
252    /// # use freya::radio::*;
253    /// let value = radio_station.peek();
254    /// ```
255    #[track_caller]
256    pub fn peek(&'_ self) -> ReadRef<'_, Value> {
257        self.value.peek()
258    }
259
260    pub(crate) fn cleanup(&self) {
261        let mut listeners = self.listeners.write_unchecked();
262
263        // Clean up those channels with no reactive contexts
264        listeners.retain(|_, listeners| !listeners.borrow().is_empty());
265
266        #[cfg(feature = "tracing")]
267        {
268            use itertools::Itertools;
269            use tracing::{
270                Level,
271                info,
272                span,
273            };
274
275            let mut channels_subscribers = HashMap::<&Channel, usize>::new();
276
277            for (channel, listeners) in listeners.iter() {
278                *channels_subscribers.entry(&channel).or_default() = listeners.borrow().len();
279            }
280
281            let span = span!(Level::DEBUG, "Radio Station Metrics");
282            let _enter = span.enter();
283
284            for (channel, count) in channels_subscribers.iter().sorted() {
285                info!(" {count} subscribers for {channel:?}")
286            }
287        }
288    }
289
290    /// Modify the state using a specific channel.
291    /// This will notify all subscribers to that channel (and any derived channels).
292    ///
293    /// Returns a [`RadioGuard`] that allows direct mutation of the state.
294    /// The guard automatically notifies listeners when dropped.
295    ///
296    /// # Example
297    ///
298    /// ```rust, ignore
299    /// # use freya::radio::*;
300    /// radio_station.write_channel(MyChannel::Update).count += 1;
301    /// ```
302    #[track_caller]
303    pub fn write_channel(&mut self, channel: Channel) -> RadioGuard<Value, Channel> {
304        let value = self.value.write_unchecked();
305        RadioGuard {
306            channels: channel.derive_channel(&*value),
307            station: *self,
308            value,
309        }
310    }
311}
312
313pub struct RadioAntenna<Value, Channel>
314where
315    Channel: RadioChannel<Value>,
316    Value: 'static,
317{
318    pub(crate) channel: Channel,
319    pub(crate) station: RadioStation<Value, Channel>,
320}
321
322impl<Value, Channel> RadioAntenna<Value, Channel>
323where
324    Channel: RadioChannel<Value>,
325{
326    pub(crate) fn new(
327        channel: Channel,
328        station: RadioStation<Value, Channel>,
329    ) -> RadioAntenna<Value, Channel> {
330        RadioAntenna { channel, station }
331    }
332}
333impl<Value, Channel> Clone for RadioAntenna<Value, Channel>
334where
335    Channel: RadioChannel<Value>,
336{
337    fn clone(&self) -> Self {
338        Self {
339            channel: self.channel.clone(),
340            station: self.station,
341        }
342    }
343}
344
345pub struct RadioGuard<Value, Channel>
346where
347    Channel: RadioChannel<Value>,
348    Value: 'static,
349{
350    pub(crate) station: RadioStation<Value, Channel>,
351    pub(crate) channels: Vec<Channel>,
352    pub(crate) value: WriteRef<'static, Value>,
353}
354
355impl<Value, Channel> Drop for RadioGuard<Value, Channel>
356where
357    Channel: RadioChannel<Value>,
358{
359    fn drop(&mut self) {
360        for channel in &mut self.channels {
361            self.station.notify_listeners(channel)
362        }
363        if !self.channels.is_empty() {
364            self.station.cleanup();
365        }
366    }
367}
368
369impl<Value, Channel> Deref for RadioGuard<Value, Channel>
370where
371    Channel: RadioChannel<Value>,
372{
373    type Target = WriteRef<'static, Value>;
374
375    fn deref(&self) -> &Self::Target {
376        &self.value
377    }
378}
379
380impl<Value, Channel> DerefMut for RadioGuard<Value, Channel>
381where
382    Channel: RadioChannel<Value>,
383{
384    fn deref_mut(&mut self) -> &mut WriteRef<'static, Value> {
385        &mut self.value
386    }
387}
388
389/// A reactive handle to the global state for a specific channel.
390/// `Radio` provides methods to read and write the global state, and automatically subscribes
391/// the current component to re-render when the associated channel is notified.
392///
393/// Each `Radio` instance is tied to a specific channel, allowing fine-grained control
394/// over which components update when the state changes.
395///
396/// # Examples
397///
398/// ## Basic usage
399///
400/// ```rust, ignore
401/// # use freya::prelude::*;
402/// # use freya::radio::*;
403///
404/// #[derive(PartialEq)]
405/// struct MyComponent {}
406///
407/// impl Component for MyComponent {
408///     fn render(&self) -> impl IntoElement {
409///         let mut radio = use_radio(MyChannel::Count);
410///
411///         rect()
412///             .child(label().text(format!("Count: {}", radio.read().count)))
413///             .child(
414///                 Button::new()
415///                     .on_press(move |_| radio.write().count += 1)
416///                     .child("Increment"),
417///             )
418///     }
419/// }
420/// ```
421///
422/// ## Using reducers
423///
424/// ```rust, ignore
425/// # use freya::prelude::*;
426/// # use freya::radio::*;
427///
428/// #[derive(Clone)]
429/// struct CounterState {
430///     count: i32,
431/// }
432///
433/// impl DataReducer for CounterState {
434///     type Channel = CounterChannel;
435///     type Action = CounterAction;
436///
437///     fn reduce(&mut self, action: CounterAction) -> ChannelSelection<CounterChannel> {
438///         match action {
439///             CounterAction::Increment => self.count += 1,
440///             CounterAction::Decrement => self.count -= 1,
441///         }
442///         ChannelSelection::Current
443///     }
444/// }
445///
446/// #[derive(PartialEq)]
447/// struct CounterComponent {}
448///
449/// impl Component for CounterComponent {
450///     fn render(&self) -> impl IntoElement {
451///         let mut radio = use_radio(CounterChannel::Count);
452///
453///         rect()
454///             .child(
455///                 Button::new()
456///                     .on_press(move |_| radio.apply(CounterAction::Increment))
457///                     .child("+"),
458///             )
459///             .child(label().text(format!("{}", radio.read().count)))
460///             .child(
461///                 Button::new()
462///                     .on_press(move |_| radio.apply(CounterAction::Decrement))
463///                     .child("-"),
464///             )
465///     }
466/// }
467/// ```
468pub struct Radio<Value, Channel>
469where
470    Channel: RadioChannel<Value>,
471    Value: 'static,
472{
473    pub(crate) antenna: State<RadioAntenna<Value, Channel>>,
474}
475
476impl<Value, Channel> Clone for Radio<Value, Channel>
477where
478    Channel: RadioChannel<Value>,
479{
480    fn clone(&self) -> Self {
481        *self
482    }
483}
484impl<Value, Channel> Copy for Radio<Value, Channel> where Channel: RadioChannel<Value> {}
485
486impl<Value, Channel> PartialEq for Radio<Value, Channel>
487where
488    Channel: RadioChannel<Value>,
489{
490    fn eq(&self, other: &Self) -> bool {
491        self.antenna == other.antenna
492    }
493}
494
495impl<Value, Channel> Radio<Value, Channel>
496where
497    Channel: RadioChannel<Value>,
498{
499    pub(crate) fn new(antenna: State<RadioAntenna<Value, Channel>>) -> Radio<Value, Channel> {
500        Radio { antenna }
501    }
502
503    pub(crate) fn subscribe_if_not(&self) {
504        if let Some(rc) = ReactiveContext::try_current() {
505            let antenna = &self.antenna.write_unchecked();
506            let channel = antenna.channel.clone();
507            let is_listening = antenna.station.is_listening(&channel, &rc);
508
509            // Subscribe the reader reactive context to the channel if it wasn't already
510            if !is_listening {
511                antenna.station.listen(channel, rc);
512            }
513        }
514    }
515
516    /// Read the current state value and subscribe the current component to changes
517    /// on this radio's channel. The component will re-render when this channel is notified.
518    ///
519    /// # Example
520    ///
521    /// ```rust, ignore
522    /// # use freya::radio::*;
523    /// let count = radio.read().count;
524    /// ```
525    #[track_caller]
526    pub fn read(&'_ self) -> ReadRef<'_, Value> {
527        self.subscribe_if_not();
528        self.antenna.peek().station.value.peek()
529    }
530
531    /// Read the current state value inside a callback.
532    ///
533    /// Example:
534    ///
535    /// ```rust, ignore
536    /// # use freya::radio::*;
537    /// radio.with(|value| {
538    ///     // Do something with `value`
539    /// });
540    /// ```
541    #[track_caller]
542    pub fn with(&self, cb: impl FnOnce(ReadRef<Value>)) {
543        self.subscribe_if_not();
544        let value = self.antenna.peek().station.value;
545        let borrow = value.read();
546        cb(borrow);
547    }
548
549    /// Get a mutable reference to the state for writing.
550    /// Changes will notify subscribers to this radio's channel.
551    ///
552    /// Returns a [`RadioGuard`] that allows direct mutation of the state.
553    ///
554    /// # Example
555    ///
556    /// ```rust, ignore
557    /// # use freya::radio::*;
558    /// radio.write().count += 1;
559    /// ```
560    #[track_caller]
561    pub fn write(&mut self) -> RadioGuard<Value, Channel> {
562        let value = self.antenna.peek().station.value.write_unchecked();
563        let channel = self.antenna.peek().channel.clone();
564        RadioGuard {
565            channels: channel.derive_channel(&*value),
566            station: self.antenna.read().station,
567            value,
568        }
569    }
570
571    /// Get a mutable reference to the current state value, inside a callback.
572    ///
573    /// Example:
574    ///
575    /// ```rust, ignore
576    /// # use freya::radio::*;
577    /// radio.write_with(|value| {
578    ///     // Modify `value`
579    /// });
580    /// ```
581    #[track_caller]
582    pub fn write_with(&mut self, cb: impl FnOnce(RadioGuard<Value, Channel>)) {
583        let guard = self.write();
584        cb(guard);
585    }
586
587    /// Modify the state using a custom Channel.
588    ///
589    /// ## Example:
590    /// ```rust, ignore
591    /// # use freya::radio::*;
592    /// radio.write(Channel::Whatever).value = 1;
593    /// ```
594    #[track_caller]
595    pub fn write_channel(&mut self, channel: Channel) -> RadioGuard<Value, Channel> {
596        let value = self.antenna.peek().station.value.write_unchecked();
597        RadioGuard {
598            channels: channel.derive_channel(&*value),
599            station: self.antenna.read().station,
600            value,
601        }
602    }
603
604    /// Get a mutable reference to the current state value, inside a callback.
605    ///
606    /// Example:
607    ///
608    /// ```rust, ignore
609    /// # use freya::radio::*;
610    /// radio.write_channel_with(Channel::Whatever, |value| {
611    ///     // Modify `value`
612    /// });
613    /// ```
614    #[track_caller]
615    pub fn write_channel_with(
616        &mut self,
617        channel: Channel,
618        cb: impl FnOnce(RadioGuard<Value, Channel>),
619    ) {
620        let guard = self.write_channel(channel);
621        cb(guard);
622    }
623
624    /// Get a mutable reference to the current state value, inside a callback that returns the channel to be used.
625    ///
626    /// Example:
627    ///
628    /// ```rust, ignore
629    /// # use freya::radio::*;
630    /// radio.write_with_channel_selection(|value| {
631    ///     // Modify `value`
632    ///     if value.cool {
633    ///         ChannelSelection::Select(Channel::Whatever)
634    ///     } else {
635    ///         ChannelSelection::Silence
636    ///     }
637    /// });
638    /// ```
639    #[track_caller]
640    pub fn write_with_channel_selection(
641        &mut self,
642        cb: impl FnOnce(&mut Value) -> ChannelSelection<Channel>,
643    ) -> ChannelSelection<Channel> {
644        let value = self.antenna.peek().station.value.write_unchecked();
645        let mut guard = RadioGuard {
646            channels: Vec::default(),
647            station: self.antenna.read().station,
648            value,
649        };
650        let channel_selection = cb(&mut guard.value);
651        let channel = match channel_selection.clone() {
652            ChannelSelection::Current => Some(self.antenna.peek().channel.clone()),
653            ChannelSelection::Silence => None,
654            ChannelSelection::Select(c) => Some(c),
655        };
656        if let Some(channel) = channel {
657            for channel in channel.derive_channel(&guard.value) {
658                self.antenna.peek().station.notify_listeners(&channel)
659            }
660            self.antenna.peek().station.cleanup();
661        }
662
663        channel_selection
664    }
665
666    /// Modify the state silently, no component will be notified.
667    ///
668    /// This is not recommended, the only intended usage for this is inside [RadioAsyncReducer].
669    #[track_caller]
670    pub fn write_silently(&mut self) -> RadioGuard<Value, Channel> {
671        let value = self.antenna.peek().station.value.write_unchecked();
672        RadioGuard {
673            channels: Vec::default(),
674            station: self.antenna.read().station,
675            value,
676        }
677    }
678}
679
680impl<Value, Channel> WritableUtils<Value> for Radio<Value, Channel>
681where
682    Channel: RadioChannel<Value>,
683    Value: 'static,
684{
685    fn write_state(&mut self) -> WriteRef<'static, Value> {
686        let antenna = self.antenna.peek();
687        let channel = antenna.channel.clone();
688        let value = antenna.station.value.write_unchecked();
689        for ch in channel.derive_channel(&*value) {
690            antenna.station.notify_listeners(&ch);
691        }
692        antenna.station.cleanup();
693        value
694    }
695
696    fn peek_state(&self) -> ReadRef<'static, Value> {
697        self.antenna.peek().station.peek_unchecked()
698    }
699}
700
701impl<Channel> Copy for ChannelSelection<Channel> where Channel: Copy {}
702
703#[derive(Clone)]
704pub enum ChannelSelection<Channel> {
705    /// Notify the channel associated with the used [Radio].
706    Current,
707    /// Notify a given `Channel`.
708    Select(Channel),
709    /// No subscriber will be notified.
710    Silence,
711}
712
713impl<Channel> ChannelSelection<Channel> {
714    /// Change to [ChannelSelection::Current]
715    pub fn current(&mut self) {
716        *self = Self::Current
717    }
718
719    /// Change to [ChannelSelection::Select]
720    pub fn select(&mut self, channel: Channel) {
721        *self = Self::Select(channel)
722    }
723
724    /// Change to [ChannelSelection::Silence]
725    pub fn silence(&mut self) {
726        *self = Self::Silence
727    }
728
729    /// Check if it is of type [ChannelSelection::Current]
730    pub fn is_current(&self) -> bool {
731        matches!(self, Self::Current)
732    }
733
734    /// Check if it is of type [ChannelSelection::Select] and return the channel.
735    pub fn is_select(&self) -> Option<&Channel> {
736        match self {
737            Self::Select(channel) => Some(channel),
738            _ => None,
739        }
740    }
741
742    /// Check if it is of type [ChannelSelection::Silence]
743    pub fn is_silence(&self) -> bool {
744        matches!(self, Self::Silence)
745    }
746}
747
748/// Provide an existing [`RadioStation`] to descendant components.
749/// This is useful for sharing the same global state across different parts of the component tree
750/// or across multiple windows.
751pub fn use_share_radio<Value, Channel>(radio: impl FnOnce() -> RadioStation<Value, Channel>)
752where
753    Channel: RadioChannel<Value>,
754    Value: 'static,
755{
756    use_provide_context(radio);
757}
758
759/// Subscribe to the global state for a specific channel.
760/// Returns a [`Radio`] handle that allows reading and writing the state.
761/// The current component will re-render whenever the specified channel is notified.
762///
763/// This hook must be called within a component that has access to a [`RadioStation`]
764/// (either through [`use_init_radio_station`] or [`use_share_radio`]).
765///
766/// # Example
767///
768/// ```rust, ignore
769/// # use freya::prelude::*;
770/// # use freya::radio::*;
771///
772/// fn app() -> impl IntoElement {
773///     use_init_radio_station::<AppState, AppChannel>(AppState::default);
774///
775///     rect().child(Counter {})
776/// }
777///
778/// #[derive(PartialEq)]
779/// struct Counter {}
780///
781/// impl Component for Counter {
782///     fn render(&self) -> impl IntoElement {
783///         let mut radio = use_radio(AppChannel::Count);
784///
785///         rect()
786///             .child(label().text(format!("Count: {}", radio.read().count)))
787///             .child(
788///                 Button::new()
789///                     .on_press(move |_| radio.write().count += 1)
790///                     .child("+"),
791///             )
792///     }
793/// }
794/// ```
795pub fn use_radio<Value, Channel>(channel: Channel) -> Radio<Value, Channel>
796where
797    Channel: RadioChannel<Value>,
798    Value: 'static,
799{
800    let station = use_consume::<RadioStation<Value, Channel>>();
801
802    let mut radio = use_hook(|| {
803        let antenna = RadioAntenna::new(channel.clone(), station);
804        Radio::new(State::create(antenna))
805    });
806
807    if radio.antenna.peek().channel != channel {
808        radio.antenna.write().channel = channel;
809    }
810
811    radio
812}
813
814/// Initialize a new radio station in the current component tree.
815/// This provides the global state to all descendant components.
816///
817/// Returns the [`RadioStation`] instance for direct access if needed.
818///
819/// # Example
820///
821/// ```rust, ignore
822/// # use freya::prelude::*;
823/// # use freya::radio::*;
824///
825/// fn app() -> impl IntoElement {
826///     use_init_radio_station::<AppState, AppChannel>(AppState::default);
827///
828///     rect().child(MyComponent {})
829/// }
830/// ```
831pub fn use_init_radio_station<Value, Channel>(
832    init_value: impl FnOnce() -> Value,
833) -> RadioStation<Value, Channel>
834where
835    Channel: RadioChannel<Value>,
836    Value: 'static,
837{
838    use_provide_context(|| RadioStation::create(init_value()))
839}
840
841pub fn use_radio_station<Value, Channel>() -> RadioStation<Value, Channel>
842where
843    Channel: RadioChannel<Value>,
844    Value: 'static,
845{
846    use_consume::<RadioStation<Value, Channel>>()
847}
848
849/// Trait for implementing a reducer pattern on your state.
850/// Reducers allow you to define actions that modify the state in a controlled way.
851///
852/// Implement this trait on your state type to enable the [`RadioReducer`] functionality.
853///
854/// # Example
855///
856/// ```rust, ignore
857/// # use freya::radio::*;
858///
859/// #[derive(Clone)]
860/// struct Counter {
861///     count: i32,
862/// }
863///
864/// #[derive(Clone)]
865/// enum CounterAction {
866///     Increment,
867///     Decrement,
868///     Set(i32),
869/// }
870///
871/// impl DataReducer for Counter {
872///     type Channel = CounterChannel;
873///     type Action = CounterAction;
874///
875///     fn reduce(&mut self, action: Self::Action) -> ChannelSelection<Self::Channel> {
876///         match action {
877///             CounterAction::Increment => self.count += 1,
878///             CounterAction::Decrement => self.count -= 1,
879///             CounterAction::Set(value) => self.count = value,
880///         }
881///         ChannelSelection::Current
882///     }
883/// }
884/// ```
885pub trait DataReducer {
886    type Channel;
887    type Action;
888
889    fn reduce(&mut self, action: Self::Action) -> ChannelSelection<Self::Channel>;
890}
891
892pub trait RadioReducer {
893    type Action;
894    type Channel;
895
896    fn apply(&mut self, action: Self::Action) -> ChannelSelection<Self::Channel>;
897}
898
899impl<Data: DataReducer<Channel = Channel, Action = Action>, Channel: RadioChannel<Data>, Action>
900    RadioReducer for Radio<Data, Channel>
901{
902    type Action = Action;
903    type Channel = Channel;
904
905    fn apply(&mut self, action: Action) -> ChannelSelection<Channel> {
906        self.write_with_channel_selection(|data| data.reduce(action))
907    }
908}
909
910pub trait DataAsyncReducer {
911    type Channel;
912    type Action;
913
914    #[allow(async_fn_in_trait)]
915    async fn async_reduce(
916        _radio: &mut Radio<Self, Self::Channel>,
917        _action: Self::Action,
918    ) -> ChannelSelection<Self::Channel>
919    where
920        Self::Channel: RadioChannel<Self>,
921        Self: Sized;
922}
923
924pub trait RadioAsyncReducer {
925    type Action;
926
927    fn async_apply(&mut self, _action: Self::Action)
928    where
929        Self::Action: 'static;
930}
931
932impl<
933    Data: DataAsyncReducer<Channel = Channel, Action = Action>,
934    Channel: RadioChannel<Data>,
935    Action,
936> RadioAsyncReducer for Radio<Data, Channel>
937{
938    type Action = Action;
939
940    fn async_apply(&mut self, action: Self::Action)
941    where
942        Self::Action: 'static,
943    {
944        let mut radio = *self;
945        spawn(async move {
946            let channel = Data::async_reduce(&mut radio, action).await;
947            radio.write_with_channel_selection(|_| channel);
948        });
949    }
950}