Merge pull request #164 from hecrj/feature/custom-runtime
Custom futures executor with `iced_futures`
This commit is contained in:
commit
7016221556
29
Cargo.toml
29
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"
|
||||
|
@ -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 }
|
||||
|
@ -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;
|
||||
|
@ -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.
|
||||
|
||||
|
12
examples/bezier_tool/Cargo.toml
Normal file
12
examples/bezier_tool/Cargo.toml
Normal file
@ -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"
|
9
examples/counter/Cargo.toml
Normal file
9
examples/counter/Cargo.toml
Normal file
@ -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 = "../.." }
|
11
examples/custom_widget/Cargo.toml
Normal file
11
examples/custom_widget/Cargo.toml
Normal file
@ -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" }
|
10
examples/events/Cargo.toml
Normal file
10
examples/events/Cargo.toml
Normal file
@ -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" }
|
@ -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>) {
|
11
examples/geometry/Cargo.toml
Normal file
11
examples/geometry/Cargo.toml
Normal file
@ -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" }
|
14
examples/pokedex/Cargo.toml
Normal file
14
examples/pokedex/Cargo.toml
Normal file
@ -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"
|
17
examples/pokedex/README.md
Normal file
17
examples/pokedex/README.md
Normal file
@ -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/
|
@ -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>) {
|
9
examples/progress_bar/Cargo.toml
Normal file
9
examples/progress_bar/Cargo.toml
Normal file
@ -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 = "../.." }
|
12
examples/stopwatch/Cargo.toml
Normal file
12
examples/stopwatch/Cargo.toml
Normal file
@ -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"] }
|
@ -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;
|
||||
|
9
examples/styling/Cargo.toml
Normal file
9
examples/styling/Cargo.toml
Normal file
@ -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 = "../.." }
|
15
examples/styling/README.md
Normal file
15
examples/styling/README.md
Normal file
@ -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
|
||||
```
|
@ -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()
|
||||
}
|
||||
}
|
9
examples/svg/Cargo.toml
Normal file
9
examples/svg/Cargo.toml
Normal file
@ -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"] }
|
Before Width: | Height: | Size: 67 KiB After Width: | Height: | Size: 67 KiB |
37
examples/svg/src/main.rs
Normal file
37
examples/svg/src/main.rs
Normal file
@ -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()
|
||||
}
|
||||
}
|
16
examples/todos/Cargo.toml
Normal file
16
examples/todos/Cargo.toml
Normal file
@ -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"
|
20
examples/todos/README.md
Normal file
20
examples/todos/README.md
Normal file
@ -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/
|
@ -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 {
|
13
examples/tour/Cargo.toml
Normal file
13
examples/tour/Cargo.toml
Normal file
@ -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"
|
28
examples/tour/README.md
Normal file
28
examples/tour/README.md
Normal file
@ -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
|
Before Width: | Height: | Size: 32 KiB After Width: | Height: | Size: 32 KiB |
@ -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")
|
||||
))
|
||||
}
|
32
futures/Cargo.toml
Normal file
32
futures/Cargo.toml
Normal file
@ -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"
|
55
futures/src/executor.rs
Normal file
55
futures/src/executor.rs
Normal file
@ -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()
|
||||
}
|
||||
}
|
17
futures/src/executor/async_std.rs
Normal file
17
futures/src/executor/async_std.rs
Normal file
@ -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);
|
||||
}
|
||||
}
|
15
futures/src/executor/null.rs
Normal file
15
futures/src/executor/null.rs
Normal file
@ -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) {}
|
||||
}
|
16
futures/src/executor/thread_pool.rs
Normal file
16
futures/src/executor/thread_pool.rs
Normal file
@ -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);
|
||||
}
|
||||
}
|
20
futures/src/executor/tokio.rs
Normal file
20
futures/src/executor/tokio.rs
Normal file
@ -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)
|
||||
}
|
||||
}
|
18
futures/src/executor/wasm_bindgen.rs
Normal file
18
futures/src/executor/wasm_bindgen.rs
Normal file
@ -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);
|
||||
}
|
||||
}
|
18
futures/src/lib.rs
Normal file
18
futures/src/lib.rs
Normal file
@ -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;
|
119
futures/src/runtime.rs
Normal file
119
futures/src/runtime.rs
Normal file
@ -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);
|
||||
}
|
||||
}
|
@ -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;
|
||||
|
148
futures/src/subscription/tracker.rs
Normal file
148
futures/src/subscription/tracker.rs
Normal file
@ -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
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
@ -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"]
|
||||
|
@ -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};
|
||||
|
12
native/src/runtime.rs
Normal file
12
native/src/runtime.rs
Normal file
@ -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>;
|
@ -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;
|
||||
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
@ -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>) {
|
||||
|
9
src/element.rs
Normal file
9
src/element.rs
Normal file
@ -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;
|
54
src/executor.rs
Normal file
54
src/executor.rs
Normal file
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
26
src/lib.rs
26
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,
|
||||
};
|
||||
|
@ -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>) {
|
||||
|
@ -1 +0,0 @@
|
||||
pub use iced_web::*;
|
@ -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::*;
|
@ -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;
|
||||
|
@ -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"
|
||||
|
@ -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
|
||||
```
|
||||
|
||||
|
@ -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 {
|
||||
|
@ -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;
|
||||
|
@ -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"
|
||||
|
@ -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 {
|
||||
|
@ -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;
|
||||
|
57
winit/src/proxy.rs
Normal file
57
winit/src/proxy.rs
Normal file
@ -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(()))
|
||||
}
|
||||
}
|
@ -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
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user