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}