summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Cargo.toml29
-rw-r--r--core/Cargo.toml7
-rw-r--r--core/src/lib.rs12
-rw-r--r--examples/README.md74
-rw-r--r--examples/bezier_tool/Cargo.toml12
-rw-r--r--examples/bezier_tool/src/main.rs (renamed from examples/bezier_tool.rs)0
-rw-r--r--examples/counter/Cargo.toml9
-rw-r--r--examples/counter/src/main.rs (renamed from examples/counter.rs)0
-rw-r--r--examples/custom_widget/Cargo.toml11
-rw-r--r--examples/custom_widget/src/main.rs (renamed from examples/custom_widget.rs)0
-rw-r--r--examples/events/Cargo.toml10
-rw-r--r--examples/events/src/main.rs (renamed from examples/events.rs)5
-rw-r--r--examples/geometry/Cargo.toml11
-rw-r--r--examples/geometry/src/main.rs (renamed from examples/geometry.rs)0
-rw-r--r--examples/pokedex/Cargo.toml14
-rw-r--r--examples/pokedex/README.md17
-rw-r--r--examples/pokedex/src/main.rs (renamed from examples/pokedex.rs)5
-rw-r--r--examples/progress_bar/Cargo.toml9
-rw-r--r--examples/progress_bar/src/main.rs (renamed from examples/progress_bar.rs)0
-rw-r--r--examples/stopwatch/Cargo.toml12
-rw-r--r--examples/stopwatch/src/main.rs (renamed from examples/stopwatch.rs)5
-rw-r--r--examples/styling/Cargo.toml9
-rw-r--r--examples/styling/README.md15
-rw-r--r--examples/styling/src/main.rs (renamed from examples/styling.rs)0
-rw-r--r--examples/svg.rs54
-rw-r--r--examples/svg/Cargo.toml9
-rw-r--r--examples/svg/resources/tiger.svg (renamed from examples/resources/tiger.svg)0
-rw-r--r--examples/svg/src/main.rs37
-rw-r--r--examples/todos/Cargo.toml16
-rw-r--r--examples/todos/README.md20
-rw-r--r--examples/todos/fonts/icons.ttf (renamed from examples/resources/icons.ttf)bin5596 -> 5596 bytes
-rw-r--r--examples/todos/src/main.rs (renamed from examples/todos.rs)3
-rw-r--r--examples/tour/Cargo.toml13
-rw-r--r--examples/tour/README.md28
-rw-r--r--examples/tour/images/ferris.png (renamed from examples/resources/ferris.png)bin33061 -> 33061 bytes
-rw-r--r--examples/tour/src/main.rs (renamed from examples/tour.rs)4
-rw-r--r--futures/Cargo.toml32
-rw-r--r--futures/src/command.rs (renamed from core/src/command.rs)0
-rw-r--r--futures/src/executor.rs55
-rw-r--r--futures/src/executor/async_std.rs17
-rw-r--r--futures/src/executor/null.rs15
-rw-r--r--futures/src/executor/thread_pool.rs16
-rw-r--r--futures/src/executor/tokio.rs20
-rw-r--r--futures/src/executor/wasm_bindgen.rs18
-rw-r--r--futures/src/lib.rs18
-rw-r--r--futures/src/runtime.rs119
-rw-r--r--futures/src/subscription.rs (renamed from core/src/subscription.rs)46
-rw-r--r--futures/src/subscription/tracker.rs148
-rw-r--r--native/Cargo.toml11
-rw-r--r--native/src/lib.rs10
-rw-r--r--native/src/runtime.rs12
-rw-r--r--native/src/subscription.rs11
-rw-r--r--native/src/subscription/events.rs5
-rw-r--r--src/application.rs14
-rw-r--r--src/element.rs9
-rw-r--r--src/executor.rs54
-rw-r--r--src/lib.rs26
-rw-r--r--src/sandbox.rs3
-rw-r--r--src/web.rs1
-rw-r--r--src/widget.rs (renamed from src/native.rs)56
-rw-r--r--style/src/lib.rs4
-rw-r--r--web/Cargo.toml10
-rw-r--r--web/README.md2
-rw-r--r--web/src/lib.rs11
-rw-r--r--web/src/subscription.rs4
-rw-r--r--winit/Cargo.toml11
-rw-r--r--winit/src/application.rs61
-rw-r--r--winit/src/lib.rs3
-rw-r--r--winit/src/proxy.rs57
-rw-r--r--winit/src/subscription.rs97
70 files changed, 1097 insertions, 329 deletions
diff --git a/Cargo.toml b/Cargo.toml
index aeb8382e..28a97af9 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -23,11 +23,24 @@ maintenance = { status = "actively-developed" }
[workspace]
members = [
"core",
+ "futures",
"native",
"style",
"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]
@@ -36,19 +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" }
-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/core/Cargo.toml b/core/Cargo.toml
index 0a8fd8ef..22bc7ceb 100644
--- a/core/Cargo.toml
+++ b/core/Cargo.toml
@@ -7,11 +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"]
-
[dependencies]
-futures = { version = "0.3", optional = true }
diff --git a/core/src/lib.rs b/core/src/lib.rs
index 821b09c1..51863327 100644
--- a/core/src/lib.rs
+++ b/core/src/lib.rs
@@ -32,15 +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;
diff --git a/examples/README.md b/examples/README.md
index 95ec6c5c..c7820f76 100644
--- a/examples/README.md
+++ b/examples/README.md
@@ -4,11 +4,10 @@ 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)
-
+## [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.
-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__.
+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__.
<div align="center">
<a href="https://gfycat.com/politeadorableiberianmole">
@@ -16,7 +15,6 @@ The __[`tour`]__ file contains all the code of the example! All the cross-platfo
</a>
</div>
-[`tour`]: tour.rs
[`iced_winit`]: ../winit
[`iced_native`]: ../native
[`iced_wgpu`]: ../wgpu
@@ -26,19 +24,17 @@ The __[`tour`]__ file contains all the code of the example! All the cross-platfo
You can run the native version with `cargo run`:
```
-cargo run --example tour
+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.
-## [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.
-
-All the example code is located in the __[`todos`]__ file.
+The example code is located in the __[`main`](todos/src/main.rs)__ file.
<div align="center">
<a href="https://gfycat.com/littlesanehalicore">
@@ -48,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.
+
+<div align="center">
+ <a href="https://gfycat.com/aggressivedarkelephantseal-rust-gui">
+ <img src="https://thumbs.gfycat.com/AggressiveDarkElephantseal-small.gif" height="400px">
+ </a>
+</div>
+
+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.
+<div align="center">
+ <a href="https://user-images.githubusercontent.com/518289/71867993-acff4300-310c-11ea-85a3-d01d8f884346.gif">
+ <img src="https://user-images.githubusercontent.com/518289/71867993-acff4300-310c-11ea-85a3-d01d8f884346.gif" height="400px">
+ </a>
+</div>
+
+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 <example>
+```
+
+[`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.
diff --git a/examples/bezier_tool/Cargo.toml b/examples/bezier_tool/Cargo.toml
new file mode 100644
index 00000000..b13a0aa5
--- /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 <hector0193@gmail.com>"]
+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
index 043d265c..043d265c 100644
--- a/examples/bezier_tool.rs
+++ b/examples/bezier_tool/src/main.rs
diff --git a/examples/counter/Cargo.toml b/examples/counter/Cargo.toml
new file mode 100644
index 00000000..a763cd78
--- /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 <hector0193@gmail.com>"]
+edition = "2018"
+publish = false
+
+[dependencies]
+iced = { path = "../.." }
diff --git a/examples/counter.rs b/examples/counter/src/main.rs
index b85db70d..b85db70d 100644
--- a/examples/counter.rs
+++ b/examples/counter/src/main.rs
diff --git a/examples/custom_widget/Cargo.toml b/examples/custom_widget/Cargo.toml
new file mode 100644
index 00000000..30747dc0
--- /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 <hector0193@gmail.com>"]
+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
index 0a570745..0a570745 100644
--- a/examples/custom_widget.rs
+++ b/examples/custom_widget/src/main.rs
diff --git a/examples/events/Cargo.toml b/examples/events/Cargo.toml
new file mode 100644
index 00000000..f883075f
--- /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 <hector0193@gmail.com>"]
+edition = "2018"
+publish = false
+
+[dependencies]
+iced = { path = "../.." }
+iced_native = { path = "../../native" }
diff --git a/examples/events.rs b/examples/events/src/main.rs
index 74542171..0c9dca05 100644
--- a/examples/events.rs
+++ b/examples/events/src/main.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<Message>) {
diff --git a/examples/geometry/Cargo.toml b/examples/geometry/Cargo.toml
new file mode 100644
index 00000000..9df52454
--- /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 <hector0193@gmail.com>"]
+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
index 9d5fd611..9d5fd611 100644
--- a/examples/geometry.rs
+++ b/examples/geometry/src/main.rs
diff --git a/examples/pokedex/Cargo.toml b/examples/pokedex/Cargo.toml
new file mode 100644
index 00000000..2972590f
--- /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 <hector0193@gmail.com>"]
+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/README.md b/examples/pokedex/README.md
new file mode 100644
index 00000000..50720f57
--- /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.
+
+<div align="center">
+ <a href="https://gfycat.com/aggressivedarkelephantseal-rust-gui">
+ <img src="https://thumbs.gfycat.com/AggressiveDarkElephantseal-small.gif" height="400px">
+ </a>
+</div>
+
+You can run it on native platforms with `cargo run`:
+```
+cargo run --package pokedex
+```
+
+[PokéAPI]: https://pokeapi.co/
diff --git a/examples/pokedex.rs b/examples/pokedex/src/main.rs
index 7326f94f..283437b2 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() {
@@ -27,6 +27,7 @@ enum Message {
}
impl Application for Pokedex {
+ type Executor = iced_futures::executor::AsyncStd;
type Message = Message;
fn new() -> (Pokedex, Command<Message>) {
diff --git a/examples/progress_bar/Cargo.toml b/examples/progress_bar/Cargo.toml
new file mode 100644
index 00000000..4eccbf14
--- /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 <hector0193@gmail.com>"]
+edition = "2018"
+publish = false
+
+[dependencies]
+iced = { path = "../.." }
diff --git a/examples/progress_bar.rs b/examples/progress_bar/src/main.rs
index 43b09928..43b09928 100644
--- a/examples/progress_bar.rs
+++ b/examples/progress_bar/src/main.rs
diff --git a/examples/stopwatch/Cargo.toml b/examples/stopwatch/Cargo.toml
new file mode 100644
index 00000000..1dae3b83
--- /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 <hector0193@gmail.com>"]
+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
index c9a61ee9..d84c4817 100644
--- a/examples/stopwatch.rs
+++ b/examples/stopwatch/src/main.rs
@@ -28,6 +28,7 @@ enum Message {
}
impl Application for Stopwatch {
+ type Executor = iced_futures::executor::AsyncStd;
type Message = Message;
fn new() -> (Stopwatch, Command<Message>) {
@@ -142,6 +143,8 @@ impl Application for Stopwatch {
}
mod time {
+ use iced::futures;
+
pub fn every(
duration: std::time::Duration,
) -> iced::Subscription<std::time::Instant> {
@@ -165,7 +168,7 @@ mod time {
fn stream(
self: Box<Self>,
- _input: I,
+ _input: futures::stream::BoxStream<'static, I>,
) -> futures::stream::BoxStream<'static, Self::Output> {
use futures::stream::StreamExt;
diff --git a/examples/styling/Cargo.toml b/examples/styling/Cargo.toml
new file mode 100644
index 00000000..eb729f93
--- /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 <hector0193@gmail.com>"]
+edition = "2018"
+publish = false
+
+[dependencies]
+iced = { path = "../.." }
diff --git a/examples/styling/README.md b/examples/styling/README.md
new file mode 100644
index 00000000..6c198a54
--- /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.
+
+<div align="center">
+ <a href="https://user-images.githubusercontent.com/518289/71867993-acff4300-310c-11ea-85a3-d01d8f884346.gif">
+ <img src="https://user-images.githubusercontent.com/518289/71867993-acff4300-310c-11ea-85a3-d01d8f884346.gif" height="400px">
+ </a>
+</div>
+
+You can run it with `cargo run`:
+```
+cargo run --package styling
+```
diff --git a/examples/styling.rs b/examples/styling/src/main.rs
index 50095ec7..50095ec7 100644
--- a/examples/styling.rs
+++ b/examples/styling/src/main.rs
diff --git a/examples/svg.rs b/examples/svg.rs
deleted file mode 100644
index 1895039d..00000000
--- 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 00000000..d8f83ac2
--- /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 <hector0193@gmail.com>"]
+edition = "2018"
+publish = false
+
+[dependencies]
+iced = { path = "../..", features = ["svg"] }
diff --git a/examples/resources/tiger.svg b/examples/svg/resources/tiger.svg
index 679edec2..679edec2 100644
--- a/examples/resources/tiger.svg
+++ b/examples/svg/resources/tiger.svg
diff --git a/examples/svg/src/main.rs b/examples/svg/src/main.rs
new file mode 100644
index 00000000..57358e24
--- /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 00000000..53a135e6
--- /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 <hector0193@gmail.com>"]
+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/todos/README.md b/examples/todos/README.md
new file mode 100644
index 00000000..9c2598b9
--- /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.
+
+<div align="center">
+ <a href="https://gfycat.com/littlesanehalicore">
+ <img src="https://thumbs.gfycat.com/LittleSaneHalicore-small.gif" height="400px">
+ </a>
+</div>
+
+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/
diff --git a/examples/resources/icons.ttf b/examples/todos/fonts/icons.ttf
index 4498299d..4498299d 100644
--- a/examples/resources/icons.ttf
+++ b/examples/todos/fonts/icons.ttf
Binary files differ
diff --git a/examples/todos.rs b/examples/todos/src/main.rs
index 4166f75a..c6ddf2ea 100644
--- a/examples/todos.rs
+++ b/examples/todos/src/main.rs
@@ -38,6 +38,7 @@ enum Message {
}
impl Application for Todos {
+ type Executor = iced_futures::executor::AsyncStd;
type Message = Message;
fn new() -> (Todos, Command<Message>) {
@@ -450,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 00000000..7772df1b
--- /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 <hector0193@gmail.com>"]
+edition = "2018"
+publish = false
+
+[dependencies]
+iced = { path = "../..", features = ["debug"] }
+env_logger = "0.7"
+
+[target.'cfg(target_arch = "wasm32")'.dependencies]
+wasm-bindgen = "0.2.51"
diff --git a/examples/tour/README.md b/examples/tour/README.md
new file mode 100644
index 00000000..f380931a
--- /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__.
+
+<div align="center">
+ <a href="https://gfycat.com/politeadorableiberianmole">
+ <img src="https://thumbs.gfycat.com/PoliteAdorableIberianmole-small.gif">
+ </a>
+</div>
+
+[`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/examples/resources/ferris.png b/examples/tour/images/ferris.png
index ebce1a14..ebce1a14 100644
--- a/examples/resources/ferris.png
+++ b/examples/tour/images/ferris.png
Binary files differ
diff --git a/examples/tour.rs b/examples/tour/src/main.rs
index b0ee4d96..43c7e50f 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/futures/Cargo.toml b/futures/Cargo.toml
new file mode 100644
index 00000000..91860e1e
--- /dev/null
+++ b/futures/Cargo.toml
@@ -0,0 +1,32 @@
+[package]
+name = "iced_futures"
+version = "0.1.0-alpha"
+authors = ["Héctor Ramón Jiménez <hector0193@gmail.com>"]
+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"]
+
+[features]
+thread-pool = ["futures/thread-pool"]
+
+[dependencies]
+log = "0.4"
+
+[dependencies.futures]
+version = "0.3"
+
+[dependencies.tokio]
+version = "0.2"
+optional = true
+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/core/src/command.rs b/futures/src/command.rs
index e7885fb8..e7885fb8 100644
--- a/core/src/command.rs
+++ b/futures/src/command.rs
diff --git a/futures/src/executor.rs b/futures/src/executor.rs
new file mode 100644
index 00000000..c2b9cc72
--- /dev/null
+++ b/futures/src/executor.rs
@@ -0,0 +1,55 @@
+//! Choose your preferred executor to power a runtime.
+mod null;
+
+#[cfg(feature = "thread-pool")]
+mod thread_pool;
+
+#[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;
+
+/// A type that can run futures.
+pub trait Executor: Sized {
+ /// Creates a new [`Executor`].
+ ///
+ /// [`Executor`]: trait.Executor.html
+ fn new() -> Result<Self, futures::io::Error>
+ where
+ Self: Sized;
+
+ /// Spawns a future in the [`Executor`].
+ ///
+ /// [`Executor`]: trait.Executor.html
+ fn spawn(&self, future: impl Future<Output = ()> + 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<R>(&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 00000000..27949e31
--- /dev/null
+++ b/futures/src/executor/async_std.rs
@@ -0,0 +1,17 @@
+use crate::Executor;
+
+use futures::Future;
+
+/// An `async-std` runtime.
+#[derive(Debug)]
+pub struct AsyncStd;
+
+impl Executor for AsyncStd {
+ fn new() -> Result<Self, futures::io::Error> {
+ Ok(Self)
+ }
+
+ fn spawn(&self, future: impl Future<Output = ()> + 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 00000000..6d5cf982
--- /dev/null
+++ b/futures/src/executor/null.rs
@@ -0,0 +1,15 @@
+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 {
+ fn new() -> Result<Self, futures::io::Error> {
+ Ok(Self)
+ }
+
+ fn spawn(&self, _future: impl Future<Output = ()> + Send + 'static) {}
+}
diff --git a/futures/src/executor/thread_pool.rs b/futures/src/executor/thread_pool.rs
new file mode 100644
index 00000000..1ec5bf69
--- /dev/null
+++ b/futures/src/executor/thread_pool.rs
@@ -0,0 +1,16 @@
+use crate::Executor;
+
+use futures::Future;
+
+/// A thread pool runtime for futures.
+pub type ThreadPool = futures::executor::ThreadPool;
+
+impl Executor for futures::executor::ThreadPool {
+ fn new() -> Result<Self, futures::io::Error> {
+ futures::executor::ThreadPool::new()
+ }
+
+ fn spawn(&self, future: impl Future<Output = ()> + 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 00000000..20802ceb
--- /dev/null
+++ b/futures/src/executor/tokio.rs
@@ -0,0 +1,20 @@
+use crate::Executor;
+
+use futures::Future;
+
+/// A `tokio` runtime.
+pub type Tokio = tokio::runtime::Runtime;
+
+impl Executor for Tokio {
+ fn new() -> Result<Self, futures::io::Error> {
+ tokio::runtime::Runtime::new()
+ }
+
+ fn spawn(&self, future: impl Future<Output = ()> + Send + 'static) {
+ let _ = tokio::runtime::Runtime::spawn(self, future);
+ }
+
+ fn enter<R>(&self, f: impl FnOnce() -> R) -> R {
+ tokio::runtime::Runtime::enter(self, f)
+ }
+}
diff --git a/futures/src/executor/wasm_bindgen.rs b/futures/src/executor/wasm_bindgen.rs
new file mode 100644
index 00000000..69b7c7e2
--- /dev/null
+++ b/futures/src/executor/wasm_bindgen.rs
@@ -0,0 +1,18 @@
+use crate::Executor;
+
+/// A `wasm-bindgen-futures` runtime.
+#[derive(Debug)]
+pub struct WasmBindgen;
+
+impl Executor for WasmBindgen {
+ fn new() -> Result<Self, futures::io::Error> {
+ Ok(Self)
+ }
+
+ fn spawn(
+ &self,
+ future: impl futures::Future<Output = ()> + Send + 'static,
+ ) {
+ wasm_bindgen_futures::spawn_local(future);
+ }
+}
diff --git a/futures/src/lib.rs b/futures/src/lib.rs
new file mode 100644
index 00000000..4872df10
--- /dev/null
+++ b/futures/src/lib.rs
@@ -0,0 +1,18 @@
+//! 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;
+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
new file mode 100644
index 00000000..9fd9899a
--- /dev/null
+++ b/futures/src/runtime.rs
@@ -0,0 +1,119 @@
+//! Run commands and keep track of subscriptions.
+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<Hasher, Event, Executor, Sender, Message> {
+ executor: Executor,
+ sender: Sender,
+ subscriptions: subscription::Tracker<Hasher, Event>,
+ _message: PhantomData<Message>,
+}
+
+impl<Hasher, Event, Executor, Sender, Message>
+ Runtime<Hasher, Event, Executor, Sender, Message>
+where
+ Hasher: std::hash::Hasher + Default,
+ Event: Send + Clone + 'static,
+ Executor: self::Executor,
+ Sender: Sink<Message, Error = core::convert::Infallible>
+ + Unpin
+ + Send
+ + Clone
+ + '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,
+ sender,
+ subscriptions: subscription::Tracker::new(),
+ _message: PhantomData,
+ }
+ }
+
+ /// 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<R>(&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<Message>) {
+ use futures::{FutureExt, SinkExt};
+
+ let futures = command.futures();
+
+ for future in futures {
+ let mut sender = self.sender.clone();
+
+ self.executor.spawn(future.then(|message| {
+ async move {
+ let _ = sender.send(message).await;
+
+ ()
+ }
+ }));
+ }
+ }
+
+ /// 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<Hasher, Event, Message>,
+ ) {
+ let futures =
+ self.subscriptions.update(subscription, self.sender.clone());
+
+ for future in futures {
+ self.executor.spawn(future);
+ }
+ }
+
+ /// 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/core/src/subscription.rs b/futures/src/subscription.rs
index d9e7e388..b68444cd 100644
--- a/core/src/subscription.rs
+++ b/futures/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.
///
@@ -11,16 +16,16 @@
/// 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<Hasher, Input, Output> {
- recipes: Vec<Box<dyn Recipe<Hasher, Input, Output = Output>>>,
+pub struct Subscription<Hasher, Event, Output> {
+ recipes: Vec<Box<dyn Recipe<Hasher, Event, Output = Output>>>,
}
-impl<H, I, O> Subscription<H, I, O>
+impl<H, E, O> Subscription<H, E, O>
where
H: std::hash::Hasher,
{
@@ -38,7 +43,7 @@ where
/// [`Subscription`]: struct.Subscription.html
/// [`Recipe`]: trait.Recipe.html
pub fn from_recipe(
- recipe: impl Recipe<H, I, Output = O> + 'static,
+ recipe: impl Recipe<H, E, Output = O> + 'static,
) -> Self {
Self {
recipes: vec![Box::new(recipe)],
@@ -50,7 +55,7 @@ where
///
/// [`Subscription`]: struct.Subscription.html
pub fn batch(
- subscriptions: impl IntoIterator<Item = Subscription<H, I, O>>,
+ subscriptions: impl IntoIterator<Item = Subscription<H, E, O>>,
) -> Self {
Self {
recipes: subscriptions
@@ -63,7 +68,7 @@ where
/// Returns the different recipes of the [`Subscription`].
///
/// [`Subscription`]: struct.Subscription.html
- pub fn recipes(self) -> Vec<Box<dyn Recipe<H, I, Output = O>>> {
+ pub fn recipes(self) -> Vec<Box<dyn Recipe<H, E, Output = O>>> {
self.recipes
}
@@ -73,10 +78,10 @@ where
pub fn map<A>(
mut self,
f: impl Fn(O) -> A + Send + Sync + 'static,
- ) -> Subscription<H, I, A>
+ ) -> Subscription<H, E, A>
where
H: 'static,
- I: 'static,
+ E: 'static,
O: 'static,
A: 'static,
{
@@ -88,7 +93,7 @@ where
.drain(..)
.map(|recipe| {
Box::new(Map::new(recipe, function.clone()))
- as Box<dyn Recipe<H, I, Output = A>>
+ as Box<dyn Recipe<H, E, Output = A>>
})
.collect(),
}
@@ -109,7 +114,7 @@ impl<I, O, H> std::fmt::Debug for Subscription<I, O, H> {
///
/// [`Subscription`]: struct.Subscription.html
/// [`Recipe`]: trait.Recipe.html
-pub trait Recipe<Hasher: std::hash::Hasher, Input> {
+pub trait Recipe<Hasher: std::hash::Hasher, Event> {
/// The events that will be produced by a [`Subscription`] with this
/// [`Recipe`].
///
@@ -128,31 +133,32 @@ pub trait Recipe<Hasher: std::hash::Hasher, Input> {
/// 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<Self>,
- input: Input,
- ) -> futures::stream::BoxStream<'static, Self::Output>;
+ input: BoxStream<'static, Event>,
+ ) -> BoxStream<'static, Self::Output>;
}
-struct Map<Hasher, Input, A, B> {
- recipe: Box<dyn Recipe<Hasher, Input, Output = A>>,
+struct Map<Hasher, Event, A, B> {
+ recipe: Box<dyn Recipe<Hasher, Event, Output = A>>,
mapper: std::sync::Arc<dyn Fn(A) -> B + Send + Sync>,
}
-impl<H, I, A, B> Map<H, I, A, B> {
+impl<H, E, A, B> Map<H, E, A, B> {
fn new(
- recipe: Box<dyn Recipe<H, I, Output = A>>,
+ recipe: Box<dyn Recipe<H, E, Output = A>>,
mapper: std::sync::Arc<dyn Fn(A) -> B + Send + Sync + 'static>,
) -> Self {
Map { recipe, mapper }
}
}
-impl<H, I, A, B> Recipe<H, I> for Map<H, I, A, B>
+impl<H, E, A, B> Recipe<H, E> for Map<H, E, A, B>
where
A: 'static,
B: 'static,
@@ -169,7 +175,7 @@ where
fn stream(
self: Box<Self>,
- input: 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
new file mode 100644
index 00000000..c8a1ee18
--- /dev/null
+++ b/futures/src/subscription/tracker.rs
@@ -0,0 +1,148 @@
+use crate::Subscription;
+
+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<Hasher, Event> {
+ subscriptions: HashMap<u64, Execution<Event>>,
+ _hasher: PhantomData<Hasher>,
+}
+
+#[derive(Debug)]
+pub struct Execution<Event> {
+ _cancel: futures::channel::oneshot::Sender<()>,
+ listener: Option<futures::channel::mpsc::Sender<Event>>,
+}
+
+impl<Hasher, Event> Tracker<Hasher, Event>
+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(),
+ _hasher: PhantomData,
+ }
+ }
+
+ /// 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<Message, Receiver>(
+ &mut self,
+ subscription: Subscription<Hasher, Event, Message>,
+ receiver: Receiver,
+ ) -> Vec<BoxFuture<'static, ()>>
+ where
+ Message: 'static + Send,
+ Receiver: 'static
+ + Sink<Message, Error = core::convert::Infallible>
+ + 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(receiver.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
+ }
+
+ /// 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()
+ .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/native/Cargo.toml b/native/Cargo.toml
index a31b6627..6276535e 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"
+features = ["thread-pool"]
diff --git a/native/src/lib.rs b/native/src/lib.rs
index 340b9ea7..b5856c00 100644
--- a/native/src/lib.rs
+++ b/native/src/lib.rs
@@ -51,13 +51,18 @@ mod element;
mod event;
mod hasher;
mod mouse_cursor;
+mod runtime;
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::{executor, futures, Command};
+
+#[doc(no_inline)]
+pub use executor::Executor;
pub use clipboard::Clipboard;
pub use element::Element;
@@ -66,6 +71,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 00000000..9fa031f4
--- /dev/null
+++ b/native/src/runtime.rs
@@ -0,0 +1,12 @@
+//! 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<Executor, Receiver, Message> =
+ iced_futures::Runtime<Hasher, Event, Executor, Receiver, Message>;
diff --git a/native/src/subscription.rs b/native/src/subscription.rs
index db88867a..0d002c6c 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.
///
@@ -15,7 +15,7 @@ use futures::stream::BoxStream;
///
/// [`Command`]: ../struct.Command.html
/// [`Subscription`]: struct.Subscription.html
-pub type Subscription<T> = iced_core::Subscription<Hasher, EventStream, T>;
+pub type Subscription<T> = iced_futures::Subscription<Hasher, Event, T>;
/// A stream of runtime events.
///
@@ -24,7 +24,12 @@ pub type Subscription<T> = iced_core::Subscription<Hasher, EventStream, T>;
/// [`Subscription`]: type.Subscription.html
pub type EventStream = BoxStream<'static, Event>;
-pub use iced_core::subscription::Recipe;
+/// A native [`Subscription`] tracker.
+///
+/// [`Subscription`]: type.Subscription.html
+pub type Tracker = iced_futures::subscription::Tracker<Hasher, Event>;
+
+pub use iced_futures::subscription::Recipe;
mod events;
diff --git a/native/src/subscription/events.rs b/native/src/subscription/events.rs
index b7301828..7d33166e 100644
--- a/native/src/subscription/events.rs
+++ b/native/src/subscription/events.rs
@@ -2,10 +2,11 @@ use crate::{
subscription::{EventStream, Recipe},
Event, Hasher,
};
+use iced_futures::futures::stream::BoxStream;
pub struct Events;
-impl Recipe<Hasher, EventStream> for Events {
+impl Recipe<Hasher, Event> for Events {
type Output = Event;
fn hash(&self, state: &mut Hasher) {
@@ -17,7 +18,7 @@ impl Recipe<Hasher, EventStream> for Events {
fn stream(
self: Box<Self>,
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 b940cc17..3a526f1b 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<Message>) {
@@ -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<A::Message>) {
diff --git a/src/element.rs b/src/element.rs
new file mode 100644
index 00000000..e5356fb6
--- /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 00000000..cbbd8283
--- /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<Self, futures::io::Error> {
+ Ok(Default(ThreadPool::new()?))
+ }
+
+ fn spawn(
+ &self,
+ future: impl futures::Future<Output = ()> + 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<Self, futures::io::Error> {
+ Ok(Default(WasmBindgen::new()?))
+ }
+
+ fn spawn(
+ &self,
+ future: impl futures::Future<Output = ()> + Send + 'static,
+ ) {
+ self.0.spawn(future);
+ }
+ }
+}
diff --git a/src/lib.rs b/src/lib.rs
index 759dea2f..1da3f549 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -180,18 +180,30 @@
#![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 element;
mod sandbox;
+pub mod executor;
pub mod settings;
+pub mod widget;
pub mod window;
+#[doc(no_inline)]
+pub use widget::*;
+
pub use application::Application;
-pub use platform::*;
+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::{
+ futures, Align, Background, Color, Command, Font, HorizontalAlignment,
+ Length, Space, Subscription, Vector, VerticalAlignment,
+};
diff --git a/src/sandbox.rs b/src/sandbox.rs
index dda4c3f5..2c0332ff 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<T> Application for T
where
T: Sandbox,
{
+ type Executor = executor::Null;
type Message = T::Message;
fn new() -> (Self, Command<T::Message>) {
diff --git a/src/web.rs b/src/web.rs
deleted file mode 100644
index 31f1a6fc..00000000
--- a/src/web.rs
+++ /dev/null
@@ -1 +0,0 @@
-pub use iced_web::*;
diff --git a/src/native.rs b/src/widget.rs
index 35441a3e..7d3a1cef 100644
--- a/src/native.rs
+++ b/src/widget.rs
@@ -1,27 +1,23 @@
-pub use iced_winit::{
- Align, Background, Color, Command, Font, HorizontalAlignment, Length,
- Space, Subscription, Vector, VerticalAlignment,
-};
-
-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
+//! 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 {
@@ -56,11 +52,9 @@ pub mod widget {
iced_winit::Row<'a, Message, iced_wgpu::Renderer>;
}
-#[doc(no_inline)]
-pub use widget::*;
+#[cfg(target_arch = "wasm32")]
+mod platform {
+ pub use iced_web::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>;
+pub use platform::*;
diff --git a/style/src/lib.rs b/style/src/lib.rs
index e0f56594..2c5977b5 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;
diff --git a/web/Cargo.toml b/web/Cargo.toml
index 605c7462..46953863 100644
--- a/web/Cargo.toml
+++ b/web/Cargo.toml
@@ -15,11 +15,17 @@ 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"
diff --git a/web/README.md b/web/README.md
index 6a3da7b4..cfd73320 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
```
diff --git a/web/src/lib.rs b/web/src/lib.rs
index 7ea22e85..b1bb80e3 100644
--- a/web/src/lib.rs
+++ b/web/src/lib.rs
@@ -72,13 +72,19 @@ 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, Vector,
VerticalAlignment,
};
+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
@@ -148,7 +154,6 @@ pub trait Application {
}
}
-
struct Instance<Message> {
title: String,
ui: Rc<RefCell<Box<dyn Application<Message = Message>>>>,
@@ -167,7 +172,7 @@ impl<Message> Clone for Instance<Message> {
impl<Message> Instance<Message>
where
- Message: 'static
+ Message: 'static,
{
fn new(ui: impl Application<Message = Message> + 'static) -> Self {
Self {
diff --git a/web/src/subscription.rs b/web/src/subscription.rs
index 4638c8ab..6b8415c0 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<T> = iced_core::Subscription<Hasher, (), T>;
+pub type Subscription<T> = iced_futures::Subscription<Hasher, (), T>;
-pub use iced_core::subscription::Recipe;
+pub use iced_futures::subscription::Recipe;
diff --git a/winit/Cargo.toml b/winit/Cargo.toml
index 5727f8cf..cef41e9c 100644
--- a/winit/Cargo.toml
+++ b/winit/Cargo.toml
@@ -14,11 +14,16 @@ 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"
+[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 a14924ac..4b21a930 100644
--- a/winit/src/application.rs
+++ b/winit/src/application.rs
@@ -1,8 +1,8 @@
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, Executor, Mode,
+ MouseCursor, Proxy, Runtime, Settings, Size, Subscription, UserInterface,
};
/// An interactive, native cross-platform application.
@@ -19,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
@@ -109,17 +114,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 external_messages = Vec::new();
+ let mut runtime = {
+ let executor = Self::Executor::new().expect("Create executor");
+
+ Runtime::new(executor, Proxy::new(event_loop.create_proxy()))
+ };
+
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 +219,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 +248,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 +468,6 @@ fn to_physical(size: winit::dpi::LogicalSize, dpi: f64) -> (u16, u16) {
)
}
-fn spawn<Message: Send>(
- command: Command<Message>,
- thread_pool: &mut futures::executor::ThreadPool,
- proxy: &winit::event_loop::EventLoopProxy<Message>,
-) {
- 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 9000f977..056ae8f0 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 00000000..cff9df33
--- /dev/null
+++ b/winit/src/proxy.rs
@@ -0,0 +1,57 @@
+use iced_native::futures::{
+ task::{Context, Poll},
+ Sink,
+};
+use std::pin::Pin;
+
+pub struct Proxy<Message: 'static> {
+ raw: winit::event_loop::EventLoopProxy<Message>,
+}
+
+impl<Message: 'static> Clone for Proxy<Message> {
+ fn clone(&self) -> Self {
+ Self {
+ raw: self.raw.clone(),
+ }
+ }
+}
+
+impl<Message: 'static> Proxy<Message> {
+ pub fn new(raw: winit::event_loop::EventLoopProxy<Message>) -> Self {
+ Self { raw }
+ }
+}
+
+impl<Message: 'static> Sink<Message> for Proxy<Message> {
+ type Error = core::convert::Infallible;
+
+ fn poll_ready(
+ self: Pin<&mut Self>,
+ _cx: &mut Context<'_>,
+ ) -> Poll<Result<(), Self::Error>> {
+ 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<Result<(), Self::Error>> {
+ Poll::Ready(Ok(()))
+ }
+
+ fn poll_close(
+ self: Pin<&mut Self>,
+ _cx: &mut Context<'_>,
+ ) -> Poll<Result<(), Self::Error>> {
+ Poll::Ready(Ok(()))
+ }
+}
diff --git a/winit/src/subscription.rs b/winit/src/subscription.rs
deleted file mode 100644
index bad68d55..00000000
--- 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<u64, Handle>,
-}
-
-pub struct Handle {
- _cancel: futures::channel::oneshot::Sender<()>,
- listener: Option<futures::channel::mpsc::Sender<Event>>,
-}
-
-impl Pool {
- pub fn new() -> Self {
- Self {
- alive: HashMap::new(),
- }
- }
-
- pub fn update<Message: Send>(
- &mut self,
- subscription: Subscription<Message>,
- thread_pool: &mut futures::executor::ThreadPool,
- proxy: &winit::event_loop::EventLoopProxy<Message>,
- ) {
- 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
- );
- }
- });
- }
-}