diff options
| author | 2023-11-29 22:28:31 +0100 | |
|---|---|---|
| committer | 2023-11-29 22:28:31 +0100 | |
| commit | e09b4e24dda51b8212d8ece52431dacaa3922a7b (patch) | |
| tree | 7005e181528134ebdde5bbbe5909273db9f30174 /futures/src | |
| parent | 83c7870c569a2976923ee6243a19813094d44673 (diff) | |
| parent | 7f8b17604a31e00becc43130ec516c1a53552c88 (diff) | |
| download | iced-e09b4e24dda51b8212d8ece52431dacaa3922a7b.tar.gz iced-e09b4e24dda51b8212d8ece52431dacaa3922a7b.tar.bz2 iced-e09b4e24dda51b8212d8ece52431dacaa3922a7b.zip | |
Merge branch 'master' into feat/multi-window-support
Diffstat (limited to '')
| -rw-r--r-- | futures/src/event.rs | 59 | ||||
| -rw-r--r-- | futures/src/keyboard.rs | 61 | ||||
| -rw-r--r-- | futures/src/lib.rs | 11 | ||||
| -rw-r--r-- | futures/src/runtime.rs | 30 | ||||
| -rw-r--r-- | futures/src/subscription.rs | 109 | ||||
| -rw-r--r-- | futures/src/subscription/tracker.rs | 6 | 
6 files changed, 182 insertions, 94 deletions
| diff --git a/futures/src/event.rs b/futures/src/event.rs new file mode 100644 index 00000000..97224506 --- /dev/null +++ b/futures/src/event.rs @@ -0,0 +1,59 @@ +//! Listen to runtime events. +use crate::core::event::{self, Event}; +use crate::core::window; +use crate::subscription::{self, Subscription}; +use crate::MaybeSend; + +/// Returns a [`Subscription`] to all the ignored runtime events. +/// +/// This subscription will notify your application of any [`Event`] that was +/// not captured by any widget. +pub fn listen() -> Subscription<Event> { +    listen_with(|event, status| match status { +        event::Status::Ignored => Some(event), +        event::Status::Captured => None, +    }) +} + +/// Creates a [`Subscription`] that listens and filters all the runtime events +/// with the provided function, producing messages accordingly. +/// +/// This subscription will call the provided function for every [`Event`] +/// handled by the runtime. If the function: +/// +/// - Returns `None`, the [`Event`] will be discarded. +/// - Returns `Some` message, the `Message` will be produced. +pub fn listen_with<Message>( +    f: fn(Event, event::Status) -> Option<Message>, +) -> Subscription<Message> +where +    Message: 'static + MaybeSend, +{ +    #[derive(Hash)] +    struct EventsWith; + +    subscription::filter_map( +        (EventsWith, f), +        move |event, status| match event { +            Event::Window(_, window::Event::RedrawRequested(_)) => None, +            _ => f(event, status), +        }, +    ) +} + +/// Creates a [`Subscription`] that produces a message for every runtime event, +/// including the redraw request events. +/// +/// **Warning:** This [`Subscription`], if unfiltered, may produce messages in +/// an infinite loop. +pub fn listen_raw<Message>( +    f: fn(Event, event::Status) -> Option<Message>, +) -> Subscription<Message> +where +    Message: 'static + MaybeSend, +{ +    #[derive(Hash)] +    struct RawEvents; + +    subscription::filter_map((RawEvents, f), f) +} diff --git a/futures/src/keyboard.rs b/futures/src/keyboard.rs new file mode 100644 index 00000000..af68e1f2 --- /dev/null +++ b/futures/src/keyboard.rs @@ -0,0 +1,61 @@ +//! Listen to keyboard events. +use crate::core; +use crate::core::keyboard::{Event, KeyCode, Modifiers}; +use crate::subscription::{self, Subscription}; +use crate::MaybeSend; + +/// Listens to keyboard key presses and calls the given function +/// map them into actual messages. +/// +/// If the function returns `None`, the key press will be simply +/// ignored. +pub fn on_key_press<Message>( +    f: fn(KeyCode, Modifiers) -> Option<Message>, +) -> Subscription<Message> +where +    Message: MaybeSend + 'static, +{ +    #[derive(Hash)] +    struct OnKeyPress; + +    subscription::filter_map((OnKeyPress, f), move |event, status| { +        match (event, status) { +            ( +                core::Event::Keyboard(Event::KeyPressed { +                    key_code, +                    modifiers, +                }), +                core::event::Status::Ignored, +            ) => f(key_code, modifiers), +            _ => None, +        } +    }) +} + +/// Listens to keyboard key releases and calls the given function +/// map them into actual messages. +/// +/// If the function returns `None`, the key release will be simply +/// ignored. +pub fn on_key_release<Message>( +    f: fn(KeyCode, Modifiers) -> Option<Message>, +) -> Subscription<Message> +where +    Message: MaybeSend + 'static, +{ +    #[derive(Hash)] +    struct OnKeyRelease; + +    subscription::filter_map((OnKeyRelease, f), move |event, status| { +        match (event, status) { +            ( +                core::Event::Keyboard(Event::KeyReleased { +                    key_code, +                    modifiers, +                }), +                core::event::Status::Ignored, +            ) => f(key_code, modifiers), +            _ => None, +        } +    }) +} diff --git a/futures/src/lib.rs b/futures/src/lib.rs index 34d81e1e..d54ba18a 100644 --- a/futures/src/lib.rs +++ b/futures/src/lib.rs @@ -4,18 +4,13 @@  #![doc(      html_logo_url = "https://raw.githubusercontent.com/iced-rs/iced/9ab6923e943f784985e9ef9ca28b10278297225d/docs/logo.svg"  )] +#![forbid(unsafe_code, rust_2018_idioms)]  #![deny(      missing_debug_implementations,      missing_docs,      unused_results, -    clippy::extra_unused_lifetimes, -    clippy::from_over_into, -    clippy::needless_borrow, -    clippy::new_without_default, -    clippy::useless_conversion +    rustdoc::broken_intra_doc_links  )] -#![forbid(unsafe_code, rust_2018_idioms)] -#![allow(clippy::inherent_to_string, clippy::type_complexity)]  #![cfg_attr(docsrs, feature(doc_auto_cfg))]  pub use futures;  pub use iced_core as core; @@ -24,7 +19,9 @@ mod maybe_send;  mod runtime;  pub mod backend; +pub mod event;  pub mod executor; +pub mod keyboard;  pub mod subscription;  pub use executor::Executor; diff --git a/futures/src/runtime.rs b/futures/src/runtime.rs index 2241a494..cac7b7e1 100644 --- a/futures/src/runtime.rs +++ b/futures/src/runtime.rs @@ -1,7 +1,7 @@  //! Run commands and keep track of subscriptions.  use crate::core::event::{self, Event};  use crate::subscription; -use crate::{BoxFuture, Executor, MaybeSend}; +use crate::{BoxFuture, BoxStream, Executor, MaybeSend};  use futures::{channel::mpsc, Sink};  use std::marker::PhantomData; @@ -9,9 +9,9 @@ use std::marker::PhantomData;  /// A batteries-included runtime of commands and subscriptions.  ///  /// If you have an [`Executor`], a [`Runtime`] can be leveraged to run any -/// [`Command`] or [`Subscription`] and get notified of the results! +/// `Command` or [`Subscription`] and get notified of the results!  /// -/// [`Command`]: crate::Command +/// [`Subscription`]: crate::Subscription  #[derive(Debug)]  pub struct Runtime<Executor, Sender, Message> {      executor: Executor, @@ -69,12 +69,36 @@ where          self.executor.spawn(future);      } +    /// Runs a [`Stream`] in the [`Runtime`] until completion. +    /// +    /// The resulting `Message`s will be forwarded to the `Sender` of the +    /// [`Runtime`]. +    /// +    /// [`Stream`]: BoxStream +    pub fn run(&mut self, stream: BoxStream<Message>) { +        use futures::{FutureExt, StreamExt}; + +        let sender = self.sender.clone(); +        let future = +            stream.map(Ok).forward(sender).map(|result| match result { +                Ok(()) => (), +                Err(error) => { +                    log::warn!( +                        "Stream could not run until completion: {error}" +                    ); +                } +            }); + +        self.executor.spawn(future); +    } +      /// Tracks a [`Subscription`] in the [`Runtime`].      ///      /// It will spawn new streams or close old ones as necessary! See      /// [`Tracker::update`] to learn more about this!      ///      /// [`Tracker::update`]: subscription::Tracker::update +    /// [`Subscription`]: crate::Subscription      pub fn track(          &mut self,          recipes: impl IntoIterator< diff --git a/futures/src/subscription.rs b/futures/src/subscription.rs index c087fdab..7163248d 100644 --- a/futures/src/subscription.rs +++ b/futures/src/subscription.rs @@ -4,7 +4,6 @@ mod tracker;  pub use tracker::Tracker;  use crate::core::event::{self, Event}; -use crate::core::window;  use crate::core::Hasher;  use crate::futures::{Future, Stream};  use crate::{BoxStream, MaybeSend}; @@ -20,16 +19,14 @@ pub type EventStream = BoxStream<(Event, event::Status)>;  /// A request to listen to external events.  /// -/// Besides performing async actions on demand with [`Command`], most +/// Besides performing async actions on demand with `Command`, most  /// applications also need to listen to external events passively.  /// -/// A [`Subscription`] is normally provided to some runtime, like a [`Command`], +/// A [`Subscription`] is normally provided to some runtime, like a `Command`,  /// and it will generate events as long as the user keeps requesting it.  /// -/// For instance, you can use a [`Subscription`] to listen to a WebSocket +/// For instance, you can use a [`Subscription`] to listen to a `WebSocket`  /// connection, keyboard presses, mouse events, time ticks, etc. -/// -/// [`Command`]: crate::Command  #[must_use = "`Subscription` must be returned to runtime to take effect"]  pub struct Subscription<Message> {      recipes: Vec<Box<dyn Recipe<Output = Message>>>, @@ -128,9 +125,9 @@ impl<Message> std::fmt::Debug for Subscription<Message> {  /// - [`stopwatch`], a watch with start/stop and reset buttons showcasing how  /// to listen to time.  /// -/// [examples]: https://github.com/iced-rs/iced/tree/0.9/examples -/// [`download_progress`]: https://github.com/iced-rs/iced/tree/0.9/examples/download_progress -/// [`stopwatch`]: https://github.com/iced-rs/iced/tree/0.9/examples/stopwatch +/// [examples]: https://github.com/iced-rs/iced/tree/0.10/examples +/// [`download_progress`]: https://github.com/iced-rs/iced/tree/0.10/examples/download_progress +/// [`stopwatch`]: https://github.com/iced-rs/iced/tree/0.10/examples/stopwatch  pub trait Recipe {      /// The events that will be produced by a [`Subscription`] with this      /// [`Recipe`]. @@ -215,77 +212,6 @@ where      }  } -/// Returns a [`Subscription`] to all the ignored runtime events. -/// -/// This subscription will notify your application of any [`Event`] that was -/// not captured by any widget. -pub fn events() -> Subscription<Event> { -    events_with(|event, status| match status { -        event::Status::Ignored => Some(event), -        event::Status::Captured => None, -    }) -} - -/// Returns a [`Subscription`] that filters all the runtime events with the -/// provided function, producing messages accordingly. -/// -/// This subscription will call the provided function for every [`Event`] -/// handled by the runtime. If the function: -/// -/// - Returns `None`, the [`Event`] will be discarded. -/// - Returns `Some` message, the `Message` will be produced. -pub fn events_with<Message>( -    f: fn(Event, event::Status) -> Option<Message>, -) -> Subscription<Message> -where -    Message: 'static + MaybeSend, -{ -    #[derive(Hash)] -    struct EventsWith; - -    Subscription::from_recipe(Runner { -        id: (EventsWith, f), -        spawn: move |events| { -            use futures::future; -            use futures::stream::StreamExt; - -            events.filter_map(move |(event, status)| { -                future::ready(match event { -                    Event::Window(_, window::Event::RedrawRequested(_)) => None, -                    _ => f(event, status), -                }) -            }) -        }, -    }) -} - -/// Returns a [`Subscription`] that produces a message for every runtime event, -/// including the redraw request events. -/// -/// **Warning:** This [`Subscription`], if unfiltered, may produce messages in -/// an infinite loop. -pub fn raw_events<Message>( -    f: fn(Event, event::Status) -> Option<Message>, -) -> Subscription<Message> -where -    Message: 'static + MaybeSend, -{ -    #[derive(Hash)] -    struct RawEvents; - -    Subscription::from_recipe(Runner { -        id: (RawEvents, f), -        spawn: move |events| { -            use futures::future; -            use futures::stream::StreamExt; - -            events.filter_map(move |(event, status)| { -                future::ready(f(event, status)) -            }) -        }, -    }) -} -  /// Returns a [`Subscription`] that will call the given function to create and  /// asynchronously run the given [`Stream`].  pub fn run<S, Message>(builder: fn() -> S) -> Subscription<Message> @@ -338,6 +264,25 @@ where      )  } +pub(crate) fn filter_map<I, F, Message>(id: I, f: F) -> Subscription<Message> +where +    I: Hash + 'static, +    F: Fn(Event, event::Status) -> Option<Message> + MaybeSend + 'static, +    Message: 'static + MaybeSend, +{ +    Subscription::from_recipe(Runner { +        id, +        spawn: |events| { +            use futures::future; +            use futures::stream::StreamExt; + +            events.filter_map(move |(event, status)| { +                future::ready(f(event, status)) +            }) +        }, +    }) +} +  /// Creates a [`Subscription`] that publishes the events sent from a [`Future`]  /// to an [`mpsc::Sender`] with the given bounds.  /// @@ -410,10 +355,10 @@ where  /// }  /// ```  /// -/// Check out the [`websocket`] example, which showcases this pattern to maintain a WebSocket +/// Check out the [`websocket`] example, which showcases this pattern to maintain a `WebSocket`  /// connection open.  /// -/// [`websocket`]: https://github.com/iced-rs/iced/tree/0.9/examples/websocket +/// [`websocket`]: https://github.com/iced-rs/iced/tree/0.10/examples/websocket  pub fn channel<I, Fut, Message>(      id: I,      size: usize, diff --git a/futures/src/subscription/tracker.rs b/futures/src/subscription/tracker.rs index ae71cd25..15ed5b87 100644 --- a/futures/src/subscription/tracker.rs +++ b/futures/src/subscription/tracker.rs @@ -14,6 +14,8 @@ use std::hash::Hasher as _;  /// If you have an application that continuously returns a [`Subscription`],  /// you can use a [`Tracker`] to keep track of the different recipes and keep  /// its executions alive. +/// +/// [`Subscription`]: crate::Subscription  #[derive(Debug, Default)]  pub struct Tracker {      subscriptions: HashMap<u64, Execution>, @@ -51,6 +53,7 @@ impl Tracker {      /// the [`Tracker`] changes.      ///      /// [`Recipe`]: crate::subscription::Recipe +    /// [`Subscription`]: crate::Subscription      pub fn update<Message, Receiver>(          &mut self,          recipes: impl Iterator<Item = Box<dyn Recipe<Output = Message>>>, @@ -144,8 +147,7 @@ impl Tracker {              .for_each(|listener| {                  if let Err(error) = listener.try_send((event.clone(), status)) {                      log::warn!( -                        "Error sending event to subscription: {:?}", -                        error +                        "Error sending event to subscription: {error:?}"                      );                  }              }); | 
