summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.github/FUNDING.yml1
-rw-r--r--.github/workflows/build.yml7
-rw-r--r--.github/workflows/test.yml10
-rw-r--r--.gitignore2
-rw-r--r--CHANGELOG.md115
-rw-r--r--Cargo.toml35
-rw-r--r--ECOSYSTEM.md95
-rw-r--r--README.md32
-rw-r--r--ROADMAP.md21
-rw-r--r--core/Cargo.toml3
-rw-r--r--core/README.md6
-rw-r--r--core/src/color.rs25
-rw-r--r--core/src/keyboard.rs6
-rw-r--r--core/src/keyboard/event.rs8
-rw-r--r--core/src/keyboard/hotkey.rs18
-rw-r--r--core/src/keyboard/key_code.rs37
-rw-r--r--core/src/keyboard/modifiers.rs79
-rw-r--r--core/src/keyboard/modifiers_state.rs30
-rw-r--r--core/src/length.rs2
-rw-r--r--core/src/lib.rs8
-rw-r--r--core/src/menu.rs145
-rw-r--r--core/src/mouse/event.rs9
-rw-r--r--core/src/padding.rs107
-rw-r--r--core/src/point.rs12
-rw-r--r--core/src/rectangle.rs32
-rw-r--r--core/src/size.rs38
-rw-r--r--core/src/vector.rs21
-rw-r--r--docs/graphs/core.dot13
-rw-r--r--docs/graphs/core.pngbin13172 -> 0 bytes
-rw-r--r--docs/graphs/ecosystem.dot56
-rw-r--r--docs/graphs/ecosystem.pngbin44705 -> 127113 bytes
-rw-r--r--docs/graphs/foundations.pngbin0 -> 18626 bytes
-rwxr-xr-xdocs/graphs/generate.sh6
-rw-r--r--docs/graphs/iced.dot46
-rw-r--r--docs/graphs/iced.pngbin26302 -> 100093 bytes
-rw-r--r--docs/graphs/native.dot41
-rw-r--r--docs/graphs/native.pngbin24980 -> 59288 bytes
-rw-r--r--docs/graphs/web.dot12
-rw-r--r--docs/graphs/web.pngbin11717 -> 0 bytes
-rw-r--r--docs/graphs/wgpu.dot31
-rw-r--r--docs/graphs/wgpu.pngbin16570 -> 0 bytes
-rw-r--r--docs/graphs/winit.dot31
-rw-r--r--docs/graphs/winit.pngbin15892 -> 0 bytes
-rw-r--r--examples/README.md5
-rw-r--r--examples/bezier_tool/src/main.rs75
-rw-r--r--examples/clock/src/main.rs10
-rw-r--r--examples/color_palette/src/main.rs2
-rw-r--r--examples/custom_widget/src/main.rs39
-rw-r--r--examples/download_progress/Cargo.toml4
-rw-r--r--examples/download_progress/README.md2
-rw-r--r--examples/download_progress/src/download.rs46
-rw-r--r--examples/download_progress/src/main.rs205
-rw-r--r--examples/events/src/main.rs53
-rw-r--r--examples/game_of_life/Cargo.toml2
-rw-r--r--examples/game_of_life/README.md4
-rw-r--r--examples/game_of_life/src/main.rs132
-rw-r--r--examples/game_of_life/src/preset.rs13
-rw-r--r--examples/game_of_life/src/style.rs19
-rw-r--r--examples/geometry/src/main.rs5
-rw-r--r--examples/integration/Cargo.toml2
-rw-r--r--examples/integration/src/controls.rs11
-rw-r--r--examples/integration/src/main.rs145
-rw-r--r--examples/integration/src/scene.rs47
-rw-r--r--examples/menu/Cargo.toml10
-rw-r--r--examples/menu/src/main.rs117
-rw-r--r--examples/pane_grid/Cargo.toml3
-rw-r--r--examples/pane_grid/src/main.rs246
-rw-r--r--examples/pick_list/src/main.rs9
-rw-r--r--examples/pokedex/Cargo.toml2
-rw-r--r--examples/pokedex/src/main.rs26
-rw-r--r--examples/qr_code/Cargo.toml9
-rw-r--r--examples/qr_code/README.md18
-rw-r--r--examples/qr_code/src/main.rs81
-rw-r--r--examples/scrollable/Cargo.toml9
-rw-r--r--examples/scrollable/README.md15
-rw-r--r--examples/scrollable/screenshot.pngbin0 -> 148253 bytes
-rw-r--r--examples/scrollable/src/main.rs255
-rw-r--r--examples/scrollable/src/style.rs190
-rw-r--r--examples/solar_system/Cargo.toml2
-rw-r--r--examples/solar_system/src/main.rs18
-rw-r--r--examples/stopwatch/Cargo.toml2
-rw-r--r--examples/stopwatch/src/main.rs12
-rw-r--r--examples/styling/src/main.rs111
-rw-r--r--examples/todos/Cargo.toml2
-rw-r--r--examples/todos/src/main.rs24
-rw-r--r--examples/tooltip/Cargo.toml9
-rw-r--r--examples/tooltip/README.md14
-rw-r--r--examples/tooltip/src/main.rs138
-rw-r--r--examples/tour/Cargo.toml2
-rw-r--r--examples/tour/src/main.rs36
-rw-r--r--examples/url_handler/Cargo.toml10
-rw-r--r--examples/url_handler/src/main.rs73
-rw-r--r--futures/Cargo.toml15
-rw-r--r--futures/src/command.rs17
-rw-r--r--futures/src/executor.rs20
-rw-r--r--futures/src/executor/smol.rs18
-rw-r--r--futures/src/executor/tokio.rs3
-rw-r--r--futures/src/executor/tokio_old.rs21
-rw-r--r--futures/src/lib.rs18
-rw-r--r--futures/src/runtime.rs22
-rw-r--r--futures/src/subscription.rs53
-rw-r--r--futures/src/subscription/tracker.rs10
-rw-r--r--futures/src/time.rs55
-rw-r--r--glow/Cargo.toml15
-rw-r--r--glow/src/backend.rs9
-rw-r--r--glow/src/lib.rs2
-rw-r--r--glow/src/program.rs4
-rw-r--r--glow/src/settings.rs22
-rw-r--r--glow/src/shader/quad.vert5
-rw-r--r--glow/src/text.rs8
-rw-r--r--glow/src/widget.rs14
-rw-r--r--glow/src/widget/button.rs3
-rw-r--r--glow/src/widget/canvas.rs3
-rw-r--r--glow/src/widget/pane_grid.rs13
-rw-r--r--glow/src/widget/progress_bar.rs2
-rw-r--r--glow/src/widget/qr_code.rs2
-rw-r--r--glow/src/widget/slider.rs3
-rw-r--r--glow/src/widget/text_input.rs3
-rw-r--r--glow/src/widget/toggler.rs9
-rw-r--r--glow/src/widget/tooltip.rs6
-rw-r--r--glutin/Cargo.toml14
-rw-r--r--glutin/README.md22
-rw-r--r--glutin/src/application.rs429
-rw-r--r--glutin/src/lib.rs4
-rw-r--r--graphics/Cargo.toml29
-rw-r--r--graphics/src/antialiasing.rs2
-rw-r--r--graphics/src/backend.rs8
-rw-r--r--graphics/src/font.rs4
-rw-r--r--graphics/src/font/source.rs4
-rw-r--r--graphics/src/layer.rs92
-rw-r--r--graphics/src/lib.rs2
-rw-r--r--graphics/src/overlay/menu.rs31
-rw-r--r--graphics/src/primitive.rs4
-rw-r--r--graphics/src/renderer.rs16
-rw-r--r--graphics/src/triangle.rs11
-rw-r--r--graphics/src/viewport.rs18
-rw-r--r--graphics/src/widget.rs14
-rw-r--r--graphics/src/widget/button.rs12
-rw-r--r--graphics/src/widget/canvas.rs37
-rw-r--r--graphics/src/widget/canvas/cache.rs10
-rw-r--r--graphics/src/widget/canvas/cursor.rs8
-rw-r--r--graphics/src/widget/canvas/event.rs5
-rw-r--r--graphics/src/widget/canvas/frame.rs39
-rw-r--r--graphics/src/widget/canvas/geometry.rs8
-rw-r--r--graphics/src/widget/canvas/path.rs11
-rw-r--r--graphics/src/widget/canvas/path/arc.rs2
-rw-r--r--graphics/src/widget/canvas/path/builder.rs29
-rw-r--r--graphics/src/widget/canvas/program.rs25
-rw-r--r--graphics/src/widget/canvas/stroke.rs10
-rw-r--r--graphics/src/widget/column.rs12
-rw-r--r--graphics/src/widget/container.rs12
-rw-r--r--graphics/src/widget/image.rs5
-rw-r--r--graphics/src/widget/image/viewer.rs55
-rw-r--r--graphics/src/widget/pane_grid.rs134
-rw-r--r--graphics/src/widget/pick_list.rs21
-rw-r--r--graphics/src/widget/progress_bar.rs15
-rw-r--r--graphics/src/widget/qr_code.rs305
-rw-r--r--graphics/src/widget/radio.rs6
-rw-r--r--graphics/src/widget/row.rs12
-rw-r--r--graphics/src/widget/rule.rs4
-rw-r--r--graphics/src/widget/scrollable.rs50
-rw-r--r--graphics/src/widget/slider.rs25
-rw-r--r--graphics/src/widget/text_input.rs11
-rw-r--r--graphics/src/widget/toggler.rs99
-rw-r--r--graphics/src/widget/tooltip.rs168
-rw-r--r--graphics/src/window.rs2
-rw-r--r--graphics/src/window/compositor.rs44
-rw-r--r--graphics/src/window/gl_compositor.rs27
-rw-r--r--native/Cargo.toml6
-rw-r--r--native/README.md6
-rw-r--r--native/src/clipboard.rs23
-rw-r--r--native/src/debug/basic.rs4
-rw-r--r--native/src/element.rs70
-rw-r--r--native/src/event.rs63
-rw-r--r--native/src/layout.rs20
-rw-r--r--native/src/layout/debugger.rs10
-rw-r--r--native/src/layout/flex.rs14
-rw-r--r--native/src/layout/limits.rs42
-rw-r--r--native/src/layout/node.rs17
-rw-r--r--native/src/lib.rs15
-rw-r--r--native/src/mouse/click.rs5
-rw-r--r--native/src/overlay.rs26
-rw-r--r--native/src/overlay/element.rs42
-rw-r--r--native/src/overlay/menu.rs116
-rw-r--r--native/src/program.rs22
-rw-r--r--native/src/program/state.rs36
-rw-r--r--native/src/renderer.rs16
-rw-r--r--native/src/renderer/null.rs47
-rw-r--r--native/src/runtime.rs16
-rw-r--r--native/src/subscription.rs49
-rw-r--r--native/src/subscription/events.rs28
-rw-r--r--native/src/touch.rs23
-rw-r--r--native/src/user_interface.rs144
-rw-r--r--native/src/widget.rs58
-rw-r--r--native/src/widget/button.rs143
-rw-r--r--native/src/widget/checkbox.rs59
-rw-r--r--native/src/widget/column.rs76
-rw-r--r--native/src/widget/container.rs66
-rw-r--r--native/src/widget/image.rs34
-rw-r--r--native/src/widget/image/viewer.rs413
-rw-r--r--native/src/widget/pane_grid.rs451
-rw-r--r--native/src/widget/pane_grid/configuration.rs12
-rw-r--r--native/src/widget/pane_grid/content.rs85
-rw-r--r--native/src/widget/pane_grid/node.rs36
-rw-r--r--native/src/widget/pane_grid/pane.rs4
-rw-r--r--native/src/widget/pane_grid/split.rs4
-rw-r--r--native/src/widget/pane_grid/state.rs193
-rw-r--r--native/src/widget/pane_grid/title_bar.rs238
-rw-r--r--native/src/widget/pick_list.rs185
-rw-r--r--native/src/widget/progress_bar.rs16
-rw-r--r--native/src/widget/radio.rs76
-rw-r--r--native/src/widget/row.rs81
-rw-r--r--native/src/widget/rule.rs11
-rw-r--r--native/src/widget/scrollable.rs373
-rw-r--r--native/src/widget/slider.rs96
-rw-r--r--native/src/widget/space.rs11
-rw-r--r--native/src/widget/svg.rs35
-rw-r--r--native/src/widget/text.rs37
-rw-r--r--native/src/widget/text_input.rs534
-rw-r--r--native/src/widget/text_input/cursor.rs30
-rw-r--r--native/src/widget/text_input/editor.rs4
-rw-r--r--native/src/widget/text_input/value.rs34
-rw-r--r--native/src/widget/toggler.rs277
-rw-r--r--native/src/widget/tooltip.rs210
-rw-r--r--native/src/window/event.rs22
-rw-r--r--rustfmt.toml1
-rw-r--r--src/application.rs132
-rw-r--r--src/error.rs13
-rw-r--r--src/executor.rs31
-rw-r--r--src/keyboard.rs2
-rw-r--r--src/lib.rs26
-rw-r--r--src/result.rs2
-rw-r--r--src/sandbox.rs66
-rw-r--r--src/settings.rs46
-rw-r--r--src/time.rs2
-rw-r--r--src/widget.rs22
-rw-r--r--src/window.rs2
-rw-r--r--src/window/icon.rs18
-rw-r--r--src/window/mode.rs3
-rw-r--r--src/window/position.rs33
-rw-r--r--src/window/settings.rs14
-rw-r--r--style/Cargo.toml7
-rw-r--r--style/src/button.rs14
-rw-r--r--style/src/checkbox.rs10
-rw-r--r--style/src/container.rs12
-rw-r--r--style/src/lib.rs4
-rw-r--r--style/src/menu.rs4
-rw-r--r--style/src/pane_grid.rs51
-rw-r--r--style/src/pick_list.rs10
-rw-r--r--style/src/progress_bar.rs6
-rw-r--r--style/src/radio.rs6
-rw-r--r--style/src/rule.rs22
-rw-r--r--style/src/scrollable.rs16
-rw-r--r--style/src/slider.rs10
-rw-r--r--style/src/text_input.rs12
-rw-r--r--style/src/toggler.rs57
-rw-r--r--web/Cargo.toml8
-rw-r--r--web/README.md4
-rw-r--r--web/src/bus.rs6
-rw-r--r--web/src/clipboard.rs21
-rw-r--r--web/src/css.rs96
-rw-r--r--web/src/element.rs9
-rw-r--r--web/src/lib.rs50
-rw-r--r--web/src/subscription.rs3
-rw-r--r--web/src/widget.rs9
-rw-r--r--web/src/widget/button.rs76
-rw-r--r--web/src/widget/checkbox.rs9
-rw-r--r--web/src/widget/column.rs40
-rw-r--r--web/src/widget/container.rs39
-rw-r--r--web/src/widget/image.rs18
-rw-r--r--web/src/widget/progress_bar.rs8
-rw-r--r--web/src/widget/radio.rs9
-rw-r--r--web/src/widget/row.rs41
-rw-r--r--web/src/widget/scrollable.rs34
-rw-r--r--web/src/widget/slider.rs20
-rw-r--r--web/src/widget/space.rs6
-rw-r--r--web/src/widget/text.rs20
-rw-r--r--web/src/widget/text_input.rs58
-rw-r--r--web/src/widget/toggler.rs171
-rw-r--r--wgpu/Cargo.toml44
-rw-r--r--wgpu/README.md6
-rw-r--r--wgpu/src/backend.rs38
-rw-r--r--wgpu/src/image.rs222
-rw-r--r--wgpu/src/image/atlas.rs24
-rw-r--r--wgpu/src/image/atlas/entry.rs2
-rw-r--r--wgpu/src/image/raster.rs12
-rw-r--r--wgpu/src/image/vector.rs42
-rw-r--r--wgpu/src/lib.rs4
-rw-r--r--wgpu/src/quad.rs190
-rw-r--r--wgpu/src/settings.rs64
-rw-r--r--wgpu/src/shader/blit.frag12
-rw-r--r--wgpu/src/shader/blit.frag.spvbin684 -> 0 bytes
-rw-r--r--wgpu/src/shader/blit.vert26
-rw-r--r--wgpu/src/shader/blit.vert.spvbin1384 -> 0 bytes
-rw-r--r--wgpu/src/shader/blit.wgsl43
-rw-r--r--wgpu/src/shader/image.frag12
-rw-r--r--wgpu/src/shader/image.frag.spvbin684 -> 0 bytes
-rw-r--r--wgpu/src/shader/image.vert27
-rw-r--r--wgpu/src/shader/image.vert.spvbin2504 -> 0 bytes
-rw-r--r--wgpu/src/shader/image.wgsl47
-rw-r--r--wgpu/src/shader/quad.frag66
-rw-r--r--wgpu/src/shader/quad.frag.spvbin4212 -> 0 bytes
-rw-r--r--wgpu/src/shader/quad.vert42
-rw-r--r--wgpu/src/shader/quad.vert.spvbin3372 -> 0 bytes
-rw-r--r--wgpu/src/shader/quad.wgsl122
-rw-r--r--wgpu/src/shader/triangle.frag8
-rw-r--r--wgpu/src/shader/triangle.frag.spvbin372 -> 0 bytes
-rw-r--r--wgpu/src/shader/triangle.vert15
-rw-r--r--wgpu/src/shader/triangle.vert.spvbin1256 -> 0 bytes
-rw-r--r--wgpu/src/shader/triangle.wgsl31
-rw-r--r--wgpu/src/text.rs4
-rw-r--r--wgpu/src/triangle.rs153
-rw-r--r--wgpu/src/triangle/msaa.rs119
-rw-r--r--wgpu/src/widget.rs14
-rw-r--r--wgpu/src/widget/button.rs3
-rw-r--r--wgpu/src/widget/canvas.rs3
-rw-r--r--wgpu/src/widget/pane_grid.rs13
-rw-r--r--wgpu/src/widget/progress_bar.rs2
-rw-r--r--wgpu/src/widget/qr_code.rs2
-rw-r--r--wgpu/src/widget/slider.rs3
-rw-r--r--wgpu/src/widget/text_input.rs3
-rw-r--r--wgpu/src/widget/toggler.rs9
-rw-r--r--wgpu/src/widget/tooltip.rs6
-rw-r--r--wgpu/src/window/compositor.rs176
-rw-r--r--winit/Cargo.toml16
-rw-r--r--winit/README.md6
-rw-r--r--winit/src/application.rs601
-rw-r--r--winit/src/application/state.rs224
-rw-r--r--winit/src/clipboard.rs49
-rw-r--r--winit/src/conversion.rs442
-rw-r--r--winit/src/lib.rs7
-rw-r--r--winit/src/mode.rs3
-rw-r--r--winit/src/position.rs22
-rw-r--r--winit/src/proxy.rs2
-rw-r--r--winit/src/settings.rs61
-rw-r--r--winit/src/settings/windows.rs16
336 files changed, 9691 insertions, 5226 deletions
diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml
index 4a3aad7c..8a891884 100644
--- a/.github/FUNDING.yml
+++ b/.github/FUNDING.yml
@@ -1 +1,2 @@
+github: hecrj
ko_fi: hecrj_
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 3b0bf033..a00e2a22 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -11,6 +11,11 @@ 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
@@ -67,6 +72,8 @@ jobs:
env:
MACOSX_DEPLOYMENT_TARGET: 10.14
run: cargo build --verbose --release --package todos
+ - name: Open binary via double-click
+ run: chmod +x target/release/todos
- name: Archive todos binary
uses: actions/upload-artifact@v1
with:
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index 9e73d3d3..0450f13d 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -12,6 +12,12 @@ 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
@@ -29,5 +35,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 `pokedex` example
- run: cargo build --package pokedex --target wasm32-unknown-unknown
+ - name: Check compilation of `todos` example
+ run: cargo build --package todos --target wasm32-unknown-unknown
diff --git a/.gitignore b/.gitignore
index eee98b1e..3872ed2f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,4 +1,4 @@
-/target
+target/
pkg/
**/*.rs.bk
Cargo.lock
diff --git a/CHANGELOG.md b/CHANGELOG.md
index ee650337..3feaeba3 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5,10 +5,115 @@ 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
-- `"system_font"` feature gates reading system fonts. [#370]
+- 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/
-[#370]: https://github.com/hecrj/iced/pull/370
## [0.1.1] - 2020-04-15
### Added
@@ -102,7 +207,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/0.1/wgpu
+[`iced_wgpu`]: https://github.com/hecrj/iced/tree/master/wgpu
## [0.1.0-beta] - 2019-11-25
@@ -114,7 +219,9 @@ 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.1.1...HEAD
+[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
[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
diff --git a/Cargo.toml b/Cargo.toml
index d97707ab..5b1cfb0e 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,6 +1,6 @@
[package]
name = "iced"
-version = "0.1.1"
+version = "0.3.0"
authors = ["Héctor Ramón Jiménez <hector0193@gmail.com>"]
edition = "2018"
description = "A cross-platform GUI library inspired by Elm"
@@ -21,20 +21,28 @@ image = ["iced_wgpu/image"]
svg = ["iced_wgpu/svg"]
# Enables the `Canvas` widget
canvas = ["iced_wgpu/canvas"]
-# Enables using system fonts.
+# Enables the `QRCode` widget
+qr_code = ["iced_wgpu/qr_code"]
+# 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 using system fonts for `iced_glow`.
+# Enables the `QRCode` widget for `iced_glow`
+glow_qr_code = ["iced_glow/qr_code"]
+# 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"]
@@ -67,28 +75,33 @@ 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/tooltip",
+ "examples/url_handler",
+ "examples/menu",
]
[dependencies]
-iced_core = { version = "0.2", path = "core" }
-iced_futures = { version = "0.1", path = "futures" }
+iced_core = { version = "0.4", path = "core" }
+iced_futures = { version = "0.3", path = "futures" }
thiserror = "1.0"
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
-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}
+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}
[target.'cfg(target_arch = "wasm32")'.dependencies]
-iced_web = { version = "0.2", path = "web" }
+iced_web = { version = "0.4", path = "web" }
[package.metadata.docs.rs]
rustdoc-args = ["--cfg", "docsrs"]
-features = ["image", "svg", "canvas"]
+features = ["image", "svg", "canvas", "qr_code"]
diff --git a/ECOSYSTEM.md b/ECOSYSTEM.md
index 65815b96..82303130 100644
--- a/ECOSYSTEM.md
+++ b/ECOSYSTEM.md
@@ -1,10 +1,7 @@
# Ecosystem
-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
+This document describes the Iced ecosystem and explains how the different crates relate to each other.
+## Overview
Iced is meant to be used by 2 different types of users:
- __End-users__. They should be able to:
@@ -18,71 +15,81 @@ 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.
-![Ecosystem graph](docs/graphs/ecosystem.png)
+<p align="center">
+ <img alt="The Iced Ecosystem" src="docs/graphs/ecosystem.png" width="60%">
+</p>
-### [`iced_core`]
-[`iced_core`] holds basic reusable types of the public API. For instance, basic data types like `Point`, `Rectangle`, `Length`, etc.
+## 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:
-This crate is meant to be a starting point for an Iced runtime.
+- [`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.
-### [`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
+<p align="center">
+ <img alt="The foundations" src="docs/graphs/foundations.png" width="50%">
+</p>
-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.
+## The native target
+The native side of the ecosystem is split into two different groups: __renderers__ and __shells__.
-[`druid`]: https://github.com/xi-editor/druid
-[`raw-window-handle`]: https://github.com/rust-windowing/raw-window-handle
+<p align="center">
+ <img alt="The native target" src="docs/graphs/native.png" width="80%">
+</p>
-### [`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.
+### 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.
-The crate is currently a simple abstraction layer over [`dodrio`].
+Currently, there are two different official renderers:
-[`dodrio`]: https://github.com/fitzgen/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+.
-### [`iced_wgpu`]
-[`iced_wgpu`] is a [`wgpu`] renderer for [`iced_native`]. For now, it is the default renderer of Iced in native platforms.
+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.
-[`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].
+### Shells
+The widgets of a graphical user _interface_ are interactive. __Shells__ gather and process user interactions in an event loop.
-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.
+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].
-[`wgpu`]: https://github.com/gfx-rs/wgpu-rs
-[WebGPU API]: https://gpuweb.github.io/gpuweb/
-[`wgpu_glyph`]: https://github.com/hecrj/wgpu_glyph
+As of now, there are two official shells:
-### [`iced_winit`]
-[`iced_winit`] offers some convenient abstractions on top of [`iced_native`] to quickstart development when using [`winit`].
+- [`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].
-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.
+## The web target
+The Web platform provides all the abstractions necessary to draw widgets and gather users interactions.
-[`winit`]: https://github.com/rust-windowing/winit
+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.
-### [`iced`]
+## Iced
Finally, [`iced`] unifies everything into a simple abstraction to create cross-platform applications:
-- On native, it uses [`iced_winit`] and [`iced_wgpu`].
+- On native, it uses __[shells](#shells)__ and __[renderers](#renderers)__.
- On the web, it uses [`iced_web`].
-This is the crate meant to be used by __end-users__.
+<p align="center">
+ <img alt="Iced" src="docs/graphs/iced.png" width="80%">
+</p>
[`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/
diff --git a/README.md b/README.md
index 9dbd3ed4..4ad2fb70 100644
--- a/README.md
+++ b/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.1"
+iced = "0.3"
```
__Iced moves fast and the `master` branch can contain breaking changes!__ If
@@ -168,26 +168,19 @@ 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 in [this pull request].
+The core of the library was implemented during May 2019 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:
-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`].
-
-[![Iced ecosystem](docs/graphs/ecosystem.png)](https://github.com/hecrj/iced/blob/master/ECOSYSTEM.md)
+<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>
[this pull request]: https://github.com/hecrj/coffee/pull/35
[The first alpha version]: https://github.com/hecrj/iced/tree/0.1.0-alpha
@@ -195,15 +188,6 @@ Currently, Iced is a cross-platform GUI library built on top of smaller crates:
[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
diff --git a/ROADMAP.md b/ROADMAP.md
index c47c08ff..05aa9bda 100644
--- a/ROADMAP.md
+++ b/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 `iced_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 __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,6 +17,8 @@ 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
@@ -25,6 +27,8 @@ 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.
@@ -35,17 +39,6 @@ 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.
@@ -55,8 +48,8 @@ This is a necessary feature to render loading spinners, a blinking text cursor,
[#31]: https://github.com/hecrj/iced/issues/31
-### 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.
+### 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.
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.
diff --git a/core/Cargo.toml b/core/Cargo.toml
index b52bf315..54d927af 100644
--- a/core/Cargo.toml
+++ b/core/Cargo.toml
@@ -1,6 +1,6 @@
[package]
name = "iced_core"
-version = "0.2.1"
+version = "0.4.0"
authors = ["Héctor Ramón Jiménez <hector0193@gmail.com>"]
edition = "2018"
description = "The essential concepts of Iced"
@@ -8,6 +8,7 @@ license = "MIT"
repository = "https://github.com/hecrj/iced"
[dependencies]
+bitflags = "1.2"
[dependencies.palette]
version = "0.5.0"
diff --git a/core/README.md b/core/README.md
index 641612c0..86d631d2 100644
--- a/core/README.md
+++ b/core/README.md
@@ -8,7 +8,9 @@
This crate is meant to be a starting point for an Iced runtime.
-![iced_core](../docs/graphs/core.png)
+<p align="center">
+ <img alt="The foundations" src="../docs/graphs/foundations.png" width="50%">
+</p>
[documentation]: https://docs.rs/iced_core
@@ -16,7 +18,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.2"
+iced_core = "0.4"
```
__Iced moves fast and the `master` branch can contain breaking changes!__ If
diff --git a/core/src/color.rs b/core/src/color.rs
index a4c3d87c..c66ee97c 100644
--- a/core/src/color.rs
+++ b/core/src/color.rs
@@ -43,8 +43,6 @@ 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),
@@ -67,29 +65,21 @@ 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,
@@ -100,8 +90,6 @@ 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
@@ -122,8 +110,6 @@ 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;
@@ -131,8 +117,6 @@ 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)
}
@@ -152,8 +136,6 @@ 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)
@@ -162,8 +144,6 @@ 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)
@@ -172,8 +152,6 @@ 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)
@@ -182,9 +160,6 @@ 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)
diff --git a/core/src/keyboard.rs b/core/src/keyboard.rs
index b26bdb3d..cb64701a 100644
--- a/core/src/keyboard.rs
+++ b/core/src/keyboard.rs
@@ -1,8 +1,10 @@
//! Reuse basic keyboard types.
mod event;
+mod hotkey;
mod key_code;
-mod modifiers_state;
+mod modifiers;
pub use event::Event;
+pub use hotkey::Hotkey;
pub use key_code::KeyCode;
-pub use modifiers_state::ModifiersState;
+pub use modifiers::Modifiers;
diff --git a/core/src/keyboard/event.rs b/core/src/keyboard/event.rs
index d142c3bc..0564c171 100644
--- a/core/src/keyboard/event.rs
+++ b/core/src/keyboard/event.rs
@@ -1,4 +1,4 @@
-use super::{KeyCode, ModifiersState};
+use super::{KeyCode, Modifiers};
/// A keyboard event.
///
@@ -14,7 +14,7 @@ pub enum Event {
key_code: KeyCode,
/// The state of the modifier keys
- modifiers: ModifiersState,
+ modifiers: Modifiers,
},
/// A keyboard key was released.
@@ -23,12 +23,12 @@ pub enum Event {
key_code: KeyCode,
/// The state of the modifier keys
- modifiers: ModifiersState,
+ modifiers: Modifiers,
},
/// A unicode character was received.
CharacterReceived(char),
/// The keyboard modifiers have changed.
- ModifiersChanged(ModifiersState),
+ ModifiersChanged(Modifiers),
}
diff --git a/core/src/keyboard/hotkey.rs b/core/src/keyboard/hotkey.rs
new file mode 100644
index 00000000..310ef286
--- /dev/null
+++ b/core/src/keyboard/hotkey.rs
@@ -0,0 +1,18 @@
+use crate::keyboard::{KeyCode, Modifiers};
+
+/// Representation of a hotkey, consists on the combination of a [`KeyCode`] and [`Modifiers`].
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub struct Hotkey {
+ /// The key that represents this hotkey.
+ pub key: KeyCode,
+
+ /// The list of modifiers that represents this hotkey.
+ pub modifiers: Modifiers,
+}
+
+impl Hotkey {
+ /// Creates a new [`Hotkey`] with the given [`Modifiers`] and [`KeyCode`].
+ pub fn new(modifiers: Modifiers, key: KeyCode) -> Self {
+ Self { modifiers, key }
+ }
+}
diff --git a/core/src/keyboard/key_code.rs b/core/src/keyboard/key_code.rs
index 26020a57..74ead170 100644
--- a/core/src/keyboard/key_code.rs
+++ b/core/src/keyboard/key_code.rs
@@ -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,11 +103,14 @@ 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,
@@ -123,12 +126,20 @@ pub enum KeyCode {
Numpad7,
Numpad8,
Numpad9,
+ NumpadAdd,
+ NumpadDivide,
+ NumpadDecimal,
+ NumpadComma,
+ NumpadEnter,
+ NumpadEquals,
+ NumpadMultiply,
+ NumpadSubtract,
AbntC1,
AbntC2,
- Add,
Apostrophe,
Apps,
+ Asterisk,
At,
Ax,
Backslash,
@@ -137,8 +148,6 @@ pub enum KeyCode {
Colon,
Comma,
Convert,
- Decimal,
- Divide,
Equals,
Grave,
Kana,
@@ -152,19 +161,16 @@ pub enum KeyCode {
MediaSelect,
MediaStop,
Minus,
- Multiply,
Mute,
MyComputer,
- NavigateForward, // also called "Prior"
- NavigateBackward, // also called "Next"
+ NavigateForward, // also called "Next"
+ NavigateBackward, // also called "Prior"
NextTrack,
NoConvert,
- NumpadComma,
- NumpadEnter,
- NumpadEquals,
OEM102,
Period,
PlayPause,
+ Plus,
Power,
PrevTrack,
RAlt,
@@ -176,7 +182,6 @@ pub enum KeyCode {
Slash,
Sleep,
Stop,
- Subtract,
Sysrq,
Tab,
Underline,
diff --git a/core/src/keyboard/modifiers.rs b/core/src/keyboard/modifiers.rs
new file mode 100644
index 00000000..e61f145a
--- /dev/null
+++ b/core/src/keyboard/modifiers.rs
@@ -0,0 +1,79 @@
+use bitflags::bitflags;
+
+bitflags! {
+ /// The current state of the keyboard modifiers.
+ #[derive(Default)]
+ pub struct Modifiers: u32{
+ /// The "shift" key.
+ const SHIFT = 0b100 << 0;
+ // const LSHIFT = 0b010 << 0;
+ // const RSHIFT = 0b001 << 0;
+ //
+ /// The "control" key.
+ const CTRL = 0b100 << 3;
+ // const LCTRL = 0b010 << 3;
+ // const RCTRL = 0b001 << 3;
+ //
+ /// The "alt" key.
+ const ALT = 0b100 << 6;
+ // const LALT = 0b010 << 6;
+ // const RALT = 0b001 << 6;
+ //
+ /// The "windows" key on Windows, "command" key on Mac, and
+ /// "super" key on Linux.
+ const LOGO = 0b100 << 9;
+ // const LLOGO = 0b010 << 9;
+ // const RLOGO = 0b001 << 9;
+ }
+}
+
+impl Modifiers {
+ /// The "command" key.
+ ///
+ /// This is normally the main modifier to be used for hotkeys.
+ ///
+ /// On macOS, this is equivalent to `Self::LOGO`.
+ /// Ohterwise, this is equivalent to `Self::CTRL`.
+ pub const COMMAND: Self = if cfg!(target_os = "macos") {
+ Self::LOGO
+ } else {
+ Self::CTRL
+ };
+
+ /// Returns true if the [`SHIFT`] key is pressed in the [`Modifiers`].
+ pub fn shift(self) -> bool {
+ self.contains(Self::SHIFT)
+ }
+
+ /// Returns true if the [`CTRL`] key is pressed in the [`Modifiers`].
+ pub fn control(self) -> bool {
+ self.contains(Self::CTRL)
+ }
+
+ /// Returns true if the [`ALT`] key is pressed in the [`Modifiers`].
+ pub fn alt(self) -> bool {
+ self.contains(Self::ALT)
+ }
+
+ /// Returns true if the [`LOGO`] key is pressed in the [`Modifiers`].
+ pub fn logo(self) -> bool {
+ self.contains(Self::LOGO)
+ }
+
+ /// 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 command(self) -> bool {
+ #[cfg(target_os = "macos")]
+ let is_pressed = self.logo();
+
+ #[cfg(not(target_os = "macos"))]
+ let is_pressed = self.control();
+
+ is_pressed
+ }
+}
diff --git a/core/src/keyboard/modifiers_state.rs b/core/src/keyboard/modifiers_state.rs
deleted file mode 100644
index 4d24266f..00000000
--- a/core/src/keyboard/modifiers_state.rs
+++ /dev/null
@@ -1,30 +0,0 @@
-/// 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
- }
-}
diff --git a/core/src/length.rs b/core/src/length.rs
index 06d8cf0a..186411a5 100644
--- a/core/src/length.rs
+++ b/core/src/length.rs
@@ -26,8 +26,6 @@ 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,
diff --git a/core/src/lib.rs b/core/src/lib.rs
index 6b9e612e..c4288158 100644
--- a/core/src/lib.rs
+++ b/core/src/lib.rs
@@ -1,11 +1,11 @@
//! The core library of [Iced].
//!
-//! ![`iced_core` crate graph](https://github.com/hecrj/iced/blob/cae26cb7bc627f4a5b3bcf1cd023a0c552e8c65e/docs/graphs/core.png?raw=true)
-//!
//! 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`.
//!
+//! ![The foundations of the Iced ecosystem](https://github.com/hecrj/iced/blob/0525d76ff94e828b7b21634fa94a747022001c83/docs/graphs/foundations.png?raw=true)
+//!
//! [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
@@ -15,6 +15,7 @@
#![forbid(unsafe_code)]
#![forbid(rust_2018_idioms)]
pub mod keyboard;
+pub mod menu;
pub mod mouse;
mod align;
@@ -22,6 +23,7 @@ mod background;
mod color;
mod font;
mod length;
+mod padding;
mod point;
mod rectangle;
mod size;
@@ -32,6 +34,8 @@ pub use background::Background;
pub use color::Color;
pub use font::Font;
pub use length::Length;
+pub use menu::Menu;
+pub use padding::Padding;
pub use point::Point;
pub use rectangle::Rectangle;
pub use size::Size;
diff --git a/core/src/menu.rs b/core/src/menu.rs
new file mode 100644
index 00000000..8a679085
--- /dev/null
+++ b/core/src/menu.rs
@@ -0,0 +1,145 @@
+//! Build menus for your application.
+use crate::keyboard::Hotkey;
+
+/// Menu representation.
+///
+/// This can be used by `shell` implementations to create a menu.
+#[derive(Debug, Clone)]
+pub struct Menu<Message> {
+ entries: Vec<Entry<Message>>,
+}
+
+impl<Message> PartialEq for Menu<Message> {
+ fn eq(&self, other: &Self) -> bool {
+ self.entries == other.entries
+ }
+}
+
+impl<Message> Menu<Message> {
+ /// Creates an empty [`Menu`].
+ pub fn new() -> Self {
+ Self::with_entries(Vec::new())
+ }
+
+ /// Creates a new [`Menu`] with the given entries.
+ pub fn with_entries(entries: Vec<Entry<Message>>) -> Self {
+ Self { entries }
+ }
+
+ /// Returns a [`MenuEntry`] iterator.
+ pub fn iter(&self) -> impl Iterator<Item = &Entry<Message>> {
+ self.entries.iter()
+ }
+
+ /// Adds an [`Entry`] to the [`Menu`].
+ pub fn push(mut self, entry: Entry<Message>) -> Self {
+ self.entries.push(entry);
+ self
+ }
+
+ /// Maps the `Message` of the [`Menu`] using the provided function.
+ ///
+ /// This is useful to compose menus and split them into different
+ /// abstraction levels.
+ pub fn map<B>(self, f: impl Fn(Message) -> B + Copy) -> Menu<B> {
+ // TODO: Use a boxed trait to avoid reallocation of entries
+ Menu {
+ entries: self
+ .entries
+ .into_iter()
+ .map(|entry| entry.map(f))
+ .collect(),
+ }
+ }
+}
+
+/// Represents one of the possible entries used to build a [`Menu`].
+#[derive(Debug, Clone)]
+pub enum Entry<Message> {
+ /// Item for a [`Menu`]
+ Item {
+ /// The title of the item
+ title: String,
+ /// The [`Hotkey`] to activate the item, if any
+ hotkey: Option<Hotkey>,
+ /// The message generated when the item is activated
+ on_activation: Message,
+ },
+ /// Dropdown for a [`Menu`]
+ Dropdown {
+ /// Title of the dropdown
+ title: String,
+ /// The submenu of the dropdown
+ submenu: Menu<Message>,
+ },
+ /// Separator for a [`Menu`]
+ Separator,
+}
+
+impl<Message> Entry<Message> {
+ /// Creates an [`Entry::Item`].
+ pub fn item<S: Into<String>>(
+ title: S,
+ hotkey: impl Into<Option<Hotkey>>,
+ on_activation: Message,
+ ) -> Self {
+ let title = title.into();
+ let hotkey = hotkey.into();
+
+ Self::Item {
+ title,
+ hotkey,
+ on_activation,
+ }
+ }
+
+ /// Creates an [`Entry::Dropdown`].
+ pub fn dropdown<S: Into<String>>(title: S, submenu: Menu<Message>) -> Self {
+ let title = title.into();
+
+ Self::Dropdown { title, submenu }
+ }
+
+ fn map<B>(self, f: impl Fn(Message) -> B + Copy) -> Entry<B> {
+ match self {
+ Self::Item {
+ title,
+ hotkey,
+ on_activation,
+ } => Entry::Item {
+ title,
+ hotkey,
+ on_activation: f(on_activation),
+ },
+ Self::Dropdown { title, submenu } => Entry::Dropdown {
+ title,
+ submenu: submenu.map(f),
+ },
+ Self::Separator => Entry::Separator,
+ }
+ }
+}
+
+impl<Message> PartialEq for Entry<Message> {
+ fn eq(&self, other: &Self) -> bool {
+ match (self, other) {
+ (
+ Entry::Item { title, hotkey, .. },
+ Entry::Item {
+ title: other_title,
+ hotkey: other_hotkey,
+ ..
+ },
+ ) => title == other_title && hotkey == other_hotkey,
+ (
+ Entry::Dropdown { title, submenu },
+ Entry::Dropdown {
+ title: other_title,
+ submenu: other_submenu,
+ },
+ ) => title == other_title && submenu == other_submenu,
+ (Entry::Separator, Entry::Separator) => true,
+ _ => false,
+ }
+ }
+}
diff --git a/core/src/mouse/event.rs b/core/src/mouse/event.rs
index 2f07b207..321b8399 100644
--- a/core/src/mouse/event.rs
+++ b/core/src/mouse/event.rs
@@ -1,3 +1,5 @@
+use crate::Point;
+
use super::Button;
/// A mouse event.
@@ -16,11 +18,8 @@ pub enum Event {
/// The mouse cursor was moved
CursorMoved {
- /// The X coordinate of the mouse position
- x: f32,
-
- /// The Y coordinate of the mouse position
- y: f32,
+ /// The new position of the mouse cursor
+ position: Point,
},
/// A mouse button was pressed.
diff --git a/core/src/padding.rs b/core/src/padding.rs
new file mode 100644
index 00000000..22467d6b
--- /dev/null
+++ b/core/src/padding.rs
@@ -0,0 +1,107 @@
+/// 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],
+ }
+ }
+}
diff --git a/core/src/point.rs b/core/src/point.rs
index 3714aa2f..9bf7726b 100644
--- a/core/src/point.rs
+++ b/core/src/point.rs
@@ -12,20 +12,14 @@ 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;
@@ -46,6 +40,12 @@ 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;
diff --git a/core/src/rectangle.rs b/core/src/rectangle.rs
index ce80c661..4e082051 100644
--- a/core/src/rectangle.rs
+++ b/core/src/rectangle.rs
@@ -19,10 +19,6 @@ 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,
@@ -34,9 +30,6 @@ 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,
@@ -47,50 +40,33 @@ 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
@@ -99,8 +75,6 @@ impl Rectangle<f32> {
}
/// Computes the intersection with the given [`Rectangle`].
- ///
- /// [`Rectangle`]: struct.Rectangle.html
pub fn intersection(
&self,
other: &Rectangle<f32>,
@@ -127,14 +101,12 @@ 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.ceil() as u32,
- height: self.height.ceil() as u32,
+ width: self.width as u32,
+ height: self.height as u32,
}
}
}
diff --git a/core/src/size.rs b/core/src/size.rs
index aceb5311..6745c6c8 100644
--- a/core/src/size.rs
+++ b/core/src/size.rs
@@ -1,3 +1,4 @@
+use crate::{Padding, Vector};
use std::f32;
/// An amount of space in 2 dimensions.
@@ -11,8 +12,6 @@ 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 }
}
@@ -20,27 +19,19 @@ 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.
- ///
- /// [`Size`]: struct.Size.html
- pub fn pad(&self, padding: f32) -> Self {
+ pub fn pad(&self, padding: Padding) -> Self {
Size {
- width: self.width + padding * 2.0,
- height: self.height + padding * 2.0,
+ width: self.width + padding.horizontal() as f32,
+ height: self.height + padding.vertical() as f32,
}
}
}
@@ -56,3 +47,24 @@ 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)
+ }
+}
diff --git a/core/src/vector.rs b/core/src/vector.rs
index def3f8c0..92bb7648 100644
--- a/core/src/vector.rs
+++ b/core/src/vector.rs
@@ -2,20 +2,14 @@
#[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 }
}
@@ -65,3 +59,18 @@ 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]
+ }
+}
diff --git a/docs/graphs/core.dot b/docs/graphs/core.dot
deleted file mode 100644
index 93724927..00000000
--- a/docs/graphs/core.dot
+++ /dev/null
@@ -1,13 +0,0 @@
-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];
-}
diff --git a/docs/graphs/core.png b/docs/graphs/core.png
deleted file mode 100644
index 0b14ab6c..00000000
--- a/docs/graphs/core.png
+++ /dev/null
Binary files differ
diff --git a/docs/graphs/ecosystem.dot b/docs/graphs/ecosystem.dot
deleted file mode 100644
index 609cf726..00000000
--- a/docs/graphs/ecosystem.dot
+++ /dev/null
@@ -1,56 +0,0 @@
-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];
-}
diff --git a/docs/graphs/ecosystem.png b/docs/graphs/ecosystem.png
index 03fe1130..8b418c52 100644
--- a/docs/graphs/ecosystem.png
+++ b/docs/graphs/ecosystem.png
Binary files differ
diff --git a/docs/graphs/foundations.png b/docs/graphs/foundations.png
new file mode 100644
index 00000000..cc043c99
--- /dev/null
+++ b/docs/graphs/foundations.png
Binary files differ
diff --git a/docs/graphs/generate.sh b/docs/graphs/generate.sh
deleted file mode 100755
index 45073820..00000000
--- a/docs/graphs/generate.sh
+++ /dev/null
@@ -1,6 +0,0 @@
-#!/usr/bin/env bash
-
-for file in *.dot
-do
- dot -Tpng ${file} -o ${file%.*}.png
-done
diff --git a/docs/graphs/iced.dot b/docs/graphs/iced.dot
deleted file mode 100644
index 24dbb972..00000000
--- a/docs/graphs/iced.dot
+++ /dev/null
@@ -1,46 +0,0 @@
-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;
-}
diff --git a/docs/graphs/iced.png b/docs/graphs/iced.png
index 5d4a35bc..bf777e53 100644
--- a/docs/graphs/iced.png
+++ b/docs/graphs/iced.png
Binary files differ
diff --git a/docs/graphs/native.dot b/docs/graphs/native.dot
deleted file mode 100644
index b57736b5..00000000
--- a/docs/graphs/native.dot
+++ /dev/null
@@ -1,41 +0,0 @@
-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];
-}
diff --git a/docs/graphs/native.png b/docs/graphs/native.png
index 892e4fee..6a8759e0 100644
--- a/docs/graphs/native.png
+++ b/docs/graphs/native.png
Binary files differ
diff --git a/docs/graphs/web.dot b/docs/graphs/web.dot
deleted file mode 100644
index 853ca398..00000000
--- a/docs/graphs/web.dot
+++ /dev/null
@@ -1,12 +0,0 @@
-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];
-}
diff --git a/docs/graphs/web.png b/docs/graphs/web.png
deleted file mode 100644
index e6a1a5f6..00000000
--- a/docs/graphs/web.png
+++ /dev/null
Binary files differ
diff --git a/docs/graphs/wgpu.dot b/docs/graphs/wgpu.dot
deleted file mode 100644
index 410c2eeb..00000000
--- a/docs/graphs/wgpu.dot
+++ /dev/null
@@ -1,31 +0,0 @@
-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;
-}
diff --git a/docs/graphs/wgpu.png b/docs/graphs/wgpu.png
deleted file mode 100644
index 4831caba..00000000
--- a/docs/graphs/wgpu.png
+++ /dev/null
Binary files differ
diff --git a/docs/graphs/winit.dot b/docs/graphs/winit.dot
deleted file mode 100644
index 4ea5149a..00000000
--- a/docs/graphs/winit.dot
+++ /dev/null
@@ -1,31 +0,0 @@
-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;
-}
diff --git a/docs/graphs/winit.png b/docs/graphs/winit.png
deleted file mode 100644
index 1c028b29..00000000
--- a/docs/graphs/winit.png
+++ /dev/null
Binary files differ
diff --git a/examples/README.md b/examples/README.md
index 34a916a1..10c28cf5 100644
--- a/examples/README.md
+++ b/examples/README.md
@@ -103,6 +103,7 @@ 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.
@@ -117,7 +118,7 @@ cargo run --package <example>
[Ghostscript Tiger]: https://commons.wikimedia.org/wiki/File:Ghostscript_Tiger.svg
## [Coffee]
-Since [Iced was born in May], it has been powering the user interfaces in
+Since [Iced was born in May 2019], it has been powering the user interfaces in
[Coffee], an experimental 2D game engine.
@@ -127,6 +128,6 @@ Since [Iced was born in May], it has been powering the user interfaces in
</a>
</div>
-[Iced was born in May]: https://github.com/hecrj/coffee/pull/35
+[Iced was born in May 2019]: 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
diff --git a/examples/bezier_tool/src/main.rs b/examples/bezier_tool/src/main.rs
index cb5247aa..97832e01 100644
--- a/examples/bezier_tool/src/main.rs
+++ b/examples/bezier_tool/src/main.rs
@@ -69,7 +69,8 @@ impl Sandbox for Example {
mod bezier {
use iced::{
- canvas::{self, Canvas, Cursor, Event, Frame, Geometry, Path, Stroke},
+ canvas::event::{self, Event},
+ canvas::{self, Canvas, Cursor, Frame, Geometry, Path, Stroke},
mouse, Element, Length, Point, Rectangle,
};
@@ -109,41 +110,51 @@ mod bezier {
event: Event,
bounds: Rectangle,
cursor: Cursor,
- ) -> Option<Curve> {
- let cursor_position = cursor.position_in(&bounds)?;
+ ) -> (event::Status, Option<Curve>) {
+ let cursor_position =
+ if let Some(position) = cursor.position_in(&bounds) {
+ position
+ } else {
+ return (event::Status::Ignored, None);
+ };
match event {
- 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::Two { from, to }) => {
- self.state.pending = None;
-
- Some(Curve {
- from,
- to,
- control: cursor_position,
- })
+ 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,
+ });
+
+ 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;
+
+ Some(Curve {
+ from,
+ to,
+ control: cursor_position,
+ })
+ }
}
}
- }
- _ => None,
- },
- _ => None,
+ _ => None,
+ };
+
+ (event::Status::Captured, message)
+ }
+ _ => (event::Status::Ignored, None),
}
}
diff --git a/examples/clock/src/main.rs b/examples/clock/src/main.rs
index b317ac00..9bcc827b 100644
--- a/examples/clock/src/main.rs
+++ b/examples/clock/src/main.rs
@@ -1,7 +1,7 @@
use iced::{
canvas::{self, Cache, Canvas, Cursor, Geometry, LineCap, Path, Stroke},
- executor, time, Application, Color, Command, Container, Element, Length,
- Point, Rectangle, Settings, Subscription, Vector,
+ executor, time, Application, Clipboard, Color, Command, Container, Element,
+ Length, Point, Rectangle, Settings, Subscription, Vector,
};
pub fn main() -> iced::Result {
@@ -40,7 +40,11 @@ impl Application for Clock {
String::from("Clock - Iced")
}
- fn update(&mut self, message: Message) -> Command<Message> {
+ fn update(
+ &mut self,
+ message: Message,
+ _clipboard: &mut Clipboard,
+ ) -> Command<Message> {
match message {
Message::Tick(local_time) => {
let now = local_time;
diff --git a/examples/color_palette/src/main.rs b/examples/color_palette/src/main.rs
index fec5f48c..bb2c61cb 100644
--- a/examples/color_palette/src/main.rs
+++ b/examples/color_palette/src/main.rs
@@ -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>(
+ fn slider<C: Clone>(
state: &mut slider::State,
range: RangeInclusive<f64>,
component: f32,
diff --git a/examples/custom_widget/src/main.rs b/examples/custom_widget/src/main.rs
index 0eba1cd0..36f468c7 100644
--- a/examples/custom_widget/src/main.rs
+++ b/examples/custom_widget/src/main.rs
@@ -12,15 +12,15 @@ mod circle {
use iced_graphics::{Backend, Defaults, Primitive, Renderer};
use iced_native::{
layout, mouse, Background, Color, Element, Hasher, Layout, Length,
- Point, Size, Widget,
+ Point, Rectangle, Size, Widget,
};
pub struct Circle {
- radius: u16,
+ radius: f32,
}
impl Circle {
- pub fn new(radius: u16) -> Self {
+ pub fn new(radius: f32) -> Self {
Self { radius }
}
}
@@ -42,16 +42,13 @@ mod circle {
_renderer: &Renderer<B>,
_limits: &layout::Limits,
) -> layout::Node {
- layout::Node::new(Size::new(
- f32::from(self.radius) * 2.0,
- f32::from(self.radius) * 2.0,
- ))
+ layout::Node::new(Size::new(self.radius * 2.0, self.radius * 2.0))
}
fn hash_layout(&self, state: &mut Hasher) {
use std::hash::Hash;
- self.radius.hash(state);
+ self.radius.to_bits().hash(state);
}
fn draw(
@@ -60,13 +57,14 @@ mod circle {
_defaults: &Defaults,
layout: Layout<'_>,
_cursor_position: Point,
+ _viewport: &Rectangle,
) -> (Primitive, mouse::Interaction) {
(
Primitive::Quad {
bounds: layout.bounds(),
background: Background::Color(Color::BLACK),
border_radius: self.radius,
- border_width: 0,
+ border_width: 0.0,
border_color: Color::TRANSPARENT,
},
mouse::Interaction::default(),
@@ -95,7 +93,7 @@ pub fn main() -> iced::Result {
}
struct Example {
- radius: u16,
+ radius: f32,
slider: slider::State,
}
@@ -109,7 +107,7 @@ impl Sandbox for Example {
fn new() -> Self {
Example {
- radius: 50,
+ radius: 50.0,
slider: slider::State::new(),
}
}
@@ -121,7 +119,7 @@ impl Sandbox for Example {
fn update(&mut self, message: Message) {
match message {
Message::RadiusChanged(radius) => {
- self.radius = radius.round() as u16;
+ self.radius = radius;
}
}
}
@@ -133,13 +131,16 @@ impl Sandbox for Example {
.max_width(500)
.align_items(Align::Center)
.push(Circle::new(self.radius))
- .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,
- ));
+ .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),
+ );
Container::new(content)
.width(Length::Fill)
diff --git a/examples/download_progress/Cargo.toml b/examples/download_progress/Cargo.toml
index 34e6a132..d3c578b1 100644
--- a/examples/download_progress/Cargo.toml
+++ b/examples/download_progress/Cargo.toml
@@ -1,7 +1,7 @@
[package]
name = "download_progress"
version = "0.1.0"
-authors = ["Songtronix <contact@songtronix.com>"]
+authors = ["Songtronix <contact@songtronix.com>", "Folyd <lyshuhow@gmail.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.10"
+reqwest = "0.11"
diff --git a/examples/download_progress/README.md b/examples/download_progress/README.md
index c606c5f9..7999ce94 100644
--- a/examples/download_progress/README.md
+++ b/examples/download_progress/README.md
@@ -1,6 +1,6 @@
## Download progress
-A basic application that asynchronously downloads a dummy file of 100 MB and tracks the download progress.
+A basic application that asynchronously downloads multiple dummy files 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.
diff --git a/examples/download_progress/src/download.rs b/examples/download_progress/src/download.rs
index f46a01f7..08805f13 100644
--- a/examples/download_progress/src/download.rs
+++ b/examples/download_progress/src/download.rs
@@ -1,37 +1,46 @@
use iced_futures::futures;
+use std::hash::{Hash, Hasher};
// Just a little utility function
-pub fn file<T: ToString>(url: T) -> iced::Subscription<Progress> {
+pub fn file<I: 'static + Hash + Copy + Send, T: ToString>(
+ id: I,
+ url: T,
+) -> iced::Subscription<(I, Progress)> {
iced::Subscription::from_recipe(Download {
+ id,
url: url.to_string(),
})
}
-pub struct Download {
+pub struct Download<I> {
+ id: I,
url: String,
}
// Make sure iced can use our download stream
-impl<H, I> iced_native::subscription::Recipe<H, I> for Download
+impl<H, I, T> iced_native::subscription::Recipe<H, I> for Download<T>
where
- H: std::hash::Hasher,
+ T: 'static + Hash + Copy + Send,
+ H: Hasher,
{
- type Output = Progress;
+ type Output = (T, Progress);
fn hash(&self, state: &mut H) {
- use std::hash::Hash;
+ struct Marker;
+ std::any::TypeId::of::<Marker>().hash(state);
- std::any::TypeId::of::<Self>().hash(state);
- self.url.hash(state);
+ self.id.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),
- |state| async move {
+ move |state| async move {
match state {
State::Ready(url) => {
let response = reqwest::get(&url).await;
@@ -40,7 +49,7 @@ where
Ok(response) => {
if let Some(total) = response.content_length() {
Some((
- Progress::Started,
+ (id, Progress::Started),
State::Downloading {
response,
total,
@@ -48,11 +57,14 @@ where
},
))
} else {
- Some((Progress::Errored, State::Finished))
+ Some((
+ (id, Progress::Errored),
+ State::Finished,
+ ))
}
}
Err(_) => {
- Some((Progress::Errored, State::Finished))
+ Some(((id, Progress::Errored), State::Finished))
}
}
}
@@ -68,7 +80,7 @@ where
(downloaded as f32 / total as f32) * 100.0;
Some((
- Progress::Advanced(percentage),
+ (id, Progress::Advanced(percentage)),
State::Downloading {
response,
total,
@@ -76,8 +88,12 @@ where
},
))
}
- Ok(None) => Some((Progress::Finished, State::Finished)),
- Err(_) => Some((Progress::Errored, State::Finished)),
+ Ok(None) => {
+ Some(((id, Progress::Finished), State::Finished))
+ }
+ Err(_) => {
+ Some(((id, Progress::Errored), State::Finished))
+ }
},
State::Finished => {
// We do not let the stream die, as it would start a
diff --git a/examples/download_progress/src/main.rs b/examples/download_progress/src/main.rs
index 77b01354..6f844e66 100644
--- a/examples/download_progress/src/main.rs
+++ b/examples/download_progress/src/main.rs
@@ -1,6 +1,6 @@
use iced::{
- button, executor, Align, Application, Button, Column, Command, Container,
- Element, Length, ProgressBar, Settings, Subscription, Text,
+ button, executor, Align, Application, Button, Clipboard, Column, Command,
+ Container, Element, Length, ProgressBar, Settings, Subscription, Text,
};
mod download;
@@ -10,17 +10,17 @@ pub fn main() -> iced::Result {
}
#[derive(Debug)]
-enum Example {
- Idle { button: button::State },
- Downloading { progress: f32 },
- Finished { button: button::State },
- Errored { button: button::State },
+struct Example {
+ downloads: Vec<Download>,
+ last_id: usize,
+ add: button::State,
}
#[derive(Debug, Clone)]
pub enum Message {
- Download,
- DownloadProgressed(download::Progress),
+ Add,
+ Download(usize),
+ DownloadProgressed((usize, download::Progress)),
}
impl Application for Example {
@@ -30,8 +30,10 @@ impl Application for Example {
fn new(_flags: ()) -> (Example, Command<Message>) {
(
- Example::Idle {
- button: button::State::new(),
+ Example {
+ downloads: vec![Download::new(0)],
+ last_id: 0,
+ add: button::State::new(),
},
Command::none(),
)
@@ -41,104 +43,177 @@ impl Application for Example {
String::from("Download progress - Iced")
}
- fn update(&mut self, message: Message) -> Command<Message> {
+ fn update(
+ &mut self,
+ message: Message,
+ _clipboard: &mut Clipboard,
+ ) -> Command<Message> {
match message {
- Message::Download => match self {
- Example::Idle { .. }
- | Example::Finished { .. }
- | Example::Errored { .. } => {
- *self = Example::Downloading { progress: 0.0 };
+ 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::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(),
- };
- }
- },
- _ => {}
- },
+ }
+ Message::DownloadProgressed((id, progress)) => {
+ if let Some(download) =
+ self.downloads.iter_mut().find(|download| download.id == id)
+ {
+ download.progress(progress);
+ }
+ }
};
Command::none()
}
fn subscription(&self) -> Subscription<Message> {
- match self {
- Example::Downloading { .. } => {
- download::file("https://speed.hetzner.de/100MB.bin")
+ 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?")
.map(Message::DownloadProgressed)
}
_ => Subscription::none(),
}
}
- 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,
+ 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,
};
let progress_bar = ProgressBar::new(0.0..=100.0, current_progress);
- let control: Element<_> = match self {
- Example::Idle { button } => {
+ let control: Element<_> = match &mut self.state {
+ State::Idle { button } => {
Button::new(button, Text::new("Start the download!"))
- .on_press(Message::Download)
+ .on_press(Message::Download(self.id))
.into()
}
- Example::Finished { button } => Column::new()
+ State::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),
+ .on_press(Message::Download(self.id)),
)
.into(),
- Example::Downloading { .. } => {
+ State::Downloading { .. } => {
Text::new(format!("Downloading... {:.2}%", current_progress))
.into()
}
- Example::Errored { button } => Column::new()
+ State::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),
+ .on_press(Message::Download(self.id)),
)
.into(),
};
- let content = Column::new()
+ Column::new()
.spacing(10)
.padding(10)
.align_items(Align::Center)
.push(progress_bar)
- .push(control);
-
- Container::new(content)
- .width(Length::Fill)
- .height(Length::Fill)
- .center_x()
- .center_y()
+ .push(control)
.into()
}
}
diff --git a/examples/events/src/main.rs b/examples/events/src/main.rs
index 6eba6aad..446c190b 100644
--- a/examples/events/src/main.rs
+++ b/examples/events/src/main.rs
@@ -1,22 +1,30 @@
use iced::{
- executor, Align, Application, Checkbox, Column, Command, Container,
- Element, Length, Settings, Subscription, Text,
+ button, executor, Align, Application, Button, Checkbox, Clipboard, Column,
+ Command, Container, Element, HorizontalAlignment, Length, Settings,
+ Subscription, Text,
};
+use iced_native::{window, Event};
pub fn main() -> iced::Result {
- Events::run(Settings::default())
+ Events::run(Settings {
+ exit_on_close_request: false,
+ ..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 {
@@ -32,29 +40,41 @@ impl Application for Events {
String::from("Events - Iced")
}
- fn update(&mut self, message: Message) -> Command<Message> {
+ fn update(
+ &mut self,
+ message: Message,
+ _clipboard: &mut Clipboard,
+ ) -> Command<Message> {
match message {
- Message::EventOccurred(event) => {
+ Message::EventOccurred(event) if self.enabled => {
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> {
- if self.enabled {
- iced_native::subscription::events().map(Message::EventOccurred)
- } else {
- Subscription::none()
- }
+ iced_native::subscription::events().map(Message::EventOccurred)
+ }
+
+ fn should_exit(&self) -> bool {
+ self.should_exit
}
fn view(&mut self) -> Element<Message> {
@@ -71,11 +91,22 @@ 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(toggle)
+ .push(exit);
Container::new(content)
.width(Length::Fill)
diff --git a/examples/game_of_life/Cargo.toml b/examples/game_of_life/Cargo.toml
index b9bb7f2a..ffd2f19e 100644
--- a/examples/game_of_life/Cargo.toml
+++ b/examples/game_of_life/Cargo.toml
@@ -7,6 +7,6 @@ publish = false
[dependencies]
iced = { path = "../..", features = ["canvas", "tokio", "debug"] }
-tokio = { version = "0.2", features = ["blocking"] }
+tokio = { version = "1.0", features = ["sync"] }
itertools = "0.9"
rustc-hash = "1.1"
diff --git a/examples/game_of_life/README.md b/examples/game_of_life/README.md
index 1aeb1455..aa39201c 100644
--- a/examples/game_of_life/README.md
+++ b/examples/game_of_life/README.md
@@ -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/briefaccurateaardvark">
- <img src="https://thumbs.gfycat.com/BriefAccurateAardvark-size_restricted.gif">
+ <a href="https://gfycat.com/WhichPaltryChick">
+ <img src="https://thumbs.gfycat.com/WhichPaltryChick-size_restricted.gif">
</a>
</div>
diff --git a/examples/game_of_life/src/main.rs b/examples/game_of_life/src/main.rs
index 332d2d95..c3e16e8b 100644
--- a/examples/game_of_life/src/main.rs
+++ b/examples/game_of_life/src/main.rs
@@ -6,12 +6,14 @@ mod style;
use grid::Grid;
use iced::button::{self, Button};
use iced::executor;
+use iced::menu::{self, Menu};
use iced::pick_list::{self, PickList};
use iced::slider::{self, Slider};
use iced::time;
+use iced::window;
use iced::{
- Align, Application, Checkbox, Column, Command, Container, Element, Length,
- Row, Settings, Subscription, Text,
+ Align, Application, Checkbox, Clipboard, Column, Command, Container,
+ Element, Length, Row, Settings, Subscription, Text,
};
use preset::Preset;
use std::time::{Duration, Instant};
@@ -19,6 +21,10 @@ use std::time::{Duration, Instant};
pub fn main() -> iced::Result {
GameOfLife::run(Settings {
antialiasing: true,
+ window: window::Settings {
+ position: window::Position::Centered,
+ ..window::Settings::default()
+ },
..Settings::default()
})
}
@@ -31,11 +37,12 @@ struct GameOfLife {
queued_ticks: usize,
speed: usize,
next_speed: Option<usize>,
+ version: usize,
}
#[derive(Debug, Clone)]
enum Message {
- Grid(grid::Message),
+ Grid(grid::Message, usize),
Tick(Instant),
TogglePlayback,
ToggleGrid(bool),
@@ -64,10 +71,16 @@ impl Application for GameOfLife {
String::from("Game of Life - Iced")
}
- fn update(&mut self, message: Message) -> Command<Message> {
+ fn update(
+ &mut self,
+ message: Message,
+ _clipboard: &mut Clipboard,
+ ) -> Command<Message> {
match message {
- Message::Grid(message) => {
- self.grid.update(message);
+ Message::Grid(message, version) => {
+ if version == self.version {
+ self.grid.update(message);
+ }
}
Message::Tick(_) | Message::Next => {
self.queued_ticks = (self.queued_ticks + 1).min(self.speed);
@@ -79,7 +92,11 @@ impl Application for GameOfLife {
self.queued_ticks = 0;
- return Command::perform(task, Message::Grid);
+ let version = self.version;
+
+ return Command::perform(task, move |message| {
+ Message::Grid(message, version)
+ });
}
}
Message::TogglePlayback => {
@@ -90,6 +107,7 @@ impl Application for GameOfLife {
}
Message::Clear => {
self.grid.clear();
+ self.version += 1;
}
Message::SpeedChanged(speed) => {
if self.is_playing {
@@ -100,6 +118,7 @@ impl Application for GameOfLife {
}
Message::PresetPicked(new_preset) => {
self.grid = Grid::from_preset(new_preset);
+ self.version += 1;
}
}
@@ -115,7 +134,15 @@ impl Application for GameOfLife {
}
}
+ fn menu(&self) -> Menu<Message> {
+ Menu::with_entries(vec![menu::Entry::dropdown(
+ "Presets",
+ Preset::menu().map(Message::PresetPicked),
+ )])
+ }
+
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,
@@ -125,7 +152,11 @@ impl Application for GameOfLife {
);
let content = Column::new()
- .push(self.grid.view().map(Message::Grid))
+ .push(
+ self.grid
+ .view()
+ .map(move |message| Message::Grid(message, version)),
+ )
.push(controls);
Container::new(content)
@@ -139,9 +170,8 @@ impl Application for GameOfLife {
mod grid {
use crate::Preset;
use iced::{
- canvas::{
- self, Cache, Canvas, Cursor, Event, Frame, Geometry, Path, Text,
- },
+ canvas::event::{self, Event},
+ canvas::{self, Cache, Canvas, Cursor, Frame, Geometry, Path, Text},
mouse, Color, Element, HorizontalAlignment, Length, Point, Rectangle,
Size, Vector, VerticalAlignment,
};
@@ -161,7 +191,6 @@ mod grid {
show_lines: bool,
last_tick_duration: Duration,
last_queued_ticks: usize,
- version: usize,
}
#[derive(Debug, Clone)]
@@ -171,7 +200,6 @@ mod grid {
Ticked {
result: Result<Life, TickError>,
tick_duration: Duration,
- version: usize,
},
}
@@ -208,7 +236,6 @@ mod grid {
show_lines: true,
last_tick_duration: Duration::default(),
last_queued_ticks: 0,
- version: 0,
}
}
@@ -216,7 +243,6 @@ 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;
@@ -228,7 +254,6 @@ mod grid {
Message::Ticked {
result,
- version,
tick_duration,
}
})
@@ -250,13 +275,11 @@ 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 {
@@ -264,7 +287,6 @@ mod grid {
} => {
dbg!(error);
}
- Message::Ticked { .. } => {}
}
}
@@ -278,7 +300,6 @@ mod grid {
pub fn clear(&mut self) {
self.state = State::default();
self.preset = Preset::Custom;
- self.version += 1;
self.life_cache.clear();
}
@@ -323,12 +344,18 @@ mod grid {
event: Event,
bounds: Rectangle,
cursor: Cursor,
- ) -> Option<Message> {
+ ) -> (event::Status, Option<Message>) {
if let Event::Mouse(mouse::Event::ButtonReleased(_)) = event {
self.interaction = Interaction::None;
}
- let cursor_position = cursor.position_in(&bounds)?;
+ let cursor_position =
+ if let Some(position) = cursor.position_in(&bounds) {
+ position
+ } else {
+ return (event::Status::Ignored, None);
+ };
+
let cell = Cell::at(self.project(cursor_position, bounds.size()));
let is_populated = self.state.contains(&cell);
@@ -340,28 +367,32 @@ mod grid {
match event {
Event::Mouse(mouse_event) => match mouse_event {
- 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,
- };
+ mouse::Event::ButtonPressed(button) => {
+ let message = 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,
+ };
- None
- }
- _ => None,
- },
+ None
+ }
+ _ => None,
+ };
+
+ (event::Status::Captured, message)
+ }
mouse::Event::CursorMoved { .. } => {
- match self.interaction {
+ let message = match self.interaction {
Interaction::Drawing => populate,
Interaction::Erasing => unpopulate,
Interaction::Panning { translation, start } => {
@@ -375,7 +406,14 @@ 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, .. }
@@ -408,12 +446,12 @@ mod grid {
self.grid_cache.clear();
}
- None
+ (event::Status::Captured, None)
}
},
- _ => None,
+ _ => (event::Status::Ignored, None),
},
- _ => None,
+ _ => (event::Status::Ignored, None),
}
}
diff --git a/examples/game_of_life/src/preset.rs b/examples/game_of_life/src/preset.rs
index 05157b6a..1c199a72 100644
--- a/examples/game_of_life/src/preset.rs
+++ b/examples/game_of_life/src/preset.rs
@@ -1,3 +1,5 @@
+use iced::menu::{self, Menu};
+
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Preset {
Custom,
@@ -26,6 +28,17 @@ pub static ALL: &[Preset] = &[
];
impl Preset {
+ pub fn menu() -> Menu<Self> {
+ Menu::with_entries(
+ ALL.iter()
+ .copied()
+ .map(|preset| {
+ menu::Entry::item(preset.to_string(), None, preset)
+ })
+ .collect(),
+ )
+ }
+
pub fn life(self) -> Vec<(isize, isize)> {
#[rustfmt::skip]
let cells = match self {
diff --git a/examples/game_of_life/src/style.rs b/examples/game_of_life/src/style.rs
index 308ce43c..be9a0e96 100644
--- a/examples/game_of_life/src/style.rs
+++ b/examples/game_of_life/src/style.rs
@@ -44,7 +44,7 @@ impl button::StyleSheet for Button {
fn active(&self) -> button::Style {
button::Style {
background: Some(Background::Color(ACTIVE)),
- border_radius: 3,
+ border_radius: 3.0,
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,
+ border_width: 1.0,
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,
+ border_radius: 3.0,
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,
+ border_width: 1.0,
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 },
+ shape: slider::HandleShape::Circle { radius: 9.0 },
color: ACTIVE,
- border_width: 0,
+ border_width: 0.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,
+ border_width: 1.0,
border_color: Color {
a: 0.7,
..Color::BLACK
@@ -164,13 +164,14 @@ impl pick_list::StyleSheet for PickList {
pick_list::Style {
text_color: Color::WHITE,
background: BACKGROUND.into(),
- border_width: 1,
+ border_width: 1.0,
border_color: Color {
a: 0.6,
..Color::BLACK
},
- border_radius: 2,
+ border_radius: 2.0,
icon_size: 0.5,
+ ..pick_list::Style::default()
}
}
diff --git a/examples/geometry/src/main.rs b/examples/geometry/src/main.rs
index 1c13c8d5..f650b2c1 100644
--- a/examples/geometry/src/main.rs
+++ b/examples/geometry/src/main.rs
@@ -15,8 +15,8 @@ mod rainbow {
Backend, Defaults, Primitive, Renderer,
};
use iced_native::{
- layout, mouse, Element, Hasher, Layout, Length, Point, Size, Vector,
- Widget,
+ layout, mouse, Element, Hasher, Layout, Length, Point, Rectangle, Size,
+ Vector, Widget,
};
pub struct Rainbow;
@@ -57,6 +57,7 @@ mod rainbow {
_defaults: &Defaults,
layout: Layout<'_>,
cursor_position: Point,
+ _viewport: &Rectangle,
) -> (Primitive, mouse::Interaction) {
let b = layout.bounds();
diff --git a/examples/integration/Cargo.toml b/examples/integration/Cargo.toml
index afc2c791..4515502f 100644
--- a/examples/integration/Cargo.toml
+++ b/examples/integration/Cargo.toml
@@ -8,4 +8,4 @@ publish = false
[dependencies]
iced_winit = { path = "../../winit" }
iced_wgpu = { path = "../../wgpu" }
-env_logger = "0.7"
+env_logger = "0.8"
diff --git a/examples/integration/src/controls.rs b/examples/integration/src/controls.rs
index 824f9f53..36ee9b7e 100644
--- a/examples/integration/src/controls.rs
+++ b/examples/integration/src/controls.rs
@@ -1,7 +1,7 @@
use iced_wgpu::Renderer;
use iced_winit::{
- slider, Align, Color, Column, Command, Element, Length, Program, Row,
- Slider, Text,
+ slider, Align, Clipboard, Color, Column, Command, Element, Length, Program,
+ Row, Slider, Text,
};
pub struct Controls {
@@ -30,8 +30,13 @@ impl Controls {
impl Program for Controls {
type Renderer = Renderer;
type Message = Message;
+ type Clipboard = Clipboard;
- fn update(&mut self, message: Message) -> Command<Message> {
+ fn update(
+ &mut self,
+ message: Message,
+ _clipboard: &mut Clipboard,
+ ) -> Command<Message> {
match message {
Message::BackgroundColorChanged(color) => {
self.background_color = color;
diff --git a/examples/integration/src/main.rs b/examples/integration/src/main.rs
index 9b52f3a5..6f319466 100644
--- a/examples/integration/src/main.rs
+++ b/examples/integration/src/main.rs
@@ -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, Debug, Size};
+use iced_winit::{conversion, futures, program, winit, Clipboard, Debug, Size};
use futures::task::SpawnExt;
use winit::{
@@ -28,43 +28,47 @@ 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);
let surface = unsafe { instance.create_surface(&window) };
- let (mut device, queue) = futures::executor::block_on(async {
+ let (format, (mut device, queue)) = futures::executor::block_on(async {
let adapter = instance
.request_adapter(&wgpu::RequestAdapterOptions {
- power_preference: wgpu::PowerPreference::Default,
+ power_preference: wgpu::PowerPreference::HighPerformance,
compatible_surface: Some(&surface),
})
.await
.expect("Request adapter");
- adapter
- .request_device(
- &wgpu::DeviceDescriptor {
- features: wgpu::Features::empty(),
- limits: wgpu::Limits::default(),
- shader_validation: false,
- },
- None,
- )
- .await
- .expect("Request device")
+ (
+ adapter
+ .get_swap_chain_preferred_format(&surface)
+ .expect("Get preferred format"),
+ adapter
+ .request_device(
+ &wgpu::DeviceDescriptor {
+ label: None,
+ features: wgpu::Features::empty(),
+ limits: wgpu::Limits::default(),
+ },
+ None,
+ )
+ .await
+ .expect("Request device"),
+ )
});
- let format = wgpu::TextureFormat::Bgra8UnormSrgb;
-
let mut swap_chain = {
let size = window.inner_size();
device.create_swap_chain(
&surface,
&wgpu::SwapChainDescriptor {
- usage: wgpu::TextureUsage::OUTPUT_ATTACHMENT,
- format: format,
+ usage: wgpu::TextureUsage::RENDER_ATTACHMENT,
+ format,
width: size.width,
height: size.height,
present_mode: wgpu::PresentMode::Mailbox,
@@ -84,7 +88,7 @@ pub fn main() {
// Initialize iced
let mut debug = Debug::new();
let mut renderer =
- Renderer::new(Backend::new(&mut device, Settings::default()));
+ Renderer::new(Backend::new(&mut device, Settings::default(), format));
let mut state = program::State::new(
controls,
@@ -141,8 +145,8 @@ pub fn main() {
cursor_position,
viewport.scale_factor(),
),
- None,
&mut renderer,
+ &mut clipboard,
&mut debug,
);
@@ -157,7 +161,7 @@ pub fn main() {
swap_chain = device.create_swap_chain(
&surface,
&wgpu::SwapChainDescriptor {
- usage: wgpu::TextureUsage::OUTPUT_ATTACHMENT,
+ usage: wgpu::TextureUsage::RENDER_ATTACHMENT,
format: format,
width: size.width,
height: size.height,
@@ -168,55 +172,66 @@ pub fn main() {
resized = false;
}
- let frame = swap_chain.get_current_frame().expect("Next frame");
+ match swap_chain.get_current_frame() {
+ Ok(frame) => {
+ let mut encoder = device.create_command_encoder(
+ &wgpu::CommandEncoderDescriptor { label: None },
+ );
+
+ let program = state.program();
+
+ {
+ // We clear the frame
+ let mut render_pass = scene.clear(
+ &frame.output.view,
+ &mut encoder,
+ program.background_color(),
+ );
+
+ // Draw the scene
+ scene.draw(&mut render_pass);
+ }
+
+ // And then iced on top
+ let mouse_interaction = renderer.backend_mut().draw(
+ &mut device,
+ &mut staging_belt,
+ &mut encoder,
+ &frame.output.view,
+ &viewport,
+ state.primitive(),
+ &debug.overlay(),
+ );
- let mut encoder = device.create_command_encoder(
- &wgpu::CommandEncoderDescriptor { label: None },
- );
+ // Then we submit the work
+ staging_belt.finish();
+ queue.submit(Some(encoder.finish()));
- let program = state.program();
+ // Update the mouse cursor
+ window.set_cursor_icon(
+ iced_winit::conversion::mouse_interaction(
+ mouse_interaction,
+ ),
+ );
- {
- // We clear the frame
- let mut render_pass = scene.clear(
- &frame.output.view,
- &mut encoder,
- program.background_color(),
- );
+ // And recall staging buffers
+ local_pool
+ .spawner()
+ .spawn(staging_belt.recall())
+ .expect("Recall staging buffers");
- // Draw the scene
- scene.draw(&mut render_pass);
+ local_pool.run_until_stalled();
+ }
+ Err(error) => match error {
+ wgpu::SwapChainError::OutOfMemory => {
+ panic!("Swapchain error: {}. Rendering cannot continue.", error)
+ }
+ _ => {
+ // Try rendering again next frame.
+ window.request_redraw();
+ }
+ },
}
-
- // And then iced on top
- let mouse_interaction = renderer.backend_mut().draw(
- &mut device,
- &mut staging_belt,
- &mut encoder,
- &frame.output.view,
- &viewport,
- state.primitive(),
- &debug.overlay(),
- );
-
- // Then we submit the work
- staging_belt.finish();
- queue.submit(Some(encoder.finish()));
-
- // Update the mouse cursor
- window.set_cursor_icon(
- iced_winit::conversion::mouse_interaction(
- mouse_interaction,
- ),
- );
-
- // And recall staging buffers
- local_pool
- .spawner()
- .spawn(staging_belt.recall())
- .expect("Recall staging buffers");
-
- local_pool.run_until_stalled();
}
_ => {}
}
diff --git a/examples/integration/src/scene.rs b/examples/integration/src/scene.rs
index 03a338c6..3e8277c8 100644
--- a/examples/integration/src/scene.rs
+++ b/examples/integration/src/scene.rs
@@ -19,8 +19,9 @@ impl Scene {
background_color: Color,
) -> wgpu::RenderPass<'a> {
encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
- color_attachments: &[wgpu::RenderPassColorAttachmentDescriptor {
- attachment: target,
+ label: None,
+ color_attachments: &[wgpu::RenderPassColorAttachment {
+ view: target,
resolve_target: None,
ops: wgpu::Operations {
load: wgpu::LoadOp::Clear({
@@ -48,10 +49,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 {
@@ -64,34 +65,34 @@ fn build_pipeline(device: &wgpu::Device) -> wgpu::RenderPipeline {
device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
label: None,
layout: Some(&pipeline_layout),
- vertex_stage: wgpu::ProgrammableStageDescriptor {
+ vertex: wgpu::VertexState {
module: &vs_module,
entry_point: "main",
+ buffers: &[],
},
- fragment_stage: Some(wgpu::ProgrammableStageDescriptor {
+ fragment: Some(wgpu::FragmentState {
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,
+ }],
}),
- rasterization_state: Some(wgpu::RasterizationStateDescriptor {
+ primitive: wgpu::PrimitiveState {
+ topology: wgpu::PrimitiveTopology::TriangleList,
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: &[],
},
- sample_count: 1,
- sample_mask: !0,
- alpha_to_coverage_enabled: false,
+ depth_stencil: None,
+ multisample: wgpu::MultisampleState {
+ count: 1,
+ mask: !0,
+ alpha_to_coverage_enabled: false,
+ },
});
pipeline
diff --git a/examples/menu/Cargo.toml b/examples/menu/Cargo.toml
new file mode 100644
index 00000000..44597734
--- /dev/null
+++ b/examples/menu/Cargo.toml
@@ -0,0 +1,10 @@
+[package]
+name = "menu"
+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" } \ No newline at end of file
diff --git a/examples/menu/src/main.rs b/examples/menu/src/main.rs
new file mode 100644
index 00000000..7403713c
--- /dev/null
+++ b/examples/menu/src/main.rs
@@ -0,0 +1,117 @@
+use iced::menu::{self, Menu};
+use iced::{
+ executor, Application, Clipboard, Command, Container, Element, Length,
+ Settings, Text,
+};
+use iced_native::keyboard::{Hotkey, KeyCode, Modifiers};
+
+pub fn main() -> iced::Result {
+ App::run(Settings::default())
+}
+
+#[derive(Debug, Default)]
+struct App {
+ selected: Option<Entry>,
+}
+
+#[derive(Debug, Clone)]
+enum Entry {
+ One,
+ Two,
+ Three,
+ A,
+ B,
+ C,
+}
+
+#[derive(Debug, Clone)]
+enum Message {
+ MenuActivated(Entry),
+}
+
+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("Menu - Iced")
+ }
+
+ fn menu(&self) -> Menu<Message> {
+ let alt = Modifiers::ALT;
+ let ctrl_shift = Modifiers::CTRL | Modifiers::SHIFT;
+
+ Menu::with_entries(vec![
+ menu::Entry::dropdown(
+ "First",
+ Menu::with_entries(vec![
+ menu::Entry::item(
+ "One",
+ Hotkey::new(alt, KeyCode::F1),
+ Message::MenuActivated(Entry::One),
+ ),
+ menu::Entry::item(
+ "Two",
+ Hotkey::new(alt, KeyCode::F2),
+ Message::MenuActivated(Entry::Two),
+ ),
+ menu::Entry::Separator,
+ menu::Entry::item(
+ "Three",
+ Hotkey::new(alt, KeyCode::F3),
+ Message::MenuActivated(Entry::Three),
+ ),
+ ]),
+ ),
+ menu::Entry::dropdown(
+ "Second",
+ Menu::with_entries(vec![
+ menu::Entry::item(
+ "A",
+ Hotkey::new(ctrl_shift, KeyCode::A),
+ Message::MenuActivated(Entry::A),
+ ),
+ menu::Entry::item(
+ "B",
+ Hotkey::new(ctrl_shift, KeyCode::B),
+ Message::MenuActivated(Entry::B),
+ ),
+ menu::Entry::Separator,
+ menu::Entry::item(
+ "C",
+ Hotkey::new(ctrl_shift, KeyCode::C),
+ Message::MenuActivated(Entry::C),
+ ),
+ ]),
+ ),
+ ])
+ }
+
+ fn update(
+ &mut self,
+ message: Message,
+ _clipboard: &mut Clipboard,
+ ) -> Command<Message> {
+ match message {
+ Message::MenuActivated(entry) => self.selected = Some(entry),
+ }
+
+ Command::none()
+ }
+
+ fn view(&mut self) -> Element<Message> {
+ Container::new(
+ Text::new(format!("Selected {:?}", self.selected)).size(48),
+ )
+ .width(Length::Fill)
+ .height(Length::Fill)
+ .center_x()
+ .center_y()
+ .into()
+ }
+}
diff --git a/examples/pane_grid/Cargo.toml b/examples/pane_grid/Cargo.toml
index 3ed912ac..e489f210 100644
--- a/examples/pane_grid/Cargo.toml
+++ b/examples/pane_grid/Cargo.toml
@@ -6,4 +6,5 @@ edition = "2018"
publish = false
[dependencies]
-iced = { path = "../.." }
+iced = { path = "../..", features = ["debug"] }
+iced_native = { path = "../../native" }
diff --git a/examples/pane_grid/src/main.rs b/examples/pane_grid/src/main.rs
index c4946645..3bd8aa25 100644
--- a/examples/pane_grid/src/main.rs
+++ b/examples/pane_grid/src/main.rs
@@ -1,16 +1,19 @@
use iced::{
- button, keyboard, pane_grid, scrollable, Align, Button, Column, Container,
- Element, HorizontalAlignment, Length, PaneGrid, Sandbox, Scrollable,
- Settings, Text,
+ button, executor, keyboard, pane_grid, scrollable, Align, Application,
+ Button, Clipboard, Color, Column, Command, Container, Element,
+ HorizontalAlignment, Length, PaneGrid, Row, Scrollable, Settings,
+ Subscription, Text,
};
+use iced_native::{event, subscription, Event};
pub fn main() -> iced::Result {
Example::run(Settings::default())
}
struct Example {
- panes: pane_grid::State<Content>,
+ panes: pane_grid::State<Pane>,
panes_created: usize,
+ focus: Option<pane_grid::Pane>,
}
#[derive(Debug, Clone, Copy)]
@@ -18,59 +21,82 @@ 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 Sandbox for Example {
+impl Application for Example {
type Message = Message;
-
- fn new() -> Self {
- let (panes, _) = pane_grid::State::new(Content::new(0));
-
- Example {
- panes,
- panes_created: 1,
- }
+ type Executor = executor::Default;
+ type Flags = ();
+
+ fn new(_flags: ()) -> (Self, Command<Message>) {
+ let (panes, _) = pane_grid::State::new(Pane::new(0));
+
+ (
+ Example {
+ panes,
+ panes_created: 1,
+ focus: None,
+ },
+ Command::none(),
+ )
}
fn title(&self) -> String {
String::from("Pane grid - Iced")
}
- fn update(&mut self, message: Message) {
+ fn update(
+ &mut self,
+ message: Message,
+ _clipboard: &mut Clipboard,
+ ) -> Command<Message> {
match message {
Message::Split(axis, pane) => {
- let _ = self.panes.split(
+ let result = self.panes.split(
axis,
&pane,
- Content::new(self.panes_created),
+ Pane::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.panes.active() {
- let _ = self.panes.split(
+ if let Some(pane) = self.focus {
+ let result = self.panes.split(
axis,
&pane,
- Content::new(self.panes_created),
+ Pane::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.panes.active() {
+ if let Some(pane) = self.focus {
if let Some(adjacent) =
self.panes.adjacent(&pane, direction)
{
- self.panes.focus(&adjacent);
+ self.focus = Some(adjacent);
}
}
}
+ Message::Clicked(pane) => {
+ self.focus = Some(pane);
+ }
Message::Resized(pane_grid::ResizeEvent { split, ratio }) => {
self.panes.resize(&split, ratio);
}
@@ -81,38 +107,97 @@ impl Sandbox 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) => {
- let _ = self.panes.close(&pane);
+ if let Some((_, sibling)) = self.panes.close(&pane) {
+ self.focus = Some(sibling);
+ }
}
Message::CloseFocused => {
- if let Some(pane) = self.panes.active() {
- let _ = self.panes.close(&pane);
+ 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);
+ }
+ }
+ }
}
}
}
+
+ 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.command() => 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, |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 });
-
- 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);
+ let pane_grid = PaneGrid::new(&mut self.panes, |id, pane| {
+ let is_focused = focus == Some(id);
+
+ 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);
Container::new(pane_grid)
.width(Length::Fill)
@@ -122,11 +207,22 @@ impl Sandbox for Example {
}
}
-fn handle_hotkey(event: pane_grid::KeyPressEvent) -> Option<Message> {
+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> {
use keyboard::KeyCode;
use pane_grid::{Axis, Direction};
- let direction = match event.key_code {
+ let direction = match key_code {
KeyCode::Up => Some(Direction::Up),
KeyCode::Down => Some(Direction::Down),
KeyCode::Left => Some(Direction::Left),
@@ -134,7 +230,7 @@ fn handle_hotkey(event: pane_grid::KeyPressEvent) -> Option<Message> {
_ => None,
};
- match event.key_code {
+ match key_code {
KeyCode::V => Some(Message::SplitFocused(Axis::Vertical)),
KeyCode::H => Some(Message::SplitFocused(Axis::Horizontal)),
KeyCode::W => Some(Message::CloseFocused),
@@ -142,6 +238,13 @@ fn handle_hotkey(event: pane_grid::KeyPressEvent) -> 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,
@@ -150,6 +253,21 @@ 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 {
@@ -164,6 +282,7 @@ impl Content {
&mut self,
pane: pane_grid::Pane,
total_panes: usize,
+ is_pinned: bool,
) -> Element<Message> {
let Content {
scroll,
@@ -203,7 +322,7 @@ impl Content {
style::Button::Primary,
));
- if total_panes > 1 {
+ if total_panes > 1 && !is_pinned {
controls = controls.push(button(
close,
"Close",
@@ -227,7 +346,32 @@ 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(
@@ -275,7 +419,7 @@ mod style {
fn style(&self) -> container::Style {
container::Style {
background: Some(Background::Color(SURFACE)),
- border_width: 2,
+ border_width: 2.0,
border_color: if self.is_focused {
Color::BLACK
} else {
@@ -289,6 +433,8 @@ mod style {
pub enum Button {
Primary,
Destructive,
+ Control,
+ Pin,
}
impl button::StyleSheet for Button {
@@ -298,12 +444,14 @@ 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,
+ border_radius: 5.0,
shadow_offset: Vector::new(0.0, 0.0),
..button::Style::default()
}
@@ -318,6 +466,8 @@ mod style {
a: 0.2,
..active.text_color
}),
+ Button::Control => Some(PANE_ID_COLOR_FOCUSED),
+ Button::Pin => Some(HOVERED),
};
button::Style {
diff --git a/examples/pick_list/src/main.rs b/examples/pick_list/src/main.rs
index 68662602..1eec9791 100644
--- a/examples/pick_list/src/main.rs
+++ b/examples/pick_list/src/main.rs
@@ -11,7 +11,7 @@ pub fn main() -> iced::Result {
struct Example {
scroll: scrollable::State,
pick_list: pick_list::State<Language>,
- selected_language: Language,
+ selected_language: Option<Language>,
}
#[derive(Debug, Clone, Copy)]
@@ -33,7 +33,7 @@ impl Sandbox for Example {
fn update(&mut self, message: Message) {
match message {
Message::LanguageSelected(language) => {
- self.selected_language = language;
+ self.selected_language = Some(language);
}
}
}
@@ -42,9 +42,10 @@ impl Sandbox for Example {
let pick_list = PickList::new(
&mut self.pick_list,
&Language::ALL[..],
- Some(self.selected_language),
+ self.selected_language,
Message::LanguageSelected,
- );
+ )
+ .placeholder("Choose a language...");
let mut content = Scrollable::new(&mut self.scroll)
.width(Length::Fill)
diff --git a/examples/pokedex/Cargo.toml b/examples/pokedex/Cargo.toml
index 94320086..05e73992 100644
--- a/examples/pokedex/Cargo.toml
+++ b/examples/pokedex/Cargo.toml
@@ -6,7 +6,7 @@ edition = "2018"
publish = false
[dependencies]
-iced = { path = "../..", features = ["image", "debug", "tokio"] }
+iced = { path = "../..", features = ["image", "debug", "tokio_old"] }
serde_json = "1.0"
[dependencies.serde]
diff --git a/examples/pokedex/src/main.rs b/examples/pokedex/src/main.rs
index 30674fa0..da1d5d5d 100644
--- a/examples/pokedex/src/main.rs
+++ b/examples/pokedex/src/main.rs
@@ -1,6 +1,6 @@
use iced::{
- button, futures, image, Align, Application, Button, Column, Command,
- Container, Element, Image, Length, Row, Settings, Text,
+ button, futures, image, Align, Application, Button, Clipboard, Column,
+ Command, Container, Element, Length, Row, Settings, Text,
};
pub fn main() -> iced::Result {
@@ -48,7 +48,11 @@ impl Application for Pokedex {
format!("{} - Pokédex", subtitle)
}
- fn update(&mut self, message: Message) -> Command<Message> {
+ fn update(
+ &mut self,
+ message: Message,
+ _clipboard: &mut Clipboard,
+ ) -> Command<Message> {
match message {
Message::PokemonFound(Ok(pokemon)) => {
*self = Pokedex::Loaded {
@@ -112,16 +116,20 @@ struct Pokemon {
name: String,
description: String,
image: image::Handle,
+ image_viewer: image::viewer::State,
}
impl Pokemon {
const TOTAL: u16 = 807;
- fn view(&self) -> Element<Message> {
+ fn view(&mut self) -> Element<Message> {
Row::new()
.spacing(20)
.align_items(Align::Center)
- .push(Image::new(self.image.clone()))
+ .push(image::Viewer::new(
+ &mut self.image_viewer,
+ self.image.clone(),
+ ))
.push(
Column::new()
.spacing(20)
@@ -200,11 +208,15 @@ 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"))]
{
@@ -251,7 +263,7 @@ mod style {
background: Some(Background::Color(match self {
Button::Primary => Color::from_rgb(0.11, 0.42, 0.87),
})),
- border_radius: 12,
+ border_radius: 12.0,
shadow_offset: Vector::new(1.0, 1.0),
text_color: Color::WHITE,
..button::Style::default()
diff --git a/examples/qr_code/Cargo.toml b/examples/qr_code/Cargo.toml
new file mode 100644
index 00000000..7f2d4e42
--- /dev/null
+++ b/examples/qr_code/Cargo.toml
@@ -0,0 +1,9 @@
+[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"] }
diff --git a/examples/qr_code/README.md b/examples/qr_code/README.md
new file mode 100644
index 00000000..2dd89c26
--- /dev/null
+++ b/examples/qr_code/README.md
@@ -0,0 +1,18 @@
+## 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
diff --git a/examples/qr_code/src/main.rs b/examples/qr_code/src/main.rs
new file mode 100644
index 00000000..37b4855d
--- /dev/null
+++ b/examples/qr_code/src/main.rs
@@ -0,0 +1,81 @@
+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()
+ }
+}
diff --git a/examples/scrollable/Cargo.toml b/examples/scrollable/Cargo.toml
new file mode 100644
index 00000000..08502458
--- /dev/null
+++ b/examples/scrollable/Cargo.toml
@@ -0,0 +1,9 @@
+[package]
+name = "scrollable"
+version = "0.1.0"
+authors = ["Clark Moody <clark@clarkmoody.com>"]
+edition = "2018"
+publish = false
+
+[dependencies]
+iced = { path = "../..", features = ["debug"] }
diff --git a/examples/scrollable/README.md b/examples/scrollable/README.md
new file mode 100644
index 00000000..ed0e31b5
--- /dev/null
+++ b/examples/scrollable/README.md
@@ -0,0 +1,15 @@
+# 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
+```
diff --git a/examples/scrollable/screenshot.png b/examples/scrollable/screenshot.png
new file mode 100644
index 00000000..2d800251
--- /dev/null
+++ b/examples/scrollable/screenshot.png
Binary files differ
diff --git a/examples/scrollable/src/main.rs b/examples/scrollable/src/main.rs
new file mode 100644
index 00000000..3416b83d
--- /dev/null
+++ b/examples/scrollable/src/main.rs
@@ -0,0 +1,255 @@
+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,
+ },
+ ]
+ }
+}
diff --git a/examples/scrollable/src/style.rs b/examples/scrollable/src/style.rs
new file mode 100644
index 00000000..ae449141
--- /dev/null
+++ b/examples/scrollable/src/style.rs
@@ -0,0 +1,190 @@
+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),
+ }
+ }
+ }
+}
diff --git a/examples/solar_system/Cargo.toml b/examples/solar_system/Cargo.toml
index 44ced729..327fe0aa 100644
--- a/examples/solar_system/Cargo.toml
+++ b/examples/solar_system/Cargo.toml
@@ -7,4 +7,4 @@ publish = false
[dependencies]
iced = { path = "../..", features = ["canvas", "tokio", "debug"] }
-rand = "0.7"
+rand = "0.8.3"
diff --git a/examples/solar_system/src/main.rs b/examples/solar_system/src/main.rs
index 6a2de736..8f844828 100644
--- a/examples/solar_system/src/main.rs
+++ b/examples/solar_system/src/main.rs
@@ -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, Color, Command, Element,
- Length, Point, Rectangle, Settings, Size, Subscription, Vector,
+ executor, time, window, Application, Canvas, Clipboard, Color, Command,
+ Element, Length, Point, Rectangle, Settings, Size, Subscription, Vector,
};
use std::time::Instant;
@@ -48,7 +48,11 @@ impl Application for SolarSystem {
String::from("Solar system - Iced")
}
- fn update(&mut self, message: Message) -> Command<Message> {
+ fn update(
+ &mut self,
+ message: Message,
+ _clipboard: &mut Clipboard,
+ ) -> Command<Message> {
match message {
Message::Tick(instant) => {
self.state.update(instant);
@@ -117,15 +121,13 @@ 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()
diff --git a/examples/stopwatch/Cargo.toml b/examples/stopwatch/Cargo.toml
index 075aa111..9f935951 100644
--- a/examples/stopwatch/Cargo.toml
+++ b/examples/stopwatch/Cargo.toml
@@ -6,4 +6,4 @@ edition = "2018"
publish = false
[dependencies]
-iced = { path = "../..", features = ["tokio"] }
+iced = { path = "../..", features = ["smol"] }
diff --git a/examples/stopwatch/src/main.rs b/examples/stopwatch/src/main.rs
index 5a69aa9a..51972e01 100644
--- a/examples/stopwatch/src/main.rs
+++ b/examples/stopwatch/src/main.rs
@@ -1,6 +1,6 @@
use iced::{
- button, executor, time, Align, Application, Button, Column, Command,
- Container, Element, HorizontalAlignment, Length, Row, Settings,
+ button, executor, time, Align, Application, Button, Clipboard, Column,
+ Command, Container, Element, HorizontalAlignment, Length, Row, Settings,
Subscription, Text,
};
use std::time::{Duration, Instant};
@@ -49,7 +49,11 @@ impl Application for Stopwatch {
String::from("Stopwatch - Iced")
}
- fn update(&mut self, message: Message) -> Command<Message> {
+ fn update(
+ &mut self,
+ message: Message,
+ _clipboard: &mut Clipboard,
+ ) -> Command<Message> {
match message {
Message::Toggle => match self.state {
State::Idle => {
@@ -161,7 +165,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,
+ border_radius: 12.0,
shadow_offset: Vector::new(1.0, 1.0),
text_color: Color::WHITE,
..button::Style::default()
diff --git a/examples/styling/src/main.rs b/examples/styling/src/main.rs
index ef302e61..7bc49281 100644
--- a/examples/styling/src/main.rs
+++ b/examples/styling/src/main.rs
@@ -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,
+ Scrollable, Settings, Slider, Space, Text, TextInput, Toggler,
};
pub fn main() -> iced::Result {
@@ -17,7 +17,8 @@ struct Styling {
button: button::State,
slider: slider::State,
slider_value: f32,
- toggle_value: bool,
+ checkbox_value: bool,
+ toggler_value: bool,
}
#[derive(Debug, Clone)]
@@ -27,6 +28,7 @@ enum Message {
ButtonPressed,
SliderChanged(f32),
CheckboxToggled(bool),
+ TogglerToggled(bool),
}
impl Sandbox for Styling {
@@ -44,9 +46,10 @@ 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.toggle_value = value,
+ Message::CheckboxToggled(value) => self.checkbox_value = value,
+ Message::TogglerToggled(value) => self.toggler_value = value,
}
}
@@ -101,11 +104,19 @@ impl Sandbox for Styling {
.push(Text::new("You did it!"));
let checkbox = Checkbox::new(
- self.toggle_value,
- "Toggle me!",
+ self.checkbox_value,
+ "Check me!",
Message::CheckboxToggled,
)
- .width(Length::Fill)
+ .style(self.theme);
+
+ let toggler = Toggler::new(
+ self.toggler_value,
+ String::from("Toggle me!"),
+ Message::TogglerToggled,
+ )
+ .width(Length::Shrink)
+ .spacing(10)
.style(self.theme);
let content = Column::new()
@@ -124,7 +135,13 @@ impl Sandbox for Styling {
.align_items(Align::Center)
.push(scrollable)
.push(Rule::vertical(38).style(self.theme))
- .push(checkbox),
+ .push(
+ Column::new()
+ .width(Length::Shrink)
+ .spacing(20)
+ .push(checkbox)
+ .push(toggler),
+ ),
);
Container::new(content)
@@ -140,7 +157,7 @@ impl Sandbox for Styling {
mod style {
use iced::{
button, checkbox, container, progress_bar, radio, rule, scrollable,
- slider, text_input,
+ slider, text_input, toggler,
};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
@@ -231,6 +248,15 @@ 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 {
@@ -249,7 +275,7 @@ mod style {
fn active(&self) -> button::Style {
button::Style {
background: Color::from_rgb(0.11, 0.42, 0.87).into(),
- border_radius: 12,
+ border_radius: 12.0,
shadow_offset: Vector::new(1.0, 1.0),
text_color: Color::from_rgb8(0xEE, 0xEE, 0xEE),
..button::Style::default()
@@ -269,7 +295,7 @@ mod style {
mod dark {
use iced::{
button, checkbox, container, progress_bar, radio, rule, scrollable,
- slider, text_input, Color,
+ slider, text_input, toggler, Color,
};
const SURFACE: Color = Color::from_rgb(
@@ -315,7 +341,7 @@ mod style {
radio::Style {
background: SURFACE.into(),
dot_color: ACTIVE,
- border_width: 1,
+ border_width: 1.0,
border_color: ACTIVE,
}
}
@@ -334,15 +360,15 @@ mod style {
fn active(&self) -> text_input::Style {
text_input::Style {
background: SURFACE.into(),
- border_radius: 2,
- border_width: 0,
+ border_radius: 2.0,
+ border_width: 0.0,
border_color: Color::TRANSPARENT,
}
}
fn focused(&self) -> text_input::Style {
text_input::Style {
- border_width: 1,
+ border_width: 1.0,
border_color: ACCENT,
..self.active()
}
@@ -350,7 +376,7 @@ mod style {
fn hovered(&self) -> text_input::Style {
text_input::Style {
- border_width: 1,
+ border_width: 1.0,
border_color: Color { a: 0.3, ..ACCENT },
..self.focused()
}
@@ -375,7 +401,7 @@ mod style {
fn active(&self) -> button::Style {
button::Style {
background: ACTIVE.into(),
- border_radius: 3,
+ border_radius: 3.0,
text_color: Color::WHITE,
..button::Style::default()
}
@@ -391,7 +417,7 @@ mod style {
fn pressed(&self) -> button::Style {
button::Style {
- border_width: 1,
+ border_width: 1.0,
border_color: Color::WHITE,
..self.hovered()
}
@@ -404,13 +430,13 @@ mod style {
fn active(&self) -> scrollable::Scrollbar {
scrollable::Scrollbar {
background: SURFACE.into(),
- border_radius: 2,
- border_width: 0,
+ border_radius: 2.0,
+ border_width: 0.0,
border_color: Color::TRANSPARENT,
scroller: scrollable::Scroller {
color: ACTIVE,
- border_radius: 2,
- border_width: 0,
+ border_radius: 2.0,
+ border_width: 0.0,
border_color: Color::TRANSPARENT,
},
}
@@ -449,9 +475,9 @@ mod style {
slider::Style {
rail_colors: (ACTIVE, Color { a: 0.1, ..ACTIVE }),
handle: slider::Handle {
- shape: slider::HandleShape::Circle { radius: 9 },
+ shape: slider::HandleShape::Circle { radius: 9.0 },
color: ACTIVE,
- border_width: 0,
+ border_width: 0.0,
border_color: Color::TRANSPARENT,
},
}
@@ -489,7 +515,7 @@ mod style {
progress_bar::Style {
background: SURFACE.into(),
bar: ACTIVE.into(),
- border_radius: 10,
+ border_radius: 10.0,
}
}
}
@@ -502,8 +528,8 @@ mod style {
background: if is_checked { ACTIVE } else { SURFACE }
.into(),
checkmark_color: Color::WHITE,
- border_radius: 2,
- border_width: 1,
+ border_radius: 2.0,
+ border_width: 1.0,
border_color: ACTIVE,
}
}
@@ -520,6 +546,35 @@ 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 {
@@ -527,7 +582,7 @@ mod style {
rule::Style {
color: SURFACE,
width: 2,
- radius: 1,
+ radius: 1.0,
fill_mode: rule::FillMode::Padded(15),
}
}
diff --git a/examples/todos/Cargo.toml b/examples/todos/Cargo.toml
index b236cc0d..c8926c33 100644
--- a/examples/todos/Cargo.toml
+++ b/examples/todos/Cargo.toml
@@ -12,7 +12,7 @@ serde_json = "1.0"
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
async-std = "1.0"
-directories = "2.0"
+directories-next = "2.0"
[target.'cfg(target_arch = "wasm32")'.dependencies]
web-sys = { version = "0.3", features = ["Window", "Storage"] }
diff --git a/examples/todos/src/main.rs b/examples/todos/src/main.rs
index 7a546815..97415475 100644
--- a/examples/todos/src/main.rs
+++ b/examples/todos/src/main.rs
@@ -1,7 +1,7 @@
use iced::{
button, scrollable, text_input, Align, Application, Button, Checkbox,
- Column, Command, Container, Element, Font, HorizontalAlignment, Length,
- Row, Scrollable, Settings, Text, TextInput,
+ Clipboard, Column, Command, Container, Element, Font, HorizontalAlignment,
+ Length, Row, Scrollable, Settings, Text, TextInput,
};
use serde::{Deserialize, Serialize};
@@ -58,7 +58,11 @@ impl Application for Todos {
format!("Todos{} - Iced", if dirty { "*" } else { "" })
}
- fn update(&mut self, message: Message) -> Command<Message> {
+ fn update(
+ &mut self,
+ message: Message,
+ _clipboard: &mut Clipboard,
+ ) -> Command<Message> {
match self {
Todos::Loading => {
match message {
@@ -261,8 +265,11 @@ impl Task {
self.completed = completed;
}
TaskMessage::Edit => {
+ let mut text_input = text_input::State::focused();
+ text_input.select_all();
+
self.state = TaskState::Editing {
- text_input: text_input::State::focused(),
+ text_input,
delete_button: button::State::new(),
};
}
@@ -489,7 +496,6 @@ enum LoadError {
#[derive(Debug, Clone)]
enum SaveError {
- DirectoryError,
FileError,
WriteError,
FormatError,
@@ -499,7 +505,7 @@ enum SaveError {
impl SavedState {
fn path() -> std::path::PathBuf {
let mut path = if let Some(project_dirs) =
- directories::ProjectDirs::from("rs", "Iced", "Todos")
+ directories_next::ProjectDirs::from("rs", "Iced", "Todos")
{
project_dirs.data_dir().into()
} else {
@@ -538,7 +544,7 @@ impl SavedState {
if let Some(dir) = path.parent() {
async_std::fs::create_dir_all(dir)
.await
- .map_err(|_| SaveError::DirectoryError)?;
+ .map_err(|_| SaveError::FileError)?;
}
{
@@ -611,7 +617,7 @@ mod style {
background: Some(Background::Color(
Color::from_rgb(0.2, 0.2, 0.7),
)),
- border_radius: 10,
+ border_radius: 10.0,
text_color: Color::WHITE,
..button::Style::default()
}
@@ -627,7 +633,7 @@ mod style {
background: Some(Background::Color(Color::from_rgb(
0.8, 0.2, 0.2,
))),
- border_radius: 5,
+ border_radius: 5.0,
text_color: Color::WHITE,
shadow_offset: Vector::new(1.0, 1.0),
..button::Style::default()
diff --git a/examples/tooltip/Cargo.toml b/examples/tooltip/Cargo.toml
new file mode 100644
index 00000000..1171de00
--- /dev/null
+++ b/examples/tooltip/Cargo.toml
@@ -0,0 +1,9 @@
+[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"] }
diff --git a/examples/tooltip/README.md b/examples/tooltip/README.md
new file mode 100644
index 00000000..4ccf6578
--- /dev/null
+++ b/examples/tooltip/README.md
@@ -0,0 +1,14 @@
+## 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
diff --git a/examples/tooltip/src/main.rs b/examples/tooltip/src/main.rs
new file mode 100644
index 00000000..d6c8b8e1
--- /dev/null
+++ b/examples/tooltip/src/main.rs
@@ -0,0 +1,138 @@
+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()
+ }
+ }
+ }
+}
diff --git a/examples/tour/Cargo.toml b/examples/tour/Cargo.toml
index 96749e90..bc7fac11 100644
--- a/examples/tour/Cargo.toml
+++ b/examples/tour/Cargo.toml
@@ -7,4 +7,4 @@ publish = false
[dependencies]
iced = { path = "../..", features = ["image", "debug"] }
-env_logger = "0.7"
+env_logger = "0.8"
diff --git a/examples/tour/src/main.rs b/examples/tour/src/main.rs
index ec464801..1215f83d 100644
--- a/examples/tour/src/main.rs
+++ b/examples/tour/src/main.rs
@@ -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,
+ Sandbox, Scrollable, Settings, Slider, Space, Text, TextInput, Toggler,
};
pub fn main() -> iced::Result {
@@ -135,6 +135,9 @@ impl Steps {
color: Color::BLACK,
},
Step::Radio { selection: None },
+ Step::Toggler {
+ can_continue: false,
+ },
Step::Image {
width: 300,
slider: slider::State::new(),
@@ -206,6 +209,9 @@ enum Step {
Radio {
selection: Option<Language>,
},
+ Toggler {
+ can_continue: bool,
+ },
Image {
width: u16,
slider: slider::State,
@@ -232,6 +238,7 @@ pub enum StepMessage {
InputChanged(String),
ToggleSecureInput(bool),
DebugToggled(bool),
+ TogglerChanged(bool),
}
impl<'a> Step {
@@ -287,6 +294,11 @@ impl<'a> Step {
*is_secure = toggle;
}
}
+ StepMessage::TogglerChanged(value) => {
+ if let Step::Toggler { can_continue, .. } = self {
+ *can_continue = value;
+ }
+ }
};
}
@@ -294,6 +306,7 @@ impl<'a> Step {
match self {
Step::Welcome => "Welcome",
Step::Radio { .. } => "Radio button",
+ Step::Toggler { .. } => "Toggler",
Step::Slider { .. } => "Slider",
Step::Text { .. } => "Text",
Step::Image { .. } => "Image",
@@ -309,6 +322,7 @@ 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,
@@ -324,6 +338,7 @@ 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,
@@ -545,6 +560,21 @@ 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,
@@ -689,7 +719,7 @@ fn ferris<'a>(width: u16) -> Container<'a, StepMessage> {
.center_x()
}
-fn button<'a, Message>(
+fn button<'a, Message: Clone>(
state: &'a mut button::State,
label: &str,
) -> Button<'a, Message> {
@@ -769,7 +799,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,
+ border_radius: 12.0,
shadow_offset: Vector::new(1.0, 1.0),
text_color: Color::from_rgb8(0xEE, 0xEE, 0xEE),
..button::Style::default()
diff --git a/examples/url_handler/Cargo.toml b/examples/url_handler/Cargo.toml
new file mode 100644
index 00000000..911b2f25
--- /dev/null
+++ b/examples/url_handler/Cargo.toml
@@ -0,0 +1,10 @@
+[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" } \ No newline at end of file
diff --git a/examples/url_handler/src/main.rs b/examples/url_handler/src/main.rs
new file mode 100644
index 00000000..f14e5227
--- /dev/null
+++ b/examples/url_handler/src/main.rs
@@ -0,0 +1,73 @@
+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()
+ }
+}
diff --git a/futures/Cargo.toml b/futures/Cargo.toml
index 275d0391..3cea6e1a 100644
--- a/futures/Cargo.toml
+++ b/futures/Cargo.toml
@@ -1,6 +1,6 @@
[package]
name = "iced_futures"
-version = "0.1.2"
+version = "0.3.0"
authors = ["Héctor Ramón Jiménez <hector0193@gmail.com>"]
edition = "2018"
description = "Commands, subscriptions, and runtimes for Iced"
@@ -19,16 +19,27 @@ log = "0.4"
[dependencies.futures]
version = "0.3"
-[target.'cfg(not(target_arch = "wasm32"))'.dependencies.tokio]
+[target.'cfg(not(target_arch = "wasm32"))'.dependencies.tokio_old]
+package = "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"
diff --git a/futures/src/command.rs b/futures/src/command.rs
index 063e9b68..b06ab3f8 100644
--- a/futures/src/command.rs
+++ b/futures/src/command.rs
@@ -5,9 +5,6 @@ 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>>,
}
@@ -16,8 +13,6 @@ 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(),
@@ -25,8 +20,6 @@ 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,
@@ -38,8 +31,6 @@ 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,
@@ -51,8 +42,6 @@ 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,
@@ -78,8 +67,6 @@ 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
@@ -105,8 +92,6 @@ 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
@@ -117,8 +102,6 @@ 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
}
diff --git a/futures/src/executor.rs b/futures/src/executor.rs
index cbd34ee8..b35b5bc1 100644
--- a/futures/src/executor.rs
+++ b/futures/src/executor.rs
@@ -7,9 +7,15 @@ 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;
@@ -21,9 +27,15 @@ 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;
@@ -32,21 +44,15 @@ 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);
@@ -56,8 +62,6 @@ 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()
}
diff --git a/futures/src/executor/smol.rs b/futures/src/executor/smol.rs
new file mode 100644
index 00000000..deafd43a
--- /dev/null
+++ b/futures/src/executor/smol.rs
@@ -0,0 +1,18 @@
+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();
+ }
+}
diff --git a/futures/src/executor/tokio.rs b/futures/src/executor/tokio.rs
index a730bce8..c6a21cec 100644
--- a/futures/src/executor/tokio.rs
+++ b/futures/src/executor/tokio.rs
@@ -16,6 +16,7 @@ impl Executor for Tokio {
}
fn enter<R>(&self, f: impl FnOnce() -> R) -> R {
- tokio::runtime::Runtime::enter(self, f)
+ let _guard = tokio::runtime::Runtime::enter(self);
+ f()
}
}
diff --git a/futures/src/executor/tokio_old.rs b/futures/src/executor/tokio_old.rs
new file mode 100644
index 00000000..d64729fa
--- /dev/null
+++ b/futures/src/executor/tokio_old.rs
@@ -0,0 +1,21 @@
+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)
+ }
+}
diff --git a/futures/src/lib.rs b/futures/src/lib.rs
index 46fc59fc..01cf5c89 100644
--- a/futures/src/lib.rs
+++ b/futures/src/lib.rs
@@ -1,4 +1,6 @@
//! Asynchronous tasks for GUI programming, inspired by Elm.
+//!
+//! ![The foundations of the Iced ecosystem](https://github.com/hecrj/iced/blob/0525d76ff94e828b7b21634fa94a747022001c83/docs/graphs/foundations.png?raw=true)
#![deny(missing_docs)]
#![deny(missing_debug_implementations)]
#![deny(unused_results)]
@@ -15,10 +17,22 @@ pub mod executor;
pub mod subscription;
#[cfg(all(
- any(feature = "tokio", feature = "async-std"),
+ any(
+ feature = "tokio",
+ feature = "tokio_old",
+ feature = "async-std",
+ feature = "smol"
+ ),
not(target_arch = "wasm32")
))]
-#[cfg_attr(docsrs, doc(cfg(any(feature = "tokio", feature = "async-std"))))]
+#[cfg_attr(
+ docsrs,
+ doc(cfg(any(
+ feature = "tokio",
+ feature = "async-std",
+ feature = "smol"
+ )))
+)]
pub mod time;
pub use command::Command;
diff --git a/futures/src/runtime.rs b/futures/src/runtime.rs
index d204670b..e56a4eb0 100644
--- a/futures/src/runtime.rs
+++ b/futures/src/runtime.rs
@@ -8,11 +8,6 @@ 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,
@@ -36,8 +31,6 @@ 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,
@@ -50,10 +43,6 @@ 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)
}
@@ -62,9 +51,6 @@ 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};
@@ -88,9 +74,7 @@ where
/// It will spawn new streams or close old ones as necessary! See
/// [`Tracker::update`] to learn more about this!
///
- /// [`Subscription`]: subscription/struct.Subscription.html
- /// [`Runtime`]: struct.Runtime.html
- /// [`Tracker::update`]: subscription/struct.Tracker.html#method.update
+ /// [`Tracker::update`]: subscription::Tracker::update
pub fn track(
&mut self,
subscription: Subscription<Hasher, Event, Message>,
@@ -115,9 +99,7 @@ where
///
/// See [`Tracker::broadcast`] to learn more.
///
- /// [`Runtime`]: struct.Runtime.html
- /// [`Tracker::broadcast`]:
- /// subscription/struct.Tracker.html#method.broadcast
+ /// [`Tracker::broadcast`]: subscription::Tracker::broadcast
pub fn broadcast(&mut self, event: Event) {
self.subscriptions.broadcast(event);
}
diff --git a/futures/src/subscription.rs b/futures/src/subscription.rs
index 7a75fc31..e60ad79a 100644
--- a/futures/src/subscription.rs
+++ b/futures/src/subscription.rs
@@ -19,8 +19,7 @@ use crate::BoxStream;
/// This type is normally aliased by runtimes with a specific `Event` and/or
/// `Hasher`.
///
-/// [`Command`]: ../struct.Command.html
-/// [`Subscription`]: struct.Subscription.html
+/// [`Command`]: crate::Command
pub struct Subscription<Hasher, Event, Output> {
recipes: Vec<Box<dyn Recipe<Hasher, Event, Output = Output>>>,
}
@@ -30,8 +29,6 @@ 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(),
@@ -39,9 +36,6 @@ 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 {
@@ -52,8 +46,6 @@ 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 {
@@ -66,8 +58,6 @@ 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
}
@@ -75,12 +65,6 @@ 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,
@@ -101,26 +85,19 @@ where
}
/// Transforms the [`Subscription`] output with the given function.
- ///
- /// [`Subscription`]: struct.Subscription.html
- pub fn map<A>(
- mut self,
- f: impl Fn(O) -> A + Send + Sync + 'static,
- ) -> Subscription<H, E, A>
+ pub fn map<A>(mut self, f: fn(O) -> A) -> 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, function.clone()))
+ Box::new(Map::new(recipe, f))
as Box<dyn Recipe<H, E, Output = A>>
})
.collect(),
@@ -140,9 +117,6 @@ 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`]:
///
@@ -151,23 +125,17 @@ 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.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
+/// [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
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
@@ -175,9 +143,6 @@ 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>,
@@ -186,13 +151,13 @@ pub trait Recipe<Hasher: std::hash::Hasher, Event> {
struct Map<Hasher, Event, A, B> {
recipe: Box<dyn Recipe<Hasher, Event, Output = A>>,
- mapper: std::sync::Arc<dyn Fn(A) -> B + Send + Sync>,
+ mapper: fn(A) -> B,
}
impl<H, E, A, B> Map<H, E, A, B> {
fn new(
recipe: Box<dyn Recipe<H, E, Output = A>>,
- mapper: std::sync::Arc<dyn Fn(A) -> B + Send + Sync + 'static>,
+ mapper: fn(A) -> B,
) -> Self {
Map { recipe, mapper }
}
@@ -209,8 +174,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> {
diff --git a/futures/src/subscription/tracker.rs b/futures/src/subscription/tracker.rs
index c2a0d0f1..3a8d4a87 100644
--- a/futures/src/subscription/tracker.rs
+++ b/futures/src/subscription/tracker.rs
@@ -26,8 +26,6 @@ where
Event: 'static + Send + Clone,
{
/// Creates a new empty [`Tracker`].
- ///
- /// [`Tracker`]: struct.Tracker.html
pub fn new() -> Self {
Self {
subscriptions: HashMap::new(),
@@ -52,9 +50,7 @@ where
/// It returns a list of futures that need to be spawned to materialize
/// the [`Tracker`] changes.
///
- /// [`Tracker`]: struct.Tracker.html
- /// [`Subscription`]: struct.Subscription.html
- /// [`Recipe`]: trait.Recipe.html
+ /// [`Recipe`]: crate::subscription::Recipe
pub fn update<Message, Receiver>(
&mut self,
subscription: Subscription<Hasher, Event, Message>,
@@ -132,14 +128,14 @@ where
/// This method publishes the given event to all the subscription streams
/// currently open.
///
- /// [`Recipe::stream`]: trait.Recipe.html#tymethod.stream
+ /// [`Recipe::stream`]: crate::subscription::Recipe::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::error!(
+ log::warn!(
"Error sending event to subscription: {:?}",
error
);
diff --git a/futures/src/time.rs b/futures/src/time.rs
index e87b4a83..86b4a4e7 100644
--- a/futures/src/time.rs
+++ b/futures/src/time.rs
@@ -5,8 +5,6 @@ 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> {
@@ -15,6 +13,33 @@ 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
@@ -41,7 +66,10 @@ where
}
}
-#[cfg(all(feature = "tokio", not(feature = "async-std")))]
+#[cfg(all(
+ any(feature = "tokio", feature = "tokio_old"),
+ not(any(feature = "async-std", feature = "smol"))
+))]
impl<H, E> subscription::Recipe<H, E> for Every
where
H: std::hash::Hasher,
@@ -61,10 +89,25 @@ 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;
- tokio::time::interval_at(start, self.0)
- .map(|_| std::time::Instant::now())
- .boxed()
+ 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()
}
}
diff --git a/glow/Cargo.toml b/glow/Cargo.toml
index 11ca80e2..e40b8ba8 100644
--- a/glow/Cargo.toml
+++ b/glow/Cargo.toml
@@ -1,6 +1,6 @@
[package]
name = "iced_glow"
-version = "0.1.0"
+version = "0.2.0"
authors = ["Héctor Ramón Jiménez <hector0193@gmail.com>"]
edition = "2018"
description = "A glow renderer for iced"
@@ -9,25 +9,26 @@ 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.5"
-glow_glyph = "0.3"
+glow = "0.6"
+glow_glyph = "0.4"
glyph_brush = "0.7"
-euclid = "0.20"
-bytemuck = "1.2"
+euclid = "0.22"
+bytemuck = "1.4"
log = "0.4"
[dependencies.iced_native]
-version = "0.2"
+version = "0.4"
path = "../native"
[dependencies.iced_graphics]
-version = "0.1"
+version = "0.2"
path = "../graphics"
features = ["font-fallback", "font-icons", "opengl"]
diff --git a/glow/src/backend.rs b/glow/src/backend.rs
index e1685816..1680fc00 100644
--- a/glow/src/backend.rs
+++ b/glow/src/backend.rs
@@ -23,10 +23,13 @@ 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 text_pipeline = text::Pipeline::new(
+ gl,
+ settings.default_font,
+ settings.text_multithreading,
+ );
+
let quad_pipeline = quad::Pipeline::new(gl);
let triangle_pipeline = triangle::Pipeline::new(gl);
diff --git a/glow/src/lib.rs b/glow/src/lib.rs
index 5011da8e..98faf24c 100644
--- a/glow/src/lib.rs
+++ b/glow/src/lib.rs
@@ -1,5 +1,7 @@
//! A [`glow`] renderer for [`iced_native`].
//!
+//! ![The native path of the Iced ecosystem](https://github.com/hecrj/iced/blob/0525d76ff94e828b7b21634fa94a747022001c83/docs/graphs/native.png?raw=true)
+//!
//! [`glow`]: https://github.com/grovesNL/glow
//! [`iced_native`]: https://github.com/hecrj/iced/tree/master/native
#![deny(missing_docs)]
diff --git a/glow/src/program.rs b/glow/src/program.rs
index 489a194f..601f9ce6 100644
--- a/glow/src/program.rs
+++ b/glow/src/program.rs
@@ -17,7 +17,7 @@ pub unsafe fn create(
gl.compile_shader(shader);
if !gl.get_shader_compile_status(shader) {
- panic!(gl.get_shader_info_log(shader));
+ panic!("{}", gl.get_shader_info_log(shader));
}
gl.attach_shader(program, shader);
@@ -27,7 +27,7 @@ pub unsafe fn create(
gl.link_program(program);
if !gl.get_program_link_status(program) {
- panic!(gl.get_program_info_log(program));
+ panic!("{}", gl.get_program_info_log(program));
}
for shader in shaders {
diff --git a/glow/src/settings.rs b/glow/src/settings.rs
index c2c605ef..f3dddfaf 100644
--- a/glow/src/settings.rs
+++ b/glow/src/settings.rs
@@ -1,9 +1,9 @@
//! Configure a renderer.
pub use iced_graphics::Antialiasing;
-/// The settings of a [`Renderer`].
+/// The settings of a [`Backend`].
///
-/// [`Renderer`]: ../struct.Renderer.html
+/// [`Backend`]: crate::Backend
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Settings {
/// The bytes of the font that will be used by default.
@@ -16,7 +16,15 @@ pub struct Settings {
/// By default, it will be set to 20.
pub default_text_size: u16,
+ /// If enabled, spread text workload in multiple threads when multiple cores
+ /// are available.
+ ///
+ /// By default, it is disabled.
+ pub text_multithreading: bool,
+
/// The antialiasing strategy that will be used for triangle primitives.
+ ///
+ /// By default, it is `None`.
pub antialiasing: Option<Antialiasing>,
}
@@ -25,7 +33,17 @@ impl Default for Settings {
Settings {
default_font: None,
default_text_size: 20,
+ text_multithreading: false,
antialiasing: None,
}
}
}
+
+impl Settings {
+ /// Creates new [`Settings`] using environment configuration.
+ ///
+ /// Currently, this is equivalent to calling [`Settings::default`].
+ pub fn from_env() -> Self {
+ Self::default()
+ }
+}
diff --git a/glow/src/shader/quad.vert b/glow/src/shader/quad.vert
index d37b5c8d..82417856 100644
--- a/glow/src/shader/quad.vert
+++ b/glow/src/shader/quad.vert
@@ -29,6 +29,11 @@ void main() {
vec2 p_Pos = i_Pos * u_Scale;
vec2 p_Scale = i_Scale * u_Scale;
+ float i_BorderRadius = min(
+ i_BorderRadius,
+ min(i_Scale.x, i_Scale.y) / 2.0
+ );
+
mat4 i_Transform = mat4(
vec4(p_Scale.x + 1.0, 0.0, 0.0, 0.0),
vec4(0.0, p_Scale.y + 1.0, 0.0, 0.0),
diff --git a/glow/src/text.rs b/glow/src/text.rs
index 925c7287..a4c39dfe 100644
--- a/glow/src/text.rs
+++ b/glow/src/text.rs
@@ -11,7 +11,11 @@ pub struct Pipeline {
}
impl Pipeline {
- pub fn new(gl: &glow::Context, default_font: Option<&[u8]>) -> Self {
+ pub fn new(
+ gl: &glow::Context,
+ default_font: Option<&[u8]>,
+ multithreading: bool,
+ ) -> Self {
let default_font = default_font.map(|slice| slice.to_vec());
// TODO: Font customization
@@ -41,7 +45,7 @@ impl Pipeline {
let draw_brush =
glow_glyph::GlyphBrushBuilder::using_font(font.clone())
.initial_cache_size((2048, 2048))
- .draw_cache_multithread(false) // TODO: Expose as a configuration flag
+ .draw_cache_multithread(multithreading)
.build(&gl);
let measure_brush =
diff --git a/glow/src/widget.rs b/glow/src/widget.rs
index 0e33909d..a77511e8 100644
--- a/glow/src/widget.rs
+++ b/glow/src/widget.rs
@@ -20,6 +20,8 @@ pub mod rule;
pub mod scrollable;
pub mod slider;
pub mod text_input;
+pub mod toggler;
+pub mod tooltip;
#[doc(no_inline)]
pub use button::Button;
@@ -43,6 +45,10 @@ pub use scrollable::Scrollable;
pub use slider::Slider;
#[doc(no_inline)]
pub use text_input::TextInput;
+#[doc(no_inline)]
+pub use toggler::Toggler;
+#[doc(no_inline)]
+pub use tooltip::Tooltip;
#[cfg(feature = "canvas")]
#[cfg_attr(docsrs, doc(cfg(feature = "canvas")))]
@@ -52,6 +58,14 @@ pub mod canvas;
#[doc(no_inline)]
pub use canvas::Canvas;
+#[cfg(feature = "qr_code")]
+#[cfg_attr(docsrs, doc(cfg(feature = "qr_code")))]
+pub mod qr_code;
+
+#[cfg(feature = "qr_code")]
+#[doc(no_inline)]
+pub use qr_code::QRCode;
+
pub use iced_native::{Image, Space};
/// A container that distributes its contents vertically.
diff --git a/glow/src/widget/button.rs b/glow/src/widget/button.rs
index fee7a7f8..fc729cd5 100644
--- a/glow/src/widget/button.rs
+++ b/glow/src/widget/button.rs
@@ -1,9 +1,6 @@
//! Allow your users to perform actions by pressing a button.
//!
//! A [`Button`] has some local [`State`].
-//!
-//! [`Button`]: type.Button.html
-//! [`State`]: struct.State.html
use crate::Renderer;
pub use iced_graphics::button::{Style, StyleSheet};
diff --git a/glow/src/widget/canvas.rs b/glow/src/widget/canvas.rs
index bef34857..399dd19c 100644
--- a/glow/src/widget/canvas.rs
+++ b/glow/src/widget/canvas.rs
@@ -3,7 +3,4 @@
//! A [`Canvas`] widget can be used to draw different kinds of 2D shapes in a
//! [`Frame`]. It can be used for animation, data visualization, game graphics,
//! and more!
-//!
-//! [`Canvas`]: struct.Canvas.html
-//! [`Frame`]: struct.Frame.html
pub use iced_graphics::canvas::*;
diff --git a/glow/src/widget/pane_grid.rs b/glow/src/widget/pane_grid.rs
index 3c47b562..fc36862c 100644
--- a/glow/src/widget/pane_grid.rs
+++ b/glow/src/widget/pane_grid.rs
@@ -6,13 +6,12 @@
//! The [`pane_grid` example] showcases how to use a [`PaneGrid`] with resizing,
//! drag and drop, and hotkey support.
//!
-//! [`pane_grid` example]: https://github.com/hecrj/iced/tree/0.1/examples/pane_grid
-//! [`PaneGrid`]: type.PaneGrid.html
+//! [`pane_grid` example]: https://github.com/hecrj/iced/tree/0.3/examples/pane_grid
use crate::Renderer;
-pub use iced_native::pane_grid::{
- Axis, Configuration, Direction, DragEvent, Focus, KeyPressEvent, Node,
- Pane, ResizeEvent, Split, State,
+pub use iced_graphics::pane_grid::{
+ Axis, Configuration, Direction, DragEvent, Line, Node, Pane, ResizeEvent,
+ Split, State, StyleSheet,
};
/// A collection of panes distributed using either vertical or horizontal splits
@@ -24,13 +23,9 @@ pub use iced_native::pane_grid::{
pub type PaneGrid<'a, Message> = iced_native::PaneGrid<'a, Message, Renderer>;
/// The content of a [`Pane`].
-///
-/// [`Pane`]: struct.Pane.html
pub type Content<'a, Message> =
iced_native::pane_grid::Content<'a, Message, Renderer>;
/// The title bar of a [`Pane`].
-///
-/// [`Pane`]: struct.Pane.html
pub type TitleBar<'a, Message> =
iced_native::pane_grid::TitleBar<'a, Message, Renderer>;
diff --git a/glow/src/widget/progress_bar.rs b/glow/src/widget/progress_bar.rs
index a636a3a6..45a25d00 100644
--- a/glow/src/widget/progress_bar.rs
+++ b/glow/src/widget/progress_bar.rs
@@ -2,8 +2,6 @@
//!
//! A [`ProgressBar`] has a range of possible values and a current value,
//! as well as a length, height and style.
-//!
-//! [`ProgressBar`]: type.ProgressBar.html
use crate::Renderer;
pub use iced_graphics::progress_bar::{Style, StyleSheet};
diff --git a/glow/src/widget/qr_code.rs b/glow/src/widget/qr_code.rs
new file mode 100644
index 00000000..7b1c2408
--- /dev/null
+++ b/glow/src/widget/qr_code.rs
@@ -0,0 +1,2 @@
+//! Encode and display information in a QR code.
+pub use iced_graphics::qr_code::*;
diff --git a/glow/src/widget/slider.rs b/glow/src/widget/slider.rs
index 3a8c2595..9a269858 100644
--- a/glow/src/widget/slider.rs
+++ b/glow/src/widget/slider.rs
@@ -1,9 +1,6 @@
//! Display an interactive selector of a single value from a range of values.
//!
//! A [`Slider`] has some local [`State`].
-//!
-//! [`Slider`]: struct.Slider.html
-//! [`State`]: struct.State.html
use crate::Renderer;
pub use iced_graphics::slider::{Handle, HandleShape, Style, StyleSheet};
diff --git a/glow/src/widget/text_input.rs b/glow/src/widget/text_input.rs
index 1da3fbe6..db18b1cc 100644
--- a/glow/src/widget/text_input.rs
+++ b/glow/src/widget/text_input.rs
@@ -1,9 +1,6 @@
//! Display fields that can be filled with text.
//!
//! A [`TextInput`] has some local [`State`].
-//!
-//! [`TextInput`]: struct.TextInput.html
-//! [`State`]: struct.State.html
use crate::Renderer;
pub use iced_graphics::text_input::{Style, StyleSheet};
diff --git a/glow/src/widget/toggler.rs b/glow/src/widget/toggler.rs
new file mode 100644
index 00000000..1cd8711b
--- /dev/null
+++ b/glow/src/widget/toggler.rs
@@ -0,0 +1,9 @@
+//! Show toggle controls using togglers.
+use crate::Renderer;
+
+pub use iced_graphics::toggler::{Style, StyleSheet};
+
+/// A toggler that can be toggled.
+///
+/// This is an alias of an `iced_native` checkbox with an `iced_wgpu::Renderer`.
+pub type Toggler<Message> = iced_native::Toggler<Message, Renderer>;
diff --git a/glow/src/widget/tooltip.rs b/glow/src/widget/tooltip.rs
new file mode 100644
index 00000000..89ab3a15
--- /dev/null
+++ b/glow/src/widget/tooltip.rs
@@ -0,0 +1,6 @@
+//! Display a widget over another.
+/// A widget allowing the selection of a single value from a list of options.
+pub type Tooltip<'a, Message> =
+ iced_native::Tooltip<'a, Message, crate::Renderer>;
+
+pub use iced_native::tooltip::Position;
diff --git a/glutin/Cargo.toml b/glutin/Cargo.toml
index 4652112c..b2a7f307 100644
--- a/glutin/Cargo.toml
+++ b/glutin/Cargo.toml
@@ -1,6 +1,6 @@
[package]
name = "iced_glutin"
-version = "0.1.0"
+version = "0.2.0"
authors = ["Héctor Ramón Jiménez <hector0193@gmail.com>"]
edition = "2018"
description = "A glutin runtime for Iced"
@@ -13,18 +13,20 @@ categories = ["gui"]
[features]
debug = ["iced_winit/debug"]
-[dependencies]
-glutin = "0.24"
+[dependencies.glutin]
+version = "0.27"
+git = "https://github.com/iced-rs/glutin"
+rev = "03437d8a1826d83c62017b2bb7bf18bfc9e352cc"
[dependencies.iced_native]
-version = "0.2"
+version = "0.4"
path = "../native"
[dependencies.iced_winit]
-version = "0.1"
+version = "0.3"
path = "../winit"
[dependencies.iced_graphics]
-version = "0.1"
+version = "0.2"
path = "../graphics"
features = ["opengl"]
diff --git a/glutin/README.md b/glutin/README.md
index 34dec1b3..fcae157e 100644
--- a/glutin/README.md
+++ b/glutin/README.md
@@ -1,24 +1,26 @@
-# `iced_winit`
-[![Documentation](https://docs.rs/iced_winit/badge.svg)][documentation]
-[![Crates.io](https://img.shields.io/crates/v/iced_winit.svg)](https://crates.io/crates/iced_winit)
-[![License](https://img.shields.io/crates/l/iced_winit.svg)](https://github.com/hecrj/iced/blob/master/LICENSE)
+# `iced_glutin`
+[![Documentation](https://docs.rs/iced_glutin/badge.svg)][documentation]
+[![Crates.io](https://img.shields.io/crates/v/iced_glutin.svg)](https://crates.io/crates/iced_glutin)
+[![License](https://img.shields.io/crates/l/iced_glutin.svg)](https://github.com/hecrj/iced/blob/master/LICENSE)
[![project chat](https://img.shields.io/badge/chat-on_zulip-brightgreen.svg)](https://iced.zulipchat.com)
-`iced_winit` offers some convenient abstractions on top of [`iced_native`] to quickstart development when using [`winit`].
+`iced_glutin` offers some convenient abstractions on top of [`iced_native`] to quickstart development when using [`glutin`].
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.
-![iced_winit](../docs/graphs/winit.png)
+<p align="center">
+ <img alt="The native target" src="../docs/graphs/native.png" width="80%">
+</p>
-[documentation]: https://docs.rs/iced_winit
+[documentation]: https://docs.rs/iced_glutin
[`iced_native`]: ../native
-[`winit`]: https://github.com/rust-windowing/winit
+[`glutin`]: https://github.com/rust-windowing/glutin
## Installation
-Add `iced_winit` as a dependency in your `Cargo.toml`:
+Add `iced_glutin` as a dependency in your `Cargo.toml`:
```toml
-iced_winit = "0.1"
+iced_glutin = "0.2"
```
__Iced moves fast and the `master` branch can contain breaking changes!__ If
diff --git a/glutin/src/application.rs b/glutin/src/application.rs
index fe6ad99d..4a5f4bd2 100644
--- a/glutin/src/application.rs
+++ b/glutin/src/application.rs
@@ -1,18 +1,20 @@
//! Create interactive, native cross-platform applications.
-use crate::{mouse, Error, Executor, Runtime, Size};
+use crate::{mouse, Error, Executor, Runtime};
+
+pub use iced_winit::Application;
+
use iced_graphics::window;
-use iced_graphics::Viewport;
use iced_winit::application;
use iced_winit::conversion;
-use iced_winit::{Clipboard, Debug, Proxy, Settings};
+use iced_winit::futures;
+use iced_winit::futures::channel::mpsc;
+use iced_winit::{Cache, Clipboard, Debug, Proxy, Settings};
-pub use iced_winit::Application;
-pub use iced_winit::{program, Program};
+use glutin::window::Window;
+use std::mem::ManuallyDrop;
/// Runs an [`Application`] with an executor, compositor, and the provided
/// settings.
-///
-/// [`Application`]: trait.Application.html
pub fn run<A, E, C>(
settings: Settings<A::Flags>,
compositor_settings: C::Settings,
@@ -22,11 +24,10 @@ where
E: Executor + 'static,
C: window::GLCompositor<Renderer = A::Renderer> + 'static,
{
- use glutin::{
- event,
- event_loop::{ControlFlow, EventLoop},
- ContextBuilder,
- };
+ use futures::task;
+ use futures::Future;
+ use glutin::event_loop::EventLoop;
+ use glutin::ContextBuilder;
let mut debug = Debug::new();
debug.startup_started();
@@ -39,24 +40,27 @@ where
Runtime::new(executor, proxy)
};
- let flags = settings.flags;
- let (application, init_command) = runtime.enter(|| A::new(flags));
- runtime.spawn(init_command);
+ let (application, init_command) = {
+ let flags = settings.flags;
+
+ runtime.enter(|| A::new(flags))
+ };
let subscription = application.subscription();
- runtime.track(subscription);
- let mut title = application.title();
- let mut mode = application.mode();
- let mut background_color = application.background_color();
- let mut scale_factor = application.scale_factor();
+ runtime.spawn(init_command);
+ runtime.track(subscription);
let context = {
- let builder = settings.window.into_builder(
- &title,
- mode,
- event_loop.primary_monitor(),
- );
+ let builder = settings
+ .window
+ .into_builder(
+ &application.title(),
+ application.mode(),
+ event_loop.primary_monitor(),
+ settings.id,
+ )
+ .with_menu(Some(conversion::menu(&application.menu())));
let context = ContextBuilder::new()
.with_vsync(true)
@@ -79,189 +83,274 @@ where
}
};
- let clipboard = Clipboard::new(&context.window());
- let mut cursor_position = glutin::dpi::PhysicalPosition::new(-1.0, -1.0);
- let mut mouse_interaction = mouse::Interaction::default();
- let mut modifiers = glutin::event::ModifiersState::default();
-
- let physical_size = context.window().inner_size();
- let mut viewport = Viewport::with_physical_size(
- Size::new(physical_size.width, physical_size.height),
- context.window().scale_factor() * scale_factor,
- );
- let mut resized = false;
-
#[allow(unsafe_code)]
- let (mut compositor, mut renderer) = unsafe {
+ let (compositor, renderer) = unsafe {
C::new(compositor_settings, |address| {
context.get_proc_address(address)
})?
};
- let mut state = program::State::new(
+ let (mut sender, receiver) = mpsc::unbounded();
+
+ let mut instance = Box::pin(run_instance::<A, E, C>(
application,
- viewport.logical_size(),
- conversion::cursor_position(cursor_position, viewport.scale_factor()),
- &mut renderer,
- &mut debug,
- );
- debug.startup_finished();
+ compositor,
+ renderer,
+ runtime,
+ debug,
+ receiver,
+ context,
+ settings.exit_on_close_request,
+ ));
+
+ let mut context = task::Context::from_waker(task::noop_waker_ref());
+
+ event_loop.run(move |event, _, control_flow| {
+ use glutin::event_loop::ControlFlow;
+
+ if let ControlFlow::Exit = control_flow {
+ return;
+ }
- event_loop.run(move |event, _, control_flow| match event {
- event::Event::MainEventsCleared => {
- if state.is_queue_empty() {
- return;
- }
+ let event = match event {
+ glutin::event::Event::WindowEvent {
+ event:
+ glutin::event::WindowEvent::ScaleFactorChanged {
+ new_inner_size,
+ ..
+ },
+ window_id,
+ } => Some(glutin::event::Event::WindowEvent {
+ event: glutin::event::WindowEvent::Resized(*new_inner_size),
+ window_id,
+ }),
+ _ => event.to_static(),
+ };
+
+ if let Some(event) = event {
+ sender.start_send(event).expect("Send event");
+
+ let poll = instance.as_mut().poll(&mut context);
+
+ *control_flow = match poll {
+ task::Poll::Pending => ControlFlow::Wait,
+ task::Poll::Ready(_) => ControlFlow::Exit,
+ };
+ }
+ });
+}
- let command = runtime.enter(|| {
- state.update(
- viewport.logical_size(),
- conversion::cursor_position(
- cursor_position,
- viewport.scale_factor(),
- ),
- clipboard.as_ref().map(|c| c as _),
- &mut renderer,
- &mut debug,
- )
- });
+async fn run_instance<A, E, C>(
+ mut application: A,
+ mut compositor: C,
+ mut renderer: A::Renderer,
+ mut runtime: Runtime<E, Proxy<A::Message>, A::Message>,
+ mut debug: Debug,
+ mut receiver: mpsc::UnboundedReceiver<glutin::event::Event<'_, A::Message>>,
+ mut context: glutin::ContextWrapper<glutin::PossiblyCurrent, Window>,
+ exit_on_close_request: bool,
+) where
+ A: Application + 'static,
+ E: Executor + 'static,
+ C: window::GLCompositor<Renderer = A::Renderer> + 'static,
+{
+ use glutin::event;
+ use iced_winit::futures::stream::StreamExt;
+
+ let mut clipboard = Clipboard::connect(context.window());
+
+ let mut state = application::State::new(&application, context.window());
+ let mut viewport_version = state.viewport_version();
+ let mut user_interface =
+ ManuallyDrop::new(application::build_user_interface(
+ &mut application,
+ Cache::default(),
+ &mut renderer,
+ state.logical_size(),
+ &mut debug,
+ ));
+
+ let mut primitive =
+ user_interface.draw(&mut renderer, state.cursor_position());
+ let mut mouse_interaction = mouse::Interaction::default();
- // If the application was updated
- if let Some(command) = command {
- runtime.spawn(command);
+ let mut events = Vec::new();
+ let mut messages = Vec::new();
- let program = state.program();
+ debug.startup_finished();
+
+ while let Some(event) = receiver.next().await {
+ match event {
+ event::Event::MainEventsCleared => {
+ if events.is_empty() && messages.is_empty() {
+ continue;
+ }
- // Update subscriptions
- let subscription = program.subscription();
- runtime.track(subscription);
+ debug.event_processing_started();
- // Update window title
- let new_title = program.title();
+ let statuses = user_interface.update(
+ &events,
+ state.cursor_position(),
+ &mut renderer,
+ &mut clipboard,
+ &mut messages,
+ );
- if title != new_title {
- context.window().set_title(&new_title);
+ debug.event_processing_finished();
- title = new_title;
+ for event in events.drain(..).zip(statuses.into_iter()) {
+ runtime.broadcast(event);
}
- // Update window mode
- let new_mode = program.mode();
+ if !messages.is_empty() {
+ let cache =
+ ManuallyDrop::into_inner(user_interface).into_cache();
- if mode != new_mode {
- context.window().set_fullscreen(conversion::fullscreen(
- context.window().current_monitor(),
- new_mode,
- ));
+ // Update application
+ application::update(
+ &mut application,
+ &mut runtime,
+ &mut debug,
+ &mut clipboard,
+ &mut messages,
+ );
- mode = new_mode;
+ // Update window
+ state.synchronize(&application, context.window());
+
+ let should_exit = application.should_exit();
+
+ user_interface =
+ ManuallyDrop::new(application::build_user_interface(
+ &mut application,
+ cache,
+ &mut renderer,
+ state.logical_size(),
+ &mut debug,
+ ));
+
+ if should_exit {
+ break;
+ }
}
- // Update background color
- background_color = program.background_color();
+ debug.draw_started();
+ primitive =
+ user_interface.draw(&mut renderer, state.cursor_position());
+ debug.draw_finished();
- // Update scale factor
- let new_scale_factor = program.scale_factor();
+ context.window().request_redraw();
+ }
+ event::Event::PlatformSpecific(event::PlatformSpecific::MacOS(
+ event::MacOS::ReceivedUrl(url),
+ )) => {
+ use iced_native::event;
+ events.push(iced_native::Event::PlatformSpecific(
+ event::PlatformSpecific::MacOS(event::MacOS::ReceivedUrl(
+ url,
+ )),
+ ));
+ }
+ event::Event::UserEvent(message) => {
+ messages.push(message);
+ }
+ event::Event::RedrawRequested(_) => {
+ debug.render_started();
+
+ #[allow(unsafe_code)]
+ unsafe {
+ if !context.is_current() {
+ context = context
+ .make_current()
+ .expect("Make OpenGL context current");
+ }
+ }
- if scale_factor != new_scale_factor {
- let size = context.window().inner_size();
+ let current_viewport_version = state.viewport_version();
- viewport = Viewport::with_physical_size(
- Size::new(size.width, size.height),
- context.window().scale_factor() * new_scale_factor,
- );
+ if viewport_version != current_viewport_version {
+ let physical_size = state.physical_size();
+ let logical_size = state.logical_size();
- // We relayout the UI with the new logical size.
- // The queue is empty, therefore this will never produce
- // a `Command`.
- //
- // TODO: Properly queue `WindowResized`
- let _ = state.update(
- viewport.logical_size(),
- conversion::cursor_position(
- cursor_position,
- viewport.scale_factor(),
- ),
- clipboard.as_ref().map(|c| c as _),
- &mut renderer,
- &mut debug,
+ debug.layout_started();
+ user_interface = ManuallyDrop::new(
+ ManuallyDrop::into_inner(user_interface)
+ .relayout(logical_size, &mut renderer),
);
+ debug.layout_finished();
- scale_factor = new_scale_factor;
- }
- }
-
- context.window().request_redraw();
- }
- event::Event::UserEvent(message) => {
- state.queue_message(message);
- }
- event::Event::RedrawRequested(_) => {
- debug.render_started();
+ debug.draw_started();
+ primitive = user_interface
+ .draw(&mut renderer, state.cursor_position());
+ debug.draw_finished();
- if resized {
- let physical_size = viewport.physical_size();
+ context.resize(glutin::dpi::PhysicalSize::new(
+ physical_size.width,
+ physical_size.height,
+ ));
- context.resize(glutin::dpi::PhysicalSize::new(
- physical_size.width,
- physical_size.height,
- ));
+ compositor.resize_viewport(physical_size);
- compositor.resize_viewport(physical_size);
+ viewport_version = current_viewport_version;
+ }
- resized = false;
- }
+ let new_mouse_interaction = compositor.draw(
+ &mut renderer,
+ state.viewport(),
+ state.background_color(),
+ &primitive,
+ &debug.overlay(),
+ );
- let new_mouse_interaction = compositor.draw(
- &mut renderer,
- &viewport,
- background_color,
- state.primitive(),
- &debug.overlay(),
- );
+ context.swap_buffers().expect("Swap buffers");
- context.swap_buffers().expect("Swap buffers");
+ debug.render_finished();
- debug.render_finished();
+ if new_mouse_interaction != mouse_interaction {
+ context.window().set_cursor_icon(
+ conversion::mouse_interaction(new_mouse_interaction),
+ );
- if new_mouse_interaction != mouse_interaction {
- context.window().set_cursor_icon(
- conversion::mouse_interaction(new_mouse_interaction),
- );
+ mouse_interaction = new_mouse_interaction;
+ }
- mouse_interaction = new_mouse_interaction;
+ // TODO: Handle animations!
+ // Maybe we can use `ControlFlow::WaitUntil` for this.
}
+ event::Event::WindowEvent {
+ event: event::WindowEvent::MenuEntryActivated(entry_id),
+ ..
+ } => {
+ if let Some(message) =
+ conversion::menu_message(state.menu(), entry_id)
+ {
+ messages.push(message);
+ }
+ }
+ event::Event::WindowEvent {
+ event: window_event,
+ ..
+ } => {
+ if application::requests_exit(&window_event, state.modifiers())
+ && exit_on_close_request
+ {
+ break;
+ }
- // TODO: Handle animations!
- // Maybe we can use `ControlFlow::WaitUntil` for this.
- }
- event::Event::WindowEvent {
- event: window_event,
- ..
- } => {
- application::handle_window_event(
- &window_event,
- context.window(),
- scale_factor,
- control_flow,
- &mut cursor_position,
- &mut modifiers,
- &mut viewport,
- &mut resized,
- &mut debug,
- );
-
- if let Some(event) = conversion::window_event(
- &window_event,
- viewport.scale_factor(),
- modifiers,
- ) {
- state.queue_event(event.clone());
- runtime.broadcast(event);
+ state.update(context.window(), &window_event, &mut debug);
+
+ if let Some(event) = conversion::window_event(
+ &window_event,
+ state.scale_factor(),
+ state.modifiers(),
+ ) {
+ events.push(event);
+ }
}
+ _ => {}
}
- _ => {
- *control_flow = ControlFlow::Wait;
- }
- })
+ }
+
+ // Manually drop the user interface
+ drop(ManuallyDrop::into_inner(user_interface));
}
diff --git a/glutin/src/lib.rs b/glutin/src/lib.rs
index 49bc2a33..107d9556 100644
--- a/glutin/src/lib.rs
+++ b/glutin/src/lib.rs
@@ -1,5 +1,7 @@
//! A windowing shell for [`iced`], on top of [`glutin`].
//!
+//! ![The native path of the Iced ecosystem](https://github.com/hecrj/iced/blob/0525d76ff94e828b7b21634fa94a747022001c83/docs/graphs/native.png?raw=true)
+//!
//! [`iced`]: https://github.com/hecrj/iced
//! [`glutin`]: https://github.com/rust-windowing/glutin
#![deny(missing_docs)]
@@ -15,7 +17,7 @@ pub use iced_native::*;
pub mod application;
pub use iced_winit::settings;
-pub use iced_winit::{Error, Mode};
+pub use iced_winit::{Clipboard, Error, Mode};
#[doc(no_inline)]
pub use application::Application;
diff --git a/graphics/Cargo.toml b/graphics/Cargo.toml
index dec24c59..ea9471c6 100644
--- a/graphics/Cargo.toml
+++ b/graphics/Cargo.toml
@@ -1,36 +1,51 @@
[package]
name = "iced_graphics"
-version = "0.1.0"
+version = "0.2.0"
authors = ["Héctor Ramón Jiménez <hector0193@gmail.com>"]
edition = "2018"
+description = "A bunch of backend-agnostic types that can be leveraged to build a renderer for Iced"
+license = "MIT"
+repository = "https://github.com/hecrj/iced"
+documentation = "https://docs.rs/iced_graphics"
+keywords = ["gui", "ui", "graphics", "interface", "widgets"]
+categories = ["gui"]
[features]
canvas = ["lyon"]
+qr_code = ["qrcode", "canvas"]
font-source = ["font-kit"]
font-fallback = []
font-icons = []
opengl = []
[dependencies]
-bytemuck = "1.2"
-glam = "0.9"
+glam = "0.10"
raw-window-handle = "0.3"
thiserror = "1.0"
+[dependencies.bytemuck]
+version = "1.4"
+features = ["derive"]
+
[dependencies.iced_native]
-version = "0.2"
+version = "0.4"
path = "../native"
[dependencies.iced_style]
-version = "0.1"
+version = "0.3"
path = "../style"
[dependencies.lyon]
-version = "0.15"
+version = "0.16"
+optional = true
+
+[dependencies.qrcode]
+version = "0.12"
optional = true
+default-features = false
[dependencies.font-kit]
-version = "0.6"
+version = "0.10"
optional = true
[package.metadata.docs.rs]
diff --git a/graphics/src/antialiasing.rs b/graphics/src/antialiasing.rs
index 34d94711..7631c97c 100644
--- a/graphics/src/antialiasing.rs
+++ b/graphics/src/antialiasing.rs
@@ -13,8 +13,6 @@ pub enum Antialiasing {
impl Antialiasing {
/// Returns the amount of samples of the [`Antialiasing`].
- ///
- /// [`Antialiasing`]: enum.Antialiasing.html
pub fn sample_count(self) -> u32 {
match self {
Antialiasing::MSAAx2 => 2,
diff --git a/graphics/src/backend.rs b/graphics/src/backend.rs
index dd7dbbc2..ed1b9e08 100644
--- a/graphics/src/backend.rs
+++ b/graphics/src/backend.rs
@@ -5,7 +5,7 @@ use iced_native::{Font, Size};
/// The graphics backend of a [`Renderer`].
///
-/// [`Renderer`]: ../struct.Renderer.html
+/// [`Renderer`]: crate::Renderer
pub trait Backend {
/// Trims the measurements cache.
///
@@ -22,12 +22,12 @@ pub trait Text {
/// The `char` representing a ✔ icon in the [`ICON_FONT`].
///
- /// [`ICON_FONT`]: #associatedconst.ICON_FONT
+ /// [`ICON_FONT`]: Self::ICON_FONT
const CHECKMARK_ICON: char;
- /// The `char` representing a â–¼ icon in the built-in [`ICONS`] font.
+ /// The `char` representing a â–¼ icon in the built-in [`ICON_FONT`].
///
- /// [`ICON_FONT`]: #associatedconst.ICON_FONT
+ /// [`ICON_FONT`]: Self::ICON_FONT
const ARROW_DOWN_ICON: char;
/// Returns the default size of text.
diff --git a/graphics/src/font.rs b/graphics/src/font.rs
index 5c62681c..d55d0faf 100644
--- a/graphics/src/font.rs
+++ b/graphics/src/font.rs
@@ -26,14 +26,10 @@ pub const ICONS: iced_native::Font = iced_native::Font::External {
};
/// The `char` representing a ✔ icon in the built-in [`ICONS`] font.
-///
-/// [`ICONS`]: const.ICONS.html
#[cfg(feature = "font-icons")]
#[cfg_attr(docsrs, doc(cfg(feature = "font-icons")))]
pub const CHECKMARK_ICON: char = '\u{F00C}';
/// The `char` representing a â–¼ icon in the built-in [`ICONS`] font.
-///
-/// [`ICONS`]: const.ICONS.html
#[cfg(feature = "font-icons")]
pub const ARROW_DOWN_ICON: char = '\u{E800}';
diff --git a/graphics/src/font/source.rs b/graphics/src/font/source.rs
index 917291ff..a2d3f51d 100644
--- a/graphics/src/font/source.rs
+++ b/graphics/src/font/source.rs
@@ -8,8 +8,6 @@ pub struct Source {
impl Source {
/// Creates a new [`Source`].
- ///
- /// [`Source`]: struct.Source.html
pub fn new() -> Self {
Source {
raw: font_kit::source::SystemSource::new(),
@@ -17,8 +15,6 @@ impl Source {
}
/// Finds and loads a font matching the set of provided family priorities.
- ///
- /// [`Source`]: struct.Source.html
pub fn load(&self, families: &[Family]) -> Result<Vec<u8>, LoadError> {
let font = self.raw.select_best_match(
families,
diff --git a/graphics/src/layer.rs b/graphics/src/layer.rs
index 6aca738e..7dce1d4c 100644
--- a/graphics/src/layer.rs
+++ b/graphics/src/layer.rs
@@ -11,35 +11,23 @@ use crate::{
#[derive(Debug, Clone)]
pub struct Layer<'a> {
/// The clipping bounds of the [`Layer`].
- ///
- /// [`Layer`]: struct.Layer.html
pub bounds: Rectangle,
/// The quads of the [`Layer`].
- ///
- /// [`Layer`]: struct.Layer.html
pub quads: Vec<Quad>,
/// The triangle meshes of the [`Layer`].
- ///
- /// [`Layer`]: struct.Layer.html
pub meshes: Vec<Mesh<'a>>,
/// The text of the [`Layer`].
- ///
- /// [`Layer`]: struct.Layer.html
pub text: Vec<Text<'a>>,
/// The images of the [`Layer`].
- ///
- /// [`Layer`]: struct.Layer.html
pub images: Vec<Image>,
}
impl<'a> Layer<'a> {
/// Creates a new [`Layer`] with the given clipping bounds.
- ///
- /// [`Layer`]: struct.Layer.html
pub fn new(bounds: Rectangle) -> Self {
Self {
bounds,
@@ -53,8 +41,6 @@ impl<'a> Layer<'a> {
/// Creates a new [`Layer`] for the provided overlay text.
///
/// This can be useful for displaying debug information.
- ///
- /// [`Layer`]: struct.Layer.html
pub fn overlay(lines: &'a [impl AsRef<str>], viewport: &Viewport) -> Self {
let mut overlay =
Layer::new(Rectangle::with_size(viewport.logical_size()));
@@ -87,8 +73,6 @@ impl<'a> Layer<'a> {
/// Distributes the given [`Primitive`] and generates a list of layers based
/// on its contents.
- ///
- /// [`Primitive`]: ../enum.Primitive.html
pub fn generate(
primitive: &'a Primitive,
viewport: &Viewport,
@@ -98,7 +82,12 @@ impl<'a> Layer<'a> {
let mut layers = vec![first_layer];
- Self::process_primitive(&mut layers, Vector::new(0.0, 0.0), primitive);
+ Self::process_primitive(
+ &mut layers,
+ Vector::new(0.0, 0.0),
+ primitive,
+ 0,
+ );
layers
}
@@ -107,13 +96,19 @@ impl<'a> Layer<'a> {
layers: &mut Vec<Self>,
translation: Vector,
primitive: &'a Primitive,
+ current_layer: usize,
) {
match primitive {
Primitive::None => {}
Primitive::Group { primitives } => {
// TODO: Inspect a bit and regroup (?)
for primitive in primitives {
- Self::process_primitive(layers, translation, primitive)
+ Self::process_primitive(
+ layers,
+ translation,
+ primitive,
+ current_layer,
+ )
}
}
Primitive::Text {
@@ -125,7 +120,7 @@ impl<'a> Layer<'a> {
horizontal_alignment,
vertical_alignment,
} => {
- let layer = layers.last_mut().unwrap();
+ let layer = &mut layers[current_layer];
layer.text.push(Text {
content,
@@ -144,7 +139,7 @@ impl<'a> Layer<'a> {
border_width,
border_color,
} => {
- let layer = layers.last_mut().unwrap();
+ let layer = &mut layers[current_layer];
// TODO: Move some of these computations to the GPU (?)
layer.quads.push(Quad {
@@ -156,13 +151,13 @@ impl<'a> Layer<'a> {
color: match background {
Background::Color(color) => color.into_linear(),
},
- border_radius: *border_radius as f32,
- border_width: *border_width as f32,
+ border_radius: *border_radius,
+ border_width: *border_width,
border_color: border_color.into_linear(),
});
}
Primitive::Mesh2D { buffers, size } => {
- let layer = layers.last_mut().unwrap();
+ let layer = &mut layers[current_layer];
let bounds = Rectangle::new(
Point::new(translation.x, translation.y),
@@ -183,7 +178,7 @@ impl<'a> Layer<'a> {
offset,
content,
} => {
- let layer = layers.last_mut().unwrap();
+ let layer = &mut layers[current_layer];
let translated_bounds = *bounds + translation;
// Only draw visible content
@@ -191,16 +186,15 @@ impl<'a> Layer<'a> {
layer.bounds.intersection(&translated_bounds)
{
let clip_layer = Layer::new(clip_bounds);
- let new_layer = Layer::new(layer.bounds);
-
layers.push(clip_layer);
+
Self::process_primitive(
layers,
translation
- Vector::new(offset.x as f32, offset.y as f32),
content,
+ layers.len() - 1,
);
- layers.push(new_layer);
}
}
Primitive::Translate {
@@ -211,13 +205,19 @@ impl<'a> Layer<'a> {
layers,
translation + *new_translation,
&content,
+ current_layer,
);
}
Primitive::Cached { cache } => {
- Self::process_primitive(layers, translation, &cache);
+ Self::process_primitive(
+ layers,
+ translation,
+ &cache,
+ current_layer,
+ );
}
Primitive::Image { handle, bounds } => {
- let layer = layers.last_mut().unwrap();
+ let layer = &mut layers[current_layer];
layer.images.push(Image::Raster {
handle: handle.clone(),
@@ -225,7 +225,7 @@ impl<'a> Layer<'a> {
});
}
Primitive::Svg { handle, bounds } => {
- let layer = layers.last_mut().unwrap();
+ let layer = &mut layers[current_layer];
layer.images.push(Image::Vector {
handle: handle.clone(),
@@ -243,33 +243,21 @@ impl<'a> Layer<'a> {
#[repr(C)]
pub struct Quad {
/// The position of the [`Quad`].
- ///
- /// [`Quad`]: struct.Quad.html
pub position: [f32; 2],
/// The size of the [`Quad`].
- ///
- /// [`Quad`]: struct.Quad.html
pub size: [f32; 2],
/// The color of the [`Quad`], in __linear RGB__.
- ///
- /// [`Quad`]: struct.Quad.html
pub color: [f32; 4],
/// The border color of the [`Quad`], in __linear RGB__.
- ///
- /// [`Quad`]: struct.Quad.html
pub border_color: [f32; 4],
/// The border radius of the [`Quad`].
- ///
- /// [`Quad`]: struct.Quad.html
pub border_radius: f32,
/// The border width of the [`Quad`].
- ///
- /// [`Quad`]: struct.Quad.html
pub border_width: f32,
}
@@ -277,18 +265,12 @@ pub struct Quad {
#[derive(Debug, Clone, Copy)]
pub struct Mesh<'a> {
/// The origin of the vertices of the [`Mesh`].
- ///
- /// [`Mesh`]: struct.Mesh.html
pub origin: Point,
/// The vertex and index buffers of the [`Mesh`].
- ///
- /// [`Mesh`]: struct.Mesh.html
pub buffers: &'a triangle::Mesh2D,
/// The clipping bounds of the [`Mesh`].
- ///
- /// [`Mesh`]: struct.Mesh.html
pub clip_bounds: Rectangle<f32>,
}
@@ -296,38 +278,24 @@ pub struct Mesh<'a> {
#[derive(Debug, Clone, Copy)]
pub struct Text<'a> {
/// The content of the [`Text`].
- ///
- /// [`Text`]: struct.Text.html
pub content: &'a str,
/// The layout bounds of the [`Text`].
- ///
- /// [`Text`]: struct.Text.html
pub bounds: Rectangle,
/// The color of the [`Text`], in __linear RGB_.
- ///
- /// [`Text`]: struct.Text.html
pub color: [f32; 4],
/// The size of the [`Text`].
- ///
- /// [`Text`]: struct.Text.html
pub size: f32,
/// The font of the [`Text`].
- ///
- /// [`Text`]: struct.Text.html
pub font: Font,
/// The horizontal alignment of the [`Text`].
- ///
- /// [`Text`]: struct.Text.html
pub horizontal_alignment: HorizontalAlignment,
/// The vertical alignment of the [`Text`].
- ///
- /// [`Text`]: struct.Text.html
pub vertical_alignment: VerticalAlignment,
}
diff --git a/graphics/src/lib.rs b/graphics/src/lib.rs
index a3bd5364..14388653 100644
--- a/graphics/src/lib.rs
+++ b/graphics/src/lib.rs
@@ -1,6 +1,8 @@
//! A bunch of backend-agnostic types that can be leveraged to build a renderer
//! for [`iced`].
//!
+//! ![The native path of the Iced ecosystem](https://github.com/hecrj/iced/blob/0525d76ff94e828b7b21634fa94a747022001c83/docs/graphs/native.png?raw=true)
+//!
//! [`iced`]: https://github.com/hecrj/iced
#![deny(missing_docs)]
#![deny(missing_debug_implementations)]
diff --git a/graphics/src/overlay/menu.rs b/graphics/src/overlay/menu.rs
index a952f065..9e91a0ef 100644
--- a/graphics/src/overlay/menu.rs
+++ b/graphics/src/overlay/menu.rs
@@ -2,8 +2,8 @@
use crate::backend::{self, Backend};
use crate::{Primitive, Renderer};
use iced_native::{
- mouse, overlay, Color, Font, HorizontalAlignment, Point, Rectangle,
- VerticalAlignment,
+ mouse, overlay, Color, Font, HorizontalAlignment, Padding, Point,
+ Rectangle, VerticalAlignment,
};
pub use iced_style::menu::Style;
@@ -29,7 +29,7 @@ where
background: style.background,
border_color: style.border_color,
border_width: style.border_width,
- border_radius: 0,
+ border_radius: 0.0,
},
primitives,
],
@@ -42,9 +42,10 @@ where
&mut self,
bounds: Rectangle,
cursor_position: Point,
+ viewport: &Rectangle,
options: &[T],
hovered_option: Option<usize>,
- padding: u16,
+ padding: Padding,
text_size: u16,
font: Font,
style: &Style,
@@ -52,18 +53,26 @@ where
use std::f32;
let is_mouse_over = bounds.contains(cursor_position);
+ let option_height = (text_size + padding.vertical()) as usize;
let mut primitives = Vec::new();
- for (i, option) in options.iter().enumerate() {
+ let offset = viewport.y - bounds.y;
+ let start = (offset / option_height as f32) as usize;
+ let end =
+ ((offset + viewport.height) / option_height as f32).ceil() as usize;
+
+ let visible_options = &options[start..end.min(options.len())];
+
+ for (i, option) in visible_options.iter().enumerate() {
+ let i = start + i;
let is_selected = hovered_option == Some(i);
let bounds = Rectangle {
x: bounds.x,
- y: bounds.y
- + ((text_size as usize + padding as usize * 2) * i) as f32,
+ y: bounds.y + (option_height * i) as f32,
width: bounds.width,
- height: f32::from(text_size + padding * 2),
+ height: f32::from(text_size + padding.vertical()),
};
if is_selected {
@@ -71,15 +80,15 @@ where
bounds,
background: style.selected_background,
border_color: Color::TRANSPARENT,
- border_width: 0,
- border_radius: 0,
+ border_width: 0.0,
+ border_radius: 0.0,
});
}
primitives.push(Primitive::Text {
content: option.to_string(),
bounds: Rectangle {
- x: bounds.x + f32::from(padding),
+ x: bounds.x + padding.left as f32,
y: bounds.center_y(),
width: f32::INFINITY,
..bounds
diff --git a/graphics/src/primitive.rs b/graphics/src/primitive.rs
index 95dbf7dd..30263bd4 100644
--- a/graphics/src/primitive.rs
+++ b/graphics/src/primitive.rs
@@ -40,9 +40,9 @@ pub enum Primitive {
/// The background of the quad
background: Background,
/// The border radius of the quad
- border_radius: u16,
+ border_radius: f32,
/// The border width of the quad
- border_width: u16,
+ border_width: f32,
/// The border color of the quad
border_color: Color,
},
diff --git a/graphics/src/renderer.rs b/graphics/src/renderer.rs
index 5d51e6d4..fa63991b 100644
--- a/graphics/src/renderer.rs
+++ b/graphics/src/renderer.rs
@@ -13,25 +13,16 @@ pub struct Renderer<B: Backend> {
impl<B: Backend> Renderer<B> {
/// Creates a new [`Renderer`] from the given [`Backend`].
- ///
- /// [`Renderer`]: struct.Renderer.html
- /// [`Backend`]: backend/trait.Backend.html
pub fn new(backend: B) -> Self {
Self { backend }
}
/// Returns a reference to the [`Backend`] of the [`Renderer`].
- ///
- /// [`Renderer`]: struct.Renderer.html
- /// [`Backend`]: backend/trait.Backend.html
pub fn backend(&self) -> &B {
&self.backend
}
/// Returns a mutable reference to the [`Backend`] of the [`Renderer`].
- ///
- /// [`Renderer`]: struct.Renderer.html
- /// [`Backend`]: backend/trait.Backend.html
pub fn backend_mut(&mut self) -> &mut B {
&mut self.backend
}
@@ -96,10 +87,11 @@ where
widget: &dyn Widget<Message, Self>,
layout: Layout<'_>,
cursor_position: Point,
+ viewport: &Rectangle,
color: Color,
) -> Self::Output {
let (primitive, cursor) =
- widget.draw(self, defaults, layout, cursor_position);
+ widget.draw(self, defaults, layout, cursor_position, viewport);
let mut primitives = Vec::new();
@@ -118,8 +110,8 @@ fn explain_layout(
primitives.push(Primitive::Quad {
bounds: layout.bounds(),
background: Background::Color(Color::TRANSPARENT),
- border_radius: 0,
- border_width: 1,
+ border_radius: 0.0,
+ border_width: 1.0,
border_color: [0.6, 0.6, 0.6, 0.5].into(),
});
diff --git a/graphics/src/triangle.rs b/graphics/src/triangle.rs
index ce879ffc..05028f51 100644
--- a/graphics/src/triangle.rs
+++ b/graphics/src/triangle.rs
@@ -1,8 +1,7 @@
//! Draw geometry using meshes of triangles.
+use bytemuck::{Pod, Zeroable};
/// A set of [`Vertex2D`] and indices representing a list of triangles.
-///
-/// [`Vertex2D`]: struct.Vertex2D.html
#[derive(Clone, Debug)]
pub struct Mesh2D {
/// The vertices of the mesh
@@ -16,7 +15,7 @@ pub struct Mesh2D {
}
/// A two-dimensional vertex with some color in __linear__ RGBA.
-#[derive(Copy, Clone, Debug)]
+#[derive(Copy, Clone, Debug, Zeroable, Pod)]
#[repr(C)]
pub struct Vertex2D {
/// The vertex position
@@ -24,9 +23,3 @@ pub struct Vertex2D {
/// The vertex color in __linear__ RGBA.
pub color: [f32; 4],
}
-
-#[allow(unsafe_code)]
-unsafe impl bytemuck::Zeroable for Vertex2D {}
-
-#[allow(unsafe_code)]
-unsafe impl bytemuck::Pod for Vertex2D {}
diff --git a/graphics/src/viewport.rs b/graphics/src/viewport.rs
index 66122e6d..2c0b541a 100644
--- a/graphics/src/viewport.rs
+++ b/graphics/src/viewport.rs
@@ -1,7 +1,7 @@
use crate::{Size, Transformation};
/// A viewing region for displaying computer graphics.
-#[derive(Debug)]
+#[derive(Debug, Clone)]
pub struct Viewport {
physical_size: Size<u32>,
logical_size: Size<f32>,
@@ -12,8 +12,6 @@ pub struct Viewport {
impl Viewport {
/// Creates a new [`Viewport`] with the given physical dimensions and scale
/// factor.
- ///
- /// [`Viewport`]: struct.Viewport.html
pub fn with_physical_size(size: Size<u32>, scale_factor: f64) -> Viewport {
Viewport {
physical_size: size,
@@ -27,43 +25,31 @@ impl Viewport {
}
/// Returns the physical size of the [`Viewport`].
- ///
- /// [`Viewport`]: struct.Viewport.html
pub fn physical_size(&self) -> Size<u32> {
self.physical_size
}
/// Returns the physical width of the [`Viewport`].
- ///
- /// [`Viewport`]: struct.Viewport.html
pub fn physical_width(&self) -> u32 {
- self.physical_size.height
+ self.physical_size.width
}
/// Returns the physical height of the [`Viewport`].
- ///
- /// [`Viewport`]: struct.Viewport.html
pub fn physical_height(&self) -> u32 {
self.physical_size.height
}
/// Returns the logical size of the [`Viewport`].
- ///
- /// [`Viewport`]: struct.Viewport.html
pub fn logical_size(&self) -> Size<f32> {
self.logical_size
}
/// Returns the scale factor of the [`Viewport`].
- ///
- /// [`Viewport`]: struct.Viewport.html
pub fn scale_factor(&self) -> f64 {
self.scale_factor
}
/// Returns the projection transformation of the [`Viewport`].
- ///
- /// [`Viewport`]: struct.Viewport.html
pub fn projection(&self) -> Transformation {
self.projection
}
diff --git a/graphics/src/widget.rs b/graphics/src/widget.rs
index f87b558a..e34d267f 100644
--- a/graphics/src/widget.rs
+++ b/graphics/src/widget.rs
@@ -20,6 +20,8 @@ pub mod scrollable;
pub mod slider;
pub mod svg;
pub mod text_input;
+pub mod toggler;
+pub mod tooltip;
mod column;
mod row;
@@ -48,6 +50,10 @@ pub use scrollable::Scrollable;
pub use slider::Slider;
#[doc(no_inline)]
pub use text_input::TextInput;
+#[doc(no_inline)]
+pub use toggler::Toggler;
+#[doc(no_inline)]
+pub use tooltip::Tooltip;
pub use column::Column;
pub use image::Image;
@@ -63,3 +69,11 @@ pub mod canvas;
#[cfg(feature = "canvas")]
#[doc(no_inline)]
pub use canvas::Canvas;
+
+#[cfg(feature = "qr_code")]
+#[cfg_attr(docsrs, doc(cfg(feature = "qr_code")))]
+pub mod qr_code;
+
+#[cfg(feature = "qr_code")]
+#[doc(no_inline)]
+pub use qr_code::QRCode;
diff --git a/graphics/src/widget/button.rs b/graphics/src/widget/button.rs
index ecabc868..60400ed8 100644
--- a/graphics/src/widget/button.rs
+++ b/graphics/src/widget/button.rs
@@ -1,14 +1,11 @@
//! Allow your users to perform actions by pressing a button.
//!
//! A [`Button`] has some local [`State`].
-//!
-//! [`Button`]: type.Button.html
-//! [`State`]: struct.State.html
use crate::defaults::{self, Defaults};
use crate::{Backend, Primitive, Renderer};
use iced_native::mouse;
use iced_native::{
- Background, Color, Element, Layout, Point, Rectangle, Vector,
+ Background, Color, Element, Layout, Padding, Point, Rectangle, Vector,
};
pub use iced_native::button::State;
@@ -24,7 +21,7 @@ impl<B> iced_native::button::Renderer for Renderer<B>
where
B: Backend,
{
- const DEFAULT_PADDING: u16 = 5;
+ const DEFAULT_PADDING: Padding = Padding::new(5);
type Style = Box<dyn StyleSheet>;
@@ -62,10 +59,11 @@ where
},
content_layout,
cursor_position,
+ &bounds,
);
(
- if styling.background.is_some() || styling.border_width > 0 {
+ if styling.background.is_some() || styling.border_width > 0.0 {
let background = Primitive::Quad {
bounds,
background: styling
@@ -92,7 +90,7 @@ where
[0.0, 0.0, 0.0, 0.5].into(),
),
border_radius: styling.border_radius,
- border_width: 0,
+ border_width: 0.0,
border_color: Color::TRANSPARENT,
};
diff --git a/graphics/src/widget/canvas.rs b/graphics/src/widget/canvas.rs
index bc0802e5..7897c8ec 100644
--- a/graphics/src/widget/canvas.rs
+++ b/graphics/src/widget/canvas.rs
@@ -3,22 +3,21 @@
//! A [`Canvas`] widget can be used to draw different kinds of 2D shapes in a
//! [`Frame`]. It can be used for animation, data visualization, game graphics,
//! and more!
-//!
-//! [`Canvas`]: struct.Canvas.html
-//! [`Frame`]: struct.Frame.html
use crate::{Backend, Defaults, Primitive, Renderer};
+use iced_native::layout;
+use iced_native::mouse;
use iced_native::{
- layout, mouse, Clipboard, Element, Hasher, Layout, Length, Point, Size,
- Vector, Widget,
+ Clipboard, Element, Hasher, Layout, Length, Point, Rectangle, Size, Vector,
+ Widget,
};
use std::hash::Hash;
use std::marker::PhantomData;
+pub mod event;
pub mod path;
mod cache;
mod cursor;
-mod event;
mod fill;
mod frame;
mod geometry;
@@ -39,8 +38,6 @@ pub use text::Text;
/// A widget capable of drawing 2D graphics.
///
-/// [`Canvas`]: struct.Canvas.html
-///
/// # Examples
/// The repository has a couple of [examples] showcasing how to use a
/// [`Canvas`]:
@@ -106,8 +103,6 @@ impl<Message, P: Program<Message>> Canvas<Message, P> {
const DEFAULT_SIZE: u16 = 100;
/// Creates a new [`Canvas`].
- ///
- /// [`Canvas`]: struct.Canvas.html
pub fn new(program: P) -> Self {
Canvas {
width: Length::Units(Self::DEFAULT_SIZE),
@@ -118,16 +113,12 @@ impl<Message, P: Program<Message>> Canvas<Message, P> {
}
/// Sets the width of the [`Canvas`].
- ///
- /// [`Canvas`]: struct.Canvas.html
pub fn width(mut self, width: Length) -> Self {
self.width = width;
self
}
/// Sets the height of the [`Canvas`].
- ///
- /// [`Canvas`]: struct.Canvas.html
pub fn height(mut self, height: Length) -> Self {
self.height = height;
self
@@ -163,10 +154,10 @@ where
event: iced_native::Event,
layout: Layout<'_>,
cursor_position: Point,
- messages: &mut Vec<Message>,
_renderer: &Renderer<B>,
- _clipboard: Option<&dyn Clipboard>,
- ) {
+ _clipboard: &mut dyn Clipboard,
+ messages: &mut Vec<Message>,
+ ) -> event::Status {
let bounds = layout.bounds();
let canvas_event = match event {
@@ -182,12 +173,17 @@ where
let cursor = Cursor::from_window_position(cursor_position);
if let Some(canvas_event) = canvas_event {
- if let Some(message) =
- self.program.update(canvas_event, bounds, cursor)
- {
+ let (event_status, message) =
+ self.program.update(canvas_event, bounds, cursor);
+
+ if let Some(message) = message {
messages.push(message);
}
+
+ return event_status;
}
+
+ event::Status::Ignored
}
fn draw(
@@ -196,6 +192,7 @@ where
_defaults: &Defaults,
layout: Layout<'_>,
cursor_position: Point,
+ _viewport: &Rectangle,
) -> (Primitive, mouse::Interaction) {
let bounds = layout.bounds();
let translation = Vector::new(bounds.x, bounds.y);
diff --git a/graphics/src/widget/canvas/cache.rs b/graphics/src/widget/canvas/cache.rs
index 4b28d164..a469417d 100644
--- a/graphics/src/widget/canvas/cache.rs
+++ b/graphics/src/widget/canvas/cache.rs
@@ -23,10 +23,6 @@ impl Default for State {
///
/// A [`Cache`] will not redraw its geometry unless the dimensions of its layer
/// change or it is explicitly cleared.
-///
-/// [`Layer`]: ../trait.Layer.html
-/// [`Cache`]: struct.Cache.html
-/// [`Geometry`]: struct.Geometry.html
#[derive(Debug, Default)]
pub struct Cache {
state: RefCell<State>,
@@ -34,8 +30,6 @@ pub struct Cache {
impl Cache {
/// Creates a new empty [`Cache`].
- ///
- /// [`Cache`]: struct.Cache.html
pub fn new() -> Self {
Cache {
state: Default::default(),
@@ -43,8 +37,6 @@ impl Cache {
}
/// Clears the [`Cache`], forcing a redraw the next time it is used.
- ///
- /// [`Cache`]: struct.Cache.html
pub fn clear(&mut self) {
*self.state.borrow_mut() = State::Empty;
}
@@ -59,8 +51,6 @@ impl Cache {
/// Otherwise, the previously stored [`Geometry`] will be returned. The
/// [`Cache`] is not cleared in this case. In other words, it will keep
/// returning the stored [`Geometry`] if needed.
- ///
- /// [`Cache`]: struct.Cache.html
pub fn draw(&self, bounds: Size, draw_fn: impl Fn(&mut Frame)) -> Geometry {
use std::ops::Deref;
diff --git a/graphics/src/widget/canvas/cursor.rs b/graphics/src/widget/canvas/cursor.rs
index 456760ea..9588d129 100644
--- a/graphics/src/widget/canvas/cursor.rs
+++ b/graphics/src/widget/canvas/cursor.rs
@@ -22,8 +22,6 @@ impl Cursor {
}
/// Returns the absolute position of the [`Cursor`], if available.
- ///
- /// [`Cursor`]: enum.Cursor.html
pub fn position(&self) -> Option<Point> {
match self {
Cursor::Available(position) => Some(*position),
@@ -36,8 +34,6 @@ impl Cursor {
///
/// If the [`Cursor`] is not over the provided bounds, this method will
/// return `None`.
- ///
- /// [`Cursor`]: enum.Cursor.html
pub fn position_in(&self, bounds: &Rectangle) -> Option<Point> {
if self.is_over(bounds) {
self.position_from(bounds.position())
@@ -48,8 +44,6 @@ impl Cursor {
/// Returns the relative position of the [`Cursor`] from the given origin,
/// if available.
- ///
- /// [`Cursor`]: enum.Cursor.html
pub fn position_from(&self, origin: Point) -> Option<Point> {
match self {
Cursor::Available(position) => {
@@ -61,8 +55,6 @@ impl Cursor {
/// Returns whether the [`Cursor`] is currently over the provided bounds
/// or not.
- ///
- /// [`Cursor`]: enum.Cursor.html
pub fn is_over(&self, bounds: &Rectangle) -> bool {
match self {
Cursor::Available(position) => bounds.contains(*position),
diff --git a/graphics/src/widget/canvas/event.rs b/graphics/src/widget/canvas/event.rs
index 0e66f0ff..5bf6f7a6 100644
--- a/graphics/src/widget/canvas/event.rs
+++ b/graphics/src/widget/canvas/event.rs
@@ -1,9 +1,12 @@
+//! Handle events of a canvas.
use iced_native::keyboard;
use iced_native::mouse;
+pub use iced_native::event::Status;
+
/// A [`Canvas`] event.
///
-/// [`Canvas`]: struct.Event.html
+/// [`Canvas`]: crate::widget::Canvas
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum Event {
/// A mouse event.
diff --git a/graphics/src/widget/canvas/frame.rs b/graphics/src/widget/canvas/frame.rs
index b5c6a2b1..5af9d11f 100644
--- a/graphics/src/widget/canvas/frame.rs
+++ b/graphics/src/widget/canvas/frame.rs
@@ -7,7 +7,7 @@ use crate::{
/// The frame of a [`Canvas`].
///
-/// [`Canvas`]: struct.Canvas.html
+/// [`Canvas`]: crate::widget::Canvas
#[derive(Debug)]
pub struct Frame {
size: Size,
@@ -33,8 +33,6 @@ impl Frame {
///
/// The default coordinate system of a [`Frame`] has its origin at the
/// top-left corner of its bounds.
- ///
- /// [`Frame`]: struct.Frame.html
pub fn new(size: Size) -> Frame {
Frame {
size,
@@ -51,32 +49,24 @@ impl Frame {
}
/// Returns the width of the [`Frame`].
- ///
- /// [`Frame`]: struct.Frame.html
#[inline]
pub fn width(&self) -> f32 {
self.size.width
}
- /// Returns the width of the [`Frame`].
- ///
- /// [`Frame`]: struct.Frame.html
+ /// Returns the height of the [`Frame`].
#[inline]
pub fn height(&self) -> f32 {
self.size.height
}
/// Returns the dimensions of the [`Frame`].
- ///
- /// [`Frame`]: struct.Frame.html
#[inline]
pub fn size(&self) -> Size {
self.size
}
/// Returns the coordinate of the center of the [`Frame`].
- ///
- /// [`Frame`]: struct.Frame.html
#[inline]
pub fn center(&self) -> Point {
Point::new(self.size.width / 2.0, self.size.height / 2.0)
@@ -84,9 +74,6 @@ impl Frame {
/// Draws the given [`Path`] on the [`Frame`] by filling it with the
/// provided style.
- ///
- /// [`Path`]: path/struct.Path.html
- /// [`Frame`]: struct.Frame.html
pub fn fill(&mut self, path: &Path, fill: impl Into<Fill>) {
use lyon::tessellation::{
BuffersBuilder, FillOptions, FillTessellator,
@@ -115,8 +102,6 @@ impl Frame {
/// Draws an axis-aligned rectangle given its top-left corner coordinate and
/// its `Size` on the [`Frame`] by filling it with the provided style.
- ///
- /// [`Frame`]: struct.Frame.html
pub fn fill_rectangle(
&mut self,
top_left: Point,
@@ -152,9 +137,6 @@ impl Frame {
/// Draws the stroke of the given [`Path`] on the [`Frame`] with the
/// provided style.
- ///
- /// [`Path`]: path/struct.Path.html
- /// [`Frame`]: struct.Frame.html
pub fn stroke(&mut self, path: &Path, stroke: impl Into<Stroke>) {
use lyon::tessellation::{
BuffersBuilder, StrokeOptions, StrokeTessellator,
@@ -200,9 +182,7 @@ impl Frame {
/// Support for vectorial text is planned, and should address all these
/// limitations.
///
- /// [`Text`]: struct.Text.html
- /// [`Frame`]: struct.Frame.html
- /// [`Canvas`]: struct.Canvas.html
+ /// [`Canvas`]: crate::widget::Canvas
pub fn fill_text(&mut self, text: impl Into<Text>) {
use std::f32;
@@ -240,8 +220,6 @@ impl Frame {
///
/// This method is useful to compose transforms and perform drawing
/// operations in different coordinate systems.
- ///
- /// [`Frame`]: struct.Frame.html
#[inline]
pub fn with_save(&mut self, f: impl FnOnce(&mut Frame)) {
self.transforms.previous.push(self.transforms.current);
@@ -252,8 +230,6 @@ impl Frame {
}
/// Applies a translation to the current transform of the [`Frame`].
- ///
- /// [`Frame`]: struct.Frame.html
#[inline]
pub fn translate(&mut self, translation: Vector) {
self.transforms.current.raw = self
@@ -268,21 +244,17 @@ impl Frame {
}
/// Applies a rotation to the current transform of the [`Frame`].
- ///
- /// [`Frame`]: struct.Frame.html
#[inline]
pub fn rotate(&mut self, angle: f32) {
self.transforms.current.raw = self
.transforms
.current
.raw
- .pre_rotate(lyon::math::Angle::radians(-angle));
+ .pre_rotate(lyon::math::Angle::radians(angle));
self.transforms.current.is_identity = false;
}
/// Applies a scaling to the current transform of the [`Frame`].
- ///
- /// [`Frame`]: struct.Frame.html
#[inline]
pub fn scale(&mut self, scale: f32) {
self.transforms.current.raw =
@@ -291,9 +263,6 @@ impl Frame {
}
/// Produces the [`Geometry`] representing everything drawn on the [`Frame`].
- ///
- /// [`Frame`]: struct.Frame.html
- /// [`Geometry`]: struct.Geometry.html
pub fn into_geometry(mut self) -> Geometry {
if !self.buffers.indices.is_empty() {
self.primitives.push(Primitive::Mesh2D {
diff --git a/graphics/src/widget/canvas/geometry.rs b/graphics/src/widget/canvas/geometry.rs
index 4cadee39..8915cda1 100644
--- a/graphics/src/widget/canvas/geometry.rs
+++ b/graphics/src/widget/canvas/geometry.rs
@@ -5,9 +5,8 @@ use crate::Primitive;
/// [`Geometry`] can be easily generated with a [`Frame`] or stored in a
/// [`Cache`].
///
-/// [`Geometry`]: struct.Geometry.html
-/// [`Frame`]: struct.Frame.html
-/// [`Cache`]: struct.Cache.html
+/// [`Frame`]: crate::widget::canvas::Frame
+/// [`Cache`]: crate::widget::canvas::Cache
#[derive(Debug, Clone)]
pub struct Geometry(Primitive);
@@ -19,9 +18,6 @@ impl Geometry {
/// Turns the [`Geometry`] into a [`Primitive`].
///
/// This can be useful if you are building a custom widget.
- ///
- /// [`Geometry`]: struct.Geometry.html
- /// [`Primitive`]: ../enum.Primitive.html
pub fn into_primitive(self) -> Primitive {
self.0
}
diff --git a/graphics/src/widget/canvas/path.rs b/graphics/src/widget/canvas/path.rs
index c26bf187..6de19321 100644
--- a/graphics/src/widget/canvas/path.rs
+++ b/graphics/src/widget/canvas/path.rs
@@ -12,8 +12,6 @@ use iced_native::{Point, Size};
/// An immutable set of points that may or may not be connected.
///
/// A single [`Path`] can represent different kinds of 2D shapes!
-///
-/// [`Path`]: struct.Path.html
#[derive(Debug, Clone)]
pub struct Path {
raw: lyon::path::Path,
@@ -23,9 +21,6 @@ impl Path {
/// Creates a new [`Path`] with the provided closure.
///
/// Use the [`Builder`] to configure your [`Path`].
- ///
- /// [`Path`]: struct.Path.html
- /// [`Builder`]: struct.Builder.html
pub fn new(f: impl FnOnce(&mut Builder)) -> Self {
let mut builder = Builder::new();
@@ -37,8 +32,6 @@ impl Path {
/// Creates a new [`Path`] representing a line segment given its starting
/// and end points.
- ///
- /// [`Path`]: struct.Path.html
pub fn line(from: Point, to: Point) -> Self {
Self::new(|p| {
p.move_to(from);
@@ -48,16 +41,12 @@ impl Path {
/// Creates a new [`Path`] representing a rectangle given its top-left
/// corner coordinate and its `Size`.
- ///
- /// [`Path`]: struct.Path.html
pub fn rectangle(top_left: Point, size: Size) -> Self {
Self::new(|p| p.rectangle(top_left, size))
}
/// Creates a new [`Path`] representing a circle given its center
/// coordinate and its radius.
- ///
- /// [`Path`]: struct.Path.html
pub fn circle(center: Point, radius: f32) -> Self {
Self::new(|p| p.circle(center, radius))
}
diff --git a/graphics/src/widget/canvas/path/arc.rs b/graphics/src/widget/canvas/path/arc.rs
index 343191f1..b8e72daf 100644
--- a/graphics/src/widget/canvas/path/arc.rs
+++ b/graphics/src/widget/canvas/path/arc.rs
@@ -15,8 +15,6 @@ pub struct Arc {
}
/// An elliptical [`Arc`].
-///
-/// [`Arc`]: struct.Arc.html
#[derive(Debug, Clone, Copy)]
pub struct Elliptical {
/// The center of the arc.
diff --git a/graphics/src/widget/canvas/path/builder.rs b/graphics/src/widget/canvas/path/builder.rs
index e0e52845..5ce0e02c 100644
--- a/graphics/src/widget/canvas/path/builder.rs
+++ b/graphics/src/widget/canvas/path/builder.rs
@@ -6,8 +6,6 @@ use lyon::path::builder::{Build, FlatPathBuilder, PathBuilder, SvgBuilder};
/// A [`Path`] builder.
///
/// Once a [`Path`] is built, it can no longer be mutated.
-///
-/// [`Path`]: struct.Path.html
#[allow(missing_debug_implementations)]
pub struct Builder {
raw: lyon::path::builder::SvgPathBuilder<lyon::path::Builder>,
@@ -15,8 +13,6 @@ pub struct Builder {
impl Builder {
/// Creates a new [`Builder`].
- ///
- /// [`Builder`]: struct.Builder.html
pub fn new() -> Builder {
Builder {
raw: lyon::path::Path::builder().with_svg(),
@@ -31,8 +27,6 @@ impl Builder {
/// Connects the last point in the [`Path`] to the given `Point` with a
/// straight line.
- ///
- /// [`Path`]: struct.Path.html
#[inline]
pub fn line_to(&mut self, point: Point) {
let _ = self.raw.line_to(lyon::math::Point::new(point.x, point.y));
@@ -40,9 +34,6 @@ impl Builder {
/// Adds an [`Arc`] to the [`Path`] from `start_angle` to `end_angle` in
/// a clockwise direction.
- ///
- /// [`Arc`]: struct.Arc.html
- /// [`Path`]: struct.Path.html
#[inline]
pub fn arc(&mut self, arc: Arc) {
self.ellipse(arc.into());
@@ -53,8 +44,6 @@ impl Builder {
///
/// The arc is connected to the previous point by a straight line, if
/// necessary.
- ///
- /// [`Path`]: struct.Path.html
pub fn arc_to(&mut self, a: Point, b: Point, radius: f32) {
use lyon::{math, path};
@@ -72,10 +61,7 @@ impl Builder {
);
}
- /// Adds an [`Ellipse`] to the [`Path`] using a clockwise direction.
- ///
- /// [`Ellipse`]: struct.Arc.html
- /// [`Path`]: struct.Path.html
+ /// Adds an ellipse to the [`Path`] using a clockwise direction.
pub fn ellipse(&mut self, arc: arc::Elliptical) {
use lyon::{geom, math};
@@ -96,8 +82,6 @@ impl Builder {
/// Adds a cubic Bézier curve to the [`Path`] given its two control points
/// and its end point.
- ///
- /// [`Path`]: struct.Path.html
#[inline]
pub fn bezier_curve_to(
&mut self,
@@ -116,8 +100,6 @@ impl Builder {
/// Adds a quadratic Bézier curve to the [`Path`] given its control point
/// and its end point.
- ///
- /// [`Path`]: struct.Path.html
#[inline]
pub fn quadratic_curve_to(&mut self, control: Point, to: Point) {
use lyon::math;
@@ -130,8 +112,6 @@ impl Builder {
/// Adds a rectangle to the [`Path`] given its top-left corner coordinate
/// and its `Size`.
- ///
- /// [`Path`]: struct.Path.html
#[inline]
pub fn rectangle(&mut self, top_left: Point, size: Size) {
self.move_to(top_left);
@@ -146,8 +126,6 @@ impl Builder {
/// Adds a circle to the [`Path`] given its center coordinate and its
/// radius.
- ///
- /// [`Path`]: struct.Path.html
#[inline]
pub fn circle(&mut self, center: Point, radius: f32) {
self.arc(Arc {
@@ -160,17 +138,12 @@ impl Builder {
/// Closes the current sub-path in the [`Path`] with a straight line to
/// the starting point.
- ///
- /// [`Path`]: struct.Path.html
#[inline]
pub fn close(&mut self) {
self.raw.close()
}
/// Builds the [`Path`] of this [`Builder`].
- ///
- /// [`Path`]: struct.Path.html
- /// [`Builder`]: struct.Builder.html
#[inline]
pub fn build(self) -> Path {
Path {
diff --git a/graphics/src/widget/canvas/program.rs b/graphics/src/widget/canvas/program.rs
index 725d9d72..85a2f67b 100644
--- a/graphics/src/widget/canvas/program.rs
+++ b/graphics/src/widget/canvas/program.rs
@@ -1,4 +1,5 @@
-use crate::canvas::{Cursor, Event, Geometry};
+use crate::canvas::event::{self, Event};
+use crate::canvas::{Cursor, Geometry};
use iced_native::{mouse, Rectangle};
/// The state and logic of a [`Canvas`].
@@ -6,8 +7,7 @@ use iced_native::{mouse, Rectangle};
/// A [`Program`] can mutate internal state and produce messages for an
/// application.
///
-/// [`Canvas`]: struct.Canvas.html
-/// [`Program`]: trait.Program.html
+/// [`Canvas`]: crate::widget::Canvas
pub trait Program<Message> {
/// Updates the state of the [`Program`].
///
@@ -19,16 +19,14 @@ pub trait Program<Message> {
///
/// By default, this method does and returns nothing.
///
- /// [`Program`]: trait.Program.html
- /// [`Canvas`]: struct.Canvas.html
- /// [`Event`]: enum.Event.html
+ /// [`Canvas`]: crate::widget::Canvas
fn update(
&mut self,
_event: Event,
_bounds: Rectangle,
_cursor: Cursor,
- ) -> Option<Message> {
- None
+ ) -> (event::Status, Option<Message>) {
+ (event::Status::Ignored, None)
}
/// Draws the state of the [`Program`], producing a bunch of [`Geometry`].
@@ -36,10 +34,8 @@ pub trait Program<Message> {
/// [`Geometry`] can be easily generated with a [`Frame`] or stored in a
/// [`Cache`].
///
- /// [`Program`]: trait.Program.html
- /// [`Geometry`]: struct.Geometry.html
- /// [`Frame`]: struct.Frame.html
- /// [`Cache`]: struct.Cache.html
+ /// [`Frame`]: crate::widget::canvas::Frame
+ /// [`Cache`]: crate::widget::canvas::Cache
fn draw(&self, bounds: Rectangle, cursor: Cursor) -> Vec<Geometry>;
/// Returns the current mouse interaction of the [`Program`].
@@ -47,8 +43,7 @@ pub trait Program<Message> {
/// The interaction returned will be in effect even if the cursor position
/// is out of bounds of the program's [`Canvas`].
///
- /// [`Program`]: trait.Program.html
- /// [`Canvas`]: struct.Canvas.html
+ /// [`Canvas`]: crate::widget::Canvas
fn mouse_interaction(
&self,
_bounds: Rectangle,
@@ -67,7 +62,7 @@ where
event: Event,
bounds: Rectangle,
cursor: Cursor,
- ) -> Option<Message> {
+ ) -> (event::Status, Option<Message>) {
T::update(self, event, bounds, cursor)
}
diff --git a/graphics/src/widget/canvas/stroke.rs b/graphics/src/widget/canvas/stroke.rs
index 5b6fc56a..9f0449d0 100644
--- a/graphics/src/widget/canvas/stroke.rs
+++ b/graphics/src/widget/canvas/stroke.rs
@@ -16,31 +16,21 @@ pub struct Stroke {
impl Stroke {
/// Sets the color of the [`Stroke`].
- ///
- /// [`Stroke`]: struct.Stroke.html
pub fn with_color(self, color: Color) -> Stroke {
Stroke { color, ..self }
}
/// Sets the width of the [`Stroke`].
- ///
- /// [`Stroke`]: struct.Stroke.html
pub fn with_width(self, width: f32) -> Stroke {
Stroke { width, ..self }
}
/// Sets the [`LineCap`] of the [`Stroke`].
- ///
- /// [`LineCap`]: enum.LineCap.html
- /// [`Stroke`]: struct.Stroke.html
pub fn with_line_cap(self, line_cap: LineCap) -> Stroke {
Stroke { line_cap, ..self }
}
/// Sets the [`LineJoin`] of the [`Stroke`].
- ///
- /// [`LineJoin`]: enum.LineJoin.html
- /// [`Stroke`]: struct.Stroke.html
pub fn with_line_join(self, line_join: LineJoin) -> Stroke {
Stroke { line_join, ..self }
}
diff --git a/graphics/src/widget/column.rs b/graphics/src/widget/column.rs
index 6c7235c7..0cf56842 100644
--- a/graphics/src/widget/column.rs
+++ b/graphics/src/widget/column.rs
@@ -1,7 +1,7 @@
use crate::{Backend, Primitive, Renderer};
use iced_native::column;
use iced_native::mouse;
-use iced_native::{Element, Layout, Point};
+use iced_native::{Element, Layout, Point, Rectangle};
/// A container that distributes its contents vertically.
pub type Column<'a, Message, Backend> =
@@ -17,6 +17,7 @@ where
content: &[Element<'_, Message, Self>],
layout: Layout<'_>,
cursor_position: Point,
+ viewport: &Rectangle,
) -> Self::Output {
let mut mouse_interaction = mouse::Interaction::default();
@@ -26,8 +27,13 @@ where
.iter()
.zip(layout.children())
.map(|(child, layout)| {
- let (primitive, new_mouse_interaction) =
- child.draw(self, defaults, layout, cursor_position);
+ let (primitive, new_mouse_interaction) = child.draw(
+ self,
+ defaults,
+ layout,
+ cursor_position,
+ viewport,
+ );
if new_mouse_interaction > mouse_interaction {
mouse_interaction = new_mouse_interaction;
diff --git a/graphics/src/widget/container.rs b/graphics/src/widget/container.rs
index 5b3a01d2..aae3e1d8 100644
--- a/graphics/src/widget/container.rs
+++ b/graphics/src/widget/container.rs
@@ -24,6 +24,7 @@ where
defaults: &Defaults,
bounds: Rectangle,
cursor_position: Point,
+ viewport: &Rectangle,
style_sheet: &Self::Style,
content: &Element<'_, Message, Self>,
content_layout: Layout<'_>,
@@ -36,8 +37,13 @@ where
},
};
- let (content, mouse_interaction) =
- content.draw(self, &defaults, content_layout, cursor_position);
+ let (content, mouse_interaction) = content.draw(
+ self,
+ &defaults,
+ content_layout,
+ cursor_position,
+ viewport,
+ );
if let Some(background) = background(bounds, &style) {
(
@@ -56,7 +62,7 @@ pub(crate) fn background(
bounds: Rectangle,
style: &container::Style,
) -> Option<Primitive> {
- if style.background.is_some() || style.border_width > 0 {
+ if style.background.is_some() || style.border_width > 0.0 {
Some(Primitive::Quad {
bounds,
background: style
diff --git a/graphics/src/widget/image.rs b/graphics/src/widget/image.rs
index 30f446e8..bdf03de3 100644
--- a/graphics/src/widget/image.rs
+++ b/graphics/src/widget/image.rs
@@ -1,11 +1,14 @@
//! Display images in your user interface.
+pub mod viewer;
+
use crate::backend::{self, Backend};
+
use crate::{Primitive, Renderer};
use iced_native::image;
use iced_native::mouse;
use iced_native::Layout;
-pub use iced_native::image::{Handle, Image};
+pub use iced_native::image::{Handle, Image, Viewer};
impl<B> image::Renderer for Renderer<B>
where
diff --git a/graphics/src/widget/image/viewer.rs b/graphics/src/widget/image/viewer.rs
new file mode 100644
index 00000000..28dffc4f
--- /dev/null
+++ b/graphics/src/widget/image/viewer.rs
@@ -0,0 +1,55 @@
+//! Zoom and pan on an image.
+use crate::backend::{self, Backend};
+use crate::{Primitive, Renderer};
+
+use iced_native::image;
+use iced_native::image::viewer;
+use iced_native::mouse;
+use iced_native::{Rectangle, Size, Vector};
+
+impl<B> viewer::Renderer for Renderer<B>
+where
+ B: Backend + backend::Image,
+{
+ fn draw(
+ &mut self,
+ state: &viewer::State,
+ bounds: Rectangle,
+ image_size: Size,
+ translation: Vector,
+ handle: image::Handle,
+ is_mouse_over: bool,
+ ) -> Self::Output {
+ (
+ {
+ Primitive::Clip {
+ bounds,
+ content: Box::new(Primitive::Translate {
+ translation,
+ content: Box::new(Primitive::Image {
+ handle,
+ bounds: Rectangle {
+ x: bounds.x,
+ y: bounds.y,
+ ..Rectangle::with_size(image_size)
+ },
+ }),
+ }),
+ offset: Vector::new(0, 0),
+ }
+ },
+ {
+ if state.is_cursor_grabbed() {
+ mouse::Interaction::Grabbing
+ } else if is_mouse_over
+ && (image_size.width > bounds.width
+ || image_size.height > bounds.height)
+ {
+ mouse::Interaction::Grab
+ } else {
+ mouse::Interaction::Idle
+ }
+ },
+ )
+ }
+}
diff --git a/graphics/src/widget/pane_grid.rs b/graphics/src/widget/pane_grid.rs
index aa8a3f7c..92cdbb77 100644
--- a/graphics/src/widget/pane_grid.rs
+++ b/graphics/src/widget/pane_grid.rs
@@ -6,24 +6,21 @@
//! The [`pane_grid` example] showcases how to use a [`PaneGrid`] with resizing,
//! drag and drop, and hotkey support.
//!
-//! [`pane_grid` example]: https://github.com/hecrj/iced/tree/0.1/examples/pane_grid
-//! [`PaneGrid`]: type.PaneGrid.html
-use crate::backend::{self, Backend};
+//! [`pane_grid` example]: https://github.com/hecrj/iced/tree/0.3/examples/pane_grid
use crate::defaults;
-use crate::{Primitive, Renderer};
+use crate::{Backend, Color, Primitive, Renderer};
+use iced_native::container;
use iced_native::mouse;
use iced_native::pane_grid;
-use iced_native::text;
-use iced_native::{
- Element, HorizontalAlignment, Layout, Point, Rectangle, Vector,
- VerticalAlignment,
-};
+use iced_native::{Element, Layout, Point, Rectangle, Vector};
pub use iced_native::pane_grid::{
- Axis, Configuration, Content, Direction, DragEvent, Focus, KeyPressEvent,
- Pane, ResizeEvent, Split, State, TitleBar,
+ Axis, Configuration, Content, Direction, DragEvent, Node, Pane,
+ ResizeEvent, Split, State, TitleBar,
};
+pub use iced_style::pane_grid::{Line, StyleSheet};
+
/// A collection of panes distributed using either vertical or horizontal splits
/// to completely fill the space available.
///
@@ -35,16 +32,20 @@ pub type PaneGrid<'a, Message, Backend> =
impl<B> pane_grid::Renderer for Renderer<B>
where
- B: Backend + backend::Text,
+ B: Backend,
{
+ type Style = Box<dyn StyleSheet>;
+
fn draw<Message>(
&mut self,
defaults: &Self::Defaults,
content: &[(Pane, Content<'_, Message, Self>)],
dragging: Option<(Pane, Point)>,
- resizing: Option<Axis>,
+ resizing: Option<(Axis, Rectangle, bool)>,
layout: Layout<'_>,
+ style_sheet: &<Self as pane_grid::Renderer>::Style,
cursor_position: Point,
+ viewport: &Rectangle,
) -> Self::Output {
let pane_cursor_position = if dragging.is_some() {
// TODO: Remove once cursor availability is encoded in the type
@@ -62,8 +63,13 @@ where
.zip(layout.children())
.enumerate()
.map(|(i, ((id, pane), layout))| {
- let (primitive, new_mouse_interaction) =
- pane.draw(self, defaults, layout, pane_cursor_position);
+ let (primitive, new_mouse_interaction) = pane.draw(
+ self,
+ defaults,
+ layout,
+ pane_cursor_position,
+ viewport,
+ );
if new_mouse_interaction > mouse_interaction {
mouse_interaction = new_mouse_interaction;
@@ -79,7 +85,8 @@ where
})
.collect();
- let primitives = if let Some((index, layout, origin)) = dragged_pane {
+ let mut primitives = if let Some((index, layout, origin)) = dragged_pane
+ {
let pane = panes.remove(index);
let bounds = layout.bounds();
@@ -109,15 +116,62 @@ where
panes
};
+ let (primitives, mouse_interaction) =
+ if let Some((axis, split_region, is_picked)) = resizing {
+ let highlight = if is_picked {
+ style_sheet.picked_split()
+ } else {
+ style_sheet.hovered_split()
+ };
+
+ if let Some(highlight) = highlight {
+ primitives.push(Primitive::Quad {
+ bounds: match axis {
+ Axis::Horizontal => Rectangle {
+ x: split_region.x,
+ y: (split_region.y
+ + (split_region.height - highlight.width)
+ / 2.0)
+ .round(),
+ width: split_region.width,
+ height: highlight.width,
+ },
+ Axis::Vertical => Rectangle {
+ x: (split_region.x
+ + (split_region.width - highlight.width)
+ / 2.0)
+ .round(),
+ y: split_region.y,
+ width: highlight.width,
+ height: split_region.height,
+ },
+ },
+ background: highlight.color.into(),
+ border_radius: 0.0,
+ border_width: 0.0,
+ border_color: Color::TRANSPARENT,
+ });
+ }
+
+ (
+ primitives,
+ match axis {
+ Axis::Horizontal => {
+ mouse::Interaction::ResizingVertically
+ }
+ Axis::Vertical => {
+ mouse::Interaction::ResizingHorizontally
+ }
+ },
+ )
+ } else {
+ (primitives, mouse_interaction)
+ };
+
(
Primitive::Group { primitives },
if dragging.is_some() {
mouse::Interaction::Grabbing
- } else if let Some(axis) = resizing {
- match axis {
- Axis::Horizontal => mouse::Interaction::ResizingVertically,
- Axis::Vertical => mouse::Interaction::ResizingHorizontally,
- }
} else {
mouse_interaction
},
@@ -128,16 +182,17 @@ where
&mut self,
defaults: &Self::Defaults,
bounds: Rectangle,
- style_sheet: &Self::Style,
+ style_sheet: &<Self as container::Renderer>::Style,
title_bar: Option<(&TitleBar<'_, Message, Self>, Layout<'_>)>,
body: (&Element<'_, Message, Self>, Layout<'_>),
cursor_position: Point,
+ viewport: &Rectangle,
) -> Self::Output {
let style = style_sheet.style();
let (body, body_layout) = body;
let (body_primitive, body_interaction) =
- body.draw(self, defaults, body_layout, cursor_position);
+ body.draw(self, defaults, body_layout, cursor_position, viewport);
let background = crate::widget::container::background(bounds, &style);
@@ -151,6 +206,7 @@ where
defaults,
title_bar_layout,
cursor_position,
+ viewport,
show_controls,
);
@@ -162,10 +218,10 @@ where
body_primitive,
],
},
- if is_over_pick_area {
- mouse::Interaction::Grab
- } else if title_bar_interaction > body_interaction {
+ if title_bar_interaction > body_interaction {
title_bar_interaction
+ } else if is_over_pick_area {
+ mouse::Interaction::Grab
} else {
body_interaction
},
@@ -188,15 +244,14 @@ where
&mut self,
defaults: &Self::Defaults,
bounds: Rectangle,
- style_sheet: &Self::Style,
- title: &str,
- title_size: u16,
- title_font: Self::Font,
- title_bounds: Rectangle,
+ style_sheet: &<Self as container::Renderer>::Style,
+ content: (&Element<'_, Message, Self>, Layout<'_>),
controls: Option<(&Element<'_, Message, Self>, Layout<'_>)>,
cursor_position: Point,
+ viewport: &Rectangle,
) -> Self::Output {
let style = style_sheet.style();
+ let (title_content, title_layout) = content;
let defaults = Self::Defaults {
text: defaults::Text {
@@ -206,16 +261,12 @@ where
let background = crate::widget::container::background(bounds, &style);
- let (title_primitive, _) = text::Renderer::draw(
+ let (title_primitive, title_interaction) = title_content.draw(
self,
&defaults,
- title_bounds,
- title,
- title_size,
- title_font,
- None,
- HorizontalAlignment::Left,
- VerticalAlignment::Top,
+ title_layout,
+ cursor_position,
+ viewport,
);
if let Some((controls, controls_layout)) = controls {
@@ -224,6 +275,7 @@ where
&defaults,
controls_layout,
cursor_position,
+ viewport,
);
(
@@ -234,7 +286,7 @@ where
controls_primitive,
],
},
- controls_interaction,
+ controls_interaction.max(title_interaction),
)
} else {
(
@@ -245,7 +297,7 @@ where
} else {
title_primitive
},
- mouse::Interaction::default(),
+ title_interaction,
)
}
}
diff --git a/graphics/src/widget/pick_list.rs b/graphics/src/widget/pick_list.rs
index f42a8707..88a590b5 100644
--- a/graphics/src/widget/pick_list.rs
+++ b/graphics/src/widget/pick_list.rs
@@ -2,7 +2,8 @@
use crate::backend::{self, Backend};
use crate::{Primitive, Renderer};
use iced_native::{
- mouse, Font, HorizontalAlignment, Point, Rectangle, VerticalAlignment,
+ mouse, Font, HorizontalAlignment, Padding, Point, Rectangle,
+ VerticalAlignment,
};
use iced_style::menu;
@@ -19,7 +20,7 @@ where
{
type Style = Box<dyn StyleSheet>;
- const DEFAULT_PADDING: u16 = 5;
+ const DEFAULT_PADDING: Padding = Padding::new(5);
fn menu_style(style: &Box<dyn StyleSheet>) -> menu::Style {
style.menu()
@@ -30,12 +31,14 @@ where
bounds: Rectangle,
cursor_position: Point,
selected: Option<String>,
- padding: u16,
+ placeholder: Option<&str>,
+ padding: Padding,
text_size: u16,
font: Font,
style: &Box<dyn StyleSheet>,
) -> Self::Output {
let is_mouse_over = bounds.contains(cursor_position);
+ let is_selected = selected.is_some();
let style = if is_mouse_over {
style.hovered()
@@ -56,7 +59,7 @@ where
font: B::ICON_FONT,
size: bounds.height * style.icon_size,
bounds: Rectangle {
- x: bounds.x + bounds.width - f32::from(padding) * 2.0,
+ x: bounds.x + bounds.width - f32::from(padding.horizontal()),
y: bounds.center_y(),
..bounds
},
@@ -67,14 +70,18 @@ where
(
Primitive::Group {
- primitives: if let Some(label) = selected {
+ primitives: if let Some(label) =
+ selected.or_else(|| placeholder.map(str::to_string))
+ {
let label = Primitive::Text {
content: label,
size: f32::from(text_size),
font,
- color: style.text_color,
+ color: is_selected
+ .then(|| style.text_color)
+ .unwrap_or(style.placeholder_color),
bounds: Rectangle {
- x: bounds.x + f32::from(padding),
+ x: bounds.x + f32::from(padding.left),
y: bounds.center_y(),
..bounds
},
diff --git a/graphics/src/widget/progress_bar.rs b/graphics/src/widget/progress_bar.rs
index 48acb3c1..32ee42c6 100644
--- a/graphics/src/widget/progress_bar.rs
+++ b/graphics/src/widget/progress_bar.rs
@@ -2,8 +2,6 @@
//!
//! A [`ProgressBar`] has a range of possible values and a current value,
//! as well as a length, height and style.
-//!
-//! [`ProgressBar`]: type.ProgressBar.html
use crate::{Backend, Primitive, Renderer};
use iced_native::mouse;
use iced_native::progress_bar;
@@ -33,17 +31,20 @@ where
style_sheet: &Self::Style,
) -> Self::Output {
let style = style_sheet.style();
-
let (range_start, range_end) = range.into_inner();
- let active_progress_width = bounds.width
- * ((value - range_start) / (range_end - range_start).max(1.0));
+
+ let active_progress_width = if range_start >= range_end {
+ 0.0
+ } else {
+ bounds.width * (value - range_start) / (range_end - range_start)
+ };
let background = Primitive::Group {
primitives: vec![Primitive::Quad {
bounds: Rectangle { ..bounds },
background: style.background,
border_radius: style.border_radius,
- border_width: 0,
+ border_width: 0.0,
border_color: Color::TRANSPARENT,
}],
};
@@ -57,7 +58,7 @@ where
},
background: style.bar,
border_radius: style.border_radius,
- border_width: 0,
+ border_width: 0.0,
border_color: Color::TRANSPARENT,
};
diff --git a/graphics/src/widget/qr_code.rs b/graphics/src/widget/qr_code.rs
new file mode 100644
index 00000000..b3a01dd7
--- /dev/null
+++ b/graphics/src/widget/qr_code.rs
@@ -0,0 +1,305 @@
+//! Encode and display information in a QR code.
+use crate::canvas;
+use crate::{Backend, Defaults, Primitive, Renderer, Vector};
+
+use iced_native::{
+ layout, mouse, Color, Element, Hasher, Layout, Length, Point, Rectangle,
+ Size, Widget,
+};
+use thiserror::Error;
+
+const DEFAULT_CELL_SIZE: u16 = 4;
+const QUIET_ZONE: usize = 2;
+
+/// A type of matrix barcode consisting of squares arranged in a grid which
+/// can be read by an imaging device, such as a camera.
+#[derive(Debug)]
+pub struct QRCode<'a> {
+ state: &'a State,
+ dark: Color,
+ light: Color,
+ cell_size: u16,
+}
+
+impl<'a> QRCode<'a> {
+ /// Creates a new [`QRCode`] with the provided [`State`].
+ pub fn new(state: &'a State) -> Self {
+ Self {
+ cell_size: DEFAULT_CELL_SIZE,
+ dark: Color::BLACK,
+ light: Color::WHITE,
+ state,
+ }
+ }
+
+ /// Sets both the dark and light [`Color`]s of the [`QRCode`].
+ pub fn color(mut self, dark: Color, light: Color) -> Self {
+ self.dark = dark;
+ self.light = light;
+ self
+ }
+
+ /// Sets the size of the squares of the grid cell of the [`QRCode`].
+ pub fn cell_size(mut self, cell_size: u16) -> Self {
+ self.cell_size = cell_size;
+ self
+ }
+}
+
+impl<'a, Message, B> Widget<Message, Renderer<B>> for QRCode<'a>
+where
+ B: Backend,
+{
+ fn width(&self) -> Length {
+ Length::Shrink
+ }
+
+ fn height(&self) -> Length {
+ Length::Shrink
+ }
+
+ fn layout(
+ &self,
+ _renderer: &Renderer<B>,
+ _limits: &layout::Limits,
+ ) -> layout::Node {
+ let side_length = (self.state.width + 2 * QUIET_ZONE) as f32
+ * f32::from(self.cell_size);
+
+ layout::Node::new(Size::new(
+ f32::from(side_length),
+ f32::from(side_length),
+ ))
+ }
+
+ fn hash_layout(&self, state: &mut Hasher) {
+ use std::hash::Hash;
+
+ self.state.contents.hash(state);
+ }
+
+ fn draw(
+ &self,
+ _renderer: &mut Renderer<B>,
+ _defaults: &Defaults,
+ layout: Layout<'_>,
+ _cursor_position: Point,
+ _viewport: &Rectangle,
+ ) -> (Primitive, mouse::Interaction) {
+ let bounds = layout.bounds();
+ let side_length = self.state.width + 2 * QUIET_ZONE;
+
+ // Reuse cache if possible
+ let geometry = self.state.cache.draw(bounds.size(), |frame| {
+ // Scale units to cell size
+ frame.scale(f32::from(self.cell_size));
+
+ // Draw background
+ frame.fill_rectangle(
+ Point::ORIGIN,
+ Size::new(side_length as f32, side_length as f32),
+ self.light,
+ );
+
+ // Avoid drawing on the quiet zone
+ frame.translate(Vector::new(QUIET_ZONE as f32, QUIET_ZONE as f32));
+
+ // Draw contents
+ self.state
+ .contents
+ .iter()
+ .enumerate()
+ .filter(|(_, value)| **value == qrcode::Color::Dark)
+ .for_each(|(index, _)| {
+ let row = index / self.state.width;
+ let column = index % self.state.width;
+
+ frame.fill_rectangle(
+ Point::new(column as f32, row as f32),
+ Size::UNIT,
+ self.dark,
+ );
+ });
+ });
+
+ (
+ Primitive::Translate {
+ translation: Vector::new(bounds.x, bounds.y),
+ content: Box::new(geometry.into_primitive()),
+ },
+ mouse::Interaction::default(),
+ )
+ }
+}
+
+impl<'a, Message, B> Into<Element<'a, Message, Renderer<B>>> for QRCode<'a>
+where
+ B: Backend,
+{
+ fn into(self) -> Element<'a, Message, Renderer<B>> {
+ Element::new(self)
+ }
+}
+
+/// The state of a [`QRCode`].
+///
+/// It stores the data that will be displayed.
+#[derive(Debug)]
+pub struct State {
+ contents: Vec<qrcode::Color>,
+ width: usize,
+ cache: canvas::Cache,
+}
+
+impl State {
+ /// Creates a new [`State`] with the provided data.
+ ///
+ /// This method uses an [`ErrorCorrection::Medium`] and chooses the smallest
+ /// size to display the data.
+ pub fn new(data: impl AsRef<[u8]>) -> Result<Self, Error> {
+ let encoded = qrcode::QrCode::new(data)?;
+
+ Ok(Self::build(encoded))
+ }
+
+ /// Creates a new [`State`] with the provided [`ErrorCorrection`].
+ pub fn with_error_correction(
+ data: impl AsRef<[u8]>,
+ error_correction: ErrorCorrection,
+ ) -> Result<Self, Error> {
+ let encoded = qrcode::QrCode::with_error_correction_level(
+ data,
+ error_correction.into(),
+ )?;
+
+ Ok(Self::build(encoded))
+ }
+
+ /// Creates a new [`State`] with the provided [`Version`] and
+ /// [`ErrorCorrection`].
+ pub fn with_version(
+ data: impl AsRef<[u8]>,
+ version: Version,
+ error_correction: ErrorCorrection,
+ ) -> Result<Self, Error> {
+ let encoded = qrcode::QrCode::with_version(
+ data,
+ version.into(),
+ error_correction.into(),
+ )?;
+
+ Ok(Self::build(encoded))
+ }
+
+ fn build(encoded: qrcode::QrCode) -> Self {
+ let width = encoded.width();
+ let contents = encoded.into_colors();
+
+ Self {
+ contents,
+ width,
+ cache: canvas::Cache::new(),
+ }
+ }
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+/// The size of a [`QRCode`].
+///
+/// The higher the version the larger the grid of cells, and therefore the more
+/// information the [`QRCode`] can carry.
+pub enum Version {
+ /// A normal QR code version. It should be between 1 and 40.
+ Normal(u8),
+
+ /// A micro QR code version. It should be between 1 and 4.
+ Micro(u8),
+}
+
+impl From<Version> for qrcode::Version {
+ fn from(version: Version) -> Self {
+ match version {
+ Version::Normal(v) => qrcode::Version::Normal(i16::from(v)),
+ Version::Micro(v) => qrcode::Version::Micro(i16::from(v)),
+ }
+ }
+}
+
+/// The error correction level.
+///
+/// It controls the amount of data that can be damaged while still being able
+/// to recover the original information.
+///
+/// A higher error correction level allows for more corrupted data.
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub enum ErrorCorrection {
+ /// Low error correction. 7% of the data can be restored.
+ Low,
+ /// Medium error correction. 15% of the data can be restored.
+ Medium,
+ /// Quartile error correction. 25% of the data can be restored.
+ Quartile,
+ /// High error correction. 30% of the data can be restored.
+ High,
+}
+
+impl From<ErrorCorrection> for qrcode::EcLevel {
+ fn from(ec_level: ErrorCorrection) -> Self {
+ match ec_level {
+ ErrorCorrection::Low => qrcode::EcLevel::L,
+ ErrorCorrection::Medium => qrcode::EcLevel::M,
+ ErrorCorrection::Quartile => qrcode::EcLevel::Q,
+ ErrorCorrection::High => qrcode::EcLevel::H,
+ }
+ }
+}
+
+/// An error that occurred when building a [`State`] for a [`QRCode`].
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Error)]
+pub enum Error {
+ /// The data is too long to encode in a QR code for the chosen [`Version`].
+ #[error(
+ "The data is too long to encode in a QR code for the chosen version"
+ )]
+ DataTooLong,
+
+ /// The chosen [`Version`] and [`ErrorCorrection`] combination is invalid.
+ #[error(
+ "The chosen version and error correction level combination is invalid."
+ )]
+ InvalidVersion,
+
+ /// One or more characters in the provided data are not supported by the
+ /// chosen [`Version`].
+ #[error(
+ "One or more characters in the provided data are not supported by the \
+ chosen version"
+ )]
+ UnsupportedCharacterSet,
+
+ /// The chosen ECI designator is invalid. A valid designator should be
+ /// between 0 and 999999.
+ #[error(
+ "The chosen ECI designator is invalid. A valid designator should be \
+ between 0 and 999999."
+ )]
+ InvalidEciDesignator,
+
+ /// A character that does not belong to the character set was found.
+ #[error("A character that does not belong to the character set was found")]
+ InvalidCharacter,
+}
+
+impl From<qrcode::types::QrError> for Error {
+ fn from(error: qrcode::types::QrError) -> Self {
+ use qrcode::types::QrError;
+
+ match error {
+ QrError::DataTooLong => Error::DataTooLong,
+ QrError::InvalidVersion => Error::InvalidVersion,
+ QrError::UnsupportedCharacterSet => Error::UnsupportedCharacterSet,
+ QrError::InvalidEciDesignator => Error::InvalidEciDesignator,
+ QrError::InvalidCharacter => Error::InvalidCharacter,
+ }
+ }
+}
diff --git a/graphics/src/widget/radio.rs b/graphics/src/widget/radio.rs
index da41ac47..fd3d8145 100644
--- a/graphics/src/widget/radio.rs
+++ b/graphics/src/widget/radio.rs
@@ -42,7 +42,7 @@ where
let radio = Primitive::Quad {
bounds,
background: style.background,
- border_radius: (size / 2.0) as u16,
+ border_radius: size / 2.0,
border_width: style.border_width,
border_color: style.border_color,
};
@@ -58,8 +58,8 @@ where
height: bounds.height - dot_size,
},
background: Background::Color(style.dot_color),
- border_radius: (dot_size / 2.0) as u16,
- border_width: 0,
+ border_radius: dot_size / 2.0,
+ border_width: 0.0,
border_color: Color::TRANSPARENT,
};
diff --git a/graphics/src/widget/row.rs b/graphics/src/widget/row.rs
index 4c1dbadc..397d80bf 100644
--- a/graphics/src/widget/row.rs
+++ b/graphics/src/widget/row.rs
@@ -1,7 +1,7 @@
use crate::{Backend, Primitive, Renderer};
use iced_native::mouse;
use iced_native::row;
-use iced_native::{Element, Layout, Point};
+use iced_native::{Element, Layout, Point, Rectangle};
/// A container that distributes its contents horizontally.
pub type Row<'a, Message, Backend> =
@@ -17,6 +17,7 @@ where
content: &[Element<'_, Message, Self>],
layout: Layout<'_>,
cursor_position: Point,
+ viewport: &Rectangle,
) -> Self::Output {
let mut mouse_interaction = mouse::Interaction::default();
@@ -26,8 +27,13 @@ where
.iter()
.zip(layout.children())
.map(|(child, layout)| {
- let (primitive, new_mouse_interaction) =
- child.draw(self, defaults, layout, cursor_position);
+ let (primitive, new_mouse_interaction) = child.draw(
+ self,
+ defaults,
+ layout,
+ cursor_position,
+ viewport,
+ );
if new_mouse_interaction > mouse_interaction {
mouse_interaction = new_mouse_interaction;
diff --git a/graphics/src/widget/rule.rs b/graphics/src/widget/rule.rs
index a7a5d0e7..835ebed8 100644
--- a/graphics/src/widget/rule.rs
+++ b/graphics/src/widget/rule.rs
@@ -43,7 +43,7 @@ where
},
background: Background::Color(style.color),
border_radius: style.radius,
- border_width: 0,
+ border_width: 0.0,
border_color: Color::TRANSPARENT,
}
} else {
@@ -63,7 +63,7 @@ where
},
background: Background::Color(style.color),
border_radius: style.radius,
- border_width: 0,
+ border_width: 0.0,
border_color: Color::TRANSPARENT,
}
};
diff --git a/graphics/src/widget/scrollable.rs b/graphics/src/widget/scrollable.rs
index b149db0a..2220e4b8 100644
--- a/graphics/src/widget/scrollable.rs
+++ b/graphics/src/widget/scrollable.rs
@@ -15,9 +15,6 @@ pub use iced_style::scrollable::{Scrollbar, Scroller, StyleSheet};
pub type Scrollable<'a, Message, Backend> =
iced_native::Scrollable<'a, Message, Renderer<Backend>>;
-const SCROLLBAR_WIDTH: u16 = 10;
-const SCROLLBAR_MARGIN: u16 = 2;
-
impl<B> scrollable::Renderer for Renderer<B>
where
B: Backend,
@@ -29,29 +26,45 @@ where
bounds: Rectangle,
content_bounds: Rectangle,
offset: u32,
+ scrollbar_width: u16,
+ scrollbar_margin: u16,
+ scroller_width: u16,
) -> Option<scrollable::Scrollbar> {
if content_bounds.height > bounds.height {
+ let outer_width =
+ scrollbar_width.max(scroller_width) + 2 * scrollbar_margin;
+
+ let outer_bounds = Rectangle {
+ x: bounds.x + bounds.width - outer_width as f32,
+ y: bounds.y,
+ width: outer_width as f32,
+ height: bounds.height,
+ };
+
let scrollbar_bounds = Rectangle {
x: bounds.x + bounds.width
- - f32::from(SCROLLBAR_WIDTH + 2 * SCROLLBAR_MARGIN),
+ - f32::from(outer_width / 2 + scrollbar_width / 2),
y: bounds.y,
- width: f32::from(SCROLLBAR_WIDTH + 2 * SCROLLBAR_MARGIN),
+ width: scrollbar_width as f32,
height: bounds.height,
};
let ratio = bounds.height / content_bounds.height;
- let scrollbar_height = bounds.height * ratio;
+ let scroller_height = bounds.height * ratio;
let y_offset = offset as f32 * ratio;
let scroller_bounds = Rectangle {
- x: scrollbar_bounds.x + f32::from(SCROLLBAR_MARGIN),
+ x: bounds.x + bounds.width
+ - f32::from(outer_width / 2 + scroller_width / 2),
y: scrollbar_bounds.y + y_offset,
- width: scrollbar_bounds.width - f32::from(2 * SCROLLBAR_MARGIN),
- height: scrollbar_height,
+ width: scroller_width as f32,
+ height: scroller_height,
};
Some(scrollable::Scrollbar {
+ outer_bounds,
bounds: scrollbar_bounds,
+ margin: scrollbar_margin,
scroller: scrollable::Scroller {
bounds: scroller_bounds,
},
@@ -90,7 +103,7 @@ where
};
let is_scrollbar_visible =
- style.background.is_some() || style.border_width > 0;
+ style.background.is_some() || style.border_width > 0.0;
let scroller = if is_mouse_over
|| state.is_scroller_grabbed()
@@ -109,12 +122,7 @@ where
let scrollbar = if is_scrollbar_visible {
Primitive::Quad {
- bounds: Rectangle {
- x: scrollbar.bounds.x + f32::from(SCROLLBAR_MARGIN),
- width: scrollbar.bounds.width
- - f32::from(2 * SCROLLBAR_MARGIN),
- ..scrollbar.bounds
- },
+ bounds: scrollbar.bounds,
background: style
.background
.unwrap_or(Background::Color(Color::TRANSPARENT)),
@@ -126,8 +134,16 @@ where
Primitive::None
};
+ let scroll = Primitive::Clip {
+ bounds,
+ offset: Vector::new(0, 0),
+ content: Box::new(Primitive::Group {
+ primitives: vec![scrollbar, scroller],
+ }),
+ };
+
Primitive::Group {
- primitives: vec![clip, scrollbar, scroller],
+ primitives: vec![clip, scroll],
}
} else {
content
diff --git a/graphics/src/widget/slider.rs b/graphics/src/widget/slider.rs
index 99f0a098..aeceec3f 100644
--- a/graphics/src/widget/slider.rs
+++ b/graphics/src/widget/slider.rs
@@ -1,9 +1,6 @@
//! Display an interactive selector of a single value from a range of values.
//!
//! A [`Slider`] has some local [`State`].
-//!
-//! [`Slider`]: struct.Slider.html
-//! [`State`]: struct.State.html
use crate::{Backend, Primitive, Renderer};
use iced_native::mouse;
use iced_native::slider;
@@ -57,8 +54,8 @@ where
height: 2.0,
},
background: Background::Color(style.rail_colors.0),
- border_radius: 0,
- border_width: 0,
+ border_radius: 0.0,
+ border_width: 0.0,
border_color: Color::TRANSPARENT,
},
Primitive::Quad {
@@ -69,20 +66,18 @@ where
height: 2.0,
},
background: Background::Color(style.rail_colors.1),
- border_radius: 0,
- border_width: 0,
+ border_radius: 0.0,
+ border_width: 0.0,
border_color: Color::TRANSPARENT,
},
);
- let (range_start, range_end) = range.into_inner();
-
let (handle_width, handle_height, handle_border_radius) = match style
.handle
.shape
{
HandleShape::Circle { radius } => {
- (f32::from(radius * 2), f32::from(radius * 2), radius)
+ (radius * 2.0, radius * 2.0, radius)
}
HandleShape::Rectangle {
width,
@@ -90,8 +85,14 @@ where
} => (f32::from(width), f32::from(bounds.height), border_radius),
};
- let handle_offset = (bounds.width - handle_width)
- * ((value - range_start) / (range_end - range_start).max(1.0));
+ let (range_start, range_end) = range.into_inner();
+
+ let handle_offset = if range_start >= range_end {
+ 0.0
+ } else {
+ (bounds.width - handle_width) * (value - range_start)
+ / (range_end - range_start)
+ };
let handle = Primitive::Quad {
bounds: Rectangle {
diff --git a/graphics/src/widget/text_input.rs b/graphics/src/widget/text_input.rs
index 575d67f5..c269022b 100644
--- a/graphics/src/widget/text_input.rs
+++ b/graphics/src/widget/text_input.rs
@@ -1,9 +1,6 @@
//! Display fields that can be filled with text.
//!
//! A [`TextInput`] has some local [`State`].
-//!
-//! [`TextInput`]: struct.TextInput.html
-//! [`State`]: struct.State.html
use crate::backend::{self, Backend};
use crate::{Primitive, Renderer};
use iced_native::mouse;
@@ -149,8 +146,8 @@ where
background: Background::Color(
style_sheet.value_color(),
),
- border_radius: 0,
- border_width: 0,
+ border_radius: 0.0,
+ border_width: 0.0,
border_color: Color::TRANSPARENT,
},
offset,
@@ -193,8 +190,8 @@ where
background: Background::Color(
style_sheet.selection_color(),
),
- border_radius: 0,
- border_width: 0,
+ border_radius: 0.0,
+ border_width: 0.0,
border_color: Color::TRANSPARENT,
},
if end == right {
diff --git a/graphics/src/widget/toggler.rs b/graphics/src/widget/toggler.rs
new file mode 100644
index 00000000..852d18ee
--- /dev/null
+++ b/graphics/src/widget/toggler.rs
@@ -0,0 +1,99 @@
+//! Show toggle controls using togglers.
+use crate::backend::{self, Backend};
+use crate::{Primitive, Renderer};
+use iced_native::mouse;
+use iced_native::toggler;
+use iced_native::Rectangle;
+
+pub use iced_style::toggler::{Style, StyleSheet};
+
+/// Makes sure that the border radius of the toggler looks good at every size.
+const BORDER_RADIUS_RATIO: f32 = 32.0 / 13.0;
+
+/// The space ratio between the background Quad and the Toggler bounds, and
+/// between the background Quad and foreground Quad.
+const SPACE_RATIO: f32 = 0.05;
+
+/// A toggler that can be toggled.
+///
+/// This is an alias of an `iced_native` toggler with an `iced_wgpu::Renderer`.
+pub type Toggler<Message, Backend> =
+ iced_native::Toggler<Message, Renderer<Backend>>;
+
+impl<B> toggler::Renderer for Renderer<B>
+where
+ B: Backend + backend::Text,
+{
+ type Style = Box<dyn StyleSheet>;
+
+ const DEFAULT_SIZE: u16 = 20;
+
+ fn draw(
+ &mut self,
+ bounds: Rectangle,
+ is_active: bool,
+ is_mouse_over: bool,
+ label: Option<Self::Output>,
+ style_sheet: &Self::Style,
+ ) -> Self::Output {
+ let style = if is_mouse_over {
+ style_sheet.hovered(is_active)
+ } else {
+ style_sheet.active(is_active)
+ };
+
+ let border_radius = bounds.height as f32 / BORDER_RADIUS_RATIO;
+ let space = SPACE_RATIO * bounds.height as f32;
+
+ let toggler_background_bounds = Rectangle {
+ x: bounds.x + space,
+ y: bounds.y + space,
+ width: bounds.width - (2.0 * space),
+ height: bounds.height - (2.0 * space),
+ };
+
+ let toggler_background = Primitive::Quad {
+ bounds: toggler_background_bounds,
+ background: style.background.into(),
+ border_radius,
+ border_width: 1.0,
+ border_color: style.background_border.unwrap_or(style.background),
+ };
+
+ let toggler_foreground_bounds = Rectangle {
+ x: bounds.x
+ + if is_active {
+ bounds.width - 2.0 * space - (bounds.height - (4.0 * space))
+ } else {
+ 2.0 * space
+ },
+ y: bounds.y + (2.0 * space),
+ width: bounds.height - (4.0 * space),
+ height: bounds.height - (4.0 * space),
+ };
+
+ let toggler_foreground = Primitive::Quad {
+ bounds: toggler_foreground_bounds,
+ background: style.foreground.into(),
+ border_radius,
+ border_width: 1.0,
+ border_color: style.foreground_border.unwrap_or(style.foreground),
+ };
+
+ (
+ Primitive::Group {
+ primitives: match label {
+ Some((l, _)) => {
+ vec![l, toggler_background, toggler_foreground]
+ }
+ None => vec![toggler_background, toggler_foreground],
+ },
+ },
+ if is_mouse_over {
+ mouse::Interaction::Pointer
+ } else {
+ mouse::Interaction::default()
+ },
+ )
+ }
+}
diff --git a/graphics/src/widget/tooltip.rs b/graphics/src/widget/tooltip.rs
new file mode 100644
index 00000000..493a6389
--- /dev/null
+++ b/graphics/src/widget/tooltip.rs
@@ -0,0 +1,168 @@
+//! Decorate content and apply alignment.
+use crate::backend::{self, Backend};
+use crate::defaults::{self, Defaults};
+use crate::{Primitive, Renderer, Vector};
+
+use iced_native::container;
+use iced_native::layout::{self, Layout};
+use iced_native::{Element, Padding, Point, Rectangle, Size, Text};
+
+/// An element decorating some content.
+///
+/// This is an alias of an `iced_native` tooltip with a default
+/// `Renderer`.
+pub type Tooltip<'a, Message, Backend> =
+ iced_native::Tooltip<'a, Message, Renderer<Backend>>;
+
+pub use iced_native::tooltip::Position;
+
+impl<B> iced_native::tooltip::Renderer for Renderer<B>
+where
+ B: Backend + backend::Text,
+{
+ const DEFAULT_PADDING: u16 = 5;
+
+ fn draw<Message>(
+ &mut self,
+ defaults: &Defaults,
+ cursor_position: Point,
+ content_layout: Layout<'_>,
+ viewport: &Rectangle,
+ content: &Element<'_, Message, Self>,
+ tooltip: &Text<Self>,
+ position: Position,
+ style_sheet: &<Self as container::Renderer>::Style,
+ gap: u16,
+ padding: u16,
+ ) -> Self::Output {
+ let (content, mouse_interaction) = content.draw(
+ self,
+ &defaults,
+ content_layout,
+ cursor_position,
+ viewport,
+ );
+
+ let bounds = content_layout.bounds();
+
+ if bounds.contains(cursor_position) {
+ use iced_native::Widget;
+
+ let gap = f32::from(gap);
+ let style = style_sheet.style();
+
+ let defaults = Defaults {
+ text: defaults::Text {
+ color: style.text_color.unwrap_or(defaults.text.color),
+ },
+ };
+
+ let text_layout = Widget::<(), Self>::layout(
+ tooltip,
+ self,
+ &layout::Limits::new(Size::ZERO, viewport.size())
+ .pad(Padding::new(padding)),
+ );
+
+ let padding = f32::from(padding);
+ let text_bounds = text_layout.bounds();
+ let x_center = bounds.x + (bounds.width - text_bounds.width) / 2.0;
+ let y_center =
+ bounds.y + (bounds.height - text_bounds.height) / 2.0;
+
+ let mut tooltip_bounds = {
+ let offset = match position {
+ Position::Top => Vector::new(
+ x_center,
+ bounds.y - text_bounds.height - gap - padding,
+ ),
+ Position::Bottom => Vector::new(
+ x_center,
+ bounds.y + bounds.height + gap + padding,
+ ),
+ Position::Left => Vector::new(
+ bounds.x - text_bounds.width - gap - padding,
+ y_center,
+ ),
+ Position::Right => Vector::new(
+ bounds.x + bounds.width + gap + padding,
+ y_center,
+ ),
+ Position::FollowCursor => Vector::new(
+ cursor_position.x,
+ cursor_position.y - text_bounds.height,
+ ),
+ };
+
+ Rectangle {
+ x: offset.x - padding,
+ y: offset.y - padding,
+ width: text_bounds.width + padding * 2.0,
+ height: text_bounds.height + padding * 2.0,
+ }
+ };
+
+ if tooltip_bounds.x < viewport.x {
+ tooltip_bounds.x = viewport.x;
+ } else if viewport.x + viewport.width
+ < tooltip_bounds.x + tooltip_bounds.width
+ {
+ tooltip_bounds.x =
+ viewport.x + viewport.width - tooltip_bounds.width;
+ }
+
+ if tooltip_bounds.y < viewport.y {
+ tooltip_bounds.y = viewport.y;
+ } else if viewport.y + viewport.height
+ < tooltip_bounds.y + tooltip_bounds.height
+ {
+ tooltip_bounds.y =
+ viewport.y + viewport.height - tooltip_bounds.height;
+ }
+
+ let (tooltip, _) = Widget::<(), Self>::draw(
+ tooltip,
+ self,
+ &defaults,
+ Layout::with_offset(
+ Vector::new(
+ tooltip_bounds.x + padding,
+ tooltip_bounds.y + padding,
+ ),
+ &text_layout,
+ ),
+ cursor_position,
+ viewport,
+ );
+
+ (
+ Primitive::Group {
+ primitives: vec![
+ content,
+ Primitive::Clip {
+ bounds: *viewport,
+ offset: Vector::new(0, 0),
+ content: Box::new(
+ if let Some(background) =
+ crate::container::background(
+ tooltip_bounds,
+ &style,
+ )
+ {
+ Primitive::Group {
+ primitives: vec![background, tooltip],
+ }
+ } else {
+ tooltip
+ },
+ ),
+ },
+ ],
+ },
+ mouse_interaction,
+ )
+ } else {
+ (content, mouse_interaction)
+ }
+ }
+}
diff --git a/graphics/src/window.rs b/graphics/src/window.rs
index 3e74db5f..365ddfbc 100644
--- a/graphics/src/window.rs
+++ b/graphics/src/window.rs
@@ -4,7 +4,7 @@ mod compositor;
#[cfg(feature = "opengl")]
mod gl_compositor;
-pub use compositor::Compositor;
+pub use compositor::{Compositor, SwapChainError};
#[cfg(feature = "opengl")]
pub use gl_compositor::GLCompositor;
diff --git a/graphics/src/window/compositor.rs b/graphics/src/window/compositor.rs
index 7674f227..de2a6990 100644
--- a/graphics/src/window/compositor.rs
+++ b/graphics/src/window/compositor.rs
@@ -1,6 +1,9 @@
use crate::{Color, Error, Viewport};
+
use iced_native::mouse;
+
use raw_window_handle::HasRawWindowHandle;
+use thiserror::Error;
/// A graphics compositor that can draw to windows.
pub trait Compositor: Sized {
@@ -16,14 +19,15 @@ pub trait Compositor: Sized {
/// The swap chain of the backend.
type SwapChain;
- /// Creates a new [`Backend`].
- ///
- /// [`Backend`]: trait.Backend.html
- fn new(settings: Self::Settings) -> Result<(Self, Self::Renderer), Error>;
+ /// Creates a new [`Compositor`].
+ fn new<W: HasRawWindowHandle>(
+ settings: Self::Settings,
+ compatible_window: Option<&W>,
+ ) -> Result<(Self, Self::Renderer), Error>;
/// Crates a new [`Surface`] for the given window.
///
- /// [`Surface`]: #associatedtype.Surface
+ /// [`Surface`]: Self::Surface
fn create_surface<W: HasRawWindowHandle>(
&mut self,
window: &W,
@@ -31,8 +35,8 @@ pub trait Compositor: Sized {
/// Crates a new [`SwapChain`] for the given [`Surface`].
///
- /// [`SwapChain`]: #associatedtype.SwapChain
- /// [`Surface`]: #associatedtype.Surface
+ /// [`SwapChain`]: Self::SwapChain
+ /// [`Surface`]: Self::Surface
fn create_swap_chain(
&mut self,
surface: &Self::Surface,
@@ -42,8 +46,7 @@ pub trait Compositor: Sized {
/// Draws the output primitives to the next frame of the given [`SwapChain`].
///
- /// [`SwapChain`]: #associatedtype.SwapChain
- /// [`Surface`]: #associatedtype.Surface
+ /// [`SwapChain`]: Self::SwapChain
fn draw<T: AsRef<str>>(
&mut self,
renderer: &mut Self::Renderer,
@@ -52,5 +55,26 @@ pub trait Compositor: Sized {
background_color: Color,
output: &<Self::Renderer as iced_native::Renderer>::Output,
overlay: &[T],
- ) -> mouse::Interaction;
+ ) -> Result<mouse::Interaction, SwapChainError>;
+}
+
+/// Result of an unsuccessful call to [`Compositor::draw`].
+#[derive(Clone, PartialEq, Eq, Debug, Error)]
+pub enum SwapChainError {
+ /// A timeout was encountered while trying to acquire the next frame.
+ #[error(
+ "A timeout was encountered while trying to acquire the next frame"
+ )]
+ Timeout,
+ /// The underlying surface has changed, and therefore the swap chain must be updated.
+ #[error(
+ "The underlying surface has changed, and therefore the swap chain must be updated."
+ )]
+ Outdated,
+ /// The swap chain has been lost and needs to be recreated.
+ #[error("The swap chain has been lost and needs to be recreated")]
+ Lost,
+ /// There is no more memory left to allocate a new frame.
+ #[error("There is no more memory left to allocate a new frame")]
+ OutOfMemory,
}
diff --git a/graphics/src/window/gl_compositor.rs b/graphics/src/window/gl_compositor.rs
index 1f37642e..34d70be3 100644
--- a/graphics/src/window/gl_compositor.rs
+++ b/graphics/src/window/gl_compositor.rs
@@ -15,28 +15,27 @@ use core::ffi::c_void;
/// If you implement an OpenGL renderer, you can implement this trait to ease
/// integration with existing windowing shells, like `iced_glutin`.
pub trait GLCompositor: Sized {
- /// The renderer of the [`Compositor`].
+ /// The renderer of the [`GLCompositor`].
///
/// This should point to your renderer type, which could be a type alias
/// of the [`Renderer`] provided in this crate with with a specific
/// [`Backend`].
///
- /// [`Compositor`]: trait.Compositor.html
- /// [`Renderer`]: ../struct.Renderer.html
- /// [`Backend`]: ../backend/trait.Backend.html
+ /// [`Renderer`]: crate::Renderer
+ /// [`Backend`]: crate::Backend
type Renderer: iced_native::Renderer;
- /// The settings of the [`Compositor`].
+ /// The settings of the [`GLCompositor`].
///
/// It's up to you to decide the configuration supported by your renderer!
type Settings: Default;
- /// Creates a new [`Compositor`] and [`Renderer`] with the given
+ /// Creates a new [`GLCompositor`] and [`Renderer`] with the given
/// [`Settings`] and an OpenGL address loader function.
///
- /// [`Compositor`]: trait.Compositor.html
- /// [`Renderer`]: #associatedtype.Renderer
- /// [`Backend`]: ../backend/trait.Backend.html
+ /// [`Renderer`]: crate::Renderer
+ /// [`Backend`]: crate::Backend
+ /// [`Settings`]: Self::Settings
#[allow(unsafe_code)]
unsafe fn new(
settings: Self::Settings,
@@ -44,19 +43,15 @@ pub trait GLCompositor: Sized {
) -> Result<(Self, Self::Renderer), Error>;
/// Returns the amount of samples that should be used when configuring
- /// an OpenGL context for this [`Compositor`].
- ///
- /// [`Compositor`]: trait.Compositor.html
+ /// an OpenGL context for this [`GLCompositor`].
fn sample_count(settings: &Self::Settings) -> u32;
- /// Resizes the viewport of the [`Compositor`].
- ///
- /// [`Compositor`]: trait.Compositor.html
+ /// Resizes the viewport of the [`GLCompositor`].
fn resize_viewport(&mut self, physical_size: Size<u32>);
/// Draws the provided output with the given [`Renderer`].
///
- /// [`Compositor`]: trait.Compositor.html
+ /// [`Renderer`]: crate::Renderer
fn draw<T: AsRef<str>>(
&mut self,
renderer: &mut Self::Renderer,
diff --git a/native/Cargo.toml b/native/Cargo.toml
index 13052a93..a3134ef4 100644
--- a/native/Cargo.toml
+++ b/native/Cargo.toml
@@ -1,6 +1,6 @@
[package]
name = "iced_native"
-version = "0.2.2"
+version = "0.4.0"
authors = ["Héctor Ramón Jiménez <hector0193@gmail.com>"]
edition = "2018"
description = "A renderer-agnostic library for native GUIs"
@@ -16,10 +16,10 @@ unicode-segmentation = "1.6"
num-traits = "0.2"
[dependencies.iced_core]
-version = "0.2"
+version = "0.4"
path = "../core"
[dependencies.iced_futures]
-version = "0.1"
+version = "0.3"
path = "../futures"
features = ["thread-pool"]
diff --git a/native/README.md b/native/README.md
index 31c7db88..0d79690a 100644
--- a/native/README.md
+++ b/native/README.md
@@ -14,7 +14,9 @@ To achieve this, it introduces a bunch of reusable interfaces:
- A bunch of `Renderer` traits, meant to keep the crate renderer-agnostic.
- A `Windowed` 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.
-![iced_native](../docs/graphs/native.png)
+<p align="center">
+ <img alt="The native target" src="../docs/graphs/native.png" width="80%">
+</p>
[documentation]: https://docs.rs/iced_native
[`iced_core`]: ../core
@@ -26,7 +28,7 @@ To achieve this, it introduces a bunch of reusable interfaces:
Add `iced_native` as a dependency in your `Cargo.toml`:
```toml
-iced_native = "0.2"
+iced_native = "0.4"
```
__Iced moves fast and the `master` branch can contain breaking changes!__ If
diff --git a/native/src/clipboard.rs b/native/src/clipboard.rs
index 4c574590..081b4004 100644
--- a/native/src/clipboard.rs
+++ b/native/src/clipboard.rs
@@ -1,8 +1,23 @@
+//! Access the clipboard.
+
/// A buffer for short-term storage and transfer within and between
/// applications.
pub trait Clipboard {
- /// Returns the current content of the [`Clipboard`] as text.
- ///
- /// [`Clipboard`]: trait.Clipboard.html
- fn content(&self) -> Option<String>;
+ /// Reads the current content of the [`Clipboard`] as text.
+ fn read(&self) -> Option<String>;
+
+ /// Writes the given text contents to the [`Clipboard`].
+ fn write(&mut self, contents: String);
+}
+
+/// A null implementation of the [`Clipboard`] trait.
+#[derive(Debug, Clone, Copy)]
+pub struct Null;
+
+impl Clipboard for Null {
+ fn read(&self) -> Option<String> {
+ None
+ }
+
+ fn write(&mut self, _contents: String) {}
}
diff --git a/native/src/debug/basic.rs b/native/src/debug/basic.rs
index 8a712038..a42f66ea 100644
--- a/native/src/debug/basic.rs
+++ b/native/src/debug/basic.rs
@@ -32,9 +32,7 @@ pub struct Debug {
}
impl Debug {
- /// Creates a new [`Debug`].
- ///
- /// [`Debug`]: struct.Debug.html
+ /// Creates a new [`struct@Debug`].
pub fn new() -> Self {
let now = time::Instant::now();
diff --git a/native/src/element.rs b/native/src/element.rs
index 514a135b..5c84a388 100644
--- a/native/src/element.rs
+++ b/native/src/element.rs
@@ -1,6 +1,8 @@
+use crate::event::{self, Event};
+use crate::layout;
+use crate::overlay;
use crate::{
- layout, overlay, Clipboard, Color, Event, Hasher, Layout, Length, Point,
- Widget,
+ Clipboard, Color, Hasher, Layout, Length, Point, Rectangle, Widget,
};
/// A generic [`Widget`].
@@ -12,8 +14,6 @@ use crate::{
/// to turn it into an [`Element`].
///
/// [built-in widget]: widget/index.html#built-in-widgets
-/// [`Widget`]: widget/trait.Widget.html
-/// [`Element`]: struct.Element.html
#[allow(missing_debug_implementations)]
pub struct Element<'a, Message, Renderer> {
pub(crate) widget: Box<dyn Widget<Message, Renderer> + 'a>,
@@ -24,9 +24,6 @@ where
Renderer: crate::Renderer,
{
/// Creates a new [`Element`] containing the given [`Widget`].
- ///
- /// [`Element`]: struct.Element.html
- /// [`Widget`]: widget/trait.Widget.html
pub fn new(
widget: impl Widget<Message, Renderer> + 'a,
) -> Element<'a, Message, Renderer> {
@@ -40,8 +37,6 @@ where
/// This method is useful when you want to decouple different parts of your
/// UI and make them __composable__.
///
- /// [`Element`]: struct.Element.html
- ///
/// # Example
/// Imagine we want to use [our counter](index.html#usage). But instead of
/// showing a single counter, we want to display many of them. We can reuse
@@ -187,8 +182,7 @@ where
/// The [`Renderer`] will explain the layout of the [`Element`] graphically.
/// This can be very useful for debugging your layout!
///
- /// [`Element`]: struct.Element.html
- /// [`Renderer`]: trait.Renderer.html
+ /// [`Renderer`]: crate::Renderer
pub fn explain<C: Into<Color>>(
self,
color: C,
@@ -203,23 +197,18 @@ where
}
/// Returns the width of the [`Element`].
- ///
- /// [`Element`]: struct.Element.html
pub fn width(&self) -> Length {
self.widget.width()
}
/// Returns the height of the [`Element`].
- ///
- /// [`Element`]: struct.Element.html
pub fn height(&self) -> Length {
self.widget.height()
}
/// Computes the layout of the [`Element`] in the given [`Limits`].
///
- /// [`Element`]: struct.Element.html
- /// [`Limits`]: layout/struct.Limits.html
+ /// [`Limits`]: layout::Limits
pub fn layout(
&self,
renderer: &Renderer,
@@ -229,52 +218,44 @@ where
}
/// Processes a runtime [`Event`].
- ///
- /// [`Event`]: enum.Event.html
pub fn on_event(
&mut self,
event: Event,
layout: Layout<'_>,
cursor_position: Point,
- messages: &mut Vec<Message>,
renderer: &Renderer,
- clipboard: Option<&dyn Clipboard>,
- ) {
+ clipboard: &mut dyn Clipboard,
+ messages: &mut Vec<Message>,
+ ) -> event::Status {
self.widget.on_event(
event,
layout,
cursor_position,
- messages,
renderer,
clipboard,
- );
+ messages,
+ )
}
/// Draws the [`Element`] and its children using the given [`Layout`].
- ///
- /// [`Element`]: struct.Element.html
- /// [`Layout`]: layout/struct.Layout.html
pub fn draw(
&self,
renderer: &mut Renderer,
defaults: &Renderer::Defaults,
layout: Layout<'_>,
cursor_position: Point,
+ viewport: &Rectangle,
) -> Renderer::Output {
self.widget
- .draw(renderer, defaults, layout, cursor_position)
+ .draw(renderer, defaults, layout, cursor_position, viewport)
}
/// Computes the _layout_ hash of the [`Element`].
- ///
- /// [`Element`]: struct.Element.html
pub fn hash_layout(&self, state: &mut Hasher) {
self.widget.hash_layout(state);
}
/// Returns the overlay of the [`Element`], if there is any.
- ///
- /// [`Element`]: struct.Element.html
pub fn overlay<'b>(
&'b mut self,
layout: Layout<'_>,
@@ -330,24 +311,26 @@ where
event: Event,
layout: Layout<'_>,
cursor_position: Point,
- messages: &mut Vec<B>,
renderer: &Renderer,
- clipboard: Option<&dyn Clipboard>,
- ) {
+ clipboard: &mut dyn Clipboard,
+ messages: &mut Vec<B>,
+ ) -> event::Status {
let mut original_messages = Vec::new();
- self.widget.on_event(
+ let status = self.widget.on_event(
event,
layout,
cursor_position,
- &mut original_messages,
renderer,
clipboard,
+ &mut original_messages,
);
original_messages
.drain(..)
.for_each(|message| messages.push((self.mapper)(message)));
+
+ status
}
fn draw(
@@ -356,9 +339,10 @@ where
defaults: &Renderer::Defaults,
layout: Layout<'_>,
cursor_position: Point,
+ viewport: &Rectangle,
) -> Renderer::Output {
self.widget
- .draw(renderer, defaults, layout, cursor_position)
+ .draw(renderer, defaults, layout, cursor_position, viewport)
}
fn hash_layout(&self, state: &mut Hasher) {
@@ -417,17 +401,17 @@ where
event: Event,
layout: Layout<'_>,
cursor_position: Point,
- messages: &mut Vec<Message>,
renderer: &Renderer,
- clipboard: Option<&dyn Clipboard>,
- ) {
+ clipboard: &mut dyn Clipboard,
+ messages: &mut Vec<Message>,
+ ) -> event::Status {
self.element.widget.on_event(
event,
layout,
cursor_position,
- messages,
renderer,
clipboard,
+ messages,
)
}
@@ -437,12 +421,14 @@ where
defaults: &Renderer::Defaults,
layout: Layout<'_>,
cursor_position: Point,
+ viewport: &Rectangle,
) -> Renderer::Output {
renderer.explain(
defaults,
self.element.widget.as_ref(),
layout,
cursor_position,
+ viewport,
self.color,
)
}
diff --git a/native/src/event.rs b/native/src/event.rs
index 606a71d6..1c26b5f2 100644
--- a/native/src/event.rs
+++ b/native/src/event.rs
@@ -1,4 +1,8 @@
-use crate::{keyboard, mouse, window};
+//! Handle events of a user interface.
+use crate::keyboard;
+use crate::mouse;
+use crate::touch;
+use crate::window;
/// A user interface event.
///
@@ -6,7 +10,7 @@ use crate::{keyboard, mouse, window};
/// additional events, feel free to [open an issue] and share your use case!_
///
/// [open an issue]: https://github.com/hecrj/iced/issues
-#[derive(PartialEq, Clone, Debug)]
+#[derive(Debug, Clone, PartialEq)]
pub enum Event {
/// A keyboard event
Keyboard(keyboard::Event),
@@ -16,4 +20,59 @@ pub enum Event {
/// A window event
Window(window::Event),
+
+ /// A touch event
+ Touch(touch::Event),
+
+ /// A platform specific event
+ PlatformSpecific(PlatformSpecific),
+}
+
+/// A platform specific event
+#[derive(Debug, Clone, PartialEq)]
+pub enum PlatformSpecific {
+ /// A MacOS specific event
+ MacOS(MacOS),
+}
+
+/// Describes an event specific to MacOS
+#[derive(Debug, Clone, PartialEq)]
+pub enum MacOS {
+ /// Triggered when the app receives an URL from the system
+ ///
+ /// _**Note:** For this event to be triggered, the executable needs to be properly [bundled]!_
+ ///
+ /// [bundled]: https://developer.apple.com/library/archive/documentation/CoreFoundation/Conceptual/CFBundles/BundleTypes/BundleTypes.html#//apple_ref/doc/uid/10000123i-CH101-SW19
+ ReceivedUrl(String),
+}
+
+/// The status of an [`Event`] after being processed.
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub enum Status {
+ /// The [`Event`] was **NOT** handled by any widget.
+ Ignored,
+
+ /// The [`Event`] was handled and processed by a widget.
+ Captured,
+}
+
+impl Status {
+ /// Merges two [`Status`] into one.
+ ///
+ /// `Captured` takes precedence over `Ignored`:
+ ///
+ /// ```
+ /// use iced_native::event::Status;
+ ///
+ /// assert_eq!(Status::Ignored.merge(Status::Ignored), Status::Ignored);
+ /// assert_eq!(Status::Ignored.merge(Status::Captured), Status::Captured);
+ /// assert_eq!(Status::Captured.merge(Status::Ignored), Status::Captured);
+ /// assert_eq!(Status::Captured.merge(Status::Captured), Status::Captured);
+ /// ```
+ pub fn merge(self, b: Self) -> Self {
+ match self {
+ Status::Ignored => b,
+ Status::Captured => Status::Captured,
+ }
+ }
}
diff --git a/native/src/layout.rs b/native/src/layout.rs
index 93d95e17..b4b4a021 100644
--- a/native/src/layout.rs
+++ b/native/src/layout.rs
@@ -12,8 +12,6 @@ pub use node::Node;
use crate::{Point, Rectangle, Vector};
/// The bounds of a [`Node`] and its children, using absolute coordinates.
-///
-/// [`Node`]: struct.Node.html
#[derive(Debug, Clone, Copy)]
pub struct Layout<'a> {
position: Point,
@@ -21,11 +19,14 @@ pub struct Layout<'a> {
}
impl<'a> Layout<'a> {
- pub(crate) fn new(node: &'a Node) -> Self {
+ /// Creates a new [`Layout`] for the given [`Node`] at the origin.
+ pub fn new(node: &'a Node) -> Self {
Self::with_offset(Vector::new(0.0, 0.0), node)
}
- pub(crate) fn with_offset(offset: Vector, node: &'a Node) -> Self {
+ /// Creates a new [`Layout`] for the given [`Node`] with the provided offset
+ /// from the origin.
+ pub fn with_offset(offset: Vector, node: &'a Node) -> Self {
let bounds = node.bounds();
Self {
@@ -35,8 +36,6 @@ impl<'a> Layout<'a> {
}
/// Returns the position of the [`Layout`].
- ///
- /// [`Layout`]: struct.Layout.html
pub fn position(&self) -> Point {
self.position
}
@@ -45,10 +44,6 @@ impl<'a> Layout<'a> {
///
/// The returned [`Rectangle`] describes the position and size of a
/// [`Node`].
- ///
- /// [`Layout`]: struct.Layout.html
- /// [`Rectangle`]: struct.Rectangle.html
- /// [`Node`]: struct.Node.html
pub fn bounds(&self) -> Rectangle {
let bounds = self.node.bounds();
@@ -61,10 +56,7 @@ impl<'a> Layout<'a> {
}
/// Returns an iterator over the [`Layout`] of the children of a [`Node`].
- ///
- /// [`Layout`]: struct.Layout.html
- /// [`Node`]: struct.Node.html
- pub fn children(&'a self) -> impl Iterator<Item = Layout<'a>> {
+ pub fn children(self) -> impl Iterator<Item = Layout<'a>> {
self.node.children().iter().map(move |node| {
Layout::with_offset(
Vector::new(self.position.x, self.position.y),
diff --git a/native/src/layout/debugger.rs b/native/src/layout/debugger.rs
index e4b21609..0759613f 100644
--- a/native/src/layout/debugger.rs
+++ b/native/src/layout/debugger.rs
@@ -1,8 +1,6 @@
-use crate::{Color, Layout, Point, Renderer, Widget};
+use crate::{Color, Layout, Point, Rectangle, Renderer, Widget};
/// A renderer able to graphically explain a [`Layout`].
-///
-/// [`Layout`]: struct.Layout.html
pub trait Debugger: Renderer {
/// Explains the [`Layout`] of an [`Element`] for debugging purposes.
///
@@ -12,15 +10,15 @@ pub trait Debugger: Renderer {
/// A common approach consists in recursively rendering the bounds of the
/// [`Layout`] and its children.
///
- /// [`Layout`]: struct.Layout.html
- /// [`Element`]: ../struct.Element.html
- /// [`Element::explain`]: ../struct.Element.html#method.explain
+ /// [`Element`]: crate::Element
+ /// [`Element::explain`]: crate::Element::explain
fn explain<Message>(
&mut self,
defaults: &Self::Defaults,
widget: &dyn Widget<Message, Self>,
layout: Layout<'_>,
cursor_position: Point,
+ viewport: &Rectangle,
color: Color,
) -> Self::Output;
}
diff --git a/native/src/layout/flex.rs b/native/src/layout/flex.rs
index 9da75a21..3d3ff82c 100644
--- a/native/src/layout/flex.rs
+++ b/native/src/layout/flex.rs
@@ -16,9 +16,10 @@
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
+
use crate::{
layout::{Limits, Node},
- Align, Element, Point, Size,
+ Align, Element, Padding, Point, Size,
};
/// The main axis of a flex layout.
@@ -58,13 +59,11 @@ impl Axis {
/// padding and alignment to the items as needed.
///
/// It returns a new layout [`Node`].
-///
-/// [`Node`]: ../struct.Node.html
pub fn resolve<Message, Renderer>(
axis: Axis,
renderer: &Renderer,
limits: &Limits,
- padding: f32,
+ padding: Padding,
spacing: f32,
align_items: Align,
items: &[Element<'_, Message, Renderer>],
@@ -143,14 +142,15 @@ where
}
}
- let mut main = padding;
+ let pad = axis.pack(padding.left as f32, padding.top as f32);
+ let mut main = pad.0;
for (i, node) in nodes.iter_mut().enumerate() {
if i > 0 {
main += spacing;
}
- let (x, y) = axis.pack(main, padding);
+ let (x, y) = axis.pack(main, pad.1);
node.move_to(Point::new(x, y));
@@ -168,7 +168,7 @@ where
main += axis.main(size);
}
- let (width, height) = axis.pack(main - padding, cross);
+ let (width, height) = axis.pack(main - pad.0, cross);
let size = limits.resolve(Size::new(width, height));
Node::with_children(size.pad(padding), nodes)
diff --git a/native/src/layout/limits.rs b/native/src/layout/limits.rs
index 664c881a..6d5f6563 100644
--- a/native/src/layout/limits.rs
+++ b/native/src/layout/limits.rs
@@ -1,4 +1,4 @@
-use crate::{Length, Size};
+use crate::{Length, Padding, Size};
/// A set of size constraints for layouting.
#[derive(Debug, Clone, Copy)]
@@ -17,9 +17,6 @@ impl Limits {
};
/// Creates new [`Limits`] with the given minimum and maximum [`Size`].
- ///
- /// [`Limits`]: struct.Limits.html
- /// [`Size`]: ../struct.Size.html
pub const fn new(min: Size, max: Size) -> Limits {
Limits {
min,
@@ -29,32 +26,21 @@ impl Limits {
}
/// Returns the minimum [`Size`] of the [`Limits`].
- ///
- /// [`Limits`]: struct.Limits.html
- /// [`Size`]: ../struct.Size.html
pub fn min(&self) -> Size {
self.min
}
/// Returns the maximum [`Size`] of the [`Limits`].
- ///
- /// [`Limits`]: struct.Limits.html
- /// [`Size`]: ../struct.Size.html
pub fn max(&self) -> Size {
self.max
}
/// Returns the fill [`Size`] of the [`Limits`].
- ///
- /// [`Limits`]: struct.Limits.html
- /// [`Size`]: ../struct.Size.html
pub fn fill(&self) -> Size {
self.fill
}
/// Applies a width constraint to the current [`Limits`].
- ///
- /// [`Limits`]: struct.Limits.html
pub fn width(mut self, width: Length) -> Limits {
match width {
Length::Shrink => {
@@ -77,8 +63,6 @@ impl Limits {
}
/// Applies a height constraint to the current [`Limits`].
- ///
- /// [`Limits`]: struct.Limits.html
pub fn height(mut self, height: Length) -> Limits {
match height {
Length::Shrink => {
@@ -101,8 +85,6 @@ impl Limits {
}
/// Applies a minimum width constraint to the current [`Limits`].
- ///
- /// [`Limits`]: struct.Limits.html
pub fn min_width(mut self, min_width: u32) -> Limits {
self.min.width =
self.min.width.max(min_width as f32).min(self.max.width);
@@ -111,8 +93,6 @@ impl Limits {
}
/// Applies a maximum width constraint to the current [`Limits`].
- ///
- /// [`Limits`]: struct.Limits.html
pub fn max_width(mut self, max_width: u32) -> Limits {
self.max.width =
self.max.width.min(max_width as f32).max(self.min.width);
@@ -121,8 +101,6 @@ impl Limits {
}
/// Applies a minimum height constraint to the current [`Limits`].
- ///
- /// [`Limits`]: struct.Limits.html
pub fn min_height(mut self, min_height: u32) -> Limits {
self.min.height =
self.min.height.max(min_height as f32).min(self.max.height);
@@ -131,8 +109,6 @@ impl Limits {
}
/// Applies a maximum height constraint to the current [`Limits`].
- ///
- /// [`Limits`]: struct.Limits.html
pub fn max_height(mut self, max_height: u32) -> Limits {
self.max.height =
self.max.height.min(max_height as f32).max(self.min.height);
@@ -141,16 +117,14 @@ impl Limits {
}
/// Shrinks the current [`Limits`] to account for the given padding.
- ///
- /// [`Limits`]: struct.Limits.html
- pub fn pad(&self, padding: f32) -> Limits {
- self.shrink(Size::new(padding * 2.0, padding * 2.0))
+ pub fn pad(&self, padding: Padding) -> Limits {
+ self.shrink(Size::new(
+ padding.horizontal() as f32,
+ padding.vertical() as f32,
+ ))
}
/// Shrinks the current [`Limits`] by the given [`Size`].
- ///
- /// [`Limits`]: struct.Limits.html
- /// [`Size`]: ../struct.Size.html
pub fn shrink(&self, size: Size) -> Limits {
let min = Size::new(
(self.min().width - size.width).max(0.0),
@@ -171,8 +145,6 @@ impl Limits {
}
/// Removes the minimum width constraint for the current [`Limits`].
- ///
- /// [`Limits`]: struct.Limits.html
pub fn loose(&self) -> Limits {
Limits {
min: Size::ZERO,
@@ -183,8 +155,6 @@ impl Limits {
/// Computes the resulting [`Size`] that fits the [`Limits`] given the
/// intrinsic size of some content.
- ///
- /// [`Limits`]: struct.Limits.html
pub fn resolve(&self, intrinsic_size: Size) -> Size {
Size::new(
intrinsic_size
diff --git a/native/src/layout/node.rs b/native/src/layout/node.rs
index a265c46a..d7666f31 100644
--- a/native/src/layout/node.rs
+++ b/native/src/layout/node.rs
@@ -9,17 +9,11 @@ pub struct Node {
impl Node {
/// Creates a new [`Node`] with the given [`Size`].
- ///
- /// [`Node`]: struct.Node.html
- /// [`Size`]: ../struct.Size.html
pub const fn new(size: Size) -> Self {
Self::with_children(size, Vec::new())
}
/// Creates a new [`Node`] with the given [`Size`] and children.
- ///
- /// [`Node`]: struct.Node.html
- /// [`Size`]: ../struct.Size.html
pub const fn with_children(size: Size, children: Vec<Node>) -> Self {
Node {
bounds: Rectangle {
@@ -33,30 +27,21 @@ impl Node {
}
/// Returns the [`Size`] of the [`Node`].
- ///
- /// [`Node`]: struct.Node.html
- /// [`Size`]: ../struct.Size.html
pub fn size(&self) -> Size {
Size::new(self.bounds.width, self.bounds.height)
}
/// Returns the bounds of the [`Node`].
- ///
- /// [`Node`]: struct.Node.html
pub fn bounds(&self) -> Rectangle {
self.bounds
}
/// Returns the children of the [`Node`].
- ///
- /// [`Node`]: struct.Node.html
pub fn children(&self) -> &[Node] {
&self.children
}
/// Aligns the [`Node`] in the given space.
- ///
- /// [`Node`]: struct.Node.html
pub fn align(
&mut self,
horizontal_alignment: Align,
@@ -85,8 +70,6 @@ impl Node {
}
/// Moves the [`Node`] to the given position.
- ///
- /// [`Node`]: struct.Node.html
pub fn move_to(&mut self, position: Point) {
self.bounds.x = position.x;
self.bounds.y = position.y;
diff --git a/native/src/lib.rs b/native/src/lib.rs
index 067e3c0a..cbb02506 100644
--- a/native/src/lib.rs
+++ b/native/src/lib.rs
@@ -1,6 +1,6 @@
//! A renderer-agnostic native GUI runtime.
//!
-//! ![`iced_native` crate graph](https://github.com/hecrj/iced/blob/cae26cb7bc627f4a5b3bcf1cd023a0c552e8c65e/docs/graphs/native.png?raw=true)
+//! ![The native path of the Iced ecosystem](https://github.com/hecrj/iced/raw/improvement/update-ecosystem-and-roadmap/docs/graphs/native.png)
//!
//! `iced_native` takes [`iced_core`] and builds a native runtime on top of it,
//! featuring:
@@ -27,14 +27,14 @@
//! [`iced_winit`]: https://github.com/hecrj/iced/tree/master/winit
//! [`druid`]: https://github.com/xi-editor/druid
//! [`raw-window-handle`]: https://github.com/rust-windowing/raw-window-handle
-//! [`Widget`]: widget/trait.Widget.html
-//! [`UserInterface`]: struct.UserInterface.html
-//! [renderer]: renderer/index.html
+//! [renderer]: crate::renderer
#![deny(missing_docs)]
#![deny(missing_debug_implementations)]
#![deny(unused_results)]
#![forbid(unsafe_code)]
#![forbid(rust_2018_idioms)]
+pub mod clipboard;
+pub mod event;
pub mod keyboard;
pub mod layout;
pub mod mouse;
@@ -42,12 +42,11 @@ pub mod overlay;
pub mod program;
pub mod renderer;
pub mod subscription;
+pub mod touch;
pub mod widget;
pub mod window;
-mod clipboard;
mod element;
-mod event;
mod hasher;
mod runtime;
mod user_interface;
@@ -62,8 +61,8 @@ mod debug;
mod debug;
pub use iced_core::{
- Align, Background, Color, Font, HorizontalAlignment, Length, Point,
- Rectangle, Size, Vector, VerticalAlignment,
+ menu, Align, Background, Color, Font, HorizontalAlignment, Length, Menu,
+ Padding, Point, Rectangle, Size, Vector, VerticalAlignment,
};
pub use iced_futures::{executor, futures, Command};
diff --git a/native/src/mouse/click.rs b/native/src/mouse/click.rs
index d27bc67e..6c8b61a5 100644
--- a/native/src/mouse/click.rs
+++ b/native/src/mouse/click.rs
@@ -36,8 +36,6 @@ impl Kind {
impl Click {
/// Creates a new [`Click`] with the given position and previous last
/// [`Click`].
- ///
- /// [`Click`]: struct.Click.html
pub fn new(position: Point, previous: Option<Click>) -> Click {
let time = Instant::now();
@@ -59,9 +57,6 @@ impl Click {
}
/// Returns the [`Kind`] of [`Click`].
- ///
- /// [`Kind`]: enum.Kind.html
- /// [`Click`]: struct.Click.html
pub fn kind(&self) -> Kind {
self.kind
}
diff --git a/native/src/overlay.rs b/native/src/overlay.rs
index 7c3bec32..84145e7f 100644
--- a/native/src/overlay.rs
+++ b/native/src/overlay.rs
@@ -6,7 +6,9 @@ pub mod menu;
pub use element::Element;
pub use menu::Menu;
-use crate::{layout, Clipboard, Event, Hasher, Layout, Point, Size};
+use crate::event::{self, Event};
+use crate::layout;
+use crate::{Clipboard, Hasher, Layout, Point, Size};
/// An interactive component that can be displayed on top of other widgets.
pub trait Overlay<Message, Renderer>
@@ -18,9 +20,7 @@ where
/// This [`Node`] is used by the runtime to compute the [`Layout`] of the
/// user interface.
///
- /// [`Node`]: ../layout/struct.Node.html
- /// [`Widget`]: trait.Overlay.html
- /// [`Layout`]: ../layout/struct.Layout.html
+ /// [`Node`]: layout::Node
fn layout(
&self,
renderer: &Renderer,
@@ -29,8 +29,6 @@ where
) -> layout::Node;
/// Draws the [`Overlay`] using the associated `Renderer`.
- ///
- /// [`Overlay`]: trait.Overlay.html
fn draw(
&self,
renderer: &mut Renderer,
@@ -49,9 +47,7 @@ where
/// For example, the [`Text`] widget does not hash its color property, as
/// its value cannot affect the overall [`Layout`] of the user interface.
///
- /// [`Overlay`]: trait.Overlay.html
- /// [`Layout`]: ../layout/struct.Layout.html
- /// [`Text`]: text/struct.Text.html
+ /// [`Text`]: crate::widget::Text
fn hash_layout(&self, state: &mut Hasher, position: Point);
/// Processes a runtime [`Event`].
@@ -66,19 +62,15 @@ where
/// * a [`Clipboard`], if available
///
/// By default, it does nothing.
- ///
- /// [`Event`]: ../enum.Event.html
- /// [`Overlay`]: trait.Widget.html
- /// [`Layout`]: ../layout/struct.Layout.html
- /// [`Clipboard`]: ../trait.Clipboard.html
fn on_event(
&mut self,
_event: Event,
_layout: Layout<'_>,
_cursor_position: Point,
- _messages: &mut Vec<Message>,
_renderer: &Renderer,
- _clipboard: Option<&dyn Clipboard>,
- ) {
+ _clipboard: &mut dyn Clipboard,
+ _messages: &mut Vec<Message>,
+ ) -> event::Status {
+ event::Status::Ignored
}
}
diff --git a/native/src/overlay/element.rs b/native/src/overlay/element.rs
index e1fd9b88..e4819037 100644
--- a/native/src/overlay/element.rs
+++ b/native/src/overlay/element.rs
@@ -1,10 +1,10 @@
pub use crate::Overlay;
-use crate::{layout, Clipboard, Event, Hasher, Layout, Point, Size, Vector};
+use crate::event::{self, Event};
+use crate::layout;
+use crate::{Clipboard, Hasher, Layout, Point, Size, Vector};
/// A generic [`Overlay`].
-///
-/// [`Overlay`]: trait.Overlay.html
#[allow(missing_debug_implementations)]
pub struct Element<'a, Message, Renderer> {
position: Point,
@@ -16,9 +16,6 @@ where
Renderer: crate::Renderer,
{
/// Creates a new [`Element`] containing the given [`Overlay`].
- ///
- /// [`Element`]: struct.Element.html
- /// [`Overlay`]: trait.Overlay.html
pub fn new(
position: Point,
overlay: Box<dyn Overlay<Message, Renderer> + 'a>,
@@ -27,16 +24,12 @@ where
}
/// Translates the [`Element`].
- ///
- /// [`Element`]: struct.Element.html
pub fn translate(mut self, translation: Vector) -> Self {
self.position = self.position + translation;
self
}
/// Applies a transformation to the produced message of the [`Element`].
- ///
- /// [`Element`]: struct.Element.html
pub fn map<B>(self, f: &'a dyn Fn(Message) -> B) -> Element<'a, B, Renderer>
where
Message: 'a,
@@ -50,38 +43,31 @@ where
}
/// Computes the layout of the [`Element`] in the given bounds.
- ///
- /// [`Element`]: struct.Element.html
pub fn layout(&self, renderer: &Renderer, bounds: Size) -> layout::Node {
self.overlay.layout(renderer, bounds, self.position)
}
/// Processes a runtime [`Event`].
- ///
- /// [`Event`]: enum.Event.html
pub fn on_event(
&mut self,
event: Event,
layout: Layout<'_>,
cursor_position: Point,
- messages: &mut Vec<Message>,
renderer: &Renderer,
- clipboard: Option<&dyn Clipboard>,
- ) {
+ clipboard: &mut dyn Clipboard,
+ messages: &mut Vec<Message>,
+ ) -> event::Status {
self.overlay.on_event(
event,
layout,
cursor_position,
- messages,
renderer,
clipboard,
+ messages,
)
}
/// Draws the [`Element`] and its children using the given [`Layout`].
- ///
- /// [`Element`]: struct.Element.html
- /// [`Layout`]: layout/struct.Layout.html
pub fn draw(
&self,
renderer: &mut Renderer,
@@ -94,8 +80,6 @@ where
}
/// Computes the _layout_ hash of the [`Element`].
- ///
- /// [`Element`]: struct.Element.html
pub fn hash_layout(&self, state: &mut Hasher) {
self.overlay.hash_layout(state, self.position);
}
@@ -133,24 +117,26 @@ where
event: Event,
layout: Layout<'_>,
cursor_position: Point,
- messages: &mut Vec<B>,
renderer: &Renderer,
- clipboard: Option<&dyn Clipboard>,
- ) {
+ clipboard: &mut dyn Clipboard,
+ messages: &mut Vec<B>,
+ ) -> event::Status {
let mut original_messages = Vec::new();
- self.content.on_event(
+ let event_status = self.content.on_event(
event,
layout,
cursor_position,
- &mut original_messages,
renderer,
clipboard,
+ &mut original_messages,
);
original_messages
.drain(..)
.for_each(|message| messages.push((self.mapper)(message)));
+
+ event_status
}
fn draw(
diff --git a/native/src/overlay/menu.rs b/native/src/overlay/menu.rs
index c2df468e..f62dcb46 100644
--- a/native/src/overlay/menu.rs
+++ b/native/src/overlay/menu.rs
@@ -1,8 +1,15 @@
//! Build and show dropdown menus.
+use crate::container;
+use crate::event::{self, Event};
+use crate::layout;
+use crate::mouse;
+use crate::overlay;
+use crate::scrollable;
+use crate::text;
+use crate::touch;
use crate::{
- container, layout, mouse, overlay, scrollable, text, Clipboard, Container,
- Element, Event, Hasher, Layout, Length, Point, Rectangle, Scrollable, Size,
- Vector, Widget,
+ Clipboard, Container, Element, Hasher, Layout, Length, Padding, Point,
+ Rectangle, Scrollable, Size, Vector, Widget,
};
/// A list of selectable options.
@@ -13,7 +20,7 @@ pub struct Menu<'a, T, Renderer: self::Renderer> {
hovered_option: &'a mut Option<usize>,
last_selection: &'a mut Option<T>,
width: u16,
- padding: u16,
+ padding: Padding,
text_size: Option<u16>,
font: Renderer::Font,
style: <Renderer as self::Renderer>::Style,
@@ -26,9 +33,6 @@ where
{
/// Creates a new [`Menu`] with the given [`State`], a list of options, and
/// the message to produced when an option is selected.
- ///
- /// [`Menu`]: struct.Menu.html
- /// [`State`]: struct.State.html
pub fn new(
state: &'a mut State,
options: &'a [T],
@@ -41,7 +45,7 @@ where
hovered_option,
last_selection,
width: 0,
- padding: 0,
+ padding: Padding::ZERO,
text_size: None,
font: Default::default(),
style: Default::default(),
@@ -49,40 +53,30 @@ where
}
/// Sets the width of the [`Menu`].
- ///
- /// [`Menu`]: struct.Menu.html
pub fn width(mut self, width: u16) -> Self {
self.width = width;
self
}
- /// Sets the padding of the [`Menu`].
- ///
- /// [`Menu`]: struct.Menu.html
- pub fn padding(mut self, padding: u16) -> Self {
- self.padding = padding;
+ /// Sets the [`Padding`] of the [`Menu`].
+ pub fn padding<P: Into<Padding>>(mut self, padding: P) -> Self {
+ self.padding = padding.into();
self
}
/// Sets the text size of the [`Menu`].
- ///
- /// [`Menu`]: struct.Menu.html
pub fn text_size(mut self, text_size: u16) -> Self {
self.text_size = Some(text_size);
self
}
/// Sets the font of the [`Menu`].
- ///
- /// [`Menu`]: struct.Menu.html
pub fn font(mut self, font: Renderer::Font) -> Self {
self.font = font;
self
}
/// Sets the style of the [`Menu`].
- ///
- /// [`Menu`]: struct.Menu.html
pub fn style(
mut self,
style: impl Into<<Renderer as self::Renderer>::Style>,
@@ -97,8 +91,6 @@ where
/// The `target_height` will be used to display the menu either on top
/// of the target or under it, depending on the screen position and the
/// dimensions of the [`Menu`].
- ///
- /// [`Menu`]: struct.Menu.html
pub fn overlay<Message: 'a>(
self,
position: Point,
@@ -112,8 +104,6 @@ where
}
/// The local state of a [`Menu`].
-///
-/// [`Menu`]: struct.Menu.html
#[derive(Debug, Clone, Default)]
pub struct State {
scrollable: scrollable::State,
@@ -121,9 +111,6 @@ pub struct State {
impl State {
/// Creates a new [`State`] for a [`Menu`].
- ///
- /// [`State`]: struct.State.html
- /// [`Menu`]: struct.Menu.html
pub fn new() -> Self {
Self::default()
}
@@ -232,18 +219,18 @@ where
event: Event,
layout: Layout<'_>,
cursor_position: Point,
- messages: &mut Vec<Message>,
renderer: &Renderer,
- clipboard: Option<&dyn Clipboard>,
- ) {
+ clipboard: &mut dyn Clipboard,
+ messages: &mut Vec<Message>,
+ ) -> event::Status {
self.container.on_event(
event.clone(),
layout,
cursor_position,
- messages,
renderer,
clipboard,
- );
+ messages,
+ )
}
fn draw(
@@ -253,9 +240,13 @@ where
layout: Layout<'_>,
cursor_position: Point,
) -> Renderer::Output {
- let primitives =
- self.container
- .draw(renderer, defaults, layout, cursor_position);
+ let primitives = self.container.draw(
+ renderer,
+ defaults,
+ layout,
+ cursor_position,
+ &layout.bounds(),
+ );
renderer.decorate(
layout.bounds(),
@@ -270,7 +261,7 @@ struct List<'a, T, Renderer: self::Renderer> {
options: &'a [T],
hovered_option: &'a mut Option<usize>,
last_selection: &'a mut Option<T>,
- padding: u16,
+ padding: Padding,
text_size: Option<u16>,
font: Renderer::Font,
style: <Renderer as self::Renderer>::Style,
@@ -303,7 +294,7 @@ where
let size = {
let intrinsic = Size::new(
0.0,
- f32::from(text_size + self.padding * 2)
+ f32::from(text_size + self.padding.vertical())
* self.options.len() as f32,
);
@@ -329,10 +320,10 @@ where
event: Event,
layout: Layout<'_>,
cursor_position: Point,
- _messages: &mut Vec<Message>,
renderer: &Renderer,
- _clipboard: Option<&dyn Clipboard>,
- ) {
+ _clipboard: &mut dyn Clipboard,
+ _messages: &mut Vec<Message>,
+ ) -> event::Status {
match event {
Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) => {
let bounds = layout.bounds();
@@ -347,19 +338,42 @@ where
}
Event::Mouse(mouse::Event::CursorMoved { .. }) => {
let bounds = layout.bounds();
- let text_size =
- self.text_size.unwrap_or(renderer.default_size());
if bounds.contains(cursor_position) {
+ let text_size =
+ self.text_size.unwrap_or(renderer.default_size());
+
*self.hovered_option = Some(
((cursor_position.y - bounds.y)
- / f32::from(text_size + self.padding * 2))
+ / f32::from(text_size + self.padding.vertical()))
as usize,
);
}
}
+ Event::Touch(touch::Event::FingerPressed { .. }) => {
+ let bounds = layout.bounds();
+
+ if bounds.contains(cursor_position) {
+ let text_size =
+ self.text_size.unwrap_or(renderer.default_size());
+
+ *self.hovered_option = Some(
+ ((cursor_position.y - bounds.y)
+ / f32::from(text_size + self.padding.vertical()))
+ as usize,
+ );
+
+ if let Some(index) = *self.hovered_option {
+ if let Some(option) = self.options.get(index) {
+ *self.last_selection = Some(option.clone());
+ }
+ }
+ }
+ }
_ => {}
}
+
+ event::Status::Ignored
}
fn draw(
@@ -368,11 +382,13 @@ where
_defaults: &Renderer::Defaults,
layout: Layout<'_>,
cursor_position: Point,
+ viewport: &Rectangle,
) -> Renderer::Output {
self::Renderer::draw(
renderer,
layout.bounds(),
cursor_position,
+ viewport,
self.options,
*self.hovered_option,
self.padding,
@@ -388,21 +404,16 @@ where
/// Your [renderer] will need to implement this trait before being
/// able to use a [`Menu`] in your user interface.
///
-/// [`Menu`]: struct.Menu.html
-/// [renderer]: ../../renderer/index.html
+/// [renderer]: crate::renderer
pub trait Renderer:
scrollable::Renderer + container::Renderer + text::Renderer
{
/// The [`Menu`] style supported by this renderer.
- ///
- /// [`Menu`]: struct.Menu.html
type Style: Default + Clone;
/// Decorates a the list of options of a [`Menu`].
///
/// This method can be used to draw a background for the [`Menu`].
- ///
- /// [`Menu`]: struct.Menu.html
fn decorate(
&mut self,
bounds: Rectangle,
@@ -412,15 +423,14 @@ pub trait Renderer:
) -> Self::Output;
/// Draws the list of options of a [`Menu`].
- ///
- /// [`Menu`]: struct.Menu.html
fn draw<T: ToString>(
&mut self,
bounds: Rectangle,
cursor_position: Point,
+ viewport: &Rectangle,
options: &[T],
hovered_option: Option<usize>,
- padding: u16,
+ padding: Padding,
text_size: u16,
font: Self::Font,
style: &<Self as Renderer>::Style,
diff --git a/native/src/program.rs b/native/src/program.rs
index 14afcd84..75fab094 100644
--- a/native/src/program.rs
+++ b/native/src/program.rs
@@ -1,5 +1,5 @@
//! Build interactive programs using The Elm Architecture.
-use crate::{Command, Element, Renderer};
+use crate::{Clipboard, Command, Element, Renderer};
mod state;
@@ -8,14 +8,13 @@ pub use state::State;
/// The core of a user interface application following The Elm Architecture.
pub trait Program: Sized {
/// The graphics backend to use to draw the [`Program`].
- ///
- /// [`Program`]: trait.Program.html
type Renderer: Renderer;
/// The type of __messages__ your [`Program`] will produce.
- ///
- /// [`Program`]: trait.Program.html
- type Message: std::fmt::Debug + Send;
+ type Message: std::fmt::Debug + Clone + Send;
+
+ /// The type of [`Clipboard`] your [`Program`] will use.
+ type Clipboard: Clipboard;
/// Handles a __message__ and updates the state of the [`Program`].
///
@@ -25,15 +24,14 @@ pub trait Program: Sized {
///
/// Any [`Command`] returned will be executed immediately in the
/// background by shells.
- ///
- /// [`Program`]: trait.Application.html
- /// [`Command`]: struct.Command.html
- fn update(&mut self, message: Self::Message) -> Command<Self::Message>;
+ fn update(
+ &mut self,
+ message: Self::Message,
+ clipboard: &mut Self::Clipboard,
+ ) -> Command<Self::Message>;
/// Returns the widgets to display in the [`Program`].
///
/// These widgets can produce __messages__ based on user interaction.
- ///
- /// [`Program`]: trait.Program.html
fn view(&mut self) -> Element<'_, Self::Message, Self::Renderer>;
}
diff --git a/native/src/program/state.rs b/native/src/program/state.rs
index 95e6b60c..fd1f2b52 100644
--- a/native/src/program/state.rs
+++ b/native/src/program/state.rs
@@ -1,12 +1,9 @@
use crate::{
- Cache, Clipboard, Command, Debug, Event, Point, Program, Renderer, Size,
- UserInterface,
+ Cache, Command, Debug, Event, Point, Program, Renderer, Size, UserInterface,
};
/// The execution state of a [`Program`]. It leverages caching, event
/// processing, and rendering primitive storage.
-///
-/// [`Program`]: trait.Program.html
#[allow(missing_debug_implementations)]
pub struct State<P>
where
@@ -25,9 +22,6 @@ where
{
/// Creates a new [`State`] with the provided [`Program`], initializing its
/// primitive with the given logical bounds and renderer.
- ///
- /// [`State`]: struct.State.html
- /// [`Program`]: trait.Program.html
pub fn new(
mut program: P,
bounds: Size,
@@ -59,39 +53,30 @@ where
}
/// Returns a reference to the [`Program`] of the [`State`].
- ///
- /// [`Program`]: trait.Program.html
- /// [`State`]: struct.State.html
pub fn program(&self) -> &P {
&self.program
}
/// Returns a reference to the current rendering primitive of the [`State`].
- ///
- /// [`State`]: struct.State.html
pub fn primitive(&self) -> &<P::Renderer as Renderer>::Output {
&self.primitive
}
/// Queues an event in the [`State`] for processing during an [`update`].
///
- /// [`State`]: struct.State.html
- /// [`update`]: #method.update
+ /// [`update`]: Self::update
pub fn queue_event(&mut self, event: Event) {
self.queued_events.push(event);
}
/// Queues a message in the [`State`] for processing during an [`update`].
///
- /// [`State`]: struct.State.html
- /// [`update`]: #method.update
+ /// [`update`]: Self::update
pub fn queue_message(&mut self, message: P::Message) {
self.queued_messages.push(message);
}
/// Returns whether the event queue of the [`State`] is empty or not.
- ///
- /// [`State`]: struct.State.html
pub fn is_queue_empty(&self) -> bool {
self.queued_events.is_empty() && self.queued_messages.is_empty()
}
@@ -101,14 +86,12 @@ where
///
/// Returns the [`Command`] obtained from [`Program`] after updating it,
/// only if an update was necessary.
- ///
- /// [`Program`]: trait.Program.html
pub fn update(
&mut self,
bounds: Size,
cursor_position: Point,
- clipboard: Option<&dyn Clipboard>,
renderer: &mut P::Renderer,
+ clipboard: &mut P::Clipboard,
debug: &mut Debug,
) -> Option<Command<P::Message>> {
let mut user_interface = build_user_interface(
@@ -120,14 +103,17 @@ where
);
debug.event_processing_started();
- let mut messages = user_interface.update(
+ let mut messages = Vec::new();
+
+ let _ = user_interface.update(
&self.queued_events,
cursor_position,
- clipboard,
renderer,
+ clipboard,
+ &mut messages,
);
- messages.extend(self.queued_messages.drain(..));
+ messages.extend(self.queued_messages.drain(..));
self.queued_events.clear();
debug.event_processing_finished();
@@ -149,7 +135,7 @@ where
debug.log_message(&message);
debug.update_started();
- let command = self.program.update(message);
+ let command = self.program.update(message, clipboard);
debug.update_finished();
command
diff --git a/native/src/renderer.rs b/native/src/renderer.rs
index d986141f..39a6cff1 100644
--- a/native/src/renderer.rs
+++ b/native/src/renderer.rs
@@ -13,12 +13,12 @@
//! In the end, a __renderer__ satisfying all the constraints is
//! needed to build a [`UserInterface`].
//!
-//! [`Widget`]: ../widget/trait.Widget.html
-//! [`UserInterface`]: ../struct.UserInterface.html
-//! [`Text`]: ../widget/text/struct.Text.html
-//! [`text::Renderer`]: ../widget/text/trait.Renderer.html
-//! [`Checkbox`]: ../widget/checkbox/struct.Checkbox.html
-//! [`checkbox::Renderer`]: ../widget/checkbox/trait.Renderer.html
+//! [`Widget`]: crate::Widget
+//! [`UserInterface`]: crate::UserInterface
+//! [`Text`]: crate::widget::Text
+//! [`text::Renderer`]: crate::widget::text::Renderer
+//! [`Checkbox`]: crate::widget::Checkbox
+//! [`checkbox::Renderer`]: crate::widget::checkbox::Renderer
#[cfg(debug_assertions)]
mod null;
@@ -34,15 +34,11 @@ pub trait Renderer: Sized {
///
/// If you are implementing a graphical renderer, your output will most
/// likely be a tree of visual primitives.
- ///
- /// [`Renderer`]: trait.Renderer.html
type Output;
/// The default styling attributes of the [`Renderer`].
///
/// This type can be leveraged to implement style inheritance.
- ///
- /// [`Renderer`]: trait.Renderer.html
type Defaults: Default;
/// Lays out the elements of a user interface.
diff --git a/native/src/renderer/null.rs b/native/src/renderer/null.rs
index 2aee0da1..bb57c163 100644
--- a/native/src/renderer/null.rs
+++ b/native/src/renderer/null.rs
@@ -1,7 +1,7 @@
use crate::{
button, checkbox, column, container, pane_grid, progress_bar, radio, row,
- scrollable, slider, text, text_input, Color, Element, Font,
- HorizontalAlignment, Layout, Point, Rectangle, Renderer, Size,
+ scrollable, slider, text, text_input, toggler, Color, Element, Font,
+ HorizontalAlignment, Layout, Padding, Point, Rectangle, Renderer, Size,
VerticalAlignment,
};
@@ -13,8 +13,6 @@ pub struct Null;
impl Null {
/// Creates a new [`Null`] renderer.
- ///
- /// [`Null`]: struct.Null.html
pub fn new() -> Null {
Null
}
@@ -35,6 +33,7 @@ impl column::Renderer for Null {
_content: &[Element<'_, Message, Self>],
_layout: Layout<'_>,
_cursor_position: Point,
+ _viewport: &Rectangle,
) {
}
}
@@ -46,6 +45,7 @@ impl row::Renderer for Null {
_content: &[Element<'_, Message, Self>],
_layout: Layout<'_>,
_cursor_position: Point,
+ _viewport: &Rectangle,
) {
}
}
@@ -89,6 +89,9 @@ impl scrollable::Renderer for Null {
_bounds: Rectangle,
_content_bounds: Rectangle,
_offset: u32,
+ _scrollbar_width: u16,
+ _scrollbar_margin: u16,
+ _scroller_width: u16,
) -> Option<scrollable::Scrollbar> {
None
}
@@ -142,7 +145,7 @@ impl text_input::Renderer for Null {
}
impl button::Renderer for Null {
- const DEFAULT_PADDING: u16 = 0;
+ const DEFAULT_PADDING: Padding = Padding::ZERO;
type Style = ();
@@ -234,6 +237,7 @@ impl container::Renderer for Null {
_defaults: &Self::Defaults,
_bounds: Rectangle,
_cursor_position: Point,
+ _viewport: &Rectangle,
_style: &Self::Style,
_content: &Element<'_, Message, Self>,
_content_layout: Layout<'_>,
@@ -242,14 +246,18 @@ impl container::Renderer for Null {
}
impl pane_grid::Renderer for Null {
+ type Style = ();
+
fn draw<Message>(
&mut self,
_defaults: &Self::Defaults,
_content: &[(pane_grid::Pane, pane_grid::Content<'_, Message, Self>)],
_dragging: Option<(pane_grid::Pane, Point)>,
- _resizing: Option<pane_grid::Axis>,
+ _resizing: Option<(pane_grid::Axis, Rectangle, bool)>,
_layout: Layout<'_>,
+ _style: &<Self as pane_grid::Renderer>::Style,
_cursor_position: Point,
+ _viewport: &Rectangle,
) {
}
@@ -257,13 +265,14 @@ impl pane_grid::Renderer for Null {
&mut self,
_defaults: &Self::Defaults,
_bounds: Rectangle,
- _style: &Self::Style,
+ _style: &<Self as container::Renderer>::Style,
_title_bar: Option<(
&pane_grid::TitleBar<'_, Message, Self>,
Layout<'_>,
)>,
_body: (&Element<'_, Message, Self>, Layout<'_>),
_cursor_position: Point,
+ _viewport: &Rectangle,
) {
}
@@ -271,13 +280,27 @@ impl pane_grid::Renderer for Null {
&mut self,
_defaults: &Self::Defaults,
_bounds: Rectangle,
- _style: &Self::Style,
- _title: &str,
- _title_size: u16,
- _title_font: Self::Font,
- _title_bounds: Rectangle,
+ _style: &<Self as container::Renderer>::Style,
+ _content: (&Element<'_, Message, Self>, Layout<'_>),
_controls: Option<(&Element<'_, Message, Self>, Layout<'_>)>,
_cursor_position: Point,
+ _viewport: &Rectangle,
+ ) {
+ }
+}
+
+impl toggler::Renderer for Null {
+ type Style = ();
+
+ const DEFAULT_SIZE: u16 = 20;
+
+ fn draw(
+ &mut self,
+ _bounds: Rectangle,
+ _is_checked: bool,
+ _is_mouse_over: bool,
+ _label: Option<Self::Output>,
+ _style: &Self::Style,
) {
}
}
diff --git a/native/src/runtime.rs b/native/src/runtime.rs
index 9fa031f4..5b0a6925 100644
--- a/native/src/runtime.rs
+++ b/native/src/runtime.rs
@@ -1,12 +1,18 @@
//! Run commands and subscriptions.
-use crate::{Event, Hasher};
+use crate::event::{self, Event};
+use crate::Hasher;
/// A native runtime with a generic executor and receiver of results.
///
/// It can be used by shells to easily spawn a [`Command`] or track a
/// [`Subscription`].
///
-/// [`Command`]: ../struct.Command.html
-/// [`Subscription`]: ../struct.Subscription.html
-pub type Runtime<Executor, Receiver, Message> =
- iced_futures::Runtime<Hasher, Event, Executor, Receiver, Message>;
+/// [`Command`]: crate::Command
+/// [`Subscription`]: crate::Subscription
+pub type Runtime<Executor, Receiver, Message> = iced_futures::Runtime<
+ Hasher,
+ (Event, event::Status),
+ Executor,
+ Receiver,
+ Message,
+>;
diff --git a/native/src/subscription.rs b/native/src/subscription.rs
index 0d002c6c..ff954382 100644
--- a/native/src/subscription.rs
+++ b/native/src/subscription.rs
@@ -1,5 +1,6 @@
//! Listen to external events in your application.
-use crate::{Event, Hasher};
+use crate::event::{self, Event};
+use crate::Hasher;
use iced_futures::futures::stream::BoxStream;
/// A request to listen to external events.
@@ -13,21 +14,18 @@ use iced_futures::futures::stream::BoxStream;
/// For instance, you can use a [`Subscription`] to listen to a WebSocket
/// connection, keyboard presses, mouse events, time ticks, etc.
///
-/// [`Command`]: ../struct.Command.html
-/// [`Subscription`]: struct.Subscription.html
-pub type Subscription<T> = iced_futures::Subscription<Hasher, Event, T>;
+/// [`Command`]: crate::Command
+pub type Subscription<T> =
+ iced_futures::Subscription<Hasher, (Event, event::Status), T>;
/// A stream of runtime events.
///
/// It is the input of a [`Subscription`] in the native runtime.
-///
-/// [`Subscription`]: type.Subscription.html
-pub type EventStream = BoxStream<'static, Event>;
+pub type EventStream = BoxStream<'static, (Event, event::Status)>;
/// A native [`Subscription`] tracker.
-///
-/// [`Subscription`]: type.Subscription.html
-pub type Tracker = iced_futures::subscription::Tracker<Hasher, Event>;
+pub type Tracker =
+ iced_futures::subscription::Tracker<Hasher, (Event, event::Status)>;
pub use iced_futures::subscription::Recipe;
@@ -37,11 +35,30 @@ use events::Events;
/// Returns a [`Subscription`] to all the runtime events.
///
-/// This subscription will notify your application of any [`Event`] handled by
-/// the runtime.
-///
-/// [`Subscription`]: type.Subscription.html
-/// [`Event`]: ../enum.Event.html
+/// This subscription will notify your application of any [`Event`] that was
+/// not captured by any widget.
pub fn events() -> Subscription<Event> {
- Subscription::from_recipe(Events)
+ Subscription::from_recipe(Events {
+ f: |event, status| match status {
+ event::Status::Ignored => Some(event),
+ event::Status::Captured => None,
+ },
+ })
+}
+
+/// Returns a [`Subscription`] that filters all the runtime events with the
+/// provided function, producing messages accordingly.
+///
+/// This subscription will call the provided function for every [`Event`]
+/// handled by the runtime. If the function:
+///
+/// - Returns `None`, the [`Event`] will be discarded.
+/// - Returns `Some` message, the `Message` will be produced.
+pub fn events_with<Message>(
+ f: fn(Event, event::Status) -> Option<Message>,
+) -> Subscription<Message>
+where
+ Message: 'static + Send,
+{
+ Subscription::from_recipe(Events { f })
}
diff --git a/native/src/subscription/events.rs b/native/src/subscription/events.rs
index ceae467d..f689f3af 100644
--- a/native/src/subscription/events.rs
+++ b/native/src/subscription/events.rs
@@ -1,18 +1,26 @@
-use crate::{
- subscription::{EventStream, Recipe},
- Event, Hasher,
-};
+use crate::event::{self, Event};
+use crate::subscription::{EventStream, Recipe};
+use crate::Hasher;
+use iced_futures::futures::future;
+use iced_futures::futures::StreamExt;
use iced_futures::BoxStream;
-pub struct Events;
+pub struct Events<Message> {
+ pub(super) f: fn(Event, event::Status) -> Option<Message>,
+}
-impl Recipe<Hasher, Event> for Events {
- type Output = Event;
+impl<Message> Recipe<Hasher, (Event, event::Status)> for Events<Message>
+where
+ Message: 'static + Send,
+{
+ type Output = Message;
fn hash(&self, state: &mut Hasher) {
use std::hash::Hash;
- std::any::TypeId::of::<Self>().hash(state);
+ struct Marker;
+ std::any::TypeId::of::<Marker>().hash(state);
+ self.f.hash(state);
}
fn stream(
@@ -20,5 +28,9 @@ impl Recipe<Hasher, Event> for Events {
event_stream: EventStream,
) -> BoxStream<Self::Output> {
event_stream
+ .filter_map(move |(event, status)| {
+ future::ready((self.f)(event, status))
+ })
+ .boxed()
}
}
diff --git a/native/src/touch.rs b/native/src/touch.rs
new file mode 100644
index 00000000..18120644
--- /dev/null
+++ b/native/src/touch.rs
@@ -0,0 +1,23 @@
+//! Build touch events.
+use crate::Point;
+
+/// A touch interaction.
+#[derive(Debug, Clone, Copy, PartialEq)]
+#[allow(missing_docs)]
+pub enum Event {
+ /// A touch interaction was started.
+ FingerPressed { id: Finger, position: Point },
+
+ /// An on-going touch interaction was moved.
+ FingerMoved { id: Finger, position: Point },
+
+ /// A touch interaction was ended.
+ FingerLifted { id: Finger, position: Point },
+
+ /// A touch interaction was canceled.
+ FingerLost { id: Finger, position: Point },
+}
+
+/// A unique identifier representing a finger on a touch interaction.
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
+pub struct Finger(pub u64);
diff --git a/native/src/user_interface.rs b/native/src/user_interface.rs
index 00a290f1..8e0d7d1c 100644
--- a/native/src/user_interface.rs
+++ b/native/src/user_interface.rs
@@ -1,4 +1,7 @@
-use crate::{layout, overlay, Clipboard, Element, Event, Layout, Point, Size};
+use crate::event::{self, Event};
+use crate::layout;
+use crate::overlay;
+use crate::{Clipboard, Element, Layout, Point, Rectangle, Size};
use std::hash::Hasher;
@@ -9,14 +12,11 @@ use std::hash::Hasher;
/// Iced tries to avoid dictating how to write your event loop. You are in
/// charge of using this type in your system in any way you want.
///
-/// [`Layout`]: struct.Layout.html
-///
/// # Example
/// The [`integration` example] uses a [`UserInterface`] to integrate Iced in
/// an existing graphical application.
///
-/// [`integration` example]: https://github.com/hecrj/iced/tree/0.1/examples/integration
-/// [`UserInterface`]: struct.UserInterface.html
+/// [`integration` example]: https://github.com/hecrj/iced/tree/0.3/examples/integration
#[allow(missing_debug_implementations)]
pub struct UserInterface<'a, Message, Renderer> {
root: Element<'a, Message, Renderer>,
@@ -34,10 +34,6 @@ where
/// It is able to avoid expensive computations when using a [`Cache`]
/// obtained from a previous instance of a [`UserInterface`].
///
- /// [`Element`]: struct.Element.html
- /// [`Cache`]: struct.Cache.html
- /// [`UserInterface`]: struct.UserInterface.html
- ///
/// # Example
/// Imagine we want to build a [`UserInterface`] for
/// [the counter example that we previously wrote](index.html#usage). Here
@@ -133,15 +129,12 @@ where
/// It returns __messages__ that may have been produced as a result of user
/// interactions. You should feed these to your __update logic__.
///
- /// [`UserInterface`]: struct.UserInterface.html
- /// [`Event`]: enum.Event.html
- ///
/// # Example
/// Let's allow our [counter](index.html#usage) to change state by
/// completing [the previous example](#example):
///
/// ```no_run
- /// use iced_native::{UserInterface, Cache, Size, Point};
+ /// use iced_native::{clipboard, UserInterface, Cache, Size, Point};
/// use iced_wgpu::Renderer;
///
/// # mod iced_wgpu {
@@ -164,12 +157,14 @@ where
/// let mut renderer = Renderer::new();
/// let mut window_size = Size::new(1024.0, 768.0);
/// let mut cursor_position = Point::default();
+ /// let mut clipboard = clipboard::Null;
///
/// // Initialize our event storage
/// let mut events = Vec::new();
+ /// let mut messages = Vec::new();
///
/// loop {
- /// // Process system events...
+ /// // Obtain system events...
///
/// let mut user_interface = UserInterface::build(
/// counter.view(),
@@ -179,17 +174,18 @@ where
/// );
///
/// // Update the user interface
- /// let messages = user_interface.update(
+ /// let event_statuses = user_interface.update(
/// &events,
/// cursor_position,
- /// None,
/// &renderer,
+ /// &mut clipboard,
+ /// &mut messages
/// );
///
/// cache = user_interface.into_cache();
///
/// // Process the produced messages
- /// for message in messages {
+ /// for message in messages.drain(..) {
/// counter.update(message);
/// }
/// }
@@ -198,12 +194,11 @@ where
&mut self,
events: &[Event],
cursor_position: Point,
- clipboard: Option<&dyn Clipboard>,
renderer: &Renderer,
- ) -> Vec<Message> {
- let mut messages = Vec::new();
-
- let base_cursor = if let Some(mut overlay) =
+ clipboard: &mut dyn Clipboard,
+ messages: &mut Vec<Message>,
+ ) -> Vec<event::Status> {
+ let (base_cursor, overlay_statuses) = if let Some(mut overlay) =
self.root.overlay(Layout::new(&self.base.layout))
{
let layer = Self::overlay_layer(
@@ -213,16 +208,20 @@ where
renderer,
);
- for event in events {
- overlay.on_event(
- event.clone(),
- Layout::new(&layer.layout),
- cursor_position,
- &mut messages,
- renderer,
- clipboard,
- );
- }
+ let event_statuses = events
+ .iter()
+ .cloned()
+ .map(|event| {
+ overlay.on_event(
+ event,
+ Layout::new(&layer.layout),
+ cursor_position,
+ renderer,
+ clipboard,
+ messages,
+ )
+ })
+ .collect();
let base_cursor = if layer.layout.bounds().contains(cursor_position)
{
@@ -234,40 +233,44 @@ where
self.overlay = Some(layer);
- base_cursor
+ (base_cursor, event_statuses)
} else {
- cursor_position
+ (cursor_position, vec![event::Status::Ignored; events.len()])
};
- for event in events {
- self.root.widget.on_event(
- event.clone(),
- Layout::new(&self.base.layout),
- base_cursor,
- &mut messages,
- renderer,
- clipboard,
- );
- }
+ events
+ .iter()
+ .cloned()
+ .zip(overlay_statuses.into_iter())
+ .map(|(event, overlay_status)| {
+ let event_status = self.root.widget.on_event(
+ event,
+ Layout::new(&self.base.layout),
+ base_cursor,
+ renderer,
+ clipboard,
+ messages,
+ );
- messages
+ event_status.merge(overlay_status)
+ })
+ .collect()
}
/// Draws the [`UserInterface`] with the provided [`Renderer`].
///
- /// It returns the current state of the [`MouseCursor`]. You should update
- /// the icon of the mouse cursor accordingly in your system.
+ /// It returns the some [`Renderer::Output`]. You should update the icon of
+ /// the mouse cursor accordingly in your system.
///
- /// [`UserInterface`]: struct.UserInterface.html
- /// [`Renderer`]: trait.Renderer.html
- /// [`MouseCursor`]: enum.MouseCursor.html
+ /// [`Renderer`]: crate::Renderer
+ /// [`Renderer::Output`]: crate::Renderer::Output
///
/// # Example
/// We can finally draw our [counter](index.html#usage) by
/// [completing the last example](#example-1):
///
/// ```no_run
- /// use iced_native::{UserInterface, Cache, Size, Point};
+ /// use iced_native::{clipboard, UserInterface, Cache, Size, Point};
/// use iced_wgpu::Renderer;
///
/// # mod iced_wgpu {
@@ -290,10 +293,12 @@ where
/// let mut renderer = Renderer::new();
/// let mut window_size = Size::new(1024.0, 768.0);
/// let mut cursor_position = Point::default();
+ /// let mut clipboard = clipboard::Null;
/// let mut events = Vec::new();
+ /// let mut messages = Vec::new();
///
/// loop {
- /// // Process system events...
+ /// // Obtain system events...
///
/// let mut user_interface = UserInterface::build(
/// counter.view(),
@@ -302,11 +307,13 @@ where
/// &mut renderer,
/// );
///
- /// let messages = user_interface.update(
+ /// // Update the user interface
+ /// let event_statuses = user_interface.update(
/// &events,
/// cursor_position,
- /// None,
/// &renderer,
+ /// &mut clipboard,
+ /// &mut messages
/// );
///
/// // Draw the user interface
@@ -314,7 +321,7 @@ where
///
/// cache = user_interface.into_cache();
///
- /// for message in messages {
+ /// for message in messages.drain(..) {
/// counter.update(message);
/// }
///
@@ -327,6 +334,8 @@ where
renderer: &mut Renderer,
cursor_position: Point,
) -> Renderer::Output {
+ let viewport = Rectangle::with_size(self.bounds);
+
let overlay = if let Some(mut overlay) =
self.root.overlay(Layout::new(&self.base.layout))
{
@@ -365,6 +374,7 @@ where
&Renderer::Defaults::default(),
Layout::new(&self.base.layout),
base_cursor,
+ &viewport,
);
renderer.overlay(
@@ -378,15 +388,28 @@ where
&Renderer::Defaults::default(),
Layout::new(&self.base.layout),
cursor_position,
+ &viewport,
)
}
}
+ /// Relayouts and returns a new [`UserInterface`] using the provided
+ /// bounds.
+ pub fn relayout(self, bounds: Size, renderer: &mut Renderer) -> Self {
+ Self::build(
+ self.root,
+ bounds,
+ Cache {
+ base: self.base,
+ overlay: self.overlay,
+ bounds: self.bounds,
+ },
+ renderer,
+ )
+ }
+
/// Extract the [`Cache`] of the [`UserInterface`], consuming it in the
/// process.
- ///
- /// [`Cache`]: struct.Cache.html
- /// [`UserInterface`]: struct.UserInterface.html
pub fn into_cache(self) -> Cache {
Cache {
base: self.base,
@@ -427,8 +450,6 @@ struct Layer {
}
/// Reusable data of a specific [`UserInterface`].
-///
-/// [`UserInterface`]: struct.UserInterface.html
#[derive(Debug, Clone)]
pub struct Cache {
base: Layer,
@@ -441,9 +462,6 @@ impl Cache {
///
/// You should use this to initialize a [`Cache`] before building your first
/// [`UserInterface`].
- ///
- /// [`Cache`]: struct.Cache.html
- /// [`UserInterface`]: struct.UserInterface.html
pub fn new() -> Cache {
Cache {
base: Layer {
diff --git a/native/src/widget.rs b/native/src/widget.rs
index a10281df..43c1b023 100644
--- a/native/src/widget.rs
+++ b/native/src/widget.rs
@@ -18,8 +18,7 @@
//! use iced_native::{button, Button, Widget};
//! ```
//!
-//! [`Widget`]: trait.Widget.html
-//! [renderer]: ../renderer/index.html
+//! [renderer]: crate::renderer
pub mod button;
pub mod checkbox;
pub mod column;
@@ -37,6 +36,8 @@ pub mod space;
pub mod svg;
pub mod text;
pub mod text_input;
+pub mod toggler;
+pub mod tooltip;
#[doc(no_inline)]
pub use button::Button;
@@ -72,17 +73,21 @@ pub use svg::Svg;
pub use text::Text;
#[doc(no_inline)]
pub use text_input::TextInput;
+#[doc(no_inline)]
+pub use toggler::Toggler;
+#[doc(no_inline)]
+pub use tooltip::Tooltip;
-use crate::{layout, overlay, Clipboard, Event, Hasher, Layout, Length, Point};
+use crate::event::{self, Event};
+use crate::layout;
+use crate::overlay;
+use crate::{Clipboard, Hasher, Layout, Length, Point, Rectangle};
/// A component that displays information and allows interaction.
///
/// If you want to build your own widgets, you will need to implement this
/// trait.
///
-/// [`Widget`]: trait.Widget.html
-/// [`Element`]: ../struct.Element.html
-///
/// # Examples
/// The repository has some [examples] showcasing how to implement a custom
/// widget:
@@ -94,24 +99,20 @@ use crate::{layout, overlay, Clipboard, Event, Hasher, Layout, Length, Point};
/// - [`geometry`], a custom widget showcasing how to draw geometry with the
/// `Mesh2D` primitive in [`iced_wgpu`].
///
-/// [examples]: https://github.com/hecrj/iced/tree/0.1/examples
-/// [`bezier_tool`]: https://github.com/hecrj/iced/tree/0.1/examples/bezier_tool
-/// [`custom_widget`]: https://github.com/hecrj/iced/tree/0.1/examples/custom_widget
-/// [`geometry`]: https://github.com/hecrj/iced/tree/0.1/examples/geometry
+/// [examples]: https://github.com/hecrj/iced/tree/0.3/examples
+/// [`bezier_tool`]: https://github.com/hecrj/iced/tree/0.3/examples/bezier_tool
+/// [`custom_widget`]: https://github.com/hecrj/iced/tree/0.3/examples/custom_widget
+/// [`geometry`]: https://github.com/hecrj/iced/tree/0.3/examples/geometry
/// [`lyon`]: https://github.com/nical/lyon
-/// [`iced_wgpu`]: https://github.com/hecrj/iced/tree/0.1/wgpu
+/// [`iced_wgpu`]: https://github.com/hecrj/iced/tree/0.3/wgpu
pub trait Widget<Message, Renderer>
where
Renderer: crate::Renderer,
{
/// Returns the width of the [`Widget`].
- ///
- /// [`Widget`]: trait.Widget.html
fn width(&self) -> Length;
/// Returns the height of the [`Widget`].
- ///
- /// [`Widget`]: trait.Widget.html
fn height(&self) -> Length;
/// Returns the [`Node`] of the [`Widget`].
@@ -119,9 +120,7 @@ where
/// This [`Node`] is used by the runtime to compute the [`Layout`] of the
/// user interface.
///
- /// [`Node`]: ../layout/struct.Node.html
- /// [`Widget`]: trait.Widget.html
- /// [`Layout`]: ../layout/struct.Layout.html
+ /// [`Node`]: layout::Node
fn layout(
&self,
renderer: &Renderer,
@@ -129,14 +128,13 @@ where
) -> layout::Node;
/// Draws the [`Widget`] using the associated `Renderer`.
- ///
- /// [`Widget`]: trait.Widget.html
fn draw(
&self,
renderer: &mut Renderer,
defaults: &Renderer::Defaults,
layout: Layout<'_>,
cursor_position: Point,
+ viewport: &Rectangle,
) -> Renderer::Output;
/// Computes the _layout_ hash of the [`Widget`].
@@ -149,9 +147,7 @@ where
/// For example, the [`Text`] widget does not hash its color property, as
/// its value cannot affect the overall [`Layout`] of the user interface.
///
- /// [`Widget`]: trait.Widget.html
- /// [`Layout`]: ../layout/struct.Layout.html
- /// [`Text`]: text/struct.Text.html
+ /// [`Text`]: crate::widget::Text
fn hash_layout(&self, state: &mut Hasher);
/// Processes a runtime [`Event`].
@@ -166,25 +162,19 @@ where
/// * a [`Clipboard`], if available
///
/// By default, it does nothing.
- ///
- /// [`Event`]: ../enum.Event.html
- /// [`Widget`]: trait.Widget.html
- /// [`Layout`]: ../layout/struct.Layout.html
- /// [`Clipboard`]: ../trait.Clipboard.html
fn on_event(
&mut self,
_event: Event,
_layout: Layout<'_>,
_cursor_position: Point,
- _messages: &mut Vec<Message>,
_renderer: &Renderer,
- _clipboard: Option<&dyn Clipboard>,
- ) {
+ _clipboard: &mut dyn Clipboard,
+ _messages: &mut Vec<Message>,
+ ) -> event::Status {
+ event::Status::Ignored
}
- /// Returns the overlay of the [`Element`], if there is any.
- ///
- /// [`Element`]: struct.Element.html
+ /// Returns the overlay of the [`Widget`], if there is any.
fn overlay(
&mut self,
_layout: Layout<'_>,
diff --git a/native/src/widget/button.rs b/native/src/widget/button.rs
index c932da2b..c469a0e5 100644
--- a/native/src/widget/button.rs
+++ b/native/src/widget/button.rs
@@ -1,12 +1,14 @@
//! Allow your users to perform actions by pressing a button.
//!
//! A [`Button`] has some local [`State`].
-//!
-//! [`Button`]: struct.Button.html
-//! [`State`]: struct.State.html
+use crate::event::{self, Event};
+use crate::layout;
+use crate::mouse;
+use crate::overlay;
+use crate::touch;
use crate::{
- layout, mouse, Clipboard, Element, Event, Hasher, Layout, Length, Point,
- Rectangle, Widget,
+ Clipboard, Element, Hasher, Layout, Length, Padding, Point, Rectangle,
+ Widget,
};
use std::hash::Hash;
@@ -18,6 +20,7 @@ use std::hash::Hash;
/// # type Button<'a, Message> =
/// # iced_native::Button<'a, Message, iced_native::renderer::Null>;
/// #
+/// #[derive(Clone)]
/// enum Message {
/// ButtonPressed,
/// }
@@ -26,6 +29,29 @@ use std::hash::Hash;
/// let button = Button::new(&mut state, Text::new("Press me!"))
/// .on_press(Message::ButtonPressed);
/// ```
+///
+/// If a [`Button::on_press`] handler is not set, the resulting [`Button`] will
+/// be disabled:
+///
+/// ```
+/// # use iced_native::{button, Text};
+/// #
+/// # type Button<'a, Message> =
+/// # iced_native::Button<'a, Message, iced_native::renderer::Null>;
+/// #
+/// #[derive(Clone)]
+/// enum Message {
+/// ButtonPressed,
+/// }
+///
+/// fn disabled_button(state: &mut button::State) -> Button<'_, Message> {
+/// Button::new(state, Text::new("I'm disabled!"))
+/// }
+///
+/// fn enabled_button(state: &mut button::State) -> Button<'_, Message> {
+/// disabled_button(state).on_press(Message::ButtonPressed)
+/// }
+/// ```
#[allow(missing_debug_implementations)]
pub struct Button<'a, Message, Renderer: self::Renderer> {
state: &'a mut State,
@@ -35,19 +61,17 @@ pub struct Button<'a, Message, Renderer: self::Renderer> {
height: Length,
min_width: u32,
min_height: u32,
- padding: u16,
+ padding: Padding,
style: Renderer::Style,
}
impl<'a, Message, Renderer> Button<'a, Message, Renderer>
where
+ Message: Clone,
Renderer: self::Renderer,
{
/// Creates a new [`Button`] with some local [`State`] and the given
/// content.
- ///
- /// [`Button`]: struct.Button.html
- /// [`State`]: struct.State.html
pub fn new<E>(state: &'a mut State, content: E) -> Self
where
E: Into<Element<'a, Message, Renderer>>,
@@ -66,56 +90,43 @@ where
}
/// Sets the width of the [`Button`].
- ///
- /// [`Button`]: struct.Button.html
pub fn width(mut self, width: Length) -> Self {
self.width = width;
self
}
/// Sets the height of the [`Button`].
- ///
- /// [`Button`]: struct.Button.html
pub fn height(mut self, height: Length) -> Self {
self.height = height;
self
}
/// Sets the minimum width of the [`Button`].
- ///
- /// [`Button`]: struct.Button.html
pub fn min_width(mut self, min_width: u32) -> Self {
self.min_width = min_width;
self
}
/// Sets the minimum height of the [`Button`].
- ///
- /// [`Button`]: struct.Button.html
pub fn min_height(mut self, min_height: u32) -> Self {
self.min_height = min_height;
self
}
- /// Sets the padding of the [`Button`].
- ///
- /// [`Button`]: struct.Button.html
- pub fn padding(mut self, padding: u16) -> Self {
- self.padding = padding;
+ /// Sets the [`Padding`] of the [`Button`].
+ pub fn padding<P: Into<Padding>>(mut self, padding: P) -> Self {
+ self.padding = padding.into();
self
}
/// Sets the message that will be produced when the [`Button`] is pressed.
- ///
- /// [`Button`]: struct.Button.html
+ /// If on_press isn't set, button will be disabled.
pub fn on_press(mut self, msg: Message) -> Self {
self.on_press = Some(msg);
self
}
/// Sets the style of the [`Button`].
- ///
- /// [`Button`]: struct.Button.html
pub fn style(mut self, style: impl Into<Renderer::Style>) -> Self {
self.style = style.into();
self
@@ -123,8 +134,6 @@ where
}
/// The local state of a [`Button`].
-///
-/// [`Button`]: struct.Button.html
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct State {
is_pressed: bool,
@@ -132,8 +141,6 @@ pub struct State {
impl State {
/// Creates a new [`State`].
- ///
- /// [`State`]: struct.State.html
pub fn new() -> State {
State::default()
}
@@ -142,8 +149,8 @@ impl State {
impl<'a, Message, Renderer> Widget<Message, Renderer>
for Button<'a, Message, Renderer>
where
- Renderer: self::Renderer,
Message: Clone,
+ Renderer: self::Renderer,
{
fn width(&self) -> Length {
self.width
@@ -158,18 +165,20 @@ where
renderer: &Renderer,
limits: &layout::Limits,
) -> layout::Node {
- let padding = f32::from(self.padding);
let limits = limits
.min_width(self.min_width)
.min_height(self.min_height)
.width(self.width)
.height(self.height)
- .pad(padding);
+ .pad(self.padding);
let mut content = self.content.layout(renderer, &limits);
- content.move_to(Point::new(padding, padding));
+ content.move_to(Point::new(
+ self.padding.left.into(),
+ self.padding.top.into(),
+ ));
- let size = limits.resolve(content.size()).pad(padding);
+ let size = limits.resolve(content.size()).pad(self.padding);
layout::Node::with_children(size, vec![content])
}
@@ -179,34 +188,57 @@ where
event: Event,
layout: Layout<'_>,
cursor_position: Point,
+ renderer: &Renderer,
+ clipboard: &mut dyn Clipboard,
messages: &mut Vec<Message>,
- _renderer: &Renderer,
- _clipboard: Option<&dyn Clipboard>,
- ) {
+ ) -> event::Status {
+ if let event::Status::Captured = self.content.on_event(
+ event.clone(),
+ layout.children().next().unwrap(),
+ cursor_position,
+ renderer,
+ clipboard,
+ messages,
+ ) {
+ return event::Status::Captured;
+ }
+
match event {
- Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) => {
+ Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left))
+ | Event::Touch(touch::Event::FingerPressed { .. }) => {
if self.on_press.is_some() {
let bounds = layout.bounds();
- self.state.is_pressed = bounds.contains(cursor_position);
+ if bounds.contains(cursor_position) {
+ self.state.is_pressed = true;
+
+ return event::Status::Captured;
+ }
}
}
- Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left)) => {
+ Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left))
+ | Event::Touch(touch::Event::FingerLifted { .. }) => {
if let Some(on_press) = self.on_press.clone() {
let bounds = layout.bounds();
- let is_clicked = self.state.is_pressed
- && bounds.contains(cursor_position);
+ if self.state.is_pressed {
+ self.state.is_pressed = false;
- self.state.is_pressed = false;
+ if bounds.contains(cursor_position) {
+ messages.push(on_press);
+ }
- if is_clicked {
- messages.push(on_press);
+ return event::Status::Captured;
}
}
}
+ Event::Touch(touch::Event::FingerLost { .. }) => {
+ self.state.is_pressed = false;
+ }
_ => {}
}
+
+ event::Status::Ignored
}
fn draw(
@@ -215,6 +247,7 @@ where
defaults: &Renderer::Defaults,
layout: Layout<'_>,
cursor_position: Point,
+ _viewport: &Rectangle,
) -> Renderer::Output {
renderer.draw(
defaults,
@@ -235,6 +268,13 @@ where
self.width.hash(state);
self.content.hash_layout(state);
}
+
+ fn overlay(
+ &mut self,
+ layout: Layout<'_>,
+ ) -> Option<overlay::Element<'_, Message, Renderer>> {
+ self.content.overlay(layout.children().next().unwrap())
+ }
}
/// The renderer of a [`Button`].
@@ -242,20 +282,15 @@ where
/// Your [renderer] will need to implement this trait before being
/// able to use a [`Button`] in your user interface.
///
-/// [`Button`]: struct.Button.html
-/// [renderer]: ../../renderer/index.html
+/// [renderer]: crate::renderer
pub trait Renderer: crate::Renderer + Sized {
/// The default padding of a [`Button`].
- ///
- /// [`Button`]: struct.Button.html
- const DEFAULT_PADDING: u16;
+ const DEFAULT_PADDING: Padding;
/// The style supported by this renderer.
type Style: Default;
/// Draws a [`Button`].
- ///
- /// [`Button`]: struct.Button.html
fn draw<Message>(
&mut self,
defaults: &Self::Defaults,
@@ -272,8 +307,8 @@ pub trait Renderer: crate::Renderer + Sized {
impl<'a, Message, Renderer> From<Button<'a, Message, Renderer>>
for Element<'a, Message, Renderer>
where
- Renderer: 'a + self::Renderer,
Message: 'a + Clone,
+ Renderer: 'a + self::Renderer,
{
fn from(
button: Button<'a, Message, Renderer>,
diff --git a/native/src/widget/checkbox.rs b/native/src/widget/checkbox.rs
index 99178aae..0f21c873 100644
--- a/native/src/widget/checkbox.rs
+++ b/native/src/widget/checkbox.rs
@@ -1,10 +1,15 @@
//! Show toggle controls using checkboxes.
use std::hash::Hash;
+use crate::event::{self, Event};
+use crate::layout;
+use crate::mouse;
+use crate::row;
+use crate::text;
+use crate::touch;
use crate::{
- layout, mouse, row, text, Align, Clipboard, Element, Event, Hasher,
- HorizontalAlignment, Layout, Length, Point, Rectangle, Row, Text,
- VerticalAlignment, Widget,
+ Align, Clipboard, Color, Element, Hasher, HorizontalAlignment, Layout,
+ Length, Point, Rectangle, Row, Text, VerticalAlignment, Widget,
};
/// A box that can be checked.
@@ -34,6 +39,7 @@ pub struct Checkbox<Message, Renderer: self::Renderer + text::Renderer> {
spacing: u16,
text_size: Option<u16>,
font: Renderer::Font,
+ text_color: Option<Color>,
style: Renderer::Style,
}
@@ -48,8 +54,6 @@ impl<Message, Renderer: self::Renderer + text::Renderer>
/// * a function that will be called when the [`Checkbox`] is toggled. It
/// will receive the new state of the [`Checkbox`] and must produce a
/// `Message`.
- ///
- /// [`Checkbox`]: struct.Checkbox.html
pub fn new<F>(is_checked: bool, label: impl Into<String>, f: F) -> Self
where
F: 'static + Fn(bool) -> Message,
@@ -63,37 +67,30 @@ impl<Message, Renderer: self::Renderer + text::Renderer>
spacing: Renderer::DEFAULT_SPACING,
text_size: None,
font: Renderer::Font::default(),
+ text_color: None,
style: Renderer::Style::default(),
}
}
/// Sets the size of the [`Checkbox`].
- ///
- /// [`Checkbox`]: struct.Checkbox.html
pub fn size(mut self, size: u16) -> Self {
self.size = size;
self
}
/// Sets the width of the [`Checkbox`].
- ///
- /// [`Checkbox`]: struct.Checkbox.html
pub fn width(mut self, width: Length) -> Self {
self.width = width;
self
}
/// Sets the spacing between the [`Checkbox`] and the text.
- ///
- /// [`Checkbox`]: struct.Checkbox.html
pub fn spacing(mut self, spacing: u16) -> Self {
self.spacing = spacing;
self
}
/// Sets the text size of the [`Checkbox`].
- ///
- /// [`Checkbox`]: struct.Checkbox.html
pub fn text_size(mut self, text_size: u16) -> Self {
self.text_size = Some(text_size);
self
@@ -101,16 +98,19 @@ impl<Message, Renderer: self::Renderer + text::Renderer>
/// Sets the [`Font`] of the text of the [`Checkbox`].
///
- /// [`Checkbox`]: struct.Checkbox.html
- /// [`Font`]: ../../struct.Font.html
+ /// [`Font`]: crate::widget::text::Renderer::Font
pub fn font(mut self, font: Renderer::Font) -> Self {
self.font = font;
self
}
+ /// Sets the text color of the [`Checkbox`] button.
+ pub fn text_color(mut self, color: Color) -> Self {
+ self.text_color = Some(color);
+ self
+ }
+
/// Sets the style of the [`Checkbox`].
- ///
- /// [`Checkbox`]: struct.Checkbox.html
pub fn style(mut self, style: impl Into<Renderer::Style>) -> Self {
self.style = style.into();
self
@@ -158,20 +158,25 @@ where
event: Event,
layout: Layout<'_>,
cursor_position: Point,
- messages: &mut Vec<Message>,
_renderer: &Renderer,
- _clipboard: Option<&dyn Clipboard>,
- ) {
+ _clipboard: &mut dyn Clipboard,
+ messages: &mut Vec<Message>,
+ ) -> event::Status {
match event {
- Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) => {
+ Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left))
+ | Event::Touch(touch::Event::FingerPressed { .. }) => {
let mouse_over = layout.bounds().contains(cursor_position);
if mouse_over {
messages.push((self.on_toggle)(!self.is_checked));
+
+ return event::Status::Captured;
}
}
_ => {}
}
+
+ event::Status::Ignored
}
fn draw(
@@ -180,6 +185,7 @@ where
defaults: &Renderer::Defaults,
layout: Layout<'_>,
cursor_position: Point,
+ _viewport: &Rectangle,
) -> Renderer::Output {
let bounds = layout.bounds();
let mut children = layout.children();
@@ -195,7 +201,7 @@ where
&self.label,
self.text_size.unwrap_or(renderer.default_size()),
self.font,
- None,
+ self.text_color,
HorizontalAlignment::Left,
VerticalAlignment::Center,
);
@@ -225,20 +231,15 @@ where
/// Your [renderer] will need to implement this trait before being
/// able to use a [`Checkbox`] in your user interface.
///
-/// [`Checkbox`]: struct.Checkbox.html
-/// [renderer]: ../../renderer/index.html
+/// [renderer]: crate::Renderer
pub trait Renderer: crate::Renderer {
/// The style supported by this renderer.
type Style: Default;
/// The default size of a [`Checkbox`].
- ///
- /// [`Checkbox`]: struct.Checkbox.html
const DEFAULT_SIZE: u16;
/// The default spacing of a [`Checkbox`].
- ///
- /// [`Checkbox`]: struct.Checkbox.html
const DEFAULT_SPACING: u16;
/// Draws a [`Checkbox`].
@@ -248,8 +249,6 @@ pub trait Renderer: crate::Renderer {
/// * whether the [`Checkbox`] is selected or not
/// * whether the mouse is over the [`Checkbox`] or not
/// * the drawn label of the [`Checkbox`]
- ///
- /// [`Checkbox`]: struct.Checkbox.html
fn draw(
&mut self,
bounds: Rectangle,
diff --git a/native/src/widget/column.rs b/native/src/widget/column.rs
index 42cfe9b9..52a2e80c 100644
--- a/native/src/widget/column.rs
+++ b/native/src/widget/column.rs
@@ -1,22 +1,21 @@
//! Distribute content vertically.
use std::hash::Hash;
+use crate::event::{self, Event};
+use crate::layout;
+use crate::overlay;
use crate::{
- layout, overlay, Align, Clipboard, Element, Event, Hasher, Layout, Length,
- Point, Widget,
+ Align, Clipboard, Element, Hasher, Layout, Length, Padding, Point,
+ Rectangle, Widget,
};
use std::u32;
/// A container that distributes its contents vertically.
-///
-/// A [`Column`] will try to fill the horizontal space of its container.
-///
-/// [`Column`]: struct.Column.html
#[allow(missing_debug_implementations)]
pub struct Column<'a, Message, Renderer> {
spacing: u16,
- padding: u16,
+ padding: Padding,
width: Length,
height: Length,
max_width: u32,
@@ -27,21 +26,17 @@ pub struct Column<'a, Message, Renderer> {
impl<'a, Message, Renderer> Column<'a, Message, Renderer> {
/// Creates an empty [`Column`].
- ///
- /// [`Column`]: struct.Column.html
pub fn new() -> Self {
Self::with_children(Vec::new())
}
/// Creates a [`Column`] with the given elements.
- ///
- /// [`Column`]: struct.Column.html
pub fn with_children(
children: Vec<Element<'a, Message, Renderer>>,
) -> Self {
Column {
spacing: 0,
- padding: 0,
+ padding: Padding::ZERO,
width: Length::Shrink,
height: Length::Shrink,
max_width: u32::MAX,
@@ -61,57 +56,43 @@ impl<'a, Message, Renderer> Column<'a, Message, Renderer> {
self
}
- /// Sets the padding of the [`Column`].
- ///
- /// [`Column`]: struct.Column.html
- pub fn padding(mut self, units: u16) -> Self {
- self.padding = units;
+ /// Sets the [`Padding`] of the [`Column`].
+ pub fn padding<P: Into<Padding>>(mut self, padding: P) -> Self {
+ self.padding = padding.into();
self
}
/// Sets the width of the [`Column`].
- ///
- /// [`Column`]: struct.Column.html
pub fn width(mut self, width: Length) -> Self {
self.width = width;
self
}
/// Sets the height of the [`Column`].
- ///
- /// [`Column`]: struct.Column.html
pub fn height(mut self, height: Length) -> Self {
self.height = height;
self
}
/// Sets the maximum width of the [`Column`].
- ///
- /// [`Column`]: struct.Column.html
pub fn max_width(mut self, max_width: u32) -> Self {
self.max_width = max_width;
self
}
/// Sets the maximum height of the [`Column`] in pixels.
- ///
- /// [`Column`]: struct.Column.html
pub fn max_height(mut self, max_height: u32) -> Self {
self.max_height = max_height;
self
}
/// Sets the horizontal alignment of the contents of the [`Column`] .
- ///
- /// [`Column`]: struct.Column.html
pub fn align_items(mut self, align: Align) -> Self {
self.align_items = align;
self
}
/// Adds an element to the [`Column`].
- ///
- /// [`Column`]: struct.Column.html
pub fn push<E>(mut self, child: E) -> Self
where
E: Into<Element<'a, Message, Renderer>>,
@@ -149,7 +130,7 @@ where
layout::flex::Axis::Vertical,
renderer,
&limits,
- self.padding as f32,
+ self.padding,
self.spacing as f32,
self.align_items,
&self.children,
@@ -161,22 +142,24 @@ where
event: Event,
layout: Layout<'_>,
cursor_position: Point,
- messages: &mut Vec<Message>,
renderer: &Renderer,
- clipboard: Option<&dyn Clipboard>,
- ) {
- self.children.iter_mut().zip(layout.children()).for_each(
- |(child, layout)| {
+ clipboard: &mut dyn Clipboard,
+ messages: &mut Vec<Message>,
+ ) -> event::Status {
+ self.children
+ .iter_mut()
+ .zip(layout.children())
+ .map(|(child, layout)| {
child.widget.on_event(
event.clone(),
layout,
cursor_position,
- messages,
renderer,
clipboard,
+ messages,
)
- },
- );
+ })
+ .fold(event::Status::Ignored, event::Status::merge)
}
fn draw(
@@ -185,8 +168,15 @@ where
defaults: &Renderer::Defaults,
layout: Layout<'_>,
cursor_position: Point,
+ viewport: &Rectangle,
) -> Renderer::Output {
- renderer.draw(defaults, &self.children, layout, cursor_position)
+ renderer.draw(
+ defaults,
+ &self.children,
+ layout,
+ cursor_position,
+ viewport,
+ )
}
fn hash_layout(&self, state: &mut Hasher) {
@@ -199,6 +189,7 @@ where
self.max_height.hash(state);
self.align_items.hash(state);
self.spacing.hash(state);
+ self.padding.hash(state);
for child in &self.children {
child.widget.hash_layout(state);
@@ -222,8 +213,7 @@ where
/// Your [renderer] will need to implement this trait before being
/// able to use a [`Column`] in your user interface.
///
-/// [`Column`]: struct.Column.html
-/// [renderer]: ../../renderer/index.html
+/// [renderer]: crate::renderer
pub trait Renderer: crate::Renderer + Sized {
/// Draws a [`Column`].
///
@@ -231,15 +221,13 @@ pub trait Renderer: crate::Renderer + Sized {
/// - the children of the [`Column`]
/// - the [`Layout`] of the [`Column`] and its children
/// - the cursor position
- ///
- /// [`Column`]: struct.Column.html
- /// [`Layout`]: ../layout/struct.Layout.html
fn draw<Message>(
&mut self,
defaults: &Self::Defaults,
content: &[Element<'_, Message, Self>],
layout: Layout<'_>,
cursor_position: Point,
+ viewport: &Rectangle,
) -> Self::Output;
}
diff --git a/native/src/widget/container.rs b/native/src/widget/container.rs
index b8316e62..69aee64d 100644
--- a/native/src/widget/container.rs
+++ b/native/src/widget/container.rs
@@ -1,9 +1,12 @@
//! Decorate content and apply alignment.
use std::hash::Hash;
+use crate::event::{self, Event};
+use crate::layout;
+use crate::overlay;
use crate::{
- layout, overlay, Align, Clipboard, Element, Event, Hasher, Layout, Length,
- Point, Rectangle, Widget,
+ Align, Clipboard, Element, Hasher, Layout, Length, Padding, Point,
+ Rectangle, Widget,
};
use std::u32;
@@ -13,7 +16,7 @@ use std::u32;
/// It is normally used for alignment purposes.
#[allow(missing_debug_implementations)]
pub struct Container<'a, Message, Renderer: self::Renderer> {
- padding: u16,
+ padding: Padding,
width: Length,
height: Length,
max_width: u32,
@@ -29,14 +32,12 @@ where
Renderer: self::Renderer,
{
/// Creates an empty [`Container`].
- ///
- /// [`Container`]: struct.Container.html
pub fn new<T>(content: T) -> Self
where
T: Into<Element<'a, Message, Renderer>>,
{
Container {
- padding: 0,
+ padding: Padding::ZERO,
width: Length::Shrink,
height: Length::Shrink,
max_width: u32::MAX,
@@ -48,81 +49,61 @@ where
}
}
- /// Sets the padding of the [`Container`].
- ///
- /// [`Container`]: struct.Column.html
- pub fn padding(mut self, units: u16) -> Self {
- self.padding = units;
+ /// Sets the [`Padding`] of the [`Container`].
+ pub fn padding<P: Into<Padding>>(mut self, padding: P) -> Self {
+ self.padding = padding.into();
self
}
/// Sets the width of the [`Container`].
- ///
- /// [`Container`]: struct.Container.html
pub fn width(mut self, width: Length) -> Self {
self.width = width;
self
}
/// Sets the height of the [`Container`].
- ///
- /// [`Container`]: struct.Container.html
pub fn height(mut self, height: Length) -> Self {
self.height = height;
self
}
/// Sets the maximum width of the [`Container`].
- ///
- /// [`Container`]: struct.Container.html
pub fn max_width(mut self, max_width: u32) -> Self {
self.max_width = max_width;
self
}
/// Sets the maximum height of the [`Container`] in pixels.
- ///
- /// [`Container`]: struct.Container.html
pub fn max_height(mut self, max_height: u32) -> Self {
self.max_height = max_height;
self
}
/// Sets the content alignment for the horizontal axis of the [`Container`].
- ///
- /// [`Container`]: struct.Container.html
pub fn align_x(mut self, alignment: Align) -> Self {
self.horizontal_alignment = alignment;
self
}
/// Sets the content alignment for the vertical axis of the [`Container`].
- ///
- /// [`Container`]: struct.Container.html
pub fn align_y(mut self, alignment: Align) -> Self {
self.vertical_alignment = alignment;
self
}
/// Centers the contents in the horizontal axis of the [`Container`].
- ///
- /// [`Container`]: struct.Container.html
pub fn center_x(mut self) -> Self {
self.horizontal_alignment = Align::Center;
self
}
/// Centers the contents in the vertical axis of the [`Container`].
- ///
- /// [`Container`]: struct.Container.html
pub fn center_y(mut self) -> Self {
self.vertical_alignment = Align::Center;
self
}
/// Sets the style of the [`Container`].
- ///
- /// [`Container`]: struct.Container.html
pub fn style(mut self, style: impl Into<Renderer::Style>) -> Self {
self.style = style.into();
self
@@ -147,23 +128,24 @@ where
renderer: &Renderer,
limits: &layout::Limits,
) -> layout::Node {
- let padding = f32::from(self.padding);
-
let limits = limits
.loose()
.max_width(self.max_width)
.max_height(self.max_height)
.width(self.width)
.height(self.height)
- .pad(padding);
+ .pad(self.padding);
let mut content = self.content.layout(renderer, &limits.loose());
let size = limits.resolve(content.size());
- content.move_to(Point::new(padding, padding));
+ content.move_to(Point::new(
+ self.padding.left.into(),
+ self.padding.top.into(),
+ ));
content.align(self.horizontal_alignment, self.vertical_alignment, size);
- layout::Node::with_children(size.pad(padding), vec![content])
+ layout::Node::with_children(size.pad(self.padding), vec![content])
}
fn on_event(
@@ -171,17 +153,17 @@ where
event: Event,
layout: Layout<'_>,
cursor_position: Point,
- messages: &mut Vec<Message>,
renderer: &Renderer,
- clipboard: Option<&dyn Clipboard>,
- ) {
+ clipboard: &mut dyn Clipboard,
+ messages: &mut Vec<Message>,
+ ) -> event::Status {
self.content.widget.on_event(
event,
layout.children().next().unwrap(),
cursor_position,
- messages,
renderer,
clipboard,
+ messages,
)
}
@@ -191,11 +173,13 @@ where
defaults: &Renderer::Defaults,
layout: Layout<'_>,
cursor_position: Point,
+ viewport: &Rectangle,
) -> Renderer::Output {
renderer.draw(
defaults,
layout.bounds(),
cursor_position,
+ viewport,
&self.style,
&self.content,
layout.children().next().unwrap(),
@@ -228,20 +212,18 @@ where
/// Your [renderer] will need to implement this trait before being
/// able to use a [`Container`] in your user interface.
///
-/// [`Container`]: struct.Container.html
-/// [renderer]: ../../renderer/index.html
+/// [renderer]: crate::renderer
pub trait Renderer: crate::Renderer {
/// The style supported by this renderer.
type Style: Default;
/// Draws a [`Container`].
- ///
- /// [`Container`]: struct.Container.html
fn draw<Message>(
&mut self,
defaults: &Self::Defaults,
bounds: Rectangle,
cursor_position: Point,
+ viewport: &Rectangle,
style: &Self::Style,
content: &Element<'_, Message, Self>,
content_layout: Layout<'_>,
diff --git a/native/src/widget/image.rs b/native/src/widget/image.rs
index 132f249d..4d8e0a3f 100644
--- a/native/src/widget/image.rs
+++ b/native/src/widget/image.rs
@@ -1,5 +1,9 @@
//! Display images in your user interface.
-use crate::{layout, Element, Hasher, Layout, Length, Point, Size, Widget};
+pub mod viewer;
+pub use viewer::Viewer;
+
+use crate::layout;
+use crate::{Element, Hasher, Layout, Length, Point, Rectangle, Size, Widget};
use std::{
hash::{Hash, Hasher as _},
@@ -27,8 +31,6 @@ pub struct Image {
impl Image {
/// Creates a new [`Image`] with the given path.
- ///
- /// [`Image`]: struct.Image.html
pub fn new<T: Into<Handle>>(handle: T) -> Self {
Image {
handle: handle.into(),
@@ -38,16 +40,12 @@ impl Image {
}
/// Sets the width of the [`Image`] boundaries.
- ///
- /// [`Image`]: struct.Image.html
pub fn width(mut self, width: Length) -> Self {
self.width = width;
self
}
/// Sets the height of the [`Image`] boundaries.
- ///
- /// [`Image`]: struct.Image.html
pub fn height(mut self, height: Length) -> Self {
self.height = height;
self
@@ -97,6 +95,7 @@ where
_defaults: &Renderer::Defaults,
layout: Layout<'_>,
_cursor_position: Point,
+ _viewport: &Rectangle,
) -> Renderer::Output {
renderer.draw(self.handle.clone(), layout)
}
@@ -112,8 +111,6 @@ where
}
/// An [`Image`] handle.
-///
-/// [`Image`]: struct.Image.html
#[derive(Debug, Clone)]
pub struct Handle {
id: u64,
@@ -124,8 +121,6 @@ impl Handle {
/// Creates an image [`Handle`] pointing to the image of the given path.
///
/// Makes an educated guess about the image format by examining the data in the file.
- ///
- /// [`Handle`]: struct.Handle.html
pub fn from_path<T: Into<PathBuf>>(path: T) -> Handle {
Self::from_data(Data::Path(path.into()))
}
@@ -135,8 +130,6 @@ impl Handle {
/// pixels.
///
/// This is useful if you have already decoded your image.
- ///
- /// [`Handle`]: struct.Handle.html
pub fn from_pixels(width: u32, height: u32, pixels: Vec<u8>) -> Handle {
Self::from_data(Data::Pixels {
width,
@@ -151,8 +144,6 @@ impl Handle {
///
/// This is useful if you already have your image loaded in-memory, maybe
/// because you downloaded or generated it procedurally.
- ///
- /// [`Handle`]: struct.Handle.html
pub fn from_memory(bytes: Vec<u8>) -> Handle {
Self::from_data(Data::Bytes(bytes))
}
@@ -168,15 +159,11 @@ impl Handle {
}
/// Returns the unique identifier of the [`Handle`].
- ///
- /// [`Handle`]: struct.Handle.html
pub fn id(&self) -> u64 {
self.id
}
/// Returns a reference to the image [`Data`].
- ///
- /// [`Data`]: enum.Data.html
pub fn data(&self) -> &Data {
&self.data
}
@@ -198,8 +185,6 @@ impl Hash for Handle {
}
/// The data of an [`Image`].
-///
-/// [`Image`]: struct.Image.html
#[derive(Clone, Hash)]
pub enum Data {
/// File data
@@ -236,17 +221,12 @@ impl std::fmt::Debug for Data {
/// Your [renderer] will need to implement this trait before being able to use
/// an [`Image`] in your user interface.
///
-/// [`Image`]: struct.Image.html
-/// [renderer]: ../../renderer/index.html
+/// [renderer]: crate::renderer
pub trait Renderer: crate::Renderer {
/// Returns the dimensions of an [`Image`] located on the given path.
- ///
- /// [`Image`]: struct.Image.html
fn dimensions(&self, handle: &Handle) -> (u32, u32);
/// Draws an [`Image`].
- ///
- /// [`Image`]: struct.Image.html
fn draw(&mut self, handle: Handle, layout: Layout<'_>) -> Self::Output;
}
diff --git a/native/src/widget/image/viewer.rs b/native/src/widget/image/viewer.rs
new file mode 100644
index 00000000..405daf00
--- /dev/null
+++ b/native/src/widget/image/viewer.rs
@@ -0,0 +1,413 @@
+//! Zoom and pan on an image.
+use crate::event::{self, Event};
+use crate::image;
+use crate::layout;
+use crate::mouse;
+use crate::{
+ Clipboard, Element, Hasher, Layout, Length, Point, Rectangle, Size, Vector,
+ Widget,
+};
+
+use std::hash::Hash;
+
+/// A frame that displays an image with the ability to zoom in/out and pan.
+#[allow(missing_debug_implementations)]
+pub struct Viewer<'a> {
+ state: &'a mut State,
+ padding: u16,
+ width: Length,
+ height: Length,
+ min_scale: f32,
+ max_scale: f32,
+ scale_step: f32,
+ handle: image::Handle,
+}
+
+impl<'a> Viewer<'a> {
+ /// Creates a new [`Viewer`] with the given [`State`] and [`Handle`].
+ ///
+ /// [`Handle`]: image::Handle
+ pub fn new(state: &'a mut State, handle: image::Handle) -> Self {
+ Viewer {
+ state,
+ padding: 0,
+ width: Length::Shrink,
+ height: Length::Shrink,
+ min_scale: 0.25,
+ max_scale: 10.0,
+ scale_step: 0.10,
+ handle,
+ }
+ }
+
+ /// Sets the padding of the [`Viewer`].
+ pub fn padding(mut self, units: u16) -> Self {
+ self.padding = units;
+ self
+ }
+
+ /// Sets the width of the [`Viewer`].
+ pub fn width(mut self, width: Length) -> Self {
+ self.width = width;
+ self
+ }
+
+ /// Sets the height of the [`Viewer`].
+ pub fn height(mut self, height: Length) -> Self {
+ self.height = height;
+ self
+ }
+
+ /// Sets the max scale applied to the image of the [`Viewer`].
+ ///
+ /// Default is `10.0`
+ pub fn max_scale(mut self, max_scale: f32) -> Self {
+ self.max_scale = max_scale;
+ self
+ }
+
+ /// Sets the min scale applied to the image of the [`Viewer`].
+ ///
+ /// Default is `0.25`
+ pub fn min_scale(mut self, min_scale: f32) -> Self {
+ self.min_scale = min_scale;
+ self
+ }
+
+ /// Sets the percentage the image of the [`Viewer`] will be scaled by
+ /// when zoomed in / out.
+ ///
+ /// Default is `0.10`
+ pub fn scale_step(mut self, scale_step: f32) -> Self {
+ self.scale_step = scale_step;
+ self
+ }
+
+ /// Returns the bounds of the underlying image, given the bounds of
+ /// the [`Viewer`]. Scaling will be applied and original aspect ratio
+ /// will be respected.
+ fn image_size<Renderer>(&self, renderer: &Renderer, bounds: Size) -> Size
+ where
+ Renderer: self::Renderer + image::Renderer,
+ {
+ let (width, height) = renderer.dimensions(&self.handle);
+
+ let (width, height) = {
+ let dimensions = (width as f32, height as f32);
+
+ let width_ratio = bounds.width / dimensions.0;
+ let height_ratio = bounds.height / dimensions.1;
+
+ let ratio = width_ratio.min(height_ratio);
+
+ let scale = self.state.scale;
+
+ if ratio < 1.0 {
+ (dimensions.0 * ratio * scale, dimensions.1 * ratio * scale)
+ } else {
+ (dimensions.0 * scale, dimensions.1 * scale)
+ }
+ };
+
+ Size::new(width, height)
+ }
+}
+
+impl<'a, Message, Renderer> Widget<Message, Renderer> for Viewer<'a>
+where
+ Renderer: self::Renderer + image::Renderer,
+{
+ fn width(&self) -> Length {
+ self.width
+ }
+
+ fn height(&self) -> Length {
+ self.height
+ }
+
+ fn layout(
+ &self,
+ renderer: &Renderer,
+ limits: &layout::Limits,
+ ) -> layout::Node {
+ let (width, height) = renderer.dimensions(&self.handle);
+
+ let mut size = limits
+ .width(self.width)
+ .height(self.height)
+ .resolve(Size::new(width as f32, height as f32));
+
+ let expansion_size = if height > width {
+ self.width
+ } else {
+ self.height
+ };
+
+ // Only calculate viewport sizes if the images are constrained to a limited space.
+ // If they are Fill|Portion let them expand within their alotted space.
+ match expansion_size {
+ Length::Shrink | Length::Units(_) => {
+ let aspect_ratio = width as f32 / height as f32;
+ let viewport_aspect_ratio = size.width / size.height;
+ if viewport_aspect_ratio > aspect_ratio {
+ size.width = width as f32 * size.height / height as f32;
+ } else {
+ size.height = height as f32 * size.width / width as f32;
+ }
+ }
+ Length::Fill | Length::FillPortion(_) => {}
+ }
+
+ layout::Node::new(size)
+ }
+
+ fn on_event(
+ &mut self,
+ event: Event,
+ layout: Layout<'_>,
+ cursor_position: Point,
+ renderer: &Renderer,
+ _clipboard: &mut dyn Clipboard,
+ _messages: &mut Vec<Message>,
+ ) -> event::Status {
+ let bounds = layout.bounds();
+ let is_mouse_over = bounds.contains(cursor_position);
+
+ match event {
+ Event::Mouse(mouse::Event::WheelScrolled { delta })
+ if is_mouse_over =>
+ {
+ match delta {
+ mouse::ScrollDelta::Lines { y, .. }
+ | mouse::ScrollDelta::Pixels { y, .. } => {
+ let previous_scale = self.state.scale;
+
+ if y < 0.0 && previous_scale > self.min_scale
+ || y > 0.0 && previous_scale < self.max_scale
+ {
+ self.state.scale = (if y > 0.0 {
+ self.state.scale * (1.0 + self.scale_step)
+ } else {
+ self.state.scale / (1.0 + self.scale_step)
+ })
+ .max(self.min_scale)
+ .min(self.max_scale);
+
+ let image_size =
+ self.image_size(renderer, bounds.size());
+
+ let factor =
+ self.state.scale / previous_scale - 1.0;
+
+ let cursor_to_center =
+ cursor_position - bounds.center();
+
+ let adjustment = cursor_to_center * factor
+ + self.state.current_offset * factor;
+
+ self.state.current_offset = Vector::new(
+ if image_size.width > bounds.width {
+ self.state.current_offset.x + adjustment.x
+ } else {
+ 0.0
+ },
+ if image_size.height > bounds.height {
+ self.state.current_offset.y + adjustment.y
+ } else {
+ 0.0
+ },
+ );
+ }
+ }
+ }
+
+ event::Status::Captured
+ }
+ Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left))
+ if is_mouse_over =>
+ {
+ self.state.cursor_grabbed_at = Some(cursor_position);
+ self.state.starting_offset = self.state.current_offset;
+
+ event::Status::Captured
+ }
+ Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left))
+ if self.state.cursor_grabbed_at.is_some() =>
+ {
+ self.state.cursor_grabbed_at = None;
+
+ event::Status::Captured
+ }
+ Event::Mouse(mouse::Event::CursorMoved { position }) => {
+ if let Some(origin) = self.state.cursor_grabbed_at {
+ let image_size = self.image_size(renderer, bounds.size());
+
+ let hidden_width = (image_size.width - bounds.width / 2.0)
+ .max(0.0)
+ .round();
+
+ let hidden_height = (image_size.height
+ - bounds.height / 2.0)
+ .max(0.0)
+ .round();
+
+ let delta = position - origin;
+
+ let x = if bounds.width < image_size.width {
+ (self.state.starting_offset.x - delta.x)
+ .min(hidden_width)
+ .max(-hidden_width)
+ } else {
+ 0.0
+ };
+
+ let y = if bounds.height < image_size.height {
+ (self.state.starting_offset.y - delta.y)
+ .min(hidden_height)
+ .max(-hidden_height)
+ } else {
+ 0.0
+ };
+
+ self.state.current_offset = Vector::new(x, y);
+
+ event::Status::Captured
+ } else {
+ event::Status::Ignored
+ }
+ }
+ _ => event::Status::Ignored,
+ }
+ }
+
+ fn draw(
+ &self,
+ renderer: &mut Renderer,
+ _defaults: &Renderer::Defaults,
+ layout: Layout<'_>,
+ cursor_position: Point,
+ _viewport: &Rectangle,
+ ) -> Renderer::Output {
+ let bounds = layout.bounds();
+
+ let image_size = self.image_size(renderer, bounds.size());
+
+ let translation = {
+ let image_top_left = Vector::new(
+ bounds.width / 2.0 - image_size.width / 2.0,
+ bounds.height / 2.0 - image_size.height / 2.0,
+ );
+
+ image_top_left - self.state.offset(bounds, image_size)
+ };
+
+ let is_mouse_over = bounds.contains(cursor_position);
+
+ self::Renderer::draw(
+ renderer,
+ &self.state,
+ bounds,
+ image_size,
+ translation,
+ self.handle.clone(),
+ is_mouse_over,
+ )
+ }
+
+ fn hash_layout(&self, state: &mut Hasher) {
+ struct Marker;
+ std::any::TypeId::of::<Marker>().hash(state);
+
+ self.width.hash(state);
+ self.height.hash(state);
+ self.padding.hash(state);
+
+ self.handle.hash(state);
+ }
+}
+
+/// The local state of a [`Viewer`].
+#[derive(Debug, Clone, Copy)]
+pub struct State {
+ scale: f32,
+ starting_offset: Vector,
+ current_offset: Vector,
+ cursor_grabbed_at: Option<Point>,
+}
+
+impl Default for State {
+ fn default() -> Self {
+ Self {
+ scale: 1.0,
+ starting_offset: Vector::default(),
+ current_offset: Vector::default(),
+ cursor_grabbed_at: None,
+ }
+ }
+}
+
+impl State {
+ /// Creates a new [`State`].
+ pub fn new() -> Self {
+ State::default()
+ }
+
+ /// Returns the current offset of the [`State`], given the bounds
+ /// of the [`Viewer`] and its image.
+ fn offset(&self, bounds: Rectangle, image_size: Size) -> Vector {
+ let hidden_width =
+ (image_size.width - bounds.width / 2.0).max(0.0).round();
+
+ let hidden_height =
+ (image_size.height - bounds.height / 2.0).max(0.0).round();
+
+ Vector::new(
+ self.current_offset.x.min(hidden_width).max(-hidden_width),
+ self.current_offset.y.min(hidden_height).max(-hidden_height),
+ )
+ }
+
+ /// Returns if the cursor is currently grabbed by the [`Viewer`].
+ pub fn is_cursor_grabbed(&self) -> bool {
+ self.cursor_grabbed_at.is_some()
+ }
+}
+
+/// The renderer of an [`Viewer`].
+///
+/// Your [renderer] will need to implement this trait before being
+/// able to use a [`Viewer`] in your user interface.
+///
+/// [renderer]: crate::renderer
+pub trait Renderer: crate::Renderer + Sized {
+ /// Draws the [`Viewer`].
+ ///
+ /// It receives:
+ /// - the [`State`] of the [`Viewer`]
+ /// - the bounds of the [`Viewer`] widget
+ /// - the [`Size`] of the scaled [`Viewer`] image
+ /// - the translation of the clipped image
+ /// - the [`Handle`] to the underlying image
+ /// - whether the mouse is over the [`Viewer`] or not
+ ///
+ /// [`Handle`]: image::Handle
+ fn draw(
+ &mut self,
+ state: &State,
+ bounds: Rectangle,
+ image_size: Size,
+ translation: Vector,
+ handle: image::Handle,
+ is_mouse_over: bool,
+ ) -> Self::Output;
+}
+
+impl<'a, Message, Renderer> From<Viewer<'a>> for Element<'a, Message, Renderer>
+where
+ Renderer: 'a + self::Renderer + image::Renderer,
+ Message: 'a,
+{
+ fn from(viewer: Viewer<'a>) -> Element<'a, Message, Renderer> {
+ Element::new(viewer)
+ }
+}
diff --git a/native/src/widget/pane_grid.rs b/native/src/widget/pane_grid.rs
index 5180fd3b..26a72409 100644
--- a/native/src/widget/pane_grid.rs
+++ b/native/src/widget/pane_grid.rs
@@ -6,8 +6,7 @@
//! The [`pane_grid` example] showcases how to use a [`PaneGrid`] with resizing,
//! drag and drop, and hotkey support.
//!
-//! [`pane_grid` example]: https://github.com/hecrj/iced/tree/0.1/examples/pane_grid
-//! [`PaneGrid`]: struct.PaneGrid.html
+//! [`pane_grid` example]: https://github.com/hecrj/iced/tree/0.3/examples/pane_grid
mod axis;
mod configuration;
mod content;
@@ -25,12 +24,19 @@ pub use direction::Direction;
pub use node::Node;
pub use pane::Pane;
pub use split::Split;
-pub use state::{Focus, State};
+pub use state::State;
pub use title_bar::TitleBar;
+use crate::container;
+use crate::event::{self, Event};
+use crate::layout;
+use crate::mouse;
+use crate::overlay;
+use crate::row;
+use crate::touch;
use crate::{
- container, keyboard, layout, mouse, overlay, row, text, Clipboard, Element,
- Event, Hasher, Layout, Length, Point, Rectangle, Size, Vector, Widget,
+ Clipboard, Element, Hasher, Layout, Length, Point, Rectangle, Size, Vector,
+ Widget,
};
/// A collection of panes distributed using either vertical or horizontal splits
@@ -73,7 +79,7 @@ use crate::{
/// let (mut state, _) = pane_grid::State::new(PaneState::SomePane);
///
/// let pane_grid =
-/// PaneGrid::new(&mut state, |pane, state, focus| {
+/// PaneGrid::new(&mut state, |pane, state| {
/// pane_grid::Content::new(match state {
/// PaneState::SomePane => Text::new("This is some pane"),
/// PaneState::AnotherKindOfPane => Text::new("This is another kind of pane"),
@@ -82,9 +88,6 @@ use crate::{
/// .on_drag(Message::PaneDragged)
/// .on_resize(10, Message::PaneResized);
/// ```
-///
-/// [`PaneGrid`]: struct.PaneGrid.html
-/// [`State`]: struct.State.html
#[allow(missing_debug_implementations)]
pub struct PaneGrid<'a, Message, Renderer: self::Renderer> {
state: &'a mut state::Internal,
@@ -92,10 +95,10 @@ pub struct PaneGrid<'a, Message, Renderer: self::Renderer> {
width: Length,
height: Length,
spacing: u16,
- modifier_keys: keyboard::ModifiersState,
+ on_click: Option<Box<dyn Fn(Pane) -> Message + 'a>>,
on_drag: Option<Box<dyn Fn(DragEvent) -> Message + 'a>>,
on_resize: Option<(u16, Box<dyn Fn(ResizeEvent) -> Message + 'a>)>,
- on_key_press: Option<Box<dyn Fn(KeyPressEvent) -> Option<Message> + 'a>>,
+ style: <Renderer as self::Renderer>::Style,
}
impl<'a, Message, Renderer> PaneGrid<'a, Message, Renderer>
@@ -106,37 +109,15 @@ where
///
/// The view function will be called to display each [`Pane`] present in the
/// [`State`].
- ///
- /// [`PaneGrid`]: struct.PaneGrid.html
- /// [`State`]: struct.State.html
- /// [`Pane`]: struct.Pane.html
pub fn new<T>(
state: &'a mut State<T>,
- view: impl Fn(
- Pane,
- &'a mut T,
- Option<Focus>,
- ) -> Content<'a, Message, Renderer>,
+ view: impl Fn(Pane, &'a mut T) -> Content<'a, Message, Renderer>,
) -> Self {
let elements = {
- let action = state.internal.action();
- let current_focus = action.focus();
-
state
.panes
.iter_mut()
- .map(move |(pane, pane_state)| {
- let focus = match current_focus {
- Some((focused_pane, focus))
- if *pane == focused_pane =>
- {
- Some(focus)
- }
- _ => None,
- };
-
- (*pane, view(*pane, pane_state, focus))
- })
+ .map(|(pane, pane_state)| (*pane, view(*pane, pane_state)))
.collect()
};
@@ -146,59 +127,43 @@ where
width: Length::Fill,
height: Length::Fill,
spacing: 0,
- modifier_keys: keyboard::ModifiersState {
- control: true,
- ..Default::default()
- },
+ on_click: None,
on_drag: None,
on_resize: None,
- on_key_press: None,
+ style: Default::default(),
}
}
/// Sets the width of the [`PaneGrid`].
- ///
- /// [`PaneGrid`]: struct.PaneGrid.html
pub fn width(mut self, width: Length) -> Self {
self.width = width;
self
}
/// Sets the height of the [`PaneGrid`].
- ///
- /// [`PaneGrid`]: struct.PaneGrid.html
pub fn height(mut self, height: Length) -> Self {
self.height = height;
self
}
/// Sets the spacing _between_ the panes of the [`PaneGrid`].
- ///
- /// [`PaneGrid`]: struct.PaneGrid.html
pub fn spacing(mut self, units: u16) -> Self {
self.spacing = units;
self
}
- /// Sets the modifier keys of the [`PaneGrid`].
- ///
- /// The modifier keys will need to be pressed to trigger key events.
- ///
- /// The default modifier key is `Ctrl`.
- ///
- /// [`PaneGrid`]: struct.PaneGrid.html
- pub fn modifier_keys(
- mut self,
- modifier_keys: keyboard::ModifiersState,
- ) -> Self {
- self.modifier_keys = modifier_keys;
+ /// Sets the message that will be produced when a [`Pane`] of the
+ /// [`PaneGrid`] is clicked.
+ pub fn on_click<F>(mut self, f: F) -> Self
+ where
+ F: 'a + Fn(Pane) -> Message,
+ {
+ self.on_click = Some(Box::new(f));
self
}
/// Enables the drag and drop interactions of the [`PaneGrid`], which will
/// use the provided function to produce messages.
- ///
- /// [`PaneGrid`]: struct.PaneGrid.html
pub fn on_drag<F>(mut self, f: F) -> Self
where
F: 'a + Fn(DragEvent) -> Message,
@@ -216,8 +181,6 @@ where
/// The grabbable area of a split will have a length of `spacing + leeway`,
/// properly centered. In other words, a length of
/// `(spacing + leeway) / 2.0` on either side of the split line.
- ///
- /// [`PaneGrid`]: struct.PaneGrid.html
pub fn on_resize<F>(mut self, leeway: u16, f: F) -> Self
where
F: 'a + Fn(ResizeEvent) -> Message,
@@ -226,28 +189,12 @@ where
self
}
- /// Captures hotkey interactions with the [`PaneGrid`], using the provided
- /// function to produce messages.
- ///
- /// The function will be called when:
- /// - a [`Pane`] is focused
- /// - a key is pressed
- /// - all the modifier keys are pressed
- ///
- /// If the function returns `None`, the key press event will be discarded
- /// without producing any message.
- ///
- /// This method is particularly useful to implement hotkey interactions.
- /// For instance, you can use it to enable splitting, swapping, or resizing
- /// panes by pressing combinations of keys.
- ///
- /// [`PaneGrid`]: struct.PaneGrid.html
- /// [`Pane`]: struct.Pane.html
- pub fn on_key_press<F>(mut self, f: F) -> Self
- where
- F: 'a + Fn(KeyPressEvent) -> Option<Message>,
- {
- self.on_key_press = Some(Box::new(f));
+ /// Sets the style of the [`PaneGrid`].
+ pub fn style(
+ mut self,
+ style: impl Into<<Renderer as self::Renderer>::Style>,
+ ) -> Self {
+ self.style = style.into();
self
}
}
@@ -268,24 +215,20 @@ where
);
if let Some(((pane, content), layout)) = clicked_region.next() {
- match &self.on_drag {
- Some(on_drag) => {
- if content.can_be_picked_at(layout, cursor_position) {
- let pane_position = layout.position();
+ if let Some(on_click) = &self.on_click {
+ messages.push(on_click(*pane));
+ }
- let origin = cursor_position
- - Vector::new(pane_position.x, pane_position.y);
+ if let Some(on_drag) = &self.on_drag {
+ if content.can_be_picked_at(layout, cursor_position) {
+ let pane_position = layout.position();
- self.state.pick_pane(pane, origin);
+ let origin = cursor_position
+ - Vector::new(pane_position.x, pane_position.y);
- messages
- .push(on_drag(DragEvent::Picked { pane: *pane }));
- } else {
- self.state.focus(pane);
- }
- }
- None => {
- self.state.focus(pane);
+ self.state.pick_pane(pane, origin);
+
+ messages.push(on_drag(DragEvent::Picked { pane: *pane }));
}
}
}
@@ -296,7 +239,7 @@ where
layout: Layout<'_>,
cursor_position: Point,
messages: &mut Vec<Message>,
- ) {
+ ) -> event::Status {
if let Some((_, on_resize)) = &self.on_resize {
if let Some((split, _)) = self.state.picked_split() {
let bounds = layout.bounds();
@@ -323,85 +266,55 @@ where
};
messages.push(on_resize(ResizeEvent { split, ratio }));
+
+ return event::Status::Captured;
}
}
}
+
+ event::Status::Ignored
}
}
/// An event produced during a drag and drop interaction of a [`PaneGrid`].
-///
-/// [`PaneGrid`]: struct.PaneGrid.html
#[derive(Debug, Clone, Copy)]
pub enum DragEvent {
/// A [`Pane`] was picked for dragging.
- ///
- /// [`Pane`]: struct.Pane.html
Picked {
/// The picked [`Pane`].
- ///
- /// [`Pane`]: struct.Pane.html
pane: Pane,
},
/// A [`Pane`] was dropped on top of another [`Pane`].
- ///
- /// [`Pane`]: struct.Pane.html
Dropped {
/// The picked [`Pane`].
- ///
- /// [`Pane`]: struct.Pane.html
pane: Pane,
/// The [`Pane`] where the picked one was dropped on.
- ///
- /// [`Pane`]: struct.Pane.html
target: Pane,
},
/// A [`Pane`] was picked and then dropped outside of other [`Pane`]
/// boundaries.
- ///
- /// [`Pane`]: struct.Pane.html
Canceled {
/// The picked [`Pane`].
- ///
- /// [`Pane`]: struct.Pane.html
pane: Pane,
},
}
/// An event produced during a resize interaction of a [`PaneGrid`].
-///
-/// [`PaneGrid`]: struct.PaneGrid.html
#[derive(Debug, Clone, Copy)]
pub struct ResizeEvent {
/// The [`Split`] that is being dragged for resizing.
- ///
- /// [`Split`]: struct.Split.html
pub split: Split,
/// The new ratio of the [`Split`].
///
/// The ratio is a value in [0, 1], representing the exact position of a
/// [`Split`] between two panes.
- ///
- /// [`Split`]: struct.Split.html
pub ratio: f32,
}
-/// An event produced during a key press interaction of a [`PaneGrid`].
-///
-/// [`PaneGrid`]: struct.PaneGrid.html
-#[derive(Debug, Clone, Copy)]
-pub struct KeyPressEvent {
- /// The key that was pressed.
- pub key_code: keyboard::KeyCode,
-
- /// The state of the modifier keys when the key was pressed.
- pub modifiers: keyboard::ModifiersState,
-}
-
impl<'a, Message, Renderer> Widget<Message, Renderer>
for PaneGrid<'a, Message, Renderer>
where
@@ -449,45 +362,41 @@ where
event: Event,
layout: Layout<'_>,
cursor_position: Point,
- messages: &mut Vec<Message>,
renderer: &Renderer,
- clipboard: Option<&dyn Clipboard>,
- ) {
- match event {
- Event::Mouse(mouse_event) => match mouse_event {
- mouse::Event::ButtonPressed(mouse::Button::Left) => {
- let bounds = layout.bounds();
-
- if bounds.contains(cursor_position) {
- match self.on_resize {
- Some((leeway, _)) => {
- let relative_cursor = Point::new(
- cursor_position.x - bounds.x,
- cursor_position.y - bounds.y,
- );
-
- let splits = self.state.split_regions(
- f32::from(self.spacing),
- Size::new(bounds.width, bounds.height),
- );
+ clipboard: &mut dyn Clipboard,
+ messages: &mut Vec<Message>,
+ ) -> event::Status {
+ let mut event_status = event::Status::Ignored;
- let clicked_split = hovered_split(
- splits.iter(),
- f32::from(self.spacing + leeway),
- relative_cursor,
- );
+ match event {
+ Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left))
+ | Event::Touch(touch::Event::FingerPressed { .. }) => {
+ let bounds = layout.bounds();
- if let Some((split, axis)) = clicked_split {
- self.state.pick_split(&split, axis);
- } else {
- self.click_pane(
- layout,
- cursor_position,
- messages,
- );
- }
- }
- None => {
+ if bounds.contains(cursor_position) {
+ event_status = event::Status::Captured;
+
+ match self.on_resize {
+ Some((leeway, _)) => {
+ let relative_cursor = Point::new(
+ cursor_position.x - bounds.x,
+ cursor_position.y - bounds.y,
+ );
+
+ let splits = self.state.split_regions(
+ f32::from(self.spacing),
+ Size::new(bounds.width, bounds.height),
+ );
+
+ let clicked_split = hovered_split(
+ splits.iter(),
+ f32::from(self.spacing + leeway),
+ relative_cursor,
+ );
+
+ if let Some((split, axis, _)) = clicked_split {
+ self.state.pick_split(&split, axis);
+ } else {
self.click_pane(
layout,
cursor_position,
@@ -495,91 +404,73 @@ where
);
}
}
- } else {
- // TODO: Encode cursor availability in the type system
- if cursor_position.x > 0.0 && cursor_position.y > 0.0 {
- self.state.unfocus();
+ None => {
+ self.click_pane(layout, cursor_position, messages);
}
}
}
- mouse::Event::ButtonReleased(mouse::Button::Left) => {
- if let Some((pane, _)) = self.state.picked_pane() {
- self.state.focus(&pane);
-
- if let Some(on_drag) = &self.on_drag {
- let mut dropped_region = self
- .elements
- .iter()
- .zip(layout.children())
- .filter(|(_, layout)| {
+ }
+ Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left))
+ | Event::Touch(touch::Event::FingerLifted { .. })
+ | Event::Touch(touch::Event::FingerLost { .. }) => {
+ if let Some((pane, _)) = self.state.picked_pane() {
+ if let Some(on_drag) = &self.on_drag {
+ let mut dropped_region =
+ self.elements.iter().zip(layout.children()).filter(
+ |(_, layout)| {
layout.bounds().contains(cursor_position)
- });
-
- let event = match dropped_region.next() {
- Some(((target, _), _)) if pane != *target => {
- DragEvent::Dropped {
- pane,
- target: *target,
- }
+ },
+ );
+
+ let event = match dropped_region.next() {
+ Some(((target, _), _)) if pane != *target => {
+ DragEvent::Dropped {
+ pane,
+ target: *target,
}
- _ => DragEvent::Canceled { pane },
- };
+ }
+ _ => DragEvent::Canceled { pane },
+ };
- messages.push(on_drag(event));
- }
- } else if self.state.picked_split().is_some() {
- self.state.drop_split();
+ messages.push(on_drag(event));
}
+
+ self.state.idle();
+
+ event_status = event::Status::Captured;
+ } else if self.state.picked_split().is_some() {
+ self.state.idle();
+
+ event_status = event::Status::Captured;
}
- mouse::Event::CursorMoved { .. } => {
+ }
+ Event::Mouse(mouse::Event::CursorMoved { .. })
+ | Event::Touch(touch::Event::FingerMoved { .. }) => {
+ event_status =
self.trigger_resize(layout, cursor_position, messages);
- }
- _ => {}
- },
- Event::Keyboard(keyboard_event) => {
- match keyboard_event {
- keyboard::Event::KeyPressed {
- modifiers,
- key_code,
- } => {
- if let Some(on_key_press) = &self.on_key_press {
- // TODO: Discard when event is captured
- if let Some(_) = self.state.active_pane() {
- if modifiers.matches(self.modifier_keys) {
- if let Some(message) =
- on_key_press(KeyPressEvent {
- key_code,
- modifiers,
- })
- {
- messages.push(message);
- }
- }
- }
- }
- }
- _ => {}
- }
}
_ => {}
}
- if self.state.picked_pane().is_none() {
- {
- self.elements.iter_mut().zip(layout.children()).for_each(
- |((_, pane), layout)| {
- pane.on_event(
- event.clone(),
- layout,
- cursor_position,
- messages,
- renderer,
- clipboard,
- )
- },
- );
- }
- }
+ let picked_pane = self.state.picked_pane().map(|(pane, _)| pane);
+
+ self.elements
+ .iter_mut()
+ .zip(layout.children())
+ .map(|((pane, content), layout)| {
+ let is_picked = picked_pane == Some(*pane);
+
+ content.on_event(
+ event.clone(),
+ layout,
+ cursor_position,
+ renderer,
+ clipboard,
+ messages,
+ is_picked,
+ )
+ })
+ .fold(event_status, event::Status::merge)
}
fn draw(
@@ -588,10 +479,28 @@ where
defaults: &Renderer::Defaults,
layout: Layout<'_>,
cursor_position: Point,
+ viewport: &Rectangle,
) -> Renderer::Output {
let picked_split = self
.state
.picked_split()
+ .and_then(|(split, axis)| {
+ let bounds = layout.bounds();
+
+ let splits = self
+ .state
+ .split_regions(f32::from(self.spacing), bounds.size());
+
+ let (_axis, region, ratio) = splits.get(&split)?;
+
+ let region = axis.split_line_bounds(
+ *region,
+ *ratio,
+ f32::from(self.spacing),
+ );
+
+ Some((axis, region + Vector::new(bounds.x, bounds.y), true))
+ })
.or_else(|| match self.on_resize {
Some((leeway, _)) => {
let bounds = layout.bounds();
@@ -605,15 +514,20 @@ where
.state
.split_regions(f32::from(self.spacing), bounds.size());
- hovered_split(
+ let (_split, axis, region) = hovered_split(
splits.iter(),
f32::from(self.spacing + leeway),
relative_cursor,
- )
+ )?;
+
+ Some((
+ axis,
+ region + Vector::new(bounds.x, bounds.y),
+ false,
+ ))
}
None => None,
- })
- .map(|(_, axis)| axis);
+ });
self::Renderer::draw(
renderer,
@@ -622,7 +536,9 @@ where
self.state.picked_pane(),
picked_split,
layout,
+ &self.style,
cursor_position,
+ viewport,
)
}
@@ -658,11 +574,11 @@ where
/// Your [renderer] will need to implement this trait before being
/// able to use a [`PaneGrid`] in your user interface.
///
-/// [`PaneGrid`]: struct.PaneGrid.html
-/// [renderer]: ../../renderer/index.html
-pub trait Renderer:
- crate::Renderer + container::Renderer + text::Renderer + Sized
-{
+/// [renderer]: crate::renderer
+pub trait Renderer: crate::Renderer + container::Renderer + Sized {
+ /// The style supported by this renderer.
+ type Style: Default;
+
/// Draws a [`PaneGrid`].
///
/// It receives:
@@ -671,18 +587,16 @@ pub trait Renderer:
/// - the [`Axis`] that is currently being resized
/// - the [`Layout`] of the [`PaneGrid`] and its elements
/// - the cursor position
- ///
- /// [`PaneGrid`]: struct.PaneGrid.html
- /// [`Pane`]: struct.Pane.html
- /// [`Layout`]: ../layout/struct.Layout.html
fn draw<Message>(
&mut self,
defaults: &Self::Defaults,
content: &[(Pane, Content<'_, Message, Self>)],
dragging: Option<(Pane, Point)>,
- resizing: Option<Axis>,
+ resizing: Option<(Axis, Rectangle, bool)>,
layout: Layout<'_>,
+ style: &<Self as self::Renderer>::Style,
cursor_position: Point,
+ viewport: &Rectangle,
) -> Self::Output;
/// Draws a [`Pane`].
@@ -692,17 +606,15 @@ pub trait Renderer:
/// - the [`Content`] of the [`Pane`]
/// - the [`Layout`] of the [`Pane`] and its elements
/// - the cursor position
- ///
- /// [`Pane`]: struct.Pane.html
- /// [`Layout`]: ../layout/struct.Layout.html
fn draw_pane<Message>(
&mut self,
defaults: &Self::Defaults,
bounds: Rectangle,
- style: &Self::Style,
+ style: &<Self as container::Renderer>::Style,
title_bar: Option<(&TitleBar<'_, Message, Self>, Layout<'_>)>,
body: (&Element<'_, Message, Self>, Layout<'_>),
cursor_position: Point,
+ viewport: &Rectangle,
) -> Self::Output;
/// Draws a [`TitleBar`].
@@ -710,23 +622,18 @@ pub trait Renderer:
/// It receives:
/// - the bounds, style of the [`TitleBar`]
/// - the style of the [`TitleBar`]
- /// - the title of the [`TitleBar`] with its size, font, and bounds
- /// - the controls of the [`TitleBar`] with their [`Layout`+, if any
+ /// - the content of the [`TitleBar`] with its layout
+ /// - the controls of the [`TitleBar`] with their [`Layout`], if any
/// - the cursor position
- ///
- /// [`TitleBar`]: struct.TitleBar.html
- /// [`Layout`]: ../layout/struct.Layout.html
fn draw_title_bar<Message>(
&mut self,
defaults: &Self::Defaults,
bounds: Rectangle,
- style: &Self::Style,
- title: &str,
- title_size: u16,
- title_font: Self::Font,
- title_bounds: Rectangle,
+ style: &<Self as container::Renderer>::Style,
+ content: (&Element<'_, Message, Self>, Layout<'_>),
controls: Option<(&Element<'_, Message, Self>, Layout<'_>)>,
cursor_position: Point,
+ viewport: &Rectangle,
) -> Self::Output;
}
@@ -750,14 +657,14 @@ fn hovered_split<'a>(
splits: impl Iterator<Item = (&'a Split, &'a (Axis, Rectangle, f32))>,
spacing: f32,
cursor_position: Point,
-) -> Option<(Split, Axis)> {
+) -> Option<(Split, Axis, Rectangle)> {
splits
.filter_map(|(split, (axis, region, ratio))| {
let bounds =
axis.split_line_bounds(*region, *ratio, f32::from(spacing));
if bounds.contains(cursor_position) {
- Some((*split, *axis))
+ Some((*split, *axis, bounds))
} else {
None
}
diff --git a/native/src/widget/pane_grid/configuration.rs b/native/src/widget/pane_grid/configuration.rs
index 1fed98b7..4c43826e 100644
--- a/native/src/widget/pane_grid/configuration.rs
+++ b/native/src/widget/pane_grid/configuration.rs
@@ -2,7 +2,7 @@ use crate::pane_grid::Axis;
/// The arrangement of a [`PaneGrid`].
///
-/// [`PaneGrid`]: struct.PaneGrid.html
+/// [`PaneGrid`]: crate::pane_grid::PaneGrid
#[derive(Debug, Clone)]
pub enum Configuration<T> {
/// A split of the available space.
@@ -13,18 +13,14 @@ pub enum Configuration<T> {
/// The ratio of the split in [0.0, 1.0].
ratio: f32,
- /// The left/top [`Content`] of the split.
- ///
- /// [`Configuration`]: enum.Node.html
+ /// The left/top [`Configuration`] of the split.
a: Box<Configuration<T>>,
- /// The right/bottom [`Content`] of the split.
- ///
- /// [`Configuration`]: enum.Node.html
+ /// The right/bottom [`Configuration`] of the split.
b: Box<Configuration<T>>,
},
/// A [`Pane`].
///
- /// [`Pane`]: struct.Pane.html
+ /// [`Pane`]: crate::pane_grid::Pane
Pane(T),
}
diff --git a/native/src/widget/pane_grid/content.rs b/native/src/widget/pane_grid/content.rs
index 1d339b75..bac9fdd4 100644
--- a/native/src/widget/pane_grid/content.rs
+++ b/native/src/widget/pane_grid/content.rs
@@ -1,17 +1,18 @@
use crate::container;
+use crate::event::{self, Event};
use crate::layout;
use crate::overlay;
use crate::pane_grid::{self, TitleBar};
-use crate::{Clipboard, Element, Event, Hasher, Layout, Point, Size};
+use crate::{Clipboard, Element, Hasher, Layout, Point, Rectangle, Size};
/// The content of a [`Pane`].
///
-/// [`Pane`]: struct.Pane.html
+/// [`Pane`]: crate::widget::pane_grid::Pane
#[allow(missing_debug_implementations)]
pub struct Content<'a, Message, Renderer: pane_grid::Renderer> {
title_bar: Option<TitleBar<'a, Message, Renderer>>,
body: Element<'a, Message, Renderer>,
- style: Renderer::Style,
+ style: <Renderer as container::Renderer>::Style,
}
impl<'a, Message, Renderer> Content<'a, Message, Renderer>
@@ -19,20 +20,15 @@ where
Renderer: pane_grid::Renderer,
{
/// Creates a new [`Content`] with the provided body.
- ///
- /// [`Content`]: struct.Content.html
pub fn new(body: impl Into<Element<'a, Message, Renderer>>) -> Self {
Self {
title_bar: None,
body: body.into(),
- style: Renderer::Style::default(),
+ style: Default::default(),
}
}
/// Sets the [`TitleBar`] of this [`Content`].
- ///
- /// [`TitleBar`]: struct.TitleBar.html
- /// [`Content`]: struct.Content.html
pub fn title_bar(
mut self,
title_bar: TitleBar<'a, Message, Renderer>,
@@ -41,10 +37,11 @@ where
self
}
- /// Sets the style of the [`TitleBar`].
- ///
- /// [`TitleBar`]: struct.TitleBar.html
- pub fn style(mut self, style: impl Into<Renderer::Style>) -> Self {
+ /// Sets the style of the [`Content`].
+ pub fn style(
+ mut self,
+ style: impl Into<<Renderer as container::Renderer>::Style>,
+ ) -> Self {
self.style = style.into();
self
}
@@ -56,15 +53,14 @@ where
{
/// Draws the [`Content`] with the provided [`Renderer`] and [`Layout`].
///
- /// [`Content`]: struct.Content.html
- /// [`Renderer`]: trait.Renderer.html
- /// [`Layout`]: ../layout/struct.Layout.html
+ /// [`Renderer`]: crate::widget::pane_grid::Renderer
pub fn draw(
&self,
renderer: &mut Renderer,
defaults: &Renderer::Defaults,
layout: Layout<'_>,
cursor_position: Point,
+ viewport: &Rectangle,
) -> Renderer::Output {
if let Some(title_bar) = &self.title_bar {
let mut children = layout.children();
@@ -78,6 +74,7 @@ where
Some((title_bar, title_bar_layout)),
(&self.body, body_layout),
cursor_position,
+ viewport,
)
} else {
renderer.draw_pane(
@@ -87,15 +84,13 @@ where
None,
(&self.body, layout),
cursor_position,
+ viewport,
)
}
}
/// Returns whether the [`Content`] with the given [`Layout`] can be picked
/// at the provided cursor position.
- ///
- /// [`Content`]: struct.Content.html
- /// [`Layout`]: ../layout/struct.Layout.html
pub fn can_be_picked_at(
&self,
layout: Layout<'_>,
@@ -151,20 +146,23 @@ where
event: Event,
layout: Layout<'_>,
cursor_position: Point,
- messages: &mut Vec<Message>,
renderer: &Renderer,
- clipboard: Option<&dyn Clipboard>,
- ) {
+ clipboard: &mut dyn Clipboard,
+ messages: &mut Vec<Message>,
+ is_picked: bool,
+ ) -> event::Status {
+ let mut event_status = event::Status::Ignored;
+
let body_layout = if let Some(title_bar) = &mut self.title_bar {
let mut children = layout.children();
- title_bar.on_event(
+ event_status = title_bar.on_event(
event.clone(),
children.next().unwrap(),
cursor_position,
- messages,
renderer,
clipboard,
+ messages,
);
children.next().unwrap()
@@ -172,14 +170,20 @@ where
layout
};
- self.body.on_event(
- event,
- body_layout,
- cursor_position,
- messages,
- renderer,
- clipboard,
- );
+ let body_status = if is_picked {
+ event::Status::Ignored
+ } else {
+ self.body.on_event(
+ event,
+ body_layout,
+ cursor_position,
+ renderer,
+ clipboard,
+ messages,
+ )
+ };
+
+ event_status.merge(body_status)
}
pub(crate) fn hash_layout(&self, state: &mut Hasher) {
@@ -194,18 +198,17 @@ where
&mut self,
layout: Layout<'_>,
) -> Option<overlay::Element<'_, Message, Renderer>> {
- let body_layout = if self.title_bar.is_some() {
+ if let Some(title_bar) = self.title_bar.as_mut() {
let mut children = layout.children();
+ let title_bar_layout = children.next()?;
- // Overlays only allowed in the pane body, for now at least.
- let _title_bar_layout = children.next();
-
- children.next()?
+ match title_bar.overlay(title_bar_layout) {
+ Some(overlay) => Some(overlay),
+ None => self.body.overlay(children.next()?),
+ }
} else {
- layout
- };
-
- self.body.overlay(body_layout)
+ self.body.overlay(layout)
+ }
}
}
diff --git a/native/src/widget/pane_grid/node.rs b/native/src/widget/pane_grid/node.rs
index cbfd8a43..84714e00 100644
--- a/native/src/widget/pane_grid/node.rs
+++ b/native/src/widget/pane_grid/node.rs
@@ -3,21 +3,16 @@ use crate::{
Rectangle, Size,
};
-use std::collections::HashMap;
+use std::collections::BTreeMap;
/// A layout node of a [`PaneGrid`].
///
-/// [`PaneGrid`]: struct.PaneGrid.html
+/// [`PaneGrid`]: crate::widget::PaneGrid
#[derive(Debug, Clone)]
pub enum Node {
/// The region of this [`Node`] is split into two.
- ///
- /// [`Node`]: enum.Node.html
Split {
/// The [`Split`] of this [`Node`].
- ///
- /// [`Split`]: struct.Split.html
- /// [`Node`]: enum.Node.html
id: Split,
/// The direction of the split.
@@ -27,26 +22,17 @@ pub enum Node {
ratio: f32,
/// The left/top [`Node`] of the split.
- ///
- /// [`Node`]: enum.Node.html
a: Box<Node>,
/// The right/bottom [`Node`] of the split.
- ///
- /// [`Node`]: enum.Node.html
b: Box<Node>,
},
/// The region of this [`Node`] is taken by a [`Pane`].
- ///
- /// [`Pane`]: struct.Pane.html
Pane(Pane),
}
impl Node {
/// Returns an iterator over each [`Split`] in this [`Node`].
- ///
- /// [`Split`]: struct.Split.html
- /// [`Node`]: enum.Node.html
pub fn splits(&self) -> impl Iterator<Item = &Split> {
let mut unvisited_nodes = vec![self];
@@ -69,15 +55,12 @@ impl Node {
/// Returns the rectangular region for each [`Pane`] in the [`Node`] given
/// the spacing between panes and the total available space.
- ///
- /// [`Pane`]: struct.Pane.html
- /// [`Node`]: enum.Node.html
pub fn pane_regions(
&self,
spacing: f32,
size: Size,
- ) -> HashMap<Pane, Rectangle> {
- let mut regions = HashMap::new();
+ ) -> BTreeMap<Pane, Rectangle> {
+ let mut regions = BTreeMap::new();
self.compute_regions(
spacing,
@@ -96,15 +79,12 @@ impl Node {
/// Returns the axis, rectangular region, and ratio for each [`Split`] in
/// the [`Node`] given the spacing between panes and the total available
/// space.
- ///
- /// [`Split`]: struct.Split.html
- /// [`Node`]: enum.Node.html
pub fn split_regions(
&self,
spacing: f32,
size: Size,
- ) -> HashMap<Split, (Axis, Rectangle, f32)> {
- let mut splits = HashMap::new();
+ ) -> BTreeMap<Split, (Axis, Rectangle, f32)> {
+ let mut splits = BTreeMap::new();
self.compute_splits(
spacing,
@@ -211,7 +191,7 @@ impl Node {
&self,
spacing: f32,
current: &Rectangle,
- regions: &mut HashMap<Pane, Rectangle>,
+ regions: &mut BTreeMap<Pane, Rectangle>,
) {
match self {
Node::Split {
@@ -232,7 +212,7 @@ impl Node {
&self,
spacing: f32,
current: &Rectangle,
- splits: &mut HashMap<Split, (Axis, Rectangle, f32)>,
+ splits: &mut BTreeMap<Split, (Axis, Rectangle, f32)>,
) {
match self {
Node::Split {
diff --git a/native/src/widget/pane_grid/pane.rs b/native/src/widget/pane_grid/pane.rs
index f9866407..d6fbab83 100644
--- a/native/src/widget/pane_grid/pane.rs
+++ b/native/src/widget/pane_grid/pane.rs
@@ -1,5 +1,5 @@
/// A rectangular region in a [`PaneGrid`] used to display widgets.
///
-/// [`PaneGrid`]: struct.PaneGrid.html
-#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
+/// [`PaneGrid`]: crate::widget::PaneGrid
+#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Pane(pub(super) usize);
diff --git a/native/src/widget/pane_grid/split.rs b/native/src/widget/pane_grid/split.rs
index d020c510..8132272a 100644
--- a/native/src/widget/pane_grid/split.rs
+++ b/native/src/widget/pane_grid/split.rs
@@ -1,5 +1,5 @@
/// A divider that splits a region in a [`PaneGrid`] into two different panes.
///
-/// [`PaneGrid`]: struct.PaneGrid.html
-#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
+/// [`PaneGrid`]: crate::widget::PaneGrid
+#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Split(pub(super) usize);
diff --git a/native/src/widget/pane_grid/state.rs b/native/src/widget/pane_grid/state.rs
index fb59c846..fb96f89f 100644
--- a/native/src/widget/pane_grid/state.rs
+++ b/native/src/widget/pane_grid/state.rs
@@ -3,7 +3,7 @@ use crate::{
Hasher, Point, Rectangle, Size,
};
-use std::collections::HashMap;
+use std::collections::{BTreeMap, HashMap};
/// The state of a [`PaneGrid`].
///
@@ -15,41 +15,19 @@ use std::collections::HashMap;
/// provided to the view function of [`PaneGrid::new`] for displaying each
/// [`Pane`].
///
-/// [`PaneGrid`]: struct.PaneGrid.html
-/// [`PaneGrid::new`]: struct.PaneGrid.html#method.new
-/// [`Pane`]: struct.Pane.html
-/// [`Split`]: struct.Split.html
-/// [`State`]: struct.State.html
+/// [`PaneGrid`]: crate::widget::PaneGrid
+/// [`PaneGrid::new`]: crate::widget::PaneGrid::new
#[derive(Debug, Clone)]
pub struct State<T> {
pub(super) panes: HashMap<Pane, T>,
pub(super) internal: Internal,
}
-/// The current focus of a [`Pane`].
-///
-/// [`Pane`]: struct.Pane.html
-#[derive(Debug, Clone, Copy, PartialEq, Eq)]
-pub enum Focus {
- /// The [`Pane`] is just focused.
- ///
- /// [`Pane`]: struct.Pane.html
- Idle,
-
- /// The [`Pane`] is being dragged.
- ///
- /// [`Pane`]: struct.Pane.html
- Dragging,
-}
-
impl<T> State<T> {
/// Creates a new [`State`], initializing the first pane with the provided
/// state.
///
/// Alongside the [`State`], it returns the first [`Pane`] identifier.
- ///
- /// [`State`]: struct.State.html
- /// [`Pane`]: struct.Pane.html
pub fn new(first_pane_state: T) -> (Self, Pane) {
(
Self::with_configuration(Configuration::Pane(first_pane_state)),
@@ -58,9 +36,6 @@ impl<T> State<T> {
}
/// Creates a new [`State`] with the given [`Configuration`].
- ///
- /// [`State`]: struct.State.html
- /// [`Configuration`]: enum.Configuration.html
pub fn with_configuration(config: impl Into<Configuration<T>>) -> Self {
let mut panes = HashMap::new();
@@ -72,95 +47,46 @@ impl<T> State<T> {
internal: Internal {
layout,
last_id,
- action: Action::Idle { focus: None },
+ action: Action::Idle,
},
}
}
/// Returns the total amount of panes in the [`State`].
- ///
- /// [`State`]: struct.State.html
pub fn len(&self) -> usize {
self.panes.len()
}
/// Returns the internal state of the given [`Pane`], if it exists.
- ///
- /// [`Pane`]: struct.Pane.html
pub fn get(&self, pane: &Pane) -> Option<&T> {
self.panes.get(pane)
}
/// Returns the internal state of the given [`Pane`] with mutability, if it
/// exists.
- ///
- /// [`Pane`]: struct.Pane.html
pub fn get_mut(&mut self, pane: &Pane) -> Option<&mut T> {
self.panes.get_mut(pane)
}
/// Returns an iterator over all the panes of the [`State`], alongside its
/// internal state.
- ///
- /// [`State`]: struct.State.html
pub fn iter(&self) -> impl Iterator<Item = (&Pane, &T)> {
self.panes.iter()
}
/// Returns a mutable iterator over all the panes of the [`State`],
/// alongside its internal state.
- ///
- /// [`State`]: struct.State.html
pub fn iter_mut(&mut self) -> impl Iterator<Item = (&Pane, &mut T)> {
self.panes.iter_mut()
}
/// Returns the layout of the [`State`].
- ///
- /// [`State`]: struct.State.html
pub fn layout(&self) -> &Node {
&self.internal.layout
}
- /// Returns the focused [`Pane`] of the [`State`], if there is one.
- ///
- /// [`Pane`]: struct.Pane.html
- /// [`State`]: struct.State.html
- pub fn focused(&self) -> Option<Pane> {
- self.internal.focused_pane()
- }
-
- /// Returns the active [`Pane`] of the [`State`], if there is one.
- ///
- /// A [`Pane`] is active if it is focused and is __not__ being dragged.
- ///
- /// [`Pane`]: struct.Pane.html
- /// [`State`]: struct.State.html
- pub fn active(&self) -> Option<Pane> {
- self.internal.active_pane()
- }
-
/// Returns the adjacent [`Pane`] of another [`Pane`] in the given
/// direction, if there is one.
- ///
- /// ## Example
- /// You can combine this with [`State::active`] to find the pane that is
- /// adjacent to the current active one, and then swap them. For instance:
- ///
- /// ```
- /// # use iced_native::pane_grid;
- /// #
- /// # let (mut state, _) = pane_grid::State::new(());
- /// #
- /// if let Some(active) = state.active() {
- /// if let Some(adjacent) = state.adjacent(&active, pane_grid::Direction::Right) {
- /// state.swap(&active, &adjacent);
- /// }
- /// }
- /// ```
- ///
- /// [`Pane`]: struct.Pane.html
- /// [`State::active`]: struct.State.html#method.active
pub fn adjacent(&self, pane: &Pane, direction: Direction) -> Option<Pane> {
let regions = self
.internal
@@ -194,25 +120,8 @@ impl<T> State<T> {
Some(*pane)
}
- /// Focuses the given [`Pane`].
- ///
- /// [`Pane`]: struct.Pane.html
- pub fn focus(&mut self, pane: &Pane) {
- self.internal.focus(pane);
- }
-
- /// Unfocuses the current focused [`Pane`].
- ///
- /// [`Pane`]: struct.Pane.html
- pub fn unfocus(&mut self) {
- self.internal.unfocus();
- }
-
/// Splits the given [`Pane`] into two in the given [`Axis`] and
/// initializing the new [`Pane`] with the provided internal state.
- ///
- /// [`Pane`]: struct.Pane.html
- /// [`Axis`]: enum.Axis.html
pub fn split(
&mut self,
axis: Axis,
@@ -236,7 +145,6 @@ impl<T> State<T> {
node.split(new_split, axis, new_pane);
let _ = self.panes.insert(new_pane, state);
- self.focus(&new_pane);
Some((new_pane, new_split))
}
@@ -246,9 +154,8 @@ impl<T> State<T> {
/// If you want to swap panes on drag and drop in your [`PaneGrid`], you
/// will need to call this method when handling a [`DragEvent`].
///
- /// [`State`]: struct.State.html
- /// [`PaneGrid`]: struct.PaneGrid.html
- /// [`DragEvent`]: struct.DragEvent.html
+ /// [`PaneGrid`]: crate::widget::PaneGrid
+ /// [`DragEvent`]: crate::widget::pane_grid::DragEvent
pub fn swap(&mut self, a: &Pane, b: &Pane) {
self.internal.layout.update(&|node| match node {
Node::Split { .. } => {}
@@ -270,20 +177,17 @@ impl<T> State<T> {
/// If you want to enable resize interactions in your [`PaneGrid`], you will
/// need to call this method when handling a [`ResizeEvent`].
///
- /// [`Split`]: struct.Split.html
- /// [`PaneGrid`]: struct.PaneGrid.html
- /// [`ResizeEvent`]: struct.ResizeEvent.html
+ /// [`PaneGrid`]: crate::widget::PaneGrid
+ /// [`ResizeEvent`]: crate::widget::pane_grid::ResizeEvent
pub fn resize(&mut self, split: &Split, ratio: f32) {
let _ = self.internal.layout.resize(split, ratio);
}
- /// Closes the given [`Pane`] and returns its internal state, if it exists.
- ///
- /// [`Pane`]: struct.Pane.html
- pub fn close(&mut self, pane: &Pane) -> Option<T> {
+ /// Closes the given [`Pane`] and returns its internal state and its closest
+ /// sibling, if it exists.
+ pub fn close(&mut self, pane: &Pane) -> Option<(T, Pane)> {
if let Some(sibling) = self.internal.layout.remove(pane) {
- self.focus(&sibling);
- self.panes.remove(pane)
+ self.panes.remove(pane).map(|state| (state, sibling))
} else {
None
}
@@ -329,52 +233,12 @@ pub struct Internal {
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum Action {
- Idle {
- focus: Option<Pane>,
- },
- Dragging {
- pane: Pane,
- origin: Point,
- focus: Option<Pane>,
- },
- Resizing {
- split: Split,
- axis: Axis,
- focus: Option<Pane>,
- },
-}
-
-impl Action {
- pub fn focus(&self) -> Option<(Pane, Focus)> {
- match self {
- Action::Idle { focus } | Action::Resizing { focus, .. } => {
- focus.map(|pane| (pane, Focus::Idle))
- }
- Action::Dragging { pane, .. } => Some((*pane, Focus::Dragging)),
- }
- }
+ Idle,
+ Dragging { pane: Pane, origin: Point },
+ Resizing { split: Split, axis: Axis },
}
impl Internal {
- pub fn action(&self) -> Action {
- self.action
- }
-
- pub fn focused_pane(&self) -> Option<Pane> {
- match self.action {
- Action::Idle { focus } => focus,
- Action::Dragging { focus, .. } => focus,
- Action::Resizing { focus, .. } => focus,
- }
- }
-
- pub fn active_pane(&self) -> Option<Pane> {
- match self.action {
- Action::Idle { focus } => focus,
- _ => None,
- }
- }
-
pub fn picked_pane(&self) -> Option<(Pane, Point)> {
match self.action {
Action::Dragging { pane, origin, .. } => Some((pane, origin)),
@@ -393,7 +257,7 @@ impl Internal {
&self,
spacing: f32,
size: Size,
- ) -> HashMap<Pane, Rectangle> {
+ ) -> BTreeMap<Pane, Rectangle> {
self.layout.pane_regions(spacing, size)
}
@@ -401,21 +265,14 @@ impl Internal {
&self,
spacing: f32,
size: Size,
- ) -> HashMap<Split, (Axis, Rectangle, f32)> {
+ ) -> BTreeMap<Split, (Axis, Rectangle, f32)> {
self.layout.split_regions(spacing, size)
}
- pub fn focus(&mut self, pane: &Pane) {
- self.action = Action::Idle { focus: Some(*pane) };
- }
-
pub fn pick_pane(&mut self, pane: &Pane, origin: Point) {
- let focus = self.focused_pane();
-
self.action = Action::Dragging {
pane: *pane,
origin,
- focus,
};
}
@@ -426,26 +283,14 @@ impl Internal {
return;
}
- let focus = self.action.focus().map(|(pane, _)| pane);
-
self.action = Action::Resizing {
split: *split,
axis,
- focus,
};
}
- pub fn drop_split(&mut self) {
- match self.action {
- Action::Resizing { focus, .. } => {
- self.action = Action::Idle { focus };
- }
- _ => {}
- }
- }
-
- pub fn unfocus(&mut self) {
- self.action = Action::Idle { focus: None };
+ pub fn idle(&mut self) {
+ self.action = Action::Idle;
}
pub fn hash_layout(&self, hasher: &mut Hasher) {
diff --git a/native/src/widget/pane_grid/title_bar.rs b/native/src/widget/pane_grid/title_bar.rs
index 9dfb9ae4..070010f8 100644
--- a/native/src/widget/pane_grid/title_bar.rs
+++ b/native/src/widget/pane_grid/title_bar.rs
@@ -1,51 +1,43 @@
+use crate::container;
+use crate::event::{self, Event};
use crate::layout;
+use crate::overlay;
use crate::pane_grid;
use crate::{
- Clipboard, Element, Event, Hasher, Layout, Point, Rectangle, Size,
+ Clipboard, Element, Hasher, Layout, Padding, Point, Rectangle, Size,
};
/// The title bar of a [`Pane`].
///
-/// [`Pane`]: struct.Pane.html
+/// [`Pane`]: crate::widget::pane_grid::Pane
#[allow(missing_debug_implementations)]
pub struct TitleBar<'a, Message, Renderer: pane_grid::Renderer> {
- title: String,
- title_size: Option<u16>,
+ content: Element<'a, Message, Renderer>,
controls: Option<Element<'a, Message, Renderer>>,
- padding: u16,
+ padding: Padding,
always_show_controls: bool,
- style: Renderer::Style,
+ style: <Renderer as container::Renderer>::Style,
}
impl<'a, Message, Renderer> TitleBar<'a, Message, Renderer>
where
Renderer: pane_grid::Renderer,
{
- /// Creates a new [`TitleBar`] with the given title.
- ///
- /// [`TitleBar`]: struct.TitleBar.html
- pub fn new(title: impl Into<String>) -> Self {
+ /// Creates a new [`TitleBar`] with the given content.
+ pub fn new<E>(content: E) -> Self
+ where
+ E: Into<Element<'a, Message, Renderer>>,
+ {
Self {
- title: title.into(),
- title_size: None,
+ content: content.into(),
controls: None,
- padding: 0,
+ padding: Padding::ZERO,
always_show_controls: false,
- style: Renderer::Style::default(),
+ style: Default::default(),
}
}
- /// Sets the size of the title of the [`TitleBar`].
- ///
- /// [`TitleBar`]: struct.TitleBar.html
- pub fn title_size(mut self, size: u16) -> Self {
- self.title_size = Some(size);
- self
- }
-
/// Sets the controls of the [`TitleBar`].
- ///
- /// [`TitleBar`]: struct.TitleBar.html
pub fn controls(
mut self,
controls: impl Into<Element<'a, Message, Renderer>>,
@@ -54,18 +46,17 @@ where
self
}
- /// Sets the padding of the [`TitleBar`].
- ///
- /// [`TitleBar`]: struct.TitleBar.html
- pub fn padding(mut self, units: u16) -> Self {
- self.padding = units;
+ /// Sets the [`Padding`] of the [`TitleBar`].
+ pub fn padding<P: Into<Padding>>(mut self, padding: P) -> Self {
+ self.padding = padding.into();
self
}
/// Sets the style of the [`TitleBar`].
- ///
- /// [`TitleBar`]: struct.TitleBar.html
- pub fn style(mut self, style: impl Into<Renderer::Style>) -> Self {
+ pub fn style(
+ mut self,
+ style: impl Into<<Renderer as container::Renderer>::Style>,
+ ) -> Self {
self.style = style.into();
self
}
@@ -76,9 +67,8 @@ where
/// By default, the controls are only visible when the [`Pane`] of this
/// [`TitleBar`] is hovered.
///
- /// [`TitleBar`]: struct.TitleBar.html
- /// [`controls`]: struct.TitleBar.html#method.controls
- /// [`Pane`]: struct.Pane.html
+ /// [`controls`]: Self::controls
+ /// [`Pane`]: crate::widget::pane_grid::Pane
pub fn always_show_controls(mut self) -> Self {
self.always_show_controls = true;
self
@@ -91,70 +81,49 @@ where
{
/// Draws the [`TitleBar`] with the provided [`Renderer`] and [`Layout`].
///
- /// [`TitleBar`]: struct.TitleBar.html
- /// [`Renderer`]: trait.Renderer.html
- /// [`Layout`]: ../layout/struct.Layout.html
+ /// [`Renderer`]: crate::widget::pane_grid::Renderer
pub fn draw(
&self,
renderer: &mut Renderer,
defaults: &Renderer::Defaults,
layout: Layout<'_>,
cursor_position: Point,
+ viewport: &Rectangle,
show_controls: bool,
) -> Renderer::Output {
let mut children = layout.children();
let padded = children.next().unwrap();
- if let Some(controls) = &self.controls {
- let mut children = padded.children();
- let title_layout = children.next().unwrap();
+ let mut children = padded.children();
+ let title_layout = children.next().unwrap();
+
+ let controls = if let Some(controls) = &self.controls {
let controls_layout = children.next().unwrap();
- let (title_bounds, controls) =
- if show_controls || self.always_show_controls {
- (title_layout.bounds(), Some((controls, controls_layout)))
- } else {
- (
- Rectangle {
- width: padded.bounds().width,
- ..title_layout.bounds()
- },
- None,
- )
- };
-
- renderer.draw_title_bar(
- defaults,
- layout.bounds(),
- &self.style,
- &self.title,
- self.title_size.unwrap_or(renderer.default_size()),
- Renderer::Font::default(),
- title_bounds,
- controls,
- cursor_position,
- )
+ if show_controls || self.always_show_controls {
+ Some((controls, controls_layout))
+ } else {
+ None
+ }
} else {
- renderer.draw_title_bar::<()>(
- defaults,
- layout.bounds(),
- &self.style,
- &self.title,
- self.title_size.unwrap_or(renderer.default_size()),
- Renderer::Font::default(),
- padded.bounds(),
- None,
- cursor_position,
- )
- }
+ None
+ };
+
+ renderer.draw_title_bar(
+ defaults,
+ layout.bounds(),
+ &self.style,
+ (&self.content, title_layout),
+ controls,
+ cursor_position,
+ viewport,
+ )
}
/// Returns whether the mouse cursor is over the pick area of the
/// [`TitleBar`] or not.
///
/// The whole [`TitleBar`] is a pick area, except its controls.
- ///
- /// [`TitleBar`]: struct.TitleBar.html
pub fn is_over_pick_area(
&self,
layout: Layout<'_>,
@@ -163,15 +132,16 @@ where
if layout.bounds().contains(cursor_position) {
let mut children = layout.children();
let padded = children.next().unwrap();
+ let mut children = padded.children();
+ let title_layout = children.next().unwrap();
if self.controls.is_some() {
- let mut children = padded.children();
- let _ = children.next().unwrap();
let controls_layout = children.next().unwrap();
!controls_layout.bounds().contains(cursor_position)
+ && !title_layout.bounds().contains(cursor_position)
} else {
- true
+ !title_layout.bounds().contains(cursor_position)
}
} else {
false
@@ -181,9 +151,12 @@ where
pub(crate) fn hash_layout(&self, hasher: &mut Hasher) {
use std::hash::Hash;
- self.title.hash(hasher);
- self.title_size.hash(hasher);
+ self.content.hash_layout(hasher);
self.padding.hash(hasher);
+
+ if let Some(controls) = &self.controls {
+ controls.hash_layout(hasher);
+ }
}
pub(crate) fn layout(
@@ -191,19 +164,13 @@ where
renderer: &Renderer,
limits: &layout::Limits,
) -> layout::Node {
- let padding = f32::from(self.padding);
- let limits = limits.pad(padding);
+ let limits = limits.pad(self.padding);
let max_size = limits.max();
- let title_size = self.title_size.unwrap_or(renderer.default_size());
- let title_font = Renderer::Font::default();
-
- let (title_width, title_height) = renderer.measure(
- &self.title,
- title_size,
- title_font,
- Size::new(f32::INFINITY, max_size.height),
- );
+ let title_layout = self
+ .content
+ .layout(renderer, &layout::Limits::new(Size::ZERO, max_size));
+ let title_size = title_layout.size();
let mut node = if let Some(controls) = &self.controls {
let mut controls_layout = controls
@@ -212,16 +179,8 @@ where
let controls_size = controls_layout.size();
let space_before_controls = max_size.width - controls_size.width;
- let mut title_layout = layout::Node::new(Size::new(
- title_width.min(space_before_controls),
- title_height,
- ));
-
- let title_size = title_layout.size();
let height = title_size.height.max(controls_size.height);
- title_layout
- .move_to(Point::new(0.0, (height - title_size.height) / 2.0));
controls_layout.move_to(Point::new(space_before_controls, 0.0));
layout::Node::with_children(
@@ -229,12 +188,18 @@ where
vec![title_layout, controls_layout],
)
} else {
- layout::Node::new(Size::new(max_size.width, title_height))
+ layout::Node::with_children(
+ Size::new(max_size.width, title_size.height),
+ vec![title_layout],
+ )
};
- node.move_to(Point::new(padding, padding));
+ node.move_to(Point::new(
+ self.padding.left.into(),
+ self.padding.top.into(),
+ ));
- layout::Node::with_children(node.size().pad(padding), vec![node])
+ layout::Node::with_children(node.size().pad(self.padding), vec![node])
}
pub(crate) fn on_event(
@@ -242,26 +207,63 @@ where
event: Event,
layout: Layout<'_>,
cursor_position: Point,
- messages: &mut Vec<Message>,
renderer: &Renderer,
- clipboard: Option<&dyn Clipboard>,
- ) {
- if let Some(controls) = &mut self.controls {
- let mut children = layout.children();
- let padded = children.next().unwrap();
+ clipboard: &mut dyn Clipboard,
+ messages: &mut Vec<Message>,
+ ) -> event::Status {
+ let mut children = layout.children();
+ let padded = children.next().unwrap();
- let mut children = padded.children();
- let _ = children.next();
+ let mut children = padded.children();
+ let title_layout = children.next().unwrap();
+
+ let control_status = if let Some(controls) = &mut self.controls {
let controls_layout = children.next().unwrap();
controls.on_event(
- event,
+ event.clone(),
controls_layout,
cursor_position,
- messages,
renderer,
clipboard,
- );
- }
+ messages,
+ )
+ } else {
+ event::Status::Ignored
+ };
+
+ let title_status = self.content.on_event(
+ event,
+ title_layout,
+ cursor_position,
+ renderer,
+ clipboard,
+ messages,
+ );
+
+ control_status.merge(title_status)
+ }
+
+ pub(crate) fn overlay(
+ &mut self,
+ layout: Layout<'_>,
+ ) -> Option<overlay::Element<'_, Message, Renderer>> {
+ let mut children = layout.children();
+ let padded = children.next()?;
+
+ let mut children = padded.children();
+ let title_layout = children.next()?;
+
+ let Self {
+ content, controls, ..
+ } = self;
+
+ content.overlay(title_layout).or_else(move || {
+ controls.as_mut().and_then(|controls| {
+ let controls_layout = children.next()?;
+
+ controls.overlay(controls_layout)
+ })
+ })
}
}
diff --git a/native/src/widget/pick_list.rs b/native/src/widget/pick_list.rs
index 04478225..d7792000 100644
--- a/native/src/widget/pick_list.rs
+++ b/native/src/widget/pick_list.rs
@@ -1,9 +1,16 @@
//! Display a dropdown list of selectable values.
+use crate::event::{self, Event};
+use crate::keyboard;
+use crate::layout;
+use crate::mouse;
+use crate::overlay;
+use crate::overlay::menu::{self, Menu};
+use crate::scrollable;
+use crate::text;
+use crate::touch;
use crate::{
- layout, mouse, overlay,
- overlay::menu::{self, Menu},
- scrollable, text, Clipboard, Element, Event, Hasher, Layout, Length, Point,
- Rectangle, Size, Widget,
+ Clipboard, Element, Hasher, Layout, Length, Padding, Point, Rectangle,
+ Size, Widget,
};
use std::borrow::Cow;
@@ -14,25 +21,26 @@ where
[T]: ToOwned<Owned = Vec<T>>,
{
menu: &'a mut menu::State,
+ keyboard_modifiers: &'a mut keyboard::Modifiers,
is_open: &'a mut bool,
hovered_option: &'a mut Option<usize>,
last_selection: &'a mut Option<T>,
on_selected: Box<dyn Fn(T) -> Message>,
options: Cow<'a, [T]>,
+ placeholder: Option<String>,
selected: Option<T>,
width: Length,
- padding: u16,
+ padding: Padding,
text_size: Option<u16>,
font: Renderer::Font,
style: <Renderer as self::Renderer>::Style,
}
/// The local state of a [`PickList`].
-///
-/// [`PickList`]: struct.PickList.html
#[derive(Debug, Clone)]
pub struct State<T> {
menu: menu::State,
+ keyboard_modifiers: keyboard::Modifiers,
is_open: bool,
hovered_option: Option<usize>,
last_selection: Option<T>,
@@ -42,6 +50,7 @@ impl<T> Default for State<T> {
fn default() -> Self {
Self {
menu: menu::State::default(),
+ keyboard_modifiers: keyboard::Modifiers::default(),
is_open: bool::default(),
hovered_option: Option::default(),
last_selection: Option::default(),
@@ -52,15 +61,12 @@ impl<T> Default for State<T> {
impl<'a, T: 'a, Message, Renderer: self::Renderer>
PickList<'a, T, Message, Renderer>
where
- T: ToString,
+ T: ToString + Eq,
[T]: ToOwned<Owned = Vec<T>>,
{
/// Creates a new [`PickList`] with the given [`State`], a list of options,
/// the current selected value, and the message to produce when an option is
/// selected.
- ///
- /// [`PickList`]: struct.PickList.html
- /// [`State`]: struct.State.html
pub fn new(
state: &'a mut State<T>,
options: impl Into<Cow<'a, [T]>>,
@@ -69,6 +75,7 @@ where
) -> Self {
let State {
menu,
+ keyboard_modifiers,
is_open,
hovered_option,
last_selection,
@@ -76,11 +83,13 @@ where
Self {
menu,
+ keyboard_modifiers,
is_open,
hovered_option,
last_selection,
on_selected: Box::new(on_selected),
options: options.into(),
+ placeholder: None,
selected,
width: Length::Shrink,
text_size: None,
@@ -90,41 +99,37 @@ where
}
}
+ /// Sets the placeholder of the [`PickList`].
+ pub fn placeholder(mut self, placeholder: impl Into<String>) -> Self {
+ self.placeholder = Some(placeholder.into());
+ self
+ }
+
/// Sets the width of the [`PickList`].
- ///
- /// [`PickList`]: struct.PickList.html
pub fn width(mut self, width: Length) -> Self {
self.width = width;
self
}
- /// Sets the padding of the [`PickList`].
- ///
- /// [`PickList`]: struct.PickList.html
- pub fn padding(mut self, padding: u16) -> Self {
- self.padding = padding;
+ /// Sets the [`Padding`] of the [`PickList`].
+ pub fn padding<P: Into<Padding>>(mut self, padding: P) -> Self {
+ self.padding = padding.into();
self
}
/// Sets the text size of the [`PickList`].
- ///
- /// [`PickList`]: struct.PickList.html
pub fn text_size(mut self, size: u16) -> Self {
self.text_size = Some(size);
self
}
/// Sets the font of the [`PickList`].
- ///
- /// [`PickList`]: struct.PickList.html
pub fn font(mut self, font: Renderer::Font) -> Self {
self.font = font;
self
}
/// Sets the style of the [`PickList`].
- ///
- /// [`PickList`]: struct.PickList.html
pub fn style(
mut self,
style: impl Into<<Renderer as self::Renderer>::Style>,
@@ -143,7 +148,7 @@ where
Renderer: self::Renderer + scrollable::Renderer + 'a,
{
fn width(&self) -> Length {
- Length::Shrink
+ self.width
}
fn height(&self) -> Length {
@@ -160,27 +165,37 @@ where
let limits = limits
.width(self.width)
.height(Length::Shrink)
- .pad(f32::from(self.padding));
+ .pad(self.padding);
let text_size = self.text_size.unwrap_or(renderer.default_size());
+ let font = self.font;
let max_width = match self.width {
Length::Shrink => {
+ let measure = |label: &str| -> u32 {
+ let (width, _) = renderer.measure(
+ label,
+ text_size,
+ font,
+ Size::new(f32::INFINITY, f32::INFINITY),
+ );
+
+ width.round() as u32
+ };
+
let labels = self.options.iter().map(ToString::to_string);
- labels
- .map(|label| {
- let (width, _) = renderer.measure(
- &label,
- text_size,
- Renderer::Font::default(),
- Size::new(f32::INFINITY, f32::INFINITY),
- );
-
- width.round() as u32
- })
- .max()
- .unwrap_or(100)
+ let labels_width =
+ labels.map(|label| measure(&label)).max().unwrap_or(100);
+
+ let placeholder_width = self
+ .placeholder
+ .as_ref()
+ .map(String::as_str)
+ .map(measure)
+ .unwrap_or(100);
+
+ labels_width.max(placeholder_width)
}
_ => 0,
};
@@ -189,11 +204,11 @@ where
let intrinsic = Size::new(
max_width as f32
+ f32::from(text_size)
- + f32::from(self.padding),
+ + f32::from(self.padding.left),
f32::from(text_size),
);
- limits.resolve(intrinsic).pad(f32::from(self.padding))
+ limits.resolve(intrinsic).pad(self.padding)
};
layout::Node::new(size)
@@ -204,6 +219,8 @@ where
match self.width {
Length::Shrink => {
+ self.placeholder.hash(state);
+
self.options
.iter()
.map(ToString::to_string)
@@ -220,16 +237,19 @@ where
event: Event,
layout: Layout<'_>,
cursor_position: Point,
- messages: &mut Vec<Message>,
_renderer: &Renderer,
- _clipboard: Option<&dyn Clipboard>,
- ) {
+ _clipboard: &mut dyn Clipboard,
+ messages: &mut Vec<Message>,
+ ) -> event::Status {
match event {
- Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) => {
- if *self.is_open {
+ Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left))
+ | Event::Touch(touch::Event::FingerPressed { .. }) => {
+ let event_status = if *self.is_open {
// TODO: Encode cursor availability in the type system
*self.is_open =
cursor_position.x < 0.0 || cursor_position.y < 0.0;
+
+ event::Status::Captured
} else if layout.bounds().contains(cursor_position) {
let selected = self.selected.as_ref();
@@ -238,15 +258,65 @@ where
.options
.iter()
.position(|option| Some(option) == selected);
- }
+
+ event::Status::Captured
+ } else {
+ event::Status::Ignored
+ };
if let Some(last_selection) = self.last_selection.take() {
messages.push((self.on_selected)(last_selection));
*self.is_open = false;
+
+ event::Status::Captured
+ } else {
+ event_status
+ }
+ }
+ Event::Mouse(mouse::Event::WheelScrolled {
+ delta: mouse::ScrollDelta::Lines { y, .. },
+ }) if self.keyboard_modifiers.command()
+ && layout.bounds().contains(cursor_position)
+ && !*self.is_open =>
+ {
+ fn find_next<'a, T: PartialEq>(
+ selected: &'a T,
+ mut options: impl Iterator<Item = &'a T>,
+ ) -> Option<&'a T> {
+ let _ = options.find(|&option| option == selected);
+
+ options.next()
}
+
+ let next_option = if y < 0.0 {
+ if let Some(selected) = self.selected.as_ref() {
+ find_next(selected, self.options.iter())
+ } else {
+ self.options.first()
+ }
+ } else if y > 0.0 {
+ if let Some(selected) = self.selected.as_ref() {
+ find_next(selected, self.options.iter().rev())
+ } else {
+ self.options.last()
+ }
+ } else {
+ None
+ };
+
+ if let Some(next_option) = next_option {
+ messages.push((self.on_selected)(next_option.clone()));
+ }
+
+ event::Status::Captured
+ }
+ Event::Keyboard(keyboard::Event::ModifiersChanged(modifiers)) => {
+ *self.keyboard_modifiers = modifiers;
+
+ event::Status::Ignored
}
- _ => {}
+ _ => event::Status::Ignored,
}
}
@@ -256,12 +326,14 @@ where
_defaults: &Renderer::Defaults,
layout: Layout<'_>,
cursor_position: Point,
+ _viewport: &Rectangle,
) -> Renderer::Output {
self::Renderer::draw(
renderer,
layout.bounds(),
cursor_position,
self.selected.as_ref().map(ToString::to_string),
+ self.placeholder.as_ref().map(String::as_str),
self.padding,
self.text_size.unwrap_or(renderer.default_size()),
self.font,
@@ -303,36 +375,27 @@ where
/// Your [renderer] will need to implement this trait before being
/// able to use a [`PickList`] in your user interface.
///
-/// [`PickList`]: struct.PickList.html
-/// [renderer]: ../../renderer/index.html
+/// [renderer]: crate::renderer
pub trait Renderer: text::Renderer + menu::Renderer {
/// The default padding of a [`PickList`].
- ///
- /// [`PickList`]: struct.PickList.html
- const DEFAULT_PADDING: u16;
+ const DEFAULT_PADDING: Padding;
/// The [`PickList`] style supported by this renderer.
- ///
- /// [`PickList`]: struct.PickList.html
type Style: Default;
/// Returns the style of the [`Menu`] of the [`PickList`].
- ///
- /// [`Menu`]: ../../overlay/menu/struct.Menu.html
- /// [`PickList`]: struct.PickList.html
fn menu_style(
style: &<Self as Renderer>::Style,
) -> <Self as menu::Renderer>::Style;
/// Draws a [`PickList`].
- ///
- /// [`PickList`]: struct.PickList.html
fn draw(
&mut self,
bounds: Rectangle,
cursor_position: Point,
selected: Option<String>,
- padding: u16,
+ placeholder: Option<&str>,
+ padding: Padding,
text_size: u16,
font: Self::Font,
style: &<Self as Renderer>::Style,
diff --git a/native/src/widget/progress_bar.rs b/native/src/widget/progress_bar.rs
index 5ab76d47..d294f198 100644
--- a/native/src/widget/progress_bar.rs
+++ b/native/src/widget/progress_bar.rs
@@ -33,8 +33,6 @@ impl<Renderer: self::Renderer> ProgressBar<Renderer> {
/// It expects:
/// * an inclusive range of possible values
/// * the current value of the [`ProgressBar`]
- ///
- /// [`ProgressBar`]: struct.ProgressBar.html
pub fn new(range: RangeInclusive<f32>, value: f32) -> Self {
ProgressBar {
value: value.max(*range.start()).min(*range.end()),
@@ -46,24 +44,18 @@ impl<Renderer: self::Renderer> ProgressBar<Renderer> {
}
/// Sets the width of the [`ProgressBar`].
- ///
- /// [`ProgressBar`]: struct.ProgressBar.html
pub fn width(mut self, width: Length) -> Self {
self.width = width;
self
}
/// Sets the height of the [`ProgressBar`].
- ///
- /// [`ProgressBar`]: struct.ProgressBar.html
pub fn height(mut self, height: Length) -> Self {
self.height = Some(height);
self
}
/// Sets the style of the [`ProgressBar`].
- ///
- /// [`ProgressBar`]: struct.ProgressBar.html
pub fn style(mut self, style: impl Into<Renderer::Style>) -> Self {
self.style = style.into();
self
@@ -104,6 +96,7 @@ where
_defaults: &Renderer::Defaults,
layout: Layout<'_>,
_cursor_position: Point,
+ _viewport: &Rectangle,
) -> Renderer::Output {
renderer.draw(
layout.bounds(),
@@ -127,15 +120,12 @@ where
/// Your [renderer] will need to implement this trait before being
/// able to use a [`ProgressBar`] in your user interface.
///
-/// [`ProgressBar`]: struct.ProgressBar.html
-/// [renderer]: ../../renderer/index.html
+/// [renderer]: crate::renderer
pub trait Renderer: crate::Renderer {
/// The style supported by this renderer.
type Style: Default;
/// The default height of a [`ProgressBar`].
- ///
- /// [`ProgressBar`]: struct.ProgressBar.html
const DEFAULT_HEIGHT: u16;
/// Draws a [`ProgressBar`].
@@ -146,8 +136,6 @@ pub trait Renderer: crate::Renderer {
/// * the current value of the [`ProgressBar`]
/// * maybe a specific background of the [`ProgressBar`]
/// * maybe a specific active color of the [`ProgressBar`]
- ///
- /// [`ProgressBar`]: struct.ProgressBar.html
fn draw(
&self,
bounds: Rectangle,
diff --git a/native/src/widget/radio.rs b/native/src/widget/radio.rs
index 5b8d00e9..dee82d1f 100644
--- a/native/src/widget/radio.rs
+++ b/native/src/widget/radio.rs
@@ -1,12 +1,17 @@
//! Create choices using radio buttons.
+use std::hash::Hash;
+
+use crate::event::{self, Event};
+use crate::mouse;
+use crate::row;
+use crate::text;
+use crate::touch;
+use crate::{layout, Color};
use crate::{
- layout, mouse, row, text, Align, Clipboard, Element, Event, Hasher,
- HorizontalAlignment, Layout, Length, Point, Rectangle, Row, Text,
- VerticalAlignment, Widget,
+ Align, Clipboard, Element, Hasher, HorizontalAlignment, Layout, Length,
+ Point, Rectangle, Row, Text, VerticalAlignment, Widget,
};
-use std::hash::Hash;
-
/// A circular button representing a choice.
///
/// # Example
@@ -42,11 +47,15 @@ pub struct Radio<Message, Renderer: self::Renderer + text::Renderer> {
size: u16,
spacing: u16,
text_size: Option<u16>,
+ text_color: Option<Color>,
+ font: Renderer::Font,
style: Renderer::Style,
}
impl<Message, Renderer: self::Renderer + text::Renderer>
Radio<Message, Renderer>
+where
+ Message: Clone,
{
/// Creates a new [`Radio`] button.
///
@@ -56,8 +65,6 @@ impl<Message, Renderer: self::Renderer + text::Renderer>
/// * the current selected value
/// * a function that will be called when the [`Radio`] is selected. It
/// receives the value of the radio and must produce a `Message`.
- ///
- /// [`Radio`]: struct.Radio.html
pub fn new<F, V>(
value: V,
label: impl Into<String>,
@@ -76,45 +83,49 @@ impl<Message, Renderer: self::Renderer + text::Renderer>
size: <Renderer as self::Renderer>::DEFAULT_SIZE,
spacing: Renderer::DEFAULT_SPACING, //15
text_size: None,
+ text_color: None,
+ font: Default::default(),
style: Renderer::Style::default(),
}
}
/// Sets the size of the [`Radio`] button.
- ///
- /// [`Radio`]: struct.Radio.html
pub fn size(mut self, size: u16) -> Self {
self.size = size;
self
}
/// Sets the width of the [`Radio`] button.
- ///
- /// [`Radio`]: struct.Radio.html
pub fn width(mut self, width: Length) -> Self {
self.width = width;
self
}
/// Sets the spacing between the [`Radio`] button and the text.
- ///
- /// [`Radio`]: struct.Radio.html
pub fn spacing(mut self, spacing: u16) -> Self {
self.spacing = spacing;
self
}
/// Sets the text size of the [`Radio`] button.
- ///
- /// [`Radio`]: struct.Radio.html
pub fn text_size(mut self, text_size: u16) -> Self {
self.text_size = Some(text_size);
self
}
+ /// Sets the text color of the [`Radio`] button.
+ pub fn text_color(mut self, color: Color) -> Self {
+ self.text_color = Some(color);
+ self
+ }
+
+ /// Sets the text font of the [`Radio`] button.
+ pub fn font(mut self, font: Renderer::Font) -> Self {
+ self.font = font;
+ self
+ }
+
/// Sets the style of the [`Radio`] button.
- ///
- /// [`Radio`]: struct.Radio.html
pub fn style(mut self, style: impl Into<Renderer::Style>) -> Self {
self.style = style.into();
self
@@ -123,8 +134,8 @@ impl<Message, Renderer: self::Renderer + text::Renderer>
impl<Message, Renderer> Widget<Message, Renderer> for Radio<Message, Renderer>
where
- Renderer: self::Renderer + text::Renderer + row::Renderer,
Message: Clone,
+ Renderer: self::Renderer + text::Renderer + row::Renderer,
{
fn width(&self) -> Length {
self.width
@@ -161,18 +172,23 @@ where
event: Event,
layout: Layout<'_>,
cursor_position: Point,
- messages: &mut Vec<Message>,
_renderer: &Renderer,
- _clipboard: Option<&dyn Clipboard>,
- ) {
+ _clipboard: &mut dyn Clipboard,
+ messages: &mut Vec<Message>,
+ ) -> event::Status {
match event {
- Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) => {
+ Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left))
+ | Event::Touch(touch::Event::FingerPressed { .. }) => {
if layout.bounds().contains(cursor_position) {
messages.push(self.on_click.clone());
+
+ return event::Status::Captured;
}
}
_ => {}
}
+
+ event::Status::Ignored
}
fn draw(
@@ -181,6 +197,7 @@ where
defaults: &Renderer::Defaults,
layout: Layout<'_>,
cursor_position: Point,
+ _viewport: &Rectangle,
) -> Renderer::Output {
let bounds = layout.bounds();
let mut children = layout.children();
@@ -195,8 +212,8 @@ where
label_layout.bounds(),
&self.label,
self.text_size.unwrap_or(renderer.default_size()),
- Default::default(),
- None,
+ self.font,
+ self.text_color,
HorizontalAlignment::Left,
VerticalAlignment::Center,
);
@@ -226,20 +243,15 @@ where
/// Your [renderer] will need to implement this trait before being
/// able to use a [`Radio`] button in your user interface.
///
-/// [`Radio`]: struct.Radio.html
-/// [renderer]: ../../renderer/index.html
+/// [renderer]: crate::renderer
pub trait Renderer: crate::Renderer {
/// The style supported by this renderer.
type Style: Default;
/// The default size of a [`Radio`] button.
- ///
- /// [`Radio`]: struct.Radio.html
const DEFAULT_SIZE: u16;
/// The default spacing of a [`Radio`] button.
- ///
- /// [`Radio`]: struct.Radio.html
const DEFAULT_SPACING: u16;
/// Draws a [`Radio`] button.
@@ -249,8 +261,6 @@ pub trait Renderer: crate::Renderer {
/// * whether the [`Radio`] is selected or not
/// * whether the mouse is over the [`Radio`] or not
/// * the drawn label of the [`Radio`]
- ///
- /// [`Radio`]: struct.Radio.html
fn draw(
&mut self,
bounds: Rectangle,
@@ -264,8 +274,8 @@ pub trait Renderer: crate::Renderer {
impl<'a, Message, Renderer> From<Radio<Message, Renderer>>
for Element<'a, Message, Renderer>
where
- Renderer: 'a + self::Renderer + row::Renderer + text::Renderer,
Message: 'a + Clone,
+ Renderer: 'a + self::Renderer + row::Renderer + text::Renderer,
{
fn from(radio: Radio<Message, Renderer>) -> Element<'a, Message, Renderer> {
Element::new(radio)
diff --git a/native/src/widget/row.rs b/native/src/widget/row.rs
index 2b6db224..9ebc9145 100644
--- a/native/src/widget/row.rs
+++ b/native/src/widget/row.rs
@@ -1,22 +1,20 @@
//! Distribute content horizontally.
-use std::hash::Hash;
-
+use crate::event::{self, Event};
+use crate::layout;
+use crate::overlay;
use crate::{
- layout, overlay, Align, Clipboard, Element, Event, Hasher, Layout, Length,
- Point, Widget,
+ Align, Clipboard, Element, Hasher, Layout, Length, Padding, Point,
+ Rectangle, Widget,
};
+use std::hash::Hash;
use std::u32;
/// A container that distributes its contents horizontally.
-///
-/// A [`Row`] will try to fill the horizontal space of its container.
-///
-/// [`Row`]: struct.Row.html
#[allow(missing_debug_implementations)]
pub struct Row<'a, Message, Renderer> {
spacing: u16,
- padding: u16,
+ padding: Padding,
width: Length,
height: Length,
max_width: u32,
@@ -27,21 +25,17 @@ pub struct Row<'a, Message, Renderer> {
impl<'a, Message, Renderer> Row<'a, Message, Renderer> {
/// Creates an empty [`Row`].
- ///
- /// [`Row`]: struct.Row.html
pub fn new() -> Self {
Self::with_children(Vec::new())
}
/// Creates a [`Row`] with the given elements.
- ///
- /// [`Row`]: struct.Row.html
pub fn with_children(
children: Vec<Element<'a, Message, Renderer>>,
) -> Self {
Row {
spacing: 0,
- padding: 0,
+ padding: Padding::ZERO,
width: Length::Shrink,
height: Length::Shrink,
max_width: u32::MAX,
@@ -61,58 +55,43 @@ impl<'a, Message, Renderer> Row<'a, Message, Renderer> {
self
}
- /// Sets the padding of the [`Row`].
- ///
- /// [`Row`]: struct.Row.html
- pub fn padding(mut self, units: u16) -> Self {
- self.padding = units;
+ /// Sets the [`Padding`] of the [`Row`].
+ pub fn padding<P: Into<Padding>>(mut self, padding: P) -> Self {
+ self.padding = padding.into();
self
}
/// Sets the width of the [`Row`].
- ///
- /// [`Row`]: struct.Row.html
pub fn width(mut self, width: Length) -> Self {
self.width = width;
self
}
/// Sets the height of the [`Row`].
- ///
- /// [`Row`]: struct.Row.html
pub fn height(mut self, height: Length) -> Self {
self.height = height;
self
}
/// Sets the maximum width of the [`Row`].
- ///
- /// [`Row`]: struct.Row.html
pub fn max_width(mut self, max_width: u32) -> Self {
self.max_width = max_width;
self
}
/// Sets the maximum height of the [`Row`].
- ///
- /// [`Row`]: struct.Row.html
pub fn max_height(mut self, max_height: u32) -> Self {
self.max_height = max_height;
self
}
/// Sets the vertical alignment of the contents of the [`Row`] .
- ///
- /// [`Row`]: struct.Row.html
pub fn align_items(mut self, align: Align) -> Self {
self.align_items = align;
self
}
/// Adds an [`Element`] to the [`Row`].
- ///
- /// [`Element`]: ../struct.Element.html
- /// [`Row`]: struct.Row.html
pub fn push<E>(mut self, child: E) -> Self
where
E: Into<Element<'a, Message, Renderer>>,
@@ -150,7 +129,7 @@ where
layout::flex::Axis::Horizontal,
renderer,
&limits,
- self.padding as f32,
+ self.padding,
self.spacing as f32,
self.align_items,
&self.children,
@@ -162,22 +141,24 @@ where
event: Event,
layout: Layout<'_>,
cursor_position: Point,
- messages: &mut Vec<Message>,
renderer: &Renderer,
- clipboard: Option<&dyn Clipboard>,
- ) {
- self.children.iter_mut().zip(layout.children()).for_each(
- |(child, layout)| {
+ clipboard: &mut dyn Clipboard,
+ messages: &mut Vec<Message>,
+ ) -> event::Status {
+ self.children
+ .iter_mut()
+ .zip(layout.children())
+ .map(|(child, layout)| {
child.widget.on_event(
event.clone(),
layout,
cursor_position,
- messages,
renderer,
clipboard,
+ messages,
)
- },
- );
+ })
+ .fold(event::Status::Ignored, event::Status::merge)
}
fn draw(
@@ -186,8 +167,15 @@ where
defaults: &Renderer::Defaults,
layout: Layout<'_>,
cursor_position: Point,
+ viewport: &Rectangle,
) -> Renderer::Output {
- renderer.draw(defaults, &self.children, layout, cursor_position)
+ renderer.draw(
+ defaults,
+ &self.children,
+ layout,
+ cursor_position,
+ viewport,
+ )
}
fn hash_layout(&self, state: &mut Hasher) {
@@ -200,7 +188,7 @@ where
self.max_height.hash(state);
self.align_items.hash(state);
self.spacing.hash(state);
- self.spacing.hash(state);
+ self.padding.hash(state);
for child in &self.children {
child.widget.hash_layout(state);
@@ -224,8 +212,7 @@ where
/// Your [renderer] will need to implement this trait before being
/// able to use a [`Row`] in your user interface.
///
-/// [`Row`]: struct.Row.html
-/// [renderer]: ../../renderer/index.html
+/// [renderer]: crate::renderer
pub trait Renderer: crate::Renderer + Sized {
/// Draws a [`Row`].
///
@@ -233,15 +220,13 @@ pub trait Renderer: crate::Renderer + Sized {
/// - the children of the [`Row`]
/// - the [`Layout`] of the [`Row`] and its children
/// - the cursor position
- ///
- /// [`Row`]: struct.Row.html
- /// [`Layout`]: ../layout/struct.Layout.html
fn draw<Message>(
&mut self,
defaults: &Self::Defaults,
children: &[Element<'_, Message, Self>],
layout: Layout<'_>,
cursor_position: Point,
+ viewport: &Rectangle,
) -> Self::Output;
}
diff --git a/native/src/widget/rule.rs b/native/src/widget/rule.rs
index 25cec53b..18c88658 100644
--- a/native/src/widget/rule.rs
+++ b/native/src/widget/rule.rs
@@ -17,8 +17,6 @@ pub struct Rule<Renderer: self::Renderer> {
impl<Renderer: self::Renderer> Rule<Renderer> {
/// Creates a horizontal [`Rule`] for dividing content by the given vertical spacing.
- ///
- /// [`Rule`]: struct.Rule.html
pub fn horizontal(spacing: u16) -> Self {
Rule {
width: Length::Fill,
@@ -29,8 +27,6 @@ impl<Renderer: self::Renderer> Rule<Renderer> {
}
/// Creates a vertical [`Rule`] for dividing content by the given horizontal spacing.
- ///
- /// [`Rule`]: struct.Rule.html
pub fn vertical(spacing: u16) -> Self {
Rule {
width: Length::from(Length::Units(spacing)),
@@ -41,8 +37,6 @@ impl<Renderer: self::Renderer> Rule<Renderer> {
}
/// Sets the style of the [`Rule`].
- ///
- /// [`Rule`]: struct.Rule.html
pub fn style(mut self, style: impl Into<Renderer::Style>) -> Self {
self.style = style.into();
self
@@ -77,6 +71,7 @@ where
_defaults: &Renderer::Defaults,
layout: Layout<'_>,
_cursor_position: Point,
+ _viewport: &Rectangle,
) -> Renderer::Output {
renderer.draw(layout.bounds(), &self.style, self.is_horizontal)
}
@@ -91,8 +86,6 @@ where
}
/// The renderer of a [`Rule`].
-///
-/// [`Rule`]: struct.Rule.html
pub trait Renderer: crate::Renderer {
/// The style supported by this renderer.
type Style: Default;
@@ -103,8 +96,6 @@ pub trait Renderer: crate::Renderer {
/// * the bounds of the [`Rule`]
/// * the style of the [`Rule`]
/// * whether the [`Rule`] is horizontal (true) or vertical (false)
- ///
- /// [`Rule`]: struct.Rule.html
fn draw(
&mut self,
bounds: Rectangle,
diff --git a/native/src/widget/scrollable.rs b/native/src/widget/scrollable.rs
index 75e97027..68da2e67 100644
--- a/native/src/widget/scrollable.rs
+++ b/native/src/widget/scrollable.rs
@@ -1,7 +1,13 @@
//! Navigate an endless amount of content with a scrollbar.
+use crate::column;
+use crate::event::{self, Event};
+use crate::layout;
+use crate::mouse;
+use crate::overlay;
+use crate::touch;
use crate::{
- column, layout, mouse, overlay, Align, Clipboard, Column, Element, Event,
- Hasher, Layout, Length, Point, Rectangle, Size, Vector, Widget,
+ Align, Clipboard, Column, Element, Hasher, Layout, Length, Padding, Point,
+ Rectangle, Size, Vector, Widget,
};
use std::{f32, hash::Hash, u32};
@@ -13,21 +19,26 @@ pub struct Scrollable<'a, Message, Renderer: self::Renderer> {
state: &'a mut State,
height: Length,
max_height: u32,
+ scrollbar_width: u16,
+ scrollbar_margin: u16,
+ scroller_width: u16,
content: Column<'a, Message, Renderer>,
+ on_scroll: Option<Box<dyn Fn(f32) -> Message>>,
style: Renderer::Style,
}
impl<'a, Message, Renderer: self::Renderer> Scrollable<'a, Message, Renderer> {
/// Creates a new [`Scrollable`] with the given [`State`].
- ///
- /// [`Scrollable`]: struct.Scrollable.html
- /// [`State`]: struct.State.html
pub fn new(state: &'a mut State) -> Self {
Scrollable {
state,
height: Length::Shrink,
max_height: u32::MAX,
+ scrollbar_width: 10,
+ scrollbar_margin: 0,
+ scroller_width: 10,
content: Column::new(),
+ on_scroll: None,
style: Renderer::Style::default(),
}
}
@@ -42,65 +53,79 @@ impl<'a, Message, Renderer: self::Renderer> Scrollable<'a, Message, Renderer> {
self
}
- /// Sets the padding of the [`Scrollable`].
- ///
- /// [`Scrollable`]: struct.Scrollable.html
- pub fn padding(mut self, units: u16) -> Self {
- self.content = self.content.padding(units);
+ /// Sets the [`Padding`] of the [`Scrollable`].
+ pub fn padding<P: Into<Padding>>(mut self, padding: P) -> Self {
+ self.content = self.content.padding(padding);
self
}
/// Sets the width of the [`Scrollable`].
- ///
- /// [`Scrollable`]: struct.Scrollable.html
pub fn width(mut self, width: Length) -> Self {
self.content = self.content.width(width);
self
}
/// Sets the height of the [`Scrollable`].
- ///
- /// [`Scrollable`]: struct.Scrollable.html
pub fn height(mut self, height: Length) -> Self {
self.height = height;
self
}
/// Sets the maximum width of the [`Scrollable`].
- ///
- /// [`Scrollable`]: struct.Scrollable.html
pub fn max_width(mut self, max_width: u32) -> Self {
self.content = self.content.max_width(max_width);
self
}
/// Sets the maximum height of the [`Scrollable`] in pixels.
- ///
- /// [`Scrollable`]: struct.Scrollable.html
pub fn max_height(mut self, max_height: u32) -> Self {
self.max_height = max_height;
self
}
/// Sets the horizontal alignment of the contents of the [`Scrollable`] .
- ///
- /// [`Scrollable`]: struct.Scrollable.html
pub fn align_items(mut self, align_items: Align) -> Self {
self.content = self.content.align_items(align_items);
self
}
- /// Sets the style of the [`Scrollable`] .
+ /// Sets the scrollbar width of the [`Scrollable`] .
+ /// Silently enforces a minimum value of 1.
+ pub fn scrollbar_width(mut self, scrollbar_width: u16) -> Self {
+ self.scrollbar_width = scrollbar_width.max(1);
+ self
+ }
+
+ /// Sets the scrollbar margin of the [`Scrollable`] .
+ pub fn scrollbar_margin(mut self, scrollbar_margin: u16) -> Self {
+ self.scrollbar_margin = scrollbar_margin;
+ self
+ }
+
+ /// Sets the scroller width of the [`Scrollable`] .
///
- /// [`Scrollable`]: struct.Scrollable.html
+ /// It silently enforces a minimum value of 1.
+ pub fn scroller_width(mut self, scroller_width: u16) -> Self {
+ self.scroller_width = scroller_width.max(1);
+ self
+ }
+
+ /// Sets a function to call when the [`Scrollable`] is scrolled.
+ ///
+ /// The function takes the new relative offset of the [`Scrollable`]
+ /// (e.g. `0` means top, while `1` means bottom).
+ pub fn on_scroll(mut self, f: impl Fn(f32) -> Message + 'static) -> Self {
+ self.on_scroll = Some(Box::new(f));
+ self
+ }
+
+ /// Sets the style of the [`Scrollable`] .
pub fn style(mut self, style: impl Into<Renderer::Style>) -> Self {
self.style = style.into();
self
}
/// Adds an element to the [`Scrollable`].
- ///
- /// [`Scrollable`]: struct.Scrollable.html
pub fn push<E>(mut self, child: E) -> Self
where
E: Into<Element<'a, Message, Renderer>>,
@@ -108,6 +133,24 @@ impl<'a, Message, Renderer: self::Renderer> Scrollable<'a, Message, Renderer> {
self.content = self.content.push(child);
self
}
+
+ fn notify_on_scroll(
+ &self,
+ bounds: Rectangle,
+ content_bounds: Rectangle,
+ messages: &mut Vec<Message>,
+ ) {
+ if content_bounds.height <= bounds.height {
+ return;
+ }
+
+ if let Some(on_scroll) = &self.on_scroll {
+ messages.push(on_scroll(
+ self.state.offset.absolute(bounds, content_bounds)
+ / (content_bounds.height - bounds.height),
+ ));
+ }
+ }
}
impl<'a, Message, Renderer> Widget<Message, Renderer>
@@ -149,17 +192,59 @@ where
event: Event,
layout: Layout<'_>,
cursor_position: Point,
- messages: &mut Vec<Message>,
renderer: &Renderer,
- clipboard: Option<&dyn Clipboard>,
- ) {
+ clipboard: &mut dyn Clipboard,
+ messages: &mut Vec<Message>,
+ ) -> event::Status {
let bounds = layout.bounds();
let is_mouse_over = bounds.contains(cursor_position);
let content = layout.children().next().unwrap();
let content_bounds = content.bounds();
- // TODO: Event capture. Nested scrollables should capture scroll events.
+ let offset = self.state.offset(bounds, content_bounds);
+ let scrollbar = renderer.scrollbar(
+ bounds,
+ content_bounds,
+ offset,
+ self.scrollbar_width,
+ self.scrollbar_margin,
+ self.scroller_width,
+ );
+ let is_mouse_over_scrollbar = scrollbar
+ .as_ref()
+ .map(|scrollbar| scrollbar.is_mouse_over(cursor_position))
+ .unwrap_or(false);
+
+ let event_status = {
+ let cursor_position = if is_mouse_over && !is_mouse_over_scrollbar {
+ Point::new(
+ cursor_position.x,
+ cursor_position.y
+ + self.state.offset(bounds, content_bounds) as f32,
+ )
+ } else {
+ // TODO: Make `cursor_position` an `Option<Point>` so we can encode
+ // cursor availability.
+ // This will probably happen naturally once we add multi-window
+ // support.
+ Point::new(cursor_position.x, -1.0)
+ };
+
+ self.content.on_event(
+ event.clone(),
+ content,
+ cursor_position,
+ renderer,
+ clipboard,
+ messages,
+ )
+ };
+
+ if let event::Status::Captured = event_status {
+ return event::Status::Captured;
+ }
+
if is_mouse_over {
match event {
Event::Mouse(mouse::Event::WheelScrolled { delta }) => {
@@ -172,26 +257,65 @@ where
self.state.scroll(y, bounds, content_bounds);
}
}
+
+ self.notify_on_scroll(bounds, content_bounds, messages);
+
+ return event::Status::Captured;
+ }
+ Event::Touch(event) => {
+ match event {
+ touch::Event::FingerPressed { .. } => {
+ self.state.scroll_box_touched_at =
+ Some(cursor_position);
+ }
+ touch::Event::FingerMoved { .. } => {
+ if let Some(scroll_box_touched_at) =
+ self.state.scroll_box_touched_at
+ {
+ let delta =
+ cursor_position.y - scroll_box_touched_at.y;
+
+ self.state.scroll(
+ delta,
+ bounds,
+ content_bounds,
+ );
+
+ self.state.scroll_box_touched_at =
+ Some(cursor_position);
+
+ self.notify_on_scroll(
+ bounds,
+ content_bounds,
+ messages,
+ );
+ }
+ }
+ touch::Event::FingerLifted { .. }
+ | touch::Event::FingerLost { .. } => {
+ self.state.scroll_box_touched_at = None;
+ }
+ }
+
+ return event::Status::Captured;
}
_ => {}
}
}
- let offset = self.state.offset(bounds, content_bounds);
- let scrollbar = renderer.scrollbar(bounds, content_bounds, offset);
- let is_mouse_over_scrollbar = scrollbar
- .as_ref()
- .map(|scrollbar| scrollbar.is_mouse_over(cursor_position))
- .unwrap_or(false);
-
if self.state.is_scroller_grabbed() {
match event {
Event::Mouse(mouse::Event::ButtonReleased(
mouse::Button::Left,
- )) => {
+ ))
+ | Event::Touch(touch::Event::FingerLifted { .. })
+ | Event::Touch(touch::Event::FingerLost { .. }) => {
self.state.scroller_grabbed_at = None;
+
+ return event::Status::Captured;
}
- Event::Mouse(mouse::Event::CursorMoved { .. }) => {
+ Event::Mouse(mouse::Event::CursorMoved { .. })
+ | Event::Touch(touch::Event::FingerMoved { .. }) => {
if let (Some(scrollbar), Some(scroller_grabbed_at)) =
(scrollbar, self.state.scroller_grabbed_at)
{
@@ -203,6 +327,10 @@ where
bounds,
content_bounds,
);
+
+ self.notify_on_scroll(bounds, content_bounds, messages);
+
+ return event::Status::Captured;
}
}
_ => {}
@@ -211,7 +339,8 @@ where
match event {
Event::Mouse(mouse::Event::ButtonPressed(
mouse::Button::Left,
- )) => {
+ ))
+ | Event::Touch(touch::Event::FingerPressed { .. }) => {
if let Some(scrollbar) = scrollbar {
if let Some(scroller_grabbed_at) =
scrollbar.grab_scroller(cursor_position)
@@ -227,6 +356,14 @@ where
self.state.scroller_grabbed_at =
Some(scroller_grabbed_at);
+
+ self.notify_on_scroll(
+ bounds,
+ content_bounds,
+ messages,
+ );
+
+ return event::Status::Captured;
}
}
}
@@ -234,28 +371,7 @@ where
}
}
- let cursor_position = if is_mouse_over && !is_mouse_over_scrollbar {
- Point::new(
- cursor_position.x,
- cursor_position.y
- + self.state.offset(bounds, content_bounds) as f32,
- )
- } else {
- // TODO: Make `cursor_position` an `Option<Point>` so we can encode
- // cursor availability.
- // This will probably happen naturally once we add multi-window
- // support.
- Point::new(cursor_position.x, -1.0)
- };
-
- self.content.on_event(
- event,
- content,
- cursor_position,
- messages,
- renderer,
- clipboard,
- )
+ event::Status::Ignored
}
fn draw(
@@ -264,12 +380,20 @@ where
defaults: &Renderer::Defaults,
layout: Layout<'_>,
cursor_position: Point,
+ _viewport: &Rectangle,
) -> Renderer::Output {
let bounds = layout.bounds();
let content_layout = layout.children().next().unwrap();
let content_bounds = content_layout.bounds();
let offset = self.state.offset(bounds, content_bounds);
- let scrollbar = renderer.scrollbar(bounds, content_bounds, offset);
+ let scrollbar = renderer.scrollbar(
+ bounds,
+ content_bounds,
+ offset,
+ self.scrollbar_width,
+ self.scrollbar_margin,
+ self.scroller_width,
+ );
let is_mouse_over = bounds.contains(cursor_position);
let is_mouse_over_scrollbar = scrollbar
@@ -289,6 +413,10 @@ where
defaults,
content_layout,
cursor_position,
+ &Rectangle {
+ y: bounds.y + offset as f32,
+ ..bounds
+ },
)
};
@@ -336,27 +464,54 @@ where
}
/// The local state of a [`Scrollable`].
-///
-/// [`Scrollable`]: struct.Scrollable.html
-#[derive(Debug, Clone, Copy, Default)]
+#[derive(Debug, Clone, Copy)]
pub struct State {
scroller_grabbed_at: Option<f32>,
- offset: f32,
+ scroll_box_touched_at: Option<Point>,
+ offset: Offset,
+}
+
+impl Default for State {
+ fn default() -> Self {
+ Self {
+ scroller_grabbed_at: None,
+ scroll_box_touched_at: None,
+ offset: Offset::Absolute(0.0),
+ }
+ }
+}
+
+/// The local state of a [`Scrollable`].
+#[derive(Debug, Clone, Copy)]
+enum Offset {
+ Absolute(f32),
+ Relative(f32),
+}
+
+impl Offset {
+ fn absolute(self, bounds: Rectangle, content_bounds: Rectangle) -> f32 {
+ match self {
+ Self::Absolute(absolute) => {
+ let hidden_content =
+ (content_bounds.height - bounds.height).max(0.0);
+
+ absolute.min(hidden_content)
+ }
+ Self::Relative(percentage) => {
+ ((content_bounds.height - bounds.height) * percentage).max(0.0)
+ }
+ }
+ }
}
impl State {
/// Creates a new [`State`] with the scrollbar located at the top.
- ///
- /// [`State`]: struct.State.html
pub fn new() -> Self {
State::default()
}
/// Apply a scrolling offset to the current [`State`], given the bounds of
/// the [`Scrollable`] and its contents.
- ///
- /// [`Scrollable`]: struct.Scrollable.html
- /// [`State`]: struct.State.html
pub fn scroll(
&mut self,
delta_y: f32,
@@ -367,70 +522,83 @@ impl State {
return;
}
- self.offset = (self.offset - delta_y)
- .max(0.0)
- .min((content_bounds.height - bounds.height) as f32);
+ self.offset = Offset::Absolute(
+ (self.offset.absolute(bounds, content_bounds) - delta_y)
+ .max(0.0)
+ .min((content_bounds.height - bounds.height) as f32),
+ );
}
- /// Moves the scroll position to a relative amount, given the bounds of
- /// the [`Scrollable`] and its contents.
+ /// Scrolls the [`Scrollable`] to a relative amount.
///
/// `0` represents scrollbar at the top, while `1` represents scrollbar at
/// the bottom.
- ///
- /// [`Scrollable`]: struct.Scrollable.html
- /// [`State`]: struct.State.html
pub fn scroll_to(
&mut self,
percentage: f32,
bounds: Rectangle,
content_bounds: Rectangle,
) {
+ self.snap_to(percentage);
+ self.unsnap(bounds, content_bounds);
+ }
+
+ /// Snaps the scroll position to a relative amount.
+ ///
+ /// `0` represents scrollbar at the top, while `1` represents scrollbar at
+ /// the bottom.
+ pub fn snap_to(&mut self, percentage: f32) {
+ self.offset = Offset::Relative(percentage.max(0.0).min(1.0));
+ }
+
+ /// Unsnaps the current scroll position, if snapped, given the bounds of the
+ /// [`Scrollable`] and its contents.
+ pub fn unsnap(&mut self, bounds: Rectangle, content_bounds: Rectangle) {
self.offset =
- ((content_bounds.height - bounds.height) * percentage).max(0.0);
+ Offset::Absolute(self.offset.absolute(bounds, content_bounds));
}
/// Returns the current scrolling offset of the [`State`], given the bounds
/// of the [`Scrollable`] and its contents.
- ///
- /// [`Scrollable`]: struct.Scrollable.html
- /// [`State`]: struct.State.html
pub fn offset(&self, bounds: Rectangle, content_bounds: Rectangle) -> u32 {
- let hidden_content =
- (content_bounds.height - bounds.height).max(0.0).round() as u32;
-
- self.offset.min(hidden_content as f32) as u32
+ self.offset.absolute(bounds, content_bounds) as u32
}
/// Returns whether the scroller is currently grabbed or not.
pub fn is_scroller_grabbed(&self) -> bool {
self.scroller_grabbed_at.is_some()
}
+
+ /// Returns whether the scroll box is currently touched or not.
+ pub fn is_scroll_box_touched(&self) -> bool {
+ self.scroll_box_touched_at.is_some()
+ }
}
/// The scrollbar of a [`Scrollable`].
-///
-/// [`Scrollable`]: struct.Scrollable.html
#[derive(Debug)]
pub struct Scrollbar {
+ /// The outer bounds of the scrollable, including the [`Scrollbar`] and
+ /// [`Scroller`].
+ pub outer_bounds: Rectangle,
+
/// The bounds of the [`Scrollbar`].
- ///
- /// [`Scrollbar`]: struct.Scrollbar.html
pub bounds: Rectangle,
+ /// The margin within the [`Scrollbar`].
+ pub margin: u16,
+
/// The bounds of the [`Scroller`].
- ///
- /// [`Scroller`]: struct.Scroller.html
pub scroller: Scroller,
}
impl Scrollbar {
fn is_mouse_over(&self, cursor_position: Point) -> bool {
- self.bounds.contains(cursor_position)
+ self.outer_bounds.contains(cursor_position)
}
fn grab_scroller(&self, cursor_position: Point) -> Option<f32> {
- if self.bounds.contains(cursor_position) {
+ if self.outer_bounds.contains(cursor_position) {
Some(if self.scroller.bounds.contains(cursor_position) {
(cursor_position.y - self.scroller.bounds.y)
/ self.scroller.bounds.height
@@ -455,13 +623,9 @@ impl Scrollbar {
}
/// The handle of a [`Scrollbar`].
-///
-/// [`Scrollbar`]: struct.Scrollbar.html
#[derive(Debug, Clone, Copy)]
pub struct Scroller {
/// The bounds of the [`Scroller`].
- ///
- /// [`Scroller`]: struct.Scrollbar.html
pub bounds: Rectangle,
}
@@ -470,22 +634,21 @@ pub struct Scroller {
/// Your [renderer] will need to implement this trait before being
/// able to use a [`Scrollable`] in your user interface.
///
-/// [`Scrollable`]: struct.Scrollable.html
-/// [renderer]: ../../renderer/index.html
+/// [renderer]: crate::renderer
pub trait Renderer: column::Renderer + Sized {
/// The style supported by this renderer.
type Style: Default;
/// Returns the [`Scrollbar`] given the bounds and content bounds of a
/// [`Scrollable`].
- ///
- /// [`Scrollbar`]: struct.Scrollbar.html
- /// [`Scrollable`]: struct.Scrollable.html
fn scrollbar(
&self,
bounds: Rectangle,
content_bounds: Rectangle,
offset: u32,
+ scrollbar_width: u16,
+ scrollbar_margin: u16,
+ scroller_width: u16,
) -> Option<Scrollbar>;
/// Draws the [`Scrollable`].
@@ -499,10 +662,6 @@ pub trait Renderer: column::Renderer + Sized {
/// - a optional [`Scrollbar`] to be rendered
/// - the scrolling offset
/// - the drawn content
- ///
- /// [`Scrollbar`]: struct.Scrollbar.html
- /// [`Scrollable`]: struct.Scrollable.html
- /// [`State`]: struct.State.html
fn draw(
&mut self,
scrollable: &State,
diff --git a/native/src/widget/slider.rs b/native/src/widget/slider.rs
index c49053f1..2a74d5a3 100644
--- a/native/src/widget/slider.rs
+++ b/native/src/widget/slider.rs
@@ -1,12 +1,12 @@
//! Display an interactive selector of a single value from a range of values.
//!
//! A [`Slider`] has some local [`State`].
-//!
-//! [`Slider`]: struct.Slider.html
-//! [`State`]: struct.State.html
+use crate::event::{self, Event};
+use crate::layout;
+use crate::mouse;
+use crate::touch;
use crate::{
- layout, mouse, Clipboard, Element, Event, Hasher, Layout, Length, Point,
- Rectangle, Size, Widget,
+ Clipboard, Element, Hasher, Layout, Length, Point, Rectangle, Size, Widget,
};
use std::{hash::Hash, ops::RangeInclusive};
@@ -19,13 +19,12 @@ use std::{hash::Hash, ops::RangeInclusive};
/// The [`Slider`] range of numeric values is generic and its step size defaults
/// to 1 unit.
///
-/// [`Slider`]: struct.Slider.html
-///
/// # Example
/// ```
/// # use iced_native::{slider, renderer::Null};
/// #
/// # pub type Slider<'a, T, Message> = iced_native::Slider<'a, T, Message, Null>;
+/// #[derive(Clone)]
/// pub enum Message {
/// SliderChanged(f32),
/// }
@@ -53,6 +52,7 @@ pub struct Slider<'a, T, Message, Renderer: self::Renderer> {
impl<'a, T, Message, Renderer> Slider<'a, T, Message, Renderer>
where
T: Copy + From<u8> + std::cmp::PartialOrd,
+ Message: Clone,
Renderer: self::Renderer,
{
/// Creates a new [`Slider`].
@@ -64,9 +64,6 @@ where
/// * a function that will be called when the [`Slider`] is dragged.
/// It receives the new value of the [`Slider`] and must produce a
/// `Message`.
- ///
- /// [`Slider`]: struct.Slider.html
- /// [`State`]: struct.State.html
pub fn new<F>(
state: &'a mut State,
range: RangeInclusive<T>,
@@ -107,40 +104,30 @@ where
/// Typically, the user's interaction with the slider is finished when this message is produced.
/// This is useful if you need to spawn a long-running task from the slider's result, where
/// the default on_change message could create too many events.
- ///
- /// [`Slider`]: struct.Slider.html
pub fn on_release(mut self, on_release: Message) -> Self {
self.on_release = Some(on_release);
self
}
/// Sets the width of the [`Slider`].
- ///
- /// [`Slider`]: struct.Slider.html
pub fn width(mut self, width: Length) -> Self {
self.width = width;
self
}
/// Sets the height of the [`Slider`].
- ///
- /// [`Slider`]: struct.Slider.html
pub fn height(mut self, height: u16) -> Self {
self.height = height;
self
}
/// Sets the style of the [`Slider`].
- ///
- /// [`Slider`]: struct.Slider.html
pub fn style(mut self, style: impl Into<Renderer::Style>) -> Self {
self.style = style.into();
self
}
/// Sets the step size of the [`Slider`].
- ///
- /// [`Slider`]: struct.Slider.html
pub fn step(mut self, step: T) -> Self {
self.step = step;
self
@@ -148,8 +135,6 @@ where
}
/// The local state of a [`Slider`].
-///
-/// [`Slider`]: struct.Slider.html
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct State {
is_dragging: bool,
@@ -157,8 +142,6 @@ pub struct State {
impl State {
/// Creates a new [`State`].
- ///
- /// [`State`]: struct.State.html
pub fn new() -> State {
State::default()
}
@@ -168,8 +151,8 @@ impl<'a, T, Message, Renderer> Widget<Message, Renderer>
for Slider<'a, T, Message, Renderer>
where
T: Copy + Into<f64> + num_traits::FromPrimitive,
- Renderer: self::Renderer,
Message: Clone,
+ Renderer: self::Renderer,
{
fn width(&self) -> Length {
self.width
@@ -197,10 +180,10 @@ where
event: Event,
layout: Layout<'_>,
cursor_position: Point,
- messages: &mut Vec<Message>,
_renderer: &Renderer,
- _clipboard: Option<&dyn Clipboard>,
- ) {
+ _clipboard: &mut dyn Clipboard,
+ messages: &mut Vec<Message>,
+ ) -> event::Status {
let mut change = || {
let bounds = layout.bounds();
if cursor_position.x <= bounds.x {
@@ -225,30 +208,39 @@ where
};
match event {
- Event::Mouse(mouse_event) => match mouse_event {
- mouse::Event::ButtonPressed(mouse::Button::Left) => {
- if layout.bounds().contains(cursor_position) {
- change();
- self.state.is_dragging = true;
- }
+ Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left))
+ | Event::Touch(touch::Event::FingerPressed { .. }) => {
+ if layout.bounds().contains(cursor_position) {
+ change();
+ self.state.is_dragging = true;
+
+ return event::Status::Captured;
}
- mouse::Event::ButtonReleased(mouse::Button::Left) => {
- if self.state.is_dragging {
- if let Some(on_release) = self.on_release.clone() {
- messages.push(on_release);
- }
- self.state.is_dragging = false;
+ }
+ Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left))
+ | Event::Touch(touch::Event::FingerLifted { .. })
+ | Event::Touch(touch::Event::FingerLost { .. }) => {
+ if self.state.is_dragging {
+ if let Some(on_release) = self.on_release.clone() {
+ messages.push(on_release);
}
+ self.state.is_dragging = false;
+
+ return event::Status::Captured;
}
- mouse::Event::CursorMoved { .. } => {
- if self.state.is_dragging {
- change();
- }
+ }
+ Event::Mouse(mouse::Event::CursorMoved { .. })
+ | Event::Touch(touch::Event::FingerMoved { .. }) => {
+ if self.state.is_dragging {
+ change();
+
+ return event::Status::Captured;
}
- _ => {}
- },
+ }
_ => {}
}
+
+ event::Status::Ignored
}
fn draw(
@@ -257,6 +249,7 @@ where
_defaults: &Renderer::Defaults,
layout: Layout<'_>,
cursor_position: Point,
+ _viewport: &Rectangle,
) -> Renderer::Output {
let start = *self.range.start();
let end = *self.range.end();
@@ -284,15 +277,12 @@ where
/// Your [renderer] will need to implement this trait before being
/// able to use a [`Slider`] in your user interface.
///
-/// [`Slider`]: struct.Slider.html
-/// [renderer]: ../../renderer/index.html
+/// [renderer]: crate::renderer
pub trait Renderer: crate::Renderer {
/// The style supported by this renderer.
type Style: Default;
/// The default height of a [`Slider`].
- ///
- /// [`Slider`]: struct.Slider.html
const DEFAULT_HEIGHT: u16;
/// Draws a [`Slider`].
@@ -303,10 +293,6 @@ pub trait Renderer: crate::Renderer {
/// * the local state of the [`Slider`]
/// * the range of values of the [`Slider`]
/// * the current value of the [`Slider`]
- ///
- /// [`Slider`]: struct.Slider.html
- /// [`State`]: struct.State.html
- /// [`Class`]: enum.Class.html
fn draw(
&mut self,
bounds: Rectangle,
@@ -322,8 +308,8 @@ impl<'a, T, Message, Renderer> From<Slider<'a, T, Message, Renderer>>
for Element<'a, Message, Renderer>
where
T: 'a + Copy + Into<f64> + num_traits::FromPrimitive,
- Renderer: 'a + self::Renderer,
Message: 'a + Clone,
+ Renderer: 'a + self::Renderer,
{
fn from(
slider: Slider<'a, T, Message, Renderer>,
diff --git a/native/src/widget/space.rs b/native/src/widget/space.rs
index f1576ffb..6b34ece8 100644
--- a/native/src/widget/space.rs
+++ b/native/src/widget/space.rs
@@ -16,15 +16,11 @@ pub struct Space {
impl Space {
/// Creates an amount of empty [`Space`] with the given width and height.
- ///
- /// [`Space`]: struct.Space.html
pub fn new(width: Length, height: Length) -> Self {
Space { width, height }
}
/// Creates an amount of horizontal [`Space`].
- ///
- /// [`Space`]: struct.Space.html
pub fn with_width(width: Length) -> Self {
Space {
width,
@@ -33,8 +29,6 @@ impl Space {
}
/// Creates an amount of vertical [`Space`].
- ///
- /// [`Space`]: struct.Space.html
pub fn with_height(height: Length) -> Self {
Space {
width: Length::Shrink,
@@ -71,6 +65,7 @@ where
_defaults: &Renderer::Defaults,
layout: Layout<'_>,
_cursor_position: Point,
+ _viewport: &Rectangle,
) -> Renderer::Output {
renderer.draw(layout.bounds())
}
@@ -84,14 +79,10 @@ where
}
/// The renderer of an amount of [`Space`].
-///
-/// [`Space`]: struct.Space.html
pub trait Renderer: crate::Renderer {
/// Draws an amount of empty [`Space`].
///
/// You should most likely return an empty primitive here.
- ///
- /// [`Space`]: struct.Space.html
fn draw(&mut self, bounds: Rectangle) -> Self::Output;
}
diff --git a/native/src/widget/svg.rs b/native/src/widget/svg.rs
index 114d5e41..9cd61918 100644
--- a/native/src/widget/svg.rs
+++ b/native/src/widget/svg.rs
@@ -1,5 +1,6 @@
//! Display vector graphics in your application.
-use crate::{layout, Element, Hasher, Layout, Length, Point, Size, Widget};
+use crate::layout;
+use crate::{Element, Hasher, Layout, Length, Point, Rectangle, Size, Widget};
use std::{
hash::{Hash, Hasher as _},
@@ -13,8 +14,6 @@ use std::{
///
/// [`Svg`] images can have a considerable rendering cost when resized,
/// specially when they are complex.
-///
-/// [`Svg`]: struct.Svg.html
#[derive(Debug, Clone)]
pub struct Svg {
handle: Handle,
@@ -24,9 +23,6 @@ pub struct Svg {
impl Svg {
/// Creates a new [`Svg`] from the given [`Handle`].
- ///
- /// [`Svg`]: struct.Svg.html
- /// [`Handle`]: struct.Handle.html
pub fn new(handle: impl Into<Handle>) -> Self {
Svg {
handle: handle.into(),
@@ -37,23 +33,17 @@ impl Svg {
/// Creates a new [`Svg`] that will display the contents of the file at the
/// provided path.
- ///
- /// [`Svg`]: struct.Svg.html
pub fn from_path(path: impl Into<PathBuf>) -> Self {
Self::new(Handle::from_path(path))
}
/// Sets the width of the [`Svg`].
- ///
- /// [`Svg`]: struct.Svg.html
pub fn width(mut self, width: Length) -> Self {
self.width = width;
self
}
/// Sets the height of the [`Svg`].
- ///
- /// [`Svg`]: struct.Svg.html
pub fn height(mut self, height: Length) -> Self {
self.height = height;
self
@@ -103,6 +93,7 @@ where
_defaults: &Renderer::Defaults,
layout: Layout<'_>,
_cursor_position: Point,
+ _viewport: &Rectangle,
) -> Renderer::Output {
renderer.draw(self.handle.clone(), layout)
}
@@ -117,8 +108,6 @@ where
}
/// An [`Svg`] handle.
-///
-/// [`Svg`]: struct.Svg.html
#[derive(Debug, Clone)]
pub struct Handle {
id: u64,
@@ -128,8 +117,6 @@ pub struct Handle {
impl Handle {
/// Creates an SVG [`Handle`] pointing to the vector image of the given
/// path.
- ///
- /// [`Handle`]: struct.Handle.html
pub fn from_path(path: impl Into<PathBuf>) -> Handle {
Self::from_data(Data::Path(path.into()))
}
@@ -139,8 +126,6 @@ impl Handle {
///
/// This is useful if you already have your SVG data in-memory, maybe
/// because you downloaded or generated it procedurally.
- ///
- /// [`Handle`]: struct.Handle.html
pub fn from_memory(bytes: impl Into<Vec<u8>>) -> Handle {
Self::from_data(Data::Bytes(bytes.into()))
}
@@ -156,15 +141,11 @@ impl Handle {
}
/// Returns the unique identifier of the [`Handle`].
- ///
- /// [`Handle`]: struct.Handle.html
pub fn id(&self) -> u64 {
self.id
}
/// Returns a reference to the SVG [`Data`].
- ///
- /// [`Data`]: enum.Data.html
pub fn data(&self) -> &Data {
&self.data
}
@@ -177,8 +158,6 @@ impl Hash for Handle {
}
/// The data of an [`Svg`].
-///
-/// [`Svg`]: struct.Svg.html
#[derive(Clone, Hash)]
pub enum Data {
/// File data
@@ -204,18 +183,12 @@ impl std::fmt::Debug for Data {
/// Your [renderer] will need to implement this trait before being able to use
/// an [`Svg`] in your user interface.
///
-/// [`Svg`]: struct.Svg.html
-/// [renderer]: ../../renderer/index.html
+/// [renderer]: crate::renderer
pub trait Renderer: crate::Renderer {
/// Returns the default dimensions of an [`Svg`] for the given [`Handle`].
- ///
- /// [`Svg`]: struct.Svg.html
- /// [`Handle`]: struct.Handle.html
fn dimensions(&self, handle: &Handle) -> (u32, u32);
/// Draws an [`Svg`].
- ///
- /// [`Svg`]: struct.Svg.html
fn draw(&mut self, handle: Handle, layout: Layout<'_>) -> Self::Output;
}
diff --git a/native/src/widget/text.rs b/native/src/widget/text.rs
index 48a69e34..6cc18e6c 100644
--- a/native/src/widget/text.rs
+++ b/native/src/widget/text.rs
@@ -33,8 +33,6 @@ pub struct Text<Renderer: self::Renderer> {
impl<Renderer: self::Renderer> Text<Renderer> {
/// Create a new fragment of [`Text`] with the given contents.
- ///
- /// [`Text`]: struct.Text.html
pub fn new<T: Into<String>>(label: T) -> Self {
Text {
content: label.into(),
@@ -49,17 +47,12 @@ impl<Renderer: self::Renderer> Text<Renderer> {
}
/// Sets the size of the [`Text`].
- ///
- /// [`Text`]: struct.Text.html
pub fn size(mut self, size: u16) -> Self {
self.size = Some(size);
self
}
/// Sets the [`Color`] of the [`Text`].
- ///
- /// [`Text`]: struct.Text.html
- /// [`Color`]: ../../struct.Color.html
pub fn color<C: Into<Color>>(mut self, color: C) -> Self {
self.color = Some(color.into());
self
@@ -67,33 +60,25 @@ impl<Renderer: self::Renderer> Text<Renderer> {
/// Sets the [`Font`] of the [`Text`].
///
- /// [`Text`]: struct.Text.html
- /// [`Font`]: ../../struct.Font.html
+ /// [`Font`]: Renderer::Font
pub fn font(mut self, font: impl Into<Renderer::Font>) -> Self {
self.font = font.into();
self
}
/// Sets the width of the [`Text`] boundaries.
- ///
- /// [`Text`]: struct.Text.html
pub fn width(mut self, width: Length) -> Self {
self.width = width;
self
}
/// Sets the height of the [`Text`] boundaries.
- ///
- /// [`Text`]: struct.Text.html
pub fn height(mut self, height: Length) -> Self {
self.height = height;
self
}
/// Sets the [`HorizontalAlignment`] of the [`Text`].
- ///
- /// [`Text`]: struct.Text.html
- /// [`HorizontalAlignment`]: enum.HorizontalAlignment.html
pub fn horizontal_alignment(
mut self,
alignment: HorizontalAlignment,
@@ -103,9 +88,6 @@ impl<Renderer: self::Renderer> Text<Renderer> {
}
/// Sets the [`VerticalAlignment`] of the [`Text`].
- ///
- /// [`Text`]: struct.Text.html
- /// [`VerticalAlignment`]: enum.VerticalAlignment.html
pub fn vertical_alignment(mut self, alignment: VerticalAlignment) -> Self {
self.vertical_alignment = alignment;
self
@@ -149,6 +131,7 @@ where
defaults: &Renderer::Defaults,
layout: Layout<'_>,
_cursor_position: Point,
+ _viewport: &Rectangle,
) -> Renderer::Output {
renderer.draw(
defaults,
@@ -176,26 +159,18 @@ where
/// The renderer of a [`Text`] fragment.
///
/// Your [renderer] will need to implement this trait before being
-/// able to use [`Text`] in your [`UserInterface`].
+/// able to use [`Text`] in your user interface.
///
-/// [`Text`]: struct.Text.html
-/// [renderer]: ../../renderer/index.html
-/// [`UserInterface`]: ../../struct.UserInterface.html
+/// [renderer]: crate::Renderer
pub trait Renderer: crate::Renderer {
/// The font type used for [`Text`].
- ///
- /// [`Text`]: struct.Text.html
type Font: Default + Copy;
/// Returns the default size of [`Text`].
- ///
- /// [`Text`]: struct.Text.html
fn default_size(&self) -> u16;
/// Measures the [`Text`] in the given bounds and returns the minimum
/// boundaries that can fit the contents.
- ///
- /// [`Text`]: struct.Text.html
fn measure(
&self,
content: &str,
@@ -213,10 +188,6 @@ pub trait Renderer: crate::Renderer {
/// * the color of the [`Text`]
/// * the [`HorizontalAlignment`] of the [`Text`]
/// * the [`VerticalAlignment`] of the [`Text`]
- ///
- /// [`Text`]: struct.Text.html
- /// [`HorizontalAlignment`]: enum.HorizontalAlignment.html
- /// [`VerticalAlignment`]: enum.VerticalAlignment.html
fn draw(
&mut self,
defaults: &Self::Defaults,
diff --git a/native/src/widget/text_input.rs b/native/src/widget/text_input.rs
index 9e15f4be..cec1e485 100644
--- a/native/src/widget/text_input.rs
+++ b/native/src/widget/text_input.rs
@@ -1,9 +1,6 @@
//! Display fields that can be filled with text.
//!
//! A [`TextInput`] has some local [`State`].
-//!
-//! [`TextInput`]: struct.TextInput.html
-//! [`State`]: struct.State.html
mod editor;
mod value;
@@ -14,10 +11,14 @@ pub use value::Value;
use editor::Editor;
+use crate::event::{self, Event};
+use crate::keyboard;
+use crate::layout;
+use crate::mouse::{self, click};
+use crate::text;
+use crate::touch;
use crate::{
- keyboard, layout,
- mouse::{self, click},
- text, Clipboard, Element, Event, Hasher, Layout, Length, Point, Rectangle,
+ Clipboard, Element, Hasher, Layout, Length, Padding, Point, Rectangle,
Size, Widget,
};
@@ -56,14 +57,18 @@ pub struct TextInput<'a, Message, Renderer: self::Renderer> {
font: Renderer::Font,
width: Length,
max_width: u32,
- padding: u16,
+ padding: Padding,
size: Option<u16>,
on_change: Box<dyn Fn(String) -> Message>,
on_submit: Option<Message>,
style: Renderer::Style,
}
-impl<'a, Message, Renderer: self::Renderer> TextInput<'a, Message, Renderer> {
+impl<'a, Message, Renderer> TextInput<'a, Message, Renderer>
+where
+ Message: Clone,
+ Renderer: self::Renderer,
+{
/// Creates a new [`TextInput`].
///
/// It expects:
@@ -71,9 +76,6 @@ impl<'a, Message, Renderer: self::Renderer> TextInput<'a, Message, Renderer> {
/// - a placeholder
/// - the current value
/// - a function that produces a message when the [`TextInput`] changes
- ///
- /// [`TextInput`]: struct.TextInput.html
- /// [`State`]: struct.State.html
pub fn new<F>(
state: &'a mut State,
placeholder: &str,
@@ -91,7 +93,7 @@ impl<'a, Message, Renderer: self::Renderer> TextInput<'a, Message, Renderer> {
font: Default::default(),
width: Length::Fill,
max_width: u32::MAX,
- padding: 0,
+ padding: Padding::ZERO,
size: None,
on_change: Box::new(on_change),
on_submit: None,
@@ -100,8 +102,6 @@ impl<'a, Message, Renderer: self::Renderer> TextInput<'a, Message, Renderer> {
}
/// Converts the [`TextInput`] into a secure password input.
- ///
- /// [`TextInput`]: struct.TextInput.html
pub fn password(mut self) -> Self {
self.is_secure = true;
self
@@ -109,39 +109,31 @@ impl<'a, Message, Renderer: self::Renderer> TextInput<'a, Message, Renderer> {
/// Sets the [`Font`] of the [`Text`].
///
- /// [`Text`]: struct.Text.html
- /// [`Font`]: ../../struct.Font.html
+ /// [`Font`]: crate::widget::text::Renderer::Font
+ /// [`Text`]: crate::widget::Text
pub fn font(mut self, font: Renderer::Font) -> Self {
self.font = font;
self
}
/// Sets the width of the [`TextInput`].
- ///
- /// [`TextInput`]: struct.TextInput.html
pub fn width(mut self, width: Length) -> Self {
self.width = width;
self
}
/// Sets the maximum width of the [`TextInput`].
- ///
- /// [`TextInput`]: struct.TextInput.html
pub fn max_width(mut self, max_width: u32) -> Self {
self.max_width = max_width;
self
}
- /// Sets the padding of the [`TextInput`].
- ///
- /// [`TextInput`]: struct.TextInput.html
- pub fn padding(mut self, units: u16) -> Self {
- self.padding = units;
+ /// Sets the [`Padding`] of the [`TextInput`].
+ pub fn padding<P: Into<Padding>>(mut self, padding: P) -> Self {
+ self.padding = padding.into();
self
}
/// Sets the text size of the [`TextInput`].
- ///
- /// [`TextInput`]: struct.TextInput.html
pub fn size(mut self, size: u16) -> Self {
self.size = Some(size);
self
@@ -149,34 +141,75 @@ impl<'a, Message, Renderer: self::Renderer> TextInput<'a, Message, Renderer> {
/// Sets the message that should be produced when the [`TextInput`] is
/// focused and the enter key is pressed.
- ///
- /// [`TextInput`]: struct.TextInput.html
pub fn on_submit(mut self, message: Message) -> Self {
self.on_submit = Some(message);
self
}
/// Sets the style of the [`TextInput`].
- ///
- /// [`TextInput`]: struct.TextInput.html
pub fn style(mut self, style: impl Into<Renderer::Style>) -> Self {
self.style = style.into();
self
}
/// Returns the current [`State`] of the [`TextInput`].
- ///
- /// [`TextInput`]: struct.TextInput.html
pub fn state(&self) -> &State {
self.state
}
}
+impl<'a, Message, Renderer> TextInput<'a, Message, Renderer>
+where
+ Renderer: self::Renderer,
+{
+ /// Draws the [`TextInput`] with the given [`Renderer`], overriding its
+ /// [`Value`] if provided.
+ pub fn draw(
+ &self,
+ renderer: &mut Renderer,
+ layout: Layout<'_>,
+ cursor_position: Point,
+ value: Option<&Value>,
+ ) -> Renderer::Output {
+ let value = value.unwrap_or(&self.value);
+ let bounds = layout.bounds();
+ let text_bounds = layout.children().next().unwrap().bounds();
+
+ if self.is_secure {
+ self::Renderer::draw(
+ renderer,
+ bounds,
+ text_bounds,
+ cursor_position,
+ self.font,
+ self.size.unwrap_or(renderer.default_size()),
+ &self.placeholder,
+ &value.secure(),
+ &self.state,
+ &self.style,
+ )
+ } else {
+ self::Renderer::draw(
+ renderer,
+ bounds,
+ text_bounds,
+ cursor_position,
+ self.font,
+ self.size.unwrap_or(renderer.default_size()),
+ &self.placeholder,
+ value,
+ &self.state,
+ &self.style,
+ )
+ }
+ }
+}
+
impl<'a, Message, Renderer> Widget<Message, Renderer>
for TextInput<'a, Message, Renderer>
where
- Renderer: self::Renderer,
Message: Clone,
+ Renderer: self::Renderer,
{
fn width(&self) -> Length {
self.width
@@ -191,19 +224,21 @@ where
renderer: &Renderer,
limits: &layout::Limits,
) -> layout::Node {
- let padding = self.padding as f32;
let text_size = self.size.unwrap_or(renderer.default_size());
let limits = limits
- .pad(padding)
+ .pad(self.padding)
.width(self.width)
.max_width(self.max_width)
.height(Length::Units(text_size));
let mut text = layout::Node::new(limits.resolve(Size::ZERO));
- text.move_to(Point::new(padding, padding));
+ text.move_to(Point::new(
+ self.padding.left.into(),
+ self.padding.top.into(),
+ ));
- layout::Node::with_children(text.size().pad(padding), vec![text])
+ layout::Node::with_children(text.size().pad(self.padding), vec![text])
}
fn on_event(
@@ -211,14 +246,17 @@ where
event: Event,
layout: Layout<'_>,
cursor_position: Point,
- messages: &mut Vec<Message>,
renderer: &Renderer,
- clipboard: Option<&dyn Clipboard>,
- ) {
+ clipboard: &mut dyn Clipboard,
+ messages: &mut Vec<Message>,
+ ) -> event::Status {
match event {
- Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) => {
+ Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left))
+ | Event::Touch(touch::Event::FingerPressed { .. }) => {
let is_clicked = layout.bounds().contains(cursor_position);
+ self.state.is_focused = is_clicked;
+
if is_clicked {
let text_layout = layout.children().next().unwrap();
let target = cursor_position.x - text_layout.bounds().x;
@@ -250,6 +288,8 @@ where
} else {
self.state.cursor.move_to(0);
}
+
+ self.state.is_dragging = true;
}
click::Kind::Double => {
if self.is_secure {
@@ -269,25 +309,30 @@ where
self.value.next_end_of_word(position),
);
}
+
+ self.state.is_dragging = false;
}
click::Kind::Triple => {
self.state.cursor.select_all(&self.value);
+ self.state.is_dragging = false;
}
}
self.state.last_click = Some(click);
- }
- self.state.is_dragging = is_clicked;
- self.state.is_focused = is_clicked;
+ return event::Status::Captured;
+ }
}
- Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left)) => {
+ Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left))
+ | Event::Touch(touch::Event::FingerLifted { .. })
+ | Event::Touch(touch::Event::FingerLost { .. }) => {
self.state.is_dragging = false;
}
- Event::Mouse(mouse::Event::CursorMoved { x, .. }) => {
+ Event::Mouse(mouse::Event::CursorMoved { position })
+ | Event::Touch(touch::Event::FingerMoved { position, .. }) => {
if self.state.is_dragging {
let text_layout = layout.children().next().unwrap();
- let target = x - text_layout.bounds().x;
+ let target = position.x - text_layout.bounds().x;
if target > 0.0 {
let value = if self.is_secure {
@@ -310,11 +355,14 @@ where
position,
);
}
+
+ return event::Status::Captured;
}
}
Event::Keyboard(keyboard::Event::CharacterReceived(c))
if self.state.is_focused
&& self.state.is_pasting.is_none()
+ && !self.state.keyboard_modifiers.command()
&& !c.is_control() =>
{
let mut editor =
@@ -324,120 +372,179 @@ where
let message = (self.on_change)(editor.contents());
messages.push(message);
+
+ return event::Status::Captured;
}
Event::Keyboard(keyboard::Event::KeyPressed {
- key_code,
- modifiers,
- }) if self.state.is_focused => match key_code {
- keyboard::KeyCode::Enter => {
- if let Some(on_submit) = self.on_submit.clone() {
- messages.push(on_submit);
- }
- }
- keyboard::KeyCode::Backspace => {
- if platform::is_jump_modifier_pressed(modifiers)
- && self.state.cursor.selection(&self.value).is_none()
- {
- if self.is_secure {
- let cursor_pos = self.state.cursor.end(&self.value);
- self.state.cursor.select_range(0, cursor_pos);
- } else {
- self.state.cursor.select_left_by_words(&self.value);
+ key_code, ..
+ }) if self.state.is_focused => {
+ let modifiers = self.state.keyboard_modifiers;
+
+ match key_code {
+ keyboard::KeyCode::Enter => {
+ if let Some(on_submit) = self.on_submit.clone() {
+ messages.push(on_submit);
}
}
+ keyboard::KeyCode::Backspace => {
+ if platform::is_jump_modifier_pressed(modifiers)
+ && self
+ .state
+ .cursor
+ .selection(&self.value)
+ .is_none()
+ {
+ if self.is_secure {
+ let cursor_pos =
+ self.state.cursor.end(&self.value);
+ self.state.cursor.select_range(0, cursor_pos);
+ } else {
+ self.state
+ .cursor
+ .select_left_by_words(&self.value);
+ }
+ }
- let mut editor =
- Editor::new(&mut self.value, &mut self.state.cursor);
+ let mut editor = Editor::new(
+ &mut self.value,
+ &mut self.state.cursor,
+ );
- editor.backspace();
+ editor.backspace();
- let message = (self.on_change)(editor.contents());
- messages.push(message);
- }
- keyboard::KeyCode::Delete => {
- if platform::is_jump_modifier_pressed(modifiers)
- && self.state.cursor.selection(&self.value).is_none()
- {
- if self.is_secure {
- let cursor_pos = self.state.cursor.end(&self.value);
- self.state
- .cursor
- .select_range(cursor_pos, self.value.len());
- } else {
- self.state
+ let message = (self.on_change)(editor.contents());
+ messages.push(message);
+ }
+ keyboard::KeyCode::Delete => {
+ if platform::is_jump_modifier_pressed(modifiers)
+ && self
+ .state
.cursor
- .select_right_by_words(&self.value);
+ .selection(&self.value)
+ .is_none()
+ {
+ if self.is_secure {
+ let cursor_pos =
+ self.state.cursor.end(&self.value);
+ self.state
+ .cursor
+ .select_range(cursor_pos, self.value.len());
+ } else {
+ self.state
+ .cursor
+ .select_right_by_words(&self.value);
+ }
}
- }
- let mut editor =
- Editor::new(&mut self.value, &mut self.state.cursor);
+ let mut editor = Editor::new(
+ &mut self.value,
+ &mut self.state.cursor,
+ );
- editor.delete();
+ editor.delete();
- let message = (self.on_change)(editor.contents());
- messages.push(message);
- }
- keyboard::KeyCode::Left => {
- if platform::is_jump_modifier_pressed(modifiers)
- && !self.is_secure
- {
- if modifiers.shift {
- self.state.cursor.select_left_by_words(&self.value);
+ let message = (self.on_change)(editor.contents());
+ messages.push(message);
+ }
+ keyboard::KeyCode::Left => {
+ if platform::is_jump_modifier_pressed(modifiers)
+ && !self.is_secure
+ {
+ if modifiers.shift() {
+ self.state
+ .cursor
+ .select_left_by_words(&self.value);
+ } else {
+ self.state
+ .cursor
+ .move_left_by_words(&self.value);
+ }
+ } else if modifiers.shift() {
+ self.state.cursor.select_left(&self.value)
} else {
- self.state.cursor.move_left_by_words(&self.value);
+ self.state.cursor.move_left(&self.value);
}
- } else if modifiers.shift {
- self.state.cursor.select_left(&self.value)
- } else {
- self.state.cursor.move_left(&self.value);
}
- }
- keyboard::KeyCode::Right => {
- if platform::is_jump_modifier_pressed(modifiers)
- && !self.is_secure
- {
- if modifiers.shift {
- self.state
- .cursor
- .select_right_by_words(&self.value);
+ keyboard::KeyCode::Right => {
+ if platform::is_jump_modifier_pressed(modifiers)
+ && !self.is_secure
+ {
+ if modifiers.shift() {
+ self.state
+ .cursor
+ .select_right_by_words(&self.value);
+ } else {
+ self.state
+ .cursor
+ .move_right_by_words(&self.value);
+ }
+ } else if modifiers.shift() {
+ self.state.cursor.select_right(&self.value)
} else {
- self.state.cursor.move_right_by_words(&self.value);
+ self.state.cursor.move_right(&self.value);
}
- } else if modifiers.shift {
- self.state.cursor.select_right(&self.value)
- } else {
- self.state.cursor.move_right(&self.value);
}
- }
- keyboard::KeyCode::Home => {
- if modifiers.shift {
- self.state.cursor.select_range(
- self.state.cursor.start(&self.value),
- 0,
- );
- } else {
- self.state.cursor.move_to(0);
+ keyboard::KeyCode::Home => {
+ if modifiers.shift() {
+ self.state.cursor.select_range(
+ self.state.cursor.start(&self.value),
+ 0,
+ );
+ } else {
+ self.state.cursor.move_to(0);
+ }
}
- }
- keyboard::KeyCode::End => {
- if modifiers.shift {
- self.state.cursor.select_range(
- self.state.cursor.start(&self.value),
- self.value.len(),
+ keyboard::KeyCode::End => {
+ if modifiers.shift() {
+ self.state.cursor.select_range(
+ self.state.cursor.start(&self.value),
+ self.value.len(),
+ );
+ } else {
+ self.state.cursor.move_to(self.value.len());
+ }
+ }
+ keyboard::KeyCode::C
+ if self.state.keyboard_modifiers.command() =>
+ {
+ match self.state.cursor.selection(&self.value) {
+ Some((start, end)) => {
+ clipboard.write(
+ self.value.select(start, end).to_string(),
+ );
+ }
+ None => {}
+ }
+ }
+ keyboard::KeyCode::X
+ if self.state.keyboard_modifiers.command() =>
+ {
+ match self.state.cursor.selection(&self.value) {
+ Some((start, end)) => {
+ clipboard.write(
+ self.value.select(start, end).to_string(),
+ );
+ }
+ None => {}
+ }
+
+ let mut editor = Editor::new(
+ &mut self.value,
+ &mut self.state.cursor,
);
- } else {
- self.state.cursor.move_to(self.value.len());
+
+ editor.delete();
+
+ let message = (self.on_change)(editor.contents());
+ messages.push(message);
}
- }
- keyboard::KeyCode::V => {
- if platform::is_copy_paste_modifier_pressed(modifiers) {
- if let Some(clipboard) = clipboard {
+ keyboard::KeyCode::V => {
+ if self.state.keyboard_modifiers.command() {
let content = match self.state.is_pasting.take() {
Some(content) => content,
None => {
let content: String = clipboard
- .content()
+ .read()
.unwrap_or(String::new())
.chars()
.filter(|c| !c.is_control())
@@ -458,33 +565,49 @@ where
messages.push(message);
self.state.is_pasting = Some(content);
+ } else {
+ self.state.is_pasting = None;
}
- } else {
- self.state.is_pasting = None;
}
- }
- keyboard::KeyCode::A => {
- if platform::is_copy_paste_modifier_pressed(modifiers) {
+ keyboard::KeyCode::A
+ if self.state.keyboard_modifiers.command() =>
+ {
self.state.cursor.select_all(&self.value);
}
+ keyboard::KeyCode::Escape => {
+ self.state.is_focused = false;
+ self.state.is_dragging = false;
+ self.state.is_pasting = None;
+
+ self.state.keyboard_modifiers =
+ keyboard::Modifiers::default();
+ }
+ _ => {}
}
- keyboard::KeyCode::Escape => {
- self.state.is_focused = false;
- self.state.is_dragging = false;
- self.state.is_pasting = None;
- }
- _ => {}
- },
+
+ return event::Status::Captured;
+ }
Event::Keyboard(keyboard::Event::KeyReleased {
key_code, ..
- }) => match key_code {
- keyboard::KeyCode::V => {
- self.state.is_pasting = None;
+ }) if self.state.is_focused => {
+ match key_code {
+ keyboard::KeyCode::V => {
+ self.state.is_pasting = None;
+ }
+ _ => {}
}
- _ => {}
- },
+
+ return event::Status::Captured;
+ }
+ Event::Keyboard(keyboard::Event::ModifiersChanged(modifiers))
+ if self.state.is_focused =>
+ {
+ self.state.keyboard_modifiers = modifiers;
+ }
_ => {}
}
+
+ event::Status::Ignored
}
fn draw(
@@ -493,37 +616,9 @@ where
_defaults: &Renderer::Defaults,
layout: Layout<'_>,
cursor_position: Point,
+ _viewport: &Rectangle,
) -> Renderer::Output {
- let bounds = layout.bounds();
- let text_bounds = layout.children().next().unwrap().bounds();
-
- if self.is_secure {
- self::Renderer::draw(
- renderer,
- bounds,
- text_bounds,
- cursor_position,
- self.font,
- self.size.unwrap_or(renderer.default_size()),
- &self.placeholder,
- &self.value.secure(),
- &self.state,
- &self.style,
- )
- } else {
- self::Renderer::draw(
- renderer,
- bounds,
- text_bounds,
- cursor_position,
- self.font,
- self.size.unwrap_or(renderer.default_size()),
- &self.placeholder,
- &self.value,
- &self.state,
- &self.style,
- )
- }
+ self.draw(renderer, layout, cursor_position, None)
}
fn hash_layout(&self, state: &mut Hasher) {
@@ -543,15 +638,12 @@ where
/// Your [renderer] will need to implement this trait before being
/// able to use a [`TextInput`] in your user interface.
///
-/// [`TextInput`]: struct.TextInput.html
-/// [renderer]: ../../renderer/index.html
+/// [renderer]: crate::renderer
pub trait Renderer: text::Renderer + Sized {
/// The style supported by this renderer.
type Style: Default;
/// Returns the width of the value of the [`TextInput`].
- ///
- /// [`TextInput`]: struct.TextInput.html
fn measure_value(&self, value: &str, size: u16, font: Self::Font) -> f32;
/// Returns the current horizontal offset of the value of the
@@ -559,9 +651,6 @@ pub trait Renderer: text::Renderer + Sized {
///
/// This is the amount of horizontal scrolling applied when the [`Value`]
/// does not fit the [`TextInput`].
- ///
- /// [`TextInput`]: struct.TextInput.html
- /// [`Value`]: struct.Value.html
fn offset(
&self,
text_bounds: Rectangle,
@@ -580,10 +669,6 @@ pub trait Renderer: text::Renderer + Sized {
/// - the placeholder to show when the value is empty
/// - the current [`Value`]
/// - the current [`State`]
- ///
- /// [`TextInput`]: struct.TextInput.html
- /// [`Value`]: struct.Value.html
- /// [`State`]: struct.State.html
fn draw(
&mut self,
bounds: Rectangle,
@@ -599,8 +684,6 @@ pub trait Renderer: text::Renderer + Sized {
/// Computes the position of the text cursor at the given X coordinate of
/// a [`TextInput`].
- ///
- /// [`TextInput`]: struct.TextInput.html
fn find_cursor_position(
&self,
text_bounds: Rectangle,
@@ -629,8 +712,8 @@ pub trait Renderer: text::Renderer + Sized {
impl<'a, Message, Renderer> From<TextInput<'a, Message, Renderer>>
for Element<'a, Message, Renderer>
where
- Renderer: 'a + self::Renderer,
Message: 'a + Clone,
+ Renderer: 'a + self::Renderer,
{
fn from(
text_input: TextInput<'a, Message, Renderer>,
@@ -640,8 +723,6 @@ where
}
/// The state of a [`TextInput`].
-///
-/// [`TextInput`]: struct.TextInput.html
#[derive(Debug, Default, Clone)]
pub struct State {
is_focused: bool,
@@ -649,20 +730,17 @@ pub struct State {
is_pasting: Option<Value>,
last_click: Option<mouse::Click>,
cursor: Cursor,
+ keyboard_modifiers: keyboard::Modifiers,
// TODO: Add stateful horizontal scrolling offset
}
impl State {
/// Creates a new [`State`], representing an unfocused [`TextInput`].
- ///
- /// [`State`]: struct.State.html
pub fn new() -> Self {
Self::default()
}
/// Creates a new [`State`], representing a focused [`TextInput`].
- ///
- /// [`State`]: struct.State.html
pub fn focused() -> Self {
Self {
is_focused: true,
@@ -670,47 +748,49 @@ impl State {
is_pasting: None,
last_click: None,
cursor: Cursor::default(),
+ keyboard_modifiers: keyboard::Modifiers::default(),
}
}
/// Returns whether the [`TextInput`] is currently focused or not.
- ///
- /// [`TextInput`]: struct.TextInput.html
pub fn is_focused(&self) -> bool {
self.is_focused
}
/// Returns the [`Cursor`] of the [`TextInput`].
- ///
- /// [`Cursor`]: struct.Cursor.html
- /// [`TextInput`]: struct.TextInput.html
pub fn cursor(&self) -> Cursor {
self.cursor
}
+ /// Focuses the [`TextInput`].
+ pub fn focus(&mut self) {
+ self.is_focused = true;
+ }
+
+ /// Unfocuses the [`TextInput`].
+ pub fn unfocus(&mut self) {
+ self.is_focused = false;
+ }
+
/// Moves the [`Cursor`] of the [`TextInput`] to the front of the input text.
- ///
- /// [`Cursor`]: struct.Cursor.html
- /// [`TextInput`]: struct.TextInput.html
pub fn move_cursor_to_front(&mut self) {
self.cursor.move_to(0);
}
/// Moves the [`Cursor`] of the [`TextInput`] to the end of the input text.
- ///
- /// [`Cursor`]: struct.Cursor.html
- /// [`TextInput`]: struct.TextInput.html
pub fn move_cursor_to_end(&mut self) {
self.cursor.move_to(usize::MAX);
}
/// Moves the [`Cursor`] of the [`TextInput`] to an arbitrary location.
- ///
- /// [`Cursor`]: struct.Cursor.html
- /// [`TextInput`]: struct.TextInput.html
pub fn move_cursor_to(&mut self, position: usize) {
self.cursor.move_to(position);
}
+
+ /// Selects all the content of the [`TextInput`].
+ pub fn select_all(&mut self) {
+ self.cursor.select_range(0, usize::MAX);
+ }
}
// TODO: Reduce allocations
@@ -772,23 +852,11 @@ fn find_cursor_position<Renderer: self::Renderer>(
mod platform {
use crate::keyboard;
- pub fn is_jump_modifier_pressed(
- modifiers: keyboard::ModifiersState,
- ) -> bool {
- if cfg!(target_os = "macos") {
- modifiers.alt
- } else {
- modifiers.control
- }
- }
-
- pub fn is_copy_paste_modifier_pressed(
- modifiers: keyboard::ModifiersState,
- ) -> bool {
+ pub fn is_jump_modifier_pressed(modifiers: keyboard::Modifiers) -> bool {
if cfg!(target_os = "macos") {
- modifiers.logo
+ modifiers.alt()
} else {
- modifiers.control
+ modifiers.control()
}
}
}
diff --git a/native/src/widget/text_input/cursor.rs b/native/src/widget/text_input/cursor.rs
index aa03bb74..4f3b159b 100644
--- a/native/src/widget/text_input/cursor.rs
+++ b/native/src/widget/text_input/cursor.rs
@@ -8,8 +8,6 @@ pub struct Cursor {
}
/// The state of a [`Cursor`].
-///
-/// [`Cursor`]: struct.Cursor.html
#[derive(Debug, Copy, Clone)]
pub enum State {
/// Cursor without a selection
@@ -34,9 +32,6 @@ impl Default for Cursor {
impl Cursor {
/// Returns the [`State`] of the [`Cursor`].
- ///
- /// [`State`]: struct.State.html
- /// [`Cursor`]: struct.Cursor.html
pub fn state(&self, value: &Value) -> State {
match self.state {
State::Index(index) => State::Index(index.min(value.len())),
@@ -53,6 +48,18 @@ impl Cursor {
}
}
+ /// Returns the current selection of the [`Cursor`] for the given [`Value`].
+ ///
+ /// `start` is guaranteed to be <= than `end`.
+ pub fn selection(&self, value: &Value) -> Option<(usize, usize)> {
+ match self.state(value) {
+ State::Selection { start, end } => {
+ Some((start.min(end), start.max(end)))
+ }
+ _ => None,
+ }
+ }
+
pub(crate) fn move_to(&mut self, position: usize) {
self.state = State::Index(position);
}
@@ -106,7 +113,7 @@ impl Cursor {
State::Selection { start, end } if end > 0 => {
self.select_range(start, end - 1)
}
- _ => (),
+ _ => {}
}
}
@@ -118,7 +125,7 @@ impl Cursor {
State::Selection { start, end } if end < value.len() => {
self.select_range(start, end + 1)
}
- _ => (),
+ _ => {}
}
}
@@ -166,15 +173,6 @@ impl Cursor {
end.min(value.len())
}
- pub(crate) fn selection(&self, value: &Value) -> Option<(usize, usize)> {
- match self.state(value) {
- State::Selection { start, end } => {
- Some((start.min(end), start.max(end)))
- }
- _ => None,
- }
- }
-
fn left(&self, value: &Value) -> usize {
match self.state(value) {
State::Index(index) => index,
diff --git a/native/src/widget/text_input/editor.rs b/native/src/widget/text_input/editor.rs
index 20e42567..0b50a382 100644
--- a/native/src/widget/text_input/editor.rs
+++ b/native/src/widget/text_input/editor.rs
@@ -20,7 +20,7 @@ impl<'a> Editor<'a> {
self.cursor.move_left(self.value);
self.value.remove_many(left, right);
}
- _ => (),
+ _ => {}
}
self.value.insert(self.cursor.end(self.value), character);
@@ -35,7 +35,7 @@ impl<'a> Editor<'a> {
self.cursor.move_left(self.value);
self.value.remove_many(left, right);
}
- _ => (),
+ _ => {}
}
self.value.insert_many(self.cursor.end(self.value), content);
diff --git a/native/src/widget/text_input/value.rs b/native/src/widget/text_input/value.rs
index 1e9ba45b..2034cca4 100644
--- a/native/src/widget/text_input/value.rs
+++ b/native/src/widget/text_input/value.rs
@@ -2,7 +2,7 @@ use unicode_segmentation::UnicodeSegmentation;
/// The value of a [`TextInput`].
///
-/// [`TextInput`]: struct.TextInput.html
+/// [`TextInput`]: crate::widget::TextInput
// TODO: Reduce allocations, cache results (?)
#[derive(Debug, Clone)]
pub struct Value {
@@ -11,8 +11,6 @@ pub struct Value {
impl Value {
/// Creates a new [`Value`] from a string slice.
- ///
- /// [`Value`]: struct.Value.html
pub fn new(string: &str) -> Self {
let graphemes = UnicodeSegmentation::graphemes(string, true)
.map(String::from)
@@ -21,17 +19,20 @@ impl Value {
Self { graphemes }
}
- /// Returns the total amount of graphemes in the [`Value`].
+ /// Returns whether the [`Value`] is empty or not.
///
- /// [`Value`]: struct.Value.html
+ /// A [`Value`] is empty when it contains no graphemes.
+ pub fn is_empty(&self) -> bool {
+ self.len() == 0
+ }
+
+ /// Returns the total amount of graphemes in the [`Value`].
pub fn len(&self) -> usize {
self.graphemes.len()
}
/// Returns the position of the previous start of a word from the given
/// grapheme `index`.
- ///
- /// [`Value`]: struct.Value.html
pub fn previous_start_of_word(&self, index: usize) -> usize {
let previous_string =
&self.graphemes[..index.min(self.graphemes.len())].concat();
@@ -54,8 +55,6 @@ impl Value {
/// Returns the position of the next end of a word from the given grapheme
/// `index`.
- ///
- /// [`Value`]: struct.Value.html
pub fn next_end_of_word(&self, index: usize) -> usize {
let next_string = &self.graphemes[index..].concat();
@@ -74,10 +73,17 @@ impl Value {
.unwrap_or(self.len())
}
+ /// Returns a new [`Value`] containing the graphemes from `start` until the
+ /// given `end`.
+ pub fn select(&self, start: usize, end: usize) -> Self {
+ let graphemes =
+ self.graphemes[start.min(self.len())..end.min(self.len())].to_vec();
+
+ Self { graphemes }
+ }
+
/// Returns a new [`Value`] containing the graphemes until the given
/// `index`.
- ///
- /// [`Value`]: struct.Value.html
pub fn until(&self, index: usize) -> Self {
let graphemes = self.graphemes[..index.min(self.len())].to_vec();
@@ -85,8 +91,6 @@ impl Value {
}
/// Converts the [`Value`] into a `String`.
- ///
- /// [`Value`]: struct.Value.html
pub fn to_string(&self) -> String {
self.graphemes.concat()
}
@@ -109,8 +113,6 @@ impl Value {
}
/// Removes the grapheme at the given `index`.
- ///
- /// [`Value`]: struct.Value.html
pub fn remove(&mut self, index: usize) {
let _ = self.graphemes.remove(index);
}
@@ -122,8 +124,6 @@ impl Value {
/// Returns a new [`Value`] with all its graphemes replaced with the
/// dot ('•') character.
- ///
- /// [`Value`]: struct.Value.html
pub fn secure(&self) -> Self {
Self {
graphemes: std::iter::repeat(String::from("•"))
diff --git a/native/src/widget/toggler.rs b/native/src/widget/toggler.rs
new file mode 100644
index 00000000..4035276c
--- /dev/null
+++ b/native/src/widget/toggler.rs
@@ -0,0 +1,277 @@
+//! Show toggle controls using togglers.
+use std::hash::Hash;
+
+use crate::{
+ event, layout, mouse, row, text, Align, Clipboard, Element, Event, Hasher,
+ HorizontalAlignment, Layout, Length, Point, Rectangle, Row, Text,
+ VerticalAlignment, Widget,
+};
+
+/// A toggler widget
+///
+/// # Example
+///
+/// ```
+/// # type Toggler<Message> = iced_native::Toggler<Message, iced_native::renderer::Null>;
+/// #
+/// pub enum Message {
+/// TogglerToggled(bool),
+/// }
+///
+/// let is_active = true;
+///
+/// Toggler::new(is_active, String::from("Toggle me!"), |b| Message::TogglerToggled(b));
+/// ```
+#[allow(missing_debug_implementations)]
+pub struct Toggler<Message, Renderer: self::Renderer + text::Renderer> {
+ is_active: bool,
+ on_toggle: Box<dyn Fn(bool) -> Message>,
+ label: Option<String>,
+ width: Length,
+ size: u16,
+ text_size: Option<u16>,
+ text_alignment: HorizontalAlignment,
+ spacing: u16,
+ font: Renderer::Font,
+ style: Renderer::Style,
+}
+
+impl<Message, Renderer: self::Renderer + text::Renderer>
+ Toggler<Message, Renderer>
+{
+ /// Creates a new [`Toggler`].
+ ///
+ /// It expects:
+ /// * a boolean describing whether the [`Toggler`] is checked or not
+ /// * An optional label for the [`Toggler`]
+ /// * a function that will be called when the [`Toggler`] is toggled. It
+ /// will receive the new state of the [`Toggler`] and must produce a
+ /// `Message`.
+ pub fn new<F>(
+ is_active: bool,
+ label: impl Into<Option<String>>,
+ f: F,
+ ) -> Self
+ where
+ F: 'static + Fn(bool) -> Message,
+ {
+ Toggler {
+ is_active,
+ on_toggle: Box::new(f),
+ label: label.into(),
+ width: Length::Fill,
+ size: <Renderer as self::Renderer>::DEFAULT_SIZE,
+ text_size: None,
+ text_alignment: HorizontalAlignment::Left,
+ spacing: 0,
+ font: Renderer::Font::default(),
+ style: Renderer::Style::default(),
+ }
+ }
+
+ /// Sets the size of the [`Toggler`].
+ pub fn size(mut self, size: u16) -> Self {
+ self.size = size;
+ self
+ }
+
+ /// Sets the width of the [`Toggler`].
+ pub fn width(mut self, width: Length) -> Self {
+ self.width = width;
+ self
+ }
+
+ /// Sets the text size o the [`Toggler`].
+ pub fn text_size(mut self, text_size: u16) -> Self {
+ self.text_size = Some(text_size);
+ self
+ }
+
+ /// Sets the horizontal alignment of the text of the [`Toggler`]
+ pub fn text_alignment(mut self, alignment: HorizontalAlignment) -> Self {
+ self.text_alignment = alignment;
+ self
+ }
+
+ /// Sets the spacing between the [`Toggler`] and the text.
+ pub fn spacing(mut self, spacing: u16) -> Self {
+ self.spacing = spacing;
+ self
+ }
+
+ /// Sets the [`Font`] of the text of the [`Toggler`]
+ pub fn font(mut self, font: Renderer::Font) -> Self {
+ self.font = font;
+ self
+ }
+
+ /// Sets the style of the [`Toggler`].
+ pub fn style(mut self, style: impl Into<Renderer::Style>) -> Self {
+ self.style = style.into();
+ self
+ }
+}
+
+impl<Message, Renderer> Widget<Message, Renderer> for Toggler<Message, Renderer>
+where
+ Renderer: self::Renderer + text::Renderer + row::Renderer,
+{
+ fn width(&self) -> Length {
+ self.width
+ }
+
+ fn height(&self) -> Length {
+ Length::Shrink
+ }
+
+ fn layout(
+ &self,
+ renderer: &Renderer,
+ limits: &layout::Limits,
+ ) -> layout::Node {
+ let mut row = Row::<(), Renderer>::new()
+ .width(self.width)
+ .spacing(self.spacing)
+ .align_items(Align::Center);
+
+ if let Some(label) = &self.label {
+ row = row.push(
+ Text::new(label)
+ .horizontal_alignment(self.text_alignment)
+ .font(self.font)
+ .width(self.width)
+ .size(self.text_size.unwrap_or(renderer.default_size())),
+ );
+ }
+
+ row = row.push(
+ Row::new()
+ .width(Length::Units(2 * self.size))
+ .height(Length::Units(self.size)),
+ );
+
+ row.layout(renderer, limits)
+ }
+
+ fn on_event(
+ &mut self,
+ event: Event,
+ layout: Layout<'_>,
+ cursor_position: Point,
+ _renderer: &Renderer,
+ _clipboard: &mut dyn Clipboard,
+ messages: &mut Vec<Message>,
+ ) -> event::Status {
+ match event {
+ Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) => {
+ let mouse_over = layout.bounds().contains(cursor_position);
+
+ if mouse_over {
+ messages.push((self.on_toggle)(!self.is_active));
+
+ event::Status::Captured
+ } else {
+ event::Status::Ignored
+ }
+ }
+ _ => event::Status::Ignored,
+ }
+ }
+
+ fn draw(
+ &self,
+ renderer: &mut Renderer,
+ defaults: &Renderer::Defaults,
+ layout: Layout<'_>,
+ cursor_position: Point,
+ _viewport: &Rectangle,
+ ) -> Renderer::Output {
+ let bounds = layout.bounds();
+ let mut children = layout.children();
+
+ let label = match &self.label {
+ Some(label) => {
+ let label_layout = children.next().unwrap();
+
+ Some(text::Renderer::draw(
+ renderer,
+ defaults,
+ label_layout.bounds(),
+ &label,
+ self.text_size.unwrap_or(renderer.default_size()),
+ self.font,
+ None,
+ self.text_alignment,
+ VerticalAlignment::Center,
+ ))
+ }
+
+ None => None,
+ };
+
+ let toggler_layout = children.next().unwrap();
+ let toggler_bounds = toggler_layout.bounds();
+
+ let is_mouse_over = bounds.contains(cursor_position);
+
+ self::Renderer::draw(
+ renderer,
+ toggler_bounds,
+ self.is_active,
+ is_mouse_over,
+ label,
+ &self.style,
+ )
+ }
+
+ fn hash_layout(&self, state: &mut Hasher) {
+ struct Marker;
+ std::any::TypeId::of::<Marker>().hash(state);
+
+ self.label.hash(state)
+ }
+}
+
+/// The renderer of a [`Toggler`].
+///
+/// Your [renderer] will need to implement this trait before being
+/// able to use a [`Toggler`] in your user interface.
+///
+/// [renderer]: ../../renderer/index.html
+pub trait Renderer: crate::Renderer {
+ /// The style supported by this renderer.
+ type Style: Default;
+
+ /// The default size of a [`Toggler`].
+ const DEFAULT_SIZE: u16;
+
+ /// Draws a [`Toggler`].
+ ///
+ /// It receives:
+ /// * the bounds of the [`Toggler`]
+ /// * whether the [`Toggler`] is activated or not
+ /// * whether the mouse is over the [`Toggler`] or not
+ /// * the drawn label of the [`Toggler`]
+ /// * the style of the [`Toggler`]
+ fn draw(
+ &mut self,
+ bounds: Rectangle,
+ is_active: bool,
+ is_mouse_over: bool,
+ label: Option<Self::Output>,
+ style: &Self::Style,
+ ) -> Self::Output;
+}
+
+impl<'a, Message, Renderer> From<Toggler<Message, Renderer>>
+ for Element<'a, Message, Renderer>
+where
+ Renderer: 'a + self::Renderer + text::Renderer + row::Renderer,
+ Message: 'a,
+{
+ fn from(
+ toggler: Toggler<Message, Renderer>,
+ ) -> Element<'a, Message, Renderer> {
+ Element::new(toggler)
+ }
+}
diff --git a/native/src/widget/tooltip.rs b/native/src/widget/tooltip.rs
new file mode 100644
index 00000000..276afd41
--- /dev/null
+++ b/native/src/widget/tooltip.rs
@@ -0,0 +1,210 @@
+//! Display a widget over another.
+use std::hash::Hash;
+
+use iced_core::Rectangle;
+
+use crate::widget::container;
+use crate::widget::text::{self, Text};
+use crate::{
+ event, layout, Clipboard, Element, Event, Hasher, Layout, Length, Point,
+ Widget,
+};
+
+/// An element to display a widget over another.
+#[allow(missing_debug_implementations)]
+pub struct Tooltip<'a, Message, Renderer: self::Renderer> {
+ content: Element<'a, Message, Renderer>,
+ tooltip: Text<Renderer>,
+ position: Position,
+ style: <Renderer as container::Renderer>::Style,
+ gap: u16,
+ padding: u16,
+}
+
+impl<'a, Message, Renderer> Tooltip<'a, Message, Renderer>
+where
+ Renderer: self::Renderer,
+{
+ /// Creates an empty [`Tooltip`].
+ ///
+ /// [`Tooltip`]: struct.Tooltip.html
+ pub fn new(
+ content: impl Into<Element<'a, Message, Renderer>>,
+ tooltip: impl ToString,
+ position: Position,
+ ) -> Self {
+ Tooltip {
+ content: content.into(),
+ tooltip: Text::new(tooltip.to_string()),
+ position,
+ style: Default::default(),
+ gap: 0,
+ padding: Renderer::DEFAULT_PADDING,
+ }
+ }
+
+ /// Sets the size of the text of the [`Tooltip`].
+ pub fn size(mut self, size: u16) -> Self {
+ self.tooltip = self.tooltip.size(size);
+ self
+ }
+
+ /// Sets the font of the [`Tooltip`].
+ ///
+ /// [`Font`]: Renderer::Font
+ pub fn font(mut self, font: impl Into<Renderer::Font>) -> Self {
+ self.tooltip = self.tooltip.font(font);
+ self
+ }
+
+ /// Sets the gap between the content and its [`Tooltip`].
+ pub fn gap(mut self, gap: u16) -> Self {
+ self.gap = gap;
+ self
+ }
+
+ /// Sets the padding of the [`Tooltip`].
+ pub fn padding(mut self, padding: u16) -> Self {
+ self.padding = padding;
+ self
+ }
+
+ /// Sets the style of the [`Tooltip`].
+ pub fn style(
+ mut self,
+ style: impl Into<<Renderer as container::Renderer>::Style>,
+ ) -> Self {
+ self.style = style.into();
+ self
+ }
+}
+
+/// The position of the tooltip. Defaults to following the cursor.
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub enum Position {
+ /// The tooltip will follow the cursor.
+ FollowCursor,
+ /// The tooltip will appear on the top of the widget.
+ Top,
+ /// The tooltip will appear on the bottom of the widget.
+ Bottom,
+ /// The tooltip will appear on the left of the widget.
+ Left,
+ /// The tooltip will appear on the right of the widget.
+ Right,
+}
+
+impl<'a, Message, Renderer> Widget<Message, Renderer>
+ for Tooltip<'a, Message, Renderer>
+where
+ Renderer: self::Renderer,
+{
+ fn width(&self) -> Length {
+ self.content.width()
+ }
+
+ fn height(&self) -> Length {
+ self.content.height()
+ }
+
+ fn layout(
+ &self,
+ renderer: &Renderer,
+ limits: &layout::Limits,
+ ) -> layout::Node {
+ self.content.layout(renderer, limits)
+ }
+
+ fn on_event(
+ &mut self,
+ event: Event,
+ layout: Layout<'_>,
+ cursor_position: Point,
+ renderer: &Renderer,
+ clipboard: &mut dyn Clipboard,
+ messages: &mut Vec<Message>,
+ ) -> event::Status {
+ self.content.widget.on_event(
+ event,
+ layout,
+ cursor_position,
+ renderer,
+ clipboard,
+ messages,
+ )
+ }
+
+ fn draw(
+ &self,
+ renderer: &mut Renderer,
+ defaults: &Renderer::Defaults,
+ layout: Layout<'_>,
+ cursor_position: Point,
+ viewport: &Rectangle,
+ ) -> Renderer::Output {
+ self::Renderer::draw(
+ renderer,
+ defaults,
+ cursor_position,
+ layout,
+ viewport,
+ &self.content,
+ &self.tooltip,
+ self.position,
+ &self.style,
+ self.gap,
+ self.padding,
+ )
+ }
+
+ fn hash_layout(&self, state: &mut Hasher) {
+ struct Marker;
+ std::any::TypeId::of::<Marker>().hash(state);
+
+ self.content.hash_layout(state);
+ }
+}
+
+/// The renderer of a [`Tooltip`].
+///
+/// Your [renderer] will need to implement this trait before being
+/// able to use a [`Tooltip`] in your user interface.
+///
+/// [`Tooltip`]: struct.Tooltip.html
+/// [renderer]: ../../renderer/index.html
+pub trait Renderer:
+ crate::Renderer + text::Renderer + container::Renderer
+{
+ /// The default padding of a [`Tooltip`] drawn by this renderer.
+ const DEFAULT_PADDING: u16;
+
+ /// Draws a [`Tooltip`].
+ ///
+ /// [`Tooltip`]: struct.Tooltip.html
+ fn draw<Message>(
+ &mut self,
+ defaults: &Self::Defaults,
+ cursor_position: Point,
+ content_layout: Layout<'_>,
+ viewport: &Rectangle,
+ content: &Element<'_, Message, Self>,
+ tooltip: &Text<Self>,
+ position: Position,
+ style: &<Self as container::Renderer>::Style,
+ gap: u16,
+ padding: u16,
+ ) -> Self::Output;
+}
+
+impl<'a, Message, Renderer> From<Tooltip<'a, Message, Renderer>>
+ for Element<'a, Message, Renderer>
+where
+ Renderer: 'a + self::Renderer,
+ Message: 'a,
+{
+ fn from(
+ column: Tooltip<'a, Message, Renderer>,
+ ) -> Element<'a, Message, Renderer> {
+ Element::new(column)
+ }
+}
diff --git a/native/src/window/event.rs b/native/src/window/event.rs
index b177141a..64f2b8d8 100644
--- a/native/src/window/event.rs
+++ b/native/src/window/event.rs
@@ -3,7 +3,15 @@ use std::path::PathBuf;
/// A window-related event.
#[derive(PartialEq, Clone, Debug)]
pub enum Event {
- /// A window was resized
+ /// A window was moved.
+ Moved {
+ /// The new logical x location of the window
+ x: i32,
+ /// The new logical y location of the window
+ y: i32,
+ },
+
+ /// A window was resized.
Resized {
/// The new width of the window (in units)
width: u32,
@@ -12,6 +20,18 @@ pub enum Event {
height: u32,
},
+ /// The user has requested for the window to close.
+ ///
+ /// Usually, you will want to terminate the execution whenever this event
+ /// occurs.
+ CloseRequested,
+
+ /// A window was focused.
+ Focused,
+
+ /// A window was unfocused.
+ Unfocused,
+
/// A file is being hovered over the window.
///
/// When the user hovers multiple files at once, this event will be emitted
diff --git a/rustfmt.toml b/rustfmt.toml
index d979d317..fa50ab92 100644
--- a/rustfmt.toml
+++ b/rustfmt.toml
@@ -1 +1,2 @@
max_width=80
+edition="2018"
diff --git a/src/application.rs b/src/application.rs
index d46cd2ac..78280e98 100644
--- a/src/application.rs
+++ b/src/application.rs
@@ -1,5 +1,7 @@
use crate::window;
-use crate::{Color, Command, Element, Executor, Settings, Subscription};
+use crate::{
+ Clipboard, Color, Command, Element, Executor, Menu, Settings, Subscription,
+};
/// An interactive cross-platform application.
///
@@ -11,15 +13,13 @@ use crate::{Color, Command, Element, Executor, Settings, Subscription};
/// document.
///
/// An [`Application`] can execute asynchronous actions by returning a
-/// [`Command`](struct.Command.html) in some of its methods. If
-/// you do not intend to perform any background work in your program, the
-/// [`Sandbox`](trait.Sandbox.html) trait offers a simplified interface.
+/// [`Command`] in some of its methods. If you do not intend to perform any
+/// background work in your program, the [`Sandbox`] trait offers a simplified
+/// interface.
///
/// When using an [`Application`] with the `debug` feature enabled, a debug view
/// can be toggled by pressing `F12`.
///
-/// [`Application`]: trait.Application.html
-///
/// # Examples
/// [The repository has a bunch of examples] that use the [`Application`] trait:
///
@@ -29,6 +29,8 @@ use crate::{Color, Command, Element, Executor, Settings, Subscription};
/// a dummy file of 100 MB and tracks the download progress.
/// - [`events`], a log of native events displayed using a conditional
/// [`Subscription`].
+/// - [`game_of_life`], an interactive version of the [Game of Life], invented
+/// by [John Horton Conway].
/// - [`pokedex`], an application that displays a random Pokédex entry (sprite
/// included!) by using the [PokéAPI].
/// - [`solar_system`], an animated solar system drawn using the [`Canvas`] widget
@@ -37,17 +39,18 @@ use crate::{Color, Command, Element, Executor, Settings, Subscription};
/// to listen to time.
/// - [`todos`], a todos tracker inspired by [TodoMVC].
///
-/// [The repository has a bunch of examples]: https://github.com/hecrj/iced/tree/0.1/examples
-/// [`clock`]: https://github.com/hecrj/iced/tree/0.1/examples/clock
-/// [`download_progress`]: https://github.com/hecrj/iced/tree/0.1/examples/download_progress
-/// [`events`]: https://github.com/hecrj/iced/tree/0.1/examples/events
-/// [`pokedex`]: https://github.com/hecrj/iced/tree/0.1/examples/pokedex
-/// [`solar_system`]: https://github.com/hecrj/iced/tree/0.1/examples/solar_system
-/// [`stopwatch`]: https://github.com/hecrj/iced/tree/0.1/examples/stopwatch
-/// [`todos`]: https://github.com/hecrj/iced/tree/0.1/examples/todos
-/// [`Canvas`]: widget/canvas/struct.Canvas.html
+/// [The repository has a bunch of examples]: https://github.com/hecrj/iced/tree/0.3/examples
+/// [`clock`]: https://github.com/hecrj/iced/tree/0.3/examples/clock
+/// [`download_progress`]: https://github.com/hecrj/iced/tree/0.3/examples/download_progress
+/// [`events`]: https://github.com/hecrj/iced/tree/0.3/examples/events
+/// [`game_of_life`]: https://github.com/hecrj/iced/tree/0.3/examples/game_of_life
+/// [`pokedex`]: https://github.com/hecrj/iced/tree/0.3/examples/pokedex
+/// [`solar_system`]: https://github.com/hecrj/iced/tree/0.3/examples/solar_system
+/// [`stopwatch`]: https://github.com/hecrj/iced/tree/0.3/examples/stopwatch
+/// [`todos`]: https://github.com/hecrj/iced/tree/0.3/examples/todos
+/// [`Sandbox`]: crate::Sandbox
+/// [`Canvas`]: crate::widget::Canvas
/// [PokéAPI]: https://pokeapi.co/
-/// [`Subscription`]: type.Subscription.html
/// [TodoMVC]: http://todomvc.com/
///
/// ## A simple "Hello, world!"
@@ -56,7 +59,7 @@ use crate::{Color, Command, Element, Executor, Settings, Subscription};
/// says "Hello, world!":
///
/// ```no_run
-/// use iced::{executor, Application, Command, Element, Settings, Text};
+/// use iced::{executor, Application, Clipboard, Command, Element, Settings, Text};
///
/// pub fn main() -> iced::Result {
/// Hello::run(Settings::default())
@@ -65,7 +68,7 @@ use crate::{Color, Command, Element, Executor, Settings, Subscription};
/// struct Hello;
///
/// impl Application for Hello {
-/// type Executor = executor::Null;
+/// type Executor = executor::Default;
/// type Message = ();
/// type Flags = ();
///
@@ -77,7 +80,7 @@ use crate::{Color, Command, Element, Executor, Settings, Subscription};
/// String::from("A cool application")
/// }
///
-/// fn update(&mut self, _message: Self::Message) -> Command<Self::Message> {
+/// fn update(&mut self, _message: Self::Message, _clipboard: &mut Clipboard) -> Command<Self::Message> {
/// Command::none()
/// }
///
@@ -91,18 +94,14 @@ pub trait Application: Sized {
///
/// The [default executor] can be a good starting point!
///
- /// [`Executor`]: trait.Executor.html
- /// [default executor]: executor/struct.Default.html
+ /// [`Executor`]: Self::Executor
+ /// [default executor]: crate::executor::Default
type Executor: Executor;
/// The type of __messages__ your [`Application`] will produce.
- ///
- /// [`Application`]: trait.Application.html
- type Message: std::fmt::Debug + Send;
+ type Message: std::fmt::Debug + Clone + Send;
/// The data needed to initialize your [`Application`].
- ///
- /// [`Application`]: trait.Application.html
type Flags;
/// Initializes the [`Application`] with the flags provided to
@@ -110,22 +109,17 @@ pub trait Application: Sized {
///
/// Here is where you should return the initial state of your app.
///
- /// Additionally, you can return a [`Command`](struct.Command.html) if you
- /// need to perform some async action in the background on startup. This is
- /// useful if you want to load state from a file, perform an initial HTTP
- /// request, etc.
+ /// Additionally, you can return a [`Command`] if you need to perform some
+ /// async action in the background on startup. This is useful if you want to
+ /// load state from a file, perform an initial HTTP request, etc.
///
- /// [`Application`]: trait.Application.html
- /// [`run`]: #method.run.html
- /// [`Settings`]: struct.Settings.html
+ /// [`run`]: Self::run
fn new(flags: Self::Flags) -> (Self, Command<Self::Message>);
/// Returns the current title of the [`Application`].
///
/// This title can be dynamic! The runtime will automatically update the
/// title of your application when necessary.
- ///
- /// [`Application`]: trait.Application.html
fn title(&self) -> String;
/// Handles a __message__ and updates the state of the [`Application`].
@@ -135,10 +129,11 @@ pub trait Application: Sized {
/// this method.
///
/// Any [`Command`] returned will be executed immediately in the background.
- ///
- /// [`Application`]: trait.Application.html
- /// [`Command`]: struct.Command.html
- fn update(&mut self, message: Self::Message) -> Command<Self::Message>;
+ fn update(
+ &mut self,
+ message: Self::Message,
+ clipboard: &mut Clipboard,
+ ) -> Command<Self::Message>;
/// Returns the event [`Subscription`] for the current state of the
/// application.
@@ -148,8 +143,6 @@ pub trait Application: Sized {
/// [`update`](#tymethod.update).
///
/// By default, this method returns an empty [`Subscription`].
- ///
- /// [`Subscription`]: struct.Subscription.html
fn subscription(&self) -> Subscription<Self::Message> {
Subscription::none()
}
@@ -157,8 +150,6 @@ pub trait Application: Sized {
/// Returns the widgets to display in the [`Application`].
///
/// These widgets can produce __messages__ based on user interaction.
- ///
- /// [`Application`]: trait.Application.html
fn view(&mut self) -> Element<'_, Self::Message>;
/// Returns the current [`Application`] mode.
@@ -169,8 +160,6 @@ pub trait Application: Sized {
/// Currently, the mode only has an effect in native platforms.
///
/// By default, an application will run in windowed mode.
- ///
- /// [`Application`]: trait.Application.html
fn mode(&self) -> window::Mode {
window::Mode::Windowed
}
@@ -178,9 +167,6 @@ pub trait Application: Sized {
/// Returns the background color of the [`Application`].
///
/// By default, it returns [`Color::WHITE`].
- ///
- /// [`Application`]: trait.Application.html
- /// [`Color::WHITE`]: struct.Color.html#const.WHITE
fn background_color(&self) -> Color {
Color::WHITE
}
@@ -194,12 +180,24 @@ pub trait Application: Sized {
/// while a scale factor of `0.5` will shrink them to half their size.
///
/// By default, it returns `1.0`.
- ///
- /// [`Application`]: trait.Application.html
fn scale_factor(&self) -> f64 {
1.0
}
+ /// Returns whether the [`Application`] should be terminated.
+ ///
+ /// By default, it returns `false`.
+ fn should_exit(&self) -> bool {
+ false
+ }
+
+ /// Returns the current system [`Menu`] of the [`Application`].
+ ///
+ /// By default, it returns an empty [`Menu`].
+ fn menu(&self) -> Menu<Self::Message> {
+ Menu::new()
+ }
+
/// Runs the [`Application`].
///
/// On native platforms, this method will take control of the current thread
@@ -207,8 +205,7 @@ pub trait Application: Sized {
///
/// It should probably be that last thing you call in your `main` function.
///
- /// [`Application`]: trait.Application.html
- /// [`Error`]: enum.Error.html
+ /// [`Error`]: crate::Error
fn run(settings: Settings<Self::Flags>) -> crate::Result
where
Self: 'static,
@@ -218,12 +215,13 @@ pub trait Application: Sized {
let renderer_settings = crate::renderer::Settings {
default_font: settings.default_font,
default_text_size: settings.default_text_size,
+ text_multithreading: settings.text_multithreading,
antialiasing: if settings.antialiasing {
Some(crate::renderer::settings::Antialiasing::MSAAx4)
} else {
None
},
- ..crate::renderer::Settings::default()
+ ..crate::renderer::Settings::from_env()
};
Ok(crate::runtime::application::run::<
@@ -251,9 +249,14 @@ where
{
type Renderer = crate::renderer::Renderer;
type Message = A::Message;
-
- fn update(&mut self, message: Self::Message) -> Command<Self::Message> {
- self.0.update(message)
+ type Clipboard = iced_winit::Clipboard;
+
+ fn update(
+ &mut self,
+ message: Self::Message,
+ clipboard: &mut iced_winit::Clipboard,
+ ) -> Command<Self::Message> {
+ self.0.update(message, clipboard)
}
fn view(&mut self) -> Element<'_, Self::Message> {
@@ -282,6 +285,7 @@ where
match self.0.mode() {
window::Mode::Windowed => iced_winit::Mode::Windowed,
window::Mode::Fullscreen => iced_winit::Mode::Fullscreen,
+ window::Mode::Hidden => iced_winit::Mode::Hidden,
}
}
@@ -296,6 +300,14 @@ where
fn scale_factor(&self) -> f64 {
self.0.scale_factor()
}
+
+ fn should_exit(&self) -> bool {
+ self.0.should_exit()
+ }
+
+ fn menu(&self) -> Menu<Self::Message> {
+ self.0.menu()
+ }
}
#[cfg(target_arch = "wasm32")]
@@ -317,8 +329,12 @@ where
self.0.title()
}
- fn update(&mut self, message: Self::Message) -> Command<Self::Message> {
- self.0.update(message)
+ fn update(
+ &mut self,
+ message: Self::Message,
+ clipboard: &mut Clipboard,
+ ) -> Command<Self::Message> {
+ self.0.update(message, clipboard)
}
fn subscription(&self) -> Subscription<Self::Message> {
diff --git a/src/error.rs b/src/error.rs
index 31b87d17..c8fa6636 100644
--- a/src/error.rs
+++ b/src/error.rs
@@ -9,7 +9,7 @@ pub enum Error {
/// The application window could not be created.
#[error("the application window could not be created")]
- WindowCreationFailed(Box<dyn std::error::Error>),
+ WindowCreationFailed(Box<dyn std::error::Error + Send + Sync>),
/// A suitable graphics adapter or device could not be found.
#[error("a suitable graphics adapter or device could not be found")]
@@ -32,3 +32,14 @@ impl From<iced_winit::Error> for Error {
}
}
}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn assert_send_sync() {
+ fn _assert<T: Send + Sync>() {}
+ _assert::<Error>();
+ }
+}
diff --git a/src/executor.rs b/src/executor.rs
index 59d59a5a..9f3656b1 100644
--- a/src/executor.rs
+++ b/src/executor.rs
@@ -1,5 +1,5 @@
//! Choose your preferred executor to power your application.
-pub use crate::runtime::{executor::Null, Executor};
+pub use crate::runtime::Executor;
pub use platform::Default;
@@ -7,13 +7,34 @@ pub use platform::Default;
mod platform {
use iced_futures::{executor, futures};
- #[cfg(feature = "tokio")]
+ #[cfg(feature = "tokio_old")]
+ type Executor = executor::TokioOld;
+
+ #[cfg(all(feature = "tokio", not(feature = "tokio_old")))]
type Executor = executor::Tokio;
- #[cfg(all(not(feature = "tokio"), feature = "async-std"))]
+ #[cfg(all(
+ feature = "async-std",
+ not(any(feature = "tokio_old", feature = "tokio")),
+ ))]
type Executor = executor::AsyncStd;
- #[cfg(not(any(feature = "tokio", feature = "async-std")))]
+ #[cfg(all(
+ feature = "smol",
+ not(any(
+ feature = "tokio_old",
+ feature = "tokio",
+ feature = "async-std"
+ )),
+ ))]
+ type Executor = executor::Smol;
+
+ #[cfg(not(any(
+ feature = "tokio_old",
+ feature = "tokio",
+ feature = "async-std",
+ feature = "smol",
+ )))]
type Executor = executor::ThreadPool;
/// A default cross-platform executor.
@@ -40,7 +61,7 @@ mod platform {
}
fn enter<R>(&self, f: impl FnOnce() -> R) -> R {
- self.0.enter(f)
+ super::Executor::enter(&self.0, f)
}
}
}
diff --git a/src/keyboard.rs b/src/keyboard.rs
index 0b3e894d..2134a66b 100644
--- a/src/keyboard.rs
+++ b/src/keyboard.rs
@@ -1,2 +1,2 @@
//! Listen and react to keyboard events.
-pub use crate::runtime::keyboard::{Event, KeyCode, ModifiersState};
+pub use crate::runtime::keyboard::{Event, KeyCode, Modifiers};
diff --git a/src/lib.rs b/src/lib.rs
index 610683b3..8cd6aa70 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -30,7 +30,7 @@
//! [windowing shell]: https://github.com/hecrj/iced/tree/master/winit
//! [`dodrio`]: https://github.com/fitzgen/dodrio
//! [web runtime]: https://github.com/hecrj/iced/tree/master/web
-//! [examples]: https://github.com/hecrj/iced/tree/0.1/examples
+//! [examples]: https://github.com/hecrj/iced/tree/0.3/examples
//! [repository]: https://github.com/hecrj/iced
//!
//! # Overview
@@ -171,8 +171,6 @@
//!
//! [Elm]: https://elm-lang.org/
//! [The Elm Architecture]: https://guide.elm-lang.org/architecture/
-//! [`Application`]: trait.Application.html
-//! [`Sandbox`]: trait.Sandbox.html
#![deny(missing_docs)]
#![deny(missing_debug_implementations)]
#![deny(unused_results)]
@@ -193,10 +191,23 @@ pub mod widget;
pub mod window;
#[cfg(all(
- any(feature = "tokio", feature = "async-std"),
+ any(
+ feature = "tokio",
+ feature = "tokio_old",
+ feature = "async-std",
+ feature = "smol"
+ ),
not(target_arch = "wasm32")
))]
-#[cfg_attr(docsrs, doc(cfg(any(feature = "tokio", feature = "async-std"))))]
+#[cfg_attr(
+ docsrs,
+ doc(cfg(any(
+ feature = "tokio",
+ feature = "tokio_old",
+ feature = "async-std"
+ feature = "smol"
+ )))
+)]
pub mod time;
#[cfg(all(
@@ -234,6 +245,7 @@ pub use sandbox::Sandbox;
pub use settings::Settings;
pub use runtime::{
- futures, Align, Background, Color, Command, Font, HorizontalAlignment,
- Length, Point, Rectangle, Size, Subscription, Vector, VerticalAlignment,
+ futures, menu, Align, Background, Clipboard, Color, Command, Font,
+ HorizontalAlignment, Length, Menu, Point, Rectangle, Size, Subscription,
+ Vector, VerticalAlignment,
};
diff --git a/src/result.rs b/src/result.rs
index 2f05a6a9..ef565bd6 100644
--- a/src/result.rs
+++ b/src/result.rs
@@ -2,5 +2,5 @@ use crate::Error;
/// The result of running an [`Application`].
///
-/// [`Application`]: trait.Application.html
+/// [`Application`]: crate::Application
pub type Result = std::result::Result<(), Error>;
diff --git a/src/sandbox.rs b/src/sandbox.rs
index c72b58d8..cb3cf624 100644
--- a/src/sandbox.rs
+++ b/src/sandbox.rs
@@ -1,6 +1,6 @@
-use crate::executor;
use crate::{
- Application, Color, Command, Element, Error, Settings, Subscription,
+ Application, Clipboard, Color, Command, Element, Error, Settings,
+ Subscription,
};
/// A sandboxed [`Application`].
@@ -15,16 +15,11 @@ use crate::{
/// Therefore, it is recommended to always start by implementing this trait and
/// upgrade only once necessary.
///
-/// [`Application`]: trait.Application.html
-/// [`Sandbox`]: trait.Sandbox.html
-/// [`Command`]: struct.Command.html
-/// [`Command::none`]: struct.Command.html#method.none
-///
/// # Examples
/// [The repository has a bunch of examples] that use the [`Sandbox`] trait:
///
-/// - [`bezier_tool`], a Paint-like tool for drawing Bézier curves using
-/// [`lyon`].
+/// - [`bezier_tool`], a Paint-like tool for drawing Bézier curves using the
+/// [`Canvas widget`].
/// - [`counter`], the classic counter example explained in [the overview].
/// - [`custom_widget`], a demonstration of how to build a custom widget that
/// draws a circle.
@@ -41,20 +36,20 @@ use crate::{
/// - [`tour`], a simple UI tour that can run both on native platforms and the
/// web!
///
-/// [The repository has a bunch of examples]: https://github.com/hecrj/iced/tree/0.1/examples
-/// [`bezier_tool`]: https://github.com/hecrj/iced/tree/0.1/examples/bezier_tool
-/// [`counter`]: https://github.com/hecrj/iced/tree/0.1/examples/counter
-/// [`custom_widget`]: https://github.com/hecrj/iced/tree/0.1/examples/custom_widget
-/// [`geometry`]: https://github.com/hecrj/iced/tree/0.1/examples/geometry
-/// [`pane_grid`]: https://github.com/hecrj/iced/tree/0.1/examples/pane_grid
-/// [`progress_bar`]: https://github.com/hecrj/iced/tree/0.1/examples/progress_bar
-/// [`styling`]: https://github.com/hecrj/iced/tree/0.1/examples/styling
-/// [`svg`]: https://github.com/hecrj/iced/tree/0.1/examples/svg
-/// [`tour`]: https://github.com/hecrj/iced/tree/0.1/examples/tour
-/// [`lyon`]: https://github.com/nical/lyon
+/// [The repository has a bunch of examples]: https://github.com/hecrj/iced/tree/0.3/examples
+/// [`bezier_tool`]: https://github.com/hecrj/iced/tree/0.3/examples/bezier_tool
+/// [`counter`]: https://github.com/hecrj/iced/tree/0.3/examples/counter
+/// [`custom_widget`]: https://github.com/hecrj/iced/tree/0.3/examples/custom_widget
+/// [`geometry`]: https://github.com/hecrj/iced/tree/0.3/examples/geometry
+/// [`pane_grid`]: https://github.com/hecrj/iced/tree/0.3/examples/pane_grid
+/// [`progress_bar`]: https://github.com/hecrj/iced/tree/0.3/examples/progress_bar
+/// [`styling`]: https://github.com/hecrj/iced/tree/0.3/examples/styling
+/// [`svg`]: https://github.com/hecrj/iced/tree/0.3/examples/svg
+/// [`tour`]: https://github.com/hecrj/iced/tree/0.3/examples/tour
+/// [`Canvas widget`]: crate::widget::Canvas
/// [the overview]: index.html#overview
-/// [`iced_wgpu`]: https://github.com/hecrj/iced/tree/0.1/wgpu
-/// [`Svg` widget]: widget/svg/struct.Svg.html
+/// [`iced_wgpu`]: https://github.com/hecrj/iced/tree/0.3/wgpu
+/// [`Svg` widget]: crate::widget::Svg
/// [Ghostscript Tiger]: https://commons.wikimedia.org/wiki/File:Ghostscript_Tiger.svg
///
/// ## A simple "Hello, world!"
@@ -93,46 +88,33 @@ use crate::{
/// ```
pub trait Sandbox {
/// The type of __messages__ your [`Sandbox`] will produce.
- ///
- /// [`Sandbox`]: trait.Sandbox.html
- type Message: std::fmt::Debug + Send;
+ type Message: std::fmt::Debug + Clone + Send;
/// Initializes the [`Sandbox`].
///
/// Here is where you should return the initial state of your app.
- ///
- /// [`Sandbox`]: trait.Sandbox.html
fn new() -> Self;
/// Returns the current title of the [`Sandbox`].
///
/// This title can be dynamic! The runtime will automatically update the
/// title of your application when necessary.
- ///
- /// [`Sandbox`]: trait.Sandbox.html
fn title(&self) -> String;
/// Handles a __message__ and updates the state of the [`Sandbox`].
///
/// This is where you define your __update logic__. All the __messages__,
/// produced by user interactions, will be handled by this method.
- ///
- /// [`Sandbox`]: trait.Sandbox.html
fn update(&mut self, message: Self::Message);
/// Returns the widgets to display in the [`Sandbox`].
///
/// These widgets can produce __messages__ based on user interaction.
- ///
- /// [`Sandbox`]: trait.Sandbox.html
fn view(&mut self) -> Element<'_, Self::Message>;
/// Returns the background color of the [`Sandbox`].
///
/// By default, it returns [`Color::WHITE`].
- ///
- /// [`Sandbox`]: trait.Sandbox.html
- /// [`Color::WHITE`]: struct.Color.html#const.WHITE
fn background_color(&self) -> Color {
Color::WHITE
}
@@ -146,8 +128,6 @@ pub trait Sandbox {
/// while a scale factor of `0.5` will shrink them to half their size.
///
/// By default, it returns `1.0`.
- ///
- /// [`Sandbox`]: trait.Sandbox.html
fn scale_factor(&self) -> f64 {
1.0
}
@@ -158,8 +138,6 @@ pub trait Sandbox {
/// and __will NOT return__.
///
/// It should probably be that last thing you call in your `main` function.
- ///
- /// [`Sandbox`]: trait.Sandbox.html
fn run(settings: Settings<()>) -> Result<(), Error>
where
Self: 'static + Sized,
@@ -172,7 +150,7 @@ impl<T> Application for T
where
T: Sandbox,
{
- type Executor = executor::Null;
+ type Executor = crate::runtime::executor::Null;
type Flags = ();
type Message = T::Message;
@@ -184,7 +162,11 @@ where
T::title(self)
}
- fn update(&mut self, message: T::Message) -> Command<T::Message> {
+ fn update(
+ &mut self,
+ message: T::Message,
+ _clipboard: &mut Clipboard,
+ ) -> Command<T::Message> {
T::update(self, message);
Command::none()
diff --git a/src/settings.rs b/src/settings.rs
index 40b1b1ea..d726dc4f 100644
--- a/src/settings.rs
+++ b/src/settings.rs
@@ -4,16 +4,20 @@ use crate::window;
/// The settings of an application.
#[derive(Debug, Clone)]
pub struct Settings<Flags> {
+ /// The identifier of the application.
+ ///
+ /// If provided, this identifier may be used to identify the application or
+ /// communicate with it through the windowing system.
+ pub id: Option<String>,
+
/// The window settings.
///
/// They will be ignored on the Web.
- ///
- /// [`Window`]: struct.Window.html
pub window: window::Settings,
- /// The data needed to initialize an [`Application`].
+ /// The data needed to initialize the [`Application`].
///
- /// [`Application`]: ../trait.Application.html
+ /// [`Application`]: crate::Application
pub flags: Flags,
/// The bytes of the font that will be used by default.
@@ -27,6 +31,12 @@ pub struct Settings<Flags> {
/// The default value is 20.
pub default_text_size: u16,
+ /// If enabled, spread text workload in multiple threads when multiple cores
+ /// are available.
+ ///
+ /// By default, it is disabled.
+ pub text_multithreading: bool,
+
/// If set to true, the renderer will try to perform antialiasing for some
/// primitives.
///
@@ -35,23 +45,32 @@ pub struct Settings<Flags> {
///
/// By default, it is disabled.
///
- /// [`Canvas`]: ../widget/canvas/struct.Canvas.html
+ /// [`Canvas`]: crate::widget::Canvas
pub antialiasing: bool,
+
+ /// Whether the [`Application`] should exit when the user requests the
+ /// window to close (e.g. the user presses the close button).
+ ///
+ /// By default, it is enabled.
+ pub exit_on_close_request: bool,
}
impl<Flags> Settings<Flags> {
- /// Initialize application settings using the given data.
+ /// Initialize [`Application`] settings using the given data.
///
- /// [`Application`]: ../trait.Application.html
+ /// [`Application`]: crate::Application
pub fn with_flags(flags: Flags) -> Self {
let default_settings = Settings::<()>::default();
Self {
flags,
- antialiasing: default_settings.antialiasing,
+ id: default_settings.id,
+ window: default_settings.window,
default_font: default_settings.default_font,
default_text_size: default_settings.default_text_size,
- window: default_settings.window,
+ text_multithreading: default_settings.text_multithreading,
+ antialiasing: default_settings.antialiasing,
+ exit_on_close_request: default_settings.exit_on_close_request,
}
}
}
@@ -62,11 +81,14 @@ where
{
fn default() -> Self {
Self {
+ id: None,
+ window: Default::default(),
flags: Default::default(),
- antialiasing: Default::default(),
default_font: Default::default(),
default_text_size: 20,
- window: Default::default(),
+ text_multithreading: false,
+ antialiasing: false,
+ exit_on_close_request: true,
}
}
}
@@ -75,8 +97,10 @@ where
impl<Flags> From<Settings<Flags>> for iced_winit::Settings<Flags> {
fn from(settings: Settings<Flags>) -> iced_winit::Settings<Flags> {
iced_winit::Settings {
+ id: settings.id,
window: settings.window.into(),
flags: settings.flags,
+ exit_on_close_request: settings.exit_on_close_request,
}
}
}
diff --git a/src/time.rs b/src/time.rs
index cd442461..b8432895 100644
--- a/src/time.rs
+++ b/src/time.rs
@@ -5,8 +5,6 @@ use crate::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(
duration: std::time::Duration,
) -> Subscription<std::time::Instant> {
diff --git a/src/widget.rs b/src/widget.rs
index e8fff9cc..db052106 100644
--- a/src/widget.rs
+++ b/src/widget.rs
@@ -13,14 +13,12 @@
//!
//! These widgets have their own module with a `State` type. For instance, a
//! [`TextInput`] has some [`text_input::State`].
-//!
-//! [`TextInput`]: text_input/struct.TextInput.html
-//! [`text_input::State`]: text_input/struct.State.html
#[cfg(not(target_arch = "wasm32"))]
mod platform {
pub use crate::renderer::widget::{
button, checkbox, container, pane_grid, pick_list, progress_bar, radio,
- rule, scrollable, slider, text_input, Column, Row, Space, Text,
+ rule, scrollable, slider, text_input, toggler, tooltip, Column, Row,
+ Space, Text,
};
#[cfg(any(feature = "canvas", feature = "glow_canvas"))]
@@ -30,10 +28,18 @@ mod platform {
)]
pub use crate::renderer::widget::canvas;
+ #[cfg(any(feature = "qr_code", feature = "glow_qr_code"))]
+ #[cfg_attr(
+ docsrs,
+ doc(cfg(any(feature = "qr_code", feature = "glow_qr_code")))
+ )]
+ pub use crate::renderer::widget::qr_code;
+
#[cfg_attr(docsrs, doc(cfg(feature = "image")))]
pub mod image {
//! Display images in your user interface.
- pub use crate::runtime::image::{Handle, Image};
+ pub use crate::runtime::image::viewer;
+ pub use crate::runtime::image::{Handle, Image, Viewer};
}
#[cfg_attr(docsrs, doc(cfg(feature = "svg")))]
@@ -47,12 +53,16 @@ mod platform {
button::Button, checkbox::Checkbox, container::Container, image::Image,
pane_grid::PaneGrid, pick_list::PickList, progress_bar::ProgressBar,
radio::Radio, rule::Rule, scrollable::Scrollable, slider::Slider,
- svg::Svg, text_input::TextInput,
+ svg::Svg, text_input::TextInput, toggler::Toggler, tooltip::Tooltip,
};
#[cfg(any(feature = "canvas", feature = "glow_canvas"))]
#[doc(no_inline)]
pub use canvas::Canvas;
+
+ #[cfg(any(feature = "qr_code", feature = "glow_qr_code"))]
+ #[doc(no_inline)]
+ pub use qr_code::QRCode;
}
#[cfg(target_arch = "wasm32")]
diff --git a/src/window.rs b/src/window.rs
index a2883b62..7d441062 100644
--- a/src/window.rs
+++ b/src/window.rs
@@ -1,9 +1,11 @@
//! Configure the window of your application in native platforms.
mod mode;
+mod position;
mod settings;
pub mod icon;
pub use icon::Icon;
pub use mode::Mode;
+pub use position::Position;
pub use settings::Settings;
diff --git a/src/window/icon.rs b/src/window/icon.rs
index 15e0312d..287538b1 100644
--- a/src/window/icon.rs
+++ b/src/window/icon.rs
@@ -97,23 +97,25 @@ impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Error::InvalidData { byte_count } => {
- write!(f,
- "The provided RGBA data (with length {:?}) isn't divisble by \
+ write!(
+ f,
+ "The provided RGBA data (with length {:?}) isn't divisble by \
4. Therefore, it cannot be safely interpreted as 32bpp RGBA \
pixels.",
- byte_count,
- )
+ byte_count,
+ )
}
Error::DimensionsMismatch {
width,
height,
pixel_count,
} => {
- write!(f,
- "The number of RGBA pixels ({:?}) does not match the provided \
+ write!(
+ f,
+ "The number of RGBA pixels ({:?}) does not match the provided \
dimensions ({:?}x{:?}).",
- width, height, pixel_count,
- )
+ pixel_count, width, height,
+ )
}
Error::OsError(e) => write!(
f,
diff --git a/src/window/mode.rs b/src/window/mode.rs
index 37464711..fdce8e23 100644
--- a/src/window/mode.rs
+++ b/src/window/mode.rs
@@ -6,4 +6,7 @@ pub enum Mode {
/// The application takes the whole screen of its current monitor.
Fullscreen,
+
+ /// The application is hidden
+ Hidden,
}
diff --git a/src/window/position.rs b/src/window/position.rs
new file mode 100644
index 00000000..8535ef6a
--- /dev/null
+++ b/src/window/position.rs
@@ -0,0 +1,33 @@
+/// The position of a window in a given screen.
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub enum Position {
+ /// The platform-specific default position for a new window.
+ Default,
+ /// The window is completely centered on the screen.
+ Centered,
+ /// The window is positioned with specific coordinates: `(X, Y)`.
+ ///
+ /// When the decorations of the window are enabled, Windows 10 will add some
+ /// invisible padding to the window. This padding gets included in the
+ /// position. So if you have decorations enabled and want the window to be
+ /// at (0, 0) you would have to set the position to
+ /// `(PADDING_X, PADDING_Y)`.
+ Specific(i32, i32),
+}
+
+impl Default for Position {
+ fn default() -> Self {
+ Self::Default
+ }
+}
+
+#[cfg(not(target_arch = "wasm32"))]
+impl From<Position> for iced_winit::Position {
+ fn from(position: Position) -> Self {
+ match position {
+ Position::Default => Self::Default,
+ Position::Centered => Self::Centered,
+ Position::Specific(x, y) => Self::Specific(x, y),
+ }
+ }
+}
diff --git a/src/window/settings.rs b/src/window/settings.rs
index 7bc49ce1..ec6c3071 100644
--- a/src/window/settings.rs
+++ b/src/window/settings.rs
@@ -1,4 +1,4 @@
-use crate::window::Icon;
+use crate::window::{Icon, Position};
/// The window settings of an application.
#[derive(Debug, Clone)]
@@ -6,6 +6,9 @@ pub struct Settings {
/// The initial size of the window.
pub size: (u32, u32),
+ /// The initial position of the window.
+ pub position: Position,
+
/// The minimum size of the window.
pub min_size: Option<(u32, u32)>,
@@ -18,9 +21,12 @@ pub struct Settings {
/// Whether the window should have a border, a title bar, etc. or not.
pub decorations: bool,
- /// Whether the window should be transparent
+ /// Whether the window should be transparent.
pub transparent: bool,
+ /// Whether the window will always be on top of other windows.
+ pub always_on_top: bool,
+
/// The icon of the window.
pub icon: Option<Icon>,
}
@@ -29,11 +35,13 @@ impl Default for Settings {
fn default() -> Settings {
Settings {
size: (1024, 768),
+ position: Position::default(),
min_size: None,
max_size: None,
resizable: true,
decorations: true,
transparent: false,
+ always_on_top: false,
icon: None,
}
}
@@ -44,11 +52,13 @@ impl From<Settings> for iced_winit::settings::Window {
fn from(settings: Settings) -> Self {
Self {
size: settings.size,
+ position: iced_winit::Position::from(settings.position),
min_size: settings.min_size,
max_size: settings.max_size,
resizable: settings.resizable,
decorations: settings.decorations,
transparent: settings.transparent,
+ always_on_top: settings.always_on_top,
icon: settings.icon.map(Icon::into),
platform_specific: Default::default(),
}
diff --git a/style/Cargo.toml b/style/Cargo.toml
index abc64c0f..a3086477 100644
--- a/style/Cargo.toml
+++ b/style/Cargo.toml
@@ -1,6 +1,6 @@
[package]
name = "iced_style"
-version = "0.1.0"
+version = "0.3.0"
authors = ["Héctor Ramón Jiménez <hector0193@gmail.com>"]
edition = "2018"
description = "The default set of styles of Iced"
@@ -10,5 +10,6 @@ documentation = "https://docs.rs/iced_style"
keywords = ["gui", "ui", "graphics", "interface", "widgets"]
categories = ["gui"]
-[dependencies]
-iced_core = { version = "0.2", path = "../core" }
+[dependencies.iced_core]
+version = "0.4"
+path = "../core"
diff --git a/style/src/button.rs b/style/src/button.rs
index 1e3844f9..2281e32f 100644
--- a/style/src/button.rs
+++ b/style/src/button.rs
@@ -2,12 +2,12 @@
use iced_core::{Background, Color, Vector};
/// The appearance of a button.
-#[derive(Debug)]
+#[derive(Debug, Clone, Copy)]
pub struct Style {
pub shadow_offset: Vector,
pub background: Option<Background>,
- pub border_radius: u16,
- pub border_width: u16,
+ pub border_radius: f32,
+ pub border_width: f32,
pub border_color: Color,
pub text_color: Color,
}
@@ -17,8 +17,8 @@ impl std::default::Default for Style {
Self {
shadow_offset: Vector::default(),
background: None,
- border_radius: 0,
- border_width: 0,
+ border_radius: 0.0,
+ border_width: 0.0,
border_color: Color::TRANSPARENT,
text_color: Color::BLACK,
}
@@ -72,8 +72,8 @@ impl StyleSheet for Default {
Style {
shadow_offset: Vector::new(0.0, 0.0),
background: Some(Background::Color([0.87, 0.87, 0.87].into())),
- border_radius: 2,
- border_width: 1,
+ border_radius: 2.0,
+ border_width: 1.0,
border_color: [0.7, 0.7, 0.7].into(),
text_color: Color::BLACK,
}
diff --git a/style/src/checkbox.rs b/style/src/checkbox.rs
index 3c645f15..566136bb 100644
--- a/style/src/checkbox.rs
+++ b/style/src/checkbox.rs
@@ -2,12 +2,12 @@
use iced_core::{Background, Color};
/// The appearance of a checkbox.
-#[derive(Debug)]
+#[derive(Debug, Clone, Copy)]
pub struct Style {
pub background: Background,
pub checkmark_color: Color,
- pub border_radius: u16,
- pub border_width: u16,
+ pub border_radius: f32,
+ pub border_width: f32,
pub border_color: Color,
}
@@ -25,8 +25,8 @@ impl StyleSheet for Default {
Style {
background: Background::Color(Color::from_rgb(0.95, 0.95, 0.95)),
checkmark_color: Color::from_rgb(0.3, 0.3, 0.3),
- border_radius: 5,
- border_width: 1,
+ border_radius: 5.0,
+ border_width: 1.0,
border_color: Color::from_rgb(0.6, 0.6, 0.6),
}
}
diff --git a/style/src/container.rs b/style/src/container.rs
index d2247342..1ce6a7ca 100644
--- a/style/src/container.rs
+++ b/style/src/container.rs
@@ -6,8 +6,8 @@ use iced_core::{Background, Color};
pub struct Style {
pub text_color: Option<Color>,
pub background: Option<Background>,
- pub border_radius: u16,
- pub border_width: u16,
+ pub border_radius: f32,
+ pub border_width: f32,
pub border_color: Color,
}
@@ -16,8 +16,8 @@ impl std::default::Default for Style {
Self {
text_color: None,
background: None,
- border_radius: 0,
- border_width: 0,
+ border_radius: 0.0,
+ border_width: 0.0,
border_color: Color::TRANSPARENT,
}
}
@@ -36,8 +36,8 @@ impl StyleSheet for Default {
Style {
text_color: None,
background: None,
- border_radius: 0,
- border_width: 0,
+ border_radius: 0.0,
+ border_width: 0.0,
border_color: Color::TRANSPARENT,
}
}
diff --git a/style/src/lib.rs b/style/src/lib.rs
index 3d23d990..08d9f044 100644
--- a/style/src/lib.rs
+++ b/style/src/lib.rs
@@ -2,12 +2,15 @@
//!
//! It contains a set of styles and stylesheets for most of the built-in
//! widgets.
+//!
+//! ![The foundations of the Iced ecosystem](https://github.com/hecrj/iced/blob/0525d76ff94e828b7b21634fa94a747022001c83/docs/graphs/foundations.png?raw=true)
pub use iced_core::{Background, Color};
pub mod button;
pub mod checkbox;
pub mod container;
pub mod menu;
+pub mod pane_grid;
pub mod pick_list;
pub mod progress_bar;
pub mod radio;
@@ -15,3 +18,4 @@ pub mod rule;
pub mod scrollable;
pub mod slider;
pub mod text_input;
+pub mod toggler;
diff --git a/style/src/menu.rs b/style/src/menu.rs
index e8321dc7..90985b8f 100644
--- a/style/src/menu.rs
+++ b/style/src/menu.rs
@@ -5,7 +5,7 @@ use iced_core::{Background, Color};
pub struct Style {
pub text_color: Color,
pub background: Background,
- pub border_width: u16,
+ pub border_width: f32,
pub border_color: Color,
pub selected_text_color: Color,
pub selected_background: Background,
@@ -16,7 +16,7 @@ impl std::default::Default for Style {
Self {
text_color: Color::BLACK,
background: Background::Color([0.87, 0.87, 0.87].into()),
- border_width: 1,
+ border_width: 1.0,
border_color: [0.7, 0.7, 0.7].into(),
selected_text_color: Color::WHITE,
selected_background: Background::Color([0.4, 0.4, 1.0].into()),
diff --git a/style/src/pane_grid.rs b/style/src/pane_grid.rs
new file mode 100644
index 00000000..e39ee797
--- /dev/null
+++ b/style/src/pane_grid.rs
@@ -0,0 +1,51 @@
+//! Let your users split regions of your application and organize layout
+//! dynamically.
+use iced_core::Color;
+
+/// A set of rules that dictate the style of a container.
+pub trait StyleSheet {
+ /// The [`Line`] to draw when a split is picked.
+ fn picked_split(&self) -> Option<Line>;
+
+ /// The [`Line`] to draw when a split is hovered.
+ fn hovered_split(&self) -> Option<Line>;
+}
+
+/// A line.
+///
+/// It is normally used to define the highlight of something, like a split.
+#[derive(Debug, Clone, Copy, PartialEq)]
+pub struct Line {
+ /// The [`Color`] of the [`Line`].
+ pub color: Color,
+
+ /// The width of the [`Line`].
+ pub width: f32,
+}
+
+struct Default;
+
+impl StyleSheet for Default {
+ fn picked_split(&self) -> Option<Line> {
+ None
+ }
+
+ fn hovered_split(&self) -> Option<Line> {
+ None
+ }
+}
+
+impl std::default::Default for Box<dyn StyleSheet> {
+ fn default() -> Self {
+ Box::new(Default)
+ }
+}
+
+impl<T> From<T> for Box<dyn StyleSheet>
+where
+ T: 'static + StyleSheet,
+{
+ fn from(style: T) -> Self {
+ Box::new(style)
+ }
+}
diff --git a/style/src/pick_list.rs b/style/src/pick_list.rs
index fbd431c0..d1801e5f 100644
--- a/style/src/pick_list.rs
+++ b/style/src/pick_list.rs
@@ -5,9 +5,10 @@ use iced_core::{Background, Color};
#[derive(Debug, Clone, Copy)]
pub struct Style {
pub text_color: Color,
+ pub placeholder_color: Color,
pub background: Background,
- pub border_radius: u16,
- pub border_width: u16,
+ pub border_radius: f32,
+ pub border_width: f32,
pub border_color: Color,
pub icon_size: f32,
}
@@ -16,9 +17,10 @@ impl std::default::Default for Style {
fn default() -> Self {
Self {
text_color: Color::BLACK,
+ placeholder_color: [0.4, 0.4, 0.4].into(),
background: Background::Color([0.87, 0.87, 0.87].into()),
- border_radius: 0,
- border_width: 1,
+ border_radius: 0.0,
+ border_width: 1.0,
border_color: [0.7, 0.7, 0.7].into(),
icon_size: 0.7,
}
diff --git a/style/src/progress_bar.rs b/style/src/progress_bar.rs
index 73503fa8..d0878c84 100644
--- a/style/src/progress_bar.rs
+++ b/style/src/progress_bar.rs
@@ -2,11 +2,11 @@
use iced_core::{Background, Color};
/// The appearance of a progress bar.
-#[derive(Debug)]
+#[derive(Debug, Clone, Copy)]
pub struct Style {
pub background: Background,
pub bar: Background,
- pub border_radius: u16,
+ pub border_radius: f32,
}
/// A set of rules that dictate the style of a progress bar.
@@ -21,7 +21,7 @@ impl StyleSheet for Default {
Style {
background: Background::Color(Color::from_rgb(0.6, 0.6, 0.6)),
bar: Background::Color(Color::from_rgb(0.3, 0.9, 0.3)),
- border_radius: 5,
+ border_radius: 5.0,
}
}
}
diff --git a/style/src/radio.rs b/style/src/radio.rs
index 1f0689b9..c41b70c0 100644
--- a/style/src/radio.rs
+++ b/style/src/radio.rs
@@ -2,11 +2,11 @@
use iced_core::{Background, Color};
/// The appearance of a radio button.
-#[derive(Debug)]
+#[derive(Debug, Clone, Copy)]
pub struct Style {
pub background: Background,
pub dot_color: Color,
- pub border_width: u16,
+ pub border_width: f32,
pub border_color: Color,
}
@@ -24,7 +24,7 @@ impl StyleSheet for Default {
Style {
background: Background::Color(Color::from_rgb(0.95, 0.95, 0.95)),
dot_color: Color::from_rgb(0.3, 0.3, 0.3),
- border_width: 1,
+ border_width: 1.0,
border_color: Color::from_rgb(0.6, 0.6, 0.6),
}
}
diff --git a/style/src/rule.rs b/style/src/rule.rs
index 6ba54e33..be4c86d1 100644
--- a/style/src/rule.rs
+++ b/style/src/rule.rs
@@ -74,13 +74,22 @@ pub struct Style {
/// The width (thickness) of the rule line.
pub width: u16,
/// The radius of the line corners.
- pub radius: u16,
+ pub radius: f32,
/// The [`FillMode`] of the rule.
- ///
- /// [`FillMode`]: enum.FillMode.html
pub fill_mode: FillMode,
}
+impl std::default::Default for Style {
+ fn default() -> Self {
+ Style {
+ color: [0.6, 0.6, 0.6, 0.51].into(),
+ width: 1,
+ radius: 0.0,
+ fill_mode: FillMode::Percent(90.0),
+ }
+ }
+}
+
/// A set of rules that dictate the style of a rule.
pub trait StyleSheet {
/// Produces the style of a rule.
@@ -91,12 +100,7 @@ struct Default;
impl StyleSheet for Default {
fn style(&self) -> Style {
- Style {
- color: [0.6, 0.6, 0.6, 0.51].into(),
- width: 1,
- radius: 0,
- fill_mode: FillMode::Percent(90.0),
- }
+ Style::default()
}
}
diff --git a/style/src/scrollable.rs b/style/src/scrollable.rs
index 690c14a2..65da9803 100644
--- a/style/src/scrollable.rs
+++ b/style/src/scrollable.rs
@@ -5,8 +5,8 @@ use iced_core::{Background, Color};
#[derive(Debug, Clone, Copy)]
pub struct Scrollbar {
pub background: Option<Background>,
- pub border_radius: u16,
- pub border_width: u16,
+ pub border_radius: f32,
+ pub border_width: f32,
pub border_color: Color,
pub scroller: Scroller,
}
@@ -15,8 +15,8 @@ pub struct Scrollbar {
#[derive(Debug, Clone, Copy)]
pub struct Scroller {
pub color: Color,
- pub border_radius: u16,
- pub border_width: u16,
+ pub border_radius: f32,
+ pub border_width: f32,
pub border_color: Color,
}
@@ -40,13 +40,13 @@ impl StyleSheet for Default {
fn active(&self) -> Scrollbar {
Scrollbar {
background: None,
- border_radius: 5,
- border_width: 0,
+ border_radius: 5.0,
+ border_width: 0.0,
border_color: Color::TRANSPARENT,
scroller: Scroller {
color: [0.0, 0.0, 0.0, 0.7].into(),
- border_radius: 5,
- border_width: 0,
+ border_radius: 5.0,
+ border_width: 0.0,
border_color: Color::TRANSPARENT,
},
}
diff --git a/style/src/slider.rs b/style/src/slider.rs
index 776e180c..9148fcbe 100644
--- a/style/src/slider.rs
+++ b/style/src/slider.rs
@@ -13,15 +13,15 @@ pub struct Style {
pub struct Handle {
pub shape: HandleShape,
pub color: Color,
- pub border_width: u16,
+ pub border_width: f32,
pub border_color: Color,
}
/// The shape of the handle of a slider.
#[derive(Debug, Clone, Copy)]
pub enum HandleShape {
- Circle { radius: u16 },
- Rectangle { width: u16, border_radius: u16 },
+ Circle { radius: f32 },
+ Rectangle { width: u16, border_radius: f32 },
}
/// A set of rules that dictate the style of a slider.
@@ -45,11 +45,11 @@ impl StyleSheet for Default {
handle: Handle {
shape: HandleShape::Rectangle {
width: 8,
- border_radius: 4,
+ border_radius: 4.0,
},
color: Color::from_rgb(0.95, 0.95, 0.95),
border_color: Color::from_rgb(0.6, 0.6, 0.6),
- border_width: 1,
+ border_width: 1.0,
},
}
}
diff --git a/style/src/text_input.rs b/style/src/text_input.rs
index 1cb72364..19acea65 100644
--- a/style/src/text_input.rs
+++ b/style/src/text_input.rs
@@ -5,8 +5,8 @@ use iced_core::{Background, Color};
#[derive(Debug, Clone, Copy)]
pub struct Style {
pub background: Background,
- pub border_radius: u16,
- pub border_width: u16,
+ pub border_radius: f32,
+ pub border_width: f32,
pub border_color: Color,
}
@@ -14,8 +14,8 @@ impl std::default::Default for Style {
fn default() -> Self {
Self {
background: Background::Color(Color::WHITE),
- border_radius: 0,
- border_width: 0,
+ border_radius: 0.0,
+ border_width: 0.0,
border_color: Color::TRANSPARENT,
}
}
@@ -47,8 +47,8 @@ impl StyleSheet for Default {
fn active(&self) -> Style {
Style {
background: Background::Color(Color::WHITE),
- border_radius: 5,
- border_width: 1,
+ border_radius: 5.0,
+ border_width: 1.0,
border_color: Color::from_rgb(0.7, 0.7, 0.7),
}
}
diff --git a/style/src/toggler.rs b/style/src/toggler.rs
new file mode 100644
index 00000000..5a155123
--- /dev/null
+++ b/style/src/toggler.rs
@@ -0,0 +1,57 @@
+//! Show toggle controls using togglers.
+use iced_core::Color;
+
+/// The appearance of a toggler.
+#[derive(Debug)]
+pub struct Style {
+ pub background: Color,
+ pub background_border: Option<Color>,
+ pub foreground: Color,
+ pub foreground_border: Option<Color>,
+}
+
+/// A set of rules that dictate the style of a toggler.
+pub trait StyleSheet {
+ fn active(&self, is_active: bool) -> Style;
+
+ fn hovered(&self, is_active: bool) -> Style;
+}
+
+struct Default;
+
+impl StyleSheet for Default {
+ fn active(&self, is_active: bool) -> Style {
+ Style {
+ background: if is_active {
+ Color::from_rgb(0.0, 1.0, 0.0)
+ } else {
+ Color::from_rgb(0.7, 0.7, 0.7)
+ },
+ background_border: None,
+ foreground: Color::WHITE,
+ foreground_border: None,
+ }
+ }
+
+ fn hovered(&self, is_active: bool) -> Style {
+ Style {
+ foreground: Color::from_rgb(0.95, 0.95, 0.95),
+ ..self.active(is_active)
+ }
+ }
+}
+
+impl std::default::Default for Box<dyn StyleSheet> {
+ fn default() -> Self {
+ Box::new(Default)
+ }
+}
+
+impl<T> From<T> for Box<dyn StyleSheet>
+where
+ T: 'static + StyleSheet,
+{
+ fn from(style: T) -> Self {
+ Box::new(style)
+ }
+}
diff --git a/web/Cargo.toml b/web/Cargo.toml
index e03d2b63..06573b95 100644
--- a/web/Cargo.toml
+++ b/web/Cargo.toml
@@ -1,6 +1,6 @@
[package]
name = "iced_web"
-version = "0.2.1"
+version = "0.4.0"
authors = ["Héctor Ramón Jiménez <hector0193@gmail.com>"]
edition = "2018"
description = "A web backend for Iced"
@@ -22,15 +22,15 @@ url = "2.0"
num-traits = "0.2"
[dependencies.iced_core]
-version = "0.2"
+version = "0.4"
path = "../core"
[dependencies.iced_futures]
-version = "0.1"
+version = "0.3"
path = "../futures"
[dependencies.iced_style]
-version = "0.1"
+version = "0.3"
path = "../style"
[dependencies.web-sys]
diff --git a/web/README.md b/web/README.md
index 810bd1ec..58ad8235 100644
--- a/web/README.md
+++ b/web/README.md
@@ -8,8 +8,6 @@
The crate is currently a __very experimental__, simple abstraction layer over [`dodrio`].
-![iced_core](../docs/graphs/web.png)
-
[documentation]: https://docs.rs/iced_web
[`iced_core`]: ../core
[`dodrio`]: https://github.com/fitzgen/dodrio
@@ -18,7 +16,7 @@ The crate is currently a __very experimental__, simple abstraction layer over [`
Add `iced_web` as a dependency in your `Cargo.toml`:
```toml
-iced_web = "0.2"
+iced_web = "0.4"
```
__Iced moves fast and the `master` branch can contain breaking changes!__ If
diff --git a/web/src/bus.rs b/web/src/bus.rs
index c66e9659..5ce8e810 100644
--- a/web/src/bus.rs
+++ b/web/src/bus.rs
@@ -5,7 +5,7 @@ use std::rc::Rc;
///
/// It can be used to route messages back to the [`Application`].
///
-/// [`Application`]: trait.Application.html
+/// [`Application`]: crate::Application
#[allow(missing_debug_implementations)]
pub struct Bus<Message> {
publish: Rc<Box<dyn Fn(Message) -> ()>>,
@@ -33,15 +33,13 @@ where
/// Publishes a new message for the [`Application`].
///
- /// [`Application`]: trait.Application.html
+ /// [`Application`]: crate::Application
pub fn publish(&self, message: Message) {
(self.publish)(message)
}
/// Creates a new [`Bus`] that applies the given function to the messages
/// before publishing.
- ///
- /// [`Bus`]: struct.Bus.html
pub fn map<B>(&self, mapper: Rc<Box<dyn Fn(B) -> Message>>) -> Bus<B>
where
B: 'static,
diff --git a/web/src/clipboard.rs b/web/src/clipboard.rs
new file mode 100644
index 00000000..167a1e53
--- /dev/null
+++ b/web/src/clipboard.rs
@@ -0,0 +1,21 @@
+/// A buffer for short-term storage and transfer within and between
+/// applications.
+#[derive(Debug, Clone, Copy)]
+pub struct Clipboard;
+
+impl Clipboard {
+ /// Creates a new [`Clipboard`].
+ pub fn new() -> Self {
+ Self
+ }
+
+ /// Reads the current content of the [`Clipboard`] as text.
+ pub fn read(&self) -> Option<String> {
+ unimplemented! {}
+ }
+
+ /// Writes the given text contents to the [`Clipboard`].
+ pub fn write(&mut self, _contents: String) {
+ unimplemented! {}
+ }
+}
diff --git a/web/src/css.rs b/web/src/css.rs
index 6a307770..21f51f85 100644
--- a/web/src/css.rs
+++ b/web/src/css.rs
@@ -1,5 +1,5 @@
//! Style your widgets.
-use crate::{bumpalo, Align, Background, Color, Length};
+use crate::{bumpalo, Align, Background, Color, Length, Padding};
use std::collections::BTreeMap;
@@ -12,29 +12,25 @@ pub enum Rule {
/// Container with horizonal distribution
Row,
- /// Padding of the container
- Padding(u16),
-
/// Spacing between elements
Spacing(u16),
+
+ /// Toggler input for a specific size
+ Toggler(u16),
}
impl Rule {
- /// Returns the class name of the [`Style`].
- ///
- /// [`Style`]: enum.Style.html
+ /// Returns the class name of the [`Rule`].
pub fn class<'a>(&self) -> String {
match self {
Rule::Column => String::from("c"),
Rule::Row => String::from("r"),
- Rule::Padding(padding) => format!("p-{}", padding),
Rule::Spacing(spacing) => format!("s-{}", spacing),
+ Rule::Toggler(size) => format!("toggler-{}", size),
}
}
- /// Returns the declaration of the [`Style`].
- ///
- /// [`Style`]: enum.Style.html
+ /// Returns the declaration of the [`Rule`].
pub fn declaration<'a>(&self, bump: &'a bumpalo::Bump) -> &'a str {
let class = self.class();
@@ -49,13 +45,6 @@ impl Rule {
bumpalo::format!(in bump, ".{} {}", class, body).into_bump_str()
}
- Rule::Padding(padding) => bumpalo::format!(
- in bump,
- ".{} {{ box-sizing: border-box; padding: {}px }}",
- class,
- padding
- )
- .into_bump_str(),
Rule::Spacing(spacing) => bumpalo::format!(
in bump,
".c.{} > * {{ margin-bottom: {}px }} \
@@ -70,6 +59,46 @@ impl Rule {
class
)
.into_bump_str(),
+ Rule::Toggler(size) => bumpalo::format!(
+ in bump,
+ ".toggler-{} {{ display: flex; cursor: pointer; justify-content: space-between; }} \
+ .toggler-{} input {{ display:none; }} \
+ .toggler-{} span {{ background-color: #b1b1b1; position: relative; display: inline-flex; width:{}px; height: {}px; border-radius: {}px;}} \
+ .toggler-{} span > span {{ background-color: #FFFFFF; width: {}px; height: {}px; border-radius: 50%; top: 1px; left: 1px;}} \
+ .toggler-{}:hover span > span {{ background-color: #f1f1f1 !important; }} \
+ .toggler-{} input:checked + span {{ background-color: #00FF00; }} \
+ .toggler-{} input:checked + span > span {{ -webkit-transform: translateX({}px); -ms-transform:translateX({}px); transform: translateX({}px); }}
+ ",
+ // toggler
+ size,
+
+ // toggler input
+ size,
+
+ // toggler span
+ size,
+ size*2,
+ size,
+ size,
+
+ // toggler span > span
+ size,
+ size-2,
+ size-2,
+
+ // toggler: hover + span > span
+ size,
+
+ // toggler input:checked + span
+ size,
+
+ // toggler input:checked + span > span
+ size,
+ size,
+ size,
+ size
+ )
+ .into_bump_str(),
}
}
}
@@ -81,22 +110,17 @@ pub struct Css<'a> {
}
impl<'a> Css<'a> {
- /// Creates an empty style [`Sheet`].
- ///
- /// [`Sheet`]: struct.Sheet.html
+ /// Creates an empty [`Css`].
pub fn new() -> Self {
Css {
rules: BTreeMap::new(),
}
}
- /// Inserts the [`rule`] in the [`Sheet`], if it was not previously
+ /// Inserts the [`Rule`] in the [`Css`], if it was not previously
/// inserted.
///
/// It returns the class name of the provided [`Rule`].
- ///
- /// [`Sheet`]: struct.Sheet.html
- /// [`Rule`]: enum.Rule.html
pub fn insert(&mut self, bump: &'a bumpalo::Bump, rule: Rule) -> String {
let class = rule.class();
@@ -107,9 +131,7 @@ impl<'a> Css<'a> {
class
}
- /// Produces the VDOM node of the style [`Sheet`].
- ///
- /// [`Sheet`]: struct.Sheet.html
+ /// Produces the VDOM node of the [`Css`].
pub fn node(self, bump: &'a bumpalo::Bump) -> dodrio::Node<'a> {
use dodrio::builder::*;
@@ -133,8 +155,6 @@ impl<'a> Css<'a> {
}
/// Returns the style value for the given [`Length`].
-///
-/// [`Length`]: ../enum.Length.html
pub fn length(length: Length) -> String {
match length {
Length::Shrink => String::from("auto"),
@@ -164,15 +184,11 @@ pub fn min_length(units: u32) -> String {
}
/// Returns the style value for the given [`Color`].
-///
-/// [`Color`]: ../struct.Color.html
pub fn color(Color { r, g, b, a }: Color) -> String {
format!("rgba({}, {}, {}, {})", 255.0 * r, 255.0 * g, 255.0 * b, a)
}
/// Returns the style value for the given [`Background`].
-///
-/// [`Background`]: ../struct.Background.html
pub fn background(background: Background) -> String {
match background {
Background::Color(c) => color(c),
@@ -180,8 +196,6 @@ pub fn background(background: Background) -> String {
}
/// Returns the style value for the given [`Align`].
-///
-/// [`Align`]: ../enum.Align.html
pub fn align(align: Align) -> &'static str {
match align {
Align::Start => "flex-start",
@@ -189,3 +203,13 @@ pub fn align(align: Align) -> &'static str {
Align::End => "flex-end",
}
}
+
+/// Returns the style value for the given [`Padding`].
+///
+/// [`Padding`]: struct.Padding.html
+pub fn padding(padding: Padding) -> String {
+ format!(
+ "{}px {}px {}px {}px",
+ padding.top, padding.right, padding.bottom, padding.left
+ )
+}
diff --git a/web/src/element.rs b/web/src/element.rs
index 93e73713..6bb90177 100644
--- a/web/src/element.rs
+++ b/web/src/element.rs
@@ -11,9 +11,7 @@ use std::rc::Rc;
/// If you have a [built-in widget], you should be able to use `Into<Element>`
/// to turn it into an [`Element`].
///
-/// [built-in widget]: widget/index.html
-/// [`Widget`]: widget/trait.Widget.html
-/// [`Element`]: struct.Element.html
+/// [built-in widget]: mod@crate::widget
#[allow(missing_debug_implementations)]
pub struct Element<'a, Message> {
pub(crate) widget: Box<dyn Widget<Message> + 'a>,
@@ -21,9 +19,6 @@ pub struct Element<'a, Message> {
impl<'a, Message> Element<'a, Message> {
/// Create a new [`Element`] containing the given [`Widget`].
- ///
- /// [`Element`]: struct.Element.html
- /// [`Widget`]: widget/trait.Widget.html
pub fn new(widget: impl Widget<Message> + 'a) -> Self {
Self {
widget: Box::new(widget),
@@ -34,8 +29,6 @@ impl<'a, Message> Element<'a, Message> {
///
/// This method is useful when you want to decouple different parts of your
/// UI and make them __composable__.
- ///
- /// [`Element`]: struct.Element.html
pub fn map<F, B>(self, f: F) -> Element<'a, B>
where
Message: 'static,
diff --git a/web/src/lib.rs b/web/src/lib.rs
index 0776e9d5..bb09bb0d 100644
--- a/web/src/lib.rs
+++ b/web/src/lib.rs
@@ -1,7 +1,5 @@
//! A web runtime for Iced, targetting the DOM.
//!
-//! ![`iced_web` crate graph](https://github.com/hecrj/iced/blob/cae26cb7bc627f4a5b3bcf1cd023a0c552e8c65e/docs/graphs/web.png?raw=true)
-//!
//! `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.
@@ -51,7 +49,7 @@
//!
//! [`wasm-pack`]: https://github.com/rustwasm/wasm-pack
//! [`wasm-bindgen`]: https://github.com/rustwasm/wasm-bindgen
-//! [`tour` example]: https://github.com/hecrj/iced/tree/0.1/examples/tour
+//! [`tour` example]: https://github.com/hecrj/iced/tree/0.3/examples/tour
#![deny(missing_docs)]
#![deny(missing_debug_implementations)]
#![deny(unused_results)]
@@ -61,6 +59,7 @@ use dodrio::bumpalo;
use std::{cell::RefCell, rc::Rc};
mod bus;
+mod clipboard;
mod element;
mod hasher;
@@ -69,13 +68,14 @@ pub mod subscription;
pub mod widget;
pub use bus::Bus;
+pub use clipboard::Clipboard;
pub use css::Css;
pub use dodrio;
pub use element::Element;
pub use hasher::Hasher;
pub use iced_core::{
- keyboard, mouse, Align, Background, Color, Font, HorizontalAlignment,
- Length, Point, Rectangle, Size, Vector, VerticalAlignment,
+ keyboard, menu, mouse, Align, Background, Color, Font, HorizontalAlignment,
+ Length, Menu, Padding, Point, Rectangle, Size, Vector, VerticalAlignment,
};
pub use iced_futures::{executor, futures, Command};
pub use subscription::Subscription;
@@ -96,33 +96,21 @@ pub use executor::Executor;
/// by returning a [`Command`](struct.Command.html) in some of its methods.
pub trait Application {
/// The [`Executor`] that will run commands and subscriptions.
- ///
- /// The [`executor::WasmBindgen`] can be a good choice for the Web.
- ///
- /// [`Executor`]: trait.Executor.html
- /// [`executor::Default`]: executor/struct.Default.html
type Executor: Executor;
/// The type of __messages__ your [`Application`] will produce.
- ///
- /// [`Application`]: trait.Application.html
type Message: Send;
/// The data needed to initialize your [`Application`].
- ///
- /// [`Application`]: trait.Application.html
type Flags;
/// Initializes the [`Application`].
///
/// Here is where you should return the initial state of your app.
///
- /// Additionally, you can return a [`Command`](struct.Command.html) if you
- /// need to perform some async action in the background on startup. This is
- /// useful if you want to load state from a file, perform an initial HTTP
- /// request, etc.
- ///
- /// [`Application`]: trait.Application.html
+ /// Additionally, you can return a [`Command`] if you need to perform some
+ /// async action in the background on startup. This is useful if you want to
+ /// load state from a file, perform an initial HTTP request, etc.
fn new(flags: Self::Flags) -> (Self, Command<Self::Message>)
where
Self: Sized;
@@ -131,8 +119,6 @@ pub trait Application {
///
/// This title can be dynamic! The runtime will automatically update the
/// title of your application when necessary.
- ///
- /// [`Application`]: trait.Application.html
fn title(&self) -> String;
/// Handles a __message__ and updates the state of the [`Application`].
@@ -142,16 +128,15 @@ pub trait Application {
/// this method.
///
/// Any [`Command`] returned will be executed immediately in the background.
- ///
- /// [`Application`]: trait.Application.html
- /// [`Command`]: struct.Command.html
- fn update(&mut self, message: Self::Message) -> Command<Self::Message>;
+ fn update(
+ &mut self,
+ message: Self::Message,
+ clipboard: &mut Clipboard,
+ ) -> Command<Self::Message>;
/// Returns the widgets to display in the [`Application`].
///
/// These widgets can produce __messages__ based on user interaction.
- ///
- /// [`Application`]: trait.Application.html
fn view(&mut self) -> Element<'_, Self::Message>;
/// Returns the event [`Subscription`] for the current state of the
@@ -162,15 +147,11 @@ pub trait Application {
/// [`update`](#tymethod.update).
///
/// By default, this method returns an empty [`Subscription`].
- ///
- /// [`Subscription`]: struct.Subscription.html
fn subscription(&self) -> Subscription<Self::Message> {
Subscription::none()
}
/// Runs the [`Application`].
- ///
- /// [`Application`]: trait.Application.html
fn run(flags: Self::Flags)
where
Self: 'static + Sized,
@@ -181,6 +162,8 @@ pub trait Application {
let document = window.document().unwrap();
let body = document.body().unwrap();
+ let mut clipboard = Clipboard::new();
+
let (sender, receiver) =
iced_futures::futures::channel::mpsc::unbounded();
@@ -207,7 +190,8 @@ pub trait Application {
let event_loop = receiver.for_each(move |message| {
let (command, subscription) = runtime.enter(|| {
- let command = application.borrow_mut().update(message);
+ let command =
+ application.borrow_mut().update(message, &mut clipboard);
let subscription = application.borrow().subscription();
(command, subscription)
diff --git a/web/src/subscription.rs b/web/src/subscription.rs
index 6b8415c0..fb54f7e3 100644
--- a/web/src/subscription.rs
+++ b/web/src/subscription.rs
@@ -12,8 +12,7 @@ use crate::Hasher;
/// For instance, you can use a [`Subscription`] to listen to a WebSocket
/// connection, keyboard presses, mouse events, time ticks, etc.
///
-/// [`Command`]: ../struct.Command.html
-/// [`Subscription`]: struct.Subscription.html
+/// [`Command`]: crate::Command
pub type Subscription<T> = iced_futures::Subscription<Hasher, (), T>;
pub use iced_futures::subscription::Recipe;
diff --git a/web/src/widget.rs b/web/src/widget.rs
index 025cf22f..4cb0a9cc 100644
--- a/web/src/widget.rs
+++ b/web/src/widget.rs
@@ -12,8 +12,6 @@
//! ```
//! use iced_web::{button, Button, Widget};
//! ```
-//!
-//! [`Widget`]: trait.Widget.html
use crate::{Bus, Css};
use dodrio::bumpalo;
@@ -26,6 +24,7 @@ pub mod radio;
pub mod scrollable;
pub mod slider;
pub mod text_input;
+pub mod toggler;
mod column;
mod row;
@@ -42,6 +41,8 @@ pub use slider::Slider;
pub use text::Text;
#[doc(no_inline)]
pub use text_input::TextInput;
+#[doc(no_inline)]
+pub use toggler::Toggler;
pub use checkbox::Checkbox;
pub use column::Column;
@@ -56,12 +57,8 @@ pub use space::Space;
///
/// If you want to build your own widgets, you will need to implement this
/// trait.
-///
-/// [`Widget`]: trait.Widget.html
pub trait Widget<Message> {
/// Produces a VDOM node for the [`Widget`].
- ///
- /// [`Widget`]: trait.Widget.html
fn node<'b>(
&self,
bump: &'b bumpalo::Bump,
diff --git a/web/src/widget/button.rs b/web/src/widget/button.rs
index a4bcc33d..cd450b55 100644
--- a/web/src/widget/button.rs
+++ b/web/src/widget/button.rs
@@ -1,10 +1,7 @@
//! Allow your users to perform actions by pressing a button.
//!
//! A [`Button`] has some local [`State`].
-//!
-//! [`Button`]: struct.Button.html
-//! [`State`]: struct.State.html
-use crate::{css, Background, Bus, Css, Element, Length, Widget};
+use crate::{css, Background, Bus, Css, Element, Length, Padding, Widget};
pub use iced_style::button::{Style, StyleSheet};
@@ -23,24 +20,43 @@ use dodrio::bumpalo;
/// let button = Button::new(&mut state, Text::new("Press me!"))
/// .on_press(Message::ButtonPressed);
/// ```
+///
+/// If a [`Button::on_press`] handler is not set, the resulting [`Button`] will
+/// be disabled:
+///
+/// ```
+/// # use iced_web::{button, Button, Text};
+/// #
+/// #[derive(Clone)]
+/// enum Message {
+/// ButtonPressed,
+/// }
+///
+/// fn disabled_button(state: &mut button::State) -> Button<'_, Message> {
+/// Button::new(state, Text::new("I'm disabled!"))
+/// }
+///
+/// fn enabled_button(state: &mut button::State) -> Button<'_, Message> {
+/// disabled_button(state).on_press(Message::ButtonPressed)
+/// }
+/// ```
#[allow(missing_debug_implementations)]
pub struct Button<'a, Message> {
content: Element<'a, Message>,
on_press: Option<Message>,
width: Length,
+ #[allow(dead_code)]
height: Length,
min_width: u32,
+ #[allow(dead_code)]
min_height: u32,
- padding: u16,
+ padding: Padding,
style: Box<dyn StyleSheet>,
}
impl<'a, Message> Button<'a, Message> {
/// Creates a new [`Button`] with some local [`State`] and the given
/// content.
- ///
- /// [`Button`]: struct.Button.html
- /// [`State`]: struct.State.html
pub fn new<E>(_state: &'a mut State, content: E) -> Self
where
E: Into<Element<'a, Message>>,
@@ -52,62 +68,49 @@ impl<'a, Message> Button<'a, Message> {
height: Length::Shrink,
min_width: 0,
min_height: 0,
- padding: 5,
+ padding: Padding::new(5),
style: Default::default(),
}
}
/// Sets the width of the [`Button`].
- ///
- /// [`Button`]: struct.Button.html
pub fn width(mut self, width: Length) -> Self {
self.width = width;
self
}
/// Sets the height of the [`Button`].
- ///
- /// [`Button`]: struct.Button.html
pub fn height(mut self, height: Length) -> Self {
self.height = height;
self
}
/// Sets the minimum width of the [`Button`].
- ///
- /// [`Button`]: struct.Button.html
pub fn min_width(mut self, min_width: u32) -> Self {
self.min_width = min_width;
self
}
/// Sets the minimum height of the [`Button`].
- ///
- /// [`Button`]: struct.Button.html
pub fn min_height(mut self, min_height: u32) -> Self {
self.min_height = min_height;
self
}
- /// Sets the padding of the [`Button`].
- ///
- /// [`Button`]: struct.Button.html
- pub fn padding(mut self, padding: u16) -> Self {
- self.padding = padding;
+ /// Sets the [`Padding`] of the [`Button`].
+ pub fn padding<P: Into<Padding>>(mut self, padding: P) -> Self {
+ self.padding = padding.into();
self
}
/// Sets the style of the [`Button`].
- ///
- /// [`Button`]: struct.Button.html
pub fn style(mut self, style: impl Into<Box<dyn StyleSheet>>) -> Self {
self.style = style.into();
self
}
/// Sets the message that will be produced when the [`Button`] is pressed.
- ///
- /// [`Button`]: struct.Button.html
+ /// If on_press isn't set, button will be disabled.
pub fn on_press(mut self, msg: Message) -> Self {
self.on_press = Some(msg);
self
@@ -115,15 +118,11 @@ impl<'a, Message> Button<'a, Message> {
}
/// The local state of a [`Button`].
-///
-/// [`Button`]: struct.Button.html
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct State;
impl State {
/// Creates a new [`State`].
- ///
- /// [`State`]: struct.State.html
pub fn new() -> State {
State::default()
}
@@ -144,9 +143,6 @@ where
// TODO: State-based styling
let style = self.style.active();
- let padding_class =
- style_sheet.insert(bump, css::Rule::Padding(self.padding));
-
let background = match style.background {
None => String::from("none"),
Some(background) => match background {
@@ -154,25 +150,19 @@ where
},
};
- let class = {
- use dodrio::bumpalo::collections::String;
-
- String::from_str_in(&padding_class, bump).into_bump_str()
- };
-
let mut node = button(bump)
- .attr("class", class)
.attr(
"style",
bumpalo::format!(
in bump,
"background: {}; border-radius: {}px; width:{}; \
- min-width: {}; color: {}",
+ min-width: {}; color: {}; padding: {}",
background,
style.border_radius,
css::length(self.width),
css::min_length(self.min_width),
- css::color(style.text_color)
+ css::color(style.text_color),
+ css::padding(self.padding)
)
.into_bump_str(),
)
@@ -184,6 +174,8 @@ where
node = node.on("click", move |_root, _vdom, _event| {
event_bus.publish(on_press.clone());
});
+ } else {
+ node = node.attr("disabled", "");
}
node.finish()
diff --git a/web/src/widget/checkbox.rs b/web/src/widget/checkbox.rs
index 21801e39..43110aa7 100644
--- a/web/src/widget/checkbox.rs
+++ b/web/src/widget/checkbox.rs
@@ -30,6 +30,7 @@ pub struct Checkbox<Message> {
label: String,
id: Option<String>,
width: Length,
+ #[allow(dead_code)]
style: Box<dyn StyleSheet>,
}
@@ -42,8 +43,6 @@ impl<Message> Checkbox<Message> {
/// * a function that will be called when the [`Checkbox`] is toggled. It
/// will receive the new state of the [`Checkbox`] and must produce a
/// `Message`.
- ///
- /// [`Checkbox`]: struct.Checkbox.html
pub fn new<F>(is_checked: bool, label: impl Into<String>, f: F) -> Self
where
F: 'static + Fn(bool) -> Message,
@@ -59,24 +58,18 @@ impl<Message> Checkbox<Message> {
}
/// Sets the width of the [`Checkbox`].
- ///
- /// [`Checkbox`]: struct.Checkbox.html
pub fn width(mut self, width: Length) -> Self {
self.width = width;
self
}
/// Sets the style of the [`Checkbox`].
- ///
- /// [`Checkbox`]: struct.Checkbox.html
pub fn style(mut self, style: impl Into<Box<dyn StyleSheet>>) -> Self {
self.style = style.into();
self
}
/// Sets the id of the [`Checkbox`].
- ///
- /// [`Checkbox`]: struct.Checkbox.html
pub fn id(mut self, id: impl Into<String>) -> Self {
self.id = Some(id.into());
self
diff --git a/web/src/widget/column.rs b/web/src/widget/column.rs
index 25b88b0e..8738c2af 100644
--- a/web/src/widget/column.rs
+++ b/web/src/widget/column.rs
@@ -1,4 +1,4 @@
-use crate::{css, Align, Bus, Css, Element, Length, Widget};
+use crate::{css, Align, Bus, Css, Element, Length, Padding, Widget};
use dodrio::bumpalo;
use std::u32;
@@ -6,12 +6,10 @@ use std::u32;
/// A container that distributes its contents vertically.
///
/// A [`Column`] will try to fill the horizontal space of its container.
-///
-/// [`Column`]: struct.Column.html
#[allow(missing_debug_implementations)]
pub struct Column<'a, Message> {
spacing: u16,
- padding: u16,
+ padding: Padding,
width: Length,
height: Length,
max_width: u32,
@@ -22,19 +20,15 @@ pub struct Column<'a, Message> {
impl<'a, Message> Column<'a, Message> {
/// Creates an empty [`Column`].
- ///
- /// [`Column`]: struct.Column.html
pub fn new() -> Self {
Self::with_children(Vec::new())
}
/// Creates a [`Column`] with the given elements.
- ///
- /// [`Column`]: struct.Column.html
pub fn with_children(children: Vec<Element<'a, Message>>) -> Self {
Column {
spacing: 0,
- padding: 0,
+ padding: Padding::ZERO,
width: Length::Fill,
height: Length::Shrink,
max_width: u32::MAX,
@@ -54,57 +48,43 @@ impl<'a, Message> Column<'a, Message> {
self
}
- /// Sets the padding of the [`Column`].
- ///
- /// [`Column`]: struct.Column.html
- pub fn padding(mut self, units: u16) -> Self {
- self.padding = units;
+ /// Sets the [`Padding`] of the [`Column`].
+ pub fn padding<P: Into<Padding>>(mut self, padding: P) -> Self {
+ self.padding = padding.into();
self
}
/// Sets the width of the [`Column`].
- ///
- /// [`Column`]: struct.Column.html
pub fn width(mut self, width: Length) -> Self {
self.width = width;
self
}
/// Sets the height of the [`Column`].
- ///
- /// [`Column`]: struct.Column.html
pub fn height(mut self, height: Length) -> Self {
self.height = height;
self
}
/// Sets the maximum width of the [`Column`].
- ///
- /// [`Column`]: struct.Column.html
pub fn max_width(mut self, max_width: u32) -> Self {
self.max_width = max_width;
self
}
/// Sets the maximum height of the [`Column`] in pixels.
- ///
- /// [`Column`]: struct.Column.html
pub fn max_height(mut self, max_height: u32) -> Self {
self.max_height = max_height;
self
}
/// Sets the horizontal alignment of the contents of the [`Column`] .
- ///
- /// [`Column`]: struct.Column.html
pub fn align_items(mut self, align: Align) -> Self {
self.align_items = align;
self
}
/// Adds an element to the [`Column`].
- ///
- /// [`Column`]: struct.Column.html
pub fn push<E>(mut self, child: E) -> Self
where
E: Into<Element<'a, Message>>,
@@ -134,23 +114,21 @@ impl<'a, Message> Widget<Message> for Column<'a, Message> {
let spacing_class =
style_sheet.insert(bump, css::Rule::Spacing(self.spacing));
- let padding_class =
- style_sheet.insert(bump, css::Rule::Padding(self.padding));
-
// TODO: Complete styling
div(bump)
.attr(
"class",
- bumpalo::format!(in bump, "{} {} {}", column_class, spacing_class, padding_class)
+ bumpalo::format!(in bump, "{} {}", column_class, spacing_class)
.into_bump_str(),
)
.attr("style", bumpalo::format!(
in bump,
- "width: {}; height: {}; max-width: {}; max-height: {}; align-items: {}",
+ "width: {}; height: {}; max-width: {}; max-height: {}; padding: {}; align-items: {}",
css::length(self.width),
css::length(self.height),
css::max_length(self.max_width),
css::max_length(self.max_height),
+ css::padding(self.padding),
css::align(self.align_items)
).into_bump_str()
)
diff --git a/web/src/widget/container.rs b/web/src/widget/container.rs
index 78be3543..c006e011 100644
--- a/web/src/widget/container.rs
+++ b/web/src/widget/container.rs
@@ -1,5 +1,5 @@
//! Decorate content and apply alignment.
-use crate::{bumpalo, css, Align, Bus, Css, Element, Length, Widget};
+use crate::{bumpalo, css, Align, Bus, Css, Element, Length, Padding, Widget};
pub use iced_style::container::{Style, StyleSheet};
@@ -8,10 +8,11 @@ pub use iced_style::container::{Style, StyleSheet};
/// It is normally used for alignment purposes.
#[allow(missing_debug_implementations)]
pub struct Container<'a, Message> {
- padding: u16,
+ padding: Padding,
width: Length,
height: Length,
max_width: u32,
+ #[allow(dead_code)]
max_height: u32,
horizontal_alignment: Align,
vertical_alignment: Align,
@@ -21,8 +22,6 @@ pub struct Container<'a, Message> {
impl<'a, Message> Container<'a, Message> {
/// Creates an empty [`Container`].
- ///
- /// [`Container`]: struct.Container.html
pub fn new<T>(content: T) -> Self
where
T: Into<Element<'a, Message>>,
@@ -30,7 +29,7 @@ impl<'a, Message> Container<'a, Message> {
use std::u32;
Container {
- padding: 0,
+ padding: Padding::ZERO,
width: Length::Shrink,
height: Length::Shrink,
max_width: u32::MAX,
@@ -42,49 +41,37 @@ impl<'a, Message> Container<'a, Message> {
}
}
- /// Sets the padding of the [`Container`].
- ///
- /// [`Container`]: struct.Column.html
- pub fn padding(mut self, units: u16) -> Self {
- self.padding = units;
+ /// Sets the [`Padding`] of the [`Container`].
+ pub fn padding<P: Into<Padding>>(mut self, padding: P) -> Self {
+ self.padding = padding.into();
self
}
/// Sets the width of the [`Container`].
- ///
- /// [`Container`]: struct.Container.html
pub fn width(mut self, width: Length) -> Self {
self.width = width;
self
}
/// Sets the height of the [`Container`].
- ///
- /// [`Container`]: struct.Container.html
pub fn height(mut self, height: Length) -> Self {
self.height = height;
self
}
/// Sets the maximum width of the [`Container`].
- ///
- /// [`Container`]: struct.Container.html
pub fn max_width(mut self, max_width: u32) -> Self {
self.max_width = max_width;
self
}
/// Sets the maximum height of the [`Container`] in pixels.
- ///
- /// [`Container`]: struct.Container.html
pub fn max_height(mut self, max_height: u32) -> Self {
self.max_height = max_height;
self
}
/// Centers the contents in the horizontal axis of the [`Container`].
- ///
- /// [`Container`]: struct.Container.html
pub fn center_x(mut self) -> Self {
self.horizontal_alignment = Align::Center;
@@ -92,8 +79,6 @@ impl<'a, Message> Container<'a, Message> {
}
/// Centers the contents in the vertical axis of the [`Container`].
- ///
- /// [`Container`]: struct.Container.html
pub fn center_y(mut self) -> Self {
self.vertical_alignment = Align::Center;
@@ -101,8 +86,6 @@ impl<'a, Message> Container<'a, Message> {
}
/// Sets the style of the [`Container`].
- ///
- /// [`Container`]: struct.Container.html
pub fn style(mut self, style: impl Into<Box<dyn StyleSheet>>) -> Self {
self.style_sheet = style.into();
self
@@ -123,24 +106,22 @@ where
let column_class = style_sheet.insert(bump, css::Rule::Column);
- let padding_class =
- style_sheet.insert(bump, css::Rule::Padding(self.padding));
-
let style = self.style_sheet.style();
let node = div(bump)
.attr(
"class",
- bumpalo::format!(in bump, "{} {}", column_class, padding_class).into_bump_str(),
+ bumpalo::format!(in bump, "{}", column_class).into_bump_str(),
)
.attr(
"style",
bumpalo::format!(
in bump,
- "width: {}; height: {}; max-width: {}; align-items: {}; justify-content: {}; background: {}; color: {}; border-width: {}px; border-color: {}; border-radius: {}px",
+ "width: {}; height: {}; max-width: {}; padding: {}; align-items: {}; justify-content: {}; background: {}; color: {}; border-width: {}px; border-color: {}; border-radius: {}px",
css::length(self.width),
css::length(self.height),
css::max_length(self.max_width),
+ css::padding(self.padding),
css::align(self.horizontal_alignment),
css::align(self.vertical_alignment),
style.background.map(css::background).unwrap_or(String::from("initial")),
diff --git a/web/src/widget/image.rs b/web/src/widget/image.rs
index a595c29a..05c89ea5 100644
--- a/web/src/widget/image.rs
+++ b/web/src/widget/image.rs
@@ -34,8 +34,6 @@ pub struct Image {
impl Image {
/// Creates a new [`Image`] with the given path.
- ///
- /// [`Image`]: struct.Image.html
pub fn new<T: Into<Handle>>(handle: T) -> Self {
Image {
handle: handle.into(),
@@ -46,24 +44,18 @@ impl Image {
}
/// Sets the width of the [`Image`] boundaries.
- ///
- /// [`Image`]: struct.Image.html
pub fn width(mut self, width: Length) -> Self {
self.width = width;
self
}
/// Sets the height of the [`Image`] boundaries.
- ///
- /// [`Image`]: struct.Image.html
pub fn height(mut self, height: Length) -> Self {
self.height = height;
self
}
/// Sets the alt text of the [`Image`].
- ///
- /// [`Image`]: struct.Image.html
pub fn alt(mut self, alt: impl Into<String>) -> Self {
self.alt = alt.into();
self
@@ -118,8 +110,6 @@ impl<'a, Message> From<Image> for Element<'a, Message> {
}
/// An [`Image`] handle.
-///
-/// [`Image`]: struct.Image.html
#[derive(Debug, Clone)]
pub struct Handle {
id: u64,
@@ -128,8 +118,6 @@ pub struct Handle {
impl Handle {
/// Creates an image [`Handle`] pointing to the image of the given path.
- ///
- /// [`Handle`]: struct.Handle.html
pub fn from_path<T: Into<PathBuf>>(path: T) -> Handle {
Self::from_data(Data::Path(path.into()))
}
@@ -145,15 +133,11 @@ impl Handle {
}
/// Returns the unique identifier of the [`Handle`].
- ///
- /// [`Handle`]: struct.Handle.html
pub fn id(&self) -> u64 {
self.id
}
/// Returns a reference to the image [`Data`].
- ///
- /// [`Data`]: enum.Data.html
pub fn data(&self) -> &Data {
&self.data
}
@@ -172,8 +156,6 @@ impl From<&str> for Handle {
}
/// The data of an [`Image`].
-///
-/// [`Image`]: struct.Image.html
#[derive(Clone, Hash)]
pub enum Data {
/// A remote image
diff --git a/web/src/widget/progress_bar.rs b/web/src/widget/progress_bar.rs
index 856203c0..7d77616e 100644
--- a/web/src/widget/progress_bar.rs
+++ b/web/src/widget/progress_bar.rs
@@ -32,8 +32,6 @@ impl ProgressBar {
/// It expects:
/// * an inclusive range of possible values
/// * the current value of the [`ProgressBar`]
- ///
- /// [`ProgressBar`]: struct.ProgressBar.html
pub fn new(range: RangeInclusive<f32>, value: f32) -> Self {
ProgressBar {
value: value.max(*range.start()).min(*range.end()),
@@ -45,24 +43,18 @@ impl ProgressBar {
}
/// Sets the width of the [`ProgressBar`].
- ///
- /// [`ProgressBar`]: struct.ProgressBar.html
pub fn width(mut self, width: Length) -> Self {
self.width = width;
self
}
/// Sets the height of the [`ProgressBar`].
- ///
- /// [`ProgressBar`]: struct.ProgressBar.html
pub fn height(mut self, height: Length) -> Self {
self.height = Some(height);
self
}
/// Sets the style of the [`ProgressBar`].
- ///
- /// [`ProgressBar`]: struct.ProgressBar.html
pub fn style(mut self, style: impl Into<Box<dyn StyleSheet>>) -> Self {
self.style = style.into();
self
diff --git a/web/src/widget/radio.rs b/web/src/widget/radio.rs
index c9d0a00e..fbc88d29 100644
--- a/web/src/widget/radio.rs
+++ b/web/src/widget/radio.rs
@@ -37,6 +37,7 @@ pub struct Radio<Message> {
label: String,
id: Option<String>,
name: Option<String>,
+ #[allow(dead_code)]
style: Box<dyn StyleSheet>,
}
@@ -49,8 +50,6 @@ impl<Message> Radio<Message> {
/// * the current selected value
/// * a function that will be called when the [`Radio`] is selected. It
/// receives the value of the radio and must produce a `Message`.
- ///
- /// [`Radio`]: struct.Radio.html
pub fn new<F, V>(
value: V,
label: impl Into<String>,
@@ -72,24 +71,18 @@ impl<Message> Radio<Message> {
}
/// Sets the style of the [`Radio`] button.
- ///
- /// [`Radio`]: struct.Radio.html
pub fn style(mut self, style: impl Into<Box<dyn StyleSheet>>) -> Self {
self.style = style.into();
self
}
/// Sets the name attribute of the [`Radio`] button.
- ///
- /// [`Radio`]: struct.Radio.html
pub fn name(mut self, name: impl Into<String>) -> Self {
self.name = Some(name.into());
self
}
/// Sets the id of the [`Radio`] button.
- ///
- /// [`Radio`]: struct.Radio.html
pub fn id(mut self, id: impl Into<String>) -> Self {
self.id = Some(id.into());
self
diff --git a/web/src/widget/row.rs b/web/src/widget/row.rs
index cfa10fdf..ffb515cf 100644
--- a/web/src/widget/row.rs
+++ b/web/src/widget/row.rs
@@ -1,4 +1,4 @@
-use crate::{css, Align, Bus, Css, Element, Length, Widget};
+use crate::{css, Align, Bus, Css, Element, Length, Padding, Widget};
use dodrio::bumpalo;
use std::u32;
@@ -6,12 +6,10 @@ use std::u32;
/// A container that distributes its contents horizontally.
///
/// A [`Row`] will try to fill the horizontal space of its container.
-///
-/// [`Row`]: struct.Row.html
#[allow(missing_debug_implementations)]
pub struct Row<'a, Message> {
spacing: u16,
- padding: u16,
+ padding: Padding,
width: Length,
height: Length,
max_width: u32,
@@ -22,19 +20,15 @@ pub struct Row<'a, Message> {
impl<'a, Message> Row<'a, Message> {
/// Creates an empty [`Row`].
- ///
- /// [`Row`]: struct.Row.html
pub fn new() -> Self {
Self::with_children(Vec::new())
}
/// Creates a [`Row`] with the given elements.
- ///
- /// [`Row`]: struct.Row.html
pub fn with_children(children: Vec<Element<'a, Message>>) -> Self {
Row {
spacing: 0,
- padding: 0,
+ padding: Padding::ZERO,
width: Length::Fill,
height: Length::Shrink,
max_width: u32::MAX,
@@ -54,58 +48,43 @@ impl<'a, Message> Row<'a, Message> {
self
}
- /// Sets the padding of the [`Row`].
- ///
- /// [`Row`]: struct.Row.html
- pub fn padding(mut self, units: u16) -> Self {
- self.padding = units;
+ /// Sets the [`Padding`] of the [`Row`].
+ pub fn padding<P: Into<Padding>>(mut self, padding: P) -> Self {
+ self.padding = padding.into();
self
}
/// Sets the width of the [`Row`].
- ///
- /// [`Row`]: struct.Row.html
pub fn width(mut self, width: Length) -> Self {
self.width = width;
self
}
/// Sets the height of the [`Row`].
- ///
- /// [`Row`]: struct.Row.html
pub fn height(mut self, height: Length) -> Self {
self.height = height;
self
}
/// Sets the maximum width of the [`Row`].
- ///
- /// [`Row`]: struct.Row.html
pub fn max_width(mut self, max_width: u32) -> Self {
self.max_width = max_width;
self
}
/// Sets the maximum height of the [`Row`].
- ///
- /// [`Row`]: struct.Row.html
pub fn max_height(mut self, max_height: u32) -> Self {
self.max_height = max_height;
self
}
/// Sets the vertical alignment of the contents of the [`Row`] .
- ///
- /// [`Row`]: struct.Row.html
pub fn align_items(mut self, align: Align) -> Self {
self.align_items = align;
self
}
/// Adds an [`Element`] to the [`Row`].
- ///
- /// [`Element`]: ../struct.Element.html
- /// [`Row`]: struct.Row.html
pub fn push<E>(mut self, child: E) -> Self
where
E: Into<Element<'a, Message>>,
@@ -135,23 +114,21 @@ impl<'a, Message> Widget<Message> for Row<'a, Message> {
let spacing_class =
style_sheet.insert(bump, css::Rule::Spacing(self.spacing));
- let padding_class =
- style_sheet.insert(bump, css::Rule::Padding(self.padding));
-
// TODO: Complete styling
div(bump)
.attr(
"class",
- bumpalo::format!(in bump, "{} {} {}", row_class, spacing_class, padding_class)
+ bumpalo::format!(in bump, "{} {}", row_class, spacing_class)
.into_bump_str(),
)
.attr("style", bumpalo::format!(
in bump,
- "width: {}; height: {}; max-width: {}; max-height: {}; align-items: {}",
+ "width: {}; height: {}; max-width: {}; max-height: {}; padding: {}; align-items: {}",
css::length(self.width),
css::length(self.height),
css::max_length(self.max_width),
css::max_length(self.max_height),
+ css::padding(self.padding),
css::align(self.align_items)
).into_bump_str()
)
diff --git a/web/src/widget/scrollable.rs b/web/src/widget/scrollable.rs
index 07b38aad..ce0a10d4 100644
--- a/web/src/widget/scrollable.rs
+++ b/web/src/widget/scrollable.rs
@@ -1,5 +1,7 @@
//! Navigate an endless amount of content with a scrollbar.
-use crate::{bumpalo, css, Align, Bus, Column, Css, Element, Length, Widget};
+use crate::{
+ bumpalo, css, Align, Bus, Column, Css, Element, Length, Padding, Widget,
+};
pub use iced_style::scrollable::{Scrollbar, Scroller, StyleSheet};
@@ -11,14 +13,12 @@ pub struct Scrollable<'a, Message> {
height: Length,
max_height: u32,
content: Column<'a, Message>,
+ #[allow(dead_code)]
style: Box<dyn StyleSheet>,
}
impl<'a, Message> Scrollable<'a, Message> {
/// Creates a new [`Scrollable`] with the given [`State`].
- ///
- /// [`Scrollable`]: struct.Scrollable.html
- /// [`State`]: struct.State.html
pub fn new(_state: &'a mut State) -> Self {
use std::u32;
@@ -41,65 +41,49 @@ impl<'a, Message> Scrollable<'a, Message> {
self
}
- /// Sets the padding of the [`Scrollable`].
- ///
- /// [`Scrollable`]: struct.Scrollable.html
- pub fn padding(mut self, units: u16) -> Self {
- self.content = self.content.padding(units);
+ /// Sets the [`Padding`] of the [`Scrollable`].
+ pub fn padding<P: Into<Padding>>(mut self, padding: P) -> Self {
+ self.content = self.content.padding(padding);
self
}
/// Sets the width of the [`Scrollable`].
- ///
- /// [`Scrollable`]: struct.Scrollable.html
pub fn width(mut self, width: Length) -> Self {
self.width = width;
self
}
/// Sets the height of the [`Scrollable`].
- ///
- /// [`Scrollable`]: struct.Scrollable.html
pub fn height(mut self, height: Length) -> Self {
self.height = height;
self
}
/// Sets the maximum width of the [`Scrollable`].
- ///
- /// [`Scrollable`]: struct.Scrollable.html
pub fn max_width(mut self, max_width: u32) -> Self {
self.content = self.content.max_width(max_width);
self
}
/// Sets the maximum height of the [`Scrollable`] in pixels.
- ///
- /// [`Scrollable`]: struct.Scrollable.html
pub fn max_height(mut self, max_height: u32) -> Self {
self.max_height = max_height;
self
}
/// Sets the horizontal alignment of the contents of the [`Scrollable`] .
- ///
- /// [`Scrollable`]: struct.Scrollable.html
pub fn align_items(mut self, align_items: Align) -> Self {
self.content = self.content.align_items(align_items);
self
}
/// Sets the style of the [`Scrollable`] .
- ///
- /// [`Scrollable`]: struct.Scrollable.html
pub fn style(mut self, style: impl Into<Box<dyn StyleSheet>>) -> Self {
self.style = style.into();
self
}
/// Adds an element to the [`Scrollable`].
- ///
- /// [`Scrollable`]: struct.Scrollable.html
pub fn push<E>(mut self, child: E) -> Self
where
E: Into<Element<'a, Message>>,
@@ -154,15 +138,11 @@ where
}
/// The local state of a [`Scrollable`].
-///
-/// [`Scrollable`]: struct.Scrollable.html
#[derive(Debug, Clone, Copy, Default)]
pub struct State;
impl State {
/// Creates a new [`State`] with the scrollbar located at the top.
- ///
- /// [`State`]: struct.State.html
pub fn new() -> Self {
State::default()
}
diff --git a/web/src/widget/slider.rs b/web/src/widget/slider.rs
index a0d9df00..f457aa4c 100644
--- a/web/src/widget/slider.rs
+++ b/web/src/widget/slider.rs
@@ -1,9 +1,6 @@
//! Display an interactive selector of a single value from a range of values.
//!
//! A [`Slider`] has some local [`State`].
-//!
-//! [`Slider`]: struct.Slider.html
-//! [`State`]: struct.State.html
use crate::{Bus, Css, Element, Length, Widget};
pub use iced_style::slider::{Handle, HandleShape, Style, StyleSheet};
@@ -19,8 +16,6 @@ use std::{ops::RangeInclusive, rc::Rc};
/// The [`Slider`] range of numeric values is generic and its step size defaults
/// to 1 unit.
///
-/// [`Slider`]: struct.Slider.html
-///
/// # Example
/// ```
/// # use iced_web::{slider, Slider};
@@ -43,7 +38,9 @@ pub struct Slider<'a, T, Message> {
step: T,
value: T,
on_change: Rc<Box<dyn Fn(T) -> Message>>,
+ #[allow(dead_code)]
width: Length,
+ #[allow(dead_code)]
style: Box<dyn StyleSheet>,
}
@@ -60,9 +57,6 @@ where
/// * a function that will be called when the [`Slider`] is dragged.
/// It receives the new value of the [`Slider`] and must produce a
/// `Message`.
- ///
- /// [`Slider`]: struct.Slider.html
- /// [`State`]: struct.State.html
pub fn new<F>(
state: &'a mut State,
range: RangeInclusive<T>,
@@ -96,24 +90,18 @@ where
}
/// Sets the width of the [`Slider`].
- ///
- /// [`Slider`]: struct.Slider.html
pub fn width(mut self, width: Length) -> Self {
self.width = width;
self
}
/// Sets the style of the [`Slider`].
- ///
- /// [`Slider`]: struct.Slider.html
pub fn style(mut self, style: impl Into<Box<dyn StyleSheet>>) -> Self {
self.style = style.into();
self
}
/// Sets the step size of the [`Slider`].
- ///
- /// [`Slider`]: struct.Slider.html
pub fn step(mut self, step: T) -> Self {
self.step = step;
self
@@ -181,15 +169,11 @@ where
}
/// The local state of a [`Slider`].
-///
-/// [`Slider`]: struct.Slider.html
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct State;
impl State {
/// Creates a new [`State`].
- ///
- /// [`State`]: struct.State.html
pub fn new() -> Self {
Self
}
diff --git a/web/src/widget/space.rs b/web/src/widget/space.rs
index 4ce52595..a8571fdb 100644
--- a/web/src/widget/space.rs
+++ b/web/src/widget/space.rs
@@ -12,15 +12,11 @@ pub struct Space {
impl Space {
/// Creates an amount of empty [`Space`] with the given width and height.
- ///
- /// [`Space`]: struct.Space.html
pub fn new(width: Length, height: Length) -> Self {
Space { width, height }
}
/// Creates an amount of horizontal [`Space`].
- ///
- /// [`Space`]: struct.Space.html
pub fn with_width(width: Length) -> Self {
Space {
width,
@@ -29,8 +25,6 @@ impl Space {
}
/// Creates an amount of vertical [`Space`].
- ///
- /// [`Space`]: struct.Space.html
pub fn with_height(height: Length) -> Self {
Space {
width: Length::Shrink,
diff --git a/web/src/widget/text.rs b/web/src/widget/text.rs
index 2f7308ee..72232dc0 100644
--- a/web/src/widget/text.rs
+++ b/web/src/widget/text.rs
@@ -28,8 +28,6 @@ pub struct Text {
impl Text {
/// Create a new fragment of [`Text`] with the given contents.
- ///
- /// [`Text`]: struct.Text.html
pub fn new<T: Into<String>>(label: T) -> Self {
Text {
content: label.into(),
@@ -44,51 +42,36 @@ impl Text {
}
/// Sets the size of the [`Text`].
- ///
- /// [`Text`]: struct.Text.html
pub fn size(mut self, size: u16) -> Self {
self.size = Some(size);
self
}
/// Sets the [`Color`] of the [`Text`].
- ///
- /// [`Text`]: struct.Text.html
- /// [`Color`]: ../../struct.Color.html
pub fn color<C: Into<Color>>(mut self, color: C) -> Self {
self.color = Some(color.into());
self
}
/// Sets the [`Font`] of the [`Text`].
- ///
- /// [`Text`]: struct.Text.html
- /// [`Font`]: ../../struct.Font.html
pub fn font(mut self, font: Font) -> Self {
self.font = font;
self
}
/// Sets the width of the [`Text`] boundaries.
- ///
- /// [`Text`]: struct.Text.html
pub fn width(mut self, width: Length) -> Self {
self.width = width;
self
}
/// Sets the height of the [`Text`] boundaries.
- ///
- /// [`Text`]: struct.Text.html
pub fn height(mut self, height: Length) -> Self {
self.height = height;
self
}
/// Sets the [`HorizontalAlignment`] of the [`Text`].
- ///
- /// [`Text`]: struct.Text.html
- /// [`HorizontalAlignment`]: enum.HorizontalAlignment.html
pub fn horizontal_alignment(
mut self,
alignment: HorizontalAlignment,
@@ -98,9 +81,6 @@ impl Text {
}
/// Sets the [`VerticalAlignment`] of the [`Text`].
- ///
- /// [`Text`]: struct.Text.html
- /// [`VerticalAlignment`]: enum.VerticalAlignment.html
pub fn vertical_alignment(mut self, alignment: VerticalAlignment) -> Self {
self.vertical_alignment = alignment;
self
diff --git a/web/src/widget/text_input.rs b/web/src/widget/text_input.rs
index 0049a553..e4877f2a 100644
--- a/web/src/widget/text_input.rs
+++ b/web/src/widget/text_input.rs
@@ -1,10 +1,7 @@
//! Display fields that can be filled with text.
//!
//! A [`TextInput`] has some local [`State`].
-//!
-//! [`TextInput`]: struct.TextInput.html
-//! [`State`]: struct.State.html
-use crate::{bumpalo, css, Bus, Css, Element, Length, Widget};
+use crate::{bumpalo, css, Bus, Css, Element, Length, Padding, Widget};
pub use iced_style::text_input::{Style, StyleSheet};
@@ -38,7 +35,7 @@ pub struct TextInput<'a, Message> {
is_secure: bool,
width: Length,
max_width: u32,
- padding: u16,
+ padding: Padding,
size: Option<u16>,
on_change: Rc<Box<dyn Fn(String) -> Message>>,
on_submit: Option<Message>,
@@ -53,9 +50,6 @@ impl<'a, Message> TextInput<'a, Message> {
/// - a placeholder
/// - the current value
/// - a function that produces a message when the [`TextInput`] changes
- ///
- /// [`TextInput`]: struct.TextInput.html
- /// [`State`]: struct.State.html
pub fn new<F>(
state: &'a mut State,
placeholder: &str,
@@ -72,7 +66,7 @@ impl<'a, Message> TextInput<'a, Message> {
is_secure: false,
width: Length::Fill,
max_width: u32::MAX,
- padding: 0,
+ padding: Padding::ZERO,
size: None,
on_change: Rc::new(Box::new(on_change)),
on_submit: None,
@@ -81,40 +75,30 @@ impl<'a, Message> TextInput<'a, Message> {
}
/// Converts the [`TextInput`] into a secure password input.
- ///
- /// [`TextInput`]: struct.TextInput.html
pub fn password(mut self) -> Self {
self.is_secure = true;
self
}
/// Sets the width of the [`TextInput`].
- ///
- /// [`TextInput`]: struct.TextInput.html
pub fn width(mut self, width: Length) -> Self {
self.width = width;
self
}
/// Sets the maximum width of the [`TextInput`].
- ///
- /// [`TextInput`]: struct.TextInput.html
pub fn max_width(mut self, max_width: u32) -> Self {
self.max_width = max_width;
self
}
- /// Sets the padding of the [`TextInput`].
- ///
- /// [`TextInput`]: struct.TextInput.html
- pub fn padding(mut self, units: u16) -> Self {
- self.padding = units;
+ /// Sets the [`Padding`] of the [`TextInput`].
+ pub fn padding<P: Into<Padding>>(mut self, padding: P) -> Self {
+ self.padding = padding.into();
self
}
/// Sets the text size of the [`TextInput`].
- ///
- /// [`TextInput`]: struct.TextInput.html
pub fn size(mut self, size: u16) -> Self {
self.size = Some(size);
self
@@ -122,16 +106,12 @@ impl<'a, Message> TextInput<'a, Message> {
/// Sets the message that should be produced when the [`TextInput`] is
/// focused and the enter key is pressed.
- ///
- /// [`TextInput`]: struct.TextInput.html
pub fn on_submit(mut self, message: Message) -> Self {
self.on_submit = Some(message);
self
}
/// Sets the style of the [`TextInput`].
- ///
- /// [`TextInput`]: struct.TextInput.html
pub fn style(mut self, style: impl Into<Box<dyn StyleSheet>>) -> Self {
self.style_sheet = style.into();
self
@@ -146,20 +126,11 @@ where
&self,
bump: &'b bumpalo::Bump,
bus: &Bus<Message>,
- style_sheet: &mut Css<'b>,
+ _style_sheet: &mut Css<'b>,
) -> dodrio::Node<'b> {
use dodrio::builder::*;
use wasm_bindgen::JsCast;
- let class = {
- use dodrio::bumpalo::collections::String;
-
- let padding_class =
- style_sheet.insert(bump, css::Rule::Padding(self.padding));
-
- String::from_str_in(&padding_class, bump).into_bump_str()
- };
-
let placeholder = {
use dodrio::bumpalo::collections::String;
@@ -179,16 +150,16 @@ where
let style = self.style_sheet.active();
input(bump)
- .attr("class", class)
.attr(
"style",
bumpalo::format!(
in bump,
- "width: {}; max-width: {}; font-size: {}px; \
+ "width: {}; max-width: {}; padding: {}; font-size: {}px; \
background: {}; border-width: {}px; border-color: {}; \
border-radius: {}px; color: {}",
css::length(self.width),
css::max_length(self.max_width),
+ css::padding(self.padding),
self.size.unwrap_or(20),
css::background(style.background),
style.border_width,
@@ -238,24 +209,23 @@ where
}
/// The state of a [`TextInput`].
-///
-/// [`TextInput`]: struct.TextInput.html
#[derive(Debug, Clone, Copy, Default)]
pub struct State;
impl State {
/// Creates a new [`State`], representing an unfocused [`TextInput`].
- ///
- /// [`State`]: struct.State.html
pub fn new() -> Self {
Self::default()
}
/// Creates a new [`State`], representing a focused [`TextInput`].
- ///
- /// [`State`]: struct.State.html
pub fn focused() -> Self {
// TODO
Self::default()
}
+
+ /// Selects all the content of the [`TextInput`].
+ pub fn select_all(&mut self) {
+ // TODO
+ }
}
diff --git a/web/src/widget/toggler.rs b/web/src/widget/toggler.rs
new file mode 100644
index 00000000..0a198079
--- /dev/null
+++ b/web/src/widget/toggler.rs
@@ -0,0 +1,171 @@
+//! Show toggle controls using togglers.
+use crate::{css, Bus, Css, Element, Length, Widget};
+
+pub use iced_style::toggler::{Style, StyleSheet};
+
+use dodrio::bumpalo;
+use std::rc::Rc;
+
+/// A toggler that can be toggled.
+///
+/// # Example
+///
+/// ```
+/// # use iced_web::Toggler;
+///
+/// pub enum Message {
+/// TogglerToggled(bool),
+/// }
+///
+/// let is_active = true;
+///
+/// Toggler::new(is_active, String::from("Toggle me!"), Message::TogglerToggled);
+/// ```
+///
+#[allow(missing_debug_implementations)]
+pub struct Toggler<Message> {
+ is_active: bool,
+ on_toggle: Rc<dyn Fn(bool) -> Message>,
+ label: Option<String>,
+ id: Option<String>,
+ width: Length,
+ style: Box<dyn StyleSheet>,
+}
+
+impl<Message> Toggler<Message> {
+ /// Creates a new [`Toggler`].
+ ///
+ /// It expects:
+ /// * a boolean describing whether the [`Toggler`] is active or not
+ /// * An optional label for the [`Toggler`]
+ /// * a function that will be called when the [`Toggler`] is toggled. It
+ /// will receive the new state of the [`Toggler`] and must produce a
+ /// `Message`.
+ ///
+ /// [`Toggler`]: struct.Toggler.html
+ pub fn new<F>(
+ is_active: bool,
+ label: impl Into<Option<String>>,
+ f: F,
+ ) -> Self
+ where
+ F: 'static + Fn(bool) -> Message,
+ {
+ Toggler {
+ is_active,
+ on_toggle: Rc::new(f),
+ label: label.into(),
+ id: None,
+ width: Length::Shrink,
+ style: Default::default(),
+ }
+ }
+
+ /// Sets the width of the [`Toggler`].
+ ///
+ /// [`Toggler`]: struct.Toggler.html
+ pub fn width(mut self, width: Length) -> Self {
+ self.width = width;
+ self
+ }
+
+ /// Sets the style of the [`Toggler`].
+ ///
+ /// [`Toggler`]: struct.Toggler.html
+ pub fn style(mut self, style: impl Into<Box<dyn StyleSheet>>) -> Self {
+ self.style = style.into();
+ self
+ }
+
+ /// Sets the id of the [`Toggler`].
+ ///
+ /// [`Toggler`]: struct.Toggler.html
+ pub fn id(mut self, id: impl Into<String>) -> Self {
+ self.id = Some(id.into());
+ self
+ }
+}
+
+impl<Message> Widget<Message> for Toggler<Message>
+where
+ Message: 'static,
+{
+ fn node<'b>(
+ &self,
+ bump: &'b bumpalo::Bump,
+ bus: &Bus<Message>,
+ style_sheet: &mut Css<'b>,
+ ) -> dodrio::Node<'b> {
+ use dodrio::builder::*;
+ use dodrio::bumpalo::collections::String;
+
+ let toggler_label = &self
+ .label
+ .as_ref()
+ .map(|label| String::from_str_in(&label, bump).into_bump_str());
+
+ let event_bus = bus.clone();
+ let on_toggle = self.on_toggle.clone();
+ let is_active = self.is_active;
+
+ let row_class = style_sheet.insert(bump, css::Rule::Row);
+ let toggler_class = style_sheet.insert(bump, css::Rule::Toggler(16));
+
+ let (label, input) = if let Some(id) = &self.id {
+ let id = String::from_str_in(id, bump).into_bump_str();
+
+ (label(bump).attr("for", id), input(bump).attr("id", id))
+ } else {
+ (label(bump), input(bump))
+ };
+
+ let checkbox = input
+ .attr("type", "checkbox")
+ .bool_attr("checked", self.is_active)
+ .on("click", move |_root, vdom, _event| {
+ let msg = on_toggle(!is_active);
+ event_bus.publish(msg);
+
+ vdom.schedule_render();
+ })
+ .finish();
+
+ let toggler = span(bump).children(vec![span(bump).finish()]).finish();
+
+ label
+ .attr(
+ "class",
+ bumpalo::format!(in bump, "{} {}", row_class, toggler_class)
+ .into_bump_str(),
+ )
+ .attr(
+ "style",
+ bumpalo::format!(in bump, "width: {}; align-items: center", css::length(self.width))
+ .into_bump_str()
+ )
+ .children(
+ if let Some(label) = toggler_label {
+ vec![
+ text(label),
+ checkbox,
+ toggler,
+ ]
+ } else {
+ vec![
+ checkbox,
+ toggler,
+ ]
+ }
+ )
+ .finish()
+ }
+}
+
+impl<'a, Message> From<Toggler<Message>> for Element<'a, Message>
+where
+ Message: 'static,
+{
+ fn from(toggler: Toggler<Message>) -> Element<'a, Message> {
+ Element::new(toggler)
+ }
+}
diff --git a/wgpu/Cargo.toml b/wgpu/Cargo.toml
index 05088bbd..9675797d 100644
--- a/wgpu/Cargo.toml
+++ b/wgpu/Cargo.toml
@@ -1,6 +1,6 @@
[package]
name = "iced_wgpu"
-version = "0.2.2"
+version = "0.4.0"
authors = ["Héctor Ramón Jiménez <hector0193@gmail.com>"]
edition = "2018"
description = "A wgpu renderer for Iced"
@@ -8,37 +8,57 @@ license = "MIT AND OFL-1.1"
repository = "https://github.com/hecrj/iced"
[features]
-svg = ["resvg"]
+svg = ["resvg", "usvg"]
+image = ["png", "jpeg", "jpeg_rayon", "gif", "webp", "bmp"]
+png = ["image_rs/png"]
+jpeg = ["image_rs/jpeg"]
+jpeg_rayon = ["image_rs/jpeg_rayon"]
+gif = ["image_rs/gif"]
+webp = ["image_rs/webp"]
+pnm = ["image_rs/pnm"]
+ico = ["image_rs/ico"]
+bmp = ["image_rs/bmp"]
+hdr = ["image_rs/hdr"]
+dds = ["image_rs/dds"]
+farbfeld = ["image_rs/farbfeld"]
canvas = ["iced_graphics/canvas"]
+qr_code = ["iced_graphics/qr_code"]
default_system_font = ["iced_graphics/font-source"]
[dependencies]
-wgpu = "0.6"
-wgpu_glyph = "0.10"
+wgpu = "0.9"
+wgpu_glyph = "0.13"
glyph_brush = "0.7"
-zerocopy = "0.3"
-bytemuck = "1.2"
raw-window-handle = "0.3"
log = "0.4"
-guillotiere = "0.5"
+guillotiere = "0.6"
futures = "0.3"
+[dependencies.bytemuck]
+version = "1.4"
+features = ["derive"]
+
[dependencies.iced_native]
-version = "0.2"
+version = "0.4"
path = "../native"
[dependencies.iced_graphics]
-version = "0.1"
+version = "0.2"
path = "../graphics"
features = ["font-fallback", "font-icons"]
-[dependencies.image]
+[dependencies.image_rs]
version = "0.23"
+package = "image"
+default-features = false
optional = true
[dependencies.resvg]
-version = "0.9"
-features = ["raqote-backend"]
+version = "0.12"
+optional = true
+
+[dependencies.usvg]
+version = "0.12"
optional = true
[package.metadata.docs.rs]
diff --git a/wgpu/README.md b/wgpu/README.md
index 67cb59dd..a9d95ea7 100644
--- a/wgpu/README.md
+++ b/wgpu/README.md
@@ -15,7 +15,9 @@ Currently, `iced_wgpu` supports the following primitives:
- Images and SVG, loaded from memory or the file system.
- Meshes of triangles, useful to draw geometry freely.
-![iced_wgpu](../docs/graphs/wgpu.png)
+<p align="center">
+ <img alt="The native target" src="../docs/graphs/native.png" width="80%">
+</p>
[documentation]: https://docs.rs/iced_wgpu
[`iced_native`]: ../native
@@ -27,7 +29,7 @@ Currently, `iced_wgpu` supports the following primitives:
Add `iced_wgpu` as a dependency in your `Cargo.toml`:
```toml
-iced_wgpu = "0.2"
+iced_wgpu = "0.4"
```
__Iced moves fast and the `master` branch can contain breaking changes!__ If
diff --git a/wgpu/src/backend.rs b/wgpu/src/backend.rs
index 819d65c7..4f34045b 100644
--- a/wgpu/src/backend.rs
+++ b/wgpu/src/backend.rs
@@ -9,7 +9,7 @@ use iced_graphics::{Primitive, Viewport};
use iced_native::mouse;
use iced_native::{Font, HorizontalAlignment, Size, VerticalAlignment};
-#[cfg(any(feature = "image", feature = "svg"))]
+#[cfg(any(feature = "image_rs", feature = "svg"))]
use crate::image;
/// A [`wgpu`] graphics backend for [`iced`].
@@ -22,7 +22,7 @@ pub struct Backend {
text_pipeline: text::Pipeline,
triangle_pipeline: triangle::Pipeline,
- #[cfg(any(feature = "image", feature = "svg"))]
+ #[cfg(any(feature = "image_rs", feature = "svg"))]
image_pipeline: image::Pipeline,
default_text_size: u16,
@@ -30,27 +30,31 @@ pub struct Backend {
impl Backend {
/// Creates a new [`Backend`].
- ///
- /// [`Backend`]: struct.Backend.html
- pub fn new(device: &wgpu::Device, settings: Settings) -> Self {
- let text_pipeline =
- text::Pipeline::new(device, settings.format, settings.default_font);
- let quad_pipeline = quad::Pipeline::new(device, settings.format);
- let triangle_pipeline = triangle::Pipeline::new(
+ pub fn new(
+ device: &wgpu::Device,
+ settings: Settings,
+ format: wgpu::TextureFormat,
+ ) -> Self {
+ let text_pipeline = text::Pipeline::new(
device,
- settings.format,
- settings.antialiasing,
+ format,
+ settings.default_font,
+ settings.text_multithreading,
);
- #[cfg(any(feature = "image", feature = "svg"))]
- let image_pipeline = image::Pipeline::new(device, settings.format);
+ let quad_pipeline = quad::Pipeline::new(device, format);
+ let triangle_pipeline =
+ triangle::Pipeline::new(device, format, settings.antialiasing);
+
+ #[cfg(any(feature = "image_rs", feature = "svg"))]
+ let image_pipeline = image::Pipeline::new(device, format);
Self {
quad_pipeline,
text_pipeline,
triangle_pipeline,
- #[cfg(any(feature = "image", feature = "svg"))]
+ #[cfg(any(feature = "image_rs", feature = "svg"))]
image_pipeline,
default_text_size: settings.default_text_size,
@@ -94,7 +98,7 @@ impl Backend {
);
}
- #[cfg(any(feature = "image", feature = "svg"))]
+ #[cfg(any(feature = "image_rs", feature = "svg"))]
self.image_pipeline.trim_cache();
*mouse_interaction
@@ -144,7 +148,7 @@ impl Backend {
);
}
- #[cfg(any(feature = "image", feature = "svg"))]
+ #[cfg(any(feature = "image_rs", feature = "svg"))]
{
if !layer.images.is_empty() {
let scaled = transformation
@@ -272,7 +276,7 @@ impl backend::Text for Backend {
}
}
-#[cfg(feature = "image")]
+#[cfg(feature = "image_rs")]
impl backend::Image for Backend {
fn dimensions(&self, handle: &iced_native::image::Handle) -> (u32, u32) {
self.image_pipeline.dimensions(handle)
diff --git a/wgpu/src/image.rs b/wgpu/src/image.rs
index 891dba1e..85663bf5 100644
--- a/wgpu/src/image.rs
+++ b/wgpu/src/image.rs
@@ -1,6 +1,6 @@
mod atlas;
-#[cfg(feature = "image")]
+#[cfg(feature = "image_rs")]
mod raster;
#[cfg(feature = "svg")]
@@ -13,9 +13,10 @@ use iced_graphics::layer;
use iced_native::Rectangle;
use std::cell::RefCell;
use std::mem;
-use zerocopy::AsBytes;
-#[cfg(feature = "image")]
+use bytemuck::{Pod, Zeroable};
+
+#[cfg(feature = "image_rs")]
use iced_native::image;
#[cfg(feature = "svg")]
@@ -23,7 +24,7 @@ use iced_native::svg;
#[derive(Debug)]
pub struct Pipeline {
- #[cfg(feature = "image")]
+ #[cfg(feature = "image_rs")]
raster_cache: RefCell<raster::Cache>,
#[cfg(feature = "svg")]
vector_cache: RefCell<vector::Cache>,
@@ -61,8 +62,9 @@ impl Pipeline {
wgpu::BindGroupLayoutEntry {
binding: 0,
visibility: wgpu::ShaderStage::VERTEX,
- ty: wgpu::BindingType::UniformBuffer {
- dynamic: false,
+ ty: wgpu::BindingType::Buffer {
+ ty: wgpu::BufferBindingType::Uniform,
+ has_dynamic_offset: false,
min_binding_size: wgpu::BufferSize::new(
mem::size_of::<Uniforms>() as u64,
),
@@ -72,7 +74,10 @@ impl Pipeline {
wgpu::BindGroupLayoutEntry {
binding: 1,
visibility: wgpu::ShaderStage::FRAGMENT,
- ty: wgpu::BindingType::Sampler { comparison: false },
+ ty: wgpu::BindingType::Sampler {
+ comparison: false,
+ filtering: true,
+ },
count: None,
},
],
@@ -93,7 +98,11 @@ impl Pipeline {
wgpu::BindGroupEntry {
binding: 0,
resource: wgpu::BindingResource::Buffer(
- uniforms_buffer.slice(..),
+ wgpu::BufferBinding {
+ buffer: &uniforms_buffer,
+ offset: 0,
+ size: None,
+ },
),
},
wgpu::BindGroupEntry {
@@ -109,9 +118,11 @@ impl Pipeline {
entries: &[wgpu::BindGroupLayoutEntry {
binding: 0,
visibility: wgpu::ShaderStage::FRAGMENT,
- ty: wgpu::BindingType::SampledTexture {
- dimension: wgpu::TextureViewDimension::D2,
- component_type: wgpu::TextureComponentType::Float,
+ ty: wgpu::BindingType::Texture {
+ sample_type: wgpu::TextureSampleType::Float {
+ filterable: true,
+ },
+ view_dimension: wgpu::TextureViewDimension::D2Array,
multisampled: false,
},
count: None,
@@ -125,108 +136,89 @@ impl Pipeline {
bind_group_layouts: &[&constant_layout, &texture_layout],
});
- let vs_module = device.create_shader_module(wgpu::include_spirv!(
- "shader/image.vert.spv"
- ));
-
- let fs_module = device.create_shader_module(wgpu::include_spirv!(
- "shader/image.frag.spv"
- ));
+ let shader =
+ device.create_shader_module(&wgpu::ShaderModuleDescriptor {
+ label: Some("iced_wgpu::image::shader"),
+ source: wgpu::ShaderSource::Wgsl(std::borrow::Cow::Borrowed(
+ include_str!("shader/image.wgsl"),
+ )),
+ flags: wgpu::ShaderFlags::all(),
+ });
let pipeline =
device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
label: Some("iced_wgpu::image pipeline"),
layout: Some(&layout),
- vertex_stage: wgpu::ProgrammableStageDescriptor {
- module: &vs_module,
- entry_point: "main",
- },
- fragment_stage: Some(wgpu::ProgrammableStageDescriptor {
- module: &fs_module,
- entry_point: "main",
- }),
- rasterization_state: Some(wgpu::RasterizationStateDescriptor {
- front_face: wgpu::FrontFace::Cw,
- cull_mode: wgpu::CullMode::None,
- ..Default::default()
- }),
- primitive_topology: wgpu::PrimitiveTopology::TriangleList,
- color_states: &[wgpu::ColorStateDescriptor {
- format,
- color_blend: wgpu::BlendDescriptor {
- src_factor: wgpu::BlendFactor::SrcAlpha,
- dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha,
- operation: wgpu::BlendOperation::Add,
- },
- alpha_blend: wgpu::BlendDescriptor {
- src_factor: wgpu::BlendFactor::One,
- dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha,
- operation: wgpu::BlendOperation::Add,
- },
- write_mask: wgpu::ColorWrite::ALL,
- }],
- depth_stencil_state: None,
- vertex_state: wgpu::VertexStateDescriptor {
- index_format: wgpu::IndexFormat::Uint16,
- vertex_buffers: &[
- wgpu::VertexBufferDescriptor {
- stride: mem::size_of::<Vertex>() as u64,
+ vertex: wgpu::VertexState {
+ module: &shader,
+ entry_point: "vs_main",
+ buffers: &[
+ wgpu::VertexBufferLayout {
+ array_stride: mem::size_of::<Vertex>() as u64,
step_mode: wgpu::InputStepMode::Vertex,
- attributes: &[wgpu::VertexAttributeDescriptor {
+ attributes: &[wgpu::VertexAttribute {
shader_location: 0,
- format: wgpu::VertexFormat::Float2,
+ format: wgpu::VertexFormat::Float32x2,
offset: 0,
}],
},
- wgpu::VertexBufferDescriptor {
- stride: mem::size_of::<Instance>() as u64,
+ wgpu::VertexBufferLayout {
+ array_stride: mem::size_of::<Instance>() as u64,
step_mode: wgpu::InputStepMode::Instance,
- attributes: &[
- wgpu::VertexAttributeDescriptor {
- shader_location: 1,
- format: wgpu::VertexFormat::Float2,
- offset: 0,
- },
- wgpu::VertexAttributeDescriptor {
- shader_location: 2,
- format: wgpu::VertexFormat::Float2,
- offset: 4 * 2,
- },
- wgpu::VertexAttributeDescriptor {
- shader_location: 3,
- format: wgpu::VertexFormat::Float2,
- offset: 4 * 4,
- },
- wgpu::VertexAttributeDescriptor {
- shader_location: 4,
- format: wgpu::VertexFormat::Float2,
- offset: 4 * 6,
- },
- wgpu::VertexAttributeDescriptor {
- shader_location: 5,
- format: wgpu::VertexFormat::Uint,
- offset: 4 * 8,
- },
- ],
+ attributes: &wgpu::vertex_attr_array!(
+ 1 => Float32x2,
+ 2 => Float32x2,
+ 3 => Float32x2,
+ 4 => Float32x2,
+ 5 => Sint32,
+ ),
},
],
},
- sample_count: 1,
- sample_mask: !0,
- alpha_to_coverage_enabled: false,
+ fragment: Some(wgpu::FragmentState {
+ module: &shader,
+ entry_point: "fs_main",
+ targets: &[wgpu::ColorTargetState {
+ format,
+ blend: Some(wgpu::BlendState {
+ color: wgpu::BlendComponent {
+ src_factor: wgpu::BlendFactor::SrcAlpha,
+ dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha,
+ operation: wgpu::BlendOperation::Add,
+ },
+ alpha: wgpu::BlendComponent {
+ src_factor: wgpu::BlendFactor::One,
+ dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha,
+ operation: wgpu::BlendOperation::Add,
+ },
+ }),
+ write_mask: wgpu::ColorWrite::ALL,
+ }],
+ }),
+ primitive: wgpu::PrimitiveState {
+ topology: wgpu::PrimitiveTopology::TriangleList,
+ front_face: wgpu::FrontFace::Cw,
+ ..Default::default()
+ },
+ depth_stencil: None,
+ multisample: wgpu::MultisampleState {
+ count: 1,
+ mask: !0,
+ alpha_to_coverage_enabled: false,
+ },
});
let vertices =
device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
label: Some("iced_wgpu::image vertex buffer"),
- contents: QUAD_VERTS.as_bytes(),
+ contents: bytemuck::cast_slice(&QUAD_VERTS),
usage: wgpu::BufferUsage::VERTEX,
});
let indices =
device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
label: Some("iced_wgpu::image index buffer"),
- contents: QUAD_INDICES.as_bytes(),
+ contents: bytemuck::cast_slice(&QUAD_INDICES),
usage: wgpu::BufferUsage::INDEX,
});
@@ -251,7 +243,7 @@ impl Pipeline {
});
Pipeline {
- #[cfg(feature = "image")]
+ #[cfg(feature = "image_rs")]
raster_cache: RefCell::new(raster::Cache::new()),
#[cfg(feature = "svg")]
@@ -270,7 +262,7 @@ impl Pipeline {
}
}
- #[cfg(feature = "image")]
+ #[cfg(feature = "image_rs")]
pub fn dimensions(&self, handle: &image::Handle) -> (u32, u32) {
let mut cache = self.raster_cache.borrow_mut();
let memory = cache.load(&handle);
@@ -299,7 +291,7 @@ impl Pipeline {
) {
let instances: &mut Vec<Instance> = &mut Vec::new();
- #[cfg(feature = "image")]
+ #[cfg(feature = "image_rs")]
let mut raster_cache = self.raster_cache.borrow_mut();
#[cfg(feature = "svg")]
@@ -307,7 +299,7 @@ impl Pipeline {
for image in images {
match &image {
- #[cfg(feature = "image")]
+ #[cfg(feature = "image_rs")]
layer::Image::Raster { handle, bounds } => {
if let Some(atlas_entry) = raster_cache.upload(
handle,
@@ -323,7 +315,7 @@ impl Pipeline {
);
}
}
- #[cfg(not(feature = "image"))]
+ #[cfg(not(feature = "image_rs"))]
layer::Image::Raster { .. } => {}
#[cfg(feature = "svg")]
@@ -385,12 +377,9 @@ impl Pipeline {
device,
);
- uniforms_buffer.copy_from_slice(
- Uniforms {
- transform: transformation.into(),
- }
- .as_bytes(),
- );
+ uniforms_buffer.copy_from_slice(bytemuck::bytes_of(&Uniforms {
+ transform: transformation.into(),
+ }));
}
let mut i = 0;
@@ -411,28 +400,31 @@ impl Pipeline {
device,
);
- instances_buffer
- .copy_from_slice(instances[i..i + amount].as_bytes());
+ instances_buffer.copy_from_slice(bytemuck::cast_slice(
+ &instances[i..i + amount],
+ ));
let mut render_pass =
encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
- color_attachments: &[
- wgpu::RenderPassColorAttachmentDescriptor {
- attachment: target,
- resolve_target: None,
- ops: wgpu::Operations {
- load: wgpu::LoadOp::Load,
- store: true,
- },
+ label: Some("iced_wgpu::image render pass"),
+ color_attachments: &[wgpu::RenderPassColorAttachment {
+ view: target,
+ resolve_target: None,
+ ops: wgpu::Operations {
+ load: wgpu::LoadOp::Load,
+ store: true,
},
- ],
+ }],
depth_stencil_attachment: None,
});
render_pass.set_pipeline(&self.pipeline);
render_pass.set_bind_group(0, &self.constants, &[]);
render_pass.set_bind_group(1, &self.texture, &[]);
- render_pass.set_index_buffer(self.indices.slice(..));
+ render_pass.set_index_buffer(
+ self.indices.slice(..),
+ wgpu::IndexFormat::Uint16,
+ );
render_pass.set_vertex_buffer(0, self.vertices.slice(..));
render_pass.set_vertex_buffer(1, self.instances.slice(..));
@@ -454,7 +446,7 @@ impl Pipeline {
}
pub fn trim_cache(&mut self) {
- #[cfg(feature = "image")]
+ #[cfg(feature = "image_rs")]
self.raster_cache.borrow_mut().trim(&mut self.texture_atlas);
#[cfg(feature = "svg")]
@@ -463,7 +455,7 @@ impl Pipeline {
}
#[repr(C)]
-#[derive(Clone, Copy, AsBytes)]
+#[derive(Clone, Copy, Zeroable, Pod)]
pub struct Vertex {
_position: [f32; 2],
}
@@ -486,7 +478,7 @@ const QUAD_VERTS: [Vertex; 4] = [
];
#[repr(C)]
-#[derive(Debug, Clone, Copy, AsBytes)]
+#[derive(Debug, Clone, Copy, Zeroable, Pod)]
struct Instance {
_position: [f32; 2],
_size: [f32; 2],
@@ -500,7 +492,7 @@ impl Instance {
}
#[repr(C)]
-#[derive(Debug, Clone, Copy, AsBytes)]
+#[derive(Debug, Clone, Copy, Zeroable, Pod)]
struct Uniforms {
transform: [f32; 16],
}
diff --git a/wgpu/src/image/atlas.rs b/wgpu/src/image/atlas.rs
index 660ebe44..4855fa4a 100644
--- a/wgpu/src/image/atlas.rs
+++ b/wgpu/src/image/atlas.rs
@@ -4,6 +4,8 @@ mod allocation;
mod allocator;
mod layer;
+use std::num::NonZeroU32;
+
pub use allocation::Allocation;
pub use entry::Entry;
pub use layer::Layer;
@@ -24,7 +26,7 @@ impl Atlas {
let extent = wgpu::Extent3d {
width: SIZE,
height: SIZE,
- depth: 1,
+ depth_or_array_layers: 1,
};
let texture = device.create_texture(&wgpu::TextureDescriptor {
@@ -294,19 +296,19 @@ impl Atlas {
let extent = wgpu::Extent3d {
width,
height,
- depth: 1,
+ depth_or_array_layers: 1,
};
encoder.copy_buffer_to_texture(
- wgpu::BufferCopyView {
+ wgpu::ImageCopyBuffer {
buffer,
- layout: wgpu::TextureDataLayout {
+ layout: wgpu::ImageDataLayout {
offset: offset as u64,
- bytes_per_row: 4 * image_width + padding,
- rows_per_image: image_height,
+ bytes_per_row: NonZeroU32::new(4 * image_width + padding),
+ rows_per_image: NonZeroU32::new(image_height),
},
},
- wgpu::TextureCopyView {
+ wgpu::ImageCopyTexture {
texture: &self.texture,
mip_level: 0,
origin: wgpu::Origin3d {
@@ -334,7 +336,7 @@ impl Atlas {
size: wgpu::Extent3d {
width: SIZE,
height: SIZE,
- depth: self.layers.len() as u32,
+ depth_or_array_layers: self.layers.len() as u32,
},
mip_level_count: 1,
sample_count: 1,
@@ -355,7 +357,7 @@ impl Atlas {
}
encoder.copy_texture_to_texture(
- wgpu::TextureCopyView {
+ wgpu::ImageCopyTexture {
texture: &self.texture,
mip_level: 0,
origin: wgpu::Origin3d {
@@ -364,7 +366,7 @@ impl Atlas {
z: i as u32,
},
},
- wgpu::TextureCopyView {
+ wgpu::ImageCopyTexture {
texture: &new_texture,
mip_level: 0,
origin: wgpu::Origin3d {
@@ -376,7 +378,7 @@ impl Atlas {
wgpu::Extent3d {
width: SIZE,
height: SIZE,
- depth: 1,
+ depth_or_array_layers: 1,
},
);
}
diff --git a/wgpu/src/image/atlas/entry.rs b/wgpu/src/image/atlas/entry.rs
index 0310fc54..9b3f16df 100644
--- a/wgpu/src/image/atlas/entry.rs
+++ b/wgpu/src/image/atlas/entry.rs
@@ -10,7 +10,7 @@ pub enum Entry {
}
impl Entry {
- #[cfg(feature = "image")]
+ #[cfg(feature = "image_rs")]
pub fn size(&self) -> (u32, u32) {
match self {
Entry::Contiguous(allocation) => allocation.size(),
diff --git a/wgpu/src/image/raster.rs b/wgpu/src/image/raster.rs
index 4f69df8c..d5c62545 100644
--- a/wgpu/src/image/raster.rs
+++ b/wgpu/src/image/raster.rs
@@ -4,7 +4,7 @@ use std::collections::{HashMap, HashSet};
#[derive(Debug)]
pub enum Memory {
- Host(::image::ImageBuffer<::image::Bgra<u8>, Vec<u8>>),
+ Host(::image_rs::ImageBuffer<::image_rs::Bgra<u8>, Vec<u8>>),
Device(atlas::Entry),
NotFound,
Invalid,
@@ -42,15 +42,15 @@ impl Cache {
let memory = match handle.data() {
image::Data::Path(path) => {
- if let Ok(image) = ::image::open(path) {
- Memory::Host(image.to_bgra())
+ if let Ok(image) = ::image_rs::open(path) {
+ Memory::Host(image.to_bgra8())
} else {
Memory::NotFound
}
}
image::Data::Bytes(bytes) => {
- if let Ok(image) = ::image::load_from_memory(&bytes) {
- Memory::Host(image.to_bgra())
+ if let Ok(image) = ::image_rs::load_from_memory(&bytes) {
+ Memory::Host(image.to_bgra8())
} else {
Memory::Invalid
}
@@ -60,7 +60,7 @@ impl Cache {
height,
pixels,
} => {
- if let Some(image) = ::image::ImageBuffer::from_vec(
+ if let Some(image) = ::image_rs::ImageBuffer::from_vec(
*width,
*height,
pixels.to_vec(),
diff --git a/wgpu/src/image/vector.rs b/wgpu/src/image/vector.rs
index 7648aa7e..cd511a45 100644
--- a/wgpu/src/image/vector.rs
+++ b/wgpu/src/image/vector.rs
@@ -1,11 +1,10 @@
-use crate::image::atlas::{self, Atlas};
use iced_native::svg;
use std::collections::{HashMap, HashSet};
-use zerocopy::AsBytes;
+use crate::image::atlas::{self, Atlas};
pub enum Svg {
- Loaded(resvg::usvg::Tree),
+ Loaded(usvg::Tree),
NotFound,
}
@@ -45,17 +44,15 @@ impl Cache {
return self.svgs.get(&handle.id()).unwrap();
}
- let opt = resvg::Options::default();
-
let svg = match handle.data() {
svg::Data::Path(path) => {
- match resvg::usvg::Tree::from_file(path, &opt.usvg) {
+ match usvg::Tree::from_file(path, &Default::default()) {
Ok(tree) => Svg::Loaded(tree),
Err(_) => Svg::NotFound,
}
}
svg::Data::Bytes(bytes) => {
- match resvg::usvg::Tree::from_data(&bytes, &opt.usvg) {
+ match usvg::Tree::from_data(&bytes, &Default::default()) {
Ok(tree) => Svg::Loaded(tree),
Err(_) => Svg::NotFound,
}
@@ -78,8 +75,8 @@ impl Cache {
let id = handle.id();
let (width, height) = (
- (scale * width).round() as u32,
- (scale * height).round() as u32,
+ (scale * width).ceil() as u32,
+ (scale * height).ceil() as u32,
);
// TODO: Optimize!
@@ -103,26 +100,29 @@ impl Cache {
// We currently rerasterize the SVG when its size changes. This is slow
// as heck. A GPU rasterizer like `pathfinder` may perform better.
// It would be cool to be able to smooth resize the `svg` example.
- let screen_size =
- resvg::ScreenSize::new(width, height).unwrap();
-
- let mut canvas =
- resvg::raqote::DrawTarget::new(width as i32, height as i32);
-
- resvg::backend_raqote::render_to_canvas(
+ let img = resvg::render(
tree,
- &resvg::Options::default(),
- screen_size,
- &mut canvas,
- );
+ if width > height {
+ usvg::FitTo::Width(width)
+ } else {
+ usvg::FitTo::Height(height)
+ },
+ None,
+ )?;
+ let width = img.width();
+ let height = img.height();
+
+ let mut rgba = img.take();
+ rgba.chunks_exact_mut(4).for_each(|rgba| rgba.swap(0, 2));
let allocation = texture_atlas.upload(
width,
height,
- canvas.get_data().as_bytes(),
+ bytemuck::cast_slice(rgba.as_slice()),
device,
encoder,
)?;
+ log::debug!("allocating {} {}x{}", id, width, height);
let _ = self.svg_hits.insert(id);
let _ = self.rasterized_hits.insert((id, width, height));
diff --git a/wgpu/src/lib.rs b/wgpu/src/lib.rs
index 5f44dd91..e868a655 100644
--- a/wgpu/src/lib.rs
+++ b/wgpu/src/lib.rs
@@ -1,6 +1,6 @@
//! A [`wgpu`] renderer for [`iced_native`].
//!
-//! ![`iced_wgpu` crate graph](https://github.com/hecrj/iced/blob/cae26cb7bc627f4a5b3bcf1cd023a0c552e8c65e/docs/graphs/wgpu.png?raw=true)
+//! ![The native path of the Iced ecosystem](https://github.com/hecrj/iced/blob/0525d76ff94e828b7b21634fa94a747022001c83/docs/graphs/native.png?raw=true)
//!
//! For now, it is the default renderer of [Iced] in native platforms.
//!
@@ -49,7 +49,7 @@ pub use widget::*;
pub(crate) use iced_graphics::Transformation;
-#[cfg(any(feature = "image", feature = "svg"))]
+#[cfg(any(feature = "image_rs", feature = "svg"))]
mod image;
/// A [`wgpu`] graphics renderer for [`iced`].
diff --git a/wgpu/src/quad.rs b/wgpu/src/quad.rs
index d54f2577..93942fba 100644
--- a/wgpu/src/quad.rs
+++ b/wgpu/src/quad.rs
@@ -2,9 +2,9 @@ use crate::Transformation;
use iced_graphics::layer;
use iced_native::Rectangle;
+use bytemuck::{Pod, Zeroable};
use std::mem;
use wgpu::util::DeviceExt;
-use zerocopy::AsBytes;
#[derive(Debug)]
pub struct Pipeline {
@@ -24,10 +24,11 @@ impl Pipeline {
entries: &[wgpu::BindGroupLayoutEntry {
binding: 0,
visibility: wgpu::ShaderStage::VERTEX,
- ty: wgpu::BindingType::UniformBuffer {
- dynamic: false,
+ ty: wgpu::BindingType::Buffer {
+ ty: wgpu::BufferBindingType::Uniform,
+ has_dynamic_offset: false,
min_binding_size: wgpu::BufferSize::new(
- mem::size_of::<Uniforms>() as u64,
+ mem::size_of::<Uniforms>() as wgpu::BufferAddress,
),
},
count: None,
@@ -36,7 +37,7 @@ impl Pipeline {
let constants_buffer = device.create_buffer(&wgpu::BufferDescriptor {
label: Some("iced_wgpu::quad uniforms buffer"),
- size: mem::size_of::<Uniforms>() as u64,
+ size: mem::size_of::<Uniforms>() as wgpu::BufferAddress,
usage: wgpu::BufferUsage::UNIFORM | wgpu::BufferUsage::COPY_DST,
mapped_at_creation: false,
});
@@ -46,9 +47,7 @@ impl Pipeline {
layout: &constant_layout,
entries: &[wgpu::BindGroupEntry {
binding: 0,
- resource: wgpu::BindingResource::Buffer(
- constants_buffer.slice(..),
- ),
+ resource: constants_buffer.as_entire_binding(),
}],
});
@@ -59,111 +58,90 @@ impl Pipeline {
bind_group_layouts: &[&constant_layout],
});
- let vs_module = device
- .create_shader_module(wgpu::include_spirv!("shader/quad.vert.spv"));
-
- let fs_module = device
- .create_shader_module(wgpu::include_spirv!("shader/quad.frag.spv"));
+ let shader =
+ device.create_shader_module(&wgpu::ShaderModuleDescriptor {
+ label: Some("iced_wgpu::quad::shader"),
+ source: wgpu::ShaderSource::Wgsl(std::borrow::Cow::Borrowed(
+ include_str!("shader/quad.wgsl"),
+ )),
+ flags: wgpu::ShaderFlags::all(),
+ });
let pipeline =
device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
label: Some("iced_wgpu::quad pipeline"),
layout: Some(&layout),
- vertex_stage: wgpu::ProgrammableStageDescriptor {
- module: &vs_module,
- entry_point: "main",
- },
- fragment_stage: Some(wgpu::ProgrammableStageDescriptor {
- module: &fs_module,
- entry_point: "main",
- }),
- rasterization_state: Some(wgpu::RasterizationStateDescriptor {
- front_face: wgpu::FrontFace::Cw,
- cull_mode: wgpu::CullMode::None,
- ..Default::default()
- }),
- primitive_topology: wgpu::PrimitiveTopology::TriangleList,
- color_states: &[wgpu::ColorStateDescriptor {
- format,
- color_blend: wgpu::BlendDescriptor {
- src_factor: wgpu::BlendFactor::SrcAlpha,
- dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha,
- operation: wgpu::BlendOperation::Add,
- },
- alpha_blend: wgpu::BlendDescriptor {
- src_factor: wgpu::BlendFactor::One,
- dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha,
- operation: wgpu::BlendOperation::Add,
- },
- write_mask: wgpu::ColorWrite::ALL,
- }],
- depth_stencil_state: None,
- vertex_state: wgpu::VertexStateDescriptor {
- index_format: wgpu::IndexFormat::Uint16,
- vertex_buffers: &[
- wgpu::VertexBufferDescriptor {
- stride: mem::size_of::<Vertex>() as u64,
+ vertex: wgpu::VertexState {
+ module: &shader,
+ entry_point: "vs_main",
+ buffers: &[
+ wgpu::VertexBufferLayout {
+ array_stride: mem::size_of::<Vertex>() as u64,
step_mode: wgpu::InputStepMode::Vertex,
- attributes: &[wgpu::VertexAttributeDescriptor {
+ attributes: &[wgpu::VertexAttribute {
shader_location: 0,
- format: wgpu::VertexFormat::Float2,
+ format: wgpu::VertexFormat::Float32x2,
offset: 0,
}],
},
- wgpu::VertexBufferDescriptor {
- stride: mem::size_of::<layer::Quad>() as u64,
+ wgpu::VertexBufferLayout {
+ array_stride: mem::size_of::<layer::Quad>() as u64,
step_mode: wgpu::InputStepMode::Instance,
- attributes: &[
- wgpu::VertexAttributeDescriptor {
- shader_location: 1,
- format: wgpu::VertexFormat::Float2,
- offset: 0,
- },
- wgpu::VertexAttributeDescriptor {
- shader_location: 2,
- format: wgpu::VertexFormat::Float2,
- offset: 4 * 2,
- },
- wgpu::VertexAttributeDescriptor {
- shader_location: 3,
- format: wgpu::VertexFormat::Float4,
- offset: 4 * (2 + 2),
- },
- wgpu::VertexAttributeDescriptor {
- shader_location: 4,
- format: wgpu::VertexFormat::Float4,
- offset: 4 * (2 + 2 + 4),
- },
- wgpu::VertexAttributeDescriptor {
- shader_location: 5,
- format: wgpu::VertexFormat::Float,
- offset: 4 * (2 + 2 + 4 + 4),
- },
- wgpu::VertexAttributeDescriptor {
- shader_location: 6,
- format: wgpu::VertexFormat::Float,
- offset: 4 * (2 + 2 + 4 + 4 + 1),
- },
- ],
+ attributes: &wgpu::vertex_attr_array!(
+ 1 => Float32x2,
+ 2 => Float32x2,
+ 3 => Float32x4,
+ 4 => Float32x4,
+ 5 => Float32,
+ 6 => Float32,
+ ),
},
],
},
- sample_count: 1,
- sample_mask: !0,
- alpha_to_coverage_enabled: false,
+ fragment: Some(wgpu::FragmentState {
+ module: &shader,
+ entry_point: "fs_main",
+ targets: &[wgpu::ColorTargetState {
+ format,
+ blend: Some(wgpu::BlendState {
+ color: wgpu::BlendComponent {
+ src_factor: wgpu::BlendFactor::SrcAlpha,
+ dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha,
+ operation: wgpu::BlendOperation::Add,
+ },
+ alpha: wgpu::BlendComponent {
+ src_factor: wgpu::BlendFactor::One,
+ dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha,
+ operation: wgpu::BlendOperation::Add,
+ },
+ }),
+ write_mask: wgpu::ColorWrite::ALL,
+ }],
+ }),
+ primitive: wgpu::PrimitiveState {
+ topology: wgpu::PrimitiveTopology::TriangleList,
+ front_face: wgpu::FrontFace::Cw,
+ ..Default::default()
+ },
+ depth_stencil: None,
+ multisample: wgpu::MultisampleState {
+ count: 1,
+ mask: !0,
+ alpha_to_coverage_enabled: false,
+ },
});
let vertices =
device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
label: Some("iced_wgpu::quad vertex buffer"),
- contents: QUAD_VERTS.as_bytes(),
+ contents: bytemuck::cast_slice(&QUAD_VERTS),
usage: wgpu::BufferUsage::VERTEX,
});
let indices =
device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
label: Some("iced_wgpu::quad index buffer"),
- contents: QUAD_INDICES.as_bytes(),
+ contents: bytemuck::cast_slice(&QUAD_INDICES),
usage: wgpu::BufferUsage::INDEX,
});
@@ -207,7 +185,7 @@ impl Pipeline {
device,
);
- constants_buffer.copy_from_slice(uniforms.as_bytes());
+ constants_buffer.copy_from_slice(bytemuck::bytes_of(&uniforms));
}
let mut i = 0;
@@ -232,30 +210,33 @@ impl Pipeline {
{
let mut render_pass =
encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
- color_attachments: &[
- wgpu::RenderPassColorAttachmentDescriptor {
- attachment: target,
- resolve_target: None,
- ops: wgpu::Operations {
- load: wgpu::LoadOp::Load,
- store: true,
- },
+ label: Some("iced_wgpu::quad render pass"),
+ color_attachments: &[wgpu::RenderPassColorAttachment {
+ view: target,
+ resolve_target: None,
+ ops: wgpu::Operations {
+ load: wgpu::LoadOp::Load,
+ store: true,
},
- ],
+ }],
depth_stencil_attachment: None,
});
render_pass.set_pipeline(&self.pipeline);
render_pass.set_bind_group(0, &self.constants, &[]);
- render_pass.set_index_buffer(self.indices.slice(..));
+ render_pass.set_index_buffer(
+ self.indices.slice(..),
+ wgpu::IndexFormat::Uint16,
+ );
render_pass.set_vertex_buffer(0, self.vertices.slice(..));
render_pass.set_vertex_buffer(1, self.instances.slice(..));
+
render_pass.set_scissor_rect(
bounds.x,
bounds.y,
bounds.width,
// TODO: Address anti-aliasing adjustments properly
- bounds.height + 1,
+ bounds.height,
);
render_pass.draw_indexed(
@@ -271,7 +252,7 @@ impl Pipeline {
}
#[repr(C)]
-#[derive(Clone, Copy, AsBytes)]
+#[derive(Clone, Copy, Zeroable, Pod)]
pub struct Vertex {
_position: [f32; 2],
}
@@ -296,10 +277,13 @@ const QUAD_VERTS: [Vertex; 4] = [
const MAX_INSTANCES: usize = 100_000;
#[repr(C)]
-#[derive(Debug, Clone, Copy, AsBytes)]
+#[derive(Debug, Clone, Copy, Zeroable, Pod)]
struct Uniforms {
transform: [f32; 16],
scale: f32,
+ // Uniforms must be aligned to their largest member,
+ // this uses a mat4x4<f32> which aligns to 16, so align to that
+ _padding: [f32; 3],
}
impl Uniforms {
@@ -307,6 +291,7 @@ impl Uniforms {
Self {
transform: *transformation.as_ref(),
scale,
+ _padding: [0.0; 3],
}
}
}
@@ -316,6 +301,7 @@ impl Default for Uniforms {
Self {
transform: *Transformation::identity().as_ref(),
scale: 1.0,
+ _padding: [0.0; 3],
}
}
}
diff --git a/wgpu/src/settings.rs b/wgpu/src/settings.rs
index bc146c4c..dc06b82d 100644
--- a/wgpu/src/settings.rs
+++ b/wgpu/src/settings.rs
@@ -1,15 +1,18 @@
//! Configure a renderer.
pub use crate::Antialiasing;
-/// The settings of a [`Renderer`].
+/// The settings of a [`Backend`].
///
-/// [`Renderer`]: ../struct.Renderer.html
+/// [`Backend`]: crate::Backend
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Settings {
- /// The output format of the [`Renderer`].
+ /// The present mode of the [`Backend`].
///
- /// [`Renderer`]: ../struct.Renderer.html
- pub format: wgpu::TextureFormat,
+ /// [`Backend`]: crate::Backend
+ pub present_mode: wgpu::PresentMode,
+
+ /// The internal graphics backend to use.
+ pub internal_backend: wgpu::BackendBit,
/// The bytes of the font that will be used by default.
///
@@ -21,17 +24,66 @@ pub struct Settings {
/// By default, it will be set to 20.
pub default_text_size: u16,
+ /// If enabled, spread text workload in multiple threads when multiple cores
+ /// are available.
+ ///
+ /// By default, it is disabled.
+ pub text_multithreading: bool,
+
/// The antialiasing strategy that will be used for triangle primitives.
+ ///
+ /// By default, it is `None`.
pub antialiasing: Option<Antialiasing>,
}
+impl Settings {
+ /// Creates new [`Settings`] using environment configuration.
+ ///
+ /// Specifically:
+ ///
+ /// - The `internal_backend` can be configured using the `WGPU_BACKEND`
+ /// environment variable. If the variable is not set, the primary backend
+ /// will be used. The following values are allowed:
+ /// - `vulkan`
+ /// - `metal`
+ /// - `dx12`
+ /// - `dx11`
+ /// - `gl`
+ /// - `webgpu`
+ /// - `primary`
+ pub fn from_env() -> Self {
+ Settings {
+ internal_backend: backend_from_env()
+ .unwrap_or(wgpu::BackendBit::PRIMARY),
+ ..Self::default()
+ }
+ }
+}
+
impl Default for Settings {
fn default() -> Settings {
Settings {
- format: wgpu::TextureFormat::Bgra8UnormSrgb,
+ present_mode: wgpu::PresentMode::Mailbox,
+ internal_backend: wgpu::BackendBit::PRIMARY,
default_font: None,
default_text_size: 20,
+ text_multithreading: false,
antialiasing: None,
}
}
}
+
+fn backend_from_env() -> Option<wgpu::BackendBit> {
+ std::env::var("WGPU_BACKEND").ok().map(|backend| {
+ match backend.to_lowercase().as_str() {
+ "vulkan" => wgpu::BackendBit::VULKAN,
+ "metal" => wgpu::BackendBit::METAL,
+ "dx12" => wgpu::BackendBit::DX12,
+ "dx11" => wgpu::BackendBit::DX11,
+ "gl" => wgpu::BackendBit::GL,
+ "webgpu" => wgpu::BackendBit::BROWSER_WEBGPU,
+ "primary" => wgpu::BackendBit::PRIMARY,
+ other => panic!("Unknown backend: {}", other),
+ }
+ })
+}
diff --git a/wgpu/src/shader/blit.frag b/wgpu/src/shader/blit.frag
deleted file mode 100644
index dfed960f..00000000
--- a/wgpu/src/shader/blit.frag
+++ /dev/null
@@ -1,12 +0,0 @@
-#version 450
-
-layout(location = 0) in vec2 v_Uv;
-
-layout(set = 0, binding = 0) uniform sampler u_Sampler;
-layout(set = 1, binding = 0) uniform texture2D u_Texture;
-
-layout(location = 0) out vec4 o_Color;
-
-void main() {
- o_Color = texture(sampler2D(u_Texture, u_Sampler), v_Uv);
-}
diff --git a/wgpu/src/shader/blit.frag.spv b/wgpu/src/shader/blit.frag.spv
deleted file mode 100644
index 2c5638b5..00000000
--- a/wgpu/src/shader/blit.frag.spv
+++ /dev/null
Binary files differ
diff --git a/wgpu/src/shader/blit.vert b/wgpu/src/shader/blit.vert
deleted file mode 100644
index 899cd39d..00000000
--- a/wgpu/src/shader/blit.vert
+++ /dev/null
@@ -1,26 +0,0 @@
-#version 450
-
-layout(location = 0) out vec2 o_Uv;
-
-const vec2 positions[6] = vec2[6](
- vec2(-1.0, 1.0),
- vec2(-1.0, -1.0),
- vec2(1.0, -1.0),
- vec2(-1.0, 1.0),
- vec2(1.0, 1.0),
- vec2(1.0, -1.0)
-);
-
-const vec2 uvs[6] = vec2[6](
- vec2(0.0, 0.0),
- vec2(0.0, 1.0),
- vec2(1.0, 1.0),
- vec2(0.0, 0.0),
- vec2(1.0, 0.0),
- vec2(1.0, 1.0)
-);
-
-void main() {
- o_Uv = uvs[gl_VertexIndex];
- gl_Position = vec4(positions[gl_VertexIndex], 0.0, 1.0);
-}
diff --git a/wgpu/src/shader/blit.vert.spv b/wgpu/src/shader/blit.vert.spv
deleted file mode 100644
index e0b436ce..00000000
--- a/wgpu/src/shader/blit.vert.spv
+++ /dev/null
Binary files differ
diff --git a/wgpu/src/shader/blit.wgsl b/wgpu/src/shader/blit.wgsl
new file mode 100644
index 00000000..694f192e
--- /dev/null
+++ b/wgpu/src/shader/blit.wgsl
@@ -0,0 +1,43 @@
+var positions: array<vec2<f32>, 6> = array<vec2<f32>, 6>(
+ vec2<f32>(-1.0, 1.0),
+ vec2<f32>(-1.0, -1.0),
+ vec2<f32>(1.0, -1.0),
+ vec2<f32>(-1.0, 1.0),
+ vec2<f32>(1.0, 1.0),
+ vec2<f32>(1.0, -1.0)
+);
+
+var uvs: array<vec2<f32>, 6> = array<vec2<f32>, 6>(
+ vec2<f32>(0.0, 0.0),
+ vec2<f32>(0.0, 1.0),
+ vec2<f32>(1.0, 1.0),
+ vec2<f32>(0.0, 0.0),
+ vec2<f32>(1.0, 0.0),
+ vec2<f32>(1.0, 1.0)
+);
+
+[[group(0), binding(0)]] var u_sampler: sampler;
+[[group(1), binding(0)]] var u_texture: texture_2d<f32>;
+
+struct VertexInput {
+ [[builtin(vertex_index)]] vertex_index: u32;
+};
+
+struct VertexOutput {
+ [[builtin(position)]] position: vec4<f32>;
+ [[location(0)]] uv: vec2<f32>;
+};
+
+[[stage(vertex)]]
+fn vs_main(input: VertexInput) -> VertexOutput {
+ var out: VertexOutput;
+ out.uv = uvs[input.vertex_index];
+ out.position = vec4<f32>(positions[input.vertex_index], 0.0, 1.0);
+
+ return out;
+}
+
+[[stage(fragment)]]
+fn fs_main(input: VertexOutput) -> [[location(0)]] vec4<f32> {
+ return textureSample(u_texture, u_sampler, input.uv);
+}
diff --git a/wgpu/src/shader/image.frag b/wgpu/src/shader/image.frag
deleted file mode 100644
index 2809e9e6..00000000
--- a/wgpu/src/shader/image.frag
+++ /dev/null
@@ -1,12 +0,0 @@
-#version 450
-
-layout(location = 0) in vec3 v_Uv;
-
-layout(set = 0, binding = 1) uniform sampler u_Sampler;
-layout(set = 1, binding = 0) uniform texture2DArray u_Texture;
-
-layout(location = 0) out vec4 o_Color;
-
-void main() {
- o_Color = texture(sampler2DArray(u_Texture, u_Sampler), v_Uv);
-}
diff --git a/wgpu/src/shader/image.frag.spv b/wgpu/src/shader/image.frag.spv
deleted file mode 100644
index 65b08aa3..00000000
--- a/wgpu/src/shader/image.frag.spv
+++ /dev/null
Binary files differ
diff --git a/wgpu/src/shader/image.vert b/wgpu/src/shader/image.vert
deleted file mode 100644
index dab53cfe..00000000
--- a/wgpu/src/shader/image.vert
+++ /dev/null
@@ -1,27 +0,0 @@
-#version 450
-
-layout(location = 0) in vec2 v_Pos;
-layout(location = 1) in vec2 i_Pos;
-layout(location = 2) in vec2 i_Scale;
-layout(location = 3) in vec2 i_Atlas_Pos;
-layout(location = 4) in vec2 i_Atlas_Scale;
-layout(location = 5) in uint i_Layer;
-
-layout (set = 0, binding = 0) uniform Globals {
- mat4 u_Transform;
-};
-
-layout(location = 0) out vec3 o_Uv;
-
-void main() {
- o_Uv = vec3(v_Pos * i_Atlas_Scale + i_Atlas_Pos, i_Layer);
-
- mat4 i_Transform = mat4(
- vec4(i_Scale.x, 0.0, 0.0, 0.0),
- vec4(0.0, i_Scale.y, 0.0, 0.0),
- vec4(0.0, 0.0, 1.0, 0.0),
- vec4(i_Pos, 0.0, 1.0)
- );
-
- gl_Position = u_Transform * i_Transform * vec4(v_Pos, 0.0, 1.0);
-}
diff --git a/wgpu/src/shader/image.vert.spv b/wgpu/src/shader/image.vert.spv
deleted file mode 100644
index 21f5db2d..00000000
--- a/wgpu/src/shader/image.vert.spv
+++ /dev/null
Binary files differ
diff --git a/wgpu/src/shader/image.wgsl b/wgpu/src/shader/image.wgsl
new file mode 100644
index 00000000..a63ee8f6
--- /dev/null
+++ b/wgpu/src/shader/image.wgsl
@@ -0,0 +1,47 @@
+[[block]]
+struct Globals {
+ transform: mat4x4<f32>;
+};
+
+[[group(0), binding(0)]] var<uniform> globals: Globals;
+[[group(0), binding(1)]] var u_sampler: sampler;
+[[group(1), binding(0)]] var u_texture: texture_2d_array<f32>;
+
+struct VertexInput {
+ [[location(0)]] v_pos: vec2<f32>;
+ [[location(1)]] pos: vec2<f32>;
+ [[location(2)]] scale: vec2<f32>;
+ [[location(3)]] atlas_pos: vec2<f32>;
+ [[location(4)]] atlas_scale: vec2<f32>;
+ [[location(5)]] layer: i32;
+};
+
+struct VertexOutput {
+ [[builtin(position)]] position: vec4<f32>;
+ [[location(0)]] uv: vec2<f32>;
+ [[location(1)]] layer: f32; // this should be an i32, but naga currently reads that as requiring interpolation.
+};
+
+[[stage(vertex)]]
+fn vs_main(input: VertexInput) -> VertexOutput {
+ var out: VertexOutput;
+
+ out.uv = vec2<f32>(input.v_pos * input.atlas_scale + input.atlas_pos);
+ out.layer = f32(input.layer);
+
+ var transform: mat4x4<f32> = mat4x4<f32>(
+ vec4<f32>(input.scale.x, 0.0, 0.0, 0.0),
+ vec4<f32>(0.0, input.scale.y, 0.0, 0.0),
+ vec4<f32>(0.0, 0.0, 1.0, 0.0),
+ vec4<f32>(input.pos, 0.0, 1.0)
+ );
+
+ out.position = globals.transform * transform * vec4<f32>(input.v_pos, 0.0, 1.0);
+
+ return out;
+}
+
+[[stage(fragment)]]
+fn fs_main(input: VertexOutput) -> [[location(0)]] vec4<f32> {
+ return textureSample(u_texture, u_sampler, input.uv, i32(input.layer));
+}
diff --git a/wgpu/src/shader/quad.frag b/wgpu/src/shader/quad.frag
deleted file mode 100644
index ad1af1ad..00000000
--- a/wgpu/src/shader/quad.frag
+++ /dev/null
@@ -1,66 +0,0 @@
-#version 450
-
-layout(location = 0) in vec4 v_Color;
-layout(location = 1) in vec4 v_BorderColor;
-layout(location = 2) in vec2 v_Pos;
-layout(location = 3) in vec2 v_Scale;
-layout(location = 4) in float v_BorderRadius;
-layout(location = 5) in float v_BorderWidth;
-
-layout(location = 0) out vec4 o_Color;
-
-float distance(in vec2 frag_coord, in vec2 position, in vec2 size, float radius)
-{
- // TODO: Try SDF approach: https://www.shadertoy.com/view/wd3XRN
- vec2 inner_size = size - vec2(radius, radius) * 2.0;
- vec2 top_left = position + vec2(radius, radius);
- vec2 bottom_right = top_left + inner_size;
-
- vec2 top_left_distance = top_left - frag_coord;
- vec2 bottom_right_distance = frag_coord - bottom_right;
-
- vec2 distance = vec2(
- max(max(top_left_distance.x, bottom_right_distance.x), 0),
- max(max(top_left_distance.y, bottom_right_distance.y), 0)
- );
-
- return sqrt(distance.x * distance.x + distance.y * distance.y);
-}
-
-void main() {
- vec4 mixed_color;
-
- // TODO: Remove branching (?)
- if(v_BorderWidth > 0) {
- float internal_border = max(v_BorderRadius - v_BorderWidth, 0);
-
- float internal_distance = distance(
- gl_FragCoord.xy,
- v_Pos + vec2(v_BorderWidth),
- v_Scale - vec2(v_BorderWidth * 2.0),
- internal_border
- );
-
- float border_mix = smoothstep(
- max(internal_border - 0.5, 0.0),
- internal_border + 0.5,
- internal_distance
- );
-
- mixed_color = mix(v_Color, v_BorderColor, border_mix);
- } else {
- mixed_color = v_Color;
- }
-
- float d = distance(
- gl_FragCoord.xy,
- v_Pos,
- v_Scale,
- v_BorderRadius
- );
-
- float radius_alpha =
- 1.0 - smoothstep(max(v_BorderRadius - 0.5, 0), v_BorderRadius + 0.5, d);
-
- o_Color = vec4(mixed_color.xyz, mixed_color.w * radius_alpha);
-}
diff --git a/wgpu/src/shader/quad.frag.spv b/wgpu/src/shader/quad.frag.spv
deleted file mode 100644
index 519f5f01..00000000
--- a/wgpu/src/shader/quad.frag.spv
+++ /dev/null
Binary files differ
diff --git a/wgpu/src/shader/quad.vert b/wgpu/src/shader/quad.vert
deleted file mode 100644
index 1d9a4fd2..00000000
--- a/wgpu/src/shader/quad.vert
+++ /dev/null
@@ -1,42 +0,0 @@
-#version 450
-
-layout(location = 0) in vec2 v_Pos;
-layout(location = 1) in vec2 i_Pos;
-layout(location = 2) in vec2 i_Scale;
-layout(location = 3) in vec4 i_Color;
-layout(location = 4) in vec4 i_BorderColor;
-layout(location = 5) in float i_BorderRadius;
-layout(location = 6) in float i_BorderWidth;
-
-layout (set = 0, binding = 0) uniform Globals {
- mat4 u_Transform;
- float u_Scale;
-};
-
-layout(location = 0) out vec4 o_Color;
-layout(location = 1) out vec4 o_BorderColor;
-layout(location = 2) out vec2 o_Pos;
-layout(location = 3) out vec2 o_Scale;
-layout(location = 4) out float o_BorderRadius;
-layout(location = 5) out float o_BorderWidth;
-
-void main() {
- vec2 p_Pos = i_Pos * u_Scale;
- vec2 p_Scale = i_Scale * u_Scale;
-
- mat4 i_Transform = mat4(
- vec4(p_Scale.x + 1.0, 0.0, 0.0, 0.0),
- vec4(0.0, p_Scale.y + 1.0, 0.0, 0.0),
- vec4(0.0, 0.0, 1.0, 0.0),
- vec4(p_Pos - vec2(0.5, 0.5), 0.0, 1.0)
- );
-
- o_Color = i_Color;
- o_BorderColor = i_BorderColor;
- o_Pos = p_Pos;
- o_Scale = p_Scale;
- o_BorderRadius = i_BorderRadius * u_Scale;
- o_BorderWidth = i_BorderWidth * u_Scale;
-
- gl_Position = u_Transform * i_Transform * vec4(v_Pos, 0.0, 1.0);
-}
diff --git a/wgpu/src/shader/quad.vert.spv b/wgpu/src/shader/quad.vert.spv
deleted file mode 100644
index 7059b51b..00000000
--- a/wgpu/src/shader/quad.vert.spv
+++ /dev/null
Binary files differ
diff --git a/wgpu/src/shader/quad.wgsl b/wgpu/src/shader/quad.wgsl
new file mode 100644
index 00000000..80d733ab
--- /dev/null
+++ b/wgpu/src/shader/quad.wgsl
@@ -0,0 +1,122 @@
+[[block]]
+struct Globals {
+ transform: mat4x4<f32>;
+ scale: f32;
+};
+
+[[group(0), binding(0)]] var<uniform> globals: Globals;
+
+struct VertexInput {
+ [[location(0)]] v_pos: vec2<f32>;
+ [[location(1)]] pos: vec2<f32>;
+ [[location(2)]] scale: vec2<f32>;
+ [[location(3)]] color: vec4<f32>;
+ [[location(4)]] border_color: vec4<f32>;
+ [[location(5)]] border_radius: f32;
+ [[location(6)]] border_width: f32;
+};
+
+struct VertexOutput {
+ [[builtin(position)]] position: vec4<f32>;
+ [[location(0)]] color: vec4<f32>;
+ [[location(1)]] border_color: vec4<f32>;
+ [[location(2)]] pos: vec2<f32>;
+ [[location(3)]] scale: vec2<f32>;
+ [[location(4)]] border_radius: f32;
+ [[location(5)]] border_width: f32;
+};
+
+[[stage(vertex)]]
+fn vs_main(input: VertexInput) -> VertexOutput {
+ var out: VertexOutput;
+
+ var pos: vec2<f32> = input.pos * globals.scale;
+ var scale: vec2<f32> = input.scale * globals.scale;
+
+ var border_radius: f32 = min(
+ input.border_radius,
+ min(input.scale.x, input.scale.y) / 2.0
+ );
+
+ var transform: mat4x4<f32> = mat4x4<f32>(
+ vec4<f32>(scale.x + 1.0, 0.0, 0.0, 0.0),
+ vec4<f32>(0.0, scale.y + 1.0, 0.0, 0.0),
+ vec4<f32>(0.0, 0.0, 1.0, 0.0),
+ vec4<f32>(pos - vec2<f32>(0.5, 0.5), 0.0, 1.0)
+ );
+
+ out.color = input.color;
+ out.border_color = input.border_color;
+ out.pos = pos;
+ out.scale = scale;
+ out.border_radius = border_radius * globals.scale;
+ out.border_width = input.border_width * globals.scale;
+ out.position = globals.transform * transform * vec4<f32>(input.v_pos, 0.0, 1.0);
+
+ return out;
+}
+
+fn distance_alg(
+ frag_coord: vec2<f32>,
+ position: vec2<f32>,
+ size: vec2<f32>,
+ radius: f32
+) -> f32 {
+ var inner_size: vec2<f32> = size - vec2<f32>(radius, radius) * 2.0;
+ var top_left: vec2<f32> = position + vec2<f32>(radius, radius);
+ var bottom_right: vec2<f32> = top_left + inner_size;
+
+ var top_left_distance: vec2<f32> = top_left - frag_coord;
+ var bottom_right_distance: vec2<f32> = frag_coord - bottom_right;
+
+ var dist: vec2<f32> = vec2<f32>(
+ max(max(top_left_distance.x, bottom_right_distance.x), 0.0),
+ max(max(top_left_distance.y, bottom_right_distance.y), 0.0)
+ );
+
+ return sqrt(dist.x * dist.x + dist.y * dist.y);
+}
+
+
+[[stage(fragment)]]
+fn fs_main(
+ input: VertexOutput
+) -> [[location(0)]] vec4<f32> {
+ var mixed_color: vec4<f32> = input.color;
+
+ if (input.border_width > 0.0) {
+ var internal_border: f32 = max(
+ input.border_radius - input.border_width,
+ 0.0
+ );
+
+ var internal_distance: f32 = distance_alg(
+ vec2<f32>(input.position.x, input.position.y),
+ input.pos + vec2<f32>(input.border_width, input.border_width),
+ input.scale - vec2<f32>(input.border_width * 2.0, input.border_width * 2.0),
+ internal_border
+ );
+
+ var border_mix: f32 = smoothStep(
+ max(internal_border - 0.5, 0.0),
+ internal_border + 0.5,
+ internal_distance
+ );
+
+ mixed_color = mix(input.color, input.border_color, vec4<f32>(border_mix, border_mix, border_mix, border_mix));
+ }
+
+ var dist: f32 = distance_alg(
+ vec2<f32>(input.position.x, input.position.y),
+ input.pos,
+ input.scale,
+ input.border_radius
+ );
+
+ var radius_alpha: f32 = 1.0 - smoothStep(
+ max(input.border_radius - 0.5, 0.0),
+ input.border_radius + 0.5,
+ dist);
+
+ return vec4<f32>(mixed_color.x, mixed_color.y, mixed_color.z, mixed_color.w * radius_alpha);
+}
diff --git a/wgpu/src/shader/triangle.frag b/wgpu/src/shader/triangle.frag
deleted file mode 100644
index e39c45e7..00000000
--- a/wgpu/src/shader/triangle.frag
+++ /dev/null
@@ -1,8 +0,0 @@
-#version 450
-
-layout(location = 0) in vec4 i_Color;
-layout(location = 0) out vec4 o_Color;
-
-void main() {
- o_Color = i_Color;
-}
diff --git a/wgpu/src/shader/triangle.frag.spv b/wgpu/src/shader/triangle.frag.spv
deleted file mode 100644
index 11201872..00000000
--- a/wgpu/src/shader/triangle.frag.spv
+++ /dev/null
Binary files differ
diff --git a/wgpu/src/shader/triangle.vert b/wgpu/src/shader/triangle.vert
deleted file mode 100644
index 1f2c009b..00000000
--- a/wgpu/src/shader/triangle.vert
+++ /dev/null
@@ -1,15 +0,0 @@
-#version 450
-
-layout(location = 0) in vec2 i_Position;
-layout(location = 1) in vec4 i_Color;
-
-layout(location = 0) out vec4 o_Color;
-
-layout (set = 0, binding = 0) uniform Globals {
- mat4 u_Transform;
-};
-
-void main() {
- gl_Position = u_Transform * vec4(i_Position, 0.0, 1.0);
- o_Color = i_Color;
-}
diff --git a/wgpu/src/shader/triangle.vert.spv b/wgpu/src/shader/triangle.vert.spv
deleted file mode 100644
index 871f4f55..00000000
--- a/wgpu/src/shader/triangle.vert.spv
+++ /dev/null
Binary files differ
diff --git a/wgpu/src/shader/triangle.wgsl b/wgpu/src/shader/triangle.wgsl
new file mode 100644
index 00000000..96eaabcc
--- /dev/null
+++ b/wgpu/src/shader/triangle.wgsl
@@ -0,0 +1,31 @@
+[[block]]
+struct Globals {
+ transform: mat4x4<f32>;
+};
+
+[[group(0), binding(0)]] var<uniform> globals: Globals;
+
+struct VertexInput {
+ [[location(0)]] position: vec2<f32>;
+ [[location(1)]] color: vec4<f32>;
+};
+
+struct VertexOutput {
+ [[builtin(position)]] position: vec4<f32>;
+ [[location(0)]] color: vec4<f32>;
+};
+
+[[stage(vertex)]]
+fn vs_main(input: VertexInput) -> VertexOutput {
+ var out: VertexOutput;
+
+ out.color = input.color;
+ out.position = globals.transform * vec4<f32>(input.position, 0.0, 1.0);
+
+ return out;
+}
+
+[[stage(fragment)]]
+fn fs_main(input: VertexOutput) -> [[location(0)]] vec4<f32> {
+ return input.color;
+}
diff --git a/wgpu/src/text.rs b/wgpu/src/text.rs
index 78999cf8..2b5b94c9 100644
--- a/wgpu/src/text.rs
+++ b/wgpu/src/text.rs
@@ -15,10 +15,12 @@ impl Pipeline {
device: &wgpu::Device,
format: wgpu::TextureFormat,
default_font: Option<&[u8]>,
+ multithreading: bool,
) -> Self {
let default_font = default_font.map(|slice| slice.to_vec());
// TODO: Font customization
+ #[cfg(not(target_os = "ios"))]
#[cfg(feature = "default_system_font")]
let default_font = {
default_font.or_else(|| {
@@ -45,7 +47,7 @@ impl Pipeline {
let draw_brush =
wgpu_glyph::GlyphBrushBuilder::using_font(font.clone())
.initial_cache_size((2048, 2048))
- .draw_cache_multithread(false) // TODO: Expose as a configuration flag
+ .draw_cache_multithread(multithreading)
.build(device, format);
let measure_brush =
diff --git a/wgpu/src/triangle.rs b/wgpu/src/triangle.rs
index 53ce454b..65b2b7b0 100644
--- a/wgpu/src/triangle.rs
+++ b/wgpu/src/triangle.rs
@@ -1,8 +1,9 @@
//! Draw meshes of triangles.
use crate::{settings, Transformation};
use iced_graphics::layer;
+
+use bytemuck::{Pod, Zeroable};
use std::mem;
-use zerocopy::AsBytes;
pub use iced_graphics::triangle::{Mesh2D, Vertex2D};
@@ -85,8 +86,9 @@ impl Pipeline {
entries: &[wgpu::BindGroupLayoutEntry {
binding: 0,
visibility: wgpu::ShaderStage::VERTEX,
- ty: wgpu::BindingType::UniformBuffer {
- dynamic: true,
+ ty: wgpu::BindingType::Buffer {
+ ty: wgpu::BufferBindingType::Uniform,
+ has_dynamic_offset: true,
min_binding_size: wgpu::BufferSize::new(
mem::size_of::<Uniforms>() as u64,
),
@@ -109,9 +111,15 @@ impl Pipeline {
entries: &[wgpu::BindGroupEntry {
binding: 0,
resource: wgpu::BindingResource::Buffer(
- constants_buffer
- .raw
- .slice(0..std::mem::size_of::<Uniforms>() as u64),
+ wgpu::BufferBinding {
+ buffer: &constants_buffer.raw,
+ offset: 0,
+ size: wgpu::BufferSize::new(std::mem::size_of::<
+ Uniforms,
+ >(
+ )
+ as u64),
+ },
),
}],
});
@@ -123,73 +131,66 @@ impl Pipeline {
bind_group_layouts: &[&constants_layout],
});
- let vs_module = device.create_shader_module(wgpu::include_spirv!(
- "shader/triangle.vert.spv"
- ));
-
- let fs_module = device.create_shader_module(wgpu::include_spirv!(
- "shader/triangle.frag.spv"
- ));
+ let shader =
+ device.create_shader_module(&wgpu::ShaderModuleDescriptor {
+ label: Some("iced_wgpu::triangle::shader"),
+ source: wgpu::ShaderSource::Wgsl(std::borrow::Cow::Borrowed(
+ include_str!("shader/triangle.wgsl"),
+ )),
+ flags: wgpu::ShaderFlags::all(),
+ });
let pipeline =
device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
label: Some("iced_wgpu::triangle pipeline"),
layout: Some(&layout),
- vertex_stage: wgpu::ProgrammableStageDescriptor {
- module: &vs_module,
- entry_point: "main",
- },
- fragment_stage: Some(wgpu::ProgrammableStageDescriptor {
- module: &fs_module,
- entry_point: "main",
- }),
- rasterization_state: Some(wgpu::RasterizationStateDescriptor {
- front_face: wgpu::FrontFace::Cw,
- cull_mode: wgpu::CullMode::None,
- ..Default::default()
- }),
- primitive_topology: wgpu::PrimitiveTopology::TriangleList,
- color_states: &[wgpu::ColorStateDescriptor {
- format,
- color_blend: wgpu::BlendDescriptor {
- src_factor: wgpu::BlendFactor::SrcAlpha,
- dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha,
- operation: wgpu::BlendOperation::Add,
- },
- alpha_blend: wgpu::BlendDescriptor {
- src_factor: wgpu::BlendFactor::One,
- dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha,
- operation: wgpu::BlendOperation::Add,
- },
- write_mask: wgpu::ColorWrite::ALL,
- }],
- depth_stencil_state: None,
- vertex_state: wgpu::VertexStateDescriptor {
- index_format: wgpu::IndexFormat::Uint32,
- vertex_buffers: &[wgpu::VertexBufferDescriptor {
- stride: mem::size_of::<Vertex2D>() as u64,
+ vertex: wgpu::VertexState {
+ module: &shader,
+ entry_point: "vs_main",
+ buffers: &[wgpu::VertexBufferLayout {
+ array_stride: mem::size_of::<Vertex2D>() as u64,
step_mode: wgpu::InputStepMode::Vertex,
- attributes: &[
+ attributes: &wgpu::vertex_attr_array!(
// Position
- wgpu::VertexAttributeDescriptor {
- shader_location: 0,
- format: wgpu::VertexFormat::Float2,
- offset: 0,
- },
+ 0 => Float32x2,
// Color
- wgpu::VertexAttributeDescriptor {
- shader_location: 1,
- format: wgpu::VertexFormat::Float4,
- offset: 4 * 2,
+ 1 => Float32x4,
+ ),
+ }],
+ },
+ fragment: Some(wgpu::FragmentState {
+ module: &shader,
+ entry_point: "fs_main",
+ targets: &[wgpu::ColorTargetState {
+ format,
+ blend: Some(wgpu::BlendState {
+ color: wgpu::BlendComponent {
+ src_factor: wgpu::BlendFactor::SrcAlpha,
+ dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha,
+ operation: wgpu::BlendOperation::Add,
+ },
+ alpha: wgpu::BlendComponent {
+ src_factor: wgpu::BlendFactor::One,
+ dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha,
+ operation: wgpu::BlendOperation::Add,
},
- ],
+ }),
+ write_mask: wgpu::ColorWrite::ALL,
}],
+ }),
+ primitive: wgpu::PrimitiveState {
+ topology: wgpu::PrimitiveTopology::TriangleList,
+ front_face: wgpu::FrontFace::Cw,
+ ..Default::default()
+ },
+ depth_stencil: None,
+ multisample: wgpu::MultisampleState {
+ count: u32::from(
+ antialiasing.map(|a| a.sample_count()).unwrap_or(1),
+ ),
+ mask: !0,
+ alpha_to_coverage_enabled: false,
},
- sample_count: u32::from(
- antialiasing.map(|a| a.sample_count()).unwrap_or(1),
- ),
- sample_mask: !0,
- alpha_to_coverage_enabled: false,
});
Pipeline {
@@ -252,9 +253,13 @@ impl Pipeline {
entries: &[wgpu::BindGroupEntry {
binding: 0,
resource: wgpu::BindingResource::Buffer(
- self.uniforms_buffer.raw.slice(
- 0..std::mem::size_of::<Uniforms>() as u64,
- ),
+ wgpu::BufferBinding {
+ buffer: &self.uniforms_buffer.raw,
+ offset: 0,
+ size: wgpu::BufferSize::new(
+ std::mem::size_of::<Uniforms>() as u64,
+ ),
+ },
),
}],
});
@@ -322,7 +327,7 @@ impl Pipeline {
}
}
- let uniforms = uniforms.as_bytes();
+ let uniforms = bytemuck::cast_slice(&uniforms);
if let Some(uniforms_size) =
wgpu::BufferSize::new(uniforms.len() as u64)
@@ -355,13 +360,12 @@ impl Pipeline {
let mut render_pass =
encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
- color_attachments: &[
- wgpu::RenderPassColorAttachmentDescriptor {
- attachment,
- resolve_target,
- ops: wgpu::Operations { load, store: true },
- },
- ],
+ label: Some("iced_wgpu::triangle render pass"),
+ color_attachments: &[wgpu::RenderPassColorAttachment {
+ view: attachment,
+ resolve_target,
+ ops: wgpu::Operations { load, store: true },
+ }],
depth_stencil_attachment: None,
});
@@ -389,6 +393,7 @@ impl Pipeline {
self.index_buffer
.raw
.slice(index_offset * mem::size_of::<u32>() as u64..),
+ wgpu::IndexFormat::Uint32,
);
render_pass.set_vertex_buffer(
@@ -409,7 +414,7 @@ impl Pipeline {
}
#[repr(C)]
-#[derive(Debug, Clone, Copy, AsBytes)]
+#[derive(Debug, Clone, Copy, Zeroable, Pod)]
struct Uniforms {
transform: [f32; 16],
// We need to align this to 256 bytes to please `wgpu`...
diff --git a/wgpu/src/triangle/msaa.rs b/wgpu/src/triangle/msaa.rs
index db86f748..c099d518 100644
--- a/wgpu/src/triangle/msaa.rs
+++ b/wgpu/src/triangle/msaa.rs
@@ -20,9 +20,9 @@ impl Blit {
address_mode_u: wgpu::AddressMode::ClampToEdge,
address_mode_v: wgpu::AddressMode::ClampToEdge,
address_mode_w: wgpu::AddressMode::ClampToEdge,
- mag_filter: wgpu::FilterMode::Linear,
- min_filter: wgpu::FilterMode::Linear,
- mipmap_filter: wgpu::FilterMode::Linear,
+ mag_filter: wgpu::FilterMode::Nearest,
+ min_filter: wgpu::FilterMode::Nearest,
+ mipmap_filter: wgpu::FilterMode::Nearest,
..Default::default()
});
@@ -32,7 +32,10 @@ impl Blit {
entries: &[wgpu::BindGroupLayoutEntry {
binding: 0,
visibility: wgpu::ShaderStage::FRAGMENT,
- ty: wgpu::BindingType::Sampler { comparison: false },
+ ty: wgpu::BindingType::Sampler {
+ comparison: false,
+ filtering: false,
+ },
count: None,
}],
});
@@ -53,9 +56,11 @@ impl Blit {
entries: &[wgpu::BindGroupLayoutEntry {
binding: 0,
visibility: wgpu::ShaderStage::FRAGMENT,
- ty: wgpu::BindingType::SampledTexture {
- dimension: wgpu::TextureViewDimension::D2,
- component_type: wgpu::TextureComponentType::Float,
+ ty: wgpu::BindingType::Texture {
+ sample_type: wgpu::TextureSampleType::Float {
+ filterable: false,
+ },
+ view_dimension: wgpu::TextureViewDimension::D2,
multisampled: false,
},
count: None,
@@ -69,54 +74,55 @@ impl Blit {
bind_group_layouts: &[&constant_layout, &texture_layout],
});
- let vs_module = device.create_shader_module(wgpu::include_spirv!(
- "../shader/blit.vert.spv"
- ));
-
- let fs_module = device.create_shader_module(wgpu::include_spirv!(
- "../shader/blit.frag.spv"
- ));
+ let shader =
+ device.create_shader_module(&wgpu::ShaderModuleDescriptor {
+ label: Some("iced_wgpu::triangle::blit_shader"),
+ source: wgpu::ShaderSource::Wgsl(std::borrow::Cow::Borrowed(
+ include_str!("../shader/blit.wgsl"),
+ )),
+ flags: wgpu::ShaderFlags::all(),
+ });
let pipeline =
device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
label: Some("iced_wgpu::triangle::msaa pipeline"),
layout: Some(&layout),
- vertex_stage: wgpu::ProgrammableStageDescriptor {
- module: &vs_module,
- entry_point: "main",
+ vertex: wgpu::VertexState {
+ module: &shader,
+ entry_point: "vs_main",
+ buffers: &[],
},
- fragment_stage: Some(wgpu::ProgrammableStageDescriptor {
- module: &fs_module,
- entry_point: "main",
+ fragment: Some(wgpu::FragmentState {
+ module: &shader,
+ entry_point: "fs_main",
+ targets: &[wgpu::ColorTargetState {
+ format,
+ blend: Some(wgpu::BlendState {
+ color: wgpu::BlendComponent {
+ src_factor: wgpu::BlendFactor::SrcAlpha,
+ dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha,
+ operation: wgpu::BlendOperation::Add,
+ },
+ alpha: wgpu::BlendComponent {
+ src_factor: wgpu::BlendFactor::One,
+ dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha,
+ operation: wgpu::BlendOperation::Add,
+ },
+ }),
+ write_mask: wgpu::ColorWrite::ALL,
+ }],
}),
- rasterization_state: Some(wgpu::RasterizationStateDescriptor {
+ primitive: wgpu::PrimitiveState {
+ topology: wgpu::PrimitiveTopology::TriangleList,
front_face: wgpu::FrontFace::Cw,
- cull_mode: wgpu::CullMode::None,
..Default::default()
- }),
- primitive_topology: wgpu::PrimitiveTopology::TriangleList,
- color_states: &[wgpu::ColorStateDescriptor {
- format,
- color_blend: wgpu::BlendDescriptor {
- src_factor: wgpu::BlendFactor::SrcAlpha,
- dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha,
- operation: wgpu::BlendOperation::Add,
- },
- alpha_blend: wgpu::BlendDescriptor {
- src_factor: wgpu::BlendFactor::One,
- dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha,
- operation: wgpu::BlendOperation::Add,
- },
- write_mask: wgpu::ColorWrite::ALL,
- }],
- depth_stencil_state: None,
- vertex_state: wgpu::VertexStateDescriptor {
- index_format: wgpu::IndexFormat::Uint16,
- vertex_buffers: &[],
},
- sample_count: 1,
- sample_mask: !0,
- alpha_to_coverage_enabled: false,
+ depth_stencil: None,
+ multisample: wgpu::MultisampleState {
+ count: 1,
+ mask: !0,
+ alpha_to_coverage_enabled: false,
+ },
});
Blit {
@@ -172,16 +178,15 @@ impl Blit {
) {
let mut render_pass =
encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
- color_attachments: &[
- wgpu::RenderPassColorAttachmentDescriptor {
- attachment: target,
- resolve_target: None,
- ops: wgpu::Operations {
- load: wgpu::LoadOp::Load,
- store: true,
- },
+ label: Some("iced_wgpu::triangle::msaa render pass"),
+ color_attachments: &[wgpu::RenderPassColorAttachment {
+ view: target,
+ resolve_target: None,
+ ops: wgpu::Operations {
+ load: wgpu::LoadOp::Load,
+ store: true,
},
- ],
+ }],
depth_stencil_attachment: None,
});
@@ -217,7 +222,7 @@ impl Targets {
let extent = wgpu::Extent3d {
width,
height,
- depth: 1,
+ depth_or_array_layers: 1,
};
let attachment = device.create_texture(&wgpu::TextureDescriptor {
@@ -227,7 +232,7 @@ impl Targets {
sample_count,
dimension: wgpu::TextureDimension::D2,
format,
- usage: wgpu::TextureUsage::OUTPUT_ATTACHMENT,
+ usage: wgpu::TextureUsage::RENDER_ATTACHMENT,
});
let resolve = device.create_texture(&wgpu::TextureDescriptor {
@@ -237,7 +242,7 @@ impl Targets {
sample_count: 1,
dimension: wgpu::TextureDimension::D2,
format,
- usage: wgpu::TextureUsage::OUTPUT_ATTACHMENT
+ usage: wgpu::TextureUsage::RENDER_ATTACHMENT
| wgpu::TextureUsage::SAMPLED,
});
diff --git a/wgpu/src/widget.rs b/wgpu/src/widget.rs
index 1dae26f5..a575d036 100644
--- a/wgpu/src/widget.rs
+++ b/wgpu/src/widget.rs
@@ -20,6 +20,8 @@ pub mod rule;
pub mod scrollable;
pub mod slider;
pub mod text_input;
+pub mod toggler;
+pub mod tooltip;
#[doc(no_inline)]
pub use button::Button;
@@ -43,6 +45,10 @@ pub use scrollable::Scrollable;
pub use slider::Slider;
#[doc(no_inline)]
pub use text_input::TextInput;
+#[doc(no_inline)]
+pub use toggler::Toggler;
+#[doc(no_inline)]
+pub use tooltip::Tooltip;
#[cfg(feature = "canvas")]
#[cfg_attr(docsrs, doc(cfg(feature = "canvas")))]
@@ -52,6 +58,14 @@ pub mod canvas;
#[doc(no_inline)]
pub use canvas::Canvas;
+#[cfg(feature = "qr_code")]
+#[cfg_attr(docsrs, doc(cfg(feature = "qr_code")))]
+pub mod qr_code;
+
+#[cfg(feature = "qr_code")]
+#[doc(no_inline)]
+pub use qr_code::QRCode;
+
pub use iced_native::Space;
/// A container that distributes its contents vertically.
diff --git a/wgpu/src/widget/button.rs b/wgpu/src/widget/button.rs
index fee7a7f8..fc729cd5 100644
--- a/wgpu/src/widget/button.rs
+++ b/wgpu/src/widget/button.rs
@@ -1,9 +1,6 @@
//! Allow your users to perform actions by pressing a button.
//!
//! A [`Button`] has some local [`State`].
-//!
-//! [`Button`]: type.Button.html
-//! [`State`]: struct.State.html
use crate::Renderer;
pub use iced_graphics::button::{Style, StyleSheet};
diff --git a/wgpu/src/widget/canvas.rs b/wgpu/src/widget/canvas.rs
index bef34857..399dd19c 100644
--- a/wgpu/src/widget/canvas.rs
+++ b/wgpu/src/widget/canvas.rs
@@ -3,7 +3,4 @@
//! A [`Canvas`] widget can be used to draw different kinds of 2D shapes in a
//! [`Frame`]. It can be used for animation, data visualization, game graphics,
//! and more!
-//!
-//! [`Canvas`]: struct.Canvas.html
-//! [`Frame`]: struct.Frame.html
pub use iced_graphics::canvas::*;
diff --git a/wgpu/src/widget/pane_grid.rs b/wgpu/src/widget/pane_grid.rs
index 3c47b562..fc36862c 100644
--- a/wgpu/src/widget/pane_grid.rs
+++ b/wgpu/src/widget/pane_grid.rs
@@ -6,13 +6,12 @@
//! The [`pane_grid` example] showcases how to use a [`PaneGrid`] with resizing,
//! drag and drop, and hotkey support.
//!
-//! [`pane_grid` example]: https://github.com/hecrj/iced/tree/0.1/examples/pane_grid
-//! [`PaneGrid`]: type.PaneGrid.html
+//! [`pane_grid` example]: https://github.com/hecrj/iced/tree/0.3/examples/pane_grid
use crate::Renderer;
-pub use iced_native::pane_grid::{
- Axis, Configuration, Direction, DragEvent, Focus, KeyPressEvent, Node,
- Pane, ResizeEvent, Split, State,
+pub use iced_graphics::pane_grid::{
+ Axis, Configuration, Direction, DragEvent, Line, Node, Pane, ResizeEvent,
+ Split, State, StyleSheet,
};
/// A collection of panes distributed using either vertical or horizontal splits
@@ -24,13 +23,9 @@ pub use iced_native::pane_grid::{
pub type PaneGrid<'a, Message> = iced_native::PaneGrid<'a, Message, Renderer>;
/// The content of a [`Pane`].
-///
-/// [`Pane`]: struct.Pane.html
pub type Content<'a, Message> =
iced_native::pane_grid::Content<'a, Message, Renderer>;
/// The title bar of a [`Pane`].
-///
-/// [`Pane`]: struct.Pane.html
pub type TitleBar<'a, Message> =
iced_native::pane_grid::TitleBar<'a, Message, Renderer>;
diff --git a/wgpu/src/widget/progress_bar.rs b/wgpu/src/widget/progress_bar.rs
index a636a3a6..45a25d00 100644
--- a/wgpu/src/widget/progress_bar.rs
+++ b/wgpu/src/widget/progress_bar.rs
@@ -2,8 +2,6 @@
//!
//! A [`ProgressBar`] has a range of possible values and a current value,
//! as well as a length, height and style.
-//!
-//! [`ProgressBar`]: type.ProgressBar.html
use crate::Renderer;
pub use iced_graphics::progress_bar::{Style, StyleSheet};
diff --git a/wgpu/src/widget/qr_code.rs b/wgpu/src/widget/qr_code.rs
new file mode 100644
index 00000000..7b1c2408
--- /dev/null
+++ b/wgpu/src/widget/qr_code.rs
@@ -0,0 +1,2 @@
+//! Encode and display information in a QR code.
+pub use iced_graphics::qr_code::*;
diff --git a/wgpu/src/widget/slider.rs b/wgpu/src/widget/slider.rs
index 3a8c2595..9a269858 100644
--- a/wgpu/src/widget/slider.rs
+++ b/wgpu/src/widget/slider.rs
@@ -1,9 +1,6 @@
//! Display an interactive selector of a single value from a range of values.
//!
//! A [`Slider`] has some local [`State`].
-//!
-//! [`Slider`]: struct.Slider.html
-//! [`State`]: struct.State.html
use crate::Renderer;
pub use iced_graphics::slider::{Handle, HandleShape, Style, StyleSheet};
diff --git a/wgpu/src/widget/text_input.rs b/wgpu/src/widget/text_input.rs
index 1da3fbe6..db18b1cc 100644
--- a/wgpu/src/widget/text_input.rs
+++ b/wgpu/src/widget/text_input.rs
@@ -1,9 +1,6 @@
//! Display fields that can be filled with text.
//!
//! A [`TextInput`] has some local [`State`].
-//!
-//! [`TextInput`]: struct.TextInput.html
-//! [`State`]: struct.State.html
use crate::Renderer;
pub use iced_graphics::text_input::{Style, StyleSheet};
diff --git a/wgpu/src/widget/toggler.rs b/wgpu/src/widget/toggler.rs
new file mode 100644
index 00000000..dfcf759b
--- /dev/null
+++ b/wgpu/src/widget/toggler.rs
@@ -0,0 +1,9 @@
+//! Show toggle controls using togglers.
+use crate::Renderer;
+
+pub use iced_graphics::toggler::{Style, StyleSheet};
+
+/// A toggler that can be toggled
+///
+/// This is an alias of an `iced_native` toggler with an `iced_wgpu::Renderer`.
+pub type Toggler<Message> = iced_native::Toggler<Message, Renderer>;
diff --git a/wgpu/src/widget/tooltip.rs b/wgpu/src/widget/tooltip.rs
new file mode 100644
index 00000000..89ab3a15
--- /dev/null
+++ b/wgpu/src/widget/tooltip.rs
@@ -0,0 +1,6 @@
+//! Display a widget over another.
+/// A widget allowing the selection of a single value from a list of options.
+pub type Tooltip<'a, Message> =
+ iced_native::Tooltip<'a, Message, crate::Renderer>;
+
+pub use iced_native::tooltip::Position;
diff --git a/wgpu/src/window/compositor.rs b/wgpu/src/window/compositor.rs
index 79ffacdd..b60efd25 100644
--- a/wgpu/src/window/compositor.rs
+++ b/wgpu/src/window/compositor.rs
@@ -13,6 +13,7 @@ pub struct Compositor {
queue: wgpu::Queue,
staging_belt: wgpu::util::StagingBelt,
local_pool: futures::executor::LocalPool,
+ format: wgpu::TextureFormat,
}
impl Compositor {
@@ -21,32 +22,42 @@ impl Compositor {
/// Requests a new [`Compositor`] with the given [`Settings`].
///
/// Returns `None` if no compatible graphics adapter could be found.
- ///
- /// [`Compositor`]: struct.Compositor.html
- /// [`Settings`]: struct.Settings.html
- pub async fn request(settings: Settings) -> Option<Self> {
- let instance = wgpu::Instance::new(wgpu::BackendBit::PRIMARY);
+ pub async fn request<W: HasRawWindowHandle>(
+ settings: Settings,
+ compatible_window: Option<&W>,
+ ) -> Option<Self> {
+ let instance = wgpu::Instance::new(settings.internal_backend);
+
+ #[allow(unsafe_code)]
+ let compatible_surface = compatible_window
+ .map(|window| unsafe { instance.create_surface(window) });
let adapter = instance
.request_adapter(&wgpu::RequestAdapterOptions {
power_preference: if settings.antialiasing.is_none() {
- wgpu::PowerPreference::Default
+ wgpu::PowerPreference::LowPower
} else {
wgpu::PowerPreference::HighPerformance
},
- compatible_surface: None,
+ compatible_surface: compatible_surface.as_ref(),
})
.await?;
+ let format = compatible_surface
+ .as_ref()
+ .and_then(|surf| adapter.get_swap_chain_preferred_format(surf))?;
+
let (device, queue) = adapter
.request_device(
&wgpu::DeviceDescriptor {
+ label: Some(
+ "iced_wgpu::window::compositor device descriptor",
+ ),
features: wgpu::Features::empty(),
limits: wgpu::Limits {
max_bind_groups: 2,
..wgpu::Limits::default()
},
- shader_validation: false,
},
None,
)
@@ -63,15 +74,13 @@ impl Compositor {
queue,
staging_belt,
local_pool,
+ format,
})
}
/// Creates a new rendering [`Backend`] for this [`Compositor`].
- ///
- /// [`Compositor`]: struct.Compositor.html
- /// [`Backend`]: struct.Backend.html
pub fn create_backend(&self) -> Backend {
- Backend::new(&self.device, self.settings)
+ Backend::new(&self.device, self.settings, self.format)
}
}
@@ -81,9 +90,15 @@ impl iced_graphics::window::Compositor for Compositor {
type Surface = wgpu::Surface;
type SwapChain = wgpu::SwapChain;
- fn new(settings: Self::Settings) -> Result<(Self, Renderer), Error> {
- let compositor = futures::executor::block_on(Self::request(settings))
- .ok_or(Error::AdapterNotFound)?;
+ fn new<W: HasRawWindowHandle>(
+ settings: Self::Settings,
+ compatible_window: Option<&W>,
+ ) -> Result<(Self, Renderer), Error> {
+ let compositor = futures::executor::block_on(Self::request(
+ settings,
+ compatible_window,
+ ))
+ .ok_or(Error::AdapterNotFound)?;
let backend = compositor.create_backend();
@@ -109,11 +124,11 @@ impl iced_graphics::window::Compositor for Compositor {
self.device.create_swap_chain(
surface,
&wgpu::SwapChainDescriptor {
- usage: wgpu::TextureUsage::OUTPUT_ATTACHMENT,
- format: self.settings.format,
+ usage: wgpu::TextureUsage::RENDER_ATTACHMENT,
+ format: self.format,
+ present_mode: self.settings.present_mode,
width,
height,
- present_mode: wgpu::PresentMode::Mailbox,
},
)
}
@@ -126,58 +141,79 @@ impl iced_graphics::window::Compositor for Compositor {
background_color: Color,
output: &<Self::Renderer as iced_native::Renderer>::Output,
overlay: &[T],
- ) -> mouse::Interaction {
- let frame = swap_chain.get_current_frame().expect("Next frame");
-
- let mut encoder = self.device.create_command_encoder(
- &wgpu::CommandEncoderDescriptor {
- label: Some("iced_wgpu encoder"),
+ ) -> Result<mouse::Interaction, iced_graphics::window::SwapChainError> {
+ match swap_chain.get_current_frame() {
+ Ok(frame) => {
+ let mut encoder = self.device.create_command_encoder(
+ &wgpu::CommandEncoderDescriptor {
+ label: Some("iced_wgpu encoder"),
+ },
+ );
+
+ let _ =
+ encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
+ label: Some(
+ "iced_wgpu::window::Compositor render pass",
+ ),
+ color_attachments: &[wgpu::RenderPassColorAttachment {
+ view: &frame.output.view,
+ resolve_target: None,
+ ops: wgpu::Operations {
+ load: wgpu::LoadOp::Clear({
+ let [r, g, b, a] =
+ background_color.into_linear();
+
+ wgpu::Color {
+ r: f64::from(r),
+ g: f64::from(g),
+ b: f64::from(b),
+ a: f64::from(a),
+ }
+ }),
+ store: true,
+ },
+ }],
+ depth_stencil_attachment: None,
+ });
+
+ let mouse_interaction = renderer.backend_mut().draw(
+ &mut self.device,
+ &mut self.staging_belt,
+ &mut encoder,
+ &frame.output.view,
+ viewport,
+ output,
+ overlay,
+ );
+
+ // Submit work
+ self.staging_belt.finish();
+ self.queue.submit(Some(encoder.finish()));
+
+ // Recall staging buffers
+ self.local_pool
+ .spawner()
+ .spawn(self.staging_belt.recall())
+ .expect("Recall staging belt");
+
+ self.local_pool.run_until_stalled();
+
+ Ok(mouse_interaction)
+ }
+ Err(error) => match error {
+ wgpu::SwapChainError::Timeout => {
+ Err(iced_graphics::window::SwapChainError::Timeout)
+ }
+ wgpu::SwapChainError::Outdated => {
+ Err(iced_graphics::window::SwapChainError::Outdated)
+ }
+ wgpu::SwapChainError::Lost => {
+ Err(iced_graphics::window::SwapChainError::Lost)
+ }
+ wgpu::SwapChainError::OutOfMemory => {
+ Err(iced_graphics::window::SwapChainError::OutOfMemory)
+ }
},
- );
-
- let _ = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
- color_attachments: &[wgpu::RenderPassColorAttachmentDescriptor {
- attachment: &frame.output.view,
- resolve_target: None,
- ops: wgpu::Operations {
- load: wgpu::LoadOp::Clear({
- let [r, g, b, a] = background_color.into_linear();
-
- wgpu::Color {
- r: f64::from(r),
- g: f64::from(g),
- b: f64::from(b),
- a: f64::from(a),
- }
- }),
- store: true,
- },
- }],
- depth_stencil_attachment: None,
- });
-
- let mouse_interaction = renderer.backend_mut().draw(
- &mut self.device,
- &mut self.staging_belt,
- &mut encoder,
- &frame.output.view,
- viewport,
- output,
- overlay,
- );
-
- // Submit work
- self.staging_belt.finish();
- self.queue.submit(Some(encoder.finish()));
-
- // Recall staging buffers
- self.local_pool
- .spawner()
- .spawn(self.staging_belt.recall())
- .expect("Recall staging belt");
-
- self.local_pool.run_until_stalled();
-
- mouse_interaction
+ }
}
}
diff --git a/winit/Cargo.toml b/winit/Cargo.toml
index 06e5df9a..b1192135 100644
--- a/winit/Cargo.toml
+++ b/winit/Cargo.toml
@@ -1,6 +1,6 @@
[package]
name = "iced_winit"
-version = "0.1.1"
+version = "0.3.0"
authors = ["Héctor Ramón Jiménez <hector0193@gmail.com>"]
edition = "2018"
description = "A winit runtime for Iced"
@@ -14,21 +14,25 @@ categories = ["gui"]
debug = ["iced_native/debug"]
[dependencies]
-winit = "0.22"
-window_clipboard = "0.1"
+window_clipboard = "0.2"
log = "0.4"
thiserror = "1.0"
+[dependencies.winit]
+version = "0.25"
+git = "https://github.com/iced-rs/winit"
+rev = "844485272a7412cb35cdbfac3524decdf59475ca"
+
[dependencies.iced_native]
-version = "0.2"
+version = "0.4"
path = "../native"
[dependencies.iced_graphics]
-version = "0.1"
+version = "0.2"
path = "../graphics"
[dependencies.iced_futures]
-version = "0.1"
+version = "0.3"
path = "../futures"
[target.'cfg(target_os = "windows")'.dependencies.winapi]
diff --git a/winit/README.md b/winit/README.md
index 34dec1b3..58c782c6 100644
--- a/winit/README.md
+++ b/winit/README.md
@@ -8,7 +8,9 @@
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.
-![iced_winit](../docs/graphs/winit.png)
+<p align="center">
+ <img alt="The native target" src="../docs/graphs/native.png" width="80%">
+</p>
[documentation]: https://docs.rs/iced_winit
[`iced_native`]: ../native
@@ -18,7 +20,7 @@ It exposes a renderer-agnostic `Application` trait that can be implemented and t
Add `iced_winit` as a dependency in your `Cargo.toml`:
```toml
-iced_winit = "0.1"
+iced_winit = "0.3"
```
__Iced moves fast and the `master` branch can contain breaking changes!__ If
diff --git a/winit/src/application.rs b/winit/src/application.rs
index 12f92053..c43eed11 100644
--- a/winit/src/application.rs
+++ b/winit/src/application.rs
@@ -1,13 +1,23 @@
//! Create interactive, native cross-platform applications.
+mod state;
+
+pub use state::State;
+
use crate::conversion;
use crate::mouse;
use crate::{
Clipboard, Color, Command, Debug, Error, Executor, Mode, Proxy, Runtime,
Settings, Size, Subscription,
};
+
+use iced_futures::futures;
+use iced_futures::futures::channel::mpsc;
use iced_graphics::window;
-use iced_graphics::Viewport;
-use iced_native::program::{self, Program};
+use iced_native::program::Program;
+use iced_native::Menu;
+use iced_native::{Cache, UserInterface};
+
+use std::mem::ManuallyDrop;
/// An interactive, native cross-platform application.
///
@@ -15,17 +25,13 @@ use iced_native::program::{self, Program};
/// your GUI application by simply calling [`run`](#method.run). It will run in
/// its own window.
///
-/// An [`Application`](trait.Application.html) can execute asynchronous actions
-/// by returning a [`Command`](struct.Command.html) in some of its methods.
+/// An [`Application`] can execute asynchronous actions by returning a
+/// [`Command`] in some of its methods.
///
/// When using an [`Application`] with the `debug` feature enabled, a debug view
/// can be toggled by pressing `F12`.
-///
-/// [`Application`]: trait.Application.html
-pub trait Application: Program {
+pub trait Application: Program<Clipboard = Clipboard> {
/// The data needed to initialize your [`Application`].
- ///
- /// [`Application`]: trait.Application.html
type Flags;
/// Initializes the [`Application`] with the flags provided to
@@ -33,22 +39,15 @@ pub trait Application: Program {
///
/// Here is where you should return the initial state of your app.
///
- /// Additionally, you can return a [`Command`](struct.Command.html) if you
- /// need to perform some async action in the background on startup. This is
- /// useful if you want to load state from a file, perform an initial HTTP
- /// request, etc.
- ///
- /// [`Application`]: trait.Application.html
- /// [`run`]: #method.run.html
- /// [`Settings`]: ../settings/struct.Settings.html
+ /// Additionally, you can return a [`Command`] if you need to perform some
+ /// async action in the background on startup. This is useful if you want to
+ /// load state from a file, perform an initial HTTP request, etc.
fn new(flags: Self::Flags) -> (Self, Command<Self::Message>);
/// Returns the current title of the [`Application`].
///
/// This title can be dynamic! The runtime will automatically update the
/// title of your application when necessary.
- ///
- /// [`Application`]: trait.Application.html
fn title(&self) -> String;
/// Returns the event `Subscription` for the current state of the
@@ -70,8 +69,6 @@ pub trait Application: Program {
/// is returned.
///
/// By default, an application will run in windowed mode.
- ///
- /// [`Application`]: trait.Application.html
fn mode(&self) -> Mode {
Mode::Windowed
}
@@ -79,10 +76,6 @@ pub trait Application: Program {
/// Returns the background [`Color`] of the [`Application`].
///
/// By default, it returns [`Color::WHITE`].
- ///
- /// [`Color`]: struct.Color.html
- /// [`Application`]: trait.Application.html
- /// [`Color::WHITE`]: struct.Color.html#const.WHITE
fn background_color(&self) -> Color {
Color::WHITE
}
@@ -96,17 +89,27 @@ pub trait Application: Program {
/// while a scale factor of `0.5` will shrink them to half their size.
///
/// By default, it returns `1.0`.
- ///
- /// [`Application`]: trait.Application.html
fn scale_factor(&self) -> f64 {
1.0
}
+
+ /// Returns whether the [`Application`] should be terminated.
+ ///
+ /// By default, it returns `false`.
+ fn should_exit(&self) -> bool {
+ false
+ }
+
+ /// Returns the current system [`Menu`] of the [`Application`].
+ ///
+ /// By default, it returns an empty [`Menu`].
+ fn menu(&self) -> Menu<Self::Message> {
+ Menu::new()
+ }
}
/// Runs an [`Application`] with an executor, compositor, and the provided
/// settings.
-///
-/// [`Application`]: trait.Application.html
pub fn run<A, E, C>(
settings: Settings<A::Flags>,
compositor_settings: C::Settings,
@@ -116,279 +119,340 @@ where
E: Executor + 'static,
C: window::Compositor<Renderer = A::Renderer> + 'static,
{
- use winit::{
- event,
- event_loop::{ControlFlow, EventLoop},
- };
+ use futures::task;
+ use futures::Future;
+ use winit::event_loop::EventLoop;
let mut debug = Debug::new();
debug.startup_started();
let event_loop = EventLoop::with_user_event();
+
let mut runtime = {
- let executor = E::new().map_err(Error::ExecutorCreationFailed)?;
let proxy = Proxy::new(event_loop.create_proxy());
+ let executor = E::new().map_err(Error::ExecutorCreationFailed)?;
Runtime::new(executor, proxy)
};
- let flags = settings.flags;
- let (application, init_command) = runtime.enter(|| A::new(flags));
- runtime.spawn(init_command);
+ let (application, init_command) = {
+ let flags = settings.flags;
+
+ runtime.enter(|| A::new(flags))
+ };
let subscription = application.subscription();
- runtime.track(subscription);
- let mut title = application.title();
- let mut mode = application.mode();
- let mut background_color = application.background_color();
- let mut scale_factor = application.scale_factor();
+ runtime.spawn(init_command);
+ runtime.track(subscription);
let window = settings
.window
- .into_builder(&title, mode, event_loop.primary_monitor())
+ .into_builder(
+ &application.title(),
+ application.mode(),
+ event_loop.primary_monitor(),
+ settings.id,
+ )
+ .with_menu(Some(conversion::menu(&application.menu())))
.build(&event_loop)
.map_err(Error::WindowCreationFailed)?;
- let clipboard = Clipboard::new(&window);
- // TODO: Encode cursor availability in the type-system
- let mut cursor_position = winit::dpi::PhysicalPosition::new(-1.0, -1.0);
- let mut mouse_interaction = mouse::Interaction::default();
- let mut modifiers = winit::event::ModifiersState::default();
-
- let physical_size = window.inner_size();
- let mut viewport = Viewport::with_physical_size(
- Size::new(physical_size.width, physical_size.height),
- window.scale_factor() * scale_factor,
- );
- let mut resized = false;
+ let (compositor, renderer) = C::new(compositor_settings, Some(&window))?;
- let (mut compositor, mut renderer) = C::new(compositor_settings)?;
-
- let surface = compositor.create_surface(&window);
+ let (mut sender, receiver) = mpsc::unbounded();
- let mut swap_chain = compositor.create_swap_chain(
- &surface,
- physical_size.width,
- physical_size.height,
- );
-
- let mut state = program::State::new(
+ let mut instance = Box::pin(run_instance::<A, E, C>(
application,
- viewport.logical_size(),
- conversion::cursor_position(cursor_position, viewport.scale_factor()),
- &mut renderer,
- &mut debug,
- );
- debug.startup_finished();
+ compositor,
+ renderer,
+ runtime,
+ debug,
+ receiver,
+ window,
+ settings.exit_on_close_request,
+ ));
+
+ let mut context = task::Context::from_waker(task::noop_waker_ref());
+
+ event_loop.run(move |event, _, control_flow| {
+ use winit::event_loop::ControlFlow;
+
+ if let ControlFlow::Exit = control_flow {
+ return;
+ }
- event_loop.run(move |event, _, control_flow| match event {
- event::Event::MainEventsCleared => {
- if state.is_queue_empty() {
- return;
- }
+ let event = match event {
+ winit::event::Event::WindowEvent {
+ event:
+ winit::event::WindowEvent::ScaleFactorChanged {
+ new_inner_size,
+ ..
+ },
+ window_id,
+ } => Some(winit::event::Event::WindowEvent {
+ event: winit::event::WindowEvent::Resized(*new_inner_size),
+ window_id,
+ }),
+ _ => event.to_static(),
+ };
+
+ if let Some(event) = event {
+ sender.start_send(event).expect("Send event");
+
+ let poll = instance.as_mut().poll(&mut context);
+
+ *control_flow = match poll {
+ task::Poll::Pending => ControlFlow::Wait,
+ task::Poll::Ready(_) => ControlFlow::Exit,
+ };
+ }
+ });
+}
- let command = runtime.enter(|| {
- state.update(
- viewport.logical_size(),
- conversion::cursor_position(
- cursor_position,
- viewport.scale_factor(),
- ),
- clipboard.as_ref().map(|c| c as _),
- &mut renderer,
- &mut debug,
- )
- });
+async fn run_instance<A, E, C>(
+ mut application: A,
+ mut compositor: C,
+ mut renderer: A::Renderer,
+ mut runtime: Runtime<E, Proxy<A::Message>, A::Message>,
+ mut debug: Debug,
+ mut receiver: mpsc::UnboundedReceiver<winit::event::Event<'_, A::Message>>,
+ window: winit::window::Window,
+ exit_on_close_request: bool,
+) where
+ A: Application + 'static,
+ E: Executor + 'static,
+ C: window::Compositor<Renderer = A::Renderer> + 'static,
+{
+ use iced_futures::futures::stream::StreamExt;
+ use winit::event;
- // If the application was updated
- if let Some(command) = command {
- runtime.spawn(command);
+ let surface = compositor.create_surface(&window);
+ let mut clipboard = Clipboard::connect(&window);
+
+ let mut state = State::new(&application, &window);
+ let mut viewport_version = state.viewport_version();
+ let mut swap_chain = {
+ let physical_size = state.physical_size();
+
+ compositor.create_swap_chain(
+ &surface,
+ physical_size.width,
+ physical_size.height,
+ )
+ };
- let program = state.program();
+ let mut user_interface = ManuallyDrop::new(build_user_interface(
+ &mut application,
+ Cache::default(),
+ &mut renderer,
+ state.logical_size(),
+ &mut debug,
+ ));
- // Update subscriptions
- let subscription = program.subscription();
- runtime.track(subscription);
+ let mut primitive =
+ user_interface.draw(&mut renderer, state.cursor_position());
+ let mut mouse_interaction = mouse::Interaction::default();
- // Update window title
- let new_title = program.title();
+ let mut events = Vec::new();
+ let mut messages = Vec::new();
- if title != new_title {
- window.set_title(&new_title);
+ debug.startup_finished();
- title = new_title;
+ while let Some(event) = receiver.next().await {
+ match event {
+ event::Event::MainEventsCleared => {
+ if events.is_empty() && messages.is_empty() {
+ continue;
}
- // Update window mode
- let new_mode = program.mode();
+ debug.event_processing_started();
- if mode != new_mode {
- window.set_fullscreen(conversion::fullscreen(
- window.current_monitor(),
- new_mode,
- ));
+ let statuses = user_interface.update(
+ &events,
+ state.cursor_position(),
+ &mut renderer,
+ &mut clipboard,
+ &mut messages,
+ );
+
+ debug.event_processing_finished();
- mode = new_mode;
+ for event in events.drain(..).zip(statuses.into_iter()) {
+ runtime.broadcast(event);
}
- // Update background color
- background_color = program.background_color();
+ if !messages.is_empty() {
+ let cache =
+ ManuallyDrop::into_inner(user_interface).into_cache();
- // Update scale factor
- let new_scale_factor = program.scale_factor();
+ // Update application
+ update(
+ &mut application,
+ &mut runtime,
+ &mut debug,
+ &mut clipboard,
+ &mut messages,
+ );
- if scale_factor != new_scale_factor {
- let size = window.inner_size();
+ // Update window
+ state.synchronize(&application, &window);
- viewport = Viewport::with_physical_size(
- Size::new(size.width, size.height),
- window.scale_factor() * new_scale_factor,
- );
+ let should_exit = application.should_exit();
- // We relayout the UI with the new logical size.
- // The queue is empty, therefore this will never produce
- // a `Command`.
- //
- // TODO: Properly queue `WindowResized`
- let _ = state.update(
- viewport.logical_size(),
- conversion::cursor_position(
- cursor_position,
- viewport.scale_factor(),
- ),
- clipboard.as_ref().map(|c| c as _),
+ user_interface = ManuallyDrop::new(build_user_interface(
+ &mut application,
+ cache,
&mut renderer,
+ state.logical_size(),
&mut debug,
- );
+ ));
- scale_factor = new_scale_factor;
+ if should_exit {
+ break;
+ }
}
+
+ debug.draw_started();
+ primitive =
+ user_interface.draw(&mut renderer, state.cursor_position());
+ debug.draw_finished();
+
+ window.request_redraw();
+ }
+ event::Event::PlatformSpecific(event::PlatformSpecific::MacOS(
+ event::MacOS::ReceivedUrl(url),
+ )) => {
+ use iced_native::event;
+ events.push(iced_native::Event::PlatformSpecific(
+ event::PlatformSpecific::MacOS(event::MacOS::ReceivedUrl(
+ url,
+ )),
+ ));
+ }
+ event::Event::UserEvent(message) => {
+ messages.push(message);
}
+ event::Event::RedrawRequested(_) => {
+ let physical_size = state.physical_size();
- window.request_redraw();
- }
- event::Event::UserEvent(message) => {
- state.queue_message(message);
- }
- event::Event::RedrawRequested(_) => {
- debug.render_started();
+ if physical_size.width == 0 || physical_size.height == 0 {
+ continue;
+ }
- if resized {
- let physical_size = viewport.physical_size();
+ debug.render_started();
+ let current_viewport_version = state.viewport_version();
- swap_chain = compositor.create_swap_chain(
- &surface,
- physical_size.width,
- physical_size.height,
- );
+ if viewport_version != current_viewport_version {
+ let logical_size = state.logical_size();
- resized = false;
- }
+ debug.layout_started();
+ user_interface = ManuallyDrop::new(
+ ManuallyDrop::into_inner(user_interface)
+ .relayout(logical_size, &mut renderer),
+ );
+ debug.layout_finished();
- let new_mouse_interaction = compositor.draw(
- &mut renderer,
- &mut swap_chain,
- &viewport,
- background_color,
- state.primitive(),
- &debug.overlay(),
- );
+ debug.draw_started();
+ primitive = user_interface
+ .draw(&mut renderer, state.cursor_position());
+ debug.draw_finished();
- debug.render_finished();
+ swap_chain = compositor.create_swap_chain(
+ &surface,
+ physical_size.width,
+ physical_size.height,
+ );
- if new_mouse_interaction != mouse_interaction {
- window.set_cursor_icon(conversion::mouse_interaction(
- new_mouse_interaction,
- ));
+ viewport_version = current_viewport_version;
+ }
- mouse_interaction = new_mouse_interaction;
+ match compositor.draw(
+ &mut renderer,
+ &mut swap_chain,
+ state.viewport(),
+ state.background_color(),
+ &primitive,
+ &debug.overlay(),
+ ) {
+ Ok(new_mouse_interaction) => {
+ debug.render_finished();
+
+ if new_mouse_interaction != mouse_interaction {
+ window.set_cursor_icon(
+ conversion::mouse_interaction(
+ new_mouse_interaction,
+ ),
+ );
+
+ mouse_interaction = new_mouse_interaction;
+ }
+
+ // TODO: Handle animations!
+ // Maybe we can use `ControlFlow::WaitUntil` for this.
+ }
+ Err(error) => match error {
+ // This is an unrecoverable error.
+ window::SwapChainError::OutOfMemory => {
+ panic!("{}", error);
+ }
+ _ => {
+ debug.render_finished();
+
+ // Try rendering again next frame.
+ window.request_redraw();
+ }
+ },
+ }
+ }
+ event::Event::WindowEvent {
+ event: event::WindowEvent::MenuEntryActivated(entry_id),
+ ..
+ } => {
+ if let Some(message) =
+ conversion::menu_message(state.menu(), entry_id)
+ {
+ messages.push(message);
+ }
}
+ event::Event::WindowEvent {
+ event: window_event,
+ ..
+ } => {
+ if requests_exit(&window_event, state.modifiers())
+ && exit_on_close_request
+ {
+ break;
+ }
- // TODO: Handle animations!
- // Maybe we can use `ControlFlow::WaitUntil` for this.
- }
- event::Event::WindowEvent {
- event: window_event,
- ..
- } => {
- handle_window_event(
- &window_event,
- &window,
- scale_factor,
- control_flow,
- &mut cursor_position,
- &mut modifiers,
- &mut viewport,
- &mut resized,
- &mut debug,
- );
-
- if let Some(event) = conversion::window_event(
- &window_event,
- viewport.scale_factor(),
- modifiers,
- ) {
- state.queue_event(event.clone());
- runtime.broadcast(event);
+ state.update(&window, &window_event, &mut debug);
+
+ if let Some(event) = conversion::window_event(
+ &window_event,
+ state.scale_factor(),
+ state.modifiers(),
+ ) {
+ events.push(event);
+ }
}
+ _ => {}
}
- _ => {
- *control_flow = ControlFlow::Wait;
- }
- })
+ }
+
+ // Manually drop the user interface
+ drop(ManuallyDrop::into_inner(user_interface));
}
-/// Handles a `WindowEvent` and mutates the provided control flow, keyboard
-/// modifiers, viewport, and resized flag accordingly.
-pub fn handle_window_event(
+/// Returns true if the provided event should cause an [`Application`] to
+/// exit.
+pub fn requests_exit(
event: &winit::event::WindowEvent<'_>,
- window: &winit::window::Window,
- scale_factor: f64,
- control_flow: &mut winit::event_loop::ControlFlow,
- cursor_position: &mut winit::dpi::PhysicalPosition<f64>,
- modifiers: &mut winit::event::ModifiersState,
- viewport: &mut Viewport,
- resized: &mut bool,
- _debug: &mut Debug,
-) {
- use winit::{event::WindowEvent, event_loop::ControlFlow};
+ _modifiers: winit::event::ModifiersState,
+) -> bool {
+ use winit::event::WindowEvent;
match event {
- WindowEvent::Resized(new_size) => {
- let size = Size::new(new_size.width, new_size.height);
-
- *viewport = Viewport::with_physical_size(
- size,
- window.scale_factor() * scale_factor,
- );
- *resized = true;
- }
- WindowEvent::ScaleFactorChanged {
- scale_factor: new_scale_factor,
- new_inner_size,
- } => {
- let size = Size::new(new_inner_size.width, new_inner_size.height);
-
- *viewport = Viewport::with_physical_size(
- size,
- new_scale_factor * scale_factor,
- );
- *resized = true;
- }
- WindowEvent::CloseRequested => {
- *control_flow = ControlFlow::Exit;
- }
- WindowEvent::CursorMoved { position, .. } => {
- *cursor_position = *position;
- }
- WindowEvent::CursorLeft { .. } => {
- // TODO: Encode cursor availability in the type-system
- *cursor_position = winit::dpi::PhysicalPosition::new(-1.0, -1.0);
- }
- WindowEvent::ModifiersChanged(new_modifiers) => {
- *modifiers = *new_modifiers;
- }
+ WindowEvent::CloseRequested => true,
#[cfg(target_os = "macos")]
WindowEvent::KeyboardInput {
input:
@@ -398,19 +462,50 @@ pub fn handle_window_event(
..
},
..
- } if modifiers.logo() => {
- *control_flow = ControlFlow::Exit;
- }
- #[cfg(feature = "debug")]
- WindowEvent::KeyboardInput {
- input:
- winit::event::KeyboardInput {
- virtual_keycode: Some(winit::event::VirtualKeyCode::F12),
- state: winit::event::ElementState::Pressed,
- ..
- },
- ..
- } => _debug.toggle(),
- _ => {}
+ } if _modifiers.logo() => true,
+ _ => false,
+ }
+}
+
+/// Builds a [`UserInterface`] for the provided [`Application`], logging
+/// [`struct@Debug`] information accordingly.
+pub fn build_user_interface<'a, A: Application>(
+ application: &'a mut A,
+ cache: Cache,
+ renderer: &mut A::Renderer,
+ size: Size,
+ debug: &mut Debug,
+) -> UserInterface<'a, A::Message, A::Renderer> {
+ debug.view_started();
+ let view = application.view();
+ debug.view_finished();
+
+ debug.layout_started();
+ let user_interface = UserInterface::build(view, size, cache, renderer);
+ debug.layout_finished();
+
+ user_interface
+}
+
+/// Updates an [`Application`] by feeding it the provided messages, spawning any
+/// resulting [`Command`], and tracking its [`Subscription`].
+pub fn update<A: Application, E: Executor>(
+ application: &mut A,
+ runtime: &mut Runtime<E, Proxy<A::Message>, A::Message>,
+ debug: &mut Debug,
+ clipboard: &mut A::Clipboard,
+ messages: &mut Vec<A::Message>,
+) {
+ for message in messages.drain(..) {
+ debug.log_message(&message);
+
+ debug.update_started();
+ let command = runtime.enter(|| application.update(message, clipboard));
+ debug.update_finished();
+
+ runtime.spawn(command);
}
+
+ let subscription = application.subscription();
+ runtime.track(subscription);
}
diff --git a/winit/src/application/state.rs b/winit/src/application/state.rs
new file mode 100644
index 00000000..f60f09be
--- /dev/null
+++ b/winit/src/application/state.rs
@@ -0,0 +1,224 @@
+use crate::conversion;
+use crate::{Application, Color, Debug, Menu, Mode, Point, Size, Viewport};
+
+use std::marker::PhantomData;
+use winit::event::{Touch, WindowEvent};
+use winit::window::Window;
+
+/// The state of a windowed [`Application`].
+#[derive(Debug, Clone)]
+pub struct State<A: Application> {
+ title: String,
+ menu: Menu<A::Message>,
+ mode: Mode,
+ background_color: Color,
+ scale_factor: f64,
+ viewport: Viewport,
+ viewport_version: usize,
+ cursor_position: winit::dpi::PhysicalPosition<f64>,
+ modifiers: winit::event::ModifiersState,
+ application: PhantomData<A>,
+}
+
+impl<A: Application> State<A> {
+ /// Creates a new [`State`] for the provided [`Application`] and window.
+ pub fn new(application: &A, window: &Window) -> Self {
+ let title = application.title();
+ let menu = application.menu();
+ let mode = application.mode();
+ let background_color = application.background_color();
+ let scale_factor = application.scale_factor();
+
+ let viewport = {
+ let physical_size = window.inner_size();
+
+ Viewport::with_physical_size(
+ Size::new(physical_size.width, physical_size.height),
+ window.scale_factor() * scale_factor,
+ )
+ };
+
+ Self {
+ title,
+ menu,
+ mode,
+ background_color,
+ scale_factor,
+ viewport,
+ viewport_version: 0,
+ // TODO: Encode cursor availability in the type-system
+ cursor_position: winit::dpi::PhysicalPosition::new(-1.0, -1.0),
+ modifiers: winit::event::ModifiersState::default(),
+ application: PhantomData,
+ }
+ }
+
+ /// Returns the current [`Menu`] of the [`State`].
+ pub fn menu(&self) -> &Menu<A::Message> {
+ &self.menu
+ }
+
+ /// Returns the current background [`Color`] of the [`State`].
+ pub fn background_color(&self) -> Color {
+ self.background_color
+ }
+
+ /// Returns the current [`Viewport`] of the [`State`].
+ pub fn viewport(&self) -> &Viewport {
+ &self.viewport
+ }
+
+ /// Returns the version of the [`Viewport`] of the [`State`].
+ ///
+ /// The version is incremented every time the [`Viewport`] changes.
+ pub fn viewport_version(&self) -> usize {
+ self.viewport_version
+ }
+
+ /// Returns the physical [`Size`] of the [`Viewport`] of the [`State`].
+ pub fn physical_size(&self) -> Size<u32> {
+ self.viewport.physical_size()
+ }
+
+ /// Returns the logical [`Size`] of the [`Viewport`] of the [`State`].
+ pub fn logical_size(&self) -> Size<f32> {
+ self.viewport.logical_size()
+ }
+
+ /// Returns the current scale factor of the [`Viewport`] of the [`State`].
+ pub fn scale_factor(&self) -> f64 {
+ self.viewport.scale_factor()
+ }
+
+ /// Returns the current cursor position of the [`State`].
+ pub fn cursor_position(&self) -> Point {
+ conversion::cursor_position(
+ self.cursor_position,
+ self.viewport.scale_factor(),
+ )
+ }
+
+ /// Returns the current keyboard modifiers of the [`State`].
+ pub fn modifiers(&self) -> winit::event::ModifiersState {
+ self.modifiers
+ }
+
+ /// Processes the provided window event and updates the [`State`]
+ /// accordingly.
+ pub fn update(
+ &mut self,
+ window: &Window,
+ event: &WindowEvent<'_>,
+ _debug: &mut Debug,
+ ) {
+ match event {
+ WindowEvent::Resized(new_size) => {
+ let size = Size::new(new_size.width, new_size.height);
+
+ self.viewport = Viewport::with_physical_size(
+ size,
+ window.scale_factor() * self.scale_factor,
+ );
+
+ self.viewport_version = self.viewport_version.wrapping_add(1);
+ }
+ WindowEvent::ScaleFactorChanged {
+ scale_factor: new_scale_factor,
+ new_inner_size,
+ } => {
+ let size =
+ Size::new(new_inner_size.width, new_inner_size.height);
+
+ self.viewport = Viewport::with_physical_size(
+ size,
+ new_scale_factor * self.scale_factor,
+ );
+
+ self.viewport_version = self.viewport_version.wrapping_add(1);
+ }
+ WindowEvent::CursorMoved { position, .. }
+ | WindowEvent::Touch(Touch {
+ location: position, ..
+ }) => {
+ self.cursor_position = *position;
+ }
+ WindowEvent::CursorLeft { .. } => {
+ // TODO: Encode cursor availability in the type-system
+ self.cursor_position =
+ winit::dpi::PhysicalPosition::new(-1.0, -1.0);
+ }
+ WindowEvent::ModifiersChanged(new_modifiers) => {
+ self.modifiers = *new_modifiers;
+ }
+ #[cfg(feature = "debug")]
+ WindowEvent::KeyboardInput {
+ input:
+ winit::event::KeyboardInput {
+ virtual_keycode: Some(winit::event::VirtualKeyCode::F12),
+ state: winit::event::ElementState::Pressed,
+ ..
+ },
+ ..
+ } => _debug.toggle(),
+ _ => {}
+ }
+ }
+
+ /// Synchronizes the [`State`] with its [`Application`] and its respective
+ /// window.
+ ///
+ /// Normally an [`Application`] should be synchronized with its [`State`]
+ /// and window after calling [`Application::update`].
+ ///
+ /// [`Application::update`]: crate::Program::update
+ pub fn synchronize(&mut self, application: &A, window: &Window) {
+ // Update window title
+ let new_title = application.title();
+
+ if self.title != new_title {
+ window.set_title(&new_title);
+
+ self.title = new_title;
+ }
+
+ // Update window mode
+ let new_mode = application.mode();
+
+ if self.mode != new_mode {
+ window.set_fullscreen(conversion::fullscreen(
+ window.current_monitor(),
+ new_mode,
+ ));
+
+ window.set_visible(conversion::visible(new_mode));
+
+ self.mode = new_mode;
+ }
+
+ // Update background color
+ self.background_color = application.background_color();
+
+ // Update scale factor
+ let new_scale_factor = application.scale_factor();
+
+ if self.scale_factor != new_scale_factor {
+ let size = window.inner_size();
+
+ self.viewport = Viewport::with_physical_size(
+ Size::new(size.width, size.height),
+ window.scale_factor() * new_scale_factor,
+ );
+
+ self.scale_factor = new_scale_factor;
+ }
+
+ // Update menu
+ let new_menu = application.menu();
+
+ if self.menu != new_menu {
+ window.set_menu(Some(conversion::menu(&new_menu)));
+
+ self.menu = new_menu;
+ }
+ }
+}
diff --git a/winit/src/clipboard.rs b/winit/src/clipboard.rs
index 1ff029ab..ca25c065 100644
--- a/winit/src/clipboard.rs
+++ b/winit/src/clipboard.rs
@@ -1,19 +1,54 @@
/// A buffer for short-term storage and transfer within and between
/// applications.
#[allow(missing_debug_implementations)]
-pub struct Clipboard(window_clipboard::Clipboard);
+pub struct Clipboard {
+ state: State,
+}
+
+enum State {
+ Connected(window_clipboard::Clipboard),
+ Unavailable,
+}
impl Clipboard {
/// Creates a new [`Clipboard`] for the given window.
- ///
- /// [`Clipboard`]: struct.Clipboard.html
- pub fn new(window: &winit::window::Window) -> Option<Clipboard> {
- window_clipboard::Clipboard::new(window).map(Clipboard).ok()
+ pub fn connect(window: &winit::window::Window) -> Clipboard {
+ let state = window_clipboard::Clipboard::connect(window)
+ .ok()
+ .map(State::Connected)
+ .unwrap_or(State::Unavailable);
+
+ Clipboard { state }
+ }
+
+ /// Reads the current content of the [`Clipboard`] as text.
+ pub fn read(&self) -> Option<String> {
+ match &self.state {
+ State::Connected(clipboard) => clipboard.read().ok(),
+ State::Unavailable => None,
+ }
+ }
+
+ /// Writes the given text contents to the [`Clipboard`].
+ pub fn write(&mut self, contents: String) {
+ match &mut self.state {
+ State::Connected(clipboard) => match clipboard.write(contents) {
+ Ok(()) => {}
+ Err(error) => {
+ log::warn!("error writing to clipboard: {}", error)
+ }
+ },
+ State::Unavailable => {}
+ }
}
}
impl iced_native::Clipboard for Clipboard {
- fn content(&self) -> Option<String> {
- self.0.read().ok()
+ fn read(&self) -> Option<String> {
+ self.read()
+ }
+
+ fn write(&mut self, contents: String) {
+ self.write(contents)
}
}
diff --git a/winit/src/conversion.rs b/winit/src/conversion.rs
index 638787ab..e0934f43 100644
--- a/winit/src/conversion.rs
+++ b/winit/src/conversion.rs
@@ -2,10 +2,12 @@
//!
//! [`winit`]: https://github.com/rust-windowing/winit
//! [`iced_native`]: https://github.com/hecrj/iced/tree/master/native
-use crate::{
- keyboard::{self, KeyCode, ModifiersState},
- mouse, window, Event, Mode, Point,
-};
+use crate::keyboard;
+use crate::menu::{self, Menu};
+use crate::mouse;
+use crate::touch;
+use crate::window;
+use crate::{Event, Mode, Point, Position};
/// Converts a winit window event into an iced event.
pub fn window_event(
@@ -32,12 +34,14 @@ pub fn window_event(
height: logical_size.height,
}))
}
+ WindowEvent::CloseRequested => {
+ Some(Event::Window(window::Event::CloseRequested))
+ }
WindowEvent::CursorMoved { position, .. } => {
let position = position.to_logical::<f64>(scale_factor);
Some(Event::Mouse(mouse::Event::CursorMoved {
- x: position.x as f32,
- y: position.y as f32,
+ position: Point::new(position.x as f32, position.y as f32),
}))
}
WindowEvent::CursorEntered { .. } => {
@@ -89,7 +93,7 @@ pub fn window_event(
..
} => Some(Event::Keyboard({
let key_code = key_code(*virtual_keycode);
- let modifiers = modifiers_state(modifiers);
+ let modifiers = self::modifiers(modifiers);
match state {
winit::event::ElementState::Pressed => {
@@ -107,8 +111,13 @@ pub fn window_event(
}
})),
WindowEvent::ModifiersChanged(new_modifiers) => Some(Event::Keyboard(
- keyboard::Event::ModifiersChanged(modifiers_state(*new_modifiers)),
+ keyboard::Event::ModifiersChanged(self::modifiers(*new_modifiers)),
)),
+ WindowEvent::Focused(focused) => Some(Event::Window(if *focused {
+ window::Event::Focused
+ } else {
+ window::Event::Unfocused
+ })),
WindowEvent::HoveredFile(path) => {
Some(Event::Window(window::Event::FileHovered(path.clone())))
}
@@ -118,26 +127,189 @@ pub fn window_event(
WindowEvent::HoveredFileCancelled => {
Some(Event::Window(window::Event::FilesHoveredLeft))
}
+ WindowEvent::Touch(touch) => {
+ Some(Event::Touch(touch_event(*touch, scale_factor)))
+ }
+ WindowEvent::Moved(position) => {
+ let winit::dpi::LogicalPosition { x, y } =
+ position.to_logical(scale_factor);
+
+ Some(Event::Window(window::Event::Moved { x, y }))
+ }
_ => None,
}
}
+/// Converts a [`Position`] to a [`winit`] logical position for a given monitor.
+///
+/// [`winit`]: https://github.com/rust-windowing/winit
+pub fn position(
+ monitor: Option<&winit::monitor::MonitorHandle>,
+ (width, height): (u32, u32),
+ position: Position,
+) -> Option<winit::dpi::Position> {
+ match position {
+ Position::Default => None,
+ Position::Specific(x, y) => {
+ Some(winit::dpi::Position::Logical(winit::dpi::LogicalPosition {
+ x: f64::from(x),
+ y: f64::from(y),
+ }))
+ }
+ Position::Centered => {
+ if let Some(monitor) = monitor {
+ let start = monitor.position();
+
+ let resolution: winit::dpi::LogicalSize<f64> =
+ monitor.size().to_logical(monitor.scale_factor());
+
+ let centered: winit::dpi::PhysicalPosition<i32> =
+ winit::dpi::LogicalPosition {
+ x: (resolution.width - f64::from(width)) / 2.0,
+ y: (resolution.height - f64::from(height)) / 2.0,
+ }
+ .to_physical(monitor.scale_factor());
+
+ Some(winit::dpi::Position::Physical(
+ winit::dpi::PhysicalPosition {
+ x: start.x + centered.x,
+ y: start.y + centered.y,
+ },
+ ))
+ } else {
+ None
+ }
+ }
+ }
+}
+
/// Converts a [`Mode`] to a [`winit`] fullscreen mode.
///
-/// [`Mode`]: ../enum.Mode.html
/// [`winit`]: https://github.com/rust-windowing/winit
pub fn fullscreen(
- monitor: winit::monitor::MonitorHandle,
+ monitor: Option<winit::monitor::MonitorHandle>,
mode: Mode,
) -> Option<winit::window::Fullscreen> {
match mode {
- Mode::Windowed => None,
+ Mode::Windowed | Mode::Hidden => None,
Mode::Fullscreen => {
Some(winit::window::Fullscreen::Borderless(monitor))
}
}
}
+/// Converts a [`Mode`] to a visibility flag.
+pub fn visible(mode: Mode) -> bool {
+ match mode {
+ Mode::Windowed | Mode::Fullscreen => true,
+ Mode::Hidden => false,
+ }
+}
+
+/// Converts a `Hotkey` from [`iced_native`] to a [`winit`] Hotkey.
+///
+/// [`winit`]: https://github.com/rust-windowing/winit
+/// [`iced_native`]: https://github.com/hecrj/iced/tree/master/native
+fn hotkey(hotkey: keyboard::Hotkey) -> winit::window::Hotkey {
+ use winit::event::ModifiersState;
+
+ let mut modifiers = ModifiersState::empty();
+ modifiers.set(ModifiersState::CTRL, hotkey.modifiers.control());
+ modifiers.set(ModifiersState::SHIFT, hotkey.modifiers.shift());
+ modifiers.set(ModifiersState::ALT, hotkey.modifiers.alt());
+ modifiers.set(ModifiersState::LOGO, hotkey.modifiers.logo());
+
+ winit::window::Hotkey::new(modifiers, to_virtual_keycode(hotkey.key))
+}
+
+/// Converts a `Menu` from [`iced_native`] to a [`winit`] menu.
+///
+/// [`winit`]: https://github.com/rust-windowing/winit
+/// [`iced_native`]: https://github.com/hecrj/iced/tree/master/native
+pub fn menu<Message>(menu: &Menu<Message>) -> winit::window::Menu {
+ fn menu_i<Message>(
+ converted: &mut winit::window::Menu,
+ starting_id: usize,
+ menu: &Menu<Message>,
+ ) -> usize {
+ let mut id = starting_id;
+
+ for item in menu.iter() {
+ match item {
+ menu::Entry::Item { title, hotkey, .. } => {
+ converted.add_item(id, title, hotkey.map(self::hotkey));
+
+ id += 1;
+ }
+ menu::Entry::Dropdown { title, submenu } => {
+ let mut converted_submenu = winit::window::Menu::new();
+ let n_children =
+ menu_i(&mut converted_submenu, id, submenu);
+
+ converted.add_dropdown(title, converted_submenu);
+
+ id += n_children;
+ }
+ menu::Entry::Separator => {
+ converted.add_separator();
+ }
+ }
+ }
+
+ id - starting_id
+ }
+
+ let mut converted = winit::window::Menu::default();
+ let _ = menu_i(&mut converted, 0, menu);
+
+ converted
+}
+
+/// Given a [`Menu`] and an identifier of a [`menu::Entry`], it returns the
+/// `Message` that should be produced when that entry is activated.
+pub fn menu_message<Message>(menu: &Menu<Message>, id: u32) -> Option<Message>
+where
+ Message: Clone,
+{
+ fn find_message<Message>(
+ target: u32,
+ starting_id: u32,
+ menu: &Menu<Message>,
+ ) -> Result<Message, u32>
+ where
+ Message: Clone,
+ {
+ let mut id = starting_id;
+
+ for entry in menu.iter() {
+ match entry {
+ menu::Entry::Item { on_activation, .. } => {
+ if id == target {
+ return Ok(on_activation.clone());
+ }
+
+ id += 1;
+ }
+ menu::Entry::Dropdown { submenu, .. } => {
+ match find_message(target, id, submenu) {
+ Ok(message) => {
+ return Ok(message);
+ }
+ Err(n_children) => {
+ id += n_children;
+ }
+ }
+ }
+ menu::Entry::Separator => {}
+ }
+ }
+
+ Err(id - starting_id)
+ }
+
+ find_message(id, 0, menu).ok()
+}
+
/// Converts a `MouseCursor` from [`iced_native`] to a [`winit`] cursor icon.
///
/// [`winit`]: https://github.com/rust-windowing/winit
@@ -171,7 +343,9 @@ pub fn mouse_button(mouse_button: winit::event::MouseButton) -> mouse::Button {
winit::event::MouseButton::Left => mouse::Button::Left,
winit::event::MouseButton::Right => mouse::Button::Right,
winit::event::MouseButton::Middle => mouse::Button::Middle,
- winit::event::MouseButton::Other(other) => mouse::Button::Other(other),
+ winit::event::MouseButton::Other(other) => {
+ mouse::Button::Other(other as u8)
+ }
}
}
@@ -180,15 +354,17 @@ pub fn mouse_button(mouse_button: winit::event::MouseButton) -> mouse::Button {
///
/// [`winit`]: https://github.com/rust-windowing/winit
/// [`iced_native`]: https://github.com/hecrj/iced/tree/master/native
-pub fn modifiers_state(
+pub fn modifiers(
modifiers: winit::event::ModifiersState,
-) -> ModifiersState {
- ModifiersState {
- shift: modifiers.shift(),
- control: modifiers.ctrl(),
- alt: modifiers.alt(),
- logo: modifiers.logo(),
- }
+) -> keyboard::Modifiers {
+ let mut result = keyboard::Modifiers::empty();
+
+ result.set(keyboard::Modifiers::SHIFT, modifiers.shift());
+ result.set(keyboard::Modifiers::CTRL, modifiers.ctrl());
+ result.set(keyboard::Modifiers::ALT, modifiers.alt());
+ result.set(keyboard::Modifiers::LOGO, modifiers.logo());
+
+ result
}
/// Converts a physical cursor position to a logical `Point`.
@@ -201,11 +377,223 @@ pub fn cursor_position(
Point::new(logical_position.x, logical_position.y)
}
+/// Converts a `Touch` from [`winit`] to an [`iced_native`] touch event.
+///
+/// [`winit`]: https://github.com/rust-windowing/winit
+/// [`iced_native`]: https://github.com/hecrj/iced/tree/master/native
+pub fn touch_event(
+ touch: winit::event::Touch,
+ scale_factor: f64,
+) -> touch::Event {
+ let id = touch::Finger(touch.id);
+ let position = {
+ let location = touch.location.to_logical::<f64>(scale_factor);
+
+ Point::new(location.x as f32, location.y as f32)
+ };
+
+ match touch.phase {
+ winit::event::TouchPhase::Started => {
+ touch::Event::FingerPressed { id, position }
+ }
+ winit::event::TouchPhase::Moved => {
+ touch::Event::FingerMoved { id, position }
+ }
+ winit::event::TouchPhase::Ended => {
+ touch::Event::FingerLifted { id, position }
+ }
+ winit::event::TouchPhase::Cancelled => {
+ touch::Event::FingerLost { id, position }
+ }
+ }
+}
+
+/// Converts a `KeyCode` from [`iced_native`] to an [`winit`] key code.
+///
+/// [`winit`]: https://github.com/rust-windowing/winit
+/// [`iced_native`]: https://github.com/hecrj/iced/tree/master/native
+fn to_virtual_keycode(
+ keycode: keyboard::KeyCode,
+) -> winit::event::VirtualKeyCode {
+ use keyboard::KeyCode;
+ use winit::event::VirtualKeyCode;
+
+ match keycode {
+ KeyCode::Key1 => VirtualKeyCode::Key1,
+ KeyCode::Key2 => VirtualKeyCode::Key2,
+ KeyCode::Key3 => VirtualKeyCode::Key3,
+ KeyCode::Key4 => VirtualKeyCode::Key4,
+ KeyCode::Key5 => VirtualKeyCode::Key5,
+ KeyCode::Key6 => VirtualKeyCode::Key6,
+ KeyCode::Key7 => VirtualKeyCode::Key7,
+ KeyCode::Key8 => VirtualKeyCode::Key8,
+ KeyCode::Key9 => VirtualKeyCode::Key9,
+ KeyCode::Key0 => VirtualKeyCode::Key0,
+ KeyCode::A => VirtualKeyCode::A,
+ KeyCode::B => VirtualKeyCode::B,
+ KeyCode::C => VirtualKeyCode::C,
+ KeyCode::D => VirtualKeyCode::D,
+ KeyCode::E => VirtualKeyCode::E,
+ KeyCode::F => VirtualKeyCode::F,
+ KeyCode::G => VirtualKeyCode::G,
+ KeyCode::H => VirtualKeyCode::H,
+ KeyCode::I => VirtualKeyCode::I,
+ KeyCode::J => VirtualKeyCode::J,
+ KeyCode::K => VirtualKeyCode::K,
+ KeyCode::L => VirtualKeyCode::L,
+ KeyCode::M => VirtualKeyCode::M,
+ KeyCode::N => VirtualKeyCode::N,
+ KeyCode::O => VirtualKeyCode::O,
+ KeyCode::P => VirtualKeyCode::P,
+ KeyCode::Q => VirtualKeyCode::Q,
+ KeyCode::R => VirtualKeyCode::R,
+ KeyCode::S => VirtualKeyCode::S,
+ KeyCode::T => VirtualKeyCode::T,
+ KeyCode::U => VirtualKeyCode::U,
+ KeyCode::V => VirtualKeyCode::V,
+ KeyCode::W => VirtualKeyCode::W,
+ KeyCode::X => VirtualKeyCode::X,
+ KeyCode::Y => VirtualKeyCode::Y,
+ KeyCode::Z => VirtualKeyCode::Z,
+ KeyCode::Escape => VirtualKeyCode::Escape,
+ KeyCode::F1 => VirtualKeyCode::F1,
+ KeyCode::F2 => VirtualKeyCode::F2,
+ KeyCode::F3 => VirtualKeyCode::F3,
+ KeyCode::F4 => VirtualKeyCode::F4,
+ KeyCode::F5 => VirtualKeyCode::F5,
+ KeyCode::F6 => VirtualKeyCode::F6,
+ KeyCode::F7 => VirtualKeyCode::F7,
+ KeyCode::F8 => VirtualKeyCode::F8,
+ KeyCode::F9 => VirtualKeyCode::F9,
+ KeyCode::F10 => VirtualKeyCode::F10,
+ KeyCode::F11 => VirtualKeyCode::F11,
+ KeyCode::F12 => VirtualKeyCode::F12,
+ KeyCode::F13 => VirtualKeyCode::F13,
+ KeyCode::F14 => VirtualKeyCode::F14,
+ KeyCode::F15 => VirtualKeyCode::F15,
+ KeyCode::F16 => VirtualKeyCode::F16,
+ KeyCode::F17 => VirtualKeyCode::F17,
+ KeyCode::F18 => VirtualKeyCode::F18,
+ KeyCode::F19 => VirtualKeyCode::F19,
+ KeyCode::F20 => VirtualKeyCode::F20,
+ KeyCode::F21 => VirtualKeyCode::F21,
+ KeyCode::F22 => VirtualKeyCode::F22,
+ KeyCode::F23 => VirtualKeyCode::F23,
+ KeyCode::F24 => VirtualKeyCode::F24,
+ KeyCode::Snapshot => VirtualKeyCode::Snapshot,
+ KeyCode::Scroll => VirtualKeyCode::Scroll,
+ KeyCode::Pause => VirtualKeyCode::Pause,
+ KeyCode::Insert => VirtualKeyCode::Insert,
+ KeyCode::Home => VirtualKeyCode::Home,
+ KeyCode::Delete => VirtualKeyCode::Delete,
+ KeyCode::End => VirtualKeyCode::End,
+ KeyCode::PageDown => VirtualKeyCode::PageDown,
+ KeyCode::PageUp => VirtualKeyCode::PageUp,
+ KeyCode::Left => VirtualKeyCode::Left,
+ KeyCode::Up => VirtualKeyCode::Up,
+ KeyCode::Right => VirtualKeyCode::Right,
+ KeyCode::Down => VirtualKeyCode::Down,
+ KeyCode::Backspace => VirtualKeyCode::Back,
+ KeyCode::Enter => VirtualKeyCode::Return,
+ KeyCode::Space => VirtualKeyCode::Space,
+ KeyCode::Compose => VirtualKeyCode::Compose,
+ KeyCode::Caret => VirtualKeyCode::Caret,
+ KeyCode::Numlock => VirtualKeyCode::Numlock,
+ KeyCode::Numpad0 => VirtualKeyCode::Numpad0,
+ KeyCode::Numpad1 => VirtualKeyCode::Numpad1,
+ KeyCode::Numpad2 => VirtualKeyCode::Numpad2,
+ KeyCode::Numpad3 => VirtualKeyCode::Numpad3,
+ KeyCode::Numpad4 => VirtualKeyCode::Numpad4,
+ KeyCode::Numpad5 => VirtualKeyCode::Numpad5,
+ KeyCode::Numpad6 => VirtualKeyCode::Numpad6,
+ KeyCode::Numpad7 => VirtualKeyCode::Numpad7,
+ KeyCode::Numpad8 => VirtualKeyCode::Numpad8,
+ KeyCode::Numpad9 => VirtualKeyCode::Numpad9,
+ KeyCode::AbntC1 => VirtualKeyCode::AbntC1,
+ KeyCode::AbntC2 => VirtualKeyCode::AbntC2,
+ KeyCode::NumpadAdd => VirtualKeyCode::NumpadAdd,
+ KeyCode::Plus => VirtualKeyCode::Plus,
+ KeyCode::Apostrophe => VirtualKeyCode::Apostrophe,
+ KeyCode::Apps => VirtualKeyCode::Apps,
+ KeyCode::At => VirtualKeyCode::At,
+ KeyCode::Ax => VirtualKeyCode::Ax,
+ KeyCode::Backslash => VirtualKeyCode::Backslash,
+ KeyCode::Calculator => VirtualKeyCode::Calculator,
+ KeyCode::Capital => VirtualKeyCode::Capital,
+ KeyCode::Colon => VirtualKeyCode::Colon,
+ KeyCode::Comma => VirtualKeyCode::Comma,
+ KeyCode::Convert => VirtualKeyCode::Convert,
+ KeyCode::NumpadDecimal => VirtualKeyCode::NumpadDecimal,
+ KeyCode::NumpadDivide => VirtualKeyCode::NumpadDivide,
+ KeyCode::Equals => VirtualKeyCode::Equals,
+ KeyCode::Grave => VirtualKeyCode::Grave,
+ KeyCode::Kana => VirtualKeyCode::Kana,
+ KeyCode::Kanji => VirtualKeyCode::Kanji,
+ KeyCode::LAlt => VirtualKeyCode::LAlt,
+ KeyCode::LBracket => VirtualKeyCode::LBracket,
+ KeyCode::LControl => VirtualKeyCode::LControl,
+ KeyCode::LShift => VirtualKeyCode::LShift,
+ KeyCode::LWin => VirtualKeyCode::LWin,
+ KeyCode::Mail => VirtualKeyCode::Mail,
+ KeyCode::MediaSelect => VirtualKeyCode::MediaSelect,
+ KeyCode::MediaStop => VirtualKeyCode::MediaStop,
+ KeyCode::Minus => VirtualKeyCode::Minus,
+ KeyCode::NumpadMultiply => VirtualKeyCode::NumpadMultiply,
+ KeyCode::Mute => VirtualKeyCode::Mute,
+ KeyCode::MyComputer => VirtualKeyCode::MyComputer,
+ KeyCode::NavigateForward => VirtualKeyCode::NavigateForward,
+ KeyCode::NavigateBackward => VirtualKeyCode::NavigateBackward,
+ KeyCode::NextTrack => VirtualKeyCode::NextTrack,
+ KeyCode::NoConvert => VirtualKeyCode::NoConvert,
+ KeyCode::NumpadComma => VirtualKeyCode::NumpadComma,
+ KeyCode::NumpadEnter => VirtualKeyCode::NumpadEnter,
+ KeyCode::NumpadEquals => VirtualKeyCode::NumpadEquals,
+ KeyCode::OEM102 => VirtualKeyCode::OEM102,
+ KeyCode::Period => VirtualKeyCode::Period,
+ KeyCode::PlayPause => VirtualKeyCode::PlayPause,
+ KeyCode::Power => VirtualKeyCode::Power,
+ KeyCode::PrevTrack => VirtualKeyCode::PrevTrack,
+ KeyCode::RAlt => VirtualKeyCode::RAlt,
+ KeyCode::RBracket => VirtualKeyCode::RBracket,
+ KeyCode::RControl => VirtualKeyCode::RControl,
+ KeyCode::RShift => VirtualKeyCode::RShift,
+ KeyCode::RWin => VirtualKeyCode::RWin,
+ KeyCode::Semicolon => VirtualKeyCode::Semicolon,
+ KeyCode::Slash => VirtualKeyCode::Slash,
+ KeyCode::Sleep => VirtualKeyCode::Sleep,
+ KeyCode::Stop => VirtualKeyCode::Stop,
+ KeyCode::NumpadSubtract => VirtualKeyCode::NumpadSubtract,
+ KeyCode::Sysrq => VirtualKeyCode::Sysrq,
+ KeyCode::Tab => VirtualKeyCode::Tab,
+ KeyCode::Underline => VirtualKeyCode::Underline,
+ KeyCode::Unlabeled => VirtualKeyCode::Unlabeled,
+ KeyCode::VolumeDown => VirtualKeyCode::VolumeDown,
+ KeyCode::VolumeUp => VirtualKeyCode::VolumeUp,
+ KeyCode::Wake => VirtualKeyCode::Wake,
+ KeyCode::WebBack => VirtualKeyCode::WebBack,
+ KeyCode::WebFavorites => VirtualKeyCode::WebFavorites,
+ KeyCode::WebForward => VirtualKeyCode::WebForward,
+ KeyCode::WebHome => VirtualKeyCode::WebHome,
+ KeyCode::WebRefresh => VirtualKeyCode::WebRefresh,
+ KeyCode::WebSearch => VirtualKeyCode::WebSearch,
+ KeyCode::WebStop => VirtualKeyCode::WebStop,
+ KeyCode::Yen => VirtualKeyCode::Yen,
+ KeyCode::Copy => VirtualKeyCode::Copy,
+ KeyCode::Paste => VirtualKeyCode::Paste,
+ KeyCode::Cut => VirtualKeyCode::Cut,
+ KeyCode::Asterisk => VirtualKeyCode::Asterisk,
+ }
+}
+
/// Converts a `VirtualKeyCode` from [`winit`] to an [`iced_native`] key code.
///
/// [`winit`]: https://github.com/rust-windowing/winit
/// [`iced_native`]: https://github.com/hecrj/iced/tree/master/native
-pub fn key_code(virtual_keycode: winit::event::VirtualKeyCode) -> KeyCode {
+pub fn key_code(
+ virtual_keycode: winit::event::VirtualKeyCode,
+) -> keyboard::KeyCode {
+ use keyboard::KeyCode;
+
match virtual_keycode {
winit::event::VirtualKeyCode::Key1 => KeyCode::Key1,
winit::event::VirtualKeyCode::Key2 => KeyCode::Key2,
@@ -299,7 +687,8 @@ pub fn key_code(virtual_keycode: winit::event::VirtualKeyCode) -> KeyCode {
winit::event::VirtualKeyCode::Numpad9 => KeyCode::Numpad9,
winit::event::VirtualKeyCode::AbntC1 => KeyCode::AbntC1,
winit::event::VirtualKeyCode::AbntC2 => KeyCode::AbntC2,
- winit::event::VirtualKeyCode::Add => KeyCode::Add,
+ winit::event::VirtualKeyCode::NumpadAdd => KeyCode::NumpadAdd,
+ winit::event::VirtualKeyCode::Plus => KeyCode::Plus,
winit::event::VirtualKeyCode::Apostrophe => KeyCode::Apostrophe,
winit::event::VirtualKeyCode::Apps => KeyCode::Apps,
winit::event::VirtualKeyCode::At => KeyCode::At,
@@ -310,8 +699,8 @@ pub fn key_code(virtual_keycode: winit::event::VirtualKeyCode) -> KeyCode {
winit::event::VirtualKeyCode::Colon => KeyCode::Colon,
winit::event::VirtualKeyCode::Comma => KeyCode::Comma,
winit::event::VirtualKeyCode::Convert => KeyCode::Convert,
- winit::event::VirtualKeyCode::Decimal => KeyCode::Decimal,
- winit::event::VirtualKeyCode::Divide => KeyCode::Divide,
+ winit::event::VirtualKeyCode::NumpadDecimal => KeyCode::NumpadDecimal,
+ winit::event::VirtualKeyCode::NumpadDivide => KeyCode::NumpadDivide,
winit::event::VirtualKeyCode::Equals => KeyCode::Equals,
winit::event::VirtualKeyCode::Grave => KeyCode::Grave,
winit::event::VirtualKeyCode::Kana => KeyCode::Kana,
@@ -325,7 +714,7 @@ pub fn key_code(virtual_keycode: winit::event::VirtualKeyCode) -> KeyCode {
winit::event::VirtualKeyCode::MediaSelect => KeyCode::MediaSelect,
winit::event::VirtualKeyCode::MediaStop => KeyCode::MediaStop,
winit::event::VirtualKeyCode::Minus => KeyCode::Minus,
- winit::event::VirtualKeyCode::Multiply => KeyCode::Multiply,
+ winit::event::VirtualKeyCode::NumpadMultiply => KeyCode::NumpadMultiply,
winit::event::VirtualKeyCode::Mute => KeyCode::Mute,
winit::event::VirtualKeyCode::MyComputer => KeyCode::MyComputer,
winit::event::VirtualKeyCode::NavigateForward => {
@@ -353,7 +742,7 @@ pub fn key_code(virtual_keycode: winit::event::VirtualKeyCode) -> KeyCode {
winit::event::VirtualKeyCode::Slash => KeyCode::Slash,
winit::event::VirtualKeyCode::Sleep => KeyCode::Sleep,
winit::event::VirtualKeyCode::Stop => KeyCode::Stop,
- winit::event::VirtualKeyCode::Subtract => KeyCode::Subtract,
+ winit::event::VirtualKeyCode::NumpadSubtract => KeyCode::NumpadSubtract,
winit::event::VirtualKeyCode::Sysrq => KeyCode::Sysrq,
winit::event::VirtualKeyCode::Tab => KeyCode::Tab,
winit::event::VirtualKeyCode::Underline => KeyCode::Underline,
@@ -372,6 +761,7 @@ pub fn key_code(virtual_keycode: winit::event::VirtualKeyCode) -> KeyCode {
winit::event::VirtualKeyCode::Copy => KeyCode::Copy,
winit::event::VirtualKeyCode::Paste => KeyCode::Paste,
winit::event::VirtualKeyCode::Cut => KeyCode::Cut,
+ winit::event::VirtualKeyCode::Asterisk => KeyCode::Asterisk,
}
}
diff --git a/winit/src/lib.rs b/winit/src/lib.rs
index 8ca8eec1..1707846a 100644
--- a/winit/src/lib.rs
+++ b/winit/src/lib.rs
@@ -1,6 +1,6 @@
//! A windowing shell for Iced, on top of [`winit`].
//!
-//! ![`iced_winit` crate graph](https://github.com/hecrj/iced/blob/cae26cb7bc627f4a5b3bcf1cd023a0c552e8c65e/docs/graphs/winit.png?raw=true)
+//! ![The native path of the Iced ecosystem](https://github.com/hecrj/iced/blob/0525d76ff94e828b7b21634fa94a747022001c83/docs/graphs/native.png?raw=true)
//!
//! `iced_winit` offers some convenient abstractions on top of [`iced_native`]
//! to quickstart development when using [`winit`].
@@ -13,8 +13,7 @@
//!
//! [`iced_native`]: https://github.com/hecrj/iced/tree/master/native
//! [`winit`]: https://github.com/rust-windowing/winit
-//! [`Application`]: trait.Application.html
-//! [`conversion`]: conversion
+//! [`conversion`]: crate::conversion
#![deny(missing_docs)]
#![deny(missing_debug_implementations)]
#![deny(unused_results)]
@@ -32,12 +31,14 @@ pub mod settings;
mod clipboard;
mod error;
mod mode;
+mod position;
mod proxy;
pub use application::Application;
pub use clipboard::Clipboard;
pub use error::Error;
pub use mode::Mode;
+pub use position::Position;
pub use proxy::Proxy;
pub use settings::Settings;
diff --git a/winit/src/mode.rs b/winit/src/mode.rs
index 37464711..fdce8e23 100644
--- a/winit/src/mode.rs
+++ b/winit/src/mode.rs
@@ -6,4 +6,7 @@ pub enum Mode {
/// The application takes the whole screen of its current monitor.
Fullscreen,
+
+ /// The application is hidden
+ Hidden,
}
diff --git a/winit/src/position.rs b/winit/src/position.rs
new file mode 100644
index 00000000..c260c29e
--- /dev/null
+++ b/winit/src/position.rs
@@ -0,0 +1,22 @@
+/// The position of a window in a given screen.
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub enum Position {
+ /// The platform-specific default position for a new window.
+ Default,
+ /// The window is completely centered on the screen.
+ Centered,
+ /// The window is positioned with specific coordinates: `(X, Y)`.
+ ///
+ /// When the decorations of the window are enabled, Windows 10 will add some
+ /// invisible padding to the window. This padding gets included in the
+ /// position. So if you have decorations enabled and want the window to be
+ /// at (0, 0) you would have to set the position to
+ /// `(PADDING_X, PADDING_Y)`.
+ Specific(i32, i32),
+}
+
+impl Default for Position {
+ fn default() -> Self {
+ Self::Default
+ }
+}
diff --git a/winit/src/proxy.rs b/winit/src/proxy.rs
index 532f8c56..7b9074d7 100644
--- a/winit/src/proxy.rs
+++ b/winit/src/proxy.rs
@@ -21,8 +21,6 @@ impl<Message: 'static> Clone for Proxy<Message> {
impl<Message: 'static> Proxy<Message> {
/// Creates a new [`Proxy`] from an `EventLoopProxy`.
- ///
- /// [`Proxy`]: struct.Proxy.html
pub fn new(raw: winit::event_loop::EventLoopProxy<Message>) -> Self {
Self { raw }
}
diff --git a/winit/src/settings.rs b/winit/src/settings.rs
index 92541e7d..72a1a24a 100644
--- a/winit/src/settings.rs
+++ b/winit/src/settings.rs
@@ -9,22 +9,30 @@ mod platform;
pub use platform::PlatformSpecific;
use crate::conversion;
-use crate::Mode;
+use crate::{Mode, Position};
use winit::monitor::MonitorHandle;
use winit::window::WindowBuilder;
/// The settings of an application.
#[derive(Debug, Clone, Default)]
pub struct Settings<Flags> {
- /// The [`Window`] settings
+ /// The identifier of the application.
///
- /// [`Window`]: struct.Window.html
+ /// If provided, this identifier may be used to identify the application or
+ /// communicate with it through the windowing system.
+ pub id: Option<String>,
+
+ /// The [`Window`] settings
pub window: Window,
/// The data needed to initialize an [`Application`].
///
- /// [`Application`]: trait.Application.html
+ /// [`Application`]: crate::Application
pub flags: Flags,
+
+ /// Whether the [`Application`] should exit when the user requests the
+ /// window to close (e.g. the user presses the close button).
+ pub exit_on_close_request: bool,
}
/// The window settings of an application.
@@ -33,6 +41,9 @@ pub struct Window {
/// The size of the window.
pub size: (u32, u32),
+ /// The position of the window.
+ pub position: Position,
+
/// The minimum size of the window.
pub min_size: Option<(u32, u32)>,
@@ -45,9 +56,12 @@ pub struct Window {
/// Whether the window should have a border, a title bar, etc.
pub decorations: bool,
- /// Whether the window should be transparent
+ /// Whether the window should be transparent.
pub transparent: bool,
+ /// Whether the window will always be on top of other windows.
+ pub always_on_top: bool,
+
/// The window icon, which is also usually used in the taskbar
pub icon: Option<winit::window::Icon>,
@@ -61,7 +75,8 @@ impl Window {
self,
title: &str,
mode: Mode,
- primary_monitor: MonitorHandle,
+ primary_monitor: Option<MonitorHandle>,
+ _id: Option<String>,
) -> WindowBuilder {
let mut window_builder = WindowBuilder::new();
@@ -74,7 +89,16 @@ impl Window {
.with_decorations(self.decorations)
.with_transparent(self.transparent)
.with_window_icon(self.icon)
- .with_fullscreen(conversion::fullscreen(primary_monitor, mode));
+ .with_always_on_top(self.always_on_top)
+ .with_visible(conversion::visible(mode));
+
+ if let Some(position) = conversion::position(
+ primary_monitor.as_ref(),
+ self.size,
+ self.position,
+ ) {
+ window_builder = window_builder.with_position(position);
+ }
if let Some((width, height)) = self.min_size {
window_builder = window_builder
@@ -86,6 +110,21 @@ impl Window {
.with_max_inner_size(winit::dpi::LogicalSize { width, height });
}
+ #[cfg(any(
+ target_os = "linux",
+ target_os = "dragonfly",
+ target_os = "freebsd",
+ target_os = "netbsd",
+ target_os = "openbsd"
+ ))]
+ {
+ use ::winit::platform::unix::WindowBuilderExtUnix;
+
+ if let Some(id) = _id {
+ window_builder = window_builder.with_app_id(id);
+ }
+ }
+
#[cfg(target_os = "windows")]
{
use winit::platform::windows::WindowBuilderExtWindows;
@@ -93,8 +132,14 @@ impl Window {
if let Some(parent) = self.platform_specific.parent {
window_builder = window_builder.with_parent_window(parent);
}
+
+ window_builder = window_builder
+ .with_drag_and_drop(self.platform_specific.drag_and_drop);
}
+ window_builder = window_builder
+ .with_fullscreen(conversion::fullscreen(primary_monitor, mode));
+
window_builder
}
}
@@ -103,11 +148,13 @@ impl Default for Window {
fn default() -> Window {
Window {
size: (1024, 768),
+ position: Position::default(),
min_size: None,
max_size: None,
resizable: true,
decorations: true,
transparent: false,
+ always_on_top: false,
icon: None,
platform_specific: Default::default(),
}
diff --git a/winit/src/settings/windows.rs b/winit/src/settings/windows.rs
index 76b8d067..fc26acd7 100644
--- a/winit/src/settings/windows.rs
+++ b/winit/src/settings/windows.rs
@@ -2,8 +2,20 @@
//! Platform specific settings for Windows.
/// The platform specific window settings of an application.
-#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct PlatformSpecific {
- /// Parent Window
+ /// Parent window
pub parent: Option<winapi::shared::windef::HWND>,
+
+ /// Drag and drop support
+ pub drag_and_drop: bool,
+}
+
+impl Default for PlatformSpecific {
+ fn default() -> Self {
+ Self {
+ parent: None,
+ drag_and_drop: true,
+ }
+ }
}