Merge branch 'master' into feat/multi-window-support

This commit is contained in:
Héctor Ramón Jiménez 2023-11-29 22:28:31 +01:00
commit e09b4e24dd
No known key found for this signature in database
GPG key ID: 7CC46565708259A7
331 changed files with 12085 additions and 3976 deletions

View file

@ -1,47 +1,40 @@
[package]
name = "iced_futures"
version = "0.6.0"
authors = ["Héctor Ramón Jiménez <hector0193@gmail.com>"]
edition = "2021"
description = "Commands, subscriptions, and runtimes for Iced"
license = "MIT"
repository = "https://github.com/iced-rs/iced"
documentation = "https://docs.rs/iced_futures"
keywords = ["gui", "ui", "graphics", "interface", "futures"]
categories = ["gui"]
description = "Commands, subscriptions, and future executors for iced"
version.workspace = true
edition.workspace = true
authors.workspace = true
license.workspace = true
repository.workspace = true
homepage.workspace = true
categories.workspace = true
keywords.workspace = true
[package.metadata.docs.rs]
rustdoc-args = ["--cfg", "docsrs"]
all-features = true
[features]
thread-pool = ["futures/thread-pool"]
[dependencies]
log = "0.4"
iced_core.workspace = true
[dependencies.iced_core]
version = "0.9"
path = "../core"
futures.workspace = true
log.workspace = true
[dependencies.futures]
version = "0.3"
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
async-std.workspace = true
async-std.optional = true
async-std.features = ["unstable"]
[target.'cfg(not(target_arch = "wasm32"))'.dependencies.tokio]
package = "tokio"
version = "1.0"
optional = true
features = ["rt", "rt-multi-thread", "time"]
smol.workspace = true
smol.optional = true
[target.'cfg(not(target_arch = "wasm32"))'.dependencies.async-std]
version = "1.0"
optional = true
features = ["unstable"]
[target.'cfg(not(target_arch = "wasm32"))'.dependencies.smol]
version = "1.2"
optional = true
tokio.workspace = true
tokio.optional = true
tokio.features = ["rt", "rt-multi-thread", "time"]
[target.'cfg(target_arch = "wasm32")'.dependencies]
wasm-bindgen-futures = "0.4"
wasm-timer = "0.2"
[package.metadata.docs.rs]
rustdoc-args = ["--cfg", "docsrs"]
all-features = true
wasm-bindgen-futures.workspace = true
wasm-timer.workspace = true

59
futures/src/event.rs Normal file
View file

@ -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)
}

61
futures/src/keyboard.rs Normal file
View file

@ -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,
}
})
}

View file

@ -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;

View file

@ -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<

View file

@ -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,

View file

@ -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:?}"
);
}
});