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