summaryrefslogtreecommitdiffstats
path: root/core/src
diff options
context:
space:
mode:
authorLibravatar Héctor Ramón <hector0193@gmail.com>2019-12-16 21:38:56 +0100
committerLibravatar GitHub <noreply@github.com>2019-12-16 21:38:56 +0100
commit0f2e20f5e5b1f0658ab4e6cbe6fdda9ca97f2b36 (patch)
tree6b4c601bfa0ced1e003f597d7f485be7c108e12c /core/src
parent3702b109977a249247a0f1be40e57bec2cbaa4e3 (diff)
parent430ab6e44432d044f8444575053d97651f0f7d20 (diff)
downloadiced-0f2e20f5e5b1f0658ab4e6cbe6fdda9ca97f2b36.tar.gz
iced-0f2e20f5e5b1f0658ab4e6cbe6fdda9ca97f2b36.tar.bz2
iced-0f2e20f5e5b1f0658ab4e6cbe6fdda9ca97f2b36.zip
Merge pull request #122 from hecrj/feature/event-subscriptions
Event subscriptions
Diffstat (limited to 'core/src')
-rw-r--r--core/src/command.rs4
-rw-r--r--core/src/lib.rs6
-rw-r--r--core/src/subscription.rs182
3 files changed, 190 insertions, 2 deletions
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()
+ }
+}