iced_native/
subscription.rs

1//! Listen to external events in your application.
2use crate::event::{self, Event};
3use crate::window;
4use crate::Hasher;
5
6use iced_futures::futures::channel::mpsc;
7use iced_futures::futures::never::Never;
8use iced_futures::futures::{self, Future, Stream};
9use iced_futures::{BoxStream, MaybeSend};
10
11use std::hash::Hash;
12
13/// A request to listen to external events.
14///
15/// Besides performing async actions on demand with [`Command`], most
16/// applications also need to listen to external events passively.
17///
18/// A [`Subscription`] is normally provided to some runtime, like a [`Command`],
19/// and it will generate events as long as the user keeps requesting it.
20///
21/// For instance, you can use a [`Subscription`] to listen to a WebSocket
22/// connection, keyboard presses, mouse events, time ticks, etc.
23///
24/// [`Command`]: crate::Command
25pub type Subscription<T> =
26    iced_futures::Subscription<Hasher, (Event, event::Status), T>;
27
28/// A stream of runtime events.
29///
30/// It is the input of a [`Subscription`] in the native runtime.
31pub type EventStream = BoxStream<(Event, event::Status)>;
32
33/// A native [`Subscription`] tracker.
34pub type Tracker =
35    iced_futures::subscription::Tracker<Hasher, (Event, event::Status)>;
36
37pub use iced_futures::subscription::Recipe;
38
39/// Returns a [`Subscription`] to all the ignored runtime events.
40///
41/// This subscription will notify your application of any [`Event`] that was
42/// not captured by any widget.
43pub fn events() -> Subscription<Event> {
44    events_with(|event, status| match status {
45        event::Status::Ignored => Some(event),
46        event::Status::Captured => None,
47    })
48}
49
50/// Returns a [`Subscription`] that filters all the runtime events with the
51/// provided function, producing messages accordingly.
52///
53/// This subscription will call the provided function for every [`Event`]
54/// handled by the runtime. If the function:
55///
56/// - Returns `None`, the [`Event`] will be discarded.
57/// - Returns `Some` message, the `Message` will be produced.
58pub fn events_with<Message>(
59    f: fn(Event, event::Status) -> Option<Message>,
60) -> Subscription<Message>
61where
62    Message: 'static + MaybeSend,
63{
64    #[derive(Hash)]
65    struct EventsWith;
66
67    Subscription::from_recipe(Runner {
68        id: (EventsWith, f),
69        spawn: move |events| {
70            use futures::future;
71            use futures::stream::StreamExt;
72
73            events.filter_map(move |(event, status)| {
74                future::ready(match event {
75                    Event::Window(window::Event::RedrawRequested(_)) => None,
76                    _ => f(event, status),
77                })
78            })
79        },
80    })
81}
82
83pub(crate) fn raw_events<Message>(
84    f: fn(Event, event::Status) -> Option<Message>,
85) -> Subscription<Message>
86where
87    Message: 'static + MaybeSend,
88{
89    #[derive(Hash)]
90    struct RawEvents;
91
92    Subscription::from_recipe(Runner {
93        id: (RawEvents, f),
94        spawn: move |events| {
95            use futures::future;
96            use futures::stream::StreamExt;
97
98            events.filter_map(move |(event, status)| {
99                future::ready(f(event, status))
100            })
101        },
102    })
103}
104
105/// Returns a [`Subscription`] that will call the given function to create and
106/// asynchronously run the given [`Stream`].
107pub fn run<S, Message>(builder: fn() -> S) -> Subscription<Message>
108where
109    S: Stream<Item = Message> + MaybeSend + 'static,
110    Message: 'static,
111{
112    Subscription::from_recipe(Runner {
113        id: builder,
114        spawn: move |_| builder(),
115    })
116}
117
118/// Returns a [`Subscription`] that will create and asynchronously run the
119/// given [`Stream`].
120///
121/// The `id` will be used to uniquely identify the [`Subscription`].
122pub fn run_with_id<I, S, Message>(id: I, stream: S) -> Subscription<Message>
123where
124    I: Hash + 'static,
125    S: Stream<Item = Message> + MaybeSend + 'static,
126    Message: 'static,
127{
128    Subscription::from_recipe(Runner {
129        id,
130        spawn: move |_| stream,
131    })
132}
133
134/// Returns a [`Subscription`] that will create and asynchronously run a
135/// [`Stream`] that will call the provided closure to produce every `Message`.
136///
137/// The `id` will be used to uniquely identify the [`Subscription`].
138pub fn unfold<I, T, Fut, Message>(
139    id: I,
140    initial: T,
141    mut f: impl FnMut(T) -> Fut + MaybeSend + Sync + 'static,
142) -> Subscription<Message>
143where
144    I: Hash + 'static,
145    T: MaybeSend + 'static,
146    Fut: Future<Output = (Message, T)> + MaybeSend + 'static,
147    Message: 'static + MaybeSend,
148{
149    use futures::future::FutureExt;
150
151    run_with_id(
152        id,
153        futures::stream::unfold(initial, move |state| f(state).map(Some)),
154    )
155}
156
157/// Creates a [`Subscription`] that publishes the events sent from a [`Future`]
158/// to an [`mpsc::Sender`] with the given bounds.
159///
160/// # Creating an asynchronous worker with bidirectional communication
161/// You can leverage this helper to create a [`Subscription`] that spawns
162/// an asynchronous worker in the background and establish a channel of
163/// communication with an `iced` application.
164///
165/// You can achieve this by creating an `mpsc` channel inside the closure
166/// and returning the `Sender` as a `Message` for the `Application`:
167///
168/// ```
169/// use iced_native::subscription::{self, Subscription};
170/// use iced_native::futures::channel::mpsc;
171/// use iced_native::futures::sink::SinkExt;
172///
173/// pub enum Event {
174///     Ready(mpsc::Sender<Input>),
175///     WorkFinished,
176///     // ...
177/// }
178///
179/// enum Input {
180///     DoSomeWork,
181///     // ...
182/// }
183///
184/// enum State {
185///     Starting,
186///     Ready(mpsc::Receiver<Input>),
187/// }
188///
189/// fn some_worker() -> Subscription<Event> {
190///     struct SomeWorker;
191///
192///     subscription::channel(std::any::TypeId::of::<SomeWorker>(), 100, |mut output| async move {
193///         let mut state = State::Starting;
194///
195///         loop {
196///             match &mut state {
197///                 State::Starting => {
198///                     // Create channel
199///                     let (sender, receiver) = mpsc::channel(100);
200///
201///                     // Send the sender back to the application
202///                     output.send(Event::Ready(sender)).await;
203///
204///                     // We are ready to receive messages
205///                     state = State::Ready(receiver);
206///                 }
207///                 State::Ready(receiver) => {
208///                     use iced_native::futures::StreamExt;
209///
210///                     // Read next input sent from `Application`
211///                     let input = receiver.select_next_some().await;
212///
213///                     match input {
214///                         Input::DoSomeWork => {
215///                             // Do some async work...
216///
217///                             // Finally, we can optionally produce a message to tell the
218///                             // `Application` the work is done
219///                             output.send(Event::WorkFinished).await;
220///                         }
221///                     }
222///                 }
223///             }
224///         }
225///     })
226/// }
227/// ```
228///
229/// Check out the [`websocket`] example, which showcases this pattern to maintain a WebSocket
230/// connection open.
231///
232/// [`websocket`]: https://github.com/iced-rs/iced/tree/0.9/examples/websocket
233pub fn channel<I, Fut, Message>(
234    id: I,
235    size: usize,
236    f: impl Fn(mpsc::Sender<Message>) -> Fut + MaybeSend + Sync + 'static,
237) -> Subscription<Message>
238where
239    I: Hash + 'static,
240    Fut: Future<Output = Never> + MaybeSend + 'static,
241    Message: 'static + MaybeSend,
242{
243    use futures::stream::{self, StreamExt};
244
245    Subscription::from_recipe(Runner {
246        id,
247        spawn: move |_| {
248            let (sender, receiver) = mpsc::channel(size);
249
250            let runner = stream::once(f(sender)).map(|_| unreachable!());
251
252            stream::select(receiver, runner)
253        },
254    })
255}
256
257struct Runner<I, F, S, Message>
258where
259    F: FnOnce(EventStream) -> S,
260    S: Stream<Item = Message>,
261{
262    id: I,
263    spawn: F,
264}
265
266impl<I, S, F, Message> Recipe<Hasher, (Event, event::Status)>
267    for Runner<I, F, S, Message>
268where
269    I: Hash + 'static,
270    F: FnOnce(EventStream) -> S,
271    S: Stream<Item = Message> + MaybeSend + 'static,
272{
273    type Output = Message;
274
275    fn hash(&self, state: &mut Hasher) {
276        std::any::TypeId::of::<I>().hash(state);
277        self.id.hash(state);
278    }
279
280    fn stream(self: Box<Self>, input: EventStream) -> BoxStream<Self::Output> {
281        iced_futures::boxed_stream((self.spawn)(input))
282    }
283}