From 32f7ca261f0655938ae7c8919599b020ddea8ff8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Sun, 19 Jan 2020 08:36:44 +0100 Subject: [PATCH 01/16] Implement `subscription::Tracker` in `iced_core` --- core/Cargo.toml | 3 +- core/src/lib.rs | 2 +- core/src/subscription.rs | 11 ++- core/src/subscription/tracker.rs | 112 ++++++++++++++++++++++++++++++ examples/stopwatch.rs | 2 +- native/src/subscription.rs | 7 +- native/src/subscription/events.rs | 2 +- 7 files changed, 131 insertions(+), 8 deletions(-) create mode 100644 core/src/subscription/tracker.rs diff --git a/core/Cargo.toml b/core/Cargo.toml index 0a8fd8efd2..4e019ba9bc 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -11,7 +11,8 @@ repository = "https://github.com/hecrj/iced" # Exposes a future-based `Command` type command = ["futures"] # Exposes a future-based `Subscription` type -subscription = ["futures"] +subscription = ["futures", "log"] [dependencies] futures = { version = "0.3", optional = true } +log = { version = "0.4", optional = true } diff --git a/core/src/lib.rs b/core/src/lib.rs index 821b09c13d..6f13c31092 100644 --- a/core/src/lib.rs +++ b/core/src/lib.rs @@ -9,7 +9,7 @@ //! [Iced]: https://github.com/hecrj/iced //! [`iced_native`]: https://github.com/hecrj/iced/tree/master/native //! [`iced_web`]: https://github.com/hecrj/iced/tree/master/web -#![deny(missing_docs)] +//#![deny(missing_docs)] #![deny(missing_debug_implementations)] #![deny(unused_results)] #![deny(unsafe_code)] diff --git a/core/src/subscription.rs b/core/src/subscription.rs index d9e7e3886f..87e51e4851 100644 --- a/core/src/subscription.rs +++ b/core/src/subscription.rs @@ -1,4 +1,9 @@ //! Listen to external events in your application. +mod tracker; + +pub use tracker::Tracker; + +use futures::stream::BoxStream; /// A request to listen to external events. /// @@ -134,8 +139,8 @@ pub trait Recipe { /// [`Recipe`]: trait.Recipe.html fn stream( self: Box, - input: Input, - ) -> futures::stream::BoxStream<'static, Self::Output>; + input: BoxStream<'static, Input>, + ) -> BoxStream<'static, Self::Output>; } struct Map { @@ -169,7 +174,7 @@ where fn stream( self: Box, - input: I, + input: BoxStream<'static, I>, ) -> futures::stream::BoxStream<'static, Self::Output> { use futures::StreamExt; diff --git a/core/src/subscription/tracker.rs b/core/src/subscription/tracker.rs new file mode 100644 index 0000000000..826f60c003 --- /dev/null +++ b/core/src/subscription/tracker.rs @@ -0,0 +1,112 @@ +use crate::Subscription; + +use futures::{future::BoxFuture, sink::Sink}; +use std::collections::HashMap; +use std::marker::PhantomData; + +#[derive(Debug)] +pub struct Tracker { + subscriptions: HashMap>, + _hasher: PhantomData, +} + +#[derive(Debug)] +pub struct Execution { + _cancel: futures::channel::oneshot::Sender<()>, + listener: Option>, +} + +impl Tracker +where + Hasher: std::hash::Hasher + Default, + Event: 'static + Send + Clone, +{ + pub fn new() -> Self { + Self { + subscriptions: HashMap::new(), + _hasher: PhantomData, + } + } + + pub fn update( + &mut self, + subscription: Subscription, + sink: S, + ) -> Vec> + where + Message: 'static + Send, + S: 'static + + Sink + + Unpin + + Send + + Clone, + { + use futures::{future::FutureExt, stream::StreamExt}; + + let mut futures = Vec::new(); + + let recipes = subscription.recipes(); + let mut alive = std::collections::HashSet::new(); + + for recipe in recipes { + let id = { + let mut hasher = Hasher::default(); + recipe.hash(&mut hasher); + + hasher.finish() + }; + + let _ = alive.insert(id); + + if self.subscriptions.contains_key(&id) { + continue; + } + + let (cancel, cancelled) = futures::channel::oneshot::channel(); + + // TODO: Use bus if/when it supports async + let (event_sender, event_receiver) = + futures::channel::mpsc::channel(100); + + let stream = recipe.stream(event_receiver.boxed()); + + let future = futures::future::select( + cancelled, + stream.map(Ok).forward(sink.clone()), + ) + .map(|_| ()); + + let _ = self.subscriptions.insert( + id, + Execution { + _cancel: cancel, + listener: if event_sender.is_closed() { + None + } else { + Some(event_sender) + }, + }, + ); + + futures.push(future.boxed()); + } + + self.subscriptions.retain(|id, _| alive.contains(&id)); + + futures + } + + pub fn broadcast(&mut self, event: Event) { + self.subscriptions + .values_mut() + .filter_map(|connection| connection.listener.as_mut()) + .for_each(|listener| { + if let Err(error) = listener.try_send(event.clone()) { + log::error!( + "Error sending event to subscription: {:?}", + error + ); + } + }); + } +} diff --git a/examples/stopwatch.rs b/examples/stopwatch.rs index c9a61ee947..2bc85c4da4 100644 --- a/examples/stopwatch.rs +++ b/examples/stopwatch.rs @@ -165,7 +165,7 @@ mod time { fn stream( self: Box, - _input: I, + _input: futures::stream::BoxStream<'static, I>, ) -> futures::stream::BoxStream<'static, Self::Output> { use futures::stream::StreamExt; diff --git a/native/src/subscription.rs b/native/src/subscription.rs index db88867ab3..cd0822c155 100644 --- a/native/src/subscription.rs +++ b/native/src/subscription.rs @@ -15,7 +15,7 @@ use futures::stream::BoxStream; /// /// [`Command`]: ../struct.Command.html /// [`Subscription`]: struct.Subscription.html -pub type Subscription = iced_core::Subscription; +pub type Subscription = iced_core::Subscription; /// A stream of runtime events. /// @@ -24,6 +24,11 @@ pub type Subscription = iced_core::Subscription; /// [`Subscription`]: type.Subscription.html pub type EventStream = BoxStream<'static, Event>; +/// A native [`Subscription`] tracker. +/// +/// [`Subscription`]: type.Subscription.html +pub type Tracker = iced_core::subscription::Tracker; + pub use iced_core::subscription::Recipe; mod events; diff --git a/native/src/subscription/events.rs b/native/src/subscription/events.rs index b730182873..6ff2c0fb59 100644 --- a/native/src/subscription/events.rs +++ b/native/src/subscription/events.rs @@ -5,7 +5,7 @@ use crate::{ pub struct Events; -impl Recipe for Events { +impl Recipe for Events { type Output = Event; fn hash(&self, state: &mut Hasher) { From d50ff9b5d97d9c3d6c6c70a9b4efe764b6126c86 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Sun, 19 Jan 2020 09:06:48 +0100 Subject: [PATCH 02/16] Implement `Runtime` and `Executor` in `iced_core` They can be leveraged by shells to easily execute commands and track subscriptions. --- core/Cargo.toml | 2 + core/src/lib.rs | 6 +++ core/src/runtime.rs | 74 ++++++++++++++++++++++++++++++++ core/src/runtime/executor.rs | 11 +++++ core/src/subscription/tracker.rs | 8 ++-- 5 files changed, 97 insertions(+), 4 deletions(-) create mode 100644 core/src/runtime.rs create mode 100644 core/src/runtime/executor.rs diff --git a/core/Cargo.toml b/core/Cargo.toml index 4e019ba9bc..5e1a55326c 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -12,6 +12,8 @@ repository = "https://github.com/hecrj/iced" command = ["futures"] # Exposes a future-based `Subscription` type subscription = ["futures", "log"] +# Exposes a `runtime` module meant to abstract over different future executors +runtime = ["command", "subscription"] [dependencies] futures = { version = "0.3", optional = true } diff --git a/core/src/lib.rs b/core/src/lib.rs index 6f13c31092..760acefe81 100644 --- a/core/src/lib.rs +++ b/core/src/lib.rs @@ -44,3 +44,9 @@ pub mod subscription; #[cfg(feature = "subscription")] pub use subscription::Subscription; + +#[cfg(feature = "runtime")] +mod runtime; + +#[cfg(feature = "runtime")] +pub use runtime::Runtime; diff --git a/core/src/runtime.rs b/core/src/runtime.rs new file mode 100644 index 0000000000..31234d110b --- /dev/null +++ b/core/src/runtime.rs @@ -0,0 +1,74 @@ +mod executor; + +pub use executor::Executor; + +use crate::{subscription, Command, Subscription}; + +use futures::Sink; +use std::marker::PhantomData; + +#[derive(Debug)] +pub struct Runtime { + executor: Executor, + subscriptions: subscription::Tracker, + receiver: Receiver, + _message: PhantomData, +} + +impl + Runtime +where + Hasher: std::hash::Hasher + Default, + Event: Send + Clone + 'static, + Executor: self::Executor, + Receiver: Sink + + Unpin + + Send + + Clone + + 'static, + Message: Send + 'static, +{ + pub fn new(receiver: Receiver) -> Self { + Self { + executor: Executor::new(), + subscriptions: subscription::Tracker::new(), + receiver, + _message: PhantomData, + } + } + + pub fn spawn(&mut self, command: Command) { + use futures::{FutureExt, SinkExt}; + + let futures = command.futures(); + + for future in futures { + let mut receiver = self.receiver.clone(); + + self.executor.spawn(future.then(|message| { + async move { + let _ = receiver.send(message).await; + + () + } + })); + } + } + + pub fn track( + &mut self, + subscription: Subscription, + ) { + let futures = self + .subscriptions + .update(subscription, self.receiver.clone()); + + for future in futures { + self.executor.spawn(future); + } + } + + pub fn broadcast(&mut self, event: Event) { + self.subscriptions.broadcast(event); + } +} diff --git a/core/src/runtime/executor.rs b/core/src/runtime/executor.rs new file mode 100644 index 0000000000..d171c6d5da --- /dev/null +++ b/core/src/runtime/executor.rs @@ -0,0 +1,11 @@ +use futures::Future; + +pub trait Executor { + fn new() -> Self; + + fn spawn(&self, future: impl Future + Send + 'static); + + fn enter(&self, f: impl FnOnce() -> R) -> R { + f() + } +} diff --git a/core/src/subscription/tracker.rs b/core/src/subscription/tracker.rs index 826f60c003..a942b6199d 100644 --- a/core/src/subscription/tracker.rs +++ b/core/src/subscription/tracker.rs @@ -28,14 +28,14 @@ where } } - pub fn update( + pub fn update( &mut self, subscription: Subscription, - sink: S, + receiver: Receiver, ) -> Vec> where Message: 'static + Send, - S: 'static + Receiver: 'static + Sink + Unpin + Send @@ -72,7 +72,7 @@ where let future = futures::future::select( cancelled, - stream.map(Ok).forward(sink.clone()), + stream.map(Ok).forward(receiver.clone()), ) .map(|_| ()); From b5b17ed4d800c03beb3ad535d1069a7784e8dc1d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Sun, 19 Jan 2020 10:17:08 +0100 Subject: [PATCH 03/16] Create `iced_futures` and wire everything up --- Cargo.toml | 1 + core/Cargo.toml | 10 -- core/src/lib.rs | 18 ---- core/src/runtime/executor.rs | 11 --- futures/Cargo.toml | 23 +++++ {core => futures}/src/command.rs | 0 futures/src/lib.rs | 8 ++ {core => futures}/src/runtime.rs | 13 ++- futures/src/runtime/executor.rs | 26 +++++ {core => futures}/src/subscription.rs | 0 {core => futures}/src/subscription/tracker.rs | 0 native/Cargo.toml | 9 +- native/src/lib.rs | 7 +- native/src/runtime.rs | 14 +++ native/src/subscription.rs | 6 +- web/Cargo.toml | 9 +- web/src/lib.rs | 6 +- web/src/subscription.rs | 4 +- winit/Cargo.toml | 12 ++- winit/src/application.rs | 57 ++++------- winit/src/lib.rs | 3 +- winit/src/proxy.rs | 57 +++++++++++ winit/src/subscription.rs | 97 ------------------- 23 files changed, 196 insertions(+), 195 deletions(-) delete mode 100644 core/src/runtime/executor.rs create mode 100644 futures/Cargo.toml rename {core => futures}/src/command.rs (100%) create mode 100644 futures/src/lib.rs rename {core => futures}/src/runtime.rs (88%) create mode 100644 futures/src/runtime/executor.rs rename {core => futures}/src/subscription.rs (100%) rename {core => futures}/src/subscription/tracker.rs (100%) create mode 100644 native/src/runtime.rs create mode 100644 winit/src/proxy.rs delete mode 100644 winit/src/subscription.rs diff --git a/Cargo.toml b/Cargo.toml index aeb8382ee6..fbe3b9f295 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,6 +23,7 @@ maintenance = { status = "actively-developed" } [workspace] members = [ "core", + "futures", "native", "style", "web", diff --git a/core/Cargo.toml b/core/Cargo.toml index 5e1a55326c..22bc7ceb9b 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -7,14 +7,4 @@ description = "The essential concepts of Iced" license = "MIT" repository = "https://github.com/hecrj/iced" -[features] -# Exposes a future-based `Command` type -command = ["futures"] -# Exposes a future-based `Subscription` type -subscription = ["futures", "log"] -# Exposes a `runtime` module meant to abstract over different future executors -runtime = ["command", "subscription"] - [dependencies] -futures = { version = "0.3", optional = true } -log = { version = "0.4", optional = true } diff --git a/core/src/lib.rs b/core/src/lib.rs index 760acefe81..bec307ad73 100644 --- a/core/src/lib.rs +++ b/core/src/lib.rs @@ -32,21 +32,3 @@ pub use length::Length; pub use point::Point; pub use rectangle::Rectangle; pub use vector::Vector; - -#[cfg(feature = "command")] -mod command; - -#[cfg(feature = "command")] -pub use command::Command; - -#[cfg(feature = "subscription")] -pub mod subscription; - -#[cfg(feature = "subscription")] -pub use subscription::Subscription; - -#[cfg(feature = "runtime")] -mod runtime; - -#[cfg(feature = "runtime")] -pub use runtime::Runtime; diff --git a/core/src/runtime/executor.rs b/core/src/runtime/executor.rs deleted file mode 100644 index d171c6d5da..0000000000 --- a/core/src/runtime/executor.rs +++ /dev/null @@ -1,11 +0,0 @@ -use futures::Future; - -pub trait Executor { - fn new() -> Self; - - fn spawn(&self, future: impl Future + Send + 'static); - - fn enter(&self, f: impl FnOnce() -> R) -> R { - f() - } -} diff --git a/futures/Cargo.toml b/futures/Cargo.toml new file mode 100644 index 0000000000..fe0d378ce2 --- /dev/null +++ b/futures/Cargo.toml @@ -0,0 +1,23 @@ +[package] +name = "iced_futures" +version = "0.1.0-alpha" +authors = ["Héctor Ramón Jiménez "] +edition = "2018" +description = "Commands, subscriptions, and runtimes for Iced" +license = "MIT" +repository = "https://github.com/hecrj/iced" +documentation = "https://docs.rs/iced_futures" +keywords = ["gui", "ui", "graphics", "interface", "futures"] +categories = ["gui"] + +[dependencies] +log = "0.4" + +[dependencies.futures] +version = "0.3" +features = ["thread-pool"] + +[dependencies.tokio] +version = "0.2" +optional = true +features = ["rt-core"] diff --git a/core/src/command.rs b/futures/src/command.rs similarity index 100% rename from core/src/command.rs rename to futures/src/command.rs diff --git a/futures/src/lib.rs b/futures/src/lib.rs new file mode 100644 index 0000000000..f6bcf85aea --- /dev/null +++ b/futures/src/lib.rs @@ -0,0 +1,8 @@ +mod command; + +pub mod runtime; +pub mod subscription; + +pub use command::Command; +pub use runtime::Runtime; +pub use subscription::Subscription; diff --git a/core/src/runtime.rs b/futures/src/runtime.rs similarity index 88% rename from core/src/runtime.rs rename to futures/src/runtime.rs index 31234d110b..bc1ad8ace9 100644 --- a/core/src/runtime.rs +++ b/futures/src/runtime.rs @@ -1,3 +1,4 @@ +//! Run commands and subscriptions. mod executor; pub use executor::Executor; @@ -10,8 +11,8 @@ use std::marker::PhantomData; #[derive(Debug)] pub struct Runtime { executor: Executor, - subscriptions: subscription::Tracker, receiver: Receiver, + subscriptions: subscription::Tracker, _message: PhantomData, } @@ -28,15 +29,19 @@ where + 'static, Message: Send + 'static, { - pub fn new(receiver: Receiver) -> Self { + pub fn new(executor: Executor, receiver: Receiver) -> Self { Self { - executor: Executor::new(), - subscriptions: subscription::Tracker::new(), + executor, receiver, + subscriptions: subscription::Tracker::new(), _message: PhantomData, } } + pub fn enter(&self, f: impl FnOnce() -> R) -> R { + self.executor.enter(f) + } + pub fn spawn(&mut self, command: Command) { use futures::{FutureExt, SinkExt}; diff --git a/futures/src/runtime/executor.rs b/futures/src/runtime/executor.rs new file mode 100644 index 0000000000..855aa10594 --- /dev/null +++ b/futures/src/runtime/executor.rs @@ -0,0 +1,26 @@ +use futures::Future; + +pub trait Executor { + fn spawn(&self, future: impl Future + Send + 'static); + + fn enter(&self, f: impl FnOnce() -> R) -> R { + f() + } +} + +impl Executor for futures::executor::ThreadPool { + fn spawn(&self, future: impl Future + Send + 'static) { + self.spawn_ok(future); + } +} + +#[cfg(feature = "tokio")] +impl Executor for tokio::runtime::Runtime { + fn spawn(&self, future: impl Future + Send + 'static) { + let _ = tokio::runtime::Runtime::spawn(self, future); + } + + fn enter(&self, f: impl FnOnce() -> R) -> R { + tokio::runtime::Runtime::enter(self, f) + } +} diff --git a/core/src/subscription.rs b/futures/src/subscription.rs similarity index 100% rename from core/src/subscription.rs rename to futures/src/subscription.rs diff --git a/core/src/subscription/tracker.rs b/futures/src/subscription/tracker.rs similarity index 100% rename from core/src/subscription/tracker.rs rename to futures/src/subscription/tracker.rs diff --git a/native/Cargo.toml b/native/Cargo.toml index a31b662756..57a869e2b3 100644 --- a/native/Cargo.toml +++ b/native/Cargo.toml @@ -8,8 +8,15 @@ license = "MIT" repository = "https://github.com/hecrj/iced" [dependencies] -iced_core = { version = "0.1.0", path = "../core", features = ["command", "subscription"] } twox-hash = "1.5" raw-window-handle = "0.3" unicode-segmentation = "1.6" futures = "0.3" + +[dependencies.iced_core] +version = "0.1.0" +path = "../core" + +[dependencies.iced_futures] +version = "0.1.0-alpha" +path = "../futures" diff --git a/native/src/lib.rs b/native/src/lib.rs index 340b9ea7e9..7730c6a33c 100644 --- a/native/src/lib.rs +++ b/native/src/lib.rs @@ -42,6 +42,7 @@ pub mod input; pub mod layout; pub mod renderer; +pub mod runtime; pub mod subscription; pub mod widget; pub mod window; @@ -55,9 +56,10 @@ mod size; mod user_interface; pub use iced_core::{ - Align, Background, Color, Command, Font, HorizontalAlignment, Length, - Point, Rectangle, Vector, VerticalAlignment, + Align, Background, Color, Font, HorizontalAlignment, Length, Point, + Rectangle, Vector, VerticalAlignment, }; +pub use iced_futures::Command; pub use clipboard::Clipboard; pub use element::Element; @@ -66,6 +68,7 @@ pub use hasher::Hasher; pub use layout::Layout; pub use mouse_cursor::MouseCursor; pub use renderer::Renderer; +pub use runtime::Runtime; pub use size::Size; pub use subscription::Subscription; pub use user_interface::{Cache, UserInterface}; diff --git a/native/src/runtime.rs b/native/src/runtime.rs new file mode 100644 index 0000000000..2b3abbf186 --- /dev/null +++ b/native/src/runtime.rs @@ -0,0 +1,14 @@ +//! Run commands and subscriptions. +use crate::{Event, Hasher}; + +/// A native runtime with a generic executor and receiver of results. +/// +/// It can be used by shells to easily spawn a [`Command`] or track a +/// [`Subscription`]. +/// +/// [`Command`]: ../struct.Command.html +/// [`Subscription`]: ../struct.Subscription.html +pub type Runtime = + iced_futures::Runtime; + +pub use iced_futures::runtime::Executor; diff --git a/native/src/subscription.rs b/native/src/subscription.rs index cd0822c155..43f1758aa8 100644 --- a/native/src/subscription.rs +++ b/native/src/subscription.rs @@ -15,7 +15,7 @@ use futures::stream::BoxStream; /// /// [`Command`]: ../struct.Command.html /// [`Subscription`]: struct.Subscription.html -pub type Subscription = iced_core::Subscription; +pub type Subscription = iced_futures::Subscription; /// A stream of runtime events. /// @@ -27,9 +27,9 @@ pub type EventStream = BoxStream<'static, Event>; /// A native [`Subscription`] tracker. /// /// [`Subscription`]: type.Subscription.html -pub type Tracker = iced_core::subscription::Tracker; +pub type Tracker = iced_futures::subscription::Tracker; -pub use iced_core::subscription::Recipe; +pub use iced_futures::subscription::Recipe; mod events; diff --git a/web/Cargo.toml b/web/Cargo.toml index 605c746253..ea09257551 100644 --- a/web/Cargo.toml +++ b/web/Cargo.toml @@ -15,12 +15,19 @@ categories = ["web-programming"] maintenance = { status = "actively-developed" } [dependencies] -iced_core = { version = "0.1.0", path = "../core", features = ["command", "subscription"] } dodrio = "0.1.0" wasm-bindgen = "0.2.51" wasm-bindgen-futures = "0.4" futures = "0.3" +[dependencies.iced_core] +version = "0.1.0" +path = "../core" + +[dependencies.iced_futures] +version = "0.1.0-alpha" +path = "../futures" + [dependencies.web-sys] version = "0.3.27" features = [ diff --git a/web/src/lib.rs b/web/src/lib.rs index 7ea22e857e..b183c39084 100644 --- a/web/src/lib.rs +++ b/web/src/lib.rs @@ -72,9 +72,10 @@ pub use dodrio; pub use element::Element; pub use hasher::Hasher; pub use iced_core::{ - Align, Background, Color, Command, Font, HorizontalAlignment, Length, + Align, Background, Color, Font, HorizontalAlignment, Length, VerticalAlignment, }; +pub use iced_futures::Command; pub use style::Style; pub use subscription::Subscription; pub use widget::*; @@ -148,7 +149,6 @@ pub trait Application { } } - struct Instance { title: String, ui: Rc>>>, @@ -167,7 +167,7 @@ impl Clone for Instance { impl Instance where - Message: 'static + Message: 'static, { fn new(ui: impl Application + 'static) -> Self { Self { diff --git a/web/src/subscription.rs b/web/src/subscription.rs index 4638c8ab0a..6b8415c03a 100644 --- a/web/src/subscription.rs +++ b/web/src/subscription.rs @@ -14,6 +14,6 @@ use crate::Hasher; /// /// [`Command`]: ../struct.Command.html /// [`Subscription`]: struct.Subscription.html -pub type Subscription = iced_core::Subscription; +pub type Subscription = iced_futures::Subscription; -pub use iced_core::subscription::Recipe; +pub use iced_futures::subscription::Recipe; diff --git a/winit/Cargo.toml b/winit/Cargo.toml index 5727f8cf12..3ed37dd578 100644 --- a/winit/Cargo.toml +++ b/winit/Cargo.toml @@ -14,11 +14,17 @@ categories = ["gui"] debug = [] [dependencies] -iced_native = { version = "0.1.0-alpha", path = "../native" } winit = { version = "0.20.0-alpha3", git = "https://github.com/hecrj/winit", rev = "709808eb4e69044705fcb214bcc30556db761405"} -window_clipboard = { git = "https://github.com/hecrj/window_clipboard", rev = "22c6dd6c04cd05d528029b50a30c56417cd4bebf" } -futures = { version = "0.3", features = ["thread-pool"] } log = "0.4" +futures = { version = "0.3", features = ["thread-pool"] } + +[dependencies.iced_native] +version = "0.1.0-alpha" +path = "../native" + +[dependencies.window_clipboard] +git = "https://github.com/hecrj/window_clipboard" +rev = "22c6dd6c04cd05d528029b50a30c56417cd4bebf" [target.'cfg(target_os = "windows")'.dependencies.winapi] version = "0.3.6" diff --git a/winit/src/application.rs b/winit/src/application.rs index a14924ac5c..076ac092d7 100644 --- a/winit/src/application.rs +++ b/winit/src/application.rs @@ -1,9 +1,10 @@ use crate::{ conversion, input::{keyboard, mouse}, - subscription, window, Cache, Clipboard, Command, Debug, Element, Event, - Mode, MouseCursor, Settings, Size, Subscription, UserInterface, + window, Cache, Clipboard, Command, Debug, Element, Event, Mode, + MouseCursor, Proxy, Settings, Size, Subscription, UserInterface, }; +use iced_native::Runtime; /// An interactive, native cross-platform application. /// @@ -109,17 +110,19 @@ pub trait Application: Sized { debug.startup_started(); let event_loop = EventLoop::with_user_event(); - let proxy = event_loop.create_proxy(); - let mut thread_pool = - futures::executor::ThreadPool::new().expect("Create thread pool"); - let mut subscription_pool = subscription::Pool::new(); + let mut runtime = { + let thread_pool = futures::executor::ThreadPool::new() + .expect("Create thread pool"); + + Runtime::new(thread_pool, Proxy::new(event_loop.create_proxy())) + }; let mut external_messages = Vec::new(); let (mut application, init_command) = Self::new(); - spawn(init_command, &mut thread_pool, &proxy); + runtime.spawn(init_command); let subscription = application.subscription(); - subscription_pool.update(subscription, &mut thread_pool, &proxy); + runtime.track(subscription); let mut title = application.title(); let mut mode = application.mode(); @@ -212,7 +215,7 @@ pub trait Application: Sized { events .iter() .cloned() - .for_each(|event| subscription_pool.broadcast_event(event)); + .for_each(|event| runtime.broadcast(event)); let mut messages = user_interface.update( &renderer, @@ -241,17 +244,15 @@ pub trait Application: Sized { debug.log_message(&message); debug.update_started(); - let command = application.update(message); - spawn(command, &mut thread_pool, &proxy); + let command = + runtime.enter(|| application.update(message)); + runtime.spawn(command); debug.update_finished(); } - let subscription = application.subscription(); - subscription_pool.update( - subscription, - &mut thread_pool, - &proxy, - ); + let subscription = + runtime.enter(|| application.subscription()); + runtime.track(subscription); // Update window title let new_title = application.title(); @@ -463,28 +464,6 @@ fn to_physical(size: winit::dpi::LogicalSize, dpi: f64) -> (u16, u16) { ) } -fn spawn( - command: Command, - thread_pool: &mut futures::executor::ThreadPool, - proxy: &winit::event_loop::EventLoopProxy, -) { - use futures::FutureExt; - - let futures = command.futures(); - - for future in futures { - let proxy = proxy.clone(); - - let future = future.map(move |message| { - proxy - .send_event(message) - .expect("Send command result to event loop"); - }); - - thread_pool.spawn_ok(future); - } -} - // As defined in: http://www.unicode.org/faq/private_use.html // TODO: Remove once https://github.com/rust-windowing/winit/pull/1254 lands fn is_private_use_character(c: char) -> bool { diff --git a/winit/src/lib.rs b/winit/src/lib.rs index 9000f977df..056ae8f05d 100644 --- a/winit/src/lib.rs +++ b/winit/src/lib.rs @@ -31,7 +31,7 @@ pub mod settings; mod application; mod clipboard; mod mode; -mod subscription; +mod proxy; // We disable debug capabilities on release builds unless the `debug` feature // is explicitly enabled. @@ -48,3 +48,4 @@ pub use settings::Settings; use clipboard::Clipboard; use debug::Debug; +use proxy::Proxy; diff --git a/winit/src/proxy.rs b/winit/src/proxy.rs new file mode 100644 index 0000000000..7e8dee9816 --- /dev/null +++ b/winit/src/proxy.rs @@ -0,0 +1,57 @@ +use futures::{ + task::{Context, Poll}, + Sink, +}; +use std::pin::Pin; + +pub struct Proxy { + raw: winit::event_loop::EventLoopProxy, +} + +impl Clone for Proxy { + fn clone(&self) -> Self { + Self { + raw: self.raw.clone(), + } + } +} + +impl Proxy { + pub fn new(raw: winit::event_loop::EventLoopProxy) -> Self { + Self { raw } + } +} + +impl Sink for Proxy { + type Error = core::convert::Infallible; + + fn poll_ready( + self: Pin<&mut Self>, + _cx: &mut Context<'_>, + ) -> Poll> { + Poll::Ready(Ok(())) + } + + fn start_send( + self: Pin<&mut Self>, + message: Message, + ) -> Result<(), Self::Error> { + let _ = self.raw.send_event(message); + + Ok(()) + } + + fn poll_flush( + self: Pin<&mut Self>, + _cx: &mut Context<'_>, + ) -> Poll> { + Poll::Ready(Ok(())) + } + + fn poll_close( + self: Pin<&mut Self>, + _cx: &mut Context<'_>, + ) -> Poll> { + Poll::Ready(Ok(())) + } +} diff --git a/winit/src/subscription.rs b/winit/src/subscription.rs deleted file mode 100644 index bad68d5585..0000000000 --- a/winit/src/subscription.rs +++ /dev/null @@ -1,97 +0,0 @@ -use iced_native::{Event, Hasher, Subscription}; -use std::collections::HashMap; - -pub struct Pool { - alive: HashMap, -} - -pub struct Handle { - _cancel: futures::channel::oneshot::Sender<()>, - listener: Option>, -} - -impl Pool { - pub fn new() -> Self { - Self { - alive: HashMap::new(), - } - } - - pub fn update( - &mut self, - subscription: Subscription, - thread_pool: &mut futures::executor::ThreadPool, - proxy: &winit::event_loop::EventLoopProxy, - ) { - use futures::{future::FutureExt, stream::StreamExt}; - - let recipes = subscription.recipes(); - let mut alive = std::collections::HashSet::new(); - - for recipe in recipes { - let id = { - use std::hash::Hasher as _; - - let mut hasher = Hasher::default(); - recipe.hash(&mut hasher); - - hasher.finish() - }; - - let _ = alive.insert(id); - - if !self.alive.contains_key(&id) { - let (cancel, cancelled) = futures::channel::oneshot::channel(); - - // TODO: Use bus if/when it supports async - let (event_sender, event_receiver) = - futures::channel::mpsc::channel(100); - - let stream = recipe.stream(event_receiver.boxed()); - let proxy = proxy.clone(); - - let future = futures::future::select( - cancelled, - stream.for_each(move |message| { - proxy - .send_event(message) - .expect("Send subscription result to event loop"); - - futures::future::ready(()) - }), - ) - .map(|_| ()); - - thread_pool.spawn_ok(future); - - let _ = self.alive.insert( - id, - Handle { - _cancel: cancel, - listener: if event_sender.is_closed() { - None - } else { - Some(event_sender) - }, - }, - ); - } - } - - self.alive.retain(|id, _| alive.contains(&id)); - } - - pub fn broadcast_event(&mut self, event: Event) { - self.alive - .values_mut() - .filter_map(|connection| connection.listener.as_mut()) - .for_each(|listener| { - if let Err(error) = listener.try_send(event.clone()) { - log::error!( - "Error sending event to subscription: {:?}", - error - ); - } - }); - } -} From b8b0d97525aaa2641d8493aa65e3108d70c1560a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Sun, 19 Jan 2020 11:08:32 +0100 Subject: [PATCH 04/16] Rename `Receiver` to `Sender` in `Runtime` --- futures/src/runtime.rs | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/futures/src/runtime.rs b/futures/src/runtime.rs index bc1ad8ace9..37905c6173 100644 --- a/futures/src/runtime.rs +++ b/futures/src/runtime.rs @@ -9,30 +9,30 @@ use futures::Sink; use std::marker::PhantomData; #[derive(Debug)] -pub struct Runtime { +pub struct Runtime { executor: Executor, - receiver: Receiver, + sender: Sender, subscriptions: subscription::Tracker, _message: PhantomData, } -impl - Runtime +impl + Runtime where Hasher: std::hash::Hasher + Default, Event: Send + Clone + 'static, Executor: self::Executor, - Receiver: Sink + Sender: Sink + Unpin + Send + Clone + 'static, Message: Send + 'static, { - pub fn new(executor: Executor, receiver: Receiver) -> Self { + pub fn new(executor: Executor, sender: Sender) -> Self { Self { executor, - receiver, + sender, subscriptions: subscription::Tracker::new(), _message: PhantomData, } @@ -48,11 +48,11 @@ where let futures = command.futures(); for future in futures { - let mut receiver = self.receiver.clone(); + let mut sender = self.sender.clone(); self.executor.spawn(future.then(|message| { async move { - let _ = receiver.send(message).await; + let _ = sender.send(message).await; () } @@ -64,9 +64,8 @@ where &mut self, subscription: Subscription, ) { - let futures = self - .subscriptions - .update(subscription, self.receiver.clone()); + let futures = + self.subscriptions.update(subscription, self.sender.clone()); for future in futures { self.executor.spawn(future); From 35760ac68f06e783e64e9048aff0fff6df1c09cf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Sun, 19 Jan 2020 11:08:47 +0100 Subject: [PATCH 05/16] Make `thread-pool` optional in `iced_futures` --- futures/Cargo.toml | 4 +++- futures/src/runtime/executor.rs | 1 + winit/Cargo.toml | 7 ++++++- 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/futures/Cargo.toml b/futures/Cargo.toml index fe0d378ce2..5b303e018b 100644 --- a/futures/Cargo.toml +++ b/futures/Cargo.toml @@ -10,12 +10,14 @@ documentation = "https://docs.rs/iced_futures" keywords = ["gui", "ui", "graphics", "interface", "futures"] categories = ["gui"] +[features] +thread-pool = ["futures/thread-pool"] + [dependencies] log = "0.4" [dependencies.futures] version = "0.3" -features = ["thread-pool"] [dependencies.tokio] version = "0.2" diff --git a/futures/src/runtime/executor.rs b/futures/src/runtime/executor.rs index 855aa10594..eec5e231ef 100644 --- a/futures/src/runtime/executor.rs +++ b/futures/src/runtime/executor.rs @@ -8,6 +8,7 @@ pub trait Executor { } } +#[cfg(feature = "thread-pool")] impl Executor for futures::executor::ThreadPool { fn spawn(&self, future: impl Future + Send + 'static) { self.spawn_ok(future); diff --git a/winit/Cargo.toml b/winit/Cargo.toml index 3ed37dd578..ba6d5229d8 100644 --- a/winit/Cargo.toml +++ b/winit/Cargo.toml @@ -16,12 +16,17 @@ debug = [] [dependencies] winit = { version = "0.20.0-alpha3", git = "https://github.com/hecrj/winit", rev = "709808eb4e69044705fcb214bcc30556db761405"} log = "0.4" -futures = { version = "0.3", features = ["thread-pool"] } +futures = "0.3" [dependencies.iced_native] version = "0.1.0-alpha" path = "../native" +[dependencies.iced_futures] +version = "0.1.0-alpha" +path = "../futures" +features = ["thread-pool"] + [dependencies.window_clipboard] git = "https://github.com/hecrj/window_clipboard" rev = "22c6dd6c04cd05d528029b50a30c56417cd4bebf" From 90690702e1e4abab804ec91e8ff4183824bec436 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Mon, 20 Jan 2020 04:47:36 +0100 Subject: [PATCH 06/16] Add `Application::Executor` associated type --- Cargo.toml | 1 + examples/events.rs | 5 ++-- examples/pokedex.rs | 1 + examples/stopwatch.rs | 1 + examples/todos.rs | 1 + futures/Cargo.toml | 4 ++++ futures/src/executor.rs | 36 +++++++++++++++++++++++++++++ futures/src/executor/async_std.rs | 15 ++++++++++++ futures/src/executor/null.rs | 13 +++++++++++ futures/src/executor/thread_pool.rs | 15 ++++++++++++ futures/src/executor/tokio.rs | 19 +++++++++++++++ futures/src/lib.rs | 6 ++++- futures/src/runtime.rs | 8 ++----- futures/src/runtime/executor.rs | 27 ---------------------- native/Cargo.toml | 2 +- native/src/lib.rs | 7 ++++-- native/src/runtime.rs | 2 -- native/src/subscription.rs | 2 +- native/src/subscription/events.rs | 3 ++- src/application.rs | 14 +++++++++-- src/lib.rs | 22 ++++++++++++------ src/native.rs | 2 ++ src/native/executor.rs | 23 ++++++++++++++++++ src/sandbox.rs | 3 ++- web/Cargo.toml | 1 - web/src/lib.rs | 2 +- winit/Cargo.toml | 6 ----- winit/src/application.rs | 18 +++++++++------ winit/src/proxy.rs | 2 +- 29 files changed, 192 insertions(+), 69 deletions(-) create mode 100644 futures/src/executor.rs create mode 100644 futures/src/executor/async_std.rs create mode 100644 futures/src/executor/null.rs create mode 100644 futures/src/executor/thread_pool.rs create mode 100644 futures/src/executor/tokio.rs delete mode 100644 futures/src/runtime/executor.rs create mode 100644 src/native/executor.rs diff --git a/Cargo.toml b/Cargo.toml index fbe3b9f295..87f3000eec 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -41,6 +41,7 @@ iced_web = { version = "0.1.0", path = "web" } [dev-dependencies] iced_native = { version = "0.1", path = "./native" } iced_wgpu = { version = "0.1", path = "./wgpu" } +iced_futures = { version = "0.1.0-alpha", path = "./futures", features = ["async-std"] } env_logger = "0.7" serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" diff --git a/examples/events.rs b/examples/events.rs index 74542171e3..0c9dca059a 100644 --- a/examples/events.rs +++ b/examples/events.rs @@ -1,6 +1,6 @@ use iced::{ - Align, Application, Checkbox, Column, Command, Container, Element, Length, - Settings, Subscription, Text, + executor, Align, Application, Checkbox, Column, Command, Container, + Element, Length, Settings, Subscription, Text, }; pub fn main() { @@ -20,6 +20,7 @@ enum Message { } impl Application for Events { + type Executor = executor::Default; type Message = Message; fn new() -> (Events, Command) { diff --git a/examples/pokedex.rs b/examples/pokedex.rs index 7326f94fd8..505dbf1986 100644 --- a/examples/pokedex.rs +++ b/examples/pokedex.rs @@ -27,6 +27,7 @@ enum Message { } impl Application for Pokedex { + type Executor = iced_futures::executor::AsyncStd; type Message = Message; fn new() -> (Pokedex, Command) { diff --git a/examples/stopwatch.rs b/examples/stopwatch.rs index 2bc85c4da4..6e35703981 100644 --- a/examples/stopwatch.rs +++ b/examples/stopwatch.rs @@ -28,6 +28,7 @@ enum Message { } impl Application for Stopwatch { + type Executor = iced_futures::executor::AsyncStd; type Message = Message; fn new() -> (Stopwatch, Command) { diff --git a/examples/todos.rs b/examples/todos.rs index 4166f75a7e..06595a1e47 100644 --- a/examples/todos.rs +++ b/examples/todos.rs @@ -38,6 +38,7 @@ enum Message { } impl Application for Todos { + type Executor = iced_futures::executor::AsyncStd; type Message = Message; fn new() -> (Todos, Command) { diff --git a/futures/Cargo.toml b/futures/Cargo.toml index 5b303e018b..13c2d6b731 100644 --- a/futures/Cargo.toml +++ b/futures/Cargo.toml @@ -23,3 +23,7 @@ version = "0.3" version = "0.2" optional = true features = ["rt-core"] + +[dependencies.async-std] +version = "1.0" +optional = true diff --git a/futures/src/executor.rs b/futures/src/executor.rs new file mode 100644 index 0000000000..144a41f8a4 --- /dev/null +++ b/futures/src/executor.rs @@ -0,0 +1,36 @@ +//! Choose your preferred executor to power a runtime. +mod null; + +#[cfg(feature = "thread-pool")] +mod thread_pool; + +#[cfg(feature = "thread-pool")] +pub use thread_pool::ThreadPool; + +#[cfg(feature = "tokio")] +mod tokio; + +#[cfg(feature = "async-std")] +mod async_std; + +pub use null::Null; + +#[cfg(feature = "tokio")] +pub use self::tokio::Tokio; + +#[cfg(feature = "async-std")] +pub use self::async_std::AsyncStd; + +use futures::Future; + +pub trait Executor: Sized { + fn new() -> Result + where + Self: Sized; + + fn spawn(&self, future: impl Future + Send + 'static); + + fn enter(&self, f: impl FnOnce() -> R) -> R { + f() + } +} diff --git a/futures/src/executor/async_std.rs b/futures/src/executor/async_std.rs new file mode 100644 index 0000000000..b056b23d02 --- /dev/null +++ b/futures/src/executor/async_std.rs @@ -0,0 +1,15 @@ +use crate::Executor; + +use futures::Future; + +pub struct AsyncStd; + +impl Executor for AsyncStd { + fn new() -> Result { + Ok(Self) + } + + fn spawn(&self, future: impl Future + Send + 'static) { + let _ = async_std::task::spawn(future); + } +} diff --git a/futures/src/executor/null.rs b/futures/src/executor/null.rs new file mode 100644 index 0000000000..722073bb00 --- /dev/null +++ b/futures/src/executor/null.rs @@ -0,0 +1,13 @@ +use crate::Executor; + +use futures::Future; + +pub struct Null; + +impl Executor for Null { + fn new() -> Result { + Ok(Self) + } + + fn spawn(&self, _future: impl Future + Send + 'static) {} +} diff --git a/futures/src/executor/thread_pool.rs b/futures/src/executor/thread_pool.rs new file mode 100644 index 0000000000..6393d0d549 --- /dev/null +++ b/futures/src/executor/thread_pool.rs @@ -0,0 +1,15 @@ +use crate::Executor; + +use futures::Future; + +pub type ThreadPool = futures::executor::ThreadPool; + +impl Executor for futures::executor::ThreadPool { + fn new() -> Result { + futures::executor::ThreadPool::new() + } + + fn spawn(&self, future: impl Future + Send + 'static) { + self.spawn_ok(future); + } +} diff --git a/futures/src/executor/tokio.rs b/futures/src/executor/tokio.rs new file mode 100644 index 0000000000..aafa7e7bda --- /dev/null +++ b/futures/src/executor/tokio.rs @@ -0,0 +1,19 @@ +use crate::Executor; + +use futures::Future; + +pub type Tokio = tokio::runtime::Runtime; + +impl Executor for Tokio { + fn new() -> Result { + tokio::runtime::Runtime::new() + } + + fn spawn(&self, future: impl Future + Send + 'static) { + let _ = tokio::runtime::Runtime::spawn(self, future); + } + + fn enter(&self, f: impl FnOnce() -> R) -> R { + tokio::runtime::Runtime::enter(self, f) + } +} diff --git a/futures/src/lib.rs b/futures/src/lib.rs index f6bcf85aea..832a50f68f 100644 --- a/futures/src/lib.rs +++ b/futures/src/lib.rs @@ -1,8 +1,12 @@ +pub use futures; + mod command; +mod runtime; -pub mod runtime; +pub mod executor; pub mod subscription; pub use command::Command; +pub use executor::Executor; pub use runtime::Runtime; pub use subscription::Subscription; diff --git a/futures/src/runtime.rs b/futures/src/runtime.rs index 37905c6173..a508c46e92 100644 --- a/futures/src/runtime.rs +++ b/futures/src/runtime.rs @@ -1,9 +1,5 @@ -//! Run commands and subscriptions. -mod executor; - -pub use executor::Executor; - -use crate::{subscription, Command, Subscription}; +//! Run commands and keep track of subscriptions. +use crate::{subscription, Command, Executor, Subscription}; use futures::Sink; use std::marker::PhantomData; diff --git a/futures/src/runtime/executor.rs b/futures/src/runtime/executor.rs deleted file mode 100644 index eec5e231ef..0000000000 --- a/futures/src/runtime/executor.rs +++ /dev/null @@ -1,27 +0,0 @@ -use futures::Future; - -pub trait Executor { - fn spawn(&self, future: impl Future + Send + 'static); - - fn enter(&self, f: impl FnOnce() -> R) -> R { - f() - } -} - -#[cfg(feature = "thread-pool")] -impl Executor for futures::executor::ThreadPool { - fn spawn(&self, future: impl Future + Send + 'static) { - self.spawn_ok(future); - } -} - -#[cfg(feature = "tokio")] -impl Executor for tokio::runtime::Runtime { - fn spawn(&self, future: impl Future + Send + 'static) { - let _ = tokio::runtime::Runtime::spawn(self, future); - } - - fn enter(&self, f: impl FnOnce() -> R) -> R { - tokio::runtime::Runtime::enter(self, f) - } -} diff --git a/native/Cargo.toml b/native/Cargo.toml index 57a869e2b3..6276535ef6 100644 --- a/native/Cargo.toml +++ b/native/Cargo.toml @@ -11,7 +11,6 @@ repository = "https://github.com/hecrj/iced" twox-hash = "1.5" raw-window-handle = "0.3" unicode-segmentation = "1.6" -futures = "0.3" [dependencies.iced_core] version = "0.1.0" @@ -20,3 +19,4 @@ path = "../core" [dependencies.iced_futures] version = "0.1.0-alpha" path = "../futures" +features = ["thread-pool"] diff --git a/native/src/lib.rs b/native/src/lib.rs index 7730c6a33c..b5856c0098 100644 --- a/native/src/lib.rs +++ b/native/src/lib.rs @@ -42,7 +42,6 @@ pub mod input; pub mod layout; pub mod renderer; -pub mod runtime; pub mod subscription; pub mod widget; pub mod window; @@ -52,6 +51,7 @@ mod element; mod event; mod hasher; mod mouse_cursor; +mod runtime; mod size; mod user_interface; @@ -59,7 +59,10 @@ pub use iced_core::{ Align, Background, Color, Font, HorizontalAlignment, Length, Point, Rectangle, Vector, VerticalAlignment, }; -pub use iced_futures::Command; +pub use iced_futures::{executor, futures, Command}; + +#[doc(no_inline)] +pub use executor::Executor; pub use clipboard::Clipboard; pub use element::Element; diff --git a/native/src/runtime.rs b/native/src/runtime.rs index 2b3abbf186..9fa031f4d4 100644 --- a/native/src/runtime.rs +++ b/native/src/runtime.rs @@ -10,5 +10,3 @@ use crate::{Event, Hasher}; /// [`Subscription`]: ../struct.Subscription.html pub type Runtime = iced_futures::Runtime; - -pub use iced_futures::runtime::Executor; diff --git a/native/src/subscription.rs b/native/src/subscription.rs index 43f1758aa8..0d002c6ce7 100644 --- a/native/src/subscription.rs +++ b/native/src/subscription.rs @@ -1,6 +1,6 @@ //! Listen to external events in your application. use crate::{Event, Hasher}; -use futures::stream::BoxStream; +use iced_futures::futures::stream::BoxStream; /// A request to listen to external events. /// diff --git a/native/src/subscription/events.rs b/native/src/subscription/events.rs index 6ff2c0fb59..7d33166ef4 100644 --- a/native/src/subscription/events.rs +++ b/native/src/subscription/events.rs @@ -2,6 +2,7 @@ use crate::{ subscription::{EventStream, Recipe}, Event, Hasher, }; +use iced_futures::futures::stream::BoxStream; pub struct Events; @@ -17,7 +18,7 @@ impl Recipe for Events { fn stream( self: Box, event_stream: EventStream, - ) -> futures::stream::BoxStream<'static, Self::Output> { + ) -> BoxStream<'static, Self::Output> { event_stream } } diff --git a/src/application.rs b/src/application.rs index b940cc1753..3a526f1b11 100644 --- a/src/application.rs +++ b/src/application.rs @@ -1,4 +1,4 @@ -use crate::{window, Command, Element, Settings, Subscription}; +use crate::{window, Command, Element, Executor, Settings, Subscription}; /// An interactive cross-platform application. /// @@ -19,7 +19,7 @@ use crate::{window, Command, Element, Settings, Subscription}; /// before](index.html#overview). We just need to fill in the gaps: /// /// ```no_run -/// use iced::{button, Application, Button, Column, Command, Element, Settings, Text}; +/// use iced::{button, executor, Application, Button, Column, Command, Element, Settings, Text}; /// /// pub fn main() { /// Counter::run(Settings::default()) @@ -39,6 +39,7 @@ use crate::{window, Command, Element, Settings, Subscription}; /// } /// /// impl Application for Counter { +/// type Executor = executor::Null; /// type Message = Message; /// /// fn new() -> (Self, Command) { @@ -80,6 +81,14 @@ use crate::{window, Command, Element, Settings, Subscription}; /// } /// ``` pub trait Application: Sized { + /// The [`Executor`] that will run commands and subscriptions. + /// + /// The [`executor::Default`] can be a good starting point! + /// + /// [`Executor`]: trait.Executor.html + /// [`executor::Default`]: executor/struct.Default.html + type Executor: Executor; + /// The type of __messages__ your [`Application`] will produce. /// /// [`Application`]: trait.Application.html @@ -185,6 +194,7 @@ where A: Application, { type Renderer = iced_wgpu::Renderer; + type Executor = A::Executor; type Message = A::Message; fn new() -> (Self, Command) { diff --git a/src/lib.rs b/src/lib.rs index 759dea2f55..18dfc09856 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -180,18 +180,26 @@ #![deny(unsafe_code)] #![deny(rust_2018_idioms)] mod application; -#[cfg(target_arch = "wasm32")] -#[path = "web.rs"] -mod platform; -#[cfg(not(target_arch = "wasm32"))] -#[path = "native.rs"] -mod platform; mod sandbox; +#[cfg(not(target_arch = "wasm32"))] +mod native; + +#[cfg(not(target_arch = "wasm32"))] +pub use native::*; + +#[cfg(target_arch = "wasm32")] +mod web; + +#[cfg(target_arch = "wasm32")] +pub use web::*; + pub mod settings; pub mod window; +#[doc(no_inline)] +pub use executor::Executor; + pub use application::Application; -pub use platform::*; pub use sandbox::Sandbox; pub use settings::Settings; diff --git a/src/native.rs b/src/native.rs index 35441a3e26..86ccffab5c 100644 --- a/src/native.rs +++ b/src/native.rs @@ -3,6 +3,8 @@ pub use iced_winit::{ Space, Subscription, Vector, VerticalAlignment, }; +pub mod executor; + pub mod widget { //! Display information and interactive controls in your application. //! diff --git a/src/native/executor.rs b/src/native/executor.rs new file mode 100644 index 0000000000..68a1d280e2 --- /dev/null +++ b/src/native/executor.rs @@ -0,0 +1,23 @@ +//! Choose your preferred executor to power your application. +pub use iced_winit::{executor::Null, Executor}; +use iced_winit::{executor::ThreadPool, futures}; + +/// The default cross-platform executor. +/// +/// - On native platforms, it will use a `ThreadPool`. +/// - On the Web, it will use `wasm-bindgen-futures::spawn_local`. +#[derive(Debug)] +pub struct Default(ThreadPool); + +impl Executor for Default { + fn new() -> Result { + Ok(Default(ThreadPool::new()?)) + } + + fn spawn( + &self, + future: impl futures::Future + Send + 'static, + ) { + self.0.spawn(future); + } +} diff --git a/src/sandbox.rs b/src/sandbox.rs index dda4c3f510..2c0332ff85 100644 --- a/src/sandbox.rs +++ b/src/sandbox.rs @@ -1,4 +1,4 @@ -use crate::{Application, Command, Element, Settings, Subscription}; +use crate::{executor, Application, Command, Element, Settings, Subscription}; /// A sandboxed [`Application`]. /// @@ -133,6 +133,7 @@ impl Application for T where T: Sandbox, { + type Executor = executor::Null; type Message = T::Message; fn new() -> (Self, Command) { diff --git a/web/Cargo.toml b/web/Cargo.toml index ea09257551..4695386397 100644 --- a/web/Cargo.toml +++ b/web/Cargo.toml @@ -18,7 +18,6 @@ maintenance = { status = "actively-developed" } dodrio = "0.1.0" wasm-bindgen = "0.2.51" wasm-bindgen-futures = "0.4" -futures = "0.3" [dependencies.iced_core] version = "0.1.0" diff --git a/web/src/lib.rs b/web/src/lib.rs index b183c39084..c44b99b5a5 100644 --- a/web/src/lib.rs +++ b/web/src/lib.rs @@ -75,7 +75,7 @@ pub use iced_core::{ Align, Background, Color, Font, HorizontalAlignment, Length, VerticalAlignment, }; -pub use iced_futures::Command; +pub use iced_futures::{futures, Command}; pub use style::Style; pub use subscription::Subscription; pub use widget::*; diff --git a/winit/Cargo.toml b/winit/Cargo.toml index ba6d5229d8..cef41e9c28 100644 --- a/winit/Cargo.toml +++ b/winit/Cargo.toml @@ -16,17 +16,11 @@ debug = [] [dependencies] winit = { version = "0.20.0-alpha3", git = "https://github.com/hecrj/winit", rev = "709808eb4e69044705fcb214bcc30556db761405"} log = "0.4" -futures = "0.3" [dependencies.iced_native] version = "0.1.0-alpha" path = "../native" -[dependencies.iced_futures] -version = "0.1.0-alpha" -path = "../futures" -features = ["thread-pool"] - [dependencies.window_clipboard] git = "https://github.com/hecrj/window_clipboard" rev = "22c6dd6c04cd05d528029b50a30c56417cd4bebf" diff --git a/winit/src/application.rs b/winit/src/application.rs index 076ac092d7..4b21a93096 100644 --- a/winit/src/application.rs +++ b/winit/src/application.rs @@ -1,10 +1,9 @@ use crate::{ conversion, input::{keyboard, mouse}, - window, Cache, Clipboard, Command, Debug, Element, Event, Mode, - MouseCursor, Proxy, Settings, Size, Subscription, UserInterface, + window, Cache, Clipboard, Command, Debug, Element, Event, Executor, Mode, + MouseCursor, Proxy, Runtime, Settings, Size, Subscription, UserInterface, }; -use iced_native::Runtime; /// An interactive, native cross-platform application. /// @@ -20,6 +19,11 @@ pub trait Application: Sized { /// [`Application`]: trait.Application.html type Renderer: window::Renderer; + /// The [`Executor`] that will run commands and subscriptions. + /// + /// [`Executor`]: trait.Executor.html + type Executor: Executor; + /// The type of __messages__ your [`Application`] will produce. /// /// [`Application`]: trait.Application.html @@ -110,13 +114,13 @@ pub trait Application: Sized { debug.startup_started(); let event_loop = EventLoop::with_user_event(); + let mut external_messages = Vec::new(); + let mut runtime = { - let thread_pool = futures::executor::ThreadPool::new() - .expect("Create thread pool"); + let executor = Self::Executor::new().expect("Create executor"); - Runtime::new(thread_pool, Proxy::new(event_loop.create_proxy())) + Runtime::new(executor, Proxy::new(event_loop.create_proxy())) }; - let mut external_messages = Vec::new(); let (mut application, init_command) = Self::new(); runtime.spawn(init_command); diff --git a/winit/src/proxy.rs b/winit/src/proxy.rs index 7e8dee9816..cff9df33fd 100644 --- a/winit/src/proxy.rs +++ b/winit/src/proxy.rs @@ -1,4 +1,4 @@ -use futures::{ +use iced_native::futures::{ task::{Context, Poll}, Sink, }; From 04086a90c9e933ebfb42de378054e1115b33529d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Mon, 20 Jan 2020 05:43:09 +0100 Subject: [PATCH 07/16] Implement `WasmBindgen` executor and reorganize --- futures/Cargo.toml | 3 ++ futures/src/executor.rs | 12 +++-- futures/src/executor/wasm_bindgen.rs | 17 +++++++ src/element.rs | 9 ++++ src/executor.rs | 54 ++++++++++++++++++++++ src/lib.rs | 30 ++++++------ src/native.rs | 68 ---------------------------- src/native/executor.rs | 23 ---------- src/web.rs | 1 - src/widget.rs | 60 ++++++++++++++++++++++++ web/src/lib.rs | 9 +++- 11 files changed, 176 insertions(+), 110 deletions(-) create mode 100644 futures/src/executor/wasm_bindgen.rs create mode 100644 src/element.rs create mode 100644 src/executor.rs delete mode 100644 src/native.rs delete mode 100644 src/native/executor.rs delete mode 100644 src/web.rs create mode 100644 src/widget.rs diff --git a/futures/Cargo.toml b/futures/Cargo.toml index 13c2d6b731..91860e1e18 100644 --- a/futures/Cargo.toml +++ b/futures/Cargo.toml @@ -27,3 +27,6 @@ features = ["rt-core"] [dependencies.async-std] version = "1.0" optional = true + +[target.'cfg(target_arch = "wasm32")'.dependencies] +wasm-bindgen-futures = "0.4" diff --git a/futures/src/executor.rs b/futures/src/executor.rs index 144a41f8a4..b2ff043eca 100644 --- a/futures/src/executor.rs +++ b/futures/src/executor.rs @@ -4,23 +4,29 @@ mod null; #[cfg(feature = "thread-pool")] mod thread_pool; -#[cfg(feature = "thread-pool")] -pub use thread_pool::ThreadPool; - #[cfg(feature = "tokio")] mod tokio; #[cfg(feature = "async-std")] mod async_std; +#[cfg(target_arch = "wasm32")] +mod wasm_bindgen; + pub use null::Null; +#[cfg(feature = "thread-pool")] +pub use thread_pool::ThreadPool; + #[cfg(feature = "tokio")] pub use self::tokio::Tokio; #[cfg(feature = "async-std")] pub use self::async_std::AsyncStd; +#[cfg(target_arch = "wasm32")] +pub use wasm_bindgen::WasmBindgen; + use futures::Future; pub trait Executor: Sized { diff --git a/futures/src/executor/wasm_bindgen.rs b/futures/src/executor/wasm_bindgen.rs new file mode 100644 index 0000000000..70a8ea8ea0 --- /dev/null +++ b/futures/src/executor/wasm_bindgen.rs @@ -0,0 +1,17 @@ +use crate::Executor; + +#[derive(Debug)] +pub struct WasmBindgen; + +impl Executor for WasmBindgen { + fn new() -> Result { + Ok(Self) + } + + fn spawn( + &self, + future: impl futures::Future + Send + 'static, + ) { + wasm_bindgen_futures::spawn_local(future); + } +} diff --git a/src/element.rs b/src/element.rs new file mode 100644 index 0000000000..e5356fb69e --- /dev/null +++ b/src/element.rs @@ -0,0 +1,9 @@ +/// A generic widget. +/// +/// This is an alias of an `iced_native` element with a default `Renderer`. +#[cfg(not(target_arch = "wasm32"))] +pub type Element<'a, Message> = + iced_winit::Element<'a, Message, iced_wgpu::Renderer>; + +#[cfg(target_arch = "wasm32")] +pub use iced_web::Element; diff --git a/src/executor.rs b/src/executor.rs new file mode 100644 index 0000000000..cbbd8283b7 --- /dev/null +++ b/src/executor.rs @@ -0,0 +1,54 @@ +//! Choose your preferred executor to power your application. +pub use crate::common::{executor::Null, Executor}; + +pub use platform::Default; + +#[cfg(not(target_arch = "wasm32"))] +mod platform { + use iced_winit::{executor::ThreadPool, futures, Executor}; + + /// A default cross-platform executor. + /// + /// - On native platforms, it will use a `iced_futures::executor::ThreadPool`. + /// - On the Web, it will use `iced_futures::executor::WasmBindgen`. + #[derive(Debug)] + pub struct Default(ThreadPool); + + impl Executor for Default { + fn new() -> Result { + Ok(Default(ThreadPool::new()?)) + } + + fn spawn( + &self, + future: impl futures::Future + Send + 'static, + ) { + self.0.spawn(future); + } + } +} + +#[cfg(target_arch = "wasm32")] +mod platform { + use iced_web::{executor::WasmBindgen, futures, Executor}; + + /// A default cross-platform executor. + /// + /// - On native platforms, it will use a `iced_futures::executor::ThreadPool`. + /// - On the Web, it will use `iced_futures::executor::WasmBindgen`. + #[derive(Debug)] + pub struct Default(WasmBindgen); + + impl Executor for Default { + fn new() -> Result { + Ok(Default(WasmBindgen::new()?)) + } + + fn spawn( + &self, + future: impl futures::Future + Send + 'static, + ) { + self.0.spawn(future); + } + } +} diff --git a/src/lib.rs b/src/lib.rs index 18dfc09856..9c9bcff54b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -180,26 +180,30 @@ #![deny(unsafe_code)] #![deny(rust_2018_idioms)] mod application; +mod element; mod sandbox; -#[cfg(not(target_arch = "wasm32"))] -mod native; - -#[cfg(not(target_arch = "wasm32"))] -pub use native::*; - -#[cfg(target_arch = "wasm32")] -mod web; - -#[cfg(target_arch = "wasm32")] -pub use web::*; - +pub mod executor; pub mod settings; +pub mod widget; pub mod window; #[doc(no_inline)] -pub use executor::Executor; +pub use widget::*; pub use application::Application; +pub use element::Element; +pub use executor::Executor; pub use sandbox::Sandbox; pub use settings::Settings; + +#[cfg(not(target_arch = "wasm32"))] +use iced_winit as common; + +#[cfg(target_arch = "wasm32")] +use iced_web as common; + +pub use common::{ + Align, Background, Color, Command, Font, HorizontalAlignment, Length, + Space, Subscription, Vector, VerticalAlignment, +}; diff --git a/src/native.rs b/src/native.rs deleted file mode 100644 index 86ccffab5c..0000000000 --- a/src/native.rs +++ /dev/null @@ -1,68 +0,0 @@ -pub use iced_winit::{ - Align, Background, Color, Command, Font, HorizontalAlignment, Length, - Space, Subscription, Vector, VerticalAlignment, -}; - -pub mod executor; - -pub mod widget { - //! Display information and interactive controls in your application. - //! - //! # Re-exports - //! For convenience, the contents of this module are available at the root - //! module. Therefore, you can directly type: - //! - //! ``` - //! use iced::{button, Button}; - //! ``` - //! - //! # Stateful widgets - //! Some widgets need to keep track of __local state__. - //! - //! These widgets have their own module with a `State` type. For instance, a - //! [`TextInput`] has some [`text_input::State`]. - //! - //! [`TextInput`]: text_input/struct.TextInput.html - //! [`text_input::State`]: text_input/struct.State.html - pub use iced_wgpu::widget::*; - - pub mod image { - //! Display images in your user interface. - pub use iced_winit::image::{Handle, Image}; - } - - pub mod svg { - //! Display vector graphics in your user interface. - pub use iced_winit::svg::{Handle, Svg}; - } - - pub use iced_winit::Text; - - #[doc(no_inline)] - pub use { - button::Button, checkbox::Checkbox, container::Container, image::Image, - progress_bar::ProgressBar, radio::Radio, scrollable::Scrollable, - slider::Slider, svg::Svg, text_input::TextInput, - }; - - /// A container that distributes its contents vertically. - /// - /// This is an alias of an `iced_native` column with a default `Renderer`. - pub type Column<'a, Message> = - iced_winit::Column<'a, Message, iced_wgpu::Renderer>; - - /// A container that distributes its contents horizontally. - /// - /// This is an alias of an `iced_native` row with a default `Renderer`. - pub type Row<'a, Message> = - iced_winit::Row<'a, Message, iced_wgpu::Renderer>; -} - -#[doc(no_inline)] -pub use widget::*; - -/// A generic widget. -/// -/// This is an alias of an `iced_native` element with a default `Renderer`. -pub type Element<'a, Message> = - iced_winit::Element<'a, Message, iced_wgpu::Renderer>; diff --git a/src/native/executor.rs b/src/native/executor.rs deleted file mode 100644 index 68a1d280e2..0000000000 --- a/src/native/executor.rs +++ /dev/null @@ -1,23 +0,0 @@ -//! Choose your preferred executor to power your application. -pub use iced_winit::{executor::Null, Executor}; -use iced_winit::{executor::ThreadPool, futures}; - -/// The default cross-platform executor. -/// -/// - On native platforms, it will use a `ThreadPool`. -/// - On the Web, it will use `wasm-bindgen-futures::spawn_local`. -#[derive(Debug)] -pub struct Default(ThreadPool); - -impl Executor for Default { - fn new() -> Result { - Ok(Default(ThreadPool::new()?)) - } - - fn spawn( - &self, - future: impl futures::Future + Send + 'static, - ) { - self.0.spawn(future); - } -} diff --git a/src/web.rs b/src/web.rs deleted file mode 100644 index 31f1a6fc1a..0000000000 --- a/src/web.rs +++ /dev/null @@ -1 +0,0 @@ -pub use iced_web::*; diff --git a/src/widget.rs b/src/widget.rs new file mode 100644 index 0000000000..7d3a1cefe6 --- /dev/null +++ b/src/widget.rs @@ -0,0 +1,60 @@ +//! Display information and interactive controls in your application. +//! +//! # Re-exports +//! For convenience, the contents of this module are available at the root +//! module. Therefore, you can directly type: +//! +//! ``` +//! use iced::{button, Button}; +//! ``` +//! +//! # Stateful widgets +//! Some widgets need to keep track of __local state__. +//! +//! These widgets have their own module with a `State` type. For instance, a +//! [`TextInput`] has some [`text_input::State`]. +//! +//! [`TextInput`]: text_input/struct.TextInput.html +//! [`text_input::State`]: text_input/struct.State.html +#[cfg(not(target_arch = "wasm32"))] +mod platform { + pub use iced_wgpu::widget::*; + + pub mod image { + //! Display images in your user interface. + pub use iced_winit::image::{Handle, Image}; + } + + pub mod svg { + //! Display vector graphics in your user interface. + pub use iced_winit::svg::{Handle, Svg}; + } + + pub use iced_winit::Text; + + #[doc(no_inline)] + pub use { + button::Button, checkbox::Checkbox, container::Container, image::Image, + progress_bar::ProgressBar, radio::Radio, scrollable::Scrollable, + slider::Slider, svg::Svg, text_input::TextInput, + }; + + /// A container that distributes its contents vertically. + /// + /// This is an alias of an `iced_native` column with a default `Renderer`. + pub type Column<'a, Message> = + iced_winit::Column<'a, Message, iced_wgpu::Renderer>; + + /// A container that distributes its contents horizontally. + /// + /// This is an alias of an `iced_native` row with a default `Renderer`. + pub type Row<'a, Message> = + iced_winit::Row<'a, Message, iced_wgpu::Renderer>; +} + +#[cfg(target_arch = "wasm32")] +mod platform { + pub use iced_web::widget::*; +} + +pub use platform::*; diff --git a/web/src/lib.rs b/web/src/lib.rs index c44b99b5a5..b1bb80e3bb 100644 --- a/web/src/lib.rs +++ b/web/src/lib.rs @@ -72,14 +72,19 @@ pub use dodrio; pub use element::Element; pub use hasher::Hasher; pub use iced_core::{ - Align, Background, Color, Font, HorizontalAlignment, Length, + Align, Background, Color, Font, HorizontalAlignment, Length, Vector, VerticalAlignment, }; -pub use iced_futures::{futures, Command}; +pub use iced_futures::{executor, futures, Command}; pub use style::Style; pub use subscription::Subscription; + +#[doc(no_inline)] pub use widget::*; +#[doc(no_inline)] +pub use executor::Executor; + /// An interactive web application. /// /// This trait is the main entrypoint of Iced. Once implemented, you can run From 7cea7371150e6de28032827519936008592f112d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Mon, 20 Jan 2020 06:27:01 +0100 Subject: [PATCH 08/16] Package examples and remove `dev-dependencies` --- Cargo.toml | 29 ++++------ examples/bezier_tool/Cargo.toml | 12 ++++ .../src/main.rs} | 0 examples/counter/Cargo.toml | 9 +++ examples/{counter.rs => counter/src/main.rs} | 0 examples/custom_widget/Cargo.toml | 11 ++++ .../src/main.rs} | 0 examples/events/Cargo.toml | 10 ++++ examples/{events.rs => events/src/main.rs} | 0 examples/geometry/Cargo.toml | 11 ++++ .../{geometry.rs => geometry/src/main.rs} | 0 examples/pokedex/Cargo.toml | 14 +++++ examples/{pokedex.rs => pokedex/src/main.rs} | 4 +- examples/progress_bar/Cargo.toml | 9 +++ .../src/main.rs} | 0 examples/stopwatch/Cargo.toml | 12 ++++ .../{stopwatch.rs => stopwatch/src/main.rs} | 2 + examples/styling/Cargo.toml | 9 +++ examples/{styling.rs => styling/src/main.rs} | 0 examples/svg.rs | 54 ------------------ examples/svg/Cargo.toml | 9 +++ examples/{ => svg}/resources/tiger.svg | 0 examples/svg/src/main.rs | 37 ++++++++++++ examples/todos/Cargo.toml | 16 ++++++ examples/{resources => todos/fonts}/icons.ttf | Bin examples/{todos.rs => todos/src/main.rs} | 2 +- examples/tour/Cargo.toml | 13 +++++ .../{resources => tour/images}/ferris.png | Bin examples/{tour.rs => tour/src/main.rs} | 4 +- src/lib.rs | 4 +- 30 files changed, 193 insertions(+), 78 deletions(-) create mode 100644 examples/bezier_tool/Cargo.toml rename examples/{bezier_tool.rs => bezier_tool/src/main.rs} (100%) create mode 100644 examples/counter/Cargo.toml rename examples/{counter.rs => counter/src/main.rs} (100%) create mode 100644 examples/custom_widget/Cargo.toml rename examples/{custom_widget.rs => custom_widget/src/main.rs} (100%) create mode 100644 examples/events/Cargo.toml rename examples/{events.rs => events/src/main.rs} (100%) create mode 100644 examples/geometry/Cargo.toml rename examples/{geometry.rs => geometry/src/main.rs} (100%) create mode 100644 examples/pokedex/Cargo.toml rename examples/{pokedex.rs => pokedex/src/main.rs} (98%) create mode 100644 examples/progress_bar/Cargo.toml rename examples/{progress_bar.rs => progress_bar/src/main.rs} (100%) create mode 100644 examples/stopwatch/Cargo.toml rename examples/{stopwatch.rs => stopwatch/src/main.rs} (99%) create mode 100644 examples/styling/Cargo.toml rename examples/{styling.rs => styling/src/main.rs} (100%) delete mode 100644 examples/svg.rs create mode 100644 examples/svg/Cargo.toml rename examples/{ => svg}/resources/tiger.svg (100%) create mode 100644 examples/svg/src/main.rs create mode 100644 examples/todos/Cargo.toml rename examples/{resources => todos/fonts}/icons.ttf (100%) rename examples/{todos.rs => todos/src/main.rs} (99%) create mode 100644 examples/tour/Cargo.toml rename examples/{resources => tour/images}/ferris.png (100%) rename examples/{tour.rs => tour/src/main.rs} (99%) diff --git a/Cargo.toml b/Cargo.toml index 87f3000eec..28a97af9fc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -29,6 +29,18 @@ members = [ "web", "wgpu", "winit", + "examples/bezier_tool", + "examples/counter", + "examples/custom_widget", + "examples/events", + "examples/geometry", + "examples/pokedex", + "examples/progress_bar", + "examples/stopwatch", + "examples/styling", + "examples/svg", + "examples/todos", + "examples/tour", ] [target.'cfg(not(target_arch = "wasm32"))'.dependencies] @@ -37,20 +49,3 @@ iced_wgpu = { version = "0.1.0", path = "wgpu" } [target.'cfg(target_arch = "wasm32")'.dependencies] iced_web = { version = "0.1.0", path = "web" } - -[dev-dependencies] -iced_native = { version = "0.1", path = "./native" } -iced_wgpu = { version = "0.1", path = "./wgpu" } -iced_futures = { version = "0.1.0-alpha", path = "./futures", features = ["async-std"] } -env_logger = "0.7" -serde = { version = "1.0", features = ["derive"] } -serde_json = "1.0" -directories = "2.0" -futures = "0.3" -async-std = { version = "1.3", features = ["unstable"] } -surf = "1.0" -rand = "0.7" -lyon = "0.15" - -[target.'cfg(target_arch = "wasm32")'.dev-dependencies] -wasm-bindgen = "0.2.51" diff --git a/examples/bezier_tool/Cargo.toml b/examples/bezier_tool/Cargo.toml new file mode 100644 index 0000000000..b13a0aa54d --- /dev/null +++ b/examples/bezier_tool/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "bezier_tool" +version = "0.1.0" +authors = ["Héctor Ramón Jiménez "] +edition = "2018" +publish = false + +[dependencies] +iced = { path = "../.." } +iced_native = { path = "../../native" } +iced_wgpu = { path = "../../wgpu" } +lyon = "0.15" diff --git a/examples/bezier_tool.rs b/examples/bezier_tool/src/main.rs similarity index 100% rename from examples/bezier_tool.rs rename to examples/bezier_tool/src/main.rs diff --git a/examples/counter/Cargo.toml b/examples/counter/Cargo.toml new file mode 100644 index 0000000000..a763cd7812 --- /dev/null +++ b/examples/counter/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "counter" +version = "0.1.0" +authors = ["Héctor Ramón Jiménez "] +edition = "2018" +publish = false + +[dependencies] +iced = { path = "../.." } diff --git a/examples/counter.rs b/examples/counter/src/main.rs similarity index 100% rename from examples/counter.rs rename to examples/counter/src/main.rs diff --git a/examples/custom_widget/Cargo.toml b/examples/custom_widget/Cargo.toml new file mode 100644 index 0000000000..30747dc0da --- /dev/null +++ b/examples/custom_widget/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "custom_widget" +version = "0.1.0" +authors = ["Héctor Ramón Jiménez "] +edition = "2018" +publish = false + +[dependencies] +iced = { path = "../.." } +iced_native = { path = "../../native" } +iced_wgpu = { path = "../../wgpu" } diff --git a/examples/custom_widget.rs b/examples/custom_widget/src/main.rs similarity index 100% rename from examples/custom_widget.rs rename to examples/custom_widget/src/main.rs diff --git a/examples/events/Cargo.toml b/examples/events/Cargo.toml new file mode 100644 index 0000000000..f883075fea --- /dev/null +++ b/examples/events/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "events" +version = "0.1.0" +authors = ["Héctor Ramón Jiménez "] +edition = "2018" +publish = false + +[dependencies] +iced = { path = "../.." } +iced_native = { path = "../../native" } diff --git a/examples/events.rs b/examples/events/src/main.rs similarity index 100% rename from examples/events.rs rename to examples/events/src/main.rs diff --git a/examples/geometry/Cargo.toml b/examples/geometry/Cargo.toml new file mode 100644 index 0000000000..9df5245434 --- /dev/null +++ b/examples/geometry/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "geometry" +version = "0.1.0" +authors = ["Héctor Ramón Jiménez "] +edition = "2018" +publish = false + +[dependencies] +iced = { path = "../.." } +iced_native = { path = "../../native" } +iced_wgpu = { path = "../../wgpu" } diff --git a/examples/geometry.rs b/examples/geometry/src/main.rs similarity index 100% rename from examples/geometry.rs rename to examples/geometry/src/main.rs diff --git a/examples/pokedex/Cargo.toml b/examples/pokedex/Cargo.toml new file mode 100644 index 0000000000..2972590f0a --- /dev/null +++ b/examples/pokedex/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "pokedex" +version = "0.1.0" +authors = ["Héctor Ramón Jiménez "] +edition = "2018" +publish = false + +[dependencies] +iced = { path = "../.." } +iced_futures = { path = "../../futures", features = ["async-std"] } +surf = "1.0" +rand = "0.7" +serde = { version = "1.0", features = ["derive"] } +serde_json = "1.0" diff --git a/examples/pokedex.rs b/examples/pokedex/src/main.rs similarity index 98% rename from examples/pokedex.rs rename to examples/pokedex/src/main.rs index 505dbf1986..283437b245 100644 --- a/examples/pokedex.rs +++ b/examples/pokedex/src/main.rs @@ -1,6 +1,6 @@ use iced::{ - button, image, Align, Application, Button, Column, Command, Container, - Element, Image, Length, Row, Settings, Text, + button, futures, image, Align, Application, Button, Column, Command, + Container, Element, Image, Length, Row, Settings, Text, }; pub fn main() { diff --git a/examples/progress_bar/Cargo.toml b/examples/progress_bar/Cargo.toml new file mode 100644 index 0000000000..4eccbf1482 --- /dev/null +++ b/examples/progress_bar/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "progress_bar" +version = "0.1.0" +authors = ["Héctor Ramón Jiménez "] +edition = "2018" +publish = false + +[dependencies] +iced = { path = "../.." } diff --git a/examples/progress_bar.rs b/examples/progress_bar/src/main.rs similarity index 100% rename from examples/progress_bar.rs rename to examples/progress_bar/src/main.rs diff --git a/examples/stopwatch/Cargo.toml b/examples/stopwatch/Cargo.toml new file mode 100644 index 0000000000..1dae3b83dc --- /dev/null +++ b/examples/stopwatch/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "stopwatch" +version = "0.1.0" +authors = ["Héctor Ramón Jiménez "] +edition = "2018" +publish = false + +[dependencies] +iced = { path = "../.." } +iced_native = { path = "../../native" } +iced_futures = { path = "../../futures", features = ["async-std"] } +async-std = { version = "1.0", features = ["unstable"] } diff --git a/examples/stopwatch.rs b/examples/stopwatch/src/main.rs similarity index 99% rename from examples/stopwatch.rs rename to examples/stopwatch/src/main.rs index 6e35703981..d84c481744 100644 --- a/examples/stopwatch.rs +++ b/examples/stopwatch/src/main.rs @@ -143,6 +143,8 @@ impl Application for Stopwatch { } mod time { + use iced::futures; + pub fn every( duration: std::time::Duration, ) -> iced::Subscription { diff --git a/examples/styling/Cargo.toml b/examples/styling/Cargo.toml new file mode 100644 index 0000000000..eb729f9338 --- /dev/null +++ b/examples/styling/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "styling" +version = "0.1.0" +authors = ["Héctor Ramón Jiménez "] +edition = "2018" +publish = false + +[dependencies] +iced = { path = "../.." } diff --git a/examples/styling.rs b/examples/styling/src/main.rs similarity index 100% rename from examples/styling.rs rename to examples/styling/src/main.rs diff --git a/examples/svg.rs b/examples/svg.rs deleted file mode 100644 index 1895039df3..0000000000 --- a/examples/svg.rs +++ /dev/null @@ -1,54 +0,0 @@ -use iced::{Container, Element, Length, Sandbox, Settings}; - -pub fn main() { - Tiger::run(Settings::default()) -} - -#[derive(Default)] -struct Tiger; - -impl Sandbox for Tiger { - type Message = (); - - fn new() -> Self { - Self::default() - } - - fn title(&self) -> String { - String::from("SVG - Iced") - } - - fn update(&mut self, _message: ()) {} - - fn view(&mut self) -> Element<()> { - #[cfg(feature = "svg")] - let content = { - use iced::{Column, Svg}; - - Column::new().padding(20).push( - Svg::new(format!( - "{}/examples/resources/tiger.svg", - env!("CARGO_MANIFEST_DIR") - )) - .width(Length::Fill) - .height(Length::Fill), - ) - }; - - #[cfg(not(feature = "svg"))] - let content = { - use iced::{HorizontalAlignment, Text}; - - Text::new("You need to enable the `svg` feature!") - .horizontal_alignment(HorizontalAlignment::Center) - .size(30) - }; - - Container::new(content) - .width(Length::Fill) - .height(Length::Fill) - .center_x() - .center_y() - .into() - } -} diff --git a/examples/svg/Cargo.toml b/examples/svg/Cargo.toml new file mode 100644 index 0000000000..d8f83ac24a --- /dev/null +++ b/examples/svg/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "svg" +version = "0.1.0" +authors = ["Héctor Ramón Jiménez "] +edition = "2018" +publish = false + +[dependencies] +iced = { path = "../..", features = ["svg"] } diff --git a/examples/resources/tiger.svg b/examples/svg/resources/tiger.svg similarity index 100% rename from examples/resources/tiger.svg rename to examples/svg/resources/tiger.svg diff --git a/examples/svg/src/main.rs b/examples/svg/src/main.rs new file mode 100644 index 0000000000..57358e24eb --- /dev/null +++ b/examples/svg/src/main.rs @@ -0,0 +1,37 @@ +use iced::{Column, Container, Element, Length, Sandbox, Settings, Svg}; + +pub fn main() { + Tiger::run(Settings::default()) +} + +#[derive(Default)] +struct Tiger; + +impl Sandbox for Tiger { + type Message = (); + + fn new() -> Self { + Self::default() + } + + fn title(&self) -> String { + String::from("SVG - Iced") + } + + fn update(&mut self, _message: ()) {} + + fn view(&mut self) -> Element<()> { + let content = Column::new().padding(20).push( + Svg::new(format!("{}/tiger.svg", env!("CARGO_MANIFEST_DIR"))) + .width(Length::Fill) + .height(Length::Fill), + ); + + Container::new(content) + .width(Length::Fill) + .height(Length::Fill) + .center_x() + .center_y() + .into() + } +} diff --git a/examples/todos/Cargo.toml b/examples/todos/Cargo.toml new file mode 100644 index 0000000000..53a135e6b7 --- /dev/null +++ b/examples/todos/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "todos" +version = "0.1.0" +authors = ["Héctor Ramón Jiménez "] +edition = "2018" +publish = false + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +iced = { path = "../.." } +iced_futures = { path = "../../futures", features = ["async-std"] } +async-std = "1.0" +serde = { version = "1.0", features = ["derive"] } +serde_json = "1.0" +directories = "2.0" diff --git a/examples/resources/icons.ttf b/examples/todos/fonts/icons.ttf similarity index 100% rename from examples/resources/icons.ttf rename to examples/todos/fonts/icons.ttf diff --git a/examples/todos.rs b/examples/todos/src/main.rs similarity index 99% rename from examples/todos.rs rename to examples/todos/src/main.rs index 06595a1e47..c6ddf2eae2 100644 --- a/examples/todos.rs +++ b/examples/todos/src/main.rs @@ -451,7 +451,7 @@ fn empty_message(message: &str) -> Element<'static, Message> { // Fonts const ICONS: Font = Font::External { name: "Icons", - bytes: include_bytes!("resources/icons.ttf"), + bytes: include_bytes!("../fonts/icons.ttf"), }; fn icon(unicode: char) -> Text { diff --git a/examples/tour/Cargo.toml b/examples/tour/Cargo.toml new file mode 100644 index 0000000000..10c3f1dac4 --- /dev/null +++ b/examples/tour/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "tour" +version = "0.1.0" +authors = ["Héctor Ramón Jiménez "] +edition = "2018" +publish = false + +[dependencies] +iced = { path = "../.." } +env_logger = "0.7" + +[target.'cfg(target_arch = "wasm32")'.dependencies] +wasm-bindgen = "0.2.51" diff --git a/examples/resources/ferris.png b/examples/tour/images/ferris.png similarity index 100% rename from examples/resources/ferris.png rename to examples/tour/images/ferris.png diff --git a/examples/tour.rs b/examples/tour/src/main.rs similarity index 99% rename from examples/tour.rs rename to examples/tour/src/main.rs index b0ee4d966b..43c7e50f0a 100644 --- a/examples/tour.rs +++ b/examples/tour/src/main.rs @@ -681,10 +681,10 @@ fn ferris<'a>(width: u16) -> Container<'a, StepMessage> { // This should go away once we unify resource loading on native // platforms if cfg!(target_arch = "wasm32") { - Image::new("resources/ferris.png") + Image::new("images/ferris.png") } else { Image::new(format!( - "{}/examples/resources/ferris.png", + "{}/images/ferris.png", env!("CARGO_MANIFEST_DIR") )) } diff --git a/src/lib.rs b/src/lib.rs index 9c9bcff54b..1da3f54978 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -204,6 +204,6 @@ use iced_winit as common; use iced_web as common; pub use common::{ - Align, Background, Color, Command, Font, HorizontalAlignment, Length, - Space, Subscription, Vector, VerticalAlignment, + futures, Align, Background, Color, Command, Font, HorizontalAlignment, + Length, Space, Subscription, Vector, VerticalAlignment, }; From e2ec092aeca64a8107070d72c42c5805dc2c245b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Mon, 20 Jan 2020 06:40:20 +0100 Subject: [PATCH 09/16] Add `README` for `tour` example --- examples/README.md | 30 ------------------------------ examples/tour/Cargo.toml | 2 +- examples/tour/README.md | 28 ++++++++++++++++++++++++++++ web/README.md | 2 +- 4 files changed, 30 insertions(+), 32 deletions(-) create mode 100644 examples/tour/README.md diff --git a/examples/README.md b/examples/README.md index 95ec6c5cd4..a3abde5476 100644 --- a/examples/README.md +++ b/examples/README.md @@ -4,36 +4,6 @@ you want to learn about a specific release, check out [the release list]. [the release list]: https://github.com/hecrj/iced/releases -## [Tour](tour.rs) - -A simple UI tour that can run both on native platforms and the web! It showcases different widgets that can be built using Iced. - -The __[`tour`]__ file contains all the code of the example! All the cross-platform GUI is defined in terms of __state__, __messages__, __update logic__ and __view logic__. - - - -[`tour`]: tour.rs -[`iced_winit`]: ../winit -[`iced_native`]: ../native -[`iced_wgpu`]: ../wgpu -[`iced_web`]: ../web -[`winit`]: https://github.com/rust-windowing/winit -[`wgpu`]: https://github.com/gfx-rs/wgpu-rs - -You can run the native version with `cargo run`: -``` -cargo run --example tour -``` - -The web version can be run by following [the usage instructions of `iced_web`] or by accessing [iced.rs](https://iced.rs/)! - -[the usage instructions of `iced_web`]: ../web#usage - - ## [Todos](todos.rs) A simple todos tracker inspired by [TodoMVC]. It showcases dynamic layout, text input, checkboxes, scrollables, icons, and async actions! It automatically saves your tasks in the background, even if you did not finish typing them. diff --git a/examples/tour/Cargo.toml b/examples/tour/Cargo.toml index 10c3f1dac4..7772df1bef 100644 --- a/examples/tour/Cargo.toml +++ b/examples/tour/Cargo.toml @@ -6,7 +6,7 @@ edition = "2018" publish = false [dependencies] -iced = { path = "../.." } +iced = { path = "../..", features = ["debug"] } env_logger = "0.7" [target.'cfg(target_arch = "wasm32")'.dependencies] diff --git a/examples/tour/README.md b/examples/tour/README.md new file mode 100644 index 0000000000..f380931abb --- /dev/null +++ b/examples/tour/README.md @@ -0,0 +1,28 @@ +## Tour + +A simple UI tour that can run both on native platforms and the web! It showcases different widgets that can be built using Iced. + +The __[`main`]__ file contains all the code of the example! All the cross-platform GUI is defined in terms of __state__, __messages__, __update logic__ and __view logic__. + + + +[`main`]: src/main.rs +[`iced_winit`]: ../../winit +[`iced_native`]: ../../native +[`iced_wgpu`]: ../../wgpu +[`iced_web`]: ../../web +[`winit`]: https://github.com/rust-windowing/winit +[`wgpu`]: https://github.com/gfx-rs/wgpu-rs + +You can run the native version with `cargo run`: +``` +cargo run --package tour +``` + +The web version can be run by following [the usage instructions of `iced_web`] or by accessing [iced.rs](https://iced.rs/)! + +[the usage instructions of `iced_web`]: ../../web#usage diff --git a/web/README.md b/web/README.md index 6a3da7b454..cfd73320bc 100644 --- a/web/README.md +++ b/web/README.md @@ -35,7 +35,7 @@ For instance, let's say we want to build the [`tour` example]: ``` cd examples -cargo build --example tour --target wasm32-unknown-unknown +cargo build --package tour --target wasm32-unknown-unknown wasm-bindgen ../target/wasm32-unknown-unknown/debug/examples/tour.wasm --out-dir tour --web ``` From 6e784e29de71ca1f6ccefd37f4ce7257bbbb4e43 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Mon, 20 Jan 2020 07:21:56 +0100 Subject: [PATCH 10/16] Add `README` for `pokedex` example --- examples/pokedex/README.md | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 examples/pokedex/README.md diff --git a/examples/pokedex/README.md b/examples/pokedex/README.md new file mode 100644 index 0000000000..50720f5715 --- /dev/null +++ b/examples/pokedex/README.md @@ -0,0 +1,17 @@ +# Pokédex +An application that loads a random Pokédex entry using the [PokéAPI]. + +All the example code can be found in the __[`main`](src/main.rs)__ file. + + + +You can run it on native platforms with `cargo run`: +``` +cargo run --package pokedex +``` + +[PokéAPI]: https://pokeapi.co/ From 472269580038294b67106aad4e3d1c1117ab9d59 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Mon, 20 Jan 2020 07:22:10 +0100 Subject: [PATCH 11/16] Add `README` for `styling` example --- examples/styling/README.md | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 examples/styling/README.md diff --git a/examples/styling/README.md b/examples/styling/README.md new file mode 100644 index 0000000000..6c198a541d --- /dev/null +++ b/examples/styling/README.md @@ -0,0 +1,15 @@ +# Styling +An example showcasing custom styling with a light and dark theme. + +All the example code is located in the __[`main`](src/main.rs)__ file. + + + +You can run it with `cargo run`: +``` +cargo run --package styling +``` From fd36510807e663813b5fbb7551ae409da154d1cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Mon, 20 Jan 2020 07:36:59 +0100 Subject: [PATCH 12/16] Add `README` for `todos` example --- examples/todos/README.md | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 examples/todos/README.md diff --git a/examples/todos/README.md b/examples/todos/README.md new file mode 100644 index 0000000000..9c2598b95e --- /dev/null +++ b/examples/todos/README.md @@ -0,0 +1,20 @@ +## Todos + +A todos tracker inspired by [TodoMVC]. It showcases dynamic layout, text input, checkboxes, scrollables, icons, and async actions! It automatically saves your tasks in the background, even if you did not finish typing them. + +All the example code is located in the __[`main`]__ file. + + + +You can run the native version with `cargo run`: +``` +cargo run --package todos +``` +We have not yet implemented a `LocalStorage` version of the auto-save feature. Therefore, it does not work on web _yet_! + +[`main`]: src/main.rs +[TodoMVC]: http://todomvc.com/ From 03da887339e3d0590dc84238431b61c211f7cf7e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Mon, 20 Jan 2020 07:37:09 +0100 Subject: [PATCH 13/16] Update examples `README` --- examples/README.md | 90 ++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 84 insertions(+), 6 deletions(-) diff --git a/examples/README.md b/examples/README.md index a3abde5476..c7820f76af 100644 --- a/examples/README.md +++ b/examples/README.md @@ -4,11 +4,37 @@ you want to learn about a specific release, check out [the release list]. [the release list]: https://github.com/hecrj/iced/releases -## [Todos](todos.rs) +## [Tour](tour) +A simple UI tour that can run both on native platforms and the web! It showcases different widgets that can be built using Iced. -A simple todos tracker inspired by [TodoMVC]. It showcases dynamic layout, text input, checkboxes, scrollables, icons, and async actions! It automatically saves your tasks in the background, even if you did not finish typing them. +The __[`main`](tour/src/main.rs)__ file contains all the code of the example! All the cross-platform GUI is defined in terms of __state__, __messages__, __update logic__ and __view logic__. -All the example code is located in the __[`todos`]__ file. + + +[`iced_winit`]: ../winit +[`iced_native`]: ../native +[`iced_wgpu`]: ../wgpu +[`iced_web`]: ../web +[`winit`]: https://github.com/rust-windowing/winit +[`wgpu`]: https://github.com/gfx-rs/wgpu-rs + +You can run the native version with `cargo run`: +``` +cargo run --package tour +``` + +The web version can be run by following [the usage instructions of `iced_web`] or by accessing [iced.rs](https://iced.rs/)! + +[the usage instructions of `iced_web`]: ../web#usage + +## [Todos](todos) +A todos tracker inspired by [TodoMVC]. It showcases dynamic layout, text input, checkboxes, scrollables, icons, and async actions! It automatically saves your tasks in the background, even if you did not finish typing them. + +The example code is located in the __[`main`](todos/src/main.rs)__ file.
@@ -18,15 +44,67 @@ All the example code is located in the __[`todos`]__ file. You can run the native version with `cargo run`: ``` -cargo run --example todos +cargo run --package todos ``` We have not yet implemented a `LocalStorage` version of the auto-save feature. Therefore, it does not work on web _yet_! -[`todos`]: todos.rs [TodoMVC]: http://todomvc.com/ -## [Coffee] +## [Pokédex](pokedex) +An application that helps you learn about Pokémon! It performs an asynchronous HTTP request to the [PokéAPI] in order to load and display a random Pokédex entry (sprite included!). + +The example code can be found in the __[`main`](pokedex/src/main.rs)__ file. + + + +You can run it on native platforms with `cargo run`: +``` +cargo run --package pokedex +``` + +[PokéAPI]: https://pokeapi.co/ + +## [Styling](styling) +An example showcasing custom styling with a light and dark theme. + +The example code is located in the __[`main`](styling/src/main.rs)__ file. + + +You can run it with `cargo run`: +``` +cargo run --package styling +``` + +## Extras +A bunch of simpler examples exist: + +- [`bezier_tool`](bezier_tool), a Paint-like tool for drawing Bezier curves using [`lyon`]. +- [`counter`](counter), the classic counter example explained in the [`README`](../README.md). +- [`custom_widget`](custom_widget), a demonstration of how to build a custom widget that draws a circle. +- [`events`](events), a log of native events displayed using a conditional `Subscription`. +- [`geometry`](geometry), a custom widget showcasing how to draw geometry with the `Mesh2D` primitive in [`iced_wgpu`](../wgpu). +- [`progress_bar`](progress_bar), a simple progress bar that can be filled by using a slider. +- [`stopwatch`](stopwatch), a watch with start/stop and reset buttons showcasing how to listen to time. +- [`svg`](svg), an application that renders the [Ghostscript Tiger] by leveraging the `Svg` widget. + +All of them are packaged in their own crate and, therefore, can be run using `cargo`: +``` +cargo run --package +``` + +[`lyon`]: https://github.com/nical/lyon +[Ghostscript Tiger]: https://commons.wikimedia.org/wiki/File:Ghostscript_Tiger.svg + +## [Coffee] Since [Iced was born in May], it has been powering the user interfaces in [Coffee], an experimental 2D game engine. From f14009601e270e43bdf29b8f4842cf136fbbd8b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Mon, 20 Jan 2020 09:49:17 +0100 Subject: [PATCH 14/16] Write documentation for `iced_futures` --- core/src/lib.rs | 2 +- futures/src/executor.rs | 13 +++++++++ futures/src/executor/async_std.rs | 2 ++ futures/src/executor/null.rs | 2 ++ futures/src/executor/thread_pool.rs | 1 + futures/src/executor/tokio.rs | 1 + futures/src/lib.rs | 6 ++++ futures/src/runtime.rs | 45 +++++++++++++++++++++++++++++ futures/src/subscription.rs | 39 +++++++++++++------------ futures/src/subscription/tracker.rs | 36 +++++++++++++++++++++++ style/src/lib.rs | 4 +++ 11 files changed, 131 insertions(+), 20 deletions(-) diff --git a/core/src/lib.rs b/core/src/lib.rs index bec307ad73..5186332749 100644 --- a/core/src/lib.rs +++ b/core/src/lib.rs @@ -9,7 +9,7 @@ //! [Iced]: https://github.com/hecrj/iced //! [`iced_native`]: https://github.com/hecrj/iced/tree/master/native //! [`iced_web`]: https://github.com/hecrj/iced/tree/master/web -//#![deny(missing_docs)] +#![deny(missing_docs)] #![deny(missing_debug_implementations)] #![deny(unused_results)] #![deny(unsafe_code)] diff --git a/futures/src/executor.rs b/futures/src/executor.rs index b2ff043eca..c2b9cc72c4 100644 --- a/futures/src/executor.rs +++ b/futures/src/executor.rs @@ -29,13 +29,26 @@ pub use wasm_bindgen::WasmBindgen; use futures::Future; +/// A type that can run futures. pub trait Executor: Sized { + /// Creates a new [`Executor`]. + /// + /// [`Executor`]: trait.Executor.html fn new() -> Result where Self: Sized; + /// Spawns a future in the [`Executor`]. + /// + /// [`Executor`]: trait.Executor.html fn spawn(&self, future: impl Future + Send + 'static); + /// Runs the given closure inside the [`Executor`]. + /// + /// Some executors, like `tokio`, require some global state to be in place + /// before creating futures. This method can be leveraged to set up this + /// global state, call a function, restore the state, and obtain the result + /// of the call. fn enter(&self, f: impl FnOnce() -> R) -> R { f() } diff --git a/futures/src/executor/async_std.rs b/futures/src/executor/async_std.rs index b056b23d02..641dfbd243 100644 --- a/futures/src/executor/async_std.rs +++ b/futures/src/executor/async_std.rs @@ -2,6 +2,8 @@ use crate::Executor; use futures::Future; +/// A type representing the `async-std` runtime. +#[derive(Debug)] pub struct AsyncStd; impl Executor for AsyncStd { diff --git a/futures/src/executor/null.rs b/futures/src/executor/null.rs index 722073bb00..6d5cf9829f 100644 --- a/futures/src/executor/null.rs +++ b/futures/src/executor/null.rs @@ -2,6 +2,8 @@ use crate::Executor; use futures::Future; +/// An executor that drops all the futures, instead of spawning them. +#[derive(Debug)] pub struct Null; impl Executor for Null { diff --git a/futures/src/executor/thread_pool.rs b/futures/src/executor/thread_pool.rs index 6393d0d549..09cb4d2111 100644 --- a/futures/src/executor/thread_pool.rs +++ b/futures/src/executor/thread_pool.rs @@ -2,6 +2,7 @@ use crate::Executor; use futures::Future; +/// A thread pool for futures. pub type ThreadPool = futures::executor::ThreadPool; impl Executor for futures::executor::ThreadPool { diff --git a/futures/src/executor/tokio.rs b/futures/src/executor/tokio.rs index aafa7e7bda..4c609686bd 100644 --- a/futures/src/executor/tokio.rs +++ b/futures/src/executor/tokio.rs @@ -2,6 +2,7 @@ use crate::Executor; use futures::Future; +/// The `tokio` runtime. pub type Tokio = tokio::runtime::Runtime; impl Executor for Tokio { diff --git a/futures/src/lib.rs b/futures/src/lib.rs index 832a50f68f..4872df108a 100644 --- a/futures/src/lib.rs +++ b/futures/src/lib.rs @@ -1,3 +1,9 @@ +//! Asynchronous tasks for GUI programming, inspired by Elm. +#![deny(missing_docs)] +#![deny(missing_debug_implementations)] +#![deny(unused_results)] +#![deny(unsafe_code)] +#![deny(rust_2018_idioms)] pub use futures; mod command; diff --git a/futures/src/runtime.rs b/futures/src/runtime.rs index a508c46e92..9fd9899ae7 100644 --- a/futures/src/runtime.rs +++ b/futures/src/runtime.rs @@ -4,6 +4,15 @@ use crate::{subscription, Command, Executor, Subscription}; use futures::Sink; 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! +/// +/// [`Runtime`]: struct.Runtime.html +/// [`Executor`]: executor/trait.Executor.html +/// [`Command`]: struct.Command.html +/// [`Subscription`]: subscription/struct.Subscription.html #[derive(Debug)] pub struct Runtime { executor: Executor, @@ -25,6 +34,13 @@ where + 'static, Message: Send + 'static, { + /// Creates a new empty [`Runtime`]. + /// + /// You need to provide: + /// - an [`Executor`] to spawn futures + /// - a `Sender` implementing `Sink` to receive the results + /// + /// [`Runtime`]: struct.Runtime.html pub fn new(executor: Executor, sender: Sender) -> Self { Self { executor, @@ -34,10 +50,24 @@ where } } + /// Runs the given closure inside the [`Executor`] of the [`Runtime`]. + /// + /// See [`Executor::enter`] to learn more. + /// + /// [`Executor`]: executor/trait.Executor.html + /// [`Runtime`]: struct.Runtime.html + /// [`Executor::enter`]: executor/trait.Executor.html#method.enter pub fn enter(&self, f: impl FnOnce() -> R) -> R { self.executor.enter(f) } + /// Spawns a [`Command`] in the [`Runtime`]. + /// + /// The resulting `Message` will be forwarded to the `Sender` of the + /// [`Runtime`]. + /// + /// [`Command`]: struct.Command.html + /// [`Runtime`]: struct.Runtime.html pub fn spawn(&mut self, command: Command) { use futures::{FutureExt, SinkExt}; @@ -56,6 +86,14 @@ where } } + /// 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! + /// + /// [`Subscription`]: subscription/struct.Subscription.html + /// [`Runtime`]: struct.Runtime.html + /// [`Tracker::update`]: subscription/struct.Tracker.html#method.update pub fn track( &mut self, subscription: Subscription, @@ -68,6 +106,13 @@ where } } + /// Broadcasts an event to all the subscriptions currently alive in the + /// [`Runtime`]. + /// + /// See [`Tracker::broadcast`] to learn more. + /// + /// [`Runtime`]: struct.Runtime.html + /// [`Tracker::broadcast`]: subscription/struct.Tracker.html#method.broadcast pub fn broadcast(&mut self, event: Event) { self.subscriptions.broadcast(event); } diff --git a/futures/src/subscription.rs b/futures/src/subscription.rs index 87e51e4851..b68444cd34 100644 --- a/futures/src/subscription.rs +++ b/futures/src/subscription.rs @@ -16,16 +16,16 @@ use futures::stream::BoxStream; /// 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 +/// This type is normally aliased by runtimes with a specific `Event` and/or /// `Hasher`. /// /// [`Command`]: ../struct.Command.html /// [`Subscription`]: struct.Subscription.html -pub struct Subscription { - recipes: Vec>>, +pub struct Subscription { + recipes: Vec>>, } -impl Subscription +impl Subscription where H: std::hash::Hasher, { @@ -43,7 +43,7 @@ where /// [`Subscription`]: struct.Subscription.html /// [`Recipe`]: trait.Recipe.html pub fn from_recipe( - recipe: impl Recipe + 'static, + recipe: impl Recipe + 'static, ) -> Self { Self { recipes: vec![Box::new(recipe)], @@ -55,7 +55,7 @@ where /// /// [`Subscription`]: struct.Subscription.html pub fn batch( - subscriptions: impl IntoIterator>, + subscriptions: impl IntoIterator>, ) -> Self { Self { recipes: subscriptions @@ -68,7 +68,7 @@ where /// Returns the different recipes of the [`Subscription`]. /// /// [`Subscription`]: struct.Subscription.html - pub fn recipes(self) -> Vec>> { + pub fn recipes(self) -> Vec>> { self.recipes } @@ -78,10 +78,10 @@ where pub fn map( mut self, f: impl Fn(O) -> A + Send + Sync + 'static, - ) -> Subscription + ) -> Subscription where H: 'static, - I: 'static, + E: 'static, O: 'static, A: 'static, { @@ -93,7 +93,7 @@ where .drain(..) .map(|recipe| { Box::new(Map::new(recipe, function.clone())) - as Box> + as Box> }) .collect(), } @@ -114,7 +114,7 @@ impl std::fmt::Debug for Subscription { /// /// [`Subscription`]: struct.Subscription.html /// [`Recipe`]: trait.Recipe.html -pub trait Recipe { +pub trait Recipe { /// The events that will be produced by a [`Subscription`] with this /// [`Recipe`]. /// @@ -133,31 +133,32 @@ pub trait Recipe { /// Executes the [`Recipe`] and produces the stream of events of its /// [`Subscription`]. /// - /// It receives some generic `Input`, which is normally defined by runtimes. + /// It receives some stream of generic events, which is normally defined by + /// shells. /// /// [`Subscription`]: struct.Subscription.html /// [`Recipe`]: trait.Recipe.html fn stream( self: Box, - input: BoxStream<'static, Input>, + input: BoxStream<'static, Event>, ) -> BoxStream<'static, Self::Output>; } -struct Map { - recipe: Box>, +struct Map { + recipe: Box>, mapper: std::sync::Arc B + Send + Sync>, } -impl Map { +impl Map { fn new( - recipe: Box>, + recipe: Box>, mapper: std::sync::Arc B + Send + Sync + 'static>, ) -> Self { Map { recipe, mapper } } } -impl Recipe for Map +impl Recipe for Map where A: 'static, B: 'static, @@ -174,7 +175,7 @@ where fn stream( self: Box, - input: BoxStream<'static, I>, + input: BoxStream<'static, E>, ) -> futures::stream::BoxStream<'static, Self::Output> { use futures::StreamExt; diff --git a/futures/src/subscription/tracker.rs b/futures/src/subscription/tracker.rs index a942b6199d..c8a1ee18a1 100644 --- a/futures/src/subscription/tracker.rs +++ b/futures/src/subscription/tracker.rs @@ -4,6 +4,11 @@ use futures::{future::BoxFuture, sink::Sink}; use std::collections::HashMap; use std::marker::PhantomData; +/// A registry of subscription streams. +/// +/// 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. #[derive(Debug)] pub struct Tracker { subscriptions: HashMap>, @@ -21,6 +26,9 @@ where Hasher: std::hash::Hasher + Default, Event: 'static + Send + Clone, { + /// Creates a new empty [`Tracker`]. + /// + /// [`Tracker`]: struct.Tracker.html pub fn new() -> Self { Self { subscriptions: HashMap::new(), @@ -28,6 +36,26 @@ where } } + /// Updates the [`Tracker`] with the given [`Subscription`]. + /// + /// A [`Subscription`] can cause new streams to be spawned or old streams + /// to be closed. + /// + /// The [`Tracker`] keeps track of these streams between calls to this + /// method: + /// + /// - If the provided [`Subscription`] contains a new [`Recipe`] that is + /// currently not being run, it will spawn a new stream and keep it alive. + /// - On the other hand, if a [`Recipe`] is currently in execution and the + /// provided [`Subscription`] does not contain it anymore, then the + /// [`Tracker`] will close and drop the relevant stream. + /// + /// It returns a list of futures that need to be spawned to materialize + /// the [`Tracker`] changes. + /// + /// [`Tracker`]: struct.Tracker.html + /// [`Subscription`]: struct.Subscription.html + /// [`Recipe`]: trait.Recipe.html pub fn update( &mut self, subscription: Subscription, @@ -96,6 +124,14 @@ where futures } + /// Broadcasts an event to the subscriptions currently alive. + /// + /// A subscription's [`Recipe::stream`] always receives a stream of events + /// as input. This stream can be used by some subscription to listen to + /// shell events. + /// + /// This method publishes the given event to all the subscription streams + /// currently open. pub fn broadcast(&mut self, event: Event) { self.subscriptions .values_mut() diff --git a/style/src/lib.rs b/style/src/lib.rs index e0f56594ab..2c5977b557 100644 --- a/style/src/lib.rs +++ b/style/src/lib.rs @@ -1,3 +1,7 @@ +//! The styling library of Iced. +//! +//! It contains a set of styles and stylesheets for most of the built-in +//! widgets. pub mod button; pub mod checkbox; pub mod container; From 7bb6411dfc6797c567a96ff940fc72f3a6747ff4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Mon, 20 Jan 2020 10:39:17 +0100 Subject: [PATCH 15/16] Write documentation for `executor::WasmBindgen` --- futures/src/executor/wasm_bindgen.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/futures/src/executor/wasm_bindgen.rs b/futures/src/executor/wasm_bindgen.rs index 70a8ea8ea0..2a12a1c0cd 100644 --- a/futures/src/executor/wasm_bindgen.rs +++ b/futures/src/executor/wasm_bindgen.rs @@ -1,5 +1,6 @@ use crate::Executor; +/// A type representing a `wasm-bindgen-futures` runtime. #[derive(Debug)] pub struct WasmBindgen; From 91d9d65a03ce9b211e4043726e7424949d314325 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Mon, 20 Jan 2020 10:49:25 +0100 Subject: [PATCH 16/16] Improve consistency in executor documentation --- futures/src/executor/async_std.rs | 2 +- futures/src/executor/thread_pool.rs | 2 +- futures/src/executor/tokio.rs | 2 +- futures/src/executor/wasm_bindgen.rs | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/futures/src/executor/async_std.rs b/futures/src/executor/async_std.rs index 641dfbd243..27949e315c 100644 --- a/futures/src/executor/async_std.rs +++ b/futures/src/executor/async_std.rs @@ -2,7 +2,7 @@ use crate::Executor; use futures::Future; -/// A type representing the `async-std` runtime. +/// An `async-std` runtime. #[derive(Debug)] pub struct AsyncStd; diff --git a/futures/src/executor/thread_pool.rs b/futures/src/executor/thread_pool.rs index 09cb4d2111..1ec5bf6989 100644 --- a/futures/src/executor/thread_pool.rs +++ b/futures/src/executor/thread_pool.rs @@ -2,7 +2,7 @@ use crate::Executor; use futures::Future; -/// A thread pool for futures. +/// A thread pool runtime for futures. pub type ThreadPool = futures::executor::ThreadPool; impl Executor for futures::executor::ThreadPool { diff --git a/futures/src/executor/tokio.rs b/futures/src/executor/tokio.rs index 4c609686bd..20802cebfd 100644 --- a/futures/src/executor/tokio.rs +++ b/futures/src/executor/tokio.rs @@ -2,7 +2,7 @@ use crate::Executor; use futures::Future; -/// The `tokio` runtime. +/// A `tokio` runtime. pub type Tokio = tokio::runtime::Runtime; impl Executor for Tokio { diff --git a/futures/src/executor/wasm_bindgen.rs b/futures/src/executor/wasm_bindgen.rs index 2a12a1c0cd..69b7c7e2c8 100644 --- a/futures/src/executor/wasm_bindgen.rs +++ b/futures/src/executor/wasm_bindgen.rs @@ -1,6 +1,6 @@ use crate::Executor; -/// A type representing a `wasm-bindgen-futures` runtime. +/// A `wasm-bindgen-futures` runtime. #[derive(Debug)] pub struct WasmBindgen;