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}