summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorLibravatar Héctor Ramón Jiménez <hector0193@gmail.com>2022-12-02 18:53:21 +0100
committerLibravatar Héctor Ramón Jiménez <hector0193@gmail.com>2022-12-02 18:53:21 +0100
commit4029a1cdaaac1abbdcc141b20469a49670cd99b6 (patch)
tree71fa9d9c4aa1f02ce05771db43a4bb7bc6570e77
parent676d8efe03ebdbeeb95aef96b8097395b788b1ab (diff)
parent8b55e9b9e6ba0b83038dd491dd34d95b4f9a381b (diff)
downloadiced-4029a1cdaaac1abbdcc141b20469a49670cd99b6.tar.gz
iced-4029a1cdaaac1abbdcc141b20469a49670cd99b6.tar.bz2
iced-4029a1cdaaac1abbdcc141b20469a49670cd99b6.zip
Merge branch 'master' into non-uniform-border-radius-for-quads
-rw-r--r--CHANGELOG.md34
-rw-r--r--Cargo.toml65
-rw-r--r--README.md4
-rw-r--r--core/Cargo.toml2
-rw-r--r--core/src/lib.rs2
-rw-r--r--core/src/padding.rs20
-rw-r--r--core/src/size.rs27
-rw-r--r--examples/geometry/Cargo.toml11
-rw-r--r--examples/geometry/README.md18
-rw-r--r--examples/geometry/src/main.rs212
-rw-r--r--examples/modal/Cargo.toml10
-rw-r--r--examples/modal/src/main.rs475
-rw-r--r--examples/modern_art/Cargo.toml1
-rw-r--r--examples/modern_art/src/main.rs2
-rw-r--r--examples/pane_grid/src/main.rs40
-rw-r--r--examples/solar_system/src/main.rs12
-rw-r--r--examples/todos/src/main.rs6
-rw-r--r--futures/Cargo.toml2
-rw-r--r--futures/src/subscription.rs6
-rw-r--r--glow/Cargo.toml22
-rw-r--r--glow/src/backend.rs36
-rw-r--r--glow/src/image.rs243
-rw-r--r--glow/src/image/storage.rs78
-rw-r--r--glow/src/lib.rs4
-rw-r--r--glow/src/shader/common/gradient.frag32
-rw-r--r--glow/src/shader/common/gradient.vert (renamed from glow/src/shader/common/triangle.vert)0
-rw-r--r--glow/src/shader/common/image.frag22
-rw-r--r--glow/src/shader/common/image.vert9
-rw-r--r--glow/src/shader/common/solid.frag (renamed from glow/src/shader/common/triangle.frag)4
-rw-r--r--glow/src/shader/common/solid.vert11
-rw-r--r--glow/src/triangle.rs564
-rw-r--r--glow/src/triangle/gradient.rs162
-rw-r--r--glow/src/triangle/solid.rs91
-rw-r--r--glutin/Cargo.toml8
-rw-r--r--graphics/Cargo.toml44
-rw-r--r--graphics/src/backend.rs4
-rw-r--r--graphics/src/gradient.rs7
-rw-r--r--graphics/src/gradient/linear.rs5
-rw-r--r--graphics/src/image.rs10
-rw-r--r--graphics/src/image/raster.rs (renamed from wgpu/src/image/raster.rs)86
-rw-r--r--graphics/src/image/storage.rs31
-rw-r--r--graphics/src/image/vector.rs (renamed from wgpu/src/image/vector.rs)67
-rw-r--r--graphics/src/layer.rs25
-rw-r--r--graphics/src/layer/mesh.rs94
-rw-r--r--graphics/src/lib.rs1
-rw-r--r--graphics/src/primitive.rs25
-rw-r--r--graphics/src/renderer.rs4
-rw-r--r--graphics/src/triangle.rs33
-rw-r--r--graphics/src/widget/canvas.rs2
-rw-r--r--graphics/src/widget/canvas/fill.rs4
-rw-r--r--graphics/src/widget/canvas/frame.rs195
-rw-r--r--graphics/src/widget/canvas/stroke.rs4
-rw-r--r--graphics/src/widget/canvas/style.rs23
-rw-r--r--graphics/src/window/compositor.rs2
-rw-r--r--graphics/src/window/gl_compositor.rs2
-rw-r--r--lazy/Cargo.toml4
-rw-r--r--lazy/src/component.rs39
-rw-r--r--lazy/src/lazy.rs27
-rw-r--r--lazy/src/responsive.rs22
-rw-r--r--native/Cargo.toml8
-rw-r--r--native/src/element.rs20
-rw-r--r--native/src/image.rs31
-rw-r--r--native/src/lib.rs4
-rw-r--r--native/src/overlay.rs34
-rw-r--r--native/src/overlay/element.rs55
-rw-r--r--native/src/overlay/menu.rs20
-rw-r--r--native/src/subscription.rs2
-rw-r--r--native/src/svg.rs9
-rw-r--r--native/src/user_interface.rs43
-rw-r--r--native/src/widget.rs12
-rw-r--r--native/src/widget/action.rs67
-rw-r--r--native/src/widget/button.rs17
-rw-r--r--native/src/widget/checkbox.rs4
-rw-r--r--native/src/widget/column.rs4
-rw-r--r--native/src/widget/container.rs16
-rw-r--r--native/src/widget/helpers.rs11
-rw-r--r--native/src/widget/image.rs4
-rw-r--r--native/src/widget/image/viewer.rs4
-rw-r--r--native/src/widget/operation.rs48
-rw-r--r--native/src/widget/operation/focusable.rs34
-rw-r--r--native/src/widget/operation/text_input.rs131
-rw-r--r--native/src/widget/pane_grid.rs221
-rw-r--r--native/src/widget/pane_grid/content.rs41
-rw-r--r--native/src/widget/pane_grid/state.rs68
-rw-r--r--native/src/widget/pane_grid/title_bar.rs52
-rw-r--r--native/src/widget/pick_list.rs38
-rw-r--r--native/src/widget/progress_bar.rs2
-rw-r--r--native/src/widget/radio.rs4
-rw-r--r--native/src/widget/row.rs4
-rw-r--r--native/src/widget/rule.rs2
-rw-r--r--native/src/widget/scrollable.rs14
-rw-r--r--native/src/widget/slider.rs4
-rw-r--r--native/src/widget/svg.rs4
-rw-r--r--native/src/widget/text.rs2
-rw-r--r--native/src/widget/text_input.rs61
-rw-r--r--native/src/widget/toggler.rs4
-rw-r--r--native/src/widget/tooltip.rs8
-rw-r--r--native/src/widget/tree.rs12
-rw-r--r--src/application.rs21
-rw-r--r--src/lib.rs6
-rw-r--r--src/overlay.rs2
-rw-r--r--src/sandbox.rs22
-rw-r--r--src/widget.rs5
-rw-r--r--src/window.rs2
-rw-r--r--style/Cargo.toml4
-rw-r--r--style/src/application.rs14
-rw-r--r--style/src/button.rs23
-rw-r--r--style/src/checkbox.rs17
-rw-r--r--style/src/container.rs12
-rw-r--r--style/src/lib.rs1
-rw-r--r--style/src/menu.rs15
-rw-r--r--style/src/pane_grid.rs10
-rw-r--r--style/src/pick_list.rs26
-rw-r--r--style/src/progress_bar.rs11
-rw-r--r--style/src/radio.rs16
-rw-r--r--style/src/rule.rs7
-rw-r--r--style/src/scrollable.rs20
-rw-r--r--style/src/slider.rs31
-rw-r--r--style/src/text.rs8
-rw-r--r--style/src/text_input.rs24
-rw-r--r--style/src/theme.rs805
-rw-r--r--style/src/theme/palette.rs53
-rw-r--r--style/src/toggler.rs19
-rw-r--r--wgpu/Cargo.toml51
-rw-r--r--wgpu/src/backend.rs20
-rw-r--r--wgpu/src/buffer/dynamic.rs22
-rw-r--r--wgpu/src/buffer/static.rs2
-rw-r--r--wgpu/src/image.rs68
-rw-r--r--wgpu/src/image/atlas.rs205
-rw-r--r--wgpu/src/image/atlas/allocation.rs6
-rw-r--r--wgpu/src/image/atlas/allocator.rs4
-rw-r--r--wgpu/src/image/atlas/entry.rs10
-rw-r--r--wgpu/src/lib.rs6
-rw-r--r--wgpu/src/shader/solid.wgsl29
-rw-r--r--wgpu/src/shader/triangle.wgsl30
-rw-r--r--wgpu/src/triangle.rs674
-rw-r--r--wgpu/src/triangle/gradient.rs268
-rw-r--r--wgpu/src/triangle/solid.rs170
-rw-r--r--winit/Cargo.toml8
-rw-r--r--winit/src/application.rs24
-rw-r--r--winit/src/application/state.rs4
-rw-r--r--winit/src/conversion.rs12
-rw-r--r--winit/src/lib.rs2
-rw-r--r--winit/src/settings.rs12
-rw-r--r--winit/src/settings/macos.rs1
-rw-r--r--winit/src/settings/wasm.rs11
-rw-r--r--winit/src/settings/windows.rs1
147 files changed, 4786 insertions, 2142 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 0f0fa47c..d098e8ec 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -6,6 +6,37 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased]
+## [0.5.0] - 2022-11-10
+### Added
+- __[Stabilization of stateless widgets][stateless]__ (#1393)
+ The old widget API has been completely replaced by stateless widgets (introduced in #1284). Alongside the new API, there are a bunch of new helper functions and macros for easily describing view logic (like `row!` and `column!`).
+
+- __[First-class theming][theming]__ (#1362)
+ A complete overhaul of our styling primitives, introducing a `Theme` as a first-class concept of the library.
+
+- __[Widget operations][operations]__ (#1399)
+ An abstraction that can be used to traverse (and operate on) the widget tree of an application in order to query or update some widget state.
+
+- __[`Lazy` widget][lazy]__ (#1400)
+ A widget that can call some view logic lazily only when some data has changed. Thanks to @nicksenger!
+
+- __[Linear gradient support for `Canvas`][gradient]__ (#1448)
+ The `Canvas` widget can draw linear gradients now. Thanks to @bungoboingo!
+
+- __[Touch support for `Canvas`][touch]__ (#1305)
+ The `Canvas` widget now supports touch events. Thanks to @artursapek!
+
+- __[`Image` and `Svg` support for `iced_glow`][image]__ (#1485)
+ Our OpenGL renderer now is capable of rendering both the `Image` and `Svg` widgets. Thanks to @ids1024!
+
+[stateless]: https://github.com/iced-rs/iced/pull/1393
+[theming]: https://github.com/iced-rs/iced/pull/1362
+[operations]: https://github.com/iced-rs/iced/pull/1399
+[lazy]: https://github.com/iced-rs/iced/pull/1400
+[gradient]: https://github.com/iced-rs/iced/pull/1448
+[touch]: https://github.com/iced-rs/iced/pull/1305
+[image]: https://github.com/iced-rs/iced/pull/1485
+
## [0.4.2] - 2022-05-03
### Fixed
- `Padding` type not exposed in `iced`.
@@ -257,7 +288,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added
- First release! :tada:
-[Unreleased]: https://github.com/iced-rs/iced/compare/0.4.2...HEAD
+[Unreleased]: https://github.com/iced-rs/iced/compare/0.5.0...HEAD
+[0.5.0]: https://github.com/iced-rs/iced/compare/0.4.2...0.5.0
[0.4.2]: https://github.com/iced-rs/iced/compare/0.4.1...0.4.2
[0.4.1]: https://github.com/iced-rs/iced/compare/0.4.0...0.4.1
[0.4.0]: https://github.com/iced-rs/iced/compare/0.3.0...0.4.0
diff --git a/Cargo.toml b/Cargo.toml
index f7d578ba..5b76b22d 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,6 +1,6 @@
[package]
name = "iced"
-version = "0.4.2"
+version = "0.5.2"
authors = ["Héctor Ramón Jiménez <hector0193@gmail.com>"]
edition = "2021"
description = "A cross-platform GUI library inspired by Elm"
@@ -10,14 +10,13 @@ documentation = "https://docs.rs/iced"
readme = "README.md"
keywords = ["gui", "ui", "graphics", "interface", "widgets"]
categories = ["gui"]
-resolver = "2"
[features]
default = ["wgpu"]
# Enables the `Image` widget
-image = ["iced_wgpu/image", "image_rs"]
+image = ["iced_wgpu?/image", "iced_glow?/image", "image_rs"]
# Enables the `Svg` widget
-svg = ["iced_wgpu/svg"]
+svg = ["iced_wgpu?/svg", "iced_glow?/svg"]
# Enables the `Canvas` widget
canvas = ["iced_graphics/canvas"]
# Enables the `QRCode` widget
@@ -25,11 +24,9 @@ qr_code = ["iced_graphics/qr_code"]
# Enables the `iced_wgpu` renderer
wgpu = ["iced_wgpu"]
# Enables using system fonts
-default_system_font = ["iced_wgpu/default_system_font"]
+default_system_font = ["iced_wgpu?/default_system_font", "iced_glow?/default_system_font"]
# Enables the `iced_glow` renderer. Overrides `iced_wgpu`
glow = ["iced_glow", "iced_glutin"]
-# Enables using system fonts for `iced_glow`
-glow_default_system_font = ["iced_glow/default_system_font"]
# Enables a debug view in native platforms (press F12)
debug = ["iced_winit/debug"]
# Enables `tokio` as the `executor::Default` on native platforms
@@ -58,61 +55,29 @@ members = [
"style",
"wgpu",
"winit",
- "examples/arc",
- "examples/bezier_tool",
- "examples/clock",
- "examples/color_palette",
- "examples/component",
- "examples/counter",
- "examples/custom_widget",
- "examples/download_progress",
- "examples/events",
- "examples/exit",
- "examples/game_of_life",
- "examples/integration_opengl",
- "examples/integration_wgpu",
- "examples/lazy",
- "examples/modern_art",
- "examples/multitouch",
- "examples/pane_grid",
- "examples/pick_list",
- "examples/pokedex",
- "examples/progress_bar",
- "examples/qr_code",
- "examples/scrollable",
- "examples/sierpinski_triangle",
- "examples/solar_system",
- "examples/stopwatch",
- "examples/styling",
- "examples/svg",
- "examples/system_information",
- "examples/todos",
- "examples/tooltip",
- "examples/tour",
- "examples/url_handler",
- "examples/websocket",
+ "examples/*",
]
[dependencies]
-iced_core = { version = "0.5", path = "core" }
-iced_futures = { version = "0.4", path = "futures" }
-iced_native = { version = "0.5", path = "native" }
-iced_graphics = { version = "0.3", path = "graphics" }
-iced_winit = { version = "0.4", path = "winit", features = ["application"] }
-iced_glutin = { version = "0.3", path = "glutin", optional = true }
-iced_glow = { version = "0.3", path = "glow", optional = true }
+iced_core = { version = "0.6", path = "core" }
+iced_futures = { version = "0.5", path = "futures" }
+iced_native = { version = "0.6", path = "native" }
+iced_graphics = { version = "0.4", path = "graphics" }
+iced_winit = { version = "0.5", path = "winit", features = ["application"] }
+iced_glutin = { version = "0.4", path = "glutin", optional = true }
+iced_glow = { version = "0.4", path = "glow", optional = true }
thiserror = "1.0"
[dependencies.image_rs]
-version = "0.23"
+version = "0.24"
package = "image"
optional = true
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
-iced_wgpu = { version = "0.5", path = "wgpu", optional = true }
+iced_wgpu = { version = "0.6", path = "wgpu", optional = true }
[target.'cfg(target_arch = "wasm32")'.dependencies]
-iced_wgpu = { version = "0.5", path = "wgpu", features = ["webgl"], optional = true }
+iced_wgpu = { version = "0.6", path = "wgpu", features = ["webgl"], optional = true }
[package.metadata.docs.rs]
rustdoc-args = ["--cfg", "docsrs"]
diff --git a/README.md b/README.md
index dc065ada..1d2fb76f 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.4"
+iced = "0.5"
```
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.4", default-features = false, features = ["glow"] }
+iced = { version = "0.5", 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/core/Cargo.toml b/core/Cargo.toml
index c9c7686e..6fd0a38c 100644
--- a/core/Cargo.toml
+++ b/core/Cargo.toml
@@ -1,6 +1,6 @@
[package]
name = "iced_core"
-version = "0.5.0"
+version = "0.6.1"
authors = ["Héctor Ramón Jiménez <hector0193@gmail.com>"]
edition = "2021"
description = "The essential concepts of Iced"
diff --git a/core/src/lib.rs b/core/src/lib.rs
index 03ba8cca..383b4f73 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.4/native
+//! [`iced_native`]: https://github.com/iced-rs/iced/tree/0.5/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/padding.rs b/core/src/padding.rs
index 22467d6b..140ad8ee 100644
--- a/core/src/padding.rs
+++ b/core/src/padding.rs
@@ -1,3 +1,5 @@
+use crate::Size;
+
/// An amount of space to pad for each side of a box
///
/// You can leverage the `From` trait to build [`Padding`] conveniently:
@@ -71,9 +73,21 @@ impl Padding {
pub fn horizontal(self) -> u16 {
self.left + self.right
}
+
+ /// Fits the [`Padding`] between the provided `inner` and `outer` [`Size`].
+ pub fn fit(self, inner: Size, outer: Size) -> Self {
+ let available = (outer - inner).max(Size::ZERO);
+
+ Padding {
+ top: self.top.min((available.height as u16) / 2),
+ right: self.right.min((available.width as u16) / 2),
+ bottom: self.bottom.min((available.height as u16) / 2),
+ left: self.left.min((available.width as u16) / 2),
+ }
+ }
}
-impl std::convert::From<u16> for Padding {
+impl From<u16> for Padding {
fn from(p: u16) -> Self {
Padding {
top: p,
@@ -84,7 +98,7 @@ impl std::convert::From<u16> for Padding {
}
}
-impl std::convert::From<[u16; 2]> for Padding {
+impl From<[u16; 2]> for Padding {
fn from(p: [u16; 2]) -> Self {
Padding {
top: p[0],
@@ -95,7 +109,7 @@ impl std::convert::From<[u16; 2]> for Padding {
}
}
-impl std::convert::From<[u16; 4]> for Padding {
+impl From<[u16; 4]> for Padding {
fn from(p: [u16; 4]) -> Self {
Padding {
top: p[0],
diff --git a/core/src/size.rs b/core/src/size.rs
index 2db33a88..31f3171b 100644
--- a/core/src/size.rs
+++ b/core/src/size.rs
@@ -34,6 +34,22 @@ impl Size {
height: self.height + padding.vertical() as f32,
}
}
+
+ /// Returns the minimum of each component of this size and another
+ pub fn min(self, other: Self) -> Self {
+ Size {
+ width: self.width.min(other.width),
+ height: self.height.min(other.height),
+ }
+ }
+
+ /// Returns the maximum of each component of this size and another
+ pub fn max(self, other: Self) -> Self {
+ Size {
+ width: self.width.max(other.width),
+ height: self.height.max(other.height),
+ }
+ }
}
impl From<[f32; 2]> for Size {
@@ -68,3 +84,14 @@ impl From<Size> for Vector<f32> {
Vector::new(size.width, size.height)
}
}
+
+impl std::ops::Sub for Size {
+ type Output = Size;
+
+ fn sub(self, rhs: Self) -> Self::Output {
+ Size {
+ width: self.width - rhs.width,
+ height: self.height - rhs.height,
+ }
+ }
+}
diff --git a/examples/geometry/Cargo.toml b/examples/geometry/Cargo.toml
new file mode 100644
index 00000000..22ede0e0
--- /dev/null
+++ b/examples/geometry/Cargo.toml
@@ -0,0 +1,11 @@
+[package]
+name = "geometry"
+version = "0.1.0"
+authors = ["Héctor Ramón Jiménez <hector0193@gmail.com>"]
+edition = "2021"
+publish = false
+
+[dependencies]
+iced = { path = "../.." }
+iced_native = { path = "../../native" }
+iced_graphics = { path = "../../graphics" }
diff --git a/examples/geometry/README.md b/examples/geometry/README.md
new file mode 100644
index 00000000..4d5c81cb
--- /dev/null
+++ b/examples/geometry/README.md
@@ -0,0 +1,18 @@
+## Geometry
+
+A custom widget showcasing how to draw geometry with the `Mesh2D` primitive in [`iced_wgpu`](../../wgpu).
+
+The __[`main`]__ file contains all the code of the example.
+
+<div align="center">
+ <a href="https://gfycat.com/activeunfitkangaroo">
+ <img src="https://thumbs.gfycat.com/ActiveUnfitKangaroo-small.gif">
+ </a>
+</div>
+
+You can run it with `cargo run`:
+```
+cargo run --package geometry
+```
+
+[`main`]: src/main.rs
diff --git a/examples/geometry/src/main.rs b/examples/geometry/src/main.rs
new file mode 100644
index 00000000..9bacce7f
--- /dev/null
+++ b/examples/geometry/src/main.rs
@@ -0,0 +1,212 @@
+//! This example showcases a simple native custom widget that renders using
+//! arbitrary low-level geometry.
+mod rainbow {
+ // For now, to implement a custom native widget you will need to add
+ // `iced_native` and `iced_wgpu` to your dependencies.
+ //
+ // Then, you simply need to define your widget type and implement the
+ // `iced_native::Widget` trait with the `iced_wgpu::Renderer`.
+ //
+ // Of course, you can choose to make the implementation renderer-agnostic,
+ // if you wish to, by creating your own `Renderer` trait, which could be
+ // implemented by `iced_wgpu` and other renderers.
+ use iced_graphics::renderer::{self, Renderer};
+ use iced_graphics::triangle::ColoredVertex2D;
+ use iced_graphics::{Backend, Primitive};
+
+ use iced_native::layout;
+ use iced_native::widget::{self, Widget};
+ use iced_native::{
+ Element, Layout, Length, Point, Rectangle, Size, Vector,
+ };
+
+ #[derive(Debug, Clone, Copy, Default)]
+ pub struct Rainbow;
+
+ pub fn rainbow() -> Rainbow {
+ Rainbow
+ }
+
+ impl<Message, B, T> Widget<Message, Renderer<B, T>> for Rainbow
+ where
+ B: Backend,
+ {
+ fn width(&self) -> Length {
+ Length::Fill
+ }
+
+ fn height(&self) -> Length {
+ Length::Shrink
+ }
+
+ fn layout(
+ &self,
+ _renderer: &Renderer<B, T>,
+ limits: &layout::Limits,
+ ) -> layout::Node {
+ let size = limits.width(Length::Fill).resolve(Size::ZERO);
+
+ layout::Node::new(Size::new(size.width, size.width))
+ }
+
+ fn draw(
+ &self,
+ _tree: &widget::Tree,
+ renderer: &mut Renderer<B, T>,
+ _theme: &T,
+ _style: &renderer::Style,
+ layout: Layout<'_>,
+ cursor_position: Point,
+ _viewport: &Rectangle,
+ ) {
+ use iced_graphics::triangle::Mesh2D;
+ use iced_native::Renderer as _;
+
+ let b = layout.bounds();
+
+ // R O Y G B I V
+ let color_r = [1.0, 0.0, 0.0, 1.0];
+ let color_o = [1.0, 0.5, 0.0, 1.0];
+ let color_y = [1.0, 1.0, 0.0, 1.0];
+ let color_g = [0.0, 1.0, 0.0, 1.0];
+ let color_gb = [0.0, 1.0, 0.5, 1.0];
+ let color_b = [0.0, 0.2, 1.0, 1.0];
+ let color_i = [0.5, 0.0, 1.0, 1.0];
+ let color_v = [0.75, 0.0, 0.5, 1.0];
+
+ let posn_center = {
+ if b.contains(cursor_position) {
+ [cursor_position.x - b.x, cursor_position.y - b.y]
+ } else {
+ [b.width / 2.0, b.height / 2.0]
+ }
+ };
+
+ let posn_tl = [0.0, 0.0];
+ let posn_t = [b.width / 2.0, 0.0];
+ let posn_tr = [b.width, 0.0];
+ let posn_r = [b.width, b.height / 2.0];
+ let posn_br = [b.width, b.height];
+ let posn_b = [(b.width / 2.0), b.height];
+ let posn_bl = [0.0, b.height];
+ let posn_l = [0.0, b.height / 2.0];
+
+ let mesh = Primitive::SolidMesh {
+ size: b.size(),
+ buffers: Mesh2D {
+ vertices: vec![
+ ColoredVertex2D {
+ position: posn_center,
+ color: [1.0, 1.0, 1.0, 1.0],
+ },
+ ColoredVertex2D {
+ position: posn_tl,
+ color: color_r,
+ },
+ ColoredVertex2D {
+ position: posn_t,
+ color: color_o,
+ },
+ ColoredVertex2D {
+ position: posn_tr,
+ color: color_y,
+ },
+ ColoredVertex2D {
+ position: posn_r,
+ color: color_g,
+ },
+ ColoredVertex2D {
+ position: posn_br,
+ color: color_gb,
+ },
+ ColoredVertex2D {
+ position: posn_b,
+ color: color_b,
+ },
+ ColoredVertex2D {
+ position: posn_bl,
+ color: color_i,
+ },
+ ColoredVertex2D {
+ position: posn_l,
+ color: color_v,
+ },
+ ],
+ indices: vec![
+ 0, 1, 2, // TL
+ 0, 2, 3, // T
+ 0, 3, 4, // TR
+ 0, 4, 5, // R
+ 0, 5, 6, // BR
+ 0, 6, 7, // B
+ 0, 7, 8, // BL
+ 0, 8, 1, // L
+ ],
+ },
+ };
+
+ renderer.with_translation(Vector::new(b.x, b.y), |renderer| {
+ renderer.draw_primitive(mesh);
+ });
+ }
+ }
+
+ impl<'a, Message, B, T> From<Rainbow> for Element<'a, Message, Renderer<B, T>>
+ where
+ B: Backend,
+ {
+ fn from(rainbow: Rainbow) -> Self {
+ Self::new(rainbow)
+ }
+ }
+}
+
+use iced::widget::{column, container, scrollable};
+use iced::{Element, Length, Sandbox, Settings};
+use rainbow::rainbow;
+
+pub fn main() -> iced::Result {
+ Example::run(Settings::default())
+}
+
+struct Example;
+
+impl Sandbox for Example {
+ type Message = ();
+
+ fn new() -> Self {
+ Self
+ }
+
+ fn title(&self) -> String {
+ String::from("Custom 2D geometry - Iced")
+ }
+
+ fn update(&mut self, _: ()) {}
+
+ fn view(&self) -> Element<()> {
+ let content = column![
+ rainbow(),
+ "In this example we draw a custom widget Rainbow, using \
+ the Mesh2D primitive. This primitive supplies a list of \
+ triangles, expressed as vertices and indices.",
+ "Move your cursor over it, and see the center vertex \
+ follow you!",
+ "Every Vertex2D defines its own color. You could use the \
+ Mesh2D primitive to render virtually any two-dimensional \
+ geometry for your widget.",
+ ]
+ .padding(20)
+ .spacing(20)
+ .max_width(500);
+
+ let scrollable =
+ scrollable(container(content).width(Length::Fill).center_x());
+
+ container(scrollable)
+ .width(Length::Fill)
+ .height(Length::Fill)
+ .center_y()
+ .into()
+ }
+}
diff --git a/examples/modal/Cargo.toml b/examples/modal/Cargo.toml
new file mode 100644
index 00000000..8770acac
--- /dev/null
+++ b/examples/modal/Cargo.toml
@@ -0,0 +1,10 @@
+[package]
+name = "modal"
+version = "0.1.0"
+authors = ["tarkah <admin@tarkah.dev>"]
+edition = "2021"
+publish = false
+
+[dependencies]
+iced = { path = "../..", features = [] }
+iced_native = { path = "../../native" }
diff --git a/examples/modal/src/main.rs b/examples/modal/src/main.rs
new file mode 100644
index 00000000..25483cf3
--- /dev/null
+++ b/examples/modal/src/main.rs
@@ -0,0 +1,475 @@
+use iced::widget::{
+ self, button, column, container, horizontal_space, row, text, text_input,
+};
+use iced::{
+ executor, keyboard, subscription, theme, Alignment, Application, Command,
+ Element, Event, Length, Settings, Subscription,
+};
+
+use self::modal::Modal;
+
+pub fn main() -> iced::Result {
+ App::run(Settings::default())
+}
+
+#[derive(Default)]
+struct App {
+ show_modal: bool,
+ email: String,
+ password: String,
+}
+
+#[derive(Debug, Clone)]
+enum Message {
+ ShowModal,
+ HideModal,
+ Email(String),
+ Password(String),
+ Submit,
+ Event(Event),
+}
+
+impl Application for App {
+ type Executor = executor::Default;
+ type Message = Message;
+ type Theme = iced::Theme;
+ type Flags = ();
+
+ fn new(_flags: ()) -> (Self, Command<Message>) {
+ (App::default(), Command::none())
+ }
+
+ fn title(&self) -> String {
+ String::from("Modal - Iced")
+ }
+
+ fn subscription(&self) -> Subscription<Self::Message> {
+ subscription::events().map(Message::Event)
+ }
+
+ fn update(&mut self, message: Message) -> Command<Message> {
+ match message {
+ Message::ShowModal => {
+ self.show_modal = true;
+ widget::focus_next()
+ }
+ Message::HideModal => {
+ self.hide_modal();
+ Command::none()
+ }
+ Message::Email(email) => {
+ self.email = email;
+ Command::none()
+ }
+ Message::Password(password) => {
+ self.password = password;
+ Command::none()
+ }
+ Message::Submit => {
+ if !self.email.is_empty() && !self.password.is_empty() {
+ self.hide_modal();
+ }
+
+ Command::none()
+ }
+ Message::Event(event) => match event {
+ Event::Keyboard(keyboard::Event::KeyPressed {
+ key_code: keyboard::KeyCode::Tab,
+ modifiers,
+ }) => {
+ if modifiers.shift() {
+ widget::focus_previous()
+ } else {
+ widget::focus_next()
+ }
+ }
+ Event::Keyboard(keyboard::Event::KeyPressed {
+ key_code: keyboard::KeyCode::Escape,
+ ..
+ }) => {
+ self.hide_modal();
+ Command::none()
+ }
+ _ => Command::none(),
+ },
+ }
+ }
+
+ fn view(&self) -> Element<Message> {
+ let content = container(
+ column![
+ row![
+ text("Top Left"),
+ horizontal_space(Length::Fill),
+ text("Top Right")
+ ]
+ .align_items(Alignment::Start)
+ .height(Length::Fill),
+ container(
+ button(text("Show Modal")).on_press(Message::ShowModal)
+ )
+ .center_x()
+ .center_y()
+ .width(Length::Fill)
+ .height(Length::Fill),
+ row![
+ text("Bottom Left"),
+ horizontal_space(Length::Fill),
+ text("Bottom Right")
+ ]
+ .align_items(Alignment::End)
+ .height(Length::Fill),
+ ]
+ .height(Length::Fill),
+ )
+ .padding(10)
+ .width(Length::Fill)
+ .height(Length::Fill);
+
+ if self.show_modal {
+ let modal = container(
+ column![
+ text("Sign Up").size(24),
+ column![
+ column![
+ text("Email").size(12),
+ text_input(
+ "abc@123.com",
+ &self.email,
+ Message::Email
+ )
+ .on_submit(Message::Submit)
+ .padding(5),
+ ]
+ .spacing(5),
+ column![
+ text("Password").size(12),
+ text_input("", &self.password, Message::Password)
+ .on_submit(Message::Submit)
+ .password()
+ .padding(5),
+ ]
+ .spacing(5),
+ button(text("Submit")).on_press(Message::HideModal),
+ ]
+ .spacing(10)
+ ]
+ .spacing(20),
+ )
+ .width(Length::Units(300))
+ .padding(10)
+ .style(theme::Container::Box);
+
+ Modal::new(content, modal)
+ .on_blur(Message::HideModal)
+ .into()
+ } else {
+ content.into()
+ }
+ }
+}
+
+impl App {
+ fn hide_modal(&mut self) {
+ self.show_modal = false;
+ self.email.clear();
+ self.password.clear();
+ }
+}
+
+mod modal {
+ use iced_native::alignment::Alignment;
+ use iced_native::widget::{self, Tree};
+ use iced_native::{
+ event, layout, mouse, overlay, renderer, Clipboard, Color, Element,
+ Event, Layout, Length, Point, Rectangle, Shell, Size, Widget,
+ };
+
+ /// A widget that centers a modal element over some base element
+ pub struct Modal<'a, Message, Renderer> {
+ base: Element<'a, Message, Renderer>,
+ modal: Element<'a, Message, Renderer>,
+ on_blur: Option<Message>,
+ }
+
+ impl<'a, Message, Renderer> Modal<'a, Message, Renderer> {
+ /// Returns a new [`Modal`]
+ pub fn new(
+ base: impl Into<Element<'a, Message, Renderer>>,
+ modal: impl Into<Element<'a, Message, Renderer>>,
+ ) -> Self {
+ Self {
+ base: base.into(),
+ modal: modal.into(),
+ on_blur: None,
+ }
+ }
+
+ /// Sets the message that will be produces when the background
+ /// of the [`Modal`] is pressed
+ pub fn on_blur(self, on_blur: Message) -> Self {
+ Self {
+ on_blur: Some(on_blur),
+ ..self
+ }
+ }
+ }
+
+ impl<'a, Message, Renderer> Widget<Message, Renderer>
+ for Modal<'a, Message, Renderer>
+ where
+ Renderer: iced_native::Renderer,
+ Message: Clone,
+ {
+ fn children(&self) -> Vec<Tree> {
+ vec![Tree::new(&self.base), Tree::new(&self.modal)]
+ }
+
+ fn diff(&self, tree: &mut Tree) {
+ tree.diff_children(&[&self.base, &self.modal]);
+ }
+
+ fn width(&self) -> Length {
+ self.base.as_widget().width()
+ }
+
+ fn height(&self) -> Length {
+ self.base.as_widget().height()
+ }
+
+ fn layout(
+ &self,
+ renderer: &Renderer,
+ limits: &layout::Limits,
+ ) -> layout::Node {
+ self.base.as_widget().layout(renderer, limits)
+ }
+
+ fn on_event(
+ &mut self,
+ state: &mut Tree,
+ event: Event,
+ layout: Layout<'_>,
+ cursor_position: Point,
+ renderer: &Renderer,
+ clipboard: &mut dyn Clipboard,
+ shell: &mut Shell<'_, Message>,
+ ) -> event::Status {
+ self.base.as_widget_mut().on_event(
+ &mut state.children[0],
+ event,
+ layout,
+ cursor_position,
+ renderer,
+ clipboard,
+ shell,
+ )
+ }
+
+ fn draw(
+ &self,
+ state: &Tree,
+ renderer: &mut Renderer,
+ theme: &<Renderer as iced_native::Renderer>::Theme,
+ style: &renderer::Style,
+ layout: Layout<'_>,
+ cursor_position: Point,
+ viewport: &Rectangle,
+ ) {
+ self.base.as_widget().draw(
+ &state.children[0],
+ renderer,
+ theme,
+ style,
+ layout,
+ cursor_position,
+ viewport,
+ );
+ }
+
+ fn overlay<'b>(
+ &'b mut self,
+ state: &'b mut Tree,
+ layout: Layout<'_>,
+ _renderer: &Renderer,
+ ) -> Option<overlay::Element<'b, Message, Renderer>> {
+ Some(overlay::Element::new(
+ layout.position(),
+ Box::new(Overlay {
+ content: &mut self.modal,
+ tree: &mut state.children[1],
+ size: layout.bounds().size(),
+ on_blur: self.on_blur.clone(),
+ }),
+ ))
+ }
+
+ fn mouse_interaction(
+ &self,
+ state: &Tree,
+ layout: Layout<'_>,
+ cursor_position: Point,
+ viewport: &Rectangle,
+ renderer: &Renderer,
+ ) -> mouse::Interaction {
+ self.base.as_widget().mouse_interaction(
+ &state.children[0],
+ layout,
+ cursor_position,
+ viewport,
+ renderer,
+ )
+ }
+
+ fn operate(
+ &self,
+ state: &mut Tree,
+ layout: Layout<'_>,
+ operation: &mut dyn widget::Operation<Message>,
+ ) {
+ self.base.as_widget().operate(
+ &mut state.children[0],
+ layout,
+ operation,
+ );
+ }
+ }
+
+ struct Overlay<'a, 'b, Message, Renderer> {
+ content: &'b mut Element<'a, Message, Renderer>,
+ tree: &'b mut Tree,
+ size: Size,
+ on_blur: Option<Message>,
+ }
+
+ impl<'a, 'b, Message, Renderer> overlay::Overlay<Message, Renderer>
+ for Overlay<'a, 'b, Message, Renderer>
+ where
+ Renderer: iced_native::Renderer,
+ Message: Clone,
+ {
+ fn layout(
+ &self,
+ renderer: &Renderer,
+ _bounds: Size,
+ position: Point,
+ ) -> layout::Node {
+ let limits = layout::Limits::new(Size::ZERO, self.size)
+ .width(Length::Fill)
+ .height(Length::Fill);
+
+ let mut child = self.content.as_widget().layout(renderer, &limits);
+ child.align(Alignment::Center, Alignment::Center, limits.max());
+
+ let mut node = layout::Node::with_children(self.size, vec![child]);
+ node.move_to(position);
+
+ node
+ }
+
+ fn on_event(
+ &mut self,
+ event: Event,
+ layout: Layout<'_>,
+ cursor_position: Point,
+ renderer: &Renderer,
+ clipboard: &mut dyn Clipboard,
+ shell: &mut Shell<'_, Message>,
+ ) -> event::Status {
+ let content_bounds = layout.children().next().unwrap().bounds();
+
+ if let Some(message) = self.on_blur.as_ref() {
+ if let Event::Mouse(mouse::Event::ButtonPressed(
+ mouse::Button::Left,
+ )) = &event
+ {
+ if !content_bounds.contains(cursor_position) {
+ shell.publish(message.clone());
+ return event::Status::Captured;
+ }
+ }
+ }
+
+ self.content.as_widget_mut().on_event(
+ self.tree,
+ event,
+ layout.children().next().unwrap(),
+ cursor_position,
+ renderer,
+ clipboard,
+ shell,
+ )
+ }
+
+ fn draw(
+ &self,
+ renderer: &mut Renderer,
+ theme: &Renderer::Theme,
+ style: &renderer::Style,
+ layout: Layout<'_>,
+ cursor_position: Point,
+ ) {
+ renderer.fill_quad(
+ renderer::Quad {
+ bounds: layout.bounds(),
+ border_radius: 0.0,
+ border_width: 0.0,
+ border_color: Color::TRANSPARENT,
+ },
+ Color {
+ a: 0.80,
+ ..Color::BLACK
+ },
+ );
+
+ self.content.as_widget().draw(
+ self.tree,
+ renderer,
+ theme,
+ style,
+ layout.children().next().unwrap(),
+ cursor_position,
+ &layout.bounds(),
+ );
+ }
+
+ fn operate(
+ &mut self,
+ layout: Layout<'_>,
+ operation: &mut dyn widget::Operation<Message>,
+ ) {
+ self.content.as_widget().operate(
+ self.tree,
+ layout.children().next().unwrap(),
+ operation,
+ );
+ }
+
+ fn mouse_interaction(
+ &self,
+ layout: Layout<'_>,
+ cursor_position: Point,
+ viewport: &Rectangle,
+ renderer: &Renderer,
+ ) -> mouse::Interaction {
+ self.content.as_widget().mouse_interaction(
+ self.tree,
+ layout.children().next().unwrap(),
+ cursor_position,
+ viewport,
+ renderer,
+ )
+ }
+ }
+
+ impl<'a, Message, Renderer> From<Modal<'a, Message, Renderer>>
+ for Element<'a, Message, Renderer>
+ where
+ Renderer: 'a + iced_native::Renderer,
+ Message: 'a + Clone,
+ {
+ fn from(modal: Modal<'a, Message, Renderer>) -> Self {
+ Element::new(modal)
+ }
+ }
+}
diff --git a/examples/modern_art/Cargo.toml b/examples/modern_art/Cargo.toml
index a48361ae..4242d209 100644
--- a/examples/modern_art/Cargo.toml
+++ b/examples/modern_art/Cargo.toml
@@ -8,3 +8,4 @@ publish = false
[dependencies]
iced = { path = "../..", features = ["canvas", "tokio", "debug"] }
rand = "0.8.5"
+env_logger = "0.9"
diff --git a/examples/modern_art/src/main.rs b/examples/modern_art/src/main.rs
index 0dd21c74..28ed3e21 100644
--- a/examples/modern_art/src/main.rs
+++ b/examples/modern_art/src/main.rs
@@ -9,6 +9,8 @@ use iced::{
use rand::{thread_rng, Rng};
fn main() -> iced::Result {
+ env_logger::builder().format_timestamp(None).init();
+
ModernArt::run(Settings {
antialiasing: true,
..Settings::default()
diff --git a/examples/pane_grid/src/main.rs b/examples/pane_grid/src/main.rs
index ae8fa22b..c9f1376c 100644
--- a/examples/pane_grid/src/main.rs
+++ b/examples/pane_grid/src/main.rs
@@ -29,6 +29,8 @@ enum Message {
Dragged(pane_grid::DragEvent),
Resized(pane_grid::ResizeEvent),
TogglePin(pane_grid::Pane),
+ Maximize(pane_grid::Pane),
+ Restore,
Close(pane_grid::Pane),
CloseFocused,
}
@@ -114,6 +116,10 @@ impl Application for Example {
*is_pinned = !*is_pinned;
}
}
+ Message::Maximize(pane) => self.panes.maximize(&pane),
+ Message::Restore => {
+ self.panes.restore();
+ }
Message::Close(pane) => {
if let Some((_, sibling)) = self.panes.close(&pane) {
self.focus = Some(sibling);
@@ -157,7 +163,7 @@ impl Application for Example {
let focus = self.focus;
let total_panes = self.panes.len();
- let pane_grid = PaneGrid::new(&self.panes, |id, pane| {
+ let pane_grid = PaneGrid::new(&self.panes, |id, pane, is_maximized| {
let is_focused = focus == Some(id);
let pin_button = button(
@@ -178,7 +184,12 @@ impl Application for Example {
.spacing(5);
let title_bar = pane_grid::TitleBar::new(title)
- .controls(view_controls(id, total_panes, pane.is_pinned))
+ .controls(view_controls(
+ id,
+ total_panes,
+ pane.is_pinned,
+ is_maximized,
+ ))
.padding(10)
.style(if is_focused {
style::title_bar_focused
@@ -314,16 +325,35 @@ fn view_controls<'a>(
pane: pane_grid::Pane,
total_panes: usize,
is_pinned: bool,
+ is_maximized: bool,
) -> Element<'a, Message> {
- let mut button = button(text("Close").size(14))
+ let mut row = row![].spacing(5);
+
+ if total_panes > 1 {
+ let toggle = {
+ let (content, message) = if is_maximized {
+ ("Restore", Message::Restore)
+ } else {
+ ("Maximize", Message::Maximize(pane))
+ };
+ button(text(content).size(14))
+ .style(theme::Button::Secondary)
+ .padding(3)
+ .on_press(message)
+ };
+
+ row = row.push(toggle);
+ }
+
+ let mut close = button(text("Close").size(14))
.style(theme::Button::Destructive)
.padding(3);
if total_panes > 1 && !is_pinned {
- button = button.on_press(Message::Close(pane));
+ close = close.on_press(Message::Close(pane));
}
- button.into()
+ row.push(close).into()
}
mod style {
diff --git a/examples/solar_system/src/main.rs b/examples/solar_system/src/main.rs
index 56787a99..9e303576 100644
--- a/examples/solar_system/src/main.rs
+++ b/examples/solar_system/src/main.rs
@@ -79,10 +79,14 @@ impl Application for SolarSystem {
}
fn style(&self) -> theme::Application {
- theme::Application::Custom(|_theme| application::Appearance {
- background_color: Color::BLACK,
- text_color: Color::WHITE,
- })
+ fn dark_background(_theme: &Theme) -> application::Appearance {
+ application::Appearance {
+ background_color: Color::BLACK,
+ text_color: Color::WHITE,
+ }
+ }
+
+ theme::Application::from(dark_background as fn(&Theme) -> _)
}
fn subscription(&self) -> Subscription<Message> {
diff --git a/examples/todos/src/main.rs b/examples/todos/src/main.rs
index be48ae8c..690d9c09 100644
--- a/examples/todos/src/main.rs
+++ b/examples/todos/src/main.rs
@@ -131,7 +131,11 @@ impl Application for Todos {
task.update(task_message);
if should_focus {
- text_input::focus(Task::text_input_id(i))
+ let id = Task::text_input_id(i);
+ Command::batch(vec![
+ text_input::focus(id.clone()),
+ text_input::select_all(id),
+ ])
} else {
Command::none()
}
diff --git a/futures/Cargo.toml b/futures/Cargo.toml
index 61ee00a5..b69cb59b 100644
--- a/futures/Cargo.toml
+++ b/futures/Cargo.toml
@@ -1,6 +1,6 @@
[package]
name = "iced_futures"
-version = "0.4.1"
+version = "0.5.1"
authors = ["Héctor Ramón Jiménez <hector0193@gmail.com>"]
edition = "2021"
description = "Commands, subscriptions, and runtimes for Iced"
diff --git a/futures/src/subscription.rs b/futures/src/subscription.rs
index 0479c63c..30cf7581 100644
--- a/futures/src/subscription.rs
+++ b/futures/src/subscription.rs
@@ -125,9 +125,9 @@ impl<I, O, H> std::fmt::Debug for Subscription<I, O, H> {
/// - [`stopwatch`], a watch with start/stop and reset buttons showcasing how
/// to listen to time.
///
-/// [examples]: https://github.com/iced-rs/iced/tree/0.4/examples
-/// [`download_progress`]: https://github.com/iced-rs/iced/tree/0.4/examples/download_progress
-/// [`stopwatch`]: https://github.com/iced-rs/iced/tree/0.4/examples/stopwatch
+/// [examples]: https://github.com/iced-rs/iced/tree/0.5/examples
+/// [`download_progress`]: https://github.com/iced-rs/iced/tree/0.5/examples/download_progress
+/// [`stopwatch`]: https://github.com/iced-rs/iced/tree/0.5/examples/stopwatch
pub trait Recipe<Hasher: std::hash::Hasher, Event> {
/// The events that will be produced by a [`Subscription`] with this
/// [`Recipe`].
diff --git a/glow/Cargo.toml b/glow/Cargo.toml
index 18215e9b..1977f4b6 100644
--- a/glow/Cargo.toml
+++ b/glow/Cargo.toml
@@ -1,6 +1,6 @@
[package]
name = "iced_glow"
-version = "0.3.0"
+version = "0.4.1"
authors = ["Héctor Ramón Jiménez <hector0193@gmail.com>"]
edition = "2021"
description = "A glow renderer for iced"
@@ -8,12 +8,22 @@ license = "MIT AND OFL-1.1"
repository = "https://github.com/iced-rs/iced"
[features]
+svg = ["iced_graphics/svg"]
+image = ["iced_graphics/image"]
+png = ["iced_graphics/png"]
+jpeg = ["iced_graphics/jpeg"]
+jpeg_rayon = ["iced_graphics/jpeg_rayon"]
+gif = ["iced_graphics/gif"]
+webp = ["iced_graphics/webp"]
+pnm = ["iced_graphics/pnm"]
+ico = ["iced_graphics/ico"]
+bmp = ["iced_graphics/bmp"]
+hdr = ["iced_graphics/hdr"]
+dds = ["iced_graphics/dds"]
+farbfeld = ["iced_graphics/farbfeld"]
canvas = ["iced_graphics/canvas"]
qr_code = ["iced_graphics/qr_code"]
default_system_font = ["iced_graphics/font-source"]
-# Not supported yet!
-image = []
-svg = []
[dependencies]
glow = "0.11.1"
@@ -24,11 +34,11 @@ bytemuck = "1.4"
log = "0.4"
[dependencies.iced_native]
-version = "0.5"
+version = "0.6"
path = "../native"
[dependencies.iced_graphics]
-version = "0.3"
+version = "0.4"
path = "../graphics"
features = ["font-fallback", "font-icons", "opengl"]
diff --git a/glow/src/backend.rs b/glow/src/backend.rs
index 21af2ecf..416c3b94 100644
--- a/glow/src/backend.rs
+++ b/glow/src/backend.rs
@@ -1,3 +1,5 @@
+#[cfg(any(feature = "image", feature = "svg"))]
+use crate::image;
use crate::quad;
use crate::text;
use crate::{program, triangle};
@@ -15,6 +17,8 @@ use iced_native::{Font, Size};
/// [`iced`]: https://github.com/iced-rs/iced
#[derive(Debug)]
pub struct Backend {
+ #[cfg(any(feature = "image", feature = "svg"))]
+ image_pipeline: image::Pipeline,
quad_pipeline: quad::Pipeline,
text_pipeline: text::Pipeline,
triangle_pipeline: triangle::Pipeline,
@@ -32,10 +36,14 @@ impl Backend {
let shader_version = program::Version::new(gl);
+ #[cfg(any(feature = "image", feature = "svg"))]
+ let image_pipeline = image::Pipeline::new(gl, &shader_version);
let quad_pipeline = quad::Pipeline::new(gl, &shader_version);
let triangle_pipeline = triangle::Pipeline::new(gl, &shader_version);
Self {
+ #[cfg(any(feature = "image", feature = "svg"))]
+ image_pipeline,
quad_pipeline,
text_pipeline,
triangle_pipeline,
@@ -70,6 +78,9 @@ impl Backend {
viewport_size.height,
);
}
+
+ #[cfg(any(feature = "image", feature = "svg"))]
+ self.image_pipeline.trim_cache(gl);
}
fn flush(
@@ -112,6 +123,21 @@ impl Backend {
);
}
+ #[cfg(any(feature = "image", feature = "svg"))]
+ if !layer.images.is_empty() {
+ let scaled = transformation
+ * Transformation::scale(scale_factor, scale_factor);
+
+ self.image_pipeline.draw(
+ gl,
+ target_height,
+ scaled,
+ scale_factor,
+ &layer.images,
+ bounds,
+ );
+ }
+
if !layer.text.is_empty() {
for text in layer.text.iter() {
// Target physical coordinates directly to avoid blurry text
@@ -238,8 +264,8 @@ impl backend::Text for Backend {
#[cfg(feature = "image")]
impl backend::Image for Backend {
- fn dimensions(&self, _handle: &iced_native::image::Handle) -> (u32, u32) {
- (50, 50)
+ fn dimensions(&self, handle: &iced_native::image::Handle) -> Size<u32> {
+ self.image_pipeline.dimensions(handle)
}
}
@@ -247,8 +273,8 @@ impl backend::Image for Backend {
impl backend::Svg for Backend {
fn viewport_dimensions(
&self,
- _handle: &iced_native::svg::Handle,
- ) -> (u32, u32) {
- (50, 50)
+ handle: &iced_native::svg::Handle,
+ ) -> Size<u32> {
+ self.image_pipeline.viewport_dimensions(handle)
}
}
diff --git a/glow/src/image.rs b/glow/src/image.rs
new file mode 100644
index 00000000..955fd1ab
--- /dev/null
+++ b/glow/src/image.rs
@@ -0,0 +1,243 @@
+mod storage;
+
+use storage::Storage;
+
+pub use iced_graphics::triangle::{Mesh2D, Vertex2D};
+
+use crate::program::{self, Shader};
+use crate::Transformation;
+
+#[cfg(feature = "image")]
+use iced_graphics::image::raster;
+
+#[cfg(feature = "svg")]
+use iced_graphics::image::vector;
+
+use iced_graphics::layer;
+use iced_graphics::Rectangle;
+use iced_graphics::Size;
+
+use glow::HasContext;
+
+use std::cell::RefCell;
+
+#[derive(Debug)]
+pub(crate) struct Pipeline {
+ program: <glow::Context as HasContext>::Program,
+ vertex_array: <glow::Context as HasContext>::VertexArray,
+ vertex_buffer: <glow::Context as HasContext>::Buffer,
+ transform_location: <glow::Context as HasContext>::UniformLocation,
+ storage: Storage,
+ #[cfg(feature = "image")]
+ raster_cache: RefCell<raster::Cache<Storage>>,
+ #[cfg(feature = "svg")]
+ vector_cache: RefCell<vector::Cache<Storage>>,
+}
+
+impl Pipeline {
+ pub fn new(
+ gl: &glow::Context,
+ shader_version: &program::Version,
+ ) -> Pipeline {
+ let program = unsafe {
+ let vertex_shader = Shader::vertex(
+ gl,
+ shader_version,
+ include_str!("shader/common/image.vert"),
+ );
+ let fragment_shader = Shader::fragment(
+ gl,
+ shader_version,
+ include_str!("shader/common/image.frag"),
+ );
+
+ program::create(
+ gl,
+ &[vertex_shader, fragment_shader],
+ &[(0, "i_Position")],
+ )
+ };
+
+ let transform_location =
+ unsafe { gl.get_uniform_location(program, "u_Transform") }
+ .expect("Get transform location");
+
+ unsafe {
+ gl.use_program(Some(program));
+
+ let transform: [f32; 16] = Transformation::identity().into();
+ gl.uniform_matrix_4_f32_slice(
+ Some(&transform_location),
+ false,
+ &transform,
+ );
+
+ gl.use_program(None);
+ }
+
+ let vertex_buffer =
+ unsafe { gl.create_buffer().expect("Create vertex buffer") };
+ let vertex_array =
+ unsafe { gl.create_vertex_array().expect("Create vertex array") };
+
+ unsafe {
+ gl.bind_vertex_array(Some(vertex_array));
+ gl.bind_buffer(glow::ARRAY_BUFFER, Some(vertex_buffer));
+
+ let vertices = &[0u8, 0, 1, 0, 0, 1, 1, 1];
+ gl.buffer_data_size(
+ glow::ARRAY_BUFFER,
+ vertices.len() as i32,
+ glow::STATIC_DRAW,
+ );
+ gl.buffer_sub_data_u8_slice(
+ glow::ARRAY_BUFFER,
+ 0,
+ bytemuck::cast_slice(vertices),
+ );
+
+ gl.enable_vertex_attrib_array(0);
+ gl.vertex_attrib_pointer_f32(
+ 0,
+ 2,
+ glow::UNSIGNED_BYTE,
+ false,
+ 0,
+ 0,
+ );
+
+ gl.bind_buffer(glow::ARRAY_BUFFER, None);
+ gl.bind_vertex_array(None);
+ }
+
+ Pipeline {
+ program,
+ vertex_array,
+ vertex_buffer,
+ transform_location,
+ storage: Storage::default(),
+ #[cfg(feature = "image")]
+ raster_cache: RefCell::new(raster::Cache::default()),
+ #[cfg(feature = "svg")]
+ vector_cache: RefCell::new(vector::Cache::default()),
+ }
+ }
+
+ #[cfg(feature = "image")]
+ pub fn dimensions(&self, handle: &iced_native::image::Handle) -> Size<u32> {
+ self.raster_cache.borrow_mut().load(handle).dimensions()
+ }
+
+ #[cfg(feature = "svg")]
+ pub fn viewport_dimensions(
+ &self,
+ handle: &iced_native::svg::Handle,
+ ) -> Size<u32> {
+ let mut cache = self.vector_cache.borrow_mut();
+ let svg = cache.load(handle);
+
+ svg.viewport_dimensions()
+ }
+
+ pub fn draw(
+ &mut self,
+ mut gl: &glow::Context,
+ target_height: u32,
+ transformation: Transformation,
+ _scale_factor: f32,
+ images: &[layer::Image],
+ layer_bounds: Rectangle<u32>,
+ ) {
+ unsafe {
+ gl.use_program(Some(self.program));
+ gl.bind_vertex_array(Some(self.vertex_array));
+ gl.bind_buffer(glow::ARRAY_BUFFER, Some(self.vertex_buffer));
+ gl.enable(glow::SCISSOR_TEST);
+ }
+
+ #[cfg(feature = "image")]
+ let mut raster_cache = self.raster_cache.borrow_mut();
+
+ #[cfg(feature = "svg")]
+ let mut vector_cache = self.vector_cache.borrow_mut();
+
+ for image in images {
+ let (entry, bounds) = match &image {
+ #[cfg(feature = "image")]
+ layer::Image::Raster { handle, bounds } => (
+ raster_cache.upload(handle, &mut gl, &mut self.storage),
+ bounds,
+ ),
+ #[cfg(not(feature = "image"))]
+ layer::Image::Raster { handle: _, bounds } => (None, bounds),
+
+ #[cfg(feature = "svg")]
+ layer::Image::Vector { handle, bounds } => {
+ let size = [bounds.width, bounds.height];
+ (
+ vector_cache.upload(
+ handle,
+ size,
+ _scale_factor,
+ &mut gl,
+ &mut self.storage,
+ ),
+ bounds,
+ )
+ }
+
+ #[cfg(not(feature = "svg"))]
+ layer::Image::Vector { handle: _, bounds } => (None, bounds),
+ };
+
+ unsafe {
+ gl.scissor(
+ layer_bounds.x as i32,
+ (target_height - (layer_bounds.y + layer_bounds.height))
+ as i32,
+ layer_bounds.width as i32,
+ layer_bounds.height as i32,
+ );
+
+ if let Some(storage::Entry { texture, .. }) = entry {
+ gl.bind_texture(glow::TEXTURE_2D, Some(*texture))
+ } else {
+ continue;
+ }
+
+ let translate = Transformation::translate(bounds.x, bounds.y);
+ let scale = Transformation::scale(bounds.width, bounds.height);
+ let transformation = transformation * translate * scale;
+ let matrix: [f32; 16] = transformation.into();
+ gl.uniform_matrix_4_f32_slice(
+ Some(&self.transform_location),
+ false,
+ &matrix,
+ );
+
+ gl.draw_arrays(glow::TRIANGLE_STRIP, 0, 4);
+
+ gl.bind_texture(glow::TEXTURE_2D, None);
+ }
+ }
+
+ unsafe {
+ gl.bind_buffer(glow::ARRAY_BUFFER, None);
+ gl.bind_vertex_array(None);
+ gl.use_program(None);
+ gl.disable(glow::SCISSOR_TEST);
+ }
+ }
+
+ pub fn trim_cache(&mut self, mut gl: &glow::Context) {
+ #[cfg(feature = "image")]
+ self.raster_cache
+ .borrow_mut()
+ .trim(&mut self.storage, &mut gl);
+
+ #[cfg(feature = "svg")]
+ self.vector_cache
+ .borrow_mut()
+ .trim(&mut self.storage, &mut gl);
+ }
+}
diff --git a/glow/src/image/storage.rs b/glow/src/image/storage.rs
new file mode 100644
index 00000000..9bc20641
--- /dev/null
+++ b/glow/src/image/storage.rs
@@ -0,0 +1,78 @@
+use iced_graphics::image;
+use iced_graphics::Size;
+
+use glow::HasContext;
+
+#[derive(Debug, Default)]
+pub struct Storage;
+
+impl image::Storage for Storage {
+ type Entry = Entry;
+ type State<'a> = &'a glow::Context;
+
+ fn upload(
+ &mut self,
+ width: u32,
+ height: u32,
+ data: &[u8],
+ gl: &mut &glow::Context,
+ ) -> Option<Self::Entry> {
+ unsafe {
+ let texture = gl.create_texture().expect("create texture");
+ gl.bind_texture(glow::TEXTURE_2D, Some(texture));
+ gl.tex_image_2d(
+ glow::TEXTURE_2D,
+ 0,
+ glow::SRGB8_ALPHA8 as i32,
+ width as i32,
+ height as i32,
+ 0,
+ glow::RGBA,
+ glow::UNSIGNED_BYTE,
+ Some(data),
+ );
+ gl.tex_parameter_i32(
+ glow::TEXTURE_2D,
+ glow::TEXTURE_WRAP_S,
+ glow::CLAMP_TO_EDGE as _,
+ );
+ gl.tex_parameter_i32(
+ glow::TEXTURE_2D,
+ glow::TEXTURE_WRAP_T,
+ glow::CLAMP_TO_EDGE as _,
+ );
+ gl.tex_parameter_i32(
+ glow::TEXTURE_2D,
+ glow::TEXTURE_MIN_FILTER,
+ glow::LINEAR as _,
+ );
+ gl.tex_parameter_i32(
+ glow::TEXTURE_2D,
+ glow::TEXTURE_MAG_FILTER,
+ glow::LINEAR as _,
+ );
+ gl.bind_texture(glow::TEXTURE_2D, None);
+
+ Some(Entry {
+ size: Size::new(width, height),
+ texture,
+ })
+ }
+ }
+
+ fn remove(&mut self, entry: &Entry, gl: &mut &glow::Context) {
+ unsafe { gl.delete_texture(entry.texture) }
+ }
+}
+
+#[derive(Debug)]
+pub struct Entry {
+ size: Size<u32>,
+ pub(super) texture: glow::NativeTexture,
+}
+
+impl image::storage::Entry for Entry {
+ fn size(&self) -> Size<u32> {
+ self.size
+ }
+}
diff --git a/glow/src/lib.rs b/glow/src/lib.rs
index de9c0002..e6ca0562 100644
--- a/glow/src/lib.rs
+++ b/glow/src/lib.rs
@@ -3,7 +3,7 @@
//! ![The native path of the Iced ecosystem](https://github.com/iced-rs/iced/blob/0525d76ff94e828b7b21634fa94a747022001c83/docs/graphs/native.png?raw=true)
//!
//! [`glow`]: https://github.com/grovesNL/glow
-//! [`iced_native`]: https://github.com/iced-rs/iced/tree/0.4/native
+//! [`iced_native`]: https://github.com/iced-rs/iced/tree/0.5/native
#![doc(
html_logo_url = "https://raw.githubusercontent.com/iced-rs/iced/9ab6923e943f784985e9ef9ca28b10278297225d/docs/logo.svg"
)]
@@ -24,6 +24,8 @@
pub use glow;
mod backend;
+#[cfg(any(feature = "image", feature = "svg"))]
+mod image;
mod program;
mod quad;
mod text;
diff --git a/glow/src/shader/common/gradient.frag b/glow/src/shader/common/gradient.frag
index 42d0201f..9af0cb6e 100644
--- a/glow/src/shader/common/gradient.frag
+++ b/glow/src/shader/common/gradient.frag
@@ -1,20 +1,20 @@
#ifdef GL_ES
- #ifdef GL_FRAGMENT_PRECISION_HIGH
- precision highp float;
- #else
- precision mediump float;
- #endif
+#ifdef GL_FRAGMENT_PRECISION_HIGH
+precision highp float;
+#else
+precision mediump float;
+#endif
#endif
#ifdef HIGHER_THAN_300
- layout (location = 0) out vec4 fragColor;
- #define gl_FragColor fragColor
+layout (location = 0) out vec4 fragColor;
+#define gl_FragColor fragColor
#endif
in vec2 raw_position;
uniform vec4 gradient_direction;
-uniform uint color_stops_size;
+uniform int color_stops_size;
// GLSL does not support dynamically sized arrays without SSBOs so this is capped to 16 stops
//stored as color(vec4) -> offset(vec4) sequentially;
uniform vec4 color_stops[32];
@@ -28,23 +28,23 @@ void main() {
vec2 unit = normalize(gradient_vec);
float coord_offset = dot(unit, current_vec) / length(gradient_vec);
//if a gradient has a start/end stop that is identical, the mesh will have a transparent fill
- fragColor = vec4(0.0, 0.0, 0.0, 0.0);
+ gl_FragColor = vec4(0.0, 0.0, 0.0, 0.0);
float min_offset = color_stops[1].x;
- float max_offset = color_stops[color_stops_size - 1u].x;
+ float max_offset = color_stops[color_stops_size - 1].x;
- for (uint i = 0u; i < color_stops_size - 2u; i += 2u) {
- float curr_offset = color_stops[i+1u].x;
- float next_offset = color_stops[i+3u].x;
+ for (int i = 0; i < color_stops_size - 2; i += 2) {
+ float curr_offset = color_stops[i+1].x;
+ float next_offset = color_stops[i+3].x;
if (coord_offset <= min_offset) {
//current coordinate is before the first defined offset, set it to the start color
- fragColor = color_stops[0];
+ gl_FragColor = color_stops[0];
}
if (curr_offset <= coord_offset && coord_offset <= next_offset) {
//current fragment is between the current offset processing & the next one, interpolate colors
- fragColor = mix(color_stops[i], color_stops[i+2u], smoothstep(
+ gl_FragColor = mix(color_stops[i], color_stops[i+2], smoothstep(
curr_offset,
next_offset,
coord_offset
@@ -53,7 +53,7 @@ void main() {
if (coord_offset >= max_offset) {
//current coordinate is before the last defined offset, set it to the last color
- fragColor = color_stops[color_stops_size - 2u];
+ gl_FragColor = color_stops[color_stops_size - 2];
}
}
}
diff --git a/glow/src/shader/common/triangle.vert b/glow/src/shader/common/gradient.vert
index fe505997..fe505997 100644
--- a/glow/src/shader/common/triangle.vert
+++ b/glow/src/shader/common/gradient.vert
diff --git a/glow/src/shader/common/image.frag b/glow/src/shader/common/image.frag
new file mode 100644
index 00000000..5e05abdf
--- /dev/null
+++ b/glow/src/shader/common/image.frag
@@ -0,0 +1,22 @@
+#ifdef GL_ES
+#ifdef GL_FRAGMENT_PRECISION_HIGH
+precision highp float;
+#else
+precision mediump float;
+#endif
+#endif
+
+uniform sampler2D tex;
+in vec2 tex_pos;
+
+#ifdef HIGHER_THAN_300
+out vec4 fragColor;
+#define gl_FragColor fragColor
+#endif
+#ifdef GL_ES
+#define texture texture2D
+#endif
+
+void main() {
+ gl_FragColor = texture(tex, tex_pos);
+}
diff --git a/glow/src/shader/common/image.vert b/glow/src/shader/common/image.vert
new file mode 100644
index 00000000..93e541f2
--- /dev/null
+++ b/glow/src/shader/common/image.vert
@@ -0,0 +1,9 @@
+uniform mat4 u_Transform;
+
+in vec2 i_Position;
+out vec2 tex_pos;
+
+void main() {
+ gl_Position = u_Transform * vec4(i_Position, 0.0, 1.0);
+ tex_pos = i_Position;
+}
diff --git a/glow/src/shader/common/triangle.frag b/glow/src/shader/common/solid.frag
index ead40fe5..174ffdd3 100644
--- a/glow/src/shader/common/triangle.frag
+++ b/glow/src/shader/common/solid.frag
@@ -11,8 +11,8 @@ out vec4 fragColor;
#define gl_FragColor fragColor
#endif
-uniform vec4 color;
+in vec4 v_Color;
void main() {
- fragColor = color;
+ gl_FragColor = v_Color;
}
diff --git a/glow/src/shader/common/solid.vert b/glow/src/shader/common/solid.vert
new file mode 100644
index 00000000..59ed88e5
--- /dev/null
+++ b/glow/src/shader/common/solid.vert
@@ -0,0 +1,11 @@
+uniform mat4 u_Transform;
+
+in vec2 i_Position;
+in vec4 i_Color;
+
+out vec4 v_Color;
+
+void main() {
+ gl_Position = u_Transform * vec4(i_Position, 0.0, 1.0);
+ v_Color = i_Color;
+}
diff --git a/glow/src/triangle.rs b/glow/src/triangle.rs
index 0e27bcf2..d0205e08 100644
--- a/glow/src/triangle.rs
+++ b/glow/src/triangle.rs
@@ -1,74 +1,52 @@
//! Draw meshes of triangles.
-mod gradient;
-mod solid;
-
use crate::program;
use crate::Transformation;
+use iced_graphics::gradient::Gradient;
use iced_graphics::layer::mesh::{self, Mesh};
-use iced_graphics::triangle::{self, Vertex2D};
+use iced_graphics::triangle::{ColoredVertex2D, Vertex2D};
use glow::HasContext;
use std::marker::PhantomData;
+const DEFAULT_VERTICES: usize = 1_000;
+const DEFAULT_INDICES: usize = 1_000;
+
#[derive(Debug)]
pub(crate) struct Pipeline {
- vertex_array: <glow::Context as HasContext>::VertexArray,
- vertices: Buffer<Vertex2D>,
indices: Buffer<u32>,
- programs: ProgramList,
-}
-
-#[derive(Debug)]
-struct ProgramList {
solid: solid::Program,
gradient: gradient::Program,
}
impl Pipeline {
pub fn new(gl: &glow::Context, shader_version: &program::Version) -> Self {
- let vertex_array =
- unsafe { gl.create_vertex_array().expect("Create vertex array") };
-
- unsafe {
- gl.bind_vertex_array(Some(vertex_array));
- }
-
- let vertices = unsafe {
- Buffer::new(
- gl,
- glow::ARRAY_BUFFER,
- glow::DYNAMIC_DRAW,
- std::mem::size_of::<Vertex2D>() as usize,
- )
- };
-
- let indices = unsafe {
+ let mut indices = unsafe {
Buffer::new(
gl,
glow::ELEMENT_ARRAY_BUFFER,
glow::DYNAMIC_DRAW,
- std::mem::size_of::<u32>() as usize,
+ DEFAULT_INDICES,
)
};
+ let solid = solid::Program::new(gl, shader_version);
+ let gradient = gradient::Program::new(gl, shader_version);
+
unsafe {
- let stride = std::mem::size_of::<Vertex2D>() as i32;
+ gl.bind_vertex_array(Some(solid.vertex_array));
+ indices.bind(gl, 0);
- gl.enable_vertex_attrib_array(0);
- gl.vertex_attrib_pointer_f32(0, 2, glow::FLOAT, false, stride, 0);
+ gl.bind_vertex_array(Some(gradient.vertex_array));
+ indices.bind(gl, 0);
gl.bind_vertex_array(None);
- };
+ }
Self {
- vertex_array,
- vertices,
indices,
- programs: ProgramList {
- solid: solid::Program::new(gl, shader_version),
- gradient: gradient::Program::new(gl, shader_version),
- },
+ solid,
+ gradient,
}
}
@@ -83,50 +61,83 @@ impl Pipeline {
unsafe {
gl.enable(glow::MULTISAMPLE);
gl.enable(glow::SCISSOR_TEST);
- gl.bind_vertex_array(Some(self.vertex_array))
}
- //count the total amount of vertices & indices we need to handle
- let (total_vertices, total_indices) = mesh::attribute_count_of(meshes);
+ // Count the total amount of vertices & indices we need to handle
+ let count = mesh::attribute_count_of(meshes);
// Then we ensure the current attribute buffers are big enough, resizing if necessary
unsafe {
- self.vertices.bind(gl, total_vertices);
- self.indices.bind(gl, total_indices);
+ self.indices.bind(gl, count.indices);
}
// We upload all the vertices and indices upfront
- let mut vertex_offset = 0;
+ let mut solid_vertex_offset = 0;
+ let mut gradient_vertex_offset = 0;
let mut index_offset = 0;
for mesh in meshes {
- unsafe {
- gl.buffer_sub_data_u8_slice(
- glow::ARRAY_BUFFER,
- (vertex_offset * std::mem::size_of::<Vertex2D>()) as i32,
- bytemuck::cast_slice(&mesh.buffers.vertices),
- );
+ let indices = mesh.indices();
+ unsafe {
gl.buffer_sub_data_u8_slice(
glow::ELEMENT_ARRAY_BUFFER,
(index_offset * std::mem::size_of::<u32>()) as i32,
- bytemuck::cast_slice(&mesh.buffers.indices),
+ bytemuck::cast_slice(indices),
);
- vertex_offset += mesh.buffers.vertices.len();
- index_offset += mesh.buffers.indices.len();
+ index_offset += indices.len();
+ }
+
+ match mesh {
+ Mesh::Solid { buffers, .. } => {
+ unsafe {
+ self.solid.vertices.bind(gl, count.solid_vertices);
+
+ gl.buffer_sub_data_u8_slice(
+ glow::ARRAY_BUFFER,
+ (solid_vertex_offset
+ * std::mem::size_of::<ColoredVertex2D>())
+ as i32,
+ bytemuck::cast_slice(&buffers.vertices),
+ );
+ }
+
+ solid_vertex_offset += buffers.vertices.len();
+ }
+ Mesh::Gradient { buffers, .. } => {
+ unsafe {
+ self.gradient
+ .vertices
+ .bind(gl, count.gradient_vertices);
+
+ gl.buffer_sub_data_u8_slice(
+ glow::ARRAY_BUFFER,
+ (gradient_vertex_offset
+ * std::mem::size_of::<Vertex2D>())
+ as i32,
+ bytemuck::cast_slice(&buffers.vertices),
+ );
+ }
+
+ gradient_vertex_offset += buffers.vertices.len();
+ }
}
}
// Then we draw each mesh using offsets
- let mut last_vertex = 0;
+ let mut last_solid_vertex = 0;
+ let mut last_gradient_vertex = 0;
let mut last_index = 0;
for mesh in meshes {
- let transform = transformation
- * Transformation::translate(mesh.origin.x, mesh.origin.y);
+ let indices = mesh.indices();
+ let origin = mesh.origin();
+
+ let transform =
+ transformation * Transformation::translate(origin.x, origin.y);
- let clip_bounds = (mesh.clip_bounds * scale_factor).snap();
+ let clip_bounds = (mesh.clip_bounds() * scale_factor).snap();
unsafe {
gl.scissor(
@@ -136,29 +147,126 @@ impl Pipeline {
clip_bounds.width as i32,
clip_bounds.height as i32,
);
+ }
+
+ match mesh {
+ Mesh::Solid { buffers, .. } => unsafe {
+ gl.use_program(Some(self.solid.program));
+ gl.bind_vertex_array(Some(self.solid.vertex_array));
- match mesh.style {
- triangle::Style::Solid(color) => {
- self.programs.solid.use_program(gl, color, &transform);
+ if transform != self.solid.uniforms.transform {
+ gl.uniform_matrix_4_f32_slice(
+ Some(&self.solid.uniforms.transform_location),
+ false,
+ transform.as_ref(),
+ );
+
+ self.solid.uniforms.transform = transform;
}
- triangle::Style::Gradient(gradient) => {
- self.programs
- .gradient
- .use_program(gl, gradient, &transform);
+
+ gl.draw_elements_base_vertex(
+ glow::TRIANGLES,
+ indices.len() as i32,
+ glow::UNSIGNED_INT,
+ (last_index * std::mem::size_of::<u32>()) as i32,
+ last_solid_vertex as i32,
+ );
+
+ last_solid_vertex += buffers.vertices.len();
+ },
+ Mesh::Gradient {
+ buffers, gradient, ..
+ } => unsafe {
+ gl.use_program(Some(self.gradient.program));
+ gl.bind_vertex_array(Some(self.gradient.vertex_array));
+
+ if transform != self.gradient.uniforms.transform {
+ gl.uniform_matrix_4_f32_slice(
+ Some(&self.gradient.uniforms.locations.transform),
+ false,
+ transform.as_ref(),
+ );
+
+ self.gradient.uniforms.transform = transform;
}
- }
- gl.draw_elements_base_vertex(
- glow::TRIANGLES,
- mesh.buffers.indices.len() as i32,
- glow::UNSIGNED_INT,
- (last_index * std::mem::size_of::<u32>()) as i32,
- last_vertex as i32,
- );
+ if &self.gradient.uniforms.gradient != *gradient {
+ match gradient {
+ Gradient::Linear(linear) => {
+ gl.uniform_4_f32(
+ Some(
+ &self
+ .gradient
+ .uniforms
+ .locations
+ .gradient_direction,
+ ),
+ linear.start.x,
+ linear.start.y,
+ linear.end.x,
+ linear.end.y,
+ );
+
+ gl.uniform_1_i32(
+ Some(
+ &self
+ .gradient
+ .uniforms
+ .locations
+ .color_stops_size,
+ ),
+ (linear.color_stops.len() * 2) as i32,
+ );
+
+ let mut stops = [0.0; 128];
+
+ for (index, stop) in linear
+ .color_stops
+ .iter()
+ .enumerate()
+ .take(16)
+ {
+ let [r, g, b, a] = stop.color.into_linear();
+
+ stops[index * 8] = r;
+ stops[(index * 8) + 1] = g;
+ stops[(index * 8) + 2] = b;
+ stops[(index * 8) + 3] = a;
+ stops[(index * 8) + 4] = stop.offset;
+ stops[(index * 8) + 5] = 0.;
+ stops[(index * 8) + 6] = 0.;
+ stops[(index * 8) + 7] = 0.;
+ }
+
+ gl.uniform_4_f32_slice(
+ Some(
+ &self
+ .gradient
+ .uniforms
+ .locations
+ .color_stops,
+ ),
+ &stops,
+ );
+ }
+ }
+
+ self.gradient.uniforms.gradient = (*gradient).clone();
+ }
+
+ gl.draw_elements_base_vertex(
+ glow::TRIANGLES,
+ indices.len() as i32,
+ glow::UNSIGNED_INT,
+ (last_index * std::mem::size_of::<u32>()) as i32,
+ last_gradient_vertex as i32,
+ );
- last_vertex += mesh.buffers.vertices.len();
- last_index += mesh.buffers.indices.len();
+ last_gradient_vertex += buffers.vertices.len();
+ },
}
+
+ last_index += indices.len();
}
unsafe {
@@ -169,47 +277,8 @@ impl Pipeline {
}
}
-/// A simple shader program. Uses [`triangle.vert`] for its vertex shader and only binds position
-/// attribute location.
-pub(super) fn program(
- gl: &glow::Context,
- shader_version: &program::Version,
- fragment_shader: &'static str,
-) -> <glow::Context as HasContext>::Program {
- unsafe {
- let vertex_shader = program::Shader::vertex(
- gl,
- shader_version,
- include_str!("shader/common/triangle.vert"),
- );
-
- let fragment_shader =
- program::Shader::fragment(gl, shader_version, fragment_shader);
-
- program::create(
- gl,
- &[vertex_shader, fragment_shader],
- &[(0, "i_Position")],
- )
- }
-}
-
-pub fn set_transform(
- gl: &glow::Context,
- location: <glow::Context as HasContext>::UniformLocation,
- transform: Transformation,
-) {
- unsafe {
- gl.uniform_matrix_4_f32_slice(
- Some(&location),
- false,
- transform.as_ref(),
- );
- }
-}
-
#[derive(Debug)]
-struct Buffer<T> {
+pub struct Buffer<T> {
raw: <glow::Context as HasContext>::Buffer,
target: u32,
usage: u32,
@@ -253,3 +322,268 @@ impl<T> Buffer<T> {
}
}
}
+
+mod solid {
+ use crate::program;
+ use crate::triangle;
+ use glow::{Context, HasContext, NativeProgram};
+ use iced_graphics::triangle::ColoredVertex2D;
+ use iced_graphics::Transformation;
+
+ #[derive(Debug)]
+ pub struct Program {
+ pub program: <Context as HasContext>::Program,
+ pub vertex_array: <glow::Context as HasContext>::VertexArray,
+ pub vertices: triangle::Buffer<ColoredVertex2D>,
+ pub uniforms: Uniforms,
+ }
+
+ impl Program {
+ pub fn new(gl: &Context, shader_version: &program::Version) -> Self {
+ let program = unsafe {
+ let vertex_shader = program::Shader::vertex(
+ gl,
+ shader_version,
+ include_str!("shader/common/solid.vert"),
+ );
+
+ let fragment_shader = program::Shader::fragment(
+ gl,
+ shader_version,
+ include_str!("shader/common/solid.frag"),
+ );
+
+ program::create(
+ gl,
+ &[vertex_shader, fragment_shader],
+ &[(0, "i_Position"), (1, "i_Color")],
+ )
+ };
+
+ let vertex_array = unsafe {
+ gl.create_vertex_array().expect("Create vertex array")
+ };
+
+ let vertices = unsafe {
+ triangle::Buffer::new(
+ gl,
+ glow::ARRAY_BUFFER,
+ glow::DYNAMIC_DRAW,
+ super::DEFAULT_VERTICES,
+ )
+ };
+
+ unsafe {
+ gl.bind_vertex_array(Some(vertex_array));
+
+ let stride = std::mem::size_of::<ColoredVertex2D>() as i32;
+
+ gl.enable_vertex_attrib_array(0);
+ gl.vertex_attrib_pointer_f32(
+ 0,
+ 2,
+ glow::FLOAT,
+ false,
+ stride,
+ 0,
+ );
+
+ gl.enable_vertex_attrib_array(1);
+ gl.vertex_attrib_pointer_f32(
+ 1,
+ 4,
+ glow::FLOAT,
+ false,
+ stride,
+ 4 * 2,
+ );
+
+ gl.bind_vertex_array(None);
+ };
+
+ Self {
+ program,
+ vertex_array,
+ vertices,
+ uniforms: Uniforms::new(gl, program),
+ }
+ }
+ }
+
+ #[derive(Debug)]
+ pub struct Uniforms {
+ pub transform: Transformation,
+ pub transform_location: <Context as HasContext>::UniformLocation,
+ }
+
+ impl Uniforms {
+ fn new(gl: &Context, program: NativeProgram) -> Self {
+ let transform = Transformation::identity();
+ let transform_location =
+ unsafe { gl.get_uniform_location(program, "u_Transform") }
+ .expect("Solid - Get u_Transform.");
+
+ unsafe {
+ gl.use_program(Some(program));
+
+ gl.uniform_matrix_4_f32_slice(
+ Some(&transform_location),
+ false,
+ transform.as_ref(),
+ );
+
+ gl.use_program(None);
+ }
+
+ Self {
+ transform,
+ transform_location,
+ }
+ }
+ }
+}
+
+mod gradient {
+ use crate::program;
+ use crate::triangle;
+ use glow::{Context, HasContext, NativeProgram};
+ use iced_graphics::gradient::{self, Gradient};
+ use iced_graphics::triangle::Vertex2D;
+ use iced_graphics::Transformation;
+
+ #[derive(Debug)]
+ pub struct Program {
+ pub program: <Context as HasContext>::Program,
+ pub vertex_array: <glow::Context as HasContext>::VertexArray,
+ pub vertices: triangle::Buffer<Vertex2D>,
+ pub uniforms: Uniforms,
+ }
+
+ impl Program {
+ pub fn new(gl: &Context, shader_version: &program::Version) -> Self {
+ let program = unsafe {
+ let vertex_shader = program::Shader::vertex(
+ gl,
+ shader_version,
+ include_str!("shader/common/gradient.vert"),
+ );
+
+ let fragment_shader = program::Shader::fragment(
+ gl,
+ shader_version,
+ include_str!("shader/common/gradient.frag"),
+ );
+
+ program::create(
+ gl,
+ &[vertex_shader, fragment_shader],
+ &[(0, "i_Position")],
+ )
+ };
+
+ let vertex_array = unsafe {
+ gl.create_vertex_array().expect("Create vertex array")
+ };
+
+ let vertices = unsafe {
+ triangle::Buffer::new(
+ gl,
+ glow::ARRAY_BUFFER,
+ glow::DYNAMIC_DRAW,
+ super::DEFAULT_VERTICES,
+ )
+ };
+
+ unsafe {
+ gl.bind_vertex_array(Some(vertex_array));
+
+ let stride = std::mem::size_of::<Vertex2D>() as i32;
+
+ gl.enable_vertex_attrib_array(0);
+ gl.vertex_attrib_pointer_f32(
+ 0,
+ 2,
+ glow::FLOAT,
+ false,
+ stride,
+ 0,
+ );
+
+ gl.bind_vertex_array(None);
+ };
+
+ Self {
+ program,
+ vertex_array,
+ vertices,
+ uniforms: Uniforms::new(gl, program),
+ }
+ }
+ }
+
+ #[derive(Debug)]
+ pub struct Uniforms {
+ pub gradient: Gradient,
+ pub transform: Transformation,
+ pub locations: Locations,
+ }
+
+ #[derive(Debug)]
+ pub struct Locations {
+ pub gradient_direction: <Context as HasContext>::UniformLocation,
+ pub color_stops_size: <Context as HasContext>::UniformLocation,
+ //currently the maximum number of stops is 16 due to lack of SSBO in GL2.1
+ pub color_stops: <Context as HasContext>::UniformLocation,
+ pub transform: <Context as HasContext>::UniformLocation,
+ }
+
+ impl Uniforms {
+ fn new(gl: &Context, program: NativeProgram) -> Self {
+ let gradient_direction = unsafe {
+ gl.get_uniform_location(program, "gradient_direction")
+ }
+ .expect("Gradient - Get gradient_direction.");
+
+ let color_stops_size =
+ unsafe { gl.get_uniform_location(program, "color_stops_size") }
+ .expect("Gradient - Get color_stops_size.");
+
+ let color_stops = unsafe {
+ gl.get_uniform_location(program, "color_stops")
+ .expect("Gradient - Get color_stops.")
+ };
+
+ let transform = Transformation::identity();
+ let transform_location =
+ unsafe { gl.get_uniform_location(program, "u_Transform") }
+ .expect("Solid - Get u_Transform.");
+
+ unsafe {
+ gl.use_program(Some(program));
+
+ gl.uniform_matrix_4_f32_slice(
+ Some(&transform_location),
+ false,
+ transform.as_ref(),
+ );
+
+ gl.use_program(None);
+ }
+
+ Self {
+ gradient: Gradient::Linear(gradient::Linear {
+ start: Default::default(),
+ end: Default::default(),
+ color_stops: vec![],
+ }),
+ transform: Transformation::identity(),
+ locations: Locations {
+ gradient_direction,
+ color_stops_size,
+ color_stops,
+ transform: transform_location,
+ },
+ }
+ }
+ }
+}
diff --git a/glow/src/triangle/gradient.rs b/glow/src/triangle/gradient.rs
deleted file mode 100644
index 5225612e..00000000
--- a/glow/src/triangle/gradient.rs
+++ /dev/null
@@ -1,162 +0,0 @@
-use crate::program::Version;
-use crate::triangle;
-use glow::{Context, HasContext, NativeProgram};
-use iced_graphics::gradient::Gradient;
-use iced_graphics::gradient::Linear;
-use iced_graphics::Transformation;
-
-#[derive(Debug)]
-pub struct Program {
- pub program: <Context as HasContext>::Program,
- pub uniform_data: UniformData,
-}
-
-#[derive(Debug)]
-pub struct UniformData {
- gradient: Gradient,
- transform: Transformation,
- uniform_locations: UniformLocations,
-}
-
-#[derive(Debug)]
-struct UniformLocations {
- gradient_direction_location: <Context as HasContext>::UniformLocation,
- color_stops_size_location: <Context as HasContext>::UniformLocation,
- //currently the maximum number of stops is 16 due to lack of SSBO in GL2.1
- color_stops_location: <Context as HasContext>::UniformLocation,
- transform_location: <Context as HasContext>::UniformLocation,
-}
-
-impl Program {
- pub fn new(gl: &Context, shader_version: &Version) -> Self {
- let program = triangle::program(
- gl,
- shader_version,
- include_str!("../shader/common/gradient.frag"),
- );
-
- Self {
- program,
- uniform_data: UniformData::new(gl, program),
- }
- }
-
- pub fn write_uniforms(
- &mut self,
- gl: &Context,
- gradient: &Gradient,
- transform: &Transformation,
- ) {
- if transform != &self.uniform_data.transform {
- triangle::set_transform(
- gl,
- self.uniform_data.uniform_locations.transform_location,
- *transform,
- );
- }
-
- if &self.uniform_data.gradient != gradient {
- match gradient {
- Gradient::Linear(linear) => unsafe {
- gl.uniform_4_f32(
- Some(
- &self
- .uniform_data
- .uniform_locations
- .gradient_direction_location,
- ),
- linear.start.x,
- linear.start.y,
- linear.end.x,
- linear.end.y,
- );
-
- gl.uniform_1_u32(
- Some(
- &self
- .uniform_data
- .uniform_locations
- .color_stops_size_location,
- ),
- (linear.color_stops.len() * 2) as u32,
- );
-
- let mut stops = [0.0; 128];
-
- for (index, stop) in
- linear.color_stops.iter().enumerate().take(16)
- {
- let [r, g, b, a] = stop.color.into_linear();
-
- stops[index * 8] = r;
- stops[(index * 8) + 1] = g;
- stops[(index * 8) + 2] = b;
- stops[(index * 8) + 3] = a;
- stops[(index * 8) + 4] = stop.offset;
- stops[(index * 8) + 5] = 0.;
- stops[(index * 8) + 6] = 0.;
- stops[(index * 8) + 7] = 0.;
- }
-
- gl.uniform_4_f32_slice(
- Some(
- &self
- .uniform_data
- .uniform_locations
- .color_stops_location,
- ),
- &stops,
- );
- },
- }
-
- self.uniform_data.gradient = gradient.clone();
- }
- }
-
- pub fn use_program(
- &mut self,
- gl: &Context,
- gradient: &Gradient,
- transform: &Transformation,
- ) {
- unsafe { gl.use_program(Some(self.program)) }
- self.write_uniforms(gl, gradient, transform);
- }
-}
-
-impl UniformData {
- fn new(gl: &Context, program: NativeProgram) -> Self {
- let gradient_direction_location =
- unsafe { gl.get_uniform_location(program, "gradient_direction") }
- .expect("Gradient - Get gradient_direction.");
-
- let color_stops_size_location =
- unsafe { gl.get_uniform_location(program, "color_stops_size") }
- .expect("Gradient - Get color_stops_size.");
-
- let color_stops_location = unsafe {
- gl.get_uniform_location(program, "color_stops")
- .expect("Gradient - Get color_stops.")
- };
-
- let transform_location =
- unsafe { gl.get_uniform_location(program, "u_Transform") }
- .expect("Gradient - Get u_Transform.");
-
- Self {
- gradient: Gradient::Linear(Linear {
- start: Default::default(),
- end: Default::default(),
- color_stops: vec![],
- }),
- transform: Transformation::identity(),
- uniform_locations: UniformLocations {
- gradient_direction_location,
- color_stops_size_location,
- color_stops_location,
- transform_location,
- },
- }
- }
-}
diff --git a/glow/src/triangle/solid.rs b/glow/src/triangle/solid.rs
deleted file mode 100644
index fb3d40c3..00000000
--- a/glow/src/triangle/solid.rs
+++ /dev/null
@@ -1,91 +0,0 @@
-use crate::program::Version;
-use crate::{triangle, Color};
-use glow::{Context, HasContext, NativeProgram};
-use iced_graphics::Transformation;
-
-#[derive(Debug)]
-pub struct Program {
- program: <Context as HasContext>::Program,
- uniform_data: UniformData,
-}
-
-#[derive(Debug)]
-struct UniformData {
- pub color: Color,
- pub color_location: <Context as HasContext>::UniformLocation,
- pub transform: Transformation,
- pub transform_location: <Context as HasContext>::UniformLocation,
-}
-
-impl UniformData {
- fn new(gl: &Context, program: NativeProgram) -> Self {
- Self {
- color: Color::TRANSPARENT,
- color_location: unsafe {
- gl.get_uniform_location(program, "color")
- }
- .expect("Solid - Get color."),
- transform: Transformation::identity(),
- transform_location: unsafe {
- gl.get_uniform_location(program, "u_Transform")
- }
- .expect("Solid - Get u_Transform."),
- }
- }
-}
-
-impl Program {
- pub fn new(gl: &Context, shader_version: &Version) -> Self {
- let program = triangle::program(
- gl,
- shader_version,
- include_str!("../shader/common/triangle.frag"),
- );
-
- Self {
- program,
- uniform_data: UniformData::new(gl, program),
- }
- }
-
- pub fn write_uniforms(
- &mut self,
- gl: &Context,
- color: &Color,
- transform: &Transformation,
- ) {
- if transform != &self.uniform_data.transform {
- triangle::set_transform(
- gl,
- self.uniform_data.transform_location,
- *transform,
- )
- }
-
- if color != &self.uniform_data.color {
- let [r, g, b, a] = color.into_linear();
-
- unsafe {
- gl.uniform_4_f32(
- Some(&self.uniform_data.color_location),
- r,
- g,
- b,
- a,
- );
- }
-
- self.uniform_data.color = *color;
- }
- }
-
- pub fn use_program(
- &mut self,
- gl: &Context,
- color: &Color,
- transform: &Transformation,
- ) {
- unsafe { gl.use_program(Some(self.program)) }
- self.write_uniforms(gl, color, transform)
- }
-}
diff --git a/glutin/Cargo.toml b/glutin/Cargo.toml
index 65b16947..730ee6f7 100644
--- a/glutin/Cargo.toml
+++ b/glutin/Cargo.toml
@@ -1,6 +1,6 @@
[package]
name = "iced_glutin"
-version = "0.3.0"
+version = "0.4.0"
authors = ["Héctor Ramón Jiménez <hector0193@gmail.com>"]
edition = "2021"
description = "A glutin runtime for Iced"
@@ -23,15 +23,15 @@ git = "https://github.com/iced-rs/glutin"
rev = "da8d291486b4c9bec12487a46c119c4b1d386abf"
[dependencies.iced_native]
-version = "0.5"
+version = "0.6"
path = "../native"
[dependencies.iced_winit]
-version = "0.4"
+version = "0.5"
path = "../winit"
features = ["application"]
[dependencies.iced_graphics]
-version = "0.3"
+version = "0.4"
path = "../graphics"
features = ["opengl"]
diff --git a/graphics/Cargo.toml b/graphics/Cargo.toml
index 3b0e5236..b601f37c 100644
--- a/graphics/Cargo.toml
+++ b/graphics/Cargo.toml
@@ -1,6 +1,6 @@
[package]
name = "iced_graphics"
-version = "0.3.1"
+version = "0.4.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"
@@ -11,28 +11,44 @@ keywords = ["gui", "ui", "graphics", "interface", "widgets"]
categories = ["gui"]
[features]
+svg = ["resvg", "usvg", "tiny-skia"]
+image = ["png", "jpeg", "jpeg_rayon", "gif", "webp", "bmp"]
+png = ["image_rs/png"]
+jpeg = ["image_rs/jpeg"]
+jpeg_rayon = ["image_rs/jpeg_rayon"]
+gif = ["image_rs/gif"]
+webp = ["image_rs/webp"]
+pnm = ["image_rs/pnm"]
+ico = ["image_rs/ico"]
+bmp = ["image_rs/bmp"]
+hdr = ["image_rs/hdr"]
+dds = ["image_rs/dds"]
+farbfeld = ["image_rs/farbfeld"]
canvas = ["lyon"]
qr_code = ["qrcode", "canvas"]
font-source = ["font-kit"]
font-fallback = []
font-icons = []
opengl = []
+image_rs = ["kamadak-exif"]
[dependencies]
glam = "0.21.3"
+log = "0.4"
raw-window-handle = "0.5"
thiserror = "1.0"
+bitflags = "1.2"
[dependencies.bytemuck]
version = "1.4"
features = ["derive"]
[dependencies.iced_native]
-version = "0.5"
+version = "0.6"
path = "../native"
[dependencies.iced_style]
-version = "0.4"
+version = "0.5"
path = "../style"
[dependencies.lyon]
@@ -48,6 +64,28 @@ default-features = false
version = "0.10"
optional = true
+[dependencies.image_rs]
+version = "0.24"
+package = "image"
+default-features = false
+optional = true
+
+[dependencies.resvg]
+version = "0.18"
+optional = true
+
+[dependencies.usvg]
+version = "0.18"
+optional = true
+
+[dependencies.tiny-skia]
+version = "0.6"
+optional = true
+
+[dependencies.kamadak-exif]
+version = "0.5"
+optional = true
+
[package.metadata.docs.rs]
rustdoc-args = ["--cfg", "docsrs"]
all-features = true
diff --git a/graphics/src/backend.rs b/graphics/src/backend.rs
index 7e0af2cc..2f8e9fc3 100644
--- a/graphics/src/backend.rs
+++ b/graphics/src/backend.rs
@@ -66,11 +66,11 @@ pub trait Text {
/// A graphics backend that supports image rendering.
pub trait Image {
/// Returns the dimensions of the provided image.
- fn dimensions(&self, handle: &image::Handle) -> (u32, u32);
+ fn dimensions(&self, handle: &image::Handle) -> Size<u32>;
}
/// A graphics backend that supports SVG rendering.
pub trait Svg {
/// Returns the viewport dimensions of the provided SVG.
- fn viewport_dimensions(&self, handle: &svg::Handle) -> (u32, u32);
+ fn viewport_dimensions(&self, handle: &svg::Handle) -> Size<u32>;
}
diff --git a/graphics/src/gradient.rs b/graphics/src/gradient.rs
index 64f9e4b3..83f25238 100644
--- a/graphics/src/gradient.rs
+++ b/graphics/src/gradient.rs
@@ -9,7 +9,7 @@ use crate::{Color, Point, Size};
/// A fill which transitions colors progressively along a direction, either linearly, radially (TBD),
/// or conically (TBD).
pub enum Gradient {
- /// A linear gradient interpolates colors along a direction from its [`start`] to its [`end`]
+ /// A linear gradient interpolates colors along a direction from its `start` to its `end`
/// point.
Linear(Linear),
}
@@ -23,10 +23,15 @@ impl Gradient {
#[derive(Debug, Clone, Copy, PartialEq)]
/// A point along the gradient vector where the specified [`color`] is unmixed.
+///
+/// [`color`]: Self::color
pub struct ColorStop {
/// Offset along the gradient vector.
pub offset: f32,
+
/// The color of the gradient at the specified [`offset`].
+ ///
+ /// [`offset`]: Self::offset
pub color: Color,
}
diff --git a/graphics/src/gradient/linear.rs b/graphics/src/gradient/linear.rs
index 9928c1eb..c886db47 100644
--- a/graphics/src/gradient/linear.rs
+++ b/graphics/src/gradient/linear.rs
@@ -2,7 +2,10 @@
use crate::gradient::{ColorStop, Gradient, Position};
use crate::{Color, Point};
-/// A linear gradient that can be used in the style of [`super::Fill`] or [`super::Stroke`].
+/// A linear gradient that can be used in the style of [`Fill`] or [`Stroke`].
+///
+/// [`Fill`]: crate::widget::canvas::Fill
+/// [`Stroke`]: crate::widget::canvas::Stroke
#[derive(Debug, Clone, PartialEq)]
pub struct Linear {
/// The point where the linear gradient begins.
diff --git a/graphics/src/image.rs b/graphics/src/image.rs
new file mode 100644
index 00000000..04f4ff9d
--- /dev/null
+++ b/graphics/src/image.rs
@@ -0,0 +1,10 @@
+//! Render images.
+#[cfg(feature = "image_rs")]
+pub mod raster;
+
+#[cfg(feature = "svg")]
+pub mod vector;
+
+pub mod storage;
+
+pub use storage::Storage;
diff --git a/wgpu/src/image/raster.rs b/graphics/src/image/raster.rs
index 2b4d4af3..da46c30f 100644
--- a/wgpu/src/image/raster.rs
+++ b/graphics/src/image/raster.rs
@@ -1,43 +1,53 @@
-use crate::image::atlas::{self, Atlas};
+//! Raster image loading and caching.
+use crate::image::Storage;
+use crate::Size;
+
use iced_native::image;
-use std::collections::{HashMap, HashSet};
use bitflags::bitflags;
+use std::collections::{HashMap, HashSet};
+/// Entry in cache corresponding to an image handle
#[derive(Debug)]
-pub enum Memory {
- Host(::image_rs::ImageBuffer<::image_rs::Bgra<u8>, Vec<u8>>),
- Device(atlas::Entry),
+pub enum Memory<T: Storage> {
+ /// Image data on host
+ Host(::image_rs::ImageBuffer<::image_rs::Rgba<u8>, Vec<u8>>),
+ /// Storage entry
+ Device(T::Entry),
+ /// Image not found
NotFound,
+ /// Invalid image data
Invalid,
}
-impl Memory {
- pub fn dimensions(&self) -> (u32, u32) {
+impl<T: Storage> Memory<T> {
+ /// Width and height of image
+ pub fn dimensions(&self) -> Size<u32> {
+ use crate::image::storage::Entry;
+
match self {
- Memory::Host(image) => image.dimensions(),
+ Memory::Host(image) => {
+ let (width, height) = image.dimensions();
+
+ Size::new(width, height)
+ }
Memory::Device(entry) => entry.size(),
- Memory::NotFound => (1, 1),
- Memory::Invalid => (1, 1),
+ Memory::NotFound => Size::new(1, 1),
+ Memory::Invalid => Size::new(1, 1),
}
}
}
+/// Caches image raster data
#[derive(Debug)]
-pub struct Cache {
- map: HashMap<u64, Memory>,
+pub struct Cache<T: Storage> {
+ map: HashMap<u64, Memory<T>>,
hits: HashSet<u64>,
}
-impl Cache {
- pub fn new() -> Self {
- Self {
- map: HashMap::new(),
- hits: HashSet::new(),
- }
- }
-
- pub fn load(&mut self, handle: &image::Handle) -> &mut Memory {
+impl<T: Storage> Cache<T> {
+ /// Load image
+ pub fn load(&mut self, handle: &image::Handle) -> &mut Memory<T> {
if self.contains(handle) {
return self.get(handle).unwrap();
}
@@ -53,7 +63,7 @@ impl Cache {
})
.unwrap_or_else(Operation::empty);
- Memory::Host(operation.perform(image.to_bgra8()))
+ Memory::Host(operation.perform(image.to_rgba8()))
} else {
Memory::NotFound
}
@@ -65,12 +75,12 @@ impl Cache {
.ok()
.unwrap_or_else(Operation::empty);
- Memory::Host(operation.perform(image.to_bgra8()))
+ Memory::Host(operation.perform(image.to_rgba8()))
} else {
Memory::Invalid
}
}
- image::Data::Pixels {
+ image::Data::Rgba {
width,
height,
pixels,
@@ -91,19 +101,19 @@ impl Cache {
self.get(handle).unwrap()
}
+ /// Load image and upload raster data
pub fn upload(
&mut self,
handle: &image::Handle,
- device: &wgpu::Device,
- encoder: &mut wgpu::CommandEncoder,
- atlas: &mut Atlas,
- ) -> Option<&atlas::Entry> {
+ state: &mut T::State<'_>,
+ storage: &mut T,
+ ) -> Option<&T::Entry> {
let memory = self.load(handle);
if let Memory::Host(image) = memory {
let (width, height) = image.dimensions();
- let entry = atlas.upload(width, height, image, device, encoder)?;
+ let entry = storage.upload(width, height, image, state)?;
*memory = Memory::Device(entry);
}
@@ -115,7 +125,8 @@ impl Cache {
}
}
- pub fn trim(&mut self, atlas: &mut Atlas) {
+ /// Trim cache misses from cache
+ pub fn trim(&mut self, storage: &mut T, state: &mut T::State<'_>) {
let hits = &self.hits;
self.map.retain(|k, memory| {
@@ -123,7 +134,7 @@ impl Cache {
if !retain {
if let Memory::Device(entry) = memory {
- atlas.remove(entry);
+ storage.remove(entry, state);
}
}
@@ -133,13 +144,13 @@ impl Cache {
self.hits.clear();
}
- fn get(&mut self, handle: &image::Handle) -> Option<&mut Memory> {
+ fn get(&mut self, handle: &image::Handle) -> Option<&mut Memory<T>> {
let _ = self.hits.insert(handle.id());
self.map.get_mut(&handle.id())
}
- fn insert(&mut self, handle: &image::Handle, memory: Memory) {
+ fn insert(&mut self, handle: &image::Handle, memory: Memory<T>) {
let _ = self.map.insert(handle.id(), memory);
}
@@ -148,6 +159,15 @@ impl Cache {
}
}
+impl<T: Storage> Default for Cache<T> {
+ fn default() -> Self {
+ Self {
+ map: HashMap::new(),
+ hits: HashSet::new(),
+ }
+ }
+}
+
bitflags! {
struct Operation: u8 {
const FLIP_HORIZONTALLY = 0b001;
diff --git a/graphics/src/image/storage.rs b/graphics/src/image/storage.rs
new file mode 100644
index 00000000..2098c7b2
--- /dev/null
+++ b/graphics/src/image/storage.rs
@@ -0,0 +1,31 @@
+//! Store images.
+use crate::Size;
+
+use std::fmt::Debug;
+
+/// Stores cached image data for use in rendering
+pub trait Storage {
+ /// The type of an [`Entry`] in the [`Storage`].
+ type Entry: Entry;
+
+ /// State provided to upload or remove a [`Self::Entry`].
+ type State<'a>;
+
+ /// Upload the image data of a [`Self::Entry`].
+ fn upload(
+ &mut self,
+ width: u32,
+ height: u32,
+ data: &[u8],
+ state: &mut Self::State<'_>,
+ ) -> Option<Self::Entry>;
+
+ /// Romve a [`Self::Entry`] from the [`Storage`].
+ fn remove(&mut self, entry: &Self::Entry, state: &mut Self::State<'_>);
+}
+
+/// An entry in some [`Storage`],
+pub trait Entry: Debug {
+ /// The [`Size`] of the [`Entry`].
+ fn size(&self) -> Size<u32>;
+}
diff --git a/wgpu/src/image/vector.rs b/graphics/src/image/vector.rs
index b08a0aa2..42f4b500 100644
--- a/wgpu/src/image/vector.rs
+++ b/graphics/src/image/vector.rs
@@ -1,46 +1,45 @@
-use crate::image::atlas::{self, Atlas};
+//! Vector image loading and caching
+use crate::image::Storage;
use iced_native::svg;
+use iced_native::Size;
use std::collections::{HashMap, HashSet};
use std::fs;
+/// Entry in cache corresponding to an svg handle
pub enum Svg {
+ /// Parsed svg
Loaded(usvg::Tree),
+ /// Svg not found or failed to parse
NotFound,
}
impl Svg {
- pub fn viewport_dimensions(&self) -> (u32, u32) {
+ /// Viewport width and height
+ pub fn viewport_dimensions(&self) -> Size<u32> {
match self {
Svg::Loaded(tree) => {
let size = tree.svg_node().size;
- (size.width() as u32, size.height() as u32)
+ Size::new(size.width() as u32, size.height() as u32)
}
- Svg::NotFound => (1, 1),
+ Svg::NotFound => Size::new(1, 1),
}
}
}
+/// Caches svg vector and raster data
#[derive(Debug)]
-pub struct Cache {
+pub struct Cache<T: Storage> {
svgs: HashMap<u64, Svg>,
- rasterized: HashMap<(u64, u32, u32), atlas::Entry>,
+ rasterized: HashMap<(u64, u32, u32), T::Entry>,
svg_hits: HashSet<u64>,
rasterized_hits: HashSet<(u64, u32, u32)>,
}
-impl Cache {
- pub fn new() -> Self {
- Self {
- svgs: HashMap::new(),
- rasterized: HashMap::new(),
- svg_hits: HashSet::new(),
- rasterized_hits: HashSet::new(),
- }
- }
-
+impl<T: Storage> Cache<T> {
+ /// Load svg
pub fn load(&mut self, handle: &svg::Handle) -> &Svg {
if self.svgs.contains_key(&handle.id()) {
return self.svgs.get(&handle.id()).unwrap();
@@ -73,15 +72,15 @@ impl Cache {
self.svgs.get(&handle.id()).unwrap()
}
+ /// Load svg and upload raster data
pub fn upload(
&mut self,
handle: &svg::Handle,
[width, height]: [f32; 2],
scale: f32,
- device: &wgpu::Device,
- encoder: &mut wgpu::CommandEncoder,
- texture_atlas: &mut Atlas,
- ) -> Option<&atlas::Entry> {
+ state: &mut T::State<'_>,
+ storage: &mut T,
+ ) -> Option<&T::Entry> {
let id = handle.id();
let (width, height) = (
@@ -122,16 +121,8 @@ impl Cache {
img.as_mut(),
)?;
- let mut rgba = img.take();
- rgba.chunks_exact_mut(4).for_each(|rgba| rgba.swap(0, 2));
-
- let allocation = texture_atlas.upload(
- width,
- height,
- bytemuck::cast_slice(rgba.as_slice()),
- device,
- encoder,
- )?;
+ let allocation =
+ storage.upload(width, height, img.data(), state)?;
log::debug!("allocating {} {}x{}", id, width, height);
let _ = self.svg_hits.insert(id);
@@ -144,7 +135,8 @@ impl Cache {
}
}
- pub fn trim(&mut self, atlas: &mut Atlas) {
+ /// Load svg and upload raster data
+ pub fn trim(&mut self, storage: &mut T, state: &mut T::State<'_>) {
let svg_hits = &self.svg_hits;
let rasterized_hits = &self.rasterized_hits;
@@ -153,7 +145,7 @@ impl Cache {
let retain = rasterized_hits.contains(k);
if !retain {
- atlas.remove(entry);
+ storage.remove(entry, state);
}
retain
@@ -163,6 +155,17 @@ impl Cache {
}
}
+impl<T: Storage> Default for Cache<T> {
+ fn default() -> Self {
+ Self {
+ svgs: HashMap::new(),
+ rasterized: HashMap::new(),
+ svg_hits: HashSet::new(),
+ rasterized_hits: HashSet::new(),
+ }
+ }
+}
+
impl std::fmt::Debug for Svg {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
diff --git a/graphics/src/layer.rs b/graphics/src/layer.rs
index e95934b0..fd670f48 100644
--- a/graphics/src/layer.rs
+++ b/graphics/src/layer.rs
@@ -166,10 +166,27 @@ impl<'a> Layer<'a> {
border_color: border_color.into_linear(),
});
}
- Primitive::Mesh2D {
+ Primitive::SolidMesh { buffers, size } => {
+ let layer = &mut layers[current_layer];
+
+ let bounds = Rectangle::new(
+ Point::new(translation.x, translation.y),
+ *size,
+ );
+
+ // Only draw visible content
+ if let Some(clip_bounds) = layer.bounds.intersection(&bounds) {
+ layer.meshes.push(Mesh::Solid {
+ origin: Point::new(translation.x, translation.y),
+ buffers,
+ clip_bounds,
+ });
+ }
+ }
+ Primitive::GradientMesh {
buffers,
size,
- style,
+ gradient,
} => {
let layer = &mut layers[current_layer];
@@ -180,11 +197,11 @@ impl<'a> Layer<'a> {
// Only draw visible content
if let Some(clip_bounds) = layer.bounds.intersection(&bounds) {
- layer.meshes.push(Mesh {
+ layer.meshes.push(Mesh::Gradient {
origin: Point::new(translation.x, translation.y),
buffers,
clip_bounds,
- style,
+ gradient,
});
}
}
diff --git a/graphics/src/layer/mesh.rs b/graphics/src/layer/mesh.rs
index 979081f1..7661c5c9 100644
--- a/graphics/src/layer/mesh.rs
+++ b/graphics/src/layer/mesh.rs
@@ -1,31 +1,93 @@
//! A collection of triangle primitives.
use crate::triangle;
-use crate::{Point, Rectangle};
+use crate::{Gradient, Point, Rectangle};
/// A mesh of triangles.
#[derive(Debug, Clone, Copy)]
-pub struct Mesh<'a> {
- /// The origin of the vertices of the [`Mesh`].
- pub origin: Point,
+pub enum Mesh<'a> {
+ /// A mesh of triangles with a solid color.
+ Solid {
+ /// The origin of the vertices of the [`Mesh`].
+ origin: Point,
- /// The vertex and index buffers of the [`Mesh`].
- pub buffers: &'a triangle::Mesh2D,
+ /// The vertex and index buffers of the [`Mesh`].
+ buffers: &'a triangle::Mesh2D<triangle::ColoredVertex2D>,
- /// The clipping bounds of the [`Mesh`].
- pub clip_bounds: Rectangle<f32>,
+ /// The clipping bounds of the [`Mesh`].
+ clip_bounds: Rectangle<f32>,
+ },
+ /// A mesh of triangles with a gradient color.
+ Gradient {
+ /// The origin of the vertices of the [`Mesh`].
+ origin: Point,
- /// The shader of the [`Mesh`].
- pub style: &'a triangle::Style,
+ /// The vertex and index buffers of the [`Mesh`].
+ buffers: &'a triangle::Mesh2D<triangle::Vertex2D>,
+
+ /// The clipping bounds of the [`Mesh`].
+ clip_bounds: Rectangle<f32>,
+
+ /// The gradient to apply to the [`Mesh`].
+ gradient: &'a Gradient,
+ },
+}
+
+impl Mesh<'_> {
+ /// Returns the origin of the [`Mesh`].
+ pub fn origin(&self) -> Point {
+ match self {
+ Self::Solid { origin, .. } | Self::Gradient { origin, .. } => {
+ *origin
+ }
+ }
+ }
+
+ /// Returns the indices of the [`Mesh`].
+ pub fn indices(&self) -> &[u32] {
+ match self {
+ Self::Solid { buffers, .. } => &buffers.indices,
+ Self::Gradient { buffers, .. } => &buffers.indices,
+ }
+ }
+
+ /// Returns the clip bounds of the [`Mesh`].
+ pub fn clip_bounds(&self) -> Rectangle<f32> {
+ match self {
+ Self::Solid { clip_bounds, .. }
+ | Self::Gradient { clip_bounds, .. } => *clip_bounds,
+ }
+ }
+}
+
+/// The result of counting the attributes of a set of meshes.
+#[derive(Debug, Clone, Copy, Default)]
+pub struct AttributeCount {
+ /// The total amount of solid vertices.
+ pub solid_vertices: usize,
+
+ /// The total amount of gradient vertices.
+ pub gradient_vertices: usize,
+
+ /// The total amount of indices.
+ pub indices: usize,
}
/// Returns the number of total vertices & total indices of all [`Mesh`]es.
-pub fn attribute_count_of<'a>(meshes: &'a [Mesh<'a>]) -> (usize, usize) {
+pub fn attribute_count_of<'a>(meshes: &'a [Mesh<'a>]) -> AttributeCount {
meshes
.iter()
- .map(|Mesh { buffers, .. }| {
- (buffers.vertices.len(), buffers.indices.len())
- })
- .fold((0, 0), |(total_v, total_i), (v, i)| {
- (total_v + v, total_i + i)
+ .fold(AttributeCount::default(), |mut count, mesh| {
+ match mesh {
+ Mesh::Solid { buffers, .. } => {
+ count.solid_vertices += buffers.vertices.len();
+ count.indices += buffers.indices.len();
+ }
+ Mesh::Gradient { buffers, .. } => {
+ count.gradient_vertices += buffers.vertices.len();
+ count.indices += buffers.indices.len();
+ }
+ }
+
+ count
})
}
diff --git a/graphics/src/lib.rs b/graphics/src/lib.rs
index ec28ee58..d39dd90c 100644
--- a/graphics/src/lib.rs
+++ b/graphics/src/lib.rs
@@ -30,6 +30,7 @@ mod viewport;
pub mod backend;
pub mod font;
pub mod gradient;
+pub mod image;
pub mod layer;
pub mod overlay;
pub mod renderer;
diff --git a/graphics/src/primitive.rs b/graphics/src/primitive.rs
index 84f04624..6f1b6f26 100644
--- a/graphics/src/primitive.rs
+++ b/graphics/src/primitive.rs
@@ -3,6 +3,7 @@ use iced_native::svg;
use iced_native::{Background, Color, Font, Rectangle, Size, Vector};
use crate::alignment;
+use crate::gradient::Gradient;
use crate::triangle;
use std::sync::Arc;
@@ -77,20 +78,32 @@ pub enum Primitive {
/// The primitive to translate
content: Box<Primitive>,
},
- /// A low-level primitive to render a mesh of triangles.
+ /// A low-level primitive to render a mesh of triangles with a solid color.
///
/// It can be used to render many kinds of geometry freely.
- Mesh2D {
- /// The vertex and index buffers of the mesh
- buffers: triangle::Mesh2D,
+ SolidMesh {
+ /// The vertices and indices of the mesh.
+ buffers: triangle::Mesh2D<triangle::ColoredVertex2D>,
+
+ /// The size of the drawable region of the mesh.
+ ///
+ /// Any geometry that falls out of this region will be clipped.
+ size: Size,
+ },
+ /// A low-level primitive to render a mesh of triangles with a gradient.
+ ///
+ /// It can be used to render many kinds of geometry freely.
+ GradientMesh {
+ /// The vertices and indices of the mesh.
+ buffers: triangle::Mesh2D<triangle::Vertex2D>,
/// The size of the drawable region of the mesh.
///
/// Any geometry that falls out of this region will be clipped.
size: Size,
- /// The shader of the mesh
- style: triangle::Style,
+ /// The [`Gradient`] to apply to the mesh.
+ gradient: Gradient,
},
/// A cached primitive.
///
diff --git a/graphics/src/renderer.rs b/graphics/src/renderer.rs
index ff0dad2b..65350037 100644
--- a/graphics/src/renderer.rs
+++ b/graphics/src/renderer.rs
@@ -183,7 +183,7 @@ where
{
type Handle = image::Handle;
- fn dimensions(&self, handle: &image::Handle) -> (u32, u32) {
+ fn dimensions(&self, handle: &image::Handle) -> Size<u32> {
self.backend().dimensions(handle)
}
@@ -196,7 +196,7 @@ impl<B, T> svg::Renderer for Renderer<B, T>
where
B: Backend + backend::Svg,
{
- fn dimensions(&self, handle: &svg::Handle) -> (u32, u32) {
+ fn dimensions(&self, handle: &svg::Handle) -> Size<u32> {
self.backend().viewport_dimensions(handle)
}
diff --git a/graphics/src/triangle.rs b/graphics/src/triangle.rs
index 04ff6d21..f52b2339 100644
--- a/graphics/src/triangle.rs
+++ b/graphics/src/triangle.rs
@@ -1,13 +1,12 @@
//! Draw geometry using meshes of triangles.
-use crate::{Color, Gradient};
-
use bytemuck::{Pod, Zeroable};
/// A set of [`Vertex2D`] and indices representing a list of triangles.
#[derive(Clone, Debug)]
-pub struct Mesh2D {
+pub struct Mesh2D<T> {
/// The vertices of the mesh
- pub vertices: Vec<Vertex2D>,
+ pub vertices: Vec<T>,
+
/// The list of vertex indices that defines the triangles of the mesh.
///
/// Therefore, this list should always have a length that is a multiple of 3.
@@ -22,23 +21,13 @@ pub struct Vertex2D {
pub position: [f32; 2],
}
-#[derive(Debug, Clone, PartialEq)]
-/// Supported shaders for triangle primitives.
-pub enum Style {
- /// Fill a primitive with a solid color.
- Solid(Color),
- /// Fill a primitive with an interpolated color.
- Gradient(Gradient),
-}
-
-impl From<Color> for Style {
- fn from(color: Color) -> Self {
- Self::Solid(color)
- }
-}
+/// A two-dimensional vertex with a color.
+#[derive(Copy, Clone, Debug, Zeroable, Pod)]
+#[repr(C)]
+pub struct ColoredVertex2D {
+ /// The vertex position in 2D space.
+ pub position: [f32; 2],
-impl From<Gradient> for Style {
- fn from(gradient: Gradient) -> Self {
- Self::Gradient(gradient)
- }
+ /// The color of the vertex in __linear__ RGBA.
+ pub color: [f32; 4],
}
diff --git a/graphics/src/widget/canvas.rs b/graphics/src/widget/canvas.rs
index a14940d9..b070d0a6 100644
--- a/graphics/src/widget/canvas.rs
+++ b/graphics/src/widget/canvas.rs
@@ -13,6 +13,7 @@ mod cursor;
mod frame;
mod geometry;
mod program;
+mod style;
mod text;
pub use crate::gradient::{self, Gradient};
@@ -25,6 +26,7 @@ pub use geometry::Geometry;
pub use path::Path;
pub use program::Program;
pub use stroke::{LineCap, LineDash, LineJoin, Stroke};
+pub use style::Style;
pub use text::Text;
use crate::{Backend, Primitive, Renderer};
diff --git a/graphics/src/widget/canvas/fill.rs b/graphics/src/widget/canvas/fill.rs
index c69fc0d7..e954ebb5 100644
--- a/graphics/src/widget/canvas/fill.rs
+++ b/graphics/src/widget/canvas/fill.rs
@@ -1,14 +1,14 @@
//! Fill [crate::widget::canvas::Geometry] with a certain style.
use crate::{Color, Gradient};
-pub use crate::triangle::Style;
+pub use crate::widget::canvas::Style;
/// The style used to fill geometry.
#[derive(Debug, Clone)]
pub struct Fill {
/// The color or gradient of the fill.
///
- /// By default, it is set to [`FillStyle::Solid`] `BLACK`.
+ /// By default, it is set to [`Style::Solid`] with [`Color::BLACK`].
pub style: Style,
/// The fill rule defines how to determine what is inside and what is
diff --git a/graphics/src/widget/canvas/frame.rs b/graphics/src/widget/canvas/frame.rs
index cf6c6928..d68548ae 100644
--- a/graphics/src/widget/canvas/frame.rs
+++ b/graphics/src/widget/canvas/frame.rs
@@ -1,7 +1,6 @@
use crate::gradient::Gradient;
use crate::triangle;
-use crate::triangle::Vertex2D;
-use crate::widget::canvas::{path, Fill, Geometry, Path, Stroke, Text};
+use crate::widget::canvas::{path, Fill, Geometry, Path, Stroke, Style, Text};
use crate::Primitive;
use iced_native::{Point, Rectangle, Size, Vector};
@@ -23,8 +22,16 @@ pub struct Frame {
stroke_tessellator: tessellation::StrokeTessellator,
}
+enum Buffer {
+ Solid(tessellation::VertexBuffers<triangle::ColoredVertex2D, u32>),
+ Gradient(
+ tessellation::VertexBuffers<triangle::Vertex2D, u32>,
+ Gradient,
+ ),
+}
+
struct BufferStack {
- stack: Vec<(tessellation::VertexBuffers<Vertex2D, u32>, triangle::Style)>,
+ stack: Vec<Buffer>,
}
impl BufferStack {
@@ -32,22 +39,64 @@ impl BufferStack {
Self { stack: Vec::new() }
}
- fn get(
- &mut self,
- mesh_style: triangle::Style,
- ) -> tessellation::BuffersBuilder<'_, Vertex2D, u32, Vertex2DBuilder> {
- match self.stack.last_mut() {
- Some((_, current_style)) if current_style == &mesh_style => {}
- _ => {
- self.stack
- .push((tessellation::VertexBuffers::new(), mesh_style));
+ fn get_mut(&mut self, style: &Style) -> &mut Buffer {
+ match style {
+ Style::Solid(_) => match self.stack.last() {
+ Some(Buffer::Solid(_)) => {}
+ _ => {
+ self.stack.push(Buffer::Solid(
+ tessellation::VertexBuffers::new(),
+ ));
+ }
+ },
+ Style::Gradient(gradient) => match self.stack.last() {
+ Some(Buffer::Gradient(_, last)) if gradient == last => {}
+ _ => {
+ self.stack.push(Buffer::Gradient(
+ tessellation::VertexBuffers::new(),
+ gradient.clone(),
+ ));
+ }
+ },
+ }
+
+ self.stack.last_mut().unwrap()
+ }
+
+ fn get_fill<'a>(
+ &'a mut self,
+ style: &Style,
+ ) -> Box<dyn tessellation::FillGeometryBuilder + 'a> {
+ match (style, self.get_mut(style)) {
+ (Style::Solid(color), Buffer::Solid(buffer)) => {
+ Box::new(tessellation::BuffersBuilder::new(
+ buffer,
+ TriangleVertex2DBuilder(color.into_linear()),
+ ))
}
- };
+ (Style::Gradient(_), Buffer::Gradient(buffer, _)) => Box::new(
+ tessellation::BuffersBuilder::new(buffer, Vertex2DBuilder),
+ ),
+ _ => unreachable!(),
+ }
+ }
- tessellation::BuffersBuilder::new(
- &mut self.stack.last_mut().unwrap().0,
- Vertex2DBuilder,
- )
+ fn get_stroke<'a>(
+ &'a mut self,
+ style: &Style,
+ ) -> Box<dyn tessellation::StrokeGeometryBuilder + 'a> {
+ match (style, self.get_mut(style)) {
+ (Style::Solid(color), Buffer::Solid(buffer)) => {
+ Box::new(tessellation::BuffersBuilder::new(
+ buffer,
+ TriangleVertex2DBuilder(color.into_linear()),
+ ))
+ }
+ (Style::Gradient(_), Buffer::Gradient(buffer, _)) => Box::new(
+ tessellation::BuffersBuilder::new(buffer, Vertex2DBuilder),
+ ),
+ _ => unreachable!(),
+ }
}
}
@@ -73,11 +122,11 @@ impl Transform {
point.y = transformed.y;
}
- fn transform_style(&self, style: triangle::Style) -> triangle::Style {
+ fn transform_style(&self, style: Style) -> Style {
match style {
- triangle::Style::Solid(color) => triangle::Style::Solid(color),
- triangle::Style::Gradient(gradient) => {
- triangle::Style::Gradient(self.transform_gradient(gradient))
+ Style::Solid(color) => Style::Solid(color),
+ Style::Gradient(gradient) => {
+ Style::Gradient(self.transform_gradient(gradient))
}
}
}
@@ -145,7 +194,7 @@ impl Frame {
let mut buffer = self
.buffers
- .get(self.transforms.current.transform_style(style));
+ .get_fill(&self.transforms.current.transform_style(style));
let options =
tessellation::FillOptions::default().with_fill_rule(rule.into());
@@ -154,7 +203,7 @@ impl Frame {
self.fill_tessellator.tessellate_path(
path.raw(),
&options,
- &mut buffer,
+ buffer.as_mut(),
)
} else {
let path = path.transformed(&self.transforms.current.raw);
@@ -162,7 +211,7 @@ impl Frame {
self.fill_tessellator.tessellate_path(
path.raw(),
&options,
- &mut buffer,
+ buffer.as_mut(),
)
}
.expect("Tessellate path.");
@@ -180,7 +229,7 @@ impl Frame {
let mut buffer = self
.buffers
- .get(self.transforms.current.transform_style(style));
+ .get_fill(&self.transforms.current.transform_style(style));
let top_left =
self.transforms.current.raw.transform_point(
@@ -199,7 +248,7 @@ impl Frame {
.tessellate_rectangle(
&lyon::math::Box2D::new(top_left, top_left + size),
&options,
- &mut buffer,
+ buffer.as_mut(),
)
.expect("Fill rectangle");
}
@@ -211,7 +260,7 @@ impl Frame {
let mut buffer = self
.buffers
- .get(self.transforms.current.transform_style(stroke.style));
+ .get_stroke(&self.transforms.current.transform_style(stroke.style));
let mut options = tessellation::StrokeOptions::default();
options.line_width = stroke.width;
@@ -229,7 +278,7 @@ impl Frame {
self.stroke_tessellator.tessellate_path(
path.raw(),
&options,
- &mut buffer,
+ buffer.as_mut(),
)
} else {
let path = path.transformed(&self.transforms.current.raw);
@@ -237,7 +286,7 @@ impl Frame {
self.stroke_tessellator.tessellate_path(
path.raw(),
&options,
- &mut buffer,
+ buffer.as_mut(),
)
}
.expect("Stroke path");
@@ -382,16 +431,31 @@ impl Frame {
}
fn into_primitives(mut self) -> Vec<Primitive> {
- for (buffer, style) in self.buffers.stack {
- if !buffer.indices.is_empty() {
- self.primitives.push(Primitive::Mesh2D {
- buffers: triangle::Mesh2D {
- vertices: buffer.vertices,
- indices: buffer.indices,
- },
- size: self.size,
- style,
- })
+ for buffer in self.buffers.stack {
+ match buffer {
+ Buffer::Solid(buffer) => {
+ if !buffer.indices.is_empty() {
+ self.primitives.push(Primitive::SolidMesh {
+ buffers: triangle::Mesh2D {
+ vertices: buffer.vertices,
+ indices: buffer.indices,
+ },
+ size: self.size,
+ })
+ }
+ }
+ Buffer::Gradient(buffer, gradient) => {
+ if !buffer.indices.is_empty() {
+ self.primitives.push(Primitive::GradientMesh {
+ buffers: triangle::Mesh2D {
+ vertices: buffer.vertices,
+ indices: buffer.indices,
+ },
+ size: self.size,
+ gradient,
+ })
+ }
+ }
}
}
@@ -401,25 +465,66 @@ impl Frame {
struct Vertex2DBuilder;
-impl tessellation::FillVertexConstructor<Vertex2D> for Vertex2DBuilder {
- fn new_vertex(&mut self, vertex: tessellation::FillVertex<'_>) -> Vertex2D {
+impl tessellation::FillVertexConstructor<triangle::Vertex2D>
+ for Vertex2DBuilder
+{
+ fn new_vertex(
+ &mut self,
+ vertex: tessellation::FillVertex<'_>,
+ ) -> triangle::Vertex2D {
+ let position = vertex.position();
+
+ triangle::Vertex2D {
+ position: [position.x, position.y],
+ }
+ }
+}
+
+impl tessellation::StrokeVertexConstructor<triangle::Vertex2D>
+ for Vertex2DBuilder
+{
+ fn new_vertex(
+ &mut self,
+ vertex: tessellation::StrokeVertex<'_, '_>,
+ ) -> triangle::Vertex2D {
+ let position = vertex.position();
+
+ triangle::Vertex2D {
+ position: [position.x, position.y],
+ }
+ }
+}
+
+struct TriangleVertex2DBuilder([f32; 4]);
+
+impl tessellation::FillVertexConstructor<triangle::ColoredVertex2D>
+ for TriangleVertex2DBuilder
+{
+ fn new_vertex(
+ &mut self,
+ vertex: tessellation::FillVertex<'_>,
+ ) -> triangle::ColoredVertex2D {
let position = vertex.position();
- Vertex2D {
+ triangle::ColoredVertex2D {
position: [position.x, position.y],
+ color: self.0,
}
}
}
-impl tessellation::StrokeVertexConstructor<Vertex2D> for Vertex2DBuilder {
+impl tessellation::StrokeVertexConstructor<triangle::ColoredVertex2D>
+ for TriangleVertex2DBuilder
+{
fn new_vertex(
&mut self,
vertex: tessellation::StrokeVertex<'_, '_>,
- ) -> Vertex2D {
+ ) -> triangle::ColoredVertex2D {
let position = vertex.position();
- Vertex2D {
+ triangle::ColoredVertex2D {
position: [position.x, position.y],
+ color: self.0,
}
}
}
diff --git a/graphics/src/widget/canvas/stroke.rs b/graphics/src/widget/canvas/stroke.rs
index f9b8e447..4c19251d 100644
--- a/graphics/src/widget/canvas/stroke.rs
+++ b/graphics/src/widget/canvas/stroke.rs
@@ -1,5 +1,5 @@
//! Create lines from a [crate::widget::canvas::Path] and assigns them various attributes/styles.
-pub use crate::triangle::Style;
+pub use crate::widget::canvas::Style;
use iced_native::Color;
@@ -8,7 +8,7 @@ use iced_native::Color;
pub struct Stroke<'a> {
/// The color or gradient of the stroke.
///
- /// By default, it is set to [`StrokeStyle::Solid`] `BLACK`.
+ /// By default, it is set to a [`Style::Solid`] with [`Color::BLACK`].
pub style: Style,
/// The distance between the two edges of the stroke.
pub width: f32,
diff --git a/graphics/src/widget/canvas/style.rs b/graphics/src/widget/canvas/style.rs
new file mode 100644
index 00000000..6794f2e7
--- /dev/null
+++ b/graphics/src/widget/canvas/style.rs
@@ -0,0 +1,23 @@
+use crate::{Color, Gradient};
+
+/// The coloring style of some drawing.
+#[derive(Debug, Clone, PartialEq)]
+pub enum Style {
+ /// A solid [`Color`].
+ Solid(Color),
+
+ /// A [`Gradient`] color.
+ Gradient(Gradient),
+}
+
+impl From<Color> for Style {
+ fn from(color: Color) -> Self {
+ Self::Solid(color)
+ }
+}
+
+impl From<Gradient> for Style {
+ fn from(gradient: Gradient) -> Self {
+ Self::Gradient(gradient)
+ }
+}
diff --git a/graphics/src/window/compositor.rs b/graphics/src/window/compositor.rs
index 52255666..db4ba45d 100644
--- a/graphics/src/window/compositor.rs
+++ b/graphics/src/window/compositor.rs
@@ -40,7 +40,7 @@ pub trait Compositor: Sized {
height: u32,
);
- /// Returns [`GraphicsInformation`] used by this [`Compositor`].
+ /// Returns [`Information`] used by this [`Compositor`].
fn fetch_information(&self) -> Information;
/// Presents the [`Renderer`] primitives to the next frame of the given [`Surface`].
diff --git a/graphics/src/window/gl_compositor.rs b/graphics/src/window/gl_compositor.rs
index 722e4d9c..a45a7ca1 100644
--- a/graphics/src/window/gl_compositor.rs
+++ b/graphics/src/window/gl_compositor.rs
@@ -54,7 +54,7 @@ pub trait GLCompositor: Sized {
/// Resizes the viewport of the [`GLCompositor`].
fn resize_viewport(&mut self, physical_size: Size<u32>);
- /// Returns [`GraphicsInformation`] used by this [`Compositor`].
+ /// Returns [`Information`] used by this [`GLCompositor`].
fn fetch_information(&self) -> Information;
/// Presents the primitives of the [`Renderer`] to the next frame of the
diff --git a/lazy/Cargo.toml b/lazy/Cargo.toml
index 12e4e313..51459148 100644
--- a/lazy/Cargo.toml
+++ b/lazy/Cargo.toml
@@ -1,6 +1,6 @@
[package]
name = "iced_lazy"
-version = "0.1.1"
+version = "0.2.0"
authors = ["Héctor Ramón Jiménez <hector0193@gmail.com>"]
edition = "2021"
description = "Lazy widgets for Iced"
@@ -14,5 +14,5 @@ categories = ["gui"]
ouroboros = "0.13"
[dependencies.iced_native]
-version = "0.5"
+version = "0.6"
path = "../native"
diff --git a/lazy/src/component.rs b/lazy/src/component.rs
index 8987b993..3d7b8758 100644
--- a/lazy/src/component.rs
+++ b/lazy/src/component.rs
@@ -11,7 +11,7 @@ use iced_native::{
};
use ouroboros::self_referencing;
-use std::cell::{Ref, RefCell};
+use std::cell::RefCell;
use std::marker::PhantomData;
/// A reusable, custom widget that uses The Elm Architecture.
@@ -260,6 +260,14 @@ where
) {
self.operation.focusable(state, id);
}
+
+ fn text_input(
+ &mut self,
+ state: &mut dyn widget::operation::TextInput,
+ id: Option<&widget::Id>,
+ ) {
+ self.operation.text_input(state, id);
+ }
}
self.with_element(|element| {
@@ -314,25 +322,25 @@ where
}
fn overlay<'b>(
- &'b self,
+ &'b mut self,
tree: &'b mut Tree,
layout: Layout<'_>,
renderer: &Renderer,
) -> Option<overlay::Element<'b, Message, Renderer>> {
let overlay = OverlayBuilder {
instance: self,
- instance_ref_builder: |instance| instance.state.borrow(),
tree,
types: PhantomData,
overlay_builder: |instance, tree| {
- instance
- .as_ref()
- .unwrap()
- .borrow_element()
- .as_ref()
- .unwrap()
- .as_widget()
- .overlay(&mut tree.children[0], layout, renderer)
+ 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();
@@ -354,15 +362,11 @@ where
#[self_referencing]
struct Overlay<'a, 'b, Message, Renderer, Event, S> {
- instance: &'a Instance<'b, Message, Renderer, Event, S>,
+ instance: &'a mut Instance<'b, Message, Renderer, Event, S>,
tree: &'a mut Tree,
types: PhantomData<(Message, Event, S)>,
- #[borrows(instance)]
- #[covariant]
- instance_ref: Ref<'this, Option<State<'a, Message, Renderer, Event, S>>>,
-
- #[borrows(instance_ref, mut tree)]
+ #[borrows(mut instance, mut tree)]
#[covariant]
overlay: Option<overlay::Element<'this, Event, Renderer>>,
}
@@ -506,7 +510,6 @@ where
self.overlay = Some(
OverlayBuilder {
instance: overlay.instance,
- instance_ref_builder: |instance| instance.state.borrow(),
tree: overlay.tree,
types: PhantomData,
overlay_builder: |_, _| None,
diff --git a/lazy/src/lazy.rs b/lazy/src/lazy.rs
index d61cc77e..2611dd10 100644
--- a/lazy/src/lazy.rs
+++ b/lazy/src/lazy.rs
@@ -207,7 +207,7 @@ where
}
fn overlay<'b>(
- &'b self,
+ &'b mut self,
tree: &'b mut Tree,
layout: Layout<'_>,
renderer: &Renderer,
@@ -216,12 +216,12 @@ where
cached: self,
tree: &mut tree.children[0],
types: PhantomData,
- element_ref_builder: |cached| cached.element.borrow(),
- element_builder: |element_ref| {
- element_ref.as_ref().unwrap().borrow()
- },
- overlay_builder: |element, tree| {
- element.as_widget().overlay(tree, layout, renderer)
+ overlay_builder: |cached, tree| {
+ Rc::get_mut(cached.element.get_mut().as_mut().unwrap())
+ .unwrap()
+ .get_mut()
+ .as_widget_mut()
+ .overlay(tree, layout, renderer)
},
}
.build();
@@ -237,20 +237,11 @@ where
#[self_referencing]
struct Overlay<'a, 'b, Message, Renderer, Dependency, View> {
- cached: &'a Lazy<'b, Message, Renderer, Dependency, View>,
+ cached: &'a mut Lazy<'b, Message, Renderer, Dependency, View>,
tree: &'a mut Tree,
types: PhantomData<(Message, Dependency, View)>,
- #[borrows(cached)]
- #[covariant]
- element_ref:
- Ref<'this, Option<Rc<RefCell<Element<'static, Message, Renderer>>>>>,
-
- #[borrows(element_ref)]
- #[covariant]
- element: Ref<'this, Element<'static, Message, Renderer>>,
-
- #[borrows(element, mut tree)]
+ #[borrows(mut cached, mut tree)]
#[covariant]
overlay: Option<overlay::Element<'this, Message, Renderer>>,
}
diff --git a/lazy/src/responsive.rs b/lazy/src/responsive.rs
index 0b7ae6de..5e1b5dff 100644
--- a/lazy/src/responsive.rs
+++ b/lazy/src/responsive.rs
@@ -235,18 +235,20 @@ where
}
fn overlay<'b>(
- &'b self,
+ &'b mut self,
tree: &'b mut Tree,
layout: Layout<'_>,
renderer: &Renderer,
) -> Option<overlay::Element<'b, Message, Renderer>> {
+ use std::ops::DerefMut;
+
let state = tree.state.downcast_ref::<State>();
let overlay = OverlayBuilder {
content: self.content.borrow_mut(),
tree: state.tree.borrow_mut(),
types: PhantomData,
- overlay_builder: |content, tree| {
+ overlay_builder: |content: &mut RefMut<Content<_, _>>, tree| {
content.update(
tree,
renderer,
@@ -254,16 +256,18 @@ where
&self.view,
);
+ let Content {
+ element, layout, ..
+ } = content.deref_mut();
+
let content_layout = Layout::with_offset(
- layout.position() - Point::ORIGIN,
- &content.layout,
+ layout.bounds().position() - Point::ORIGIN,
+ layout,
);
- content.element.as_widget().overlay(
- tree,
- content_layout,
- renderer,
- )
+ element
+ .as_widget_mut()
+ .overlay(tree, content_layout, renderer)
},
}
.build();
diff --git a/native/Cargo.toml b/native/Cargo.toml
index b4945c05..558909be 100644
--- a/native/Cargo.toml
+++ b/native/Cargo.toml
@@ -1,6 +1,6 @@
[package]
name = "iced_native"
-version = "0.5.1"
+version = "0.6.1"
authors = ["Héctor Ramón Jiménez <hector0193@gmail.com>"]
edition = "2021"
description = "A renderer-agnostic library for native GUIs"
@@ -16,14 +16,14 @@ unicode-segmentation = "1.6"
num-traits = "0.2"
[dependencies.iced_core]
-version = "0.5"
+version = "0.6"
path = "../core"
[dependencies.iced_futures]
-version = "0.4"
+version = "0.5"
path = "../futures"
features = ["thread-pool"]
[dependencies.iced_style]
-version = "0.4"
+version = "0.5"
path = "../style"
diff --git a/native/src/element.rs b/native/src/element.rs
index 955e1bee..2f1adeff 100644
--- a/native/src/element.rs
+++ b/native/src/element.rs
@@ -316,6 +316,22 @@ where
) {
self.operation.focusable(state, id);
}
+
+ fn scrollable(
+ &mut self,
+ state: &mut dyn widget::operation::Scrollable,
+ id: Option<&widget::Id>,
+ ) {
+ self.operation.scrollable(state, id);
+ }
+
+ fn text_input(
+ &mut self,
+ state: &mut dyn widget::operation::TextInput,
+ id: Option<&widget::Id>,
+ ) {
+ self.operation.text_input(state, id);
+ }
}
self.widget
@@ -389,7 +405,7 @@ where
}
fn overlay<'b>(
- &'b self,
+ &'b mut self,
tree: &'b mut Tree,
layout: Layout<'_>,
renderer: &Renderer,
@@ -544,7 +560,7 @@ where
}
fn overlay<'b>(
- &'b self,
+ &'b mut self,
state: &'b mut Tree,
layout: Layout<'_>,
renderer: &Renderer,
diff --git a/native/src/image.rs b/native/src/image.rs
index 516eb2db..06fd7ae6 100644
--- a/native/src/image.rs
+++ b/native/src/image.rs
@@ -1,6 +1,7 @@
//! Load and draw raster graphics.
-use crate::{Hasher, Rectangle};
+use crate::{Hasher, Rectangle, Size};
+use std::borrow::Cow;
use std::hash::{Hash, Hasher as _};
use std::path::PathBuf;
use std::sync::Arc;
@@ -21,15 +22,19 @@ impl Handle {
}
/// Creates an image [`Handle`] containing the image pixels directly. This
- /// function expects the input data to be provided as a `Vec<u8>` of BGRA
+ /// function expects the input data to be provided as a `Vec<u8>` of RGBA
/// pixels.
///
/// This is useful if you have already decoded your image.
- pub fn from_pixels(width: u32, height: u32, pixels: Vec<u8>) -> Handle {
- Self::from_data(Data::Pixels {
+ pub fn from_pixels(
+ width: u32,
+ height: u32,
+ pixels: impl Into<Cow<'static, [u8]>>,
+ ) -> Handle {
+ Self::from_data(Data::Rgba {
width,
height,
- pixels,
+ pixels: pixels.into(),
})
}
@@ -39,8 +44,8 @@ impl Handle {
///
/// This is useful if you already have your image loaded in-memory, maybe
/// because you downloaded or generated it procedurally.
- pub fn from_memory(bytes: Vec<u8>) -> Handle {
- Self::from_data(Data::Bytes(bytes))
+ pub fn from_memory(bytes: impl Into<Cow<'static, [u8]>>) -> Handle {
+ Self::from_data(Data::Bytes(bytes.into()))
}
fn from_data(data: Data) -> Handle {
@@ -86,16 +91,16 @@ pub enum Data {
Path(PathBuf),
/// In-memory data
- Bytes(Vec<u8>),
+ Bytes(Cow<'static, [u8]>),
- /// Decoded image pixels in BGRA format.
- Pixels {
+ /// Decoded image pixels in RGBA format.
+ Rgba {
/// The width of the image.
width: u32,
/// The height of the image.
height: u32,
/// The pixels.
- pixels: Vec<u8>,
+ pixels: Cow<'static, [u8]>,
},
}
@@ -104,7 +109,7 @@ impl std::fmt::Debug for Data {
match self {
Data::Path(path) => write!(f, "Path({:?})", path),
Data::Bytes(_) => write!(f, "Bytes(...)"),
- Data::Pixels { width, height, .. } => {
+ Data::Rgba { width, height, .. } => {
write!(f, "Pixels({} * {})", width, height)
}
}
@@ -121,7 +126,7 @@ pub trait Renderer: crate::Renderer {
type Handle: Clone + Hash;
/// Returns the dimensions of an image for the given [`Handle`].
- fn dimensions(&self, handle: &Self::Handle) -> (u32, u32);
+ fn dimensions(&self, handle: &Self::Handle) -> Size<u32>;
/// Draws an image with the given [`Handle`] and inside the provided
/// `bounds`.
diff --git a/native/src/lib.rs b/native/src/lib.rs
index 02269265..62b8f372 100644
--- a/native/src/lib.rs
+++ b/native/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.4/core
-//! [`iced_winit`]: https://github.com/iced-rs/iced/tree/0.4/winit
+//! [`iced_core`]: https://github.com/iced-rs/iced/tree/0.5/core
+//! [`iced_winit`]: https://github.com/iced-rs/iced/tree/0.5/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/native/src/overlay.rs b/native/src/overlay.rs
index 905d3389..0b05b058 100644
--- a/native/src/overlay.rs
+++ b/native/src/overlay.rs
@@ -11,7 +11,7 @@ use crate::layout;
use crate::mouse;
use crate::renderer;
use crate::widget;
-use crate::widget::tree::{self, Tree};
+use crate::widget::Tree;
use crate::{Clipboard, Layout, Point, Rectangle, Shell, Size};
/// An interactive component that can be displayed on top of other widgets.
@@ -42,31 +42,9 @@ where
cursor_position: Point,
);
- /// Returns the [`Tag`] of the [`Widget`].
- ///
- /// [`Tag`]: tree::Tag
- fn tag(&self) -> tree::Tag {
- tree::Tag::stateless()
- }
-
- /// Returns the [`State`] of the [`Widget`].
- ///
- /// [`State`]: tree::State
- fn state(&self) -> tree::State {
- tree::State::None
- }
-
- /// Returns the state [`Tree`] of the children of the [`Widget`].
- fn children(&self) -> Vec<Tree> {
- Vec::new()
- }
-
- /// Reconciliates the [`Widget`] with the provided [`Tree`].
- fn diff(&self, _tree: &mut Tree) {}
-
- /// Applies an [`Operation`] to the [`Widget`].
+ /// Applies a [`widget::Operation`] to the [`Overlay`].
fn operate(
- &self,
+ &mut self,
_layout: Layout<'_>,
_operation: &mut dyn widget::Operation<Message>,
) {
@@ -115,7 +93,7 @@ where
/// This method will generally only be used by advanced users that are
/// implementing the [`Widget`](crate::Widget) trait.
pub fn from_children<'a, Message, Renderer>(
- children: &'a [crate::Element<'_, Message, Renderer>],
+ children: &'a mut [crate::Element<'_, Message, Renderer>],
tree: &'a mut Tree,
layout: Layout<'_>,
renderer: &Renderer,
@@ -124,11 +102,11 @@ where
Renderer: crate::Renderer,
{
children
- .iter()
+ .iter_mut()
.zip(&mut tree.children)
.zip(layout.children())
.filter_map(|((child, state), layout)| {
- child.as_widget().overlay(state, layout, renderer)
+ child.as_widget_mut().overlay(state, layout, renderer)
})
.next()
}
diff --git a/native/src/overlay/element.rs b/native/src/overlay/element.rs
index b919c221..4f5ef32a 100644
--- a/native/src/overlay/element.rs
+++ b/native/src/overlay/element.rs
@@ -104,9 +104,9 @@ where
.draw(renderer, theme, style, layout, cursor_position)
}
- /// Applies an [`Operation`] to the [`Element`].
+ /// Applies a [`widget::Operation`] to the [`Element`].
pub fn operate(
- &self,
+ &mut self,
layout: Layout<'_>,
operation: &mut dyn widget::Operation<Message>,
) {
@@ -141,6 +141,57 @@ where
self.content.layout(renderer, bounds, position)
}
+ fn operate(
+ &mut self,
+ layout: Layout<'_>,
+ operation: &mut dyn widget::Operation<B>,
+ ) {
+ struct MapOperation<'a, B> {
+ operation: &'a mut dyn widget::Operation<B>,
+ }
+
+ impl<'a, T, B> widget::Operation<T> for MapOperation<'a, B> {
+ fn container(
+ &mut self,
+ id: Option<&widget::Id>,
+ operate_on_children: &mut dyn FnMut(
+ &mut dyn widget::Operation<T>,
+ ),
+ ) {
+ self.operation.container(id, &mut |operation| {
+ operate_on_children(&mut MapOperation { operation });
+ });
+ }
+
+ fn focusable(
+ &mut self,
+ state: &mut dyn widget::operation::Focusable,
+ id: Option<&widget::Id>,
+ ) {
+ self.operation.focusable(state, id);
+ }
+
+ fn scrollable(
+ &mut self,
+ state: &mut dyn widget::operation::Scrollable,
+ id: Option<&widget::Id>,
+ ) {
+ self.operation.scrollable(state, id);
+ }
+
+ fn text_input(
+ &mut self,
+ state: &mut dyn widget::operation::TextInput,
+ id: Option<&widget::Id>,
+ ) {
+ self.operation.text_input(state, id)
+ }
+ }
+
+ self.content
+ .operate(layout, &mut MapOperation { operation });
+ }
+
fn on_event(
&mut self,
event: Event,
diff --git a/native/src/overlay/menu.rs b/native/src/overlay/menu.rs
index dbcf23d4..099b1a97 100644
--- a/native/src/overlay/menu.rs
+++ b/native/src/overlay/menu.rs
@@ -9,7 +9,7 @@ use crate::text::{self, Text};
use crate::touch;
use crate::widget::container::{self, Container};
use crate::widget::scrollable::{self, Scrollable};
-use crate::widget::tree::{self, Tree};
+use crate::widget::Tree;
use crate::{
Clipboard, Color, Element, Layout, Length, Padding, Point, Rectangle,
Shell, Size, Vector, Widget,
@@ -178,7 +178,7 @@ where
font,
text_size,
padding,
- style,
+ style: style.clone(),
}));
state.tree.diff(&container as &dyn Widget<_, _>);
@@ -199,18 +199,6 @@ where
Renderer: text::Renderer,
Renderer::Theme: StyleSheet + container::StyleSheet,
{
- fn tag(&self) -> tree::Tag {
- self.container.tag()
- }
-
- fn state(&self) -> tree::State {
- self.container.state()
- }
-
- fn children(&self) -> Vec<Tree> {
- self.container.children()
- }
-
fn layout(
&self,
renderer: &Renderer,
@@ -288,7 +276,7 @@ where
layout: Layout<'_>,
cursor_position: Point,
) {
- let appearance = theme.appearance(self.style);
+ let appearance = theme.appearance(&self.style);
let bounds = layout.bounds();
renderer.fill_quad(
@@ -460,7 +448,7 @@ where
_cursor_position: Point,
viewport: &Rectangle,
) {
- let appearance = theme.appearance(self.style);
+ let appearance = theme.appearance(&self.style);
let bounds = layout.bounds();
let text_size =
diff --git a/native/src/subscription.rs b/native/src/subscription.rs
index 9775c84b..d24801d4 100644
--- a/native/src/subscription.rs
+++ b/native/src/subscription.rs
@@ -155,7 +155,7 @@ 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.4/examples/websocket
+/// [`websocket`]: https://github.com/iced-rs/iced/tree/0.5/examples/websocket
pub fn unfold<I, T, Fut, Message>(
id: I,
initial: T,
diff --git a/native/src/svg.rs b/native/src/svg.rs
index f86fec5b..a8e481d2 100644
--- a/native/src/svg.rs
+++ b/native/src/svg.rs
@@ -1,6 +1,7 @@
//! Load and draw vector graphics.
-use crate::{Hasher, Rectangle};
+use crate::{Hasher, Rectangle, Size};
+use std::borrow::Cow;
use std::hash::{Hash, Hasher as _};
use std::path::PathBuf;
use std::sync::Arc;
@@ -24,7 +25,7 @@ impl Handle {
///
/// This is useful if you already have your SVG data in-memory, maybe
/// because you downloaded or generated it procedurally.
- pub fn from_memory(bytes: impl Into<Vec<u8>>) -> Handle {
+ pub fn from_memory(bytes: impl Into<Cow<'static, [u8]>>) -> Handle {
Self::from_data(Data::Bytes(bytes.into()))
}
@@ -64,7 +65,7 @@ pub enum Data {
/// In-memory data
///
/// Can contain an SVG string or a gzip compressed data.
- Bytes(Vec<u8>),
+ Bytes(Cow<'static, [u8]>),
}
impl std::fmt::Debug for Data {
@@ -81,7 +82,7 @@ impl std::fmt::Debug for Data {
/// [renderer]: crate::renderer
pub trait Renderer: crate::Renderer {
/// Returns the default dimensions of an SVG for the given [`Handle`].
- fn dimensions(&self, handle: &Handle) -> (u32, u32);
+ fn dimensions(&self, handle: &Handle) -> Size<u32>;
/// Draws an SVG with the given [`Handle`] and inside the provided `bounds`.
fn draw(&mut self, handle: Handle, bounds: Rectangle);
diff --git a/native/src/user_interface.rs b/native/src/user_interface.rs
index 344ba4d6..6b853da8 100644
--- a/native/src/user_interface.rs
+++ b/native/src/user_interface.rs
@@ -18,8 +18,8 @@ use crate::{Clipboard, Element, Layout, Point, Rectangle, Shell, Size};
/// 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.4/examples/integration_opengl
-/// [`integration_wgpu`]: https://github.com/iced-rs/iced/tree/0.4/examples/integration_wgpu
+/// [`integration_opengl`]: https://github.com/iced-rs/iced/tree/0.5/examples/integration_opengl
+/// [`integration_wgpu`]: https://github.com/iced-rs/iced/tree/0.5/examples/integration_wgpu
#[allow(missing_debug_implementations)]
pub struct UserInterface<'a, Message, Renderer> {
root: Element<'a, Message, Renderer>,
@@ -190,7 +190,7 @@ where
let mut state = State::Updated;
let mut manual_overlay =
- ManuallyDrop::new(self.root.as_widget().overlay(
+ ManuallyDrop::new(self.root.as_widget_mut().overlay(
&mut self.state,
Layout::new(&self.base),
renderer,
@@ -226,7 +226,7 @@ where
);
manual_overlay =
- ManuallyDrop::new(self.root.as_widget().overlay(
+ ManuallyDrop::new(self.root.as_widget_mut().overlay(
&mut self.state,
Layout::new(&self.base),
renderer,
@@ -285,6 +285,10 @@ where
&mut shell,
);
+ if matches!(event_status, event::Status::Captured) {
+ self.overlay = None;
+ }
+
shell.revalidate_layout(|| {
self.base = renderer.layout(
&self.root,
@@ -391,11 +395,11 @@ where
let viewport = Rectangle::with_size(self.bounds);
- let base_cursor = if let Some(overlay) = self.root.as_widget().overlay(
- &mut self.state,
- Layout::new(&self.base),
- renderer,
- ) {
+ let base_cursor = if let Some(overlay) = self
+ .root
+ .as_widget_mut()
+ .overlay(&mut self.state, Layout::new(&self.base), renderer)
+ {
let overlay_layout = self
.overlay
.take()
@@ -448,7 +452,7 @@ where
overlay
.as_ref()
.and_then(|layout| {
- root.as_widget()
+ root.as_widget_mut()
.overlay(&mut self.state, Layout::new(base), renderer)
.map(|overlay| {
let overlay_interaction = overlay.mouse_interaction(
@@ -492,14 +496,19 @@ where
operation,
);
- if let Some(layout) = self.overlay.as_ref() {
- if let Some(overlay) = self.root.as_widget().overlay(
- &mut self.state,
- Layout::new(&self.base),
- renderer,
- ) {
- overlay.operate(Layout::new(layout), operation);
+ if let Some(mut overlay) = self.root.as_widget_mut().overlay(
+ &mut self.state,
+ Layout::new(&self.base),
+ renderer,
+ ) {
+ if self.overlay.is_none() {
+ self.overlay = Some(overlay.layout(renderer, self.bounds));
}
+
+ overlay.operate(
+ Layout::new(self.overlay.as_ref().unwrap()),
+ operation,
+ );
}
}
diff --git a/native/src/widget.rs b/native/src/widget.rs
index 8890b8e7..36d679a4 100644
--- a/native/src/widget.rs
+++ b/native/src/widget.rs
@@ -107,12 +107,12 @@ use crate::{Clipboard, Layout, 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.4/examples
-/// [`bezier_tool`]: https://github.com/iced-rs/iced/tree/0.4/examples/bezier_tool
-/// [`custom_widget`]: https://github.com/iced-rs/iced/tree/0.4/examples/custom_widget
-/// [`geometry`]: https://github.com/iced-rs/iced/tree/0.4/examples/geometry
+/// [examples]: https://github.com/iced-rs/iced/tree/0.5/examples
+/// [`bezier_tool`]: https://github.com/iced-rs/iced/tree/0.5/examples/bezier_tool
+/// [`custom_widget`]: https://github.com/iced-rs/iced/tree/0.5/examples/custom_widget
+/// [`geometry`]: https://github.com/iced-rs/iced/tree/0.5/examples/geometry
/// [`lyon`]: https://github.com/nical/lyon
-/// [`iced_wgpu`]: https://github.com/iced-rs/iced/tree/0.4/wgpu
+/// [`iced_wgpu`]: https://github.com/iced-rs/iced/tree/0.5/wgpu
pub trait Widget<Message, Renderer>
where
Renderer: crate::Renderer,
@@ -208,7 +208,7 @@ where
/// Returns the overlay of the [`Widget`], if there is any.
fn overlay<'a>(
- &'a self,
+ &'a mut self,
_state: &'a mut Tree,
_layout: Layout<'_>,
_renderer: &Renderer,
diff --git a/native/src/widget/action.rs b/native/src/widget/action.rs
index 766e902b..9aa79dec 100644
--- a/native/src/widget/action.rs
+++ b/native/src/widget/action.rs
@@ -1,8 +1,10 @@
-use crate::widget::operation::{self, Operation};
+use crate::widget::operation::{self, Focusable, Operation, Scrollable};
use crate::widget::Id;
use iced_futures::MaybeSend;
+use std::rc::Rc;
+
/// An operation to be performed on the widget tree.
#[allow(missing_debug_implementations)]
pub struct Action<T>(Box<dyn Operation<T>>);
@@ -24,7 +26,7 @@ impl<T> Action<T> {
{
Action(Box::new(Map {
operation: self.0,
- f: Box::new(f),
+ f: Rc::new(f),
}))
}
@@ -37,7 +39,7 @@ impl<T> Action<T> {
#[allow(missing_debug_implementations)]
struct Map<A, B> {
operation: Box<dyn Operation<A>>,
- f: Box<dyn Fn(A) -> B>,
+ f: Rc<dyn Fn(A) -> B>,
}
impl<A, B> Operation<B> for Map<A, B>
@@ -50,30 +52,44 @@ where
id: Option<&Id>,
operate_on_children: &mut dyn FnMut(&mut dyn Operation<B>),
) {
- struct MapRef<'a, A, B> {
+ struct MapRef<'a, A> {
operation: &'a mut dyn Operation<A>,
- f: &'a dyn Fn(A) -> B,
}
- impl<'a, A, B> Operation<B> for MapRef<'a, A, B> {
+ impl<'a, A, B> Operation<B> for MapRef<'a, A> {
fn container(
&mut self,
id: Option<&Id>,
operate_on_children: &mut dyn FnMut(&mut dyn Operation<B>),
) {
- let Self { operation, f } = self;
+ let Self { operation, .. } = self;
operation.container(id, &mut |operation| {
- operate_on_children(&mut MapRef { operation, f });
+ operate_on_children(&mut MapRef { operation });
});
}
+
+ fn scrollable(
+ &mut self,
+ state: &mut dyn Scrollable,
+ id: Option<&Id>,
+ ) {
+ self.operation.scrollable(state, id);
+ }
+
+ fn focusable(
+ &mut self,
+ state: &mut dyn Focusable,
+ id: Option<&Id>,
+ ) {
+ self.operation.focusable(state, id);
+ }
}
- let Self { operation, f } = self;
+ let Self { operation, .. } = self;
MapRef {
operation: operation.as_mut(),
- f,
}
.container(id, operate_on_children);
}
@@ -85,4 +101,35 @@ where
) {
self.operation.focusable(state, id);
}
+
+ fn scrollable(
+ &mut self,
+ state: &mut dyn operation::Scrollable,
+ id: Option<&Id>,
+ ) {
+ self.operation.scrollable(state, id);
+ }
+
+ fn text_input(
+ &mut self,
+ state: &mut dyn operation::TextInput,
+ id: Option<&Id>,
+ ) {
+ self.operation.text_input(state, id);
+ }
+
+ fn finish(&self) -> operation::Outcome<B> {
+ match self.operation.finish() {
+ operation::Outcome::None => operation::Outcome::None,
+ operation::Outcome::Some(output) => {
+ operation::Outcome::Some((self.f)(output))
+ }
+ operation::Outcome::Chain(next) => {
+ operation::Outcome::Chain(Box::new(Map {
+ operation: next,
+ f: self.f.clone(),
+ }))
+ }
+ }
+ }
}
diff --git a/native/src/widget/button.rs b/native/src/widget/button.rs
index 14759cfd..bbd9451c 100644
--- a/native/src/widget/button.rs
+++ b/native/src/widget/button.rs
@@ -231,7 +231,7 @@ where
cursor_position,
self.on_press.is_some(),
theme,
- self.style,
+ &self.style,
|| tree.state.downcast_ref::<State>(),
);
@@ -260,12 +260,12 @@ where
}
fn overlay<'b>(
- &'b self,
+ &'b mut self,
tree: &'b mut Tree,
layout: Layout<'_>,
renderer: &Renderer,
) -> Option<overlay::Element<'b, Message, Renderer>> {
- self.content.as_widget().overlay(
+ self.content.as_widget_mut().overlay(
&mut tree.children[0],
layout.children().next().unwrap(),
renderer,
@@ -361,7 +361,7 @@ pub fn draw<'a, Renderer: crate::Renderer>(
style_sheet: &dyn StyleSheet<
Style = <Renderer::Theme as StyleSheet>::Style,
>,
- style: <Renderer::Theme as StyleSheet>::Style,
+ style: &<Renderer::Theme as StyleSheet>::Style,
state: impl FnOnce() -> &'a State,
) -> Appearance
where
@@ -426,12 +426,13 @@ pub fn layout<Renderer>(
padding: Padding,
layout_content: impl FnOnce(&Renderer, &layout::Limits) -> layout::Node,
) -> layout::Node {
- let limits = limits.width(width).height(height).pad(padding);
+ let limits = limits.width(width).height(height);
- let mut content = layout_content(renderer, &limits);
- content.move_to(Point::new(padding.left.into(), padding.top.into()));
+ let mut content = layout_content(renderer, &limits.pad(padding));
+ let padding = padding.fit(content.size(), limits.max());
+ let size = limits.pad(padding).resolve(content.size()).pad(padding);
- let size = limits.resolve(content.size()).pad(padding);
+ content.move_to(Point::new(padding.left.into(), padding.top.into()));
layout::Node::with_children(size, vec![content])
}
diff --git a/native/src/widget/checkbox.rs b/native/src/widget/checkbox.rs
index 8f30037a..bec5c448 100644
--- a/native/src/widget/checkbox.rs
+++ b/native/src/widget/checkbox.rs
@@ -224,9 +224,9 @@ where
let mut children = layout.children();
let custom_style = if is_mouse_over {
- theme.hovered(self.style, self.is_checked)
+ theme.hovered(&self.style, self.is_checked)
} else {
- theme.active(self.style, self.is_checked)
+ theme.active(&self.style, self.is_checked)
};
{
diff --git a/native/src/widget/column.rs b/native/src/widget/column.rs
index a8b0f183..8030778b 100644
--- a/native/src/widget/column.rs
+++ b/native/src/widget/column.rs
@@ -242,12 +242,12 @@ where
}
fn overlay<'b>(
- &'b self,
+ &'b mut self,
tree: &'b mut Tree,
layout: Layout<'_>,
renderer: &Renderer,
) -> Option<overlay::Element<'b, Message, Renderer>> {
- overlay::from_children(&self.children, tree, layout, renderer)
+ overlay::from_children(&mut self.children, tree, layout, renderer)
}
}
diff --git a/native/src/widget/container.rs b/native/src/widget/container.rs
index b0c4938a..16d0cb61 100644
--- a/native/src/widget/container.rs
+++ b/native/src/widget/container.rs
@@ -228,7 +228,7 @@ where
cursor_position: Point,
viewport: &Rectangle,
) {
- let style = theme.appearance(self.style);
+ let style = theme.appearance(&self.style);
draw_background(renderer, &style, layout.bounds());
@@ -248,12 +248,12 @@ where
}
fn overlay<'b>(
- &'b self,
+ &'b mut self,
tree: &'b mut Tree,
layout: Layout<'_>,
renderer: &Renderer,
) -> Option<overlay::Element<'b, Message, Renderer>> {
- self.content.as_widget().overlay(
+ self.content.as_widget_mut().overlay(
&mut tree.children[0],
layout.children().next().unwrap(),
renderer,
@@ -293,11 +293,11 @@ pub fn layout<Renderer>(
.max_width(max_width)
.max_height(max_height)
.width(width)
- .height(height)
- .pad(padding);
+ .height(height);
- let mut content = layout_content(renderer, &limits.loose());
- let size = limits.resolve(content.size());
+ let mut content = layout_content(renderer, &limits.pad(padding).loose());
+ let padding = padding.fit(content.size(), limits.max());
+ let size = limits.pad(padding).resolve(content.size());
content.move_to(Point::new(padding.left.into(), padding.top.into()));
content.align(
@@ -309,7 +309,7 @@ pub fn layout<Renderer>(
layout::Node::with_children(size.pad(padding), vec![content])
}
-/// Draws the background of a [`Container`] given its [`Style`] and its `bounds`.
+/// Draws the background of a [`Container`] given its [`Appearance`] and its `bounds`.
pub fn draw_background<Renderer>(
renderer: &mut Renderer,
appearance: &Appearance,
diff --git a/native/src/widget/helpers.rs b/native/src/widget/helpers.rs
index 79751878..3bce9e60 100644
--- a/native/src/widget/helpers.rs
+++ b/native/src/widget/helpers.rs
@@ -1,4 +1,5 @@
//! Helper functions to create pure widgets.
+use crate::overlay;
use crate::widget;
use crate::{Element, Length};
@@ -18,7 +19,7 @@ macro_rules! column {
);
}
-/// Creates a [Row`] with the given children.
+/// Creates a [`Row`] with the given children.
///
/// [`Row`]: widget::Row
#[macro_export]
@@ -84,6 +85,7 @@ pub fn button<'a, Message, Renderer>(
where
Renderer: crate::Renderer,
Renderer::Theme: widget::button::StyleSheet,
+ <Renderer::Theme as widget::button::StyleSheet>::Style: Default,
{
widget::Button::new(content)
}
@@ -208,7 +210,12 @@ where
T: ToString + Eq + 'static,
[T]: ToOwned<Owned = Vec<T>>,
Renderer: crate::text::Renderer,
- Renderer::Theme: widget::pick_list::StyleSheet,
+ Renderer::Theme: widget::pick_list::StyleSheet
+ + widget::scrollable::StyleSheet
+ + overlay::menu::StyleSheet
+ + widget::container::StyleSheet,
+ <Renderer::Theme as overlay::menu::StyleSheet>::Style:
+ From<<Renderer::Theme as widget::pick_list::StyleSheet>::Style>,
{
widget::PickList::new(options, selected, on_selected)
}
diff --git a/native/src/widget/image.rs b/native/src/widget/image.rs
index 91d68e34..8bd8ca1e 100644
--- a/native/src/widget/image.rs
+++ b/native/src/widget/image.rs
@@ -85,7 +85,7 @@ where
{
// The raw w/h of the underlying image
let image_size = {
- let (width, height) = renderer.dimensions(handle);
+ let Size { width, height } = renderer.dimensions(handle);
Size::new(width as f32, height as f32)
};
@@ -149,7 +149,7 @@ where
_cursor_position: Point,
_viewport: &Rectangle,
) {
- let (width, height) = renderer.dimensions(&self.handle);
+ let Size { width, height } = renderer.dimensions(&self.handle);
let image_size = Size::new(width as f32, height as f32);
let bounds = layout.bounds();
diff --git a/native/src/widget/image/viewer.rs b/native/src/widget/image/viewer.rs
index b1fe596c..9c83287e 100644
--- a/native/src/widget/image/viewer.rs
+++ b/native/src/widget/image/viewer.rs
@@ -108,7 +108,7 @@ where
renderer: &Renderer,
limits: &layout::Limits,
) -> layout::Node {
- let (width, height) = renderer.dimensions(&self.handle);
+ let Size { width, height } = renderer.dimensions(&self.handle);
let mut size = limits
.width(self.width)
@@ -409,7 +409,7 @@ pub fn image_size<Renderer>(
where
Renderer: image::Renderer,
{
- let (width, height) = renderer.dimensions(handle);
+ let Size { width, height } = renderer.dimensions(handle);
let (width, height) = {
let dimensions = (width as f32, height as f32);
diff --git a/native/src/widget/operation.rs b/native/src/widget/operation.rs
index ef636aa2..a0aa4117 100644
--- a/native/src/widget/operation.rs
+++ b/native/src/widget/operation.rs
@@ -1,9 +1,11 @@
//! Query or update internal widget state.
pub mod focusable;
pub mod scrollable;
+pub mod text_input;
pub use focusable::Focusable;
pub use scrollable::Scrollable;
+pub use text_input::TextInput;
use crate::widget::Id;
@@ -28,6 +30,9 @@ pub trait Operation<T> {
/// Operates on a widget that can be scrolled.
fn scrollable(&mut self, _state: &mut dyn Scrollable, _id: Option<&Id>) {}
+ /// Operates on a widget that has text input.
+ fn text_input(&mut self, _state: &mut dyn TextInput, _id: Option<&Id>) {}
+
/// Finishes the [`Operation`] and returns its [`Outcome`].
fn finish(&self) -> Outcome<T> {
Outcome::None
@@ -58,3 +63,46 @@ where
}
}
}
+
+/// Produces an [`Operation`] that applies the given [`Operation`] to the
+/// children of a container with the given [`Id`].
+pub fn scoped<T: 'static>(
+ target: Id,
+ operation: impl Operation<T> + 'static,
+) -> impl Operation<T> {
+ struct ScopedOperation<Message> {
+ target: Id,
+ operation: Box<dyn Operation<Message>>,
+ }
+
+ impl<Message: 'static> Operation<Message> for ScopedOperation<Message> {
+ fn container(
+ &mut self,
+ id: Option<&Id>,
+ operate_on_children: &mut dyn FnMut(&mut dyn Operation<Message>),
+ ) {
+ if id == Some(&self.target) {
+ operate_on_children(self.operation.as_mut());
+ } else {
+ operate_on_children(self);
+ }
+ }
+
+ fn finish(&self) -> Outcome<Message> {
+ match self.operation.finish() {
+ Outcome::Chain(next) => {
+ Outcome::Chain(Box::new(ScopedOperation {
+ target: self.target.clone(),
+ operation: next,
+ }))
+ }
+ outcome => outcome,
+ }
+ }
+ }
+
+ ScopedOperation {
+ target,
+ operation: Box::new(operation),
+ }
+}
diff --git a/native/src/widget/operation/focusable.rs b/native/src/widget/operation/focusable.rs
index f17bf178..0067006b 100644
--- a/native/src/widget/operation/focusable.rs
+++ b/native/src/widget/operation/focusable.rs
@@ -167,3 +167,37 @@ pub fn focus_next<T>() -> impl Operation<T> {
count(|count| FocusNext { count, current: 0 })
}
+
+/// Produces an [`Operation`] that searches for the current focused widget
+/// and stores its ID. This ignores widgets that do not have an ID.
+pub fn find_focused() -> impl Operation<Id> {
+ struct FindFocused {
+ focused: Option<Id>,
+ }
+
+ impl Operation<Id> for FindFocused {
+ fn focusable(&mut self, state: &mut dyn Focusable, id: Option<&Id>) {
+ if state.is_focused() && id.is_some() {
+ self.focused = id.cloned();
+ }
+ }
+
+ fn container(
+ &mut self,
+ _id: Option<&Id>,
+ operate_on_children: &mut dyn FnMut(&mut dyn Operation<Id>),
+ ) {
+ operate_on_children(self)
+ }
+
+ fn finish(&self) -> Outcome<Id> {
+ if let Some(id) = &self.focused {
+ Outcome::Some(id.clone())
+ } else {
+ Outcome::None
+ }
+ }
+ }
+
+ FindFocused { focused: None }
+}
diff --git a/native/src/widget/operation/text_input.rs b/native/src/widget/operation/text_input.rs
new file mode 100644
index 00000000..4c773e99
--- /dev/null
+++ b/native/src/widget/operation/text_input.rs
@@ -0,0 +1,131 @@
+//! Operate on widgets that have text input.
+use crate::widget::operation::Operation;
+use crate::widget::Id;
+
+/// The internal state of a widget that has text input.
+pub trait TextInput {
+ /// Moves the cursor of the text input to the front of the input text.
+ fn move_cursor_to_front(&mut self);
+ /// Moves the cursor of the text input to the end of the input text.
+ fn move_cursor_to_end(&mut self);
+ /// Moves the cursor of the text input to an arbitrary location.
+ fn move_cursor_to(&mut self, position: usize);
+ /// Selects all the content of the text input.
+ fn select_all(&mut self);
+}
+
+/// Produces an [`Operation`] that moves the cursor of the widget with the given [`Id`] to the
+/// front.
+pub fn move_cursor_to_front<T>(target: Id) -> impl Operation<T> {
+ struct MoveCursor {
+ target: Id,
+ }
+
+ impl<T> Operation<T> for MoveCursor {
+ fn text_input(&mut self, state: &mut dyn TextInput, id: Option<&Id>) {
+ match id {
+ Some(id) if id == &self.target => {
+ state.move_cursor_to_front();
+ }
+ _ => {}
+ }
+ }
+
+ fn container(
+ &mut self,
+ _id: Option<&Id>,
+ operate_on_children: &mut dyn FnMut(&mut dyn Operation<T>),
+ ) {
+ operate_on_children(self)
+ }
+ }
+
+ MoveCursor { target }
+}
+
+/// Produces an [`Operation`] that moves the cursor of the widget with the given [`Id`] to the
+/// end.
+pub fn move_cursor_to_end<T>(target: Id) -> impl Operation<T> {
+ struct MoveCursor {
+ target: Id,
+ }
+
+ impl<T> Operation<T> for MoveCursor {
+ fn text_input(&mut self, state: &mut dyn TextInput, id: Option<&Id>) {
+ match id {
+ Some(id) if id == &self.target => {
+ state.move_cursor_to_end();
+ }
+ _ => {}
+ }
+ }
+
+ fn container(
+ &mut self,
+ _id: Option<&Id>,
+ operate_on_children: &mut dyn FnMut(&mut dyn Operation<T>),
+ ) {
+ operate_on_children(self)
+ }
+ }
+
+ MoveCursor { target }
+}
+
+/// Produces an [`Operation`] that moves the cursor of the widget with the given [`Id`] to the
+/// provided position.
+pub fn move_cursor_to<T>(target: Id, position: usize) -> impl Operation<T> {
+ struct MoveCursor {
+ target: Id,
+ position: usize,
+ }
+
+ impl<T> Operation<T> for MoveCursor {
+ fn text_input(&mut self, state: &mut dyn TextInput, id: Option<&Id>) {
+ match id {
+ Some(id) if id == &self.target => {
+ state.move_cursor_to(self.position);
+ }
+ _ => {}
+ }
+ }
+
+ fn container(
+ &mut self,
+ _id: Option<&Id>,
+ operate_on_children: &mut dyn FnMut(&mut dyn Operation<T>),
+ ) {
+ operate_on_children(self)
+ }
+ }
+
+ MoveCursor { target, position }
+}
+
+/// Produces an [`Operation`] that selects all the content of the widget with the given [`Id`].
+pub fn select_all<T>(target: Id) -> impl Operation<T> {
+ struct MoveCursor {
+ target: Id,
+ }
+
+ impl<T> Operation<T> for MoveCursor {
+ fn text_input(&mut self, state: &mut dyn TextInput, id: Option<&Id>) {
+ match id {
+ Some(id) if id == &self.target => {
+ state.select_all();
+ }
+ _ => {}
+ }
+ }
+
+ fn container(
+ &mut self,
+ _id: Option<&Id>,
+ operate_on_children: &mut dyn FnMut(&mut dyn Operation<T>),
+ ) {
+ operate_on_children(self)
+ }
+ }
+
+ MoveCursor { target }
+}
diff --git a/native/src/widget/pane_grid.rs b/native/src/widget/pane_grid.rs
index 45090ecf..edad993e 100644
--- a/native/src/widget/pane_grid.rs
+++ b/native/src/widget/pane_grid.rs
@@ -6,7 +6,7 @@
//! The [`pane_grid` example] showcases how to use a [`PaneGrid`] with resizing,
//! drag and drop, and hotkey support.
//!
-//! [`pane_grid` example]: https://github.com/iced-rs/iced/tree/0.4/examples/pane_grid
+//! [`pane_grid` example]: https://github.com/iced-rs/iced/tree/0.5/examples/pane_grid
mod axis;
mod configuration;
mod content;
@@ -38,6 +38,7 @@ use crate::mouse;
use crate::overlay;
use crate::renderer;
use crate::touch;
+use crate::widget;
use crate::widget::container;
use crate::widget::tree::{self, Tree};
use crate::{
@@ -85,7 +86,7 @@ use crate::{
/// let (mut state, _) = pane_grid::State::new(PaneState::SomePane);
///
/// let pane_grid =
-/// PaneGrid::new(&state, |pane, state| {
+/// PaneGrid::new(&state, |pane, state, is_maximized| {
/// pane_grid::Content::new(match state {
/// PaneState::SomePane => text("This is some pane"),
/// PaneState::AnotherKindOfPane => text("This is another kind of pane"),
@@ -100,8 +101,7 @@ where
Renderer: crate::Renderer,
Renderer::Theme: StyleSheet + container::StyleSheet,
{
- state: &'a state::Internal,
- elements: Vec<(Pane, Content<'a, Message, Renderer>)>,
+ contents: Contents<'a, Content<'a, Message, Renderer>>,
width: Length,
height: Length,
spacing: u16,
@@ -119,22 +119,35 @@ where
/// Creates a [`PaneGrid`] with the given [`State`] and view function.
///
/// The view function will be called to display each [`Pane`] present in the
- /// [`State`].
+ /// [`State`]. [`bool`] is set if the pane is maximized.
pub fn new<T>(
state: &'a State<T>,
- view: impl Fn(Pane, &'a T) -> Content<'a, Message, Renderer>,
+ view: impl Fn(Pane, &'a T, bool) -> Content<'a, Message, Renderer>,
) -> Self {
- let elements = {
- state
- .panes
- .iter()
- .map(|(pane, pane_state)| (*pane, view(*pane, pane_state)))
- .collect()
+ let contents = if let Some((pane, pane_state)) =
+ state.maximized.and_then(|pane| {
+ state.panes.get(&pane).map(|pane_state| (pane, pane_state))
+ }) {
+ Contents::Maximized(
+ pane,
+ view(pane, pane_state, true),
+ Node::Pane(pane),
+ )
+ } else {
+ Contents::All(
+ state
+ .panes
+ .iter()
+ .map(|(pane, pane_state)| {
+ (*pane, view(*pane, pane_state, false))
+ })
+ .collect(),
+ &state.internal,
+ )
};
Self {
- elements,
- state: &state.internal,
+ contents,
width: Length::Fill,
height: Length::Fill,
spacing: 0,
@@ -208,6 +221,12 @@ where
self.style = style.into();
self
}
+
+ fn drag_enabled(&self) -> bool {
+ (!self.contents.is_maximized())
+ .then(|| self.on_drag.is_some())
+ .unwrap_or_default()
+ }
}
impl<'a, Message, Renderer> Widget<Message, Renderer>
@@ -225,18 +244,25 @@ where
}
fn children(&self) -> Vec<Tree> {
- self.elements
+ self.contents
.iter()
.map(|(_, content)| content.state())
.collect()
}
fn diff(&self, tree: &mut Tree) {
- tree.diff_children_custom(
- &self.elements,
- |state, (_, content)| content.diff(state),
- |(_, content)| content.state(),
- )
+ match &self.contents {
+ Contents::All(contents, _) => tree.diff_children_custom(
+ contents,
+ |state, (_, content)| content.diff(state),
+ |(_, content)| content.state(),
+ ),
+ Contents::Maximized(_, content, _) => tree.diff_children_custom(
+ &[content],
+ |state, content| content.diff(state),
+ |content| content.state(),
+ ),
+ }
}
fn width(&self) -> Length {
@@ -255,15 +281,32 @@ where
layout(
renderer,
limits,
- self.state,
+ self.contents.layout(),
self.width,
self.height,
self.spacing,
- self.elements.iter().map(|(pane, content)| (*pane, content)),
- |element, renderer, limits| element.layout(renderer, limits),
+ self.contents.iter(),
+ |content, renderer, limits| content.layout(renderer, limits),
)
}
+ fn operate(
+ &self,
+ tree: &mut Tree,
+ layout: Layout<'_>,
+ operation: &mut dyn widget::Operation<Message>,
+ ) {
+ operation.container(None, &mut |operation| {
+ self.contents
+ .iter()
+ .zip(&mut tree.children)
+ .zip(layout.children())
+ .for_each(|(((_pane, content), state), layout)| {
+ content.operate(state, layout, operation);
+ })
+ });
+ }
+
fn on_event(
&mut self,
tree: &mut Tree,
@@ -276,28 +319,34 @@ where
) -> event::Status {
let action = tree.state.downcast_mut::<state::Action>();
+ let on_drag = if self.drag_enabled() {
+ &self.on_drag
+ } else {
+ &None
+ };
+
let event_status = update(
action,
- self.state,
+ self.contents.layout(),
&event,
layout,
cursor_position,
shell,
self.spacing,
- self.elements.iter().map(|(pane, content)| (*pane, content)),
+ self.contents.iter(),
&self.on_click,
- &self.on_drag,
+ on_drag,
&self.on_resize,
);
let picked_pane = action.picked_pane().map(|(pane, _)| pane);
- self.elements
+ self.contents
.iter_mut()
.zip(&mut tree.children)
.zip(layout.children())
.map(|(((pane, content), tree), layout)| {
- let is_picked = picked_pane == Some(*pane);
+ let is_picked = picked_pane == Some(pane);
content.on_event(
tree,
@@ -323,14 +372,14 @@ where
) -> mouse::Interaction {
mouse_interaction(
tree.state.downcast_ref(),
- self.state,
+ self.contents.layout(),
layout,
cursor_position,
self.spacing,
self.on_resize.as_ref().map(|(leeway, _)| *leeway),
)
.unwrap_or_else(|| {
- self.elements
+ self.contents
.iter()
.zip(&tree.children)
.zip(layout.children())
@@ -341,7 +390,7 @@ where
cursor_position,
viewport,
renderer,
- self.on_drag.is_some(),
+ self.drag_enabled(),
)
})
.max()
@@ -361,7 +410,7 @@ where
) {
draw(
tree.state.downcast_ref(),
- self.state,
+ self.contents.layout(),
layout,
cursor_position,
renderer,
@@ -370,11 +419,11 @@ where
viewport,
self.spacing,
self.on_resize.as_ref().map(|(leeway, _)| *leeway),
- self.style,
- self.elements
+ &self.style,
+ self.contents
.iter()
.zip(&tree.children)
- .map(|((pane, content), tree)| (*pane, (content, tree))),
+ .map(|((pane, content), tree)| (pane, (content, tree))),
|(content, tree),
renderer,
style,
@@ -395,13 +444,13 @@ where
}
fn overlay<'b>(
- &'b self,
+ &'b mut self,
tree: &'b mut Tree,
layout: Layout<'_>,
renderer: &Renderer,
) -> Option<overlay::Element<'_, Message, Renderer>> {
- self.elements
- .iter()
+ self.contents
+ .iter_mut()
.zip(&mut tree.children)
.zip(layout.children())
.filter_map(|(((_, pane), tree), layout)| {
@@ -429,24 +478,24 @@ where
pub fn layout<Renderer, T>(
renderer: &Renderer,
limits: &layout::Limits,
- state: &state::Internal,
+ node: &Node,
width: Length,
height: Length,
spacing: u16,
- elements: impl Iterator<Item = (Pane, T)>,
- layout_element: impl Fn(T, &Renderer, &layout::Limits) -> layout::Node,
+ contents: impl Iterator<Item = (Pane, T)>,
+ layout_content: impl Fn(T, &Renderer, &layout::Limits) -> layout::Node,
) -> layout::Node {
let limits = limits.width(width).height(height);
let size = limits.resolve(Size::ZERO);
- let regions = state.pane_regions(f32::from(spacing), size);
- let children = elements
- .filter_map(|(pane, element)| {
+ let regions = node.pane_regions(f32::from(spacing), size);
+ let children = contents
+ .filter_map(|(pane, content)| {
let region = regions.get(&pane)?;
let size = Size::new(region.width, region.height);
- let mut node = layout_element(
- element,
+ let mut node = layout_content(
+ content,
renderer,
&layout::Limits::new(size, size),
);
@@ -464,13 +513,13 @@ pub fn layout<Renderer, T>(
/// accordingly.
pub fn update<'a, Message, T: Draggable>(
action: &mut state::Action,
- state: &state::Internal,
+ node: &Node,
event: &Event,
layout: Layout<'_>,
cursor_position: Point,
shell: &mut Shell<'_, Message>,
spacing: u16,
- elements: impl Iterator<Item = (Pane, T)>,
+ contents: impl Iterator<Item = (Pane, T)>,
on_click: &Option<Box<dyn Fn(Pane) -> Message + 'a>>,
on_drag: &Option<Box<dyn Fn(DragEvent) -> Message + 'a>>,
on_resize: &Option<(u16, Box<dyn Fn(ResizeEvent) -> Message + 'a>)>,
@@ -492,7 +541,7 @@ pub fn update<'a, Message, T: Draggable>(
cursor_position.y - bounds.y,
);
- let splits = state.split_regions(
+ let splits = node.split_regions(
f32::from(spacing),
Size::new(bounds.width, bounds.height),
);
@@ -514,7 +563,7 @@ pub fn update<'a, Message, T: Draggable>(
layout,
cursor_position,
shell,
- elements,
+ contents,
on_click,
on_drag,
);
@@ -526,7 +575,7 @@ pub fn update<'a, Message, T: Draggable>(
layout,
cursor_position,
shell,
- elements,
+ contents,
on_click,
on_drag,
);
@@ -539,7 +588,7 @@ pub fn update<'a, Message, T: Draggable>(
| Event::Touch(touch::Event::FingerLost { .. }) => {
if let Some((pane, _)) = action.picked_pane() {
if let Some(on_drag) = on_drag {
- let mut dropped_region = elements
+ let mut dropped_region = contents
.zip(layout.children())
.filter(|(_, layout)| {
layout.bounds().contains(cursor_position)
@@ -570,7 +619,7 @@ pub fn update<'a, Message, T: Draggable>(
if let Some((split, _)) = action.picked_split() {
let bounds = layout.bounds();
- let splits = state.split_regions(
+ let splits = node.split_regions(
f32::from(spacing),
Size::new(bounds.width, bounds.height),
);
@@ -609,13 +658,13 @@ fn click_pane<'a, Message, T>(
layout: Layout<'_>,
cursor_position: Point,
shell: &mut Shell<'_, Message>,
- elements: impl Iterator<Item = (Pane, T)>,
+ contents: impl Iterator<Item = (Pane, T)>,
on_click: &Option<Box<dyn Fn(Pane) -> Message + 'a>>,
on_drag: &Option<Box<dyn Fn(DragEvent) -> Message + 'a>>,
) where
T: Draggable,
{
- let mut clicked_region = elements
+ let mut clicked_region = contents
.zip(layout.children())
.filter(|(_, layout)| layout.bounds().contains(cursor_position));
@@ -642,7 +691,7 @@ fn click_pane<'a, Message, T>(
/// Returns the current [`mouse::Interaction`] of a [`PaneGrid`].
pub fn mouse_interaction(
action: &state::Action,
- state: &state::Internal,
+ node: &Node,
layout: Layout<'_>,
cursor_position: Point,
spacing: u16,
@@ -658,7 +707,7 @@ pub fn mouse_interaction(
let bounds = layout.bounds();
let splits =
- state.split_regions(f32::from(spacing), bounds.size());
+ node.split_regions(f32::from(spacing), bounds.size());
let relative_cursor = Point::new(
cursor_position.x - bounds.x,
@@ -687,7 +736,7 @@ pub fn mouse_interaction(
/// Draws a [`PaneGrid`].
pub fn draw<Renderer, T>(
action: &state::Action,
- state: &state::Internal,
+ node: &Node,
layout: Layout<'_>,
cursor_position: Point,
renderer: &mut Renderer,
@@ -696,8 +745,8 @@ pub fn draw<Renderer, T>(
viewport: &Rectangle,
spacing: u16,
resize_leeway: Option<u16>,
- style: <Renderer::Theme as StyleSheet>::Style,
- elements: impl Iterator<Item = (Pane, T)>,
+ style: &<Renderer::Theme as StyleSheet>::Style,
+ contents: impl Iterator<Item = (Pane, T)>,
draw_pane: impl Fn(
T,
&mut Renderer,
@@ -717,7 +766,7 @@ pub fn draw<Renderer, T>(
.and_then(|(split, axis)| {
let bounds = layout.bounds();
- let splits = state.split_regions(f32::from(spacing), bounds.size());
+ let splits = node.split_regions(f32::from(spacing), bounds.size());
let (_axis, region, ratio) = splits.get(&split)?;
@@ -736,7 +785,7 @@ pub fn draw<Renderer, T>(
);
let splits =
- state.split_regions(f32::from(spacing), bounds.size());
+ node.split_regions(f32::from(spacing), bounds.size());
let (_split, axis, region) = hovered_split(
splits.iter(),
@@ -759,7 +808,7 @@ pub fn draw<Renderer, T>(
let mut render_picked_pane = None;
- for ((id, pane), layout) in elements.zip(layout.children()) {
+ for ((id, pane), layout) in contents.zip(layout.children()) {
match picked_pane {
Some((dragging, origin)) if id == dragging => {
render_picked_pane = Some((pane, origin, layout));
@@ -897,3 +946,49 @@ fn hovered_split<'a>(
})
.next()
}
+
+/// The visible contents of the [`PaneGrid`]
+#[derive(Debug)]
+pub enum Contents<'a, T> {
+ /// All panes are visible
+ All(Vec<(Pane, T)>, &'a state::Internal),
+ /// A maximized pane is visible
+ Maximized(Pane, T, Node),
+}
+
+impl<'a, T> Contents<'a, T> {
+ /// Returns the layout [`Node`] of the [`Contents`]
+ pub fn layout(&self) -> &Node {
+ match self {
+ Contents::All(_, state) => state.layout(),
+ Contents::Maximized(_, _, layout) => layout,
+ }
+ }
+
+ /// Returns an iterator over the values of the [`Contents`]
+ pub fn iter(&self) -> Box<dyn Iterator<Item = (Pane, &T)> + '_> {
+ match self {
+ Contents::All(contents, _) => Box::new(
+ contents.iter().map(|(pane, content)| (*pane, content)),
+ ),
+ Contents::Maximized(pane, content, _) => {
+ Box::new(std::iter::once((*pane, content)))
+ }
+ }
+ }
+
+ fn iter_mut(&mut self) -> Box<dyn Iterator<Item = (Pane, &mut T)> + '_> {
+ match self {
+ Contents::All(contents, _) => Box::new(
+ contents.iter_mut().map(|(pane, content)| (*pane, content)),
+ ),
+ Contents::Maximized(pane, content, _) => {
+ Box::new(std::iter::once((*pane, content)))
+ }
+ }
+ }
+
+ fn is_maximized(&self) -> bool {
+ matches!(self, Self::Maximized(..))
+ }
+}
diff --git a/native/src/widget/pane_grid/content.rs b/native/src/widget/pane_grid/content.rs
index c236d820..5f269d1f 100644
--- a/native/src/widget/pane_grid/content.rs
+++ b/native/src/widget/pane_grid/content.rs
@@ -5,7 +5,7 @@ use crate::overlay;
use crate::renderer;
use crate::widget::container;
use crate::widget::pane_grid::{Draggable, TitleBar};
-use crate::widget::Tree;
+use crate::widget::{self, Tree};
use crate::{Clipboard, Element, Layout, Point, Rectangle, Shell, Size};
/// The content of a [`Pane`].
@@ -87,7 +87,7 @@ where
/// Draws the [`Content`] with the provided [`Renderer`] and [`Layout`].
///
- /// [`Renderer`]: iced_native::Renderer
+ /// [`Renderer`]: crate::Renderer
pub fn draw(
&self,
tree: &Tree,
@@ -103,7 +103,7 @@ where
let bounds = layout.bounds();
{
- let style = theme.appearance(self.style);
+ let style = theme.appearance(&self.style);
container::draw_background(renderer, &style, bounds);
}
@@ -183,6 +183,33 @@ where
}
}
+ pub(crate) fn operate(
+ &self,
+ tree: &mut Tree,
+ layout: Layout<'_>,
+ operation: &mut dyn widget::Operation<Message>,
+ ) {
+ let body_layout = if let Some(title_bar) = &self.title_bar {
+ let mut children = layout.children();
+
+ title_bar.operate(
+ &mut tree.children[1],
+ children.next().unwrap(),
+ operation,
+ );
+
+ children.next().unwrap()
+ } else {
+ layout
+ };
+
+ self.body.as_widget().operate(
+ &mut tree.children[0],
+ body_layout,
+ operation,
+ );
+ }
+
pub(crate) fn on_event(
&mut self,
tree: &mut Tree,
@@ -278,12 +305,12 @@ where
}
pub(crate) fn overlay<'b>(
- &'b self,
+ &'b mut self,
tree: &'b mut Tree,
layout: Layout<'_>,
renderer: &Renderer,
) -> Option<overlay::Element<'b, Message, Renderer>> {
- if let Some(title_bar) = self.title_bar.as_ref() {
+ if let Some(title_bar) = self.title_bar.as_mut() {
let mut children = layout.children();
let title_bar_layout = children.next()?;
@@ -294,14 +321,14 @@ where
match title_bar.overlay(title_bar_state, title_bar_layout, renderer)
{
Some(overlay) => Some(overlay),
- None => self.body.as_widget().overlay(
+ None => self.body.as_widget_mut().overlay(
body_state,
children.next()?,
renderer,
),
}
} else {
- self.body.as_widget().overlay(
+ self.body.as_widget_mut().overlay(
&mut tree.children[0],
layout,
renderer,
diff --git a/native/src/widget/pane_grid/state.rs b/native/src/widget/pane_grid/state.rs
index cdca6267..c4ae0a0e 100644
--- a/native/src/widget/pane_grid/state.rs
+++ b/native/src/widget/pane_grid/state.rs
@@ -4,9 +4,9 @@
use crate::widget::pane_grid::{
Axis, Configuration, Direction, Node, Pane, Split,
};
-use crate::{Point, Rectangle, Size};
+use crate::{Point, Size};
-use std::collections::{BTreeMap, HashMap};
+use std::collections::HashMap;
/// The state of a [`PaneGrid`].
///
@@ -31,6 +31,11 @@ pub struct State<T> {
///
/// [`PaneGrid`]: crate::widget::PaneGrid
pub internal: Internal,
+
+ /// The maximized [`Pane`] of the [`PaneGrid`].
+ ///
+ /// [`PaneGrid`]: crate::widget::PaneGrid
+ pub(super) maximized: Option<Pane>,
}
impl<T> State<T> {
@@ -52,7 +57,11 @@ impl<T> State<T> {
let internal =
Internal::from_configuration(&mut panes, config.into(), 0);
- State { panes, internal }
+ State {
+ panes,
+ internal,
+ maximized: None,
+ }
}
/// Returns the total amount of panes in the [`State`].
@@ -153,6 +162,7 @@ impl<T> State<T> {
node.split(new_split, axis, new_pane);
let _ = self.panes.insert(new_pane, state);
+ let _ = self.maximized.take();
Some((new_pane, new_split))
}
@@ -194,12 +204,39 @@ impl<T> State<T> {
/// Closes the given [`Pane`] and returns its internal state and its closest
/// sibling, if it exists.
pub fn close(&mut self, pane: &Pane) -> Option<(T, Pane)> {
+ if self.maximized == Some(*pane) {
+ let _ = self.maximized.take();
+ }
+
if let Some(sibling) = self.internal.layout.remove(pane) {
self.panes.remove(pane).map(|state| (state, sibling))
} else {
None
}
}
+
+ /// Maximize the given [`Pane`]. Only this pane will be rendered by the
+ /// [`PaneGrid`] until [`Self::restore()`] is called.
+ ///
+ /// [`PaneGrid`]: crate::widget::PaneGrid
+ pub fn maximize(&mut self, pane: &Pane) {
+ self.maximized = Some(*pane);
+ }
+
+ /// Restore the currently maximized [`Pane`] to it's normal size. All panes
+ /// will be rendered by the [`PaneGrid`].
+ ///
+ /// [`PaneGrid`]: crate::widget::PaneGrid
+ pub fn restore(&mut self) {
+ let _ = self.maximized.take();
+ }
+
+ /// Returns the maximized [`Pane`] of the [`PaneGrid`].
+ ///
+ /// [`PaneGrid`]: crate::widget::PaneGrid
+ pub fn maximized(&self) -> Option<Pane> {
+ self.maximized
+ }
}
/// The internal state of a [`PaneGrid`].
@@ -226,11 +263,13 @@ impl Internal {
let Internal {
layout: a,
last_id: next_id,
+ ..
} = Self::from_configuration(panes, *a, next_id);
let Internal {
layout: b,
last_id: next_id,
+ ..
} = Self::from_configuration(panes, *b, next_id);
(
@@ -304,25 +343,8 @@ impl Action {
}
impl Internal {
- /// Calculates the current [`Pane`] regions from the [`PaneGrid`] layout.
- ///
- /// [`PaneGrid`]: crate::widget::PaneGrid
- pub fn pane_regions(
- &self,
- spacing: f32,
- size: Size,
- ) -> BTreeMap<Pane, Rectangle> {
- self.layout.pane_regions(spacing, size)
- }
-
- /// Calculates the current [`Split`] regions from the [`PaneGrid`] layout.
- ///
- /// [`PaneGrid`]: crate::widget::PaneGrid
- pub fn split_regions(
- &self,
- spacing: f32,
- size: Size,
- ) -> BTreeMap<Split, (Axis, Rectangle, f32)> {
- self.layout.split_regions(spacing, size)
+ /// The layout [`Node`] of the [`Internal`] state
+ pub fn layout(&self) -> &Node {
+ &self.layout
}
}
diff --git a/native/src/widget/pane_grid/title_bar.rs b/native/src/widget/pane_grid/title_bar.rs
index eb85f924..28e4670f 100644
--- a/native/src/widget/pane_grid/title_bar.rs
+++ b/native/src/widget/pane_grid/title_bar.rs
@@ -4,7 +4,7 @@ use crate::mouse;
use crate::overlay;
use crate::renderer;
use crate::widget::container;
-use crate::widget::Tree;
+use crate::widget::{self, Tree};
use crate::{
Clipboard, Element, Layout, Padding, Point, Rectangle, Shell, Size,
};
@@ -114,7 +114,7 @@ where
/// Draws the [`TitleBar`] with the provided [`Renderer`] and [`Layout`].
///
- /// [`Renderer`]: iced_native::Renderer
+ /// [`Renderer`]: crate::Renderer
pub fn draw(
&self,
tree: &Tree,
@@ -129,7 +129,7 @@ where
use container::StyleSheet;
let bounds = layout.bounds();
- let style = theme.appearance(self.style);
+ let style = theme.appearance(&self.style);
let inherited_style = renderer::Style {
text_color: style.text_color.unwrap_or(inherited_style.text_color),
};
@@ -257,6 +257,44 @@ where
layout::Node::with_children(node.size().pad(self.padding), vec![node])
}
+ pub(crate) fn operate(
+ &self,
+ tree: &mut Tree,
+ layout: Layout<'_>,
+ operation: &mut dyn widget::Operation<Message>,
+ ) {
+ let mut children = layout.children();
+ let padded = children.next().unwrap();
+
+ let mut children = padded.children();
+ let title_layout = children.next().unwrap();
+ let mut show_title = true;
+
+ if let Some(controls) = &self.controls {
+ let controls_layout = children.next().unwrap();
+
+ if title_layout.bounds().width + controls_layout.bounds().width
+ > padded.bounds().width
+ {
+ show_title = false;
+ }
+
+ controls.as_widget().operate(
+ &mut tree.children[1],
+ controls_layout,
+ operation,
+ )
+ };
+
+ if show_title {
+ self.content.as_widget().operate(
+ &mut tree.children[0],
+ title_layout,
+ operation,
+ )
+ }
+ }
+
pub(crate) fn on_event(
&mut self,
tree: &mut Tree,
@@ -357,7 +395,7 @@ where
}
pub(crate) fn overlay<'b>(
- &'b self,
+ &'b mut self,
tree: &'b mut Tree,
layout: Layout<'_>,
renderer: &Renderer,
@@ -377,13 +415,13 @@ where
let controls_state = states.next().unwrap();
content
- .as_widget()
+ .as_widget_mut()
.overlay(title_state, title_layout, renderer)
.or_else(move || {
- controls.as_ref().and_then(|controls| {
+ controls.as_mut().and_then(|controls| {
let controls_layout = children.next()?;
- controls.as_widget().overlay(
+ controls.as_widget_mut().overlay(
controls_state,
controls_layout,
renderer,
diff --git a/native/src/widget/pick_list.rs b/native/src/widget/pick_list.rs
index a92ea655..52cb1ad1 100644
--- a/native/src/widget/pick_list.rs
+++ b/native/src/widget/pick_list.rs
@@ -9,6 +9,8 @@ use crate::overlay::menu::{self, Menu};
use crate::renderer;
use crate::text::{self, Text};
use crate::touch;
+use crate::widget::container;
+use crate::widget::scrollable;
use crate::widget::tree::{self, Tree};
use crate::{
Clipboard, Element, Layout, Length, Padding, Point, Rectangle, Shell, Size,
@@ -42,7 +44,12 @@ where
T: ToString + Eq,
[T]: ToOwned<Owned = Vec<T>>,
Renderer: text::Renderer,
- Renderer::Theme: StyleSheet,
+ Renderer::Theme: StyleSheet
+ + scrollable::StyleSheet
+ + menu::StyleSheet
+ + container::StyleSheet,
+ <Renderer::Theme as menu::StyleSheet>::Style:
+ From<<Renderer::Theme as StyleSheet>::Style>,
{
/// The default padding of a [`PickList`].
pub const DEFAULT_PADDING: Padding = Padding::new(5);
@@ -114,7 +121,12 @@ where
[T]: ToOwned<Owned = Vec<T>>,
Message: 'a,
Renderer: text::Renderer + 'a,
- Renderer::Theme: StyleSheet,
+ Renderer::Theme: StyleSheet
+ + scrollable::StyleSheet
+ + menu::StyleSheet
+ + container::StyleSheet,
+ <Renderer::Theme as menu::StyleSheet>::Style:
+ From<<Renderer::Theme as StyleSheet>::Style>,
{
fn tag(&self) -> tree::Tag {
tree::Tag::of::<State<T>>()
@@ -202,12 +214,12 @@ where
&self.font,
self.placeholder.as_deref(),
self.selected.as_ref(),
- self.style,
+ &self.style,
)
}
fn overlay<'b>(
- &'b self,
+ &'b mut self,
tree: &'b mut Tree,
layout: Layout<'_>,
_renderer: &Renderer,
@@ -221,7 +233,7 @@ where
self.text_size,
self.font.clone(),
&self.options,
- self.style,
+ self.style.clone(),
)
}
}
@@ -233,7 +245,12 @@ where
[T]: ToOwned<Owned = Vec<T>>,
Message: 'a,
Renderer: text::Renderer + 'a,
- Renderer::Theme: StyleSheet,
+ Renderer::Theme: StyleSheet
+ + scrollable::StyleSheet
+ + menu::StyleSheet
+ + container::StyleSheet,
+ <Renderer::Theme as menu::StyleSheet>::Style:
+ From<<Renderer::Theme as StyleSheet>::Style>,
{
fn from(pick_list: PickList<'a, T, Message, Renderer>) -> Self {
Self::new(pick_list)
@@ -456,7 +473,12 @@ where
T: Clone + ToString,
Message: 'a,
Renderer: text::Renderer + 'a,
- Renderer::Theme: StyleSheet,
+ Renderer::Theme: StyleSheet
+ + scrollable::StyleSheet
+ + menu::StyleSheet
+ + container::StyleSheet,
+ <Renderer::Theme as menu::StyleSheet>::Style:
+ From<<Renderer::Theme as StyleSheet>::Style>,
{
if state.is_open {
let bounds = layout.bounds();
@@ -493,7 +515,7 @@ pub fn draw<T, Renderer>(
font: &Renderer::Font,
placeholder: Option<&str>,
selected: Option<&T>,
- style: <Renderer::Theme as StyleSheet>::Style,
+ style: &<Renderer::Theme as StyleSheet>::Style,
) where
Renderer: text::Renderer,
Renderer::Theme: StyleSheet,
diff --git a/native/src/widget/progress_bar.rs b/native/src/widget/progress_bar.rs
index 5f8892fe..f059026f 100644
--- a/native/src/widget/progress_bar.rs
+++ b/native/src/widget/progress_bar.rs
@@ -124,7 +124,7 @@ where
/ (range_end - range_start)
};
- let style = theme.appearance(self.style);
+ let style = theme.appearance(&self.style);
renderer.fill_quad(
renderer::Quad {
diff --git a/native/src/widget/radio.rs b/native/src/widget/radio.rs
index 10e89870..b95ccc5b 100644
--- a/native/src/widget/radio.rs
+++ b/native/src/widget/radio.rs
@@ -230,9 +230,9 @@ where
let mut children = layout.children();
let custom_style = if is_mouse_over {
- theme.hovered(self.style, self.is_selected)
+ theme.hovered(&self.style, self.is_selected)
} else {
- theme.active(self.style, self.is_selected)
+ theme.active(&self.style, self.is_selected)
};
{
diff --git a/native/src/widget/row.rs b/native/src/widget/row.rs
index eda7c2d3..c689ac13 100644
--- a/native/src/widget/row.rs
+++ b/native/src/widget/row.rs
@@ -229,12 +229,12 @@ where
}
fn overlay<'b>(
- &'b self,
+ &'b mut self,
tree: &'b mut Tree,
layout: Layout<'_>,
renderer: &Renderer,
) -> Option<overlay::Element<'b, Message, Renderer>> {
- overlay::from_children(&self.children, tree, layout, renderer)
+ overlay::from_children(&mut self.children, tree, layout, renderer)
}
}
diff --git a/native/src/widget/rule.rs b/native/src/widget/rule.rs
index 1d1c04cf..2dc7b6f0 100644
--- a/native/src/widget/rule.rs
+++ b/native/src/widget/rule.rs
@@ -88,7 +88,7 @@ where
_viewport: &Rectangle,
) {
let bounds = layout.bounds();
- let style = theme.style(self.style);
+ let style = theme.appearance(&self.style);
let bounds = if self.is_horizontal {
let line_y = (bounds.y + (bounds.height / 2.0)
diff --git a/native/src/widget/scrollable.rs b/native/src/widget/scrollable.rs
index b445e505..a5e97aa7 100644
--- a/native/src/widget/scrollable.rs
+++ b/native/src/widget/scrollable.rs
@@ -233,7 +233,7 @@ where
self.scrollbar_width,
self.scrollbar_margin,
self.scroller_width,
- self.style,
+ &self.style,
|renderer, layout, cursor_position, viewport| {
self.content.as_widget().draw(
&tree.children[0],
@@ -276,13 +276,13 @@ where
}
fn overlay<'b>(
- &'b self,
+ &'b mut self,
tree: &'b mut Tree,
layout: Layout<'_>,
renderer: &Renderer,
) -> Option<overlay::Element<'b, Message, Renderer>> {
self.content
- .as_widget()
+ .as_widget_mut()
.overlay(
&mut tree.children[0],
layout.children().next().unwrap(),
@@ -334,6 +334,12 @@ impl Id {
}
}
+impl From<Id> for widget::Id {
+ fn from(id: Id) -> Self {
+ id.0
+ }
+}
+
/// Produces a [`Command`] that snaps the [`Scrollable`] with the given [`Id`]
/// to the provided `percentage`.
pub fn snap_to<Message: 'static>(id: Id, percentage: f32) -> Command<Message> {
@@ -627,7 +633,7 @@ pub fn draw<Renderer>(
scrollbar_width: u16,
scrollbar_margin: u16,
scroller_width: u16,
- style: <Renderer::Theme as StyleSheet>::Style,
+ style: &<Renderer::Theme as StyleSheet>::Style,
draw_content: impl FnOnce(&mut Renderer, Layout<'_>, Point, &Rectangle),
) where
Renderer: crate::Renderer,
diff --git a/native/src/widget/slider.rs b/native/src/widget/slider.rs
index 011454de..87030a4d 100644
--- a/native/src/widget/slider.rs
+++ b/native/src/widget/slider.rs
@@ -222,7 +222,7 @@ where
self.value,
&self.range,
theme,
- self.style,
+ &self.style,
)
}
@@ -353,7 +353,7 @@ pub fn draw<T, R>(
value: T,
range: &RangeInclusive<T>,
style_sheet: &dyn StyleSheet<Style = <R::Theme as StyleSheet>::Style>,
- style: <R::Theme as StyleSheet>::Style,
+ style: &<R::Theme as StyleSheet>::Style,
) where
T: Into<f64> + Copy,
R: crate::Renderer,
diff --git a/native/src/widget/svg.rs b/native/src/widget/svg.rs
index aa68bfb8..1015ed0a 100644
--- a/native/src/widget/svg.rs
+++ b/native/src/widget/svg.rs
@@ -83,7 +83,7 @@ where
limits: &layout::Limits,
) -> layout::Node {
// The raw w/h of the underlying image
- let (width, height) = renderer.dimensions(&self.handle);
+ let Size { width, height } = renderer.dimensions(&self.handle);
let image_size = Size::new(width as f32, height as f32);
// The size to be available to the widget prior to `Shrink`ing
@@ -120,7 +120,7 @@ where
_cursor_position: Point,
_viewport: &Rectangle,
) {
- let (width, height) = renderer.dimensions(&self.handle);
+ let Size { width, height } = renderer.dimensions(&self.handle);
let image_size = Size::new(width as f32, height as f32);
let bounds = layout.bounds();
diff --git a/native/src/widget/text.rs b/native/src/widget/text.rs
index dab6e874..be9e775e 100644
--- a/native/src/widget/text.rs
+++ b/native/src/widget/text.rs
@@ -74,7 +74,7 @@ where
self
}
- /// Sets the [`Color`] of the [`Text`].
+ /// Sets the style of the [`Text`].
pub fn style(
mut self,
style: impl Into<<Renderer::Theme as StyleSheet>::Style>,
diff --git a/native/src/widget/text_input.rs b/native/src/widget/text_input.rs
index ae0305dc..9391d1dd 100644
--- a/native/src/widget/text_input.rs
+++ b/native/src/widget/text_input.rs
@@ -165,7 +165,7 @@ where
}
/// Draws the [`TextInput`] with the given [`Renderer`], overriding its
- /// [`text_input::Value`] if provided.
+ /// [`Value`] if provided.
///
/// [`Renderer`]: text::Renderer
pub fn draw(
@@ -188,7 +188,7 @@ where
self.size,
&self.font,
self.is_secure,
- self.style,
+ &self.style,
)
}
}
@@ -233,6 +233,7 @@ where
let state = tree.state.downcast_mut::<State>();
operation.focusable(state, self.id.as_ref().map(|id| &id.0));
+ operation.text_input(state, self.id.as_ref().map(|id| &id.0));
}
fn on_event(
@@ -284,7 +285,7 @@ where
self.size,
&self.font,
self.is_secure,
- self.style,
+ &self.style,
)
}
@@ -332,11 +333,43 @@ impl Id {
}
}
+impl From<Id> for widget::Id {
+ fn from(id: Id) -> Self {
+ id.0
+ }
+}
+
/// Produces a [`Command`] that focuses the [`TextInput`] with the given [`Id`].
pub fn focus<Message: 'static>(id: Id) -> Command<Message> {
Command::widget(operation::focusable::focus(id.0))
}
+/// Produces a [`Command`] that moves the cursor of the [`TextInput`] with the given [`Id`] to the
+/// end.
+pub fn move_cursor_to_end<Message: 'static>(id: Id) -> Command<Message> {
+ Command::widget(operation::text_input::move_cursor_to_end(id.0))
+}
+
+/// Produces a [`Command`] that moves the cursor of the [`TextInput`] with the given [`Id`] to the
+/// front.
+pub fn move_cursor_to_front<Message: 'static>(id: Id) -> Command<Message> {
+ Command::widget(operation::text_input::move_cursor_to_front(id.0))
+}
+
+/// Produces a [`Command`] that moves the cursor of the [`TextInput`] with the given [`Id`] to the
+/// provided position.
+pub fn move_cursor_to<Message: 'static>(
+ id: Id,
+ position: usize,
+) -> Command<Message> {
+ Command::widget(operation::text_input::move_cursor_to(id.0, position))
+}
+
+/// Produces a [`Command`] that selects all the content of the [`TextInput`] with the given [`Id`].
+pub fn select_all<Message: 'static>(id: Id) -> Command<Message> {
+ Command::widget(operation::text_input::select_all(id.0))
+}
+
/// Computes the layout of a [`TextInput`].
pub fn layout<Renderer>(
renderer: &Renderer,
@@ -350,6 +383,8 @@ where
{
let text_size = size.unwrap_or_else(|| renderer.default_size());
+ let padding = padding.fit(Size::ZERO, limits.max());
+
let limits = limits
.pad(padding)
.width(width)
@@ -742,7 +777,7 @@ pub fn draw<Renderer>(
size: Option<u16>,
font: &Renderer::Font,
is_secure: bool,
- style: <Renderer::Theme as StyleSheet>::Style,
+ style: &<Renderer::Theme as StyleSheet>::Style,
) where
Renderer: text::Renderer,
Renderer::Theme: StyleSheet,
@@ -997,6 +1032,24 @@ impl operation::Focusable for State {
}
}
+impl operation::TextInput for State {
+ fn move_cursor_to_front(&mut self) {
+ State::move_cursor_to_front(self)
+ }
+
+ fn move_cursor_to_end(&mut self) {
+ State::move_cursor_to_end(self)
+ }
+
+ fn move_cursor_to(&mut self, position: usize) {
+ State::move_cursor_to(self, position)
+ }
+
+ fn select_all(&mut self) {
+ State::select_all(self)
+ }
+}
+
mod platform {
use crate::keyboard;
diff --git a/native/src/widget/toggler.rs b/native/src/widget/toggler.rs
index c5c2d82a..37cafd92 100644
--- a/native/src/widget/toggler.rs
+++ b/native/src/widget/toggler.rs
@@ -260,9 +260,9 @@ where
let is_mouse_over = bounds.contains(cursor_position);
let style = if is_mouse_over {
- theme.hovered(self.style, self.is_active)
+ theme.hovered(&self.style, self.is_active)
} else {
- theme.active(self.style, self.is_active)
+ theme.active(&self.style, self.is_active)
};
let border_radius = bounds.height as f32 / BORDER_RADIUS_RATIO;
diff --git a/native/src/widget/tooltip.rs b/native/src/widget/tooltip.rs
index 674f2ba6..084dc269 100644
--- a/native/src/widget/tooltip.rs
+++ b/native/src/widget/tooltip.rs
@@ -201,7 +201,7 @@ where
self.gap,
self.padding,
self.snap_within_viewport,
- self.style,
+ &self.style,
|renderer, limits| {
Widget::<(), Renderer>::layout(tooltip, renderer, limits)
},
@@ -221,12 +221,12 @@ where
}
fn overlay<'b>(
- &'b self,
+ &'b mut self,
tree: &'b mut Tree,
layout: Layout<'_>,
renderer: &Renderer,
) -> Option<overlay::Element<'b, Message, Renderer>> {
- self.content.as_widget().overlay(
+ self.content.as_widget_mut().overlay(
&mut tree.children[0],
layout,
renderer,
@@ -275,7 +275,7 @@ pub fn draw<Renderer>(
gap: u16,
padding: u16,
snap_within_viewport: bool,
- style: <Renderer::Theme as container::StyleSheet>::Style,
+ style: &<Renderer::Theme as container::StyleSheet>::Style,
layout_text: impl FnOnce(&Renderer, &layout::Limits) -> layout::Node,
draw_text: impl FnOnce(
&mut Renderer,
diff --git a/native/src/widget/tree.rs b/native/src/widget/tree.rs
index a8b1a185..0af40c33 100644
--- a/native/src/widget/tree.rs
+++ b/native/src/widget/tree.rs
@@ -30,7 +30,7 @@ impl Tree {
}
}
- /// Creates a new [`Tree`] for the provided [`Element`].
+ /// Creates a new [`Tree`] for the provided [`Widget`].
pub fn new<'a, Message, Renderer>(
widget: impl Borrow<dyn Widget<Message, Renderer> + 'a>,
) -> Self
@@ -46,10 +46,10 @@ impl Tree {
}
}
- /// Reconciliates the current tree with the provided [`Element`].
+ /// Reconciliates the current tree with the provided [`Widget`].
///
- /// If the tag of the [`Element`] matches the tag of the [`Tree`], then the
- /// [`Element`] proceeds with the reconciliation (i.e. [`Widget::diff`] is called).
+ /// If the tag of the [`Widget`] matches the tag of the [`Tree`], then the
+ /// [`Widget`] proceeds with the reconciliation (i.e. [`Widget::diff`] is called).
///
/// Otherwise, the whole [`Tree`] is recreated.
///
@@ -67,7 +67,7 @@ impl Tree {
}
}
- /// Reconciliates the children of the tree with the provided list of [`Element`].
+ /// Reconciliates the children of the tree with the provided list of widgets.
pub fn diff_children<'a, Message, Renderer>(
&mut self,
new_children: &[impl Borrow<dyn Widget<Message, Renderer> + 'a>],
@@ -81,7 +81,7 @@ impl Tree {
)
}
- /// Reconciliates the children of the tree with the provided list of [`Element`] using custom
+ /// Reconciliates the children of the tree with the provided list of widgets using custom
/// logic both for diffing and creating new widget state.
pub fn diff_children_custom<T>(
&mut self,
diff --git a/src/application.rs b/src/application.rs
index 23ce034e..0f514f66 100644
--- a/src/application.rs
+++ b/src/application.rs
@@ -39,15 +39,15 @@ pub use iced_native::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.4/examples
-/// [`clock`]: https://github.com/iced-rs/iced/tree/0.4/examples/clock
-/// [`download_progress`]: https://github.com/iced-rs/iced/tree/0.4/examples/download_progress
-/// [`events`]: https://github.com/iced-rs/iced/tree/0.4/examples/events
-/// [`game_of_life`]: https://github.com/iced-rs/iced/tree/0.4/examples/game_of_life
-/// [`pokedex`]: https://github.com/iced-rs/iced/tree/0.4/examples/pokedex
-/// [`solar_system`]: https://github.com/iced-rs/iced/tree/0.4/examples/solar_system
-/// [`stopwatch`]: https://github.com/iced-rs/iced/tree/0.4/examples/stopwatch
-/// [`todos`]: https://github.com/iced-rs/iced/tree/0.4/examples/todos
+/// [The repository has a bunch of examples]: https://github.com/iced-rs/iced/tree/0.5/examples
+/// [`clock`]: https://github.com/iced-rs/iced/tree/0.5/examples/clock
+/// [`download_progress`]: https://github.com/iced-rs/iced/tree/0.5/examples/download_progress
+/// [`events`]: https://github.com/iced-rs/iced/tree/0.5/examples/events
+/// [`game_of_life`]: https://github.com/iced-rs/iced/tree/0.5/examples/game_of_life
+/// [`pokedex`]: https://github.com/iced-rs/iced/tree/0.5/examples/pokedex
+/// [`solar_system`]: https://github.com/iced-rs/iced/tree/0.5/examples/solar_system
+/// [`stopwatch`]: https://github.com/iced-rs/iced/tree/0.5/examples/stopwatch
+/// [`todos`]: https://github.com/iced-rs/iced/tree/0.5/examples/todos
/// [`Sandbox`]: crate::Sandbox
/// [`Canvas`]: crate::widget::Canvas
/// [PokéAPI]: https://pokeapi.co/
@@ -148,9 +148,8 @@ pub trait Application: Sized {
Self::Theme::default()
}
- /// Returns the current [`Style`] of the [`Theme`].
+ /// Returns the current `Style` of the [`Theme`].
///
- /// [`Style`]: <Self::Theme as StyleSheet>::Style
/// [`Theme`]: Self::Theme
fn style(&self) -> <Self::Theme as StyleSheet>::Style {
<Self::Theme as StyleSheet>::Style::default()
diff --git a/src/lib.rs b/src/lib.rs
index b86780f1..4d0484a7 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -26,11 +26,11 @@
//! [Modular ecosystem]: https://github.com/iced-rs/iced/blob/master/ECOSYSTEM.md
//! [renderer-agnostic native runtime]: https://github.com/iced-rs/iced/0.4/master/native
//! [`wgpu`]: https://github.com/gfx-rs/wgpu-rs
-//! [built-in renderer]: https://github.com/iced-rs/iced/tree/0.4/wgpu
-//! [windowing shell]: https://github.com/iced-rs/iced/tree/0.4/winit
+//! [built-in renderer]: https://github.com/iced-rs/iced/tree/0.5/wgpu
+//! [windowing shell]: https://github.com/iced-rs/iced/tree/0.5/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.4/examples
+//! [examples]: https://github.com/iced-rs/iced/tree/0.5/examples
//! [repository]: https://github.com/iced-rs/iced
//!
//! # Overview
diff --git a/src/overlay.rs b/src/overlay.rs
index a7003751..c0f4c492 100644
--- a/src/overlay.rs
+++ b/src/overlay.rs
@@ -3,6 +3,8 @@
/// A generic [`Overlay`].
///
/// This is an alias of an `iced_native` element with a default `Renderer`.
+///
+/// [`Overlay`]: iced_native::Overlay
pub type Element<'a, Message, Renderer = crate::Renderer> =
iced_native::overlay::Element<'a, Message, Renderer>;
diff --git a/src/sandbox.rs b/src/sandbox.rs
index bdb6ad5a..dc69b6bb 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.4/examples
-/// [`bezier_tool`]: https://github.com/iced-rs/iced/tree/0.4/examples/bezier_tool
-/// [`counter`]: https://github.com/iced-rs/iced/tree/0.4/examples/counter
-/// [`custom_widget`]: https://github.com/iced-rs/iced/tree/0.4/examples/custom_widget
-/// [`geometry`]: https://github.com/iced-rs/iced/tree/0.4/examples/geometry
-/// [`pane_grid`]: https://github.com/iced-rs/iced/tree/0.4/examples/pane_grid
-/// [`progress_bar`]: https://github.com/iced-rs/iced/tree/0.4/examples/progress_bar
-/// [`styling`]: https://github.com/iced-rs/iced/tree/0.4/examples/styling
-/// [`svg`]: https://github.com/iced-rs/iced/tree/0.4/examples/svg
-/// [`tour`]: https://github.com/iced-rs/iced/tree/0.4/examples/tour
+/// [The repository has a bunch of examples]: https://github.com/iced-rs/iced/tree/0.5/examples
+/// [`bezier_tool`]: https://github.com/iced-rs/iced/tree/0.5/examples/bezier_tool
+/// [`counter`]: https://github.com/iced-rs/iced/tree/0.5/examples/counter
+/// [`custom_widget`]: https://github.com/iced-rs/iced/tree/0.5/examples/custom_widget
+/// [`geometry`]: https://github.com/iced-rs/iced/tree/0.5/examples/geometry
+/// [`pane_grid`]: https://github.com/iced-rs/iced/tree/0.5/examples/pane_grid
+/// [`progress_bar`]: https://github.com/iced-rs/iced/tree/0.5/examples/progress_bar
+/// [`styling`]: https://github.com/iced-rs/iced/tree/0.5/examples/styling
+/// [`svg`]: https://github.com/iced-rs/iced/tree/0.5/examples/svg
+/// [`tour`]: https://github.com/iced-rs/iced/tree/0.5/examples/tour
/// [`Canvas widget`]: crate::widget::Canvas
/// [the overview]: index.html#overview
-/// [`iced_wgpu`]: https://github.com/iced-rs/iced/tree/0.4/wgpu
+/// [`iced_wgpu`]: https://github.com/iced-rs/iced/tree/0.5/wgpu
/// [`Svg` widget]: crate::widget::Svg
/// [Ghostscript Tiger]: https://commons.wikimedia.org/wiki/File:Ghostscript_Tiger.svg
///
diff --git a/src/widget.rs b/src/widget.rs
index 9f09cb8f..7c67a599 100644
--- a/src/widget.rs
+++ b/src/widget.rs
@@ -56,7 +56,7 @@ pub mod pane_grid {
//! 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.4/examples/pane_grid
+ //! [`pane_grid` example]: https://github.com/iced-rs/iced/tree/0.5/examples/pane_grid
pub use iced_native::widget::pane_grid::{
Axis, Configuration, Direction, DragEvent, Line, Node, Pane,
ResizeEvent, Split, State, StyleSheet,
@@ -120,7 +120,8 @@ pub mod toggler {
pub mod text_input {
//! Display fields that can be filled with text.
pub use iced_native::widget::text_input::{
- focus, Appearance, Id, StyleSheet,
+ focus, move_cursor_to, move_cursor_to_end, move_cursor_to_front,
+ select_all, Appearance, Id, StyleSheet,
};
/// A field that can be filled with text.
diff --git a/src/window.rs b/src/window.rs
index eb5e17a6..2018053f 100644
--- a/src/window.rs
+++ b/src/window.rs
@@ -9,4 +9,4 @@ pub use position::Position;
pub use settings::Settings;
#[cfg(not(target_arch = "wasm32"))]
-pub use crate::runtime::window::{move_to, resize};
+pub use crate::runtime::window::*;
diff --git a/style/Cargo.toml b/style/Cargo.toml
index 4a482a4f..559b4462 100644
--- a/style/Cargo.toml
+++ b/style/Cargo.toml
@@ -1,6 +1,6 @@
[package]
name = "iced_style"
-version = "0.4.0"
+version = "0.5.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.5"
+version = "0.6"
path = "../core"
features = ["palette"]
diff --git a/style/src/application.rs b/style/src/application.rs
index d48c6a34..e9a1f4ff 100644
--- a/style/src/application.rs
+++ b/style/src/application.rs
@@ -1,13 +1,23 @@
+//! Change the appearance of an application.
use iced_core::Color;
+/// A set of rules that dictate the style of an application.
pub trait StyleSheet {
- type Style: Default + Copy;
+ /// The supported style of the [`StyleSheet`].
+ type Style: Default;
- fn appearance(&self, style: Self::Style) -> Appearance;
+ /// Returns the [`Appearance`] of the application for the provided [`Style`].
+ ///
+ /// [`Style`]: Self::Style
+ fn appearance(&self, style: &Self::Style) -> Appearance;
}
+/// The appearance of an application.
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Appearance {
+ /// The background [`Color`] of the application.
pub background_color: Color,
+
+ /// The default text [`Color`] of the application.
pub text_color: Color,
}
diff --git a/style/src/button.rs b/style/src/button.rs
index c63a6b71..a564a2b7 100644
--- a/style/src/button.rs
+++ b/style/src/button.rs
@@ -1,14 +1,20 @@
-//! Allow your users to perform actions by pressing a button.
+//! Change the apperance of a button.
use iced_core::{Background, Color, Vector};
/// The appearance of a button.
#[derive(Debug, Clone, Copy)]
pub struct Appearance {
+ /// The amount of offset to apply to the shadow of the button.
pub shadow_offset: Vector,
+ /// The [`Background`] of the button.
pub background: Option<Background>,
+ /// The border radius of the button.
pub border_radius: f32,
+ /// The border width of the button.
pub border_width: f32,
+ /// The border [`Color`] of the button.
pub border_color: Color,
+ /// The text [`Color`] of the button.
pub text_color: Color,
}
@@ -27,11 +33,14 @@ impl std::default::Default for Appearance {
/// A set of rules that dictate the style of a button.
pub trait StyleSheet {
- type Style: Default + Copy;
+ /// The supported style of the [`StyleSheet`].
+ type Style: Default;
- fn active(&self, style: Self::Style) -> Appearance;
+ /// Produces the active [`Appearance`] of a button.
+ fn active(&self, style: &Self::Style) -> Appearance;
- fn hovered(&self, style: Self::Style) -> Appearance {
+ /// Produces the hovered [`Appearance`] of a button.
+ fn hovered(&self, style: &Self::Style) -> Appearance {
let active = self.active(style);
Appearance {
@@ -40,14 +49,16 @@ pub trait StyleSheet {
}
}
- fn pressed(&self, style: Self::Style) -> Appearance {
+ /// Produces the pressed [`Appearance`] of a button.
+ fn pressed(&self, style: &Self::Style) -> Appearance {
Appearance {
shadow_offset: Vector::default(),
..self.active(style)
}
}
- fn disabled(&self, style: Self::Style) -> Appearance {
+ /// Produces the disabled [`Appearance`] of a button.
+ fn disabled(&self, style: &Self::Style) -> Appearance {
let active = self.active(style);
Appearance {
diff --git a/style/src/checkbox.rs b/style/src/checkbox.rs
index ba54b0a2..827b3225 100644
--- a/style/src/checkbox.rs
+++ b/style/src/checkbox.rs
@@ -1,22 +1,31 @@
-//! Show toggle controls using checkboxes.
+//! Change the appearance of a checkbox.
use iced_core::{Background, Color};
/// The appearance of a checkbox.
#[derive(Debug, Clone, Copy)]
pub struct Appearance {
+ /// The [`Background`] of the checkbox.
pub background: Background,
+ /// The checkmark [`Color`] of the checkbox.
pub checkmark_color: Color,
+ /// The border radius of the checkbox.
pub border_radius: f32,
+ /// The border width of the checkbox.
pub border_width: f32,
+ /// The border [`Color`] of the checkbox.
pub border_color: Color,
+ /// The text [`Color`] of the checkbox.
pub text_color: Option<Color>,
}
/// A set of rules that dictate the style of a checkbox.
pub trait StyleSheet {
- type Style: Default + Copy;
+ /// The supported style of the [`StyleSheet`].
+ type Style: Default;
- fn active(&self, style: Self::Style, is_checked: bool) -> Appearance;
+ /// Produces the active [`Appearance`] of a checkbox.
+ fn active(&self, style: &Self::Style, is_checked: bool) -> Appearance;
- fn hovered(&self, style: Self::Style, is_checked: bool) -> Appearance;
+ /// Produces the hovered [`Appearance`] of a checkbox.
+ fn hovered(&self, style: &Self::Style, is_checked: bool) -> Appearance;
}
diff --git a/style/src/container.rs b/style/src/container.rs
index 184310fa..560b2d5b 100644
--- a/style/src/container.rs
+++ b/style/src/container.rs
@@ -1,13 +1,18 @@
-//! Decorate content and apply alignment.
+//! Change the appearance of a container.
use iced_core::{Background, Color};
/// The appearance of a container.
#[derive(Debug, Clone, Copy)]
pub struct Appearance {
+ /// The text [`Color`] of the container.
pub text_color: Option<Color>,
+ /// The [`Background`] of the container.
pub background: Option<Background>,
+ /// The border radius of the container.
pub border_radius: f32,
+ /// The border width of the container.
pub border_width: f32,
+ /// The border [`Color`] of the container.
pub border_color: Color,
}
@@ -25,8 +30,9 @@ impl std::default::Default for Appearance {
/// A set of rules that dictate the [`Appearance`] of a container.
pub trait StyleSheet {
- type Style: Default + Copy;
+ /// The supported style of the [`StyleSheet`].
+ type Style: Default;
/// Produces the [`Appearance`] of a container.
- fn appearance(&self, style: Self::Style) -> Appearance;
+ fn appearance(&self, style: &Self::Style) -> Appearance;
}
diff --git a/style/src/lib.rs b/style/src/lib.rs
index 0dde9582..3242602c 100644
--- a/style/src/lib.rs
+++ b/style/src/lib.rs
@@ -15,6 +15,7 @@
clippy::new_without_default,
clippy::useless_conversion
)]
+#![deny(missing_docs, unused_results)]
#![forbid(unsafe_code, rust_2018_idioms)]
#![allow(clippy::inherent_to_string, clippy::type_complexity)]
pub use iced_core::{Background, Color};
diff --git a/style/src/menu.rs b/style/src/menu.rs
index 6ef3e2a2..7d878748 100644
--- a/style/src/menu.rs
+++ b/style/src/menu.rs
@@ -1,19 +1,30 @@
+//! Change the appearance of menus.
use iced_core::{Background, Color};
/// The appearance of a menu.
#[derive(Debug, Clone, Copy)]
pub struct Appearance {
+ /// The text [`Color`] of the menu.
pub text_color: Color,
+ /// The [`Background`] of the menu.
pub background: Background,
+ /// The border width of the menu.
pub border_width: f32,
+ /// The border radius of the menu.
pub border_radius: f32,
+ /// The border [`Color`] of the menu.
pub border_color: Color,
+ /// The text [`Color`] of a selected option in the menu.
pub selected_text_color: Color,
+ /// The background [`Color`] of a selected option in the menu.
pub selected_background: Background,
}
+/// The style sheet of a menu.
pub trait StyleSheet {
- type Style: Default + Copy;
+ /// The supported style of the [`StyleSheet`].
+ type Style: Default + Clone;
- fn appearance(&self, style: Self::Style) -> Appearance;
+ /// Produces the [`Appearance`] of a menu.
+ fn appearance(&self, style: &Self::Style) -> Appearance;
}
diff --git a/style/src/pane_grid.rs b/style/src/pane_grid.rs
index 5bae353f..fd8fc05f 100644
--- a/style/src/pane_grid.rs
+++ b/style/src/pane_grid.rs
@@ -1,16 +1,16 @@
-//! Let your users split regions of your application and organize layout
-//! dynamically.
+//! Change the appearance of a pane grid.
use iced_core::Color;
/// A set of rules that dictate the style of a container.
pub trait StyleSheet {
- type Style: Default + Copy;
+ /// The supported style of the [`StyleSheet`].
+ type Style: Default;
/// The [`Line`] to draw when a split is picked.
- fn picked_split(&self, style: Self::Style) -> Option<Line>;
+ fn picked_split(&self, style: &Self::Style) -> Option<Line>;
/// The [`Line`] to draw when a split is hovered.
- fn hovered_split(&self, style: Self::Style) -> Option<Line>;
+ fn hovered_split(&self, style: &Self::Style) -> Option<Line>;
}
/// A line.
diff --git a/style/src/pick_list.rs b/style/src/pick_list.rs
index 2bafe932..8d93dff2 100644
--- a/style/src/pick_list.rs
+++ b/style/src/pick_list.rs
@@ -1,29 +1,33 @@
+//! Change the appearance of a pick list.
use iced_core::{Background, Color};
-use crate::container;
-use crate::menu;
-use crate::scrollable;
-
/// The appearance of a pick list.
#[derive(Debug, Clone, Copy)]
pub struct Appearance {
+ /// The text [`Color`] of the pick list.
pub text_color: Color,
+ /// The placeholder [`Color`] of the pick list.
pub placeholder_color: Color,
+ /// The [`Background`] of the pick list.
pub background: Background,
+ /// The border radius of the pick list.
pub border_radius: f32,
+ /// The border width of the pick list.
pub border_width: f32,
+ /// The border color of the pick list.
pub border_color: Color,
+ /// The size of the arrow icon of the pick list.
pub icon_size: f32,
}
/// A set of rules that dictate the style of a container.
-pub trait StyleSheet:
- container::StyleSheet + menu::StyleSheet + scrollable::StyleSheet
-{
- type Style: Default + Copy + Into<<Self as menu::StyleSheet>::Style>;
+pub trait StyleSheet {
+ /// The supported style of the [`StyleSheet`].
+ type Style: Default + Clone;
- fn active(&self, style: <Self as StyleSheet>::Style) -> Appearance;
+ /// Produces the active [`Appearance`] of a pick list.
+ fn active(&self, style: &<Self as StyleSheet>::Style) -> Appearance;
- /// Produces the style of a container.
- fn hovered(&self, style: <Self as StyleSheet>::Style) -> Appearance;
+ /// Produces the hovered [`Appearance`] of a pick list.
+ fn hovered(&self, style: &<Self as StyleSheet>::Style) -> Appearance;
}
diff --git a/style/src/progress_bar.rs b/style/src/progress_bar.rs
index 768e7c9c..fb1819fc 100644
--- a/style/src/progress_bar.rs
+++ b/style/src/progress_bar.rs
@@ -1,17 +1,22 @@
-//! Provide progress feedback to your users.
+//! Change the appearance of a progress bar.
use iced_core::Background;
/// The appearance of a progress bar.
#[derive(Debug, Clone, Copy)]
pub struct Appearance {
+ /// The [`Background`] of the progress bar.
pub background: Background,
+ /// The [`Background`] of the bar of the progress bar.
pub bar: Background,
+ /// The border radius of the progress bar.
pub border_radius: f32,
}
/// A set of rules that dictate the style of a progress bar.
pub trait StyleSheet {
- type Style: Default + Copy;
+ /// The supported style of the [`StyleSheet`].
+ type Style: Default;
- fn appearance(&self, style: Self::Style) -> Appearance;
+ /// Produces the [`Appearance`] of the progress bar.
+ fn appearance(&self, style: &Self::Style) -> Appearance;
}
diff --git a/style/src/radio.rs b/style/src/radio.rs
index d14ea33e..06c49029 100644
--- a/style/src/radio.rs
+++ b/style/src/radio.rs
@@ -1,21 +1,29 @@
-//! Create choices using radio buttons.
+//! Change the appearance of radio buttons.
use iced_core::{Background, Color};
/// The appearance of a radio button.
#[derive(Debug, Clone, Copy)]
pub struct Appearance {
+ /// The [`Background`] of the radio button.
pub background: Background,
+ /// The [`Color`] of the dot of the radio button.
pub dot_color: Color,
+ /// The border width of the radio button.
pub border_width: f32,
+ /// The border [`Color`] of the radio button.
pub border_color: Color,
+ /// The text [`Color`] of the radio button.
pub text_color: Option<Color>,
}
/// A set of rules that dictate the style of a radio button.
pub trait StyleSheet {
- type Style: Default + Copy;
+ /// The supported style of the [`StyleSheet`].
+ type Style: Default;
- fn active(&self, style: Self::Style, is_selected: bool) -> Appearance;
+ /// Produces the active [`Appearance`] of a radio button.
+ fn active(&self, style: &Self::Style, is_selected: bool) -> Appearance;
- fn hovered(&self, style: Self::Style, is_selected: bool) -> Appearance;
+ /// Produces the hovered [`Appearance`] of a radio button.
+ fn hovered(&self, style: &Self::Style, is_selected: bool) -> Appearance;
}
diff --git a/style/src/rule.rs b/style/src/rule.rs
index af334912..b7380747 100644
--- a/style/src/rule.rs
+++ b/style/src/rule.rs
@@ -1,4 +1,4 @@
-//! Display a horizontal or vertical rule for dividing content.
+//! Change the appearance of a rule.
use iced_core::Color;
/// The appearance of a rule.
@@ -16,10 +16,11 @@ pub struct Appearance {
/// A set of rules that dictate the style of a rule.
pub trait StyleSheet {
- type Style: Default + Copy;
+ /// The supported style of the [`StyleSheet`].
+ type Style: Default;
/// Produces the style of a rule.
- fn style(&self, style: Self::Style) -> Appearance;
+ fn appearance(&self, style: &Self::Style) -> Appearance;
}
/// The fill mode of a rule.
diff --git a/style/src/scrollable.rs b/style/src/scrollable.rs
index 8da7409c..c6d7d537 100644
--- a/style/src/scrollable.rs
+++ b/style/src/scrollable.rs
@@ -1,37 +1,47 @@
-//! Navigate an endless amount of content with a scrollbar.
+//! Change the appearance of a scrollable.
use iced_core::{Background, Color};
/// The appearance of a scrollable.
#[derive(Debug, Clone, Copy)]
pub struct Scrollbar {
+ /// The [`Background`] of a scrollable.
pub background: Option<Background>,
+ /// The border radius of a scrollable.
pub border_radius: f32,
+ /// The border width of a scrollable.
pub border_width: f32,
+ /// The border [`Color`] of a scrollable.
pub border_color: Color,
+ /// The appearance of the [`Scroller`] of a scrollable.
pub scroller: Scroller,
}
/// The appearance of the scroller of a scrollable.
#[derive(Debug, Clone, Copy)]
pub struct Scroller {
+ /// The [`Color`] of the scroller.
pub color: Color,
+ /// The border radius of the scroller.
pub border_radius: f32,
+ /// The border width of the scroller.
pub border_width: f32,
+ /// The border [`Color`] of the scroller.
pub border_color: Color,
}
/// A set of rules that dictate the style of a scrollable.
pub trait StyleSheet {
- type Style: Default + Copy;
+ /// The supported style of the [`StyleSheet`].
+ type Style: Default;
/// Produces the style of an active scrollbar.
- fn active(&self, style: Self::Style) -> Scrollbar;
+ fn active(&self, style: &Self::Style) -> Scrollbar;
/// Produces the style of an hovered scrollbar.
- fn hovered(&self, style: Self::Style) -> Scrollbar;
+ fn hovered(&self, style: &Self::Style) -> Scrollbar;
/// Produces the style of a scrollbar that is being dragged.
- fn dragging(&self, style: Self::Style) -> Scrollbar {
+ fn dragging(&self, style: &Self::Style) -> Scrollbar {
self.hovered(style)
}
}
diff --git a/style/src/slider.rs b/style/src/slider.rs
index 0ff0449b..4b52fad3 100644
--- a/style/src/slider.rs
+++ b/style/src/slider.rs
@@ -1,39 +1,56 @@
-//! Display an interactive selector of a single value from a range of values.
+//! Change the apperance of a slider.
use iced_core::Color;
/// The appearance of a slider.
#[derive(Debug, Clone, Copy)]
pub struct Appearance {
+ /// The colors of the rail of the slider.
pub rail_colors: (Color, Color),
+ /// The appearance of the [`Handle`] of the slider.
pub handle: Handle,
}
/// The appearance of the handle of a slider.
#[derive(Debug, Clone, Copy)]
pub struct Handle {
+ /// The shape of the handle.
pub shape: HandleShape,
+ /// The [`Color`] of the handle.
pub color: Color,
+ /// The border width of the handle.
pub border_width: f32,
+ /// The border [`Color`] of the handle.
pub border_color: Color,
}
/// The shape of the handle of a slider.
#[derive(Debug, Clone, Copy)]
pub enum HandleShape {
- Circle { radius: f32 },
- Rectangle { width: u16, border_radius: f32 },
+ /// A circular handle.
+ Circle {
+ /// The radius of the circle.
+ radius: f32,
+ },
+ /// A rectangular shape.
+ Rectangle {
+ /// The width of the rectangle.
+ width: u16,
+ /// The border radius of the corners of the rectangle.
+ border_radius: f32,
+ },
}
/// A set of rules that dictate the style of a slider.
pub trait StyleSheet {
- type Style: Default + Copy;
+ /// The supported style of the [`StyleSheet`].
+ type Style: Default;
/// Produces the style of an active slider.
- fn active(&self, style: Self::Style) -> Appearance;
+ fn active(&self, style: &Self::Style) -> Appearance;
/// Produces the style of an hovered slider.
- fn hovered(&self, style: Self::Style) -> Appearance;
+ fn hovered(&self, style: &Self::Style) -> Appearance;
/// Produces the style of a slider that is being dragged.
- fn dragging(&self, style: Self::Style) -> Appearance;
+ fn dragging(&self, style: &Self::Style) -> Appearance;
}
diff --git a/style/src/text.rs b/style/src/text.rs
index 6e3aeef8..f31c2306 100644
--- a/style/src/text.rs
+++ b/style/src/text.rs
@@ -1,12 +1,20 @@
+//! Change the appearance of text.
use iced_core::Color;
+/// The style sheet of some text.
pub trait StyleSheet {
+ /// The supported style of the [`StyleSheet`].
type Style: Default + Copy;
+ /// Produces the [`Appearance`] of some text.
fn appearance(&self, style: Self::Style) -> Appearance;
}
+/// The apperance of some text.
#[derive(Debug, Clone, Copy, Default)]
pub struct Appearance {
+ /// The [`Color`] of the text.
+ ///
+ /// The default, `None`, means using the inherited color.
pub color: Option<Color>,
}
diff --git a/style/src/text_input.rs b/style/src/text_input.rs
index af86617b..d97016dc 100644
--- a/style/src/text_input.rs
+++ b/style/src/text_input.rs
@@ -1,33 +1,41 @@
-//! Display fields that can be filled with text.
+//! Change the appearance of a text input.
use iced_core::{Background, Color};
/// The appearance of a text input.
#[derive(Debug, Clone, Copy)]
pub struct Appearance {
+ /// The [`Background`] of the text input.
pub background: Background,
+ /// The border radius of the text input.
pub border_radius: f32,
+ /// The border width of the text input.
pub border_width: f32,
+ /// The border [`Color`] of the text input.
pub border_color: Color,
}
/// A set of rules that dictate the style of a text input.
pub trait StyleSheet {
- type Style: Default + Copy;
+ /// The supported style of the [`StyleSheet`].
+ type Style: Default;
/// Produces the style of an active text input.
- fn active(&self, style: Self::Style) -> Appearance;
+ fn active(&self, style: &Self::Style) -> Appearance;
/// Produces the style of a focused text input.
- fn focused(&self, style: Self::Style) -> Appearance;
+ fn focused(&self, style: &Self::Style) -> Appearance;
- fn placeholder_color(&self, style: Self::Style) -> Color;
+ /// Produces the [`Color`] of the placeholder of a text input.
+ fn placeholder_color(&self, style: &Self::Style) -> Color;
- fn value_color(&self, style: Self::Style) -> Color;
+ /// Produces the [`Color`] of the value of a text input.
+ fn value_color(&self, style: &Self::Style) -> Color;
- fn selection_color(&self, style: Self::Style) -> Color;
+ /// Produces the [`Color`] of the selection of a text input.
+ fn selection_color(&self, style: &Self::Style) -> Color;
/// Produces the style of an hovered text input.
- fn hovered(&self, style: Self::Style) -> Appearance {
+ fn hovered(&self, style: &Self::Style) -> Appearance {
self.focused(style)
}
}
diff --git a/style/src/theme.rs b/style/src/theme.rs
index a253e990..d7ebb827 100644
--- a/style/src/theme.rs
+++ b/style/src/theme.rs
@@ -1,3 +1,4 @@
+//! Use the built-in theme and styles.
pub mod palette;
use self::palette::Extended;
@@ -19,20 +20,29 @@ use crate::text;
use crate::text_input;
use crate::toggler;
-use iced_core::{Background, Color};
+use iced_core::{Background, Color, Vector};
-#[derive(Debug, Clone, PartialEq)]
+use std::rc::Rc;
+
+/// A built-in theme.
+#[derive(Debug, Clone, PartialEq, Default)]
pub enum Theme {
+ /// The built-in light variant.
+ #[default]
Light,
+ /// The built-in dark variant.
Dark,
+ /// A [`Theme`] that uses a [`Custom`] palette.
Custom(Box<Custom>),
}
impl Theme {
+ /// Creates a new custom [`Theme`] from the given [`Palette`].
pub fn custom(palette: Palette) -> Self {
Self::Custom(Box::new(Custom::new(palette)))
}
+ /// Returns the [`Palette`] of the [`Theme`].
pub fn palette(&self) -> Palette {
match self {
Self::Light => Palette::LIGHT,
@@ -41,6 +51,7 @@ impl Theme {
}
}
+ /// Returns the [`palette::Extended`] of the [`Theme`].
pub fn extended_palette(&self) -> &palette::Extended {
match self {
Self::Light => &palette::EXTENDED_LIGHT,
@@ -50,12 +61,7 @@ impl Theme {
}
}
-impl Default for Theme {
- fn default() -> Self {
- Self::Light
- }
-}
-
+/// A [`Theme`] with a customized [`Palette`].
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Custom {
palette: Palette,
@@ -63,6 +69,7 @@ pub struct Custom {
}
impl Custom {
+ /// Creates a [`Custom`] theme from the given [`Palette`].
pub fn new(palette: Palette) -> Self {
Self {
palette,
@@ -71,22 +78,20 @@ impl Custom {
}
}
-#[derive(Debug, Clone, Copy)]
+/// The style of an application.
+#[derive(Default)]
pub enum Application {
+ /// The default style.
+ #[default]
Default,
- Custom(fn(Theme) -> application::Appearance),
-}
-
-impl Default for Application {
- fn default() -> Self {
- Self::Default
- }
+ /// A custom style.
+ Custom(Box<dyn application::StyleSheet<Style = Theme>>),
}
impl application::StyleSheet for Theme {
type Style = Application;
- fn appearance(&self, style: Self::Style) -> application::Appearance {
+ fn appearance(&self, style: &Self::Style) -> application::Appearance {
let palette = self.extended_palette();
match style {
@@ -94,33 +99,49 @@ impl application::StyleSheet for Theme {
background_color: palette.background.base.color,
text_color: palette.background.base.text,
},
- Application::Custom(f) => f(self.clone()),
+ Application::Custom(custom) => custom.appearance(self),
}
}
}
-/*
- * Button
- */
-#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+impl application::StyleSheet for fn(&Theme) -> application::Appearance {
+ type Style = Theme;
+
+ fn appearance(&self, style: &Self::Style) -> application::Appearance {
+ (self)(style)
+ }
+}
+
+impl From<fn(&Theme) -> application::Appearance> for Application {
+ fn from(f: fn(&Theme) -> application::Appearance) -> Self {
+ Self::Custom(Box::new(f))
+ }
+}
+
+/// The style of a button.
+#[derive(Default)]
pub enum Button {
+ /// The primary style.
+ #[default]
Primary,
+ /// The secondary style.
Secondary,
+ /// The positive style.
Positive,
+ /// The destructive style.
Destructive,
+ /// The text style.
+ ///
+ /// Useful for links!
Text,
-}
-
-impl Default for Button {
- fn default() -> Self {
- Self::Primary
- }
+ /// A custom style.
+ Custom(Box<dyn button::StyleSheet<Style = Theme>>),
}
impl button::StyleSheet for Theme {
type Style = Button;
- fn active(&self, style: Self::Style) -> button::Appearance {
+ fn active(&self, style: &Self::Style) -> button::Appearance {
let palette = self.extended_palette();
let appearance = button::Appearance {
@@ -143,19 +164,25 @@ impl button::StyleSheet for Theme {
text_color: palette.background.base.text,
..appearance
},
+ Button::Custom(custom) => custom.active(self),
}
}
- fn hovered(&self, style: Self::Style) -> button::Appearance {
- let active = self.active(style);
+ fn hovered(&self, style: &Self::Style) -> button::Appearance {
let palette = self.extended_palette();
+ if let Button::Custom(custom) = style {
+ return custom.hovered(self);
+ }
+
+ let active = self.active(style);
+
let background = match style {
Button::Primary => Some(palette.primary.base.color),
Button::Secondary => Some(palette.background.strong.color),
Button::Positive => Some(palette.success.strong.color),
Button::Destructive => Some(palette.danger.strong.color),
- Button::Text => None,
+ Button::Text | Button::Custom(_) => None,
};
button::Appearance {
@@ -163,23 +190,56 @@ impl button::StyleSheet for Theme {
..active
}
}
+
+ fn pressed(&self, style: &Self::Style) -> button::Appearance {
+ if let Button::Custom(custom) = style {
+ return custom.pressed(self);
+ }
+
+ button::Appearance {
+ shadow_offset: Vector::default(),
+ ..self.active(style)
+ }
+ }
+
+ fn disabled(&self, style: &Self::Style) -> button::Appearance {
+ if let Button::Custom(custom) = style {
+ return custom.disabled(self);
+ }
+
+ let active = self.active(style);
+
+ button::Appearance {
+ shadow_offset: Vector::default(),
+ background: active.background.map(|background| match background {
+ Background::Color(color) => Background::Color(Color {
+ a: color.a * 0.5,
+ ..color
+ }),
+ }),
+ text_color: Color {
+ a: active.text_color.a * 0.5,
+ ..active.text_color
+ },
+ ..active
+ }
+ }
}
-/*
- * Checkbox
- */
-#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+/// The style of a checkbox.
+#[derive(Default)]
pub enum Checkbox {
+ /// The primary style.
+ #[default]
Primary,
+ /// The secondary style.
Secondary,
+ /// The success style.
Success,
+ /// The danger style.
Danger,
-}
-
-impl Default for Checkbox {
- fn default() -> Self {
- Self::Primary
- }
+ /// A custom style.
+ Custom(Box<dyn checkbox::StyleSheet<Style = Theme>>),
}
impl checkbox::StyleSheet for Theme {
@@ -187,7 +247,7 @@ impl checkbox::StyleSheet for Theme {
fn active(
&self,
- style: Self::Style,
+ style: &Self::Style,
is_checked: bool,
) -> checkbox::Appearance {
let palette = self.extended_palette();
@@ -217,12 +277,13 @@ impl checkbox::StyleSheet for Theme {
palette.danger.base,
is_checked,
),
+ Checkbox::Custom(custom) => custom.active(self, is_checked),
}
}
fn hovered(
&self,
- style: Self::Style,
+ style: &Self::Style,
is_checked: bool,
) -> checkbox::Appearance {
let palette = self.extended_palette();
@@ -252,6 +313,7 @@ impl checkbox::StyleSheet for Theme {
palette.danger.base,
is_checked,
),
+ Checkbox::Custom(custom) => custom.hovered(self, is_checked),
}
}
}
@@ -276,32 +338,28 @@ fn checkbox_appearance(
}
}
-/*
- * Container
- */
-#[derive(Clone, Copy)]
+/// The style of a container.
+#[derive(Default)]
pub enum Container {
+ /// No style.
+ #[default]
Transparent,
+ /// A simple box.
Box,
- Custom(fn(&Theme) -> container::Appearance),
-}
-
-impl Default for Container {
- fn default() -> Self {
- Self::Transparent
- }
+ /// A custom style.
+ Custom(Box<dyn container::StyleSheet<Style = Theme>>),
}
impl From<fn(&Theme) -> container::Appearance> for Container {
fn from(f: fn(&Theme) -> container::Appearance) -> Self {
- Self::Custom(f)
+ Self::Custom(Box::new(f))
}
}
impl container::StyleSheet for Theme {
type Style = Container;
- fn appearance(&self, style: Self::Style) -> container::Appearance {
+ fn appearance(&self, style: &Self::Style) -> container::Appearance {
match style {
Container::Transparent => Default::default(),
Container::Box => {
@@ -315,257 +373,389 @@ impl container::StyleSheet for Theme {
border_color: Color::TRANSPARENT,
}
}
- Container::Custom(f) => f(self),
+ Container::Custom(custom) => custom.appearance(self),
}
}
}
-/*
- * Slider
- */
-impl slider::StyleSheet for Theme {
- type Style = ();
+impl container::StyleSheet for fn(&Theme) -> container::Appearance {
+ type Style = Theme;
- fn active(&self, _style: Self::Style) -> slider::Appearance {
- let palette = self.extended_palette();
+ fn appearance(&self, style: &Self::Style) -> container::Appearance {
+ (self)(style)
+ }
+}
- let handle = slider::Handle {
- shape: slider::HandleShape::Rectangle {
- width: 8,
- border_radius: 4.0,
- },
- color: Color::WHITE,
- border_color: Color::WHITE,
- border_width: 1.0,
- };
+/// The style of a slider.
+#[derive(Default)]
+pub enum Slider {
+ /// The default style.
+ #[default]
+ Default,
+ /// A custom style.
+ Custom(Box<dyn slider::StyleSheet<Style = Theme>>),
+}
- slider::Appearance {
- rail_colors: (palette.primary.base.color, Color::TRANSPARENT),
- handle: slider::Handle {
- color: palette.background.base.color,
- border_color: palette.primary.base.color,
- ..handle
- },
+impl slider::StyleSheet for Theme {
+ type Style = Slider;
+
+ fn active(&self, style: &Self::Style) -> slider::Appearance {
+ match style {
+ Slider::Default => {
+ let palette = self.extended_palette();
+
+ let handle = slider::Handle {
+ shape: slider::HandleShape::Rectangle {
+ width: 8,
+ border_radius: 4.0,
+ },
+ color: Color::WHITE,
+ border_color: Color::WHITE,
+ border_width: 1.0,
+ };
+
+ slider::Appearance {
+ rail_colors: (
+ palette.primary.base.color,
+ Color::TRANSPARENT,
+ ),
+ handle: slider::Handle {
+ color: palette.background.base.color,
+ border_color: palette.primary.base.color,
+ ..handle
+ },
+ }
+ }
+ Slider::Custom(custom) => custom.active(self),
}
}
- fn hovered(&self, style: Self::Style) -> slider::Appearance {
- let active = self.active(style);
- let palette = self.extended_palette();
+ fn hovered(&self, style: &Self::Style) -> slider::Appearance {
+ match style {
+ Slider::Default => {
+ let active = self.active(style);
+ let palette = self.extended_palette();
- slider::Appearance {
- handle: slider::Handle {
- color: palette.primary.weak.color,
- ..active.handle
- },
- ..active
+ slider::Appearance {
+ handle: slider::Handle {
+ color: palette.primary.weak.color,
+ ..active.handle
+ },
+ ..active
+ }
+ }
+ Slider::Custom(custom) => custom.hovered(self),
}
}
- fn dragging(&self, style: Self::Style) -> slider::Appearance {
- let active = self.active(style);
- let palette = self.extended_palette();
+ fn dragging(&self, style: &Self::Style) -> slider::Appearance {
+ match style {
+ Slider::Default => {
+ let active = self.active(style);
+ let palette = self.extended_palette();
- slider::Appearance {
- handle: slider::Handle {
- color: palette.primary.base.color,
- ..active.handle
- },
- ..active
+ slider::Appearance {
+ handle: slider::Handle {
+ color: palette.primary.base.color,
+ ..active.handle
+ },
+ ..active
+ }
+ }
+ Slider::Custom(custom) => custom.dragging(self),
}
}
}
-/*
- * Menu
- */
+/// The style of a menu.
+#[derive(Clone, Default)]
+pub enum Menu {
+ /// The default style.
+ #[default]
+ Default,
+ /// A custom style.
+ Custom(Rc<dyn menu::StyleSheet<Style = Theme>>),
+}
+
impl menu::StyleSheet for Theme {
- type Style = ();
+ type Style = Menu;
- fn appearance(&self, _style: Self::Style) -> menu::Appearance {
- let palette = self.extended_palette();
+ fn appearance(&self, style: &Self::Style) -> menu::Appearance {
+ match style {
+ Menu::Default => {
+ let palette = self.extended_palette();
- menu::Appearance {
- text_color: palette.background.weak.text,
- background: palette.background.weak.color.into(),
- border_width: 1.0,
- border_radius: 0.0,
- border_color: palette.background.strong.color,
- selected_text_color: palette.primary.strong.text,
- selected_background: palette.primary.strong.color.into(),
+ menu::Appearance {
+ text_color: palette.background.weak.text,
+ background: palette.background.weak.color.into(),
+ border_width: 1.0,
+ border_radius: 0.0,
+ border_color: palette.background.strong.color,
+ selected_text_color: palette.primary.strong.text,
+ selected_background: palette.primary.strong.color.into(),
+ }
+ }
+ Menu::Custom(custom) => custom.appearance(self),
+ }
+ }
+}
+
+impl From<PickList> for Menu {
+ fn from(pick_list: PickList) -> Self {
+ match pick_list {
+ PickList::Default => Self::Default,
+ PickList::Custom(_, menu) => Self::Custom(menu),
}
}
}
-/*
- * Pick List
- */
+/// The style of a pick list.
+#[derive(Clone, Default)]
+pub enum PickList {
+ /// The default style.
+ #[default]
+ Default,
+ /// A custom style.
+ Custom(
+ Rc<dyn pick_list::StyleSheet<Style = Theme>>,
+ Rc<dyn menu::StyleSheet<Style = Theme>>,
+ ),
+}
+
impl pick_list::StyleSheet for Theme {
- type Style = ();
+ type Style = PickList;
- fn active(&self, _style: ()) -> pick_list::Appearance {
- let palette = self.extended_palette();
+ fn active(&self, style: &Self::Style) -> pick_list::Appearance {
+ match style {
+ PickList::Default => {
+ let palette = self.extended_palette();
- pick_list::Appearance {
- text_color: palette.background.weak.text,
- background: palette.background.weak.color.into(),
- placeholder_color: palette.background.strong.color,
- border_radius: 2.0,
- border_width: 1.0,
- border_color: palette.background.strong.color,
- icon_size: 0.7,
+ pick_list::Appearance {
+ text_color: palette.background.weak.text,
+ background: palette.background.weak.color.into(),
+ placeholder_color: palette.background.strong.color,
+ border_radius: 2.0,
+ border_width: 1.0,
+ border_color: palette.background.strong.color,
+ icon_size: 0.7,
+ }
+ }
+ PickList::Custom(custom, _) => custom.active(self),
}
}
- fn hovered(&self, _style: ()) -> pick_list::Appearance {
- let palette = self.extended_palette();
+ fn hovered(&self, style: &Self::Style) -> pick_list::Appearance {
+ match style {
+ PickList::Default => {
+ let palette = self.extended_palette();
- pick_list::Appearance {
- text_color: palette.background.weak.text,
- background: palette.background.weak.color.into(),
- placeholder_color: palette.background.strong.color,
- border_radius: 2.0,
- border_width: 1.0,
- border_color: palette.primary.strong.color,
- icon_size: 0.7,
+ pick_list::Appearance {
+ text_color: palette.background.weak.text,
+ background: palette.background.weak.color.into(),
+ placeholder_color: palette.background.strong.color,
+ border_radius: 2.0,
+ border_width: 1.0,
+ border_color: palette.primary.strong.color,
+ icon_size: 0.7,
+ }
+ }
+ PickList::Custom(custom, _) => custom.active(self),
}
}
}
-/*
- * Radio
- */
+/// The style of a radio button.
+#[derive(Default)]
+pub enum Radio {
+ /// The default style.
+ #[default]
+ Default,
+ /// A custom style.
+ Custom(Box<dyn radio::StyleSheet<Style = Theme>>),
+}
+
impl radio::StyleSheet for Theme {
- type Style = ();
+ type Style = Radio;
fn active(
&self,
- _style: Self::Style,
- _is_selected: bool,
+ style: &Self::Style,
+ is_selected: bool,
) -> radio::Appearance {
- let palette = self.extended_palette();
+ match style {
+ Radio::Default => {
+ let palette = self.extended_palette();
- radio::Appearance {
- background: Color::TRANSPARENT.into(),
- dot_color: palette.primary.strong.color,
- border_width: 1.0,
- border_color: palette.primary.strong.color,
- text_color: None,
+ radio::Appearance {
+ background: Color::TRANSPARENT.into(),
+ dot_color: palette.primary.strong.color,
+ border_width: 1.0,
+ border_color: palette.primary.strong.color,
+ text_color: None,
+ }
+ }
+ Radio::Custom(custom) => custom.active(self, is_selected),
}
}
fn hovered(
&self,
- style: Self::Style,
+ style: &Self::Style,
is_selected: bool,
) -> radio::Appearance {
- let active = self.active(style, is_selected);
- let palette = self.extended_palette();
+ match style {
+ Radio::Default => {
+ let active = self.active(style, is_selected);
+ let palette = self.extended_palette();
- radio::Appearance {
- dot_color: palette.primary.strong.color,
- background: palette.primary.weak.color.into(),
- ..active
+ radio::Appearance {
+ dot_color: palette.primary.strong.color,
+ background: palette.primary.weak.color.into(),
+ ..active
+ }
+ }
+ Radio::Custom(custom) => custom.hovered(self, is_selected),
}
}
}
-/*
- * Toggler
- */
+/// The style of a toggler.
+#[derive(Default)]
+pub enum Toggler {
+ /// The default style.
+ #[default]
+ Default,
+ /// A custom style.
+ Custom(Box<dyn toggler::StyleSheet<Style = Theme>>),
+}
+
impl toggler::StyleSheet for Theme {
- type Style = ();
+ type Style = Toggler;
fn active(
&self,
- _style: Self::Style,
+ style: &Self::Style,
is_active: bool,
) -> toggler::Appearance {
- let palette = self.extended_palette();
+ match style {
+ Toggler::Default => {
+ let palette = self.extended_palette();
- toggler::Appearance {
- background: if is_active {
- palette.primary.strong.color
- } else {
- palette.background.strong.color
- },
- background_border: None,
- foreground: if is_active {
- palette.primary.strong.text
- } else {
- palette.background.base.color
- },
- foreground_border: None,
+ toggler::Appearance {
+ background: if is_active {
+ palette.primary.strong.color
+ } else {
+ palette.background.strong.color
+ },
+ background_border: None,
+ foreground: if is_active {
+ palette.primary.strong.text
+ } else {
+ palette.background.base.color
+ },
+ foreground_border: None,
+ }
+ }
+ Toggler::Custom(custom) => custom.active(self, is_active),
}
}
fn hovered(
&self,
- style: Self::Style,
+ style: &Self::Style,
is_active: bool,
) -> toggler::Appearance {
- let palette = self.extended_palette();
+ match style {
+ Toggler::Default => {
+ let palette = self.extended_palette();
- toggler::Appearance {
- foreground: if is_active {
- Color {
- a: 0.5,
- ..palette.primary.strong.text
+ toggler::Appearance {
+ foreground: if is_active {
+ Color {
+ a: 0.5,
+ ..palette.primary.strong.text
+ }
+ } else {
+ palette.background.weak.color
+ },
+ ..self.active(style, is_active)
}
- } else {
- palette.background.weak.color
- },
- ..self.active(style, is_active)
+ }
+ Toggler::Custom(custom) => custom.hovered(self, is_active),
}
}
}
-/*
- * Pane Grid
- */
+/// The style of a pane grid.
+#[derive(Default)]
+pub enum PaneGrid {
+ /// The default style.
+ #[default]
+ Default,
+ /// A custom style.
+ Custom(Box<dyn pane_grid::StyleSheet<Style = Theme>>),
+}
+
impl pane_grid::StyleSheet for Theme {
- type Style = ();
+ type Style = PaneGrid;
- fn picked_split(&self, _style: Self::Style) -> Option<pane_grid::Line> {
- let palette = self.extended_palette();
+ fn picked_split(&self, style: &Self::Style) -> Option<pane_grid::Line> {
+ match style {
+ PaneGrid::Default => {
+ let palette = self.extended_palette();
- Some(pane_grid::Line {
- color: palette.primary.strong.color,
- width: 2.0,
- })
+ Some(pane_grid::Line {
+ color: palette.primary.strong.color,
+ width: 2.0,
+ })
+ }
+ PaneGrid::Custom(custom) => custom.picked_split(self),
+ }
}
- fn hovered_split(&self, _style: Self::Style) -> Option<pane_grid::Line> {
- let palette = self.extended_palette();
+ fn hovered_split(&self, style: &Self::Style) -> Option<pane_grid::Line> {
+ match style {
+ PaneGrid::Default => {
+ let palette = self.extended_palette();
- Some(pane_grid::Line {
- color: palette.primary.base.color,
- width: 2.0,
- })
+ Some(pane_grid::Line {
+ color: palette.primary.base.color,
+ width: 2.0,
+ })
+ }
+ PaneGrid::Custom(custom) => custom.hovered_split(self),
+ }
}
}
-/*
- * Progress Bar
- */
-#[derive(Clone, Copy)]
+/// The style of a progress bar.
+#[derive(Default)]
pub enum ProgressBar {
+ /// The primary style.
+ #[default]
Primary,
+ /// The success style.
Success,
+ /// The danger style.
Danger,
- Custom(fn(&Theme) -> progress_bar::Appearance),
+ /// A custom style.
+ Custom(Box<dyn progress_bar::StyleSheet<Style = Theme>>),
}
-impl Default for ProgressBar {
- fn default() -> Self {
- Self::Primary
+impl From<fn(&Theme) -> progress_bar::Appearance> for ProgressBar {
+ fn from(f: fn(&Theme) -> progress_bar::Appearance) -> Self {
+ Self::Custom(Box::new(f))
}
}
impl progress_bar::StyleSheet for Theme {
type Style = ProgressBar;
- fn appearance(&self, style: Self::Style) -> progress_bar::Appearance {
+ fn appearance(&self, style: &Self::Style) -> progress_bar::Appearance {
+ if let ProgressBar::Custom(custom) = style {
+ return custom.appearance(self);
+ }
+
let palette = self.extended_palette();
let from_palette = |bar: Color| progress_bar::Appearance {
@@ -578,30 +768,39 @@ impl progress_bar::StyleSheet for Theme {
ProgressBar::Primary => from_palette(palette.primary.base.color),
ProgressBar::Success => from_palette(palette.success.base.color),
ProgressBar::Danger => from_palette(palette.danger.base.color),
- ProgressBar::Custom(f) => f(self),
+ ProgressBar::Custom(custom) => custom.appearance(self),
}
}
}
-/*
- * Rule
- */
-#[derive(Clone, Copy)]
+impl progress_bar::StyleSheet for fn(&Theme) -> progress_bar::Appearance {
+ type Style = Theme;
+
+ fn appearance(&self, style: &Self::Style) -> progress_bar::Appearance {
+ (self)(style)
+ }
+}
+
+/// The style of a rule.
+#[derive(Default)]
pub enum Rule {
+ /// The default style.
+ #[default]
Default,
- Custom(fn(&Theme) -> rule::Appearance),
+ /// A custom style.
+ Custom(Box<dyn rule::StyleSheet<Style = Theme>>),
}
-impl Default for Rule {
- fn default() -> Self {
- Self::Default
+impl From<fn(&Theme) -> rule::Appearance> for Rule {
+ fn from(f: fn(&Theme) -> rule::Appearance) -> Self {
+ Self::Custom(Box::new(f))
}
}
impl rule::StyleSheet for Theme {
type Style = Rule;
- fn style(&self, style: Self::Style) -> rule::Appearance {
+ fn appearance(&self, style: &Self::Style) -> rule::Appearance {
let palette = self.extended_palette();
match style {
@@ -611,66 +810,92 @@ impl rule::StyleSheet for Theme {
radius: 0.0,
fill_mode: rule::FillMode::Full,
},
- Rule::Custom(f) => f(self),
+ Rule::Custom(custom) => custom.appearance(self),
}
}
}
-/*
- * Scrollable
- */
+impl rule::StyleSheet for fn(&Theme) -> rule::Appearance {
+ type Style = Theme;
+
+ fn appearance(&self, style: &Self::Style) -> rule::Appearance {
+ (self)(style)
+ }
+}
+
+/// The style of a scrollable.
+#[derive(Default)]
+pub enum Scrollable {
+ /// The default style.
+ #[default]
+ Default,
+ /// A custom style.
+ Custom(Box<dyn scrollable::StyleSheet<Style = Theme>>),
+}
+
impl scrollable::StyleSheet for Theme {
- type Style = ();
+ type Style = Scrollable;
- fn active(&self, _style: Self::Style) -> scrollable::Scrollbar {
- let palette = self.extended_palette();
+ fn active(&self, style: &Self::Style) -> scrollable::Scrollbar {
+ match style {
+ Scrollable::Default => {
+ 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.background.strong.color,
- border_radius: 2.0,
- border_width: 0.0,
- border_color: Color::TRANSPARENT,
- },
+ 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.background.strong.color,
+ border_radius: 2.0,
+ border_width: 0.0,
+ border_color: Color::TRANSPARENT,
+ },
+ }
+ }
+ Scrollable::Custom(custom) => custom.active(self),
}
}
- fn hovered(&self, _style: Self::Style) -> scrollable::Scrollbar {
- let palette = self.extended_palette();
+ fn hovered(&self, style: &Self::Style) -> scrollable::Scrollbar {
+ match style {
+ Scrollable::Default => {
+ 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,
- border_radius: 2.0,
- border_width: 0.0,
- border_color: Color::TRANSPARENT,
- },
+ 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,
+ },
+ }
+ }
+ Scrollable::Custom(custom) => custom.hovered(self),
+ }
+ }
+
+ fn dragging(&self, style: &Self::Style) -> scrollable::Scrollbar {
+ match style {
+ Scrollable::Default => self.hovered(style),
+ Scrollable::Custom(custom) => custom.dragging(self),
}
}
}
-/*
- * Text
- */
-#[derive(Clone, Copy)]
+/// The style of text.
+#[derive(Clone, Copy, Default)]
pub enum Text {
+ /// The default style.
+ #[default]
Default,
+ /// Colored text.
Color(Color),
- Custom(fn(&Theme) -> text::Appearance),
-}
-
-impl Default for Text {
- fn default() -> Self {
- Self::Default
- }
}
impl From<Color> for Text {
@@ -686,18 +911,28 @@ impl text::StyleSheet for Theme {
match style {
Text::Default => Default::default(),
Text::Color(c) => text::Appearance { color: Some(c) },
- Text::Custom(f) => f(self),
}
}
}
-/*
- * Text Input
- */
+/// The style of a text input.
+#[derive(Default)]
+pub enum TextInput {
+ /// The default style.
+ #[default]
+ Default,
+ /// A custom style.
+ Custom(Box<dyn text_input::StyleSheet<Style = Theme>>),
+}
+
impl text_input::StyleSheet for Theme {
- type Style = ();
+ type Style = TextInput;
+
+ fn active(&self, style: &Self::Style) -> text_input::Appearance {
+ if let TextInput::Custom(custom) = style {
+ return custom.active(self);
+ }
- fn active(&self, _style: Self::Style) -> text_input::Appearance {
let palette = self.extended_palette();
text_input::Appearance {
@@ -708,7 +943,11 @@ impl text_input::StyleSheet for Theme {
}
}
- fn hovered(&self, _style: Self::Style) -> text_input::Appearance {
+ fn hovered(&self, style: &Self::Style) -> text_input::Appearance {
+ if let TextInput::Custom(custom) = style {
+ return custom.hovered(self);
+ }
+
let palette = self.extended_palette();
text_input::Appearance {
@@ -719,7 +958,11 @@ impl text_input::StyleSheet for Theme {
}
}
- fn focused(&self, _style: Self::Style) -> text_input::Appearance {
+ fn focused(&self, style: &Self::Style) -> text_input::Appearance {
+ if let TextInput::Custom(custom) = style {
+ return custom.focused(self);
+ }
+
let palette = self.extended_palette();
text_input::Appearance {
@@ -730,19 +973,31 @@ impl text_input::StyleSheet for Theme {
}
}
- fn placeholder_color(&self, _style: Self::Style) -> Color {
+ fn placeholder_color(&self, style: &Self::Style) -> Color {
+ if let TextInput::Custom(custom) = style {
+ return custom.placeholder_color(self);
+ }
+
let palette = self.extended_palette();
palette.background.strong.color
}
- fn value_color(&self, _style: Self::Style) -> Color {
+ fn value_color(&self, style: &Self::Style) -> Color {
+ if let TextInput::Custom(custom) = style {
+ return custom.value_color(self);
+ }
+
let palette = self.extended_palette();
palette.background.base.text
}
- fn selection_color(&self, _style: Self::Style) -> Color {
+ fn selection_color(&self, style: &Self::Style) -> Color {
+ if let TextInput::Custom(custom) = style {
+ return custom.selection_color(self);
+ }
+
let palette = self.extended_palette();
palette.primary.weak.color
diff --git a/style/src/theme/palette.rs b/style/src/theme/palette.rs
index b3a10d28..0f15494b 100644
--- a/style/src/theme/palette.rs
+++ b/style/src/theme/palette.rs
@@ -1,18 +1,26 @@
+//! Define the colors of a theme.
use iced_core::Color;
use once_cell::sync::Lazy;
use palette::{FromColor, Hsl, Mix, RelativeContrast, Srgb};
+/// A color palette.
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Palette {
+ /// The background [`Color`] of the [`Palette`].
pub background: Color,
+ /// The text [`Color`] of the [`Palette`].
pub text: Color,
+ /// The primary [`Color`] of the [`Palette`].
pub primary: Color,
+ /// The success [`Color`] of the [`Palette`].
pub success: Color,
+ /// The danger [`Color`] of the [`Palette`].
pub danger: Color,
}
impl Palette {
+ /// The built-in light variant of a [`Palette`].
pub const LIGHT: Self = Self {
background: Color::WHITE,
text: Color::BLACK,
@@ -33,6 +41,7 @@ impl Palette {
),
};
+ /// The built-in dark variant of a [`Palette`].
pub const DARK: Self = Self {
background: Color::from_rgb(
0x20 as f32 / 255.0,
@@ -58,21 +67,31 @@ impl Palette {
};
}
+/// An extended set of colors generated from a [`Palette`].
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Extended {
+ /// The set of background colors.
pub background: Background,
+ /// The set of primary colors.
pub primary: Primary,
+ /// The set of secondary colors.
pub secondary: Secondary,
+ /// The set of success colors.
pub success: Success,
+ /// The set of danger colors.
pub danger: Danger,
}
+/// The built-in light variant of an [`Extended`] palette.
pub static EXTENDED_LIGHT: Lazy<Extended> =
Lazy::new(|| Extended::generate(Palette::LIGHT));
+
+/// The built-in dark variant of an [`Extended`] palette.
pub static EXTENDED_DARK: Lazy<Extended> =
Lazy::new(|| Extended::generate(Palette::DARK));
impl Extended {
+ /// Generates an [`Extended`] palette from a simple [`Palette`].
pub fn generate(palette: Palette) -> Self {
Self {
background: Background::new(palette.background, palette.text),
@@ -96,13 +115,22 @@ impl Extended {
}
}
+/// A pair of background and text colors.
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Pair {
+ /// The background color.
pub color: Color,
+
+ /// The text color.
+ ///
+ /// It's guaranteed to be readable on top of the background [`color`].
+ ///
+ /// [`color`]: Self::color
pub text: Color,
}
impl Pair {
+ /// Creates a new [`Pair`] from a background [`Color`] and some text [`Color`].
pub fn new(color: Color, text: Color) -> Self {
Self {
color,
@@ -111,14 +139,19 @@ impl Pair {
}
}
+/// A set of background colors.
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Background {
+ /// The base background color.
pub base: Pair,
+ /// A weaker version of the base background color.
pub weak: Pair,
+ /// A stronger version of the base background color.
pub strong: Pair,
}
impl Background {
+ /// Generates a set of [`Background`] colors from the base and text colors.
pub fn new(base: Color, text: Color) -> Self {
let weak = mix(base, text, 0.15);
let strong = mix(base, text, 0.40);
@@ -131,14 +164,19 @@ impl Background {
}
}
+/// A set of primary colors.
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Primary {
+ /// The base primary color.
pub base: Pair,
+ /// A weaker version of the base primary color.
pub weak: Pair,
+ /// A stronger version of the base primary color.
pub strong: Pair,
}
impl Primary {
+ /// Generates a set of [`Primary`] colors from the base, background, and text colors.
pub fn generate(base: Color, background: Color, text: Color) -> Self {
let weak = mix(base, background, 0.4);
let strong = deviate(base, 0.1);
@@ -151,14 +189,19 @@ impl Primary {
}
}
+/// A set of secondary colors.
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Secondary {
+ /// The base secondary color.
pub base: Pair,
+ /// A weaker version of the base secondary color.
pub weak: Pair,
+ /// A stronger version of the base secondary color.
pub strong: Pair,
}
impl Secondary {
+ /// Generates a set of [`Secondary`] colors from the base and text colors.
pub fn generate(base: Color, text: Color) -> Self {
let base = mix(base, text, 0.2);
let weak = mix(base, text, 0.1);
@@ -172,14 +215,19 @@ impl Secondary {
}
}
+/// A set of success colors.
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Success {
+ /// The base success color.
pub base: Pair,
+ /// A weaker version of the base success color.
pub weak: Pair,
+ /// A stronger version of the base success color.
pub strong: Pair,
}
impl Success {
+ /// Generates a set of [`Success`] colors from the base, background, and text colors.
pub fn generate(base: Color, background: Color, text: Color) -> Self {
let weak = mix(base, background, 0.4);
let strong = deviate(base, 0.1);
@@ -192,14 +240,19 @@ impl Success {
}
}
+/// A set of danger colors.
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Danger {
+ /// The base danger color.
pub base: Pair,
+ /// A weaker version of the base danger color.
pub weak: Pair,
+ /// A stronger version of the base danger color.
pub strong: Pair,
}
impl Danger {
+ /// Generates a set of [`Danger`] colors from the base, background, and text colors.
pub fn generate(base: Color, background: Color, text: Color) -> Self {
let weak = mix(base, background, 0.4);
let strong = deviate(base, 0.1);
diff --git a/style/src/toggler.rs b/style/src/toggler.rs
index 0acf8e97..abc73f2a 100644
--- a/style/src/toggler.rs
+++ b/style/src/toggler.rs
@@ -1,20 +1,31 @@
-//! Show toggle controls using togglers.
+//! Change the appearance of a toggler.
use iced_core::Color;
/// The appearance of a toggler.
#[derive(Debug, Clone, Copy)]
pub struct Appearance {
+ /// The background [`Color`] of the toggler.
pub background: Color,
+ /// The [`Color`] of the background border of the toggler.
pub background_border: Option<Color>,
+ /// The foreground [`Color`] of the toggler.
pub foreground: Color,
+ /// The [`Color`] of the foreground border of the toggler.
pub foreground_border: Option<Color>,
}
/// A set of rules that dictate the style of a toggler.
pub trait StyleSheet {
- type Style: Default + Copy;
+ /// The supported style of the [`StyleSheet`].
+ type Style: Default;
- fn active(&self, style: Self::Style, is_active: bool) -> Appearance;
+ /// Returns the active [`Appearance`] of the toggler for the provided [`Style`].
+ ///
+ /// [`Style`]: Self::Style
+ fn active(&self, style: &Self::Style, is_active: bool) -> Appearance;
- fn hovered(&self, style: Self::Style, is_active: bool) -> Appearance;
+ /// Returns the hovered [`Appearance`] of the toggler for the provided [`Style`].
+ ///
+ /// [`Style`]: Self::Style
+ fn hovered(&self, style: &Self::Style, is_active: bool) -> Appearance;
}
diff --git a/wgpu/Cargo.toml b/wgpu/Cargo.toml
index 9a57e58b..b1fb0dd1 100644
--- a/wgpu/Cargo.toml
+++ b/wgpu/Cargo.toml
@@ -1,6 +1,6 @@
[package]
name = "iced_wgpu"
-version = "0.5.1"
+version = "0.6.1"
authors = ["Héctor Ramón Jiménez <hector0193@gmail.com>"]
edition = "2021"
description = "A wgpu renderer for Iced"
@@ -8,19 +8,19 @@ license = "MIT AND OFL-1.1"
repository = "https://github.com/iced-rs/iced"
[features]
-svg = ["resvg", "usvg", "tiny-skia"]
-image = ["png", "jpeg", "jpeg_rayon", "gif", "webp", "bmp"]
-png = ["image_rs/png"]
-jpeg = ["image_rs/jpeg"]
-jpeg_rayon = ["image_rs/jpeg_rayon"]
-gif = ["image_rs/gif"]
-webp = ["image_rs/webp"]
-pnm = ["image_rs/pnm"]
-ico = ["image_rs/ico"]
-bmp = ["image_rs/bmp"]
-hdr = ["image_rs/hdr"]
-dds = ["image_rs/dds"]
-farbfeld = ["image_rs/farbfeld"]
+svg = ["iced_graphics/svg"]
+image = ["iced_graphics/image"]
+png = ["iced_graphics/png"]
+jpeg = ["iced_graphics/jpeg"]
+jpeg_rayon = ["iced_graphics/jpeg_rayon"]
+gif = ["iced_graphics/gif"]
+webp = ["iced_graphics/webp"]
+pnm = ["iced_graphics/pnm"]
+ico = ["iced_graphics/ico"]
+bmp = ["iced_graphics/bmp"]
+hdr = ["iced_graphics/hdr"]
+dds = ["iced_graphics/dds"]
+farbfeld = ["iced_graphics/farbfeld"]
canvas = ["iced_graphics/canvas"]
qr_code = ["iced_graphics/qr_code"]
default_system_font = ["iced_graphics/font-source"]
@@ -35,7 +35,6 @@ raw-window-handle = "0.5"
log = "0.4"
guillotiere = "0.6"
futures = "0.3"
-kamadak-exif = "0.5"
bitflags = "1.2"
[dependencies.bytemuck]
@@ -43,32 +42,14 @@ version = "1.9"
features = ["derive"]
[dependencies.iced_native]
-version = "0.5"
+version = "0.6"
path = "../native"
[dependencies.iced_graphics]
-version = "0.3"
+version = "0.4"
path = "../graphics"
features = ["font-fallback", "font-icons"]
-[dependencies.image_rs]
-version = "0.23"
-package = "image"
-default-features = false
-optional = true
-
-[dependencies.resvg]
-version = "0.18"
-optional = true
-
-[dependencies.usvg]
-version = "0.18"
-optional = true
-
-[dependencies.tiny-skia]
-version = "0.6"
-optional = true
-
[dependencies.encase]
version = "0.3.0"
features = ["glam"]
diff --git a/wgpu/src/backend.rs b/wgpu/src/backend.rs
index 80026673..946eb712 100644
--- a/wgpu/src/backend.rs
+++ b/wgpu/src/backend.rs
@@ -10,7 +10,7 @@ use iced_graphics::{Primitive, Viewport};
use iced_native::alignment;
use iced_native::{Font, Size};
-#[cfg(any(feature = "image_rs", feature = "svg"))]
+#[cfg(any(feature = "image", feature = "svg"))]
use crate::image;
/// A [`wgpu`] graphics backend for [`iced`].
@@ -23,7 +23,7 @@ pub struct Backend {
text_pipeline: text::Pipeline,
triangle_pipeline: triangle::Pipeline,
- #[cfg(any(feature = "image_rs", feature = "svg"))]
+ #[cfg(any(feature = "image", feature = "svg"))]
image_pipeline: image::Pipeline,
default_text_size: u16,
@@ -47,7 +47,7 @@ impl Backend {
let triangle_pipeline =
triangle::Pipeline::new(device, format, settings.antialiasing);
- #[cfg(any(feature = "image_rs", feature = "svg"))]
+ #[cfg(any(feature = "image", feature = "svg"))]
let image_pipeline = image::Pipeline::new(device, format);
Self {
@@ -55,7 +55,7 @@ impl Backend {
text_pipeline,
triangle_pipeline,
- #[cfg(any(feature = "image_rs", feature = "svg"))]
+ #[cfg(any(feature = "image", feature = "svg"))]
image_pipeline,
default_text_size: settings.default_text_size,
@@ -98,8 +98,8 @@ impl Backend {
);
}
- #[cfg(any(feature = "image_rs", feature = "svg"))]
- self.image_pipeline.trim_cache();
+ #[cfg(any(feature = "image", feature = "svg"))]
+ self.image_pipeline.trim_cache(device, encoder);
}
fn flush(
@@ -148,7 +148,7 @@ impl Backend {
);
}
- #[cfg(any(feature = "image_rs", feature = "svg"))]
+ #[cfg(any(feature = "image", feature = "svg"))]
{
if !layer.images.is_empty() {
let scaled = transformation
@@ -294,9 +294,9 @@ impl backend::Text for Backend {
}
}
-#[cfg(feature = "image_rs")]
+#[cfg(feature = "image")]
impl backend::Image for Backend {
- fn dimensions(&self, handle: &iced_native::image::Handle) -> (u32, u32) {
+ fn dimensions(&self, handle: &iced_native::image::Handle) -> Size<u32> {
self.image_pipeline.dimensions(handle)
}
}
@@ -306,7 +306,7 @@ impl backend::Svg for Backend {
fn viewport_dimensions(
&self,
handle: &iced_native::svg::Handle,
- ) -> (u32, u32) {
+ ) -> Size<u32> {
self.image_pipeline.viewport_dimensions(handle)
}
}
diff --git a/wgpu/src/buffer/dynamic.rs b/wgpu/src/buffer/dynamic.rs
index c0c48c74..18be03dd 100644
--- a/wgpu/src/buffer/dynamic.rs
+++ b/wgpu/src/buffer/dynamic.rs
@@ -1,10 +1,13 @@
//! Utilities for uniform buffer operations.
use encase::private::WriteInto;
use encase::ShaderType;
+
+use std::fmt;
use std::marker::PhantomData;
/// A dynamic buffer is any type of buffer which does not have a static offset.
-pub(crate) struct Buffer<T: ShaderType> {
+#[derive(Debug)]
+pub struct Buffer<T: ShaderType> {
offsets: Vec<wgpu::DynamicOffset>,
cpu: Internal,
gpu: wgpu::Buffer,
@@ -24,6 +27,7 @@ impl<T: ShaderType + WriteInto> Buffer<T> {
)
}
+ #[cfg(not(target_arch = "wasm32"))]
/// Creates a new dynamic storage buffer.
pub fn storage(device: &wgpu::Device, label: &'static str) -> Self {
Buffer::new(
@@ -91,6 +95,7 @@ impl<T: ShaderType + WriteInto> Buffer<T> {
Internal::Uniform(_) => {
wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST
}
+ #[cfg(not(target_arch = "wasm32"))]
Internal::Storage(_) => {
wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST
}
@@ -154,6 +159,8 @@ impl<T: ShaderType + WriteInto> Buffer<T> {
// Currently supported dynamic buffers.
enum Internal {
Uniform(encase::DynamicUniformBuffer<Vec<u8>>),
+ #[cfg(not(target_arch = "wasm32"))]
+ //storage buffers are not supported on wgpu wasm target (yet)
Storage(encase::DynamicStorageBuffer<Vec<u8>>),
}
@@ -168,6 +175,7 @@ impl Internal {
.write(value)
.expect("Error when writing to dynamic uniform buffer.")
as u32,
+ #[cfg(not(target_arch = "wasm32"))]
Internal::Storage(buf) => buf
.write(value)
.expect("Error when writing to dynamic storage buffer.")
@@ -179,6 +187,7 @@ impl Internal {
pub(super) fn get_ref(&self) -> &Vec<u8> {
match self {
Internal::Uniform(buf) => buf.as_ref(),
+ #[cfg(not(target_arch = "wasm32"))]
Internal::Storage(buf) => buf.as_ref(),
}
}
@@ -190,6 +199,7 @@ impl Internal {
buf.as_mut().clear();
buf.set_offset(0);
}
+ #[cfg(not(target_arch = "wasm32"))]
Internal::Storage(buf) => {
buf.as_mut().clear();
buf.set_offset(0);
@@ -197,3 +207,13 @@ impl Internal {
}
}
}
+
+impl fmt::Debug for Internal {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ match self {
+ Self::Uniform(_) => write!(f, "Internal::Uniform(_)"),
+ #[cfg(not(target_arch = "wasm32"))]
+ Self::Storage(_) => write!(f, "Internal::Storage(_)"),
+ }
+ }
+}
diff --git a/wgpu/src/buffer/static.rs b/wgpu/src/buffer/static.rs
index cf06790c..ef87422f 100644
--- a/wgpu/src/buffer/static.rs
+++ b/wgpu/src/buffer/static.rs
@@ -8,7 +8,7 @@ const DEFAULT_STATIC_BUFFER_COUNT: wgpu::BufferAddress = 128;
/// A generic buffer struct useful for items which have no alignment requirements
/// (e.g. Vertex, Index buffers) & no dynamic offsets.
#[derive(Debug)]
-pub(crate) struct Buffer<T> {
+pub struct Buffer<T> {
//stored sequentially per mesh iteration; refers to the offset index in the GPU buffer
offsets: Vec<wgpu::BufferAddress>,
label: &'static str,
diff --git a/wgpu/src/image.rs b/wgpu/src/image.rs
index d964aed7..d06815bb 100644
--- a/wgpu/src/image.rs
+++ b/wgpu/src/image.rs
@@ -1,22 +1,23 @@
mod atlas;
-#[cfg(feature = "image_rs")]
-mod raster;
+#[cfg(feature = "image")]
+use iced_graphics::image::raster;
#[cfg(feature = "svg")]
-mod vector;
+use iced_graphics::image::vector;
use crate::Transformation;
use atlas::Atlas;
use iced_graphics::layer;
-use iced_native::Rectangle;
+use iced_native::{Rectangle, Size};
+
use std::cell::RefCell;
use std::mem;
use bytemuck::{Pod, Zeroable};
-#[cfg(feature = "image_rs")]
+#[cfg(feature = "image")]
use iced_native::image;
#[cfg(feature = "svg")]
@@ -24,10 +25,10 @@ use iced_native::svg;
#[derive(Debug)]
pub struct Pipeline {
- #[cfg(feature = "image_rs")]
- raster_cache: RefCell<raster::Cache>,
+ #[cfg(feature = "image")]
+ raster_cache: RefCell<raster::Cache<Atlas>>,
#[cfg(feature = "svg")]
- vector_cache: RefCell<vector::Cache>,
+ vector_cache: RefCell<vector::Cache<Atlas>>,
pipeline: wgpu::RenderPipeline,
uniforms: wgpu::Buffer,
@@ -242,11 +243,11 @@ impl Pipeline {
});
Pipeline {
- #[cfg(feature = "image_rs")]
- raster_cache: RefCell::new(raster::Cache::new()),
+ #[cfg(feature = "image")]
+ raster_cache: RefCell::new(raster::Cache::default()),
#[cfg(feature = "svg")]
- vector_cache: RefCell::new(vector::Cache::new()),
+ vector_cache: RefCell::new(vector::Cache::default()),
pipeline,
uniforms: uniforms_buffer,
@@ -261,8 +262,8 @@ impl Pipeline {
}
}
- #[cfg(feature = "image_rs")]
- pub fn dimensions(&self, handle: &image::Handle) -> (u32, u32) {
+ #[cfg(feature = "image")]
+ pub fn dimensions(&self, handle: &image::Handle) -> Size<u32> {
let mut cache = self.raster_cache.borrow_mut();
let memory = cache.load(handle);
@@ -270,7 +271,7 @@ impl Pipeline {
}
#[cfg(feature = "svg")]
- pub fn viewport_dimensions(&self, handle: &svg::Handle) -> (u32, u32) {
+ pub fn viewport_dimensions(&self, handle: &svg::Handle) -> Size<u32> {
let mut cache = self.vector_cache.borrow_mut();
let svg = cache.load(handle);
@@ -290,7 +291,7 @@ impl Pipeline {
) {
let instances: &mut Vec<Instance> = &mut Vec::new();
- #[cfg(feature = "image_rs")]
+ #[cfg(feature = "image")]
let mut raster_cache = self.raster_cache.borrow_mut();
#[cfg(feature = "svg")]
@@ -298,12 +299,11 @@ impl Pipeline {
for image in images {
match &image {
- #[cfg(feature = "image_rs")]
+ #[cfg(feature = "image")]
layer::Image::Raster { handle, bounds } => {
if let Some(atlas_entry) = raster_cache.upload(
handle,
- device,
- encoder,
+ &mut (device, encoder),
&mut self.texture_atlas,
) {
add_instances(
@@ -314,7 +314,7 @@ impl Pipeline {
);
}
}
- #[cfg(not(feature = "image_rs"))]
+ #[cfg(not(feature = "image"))]
layer::Image::Raster { .. } => {}
#[cfg(feature = "svg")]
@@ -325,8 +325,7 @@ impl Pipeline {
handle,
size,
_scale,
- device,
- encoder,
+ &mut (device, encoder),
&mut self.texture_atlas,
) {
add_instances(
@@ -446,12 +445,20 @@ impl Pipeline {
}
}
- pub fn trim_cache(&mut self) {
- #[cfg(feature = "image_rs")]
- self.raster_cache.borrow_mut().trim(&mut self.texture_atlas);
+ pub fn trim_cache(
+ &mut self,
+ device: &wgpu::Device,
+ encoder: &mut wgpu::CommandEncoder,
+ ) {
+ #[cfg(feature = "image")]
+ self.raster_cache
+ .borrow_mut()
+ .trim(&mut self.texture_atlas, &mut (device, encoder));
#[cfg(feature = "svg")]
- self.vector_cache.borrow_mut().trim(&mut self.texture_atlas);
+ self.vector_cache
+ .borrow_mut()
+ .trim(&mut self.texture_atlas, &mut (device, encoder));
}
}
@@ -509,15 +516,18 @@ fn add_instances(
add_instance(image_position, image_size, allocation, instances);
}
atlas::Entry::Fragmented { fragments, size } => {
- let scaling_x = image_size[0] / size.0 as f32;
- let scaling_y = image_size[1] / size.1 as f32;
+ let scaling_x = image_size[0] / size.width as f32;
+ let scaling_y = image_size[1] / size.height as f32;
for fragment in fragments {
let allocation = &fragment.allocation;
let [x, y] = image_position;
let (fragment_x, fragment_y) = fragment.position;
- let (fragment_width, fragment_height) = allocation.size();
+ let Size {
+ width: fragment_width,
+ height: fragment_height,
+ } = allocation.size();
let position = [
x + fragment_x as f32 * scaling_x,
@@ -543,7 +553,7 @@ fn add_instance(
instances: &mut Vec<Instance>,
) {
let (x, y) = allocation.position();
- let (width, height) = allocation.size();
+ let Size { width, height } = allocation.size();
let layer = allocation.layer();
let instance = Instance {
diff --git a/wgpu/src/image/atlas.rs b/wgpu/src/image/atlas.rs
index 953dd4e2..eafe2f96 100644
--- a/wgpu/src/image/atlas.rs
+++ b/wgpu/src/image/atlas.rs
@@ -4,8 +4,6 @@ mod allocation;
mod allocator;
mod layer;
-use std::num::NonZeroU32;
-
pub use allocation::Allocation;
pub use entry::Entry;
pub use layer::Layer;
@@ -14,6 +12,11 @@ use allocator::Allocator;
pub const SIZE: u32 = 2048;
+use iced_graphics::image;
+use iced_graphics::Size;
+
+use std::num::NonZeroU32;
+
#[derive(Debug)]
pub struct Atlas {
texture: wgpu::Texture,
@@ -35,7 +38,7 @@ impl Atlas {
mip_level_count: 1,
sample_count: 1,
dimension: wgpu::TextureDimension::D2,
- format: wgpu::TextureFormat::Bgra8UnormSrgb,
+ format: wgpu::TextureFormat::Rgba8UnormSrgb,
usage: wgpu::TextureUsages::COPY_DST
| wgpu::TextureUsages::COPY_SRC
| wgpu::TextureUsages::TEXTURE_BINDING,
@@ -61,99 +64,6 @@ impl Atlas {
self.layers.len()
}
- pub fn upload(
- &mut self,
- width: u32,
- height: u32,
- data: &[u8],
- device: &wgpu::Device,
- encoder: &mut wgpu::CommandEncoder,
- ) -> Option<Entry> {
- use wgpu::util::DeviceExt;
-
- let entry = {
- let current_size = self.layers.len();
- let entry = self.allocate(width, height)?;
-
- // We grow the internal texture after allocating if necessary
- let new_layers = self.layers.len() - current_size;
- self.grow(new_layers, device, encoder);
-
- entry
- };
-
- log::info!("Allocated atlas entry: {:?}", entry);
-
- // It is a webgpu requirement that:
- // BufferCopyView.layout.bytes_per_row % wgpu::COPY_BYTES_PER_ROW_ALIGNMENT == 0
- // So we calculate padded_width by rounding width up to the next
- // multiple of wgpu::COPY_BYTES_PER_ROW_ALIGNMENT.
- let align = wgpu::COPY_BYTES_PER_ROW_ALIGNMENT;
- let padding = (align - (4 * width) % align) % align;
- let padded_width = (4 * width + padding) as usize;
- let padded_data_size = padded_width * height as usize;
-
- let mut padded_data = vec![0; padded_data_size];
-
- for row in 0..height as usize {
- let offset = row * padded_width;
-
- padded_data[offset..offset + 4 * width as usize].copy_from_slice(
- &data[row * 4 * width as usize..(row + 1) * 4 * width as usize],
- )
- }
-
- let buffer =
- device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
- label: Some("iced_wgpu::image staging buffer"),
- contents: &padded_data,
- usage: wgpu::BufferUsages::COPY_SRC,
- });
-
- match &entry {
- Entry::Contiguous(allocation) => {
- self.upload_allocation(
- &buffer, width, height, padding, 0, allocation, encoder,
- );
- }
- Entry::Fragmented { fragments, .. } => {
- for fragment in fragments {
- let (x, y) = fragment.position;
- let offset = (y * padded_width as u32 + 4 * x) as usize;
-
- self.upload_allocation(
- &buffer,
- width,
- height,
- padding,
- offset,
- &fragment.allocation,
- encoder,
- );
- }
- }
- }
-
- log::info!("Current atlas: {:?}", self);
-
- Some(entry)
- }
-
- pub fn remove(&mut self, entry: &Entry) {
- log::info!("Removing atlas entry: {:?}", entry);
-
- match entry {
- Entry::Contiguous(allocation) => {
- self.deallocate(allocation);
- }
- Entry::Fragmented { fragments, .. } => {
- for fragment in fragments {
- self.deallocate(&fragment.allocation);
- }
- }
- }
- }
-
fn allocate(&mut self, width: u32, height: u32) -> Option<Entry> {
// Allocate one layer if texture fits perfectly
if width == SIZE && height == SIZE {
@@ -204,7 +114,7 @@ impl Atlas {
}
return Some(Entry::Fragmented {
- size: (width, height),
+ size: Size::new(width, height),
fragments,
});
}
@@ -284,7 +194,7 @@ impl Atlas {
encoder: &mut wgpu::CommandEncoder,
) {
let (x, y) = allocation.position();
- let (width, height) = allocation.size();
+ let Size { width, height } = allocation.size();
let layer = allocation.layer();
let extent = wgpu::Extent3d {
@@ -336,7 +246,7 @@ impl Atlas {
mip_level_count: 1,
sample_count: 1,
dimension: wgpu::TextureDimension::D2,
- format: wgpu::TextureFormat::Bgra8UnormSrgb,
+ format: wgpu::TextureFormat::Rgba8UnormSrgb,
usage: wgpu::TextureUsages::COPY_DST
| wgpu::TextureUsages::COPY_SRC
| wgpu::TextureUsages::TEXTURE_BINDING,
@@ -388,3 +298,100 @@ impl Atlas {
});
}
}
+
+impl image::Storage for Atlas {
+ type Entry = Entry;
+ type State<'a> = (&'a wgpu::Device, &'a mut wgpu::CommandEncoder);
+
+ fn upload(
+ &mut self,
+ width: u32,
+ height: u32,
+ data: &[u8],
+ (device, encoder): &mut Self::State<'_>,
+ ) -> Option<Self::Entry> {
+ use wgpu::util::DeviceExt;
+
+ let entry = {
+ let current_size = self.layers.len();
+ let entry = self.allocate(width, height)?;
+
+ // We grow the internal texture after allocating if necessary
+ let new_layers = self.layers.len() - current_size;
+ self.grow(new_layers, device, encoder);
+
+ entry
+ };
+
+ log::info!("Allocated atlas entry: {:?}", entry);
+
+ // It is a webgpu requirement that:
+ // BufferCopyView.layout.bytes_per_row % wgpu::COPY_BYTES_PER_ROW_ALIGNMENT == 0
+ // So we calculate padded_width by rounding width up to the next
+ // multiple of wgpu::COPY_BYTES_PER_ROW_ALIGNMENT.
+ let align = wgpu::COPY_BYTES_PER_ROW_ALIGNMENT;
+ let padding = (align - (4 * width) % align) % align;
+ let padded_width = (4 * width + padding) as usize;
+ let padded_data_size = padded_width * height as usize;
+
+ let mut padded_data = vec![0; padded_data_size];
+
+ for row in 0..height as usize {
+ let offset = row * padded_width;
+
+ padded_data[offset..offset + 4 * width as usize].copy_from_slice(
+ &data[row * 4 * width as usize..(row + 1) * 4 * width as usize],
+ )
+ }
+
+ let buffer =
+ device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
+ label: Some("iced_wgpu::image staging buffer"),
+ contents: &padded_data,
+ usage: wgpu::BufferUsages::COPY_SRC,
+ });
+
+ match &entry {
+ Entry::Contiguous(allocation) => {
+ self.upload_allocation(
+ &buffer, width, height, padding, 0, allocation, encoder,
+ );
+ }
+ Entry::Fragmented { fragments, .. } => {
+ for fragment in fragments {
+ let (x, y) = fragment.position;
+ let offset = (y * padded_width as u32 + 4 * x) as usize;
+
+ self.upload_allocation(
+ &buffer,
+ width,
+ height,
+ padding,
+ offset,
+ &fragment.allocation,
+ encoder,
+ );
+ }
+ }
+ }
+
+ log::info!("Current atlas: {:?}", self);
+
+ Some(entry)
+ }
+
+ fn remove(&mut self, entry: &Entry, _: &mut Self::State<'_>) {
+ log::info!("Removing atlas entry: {:?}", entry);
+
+ match entry {
+ Entry::Contiguous(allocation) => {
+ self.deallocate(allocation);
+ }
+ Entry::Fragmented { fragments, .. } => {
+ for fragment in fragments {
+ self.deallocate(&fragment.allocation);
+ }
+ }
+ }
+ }
+}
diff --git a/wgpu/src/image/atlas/allocation.rs b/wgpu/src/image/atlas/allocation.rs
index 59b7239f..43aba875 100644
--- a/wgpu/src/image/atlas/allocation.rs
+++ b/wgpu/src/image/atlas/allocation.rs
@@ -1,5 +1,7 @@
use crate::image::atlas::{self, allocator};
+use iced_graphics::Size;
+
#[derive(Debug)]
pub enum Allocation {
Partial {
@@ -19,10 +21,10 @@ impl Allocation {
}
}
- pub fn size(&self) -> (u32, u32) {
+ pub fn size(&self) -> Size<u32> {
match self {
Allocation::Partial { region, .. } => region.size(),
- Allocation::Full { .. } => (atlas::SIZE, atlas::SIZE),
+ Allocation::Full { .. } => Size::new(atlas::SIZE, atlas::SIZE),
}
}
diff --git a/wgpu/src/image/atlas/allocator.rs b/wgpu/src/image/atlas/allocator.rs
index 7a4ff5b1..03effdcb 100644
--- a/wgpu/src/image/atlas/allocator.rs
+++ b/wgpu/src/image/atlas/allocator.rs
@@ -46,10 +46,10 @@ impl Region {
(rectangle.min.x as u32, rectangle.min.y as u32)
}
- pub fn size(&self) -> (u32, u32) {
+ pub fn size(&self) -> iced_graphics::Size<u32> {
let size = self.allocation.rectangle.size();
- (size.width as u32, size.height as u32)
+ iced_graphics::Size::new(size.width as u32, size.height as u32)
}
}
diff --git a/wgpu/src/image/atlas/entry.rs b/wgpu/src/image/atlas/entry.rs
index 9b3f16df..69c05a50 100644
--- a/wgpu/src/image/atlas/entry.rs
+++ b/wgpu/src/image/atlas/entry.rs
@@ -1,17 +1,19 @@
use crate::image::atlas;
+use iced_graphics::image;
+use iced_graphics::Size;
+
#[derive(Debug)]
pub enum Entry {
Contiguous(atlas::Allocation),
Fragmented {
- size: (u32, u32),
+ size: Size<u32>,
fragments: Vec<Fragment>,
},
}
-impl Entry {
- #[cfg(feature = "image_rs")]
- pub fn size(&self) -> (u32, u32) {
+impl image::storage::Entry for Entry {
+ fn size(&self) -> Size<u32> {
match self {
Entry::Contiguous(allocation) => allocation.size(),
Entry::Fragmented { size, .. } => *size,
diff --git a/wgpu/src/lib.rs b/wgpu/src/lib.rs
index 1295516b..74152945 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.4/native
+//! [`iced_native`]: https://github.com/iced-rs/iced/tree/0.5/native
//! [`wgpu`]: https://github.com/gfx-rs/wgpu-rs
//! [WebGPU API]: https://gpuweb.github.io/gpuweb/
//! [`wgpu_glyph`]: https://github.com/hecrj/wgpu_glyph
@@ -39,13 +39,13 @@
#![cfg_attr(docsrs, feature(doc_cfg))]
pub mod settings;
-pub mod triangle;
pub mod window;
mod backend;
mod buffer;
mod quad;
mod text;
+mod triangle;
pub use iced_graphics::{Antialiasing, Color, Error, Primitive, Viewport};
pub use iced_native::Theme;
@@ -56,7 +56,7 @@ pub use settings::Settings;
pub(crate) use iced_graphics::Transformation;
-#[cfg(any(feature = "image_rs", feature = "svg"))]
+#[cfg(any(feature = "image", feature = "svg"))]
mod image;
/// A [`wgpu`] graphics renderer for [`iced`].
diff --git a/wgpu/src/shader/solid.wgsl b/wgpu/src/shader/solid.wgsl
index 68a8fea3..b24402f8 100644
--- a/wgpu/src/shader/solid.wgsl
+++ b/wgpu/src/shader/solid.wgsl
@@ -1,17 +1,30 @@
-struct Uniforms {
+struct Globals {
transform: mat4x4<f32>,
- color: vec4<f32>
}
-@group(0) @binding(0)
-var<uniform> uniforms: Uniforms;
+@group(0) @binding(0) var<uniform> globals: Globals;
+
+struct VertexInput {
+ @location(0) position: vec2<f32>,
+ @location(1) color: vec4<f32>,
+}
+
+struct VertexOutput {
+ @builtin(position) position: vec4<f32>,
+ @location(0) color: vec4<f32>,
+}
@vertex
-fn vs_main(@location(0) input: vec2<f32>) -> @builtin(position) vec4<f32> {
- return uniforms.transform * vec4<f32>(input.xy, 0.0, 1.0);
+fn vs_main(input: VertexInput) -> VertexOutput {
+ var out: VertexOutput;
+
+ out.color = input.color;
+ out.position = globals.transform * vec4<f32>(input.position, 0.0, 1.0);
+
+ return out;
}
@fragment
-fn fs_main() -> @location(0) vec4<f32> {
- return uniforms.color;
+fn fs_main(input: VertexOutput) -> @location(0) vec4<f32> {
+ return input.color;
}
diff --git a/wgpu/src/shader/triangle.wgsl b/wgpu/src/shader/triangle.wgsl
deleted file mode 100644
index b24402f8..00000000
--- a/wgpu/src/shader/triangle.wgsl
+++ /dev/null
@@ -1,30 +0,0 @@
-struct Globals {
- transform: mat4x4<f32>,
-}
-
-@group(0) @binding(0) var<uniform> globals: Globals;
-
-struct VertexInput {
- @location(0) position: vec2<f32>,
- @location(1) color: vec4<f32>,
-}
-
-struct VertexOutput {
- @builtin(position) position: vec4<f32>,
- @location(0) color: vec4<f32>,
-}
-
-@vertex
-fn vs_main(input: VertexInput) -> VertexOutput {
- var out: VertexOutput;
-
- out.color = input.color;
- out.position = globals.transform * vec4<f32>(input.position, 0.0, 1.0);
-
- return out;
-}
-
-@fragment
-fn fs_main(input: VertexOutput) -> @location(0) vec4<f32> {
- return input.color;
-}
diff --git a/wgpu/src/triangle.rs b/wgpu/src/triangle.rs
index f9abf2b5..b33b488a 100644
--- a/wgpu/src/triangle.rs
+++ b/wgpu/src/triangle.rs
@@ -1,64 +1,27 @@
//! Draw meshes of triangles.
-mod gradient;
mod msaa;
-mod solid;
use crate::buffer::r#static::Buffer;
use crate::settings;
use crate::Transformation;
use iced_graphics::layer::mesh::{self, Mesh};
-use iced_graphics::triangle::{self, Vertex2D};
+use iced_graphics::triangle::ColoredVertex2D;
use iced_graphics::Size;
-use core::fmt;
-use std::fmt::Formatter;
-
-/// Triangle pipeline for all mesh layers in a [`iced_graphics::Canvas`] widget.
#[derive(Debug)]
-pub(crate) struct Pipeline {
+pub struct Pipeline {
blit: Option<msaa::Blit>,
- vertex_buffer: Buffer<Vertex2D>,
index_buffer: Buffer<u32>,
index_strides: Vec<u32>,
- pipelines: PipelineList,
-}
-
-/// Supported triangle pipelines for different fills.
-pub(crate) struct PipelineList {
solid: solid::Pipeline,
- gradient: gradient::Pipeline,
-}
-impl fmt::Debug for PipelineList {
- fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
- f.debug_struct("TrianglePipelines").finish()
- }
-}
-
-impl PipelineList {
- /// Resets each pipeline's buffers.
- fn clear(&mut self) {
- self.solid.buffer.clear();
- self.gradient.uniform_buffer.clear();
- self.gradient.storage_buffer.clear();
- }
-
- /// Writes the contents of each pipeline's CPU buffer to the GPU, resizing the GPU buffer
- /// beforehand if necessary.
- fn write(
- &mut self,
- device: &wgpu::Device,
- staging_belt: &mut wgpu::util::StagingBelt,
- encoder: &mut wgpu::CommandEncoder,
- ) {
- self.solid.write(device, staging_belt, encoder);
- self.gradient.write(device, staging_belt, encoder);
- }
+ /// Gradients are currently not supported on WASM targets due to their need of storage buffers.
+ #[cfg(not(target_arch = "wasm32"))]
+ gradient: gradient::Pipeline,
}
impl Pipeline {
- /// Creates supported pipelines, listed in [TrianglePipelines].
pub fn new(
device: &wgpu::Device,
format: wgpu::TextureFormat,
@@ -66,25 +29,19 @@ impl Pipeline {
) -> Pipeline {
Pipeline {
blit: antialiasing.map(|a| msaa::Blit::new(device, format, a)),
- vertex_buffer: Buffer::new(
- device,
- "iced_wgpu::triangle vertex buffer",
- wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
- ),
index_buffer: Buffer::new(
device,
"iced_wgpu::triangle vertex buffer",
wgpu::BufferUsages::INDEX | wgpu::BufferUsages::COPY_DST,
),
index_strides: Vec::new(),
- pipelines: PipelineList {
- solid: solid::Pipeline::new(device, format, antialiasing),
- gradient: gradient::Pipeline::new(device, format, antialiasing),
- },
+ solid: solid::Pipeline::new(device, format, antialiasing),
+
+ #[cfg(not(target_arch = "wasm32"))]
+ gradient: gradient::Pipeline::new(device, format, antialiasing),
}
}
- /// Draws the contents of the current layer's meshes to the [target].
pub fn draw(
&mut self,
device: &wgpu::Device,
@@ -96,67 +53,185 @@ impl Pipeline {
scale_factor: f32,
meshes: &[Mesh<'_>],
) {
- //count the total amount of vertices & indices we need to handle
- let (total_vertices, total_indices) = mesh::attribute_count_of(meshes);
+ // Count the total amount of vertices & indices we need to handle
+ let count = mesh::attribute_count_of(meshes);
// Then we ensure the current attribute buffers are big enough, resizing if necessary.
-
- //We are not currently using the return value of these functions as we have no system in
- //place to calculate mesh diff, or to know whether or not that would be more performant for
- //the majority of use cases. Therefore we will write GPU data every frame (for now).
- let _ = self.vertex_buffer.resize(device, total_vertices);
- let _ = self.index_buffer.resize(device, total_indices);
-
- //prepare dynamic buffers & data store for writing
+ // We are not currently using the return value of these functions as we have no system in
+ // place to calculate mesh diff, or to know whether or not that would be more performant for
+ // the majority of use cases. Therefore we will write GPU data every frame (for now).
+ let _ = self.index_buffer.resize(device, count.indices);
+ let _ = self.solid.vertices.resize(device, count.solid_vertices);
+
+ #[cfg(not(target_arch = "wasm32"))]
+ let _ = self
+ .gradient
+ .vertices
+ .resize(device, count.gradient_vertices);
+
+ // Prepare dynamic buffers & data store for writing
self.index_strides.clear();
- self.pipelines.clear();
+ self.solid.vertices.clear();
+ self.solid.uniforms.clear();
+
+ #[cfg(not(target_arch = "wasm32"))]
+ {
+ self.gradient.uniforms.clear();
+ self.gradient.vertices.clear();
+ self.gradient.storage.clear();
+ }
- let mut vertex_offset = 0;
+ let mut solid_vertex_offset = 0;
let mut index_offset = 0;
+ #[cfg(not(target_arch = "wasm32"))]
+ let mut gradient_vertex_offset = 0;
+
for mesh in meshes {
- let transform = transformation
- * Transformation::translate(mesh.origin.x, mesh.origin.y);
+ let origin = mesh.origin();
+ let indices = mesh.indices();
- //write to both buffers
- let new_vertex_offset = self.vertex_buffer.write(
- device,
- staging_belt,
- encoder,
- vertex_offset,
- &mesh.buffers.vertices,
- );
+ let transform =
+ transformation * Transformation::translate(origin.x, origin.y);
let new_index_offset = self.index_buffer.write(
device,
staging_belt,
encoder,
index_offset,
- &mesh.buffers.indices,
+ indices,
);
- vertex_offset += new_vertex_offset;
index_offset += new_index_offset;
-
- self.index_strides.push(mesh.buffers.indices.len() as u32);
+ self.index_strides.push(indices.len() as u32);
//push uniform data to CPU buffers
- match mesh.style {
- triangle::Style::Solid(color) => {
- self.pipelines.solid.push(transform, color);
+ match mesh {
+ Mesh::Solid { buffers, .. } => {
+ self.solid.uniforms.push(&solid::Uniforms::new(transform));
+
+ let written_bytes = self.solid.vertices.write(
+ device,
+ staging_belt,
+ encoder,
+ solid_vertex_offset,
+ &buffers.vertices,
+ );
+
+ solid_vertex_offset += written_bytes;
}
- triangle::Style::Gradient(gradient) => {
- self.pipelines.gradient.push(transform, gradient);
+ #[cfg(not(target_arch = "wasm32"))]
+ Mesh::Gradient {
+ buffers, gradient, ..
+ } => {
+ let written_bytes = self.gradient.vertices.write(
+ device,
+ staging_belt,
+ encoder,
+ gradient_vertex_offset,
+ &buffers.vertices,
+ );
+
+ gradient_vertex_offset += written_bytes;
+
+ match gradient {
+ iced_graphics::Gradient::Linear(linear) => {
+ use glam::{IVec4, Vec4};
+
+ let start_offset = self.gradient.color_stop_offset;
+ let end_offset = (linear.color_stops.len() as i32)
+ + start_offset
+ - 1;
+
+ self.gradient.uniforms.push(&gradient::Uniforms {
+ transform: transform.into(),
+ direction: Vec4::new(
+ linear.start.x,
+ linear.start.y,
+ linear.end.x,
+ linear.end.y,
+ ),
+ stop_range: IVec4::new(
+ start_offset,
+ end_offset,
+ 0,
+ 0,
+ ),
+ });
+
+ self.gradient.color_stop_offset = end_offset + 1;
+
+ let stops: Vec<gradient::ColorStop> = linear
+ .color_stops
+ .iter()
+ .map(|stop| {
+ let [r, g, b, a] = stop.color.into_linear();
+
+ gradient::ColorStop {
+ offset: stop.offset,
+ color: Vec4::new(r, g, b, a),
+ }
+ })
+ .collect();
+
+ self.gradient
+ .color_stops_pending_write
+ .color_stops
+ .extend(stops);
+ }
+ }
}
+ #[cfg(target_arch = "wasm32")]
+ Mesh::Gradient { .. } => {}
+ }
+ }
+
+ // Write uniform data to GPU
+ if count.solid_vertices > 0 {
+ let uniforms_resized = self.solid.uniforms.resize(device);
+
+ if uniforms_resized {
+ self.solid.bind_group = solid::Pipeline::bind_group(
+ device,
+ self.solid.uniforms.raw(),
+ &self.solid.bind_group_layout,
+ )
}
+
+ self.solid.uniforms.write(device, staging_belt, encoder);
}
- //write uniform data to GPU
- self.pipelines.write(device, staging_belt, encoder);
+ #[cfg(not(target_arch = "wasm32"))]
+ if count.gradient_vertices > 0 {
+ // First write the pending color stops to the CPU buffer
+ self.gradient
+ .storage
+ .push(&self.gradient.color_stops_pending_write);
+
+ // Resize buffers if needed
+ let uniforms_resized = self.gradient.uniforms.resize(device);
+ let storage_resized = self.gradient.storage.resize(device);
+
+ if uniforms_resized || storage_resized {
+ self.gradient.bind_group = gradient::Pipeline::bind_group(
+ device,
+ self.gradient.uniforms.raw(),
+ self.gradient.storage.raw(),
+ &self.gradient.bind_group_layout,
+ );
+ }
+
+ // Write to GPU
+ self.gradient.uniforms.write(device, staging_belt, encoder);
+ self.gradient.storage.write(device, staging_belt, encoder);
+
+ // Cleanup
+ self.gradient.color_stop_offset = 0;
+ self.gradient.color_stops_pending_write.color_stops.clear();
+ }
- //configure the render pass now that the data is uploaded to the GPU
+ // Configure render pass
{
- //configure antialiasing pass
let (attachment, resolve_target, load) = if let Some(blit) =
&mut self.blit
{
@@ -186,11 +261,12 @@ impl Pipeline {
});
let mut num_solids = 0;
+ #[cfg(not(target_arch = "wasm32"))]
let mut num_gradients = 0;
let mut last_is_solid = None;
for (index, mesh) in meshes.iter().enumerate() {
- let clip_bounds = (mesh.clip_bounds * scale_factor).snap();
+ let clip_bounds = (mesh.clip_bounds() * scale_factor).snap();
render_pass.set_scissor_rect(
clip_bounds.x,
@@ -199,46 +275,57 @@ impl Pipeline {
clip_bounds.height,
);
- match mesh.style {
- triangle::Style::Solid(_) => {
+ match mesh {
+ Mesh::Solid { .. } => {
if !last_is_solid.unwrap_or(false) {
- self.pipelines
- .solid
- .set_render_pass_pipeline(&mut render_pass);
+ render_pass.set_pipeline(&self.solid.pipeline);
last_is_solid = Some(true);
}
- self.pipelines.solid.configure_render_pass(
- &mut render_pass,
- num_solids,
+ render_pass.set_bind_group(
+ 0,
+ &self.solid.bind_group,
+ &[self.solid.uniforms.offset_at_index(num_solids)],
+ );
+
+ render_pass.set_vertex_buffer(
+ 0,
+ self.solid.vertices.slice_from_index(num_solids),
);
num_solids += 1;
}
- triangle::Style::Gradient(_) => {
+ #[cfg(not(target_arch = "wasm32"))]
+ Mesh::Gradient { .. } => {
if last_is_solid.unwrap_or(true) {
- self.pipelines
- .gradient
- .set_render_pass_pipeline(&mut render_pass);
+ render_pass.set_pipeline(&self.gradient.pipeline);
last_is_solid = Some(false);
}
- self.pipelines.gradient.configure_render_pass(
- &mut render_pass,
- num_gradients,
+ render_pass.set_bind_group(
+ 0,
+ &self.gradient.bind_group,
+ &[self
+ .gradient
+ .uniforms
+ .offset_at_index(num_gradients)],
+ );
+
+ render_pass.set_vertex_buffer(
+ 0,
+ self.gradient
+ .vertices
+ .slice_from_index(num_gradients),
);
num_gradients += 1;
}
+ #[cfg(target_arch = "wasm32")]
+ Mesh::Gradient { .. } => {}
};
- render_pass.set_vertex_buffer(
- 0,
- self.vertex_buffer.slice_from_index(index),
- );
-
render_pass.set_index_buffer(
self.index_buffer.slice_from_index(index),
wgpu::IndexFormat::Uint32,
@@ -252,7 +339,6 @@ impl Pipeline {
}
}
- self.vertex_buffer.clear();
self.index_buffer.clear();
if let Some(blit) = &mut self.blit {
@@ -261,19 +347,6 @@ impl Pipeline {
}
}
-//utility functions for individual pipelines with shared functionality
-fn vertex_buffer_layout<'a>() -> wgpu::VertexBufferLayout<'a> {
- wgpu::VertexBufferLayout {
- array_stride: std::mem::size_of::<Vertex2D>() as u64,
- step_mode: wgpu::VertexStepMode::Vertex,
- attributes: &[wgpu::VertexAttribute {
- format: wgpu::VertexFormat::Float32x2,
- offset: 0,
- shader_location: 0,
- }],
- }
-}
-
fn fragment_target(
texture_format: wgpu::TextureFormat,
) -> Option<wgpu::ColorTargetState> {
@@ -301,3 +374,360 @@ fn multisample_state(
alpha_to_coverage_enabled: false,
}
}
+
+mod solid {
+ use crate::buffer::dynamic;
+ use crate::buffer::r#static::Buffer;
+ use crate::settings;
+ use crate::triangle;
+ use encase::ShaderType;
+ use iced_graphics::Transformation;
+
+ #[derive(Debug)]
+ pub struct Pipeline {
+ pub pipeline: wgpu::RenderPipeline,
+ pub vertices: Buffer<triangle::ColoredVertex2D>,
+ pub uniforms: dynamic::Buffer<Uniforms>,
+ pub bind_group_layout: wgpu::BindGroupLayout,
+ pub bind_group: wgpu::BindGroup,
+ }
+
+ #[derive(Debug, Clone, Copy, ShaderType)]
+ pub struct Uniforms {
+ transform: glam::Mat4,
+ }
+
+ impl Uniforms {
+ pub fn new(transform: Transformation) -> Self {
+ Self {
+ transform: transform.into(),
+ }
+ }
+ }
+
+ impl Pipeline {
+ /// Creates a new [SolidPipeline] using `solid.wgsl` shader.
+ pub fn new(
+ device: &wgpu::Device,
+ format: wgpu::TextureFormat,
+ antialiasing: Option<settings::Antialiasing>,
+ ) -> Self {
+ let vertices = Buffer::new(
+ device,
+ "iced_wgpu::triangle::solid vertex buffer",
+ wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
+ );
+
+ let uniforms = dynamic::Buffer::uniform(
+ device,
+ "iced_wgpu::triangle::solid uniforms",
+ );
+
+ let bind_group_layout = device.create_bind_group_layout(
+ &wgpu::BindGroupLayoutDescriptor {
+ label: Some("iced_wgpu::triangle::solid bind group layout"),
+ entries: &[wgpu::BindGroupLayoutEntry {
+ binding: 0,
+ visibility: wgpu::ShaderStages::VERTEX_FRAGMENT,
+ ty: wgpu::BindingType::Buffer {
+ ty: wgpu::BufferBindingType::Uniform,
+ has_dynamic_offset: true,
+ min_binding_size: Some(Uniforms::min_size()),
+ },
+ count: None,
+ }],
+ },
+ );
+
+ let bind_group =
+ Self::bind_group(device, uniforms.raw(), &bind_group_layout);
+
+ let layout = device.create_pipeline_layout(
+ &wgpu::PipelineLayoutDescriptor {
+ label: Some("iced_wgpu::triangle::solid pipeline layout"),
+ bind_group_layouts: &[&bind_group_layout],
+ push_constant_ranges: &[],
+ },
+ );
+
+ let shader =
+ device.create_shader_module(wgpu::ShaderModuleDescriptor {
+ label: Some(
+ "iced_wgpu::triangle::solid create shader module",
+ ),
+ source: wgpu::ShaderSource::Wgsl(
+ std::borrow::Cow::Borrowed(include_str!(
+ "shader/solid.wgsl"
+ )),
+ ),
+ });
+
+ let pipeline = device.create_render_pipeline(
+ &wgpu::RenderPipelineDescriptor {
+ label: Some("iced_wgpu::triangle::solid pipeline"),
+ layout: Some(&layout),
+ vertex: wgpu::VertexState {
+ module: &shader,
+ entry_point: "vs_main",
+ buffers: &[wgpu::VertexBufferLayout {
+ array_stride: std::mem::size_of::<
+ triangle::ColoredVertex2D,
+ >()
+ as u64,
+ step_mode: wgpu::VertexStepMode::Vertex,
+ attributes: &wgpu::vertex_attr_array!(
+ // Position
+ 0 => Float32x2,
+ // Color
+ 1 => Float32x4,
+ ),
+ }],
+ },
+ fragment: Some(wgpu::FragmentState {
+ module: &shader,
+ entry_point: "fs_main",
+ targets: &[triangle::fragment_target(format)],
+ }),
+ primitive: triangle::primitive_state(),
+ depth_stencil: None,
+ multisample: triangle::multisample_state(antialiasing),
+ multiview: None,
+ },
+ );
+
+ Self {
+ pipeline,
+ vertices,
+ uniforms,
+ bind_group_layout,
+ bind_group,
+ }
+ }
+
+ pub fn bind_group(
+ device: &wgpu::Device,
+ buffer: &wgpu::Buffer,
+ layout: &wgpu::BindGroupLayout,
+ ) -> wgpu::BindGroup {
+ device.create_bind_group(&wgpu::BindGroupDescriptor {
+ label: Some("iced_wgpu::triangle::solid bind group"),
+ layout,
+ entries: &[wgpu::BindGroupEntry {
+ binding: 0,
+ resource: wgpu::BindingResource::Buffer(
+ wgpu::BufferBinding {
+ buffer,
+ offset: 0,
+ size: Some(Uniforms::min_size()),
+ },
+ ),
+ }],
+ })
+ }
+ }
+}
+
+#[cfg(not(target_arch = "wasm32"))]
+mod gradient {
+ use crate::buffer::dynamic;
+ use crate::buffer::r#static::Buffer;
+ use crate::settings;
+ use crate::triangle;
+
+ use encase::ShaderType;
+ use glam::{IVec4, Vec4};
+ use iced_graphics::triangle::Vertex2D;
+
+ #[derive(Debug)]
+ pub struct Pipeline {
+ pub pipeline: wgpu::RenderPipeline,
+ pub vertices: Buffer<Vertex2D>,
+ pub uniforms: dynamic::Buffer<Uniforms>,
+ pub storage: dynamic::Buffer<Storage>,
+ pub color_stop_offset: i32,
+ //Need to store these and then write them all at once
+ //or else they will be padded to 256 and cause gaps in the storage buffer
+ pub color_stops_pending_write: Storage,
+ pub bind_group_layout: wgpu::BindGroupLayout,
+ pub bind_group: wgpu::BindGroup,
+ }
+
+ #[derive(Debug, ShaderType)]
+ pub struct Uniforms {
+ pub transform: glam::Mat4,
+ //xy = start, zw = end
+ pub direction: Vec4,
+ //x = start stop, y = end stop, zw = padding
+ pub stop_range: IVec4,
+ }
+
+ #[derive(Debug, ShaderType)]
+ pub struct ColorStop {
+ pub color: Vec4,
+ pub offset: f32,
+ }
+
+ #[derive(Debug, ShaderType)]
+ pub struct Storage {
+ #[size(runtime)]
+ pub color_stops: Vec<ColorStop>,
+ }
+
+ impl Pipeline {
+ /// Creates a new [GradientPipeline] using `gradient.wgsl` shader.
+ pub(super) fn new(
+ device: &wgpu::Device,
+ format: wgpu::TextureFormat,
+ antialiasing: Option<settings::Antialiasing>,
+ ) -> Self {
+ let vertices = Buffer::new(
+ device,
+ "iced_wgpu::triangle::gradient vertex buffer",
+ wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
+ );
+
+ let uniforms = dynamic::Buffer::uniform(
+ device,
+ "iced_wgpu::triangle::gradient uniforms",
+ );
+
+ //Note: with a WASM target storage buffers are not supported. Will need to use UBOs & static
+ // sized array (eg like the 32-sized array on OpenGL side right now) to make gradients work
+ let storage = dynamic::Buffer::storage(
+ device,
+ "iced_wgpu::triangle::gradient storage",
+ );
+
+ let bind_group_layout = device.create_bind_group_layout(
+ &wgpu::BindGroupLayoutDescriptor {
+ label: Some(
+ "iced_wgpu::triangle::gradient bind group layout",
+ ),
+ entries: &[
+ wgpu::BindGroupLayoutEntry {
+ binding: 0,
+ visibility: wgpu::ShaderStages::VERTEX_FRAGMENT,
+ ty: wgpu::BindingType::Buffer {
+ ty: wgpu::BufferBindingType::Uniform,
+ has_dynamic_offset: true,
+ min_binding_size: Some(Uniforms::min_size()),
+ },
+ count: None,
+ },
+ wgpu::BindGroupLayoutEntry {
+ binding: 1,
+ visibility: wgpu::ShaderStages::FRAGMENT,
+ ty: wgpu::BindingType::Buffer {
+ ty: wgpu::BufferBindingType::Storage {
+ read_only: true,
+ },
+ has_dynamic_offset: false,
+ min_binding_size: Some(Storage::min_size()),
+ },
+ count: None,
+ },
+ ],
+ },
+ );
+
+ let bind_group = Pipeline::bind_group(
+ device,
+ uniforms.raw(),
+ storage.raw(),
+ &bind_group_layout,
+ );
+
+ let layout = device.create_pipeline_layout(
+ &wgpu::PipelineLayoutDescriptor {
+ label: Some(
+ "iced_wgpu::triangle::gradient pipeline layout",
+ ),
+ bind_group_layouts: &[&bind_group_layout],
+ push_constant_ranges: &[],
+ },
+ );
+
+ let shader =
+ device.create_shader_module(wgpu::ShaderModuleDescriptor {
+ label: Some(
+ "iced_wgpu::triangle::gradient create shader module",
+ ),
+ source: wgpu::ShaderSource::Wgsl(
+ std::borrow::Cow::Borrowed(include_str!(
+ "shader/gradient.wgsl"
+ )),
+ ),
+ });
+
+ let pipeline = device.create_render_pipeline(
+ &wgpu::RenderPipelineDescriptor {
+ label: Some("iced_wgpu::triangle::gradient pipeline"),
+ layout: Some(&layout),
+ vertex: wgpu::VertexState {
+ module: &shader,
+ entry_point: "vs_main",
+ buffers: &[wgpu::VertexBufferLayout {
+ array_stride: std::mem::size_of::<Vertex2D>()
+ as u64,
+ step_mode: wgpu::VertexStepMode::Vertex,
+ attributes: &wgpu::vertex_attr_array!(
+ // Position
+ 0 => Float32x2,
+ ),
+ }],
+ },
+ fragment: Some(wgpu::FragmentState {
+ module: &shader,
+ entry_point: "fs_main",
+ targets: &[triangle::fragment_target(format)],
+ }),
+ primitive: triangle::primitive_state(),
+ depth_stencil: None,
+ multisample: triangle::multisample_state(antialiasing),
+ multiview: None,
+ },
+ );
+
+ Self {
+ pipeline,
+ vertices,
+ uniforms,
+ storage,
+ color_stop_offset: 0,
+ color_stops_pending_write: Storage {
+ color_stops: vec![],
+ },
+ bind_group_layout,
+ bind_group,
+ }
+ }
+
+ pub fn bind_group(
+ device: &wgpu::Device,
+ uniform_buffer: &wgpu::Buffer,
+ storage_buffer: &wgpu::Buffer,
+ layout: &wgpu::BindGroupLayout,
+ ) -> wgpu::BindGroup {
+ device.create_bind_group(&wgpu::BindGroupDescriptor {
+ label: Some("iced_wgpu::triangle::gradient bind group"),
+ layout,
+ entries: &[
+ wgpu::BindGroupEntry {
+ binding: 0,
+ resource: wgpu::BindingResource::Buffer(
+ wgpu::BufferBinding {
+ buffer: uniform_buffer,
+ offset: 0,
+ size: Some(Uniforms::min_size()),
+ },
+ ),
+ },
+ wgpu::BindGroupEntry {
+ binding: 1,
+ resource: storage_buffer.as_entire_binding(),
+ },
+ ],
+ })
+ }
+ }
+}
diff --git a/wgpu/src/triangle/gradient.rs b/wgpu/src/triangle/gradient.rs
deleted file mode 100644
index b06cbac6..00000000
--- a/wgpu/src/triangle/gradient.rs
+++ /dev/null
@@ -1,268 +0,0 @@
-use crate::buffer::dynamic;
-use crate::settings;
-use crate::triangle;
-use encase::ShaderType;
-use glam::{IVec4, Vec4};
-use iced_graphics::gradient::Gradient;
-use iced_graphics::Transformation;
-
-pub struct Pipeline {
- pipeline: wgpu::RenderPipeline,
- pub(super) uniform_buffer: dynamic::Buffer<Uniforms>,
- pub(super) storage_buffer: dynamic::Buffer<Storage>,
- color_stop_offset: i32,
- //Need to store these and then write them all at once
- //or else they will be padded to 256 and cause gaps in the storage buffer
- color_stops_pending_write: Storage,
- bind_group_layout: wgpu::BindGroupLayout,
- bind_group: wgpu::BindGroup,
-}
-
-#[derive(Debug, ShaderType)]
-pub(super) struct Uniforms {
- transform: glam::Mat4,
- //xy = start, zw = end
- direction: Vec4,
- //x = start stop, y = end stop, zw = padding
- stop_range: IVec4,
-}
-
-#[derive(Debug, ShaderType)]
-pub(super) struct ColorStop {
- color: Vec4,
- offset: f32,
-}
-
-#[derive(ShaderType)]
-pub(super) struct Storage {
- #[size(runtime)]
- pub color_stops: Vec<ColorStop>,
-}
-
-impl Pipeline {
- /// Creates a new [GradientPipeline] using `gradient.wgsl` shader.
- pub(super) fn new(
- device: &wgpu::Device,
- format: wgpu::TextureFormat,
- antialiasing: Option<settings::Antialiasing>,
- ) -> Self {
- let uniform_buffer = dynamic::Buffer::uniform(
- device,
- "iced_wgpu::triangle::gradient uniforms",
- );
-
- //Note: with a WASM target storage buffers are not supported. Will need to use UBOs & static
- // sized array (eg like the 32-sized array on OpenGL side right now) to make gradients work
- let storage_buffer = dynamic::Buffer::storage(
- device,
- "iced_wgpu::triangle::gradient storage",
- );
-
- let bind_group_layout =
- device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
- label: Some("iced_wgpu::triangle::gradient bind group layout"),
- entries: &[
- wgpu::BindGroupLayoutEntry {
- binding: 0,
- visibility: wgpu::ShaderStages::VERTEX_FRAGMENT,
- ty: wgpu::BindingType::Buffer {
- ty: wgpu::BufferBindingType::Uniform,
- has_dynamic_offset: true,
- min_binding_size: Some(Uniforms::min_size()),
- },
- count: None,
- },
- wgpu::BindGroupLayoutEntry {
- binding: 1,
- visibility: wgpu::ShaderStages::FRAGMENT,
- ty: wgpu::BindingType::Buffer {
- ty: wgpu::BufferBindingType::Storage {
- read_only: true,
- },
- has_dynamic_offset: false,
- min_binding_size: Some(Storage::min_size()),
- },
- count: None,
- },
- ],
- });
-
- let bind_group = Pipeline::bind_group(
- device,
- uniform_buffer.raw(),
- storage_buffer.raw(),
- &bind_group_layout,
- );
-
- let layout =
- device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
- label: Some("iced_wgpu::triangle::gradient pipeline layout"),
- bind_group_layouts: &[&bind_group_layout],
- push_constant_ranges: &[],
- });
-
- let shader =
- device.create_shader_module(wgpu::ShaderModuleDescriptor {
- label: Some(
- "iced_wgpu::triangle::gradient create shader module",
- ),
- source: wgpu::ShaderSource::Wgsl(std::borrow::Cow::Borrowed(
- include_str!("../shader/gradient.wgsl"),
- )),
- });
-
- let pipeline =
- device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
- label: Some("iced_wgpu::triangle::gradient pipeline"),
- layout: Some(&layout),
- vertex: wgpu::VertexState {
- module: &shader,
- entry_point: "vs_main",
- buffers: &[triangle::vertex_buffer_layout()],
- },
- fragment: Some(wgpu::FragmentState {
- module: &shader,
- entry_point: "fs_main",
- targets: &[triangle::fragment_target(format)],
- }),
- primitive: triangle::primitive_state(),
- depth_stencil: None,
- multisample: triangle::multisample_state(antialiasing),
- multiview: None,
- });
-
- Self {
- pipeline,
- uniform_buffer,
- storage_buffer,
- color_stop_offset: 0,
- color_stops_pending_write: Storage {
- color_stops: vec![],
- },
- bind_group_layout,
- bind_group,
- }
- }
-
- /// Pushes a new gradient uniform to the CPU buffer.
- pub fn push(&mut self, transform: Transformation, gradient: &Gradient) {
- match gradient {
- Gradient::Linear(linear) => {
- let start_offset = self.color_stop_offset;
- let end_offset =
- (linear.color_stops.len() as i32) + start_offset - 1;
-
- self.uniform_buffer.push(&Uniforms {
- transform: transform.into(),
- direction: Vec4::new(
- linear.start.x,
- linear.start.y,
- linear.end.x,
- linear.end.y,
- ),
- stop_range: IVec4::new(start_offset, end_offset, 0, 0),
- });
-
- self.color_stop_offset = end_offset + 1;
-
- let stops: Vec<ColorStop> = linear
- .color_stops
- .iter()
- .map(|stop| {
- let [r, g, b, a] = stop.color.into_linear();
-
- ColorStop {
- offset: stop.offset,
- color: Vec4::new(r, g, b, a),
- }
- })
- .collect();
-
- self.color_stops_pending_write.color_stops.extend(stops);
- }
- }
- }
-
- fn bind_group(
- device: &wgpu::Device,
- uniform_buffer: &wgpu::Buffer,
- storage_buffer: &wgpu::Buffer,
- layout: &wgpu::BindGroupLayout,
- ) -> wgpu::BindGroup {
- device.create_bind_group(&wgpu::BindGroupDescriptor {
- label: Some("iced_wgpu::triangle::gradient bind group"),
- layout,
- entries: &[
- wgpu::BindGroupEntry {
- binding: 0,
- resource: wgpu::BindingResource::Buffer(
- wgpu::BufferBinding {
- buffer: uniform_buffer,
- offset: 0,
- size: Some(Uniforms::min_size()),
- },
- ),
- },
- wgpu::BindGroupEntry {
- binding: 1,
- resource: storage_buffer.as_entire_binding(),
- },
- ],
- })
- }
-
- /// Writes the contents of the gradient CPU buffer to the GPU buffer, resizing the GPU buffer
- /// beforehand if necessary.
- pub fn write(
- &mut self,
- device: &wgpu::Device,
- staging_belt: &mut wgpu::util::StagingBelt,
- encoder: &mut wgpu::CommandEncoder,
- ) {
- //first write the pending color stops to the CPU buffer
- self.storage_buffer.push(&self.color_stops_pending_write);
-
- //resize buffers if needed
- let uniforms_resized = self.uniform_buffer.resize(device);
- let storage_resized = self.storage_buffer.resize(device);
-
- if uniforms_resized || storage_resized {
- //recreate bind groups if any buffers were resized
- self.bind_group = Pipeline::bind_group(
- device,
- self.uniform_buffer.raw(),
- self.storage_buffer.raw(),
- &self.bind_group_layout,
- );
- }
-
- //write to GPU
- self.uniform_buffer.write(device, staging_belt, encoder);
- self.storage_buffer.write(device, staging_belt, encoder);
-
- //cleanup
- self.color_stop_offset = 0;
- self.color_stops_pending_write.color_stops.clear();
- }
-
- pub fn set_render_pass_pipeline<'a>(
- &'a self,
- render_pass: &mut wgpu::RenderPass<'a>,
- ) {
- render_pass.set_pipeline(&self.pipeline);
- }
-
- /// Configures the current render pass to draw the gradient at its offset stored in the
- /// [DynamicBuffer] at [index].
- pub fn configure_render_pass<'a>(
- &'a self,
- render_pass: &mut wgpu::RenderPass<'a>,
- count: usize,
- ) {
- render_pass.set_bind_group(
- 0,
- &self.bind_group,
- &[self.uniform_buffer.offset_at_index(count)],
- )
- }
-}
diff --git a/wgpu/src/triangle/solid.rs b/wgpu/src/triangle/solid.rs
deleted file mode 100644
index 2e1052f2..00000000
--- a/wgpu/src/triangle/solid.rs
+++ /dev/null
@@ -1,170 +0,0 @@
-use crate::buffer::dynamic;
-use crate::triangle;
-use crate::{settings, Color};
-use encase::ShaderType;
-use glam::Vec4;
-use iced_graphics::Transformation;
-
-pub struct Pipeline {
- pipeline: wgpu::RenderPipeline,
- pub(super) buffer: dynamic::Buffer<Uniforms>,
- bind_group_layout: wgpu::BindGroupLayout,
- bind_group: wgpu::BindGroup,
-}
-
-#[derive(Debug, Clone, Copy, ShaderType)]
-pub(super) struct Uniforms {
- transform: glam::Mat4,
- color: Vec4,
-}
-
-impl Uniforms {
- pub fn new(transform: Transformation, color: Color) -> Self {
- let [r, g, b, a] = color.into_linear();
-
- Self {
- transform: transform.into(),
- color: Vec4::new(r, g, b, a),
- }
- }
-}
-
-impl Pipeline {
- /// Creates a new [SolidPipeline] using `solid.wgsl` shader.
- pub fn new(
- device: &wgpu::Device,
- format: wgpu::TextureFormat,
- antialiasing: Option<settings::Antialiasing>,
- ) -> Self {
- let buffer = dynamic::Buffer::uniform(
- device,
- "iced_wgpu::triangle::solid uniforms",
- );
-
- let bind_group_layout =
- device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
- label: Some("iced_wgpu::triangle::solid bind group layout"),
- entries: &[wgpu::BindGroupLayoutEntry {
- binding: 0,
- visibility: wgpu::ShaderStages::VERTEX_FRAGMENT,
- ty: wgpu::BindingType::Buffer {
- ty: wgpu::BufferBindingType::Uniform,
- has_dynamic_offset: true,
- min_binding_size: Some(Uniforms::min_size()),
- },
- count: None,
- }],
- });
-
- let bind_group =
- Pipeline::bind_group(device, buffer.raw(), &bind_group_layout);
-
- let layout =
- device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
- label: Some("iced_wgpu::triangle::solid pipeline layout"),
- bind_group_layouts: &[&bind_group_layout],
- push_constant_ranges: &[],
- });
-
- let shader =
- device.create_shader_module(wgpu::ShaderModuleDescriptor {
- label: Some("iced_wgpu::triangle::solid create shader module"),
- source: wgpu::ShaderSource::Wgsl(std::borrow::Cow::Borrowed(
- include_str!("../shader/solid.wgsl"),
- )),
- });
-
- let pipeline =
- device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
- label: Some("iced_wgpu::triangle::solid pipeline"),
- layout: Some(&layout),
- vertex: wgpu::VertexState {
- module: &shader,
- entry_point: "vs_main",
- buffers: &[triangle::vertex_buffer_layout()],
- },
- fragment: Some(wgpu::FragmentState {
- module: &shader,
- entry_point: "fs_main",
- targets: &[triangle::fragment_target(format)],
- }),
- primitive: triangle::primitive_state(),
- depth_stencil: None,
- multisample: triangle::multisample_state(antialiasing),
- multiview: None,
- });
-
- Self {
- pipeline,
- buffer,
- bind_group_layout,
- bind_group,
- }
- }
-
- fn bind_group(
- device: &wgpu::Device,
- buffer: &wgpu::Buffer,
- layout: &wgpu::BindGroupLayout,
- ) -> wgpu::BindGroup {
- device.create_bind_group(&wgpu::BindGroupDescriptor {
- label: Some("iced_wgpu::triangle::solid bind group"),
- layout,
- entries: &[wgpu::BindGroupEntry {
- binding: 0,
- resource: wgpu::BindingResource::Buffer(wgpu::BufferBinding {
- buffer,
- offset: 0,
- size: Some(Uniforms::min_size()),
- }),
- }],
- })
- }
-
- /// Pushes a new solid uniform to the CPU buffer.
- pub fn push(&mut self, transform: Transformation, color: &Color) {
- self.buffer.push(&Uniforms::new(transform, *color));
- }
-
- /// Writes the contents of the solid CPU buffer to the GPU buffer, resizing the GPU buffer
- /// beforehand if necessary.
- pub fn write(
- &mut self,
- device: &wgpu::Device,
- staging_belt: &mut wgpu::util::StagingBelt,
- encoder: &mut wgpu::CommandEncoder,
- ) {
- let uniforms_resized = self.buffer.resize(device);
-
- if uniforms_resized {
- self.bind_group = Pipeline::bind_group(
- device,
- self.buffer.raw(),
- &self.bind_group_layout,
- )
- }
-
- self.buffer.write(device, staging_belt, encoder);
- }
-
- pub fn set_render_pass_pipeline<'a>(
- &'a self,
- render_pass: &mut wgpu::RenderPass<'a>,
- ) {
- render_pass.set_pipeline(&self.pipeline);
- }
-
- /// Configures the current render pass to draw the solid at its offset stored in the
- /// [DynamicBuffer] at [index].
- pub fn configure_render_pass<'a>(
- &'a self,
- render_pass: &mut wgpu::RenderPass<'a>,
- count: usize,
- ) {
- render_pass.set_bind_group(
- 0,
- &self.bind_group,
- &[self.buffer.offset_at_index(count)],
- )
- }
-}
diff --git a/winit/Cargo.toml b/winit/Cargo.toml
index d3ed949f..a3ac3ddd 100644
--- a/winit/Cargo.toml
+++ b/winit/Cargo.toml
@@ -1,6 +1,6 @@
[package]
name = "iced_winit"
-version = "0.4.0"
+version = "0.5.1"
authors = ["Héctor Ramón Jiménez <hector0193@gmail.com>"]
edition = "2021"
description = "A winit runtime for Iced"
@@ -26,15 +26,15 @@ git = "https://github.com/iced-rs/winit.git"
rev = "940457522e9fb9f5dac228b0ecfafe0138b4048c"
[dependencies.iced_native]
-version = "0.5"
+version = "0.6"
path = "../native"
[dependencies.iced_graphics]
-version = "0.3"
+version = "0.4"
path = "../graphics"
[dependencies.iced_futures]
-version = "0.4"
+version = "0.5"
path = "../futures"
[target.'cfg(target_os = "windows")'.dependencies.winapi]
diff --git a/winit/src/application.rs b/winit/src/application.rs
index 939a50c9..1706d2e9 100644
--- a/winit/src/application.rs
+++ b/winit/src/application.rs
@@ -58,10 +58,10 @@ where
/// title of your application when necessary.
fn title(&self) -> String;
- /// Returns the current [`Theme`] of the [`Application`].
+ /// Returns the current `Theme` of the [`Application`].
fn theme(&self) -> <Self::Renderer as crate::Renderer>::Theme;
- /// Returns the [`Style`] variation of the [`Theme`].
+ /// Returns the `Style` variation of the `Theme`.
fn style(
&self,
) -> <<Self::Renderer as crate::Renderer>::Theme as StyleSheet>::Style {
@@ -137,6 +137,9 @@ where
runtime.enter(|| A::new(flags))
};
+ #[cfg(target_arch = "wasm32")]
+ let target = settings.window.platform_specific.target.clone();
+
let builder = settings.window.into_builder(
&application.title(),
event_loop.primary_monitor(),
@@ -159,9 +162,20 @@ where
let document = window.document().unwrap();
let body = document.body().unwrap();
- let _ = body
- .append_child(&canvas)
- .expect("Append canvas to HTML body");
+ let target = target.and_then(|target| {
+ body.query_selector(&format!("#{}", target))
+ .ok()
+ .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"),
+ };
}
let (compositor, renderer) = C::new(compositor_settings, Some(&window))?;
diff --git a/winit/src/application/state.rs b/winit/src/application/state.rs
index 9c539548..8d6a1df1 100644
--- a/winit/src/application/state.rs
+++ b/winit/src/application/state.rs
@@ -32,7 +32,7 @@ where
let title = application.title();
let scale_factor = application.scale_factor();
let theme = application.theme();
- let appearance = theme.appearance(application.style());
+ let appearance = theme.appearance(&application.style());
let viewport = {
let physical_size = window.inner_size();
@@ -210,6 +210,6 @@ where
// Update theme and appearance
self.theme = application.theme();
- self.appearance = self.theme.appearance(application.style());
+ self.appearance = self.theme.appearance(&application.style());
}
}
diff --git a/winit/src/conversion.rs b/winit/src/conversion.rs
index ba5b0002..0707aed5 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.4/native
+//! [`iced_native`]: https://github.com/iced-rs/iced/tree/0.5/native
use crate::keyboard;
use crate::mouse;
use crate::touch;
@@ -218,7 +218,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.4/native
+/// [`iced_native`]: https://github.com/iced-rs/iced/tree/0.5/native
pub fn mouse_interaction(
interaction: mouse::Interaction,
) -> winit::window::CursorIcon {
@@ -242,7 +242,7 @@ pub fn mouse_interaction(
/// 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.4/native
+/// [`iced_native`]: https://github.com/iced-rs/iced/tree/0.5/native
pub fn mouse_button(mouse_button: winit::event::MouseButton) -> mouse::Button {
match mouse_button {
winit::event::MouseButton::Left => mouse::Button::Left,
@@ -258,7 +258,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.4/native
+/// [`iced_native`]: https://github.com/iced-rs/iced/tree/0.5/native
pub fn modifiers(
modifiers: winit::event::ModifiersState,
) -> keyboard::Modifiers {
@@ -285,7 +285,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.4/native
+/// [`iced_native`]: https://github.com/iced-rs/iced/tree/0.5/native
pub fn touch_event(
touch: winit::event::Touch,
scale_factor: f64,
@@ -316,7 +316,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.4/native
+/// [`iced_native`]: https://github.com/iced-rs/iced/tree/0.5/native
pub fn key_code(
virtual_keycode: winit::event::VirtualKeyCode,
) -> keyboard::KeyCode {
diff --git a/winit/src/lib.rs b/winit/src/lib.rs
index edba887b..bb3a3d5b 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.4/native
+//! [`iced_native`]: https://github.com/iced-rs/iced/tree/0.5/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 6387454b..9bbdef5c 100644
--- a/winit/src/settings.rs
+++ b/winit/src/settings.rs
@@ -7,7 +7,15 @@ mod platform;
#[path = "settings/macos.rs"]
mod platform;
-#[cfg(not(any(target_os = "windows", target_os = "macos")))]
+#[cfg(target_arch = "wasm32")]
+#[path = "settings/wasm.rs"]
+mod platform;
+
+#[cfg(not(any(
+ target_os = "windows",
+ target_os = "macos",
+ target_arch = "wasm32"
+)))]
#[path = "settings/other.rs"]
mod platform;
@@ -27,7 +35,7 @@ pub struct Settings<Flags> {
/// communicate with it through the windowing system.
pub id: Option<String>,
- /// The [`Window`] settings
+ /// The [`Window`] settings.
pub window: Window,
/// The data needed to initialize an [`Application`].
diff --git a/winit/src/settings/macos.rs b/winit/src/settings/macos.rs
index ad4c8cae..f86e63ad 100644
--- a/winit/src/settings/macos.rs
+++ b/winit/src/settings/macos.rs
@@ -1,4 +1,3 @@
-#![cfg(target_os = "macos")]
//! Platform specific settings for macOS.
/// The platform specific window settings of an application.
diff --git a/winit/src/settings/wasm.rs b/winit/src/settings/wasm.rs
new file mode 100644
index 00000000..8e0f1bbc
--- /dev/null
+++ b/winit/src/settings/wasm.rs
@@ -0,0 +1,11 @@
+//! Platform specific settings for WebAssembly.
+
+/// The platform specific window settings of an application.
+#[derive(Debug, Clone, PartialEq, Eq, Default)]
+pub struct PlatformSpecific {
+ /// The identifier of a DOM element that will be replaced with the
+ /// application.
+ ///
+ /// If set to `None`, the application will be appended to the HTML body.
+ pub target: Option<String>,
+}
diff --git a/winit/src/settings/windows.rs b/winit/src/settings/windows.rs
index 9bef1eaf..ff03a9c5 100644
--- a/winit/src/settings/windows.rs
+++ b/winit/src/settings/windows.rs
@@ -1,4 +1,3 @@
-#![cfg(target_os = "windows")]
//! Platform specific settings for Windows.
/// The platform specific window settings of an application.