summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorLibravatar Héctor Ramón Jiménez <hector0193@gmail.com>2023-04-17 23:41:12 +0200
committerLibravatar Héctor Ramón Jiménez <hector0193@gmail.com>2023-04-17 23:41:12 +0200
commit4bae457c37b499f3cfddbdac9ff37a34cbce61d5 (patch)
tree79af93b2f7fabca1687900b48b165c5c74dcd26f
parentc0431aedd3bbef4161456f2fa5f29866e8f17fc5 (diff)
parent4b05f42fd6d18bf572b772dd60d6a4309ea5f343 (diff)
downloadiced-4bae457c37b499f3cfddbdac9ff37a34cbce61d5.tar.gz
iced-4bae457c37b499f3cfddbdac9ff37a34cbce61d5.tar.bz2
iced-4bae457c37b499f3cfddbdac9ff37a34cbce61d5.zip
Merge branch 'master' into advanced-text
-rw-r--r--CHANGELOG.md56
-rw-r--r--Cargo.toml6
-rw-r--r--README.md4
-rw-r--r--ROADMAP.md14
-rw-r--r--core/Cargo.toml2
-rw-r--r--core/README.md2
-rw-r--r--core/src/lib.rs2
-rw-r--r--core/src/mouse/interaction.rs1
-rw-r--r--core/src/widget.rs10
-rw-r--r--core/src/window.rs3
-rw-r--r--core/src/window/icon.rs80
-rw-r--r--docs/release_summary.py52
-rw-r--r--examples/component/src/main.rs2
-rw-r--r--examples/download_progress/src/download.rs17
-rw-r--r--examples/integration/Cargo.toml2
-rw-r--r--examples/integration/src/controls.rs9
-rw-r--r--examples/integration/src/main.rs14
-rw-r--r--examples/lazy/src/main.rs9
-rw-r--r--examples/modal/src/main.rs14
-rw-r--r--examples/qr_code/src/main.rs12
-rw-r--r--examples/scrollable/src/main.rs36
-rw-r--r--examples/styling/src/main.rs11
-rw-r--r--examples/toast/src/main.rs6
-rw-r--r--examples/todos/src/main.rs29
-rw-r--r--examples/tour/src/main.rs84
-rw-r--r--examples/websocket/src/echo.rs100
-rw-r--r--examples/websocket/src/main.rs9
-rw-r--r--futures/Cargo.toml2
-rw-r--r--futures/src/subscription.rs103
-rw-r--r--graphics/Cargo.toml4
-rw-r--r--renderer/Cargo.toml4
-rw-r--r--runtime/Cargo.toml4
-rw-r--r--runtime/README.md7
-rw-r--r--runtime/src/lib.rs4
-rw-r--r--runtime/src/user_interface.rs4
-rw-r--r--runtime/src/window.rs7
-rw-r--r--runtime/src/window/action.rs21
-rw-r--r--src/application.rs18
-rw-r--r--src/lib.rs10
-rw-r--r--src/sandbox.rs22
-rw-r--r--src/window/icon.rs209
-rw-r--r--style/Cargo.toml4
-rw-r--r--style/src/scrollable.rs22
-rw-r--r--style/src/slider.rs11
-rw-r--r--style/src/text_input.rs8
-rw-r--r--style/src/theme.rs86
-rw-r--r--tiny_skia/Cargo.toml2
-rw-r--r--wgpu/Cargo.toml4
-rw-r--r--wgpu/README.md2
-rw-r--r--wgpu/src/image.rs2
-rw-r--r--wgpu/src/lib.rs2
-rw-r--r--wgpu/src/quad.rs2
-rw-r--r--wgpu/src/triangle.rs2
-rw-r--r--wgpu/src/triangle/msaa.rs2
-rw-r--r--wgpu/src/window/compositor.rs14
-rw-r--r--widget/Cargo.toml4
-rw-r--r--widget/src/checkbox.rs22
-rw-r--r--widget/src/helpers.rs17
-rw-r--r--widget/src/lazy/component.rs252
-rw-r--r--widget/src/lib.rs3
-rw-r--r--widget/src/mouse_area.rs311
-rw-r--r--widget/src/pane_grid.rs2
-rw-r--r--widget/src/radio.rs37
-rw-r--r--widget/src/scrollable.rs52
-rw-r--r--widget/src/slider.rs78
-rw-r--r--widget/src/text_input.rs202
-rw-r--r--widget/src/vertical_slider.rs76
-rw-r--r--winit/Cargo.toml14
-rw-r--r--winit/README.md2
-rw-r--r--winit/src/application.rs21
-rw-r--r--winit/src/conversion.rs22
-rw-r--r--winit/src/lib.rs2
-rw-r--r--winit/src/settings.rs8
73 files changed, 1588 insertions, 705 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 1d9d1fb3..077f4af3 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -6,6 +6,59 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased]
+## [0.9.0] - 2023-04-13
+### Added
+- `MouseArea` widget. [#1594](https://github.com/iced-rs/iced/pull/1594)
+- `channel` helper in `subscription`. [#1786](https://github.com/iced-rs/iced/pull/1786)
+- Configurable `width` for `Scrollable`. [#1749](https://github.com/iced-rs/iced/pull/1749)
+- Support for disabled `TextInput`. [#1744](https://github.com/iced-rs/iced/pull/1744)
+- Platform-specific window settings. [#1730](https://github.com/iced-rs/iced/pull/1730)
+- Left and right colors for sliders. [#1643](https://github.com/iced-rs/iced/pull/1643)
+- Icon for `TextInput`. [#1702](https://github.com/iced-rs/iced/pull/1702)
+- Mouse over scrollbar flag for `scrollable::StyleSheet`. [#1669](https://github.com/iced-rs/iced/pull/1669)
+- Better example for `Radio`. [#1762](https://github.com/iced-rs/iced/pull/1762)
+
+### Changed
+- `wgpu` has been updated to `0.15` in `iced_wgpu`. [#1789](https://github.com/iced-rs/iced/pull/1789)
+- `resvg` has been updated to `0.29` in `iced_graphics`. [#1733](https://github.com/iced-rs/iced/pull/1733)
+- `subscription::run` now takes a function pointer. [#1723](https://github.com/iced-rs/iced/pull/1723)
+
+### Fixed
+- Redundant `on_scroll` messages for `Scrollable`. [#1788](https://github.com/iced-rs/iced/pull/1788)
+- Outdated items in `ROADMAP.md` [#1782](https://github.com/iced-rs/iced/pull/1782)
+- Colons in shader labels causing compilation issues in `iced_wgpu`. [#1779](https://github.com/iced-rs/iced/pull/1779)
+- Re-expose winit features for window servers in Linux. [#1777](https://github.com/iced-rs/iced/pull/1777)
+- Replacement of application node in Wasm. [#1765](https://github.com/iced-rs/iced/pull/1765)
+- `clippy` lints for Rust 1.68. [#1755](https://github.com/iced-rs/iced/pull/1755)
+- Unnecessary `Component` rebuilds. [#1754](https://github.com/iced-rs/iced/pull/1754)
+- Incorrect package name in checkbox example docs. [#1750](https://github.com/iced-rs/iced/pull/1750)
+- Fullscreen only working on primary monitor. [#1742](https://github.com/iced-rs/iced/pull/1742)
+- `Padding::fit` on irregular values for an axis. [#1734](https://github.com/iced-rs/iced/pull/1734)
+- `Debug` implementation of `Font` displaying its bytes. [#1731](https://github.com/iced-rs/iced/pull/1731)
+- Sliders bleeding over their rail. [#1721](https://github.com/iced-rs/iced/pull/1721)
+
+### Removed
+- `Fill` variant for `Alignment`. [#1735](https://github.com/iced-rs/iced/pull/1735)
+
+Many thanks to...
+
+- @ahoneybun
+- @bq-wrongway
+- @bungoboingo
+- @casperstorm
+- @Davidster
+- @ElhamAryanpur
+- @FinnPerry
+- @GyulyVGC
+- @JungleTryne
+- @lupd
+- @mmstick
+- @nicksenger
+- @Night-Hunter-NF
+- @tarkah
+- @traxys
+- @Xaeroxe
+
## [0.8.0] - 2023-02-18
### Added
- Generic pixel units. [#1711](https://github.com/iced-rs/iced/pull/1711)
@@ -414,7 +467,8 @@ Many thanks to...
### Added
- First release! :tada:
-[Unreleased]: https://github.com/iced-rs/iced/compare/0.8.0...HEAD
+[Unreleased]: https://github.com/iced-rs/iced/compare/0.9.0...HEAD
+[0.9.0]: https://github.com/iced-rs/iced/compare/0.8.0...0.9.0
[0.8.0]: https://github.com/iced-rs/iced/compare/0.7.0...0.8.0
[0.7.0]: https://github.com/iced-rs/iced/compare/0.6.0...0.7.0
[0.6.0]: https://github.com/iced-rs/iced/compare/0.5.0...0.6.0
diff --git a/Cargo.toml b/Cargo.toml
index 38c35f43..2f92f9db 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,6 +1,6 @@
[package]
name = "iced"
-version = "0.8.0"
+version = "0.9.0"
authors = ["Héctor Ramón Jiménez <hector0193@gmail.com>"]
edition = "2021"
description = "A cross-platform GUI library inspired by Elm"
@@ -61,11 +61,11 @@ members = [
]
[dependencies]
-iced_core = { version = "0.8", path = "core" }
+iced_core = { version = "0.9", path = "core" }
iced_futures = { version = "0.6", path = "futures" }
iced_renderer = { version = "0.1", path = "renderer" }
iced_widget = { version = "0.1", path = "widget" }
-iced_winit = { version = "0.8", path = "winit", features = ["application"] }
+iced_winit = { version = "0.9", path = "winit", features = ["application"] }
thiserror = "1"
[dependencies.image_rs]
diff --git a/README.md b/README.md
index a5ebf230..fe2a4a64 100644
--- a/README.md
+++ b/README.md
@@ -68,7 +68,7 @@ __Iced is currently experimental software.__ [Take a look at the roadmap],
Add `iced` as a dependency in your `Cargo.toml`:
```toml
-iced = "0.8"
+iced = "0.9"
```
If your project is using a Rust edition older than 2021, then you will need to
@@ -215,7 +215,7 @@ cargo run --features iced/glow --package game_of_life
and then use it in your project with
```toml
-iced = { version = "0.8", default-features = false, features = ["glow"] }
+iced = { version = "0.9", default-features = false, features = ["glow"] }
```
__NOTE:__ Chances are you have hardware that supports at least OpenGL 2.1 or OpenGL ES 2.0,
diff --git a/ROADMAP.md b/ROADMAP.md
index ef6df0e4..c30c591b 100644
--- a/ROADMAP.md
+++ b/ROADMAP.md
@@ -19,6 +19,7 @@ Once a step is completed, it is collapsed and added to this list:
* [x] Custom styling ([#146])
* [x] Canvas for 2D graphics ([#193])
* [x] Basic overlay support ([#444])
+ * [x] Animations [#31]
[#24]: https://github.com/iced-rs/iced/issues/24
[#25]: https://github.com/iced-rs/iced/issues/25
@@ -29,6 +30,7 @@ Once a step is completed, it is collapsed and added to this list:
[#146]: https://github.com/iced-rs/iced/pull/146
[#193]: https://github.com/iced-rs/iced/pull/193
[#444]: https://github.com/iced-rs/iced/pull/444
+[#31]: https://github.com/iced-rs/iced/issues/31
### Multi-window support ([#27])
Open and control multiple windows at runtime.
@@ -39,16 +41,7 @@ This approach should also allow us to perform custom optimizations for this part
[#27]: https://github.com/iced-rs/iced/issues/27
-### Animations ([#31])
-Allow widgets to request a redraw at a specific time.
-
-This is a necessary feature to render loading spinners, a blinking text cursor, GIF images, etc.
-
-[`winit`] allows flexible control of its event loop. We may be able to use [`ControlFlow::WaitUntil`](https://docs.rs/winit/0.20.0-alpha3/winit/event_loop/enum.ControlFlow.html#variant.WaitUntil) for this purpose.
-
-[#31]: https://github.com/iced-rs/iced/issues/31
-
-### Canvas widget for 3D graphics ([#32])
+### Canvas widget for 3D graphics (~~[#32]~~ [#343])
A widget to draw freely in 3D. It could be used to draw charts, implement a Paint clone, a CAD application, etc.
As a first approach, we could expose the underlying renderer directly here, and couple this widget with it ([`wgpu`] for now). Once [`wgpu`] gets WebGL or WebGPU support, this widget will be able to run on the web too. The renderer primitive could be a simple texture that the widget draws to.
@@ -56,6 +49,7 @@ As a first approach, we could expose the underlying renderer directly here, and
In the long run, we could expose a renderer-agnostic abstraction to perform the drawing.
[#32]: https://github.com/iced-rs/iced/issues/32
+[#343] https://github.com/iced-rs/iced/issues/343
### Text shaping and font fallback ([#33])
[`wgpu_glyph`] uses [`glyph_brush`], which in turn uses [`rusttype`]. While the current implementation is able to layout text quite nicely, it does not perform any [text shaping].
diff --git a/core/Cargo.toml b/core/Cargo.toml
index dac31828..92d9773f 100644
--- a/core/Cargo.toml
+++ b/core/Cargo.toml
@@ -1,6 +1,6 @@
[package]
name = "iced_core"
-version = "0.8.1"
+version = "0.9.0"
authors = ["Héctor Ramón Jiménez <hector0193@gmail.com>"]
edition = "2021"
description = "The essential concepts of Iced"
diff --git a/core/README.md b/core/README.md
index 64d92e78..519e0608 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.8"
+iced_core = "0.9"
```
__Iced moves fast and the `master` branch can contain breaking changes!__ If
diff --git a/core/src/lib.rs b/core/src/lib.rs
index 5bdcee6a..89dfb828 100644
--- a/core/src/lib.rs
+++ b/core/src/lib.rs
@@ -7,7 +7,7 @@
//! ![The foundations of the Iced ecosystem](https://github.com/iced-rs/iced/blob/0525d76ff94e828b7b21634fa94a747022001c83/docs/graphs/foundations.png?raw=true)
//!
//! [Iced]: https://github.com/iced-rs/iced
-//! [`iced_native`]: https://github.com/iced-rs/iced/tree/0.8/native
+//! [`iced_native`]: https://github.com/iced-rs/iced/tree/0.9/native
//! [`iced_web`]: https://github.com/iced-rs/iced_web
#![doc(
html_logo_url = "https://raw.githubusercontent.com/iced-rs/iced/9ab6923e943f784985e9ef9ca28b10278297225d/docs/logo.svg"
diff --git a/core/src/mouse/interaction.rs b/core/src/mouse/interaction.rs
index 57da93fe..072033fd 100644
--- a/core/src/mouse/interaction.rs
+++ b/core/src/mouse/interaction.rs
@@ -12,4 +12,5 @@ pub enum Interaction {
Grabbing,
ResizingHorizontally,
ResizingVertically,
+ NotAllowed,
}
diff --git a/core/src/widget.rs b/core/src/widget.rs
index 70e2c2d9..769f8659 100644
--- a/core/src/widget.rs
+++ b/core/src/widget.rs
@@ -33,12 +33,12 @@ use crate::{Clipboard, Length, Point, Rectangle, Shell};
/// - [`geometry`], a custom widget showcasing how to draw geometry with the
/// `Mesh2D` primitive in [`iced_wgpu`].
///
-/// [examples]: https://github.com/iced-rs/iced/tree/0.8/examples
-/// [`bezier_tool`]: https://github.com/iced-rs/iced/tree/0.8/examples/bezier_tool
-/// [`custom_widget`]: https://github.com/iced-rs/iced/tree/0.8/examples/custom_widget
-/// [`geometry`]: https://github.com/iced-rs/iced/tree/0.8/examples/geometry
+/// [examples]: https://github.com/iced-rs/iced/tree/0.9/examples
+/// [`bezier_tool`]: https://github.com/iced-rs/iced/tree/0.9/examples/bezier_tool
+/// [`custom_widget`]: https://github.com/iced-rs/iced/tree/0.9/examples/custom_widget
+/// [`geometry`]: https://github.com/iced-rs/iced/tree/0.9/examples/geometry
/// [`lyon`]: https://github.com/nical/lyon
-/// [`iced_wgpu`]: https://github.com/iced-rs/iced/tree/0.8/wgpu
+/// [`iced_wgpu`]: https://github.com/iced-rs/iced/tree/0.9/wgpu
pub trait Widget<Message, Renderer>
where
Renderer: crate::Renderer,
diff --git a/core/src/window.rs b/core/src/window.rs
index d829a4b4..81bd7e3d 100644
--- a/core/src/window.rs
+++ b/core/src/window.rs
@@ -1,10 +1,13 @@
//! Build window-based GUI applications.
+pub mod icon;
+
mod event;
mod mode;
mod redraw_request;
mod user_attention;
pub use event::Event;
+pub use icon::Icon;
pub use mode::Mode;
pub use redraw_request::RedrawRequest;
pub use user_attention::UserAttention;
diff --git a/core/src/window/icon.rs b/core/src/window/icon.rs
new file mode 100644
index 00000000..31868ecf
--- /dev/null
+++ b/core/src/window/icon.rs
@@ -0,0 +1,80 @@
+//! Change the icon of a window.
+use crate::Size;
+
+use std::mem;
+
+/// Builds an [`Icon`] from its RGBA pixels in the sRGB color space.
+pub fn from_rgba(
+ rgba: Vec<u8>,
+ width: u32,
+ height: u32,
+) -> Result<Icon, Error> {
+ const PIXEL_SIZE: usize = mem::size_of::<u8>() * 4;
+
+ if rgba.len() % PIXEL_SIZE != 0 {
+ return Err(Error::ByteCountNotDivisibleBy4 {
+ byte_count: rgba.len(),
+ });
+ }
+
+ let pixel_count = rgba.len() / PIXEL_SIZE;
+
+ if pixel_count != (width * height) as usize {
+ return Err(Error::DimensionsVsPixelCount {
+ width,
+ height,
+ width_x_height: (width * height) as usize,
+ pixel_count,
+ });
+ }
+
+ Ok(Icon {
+ rgba,
+ size: Size::new(width, height),
+ })
+}
+
+/// An window icon normally used for the titlebar or taskbar.
+#[derive(Debug, Clone)]
+pub struct Icon {
+ rgba: Vec<u8>,
+ size: Size<u32>,
+}
+
+impl Icon {
+ /// Returns the raw data of the [`Icon`].
+ pub fn into_raw(self) -> (Vec<u8>, Size<u32>) {
+ (self.rgba, self.size)
+ }
+}
+
+#[derive(Debug, thiserror::Error)]
+/// An error produced when using [`Icon::from_rgba`] with invalid arguments.
+pub enum Error {
+ /// Produced when the length of the `rgba` argument isn't divisible by 4, thus `rgba` can't be
+ /// safely interpreted as 32bpp RGBA pixels.
+ #[error(
+ "The provided RGBA data (with length {byte_count}) isn't divisible \
+ by 4. Therefore, it cannot be safely interpreted as 32bpp RGBA pixels"
+ )]
+ ByteCountNotDivisibleBy4 {
+ /// The length of the provided RGBA data.
+ byte_count: usize,
+ },
+ /// Produced when the number of pixels (`rgba.len() / 4`) isn't equal to `width * height`.
+ /// At least one of your arguments is incorrect.
+ #[error(
+ "The number of RGBA pixels ({pixel_count}) does not match the \
+ provided dimensions ({width}x{height})."
+ )]
+ DimensionsVsPixelCount {
+ /// The provided width.
+ width: u32,
+ /// The provided height.
+ height: u32,
+ /// The product of `width` and `height`.
+ width_x_height: usize,
+ /// The amount of pixels of the provided RGBA data.
+ pixel_count: usize,
+ },
+}
diff --git a/docs/release_summary.py b/docs/release_summary.py
new file mode 100644
index 00000000..cd4593b5
--- /dev/null
+++ b/docs/release_summary.py
@@ -0,0 +1,52 @@
+import re
+import sys
+import requests
+from typing import List, Tuple
+
+if len(sys.argv) < 3:
+ print("Usage: python release_summary.py <personal_access_token> <previous_release_branch>")
+ exit(1)
+
+TOKEN = sys.argv[1]
+HEADERS = {"Authorization": f"Bearer {TOKEN}"}
+PR_COMMIT_REGEX = re.compile(r"(?i)Merge pull request #(\d+).*")
+
+def get_merged_prs_since_release(repo: str, previous_release_branch: str) -> List[Tuple[str, int, str, str]]:
+ prs = []
+ compare_url = f"https://api.github.com/repos/{repo}/compare/{previous_release_branch}...master"
+ compare_response = requests.get(compare_url, headers=HEADERS)
+
+ if compare_response.status_code == 200:
+ compare_data = compare_response.json()
+ for commit in compare_data["commits"]:
+ match = PR_COMMIT_REGEX.search(commit["commit"]["message"])
+ if match:
+ pr_number = int(match.group(1))
+ pr_url = f"https://api.github.com/repos/{repo}/pulls/{pr_number}"
+ pr_response = requests.get(pr_url, headers=HEADERS)
+ if pr_response.status_code == 200:
+ pr_data = pr_response.json()
+ prs.append((pr_data["title"], pr_number, pr_data["html_url"], pr_data["user"]["login"]))
+ else:
+ print(f"Error fetching PR {pr_number}: {pr_response.status_code}")
+ else:
+ print(f"Error comparing branches: {compare_response.status_code}")
+
+ return prs
+
+def print_pr_list(prs: List[Tuple[str, int, str, str]]):
+ for pr in prs:
+ print(f"- {pr[0]}. [#{pr[1]}]({pr[2]})")
+
+def print_authors(prs: List[Tuple[str, int, str, str]]):
+ authors = set(pr[3] for pr in prs)
+ print("\nAuthors:")
+ for author in sorted(authors, key=str.casefold):
+ print(f"- @{author}")
+
+if __name__ == "__main__":
+ repo = "iced-rs/iced"
+ previous_release_branch = sys.argv[2]
+ merged_prs = get_merged_prs_since_release(repo, previous_release_branch)
+ print_pr_list(merged_prs)
+ print_authors(merged_prs)
diff --git a/examples/component/src/main.rs b/examples/component/src/main.rs
index 09e5e4a2..010321a9 100644
--- a/examples/component/src/main.rs
+++ b/examples/component/src/main.rs
@@ -134,8 +134,8 @@ mod numeric_input {
.map(u32::to_string)
.as_deref()
.unwrap_or(""),
- Event::InputChanged,
)
+ .on_input(Event::InputChanged)
.padding(10),
button("+", Event::IncrementPressed),
]
diff --git a/examples/download_progress/src/download.rs b/examples/download_progress/src/download.rs
index 5ff951b3..3b11cb76 100644
--- a/examples/download_progress/src/download.rs
+++ b/examples/download_progress/src/download.rs
@@ -18,10 +18,7 @@ pub struct Download<I> {
url: String,
}
-async fn download<I: Copy>(
- id: I,
- state: State,
-) -> (Option<(I, Progress)>, State) {
+async fn download<I: Copy>(id: I, state: State) -> ((I, Progress), State) {
match state {
State::Ready(url) => {
let response = reqwest::get(&url).await;
@@ -30,7 +27,7 @@ async fn download<I: Copy>(
Ok(response) => {
if let Some(total) = response.content_length() {
(
- Some((id, Progress::Started)),
+ (id, Progress::Started),
State::Downloading {
response,
total,
@@ -38,10 +35,10 @@ async fn download<I: Copy>(
},
)
} else {
- (Some((id, Progress::Errored)), State::Finished)
+ ((id, Progress::Errored), State::Finished)
}
}
- Err(_) => (Some((id, Progress::Errored)), State::Finished),
+ Err(_) => ((id, Progress::Errored), State::Finished),
}
}
State::Downloading {
@@ -55,7 +52,7 @@ async fn download<I: Copy>(
let percentage = (downloaded as f32 / total as f32) * 100.0;
(
- Some((id, Progress::Advanced(percentage))),
+ (id, Progress::Advanced(percentage)),
State::Downloading {
response,
total,
@@ -63,8 +60,8 @@ async fn download<I: Copy>(
},
)
}
- Ok(None) => (Some((id, Progress::Finished)), State::Finished),
- Err(_) => (Some((id, Progress::Errored)), State::Finished),
+ Ok(None) => ((id, Progress::Finished), State::Finished),
+ Err(_) => ((id, Progress::Errored), State::Finished),
},
State::Finished => {
// We do not let the stream die, as it would start a
diff --git a/examples/integration/Cargo.toml b/examples/integration/Cargo.toml
index f6863cd3..2ab5d75f 100644
--- a/examples/integration/Cargo.toml
+++ b/examples/integration/Cargo.toml
@@ -10,7 +10,7 @@ iced_winit = { path = "../../winit" }
iced_wgpu = { path = "../../wgpu" }
iced_widget = { path = "../../widget" }
iced_renderer = { path = "../../renderer", features = ["wgpu", "tiny-skia"] }
-env_logger = "0.8"
+env_logger = "0.10"
[target.'cfg(target_arch = "wasm32")'.dependencies]
console_error_panic_hook = "0.1.7"
diff --git a/examples/integration/src/controls.rs b/examples/integration/src/controls.rs
index 5849f730..14e53ede 100644
--- a/examples/integration/src/controls.rs
+++ b/examples/integration/src/controls.rs
@@ -102,11 +102,10 @@ impl Program for Controls {
.size(14)
.style(Color::WHITE),
)
- .push(text_input(
- "Placeholder",
- text,
- Message::TextChanged,
- )),
+ .push(
+ text_input("Placeholder", text)
+ .on_input(Message::TextChanged),
+ ),
),
)
.into()
diff --git a/examples/integration/src/main.rs b/examples/integration/src/main.rs
index 949a726a..98d2bc59 100644
--- a/examples/integration/src/main.rs
+++ b/examples/integration/src/main.rs
@@ -30,6 +30,7 @@ pub fn main() -> Result<(), Box<dyn std::error::Error>> {
#[cfg(target_arch = "wasm32")]
let canvas_element = {
console_log::init_with_level(log::Level::Debug)?;
+
std::panic::set_hook(Box::new(console_error_panic_hook::hook));
web_sys::window()
@@ -49,7 +50,7 @@ pub fn main() -> Result<(), Box<dyn std::error::Error>> {
.build(&event_loop)?;
#[cfg(not(target_arch = "wasm32"))]
- let window = winit::window::Window::new(&event_loop).unwrap();
+ let window = winit::window::Window::new(&event_loop)?;
let physical_size = window.inner_size();
let mut viewport = Viewport::with_physical_size(
@@ -61,7 +62,6 @@ pub fn main() -> Result<(), Box<dyn std::error::Error>> {
let mut clipboard = Clipboard::connect(&window);
// Initialize wgpu
-
#[cfg(target_arch = "wasm32")]
let default_backend = wgpu::Backends::GL;
#[cfg(not(target_arch = "wasm32"))]
@@ -95,12 +95,16 @@ pub fn main() -> Result<(), Box<dyn std::error::Error>> {
#[cfg(not(target_arch = "wasm32"))]
let needed_limits = wgpu::Limits::default();
+ let capabilities = surface.get_capabilities(&adapter);
+
(
- surface
- .get_capabilities(&adapter)
+ capabilities
.formats
- .first()
+ .iter()
+ .filter(|format| format.describe().srgb)
.copied()
+ .next()
+ .or_else(|| capabilities.formats.first().copied())
.expect("Get preferred format"),
adapter
.request_device(
diff --git a/examples/lazy/src/main.rs b/examples/lazy/src/main.rs
index e1cdaefe..c6baa6a1 100644
--- a/examples/lazy/src/main.rs
+++ b/examples/lazy/src/main.rs
@@ -213,12 +213,9 @@ impl Sandbox for App {
column![
scrollable(options).height(Length::Fill),
row![
- text_input(
- "Add a new option",
- &self.input,
- Message::InputChanged,
- )
- .on_submit(Message::AddItem(self.input.clone())),
+ text_input("Add a new option", &self.input)
+ .on_input(Message::InputChanged)
+ .on_submit(Message::AddItem(self.input.clone())),
button(text(format!("Toggle Order ({})", self.order)))
.on_press(Message::ToggleOrder)
]
diff --git a/examples/modal/src/main.rs b/examples/modal/src/main.rs
index 214ec97e..f48afb69 100644
--- a/examples/modal/src/main.rs
+++ b/examples/modal/src/main.rs
@@ -133,18 +133,16 @@ impl Application for App {
column![
column![
text("Email").size(12),
- text_input(
- "abc@123.com",
- &self.email,
- Message::Email
- )
- .on_submit(Message::Submit)
- .padding(5),
+ text_input("abc@123.com", &self.email,)
+ .on_input(Message::Email)
+ .on_submit(Message::Submit)
+ .padding(5),
]
.spacing(5),
column![
text("Password").size(12),
- text_input("", &self.password, Message::Password)
+ text_input("", &self.password)
+ .on_input(Message::Password)
.on_submit(Message::Submit)
.password()
.padding(5),
diff --git a/examples/qr_code/src/main.rs b/examples/qr_code/src/main.rs
index d8041745..867ebfa4 100644
--- a/examples/qr_code/src/main.rs
+++ b/examples/qr_code/src/main.rs
@@ -49,13 +49,11 @@ impl Sandbox for QRGenerator {
.size(70)
.style(Color::from([0.5, 0.5, 0.5]));
- let input = text_input(
- "Type the data of your QR code here...",
- &self.data,
- Message::DataChanged,
- )
- .size(30)
- .padding(15);
+ let input =
+ text_input("Type the data of your QR code here...", &self.data)
+ .on_input(Message::DataChanged)
+ .size(30)
+ .padding(15);
let mut content = column![title, input]
.width(700)
diff --git a/examples/scrollable/src/main.rs b/examples/scrollable/src/main.rs
index a3ade54f..2e99b1ac 100644
--- a/examples/scrollable/src/main.rs
+++ b/examples/scrollable/src/main.rs
@@ -338,22 +338,36 @@ impl scrollable::StyleSheet for ScrollbarCustomStyle {
style.active(&theme::Scrollable::Default)
}
- fn hovered(&self, style: &Self::Style) -> Scrollbar {
- style.hovered(&theme::Scrollable::Default)
+ fn hovered(
+ &self,
+ style: &Self::Style,
+ is_mouse_over_scrollbar: bool,
+ ) -> Scrollbar {
+ style.hovered(&theme::Scrollable::Default, is_mouse_over_scrollbar)
}
- fn hovered_horizontal(&self, style: &Self::Style) -> Scrollbar {
- Scrollbar {
- background: style.active(&theme::Scrollable::default()).background,
- border_radius: 0.0,
- border_width: 0.0,
- border_color: Default::default(),
- scroller: Scroller {
- color: Color::from_rgb8(250, 85, 134),
+ fn hovered_horizontal(
+ &self,
+ style: &Self::Style,
+ is_mouse_over_scrollbar: bool,
+ ) -> Scrollbar {
+ if is_mouse_over_scrollbar {
+ Scrollbar {
+ background: style
+ .active(&theme::Scrollable::default())
+ .background,
border_radius: 0.0,
border_width: 0.0,
border_color: Default::default(),
- },
+ scroller: Scroller {
+ color: Color::from_rgb8(250, 85, 134),
+ border_radius: 0.0,
+ border_width: 0.0,
+ border_color: Default::default(),
+ },
+ }
+ } else {
+ self.active(style)
}
}
}
diff --git a/examples/styling/src/main.rs b/examples/styling/src/main.rs
index 448c9792..e2015bac 100644
--- a/examples/styling/src/main.rs
+++ b/examples/styling/src/main.rs
@@ -90,13 +90,10 @@ impl Sandbox for Styling {
},
);
- let text_input = text_input(
- "Type something...",
- &self.input_value,
- Message::InputChanged,
- )
- .padding(10)
- .size(20);
+ let text_input = text_input("Type something...", &self.input_value)
+ .on_input(Message::InputChanged)
+ .padding(10)
+ .size(20);
let button = button("Submit")
.padding(10)
diff --git a/examples/toast/src/main.rs b/examples/toast/src/main.rs
index 78fb9de1..9d859258 100644
--- a/examples/toast/src/main.rs
+++ b/examples/toast/src/main.rs
@@ -119,13 +119,15 @@ impl Application for App {
column![
subtitle(
"Title",
- text_input("", &self.editing.title, Message::Title)
+ text_input("", &self.editing.title)
+ .on_input(Message::Title)
.on_submit(Message::Add)
.into()
),
subtitle(
"Message",
- text_input("", &self.editing.body, Message::Body)
+ text_input("", &self.editing.body)
+ .on_input(Message::Body)
.on_submit(Message::Add)
.into()
),
diff --git a/examples/todos/src/main.rs b/examples/todos/src/main.rs
index ed3684d3..8bc7be09 100644
--- a/examples/todos/src/main.rs
+++ b/examples/todos/src/main.rs
@@ -210,15 +210,12 @@ impl Application for Todos {
.style(Color::from([0.5, 0.5, 0.5]))
.horizontal_alignment(alignment::Horizontal::Center);
- let input = text_input(
- "What needs to be done?",
- input_value,
- Message::InputChanged,
- )
- .id(INPUT_ID.clone())
- .padding(15)
- .size(30)
- .on_submit(Message::CreateTask);
+ let input = text_input("What needs to be done?", input_value)
+ .id(INPUT_ID.clone())
+ .on_input(Message::InputChanged)
+ .on_submit(Message::CreateTask)
+ .padding(15)
+ .size(30);
let controls = view_controls(tasks, *filter);
let filtered_tasks =
@@ -381,14 +378,12 @@ impl Task {
.into()
}
TaskState::Editing => {
- let text_input = text_input(
- "Describe your task...",
- &self.description,
- TaskMessage::DescriptionEdited,
- )
- .id(Self::text_input_id(i))
- .on_submit(TaskMessage::FinishEdition)
- .padding(10);
+ let text_input =
+ text_input("Describe your task...", &self.description)
+ .id(Self::text_input_id(i))
+ .on_input(TaskMessage::DescriptionEdited)
+ .on_submit(TaskMessage::FinishEdition)
+ .padding(10);
row![
text_input,
diff --git a/examples/tour/src/main.rs b/examples/tour/src/main.rs
index de063d00..9c38ad0e 100644
--- a/examples/tour/src/main.rs
+++ b/examples/tour/src/main.rs
@@ -5,7 +5,7 @@ use iced::widget::{
scrollable, slider, text, text_input, toggler, vertical_space,
};
use iced::widget::{Button, Column, Container, Slider};
-use iced::{Color, Element, Length, Renderer, Sandbox, Settings};
+use iced::{Color, Element, Font, Length, Renderer, Sandbox, Settings};
pub fn main() -> iced::Result {
env_logger::init();
@@ -127,6 +127,7 @@ impl Steps {
Step::TextInput {
value: String::new(),
is_secure: false,
+ is_showing_icon: false,
},
Step::Debugger,
Step::End,
@@ -171,14 +172,32 @@ impl Steps {
enum Step {
Welcome,
- Slider { value: u8 },
- RowsAndColumns { layout: Layout, spacing: u16 },
- Text { size: u16, color: Color },
- Radio { selection: Option<Language> },
- Toggler { can_continue: bool },
- Image { width: u16 },
+ Slider {
+ value: u8,
+ },
+ RowsAndColumns {
+ layout: Layout,
+ spacing: u16,
+ },
+ Text {
+ size: u16,
+ color: Color,
+ },
+ Radio {
+ selection: Option<Language>,
+ },
+ Toggler {
+ can_continue: bool,
+ },
+ Image {
+ width: u16,
+ },
Scrollable,
- TextInput { value: String, is_secure: bool },
+ TextInput {
+ value: String,
+ is_secure: bool,
+ is_showing_icon: bool,
+ },
Debugger,
End,
}
@@ -194,6 +213,7 @@ pub enum StepMessage {
ImageWidthChanged(u16),
InputChanged(String),
ToggleSecureInput(bool),
+ ToggleTextInputIcon(bool),
DebugToggled(bool),
TogglerChanged(bool),
}
@@ -256,6 +276,14 @@ impl<'a> Step {
*can_continue = value;
}
}
+ StepMessage::ToggleTextInputIcon(toggle) => {
+ if let Step::TextInput {
+ is_showing_icon, ..
+ } = self
+ {
+ *is_showing_icon = toggle
+ }
+ }
};
}
@@ -303,9 +331,11 @@ impl<'a> Step {
Self::rows_and_columns(*layout, *spacing)
}
Step::Scrollable => Self::scrollable(),
- Step::TextInput { value, is_secure } => {
- Self::text_input(value, *is_secure)
- }
+ Step::TextInput {
+ value,
+ is_secure,
+ is_showing_icon,
+ } => Self::text_input(value, *is_secure, *is_showing_icon),
Step::Debugger => Self::debugger(debug),
Step::End => Self::end(),
}
@@ -530,14 +560,25 @@ impl<'a> Step {
)
}
- fn text_input(value: &str, is_secure: bool) -> Column<'a, StepMessage> {
- let text_input = text_input(
- "Type something to continue...",
- value,
- StepMessage::InputChanged,
- )
- .padding(10)
- .size(30);
+ fn text_input(
+ value: &str,
+ is_secure: bool,
+ is_showing_icon: bool,
+ ) -> Column<'a, StepMessage> {
+ let mut text_input = text_input("Type something to continue...", value)
+ .on_input(StepMessage::InputChanged)
+ .padding(10)
+ .size(30);
+
+ if is_showing_icon {
+ text_input = text_input.icon(text_input::Icon {
+ font: Font::default(),
+ code_point: '🚀',
+ size: Some(28.0),
+ spacing: 10.0,
+ side: text_input::Side::Right,
+ });
+ }
Self::container("Text input")
.push("Use a text input to ask for different kinds of information.")
@@ -551,6 +592,11 @@ impl<'a> Step {
is_secure,
StepMessage::ToggleSecureInput,
))
+ .push(checkbox(
+ "Show icon",
+ is_showing_icon,
+ StepMessage::ToggleTextInputIcon,
+ ))
.push(
"A text input produces a message every time it changes. It is \
very easy to keep track of its contents:",
diff --git a/examples/websocket/src/echo.rs b/examples/websocket/src/echo.rs
index 122c20db..281ed4bd 100644
--- a/examples/websocket/src/echo.rs
+++ b/examples/websocket/src/echo.rs
@@ -13,63 +13,67 @@ use std::fmt;
pub fn connect() -> Subscription<Event> {
struct Connect;
- subscription::unfold(
+ subscription::channel(
std::any::TypeId::of::<Connect>(),
- State::Disconnected,
- |state| async move {
- match state {
- State::Disconnected => {
- const ECHO_SERVER: &str = "ws://localhost:3030";
-
- match async_tungstenite::tokio::connect_async(ECHO_SERVER)
+ 100,
+ |mut output| async move {
+ let mut state = State::Disconnected;
+
+ loop {
+ match &mut state {
+ State::Disconnected => {
+ const ECHO_SERVER: &str = "ws://127.0.0.1:3030";
+
+ match async_tungstenite::tokio::connect_async(
+ ECHO_SERVER,
+ )
.await
- {
- Ok((websocket, _)) => {
- let (sender, receiver) = mpsc::channel(100);
-
- (
- Some(Event::Connected(Connection(sender))),
- State::Connected(websocket, receiver),
- )
- }
- Err(_) => {
- tokio::time::sleep(
- tokio::time::Duration::from_secs(1),
- )
- .await;
+ {
+ Ok((websocket, _)) => {
+ let (sender, receiver) = mpsc::channel(100);
+
+ let _ = output
+ .send(Event::Connected(Connection(sender)))
+ .await;
- (Some(Event::Disconnected), State::Disconnected)
+ state = State::Connected(websocket, receiver);
+ }
+ Err(_) => {
+ tokio::time::sleep(
+ tokio::time::Duration::from_secs(1),
+ )
+ .await;
+
+ let _ = output.send(Event::Disconnected).await;
+ }
}
}
- }
- State::Connected(mut websocket, mut input) => {
- let mut fused_websocket = websocket.by_ref().fuse();
-
- futures::select! {
- received = fused_websocket.select_next_some() => {
- match received {
- Ok(tungstenite::Message::Text(message)) => {
- (
- Some(Event::MessageReceived(Message::User(message))),
- State::Connected(websocket, input)
- )
- }
- Ok(_) => {
- (None, State::Connected(websocket, input))
- }
- Err(_) => {
- (Some(Event::Disconnected), State::Disconnected)
+ State::Connected(websocket, input) => {
+ let mut fused_websocket = websocket.by_ref().fuse();
+
+ futures::select! {
+ received = fused_websocket.select_next_some() => {
+ match received {
+ Ok(tungstenite::Message::Text(message)) => {
+ let _ = output.send(Event::MessageReceived(Message::User(message))).await;
+ }
+ Err(_) => {
+ let _ = output.send(Event::Disconnected).await;
+
+ state = State::Disconnected;
+ }
+ Ok(_) => continue,
}
}
- }
- message = input.select_next_some() => {
- let result = websocket.send(tungstenite::Message::Text(message.to_string())).await;
+ message = input.select_next_some() => {
+ let result = websocket.send(tungstenite::Message::Text(message.to_string())).await;
+
+ if result.is_err() {
+ let _ = output.send(Event::Disconnected).await;
- if result.is_ok() {
- (None, State::Connected(websocket, input))
- } else {
- (Some(Event::Disconnected), State::Disconnected)
+ state = State::Disconnected;
+ }
}
}
}
diff --git a/examples/websocket/src/main.rs b/examples/websocket/src/main.rs
index e617b8ce..920189f5 100644
--- a/examples/websocket/src/main.rs
+++ b/examples/websocket/src/main.rs
@@ -125,12 +125,9 @@ impl Application for WebSocket {
};
let new_message_input = {
- let mut input = text_input(
- "Type a message...",
- &self.new_message,
- Message::NewMessageChanged,
- )
- .padding(10);
+ let mut input = text_input("Type a message...", &self.new_message)
+ .on_input(Message::NewMessageChanged)
+ .padding(10);
let mut button = button(
text("Send")
diff --git a/futures/Cargo.toml b/futures/Cargo.toml
index 411e7c2a..f636a304 100644
--- a/futures/Cargo.toml
+++ b/futures/Cargo.toml
@@ -17,7 +17,7 @@ thread-pool = ["futures/thread-pool"]
log = "0.4"
[dependencies.iced_core]
-version = "0.8"
+version = "0.9"
path = "../core"
[dependencies.futures]
diff --git a/futures/src/subscription.rs b/futures/src/subscription.rs
index 876f29c2..801c2694 100644
--- a/futures/src/subscription.rs
+++ b/futures/src/subscription.rs
@@ -9,6 +9,8 @@ use crate::core::Hasher;
use crate::futures::{Future, Stream};
use crate::{BoxStream, MaybeSend};
+use futures::channel::mpsc;
+use futures::never::Never;
use std::hash::Hash;
/// A stream of runtime events.
@@ -126,9 +128,9 @@ impl<Message> std::fmt::Debug for Subscription<Message> {
/// - [`stopwatch`], a watch with start/stop and reset buttons showcasing how
/// to listen to time.
///
-/// [examples]: https://github.com/iced-rs/iced/tree/0.8/examples
-/// [`download_progress`]: https://github.com/iced-rs/iced/tree/0.8/examples/download_progress
-/// [`stopwatch`]: https://github.com/iced-rs/iced/tree/0.8/examples/stopwatch
+/// [examples]: https://github.com/iced-rs/iced/tree/0.9/examples
+/// [`download_progress`]: https://github.com/iced-rs/iced/tree/0.9/examples/download_progress
+/// [`stopwatch`]: https://github.com/iced-rs/iced/tree/0.9/examples/stopwatch
pub trait Recipe {
/// The events that will be produced by a [`Subscription`] with this
/// [`Recipe`].
@@ -317,6 +319,27 @@ where
/// [`Stream`] that will call the provided closure to produce every `Message`.
///
/// The `id` will be used to uniquely identify the [`Subscription`].
+pub fn unfold<I, T, Fut, Message>(
+ id: I,
+ initial: T,
+ mut f: impl FnMut(T) -> Fut + MaybeSend + Sync + 'static,
+) -> Subscription<Message>
+where
+ I: Hash + 'static,
+ T: MaybeSend + 'static,
+ Fut: Future<Output = (Message, T)> + MaybeSend + 'static,
+ Message: 'static + MaybeSend,
+{
+ use futures::future::FutureExt;
+
+ run_with_id(
+ id,
+ futures::stream::unfold(initial, move |state| f(state).map(Some)),
+ )
+}
+
+/// Creates a [`Subscription`] that publishes the events sent from a [`Future`]
+/// to an [`mpsc::Sender`] with the given bounds.
///
/// # Creating an asynchronous worker with bidirectional communication
/// You can leverage this helper to create a [`Subscription`] that spawns
@@ -328,9 +351,8 @@ where
///
/// ```
/// use iced_futures::subscription::{self, Subscription};
-/// use iced_futures::futures;
-///
-/// use futures::channel::mpsc;
+/// use iced_futures::futures::channel::mpsc;
+/// use iced_futures::futures::sink::SinkExt;
///
/// pub enum Event {
/// Ready(mpsc::Sender<Input>),
@@ -351,27 +373,35 @@ where
/// fn some_worker() -> Subscription<Event> {
/// struct SomeWorker;
///
-/// subscription::unfold(std::any::TypeId::of::<SomeWorker>(), State::Starting, |state| async move {
-/// match state {
-/// State::Starting => {
-/// // Create channel
-/// let (sender, receiver) = mpsc::channel(100);
+/// subscription::channel(std::any::TypeId::of::<SomeWorker>(), 100, |mut output| async move {
+/// let mut state = State::Starting;
///
-/// (Some(Event::Ready(sender)), State::Ready(receiver))
-/// }
-/// State::Ready(mut receiver) => {
-/// use futures::StreamExt;
+/// loop {
+/// match &mut state {
+/// State::Starting => {
+/// // Create channel
+/// let (sender, receiver) = mpsc::channel(100);
+///
+/// // Send the sender back to the application
+/// output.send(Event::Ready(sender)).await;
+///
+/// // We are ready to receive messages
+/// state = State::Ready(receiver);
+/// }
+/// State::Ready(receiver) => {
+/// use iced_futures::futures::StreamExt;
///
-/// // Read next input sent from `Application`
-/// let input = receiver.select_next_some().await;
+/// // Read next input sent from `Application`
+/// let input = receiver.select_next_some().await;
///
-/// match input {
-/// Input::DoSomeWork => {
-/// // Do some async work...
+/// match input {
+/// Input::DoSomeWork => {
+/// // Do some async work...
///
-/// // Finally, we can optionally return a message to tell the
-/// // `Application` the work is done
-/// (Some(Event::WorkFinished), State::Ready(receiver))
+/// // Finally, we can optionally produce a message to tell the
+/// // `Application` the work is done
+/// output.send(Event::WorkFinished).await;
+/// }
/// }
/// }
/// }
@@ -383,26 +413,29 @@ where
/// Check out the [`websocket`] example, which showcases this pattern to maintain a WebSocket
/// connection open.
///
-/// [`websocket`]: https://github.com/iced-rs/iced/tree/0.8/examples/websocket
-pub fn unfold<I, T, Fut, Message>(
+/// [`websocket`]: https://github.com/iced-rs/iced/tree/0.9/examples/websocket
+pub fn channel<I, Fut, Message>(
id: I,
- initial: T,
- mut f: impl FnMut(T) -> Fut + MaybeSend + Sync + 'static,
+ size: usize,
+ f: impl Fn(mpsc::Sender<Message>) -> Fut + MaybeSend + Sync + 'static,
) -> Subscription<Message>
where
I: Hash + 'static,
- T: MaybeSend + 'static,
- Fut: Future<Output = (Option<Message>, T)> + MaybeSend + 'static,
+ Fut: Future<Output = Never> + MaybeSend + 'static,
Message: 'static + MaybeSend,
{
- use futures::future::{self, FutureExt};
- use futures::stream::StreamExt;
+ use futures::stream::{self, StreamExt};
- run_with_id(
+ Subscription::from_recipe(Runner {
id,
- futures::stream::unfold(initial, move |state| f(state).map(Some))
- .filter_map(future::ready),
- )
+ spawn: move |_| {
+ let (sender, receiver) = mpsc::channel(size);
+
+ let runner = stream::once(f(sender)).map(|_| unreachable!());
+
+ stream::select(receiver, runner)
+ },
+ })
}
struct Runner<I, F, S, Message>
diff --git a/graphics/Cargo.toml b/graphics/Cargo.toml
index a08c6707..f1ce6b3a 100644
--- a/graphics/Cargo.toml
+++ b/graphics/Cargo.toml
@@ -1,6 +1,6 @@
[package]
name = "iced_graphics"
-version = "0.7.0"
+version = "0.8.0"
authors = ["Héctor Ramón Jiménez <hector0193@gmail.com>"]
edition = "2021"
description = "A bunch of backend-agnostic types that can be leveraged to build a renderer for Iced"
@@ -27,7 +27,7 @@ version = "1.4"
features = ["derive"]
[dependencies.iced_core]
-version = "0.8"
+version = "0.9"
path = "../core"
[dependencies.tiny-skia]
diff --git a/renderer/Cargo.toml b/renderer/Cargo.toml
index 629c11ba..17657888 100644
--- a/renderer/Cargo.toml
+++ b/renderer/Cargo.toml
@@ -16,11 +16,11 @@ raw-window-handle = "0.5"
thiserror = "1"
[dependencies.iced_graphics]
-version = "0.7"
+version = "0.8"
path = "../graphics"
[dependencies.iced_wgpu]
-version = "0.9"
+version = "0.10"
path = "../wgpu"
optional = true
diff --git a/runtime/Cargo.toml b/runtime/Cargo.toml
index 2d3e8db3..a65f07f2 100644
--- a/runtime/Cargo.toml
+++ b/runtime/Cargo.toml
@@ -1,6 +1,6 @@
[package]
name = "iced_runtime"
-version = "0.9.1"
+version = "0.1.0"
authors = ["Héctor Ramón Jiménez <hector0193@gmail.com>"]
edition = "2021"
description = "A renderer-agnostic library for native GUIs"
@@ -14,7 +14,7 @@ debug = []
thiserror = "1"
[dependencies.iced_core]
-version = "0.8"
+version = "0.9"
path = "../core"
[dependencies.iced_futures]
diff --git a/runtime/README.md b/runtime/README.md
index 497fd145..1b0fa857 100644
--- a/runtime/README.md
+++ b/runtime/README.md
@@ -12,13 +12,6 @@
[`druid`]: https://github.com/xi-editor/druid
[`raw-window-handle`]: https://github.com/rust-windowing/raw-window-handle
-## Installation
-Add `iced_runtime` as a dependency in your `Cargo.toml`:
-
-```toml
-iced_runtime = "0.9"
-```
-
__Iced moves fast and the `master` branch can contain breaking changes!__ If
you want to learn about a specific release, check out [the release list].
diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs
index aa45e57a..8a277e47 100644
--- a/runtime/src/lib.rs
+++ b/runtime/src/lib.rs
@@ -23,8 +23,8 @@
//! - Build a new renderer, see the [renderer] module.
//! - Build a custom widget, start at the [`Widget`] trait.
//!
-//! [`iced_core`]: https://github.com/iced-rs/iced/tree/0.8/core
-//! [`iced_winit`]: https://github.com/iced-rs/iced/tree/0.8/winit
+//! [`iced_core`]: https://github.com/iced-rs/iced/tree/0.9/core
+//! [`iced_winit`]: https://github.com/iced-rs/iced/tree/0.9/winit
//! [`druid`]: https://github.com/xi-editor/druid
//! [`raw-window-handle`]: https://github.com/rust-windowing/raw-window-handle
//! [renderer]: crate::renderer
diff --git a/runtime/src/user_interface.rs b/runtime/src/user_interface.rs
index 2c76fd8a..c29de7db 100644
--- a/runtime/src/user_interface.rs
+++ b/runtime/src/user_interface.rs
@@ -19,8 +19,8 @@ use crate::core::{Element, Layout, Shell};
/// The [`integration_opengl`] & [`integration_wgpu`] examples use a
/// [`UserInterface`] to integrate Iced in an existing graphical application.
///
-/// [`integration_opengl`]: https://github.com/iced-rs/iced/tree/0.8/examples/integration_opengl
-/// [`integration_wgpu`]: https://github.com/iced-rs/iced/tree/0.8/examples/integration_wgpu
+/// [`integration_opengl`]: https://github.com/iced-rs/iced/tree/0.9/examples/integration_opengl
+/// [`integration_wgpu`]: https://github.com/iced-rs/iced/tree/0.9/examples/integration_wgpu
#[allow(missing_debug_implementations)]
pub struct UserInterface<'a, Message, Renderer> {
root: Element<'a, Message, Renderer>,
diff --git a/runtime/src/window.rs b/runtime/src/window.rs
index 236064f7..833a1125 100644
--- a/runtime/src/window.rs
+++ b/runtime/src/window.rs
@@ -5,7 +5,7 @@ pub use action::Action;
use crate::command::{self, Command};
use crate::core::time::Instant;
-use crate::core::window::{Event, Mode, UserAttention};
+use crate::core::window::{Event, Icon, Mode, UserAttention};
use crate::futures::subscription::{self, Subscription};
/// Subscribes to the frames of the window of the running application.
@@ -110,3 +110,8 @@ pub fn fetch_id<Message>(
) -> Command<Message> {
Command::single(command::Action::Window(Action::FetchId(Box::new(f))))
}
+
+/// Changes the [`Icon`] of the window.
+pub fn change_icon<Message>(icon: Icon) -> Command<Message> {
+ Command::single(command::Action::Window(Action::ChangeIcon(icon)))
+}
diff --git a/runtime/src/window/action.rs b/runtime/src/window/action.rs
index c1dbd84f..83b71c75 100644
--- a/runtime/src/window/action.rs
+++ b/runtime/src/window/action.rs
@@ -1,4 +1,4 @@
-use crate::core::window::{Mode, UserAttention};
+use crate::core::window::{Icon, Mode, UserAttention};
use crate::futures::MaybeSend;
use std::fmt;
@@ -78,6 +78,21 @@ pub enum Action<T> {
ChangeAlwaysOnTop(bool),
/// Fetch an identifier unique to the window.
FetchId(Box<dyn FnOnce(u64) -> T + 'static>),
+ /// Changes the window [`Icon`].
+ ///
+ /// On Windows and X11, this is typically the small icon in the top-left
+ /// corner of the titlebar.
+ ///
+ /// ## Platform-specific
+ ///
+ /// - **Web / Wayland / macOS:** Unsupported.
+ ///
+ /// - **Windows:** Sets `ICON_SMALL`. The base size for a window icon is 16x16, but it's
+ /// recommended to account for screen scaling and pick a multiple of that, i.e. 32x32.
+ ///
+ /// - **X11:** Has no universal guidelines for icon sizes, so you're at the whims of the WM. That
+ /// said, it's usually in the same ballpark as on Windows.
+ ChangeIcon(Icon),
}
impl<T> Action<T> {
@@ -108,6 +123,7 @@ impl<T> Action<T> {
Action::ChangeAlwaysOnTop(on_top)
}
Self::FetchId(o) => Action::FetchId(Box::new(move |s| f(o(s)))),
+ Self::ChangeIcon(icon) => Action::ChangeIcon(icon),
}
}
}
@@ -142,6 +158,9 @@ impl<T> fmt::Debug for Action<T> {
write!(f, "Action::AlwaysOnTop({on_top})")
}
Self::FetchId(_) => write!(f, "Action::FetchId"),
+ Self::ChangeIcon(_icon) => {
+ write!(f, "Action::ChangeIcon(icon)")
+ }
}
}
}
diff --git a/src/application.rs b/src/application.rs
index c9ddf840..abf58fa3 100644
--- a/src/application.rs
+++ b/src/application.rs
@@ -39,15 +39,15 @@ pub use crate::style::application::{Appearance, StyleSheet};
/// to listen to time.
/// - [`todos`], a todos tracker inspired by [TodoMVC].
///
-/// [The repository has a bunch of examples]: https://github.com/iced-rs/iced/tree/0.8/examples
-/// [`clock`]: https://github.com/iced-rs/iced/tree/0.8/examples/clock
-/// [`download_progress`]: https://github.com/iced-rs/iced/tree/0.8/examples/download_progress
-/// [`events`]: https://github.com/iced-rs/iced/tree/0.8/examples/events
-/// [`game_of_life`]: https://github.com/iced-rs/iced/tree/0.8/examples/game_of_life
-/// [`pokedex`]: https://github.com/iced-rs/iced/tree/0.8/examples/pokedex
-/// [`solar_system`]: https://github.com/iced-rs/iced/tree/0.8/examples/solar_system
-/// [`stopwatch`]: https://github.com/iced-rs/iced/tree/0.8/examples/stopwatch
-/// [`todos`]: https://github.com/iced-rs/iced/tree/0.8/examples/todos
+/// [The repository has a bunch of examples]: https://github.com/iced-rs/iced/tree/0.9/examples
+/// [`clock`]: https://github.com/iced-rs/iced/tree/0.9/examples/clock
+/// [`download_progress`]: https://github.com/iced-rs/iced/tree/0.9/examples/download_progress
+/// [`events`]: https://github.com/iced-rs/iced/tree/0.9/examples/events
+/// [`game_of_life`]: https://github.com/iced-rs/iced/tree/0.9/examples/game_of_life
+/// [`pokedex`]: https://github.com/iced-rs/iced/tree/0.9/examples/pokedex
+/// [`solar_system`]: https://github.com/iced-rs/iced/tree/0.9/examples/solar_system
+/// [`stopwatch`]: https://github.com/iced-rs/iced/tree/0.9/examples/stopwatch
+/// [`todos`]: https://github.com/iced-rs/iced/tree/0.9/examples/todos
/// [`Sandbox`]: crate::Sandbox
/// [`Canvas`]: crate::widget::Canvas
/// [PokéAPI]: https://pokeapi.co/
diff --git a/src/lib.rs b/src/lib.rs
index 26d1a358..c73cc48d 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -24,13 +24,13 @@
//! [scrollables]: https://gfycat.com/perkybaggybaboon-rust-gui
//! [Debug overlay with performance metrics]: https://gfycat.com/incredibledarlingbee
//! [Modular ecosystem]: https://github.com/iced-rs/iced/blob/master/ECOSYSTEM.md
-//! [renderer-agnostic native runtime]: https://github.com/iced-rs/iced/tree/0.8/native
+//! [renderer-agnostic native runtime]: https://github.com/iced-rs/iced/tree/0.9/native
//! [`wgpu`]: https://github.com/gfx-rs/wgpu-rs
-//! [built-in renderer]: https://github.com/iced-rs/iced/tree/0.8/wgpu
-//! [windowing shell]: https://github.com/iced-rs/iced/tree/0.8/winit
+//! [built-in renderer]: https://github.com/iced-rs/iced/tree/0.9/wgpu
+//! [windowing shell]: https://github.com/iced-rs/iced/tree/0.9/winit
//! [`dodrio`]: https://github.com/fitzgen/dodrio
//! [web runtime]: https://github.com/iced-rs/iced_web
-//! [examples]: https://github.com/iced-rs/iced/tree/0.8/examples
+//! [examples]: https://github.com/iced-rs/iced/tree/0.9/examples
//! [repository]: https://github.com/iced-rs/iced
//!
//! # Overview
@@ -235,7 +235,7 @@ pub mod mouse {
pub mod subscription {
//! Listen to external events in your application.
pub use iced_futures::subscription::{
- events, events_with, run, run_with_id, unfold, Subscription,
+ channel, events, events_with, run, run_with_id, unfold, Subscription,
};
}
diff --git a/src/sandbox.rs b/src/sandbox.rs
index e8ed0f81..cca327b6 100644
--- a/src/sandbox.rs
+++ b/src/sandbox.rs
@@ -34,19 +34,19 @@ use crate::{Application, Command, Element, Error, Settings, Subscription};
/// - [`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/iced-rs/iced/tree/0.8/examples
-/// [`bezier_tool`]: https://github.com/iced-rs/iced/tree/0.8/examples/bezier_tool
-/// [`counter`]: https://github.com/iced-rs/iced/tree/0.8/examples/counter
-/// [`custom_widget`]: https://github.com/iced-rs/iced/tree/0.8/examples/custom_widget
-/// [`geometry`]: https://github.com/iced-rs/iced/tree/0.8/examples/geometry
-/// [`pane_grid`]: https://github.com/iced-rs/iced/tree/0.8/examples/pane_grid
-/// [`progress_bar`]: https://github.com/iced-rs/iced/tree/0.8/examples/progress_bar
-/// [`styling`]: https://github.com/iced-rs/iced/tree/0.8/examples/styling
-/// [`svg`]: https://github.com/iced-rs/iced/tree/0.8/examples/svg
-/// [`tour`]: https://github.com/iced-rs/iced/tree/0.8/examples/tour
+/// [The repository has a bunch of examples]: https://github.com/iced-rs/iced/tree/0.9/examples
+/// [`bezier_tool`]: https://github.com/iced-rs/iced/tree/0.9/examples/bezier_tool
+/// [`counter`]: https://github.com/iced-rs/iced/tree/0.9/examples/counter
+/// [`custom_widget`]: https://github.com/iced-rs/iced/tree/0.9/examples/custom_widget
+/// [`geometry`]: https://github.com/iced-rs/iced/tree/0.9/examples/geometry
+/// [`pane_grid`]: https://github.com/iced-rs/iced/tree/0.9/examples/pane_grid
+/// [`progress_bar`]: https://github.com/iced-rs/iced/tree/0.9/examples/progress_bar
+/// [`styling`]: https://github.com/iced-rs/iced/tree/0.9/examples/styling
+/// [`svg`]: https://github.com/iced-rs/iced/tree/0.9/examples/svg
+/// [`tour`]: https://github.com/iced-rs/iced/tree/0.9/examples/tour
/// [`Canvas widget`]: crate::widget::Canvas
/// [the overview]: index.html#overview
-/// [`iced_wgpu`]: https://github.com/iced-rs/iced/tree/0.8/wgpu
+/// [`iced_wgpu`]: https://github.com/iced-rs/iced/tree/0.9/wgpu
/// [`Svg` widget]: crate::widget::Svg
/// [Ghostscript Tiger]: https://commons.wikimedia.org/wiki/File:Ghostscript_Tiger.svg
///
diff --git a/src/window/icon.rs b/src/window/icon.rs
index 659d2b64..b67b2ea3 100644
--- a/src/window/icon.rs
+++ b/src/window/icon.rs
@@ -1,175 +1,66 @@
//! Attach an icon to the window of your application.
-use std::fmt;
+pub use crate::core::window::icon::*;
+
+use crate::core::window::icon;
+
use std::io;
-#[cfg(feature = "image_rs")]
+#[cfg(feature = "image")]
use std::path::Path;
-/// The icon of a window.
-#[derive(Clone)]
-pub struct Icon(iced_winit::winit::window::Icon);
+/// Creates an icon from an image file.
+///
+/// This will return an error in case the file is missing at run-time. You may prefer [`Self::from_file_data`] instead.
+#[cfg(feature = "image")]
+#[cfg_attr(docsrs, doc(cfg(feature = "image")))]
+pub fn from_file<P: AsRef<Path>>(icon_path: P) -> Result<Icon, Error> {
+ let icon = image_rs::io::Reader::open(icon_path)?.decode()?.to_rgba8();
-impl fmt::Debug for Icon {
- fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
- f.debug_tuple("Icon").field(&format_args!("_")).finish()
- }
+ Ok(icon::from_rgba(icon.to_vec(), icon.width(), icon.height())?)
}
-impl Icon {
- /// Creates an icon from 32bpp RGBA data.
- pub fn from_rgba(
- rgba: Vec<u8>,
- width: u32,
- height: u32,
- ) -> Result<Self, Error> {
- let raw =
- iced_winit::winit::window::Icon::from_rgba(rgba, width, height)?;
-
- Ok(Icon(raw))
- }
-
- /// Creates an icon from an image file.
- ///
- /// This will return an error in case the file is missing at run-time. You may prefer [`Self::from_file_data`] instead.
- #[cfg(feature = "image_rs")]
- pub fn from_file<P: AsRef<Path>>(icon_path: P) -> Result<Self, Error> {
- let icon = image_rs::io::Reader::open(icon_path)?.decode()?.to_rgba8();
-
- Self::from_rgba(icon.to_vec(), icon.width(), icon.height())
- }
-
- /// Creates an icon from the content of an image file.
- ///
- /// This content can be included in your application at compile-time, e.g. using the `include_bytes!` macro. \
- /// You can pass an explicit file format. Otherwise, the file format will be guessed at runtime.
- #[cfg(feature = "image_rs")]
- pub fn from_file_data(
- data: &[u8],
- explicit_format: Option<image_rs::ImageFormat>,
- ) -> Result<Self, Error> {
- let mut icon = image_rs::io::Reader::new(std::io::Cursor::new(data));
- let icon_with_format = match explicit_format {
- Some(format) => {
- icon.set_format(format);
- icon
- }
- None => icon.with_guessed_format()?,
- };
+/// Creates an icon from the content of an image file.
+///
+/// This content can be included in your application at compile-time, e.g. using the `include_bytes!` macro.
+/// You can pass an explicit file format. Otherwise, the file format will be guessed at runtime.
+#[cfg(feature = "image")]
+#[cfg_attr(docsrs, doc(cfg(feature = "image")))]
+pub fn from_file_data(
+ data: &[u8],
+ explicit_format: Option<image_rs::ImageFormat>,
+) -> Result<Icon, Error> {
+ let mut icon = image_rs::io::Reader::new(std::io::Cursor::new(data));
+ let icon_with_format = match explicit_format {
+ Some(format) => {
+ icon.set_format(format);
+ icon
+ }
+ None => icon.with_guessed_format()?,
+ };
- let pixels = icon_with_format.decode()?.to_rgba8();
+ let pixels = icon_with_format.decode()?.to_rgba8();
- Self::from_rgba(pixels.to_vec(), pixels.width(), pixels.height())
- }
+ Ok(icon::from_rgba(
+ pixels.to_vec(),
+ pixels.width(),
+ pixels.height(),
+ )?)
}
-/// An error produced when using `Icon::from_rgba` with invalid arguments.
-#[derive(Debug)]
+/// An error produced when creating an [`Icon`].
+#[derive(Debug, thiserror::Error)]
pub enum Error {
- /// The provided RGBA data isn't divisble by 4.
- ///
- /// Therefore, it cannot be safely interpreted as 32bpp RGBA pixels.
- InvalidData {
- /// The length of the provided RGBA data.
- byte_count: usize,
- },
-
- /// The number of RGBA pixels does not match the provided dimensions.
- DimensionsMismatch {
- /// The provided width.
- width: u32,
- /// The provided height.
- height: u32,
- /// The amount of pixels of the provided RGBA data.
- pixel_count: usize,
- },
+ /// The [`Icon`] is not valid.
+ #[error("The icon is invalid: {0}")]
+ InvalidError(#[from] icon::Error),
/// The underlying OS failed to create the icon.
- OsError(io::Error),
-
- /// The `image` crate reported an error
- #[cfg(feature = "image_rs")]
- ImageError(image_rs::error::ImageError),
-}
-
-impl From<std::io::Error> for Error {
- fn from(os_error: std::io::Error) -> Self {
- Error::OsError(os_error)
- }
-}
-
-impl From<iced_winit::winit::window::BadIcon> for Error {
- fn from(error: iced_winit::winit::window::BadIcon) -> Self {
- use iced_winit::winit::window::BadIcon;
-
- match error {
- BadIcon::ByteCountNotDivisibleBy4 { byte_count } => {
- Error::InvalidData { byte_count }
- }
- BadIcon::DimensionsVsPixelCount {
- width,
- height,
- pixel_count,
- ..
- } => Error::DimensionsMismatch {
- width,
- height,
- pixel_count,
- },
- BadIcon::OsError(os_error) => Error::OsError(os_error),
- }
- }
-}
-
-impl From<Icon> for iced_winit::winit::window::Icon {
- fn from(icon: Icon) -> Self {
- icon.0
- }
-}
-
-#[cfg(feature = "image_rs")]
-impl From<image_rs::error::ImageError> for Error {
- fn from(image_error: image_rs::error::ImageError) -> Self {
- Self::ImageError(image_error)
- }
-}
-
-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 {byte_count:?}) isn't divisble by \
- 4. Therefore, it cannot be safely interpreted as 32bpp RGBA \
- pixels."
- )
- }
- Error::DimensionsMismatch {
- width,
- height,
- pixel_count,
- } => {
- write!(
- f,
- "The number of RGBA pixels ({pixel_count:?}) does not match the provided \
- dimensions ({width:?}x{height:?})."
- )
- }
- Error::OsError(e) => write!(
- f,
- "The underlying OS failed to create the window \
- icon: {e:?}"
- ),
- #[cfg(feature = "image_rs")]
- Error::ImageError(e) => {
- write!(f, "Unable to create icon from a file: {e:?}")
- }
- }
- }
-}
-
-impl std::error::Error for Error {
- fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
- Some(self)
- }
+ #[error("The underlying OS failted to create the window icon: {0}")]
+ OsError(#[from] io::Error),
+
+ /// The `image` crate reported an error.
+ #[cfg(feature = "image")]
+ #[cfg_attr(docsrs, doc(cfg(feature = "image")))]
+ #[error("Unable to create icon from a file: {0}")]
+ ImageError(#[from] image_rs::error::ImageError),
}
diff --git a/style/Cargo.toml b/style/Cargo.toml
index 3b54f1ec..0bb354e0 100644
--- a/style/Cargo.toml
+++ b/style/Cargo.toml
@@ -1,6 +1,6 @@
[package]
name = "iced_style"
-version = "0.7.0"
+version = "0.8.0"
authors = ["Héctor Ramón Jiménez <hector0193@gmail.com>"]
edition = "2021"
description = "The default set of styles of Iced"
@@ -11,7 +11,7 @@ keywords = ["gui", "ui", "graphics", "interface", "widgets"]
categories = ["gui"]
[dependencies.iced_core]
-version = "0.8"
+version = "0.9"
path = "../core"
features = ["palette"]
diff --git a/style/src/scrollable.rs b/style/src/scrollable.rs
index 64ed8462..b528c444 100644
--- a/style/src/scrollable.rs
+++ b/style/src/scrollable.rs
@@ -37,12 +37,16 @@ pub trait StyleSheet {
/// Produces the style of an active scrollbar.
fn active(&self, style: &Self::Style) -> Scrollbar;
- /// Produces the style of a hovered scrollbar.
- fn hovered(&self, style: &Self::Style) -> Scrollbar;
+ /// Produces the style of a scrollbar when the scrollable is being hovered.
+ fn hovered(
+ &self,
+ style: &Self::Style,
+ is_mouse_over_scrollbar: bool,
+ ) -> Scrollbar;
/// Produces the style of a scrollbar that is being dragged.
fn dragging(&self, style: &Self::Style) -> Scrollbar {
- self.hovered(style)
+ self.hovered(style, true)
}
/// Produces the style of an active horizontal scrollbar.
@@ -50,13 +54,17 @@ pub trait StyleSheet {
self.active(style)
}
- /// Produces the style of a hovered horizontal scrollbar.
- fn hovered_horizontal(&self, style: &Self::Style) -> Scrollbar {
- self.hovered(style)
+ /// Produces the style of a horizontal scrollbar when the scrollable is being hovered.
+ fn hovered_horizontal(
+ &self,
+ style: &Self::Style,
+ is_mouse_over_scrollbar: bool,
+ ) -> Scrollbar {
+ self.hovered(style, is_mouse_over_scrollbar)
}
/// Produces the style of a horizontal scrollbar that is being dragged.
fn dragging_horizontal(&self, style: &Self::Style) -> Scrollbar {
- self.hovered_horizontal(style)
+ self.hovered_horizontal(style, true)
}
}
diff --git a/style/src/slider.rs b/style/src/slider.rs
index 4b52fad3..884d3871 100644
--- a/style/src/slider.rs
+++ b/style/src/slider.rs
@@ -5,11 +5,20 @@ use iced_core::Color;
#[derive(Debug, Clone, Copy)]
pub struct Appearance {
/// The colors of the rail of the slider.
- pub rail_colors: (Color, Color),
+ pub rail: Rail,
/// The appearance of the [`Handle`] of the slider.
pub handle: Handle,
}
+/// The appearance of a slider rail
+#[derive(Debug, Clone, Copy)]
+pub struct Rail {
+ /// The colors of the rail of the slider.
+ pub colors: (Color, Color),
+ /// The width of the stroke of a slider rail.
+ pub width: f32,
+}
+
/// The appearance of the handle of a slider.
#[derive(Debug, Clone, Copy)]
pub struct Handle {
diff --git a/style/src/text_input.rs b/style/src/text_input.rs
index d97016dc..2616ad5a 100644
--- a/style/src/text_input.rs
+++ b/style/src/text_input.rs
@@ -12,6 +12,8 @@ pub struct Appearance {
pub border_width: f32,
/// The border [`Color`] of the text input.
pub border_color: Color,
+ /// The icon [`Color`] of the text input.
+ pub icon_color: Color,
}
/// A set of rules that dictate the style of a text input.
@@ -31,6 +33,9 @@ pub trait StyleSheet {
/// Produces the [`Color`] of the value of a text input.
fn value_color(&self, style: &Self::Style) -> Color;
+ /// Produces the [`Color`] of the value of a disabled text input.
+ fn disabled_color(&self, style: &Self::Style) -> Color;
+
/// Produces the [`Color`] of the selection of a text input.
fn selection_color(&self, style: &Self::Style) -> Color;
@@ -38,4 +43,7 @@ pub trait StyleSheet {
fn hovered(&self, style: &Self::Style) -> Appearance {
self.focused(style)
}
+
+ /// Produces the style of a disabled text input.
+ fn disabled(&self, style: &Self::Style) -> Appearance;
}
diff --git a/style/src/theme.rs b/style/src/theme.rs
index b507d096..d9893bcf 100644
--- a/style/src/theme.rs
+++ b/style/src/theme.rs
@@ -416,10 +416,13 @@ impl slider::StyleSheet for Theme {
};
slider::Appearance {
- rail_colors: (
- palette.primary.base.color,
- Color::TRANSPARENT,
- ),
+ rail: slider::Rail {
+ colors: (
+ palette.primary.base.color,
+ palette.primary.base.color,
+ ),
+ width: 2.0,
+ },
handle: slider::Handle {
color: palette.background.base.color,
border_color: palette.primary.base.color,
@@ -906,31 +909,41 @@ impl scrollable::StyleSheet for Theme {
}
}
- fn hovered(&self, style: &Self::Style) -> scrollable::Scrollbar {
+ fn hovered(
+ &self,
+ style: &Self::Style,
+ is_mouse_over_scrollbar: bool,
+ ) -> scrollable::Scrollbar {
match style {
Scrollable::Default => {
- let palette = self.extended_palette();
+ if is_mouse_over_scrollbar {
+ let palette = self.extended_palette();
- scrollable::Scrollbar {
- background: palette.background.weak.color.into(),
- border_radius: 2.0,
- border_width: 0.0,
- border_color: Color::TRANSPARENT,
- scroller: scrollable::Scroller {
- color: palette.primary.strong.color,
+ scrollable::Scrollbar {
+ background: palette.background.weak.color.into(),
border_radius: 2.0,
border_width: 0.0,
border_color: Color::TRANSPARENT,
- },
+ scroller: scrollable::Scroller {
+ color: palette.primary.strong.color,
+ border_radius: 2.0,
+ border_width: 0.0,
+ border_color: Color::TRANSPARENT,
+ },
+ }
+ } else {
+ self.active(style)
}
}
- Scrollable::Custom(custom) => custom.hovered(self),
+ Scrollable::Custom(custom) => {
+ custom.hovered(self, is_mouse_over_scrollbar)
+ }
}
}
fn dragging(&self, style: &Self::Style) -> scrollable::Scrollbar {
match style {
- Scrollable::Default => self.hovered(style),
+ Scrollable::Default => self.hovered(style, true),
Scrollable::Custom(custom) => custom.dragging(self),
}
}
@@ -942,10 +955,16 @@ impl scrollable::StyleSheet for Theme {
}
}
- fn hovered_horizontal(&self, style: &Self::Style) -> scrollable::Scrollbar {
+ fn hovered_horizontal(
+ &self,
+ style: &Self::Style,
+ is_mouse_over_scrollbar: bool,
+ ) -> scrollable::Scrollbar {
match style {
- Scrollable::Default => self.hovered(style),
- Scrollable::Custom(custom) => custom.hovered_horizontal(self),
+ Scrollable::Default => self.hovered(style, is_mouse_over_scrollbar),
+ Scrollable::Custom(custom) => {
+ custom.hovered_horizontal(self, is_mouse_over_scrollbar)
+ }
}
}
@@ -954,7 +973,7 @@ impl scrollable::StyleSheet for Theme {
style: &Self::Style,
) -> scrollable::Scrollbar {
match style {
- Scrollable::Default => self.hovered_horizontal(style),
+ Scrollable::Default => self.hovered_horizontal(style, true),
Scrollable::Custom(custom) => custom.dragging_horizontal(self),
}
}
@@ -1012,6 +1031,7 @@ impl text_input::StyleSheet for Theme {
border_radius: 2.0,
border_width: 1.0,
border_color: palette.background.strong.color,
+ icon_color: palette.background.weak.text,
}
}
@@ -1027,6 +1047,7 @@ impl text_input::StyleSheet for Theme {
border_radius: 2.0,
border_width: 1.0,
border_color: palette.background.base.text,
+ icon_color: palette.background.weak.text,
}
}
@@ -1042,6 +1063,7 @@ impl text_input::StyleSheet for Theme {
border_radius: 2.0,
border_width: 1.0,
border_color: palette.primary.strong.color,
+ icon_color: palette.background.weak.text,
}
}
@@ -1074,4 +1096,28 @@ impl text_input::StyleSheet for Theme {
palette.primary.weak.color
}
+
+ fn disabled(&self, style: &Self::Style) -> text_input::Appearance {
+ if let TextInput::Custom(custom) = style {
+ return custom.disabled(self);
+ }
+
+ let palette = self.extended_palette();
+
+ text_input::Appearance {
+ background: palette.background.weak.color.into(),
+ border_radius: 2.0,
+ border_width: 1.0,
+ border_color: palette.background.strong.color,
+ icon_color: palette.background.strong.color,
+ }
+ }
+
+ fn disabled_color(&self, style: &Self::Style) -> Color {
+ if let TextInput::Custom(custom) = style {
+ return custom.disabled_color(self);
+ }
+
+ self.placeholder_color(style)
+ }
}
diff --git a/tiny_skia/Cargo.toml b/tiny_skia/Cargo.toml
index 7a5f5b17..f629dab9 100644
--- a/tiny_skia/Cargo.toml
+++ b/tiny_skia/Cargo.toml
@@ -19,7 +19,7 @@ kurbo = "0.9"
log = "0.4"
[dependencies.iced_graphics]
-version = "0.7"
+version = "0.8"
path = "../graphics"
features = ["tiny-skia"]
diff --git a/wgpu/Cargo.toml b/wgpu/Cargo.toml
index 36c891bb..4559040e 100644
--- a/wgpu/Cargo.toml
+++ b/wgpu/Cargo.toml
@@ -1,6 +1,6 @@
[package]
name = "iced_wgpu"
-version = "0.9.0"
+version = "0.10.0"
authors = ["Héctor Ramón Jiménez <hector0193@gmail.com>"]
edition = "2021"
description = "A wgpu renderer for Iced"
@@ -38,7 +38,7 @@ version = "1.9"
features = ["derive"]
[dependencies.iced_graphics]
-version = "0.7"
+version = "0.8"
path = "../graphics"
[dependencies.glyphon]
diff --git a/wgpu/README.md b/wgpu/README.md
index 3e6af103..f8c88374 100644
--- a/wgpu/README.md
+++ b/wgpu/README.md
@@ -30,7 +30,7 @@ Currently, `iced_wgpu` supports the following primitives:
Add `iced_wgpu` as a dependency in your `Cargo.toml`:
```toml
-iced_wgpu = "0.9"
+iced_wgpu = "0.10"
```
__Iced moves fast and the `master` branch can contain breaking changes!__ If
diff --git a/wgpu/src/image.rs b/wgpu/src/image.rs
index 4163e338..263bcfa2 100644
--- a/wgpu/src/image.rs
+++ b/wgpu/src/image.rs
@@ -205,7 +205,7 @@ impl Pipeline {
let shader =
device.create_shader_module(wgpu::ShaderModuleDescriptor {
- label: Some("iced_wgpu::image::shader"),
+ label: Some("iced_wgpu image shader"),
source: wgpu::ShaderSource::Wgsl(std::borrow::Cow::Borrowed(
include_str!("shader/image.wgsl"),
)),
diff --git a/wgpu/src/lib.rs b/wgpu/src/lib.rs
index 473f3621..0b169140 100644
--- a/wgpu/src/lib.rs
+++ b/wgpu/src/lib.rs
@@ -16,7 +16,7 @@
//! - Meshes of triangles, useful to draw geometry freely.
//!
//! [Iced]: https://github.com/iced-rs/iced
-//! [`iced_native`]: https://github.com/iced-rs/iced/tree/0.8/native
+//! [`iced_native`]: https://github.com/iced-rs/iced/tree/0.9/native
//! [`wgpu`]: https://github.com/gfx-rs/wgpu-rs
//! [WebGPU API]: https://gpuweb.github.io/gpuweb/
//! [`wgpu_glyph`]: https://github.com/hecrj/wgpu_glyph
diff --git a/wgpu/src/quad.rs b/wgpu/src/quad.rs
index b55216d7..8fa7359e 100644
--- a/wgpu/src/quad.rs
+++ b/wgpu/src/quad.rs
@@ -48,7 +48,7 @@ impl Pipeline {
let shader =
device.create_shader_module(wgpu::ShaderModuleDescriptor {
- label: Some("iced_wgpu::quad::shader"),
+ label: Some("iced_wgpu quad shader"),
source: wgpu::ShaderSource::Wgsl(std::borrow::Cow::Borrowed(
include_str!("shader/quad.wgsl"),
)),
diff --git a/wgpu/src/triangle.rs b/wgpu/src/triangle.rs
index 9fa521d7..0df8dd02 100644
--- a/wgpu/src/triangle.rs
+++ b/wgpu/src/triangle.rs
@@ -577,7 +577,7 @@ mod solid {
let shader =
device.create_shader_module(wgpu::ShaderModuleDescriptor {
label: Some(
- "iced_wgpu::triangle::solid create shader module",
+ "iced_wgpu triangle solid create shader module",
),
source: wgpu::ShaderSource::Wgsl(
std::borrow::Cow::Borrowed(include_str!(
diff --git a/wgpu/src/triangle/msaa.rs b/wgpu/src/triangle/msaa.rs
index 14522c89..4afbdb32 100644
--- a/wgpu/src/triangle/msaa.rs
+++ b/wgpu/src/triangle/msaa.rs
@@ -75,7 +75,7 @@ impl Blit {
let shader =
device.create_shader_module(wgpu::ShaderModuleDescriptor {
- label: Some("iced_wgpu::triangle::blit_shader"),
+ label: Some("iced_wgpu triangle blit_shader"),
source: wgpu::ShaderSource::Wgsl(std::borrow::Cow::Borrowed(
include_str!("../shader/blit.wgsl"),
)),
diff --git a/wgpu/src/window/compositor.rs b/wgpu/src/window/compositor.rs
index 025cd43a..54ab83ea 100644
--- a/wgpu/src/window/compositor.rs
+++ b/wgpu/src/window/compositor.rs
@@ -66,7 +66,19 @@ impl<Theme> Compositor<Theme> {
log::info!("Selected: {:#?}", adapter.get_info());
let format = compatible_surface.as_ref().and_then(|surface| {
- surface.get_capabilities(&adapter).formats.first().copied()
+ let capabilities = surface.get_capabilities(&adapter);
+
+ capabilities
+ .formats
+ .iter()
+ .filter(|format| format.describe().srgb)
+ .copied()
+ .next()
+ .or_else(|| {
+ log::warn!("No sRGB format found!");
+
+ capabilities.formats.first().copied()
+ })
})?;
log::info!("Selected format: {:?}", format);
diff --git a/widget/Cargo.toml b/widget/Cargo.toml
index 4c23f3e8..40e4db37 100644
--- a/widget/Cargo.toml
+++ b/widget/Cargo.toml
@@ -16,7 +16,7 @@ num-traits = "0.2"
thiserror = "1"
[dependencies.iced_runtime]
-version = "0.9"
+version = "0.1"
path = "../runtime"
[dependencies.iced_renderer]
@@ -24,7 +24,7 @@ version = "0.1"
path = "../renderer"
[dependencies.iced_style]
-version = "0.7"
+version = "0.8"
path = "../style"
[dependencies.ouroboros]
diff --git a/widget/src/checkbox.rs b/widget/src/checkbox.rs
index d1f886c6..6505cfdd 100644
--- a/widget/src/checkbox.rs
+++ b/widget/src/checkbox.rs
@@ -15,17 +15,6 @@ use crate::{Row, Text};
pub use iced_style::checkbox::{Appearance, StyleSheet};
-/// The icon in a [`Checkbox`].
-#[derive(Debug, Clone, PartialEq)]
-pub struct Icon<Font> {
- /// Font that will be used to display the `code_point`,
- pub font: Font,
- /// The unicode code point that will be used as the icon.
- pub code_point: char,
- /// Font size of the content.
- pub size: Option<f32>,
-}
-
/// A box that can be checked.
///
/// # Example
@@ -321,3 +310,14 @@ where
Element::new(checkbox)
}
}
+
+/// The icon in a [`Checkbox`].
+#[derive(Debug, Clone, PartialEq)]
+pub struct Icon<Font> {
+ /// Font that will be used to display the `code_point`,
+ pub font: Font,
+ /// The unicode code point that will be used as the icon.
+ pub code_point: char,
+ /// Font size of the content.
+ pub size: Option<f32>,
+}
diff --git a/widget/src/helpers.rs b/widget/src/helpers.rs
index a43e7248..336ac4ee 100644
--- a/widget/src/helpers.rs
+++ b/widget/src/helpers.rs
@@ -17,7 +17,7 @@ use crate::text::{self, Text};
use crate::text_input::{self, TextInput};
use crate::toggler::{self, Toggler};
use crate::tooltip::{self, Tooltip};
-use crate::{Column, Row, Space, VerticalSlider};
+use crate::{Column, MouseArea, Row, Space, VerticalSlider};
use std::borrow::Cow;
use std::ops::RangeInclusive;
@@ -163,7 +163,7 @@ where
Renderer::Theme: radio::StyleSheet,
V: Copy + Eq,
{
- Radio::new(value, label, selected, on_click)
+ Radio::new(label, value, selected, on_click)
}
/// Creates a new [`Toggler`].
@@ -187,14 +187,13 @@ where
pub fn text_input<'a, Message, Renderer>(
placeholder: &str,
value: &str,
- on_change: impl Fn(String) -> Message + 'a,
) -> TextInput<'a, Message, Renderer>
where
Message: Clone,
Renderer: core::text::Renderer,
Renderer::Theme: text_input::StyleSheet,
{
- TextInput::new(placeholder, value, on_change)
+ TextInput::new(placeholder, value)
}
/// Creates a new [`Slider`].
@@ -360,3 +359,13 @@ where
{
Command::widget(operation::focusable::focus_next())
}
+
+/// A container intercepting mouse events.
+pub fn mouse_area<'a, Message, Renderer>(
+ widget: impl Into<Element<'a, Message, Renderer>>,
+) -> MouseArea<'a, Message, Renderer>
+where
+ Renderer: core::Renderer,
+{
+ MouseArea::new(widget)
+}
diff --git a/widget/src/lazy/component.rs b/widget/src/lazy/component.rs
index 0b8070af..49ae68af 100644
--- a/widget/src/lazy/component.rs
+++ b/widget/src/lazy/component.rs
@@ -13,6 +13,7 @@ use crate::core::{
use ouroboros::self_referencing;
use std::cell::RefCell;
use std::marker::PhantomData;
+use std::rc::Rc;
/// A reusable, custom widget that uses The Elm Architecture.
///
@@ -58,6 +59,8 @@ pub trait Component<Message, Renderer> {
}
}
+struct Tag<T>(T);
+
/// Turns an implementor of [`Component`] into an [`Element`] that can be
/// embedded in any application.
pub fn view<'a, C, Message, Renderer>(
@@ -79,11 +82,13 @@ where
}
.build(),
)),
+ tree: RefCell::new(Rc::new(RefCell::new(None))),
})
}
struct Instance<'a, Message, Renderer, Event, S> {
state: RefCell<Option<State<'a, Message, Renderer, Event, S>>>,
+ tree: RefCell<Rc<RefCell<Option<Tree>>>>,
}
#[self_referencing]
@@ -100,40 +105,91 @@ struct State<'a, Message: 'a, Renderer: 'a, Event: 'a, S: 'a> {
impl<'a, Message, Renderer, Event, S> Instance<'a, Message, Renderer, Event, S>
where
- S: Default,
+ S: Default + 'static,
+ Renderer: renderer::Renderer,
{
- fn rebuild_element(&self, state: &S) {
- let heads = self.state.borrow_mut().take().unwrap().into_heads();
+ fn diff_self(&self) {
+ self.with_element(|element| {
+ self.tree
+ .borrow_mut()
+ .borrow_mut()
+ .as_mut()
+ .unwrap()
+ .diff_children(std::slice::from_ref(&element));
+ });
+ }
- *self.state.borrow_mut() = Some(
- StateBuilder {
- component: heads.component,
- message: PhantomData,
- state: PhantomData,
- element_builder: |component| Some(component.view(state)),
- }
- .build(),
- );
+ fn rebuild_element_if_necessary(&self) {
+ let inner = self.state.borrow_mut().take().unwrap();
+ if inner.borrow_element().is_none() {
+ let heads = inner.into_heads();
+
+ *self.state.borrow_mut() = Some(
+ StateBuilder {
+ component: heads.component,
+ message: PhantomData,
+ state: PhantomData,
+ element_builder: |component| {
+ Some(
+ component.view(
+ self.tree
+ .borrow()
+ .borrow()
+ .as_ref()
+ .unwrap()
+ .state
+ .downcast_ref::<S>(),
+ ),
+ )
+ },
+ }
+ .build(),
+ );
+ self.diff_self();
+ } else {
+ *self.state.borrow_mut() = Some(inner);
+ }
}
fn rebuild_element_with_operation(
&self,
- state: &mut S,
operation: &mut dyn widget::Operation<Message>,
) {
let heads = self.state.borrow_mut().take().unwrap().into_heads();
- heads.component.operate(state, operation);
+ heads.component.operate(
+ self.tree
+ .borrow_mut()
+ .borrow_mut()
+ .as_mut()
+ .unwrap()
+ .state
+ .downcast_mut(),
+ operation,
+ );
*self.state.borrow_mut() = Some(
StateBuilder {
component: heads.component,
message: PhantomData,
state: PhantomData,
- element_builder: |component| Some(component.view(state)),
+ element_builder: |component| {
+ Some(
+ component.view(
+ self.tree
+ .borrow()
+ .borrow()
+ .as_ref()
+ .unwrap()
+ .state
+ .downcast_ref(),
+ ),
+ )
+ },
}
.build(),
);
+ self.diff_self();
}
fn with_element<T>(
@@ -147,6 +203,7 @@ where
&self,
f: impl FnOnce(&mut Element<'_, Event, Renderer>) -> T,
) -> T {
+ self.rebuild_element_if_necessary();
self.state
.borrow_mut()
.as_mut()
@@ -162,24 +219,27 @@ where
Renderer: core::Renderer,
{
fn tag(&self) -> tree::Tag {
- struct Tag<T>(T);
tree::Tag::of::<Tag<S>>()
}
fn state(&self) -> tree::State {
- tree::State::new(S::default())
+ let state = Rc::new(RefCell::new(Some(Tree {
+ tag: tree::Tag::of::<Tag<S>>(),
+ state: tree::State::new(S::default()),
+ children: vec![Tree::empty()],
+ })));
+ *self.tree.borrow_mut() = state.clone();
+ tree::State::new(state)
}
fn children(&self) -> Vec<Tree> {
- self.rebuild_element(&S::default());
- self.with_element(|element| vec![Tree::new(element)])
+ vec![]
}
fn diff(&self, tree: &mut Tree) {
- self.rebuild_element(tree.state.downcast_ref());
- self.with_element(|element| {
- tree.diff_children(std::slice::from_ref(&element))
- })
+ let tree = tree.state.downcast_ref::<Rc<RefCell<Option<Tree>>>>();
+ *self.tree.borrow_mut() = tree.clone();
+ self.rebuild_element_if_necessary();
}
fn width(&self) -> Length {
@@ -213,9 +273,10 @@ where
let mut local_messages = Vec::new();
let mut local_shell = Shell::new(&mut local_messages);
+ let t = tree.state.downcast_mut::<Rc<RefCell<Option<Tree>>>>();
let event_status = self.with_element_mut(|element| {
element.as_widget_mut().on_event(
- &mut tree.children[0],
+ &mut t.borrow_mut().as_mut().unwrap().children[0],
event,
layout,
cursor_position,
@@ -235,9 +296,10 @@ where
let mut heads = self.state.take().unwrap().into_heads();
for message in local_messages.into_iter().filter_map(|message| {
- heads
- .component
- .update(tree.state.downcast_mut::<S>(), message)
+ heads.component.update(
+ t.borrow_mut().as_mut().unwrap().state.downcast_mut(),
+ message,
+ )
}) {
shell.publish(message);
}
@@ -247,17 +309,11 @@ where
component: heads.component,
message: PhantomData,
state: PhantomData,
- element_builder: |state| {
- Some(state.view(tree.state.downcast_ref::<S>()))
- },
+ element_builder: |_| None,
}
.build(),
));
- self.with_element(|element| {
- tree.diff_children(std::slice::from_ref(&element))
- });
-
shell.invalidate_layout();
}
@@ -271,10 +327,7 @@ where
renderer: &Renderer,
operation: &mut dyn widget::Operation<Message>,
) {
- self.rebuild_element_with_operation(
- tree.state.downcast_mut(),
- operation,
- );
+ self.rebuild_element_with_operation(operation);
struct MapOperation<'a, B> {
operation: &'a mut dyn widget::Operation<B>,
@@ -308,13 +361,28 @@ where
) {
self.operation.text_input(state, id);
}
+
+ fn scrollable(
+ &mut self,
+ state: &mut dyn widget::operation::Scrollable,
+ id: Option<&widget::Id>,
+ ) {
+ self.operation.scrollable(state, id);
+ }
+
+ fn custom(
+ &mut self,
+ state: &mut dyn std::any::Any,
+ id: Option<&widget::Id>,
+ ) {
+ self.operation.custom(state, id);
+ }
}
+ let tree = tree.state.downcast_mut::<Rc<RefCell<Option<Tree>>>>();
self.with_element(|element| {
- tree.diff_children(std::slice::from_ref(&element));
-
element.as_widget().operate(
- &mut tree.children[0],
+ &mut tree.borrow_mut().as_mut().unwrap().children[0],
layout,
renderer,
&mut MapOperation { operation },
@@ -332,9 +400,10 @@ where
cursor_position: Point,
viewport: &Rectangle,
) {
+ let tree = tree.state.downcast_ref::<Rc<RefCell<Option<Tree>>>>();
self.with_element(|element| {
element.as_widget().draw(
- &tree.children[0],
+ &tree.borrow().as_ref().unwrap().children[0],
renderer,
theme,
style,
@@ -353,9 +422,10 @@ where
viewport: &Rectangle,
renderer: &Renderer,
) -> mouse::Interaction {
+ let tree = tree.state.downcast_ref::<Rc<RefCell<Option<Tree>>>>();
self.with_element(|element| {
element.as_widget().mouse_interaction(
- &tree.children[0],
+ &tree.borrow().as_ref().unwrap().children[0],
layout,
cursor_position,
viewport,
@@ -370,25 +440,34 @@ where
layout: Layout<'_>,
renderer: &Renderer,
) -> Option<overlay::Element<'b, Message, Renderer>> {
- let overlay = OverlayBuilder {
- instance: self,
- tree,
- types: PhantomData,
- overlay_builder: |instance, tree| {
- instance.state.get_mut().as_mut().unwrap().with_element_mut(
- move |element| {
- element.as_mut().unwrap().as_widget_mut().overlay(
- &mut tree.children[0],
- layout,
- renderer,
- )
- },
- )
- },
- }
- .build();
+ self.rebuild_element_if_necessary();
+ let tree = tree
+ .state
+ .downcast_mut::<Rc<RefCell<Option<Tree>>>>()
+ .borrow_mut()
+ .take()
+ .unwrap();
+ let overlay = Overlay(Some(
+ InnerBuilder {
+ instance: self,
+ tree,
+ types: PhantomData,
+ overlay_builder: |instance, tree| {
+ instance.state.get_mut().as_mut().unwrap().with_element_mut(
+ move |element| {
+ element.as_mut().unwrap().as_widget_mut().overlay(
+ &mut tree.children[0],
+ layout,
+ renderer,
+ )
+ },
+ )
+ },
+ }
+ .build(),
+ ));
- let has_overlay = overlay.with_overlay(|overlay| {
+ let has_overlay = overlay.0.as_ref().unwrap().with_overlay(|overlay| {
overlay.as_ref().map(overlay::Element::position)
});
@@ -403,10 +482,24 @@ where
}
}
+struct Overlay<'a, 'b, Message, Renderer, Event, S>(
+ Option<Inner<'a, 'b, Message, Renderer, Event, S>>,
+);
+
+impl<'a, 'b, Message, Renderer, Event, S> Drop
+ for Overlay<'a, 'b, Message, Renderer, Event, S>
+{
+ fn drop(&mut self) {
+ if let Some(heads) = self.0.take().map(|inner| inner.into_heads()) {
+ *heads.instance.tree.borrow_mut().borrow_mut() = Some(heads.tree);
+ }
+ }
+}
+
#[self_referencing]
-struct Overlay<'a, 'b, Message, Renderer, Event, S> {
+struct Inner<'a, 'b, Message, Renderer, Event, S> {
instance: &'a mut Instance<'b, Message, Renderer, Event, S>,
- tree: &'a mut Tree,
+ tree: Tree,
types: PhantomData<(Message, Event, S)>,
#[borrows(mut instance, mut tree)]
@@ -428,6 +521,9 @@ impl<'a, 'b, Message, Renderer, Event, S>
self.overlay
.as_ref()
.unwrap()
+ .0
+ .as_ref()
+ .unwrap()
.borrow_overlay()
.as_ref()
.map(f)
@@ -440,6 +536,9 @@ impl<'a, 'b, Message, Renderer, Event, S>
self.overlay
.as_mut()
.unwrap()
+ .0
+ .as_mut()
+ .unwrap()
.with_overlay_mut(|overlay| overlay.as_mut().map(f))
}
}
@@ -523,42 +622,37 @@ where
local_shell.revalidate_layout(|| shell.invalidate_layout());
if !local_messages.is_empty() {
- let overlay = self.overlay.take().unwrap().into_heads();
- let mut heads = overlay.instance.state.take().unwrap().into_heads();
+ let mut inner =
+ self.overlay.take().unwrap().0.take().unwrap().into_heads();
+ let mut heads = inner.instance.state.take().unwrap().into_heads();
for message in local_messages.into_iter().filter_map(|message| {
heads
.component
- .update(overlay.tree.state.downcast_mut::<S>(), message)
+ .update(inner.tree.state.downcast_mut(), message)
}) {
shell.publish(message);
}
- *overlay.instance.state.borrow_mut() = Some(
+ *inner.instance.state.borrow_mut() = Some(
StateBuilder {
component: heads.component,
message: PhantomData,
state: PhantomData,
- element_builder: |state| {
- Some(state.view(overlay.tree.state.downcast_ref::<S>()))
- },
+ element_builder: |_| None,
}
.build(),
);
- overlay.instance.with_element(|element| {
- overlay.tree.diff_children(std::slice::from_ref(&element))
- });
-
- self.overlay = Some(
- OverlayBuilder {
- instance: overlay.instance,
- tree: overlay.tree,
+ self.overlay = Some(Overlay(Some(
+ InnerBuilder {
+ instance: inner.instance,
+ tree: inner.tree,
types: PhantomData,
overlay_builder: |_, _| None,
}
.build(),
- );
+ )));
shell.invalidate_layout();
}
diff --git a/widget/src/lib.rs b/widget/src/lib.rs
index a3e7c8bc..904f62ad 100644
--- a/widget/src/lib.rs
+++ b/widget/src/lib.rs
@@ -21,6 +21,7 @@ pub use iced_runtime::core;
pub use iced_style as style;
mod column;
+mod mouse_area;
mod row;
pub mod button;
@@ -63,6 +64,8 @@ pub use column::Column;
#[doc(no_inline)]
pub use container::Container;
#[doc(no_inline)]
+pub use mouse_area::MouseArea;
+#[doc(no_inline)]
pub use pane_grid::PaneGrid;
#[doc(no_inline)]
pub use pick_list::PickList;
diff --git a/widget/src/mouse_area.rs b/widget/src/mouse_area.rs
new file mode 100644
index 00000000..0232c494
--- /dev/null
+++ b/widget/src/mouse_area.rs
@@ -0,0 +1,311 @@
+//! A container for capturing mouse events.
+
+use crate::core::event::{self, Event};
+use crate::core::layout;
+use crate::core::mouse;
+use crate::core::overlay;
+use crate::core::renderer;
+use crate::core::touch;
+use crate::core::widget::{tree, Operation, Tree};
+use crate::core::{
+ Clipboard, Element, Layout, Length, Point, Rectangle, Shell, Widget,
+};
+
+/// Emit messages on mouse events.
+#[allow(missing_debug_implementations)]
+pub struct MouseArea<'a, Message, Renderer> {
+ content: Element<'a, Message, Renderer>,
+ on_press: Option<Message>,
+ on_release: Option<Message>,
+ on_right_press: Option<Message>,
+ on_right_release: Option<Message>,
+ on_middle_press: Option<Message>,
+ on_middle_release: Option<Message>,
+}
+
+impl<'a, Message, Renderer> MouseArea<'a, Message, Renderer> {
+ /// The message to emit on a left button press.
+ #[must_use]
+ pub fn on_press(mut self, message: Message) -> Self {
+ self.on_press = Some(message);
+ self
+ }
+
+ /// The message to emit on a left button release.
+ #[must_use]
+ pub fn on_release(mut self, message: Message) -> Self {
+ self.on_release = Some(message);
+ self
+ }
+
+ /// The message to emit on a right button press.
+ #[must_use]
+ pub fn on_right_press(mut self, message: Message) -> Self {
+ self.on_right_press = Some(message);
+ self
+ }
+
+ /// The message to emit on a right button release.
+ #[must_use]
+ pub fn on_right_release(mut self, message: Message) -> Self {
+ self.on_right_release = Some(message);
+ self
+ }
+
+ /// The message to emit on a middle button press.
+ #[must_use]
+ pub fn on_middle_press(mut self, message: Message) -> Self {
+ self.on_middle_press = Some(message);
+ self
+ }
+
+ /// The message to emit on a middle button release.
+ #[must_use]
+ pub fn on_middle_release(mut self, message: Message) -> Self {
+ self.on_middle_release = Some(message);
+ self
+ }
+}
+
+/// Local state of the [`MouseArea`].
+#[derive(Default)]
+struct State {
+ // TODO: Support on_mouse_enter and on_mouse_exit
+}
+
+impl<'a, Message, Renderer> MouseArea<'a, Message, Renderer> {
+ /// Creates a [`MouseArea`] with the given content.
+ pub fn new(content: impl Into<Element<'a, Message, Renderer>>) -> Self {
+ MouseArea {
+ content: content.into(),
+ on_press: None,
+ on_release: None,
+ on_right_press: None,
+ on_right_release: None,
+ on_middle_press: None,
+ on_middle_release: None,
+ }
+ }
+}
+
+impl<'a, Message, Renderer> Widget<Message, Renderer>
+ for MouseArea<'a, Message, Renderer>
+where
+ Renderer: renderer::Renderer,
+ Message: Clone,
+{
+ fn tag(&self) -> tree::Tag {
+ tree::Tag::of::<State>()
+ }
+
+ fn state(&self) -> tree::State {
+ tree::State::new(State::default())
+ }
+
+ fn children(&self) -> Vec<Tree> {
+ vec![Tree::new(&self.content)]
+ }
+
+ fn diff(&self, tree: &mut Tree) {
+ tree.diff_children(std::slice::from_ref(&self.content));
+ }
+
+ fn width(&self) -> Length {
+ self.content.as_widget().width()
+ }
+
+ fn height(&self) -> Length {
+ self.content.as_widget().height()
+ }
+
+ fn layout(
+ &self,
+ renderer: &Renderer,
+ limits: &layout::Limits,
+ ) -> layout::Node {
+ self.content.as_widget().layout(renderer, limits)
+ }
+
+ fn operate(
+ &self,
+ tree: &mut Tree,
+ layout: Layout<'_>,
+ renderer: &Renderer,
+ operation: &mut dyn Operation<Message>,
+ ) {
+ self.content.as_widget().operate(
+ &mut tree.children[0],
+ layout,
+ renderer,
+ operation,
+ );
+ }
+
+ fn on_event(
+ &mut self,
+ tree: &mut Tree,
+ event: Event,
+ layout: Layout<'_>,
+ cursor_position: Point,
+ renderer: &Renderer,
+ clipboard: &mut dyn Clipboard,
+ shell: &mut Shell<'_, Message>,
+ ) -> event::Status {
+ if let event::Status::Captured = self.content.as_widget_mut().on_event(
+ &mut tree.children[0],
+ event.clone(),
+ layout,
+ cursor_position,
+ renderer,
+ clipboard,
+ shell,
+ ) {
+ return event::Status::Captured;
+ }
+
+ update(self, &event, layout, cursor_position, shell)
+ }
+
+ fn mouse_interaction(
+ &self,
+ tree: &Tree,
+ layout: Layout<'_>,
+ cursor_position: Point,
+ viewport: &Rectangle,
+ renderer: &Renderer,
+ ) -> mouse::Interaction {
+ self.content.as_widget().mouse_interaction(
+ &tree.children[0],
+ layout,
+ cursor_position,
+ viewport,
+ renderer,
+ )
+ }
+
+ fn draw(
+ &self,
+ tree: &Tree,
+ renderer: &mut Renderer,
+ theme: &Renderer::Theme,
+ renderer_style: &renderer::Style,
+ layout: Layout<'_>,
+ cursor_position: Point,
+ viewport: &Rectangle,
+ ) {
+ self.content.as_widget().draw(
+ &tree.children[0],
+ renderer,
+ theme,
+ renderer_style,
+ layout,
+ cursor_position,
+ viewport,
+ );
+ }
+
+ fn overlay<'b>(
+ &'b mut self,
+ tree: &'b mut Tree,
+ layout: Layout<'_>,
+ renderer: &Renderer,
+ ) -> Option<overlay::Element<'b, Message, Renderer>> {
+ self.content.as_widget_mut().overlay(
+ &mut tree.children[0],
+ layout,
+ renderer,
+ )
+ }
+}
+
+impl<'a, Message, Renderer> From<MouseArea<'a, Message, Renderer>>
+ for Element<'a, Message, Renderer>
+where
+ Message: 'a + Clone,
+ Renderer: 'a + renderer::Renderer,
+{
+ fn from(
+ area: MouseArea<'a, Message, Renderer>,
+ ) -> Element<'a, Message, Renderer> {
+ Element::new(area)
+ }
+}
+
+/// Processes the given [`Event`] and updates the [`State`] of an [`MouseArea`]
+/// accordingly.
+fn update<Message: Clone, Renderer>(
+ widget: &mut MouseArea<'_, Message, Renderer>,
+ event: &Event,
+ layout: Layout<'_>,
+ cursor_position: Point,
+ shell: &mut Shell<'_, Message>,
+) -> event::Status {
+ if !layout.bounds().contains(cursor_position) {
+ return event::Status::Ignored;
+ }
+
+ if let Some(message) = widget.on_press.as_ref() {
+ if let Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left))
+ | Event::Touch(touch::Event::FingerPressed { .. }) = event
+ {
+ shell.publish(message.clone());
+
+ return event::Status::Captured;
+ }
+ }
+
+ if let Some(message) = widget.on_release.as_ref() {
+ if let Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left))
+ | Event::Touch(touch::Event::FingerLifted { .. }) = event
+ {
+ shell.publish(message.clone());
+
+ return event::Status::Captured;
+ }
+ }
+
+ if let Some(message) = widget.on_right_press.as_ref() {
+ if let Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Right)) =
+ event
+ {
+ shell.publish(message.clone());
+
+ return event::Status::Captured;
+ }
+ }
+
+ if let Some(message) = widget.on_right_release.as_ref() {
+ if let Event::Mouse(mouse::Event::ButtonReleased(
+ mouse::Button::Right,
+ )) = event
+ {
+ shell.publish(message.clone());
+
+ return event::Status::Captured;
+ }
+ }
+
+ if let Some(message) = widget.on_middle_press.as_ref() {
+ if let Event::Mouse(mouse::Event::ButtonPressed(
+ mouse::Button::Middle,
+ )) = event
+ {
+ shell.publish(message.clone());
+
+ return event::Status::Captured;
+ }
+ }
+
+ if let Some(message) = widget.on_middle_release.as_ref() {
+ if let Event::Mouse(mouse::Event::ButtonReleased(
+ mouse::Button::Middle,
+ )) = event
+ {
+ shell.publish(message.clone());
+
+ return event::Status::Captured;
+ }
+ }
+
+ event::Status::Ignored
+}
diff --git a/widget/src/pane_grid.rs b/widget/src/pane_grid.rs
index 257c0144..67145e8e 100644
--- a/widget/src/pane_grid.rs
+++ b/widget/src/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/iced-rs/iced/tree/0.8/examples/pane_grid
+//! [`pane_grid` example]: https://github.com/iced-rs/iced/tree/0.9/examples/pane_grid
mod axis;
mod configuration;
mod content;
diff --git a/widget/src/radio.rs b/widget/src/radio.rs
index c2b6b017..c3229aed 100644
--- a/widget/src/radio.rs
+++ b/widget/src/radio.rs
@@ -22,10 +22,13 @@ pub use iced_style::radio::{Appearance, StyleSheet};
/// # type Radio<Message> =
/// # iced_widget::Radio<Message, iced_widget::renderer::Renderer<iced_widget::style::Theme>>;
/// #
+/// # use iced_widget::column;
/// #[derive(Debug, Clone, Copy, PartialEq, Eq)]
/// pub enum Choice {
/// A,
/// B,
+/// C,
+/// All,
/// }
///
/// #[derive(Debug, Clone, Copy)]
@@ -35,12 +38,36 @@ pub use iced_style::radio::{Appearance, StyleSheet};
///
/// let selected_choice = Some(Choice::A);
///
-/// Radio::new(Choice::A, "This is A", selected_choice, Message::RadioSelected);
+/// let a = Radio::new(
+/// "A",
+/// Choice::A,
+/// selected_choice,
+/// Message::RadioSelected,
+/// );
///
-/// Radio::new(Choice::B, "This is B", selected_choice, Message::RadioSelected);
-/// ```
+/// let b = Radio::new(
+/// "B",
+/// Choice::B,
+/// selected_choice,
+/// Message::RadioSelected,
+/// );
+///
+/// let c = Radio::new(
+/// "C",
+/// Choice::C,
+/// selected_choice,
+/// Message::RadioSelected,
+/// );
+///
+/// let all = Radio::new(
+/// "All of the above",
+/// Choice::All,
+/// selected_choice,
+/// Message::RadioSelected
+/// );
///
-/// ![Radio buttons drawn by `iced_wgpu`](https://github.com/iced-rs/iced/blob/7760618fb112074bc40b148944521f312152012a/docs/images/radio.png?raw=true)
+/// let content = column![a, b, c, all];
+/// ```
#[allow(missing_debug_implementations)]
pub struct Radio<Message, Renderer = crate::Renderer>
where
@@ -79,8 +106,8 @@ where
/// * a function that will be called when the [`Radio`] is selected. It
/// receives the value of the radio and must produce a `Message`.
pub fn new<F, V>(
- value: V,
label: impl Into<String>,
+ value: V,
selected: Option<V>,
f: F,
) -> Self
diff --git a/widget/src/scrollable.rs b/widget/src/scrollable.rs
index 5a7481f7..161ae664 100644
--- a/widget/src/scrollable.rs
+++ b/widget/src/scrollable.rs
@@ -27,6 +27,7 @@ where
Renderer::Theme: StyleSheet,
{
id: Option<Id>,
+ width: Length,
height: Length,
vertical: Properties,
horizontal: Option<Properties>,
@@ -44,6 +45,7 @@ where
pub fn new(content: impl Into<Element<'a, Message, Renderer>>) -> Self {
Scrollable {
id: None,
+ width: Length::Shrink,
height: Length::Shrink,
vertical: Properties::default(),
horizontal: None,
@@ -59,6 +61,12 @@ where
self
}
+ /// Sets the width of the [`Scrollable`].
+ pub fn width(mut self, width: impl Into<Length>) -> Self {
+ self.width = width.into();
+ self
+ }
+
/// Sets the height of the [`Scrollable`].
pub fn height(mut self, height: impl Into<Length>) -> Self {
self.height = height.into();
@@ -167,7 +175,7 @@ where
}
fn width(&self) -> Length {
- self.content.as_widget().width()
+ self.width
}
fn height(&self) -> Length {
@@ -182,7 +190,7 @@ where
layout(
renderer,
limits,
- Widget::<Message, Renderer>::width(self),
+ self.width,
self.height,
self.horizontal.is_some(),
|renderer, limits| {
@@ -391,15 +399,7 @@ pub fn layout<Renderer>(
horizontal_enabled: bool,
layout_content: impl FnOnce(&Renderer, &layout::Limits) -> layout::Node,
) -> layout::Node {
- let limits = limits
- .max_height(f32::INFINITY)
- .max_width(if horizontal_enabled {
- f32::INFINITY
- } else {
- limits.max().width
- })
- .width(width)
- .height(height);
+ let limits = limits.width(width).height(height);
let child_limits = layout::Limits::new(
Size::new(limits.min().width, 0.0),
@@ -851,8 +851,8 @@ pub fn draw<Renderer>(
if let Some(scrollbar) = scrollbars.y {
let style = if state.y_scroller_grabbed_at.is_some() {
theme.dragging(style)
- } else if mouse_over_y_scrollbar {
- theme.hovered(style)
+ } else if mouse_over_scrollable {
+ theme.hovered(style, mouse_over_y_scrollbar)
} else {
theme.active(style)
};
@@ -864,8 +864,8 @@ pub fn draw<Renderer>(
if let Some(scrollbar) = scrollbars.x {
let style = if state.x_scroller_grabbed_at.is_some() {
theme.dragging_horizontal(style)
- } else if mouse_over_x_scrollbar {
- theme.hovered_horizontal(style)
+ } else if mouse_over_scrollable {
+ theme.hovered_horizontal(style, mouse_over_x_scrollbar)
} else {
theme.active_horizontal(style)
};
@@ -889,7 +889,7 @@ pub fn draw<Renderer>(
}
fn notify_on_scroll<Message>(
- state: &State,
+ state: &mut State,
on_scroll: &Option<Box<dyn Fn(RelativeOffset) -> Message + '_>>,
bounds: Rectangle,
content_bounds: Rectangle,
@@ -910,7 +910,23 @@ fn notify_on_scroll<Message>(
.absolute(bounds.height, content_bounds.height)
/ (content_bounds.height - bounds.height);
- shell.publish(on_scroll(RelativeOffset { x, y }))
+ let new_offset = RelativeOffset { x, y };
+
+ // Don't publish redundant offsets to shell
+ if let Some(prev_offset) = state.last_notified {
+ let unchanged = |a: f32, b: f32| {
+ (a - b).abs() <= f32::EPSILON || (a.is_nan() && b.is_nan())
+ };
+
+ if unchanged(prev_offset.x, new_offset.x)
+ && unchanged(prev_offset.y, new_offset.y)
+ {
+ return;
+ }
+ }
+
+ shell.publish(on_scroll(new_offset));
+ state.last_notified = Some(new_offset);
}
}
@@ -923,6 +939,7 @@ pub struct State {
offset_x: Offset,
x_scroller_grabbed_at: Option<f32>,
keyboard_modifiers: keyboard::Modifiers,
+ last_notified: Option<RelativeOffset>,
}
impl Default for State {
@@ -934,6 +951,7 @@ impl Default for State {
offset_x: Offset::Absolute(0.0),
x_scroller_grabbed_at: None,
keyboard_modifiers: keyboard::Modifiers::default(),
+ last_notified: None,
}
}
}
diff --git a/widget/src/slider.rs b/widget/src/slider.rs
index e1153d2d..5a884e21 100644
--- a/widget/src/slider.rs
+++ b/widget/src/slider.rs
@@ -8,13 +8,15 @@ use crate::core::renderer;
use crate::core::touch;
use crate::core::widget::tree::{self, Tree};
use crate::core::{
- Background, Clipboard, Color, Element, Layout, Length, Pixels, Point,
- Rectangle, Shell, Size, Widget,
+ Clipboard, Color, Element, Layout, Length, Pixels, Point, Rectangle, Shell,
+ Size, Widget,
};
use std::ops::RangeInclusive;
-pub use iced_style::slider::{Appearance, Handle, HandleShape, StyleSheet};
+pub use iced_style::slider::{
+ Appearance, Handle, HandleShape, Rail, StyleSheet,
+};
/// An horizontal bar and a handle that selects a single value from a range of
/// values.
@@ -366,38 +368,6 @@ pub fn draw<T, R>(
style_sheet.active(style)
};
- let rail_y = bounds.y + (bounds.height / 2.0).round();
-
- renderer.fill_quad(
- renderer::Quad {
- bounds: Rectangle {
- x: bounds.x,
- y: rail_y - 1.0,
- width: bounds.width,
- height: 2.0,
- },
- border_radius: 0.0.into(),
- border_width: 0.0,
- border_color: Color::TRANSPARENT,
- },
- style.rail_colors.0,
- );
-
- renderer.fill_quad(
- renderer::Quad {
- bounds: Rectangle {
- x: bounds.x,
- y: rail_y + 1.0,
- width: bounds.width,
- height: 2.0,
- },
- border_radius: 0.0.into(),
- border_width: 0.0,
- border_color: Color::TRANSPARENT,
- },
- Background::Color(style.rail_colors.1),
- );
-
let (handle_width, handle_height, handle_border_radius) = match style
.handle
.shape
@@ -416,17 +386,49 @@ pub fn draw<T, R>(
(start.into() as f32, end.into() as f32)
};
- let handle_offset = if range_start >= range_end {
+ let offset = if range_start >= range_end {
0.0
} else {
- (bounds.width - handle_width) * (value - range_start)
+ (bounds.width - handle_width / 2.0) * (value - range_start)
/ (range_end - range_start)
};
+ let rail_y = bounds.y + bounds.height / 2.0;
+
+ renderer.fill_quad(
+ renderer::Quad {
+ bounds: Rectangle {
+ x: bounds.x,
+ y: rail_y - style.rail.width / 2.0,
+ width: offset + handle_width / 2.0,
+ height: style.rail.width,
+ },
+ border_radius: Default::default(),
+ border_width: 0.0,
+ border_color: Color::TRANSPARENT,
+ },
+ style.rail.colors.0,
+ );
+
+ renderer.fill_quad(
+ renderer::Quad {
+ bounds: Rectangle {
+ x: bounds.x + offset + handle_width / 2.0,
+ y: rail_y - style.rail.width / 2.0,
+ width: bounds.width - offset,
+ height: style.rail.width,
+ },
+ border_radius: Default::default(),
+ border_width: 0.0,
+ border_color: Color::TRANSPARENT,
+ },
+ style.rail.colors.1,
+ );
+
renderer.fill_quad(
renderer::Quad {
bounds: Rectangle {
- x: bounds.x + handle_offset.round(),
+ x: bounds.x + offset,
y: rail_y - handle_height / 2.0,
width: handle_width,
height: handle_height,
diff --git a/widget/src/text_input.rs b/widget/src/text_input.rs
index d066109a..0faa51c0 100644
--- a/widget/src/text_input.rs
+++ b/widget/src/text_input.rs
@@ -49,8 +49,8 @@ pub use iced_style::text_input::{Appearance, StyleSheet};
/// let input = TextInput::new(
/// "This is the placeholder...",
/// value,
-/// Message::TextInputChanged,
/// )
+/// .on_input(Message::TextInputChanged)
/// .padding(10);
/// ```
/// ![Text input drawn by `iced_wgpu`](https://github.com/iced-rs/iced/blob/7760618fb112074bc40b148944521f312152012a/docs/images/text_input.png?raw=true)
@@ -68,9 +68,10 @@ where
width: Length,
padding: Padding,
size: Option<f32>,
- on_change: Box<dyn Fn(String) -> Message + 'a>,
+ on_input: Option<Box<dyn Fn(String) -> Message + 'a>>,
on_paste: Option<Box<dyn Fn(String) -> Message + 'a>>,
on_submit: Option<Message>,
+ icon: Option<Icon<Renderer::Font>>,
style: <Renderer::Theme as StyleSheet>::Style,
}
@@ -84,12 +85,8 @@ where
///
/// It expects:
/// - a placeholder,
- /// - the current value, and
- /// - a function that produces a message when the [`TextInput`] changes.
- pub fn new<F>(placeholder: &str, value: &str, on_change: F) -> Self
- where
- F: 'a + Fn(String) -> Message,
- {
+ /// - the current value
+ pub fn new(placeholder: &str, value: &str) -> Self {
TextInput {
id: None,
placeholder: String::from(placeholder),
@@ -99,9 +96,10 @@ where
width: Length::Fill,
padding: Padding::new(5.0),
size: None,
- on_change: Box::new(on_change),
+ on_input: None,
on_paste: None,
on_submit: None,
+ icon: None,
style: Default::default(),
}
}
@@ -118,6 +116,25 @@ where
self
}
+ /// Sets the message that should be produced when some text is typed into
+ /// the [`TextInput`].
+ ///
+ /// If this method is not called, the [`TextInput`] will be disabled.
+ pub fn on_input<F>(mut self, callback: F) -> Self
+ where
+ F: 'a + Fn(String) -> Message,
+ {
+ self.on_input = Some(Box::new(callback));
+ self
+ }
+
+ /// Sets the message that should be produced when the [`TextInput`] is
+ /// focused and the enter key is pressed.
+ pub fn on_submit(mut self, message: Message) -> Self {
+ self.on_submit = Some(message);
+ self
+ }
+
/// Sets the message that should be produced when some text is pasted into
/// the [`TextInput`].
pub fn on_paste(
@@ -135,6 +152,13 @@ where
self.font = Some(font);
self
}
+
+ /// Sets the [`Icon`] of the [`TextInput`].
+ pub fn icon(mut self, icon: Icon<Renderer::Font>) -> Self {
+ self.icon = Some(icon);
+ self
+ }
+
/// Sets the width of the [`TextInput`].
pub fn width(mut self, width: impl Into<Length>) -> Self {
self.width = width.into();
@@ -153,13 +177,6 @@ where
self
}
- /// Sets the message that should be produced when the [`TextInput`] is
- /// focused and the enter key is pressed.
- pub fn on_submit(mut self, message: Message) -> Self {
- self.on_submit = Some(message);
- self
- }
-
/// Sets the style of the [`TextInput`].
pub fn style(
mut self,
@@ -192,7 +209,9 @@ where
&self.placeholder,
self.size,
self.font,
+ self.on_input.is_none(),
self.is_secure,
+ self.icon.as_ref(),
&self.style,
)
}
@@ -213,6 +232,18 @@ where
tree::State::new(State::new())
}
+ fn diff(&self, tree: &mut Tree) {
+ let state = tree.state.downcast_mut::<State>();
+
+ // Unfocus text input if it becomes disabled
+ if self.on_input.is_none() {
+ state.last_click = None;
+ state.is_focused = None;
+ state.is_pasting = None;
+ state.is_dragging = false;
+ }
+ }
+
fn width(&self) -> Length {
self.width
}
@@ -226,7 +257,14 @@ where
renderer: &Renderer,
limits: &layout::Limits,
) -> layout::Node {
- layout(renderer, limits, self.width, self.padding, self.size)
+ layout(
+ renderer,
+ limits,
+ self.width,
+ self.padding,
+ self.size,
+ self.icon.as_ref(),
+ )
}
fn operate(
@@ -263,7 +301,7 @@ where
self.size,
self.font,
self.is_secure,
- self.on_change.as_ref(),
+ self.on_input.as_deref(),
self.on_paste.as_deref(),
&self.on_submit,
|| tree.state.downcast_mut::<State>(),
@@ -290,7 +328,9 @@ where
&self.placeholder,
self.size,
self.font,
+ self.on_input.is_none(),
self.is_secure,
+ self.icon.as_ref(),
&self.style,
)
}
@@ -303,7 +343,7 @@ where
_viewport: &Rectangle,
_renderer: &Renderer,
) -> mouse::Interaction {
- mouse_interaction(layout, cursor_position)
+ mouse_interaction(layout, cursor_position, self.on_input.is_none())
}
}
@@ -321,6 +361,30 @@ where
}
}
+/// The content of the [`Icon`].
+#[derive(Debug, Clone)]
+pub struct Icon<Font> {
+ /// The font that will be used to display the `code_point`.
+ pub font: Font,
+ /// The unicode code point that will be used as the icon.
+ pub code_point: char,
+ /// The font size of the content.
+ pub size: Option<f32>,
+ /// The spacing between the [`Icon`] and the text in a [`TextInput`].
+ pub spacing: f32,
+ /// The side of a [`TextInput`] where to display the [`Icon`].
+ pub side: Side,
+}
+
+/// The side of a [`TextInput`].
+#[derive(Debug, Clone)]
+pub enum Side {
+ /// The left side of a [`TextInput`].
+ Left,
+ /// The right side of a [`TextInput`].
+ Right,
+}
+
/// The identifier of a [`TextInput`].
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct Id(widget::Id);
@@ -383,6 +447,7 @@ pub fn layout<Renderer>(
width: Length,
padding: Padding,
size: Option<f32>,
+ icon: Option<&Icon<Renderer::Font>>,
) -> layout::Node
where
Renderer: text::Renderer,
@@ -391,10 +456,51 @@ where
let padding = padding.fit(Size::ZERO, limits.max());
let limits = limits.width(width).pad(padding).height(text_size * 1.2);
- let mut text = layout::Node::new(limits.resolve(Size::ZERO));
- text.move_to(Point::new(padding.left, padding.top));
+ let text_bounds = limits.resolve(Size::ZERO);
+
+ if let Some(icon) = icon {
+ let icon_width = renderer.measure_width(
+ &icon.code_point.to_string(),
+ icon.size.unwrap_or_else(|| renderer.default_size()),
+ icon.font,
+ );
+
+ let mut text_node = layout::Node::new(
+ text_bounds - Size::new(icon_width + icon.spacing, 0.0),
+ );
+
+ let mut icon_node =
+ layout::Node::new(Size::new(icon_width, text_bounds.height));
+
+ match icon.side {
+ Side::Left => {
+ text_node.move_to(Point::new(
+ padding.left + icon_width + icon.spacing,
+ padding.top,
+ ));
+
+ icon_node.move_to(Point::new(padding.left, padding.top));
+ }
+ Side::Right => {
+ text_node.move_to(Point::new(padding.left, padding.top));
+
+ icon_node.move_to(Point::new(
+ padding.left + text_bounds.width - icon_width,
+ padding.top,
+ ));
+ }
+ };
+
+ layout::Node::with_children(
+ text_bounds.pad(padding),
+ vec![text_node, icon_node],
+ )
+ } else {
+ let mut text = layout::Node::new(text_bounds);
+ text.move_to(Point::new(padding.left, padding.top));
- layout::Node::with_children(text.size().pad(padding), vec![text])
+ layout::Node::with_children(text_bounds.pad(padding), vec![text])
+ }
}
/// Processes an [`Event`] and updates the [`State`] of a [`TextInput`]
@@ -410,7 +516,7 @@ pub fn update<'a, Message, Renderer>(
size: Option<f32>,
font: Option<Renderer::Font>,
is_secure: bool,
- on_change: &dyn Fn(String) -> Message,
+ on_input: Option<&dyn Fn(String) -> Message>,
on_paste: Option<&dyn Fn(String) -> Message>,
on_submit: &Option<Message>,
state: impl FnOnce() -> &'a mut State,
@@ -423,7 +529,8 @@ where
Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left))
| Event::Touch(touch::Event::FingerPressed { .. }) => {
let state = state();
- let is_clicked = layout.bounds().contains(cursor_position);
+ let is_clicked =
+ layout.bounds().contains(cursor_position) && on_input.is_some();
state.is_focused = if is_clicked {
state.is_focused.or_else(|| {
@@ -553,6 +660,8 @@ where
let state = state();
if let Some(focus) = &mut state.is_focused {
+ let Some(on_input) = on_input else { return event::Status::Ignored };
+
if state.is_pasting.is_none()
&& !state.keyboard_modifiers.command()
&& !c.is_control()
@@ -561,7 +670,7 @@ where
editor.insert(c);
- let message = (on_change)(editor.contents());
+ let message = (on_input)(editor.contents());
shell.publish(message);
focus.updated_at = Instant::now();
@@ -574,6 +683,8 @@ where
let state = state();
if let Some(focus) = &mut state.is_focused {
+ let Some(on_input) = on_input else { return event::Status::Ignored };
+
let modifiers = state.keyboard_modifiers;
focus.updated_at = Instant::now();
@@ -599,7 +710,7 @@ where
let mut editor = Editor::new(value, &mut state.cursor);
editor.backspace();
- let message = (on_change)(editor.contents());
+ let message = (on_input)(editor.contents());
shell.publish(message);
}
keyboard::KeyCode::Delete => {
@@ -619,7 +730,7 @@ where
let mut editor = Editor::new(value, &mut state.cursor);
editor.delete();
- let message = (on_change)(editor.contents());
+ let message = (on_input)(editor.contents());
shell.publish(message);
}
keyboard::KeyCode::Left => {
@@ -694,7 +805,7 @@ where
let mut editor = Editor::new(value, &mut state.cursor);
editor.delete();
- let message = (on_change)(editor.contents());
+ let message = (on_input)(editor.contents());
shell.publish(message);
}
keyboard::KeyCode::V => {
@@ -721,7 +832,7 @@ where
let message = if let Some(paste) = &on_paste {
(paste)(editor.contents())
} else {
- (on_change)(editor.contents())
+ (on_input)(editor.contents())
};
shell.publish(message);
@@ -815,7 +926,9 @@ pub fn draw<Renderer>(
placeholder: &str,
size: Option<f32>,
font: Option<Renderer::Font>,
+ is_disabled: bool,
is_secure: bool,
+ icon: Option<&Icon<Renderer::Font>>,
style: &<Renderer::Theme as StyleSheet>::Style,
) where
Renderer: text::Renderer,
@@ -825,11 +938,15 @@ pub fn draw<Renderer>(
let value = secure_value.as_ref().unwrap_or(value);
let bounds = layout.bounds();
- let text_bounds = layout.children().next().unwrap().bounds();
+
+ let mut children_layout = layout.children();
+ let text_bounds = children_layout.next().unwrap().bounds();
let is_mouse_over = bounds.contains(cursor_position);
- let appearance = if state.is_focused() {
+ let appearance = if is_disabled {
+ theme.disabled(style)
+ } else if state.is_focused() {
theme.focused(style)
} else if is_mouse_over {
theme.hovered(style)
@@ -847,6 +964,20 @@ pub fn draw<Renderer>(
appearance.background,
);
+ if let Some(icon) = icon {
+ let icon_layout = children_layout.next().unwrap();
+
+ renderer.fill_text(Text {
+ content: &icon.code_point.to_string(),
+ size: icon.size.unwrap_or_else(|| renderer.default_size()),
+ font: icon.font,
+ color: appearance.icon_color,
+ bounds: icon_layout.bounds(),
+ horizontal_alignment: alignment::Horizontal::Left,
+ vertical_alignment: alignment::Vertical::Top,
+ });
+ }
+
let text = value.to_string();
let font = font.unwrap_or_else(|| renderer.default_font());
let size = size.unwrap_or_else(|| renderer.default_size());
@@ -959,6 +1090,8 @@ pub fn draw<Renderer>(
content: if text.is_empty() { placeholder } else { &text },
color: if text.is_empty() {
theme.placeholder_color(style)
+ } else if is_disabled {
+ theme.disabled_color(style)
} else {
theme.value_color(style)
},
@@ -987,9 +1120,14 @@ pub fn draw<Renderer>(
pub fn mouse_interaction(
layout: Layout<'_>,
cursor_position: Point,
+ is_disabled: bool,
) -> mouse::Interaction {
if layout.bounds().contains(cursor_position) {
- mouse::Interaction::Text
+ if is_disabled {
+ mouse::Interaction::NotAllowed
+ } else {
+ mouse::Interaction::Text
+ }
} else {
mouse::Interaction::default()
}
diff --git a/widget/src/vertical_slider.rs b/widget/src/vertical_slider.rs
index 62dc997f..a7551aef 100644
--- a/widget/src/vertical_slider.rs
+++ b/widget/src/vertical_slider.rs
@@ -13,8 +13,8 @@ use crate::core::renderer;
use crate::core::touch;
use crate::core::widget::tree::{self, Tree};
use crate::core::{
- Background, Clipboard, Color, Element, Length, Pixels, Point, Rectangle,
- Shell, Size, Widget,
+ Clipboard, Color, Element, Length, Pixels, Point, Rectangle, Shell, Size,
+ Widget,
};
/// An vertical bar and a handle that selects a single value from a range of
@@ -366,38 +366,6 @@ pub fn draw<T, R>(
style_sheet.active(style)
};
- let rail_x = bounds.x + (bounds.width / 2.0).round();
-
- renderer.fill_quad(
- renderer::Quad {
- bounds: Rectangle {
- x: rail_x - 1.0,
- y: bounds.y,
- width: 2.0,
- height: bounds.height,
- },
- border_radius: 0.0.into(),
- border_width: 0.0,
- border_color: Color::TRANSPARENT,
- },
- style.rail_colors.0,
- );
-
- renderer.fill_quad(
- renderer::Quad {
- bounds: Rectangle {
- x: rail_x + 1.0,
- y: bounds.y,
- width: 2.0,
- height: bounds.height,
- },
- border_radius: 0.0.into(),
- border_width: 0.0,
- border_color: Color::TRANSPARENT,
- },
- Background::Color(style.rail_colors.1),
- );
-
let (handle_width, handle_height, handle_border_radius) = match style
.handle
.shape
@@ -416,18 +384,50 @@ pub fn draw<T, R>(
(start.into() as f32, end.into() as f32)
};
- let handle_offset = if range_start >= range_end {
+ let offset = if range_start >= range_end {
0.0
} else {
- (bounds.height - handle_width) * (value - range_end)
+ (bounds.height - handle_width / 2.0) * (value - range_end)
/ (range_start - range_end)
};
+ let rail_x = bounds.x + bounds.width / 2.0;
+
+ renderer.fill_quad(
+ renderer::Quad {
+ bounds: Rectangle {
+ x: rail_x - style.rail.width / 2.0,
+ y: bounds.y,
+ width: style.rail.width,
+ height: offset + handle_width / 2.0,
+ },
+ border_radius: Default::default(),
+ border_width: 0.0,
+ border_color: Color::TRANSPARENT,
+ },
+ style.rail.colors.1,
+ );
+
+ renderer.fill_quad(
+ renderer::Quad {
+ bounds: Rectangle {
+ x: rail_x - style.rail.width / 2.0,
+ y: bounds.y + offset + handle_width / 2.0,
+ width: style.rail.width,
+ height: bounds.height - offset,
+ },
+ border_radius: Default::default(),
+ border_width: 0.0,
+ border_color: Color::TRANSPARENT,
+ },
+ style.rail.colors.0,
+ );
+
renderer.fill_quad(
renderer::Quad {
bounds: Rectangle {
- x: rail_x - (handle_height / 2.0),
- y: bounds.y + handle_offset.round(),
+ x: rail_x - handle_height / 2.0,
+ y: bounds.y + offset,
width: handle_height,
height: handle_width,
},
diff --git a/winit/Cargo.toml b/winit/Cargo.toml
index 1bf63804..58e13b3e 100644
--- a/winit/Cargo.toml
+++ b/winit/Cargo.toml
@@ -1,6 +1,6 @@
[package]
name = "iced_winit"
-version = "0.8.0"
+version = "0.9.1"
authors = ["Héctor Ramón Jiménez <hector0193@gmail.com>"]
edition = "2021"
description = "A winit runtime for Iced"
@@ -11,11 +11,16 @@ keywords = ["gui", "ui", "graphics", "interface", "widgets"]
categories = ["gui"]
[features]
+default = ["x11", "wayland", "wayland-dlopen", "wayland-csd-adwaita"]
trace = ["tracing", "tracing-core", "tracing-subscriber"]
chrome-trace = ["trace", "tracing-chrome"]
debug = ["iced_runtime/debug"]
system = ["sysinfo"]
application = []
+x11 = ["winit/x11"]
+wayland = ["winit/wayland"]
+wayland-dlopen = ["winit/wayland-dlopen"]
+wayland-csd-adwaita = ["winit/wayland-csd-adwaita"]
[dependencies]
window_clipboard = "0.2"
@@ -26,17 +31,18 @@ thiserror = "1.0"
version = "0.27"
git = "https://github.com/iced-rs/winit.git"
rev = "940457522e9fb9f5dac228b0ecfafe0138b4048c"
+default-features = false
[dependencies.iced_runtime]
-version = "0.9"
+version = "0.1"
path = "../runtime"
[dependencies.iced_graphics]
-version = "0.7"
+version = "0.8"
path = "../graphics"
[dependencies.iced_style]
-version = "0.7"
+version = "0.8"
path = "../style"
[dependencies.tracing]
diff --git a/winit/README.md b/winit/README.md
index 83810473..91307970 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.8"
+iced_winit = "0.9"
```
__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 48f94235..3d7c6e5d 100644
--- a/winit/src/application.rs
+++ b/winit/src/application.rs
@@ -179,13 +179,17 @@ where
.unwrap_or(None)
});
- let _ = match target {
- Some(node) => node
- .replace_child(&canvas, &node)
- .expect(&format!("Could not replace #{}", node.id())),
- None => body
- .append_child(&canvas)
- .expect("Append canvas to HTML body"),
+ match target {
+ Some(node) => {
+ let _ = node
+ .replace_with_with_node_1(&canvas)
+ .expect(&format!("Could not replace #{}", node.id()));
+ }
+ None => {
+ let _ = body
+ .append_child(&canvas)
+ .expect("Append canvas to HTML body");
+ }
};
}
@@ -762,6 +766,9 @@ pub fn run_command<A, E>(
mode,
));
}
+ window::Action::ChangeIcon(icon) => {
+ window.set_window_icon(conversion::icon(icon))
+ }
window::Action::FetchMode(tag) => {
let mode = if window.is_visible().unwrap_or(true) {
conversion::mode(window.fullscreen())
diff --git a/winit/src/conversion.rs b/winit/src/conversion.rs
index 0759ad9d..a9262184 100644
--- a/winit/src/conversion.rs
+++ b/winit/src/conversion.rs
@@ -1,7 +1,7 @@
//! Convert [`winit`] types into [`iced_native`] types, and viceversa.
//!
//! [`winit`]: https://github.com/rust-windowing/winit
-//! [`iced_native`]: https://github.com/iced-rs/iced/tree/0.8/native
+//! [`iced_native`]: https://github.com/iced-rs/iced/tree/0.9/native
use crate::core::keyboard;
use crate::core::mouse;
use crate::core::touch;
@@ -219,7 +219,7 @@ pub fn mode(mode: Option<winit::window::Fullscreen>) -> window::Mode {
/// Converts a `MouseCursor` from [`iced_native`] to a [`winit`] cursor icon.
///
/// [`winit`]: https://github.com/rust-windowing/winit
-/// [`iced_native`]: https://github.com/iced-rs/iced/tree/0.8/native
+/// [`iced_native`]: https://github.com/iced-rs/iced/tree/0.9/native
pub fn mouse_interaction(
interaction: mouse::Interaction,
) -> winit::window::CursorIcon {
@@ -237,13 +237,14 @@ pub fn mouse_interaction(
winit::window::CursorIcon::EwResize
}
Interaction::ResizingVertically => winit::window::CursorIcon::NsResize,
+ Interaction::NotAllowed => winit::window::CursorIcon::NotAllowed,
}
}
/// Converts a `MouseButton` from [`winit`] to an [`iced_native`] mouse button.
///
/// [`winit`]: https://github.com/rust-windowing/winit
-/// [`iced_native`]: https://github.com/iced-rs/iced/tree/0.8/native
+/// [`iced_native`]: https://github.com/iced-rs/iced/tree/0.9/native
pub fn mouse_button(mouse_button: winit::event::MouseButton) -> mouse::Button {
match mouse_button {
winit::event::MouseButton::Left => mouse::Button::Left,
@@ -259,7 +260,7 @@ pub fn mouse_button(mouse_button: winit::event::MouseButton) -> mouse::Button {
/// modifiers state.
///
/// [`winit`]: https://github.com/rust-windowing/winit
-/// [`iced_native`]: https://github.com/iced-rs/iced/tree/0.8/native
+/// [`iced_native`]: https://github.com/iced-rs/iced/tree/0.9/native
pub fn modifiers(
modifiers: winit::event::ModifiersState,
) -> keyboard::Modifiers {
@@ -286,7 +287,7 @@ pub fn cursor_position(
/// Converts a `Touch` from [`winit`] to an [`iced_native`] touch event.
///
/// [`winit`]: https://github.com/rust-windowing/winit
-/// [`iced_native`]: https://github.com/iced-rs/iced/tree/0.8/native
+/// [`iced_native`]: https://github.com/iced-rs/iced/tree/0.9/native
pub fn touch_event(
touch: winit::event::Touch,
scale_factor: f64,
@@ -317,7 +318,7 @@ pub fn touch_event(
/// Converts a `VirtualKeyCode` from [`winit`] to an [`iced_native`] key code.
///
/// [`winit`]: https://github.com/rust-windowing/winit
-/// [`iced_native`]: https://github.com/iced-rs/iced/tree/0.8/native
+/// [`iced_native`]: https://github.com/iced-rs/iced/tree/0.9/native
pub fn key_code(
virtual_keycode: winit::event::VirtualKeyCode,
) -> keyboard::KeyCode {
@@ -510,6 +511,15 @@ pub fn user_attention(
}
}
+/// Converts some [`Icon`] into it's `winit` counterpart.
+///
+/// Returns `None` if there is an error during the conversion.
+pub fn icon(icon: window::Icon) -> Option<winit::window::Icon> {
+ let (pixels, size) = icon.into_raw();
+
+ winit::window::Icon::from_rgba(pixels, size.width, size.height).ok()
+}
+
// As defined in: http://www.unicode.org/faq/private_use.html
pub(crate) fn is_private_use_character(c: char) -> bool {
matches!(
diff --git a/winit/src/lib.rs b/winit/src/lib.rs
index 5cde510a..9f6bcebb 100644
--- a/winit/src/lib.rs
+++ b/winit/src/lib.rs
@@ -11,7 +11,7 @@
//! Additionally, a [`conversion`] module is available for users that decide to
//! implement a custom event loop.
//!
-//! [`iced_native`]: https://github.com/iced-rs/iced/tree/0.8/native
+//! [`iced_native`]: https://github.com/iced-rs/iced/tree/0.9/native
//! [`winit`]: https://github.com/rust-windowing/winit
//! [`conversion`]: crate::conversion
#![doc(
diff --git a/winit/src/settings.rs b/winit/src/settings.rs
index 5c64727b..be0ab329 100644
--- a/winit/src/settings.rs
+++ b/winit/src/settings.rs
@@ -22,6 +22,7 @@ mod platform;
pub use platform::PlatformSpecific;
use crate::conversion;
+use crate::core::window::Icon;
use crate::Position;
use winit::monitor::MonitorHandle;
@@ -84,7 +85,7 @@ pub struct Window {
pub always_on_top: bool,
/// The window icon, which is also usually used in the taskbar
- pub icon: Option<winit::window::Icon>,
+ pub icon: Option<Icon>,
/// Platform specific settings.
pub platform_specific: platform::PlatformSpecific,
@@ -126,8 +127,9 @@ impl Window {
.with_resizable(self.resizable)
.with_decorations(self.decorations)
.with_transparent(self.transparent)
- .with_window_icon(self.icon)
- .with_always_on_top(self.always_on_top);
+ .with_window_icon(self.icon.and_then(conversion::icon))
+ .with_always_on_top(self.always_on_top)
+ .with_visible(self.visible);
if let Some(position) = conversion::position(
primary_monitor.as_ref(),