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]
|
[workspace]
|
||||||
members = [
|
members = [
|
||||||
"core",
|
"core",
|
||||||
|
"futures",
|
||||||
"native",
|
"native",
|
||||||
"style",
|
"style",
|
||||||
"web",
|
"web",
|
||||||
"wgpu",
|
"wgpu",
|
||||||
"winit",
|
"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]
|
[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]
|
[target.'cfg(target_arch = "wasm32")'.dependencies]
|
||||||
iced_web = { version = "0.1.0", path = "web" }
|
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"
|
license = "MIT"
|
||||||
repository = "https://github.com/hecrj/iced"
|
repository = "https://github.com/hecrj/iced"
|
||||||
|
|
||||||
[features]
|
|
||||||
# Exposes a future-based `Command` type
|
|
||||||
command = ["futures"]
|
|
||||||
# Exposes a future-based `Subscription` type
|
|
||||||
subscription = ["futures"]
|
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
futures = { version = "0.3", optional = true }
|
|
||||||
|
@ -32,15 +32,3 @@ pub use length::Length;
|
|||||||
pub use point::Point;
|
pub use point::Point;
|
||||||
pub use rectangle::Rectangle;
|
pub use rectangle::Rectangle;
|
||||||
pub use vector::Vector;
|
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
|
[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.
|
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">
|
<div align="center">
|
||||||
<a href="https://gfycat.com/politeadorableiberianmole">
|
<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>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
[`tour`]: tour.rs
|
|
||||||
[`iced_winit`]: ../winit
|
[`iced_winit`]: ../winit
|
||||||
[`iced_native`]: ../native
|
[`iced_native`]: ../native
|
||||||
[`iced_wgpu`]: ../wgpu
|
[`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`:
|
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 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
|
[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)
|
The example code is located in the __[`main`](todos/src/main.rs)__ file.
|
||||||
|
|
||||||
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.
|
|
||||||
|
|
||||||
<div align="center">
|
<div align="center">
|
||||||
<a href="https://gfycat.com/littlesanehalicore">
|
<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`:
|
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_!
|
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/
|
[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
|
Since [Iced was born in May], it has been powering the user interfaces in
|
||||||
[Coffee], an experimental 2D game engine.
|
[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::{
|
use iced::{
|
||||||
Align, Application, Checkbox, Column, Command, Container, Element, Length,
|
executor, Align, Application, Checkbox, Column, Command, Container,
|
||||||
Settings, Subscription, Text,
|
Element, Length, Settings, Subscription, Text,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn main() {
|
pub fn main() {
|
||||||
@ -20,6 +20,7 @@ enum Message {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Application for Events {
|
impl Application for Events {
|
||||||
|
type Executor = executor::Default;
|
||||||
type Message = Message;
|
type Message = Message;
|
||||||
|
|
||||||
fn new() -> (Events, Command<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::{
|
use iced::{
|
||||||
button, image, Align, Application, Button, Column, Command, Container,
|
button, futures, image, Align, Application, Button, Column, Command,
|
||||||
Element, Image, Length, Row, Settings, Text,
|
Container, Element, Image, Length, Row, Settings, Text,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn main() {
|
pub fn main() {
|
||||||
@ -27,6 +27,7 @@ enum Message {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Application for Pokedex {
|
impl Application for Pokedex {
|
||||||
|
type Executor = iced_futures::executor::AsyncStd;
|
||||||
type Message = Message;
|
type Message = Message;
|
||||||
|
|
||||||
fn new() -> (Pokedex, Command<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 {
|
impl Application for Stopwatch {
|
||||||
|
type Executor = iced_futures::executor::AsyncStd;
|
||||||
type Message = Message;
|
type Message = Message;
|
||||||
|
|
||||||
fn new() -> (Stopwatch, Command<Message>) {
|
fn new() -> (Stopwatch, Command<Message>) {
|
||||||
@ -142,6 +143,8 @@ impl Application for Stopwatch {
|
|||||||
}
|
}
|
||||||
|
|
||||||
mod time {
|
mod time {
|
||||||
|
use iced::futures;
|
||||||
|
|
||||||
pub fn every(
|
pub fn every(
|
||||||
duration: std::time::Duration,
|
duration: std::time::Duration,
|
||||||
) -> iced::Subscription<std::time::Instant> {
|
) -> iced::Subscription<std::time::Instant> {
|
||||||
@ -165,7 +168,7 @@ mod time {
|
|||||||
|
|
||||||
fn stream(
|
fn stream(
|
||||||
self: Box<Self>,
|
self: Box<Self>,
|
||||||
_input: I,
|
_input: futures::stream::BoxStream<'static, I>,
|
||||||
) -> futures::stream::BoxStream<'static, Self::Output> {
|
) -> futures::stream::BoxStream<'static, Self::Output> {
|
||||||
use futures::stream::StreamExt;
|
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 {
|
impl Application for Todos {
|
||||||
|
type Executor = iced_futures::executor::AsyncStd;
|
||||||
type Message = Message;
|
type Message = Message;
|
||||||
|
|
||||||
fn new() -> (Todos, Command<Message>) {
|
fn new() -> (Todos, Command<Message>) {
|
||||||
@ -450,7 +451,7 @@ fn empty_message(message: &str) -> Element<'static, Message> {
|
|||||||
// Fonts
|
// Fonts
|
||||||
const ICONS: Font = Font::External {
|
const ICONS: Font = Font::External {
|
||||||
name: "Icons",
|
name: "Icons",
|
||||||
bytes: include_bytes!("resources/icons.ttf"),
|
bytes: include_bytes!("../fonts/icons.ttf"),
|
||||||
};
|
};
|
||||||
|
|
||||||
fn icon(unicode: char) -> Text {
|
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
|
// This should go away once we unify resource loading on native
|
||||||
// platforms
|
// platforms
|
||||||
if cfg!(target_arch = "wasm32") {
|
if cfg!(target_arch = "wasm32") {
|
||||||
Image::new("resources/ferris.png")
|
Image::new("images/ferris.png")
|
||||||
} else {
|
} else {
|
||||||
Image::new(format!(
|
Image::new(format!(
|
||||||
"{}/examples/resources/ferris.png",
|
"{}/images/ferris.png",
|
||||||
env!("CARGO_MANIFEST_DIR")
|
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.
|
//! Listen to external events in your application.
|
||||||
|
mod tracker;
|
||||||
|
|
||||||
|
pub use tracker::Tracker;
|
||||||
|
|
||||||
|
use futures::stream::BoxStream;
|
||||||
|
|
||||||
/// A request to listen to external events.
|
/// A request to listen to external events.
|
||||||
///
|
///
|
||||||
@ -11,16 +16,16 @@
|
|||||||
/// For instance, you can use a [`Subscription`] to listen to a WebSocket
|
/// For instance, you can use a [`Subscription`] to listen to a WebSocket
|
||||||
/// connection, keyboard presses, mouse events, time ticks, etc.
|
/// 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`.
|
/// `Hasher`.
|
||||||
///
|
///
|
||||||
/// [`Command`]: ../struct.Command.html
|
/// [`Command`]: ../struct.Command.html
|
||||||
/// [`Subscription`]: struct.Subscription.html
|
/// [`Subscription`]: struct.Subscription.html
|
||||||
pub struct Subscription<Hasher, Input, Output> {
|
pub struct Subscription<Hasher, Event, Output> {
|
||||||
recipes: Vec<Box<dyn Recipe<Hasher, Input, Output = 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
|
where
|
||||||
H: std::hash::Hasher,
|
H: std::hash::Hasher,
|
||||||
{
|
{
|
||||||
@ -38,7 +43,7 @@ where
|
|||||||
/// [`Subscription`]: struct.Subscription.html
|
/// [`Subscription`]: struct.Subscription.html
|
||||||
/// [`Recipe`]: trait.Recipe.html
|
/// [`Recipe`]: trait.Recipe.html
|
||||||
pub fn from_recipe(
|
pub fn from_recipe(
|
||||||
recipe: impl Recipe<H, I, Output = O> + 'static,
|
recipe: impl Recipe<H, E, Output = O> + 'static,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
recipes: vec![Box::new(recipe)],
|
recipes: vec![Box::new(recipe)],
|
||||||
@ -50,7 +55,7 @@ where
|
|||||||
///
|
///
|
||||||
/// [`Subscription`]: struct.Subscription.html
|
/// [`Subscription`]: struct.Subscription.html
|
||||||
pub fn batch(
|
pub fn batch(
|
||||||
subscriptions: impl IntoIterator<Item = Subscription<H, I, O>>,
|
subscriptions: impl IntoIterator<Item = Subscription<H, E, O>>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
recipes: subscriptions
|
recipes: subscriptions
|
||||||
@ -63,7 +68,7 @@ where
|
|||||||
/// Returns the different recipes of the [`Subscription`].
|
/// Returns the different recipes of the [`Subscription`].
|
||||||
///
|
///
|
||||||
/// [`Subscription`]: struct.Subscription.html
|
/// [`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
|
self.recipes
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -73,10 +78,10 @@ where
|
|||||||
pub fn map<A>(
|
pub fn map<A>(
|
||||||
mut self,
|
mut self,
|
||||||
f: impl Fn(O) -> A + Send + Sync + 'static,
|
f: impl Fn(O) -> A + Send + Sync + 'static,
|
||||||
) -> Subscription<H, I, A>
|
) -> Subscription<H, E, A>
|
||||||
where
|
where
|
||||||
H: 'static,
|
H: 'static,
|
||||||
I: 'static,
|
E: 'static,
|
||||||
O: 'static,
|
O: 'static,
|
||||||
A: 'static,
|
A: 'static,
|
||||||
{
|
{
|
||||||
@ -88,7 +93,7 @@ where
|
|||||||
.drain(..)
|
.drain(..)
|
||||||
.map(|recipe| {
|
.map(|recipe| {
|
||||||
Box::new(Map::new(recipe, function.clone()))
|
Box::new(Map::new(recipe, function.clone()))
|
||||||
as Box<dyn Recipe<H, I, Output = A>>
|
as Box<dyn Recipe<H, E, Output = A>>
|
||||||
})
|
})
|
||||||
.collect(),
|
.collect(),
|
||||||
}
|
}
|
||||||
@ -109,7 +114,7 @@ impl<I, O, H> std::fmt::Debug for Subscription<I, O, H> {
|
|||||||
///
|
///
|
||||||
/// [`Subscription`]: struct.Subscription.html
|
/// [`Subscription`]: struct.Subscription.html
|
||||||
/// [`Recipe`]: trait.Recipe.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
|
/// The events that will be produced by a [`Subscription`] with this
|
||||||
/// [`Recipe`].
|
/// [`Recipe`].
|
||||||
///
|
///
|
||||||
@ -128,31 +133,32 @@ pub trait Recipe<Hasher: std::hash::Hasher, Input> {
|
|||||||
/// Executes the [`Recipe`] and produces the stream of events of its
|
/// Executes the [`Recipe`] and produces the stream of events of its
|
||||||
/// [`Subscription`].
|
/// [`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
|
/// [`Subscription`]: struct.Subscription.html
|
||||||
/// [`Recipe`]: trait.Recipe.html
|
/// [`Recipe`]: trait.Recipe.html
|
||||||
fn stream(
|
fn stream(
|
||||||
self: Box<Self>,
|
self: Box<Self>,
|
||||||
input: Input,
|
input: BoxStream<'static, Event>,
|
||||||
) -> futures::stream::BoxStream<'static, Self::Output>;
|
) -> BoxStream<'static, Self::Output>;
|
||||||
}
|
}
|
||||||
|
|
||||||
struct Map<Hasher, Input, A, B> {
|
struct Map<Hasher, Event, A, B> {
|
||||||
recipe: Box<dyn Recipe<Hasher, Input, Output = A>>,
|
recipe: Box<dyn Recipe<Hasher, Event, Output = A>>,
|
||||||
mapper: std::sync::Arc<dyn Fn(A) -> B + Send + Sync>,
|
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(
|
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>,
|
mapper: std::sync::Arc<dyn Fn(A) -> B + Send + Sync + 'static>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Map { recipe, mapper }
|
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
|
where
|
||||||
A: 'static,
|
A: 'static,
|
||||||
B: 'static,
|
B: 'static,
|
||||||
@ -169,7 +175,7 @@ where
|
|||||||
|
|
||||||
fn stream(
|
fn stream(
|
||||||
self: Box<Self>,
|
self: Box<Self>,
|
||||||
input: I,
|
input: BoxStream<'static, E>,
|
||||||
) -> futures::stream::BoxStream<'static, Self::Output> {
|
) -> futures::stream::BoxStream<'static, Self::Output> {
|
||||||
use futures::StreamExt;
|
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"
|
repository = "https://github.com/hecrj/iced"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
iced_core = { version = "0.1.0", path = "../core", features = ["command", "subscription"] }
|
|
||||||
twox-hash = "1.5"
|
twox-hash = "1.5"
|
||||||
raw-window-handle = "0.3"
|
raw-window-handle = "0.3"
|
||||||
unicode-segmentation = "1.6"
|
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 event;
|
||||||
mod hasher;
|
mod hasher;
|
||||||
mod mouse_cursor;
|
mod mouse_cursor;
|
||||||
|
mod runtime;
|
||||||
mod size;
|
mod size;
|
||||||
mod user_interface;
|
mod user_interface;
|
||||||
|
|
||||||
pub use iced_core::{
|
pub use iced_core::{
|
||||||
Align, Background, Color, Command, Font, HorizontalAlignment, Length,
|
Align, Background, Color, Font, HorizontalAlignment, Length, Point,
|
||||||
Point, Rectangle, Vector, VerticalAlignment,
|
Rectangle, Vector, VerticalAlignment,
|
||||||
};
|
};
|
||||||
|
pub use iced_futures::{executor, futures, Command};
|
||||||
|
|
||||||
|
#[doc(no_inline)]
|
||||||
|
pub use executor::Executor;
|
||||||
|
|
||||||
pub use clipboard::Clipboard;
|
pub use clipboard::Clipboard;
|
||||||
pub use element::Element;
|
pub use element::Element;
|
||||||
@ -66,6 +71,7 @@ pub use hasher::Hasher;
|
|||||||
pub use layout::Layout;
|
pub use layout::Layout;
|
||||||
pub use mouse_cursor::MouseCursor;
|
pub use mouse_cursor::MouseCursor;
|
||||||
pub use renderer::Renderer;
|
pub use renderer::Renderer;
|
||||||
|
pub use runtime::Runtime;
|
||||||
pub use size::Size;
|
pub use size::Size;
|
||||||
pub use subscription::Subscription;
|
pub use subscription::Subscription;
|
||||||
pub use user_interface::{Cache, UserInterface};
|
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.
|
//! Listen to external events in your application.
|
||||||
use crate::{Event, Hasher};
|
use crate::{Event, Hasher};
|
||||||
use futures::stream::BoxStream;
|
use iced_futures::futures::stream::BoxStream;
|
||||||
|
|
||||||
/// A request to listen to external events.
|
/// A request to listen to external events.
|
||||||
///
|
///
|
||||||
@ -15,7 +15,7 @@ use futures::stream::BoxStream;
|
|||||||
///
|
///
|
||||||
/// [`Command`]: ../struct.Command.html
|
/// [`Command`]: ../struct.Command.html
|
||||||
/// [`Subscription`]: struct.Subscription.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.
|
/// A stream of runtime events.
|
||||||
///
|
///
|
||||||
@ -24,7 +24,12 @@ pub type Subscription<T> = iced_core::Subscription<Hasher, EventStream, T>;
|
|||||||
/// [`Subscription`]: type.Subscription.html
|
/// [`Subscription`]: type.Subscription.html
|
||||||
pub type EventStream = BoxStream<'static, Event>;
|
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;
|
mod events;
|
||||||
|
|
||||||
|
@ -2,10 +2,11 @@ use crate::{
|
|||||||
subscription::{EventStream, Recipe},
|
subscription::{EventStream, Recipe},
|
||||||
Event, Hasher,
|
Event, Hasher,
|
||||||
};
|
};
|
||||||
|
use iced_futures::futures::stream::BoxStream;
|
||||||
|
|
||||||
pub struct Events;
|
pub struct Events;
|
||||||
|
|
||||||
impl Recipe<Hasher, EventStream> for Events {
|
impl Recipe<Hasher, Event> for Events {
|
||||||
type Output = Event;
|
type Output = Event;
|
||||||
|
|
||||||
fn hash(&self, state: &mut Hasher) {
|
fn hash(&self, state: &mut Hasher) {
|
||||||
@ -17,7 +18,7 @@ impl Recipe<Hasher, EventStream> for Events {
|
|||||||
fn stream(
|
fn stream(
|
||||||
self: Box<Self>,
|
self: Box<Self>,
|
||||||
event_stream: EventStream,
|
event_stream: EventStream,
|
||||||
) -> futures::stream::BoxStream<'static, Self::Output> {
|
) -> BoxStream<'static, Self::Output> {
|
||||||
event_stream
|
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.
|
/// 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:
|
/// before](index.html#overview). We just need to fill in the gaps:
|
||||||
///
|
///
|
||||||
/// ```no_run
|
/// ```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() {
|
/// pub fn main() {
|
||||||
/// Counter::run(Settings::default())
|
/// Counter::run(Settings::default())
|
||||||
@ -39,6 +39,7 @@ use crate::{window, Command, Element, Settings, Subscription};
|
|||||||
/// }
|
/// }
|
||||||
///
|
///
|
||||||
/// impl Application for Counter {
|
/// impl Application for Counter {
|
||||||
|
/// type Executor = executor::Null;
|
||||||
/// type Message = Message;
|
/// type Message = Message;
|
||||||
///
|
///
|
||||||
/// fn new() -> (Self, Command<Message>) {
|
/// fn new() -> (Self, Command<Message>) {
|
||||||
@ -80,6 +81,14 @@ use crate::{window, Command, Element, Settings, Subscription};
|
|||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
pub trait Application: Sized {
|
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.
|
/// The type of __messages__ your [`Application`] will produce.
|
||||||
///
|
///
|
||||||
/// [`Application`]: trait.Application.html
|
/// [`Application`]: trait.Application.html
|
||||||
@ -185,6 +194,7 @@ where
|
|||||||
A: Application,
|
A: Application,
|
||||||
{
|
{
|
||||||
type Renderer = iced_wgpu::Renderer;
|
type Renderer = iced_wgpu::Renderer;
|
||||||
|
type Executor = A::Executor;
|
||||||
type Message = A::Message;
|
type Message = A::Message;
|
||||||
|
|
||||||
fn new() -> (Self, Command<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(unsafe_code)]
|
||||||
#![deny(rust_2018_idioms)]
|
#![deny(rust_2018_idioms)]
|
||||||
mod application;
|
mod application;
|
||||||
#[cfg(target_arch = "wasm32")]
|
mod element;
|
||||||
#[path = "web.rs"]
|
|
||||||
mod platform;
|
|
||||||
#[cfg(not(target_arch = "wasm32"))]
|
|
||||||
#[path = "native.rs"]
|
|
||||||
mod platform;
|
|
||||||
mod sandbox;
|
mod sandbox;
|
||||||
|
|
||||||
|
pub mod executor;
|
||||||
pub mod settings;
|
pub mod settings;
|
||||||
|
pub mod widget;
|
||||||
pub mod window;
|
pub mod window;
|
||||||
|
|
||||||
|
#[doc(no_inline)]
|
||||||
|
pub use widget::*;
|
||||||
|
|
||||||
pub use application::Application;
|
pub use application::Application;
|
||||||
pub use platform::*;
|
pub use element::Element;
|
||||||
|
pub use executor::Executor;
|
||||||
pub use sandbox::Sandbox;
|
pub use sandbox::Sandbox;
|
||||||
pub use settings::Settings;
|
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`].
|
/// A sandboxed [`Application`].
|
||||||
///
|
///
|
||||||
@ -133,6 +133,7 @@ impl<T> Application for T
|
|||||||
where
|
where
|
||||||
T: Sandbox,
|
T: Sandbox,
|
||||||
{
|
{
|
||||||
|
type Executor = executor::Null;
|
||||||
type Message = T::Message;
|
type Message = T::Message;
|
||||||
|
|
||||||
fn new() -> (Self, Command<T::Message>) {
|
fn new() -> (Self, Command<T::Message>) {
|
||||||
|
@ -1 +0,0 @@
|
|||||||
pub use iced_web::*;
|
|
@ -1,27 +1,23 @@
|
|||||||
pub use iced_winit::{
|
//! Display information and interactive controls in your application.
|
||||||
Align, Background, Color, Command, Font, HorizontalAlignment, Length,
|
//!
|
||||||
Space, Subscription, Vector, VerticalAlignment,
|
//! # Re-exports
|
||||||
};
|
//! For convenience, the contents of this module are available at the root
|
||||||
|
//! module. Therefore, you can directly type:
|
||||||
pub mod widget {
|
//!
|
||||||
//! Display information and interactive controls in your application.
|
//! ```
|
||||||
//!
|
//! use iced::{button, Button};
|
||||||
//! # Re-exports
|
//! ```
|
||||||
//! For convenience, the contents of this module are available at the root
|
//!
|
||||||
//! module. Therefore, you can directly type:
|
//! # Stateful widgets
|
||||||
//!
|
//! Some widgets need to keep track of __local state__.
|
||||||
//! ```
|
//!
|
||||||
//! use iced::{button, Button};
|
//! These widgets have their own module with a `State` type. For instance, a
|
||||||
//! ```
|
//! [`TextInput`] has some [`text_input::State`].
|
||||||
//!
|
//!
|
||||||
//! # Stateful widgets
|
//! [`TextInput`]: text_input/struct.TextInput.html
|
||||||
//! Some widgets need to keep track of __local state__.
|
//! [`text_input::State`]: text_input/struct.State.html
|
||||||
//!
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
//! These widgets have their own module with a `State` type. For instance, a
|
mod platform {
|
||||||
//! [`TextInput`] has some [`text_input::State`].
|
|
||||||
//!
|
|
||||||
//! [`TextInput`]: text_input/struct.TextInput.html
|
|
||||||
//! [`text_input::State`]: text_input/struct.State.html
|
|
||||||
pub use iced_wgpu::widget::*;
|
pub use iced_wgpu::widget::*;
|
||||||
|
|
||||||
pub mod image {
|
pub mod image {
|
||||||
@ -56,11 +52,9 @@ pub mod widget {
|
|||||||
iced_winit::Row<'a, Message, iced_wgpu::Renderer>;
|
iced_winit::Row<'a, Message, iced_wgpu::Renderer>;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[doc(no_inline)]
|
#[cfg(target_arch = "wasm32")]
|
||||||
pub use widget::*;
|
mod platform {
|
||||||
|
pub use iced_web::widget::*;
|
||||||
|
}
|
||||||
|
|
||||||
/// A generic widget.
|
pub use platform::*;
|
||||||
///
|
|
||||||
/// 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>;
|
|
@ -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 button;
|
||||||
pub mod checkbox;
|
pub mod checkbox;
|
||||||
pub mod container;
|
pub mod container;
|
||||||
|
@ -15,11 +15,17 @@ categories = ["web-programming"]
|
|||||||
maintenance = { status = "actively-developed" }
|
maintenance = { status = "actively-developed" }
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
iced_core = { version = "0.1.0", path = "../core", features = ["command", "subscription"] }
|
|
||||||
dodrio = "0.1.0"
|
dodrio = "0.1.0"
|
||||||
wasm-bindgen = "0.2.51"
|
wasm-bindgen = "0.2.51"
|
||||||
wasm-bindgen-futures = "0.4"
|
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]
|
[dependencies.web-sys]
|
||||||
version = "0.3.27"
|
version = "0.3.27"
|
||||||
|
@ -35,7 +35,7 @@ For instance, let's say we want to build the [`tour` example]:
|
|||||||
|
|
||||||
```
|
```
|
||||||
cd examples
|
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
|
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 element::Element;
|
||||||
pub use hasher::Hasher;
|
pub use hasher::Hasher;
|
||||||
pub use iced_core::{
|
pub use iced_core::{
|
||||||
Align, Background, Color, Command, Font, HorizontalAlignment, Length,
|
Align, Background, Color, Font, HorizontalAlignment, Length, Vector,
|
||||||
VerticalAlignment,
|
VerticalAlignment,
|
||||||
};
|
};
|
||||||
|
pub use iced_futures::{executor, futures, Command};
|
||||||
pub use style::Style;
|
pub use style::Style;
|
||||||
pub use subscription::Subscription;
|
pub use subscription::Subscription;
|
||||||
|
|
||||||
|
#[doc(no_inline)]
|
||||||
pub use widget::*;
|
pub use widget::*;
|
||||||
|
|
||||||
|
#[doc(no_inline)]
|
||||||
|
pub use executor::Executor;
|
||||||
|
|
||||||
/// An interactive web application.
|
/// An interactive web application.
|
||||||
///
|
///
|
||||||
/// This trait is the main entrypoint of Iced. Once implemented, you can run
|
/// This trait is the main entrypoint of Iced. Once implemented, you can run
|
||||||
@ -148,7 +154,6 @@ pub trait Application {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
struct Instance<Message> {
|
struct Instance<Message> {
|
||||||
title: String,
|
title: String,
|
||||||
ui: Rc<RefCell<Box<dyn Application<Message = Message>>>>,
|
ui: Rc<RefCell<Box<dyn Application<Message = Message>>>>,
|
||||||
@ -167,7 +172,7 @@ impl<Message> Clone for Instance<Message> {
|
|||||||
|
|
||||||
impl<Message> Instance<Message>
|
impl<Message> Instance<Message>
|
||||||
where
|
where
|
||||||
Message: 'static
|
Message: 'static,
|
||||||
{
|
{
|
||||||
fn new(ui: impl Application<Message = Message> + 'static) -> Self {
|
fn new(ui: impl Application<Message = Message> + 'static) -> Self {
|
||||||
Self {
|
Self {
|
||||||
|
@ -14,6 +14,6 @@ use crate::Hasher;
|
|||||||
///
|
///
|
||||||
/// [`Command`]: ../struct.Command.html
|
/// [`Command`]: ../struct.Command.html
|
||||||
/// [`Subscription`]: struct.Subscription.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 = []
|
debug = []
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
iced_native = { version = "0.1.0-alpha", path = "../native" }
|
|
||||||
winit = { version = "0.20.0-alpha3", git = "https://github.com/hecrj/winit", rev = "709808eb4e69044705fcb214bcc30556db761405"}
|
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"
|
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]
|
[target.'cfg(target_os = "windows")'.dependencies.winapi]
|
||||||
version = "0.3.6"
|
version = "0.3.6"
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
conversion,
|
conversion,
|
||||||
input::{keyboard, mouse},
|
input::{keyboard, mouse},
|
||||||
subscription, window, Cache, Clipboard, Command, Debug, Element, Event,
|
window, Cache, Clipboard, Command, Debug, Element, Event, Executor, Mode,
|
||||||
Mode, MouseCursor, Settings, Size, Subscription, UserInterface,
|
MouseCursor, Proxy, Runtime, Settings, Size, Subscription, UserInterface,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// An interactive, native cross-platform application.
|
/// An interactive, native cross-platform application.
|
||||||
@ -19,6 +19,11 @@ pub trait Application: Sized {
|
|||||||
/// [`Application`]: trait.Application.html
|
/// [`Application`]: trait.Application.html
|
||||||
type Renderer: window::Renderer;
|
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.
|
/// The type of __messages__ your [`Application`] will produce.
|
||||||
///
|
///
|
||||||
/// [`Application`]: trait.Application.html
|
/// [`Application`]: trait.Application.html
|
||||||
@ -109,17 +114,19 @@ pub trait Application: Sized {
|
|||||||
|
|
||||||
debug.startup_started();
|
debug.startup_started();
|
||||||
let event_loop = EventLoop::with_user_event();
|
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 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();
|
let (mut application, init_command) = Self::new();
|
||||||
spawn(init_command, &mut thread_pool, &proxy);
|
runtime.spawn(init_command);
|
||||||
|
|
||||||
let subscription = application.subscription();
|
let subscription = application.subscription();
|
||||||
subscription_pool.update(subscription, &mut thread_pool, &proxy);
|
runtime.track(subscription);
|
||||||
|
|
||||||
let mut title = application.title();
|
let mut title = application.title();
|
||||||
let mut mode = application.mode();
|
let mut mode = application.mode();
|
||||||
@ -212,7 +219,7 @@ pub trait Application: Sized {
|
|||||||
events
|
events
|
||||||
.iter()
|
.iter()
|
||||||
.cloned()
|
.cloned()
|
||||||
.for_each(|event| subscription_pool.broadcast_event(event));
|
.for_each(|event| runtime.broadcast(event));
|
||||||
|
|
||||||
let mut messages = user_interface.update(
|
let mut messages = user_interface.update(
|
||||||
&renderer,
|
&renderer,
|
||||||
@ -241,17 +248,15 @@ pub trait Application: Sized {
|
|||||||
debug.log_message(&message);
|
debug.log_message(&message);
|
||||||
|
|
||||||
debug.update_started();
|
debug.update_started();
|
||||||
let command = application.update(message);
|
let command =
|
||||||
spawn(command, &mut thread_pool, &proxy);
|
runtime.enter(|| application.update(message));
|
||||||
|
runtime.spawn(command);
|
||||||
debug.update_finished();
|
debug.update_finished();
|
||||||
}
|
}
|
||||||
|
|
||||||
let subscription = application.subscription();
|
let subscription =
|
||||||
subscription_pool.update(
|
runtime.enter(|| application.subscription());
|
||||||
subscription,
|
runtime.track(subscription);
|
||||||
&mut thread_pool,
|
|
||||||
&proxy,
|
|
||||||
);
|
|
||||||
|
|
||||||
// Update window title
|
// Update window title
|
||||||
let new_title = application.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
|
// As defined in: http://www.unicode.org/faq/private_use.html
|
||||||
// TODO: Remove once https://github.com/rust-windowing/winit/pull/1254 lands
|
// TODO: Remove once https://github.com/rust-windowing/winit/pull/1254 lands
|
||||||
fn is_private_use_character(c: char) -> bool {
|
fn is_private_use_character(c: char) -> bool {
|
||||||
|
@ -31,7 +31,7 @@ pub mod settings;
|
|||||||
mod application;
|
mod application;
|
||||||
mod clipboard;
|
mod clipboard;
|
||||||
mod mode;
|
mod mode;
|
||||||
mod subscription;
|
mod proxy;
|
||||||
|
|
||||||
// We disable debug capabilities on release builds unless the `debug` feature
|
// We disable debug capabilities on release builds unless the `debug` feature
|
||||||
// is explicitly enabled.
|
// is explicitly enabled.
|
||||||
@ -48,3 +48,4 @@ pub use settings::Settings;
|
|||||||
|
|
||||||
use clipboard::Clipboard;
|
use clipboard::Clipboard;
|
||||||
use debug::Debug;
|
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…
x
Reference in New Issue
Block a user