Compare commits
5 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
836a41c53e | ||
|
577fe8774c | ||
|
3a0eea0dcd | ||
|
9cab88b986 | ||
|
4bd44e4ed2 |
5
.github/workflows/build.yml
vendored
@ -11,11 +11,6 @@ jobs:
|
||||
- name: Install cargo-deb
|
||||
run: cargo install cargo-deb
|
||||
- uses: actions/checkout@master
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
export DEBIAN_FRONTED=noninteractive
|
||||
sudo apt-get -qq update
|
||||
sudo apt-get install -y libxkbcommon-dev
|
||||
- name: Enable Link Time Optimizations
|
||||
run: |
|
||||
echo "[profile.release]" >> Cargo.toml
|
||||
|
10
.github/workflows/test.yml
vendored
@ -12,12 +12,6 @@ jobs:
|
||||
with:
|
||||
rust-version: ${{ matrix.rust }}
|
||||
- uses: actions/checkout@master
|
||||
- name: Install dependencies
|
||||
if: matrix.os == 'ubuntu-latest'
|
||||
run: |
|
||||
export DEBIAN_FRONTED=noninteractive
|
||||
sudo apt-get -qq update
|
||||
sudo apt-get install -y libxkbcommon-dev
|
||||
- name: Run tests
|
||||
run: |
|
||||
cargo test --verbose --all
|
||||
@ -35,5 +29,5 @@ jobs:
|
||||
run: cargo check --package iced --target wasm32-unknown-unknown
|
||||
- name: Check compilation of `tour` example
|
||||
run: cargo build --package tour --target wasm32-unknown-unknown
|
||||
- name: Check compilation of `todos` example
|
||||
run: cargo build --package todos --target wasm32-unknown-unknown
|
||||
- name: Check compilation of `pokedex` example
|
||||
run: cargo build --package pokedex --target wasm32-unknown-unknown
|
||||
|
2
.gitignore
vendored
@ -1,4 +1,4 @@
|
||||
target/
|
||||
/target
|
||||
pkg/
|
||||
**/*.rs.bk
|
||||
Cargo.lock
|
||||
|
115
CHANGELOG.md
@ -5,115 +5,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
## [0.3.0] - 2021-03-31
|
||||
### Added
|
||||
- Touch support. [#57] [#650] (thanks to @simlay and @discordance!)
|
||||
- Clipboard write access for
|
||||
- `TextInput` widget. [#770]
|
||||
- `Application::update`. [#773]
|
||||
- `image::Viewer` widget. It allows panning and scaling of an image. [#319] (thanks to @tarkah!)
|
||||
- `Tooltip` widget. It annotates content with some text on mouse hover. [#465] (thanks to @yusdacra!)
|
||||
- Support for the [`smol`] async runtime. [#699] (thanks to @JayceFayne!)
|
||||
- Support for graceful exiting when using the `Application` trait. [#804]
|
||||
- Image format features in [`iced_wgpu`] to reduce code bloat. [#392] (thanks to @unrelentingtech!)
|
||||
- `Focused` and `Unfocused` variant to `window::Event`. [#701] (thanks to @cossonleo!)
|
||||
- `WGPU_BACKEND` environment variable to configure the internal graphics backend of `iced_wgpu`. [#789] (thanks to @Cupnfish!)
|
||||
|
||||
### Changed
|
||||
- The `TitleBar` of a `PaneGrid` now supports generic elements. [#657] (thanks to @clarkmoody!)
|
||||
- The `Error` type now implements `Send` and `Sync`. [#719] (thanks to @taiki-e!)
|
||||
- The `Style` types in `iced_style` now implement `Clone` and `Copy`. [#720] (thanks to @taiki-e!)
|
||||
- The following dependencies have been updated:
|
||||
- [`font-kit`] → `0.10` [#669]
|
||||
- [`glutin`] → `0.26` [#658]
|
||||
- [`resvg`] → `0.12` [#669]
|
||||
- [`tokio`] → `1.0` [#672] (thanks to @yusdacra!)
|
||||
- [`winit`] → `0.24` [#658]
|
||||
- [`wgpu`] → `0.7` [#725] (thanks to @PolyMeilex)
|
||||
- The following examples were improved:
|
||||
- `download_progress` now showcases multiple file downloads at once. [#283] (thanks to @Folyd!)
|
||||
- `solar_system` uses the new `rand` API. [#760] (thanks to @TriedAngle!)
|
||||
|
||||
### Fixed
|
||||
- Button events not being propagated to contents. [#668]
|
||||
- Incorrect overlay implementation for the `Button` widget. [#764]
|
||||
- `Viewport::physical_width` returning the wrong value. [#700]
|
||||
- Outdated documentation for the `Sandbox` trait. [#710]
|
||||
|
||||
[#57]: https://github.com/hecrj/iced/pull/57
|
||||
[#283]: https://github.com/hecrj/iced/pull/283
|
||||
[#319]: https://github.com/hecrj/iced/pull/319
|
||||
[#392]: https://github.com/hecrj/iced/pull/392
|
||||
[#465]: https://github.com/hecrj/iced/pull/465
|
||||
[#650]: https://github.com/hecrj/iced/pull/650
|
||||
[#657]: https://github.com/hecrj/iced/pull/657
|
||||
[#658]: https://github.com/hecrj/iced/pull/658
|
||||
[#668]: https://github.com/hecrj/iced/pull/668
|
||||
[#669]: https://github.com/hecrj/iced/pull/669
|
||||
[#672]: https://github.com/hecrj/iced/pull/672
|
||||
[#699]: https://github.com/hecrj/iced/pull/699
|
||||
[#700]: https://github.com/hecrj/iced/pull/700
|
||||
[#701]: https://github.com/hecrj/iced/pull/701
|
||||
[#710]: https://github.com/hecrj/iced/pull/710
|
||||
[#719]: https://github.com/hecrj/iced/pull/719
|
||||
[#720]: https://github.com/hecrj/iced/pull/720
|
||||
[#725]: https://github.com/hecrj/iced/pull/725
|
||||
[#760]: https://github.com/hecrj/iced/pull/760
|
||||
[#764]: https://github.com/hecrj/iced/pull/764
|
||||
[#770]: https://github.com/hecrj/iced/pull/770
|
||||
[#773]: https://github.com/hecrj/iced/pull/773
|
||||
[#789]: https://github.com/hecrj/iced/pull/789
|
||||
[#804]: https://github.com/hecrj/iced/pull/804
|
||||
[`smol`]: https://github.com/smol-rs/smol
|
||||
[`winit`]: https://github.com/rust-windowing/winit
|
||||
[`glutin`]: https://github.com/rust-windowing/glutin
|
||||
[`font-kit`]: https://github.com/servo/font-kit
|
||||
|
||||
## [0.2.0] - 2020-11-26
|
||||
### Added
|
||||
- __[`Canvas` interactivity][canvas]__ (#325)
|
||||
A trait-based approach to react to mouse and keyboard interactions in [the `Canvas` widget][#193].
|
||||
|
||||
- __[`iced_graphics` subcrate][opengl]__ (#354)
|
||||
A backend-agnostic graphics subcrate that can be leveraged to build new renderers.
|
||||
|
||||
- __[OpenGL renderer][opengl]__ (#354)
|
||||
An OpenGL renderer powered by [`iced_graphics`], [`glow`], and [`glutin`]. It is an alternative to the default [`wgpu`] renderer.
|
||||
|
||||
- __[Overlay support][pick_list]__ (#444)
|
||||
Basic support for superpositioning interactive widgets on top of other widgets.
|
||||
|
||||
- __[Faster event loop][view]__ (#597)
|
||||
The event loop now takes advantage of the data dependencies in [The Elm Architecture] and leverages the borrow checker to keep the widget tree alive between iterations, avoiding unnecessary rebuilds.
|
||||
|
||||
- __[Event capturing][event]__ (#614)
|
||||
The runtime now can tell whether a widget has handled an event or not, easing [integration with existing applications].
|
||||
|
||||
- __[`PickList` widget][pick_list]__ (#444)
|
||||
A drop-down selector widget built on top of the new overlay support.
|
||||
|
||||
- __[`QRCode` widget][qr_code]__ (#622)
|
||||
A widget that displays a QR code, powered by [the `qrcode` crate].
|
||||
|
||||
[canvas]: https://github.com/hecrj/iced/pull/325
|
||||
[opengl]: https://github.com/hecrj/iced/pull/354
|
||||
[`iced_graphics`]: https://github.com/hecrj/iced/pull/354
|
||||
[pane_grid]: https://github.com/hecrj/iced/pull/397
|
||||
[pick_list]: https://github.com/hecrj/iced/pull/444
|
||||
[error]: https://github.com/hecrj/iced/pull/514
|
||||
[view]: https://github.com/hecrj/iced/pull/597
|
||||
[event]: https://github.com/hecrj/iced/pull/614
|
||||
[color]: https://github.com/hecrj/iced/pull/200
|
||||
[qr_code]: https://github.com/hecrj/iced/pull/622
|
||||
[#193]: https://github.com/hecrj/iced/pull/193
|
||||
[`glutin`]: https://github.com/rust-windowing/glutin
|
||||
[`wgpu`]: https://github.com/gfx-rs/wgpu-rs
|
||||
[`glow`]: https://github.com/grovesNL/glow
|
||||
[the `qrcode` crate]: https://docs.rs/qrcode/0.12.0/qrcode/
|
||||
[integration with existing applications]: https://github.com/hecrj/iced/pull/183
|
||||
[The Elm Architecture]: https://guide.elm-lang.org/architecture/
|
||||
- `"system_font"` feature gates reading system fonts. [#370]
|
||||
|
||||
[#370]: https://github.com/hecrj/iced/pull/370
|
||||
|
||||
## [0.1.1] - 2020-04-15
|
||||
### Added
|
||||
@ -207,7 +102,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
[`wasm-bindgen-futures`]: https://github.com/rustwasm/wasm-bindgen/tree/master/crates/futures
|
||||
[`resvg`]: https://github.com/RazrFalcon/resvg
|
||||
[`raqote`]: https://github.com/jrmuizel/raqote
|
||||
[`iced_wgpu`]: https://github.com/hecrj/iced/tree/master/wgpu
|
||||
[`iced_wgpu`]: https://github.com/hecrj/iced/tree/0.1/wgpu
|
||||
|
||||
|
||||
## [0.1.0-beta] - 2019-11-25
|
||||
@ -219,9 +114,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
### Added
|
||||
- First release! :tada:
|
||||
|
||||
[Unreleased]: https://github.com/hecrj/iced/compare/0.3.0...HEAD
|
||||
[0.3.0]: https://github.com/hecrj/iced/compare/0.2.0...0.3.0
|
||||
[0.2.0]: https://github.com/hecrj/iced/compare/0.1.1...0.2.0
|
||||
[Unreleased]: https://github.com/hecrj/iced/compare/0.1.1...HEAD
|
||||
[0.1.1]: https://github.com/hecrj/iced/compare/0.1.0...0.1.1
|
||||
[0.1.0]: https://github.com/hecrj/iced/compare/0.1.0-beta...0.1.0
|
||||
[0.1.0-beta]: https://github.com/hecrj/iced/compare/0.1.0-alpha...0.1.0-beta
|
||||
|
35
Cargo.toml
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "iced"
|
||||
version = "0.3.0"
|
||||
version = "0.1.1"
|
||||
authors = ["Héctor Ramón Jiménez <hector0193@gmail.com>"]
|
||||
edition = "2018"
|
||||
description = "A cross-platform GUI library inspired by Elm"
|
||||
@ -21,28 +21,20 @@ image = ["iced_wgpu/image"]
|
||||
svg = ["iced_wgpu/svg"]
|
||||
# Enables the `Canvas` widget
|
||||
canvas = ["iced_wgpu/canvas"]
|
||||
# Enables the `QRCode` widget
|
||||
qr_code = ["iced_wgpu/qr_code"]
|
||||
# Enables using system fonts
|
||||
# Enables using system fonts.
|
||||
default_system_font = ["iced_wgpu/default_system_font"]
|
||||
# Enables the `iced_glow` renderer. Overrides `iced_wgpu`
|
||||
glow = ["iced_glow", "iced_glutin"]
|
||||
# Enables the `Canvas` widget for `iced_glow`
|
||||
glow_canvas = ["iced_glow/canvas"]
|
||||
# Enables the `QRCode` widget for `iced_glow`
|
||||
glow_qr_code = ["iced_glow/qr_code"]
|
||||
# Enables using system fonts for `iced_glow`
|
||||
# Enables using system fonts for `iced_glow`.
|
||||
glow_default_system_font = ["iced_glow/default_system_font"]
|
||||
# Enables a debug view in native platforms (press F12)
|
||||
debug = ["iced_winit/debug"]
|
||||
# Enables `tokio` as the `executor::Default` on native platforms
|
||||
tokio = ["iced_futures/tokio"]
|
||||
# Enables old `tokio` (0.2) as the `executor::Default` on native platforms
|
||||
tokio_old = ["iced_futures/tokio_old"]
|
||||
# Enables `async-std` as the `executor::Default` on native platforms
|
||||
async-std = ["iced_futures/async-std"]
|
||||
# Enables `smol` as the `executor::Default` on native platforms
|
||||
smol = ["iced_futures/smol"]
|
||||
# Enables advanced color conversion via `palette`
|
||||
palette = ["iced_core/palette"]
|
||||
|
||||
@ -75,33 +67,28 @@ members = [
|
||||
"examples/pick_list",
|
||||
"examples/pokedex",
|
||||
"examples/progress_bar",
|
||||
"examples/qr_code",
|
||||
"examples/scrollable",
|
||||
"examples/solar_system",
|
||||
"examples/stopwatch",
|
||||
"examples/styling",
|
||||
"examples/svg",
|
||||
"examples/todos",
|
||||
"examples/tour",
|
||||
"examples/tour_glow",
|
||||
"examples/tooltip",
|
||||
"examples/url_handler",
|
||||
]
|
||||
|
||||
[dependencies]
|
||||
iced_core = { version = "0.4", path = "core" }
|
||||
iced_futures = { version = "0.3", path = "futures" }
|
||||
iced_core = { version = "0.2", path = "core" }
|
||||
iced_futures = { version = "0.1", path = "futures" }
|
||||
thiserror = "1.0"
|
||||
|
||||
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
|
||||
iced_winit = { version = "0.3", path = "winit" }
|
||||
iced_glutin = { version = "0.2", path = "glutin", optional = true }
|
||||
iced_wgpu = { version = "0.4", path = "wgpu", optional = true }
|
||||
iced_glow = { version = "0.2", path = "glow", optional = true}
|
||||
iced_winit = { version = "0.1", path = "winit" }
|
||||
iced_glutin = { version = "0.1", path = "glutin", optional = true }
|
||||
iced_wgpu = { version = "0.2", path = "wgpu", optional = true }
|
||||
iced_glow = { version = "0.1", path = "glow", optional = true}
|
||||
|
||||
[target.'cfg(target_arch = "wasm32")'.dependencies]
|
||||
iced_web = { version = "0.4", path = "web" }
|
||||
iced_web = { version = "0.2", path = "web" }
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
rustdoc-args = ["--cfg", "docsrs"]
|
||||
features = ["image", "svg", "canvas", "qr_code"]
|
||||
features = ["image", "svg", "canvas"]
|
||||
|
95
ECOSYSTEM.md
@ -1,7 +1,10 @@
|
||||
# Ecosystem
|
||||
This document describes the Iced ecosystem and explains how the different crates relate to each other.
|
||||
This document describes the Iced ecosystem.
|
||||
|
||||
It quickly lists the different audiences of the library and explains how the different crates relate to each other.
|
||||
|
||||
## Users
|
||||
|
||||
## Overview
|
||||
Iced is meant to be used by 2 different types of users:
|
||||
|
||||
- __End-users__. They should be able to:
|
||||
@ -15,81 +18,71 @@ Iced is meant to be used by 2 different types of users:
|
||||
- integrate existing runtimes in their own system (like game engines),
|
||||
- and create their own custom renderers.
|
||||
|
||||
## Crates
|
||||
Iced consists of different crates which offer different layers of abstractions for our users. This modular architecture helps us keep implementation details hidden and decoupled, which should allow us to rewrite or change strategies in the future.
|
||||
|
||||
<p align="center">
|
||||
<img alt="The Iced Ecosystem" src="docs/graphs/ecosystem.png" width="60%">
|
||||
</p>
|
||||

|
||||
|
||||
## The foundations
|
||||
There are a bunch of concepts that permeate the whole ecosystem. These concepts are considered __the foundations__, and they are provided by three different crates:
|
||||
### [`iced_core`]
|
||||
[`iced_core`] holds basic reusable types of the public API. For instance, basic data types like `Point`, `Rectangle`, `Length`, etc.
|
||||
|
||||
- [`iced_core`] contains many lightweight, reusable primitives (e.g. `Point`, `Rectangle`, `Color`).
|
||||
- [`iced_futures`] implements the concurrent concepts of [The Elm Architecture] on top of the [`futures`] ecosystem.
|
||||
- [`iced_style`] defines the default styling capabilities of built-in widgets.
|
||||
This crate is meant to be a starting point for an Iced runtime.
|
||||
|
||||
<p align="center">
|
||||
<img alt="The foundations" src="docs/graphs/foundations.png" width="50%">
|
||||
</p>
|
||||
### [`iced_native`]
|
||||
[`iced_native`] takes [`iced_core`] and builds a native runtime on top of it, featuring:
|
||||
- A custom layout engine, greatly inspired by [`druid`]
|
||||
- Event handling for all the built-in widgets
|
||||
- A renderer-agnostic API
|
||||
|
||||
## The native target
|
||||
The native side of the ecosystem is split into two different groups: __renderers__ and __shells__.
|
||||
To achieve this, it introduces a bunch of reusable interfaces:
|
||||
- A `Widget` trait, which is used to implement new widgets: from layout requirements to event and drawing logic.
|
||||
- A bunch of `Renderer` traits, meant to keep the crate renderer-agnostic.
|
||||
- A `Backend` trait, leveraging [`raw-window-handle`], which can be implemented by graphical renderers that target _windows_. Window-based shells (like [`iced_winit`]) can use this trait to stay renderer-agnostic.
|
||||
|
||||
<p align="center">
|
||||
<img alt="The native target" src="docs/graphs/native.png" width="80%">
|
||||
</p>
|
||||
[`druid`]: https://github.com/xi-editor/druid
|
||||
[`raw-window-handle`]: https://github.com/rust-windowing/raw-window-handle
|
||||
|
||||
### Renderers
|
||||
The widgets of a _graphical_ user interface produce some primitives that eventually need to be drawn on screen. __Renderers__ take care of this task, potentially leveraging GPU acceleration.
|
||||
### [`iced_web`]
|
||||
[`iced_web`] takes [`iced_core`] and builds a WebAssembly runtime on top. It achieves this by introducing a `Widget` trait that can be used to produce VDOM nodes.
|
||||
|
||||
Currently, there are two different official renderers:
|
||||
The crate is currently a simple abstraction layer over [`dodrio`].
|
||||
|
||||
- [`iced_wgpu`] is powered by [`wgpu`] and supports Vulkan, DirectX 12, and Metal.
|
||||
- [`iced_glow`] is powered by [`glow`] and supports OpenGL 3.3+.
|
||||
[`dodrio`]: https://github.com/fitzgen/dodrio
|
||||
|
||||
Additionally, the [`iced_graphics`] subcrate contains a bunch of backend-agnostic types that can be leveraged to build renderers. Both of the renderers rely on the graphical foundations provided by this crate.
|
||||
### [`iced_wgpu`]
|
||||
[`iced_wgpu`] is a [`wgpu`] renderer for [`iced_native`]. For now, it is the default renderer of Iced in native platforms.
|
||||
|
||||
### Shells
|
||||
The widgets of a graphical user _interface_ are interactive. __Shells__ gather and process user interactions in an event loop.
|
||||
[`wgpu`] supports most modern graphics backends: Vulkan, Metal, DX11, and DX12 (OpenGL and WebGL are still WIP). Additionally, it will support the incoming [WebGPU API].
|
||||
|
||||
Normally, a shell will be responsible of creating a window and managing the lifecycle of a user interface, implementing a runtime of [The Elm Architecture].
|
||||
Currently, [`iced_wgpu`] supports the following primitives:
|
||||
- Text, which is rendered using [`wgpu_glyph`]. No shaping at all.
|
||||
- Quads or rectangles, with rounded borders and a solid background color.
|
||||
- Clip areas, useful to implement scrollables or hide overflowing content.
|
||||
- Images and SVG, loaded from memory or the file system.
|
||||
- Meshes of triangles, useful to draw geometry freely.
|
||||
|
||||
As of now, there are two official shells:
|
||||
[`wgpu`]: https://github.com/gfx-rs/wgpu-rs
|
||||
[WebGPU API]: https://gpuweb.github.io/gpuweb/
|
||||
[`wgpu_glyph`]: https://github.com/hecrj/wgpu_glyph
|
||||
|
||||
- [`iced_winit`] implements a shell runtime on top of [`winit`].
|
||||
- [`iced_glutin`] is similar to [`iced_winit`], but it also deals with [OpenGL context creation].
|
||||
### [`iced_winit`]
|
||||
[`iced_winit`] offers some convenient abstractions on top of [`iced_native`] to quickstart development when using [`winit`].
|
||||
|
||||
## The web target
|
||||
The Web platform provides all the abstractions necessary to draw widgets and gather users interactions.
|
||||
It exposes a renderer-agnostic `Application` trait that can be implemented and then run with a simple call. The use of this trait is optional. A `conversion` module is provided for users that decide to implement a custom event loop.
|
||||
|
||||
Therefore, unlike the native path, the web side of the ecosystem does not need to split renderers and shells. Instead, [`iced_web`] leverages [`dodrio`] to both render widgets and implement a proper runtime.
|
||||
[`winit`]: https://github.com/rust-windowing/winit
|
||||
|
||||
## Iced
|
||||
### [`iced`]
|
||||
Finally, [`iced`] unifies everything into a simple abstraction to create cross-platform applications:
|
||||
|
||||
- On native, it uses __[shells](#shells)__ and __[renderers](#renderers)__.
|
||||
- On native, it uses [`iced_winit`] and [`iced_wgpu`].
|
||||
- On the web, it uses [`iced_web`].
|
||||
|
||||
<p align="center">
|
||||
<img alt="Iced" src="docs/graphs/iced.png" width="80%">
|
||||
</p>
|
||||
This is the crate meant to be used by __end-users__.
|
||||
|
||||
[`iced_core`]: core
|
||||
[`iced_futures`]: futures
|
||||
[`iced_style`]: style
|
||||
[`iced_native`]: native
|
||||
[`iced_web`]: web
|
||||
[`iced_graphics`]: graphics
|
||||
[`iced_wgpu`]: wgpu
|
||||
[`iced_glow`]: glow
|
||||
[`iced_winit`]: winit
|
||||
[`iced_glutin`]: glutin
|
||||
[`iced`]: ..
|
||||
[`futures`]: https://github.com/rust-lang/futures-rs
|
||||
[`glow`]: https://github.com/grovesNL/glow
|
||||
[`wgpu`]: https://github.com/gfx-rs/wgpu-rs
|
||||
[`winit`]: https://github.com/rust-windowing/winit
|
||||
[`glutin`]: https://github.com/rust-windowing/glutin
|
||||
[`dodrio`]: https://github.com/fitzgen/dodrio
|
||||
[OpenGL context creation]: https://www.khronos.org/opengl/wiki/Creating_an_OpenGL_Context
|
||||
[The Elm Architecture]: https://guide.elm-lang.org/architecture/
|
||||
|
32
README.md
@ -55,7 +55,7 @@ __Iced is currently experimental software.__ [Take a look at the roadmap],
|
||||
Add `iced` as a dependency in your `Cargo.toml`:
|
||||
|
||||
```toml
|
||||
iced = "0.3"
|
||||
iced = "0.1"
|
||||
```
|
||||
|
||||
__Iced moves fast and the `master` branch can contain breaking changes!__ If
|
||||
@ -168,19 +168,26 @@ Browse the [documentation] and the [examples] to learn more!
|
||||
Iced was originally born as an attempt at bringing the simplicity of [Elm] and
|
||||
[The Elm Architecture] into [Coffee], a 2D game engine I am working on.
|
||||
|
||||
The core of the library was implemented during May 2019 in [this pull request].
|
||||
The core of the library was implemented during May in [this pull request].
|
||||
[The first alpha version] was eventually released as
|
||||
[a renderer-agnostic GUI library]. The library did not provide a renderer and
|
||||
implemented the current [tour example] on top of [`ggez`], a game library.
|
||||
|
||||
Since then, the focus has shifted towards providing a batteries-included,
|
||||
end-user-oriented GUI library, while keeping [the ecosystem] modular:
|
||||
end-user-oriented GUI library, while keeping [the ecosystem] modular.
|
||||
|
||||
<p align="center">
|
||||
<a href="https://github.com/hecrj/iced/blob/master/ECOSYSTEM.md">
|
||||
<img alt="Iced Ecosystem" src="docs/graphs/ecosystem.png" width="80%">
|
||||
</a>
|
||||
</p>
|
||||
Currently, Iced is a cross-platform GUI library built on top of smaller crates:
|
||||
|
||||
- [`iced_core`], a bunch of basic types that can be reused in different runtimes.
|
||||
- [`iced_native`], a renderer-agnostic native runtime implementing widget
|
||||
logic and a layout engine inspired by [`druid`].
|
||||
- [`iced_web`], an experimental web runtime that targets the DOM thanks to
|
||||
[`dodrio`].
|
||||
- [`iced_wgpu`], a renderer leveraging [`wgpu`], [`wgpu_glyph`], and
|
||||
[`font-kit`].
|
||||
- [`iced_winit`], a windowing shell on top of [`winit`].
|
||||
|
||||
[](https://github.com/hecrj/iced/blob/master/ECOSYSTEM.md)
|
||||
|
||||
[this pull request]: https://github.com/hecrj/coffee/pull/35
|
||||
[The first alpha version]: https://github.com/hecrj/iced/tree/0.1.0-alpha
|
||||
@ -188,6 +195,15 @@ end-user-oriented GUI library, while keeping [the ecosystem] modular:
|
||||
[tour example]: https://github.com/hecrj/iced/blob/master/examples/README.md#tour
|
||||
[`ggez`]: https://github.com/ggez/ggez
|
||||
[the ecosystem]: https://github.com/hecrj/iced/blob/master/ECOSYSTEM.md
|
||||
[`iced_core`]: https://github.com/hecrj/iced/tree/master/core
|
||||
[`iced_native`]: https://github.com/hecrj/iced/tree/master/native
|
||||
[`iced_web`]: https://github.com/hecrj/iced/tree/master/web
|
||||
[`iced_wgpu`]: https://github.com/hecrj/iced/tree/master/wgpu
|
||||
[`iced_winit`]: https://github.com/hecrj/iced/tree/master/winit
|
||||
[`druid`]: https://github.com/xi-editor/druid
|
||||
[`wgpu_glyph`]: https://github.com/hecrj/wgpu_glyph
|
||||
[`font-kit`]: https://github.com/servo/font-kit
|
||||
[`winit`]: https://github.com/rust-windowing/winit
|
||||
|
||||
## Contributing / Feedback
|
||||
Contributions are greatly appreciated! If you want to contribute, please
|
||||
|
21
ROADMAP.md
@ -6,7 +6,7 @@ Before diving into the roadmap, check out [the ecosystem overview] to get an ide
|
||||
[the ecosystem overview]: ECOSYSTEM.md
|
||||
|
||||
## Next steps
|
||||
Most of the work related to these features needs to happen in the __native__ path of the ecosystem, as the web already supports many of them.
|
||||
Most of the work related to these features needs to happen in the `iced_native` path of the ecosystem, as the web already supports many of them.
|
||||
|
||||
Once a step is completed, it is collapsed and added to this list:
|
||||
|
||||
@ -17,8 +17,6 @@ Once a step is completed, it is collapsed and added to this list:
|
||||
* [x] Custom layout engine ([#52])
|
||||
* [x] Event subscriptions ([#122])
|
||||
* [x] Custom styling ([#146])
|
||||
* [x] Canvas for 2D graphics ([#193])
|
||||
* [x] Basic overlay support ([#444])
|
||||
|
||||
[#24]: https://github.com/hecrj/iced/issues/24
|
||||
[#25]: https://github.com/hecrj/iced/issues/25
|
||||
@ -27,8 +25,6 @@ Once a step is completed, it is collapsed and added to this list:
|
||||
[#52]: https://github.com/hecrj/iced/pull/52
|
||||
[#122]: https://github.com/hecrj/iced/pull/122
|
||||
[#146]: https://github.com/hecrj/iced/pull/146
|
||||
[#193]: https://github.com/hecrj/iced/pull/193
|
||||
[#444]: https://github.com/hecrj/iced/pull/444
|
||||
|
||||
### Multi-window support ([#27])
|
||||
Open and control multiple windows at runtime.
|
||||
@ -39,6 +35,17 @@ This approach should also allow us to perform custom optimizations for this part
|
||||
|
||||
[#27]: https://github.com/hecrj/iced/issues/27
|
||||
|
||||
### Layers ([#30])
|
||||
Currently, Iced assumes widgets cannot be laid out on top of each other. We should implement support for multiple layers of widgets.
|
||||
|
||||
This is a necessary feature to implement many kinds of interactables, like dropdown menus, select fields, etc.
|
||||
|
||||
`iced_native` will need to group widgets to perform layouting and process some events first for widgets positioned on top.
|
||||
|
||||
`iced_wgpu` will also need to process the scene graph and sort draw calls based on the different layers.
|
||||
|
||||
[#30]: https://github.com/hecrj/iced/issues/30
|
||||
|
||||
### Animations ([#31])
|
||||
Allow widgets to request a redraw at a specific time.
|
||||
|
||||
@ -48,8 +55,8 @@ This is a necessary feature to render loading spinners, a blinking text cursor,
|
||||
|
||||
[#31]: https://github.com/hecrj/iced/issues/31
|
||||
|
||||
### Canvas widget for 3D graphics ([#32])
|
||||
A widget to draw freely in 3D. It could be used to draw charts, implement a Paint clone, a CAD application, etc.
|
||||
### Canvas widget ([#32])
|
||||
A widget to draw freely in 2D or 3D. It could be used to draw charts, implement a Paint clone, a CAD application, etc.
|
||||
|
||||
As a first approach, we could expose the underlying renderer directly here, and couple this widget with it ([`wgpu`] for now). Once [`wgpu`] gets WebGL or WebGPU support, this widget will be able to run on the web too. The renderer primitive could be a simple texture that the widget draws to.
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "iced_core"
|
||||
version = "0.4.0"
|
||||
version = "0.2.1"
|
||||
authors = ["Héctor Ramón Jiménez <hector0193@gmail.com>"]
|
||||
edition = "2018"
|
||||
description = "The essential concepts of Iced"
|
||||
|
@ -8,9 +8,7 @@
|
||||
|
||||
This crate is meant to be a starting point for an Iced runtime.
|
||||
|
||||
<p align="center">
|
||||
<img alt="The foundations" src="../docs/graphs/foundations.png" width="50%">
|
||||
</p>
|
||||

|
||||
|
||||
[documentation]: https://docs.rs/iced_core
|
||||
|
||||
@ -18,7 +16,7 @@ This crate is meant to be a starting point for an Iced runtime.
|
||||
Add `iced_core` as a dependency in your `Cargo.toml`:
|
||||
|
||||
```toml
|
||||
iced_core = "0.4"
|
||||
iced_core = "0.2"
|
||||
```
|
||||
|
||||
__Iced moves fast and the `master` branch can contain breaking changes!__ If
|
||||
|
@ -43,6 +43,8 @@ impl Color {
|
||||
///
|
||||
/// In debug mode, it will panic if the values are not in the correct
|
||||
/// range: 0.0 - 1.0
|
||||
///
|
||||
/// [`Color`]: struct.Color.html
|
||||
pub fn new(r: f32, g: f32, b: f32, a: f32) -> Color {
|
||||
debug_assert!(
|
||||
(0.0..=1.0).contains(&r),
|
||||
@ -65,21 +67,29 @@ impl Color {
|
||||
}
|
||||
|
||||
/// Creates a [`Color`] from its RGB components.
|
||||
///
|
||||
/// [`Color`]: struct.Color.html
|
||||
pub const fn from_rgb(r: f32, g: f32, b: f32) -> Color {
|
||||
Color::from_rgba(r, g, b, 1.0f32)
|
||||
}
|
||||
|
||||
/// Creates a [`Color`] from its RGBA components.
|
||||
///
|
||||
/// [`Color`]: struct.Color.html
|
||||
pub const fn from_rgba(r: f32, g: f32, b: f32, a: f32) -> Color {
|
||||
Color { r, g, b, a }
|
||||
}
|
||||
|
||||
/// Creates a [`Color`] from its RGB8 components.
|
||||
///
|
||||
/// [`Color`]: struct.Color.html
|
||||
pub fn from_rgb8(r: u8, g: u8, b: u8) -> Color {
|
||||
Color::from_rgba8(r, g, b, 1.0)
|
||||
}
|
||||
|
||||
/// Creates a [`Color`] from its RGB8 components and an alpha value.
|
||||
///
|
||||
/// [`Color`]: struct.Color.html
|
||||
pub fn from_rgba8(r: u8, g: u8, b: u8, a: f32) -> Color {
|
||||
Color {
|
||||
r: f32::from(r) / 255.0,
|
||||
@ -90,6 +100,8 @@ impl Color {
|
||||
}
|
||||
|
||||
/// Converts the [`Color`] into its linear values.
|
||||
///
|
||||
/// [`Color`]: struct.Color.html
|
||||
pub fn into_linear(self) -> [f32; 4] {
|
||||
// As described in:
|
||||
// https://en.wikipedia.org/wiki/SRGB#The_reverse_transformation
|
||||
@ -110,6 +122,8 @@ impl Color {
|
||||
}
|
||||
|
||||
/// Inverts the [`Color`] in-place.
|
||||
///
|
||||
/// [`Color`]: struct.Color.html
|
||||
pub fn invert(&mut self) {
|
||||
self.r = 1.0f32 - self.r;
|
||||
self.b = 1.0f32 - self.g;
|
||||
@ -117,6 +131,8 @@ impl Color {
|
||||
}
|
||||
|
||||
/// Returns the inverted [`Color`].
|
||||
///
|
||||
/// [`Color`]: struct.Color.html
|
||||
pub fn inverse(self) -> Color {
|
||||
Color::new(1.0f32 - self.r, 1.0f32 - self.g, 1.0f32 - self.b, self.a)
|
||||
}
|
||||
@ -136,6 +152,8 @@ impl From<[f32; 4]> for Color {
|
||||
|
||||
#[cfg(feature = "palette")]
|
||||
/// Converts from palette's `Srgba` type to a [`Color`].
|
||||
///
|
||||
/// [`Color`]: struct.Color.html
|
||||
impl From<Srgba> for Color {
|
||||
fn from(srgba: Srgba) -> Self {
|
||||
Color::new(srgba.red, srgba.green, srgba.blue, srgba.alpha)
|
||||
@ -144,6 +162,8 @@ impl From<Srgba> for Color {
|
||||
|
||||
#[cfg(feature = "palette")]
|
||||
/// Converts from [`Color`] to palette's `Srgba` type.
|
||||
///
|
||||
/// [`Color`]: struct.Color.html
|
||||
impl From<Color> for Srgba {
|
||||
fn from(c: Color) -> Self {
|
||||
Srgba::new(c.r, c.g, c.b, c.a)
|
||||
@ -152,6 +172,8 @@ impl From<Color> for Srgba {
|
||||
|
||||
#[cfg(feature = "palette")]
|
||||
/// Converts from palette's `Srgb` type to a [`Color`].
|
||||
///
|
||||
/// [`Color`]: struct.Color.html
|
||||
impl From<Srgb> for Color {
|
||||
fn from(srgb: Srgb) -> Self {
|
||||
Color::new(srgb.red, srgb.green, srgb.blue, 1.0)
|
||||
@ -160,6 +182,9 @@ impl From<Srgb> for Color {
|
||||
|
||||
#[cfg(feature = "palette")]
|
||||
/// Converts from [`Color`] to palette's `Srgb` type.
|
||||
///
|
||||
/// [`Color`]: struct.Color.html
|
||||
/// [`Srgb`]: ../palette/rgb/type.Srgb.html
|
||||
impl From<Color> for Srgb {
|
||||
fn from(c: Color) -> Self {
|
||||
Srgb::new(c.r, c.g, c.b)
|
||||
|
@ -1,8 +1,8 @@
|
||||
//! Reuse basic keyboard types.
|
||||
mod event;
|
||||
mod key_code;
|
||||
mod modifiers;
|
||||
mod modifiers_state;
|
||||
|
||||
pub use event::Event;
|
||||
pub use key_code::KeyCode;
|
||||
pub use modifiers::Modifiers;
|
||||
pub use modifiers_state::ModifiersState;
|
||||
|
@ -1,4 +1,4 @@
|
||||
use super::{KeyCode, Modifiers};
|
||||
use super::{KeyCode, ModifiersState};
|
||||
|
||||
/// A keyboard event.
|
||||
///
|
||||
@ -14,7 +14,7 @@ pub enum Event {
|
||||
key_code: KeyCode,
|
||||
|
||||
/// The state of the modifier keys
|
||||
modifiers: Modifiers,
|
||||
modifiers: ModifiersState,
|
||||
},
|
||||
|
||||
/// A keyboard key was released.
|
||||
@ -23,12 +23,12 @@ pub enum Event {
|
||||
key_code: KeyCode,
|
||||
|
||||
/// The state of the modifier keys
|
||||
modifiers: Modifiers,
|
||||
modifiers: ModifiersState,
|
||||
},
|
||||
|
||||
/// A unicode character was received.
|
||||
CharacterReceived(char),
|
||||
|
||||
/// The keyboard modifiers have changed.
|
||||
ModifiersChanged(Modifiers),
|
||||
ModifiersChanged(ModifiersState),
|
||||
}
|
||||
|
@ -55,7 +55,7 @@ pub enum KeyCode {
|
||||
Y,
|
||||
Z,
|
||||
|
||||
/// The Escape key, next to F1.
|
||||
/// The Escape key, next to F1
|
||||
Escape,
|
||||
|
||||
F1,
|
||||
@ -83,14 +83,14 @@ pub enum KeyCode {
|
||||
F23,
|
||||
F24,
|
||||
|
||||
/// Print Screen/SysRq.
|
||||
/// Print Screen/SysRq
|
||||
Snapshot,
|
||||
/// Scroll Lock.
|
||||
/// Scroll Lock
|
||||
Scroll,
|
||||
/// Pause/Break key, next to Scroll lock.
|
||||
/// Pause/Break key, next to Scroll lock
|
||||
Pause,
|
||||
|
||||
/// `Insert`, next to Backspace.
|
||||
/// `Insert`, next to Backspace
|
||||
Insert,
|
||||
Home,
|
||||
Delete,
|
||||
@ -103,14 +103,11 @@ pub enum KeyCode {
|
||||
Right,
|
||||
Down,
|
||||
|
||||
/// The Backspace key, right over Enter.
|
||||
Backspace,
|
||||
/// The Enter key.
|
||||
Enter,
|
||||
/// The space bar.
|
||||
Space,
|
||||
|
||||
/// The "Compose" key on Linux.
|
||||
/// The "Compose" key on Linux
|
||||
Compose,
|
||||
|
||||
Caret,
|
||||
@ -126,20 +123,12 @@ pub enum KeyCode {
|
||||
Numpad7,
|
||||
Numpad8,
|
||||
Numpad9,
|
||||
NumpadAdd,
|
||||
NumpadDivide,
|
||||
NumpadDecimal,
|
||||
NumpadComma,
|
||||
NumpadEnter,
|
||||
NumpadEquals,
|
||||
NumpadMultiply,
|
||||
NumpadSubtract,
|
||||
|
||||
AbntC1,
|
||||
AbntC2,
|
||||
Add,
|
||||
Apostrophe,
|
||||
Apps,
|
||||
Asterisk,
|
||||
At,
|
||||
Ax,
|
||||
Backslash,
|
||||
@ -148,6 +137,8 @@ pub enum KeyCode {
|
||||
Colon,
|
||||
Comma,
|
||||
Convert,
|
||||
Decimal,
|
||||
Divide,
|
||||
Equals,
|
||||
Grave,
|
||||
Kana,
|
||||
@ -161,16 +152,19 @@ pub enum KeyCode {
|
||||
MediaSelect,
|
||||
MediaStop,
|
||||
Minus,
|
||||
Multiply,
|
||||
Mute,
|
||||
MyComputer,
|
||||
NavigateForward, // also called "Next"
|
||||
NavigateBackward, // also called "Prior"
|
||||
NavigateForward, // also called "Prior"
|
||||
NavigateBackward, // also called "Next"
|
||||
NextTrack,
|
||||
NoConvert,
|
||||
NumpadComma,
|
||||
NumpadEnter,
|
||||
NumpadEquals,
|
||||
OEM102,
|
||||
Period,
|
||||
PlayPause,
|
||||
Plus,
|
||||
Power,
|
||||
PrevTrack,
|
||||
RAlt,
|
||||
@ -182,6 +176,7 @@ pub enum KeyCode {
|
||||
Slash,
|
||||
Sleep,
|
||||
Stop,
|
||||
Subtract,
|
||||
Sysrq,
|
||||
Tab,
|
||||
Underline,
|
||||
|
@ -1,45 +0,0 @@
|
||||
/// The current state of the keyboard modifiers.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
|
||||
pub struct Modifiers {
|
||||
/// Whether a shift key is pressed
|
||||
pub shift: bool,
|
||||
|
||||
/// Whether a control key is pressed
|
||||
pub control: bool,
|
||||
|
||||
/// Whether an alt key is pressed
|
||||
pub alt: bool,
|
||||
|
||||
/// Whether a logo key is pressed (e.g. windows key, command key...)
|
||||
pub logo: bool,
|
||||
}
|
||||
|
||||
impl Modifiers {
|
||||
/// Returns true if a "command key" is pressed in the [`Modifiers`].
|
||||
///
|
||||
/// The "command key" is the main modifier key used to issue commands in the
|
||||
/// current platform. Specifically:
|
||||
///
|
||||
/// - It is the `logo` or command key (⌘) on macOS
|
||||
/// - It is the `control` key on other platforms
|
||||
pub fn is_command_pressed(self) -> bool {
|
||||
#[cfg(target_os = "macos")]
|
||||
let is_pressed = self.logo;
|
||||
|
||||
#[cfg(not(target_os = "macos"))]
|
||||
let is_pressed = self.control;
|
||||
|
||||
is_pressed
|
||||
}
|
||||
|
||||
/// Returns true if the current [`Modifiers`] have at least the same
|
||||
/// keys pressed as the provided ones, and false otherwise.
|
||||
pub fn matches(&self, modifiers: Self) -> bool {
|
||||
let shift = !modifiers.shift || self.shift;
|
||||
let control = !modifiers.control || self.control;
|
||||
let alt = !modifiers.alt || self.alt;
|
||||
let logo = !modifiers.logo || self.logo;
|
||||
|
||||
shift && control && alt && logo
|
||||
}
|
||||
}
|
30
core/src/keyboard/modifiers_state.rs
Normal file
@ -0,0 +1,30 @@
|
||||
/// The current state of the keyboard modifiers.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
|
||||
pub struct ModifiersState {
|
||||
/// Whether a shift key is pressed
|
||||
pub shift: bool,
|
||||
|
||||
/// Whether a control key is pressed
|
||||
pub control: bool,
|
||||
|
||||
/// Whether an alt key is pressed
|
||||
pub alt: bool,
|
||||
|
||||
/// Whether a logo key is pressed (e.g. windows key, command key...)
|
||||
pub logo: bool,
|
||||
}
|
||||
|
||||
impl ModifiersState {
|
||||
/// Returns true if the current [`ModifiersState`] has at least the same
|
||||
/// modifiers enabled as the given value, and false otherwise.
|
||||
///
|
||||
/// [`ModifiersState`]: struct.ModifiersState.html
|
||||
pub fn matches(&self, modifiers: ModifiersState) -> bool {
|
||||
let shift = !modifiers.shift || self.shift;
|
||||
let control = !modifiers.control || self.control;
|
||||
let alt = !modifiers.alt || self.alt;
|
||||
let logo = !modifiers.logo || self.logo;
|
||||
|
||||
shift && control && alt && logo
|
||||
}
|
||||
}
|
@ -26,6 +26,8 @@ impl Length {
|
||||
/// The _fill factor_ is a relative unit describing how much of the
|
||||
/// remaining space should be filled when compared to other elements. It
|
||||
/// is only meant to be used by layout engines.
|
||||
///
|
||||
/// [`Length`]: enum.Length.html
|
||||
pub fn fill_factor(&self) -> u16 {
|
||||
match self {
|
||||
Length::Fill => 1,
|
||||
|
@ -1,11 +1,11 @@
|
||||
//! The core library of [Iced].
|
||||
//!
|
||||
//! 
|
||||
//!
|
||||
//! This library holds basic types that can be reused and re-exported in
|
||||
//! different runtime implementations. For instance, both [`iced_native`] and
|
||||
//! [`iced_web`] are built on top of `iced_core`.
|
||||
//!
|
||||
//! 
|
||||
//!
|
||||
//! [Iced]: https://github.com/hecrj/iced
|
||||
//! [`iced_native`]: https://github.com/hecrj/iced/tree/master/native
|
||||
//! [`iced_web`]: https://github.com/hecrj/iced/tree/master/web
|
||||
@ -22,7 +22,6 @@ mod background;
|
||||
mod color;
|
||||
mod font;
|
||||
mod length;
|
||||
mod padding;
|
||||
mod point;
|
||||
mod rectangle;
|
||||
mod size;
|
||||
@ -33,7 +32,6 @@ pub use background::Background;
|
||||
pub use color::Color;
|
||||
pub use font::Font;
|
||||
pub use length::Length;
|
||||
pub use padding::Padding;
|
||||
pub use point::Point;
|
||||
pub use rectangle::Rectangle;
|
||||
pub use size::Size;
|
||||
|
@ -1,5 +1,3 @@
|
||||
use crate::Point;
|
||||
|
||||
use super::Button;
|
||||
|
||||
/// A mouse event.
|
||||
@ -18,8 +16,11 @@ pub enum Event {
|
||||
|
||||
/// The mouse cursor was moved
|
||||
CursorMoved {
|
||||
/// The new position of the mouse cursor
|
||||
position: Point,
|
||||
/// The X coordinate of the mouse position
|
||||
x: f32,
|
||||
|
||||
/// The Y coordinate of the mouse position
|
||||
y: f32,
|
||||
},
|
||||
|
||||
/// A mouse button was pressed.
|
||||
|
@ -1,107 +0,0 @@
|
||||
/// An amount of space to pad for each side of a box
|
||||
///
|
||||
/// You can leverage the `From` trait to build [`Padding`] conveniently:
|
||||
///
|
||||
/// ```
|
||||
/// # use iced_core::Padding;
|
||||
/// #
|
||||
/// let padding = Padding::from(20); // 20px on all sides
|
||||
/// let padding = Padding::from([10, 20]); // top/bottom, left/right
|
||||
/// let padding = Padding::from([5, 10, 15, 20]); // top, right, bottom, left
|
||||
/// ```
|
||||
///
|
||||
/// Normally, the `padding` method of a widget will ask for an `Into<Padding>`,
|
||||
/// so you can easily write:
|
||||
///
|
||||
/// ```
|
||||
/// # use iced_core::Padding;
|
||||
/// #
|
||||
/// # struct Widget;
|
||||
/// #
|
||||
/// impl Widget {
|
||||
/// # pub fn new() -> Self { Self }
|
||||
/// #
|
||||
/// pub fn padding(mut self, padding: impl Into<Padding>) -> Self {
|
||||
/// // ...
|
||||
/// self
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// let widget = Widget::new().padding(20); // 20px on all sides
|
||||
/// let widget = Widget::new().padding([10, 20]); // top/bottom, left/right
|
||||
/// let widget = Widget::new().padding([5, 10, 15, 20]); // top, right, bottom, left
|
||||
/// ```
|
||||
#[derive(Debug, Hash, Copy, Clone)]
|
||||
pub struct Padding {
|
||||
/// Top padding
|
||||
pub top: u16,
|
||||
/// Right padding
|
||||
pub right: u16,
|
||||
/// Bottom padding
|
||||
pub bottom: u16,
|
||||
/// Left padding
|
||||
pub left: u16,
|
||||
}
|
||||
|
||||
impl Padding {
|
||||
/// Padding of zero
|
||||
pub const ZERO: Padding = Padding {
|
||||
top: 0,
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
left: 0,
|
||||
};
|
||||
|
||||
/// Create a Padding that is equal on all sides
|
||||
pub const fn new(padding: u16) -> Padding {
|
||||
Padding {
|
||||
top: padding,
|
||||
right: padding,
|
||||
bottom: padding,
|
||||
left: padding,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the total amount of vertical [`Padding`].
|
||||
pub fn vertical(self) -> u16 {
|
||||
self.top + self.bottom
|
||||
}
|
||||
|
||||
/// Returns the total amount of horizontal [`Padding`].
|
||||
pub fn horizontal(self) -> u16 {
|
||||
self.left + self.right
|
||||
}
|
||||
}
|
||||
|
||||
impl std::convert::From<u16> for Padding {
|
||||
fn from(p: u16) -> Self {
|
||||
Padding {
|
||||
top: p,
|
||||
right: p,
|
||||
bottom: p,
|
||||
left: p,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::convert::From<[u16; 2]> for Padding {
|
||||
fn from(p: [u16; 2]) -> Self {
|
||||
Padding {
|
||||
top: p[0],
|
||||
right: p[1],
|
||||
bottom: p[0],
|
||||
left: p[1],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::convert::From<[u16; 4]> for Padding {
|
||||
fn from(p: [u16; 4]) -> Self {
|
||||
Padding {
|
||||
top: p[0],
|
||||
right: p[1],
|
||||
bottom: p[2],
|
||||
left: p[3],
|
||||
}
|
||||
}
|
||||
}
|
@ -12,14 +12,20 @@ pub struct Point {
|
||||
|
||||
impl Point {
|
||||
/// The origin (i.e. a [`Point`] at (0, 0)).
|
||||
///
|
||||
/// [`Point`]: struct.Point.html
|
||||
pub const ORIGIN: Point = Point::new(0.0, 0.0);
|
||||
|
||||
/// Creates a new [`Point`] with the given coordinates.
|
||||
///
|
||||
/// [`Point`]: struct.Point.html
|
||||
pub const fn new(x: f32, y: f32) -> Self {
|
||||
Self { x, y }
|
||||
}
|
||||
|
||||
/// Computes the distance to another [`Point`].
|
||||
///
|
||||
/// [`Point`]: struct.Point.html
|
||||
pub fn distance(&self, to: Point) -> f32 {
|
||||
let a = self.x - to.x;
|
||||
let b = self.y - to.y;
|
||||
@ -40,12 +46,6 @@ impl From<[u16; 2]> for Point {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Point> for [f32; 2] {
|
||||
fn from(point: Point) -> [f32; 2] {
|
||||
[point.x, point.y]
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::Add<Vector> for Point {
|
||||
type Output = Self;
|
||||
|
||||
|
@ -19,6 +19,10 @@ pub struct Rectangle<T = f32> {
|
||||
impl Rectangle<f32> {
|
||||
/// Creates a new [`Rectangle`] with its top-left corner in the given
|
||||
/// [`Point`] and with the provided [`Size`].
|
||||
///
|
||||
/// [`Rectangle`]: struct.Rectangle.html
|
||||
/// [`Point`]: struct.Point.html
|
||||
/// [`Size`]: struct.Size.html
|
||||
pub fn new(top_left: Point, size: Size) -> Self {
|
||||
Self {
|
||||
x: top_left.x,
|
||||
@ -30,6 +34,9 @@ impl Rectangle<f32> {
|
||||
|
||||
/// Creates a new [`Rectangle`] with its top-left corner at the origin
|
||||
/// and with the provided [`Size`].
|
||||
///
|
||||
/// [`Rectangle`]: struct.Rectangle.html
|
||||
/// [`Size`]: struct.Size.html
|
||||
pub fn with_size(size: Size) -> Self {
|
||||
Self {
|
||||
x: 0.0,
|
||||
@ -40,33 +47,50 @@ impl Rectangle<f32> {
|
||||
}
|
||||
|
||||
/// Returns the [`Point`] at the center of the [`Rectangle`].
|
||||
///
|
||||
/// [`Point`]: struct.Point.html
|
||||
/// [`Rectangle`]: struct.Rectangle.html
|
||||
pub fn center(&self) -> Point {
|
||||
Point::new(self.center_x(), self.center_y())
|
||||
}
|
||||
|
||||
/// Returns the X coordinate of the [`Point`] at the center of the
|
||||
/// [`Rectangle`].
|
||||
///
|
||||
/// [`Point`]: struct.Point.html
|
||||
/// [`Rectangle`]: struct.Rectangle.html
|
||||
pub fn center_x(&self) -> f32 {
|
||||
self.x + self.width / 2.0
|
||||
}
|
||||
|
||||
/// Returns the Y coordinate of the [`Point`] at the center of the
|
||||
/// [`Rectangle`].
|
||||
///
|
||||
/// [`Point`]: struct.Point.html
|
||||
/// [`Rectangle`]: struct.Rectangle.html
|
||||
pub fn center_y(&self) -> f32 {
|
||||
self.y + self.height / 2.0
|
||||
}
|
||||
|
||||
/// Returns the position of the top left corner of the [`Rectangle`].
|
||||
///
|
||||
/// [`Rectangle`]: struct.Rectangle.html
|
||||
pub fn position(&self) -> Point {
|
||||
Point::new(self.x, self.y)
|
||||
}
|
||||
|
||||
/// Returns the [`Size`] of the [`Rectangle`].
|
||||
///
|
||||
/// [`Size`]: struct.Size.html
|
||||
/// [`Rectangle`]: struct.Rectangle.html
|
||||
pub fn size(&self) -> Size {
|
||||
Size::new(self.width, self.height)
|
||||
}
|
||||
|
||||
/// Returns true if the given [`Point`] is contained in the [`Rectangle`].
|
||||
///
|
||||
/// [`Point`]: struct.Point.html
|
||||
/// [`Rectangle`]: struct.Rectangle.html
|
||||
pub fn contains(&self, point: Point) -> bool {
|
||||
self.x <= point.x
|
||||
&& point.x <= self.x + self.width
|
||||
@ -75,6 +99,8 @@ impl Rectangle<f32> {
|
||||
}
|
||||
|
||||
/// Computes the intersection with the given [`Rectangle`].
|
||||
///
|
||||
/// [`Rectangle`]: struct.Rectangle.html
|
||||
pub fn intersection(
|
||||
&self,
|
||||
other: &Rectangle<f32>,
|
||||
@ -101,12 +127,14 @@ impl Rectangle<f32> {
|
||||
}
|
||||
|
||||
/// Snaps the [`Rectangle`] to __unsigned__ integer coordinates.
|
||||
///
|
||||
/// [`Rectangle`]: struct.Rectangle.html
|
||||
pub fn snap(self) -> Rectangle<u32> {
|
||||
Rectangle {
|
||||
x: self.x as u32,
|
||||
y: self.y as u32,
|
||||
width: self.width as u32,
|
||||
height: self.height as u32,
|
||||
width: self.width.ceil() as u32,
|
||||
height: self.height.ceil() as u32,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,3 @@
|
||||
use crate::{Padding, Vector};
|
||||
use std::f32;
|
||||
|
||||
/// An amount of space in 2 dimensions.
|
||||
@ -12,6 +11,8 @@ pub struct Size<T = f32> {
|
||||
|
||||
impl<T> Size<T> {
|
||||
/// Creates a new [`Size`] with the given width and height.
|
||||
///
|
||||
/// [`Size`]: struct.Size.html
|
||||
pub const fn new(width: T, height: T) -> Self {
|
||||
Size { width, height }
|
||||
}
|
||||
@ -19,19 +20,27 @@ impl<T> Size<T> {
|
||||
|
||||
impl Size {
|
||||
/// A [`Size`] with zero width and height.
|
||||
///
|
||||
/// [`Size`]: struct.Size.html
|
||||
pub const ZERO: Size = Size::new(0., 0.);
|
||||
|
||||
/// A [`Size`] with a width and height of 1 unit.
|
||||
///
|
||||
/// [`Size`]: struct.Size.html
|
||||
pub const UNIT: Size = Size::new(1., 1.);
|
||||
|
||||
/// A [`Size`] with infinite width and height.
|
||||
///
|
||||
/// [`Size`]: struct.Size.html
|
||||
pub const INFINITY: Size = Size::new(f32::INFINITY, f32::INFINITY);
|
||||
|
||||
/// Increments the [`Size`] to account for the given padding.
|
||||
pub fn pad(&self, padding: Padding) -> Self {
|
||||
///
|
||||
/// [`Size`]: struct.Size.html
|
||||
pub fn pad(&self, padding: f32) -> Self {
|
||||
Size {
|
||||
width: self.width + padding.horizontal() as f32,
|
||||
height: self.height + padding.vertical() as f32,
|
||||
width: self.width + padding * 2.0,
|
||||
height: self.height + padding * 2.0,
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -47,24 +56,3 @@ impl From<[u16; 2]> for Size {
|
||||
Size::new(width.into(), height.into())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Vector<f32>> for Size {
|
||||
fn from(vector: Vector<f32>) -> Self {
|
||||
Size {
|
||||
width: vector.x,
|
||||
height: vector.y,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Size> for [f32; 2] {
|
||||
fn from(size: Size) -> [f32; 2] {
|
||||
[size.width, size.height]
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Size> for Vector<f32> {
|
||||
fn from(size: Size) -> Self {
|
||||
Vector::new(size.width, size.height)
|
||||
}
|
||||
}
|
||||
|
@ -2,14 +2,20 @@
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub struct Vector<T = f32> {
|
||||
/// The X component of the [`Vector`]
|
||||
///
|
||||
/// [`Vector`]: struct.Vector.html
|
||||
pub x: T,
|
||||
|
||||
/// The Y component of the [`Vector`]
|
||||
///
|
||||
/// [`Vector`]: struct.Vector.html
|
||||
pub y: T,
|
||||
}
|
||||
|
||||
impl<T> Vector<T> {
|
||||
/// Creates a new [`Vector`] with the given components.
|
||||
///
|
||||
/// [`Vector`]: struct.Vector.html
|
||||
pub const fn new(x: T, y: T) -> Self {
|
||||
Self { x, y }
|
||||
}
|
||||
@ -59,18 +65,3 @@ where
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> From<[T; 2]> for Vector<T> {
|
||||
fn from([x, y]: [T; 2]) -> Self {
|
||||
Self::new(x, y)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> From<Vector<T>> for [T; 2]
|
||||
where
|
||||
T: Copy,
|
||||
{
|
||||
fn from(other: Vector<T>) -> Self {
|
||||
[other.x, other.y]
|
||||
}
|
||||
}
|
||||
|
13
docs/graphs/core.dot
Normal file
@ -0,0 +1,13 @@
|
||||
digraph G {
|
||||
fontname = "Roboto";
|
||||
newrank=true;
|
||||
node[fontname = "Roboto", style="filled", fontcolor="#333333", fillcolor=white, color="#333333"];
|
||||
edge[color="#333333"];
|
||||
|
||||
{ rank = same; iced_native iced_web }
|
||||
|
||||
iced_core -> iced_native [style=dashed];
|
||||
iced_core -> iced_web [style=dashed];
|
||||
|
||||
iced_core [style=dashed];
|
||||
}
|
BIN
docs/graphs/core.png
Normal file
After Width: | Height: | Size: 13 KiB |
56
docs/graphs/ecosystem.dot
Normal file
@ -0,0 +1,56 @@
|
||||
digraph G {
|
||||
fontname = "Roboto";
|
||||
newrank=true;
|
||||
node[fontname = "Roboto", style="filled", fontcolor="#333333", fillcolor=white, color="#333333"];
|
||||
edge[color="#333333"];
|
||||
|
||||
subgraph cluster_1 {
|
||||
label = "renderers ";
|
||||
labelloc = "b";
|
||||
labeljust = "r";
|
||||
fontcolor = "#0366d6";
|
||||
color="#f6f8fa";
|
||||
bgcolor="#f6f8fa";
|
||||
style=rounded;
|
||||
|
||||
etc_1 [label="...", style=solid, shape=none];
|
||||
iced_wgpu;
|
||||
}
|
||||
|
||||
subgraph cluster_2 {
|
||||
label = "shells ";
|
||||
labelloc = "b";
|
||||
labeljust = "r";
|
||||
fontcolor = "#0366d6";
|
||||
color="#f6f8fa";
|
||||
bgcolor="#f6f8fa";
|
||||
style=rounded;
|
||||
|
||||
etc_2 [label="...", style=solid, shape=none];
|
||||
iced_winit;
|
||||
}
|
||||
|
||||
subgraph cluster_3 {
|
||||
style=invis;
|
||||
margin=20;
|
||||
iced;
|
||||
}
|
||||
|
||||
{ rank = same; iced_native iced_web }
|
||||
{ rank = same; iced_wgpu iced_winit etc_1 etc_2 }
|
||||
|
||||
iced_core -> iced_native [style=dashed];
|
||||
iced_core -> iced_web [style=dashed];
|
||||
iced_native -> iced_wgpu;
|
||||
iced_native -> iced_winit;
|
||||
|
||||
iced_winit -> iced;
|
||||
iced_wgpu -> iced;
|
||||
iced_web -> iced;
|
||||
|
||||
iced -> "cross-platform application";
|
||||
|
||||
iced_core [style=dashed];
|
||||
|
||||
"cross-platform application" [shape=box, width=2.8, height=0.6];
|
||||
}
|
Before Width: | Height: | Size: 124 KiB After Width: | Height: | Size: 44 KiB |
Before Width: | Height: | Size: 18 KiB |
6
docs/graphs/generate.sh
Executable file
@ -0,0 +1,6 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
for file in *.dot
|
||||
do
|
||||
dot -Tpng ${file} -o ${file%.*}.png
|
||||
done
|
46
docs/graphs/iced.dot
Normal file
@ -0,0 +1,46 @@
|
||||
digraph G {
|
||||
fontname = "Roboto";
|
||||
newrank=true;
|
||||
node[fontname = "Roboto", style="filled", fontcolor="#333333", fillcolor=white, color="#333333"];
|
||||
edge[color="#333333"];
|
||||
|
||||
subgraph cluster_1 {
|
||||
label = "renderers ";
|
||||
labelloc = "b";
|
||||
labeljust = "r";
|
||||
fontcolor = "#0366d6";
|
||||
color="#f6f8fa";
|
||||
bgcolor="#f6f8fa";
|
||||
style=rounded;
|
||||
|
||||
etc_1 [label="...", style=solid, shape=none];
|
||||
iced_wgpu;
|
||||
}
|
||||
|
||||
subgraph cluster_2 {
|
||||
label = "shells ";
|
||||
labelloc = "b";
|
||||
labeljust = "r";
|
||||
fontcolor = "#0366d6";
|
||||
color="#f6f8fa";
|
||||
bgcolor="#f6f8fa";
|
||||
style=rounded;
|
||||
|
||||
etc_2 [label="...", style=solid, shape=none];
|
||||
iced_winit;
|
||||
}
|
||||
|
||||
subgraph cluster_3 {
|
||||
style=invis;
|
||||
margin=20;
|
||||
iced;
|
||||
}
|
||||
|
||||
{ rank = same; iced_wgpu iced_winit etc_1 etc_2 }
|
||||
|
||||
iced_winit -> iced;
|
||||
iced_wgpu -> iced;
|
||||
iced_web -> iced;
|
||||
|
||||
iced;
|
||||
}
|
Before Width: | Height: | Size: 98 KiB After Width: | Height: | Size: 26 KiB |
41
docs/graphs/native.dot
Normal file
@ -0,0 +1,41 @@
|
||||
digraph G {
|
||||
fontname = "Roboto";
|
||||
newrank=true;
|
||||
node[fontname = "Roboto", style="filled", fontcolor="#333333", fillcolor=white, color="#333333"];
|
||||
edge[color="#333333"];
|
||||
|
||||
subgraph cluster_1 {
|
||||
label = "renderers ";
|
||||
labelloc = "b";
|
||||
labeljust = "r";
|
||||
fontcolor = "#0366d6";
|
||||
color="#f6f8fa";
|
||||
bgcolor="#f6f8fa";
|
||||
style=rounded;
|
||||
|
||||
etc_1 [label="...", style=solid, shape=none];
|
||||
iced_wgpu;
|
||||
}
|
||||
|
||||
subgraph cluster_2 {
|
||||
label = "shells ";
|
||||
labelloc = "b";
|
||||
labeljust = "r";
|
||||
fontcolor = "#0366d6";
|
||||
color="#f6f8fa";
|
||||
bgcolor="#f6f8fa";
|
||||
style=rounded;
|
||||
|
||||
etc_2 [label="...", style=solid, shape=none];
|
||||
iced_winit;
|
||||
}
|
||||
|
||||
|
||||
{ rank = same; iced_wgpu iced_winit etc_1 etc_2 }
|
||||
|
||||
iced_core -> iced_native [style=dashed];
|
||||
iced_native -> iced_wgpu;
|
||||
iced_native -> iced_winit;
|
||||
|
||||
iced_core [style=dashed];
|
||||
}
|
Before Width: | Height: | Size: 58 KiB After Width: | Height: | Size: 24 KiB |
12
docs/graphs/web.dot
Normal file
@ -0,0 +1,12 @@
|
||||
digraph G {
|
||||
fontname = "Roboto";
|
||||
newrank=true;
|
||||
node[fontname = "Roboto", style="filled", fontcolor="#333333", fillcolor=white, color="#333333"];
|
||||
edge[color="#333333"];
|
||||
|
||||
iced_core -> iced_web [style=dashed];
|
||||
|
||||
iced_web -> iced;
|
||||
|
||||
iced_core [style=dashed];
|
||||
}
|
BIN
docs/graphs/web.png
Normal file
After Width: | Height: | Size: 11 KiB |
31
docs/graphs/wgpu.dot
Normal file
@ -0,0 +1,31 @@
|
||||
digraph G {
|
||||
fontname = "Roboto";
|
||||
newrank=true;
|
||||
node[fontname = "Roboto", style="filled", fontcolor="#333333", fillcolor=white, color="#333333"];
|
||||
edge[color="#333333"];
|
||||
|
||||
subgraph cluster_1 {
|
||||
label = "renderers ";
|
||||
labelloc = "b";
|
||||
labeljust = "r";
|
||||
fontcolor = "#0366d6";
|
||||
color="#f6f8fa";
|
||||
bgcolor="#f6f8fa";
|
||||
style=rounded;
|
||||
|
||||
etc_1 [label="...", style=solid, shape=none];
|
||||
iced_wgpu;
|
||||
}
|
||||
|
||||
subgraph cluster_3 {
|
||||
style=invis;
|
||||
margin=20;
|
||||
iced;
|
||||
}
|
||||
|
||||
{ rank = same; iced_wgpu etc_1 }
|
||||
|
||||
iced_native -> iced_wgpu;
|
||||
|
||||
iced_wgpu -> iced;
|
||||
}
|
BIN
docs/graphs/wgpu.png
Normal file
After Width: | Height: | Size: 16 KiB |
31
docs/graphs/winit.dot
Normal file
@ -0,0 +1,31 @@
|
||||
digraph G {
|
||||
fontname = "Roboto";
|
||||
newrank=true;
|
||||
node[fontname = "Roboto", style="filled", fontcolor="#333333", fillcolor=white, color="#333333"];
|
||||
edge[color="#333333"];
|
||||
|
||||
subgraph cluster_2 {
|
||||
label = "shells ";
|
||||
labelloc = "b";
|
||||
labeljust = "r";
|
||||
fontcolor = "#0366d6";
|
||||
color="#f6f8fa";
|
||||
bgcolor="#f6f8fa";
|
||||
style=rounded;
|
||||
|
||||
etc_2 [label="...", style=solid, shape=none];
|
||||
iced_winit;
|
||||
}
|
||||
|
||||
subgraph cluster_3 {
|
||||
style=invis;
|
||||
margin=20;
|
||||
iced;
|
||||
}
|
||||
|
||||
{ rank = same; iced_winit etc_2 }
|
||||
|
||||
iced_native -> iced_winit;
|
||||
|
||||
iced_winit -> iced;
|
||||
}
|
BIN
docs/graphs/winit.png
Normal file
After Width: | Height: | Size: 16 KiB |
@ -103,7 +103,6 @@ A bunch of simpler examples exist:
|
||||
- [`pick_list`](pick_list), a dropdown list of selectable options.
|
||||
- [`pokedex`](pokedex), an application that displays a random Pokédex entry (sprite included!) by using the [PokéAPI].
|
||||
- [`progress_bar`](progress_bar), a simple progress bar that can be filled by using a slider.
|
||||
- [`scrollable`](scrollable), a showcase of the various scrollbar width options.
|
||||
- [`solar_system`](solar_system), an animated solar system drawn using the `Canvas` widget and showcasing how to compose different transforms.
|
||||
- [`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.
|
||||
@ -118,7 +117,7 @@ cargo run --package <example>
|
||||
[Ghostscript Tiger]: https://commons.wikimedia.org/wiki/File:Ghostscript_Tiger.svg
|
||||
|
||||
## [Coffee]
|
||||
Since [Iced was born in May 2019], 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.
|
||||
|
||||
|
||||
@ -128,6 +127,6 @@ Since [Iced was born in May 2019], it has been powering the user interfaces in
|
||||
</a>
|
||||
</div>
|
||||
|
||||
[Iced was born in May 2019]: https://github.com/hecrj/coffee/pull/35
|
||||
[Iced was born in May]: https://github.com/hecrj/coffee/pull/35
|
||||
[`ui` module]: https://docs.rs/coffee/0.3.2/coffee/ui/index.html
|
||||
[Coffee]: https://github.com/hecrj/coffee
|
||||
|
@ -69,8 +69,7 @@ impl Sandbox for Example {
|
||||
|
||||
mod bezier {
|
||||
use iced::{
|
||||
canvas::event::{self, Event},
|
||||
canvas::{self, Canvas, Cursor, Frame, Geometry, Path, Stroke},
|
||||
canvas::{self, Canvas, Cursor, Event, Frame, Geometry, Path, Stroke},
|
||||
mouse, Element, Length, Point, Rectangle,
|
||||
};
|
||||
|
||||
@ -110,51 +109,41 @@ mod bezier {
|
||||
event: Event,
|
||||
bounds: Rectangle,
|
||||
cursor: Cursor,
|
||||
) -> (event::Status, Option<Curve>) {
|
||||
let cursor_position =
|
||||
if let Some(position) = cursor.position_in(&bounds) {
|
||||
position
|
||||
} else {
|
||||
return (event::Status::Ignored, None);
|
||||
};
|
||||
) -> Option<Curve> {
|
||||
let cursor_position = cursor.position_in(&bounds)?;
|
||||
|
||||
match event {
|
||||
Event::Mouse(mouse_event) => {
|
||||
let message = match mouse_event {
|
||||
mouse::Event::ButtonPressed(mouse::Button::Left) => {
|
||||
match self.state.pending {
|
||||
None => {
|
||||
self.state.pending = Some(Pending::One {
|
||||
from: cursor_position,
|
||||
});
|
||||
Event::Mouse(mouse_event) => match mouse_event {
|
||||
mouse::Event::ButtonPressed(mouse::Button::Left) => {
|
||||
match self.state.pending {
|
||||
None => {
|
||||
self.state.pending = Some(Pending::One {
|
||||
from: cursor_position,
|
||||
});
|
||||
None
|
||||
}
|
||||
Some(Pending::One { from }) => {
|
||||
self.state.pending = Some(Pending::Two {
|
||||
from,
|
||||
to: cursor_position,
|
||||
});
|
||||
|
||||
None
|
||||
}
|
||||
Some(Pending::One { from }) => {
|
||||
self.state.pending = Some(Pending::Two {
|
||||
from,
|
||||
to: cursor_position,
|
||||
});
|
||||
None
|
||||
}
|
||||
Some(Pending::Two { from, to }) => {
|
||||
self.state.pending = None;
|
||||
|
||||
None
|
||||
}
|
||||
Some(Pending::Two { from, to }) => {
|
||||
self.state.pending = None;
|
||||
|
||||
Some(Curve {
|
||||
from,
|
||||
to,
|
||||
control: cursor_position,
|
||||
})
|
||||
}
|
||||
Some(Curve {
|
||||
from,
|
||||
to,
|
||||
control: cursor_position,
|
||||
})
|
||||
}
|
||||
}
|
||||
_ => None,
|
||||
};
|
||||
|
||||
(event::Status::Captured, message)
|
||||
}
|
||||
_ => (event::Status::Ignored, None),
|
||||
}
|
||||
_ => None,
|
||||
},
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
use iced::{
|
||||
canvas::{self, Cache, Canvas, Cursor, Geometry, LineCap, Path, Stroke},
|
||||
executor, time, Application, Clipboard, Color, Command, Container, Element,
|
||||
Length, Point, Rectangle, Settings, Subscription, Vector,
|
||||
executor, time, Application, Color, Command, Container, Element, Length,
|
||||
Point, Rectangle, Settings, Subscription, Vector,
|
||||
};
|
||||
|
||||
pub fn main() -> iced::Result {
|
||||
@ -40,11 +40,7 @@ impl Application for Clock {
|
||||
String::from("Clock - Iced")
|
||||
}
|
||||
|
||||
fn update(
|
||||
&mut self,
|
||||
message: Message,
|
||||
_clipboard: &mut Clipboard,
|
||||
) -> Command<Message> {
|
||||
fn update(&mut self, message: Message) -> Command<Message> {
|
||||
match message {
|
||||
Message::Tick(local_time) => {
|
||||
let now = local_time;
|
||||
|
@ -284,7 +284,7 @@ impl<C: 'static + ColorSpace + Copy> ColorPicker<C> {
|
||||
let [s1, s2, s3] = &mut self.sliders;
|
||||
let [cr1, cr2, cr3] = C::COMPONENT_RANGES;
|
||||
|
||||
fn slider<C: Clone>(
|
||||
fn slider<C>(
|
||||
state: &mut slider::State,
|
||||
range: RangeInclusive<f64>,
|
||||
component: f32,
|
||||
|
@ -16,11 +16,11 @@ mod circle {
|
||||
};
|
||||
|
||||
pub struct Circle {
|
||||
radius: f32,
|
||||
radius: u16,
|
||||
}
|
||||
|
||||
impl Circle {
|
||||
pub fn new(radius: f32) -> Self {
|
||||
pub fn new(radius: u16) -> Self {
|
||||
Self { radius }
|
||||
}
|
||||
}
|
||||
@ -42,13 +42,16 @@ mod circle {
|
||||
_renderer: &Renderer<B>,
|
||||
_limits: &layout::Limits,
|
||||
) -> layout::Node {
|
||||
layout::Node::new(Size::new(self.radius * 2.0, self.radius * 2.0))
|
||||
layout::Node::new(Size::new(
|
||||
f32::from(self.radius) * 2.0,
|
||||
f32::from(self.radius) * 2.0,
|
||||
))
|
||||
}
|
||||
|
||||
fn hash_layout(&self, state: &mut Hasher) {
|
||||
use std::hash::Hash;
|
||||
|
||||
self.radius.to_bits().hash(state);
|
||||
self.radius.hash(state);
|
||||
}
|
||||
|
||||
fn draw(
|
||||
@ -64,7 +67,7 @@ mod circle {
|
||||
bounds: layout.bounds(),
|
||||
background: Background::Color(Color::BLACK),
|
||||
border_radius: self.radius,
|
||||
border_width: 0.0,
|
||||
border_width: 0,
|
||||
border_color: Color::TRANSPARENT,
|
||||
},
|
||||
mouse::Interaction::default(),
|
||||
@ -93,7 +96,7 @@ pub fn main() -> iced::Result {
|
||||
}
|
||||
|
||||
struct Example {
|
||||
radius: f32,
|
||||
radius: u16,
|
||||
slider: slider::State,
|
||||
}
|
||||
|
||||
@ -107,7 +110,7 @@ impl Sandbox for Example {
|
||||
|
||||
fn new() -> Self {
|
||||
Example {
|
||||
radius: 50.0,
|
||||
radius: 50,
|
||||
slider: slider::State::new(),
|
||||
}
|
||||
}
|
||||
@ -119,7 +122,7 @@ impl Sandbox for Example {
|
||||
fn update(&mut self, message: Message) {
|
||||
match message {
|
||||
Message::RadiusChanged(radius) => {
|
||||
self.radius = radius;
|
||||
self.radius = radius.round() as u16;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -131,16 +134,13 @@ impl Sandbox for Example {
|
||||
.max_width(500)
|
||||
.align_items(Align::Center)
|
||||
.push(Circle::new(self.radius))
|
||||
.push(Text::new(format!("Radius: {:.2}", self.radius)))
|
||||
.push(
|
||||
Slider::new(
|
||||
&mut self.slider,
|
||||
1.0..=100.0,
|
||||
self.radius,
|
||||
Message::RadiusChanged,
|
||||
)
|
||||
.step(0.01),
|
||||
);
|
||||
.push(Text::new(format!("Radius: {}", self.radius.to_string())))
|
||||
.push(Slider::new(
|
||||
&mut self.slider,
|
||||
1.0..=100.0,
|
||||
f32::from(self.radius),
|
||||
Message::RadiusChanged,
|
||||
));
|
||||
|
||||
Container::new(content)
|
||||
.width(Length::Fill)
|
||||
|
@ -1,7 +1,7 @@
|
||||
[package]
|
||||
name = "download_progress"
|
||||
version = "0.1.0"
|
||||
authors = ["Songtronix <contact@songtronix.com>", "Folyd <lyshuhow@gmail.com>"]
|
||||
authors = ["Songtronix <contact@songtronix.com>"]
|
||||
edition = "2018"
|
||||
publish = false
|
||||
|
||||
@ -9,4 +9,4 @@ publish = false
|
||||
iced = { path = "../..", features = ["tokio"] }
|
||||
iced_native = { path = "../../native" }
|
||||
iced_futures = { path = "../../futures" }
|
||||
reqwest = "0.11"
|
||||
reqwest = "0.10"
|
||||
|
@ -1,6 +1,6 @@
|
||||
## Download progress
|
||||
|
||||
A basic application that asynchronously downloads multiple dummy files of 100 MB and tracks the download progress.
|
||||
A basic application that asynchronously downloads a dummy file of 100 MB and tracks the download progress.
|
||||
|
||||
The example implements a custom `Subscription` in the __[`download`](src/download.rs)__ module. This subscription downloads and produces messages that can be used to keep track of its progress.
|
||||
|
||||
|
@ -1,46 +1,37 @@
|
||||
use iced_futures::futures;
|
||||
use std::hash::{Hash, Hasher};
|
||||
|
||||
// Just a little utility function
|
||||
pub fn file<I: 'static + Hash + Copy + Send, T: ToString>(
|
||||
id: I,
|
||||
url: T,
|
||||
) -> iced::Subscription<(I, Progress)> {
|
||||
pub fn file<T: ToString>(url: T) -> iced::Subscription<Progress> {
|
||||
iced::Subscription::from_recipe(Download {
|
||||
id,
|
||||
url: url.to_string(),
|
||||
})
|
||||
}
|
||||
|
||||
pub struct Download<I> {
|
||||
id: I,
|
||||
pub struct Download {
|
||||
url: String,
|
||||
}
|
||||
|
||||
// Make sure iced can use our download stream
|
||||
impl<H, I, T> iced_native::subscription::Recipe<H, I> for Download<T>
|
||||
impl<H, I> iced_native::subscription::Recipe<H, I> for Download
|
||||
where
|
||||
T: 'static + Hash + Copy + Send,
|
||||
H: Hasher,
|
||||
H: std::hash::Hasher,
|
||||
{
|
||||
type Output = (T, Progress);
|
||||
type Output = Progress;
|
||||
|
||||
fn hash(&self, state: &mut H) {
|
||||
struct Marker;
|
||||
std::any::TypeId::of::<Marker>().hash(state);
|
||||
use std::hash::Hash;
|
||||
|
||||
self.id.hash(state);
|
||||
std::any::TypeId::of::<Self>().hash(state);
|
||||
self.url.hash(state);
|
||||
}
|
||||
|
||||
fn stream(
|
||||
self: Box<Self>,
|
||||
_input: futures::stream::BoxStream<'static, I>,
|
||||
) -> futures::stream::BoxStream<'static, Self::Output> {
|
||||
let id = self.id;
|
||||
|
||||
Box::pin(futures::stream::unfold(
|
||||
State::Ready(self.url),
|
||||
move |state| async move {
|
||||
|state| async move {
|
||||
match state {
|
||||
State::Ready(url) => {
|
||||
let response = reqwest::get(&url).await;
|
||||
@ -49,7 +40,7 @@ where
|
||||
Ok(response) => {
|
||||
if let Some(total) = response.content_length() {
|
||||
Some((
|
||||
(id, Progress::Started),
|
||||
Progress::Started,
|
||||
State::Downloading {
|
||||
response,
|
||||
total,
|
||||
@ -57,14 +48,11 @@ where
|
||||
},
|
||||
))
|
||||
} else {
|
||||
Some((
|
||||
(id, Progress::Errored),
|
||||
State::Finished,
|
||||
))
|
||||
Some((Progress::Errored, State::Finished))
|
||||
}
|
||||
}
|
||||
Err(_) => {
|
||||
Some(((id, Progress::Errored), State::Finished))
|
||||
Some((Progress::Errored, State::Finished))
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -80,7 +68,7 @@ where
|
||||
(downloaded as f32 / total as f32) * 100.0;
|
||||
|
||||
Some((
|
||||
(id, Progress::Advanced(percentage)),
|
||||
Progress::Advanced(percentage),
|
||||
State::Downloading {
|
||||
response,
|
||||
total,
|
||||
@ -88,12 +76,8 @@ where
|
||||
},
|
||||
))
|
||||
}
|
||||
Ok(None) => {
|
||||
Some(((id, Progress::Finished), State::Finished))
|
||||
}
|
||||
Err(_) => {
|
||||
Some(((id, Progress::Errored), State::Finished))
|
||||
}
|
||||
Ok(None) => Some((Progress::Finished, State::Finished)),
|
||||
Err(_) => Some((Progress::Errored, State::Finished)),
|
||||
},
|
||||
State::Finished => {
|
||||
// We do not let the stream die, as it would start a
|
||||
|
@ -1,6 +1,6 @@
|
||||
use iced::{
|
||||
button, executor, Align, Application, Button, Clipboard, Column, Command,
|
||||
Container, Element, Length, ProgressBar, Settings, Subscription, Text,
|
||||
button, executor, Align, Application, Button, Column, Command, Container,
|
||||
Element, Length, ProgressBar, Settings, Subscription, Text,
|
||||
};
|
||||
|
||||
mod download;
|
||||
@ -10,17 +10,17 @@ pub fn main() -> iced::Result {
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct Example {
|
||||
downloads: Vec<Download>,
|
||||
last_id: usize,
|
||||
add: button::State,
|
||||
enum Example {
|
||||
Idle { button: button::State },
|
||||
Downloading { progress: f32 },
|
||||
Finished { button: button::State },
|
||||
Errored { button: button::State },
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum Message {
|
||||
Add,
|
||||
Download(usize),
|
||||
DownloadProgressed((usize, download::Progress)),
|
||||
Download,
|
||||
DownloadProgressed(download::Progress),
|
||||
}
|
||||
|
||||
impl Application for Example {
|
||||
@ -30,10 +30,8 @@ impl Application for Example {
|
||||
|
||||
fn new(_flags: ()) -> (Example, Command<Message>) {
|
||||
(
|
||||
Example {
|
||||
downloads: vec![Download::new(0)],
|
||||
last_id: 0,
|
||||
add: button::State::new(),
|
||||
Example::Idle {
|
||||
button: button::State::new(),
|
||||
},
|
||||
Command::none(),
|
||||
)
|
||||
@ -43,177 +41,104 @@ impl Application for Example {
|
||||
String::from("Download progress - Iced")
|
||||
}
|
||||
|
||||
fn update(
|
||||
&mut self,
|
||||
message: Message,
|
||||
_clipboard: &mut Clipboard,
|
||||
) -> Command<Message> {
|
||||
fn update(&mut self, message: Message) -> Command<Message> {
|
||||
match message {
|
||||
Message::Add => {
|
||||
self.last_id = self.last_id + 1;
|
||||
|
||||
self.downloads.push(Download::new(self.last_id));
|
||||
}
|
||||
Message::Download(index) => {
|
||||
if let Some(download) = self.downloads.get_mut(index) {
|
||||
download.start();
|
||||
Message::Download => match self {
|
||||
Example::Idle { .. }
|
||||
| Example::Finished { .. }
|
||||
| Example::Errored { .. } => {
|
||||
*self = Example::Downloading { progress: 0.0 };
|
||||
}
|
||||
}
|
||||
Message::DownloadProgressed((id, progress)) => {
|
||||
if let Some(download) =
|
||||
self.downloads.iter_mut().find(|download| download.id == id)
|
||||
{
|
||||
download.progress(progress);
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
},
|
||||
Message::DownloadProgressed(message) => match self {
|
||||
Example::Downloading { progress } => match message {
|
||||
download::Progress::Started => {
|
||||
*progress = 0.0;
|
||||
}
|
||||
download::Progress::Advanced(percentage) => {
|
||||
*progress = percentage;
|
||||
}
|
||||
download::Progress::Finished => {
|
||||
*self = Example::Finished {
|
||||
button: button::State::new(),
|
||||
}
|
||||
}
|
||||
download::Progress::Errored => {
|
||||
*self = Example::Errored {
|
||||
button: button::State::new(),
|
||||
};
|
||||
}
|
||||
},
|
||||
_ => {}
|
||||
},
|
||||
};
|
||||
|
||||
Command::none()
|
||||
}
|
||||
|
||||
fn subscription(&self) -> Subscription<Message> {
|
||||
Subscription::batch(self.downloads.iter().map(Download::subscription))
|
||||
}
|
||||
|
||||
fn view(&mut self) -> Element<Message> {
|
||||
let downloads = self
|
||||
.downloads
|
||||
.iter_mut()
|
||||
.fold(Column::new().spacing(20), |column, download| {
|
||||
column.push(download.view())
|
||||
})
|
||||
.push(
|
||||
Button::new(&mut self.add, Text::new("Add another download"))
|
||||
.on_press(Message::Add)
|
||||
.padding(10),
|
||||
)
|
||||
.align_items(Align::End);
|
||||
|
||||
Container::new(downloads)
|
||||
.width(Length::Fill)
|
||||
.height(Length::Fill)
|
||||
.center_x()
|
||||
.center_y()
|
||||
.padding(20)
|
||||
.into()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct Download {
|
||||
id: usize,
|
||||
state: State,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
enum State {
|
||||
Idle { button: button::State },
|
||||
Downloading { progress: f32 },
|
||||
Finished { button: button::State },
|
||||
Errored { button: button::State },
|
||||
}
|
||||
|
||||
impl Download {
|
||||
pub fn new(id: usize) -> Self {
|
||||
Download {
|
||||
id,
|
||||
state: State::Idle {
|
||||
button: button::State::new(),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn start(&mut self) {
|
||||
match self.state {
|
||||
State::Idle { .. }
|
||||
| State::Finished { .. }
|
||||
| State::Errored { .. } => {
|
||||
self.state = State::Downloading { progress: 0.0 };
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn progress(&mut self, new_progress: download::Progress) {
|
||||
match &mut self.state {
|
||||
State::Downloading { progress } => match new_progress {
|
||||
download::Progress::Started => {
|
||||
*progress = 0.0;
|
||||
}
|
||||
download::Progress::Advanced(percentage) => {
|
||||
*progress = percentage;
|
||||
}
|
||||
download::Progress::Finished => {
|
||||
self.state = State::Finished {
|
||||
button: button::State::new(),
|
||||
}
|
||||
}
|
||||
download::Progress::Errored => {
|
||||
self.state = State::Errored {
|
||||
button: button::State::new(),
|
||||
};
|
||||
}
|
||||
},
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn subscription(&self) -> Subscription<Message> {
|
||||
match self.state {
|
||||
State::Downloading { .. } => {
|
||||
download::file(self.id, "https://speed.hetzner.de/100MB.bin?")
|
||||
match self {
|
||||
Example::Downloading { .. } => {
|
||||
download::file("https://speed.hetzner.de/100MB.bin")
|
||||
.map(Message::DownloadProgressed)
|
||||
}
|
||||
_ => Subscription::none(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn view(&mut self) -> Element<Message> {
|
||||
let current_progress = match &self.state {
|
||||
State::Idle { .. } => 0.0,
|
||||
State::Downloading { progress } => *progress,
|
||||
State::Finished { .. } => 100.0,
|
||||
State::Errored { .. } => 0.0,
|
||||
fn view(&mut self) -> Element<Message> {
|
||||
let current_progress = match self {
|
||||
Example::Idle { .. } => 0.0,
|
||||
Example::Downloading { progress } => *progress,
|
||||
Example::Finished { .. } => 100.0,
|
||||
Example::Errored { .. } => 0.0,
|
||||
};
|
||||
|
||||
let progress_bar = ProgressBar::new(0.0..=100.0, current_progress);
|
||||
|
||||
let control: Element<_> = match &mut self.state {
|
||||
State::Idle { button } => {
|
||||
let control: Element<_> = match self {
|
||||
Example::Idle { button } => {
|
||||
Button::new(button, Text::new("Start the download!"))
|
||||
.on_press(Message::Download(self.id))
|
||||
.on_press(Message::Download)
|
||||
.into()
|
||||
}
|
||||
State::Finished { button } => Column::new()
|
||||
Example::Finished { button } => Column::new()
|
||||
.spacing(10)
|
||||
.align_items(Align::Center)
|
||||
.push(Text::new("Download finished!"))
|
||||
.push(
|
||||
Button::new(button, Text::new("Start again"))
|
||||
.on_press(Message::Download(self.id)),
|
||||
.on_press(Message::Download),
|
||||
)
|
||||
.into(),
|
||||
State::Downloading { .. } => {
|
||||
Example::Downloading { .. } => {
|
||||
Text::new(format!("Downloading... {:.2}%", current_progress))
|
||||
.into()
|
||||
}
|
||||
State::Errored { button } => Column::new()
|
||||
Example::Errored { button } => Column::new()
|
||||
.spacing(10)
|
||||
.align_items(Align::Center)
|
||||
.push(Text::new("Something went wrong :("))
|
||||
.push(
|
||||
Button::new(button, Text::new("Try again"))
|
||||
.on_press(Message::Download(self.id)),
|
||||
.on_press(Message::Download),
|
||||
)
|
||||
.into(),
|
||||
};
|
||||
|
||||
Column::new()
|
||||
let content = Column::new()
|
||||
.spacing(10)
|
||||
.padding(10)
|
||||
.align_items(Align::Center)
|
||||
.push(progress_bar)
|
||||
.push(control)
|
||||
.push(control);
|
||||
|
||||
Container::new(content)
|
||||
.width(Length::Fill)
|
||||
.height(Length::Fill)
|
||||
.center_x()
|
||||
.center_y()
|
||||
.into()
|
||||
}
|
||||
}
|
||||
|
@ -1,30 +1,22 @@
|
||||
use iced::{
|
||||
button, executor, Align, Application, Button, Checkbox, Clipboard, Column,
|
||||
Command, Container, Element, HorizontalAlignment, Length, Settings,
|
||||
Subscription, Text,
|
||||
executor, Align, Application, Checkbox, Column, Command, Container,
|
||||
Element, Length, Settings, Subscription, Text,
|
||||
};
|
||||
use iced_native::{window, Event};
|
||||
|
||||
pub fn main() -> iced::Result {
|
||||
Events::run(Settings {
|
||||
exit_on_close_request: false,
|
||||
..Settings::default()
|
||||
})
|
||||
Events::run(Settings::default())
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
struct Events {
|
||||
last: Vec<iced_native::Event>,
|
||||
enabled: bool,
|
||||
exit: button::State,
|
||||
should_exit: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
enum Message {
|
||||
EventOccurred(iced_native::Event),
|
||||
Toggled(bool),
|
||||
Exit,
|
||||
}
|
||||
|
||||
impl Application for Events {
|
||||
@ -40,41 +32,29 @@ impl Application for Events {
|
||||
String::from("Events - Iced")
|
||||
}
|
||||
|
||||
fn update(
|
||||
&mut self,
|
||||
message: Message,
|
||||
_clipboard: &mut Clipboard,
|
||||
) -> Command<Message> {
|
||||
fn update(&mut self, message: Message) -> Command<Message> {
|
||||
match message {
|
||||
Message::EventOccurred(event) if self.enabled => {
|
||||
Message::EventOccurred(event) => {
|
||||
self.last.push(event);
|
||||
|
||||
if self.last.len() > 5 {
|
||||
let _ = self.last.remove(0);
|
||||
}
|
||||
}
|
||||
Message::EventOccurred(event) => {
|
||||
if let Event::Window(window::Event::CloseRequested) = event {
|
||||
self.should_exit = true;
|
||||
}
|
||||
}
|
||||
Message::Toggled(enabled) => {
|
||||
self.enabled = enabled;
|
||||
}
|
||||
Message::Exit => {
|
||||
self.should_exit = true;
|
||||
}
|
||||
};
|
||||
|
||||
Command::none()
|
||||
}
|
||||
|
||||
fn subscription(&self) -> Subscription<Message> {
|
||||
iced_native::subscription::events().map(Message::EventOccurred)
|
||||
}
|
||||
|
||||
fn should_exit(&self) -> bool {
|
||||
self.should_exit
|
||||
if self.enabled {
|
||||
iced_native::subscription::events().map(Message::EventOccurred)
|
||||
} else {
|
||||
Subscription::none()
|
||||
}
|
||||
}
|
||||
|
||||
fn view(&mut self) -> Element<Message> {
|
||||
@ -91,22 +71,11 @@ impl Application for Events {
|
||||
Message::Toggled,
|
||||
);
|
||||
|
||||
let exit = Button::new(
|
||||
&mut self.exit,
|
||||
Text::new("Exit")
|
||||
.width(Length::Fill)
|
||||
.horizontal_alignment(HorizontalAlignment::Center),
|
||||
)
|
||||
.width(Length::Units(100))
|
||||
.padding(10)
|
||||
.on_press(Message::Exit);
|
||||
|
||||
let content = Column::new()
|
||||
.align_items(Align::Center)
|
||||
.spacing(20)
|
||||
.push(events)
|
||||
.push(toggle)
|
||||
.push(exit);
|
||||
.push(toggle);
|
||||
|
||||
Container::new(content)
|
||||
.width(Length::Fill)
|
||||
|
@ -7,6 +7,6 @@ publish = false
|
||||
|
||||
[dependencies]
|
||||
iced = { path = "../..", features = ["canvas", "tokio", "debug"] }
|
||||
tokio = { version = "1.0", features = ["sync"] }
|
||||
tokio = { version = "0.2", features = ["blocking"] }
|
||||
itertools = "0.9"
|
||||
rustc-hash = "1.1"
|
||||
|
@ -7,8 +7,8 @@ It runs a simulation in a background thread while allowing interaction with a `C
|
||||
The __[`main`]__ file contains the relevant code of the example.
|
||||
|
||||
<div align="center">
|
||||
<a href="https://gfycat.com/WhichPaltryChick">
|
||||
<img src="https://thumbs.gfycat.com/WhichPaltryChick-size_restricted.gif">
|
||||
<a href="https://gfycat.com/briefaccurateaardvark">
|
||||
<img src="https://thumbs.gfycat.com/BriefAccurateAardvark-size_restricted.gif">
|
||||
</a>
|
||||
</div>
|
||||
|
||||
|
@ -10,8 +10,8 @@ use iced::pick_list::{self, PickList};
|
||||
use iced::slider::{self, Slider};
|
||||
use iced::time;
|
||||
use iced::{
|
||||
Align, Application, Checkbox, Clipboard, Column, Command, Container,
|
||||
Element, Length, Row, Settings, Subscription, Text,
|
||||
Align, Application, Checkbox, Column, Command, Container, Element, Length,
|
||||
Row, Settings, Subscription, Text,
|
||||
};
|
||||
use preset::Preset;
|
||||
use std::time::{Duration, Instant};
|
||||
@ -31,12 +31,11 @@ struct GameOfLife {
|
||||
queued_ticks: usize,
|
||||
speed: usize,
|
||||
next_speed: Option<usize>,
|
||||
version: usize,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
enum Message {
|
||||
Grid(grid::Message, usize),
|
||||
Grid(grid::Message),
|
||||
Tick(Instant),
|
||||
TogglePlayback,
|
||||
ToggleGrid(bool),
|
||||
@ -65,16 +64,10 @@ impl Application for GameOfLife {
|
||||
String::from("Game of Life - Iced")
|
||||
}
|
||||
|
||||
fn update(
|
||||
&mut self,
|
||||
message: Message,
|
||||
_clipboard: &mut Clipboard,
|
||||
) -> Command<Message> {
|
||||
fn update(&mut self, message: Message) -> Command<Message> {
|
||||
match message {
|
||||
Message::Grid(message, version) => {
|
||||
if version == self.version {
|
||||
self.grid.update(message);
|
||||
}
|
||||
Message::Grid(message) => {
|
||||
self.grid.update(message);
|
||||
}
|
||||
Message::Tick(_) | Message::Next => {
|
||||
self.queued_ticks = (self.queued_ticks + 1).min(self.speed);
|
||||
@ -86,11 +79,7 @@ impl Application for GameOfLife {
|
||||
|
||||
self.queued_ticks = 0;
|
||||
|
||||
let version = self.version;
|
||||
|
||||
return Command::perform(task, move |message| {
|
||||
Message::Grid(message, version)
|
||||
});
|
||||
return Command::perform(task, Message::Grid);
|
||||
}
|
||||
}
|
||||
Message::TogglePlayback => {
|
||||
@ -101,7 +90,6 @@ impl Application for GameOfLife {
|
||||
}
|
||||
Message::Clear => {
|
||||
self.grid.clear();
|
||||
self.version += 1;
|
||||
}
|
||||
Message::SpeedChanged(speed) => {
|
||||
if self.is_playing {
|
||||
@ -112,7 +100,6 @@ impl Application for GameOfLife {
|
||||
}
|
||||
Message::PresetPicked(new_preset) => {
|
||||
self.grid = Grid::from_preset(new_preset);
|
||||
self.version += 1;
|
||||
}
|
||||
}
|
||||
|
||||
@ -129,7 +116,6 @@ impl Application for GameOfLife {
|
||||
}
|
||||
|
||||
fn view(&mut self) -> Element<Message> {
|
||||
let version = self.version;
|
||||
let selected_speed = self.next_speed.unwrap_or(self.speed);
|
||||
let controls = self.controls.view(
|
||||
self.is_playing,
|
||||
@ -139,11 +125,7 @@ impl Application for GameOfLife {
|
||||
);
|
||||
|
||||
let content = Column::new()
|
||||
.push(
|
||||
self.grid
|
||||
.view()
|
||||
.map(move |message| Message::Grid(message, version)),
|
||||
)
|
||||
.push(self.grid.view().map(Message::Grid))
|
||||
.push(controls);
|
||||
|
||||
Container::new(content)
|
||||
@ -157,8 +139,9 @@ impl Application for GameOfLife {
|
||||
mod grid {
|
||||
use crate::Preset;
|
||||
use iced::{
|
||||
canvas::event::{self, Event},
|
||||
canvas::{self, Cache, Canvas, Cursor, Frame, Geometry, Path, Text},
|
||||
canvas::{
|
||||
self, Cache, Canvas, Cursor, Event, Frame, Geometry, Path, Text,
|
||||
},
|
||||
mouse, Color, Element, HorizontalAlignment, Length, Point, Rectangle,
|
||||
Size, Vector, VerticalAlignment,
|
||||
};
|
||||
@ -178,6 +161,7 @@ mod grid {
|
||||
show_lines: bool,
|
||||
last_tick_duration: Duration,
|
||||
last_queued_ticks: usize,
|
||||
version: usize,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
@ -187,6 +171,7 @@ mod grid {
|
||||
Ticked {
|
||||
result: Result<Life, TickError>,
|
||||
tick_duration: Duration,
|
||||
version: usize,
|
||||
},
|
||||
}
|
||||
|
||||
@ -223,6 +208,7 @@ mod grid {
|
||||
show_lines: true,
|
||||
last_tick_duration: Duration::default(),
|
||||
last_queued_ticks: 0,
|
||||
version: 0,
|
||||
}
|
||||
}
|
||||
|
||||
@ -230,6 +216,7 @@ mod grid {
|
||||
&mut self,
|
||||
amount: usize,
|
||||
) -> Option<impl Future<Output = Message>> {
|
||||
let version = self.version;
|
||||
let tick = self.state.tick(amount)?;
|
||||
|
||||
self.last_queued_ticks = amount;
|
||||
@ -241,6 +228,7 @@ mod grid {
|
||||
|
||||
Message::Ticked {
|
||||
result,
|
||||
version,
|
||||
tick_duration,
|
||||
}
|
||||
})
|
||||
@ -262,11 +250,13 @@ mod grid {
|
||||
}
|
||||
Message::Ticked {
|
||||
result: Ok(life),
|
||||
version,
|
||||
tick_duration,
|
||||
} => {
|
||||
} if version == self.version => {
|
||||
self.state.update(life);
|
||||
self.life_cache.clear();
|
||||
|
||||
self.version += 1;
|
||||
self.last_tick_duration = tick_duration;
|
||||
}
|
||||
Message::Ticked {
|
||||
@ -274,6 +264,7 @@ mod grid {
|
||||
} => {
|
||||
dbg!(error);
|
||||
}
|
||||
Message::Ticked { .. } => {}
|
||||
}
|
||||
}
|
||||
|
||||
@ -287,6 +278,7 @@ mod grid {
|
||||
pub fn clear(&mut self) {
|
||||
self.state = State::default();
|
||||
self.preset = Preset::Custom;
|
||||
self.version += 1;
|
||||
|
||||
self.life_cache.clear();
|
||||
}
|
||||
@ -331,18 +323,12 @@ mod grid {
|
||||
event: Event,
|
||||
bounds: Rectangle,
|
||||
cursor: Cursor,
|
||||
) -> (event::Status, Option<Message>) {
|
||||
) -> Option<Message> {
|
||||
if let Event::Mouse(mouse::Event::ButtonReleased(_)) = event {
|
||||
self.interaction = Interaction::None;
|
||||
}
|
||||
|
||||
let cursor_position =
|
||||
if let Some(position) = cursor.position_in(&bounds) {
|
||||
position
|
||||
} else {
|
||||
return (event::Status::Ignored, None);
|
||||
};
|
||||
|
||||
let cursor_position = cursor.position_in(&bounds)?;
|
||||
let cell = Cell::at(self.project(cursor_position, bounds.size()));
|
||||
let is_populated = self.state.contains(&cell);
|
||||
|
||||
@ -354,32 +340,28 @@ mod grid {
|
||||
|
||||
match event {
|
||||
Event::Mouse(mouse_event) => match mouse_event {
|
||||
mouse::Event::ButtonPressed(button) => {
|
||||
let message = match button {
|
||||
mouse::Button::Left => {
|
||||
self.interaction = if is_populated {
|
||||
Interaction::Erasing
|
||||
} else {
|
||||
Interaction::Drawing
|
||||
};
|
||||
mouse::Event::ButtonPressed(button) => match button {
|
||||
mouse::Button::Left => {
|
||||
self.interaction = if is_populated {
|
||||
Interaction::Erasing
|
||||
} else {
|
||||
Interaction::Drawing
|
||||
};
|
||||
|
||||
populate.or(unpopulate)
|
||||
}
|
||||
mouse::Button::Right => {
|
||||
self.interaction = Interaction::Panning {
|
||||
translation: self.translation,
|
||||
start: cursor_position,
|
||||
};
|
||||
populate.or(unpopulate)
|
||||
}
|
||||
mouse::Button::Right => {
|
||||
self.interaction = Interaction::Panning {
|
||||
translation: self.translation,
|
||||
start: cursor_position,
|
||||
};
|
||||
|
||||
None
|
||||
}
|
||||
_ => None,
|
||||
};
|
||||
|
||||
(event::Status::Captured, message)
|
||||
}
|
||||
None
|
||||
}
|
||||
_ => None,
|
||||
},
|
||||
mouse::Event::CursorMoved { .. } => {
|
||||
let message = match self.interaction {
|
||||
match self.interaction {
|
||||
Interaction::Drawing => populate,
|
||||
Interaction::Erasing => unpopulate,
|
||||
Interaction::Panning { translation, start } => {
|
||||
@ -393,14 +375,7 @@ mod grid {
|
||||
None
|
||||
}
|
||||
_ => None,
|
||||
};
|
||||
|
||||
let event_status = match self.interaction {
|
||||
Interaction::None => event::Status::Ignored,
|
||||
_ => event::Status::Captured,
|
||||
};
|
||||
|
||||
(event_status, message)
|
||||
}
|
||||
}
|
||||
mouse::Event::WheelScrolled { delta } => match delta {
|
||||
mouse::ScrollDelta::Lines { y, .. }
|
||||
@ -433,12 +408,12 @@ mod grid {
|
||||
self.grid_cache.clear();
|
||||
}
|
||||
|
||||
(event::Status::Captured, None)
|
||||
None
|
||||
}
|
||||
},
|
||||
_ => (event::Status::Ignored, None),
|
||||
_ => None,
|
||||
},
|
||||
_ => (event::Status::Ignored, None),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -44,7 +44,7 @@ impl button::StyleSheet for Button {
|
||||
fn active(&self) -> button::Style {
|
||||
button::Style {
|
||||
background: Some(Background::Color(ACTIVE)),
|
||||
border_radius: 3.0,
|
||||
border_radius: 3,
|
||||
text_color: Color::WHITE,
|
||||
..button::Style::default()
|
||||
}
|
||||
@ -60,7 +60,7 @@ impl button::StyleSheet for Button {
|
||||
|
||||
fn pressed(&self) -> button::Style {
|
||||
button::Style {
|
||||
border_width: 1.0,
|
||||
border_width: 1,
|
||||
border_color: Color::WHITE,
|
||||
..self.hovered()
|
||||
}
|
||||
@ -73,7 +73,7 @@ impl button::StyleSheet for Clear {
|
||||
fn active(&self) -> button::Style {
|
||||
button::Style {
|
||||
background: Some(Background::Color(DESTRUCTIVE)),
|
||||
border_radius: 3.0,
|
||||
border_radius: 3,
|
||||
text_color: Color::WHITE,
|
||||
..button::Style::default()
|
||||
}
|
||||
@ -92,7 +92,7 @@ impl button::StyleSheet for Clear {
|
||||
|
||||
fn pressed(&self) -> button::Style {
|
||||
button::Style {
|
||||
border_width: 1.0,
|
||||
border_width: 1,
|
||||
border_color: Color::WHITE,
|
||||
..self.hovered()
|
||||
}
|
||||
@ -106,9 +106,9 @@ impl slider::StyleSheet for Slider {
|
||||
slider::Style {
|
||||
rail_colors: (ACTIVE, Color { a: 0.1, ..ACTIVE }),
|
||||
handle: slider::Handle {
|
||||
shape: slider::HandleShape::Circle { radius: 9.0 },
|
||||
shape: slider::HandleShape::Circle { radius: 9 },
|
||||
color: ACTIVE,
|
||||
border_width: 0.0,
|
||||
border_width: 0,
|
||||
border_color: Color::TRANSPARENT,
|
||||
},
|
||||
}
|
||||
@ -146,7 +146,7 @@ impl pick_list::StyleSheet for PickList {
|
||||
pick_list::Menu {
|
||||
text_color: Color::WHITE,
|
||||
background: BACKGROUND.into(),
|
||||
border_width: 1.0,
|
||||
border_width: 1,
|
||||
border_color: Color {
|
||||
a: 0.7,
|
||||
..Color::BLACK
|
||||
@ -164,12 +164,12 @@ impl pick_list::StyleSheet for PickList {
|
||||
pick_list::Style {
|
||||
text_color: Color::WHITE,
|
||||
background: BACKGROUND.into(),
|
||||
border_width: 1.0,
|
||||
border_width: 1,
|
||||
border_color: Color {
|
||||
a: 0.6,
|
||||
..Color::BLACK
|
||||
},
|
||||
border_radius: 2.0,
|
||||
border_radius: 2,
|
||||
icon_size: 0.5,
|
||||
}
|
||||
}
|
||||
|
@ -8,4 +8,4 @@ publish = false
|
||||
[dependencies]
|
||||
iced_winit = { path = "../../winit" }
|
||||
iced_wgpu = { path = "../../wgpu" }
|
||||
env_logger = "0.8"
|
||||
env_logger = "0.7"
|
||||
|
@ -1,7 +1,7 @@
|
||||
use iced_wgpu::Renderer;
|
||||
use iced_winit::{
|
||||
slider, Align, Clipboard, Color, Column, Command, Element, Length, Program,
|
||||
Row, Slider, Text,
|
||||
slider, Align, Color, Column, Command, Element, Length, Program, Row,
|
||||
Slider, Text,
|
||||
};
|
||||
|
||||
pub struct Controls {
|
||||
@ -30,13 +30,8 @@ impl Controls {
|
||||
impl Program for Controls {
|
||||
type Renderer = Renderer;
|
||||
type Message = Message;
|
||||
type Clipboard = Clipboard;
|
||||
|
||||
fn update(
|
||||
&mut self,
|
||||
message: Message,
|
||||
_clipboard: &mut Clipboard,
|
||||
) -> Command<Message> {
|
||||
fn update(&mut self, message: Message) -> Command<Message> {
|
||||
match message {
|
||||
Message::BackgroundColorChanged(color) => {
|
||||
self.background_color = color;
|
||||
|
@ -5,7 +5,7 @@ use controls::Controls;
|
||||
use scene::Scene;
|
||||
|
||||
use iced_wgpu::{wgpu, Backend, Renderer, Settings, Viewport};
|
||||
use iced_winit::{conversion, futures, program, winit, Clipboard, Debug, Size};
|
||||
use iced_winit::{conversion, futures, program, winit, Debug, Size};
|
||||
|
||||
use futures::task::SpawnExt;
|
||||
use winit::{
|
||||
@ -28,7 +28,6 @@ pub fn main() {
|
||||
);
|
||||
let mut cursor_position = PhysicalPosition::new(-1.0, -1.0);
|
||||
let mut modifiers = ModifiersState::default();
|
||||
let mut clipboard = Clipboard::connect(&window);
|
||||
|
||||
// Initialize wgpu
|
||||
let instance = wgpu::Instance::new(wgpu::BackendBit::PRIMARY);
|
||||
@ -37,7 +36,7 @@ pub fn main() {
|
||||
let (mut device, queue) = futures::executor::block_on(async {
|
||||
let adapter = instance
|
||||
.request_adapter(&wgpu::RequestAdapterOptions {
|
||||
power_preference: wgpu::PowerPreference::HighPerformance,
|
||||
power_preference: wgpu::PowerPreference::Default,
|
||||
compatible_surface: Some(&surface),
|
||||
})
|
||||
.await
|
||||
@ -46,9 +45,9 @@ pub fn main() {
|
||||
adapter
|
||||
.request_device(
|
||||
&wgpu::DeviceDescriptor {
|
||||
label: None,
|
||||
features: wgpu::Features::empty(),
|
||||
limits: wgpu::Limits::default(),
|
||||
shader_validation: false,
|
||||
},
|
||||
None,
|
||||
)
|
||||
@ -64,7 +63,7 @@ pub fn main() {
|
||||
device.create_swap_chain(
|
||||
&surface,
|
||||
&wgpu::SwapChainDescriptor {
|
||||
usage: wgpu::TextureUsage::RENDER_ATTACHMENT,
|
||||
usage: wgpu::TextureUsage::OUTPUT_ATTACHMENT,
|
||||
format: format,
|
||||
width: size.width,
|
||||
height: size.height,
|
||||
@ -142,8 +141,8 @@ pub fn main() {
|
||||
cursor_position,
|
||||
viewport.scale_factor(),
|
||||
),
|
||||
None,
|
||||
&mut renderer,
|
||||
&mut clipboard,
|
||||
&mut debug,
|
||||
);
|
||||
|
||||
@ -158,7 +157,7 @@ pub fn main() {
|
||||
swap_chain = device.create_swap_chain(
|
||||
&surface,
|
||||
&wgpu::SwapChainDescriptor {
|
||||
usage: wgpu::TextureUsage::RENDER_ATTACHMENT,
|
||||
usage: wgpu::TextureUsage::OUTPUT_ATTACHMENT,
|
||||
format: format,
|
||||
width: size.width,
|
||||
height: size.height,
|
||||
|
@ -19,9 +19,8 @@ impl Scene {
|
||||
background_color: Color,
|
||||
) -> wgpu::RenderPass<'a> {
|
||||
encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
|
||||
label: None,
|
||||
color_attachments: &[wgpu::RenderPassColorAttachment {
|
||||
view: target,
|
||||
color_attachments: &[wgpu::RenderPassColorAttachmentDescriptor {
|
||||
attachment: target,
|
||||
resolve_target: None,
|
||||
ops: wgpu::Operations {
|
||||
load: wgpu::LoadOp::Clear({
|
||||
@ -49,10 +48,10 @@ impl Scene {
|
||||
|
||||
fn build_pipeline(device: &wgpu::Device) -> wgpu::RenderPipeline {
|
||||
let vs_module =
|
||||
device.create_shader_module(&wgpu::include_spirv!("shader/vert.spv"));
|
||||
device.create_shader_module(wgpu::include_spirv!("shader/vert.spv"));
|
||||
|
||||
let fs_module =
|
||||
device.create_shader_module(&wgpu::include_spirv!("shader/frag.spv"));
|
||||
device.create_shader_module(wgpu::include_spirv!("shader/frag.spv"));
|
||||
|
||||
let pipeline_layout =
|
||||
device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
|
||||
@ -65,34 +64,34 @@ fn build_pipeline(device: &wgpu::Device) -> wgpu::RenderPipeline {
|
||||
device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
|
||||
label: None,
|
||||
layout: Some(&pipeline_layout),
|
||||
vertex: wgpu::VertexState {
|
||||
vertex_stage: wgpu::ProgrammableStageDescriptor {
|
||||
module: &vs_module,
|
||||
entry_point: "main",
|
||||
buffers: &[],
|
||||
},
|
||||
fragment: Some(wgpu::FragmentState {
|
||||
fragment_stage: Some(wgpu::ProgrammableStageDescriptor {
|
||||
module: &fs_module,
|
||||
entry_point: "main",
|
||||
targets: &[wgpu::ColorTargetState {
|
||||
format: wgpu::TextureFormat::Bgra8UnormSrgb,
|
||||
blend: Some(wgpu::BlendState {
|
||||
color: wgpu::BlendComponent::REPLACE,
|
||||
alpha: wgpu::BlendComponent::REPLACE,
|
||||
}),
|
||||
write_mask: wgpu::ColorWrite::ALL,
|
||||
}],
|
||||
}),
|
||||
primitive: wgpu::PrimitiveState {
|
||||
topology: wgpu::PrimitiveTopology::TriangleList,
|
||||
rasterization_state: Some(wgpu::RasterizationStateDescriptor {
|
||||
front_face: wgpu::FrontFace::Ccw,
|
||||
cull_mode: wgpu::CullMode::None,
|
||||
..Default::default()
|
||||
}),
|
||||
primitive_topology: wgpu::PrimitiveTopology::TriangleList,
|
||||
color_states: &[wgpu::ColorStateDescriptor {
|
||||
format: wgpu::TextureFormat::Bgra8UnormSrgb,
|
||||
color_blend: wgpu::BlendDescriptor::REPLACE,
|
||||
alpha_blend: wgpu::BlendDescriptor::REPLACE,
|
||||
write_mask: wgpu::ColorWrite::ALL,
|
||||
}],
|
||||
depth_stencil_state: None,
|
||||
vertex_state: wgpu::VertexStateDescriptor {
|
||||
index_format: wgpu::IndexFormat::Uint16,
|
||||
vertex_buffers: &[],
|
||||
},
|
||||
depth_stencil: None,
|
||||
multisample: wgpu::MultisampleState {
|
||||
count: 1,
|
||||
mask: !0,
|
||||
alpha_to_coverage_enabled: false,
|
||||
},
|
||||
sample_count: 1,
|
||||
sample_mask: !0,
|
||||
alpha_to_coverage_enabled: false,
|
||||
});
|
||||
|
||||
pipeline
|
||||
|
@ -6,5 +6,4 @@ edition = "2018"
|
||||
publish = false
|
||||
|
||||
[dependencies]
|
||||
iced = { path = "../..", features = ["debug"] }
|
||||
iced_native = { path = "../../native" }
|
||||
iced = { path = "../.." }
|
||||
|
@ -1,19 +1,16 @@
|
||||
use iced::{
|
||||
button, executor, keyboard, pane_grid, scrollable, Align, Application,
|
||||
Button, Clipboard, Color, Column, Command, Container, Element,
|
||||
HorizontalAlignment, Length, PaneGrid, Row, Scrollable, Settings,
|
||||
Subscription, Text,
|
||||
button, keyboard, pane_grid, scrollable, Align, Button, Column, Container,
|
||||
Element, HorizontalAlignment, Length, PaneGrid, Sandbox, Scrollable,
|
||||
Settings, Text,
|
||||
};
|
||||
use iced_native::{event, subscription, Event};
|
||||
|
||||
pub fn main() -> iced::Result {
|
||||
Example::run(Settings::default())
|
||||
}
|
||||
|
||||
struct Example {
|
||||
panes: pane_grid::State<Pane>,
|
||||
panes: pane_grid::State<Content>,
|
||||
panes_created: usize,
|
||||
focus: Option<pane_grid::Pane>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
@ -21,82 +18,59 @@ enum Message {
|
||||
Split(pane_grid::Axis, pane_grid::Pane),
|
||||
SplitFocused(pane_grid::Axis),
|
||||
FocusAdjacent(pane_grid::Direction),
|
||||
Clicked(pane_grid::Pane),
|
||||
Dragged(pane_grid::DragEvent),
|
||||
Resized(pane_grid::ResizeEvent),
|
||||
TogglePin(pane_grid::Pane),
|
||||
Close(pane_grid::Pane),
|
||||
CloseFocused,
|
||||
}
|
||||
|
||||
impl Application for Example {
|
||||
impl Sandbox for Example {
|
||||
type Message = Message;
|
||||
type Executor = executor::Default;
|
||||
type Flags = ();
|
||||
|
||||
fn new(_flags: ()) -> (Self, Command<Message>) {
|
||||
let (panes, _) = pane_grid::State::new(Pane::new(0));
|
||||
fn new() -> Self {
|
||||
let (panes, _) = pane_grid::State::new(Content::new(0));
|
||||
|
||||
(
|
||||
Example {
|
||||
panes,
|
||||
panes_created: 1,
|
||||
focus: None,
|
||||
},
|
||||
Command::none(),
|
||||
)
|
||||
Example {
|
||||
panes,
|
||||
panes_created: 1,
|
||||
}
|
||||
}
|
||||
|
||||
fn title(&self) -> String {
|
||||
String::from("Pane grid - Iced")
|
||||
}
|
||||
|
||||
fn update(
|
||||
&mut self,
|
||||
message: Message,
|
||||
_clipboard: &mut Clipboard,
|
||||
) -> Command<Message> {
|
||||
fn update(&mut self, message: Message) {
|
||||
match message {
|
||||
Message::Split(axis, pane) => {
|
||||
let result = self.panes.split(
|
||||
let _ = self.panes.split(
|
||||
axis,
|
||||
&pane,
|
||||
Pane::new(self.panes_created),
|
||||
Content::new(self.panes_created),
|
||||
);
|
||||
|
||||
if let Some((pane, _)) = result {
|
||||
self.focus = Some(pane);
|
||||
}
|
||||
|
||||
self.panes_created += 1;
|
||||
}
|
||||
Message::SplitFocused(axis) => {
|
||||
if let Some(pane) = self.focus {
|
||||
let result = self.panes.split(
|
||||
if let Some(pane) = self.panes.active() {
|
||||
let _ = self.panes.split(
|
||||
axis,
|
||||
&pane,
|
||||
Pane::new(self.panes_created),
|
||||
Content::new(self.panes_created),
|
||||
);
|
||||
|
||||
if let Some((pane, _)) = result {
|
||||
self.focus = Some(pane);
|
||||
}
|
||||
|
||||
self.panes_created += 1;
|
||||
}
|
||||
}
|
||||
Message::FocusAdjacent(direction) => {
|
||||
if let Some(pane) = self.focus {
|
||||
if let Some(pane) = self.panes.active() {
|
||||
if let Some(adjacent) =
|
||||
self.panes.adjacent(&pane, direction)
|
||||
{
|
||||
self.focus = Some(adjacent);
|
||||
self.panes.focus(&adjacent);
|
||||
}
|
||||
}
|
||||
}
|
||||
Message::Clicked(pane) => {
|
||||
self.focus = Some(pane);
|
||||
}
|
||||
Message::Resized(pane_grid::ResizeEvent { split, ratio }) => {
|
||||
self.panes.resize(&split, ratio);
|
||||
}
|
||||
@ -107,97 +81,38 @@ impl Application for Example {
|
||||
self.panes.swap(&pane, &target);
|
||||
}
|
||||
Message::Dragged(_) => {}
|
||||
Message::TogglePin(pane) => {
|
||||
if let Some(Pane { is_pinned, .. }) = self.panes.get_mut(&pane)
|
||||
{
|
||||
*is_pinned = !*is_pinned;
|
||||
}
|
||||
}
|
||||
Message::Close(pane) => {
|
||||
if let Some((_, sibling)) = self.panes.close(&pane) {
|
||||
self.focus = Some(sibling);
|
||||
}
|
||||
let _ = self.panes.close(&pane);
|
||||
}
|
||||
Message::CloseFocused => {
|
||||
if let Some(pane) = self.focus {
|
||||
if let Some(Pane { is_pinned, .. }) = self.panes.get(&pane)
|
||||
{
|
||||
if !is_pinned {
|
||||
if let Some((_, sibling)) = self.panes.close(&pane)
|
||||
{
|
||||
self.focus = Some(sibling);
|
||||
}
|
||||
}
|
||||
}
|
||||
if let Some(pane) = self.panes.active() {
|
||||
let _ = self.panes.close(&pane);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Command::none()
|
||||
}
|
||||
|
||||
fn subscription(&self) -> Subscription<Message> {
|
||||
subscription::events_with(|event, status| {
|
||||
if let event::Status::Captured = status {
|
||||
return None;
|
||||
}
|
||||
|
||||
match event {
|
||||
Event::Keyboard(keyboard::Event::KeyPressed {
|
||||
modifiers,
|
||||
key_code,
|
||||
}) if modifiers.is_command_pressed() => handle_hotkey(key_code),
|
||||
_ => None,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn view(&mut self) -> Element<Message> {
|
||||
let focus = self.focus;
|
||||
let total_panes = self.panes.len();
|
||||
|
||||
let pane_grid = PaneGrid::new(&mut self.panes, |id, pane| {
|
||||
let is_focused = focus == Some(id);
|
||||
let pane_grid =
|
||||
PaneGrid::new(&mut self.panes, |pane, content, focus| {
|
||||
let is_focused = focus.is_some();
|
||||
let title_bar =
|
||||
pane_grid::TitleBar::new(format!("Pane {}", content.id))
|
||||
.padding(10)
|
||||
.style(style::TitleBar { is_focused });
|
||||
|
||||
let text = if pane.is_pinned { "Unpin" } else { "Pin" };
|
||||
let pin_button =
|
||||
Button::new(&mut pane.pin_button, Text::new(text).size(14))
|
||||
.on_press(Message::TogglePin(id))
|
||||
.style(style::Button::Pin)
|
||||
.padding(3);
|
||||
|
||||
let title = Row::with_children(vec![
|
||||
pin_button.into(),
|
||||
Text::new("Pane").into(),
|
||||
Text::new(pane.content.id.to_string())
|
||||
.color(if is_focused {
|
||||
PANE_ID_COLOR_FOCUSED
|
||||
} else {
|
||||
PANE_ID_COLOR_UNFOCUSED
|
||||
})
|
||||
.into(),
|
||||
])
|
||||
.spacing(5);
|
||||
|
||||
let title_bar = pane_grid::TitleBar::new(title)
|
||||
.controls(pane.controls.view(id, total_panes, pane.is_pinned))
|
||||
.padding(10)
|
||||
.style(style::TitleBar { is_focused });
|
||||
|
||||
pane_grid::Content::new(pane.content.view(
|
||||
id,
|
||||
total_panes,
|
||||
pane.is_pinned,
|
||||
))
|
||||
.title_bar(title_bar)
|
||||
.style(style::Pane { is_focused })
|
||||
})
|
||||
.width(Length::Fill)
|
||||
.height(Length::Fill)
|
||||
.spacing(10)
|
||||
.on_click(Message::Clicked)
|
||||
.on_drag(Message::Dragged)
|
||||
.on_resize(10, Message::Resized);
|
||||
pane_grid::Content::new(content.view(pane, total_panes))
|
||||
.title_bar(title_bar)
|
||||
.style(style::Pane { is_focused })
|
||||
})
|
||||
.width(Length::Fill)
|
||||
.height(Length::Fill)
|
||||
.spacing(10)
|
||||
.on_drag(Message::Dragged)
|
||||
.on_resize(10, Message::Resized)
|
||||
.on_key_press(handle_hotkey);
|
||||
|
||||
Container::new(pane_grid)
|
||||
.width(Length::Fill)
|
||||
@ -207,22 +122,11 @@ impl Application for Example {
|
||||
}
|
||||
}
|
||||
|
||||
const PANE_ID_COLOR_UNFOCUSED: Color = Color::from_rgb(
|
||||
0xFF as f32 / 255.0,
|
||||
0xC7 as f32 / 255.0,
|
||||
0xC7 as f32 / 255.0,
|
||||
);
|
||||
const PANE_ID_COLOR_FOCUSED: Color = Color::from_rgb(
|
||||
0xFF as f32 / 255.0,
|
||||
0x47 as f32 / 255.0,
|
||||
0x47 as f32 / 255.0,
|
||||
);
|
||||
|
||||
fn handle_hotkey(key_code: keyboard::KeyCode) -> Option<Message> {
|
||||
fn handle_hotkey(event: pane_grid::KeyPressEvent) -> Option<Message> {
|
||||
use keyboard::KeyCode;
|
||||
use pane_grid::{Axis, Direction};
|
||||
|
||||
let direction = match key_code {
|
||||
let direction = match event.key_code {
|
||||
KeyCode::Up => Some(Direction::Up),
|
||||
KeyCode::Down => Some(Direction::Down),
|
||||
KeyCode::Left => Some(Direction::Left),
|
||||
@ -230,7 +134,7 @@ fn handle_hotkey(key_code: keyboard::KeyCode) -> Option<Message> {
|
||||
_ => None,
|
||||
};
|
||||
|
||||
match key_code {
|
||||
match event.key_code {
|
||||
KeyCode::V => Some(Message::SplitFocused(Axis::Vertical)),
|
||||
KeyCode::H => Some(Message::SplitFocused(Axis::Horizontal)),
|
||||
KeyCode::W => Some(Message::CloseFocused),
|
||||
@ -238,13 +142,6 @@ fn handle_hotkey(key_code: keyboard::KeyCode) -> Option<Message> {
|
||||
}
|
||||
}
|
||||
|
||||
struct Pane {
|
||||
pub is_pinned: bool,
|
||||
pub pin_button: button::State,
|
||||
pub content: Content,
|
||||
pub controls: Controls,
|
||||
}
|
||||
|
||||
struct Content {
|
||||
id: usize,
|
||||
scroll: scrollable::State,
|
||||
@ -253,21 +150,6 @@ struct Content {
|
||||
close: button::State,
|
||||
}
|
||||
|
||||
struct Controls {
|
||||
close: button::State,
|
||||
}
|
||||
|
||||
impl Pane {
|
||||
fn new(id: usize) -> Self {
|
||||
Self {
|
||||
is_pinned: false,
|
||||
pin_button: button::State::new(),
|
||||
content: Content::new(id),
|
||||
controls: Controls::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Content {
|
||||
fn new(id: usize) -> Self {
|
||||
Content {
|
||||
@ -282,7 +164,6 @@ impl Content {
|
||||
&mut self,
|
||||
pane: pane_grid::Pane,
|
||||
total_panes: usize,
|
||||
is_pinned: bool,
|
||||
) -> Element<Message> {
|
||||
let Content {
|
||||
scroll,
|
||||
@ -322,7 +203,7 @@ impl Content {
|
||||
style::Button::Primary,
|
||||
));
|
||||
|
||||
if total_panes > 1 && !is_pinned {
|
||||
if total_panes > 1 {
|
||||
controls = controls.push(button(
|
||||
close,
|
||||
"Close",
|
||||
@ -346,32 +227,7 @@ impl Content {
|
||||
}
|
||||
}
|
||||
|
||||
impl Controls {
|
||||
fn new() -> Self {
|
||||
Self {
|
||||
close: button::State::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn view(
|
||||
&mut self,
|
||||
pane: pane_grid::Pane,
|
||||
total_panes: usize,
|
||||
is_pinned: bool,
|
||||
) -> Element<Message> {
|
||||
let mut button =
|
||||
Button::new(&mut self.close, Text::new("Close").size(14))
|
||||
.style(style::Button::Control)
|
||||
.padding(3);
|
||||
if total_panes > 1 && !is_pinned {
|
||||
button = button.on_press(Message::Close(pane));
|
||||
}
|
||||
button.into()
|
||||
}
|
||||
}
|
||||
|
||||
mod style {
|
||||
use crate::PANE_ID_COLOR_FOCUSED;
|
||||
use iced::{button, container, Background, Color, Vector};
|
||||
|
||||
const SURFACE: Color = Color::from_rgb(
|
||||
@ -419,7 +275,7 @@ mod style {
|
||||
fn style(&self) -> container::Style {
|
||||
container::Style {
|
||||
background: Some(Background::Color(SURFACE)),
|
||||
border_width: 2.0,
|
||||
border_width: 2,
|
||||
border_color: if self.is_focused {
|
||||
Color::BLACK
|
||||
} else {
|
||||
@ -433,8 +289,6 @@ mod style {
|
||||
pub enum Button {
|
||||
Primary,
|
||||
Destructive,
|
||||
Control,
|
||||
Pin,
|
||||
}
|
||||
|
||||
impl button::StyleSheet for Button {
|
||||
@ -444,14 +298,12 @@ mod style {
|
||||
Button::Destructive => {
|
||||
(None, Color::from_rgb8(0xFF, 0x47, 0x47))
|
||||
}
|
||||
Button::Control => (Some(PANE_ID_COLOR_FOCUSED), Color::WHITE),
|
||||
Button::Pin => (Some(ACTIVE), Color::WHITE),
|
||||
};
|
||||
|
||||
button::Style {
|
||||
text_color,
|
||||
background: background.map(Background::Color),
|
||||
border_radius: 5.0,
|
||||
border_radius: 5,
|
||||
shadow_offset: Vector::new(0.0, 0.0),
|
||||
..button::Style::default()
|
||||
}
|
||||
@ -466,8 +318,6 @@ mod style {
|
||||
a: 0.2,
|
||||
..active.text_color
|
||||
}),
|
||||
Button::Control => Some(PANE_ID_COLOR_FOCUSED),
|
||||
Button::Pin => Some(HOVERED),
|
||||
};
|
||||
|
||||
button::Style {
|
||||
|
@ -6,7 +6,7 @@ edition = "2018"
|
||||
publish = false
|
||||
|
||||
[dependencies]
|
||||
iced = { path = "../..", features = ["image", "debug", "tokio_old"] }
|
||||
iced = { path = "../..", features = ["image", "debug", "tokio"] }
|
||||
serde_json = "1.0"
|
||||
|
||||
[dependencies.serde]
|
||||
|
@ -1,6 +1,6 @@
|
||||
use iced::{
|
||||
button, futures, image, Align, Application, Button, Clipboard, Column,
|
||||
Command, Container, Element, Length, Row, Settings, Text,
|
||||
button, futures, image, Align, Application, Button, Column, Command,
|
||||
Container, Element, Image, Length, Row, Settings, Text,
|
||||
};
|
||||
|
||||
pub fn main() -> iced::Result {
|
||||
@ -48,11 +48,7 @@ impl Application for Pokedex {
|
||||
format!("{} - Pokédex", subtitle)
|
||||
}
|
||||
|
||||
fn update(
|
||||
&mut self,
|
||||
message: Message,
|
||||
_clipboard: &mut Clipboard,
|
||||
) -> Command<Message> {
|
||||
fn update(&mut self, message: Message) -> Command<Message> {
|
||||
match message {
|
||||
Message::PokemonFound(Ok(pokemon)) => {
|
||||
*self = Pokedex::Loaded {
|
||||
@ -116,20 +112,16 @@ struct Pokemon {
|
||||
name: String,
|
||||
description: String,
|
||||
image: image::Handle,
|
||||
image_viewer: image::viewer::State,
|
||||
}
|
||||
|
||||
impl Pokemon {
|
||||
const TOTAL: u16 = 807;
|
||||
|
||||
fn view(&mut self) -> Element<Message> {
|
||||
fn view(&self) -> Element<Message> {
|
||||
Row::new()
|
||||
.spacing(20)
|
||||
.align_items(Align::Center)
|
||||
.push(image::Viewer::new(
|
||||
&mut self.image_viewer,
|
||||
self.image.clone(),
|
||||
))
|
||||
.push(Image::new(self.image.clone()))
|
||||
.push(
|
||||
Column::new()
|
||||
.spacing(20)
|
||||
@ -208,15 +200,11 @@ impl Pokemon {
|
||||
.map(|c| if c.is_control() { ' ' } else { c })
|
||||
.collect(),
|
||||
image,
|
||||
image_viewer: image::viewer::State::new(),
|
||||
})
|
||||
}
|
||||
|
||||
async fn fetch_image(id: u16) -> Result<image::Handle, reqwest::Error> {
|
||||
let url = format!(
|
||||
"https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/{}.png",
|
||||
id
|
||||
);
|
||||
let url = format!("https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/{}.png", id);
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
{
|
||||
@ -263,7 +251,7 @@ mod style {
|
||||
background: Some(Background::Color(match self {
|
||||
Button::Primary => Color::from_rgb(0.11, 0.42, 0.87),
|
||||
})),
|
||||
border_radius: 12.0,
|
||||
border_radius: 12,
|
||||
shadow_offset: Vector::new(1.0, 1.0),
|
||||
text_color: Color::WHITE,
|
||||
..button::Style::default()
|
||||
|
@ -1,9 +0,0 @@
|
||||
[package]
|
||||
name = "qr_code"
|
||||
version = "0.1.0"
|
||||
authors = ["Héctor Ramón Jiménez <hector0193@gmail.com>"]
|
||||
edition = "2018"
|
||||
publish = false
|
||||
|
||||
[dependencies]
|
||||
iced = { path = "../..", features = ["qr_code"] }
|
@ -1,18 +0,0 @@
|
||||
## QR Code Generator
|
||||
|
||||
A basic QR code generator that showcases the `QRCode` widget.
|
||||
|
||||
The __[`main`]__ file contains all the code of the example.
|
||||
|
||||
<div align="center">
|
||||
<a href="https://gfycat.com/heavyexhaustedaracari">
|
||||
<img src="https://thumbs.gfycat.com/HeavyExhaustedAracari-size_restricted.gif">
|
||||
</a>
|
||||
</div>
|
||||
|
||||
You can run it with `cargo run`:
|
||||
```
|
||||
cargo run --package qr_code
|
||||
```
|
||||
|
||||
[`main`]: src/main.rs
|
@ -1,81 +0,0 @@
|
||||
use iced::qr_code::{self, QRCode};
|
||||
use iced::text_input::{self, TextInput};
|
||||
use iced::{
|
||||
Align, Column, Container, Element, Length, Sandbox, Settings, Text,
|
||||
};
|
||||
|
||||
pub fn main() -> iced::Result {
|
||||
QRGenerator::run(Settings::default())
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct QRGenerator {
|
||||
data: String,
|
||||
input: text_input::State,
|
||||
qr_code: Option<qr_code::State>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
enum Message {
|
||||
DataChanged(String),
|
||||
}
|
||||
|
||||
impl Sandbox for QRGenerator {
|
||||
type Message = Message;
|
||||
|
||||
fn new() -> Self {
|
||||
QRGenerator {
|
||||
qr_code: qr_code::State::new("").ok(),
|
||||
..Self::default()
|
||||
}
|
||||
}
|
||||
|
||||
fn title(&self) -> String {
|
||||
String::from("QR Code Generator - Iced")
|
||||
}
|
||||
|
||||
fn update(&mut self, message: Message) {
|
||||
match message {
|
||||
Message::DataChanged(mut data) => {
|
||||
data.truncate(100);
|
||||
|
||||
self.qr_code = qr_code::State::new(&data).ok();
|
||||
self.data = data;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn view(&mut self) -> Element<Message> {
|
||||
let title = Text::new("QR Code Generator")
|
||||
.size(70)
|
||||
.color([0.5, 0.5, 0.5]);
|
||||
|
||||
let input = TextInput::new(
|
||||
&mut self.input,
|
||||
"Type the data of your QR code here...",
|
||||
&self.data,
|
||||
Message::DataChanged,
|
||||
)
|
||||
.size(30)
|
||||
.padding(15);
|
||||
|
||||
let mut content = Column::new()
|
||||
.width(Length::Units(700))
|
||||
.spacing(20)
|
||||
.align_items(Align::Center)
|
||||
.push(title)
|
||||
.push(input);
|
||||
|
||||
if let Some(qr_code) = self.qr_code.as_mut() {
|
||||
content = content.push(QRCode::new(qr_code).cell_size(10));
|
||||
}
|
||||
|
||||
Container::new(content)
|
||||
.width(Length::Fill)
|
||||
.height(Length::Fill)
|
||||
.padding(20)
|
||||
.center_x()
|
||||
.center_y()
|
||||
.into()
|
||||
}
|
||||
}
|
@ -1,9 +0,0 @@
|
||||
[package]
|
||||
name = "scrollable"
|
||||
version = "0.1.0"
|
||||
authors = ["Clark Moody <clark@clarkmoody.com>"]
|
||||
edition = "2018"
|
||||
publish = false
|
||||
|
||||
[dependencies]
|
||||
iced = { path = "../..", features = ["debug"] }
|
@ -1,15 +0,0 @@
|
||||
# Scrollable
|
||||
An example showcasing the various size and style options for the Scrollable.
|
||||
|
||||
All the example code is located in the __[`main`](src/main.rs)__ file.
|
||||
|
||||
<div align="center">
|
||||
<a href="./screenshot.png">
|
||||
<img src="./screenshot.png" height="640px">
|
||||
</a>
|
||||
</div>
|
||||
|
||||
You can run it with `cargo run`:
|
||||
```
|
||||
cargo run --package scrollable
|
||||
```
|
Before Width: | Height: | Size: 145 KiB |
@ -1,255 +0,0 @@
|
||||
mod style;
|
||||
|
||||
use iced::{
|
||||
button, scrollable, Button, Column, Container, Element, Length,
|
||||
ProgressBar, Radio, Row, Rule, Sandbox, Scrollable, Settings, Space, Text,
|
||||
};
|
||||
|
||||
pub fn main() -> iced::Result {
|
||||
ScrollableDemo::run(Settings::default())
|
||||
}
|
||||
|
||||
struct ScrollableDemo {
|
||||
theme: style::Theme,
|
||||
variants: Vec<Variant>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
enum Message {
|
||||
ThemeChanged(style::Theme),
|
||||
ScrollToTop(usize),
|
||||
ScrollToBottom(usize),
|
||||
Scrolled(usize, f32),
|
||||
}
|
||||
|
||||
impl Sandbox for ScrollableDemo {
|
||||
type Message = Message;
|
||||
|
||||
fn new() -> Self {
|
||||
ScrollableDemo {
|
||||
theme: Default::default(),
|
||||
variants: Variant::all(),
|
||||
}
|
||||
}
|
||||
|
||||
fn title(&self) -> String {
|
||||
String::from("Scrollable - Iced")
|
||||
}
|
||||
|
||||
fn update(&mut self, message: Message) {
|
||||
match message {
|
||||
Message::ThemeChanged(theme) => self.theme = theme,
|
||||
Message::ScrollToTop(i) => {
|
||||
if let Some(variant) = self.variants.get_mut(i) {
|
||||
variant.scrollable.snap_to(0.0);
|
||||
|
||||
variant.latest_offset = 0.0;
|
||||
}
|
||||
}
|
||||
Message::ScrollToBottom(i) => {
|
||||
if let Some(variant) = self.variants.get_mut(i) {
|
||||
variant.scrollable.snap_to(1.0);
|
||||
|
||||
variant.latest_offset = 1.0;
|
||||
}
|
||||
}
|
||||
Message::Scrolled(i, offset) => {
|
||||
if let Some(variant) = self.variants.get_mut(i) {
|
||||
variant.latest_offset = offset;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn view(&mut self) -> Element<Message> {
|
||||
let ScrollableDemo {
|
||||
theme, variants, ..
|
||||
} = self;
|
||||
|
||||
let choose_theme = style::Theme::ALL.iter().fold(
|
||||
Column::new().spacing(10).push(Text::new("Choose a theme:")),
|
||||
|column, option| {
|
||||
column.push(
|
||||
Radio::new(
|
||||
*option,
|
||||
&format!("{:?}", option),
|
||||
Some(*theme),
|
||||
Message::ThemeChanged,
|
||||
)
|
||||
.style(*theme),
|
||||
)
|
||||
},
|
||||
);
|
||||
|
||||
let scrollable_row = Row::with_children(
|
||||
variants
|
||||
.iter_mut()
|
||||
.enumerate()
|
||||
.map(|(i, variant)| {
|
||||
let mut scrollable =
|
||||
Scrollable::new(&mut variant.scrollable)
|
||||
.padding(10)
|
||||
.spacing(10)
|
||||
.width(Length::Fill)
|
||||
.height(Length::Fill)
|
||||
.on_scroll(move |offset| {
|
||||
Message::Scrolled(i, offset)
|
||||
})
|
||||
.style(*theme)
|
||||
.push(Text::new(variant.title))
|
||||
.push(
|
||||
Button::new(
|
||||
&mut variant.scroll_to_bottom,
|
||||
Text::new("Scroll to bottom"),
|
||||
)
|
||||
.width(Length::Fill)
|
||||
.padding(10)
|
||||
.on_press(Message::ScrollToBottom(i)),
|
||||
);
|
||||
|
||||
if let Some(scrollbar_width) = variant.scrollbar_width {
|
||||
scrollable = scrollable
|
||||
.scrollbar_width(scrollbar_width)
|
||||
.push(Text::new(format!(
|
||||
"scrollbar_width: {:?}",
|
||||
scrollbar_width
|
||||
)));
|
||||
}
|
||||
|
||||
if let Some(scrollbar_margin) = variant.scrollbar_margin {
|
||||
scrollable = scrollable
|
||||
.scrollbar_margin(scrollbar_margin)
|
||||
.push(Text::new(format!(
|
||||
"scrollbar_margin: {:?}",
|
||||
scrollbar_margin
|
||||
)));
|
||||
}
|
||||
|
||||
if let Some(scroller_width) = variant.scroller_width {
|
||||
scrollable = scrollable
|
||||
.scroller_width(scroller_width)
|
||||
.push(Text::new(format!(
|
||||
"scroller_width: {:?}",
|
||||
scroller_width
|
||||
)));
|
||||
}
|
||||
|
||||
scrollable = scrollable
|
||||
.push(Space::with_height(Length::Units(100)))
|
||||
.push(Text::new(
|
||||
"Some content that should wrap within the \
|
||||
scrollable. Let's output a lot of short words, so \
|
||||
that we'll make sure to see how wrapping works \
|
||||
with these scrollbars.",
|
||||
))
|
||||
.push(Space::with_height(Length::Units(1200)))
|
||||
.push(Text::new("Middle"))
|
||||
.push(Space::with_height(Length::Units(1200)))
|
||||
.push(Text::new("The End."))
|
||||
.push(
|
||||
Button::new(
|
||||
&mut variant.scroll_to_top,
|
||||
Text::new("Scroll to top"),
|
||||
)
|
||||
.width(Length::Fill)
|
||||
.padding(10)
|
||||
.on_press(Message::ScrollToTop(i)),
|
||||
);
|
||||
|
||||
Column::new()
|
||||
.width(Length::Fill)
|
||||
.height(Length::Fill)
|
||||
.spacing(10)
|
||||
.push(
|
||||
Container::new(scrollable)
|
||||
.width(Length::Fill)
|
||||
.height(Length::Fill)
|
||||
.style(*theme),
|
||||
)
|
||||
.push(ProgressBar::new(
|
||||
0.0..=1.0,
|
||||
variant.latest_offset,
|
||||
))
|
||||
.into()
|
||||
})
|
||||
.collect(),
|
||||
)
|
||||
.spacing(20)
|
||||
.width(Length::Fill)
|
||||
.height(Length::Fill);
|
||||
|
||||
let content = Column::new()
|
||||
.spacing(20)
|
||||
.padding(20)
|
||||
.push(choose_theme)
|
||||
.push(Rule::horizontal(20).style(self.theme))
|
||||
.push(scrollable_row);
|
||||
|
||||
Container::new(content)
|
||||
.width(Length::Fill)
|
||||
.height(Length::Fill)
|
||||
.center_x()
|
||||
.center_y()
|
||||
.style(self.theme)
|
||||
.into()
|
||||
}
|
||||
}
|
||||
|
||||
/// A version of a scrollable
|
||||
struct Variant {
|
||||
title: &'static str,
|
||||
scrollable: scrollable::State,
|
||||
scroll_to_top: button::State,
|
||||
scroll_to_bottom: button::State,
|
||||
scrollbar_width: Option<u16>,
|
||||
scrollbar_margin: Option<u16>,
|
||||
scroller_width: Option<u16>,
|
||||
latest_offset: f32,
|
||||
}
|
||||
|
||||
impl Variant {
|
||||
pub fn all() -> Vec<Self> {
|
||||
vec![
|
||||
Self {
|
||||
title: "Default Scrollbar",
|
||||
scrollable: scrollable::State::new(),
|
||||
scroll_to_top: button::State::new(),
|
||||
scroll_to_bottom: button::State::new(),
|
||||
scrollbar_width: None,
|
||||
scrollbar_margin: None,
|
||||
scroller_width: None,
|
||||
latest_offset: 0.0,
|
||||
},
|
||||
Self {
|
||||
title: "Slimmed & Margin",
|
||||
scrollable: scrollable::State::new(),
|
||||
scroll_to_top: button::State::new(),
|
||||
scroll_to_bottom: button::State::new(),
|
||||
scrollbar_width: Some(4),
|
||||
scrollbar_margin: Some(3),
|
||||
scroller_width: Some(4),
|
||||
latest_offset: 0.0,
|
||||
},
|
||||
Self {
|
||||
title: "Wide Scroller",
|
||||
scrollable: scrollable::State::new(),
|
||||
scroll_to_top: button::State::new(),
|
||||
scroll_to_bottom: button::State::new(),
|
||||
scrollbar_width: Some(4),
|
||||
scrollbar_margin: None,
|
||||
scroller_width: Some(10),
|
||||
latest_offset: 0.0,
|
||||
},
|
||||
Self {
|
||||
title: "Narrow Scroller",
|
||||
scrollable: scrollable::State::new(),
|
||||
scroll_to_top: button::State::new(),
|
||||
scroll_to_bottom: button::State::new(),
|
||||
scrollbar_width: Some(10),
|
||||
scrollbar_margin: None,
|
||||
scroller_width: Some(4),
|
||||
latest_offset: 0.0,
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
@ -1,190 +0,0 @@
|
||||
use iced::{container, radio, rule, scrollable};
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum Theme {
|
||||
Light,
|
||||
Dark,
|
||||
}
|
||||
|
||||
impl Theme {
|
||||
pub const ALL: [Theme; 2] = [Theme::Light, Theme::Dark];
|
||||
}
|
||||
|
||||
impl Default for Theme {
|
||||
fn default() -> Theme {
|
||||
Theme::Light
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Theme> for Box<dyn container::StyleSheet> {
|
||||
fn from(theme: Theme) -> Self {
|
||||
match theme {
|
||||
Theme::Light => Default::default(),
|
||||
Theme::Dark => dark::Container.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Theme> for Box<dyn radio::StyleSheet> {
|
||||
fn from(theme: Theme) -> Self {
|
||||
match theme {
|
||||
Theme::Light => Default::default(),
|
||||
Theme::Dark => dark::Radio.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Theme> for Box<dyn scrollable::StyleSheet> {
|
||||
fn from(theme: Theme) -> Self {
|
||||
match theme {
|
||||
Theme::Light => Default::default(),
|
||||
Theme::Dark => dark::Scrollable.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Theme> for Box<dyn rule::StyleSheet> {
|
||||
fn from(theme: Theme) -> Self {
|
||||
match theme {
|
||||
Theme::Light => Default::default(),
|
||||
Theme::Dark => dark::Rule.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mod dark {
|
||||
use iced::{container, radio, rule, scrollable, Color};
|
||||
|
||||
const BACKGROUND: Color = Color::from_rgb(
|
||||
0x36 as f32 / 255.0,
|
||||
0x39 as f32 / 255.0,
|
||||
0x3F as f32 / 255.0,
|
||||
);
|
||||
|
||||
const SURFACE: Color = Color::from_rgb(
|
||||
0x40 as f32 / 255.0,
|
||||
0x44 as f32 / 255.0,
|
||||
0x4B as f32 / 255.0,
|
||||
);
|
||||
|
||||
const ACCENT: Color = Color::from_rgb(
|
||||
0x6F as f32 / 255.0,
|
||||
0xFF as f32 / 255.0,
|
||||
0xE9 as f32 / 255.0,
|
||||
);
|
||||
|
||||
const ACTIVE: Color = Color::from_rgb(
|
||||
0x72 as f32 / 255.0,
|
||||
0x89 as f32 / 255.0,
|
||||
0xDA as f32 / 255.0,
|
||||
);
|
||||
|
||||
const SCROLLBAR: Color = Color::from_rgb(
|
||||
0x2E as f32 / 255.0,
|
||||
0x33 as f32 / 255.0,
|
||||
0x38 as f32 / 255.0,
|
||||
);
|
||||
|
||||
const SCROLLER: Color = Color::from_rgb(
|
||||
0x20 as f32 / 255.0,
|
||||
0x22 as f32 / 255.0,
|
||||
0x25 as f32 / 255.0,
|
||||
);
|
||||
|
||||
pub struct Container;
|
||||
|
||||
impl container::StyleSheet for Container {
|
||||
fn style(&self) -> container::Style {
|
||||
container::Style {
|
||||
background: Color {
|
||||
a: 0.99,
|
||||
..BACKGROUND
|
||||
}
|
||||
.into(),
|
||||
text_color: Color::WHITE.into(),
|
||||
..container::Style::default()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Radio;
|
||||
|
||||
impl radio::StyleSheet for Radio {
|
||||
fn active(&self) -> radio::Style {
|
||||
radio::Style {
|
||||
background: SURFACE.into(),
|
||||
dot_color: ACTIVE,
|
||||
border_width: 1.0,
|
||||
border_color: ACTIVE,
|
||||
}
|
||||
}
|
||||
|
||||
fn hovered(&self) -> radio::Style {
|
||||
radio::Style {
|
||||
background: Color { a: 0.5, ..SURFACE }.into(),
|
||||
..self.active()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Scrollable;
|
||||
|
||||
impl scrollable::StyleSheet for Scrollable {
|
||||
fn active(&self) -> scrollable::Scrollbar {
|
||||
scrollable::Scrollbar {
|
||||
background: Color {
|
||||
a: 0.8,
|
||||
..SCROLLBAR
|
||||
}
|
||||
.into(),
|
||||
border_radius: 2.0,
|
||||
border_width: 0.0,
|
||||
border_color: Color::TRANSPARENT,
|
||||
scroller: scrollable::Scroller {
|
||||
color: Color { a: 0.7, ..SCROLLER },
|
||||
border_radius: 2.0,
|
||||
border_width: 0.0,
|
||||
border_color: Color::TRANSPARENT,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn hovered(&self) -> scrollable::Scrollbar {
|
||||
let active = self.active();
|
||||
|
||||
scrollable::Scrollbar {
|
||||
background: SCROLLBAR.into(),
|
||||
scroller: scrollable::Scroller {
|
||||
color: SCROLLER,
|
||||
..active.scroller
|
||||
},
|
||||
..active
|
||||
}
|
||||
}
|
||||
|
||||
fn dragging(&self) -> scrollable::Scrollbar {
|
||||
let hovered = self.hovered();
|
||||
|
||||
scrollable::Scrollbar {
|
||||
scroller: scrollable::Scroller {
|
||||
color: ACCENT,
|
||||
..hovered.scroller
|
||||
},
|
||||
..hovered
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Rule;
|
||||
|
||||
impl rule::StyleSheet for Rule {
|
||||
fn style(&self) -> rule::Style {
|
||||
rule::Style {
|
||||
color: SURFACE,
|
||||
width: 2,
|
||||
radius: 1.0,
|
||||
fill_mode: rule::FillMode::Percent(30.0),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -7,4 +7,4 @@ publish = false
|
||||
|
||||
[dependencies]
|
||||
iced = { path = "../..", features = ["canvas", "tokio", "debug"] }
|
||||
rand = "0.8.3"
|
||||
rand = "0.7"
|
||||
|
@ -8,8 +8,8 @@
|
||||
//! [1]: https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API/Tutorial/Basic_animations#An_animated_solar_system
|
||||
use iced::{
|
||||
canvas::{self, Cursor, Path, Stroke},
|
||||
executor, time, window, Application, Canvas, Clipboard, Color, Command,
|
||||
Element, Length, Point, Rectangle, Settings, Size, Subscription, Vector,
|
||||
executor, time, window, Application, Canvas, Color, Command, Element,
|
||||
Length, Point, Rectangle, Settings, Size, Subscription, Vector,
|
||||
};
|
||||
|
||||
use std::time::Instant;
|
||||
@ -48,11 +48,7 @@ impl Application for SolarSystem {
|
||||
String::from("Solar system - Iced")
|
||||
}
|
||||
|
||||
fn update(
|
||||
&mut self,
|
||||
message: Message,
|
||||
_clipboard: &mut Clipboard,
|
||||
) -> Command<Message> {
|
||||
fn update(&mut self, message: Message) -> Command<Message> {
|
||||
match message {
|
||||
Message::Tick(instant) => {
|
||||
self.state.update(instant);
|
||||
@ -121,13 +117,15 @@ impl State {
|
||||
(
|
||||
Point::new(
|
||||
rng.gen_range(
|
||||
(-(width as f32) / 2.0)..(width as f32 / 2.0),
|
||||
-(width as f32) / 2.0,
|
||||
width as f32 / 2.0,
|
||||
),
|
||||
rng.gen_range(
|
||||
(-(height as f32) / 2.0)..(height as f32 / 2.0),
|
||||
-(height as f32) / 2.0,
|
||||
height as f32 / 2.0,
|
||||
),
|
||||
),
|
||||
rng.gen_range(0.5..1.0),
|
||||
rng.gen_range(0.5, 1.0),
|
||||
)
|
||||
})
|
||||
.collect()
|
||||
|
@ -6,4 +6,4 @@ edition = "2018"
|
||||
publish = false
|
||||
|
||||
[dependencies]
|
||||
iced = { path = "../..", features = ["smol"] }
|
||||
iced = { path = "../..", features = ["tokio"] }
|
||||
|
@ -1,6 +1,6 @@
|
||||
use iced::{
|
||||
button, executor, time, Align, Application, Button, Clipboard, Column,
|
||||
Command, Container, Element, HorizontalAlignment, Length, Row, Settings,
|
||||
button, executor, time, Align, Application, Button, Column, Command,
|
||||
Container, Element, HorizontalAlignment, Length, Row, Settings,
|
||||
Subscription, Text,
|
||||
};
|
||||
use std::time::{Duration, Instant};
|
||||
@ -49,11 +49,7 @@ impl Application for Stopwatch {
|
||||
String::from("Stopwatch - Iced")
|
||||
}
|
||||
|
||||
fn update(
|
||||
&mut self,
|
||||
message: Message,
|
||||
_clipboard: &mut Clipboard,
|
||||
) -> Command<Message> {
|
||||
fn update(&mut self, message: Message) -> Command<Message> {
|
||||
match message {
|
||||
Message::Toggle => match self.state {
|
||||
State::Idle => {
|
||||
@ -165,7 +161,7 @@ mod style {
|
||||
Button::Secondary => Color::from_rgb(0.5, 0.5, 0.5),
|
||||
Button::Destructive => Color::from_rgb(0.8, 0.2, 0.2),
|
||||
})),
|
||||
border_radius: 12.0,
|
||||
border_radius: 12,
|
||||
shadow_offset: Vector::new(1.0, 1.0),
|
||||
text_color: Color::WHITE,
|
||||
..button::Style::default()
|
||||
|
@ -1,7 +1,7 @@
|
||||
use iced::{
|
||||
button, scrollable, slider, text_input, Align, Button, Checkbox, Column,
|
||||
Container, Element, Length, ProgressBar, Radio, Row, Rule, Sandbox,
|
||||
Scrollable, Settings, Slider, Space, Text, TextInput, Toggler,
|
||||
Scrollable, Settings, Slider, Space, Text, TextInput,
|
||||
};
|
||||
|
||||
pub fn main() -> iced::Result {
|
||||
@ -17,8 +17,7 @@ struct Styling {
|
||||
button: button::State,
|
||||
slider: slider::State,
|
||||
slider_value: f32,
|
||||
checkbox_value: bool,
|
||||
toggler_value: bool,
|
||||
toggle_value: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
@ -28,7 +27,6 @@ enum Message {
|
||||
ButtonPressed,
|
||||
SliderChanged(f32),
|
||||
CheckboxToggled(bool),
|
||||
TogglerToggled(bool),
|
||||
}
|
||||
|
||||
impl Sandbox for Styling {
|
||||
@ -46,10 +44,9 @@ impl Sandbox for Styling {
|
||||
match message {
|
||||
Message::ThemeChanged(theme) => self.theme = theme,
|
||||
Message::InputChanged(value) => self.input_value = value,
|
||||
Message::ButtonPressed => {}
|
||||
Message::ButtonPressed => (),
|
||||
Message::SliderChanged(value) => self.slider_value = value,
|
||||
Message::CheckboxToggled(value) => self.checkbox_value = value,
|
||||
Message::TogglerToggled(value) => self.toggler_value = value,
|
||||
Message::CheckboxToggled(value) => self.toggle_value = value,
|
||||
}
|
||||
}
|
||||
|
||||
@ -104,19 +101,11 @@ impl Sandbox for Styling {
|
||||
.push(Text::new("You did it!"));
|
||||
|
||||
let checkbox = Checkbox::new(
|
||||
self.checkbox_value,
|
||||
"Check me!",
|
||||
self.toggle_value,
|
||||
"Toggle me!",
|
||||
Message::CheckboxToggled,
|
||||
)
|
||||
.style(self.theme);
|
||||
|
||||
let toggler = Toggler::new(
|
||||
self.toggler_value,
|
||||
String::from("Toggle me!"),
|
||||
Message::TogglerToggled,
|
||||
)
|
||||
.width(Length::Shrink)
|
||||
.spacing(10)
|
||||
.width(Length::Fill)
|
||||
.style(self.theme);
|
||||
|
||||
let content = Column::new()
|
||||
@ -135,13 +124,7 @@ impl Sandbox for Styling {
|
||||
.align_items(Align::Center)
|
||||
.push(scrollable)
|
||||
.push(Rule::vertical(38).style(self.theme))
|
||||
.push(
|
||||
Column::new()
|
||||
.width(Length::Shrink)
|
||||
.spacing(20)
|
||||
.push(checkbox)
|
||||
.push(toggler),
|
||||
),
|
||||
.push(checkbox),
|
||||
);
|
||||
|
||||
Container::new(content)
|
||||
@ -157,7 +140,7 @@ impl Sandbox for Styling {
|
||||
mod style {
|
||||
use iced::{
|
||||
button, checkbox, container, progress_bar, radio, rule, scrollable,
|
||||
slider, text_input, toggler,
|
||||
slider, text_input,
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
@ -248,15 +231,6 @@ mod style {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Theme> for Box<dyn toggler::StyleSheet> {
|
||||
fn from(theme: Theme) -> Self {
|
||||
match theme {
|
||||
Theme::Light => Default::default(),
|
||||
Theme::Dark => dark::Toggler.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Theme> for Box<dyn rule::StyleSheet> {
|
||||
fn from(theme: Theme) -> Self {
|
||||
match theme {
|
||||
@ -275,7 +249,7 @@ mod style {
|
||||
fn active(&self) -> button::Style {
|
||||
button::Style {
|
||||
background: Color::from_rgb(0.11, 0.42, 0.87).into(),
|
||||
border_radius: 12.0,
|
||||
border_radius: 12,
|
||||
shadow_offset: Vector::new(1.0, 1.0),
|
||||
text_color: Color::from_rgb8(0xEE, 0xEE, 0xEE),
|
||||
..button::Style::default()
|
||||
@ -295,7 +269,7 @@ mod style {
|
||||
mod dark {
|
||||
use iced::{
|
||||
button, checkbox, container, progress_bar, radio, rule, scrollable,
|
||||
slider, text_input, toggler, Color,
|
||||
slider, text_input, Color,
|
||||
};
|
||||
|
||||
const SURFACE: Color = Color::from_rgb(
|
||||
@ -341,7 +315,7 @@ mod style {
|
||||
radio::Style {
|
||||
background: SURFACE.into(),
|
||||
dot_color: ACTIVE,
|
||||
border_width: 1.0,
|
||||
border_width: 1,
|
||||
border_color: ACTIVE,
|
||||
}
|
||||
}
|
||||
@ -360,15 +334,15 @@ mod style {
|
||||
fn active(&self) -> text_input::Style {
|
||||
text_input::Style {
|
||||
background: SURFACE.into(),
|
||||
border_radius: 2.0,
|
||||
border_width: 0.0,
|
||||
border_radius: 2,
|
||||
border_width: 0,
|
||||
border_color: Color::TRANSPARENT,
|
||||
}
|
||||
}
|
||||
|
||||
fn focused(&self) -> text_input::Style {
|
||||
text_input::Style {
|
||||
border_width: 1.0,
|
||||
border_width: 1,
|
||||
border_color: ACCENT,
|
||||
..self.active()
|
||||
}
|
||||
@ -376,7 +350,7 @@ mod style {
|
||||
|
||||
fn hovered(&self) -> text_input::Style {
|
||||
text_input::Style {
|
||||
border_width: 1.0,
|
||||
border_width: 1,
|
||||
border_color: Color { a: 0.3, ..ACCENT },
|
||||
..self.focused()
|
||||
}
|
||||
@ -401,7 +375,7 @@ mod style {
|
||||
fn active(&self) -> button::Style {
|
||||
button::Style {
|
||||
background: ACTIVE.into(),
|
||||
border_radius: 3.0,
|
||||
border_radius: 3,
|
||||
text_color: Color::WHITE,
|
||||
..button::Style::default()
|
||||
}
|
||||
@ -417,7 +391,7 @@ mod style {
|
||||
|
||||
fn pressed(&self) -> button::Style {
|
||||
button::Style {
|
||||
border_width: 1.0,
|
||||
border_width: 1,
|
||||
border_color: Color::WHITE,
|
||||
..self.hovered()
|
||||
}
|
||||
@ -430,13 +404,13 @@ mod style {
|
||||
fn active(&self) -> scrollable::Scrollbar {
|
||||
scrollable::Scrollbar {
|
||||
background: SURFACE.into(),
|
||||
border_radius: 2.0,
|
||||
border_width: 0.0,
|
||||
border_radius: 2,
|
||||
border_width: 0,
|
||||
border_color: Color::TRANSPARENT,
|
||||
scroller: scrollable::Scroller {
|
||||
color: ACTIVE,
|
||||
border_radius: 2.0,
|
||||
border_width: 0.0,
|
||||
border_radius: 2,
|
||||
border_width: 0,
|
||||
border_color: Color::TRANSPARENT,
|
||||
},
|
||||
}
|
||||
@ -475,9 +449,9 @@ mod style {
|
||||
slider::Style {
|
||||
rail_colors: (ACTIVE, Color { a: 0.1, ..ACTIVE }),
|
||||
handle: slider::Handle {
|
||||
shape: slider::HandleShape::Circle { radius: 9.0 },
|
||||
shape: slider::HandleShape::Circle { radius: 9 },
|
||||
color: ACTIVE,
|
||||
border_width: 0.0,
|
||||
border_width: 0,
|
||||
border_color: Color::TRANSPARENT,
|
||||
},
|
||||
}
|
||||
@ -515,7 +489,7 @@ mod style {
|
||||
progress_bar::Style {
|
||||
background: SURFACE.into(),
|
||||
bar: ACTIVE.into(),
|
||||
border_radius: 10.0,
|
||||
border_radius: 10,
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -528,8 +502,8 @@ mod style {
|
||||
background: if is_checked { ACTIVE } else { SURFACE }
|
||||
.into(),
|
||||
checkmark_color: Color::WHITE,
|
||||
border_radius: 2.0,
|
||||
border_width: 1.0,
|
||||
border_radius: 2,
|
||||
border_width: 1,
|
||||
border_color: ACTIVE,
|
||||
}
|
||||
}
|
||||
@ -546,35 +520,6 @@ mod style {
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Toggler;
|
||||
|
||||
impl toggler::StyleSheet for Toggler {
|
||||
fn active(&self, is_active: bool) -> toggler::Style {
|
||||
toggler::Style {
|
||||
background: if is_active { ACTIVE } else { SURFACE },
|
||||
background_border: None,
|
||||
foreground: if is_active { Color::WHITE } else { ACTIVE },
|
||||
foreground_border: None,
|
||||
}
|
||||
}
|
||||
|
||||
fn hovered(&self, is_active: bool) -> toggler::Style {
|
||||
toggler::Style {
|
||||
background: if is_active { ACTIVE } else { SURFACE },
|
||||
background_border: None,
|
||||
foreground: if is_active {
|
||||
Color {
|
||||
a: 0.5,
|
||||
..Color::WHITE
|
||||
}
|
||||
} else {
|
||||
Color { a: 0.5, ..ACTIVE }
|
||||
},
|
||||
foreground_border: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Rule;
|
||||
|
||||
impl rule::StyleSheet for Rule {
|
||||
@ -582,7 +527,7 @@ mod style {
|
||||
rule::Style {
|
||||
color: SURFACE,
|
||||
width: 2,
|
||||
radius: 1.0,
|
||||
radius: 1,
|
||||
fill_mode: rule::FillMode::Padded(15),
|
||||
}
|
||||
}
|
||||
|
@ -12,7 +12,7 @@ serde_json = "1.0"
|
||||
|
||||
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
|
||||
async-std = "1.0"
|
||||
directories-next = "2.0"
|
||||
directories = "2.0"
|
||||
|
||||
[target.'cfg(target_arch = "wasm32")'.dependencies]
|
||||
web-sys = { version = "0.3", features = ["Window", "Storage"] }
|
||||
|
@ -1,7 +1,7 @@
|
||||
use iced::{
|
||||
button, scrollable, text_input, Align, Application, Button, Checkbox,
|
||||
Clipboard, Column, Command, Container, Element, Font, HorizontalAlignment,
|
||||
Length, Row, Scrollable, Settings, Text, TextInput,
|
||||
Column, Command, Container, Element, Font, HorizontalAlignment, Length,
|
||||
Row, Scrollable, Settings, Text, TextInput,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
@ -58,11 +58,7 @@ impl Application for Todos {
|
||||
format!("Todos{} - Iced", if dirty { "*" } else { "" })
|
||||
}
|
||||
|
||||
fn update(
|
||||
&mut self,
|
||||
message: Message,
|
||||
_clipboard: &mut Clipboard,
|
||||
) -> Command<Message> {
|
||||
fn update(&mut self, message: Message) -> Command<Message> {
|
||||
match self {
|
||||
Todos::Loading => {
|
||||
match message {
|
||||
@ -493,6 +489,7 @@ enum LoadError {
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
enum SaveError {
|
||||
DirectoryError,
|
||||
FileError,
|
||||
WriteError,
|
||||
FormatError,
|
||||
@ -502,7 +499,7 @@ enum SaveError {
|
||||
impl SavedState {
|
||||
fn path() -> std::path::PathBuf {
|
||||
let mut path = if let Some(project_dirs) =
|
||||
directories_next::ProjectDirs::from("rs", "Iced", "Todos")
|
||||
directories::ProjectDirs::from("rs", "Iced", "Todos")
|
||||
{
|
||||
project_dirs.data_dir().into()
|
||||
} else {
|
||||
@ -541,7 +538,7 @@ impl SavedState {
|
||||
if let Some(dir) = path.parent() {
|
||||
async_std::fs::create_dir_all(dir)
|
||||
.await
|
||||
.map_err(|_| SaveError::FileError)?;
|
||||
.map_err(|_| SaveError::DirectoryError)?;
|
||||
}
|
||||
|
||||
{
|
||||
@ -614,7 +611,7 @@ mod style {
|
||||
background: Some(Background::Color(
|
||||
Color::from_rgb(0.2, 0.2, 0.7),
|
||||
)),
|
||||
border_radius: 10.0,
|
||||
border_radius: 10,
|
||||
text_color: Color::WHITE,
|
||||
..button::Style::default()
|
||||
}
|
||||
@ -630,7 +627,7 @@ mod style {
|
||||
background: Some(Background::Color(Color::from_rgb(
|
||||
0.8, 0.2, 0.2,
|
||||
))),
|
||||
border_radius: 5.0,
|
||||
border_radius: 5,
|
||||
text_color: Color::WHITE,
|
||||
shadow_offset: Vector::new(1.0, 1.0),
|
||||
..button::Style::default()
|
||||
|
@ -1,9 +0,0 @@
|
||||
[package]
|
||||
name = "tooltip"
|
||||
version = "0.1.0"
|
||||
authors = ["Yusuf Bera Ertan <y.bera003.06@protonmail.com>"]
|
||||
edition = "2018"
|
||||
publish = false
|
||||
|
||||
[dependencies]
|
||||
iced = { path = "../..", features = ["debug"] }
|
@ -1,14 +0,0 @@
|
||||
## Tooltip
|
||||
|
||||
A tooltip.
|
||||
|
||||
It displays and positions a widget on another based on cursor position.
|
||||
|
||||
The __[`main`]__ file contains all the code of the example.
|
||||
|
||||
You can run it with `cargo run`:
|
||||
```
|
||||
cargo run --package tooltip
|
||||
```
|
||||
|
||||
[`main`]: src/main.rs
|
@ -1,138 +0,0 @@
|
||||
use iced::tooltip::{self, Tooltip};
|
||||
use iced::{
|
||||
button, Button, Column, Container, Element, HorizontalAlignment, Length,
|
||||
Row, Sandbox, Settings, Text, VerticalAlignment,
|
||||
};
|
||||
|
||||
pub fn main() {
|
||||
Example::run(Settings::default()).unwrap()
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct Example {
|
||||
top: button::State,
|
||||
bottom: button::State,
|
||||
right: button::State,
|
||||
left: button::State,
|
||||
follow_cursor: button::State,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
struct Message;
|
||||
|
||||
impl Sandbox for Example {
|
||||
type Message = Message;
|
||||
|
||||
fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
|
||||
fn title(&self) -> String {
|
||||
String::from("Tooltip - Iced")
|
||||
}
|
||||
|
||||
fn update(&mut self, _message: Message) {}
|
||||
|
||||
fn view(&mut self) -> Element<Message> {
|
||||
let top =
|
||||
tooltip("Tooltip at top", &mut self.top, tooltip::Position::Top);
|
||||
|
||||
let bottom = tooltip(
|
||||
"Tooltip at bottom",
|
||||
&mut self.bottom,
|
||||
tooltip::Position::Bottom,
|
||||
);
|
||||
|
||||
let left =
|
||||
tooltip("Tooltip at left", &mut self.left, tooltip::Position::Left);
|
||||
|
||||
let right = tooltip(
|
||||
"Tooltip at right",
|
||||
&mut self.right,
|
||||
tooltip::Position::Right,
|
||||
);
|
||||
|
||||
let fixed_tooltips = Row::with_children(vec![
|
||||
top.into(),
|
||||
bottom.into(),
|
||||
left.into(),
|
||||
right.into(),
|
||||
])
|
||||
.width(Length::Fill)
|
||||
.height(Length::Fill)
|
||||
.align_items(iced::Align::Center)
|
||||
.spacing(50);
|
||||
|
||||
let follow_cursor = tooltip(
|
||||
"Tooltip follows cursor",
|
||||
&mut self.follow_cursor,
|
||||
tooltip::Position::FollowCursor,
|
||||
);
|
||||
|
||||
let content = Column::with_children(vec![
|
||||
Container::new(fixed_tooltips)
|
||||
.width(Length::Fill)
|
||||
.height(Length::Fill)
|
||||
.center_x()
|
||||
.center_y()
|
||||
.into(),
|
||||
follow_cursor.into(),
|
||||
])
|
||||
.width(Length::Fill)
|
||||
.height(Length::Fill)
|
||||
.spacing(50);
|
||||
|
||||
Container::new(content)
|
||||
.width(Length::Fill)
|
||||
.height(Length::Fill)
|
||||
.center_x()
|
||||
.center_y()
|
||||
.padding(50)
|
||||
.into()
|
||||
}
|
||||
}
|
||||
|
||||
fn tooltip<'a>(
|
||||
label: &str,
|
||||
button_state: &'a mut button::State,
|
||||
position: tooltip::Position,
|
||||
) -> Element<'a, Message> {
|
||||
Tooltip::new(
|
||||
Button::new(
|
||||
button_state,
|
||||
Text::new(label)
|
||||
.size(40)
|
||||
.width(Length::Fill)
|
||||
.height(Length::Fill)
|
||||
.horizontal_alignment(HorizontalAlignment::Center)
|
||||
.vertical_alignment(VerticalAlignment::Center),
|
||||
)
|
||||
.on_press(Message)
|
||||
.width(Length::Fill)
|
||||
.height(Length::Fill),
|
||||
"Tooltip",
|
||||
position,
|
||||
)
|
||||
.gap(5)
|
||||
.padding(10)
|
||||
.style(style::Tooltip)
|
||||
.into()
|
||||
}
|
||||
|
||||
mod style {
|
||||
use iced::container;
|
||||
use iced::Color;
|
||||
|
||||
pub struct Tooltip;
|
||||
|
||||
impl container::StyleSheet for Tooltip {
|
||||
fn style(&self) -> container::Style {
|
||||
container::Style {
|
||||
text_color: Some(Color::from_rgb8(0xEE, 0xEE, 0xEE)),
|
||||
background: Some(Color::from_rgb(0.11, 0.42, 0.87).into()),
|
||||
border_radius: 12.0,
|
||||
..container::Style::default()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -7,4 +7,4 @@ publish = false
|
||||
|
||||
[dependencies]
|
||||
iced = { path = "../..", features = ["image", "debug"] }
|
||||
env_logger = "0.8"
|
||||
env_logger = "0.7"
|
||||
|
@ -1,7 +1,7 @@
|
||||
use iced::{
|
||||
button, scrollable, slider, text_input, Button, Checkbox, Color, Column,
|
||||
Container, Element, HorizontalAlignment, Image, Length, Radio, Row,
|
||||
Sandbox, Scrollable, Settings, Slider, Space, Text, TextInput, Toggler,
|
||||
Sandbox, Scrollable, Settings, Slider, Space, Text, TextInput,
|
||||
};
|
||||
|
||||
pub fn main() -> iced::Result {
|
||||
@ -135,9 +135,6 @@ impl Steps {
|
||||
color: Color::BLACK,
|
||||
},
|
||||
Step::Radio { selection: None },
|
||||
Step::Toggler {
|
||||
can_continue: false,
|
||||
},
|
||||
Step::Image {
|
||||
width: 300,
|
||||
slider: slider::State::new(),
|
||||
@ -209,9 +206,6 @@ enum Step {
|
||||
Radio {
|
||||
selection: Option<Language>,
|
||||
},
|
||||
Toggler {
|
||||
can_continue: bool,
|
||||
},
|
||||
Image {
|
||||
width: u16,
|
||||
slider: slider::State,
|
||||
@ -238,7 +232,6 @@ pub enum StepMessage {
|
||||
InputChanged(String),
|
||||
ToggleSecureInput(bool),
|
||||
DebugToggled(bool),
|
||||
TogglerChanged(bool),
|
||||
}
|
||||
|
||||
impl<'a> Step {
|
||||
@ -294,11 +287,6 @@ impl<'a> Step {
|
||||
*is_secure = toggle;
|
||||
}
|
||||
}
|
||||
StepMessage::TogglerChanged(value) => {
|
||||
if let Step::Toggler { can_continue, .. } = self {
|
||||
*can_continue = value;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@ -306,7 +294,6 @@ impl<'a> Step {
|
||||
match self {
|
||||
Step::Welcome => "Welcome",
|
||||
Step::Radio { .. } => "Radio button",
|
||||
Step::Toggler { .. } => "Toggler",
|
||||
Step::Slider { .. } => "Slider",
|
||||
Step::Text { .. } => "Text",
|
||||
Step::Image { .. } => "Image",
|
||||
@ -322,7 +309,6 @@ impl<'a> Step {
|
||||
match self {
|
||||
Step::Welcome => true,
|
||||
Step::Radio { selection } => *selection == Some(Language::Rust),
|
||||
Step::Toggler { can_continue } => *can_continue,
|
||||
Step::Slider { .. } => true,
|
||||
Step::Text { .. } => true,
|
||||
Step::Image { .. } => true,
|
||||
@ -338,7 +324,6 @@ impl<'a> Step {
|
||||
match self {
|
||||
Step::Welcome => Self::welcome(),
|
||||
Step::Radio { selection } => Self::radio(*selection),
|
||||
Step::Toggler { can_continue } => Self::toggler(*can_continue),
|
||||
Step::Slider { state, value } => Self::slider(state, *value),
|
||||
Step::Text {
|
||||
size_slider,
|
||||
@ -560,21 +545,6 @@ impl<'a> Step {
|
||||
))
|
||||
}
|
||||
|
||||
fn toggler(can_continue: bool) -> Column<'a, StepMessage> {
|
||||
Self::container("Toggler")
|
||||
.push(Text::new(
|
||||
"A toggler is mostly used to enable or disable something.",
|
||||
))
|
||||
.push(
|
||||
Container::new(Toggler::new(
|
||||
can_continue,
|
||||
String::from("Toggle me to continue..."),
|
||||
StepMessage::TogglerChanged,
|
||||
))
|
||||
.padding([0, 40]),
|
||||
)
|
||||
}
|
||||
|
||||
fn image(
|
||||
width: u16,
|
||||
slider: &'a mut slider::State,
|
||||
@ -719,7 +689,7 @@ fn ferris<'a>(width: u16) -> Container<'a, StepMessage> {
|
||||
.center_x()
|
||||
}
|
||||
|
||||
fn button<'a, Message: Clone>(
|
||||
fn button<'a, Message>(
|
||||
state: &'a mut button::State,
|
||||
label: &str,
|
||||
) -> Button<'a, Message> {
|
||||
@ -799,7 +769,7 @@ mod style {
|
||||
Button::Primary => Color::from_rgb(0.11, 0.42, 0.87),
|
||||
Button::Secondary => Color::from_rgb(0.5, 0.5, 0.5),
|
||||
})),
|
||||
border_radius: 12.0,
|
||||
border_radius: 12,
|
||||
shadow_offset: Vector::new(1.0, 1.0),
|
||||
text_color: Color::from_rgb8(0xEE, 0xEE, 0xEE),
|
||||
..button::Style::default()
|
||||
|
@ -1,10 +0,0 @@
|
||||
[package]
|
||||
name = "tour_glow"
|
||||
version = "0.1.0"
|
||||
authors = ["Héctor Ramón Jiménez <hector0193@gmail.com>"]
|
||||
edition = "2018"
|
||||
publish = false
|
||||
|
||||
[dependencies]
|
||||
iced = { path = "../..", features = ["glow", "debug"] }
|
||||
env_logger = "0.8"
|
@ -1,28 +0,0 @@
|
||||
## 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
|
@ -1,809 +0,0 @@
|
||||
use iced::{
|
||||
button, scrollable, slider, text_input, Button, Checkbox, Color, Column,
|
||||
Container, Element, HorizontalAlignment, Length, Radio, Row, Sandbox,
|
||||
Scrollable, Settings, Slider, Space, Text, TextInput, Toggler,
|
||||
};
|
||||
|
||||
pub fn main() -> iced::Result {
|
||||
env_logger::init();
|
||||
|
||||
Tour::run(Settings::default())
|
||||
}
|
||||
|
||||
pub struct Tour {
|
||||
steps: Steps,
|
||||
scroll: scrollable::State,
|
||||
back_button: button::State,
|
||||
next_button: button::State,
|
||||
debug: bool,
|
||||
}
|
||||
|
||||
impl Sandbox for Tour {
|
||||
type Message = Message;
|
||||
|
||||
fn new() -> Tour {
|
||||
Tour {
|
||||
steps: Steps::new(),
|
||||
scroll: scrollable::State::new(),
|
||||
back_button: button::State::new(),
|
||||
next_button: button::State::new(),
|
||||
debug: false,
|
||||
}
|
||||
}
|
||||
|
||||
fn title(&self) -> String {
|
||||
format!("{} - Iced", self.steps.title())
|
||||
}
|
||||
|
||||
fn update(&mut self, event: Message) {
|
||||
match event {
|
||||
Message::BackPressed => {
|
||||
self.steps.go_back();
|
||||
}
|
||||
Message::NextPressed => {
|
||||
self.steps.advance();
|
||||
}
|
||||
Message::StepMessage(step_msg) => {
|
||||
self.steps.update(step_msg, &mut self.debug);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn view(&mut self) -> Element<Message> {
|
||||
let Tour {
|
||||
steps,
|
||||
scroll,
|
||||
back_button,
|
||||
next_button,
|
||||
..
|
||||
} = self;
|
||||
|
||||
let mut controls = Row::new();
|
||||
|
||||
if steps.has_previous() {
|
||||
controls = controls.push(
|
||||
button(back_button, "Back")
|
||||
.on_press(Message::BackPressed)
|
||||
.style(style::Button::Secondary),
|
||||
);
|
||||
}
|
||||
|
||||
controls = controls.push(Space::with_width(Length::Fill));
|
||||
|
||||
if steps.can_continue() {
|
||||
controls = controls.push(
|
||||
button(next_button, "Next")
|
||||
.on_press(Message::NextPressed)
|
||||
.style(style::Button::Primary),
|
||||
);
|
||||
}
|
||||
|
||||
let content: Element<_> = Column::new()
|
||||
.max_width(540)
|
||||
.spacing(20)
|
||||
.padding(20)
|
||||
.push(steps.view(self.debug).map(Message::StepMessage))
|
||||
.push(controls)
|
||||
.into();
|
||||
|
||||
let content = if self.debug {
|
||||
content.explain(Color::BLACK)
|
||||
} else {
|
||||
content
|
||||
};
|
||||
|
||||
let scrollable = Scrollable::new(scroll)
|
||||
.push(Container::new(content).width(Length::Fill).center_x());
|
||||
|
||||
Container::new(scrollable)
|
||||
.height(Length::Fill)
|
||||
.center_y()
|
||||
.into()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum Message {
|
||||
BackPressed,
|
||||
NextPressed,
|
||||
StepMessage(StepMessage),
|
||||
}
|
||||
|
||||
struct Steps {
|
||||
steps: Vec<Step>,
|
||||
current: usize,
|
||||
}
|
||||
|
||||
impl Steps {
|
||||
fn new() -> Steps {
|
||||
Steps {
|
||||
steps: vec![
|
||||
Step::Welcome,
|
||||
Step::Slider {
|
||||
state: slider::State::new(),
|
||||
value: 50,
|
||||
},
|
||||
Step::RowsAndColumns {
|
||||
layout: Layout::Row,
|
||||
spacing_slider: slider::State::new(),
|
||||
spacing: 20,
|
||||
},
|
||||
Step::Text {
|
||||
size_slider: slider::State::new(),
|
||||
size: 30,
|
||||
color_sliders: [slider::State::new(); 3],
|
||||
color: Color::BLACK,
|
||||
},
|
||||
Step::Radio { selection: None },
|
||||
Step::Toggler {
|
||||
can_continue: false,
|
||||
},
|
||||
Step::Image {
|
||||
width: 300,
|
||||
slider: slider::State::new(),
|
||||
},
|
||||
Step::Scrollable,
|
||||
Step::TextInput {
|
||||
value: String::new(),
|
||||
is_secure: false,
|
||||
state: text_input::State::new(),
|
||||
},
|
||||
Step::Debugger,
|
||||
Step::End,
|
||||
],
|
||||
current: 0,
|
||||
}
|
||||
}
|
||||
|
||||
fn update(&mut self, msg: StepMessage, debug: &mut bool) {
|
||||
self.steps[self.current].update(msg, debug);
|
||||
}
|
||||
|
||||
fn view(&mut self, debug: bool) -> Element<StepMessage> {
|
||||
self.steps[self.current].view(debug)
|
||||
}
|
||||
|
||||
fn advance(&mut self) {
|
||||
if self.can_continue() {
|
||||
self.current += 1;
|
||||
}
|
||||
}
|
||||
|
||||
fn go_back(&mut self) {
|
||||
if self.has_previous() {
|
||||
self.current -= 1;
|
||||
}
|
||||
}
|
||||
|
||||
fn has_previous(&self) -> bool {
|
||||
self.current > 0
|
||||
}
|
||||
|
||||
fn can_continue(&self) -> bool {
|
||||
self.current + 1 < self.steps.len()
|
||||
&& self.steps[self.current].can_continue()
|
||||
}
|
||||
|
||||
fn title(&self) -> &str {
|
||||
self.steps[self.current].title()
|
||||
}
|
||||
}
|
||||
|
||||
enum Step {
|
||||
Welcome,
|
||||
Slider {
|
||||
state: slider::State,
|
||||
value: u8,
|
||||
},
|
||||
RowsAndColumns {
|
||||
layout: Layout,
|
||||
spacing_slider: slider::State,
|
||||
spacing: u16,
|
||||
},
|
||||
Text {
|
||||
size_slider: slider::State,
|
||||
size: u16,
|
||||
color_sliders: [slider::State; 3],
|
||||
color: Color,
|
||||
},
|
||||
Radio {
|
||||
selection: Option<Language>,
|
||||
},
|
||||
Toggler {
|
||||
can_continue: bool,
|
||||
},
|
||||
Image {
|
||||
width: u16,
|
||||
slider: slider::State,
|
||||
},
|
||||
Scrollable,
|
||||
TextInput {
|
||||
value: String,
|
||||
is_secure: bool,
|
||||
state: text_input::State,
|
||||
},
|
||||
Debugger,
|
||||
End,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum StepMessage {
|
||||
SliderChanged(u8),
|
||||
LayoutChanged(Layout),
|
||||
SpacingChanged(u16),
|
||||
TextSizeChanged(u16),
|
||||
TextColorChanged(Color),
|
||||
LanguageSelected(Language),
|
||||
ImageWidthChanged(u16),
|
||||
InputChanged(String),
|
||||
ToggleSecureInput(bool),
|
||||
DebugToggled(bool),
|
||||
TogglerChanged(bool),
|
||||
}
|
||||
|
||||
impl<'a> Step {
|
||||
fn update(&mut self, msg: StepMessage, debug: &mut bool) {
|
||||
match msg {
|
||||
StepMessage::DebugToggled(value) => {
|
||||
if let Step::Debugger = self {
|
||||
*debug = value;
|
||||
}
|
||||
}
|
||||
StepMessage::LanguageSelected(language) => {
|
||||
if let Step::Radio { selection } = self {
|
||||
*selection = Some(language);
|
||||
}
|
||||
}
|
||||
StepMessage::SliderChanged(new_value) => {
|
||||
if let Step::Slider { value, .. } = self {
|
||||
*value = new_value;
|
||||
}
|
||||
}
|
||||
StepMessage::TextSizeChanged(new_size) => {
|
||||
if let Step::Text { size, .. } = self {
|
||||
*size = new_size;
|
||||
}
|
||||
}
|
||||
StepMessage::TextColorChanged(new_color) => {
|
||||
if let Step::Text { color, .. } = self {
|
||||
*color = new_color;
|
||||
}
|
||||
}
|
||||
StepMessage::LayoutChanged(new_layout) => {
|
||||
if let Step::RowsAndColumns { layout, .. } = self {
|
||||
*layout = new_layout;
|
||||
}
|
||||
}
|
||||
StepMessage::SpacingChanged(new_spacing) => {
|
||||
if let Step::RowsAndColumns { spacing, .. } = self {
|
||||
*spacing = new_spacing;
|
||||
}
|
||||
}
|
||||
StepMessage::ImageWidthChanged(new_width) => {
|
||||
if let Step::Image { width, .. } = self {
|
||||
*width = new_width;
|
||||
}
|
||||
}
|
||||
StepMessage::InputChanged(new_value) => {
|
||||
if let Step::TextInput { value, .. } = self {
|
||||
*value = new_value;
|
||||
}
|
||||
}
|
||||
StepMessage::ToggleSecureInput(toggle) => {
|
||||
if let Step::TextInput { is_secure, .. } = self {
|
||||
*is_secure = toggle;
|
||||
}
|
||||
}
|
||||
StepMessage::TogglerChanged(value) => {
|
||||
if let Step::Toggler { can_continue, .. } = self {
|
||||
*can_continue = value;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
fn title(&self) -> &str {
|
||||
match self {
|
||||
Step::Welcome => "Welcome",
|
||||
Step::Radio { .. } => "Radio button",
|
||||
Step::Toggler { .. } => "Toggler",
|
||||
Step::Slider { .. } => "Slider",
|
||||
Step::Text { .. } => "Text",
|
||||
Step::Image { .. } => "Image",
|
||||
Step::RowsAndColumns { .. } => "Rows and columns",
|
||||
Step::Scrollable => "Scrollable",
|
||||
Step::TextInput { .. } => "Text input",
|
||||
Step::Debugger => "Debugger",
|
||||
Step::End => "End",
|
||||
}
|
||||
}
|
||||
|
||||
fn can_continue(&self) -> bool {
|
||||
match self {
|
||||
Step::Welcome => true,
|
||||
Step::Radio { selection } => *selection == Some(Language::Rust),
|
||||
Step::Toggler { can_continue } => *can_continue,
|
||||
Step::Slider { .. } => true,
|
||||
Step::Text { .. } => true,
|
||||
Step::Image { .. } => true,
|
||||
Step::RowsAndColumns { .. } => true,
|
||||
Step::Scrollable => true,
|
||||
Step::TextInput { value, .. } => !value.is_empty(),
|
||||
Step::Debugger => true,
|
||||
Step::End => false,
|
||||
}
|
||||
}
|
||||
|
||||
fn view(&mut self, debug: bool) -> Element<StepMessage> {
|
||||
match self {
|
||||
Step::Welcome => Self::welcome(),
|
||||
Step::Radio { selection } => Self::radio(*selection),
|
||||
Step::Toggler { can_continue } => Self::toggler(*can_continue),
|
||||
Step::Slider { state, value } => Self::slider(state, *value),
|
||||
Step::Text {
|
||||
size_slider,
|
||||
size,
|
||||
color_sliders,
|
||||
color,
|
||||
} => Self::text(size_slider, *size, color_sliders, *color),
|
||||
Step::Image { width, slider } => Self::image(*width, slider),
|
||||
Step::RowsAndColumns {
|
||||
layout,
|
||||
spacing_slider,
|
||||
spacing,
|
||||
} => Self::rows_and_columns(*layout, spacing_slider, *spacing),
|
||||
Step::Scrollable => Self::scrollable(),
|
||||
Step::TextInput {
|
||||
value,
|
||||
is_secure,
|
||||
state,
|
||||
} => Self::text_input(value, *is_secure, state),
|
||||
Step::Debugger => Self::debugger(debug),
|
||||
Step::End => Self::end(),
|
||||
}
|
||||
.into()
|
||||
}
|
||||
|
||||
fn container(title: &str) -> Column<'a, StepMessage> {
|
||||
Column::new().spacing(20).push(Text::new(title).size(50))
|
||||
}
|
||||
|
||||
fn welcome() -> Column<'a, StepMessage> {
|
||||
Self::container("Welcome!")
|
||||
.push(Text::new(
|
||||
"This is a simple tour meant to showcase a bunch of widgets \
|
||||
that can be easily implemented on top of Iced.",
|
||||
))
|
||||
.push(Text::new(
|
||||
"Iced is a cross-platform GUI library for Rust focused on \
|
||||
simplicity and type-safety. It is heavily inspired by Elm.",
|
||||
))
|
||||
.push(Text::new(
|
||||
"It was originally born as part of Coffee, an opinionated \
|
||||
2D game engine for Rust.",
|
||||
))
|
||||
.push(Text::new(
|
||||
"On native platforms, Iced provides by default a renderer \
|
||||
built on top of wgpu, a graphics library supporting Vulkan, \
|
||||
Metal, DX11, and DX12.",
|
||||
))
|
||||
.push(Text::new(
|
||||
"Additionally, this tour can also run on WebAssembly thanks \
|
||||
to dodrio, an experimental VDOM library for Rust.",
|
||||
))
|
||||
.push(Text::new(
|
||||
"You will need to interact with the UI in order to reach the \
|
||||
end!",
|
||||
))
|
||||
}
|
||||
|
||||
fn slider(
|
||||
state: &'a mut slider::State,
|
||||
value: u8,
|
||||
) -> Column<'a, StepMessage> {
|
||||
Self::container("Slider")
|
||||
.push(Text::new(
|
||||
"A slider allows you to smoothly select a value from a range \
|
||||
of values.",
|
||||
))
|
||||
.push(Text::new(
|
||||
"The following slider lets you choose an integer from \
|
||||
0 to 100:",
|
||||
))
|
||||
.push(Slider::new(
|
||||
state,
|
||||
0..=100,
|
||||
value,
|
||||
StepMessage::SliderChanged,
|
||||
))
|
||||
.push(
|
||||
Text::new(&value.to_string())
|
||||
.width(Length::Fill)
|
||||
.horizontal_alignment(HorizontalAlignment::Center),
|
||||
)
|
||||
}
|
||||
|
||||
fn rows_and_columns(
|
||||
layout: Layout,
|
||||
spacing_slider: &'a mut slider::State,
|
||||
spacing: u16,
|
||||
) -> Column<'a, StepMessage> {
|
||||
let row_radio = Radio::new(
|
||||
Layout::Row,
|
||||
"Row",
|
||||
Some(layout),
|
||||
StepMessage::LayoutChanged,
|
||||
);
|
||||
|
||||
let column_radio = Radio::new(
|
||||
Layout::Column,
|
||||
"Column",
|
||||
Some(layout),
|
||||
StepMessage::LayoutChanged,
|
||||
);
|
||||
|
||||
let layout_section: Element<_> = match layout {
|
||||
Layout::Row => Row::new()
|
||||
.spacing(spacing)
|
||||
.push(row_radio)
|
||||
.push(column_radio)
|
||||
.into(),
|
||||
Layout::Column => Column::new()
|
||||
.spacing(spacing)
|
||||
.push(row_radio)
|
||||
.push(column_radio)
|
||||
.into(),
|
||||
};
|
||||
|
||||
let spacing_section = Column::new()
|
||||
.spacing(10)
|
||||
.push(Slider::new(
|
||||
spacing_slider,
|
||||
0..=80,
|
||||
spacing,
|
||||
StepMessage::SpacingChanged,
|
||||
))
|
||||
.push(
|
||||
Text::new(&format!("{} px", spacing))
|
||||
.width(Length::Fill)
|
||||
.horizontal_alignment(HorizontalAlignment::Center),
|
||||
);
|
||||
|
||||
Self::container("Rows and columns")
|
||||
.spacing(spacing)
|
||||
.push(Text::new(
|
||||
"Iced uses a layout model based on flexbox to position UI \
|
||||
elements.",
|
||||
))
|
||||
.push(Text::new(
|
||||
"Rows and columns can be used to distribute content \
|
||||
horizontally or vertically, respectively.",
|
||||
))
|
||||
.push(layout_section)
|
||||
.push(Text::new(
|
||||
"You can also easily change the spacing between elements:",
|
||||
))
|
||||
.push(spacing_section)
|
||||
}
|
||||
|
||||
fn text(
|
||||
size_slider: &'a mut slider::State,
|
||||
size: u16,
|
||||
color_sliders: &'a mut [slider::State; 3],
|
||||
color: Color,
|
||||
) -> Column<'a, StepMessage> {
|
||||
let size_section = Column::new()
|
||||
.padding(20)
|
||||
.spacing(20)
|
||||
.push(Text::new("You can change its size:"))
|
||||
.push(
|
||||
Text::new(&format!("This text is {} pixels", size)).size(size),
|
||||
)
|
||||
.push(Slider::new(
|
||||
size_slider,
|
||||
10..=70,
|
||||
size,
|
||||
StepMessage::TextSizeChanged,
|
||||
));
|
||||
|
||||
let [red, green, blue] = color_sliders;
|
||||
|
||||
let color_sliders = Row::new()
|
||||
.spacing(10)
|
||||
.push(color_slider(red, color.r, move |r| Color { r, ..color }))
|
||||
.push(color_slider(green, color.g, move |g| Color { g, ..color }))
|
||||
.push(color_slider(blue, color.b, move |b| Color { b, ..color }));
|
||||
|
||||
let color_section = Column::new()
|
||||
.padding(20)
|
||||
.spacing(20)
|
||||
.push(Text::new("And its color:"))
|
||||
.push(Text::new(&format!("{:?}", color)).color(color))
|
||||
.push(color_sliders);
|
||||
|
||||
Self::container("Text")
|
||||
.push(Text::new(
|
||||
"Text is probably the most essential widget for your UI. \
|
||||
It will try to adapt to the dimensions of its container.",
|
||||
))
|
||||
.push(size_section)
|
||||
.push(color_section)
|
||||
}
|
||||
|
||||
fn radio(selection: Option<Language>) -> Column<'a, StepMessage> {
|
||||
let question = Column::new()
|
||||
.padding(20)
|
||||
.spacing(10)
|
||||
.push(Text::new("Iced is written in...").size(24))
|
||||
.push(Language::all().iter().cloned().fold(
|
||||
Column::new().padding(10).spacing(20),
|
||||
|choices, language| {
|
||||
choices.push(Radio::new(
|
||||
language,
|
||||
language,
|
||||
selection,
|
||||
StepMessage::LanguageSelected,
|
||||
))
|
||||
},
|
||||
));
|
||||
|
||||
Self::container("Radio button")
|
||||
.push(Text::new(
|
||||
"A radio button is normally used to represent a choice... \
|
||||
Surprise test!",
|
||||
))
|
||||
.push(question)
|
||||
.push(Text::new(
|
||||
"Iced works very well with iterators! The list above is \
|
||||
basically created by folding a column over the different \
|
||||
choices, creating a radio button for each one of them!",
|
||||
))
|
||||
}
|
||||
|
||||
fn toggler(can_continue: bool) -> Column<'a, StepMessage> {
|
||||
Self::container("Toggler")
|
||||
.push(Text::new(
|
||||
"A toggler is mostly used to enable or disable something.",
|
||||
))
|
||||
.push(
|
||||
Container::new(Toggler::new(
|
||||
can_continue,
|
||||
String::from("Toggle me to continue..."),
|
||||
StepMessage::TogglerChanged,
|
||||
))
|
||||
.padding([0, 40]),
|
||||
)
|
||||
}
|
||||
|
||||
fn image(
|
||||
width: u16,
|
||||
slider: &'a mut slider::State,
|
||||
) -> Column<'a, StepMessage> {
|
||||
Self::container("Image")
|
||||
.push(Text::new("An image that tries to keep its aspect ratio."))
|
||||
.push(ferris(width))
|
||||
.push(Slider::new(
|
||||
slider,
|
||||
100..=500,
|
||||
width,
|
||||
StepMessage::ImageWidthChanged,
|
||||
))
|
||||
.push(
|
||||
Text::new(&format!("Width: {} px", width.to_string()))
|
||||
.width(Length::Fill)
|
||||
.horizontal_alignment(HorizontalAlignment::Center),
|
||||
)
|
||||
}
|
||||
|
||||
fn scrollable() -> Column<'a, StepMessage> {
|
||||
Self::container("Scrollable")
|
||||
.push(Text::new(
|
||||
"Iced supports scrollable content. Try it out! Find the \
|
||||
button further below.",
|
||||
))
|
||||
.push(
|
||||
Text::new(
|
||||
"Tip: You can use the scrollbar to scroll down faster!",
|
||||
)
|
||||
.size(16),
|
||||
)
|
||||
.push(Column::new().height(Length::Units(4096)))
|
||||
.push(
|
||||
Text::new("You are halfway there!")
|
||||
.width(Length::Fill)
|
||||
.size(30)
|
||||
.horizontal_alignment(HorizontalAlignment::Center),
|
||||
)
|
||||
.push(Column::new().height(Length::Units(4096)))
|
||||
.push(ferris(300))
|
||||
.push(
|
||||
Text::new("You made it!")
|
||||
.width(Length::Fill)
|
||||
.size(50)
|
||||
.horizontal_alignment(HorizontalAlignment::Center),
|
||||
)
|
||||
}
|
||||
|
||||
fn text_input(
|
||||
value: &str,
|
||||
is_secure: bool,
|
||||
state: &'a mut text_input::State,
|
||||
) -> Column<'a, StepMessage> {
|
||||
let text_input = TextInput::new(
|
||||
state,
|
||||
"Type something to continue...",
|
||||
value,
|
||||
StepMessage::InputChanged,
|
||||
)
|
||||
.padding(10)
|
||||
.size(30);
|
||||
Self::container("Text input")
|
||||
.push(Text::new(
|
||||
"Use a text input to ask for different kinds of information.",
|
||||
))
|
||||
.push(if is_secure {
|
||||
text_input.password()
|
||||
} else {
|
||||
text_input
|
||||
})
|
||||
.push(Checkbox::new(
|
||||
is_secure,
|
||||
"Enable password mode",
|
||||
StepMessage::ToggleSecureInput,
|
||||
))
|
||||
.push(Text::new(
|
||||
"A text input produces a message every time it changes. It is \
|
||||
very easy to keep track of its contents:",
|
||||
))
|
||||
.push(
|
||||
Text::new(if value.is_empty() {
|
||||
"You have not typed anything yet..."
|
||||
} else {
|
||||
value
|
||||
})
|
||||
.width(Length::Fill)
|
||||
.horizontal_alignment(HorizontalAlignment::Center),
|
||||
)
|
||||
}
|
||||
|
||||
fn debugger(debug: bool) -> Column<'a, StepMessage> {
|
||||
Self::container("Debugger")
|
||||
.push(Text::new(
|
||||
"You can ask Iced to visually explain the layouting of the \
|
||||
different elements comprising your UI!",
|
||||
))
|
||||
.push(Text::new(
|
||||
"Give it a shot! Check the following checkbox to be able to \
|
||||
see element boundaries.",
|
||||
))
|
||||
.push(if cfg!(target_arch = "wasm32") {
|
||||
Element::new(
|
||||
Text::new("Not available on web yet!")
|
||||
.color([0.7, 0.7, 0.7])
|
||||
.horizontal_alignment(HorizontalAlignment::Center),
|
||||
)
|
||||
} else {
|
||||
Element::new(Checkbox::new(
|
||||
debug,
|
||||
"Explain layout",
|
||||
StepMessage::DebugToggled,
|
||||
))
|
||||
})
|
||||
.push(Text::new("Feel free to go back and take a look."))
|
||||
}
|
||||
|
||||
fn end() -> Column<'a, StepMessage> {
|
||||
Self::container("You reached the end!")
|
||||
.push(Text::new(
|
||||
"This tour will be updated as more features are added.",
|
||||
))
|
||||
.push(Text::new("Make sure to keep an eye on it!"))
|
||||
}
|
||||
}
|
||||
|
||||
fn ferris<'a>(_width: u16) -> Container<'a, StepMessage> {
|
||||
Container::new(
|
||||
// This should go away once we unify resource loading on native
|
||||
// platforms
|
||||
Text::new("sorry ferris, you can't come out in glow."),
|
||||
)
|
||||
.width(Length::Fill)
|
||||
.center_x()
|
||||
}
|
||||
|
||||
fn button<'a, Message: Clone>(
|
||||
state: &'a mut button::State,
|
||||
label: &str,
|
||||
) -> Button<'a, Message> {
|
||||
Button::new(
|
||||
state,
|
||||
Text::new(label).horizontal_alignment(HorizontalAlignment::Center),
|
||||
)
|
||||
.padding(12)
|
||||
.min_width(100)
|
||||
}
|
||||
|
||||
fn color_slider(
|
||||
state: &mut slider::State,
|
||||
component: f32,
|
||||
update: impl Fn(f32) -> Color + 'static,
|
||||
) -> Slider<f64, StepMessage> {
|
||||
Slider::new(state, 0.0..=1.0, f64::from(component), move |c| {
|
||||
StepMessage::TextColorChanged(update(c as f32))
|
||||
})
|
||||
.step(0.01)
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum Language {
|
||||
Rust,
|
||||
Elm,
|
||||
Ruby,
|
||||
Haskell,
|
||||
C,
|
||||
Other,
|
||||
}
|
||||
|
||||
impl Language {
|
||||
fn all() -> [Language; 6] {
|
||||
[
|
||||
Language::C,
|
||||
Language::Elm,
|
||||
Language::Ruby,
|
||||
Language::Haskell,
|
||||
Language::Rust,
|
||||
Language::Other,
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Language> for String {
|
||||
fn from(language: Language) -> String {
|
||||
String::from(match language {
|
||||
Language::Rust => "Rust",
|
||||
Language::Elm => "Elm",
|
||||
Language::Ruby => "Ruby",
|
||||
Language::Haskell => "Haskell",
|
||||
Language::C => "C",
|
||||
Language::Other => "Other",
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum Layout {
|
||||
Row,
|
||||
Column,
|
||||
}
|
||||
|
||||
mod style {
|
||||
use iced::{button, Background, Color, Vector};
|
||||
|
||||
pub enum Button {
|
||||
Primary,
|
||||
Secondary,
|
||||
}
|
||||
|
||||
impl button::StyleSheet for Button {
|
||||
fn active(&self) -> button::Style {
|
||||
button::Style {
|
||||
background: Some(Background::Color(match self {
|
||||
Button::Primary => Color::from_rgb(0.11, 0.42, 0.87),
|
||||
Button::Secondary => Color::from_rgb(0.5, 0.5, 0.5),
|
||||
})),
|
||||
border_radius: 12.0,
|
||||
shadow_offset: Vector::new(1.0, 1.0),
|
||||
text_color: Color::from_rgb8(0xEE, 0xEE, 0xEE),
|
||||
..button::Style::default()
|
||||
}
|
||||
}
|
||||
|
||||
fn hovered(&self) -> button::Style {
|
||||
button::Style {
|
||||
text_color: Color::WHITE,
|
||||
shadow_offset: Vector::new(1.0, 2.0),
|
||||
..self.active()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,10 +0,0 @@
|
||||
[package]
|
||||
name = "url_handler"
|
||||
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,73 +0,0 @@
|
||||
use iced::{
|
||||
executor, Application, Clipboard, Command, Container, Element, Length,
|
||||
Settings, Subscription, Text,
|
||||
};
|
||||
use iced_native::{
|
||||
event::{MacOS, PlatformSpecific},
|
||||
Event,
|
||||
};
|
||||
|
||||
pub fn main() -> iced::Result {
|
||||
App::run(Settings::default())
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
struct App {
|
||||
url: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
enum Message {
|
||||
EventOccurred(iced_native::Event),
|
||||
}
|
||||
|
||||
impl Application for App {
|
||||
type Executor = executor::Default;
|
||||
type Message = Message;
|
||||
type Flags = ();
|
||||
|
||||
fn new(_flags: ()) -> (App, Command<Message>) {
|
||||
(App::default(), Command::none())
|
||||
}
|
||||
|
||||
fn title(&self) -> String {
|
||||
String::from("Url - Iced")
|
||||
}
|
||||
|
||||
fn update(
|
||||
&mut self,
|
||||
message: Message,
|
||||
_clipboard: &mut Clipboard,
|
||||
) -> Command<Message> {
|
||||
match message {
|
||||
Message::EventOccurred(event) => {
|
||||
if let Event::PlatformSpecific(PlatformSpecific::MacOS(
|
||||
MacOS::ReceivedUrl(url),
|
||||
)) = event
|
||||
{
|
||||
self.url = Some(url);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Command::none()
|
||||
}
|
||||
|
||||
fn subscription(&self) -> Subscription<Message> {
|
||||
iced_native::subscription::events().map(Message::EventOccurred)
|
||||
}
|
||||
|
||||
fn view(&mut self) -> Element<Message> {
|
||||
let content = match &self.url {
|
||||
Some(url) => Text::new(format!("{}", url)),
|
||||
None => Text::new("No URL received yet!"),
|
||||
};
|
||||
|
||||
Container::new(content.size(48))
|
||||
.width(Length::Fill)
|
||||
.height(Length::Fill)
|
||||
.center_x()
|
||||
.center_y()
|
||||
.into()
|
||||
}
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "iced_futures"
|
||||
version = "0.3.0"
|
||||
version = "0.1.2"
|
||||
authors = ["Héctor Ramón Jiménez <hector0193@gmail.com>"]
|
||||
edition = "2018"
|
||||
description = "Commands, subscriptions, and runtimes for Iced"
|
||||
@ -19,27 +19,16 @@ log = "0.4"
|
||||
[dependencies.futures]
|
||||
version = "0.3"
|
||||
|
||||
[target.'cfg(not(target_arch = "wasm32"))'.dependencies.tokio_old]
|
||||
package = "tokio"
|
||||
[target.'cfg(not(target_arch = "wasm32"))'.dependencies.tokio]
|
||||
version = "0.2"
|
||||
optional = true
|
||||
features = ["rt-core", "rt-threaded", "time", "stream"]
|
||||
|
||||
[target.'cfg(not(target_arch = "wasm32"))'.dependencies.tokio]
|
||||
package = "tokio"
|
||||
version = "1.0"
|
||||
optional = true
|
||||
features = ["rt", "rt-multi-thread", "time"]
|
||||
|
||||
[target.'cfg(not(target_arch = "wasm32"))'.dependencies.async-std]
|
||||
version = "1.0"
|
||||
optional = true
|
||||
features = ["unstable"]
|
||||
|
||||
[target.'cfg(not(target_arch = "wasm32"))'.dependencies.smol]
|
||||
version = "1.2"
|
||||
optional = true
|
||||
|
||||
[target.'cfg(target_arch = "wasm32")'.dependencies]
|
||||
wasm-bindgen-futures = "0.4"
|
||||
|
||||
|
@ -5,6 +5,9 @@ use futures::future::{Future, FutureExt};
|
||||
///
|
||||
/// You should be able to turn a future easily into a [`Command`], either by
|
||||
/// using the `From` trait or [`Command::perform`].
|
||||
///
|
||||
/// [`Command`]: struct.Command.html
|
||||
/// [`Command::perform`]: #method.perform
|
||||
pub struct Command<T> {
|
||||
futures: Vec<BoxFuture<T>>,
|
||||
}
|
||||
@ -13,6 +16,8 @@ impl<T> Command<T> {
|
||||
/// Creates an empty [`Command`].
|
||||
///
|
||||
/// In other words, a [`Command`] that does nothing.
|
||||
///
|
||||
/// [`Command`]: struct.Command.html
|
||||
pub fn none() -> Self {
|
||||
Self {
|
||||
futures: Vec::new(),
|
||||
@ -20,6 +25,8 @@ impl<T> Command<T> {
|
||||
}
|
||||
|
||||
/// Creates a [`Command`] that performs the action of the given future.
|
||||
///
|
||||
/// [`Command`]: struct.Command.html
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub fn perform<A>(
|
||||
future: impl Future<Output = T> + 'static + Send,
|
||||
@ -31,6 +38,8 @@ impl<T> Command<T> {
|
||||
}
|
||||
|
||||
/// Creates a [`Command`] that performs the action of the given future.
|
||||
///
|
||||
/// [`Command`]: struct.Command.html
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
pub fn perform<A>(
|
||||
future: impl Future<Output = T> + 'static,
|
||||
@ -42,6 +51,8 @@ impl<T> Command<T> {
|
||||
}
|
||||
|
||||
/// Applies a transformation to the result of a [`Command`].
|
||||
///
|
||||
/// [`Command`]: struct.Command.html
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub fn map<A>(
|
||||
mut self,
|
||||
@ -67,6 +78,8 @@ impl<T> Command<T> {
|
||||
}
|
||||
|
||||
/// Applies a transformation to the result of a [`Command`].
|
||||
///
|
||||
/// [`Command`]: struct.Command.html
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
pub fn map<A>(mut self, f: impl Fn(T) -> A + 'static) -> Command<A>
|
||||
where
|
||||
@ -92,6 +105,8 @@ impl<T> Command<T> {
|
||||
/// commands.
|
||||
///
|
||||
/// Once this command is run, all the commands will be executed at once.
|
||||
///
|
||||
/// [`Command`]: struct.Command.html
|
||||
pub fn batch(commands: impl IntoIterator<Item = Command<T>>) -> Self {
|
||||
Self {
|
||||
futures: commands
|
||||
@ -102,6 +117,8 @@ impl<T> Command<T> {
|
||||
}
|
||||
|
||||
/// Converts a [`Command`] into its underlying list of futures.
|
||||
///
|
||||
/// [`Command`]: struct.Command.html
|
||||
pub fn futures(self) -> Vec<BoxFuture<T>> {
|
||||
self.futures
|
||||
}
|
||||
|
@ -7,15 +7,9 @@ mod thread_pool;
|
||||
#[cfg(all(not(target_arch = "wasm32"), feature = "tokio"))]
|
||||
mod tokio;
|
||||
|
||||
#[cfg(all(not(target_arch = "wasm32"), feature = "tokio_old"))]
|
||||
mod tokio_old;
|
||||
|
||||
#[cfg(all(not(target_arch = "wasm32"), feature = "async-std"))]
|
||||
mod async_std;
|
||||
|
||||
#[cfg(all(not(target_arch = "wasm32"), feature = "smol"))]
|
||||
mod smol;
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
mod wasm_bindgen;
|
||||
|
||||
@ -27,15 +21,9 @@ pub use thread_pool::ThreadPool;
|
||||
#[cfg(all(not(target_arch = "wasm32"), feature = "tokio"))]
|
||||
pub use self::tokio::Tokio;
|
||||
|
||||
#[cfg(all(not(target_arch = "wasm32"), feature = "tokio_old"))]
|
||||
pub use self::tokio_old::TokioOld;
|
||||
|
||||
#[cfg(all(not(target_arch = "wasm32"), feature = "async-std"))]
|
||||
pub use self::async_std::AsyncStd;
|
||||
|
||||
#[cfg(all(not(target_arch = "wasm32"), feature = "smol"))]
|
||||
pub use self::smol::Smol;
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
pub use wasm_bindgen::WasmBindgen;
|
||||
|
||||
@ -44,15 +32,21 @@ 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
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
fn spawn(&self, future: impl Future<Output = ()> + Send + 'static);
|
||||
|
||||
/// Spawns a local future in the [`Executor`].
|
||||
///
|
||||
/// [`Executor`]: trait.Executor.html
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
fn spawn(&self, future: impl Future<Output = ()> + 'static);
|
||||
|
||||
@ -62,6 +56,8 @@ pub trait Executor: Sized {
|
||||
/// 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.
|
||||
///
|
||||
/// [`Executor`]: trait.Executor.html
|
||||
fn enter<R>(&self, f: impl FnOnce() -> R) -> R {
|
||||
f()
|
||||
}
|
||||
|
@ -1,18 +0,0 @@
|
||||
use crate::Executor;
|
||||
|
||||
use futures::Future;
|
||||
|
||||
/// A `smol` runtime.
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "smol")))]
|
||||
#[derive(Debug)]
|
||||
pub struct Smol;
|
||||
|
||||
impl Executor for Smol {
|
||||
fn new() -> Result<Self, futures::io::Error> {
|
||||
Ok(Self)
|
||||
}
|
||||
|
||||
fn spawn(&self, future: impl Future<Output = ()> + Send + 'static) {
|
||||
smol::spawn(future).detach();
|
||||
}
|
||||
}
|
@ -16,7 +16,6 @@ impl Executor for Tokio {
|
||||
}
|
||||
|
||||
fn enter<R>(&self, f: impl FnOnce() -> R) -> R {
|
||||
let _guard = tokio::runtime::Runtime::enter(self);
|
||||
f()
|
||||
tokio::runtime::Runtime::enter(self, f)
|
||||
}
|
||||
}
|
||||
|
@ -1,21 +0,0 @@
|
||||
use crate::Executor;
|
||||
|
||||
use futures::Future;
|
||||
|
||||
/// An old `tokio` runtime.
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "tokio_old")))]
|
||||
pub type TokioOld = tokio_old::runtime::Runtime;
|
||||
|
||||
impl Executor for TokioOld {
|
||||
fn new() -> Result<Self, futures::io::Error> {
|
||||
tokio_old::runtime::Runtime::new()
|
||||
}
|
||||
|
||||
fn spawn(&self, future: impl Future<Output = ()> + Send + 'static) {
|
||||
let _ = tokio_old::runtime::Runtime::spawn(self, future);
|
||||
}
|
||||
|
||||
fn enter<R>(&self, f: impl FnOnce() -> R) -> R {
|
||||
tokio_old::runtime::Runtime::enter(self, f)
|
||||
}
|
||||
}
|
@ -1,6 +1,4 @@
|
||||
//! Asynchronous tasks for GUI programming, inspired by Elm.
|
||||
//!
|
||||
//! 
|
||||
#![deny(missing_docs)]
|
||||
#![deny(missing_debug_implementations)]
|
||||
#![deny(unused_results)]
|
||||
@ -17,22 +15,10 @@ pub mod executor;
|
||||
pub mod subscription;
|
||||
|
||||
#[cfg(all(
|
||||
any(
|
||||
feature = "tokio",
|
||||
feature = "tokio_old",
|
||||
feature = "async-std",
|
||||
feature = "smol"
|
||||
),
|
||||
any(feature = "tokio", feature = "async-std"),
|
||||
not(target_arch = "wasm32")
|
||||
))]
|
||||
#[cfg_attr(
|
||||
docsrs,
|
||||
doc(cfg(any(
|
||||
feature = "tokio",
|
||||
feature = "async-std",
|
||||
feature = "smol"
|
||||
)))
|
||||
)]
|
||||
#[cfg_attr(docsrs, doc(cfg(any(feature = "tokio", feature = "async-std"))))]
|
||||
pub mod time;
|
||||
|
||||
pub use command::Command;
|
||||
|
@ -8,6 +8,11 @@ use std::marker::PhantomData;
|
||||
///
|
||||
/// 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,
|
||||
@ -31,6 +36,8 @@ where
|
||||
/// 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,
|
||||
@ -43,6 +50,10 @@ where
|
||||
/// Runs the given closure inside the [`Executor`] of the [`Runtime`].
|
||||
///
|
||||
/// See [`Executor::enter`] to learn more.
|
||||
///
|
||||
/// [`Executor`]: executor/trait.Executor.html
|
||||
/// [`Runtime`]: struct.Runtime.html
|
||||
/// [`Executor::enter`]: executor/trait.Executor.html#method.enter
|
||||
pub fn enter<R>(&self, f: impl FnOnce() -> R) -> R {
|
||||
self.executor.enter(f)
|
||||
}
|
||||
@ -51,6 +62,9 @@ where
|
||||
///
|
||||
/// 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};
|
||||
|
||||
@ -74,7 +88,9 @@ where
|
||||
/// It will spawn new streams or close old ones as necessary! See
|
||||
/// [`Tracker::update`] to learn more about this!
|
||||
///
|
||||
/// [`Tracker::update`]: subscription::Tracker::update
|
||||
/// [`Subscription`]: 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>,
|
||||
@ -99,7 +115,9 @@ where
|
||||
///
|
||||
/// See [`Tracker::broadcast`] to learn more.
|
||||
///
|
||||
/// [`Tracker::broadcast`]: subscription::Tracker::broadcast
|
||||
/// [`Runtime`]: struct.Runtime.html
|
||||
/// [`Tracker::broadcast`]:
|
||||
/// subscription/struct.Tracker.html#method.broadcast
|
||||
pub fn broadcast(&mut self, event: Event) {
|
||||
self.subscriptions.broadcast(event);
|
||||
}
|
||||
|
@ -19,7 +19,8 @@ use crate::BoxStream;
|
||||
/// This type is normally aliased by runtimes with a specific `Event` and/or
|
||||
/// `Hasher`.
|
||||
///
|
||||
/// [`Command`]: crate::Command
|
||||
/// [`Command`]: ../struct.Command.html
|
||||
/// [`Subscription`]: struct.Subscription.html
|
||||
pub struct Subscription<Hasher, Event, Output> {
|
||||
recipes: Vec<Box<dyn Recipe<Hasher, Event, Output = Output>>>,
|
||||
}
|
||||
@ -29,6 +30,8 @@ where
|
||||
H: std::hash::Hasher,
|
||||
{
|
||||
/// Returns an empty [`Subscription`] that will not produce any output.
|
||||
///
|
||||
/// [`Subscription`]: struct.Subscription.html
|
||||
pub fn none() -> Self {
|
||||
Self {
|
||||
recipes: Vec::new(),
|
||||
@ -36,6 +39,9 @@ where
|
||||
}
|
||||
|
||||
/// Creates a [`Subscription`] from a [`Recipe`] describing it.
|
||||
///
|
||||
/// [`Subscription`]: struct.Subscription.html
|
||||
/// [`Recipe`]: trait.Recipe.html
|
||||
pub fn from_recipe(
|
||||
recipe: impl Recipe<H, E, Output = O> + 'static,
|
||||
) -> Self {
|
||||
@ -46,6 +52,8 @@ where
|
||||
|
||||
/// Batches all the provided subscriptions and returns the resulting
|
||||
/// [`Subscription`].
|
||||
///
|
||||
/// [`Subscription`]: struct.Subscription.html
|
||||
pub fn batch(
|
||||
subscriptions: impl IntoIterator<Item = Subscription<H, E, O>>,
|
||||
) -> Self {
|
||||
@ -58,6 +66,8 @@ where
|
||||
}
|
||||
|
||||
/// Returns the different recipes of the [`Subscription`].
|
||||
///
|
||||
/// [`Subscription`]: struct.Subscription.html
|
||||
pub fn recipes(self) -> Vec<Box<dyn Recipe<H, E, Output = O>>> {
|
||||
self.recipes
|
||||
}
|
||||
@ -65,6 +75,12 @@ where
|
||||
/// Adds a value to the [`Subscription`] context.
|
||||
///
|
||||
/// The value will be part of the identity of a [`Subscription`].
|
||||
///
|
||||
/// This is necessary if you want to use multiple instances of the same
|
||||
/// [`Subscription`] to produce different kinds of messages based on some
|
||||
/// external data.
|
||||
///
|
||||
/// [`Subscription`]: struct.Subscription.html
|
||||
pub fn with<T>(mut self, value: T) -> Subscription<H, E, (T, O)>
|
||||
where
|
||||
H: 'static,
|
||||
@ -85,19 +101,26 @@ where
|
||||
}
|
||||
|
||||
/// Transforms the [`Subscription`] output with the given function.
|
||||
pub fn map<A>(mut self, f: fn(O) -> A) -> Subscription<H, E, A>
|
||||
///
|
||||
/// [`Subscription`]: struct.Subscription.html
|
||||
pub fn map<A>(
|
||||
mut self,
|
||||
f: impl Fn(O) -> A + Send + Sync + 'static,
|
||||
) -> Subscription<H, E, A>
|
||||
where
|
||||
H: 'static,
|
||||
E: 'static,
|
||||
O: 'static,
|
||||
A: 'static,
|
||||
{
|
||||
let function = std::sync::Arc::new(f);
|
||||
|
||||
Subscription {
|
||||
recipes: self
|
||||
.recipes
|
||||
.drain(..)
|
||||
.map(|recipe| {
|
||||
Box::new(Map::new(recipe, f))
|
||||
Box::new(Map::new(recipe, function.clone()))
|
||||
as Box<dyn Recipe<H, E, Output = A>>
|
||||
})
|
||||
.collect(),
|
||||
@ -117,6 +140,9 @@ impl<I, O, H> std::fmt::Debug for Subscription<I, O, H> {
|
||||
/// by runtimes to run and identify subscriptions. You can use it to create your
|
||||
/// own!
|
||||
///
|
||||
/// [`Subscription`]: struct.Subscription.html
|
||||
/// [`Recipe`]: trait.Recipe.html
|
||||
///
|
||||
/// # Examples
|
||||
/// The repository has a couple of [examples] that use a custom [`Recipe`]:
|
||||
///
|
||||
@ -125,17 +151,23 @@ impl<I, O, H> std::fmt::Debug for Subscription<I, O, H> {
|
||||
/// - [`stopwatch`], a watch with start/stop and reset buttons showcasing how
|
||||
/// to listen to time.
|
||||
///
|
||||
/// [examples]: https://github.com/hecrj/iced/tree/0.3/examples
|
||||
/// [`download_progress`]: https://github.com/hecrj/iced/tree/0.3/examples/download_progress
|
||||
/// [`stopwatch`]: https://github.com/hecrj/iced/tree/0.3/examples/stopwatch
|
||||
/// [examples]: https://github.com/hecrj/iced/tree/0.1/examples
|
||||
/// [`download_progress`]: https://github.com/hecrj/iced/tree/0.1/examples/download_progress
|
||||
/// [`stopwatch`]: https://github.com/hecrj/iced/tree/0.1/examples/stopwatch
|
||||
pub trait Recipe<Hasher: std::hash::Hasher, Event> {
|
||||
/// The events that will be produced by a [`Subscription`] with this
|
||||
/// [`Recipe`].
|
||||
///
|
||||
/// [`Subscription`]: struct.Subscription.html
|
||||
/// [`Recipe`]: trait.Recipe.html
|
||||
type Output;
|
||||
|
||||
/// Hashes the [`Recipe`].
|
||||
///
|
||||
/// This is used by runtimes to uniquely identify a [`Subscription`].
|
||||
///
|
||||
/// [`Subscription`]: struct.Subscription.html
|
||||
/// [`Recipe`]: trait.Recipe.html
|
||||
fn hash(&self, state: &mut Hasher);
|
||||
|
||||
/// Executes the [`Recipe`] and produces the stream of events of its
|
||||
@ -143,6 +175,9 @@ pub trait Recipe<Hasher: std::hash::Hasher, Event> {
|
||||
///
|
||||
/// 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: BoxStream<Event>,
|
||||
@ -151,13 +186,13 @@ pub trait Recipe<Hasher: std::hash::Hasher, Event> {
|
||||
|
||||
struct Map<Hasher, Event, A, B> {
|
||||
recipe: Box<dyn Recipe<Hasher, Event, Output = A>>,
|
||||
mapper: fn(A) -> B,
|
||||
mapper: std::sync::Arc<dyn Fn(A) -> B + Send + Sync>,
|
||||
}
|
||||
|
||||
impl<H, E, A, B> Map<H, E, A, B> {
|
||||
fn new(
|
||||
recipe: Box<dyn Recipe<H, E, Output = A>>,
|
||||
mapper: fn(A) -> B,
|
||||
mapper: std::sync::Arc<dyn Fn(A) -> B + Send + Sync + 'static>,
|
||||
) -> Self {
|
||||
Map { recipe, mapper }
|
||||
}
|
||||
@ -174,8 +209,8 @@ where
|
||||
fn hash(&self, state: &mut H) {
|
||||
use std::hash::Hash;
|
||||
|
||||
std::any::TypeId::of::<B>().hash(state);
|
||||
self.recipe.hash(state);
|
||||
self.mapper.hash(state);
|
||||
}
|
||||
|
||||
fn stream(self: Box<Self>, input: BoxStream<E>) -> BoxStream<Self::Output> {
|
||||
|
@ -26,6 +26,8 @@ where
|
||||
Event: 'static + Send + Clone,
|
||||
{
|
||||
/// Creates a new empty [`Tracker`].
|
||||
///
|
||||
/// [`Tracker`]: struct.Tracker.html
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
subscriptions: HashMap::new(),
|
||||
@ -50,7 +52,9 @@ where
|
||||
/// It returns a list of futures that need to be spawned to materialize
|
||||
/// the [`Tracker`] changes.
|
||||
///
|
||||
/// [`Recipe`]: crate::subscription::Recipe
|
||||
/// [`Tracker`]: struct.Tracker.html
|
||||
/// [`Subscription`]: struct.Subscription.html
|
||||
/// [`Recipe`]: trait.Recipe.html
|
||||
pub fn update<Message, Receiver>(
|
||||
&mut self,
|
||||
subscription: Subscription<Hasher, Event, Message>,
|
||||
@ -128,14 +132,14 @@ where
|
||||
/// This method publishes the given event to all the subscription streams
|
||||
/// currently open.
|
||||
///
|
||||
/// [`Recipe::stream`]: crate::subscription::Recipe::stream
|
||||
/// [`Recipe::stream`]: trait.Recipe.html#tymethod.stream
|
||||
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::warn!(
|
||||
log::error!(
|
||||
"Error sending event to subscription: {:?}",
|
||||
error
|
||||
);
|
||||
|
@ -5,6 +5,8 @@ use crate::subscription::{self, Subscription};
|
||||
///
|
||||
/// The first message is produced after a `duration`, and then continues to
|
||||
/// produce more messages every `duration` after that.
|
||||
///
|
||||
/// [`Subscription`]: ../subscription/struct.Subscription.html
|
||||
pub fn every<H: std::hash::Hasher, E>(
|
||||
duration: std::time::Duration,
|
||||
) -> Subscription<H, E, std::time::Instant> {
|
||||
@ -13,33 +15,6 @@ pub fn every<H: std::hash::Hasher, E>(
|
||||
|
||||
struct Every(std::time::Duration);
|
||||
|
||||
#[cfg(all(
|
||||
not(any(feature = "tokio_old", feature = "tokio", feature = "async-std")),
|
||||
feature = "smol"
|
||||
))]
|
||||
impl<H, E> subscription::Recipe<H, E> for Every
|
||||
where
|
||||
H: std::hash::Hasher,
|
||||
{
|
||||
type Output = std::time::Instant;
|
||||
|
||||
fn hash(&self, state: &mut H) {
|
||||
use std::hash::Hash;
|
||||
|
||||
std::any::TypeId::of::<Self>().hash(state);
|
||||
self.0.hash(state);
|
||||
}
|
||||
|
||||
fn stream(
|
||||
self: Box<Self>,
|
||||
_input: futures::stream::BoxStream<'static, E>,
|
||||
) -> futures::stream::BoxStream<'static, Self::Output> {
|
||||
use futures::stream::StreamExt;
|
||||
|
||||
smol::Timer::interval(self.0).boxed()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "async-std")]
|
||||
impl<H, E> subscription::Recipe<H, E> for Every
|
||||
where
|
||||
@ -66,10 +41,7 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(all(
|
||||
any(feature = "tokio", feature = "tokio_old"),
|
||||
not(any(feature = "async-std", feature = "smol"))
|
||||
))]
|
||||
#[cfg(all(feature = "tokio", not(feature = "async-std")))]
|
||||
impl<H, E> subscription::Recipe<H, E> for Every
|
||||
where
|
||||
H: std::hash::Hasher,
|
||||
@ -89,25 +61,10 @@ where
|
||||
) -> futures::stream::BoxStream<'static, Self::Output> {
|
||||
use futures::stream::StreamExt;
|
||||
|
||||
#[cfg(feature = "tokio_old")]
|
||||
use tokio_old as tokio;
|
||||
|
||||
let start = tokio::time::Instant::now() + self.0;
|
||||
|
||||
let stream = {
|
||||
#[cfg(feature = "tokio")]
|
||||
{
|
||||
futures::stream::unfold(
|
||||
tokio::time::interval_at(start, self.0),
|
||||
|mut interval| async move {
|
||||
Some((interval.tick().await, interval))
|
||||
},
|
||||
)
|
||||
}
|
||||
#[cfg(feature = "tokio_old")]
|
||||
tokio::time::interval_at(start, self.0)
|
||||
};
|
||||
|
||||
stream.map(tokio::time::Instant::into_std).boxed()
|
||||
tokio::time::interval_at(start, self.0)
|
||||
.map(|_| std::time::Instant::now())
|
||||
.boxed()
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "iced_glow"
|
||||
version = "0.2.0"
|
||||
version = "0.1.0"
|
||||
authors = ["Héctor Ramón Jiménez <hector0193@gmail.com>"]
|
||||
edition = "2018"
|
||||
description = "A glow renderer for iced"
|
||||
@ -9,26 +9,25 @@ repository = "https://github.com/hecrj/iced"
|
||||
|
||||
[features]
|
||||
canvas = ["iced_graphics/canvas"]
|
||||
qr_code = ["iced_graphics/qr_code"]
|
||||
default_system_font = ["iced_graphics/font-source"]
|
||||
# Not supported yet!
|
||||
image = []
|
||||
svg = []
|
||||
|
||||
[dependencies]
|
||||
glow = "0.6"
|
||||
glow_glyph = { version = "0.4", git = "https://bics.ga/reivilibre/glow_glyph_compat.git", commit = "13ce059bdfe1b3745fa222d73fb2faaf0ec0311d" }
|
||||
glow = "0.5"
|
||||
glow_glyph = "0.3"
|
||||
glyph_brush = "0.7"
|
||||
euclid = "0.22"
|
||||
bytemuck = "1.4"
|
||||
euclid = "0.20"
|
||||
bytemuck = "1.2"
|
||||
log = "0.4"
|
||||
|
||||
[dependencies.iced_native]
|
||||
version = "0.4"
|
||||
version = "0.2"
|
||||
path = "../native"
|
||||
|
||||
[dependencies.iced_graphics]
|
||||
version = "0.2"
|
||||
version = "0.1"
|
||||
path = "../graphics"
|
||||
features = ["font-fallback", "font-icons", "opengl"]
|
||||
|
||||
|
@ -23,6 +23,8 @@ pub struct Backend {
|
||||
|
||||
impl Backend {
|
||||
/// Creates a new [`Backend`].
|
||||
///
|
||||
/// [`Backend`]: struct.Backend.html
|
||||
pub fn new(gl: &glow::Context, settings: Settings) -> Self {
|
||||
let text_pipeline = text::Pipeline::new(gl, settings.default_font);
|
||||
let quad_pipeline = quad::Pipeline::new(gl);
|
||||
|