diff options
| author | 2019-12-16 21:38:56 +0100 | |
|---|---|---|
| committer | 2019-12-16 21:38:56 +0100 | |
| commit | 0f2e20f5e5b1f0658ab4e6cbe6fdda9ca97f2b36 (patch) | |
| tree | 6b4c601bfa0ced1e003f597d7f485be7c108e12c /core | |
| parent | 3702b109977a249247a0f1be40e57bec2cbaa4e3 (diff) | |
| parent | 430ab6e44432d044f8444575053d97651f0f7d20 (diff) | |
| download | iced-0f2e20f5e5b1f0658ab4e6cbe6fdda9ca97f2b36.tar.gz iced-0f2e20f5e5b1f0658ab4e6cbe6fdda9ca97f2b36.tar.bz2 iced-0f2e20f5e5b1f0658ab4e6cbe6fdda9ca97f2b36.zip | |
Merge pull request #122 from hecrj/feature/event-subscriptions
Event subscriptions
Diffstat (limited to '')
| -rw-r--r-- | core/Cargo.toml | 2 | ||||
| -rw-r--r-- | core/src/command.rs | 4 | ||||
| -rw-r--r-- | core/src/lib.rs | 6 | ||||
| -rw-r--r-- | core/src/subscription.rs | 182 | 
4 files changed, 192 insertions, 2 deletions
| diff --git a/core/Cargo.toml b/core/Cargo.toml index c623ba78..0a8fd8ef 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -10,6 +10,8 @@ repository = "https://github.com/hecrj/iced"  [features]  # Exposes a future-based `Command` type  command = ["futures"] +# Exposes a future-based `Subscription` type +subscription = ["futures"]  [dependencies]  futures = { version = "0.3", optional = true } diff --git a/core/src/command.rs b/core/src/command.rs index 14b48b5b..e0e5ab5c 100644 --- a/core/src/command.rs +++ b/core/src/command.rs @@ -34,8 +34,8 @@ impl<T> Command<T> {          }      } -    /// Creates a [`Command`] that performs the actions of all the givens -    /// futures. +    /// Creates a [`Command`] that performs the actions of all the given +    /// commands.      ///      /// Once this command is run, all the futures will be exectued at once.      /// diff --git a/core/src/lib.rs b/core/src/lib.rs index 65304e8b..821b09c1 100644 --- a/core/src/lib.rs +++ b/core/src/lib.rs @@ -38,3 +38,9 @@ mod command;  #[cfg(feature = "command")]  pub use command::Command; + +#[cfg(feature = "subscription")] +pub mod subscription; + +#[cfg(feature = "subscription")] +pub use subscription::Subscription; diff --git a/core/src/subscription.rs b/core/src/subscription.rs new file mode 100644 index 00000000..8de6cae8 --- /dev/null +++ b/core/src/subscription.rs @@ -0,0 +1,182 @@ +//! Listen to external events in your application. + +/// A request to listen to external events. +/// +/// 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`], +/// 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 +/// connection, keyboard presses, mouse events, time ticks, etc. +/// +/// This type is normally aliased by runtimes with a specific `Input` and/or +/// `Hasher`. +/// +/// [`Command`]: ../struct.Command.html +/// [`Subscription`]: struct.Subscription.html +pub struct Subscription<Hasher, Input, Output> { +    recipes: Vec<Box<dyn Recipe<Hasher, Input, Output = Output>>>, +} + +impl<H, I, O> Subscription<H, I, O> +where +    H: std::hash::Hasher, +{ +    /// Returns an empty [`Subscription`] that will not produce any output. +    /// +    /// [`Subscription`]: struct.Subscription.html +    pub fn none() -> Self { +        Self { +            recipes: Vec::new(), +        } +    } + +    /// Creates a [`Subscription`] from a [`Recipe`] describing it. +    /// +    /// [`Subscription`]: struct.Subscription.html +    /// [`Recipe`]: trait.Recipe.html +    pub fn from_recipe( +        recipe: impl Recipe<H, I, Output = O> + 'static, +    ) -> Self { +        Self { +            recipes: vec![Box::new(recipe)], +        } +    } + +    /// Batches all the provided subscriptions and returns the resulting +    /// [`Subscription`]. +    /// +    /// [`Subscription`]: struct.Subscription.html +    pub fn batch( +        subscriptions: impl Iterator<Item = Subscription<H, I, O>>, +    ) -> Self { +        Self { +            recipes: subscriptions +                .flat_map(|subscription| subscription.recipes) +                .collect(), +        } +    } + +    /// Returns the different recipes of the [`Subscription`]. +    /// +    /// [`Subscription`]: struct.Subscription.html +    pub fn recipes(self) -> Vec<Box<dyn Recipe<H, I, Output = O>>> { +        self.recipes +    } + +    /// Transforms the [`Subscription`] output with the given function. +    /// +    /// [`Subscription`]: struct.Subscription.html +    pub fn map<A>( +        mut self, +        f: impl Fn(O) -> A + Send + Sync + 'static, +    ) -> Subscription<H, I, A> +    where +        H: 'static, +        I: 'static, +        O: 'static, +        A: 'static, +    { +        let function = std::sync::Arc::new(f); + +        Subscription { +            recipes: self +                .recipes +                .drain(..) +                .map(|recipe| { +                    Box::new(Map::new(recipe, function.clone())) +                        as Box<dyn Recipe<H, I, Output = A>> +                }) +                .collect(), +        } +    } +} + +impl<I, O, H> std::fmt::Debug for Subscription<I, O, H> { +    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { +        f.debug_struct("Subscription").finish() +    } +} + +/// The description of a [`Subscription`]. +/// +/// A [`Recipe`] is the internal definition of a [`Subscription`]. It is used +/// by runtimes to run and identify subscriptions. You can use it to create your +/// own! +/// +/// [`Subscription`]: struct.Subscription.html +/// [`Recipe`]: trait.Recipe.html +pub trait Recipe<Hasher: std::hash::Hasher, Input> { +    /// The events that will be produced by a [`Subscription`] with this +    /// [`Recipe`]. +    /// +    /// [`Subscription`]: struct.Subscription.html +    /// [`Recipe`]: trait.Recipe.html +    type Output; + +    /// Hashes the [`Recipe`]. +    /// +    /// This is used by runtimes to uniquely identify a [`Subscription`]. +    /// +    /// [`Subscription`]: struct.Subscription.html +    /// [`Recipe`]: trait.Recipe.html +    fn hash(&self, state: &mut Hasher); + +    /// Executes the [`Recipe`] and produces the stream of events of its +    /// [`Subscription`]. +    /// +    /// It receives some generic `Input`, which is normally defined by runtimes. +    /// +    /// [`Subscription`]: struct.Subscription.html +    /// [`Recipe`]: trait.Recipe.html +    fn stream( +        self: Box<Self>, +        input: Input, +    ) -> futures::stream::BoxStream<'static, Self::Output>; +} + +struct Map<Hasher, Input, A, B> { +    recipe: Box<dyn Recipe<Hasher, Input, Output = A>>, +    mapper: std::sync::Arc<dyn Fn(A) -> B + Send + Sync>, +} + +impl<H, I, A, B> Map<H, I, A, B> { +    fn new( +        recipe: Box<dyn Recipe<H, I, Output = A>>, +        mapper: std::sync::Arc<dyn Fn(A) -> B + Send + Sync + 'static>, +    ) -> Self { +        Map { recipe, mapper } +    } +} + +impl<H, I, A, B> Recipe<H, I> for Map<H, I, A, B> +where +    A: 'static, +    B: 'static, +    H: std::hash::Hasher, +{ +    type Output = B; + +    fn hash(&self, state: &mut H) { +        use std::hash::Hash; + +        std::any::TypeId::of::<B>().hash(state); +        self.recipe.hash(state); +    } + +    fn stream( +        self: Box<Self>, +        input: I, +    ) -> futures::stream::BoxStream<'static, Self::Output> { +        use futures::StreamExt; + +        let mapper = self.mapper; + +        self.recipe +            .stream(input) +            .map(move |element| mapper(element)) +            .boxed() +    } +} | 
