Merge branch 'master' into feat/multi-window-support
This commit is contained in:
commit
e09b4e24dd
331 changed files with 12085 additions and 3976 deletions
|
|
@ -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
59
futures/src/event.rs
Normal 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
61
futures/src/keyboard.rs
Normal 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,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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<
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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:?}"
|
||||
);
|
||||
}
|
||||
});
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue