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.yml6
-rw-r--r--CHANGELOG.md66
-rw-r--r--Cargo.toml20
-rw-r--r--README.md2
-rw-r--r--core/Cargo.toml3
-rw-r--r--core/README.md2
-rw-r--r--core/src/keyboard.rs2
-rw-r--r--core/src/keyboard/hotkey.rs18
-rw-r--r--core/src/keyboard/modifiers.rs76
-rw-r--r--core/src/lib.rs4
-rw-r--r--core/src/menu.rs145
-rw-r--r--core/src/padding.rs107
-rw-r--r--core/src/rectangle.rs4
-rw-r--r--core/src/size.rs8
-rw-r--r--examples/events/src/main.rs47
-rw-r--r--examples/game_of_life/src/main.rs13
-rw-r--r--examples/game_of_life/src/preset.rs13
-rw-r--r--examples/game_of_life/src/style.rs1
-rw-r--r--examples/integration/src/scene.rs11
-rw-r--r--examples/menu/Cargo.toml10
-rw-r--r--examples/menu/src/main.rs117
-rw-r--r--examples/pane_grid/src/main.rs108
-rw-r--r--examples/pick_list/src/main.rs9
-rw-r--r--examples/pokedex/src/main.rs5
-rw-r--r--examples/scrollable/Cargo.toml2
-rw-r--r--examples/scrollable/src/main.rs107
-rw-r--r--examples/styling/src/main.rs75
-rw-r--r--examples/tour/src/main.rs32
-rw-r--r--examples/url_handler/Cargo.toml10
-rw-r--r--examples/url_handler/src/main.rs73
-rw-r--r--futures/Cargo.toml2
-rw-r--r--futures/src/subscription.rs6
-rw-r--r--glow/Cargo.toml6
-rw-r--r--glow/src/backend.rs7
-rw-r--r--glow/src/settings.rs18
-rw-r--r--glow/src/text.rs8
-rw-r--r--glow/src/widget.rs3
-rw-r--r--glow/src/widget/pane_grid.rs2
-rw-r--r--glow/src/widget/toggler.rs9
-rw-r--r--glutin/Cargo.toml14
-rw-r--r--glutin/README.md2
-rw-r--r--glutin/src/application.rs63
-rw-r--r--graphics/Cargo.toml6
-rw-r--r--graphics/src/overlay/menu.rs12
-rw-r--r--graphics/src/widget.rs3
-rw-r--r--graphics/src/widget/button.rs4
-rw-r--r--graphics/src/widget/canvas/frame.rs2
-rw-r--r--graphics/src/widget/canvas/program.rs2
-rw-r--r--graphics/src/widget/pane_grid.rs8
-rw-r--r--graphics/src/widget/pick_list.rs21
-rw-r--r--graphics/src/widget/scrollable.rs10
-rw-r--r--graphics/src/widget/toggler.rs99
-rw-r--r--graphics/src/widget/tooltip.rs6
-rw-r--r--graphics/src/window/compositor.rs5
-rw-r--r--native/Cargo.toml6
-rw-r--r--native/README.md2
-rw-r--r--native/src/event.rs21
-rw-r--r--native/src/layout/flex.rs12
-rw-r--r--native/src/layout/limits.rs9
-rw-r--r--native/src/lib.rs4
-rw-r--r--native/src/overlay/menu.rs24
-rw-r--r--native/src/program.rs2
-rw-r--r--native/src/renderer/null.rs22
-rw-r--r--native/src/user_interface.rs2
-rw-r--r--native/src/widget.rs13
-rw-r--r--native/src/widget/button.rs55
-rw-r--r--native/src/widget/checkbox.rs14
-rw-r--r--native/src/widget/column.rs15
-rw-r--r--native/src/widget/container.rs24
-rw-r--r--native/src/widget/image/viewer.rs25
-rw-r--r--native/src/widget/pane_grid.rs2
-rw-r--r--native/src/widget/pane_grid/content.rs17
-rw-r--r--native/src/widget/pane_grid/title_bar.rs81
-rw-r--r--native/src/widget/pick_list.rs69
-rw-r--r--native/src/widget/radio.rs26
-rw-r--r--native/src/widget/row.rs15
-rw-r--r--native/src/widget/scrollable.rs126
-rw-r--r--native/src/widget/text_input.rs58
-rw-r--r--native/src/widget/text_input/cursor.rs4
-rw-r--r--native/src/widget/text_input/editor.rs4
-rw-r--r--native/src/widget/toggler.rs277
-rw-r--r--native/src/window/event.rs6
-rw-r--r--rustfmt.toml1
-rw-r--r--src/application.rs48
-rw-r--r--src/lib.rs8
-rw-r--r--src/sandbox.rs24
-rw-r--r--src/settings.rs25
-rw-r--r--src/widget.rs6
-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.rs7
-rw-r--r--style/Cargo.toml7
-rw-r--r--style/src/lib.rs1
-rw-r--r--style/src/pick_list.rs2
-rw-r--r--style/src/rule.rs18
-rw-r--r--style/src/toggler.rs57
-rw-r--r--web/Cargo.toml8
-rw-r--r--web/README.md2
-rw-r--r--web/src/css.rs67
-rw-r--r--web/src/lib.rs6
-rw-r--r--web/src/widget.rs3
-rw-r--r--web/src/widget/button.rs50
-rw-r--r--web/src/widget/column.rs20
-rw-r--r--web/src/widget/container.rs20
-rw-r--r--web/src/widget/row.rs20
-rw-r--r--web/src/widget/scrollable.rs10
-rw-r--r--web/src/widget/text_input.rs27
-rw-r--r--web/src/widget/toggler.rs171
-rw-r--r--wgpu/Cargo.toml10
-rw-r--r--wgpu/README.md2
-rw-r--r--wgpu/src/backend.rs9
-rw-r--r--wgpu/src/image.rs110
-rw-r--r--wgpu/src/image/atlas.rs24
-rw-r--r--wgpu/src/image/vector.rs27
-rw-r--r--wgpu/src/quad.rs119
-rw-r--r--wgpu/src/settings.rs52
-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.vert47
-rw-r--r--wgpu/src/shader/quad.vert.spvbin3604 -> 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.rs3
-rw-r--r--wgpu/src/triangle.rs110
-rw-r--r--wgpu/src/triangle/msaa.rs70
-rw-r--r--wgpu/src/widget.rs3
-rw-r--r--wgpu/src/widget/pane_grid.rs2
-rw-r--r--wgpu/src/widget/toggler.rs9
-rw-r--r--wgpu/src/window/compositor.rs29
-rw-r--r--winit/Cargo.toml14
-rw-r--r--winit/README.md2
-rw-r--r--winit/src/application.rs80
-rw-r--r--winit/src/application/state.rs21
-rw-r--r--winit/src/conversion.rs354
-rw-r--r--winit/src/lib.rs2
-rw-r--r--winit/src/mode.rs3
-rw-r--r--winit/src/position.rs22
-rw-r--r--winit/src/settings.rs25
-rw-r--r--winit/src/settings/windows.rs16
157 files changed, 3576 insertions, 1019 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 bc531abf..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
diff --git a/CHANGELOG.md b/CHANGELOG.md
index b0cb739d..3feaeba3 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5,11 +5,70 @@ 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
-- Support for the [`smol`] async runtime. [#699]
+- 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
@@ -148,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
@@ -160,7 +219,8 @@ 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.2.0...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
diff --git a/Cargo.toml b/Cargo.toml
index f3a9676f..5b1cfb0e 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,6 +1,6 @@
[package]
name = "iced"
-version = "0.2.0"
+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"
@@ -84,22 +84,24 @@ members = [
"examples/todos",
"examples/tour",
"examples/tooltip",
+ "examples/url_handler",
+ "examples/menu",
]
[dependencies]
-iced_core = { version = "0.3", path = "core" }
-iced_futures = { version = "0.2", 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.2", path = "winit" }
-iced_glutin = { version = "0.1", path = "glutin", optional = true }
-iced_wgpu = { version = "0.3", 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.3", 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/README.md b/README.md
index da754686..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.2"
+iced = "0.3"
```
__Iced moves fast and the `master` branch can contain breaking changes!__ If
diff --git a/core/Cargo.toml b/core/Cargo.toml
index a859c868..54d927af 100644
--- a/core/Cargo.toml
+++ b/core/Cargo.toml
@@ -1,6 +1,6 @@
[package]
name = "iced_core"
-version = "0.3.0"
+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 3ec053ac..86d631d2 100644
--- a/core/README.md
+++ b/core/README.md
@@ -18,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.3"
+iced_core = "0.4"
```
__Iced moves fast and the `master` branch can contain breaking changes!__ If
diff --git a/core/src/keyboard.rs b/core/src/keyboard.rs
index 61e017ad..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;
pub use event::Event;
+pub use hotkey::Hotkey;
pub use key_code::KeyCode;
pub use modifiers::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/modifiers.rs b/core/src/keyboard/modifiers.rs
index d2a0500e..383b9370 100644
--- a/core/src/keyboard/modifiers.rs
+++ b/core/src/keyboard/modifiers.rs
@@ -1,20 +1,53 @@
-/// The current state of the keyboard modifiers.
-#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
-pub struct Modifiers {
- /// Whether a shift key is pressed
- pub shift: bool,
+use bitflags::bitflags;
- /// 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,
+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 {
+ /// 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
@@ -22,24 +55,13 @@ impl Modifiers {
///
/// - It is the `logo` or command key (⌘) on macOS
/// - It is the `control` key on other platforms
- pub fn is_command_pressed(self) -> bool {
+ pub fn command(self) -> bool {
#[cfg(target_os = "macos")]
- let is_pressed = self.logo;
+ let is_pressed = self.logo();
#[cfg(not(target_os = "macos"))]
- let is_pressed = self.control;
+ let is_pressed = self.control();
is_pressed
}
-
- /// Returns true if the current [`Modifiers`] have at least the same
- /// keys pressed as the provided ones, and false otherwise.
- pub fn matches(&self, modifiers: Self) -> bool {
- let shift = !modifiers.shift || self.shift;
- let control = !modifiers.control || self.control;
- let alt = !modifiers.alt || self.alt;
- let logo = !modifiers.logo || self.logo;
-
- shift && control && alt && logo
- }
}
diff --git a/core/src/lib.rs b/core/src/lib.rs
index f2d21a5f..c4288158 100644
--- a/core/src/lib.rs
+++ b/core/src/lib.rs
@@ -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/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/rectangle.rs b/core/src/rectangle.rs
index 0a7f5fe2..4e082051 100644
--- a/core/src/rectangle.rs
+++ b/core/src/rectangle.rs
@@ -105,8 +105,8 @@ impl Rectangle<f32> {
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 9ea9e686..6745c6c8 100644
--- a/core/src/size.rs
+++ b/core/src/size.rs
@@ -1,4 +1,4 @@
-use crate::Vector;
+use crate::{Padding, Vector};
use std::f32;
/// An amount of space in 2 dimensions.
@@ -28,10 +28,10 @@ impl Size {
pub const INFINITY: Size = Size::new(f32::INFINITY, f32::INFINITY);
/// Increments the [`Size`] to account for the given padding.
- 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,
}
}
}
diff --git a/examples/events/src/main.rs b/examples/events/src/main.rs
index 18e6364b..446c190b 100644
--- a/examples/events/src/main.rs
+++ b/examples/events/src/main.rs
@@ -1,22 +1,30 @@
use iced::{
- executor, Align, Application, Checkbox, Clipboard, 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 {
@@ -38,27 +46,35 @@ impl Application for Events {
_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> {
@@ -75,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/src/main.rs b/examples/game_of_life/src/main.rs
index 64599163..c3e16e8b 100644
--- a/examples/game_of_life/src/main.rs
+++ b/examples/game_of_life/src/main.rs
@@ -6,9 +6,11 @@ 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, Clipboard, Column, Command, Container,
Element, Length, Row, Settings, Subscription, Text,
@@ -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()
})
}
@@ -128,6 +134,13 @@ 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);
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 6605826f..be9a0e96 100644
--- a/examples/game_of_life/src/style.rs
+++ b/examples/game_of_life/src/style.rs
@@ -171,6 +171,7 @@ impl pick_list::StyleSheet for PickList {
},
border_radius: 2.0,
icon_size: 0.5,
+ ..pick_list::Style::default()
}
}
diff --git a/examples/integration/src/scene.rs b/examples/integration/src/scene.rs
index 36c0a41d..3e8277c8 100644
--- a/examples/integration/src/scene.rs
+++ b/examples/integration/src/scene.rs
@@ -20,8 +20,8 @@ impl Scene {
) -> wgpu::RenderPass<'a> {
encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
label: None,
- color_attachments: &[wgpu::RenderPassColorAttachmentDescriptor {
- attachment: target,
+ color_attachments: &[wgpu::RenderPassColorAttachment {
+ view: target,
resolve_target: None,
ops: wgpu::Operations {
load: wgpu::LoadOp::Clear({
@@ -75,15 +75,16 @@ fn build_pipeline(device: &wgpu::Device) -> wgpu::RenderPipeline {
entry_point: "main",
targets: &[wgpu::ColorTargetState {
format: wgpu::TextureFormat::Bgra8UnormSrgb,
- color_blend: wgpu::BlendState::REPLACE,
- alpha_blend: wgpu::BlendState::REPLACE,
+ blend: Some(wgpu::BlendState {
+ color: wgpu::BlendComponent::REPLACE,
+ alpha: wgpu::BlendComponent::REPLACE,
+ }),
write_mask: wgpu::ColorWrite::ALL,
}],
}),
primitive: wgpu::PrimitiveState {
topology: wgpu::PrimitiveTopology::TriangleList,
front_face: wgpu::FrontFace::Ccw,
- cull_mode: wgpu::CullMode::None,
..Default::default()
},
depth_stencil: None,
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/src/main.rs b/examples/pane_grid/src/main.rs
index 4b87a568..3bd8aa25 100644
--- a/examples/pane_grid/src/main.rs
+++ b/examples/pane_grid/src/main.rs
@@ -11,7 +11,7 @@ pub fn main() -> iced::Result {
}
struct Example {
- panes: pane_grid::State<Content>,
+ panes: pane_grid::State<Pane>,
panes_created: usize,
focus: Option<pane_grid::Pane>,
}
@@ -24,6 +24,7 @@ enum Message {
Clicked(pane_grid::Pane),
Dragged(pane_grid::DragEvent),
Resized(pane_grid::ResizeEvent),
+ TogglePin(pane_grid::Pane),
Close(pane_grid::Pane),
CloseFocused,
}
@@ -34,7 +35,7 @@ impl Application for Example {
type Flags = ();
fn new(_flags: ()) -> (Self, Command<Message>) {
- let (panes, _) = pane_grid::State::new(Content::new(0));
+ let (panes, _) = pane_grid::State::new(Pane::new(0));
(
Example {
@@ -60,7 +61,7 @@ impl Application for Example {
let result = self.panes.split(
axis,
&pane,
- Content::new(self.panes_created),
+ Pane::new(self.panes_created),
);
if let Some((pane, _)) = result {
@@ -74,7 +75,7 @@ impl Application for Example {
let result = self.panes.split(
axis,
&pane,
- Content::new(self.panes_created),
+ Pane::new(self.panes_created),
);
if let Some((pane, _)) = result {
@@ -106,6 +107,12 @@ impl Application for Example {
self.panes.swap(&pane, &target);
}
Message::Dragged(_) => {}
+ Message::TogglePin(pane) => {
+ if let Some(Pane { is_pinned, .. }) = self.panes.get_mut(&pane)
+ {
+ *is_pinned = !*is_pinned;
+ }
+ }
Message::Close(pane) => {
if let Some((_, sibling)) = self.panes.close(&pane) {
self.focus = Some(sibling);
@@ -113,8 +120,14 @@ impl Application for Example {
}
Message::CloseFocused => {
if let Some(pane) = self.focus {
- if let Some((_, sibling)) = self.panes.close(&pane) {
- self.focus = Some(sibling);
+ 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);
+ }
+ }
}
}
}
@@ -133,7 +146,7 @@ impl Application for Example {
Event::Keyboard(keyboard::Event::KeyPressed {
modifiers,
key_code,
- }) if modifiers.is_command_pressed() => handle_hotkey(key_code),
+ }) if modifiers.command() => handle_hotkey(key_code),
_ => None,
}
})
@@ -143,12 +156,20 @@ impl Application for Example {
let focus = self.focus;
let total_panes = self.panes.len();
- let pane_grid = PaneGrid::new(&mut self.panes, |pane, content| {
- let is_focused = focus == Some(pane);
+ 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(content.id.to_string())
+ Text::new(pane.content.id.to_string())
.color(if is_focused {
PANE_ID_COLOR_FOCUSED
} else {
@@ -159,12 +180,17 @@ impl Application for Example {
.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(content.view(pane, total_panes))
- .title_bar(title_bar)
- .style(style::Pane { 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)
@@ -212,6 +238,13 @@ fn handle_hotkey(key_code: keyboard::KeyCode) -> Option<Message> {
}
}
+struct Pane {
+ pub is_pinned: bool,
+ pub pin_button: button::State,
+ pub content: Content,
+ pub controls: Controls,
+}
+
struct Content {
id: usize,
scroll: scrollable::State,
@@ -220,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 {
@@ -234,6 +282,7 @@ impl Content {
&mut self,
pane: pane_grid::Pane,
total_panes: usize,
+ is_pinned: bool,
) -> Element<Message> {
let Content {
scroll,
@@ -273,7 +322,7 @@ impl Content {
style::Button::Primary,
));
- if total_panes > 1 {
+ if total_panes > 1 && !is_pinned {
controls = controls.push(button(
close,
"Close",
@@ -297,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(
@@ -359,6 +433,8 @@ mod style {
pub enum Button {
Primary,
Destructive,
+ Control,
+ Pin,
}
impl button::StyleSheet for Button {
@@ -368,6 +444,8 @@ 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 {
@@ -388,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/src/main.rs b/examples/pokedex/src/main.rs
index d2f1bb62..da1d5d5d 100644
--- a/examples/pokedex/src/main.rs
+++ b/examples/pokedex/src/main.rs
@@ -213,7 +213,10 @@ impl Pokemon {
}
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"))]
{
diff --git a/examples/scrollable/Cargo.toml b/examples/scrollable/Cargo.toml
index 12753fb6..08502458 100644
--- a/examples/scrollable/Cargo.toml
+++ b/examples/scrollable/Cargo.toml
@@ -6,4 +6,4 @@ edition = "2018"
publish = false
[dependencies]
-iced = { path = "../.." }
+iced = { path = "../..", features = ["debug"] }
diff --git a/examples/scrollable/src/main.rs b/examples/scrollable/src/main.rs
index 8dd2e20c..3416b83d 100644
--- a/examples/scrollable/src/main.rs
+++ b/examples/scrollable/src/main.rs
@@ -1,8 +1,8 @@
mod style;
use iced::{
- scrollable, Column, Container, Element, Length, Radio, Row, Rule, Sandbox,
- Scrollable, Settings, Space, Text,
+ button, scrollable, Button, Column, Container, Element, Length,
+ ProgressBar, Radio, Row, Rule, Sandbox, Scrollable, Settings, Space, Text,
};
pub fn main() -> iced::Result {
@@ -17,6 +17,9 @@ struct ScrollableDemo {
#[derive(Debug, Clone)]
enum Message {
ThemeChanged(style::Theme),
+ ScrollToTop(usize),
+ ScrollToBottom(usize),
+ Scrolled(usize, f32),
}
impl Sandbox for ScrollableDemo {
@@ -36,6 +39,25 @@ impl Sandbox for ScrollableDemo {
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;
+ }
+ }
}
}
@@ -62,13 +84,28 @@ impl Sandbox for ScrollableDemo {
let scrollable_row = Row::with_children(
variants
.iter_mut()
- .map(|variant| {
- let mut scrollable = Scrollable::new(&mut variant.state)
- .padding(10)
- .width(Length::Fill)
- .height(Length::Fill)
- .style(*theme)
- .push(Text::new(variant.title));
+ .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
@@ -108,12 +145,31 @@ impl Sandbox for ScrollableDemo {
.push(Space::with_height(Length::Units(1200)))
.push(Text::new("Middle"))
.push(Space::with_height(Length::Units(1200)))
- .push(Text::new("The End."));
-
- Container::new(scrollable)
+ .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)
- .style(*theme)
+ .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(),
@@ -142,10 +198,13 @@ impl Sandbox for ScrollableDemo {
/// A version of a scrollable
struct Variant {
title: &'static str,
- state: scrollable::State,
+ 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 {
@@ -153,31 +212,43 @@ impl Variant {
vec![
Self {
title: "Default Scrollbar",
- state: scrollable::State::new(),
+ 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",
- state: scrollable::State::new(),
+ 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",
- state: scrollable::State::new(),
+ 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",
- state: scrollable::State::new(),
+ 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/styling/src/main.rs b/examples/styling/src/main.rs
index 8975fd9a..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 {
@@ -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(
@@ -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 {
diff --git a/examples/tour/src/main.rs b/examples/tour/src/main.rs
index e8755d39..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,
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 c266f705..3cea6e1a 100644
--- a/futures/Cargo.toml
+++ b/futures/Cargo.toml
@@ -1,6 +1,6 @@
[package]
name = "iced_futures"
-version = "0.2.0"
+version = "0.3.0"
authors = ["Héctor Ramón Jiménez <hector0193@gmail.com>"]
edition = "2018"
description = "Commands, subscriptions, and runtimes for Iced"
diff --git a/futures/src/subscription.rs b/futures/src/subscription.rs
index 27d2d295..e60ad79a 100644
--- a/futures/src/subscription.rs
+++ b/futures/src/subscription.rs
@@ -125,9 +125,9 @@ 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.2/examples
-/// [`download_progress`]: https://github.com/hecrj/iced/tree/0.2/examples/download_progress
-/// [`stopwatch`]: https://github.com/hecrj/iced/tree/0.2/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`].
diff --git a/glow/Cargo.toml b/glow/Cargo.toml
index 3f85e52d..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"
@@ -24,11 +24,11 @@ bytemuck = "1.4"
log = "0.4"
[dependencies.iced_native]
-version = "0.3"
+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 92bb993e..1680fc00 100644
--- a/glow/src/backend.rs
+++ b/glow/src/backend.rs
@@ -24,7 +24,12 @@ pub struct Backend {
impl Backend {
/// Creates a new [`Backend`].
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/settings.rs b/glow/src/settings.rs
index 524d91a9..f3dddfaf 100644
--- a/glow/src/settings.rs
+++ b/glow/src/settings.rs
@@ -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/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 5481216a..a77511e8 100644
--- a/glow/src/widget.rs
+++ b/glow/src/widget.rs
@@ -20,6 +20,7 @@ pub mod rule;
pub mod scrollable;
pub mod slider;
pub mod text_input;
+pub mod toggler;
pub mod tooltip;
#[doc(no_inline)]
@@ -45,6 +46,8 @@ 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")]
diff --git a/glow/src/widget/pane_grid.rs b/glow/src/widget/pane_grid.rs
index 44f9201c..fc36862c 100644
--- a/glow/src/widget/pane_grid.rs
+++ b/glow/src/widget/pane_grid.rs
@@ -6,7 +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.2/examples/pane_grid
+//! [`pane_grid` example]: https://github.com/hecrj/iced/tree/0.3/examples/pane_grid
use crate::Renderer;
pub use iced_graphics::pane_grid::{
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/glutin/Cargo.toml b/glutin/Cargo.toml
index 505ee7e5..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.26"
+[dependencies.glutin]
+version = "0.27"
+git = "https://github.com/iced-rs/glutin"
+rev = "03437d8a1826d83c62017b2bb7bf18bfc9e352cc"
[dependencies.iced_native]
-version = "0.3"
+version = "0.4"
path = "../native"
[dependencies.iced_winit]
-version = "0.2"
+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 addb9228..fcae157e 100644
--- a/glutin/README.md
+++ b/glutin/README.md
@@ -20,7 +20,7 @@ It exposes a renderer-agnostic `Application` trait that can be implemented and t
Add `iced_glutin` as a dependency in your `Cargo.toml`:
```toml
-iced_glutin = "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 3a08104e..991c8705 100644
--- a/glutin/src/application.rs
+++ b/glutin/src/application.rs
@@ -52,11 +52,14 @@ where
runtime.track(subscription);
let context = {
- let builder = settings.window.into_builder(
- &application.title(),
- application.mode(),
- event_loop.primary_monitor(),
- );
+ let builder = settings
+ .window
+ .into_builder(
+ &application.title(),
+ application.mode(),
+ event_loop.primary_monitor(),
+ )
+ .with_menu(Some(conversion::menu(&application.menu())));
let context = ContextBuilder::new()
.with_vsync(true)
@@ -92,10 +95,11 @@ where
application,
compositor,
renderer,
- context,
runtime,
debug,
receiver,
+ context,
+ settings.exit_on_close_request,
));
let mut context = task::Context::from_waker(task::noop_waker_ref());
@@ -107,7 +111,22 @@ where
return;
}
- if let Some(event) = event.to_static() {
+ 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);
@@ -124,10 +143,11 @@ async fn run_instance<A, E, C>(
mut application: A,
mut compositor: C,
mut renderer: A::Renderer,
- context: glutin::ContextWrapper<glutin::PossiblyCurrent, Window>,
mut runtime: Runtime<E, Proxy<A::Message>, A::Message>,
mut debug: Debug,
mut receiver: mpsc::UnboundedReceiver<glutin::event::Event<'_, A::Message>>,
+ context: glutin::ContextWrapper<glutin::PossiblyCurrent, Window>,
+ exit_on_close_request: bool,
) where
A: Application + 'static,
E: Executor + 'static,
@@ -197,6 +217,8 @@ async fn run_instance<A, E, C>(
// Update window
state.synchronize(&application, context.window());
+ let should_exit = application.should_exit();
+
user_interface =
ManuallyDrop::new(application::build_user_interface(
&mut application,
@@ -205,6 +227,10 @@ async fn run_instance<A, E, C>(
state.logical_size(),
&mut debug,
));
+
+ if should_exit {
+ break;
+ }
}
debug.draw_started();
@@ -214,6 +240,16 @@ async fn run_instance<A, E, C>(
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);
}
@@ -271,10 +307,21 @@ async fn run_instance<A, E, C>(
// 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;
}
diff --git a/graphics/Cargo.toml b/graphics/Cargo.toml
index ab41ac44..ea9471c6 100644
--- a/graphics/Cargo.toml
+++ b/graphics/Cargo.toml
@@ -1,6 +1,6 @@
[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"
@@ -28,11 +28,11 @@ version = "1.4"
features = ["derive"]
[dependencies.iced_native]
-version = "0.3"
+version = "0.4"
path = "../native"
[dependencies.iced_style]
-version = "0.2"
+version = "0.3"
path = "../style"
[dependencies.lyon]
diff --git a/graphics/src/overlay/menu.rs b/graphics/src/overlay/menu.rs
index ffe998c5..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;
@@ -45,7 +45,7 @@ where
viewport: &Rectangle,
options: &[T],
hovered_option: Option<usize>,
- padding: u16,
+ padding: Padding,
text_size: u16,
font: Font,
style: &Style,
@@ -53,7 +53,7 @@ where
use std::f32;
let is_mouse_over = bounds.contains(cursor_position);
- let option_height = text_size as usize + padding as usize * 2;
+ let option_height = (text_size + padding.vertical()) as usize;
let mut primitives = Vec::new();
@@ -72,7 +72,7 @@ where
x: bounds.x,
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 {
@@ -88,7 +88,7 @@ where
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/widget.rs b/graphics/src/widget.rs
index 190ea9c0..e34d267f 100644
--- a/graphics/src/widget.rs
+++ b/graphics/src/widget.rs
@@ -20,6 +20,7 @@ pub mod scrollable;
pub mod slider;
pub mod svg;
pub mod text_input;
+pub mod toggler;
pub mod tooltip;
mod column;
@@ -50,6 +51,8 @@ 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;
diff --git a/graphics/src/widget/button.rs b/graphics/src/widget/button.rs
index 2e3f78ca..60400ed8 100644
--- a/graphics/src/widget/button.rs
+++ b/graphics/src/widget/button.rs
@@ -5,7 +5,7 @@ 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;
@@ -21,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>;
diff --git a/graphics/src/widget/canvas/frame.rs b/graphics/src/widget/canvas/frame.rs
index b86f9e04..5af9d11f 100644
--- a/graphics/src/widget/canvas/frame.rs
+++ b/graphics/src/widget/canvas/frame.rs
@@ -54,7 +54,7 @@ impl Frame {
self.size.width
}
- /// Returns the width of the [`Frame`].
+ /// Returns the height of the [`Frame`].
#[inline]
pub fn height(&self) -> f32 {
self.size.height
diff --git a/graphics/src/widget/canvas/program.rs b/graphics/src/widget/canvas/program.rs
index d703caad..85a2f67b 100644
--- a/graphics/src/widget/canvas/program.rs
+++ b/graphics/src/widget/canvas/program.rs
@@ -34,7 +34,7 @@ pub trait Program<Message> {
/// [`Geometry`] can be easily generated with a [`Frame`] or stored in a
/// [`Cache`].
///
- /// [`Frame`]: crate::widget::canvas::Cache
+ /// [`Frame`]: crate::widget::canvas::Frame
/// [`Cache`]: crate::widget::canvas::Cache
fn draw(&self, bounds: Rectangle, cursor: Cursor) -> Vec<Geometry>;
diff --git a/graphics/src/widget/pane_grid.rs b/graphics/src/widget/pane_grid.rs
index d06f8c6c..92cdbb77 100644
--- a/graphics/src/widget/pane_grid.rs
+++ b/graphics/src/widget/pane_grid.rs
@@ -6,7 +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.2/examples/pane_grid
+//! [`pane_grid` example]: https://github.com/hecrj/iced/tree/0.3/examples/pane_grid
use crate::defaults;
use crate::{Backend, Color, Primitive, Renderer};
use iced_native::container;
@@ -218,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
},
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/scrollable.rs b/graphics/src/widget/scrollable.rs
index 57065ba2..2220e4b8 100644
--- a/graphics/src/widget/scrollable.rs
+++ b/graphics/src/widget/scrollable.rs
@@ -134,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/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
index 1a1b5352..493a6389 100644
--- a/graphics/src/widget/tooltip.rs
+++ b/graphics/src/widget/tooltip.rs
@@ -5,7 +5,7 @@ use crate::{Primitive, Renderer, Vector};
use iced_native::container;
use iced_native::layout::{self, Layout};
-use iced_native::{Element, Point, Rectangle, Size, Text};
+use iced_native::{Element, Padding, Point, Rectangle, Size, Text};
/// An element decorating some content.
///
@@ -49,7 +49,6 @@ where
use iced_native::Widget;
let gap = f32::from(gap);
- let padding = f32::from(padding);
let style = style_sheet.style();
let defaults = Defaults {
@@ -62,9 +61,10 @@ where
tooltip,
self,
&layout::Limits::new(Size::ZERO, viewport.size())
- .pad(f32::from(padding)),
+ .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 =
diff --git a/graphics/src/window/compositor.rs b/graphics/src/window/compositor.rs
index 0bc8cbc8..7d5d789b 100644
--- a/graphics/src/window/compositor.rs
+++ b/graphics/src/window/compositor.rs
@@ -17,7 +17,10 @@ pub trait Compositor: Sized {
type SwapChain;
/// Creates a new [`Compositor`].
- fn new(settings: Self::Settings) -> Result<(Self, Self::Renderer), Error>;
+ fn new<W: HasRawWindowHandle>(
+ settings: Self::Settings,
+ compatible_window: Option<&W>,
+ ) -> Result<(Self, Self::Renderer), Error>;
/// Crates a new [`Surface`] for the given window.
///
diff --git a/native/Cargo.toml b/native/Cargo.toml
index 2c99638a..a3134ef4 100644
--- a/native/Cargo.toml
+++ b/native/Cargo.toml
@@ -1,6 +1,6 @@
[package]
name = "iced_native"
-version = "0.3.0"
+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.3"
+version = "0.4"
path = "../core"
[dependencies.iced_futures]
-version = "0.2"
+version = "0.3"
path = "../futures"
features = ["thread-pool"]
diff --git a/native/README.md b/native/README.md
index 6323dd4f..0d79690a 100644
--- a/native/README.md
+++ b/native/README.md
@@ -28,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.3"
+iced_native = "0.4"
```
__Iced moves fast and the `master` branch can contain breaking changes!__ If
diff --git a/native/src/event.rs b/native/src/event.rs
index 205bb797..1c26b5f2 100644
--- a/native/src/event.rs
+++ b/native/src/event.rs
@@ -23,6 +23,27 @@ pub enum 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.
diff --git a/native/src/layout/flex.rs b/native/src/layout/flex.rs
index 4f6523fb..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.
@@ -62,7 +63,7 @@ pub fn resolve<Message, Renderer>(
axis: Axis,
renderer: &Renderer,
limits: &Limits,
- padding: f32,
+ padding: Padding,
spacing: f32,
align_items: Align,
items: &[Element<'_, Message, Renderer>],
@@ -141,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));
@@ -166,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 a7bb5c9c..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)]
@@ -117,8 +117,11 @@ impl Limits {
}
/// Shrinks the current [`Limits`] to account for the given padding.
- 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`].
diff --git a/native/src/lib.rs b/native/src/lib.rs
index 20bbb1d0..cbb02506 100644
--- a/native/src/lib.rs
+++ b/native/src/lib.rs
@@ -61,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/overlay/menu.rs b/native/src/overlay/menu.rs
index afb17bd3..f62dcb46 100644
--- a/native/src/overlay/menu.rs
+++ b/native/src/overlay/menu.rs
@@ -8,8 +8,8 @@ use crate::scrollable;
use crate::text;
use crate::touch;
use crate::{
- Clipboard, Container, Element, 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.
@@ -20,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,
@@ -45,7 +45,7 @@ where
hovered_option,
last_selection,
width: 0,
- padding: 0,
+ padding: Padding::ZERO,
text_size: None,
font: Default::default(),
style: Default::default(),
@@ -58,9 +58,9 @@ where
self
}
- /// Sets the padding of the [`Menu`].
- 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
}
@@ -261,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,
@@ -294,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,
);
@@ -345,7 +345,7 @@ where
*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,
);
}
@@ -359,7 +359,7 @@ where
*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,
);
@@ -430,7 +430,7 @@ pub trait Renderer:
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 066c29d8..75fab094 100644
--- a/native/src/program.rs
+++ b/native/src/program.rs
@@ -11,7 +11,7 @@ pub trait Program: Sized {
type Renderer: Renderer;
/// The type of __messages__ your [`Program`] will produce.
- type Message: std::fmt::Debug + Send;
+ type Message: std::fmt::Debug + Clone + Send;
/// The type of [`Clipboard`] your [`Program`] will use.
type Clipboard: Clipboard;
diff --git a/native/src/renderer/null.rs b/native/src/renderer/null.rs
index 9e91d29f..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,
};
@@ -145,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 = ();
@@ -288,3 +288,19 @@ impl pane_grid::Renderer for Null {
) {
}
}
+
+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/user_interface.rs b/native/src/user_interface.rs
index 475faf8d..8e0d7d1c 100644
--- a/native/src/user_interface.rs
+++ b/native/src/user_interface.rs
@@ -16,7 +16,7 @@ use std::hash::Hasher;
/// The [`integration` example] uses a [`UserInterface`] to integrate Iced in
/// an existing graphical application.
///
-/// [`integration` example]: https://github.com/hecrj/iced/tree/0.2/examples/integration
+/// [`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>,
diff --git a/native/src/widget.rs b/native/src/widget.rs
index 791c53a3..43c1b023 100644
--- a/native/src/widget.rs
+++ b/native/src/widget.rs
@@ -36,6 +36,7 @@ pub mod space;
pub mod svg;
pub mod text;
pub mod text_input;
+pub mod toggler;
pub mod tooltip;
#[doc(no_inline)]
@@ -73,6 +74,8 @@ 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::event::{self, Event};
@@ -96,12 +99,12 @@ use crate::{Clipboard, Hasher, Layout, Length, Point, Rectangle};
/// - [`geometry`], a custom widget showcasing how to draw geometry with the
/// `Mesh2D` primitive in [`iced_wgpu`].
///
-/// [examples]: https://github.com/hecrj/iced/tree/0.2/examples
-/// [`bezier_tool`]: https://github.com/hecrj/iced/tree/0.2/examples/bezier_tool
-/// [`custom_widget`]: https://github.com/hecrj/iced/tree/0.2/examples/custom_widget
-/// [`geometry`]: https://github.com/hecrj/iced/tree/0.2/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.2/wgpu
+/// [`iced_wgpu`]: https://github.com/hecrj/iced/tree/0.3/wgpu
pub trait Widget<Message, Renderer>
where
Renderer: crate::Renderer,
diff --git a/native/src/widget/button.rs b/native/src/widget/button.rs
index 59d6e219..c469a0e5 100644
--- a/native/src/widget/button.rs
+++ b/native/src/widget/button.rs
@@ -4,9 +4,11 @@
use crate::event::{self, Event};
use crate::layout;
use crate::mouse;
+use crate::overlay;
use crate::touch;
use crate::{
- Clipboard, Element, Hasher, Layout, Length, Point, Rectangle, Widget,
+ Clipboard, Element, Hasher, Layout, Length, Padding, Point, Rectangle,
+ Widget,
};
use std::hash::Hash;
@@ -27,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,
@@ -36,7 +61,7 @@ pub struct Button<'a, Message, Renderer: self::Renderer> {
height: Length,
min_width: u32,
min_height: u32,
- padding: u16,
+ padding: Padding,
style: Renderer::Style,
}
@@ -88,13 +113,14 @@ where
self
}
- /// Sets the padding of the [`Button`].
- 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.
+ /// 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
@@ -139,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])
}
@@ -240,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`].
@@ -250,7 +285,7 @@ where
/// [renderer]: crate::renderer
pub trait Renderer: crate::Renderer + Sized {
/// The default padding of a [`Button`].
- const DEFAULT_PADDING: u16;
+ const DEFAULT_PADDING: Padding;
/// The style supported by this renderer.
type Style: Default;
diff --git a/native/src/widget/checkbox.rs b/native/src/widget/checkbox.rs
index 6ce2e973..0f21c873 100644
--- a/native/src/widget/checkbox.rs
+++ b/native/src/widget/checkbox.rs
@@ -8,8 +8,8 @@ use crate::row;
use crate::text;
use crate::touch;
use crate::{
- Align, Clipboard, Element, 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.
@@ -39,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,
}
@@ -66,6 +67,7 @@ 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(),
}
}
@@ -102,6 +104,12 @@ impl<Message, Renderer: self::Renderer + text::Renderer>
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`].
pub fn style(mut self, style: impl Into<Renderer::Style>) -> Self {
self.style = style.into();
@@ -193,7 +201,7 @@ where
&self.label,
self.text_size.unwrap_or(renderer.default_size()),
self.font,
- None,
+ self.text_color,
HorizontalAlignment::Left,
VerticalAlignment::Center,
);
diff --git a/native/src/widget/column.rs b/native/src/widget/column.rs
index d7f0365a..52a2e80c 100644
--- a/native/src/widget/column.rs
+++ b/native/src/widget/column.rs
@@ -5,7 +5,8 @@ use crate::event::{self, Event};
use crate::layout;
use crate::overlay;
use crate::{
- Align, Clipboard, Element, Hasher, Layout, Length, Point, Rectangle, Widget,
+ Align, Clipboard, Element, Hasher, Layout, Length, Padding, Point,
+ Rectangle, Widget,
};
use std::u32;
@@ -14,7 +15,7 @@ use std::u32;
#[allow(missing_debug_implementations)]
pub struct Column<'a, Message, Renderer> {
spacing: u16,
- padding: u16,
+ padding: Padding,
width: Length,
height: Length,
max_width: u32,
@@ -35,7 +36,7 @@ impl<'a, Message, Renderer> Column<'a, Message, Renderer> {
) -> Self {
Column {
spacing: 0,
- padding: 0,
+ padding: Padding::ZERO,
width: Length::Shrink,
height: Length::Shrink,
max_width: u32::MAX,
@@ -55,9 +56,9 @@ impl<'a, Message, Renderer> Column<'a, Message, Renderer> {
self
}
- /// Sets the padding of the [`Column`].
- 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
}
@@ -129,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,
diff --git a/native/src/widget/container.rs b/native/src/widget/container.rs
index 69fe699b..69aee64d 100644
--- a/native/src/widget/container.rs
+++ b/native/src/widget/container.rs
@@ -5,7 +5,8 @@ use crate::event::{self, Event};
use crate::layout;
use crate::overlay;
use crate::{
- Align, Clipboard, Element, Hasher, Layout, Length, Point, Rectangle, Widget,
+ Align, Clipboard, Element, Hasher, Layout, Length, Padding, Point,
+ Rectangle, Widget,
};
use std::u32;
@@ -15,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,
@@ -36,7 +37,7 @@ where
T: Into<Element<'a, Message, Renderer>>,
{
Container {
- padding: 0,
+ padding: Padding::ZERO,
width: Length::Shrink,
height: Length::Shrink,
max_width: u32::MAX,
@@ -48,9 +49,9 @@ where
}
}
- /// Sets the padding of the [`Container`].
- 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
}
@@ -127,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(
diff --git a/native/src/widget/image/viewer.rs b/native/src/widget/image/viewer.rs
index a006c0af..405daf00 100644
--- a/native/src/widget/image/viewer.rs
+++ b/native/src/widget/image/viewer.rs
@@ -132,19 +132,30 @@ where
) -> layout::Node {
let (width, height) = renderer.dimensions(&self.handle);
- let aspect_ratio = width as f32 / height as f32;
-
let mut size = limits
.width(self.width)
.height(self.height)
.resolve(Size::new(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;
+ let expansion_size = if height > width {
+ self.width
} else {
- size.height = height as f32 * size.width / width as f32;
+ 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)
diff --git a/native/src/widget/pane_grid.rs b/native/src/widget/pane_grid.rs
index 44028f5e..b72172cc 100644
--- a/native/src/widget/pane_grid.rs
+++ b/native/src/widget/pane_grid.rs
@@ -6,7 +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.2/examples/pane_grid
+//! [`pane_grid` example]: https://github.com/hecrj/iced/tree/0.3/examples/pane_grid
mod axis;
mod configuration;
mod content;
diff --git a/native/src/widget/pane_grid/content.rs b/native/src/widget/pane_grid/content.rs
index f028ec25..b0110393 100644
--- a/native/src/widget/pane_grid/content.rs
+++ b/native/src/widget/pane_grid/content.rs
@@ -193,18 +193,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/title_bar.rs b/native/src/widget/pane_grid/title_bar.rs
index a1e5107e..070010f8 100644
--- a/native/src/widget/pane_grid/title_bar.rs
+++ b/native/src/widget/pane_grid/title_bar.rs
@@ -1,8 +1,11 @@
use crate::container;
use crate::event::{self, Event};
use crate::layout;
+use crate::overlay;
use crate::pane_grid;
-use crate::{Clipboard, Element, Hasher, Layout, Point, Rectangle, Size};
+use crate::{
+ Clipboard, Element, Hasher, Layout, Padding, Point, Rectangle, Size,
+};
/// The title bar of a [`Pane`].
///
@@ -11,7 +14,7 @@ use crate::{Clipboard, Element, Hasher, Layout, Point, Rectangle, Size};
pub struct TitleBar<'a, Message, Renderer: pane_grid::Renderer> {
content: Element<'a, Message, Renderer>,
controls: Option<Element<'a, Message, Renderer>>,
- padding: u16,
+ padding: Padding,
always_show_controls: bool,
style: <Renderer as container::Renderer>::Style,
}
@@ -28,7 +31,7 @@ where
Self {
content: content.into(),
controls: None,
- padding: 0,
+ padding: Padding::ZERO,
always_show_controls: false,
style: Default::default(),
}
@@ -43,9 +46,9 @@ where
self
}
- /// Sets the padding of the [`TitleBar`].
- 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
}
@@ -129,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
@@ -160,8 +164,7 @@ 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_layout = self
@@ -191,9 +194,12 @@ where
)
};
- 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(
@@ -205,16 +211,17 @@ where
clipboard: &mut dyn Clipboard,
messages: &mut Vec<Message>,
) -> event::Status {
- if let Some(controls) = &mut self.controls {
- let mut children = layout.children();
- let padded = children.next().unwrap();
+ 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,
renderer,
@@ -223,6 +230,40 @@ where
)
} 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 046d5779..4f4e751e 100644
--- a/native/src/widget/pick_list.rs
+++ b/native/src/widget/pick_list.rs
@@ -8,7 +8,8 @@ use crate::scrollable;
use crate::text;
use crate::touch;
use crate::{
- Clipboard, Element, Hasher, Layout, Length, Point, Rectangle, Size, Widget,
+ Clipboard, Element, Hasher, Layout, Length, Padding, Point, Rectangle,
+ Size, Widget,
};
use std::borrow::Cow;
@@ -24,9 +25,10 @@ where
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,
@@ -81,6 +83,7 @@ where
last_selection,
on_selected: Box::new(on_selected),
options: options.into(),
+ placeholder: None,
selected,
width: Length::Shrink,
text_size: None,
@@ -90,15 +93,21 @@ 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`].
pub fn width(mut self, width: Length) -> Self {
self.width = width;
self
}
- /// Sets the padding of the [`PickList`].
- 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
}
@@ -150,27 +159,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,
};
@@ -179,11 +198,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)
@@ -194,6 +213,8 @@ where
match self.width {
Length::Shrink => {
+ self.placeholder.hash(state);
+
self.options
.iter()
.map(ToString::to_string)
@@ -264,6 +285,7 @@ where
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,
@@ -308,7 +330,7 @@ where
/// [renderer]: crate::renderer
pub trait Renderer: text::Renderer + menu::Renderer {
/// The default padding of a [`PickList`].
- const DEFAULT_PADDING: u16;
+ const DEFAULT_PADDING: Padding;
/// The [`PickList`] style supported by this renderer.
type Style: Default;
@@ -324,7 +346,8 @@ pub trait Renderer: text::Renderer + menu::Renderer {
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/radio.rs b/native/src/widget/radio.rs
index 9482a9b1..dee82d1f 100644
--- a/native/src/widget/radio.rs
+++ b/native/src/widget/radio.rs
@@ -1,17 +1,17 @@
//! Create choices using radio buttons.
+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, Color};
use crate::{
Align, Clipboard, Element, Hasher, HorizontalAlignment, Layout, Length,
Point, Rectangle, Row, Text, VerticalAlignment, Widget,
};
-use std::hash::Hash;
-
/// A circular button representing a choice.
///
/// # Example
@@ -47,6 +47,8 @@ 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,
}
@@ -81,6 +83,8 @@ where
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(),
}
}
@@ -109,6 +113,18 @@ where
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.
pub fn style(mut self, style: impl Into<Renderer::Style>) -> Self {
self.style = style.into();
@@ -196,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,
);
diff --git a/native/src/widget/row.rs b/native/src/widget/row.rs
index 5634ab12..9ebc9145 100644
--- a/native/src/widget/row.rs
+++ b/native/src/widget/row.rs
@@ -3,7 +3,8 @@ use crate::event::{self, Event};
use crate::layout;
use crate::overlay;
use crate::{
- Align, Clipboard, Element, Hasher, Layout, Length, Point, Rectangle, Widget,
+ Align, Clipboard, Element, Hasher, Layout, Length, Padding, Point,
+ Rectangle, Widget,
};
use std::hash::Hash;
@@ -13,7 +14,7 @@ use std::u32;
#[allow(missing_debug_implementations)]
pub struct Row<'a, Message, Renderer> {
spacing: u16,
- padding: u16,
+ padding: Padding,
width: Length,
height: Length,
max_width: u32,
@@ -34,7 +35,7 @@ impl<'a, Message, Renderer> Row<'a, Message, Renderer> {
) -> Self {
Row {
spacing: 0,
- padding: 0,
+ padding: Padding::ZERO,
width: Length::Shrink,
height: Length::Shrink,
max_width: u32::MAX,
@@ -54,9 +55,9 @@ impl<'a, Message, Renderer> Row<'a, Message, Renderer> {
self
}
- /// Sets the padding of the [`Row`].
- 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
}
@@ -128,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,
diff --git a/native/src/widget/scrollable.rs b/native/src/widget/scrollable.rs
index 70ebebe2..68da2e67 100644
--- a/native/src/widget/scrollable.rs
+++ b/native/src/widget/scrollable.rs
@@ -6,7 +6,7 @@ use crate::mouse;
use crate::overlay;
use crate::touch;
use crate::{
- Align, Clipboard, Column, Element, Hasher, Layout, Length, Point,
+ Align, Clipboard, Column, Element, Hasher, Layout, Length, Padding, Point,
Rectangle, Size, Vector, Widget,
};
@@ -23,6 +23,7 @@ pub struct Scrollable<'a, Message, Renderer: self::Renderer> {
scrollbar_margin: u16,
scroller_width: u16,
content: Column<'a, Message, Renderer>,
+ on_scroll: Option<Box<dyn Fn(f32) -> Message>>,
style: Renderer::Style,
}
@@ -37,6 +38,7 @@ impl<'a, Message, Renderer: self::Renderer> Scrollable<'a, Message, Renderer> {
scrollbar_margin: 0,
scroller_width: 10,
content: Column::new(),
+ on_scroll: None,
style: Renderer::Style::default(),
}
}
@@ -51,9 +53,9 @@ impl<'a, Message, Renderer: self::Renderer> Scrollable<'a, Message, Renderer> {
self
}
- /// Sets the padding of the [`Scrollable`].
- 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
}
@@ -101,12 +103,22 @@ impl<'a, Message, Renderer: self::Renderer> Scrollable<'a, Message, Renderer> {
}
/// Sets the scroller width of the [`Scrollable`] .
- /// Silently enforces a minimum value of 1.
+ ///
+ /// 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();
@@ -121,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>
@@ -228,6 +258,8 @@ where
}
}
+ self.notify_on_scroll(bounds, content_bounds, messages);
+
return event::Status::Captured;
}
Event::Touch(event) => {
@@ -251,6 +283,12 @@ where
self.state.scroll_box_touched_at =
Some(cursor_position);
+
+ self.notify_on_scroll(
+ bounds,
+ content_bounds,
+ messages,
+ );
}
}
touch::Event::FingerLifted { .. }
@@ -290,6 +328,8 @@ where
content_bounds,
);
+ self.notify_on_scroll(bounds, content_bounds, messages);
+
return event::Status::Captured;
}
}
@@ -317,6 +357,12 @@ where
self.state.scroller_grabbed_at =
Some(scroller_grabbed_at);
+ self.notify_on_scroll(
+ bounds,
+ content_bounds,
+ messages,
+ );
+
return event::Status::Captured;
}
}
@@ -418,11 +464,44 @@ where
}
/// The local state of a [`Scrollable`].
-#[derive(Debug, Clone, Copy, Default)]
+#[derive(Debug, Clone, Copy)]
pub struct State {
scroller_grabbed_at: Option<f32>,
scroll_box_touched_at: Option<Point>,
- offset: f32,
+ 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 {
@@ -443,13 +522,14 @@ 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.
@@ -459,17 +539,29 @@ impl State {
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.
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.
diff --git a/native/src/widget/text_input.rs b/native/src/widget/text_input.rs
index 2a0913f3..cec1e485 100644
--- a/native/src/widget/text_input.rs
+++ b/native/src/widget/text_input.rs
@@ -18,7 +18,8 @@ use crate::mouse::{self, click};
use crate::text;
use crate::touch;
use crate::{
- Clipboard, Element, Hasher, Layout, Length, Point, Rectangle, Size, Widget,
+ Clipboard, Element, Hasher, Layout, Length, Padding, Point, Rectangle,
+ Size, Widget,
};
use std::u32;
@@ -56,7 +57,7 @@ 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>,
@@ -92,7 +93,7 @@ where
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,
@@ -126,9 +127,9 @@ where
self
}
- /// Sets the padding of the [`TextInput`].
- 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
}
@@ -223,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(
@@ -359,7 +362,7 @@ where
Event::Keyboard(keyboard::Event::CharacterReceived(c))
if self.state.is_focused
&& self.state.is_pasting.is_none()
- && !self.state.keyboard_modifiers.is_command_pressed()
+ && !self.state.keyboard_modifiers.command()
&& !c.is_control() =>
{
let mut editor =
@@ -447,7 +450,7 @@ where
if platform::is_jump_modifier_pressed(modifiers)
&& !self.is_secure
{
- if modifiers.shift {
+ if modifiers.shift() {
self.state
.cursor
.select_left_by_words(&self.value);
@@ -456,7 +459,7 @@ where
.cursor
.move_left_by_words(&self.value);
}
- } else if modifiers.shift {
+ } else if modifiers.shift() {
self.state.cursor.select_left(&self.value)
} else {
self.state.cursor.move_left(&self.value);
@@ -466,7 +469,7 @@ where
if platform::is_jump_modifier_pressed(modifiers)
&& !self.is_secure
{
- if modifiers.shift {
+ if modifiers.shift() {
self.state
.cursor
.select_right_by_words(&self.value);
@@ -475,14 +478,14 @@ where
.cursor
.move_right_by_words(&self.value);
}
- } else if modifiers.shift {
+ } 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 {
+ if modifiers.shift() {
self.state.cursor.select_range(
self.state.cursor.start(&self.value),
0,
@@ -492,7 +495,7 @@ where
}
}
keyboard::KeyCode::End => {
- if modifiers.shift {
+ if modifiers.shift() {
self.state.cursor.select_range(
self.state.cursor.start(&self.value),
self.value.len(),
@@ -502,10 +505,7 @@ where
}
}
keyboard::KeyCode::C
- if self
- .state
- .keyboard_modifiers
- .is_command_pressed() =>
+ if self.state.keyboard_modifiers.command() =>
{
match self.state.cursor.selection(&self.value) {
Some((start, end)) => {
@@ -517,10 +517,7 @@ where
}
}
keyboard::KeyCode::X
- if self
- .state
- .keyboard_modifiers
- .is_command_pressed() =>
+ if self.state.keyboard_modifiers.command() =>
{
match self.state.cursor.selection(&self.value) {
Some((start, end)) => {
@@ -542,7 +539,7 @@ where
messages.push(message);
}
keyboard::KeyCode::V => {
- if self.state.keyboard_modifiers.is_command_pressed() {
+ if self.state.keyboard_modifiers.command() {
let content = match self.state.is_pasting.take() {
Some(content) => content,
None => {
@@ -573,10 +570,7 @@ where
}
}
keyboard::KeyCode::A
- if self
- .state
- .keyboard_modifiers
- .is_command_pressed() =>
+ if self.state.keyboard_modifiers.command() =>
{
self.state.cursor.select_all(&self.value);
}
@@ -860,9 +854,9 @@ mod platform {
pub fn is_jump_modifier_pressed(modifiers: keyboard::Modifiers) -> bool {
if cfg!(target_os = "macos") {
- modifiers.alt
+ 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 1e7aee83..4f3b159b 100644
--- a/native/src/widget/text_input/cursor.rs
+++ b/native/src/widget/text_input/cursor.rs
@@ -113,7 +113,7 @@ impl Cursor {
State::Selection { start, end } if end > 0 => {
self.select_range(start, end - 1)
}
- _ => (),
+ _ => {}
}
}
@@ -125,7 +125,7 @@ impl Cursor {
State::Selection { start, end } if end < value.len() => {
self.select_range(start, end + 1)
}
- _ => (),
+ _ => {}
}
}
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/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/window/event.rs b/native/src/window/event.rs
index fc746781..3aa1ab0b 100644
--- a/native/src/window/event.rs
+++ b/native/src/window/event.rs
@@ -12,6 +12,12 @@ 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,
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 4c6d5b7f..78280e98 100644
--- a/src/application.rs
+++ b/src/application.rs
@@ -1,6 +1,6 @@
use crate::window;
use crate::{
- Clipboard, Color, Command, Element, Executor, Settings, Subscription,
+ Clipboard, Color, Command, Element, Executor, Menu, Settings, Subscription,
};
/// An interactive cross-platform application.
@@ -39,15 +39,15 @@ use crate::{
/// 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.2/examples
-/// [`clock`]: https://github.com/hecrj/iced/tree/0.2/examples/clock
-/// [`download_progress`]: https://github.com/hecrj/iced/tree/0.2/examples/download_progress
-/// [`events`]: https://github.com/hecrj/iced/tree/0.2/examples/events
-/// [`game_of_life`]: https://github.com/hecrj/iced/tree/0.2/examples/game_of_life
-/// [`pokedex`]: https://github.com/hecrj/iced/tree/0.2/examples/pokedex
-/// [`solar_system`]: https://github.com/hecrj/iced/tree/0.2/examples/solar_system
-/// [`stopwatch`]: https://github.com/hecrj/iced/tree/0.2/examples/stopwatch
-/// [`todos`]: https://github.com/hecrj/iced/tree/0.2/examples/todos
+/// [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/
@@ -99,7 +99,7 @@ pub trait Application: Sized {
type Executor: Executor;
/// The type of __messages__ your [`Application`] will produce.
- type Message: std::fmt::Debug + Send;
+ type Message: std::fmt::Debug + Clone + Send;
/// The data needed to initialize your [`Application`].
type Flags;
@@ -184,6 +184,20 @@ pub trait Application: Sized {
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
@@ -201,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::<
@@ -270,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,
}
}
@@ -284,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")]
diff --git a/src/lib.rs b/src/lib.rs
index 812a50c1..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.2/examples
+//! [examples]: https://github.com/hecrj/iced/tree/0.3/examples
//! [repository]: https://github.com/hecrj/iced
//!
//! # Overview
@@ -245,7 +245,7 @@ pub use sandbox::Sandbox;
pub use settings::Settings;
pub use runtime::{
- futures, Align, Background, Clipboard, 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/sandbox.rs b/src/sandbox.rs
index 9dd17b7e..cb3cf624 100644
--- a/src/sandbox.rs
+++ b/src/sandbox.rs
@@ -36,19 +36,19 @@ 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.2/examples
-/// [`bezier_tool`]: https://github.com/hecrj/iced/tree/0.2/examples/bezier_tool
-/// [`counter`]: https://github.com/hecrj/iced/tree/0.2/examples/counter
-/// [`custom_widget`]: https://github.com/hecrj/iced/tree/0.2/examples/custom_widget
-/// [`geometry`]: https://github.com/hecrj/iced/tree/0.2/examples/geometry
-/// [`pane_grid`]: https://github.com/hecrj/iced/tree/0.2/examples/pane_grid
-/// [`progress_bar`]: https://github.com/hecrj/iced/tree/0.2/examples/progress_bar
-/// [`styling`]: https://github.com/hecrj/iced/tree/0.2/examples/styling
-/// [`svg`]: https://github.com/hecrj/iced/tree/0.2/examples/svg
-/// [`tour`]: https://github.com/hecrj/iced/tree/0.2/examples/tour
+/// [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.2/wgpu
+/// [`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
///
@@ -88,7 +88,7 @@ use crate::{
/// ```
pub trait Sandbox {
/// The type of __messages__ your [`Sandbox`] will produce.
- type Message: std::fmt::Debug + Send;
+ type Message: std::fmt::Debug + Clone + Send;
/// Initializes the [`Sandbox`].
///
diff --git a/src/settings.rs b/src/settings.rs
index c82a1354..480bf813 100644
--- a/src/settings.rs
+++ b/src/settings.rs
@@ -25,6 +25,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,6 +41,12 @@ pub struct Settings<Flags> {
///
/// [`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> {
@@ -46,10 +58,12 @@ impl<Flags> Settings<Flags> {
Self {
flags,
- antialiasing: default_settings.antialiasing,
+ 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,
}
}
}
@@ -61,10 +75,12 @@ where
fn default() -> Self {
Self {
flags: Default::default(),
- antialiasing: Default::default(),
+ window: Default::default(),
default_font: Default::default(),
default_text_size: 20,
- window: Default::default(),
+ text_multithreading: false,
+ antialiasing: false,
+ exit_on_close_request: true,
}
}
}
@@ -75,6 +91,7 @@ impl<Flags> From<Settings<Flags>> for iced_winit::Settings<Flags> {
iced_winit::Settings {
window: settings.window.into(),
flags: settings.flags,
+ exit_on_close_request: settings.exit_on_close_request,
}
}
}
diff --git a/src/widget.rs b/src/widget.rs
index eac50d57..db052106 100644
--- a/src/widget.rs
+++ b/src/widget.rs
@@ -17,8 +17,8 @@
mod platform {
pub use crate::renderer::widget::{
button, checkbox, container, pane_grid, pick_list, progress_bar, radio,
- rule, scrollable, slider, text_input, tooltip, Column, Row, Space,
- Text,
+ rule, scrollable, slider, text_input, toggler, tooltip, Column, Row,
+ Space, Text,
};
#[cfg(any(feature = "canvas", feature = "glow_canvas"))]
@@ -53,7 +53,7 @@ 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, tooltip::Tooltip,
+ svg::Svg, text_input::TextInput, toggler::Toggler, tooltip::Tooltip,
};
#[cfg(any(feature = "canvas", feature = "glow_canvas"))]
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 0d27b00e..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{:?}).",
- pixel_count, width, height,
- )
+ 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 6b5d2985..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)>,
@@ -32,6 +35,7 @@ impl Default for Settings {
fn default() -> Settings {
Settings {
size: (1024, 768),
+ position: Position::default(),
min_size: None,
max_size: None,
resizable: true,
@@ -48,6 +52,7 @@ 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,
diff --git a/style/Cargo.toml b/style/Cargo.toml
index ac16f8ee..a3086477 100644
--- a/style/Cargo.toml
+++ b/style/Cargo.toml
@@ -1,6 +1,6 @@
[package]
name = "iced_style"
-version = "0.2.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.3", path = "../core" }
+[dependencies.iced_core]
+version = "0.4"
+path = "../core"
diff --git a/style/src/lib.rs b/style/src/lib.rs
index f09b5f9d..08d9f044 100644
--- a/style/src/lib.rs
+++ b/style/src/lib.rs
@@ -18,3 +18,4 @@ pub mod rule;
pub mod scrollable;
pub mod slider;
pub mod text_input;
+pub mod toggler;
diff --git a/style/src/pick_list.rs b/style/src/pick_list.rs
index a757ba98..d1801e5f 100644
--- a/style/src/pick_list.rs
+++ b/style/src/pick_list.rs
@@ -5,6 +5,7 @@ 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: f32,
pub border_width: f32,
@@ -16,6 +17,7 @@ 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.0,
border_width: 1.0,
diff --git a/style/src/rule.rs b/style/src/rule.rs
index 5021340b..be4c86d1 100644
--- a/style/src/rule.rs
+++ b/style/src/rule.rs
@@ -79,6 +79,17 @@ pub struct Style {
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.
@@ -89,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.0,
- fill_mode: FillMode::Percent(90.0),
- }
+ Style::default()
}
}
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 e063a021..06573b95 100644
--- a/web/Cargo.toml
+++ b/web/Cargo.toml
@@ -1,6 +1,6 @@
[package]
name = "iced_web"
-version = "0.3.0"
+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.3"
+version = "0.4"
path = "../core"
[dependencies.iced_futures]
-version = "0.2"
+version = "0.3"
path = "../futures"
[dependencies.iced_style]
-version = "0.2"
+version = "0.3"
path = "../style"
[dependencies.web-sys]
diff --git a/web/README.md b/web/README.md
index 0e770589..58ad8235 100644
--- a/web/README.md
+++ b/web/README.md
@@ -16,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.3"
+iced_web = "0.4"
```
__Iced moves fast and the `master` branch can contain breaking changes!__ If
diff --git a/web/src/css.rs b/web/src/css.rs
index bdde23f3..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,11 +12,11 @@ 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 {
@@ -25,8 +25,8 @@ impl Rule {
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),
}
}
@@ -45,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 }} \
@@ -66,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(),
}
}
}
@@ -170,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/lib.rs b/web/src/lib.rs
index 4c65dfa3..6b7d0115 100644
--- a/web/src/lib.rs
+++ b/web/src/lib.rs
@@ -49,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.2/examples/tour
+//! [`tour` example]: https://github.com/hecrj/iced/tree/0.3/examples/tour
#![deny(missing_docs)]
#![deny(missing_debug_implementations)]
#![deny(unused_results)]
@@ -74,8 +74,8 @@ 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;
diff --git a/web/src/widget.rs b/web/src/widget.rs
index 023f5f13..4cb0a9cc 100644
--- a/web/src/widget.rs
+++ b/web/src/widget.rs
@@ -24,6 +24,7 @@ pub mod radio;
pub mod scrollable;
pub mod slider;
pub mod text_input;
+pub mod toggler;
mod column;
mod row;
@@ -40,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;
diff --git a/web/src/widget/button.rs b/web/src/widget/button.rs
index 7c389a9f..cd450b55 100644
--- a/web/src/widget/button.rs
+++ b/web/src/widget/button.rs
@@ -1,7 +1,7 @@
//! Allow your users to perform actions by pressing a button.
//!
//! A [`Button`] has some local [`State`].
-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};
@@ -20,6 +20,26 @@ 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>,
@@ -30,7 +50,7 @@ pub struct Button<'a, Message> {
min_width: u32,
#[allow(dead_code)]
min_height: u32,
- padding: u16,
+ padding: Padding,
style: Box<dyn StyleSheet>,
}
@@ -48,7 +68,7 @@ impl<'a, Message> Button<'a, Message> {
height: Length::Shrink,
min_width: 0,
min_height: 0,
- padding: 5,
+ padding: Padding::new(5),
style: Default::default(),
}
}
@@ -77,9 +97,9 @@ impl<'a, Message> Button<'a, Message> {
self
}
- /// Sets the padding of the [`Button`].
- 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
}
@@ -90,6 +110,7 @@ impl<'a, Message> Button<'a, Message> {
}
/// Sets the message that will be produced when the [`Button`] is pressed.
+ /// 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
@@ -122,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 {
@@ -132,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(),
)
@@ -162,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/column.rs b/web/src/widget/column.rs
index d832fdcb..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;
@@ -9,7 +9,7 @@ use std::u32;
#[allow(missing_debug_implementations)]
pub struct Column<'a, Message> {
spacing: u16,
- padding: u16,
+ padding: Padding,
width: Length,
height: Length,
max_width: u32,
@@ -28,7 +28,7 @@ impl<'a, Message> Column<'a, Message> {
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,
@@ -48,9 +48,9 @@ impl<'a, Message> Column<'a, Message> {
self
}
- /// Sets the padding of the [`Column`].
- 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
}
@@ -114,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 8de3cc41..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,7 +8,7 @@ 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,
@@ -29,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,
@@ -41,9 +41,9 @@ impl<'a, Message> Container<'a, Message> {
}
}
- /// Sets the padding of the [`Container`].
- 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
}
@@ -106,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/row.rs b/web/src/widget/row.rs
index f00a544a..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;
@@ -9,7 +9,7 @@ use std::u32;
#[allow(missing_debug_implementations)]
pub struct Row<'a, Message> {
spacing: u16,
- padding: u16,
+ padding: Padding,
width: Length,
height: Length,
max_width: u32,
@@ -28,7 +28,7 @@ impl<'a, Message> Row<'a, Message> {
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,
@@ -48,9 +48,9 @@ impl<'a, Message> Row<'a, Message> {
self
}
- /// Sets the padding of the [`Row`].
- 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
}
@@ -114,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 bd9260ff..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};
@@ -39,9 +41,9 @@ impl<'a, Message> Scrollable<'a, Message> {
self
}
- /// Sets the padding of the [`Scrollable`].
- 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
}
diff --git a/web/src/widget/text_input.rs b/web/src/widget/text_input.rs
index d61ee2fd..e4877f2a 100644
--- a/web/src/widget/text_input.rs
+++ b/web/src/widget/text_input.rs
@@ -1,7 +1,7 @@
//! Display fields that can be filled with text.
//!
//! A [`TextInput`] has some local [`State`].
-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};
@@ -35,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>,
@@ -66,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,
@@ -92,9 +92,9 @@ impl<'a, Message> TextInput<'a, Message> {
self
}
- /// Sets the padding of the [`TextInput`].
- 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
}
@@ -126,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;
@@ -159,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,
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 aa39f068..9675797d 100644
--- a/wgpu/Cargo.toml
+++ b/wgpu/Cargo.toml
@@ -1,6 +1,6 @@
[package]
name = "iced_wgpu"
-version = "0.3.0"
+version = "0.4.0"
authors = ["Héctor Ramón Jiménez <hector0193@gmail.com>"]
edition = "2018"
description = "A wgpu renderer for Iced"
@@ -26,8 +26,8 @@ qr_code = ["iced_graphics/qr_code"]
default_system_font = ["iced_graphics/font-source"]
[dependencies]
-wgpu = "0.7"
-wgpu_glyph = "0.11"
+wgpu = "0.9"
+wgpu_glyph = "0.13"
glyph_brush = "0.7"
raw-window-handle = "0.3"
log = "0.4"
@@ -39,11 +39,11 @@ version = "1.4"
features = ["derive"]
[dependencies.iced_native]
-version = "0.3"
+version = "0.4"
path = "../native"
[dependencies.iced_graphics]
-version = "0.1"
+version = "0.2"
path = "../graphics"
features = ["font-fallback", "font-icons"]
diff --git a/wgpu/README.md b/wgpu/README.md
index e8cb0a43..a9d95ea7 100644
--- a/wgpu/README.md
+++ b/wgpu/README.md
@@ -29,7 +29,7 @@ Currently, `iced_wgpu` supports the following primitives:
Add `iced_wgpu` as a dependency in your `Cargo.toml`:
```toml
-iced_wgpu = "0.3"
+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 534c6cb7..783079f3 100644
--- a/wgpu/src/backend.rs
+++ b/wgpu/src/backend.rs
@@ -31,8 +31,13 @@ pub struct Backend {
impl Backend {
/// Creates a new [`Backend`].
pub fn new(device: &wgpu::Device, settings: Settings) -> Self {
- let text_pipeline =
- text::Pipeline::new(device, settings.format, settings.default_font);
+ let text_pipeline = text::Pipeline::new(
+ device,
+ settings.format,
+ settings.default_font,
+ settings.text_multithreading,
+ );
+
let quad_pipeline = quad::Pipeline::new(device, settings.format);
let triangle_pipeline = triangle::Pipeline::new(
device,
diff --git a/wgpu/src/image.rs b/wgpu/src/image.rs
index 5511565e..85663bf5 100644
--- a/wgpu/src/image.rs
+++ b/wgpu/src/image.rs
@@ -97,11 +97,13 @@ impl Pipeline {
entries: &[
wgpu::BindGroupEntry {
binding: 0,
- resource: wgpu::BindingResource::Buffer {
- buffer: &uniforms_buffer,
- offset: 0,
- size: None,
- },
+ resource: wgpu::BindingResource::Buffer(
+ wgpu::BufferBinding {
+ buffer: &uniforms_buffer,
+ offset: 0,
+ size: None,
+ },
+ ),
},
wgpu::BindGroupEntry {
binding: 1,
@@ -134,86 +136,68 @@ 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: wgpu::VertexState {
- module: &vs_module,
- entry_point: "main",
+ module: &shader,
+ entry_point: "vs_main",
buffers: &[
wgpu::VertexBufferLayout {
array_stride: mem::size_of::<Vertex>() as u64,
step_mode: wgpu::InputStepMode::Vertex,
attributes: &[wgpu::VertexAttribute {
shader_location: 0,
- format: wgpu::VertexFormat::Float2,
+ format: wgpu::VertexFormat::Float32x2,
offset: 0,
}],
},
wgpu::VertexBufferLayout {
array_stride: mem::size_of::<Instance>() as u64,
step_mode: wgpu::InputStepMode::Instance,
- attributes: &[
- wgpu::VertexAttribute {
- shader_location: 1,
- format: wgpu::VertexFormat::Float2,
- offset: 0,
- },
- wgpu::VertexAttribute {
- shader_location: 2,
- format: wgpu::VertexFormat::Float2,
- offset: 4 * 2,
- },
- wgpu::VertexAttribute {
- shader_location: 3,
- format: wgpu::VertexFormat::Float2,
- offset: 4 * 4,
- },
- wgpu::VertexAttribute {
- shader_location: 4,
- format: wgpu::VertexFormat::Float2,
- offset: 4 * 6,
- },
- wgpu::VertexAttribute {
- 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,
+ ),
},
],
},
fragment: Some(wgpu::FragmentState {
- module: &fs_module,
- entry_point: "main",
+ module: &shader,
+ entry_point: "fs_main",
targets: &[wgpu::ColorTargetState {
format,
- color_blend: wgpu::BlendState {
- src_factor: wgpu::BlendFactor::SrcAlpha,
- dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha,
- operation: wgpu::BlendOperation::Add,
- },
- alpha_blend: wgpu::BlendState {
- src_factor: wgpu::BlendFactor::One,
- dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha,
- operation: wgpu::BlendOperation::Add,
- },
+ 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,
- cull_mode: wgpu::CullMode::None,
..Default::default()
},
depth_stencil: None,
@@ -423,16 +407,14 @@ impl Pipeline {
let mut render_pass =
encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
label: Some("iced_wgpu::image render pass"),
- color_attachments: &[
- wgpu::RenderPassColorAttachmentDescriptor {
- attachment: target,
- resolve_target: None,
- ops: wgpu::Operations {
- load: wgpu::LoadOp::Load,
- store: true,
- },
+ color_attachments: &[wgpu::RenderPassColorAttachment {
+ view: target,
+ resolve_target: None,
+ ops: wgpu::Operations {
+ load: wgpu::LoadOp::Load,
+ store: true,
},
- ],
+ }],
depth_stencil_attachment: None,
});
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/vector.rs b/wgpu/src/image/vector.rs
index ab0f67d0..cd511a45 100644
--- a/wgpu/src/image/vector.rs
+++ b/wgpu/src/image/vector.rs
@@ -1,7 +1,8 @@
-use crate::image::atlas::{self, Atlas};
use iced_native::svg;
use std::collections::{HashMap, HashSet};
+use crate::image::atlas::{self, Atlas};
+
pub enum Svg {
Loaded(usvg::Tree),
NotFound,
@@ -74,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!
@@ -111,29 +112,17 @@ impl Cache {
let width = img.width();
let height = img.height();
- let mut rgba = img.take().into_iter();
-
- // TODO: Perform conversion in the GPU
- let bgra: Vec<u8> = std::iter::from_fn(move || {
- use std::iter::once;
-
- let r = rgba.next()?;
- let g = rgba.next()?;
- let b = rgba.next()?;
- let a = rgba.next()?;
-
- Some(once(b).chain(once(g)).chain(once(r)).chain(once(a)))
- })
- .flatten()
- .collect();
+ let mut rgba = img.take();
+ rgba.chunks_exact_mut(4).for_each(|rgba| rgba.swap(0, 2));
let allocation = texture_atlas.upload(
width,
height,
- bytemuck::cast_slice(bgra.as_slice()),
+ 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/quad.rs b/wgpu/src/quad.rs
index e0a6e043..93942fba 100644
--- a/wgpu/src/quad.rs
+++ b/wgpu/src/quad.rs
@@ -28,7 +28,7 @@ impl Pipeline {
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,
@@ -37,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,
});
@@ -47,11 +47,7 @@ impl Pipeline {
layout: &constant_layout,
entries: &[wgpu::BindGroupEntry {
binding: 0,
- resource: wgpu::BindingResource::Buffer {
- buffer: &constants_buffer,
- offset: 0,
- size: None,
- },
+ resource: constants_buffer.as_entire_binding(),
}],
});
@@ -62,91 +58,69 @@ 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: wgpu::VertexState {
- module: &vs_module,
- entry_point: "main",
+ module: &shader,
+ entry_point: "vs_main",
buffers: &[
wgpu::VertexBufferLayout {
array_stride: mem::size_of::<Vertex>() as u64,
step_mode: wgpu::InputStepMode::Vertex,
attributes: &[wgpu::VertexAttribute {
shader_location: 0,
- format: wgpu::VertexFormat::Float2,
+ format: wgpu::VertexFormat::Float32x2,
offset: 0,
}],
},
wgpu::VertexBufferLayout {
array_stride: mem::size_of::<layer::Quad>() as u64,
step_mode: wgpu::InputStepMode::Instance,
- attributes: &[
- wgpu::VertexAttribute {
- shader_location: 1,
- format: wgpu::VertexFormat::Float2,
- offset: 0,
- },
- wgpu::VertexAttribute {
- shader_location: 2,
- format: wgpu::VertexFormat::Float2,
- offset: 4 * 2,
- },
- wgpu::VertexAttribute {
- shader_location: 3,
- format: wgpu::VertexFormat::Float4,
- offset: 4 * (2 + 2),
- },
- wgpu::VertexAttribute {
- shader_location: 4,
- format: wgpu::VertexFormat::Float4,
- offset: 4 * (2 + 2 + 4),
- },
- wgpu::VertexAttribute {
- shader_location: 5,
- format: wgpu::VertexFormat::Float,
- offset: 4 * (2 + 2 + 4 + 4),
- },
- wgpu::VertexAttribute {
- 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,
+ ),
},
],
},
fragment: Some(wgpu::FragmentState {
- module: &fs_module,
- entry_point: "main",
+ module: &shader,
+ entry_point: "fs_main",
targets: &[wgpu::ColorTargetState {
format,
- color_blend: wgpu::BlendState {
- src_factor: wgpu::BlendFactor::SrcAlpha,
- dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha,
- operation: wgpu::BlendOperation::Add,
- },
- alpha_blend: wgpu::BlendState {
- src_factor: wgpu::BlendFactor::One,
- dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha,
- operation: wgpu::BlendOperation::Add,
- },
+ 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,
- cull_mode: wgpu::CullMode::None,
..Default::default()
},
depth_stencil: None,
@@ -237,16 +211,14 @@ impl Pipeline {
let mut render_pass =
encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
label: Some("iced_wgpu::quad render pass"),
- color_attachments: &[
- wgpu::RenderPassColorAttachmentDescriptor {
- attachment: target,
- resolve_target: None,
- ops: wgpu::Operations {
- load: wgpu::LoadOp::Load,
- store: true,
- },
+ color_attachments: &[wgpu::RenderPassColorAttachment {
+ view: target,
+ resolve_target: None,
+ ops: wgpu::Operations {
+ load: wgpu::LoadOp::Load,
+ store: true,
},
- ],
+ }],
depth_stencil_attachment: None,
});
@@ -309,6 +281,9 @@ const MAX_INSTANCES: usize = 100_000;
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 {
@@ -316,6 +291,7 @@ impl Uniforms {
Self {
transform: *transformation.as_ref(),
scale,
+ _padding: [0.0; 3],
}
}
}
@@ -325,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 26763e22..9a7eed34 100644
--- a/wgpu/src/settings.rs
+++ b/wgpu/src/settings.rs
@@ -16,6 +16,9 @@ pub struct Settings {
/// [`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.
///
/// If `None` is provided, a default system font will be chosen.
@@ -26,18 +29,67 @@ 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 09a278b1..00000000
--- a/wgpu/src/shader/quad.vert
+++ /dev/null
@@ -1,47 +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;
-
- 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),
- 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 fa71ba1e..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 4d92d9e9..2b5b94c9 100644
--- a/wgpu/src/text.rs
+++ b/wgpu/src/text.rs
@@ -15,6 +15,7 @@ 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());
@@ -46,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 2f255940..65b2b7b0 100644
--- a/wgpu/src/triangle.rs
+++ b/wgpu/src/triangle.rs
@@ -110,13 +110,17 @@ impl Pipeline {
layout: &constants_layout,
entries: &[wgpu::BindGroupEntry {
binding: 0,
- resource: wgpu::BindingResource::Buffer {
- buffer: &constants_buffer.raw,
- offset: 0,
- size: wgpu::BufferSize::new(
- std::mem::size_of::<Uniforms>() as u64,
- ),
- },
+ resource: wgpu::BindingResource::Buffer(
+ wgpu::BufferBinding {
+ buffer: &constants_buffer.raw,
+ offset: 0,
+ size: wgpu::BufferSize::new(std::mem::size_of::<
+ Uniforms,
+ >(
+ )
+ as u64),
+ },
+ ),
}],
});
@@ -127,62 +131,56 @@ 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: wgpu::VertexState {
- module: &vs_module,
- entry_point: "main",
+ 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::VertexAttribute {
- shader_location: 0,
- format: wgpu::VertexFormat::Float2,
- offset: 0,
- },
+ 0 => Float32x2,
// Color
- wgpu::VertexAttribute {
- shader_location: 1,
- format: wgpu::VertexFormat::Float4,
- offset: 4 * 2,
- },
- ],
+ 1 => Float32x4,
+ ),
}],
},
fragment: Some(wgpu::FragmentState {
- module: &fs_module,
- entry_point: "main",
+ module: &shader,
+ entry_point: "fs_main",
targets: &[wgpu::ColorTargetState {
format,
- color_blend: wgpu::BlendState {
- src_factor: wgpu::BlendFactor::SrcAlpha,
- dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha,
- operation: wgpu::BlendOperation::Add,
- },
- alpha_blend: wgpu::BlendState {
- src_factor: wgpu::BlendFactor::One,
- dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha,
- operation: wgpu::BlendOperation::Add,
- },
+ 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,
- cull_mode: wgpu::CullMode::None,
..Default::default()
},
depth_stencil: None,
@@ -254,15 +252,15 @@ impl Pipeline {
layout: &self.constants_layout,
entries: &[wgpu::BindGroupEntry {
binding: 0,
- resource: wgpu::BindingResource::Buffer {
- buffer: &self.uniforms_buffer.raw,
- offset: 0,
- size: wgpu::BufferSize::new(std::mem::size_of::<
- Uniforms,
- >(
- )
- as u64),
- },
+ resource: wgpu::BindingResource::Buffer(
+ wgpu::BufferBinding {
+ buffer: &self.uniforms_buffer.raw,
+ offset: 0,
+ size: wgpu::BufferSize::new(
+ std::mem::size_of::<Uniforms>() as u64,
+ ),
+ },
+ ),
}],
});
}
@@ -363,13 +361,11 @@ impl Pipeline {
let mut render_pass =
encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
label: Some("iced_wgpu::triangle render pass"),
- color_attachments: &[
- wgpu::RenderPassColorAttachmentDescriptor {
- attachment,
- resolve_target,
- ops: wgpu::Operations { load, store: true },
- },
- ],
+ color_attachments: &[wgpu::RenderPassColorAttachment {
+ view: attachment,
+ resolve_target,
+ ops: wgpu::Operations { load, store: true },
+ }],
depth_stencil_attachment: None,
});
diff --git a/wgpu/src/triangle/msaa.rs b/wgpu/src/triangle/msaa.rs
index d964f815..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()
});
@@ -74,45 +74,47 @@ 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: wgpu::VertexState {
- module: &vs_module,
- entry_point: "main",
+ module: &shader,
+ entry_point: "vs_main",
buffers: &[],
},
fragment: Some(wgpu::FragmentState {
- module: &fs_module,
- entry_point: "main",
+ module: &shader,
+ entry_point: "fs_main",
targets: &[wgpu::ColorTargetState {
format,
- color_blend: wgpu::BlendState {
- src_factor: wgpu::BlendFactor::SrcAlpha,
- dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha,
- operation: wgpu::BlendOperation::Add,
- },
- alpha_blend: wgpu::BlendState {
- src_factor: wgpu::BlendFactor::One,
- dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha,
- operation: wgpu::BlendOperation::Add,
- },
+ 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,
- cull_mode: wgpu::CullMode::None,
..Default::default()
},
depth_stencil: None,
@@ -177,16 +179,14 @@ impl Blit {
let mut render_pass =
encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
label: Some("iced_wgpu::triangle::msaa render pass"),
- color_attachments: &[
- wgpu::RenderPassColorAttachmentDescriptor {
- attachment: target,
- resolve_target: None,
- ops: wgpu::Operations {
- load: wgpu::LoadOp::Load,
- store: true,
- },
+ color_attachments: &[wgpu::RenderPassColorAttachment {
+ view: target,
+ resolve_target: None,
+ ops: wgpu::Operations {
+ load: wgpu::LoadOp::Load,
+ store: true,
},
- ],
+ }],
depth_stencil_attachment: None,
});
@@ -222,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 {
diff --git a/wgpu/src/widget.rs b/wgpu/src/widget.rs
index 304bb726..a575d036 100644
--- a/wgpu/src/widget.rs
+++ b/wgpu/src/widget.rs
@@ -20,6 +20,7 @@ pub mod rule;
pub mod scrollable;
pub mod slider;
pub mod text_input;
+pub mod toggler;
pub mod tooltip;
#[doc(no_inline)]
@@ -45,6 +46,8 @@ 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")]
diff --git a/wgpu/src/widget/pane_grid.rs b/wgpu/src/widget/pane_grid.rs
index 44f9201c..fc36862c 100644
--- a/wgpu/src/widget/pane_grid.rs
+++ b/wgpu/src/widget/pane_grid.rs
@@ -6,7 +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.2/examples/pane_grid
+//! [`pane_grid` example]: https://github.com/hecrj/iced/tree/0.3/examples/pane_grid
use crate::Renderer;
pub use iced_graphics::pane_grid::{
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/window/compositor.rs b/wgpu/src/window/compositor.rs
index 36d6ab1a..9b65596f 100644
--- a/wgpu/src/window/compositor.rs
+++ b/wgpu/src/window/compositor.rs
@@ -21,8 +21,15 @@ impl Compositor {
/// Requests a new [`Compositor`] with the given [`Settings`].
///
/// Returns `None` if no compatible graphics adapter could be found.
- 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 {
@@ -31,7 +38,7 @@ impl Compositor {
} else {
wgpu::PowerPreference::HighPerformance
},
- compatible_surface: None,
+ compatible_surface: compatible_surface.as_ref(),
})
.await?;
@@ -77,9 +84,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();
@@ -133,8 +146,8 @@ impl iced_graphics::window::Compositor for Compositor {
let _ = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
label: Some("iced_wgpu::window::Compositor render pass"),
- color_attachments: &[wgpu::RenderPassColorAttachmentDescriptor {
- attachment: &frame.output.view,
+ color_attachments: &[wgpu::RenderPassColorAttachment {
+ view: &frame.output.view,
resolve_target: None,
ops: wgpu::Operations {
load: wgpu::LoadOp::Clear({
diff --git a/winit/Cargo.toml b/winit/Cargo.toml
index ecee0e2e..b1192135 100644
--- a/winit/Cargo.toml
+++ b/winit/Cargo.toml
@@ -1,6 +1,6 @@
[package]
name = "iced_winit"
-version = "0.2.0"
+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.24"
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.3"
+version = "0.4"
path = "../native"
[dependencies.iced_graphics]
-version = "0.1"
+version = "0.2"
path = "../graphics"
[dependencies.iced_futures]
-version = "0.2"
+version = "0.3"
path = "../futures"
[target.'cfg(target_os = "windows")'.dependencies.winapi]
diff --git a/winit/README.md b/winit/README.md
index 721baa14..58c782c6 100644
--- a/winit/README.md
+++ b/winit/README.md
@@ -20,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.2"
+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 d7d7660e..5d1aabf9 100644
--- a/winit/src/application.rs
+++ b/winit/src/application.rs
@@ -14,6 +14,7 @@ use iced_futures::futures;
use iced_futures::futures::channel::mpsc;
use iced_graphics::window;
use iced_native::program::Program;
+use iced_native::Menu;
use iced_native::{Cache, UserInterface};
use std::mem::ManuallyDrop;
@@ -91,6 +92,20 @@ pub trait Application: Program<Clipboard = Clipboard> {
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
@@ -111,8 +126,6 @@ where
let mut debug = Debug::new();
debug.startup_started();
- let (compositor, renderer) = C::new(compositor_settings)?;
-
let event_loop = EventLoop::with_user_event();
let mut runtime = {
@@ -140,19 +153,23 @@ where
application.mode(),
event_loop.primary_monitor(),
)
+ .with_menu(Some(conversion::menu(&application.menu())))
.build(&event_loop)
.map_err(Error::WindowCreationFailed)?;
+ let (compositor, renderer) = C::new(compositor_settings, Some(&window))?;
+
let (mut sender, receiver) = mpsc::unbounded();
let mut instance = Box::pin(run_instance::<A, E, C>(
application,
compositor,
renderer,
- window,
runtime,
debug,
receiver,
+ window,
+ settings.exit_on_close_request,
));
let mut context = task::Context::from_waker(task::noop_waker_ref());
@@ -164,7 +181,22 @@ where
return;
}
- if let Some(event) = event.to_static() {
+ 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);
@@ -181,10 +213,11 @@ async fn run_instance<A, E, C>(
mut application: A,
mut compositor: C,
mut renderer: A::Renderer,
- window: winit::window::Window,
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,
@@ -264,6 +297,8 @@ async fn run_instance<A, E, C>(
// Update window
state.synchronize(&application, &window);
+ let should_exit = application.should_exit();
+
user_interface = ManuallyDrop::new(build_user_interface(
&mut application,
cache,
@@ -271,6 +306,10 @@ async fn run_instance<A, E, C>(
state.logical_size(),
&mut debug,
));
+
+ if should_exit {
+ break;
+ }
}
debug.draw_started();
@@ -280,15 +319,30 @@ async fn run_instance<A, E, C>(
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();
+
+ if physical_size.width == 0 || physical_size.height == 0 {
+ continue;
+ }
+
debug.render_started();
let current_viewport_version = state.viewport_version();
if viewport_version != current_viewport_version {
- let physical_size = state.physical_size();
let logical_size = state.logical_size();
debug.layout_started();
@@ -335,10 +389,22 @@ async fn run_instance<A, E, C>(
// 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 requests_exit(&window_event, state.modifiers()) {
+ if requests_exit(&window_event, state.modifiers())
+ && exit_on_close_request
+ {
break;
}
diff --git a/winit/src/application/state.rs b/winit/src/application/state.rs
index 46297370..f60f09be 100644
--- a/winit/src/application/state.rs
+++ b/winit/src/application/state.rs
@@ -1,5 +1,5 @@
use crate::conversion;
-use crate::{Application, Color, Debug, Mode, Point, Size, Viewport};
+use crate::{Application, Color, Debug, Menu, Mode, Point, Size, Viewport};
use std::marker::PhantomData;
use winit::event::{Touch, WindowEvent};
@@ -9,6 +9,7 @@ use winit::window::Window;
#[derive(Debug, Clone)]
pub struct State<A: Application> {
title: String,
+ menu: Menu<A::Message>,
mode: Mode,
background_color: Color,
scale_factor: f64,
@@ -23,6 +24,7 @@ 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();
@@ -38,6 +40,7 @@ impl<A: Application> State<A> {
Self {
title,
+ menu,
mode,
background_color,
scale_factor,
@@ -50,6 +53,11 @@ impl<A: Application> State<A> {
}
}
+ /// 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
@@ -182,6 +190,8 @@ impl<A: Application> State<A> {
new_mode,
));
+ window.set_visible(conversion::visible(new_mode));
+
self.mode = new_mode;
}
@@ -201,5 +211,14 @@ impl<A: Application> State<A> {
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/conversion.rs b/winit/src/conversion.rs
index 0e04b35d..b3d05857 100644
--- a/winit/src/conversion.rs
+++ b/winit/src/conversion.rs
@@ -3,10 +3,11 @@
//! [`winit`]: https://github.com/rust-windowing/winit
//! [`iced_native`]: https://github.com/hecrj/iced/tree/master/native
use crate::keyboard;
+use crate::menu::{self, Menu};
use crate::mouse;
use crate::touch;
use crate::window;
-use crate::{Event, Mode, Point};
+use crate::{Event, Mode, Point, Position};
/// Converts a winit window event into an iced event.
pub fn window_event(
@@ -33,6 +34,9 @@ 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);
@@ -130,6 +134,49 @@ pub fn window_event(
}
}
+/// 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.
///
/// [`winit`]: https://github.com/rust-windowing/winit
@@ -138,13 +185,125 @@ pub fn fullscreen(
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
@@ -192,12 +351,14 @@ pub fn mouse_button(mouse_button: winit::event::MouseButton) -> mouse::Button {
pub fn modifiers(
modifiers: winit::event::ModifiersState,
) -> keyboard::Modifiers {
- keyboard::Modifiers {
- shift: modifiers.shift(),
- control: modifiers.ctrl(),
- alt: modifiers.alt(),
- logo: modifiers.logo(),
- }
+ 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`.
@@ -241,6 +402,183 @@ pub fn touch_event(
}
}
+/// 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
diff --git a/winit/src/lib.rs b/winit/src/lib.rs
index c9f324dd..1707846a 100644
--- a/winit/src/lib.rs
+++ b/winit/src/lib.rs
@@ -31,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/settings.rs b/winit/src/settings.rs
index 2e8715cd..743f79bc 100644
--- a/winit/src/settings.rs
+++ b/winit/src/settings.rs
@@ -9,7 +9,7 @@ mod platform;
pub use platform::PlatformSpecific;
use crate::conversion;
-use crate::Mode;
+use crate::{Mode, Position};
use winit::monitor::MonitorHandle;
use winit::window::WindowBuilder;
@@ -23,6 +23,10 @@ pub struct Settings<Flags> {
///
/// [`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.
@@ -31,6 +35,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)>,
@@ -76,7 +83,15 @@ impl Window {
.with_transparent(self.transparent)
.with_window_icon(self.icon)
.with_always_on_top(self.always_on_top)
- .with_fullscreen(conversion::fullscreen(primary_monitor, mode));
+ .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
@@ -95,8 +110,13 @@ 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
}
}
@@ -105,6 +125,7 @@ impl Default for Window {
fn default() -> Window {
Window {
size: (1024, 768),
+ position: Position::default(),
min_size: None,
max_size: None,
resizable: true,
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,
+ }
+ }
}