summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.github/ISSUE_TEMPLATE/BUG-REPORT.yml6
-rw-r--r--.github/workflows/document.yml8
-rw-r--r--Cargo.toml6
-rw-r--r--DEPENDENCIES.md33
-rw-r--r--README.md36
-rw-r--r--core/Cargo.toml6
-rw-r--r--core/src/angle.rs33
-rw-r--r--core/src/background.rs19
-rw-r--r--core/src/border_radius.rs22
-rw-r--r--core/src/color.rs40
-rw-r--r--core/src/element.rs72
-rw-r--r--core/src/gradient.rs154
-rw-r--r--core/src/gradient/linear.rs112
-rw-r--r--core/src/lib.rs4
-rw-r--r--core/src/mouse.rs2
-rw-r--r--core/src/mouse/button.rs2
-rw-r--r--core/src/mouse/cursor.rs52
-rw-r--r--core/src/overlay.rs22
-rw-r--r--core/src/overlay/element.rs83
-rw-r--r--core/src/overlay/group.rs43
-rw-r--r--core/src/renderer.rs25
-rw-r--r--core/src/widget.rs8
-rw-r--r--core/src/widget/text.rs7
-rw-r--r--core/src/window.rs2
-rw-r--r--core/src/window/level.rs19
-rw-r--r--examples/README.md3
-rw-r--r--examples/arc/src/main.rs5
-rw-r--r--examples/bezier_tool/src/main.rs18
-rw-r--r--examples/checkbox/src/main.rs2
-rw-r--r--examples/clock/src/main.rs7
-rw-r--r--examples/color_palette/Cargo.toml2
-rw-r--r--examples/color_palette/src/main.rs60
-rw-r--r--examples/custom_quad/src/main.rs5
-rw-r--r--examples/custom_widget/src/main.rs5
-rw-r--r--examples/game_of_life/src/main.rs26
-rw-r--r--examples/geometry/src/main.rs59
-rw-r--r--examples/integration/Cargo.toml2
-rw-r--r--examples/integration/src/main.rs43
-rw-r--r--examples/modal/src/main.rs94
-rw-r--r--examples/modern_art/Cargo.toml11
-rw-r--r--examples/modern_art/src/main.rs143
-rw-r--r--examples/multitouch/src/main.rs7
-rw-r--r--examples/pane_grid/src/main.rs6
-rw-r--r--examples/screenshot/Cargo.toml11
-rw-r--r--examples/screenshot/src/main.rs320
-rw-r--r--examples/scrollable/src/main.rs31
-rw-r--r--examples/sierpinski_triangle/src/main.rs17
-rw-r--r--examples/solar_system/Cargo.toml1
-rw-r--r--examples/solar_system/src/main.rs24
-rw-r--r--examples/styling/src/main.rs4
-rw-r--r--examples/toast/src/main.rs51
-rw-r--r--examples/tour/Cargo.toml2
-rw-r--r--examples/tour/src/main.rs7
-rw-r--r--futures/src/backend/native.rs4
-rw-r--r--futures/src/backend/native/smol.rs2
-rw-r--r--futures/src/backend/native/thread_pool.rs1
-rw-r--r--futures/src/lib.rs2
-rw-r--r--futures/src/subscription.rs2
-rw-r--r--graphics/Cargo.toml4
-rw-r--r--graphics/src/backend.rs12
-rw-r--r--graphics/src/color.rs46
-rw-r--r--graphics/src/compositor.rs15
-rw-r--r--graphics/src/damage.rs5
-rw-r--r--graphics/src/geometry.rs17
-rw-r--r--graphics/src/geometry/fill.rs14
-rw-r--r--graphics/src/geometry/path.rs2
-rw-r--r--graphics/src/geometry/style.rs3
-rw-r--r--graphics/src/gradient.rs195
-rw-r--r--graphics/src/image.rs1
-rw-r--r--graphics/src/lib.rs8
-rw-r--r--graphics/src/primitive.rs49
-rw-r--r--graphics/src/renderer.rs32
-rw-r--r--graphics/src/triangle.rs1
-rw-r--r--renderer/Cargo.toml19
-rw-r--r--renderer/src/backend.rs12
-rw-r--r--renderer/src/compositor.rs110
-rw-r--r--renderer/src/geometry.rs31
-rw-r--r--renderer/src/lib.rs3
-rw-r--r--runtime/src/lib.rs5
-rw-r--r--runtime/src/overlay.rs4
-rw-r--r--runtime/src/overlay/nested.rs353
-rw-r--r--runtime/src/program/state.rs10
-rw-r--r--runtime/src/user_interface.rs152
-rw-r--r--runtime/src/window.rs20
-rw-r--r--runtime/src/window/action.rs35
-rw-r--r--runtime/src/window/screenshot.rs92
-rw-r--r--src/lib.rs12
-rw-r--r--src/window/icon.rs3
-rw-r--r--src/window/settings.rs10
-rw-r--r--style/Cargo.toml2
-rw-r--r--style/src/button.rs9
-rw-r--r--style/src/checkbox.rs4
-rw-r--r--style/src/container.rs6
-rw-r--r--style/src/menu.rs4
-rw-r--r--style/src/pane_grid.rs38
-rw-r--r--style/src/pick_list.rs4
-rw-r--r--style/src/progress_bar.rs4
-rw-r--r--style/src/rule.rs4
-rw-r--r--style/src/scrollable.rs6
-rw-r--r--style/src/slider.rs6
-rw-r--r--style/src/text_input.rs4
-rw-r--r--style/src/theme.rs104
-rw-r--r--style/src/theme/palette.rs22
-rw-r--r--tiny_skia/Cargo.toml2
-rw-r--r--tiny_skia/fonts/Iced-Icons.ttfbin0 -> 5108 bytes
-rw-r--r--tiny_skia/src/backend.rs50
-rw-r--r--tiny_skia/src/geometry.rs50
-rw-r--r--tiny_skia/src/text.rs54
-rw-r--r--tiny_skia/src/window/compositor.rs76
-rw-r--r--wgpu/Cargo.toml9
-rw-r--r--wgpu/src/backend.rs17
-rw-r--r--wgpu/src/buffer.rs43
-rw-r--r--wgpu/src/buffer/dynamic.rs202
-rw-r--r--wgpu/src/buffer/static.rs107
-rw-r--r--wgpu/src/color.rs165
-rw-r--r--wgpu/src/geometry.rs95
-rw-r--r--wgpu/src/image.rs8
-rw-r--r--wgpu/src/image/atlas.rs48
-rw-r--r--wgpu/src/image/raster.rs4
-rw-r--r--wgpu/src/image/vector.rs5
-rw-r--r--wgpu/src/layer.rs29
-rw-r--r--wgpu/src/layer/mesh.rs15
-rw-r--r--wgpu/src/layer/quad.rs30
-rw-r--r--wgpu/src/lib.rs9
-rw-r--r--wgpu/src/quad.rs304
-rw-r--r--wgpu/src/quad/gradient.rs165
-rw-r--r--wgpu/src/quad/solid.rs150
-rw-r--r--wgpu/src/shader/gradient.wgsl88
-rw-r--r--wgpu/src/shader/quad.wgsl293
-rw-r--r--wgpu/src/shader/solid.wgsl30
-rw-r--r--wgpu/src/shader/triangle.wgsl165
-rw-r--r--wgpu/src/text.rs87
-rw-r--r--wgpu/src/triangle.rs538
-rw-r--r--wgpu/src/triangle/msaa.rs11
-rw-r--r--wgpu/src/window/compositor.rs174
-rw-r--r--widget/Cargo.toml2
-rw-r--r--widget/src/button.rs56
-rw-r--r--widget/src/canvas.rs19
-rw-r--r--widget/src/canvas/cursor.rs64
-rw-r--r--widget/src/canvas/program.rs19
-rw-r--r--widget/src/checkbox.rs19
-rw-r--r--widget/src/column.rs30
-rw-r--r--widget/src/container.rs14
-rw-r--r--widget/src/helpers.rs3
-rw-r--r--widget/src/image.rs5
-rw-r--r--widget/src/image/viewer.rs25
-rw-r--r--widget/src/lazy.rs87
-rw-r--r--widget/src/lazy/component.rs80
-rw-r--r--widget/src/lazy/helpers.rs8
-rw-r--r--widget/src/lazy/responsive.rs87
-rw-r--r--widget/src/lib.rs3
-rw-r--r--widget/src/mouse_area.rs20
-rw-r--r--widget/src/overlay.rs1
-rw-r--r--widget/src/overlay/menu.rs114
-rw-r--r--widget/src/pane_grid.rs261
-rw-r--r--widget/src/pane_grid/content.rs72
-rw-r--r--widget/src/pane_grid/draggable.rs6
-rw-r--r--widget/src/pane_grid/state.rs41
-rw-r--r--widget/src/pane_grid/title_bar.rs18
-rw-r--r--widget/src/pick_list.rs76
-rw-r--r--widget/src/progress_bar.rs11
-rw-r--r--widget/src/qr_code.rs3
-rw-r--r--widget/src/radio.rs17
-rw-r--r--widget/src/row.rs30
-rw-r--r--widget/src/rule.rs7
-rw-r--r--widget/src/scrollable.rs304
-rw-r--r--widget/src/slider.rs61
-rw-r--r--widget/src/space.rs5
-rw-r--r--widget/src/svg.rs5
-rw-r--r--widget/src/text.rs2
-rw-r--r--widget/src/text_input.rs40
-rw-r--r--widget/src/toggler.rs16
-rw-r--r--widget/src/tooltip.rs33
-rw-r--r--widget/src/vertical_slider.rs62
-rw-r--r--winit/Cargo.toml7
-rw-r--r--winit/src/application.rs51
-rw-r--r--winit/src/application/state.rs29
-rw-r--r--winit/src/conversion.rs17
-rw-r--r--winit/src/lib.rs7
-rw-r--r--winit/src/settings.rs24
-rw-r--r--winit/src/settings/windows.rs3
181 files changed, 4960 insertions, 3212 deletions
diff --git a/.github/ISSUE_TEMPLATE/BUG-REPORT.yml b/.github/ISSUE_TEMPLATE/BUG-REPORT.yml
index e93a01ae..d4c94fcd 100644
--- a/.github/ISSUE_TEMPLATE/BUG-REPORT.yml
+++ b/.github/ISSUE_TEMPLATE/BUG-REPORT.yml
@@ -62,15 +62,15 @@ body:
If you are using an older release, please upgrade to the latest one before filing an issue.
options:
+ - crates.io release
- master
- - 0.7
validations:
required: true
- type: dropdown
id: os
attributes:
- label: Operative System
- description: Which operative system are you using?
+ label: Operating System
+ description: Which operating system are you using?
options:
- Windows
- macOS
diff --git a/.github/workflows/document.yml b/.github/workflows/document.yml
index e69f4d63..09a7a4d5 100644
--- a/.github/workflows/document.yml
+++ b/.github/workflows/document.yml
@@ -20,13 +20,13 @@ jobs:
-p iced_core \
-p iced_style \
-p iced_futures \
- -p iced_native \
- -p iced_lazy \
+ -p iced_runtime \
-p iced_graphics \
-p iced_wgpu \
- -p iced_glow \
+ -p iced_tiny_skia \
+ -p iced_renderer \
+ -p iced_widget \
-p iced_winit \
- -p iced_glutin \
-p iced
- name: Write CNAME file
run: echo 'docs.iced.rs' > ./target/doc/CNAME
diff --git a/Cargo.toml b/Cargo.toml
index 2f92f9db..070cd0ce 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -12,11 +12,9 @@ keywords = ["gui", "ui", "graphics", "interface", "widgets"]
categories = ["gui"]
[features]
-default = ["wgpu", "tiny-skia"]
+default = ["wgpu"]
# Enable the `wgpu` GPU-accelerated renderer backend
wgpu = ["iced_renderer/wgpu"]
-# Enable the `tiny-skia` software renderer backend
-tiny-skia = ["iced_renderer/tiny-skia"]
# Enables the `Image` widget
image = ["iced_widget/image", "image_rs"]
# Enables the `Svg` widget
@@ -39,6 +37,8 @@ smol = ["iced_futures/smol"]
palette = ["iced_core/palette"]
# Enables querying system information
system = ["iced_winit/system"]
+# Enables broken "sRGB linear" blending to reproduce color management of the Web
+web-colors = ["iced_renderer/web-colors"]
# Enables the advanced module
advanced = []
diff --git a/DEPENDENCIES.md b/DEPENDENCIES.md
new file mode 100644
index 00000000..809371cb
--- /dev/null
+++ b/DEPENDENCIES.md
@@ -0,0 +1,33 @@
+# Dependencies
+
+Iced requires some system dependencies to work, and not
+all operating systems come with them installed.
+
+You can follow the provided instructions for your system to
+get them, if your system isn't here, add it!
+
+## NixOS
+
+You can add this `shell.nix` to your project and use it by running `nix-shell`:
+
+```nix
+{ pkgs ? import <nixpkgs> {} }:
+
+pkgs.mkShell rec {
+ buildInputs = with pkgs; [
+ expat
+ fontconfig
+ freetype
+ freetype.dev
+ libGL
+ pkgconfig
+ xorg.libX11
+ xorg.libXcursor
+ xorg.libXi
+ xorg.libXrandr
+ ];
+
+ LD_LIBRARY_PATH =
+ builtins.foldl' (a: b: "${a}:${b}/lib") "${pkgs.vulkan-loader}/lib" buildInputs;
+}
+```
diff --git a/README.md b/README.md
index fe2a4a64..c72fd770 100644
--- a/README.md
+++ b/README.md
@@ -35,9 +35,9 @@ Inspired by [Elm].
* First-class support for async actions (use futures!)
* [Modular ecosystem] split into reusable parts:
* A [renderer-agnostic native runtime] enabling integration with existing systems
- * Two [built-in renderers] leveraging [`wgpu`] and [`glow`]
+ * Two [built-in renderers] leveraging [`wgpu`] and [`tiny-skia`]
* [`iced_wgpu`] supporting Vulkan, Metal and DX12
- * [`iced_glow`] supporting OpenGL 2.1+ and OpenGL ES 2.0+
+ * [`iced_tiny_skia`] offering a software alternative as a fallback
* A [windowing shell]
* A [web runtime] leveraging the DOM
@@ -52,9 +52,9 @@ __Iced is currently experimental software.__ [Take a look at the roadmap],
[Modular ecosystem]: ECOSYSTEM.md
[renderer-agnostic native runtime]: native/
[`wgpu`]: https://github.com/gfx-rs/wgpu
-[`glow`]: https://github.com/grovesNL/glow
+[`tiny-skia`]: https://github.com/RazrFalcon/tiny-skia
[`iced_wgpu`]: wgpu/
-[`iced_glow`]: glow/
+[`iced_tiny_skia`]: tiny_skia/
[built-in renderers]: ECOSYSTEM.md#Renderers
[windowing shell]: winit/
[`dodrio`]: https://github.com/fitzgen/dodrio
@@ -196,34 +196,6 @@ end-user-oriented GUI library, while keeping [the ecosystem] modular:
[`ggez`]: https://github.com/ggez/ggez
[the ecosystem]: ECOSYSTEM.md
-## Troubleshooting
-
-### `GraphicsAdapterNotFound`
-
-This occurs when the selected [built-in renderer] is not able to create a context.
-
-Often this will occur while using [`iced_wgpu`] as the renderer without
-supported hardware (needs Vulkan, Metal or DX12). In this case, you could try using the
-[`iced_glow`] renderer:
-
-First, check if it works with
-
-```console
-cargo run --features iced/glow --package game_of_life
-```
-
-and then use it in your project with
-
-```toml
-iced = { version = "0.9", default-features = false, features = ["glow"] }
-```
-
-__NOTE:__ Chances are you have hardware that supports at least OpenGL 2.1 or OpenGL ES 2.0,
-but if you don't, right now there's no software fallback, so it means your hardware
-doesn't support Iced.
-
-[built-in renderer]: https://github.com/iced-rs/iced/blob/master/ECOSYSTEM.md#Renderers
-
## Contributing / Feedback
Contributions are greatly appreciated! If you want to contribute, please
diff --git a/core/Cargo.toml b/core/Cargo.toml
index 92d9773f..55f2e85f 100644
--- a/core/Cargo.toml
+++ b/core/Cargo.toml
@@ -10,11 +10,15 @@ repository = "https://github.com/iced-rs/iced"
[dependencies]
bitflags = "1.2"
thiserror = "1"
+log = "0.4.17"
twox-hash = { version = "1.5", default-features = false }
[dependencies.palette]
-version = "0.6"
+version = "0.7"
optional = true
[target.'cfg(target_arch = "wasm32")'.dependencies]
instant = "0.1"
+
+[dev-dependencies]
+approx = "0.5"
diff --git a/core/src/angle.rs b/core/src/angle.rs
new file mode 100644
index 00000000..75a57c76
--- /dev/null
+++ b/core/src/angle.rs
@@ -0,0 +1,33 @@
+use crate::{Point, Rectangle, Vector};
+use std::f32::consts::PI;
+
+#[derive(Debug, Copy, Clone, PartialEq)]
+/// Degrees
+pub struct Degrees(pub f32);
+
+#[derive(Debug, Copy, Clone, PartialEq)]
+/// Radians
+pub struct Radians(pub f32);
+
+impl From<Degrees> for Radians {
+ fn from(degrees: Degrees) -> Self {
+ Radians(degrees.0 * PI / 180.0)
+ }
+}
+
+impl Radians {
+ /// Calculates the line in which the [`Angle`] intercepts the `bounds`.
+ pub fn to_distance(&self, bounds: &Rectangle) -> (Point, Point) {
+ let v1 = Vector::new(f32::cos(self.0), f32::sin(self.0));
+
+ let distance_to_rect = f32::min(
+ f32::abs((bounds.y - bounds.center().y) / v1.y),
+ f32::abs(((bounds.x + bounds.width) - bounds.center().x) / v1.x),
+ );
+
+ let start = bounds.center() + v1 * distance_to_rect;
+ let end = bounds.center() - v1 * distance_to_rect;
+
+ (start, end)
+ }
+}
diff --git a/core/src/background.rs b/core/src/background.rs
index cfb95867..347c52c0 100644
--- a/core/src/background.rs
+++ b/core/src/background.rs
@@ -1,11 +1,14 @@
+use crate::gradient::{self, Gradient};
use crate::Color;
/// The background of some element.
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum Background {
- /// A solid color
+ /// A solid color.
Color(Color),
- // TODO: Add gradient and image variants
+ /// Linearly interpolate between several colors.
+ Gradient(Gradient),
+ // TODO: Add image variant
}
impl From<Color> for Background {
@@ -14,8 +17,14 @@ impl From<Color> for Background {
}
}
-impl From<Color> for Option<Background> {
- fn from(color: Color) -> Self {
- Some(Background::from(color))
+impl From<Gradient> for Background {
+ fn from(gradient: Gradient) -> Self {
+ Background::Gradient(gradient)
+ }
+}
+
+impl From<gradient::Linear> for Background {
+ fn from(gradient: gradient::Linear) -> Self {
+ Background::Gradient(Gradient::Linear(gradient))
}
}
diff --git a/core/src/border_radius.rs b/core/src/border_radius.rs
new file mode 100644
index 00000000..a444dd74
--- /dev/null
+++ b/core/src/border_radius.rs
@@ -0,0 +1,22 @@
+/// The border radii for the corners of a graphics primitive in the order:
+/// top-left, top-right, bottom-right, bottom-left.
+#[derive(Debug, Clone, Copy, PartialEq, Default)]
+pub struct BorderRadius([f32; 4]);
+
+impl From<f32> for BorderRadius {
+ fn from(w: f32) -> Self {
+ Self([w; 4])
+ }
+}
+
+impl From<[f32; 4]> for BorderRadius {
+ fn from(radi: [f32; 4]) -> Self {
+ Self(radi)
+ }
+}
+
+impl From<BorderRadius> for [f32; 4] {
+ fn from(radi: BorderRadius) -> Self {
+ radi.0
+ }
+}
diff --git a/core/src/color.rs b/core/src/color.rs
index fe0a1856..1392f28b 100644
--- a/core/src/color.rs
+++ b/core/src/color.rs
@@ -183,15 +183,15 @@ macro_rules! color {
}
#[cfg(feature = "palette")]
-/// Converts from palette's `Srgba` type to a [`Color`].
+/// Converts from palette's `Rgba` type to a [`Color`].
impl From<Srgba> for Color {
- fn from(srgba: Srgba) -> Self {
- Color::new(srgba.red, srgba.green, srgba.blue, srgba.alpha)
+ fn from(rgba: Srgba) -> Self {
+ Color::new(rgba.red, rgba.green, rgba.blue, rgba.alpha)
}
}
#[cfg(feature = "palette")]
-/// Converts from [`Color`] to palette's `Srgba` type.
+/// Converts from [`Color`] to palette's `Rgba` type.
impl From<Color> for Srgba {
fn from(c: Color) -> Self {
Srgba::new(c.r, c.g, c.b, c.a)
@@ -199,15 +199,15 @@ impl From<Color> for Srgba {
}
#[cfg(feature = "palette")]
-/// Converts from palette's `Srgb` type to a [`Color`].
+/// Converts from palette's `Rgb` type to a [`Color`].
impl From<Srgb> for Color {
- fn from(srgb: Srgb) -> Self {
- Color::new(srgb.red, srgb.green, srgb.blue, 1.0)
+ fn from(rgb: Srgb) -> Self {
+ Color::new(rgb.red, rgb.green, rgb.blue, 1.0)
}
}
#[cfg(feature = "palette")]
-/// Converts from [`Color`] to palette's `Srgb` type.
+/// Converts from [`Color`] to palette's `Rgb` type.
impl From<Color> for Srgb {
fn from(c: Color) -> Self {
Srgb::new(c.r, c.g, c.b)
@@ -218,12 +218,12 @@ impl From<Color> for Srgb {
#[cfg(test)]
mod tests {
use super::*;
- use palette::Blend;
+ use palette::blend::Blend;
#[test]
fn srgba_traits() {
let c = Color::from_rgb(0.5, 0.4, 0.3);
- // Round-trip conversion to the palette:Srgba type
+ // Round-trip conversion to the palette::Srgba type
let s: Srgba = c.into();
let r: Color = s.into();
assert_eq!(c, r);
@@ -231,6 +231,8 @@ mod tests {
#[test]
fn color_manipulation() {
+ use approx::assert_relative_eq;
+
let c1 = Color::from_rgb(0.5, 0.4, 0.3);
let c2 = Color::from_rgb(0.2, 0.5, 0.3);
@@ -238,19 +240,15 @@ mod tests {
let l1 = Srgba::from(c1).into_linear();
let l2 = Srgba::from(c2).into_linear();
- // Take the lighter of each of the RGB components
+ // Take the lighter of each of the sRGB components
let lighter = l1.lighten(l2);
// Convert back to our Color
- let r: Color = Srgba::from_linear(lighter).into();
- assert_eq!(
- r,
- Color {
- r: 0.5,
- g: 0.5,
- b: 0.3,
- a: 1.0
- }
- );
+ let result: Color = Srgba::from_linear(lighter).into();
+
+ assert_relative_eq!(result.r, 0.5);
+ assert_relative_eq!(result.g, 0.5);
+ assert_relative_eq!(result.b, 0.3);
+ assert_relative_eq!(result.a, 1.0);
}
}
diff --git a/core/src/element.rs b/core/src/element.rs
index 98c53737..3268f14b 100644
--- a/core/src/element.rs
+++ b/core/src/element.rs
@@ -5,9 +5,7 @@ use crate::overlay;
use crate::renderer;
use crate::widget;
use crate::widget::tree::{self, Tree};
-use crate::{
- Clipboard, Color, Layout, Length, Point, Rectangle, Shell, Widget,
-};
+use crate::{Clipboard, Color, Layout, Length, Rectangle, Shell, Widget};
use std::any::Any;
use std::borrow::Borrow;
@@ -378,7 +376,7 @@ where
tree: &mut Tree,
event: Event,
layout: Layout<'_>,
- cursor_position: Point,
+ cursor: mouse::Cursor,
renderer: &Renderer,
clipboard: &mut dyn Clipboard,
shell: &mut Shell<'_, B>,
@@ -390,7 +388,7 @@ where
tree,
event,
layout,
- cursor_position,
+ cursor,
renderer,
clipboard,
&mut local_shell,
@@ -408,35 +406,23 @@ where
theme: &Renderer::Theme,
style: &renderer::Style,
layout: Layout<'_>,
- cursor_position: Point,
+ cursor: mouse::Cursor,
viewport: &Rectangle,
) {
- self.widget.draw(
- tree,
- renderer,
- theme,
- style,
- layout,
- cursor_position,
- viewport,
- )
+ self.widget
+ .draw(tree, renderer, theme, style, layout, cursor, viewport)
}
fn mouse_interaction(
&self,
tree: &Tree,
layout: Layout<'_>,
- cursor_position: Point,
+ cursor: mouse::Cursor,
viewport: &Rectangle,
renderer: &Renderer,
) -> mouse::Interaction {
- self.widget.mouse_interaction(
- tree,
- layout,
- cursor_position,
- viewport,
- renderer,
- )
+ self.widget
+ .mouse_interaction(tree, layout, cursor, viewport, renderer)
}
fn overlay<'b>(
@@ -521,20 +507,14 @@ where
state: &mut Tree,
event: Event,
layout: Layout<'_>,
- cursor_position: Point,
+ cursor: mouse::Cursor,
renderer: &Renderer,
clipboard: &mut dyn Clipboard,
shell: &mut Shell<'_, Message>,
) -> event::Status {
- self.element.widget.on_event(
- state,
- event,
- layout,
- cursor_position,
- renderer,
- clipboard,
- shell,
- )
+ self.element
+ .widget
+ .on_event(state, event, layout, cursor, renderer, clipboard, shell)
}
fn draw(
@@ -544,7 +524,7 @@ where
theme: &Renderer::Theme,
style: &renderer::Style,
layout: Layout<'_>,
- cursor_position: Point,
+ cursor: mouse::Cursor,
viewport: &Rectangle,
) {
fn explain_layout<Renderer: crate::Renderer>(
@@ -567,15 +547,9 @@ where
}
}
- self.element.widget.draw(
- state,
- renderer,
- theme,
- style,
- layout,
- cursor_position,
- viewport,
- );
+ self.element
+ .widget
+ .draw(state, renderer, theme, style, layout, cursor, viewport);
explain_layout(renderer, self.color, layout);
}
@@ -584,17 +558,13 @@ where
&self,
state: &Tree,
layout: Layout<'_>,
- cursor_position: Point,
+ cursor: mouse::Cursor,
viewport: &Rectangle,
renderer: &Renderer,
) -> mouse::Interaction {
- self.element.widget.mouse_interaction(
- state,
- layout,
- cursor_position,
- viewport,
- renderer,
- )
+ self.element
+ .widget
+ .mouse_interaction(state, layout, cursor, viewport, renderer)
}
fn overlay<'b>(
diff --git a/core/src/gradient.rs b/core/src/gradient.rs
index 61e919d6..e19622fb 100644
--- a/core/src/gradient.rs
+++ b/core/src/gradient.rs
@@ -1,27 +1,40 @@
-//! For creating a Gradient.
-pub mod linear;
+//! Colors that transition progressively.
+use crate::{Color, Radians};
-pub use linear::Linear;
+use std::cmp::Ordering;
-use crate::{Color, Point, Size};
-
-#[derive(Debug, Clone, PartialEq)]
+#[derive(Debug, Clone, Copy, PartialEq)]
/// A fill which transitions colors progressively along a direction, either linearly, radially (TBD),
/// or conically (TBD).
+///
+/// For a gradient which can be used as a fill on a canvas, see [`iced_graphics::Gradient`].
pub enum Gradient {
- /// A linear gradient interpolates colors along a direction from its `start` to its `end`
- /// point.
+ /// A linear gradient interpolates colors along a direction at a specific [`Angle`].
Linear(Linear),
}
impl Gradient {
- /// Creates a new linear [`linear::Builder`].
- pub fn linear(position: impl Into<Position>) -> linear::Builder {
- linear::Builder::new(position.into())
+ /// Adjust the opacity of the gradient by a multiplier applied to each color stop.
+ pub fn mul_alpha(mut self, alpha_multiplier: f32) -> Self {
+ match &mut self {
+ Gradient::Linear(linear) => {
+ for stop in linear.stops.iter_mut().flatten() {
+ stop.color.a *= alpha_multiplier;
+ }
+ }
+ }
+
+ self
}
}
-#[derive(Debug, Clone, Copy, PartialEq)]
+impl From<Linear> for Gradient {
+ fn from(gradient: Linear) -> Self {
+ Self::Linear(gradient)
+ }
+}
+
+#[derive(Debug, Default, Clone, Copy, PartialEq)]
/// A point along the gradient vector where the specified [`color`] is unmixed.
///
/// [`color`]: Self::color
@@ -35,83 +48,58 @@ pub struct ColorStop {
pub color: Color,
}
-#[derive(Debug)]
-/// The position of the gradient within its bounds.
-pub enum Position {
- /// The gradient will be positioned with respect to two points.
- Absolute {
- /// The starting point of the gradient.
- start: Point,
- /// The ending point of the gradient.
- end: Point,
- },
- /// The gradient will be positioned relative to the provided bounds.
- Relative {
- /// The top left position of the bounds.
- top_left: Point,
- /// The width & height of the bounds.
- size: Size,
- /// The start [Location] of the gradient.
- start: Location,
- /// The end [Location] of the gradient.
- end: Location,
- },
+/// A linear gradient.
+#[derive(Debug, Clone, Copy, PartialEq)]
+pub struct Linear {
+ /// How the [`Gradient`] is angled within its bounds.
+ pub angle: Radians,
+ /// [`ColorStop`]s along the linear gradient path.
+ pub stops: [Option<ColorStop>; 8],
}
-impl From<(Point, Point)> for Position {
- fn from((start, end): (Point, Point)) -> Self {
- Self::Absolute { start, end }
+impl Linear {
+ /// Creates a new [`Linear`] gradient with the given angle in [`Radians`].
+ pub fn new(angle: impl Into<Radians>) -> Self {
+ Self {
+ angle: angle.into(),
+ stops: [None; 8],
+ }
}
-}
-#[derive(Debug, Clone, Copy)]
-/// The location of a relatively-positioned gradient.
-pub enum Location {
- /// Top left.
- TopLeft,
- /// Top.
- Top,
- /// Top right.
- TopRight,
- /// Right.
- Right,
- /// Bottom right.
- BottomRight,
- /// Bottom.
- Bottom,
- /// Bottom left.
- BottomLeft,
- /// Left.
- Left,
-}
+ /// Adds a new [`ColorStop`], defined by an offset and a color, to the gradient.
+ ///
+ /// Any `offset` that is not within `0.0..=1.0` will be silently ignored.
+ ///
+ /// Any stop added after the 8th will be silently ignored.
+ pub fn add_stop(mut self, offset: f32, color: Color) -> Self {
+ if offset.is_finite() && (0.0..=1.0).contains(&offset) {
+ let (Ok(index) | Err(index)) =
+ self.stops.binary_search_by(|stop| match stop {
+ None => Ordering::Greater,
+ Some(stop) => stop.offset.partial_cmp(&offset).unwrap(),
+ });
-impl Location {
- fn to_absolute(self, top_left: Point, size: Size) -> Point {
- match self {
- Location::TopLeft => top_left,
- Location::Top => {
- Point::new(top_left.x + size.width / 2.0, top_left.y)
- }
- Location::TopRight => {
- Point::new(top_left.x + size.width, top_left.y)
- }
- Location::Right => Point::new(
- top_left.x + size.width,
- top_left.y + size.height / 2.0,
- ),
- Location::BottomRight => {
- Point::new(top_left.x + size.width, top_left.y + size.height)
- }
- Location::Bottom => Point::new(
- top_left.x + size.width / 2.0,
- top_left.y + size.height,
- ),
- Location::BottomLeft => {
- Point::new(top_left.x, top_left.y + size.height)
- }
- Location::Left => {
- Point::new(top_left.x, top_left.y + size.height / 2.0)
+ if index < 8 {
+ self.stops[index] = Some(ColorStop { offset, color });
}
+ } else {
+ log::warn!("Gradient color stop must be within 0.0..=1.0 range.");
+ };
+
+ self
+ }
+
+ /// Adds multiple [`ColorStop`]s to the gradient.
+ ///
+ /// Any stop added after the 8th will be silently ignored.
+ pub fn add_stops(
+ mut self,
+ stops: impl IntoIterator<Item = ColorStop>,
+ ) -> Self {
+ for stop in stops.into_iter() {
+ self = self.add_stop(stop.offset, stop.color)
}
+
+ self
}
}
diff --git a/core/src/gradient/linear.rs b/core/src/gradient/linear.rs
deleted file mode 100644
index c886db47..00000000
--- a/core/src/gradient/linear.rs
+++ /dev/null
@@ -1,112 +0,0 @@
-//! Linear gradient builder & definition.
-use crate::gradient::{ColorStop, Gradient, Position};
-use crate::{Color, Point};
-
-/// 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.
- pub start: Point,
- /// The point where the linear gradient ends.
- pub end: Point,
- /// [`ColorStop`]s along the linear gradient path.
- pub color_stops: Vec<ColorStop>,
-}
-
-/// A [`Linear`] builder.
-#[derive(Debug)]
-pub struct Builder {
- start: Point,
- end: Point,
- stops: Vec<ColorStop>,
- error: Option<BuilderError>,
-}
-
-impl Builder {
- /// Creates a new [`Builder`].
- pub fn new(position: Position) -> Self {
- let (start, end) = match position {
- Position::Absolute { start, end } => (start, end),
- Position::Relative {
- top_left,
- size,
- start,
- end,
- } => (
- start.to_absolute(top_left, size),
- end.to_absolute(top_left, size),
- ),
- };
-
- Self {
- start,
- end,
- stops: vec![],
- error: None,
- }
- }
-
- /// Adds a new stop, defined by an offset and a color, to the gradient.
- ///
- /// `offset` must be between `0.0` and `1.0` or the gradient cannot be built.
- ///
- /// Note: when using the [`glow`] backend, any color stop added after the 16th
- /// will not be displayed.
- ///
- /// On the [`wgpu`] backend this limitation does not exist (technical limit is 524,288 stops).
- ///
- /// [`glow`]: https://docs.rs/iced_glow
- /// [`wgpu`]: https://docs.rs/iced_wgpu
- pub fn add_stop(mut self, offset: f32, color: Color) -> Self {
- if offset.is_finite() && (0.0..=1.0).contains(&offset) {
- match self.stops.binary_search_by(|stop| {
- stop.offset.partial_cmp(&offset).unwrap()
- }) {
- Ok(_) => {
- self.error = Some(BuilderError::DuplicateOffset(offset))
- }
- Err(index) => {
- self.stops.insert(index, ColorStop { offset, color });
- }
- }
- } else {
- self.error = Some(BuilderError::InvalidOffset(offset))
- };
-
- self
- }
-
- /// Builds the linear [`Gradient`] of this [`Builder`].
- ///
- /// Returns `BuilderError` if gradient in invalid.
- pub fn build(self) -> Result<Gradient, BuilderError> {
- if self.stops.is_empty() {
- Err(BuilderError::MissingColorStop)
- } else if let Some(error) = self.error {
- Err(error)
- } else {
- Ok(Gradient::Linear(Linear {
- start: self.start,
- end: self.end,
- color_stops: self.stops,
- }))
- }
- }
-}
-
-/// An error that happened when building a [`Linear`] gradient.
-#[derive(Debug, thiserror::Error)]
-pub enum BuilderError {
- #[error("Gradients must contain at least one color stop.")]
- /// Gradients must contain at least one color stop.
- MissingColorStop,
- #[error("Offset {0} must be a unique, finite number.")]
- /// Offsets in a gradient must all be unique & finite.
- DuplicateOffset(f32),
- #[error("Offset {0} must be between 0.0..=1.0.")]
- /// Offsets in a gradient must be between 0.0..=1.0.
- InvalidOffset(f32),
-}
diff --git a/core/src/lib.rs b/core/src/lib.rs
index 89dfb828..76d775e7 100644
--- a/core/src/lib.rs
+++ b/core/src/lib.rs
@@ -42,7 +42,9 @@ pub mod touch;
pub mod widget;
pub mod window;
+mod angle;
mod background;
+mod border_radius;
mod color;
mod content_fit;
mod element;
@@ -57,7 +59,9 @@ mod size;
mod vector;
pub use alignment::Alignment;
+pub use angle::{Degrees, Radians};
pub use background::Background;
+pub use border_radius::BorderRadius;
pub use clipboard::Clipboard;
pub use color::Color;
pub use content_fit::ContentFit;
diff --git a/core/src/mouse.rs b/core/src/mouse.rs
index 0c405ce6..d13a60fb 100644
--- a/core/src/mouse.rs
+++ b/core/src/mouse.rs
@@ -2,10 +2,12 @@
pub mod click;
mod button;
+mod cursor;
mod event;
mod interaction;
pub use button::Button;
pub use click::Click;
+pub use cursor::Cursor;
pub use event::{Event, ScrollDelta};
pub use interaction::Interaction;
diff --git a/core/src/mouse/button.rs b/core/src/mouse/button.rs
index aeb8a55d..3eec7f42 100644
--- a/core/src/mouse/button.rs
+++ b/core/src/mouse/button.rs
@@ -11,5 +11,5 @@ pub enum Button {
Middle,
/// Some other button.
- Other(u8),
+ Other(u16),
}
diff --git a/core/src/mouse/cursor.rs b/core/src/mouse/cursor.rs
new file mode 100644
index 00000000..203526e9
--- /dev/null
+++ b/core/src/mouse/cursor.rs
@@ -0,0 +1,52 @@
+use crate::{Point, Rectangle, Vector};
+
+/// The mouse cursor state.
+#[derive(Debug, Clone, Copy, PartialEq, Default)]
+pub enum Cursor {
+ /// The cursor has a defined position.
+ Available(Point),
+
+ /// The cursor is currently unavailable (i.e. out of bounds or busy).
+ #[default]
+ Unavailable,
+}
+
+impl Cursor {
+ /// Returns the absolute position of the [`Cursor`], if available.
+ pub fn position(self) -> Option<Point> {
+ match self {
+ Cursor::Available(position) => Some(position),
+ Cursor::Unavailable => None,
+ }
+ }
+
+ /// Returns the absolute position of the [`Cursor`], if available and inside
+ /// the given bounds.
+ ///
+ /// If the [`Cursor`] is not over the provided bounds, this method will
+ /// return `None`.
+ pub fn position_over(self, bounds: Rectangle) -> Option<Point> {
+ self.position().filter(|p| bounds.contains(*p))
+ }
+
+ /// Returns the relative position of the [`Cursor`] inside the given bounds,
+ /// if available.
+ ///
+ /// If the [`Cursor`] is not over the provided bounds, this method will
+ /// return `None`.
+ pub fn position_in(self, bounds: Rectangle) -> Option<Point> {
+ self.position_over(bounds)
+ .map(|p| p - Vector::new(bounds.x, bounds.y))
+ }
+
+ /// Returns the relative position of the [`Cursor`] from the given origin,
+ /// if available.
+ pub fn position_from(self, origin: Point) -> Option<Point> {
+ self.position().map(|p| p - Vector::new(origin.x, origin.y))
+ }
+
+ /// Returns true if the [`Cursor`] is over the given `bounds`.
+ pub fn is_over(self, bounds: Rectangle) -> bool {
+ self.position_over(bounds).is_some()
+ }
+}
diff --git a/core/src/overlay.rs b/core/src/overlay.rs
index b9f3c735..2e05db93 100644
--- a/core/src/overlay.rs
+++ b/core/src/overlay.rs
@@ -38,7 +38,7 @@ where
theme: &Renderer::Theme,
style: &renderer::Style,
layout: Layout<'_>,
- cursor_position: Point,
+ cursor: mouse::Cursor,
);
/// Applies a [`widget::Operation`] to the [`Overlay`].
@@ -66,7 +66,7 @@ where
&mut self,
_event: Event,
_layout: Layout<'_>,
- _cursor_position: Point,
+ _cursor: mouse::Cursor,
_renderer: &Renderer,
_clipboard: &mut dyn Clipboard,
_shell: &mut Shell<'_, Message>,
@@ -80,7 +80,7 @@ where
fn mouse_interaction(
&self,
_layout: Layout<'_>,
- _cursor_position: Point,
+ _cursor: mouse::Cursor,
_viewport: &Rectangle,
_renderer: &Renderer,
) -> mouse::Interaction {
@@ -91,9 +91,23 @@ where
///
/// By default, it returns true if the bounds of the `layout` contain
/// the `cursor_position`.
- fn is_over(&self, layout: Layout<'_>, cursor_position: Point) -> bool {
+ fn is_over(
+ &self,
+ layout: Layout<'_>,
+ _renderer: &Renderer,
+ cursor_position: Point,
+ ) -> bool {
layout.bounds().contains(cursor_position)
}
+
+ /// Returns the nested overlay of the [`Overlay`], if there is any.
+ fn overlay<'a>(
+ &'a mut self,
+ _layout: Layout<'_>,
+ _renderer: &Renderer,
+ ) -> Option<Element<'a, Message, Renderer>> {
+ None
+ }
}
/// Returns a [`Group`] of overlay [`Element`] children.
diff --git a/core/src/overlay/element.rs b/core/src/overlay/element.rs
index 237d25d1..c2134343 100644
--- a/core/src/overlay/element.rs
+++ b/core/src/overlay/element.rs
@@ -68,35 +68,25 @@ where
&mut self,
event: Event,
layout: Layout<'_>,
- cursor_position: Point,
+ cursor: mouse::Cursor,
renderer: &Renderer,
clipboard: &mut dyn Clipboard,
shell: &mut Shell<'_, Message>,
) -> event::Status {
- self.overlay.on_event(
- event,
- layout,
- cursor_position,
- renderer,
- clipboard,
- shell,
- )
+ self.overlay
+ .on_event(event, layout, cursor, renderer, clipboard, shell)
}
/// Returns the current [`mouse::Interaction`] of the [`Element`].
pub fn mouse_interaction(
&self,
layout: Layout<'_>,
- cursor_position: Point,
+ cursor: mouse::Cursor,
viewport: &Rectangle,
renderer: &Renderer,
) -> mouse::Interaction {
- self.overlay.mouse_interaction(
- layout,
- cursor_position,
- viewport,
- renderer,
- )
+ self.overlay
+ .mouse_interaction(layout, cursor, viewport, renderer)
}
/// Draws the [`Element`] and its children using the given [`Layout`].
@@ -106,10 +96,9 @@ where
theme: &Renderer::Theme,
style: &renderer::Style,
layout: Layout<'_>,
- cursor_position: Point,
+ cursor: mouse::Cursor,
) {
- self.overlay
- .draw(renderer, theme, style, layout, cursor_position)
+ self.overlay.draw(renderer, theme, style, layout, cursor)
}
/// Applies a [`widget::Operation`] to the [`Element`].
@@ -123,8 +112,22 @@ where
}
/// Returns true if the cursor is over the [`Element`].
- pub fn is_over(&self, layout: Layout<'_>, cursor_position: Point) -> bool {
- self.overlay.is_over(layout, cursor_position)
+ pub fn is_over(
+ &self,
+ layout: Layout<'_>,
+ renderer: &Renderer,
+ cursor_position: Point,
+ ) -> bool {
+ self.overlay.is_over(layout, renderer, cursor_position)
+ }
+
+ /// Returns the nested overlay of the [`Element`], if there is any.
+ pub fn overlay<'b>(
+ &'b mut self,
+ layout: Layout<'_>,
+ renderer: &Renderer,
+ ) -> Option<Element<'b, Message, Renderer>> {
+ self.overlay.overlay(layout, renderer)
}
}
@@ -215,7 +218,7 @@ where
&mut self,
event: Event,
layout: Layout<'_>,
- cursor_position: Point,
+ cursor: mouse::Cursor,
renderer: &Renderer,
clipboard: &mut dyn Clipboard,
shell: &mut Shell<'_, B>,
@@ -226,7 +229,7 @@ where
let event_status = self.content.on_event(
event,
layout,
- cursor_position,
+ cursor,
renderer,
clipboard,
&mut local_shell,
@@ -240,16 +243,12 @@ where
fn mouse_interaction(
&self,
layout: Layout<'_>,
- cursor_position: Point,
+ cursor: mouse::Cursor,
viewport: &Rectangle,
renderer: &Renderer,
) -> mouse::Interaction {
- self.content.mouse_interaction(
- layout,
- cursor_position,
- viewport,
- renderer,
- )
+ self.content
+ .mouse_interaction(layout, cursor, viewport, renderer)
}
fn draw(
@@ -258,13 +257,27 @@ where
theme: &Renderer::Theme,
style: &renderer::Style,
layout: Layout<'_>,
- cursor_position: Point,
+ cursor: mouse::Cursor,
) {
- self.content
- .draw(renderer, theme, style, layout, cursor_position)
+ self.content.draw(renderer, theme, style, layout, cursor)
}
- fn is_over(&self, layout: Layout<'_>, cursor_position: Point) -> bool {
- self.content.is_over(layout, cursor_position)
+ fn is_over(
+ &self,
+ layout: Layout<'_>,
+ renderer: &Renderer,
+ cursor_position: Point,
+ ) -> bool {
+ self.content.is_over(layout, renderer, cursor_position)
+ }
+
+ fn overlay<'b>(
+ &'b mut self,
+ layout: Layout<'_>,
+ renderer: &Renderer,
+ ) -> Option<Element<'b, B, Renderer>> {
+ self.content
+ .overlay(layout, renderer)
+ .map(|overlay| overlay.map(self.mapper))
}
}
diff --git a/core/src/overlay/group.rs b/core/src/overlay/group.rs
index 0c48df34..deffaad0 100644
--- a/core/src/overlay/group.rs
+++ b/core/src/overlay/group.rs
@@ -81,7 +81,7 @@ where
&mut self,
event: Event,
layout: Layout<'_>,
- cursor_position: Point,
+ cursor: mouse::Cursor,
renderer: &Renderer,
clipboard: &mut dyn Clipboard,
shell: &mut Shell<'_, Message>,
@@ -93,7 +93,7 @@ where
child.on_event(
event.clone(),
layout,
- cursor_position,
+ cursor,
renderer,
clipboard,
shell,
@@ -108,17 +108,17 @@ where
theme: &<Renderer as crate::Renderer>::Theme,
style: &renderer::Style,
layout: Layout<'_>,
- cursor_position: Point,
+ cursor: mouse::Cursor,
) {
for (child, layout) in self.children.iter().zip(layout.children()) {
- child.draw(renderer, theme, style, layout, cursor_position);
+ child.draw(renderer, theme, style, layout, cursor);
}
}
fn mouse_interaction(
&self,
layout: Layout<'_>,
- cursor_position: Point,
+ cursor: mouse::Cursor,
viewport: &Rectangle,
renderer: &Renderer,
) -> mouse::Interaction {
@@ -126,12 +126,7 @@ where
.iter()
.zip(layout.children())
.map(|(child, layout)| {
- child.mouse_interaction(
- layout,
- cursor_position,
- viewport,
- renderer,
- )
+ child.mouse_interaction(layout, cursor, viewport, renderer)
})
.max()
.unwrap_or_default()
@@ -152,11 +147,33 @@ where
});
}
- fn is_over(&self, layout: Layout<'_>, cursor_position: Point) -> bool {
+ fn is_over(
+ &self,
+ layout: Layout<'_>,
+ renderer: &Renderer,
+ cursor_position: Point,
+ ) -> bool {
self.children
.iter()
.zip(layout.children())
- .any(|(child, layout)| child.is_over(layout, cursor_position))
+ .any(|(child, layout)| {
+ child.is_over(layout, renderer, cursor_position)
+ })
+ }
+
+ fn overlay<'b>(
+ &'b mut self,
+ layout: Layout<'_>,
+ renderer: &Renderer,
+ ) -> Option<overlay::Element<'b, Message, Renderer>> {
+ let children = self
+ .children
+ .iter_mut()
+ .zip(layout.children())
+ .filter_map(|(child, layout)| child.overlay(layout, renderer))
+ .collect::<Vec<_>>();
+
+ (!children.is_empty()).then(|| Group::with_children(children).overlay())
}
}
diff --git a/core/src/renderer.rs b/core/src/renderer.rs
index d6247e39..7c73d2e4 100644
--- a/core/src/renderer.rs
+++ b/core/src/renderer.rs
@@ -6,7 +6,7 @@ mod null;
pub use null::Null;
use crate::layout;
-use crate::{Background, Color, Element, Rectangle, Vector};
+use crate::{Background, BorderRadius, Color, Element, Rectangle, Vector};
/// A component that can be used by widgets to draw themselves on a screen.
pub trait Renderer: Sized {
@@ -60,29 +60,6 @@ pub struct Quad {
pub border_color: Color,
}
-/// The border radi for the corners of a graphics primitive in the order:
-/// top-left, top-right, bottom-right, bottom-left.
-#[derive(Debug, Clone, Copy, PartialEq, Default)]
-pub struct BorderRadius([f32; 4]);
-
-impl From<f32> for BorderRadius {
- fn from(w: f32) -> Self {
- Self([w; 4])
- }
-}
-
-impl From<[f32; 4]> for BorderRadius {
- fn from(radi: [f32; 4]) -> Self {
- Self(radi)
- }
-}
-
-impl From<BorderRadius> for [f32; 4] {
- fn from(radi: BorderRadius) -> Self {
- radi.0
- }
-}
-
/// The styling attributes of a [`Renderer`].
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Style {
diff --git a/core/src/widget.rs b/core/src/widget.rs
index 769f8659..79d86444 100644
--- a/core/src/widget.rs
+++ b/core/src/widget.rs
@@ -15,7 +15,7 @@ use crate::layout::{self, Layout};
use crate::mouse;
use crate::overlay;
use crate::renderer;
-use crate::{Clipboard, Length, Point, Rectangle, Shell};
+use crate::{Clipboard, Length, Rectangle, Shell};
/// A component that displays information and allows interaction.
///
@@ -67,7 +67,7 @@ where
theme: &Renderer::Theme,
style: &renderer::Style,
layout: Layout<'_>,
- cursor_position: Point,
+ cursor: mouse::Cursor,
viewport: &Rectangle,
);
@@ -111,7 +111,7 @@ where
_state: &mut Tree,
_event: Event,
_layout: Layout<'_>,
- _cursor_position: Point,
+ _cursor: mouse::Cursor,
_renderer: &Renderer,
_clipboard: &mut dyn Clipboard,
_shell: &mut Shell<'_, Message>,
@@ -126,7 +126,7 @@ where
&self,
_state: &Tree,
_layout: Layout<'_>,
- _cursor_position: Point,
+ _cursor: mouse::Cursor,
_viewport: &Rectangle,
_renderer: &Renderer,
) -> mouse::Interaction {
diff --git a/core/src/widget/text.rs b/core/src/widget/text.rs
index 90af88b7..e934a2f5 100644
--- a/core/src/widget/text.rs
+++ b/core/src/widget/text.rs
@@ -1,12 +1,11 @@
//! Write some text for your users to read.
use crate::alignment;
use crate::layout;
+use crate::mouse;
use crate::renderer;
use crate::text;
use crate::widget::Tree;
-use crate::{
- Color, Element, Layout, Length, Pixels, Point, Rectangle, Size, Widget,
-};
+use crate::{Color, Element, Layout, Length, Pixels, Rectangle, Size, Widget};
use std::borrow::Cow;
@@ -163,7 +162,7 @@ where
theme: &Renderer::Theme,
style: &renderer::Style,
layout: Layout<'_>,
- _cursor_position: Point,
+ _cursor_position: mouse::Cursor,
_viewport: &Rectangle,
) {
draw(
diff --git a/core/src/window.rs b/core/src/window.rs
index 81bd7e3d..a6dbdfb4 100644
--- a/core/src/window.rs
+++ b/core/src/window.rs
@@ -2,12 +2,14 @@
pub mod icon;
mod event;
+mod level;
mod mode;
mod redraw_request;
mod user_attention;
pub use event::Event;
pub use icon::Icon;
+pub use level::Level;
pub use mode::Mode;
pub use redraw_request::RedrawRequest;
pub use user_attention::UserAttention;
diff --git a/core/src/window/level.rs b/core/src/window/level.rs
new file mode 100644
index 00000000..3878ecac
--- /dev/null
+++ b/core/src/window/level.rs
@@ -0,0 +1,19 @@
+/// A window level groups windows with respect to their z-position.
+///
+/// The relative ordering between windows in different window levels is fixed.
+/// The z-order of a window within the same window level may change dynamically
+/// on user interaction.
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
+pub enum Level {
+ /// The default behavior.
+ #[default]
+ Normal,
+
+ /// The window will always be below normal windows.
+ ///
+ /// This is useful for a widget-based app.
+ AlwaysOnBottom,
+
+ /// The window will always be on top of normal windows.
+ AlwaysOnTop,
+}
diff --git a/examples/README.md b/examples/README.md
index 74cf145b..111e8910 100644
--- a/examples/README.md
+++ b/examples/README.md
@@ -93,8 +93,7 @@ A bunch of simpler examples exist:
- [`download_progress`](download_progress), a basic application that asynchronously downloads a dummy file of 100 MB and tracks the download progress.
- [`events`](events), a log of native events displayed using a conditional `Subscription`.
- [`geometry`](geometry), a custom widget showcasing how to draw geometry with the `Mesh2D` primitive in [`iced_wgpu`](../wgpu).
-- [`integration_opengl`](integration_opengl), a demonstration of how to integrate Iced in an existing OpenGL application.
-- [`integration_wgpu`](integration_wgpu), a demonstration of how to integrate Iced in an existing [`wgpu`] application.
+- [`integration`](integration), a demonstration of how to integrate Iced in an existing [`wgpu`] application.
- [`pane_grid`](pane_grid), a grid of panes that can be split, resized, and reorganized.
- [`pick_list`](pick_list), a dropdown list of selectable options.
- [`pokedex`](pokedex), an application that displays a random Pokédex entry (sprite included!) by using the [PokéAPI].
diff --git a/examples/arc/src/main.rs b/examples/arc/src/main.rs
index 80ad0b5b..df565859 100644
--- a/examples/arc/src/main.rs
+++ b/examples/arc/src/main.rs
@@ -1,8 +1,9 @@
use std::{f32::consts::PI, time::Instant};
use iced::executor;
+use iced::mouse;
use iced::widget::canvas::{
- self, stroke, Cache, Canvas, Cursor, Geometry, Path, Stroke,
+ self, stroke, Cache, Canvas, Geometry, Path, Stroke,
};
use iced::{
Application, Command, Element, Length, Point, Rectangle, Renderer,
@@ -78,7 +79,7 @@ impl<Message> canvas::Program<Message> for Arc {
renderer: &Renderer,
theme: &Theme,
bounds: Rectangle,
- _cursor: Cursor,
+ _cursor: mouse::Cursor,
) -> Vec<Geometry> {
let geometry = self.cache.draw(renderer, bounds.size(), |frame| {
let palette = theme.palette();
diff --git a/examples/bezier_tool/src/main.rs b/examples/bezier_tool/src/main.rs
index f1c83a16..310be28f 100644
--- a/examples/bezier_tool/src/main.rs
+++ b/examples/bezier_tool/src/main.rs
@@ -61,9 +61,7 @@ impl Sandbox for Example {
mod bezier {
use iced::mouse;
use iced::widget::canvas::event::{self, Event};
- use iced::widget::canvas::{
- self, Canvas, Cursor, Frame, Geometry, Path, Stroke,
- };
+ use iced::widget::canvas::{self, Canvas, Frame, Geometry, Path, Stroke};
use iced::{Element, Length, Point, Rectangle, Renderer, Theme};
#[derive(Default)]
@@ -100,10 +98,10 @@ mod bezier {
state: &mut Self::State,
event: Event,
bounds: Rectangle,
- cursor: Cursor,
+ cursor: mouse::Cursor,
) -> (event::Status, Option<Curve>) {
let cursor_position =
- if let Some(position) = cursor.position_in(&bounds) {
+ if let Some(position) = cursor.position_in(bounds) {
position
} else {
return (event::Status::Ignored, None);
@@ -155,7 +153,7 @@ mod bezier {
renderer: &Renderer,
_theme: &Theme,
bounds: Rectangle,
- cursor: Cursor,
+ cursor: mouse::Cursor,
) -> Vec<Geometry> {
let content = self.state.cache.draw(
renderer,
@@ -183,9 +181,9 @@ mod bezier {
&self,
_state: &Self::State,
bounds: Rectangle,
- cursor: Cursor,
+ cursor: mouse::Cursor,
) -> mouse::Interaction {
- if cursor.is_over(&bounds) {
+ if cursor.is_over(bounds) {
mouse::Interaction::Crosshair
} else {
mouse::Interaction::default()
@@ -224,11 +222,11 @@ mod bezier {
&self,
renderer: &Renderer,
bounds: Rectangle,
- cursor: Cursor,
+ cursor: mouse::Cursor,
) -> Geometry {
let mut frame = Frame::new(renderer, bounds.size());
- if let Some(cursor_position) = cursor.position_in(&bounds) {
+ if let Some(cursor_position) = cursor.position_in(bounds) {
match *self {
Pending::One { from } => {
let line = Path::line(from, cursor_position);
diff --git a/examples/checkbox/src/main.rs b/examples/checkbox/src/main.rs
index ef61a974..ef1a054d 100644
--- a/examples/checkbox/src/main.rs
+++ b/examples/checkbox/src/main.rs
@@ -31,7 +31,7 @@ impl Application for Example {
fn new(_flags: Self::Flags) -> (Self, Command<Message>) {
(
Self::default(),
- font::load(include_bytes!("../fonts/icons.ttf").as_ref())
+ font::load(include_bytes!("../fonts/icons.ttf").as_slice())
.map(Message::FontLoaded),
)
}
diff --git a/examples/clock/src/main.rs b/examples/clock/src/main.rs
index 6425e2da..fae77bc0 100644
--- a/examples/clock/src/main.rs
+++ b/examples/clock/src/main.rs
@@ -1,7 +1,6 @@
use iced::executor;
-use iced::widget::canvas::{
- stroke, Cache, Cursor, Geometry, LineCap, Path, Stroke,
-};
+use iced::mouse;
+use iced::widget::canvas::{stroke, Cache, Geometry, LineCap, Path, Stroke};
use iced::widget::{canvas, container};
use iced::{
Application, Color, Command, Element, Length, Point, Rectangle, Renderer,
@@ -92,7 +91,7 @@ impl<Message> canvas::Program<Message, Renderer> for Clock {
renderer: &Renderer,
_theme: &Theme,
bounds: Rectangle,
- _cursor: Cursor,
+ _cursor: mouse::Cursor,
) -> Vec<Geometry> {
let clock = self.clock.draw(renderer, bounds.size(), |frame| {
let center = frame.center();
diff --git a/examples/color_palette/Cargo.toml b/examples/color_palette/Cargo.toml
index 8fd37202..3be732bb 100644
--- a/examples/color_palette/Cargo.toml
+++ b/examples/color_palette/Cargo.toml
@@ -7,4 +7,4 @@ publish = false
[dependencies]
iced = { path = "../..", features = ["canvas", "palette"] }
-palette = "0.6.0"
+palette = "0.7.0"
diff --git a/examples/color_palette/src/main.rs b/examples/color_palette/src/main.rs
index 5c4304ee..736a9d53 100644
--- a/examples/color_palette/src/main.rs
+++ b/examples/color_palette/src/main.rs
@@ -1,10 +1,14 @@
-use iced::widget::canvas::{self, Canvas, Cursor, Frame, Geometry, Path};
+use iced::alignment::{self, Alignment};
+use iced::mouse;
+use iced::widget::canvas::{self, Canvas, Frame, Geometry, Path};
use iced::widget::{column, row, text, Slider};
use iced::{
- alignment, Alignment, Color, Element, Length, Point, Rectangle, Renderer,
- Sandbox, Settings, Size, Vector,
+ Color, Element, Length, Point, Rectangle, Renderer, Sandbox, Settings,
+ Size, Vector,
+};
+use palette::{
+ self, convert::FromColor, rgb::Rgb, Darken, Hsl, Lighten, ShiftHue,
};
-use palette::{self, convert::FromColor, Hsl, Srgb};
use std::marker::PhantomData;
use std::ops::RangeInclusive;
@@ -49,12 +53,12 @@ impl Sandbox for ColorPalette {
fn update(&mut self, message: Message) {
let srgb = match message {
- Message::RgbColorChanged(rgb) => palette::Srgb::from(rgb),
- Message::HslColorChanged(hsl) => palette::Srgb::from_color(hsl),
- Message::HsvColorChanged(hsv) => palette::Srgb::from_color(hsv),
- Message::HwbColorChanged(hwb) => palette::Srgb::from_color(hwb),
- Message::LabColorChanged(lab) => palette::Srgb::from_color(lab),
- Message::LchColorChanged(lch) => palette::Srgb::from_color(lch),
+ Message::RgbColorChanged(rgb) => Rgb::from(rgb),
+ Message::HslColorChanged(hsl) => Rgb::from_color(hsl),
+ Message::HsvColorChanged(hsv) => Rgb::from_color(hsv),
+ Message::HwbColorChanged(hwb) => Rgb::from_color(hwb),
+ Message::LabColorChanged(lab) => Rgb::from_color(lab),
+ Message::LchColorChanged(lch) => Rgb::from_color(lch),
};
self.theme = Theme::new(srgb);
@@ -63,7 +67,7 @@ impl Sandbox for ColorPalette {
fn view(&self) -> Element<Message> {
let base = self.theme.base;
- let srgb = palette::Srgb::from(base);
+ let srgb = Rgb::from(base);
let hsl = palette::Hsl::from_color(srgb);
let hsv = palette::Hsv::from_color(srgb);
let hwb = palette::Hwb::from_color(srgb);
@@ -95,12 +99,10 @@ struct Theme {
impl Theme {
pub fn new(base: impl Into<Color>) -> Theme {
- use palette::{Hue, Shade};
-
let base = base.into();
// Convert to HSL color for manipulation
- let hsl = Hsl::from_color(Srgb::from(base));
+ let hsl = Hsl::from_color(Rgb::from(base));
let lower = [
hsl.shift_hue(-135.0).lighten(0.075),
@@ -119,12 +121,12 @@ impl Theme {
Theme {
lower: lower
.iter()
- .map(|&color| Srgb::from_color(color).into())
+ .map(|&color| Rgb::from_color(color).into())
.collect(),
base,
higher: higher
.iter()
- .map(|&color| Srgb::from_color(color).into())
+ .map(|&color| Rgb::from_color(color).into())
.collect(),
canvas_cache: canvas::Cache::default(),
}
@@ -209,14 +211,14 @@ impl Theme {
text.vertical_alignment = alignment::Vertical::Bottom;
- let hsl = Hsl::from_color(Srgb::from(self.base));
+ let hsl = Hsl::from_color(Rgb::from(self.base));
for i in 0..self.len() {
let pct = (i as f32 + 1.0) / (self.len() as f32 + 1.0);
let graded = Hsl {
lightness: 1.0 - pct,
..hsl
};
- let color: Color = Srgb::from_color(graded).into();
+ let color: Color = Rgb::from_color(graded).into();
let anchor = Point {
x: (i as f32) * box_size.width,
@@ -246,7 +248,7 @@ impl<Message> canvas::Program<Message> for Theme {
renderer: &Renderer,
_theme: &iced::Theme,
bounds: Rectangle,
- _cursor: Cursor,
+ _cursor: mouse::Cursor,
) -> Vec<Geometry> {
let theme = self.canvas_cache.draw(renderer, bounds.size(), |frame| {
self.draw(frame);
@@ -352,7 +354,7 @@ impl ColorSpace for palette::Hsl {
fn components(&self) -> [f32; 3] {
[
- self.hue.to_positive_degrees(),
+ self.hue.into_positive_degrees(),
self.saturation,
self.lightness,
]
@@ -361,7 +363,7 @@ impl ColorSpace for palette::Hsl {
fn to_string(&self) -> String {
format!(
"hsl({:.1}, {:.1}%, {:.1}%)",
- self.hue.to_positive_degrees(),
+ self.hue.into_positive_degrees(),
100.0 * self.saturation,
100.0 * self.lightness
)
@@ -378,13 +380,17 @@ impl ColorSpace for palette::Hsv {
}
fn components(&self) -> [f32; 3] {
- [self.hue.to_positive_degrees(), self.saturation, self.value]
+ [
+ self.hue.into_positive_degrees(),
+ self.saturation,
+ self.value,
+ ]
}
fn to_string(&self) -> String {
format!(
"hsv({:.1}, {:.1}%, {:.1}%)",
- self.hue.to_positive_degrees(),
+ self.hue.into_positive_degrees(),
100.0 * self.saturation,
100.0 * self.value
)
@@ -406,7 +412,7 @@ impl ColorSpace for palette::Hwb {
fn components(&self) -> [f32; 3] {
[
- self.hue.to_positive_degrees(),
+ self.hue.into_positive_degrees(),
self.whiteness,
self.blackness,
]
@@ -415,7 +421,7 @@ impl ColorSpace for palette::Hwb {
fn to_string(&self) -> String {
format!(
"hwb({:.1}, {:.1}%, {:.1}%)",
- self.hue.to_positive_degrees(),
+ self.hue.into_positive_degrees(),
100.0 * self.whiteness,
100.0 * self.blackness
)
@@ -450,7 +456,7 @@ impl ColorSpace for palette::Lch {
}
fn components(&self) -> [f32; 3] {
- [self.l, self.chroma, self.hue.to_positive_degrees()]
+ [self.l, self.chroma, self.hue.into_positive_degrees()]
}
fn to_string(&self) -> String {
@@ -458,7 +464,7 @@ impl ColorSpace for palette::Lch {
"Lch({:.1}, {:.1}, {:.1})",
self.l,
self.chroma,
- self.hue.to_positive_degrees()
+ self.hue.into_positive_degrees()
)
}
}
diff --git a/examples/custom_quad/src/main.rs b/examples/custom_quad/src/main.rs
index b07f42ce..4b300116 100644
--- a/examples/custom_quad/src/main.rs
+++ b/examples/custom_quad/src/main.rs
@@ -3,7 +3,8 @@ mod quad {
use iced::advanced::layout::{self, Layout};
use iced::advanced::renderer;
use iced::advanced::widget::{self, Widget};
- use iced::{Color, Element, Length, Point, Rectangle, Size};
+ use iced::mouse;
+ use iced::{Color, Element, Length, Rectangle, Size};
pub struct CustomQuad {
size: f32,
@@ -48,7 +49,7 @@ mod quad {
_theme: &Renderer::Theme,
_style: &renderer::Style,
layout: Layout<'_>,
- _cursor_position: Point,
+ _cursor: mouse::Cursor,
_viewport: &Rectangle,
) {
renderer.fill_quad(
diff --git a/examples/custom_widget/src/main.rs b/examples/custom_widget/src/main.rs
index 7854548c..713bc62d 100644
--- a/examples/custom_widget/src/main.rs
+++ b/examples/custom_widget/src/main.rs
@@ -12,7 +12,8 @@ mod circle {
use iced::advanced::layout::{self, Layout};
use iced::advanced::renderer;
use iced::advanced::widget::{self, Widget};
- use iced::{Color, Element, Length, Point, Rectangle, Size};
+ use iced::mouse;
+ use iced::{Color, Element, Length, Rectangle, Size};
pub struct Circle {
radius: f32,
@@ -55,7 +56,7 @@ mod circle {
_theme: &Renderer::Theme,
_style: &renderer::Style,
layout: Layout<'_>,
- _cursor_position: Point,
+ _cursor: mouse::Cursor,
_viewport: &Rectangle,
) {
renderer.fill_quad(
diff --git a/examples/game_of_life/src/main.rs b/examples/game_of_life/src/main.rs
index eab8908b..e951d734 100644
--- a/examples/game_of_life/src/main.rs
+++ b/examples/game_of_life/src/main.rs
@@ -204,15 +204,14 @@ fn view_controls<'a>(
mod grid {
use crate::Preset;
+ use iced::alignment;
+ use iced::mouse;
use iced::touch;
use iced::widget::canvas;
use iced::widget::canvas::event::{self, Event};
- use iced::widget::canvas::{
- Cache, Canvas, Cursor, Frame, Geometry, Path, Text,
- };
+ use iced::widget::canvas::{Cache, Canvas, Frame, Geometry, Path, Text};
use iced::{
- alignment, mouse, Color, Element, Length, Point, Rectangle, Renderer,
- Size, Theme, Vector,
+ Color, Element, Length, Point, Rectangle, Renderer, Size, Theme, Vector,
};
use rustc_hash::{FxHashMap, FxHashSet};
use std::future::Future;
@@ -401,14 +400,14 @@ mod grid {
interaction: &mut Interaction,
event: Event,
bounds: Rectangle,
- cursor: Cursor,
+ cursor: mouse::Cursor,
) -> (event::Status, Option<Message>) {
if let Event::Mouse(mouse::Event::ButtonReleased(_)) = event {
*interaction = Interaction::None;
}
let cursor_position =
- if let Some(position) = cursor.position_in(&bounds) {
+ if let Some(position) = cursor.position_in(bounds) {
position
} else {
return (event::Status::Ignored, None);
@@ -539,7 +538,7 @@ mod grid {
renderer: &Renderer,
_theme: &Theme,
bounds: Rectangle,
- cursor: Cursor,
+ cursor: mouse::Cursor,
) -> Vec<Geometry> {
let center = Vector::new(bounds.width / 2.0, bounds.height / 2.0);
@@ -568,10 +567,9 @@ mod grid {
let overlay = {
let mut frame = Frame::new(renderer, bounds.size());
- let hovered_cell =
- cursor.position_in(&bounds).map(|position| {
- Cell::at(self.project(position, frame.size()))
- });
+ let hovered_cell = cursor.position_in(bounds).map(|position| {
+ Cell::at(self.project(position, frame.size()))
+ });
if let Some(cell) = hovered_cell {
frame.with_save(|frame| {
@@ -670,13 +668,13 @@ mod grid {
&self,
interaction: &Interaction,
bounds: Rectangle,
- cursor: Cursor,
+ cursor: mouse::Cursor,
) -> mouse::Interaction {
match interaction {
Interaction::Drawing => mouse::Interaction::Crosshair,
Interaction::Erasing => mouse::Interaction::Crosshair,
Interaction::Panning { .. } => mouse::Interaction::Grabbing,
- Interaction::None if cursor.is_over(&bounds) => {
+ Interaction::None if cursor.is_over(bounds) => {
mouse::Interaction::Crosshair
}
_ => mouse::Interaction::default(),
diff --git a/examples/geometry/src/main.rs b/examples/geometry/src/main.rs
index 5cb41184..29f78ea1 100644
--- a/examples/geometry/src/main.rs
+++ b/examples/geometry/src/main.rs
@@ -3,12 +3,12 @@
mod rainbow {
use iced_graphics::primitive::{ColoredVertex2D, Primitive};
+ use iced::advanced::graphics::color;
use iced::advanced::layout::{self, Layout};
use iced::advanced::renderer;
use iced::advanced::widget::{self, Widget};
- use iced::{
- Element, Length, Point, Rectangle, Renderer, Size, Theme, Vector,
- };
+ use iced::mouse;
+ use iced::{Element, Length, Rectangle, Renderer, Size, Theme, Vector};
#[derive(Debug, Clone, Copy, Default)]
pub struct Rainbow;
@@ -43,13 +43,13 @@ mod rainbow {
_theme: &Theme,
_style: &renderer::Style,
layout: Layout<'_>,
- cursor_position: Point,
+ cursor: mouse::Cursor,
_viewport: &Rectangle,
) {
use iced::advanced::Renderer as _;
use iced_graphics::primitive::Mesh2D;
- let b = layout.bounds();
+ let bounds = layout.bounds();
// R O Y G B I V
let color_r = [1.0, 0.0, 0.0, 1.0];
@@ -62,61 +62,61 @@ mod rainbow {
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]
+ if let Some(cursor_position) = cursor.position_in(bounds) {
+ [cursor_position.x, cursor_position.y]
} else {
- [b.width / 2.0, b.height / 2.0]
+ [bounds.width / 2.0, bounds.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 posn_t = [bounds.width / 2.0, 0.0];
+ let posn_tr = [bounds.width, 0.0];
+ let posn_r = [bounds.width, bounds.height / 2.0];
+ let posn_br = [bounds.width, bounds.height];
+ let posn_b = [(bounds.width / 2.0), bounds.height];
+ let posn_bl = [0.0, bounds.height];
+ let posn_l = [0.0, bounds.height / 2.0];
let mesh = Primitive::SolidMesh {
- size: b.size(),
+ size: bounds.size(),
buffers: Mesh2D {
vertices: vec![
ColoredVertex2D {
position: posn_center,
- color: [1.0, 1.0, 1.0, 1.0],
+ color: color::pack([1.0, 1.0, 1.0, 1.0]),
},
ColoredVertex2D {
position: posn_tl,
- color: color_r,
+ color: color::pack(color_r),
},
ColoredVertex2D {
position: posn_t,
- color: color_o,
+ color: color::pack(color_o),
},
ColoredVertex2D {
position: posn_tr,
- color: color_y,
+ color: color::pack(color_y),
},
ColoredVertex2D {
position: posn_r,
- color: color_g,
+ color: color::pack(color_g),
},
ColoredVertex2D {
position: posn_br,
- color: color_gb,
+ color: color::pack(color_gb),
},
ColoredVertex2D {
position: posn_b,
- color: color_b,
+ color: color::pack(color_b),
},
ColoredVertex2D {
position: posn_bl,
- color: color_i,
+ color: color::pack(color_i),
},
ColoredVertex2D {
position: posn_l,
- color: color_v,
+ color: color::pack(color_v),
},
],
indices: vec![
@@ -132,9 +132,12 @@ mod rainbow {
},
};
- renderer.with_translation(Vector::new(b.x, b.y), |renderer| {
- renderer.draw_primitive(mesh);
- });
+ renderer.with_translation(
+ Vector::new(bounds.x, bounds.y),
+ |renderer| {
+ renderer.draw_primitive(mesh);
+ },
+ );
}
}
diff --git a/examples/integration/Cargo.toml b/examples/integration/Cargo.toml
index 2ab5d75f..22914742 100644
--- a/examples/integration/Cargo.toml
+++ b/examples/integration/Cargo.toml
@@ -9,7 +9,7 @@ publish = false
iced_winit = { path = "../../winit" }
iced_wgpu = { path = "../../wgpu" }
iced_widget = { path = "../../widget" }
-iced_renderer = { path = "../../renderer", features = ["wgpu", "tiny-skia"] }
+iced_renderer = { path = "../../renderer", features = ["wgpu"] }
env_logger = "0.10"
[target.'cfg(target_arch = "wasm32")'.dependencies]
diff --git a/examples/integration/src/main.rs b/examples/integration/src/main.rs
index c935aca7..342d4c69 100644
--- a/examples/integration/src/main.rs
+++ b/examples/integration/src/main.rs
@@ -6,6 +6,7 @@ use scene::Scene;
use iced_wgpu::graphics::Viewport;
use iced_wgpu::{wgpu, Backend, Renderer, Settings};
+use iced_winit::core::mouse;
use iced_winit::core::renderer;
use iced_winit::core::{Color, Size};
use iced_winit::runtime::program;
@@ -14,7 +15,6 @@ use iced_winit::style::Theme;
use iced_winit::{conversion, futures, winit, Clipboard};
use winit::{
- dpi::PhysicalPosition,
event::{Event, ModifiersState, WindowEvent},
event_loop::{ControlFlow, EventLoop},
};
@@ -39,6 +39,7 @@ pub fn main() -> Result<(), Box<dyn std::error::Error>> {
.and_then(|element| element.dyn_into::<HtmlCanvasElement>().ok())
.expect("Get canvas element")
};
+
#[cfg(not(target_arch = "wasm32"))]
env_logger::init();
@@ -58,7 +59,7 @@ pub fn main() -> Result<(), Box<dyn std::error::Error>> {
Size::new(physical_size.width, physical_size.height),
window.scale_factor(),
);
- let mut cursor_position = PhysicalPosition::new(-1.0, -1.0);
+ let mut cursor_position = None;
let mut modifiers = ModifiersState::default();
let mut clipboard = Clipboard::connect(&window);
@@ -165,7 +166,7 @@ pub fn main() -> Result<(), Box<dyn std::error::Error>> {
Event::WindowEvent { event, .. } => {
match event {
WindowEvent::CursorMoved { position, .. } => {
- cursor_position = position;
+ cursor_position = Some(position);
}
WindowEvent::ModifiersChanged(new_modifiers) => {
modifiers = new_modifiers;
@@ -194,13 +195,20 @@ pub fn main() -> Result<(), Box<dyn std::error::Error>> {
// We update iced
let _ = state.update(
viewport.logical_size(),
- conversion::cursor_position(
- cursor_position,
- viewport.scale_factor(),
- ),
+ cursor_position
+ .map(|p| {
+ conversion::cursor_position(
+ p,
+ viewport.scale_factor(),
+ )
+ })
+ .map(mouse::Cursor::Available)
+ .unwrap_or(mouse::Cursor::Unavailable),
&mut renderer,
&Theme::Dark,
- &renderer::Style { text_color: Color::WHITE },
+ &renderer::Style {
+ text_color: Color::WHITE,
+ },
&mut clipboard,
&mut debug,
);
@@ -242,7 +250,9 @@ pub fn main() -> Result<(), Box<dyn std::error::Error>> {
let program = state.program();
- let view = frame.texture.create_view(&wgpu::TextureViewDescriptor::default());
+ let view = frame.texture.create_view(
+ &wgpu::TextureViewDescriptor::default(),
+ );
{
// We clear the frame
@@ -275,15 +285,18 @@ pub fn main() -> Result<(), Box<dyn std::error::Error>> {
frame.present();
// Update the mouse cursor
- window.set_cursor_icon(
- iced_winit::conversion::mouse_interaction(
- state.mouse_interaction(),
- ),
- );
+ window.set_cursor_icon(
+ iced_winit::conversion::mouse_interaction(
+ state.mouse_interaction(),
+ ),
+ );
}
Err(error) => match error {
wgpu::SurfaceError::OutOfMemory => {
- panic!("Swapchain error: {error}. Rendering cannot continue.")
+ panic!(
+ "Swapchain error: {error}. \
+ Rendering cannot continue."
+ )
}
_ => {
// Try rendering again next frame.
diff --git a/examples/modal/src/main.rs b/examples/modal/src/main.rs
index f48afb69..7fcbbfe4 100644
--- a/examples/modal/src/main.rs
+++ b/examples/modal/src/main.rs
@@ -1,12 +1,15 @@
+use iced::executor;
+use iced::keyboard;
+use iced::subscription::{self, Subscription};
+use iced::theme;
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,
+ self, button, column, container, horizontal_space, pick_list, row, text,
+ text_input,
};
+use iced::{Alignment, Application, Command, Element, Event, Length, Settings};
-use self::modal::Modal;
+use modal::Modal;
+use std::fmt;
pub fn main() -> iced::Result {
App::run(Settings::default())
@@ -17,6 +20,7 @@ struct App {
show_modal: bool,
email: String,
password: String,
+ plan: Plan,
}
#[derive(Debug, Clone)]
@@ -25,6 +29,7 @@ enum Message {
HideModal,
Email(String),
Password(String),
+ Plan(Plan),
Submit,
Event(Event),
}
@@ -65,6 +70,10 @@ impl Application for App {
self.password = password;
Command::none()
}
+ Message::Plan(plan) => {
+ self.plan = plan;
+ Command::none()
+ }
Message::Submit => {
if !self.email.is_empty() && !self.password.is_empty() {
self.hide_modal();
@@ -148,6 +157,16 @@ impl Application for App {
.padding(5),
]
.spacing(5),
+ column![
+ text("Plan").size(12),
+ pick_list(
+ Plan::ALL,
+ Some(self.plan),
+ Message::Plan
+ )
+ .padding(5),
+ ]
+ .spacing(5),
button(text("Submit")).on_press(Message::HideModal),
]
.spacing(10)
@@ -175,6 +194,29 @@ impl App {
}
}
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
+enum Plan {
+ #[default]
+ Basic,
+ Pro,
+ Enterprise,
+}
+
+impl Plan {
+ pub const ALL: &[Self] = &[Self::Basic, Self::Pro, Self::Enterprise];
+}
+
+impl fmt::Display for Plan {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ match self {
+ Plan::Basic => "Basic",
+ Plan::Pro => "Pro",
+ Plan::Enterprise => "Enterprise",
+ }
+ .fmt(f)
+ }
+}
+
mod modal {
use iced::advanced::layout::{self, Layout};
use iced::advanced::overlay;
@@ -254,7 +296,7 @@ mod modal {
state: &mut widget::Tree,
event: Event,
layout: Layout<'_>,
- cursor_position: Point,
+ cursor: mouse::Cursor,
renderer: &Renderer,
clipboard: &mut dyn Clipboard,
shell: &mut Shell<'_, Message>,
@@ -263,7 +305,7 @@ mod modal {
&mut state.children[0],
event,
layout,
- cursor_position,
+ cursor,
renderer,
clipboard,
shell,
@@ -277,7 +319,7 @@ mod modal {
theme: &<Renderer as advanced::Renderer>::Theme,
style: &renderer::Style,
layout: Layout<'_>,
- cursor_position: Point,
+ cursor: mouse::Cursor,
viewport: &Rectangle,
) {
self.base.as_widget().draw(
@@ -286,7 +328,7 @@ mod modal {
theme,
style,
layout,
- cursor_position,
+ cursor,
viewport,
);
}
@@ -312,14 +354,14 @@ mod modal {
&self,
state: &widget::Tree,
layout: Layout<'_>,
- cursor_position: Point,
+ cursor: mouse::Cursor,
viewport: &Rectangle,
renderer: &Renderer,
) -> mouse::Interaction {
self.base.as_widget().mouse_interaction(
&state.children[0],
layout,
- cursor_position,
+ cursor,
viewport,
renderer,
)
@@ -377,7 +419,7 @@ mod modal {
&mut self,
event: Event,
layout: Layout<'_>,
- cursor_position: Point,
+ cursor: mouse::Cursor,
renderer: &Renderer,
clipboard: &mut dyn Clipboard,
shell: &mut Shell<'_, Message>,
@@ -389,7 +431,7 @@ mod modal {
mouse::Button::Left,
)) = &event
{
- if !content_bounds.contains(cursor_position) {
+ if !cursor.is_over(content_bounds) {
shell.publish(message.clone());
return event::Status::Captured;
}
@@ -400,7 +442,7 @@ mod modal {
self.tree,
event,
layout.children().next().unwrap(),
- cursor_position,
+ cursor,
renderer,
clipboard,
shell,
@@ -413,12 +455,12 @@ mod modal {
theme: &Renderer::Theme,
style: &renderer::Style,
layout: Layout<'_>,
- cursor_position: Point,
+ cursor: mouse::Cursor,
) {
renderer.fill_quad(
renderer::Quad {
bounds: layout.bounds(),
- border_radius: renderer::BorderRadius::from(0.0),
+ border_radius: Default::default(),
border_width: 0.0,
border_color: Color::TRANSPARENT,
},
@@ -434,7 +476,7 @@ mod modal {
theme,
style,
layout.children().next().unwrap(),
- cursor_position,
+ cursor,
&layout.bounds(),
);
}
@@ -456,18 +498,30 @@ mod modal {
fn mouse_interaction(
&self,
layout: Layout<'_>,
- cursor_position: Point,
+ cursor: mouse::Cursor,
viewport: &Rectangle,
renderer: &Renderer,
) -> mouse::Interaction {
self.content.as_widget().mouse_interaction(
self.tree,
layout.children().next().unwrap(),
- cursor_position,
+ cursor,
viewport,
renderer,
)
}
+
+ fn overlay<'c>(
+ &'c mut self,
+ layout: Layout<'_>,
+ renderer: &Renderer,
+ ) -> Option<overlay::Element<'c, Message, Renderer>> {
+ self.content.as_widget_mut().overlay(
+ self.tree,
+ layout.children().next().unwrap(),
+ renderer,
+ )
+ }
}
impl<'a, Message, Renderer> From<Modal<'a, Message, Renderer>>
diff --git a/examples/modern_art/Cargo.toml b/examples/modern_art/Cargo.toml
deleted file mode 100644
index 4242d209..00000000
--- a/examples/modern_art/Cargo.toml
+++ /dev/null
@@ -1,11 +0,0 @@
-[package]
-name = "modern_art"
-version = "0.1.0"
-authors = ["Bingus <shankern@protonmail.com>"]
-edition = "2021"
-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
deleted file mode 100644
index a43a2b2b..00000000
--- a/examples/modern_art/src/main.rs
+++ /dev/null
@@ -1,143 +0,0 @@
-use iced::widget::canvas::{
- self, gradient::Location, gradient::Position, Cache, Canvas, Cursor, Frame,
- Geometry, Gradient,
-};
-use iced::{
- executor, Application, Color, Command, Element, Length, Point, Rectangle,
- Renderer, Settings, Size, Theme,
-};
-use rand::{thread_rng, Rng};
-
-fn main() -> iced::Result {
- env_logger::builder().format_timestamp(None).init();
-
- ModernArt::run(Settings {
- antialiasing: true,
- ..Settings::default()
- })
-}
-
-#[derive(Debug, Clone, Copy)]
-enum Message {}
-
-struct ModernArt {
- cache: Cache,
-}
-
-impl Application for ModernArt {
- type Executor = executor::Default;
- type Message = Message;
- type Theme = Theme;
- type Flags = ();
-
- fn new(_flags: Self::Flags) -> (Self, Command<Self::Message>) {
- (
- ModernArt {
- cache: Default::default(),
- },
- Command::none(),
- )
- }
-
- fn title(&self) -> String {
- String::from("Modern Art")
- }
-
- fn update(&mut self, _message: Message) -> Command<Message> {
- Command::none()
- }
-
- fn view(&self) -> Element<'_, Self::Message, Renderer<Self::Theme>> {
- Canvas::new(self)
- .width(Length::Fill)
- .height(Length::Fill)
- .into()
- }
-}
-
-impl<Message> canvas::Program<Message, Renderer> for ModernArt {
- type State = ();
-
- fn draw(
- &self,
- _state: &Self::State,
- renderer: &Renderer,
- _theme: &Theme,
- bounds: Rectangle,
- _cursor: Cursor,
- ) -> Vec<Geometry> {
- let geometry = self.cache.draw(renderer, bounds.size(), |frame| {
- let num_squares = thread_rng().gen_range(0..1200);
-
- let mut i = 0;
- while i <= num_squares {
- generate_box(frame, bounds.size());
- i += 1;
- }
- });
-
- vec![geometry]
- }
-}
-
-fn random_direction() -> Location {
- match thread_rng().gen_range(0..8) {
- 0 => Location::TopLeft,
- 1 => Location::Top,
- 2 => Location::TopRight,
- 3 => Location::Right,
- 4 => Location::BottomRight,
- 5 => Location::Bottom,
- 6 => Location::BottomLeft,
- 7 => Location::Left,
- _ => Location::TopLeft,
- }
-}
-
-fn generate_box(frame: &mut Frame, bounds: Size) -> bool {
- let solid = rand::random::<bool>();
-
- let random_color = || -> Color {
- Color::from_rgb(
- thread_rng().gen_range(0.0..1.0),
- thread_rng().gen_range(0.0..1.0),
- thread_rng().gen_range(0.0..1.0),
- )
- };
-
- let gradient = |top_left: Point, size: Size| -> Gradient {
- let mut builder = Gradient::linear(Position::Relative {
- top_left,
- size,
- start: random_direction(),
- end: random_direction(),
- });
- let stops = thread_rng().gen_range(1..15u32);
-
- let mut i = 0;
- while i <= stops {
- builder = builder.add_stop(i as f32 / stops as f32, random_color());
- i += 1;
- }
-
- builder.build().unwrap()
- };
-
- let top_left = Point::new(
- thread_rng().gen_range(0.0..bounds.width),
- thread_rng().gen_range(0.0..bounds.height),
- );
-
- let size = Size::new(
- thread_rng().gen_range(50.0..200.0),
- thread_rng().gen_range(50.0..200.0),
- );
-
- if solid {
- frame.fill_rectangle(top_left, size, random_color());
- } else {
- frame.fill_rectangle(top_left, size, gradient(top_left, size));
- };
-
- solid
-}
diff --git a/examples/multitouch/src/main.rs b/examples/multitouch/src/main.rs
index 7df6c929..2830b78d 100644
--- a/examples/multitouch/src/main.rs
+++ b/examples/multitouch/src/main.rs
@@ -1,9 +1,10 @@
//! This example shows how to use touch events in `Canvas` to draw
//! a circle around each fingertip. This only works on touch-enabled
//! computers like Microsoft Surface.
+use iced::mouse;
use iced::widget::canvas::event;
use iced::widget::canvas::stroke::{self, Stroke};
-use iced::widget::canvas::{self, Canvas, Cursor, Geometry};
+use iced::widget::canvas::{self, Canvas, Geometry};
use iced::{
executor, touch, window, Application, Color, Command, Element, Length,
Point, Rectangle, Renderer, Settings, Subscription, Theme,
@@ -103,7 +104,7 @@ impl canvas::Program<Message, Renderer> for State {
_state: &mut Self::State,
event: event::Event,
_bounds: Rectangle,
- _cursor: Cursor,
+ _cursor: mouse::Cursor,
) -> (event::Status, Option<Message>) {
match event {
event::Event::Touch(touch_event) => match touch_event {
@@ -128,7 +129,7 @@ impl canvas::Program<Message, Renderer> for State {
renderer: &Renderer,
_theme: &Theme,
bounds: Rectangle,
- _cursor: Cursor,
+ _cursor: mouse::Cursor,
) -> Vec<Geometry> {
let fingerweb = self.cache.draw(renderer, bounds.size(), |frame| {
if self.fingers.len() < 2 {
diff --git a/examples/pane_grid/src/main.rs b/examples/pane_grid/src/main.rs
index dfb80853..54c36d69 100644
--- a/examples/pane_grid/src/main.rs
+++ b/examples/pane_grid/src/main.rs
@@ -108,8 +108,9 @@ impl Application for Example {
Message::Dragged(pane_grid::DragEvent::Dropped {
pane,
target,
+ region,
}) => {
- self.panes.swap(&pane, &target);
+ self.panes.split_with(&target, &pane, region);
}
Message::Dragged(_) => {}
Message::TogglePin(pane) => {
@@ -255,6 +256,7 @@ fn handle_hotkey(key_code: keyboard::KeyCode) -> Option<Message> {
}
}
+#[derive(Clone, Copy)]
struct Pane {
id: usize,
pub is_pinned: bool,
@@ -298,7 +300,7 @@ fn view_content<'a>(
)
]
.spacing(5)
- .max_width(150);
+ .max_width(160);
if total_panes > 1 && !is_pinned {
controls = controls.push(
diff --git a/examples/screenshot/Cargo.toml b/examples/screenshot/Cargo.toml
new file mode 100644
index 00000000..b79300b7
--- /dev/null
+++ b/examples/screenshot/Cargo.toml
@@ -0,0 +1,11 @@
+[package]
+name = "screenshot"
+version = "0.1.0"
+authors = ["Bingus <shankern@protonmail.com>"]
+edition = "2021"
+publish = false
+
+[dependencies]
+iced = { path = "../..", features = ["debug", "image", "advanced"] }
+image = { version = "0.24.6", features = ["png"]}
+env_logger = "0.10.0"
diff --git a/examples/screenshot/src/main.rs b/examples/screenshot/src/main.rs
new file mode 100644
index 00000000..83824535
--- /dev/null
+++ b/examples/screenshot/src/main.rs
@@ -0,0 +1,320 @@
+use iced::alignment;
+use iced::keyboard::KeyCode;
+use iced::theme::{Button, Container};
+use iced::widget::{button, column, container, image, row, text, text_input};
+use iced::window::screenshot::{self, Screenshot};
+use iced::{
+ event, executor, keyboard, subscription, Alignment, Application, Command,
+ ContentFit, Element, Event, Length, Rectangle, Renderer, Subscription,
+ Theme,
+};
+
+use ::image as img;
+use ::image::ColorType;
+
+fn main() -> iced::Result {
+ env_logger::builder().format_timestamp(None).init();
+
+ Example::run(iced::Settings::default())
+}
+
+struct Example {
+ screenshot: Option<Screenshot>,
+ saved_png_path: Option<Result<String, PngError>>,
+ png_saving: bool,
+ crop_error: Option<screenshot::CropError>,
+ x_input_value: Option<u32>,
+ y_input_value: Option<u32>,
+ width_input_value: Option<u32>,
+ height_input_value: Option<u32>,
+}
+
+#[derive(Clone, Debug)]
+enum Message {
+ Crop,
+ Screenshot,
+ ScreenshotData(Screenshot),
+ Png,
+ PngSaved(Result<String, PngError>),
+ XInputChanged(Option<u32>),
+ YInputChanged(Option<u32>),
+ WidthInputChanged(Option<u32>),
+ HeightInputChanged(Option<u32>),
+}
+
+impl Application for Example {
+ type Executor = executor::Default;
+ type Message = Message;
+ type Theme = Theme;
+ type Flags = ();
+
+ fn new(_flags: Self::Flags) -> (Self, Command<Self::Message>) {
+ (
+ Example {
+ screenshot: None,
+ saved_png_path: None,
+ png_saving: false,
+ crop_error: None,
+ x_input_value: None,
+ y_input_value: None,
+ width_input_value: None,
+ height_input_value: None,
+ },
+ Command::none(),
+ )
+ }
+
+ fn title(&self) -> String {
+ "Screenshot".to_string()
+ }
+
+ fn update(&mut self, message: Self::Message) -> Command<Self::Message> {
+ match message {
+ Message::Screenshot => {
+ return iced::window::screenshot(Message::ScreenshotData);
+ }
+ Message::ScreenshotData(screenshot) => {
+ self.screenshot = Some(screenshot);
+ }
+ Message::Png => {
+ if let Some(screenshot) = &self.screenshot {
+ self.png_saving = true;
+
+ return Command::perform(
+ save_to_png(screenshot.clone()),
+ Message::PngSaved,
+ );
+ }
+ }
+ Message::PngSaved(res) => {
+ self.png_saving = false;
+ self.saved_png_path = Some(res);
+ }
+ Message::XInputChanged(new_value) => {
+ self.x_input_value = new_value;
+ }
+ Message::YInputChanged(new_value) => {
+ self.y_input_value = new_value;
+ }
+ Message::WidthInputChanged(new_value) => {
+ self.width_input_value = new_value;
+ }
+ Message::HeightInputChanged(new_value) => {
+ self.height_input_value = new_value;
+ }
+ Message::Crop => {
+ if let Some(screenshot) = &self.screenshot {
+ let cropped = screenshot.crop(Rectangle::<u32> {
+ x: self.x_input_value.unwrap_or(0),
+ y: self.y_input_value.unwrap_or(0),
+ width: self.width_input_value.unwrap_or(0),
+ height: self.height_input_value.unwrap_or(0),
+ });
+
+ match cropped {
+ Ok(screenshot) => {
+ self.screenshot = Some(screenshot);
+ self.crop_error = None;
+ }
+ Err(crop_error) => {
+ self.crop_error = Some(crop_error);
+ }
+ }
+ }
+ }
+ }
+
+ Command::none()
+ }
+
+ fn view(&self) -> Element<'_, Self::Message, Renderer<Self::Theme>> {
+ let image: Element<Message> = if let Some(screenshot) = &self.screenshot
+ {
+ image(image::Handle::from_pixels(
+ screenshot.size.width,
+ screenshot.size.height,
+ screenshot.clone(),
+ ))
+ .content_fit(ContentFit::Contain)
+ .width(Length::Fill)
+ .height(Length::Fill)
+ .into()
+ } else {
+ text("Press the button to take a screenshot!").into()
+ };
+
+ let image = container(image)
+ .padding(10)
+ .style(Container::Box)
+ .width(Length::FillPortion(2))
+ .height(Length::Fill)
+ .center_x()
+ .center_y();
+
+ let crop_origin_controls = row![
+ text("X:")
+ .vertical_alignment(alignment::Vertical::Center)
+ .width(30),
+ numeric_input("0", self.x_input_value).map(Message::XInputChanged),
+ text("Y:")
+ .vertical_alignment(alignment::Vertical::Center)
+ .width(30),
+ numeric_input("0", self.y_input_value).map(Message::YInputChanged)
+ ]
+ .spacing(10)
+ .align_items(Alignment::Center);
+
+ let crop_dimension_controls = row![
+ text("W:")
+ .vertical_alignment(alignment::Vertical::Center)
+ .width(30),
+ numeric_input("0", self.width_input_value)
+ .map(Message::WidthInputChanged),
+ text("H:")
+ .vertical_alignment(alignment::Vertical::Center)
+ .width(30),
+ numeric_input("0", self.height_input_value)
+ .map(Message::HeightInputChanged)
+ ]
+ .spacing(10)
+ .align_items(Alignment::Center);
+
+ let mut crop_controls =
+ column![crop_origin_controls, crop_dimension_controls]
+ .spacing(10)
+ .align_items(Alignment::Center);
+
+ if let Some(crop_error) = &self.crop_error {
+ crop_controls = crop_controls
+ .push(text(format!("Crop error! \n{}", crop_error)));
+ }
+
+ let mut controls = column![
+ column![
+ button(centered_text("Screenshot!"))
+ .padding([10, 20, 10, 20])
+ .width(Length::Fill)
+ .on_press(Message::Screenshot),
+ if !self.png_saving {
+ button(centered_text("Save as png")).on_press_maybe(
+ self.screenshot.is_some().then(|| Message::Png),
+ )
+ } else {
+ button(centered_text("Saving...")).style(Button::Secondary)
+ }
+ .style(Button::Secondary)
+ .padding([10, 20, 10, 20])
+ .width(Length::Fill)
+ ]
+ .spacing(10),
+ column![
+ crop_controls,
+ button(centered_text("Crop"))
+ .on_press(Message::Crop)
+ .style(Button::Destructive)
+ .padding([10, 20, 10, 20])
+ .width(Length::Fill),
+ ]
+ .spacing(10)
+ .align_items(Alignment::Center),
+ ]
+ .spacing(40);
+
+ if let Some(png_result) = &self.saved_png_path {
+ let msg = match png_result {
+ Ok(path) => format!("Png saved as: {:?}!", path),
+ Err(msg) => {
+ format!("Png could not be saved due to:\n{:?}", msg)
+ }
+ };
+
+ controls = controls.push(text(msg));
+ }
+
+ let side_content = container(controls)
+ .align_x(alignment::Horizontal::Center)
+ .width(Length::FillPortion(1))
+ .height(Length::Fill)
+ .center_y()
+ .center_x();
+
+ let content = row![side_content, image]
+ .spacing(10)
+ .width(Length::Fill)
+ .height(Length::Fill)
+ .align_items(Alignment::Center);
+
+ container(content)
+ .width(Length::Fill)
+ .height(Length::Fill)
+ .padding(10)
+ .center_x()
+ .center_y()
+ .into()
+ }
+
+ fn subscription(&self) -> Subscription<Self::Message> {
+ subscription::events_with(|event, status| {
+ if let event::Status::Captured = status {
+ return None;
+ }
+
+ if let Event::Keyboard(keyboard::Event::KeyPressed {
+ key_code: KeyCode::F5,
+ ..
+ }) = event
+ {
+ Some(Message::Screenshot)
+ } else {
+ None
+ }
+ })
+ }
+}
+
+async fn save_to_png(screenshot: Screenshot) -> Result<String, PngError> {
+ let path = "screenshot.png".to_string();
+ img::save_buffer(
+ &path,
+ &screenshot.bytes,
+ screenshot.size.width,
+ screenshot.size.height,
+ ColorType::Rgba8,
+ )
+ .map(|_| path)
+ .map_err(|err| PngError(format!("{:?}", err)))
+}
+
+#[derive(Clone, Debug)]
+struct PngError(String);
+
+fn numeric_input(
+ placeholder: &str,
+ value: Option<u32>,
+) -> Element<'_, Option<u32>> {
+ text_input(
+ placeholder,
+ &value
+ .as_ref()
+ .map(ToString::to_string)
+ .unwrap_or_else(String::new),
+ )
+ .on_input(move |text| {
+ if text.is_empty() {
+ None
+ } else if let Ok(new_value) = text.parse() {
+ Some(new_value)
+ } else {
+ value
+ }
+ })
+ .width(40)
+ .into()
+}
+
+fn centered_text(content: &str) -> Element<'_, Message> {
+ text(content)
+ .width(Length::Fill)
+ .horizontal_alignment(alignment::Horizontal::Center)
+ .into()
+}
diff --git a/examples/scrollable/src/main.rs b/examples/scrollable/src/main.rs
index 97344c94..3038661e 100644
--- a/examples/scrollable/src/main.rs
+++ b/examples/scrollable/src/main.rs
@@ -289,18 +289,13 @@ impl Application for ScrollableDemo {
}
Direction::Horizontal => {
progress_bar(0.0..=1.0, self.current_scroll_offset.x)
- .style(theme::ProgressBar::Custom(Box::new(
- ProgressBarCustomStyle,
- )))
+ .style(progress_bar_custom_style)
.into()
}
Direction::Multi => column![
progress_bar(0.0..=1.0, self.current_scroll_offset.y),
- progress_bar(0.0..=1.0, self.current_scroll_offset.x).style(
- theme::ProgressBar::Custom(Box::new(
- ProgressBarCustomStyle,
- ))
- )
+ progress_bar(0.0..=1.0, self.current_scroll_offset.x)
+ .style(progress_bar_custom_style)
]
.spacing(10)
.into(),
@@ -356,12 +351,12 @@ impl scrollable::StyleSheet for ScrollbarCustomStyle {
background: style
.active(&theme::Scrollable::default())
.background,
- border_radius: 0.0,
+ border_radius: 0.0.into(),
border_width: 0.0,
border_color: Default::default(),
scroller: Scroller {
color: Color::from_rgb8(250, 85, 134),
- border_radius: 0.0,
+ border_radius: 0.0.into(),
border_width: 0.0,
border_color: Default::default(),
},
@@ -372,16 +367,10 @@ impl scrollable::StyleSheet for ScrollbarCustomStyle {
}
}
-struct ProgressBarCustomStyle;
-
-impl progress_bar::StyleSheet for ProgressBarCustomStyle {
- type Style = Theme;
-
- fn appearance(&self, style: &Self::Style) -> progress_bar::Appearance {
- progress_bar::Appearance {
- background: style.extended_palette().background.strong.color.into(),
- bar: Color::from_rgb8(250, 85, 134).into(),
- border_radius: 0.0,
- }
+fn progress_bar_custom_style(theme: &Theme) -> progress_bar::Appearance {
+ progress_bar::Appearance {
+ background: theme.extended_palette().background.strong.color.into(),
+ bar: Color::from_rgb8(250, 85, 134).into(),
+ border_radius: 0.0.into(),
}
}
diff --git a/examples/sierpinski_triangle/src/main.rs b/examples/sierpinski_triangle/src/main.rs
index 4faac6d6..885d3c63 100644
--- a/examples/sierpinski_triangle/src/main.rs
+++ b/examples/sierpinski_triangle/src/main.rs
@@ -1,6 +1,7 @@
use std::fmt::Debug;
use iced::executor;
+use iced::mouse;
use iced::widget::canvas::event::{self, Event};
use iced::widget::canvas::{self, Canvas};
use iced::widget::{column, row, slider, text};
@@ -105,14 +106,14 @@ impl canvas::Program<Message> for SierpinskiGraph {
_state: &mut Self::State,
event: Event,
bounds: Rectangle,
- cursor: canvas::Cursor,
+ cursor: mouse::Cursor,
) -> (event::Status, Option<Message>) {
- let cursor_position =
- if let Some(position) = cursor.position_in(&bounds) {
- position
- } else {
- return (event::Status::Ignored, None);
- };
+ let cursor_position = if let Some(position) = cursor.position_in(bounds)
+ {
+ position
+ } else {
+ return (event::Status::Ignored, None);
+ };
match event {
Event::Mouse(mouse_event) => {
@@ -137,7 +138,7 @@ impl canvas::Program<Message> for SierpinskiGraph {
renderer: &Renderer,
_theme: &Theme,
bounds: Rectangle,
- _cursor: canvas::Cursor,
+ _cursor: mouse::Cursor,
) -> Vec<canvas::Geometry> {
let geom = self.cache.draw(renderer, bounds.size(), |frame| {
frame.stroke(
diff --git a/examples/solar_system/Cargo.toml b/examples/solar_system/Cargo.toml
index 835396b0..1a98a87e 100644
--- a/examples/solar_system/Cargo.toml
+++ b/examples/solar_system/Cargo.toml
@@ -7,4 +7,5 @@ publish = false
[dependencies]
iced = { path = "../..", features = ["canvas", "tokio", "debug"] }
+env_logger = "0.10.0"
rand = "0.8.3"
diff --git a/examples/solar_system/src/main.rs b/examples/solar_system/src/main.rs
index f2606feb..58d06206 100644
--- a/examples/solar_system/src/main.rs
+++ b/examples/solar_system/src/main.rs
@@ -8,11 +8,12 @@
//! [1]: https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API/Tutorial/Basic_animations#An_animated_solar_system
use iced::application;
use iced::executor;
+use iced::mouse;
use iced::theme::{self, Theme};
use iced::widget::canvas;
-use iced::widget::canvas::gradient::{self, Gradient};
+use iced::widget::canvas::gradient;
use iced::widget::canvas::stroke::{self, Stroke};
-use iced::widget::canvas::{Cursor, Path};
+use iced::widget::canvas::Path;
use iced::window;
use iced::{
Application, Color, Command, Element, Length, Point, Rectangle, Renderer,
@@ -22,6 +23,8 @@ use iced::{
use std::time::Instant;
pub fn main() -> iced::Result {
+ env_logger::builder().format_timestamp(None).init();
+
SolarSystem::run(Settings {
antialiasing: true,
..Settings::default()
@@ -159,7 +162,7 @@ impl<Message> canvas::Program<Message> for State {
renderer: &Renderer,
_theme: &Theme,
bounds: Rectangle,
- _cursor: Cursor,
+ _cursor: mouse::Cursor,
) -> Vec<canvas::Geometry> {
use std::f32::consts::PI;
@@ -208,15 +211,12 @@ impl<Message> canvas::Program<Message> for State {
let earth = Path::circle(Point::ORIGIN, Self::EARTH_RADIUS);
- let earth_fill =
- Gradient::linear(gradient::Position::Absolute {
- start: Point::new(-Self::EARTH_RADIUS, 0.0),
- end: Point::new(Self::EARTH_RADIUS, 0.0),
- })
- .add_stop(0.2, Color::from_rgb(0.15, 0.50, 1.0))
- .add_stop(0.8, Color::from_rgb(0.0, 0.20, 0.47))
- .build()
- .expect("Build Earth fill gradient");
+ let earth_fill = gradient::Linear::new(
+ Point::new(-Self::EARTH_RADIUS, 0.0),
+ Point::new(Self::EARTH_RADIUS, 0.0),
+ )
+ .add_stop(0.2, Color::from_rgb(0.15, 0.50, 1.0))
+ .add_stop(0.8, Color::from_rgb(0.0, 0.20, 0.47));
frame.fill(&earth, earth_fill);
diff --git a/examples/styling/src/main.rs b/examples/styling/src/main.rs
index e2015bac..f8a4c80a 100644
--- a/examples/styling/src/main.rs
+++ b/examples/styling/src/main.rs
@@ -127,7 +127,9 @@ impl Sandbox for Styling {
let content = column![
choose_theme,
horizontal_rule(38),
- row![text_input, button].spacing(10),
+ row![text_input, button]
+ .spacing(10)
+ .align_items(Alignment::Center),
slider,
progress_bar,
row![
diff --git a/examples/toast/src/main.rs b/examples/toast/src/main.rs
index 9d859258..4282ddcf 100644
--- a/examples/toast/src/main.rs
+++ b/examples/toast/src/main.rs
@@ -1,10 +1,10 @@
+use iced::executor;
+use iced::keyboard;
+use iced::subscription::{self, Subscription};
use iced::widget::{
self, button, column, container, pick_list, row, slider, text, text_input,
};
-use iced::{
- executor, keyboard, subscription, Alignment, Application, Command, Element,
- Event, Length, Settings, Subscription,
-};
+use iced::{Alignment, Application, Command, Element, Event, Length, Settings};
use toast::{Status, Toast};
@@ -226,7 +226,7 @@ mod toast {
};
container::Appearance {
- background: pair.color.into(),
+ background: Some(pair.color.into()),
text_color: pair.text.into(),
..Default::default()
}
@@ -396,7 +396,7 @@ mod toast {
state: &mut Tree,
event: Event,
layout: Layout<'_>,
- cursor_position: Point,
+ cursor: mouse::Cursor,
renderer: &Renderer,
clipboard: &mut dyn Clipboard,
shell: &mut Shell<'_, Message>,
@@ -405,7 +405,7 @@ mod toast {
&mut state.children[0],
event,
layout,
- cursor_position,
+ cursor,
renderer,
clipboard,
shell,
@@ -419,7 +419,7 @@ mod toast {
theme: &Theme,
style: &renderer::Style,
layout: Layout<'_>,
- cursor_position: Point,
+ cursor: mouse::Cursor,
viewport: &Rectangle,
) {
self.content.as_widget().draw(
@@ -428,7 +428,7 @@ mod toast {
theme,
style,
layout,
- cursor_position,
+ cursor,
viewport,
);
}
@@ -437,14 +437,14 @@ mod toast {
&self,
state: &Tree,
layout: Layout<'_>,
- cursor_position: Point,
+ cursor: mouse::Cursor,
viewport: &Rectangle,
renderer: &Renderer,
) -> mouse::Interaction {
self.content.as_widget().mouse_interaction(
&state.children[0],
layout,
- cursor_position,
+ cursor,
viewport,
renderer,
)
@@ -523,7 +523,7 @@ mod toast {
&mut self,
event: Event,
layout: Layout<'_>,
- cursor_position: Point,
+ cursor: mouse::Cursor,
renderer: &Renderer,
clipboard: &mut dyn Clipboard,
shell: &mut Shell<'_, Message>,
@@ -572,7 +572,7 @@ mod toast {
state,
event.clone(),
layout,
- cursor_position,
+ cursor,
renderer,
clipboard,
&mut local_shell,
@@ -595,7 +595,7 @@ mod toast {
theme: &<Renderer as advanced::Renderer>::Theme,
style: &renderer::Style,
layout: Layout<'_>,
- cursor_position: Point,
+ cursor: mouse::Cursor,
) {
let viewport = layout.bounds();
@@ -606,13 +606,7 @@ mod toast {
.zip(layout.children())
{
child.as_widget().draw(
- state,
- renderer,
- theme,
- style,
- layout,
- cursor_position,
- &viewport,
+ state, renderer, theme, style, layout, cursor, &viewport,
);
}
}
@@ -639,7 +633,7 @@ mod toast {
fn mouse_interaction(
&self,
layout: Layout<'_>,
- cursor_position: Point,
+ cursor: mouse::Cursor,
viewport: &Rectangle,
renderer: &Renderer,
) -> mouse::Interaction {
@@ -649,18 +643,19 @@ mod toast {
.zip(layout.children())
.map(|((child, state), layout)| {
child.as_widget().mouse_interaction(
- state,
- layout,
- cursor_position,
- viewport,
- renderer,
+ state, layout, cursor, viewport, renderer,
)
})
.max()
.unwrap_or_default()
}
- fn is_over(&self, layout: Layout<'_>, cursor_position: Point) -> bool {
+ fn is_over(
+ &self,
+ layout: Layout<'_>,
+ _renderer: &Renderer,
+ cursor_position: Point,
+ ) -> bool {
layout
.children()
.any(|layout| layout.bounds().contains(cursor_position))
diff --git a/examples/tour/Cargo.toml b/examples/tour/Cargo.toml
index 39e83671..48471f2d 100644
--- a/examples/tour/Cargo.toml
+++ b/examples/tour/Cargo.toml
@@ -7,4 +7,4 @@ publish = false
[dependencies]
iced = { path = "../..", features = ["image", "debug"] }
-env_logger = "0.8"
+env_logger = "0.10.0"
diff --git a/examples/tour/src/main.rs b/examples/tour/src/main.rs
index 9c38ad0e..13bcd5ff 100644
--- a/examples/tour/src/main.rs
+++ b/examples/tour/src/main.rs
@@ -62,11 +62,8 @@ impl Sandbox for Tour {
controls = controls.push(horizontal_space(Length::Fill));
if steps.can_continue() {
- controls = controls.push(
- button("Next")
- .on_press(Message::NextPressed)
- .style(theme::Button::Primary),
- );
+ controls =
+ controls.push(button("Next").on_press(Message::NextPressed));
}
let content: Element<_> = column![
diff --git a/futures/src/backend/native.rs b/futures/src/backend/native.rs
index 4199ad16..85af2c88 100644
--- a/futures/src/backend/native.rs
+++ b/futures/src/backend/native.rs
@@ -1,16 +1,12 @@
//! Backends that are only available in native platforms: Windows, macOS, or Linux.
-#[cfg_attr(docsrs, doc(cfg(feature = "tokio",)))]
#[cfg(feature = "tokio")]
pub mod tokio;
-#[cfg_attr(docsrs, doc(cfg(feature = "async-std",)))]
#[cfg(feature = "async-std")]
pub mod async_std;
-#[cfg_attr(docsrs, doc(cfg(feature = "smol",)))]
#[cfg(feature = "smol")]
pub mod smol;
-#[cfg_attr(docsrs, doc(cfg(feature = "thread-pool",)))]
#[cfg(feature = "thread-pool")]
pub mod thread_pool;
diff --git a/futures/src/backend/native/smol.rs b/futures/src/backend/native/smol.rs
index 30bc8291..00d13d35 100644
--- a/futures/src/backend/native/smol.rs
+++ b/futures/src/backend/native/smol.rs
@@ -1,9 +1,7 @@
//! A `smol` backend.
-
use futures::Future;
/// A `smol` executor.
-#[cfg_attr(docsrs, doc(cfg(feature = "smol")))]
#[derive(Debug)]
pub struct Executor;
diff --git a/futures/src/backend/native/thread_pool.rs b/futures/src/backend/native/thread_pool.rs
index da5d4b9b..c96f2682 100644
--- a/futures/src/backend/native/thread_pool.rs
+++ b/futures/src/backend/native/thread_pool.rs
@@ -2,7 +2,6 @@
use futures::Future;
/// A thread pool executor for futures.
-#[cfg_attr(docsrs, doc(cfg(feature = "thread-pool")))]
pub type Executor = futures::executor::ThreadPool;
impl crate::Executor for Executor {
diff --git a/futures/src/lib.rs b/futures/src/lib.rs
index 397fc2d2..34d81e1e 100644
--- a/futures/src/lib.rs
+++ b/futures/src/lib.rs
@@ -16,7 +16,7 @@
)]
#![forbid(unsafe_code, rust_2018_idioms)]
#![allow(clippy::inherent_to_string, clippy::type_complexity)]
-#![cfg_attr(docsrs, feature(doc_cfg))]
+#![cfg_attr(docsrs, feature(doc_auto_cfg))]
pub use futures;
pub use iced_core as core;
diff --git a/futures/src/subscription.rs b/futures/src/subscription.rs
index 801c2694..0642a924 100644
--- a/futures/src/subscription.rs
+++ b/futures/src/subscription.rs
@@ -417,7 +417,7 @@ where
pub fn channel<I, Fut, Message>(
id: I,
size: usize,
- f: impl Fn(mpsc::Sender<Message>) -> Fut + MaybeSend + Sync + 'static,
+ f: impl FnOnce(mpsc::Sender<Message>) -> Fut + MaybeSend + 'static,
) -> Subscription<Message>
where
I: Hash + 'static,
diff --git a/graphics/Cargo.toml b/graphics/Cargo.toml
index 125ea17d..02621695 100644
--- a/graphics/Cargo.toml
+++ b/graphics/Cargo.toml
@@ -14,9 +14,11 @@ categories = ["gui"]
geometry = ["lyon_path"]
opengl = []
image = ["dep:image", "kamadak-exif"]
+web-colors = []
[dependencies]
-glam = "0.21.3"
+glam = "0.24"
+half = "2.2.1"
log = "0.4"
raw-window-handle = "0.5"
thiserror = "1.0"
diff --git a/graphics/src/backend.rs b/graphics/src/backend.rs
index ae89da06..6a082576 100644
--- a/graphics/src/backend.rs
+++ b/graphics/src/backend.rs
@@ -6,18 +6,6 @@ use iced_core::{Font, Point, Size};
use std::borrow::Cow;
-/// The graphics backend of a [`Renderer`].
-///
-/// [`Renderer`]: crate::Renderer
-pub trait Backend {
- /// Trims the measurements cache.
- ///
- /// This method is currently necessary to properly trim the text cache in
- /// `iced_wgpu` and `iced_glow` because of limitations in the text rendering
- /// pipeline. It will be removed in the future.
- fn trim_measurements(&mut self) {}
-}
-
/// A graphics backend that supports text rendering.
pub trait Text {
/// The icon font of the backend.
diff --git a/graphics/src/color.rs b/graphics/src/color.rs
new file mode 100644
index 00000000..92448a68
--- /dev/null
+++ b/graphics/src/color.rs
@@ -0,0 +1,46 @@
+//! Manage colors for shaders.
+use crate::core::Color;
+
+use bytemuck::{Pod, Zeroable};
+
+/// A color packed as 4 floats representing RGBA channels.
+#[derive(Debug, Clone, Copy, PartialEq, Zeroable, Pod)]
+#[repr(C)]
+pub struct Packed([f32; 4]);
+
+impl Packed {
+ /// Returns the internal components of the [`Packed`] color.
+ pub fn components(self) -> [f32; 4] {
+ self.0
+ }
+}
+
+/// A flag that indicates whether the renderer should perform gamma correction.
+pub const GAMMA_CORRECTION: bool = internal::GAMMA_CORRECTION;
+
+/// Packs a [`Color`].
+pub fn pack(color: impl Into<Color>) -> Packed {
+ Packed(internal::pack(color.into()))
+}
+
+#[cfg(not(feature = "web-colors"))]
+mod internal {
+ use crate::core::Color;
+
+ pub const GAMMA_CORRECTION: bool = true;
+
+ pub fn pack(color: Color) -> [f32; 4] {
+ color.into_linear()
+ }
+}
+
+#[cfg(feature = "web-colors")]
+mod internal {
+ use crate::core::Color;
+
+ pub const GAMMA_CORRECTION: bool = false;
+
+ pub fn pack(color: Color) -> [f32; 4] {
+ [color.r, color.g, color.b, color.a]
+ }
+}
diff --git a/graphics/src/compositor.rs b/graphics/src/compositor.rs
index d55e801a..f7b86045 100644
--- a/graphics/src/compositor.rs
+++ b/graphics/src/compositor.rs
@@ -59,6 +59,19 @@ pub trait Compositor: Sized {
background_color: Color,
overlay: &[T],
) -> Result<(), SurfaceError>;
+
+ /// Screenshots the current [`Renderer`] primitives to an offscreen texture, and returns the bytes of
+ /// the texture ordered as `RGBA` in the sRGB color space.
+ ///
+ /// [`Renderer`]: Self::Renderer;
+ fn screenshot<T: AsRef<str>>(
+ &mut self,
+ renderer: &mut Self::Renderer,
+ surface: &mut Self::Surface,
+ viewport: &Viewport,
+ background_color: Color,
+ overlay: &[T],
+ ) -> Vec<u8>;
}
/// Result of an unsuccessful call to [`Compositor::present`].
@@ -82,7 +95,7 @@ pub enum SurfaceError {
OutOfMemory,
}
-/// Contains informations about the graphics (e.g. graphics adapter, graphics backend).
+/// Contains information about the graphics (e.g. graphics adapter, graphics backend).
#[derive(Debug)]
pub struct Information {
/// Contains the graphics adapter.
diff --git a/graphics/src/damage.rs b/graphics/src/damage.rs
index 5aab06b1..c6b0f759 100644
--- a/graphics/src/damage.rs
+++ b/graphics/src/damage.rs
@@ -1,8 +1,10 @@
+//! Track and compute the damage of graphical primitives.
use crate::core::{Rectangle, Size};
use crate::Primitive;
use std::sync::Arc;
+/// Computes the damage regions between the two given primitives.
pub fn regions(a: &Primitive, b: &Primitive) -> Vec<Rectangle> {
match (a, b) {
(
@@ -73,6 +75,7 @@ pub fn regions(a: &Primitive, b: &Primitive) -> Vec<Rectangle> {
}
}
+/// Computes the damage regions between the two given lists of primitives.
pub fn list(previous: &[Primitive], current: &[Primitive]) -> Vec<Rectangle> {
let damage = previous
.iter()
@@ -95,6 +98,8 @@ pub fn list(previous: &[Primitive], current: &[Primitive]) -> Vec<Rectangle> {
}
}
+/// Groups the given damage regions that are close together inside the given
+/// bounds.
pub fn group(
mut damage: Vec<Rectangle>,
scale_factor: f32,
diff --git a/graphics/src/geometry.rs b/graphics/src/geometry.rs
index 8db1594a..729c3d44 100644
--- a/graphics/src/geometry.rs
+++ b/graphics/src/geometry.rs
@@ -1,8 +1,4 @@
-//! Draw 2D graphics for your users.
-//!
-//! A [`Canvas`] widget can be used to draw different kinds of 2D shapes in a
-//! [`Frame`]. It can be used for animation, data visualization, game graphics,
-//! and more!
+//! Build and draw geometry.
pub mod fill;
pub mod path;
pub mod stroke;
@@ -16,10 +12,11 @@ pub use stroke::{LineCap, LineDash, LineJoin, Stroke};
pub use style::Style;
pub use text::Text;
-pub use iced_core::gradient::{self, Gradient};
+pub use crate::gradient::{self, Gradient};
use crate::Primitive;
+/// A bunch of shapes that can be drawn.
#[derive(Debug, Clone)]
pub struct Geometry(pub Primitive);
@@ -29,8 +26,8 @@ impl From<Geometry> for Primitive {
}
}
-pub trait Renderer: iced_core::Renderer {
- type Geometry;
-
- fn draw(&mut self, geometry: Vec<Self::Geometry>);
+/// A renderer capable of drawing some [`Geometry`].
+pub trait Renderer: crate::core::Renderer {
+ /// Draws the given layers of [`Geometry`].
+ fn draw(&mut self, layers: Vec<Geometry>);
}
diff --git a/graphics/src/geometry/fill.rs b/graphics/src/geometry/fill.rs
index 2e8c1669..b773c99b 100644
--- a/graphics/src/geometry/fill.rs
+++ b/graphics/src/geometry/fill.rs
@@ -1,8 +1,9 @@
//! Fill [crate::widget::canvas::Geometry] with a certain style.
-use iced_core::{Color, Gradient};
-
pub use crate::geometry::Style;
+use crate::core::Color;
+use crate::gradient::{self, Gradient};
+
/// The style used to fill geometry.
#[derive(Debug, Clone)]
pub struct Fill {
@@ -49,6 +50,15 @@ impl From<Gradient> for Fill {
}
}
+impl From<gradient::Linear> for Fill {
+ fn from(gradient: gradient::Linear) -> Self {
+ Fill {
+ style: Style::Gradient(Gradient::Linear(gradient)),
+ ..Default::default()
+ }
+ }
+}
+
/// The fill rule defines how to determine what is inside and what is outside of
/// a shape.
///
diff --git a/graphics/src/geometry/path.rs b/graphics/src/geometry/path.rs
index c3127bdf..3d8fc6fa 100644
--- a/graphics/src/geometry/path.rs
+++ b/graphics/src/geometry/path.rs
@@ -53,11 +53,13 @@ impl Path {
Self::new(|p| p.circle(center, radius))
}
+ /// Returns the internal [`lyon_path::Path`].
#[inline]
pub fn raw(&self) -> &lyon_path::Path {
&self.raw
}
+ /// Returns the current [`Path`] with the given transform applied to it.
#[inline]
pub fn transform(&self, transform: &lyon_path::math::Transform) -> Path {
Path {
diff --git a/graphics/src/geometry/style.rs b/graphics/src/geometry/style.rs
index be9ee376..a0f4b08a 100644
--- a/graphics/src/geometry/style.rs
+++ b/graphics/src/geometry/style.rs
@@ -1,4 +1,5 @@
-use iced_core::{Color, Gradient};
+use crate::core::Color;
+use crate::geometry::Gradient;
/// The coloring style of some drawing.
#[derive(Debug, Clone, PartialEq)]
diff --git a/graphics/src/gradient.rs b/graphics/src/gradient.rs
new file mode 100644
index 00000000..3f5d0509
--- /dev/null
+++ b/graphics/src/gradient.rs
@@ -0,0 +1,195 @@
+//! A gradient that can be used as a [`Fill`] for some geometry.
+//!
+//! For a gradient that you can use as a background variant for a widget, see [`Gradient`].
+//!
+//! [`Gradient`]: crate::core::Gradient;
+use crate::color;
+use crate::core::gradient::ColorStop;
+use crate::core::{self, Color, Point, Rectangle};
+
+use half::f16;
+use std::cmp::Ordering;
+
+#[derive(Debug, Clone, PartialEq)]
+/// A fill which linearly interpolates colors along a direction.
+///
+/// For a gradient which can be used as a fill for a background of a widget, see [`crate::core::Gradient`].
+pub enum Gradient {
+ /// A linear gradient interpolates colors along a direction from its `start` to its `end`
+ /// point.
+ Linear(Linear),
+}
+
+impl From<Linear> for Gradient {
+ fn from(gradient: Linear) -> Self {
+ Self::Linear(gradient)
+ }
+}
+
+impl Gradient {
+ /// Packs the [`Gradient`] for use in shader code.
+ pub fn pack(&self) -> Packed {
+ match self {
+ Gradient::Linear(linear) => linear.pack(),
+ }
+ }
+}
+
+/// A linear gradient that can be used in the style of [`Fill`] or [`Stroke`].
+///
+/// [`Fill`]: crate::geometry::Fill;
+/// [`Stroke`]: crate::geometry::Stroke;
+#[derive(Debug, Clone, Copy, PartialEq)]
+pub struct Linear {
+ /// The absolute starting position of the gradient.
+ pub start: Point,
+
+ /// The absolute ending position of the gradient.
+ pub end: Point,
+
+ /// [`ColorStop`]s along the linear gradient direction.
+ pub stops: [Option<ColorStop>; 8],
+}
+
+impl Linear {
+ /// Creates a new [`Builder`].
+ pub fn new(start: Point, end: Point) -> Self {
+ Self {
+ start,
+ end,
+ stops: [None; 8],
+ }
+ }
+
+ /// Adds a new [`ColorStop`], defined by an offset and a color, to the gradient.
+ ///
+ /// Any `offset` that is not within `0.0..=1.0` will be silently ignored.
+ ///
+ /// Any stop added after the 8th will be silently ignored.
+ pub fn add_stop(mut self, offset: f32, color: Color) -> Self {
+ if offset.is_finite() && (0.0..=1.0).contains(&offset) {
+ let (Ok(index) | Err(index)) =
+ self.stops.binary_search_by(|stop| match stop {
+ None => Ordering::Greater,
+ Some(stop) => stop.offset.partial_cmp(&offset).unwrap(),
+ });
+
+ if index < 8 {
+ self.stops[index] = Some(ColorStop { offset, color });
+ }
+ } else {
+ log::warn!("Gradient: ColorStop must be within 0.0..=1.0 range.");
+ };
+
+ self
+ }
+
+ /// Adds multiple [`ColorStop`]s to the gradient.
+ ///
+ /// Any stop added after the 8th will be silently ignored.
+ pub fn add_stops(
+ mut self,
+ stops: impl IntoIterator<Item = ColorStop>,
+ ) -> Self {
+ for stop in stops.into_iter() {
+ self = self.add_stop(stop.offset, stop.color)
+ }
+
+ self
+ }
+
+ /// Packs the [`Gradient`] for use in shader code.
+ pub fn pack(&self) -> Packed {
+ let mut colors = [[0u32; 2]; 8];
+ let mut offsets = [f16::from(0u8); 8];
+
+ for (index, stop) in self.stops.iter().enumerate() {
+ let [r, g, b, a] =
+ color::pack(stop.map_or(Color::default(), |s| s.color))
+ .components();
+
+ colors[index] = [
+ pack_f16s([f16::from_f32(r), f16::from_f32(g)]),
+ pack_f16s([f16::from_f32(b), f16::from_f32(a)]),
+ ];
+
+ offsets[index] =
+ stop.map_or(f16::from_f32(2.0), |s| f16::from_f32(s.offset));
+ }
+
+ let offsets = [
+ pack_f16s([offsets[0], offsets[1]]),
+ pack_f16s([offsets[2], offsets[3]]),
+ pack_f16s([offsets[4], offsets[5]]),
+ pack_f16s([offsets[6], offsets[7]]),
+ ];
+
+ let direction = [self.start.x, self.start.y, self.end.x, self.end.y];
+
+ Packed {
+ colors,
+ offsets,
+ direction,
+ }
+ }
+}
+
+/// Packed [`Gradient`] data for use in shader code.
+#[derive(Debug, Copy, Clone, PartialEq)]
+#[repr(C)]
+pub struct Packed {
+ // 8 colors, each channel = 16 bit float, 2 colors packed into 1 u32
+ colors: [[u32; 2]; 8],
+ // 8 offsets, 8x 16 bit floats packed into 4 u32s
+ offsets: [u32; 4],
+ direction: [f32; 4],
+}
+
+/// Creates a new [`Packed`] gradient for use in shader code.
+pub fn pack(gradient: &core::Gradient, bounds: Rectangle) -> Packed {
+ match gradient {
+ core::Gradient::Linear(linear) => {
+ let mut colors = [[0u32; 2]; 8];
+ let mut offsets = [f16::from(0u8); 8];
+
+ for (index, stop) in linear.stops.iter().enumerate() {
+ let [r, g, b, a] =
+ color::pack(stop.map_or(Color::default(), |s| s.color))
+ .components();
+
+ colors[index] = [
+ pack_f16s([f16::from_f32(r), f16::from_f32(g)]),
+ pack_f16s([f16::from_f32(b), f16::from_f32(a)]),
+ ];
+
+ offsets[index] = stop
+ .map_or(f16::from_f32(2.0), |s| f16::from_f32(s.offset));
+ }
+
+ let offsets = [
+ pack_f16s([offsets[0], offsets[1]]),
+ pack_f16s([offsets[2], offsets[3]]),
+ pack_f16s([offsets[4], offsets[5]]),
+ pack_f16s([offsets[6], offsets[7]]),
+ ];
+
+ let (start, end) = linear.angle.to_distance(&bounds);
+
+ let direction = [start.x, start.y, end.x, end.y];
+
+ Packed {
+ colors,
+ offsets,
+ direction,
+ }
+ }
+ }
+}
+
+/// Packs two f16s into one u32.
+fn pack_f16s(f: [f16; 2]) -> u32 {
+ let one = (f[0].to_bits() as u32) << 16;
+ let two = f[1].to_bits() as u32;
+
+ one | two
+}
diff --git a/graphics/src/image.rs b/graphics/src/image.rs
index 2f634252..6b43f4a8 100644
--- a/graphics/src/image.rs
+++ b/graphics/src/image.rs
@@ -5,6 +5,7 @@ use bitflags::bitflags;
pub use ::image as image_rs;
+/// Tries to load an image by its [`Handle`].
pub fn load(handle: &Handle) -> image_rs::ImageResult<image_rs::DynamicImage> {
match handle.data() {
Data::Path(path) => {
diff --git a/graphics/src/lib.rs b/graphics/src/lib.rs
index e3de4025..226b245b 100644
--- a/graphics/src/lib.rs
+++ b/graphics/src/lib.rs
@@ -9,7 +9,7 @@
)]
#![deny(
missing_debug_implementations,
- //missing_docs,
+ missing_docs,
unsafe_code,
unused_results,
clippy::extra_unused_lifetimes,
@@ -20,15 +20,17 @@
)]
#![forbid(rust_2018_idioms)]
#![allow(clippy::inherent_to_string, clippy::type_complexity)]
-#![cfg_attr(docsrs, feature(doc_cfg))]
+#![cfg_attr(docsrs, feature(doc_auto_cfg))]
mod antialiasing;
mod error;
mod transformation;
mod viewport;
pub mod backend;
+pub mod color;
pub mod compositor;
pub mod damage;
+pub mod gradient;
pub mod primitive;
pub mod renderer;
@@ -39,9 +41,9 @@ pub mod geometry;
pub mod image;
pub use antialiasing::Antialiasing;
-pub use backend::Backend;
pub use compositor::Compositor;
pub use error::Error;
+pub use gradient::Gradient;
pub use primitive::Primitive;
pub use renderer::Renderer;
pub use transformation::Transformation;
diff --git a/graphics/src/primitive.rs b/graphics/src/primitive.rs
index d814c757..187c6c6c 100644
--- a/graphics/src/primitive.rs
+++ b/graphics/src/primitive.rs
@@ -1,8 +1,11 @@
+//! Draw using different graphical primitives.
+use crate::color;
use crate::core::alignment;
use crate::core::image;
use crate::core::svg;
use crate::core::text;
-use crate::core::{Background, Color, Font, Gradient, Rectangle, Size, Vector};
+use crate::core::{Background, Color, Font, Rectangle, Size, Vector};
+use crate::gradient;
use bytemuck::{Pod, Zeroable};
use std::sync::Arc;
@@ -38,7 +41,7 @@ pub enum Primitive {
bounds: Rectangle,
/// The background of the quad
background: Background,
- /// The border radius of the quad
+ /// The border radii of the quad
border_radius: [f32; 4],
/// The border width of the quad
border_width: f32,
@@ -80,28 +83,35 @@ pub enum Primitive {
/// It can be used to render many kinds of geometry freely.
GradientMesh {
/// The vertices and indices of the mesh.
- buffers: Mesh2D<Vertex2D>,
+ buffers: Mesh2D<GradientVertex2D>,
/// The size of the drawable region of the mesh.
///
/// Any geometry that falls out of this region will be clipped.
size: Size,
-
- /// The [`Gradient`] to apply to the mesh.
- gradient: Gradient,
},
+ /// A [`tiny_skia`] path filled with some paint.
#[cfg(feature = "tiny-skia")]
Fill {
+ /// The path to fill.
path: tiny_skia::Path,
+ /// The paint to use.
paint: tiny_skia::Paint<'static>,
+ /// The fill rule to follow.
rule: tiny_skia::FillRule,
+ /// The transform to apply to the path.
transform: tiny_skia::Transform,
},
+ /// A [`tiny_skia`] path stroked with some paint.
#[cfg(feature = "tiny-skia")]
Stroke {
+ /// The path to stroke.
path: tiny_skia::Path,
+ /// The paint to use.
paint: tiny_skia::Paint<'static>,
+ /// The stroke settings.
stroke: tiny_skia::Stroke,
+ /// The transform to apply to the path.
transform: tiny_skia::Transform,
},
/// A group of primitives
@@ -135,10 +145,12 @@ pub enum Primitive {
}
impl Primitive {
+ /// Creates a [`Primitive::Group`].
pub fn group(primitives: Vec<Self>) -> Self {
Self::Group { primitives }
}
+ /// Creates a [`Primitive::Clip`].
pub fn clip(self, bounds: Rectangle) -> Self {
Self::Clip {
bounds,
@@ -146,6 +158,7 @@ impl Primitive {
}
}
+ /// Creates a [`Primitive::Translate`].
pub fn translate(self, translation: Vector) -> Self {
Self::Translate {
translation,
@@ -153,6 +166,7 @@ impl Primitive {
}
}
+ /// Returns the bounds of the [`Primitive`].
pub fn bounds(&self) -> Rectangle {
match self {
Self::Text {
@@ -227,25 +241,34 @@ pub struct Mesh2D<T> {
pub indices: Vec<u32>,
}
-/// A two-dimensional vertex.
+/// A two-dimensional vertex with a color.
#[derive(Copy, Clone, Debug, PartialEq, Zeroable, Pod)]
#[repr(C)]
-pub struct Vertex2D {
+pub struct ColoredVertex2D {
/// The vertex position in 2D space.
pub position: [f32; 2],
+
+ /// The color of the vertex in __linear__ RGBA.
+ pub color: color::Packed,
}
-/// A two-dimensional vertex with a color.
-#[derive(Copy, Clone, Debug, PartialEq, Zeroable, Pod)]
+/// A vertex which contains 2D position & packed gradient data.
+#[derive(Copy, Clone, Debug, PartialEq)]
#[repr(C)]
-pub struct ColoredVertex2D {
+pub struct GradientVertex2D {
/// The vertex position in 2D space.
pub position: [f32; 2],
- /// The color of the vertex in __linear__ RGBA.
- pub color: [f32; 4],
+ /// The packed vertex data of the gradient.
+ pub gradient: gradient::Packed,
}
+#[allow(unsafe_code)]
+unsafe impl Zeroable for GradientVertex2D {}
+
+#[allow(unsafe_code)]
+unsafe impl Pod for GradientVertex2D {}
+
impl From<()> for Primitive {
fn from(_: ()) -> Self {
Self::Group { primitives: vec![] }
diff --git a/graphics/src/renderer.rs b/graphics/src/renderer.rs
index aaf1737a..4241d45c 100644
--- a/graphics/src/renderer.rs
+++ b/graphics/src/renderer.rs
@@ -1,5 +1,5 @@
//! Create a renderer from a [`Backend`].
-use crate::backend::{self, Backend};
+use crate::backend;
use crate::Primitive;
use iced_core::image;
@@ -16,13 +16,13 @@ use std::marker::PhantomData;
/// A backend-agnostic renderer that supports all the built-in widgets.
#[derive(Debug)]
-pub struct Renderer<B: Backend, Theme> {
+pub struct Renderer<B, Theme> {
backend: B,
primitives: Vec<Primitive>,
theme: PhantomData<Theme>,
}
-impl<B: Backend, T> Renderer<B, T> {
+impl<B, T> Renderer<B, T> {
/// Creates a new [`Renderer`] from the given [`Backend`].
pub fn new(backend: B) -> Self {
Self {
@@ -52,10 +52,7 @@ impl<B: Backend, T> Renderer<B, T> {
}
}
-impl<B, T> iced_core::Renderer for Renderer<B, T>
-where
- B: Backend,
-{
+impl<B, T> iced_core::Renderer for Renderer<B, T> {
type Theme = T;
fn layout<Message>(
@@ -63,11 +60,7 @@ where
element: &Element<'_, Message, Self>,
limits: &layout::Limits,
) -> layout::Node {
- let layout = element.as_widget().layout(self, limits);
-
- self.backend.trim_measurements();
-
- layout
+ element.as_widget().layout(self, limits)
}
fn with_layer(&mut self, bounds: Rectangle, f: impl FnOnce(&mut Self)) {
@@ -116,7 +109,7 @@ where
impl<B, T> text::Renderer for Renderer<B, T>
where
- B: Backend + backend::Text,
+ B: backend::Text,
{
type Font = Font;
@@ -195,7 +188,7 @@ where
impl<B, T> image::Renderer for Renderer<B, T>
where
- B: Backend + backend::Image,
+ B: backend::Image,
{
type Handle = image::Handle;
@@ -210,7 +203,7 @@ where
impl<B, T> svg::Renderer for Renderer<B, T>
where
- B: Backend + backend::Svg,
+ B: backend::Svg,
{
fn dimensions(&self, handle: &svg::Handle) -> Size<u32> {
self.backend().viewport_dimensions(handle)
@@ -231,13 +224,8 @@ where
}
#[cfg(feature = "geometry")]
-impl<B, T> crate::geometry::Renderer for Renderer<B, T>
-where
- B: Backend,
-{
- type Geometry = crate::Geometry;
-
- fn draw(&mut self, layers: Vec<Self::Geometry>) {
+impl<B, T> crate::geometry::Renderer for Renderer<B, T> {
+ fn draw(&mut self, layers: Vec<crate::Geometry>) {
self.primitives
.extend(layers.into_iter().map(crate::Geometry::into));
}
diff --git a/graphics/src/triangle.rs b/graphics/src/triangle.rs
deleted file mode 100644
index 09b61767..00000000
--- a/graphics/src/triangle.rs
+++ /dev/null
@@ -1 +0,0 @@
-//! Draw geometry using meshes of triangles.
diff --git a/renderer/Cargo.toml b/renderer/Cargo.toml
index 17657888..ddfb6445 100644
--- a/renderer/Cargo.toml
+++ b/renderer/Cargo.toml
@@ -5,11 +5,11 @@ edition = "2021"
[features]
wgpu = ["iced_wgpu"]
-tiny-skia = ["iced_tiny_skia"]
-image = ["iced_wgpu/image", "iced_tiny_skia/image"]
-svg = ["iced_wgpu/svg", "iced_tiny_skia/svg"]
-geometry = ["iced_graphics/geometry", "iced_wgpu?/geometry", "iced_tiny_skia?/geometry"]
-tracing = ["iced_wgpu/tracing"]
+image = ["iced_tiny_skia/image", "iced_wgpu?/image"]
+svg = ["iced_tiny_skia/svg", "iced_wgpu?/svg"]
+geometry = ["iced_graphics/geometry", "iced_tiny_skia/geometry", "iced_wgpu?/geometry"]
+tracing = ["iced_wgpu?/tracing"]
+web-colors = ["iced_wgpu?/web-colors"]
[dependencies]
raw-window-handle = "0.5"
@@ -19,12 +19,11 @@ thiserror = "1"
version = "0.8"
path = "../graphics"
-[dependencies.iced_wgpu]
-version = "0.10"
-path = "../wgpu"
-optional = true
-
[dependencies.iced_tiny_skia]
version = "0.1"
path = "../tiny_skia"
+
+[dependencies.iced_wgpu]
+version = "0.10"
+path = "../wgpu"
optional = true
diff --git a/renderer/src/backend.rs b/renderer/src/backend.rs
index c1bec6af..18f9f3fc 100644
--- a/renderer/src/backend.rs
+++ b/renderer/src/backend.rs
@@ -6,29 +6,21 @@ use std::borrow::Cow;
#[allow(clippy::large_enum_variant)]
pub enum Backend {
+ TinySkia(iced_tiny_skia::Backend),
#[cfg(feature = "wgpu")]
Wgpu(iced_wgpu::Backend),
- #[cfg(feature = "tiny-skia")]
- TinySkia(iced_tiny_skia::Backend),
}
macro_rules! delegate {
($backend:expr, $name:ident, $body:expr) => {
match $backend {
+ Self::TinySkia($name) => $body,
#[cfg(feature = "wgpu")]
Self::Wgpu($name) => $body,
- #[cfg(feature = "tiny-skia")]
- Self::TinySkia($name) => $body,
}
};
}
-impl iced_graphics::Backend for Backend {
- fn trim_measurements(&mut self) {
- delegate!(self, backend, backend.trim_measurements());
- }
-}
-
impl backend::Text for Backend {
const ICON_FONT: Font = Font::with_name("Iced-Icons");
const CHECKMARK_ICON: char = '\u{f00c}';
diff --git a/renderer/src/compositor.rs b/renderer/src/compositor.rs
index 48ed4b1f..57317b28 100644
--- a/renderer/src/compositor.rs
+++ b/renderer/src/compositor.rs
@@ -7,17 +7,15 @@ use raw_window_handle::{HasRawDisplayHandle, HasRawWindowHandle};
use std::env;
pub enum Compositor<Theme> {
+ TinySkia(iced_tiny_skia::window::Compositor<Theme>),
#[cfg(feature = "wgpu")]
Wgpu(iced_wgpu::window::Compositor<Theme>),
- #[cfg(feature = "tiny-skia")]
- TinySkia(iced_tiny_skia::window::Compositor<Theme>),
}
pub enum Surface {
+ TinySkia(iced_tiny_skia::window::Surface),
#[cfg(feature = "wgpu")]
Wgpu(iced_wgpu::window::Surface),
- #[cfg(feature = "tiny-skia")]
- TinySkia(iced_tiny_skia::window::Surface),
}
impl<Theme> crate::graphics::Compositor for Compositor<Theme> {
@@ -55,14 +53,13 @@ impl<Theme> crate::graphics::Compositor for Compositor<Theme> {
height: u32,
) -> Surface {
match self {
+ Self::TinySkia(compositor) => Surface::TinySkia(
+ compositor.create_surface(window, width, height),
+ ),
#[cfg(feature = "wgpu")]
Self::Wgpu(compositor) => {
Surface::Wgpu(compositor.create_surface(window, width, height))
}
- #[cfg(feature = "tiny-skia")]
- Self::TinySkia(compositor) => Surface::TinySkia(
- compositor.create_surface(window, width, height),
- ),
}
}
@@ -73,12 +70,11 @@ impl<Theme> crate::graphics::Compositor for Compositor<Theme> {
height: u32,
) {
match (self, surface) {
- #[cfg(feature = "wgpu")]
- (Self::Wgpu(compositor), Surface::Wgpu(surface)) => {
+ (Self::TinySkia(compositor), Surface::TinySkia(surface)) => {
compositor.configure_surface(surface, width, height);
}
- #[cfg(feature = "tiny-skia")]
- (Self::TinySkia(compositor), Surface::TinySkia(surface)) => {
+ #[cfg(feature = "wgpu")]
+ (Self::Wgpu(compositor), Surface::Wgpu(surface)) => {
compositor.configure_surface(surface, width, height);
}
#[allow(unreachable_patterns)]
@@ -90,10 +86,9 @@ impl<Theme> crate::graphics::Compositor for Compositor<Theme> {
fn fetch_information(&self) -> Information {
match self {
+ Self::TinySkia(compositor) => compositor.fetch_information(),
#[cfg(feature = "wgpu")]
Self::Wgpu(compositor) => compositor.fetch_information(),
- #[cfg(feature = "tiny-skia")]
- Self::TinySkia(compositor) => compositor.fetch_information(),
}
}
@@ -107,13 +102,11 @@ impl<Theme> crate::graphics::Compositor for Compositor<Theme> {
) -> Result<(), SurfaceError> {
renderer.with_primitives(|backend, primitives| {
match (self, backend, surface) {
- #[cfg(feature = "wgpu")]
(
- Self::Wgpu(compositor),
- crate::Backend::Wgpu(backend),
- Surface::Wgpu(surface),
- ) => iced_wgpu::window::compositor::present(
- compositor,
+ Self::TinySkia(_compositor),
+ crate::Backend::TinySkia(backend),
+ Surface::TinySkia(surface),
+ ) => iced_tiny_skia::window::compositor::present(
backend,
surface,
primitives,
@@ -121,12 +114,13 @@ impl<Theme> crate::graphics::Compositor for Compositor<Theme> {
background_color,
overlay,
),
- #[cfg(feature = "tiny-skia")]
+ #[cfg(feature = "wgpu")]
(
- Self::TinySkia(_compositor),
- crate::Backend::TinySkia(backend),
- Surface::TinySkia(surface),
- ) => iced_tiny_skia::window::compositor::present(
+ Self::Wgpu(compositor),
+ crate::Backend::Wgpu(backend),
+ Surface::Wgpu(surface),
+ ) => iced_wgpu::window::compositor::present(
+ compositor,
backend,
surface,
primitives,
@@ -142,6 +136,36 @@ impl<Theme> crate::graphics::Compositor for Compositor<Theme> {
}
})
}
+
+ fn screenshot<T: AsRef<str>>(
+ &mut self,
+ renderer: &mut Self::Renderer,
+ surface: &mut Self::Surface,
+ viewport: &Viewport,
+ background_color: Color,
+ overlay: &[T],
+ ) -> Vec<u8> {
+ renderer.with_primitives(|backend, primitives| match (self, backend, surface) {
+ (Self::TinySkia(_compositor), crate::Backend::TinySkia(backend), Surface::TinySkia(surface)) => {
+ iced_tiny_skia::window::compositor::screenshot(surface, backend, primitives, viewport, background_color, overlay)
+ },
+ #[cfg(feature = "wgpu")]
+ (Self::Wgpu(compositor), crate::Backend::Wgpu(backend), Surface::Wgpu(_)) => {
+ iced_wgpu::window::compositor::screenshot(
+ compositor,
+ backend,
+ primitives,
+ viewport,
+ background_color,
+ overlay,
+ )
+ },
+ #[allow(unreachable_patterns)]
+ _ => panic!(
+ "The provided renderer or backend are not compatible with the compositor."
+ ),
+ })
+ }
}
enum Candidate {
@@ -154,7 +178,6 @@ impl Candidate {
vec![
#[cfg(feature = "wgpu")]
Self::Wgpu,
- #[cfg(feature = "tiny-skia")]
Self::TinySkia,
]
}
@@ -181,6 +204,20 @@ impl Candidate {
_compatible_window: Option<&W>,
) -> Result<(Compositor<Theme>, Renderer<Theme>), Error> {
match self {
+ Self::TinySkia => {
+ let (compositor, backend) =
+ iced_tiny_skia::window::compositor::new(
+ iced_tiny_skia::Settings {
+ default_font: settings.default_font,
+ default_text_size: settings.default_text_size,
+ },
+ );
+
+ Ok((
+ Compositor::TinySkia(compositor),
+ Renderer::new(crate::Backend::TinySkia(backend)),
+ ))
+ }
#[cfg(feature = "wgpu")]
Self::Wgpu => {
let (compositor, backend) = iced_wgpu::window::compositor::new(
@@ -198,31 +235,10 @@ impl Candidate {
Renderer::new(crate::Backend::Wgpu(backend)),
))
}
- #[cfg(feature = "tiny-skia")]
- Self::TinySkia => {
- let (compositor, backend) =
- iced_tiny_skia::window::compositor::new(
- iced_tiny_skia::Settings {
- default_font: settings.default_font,
- default_text_size: settings.default_text_size,
- },
- );
-
- Ok((
- Compositor::TinySkia(compositor),
- Renderer::new(crate::Backend::TinySkia(backend)),
- ))
- }
#[cfg(not(feature = "wgpu"))]
Self::Wgpu => {
panic!("`wgpu` feature was not enabled in `iced_renderer`")
}
- #[cfg(not(feature = "tiny-skia"))]
- Self::TinySkia => {
- panic!(
- "`tiny-skia` feature was not enabled in `iced_renderer`"
- );
- }
}
}
}
diff --git a/renderer/src/geometry.rs b/renderer/src/geometry.rs
index 21ef2c06..26e2fed0 100644
--- a/renderer/src/geometry.rs
+++ b/renderer/src/geometry.rs
@@ -7,19 +7,17 @@ use crate::graphics::geometry::{Fill, Geometry, Path, Stroke, Text};
use crate::Backend;
pub enum Frame {
+ TinySkia(iced_tiny_skia::geometry::Frame),
#[cfg(feature = "wgpu")]
Wgpu(iced_wgpu::geometry::Frame),
- #[cfg(feature = "tiny-skia")]
- TinySkia(iced_tiny_skia::geometry::Frame),
}
macro_rules! delegate {
($frame:expr, $name:ident, $body:expr) => {
match $frame {
+ Self::TinySkia($name) => $body,
#[cfg(feature = "wgpu")]
Self::Wgpu($name) => $body,
- #[cfg(feature = "tiny-skia")]
- Self::TinySkia($name) => $body,
}
};
}
@@ -27,14 +25,13 @@ macro_rules! delegate {
impl Frame {
pub fn new<Theme>(renderer: &crate::Renderer<Theme>, size: Size) -> Self {
match renderer.backend() {
+ Backend::TinySkia(_) => {
+ Frame::TinySkia(iced_tiny_skia::geometry::Frame::new(size))
+ }
#[cfg(feature = "wgpu")]
Backend::Wgpu(_) => {
Frame::Wgpu(iced_wgpu::geometry::Frame::new(size))
}
- #[cfg(feature = "tiny-skia")]
- Backend::TinySkia(_) => {
- Frame::TinySkia(iced_tiny_skia::geometry::Frame::new(size))
- }
}
}
@@ -127,28 +124,26 @@ impl Frame {
#[inline]
pub fn with_clip(&mut self, region: Rectangle, f: impl FnOnce(&mut Frame)) {
let mut frame = match self {
+ Self::TinySkia(_) => Self::TinySkia(
+ iced_tiny_skia::geometry::Frame::new(region.size()),
+ ),
#[cfg(feature = "wgpu")]
Self::Wgpu(_) => {
Self::Wgpu(iced_wgpu::geometry::Frame::new(region.size()))
}
- #[cfg(feature = "tiny-skia")]
- Self::TinySkia(_) => Self::TinySkia(
- iced_tiny_skia::geometry::Frame::new(region.size()),
- ),
};
f(&mut frame);
- let translation = Vector::new(region.x, region.y);
+ let origin = Point::new(region.x, region.y);
match (self, frame) {
+ (Self::TinySkia(target), Self::TinySkia(frame)) => {
+ target.clip(frame, origin);
+ }
#[cfg(feature = "wgpu")]
(Self::Wgpu(target), Self::Wgpu(frame)) => {
- target.clip(frame, translation);
- }
- #[cfg(feature = "tiny-skia")]
- (Self::TinySkia(target), Self::TinySkia(frame)) => {
- target.clip(frame, translation);
+ target.clip(frame, origin);
}
#[allow(unreachable_patterns)]
_ => unreachable!(),
diff --git a/renderer/src/lib.rs b/renderer/src/lib.rs
index ba737b11..22ec7bd1 100644
--- a/renderer/src/lib.rs
+++ b/renderer/src/lib.rs
@@ -1,6 +1,3 @@
-#[cfg(not(any(feature = "wgpu", feature = "tiny-skia")))]
-compile_error!("No backend selected. Enable at least one backend feature: `wgpu` or `tiny-skia`.");
-
pub mod compositor;
#[cfg(feature = "geometry")]
diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs
index 8a277e47..4bbf9687 100644
--- a/runtime/src/lib.rs
+++ b/runtime/src/lib.rs
@@ -33,7 +33,7 @@
)]
#![deny(
missing_debug_implementations,
- //missing_docs,
+ missing_docs,
unused_results,
clippy::extra_unused_lifetimes,
clippy::from_over_into,
@@ -42,11 +42,12 @@
clippy::useless_conversion
)]
#![forbid(unsafe_code, rust_2018_idioms)]
-#![cfg_attr(docsrs, feature(doc_cfg))]
+#![cfg_attr(docsrs, feature(doc_auto_cfg))]
pub mod clipboard;
pub mod command;
pub mod font;
pub mod keyboard;
+pub mod overlay;
pub mod program;
pub mod system;
pub mod user_interface;
diff --git a/runtime/src/overlay.rs b/runtime/src/overlay.rs
new file mode 100644
index 00000000..03390980
--- /dev/null
+++ b/runtime/src/overlay.rs
@@ -0,0 +1,4 @@
+//! Overlays for user interfaces.
+mod nested;
+
+pub use nested::Nested;
diff --git a/runtime/src/overlay/nested.rs b/runtime/src/overlay/nested.rs
new file mode 100644
index 00000000..b729f769
--- /dev/null
+++ b/runtime/src/overlay/nested.rs
@@ -0,0 +1,353 @@
+use crate::core::event;
+use crate::core::layout;
+use crate::core::mouse;
+use crate::core::overlay;
+use crate::core::renderer;
+use crate::core::widget;
+use crate::core::{Clipboard, Event, Layout, Point, Rectangle, Shell, Size};
+
+/// An [`Overlay`] container that displays nested overlays
+#[allow(missing_debug_implementations)]
+pub struct Nested<'a, Message, Renderer> {
+ overlay: overlay::Element<'a, Message, Renderer>,
+}
+
+impl<'a, Message, Renderer> Nested<'a, Message, Renderer>
+where
+ Renderer: renderer::Renderer,
+{
+ /// Creates a nested overlay from the provided [`overlay::Element`]
+ pub fn new(element: overlay::Element<'a, Message, Renderer>) -> Self {
+ Self { overlay: element }
+ }
+
+ /// Returns the position of the [`Nested`] overlay.
+ pub fn position(&self) -> Point {
+ self.overlay.position()
+ }
+
+ /// Returns the layout [`Node`] of the [`Nested`] overlay.
+ pub fn layout(
+ &mut self,
+ renderer: &Renderer,
+ bounds: Size,
+ position: Point,
+ ) -> layout::Node {
+ fn recurse<Message, Renderer>(
+ element: &mut overlay::Element<'_, Message, Renderer>,
+ renderer: &Renderer,
+ bounds: Size,
+ position: Point,
+ ) -> layout::Node
+ where
+ Renderer: renderer::Renderer,
+ {
+ let translation = position - Point::ORIGIN;
+
+ let node = element.layout(renderer, bounds, translation);
+
+ if let Some(mut nested) =
+ element.overlay(Layout::new(&node), renderer)
+ {
+ layout::Node::with_children(
+ node.size(),
+ vec![
+ node,
+ recurse(&mut nested, renderer, bounds, position),
+ ],
+ )
+ } else {
+ layout::Node::with_children(node.size(), vec![node])
+ }
+ }
+
+ recurse(&mut self.overlay, renderer, bounds, position)
+ }
+
+ /// Draws the [`Nested`] overlay using the associated `Renderer`.
+ pub fn draw(
+ &mut self,
+ renderer: &mut Renderer,
+ theme: &<Renderer as renderer::Renderer>::Theme,
+ style: &renderer::Style,
+ layout: Layout<'_>,
+ cursor: mouse::Cursor,
+ ) {
+ fn recurse<Message, Renderer>(
+ element: &mut overlay::Element<'_, Message, Renderer>,
+ layout: Layout<'_>,
+ renderer: &mut Renderer,
+ theme: &<Renderer as renderer::Renderer>::Theme,
+ style: &renderer::Style,
+ cursor: mouse::Cursor,
+ ) where
+ Renderer: renderer::Renderer,
+ {
+ let mut layouts = layout.children();
+
+ if let Some(layout) = layouts.next() {
+ let nested_layout = layouts.next();
+
+ let is_over = cursor
+ .position()
+ .zip(nested_layout)
+ .and_then(|(cursor_position, nested_layout)| {
+ element.overlay(layout, renderer).map(|nested| {
+ nested.is_over(
+ nested_layout.children().next().unwrap(),
+ renderer,
+ cursor_position,
+ )
+ })
+ })
+ .unwrap_or_default();
+
+ renderer.with_layer(layout.bounds(), |renderer| {
+ element.draw(
+ renderer,
+ theme,
+ style,
+ layout,
+ if is_over {
+ mouse::Cursor::Unavailable
+ } else {
+ cursor
+ },
+ );
+ });
+
+ if let Some((mut nested, nested_layout)) =
+ element.overlay(layout, renderer).zip(nested_layout)
+ {
+ recurse(
+ &mut nested,
+ nested_layout,
+ renderer,
+ theme,
+ style,
+ cursor,
+ );
+ }
+ }
+ }
+
+ recurse(&mut self.overlay, layout, renderer, theme, style, cursor);
+ }
+
+ /// Applies a [`widget::Operation`] to the [`Nested`] overlay.
+ pub fn operate(
+ &mut self,
+ layout: Layout<'_>,
+ renderer: &Renderer,
+ operation: &mut dyn widget::Operation<Message>,
+ ) {
+ fn recurse<Message, Renderer>(
+ element: &mut overlay::Element<'_, Message, Renderer>,
+ layout: Layout<'_>,
+ renderer: &Renderer,
+ operation: &mut dyn widget::Operation<Message>,
+ ) where
+ Renderer: renderer::Renderer,
+ {
+ let mut layouts = layout.children();
+
+ if let Some(layout) = layouts.next() {
+ element.operate(layout, renderer, operation);
+
+ if let Some((mut nested, nested_layout)) =
+ element.overlay(layout, renderer).zip(layouts.next())
+ {
+ recurse(&mut nested, nested_layout, renderer, operation);
+ }
+ }
+ }
+
+ recurse(&mut self.overlay, layout, renderer, operation)
+ }
+
+ /// Processes a runtime [`Event`].
+ pub fn on_event(
+ &mut self,
+ event: Event,
+ layout: Layout<'_>,
+ cursor: mouse::Cursor,
+ renderer: &Renderer,
+ clipboard: &mut dyn Clipboard,
+ shell: &mut Shell<'_, Message>,
+ ) -> event::Status {
+ fn recurse<Message, Renderer>(
+ element: &mut overlay::Element<'_, Message, Renderer>,
+ layout: Layout<'_>,
+ event: Event,
+ cursor: mouse::Cursor,
+ renderer: &Renderer,
+ clipboard: &mut dyn Clipboard,
+ shell: &mut Shell<'_, Message>,
+ ) -> (event::Status, bool)
+ where
+ Renderer: renderer::Renderer,
+ {
+ let mut layouts = layout.children();
+
+ if let Some(layout) = layouts.next() {
+ let (nested_status, nested_is_over) =
+ if let Some((mut nested, nested_layout)) =
+ element.overlay(layout, renderer).zip(layouts.next())
+ {
+ recurse(
+ &mut nested,
+ nested_layout,
+ event.clone(),
+ cursor,
+ renderer,
+ clipboard,
+ shell,
+ )
+ } else {
+ (event::Status::Ignored, false)
+ };
+
+ if matches!(nested_status, event::Status::Ignored) {
+ let is_over = nested_is_over
+ || cursor
+ .position()
+ .map(|cursor_position| {
+ element.is_over(
+ layout,
+ renderer,
+ cursor_position,
+ )
+ })
+ .unwrap_or_default();
+
+ (
+ element.on_event(
+ event,
+ layout,
+ if nested_is_over {
+ mouse::Cursor::Unavailable
+ } else {
+ cursor
+ },
+ renderer,
+ clipboard,
+ shell,
+ ),
+ is_over,
+ )
+ } else {
+ (nested_status, nested_is_over)
+ }
+ } else {
+ (event::Status::Ignored, false)
+ }
+ }
+
+ let (status, _) = recurse(
+ &mut self.overlay,
+ layout,
+ event,
+ cursor,
+ renderer,
+ clipboard,
+ shell,
+ );
+
+ status
+ }
+
+ /// Returns the current [`mouse::Interaction`] of the [`Nested`] overlay.
+ pub fn mouse_interaction(
+ &mut self,
+ layout: Layout<'_>,
+ cursor: mouse::Cursor,
+ viewport: &Rectangle,
+ renderer: &Renderer,
+ ) -> mouse::Interaction {
+ fn recurse<Message, Renderer>(
+ element: &mut overlay::Element<'_, Message, Renderer>,
+ layout: Layout<'_>,
+ cursor: mouse::Cursor,
+ viewport: &Rectangle,
+ renderer: &Renderer,
+ ) -> Option<mouse::Interaction>
+ where
+ Renderer: renderer::Renderer,
+ {
+ let mut layouts = layout.children();
+
+ let layout = layouts.next()?;
+ let cursor_position = cursor.position()?;
+
+ if !element.is_over(layout, renderer, cursor_position) {
+ return None;
+ }
+
+ Some(
+ element
+ .overlay(layout, renderer)
+ .zip(layouts.next())
+ .and_then(|(mut overlay, layout)| {
+ recurse(
+ &mut overlay,
+ layout,
+ cursor,
+ viewport,
+ renderer,
+ )
+ })
+ .unwrap_or_else(|| {
+ element.mouse_interaction(
+ layout, cursor, viewport, renderer,
+ )
+ }),
+ )
+ }
+
+ recurse(&mut self.overlay, layout, cursor, viewport, renderer)
+ .unwrap_or_default()
+ }
+
+ /// Returns true if the cursor is over the [`Nested`] overlay.
+ pub fn is_over(
+ &mut self,
+ layout: Layout<'_>,
+ renderer: &Renderer,
+ cursor_position: Point,
+ ) -> bool {
+ fn recurse<Message, Renderer>(
+ element: &mut overlay::Element<'_, Message, Renderer>,
+ layout: Layout<'_>,
+ renderer: &Renderer,
+ cursor_position: Point,
+ ) -> bool
+ where
+ Renderer: renderer::Renderer,
+ {
+ let mut layouts = layout.children();
+
+ if let Some(layout) = layouts.next() {
+ if element.is_over(layout, renderer, cursor_position) {
+ return true;
+ }
+
+ if let Some((mut nested, nested_layout)) =
+ element.overlay(layout, renderer).zip(layouts.next())
+ {
+ recurse(
+ &mut nested,
+ nested_layout,
+ renderer,
+ cursor_position,
+ )
+ } else {
+ false
+ }
+ } else {
+ false
+ }
+ }
+
+ recurse(&mut self.overlay, layout, renderer, cursor_position)
+ }
+}
diff --git a/runtime/src/program/state.rs b/runtime/src/program/state.rs
index 2fa9934d..d83e3f54 100644
--- a/runtime/src/program/state.rs
+++ b/runtime/src/program/state.rs
@@ -1,7 +1,7 @@
use crate::core::event::{self, Event};
use crate::core::mouse;
use crate::core::renderer;
-use crate::core::{Clipboard, Point, Size};
+use crate::core::{Clipboard, Size};
use crate::user_interface::{self, UserInterface};
use crate::{Command, Debug, Program};
@@ -88,7 +88,7 @@ where
pub fn update(
&mut self,
bounds: Size,
- cursor_position: Point,
+ cursor: mouse::Cursor,
renderer: &mut P::Renderer,
theme: &<P::Renderer as iced_core::Renderer>::Theme,
style: &renderer::Style,
@@ -108,7 +108,7 @@ where
let (_, event_statuses) = user_interface.update(
&self.queued_events,
- cursor_position,
+ cursor,
renderer,
clipboard,
&mut messages,
@@ -131,7 +131,7 @@ where
let command = if messages.is_empty() {
debug.draw_started();
self.mouse_interaction =
- user_interface.draw(renderer, theme, style, cursor_position);
+ user_interface.draw(renderer, theme, style, cursor);
debug.draw_finished();
self.cache = Some(user_interface.into_cache());
@@ -163,7 +163,7 @@ where
debug.draw_started();
self.mouse_interaction =
- user_interface.draw(renderer, theme, style, cursor_position);
+ user_interface.draw(renderer, theme, style, cursor);
debug.draw_finished();
self.cache = Some(user_interface.into_cache());
diff --git a/runtime/src/user_interface.rs b/runtime/src/user_interface.rs
index c29de7db..619423fd 100644
--- a/runtime/src/user_interface.rs
+++ b/runtime/src/user_interface.rs
@@ -5,8 +5,8 @@ use crate::core::mouse;
use crate::core::renderer;
use crate::core::widget;
use crate::core::window;
-use crate::core::{Clipboard, Point, Rectangle, Size, Vector};
-use crate::core::{Element, Layout, Shell};
+use crate::core::{Clipboard, Element, Layout, Point, Rectangle, Shell, Size};
+use crate::overlay;
/// A set of interactive graphical elements with a specific [`Layout`].
///
@@ -16,11 +16,10 @@ use crate::core::{Element, Layout, Shell};
/// charge of using this type in your system in any way you want.
///
/// # Example
-/// The [`integration_opengl`] & [`integration_wgpu`] examples use a
-/// [`UserInterface`] to integrate Iced in an existing graphical application.
+/// The [`integration`] example uses a [`UserInterface`] to integrate Iced in an
+/// existing graphical application.
///
-/// [`integration_opengl`]: https://github.com/iced-rs/iced/tree/0.9/examples/integration_opengl
-/// [`integration_wgpu`]: https://github.com/iced-rs/iced/tree/0.9/examples/integration_wgpu
+/// [`integration`]: https://github.com/iced-rs/iced/tree/0.9/examples/integration
#[allow(missing_debug_implementations)]
pub struct UserInterface<'a, Message, Renderer> {
root: Element<'a, Message, Renderer>,
@@ -129,7 +128,9 @@ where
/// # pub fn view(&self) -> iced_core::Element<(), Renderer> { unimplemented!() }
/// # pub fn update(&mut self, _: ()) {}
/// # }
- /// use iced_runtime::core::{clipboard, Size, Point};
+ /// use iced_runtime::core::clipboard;
+ /// use iced_runtime::core::mouse;
+ /// use iced_runtime::core::Size;
/// use iced_runtime::user_interface::{self, UserInterface};
/// use iced_wgpu::Renderer;
///
@@ -137,7 +138,7 @@ where
/// let mut cache = user_interface::Cache::new();
/// let mut renderer = Renderer::new();
/// let mut window_size = Size::new(1024.0, 768.0);
- /// let mut cursor_position = Point::default();
+ /// let mut cursor = mouse::Cursor::default();
/// let mut clipboard = clipboard::Null;
///
/// // Initialize our event storage
@@ -157,7 +158,7 @@ where
/// // Update the user interface
/// let (state, event_statuses) = user_interface.update(
/// &events,
- /// cursor_position,
+ /// cursor,
/// &mut renderer,
/// &mut clipboard,
/// &mut messages
@@ -174,7 +175,7 @@ where
pub fn update(
&mut self,
events: &[Event],
- cursor_position: Point,
+ cursor: mouse::Cursor,
renderer: &mut Renderer,
clipboard: &mut dyn Clipboard,
messages: &mut Vec<Message>,
@@ -184,18 +185,18 @@ where
let mut outdated = false;
let mut redraw_request = None;
- let mut manual_overlay =
- ManuallyDrop::new(self.root.as_widget_mut().overlay(
- &mut self.state,
- Layout::new(&self.base),
- renderer,
- ));
+ let mut manual_overlay = ManuallyDrop::new(
+ self.root
+ .as_widget_mut()
+ .overlay(&mut self.state, Layout::new(&self.base), renderer)
+ .map(overlay::Nested::new),
+ );
let (base_cursor, overlay_statuses) = if manual_overlay.is_some() {
let bounds = self.bounds;
let mut overlay = manual_overlay.as_mut().unwrap();
- let mut layout = overlay.layout(renderer, bounds, Vector::ZERO);
+ let mut layout = overlay.layout(renderer, bounds, Point::ORIGIN);
let mut event_statuses = Vec::new();
for event in events.iter().cloned() {
@@ -204,7 +205,7 @@ where
let event_status = overlay.on_event(
event,
Layout::new(&layout),
- cursor_position,
+ cursor,
renderer,
clipboard,
&mut shell,
@@ -230,12 +231,16 @@ where
&layout::Limits::new(Size::ZERO, self.bounds),
);
- manual_overlay =
- ManuallyDrop::new(self.root.as_widget_mut().overlay(
- &mut self.state,
- Layout::new(&self.base),
- renderer,
- ));
+ manual_overlay = ManuallyDrop::new(
+ self.root
+ .as_widget_mut()
+ .overlay(
+ &mut self.state,
+ Layout::new(&self.base),
+ renderer,
+ )
+ .map(overlay::Nested::new),
+ );
if manual_overlay.is_none() {
break;
@@ -244,7 +249,8 @@ where
overlay = manual_overlay.as_mut().unwrap();
shell.revalidate_layout(|| {
- layout = overlay.layout(renderer, bounds, Vector::ZERO);
+ layout =
+ overlay.layout(renderer, bounds, Point::ORIGIN);
});
}
@@ -253,22 +259,29 @@ where
}
}
- let base_cursor = manual_overlay
- .as_ref()
- .filter(|overlay| {
- overlay.is_over(Layout::new(&layout), cursor_position)
- })
- .map(|_| {
- // TODO: Type-safe cursor availability
- Point::new(-1.0, -1.0)
+ let base_cursor = if manual_overlay
+ .as_mut()
+ .and_then(|overlay| {
+ cursor.position().map(|cursor_position| {
+ overlay.is_over(
+ Layout::new(&layout),
+ renderer,
+ cursor_position,
+ )
+ })
})
- .unwrap_or(cursor_position);
+ .unwrap_or_default()
+ {
+ mouse::Cursor::Unavailable
+ } else {
+ cursor
+ };
self.overlay = Some(layout);
(base_cursor, event_statuses)
} else {
- (cursor_position, vec![event::Status::Ignored; events.len()])
+ (cursor, vec![event::Status::Ignored; events.len()])
};
let _ = ManuallyDrop::into_inner(manual_overlay);
@@ -360,8 +373,9 @@ where
/// # pub fn update(&mut self, _: ()) {}
/// # }
/// use iced_runtime::core::clipboard;
+ /// use iced_runtime::core::mouse;
/// use iced_runtime::core::renderer;
- /// use iced_runtime::core::{Element, Size, Point};
+ /// use iced_runtime::core::{Element, Size};
/// use iced_runtime::user_interface::{self, UserInterface};
/// use iced_wgpu::{Renderer, Theme};
///
@@ -369,7 +383,7 @@ where
/// let mut cache = user_interface::Cache::new();
/// let mut renderer = Renderer::new();
/// let mut window_size = Size::new(1024.0, 768.0);
- /// let mut cursor_position = Point::default();
+ /// let mut cursor = mouse::Cursor::default();
/// let mut clipboard = clipboard::Null;
/// let mut events = Vec::new();
/// let mut messages = Vec::new();
@@ -388,14 +402,14 @@ where
/// // Update the user interface
/// let event_statuses = user_interface.update(
/// &events,
- /// cursor_position,
+ /// cursor,
/// &mut renderer,
/// &mut clipboard,
/// &mut messages
/// );
///
/// // Draw the user interface
- /// let mouse_cursor = user_interface.draw(&mut renderer, &theme, &renderer::Style::default(), cursor_position);
+ /// let mouse_interaction = user_interface.draw(&mut renderer, &theme, &renderer::Style::default(), cursor);
///
/// cache = user_interface.into_cache();
///
@@ -412,35 +426,44 @@ where
renderer: &mut Renderer,
theme: &Renderer::Theme,
style: &renderer::Style,
- cursor_position: Point,
+ cursor: mouse::Cursor,
) -> mouse::Interaction {
// TODO: Move to shell level (?)
renderer.clear();
let viewport = Rectangle::with_size(self.bounds);
- let base_cursor = if let Some(overlay) = self
+ let base_cursor = if let Some(mut overlay) = self
.root
.as_widget_mut()
.overlay(&mut self.state, Layout::new(&self.base), renderer)
+ .map(overlay::Nested::new)
{
let overlay_layout = self.overlay.take().unwrap_or_else(|| {
- overlay.layout(renderer, self.bounds, Vector::ZERO)
+ overlay.layout(renderer, self.bounds, Point::ORIGIN)
});
- let new_cursor_position = if overlay
- .is_over(Layout::new(&overlay_layout), cursor_position)
+ let cursor = if cursor
+ .position()
+ .map(|cursor_position| {
+ overlay.is_over(
+ Layout::new(&overlay_layout),
+ renderer,
+ cursor_position,
+ )
+ })
+ .unwrap_or_default()
{
- Point::new(-1.0, -1.0)
+ mouse::Cursor::Unavailable
} else {
- cursor_position
+ cursor
};
self.overlay = Some(overlay_layout);
- new_cursor_position
+ cursor
} else {
- cursor_position
+ cursor
};
self.root.as_widget().draw(
@@ -456,7 +479,7 @@ where
let base_interaction = self.root.as_widget().mouse_interaction(
&self.state,
Layout::new(&self.base),
- cursor_position,
+ base_cursor,
&viewport,
renderer,
);
@@ -478,10 +501,11 @@ where
.and_then(|layout| {
root.as_widget_mut()
.overlay(&mut self.state, Layout::new(base), renderer)
- .map(|overlay| {
+ .map(overlay::Nested::new)
+ .map(|mut overlay| {
let overlay_interaction = overlay.mouse_interaction(
Layout::new(layout),
- cursor_position,
+ cursor,
&viewport,
renderer,
);
@@ -494,11 +518,20 @@ where
theme,
style,
Layout::new(layout),
- cursor_position,
+ cursor,
);
});
- if overlay.is_over(Layout::new(layout), cursor_position)
+ if cursor
+ .position()
+ .map(|cursor_position| {
+ overlay.is_over(
+ Layout::new(layout),
+ renderer,
+ cursor_position,
+ )
+ })
+ .unwrap_or_default()
{
overlay_interaction
} else {
@@ -522,14 +555,15 @@ where
operation,
);
- if let Some(mut overlay) = self.root.as_widget_mut().overlay(
- &mut self.state,
- Layout::new(&self.base),
- renderer,
- ) {
+ if let Some(mut overlay) = self
+ .root
+ .as_widget_mut()
+ .overlay(&mut self.state, Layout::new(&self.base), renderer)
+ .map(overlay::Nested::new)
+ {
if self.overlay.is_none() {
self.overlay =
- Some(overlay.layout(renderer, self.bounds, Vector::ZERO));
+ Some(overlay.layout(renderer, self.bounds, Point::ORIGIN));
}
overlay.operate(
diff --git a/runtime/src/window.rs b/runtime/src/window.rs
index 833a1125..e448edef 100644
--- a/runtime/src/window.rs
+++ b/runtime/src/window.rs
@@ -1,11 +1,14 @@
//! Build window-based GUI applications.
mod action;
+pub mod screenshot;
+
pub use action::Action;
+pub use screenshot::Screenshot;
use crate::command::{self, Command};
use crate::core::time::Instant;
-use crate::core::window::{Event, Icon, Mode, UserAttention};
+use crate::core::window::{Event, Icon, Level, Mode, UserAttention};
use crate::futures::subscription::{self, Subscription};
/// Subscribes to the frames of the window of the running application.
@@ -53,7 +56,7 @@ pub fn move_to<Message>(x: i32, y: i32) -> Command<Message> {
Command::single(command::Action::Window(Action::Move { x, y }))
}
-/// Sets the [`Mode`] of the window.
+/// Changes the [`Mode`] of the window.
pub fn change_mode<Message>(mode: Mode) -> Command<Message> {
Command::single(command::Action::Window(Action::ChangeMode(mode)))
}
@@ -99,9 +102,9 @@ pub fn gain_focus<Message>() -> Command<Message> {
Command::single(command::Action::Window(Action::GainFocus))
}
-/// Changes whether or not the window will always be on top of other windows.
-pub fn change_always_on_top<Message>(on_top: bool) -> Command<Message> {
- Command::single(command::Action::Window(Action::ChangeAlwaysOnTop(on_top)))
+/// Changes the window [`Level`].
+pub fn change_level<Message>(level: Level) -> Command<Message> {
+ Command::single(command::Action::Window(Action::ChangeLevel(level)))
}
/// Fetches an identifier unique to the window.
@@ -115,3 +118,10 @@ pub fn fetch_id<Message>(
pub fn change_icon<Message>(icon: Icon) -> Command<Message> {
Command::single(command::Action::Window(Action::ChangeIcon(icon)))
}
+
+/// Captures a [`Screenshot`] from the window.
+pub fn screenshot<Message>(
+ f: impl FnOnce(Screenshot) -> Message + Send + 'static,
+) -> Command<Message> {
+ Command::single(command::Action::Window(Action::Screenshot(Box::new(f))))
+}
diff --git a/runtime/src/window/action.rs b/runtime/src/window/action.rs
index 83b71c75..09be1810 100644
--- a/runtime/src/window/action.rs
+++ b/runtime/src/window/action.rs
@@ -1,13 +1,14 @@
-use crate::core::window::{Icon, Mode, UserAttention};
+use crate::core::window::{Icon, Level, Mode, UserAttention};
use crate::futures::MaybeSend;
+use crate::window::Screenshot;
use std::fmt;
/// An operation to be performed on some window.
pub enum Action<T> {
- /// Closes the current window and exits the application.
+ /// Close the current window and exits the application.
Close,
- /// Moves the window with the left mouse button until the button is
+ /// Move the window with the left mouse button until the button is
/// released.
///
/// There’s no guarantee that this will work unless the left mouse
@@ -20,7 +21,7 @@ pub enum Action<T> {
/// The new logical height of the window
height: u32,
},
- /// Sets the window to maximized or back
+ /// Set the window to maximized or back
Maximize(bool),
/// Set the window to minimized or back
Minimize(bool),
@@ -70,15 +71,11 @@ pub enum Action<T> {
///
/// - **Web / Wayland:** Unsupported.
GainFocus,
- /// Change whether or not the window will always be on top of other windows.
- ///
- /// ## Platform-specific
- ///
- /// - **Web / Wayland:** Unsupported.
- ChangeAlwaysOnTop(bool),
+ /// Change the window [`Level`].
+ ChangeLevel(Level),
/// Fetch an identifier unique to the window.
FetchId(Box<dyn FnOnce(u64) -> T + 'static>),
- /// Changes the window [`Icon`].
+ /// Change the window [`Icon`].
///
/// On Windows and X11, this is typically the small icon in the top-left
/// corner of the titlebar.
@@ -93,6 +90,8 @@ pub enum Action<T> {
/// - **X11:** Has no universal guidelines for icon sizes, so you're at the whims of the WM. That
/// said, it's usually in the same ballpark as on Windows.
ChangeIcon(Icon),
+ /// Screenshot the viewport of the window.
+ Screenshot(Box<dyn FnOnce(Screenshot) -> T + 'static>),
}
impl<T> Action<T> {
@@ -119,11 +118,14 @@ impl<T> Action<T> {
Action::RequestUserAttention(attention_type)
}
Self::GainFocus => Action::GainFocus,
- Self::ChangeAlwaysOnTop(on_top) => {
- Action::ChangeAlwaysOnTop(on_top)
- }
+ Self::ChangeLevel(level) => Action::ChangeLevel(level),
Self::FetchId(o) => Action::FetchId(Box::new(move |s| f(o(s)))),
Self::ChangeIcon(icon) => Action::ChangeIcon(icon),
+ Self::Screenshot(tag) => {
+ Action::Screenshot(Box::new(move |screenshot| {
+ f(tag(screenshot))
+ }))
+ }
}
}
}
@@ -154,13 +156,14 @@ impl<T> fmt::Debug for Action<T> {
write!(f, "Action::RequestUserAttention")
}
Self::GainFocus => write!(f, "Action::GainFocus"),
- Self::ChangeAlwaysOnTop(on_top) => {
- write!(f, "Action::AlwaysOnTop({on_top})")
+ Self::ChangeLevel(level) => {
+ write!(f, "Action::ChangeLevel({level:?})")
}
Self::FetchId(_) => write!(f, "Action::FetchId"),
Self::ChangeIcon(_icon) => {
write!(f, "Action::ChangeIcon(icon)")
}
+ Self::Screenshot(_) => write!(f, "Action::Screenshot"),
}
}
}
diff --git a/runtime/src/window/screenshot.rs b/runtime/src/window/screenshot.rs
new file mode 100644
index 00000000..c84286b6
--- /dev/null
+++ b/runtime/src/window/screenshot.rs
@@ -0,0 +1,92 @@
+//! Take screenshots of a window.
+use crate::core::{Rectangle, Size};
+
+use std::fmt::{Debug, Formatter};
+use std::sync::Arc;
+
+/// Data of a screenshot, captured with `window::screenshot()`.
+///
+/// The `bytes` of this screenshot will always be ordered as `RGBA` in the sRGB color space.
+#[derive(Clone)]
+pub struct Screenshot {
+ /// The bytes of the [`Screenshot`].
+ pub bytes: Arc<Vec<u8>>,
+ /// The size of the [`Screenshot`].
+ pub size: Size<u32>,
+}
+
+impl Debug for Screenshot {
+ fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
+ write!(
+ f,
+ "Screenshot: {{ \n bytes: {}\n size: {:?} }}",
+ self.bytes.len(),
+ self.size
+ )
+ }
+}
+
+impl Screenshot {
+ /// Creates a new [`Screenshot`].
+ pub fn new(bytes: Vec<u8>, size: Size<u32>) -> Self {
+ Self {
+ bytes: Arc::new(bytes),
+ size,
+ }
+ }
+
+ /// Crops a [`Screenshot`] to the provided `region`. This will always be relative to the
+ /// top-left corner of the [`Screenshot`].
+ pub fn crop(&self, region: Rectangle<u32>) -> Result<Self, CropError> {
+ if region.width == 0 || region.height == 0 {
+ return Err(CropError::Zero);
+ }
+
+ if region.x + region.width > self.size.width
+ || region.y + region.height > self.size.height
+ {
+ return Err(CropError::OutOfBounds);
+ }
+
+ // Image is always RGBA8 = 4 bytes per pixel
+ const PIXEL_SIZE: usize = 4;
+
+ let bytes_per_row = self.size.width as usize * PIXEL_SIZE;
+ let row_range = region.y as usize..(region.y + region.height) as usize;
+ let column_range = region.x as usize * PIXEL_SIZE
+ ..(region.x + region.width) as usize * PIXEL_SIZE;
+
+ let chopped = self.bytes.chunks(bytes_per_row).enumerate().fold(
+ vec![],
+ |mut acc, (row, bytes)| {
+ if row_range.contains(&row) {
+ acc.extend(&bytes[column_range.clone()]);
+ }
+
+ acc
+ },
+ );
+
+ Ok(Self {
+ bytes: Arc::new(chopped),
+ size: Size::new(region.width, region.height),
+ })
+ }
+}
+
+impl AsRef<[u8]> for Screenshot {
+ fn as_ref(&self) -> &[u8] {
+ &self.bytes
+ }
+}
+
+#[derive(Debug, thiserror::Error)]
+/// Errors that can occur when cropping a [`Screenshot`].
+pub enum CropError {
+ #[error("The cropped region is out of bounds.")]
+ /// The cropped region's size is out of bounds.
+ OutOfBounds,
+ #[error("The cropped region is not visible.")]
+ /// The cropped region's size is zero.
+ Zero,
+}
diff --git a/src/lib.rs b/src/lib.rs
index c73cc48d..ce6c513d 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -163,7 +163,7 @@
)]
#![forbid(rust_2018_idioms, unsafe_code)]
#![allow(clippy::inherent_to_string, clippy::type_complexity)]
-#![cfg_attr(docsrs, feature(doc_cfg))]
+#![cfg_attr(docsrs, feature(doc_auto_cfg))]
use iced_widget::graphics;
use iced_widget::renderer;
use iced_widget::style;
@@ -188,9 +188,10 @@ pub use style::theme;
pub use crate::core::alignment;
pub use crate::core::event;
+pub use crate::core::gradient;
pub use crate::core::{
- color, Alignment, Background, Color, ContentFit, Length, Padding, Pixels,
- Point, Rectangle, Size, Vector,
+ color, Alignment, Background, Color, ContentFit, Degrees, Gradient, Length,
+ Padding, Pixels, Point, Radians, Rectangle, Size, Vector,
};
pub use crate::runtime::Command;
@@ -229,7 +230,9 @@ pub mod keyboard {
pub mod mouse {
//! Listen and react to mouse events.
- pub use crate::core::mouse::{Button, Event, Interaction, ScrollDelta};
+ pub use crate::core::mouse::{
+ Button, Cursor, Event, Interaction, ScrollDelta,
+ };
}
pub mod subscription {
@@ -275,6 +278,7 @@ pub mod widget {
mod native {}
mod renderer {}
mod style {}
+ mod runtime {}
}
pub use application::Application;
diff --git a/src/window/icon.rs b/src/window/icon.rs
index b67b2ea3..0fe010ca 100644
--- a/src/window/icon.rs
+++ b/src/window/icon.rs
@@ -12,7 +12,6 @@ use std::path::Path;
///
/// This will return an error in case the file is missing at run-time. You may prefer [`Self::from_file_data`] instead.
#[cfg(feature = "image")]
-#[cfg_attr(docsrs, doc(cfg(feature = "image")))]
pub fn from_file<P: AsRef<Path>>(icon_path: P) -> Result<Icon, Error> {
let icon = image_rs::io::Reader::open(icon_path)?.decode()?.to_rgba8();
@@ -24,7 +23,6 @@ pub fn from_file<P: AsRef<Path>>(icon_path: P) -> Result<Icon, Error> {
/// This content can be included in your application at compile-time, e.g. using the `include_bytes!` macro.
/// You can pass an explicit file format. Otherwise, the file format will be guessed at runtime.
#[cfg(feature = "image")]
-#[cfg_attr(docsrs, doc(cfg(feature = "image")))]
pub fn from_file_data(
data: &[u8],
explicit_format: Option<image_rs::ImageFormat>,
@@ -60,7 +58,6 @@ pub enum Error {
/// The `image` crate reported an error.
#[cfg(feature = "image")]
- #[cfg_attr(docsrs, doc(cfg(feature = "image")))]
#[error("Unable to create icon from a file: {0}")]
ImageError(#[from] image_rs::error::ImageError),
}
diff --git a/src/window/settings.rs b/src/window/settings.rs
index 3c8da62f..458b9232 100644
--- a/src/window/settings.rs
+++ b/src/window/settings.rs
@@ -1,4 +1,4 @@
-use crate::window::{Icon, Position};
+use crate::window::{Icon, Level, Position};
pub use iced_winit::settings::PlatformSpecific;
@@ -29,8 +29,8 @@ pub struct Settings {
/// Whether the window should be transparent.
pub transparent: bool,
- /// Whether the window will always be on top of other windows.
- pub always_on_top: bool,
+ /// The window [`Level`].
+ pub level: Level,
/// The icon of the window.
pub icon: Option<Icon>,
@@ -50,7 +50,7 @@ impl Default for Settings {
resizable: true,
decorations: true,
transparent: false,
- always_on_top: false,
+ level: Level::default(),
icon: None,
platform_specific: Default::default(),
}
@@ -68,7 +68,7 @@ impl From<Settings> for iced_winit::settings::Window {
resizable: settings.resizable,
decorations: settings.decorations,
transparent: settings.transparent,
- always_on_top: settings.always_on_top,
+ level: settings.level,
icon: settings.icon.map(Icon::into),
platform_specific: settings.platform_specific,
}
diff --git a/style/Cargo.toml b/style/Cargo.toml
index 0bb354e0..8af4a9b3 100644
--- a/style/Cargo.toml
+++ b/style/Cargo.toml
@@ -16,7 +16,7 @@ path = "../core"
features = ["palette"]
[dependencies.palette]
-version = "0.6"
+version = "0.7"
[dependencies.once_cell]
version = "1.15"
diff --git a/style/src/button.rs b/style/src/button.rs
index a564a2b7..e49ad94a 100644
--- a/style/src/button.rs
+++ b/style/src/button.rs
@@ -1,5 +1,5 @@
//! Change the apperance of a button.
-use iced_core::{Background, Color, Vector};
+use iced_core::{Background, BorderRadius, Color, Vector};
/// The appearance of a button.
#[derive(Debug, Clone, Copy)]
@@ -9,7 +9,7 @@ pub struct Appearance {
/// The [`Background`] of the button.
pub background: Option<Background>,
/// The border radius of the button.
- pub border_radius: f32,
+ pub border_radius: BorderRadius,
/// The border width of the button.
pub border_width: f32,
/// The border [`Color`] of the button.
@@ -23,7 +23,7 @@ impl std::default::Default for Appearance {
Self {
shadow_offset: Vector::default(),
background: None,
- border_radius: 0.0,
+ border_radius: 0.0.into(),
border_width: 0.0,
border_color: Color::TRANSPARENT,
text_color: Color::BLACK,
@@ -68,6 +68,9 @@ pub trait StyleSheet {
a: color.a * 0.5,
..color
}),
+ Background::Gradient(gradient) => {
+ Background::Gradient(gradient.mul_alpha(0.5))
+ }
}),
text_color: Color {
a: active.text_color.a * 0.5,
diff --git a/style/src/checkbox.rs b/style/src/checkbox.rs
index 52b90ec9..cf52c05d 100644
--- a/style/src/checkbox.rs
+++ b/style/src/checkbox.rs
@@ -1,5 +1,5 @@
//! Change the appearance of a checkbox.
-use iced_core::{Background, Color};
+use iced_core::{Background, BorderRadius, Color};
/// The appearance of a checkbox.
#[derive(Debug, Clone, Copy)]
@@ -9,7 +9,7 @@ pub struct Appearance {
/// The icon [`Color`] of the checkbox.
pub icon_color: Color,
/// The border radius of the checkbox.
- pub border_radius: f32,
+ pub border_radius: BorderRadius,
/// The border width of the checkbox.
pub border_width: f32,
/// The border [`Color`] of the checkbox.
diff --git a/style/src/container.rs b/style/src/container.rs
index 560b2d5b..ec543ae4 100644
--- a/style/src/container.rs
+++ b/style/src/container.rs
@@ -1,5 +1,5 @@
//! Change the appearance of a container.
-use iced_core::{Background, Color};
+use iced_core::{Background, BorderRadius, Color};
/// The appearance of a container.
#[derive(Debug, Clone, Copy)]
@@ -9,7 +9,7 @@ pub struct Appearance {
/// The [`Background`] of the container.
pub background: Option<Background>,
/// The border radius of the container.
- pub border_radius: f32,
+ pub border_radius: BorderRadius,
/// The border width of the container.
pub border_width: f32,
/// The border [`Color`] of the container.
@@ -21,7 +21,7 @@ impl std::default::Default for Appearance {
Self {
text_color: None,
background: None,
- border_radius: 0.0,
+ border_radius: 0.0.into(),
border_width: 0.0,
border_color: Color::TRANSPARENT,
}
diff --git a/style/src/menu.rs b/style/src/menu.rs
index 7d878748..dbf19dae 100644
--- a/style/src/menu.rs
+++ b/style/src/menu.rs
@@ -1,5 +1,5 @@
//! Change the appearance of menus.
-use iced_core::{Background, Color};
+use iced_core::{Background, BorderRadius, Color};
/// The appearance of a menu.
#[derive(Debug, Clone, Copy)]
@@ -11,7 +11,7 @@ pub struct Appearance {
/// The border width of the menu.
pub border_width: f32,
/// The border radius of the menu.
- pub border_radius: f32,
+ pub border_radius: BorderRadius,
/// The border [`Color`] of the menu.
pub border_color: Color,
/// The text [`Color`] of a selected option in the menu.
diff --git a/style/src/pane_grid.rs b/style/src/pane_grid.rs
index fd8fc05f..b99af955 100644
--- a/style/src/pane_grid.rs
+++ b/style/src/pane_grid.rs
@@ -1,16 +1,17 @@
//! Change the appearance of a pane grid.
-use iced_core::Color;
+use iced_core::{Background, BorderRadius, Color};
-/// A set of rules that dictate the style of a container.
-pub trait StyleSheet {
- /// 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>;
-
- /// The [`Line`] to draw when a split is hovered.
- fn hovered_split(&self, style: &Self::Style) -> Option<Line>;
+/// The appearance of the hovered region of a pane grid.
+#[derive(Debug, Clone, Copy)]
+pub struct Appearance {
+ /// The [`Background`] of the hovered pane region.
+ pub background: Background,
+ /// The border width of the hovered pane region.
+ pub border_width: f32,
+ /// The border [`Color`] of the hovered pane region.
+ pub border_color: Color,
+ /// The border radius of the hovered pane region.
+ pub border_radius: BorderRadius,
}
/// A line.
@@ -24,3 +25,18 @@ pub struct Line {
/// The width of the [`Line`].
pub width: f32,
}
+
+/// A set of rules that dictate the style of a container.
+pub trait StyleSheet {
+ /// The supported style of the [`StyleSheet`].
+ type Style: Default;
+
+ /// The [`Region`] to draw when a pane is hovered.
+ fn hovered_region(&self, style: &Self::Style) -> Appearance;
+
+ /// The [`Line`] to draw when a split is picked.
+ 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>;
+}
diff --git a/style/src/pick_list.rs b/style/src/pick_list.rs
index 11e13b01..961c1e93 100644
--- a/style/src/pick_list.rs
+++ b/style/src/pick_list.rs
@@ -1,5 +1,5 @@
//! Change the appearance of a pick list.
-use iced_core::{Background, Color};
+use iced_core::{Background, BorderRadius, Color};
/// The appearance of a pick list.
#[derive(Debug, Clone, Copy)]
@@ -13,7 +13,7 @@ pub struct Appearance {
/// The [`Background`] of the pick list.
pub background: Background,
/// The border radius of the pick list.
- pub border_radius: f32,
+ pub border_radius: BorderRadius,
/// The border width of the pick list.
pub border_width: f32,
/// The border color of the pick list.
diff --git a/style/src/progress_bar.rs b/style/src/progress_bar.rs
index fb1819fc..c05a6ee4 100644
--- a/style/src/progress_bar.rs
+++ b/style/src/progress_bar.rs
@@ -1,5 +1,5 @@
//! Change the appearance of a progress bar.
-use iced_core::Background;
+use iced_core::{Background, BorderRadius};
/// The appearance of a progress bar.
#[derive(Debug, Clone, Copy)]
@@ -9,7 +9,7 @@ pub struct Appearance {
/// The [`Background`] of the bar of the progress bar.
pub bar: Background,
/// The border radius of the progress bar.
- pub border_radius: f32,
+ pub border_radius: BorderRadius,
}
/// A set of rules that dictate the style of a progress bar.
diff --git a/style/src/rule.rs b/style/src/rule.rs
index b7380747..afae085c 100644
--- a/style/src/rule.rs
+++ b/style/src/rule.rs
@@ -1,5 +1,5 @@
//! Change the appearance of a rule.
-use iced_core::Color;
+use iced_core::{BorderRadius, Color};
/// The appearance of a rule.
#[derive(Debug, Clone, Copy)]
@@ -9,7 +9,7 @@ pub struct Appearance {
/// The width (thickness) of the rule line.
pub width: u16,
/// The radius of the line corners.
- pub radius: f32,
+ pub radius: BorderRadius,
/// The [`FillMode`] of the rule.
pub fill_mode: FillMode,
}
diff --git a/style/src/scrollable.rs b/style/src/scrollable.rs
index b528c444..952c11e1 100644
--- a/style/src/scrollable.rs
+++ b/style/src/scrollable.rs
@@ -1,5 +1,5 @@
//! Change the appearance of a scrollable.
-use iced_core::{Background, Color};
+use iced_core::{Background, BorderRadius, Color};
/// The appearance of a scrollable.
#[derive(Debug, Clone, Copy)]
@@ -7,7 +7,7 @@ pub struct Scrollbar {
/// The [`Background`] of a scrollable.
pub background: Option<Background>,
/// The border radius of a scrollable.
- pub border_radius: f32,
+ pub border_radius: BorderRadius,
/// The border width of a scrollable.
pub border_width: f32,
/// The border [`Color`] of a scrollable.
@@ -22,7 +22,7 @@ pub struct Scroller {
/// The [`Color`] of the scroller.
pub color: Color,
/// The border radius of the scroller.
- pub border_radius: f32,
+ pub border_radius: BorderRadius,
/// The border width of the scroller.
pub border_width: f32,
/// The border [`Color`] of the scroller.
diff --git a/style/src/slider.rs b/style/src/slider.rs
index 884d3871..f0068558 100644
--- a/style/src/slider.rs
+++ b/style/src/slider.rs
@@ -1,5 +1,5 @@
//! Change the apperance of a slider.
-use iced_core::Color;
+use iced_core::{BorderRadius, Color};
/// The appearance of a slider.
#[derive(Debug, Clone, Copy)]
@@ -17,6 +17,8 @@ pub struct Rail {
pub colors: (Color, Color),
/// The width of the stroke of a slider rail.
pub width: f32,
+ /// The border radius of the corners of the rail.
+ pub border_radius: BorderRadius,
}
/// The appearance of the handle of a slider.
@@ -45,7 +47,7 @@ pub enum HandleShape {
/// The width of the rectangle.
width: u16,
/// The border radius of the corners of the rectangle.
- border_radius: f32,
+ border_radius: BorderRadius,
},
}
diff --git a/style/src/text_input.rs b/style/src/text_input.rs
index 2616ad5a..90251b5c 100644
--- a/style/src/text_input.rs
+++ b/style/src/text_input.rs
@@ -1,5 +1,5 @@
//! Change the appearance of a text input.
-use iced_core::{Background, Color};
+use iced_core::{Background, BorderRadius, Color};
/// The appearance of a text input.
#[derive(Debug, Clone, Copy)]
@@ -7,7 +7,7 @@ pub struct Appearance {
/// The [`Background`] of the text input.
pub background: Background,
/// The border radius of the text input.
- pub border_radius: f32,
+ pub border_radius: BorderRadius,
/// The border width of the text input.
pub border_width: f32,
/// The border [`Color`] of the text input.
diff --git a/style/src/theme.rs b/style/src/theme.rs
index d9893bcf..64497181 100644
--- a/style/src/theme.rs
+++ b/style/src/theme.rs
@@ -105,7 +105,7 @@ impl application::StyleSheet for Theme {
}
}
-impl application::StyleSheet for fn(&Theme) -> application::Appearance {
+impl<T: Fn(&Theme) -> application::Appearance> application::StyleSheet for T {
type Style = Theme;
fn appearance(&self, style: &Self::Style) -> application::Appearance {
@@ -113,8 +113,10 @@ impl application::StyleSheet for fn(&Theme) -> application::Appearance {
}
}
-impl From<fn(&Theme) -> application::Appearance> for Application {
- fn from(f: fn(&Theme) -> application::Appearance) -> Self {
+impl<T: Fn(&Theme) -> application::Appearance + 'static> From<T>
+ for Application
+{
+ fn from(f: T) -> Self {
Self::Custom(Box::new(f))
}
}
@@ -139,6 +141,15 @@ pub enum Button {
Custom(Box<dyn button::StyleSheet<Style = Theme>>),
}
+impl Button {
+ /// Creates a custom [`Button`] style variant.
+ pub fn custom(
+ style_sheet: impl button::StyleSheet<Style = Theme> + 'static,
+ ) -> Self {
+ Self::Custom(Box::new(style_sheet))
+ }
+}
+
impl button::StyleSheet for Theme {
type Style = Button;
@@ -146,7 +157,7 @@ impl button::StyleSheet for Theme {
let palette = self.extended_palette();
let appearance = button::Appearance {
- border_radius: 2.0,
+ border_radius: 2.0.into(),
..button::Appearance::default()
};
@@ -217,6 +228,9 @@ impl button::StyleSheet for Theme {
a: color.a * 0.5,
..color
}),
+ Background::Gradient(gradient) => {
+ Background::Gradient(gradient.mul_alpha(0.5))
+ }
}),
text_color: Color {
a: active.text_color.a * 0.5,
@@ -332,7 +346,7 @@ fn checkbox_appearance(
base.color
}),
icon_color,
- border_radius: 2.0,
+ border_radius: 2.0.into(),
border_width: 1.0,
border_color: accent.color,
text_color: None,
@@ -351,8 +365,8 @@ pub enum Container {
Custom(Box<dyn container::StyleSheet<Style = Theme>>),
}
-impl From<fn(&Theme) -> container::Appearance> for Container {
- fn from(f: fn(&Theme) -> container::Appearance) -> Self {
+impl<T: Fn(&Theme) -> container::Appearance + 'static> From<T> for Container {
+ fn from(f: T) -> Self {
Self::Custom(Box::new(f))
}
}
@@ -368,8 +382,8 @@ impl container::StyleSheet for Theme {
container::Appearance {
text_color: None,
- background: palette.background.weak.color.into(),
- border_radius: 2.0,
+ background: Some(palette.background.weak.color.into()),
+ border_radius: 2.0.into(),
border_width: 0.0,
border_color: Color::TRANSPARENT,
}
@@ -379,7 +393,7 @@ impl container::StyleSheet for Theme {
}
}
-impl container::StyleSheet for fn(&Theme) -> container::Appearance {
+impl<T: Fn(&Theme) -> container::Appearance> container::StyleSheet for T {
type Style = Theme;
fn appearance(&self, style: &Self::Style) -> container::Appearance {
@@ -408,7 +422,7 @@ impl slider::StyleSheet for Theme {
let handle = slider::Handle {
shape: slider::HandleShape::Rectangle {
width: 8,
- border_radius: 4.0,
+ border_radius: 4.0.into(),
},
color: Color::WHITE,
border_color: Color::WHITE,
@@ -419,9 +433,10 @@ impl slider::StyleSheet for Theme {
rail: slider::Rail {
colors: (
palette.primary.base.color,
- palette.primary.base.color,
+ palette.secondary.base.color,
),
- width: 2.0,
+ width: 4.0,
+ border_radius: 2.0.into(),
},
handle: slider::Handle {
color: palette.background.base.color,
@@ -493,7 +508,7 @@ impl menu::StyleSheet for Theme {
text_color: palette.background.weak.text,
background: palette.background.weak.color.into(),
border_width: 1.0,
- border_radius: 0.0,
+ border_radius: 0.0.into(),
border_color: palette.background.strong.color,
selected_text_color: palette.primary.strong.text,
selected_background: palette.primary.strong.color.into(),
@@ -539,7 +554,7 @@ impl pick_list::StyleSheet for Theme {
background: palette.background.weak.color.into(),
placeholder_color: palette.background.strong.color,
handle_color: palette.background.weak.text,
- border_radius: 2.0,
+ border_radius: 2.0.into(),
border_width: 1.0,
border_color: palette.background.strong.color,
}
@@ -558,7 +573,7 @@ impl pick_list::StyleSheet for Theme {
background: palette.background.weak.color.into(),
placeholder_color: palette.background.strong.color,
handle_color: palette.background.weak.text,
- border_radius: 2.0,
+ border_radius: 2.0.into(),
border_width: 1.0,
border_color: palette.primary.strong.color,
}
@@ -703,6 +718,25 @@ pub enum PaneGrid {
impl pane_grid::StyleSheet for Theme {
type Style = PaneGrid;
+ fn hovered_region(&self, style: &Self::Style) -> pane_grid::Appearance {
+ match style {
+ PaneGrid::Default => {
+ let palette = self.extended_palette();
+
+ pane_grid::Appearance {
+ background: Background::Color(Color {
+ a: 0.5,
+ ..palette.primary.base.color
+ }),
+ border_width: 2.0,
+ border_color: palette.primary.strong.color,
+ border_radius: 0.0.into(),
+ }
+ }
+ PaneGrid::Custom(custom) => custom.hovered_region(self),
+ }
+ }
+
fn picked_split(&self, style: &Self::Style) -> Option<pane_grid::Line> {
match style {
PaneGrid::Default => {
@@ -746,8 +780,10 @@ pub enum ProgressBar {
Custom(Box<dyn progress_bar::StyleSheet<Style = Theme>>),
}
-impl From<fn(&Theme) -> progress_bar::Appearance> for ProgressBar {
- fn from(f: fn(&Theme) -> progress_bar::Appearance) -> Self {
+impl<T: Fn(&Theme) -> progress_bar::Appearance + 'static> From<T>
+ for ProgressBar
+{
+ fn from(f: T) -> Self {
Self::Custom(Box::new(f))
}
}
@@ -765,7 +801,7 @@ impl progress_bar::StyleSheet for Theme {
let from_palette = |bar: Color| progress_bar::Appearance {
background: palette.background.strong.color.into(),
bar: bar.into(),
- border_radius: 2.0,
+ border_radius: 2.0.into(),
};
match style {
@@ -777,7 +813,7 @@ impl progress_bar::StyleSheet for Theme {
}
}
-impl progress_bar::StyleSheet for fn(&Theme) -> progress_bar::Appearance {
+impl<T: Fn(&Theme) -> progress_bar::Appearance> progress_bar::StyleSheet for T {
type Style = Theme;
fn appearance(&self, style: &Self::Style) -> progress_bar::Appearance {
@@ -795,8 +831,8 @@ pub enum Rule {
Custom(Box<dyn rule::StyleSheet<Style = Theme>>),
}
-impl From<fn(&Theme) -> rule::Appearance> for Rule {
- fn from(f: fn(&Theme) -> rule::Appearance) -> Self {
+impl<T: Fn(&Theme) -> rule::Appearance + 'static> From<T> for Rule {
+ fn from(f: T) -> Self {
Self::Custom(Box::new(f))
}
}
@@ -811,7 +847,7 @@ impl rule::StyleSheet for Theme {
Rule::Default => rule::Appearance {
color: palette.background.strong.color,
width: 1,
- radius: 0.0,
+ radius: 0.0.into(),
fill_mode: rule::FillMode::Full,
},
Rule::Custom(custom) => custom.appearance(self),
@@ -819,7 +855,7 @@ impl rule::StyleSheet for Theme {
}
}
-impl rule::StyleSheet for fn(&Theme) -> rule::Appearance {
+impl<T: Fn(&Theme) -> rule::Appearance> rule::StyleSheet for T {
type Style = Theme;
fn appearance(&self, style: &Self::Style) -> rule::Appearance {
@@ -893,13 +929,13 @@ impl scrollable::StyleSheet for Theme {
let palette = self.extended_palette();
scrollable::Scrollbar {
- background: palette.background.weak.color.into(),
- border_radius: 2.0,
+ background: Some(palette.background.weak.color.into()),
+ border_radius: 2.0.into(),
border_width: 0.0,
border_color: Color::TRANSPARENT,
scroller: scrollable::Scroller {
color: palette.background.strong.color,
- border_radius: 2.0,
+ border_radius: 2.0.into(),
border_width: 0.0,
border_color: Color::TRANSPARENT,
},
@@ -920,13 +956,13 @@ impl scrollable::StyleSheet for Theme {
let palette = self.extended_palette();
scrollable::Scrollbar {
- background: palette.background.weak.color.into(),
- border_radius: 2.0,
+ background: Some(palette.background.weak.color.into()),
+ border_radius: 2.0.into(),
border_width: 0.0,
border_color: Color::TRANSPARENT,
scroller: scrollable::Scroller {
color: palette.primary.strong.color,
- border_radius: 2.0,
+ border_radius: 2.0.into(),
border_width: 0.0,
border_color: Color::TRANSPARENT,
},
@@ -1028,7 +1064,7 @@ impl text_input::StyleSheet for Theme {
text_input::Appearance {
background: palette.background.base.color.into(),
- border_radius: 2.0,
+ border_radius: 2.0.into(),
border_width: 1.0,
border_color: palette.background.strong.color,
icon_color: palette.background.weak.text,
@@ -1044,7 +1080,7 @@ impl text_input::StyleSheet for Theme {
text_input::Appearance {
background: palette.background.base.color.into(),
- border_radius: 2.0,
+ border_radius: 2.0.into(),
border_width: 1.0,
border_color: palette.background.base.text,
icon_color: palette.background.weak.text,
@@ -1060,7 +1096,7 @@ impl text_input::StyleSheet for Theme {
text_input::Appearance {
background: palette.background.base.color.into(),
- border_radius: 2.0,
+ border_radius: 2.0.into(),
border_width: 1.0,
border_color: palette.primary.strong.color,
icon_color: palette.background.weak.text,
@@ -1106,7 +1142,7 @@ impl text_input::StyleSheet for Theme {
text_input::Appearance {
background: palette.background.weak.color.into(),
- border_radius: 2.0,
+ border_radius: 2.0.into(),
border_width: 1.0,
border_color: palette.background.strong.color,
icon_color: palette.background.strong.color,
diff --git a/style/src/theme/palette.rs b/style/src/theme/palette.rs
index 0f15494b..aaeb799d 100644
--- a/style/src/theme/palette.rs
+++ b/style/src/theme/palette.rs
@@ -2,7 +2,9 @@
use iced_core::Color;
use once_cell::sync::Lazy;
-use palette::{FromColor, Hsl, Mix, RelativeContrast, Srgb};
+use palette::color_difference::Wcag21RelativeContrast;
+use palette::rgb::Rgb;
+use palette::{FromColor, Hsl, Mix};
/// A color palette.
#[derive(Debug, Clone, Copy, PartialEq)]
@@ -298,11 +300,11 @@ fn deviate(color: Color, amount: f32) -> Color {
}
fn mix(a: Color, b: Color, factor: f32) -> Color {
- let a_lin = Srgb::from(a).into_linear();
- let b_lin = Srgb::from(b).into_linear();
+ let a_lin = Rgb::from(a).into_linear();
+ let b_lin = Rgb::from(b).into_linear();
- let mixed = a_lin.mix(&b_lin, factor);
- Srgb::from_linear(mixed).into()
+ let mixed = a_lin.mix(b_lin, factor);
+ Rgb::from_linear(mixed).into()
}
fn readable(background: Color, text: Color) -> Color {
@@ -320,16 +322,16 @@ fn is_dark(color: Color) -> bool {
}
fn is_readable(a: Color, b: Color) -> bool {
- let a_srgb = Srgb::from(a);
- let b_srgb = Srgb::from(b);
+ let a_srgb = Rgb::from(a);
+ let b_srgb = Rgb::from(b);
- a_srgb.has_enhanced_contrast_text(&b_srgb)
+ a_srgb.has_enhanced_contrast_text(b_srgb)
}
fn to_hsl(color: Color) -> Hsl {
- Hsl::from_color(Srgb::from(color))
+ Hsl::from_color(Rgb::from(color))
}
fn from_hsl(hsl: Hsl) -> Color {
- Srgb::from_color(hsl).into()
+ Rgb::from_color(hsl).into()
}
diff --git a/tiny_skia/Cargo.toml b/tiny_skia/Cargo.toml
index 400eee6a..ef993fb9 100644
--- a/tiny_skia/Cargo.toml
+++ b/tiny_skia/Cargo.toml
@@ -24,7 +24,7 @@ features = ["tiny-skia"]
[dependencies.cosmic-text]
git = "https://github.com/hecrj/cosmic-text.git"
-rev = "b85d6a4f2376f8a8a7dadc0f8bcb89d4db10a1c9"
+rev = "c3cd24dc972bb8fd55d016c81ac9fa637e0a4ada"
[dependencies.twox-hash]
version = "1.6"
diff --git a/tiny_skia/fonts/Iced-Icons.ttf b/tiny_skia/fonts/Iced-Icons.ttf
new file mode 100644
index 00000000..e3273141
--- /dev/null
+++ b/tiny_skia/fonts/Iced-Icons.ttf
Binary files differ
diff --git a/tiny_skia/src/backend.rs b/tiny_skia/src/backend.rs
index 87738174..ba038052 100644
--- a/tiny_skia/src/backend.rs
+++ b/tiny_skia/src/backend.rs
@@ -1,4 +1,5 @@
use crate::core::text;
+use crate::core::Gradient;
use crate::core::{Background, Color, Font, Point, Rectangle, Size, Vector};
use crate::graphics::backend;
use crate::graphics::{Primitive, Viewport};
@@ -91,6 +92,7 @@ impl Backend {
background_color,
)),
anti_alias: false,
+ blend_mode: tiny_skia::BlendMode::Source,
..Default::default()
},
tiny_skia::FillRule::default(),
@@ -194,6 +196,48 @@ impl Backend {
*color,
))
}
+ Background::Gradient(Gradient::Linear(linear)) => {
+ let (start, end) =
+ linear.angle.to_distance(bounds);
+
+ let stops: Vec<tiny_skia::GradientStop> =
+ linear
+ .stops
+ .into_iter()
+ .flatten()
+ .map(|stop| {
+ tiny_skia::GradientStop::new(
+ stop.offset,
+ tiny_skia::Color::from_rgba(
+ stop.color.b,
+ stop.color.g,
+ stop.color.r,
+ stop.color.a,
+ )
+ .expect("Create color"),
+ )
+ })
+ .collect();
+
+ tiny_skia::LinearGradient::new(
+ tiny_skia::Point {
+ x: start.x,
+ y: start.y,
+ },
+ tiny_skia::Point { x: end.x, y: end.y },
+ if stops.is_empty() {
+ vec![tiny_skia::GradientStop::new(
+ 0.0,
+ tiny_skia::Color::BLACK,
+ )]
+ } else {
+ stops
+ },
+ tiny_skia::SpreadMode::Pad,
+ tiny_skia::Transform::identity(),
+ )
+ .expect("Create linear gradient")
+ }
},
anti_alias: true,
..tiny_skia::Paint::default()
@@ -722,12 +766,6 @@ fn adjust_clip_mask(clip_mask: &mut tiny_skia::Mask, bounds: Rectangle) {
);
}
-impl iced_graphics::Backend for Backend {
- fn trim_measurements(&mut self) {
- self.text_pipeline.trim_measurement_cache();
- }
-}
-
impl backend::Text for Backend {
const ICON_FONT: Font = Font::with_name("Iced-Icons");
const CHECKMARK_ICON: char = '\u{f00c}';
diff --git a/tiny_skia/src/geometry.rs b/tiny_skia/src/geometry.rs
index a445b561..ee347c73 100644
--- a/tiny_skia/src/geometry.rs
+++ b/tiny_skia/src/geometry.rs
@@ -1,8 +1,8 @@
-use crate::core::Gradient;
use crate::core::{Point, Rectangle, Size, Vector};
use crate::graphics::geometry::fill::{self, Fill};
use crate::graphics::geometry::stroke::{self, Stroke};
use crate::graphics::geometry::{Path, Style, Text};
+use crate::graphics::Gradient;
use crate::graphics::Primitive;
pub struct Frame {
@@ -127,9 +127,9 @@ impl Frame {
self.transform = self.stack.pop().expect("Pop transform");
}
- pub fn clip(&mut self, frame: Self, translation: Vector) {
+ pub fn clip(&mut self, frame: Self, at: Point) {
self.primitives.push(Primitive::Translate {
- translation,
+ translation: Vector::new(at.x, at.y),
content: Box::new(frame.into_primitive()),
});
}
@@ -231,18 +231,11 @@ pub fn into_paint(style: Style) -> tiny_skia::Paint<'static> {
.expect("Create color"),
),
Style::Gradient(gradient) => match gradient {
- Gradient::Linear(linear) => tiny_skia::LinearGradient::new(
- tiny_skia::Point {
- x: linear.start.x,
- y: linear.start.y,
- },
- tiny_skia::Point {
- x: linear.end.x,
- y: linear.end.y,
- },
- linear
- .color_stops
+ Gradient::Linear(linear) => {
+ let stops: Vec<tiny_skia::GradientStop> = linear
+ .stops
.into_iter()
+ .flatten()
.map(|stop| {
tiny_skia::GradientStop::new(
stop.offset,
@@ -255,11 +248,30 @@ pub fn into_paint(style: Style) -> tiny_skia::Paint<'static> {
.expect("Create color"),
)
})
- .collect(),
- tiny_skia::SpreadMode::Pad,
- tiny_skia::Transform::identity(),
- )
- .expect("Create linear gradient"),
+ .collect();
+
+ tiny_skia::LinearGradient::new(
+ tiny_skia::Point {
+ x: linear.start.x,
+ y: linear.start.y,
+ },
+ tiny_skia::Point {
+ x: linear.end.x,
+ y: linear.end.y,
+ },
+ if stops.is_empty() {
+ vec![tiny_skia::GradientStop::new(
+ 0.0,
+ tiny_skia::Color::BLACK,
+ )]
+ } else {
+ stops
+ },
+ tiny_skia::SpreadMode::Pad,
+ tiny_skia::Transform::identity(),
+ )
+ .expect("Create linear gradient")
+ }
},
},
anti_alias: true,
diff --git a/tiny_skia/src/text.rs b/tiny_skia/src/text.rs
index a34c7317..3441da8f 100644
--- a/tiny_skia/src/text.rs
+++ b/tiny_skia/src/text.rs
@@ -14,8 +14,7 @@ use std::sync::Arc;
pub struct Pipeline {
font_system: RefCell<cosmic_text::FontSystem>,
glyph_cache: GlyphCache,
- measurement_cache: RefCell<Cache>,
- render_cache: Cache,
+ cache: RefCell<Cache>,
}
impl Pipeline {
@@ -23,14 +22,12 @@ impl Pipeline {
Pipeline {
font_system: RefCell::new(cosmic_text::FontSystem::new_with_fonts(
[cosmic_text::fontdb::Source::Binary(Arc::new(
- include_bytes!("../../wgpu/fonts/Iced-Icons.ttf")
- .as_slice(),
+ include_bytes!("../fonts/Iced-Icons.ttf").as_slice(),
))]
.into_iter(),
)),
glyph_cache: GlyphCache::new(),
- measurement_cache: RefCell::new(Cache::new()),
- render_cache: Cache::new(),
+ cache: RefCell::new(Cache::new()),
}
}
@@ -38,6 +35,8 @@ impl Pipeline {
self.font_system.get_mut().db_mut().load_font_source(
cosmic_text::fontdb::Source::Binary(Arc::new(bytes.into_owned())),
);
+
+ self.cache = RefCell::new(Cache::new());
}
pub fn draw(
@@ -55,20 +54,11 @@ impl Pipeline {
pixels: &mut tiny_skia::PixmapMut<'_>,
clip_mask: Option<&tiny_skia::Mask>,
) {
- let line_height =
- f32::from(line_height.to_absolute(Pixels(size))) * scale_factor;
-
- let bounds = bounds * scale_factor;
- let size = size * scale_factor;
+ let line_height = f32::from(line_height.to_absolute(Pixels(size)));
let font_system = self.font_system.get_mut();
let key = Key {
- bounds: {
- let size = bounds.size();
-
- // TODO: Reuse buffers from layouting
- Size::new(size.width.ceil(), size.height.ceil())
- },
+ bounds: bounds.size(),
content,
font,
size,
@@ -76,7 +66,7 @@ impl Pipeline {
shaping,
};
- let (_, buffer) = self.render_cache.allocate(font_system, key);
+ let (_, buffer) = self.cache.get_mut().allocate(font_system, key);
let (total_lines, max_width) = buffer
.layout_runs()
@@ -85,7 +75,10 @@ impl Pipeline {
(i + 1, buffer.line_w.max(max))
});
- let total_height = total_lines as f32 * line_height;
+ let total_height = total_lines as f32 * line_height * scale_factor;
+ let max_width = max_width * scale_factor;
+
+ let bounds = bounds * scale_factor;
let x = match horizontal_alignment {
alignment::Horizontal::Left => bounds.x,
@@ -99,16 +92,14 @@ impl Pipeline {
alignment::Vertical::Bottom => bounds.y - total_height,
};
- // TODO: Subpixel glyph positioning
- let x = x.round() as i32;
- let y = y.round() as i32;
-
let mut swash = cosmic_text::SwashCache::new();
for run in buffer.layout_runs() {
for glyph in run.glyphs {
+ let physical_glyph = glyph.physical((x, y), scale_factor);
+
if let Some((buffer, placement)) = self.glyph_cache.allocate(
- glyph.cache_key,
+ physical_glyph.cache_key,
color,
font_system,
&mut swash,
@@ -121,8 +112,9 @@ impl Pipeline {
.expect("Create glyph pixel map");
pixels.draw_pixmap(
- x + glyph.x_int + placement.left,
- y - glyph.y_int - placement.top + run.line_y as i32,
+ physical_glyph.x + placement.left,
+ physical_glyph.y - placement.top
+ + (run.line_y * scale_factor).round() as i32,
pixmap,
&tiny_skia::PixmapPaint::default(),
tiny_skia::Transform::identity(),
@@ -134,7 +126,7 @@ impl Pipeline {
}
pub fn trim_cache(&mut self) {
- self.render_cache.trim();
+ self.cache.get_mut().trim();
self.glyph_cache.trim();
}
@@ -147,7 +139,7 @@ impl Pipeline {
bounds: Size,
shaping: Shaping,
) -> (f32, f32) {
- let mut measurement_cache = self.measurement_cache.borrow_mut();
+ let mut measurement_cache = self.cache.borrow_mut();
let line_height = f32::from(line_height.to_absolute(Pixels(size)));
@@ -184,7 +176,7 @@ impl Pipeline {
point: Point,
_nearest_only: bool,
) -> Option<Hit> {
- let mut measurement_cache = self.measurement_cache.borrow_mut();
+ let mut measurement_cache = self.cache.borrow_mut();
let line_height = f32::from(line_height.to_absolute(Pixels(size)));
@@ -204,10 +196,6 @@ impl Pipeline {
Some(Hit::CharOffset(cursor.index))
}
-
- pub fn trim_measurement_cache(&mut self) {
- self.measurement_cache.borrow_mut().trim();
- }
}
fn to_family(family: font::Family) -> cosmic_text::Family<'static> {
diff --git a/tiny_skia/src/window/compositor.rs b/tiny_skia/src/window/compositor.rs
index 9999a188..f3be3f16 100644
--- a/tiny_skia/src/window/compositor.rs
+++ b/tiny_skia/src/window/compositor.rs
@@ -1,5 +1,5 @@
-use crate::core::{Color, Rectangle};
-use crate::graphics::compositor::{self, Information, SurfaceError};
+use crate::core::{Color, Rectangle, Size};
+use crate::graphics::compositor::{self, Information};
use crate::graphics::damage;
use crate::graphics::{Error, Primitive, Viewport};
use crate::{Backend, Renderer, Settings};
@@ -79,7 +79,7 @@ impl<Theme> crate::graphics::Compositor for Compositor<Theme> {
viewport: &Viewport,
background_color: Color,
overlay: &[T],
- ) -> Result<(), SurfaceError> {
+ ) -> Result<(), compositor::SurfaceError> {
renderer.with_primitives(|backend, primitives| {
present(
backend,
@@ -91,6 +91,26 @@ impl<Theme> crate::graphics::Compositor for Compositor<Theme> {
)
})
}
+
+ fn screenshot<T: AsRef<str>>(
+ &mut self,
+ renderer: &mut Self::Renderer,
+ surface: &mut Self::Surface,
+ viewport: &Viewport,
+ background_color: Color,
+ overlay: &[T],
+ ) -> Vec<u8> {
+ renderer.with_primitives(|backend, primitives| {
+ screenshot(
+ surface,
+ backend,
+ primitives,
+ viewport,
+ background_color,
+ overlay,
+ )
+ })
+ }
}
pub fn new<Theme>(settings: Settings) -> (Compositor<Theme>, Backend) {
@@ -156,3 +176,53 @@ pub fn present<T: AsRef<str>>(
Ok(())
}
+
+pub fn screenshot<T: AsRef<str>>(
+ surface: &mut Surface,
+ backend: &mut Backend,
+ primitives: &[Primitive],
+ viewport: &Viewport,
+ background_color: Color,
+ overlay: &[T],
+) -> Vec<u8> {
+ let size = viewport.physical_size();
+
+ let mut offscreen_buffer: Vec<u32> =
+ vec![0; size.width as usize * size.height as usize];
+
+ backend.draw(
+ &mut tiny_skia::PixmapMut::from_bytes(
+ bytemuck::cast_slice_mut(&mut offscreen_buffer),
+ size.width,
+ size.height,
+ )
+ .expect("Create offscreen pixel map"),
+ &mut surface.clip_mask,
+ primitives,
+ viewport,
+ &[Rectangle::with_size(Size::new(
+ size.width as f32,
+ size.height as f32,
+ ))],
+ background_color,
+ overlay,
+ );
+
+ offscreen_buffer.iter().fold(
+ Vec::with_capacity(offscreen_buffer.len() * 4),
+ |mut acc, pixel| {
+ const A_MASK: u32 = 0xFF_00_00_00;
+ const R_MASK: u32 = 0x00_FF_00_00;
+ const G_MASK: u32 = 0x00_00_FF_00;
+ const B_MASK: u32 = 0x00_00_00_FF;
+
+ let a = ((A_MASK & pixel) >> 24) as u8;
+ let r = ((R_MASK & pixel) >> 16) as u8;
+ let g = ((G_MASK & pixel) >> 8) as u8;
+ let b = (B_MASK & pixel) as u8;
+
+ acc.extend([r, g, b, a]);
+ acc
+ },
+ )
+}
diff --git a/wgpu/Cargo.toml b/wgpu/Cargo.toml
index 41eb4c23..15db5b5d 100644
--- a/wgpu/Cargo.toml
+++ b/wgpu/Cargo.toml
@@ -11,6 +11,7 @@ repository = "https://github.com/iced-rs/iced"
geometry = ["iced_graphics/geometry", "lyon"]
image = ["iced_graphics/image"]
svg = ["resvg"]
+web-colors = ["iced_graphics/web-colors"]
[dependencies]
wgpu = "0.16"
@@ -44,14 +45,10 @@ path = "../graphics"
[dependencies.glyphon]
version = "0.2"
git = "https://github.com/hecrj/glyphon.git"
-rev = "f145067d292082abdd1f2b2481812d4a52c394ec"
-
-[dependencies.encase]
-version = "0.3.0"
-features = ["glam"]
+rev = "8324f20158a62f8520bad4ed09f6aa5552f8f2a6"
[dependencies.glam]
-version = "0.21.3"
+version = "0.24"
[dependencies.lyon]
version = "1.0"
diff --git a/wgpu/src/backend.rs b/wgpu/src/backend.rs
index def80a81..eecba2f1 100644
--- a/wgpu/src/backend.rs
+++ b/wgpu/src/backend.rs
@@ -1,6 +1,7 @@
use crate::core;
use crate::core::{Color, Font, Point, Size};
use crate::graphics::backend;
+use crate::graphics::color;
use crate::graphics::{Primitive, Transformation, Viewport};
use crate::quad;
use crate::text;
@@ -239,7 +240,7 @@ impl Backend {
load: match clear_color {
Some(background_color) => wgpu::LoadOp::Clear({
let [r, g, b, a] =
- background_color.into_linear();
+ color::pack(background_color).components();
wgpu::Color {
r: f64::from(r),
@@ -265,8 +266,12 @@ impl Backend {
}
if !layer.quads.is_empty() {
- self.quad_pipeline
- .render(quad_layer, bounds, &mut render_pass);
+ self.quad_pipeline.render(
+ quad_layer,
+ bounds,
+ &layer.quads,
+ &mut render_pass,
+ );
quad_layer += 1;
}
@@ -329,12 +334,6 @@ impl Backend {
}
}
-impl iced_graphics::Backend for Backend {
- fn trim_measurements(&mut self) {
- self.text_pipeline.trim_measurement_cache()
- }
-}
-
impl backend::Text for Backend {
const ICON_FONT: Font = Font::with_name("Iced-Icons");
const CHECKMARK_ICON: char = '\u{f00c}';
diff --git a/wgpu/src/buffer.rs b/wgpu/src/buffer.rs
index c210dd4e..94122187 100644
--- a/wgpu/src/buffer.rs
+++ b/wgpu/src/buffer.rs
@@ -1,7 +1,3 @@
-//! Utilities for buffer operations.
-pub mod dynamic;
-pub mod r#static;
-
use std::marker::PhantomData;
use std::ops::RangeBounds;
@@ -10,7 +6,8 @@ pub struct Buffer<T> {
label: &'static str,
size: u64,
usage: wgpu::BufferUsages,
- raw: wgpu::Buffer,
+ pub(crate) raw: wgpu::Buffer,
+ offsets: Vec<wgpu::BufferAddress>,
type_: PhantomData<T>,
}
@@ -35,6 +32,7 @@ impl<T: bytemuck::Pod> Buffer<T> {
size,
usage,
raw,
+ offsets: Vec::new(),
type_: PhantomData,
}
}
@@ -43,6 +41,8 @@ impl<T: bytemuck::Pod> Buffer<T> {
let new_size = (std::mem::size_of::<T>() * new_count) as u64;
if self.size < new_size {
+ self.offsets.clear();
+
self.raw = device.create_buffer(&wgpu::BufferDescriptor {
label: Some(self.label),
size: new_size,
@@ -58,17 +58,19 @@ impl<T: bytemuck::Pod> Buffer<T> {
}
}
+ /// Returns the size of the written bytes.
pub fn write(
- &self,
+ &mut self,
queue: &wgpu::Queue,
- offset_count: usize,
+ offset: usize,
contents: &[T],
- ) {
- queue.write_buffer(
- &self.raw,
- (std::mem::size_of::<T>() * offset_count) as u64,
- bytemuck::cast_slice(contents),
- );
+ ) -> usize {
+ let bytes: &[u8] = bytemuck::cast_slice(contents);
+ queue.write_buffer(&self.raw, offset as u64, bytes);
+
+ self.offsets.push(offset as u64);
+
+ bytes.len()
}
pub fn slice(
@@ -77,6 +79,21 @@ impl<T: bytemuck::Pod> Buffer<T> {
) -> wgpu::BufferSlice<'_> {
self.raw.slice(bounds)
}
+
+ /// Returns the slice calculated from the offset stored at the given index.
+ pub fn slice_from_index(&self, index: usize) -> wgpu::BufferSlice<'_> {
+ self.raw.slice(self.offset_at(index)..)
+ }
+
+ /// Clears any temporary data (i.e. offsets) from the buffer.
+ pub fn clear(&mut self) {
+ self.offsets.clear()
+ }
+
+ /// Returns the offset at `index`, if it exists.
+ fn offset_at(&self, index: usize) -> &wgpu::BufferAddress {
+ self.offsets.get(index).expect("No offset at index.")
+ }
}
fn next_copy_size<T>(amount: usize) -> u64 {
diff --git a/wgpu/src/buffer/dynamic.rs b/wgpu/src/buffer/dynamic.rs
deleted file mode 100644
index 43fc47ac..00000000
--- a/wgpu/src/buffer/dynamic.rs
+++ /dev/null
@@ -1,202 +0,0 @@
-//! 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.
-#[derive(Debug)]
-pub struct Buffer<T: ShaderType> {
- offsets: Vec<wgpu::DynamicOffset>,
- cpu: Internal,
- gpu: wgpu::Buffer,
- label: &'static str,
- size: u64,
- _data: PhantomData<T>,
-}
-
-impl<T: ShaderType + WriteInto> Buffer<T> {
- /// Creates a new dynamic uniform buffer.
- pub fn uniform(device: &wgpu::Device, label: &'static str) -> Self {
- Buffer::new(
- device,
- Internal::Uniform(encase::DynamicUniformBuffer::new(Vec::new())),
- label,
- wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
- )
- }
-
- #[cfg(not(target_arch = "wasm32"))]
- /// Creates a new dynamic storage buffer.
- pub fn storage(device: &wgpu::Device, label: &'static str) -> Self {
- Buffer::new(
- device,
- Internal::Storage(encase::DynamicStorageBuffer::new(Vec::new())),
- label,
- wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST,
- )
- }
-
- fn new(
- device: &wgpu::Device,
- dynamic_buffer_type: Internal,
- label: &'static str,
- usage: wgpu::BufferUsages,
- ) -> Self {
- let initial_size = u64::from(T::min_size());
-
- Self {
- offsets: Vec::new(),
- cpu: dynamic_buffer_type,
- gpu: Buffer::<T>::create_gpu_buffer(
- device,
- label,
- usage,
- initial_size,
- ),
- label,
- size: initial_size,
- _data: Default::default(),
- }
- }
-
- fn create_gpu_buffer(
- device: &wgpu::Device,
- label: &'static str,
- usage: wgpu::BufferUsages,
- size: u64,
- ) -> wgpu::Buffer {
- device.create_buffer(&wgpu::BufferDescriptor {
- label: Some(label),
- size,
- usage,
- mapped_at_creation: false,
- })
- }
-
- /// Write a new value to the CPU buffer with proper alignment. Stores the returned offset value
- /// in the buffer for future use.
- pub fn push(&mut self, value: &T) {
- //this write operation on the cpu buffer will adjust for uniform alignment requirements
- let offset = self.cpu.write(value);
- self.offsets.push(offset);
- }
-
- /// Resize buffer contents if necessary. This will re-create the GPU buffer if current size is
- /// less than the newly computed size from the CPU buffer.
- ///
- /// If the gpu buffer is resized, its bind group will need to be recreated!
- pub fn resize(&mut self, device: &wgpu::Device) -> bool {
- let new_size = self.cpu.get_ref().len() as u64;
-
- if self.size < new_size {
- let usages = match self.cpu {
- Internal::Uniform(_) => {
- wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST
- }
- #[cfg(not(target_arch = "wasm32"))]
- Internal::Storage(_) => {
- wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST
- }
- };
-
- self.gpu = Buffer::<T>::create_gpu_buffer(
- device, self.label, usages, new_size,
- );
- self.size = new_size;
- true
- } else {
- false
- }
- }
-
- /// Write the contents of this dynamic buffer to the GPU via staging belt command.
- pub fn write(&mut self, queue: &wgpu::Queue) {
- queue.write_buffer(&self.gpu, 0, self.cpu.get_ref());
- }
-
- // Gets the aligned offset at the given index from the CPU buffer.
- pub fn offset_at_index(&self, index: usize) -> wgpu::DynamicOffset {
- let offset = self
- .offsets
- .get(index)
- .copied()
- .expect("Index not found in offsets.");
-
- offset
- }
-
- /// Returns a reference to the GPU buffer.
- pub fn raw(&self) -> &wgpu::Buffer {
- &self.gpu
- }
-
- /// Reset the buffer.
- pub fn clear(&mut self) {
- self.offsets.clear();
- self.cpu.clear();
- }
-}
-
-// 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>>),
-}
-
-impl Internal {
- /// Writes the current value to its CPU buffer with proper alignment.
- pub(super) fn write<T: ShaderType + WriteInto>(
- &mut self,
- value: &T,
- ) -> wgpu::DynamicOffset {
- match self {
- Internal::Uniform(buf) => buf
- .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.")
- as u32,
- }
- }
-
- /// Returns bytearray of aligned CPU buffer.
- pub(super) fn get_ref(&self) -> &[u8] {
- match self {
- Internal::Uniform(buf) => buf.as_ref(),
- #[cfg(not(target_arch = "wasm32"))]
- Internal::Storage(buf) => buf.as_ref(),
- }
- }
-
- /// Resets the CPU buffer.
- pub(super) fn clear(&mut self) {
- match self {
- Internal::Uniform(buf) => {
- buf.as_mut().clear();
- buf.set_offset(0);
- }
- #[cfg(not(target_arch = "wasm32"))]
- Internal::Storage(buf) => {
- buf.as_mut().clear();
- buf.set_offset(0);
- }
- }
- }
-}
-
-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
deleted file mode 100644
index d8ae116e..00000000
--- a/wgpu/src/buffer/static.rs
+++ /dev/null
@@ -1,107 +0,0 @@
-use bytemuck::{Pod, Zeroable};
-use std::marker::PhantomData;
-use std::mem;
-
-const DEFAULT_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 struct Buffer<T> {
- //stored sequentially per mesh iteration; refers to the offset index in the GPU buffer
- offsets: Vec<wgpu::BufferAddress>,
- label: &'static str,
- usages: wgpu::BufferUsages,
- gpu: wgpu::Buffer,
- size: wgpu::BufferAddress,
- _data: PhantomData<T>,
-}
-
-impl<T: Pod + Zeroable> Buffer<T> {
- /// Initialize a new static buffer.
- pub fn new(
- device: &wgpu::Device,
- label: &'static str,
- usages: wgpu::BufferUsages,
- ) -> Self {
- let size = (mem::size_of::<T>() as u64) * DEFAULT_COUNT;
-
- Self {
- offsets: Vec::new(),
- label,
- usages,
- gpu: Self::gpu_buffer(device, label, size, usages),
- size,
- _data: PhantomData,
- }
- }
-
- fn gpu_buffer(
- device: &wgpu::Device,
- label: &'static str,
- size: wgpu::BufferAddress,
- usage: wgpu::BufferUsages,
- ) -> wgpu::Buffer {
- device.create_buffer(&wgpu::BufferDescriptor {
- label: Some(label),
- size,
- usage,
- mapped_at_creation: false,
- })
- }
-
- /// Returns whether or not the buffer needs to be recreated. This can happen whenever mesh data
- /// changes & a redraw is requested.
- pub fn resize(&mut self, device: &wgpu::Device, new_count: usize) -> bool {
- let size = (mem::size_of::<T>() * new_count) as u64;
-
- if self.size < size {
- self.size =
- (mem::size_of::<T>() * (new_count + new_count / 2)) as u64;
-
- self.gpu =
- Self::gpu_buffer(device, self.label, self.size, self.usages);
-
- self.offsets.clear();
- true
- } else {
- false
- }
- }
-
- /// Writes the current vertex data to the gpu buffer with a memcpy & stores its offset.
- ///
- /// Returns the size of the written bytes.
- pub fn write(
- &mut self,
- queue: &wgpu::Queue,
- offset: u64,
- content: &[T],
- ) -> u64 {
- let bytes = bytemuck::cast_slice(content);
- let bytes_size = bytes.len() as u64;
-
- queue.write_buffer(&self.gpu, offset, bytes);
- self.offsets.push(offset);
-
- bytes_size
- }
-
- fn offset_at(&self, index: usize) -> &wgpu::BufferAddress {
- self.offsets
- .get(index)
- .expect("Offset at index does not exist.")
- }
-
- /// Returns the slice calculated from the offset stored at the given index.
- /// e.g. to calculate the slice for the 2nd mesh in the layer, this would be the offset at index
- /// 1 that we stored earlier when writing.
- pub fn slice_from_index(&self, index: usize) -> wgpu::BufferSlice<'_> {
- self.gpu.slice(self.offset_at(index)..)
- }
-
- /// Clears any temporary data from the buffer.
- pub fn clear(&mut self) {
- self.offsets.clear()
- }
-}
diff --git a/wgpu/src/color.rs b/wgpu/src/color.rs
new file mode 100644
index 00000000..a1025601
--- /dev/null
+++ b/wgpu/src/color.rs
@@ -0,0 +1,165 @@
+use std::borrow::Cow;
+
+pub fn convert(
+ device: &wgpu::Device,
+ encoder: &mut wgpu::CommandEncoder,
+ source: wgpu::Texture,
+ format: wgpu::TextureFormat,
+) -> wgpu::Texture {
+ if source.format() == format {
+ return source;
+ }
+
+ let sampler = device.create_sampler(&wgpu::SamplerDescriptor {
+ label: Some("iced_wgpu.offscreen.sampler"),
+ ..Default::default()
+ });
+
+ //sampler in 0
+ let sampler_layout =
+ device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
+ label: Some("iced_wgpu.offscreen.blit.sampler_layout"),
+ entries: &[wgpu::BindGroupLayoutEntry {
+ binding: 0,
+ visibility: wgpu::ShaderStages::FRAGMENT,
+ ty: wgpu::BindingType::Sampler(
+ wgpu::SamplerBindingType::NonFiltering,
+ ),
+ count: None,
+ }],
+ });
+
+ let sampler_bind_group =
+ device.create_bind_group(&wgpu::BindGroupDescriptor {
+ label: Some("iced_wgpu.offscreen.sampler.bind_group"),
+ layout: &sampler_layout,
+ entries: &[wgpu::BindGroupEntry {
+ binding: 0,
+ resource: wgpu::BindingResource::Sampler(&sampler),
+ }],
+ });
+
+ let texture_layout =
+ device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
+ label: Some("iced_wgpu.offscreen.blit.texture_layout"),
+ entries: &[wgpu::BindGroupLayoutEntry {
+ binding: 0,
+ visibility: wgpu::ShaderStages::FRAGMENT,
+ ty: wgpu::BindingType::Texture {
+ sample_type: wgpu::TextureSampleType::Float {
+ filterable: false,
+ },
+ view_dimension: wgpu::TextureViewDimension::D2,
+ multisampled: false,
+ },
+ count: None,
+ }],
+ });
+
+ let pipeline_layout =
+ device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
+ label: Some("iced_wgpu.offscreen.blit.pipeline_layout"),
+ bind_group_layouts: &[&sampler_layout, &texture_layout],
+ push_constant_ranges: &[],
+ });
+
+ let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
+ label: Some("iced_wgpu.offscreen.blit.shader"),
+ source: wgpu::ShaderSource::Wgsl(Cow::Borrowed(include_str!(
+ "shader/blit.wgsl"
+ ))),
+ });
+
+ let pipeline =
+ device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
+ label: Some("iced_wgpu.offscreen.blit.pipeline"),
+ layout: Some(&pipeline_layout),
+ vertex: wgpu::VertexState {
+ module: &shader,
+ entry_point: "vs_main",
+ buffers: &[],
+ },
+ fragment: Some(wgpu::FragmentState {
+ module: &shader,
+ entry_point: "fs_main",
+ targets: &[Some(wgpu::ColorTargetState {
+ format,
+ blend: Some(wgpu::BlendState {
+ color: wgpu::BlendComponent {
+ src_factor: wgpu::BlendFactor::SrcAlpha,
+ dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha,
+ operation: wgpu::BlendOperation::Add,
+ },
+ alpha: wgpu::BlendComponent {
+ src_factor: wgpu::BlendFactor::One,
+ dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha,
+ operation: wgpu::BlendOperation::Add,
+ },
+ }),
+ write_mask: wgpu::ColorWrites::ALL,
+ })],
+ }),
+ primitive: wgpu::PrimitiveState {
+ topology: wgpu::PrimitiveTopology::TriangleList,
+ front_face: wgpu::FrontFace::Cw,
+ ..Default::default()
+ },
+ depth_stencil: None,
+ multisample: Default::default(),
+ multiview: None,
+ });
+
+ let texture = device.create_texture(&wgpu::TextureDescriptor {
+ label: Some("iced_wgpu.offscreen.conversion.source_texture"),
+ size: source.size(),
+ mip_level_count: 1,
+ sample_count: 1,
+ dimension: wgpu::TextureDimension::D2,
+ format,
+ usage: wgpu::TextureUsages::RENDER_ATTACHMENT
+ | wgpu::TextureUsages::COPY_SRC,
+ view_formats: &[],
+ });
+
+ let view = &texture.create_view(&wgpu::TextureViewDescriptor::default());
+
+ let texture_bind_group =
+ device.create_bind_group(&wgpu::BindGroupDescriptor {
+ label: Some("iced_wgpu.offscreen.blit.texture_bind_group"),
+ layout: &texture_layout,
+ entries: &[wgpu::BindGroupEntry {
+ binding: 0,
+ resource: wgpu::BindingResource::TextureView(
+ &source
+ .create_view(&wgpu::TextureViewDescriptor::default()),
+ ),
+ }],
+ });
+
+ let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
+ label: Some("iced_wgpu.offscreen.blit.render_pass"),
+ color_attachments: &[Some(wgpu::RenderPassColorAttachment {
+ view,
+ resolve_target: None,
+ ops: wgpu::Operations {
+ load: wgpu::LoadOp::Load,
+ store: true,
+ },
+ })],
+ depth_stencil_attachment: None,
+ });
+
+ pass.set_pipeline(&pipeline);
+ pass.set_bind_group(0, &sampler_bind_group, &[]);
+ pass.set_bind_group(1, &texture_bind_group, &[]);
+ pass.draw(0..6, 0..1);
+
+ texture
+}
+
+#[derive(Debug, Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
+#[repr(C)]
+struct Vertex {
+ ndc: [f32; 2],
+ uv: [f32; 2],
+}
diff --git a/wgpu/src/geometry.rs b/wgpu/src/geometry.rs
index 8cfed1e5..f81b5b2f 100644
--- a/wgpu/src/geometry.rs
+++ b/wgpu/src/geometry.rs
@@ -1,17 +1,19 @@
-use crate::core::{Gradient, Point, Rectangle, Size, Vector};
+//! Build and draw geometry.
+use crate::core::{Point, Rectangle, Size, Vector};
+use crate::graphics::color;
use crate::graphics::geometry::fill::{self, Fill};
use crate::graphics::geometry::{
LineCap, LineDash, LineJoin, Path, Stroke, Style, Text,
};
use crate::graphics::primitive::{self, Primitive};
+use crate::graphics::Gradient;
+use iced_graphics::gradient;
use lyon::geom::euclid;
use lyon::tessellation;
use std::borrow::Cow;
-/// The frame of a [`Canvas`].
-///
-/// [`Canvas`]: crate::widget::Canvas
+/// A frame for drawing some geometry.
#[allow(missing_debug_implementations)]
pub struct Frame {
size: Size,
@@ -24,10 +26,7 @@ pub struct Frame {
enum Buffer {
Solid(tessellation::VertexBuffers<primitive::ColoredVertex2D, u32>),
- Gradient(
- tessellation::VertexBuffers<primitive::Vertex2D, u32>,
- Gradient,
- ),
+ Gradient(tessellation::VertexBuffers<primitive::GradientVertex2D, u32>),
}
struct BufferStack {
@@ -49,12 +48,11 @@ impl BufferStack {
));
}
},
- Style::Gradient(gradient) => match self.stack.last() {
- Some(Buffer::Gradient(_, last)) if gradient == last => {}
+ Style::Gradient(_) => match self.stack.last() {
+ Some(Buffer::Gradient(_)) => {}
_ => {
self.stack.push(Buffer::Gradient(
tessellation::VertexBuffers::new(),
- gradient.clone(),
));
}
},
@@ -71,12 +69,17 @@ impl BufferStack {
(Style::Solid(color), Buffer::Solid(buffer)) => {
Box::new(tessellation::BuffersBuilder::new(
buffer,
- TriangleVertex2DBuilder(color.into_linear()),
+ TriangleVertex2DBuilder(color::pack(*color)),
+ ))
+ }
+ (Style::Gradient(gradient), Buffer::Gradient(buffer)) => {
+ Box::new(tessellation::BuffersBuilder::new(
+ buffer,
+ GradientVertex2DBuilder {
+ gradient: gradient.pack(),
+ },
))
}
- (Style::Gradient(_), Buffer::Gradient(buffer, _)) => Box::new(
- tessellation::BuffersBuilder::new(buffer, Vertex2DBuilder),
- ),
_ => unreachable!(),
}
}
@@ -89,12 +92,17 @@ impl BufferStack {
(Style::Solid(color), Buffer::Solid(buffer)) => {
Box::new(tessellation::BuffersBuilder::new(
buffer,
- TriangleVertex2DBuilder(color.into_linear()),
+ TriangleVertex2DBuilder(color::pack(*color)),
+ ))
+ }
+ (Style::Gradient(gradient), Buffer::Gradient(buffer)) => {
+ Box::new(tessellation::BuffersBuilder::new(
+ buffer,
+ GradientVertex2DBuilder {
+ gradient: gradient.pack(),
+ },
))
}
- (Style::Gradient(_), Buffer::Gradient(buffer, _)) => Box::new(
- tessellation::BuffersBuilder::new(buffer, Vertex2DBuilder),
- ),
_ => unreachable!(),
}
}
@@ -132,11 +140,13 @@ impl Transform {
}
fn transform_gradient(&self, mut gradient: Gradient) -> Gradient {
- let (start, end) = match &mut gradient {
- Gradient::Linear(linear) => (&mut linear.start, &mut linear.end),
- };
- self.transform_point(start);
- self.transform_point(end);
+ match &mut gradient {
+ Gradient::Linear(linear) => {
+ self.transform_point(&mut linear.start);
+ self.transform_point(&mut linear.end);
+ }
+ }
+
gradient
}
}
@@ -353,10 +363,12 @@ impl Frame {
self.pop_transform();
}
+ /// Pushes the current transform in the transform stack.
pub fn push_transform(&mut self) {
self.transforms.previous.push(self.transforms.current);
}
+ /// Pops a transform from the transform stack and sets it as the current transform.
pub fn pop_transform(&mut self) {
self.transforms.current = self.transforms.previous.pop().unwrap();
}
@@ -373,14 +385,16 @@ impl Frame {
f(&mut frame);
- let translation = Vector::new(region.x, region.y);
+ let origin = Point::new(region.x, region.y);
- self.clip(frame, translation);
+ self.clip(frame, origin);
}
- pub fn clip(&mut self, frame: Frame, translation: Vector) {
+ /// Draws the clipped contents of the given [`Frame`] with origin at the given [`Point`].
+ pub fn clip(&mut self, frame: Frame, at: Point) {
let size = frame.size();
let primitives = frame.into_primitives();
+ let translation = Vector::new(at.x, at.y);
let (text, meshes) = primitives
.into_iter()
@@ -459,7 +473,7 @@ impl Frame {
})
}
}
- Buffer::Gradient(buffer, gradient) => {
+ Buffer::Gradient(buffer) => {
if !buffer.indices.is_empty() {
self.primitives.push(Primitive::GradientMesh {
buffers: primitive::Mesh2D {
@@ -467,7 +481,6 @@ impl Frame {
indices: buffer.indices,
},
size: self.size,
- gradient,
})
}
}
@@ -478,39 +491,43 @@ impl Frame {
}
}
-struct Vertex2DBuilder;
+struct GradientVertex2DBuilder {
+ gradient: gradient::Packed,
+}
-impl tessellation::FillVertexConstructor<primitive::Vertex2D>
- for Vertex2DBuilder
+impl tessellation::FillVertexConstructor<primitive::GradientVertex2D>
+ for GradientVertex2DBuilder
{
fn new_vertex(
&mut self,
vertex: tessellation::FillVertex<'_>,
- ) -> primitive::Vertex2D {
+ ) -> primitive::GradientVertex2D {
let position = vertex.position();
- primitive::Vertex2D {
+ primitive::GradientVertex2D {
position: [position.x, position.y],
+ gradient: self.gradient,
}
}
}
-impl tessellation::StrokeVertexConstructor<primitive::Vertex2D>
- for Vertex2DBuilder
+impl tessellation::StrokeVertexConstructor<primitive::GradientVertex2D>
+ for GradientVertex2DBuilder
{
fn new_vertex(
&mut self,
vertex: tessellation::StrokeVertex<'_, '_>,
- ) -> primitive::Vertex2D {
+ ) -> primitive::GradientVertex2D {
let position = vertex.position();
- primitive::Vertex2D {
+ primitive::GradientVertex2D {
position: [position.x, position.y],
+ gradient: self.gradient,
}
}
}
-struct TriangleVertex2DBuilder([f32; 4]);
+struct TriangleVertex2DBuilder(color::Packed);
impl tessellation::FillVertexConstructor<primitive::ColoredVertex2D>
for TriangleVertex2DBuilder
diff --git a/wgpu/src/image.rs b/wgpu/src/image.rs
index 263bcfa2..553ba330 100644
--- a/wgpu/src/image.rs
+++ b/wgpu/src/image.rs
@@ -121,7 +121,7 @@ impl Layer {
);
let _ = self.instances.resize(device, instances.len());
- self.instances.write(queue, 0, instances);
+ let _ = self.instances.write(queue, 0, instances);
self.instance_count = instances.len();
}
@@ -278,7 +278,7 @@ impl Pipeline {
let vertices =
device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
label: Some("iced_wgpu::image vertex buffer"),
- contents: bytemuck::cast_slice(&QUAD_VERTS),
+ contents: bytemuck::cast_slice(&QUAD_VERTICES),
usage: wgpu::BufferUsages::VERTEX,
});
@@ -369,7 +369,6 @@ impl Pipeline {
layer::Image::Raster { handle, bounds } => {
if let Some(atlas_entry) = raster_cache.upload(
device,
- queue,
encoder,
handle,
&mut self.texture_atlas,
@@ -395,7 +394,6 @@ impl Pipeline {
if let Some(atlas_entry) = vector_cache.upload(
device,
- queue,
encoder,
handle,
*color,
@@ -500,7 +498,7 @@ pub struct Vertex {
const QUAD_INDICES: [u16; 6] = [0, 1, 2, 0, 2, 3];
-const QUAD_VERTS: [Vertex; 4] = [
+const QUAD_VERTICES: [Vertex; 4] = [
Vertex {
_position: [0.0, 0.0],
},
diff --git a/wgpu/src/image/atlas.rs b/wgpu/src/image/atlas.rs
index 366fe623..e3de1290 100644
--- a/wgpu/src/image/atlas.rs
+++ b/wgpu/src/image/atlas.rs
@@ -13,6 +13,7 @@ use allocator::Allocator;
pub const SIZE: u32 = 2048;
use crate::core::Size;
+use crate::graphics::color;
#[derive(Debug)]
pub struct Atlas {
@@ -35,7 +36,11 @@ impl Atlas {
mip_level_count: 1,
sample_count: 1,
dimension: wgpu::TextureDimension::D2,
- format: wgpu::TextureFormat::Rgba8UnormSrgb,
+ format: if color::GAMMA_CORRECTION {
+ wgpu::TextureFormat::Rgba8UnormSrgb
+ } else {
+ wgpu::TextureFormat::Rgba8Unorm
+ },
usage: wgpu::TextureUsages::COPY_DST
| wgpu::TextureUsages::COPY_SRC
| wgpu::TextureUsages::TEXTURE_BINDING,
@@ -65,7 +70,6 @@ impl Atlas {
pub fn upload(
&mut self,
device: &wgpu::Device,
- queue: &wgpu::Queue,
encoder: &mut wgpu::CommandEncoder,
width: u32,
height: u32,
@@ -112,7 +116,8 @@ impl Atlas {
padding,
0,
allocation,
- queue,
+ device,
+ encoder,
);
}
Entry::Fragmented { fragments, .. } => {
@@ -127,7 +132,8 @@ impl Atlas {
padding,
offset,
&fragment.allocation,
- queue,
+ device,
+ encoder,
);
}
}
@@ -280,8 +286,11 @@ impl Atlas {
padding: u32,
offset: usize,
allocation: &Allocation,
- queue: &wgpu::Queue,
+ device: &wgpu::Device,
+ encoder: &mut wgpu::CommandEncoder,
) {
+ use wgpu::util::DeviceExt;
+
let (x, y) = allocation.position();
let Size { width, height } = allocation.size();
let layer = allocation.layer();
@@ -292,7 +301,22 @@ impl Atlas {
depth_or_array_layers: 1,
};
- queue.write_texture(
+ let buffer =
+ device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
+ label: Some("image upload buffer"),
+ contents: data,
+ usage: wgpu::BufferUsages::COPY_SRC,
+ });
+
+ encoder.copy_buffer_to_texture(
+ wgpu::ImageCopyBuffer {
+ buffer: &buffer,
+ layout: wgpu::ImageDataLayout {
+ offset: offset as u64,
+ bytes_per_row: Some(4 * image_width + padding),
+ rows_per_image: Some(image_height),
+ },
+ },
wgpu::ImageCopyTexture {
texture: &self.texture,
mip_level: 0,
@@ -303,12 +327,6 @@ impl Atlas {
},
aspect: wgpu::TextureAspect::default(),
},
- data,
- wgpu::ImageDataLayout {
- offset: offset as u64,
- bytes_per_row: Some(4 * image_width + padding),
- rows_per_image: Some(image_height),
- },
extent,
);
}
@@ -333,7 +351,11 @@ impl Atlas {
mip_level_count: 1,
sample_count: 1,
dimension: wgpu::TextureDimension::D2,
- format: wgpu::TextureFormat::Rgba8UnormSrgb,
+ format: if color::GAMMA_CORRECTION {
+ wgpu::TextureFormat::Rgba8UnormSrgb
+ } else {
+ wgpu::TextureFormat::Rgba8Unorm
+ },
usage: wgpu::TextureUsages::COPY_DST
| wgpu::TextureUsages::COPY_SRC
| wgpu::TextureUsages::TEXTURE_BINDING,
diff --git a/wgpu/src/image/raster.rs b/wgpu/src/image/raster.rs
index 9b38dce4..a6cba76a 100644
--- a/wgpu/src/image/raster.rs
+++ b/wgpu/src/image/raster.rs
@@ -63,7 +63,6 @@ impl Cache {
pub fn upload(
&mut self,
device: &wgpu::Device,
- queue: &wgpu::Queue,
encoder: &mut wgpu::CommandEncoder,
handle: &image::Handle,
atlas: &mut Atlas,
@@ -73,8 +72,7 @@ impl Cache {
if let Memory::Host(image) = memory {
let (width, height) = image.dimensions();
- let entry =
- atlas.upload(device, queue, encoder, width, height, image)?;
+ let entry = atlas.upload(device, encoder, width, height, image)?;
*memory = Memory::Device(entry);
}
diff --git a/wgpu/src/image/vector.rs b/wgpu/src/image/vector.rs
index 58bdf64a..6b9be651 100644
--- a/wgpu/src/image/vector.rs
+++ b/wgpu/src/image/vector.rs
@@ -74,7 +74,6 @@ impl Cache {
pub fn upload(
&mut self,
device: &wgpu::Device,
- queue: &wgpu::Queue,
encoder: &mut wgpu::CommandEncoder,
handle: &svg::Handle,
color: Option<Color>,
@@ -138,8 +137,8 @@ impl Cache {
});
}
- let allocation = atlas
- .upload(device, queue, encoder, width, height, &rgba)?;
+ let allocation =
+ atlas.upload(device, encoder, width, height, &rgba)?;
log::debug!("allocating {} {}x{}", id, width, height);
diff --git a/wgpu/src/layer.rs b/wgpu/src/layer.rs
index 8af72b9d..71570e3d 100644
--- a/wgpu/src/layer.rs
+++ b/wgpu/src/layer.rs
@@ -1,19 +1,19 @@
//! Organize rendering primitives into a flattened list of layers.
mod image;
-mod quad;
mod text;
pub mod mesh;
pub use image::Image;
pub use mesh::Mesh;
-pub use quad::Quad;
pub use text::Text;
use crate::core;
use crate::core::alignment;
-use crate::core::{Background, Color, Font, Point, Rectangle, Size, Vector};
+use crate::core::{Color, Font, Point, Rectangle, Size, Vector};
+use crate::graphics::color;
use crate::graphics::{Primitive, Viewport};
+use crate::quad::{self, Quad};
/// A group of primitives that should be clipped together.
#[derive(Debug)]
@@ -22,7 +22,7 @@ pub struct Layer<'a> {
pub bounds: Rectangle,
/// The quads of the [`Layer`].
- pub quads: Vec<Quad>,
+ pub quads: quad::Batch,
/// The triangle meshes of the [`Layer`].
pub meshes: Vec<Mesh<'a>>,
@@ -39,7 +39,7 @@ impl<'a> Layer<'a> {
pub fn new(bounds: Rectangle) -> Self {
Self {
bounds,
- quads: Vec::new(),
+ quads: quad::Batch::default(),
meshes: Vec::new(),
text: Vec::new(),
images: Vec::new(),
@@ -145,20 +145,18 @@ impl<'a> Layer<'a> {
} => {
let layer = &mut layers[current_layer];
- // TODO: Move some of these computations to the GPU (?)
- layer.quads.push(Quad {
+ let quad = Quad {
position: [
bounds.x + translation.x,
bounds.y + translation.y,
],
size: [bounds.width, bounds.height],
- color: match background {
- Background::Color(color) => color.into_linear(),
- },
+ border_color: color::pack(*border_color),
border_radius: *border_radius,
border_width: *border_width,
- border_color: border_color.into_linear(),
- });
+ };
+
+ layer.quads.add(quad, background);
}
Primitive::Image { handle, bounds } => {
let layer = &mut layers[current_layer];
@@ -198,11 +196,7 @@ impl<'a> Layer<'a> {
});
}
}
- Primitive::GradientMesh {
- buffers,
- size,
- gradient,
- } => {
+ Primitive::GradientMesh { buffers, size } => {
let layer = &mut layers[current_layer];
let bounds = Rectangle::new(
@@ -216,7 +210,6 @@ impl<'a> Layer<'a> {
origin: Point::new(translation.x, translation.y),
buffers,
clip_bounds,
- gradient,
});
}
}
diff --git a/wgpu/src/layer/mesh.rs b/wgpu/src/layer/mesh.rs
index 9dd14391..b7dd9a0b 100644
--- a/wgpu/src/layer/mesh.rs
+++ b/wgpu/src/layer/mesh.rs
@@ -1,5 +1,5 @@
//! A collection of triangle primitives.
-use crate::core::{Gradient, Point, Rectangle};
+use crate::core::{Point, Rectangle};
use crate::graphics::primitive;
/// A mesh of triangles.
@@ -22,13 +22,10 @@ pub enum Mesh<'a> {
origin: Point,
/// The vertex and index buffers of the [`Mesh`].
- buffers: &'a primitive::Mesh2D<primitive::Vertex2D>,
+ buffers: &'a primitive::Mesh2D<primitive::GradientVertex2D>,
/// The clipping bounds of the [`Mesh`].
clip_bounds: Rectangle<f32>,
-
- /// The gradient to apply to the [`Mesh`].
- gradient: &'a Gradient,
},
}
@@ -65,9 +62,15 @@ pub struct AttributeCount {
/// The total amount of solid vertices.
pub solid_vertices: usize,
+ /// The total amount of solid meshes.
+ pub solids: usize,
+
/// The total amount of gradient vertices.
pub gradient_vertices: usize,
+ /// The total amount of gradient meshes.
+ pub gradients: usize,
+
/// The total amount of indices.
pub indices: usize,
}
@@ -79,10 +82,12 @@ pub fn attribute_count_of<'a>(meshes: &'a [Mesh<'a>]) -> AttributeCount {
.fold(AttributeCount::default(), |mut count, mesh| {
match mesh {
Mesh::Solid { buffers, .. } => {
+ count.solids += 1;
count.solid_vertices += buffers.vertices.len();
count.indices += buffers.indices.len();
}
Mesh::Gradient { buffers, .. } => {
+ count.gradients += 1;
count.gradient_vertices += buffers.vertices.len();
count.indices += buffers.indices.len();
}
diff --git a/wgpu/src/layer/quad.rs b/wgpu/src/layer/quad.rs
deleted file mode 100644
index 0d8bde9d..00000000
--- a/wgpu/src/layer/quad.rs
+++ /dev/null
@@ -1,30 +0,0 @@
-/// A colored rectangle with a border.
-///
-/// This type can be directly uploaded to GPU memory.
-#[derive(Debug, Clone, Copy)]
-#[repr(C)]
-pub struct Quad {
- /// The position of the [`Quad`].
- pub position: [f32; 2],
-
- /// The size of the [`Quad`].
- pub size: [f32; 2],
-
- /// The color of the [`Quad`], in __linear RGB__.
- pub color: [f32; 4],
-
- /// The border color of the [`Quad`], in __linear RGB__.
- pub border_color: [f32; 4],
-
- /// The border radius of the [`Quad`].
- pub border_radius: [f32; 4],
-
- /// The border width of the [`Quad`].
- pub border_width: f32,
-}
-
-#[allow(unsafe_code)]
-unsafe impl bytemuck::Zeroable for Quad {}
-
-#[allow(unsafe_code)]
-unsafe impl bytemuck::Pod for Quad {}
diff --git a/wgpu/src/lib.rs b/wgpu/src/lib.rs
index 0b169140..86a962a5 100644
--- a/wgpu/src/lib.rs
+++ b/wgpu/src/lib.rs
@@ -25,7 +25,7 @@
)]
#![deny(
missing_debug_implementations,
- //missing_docs,
+ missing_docs,
unsafe_code,
unused_results,
clippy::extra_unused_lifetimes,
@@ -36,7 +36,7 @@
)]
#![forbid(rust_2018_idioms)]
#![allow(clippy::inherent_to_string, clippy::type_complexity)]
-#![cfg_attr(docsrs, feature(doc_cfg))]
+#![cfg_attr(docsrs, feature(doc_auto_cfg))]
pub mod layer;
pub mod settings;
pub mod window;
@@ -46,10 +46,13 @@ pub mod geometry;
mod backend;
mod buffer;
+mod color;
mod quad;
mod text;
mod triangle;
+use buffer::Buffer;
+
pub use iced_graphics as graphics;
pub use iced_graphics::core;
@@ -59,8 +62,6 @@ pub use backend::Backend;
pub use layer::Layer;
pub use settings::Settings;
-use buffer::Buffer;
-
#[cfg(any(feature = "image", feature = "svg"))]
mod image;
diff --git a/wgpu/src/quad.rs b/wgpu/src/quad.rs
index 8fa7359e..37d0c623 100644
--- a/wgpu/src/quad.rs
+++ b/wgpu/src/quad.rs
@@ -1,18 +1,27 @@
-use crate::core::Rectangle;
-use crate::graphics::Transformation;
-use crate::layer;
-use crate::Buffer;
+mod gradient;
+mod solid;
+
+use gradient::Gradient;
+use solid::Solid;
+
+use crate::core::{Background, Rectangle};
+use crate::graphics::color;
+use crate::graphics::{self, Transformation};
use bytemuck::{Pod, Zeroable};
-use std::mem;
use wgpu::util::DeviceExt;
+use std::mem;
+
#[cfg(feature = "tracing")]
use tracing::info_span;
+const INITIAL_INSTANCES: usize = 2_000;
+
#[derive(Debug)]
pub struct Pipeline {
- pipeline: wgpu::RenderPipeline,
+ solid: solid::Pipeline,
+ gradient: gradient::Pipeline,
constant_layout: wgpu::BindGroupLayout,
vertices: wgpu::Buffer,
indices: wgpu::Buffer,
@@ -39,107 +48,28 @@ impl Pipeline {
}],
});
- let layout =
- device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
- label: Some("iced_wgpu::quad pipeline layout"),
- push_constant_ranges: &[],
- bind_group_layouts: &[&constant_layout],
- });
-
- let shader =
- device.create_shader_module(wgpu::ShaderModuleDescriptor {
- label: Some("iced_wgpu quad shader"),
- source: wgpu::ShaderSource::Wgsl(std::borrow::Cow::Borrowed(
- include_str!("shader/quad.wgsl"),
- )),
- });
-
- let pipeline =
- device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
- label: Some("iced_wgpu::quad pipeline"),
- layout: Some(&layout),
- vertex: wgpu::VertexState {
- module: &shader,
- entry_point: "vs_main",
- buffers: &[
- wgpu::VertexBufferLayout {
- array_stride: mem::size_of::<Vertex>() as u64,
- step_mode: wgpu::VertexStepMode::Vertex,
- attributes: &[wgpu::VertexAttribute {
- shader_location: 0,
- format: wgpu::VertexFormat::Float32x2,
- offset: 0,
- }],
- },
- wgpu::VertexBufferLayout {
- array_stride: mem::size_of::<layer::Quad>() as u64,
- step_mode: wgpu::VertexStepMode::Instance,
- attributes: &wgpu::vertex_attr_array!(
- 1 => Float32x2,
- 2 => Float32x2,
- 3 => Float32x4,
- 4 => Float32x4,
- 5 => Float32x4,
- 6 => Float32,
- ),
- },
- ],
- },
- fragment: Some(wgpu::FragmentState {
- module: &shader,
- entry_point: "fs_main",
- targets: &[Some(wgpu::ColorTargetState {
- format,
- blend: Some(wgpu::BlendState {
- color: wgpu::BlendComponent {
- src_factor: wgpu::BlendFactor::SrcAlpha,
- dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha,
- operation: wgpu::BlendOperation::Add,
- },
- alpha: wgpu::BlendComponent {
- src_factor: wgpu::BlendFactor::One,
- dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha,
- operation: wgpu::BlendOperation::Add,
- },
- }),
- write_mask: wgpu::ColorWrites::ALL,
- })],
- }),
- primitive: wgpu::PrimitiveState {
- topology: wgpu::PrimitiveTopology::TriangleList,
- front_face: wgpu::FrontFace::Cw,
- ..Default::default()
- },
- depth_stencil: None,
- multisample: wgpu::MultisampleState {
- count: 1,
- mask: !0,
- alpha_to_coverage_enabled: false,
- },
- multiview: None,
- });
-
let vertices =
device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
label: Some("iced_wgpu::quad vertex buffer"),
- contents: bytemuck::cast_slice(&QUAD_VERTS),
+ contents: bytemuck::cast_slice(&VERTICES),
usage: wgpu::BufferUsages::VERTEX,
});
let indices =
device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
label: Some("iced_wgpu::quad index buffer"),
- contents: bytemuck::cast_slice(&QUAD_INDICES),
+ contents: bytemuck::cast_slice(&INDICES),
usage: wgpu::BufferUsages::INDEX,
});
- Pipeline {
- pipeline,
- constant_layout,
+ Self {
vertices,
indices,
+ solid: solid::Pipeline::new(device, format, &constant_layout),
+ gradient: gradient::Pipeline::new(device, format, &constant_layout),
layers: Vec::new(),
prepare_layer: 0,
+ constant_layout,
}
}
@@ -147,7 +77,7 @@ impl Pipeline {
&mut self,
device: &wgpu::Device,
queue: &wgpu::Queue,
- instances: &[layer::Quad],
+ quads: &Batch,
transformation: Transformation,
scale: f32,
) {
@@ -156,7 +86,7 @@ impl Pipeline {
}
let layer = &mut self.layers[self.prepare_layer];
- layer.prepare(device, queue, instances, transformation, scale);
+ layer.prepare(device, queue, quads, transformation, scale);
self.prepare_layer += 1;
}
@@ -165,25 +95,49 @@ impl Pipeline {
&'a self,
layer: usize,
bounds: Rectangle<u32>,
+ quads: &Batch,
render_pass: &mut wgpu::RenderPass<'a>,
) {
if let Some(layer) = self.layers.get(layer) {
- render_pass.set_pipeline(&self.pipeline);
-
render_pass.set_scissor_rect(
bounds.x,
bounds.y,
bounds.width,
bounds.height,
);
-
render_pass.set_index_buffer(
self.indices.slice(..),
wgpu::IndexFormat::Uint16,
);
render_pass.set_vertex_buffer(0, self.vertices.slice(..));
- layer.draw(render_pass);
+ let mut solid_offset = 0;
+ let mut gradient_offset = 0;
+
+ for (kind, count) in &quads.order {
+ match kind {
+ Kind::Solid => {
+ self.solid.render(
+ render_pass,
+ &layer.constants,
+ &layer.solid,
+ solid_offset..(solid_offset + count),
+ );
+
+ solid_offset += count;
+ }
+ Kind::Gradient => {
+ self.gradient.render(
+ render_pass,
+ &layer.constants,
+ &layer.gradient,
+ gradient_offset..(gradient_offset + count),
+ );
+
+ gradient_offset += count;
+ }
+ }
+ }
}
}
@@ -196,8 +150,8 @@ impl Pipeline {
struct Layer {
constants: wgpu::BindGroup,
constants_buffer: wgpu::Buffer,
- instances: Buffer<layer::Quad>,
- instance_count: usize,
+ solid: solid::Layer,
+ gradient: gradient::Layer,
}
impl Layer {
@@ -221,18 +175,11 @@ impl Layer {
}],
});
- let instances = Buffer::new(
- device,
- "iced_wgpu::quad instance buffer",
- INITIAL_INSTANCES,
- wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
- );
-
Self {
constants,
constants_buffer,
- instances,
- instance_count: 0,
+ solid: solid::Layer::new(device),
+ gradient: gradient::Layer::new(device),
}
}
@@ -240,7 +187,7 @@ impl Layer {
&mut self,
device: &wgpu::Device,
queue: &wgpu::Queue,
- instances: &[layer::Quad],
+ quads: &Batch,
transformation: Transformation,
scale: f32,
) {
@@ -255,35 +202,138 @@ impl Layer {
bytemuck::bytes_of(&uniforms),
);
- let _ = self.instances.resize(device, instances.len());
- self.instances.write(queue, 0, instances);
- self.instance_count = instances.len();
+ self.solid.prepare(device, queue, &quads.solids);
+ self.gradient.prepare(device, queue, &quads.gradients);
}
+}
- pub fn draw<'a>(&'a self, render_pass: &mut wgpu::RenderPass<'a>) {
- #[cfg(feature = "tracing")]
- let _ = info_span!("Wgpu::Quad", "DRAW").entered();
+/// The properties of a quad.
+#[derive(Clone, Copy, Debug, Pod, Zeroable)]
+#[repr(C)]
+pub struct Quad {
+ /// The position of the [`Quad`].
+ pub position: [f32; 2],
- render_pass.set_bind_group(0, &self.constants, &[]);
- render_pass.set_vertex_buffer(1, self.instances.slice(..));
+ /// The size of the [`Quad`].
+ pub size: [f32; 2],
- render_pass.draw_indexed(
- 0..QUAD_INDICES.len() as u32,
- 0,
- 0..self.instance_count as u32,
- );
+ /// The border color of the [`Quad`], in __linear RGB__.
+ pub border_color: color::Packed,
+
+ /// The border radii of the [`Quad`].
+ pub border_radius: [f32; 4],
+
+ /// The border width of the [`Quad`].
+ pub border_width: f32,
+}
+
+/// A group of [`Quad`]s rendered together.
+#[derive(Default, Debug)]
+pub struct Batch {
+ /// The solid quads of the [`Layer`].
+ solids: Vec<Solid>,
+
+ /// The gradient quads of the [`Layer`].
+ gradients: Vec<Gradient>,
+
+ /// The quad order of the [`Layer`]; stored as a tuple of the quad type & its count.
+ order: Vec<(Kind, usize)>,
+}
+
+impl Batch {
+ /// Returns true if there are no quads of any type in [`Quads`].
+ pub fn is_empty(&self) -> bool {
+ self.solids.is_empty() && self.gradients.is_empty()
+ }
+
+ /// Adds a [`Quad`] with the provided `Background` type to the quad [`Layer`].
+ pub fn add(&mut self, quad: Quad, background: &Background) {
+ let kind = match background {
+ Background::Color(color) => {
+ self.solids.push(Solid {
+ color: color::pack(*color),
+ quad,
+ });
+
+ Kind::Solid
+ }
+ Background::Gradient(gradient) => {
+ self.gradients.push(Gradient {
+ gradient: graphics::gradient::pack(
+ gradient,
+ Rectangle::new(quad.position.into(), quad.size.into()),
+ ),
+ quad,
+ });
+
+ Kind::Gradient
+ }
+ };
+
+ match self.order.last_mut() {
+ Some((last_kind, count)) if kind == *last_kind => {
+ *count += 1;
+ }
+ _ => {
+ self.order.push((kind, 1));
+ }
+ }
}
}
+#[derive(Debug, Copy, Clone, PartialEq, Eq)]
+/// The kind of a quad.
+enum Kind {
+ /// A solid quad
+ Solid,
+ /// A gradient quad
+ Gradient,
+}
+
+fn color_target_state(
+ format: wgpu::TextureFormat,
+) -> [Option<wgpu::ColorTargetState>; 1] {
+ [Some(wgpu::ColorTargetState {
+ format,
+ blend: Some(wgpu::BlendState {
+ color: wgpu::BlendComponent {
+ src_factor: wgpu::BlendFactor::SrcAlpha,
+ dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha,
+ operation: wgpu::BlendOperation::Add,
+ },
+ alpha: wgpu::BlendComponent {
+ src_factor: wgpu::BlendFactor::One,
+ dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha,
+ operation: wgpu::BlendOperation::Add,
+ },
+ }),
+ write_mask: wgpu::ColorWrites::ALL,
+ })]
+}
+
#[repr(C)]
-#[derive(Clone, Copy, Zeroable, Pod)]
+#[derive(Clone, Copy, bytemuck::Zeroable, bytemuck::Pod)]
pub struct Vertex {
_position: [f32; 2],
}
-const QUAD_INDICES: [u16; 6] = [0, 1, 2, 0, 2, 3];
+impl Vertex {
+ fn buffer_layout<'a>() -> wgpu::VertexBufferLayout<'a> {
+ wgpu::VertexBufferLayout {
+ array_stride: mem::size_of::<Self>() as u64,
+ step_mode: wgpu::VertexStepMode::Vertex,
+ attributes: &[wgpu::VertexAttribute {
+ shader_location: 0,
+ format: wgpu::VertexFormat::Float32x2,
+ offset: 0,
+ }],
+ }
+ }
+}
-const QUAD_VERTS: [Vertex; 4] = [
+const INDICES: [u16; 6] = [0, 1, 2, 0, 2, 3];
+
+const VERTICES: [Vertex; 4] = [
Vertex {
_position: [0.0, 0.0],
},
@@ -298,10 +348,8 @@ const QUAD_VERTS: [Vertex; 4] = [
},
];
-const INITIAL_INSTANCES: usize = 10_000;
-
#[repr(C)]
-#[derive(Debug, Clone, Copy, Zeroable, Pod)]
+#[derive(Debug, Clone, Copy, bytemuck::Zeroable, bytemuck::Pod)]
struct Uniforms {
transform: [f32; 16],
scale: f32,
diff --git a/wgpu/src/quad/gradient.rs b/wgpu/src/quad/gradient.rs
new file mode 100644
index 00000000..6db37252
--- /dev/null
+++ b/wgpu/src/quad/gradient.rs
@@ -0,0 +1,165 @@
+use crate::graphics::gradient;
+use crate::quad::{self, Quad};
+use crate::Buffer;
+
+use bytemuck::{Pod, Zeroable};
+use std::ops::Range;
+
+/// A quad filled with interpolated colors.
+#[derive(Clone, Copy, Debug)]
+#[repr(C)]
+pub struct Gradient {
+ /// The background gradient data of the quad.
+ pub gradient: gradient::Packed,
+
+ /// The [`Quad`] data of the [`Gradient`].
+ pub quad: Quad,
+}
+
+#[allow(unsafe_code)]
+unsafe impl Pod for Gradient {}
+
+#[allow(unsafe_code)]
+unsafe impl Zeroable for Gradient {}
+
+#[derive(Debug)]
+pub struct Layer {
+ instances: Buffer<Gradient>,
+ instance_count: usize,
+}
+
+impl Layer {
+ pub fn new(device: &wgpu::Device) -> Self {
+ let instances = Buffer::new(
+ device,
+ "iced_wgpu.quad.gradient.buffer",
+ quad::INITIAL_INSTANCES,
+ wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
+ );
+
+ Self {
+ instances,
+ instance_count: 0,
+ }
+ }
+
+ pub fn prepare(
+ &mut self,
+ device: &wgpu::Device,
+ queue: &wgpu::Queue,
+ instances: &[Gradient],
+ ) {
+ let _ = self.instances.resize(device, instances.len());
+ let _ = self.instances.write(queue, 0, instances);
+
+ self.instance_count = instances.len();
+ }
+}
+
+#[derive(Debug)]
+pub struct Pipeline {
+ pipeline: wgpu::RenderPipeline,
+}
+
+impl Pipeline {
+ pub fn new(
+ device: &wgpu::Device,
+ format: wgpu::TextureFormat,
+ constants_layout: &wgpu::BindGroupLayout,
+ ) -> Self {
+ let layout =
+ device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
+ label: Some("iced_wgpu.quad.gradient.pipeline"),
+ push_constant_ranges: &[],
+ bind_group_layouts: &[constants_layout],
+ });
+
+ let shader =
+ device.create_shader_module(wgpu::ShaderModuleDescriptor {
+ label: Some("iced_wgpu.quad.gradient.shader"),
+ source: wgpu::ShaderSource::Wgsl(std::borrow::Cow::Borrowed(
+ include_str!("../shader/quad.wgsl"),
+ )),
+ });
+
+ let pipeline =
+ device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
+ label: Some("iced_wgpu.quad.gradient.pipeline"),
+ layout: Some(&layout),
+ vertex: wgpu::VertexState {
+ module: &shader,
+ entry_point: "gradient_vs_main",
+ buffers: &[
+ quad::Vertex::buffer_layout(),
+ wgpu::VertexBufferLayout {
+ array_stride: std::mem::size_of::<Gradient>()
+ as u64,
+ step_mode: wgpu::VertexStepMode::Instance,
+ attributes: &wgpu::vertex_attr_array!(
+ // Colors 1-2
+ 1 => Uint32x4,
+ // Colors 3-4
+ 2 => Uint32x4,
+ // Colors 5-6
+ 3 => Uint32x4,
+ // Colors 7-8
+ 4 => Uint32x4,
+ // Offsets 1-8
+ 5 => Uint32x4,
+ // Direction
+ 6 => Float32x4,
+ // Position & Scale
+ 7 => Float32x4,
+ // Border color
+ 8 => Float32x4,
+ // Border radius
+ 9 => Float32x4,
+ // Border width
+ 10 => Float32
+ ),
+ },
+ ],
+ },
+ fragment: Some(wgpu::FragmentState {
+ module: &shader,
+ entry_point: "gradient_fs_main",
+ targets: &quad::color_target_state(format),
+ }),
+ primitive: wgpu::PrimitiveState {
+ topology: wgpu::PrimitiveTopology::TriangleList,
+ front_face: wgpu::FrontFace::Cw,
+ ..Default::default()
+ },
+ depth_stencil: None,
+ multisample: wgpu::MultisampleState {
+ count: 1,
+ mask: !0,
+ alpha_to_coverage_enabled: false,
+ },
+ multiview: None,
+ });
+
+ Self { pipeline }
+ }
+
+ pub fn render<'a>(
+ &'a self,
+ render_pass: &mut wgpu::RenderPass<'a>,
+ constants: &'a wgpu::BindGroup,
+ layer: &'a Layer,
+ range: Range<usize>,
+ ) {
+ #[cfg(feature = "tracing")]
+ let _ = tracing::info_span!("Wgpu::Quad::Gradient", "DRAW").entered();
+
+ render_pass.set_pipeline(&self.pipeline);
+ render_pass.set_bind_group(0, constants, &[]);
+ render_pass.set_vertex_buffer(1, layer.instances.slice(..));
+
+ render_pass.draw_indexed(
+ 0..quad::INDICES.len() as u32,
+ 0,
+ range.start as u32..range.end as u32,
+ );
+ }
+}
diff --git a/wgpu/src/quad/solid.rs b/wgpu/src/quad/solid.rs
new file mode 100644
index 00000000..f8f1e3a5
--- /dev/null
+++ b/wgpu/src/quad/solid.rs
@@ -0,0 +1,150 @@
+use crate::graphics::color;
+use crate::quad::{self, Quad};
+use crate::Buffer;
+
+use bytemuck::{Pod, Zeroable};
+use std::ops::Range;
+
+/// A quad filled with a solid color.
+#[derive(Clone, Copy, Debug, Pod, Zeroable)]
+#[repr(C)]
+pub struct Solid {
+ /// The background color data of the quad.
+ pub color: color::Packed,
+
+ /// The [`Quad`] data of the [`Solid`].
+ pub quad: Quad,
+}
+
+#[derive(Debug)]
+pub struct Layer {
+ instances: Buffer<Solid>,
+ instance_count: usize,
+}
+
+impl Layer {
+ pub fn new(device: &wgpu::Device) -> Self {
+ let instances = Buffer::new(
+ device,
+ "iced_wgpu.quad.solid.buffer",
+ quad::INITIAL_INSTANCES,
+ wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
+ );
+
+ Self {
+ instances,
+ instance_count: 0,
+ }
+ }
+
+ pub fn prepare(
+ &mut self,
+ device: &wgpu::Device,
+ queue: &wgpu::Queue,
+ instances: &[Solid],
+ ) {
+ let _ = self.instances.resize(device, instances.len());
+ let _ = self.instances.write(queue, 0, instances);
+
+ self.instance_count = instances.len();
+ }
+}
+
+#[derive(Debug)]
+pub struct Pipeline {
+ pipeline: wgpu::RenderPipeline,
+}
+
+impl Pipeline {
+ pub fn new(
+ device: &wgpu::Device,
+ format: wgpu::TextureFormat,
+ constants_layout: &wgpu::BindGroupLayout,
+ ) -> Self {
+ let layout =
+ device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
+ label: Some("iced_wgpu.quad.solid.pipeline"),
+ push_constant_ranges: &[],
+ bind_group_layouts: &[constants_layout],
+ });
+
+ let shader =
+ device.create_shader_module(wgpu::ShaderModuleDescriptor {
+ label: Some("iced_wgpu.quad.solid.shader"),
+ source: wgpu::ShaderSource::Wgsl(std::borrow::Cow::Borrowed(
+ include_str!("../shader/quad.wgsl"),
+ )),
+ });
+
+ let pipeline =
+ device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
+ label: Some("iced_wgpu.quad.solid.pipeline"),
+ layout: Some(&layout),
+ vertex: wgpu::VertexState {
+ module: &shader,
+ entry_point: "solid_vs_main",
+ buffers: &[
+ quad::Vertex::buffer_layout(),
+ wgpu::VertexBufferLayout {
+ array_stride: std::mem::size_of::<Solid>() as u64,
+ step_mode: wgpu::VertexStepMode::Instance,
+ attributes: &wgpu::vertex_attr_array!(
+ // Color
+ 1 => Float32x4,
+ // Position
+ 2 => Float32x2,
+ // Size
+ 3 => Float32x2,
+ // Border color
+ 4 => Float32x4,
+ // Border radius
+ 5 => Float32x4,
+ // Border width
+ 6 => Float32,
+ ),
+ },
+ ],
+ },
+ fragment: Some(wgpu::FragmentState {
+ module: &shader,
+ entry_point: "solid_fs_main",
+ targets: &quad::color_target_state(format),
+ }),
+ primitive: wgpu::PrimitiveState {
+ topology: wgpu::PrimitiveTopology::TriangleList,
+ front_face: wgpu::FrontFace::Cw,
+ ..Default::default()
+ },
+ depth_stencil: None,
+ multisample: wgpu::MultisampleState {
+ count: 1,
+ mask: !0,
+ alpha_to_coverage_enabled: false,
+ },
+ multiview: None,
+ });
+
+ Self { pipeline }
+ }
+
+ pub fn render<'a>(
+ &'a self,
+ render_pass: &mut wgpu::RenderPass<'a>,
+ constants: &'a wgpu::BindGroup,
+ layer: &'a Layer,
+ range: Range<usize>,
+ ) {
+ #[cfg(feature = "tracing")]
+ let _ = tracing::info_span!("Wgpu::Quad::Solid", "DRAW").entered();
+
+ render_pass.set_pipeline(&self.pipeline);
+ render_pass.set_bind_group(0, constants, &[]);
+ render_pass.set_vertex_buffer(1, layer.instances.slice(..));
+
+ render_pass.draw_indexed(
+ 0..quad::INDICES.len() as u32,
+ 0,
+ range.start as u32..range.end as u32,
+ );
+ }
+}
diff --git a/wgpu/src/shader/gradient.wgsl b/wgpu/src/shader/gradient.wgsl
deleted file mode 100644
index 63825aec..00000000
--- a/wgpu/src/shader/gradient.wgsl
+++ /dev/null
@@ -1,88 +0,0 @@
-struct Uniforms {
- transform: mat4x4<f32>,
- //xy = start, wz = end
- position: vec4<f32>,
- //x = start stop, y = end stop, zw = padding
- stop_range: vec4<i32>,
-}
-
-struct Stop {
- color: vec4<f32>,
- offset: f32,
-};
-
-@group(0) @binding(0)
-var<uniform> uniforms: Uniforms;
-
-@group(0) @binding(1)
-var<storage, read> color_stops: array<Stop>;
-
-struct VertexOutput {
- @builtin(position) position: vec4<f32>,
- @location(0) raw_position: vec2<f32>
-}
-
-@vertex
-fn vs_main(@location(0) input: vec2<f32>) -> VertexOutput {
- var output: VertexOutput;
- output.position = uniforms.transform * vec4<f32>(input.xy, 0.0, 1.0);
- output.raw_position = input;
-
- return output;
-}
-
-//TODO: rewrite without branching
-@fragment
-fn fs_main(input: VertexOutput) -> @location(0) vec4<f32> {
- let start = uniforms.position.xy;
- let end = uniforms.position.zw;
- let start_stop = uniforms.stop_range.x;
- let end_stop = uniforms.stop_range.y;
-
- let v1 = end - start;
- let v2 = input.raw_position.xy - start;
- let unit = normalize(v1);
- let offset = dot(unit, v2) / length(v1);
-
- let min_stop = color_stops[start_stop];
- let max_stop = color_stops[end_stop];
-
- var color: vec4<f32>;
-
- if (offset <= min_stop.offset) {
- color = min_stop.color;
- } else if (offset >= max_stop.offset) {
- color = max_stop.color;
- } else {
- var min = min_stop;
- var max = max_stop;
- var min_index = start_stop;
- var max_index = end_stop;
-
- loop {
- if (min_index >= max_index - 1) {
- break;
- }
-
- let index = min_index + (max_index - min_index) / 2;
-
- let stop = color_stops[index];
-
- if (offset <= stop.offset) {
- max = stop;
- max_index = index;
- } else {
- min = stop;
- min_index = index;
- }
- }
-
- color = mix(min.color, max.color, smoothstep(
- min.offset,
- max.offset,
- offset
- ));
- }
-
- return color;
-}
diff --git a/wgpu/src/shader/quad.wgsl b/wgpu/src/shader/quad.wgsl
index cf4f7e4d..fb402158 100644
--- a/wgpu/src/shader/quad.wgsl
+++ b/wgpu/src/shader/quad.wgsl
@@ -5,17 +5,57 @@ struct Globals {
@group(0) @binding(0) var<uniform> globals: Globals;
-struct VertexInput {
+fn distance_alg(
+ frag_coord: vec2<f32>,
+ position: vec2<f32>,
+ size: vec2<f32>,
+ radius: f32
+) -> f32 {
+ var inner_size: vec2<f32> = size - vec2<f32>(radius, radius) * 2.0;
+ var top_left: vec2<f32> = position + vec2<f32>(radius, radius);
+ var bottom_right: vec2<f32> = top_left + inner_size;
+
+ var top_left_distance: vec2<f32> = top_left - frag_coord;
+ var bottom_right_distance: vec2<f32> = frag_coord - bottom_right;
+
+ var dist: vec2<f32> = vec2<f32>(
+ max(max(top_left_distance.x, bottom_right_distance.x), 0.0),
+ max(max(top_left_distance.y, bottom_right_distance.y), 0.0)
+ );
+
+ return sqrt(dist.x * dist.x + dist.y * dist.y);
+}
+
+// Based on the fragement position and the center of the quad, select one of the 4 radi.
+// Order matches CSS border radius attribute:
+// radi.x = top-left, radi.y = top-right, radi.z = bottom-right, radi.w = bottom-left
+fn select_border_radius(radi: vec4<f32>, position: vec2<f32>, center: vec2<f32>) -> f32 {
+ var rx = radi.x;
+ var ry = radi.y;
+ rx = select(radi.x, radi.y, position.x > center.x);
+ ry = select(radi.w, radi.z, position.x > center.x);
+ rx = select(rx, ry, position.y > center.y);
+ return rx;
+}
+
+fn unpack_u32(color: vec2<u32>) -> vec4<f32> {
+ let rg: vec2<f32> = unpack2x16float(color.x);
+ let ba: vec2<f32> = unpack2x16float(color.y);
+
+ return vec4<f32>(rg.y, rg.x, ba.y, ba.x);
+}
+
+struct SolidVertexInput {
@location(0) v_pos: vec2<f32>,
- @location(1) pos: vec2<f32>,
- @location(2) scale: vec2<f32>,
- @location(3) color: vec4<f32>,
+ @location(1) color: vec4<f32>,
+ @location(2) pos: vec2<f32>,
+ @location(3) scale: vec2<f32>,
@location(4) border_color: vec4<f32>,
@location(5) border_radius: vec4<f32>,
@location(6) border_width: f32,
}
-struct VertexOutput {
+struct SolidVertexOutput {
@builtin(position) position: vec4<f32>,
@location(0) color: vec4<f32>,
@location(1) border_color: vec4<f32>,
@@ -26,8 +66,8 @@ struct VertexOutput {
}
@vertex
-fn vs_main(input: VertexInput) -> VertexOutput {
- var out: VertexOutput;
+fn solid_vs_main(input: SolidVertexInput) -> SolidVertexOutput {
+ var out: SolidVertexOutput;
var pos: vec2<f32> = input.pos * globals.scale;
var scale: vec2<f32> = input.scale * globals.scale;
@@ -47,54 +87,20 @@ fn vs_main(input: VertexInput) -> VertexOutput {
vec4<f32>(pos - vec2<f32>(0.5, 0.5), 0.0, 1.0)
);
+ out.position = globals.transform * transform * vec4<f32>(input.v_pos, 0.0, 1.0);
out.color = input.color;
out.border_color = input.border_color;
out.pos = pos;
out.scale = scale;
out.border_radius = border_radius * globals.scale;
out.border_width = input.border_width * globals.scale;
- out.position = globals.transform * transform * vec4<f32>(input.v_pos, 0.0, 1.0);
return out;
}
-fn distance_alg(
- frag_coord: vec2<f32>,
- position: vec2<f32>,
- size: vec2<f32>,
- radius: f32
-) -> f32 {
- var inner_size: vec2<f32> = size - vec2<f32>(radius, radius) * 2.0;
- var top_left: vec2<f32> = position + vec2<f32>(radius, radius);
- var bottom_right: vec2<f32> = top_left + inner_size;
-
- var top_left_distance: vec2<f32> = top_left - frag_coord;
- var bottom_right_distance: vec2<f32> = frag_coord - bottom_right;
-
- var dist: vec2<f32> = vec2<f32>(
- max(max(top_left_distance.x, bottom_right_distance.x), 0.0),
- max(max(top_left_distance.y, bottom_right_distance.y), 0.0)
- );
-
- return sqrt(dist.x * dist.x + dist.y * dist.y);
-}
-
-// Based on the fragement position and the center of the quad, select one of the 4 radi.
-// Order matches CSS border radius attribute:
-// radi.x = top-left, radi.y = top-right, radi.z = bottom-right, radi.w = bottom-left
-fn select_border_radius(radi: vec4<f32>, position: vec2<f32>, center: vec2<f32>) -> f32 {
- var rx = radi.x;
- var ry = radi.y;
- rx = select(radi.x, radi.y, position.x > center.x);
- ry = select(radi.w, radi.z, position.x > center.x);
- rx = select(rx, ry, position.y > center.y);
- return rx;
-}
-
-
@fragment
-fn fs_main(
- input: VertexOutput
+fn solid_fs_main(
+ input: SolidVertexOutput
) -> @location(0) vec4<f32> {
var mixed_color: vec4<f32> = input.color;
@@ -138,3 +144,202 @@ fn fs_main(
return vec4<f32>(mixed_color.x, mixed_color.y, mixed_color.z, mixed_color.w * radius_alpha);
}
+
+struct GradientVertexInput {
+ @location(0) v_pos: vec2<f32>,
+ @location(1) colors_1: vec4<u32>,
+ @location(2) colors_2: vec4<u32>,
+ @location(3) colors_3: vec4<u32>,
+ @location(4) colors_4: vec4<u32>,
+ @location(5) offsets: vec4<u32>,
+ @location(6) direction: vec4<f32>,
+ @location(7) position_and_scale: vec4<f32>,
+ @location(8) border_color: vec4<f32>,
+ @location(9) border_radius: vec4<f32>,
+ @location(10) border_width: f32,
+}
+
+struct GradientVertexOutput {
+ @builtin(position) position: vec4<f32>,
+ @location(1) colors_1: vec4<u32>,
+ @location(2) colors_2: vec4<u32>,
+ @location(3) colors_3: vec4<u32>,
+ @location(4) colors_4: vec4<u32>,
+ @location(5) offsets: vec4<u32>,
+ @location(6) direction: vec4<f32>,
+ @location(7) position_and_scale: vec4<f32>,
+ @location(8) border_color: vec4<f32>,
+ @location(9) border_radius: vec4<f32>,
+ @location(10) border_width: f32,
+}
+
+@vertex
+fn gradient_vs_main(input: GradientVertexInput) -> GradientVertexOutput {
+ var out: GradientVertexOutput;
+
+ var pos: vec2<f32> = input.position_and_scale.xy * globals.scale;
+ var scale: vec2<f32> = input.position_and_scale.zw * globals.scale;
+
+ var min_border_radius = min(input.position_and_scale.z, input.position_and_scale.w) * 0.5;
+ var border_radius: vec4<f32> = vec4<f32>(
+ min(input.border_radius.x, min_border_radius),
+ min(input.border_radius.y, min_border_radius),
+ min(input.border_radius.z, min_border_radius),
+ min(input.border_radius.w, min_border_radius)
+ );
+
+ var transform: mat4x4<f32> = mat4x4<f32>(
+ vec4<f32>(scale.x + 1.0, 0.0, 0.0, 0.0),
+ vec4<f32>(0.0, scale.y + 1.0, 0.0, 0.0),
+ vec4<f32>(0.0, 0.0, 1.0, 0.0),
+ vec4<f32>(pos - vec2<f32>(0.5, 0.5), 0.0, 1.0)
+ );
+
+ out.position = globals.transform * transform * vec4<f32>(input.v_pos, 0.0, 1.0);
+ out.colors_1 = input.colors_1;
+ out.colors_2 = input.colors_2;
+ out.colors_3 = input.colors_3;
+ out.colors_4 = input.colors_4;
+ out.offsets = input.offsets;
+ out.direction = input.direction * globals.scale;
+ out.position_and_scale = vec4<f32>(pos, scale);
+ out.border_color = input.border_color;
+ out.border_radius = border_radius * globals.scale;
+ out.border_width = input.border_width * globals.scale;
+
+ return out;
+}
+
+fn random(coords: vec2<f32>) -> f32 {
+ return fract(sin(dot(coords, vec2(12.9898,78.233))) * 43758.5453);
+}
+
+/// Returns the current interpolated color with a max 8-stop gradient
+fn gradient(
+ raw_position: vec2<f32>,
+ direction: vec4<f32>,
+ colors: array<vec4<f32>, 8>,
+ offsets: array<f32, 8>,
+ last_index: i32
+) -> vec4<f32> {
+ let start = direction.xy;
+ let end = direction.zw;
+
+ let v1 = end - start;
+ let v2 = raw_position - start;
+ let unit = normalize(v1);
+ let coord_offset = dot(unit, v2) / length(v1);
+
+ //need to store these as a var to use dynamic indexing in a loop
+ //this is already added to wgsl spec but not in wgpu yet
+ var colors_arr = colors;
+ var offsets_arr = offsets;
+
+ var color: vec4<f32>;
+
+ let noise_granularity: f32 = 0.3/255.0;
+
+ for (var i: i32 = 0; i < last_index; i++) {
+ let curr_offset = offsets_arr[i];
+ let next_offset = offsets_arr[i+1];
+
+ if (coord_offset <= offsets_arr[0]) {
+ color = colors_arr[0];
+ }
+
+ if (curr_offset <= coord_offset && coord_offset <= next_offset) {
+ color = mix(colors_arr[i], colors_arr[i+1], smoothstep(
+ curr_offset,
+ next_offset,
+ coord_offset,
+ ));
+ }
+
+ if (coord_offset >= offsets_arr[last_index]) {
+ color = colors_arr[last_index];
+ }
+ }
+
+ return color + mix(-noise_granularity, noise_granularity, random(raw_position));
+}
+
+@fragment
+fn gradient_fs_main(input: GradientVertexOutput) -> @location(0) vec4<f32> {
+ let colors = array<vec4<f32>, 8>(
+ unpack_u32(input.colors_1.xy),
+ unpack_u32(input.colors_1.zw),
+ unpack_u32(input.colors_2.xy),
+ unpack_u32(input.colors_2.zw),
+ unpack_u32(input.colors_3.xy),
+ unpack_u32(input.colors_3.zw),
+ unpack_u32(input.colors_4.xy),
+ unpack_u32(input.colors_4.zw),
+ );
+
+ let offsets_1: vec4<f32> = unpack_u32(input.offsets.xy);
+ let offsets_2: vec4<f32> = unpack_u32(input.offsets.zw);
+
+ var offsets = array<f32, 8>(
+ offsets_1.x,
+ offsets_1.y,
+ offsets_1.z,
+ offsets_1.w,
+ offsets_2.x,
+ offsets_2.y,
+ offsets_2.z,
+ offsets_2.w,
+ );
+
+ //TODO could just pass this in to the shader but is probably more performant to just check it here
+ var last_index = 7;
+ for (var i: i32 = 0; i <= 7; i++) {
+ if (offsets[i] > 1.0) {
+ last_index = i - 1;
+ break;
+ }
+ }
+
+ var mixed_color: vec4<f32> = gradient(input.position.xy, input.direction, colors, offsets, last_index);
+
+ let pos = input.position_and_scale.xy;
+ let scale = input.position_and_scale.zw;
+
+ var border_radius = select_border_radius(
+ input.border_radius,
+ input.position.xy,
+ (pos + scale * 0.5).xy
+ );
+
+ if (input.border_width > 0.0) {
+ var internal_border: f32 = max(border_radius - input.border_width, 0.0);
+
+ var internal_distance: f32 = distance_alg(
+ input.position.xy,
+ pos + vec2<f32>(input.border_width, input.border_width),
+ scale - vec2<f32>(input.border_width * 2.0, input.border_width * 2.0),
+ internal_border
+ );
+
+ var border_mix: f32 = smoothstep(
+ max(internal_border - 0.5, 0.0),
+ internal_border + 0.5,
+ internal_distance
+ );
+
+ mixed_color = mix(mixed_color, input.border_color, vec4<f32>(border_mix, border_mix, border_mix, border_mix));
+ }
+
+ var dist: f32 = distance_alg(
+ input.position.xy,
+ pos,
+ scale,
+ border_radius
+ );
+
+ var radius_alpha: f32 = 1.0 - smoothstep(
+ max(border_radius - 0.5, 0.0),
+ border_radius + 0.5,
+ dist);
+
+ return vec4<f32>(mixed_color.x, mixed_color.y, mixed_color.z, mixed_color.w * radius_alpha);
+}
diff --git a/wgpu/src/shader/solid.wgsl b/wgpu/src/shader/solid.wgsl
deleted file mode 100644
index b24402f8..00000000
--- a/wgpu/src/shader/solid.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/shader/triangle.wgsl b/wgpu/src/shader/triangle.wgsl
new file mode 100644
index 00000000..9f512d14
--- /dev/null
+++ b/wgpu/src/shader/triangle.wgsl
@@ -0,0 +1,165 @@
+struct Globals {
+ transform: mat4x4<f32>,
+}
+
+@group(0) @binding(0) var<uniform> globals: Globals;
+
+fn unpack_u32(color: vec2<u32>) -> vec4<f32> {
+ let rg: vec2<f32> = unpack2x16float(color.x);
+ let ba: vec2<f32> = unpack2x16float(color.y);
+
+ return vec4<f32>(rg.y, rg.x, ba.y, ba.x);
+}
+
+struct SolidVertexInput {
+ @location(0) position: vec2<f32>,
+ @location(1) color: vec4<f32>,
+}
+
+struct SolidVertexOutput {
+ @builtin(position) position: vec4<f32>,
+ @location(0) color: vec4<f32>,
+}
+
+@vertex
+fn solid_vs_main(input: SolidVertexInput) -> SolidVertexOutput {
+ var out: SolidVertexOutput;
+
+ out.color = input.color;
+ out.position = globals.transform * vec4<f32>(input.position, 0.0, 1.0);
+
+ return out;
+}
+
+@fragment
+fn solid_fs_main(input: SolidVertexOutput) -> @location(0) vec4<f32> {
+ return input.color;
+}
+
+struct GradientVertexInput {
+ @location(0) v_pos: vec2<f32>,
+ @location(1) colors_1: vec4<u32>,
+ @location(2) colors_2: vec4<u32>,
+ @location(3) colors_3: vec4<u32>,
+ @location(4) colors_4: vec4<u32>,
+ @location(5) offsets: vec4<u32>,
+ @location(6) direction: vec4<f32>,
+}
+
+struct GradientVertexOutput {
+ @builtin(position) position: vec4<f32>,
+ @location(0) raw_position: vec2<f32>,
+ @location(1) colors_1: vec4<u32>,
+ @location(2) colors_2: vec4<u32>,
+ @location(3) colors_3: vec4<u32>,
+ @location(4) colors_4: vec4<u32>,
+ @location(5) offsets: vec4<u32>,
+ @location(6) direction: vec4<f32>,
+}
+
+@vertex
+fn gradient_vs_main(input: GradientVertexInput) -> GradientVertexOutput {
+ var output: GradientVertexOutput;
+
+ output.position = globals.transform * vec4<f32>(input.v_pos, 0.0, 1.0);
+ output.raw_position = input.v_pos;
+ output.colors_1 = input.colors_1;
+ output.colors_2 = input.colors_2;
+ output.colors_3 = input.colors_3;
+ output.colors_4 = input.colors_4;
+ output.offsets = input.offsets;
+ output.direction = input.direction;
+
+ return output;
+}
+
+fn random(coords: vec2<f32>) -> f32 {
+ return fract(sin(dot(coords, vec2(12.9898,78.233))) * 43758.5453);
+}
+
+/// Returns the current interpolated color with a max 8-stop gradient
+fn gradient(
+ raw_position: vec2<f32>,
+ direction: vec4<f32>,
+ colors: array<vec4<f32>, 8>,
+ offsets: array<f32, 8>,
+ last_index: i32
+) -> vec4<f32> {
+ let start = direction.xy;
+ let end = direction.zw;
+
+ let v1 = end - start;
+ let v2 = raw_position - start;
+ let unit = normalize(v1);
+ let coord_offset = dot(unit, v2) / length(v1);
+
+ //need to store these as a var to use dynamic indexing in a loop
+ //this is already added to wgsl spec but not in wgpu yet
+ var colors_arr = colors;
+ var offsets_arr = offsets;
+
+ var color: vec4<f32>;
+
+ let noise_granularity: f32 = 0.3/255.0;
+
+ for (var i: i32 = 0; i < last_index; i++) {
+ let curr_offset = offsets_arr[i];
+ let next_offset = offsets_arr[i+1];
+
+ if (coord_offset <= offsets_arr[0]) {
+ color = colors_arr[0];
+ }
+
+ if (curr_offset <= coord_offset && coord_offset <= next_offset) {
+ color = mix(colors_arr[i], colors_arr[i+1], smoothstep(
+ curr_offset,
+ next_offset,
+ coord_offset,
+ ));
+ }
+
+ if (coord_offset >= offsets_arr[last_index]) {
+ color = colors_arr[last_index];
+ }
+ }
+
+ return color + mix(-noise_granularity, noise_granularity, random(raw_position));
+}
+
+@fragment
+fn gradient_fs_main(input: GradientVertexOutput) -> @location(0) vec4<f32> {
+ let colors = array<vec4<f32>, 8>(
+ unpack_u32(input.colors_1.xy),
+ unpack_u32(input.colors_1.zw),
+ unpack_u32(input.colors_2.xy),
+ unpack_u32(input.colors_2.zw),
+ unpack_u32(input.colors_3.xy),
+ unpack_u32(input.colors_3.zw),
+ unpack_u32(input.colors_4.xy),
+ unpack_u32(input.colors_4.zw),
+ );
+
+ let offsets_1: vec4<f32> = unpack_u32(input.offsets.xy);
+ let offsets_2: vec4<f32> = unpack_u32(input.offsets.zw);
+
+ var offsets = array<f32, 8>(
+ offsets_1.x,
+ offsets_1.y,
+ offsets_1.z,
+ offsets_1.w,
+ offsets_2.x,
+ offsets_2.y,
+ offsets_2.z,
+ offsets_2.w,
+ );
+
+ var last_index = 7;
+ for (var i: i32 = 0; i <= 7; i++) {
+ if (offsets[i] >= 1.0) {
+ last_index = i;
+ break;
+ }
+ }
+
+ return gradient(input.raw_position, input.direction, colors, offsets, last_index);
+}
diff --git a/wgpu/src/text.rs b/wgpu/src/text.rs
index 714e0400..c9188bd1 100644
--- a/wgpu/src/text.rs
+++ b/wgpu/src/text.rs
@@ -2,6 +2,7 @@ use crate::core::alignment;
use crate::core::font::{self, Font};
use crate::core::text::{Hit, LineHeight, Shaping};
use crate::core::{Pixels, Point, Rectangle, Size};
+use crate::graphics::color;
use crate::layer::Text;
use rustc_hash::{FxHashMap, FxHashSet};
@@ -17,8 +18,7 @@ pub struct Pipeline {
renderers: Vec<glyphon::TextRenderer>,
atlas: glyphon::TextAtlas,
prepare_layer: usize,
- measurement_cache: RefCell<Cache>,
- render_cache: Cache,
+ cache: RefCell<Cache>,
}
impl Pipeline {
@@ -35,17 +35,27 @@ impl Pipeline {
.into_iter(),
)),
renderers: Vec::new(),
- atlas: glyphon::TextAtlas::new(device, queue, format),
+ atlas: glyphon::TextAtlas::new(
+ device,
+ queue,
+ format,
+ if color::GAMMA_CORRECTION {
+ glyphon::ColorMode::Accurate
+ } else {
+ glyphon::ColorMode::Web
+ },
+ ),
prepare_layer: 0,
- measurement_cache: RefCell::new(Cache::new()),
- render_cache: Cache::new(),
+ cache: RefCell::new(Cache::new()),
}
}
pub fn load_font(&mut self, bytes: Cow<'static, [u8]>) {
- self.font_system.get_mut().db_mut().load_font_source(
+ let _ = self.font_system.get_mut().db_mut().load_font_source(
glyphon::fontdb::Source::Binary(Arc::new(bytes.into_owned())),
);
+
+ self.cache = RefCell::new(Cache::new());
}
pub fn prepare(
@@ -68,25 +78,25 @@ impl Pipeline {
let font_system = self.font_system.get_mut();
let renderer = &mut self.renderers[self.prepare_layer];
+ let cache = self.cache.get_mut();
let keys: Vec<_> = sections
.iter()
.map(|section| {
- let (key, _) = self.render_cache.allocate(
+ let (key, _) = cache.allocate(
font_system,
Key {
content: section.content,
- size: section.size * scale_factor,
+ size: section.size,
line_height: f32::from(
section
.line_height
.to_absolute(Pixels(section.size)),
- ) * scale_factor,
+ ),
font: section.font,
bounds: Size {
- width: (section.bounds.width * scale_factor).ceil(),
- height: (section.bounds.height * scale_factor)
- .ceil(),
+ width: section.bounds.width,
+ height: section.bounds.height,
},
shaping: section.shaping,
},
@@ -103,22 +113,16 @@ impl Pipeline {
.iter()
.zip(keys.iter())
.filter_map(|(section, key)| {
- let buffer =
- self.render_cache.get(key).expect("Get cached buffer");
-
- let (total_lines, max_width) = buffer
- .layout_runs()
- .enumerate()
- .fold((0, 0.0), |(_, max), (i, buffer)| {
- (i + 1, buffer.line_w.max(max))
- });
-
- let total_height =
- total_lines as f32 * buffer.metrics().line_height;
+ let buffer = cache.get(key).expect("Get cached buffer");
let x = section.bounds.x * scale_factor;
let y = section.bounds.y * scale_factor;
+ let (max_width, total_height) = measure(buffer);
+
+ let max_width = max_width * scale_factor;
+ let total_height = total_height * scale_factor;
+
let left = match section.horizontal_alignment {
alignment::Horizontal::Left => x,
alignment::Horizontal::Center => x - max_width / 2.0,
@@ -140,14 +144,11 @@ impl Pipeline {
let clip_bounds = bounds.intersection(&section_bounds)?;
- // TODO: Subpixel glyph positioning
- let left = left.round() as i32;
- let top = top.round() as i32;
-
Some(glyphon::TextArea {
buffer,
left,
top,
+ scale: scale_factor,
bounds: glyphon::TextBounds {
left: clip_bounds.x as i32,
top: clip_bounds.y as i32,
@@ -155,7 +156,8 @@ impl Pipeline {
bottom: (clip_bounds.y + clip_bounds.height) as i32,
},
default_color: {
- let [r, g, b, a] = section.color.into_linear();
+ let [r, g, b, a] =
+ color::pack(section.color).components();
glyphon::Color::rgba(
(r * 255.0) as u8,
@@ -224,7 +226,7 @@ impl Pipeline {
pub fn end_frame(&mut self) {
self.atlas.trim();
- self.render_cache.trim();
+ self.cache.get_mut().trim();
self.prepare_layer = 0;
}
@@ -238,7 +240,7 @@ impl Pipeline {
bounds: Size,
shaping: Shaping,
) -> (f32, f32) {
- let mut measurement_cache = self.measurement_cache.borrow_mut();
+ let mut measurement_cache = self.cache.borrow_mut();
let line_height = f32::from(line_height.to_absolute(Pixels(size)));
@@ -254,14 +256,7 @@ impl Pipeline {
},
);
- let (total_lines, max_width) = paragraph
- .layout_runs()
- .enumerate()
- .fold((0, 0.0), |(_, max), (i, buffer)| {
- (i + 1, buffer.line_w.max(max))
- });
-
- (max_width, line_height * total_lines as f32)
+ measure(paragraph)
}
pub fn hit_test(
@@ -275,7 +270,7 @@ impl Pipeline {
point: Point,
_nearest_only: bool,
) -> Option<Hit> {
- let mut measurement_cache = self.measurement_cache.borrow_mut();
+ let mut measurement_cache = self.cache.borrow_mut();
let line_height = f32::from(line_height.to_absolute(Pixels(size)));
@@ -295,10 +290,16 @@ impl Pipeline {
Some(Hit::CharOffset(cursor.index))
}
+}
- pub fn trim_measurement_cache(&mut self) {
- self.measurement_cache.borrow_mut().trim();
- }
+fn measure(buffer: &glyphon::Buffer) -> (f32, f32) {
+ let (width, total_lines) = buffer
+ .layout_runs()
+ .fold((0.0, 0usize), |(width, total_lines), run| {
+ (run.line_w.max(width), total_lines + 1)
+ });
+
+ (width, total_lines as f32 * buffer.metrics().line_height)
}
fn to_family(family: font::Family) -> glyphon::Family<'static> {
diff --git a/wgpu/src/triangle.rs b/wgpu/src/triangle.rs
index eb15a458..3f3635cf 100644
--- a/wgpu/src/triangle.rs
+++ b/wgpu/src/triangle.rs
@@ -1,26 +1,19 @@
//! Draw meshes of triangles.
mod msaa;
-use crate::buffer::r#static::Buffer;
use crate::core::Size;
use crate::graphics::{Antialiasing, Transformation};
use crate::layer::mesh::{self, Mesh};
+use crate::Buffer;
-#[cfg(not(target_arch = "wasm32"))]
-use crate::core::Gradient;
-
-#[cfg(feature = "tracing")]
-use tracing::info_span;
+const INITIAL_INDEX_COUNT: usize = 1_000;
+const INITIAL_VERTEX_COUNT: usize = 1_000;
#[derive(Debug)]
pub struct Pipeline {
blit: Option<msaa::Blit>,
solid: solid::Pipeline,
-
- /// Gradients are currently not supported on WASM targets due to their need of storage buffers.
- #[cfg(not(target_arch = "wasm32"))]
gradient: gradient::Pipeline,
-
layers: Vec<Layer>,
prepare_layer: usize,
}
@@ -30,8 +23,6 @@ struct Layer {
index_buffer: Buffer<u32>,
index_strides: Vec<u32>,
solid: solid::Layer,
-
- #[cfg(not(target_arch = "wasm32"))]
gradient: gradient::Layer,
}
@@ -39,18 +30,17 @@ impl Layer {
fn new(
device: &wgpu::Device,
solid: &solid::Pipeline,
- #[cfg(not(target_arch = "wasm32"))] gradient: &gradient::Pipeline,
+ gradient: &gradient::Pipeline,
) -> Self {
Self {
index_buffer: Buffer::new(
device,
- "iced_wgpu::triangle index buffer",
+ "iced_wgpu.triangle.index_buffer",
+ INITIAL_INDEX_COUNT,
wgpu::BufferUsages::INDEX | wgpu::BufferUsages::COPY_DST,
),
index_strides: Vec::new(),
solid: solid::Layer::new(device, &solid.constants_layout),
-
- #[cfg(not(target_arch = "wasm32"))]
gradient: gradient::Layer::new(device, &gradient.constants_layout),
}
}
@@ -60,7 +50,7 @@ impl Layer {
device: &wgpu::Device,
queue: &wgpu::Queue,
solid: &solid::Pipeline,
- #[cfg(not(target_arch = "wasm32"))] gradient: &gradient::Pipeline,
+ gradient: &gradient::Pipeline,
meshes: &[Mesh<'_>],
transformation: Transformation,
) {
@@ -73,183 +63,102 @@ impl Layer {
// 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_buffer.clear();
+ if self.solid.uniforms.resize(device, count.solids) {
+ self.solid.constants = solid::Layer::bind_group(
+ device,
+ &self.solid.uniforms.raw,
+ &solid.constants_layout,
+ );
+ }
+
+ if self.gradient.uniforms.resize(device, count.gradients) {
+ self.gradient.constants = gradient::Layer::bind_group(
+ device,
+ &self.gradient.uniforms.raw,
+ &gradient.constants_layout,
+ );
+ }
+
self.index_strides.clear();
+ self.index_buffer.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();
- }
+ self.gradient.vertices.clear();
+ self.gradient.uniforms.clear();
let mut solid_vertex_offset = 0;
- let mut index_offset = 0;
-
- #[cfg(not(target_arch = "wasm32"))]
+ let mut solid_uniform_offset = 0;
let mut gradient_vertex_offset = 0;
+ let mut gradient_uniform_offset = 0;
+ let mut index_offset = 0;
for mesh in meshes {
let origin = mesh.origin();
let indices = mesh.indices();
- let transform =
- transformation * Transformation::translate(origin.x, origin.y);
+ let uniforms = Uniforms::new(
+ transformation * Transformation::translate(origin.x, origin.y),
+ );
- let new_index_offset =
+ index_offset +=
self.index_buffer.write(queue, index_offset, indices);
-
- index_offset += new_index_offset;
self.index_strides.push(indices.len() as u32);
- //push uniform data to CPU buffers
match mesh {
Mesh::Solid { buffers, .. } => {
- self.solid.uniforms.push(&solid::Uniforms::new(transform));
-
- let written_bytes = self.solid.vertices.write(
+ solid_vertex_offset += self.solid.vertices.write(
queue,
solid_vertex_offset,
&buffers.vertices,
);
- solid_vertex_offset += written_bytes;
+ solid_uniform_offset += self.solid.uniforms.write(
+ queue,
+ solid_uniform_offset,
+ &[uniforms],
+ );
}
- #[cfg(not(target_arch = "wasm32"))]
- Mesh::Gradient {
- buffers, gradient, ..
- } => {
- let written_bytes = self.gradient.vertices.write(
+ Mesh::Gradient { buffers, .. } => {
+ gradient_vertex_offset += self.gradient.vertices.write(
queue,
gradient_vertex_offset,
&buffers.vertices,
);
- gradient_vertex_offset += written_bytes;
-
- match gradient {
- 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);
- }
- }
+ gradient_uniform_offset += self.gradient.uniforms.write(
+ queue,
+ gradient_uniform_offset,
+ &[uniforms],
+ );
}
- #[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.constants = solid::Layer::bind_group(
- device,
- self.solid.uniforms.raw(),
- &solid.constants_layout,
- )
}
-
- self.solid.uniforms.write(queue);
- }
-
- #[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.constants = gradient::Layer::bind_group(
- device,
- self.gradient.uniforms.raw(),
- self.gradient.storage.raw(),
- &gradient.constants_layout,
- );
- }
-
- // Write to GPU
- self.gradient.uniforms.write(queue);
- self.gradient.storage.write(queue);
-
- // Cleanup
- self.gradient.color_stop_offset = 0;
- self.gradient.color_stops_pending_write.color_stops.clear();
}
}
fn render<'a>(
&'a self,
solid: &'a solid::Pipeline,
- #[cfg(not(target_arch = "wasm32"))] gradient: &'a gradient::Pipeline,
+ gradient: &'a gradient::Pipeline,
meshes: &[Mesh<'_>],
scale_factor: f32,
render_pass: &mut wgpu::RenderPass<'a>,
) {
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();
+ if clip_bounds.width < 1 || clip_bounds.height < 1 {
+ continue;
+ }
+
render_pass.set_scissor_rect(
clip_bounds.x,
clip_bounds.y,
@@ -268,7 +177,8 @@ impl Layer {
render_pass.set_bind_group(
0,
&self.solid.constants,
- &[self.solid.uniforms.offset_at_index(num_solids)],
+ &[(num_solids * std::mem::size_of::<Uniforms>())
+ as u32],
);
render_pass.set_vertex_buffer(
@@ -278,7 +188,6 @@ impl Layer {
num_solids += 1;
}
- #[cfg(not(target_arch = "wasm32"))]
Mesh::Gradient { .. } => {
if last_is_solid.unwrap_or(true) {
render_pass.set_pipeline(&gradient.pipeline);
@@ -289,10 +198,8 @@ impl Layer {
render_pass.set_bind_group(
0,
&self.gradient.constants,
- &[self
- .gradient
- .uniforms
- .offset_at_index(num_gradients)],
+ &[(num_gradients * std::mem::size_of::<Uniforms>())
+ as u32],
);
render_pass.set_vertex_buffer(
@@ -302,8 +209,6 @@ impl Layer {
num_gradients += 1;
}
- #[cfg(target_arch = "wasm32")]
- Mesh::Gradient { .. } => {}
};
render_pass.set_index_buffer(
@@ -325,10 +230,7 @@ impl Pipeline {
Pipeline {
blit: antialiasing.map(|a| msaa::Blit::new(device, format, a)),
solid: solid::Pipeline::new(device, format, antialiasing),
-
- #[cfg(not(target_arch = "wasm32"))]
gradient: gradient::Pipeline::new(device, format, antialiasing),
-
layers: Vec::new(),
prepare_layer: 0,
}
@@ -342,15 +244,11 @@ impl Pipeline {
transformation: Transformation,
) {
#[cfg(feature = "tracing")]
- let _ = info_span!("Wgpu::Triangle", "PREPARE").entered();
+ let _ = tracing::info_span!("Wgpu::Triangle", "PREPARE").entered();
if self.layers.len() <= self.prepare_layer {
- self.layers.push(Layer::new(
- device,
- &self.solid,
- #[cfg(not(target_arch = "wasm32"))]
- &self.gradient,
- ));
+ self.layers
+ .push(Layer::new(device, &self.solid, &self.gradient));
}
let layer = &mut self.layers[self.prepare_layer];
@@ -358,7 +256,6 @@ impl Pipeline {
device,
queue,
&self.solid,
- #[cfg(not(target_arch = "wasm32"))]
&self.gradient,
meshes,
transformation,
@@ -378,9 +275,8 @@ impl Pipeline {
scale_factor: f32,
) {
#[cfg(feature = "tracing")]
- let _ = info_span!("Wgpu::Triangle", "DRAW").entered();
+ let _ = tracing::info_span!("Wgpu::Triangle", "DRAW").entered();
- // Configure render pass
{
let (attachment, resolve_target, load) = if let Some(blit) =
&mut self.blit
@@ -397,12 +293,9 @@ impl Pipeline {
(target, None, wgpu::LoadOp::Load)
};
- #[cfg(feature = "tracing")]
- let _ = info_span!("Wgpu::Triangle", "BEGIN_RENDER_PASS").enter();
-
let mut render_pass =
encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
- label: Some("iced_wgpu::triangle render pass"),
+ label: Some("iced_wgpu.triangle.render_pass"),
color_attachments: &[Some(
wgpu::RenderPassColorAttachment {
view: attachment,
@@ -417,7 +310,6 @@ impl Pipeline {
layer.render(
&self.solid,
- #[cfg(not(target_arch = "wasm32"))]
&self.gradient,
meshes,
scale_factor,
@@ -463,14 +355,48 @@ fn multisample_state(
}
}
+#[derive(Debug, Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)]
+#[repr(C)]
+pub struct Uniforms {
+ transform: [f32; 16],
+ /// Uniform values must be 256-aligned;
+ /// see: [`wgpu::Limits`] `min_uniform_buffer_offset_alignment`.
+ _padding: [f32; 48],
+}
+
+impl Uniforms {
+ pub fn new(transform: Transformation) -> Self {
+ Self {
+ transform: transform.into(),
+ _padding: [0.0; 48],
+ }
+ }
+
+ pub fn entry() -> wgpu::BindGroupLayoutEntry {
+ wgpu::BindGroupLayoutEntry {
+ binding: 0,
+ visibility: wgpu::ShaderStages::VERTEX_FRAGMENT,
+ ty: wgpu::BindingType::Buffer {
+ ty: wgpu::BufferBindingType::Uniform,
+ has_dynamic_offset: true,
+ min_binding_size: wgpu::BufferSize::new(
+ std::mem::size_of::<Self>() as u64,
+ ),
+ },
+ count: None,
+ }
+ }
+
+ pub fn min_size() -> Option<wgpu::BufferSize> {
+ wgpu::BufferSize::new(std::mem::size_of::<Self>() as u64)
+ }
+}
+
mod solid {
- use crate::buffer::dynamic;
- use crate::buffer::r#static::Buffer;
use crate::graphics::primitive;
- use crate::graphics::{Antialiasing, Transformation};
+ use crate::graphics::Antialiasing;
use crate::triangle;
-
- use encase::ShaderType;
+ use crate::Buffer;
#[derive(Debug)]
pub struct Pipeline {
@@ -481,7 +407,7 @@ mod solid {
#[derive(Debug)]
pub struct Layer {
pub vertices: Buffer<primitive::ColoredVertex2D>,
- pub uniforms: dynamic::Buffer<Uniforms>,
+ pub uniforms: Buffer<triangle::Uniforms>,
pub constants: wgpu::BindGroup,
}
@@ -492,17 +418,20 @@ mod solid {
) -> Self {
let vertices = Buffer::new(
device,
- "iced_wgpu::triangle::solid vertex buffer",
+ "iced_wgpu.triangle.solid.vertex_buffer",
+ triangle::INITIAL_VERTEX_COUNT,
wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
);
- let uniforms = dynamic::Buffer::uniform(
+ let uniforms = Buffer::new(
device,
- "iced_wgpu::triangle::solid uniforms",
+ "iced_wgpu.triangle.solid.uniforms",
+ 1,
+ wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
);
let constants =
- Self::bind_group(device, uniforms.raw(), constants_layout);
+ Self::bind_group(device, &uniforms.raw, constants_layout);
Self {
vertices,
@@ -517,7 +446,7 @@ mod solid {
layout: &wgpu::BindGroupLayout,
) -> wgpu::BindGroup {
device.create_bind_group(&wgpu::BindGroupDescriptor {
- label: Some("iced_wgpu::triangle::solid bind group"),
+ label: Some("iced_wgpu.triangle.solid.bind_group"),
layout,
entries: &[wgpu::BindGroupEntry {
binding: 0,
@@ -525,7 +454,7 @@ mod solid {
wgpu::BufferBinding {
buffer,
offset: 0,
- size: Some(Uniforms::min_size()),
+ size: triangle::Uniforms::min_size(),
},
),
}],
@@ -533,21 +462,7 @@ mod solid {
}
}
- #[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,
@@ -555,23 +470,14 @@ mod solid {
) -> Self {
let constants_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,
- }],
+ label: Some("iced_wgpu.triangle.solid.bind_group_layout"),
+ entries: &[triangle::Uniforms::entry()],
},
);
let layout = device.create_pipeline_layout(
&wgpu::PipelineLayoutDescriptor {
- label: Some("iced_wgpu::triangle::solid pipeline layout"),
+ label: Some("iced_wgpu.triangle.solid.pipeline_layout"),
bind_group_layouts: &[&constants_layout],
push_constant_ranges: &[],
},
@@ -579,12 +485,10 @@ mod solid {
let shader =
device.create_shader_module(wgpu::ShaderModuleDescriptor {
- label: Some(
- "iced_wgpu triangle solid create shader module",
- ),
+ label: Some("iced_wgpu.triangle.solid.shader"),
source: wgpu::ShaderSource::Wgsl(
std::borrow::Cow::Borrowed(include_str!(
- "shader/solid.wgsl"
+ "shader/triangle.wgsl"
)),
),
});
@@ -595,7 +499,7 @@ mod solid {
layout: Some(&layout),
vertex: wgpu::VertexState {
module: &shader,
- entry_point: "vs_main",
+ entry_point: "solid_vs_main",
buffers: &[wgpu::VertexBufferLayout {
array_stride: std::mem::size_of::<
primitive::ColoredVertex2D,
@@ -612,7 +516,7 @@ mod solid {
},
fragment: Some(wgpu::FragmentState {
module: &shader,
- entry_point: "fs_main",
+ entry_point: "solid_fs_main",
targets: &[triangle::fragment_target(format)],
}),
primitive: triangle::primitive_state(),
@@ -630,16 +534,10 @@ mod solid {
}
}
-#[cfg(not(target_arch = "wasm32"))]
mod gradient {
- use crate::buffer::dynamic;
- use crate::buffer::r#static::Buffer;
- use crate::graphics::Antialiasing;
+ use crate::graphics::{primitive, Antialiasing};
use crate::triangle;
-
- use encase::ShaderType;
- use glam::{IVec4, Vec4};
- use iced_graphics::primitive;
+ use crate::Buffer;
#[derive(Debug)]
pub struct Pipeline {
@@ -649,14 +547,9 @@ mod gradient {
#[derive(Debug)]
pub struct Layer {
- pub vertices: Buffer<primitive::Vertex2D>,
- pub uniforms: dynamic::Buffer<Uniforms>,
- pub storage: dynamic::Buffer<Storage>,
+ pub vertices: Buffer<primitive::GradientVertex2D>,
+ pub uniforms: Buffer<triangle::Uniforms>,
pub constants: wgpu::BindGroup,
- 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,
}
impl Layer {
@@ -666,94 +559,52 @@ mod gradient {
) -> Self {
let vertices = Buffer::new(
device,
- "iced_wgpu::triangle::gradient vertex buffer",
+ "iced_wgpu.triangle.gradient.vertex_buffer",
+ triangle::INITIAL_VERTEX_COUNT,
wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
);
- let uniforms = dynamic::Buffer::uniform(
+ let uniforms = Buffer::new(
device,
- "iced_wgpu::triangle::gradient uniforms",
+ "iced_wgpu.triangle.gradient.uniforms",
+ 1,
+ wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
);
- // 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 constants = Self::bind_group(
- device,
- uniforms.raw(),
- storage.raw(),
- constants_layout,
- );
+ let constants =
+ Self::bind_group(device, &uniforms.raw, constants_layout);
Self {
vertices,
uniforms,
- storage,
constants,
- color_stop_offset: 0,
- color_stops_pending_write: Storage {
- color_stops: vec![],
- },
}
}
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"),
+ 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(),
- },
- ],
+ entries: &[wgpu::BindGroupEntry {
+ binding: 0,
+ resource: wgpu::BindingResource::Buffer(
+ wgpu::BufferBinding {
+ buffer: uniform_buffer,
+ offset: 0,
+ size: triangle::Uniforms::min_size(),
+ },
+ ),
+ }],
})
}
}
- #[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(
+ pub fn new(
device: &wgpu::Device,
format: wgpu::TextureFormat,
antialiasing: Option<Antialiasing>,
@@ -761,40 +612,15 @@ mod gradient {
let constants_layout = device.create_bind_group_layout(
&wgpu::BindGroupLayoutDescriptor {
label: Some(
- "iced_wgpu::triangle::gradient bind group layout",
+ "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,
- },
- ],
+ entries: &[triangle::Uniforms::entry()],
},
);
let layout = device.create_pipeline_layout(
&wgpu::PipelineLayoutDescriptor {
- label: Some(
- "iced_wgpu::triangle::gradient pipeline layout",
- ),
+ label: Some("iced_wgpu.triangle.gradient.pipeline_layout"),
bind_group_layouts: &[&constants_layout],
push_constant_ranges: &[],
},
@@ -802,48 +628,56 @@ mod gradient {
let shader =
device.create_shader_module(wgpu::ShaderModuleDescriptor {
- label: Some(
- "iced_wgpu::triangle::gradient create shader module",
- ),
+ label: Some("iced_wgpu.triangle.gradient.shader"),
source: wgpu::ShaderSource::Wgsl(
std::borrow::Cow::Borrowed(include_str!(
- "shader/gradient.wgsl"
+ "shader/triangle.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::<
- primitive::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,
+ 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: "gradient_vs_main",
+ buffers: &[wgpu::VertexBufferLayout {
+ array_stride: std::mem::size_of::<
+ primitive::GradientVertex2D,
+ >()
+ as u64,
+ step_mode: wgpu::VertexStepMode::Vertex,
+ attributes: &wgpu::vertex_attr_array!(
+ // Position
+ 0 => Float32x2,
+ // Colors 1-2
+ 1 => Uint32x4,
+ // Colors 3-4
+ 2 => Uint32x4,
+ // Colors 5-6
+ 3 => Uint32x4,
+ // Colors 7-8
+ 4 => Uint32x4,
+ // Offsets
+ 5 => Uint32x4,
+ // Direction
+ 6 => Float32x4
+ ),
+ }],
},
- );
+ fragment: Some(wgpu::FragmentState {
+ module: &shader,
+ entry_point: "gradient_fs_main",
+ targets: &[triangle::fragment_target(format)],
+ }),
+ primitive: triangle::primitive_state(),
+ depth_stencil: None,
+ multisample: triangle::multisample_state(antialiasing),
+ multiview: None,
+ },
+ );
Self {
pipeline,
diff --git a/wgpu/src/triangle/msaa.rs b/wgpu/src/triangle/msaa.rs
index 4afbdb32..320b5b12 100644
--- a/wgpu/src/triangle/msaa.rs
+++ b/wgpu/src/triangle/msaa.rs
@@ -16,15 +16,8 @@ impl Blit {
format: wgpu::TextureFormat,
antialiasing: graphics::Antialiasing,
) -> Blit {
- let sampler = device.create_sampler(&wgpu::SamplerDescriptor {
- address_mode_u: wgpu::AddressMode::ClampToEdge,
- address_mode_v: wgpu::AddressMode::ClampToEdge,
- address_mode_w: wgpu::AddressMode::ClampToEdge,
- mag_filter: wgpu::FilterMode::Nearest,
- min_filter: wgpu::FilterMode::Nearest,
- mipmap_filter: wgpu::FilterMode::Nearest,
- ..Default::default()
- });
+ let sampler =
+ device.create_sampler(&wgpu::SamplerDescriptor::default());
let constant_layout =
device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
diff --git a/wgpu/src/window/compositor.rs b/wgpu/src/window/compositor.rs
index 500458e8..1cfd7b67 100644
--- a/wgpu/src/window/compositor.rs
+++ b/wgpu/src/window/compositor.rs
@@ -1,6 +1,7 @@
//! Connect a window with a renderer.
-use crate::core::Color;
+use crate::core::{Color, Size};
use crate::graphics;
+use crate::graphics::color;
use crate::graphics::compositor;
use crate::graphics::{Error, Primitive, Viewport};
use crate::{Backend, Renderer, Settings};
@@ -69,16 +70,19 @@ impl<Theme> Compositor<Theme> {
let format = compatible_surface.as_ref().and_then(|surface| {
let capabilities = surface.get_capabilities(&adapter);
- capabilities
- .formats
- .iter()
- .copied()
- .find(wgpu::TextureFormat::is_srgb)
- .or_else(|| {
- log::warn!("No sRGB format found!");
+ let mut formats = capabilities.formats.iter().copied();
- capabilities.formats.first().copied()
- })
+ let format = if color::GAMMA_CORRECTION {
+ formats.find(wgpu::TextureFormat::is_srgb)
+ } else {
+ formats.find(|format| !wgpu::TextureFormat::is_srgb(format))
+ };
+
+ format.or_else(|| {
+ log::warn!("No format found!");
+
+ capabilities.formats.first().copied()
+ })
})?;
log::info!("Selected format: {:?}", format);
@@ -279,4 +283,154 @@ impl<Theme> graphics::Compositor for Compositor<Theme> {
)
})
}
+
+ fn screenshot<T: AsRef<str>>(
+ &mut self,
+ renderer: &mut Self::Renderer,
+ _surface: &mut Self::Surface,
+ viewport: &Viewport,
+ background_color: Color,
+ overlay: &[T],
+ ) -> Vec<u8> {
+ renderer.with_primitives(|backend, primitives| {
+ screenshot(
+ self,
+ backend,
+ primitives,
+ viewport,
+ background_color,
+ overlay,
+ )
+ })
+ }
+}
+
+/// Renders the current surface to an offscreen buffer.
+///
+/// Returns RGBA bytes of the texture data.
+pub fn screenshot<Theme, T: AsRef<str>>(
+ compositor: &Compositor<Theme>,
+ backend: &mut Backend,
+ primitives: &[Primitive],
+ viewport: &Viewport,
+ background_color: Color,
+ overlay: &[T],
+) -> Vec<u8> {
+ let mut encoder = compositor.device.create_command_encoder(
+ &wgpu::CommandEncoderDescriptor {
+ label: Some("iced_wgpu.offscreen.encoder"),
+ },
+ );
+
+ let dimensions = BufferDimensions::new(viewport.physical_size());
+
+ let texture_extent = wgpu::Extent3d {
+ width: dimensions.width,
+ height: dimensions.height,
+ depth_or_array_layers: 1,
+ };
+
+ let texture = compositor.device.create_texture(&wgpu::TextureDescriptor {
+ label: Some("iced_wgpu.offscreen.source_texture"),
+ size: texture_extent,
+ mip_level_count: 1,
+ sample_count: 1,
+ dimension: wgpu::TextureDimension::D2,
+ format: compositor.format,
+ usage: wgpu::TextureUsages::RENDER_ATTACHMENT
+ | wgpu::TextureUsages::COPY_SRC
+ | wgpu::TextureUsages::TEXTURE_BINDING,
+ view_formats: &[],
+ });
+
+ let view = texture.create_view(&wgpu::TextureViewDescriptor::default());
+
+ backend.present(
+ &compositor.device,
+ &compositor.queue,
+ &mut encoder,
+ Some(background_color),
+ &view,
+ primitives,
+ viewport,
+ overlay,
+ );
+
+ let texture = crate::color::convert(
+ &compositor.device,
+ &mut encoder,
+ texture,
+ if color::GAMMA_CORRECTION {
+ wgpu::TextureFormat::Rgba8UnormSrgb
+ } else {
+ wgpu::TextureFormat::Rgba8Unorm
+ },
+ );
+
+ let output_buffer =
+ compositor.device.create_buffer(&wgpu::BufferDescriptor {
+ label: Some("iced_wgpu.offscreen.output_texture_buffer"),
+ size: (dimensions.padded_bytes_per_row * dimensions.height as usize)
+ as u64,
+ usage: wgpu::BufferUsages::MAP_READ | wgpu::BufferUsages::COPY_DST,
+ mapped_at_creation: false,
+ });
+
+ encoder.copy_texture_to_buffer(
+ texture.as_image_copy(),
+ wgpu::ImageCopyBuffer {
+ buffer: &output_buffer,
+ layout: wgpu::ImageDataLayout {
+ offset: 0,
+ bytes_per_row: Some(dimensions.padded_bytes_per_row as u32),
+ rows_per_image: None,
+ },
+ },
+ texture_extent,
+ );
+
+ let index = compositor.queue.submit(Some(encoder.finish()));
+
+ let slice = output_buffer.slice(..);
+ slice.map_async(wgpu::MapMode::Read, |_| {});
+
+ let _ = compositor
+ .device
+ .poll(wgpu::Maintain::WaitForSubmissionIndex(index));
+
+ let mapped_buffer = slice.get_mapped_range();
+
+ mapped_buffer.chunks(dimensions.padded_bytes_per_row).fold(
+ vec![],
+ |mut acc, row| {
+ acc.extend(&row[..dimensions.unpadded_bytes_per_row]);
+ acc
+ },
+ )
+}
+
+#[derive(Clone, Copy, Debug)]
+struct BufferDimensions {
+ width: u32,
+ height: u32,
+ unpadded_bytes_per_row: usize,
+ padded_bytes_per_row: usize,
+}
+
+impl BufferDimensions {
+ fn new(size: Size<u32>) -> Self {
+ let unpadded_bytes_per_row = size.width as usize * 4; //slice of buffer per row; always RGBA
+ let alignment = wgpu::COPY_BYTES_PER_ROW_ALIGNMENT as usize; //256
+ let padded_bytes_per_row_padding =
+ (alignment - unpadded_bytes_per_row % alignment) % alignment;
+ let padded_bytes_per_row =
+ unpadded_bytes_per_row + padded_bytes_per_row_padding;
+
+ Self {
+ width: size.width,
+ height: size.height,
+ unpadded_bytes_per_row,
+ padded_bytes_per_row,
+ }
+ }
}
diff --git a/widget/Cargo.toml b/widget/Cargo.toml
index 40e4db37..14aae72e 100644
--- a/widget/Cargo.toml
+++ b/widget/Cargo.toml
@@ -28,7 +28,7 @@ version = "0.8"
path = "../style"
[dependencies.ouroboros]
-version = "0.13"
+version = "0.17"
optional = true
[dependencies.qrcode]
diff --git a/widget/src/button.rs b/widget/src/button.rs
index 7eee69cb..8ebc9657 100644
--- a/widget/src/button.rs
+++ b/widget/src/button.rs
@@ -102,8 +102,17 @@ where
/// Sets the message that will be produced when the [`Button`] is pressed.
///
/// Unless `on_press` is called, the [`Button`] will be disabled.
- pub fn on_press(mut self, msg: Message) -> Self {
- self.on_press = Some(msg);
+ pub fn on_press(mut self, on_press: Message) -> Self {
+ self.on_press = Some(on_press);
+ self
+ }
+
+ /// Sets the message that will be produced when the [`Button`] is pressed,
+ /// if `Some`.
+ ///
+ /// If `None`, the [`Button`] will be disabled.
+ pub fn on_press_maybe(mut self, on_press: Option<Message>) -> Self {
+ self.on_press = on_press;
self
}
@@ -187,7 +196,7 @@ where
tree: &mut Tree,
event: Event,
layout: Layout<'_>,
- cursor_position: Point,
+ cursor: mouse::Cursor,
renderer: &Renderer,
clipboard: &mut dyn Clipboard,
shell: &mut Shell<'_, Message>,
@@ -196,7 +205,7 @@ where
&mut tree.children[0],
event.clone(),
layout.children().next().unwrap(),
- cursor_position,
+ cursor,
renderer,
clipboard,
shell,
@@ -204,14 +213,9 @@ where
return event::Status::Captured;
}
- update(
- event,
- layout,
- cursor_position,
- shell,
- &self.on_press,
- || tree.state.downcast_mut::<State>(),
- )
+ update(event, layout, cursor, shell, &self.on_press, || {
+ tree.state.downcast_mut::<State>()
+ })
}
fn draw(
@@ -221,7 +225,7 @@ where
theme: &Renderer::Theme,
_style: &renderer::Style,
layout: Layout<'_>,
- cursor_position: Point,
+ cursor: mouse::Cursor,
_viewport: &Rectangle,
) {
let bounds = layout.bounds();
@@ -230,7 +234,7 @@ where
let styling = draw(
renderer,
bounds,
- cursor_position,
+ cursor,
self.on_press.is_some(),
theme,
&self.style,
@@ -245,7 +249,7 @@ where
text_color: styling.text_color,
},
content_layout,
- cursor_position,
+ cursor,
&bounds,
);
}
@@ -254,11 +258,11 @@ where
&self,
_tree: &Tree,
layout: Layout<'_>,
- cursor_position: Point,
+ cursor: mouse::Cursor,
_viewport: &Rectangle,
_renderer: &Renderer,
) -> mouse::Interaction {
- mouse_interaction(layout, cursor_position, self.on_press.is_some())
+ mouse_interaction(layout, cursor, self.on_press.is_some())
}
fn overlay<'b>(
@@ -305,7 +309,7 @@ impl State {
pub fn update<'a, Message: Clone>(
event: Event,
layout: Layout<'_>,
- cursor_position: Point,
+ cursor: mouse::Cursor,
shell: &mut Shell<'_, Message>,
on_press: &Option<Message>,
state: impl FnOnce() -> &'a mut State,
@@ -316,7 +320,7 @@ pub fn update<'a, Message: Clone>(
if on_press.is_some() {
let bounds = layout.bounds();
- if bounds.contains(cursor_position) {
+ if cursor.is_over(bounds) {
let state = state();
state.is_pressed = true;
@@ -335,7 +339,7 @@ pub fn update<'a, Message: Clone>(
let bounds = layout.bounds();
- if bounds.contains(cursor_position) {
+ if cursor.is_over(bounds) {
shell.publish(on_press);
}
@@ -358,7 +362,7 @@ pub fn update<'a, Message: Clone>(
pub fn draw<'a, Renderer: crate::core::Renderer>(
renderer: &mut Renderer,
bounds: Rectangle,
- cursor_position: Point,
+ cursor: mouse::Cursor,
is_enabled: bool,
style_sheet: &dyn StyleSheet<
Style = <Renderer::Theme as StyleSheet>::Style,
@@ -369,7 +373,7 @@ pub fn draw<'a, Renderer: crate::core::Renderer>(
where
Renderer::Theme: StyleSheet,
{
- let is_mouse_over = bounds.contains(cursor_position);
+ let is_mouse_over = cursor.is_over(bounds);
let styling = if !is_enabled {
style_sheet.disabled(style)
@@ -395,7 +399,7 @@ where
y: bounds.y + styling.shadow_offset.y,
..bounds
},
- border_radius: styling.border_radius.into(),
+ border_radius: styling.border_radius,
border_width: 0.0,
border_color: Color::TRANSPARENT,
},
@@ -406,7 +410,7 @@ where
renderer.fill_quad(
renderer::Quad {
bounds,
- border_radius: styling.border_radius.into(),
+ border_radius: styling.border_radius,
border_width: styling.border_width,
border_color: styling.border_color,
},
@@ -442,10 +446,10 @@ pub fn layout<Renderer>(
/// Returns the [`mouse::Interaction`] of a [`Button`].
pub fn mouse_interaction(
layout: Layout<'_>,
- cursor_position: Point,
+ cursor: mouse::Cursor,
is_enabled: bool,
) -> mouse::Interaction {
- let is_mouse_over = layout.bounds().contains(cursor_position);
+ let is_mouse_over = cursor.is_over(layout.bounds());
if is_mouse_over && is_enabled {
mouse::Interaction::Pointer
diff --git a/widget/src/canvas.rs b/widget/src/canvas.rs
index 171c4534..96062038 100644
--- a/widget/src/canvas.rs
+++ b/widget/src/canvas.rs
@@ -1,10 +1,8 @@
//! Draw 2D graphics for your users.
pub mod event;
-mod cursor;
mod program;
-pub use cursor::Cursor;
pub use event::Event;
pub use program::Program;
@@ -17,7 +15,7 @@ use crate::core::mouse;
use crate::core::renderer;
use crate::core::widget::tree::{self, Tree};
use crate::core::{Clipboard, Element, Shell, Widget};
-use crate::core::{Length, Point, Rectangle, Size, Vector};
+use crate::core::{Length, Rectangle, Size, Vector};
use crate::graphics::geometry;
use std::marker::PhantomData;
@@ -28,8 +26,9 @@ use std::marker::PhantomData;
/// If you want to get a quick overview, here's how we can draw a simple circle:
///
/// ```no_run
-/// # use iced_widget::canvas::{self, Canvas, Cursor, Fill, Frame, Geometry, Path, Program};
+/// # use iced_widget::canvas::{self, Canvas, Fill, Frame, Geometry, Path, Program};
/// # use iced_widget::core::{Color, Rectangle};
+/// # use iced_widget::core::mouse;
/// # use iced_widget::style::Theme;
/// #
/// # pub type Renderer = iced_widget::renderer::Renderer<Theme>;
@@ -43,7 +42,7 @@ use std::marker::PhantomData;
/// impl Program<()> for Circle {
/// type State = ();
///
-/// fn draw(&self, _state: &(), renderer: &Renderer, _theme: &Theme, bounds: Rectangle, _cursor: Cursor) -> Vec<Geometry>{
+/// fn draw(&self, _state: &(), renderer: &Renderer, _theme: &Theme, bounds: Rectangle, _cursor: mouse::Cursor) -> Vec<Geometry>{
/// // We prepare a new `Frame`
/// let mut frame = Frame::new(renderer, bounds.size());
///
@@ -144,7 +143,7 @@ where
tree: &mut Tree,
event: core::Event,
layout: Layout<'_>,
- cursor_position: Point,
+ cursor: mouse::Cursor,
_renderer: &Renderer,
_clipboard: &mut dyn Clipboard,
shell: &mut Shell<'_, Message>,
@@ -160,8 +159,6 @@ where
_ => None,
};
- let cursor = Cursor::from_window_position(cursor_position);
-
if let Some(canvas_event) = canvas_event {
let state = tree.state.downcast_mut::<P::State>();
@@ -182,12 +179,11 @@ where
&self,
tree: &Tree,
layout: Layout<'_>,
- cursor_position: Point,
+ cursor: mouse::Cursor,
_viewport: &Rectangle,
_renderer: &Renderer,
) -> mouse::Interaction {
let bounds = layout.bounds();
- let cursor = Cursor::from_window_position(cursor_position);
let state = tree.state.downcast_ref::<P::State>();
self.program.mouse_interaction(state, bounds, cursor)
@@ -200,7 +196,7 @@ where
theme: &Renderer::Theme,
_style: &renderer::Style,
layout: Layout<'_>,
- cursor_position: Point,
+ cursor: mouse::Cursor,
_viewport: &Rectangle,
) {
let bounds = layout.bounds();
@@ -209,7 +205,6 @@ where
return;
}
- let cursor = Cursor::from_window_position(cursor_position);
let state = tree.state.downcast_ref::<P::State>();
renderer.with_translation(
diff --git a/widget/src/canvas/cursor.rs b/widget/src/canvas/cursor.rs
deleted file mode 100644
index 5a65e9a7..00000000
--- a/widget/src/canvas/cursor.rs
+++ /dev/null
@@ -1,64 +0,0 @@
-use crate::core::{Point, Rectangle};
-
-/// The mouse cursor state.
-#[derive(Debug, Clone, Copy, PartialEq)]
-pub enum Cursor {
- /// The cursor has a defined position.
- Available(Point),
-
- /// The cursor is currently unavailable (i.e. out of bounds or busy).
- Unavailable,
-}
-
-impl Cursor {
- // TODO: Remove this once this type is used in `iced_native` to encode
- // proper cursor availability
- pub(crate) fn from_window_position(position: Point) -> Self {
- if position.x < 0.0 || position.y < 0.0 {
- Cursor::Unavailable
- } else {
- Cursor::Available(position)
- }
- }
-
- /// Returns the absolute position of the [`Cursor`], if available.
- pub fn position(&self) -> Option<Point> {
- match self {
- Cursor::Available(position) => Some(*position),
- Cursor::Unavailable => None,
- }
- }
-
- /// Returns the relative position of the [`Cursor`] inside the given bounds,
- /// if available.
- ///
- /// If the [`Cursor`] is not over the provided bounds, this method will
- /// return `None`.
- pub fn position_in(&self, bounds: &Rectangle) -> Option<Point> {
- if self.is_over(bounds) {
- self.position_from(bounds.position())
- } else {
- None
- }
- }
-
- /// Returns the relative position of the [`Cursor`] from the given origin,
- /// if available.
- pub fn position_from(&self, origin: Point) -> Option<Point> {
- match self {
- Cursor::Available(position) => {
- Some(Point::new(position.x - origin.x, position.y - origin.y))
- }
- Cursor::Unavailable => None,
- }
- }
-
- /// Returns whether the [`Cursor`] is currently over the provided bounds
- /// or not.
- pub fn is_over(&self, bounds: &Rectangle) -> bool {
- match self {
- Cursor::Available(position) => bounds.contains(*position),
- Cursor::Unavailable => false,
- }
- }
-}
diff --git a/widget/src/canvas/program.rs b/widget/src/canvas/program.rs
index efb33c56..929ee285 100644
--- a/widget/src/canvas/program.rs
+++ b/widget/src/canvas/program.rs
@@ -1,8 +1,7 @@
use crate::canvas::event::{self, Event};
use crate::canvas::mouse;
-use crate::canvas::Cursor;
use crate::core::Rectangle;
-use crate::graphics::geometry;
+use crate::graphics::geometry::{self, Geometry};
/// The state and logic of a [`Canvas`].
///
@@ -33,7 +32,7 @@ where
_state: &mut Self::State,
_event: Event,
_bounds: Rectangle,
- _cursor: Cursor,
+ _cursor: mouse::Cursor,
) -> (event::Status, Option<Message>) {
(event::Status::Ignored, None)
}
@@ -51,8 +50,8 @@ where
renderer: &Renderer,
theme: &Renderer::Theme,
bounds: Rectangle,
- cursor: Cursor,
- ) -> Vec<Renderer::Geometry>;
+ cursor: mouse::Cursor,
+ ) -> Vec<Geometry>;
/// Returns the current mouse interaction of the [`Program`].
///
@@ -64,7 +63,7 @@ where
&self,
_state: &Self::State,
_bounds: Rectangle,
- _cursor: Cursor,
+ _cursor: mouse::Cursor,
) -> mouse::Interaction {
mouse::Interaction::default()
}
@@ -82,7 +81,7 @@ where
state: &mut Self::State,
event: Event,
bounds: Rectangle,
- cursor: Cursor,
+ cursor: mouse::Cursor,
) -> (event::Status, Option<Message>) {
T::update(self, state, event, bounds, cursor)
}
@@ -93,8 +92,8 @@ where
renderer: &Renderer,
theme: &Renderer::Theme,
bounds: Rectangle,
- cursor: Cursor,
- ) -> Vec<Renderer::Geometry> {
+ cursor: mouse::Cursor,
+ ) -> Vec<Geometry> {
T::draw(self, state, renderer, theme, bounds, cursor)
}
@@ -102,7 +101,7 @@ where
&self,
state: &Self::State,
bounds: Rectangle,
- cursor: Cursor,
+ cursor: mouse::Cursor,
) -> mouse::Interaction {
T::mouse_interaction(self, state, bounds, cursor)
}
diff --git a/widget/src/checkbox.rs b/widget/src/checkbox.rs
index 7d43bb4a..aa0bff42 100644
--- a/widget/src/checkbox.rs
+++ b/widget/src/checkbox.rs
@@ -8,8 +8,8 @@ use crate::core::text;
use crate::core::touch;
use crate::core::widget::Tree;
use crate::core::{
- Alignment, Clipboard, Element, Layout, Length, Pixels, Point, Rectangle,
- Shell, Widget,
+ Alignment, Clipboard, Element, Layout, Length, Pixels, Rectangle, Shell,
+ Widget,
};
use crate::{Row, Text};
@@ -204,7 +204,7 @@ where
_tree: &mut Tree,
event: Event,
layout: Layout<'_>,
- cursor_position: Point,
+ cursor: mouse::Cursor,
_renderer: &Renderer,
_clipboard: &mut dyn Clipboard,
shell: &mut Shell<'_, Message>,
@@ -212,7 +212,7 @@ where
match event {
Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left))
| Event::Touch(touch::Event::FingerPressed { .. }) => {
- let mouse_over = layout.bounds().contains(cursor_position);
+ let mouse_over = cursor.is_over(layout.bounds());
if mouse_over {
shell.publish((self.on_toggle)(!self.is_checked));
@@ -230,11 +230,11 @@ where
&self,
_tree: &Tree,
layout: Layout<'_>,
- cursor_position: Point,
+ cursor: mouse::Cursor,
_viewport: &Rectangle,
_renderer: &Renderer,
) -> mouse::Interaction {
- if layout.bounds().contains(cursor_position) {
+ if cursor.is_over(layout.bounds()) {
mouse::Interaction::Pointer
} else {
mouse::Interaction::default()
@@ -248,11 +248,10 @@ where
theme: &Renderer::Theme,
style: &renderer::Style,
layout: Layout<'_>,
- cursor_position: Point,
+ cursor: mouse::Cursor,
_viewport: &Rectangle,
) {
- let bounds = layout.bounds();
- let is_mouse_over = bounds.contains(cursor_position);
+ let is_mouse_over = cursor.is_over(layout.bounds());
let mut children = layout.children();
@@ -269,7 +268,7 @@ where
renderer.fill_quad(
renderer::Quad {
bounds,
- border_radius: custom_style.border_radius.into(),
+ border_radius: custom_style.border_radius,
border_width: custom_style.border_width,
border_color: custom_style.border_color,
},
diff --git a/widget/src/column.rs b/widget/src/column.rs
index 8f363ec6..d92d794b 100644
--- a/widget/src/column.rs
+++ b/widget/src/column.rs
@@ -6,8 +6,8 @@ use crate::core::overlay;
use crate::core::renderer;
use crate::core::widget::{Operation, Tree};
use crate::core::{
- Alignment, Clipboard, Element, Layout, Length, Padding, Pixels, Point,
- Rectangle, Shell, Widget,
+ Alignment, Clipboard, Element, Layout, Length, Padding, Pixels, Rectangle,
+ Shell, Widget,
};
/// A container that distributes its contents vertically.
@@ -166,7 +166,7 @@ where
tree: &mut Tree,
event: Event,
layout: Layout<'_>,
- cursor_position: Point,
+ cursor: mouse::Cursor,
renderer: &Renderer,
clipboard: &mut dyn Clipboard,
shell: &mut Shell<'_, Message>,
@@ -180,7 +180,7 @@ where
state,
event.clone(),
layout,
- cursor_position,
+ cursor,
renderer,
clipboard,
shell,
@@ -193,7 +193,7 @@ where
&self,
tree: &Tree,
layout: Layout<'_>,
- cursor_position: Point,
+ cursor: mouse::Cursor,
viewport: &Rectangle,
renderer: &Renderer,
) -> mouse::Interaction {
@@ -203,11 +203,7 @@ where
.zip(layout.children())
.map(|((child, state), layout)| {
child.as_widget().mouse_interaction(
- state,
- layout,
- cursor_position,
- viewport,
- renderer,
+ state, layout, cursor, viewport, renderer,
)
})
.max()
@@ -221,7 +217,7 @@ where
theme: &Renderer::Theme,
style: &renderer::Style,
layout: Layout<'_>,
- cursor_position: Point,
+ cursor: mouse::Cursor,
viewport: &Rectangle,
) {
for ((child, state), layout) in self
@@ -230,15 +226,9 @@ where
.zip(&tree.children)
.zip(layout.children())
{
- child.as_widget().draw(
- state,
- renderer,
- theme,
- style,
- layout,
- cursor_position,
- viewport,
- );
+ child
+ .as_widget()
+ .draw(state, renderer, theme, style, layout, cursor, viewport);
}
}
diff --git a/widget/src/container.rs b/widget/src/container.rs
index 9d932772..da9a31d6 100644
--- a/widget/src/container.rs
+++ b/widget/src/container.rs
@@ -196,7 +196,7 @@ where
tree: &mut Tree,
event: Event,
layout: Layout<'_>,
- cursor_position: Point,
+ cursor: mouse::Cursor,
renderer: &Renderer,
clipboard: &mut dyn Clipboard,
shell: &mut Shell<'_, Message>,
@@ -205,7 +205,7 @@ where
&mut tree.children[0],
event,
layout.children().next().unwrap(),
- cursor_position,
+ cursor,
renderer,
clipboard,
shell,
@@ -216,14 +216,14 @@ where
&self,
tree: &Tree,
layout: Layout<'_>,
- cursor_position: Point,
+ cursor: mouse::Cursor,
viewport: &Rectangle,
renderer: &Renderer,
) -> mouse::Interaction {
self.content.as_widget().mouse_interaction(
&tree.children[0],
layout.children().next().unwrap(),
- cursor_position,
+ cursor,
viewport,
renderer,
)
@@ -236,7 +236,7 @@ where
theme: &Renderer::Theme,
renderer_style: &renderer::Style,
layout: Layout<'_>,
- cursor_position: Point,
+ cursor: mouse::Cursor,
viewport: &Rectangle,
) {
let style = theme.appearance(&self.style);
@@ -253,7 +253,7 @@ where
.unwrap_or(renderer_style.text_color),
},
layout.children().next().unwrap(),
- cursor_position,
+ cursor,
viewport,
);
}
@@ -332,7 +332,7 @@ pub fn draw_background<Renderer>(
renderer.fill_quad(
renderer::Quad {
bounds,
- border_radius: appearance.border_radius.into(),
+ border_radius: appearance.border_radius,
border_width: appearance.border_width,
border_color: appearance.border_color,
},
diff --git a/widget/src/helpers.rs b/widget/src/helpers.rs
index 336ac4ee..3f5136f8 100644
--- a/widget/src/helpers.rs
+++ b/widget/src/helpers.rs
@@ -310,7 +310,6 @@ where
///
/// [`Image`]: widget::Image
#[cfg(feature = "image")]
-#[cfg_attr(docsrs, doc(cfg(feature = "image")))]
pub fn image<Handle>(handle: impl Into<Handle>) -> crate::Image<Handle> {
crate::Image::new(handle.into())
}
@@ -320,7 +319,6 @@ pub fn image<Handle>(handle: impl Into<Handle>) -> crate::Image<Handle> {
/// [`Svg`]: widget::Svg
/// [`Handle`]: widget::svg::Handle
#[cfg(feature = "svg")]
-#[cfg_attr(docsrs, doc(cfg(feature = "svg")))]
pub fn svg<Renderer>(
handle: impl Into<core::svg::Handle>,
) -> crate::Svg<Renderer>
@@ -333,7 +331,6 @@ where
/// Creates a new [`Canvas`].
#[cfg(feature = "canvas")]
-#[cfg_attr(docsrs, doc(cfg(feature = "canvas")))]
pub fn canvas<P, Message, Renderer>(
program: P,
) -> crate::Canvas<P, Message, Renderer>
diff --git a/widget/src/image.rs b/widget/src/image.rs
index abcb6ef2..66bf2156 100644
--- a/widget/src/image.rs
+++ b/widget/src/image.rs
@@ -4,10 +4,11 @@ pub use viewer::Viewer;
use crate::core::image;
use crate::core::layout;
+use crate::core::mouse;
use crate::core::renderer;
use crate::core::widget::Tree;
use crate::core::{
- ContentFit, Element, Layout, Length, Point, Rectangle, Size, Vector, Widget,
+ ContentFit, Element, Layout, Length, Rectangle, Size, Vector, Widget,
};
use std::hash::Hash;
@@ -186,7 +187,7 @@ where
_theme: &Renderer::Theme,
_style: &renderer::Style,
layout: Layout<'_>,
- _cursor_position: Point,
+ _cursor: mouse::Cursor,
_viewport: &Rectangle,
) {
draw(renderer, layout, &self.handle, self.content_fit)
diff --git a/widget/src/image/viewer.rs b/widget/src/image/viewer.rs
index 0d60d818..8040d6bd 100644
--- a/widget/src/image/viewer.rs
+++ b/widget/src/image/viewer.rs
@@ -144,18 +144,19 @@ where
tree: &mut Tree,
event: Event,
layout: Layout<'_>,
- cursor_position: Point,
+ cursor: mouse::Cursor,
renderer: &Renderer,
_clipboard: &mut dyn Clipboard,
_shell: &mut Shell<'_, Message>,
) -> event::Status {
let bounds = layout.bounds();
- let is_mouse_over = bounds.contains(cursor_position);
match event {
- Event::Mouse(mouse::Event::WheelScrolled { delta })
- if is_mouse_over =>
- {
+ Event::Mouse(mouse::Event::WheelScrolled { delta }) => {
+ let Some(cursor_position) = cursor.position() else {
+ return event::Status::Ignored;
+ };
+
match delta {
mouse::ScrollDelta::Lines { y, .. }
| mouse::ScrollDelta::Pixels { y, .. } => {
@@ -205,9 +206,11 @@ where
event::Status::Captured
}
- Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left))
- if is_mouse_over =>
- {
+ Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) => {
+ let Some(cursor_position) = cursor.position() else {
+ return event::Status::Ignored;
+ };
+
let state = tree.state.downcast_mut::<State>();
state.cursor_grabbed_at = Some(cursor_position);
@@ -277,13 +280,13 @@ where
&self,
tree: &Tree,
layout: Layout<'_>,
- cursor_position: Point,
+ cursor: mouse::Cursor,
_viewport: &Rectangle,
_renderer: &Renderer,
) -> mouse::Interaction {
let state = tree.state.downcast_ref::<State>();
let bounds = layout.bounds();
- let is_mouse_over = bounds.contains(cursor_position);
+ let is_mouse_over = cursor.is_over(bounds);
if state.is_cursor_grabbed() {
mouse::Interaction::Grabbing
@@ -301,7 +304,7 @@ where
_theme: &Renderer::Theme,
_style: &renderer::Style,
layout: Layout<'_>,
- _cursor_position: Point,
+ _cursor: mouse::Cursor,
_viewport: &Rectangle,
) {
let state = tree.state.downcast_ref::<State>();
diff --git a/widget/src/lazy.rs b/widget/src/lazy.rs
index b08ed8cb..da287f06 100644
--- a/widget/src/lazy.rs
+++ b/widget/src/lazy.rs
@@ -20,12 +20,14 @@ use crate::core::Element;
use crate::core::{
self, Clipboard, Hasher, Length, Point, Rectangle, Shell, Size,
};
+use crate::runtime::overlay::Nested;
use ouroboros::self_referencing;
use std::cell::RefCell;
use std::hash::{Hash, Hasher as H};
use std::rc::Rc;
+/// A widget that only rebuilds its contents when necessary.
#[allow(missing_debug_implementations)]
pub struct Lazy<'a, Message, Renderer, Dependency, View> {
dependency: Dependency,
@@ -41,6 +43,8 @@ where
Dependency: Hash + 'a,
View: Into<Element<'static, Message, Renderer>>,
{
+ /// Creates a new [`Lazy`] widget with the given data `Dependency` and a
+ /// closure that can turn this data into a widget tree.
pub fn new(
dependency: Dependency,
view: impl Fn(&Dependency) -> View + 'a,
@@ -178,7 +182,7 @@ where
tree: &mut Tree,
event: Event,
layout: Layout<'_>,
- cursor_position: Point,
+ cursor: mouse::Cursor,
renderer: &Renderer,
clipboard: &mut dyn Clipboard,
shell: &mut Shell<'_, Message>,
@@ -188,7 +192,7 @@ where
&mut tree.children[0],
event,
layout,
- cursor_position,
+ cursor,
renderer,
clipboard,
shell,
@@ -200,7 +204,7 @@ where
&self,
tree: &Tree,
layout: Layout<'_>,
- cursor_position: Point,
+ cursor: mouse::Cursor,
viewport: &Rectangle,
renderer: &Renderer,
) -> mouse::Interaction {
@@ -208,7 +212,7 @@ where
element.as_widget().mouse_interaction(
&tree.children[0],
layout,
- cursor_position,
+ cursor,
viewport,
renderer,
)
@@ -222,7 +226,7 @@ where
theme: &Renderer::Theme,
style: &renderer::Style,
layout: Layout<'_>,
- cursor_position: Point,
+ cursor: mouse::Cursor,
viewport: &Rectangle,
) {
self.with_element(|element| {
@@ -232,7 +236,7 @@ where
theme,
style,
layout,
- cursor_position,
+ cursor,
viewport,
)
})
@@ -257,14 +261,17 @@ where
.unwrap(),
tree: &mut tree.children[0],
overlay_builder: |element, tree| {
- element.as_widget_mut().overlay(tree, layout, renderer)
+ element
+ .as_widget_mut()
+ .overlay(tree, layout, renderer)
+ .map(|overlay| RefCell::new(Nested::new(overlay)))
},
}
.build(),
));
- let has_overlay = overlay
- .with_overlay_maybe(|overlay| overlay::Element::position(overlay));
+ let has_overlay =
+ overlay.with_overlay_maybe(|overlay| overlay.position());
has_overlay
.map(|position| overlay::Element::new(position, Box::new(overlay)))
@@ -272,18 +279,14 @@ where
}
#[self_referencing]
-struct Inner<'a, Message, Renderer>
-where
- Message: 'a,
- Renderer: 'a,
-{
+struct Inner<'a, Message: 'a, Renderer: 'a> {
cell: Rc<RefCell<Option<Element<'static, Message, Renderer>>>>,
element: Element<'static, Message, Renderer>,
tree: &'a mut Tree,
#[borrows(mut element, mut tree)]
- #[covariant]
- overlay: Option<overlay::Element<'this, Message, Renderer>>,
+ #[not_covariant]
+ overlay: Option<RefCell<Nested<'this, Message, Renderer>>>,
}
struct Overlay<'a, Message, Renderer>(Option<Inner<'a, Message, Renderer>>);
@@ -298,19 +301,20 @@ impl<'a, Message, Renderer> Drop for Overlay<'a, Message, Renderer> {
impl<'a, Message, Renderer> Overlay<'a, Message, Renderer> {
fn with_overlay_maybe<T>(
&self,
- f: impl FnOnce(&overlay::Element<'_, Message, Renderer>) -> T,
+ f: impl FnOnce(&mut Nested<'_, Message, Renderer>) -> T,
) -> Option<T> {
- self.0.as_ref().unwrap().borrow_overlay().as_ref().map(f)
+ self.0.as_ref().unwrap().with_overlay(|overlay| {
+ overlay.as_ref().map(|nested| (f)(&mut nested.borrow_mut()))
+ })
}
fn with_overlay_mut_maybe<T>(
&mut self,
- f: impl FnOnce(&mut overlay::Element<'_, Message, Renderer>) -> T,
+ f: impl FnOnce(&mut Nested<'_, Message, Renderer>) -> T,
) -> Option<T> {
- self.0
- .as_mut()
- .unwrap()
- .with_overlay_mut(|overlay| overlay.as_mut().map(f))
+ self.0.as_mut().unwrap().with_overlay_mut(|overlay| {
+ overlay.as_mut().map(|nested| (f)(nested.get_mut()))
+ })
}
}
@@ -326,9 +330,7 @@ where
position: Point,
) -> layout::Node {
self.with_overlay_maybe(|overlay| {
- let translation = position - overlay.position();
-
- overlay.layout(renderer, bounds, translation)
+ overlay.layout(renderer, bounds, position)
})
.unwrap_or_default()
}
@@ -339,27 +341,22 @@ where
theme: &Renderer::Theme,
style: &renderer::Style,
layout: Layout<'_>,
- cursor_position: Point,
+ cursor: mouse::Cursor,
) {
let _ = self.with_overlay_maybe(|overlay| {
- overlay.draw(renderer, theme, style, layout, cursor_position);
+ overlay.draw(renderer, theme, style, layout, cursor);
});
}
fn mouse_interaction(
&self,
layout: Layout<'_>,
- cursor_position: Point,
+ cursor: mouse::Cursor,
viewport: &Rectangle,
renderer: &Renderer,
) -> mouse::Interaction {
self.with_overlay_maybe(|overlay| {
- overlay.mouse_interaction(
- layout,
- cursor_position,
- viewport,
- renderer,
- )
+ overlay.mouse_interaction(layout, cursor, viewport, renderer)
})
.unwrap_or_default()
}
@@ -368,27 +365,25 @@ where
&mut self,
event: Event,
layout: Layout<'_>,
- cursor_position: Point,
+ cursor: mouse::Cursor,
renderer: &Renderer,
clipboard: &mut dyn Clipboard,
shell: &mut Shell<'_, Message>,
) -> event::Status {
self.with_overlay_mut_maybe(|overlay| {
- overlay.on_event(
- event,
- layout,
- cursor_position,
- renderer,
- clipboard,
- shell,
- )
+ overlay.on_event(event, layout, cursor, renderer, clipboard, shell)
})
.unwrap_or(event::Status::Ignored)
}
- fn is_over(&self, layout: Layout<'_>, cursor_position: Point) -> bool {
+ fn is_over(
+ &self,
+ layout: Layout<'_>,
+ renderer: &Renderer,
+ cursor_position: Point,
+ ) -> bool {
self.with_overlay_maybe(|overlay| {
- overlay.is_over(layout, cursor_position)
+ overlay.is_over(layout, renderer, cursor_position)
})
.unwrap_or_default()
}
diff --git a/widget/src/lazy/component.rs b/widget/src/lazy/component.rs
index 49ae68af..f955d9dd 100644
--- a/widget/src/lazy/component.rs
+++ b/widget/src/lazy/component.rs
@@ -9,6 +9,7 @@ use crate::core::widget::tree::{self, Tree};
use crate::core::{
self, Clipboard, Element, Length, Point, Rectangle, Shell, Size, Widget,
};
+use crate::runtime::overlay::Nested;
use ouroboros::self_referencing;
use std::cell::RefCell;
@@ -265,7 +266,7 @@ where
tree: &mut Tree,
event: core::Event,
layout: Layout<'_>,
- cursor_position: Point,
+ cursor: mouse::Cursor,
renderer: &Renderer,
clipboard: &mut dyn Clipboard,
shell: &mut Shell<'_, Message>,
@@ -279,7 +280,7 @@ where
&mut t.borrow_mut().as_mut().unwrap().children[0],
event,
layout,
- cursor_position,
+ cursor,
renderer,
clipboard,
&mut local_shell,
@@ -397,7 +398,7 @@ where
theme: &Renderer::Theme,
style: &renderer::Style,
layout: Layout<'_>,
- cursor_position: Point,
+ cursor: mouse::Cursor,
viewport: &Rectangle,
) {
let tree = tree.state.downcast_ref::<Rc<RefCell<Option<Tree>>>>();
@@ -408,7 +409,7 @@ where
theme,
style,
layout,
- cursor_position,
+ cursor,
viewport,
);
});
@@ -418,7 +419,7 @@ where
&self,
tree: &Tree,
layout: Layout<'_>,
- cursor_position: Point,
+ cursor: mouse::Cursor,
viewport: &Rectangle,
renderer: &Renderer,
) -> mouse::Interaction {
@@ -427,7 +428,7 @@ where
element.as_widget().mouse_interaction(
&tree.borrow().as_ref().unwrap().children[0],
layout,
- cursor_position,
+ cursor,
viewport,
renderer,
)
@@ -455,11 +456,18 @@ where
overlay_builder: |instance, tree| {
instance.state.get_mut().as_mut().unwrap().with_element_mut(
move |element| {
- element.as_mut().unwrap().as_widget_mut().overlay(
- &mut tree.children[0],
- layout,
- renderer,
- )
+ element
+ .as_mut()
+ .unwrap()
+ .as_widget_mut()
+ .overlay(
+ &mut tree.children[0],
+ layout,
+ renderer,
+ )
+ .map(|overlay| {
+ RefCell::new(Nested::new(overlay))
+ })
},
)
},
@@ -468,7 +476,7 @@ where
));
let has_overlay = overlay.0.as_ref().unwrap().with_overlay(|overlay| {
- overlay.as_ref().map(overlay::Element::position)
+ overlay.as_ref().map(|nested| nested.borrow().position())
});
has_overlay.map(|position| {
@@ -503,8 +511,8 @@ struct Inner<'a, 'b, Message, Renderer, Event, S> {
types: PhantomData<(Message, Event, S)>,
#[borrows(mut instance, mut tree)]
- #[covariant]
- overlay: Option<overlay::Element<'this, Event, Renderer>>,
+ #[not_covariant]
+ overlay: Option<RefCell<Nested<'this, Event, Renderer>>>,
}
struct OverlayInstance<'a, 'b, Message, Renderer, Event, S> {
@@ -516,7 +524,7 @@ impl<'a, 'b, Message, Renderer, Event, S>
{
fn with_overlay_maybe<T>(
&self,
- f: impl FnOnce(&overlay::Element<'_, Event, Renderer>) -> T,
+ f: impl FnOnce(&mut Nested<'_, Event, Renderer>) -> T,
) -> Option<T> {
self.overlay
.as_ref()
@@ -524,14 +532,14 @@ impl<'a, 'b, Message, Renderer, Event, S>
.0
.as_ref()
.unwrap()
- .borrow_overlay()
- .as_ref()
- .map(f)
+ .with_overlay(|overlay| {
+ overlay.as_ref().map(|nested| (f)(&mut nested.borrow_mut()))
+ })
}
fn with_overlay_mut_maybe<T>(
&mut self,
- f: impl FnOnce(&mut overlay::Element<'_, Event, Renderer>) -> T,
+ f: impl FnOnce(&mut Nested<'_, Event, Renderer>) -> T,
) -> Option<T> {
self.overlay
.as_mut()
@@ -539,7 +547,9 @@ impl<'a, 'b, Message, Renderer, Event, S>
.0
.as_mut()
.unwrap()
- .with_overlay_mut(|overlay| overlay.as_mut().map(f))
+ .with_overlay_mut(|overlay| {
+ overlay.as_mut().map(|nested| (f)(nested.get_mut()))
+ })
}
}
@@ -556,9 +566,7 @@ where
position: Point,
) -> layout::Node {
self.with_overlay_maybe(|overlay| {
- let translation = position - overlay.position();
-
- overlay.layout(renderer, bounds, translation)
+ overlay.layout(renderer, bounds, position)
})
.unwrap_or_default()
}
@@ -569,27 +577,22 @@ where
theme: &Renderer::Theme,
style: &renderer::Style,
layout: Layout<'_>,
- cursor_position: Point,
+ cursor: mouse::Cursor,
) {
let _ = self.with_overlay_maybe(|overlay| {
- overlay.draw(renderer, theme, style, layout, cursor_position);
+ overlay.draw(renderer, theme, style, layout, cursor);
});
}
fn mouse_interaction(
&self,
layout: Layout<'_>,
- cursor_position: Point,
+ cursor: mouse::Cursor,
viewport: &Rectangle,
renderer: &Renderer,
) -> mouse::Interaction {
self.with_overlay_maybe(|overlay| {
- overlay.mouse_interaction(
- layout,
- cursor_position,
- viewport,
- renderer,
- )
+ overlay.mouse_interaction(layout, cursor, viewport, renderer)
})
.unwrap_or_default()
}
@@ -598,7 +601,7 @@ where
&mut self,
event: core::Event,
layout: Layout<'_>,
- cursor_position: Point,
+ cursor: mouse::Cursor,
renderer: &Renderer,
clipboard: &mut dyn Clipboard,
shell: &mut Shell<'_, Message>,
@@ -611,7 +614,7 @@ where
overlay.on_event(
event,
layout,
- cursor_position,
+ cursor,
renderer,
clipboard,
&mut local_shell,
@@ -660,9 +663,14 @@ where
event_status
}
- fn is_over(&self, layout: Layout<'_>, cursor_position: Point) -> bool {
+ fn is_over(
+ &self,
+ layout: Layout<'_>,
+ renderer: &Renderer,
+ cursor_position: Point,
+ ) -> bool {
self.with_overlay_maybe(|overlay| {
- overlay.is_over(layout, cursor_position)
+ overlay.is_over(layout, renderer, cursor_position)
})
.unwrap_or_default()
}
diff --git a/widget/src/lazy/helpers.rs b/widget/src/lazy/helpers.rs
index be60bb78..8ca9cb86 100644
--- a/widget/src/lazy/helpers.rs
+++ b/widget/src/lazy/helpers.rs
@@ -4,6 +4,8 @@ use crate::lazy::{Lazy, Responsive};
use std::hash::Hash;
+/// Creates a new [`Lazy`] widget with the given data `Dependency` and a
+/// closure that can turn this data into a widget tree.
pub fn lazy<'a, Message, Renderer, Dependency, View>(
dependency: Dependency,
view: impl Fn(&Dependency) -> View + 'a,
@@ -29,6 +31,12 @@ where
component::view(component)
}
+/// Creates a new [`Responsive`] widget with a closure that produces its
+/// contents.
+///
+/// The `view` closure will be provided with the current [`Size`] of
+/// the [`Responsive`] widget and, therefore, can be used to build the
+/// contents of the widget in a responsive way.
pub fn responsive<'a, Message, Renderer>(
f: impl Fn(Size) -> Element<'a, Message, Renderer> + 'a,
) -> Responsive<'a, Message, Renderer>
diff --git a/widget/src/lazy/responsive.rs b/widget/src/lazy/responsive.rs
index b41d978b..07300857 100644
--- a/widget/src/lazy/responsive.rs
+++ b/widget/src/lazy/responsive.rs
@@ -9,6 +9,7 @@ use crate::core::{
self, Clipboard, Element, Length, Point, Rectangle, Shell, Size, Widget,
};
use crate::horizontal_space;
+use crate::runtime::overlay::Nested;
use ouroboros::self_referencing;
use std::cell::{RefCell, RefMut};
@@ -81,6 +82,7 @@ where
self.element = view(new_size);
self.size = new_size;
+ self.layout = None;
tree.diff(&self.element);
}
@@ -176,7 +178,7 @@ where
tree: &mut Tree,
event: Event,
layout: Layout<'_>,
- cursor_position: Point,
+ cursor: mouse::Cursor,
renderer: &Renderer,
clipboard: &mut dyn Clipboard,
shell: &mut Shell<'_, Message>,
@@ -197,7 +199,7 @@ where
tree,
event,
layout,
- cursor_position,
+ cursor,
renderer,
clipboard,
&mut local_shell,
@@ -221,7 +223,7 @@ where
theme: &Renderer::Theme,
style: &renderer::Style,
layout: Layout<'_>,
- cursor_position: Point,
+ cursor: mouse::Cursor,
viewport: &Rectangle,
) {
let state = tree.state.downcast_ref::<State>();
@@ -234,13 +236,7 @@ where
&self.view,
|tree, renderer, layout, element| {
element.as_widget().draw(
- tree,
- renderer,
- theme,
- style,
- layout,
- cursor_position,
- viewport,
+ tree, renderer, theme, style, layout, cursor, viewport,
)
},
)
@@ -250,7 +246,7 @@ where
&self,
tree: &Tree,
layout: Layout<'_>,
- cursor_position: Point,
+ cursor: mouse::Cursor,
viewport: &Rectangle,
renderer: &Renderer,
) -> mouse::Interaction {
@@ -263,13 +259,9 @@ where
layout,
&self.view,
|tree, renderer, layout, element| {
- element.as_widget().mouse_interaction(
- tree,
- layout,
- cursor_position,
- viewport,
- renderer,
- )
+ element
+ .as_widget()
+ .mouse_interaction(tree, layout, cursor, viewport, renderer)
},
)
}
@@ -307,13 +299,13 @@ where
element
.as_widget_mut()
.overlay(tree, content_layout, renderer)
+ .map(|overlay| RefCell::new(Nested::new(overlay)))
},
}
.build();
- let has_overlay = overlay.with_overlay(|overlay| {
- overlay.as_ref().map(overlay::Element::position)
- });
+ let has_overlay =
+ overlay.with_overlay_maybe(|overlay| overlay.position());
has_overlay
.map(|position| overlay::Element::new(position, Box::new(overlay)))
@@ -338,23 +330,27 @@ struct Overlay<'a, 'b, Message, Renderer> {
types: PhantomData<Message>,
#[borrows(mut content, mut tree)]
- #[covariant]
- overlay: Option<overlay::Element<'this, Message, Renderer>>,
+ #[not_covariant]
+ overlay: Option<RefCell<Nested<'this, Message, Renderer>>>,
}
impl<'a, 'b, Message, Renderer> Overlay<'a, 'b, Message, Renderer> {
fn with_overlay_maybe<T>(
&self,
- f: impl FnOnce(&overlay::Element<'_, Message, Renderer>) -> T,
+ f: impl FnOnce(&mut Nested<'_, Message, Renderer>) -> T,
) -> Option<T> {
- self.borrow_overlay().as_ref().map(f)
+ self.with_overlay(|overlay| {
+ overlay.as_ref().map(|nested| (f)(&mut nested.borrow_mut()))
+ })
}
fn with_overlay_mut_maybe<T>(
&mut self,
- f: impl FnOnce(&mut overlay::Element<'_, Message, Renderer>) -> T,
+ f: impl FnOnce(&mut Nested<'_, Message, Renderer>) -> T,
) -> Option<T> {
- self.with_overlay_mut(|overlay| overlay.as_mut().map(f))
+ self.with_overlay_mut(|overlay| {
+ overlay.as_mut().map(|nested| (f)(nested.get_mut()))
+ })
}
}
@@ -370,9 +366,7 @@ where
position: Point,
) -> layout::Node {
self.with_overlay_maybe(|overlay| {
- let translation = position - overlay.position();
-
- overlay.layout(renderer, bounds, translation)
+ overlay.layout(renderer, bounds, position)
})
.unwrap_or_default()
}
@@ -383,27 +377,22 @@ where
theme: &Renderer::Theme,
style: &renderer::Style,
layout: Layout<'_>,
- cursor_position: Point,
+ cursor: mouse::Cursor,
) {
let _ = self.with_overlay_maybe(|overlay| {
- overlay.draw(renderer, theme, style, layout, cursor_position);
+ overlay.draw(renderer, theme, style, layout, cursor);
});
}
fn mouse_interaction(
&self,
layout: Layout<'_>,
- cursor_position: Point,
+ cursor: mouse::Cursor,
viewport: &Rectangle,
renderer: &Renderer,
) -> mouse::Interaction {
self.with_overlay_maybe(|overlay| {
- overlay.mouse_interaction(
- layout,
- cursor_position,
- viewport,
- renderer,
- )
+ overlay.mouse_interaction(layout, cursor, viewport, renderer)
})
.unwrap_or_default()
}
@@ -412,27 +401,25 @@ where
&mut self,
event: Event,
layout: Layout<'_>,
- cursor_position: Point,
+ cursor: mouse::Cursor,
renderer: &Renderer,
clipboard: &mut dyn Clipboard,
shell: &mut Shell<'_, Message>,
) -> event::Status {
self.with_overlay_mut_maybe(|overlay| {
- overlay.on_event(
- event,
- layout,
- cursor_position,
- renderer,
- clipboard,
- shell,
- )
+ overlay.on_event(event, layout, cursor, renderer, clipboard, shell)
})
.unwrap_or(event::Status::Ignored)
}
- fn is_over(&self, layout: Layout<'_>, cursor_position: Point) -> bool {
+ fn is_over(
+ &self,
+ layout: Layout<'_>,
+ renderer: &Renderer,
+ cursor_position: Point,
+ ) -> bool {
self.with_overlay_maybe(|overlay| {
- overlay.is_over(layout, cursor_position)
+ overlay.is_over(layout, renderer, cursor_position)
})
.unwrap_or_default()
}
diff --git a/widget/src/lib.rs b/widget/src/lib.rs
index 904f62ad..9da13f9b 100644
--- a/widget/src/lib.rs
+++ b/widget/src/lib.rs
@@ -4,7 +4,7 @@
)]
#![deny(
missing_debug_implementations,
- //missing_docs,
+ missing_docs,
unused_results,
clippy::extra_unused_lifetimes,
clippy::from_over_into,
@@ -14,6 +14,7 @@
)]
#![forbid(unsafe_code, rust_2018_idioms)]
#![allow(clippy::inherent_to_string, clippy::type_complexity)]
+#![cfg_attr(docsrs, feature(doc_auto_cfg))]
pub use iced_renderer as renderer;
pub use iced_renderer::graphics;
pub use iced_runtime as runtime;
diff --git a/widget/src/mouse_area.rs b/widget/src/mouse_area.rs
index 0232c494..da7dc88f 100644
--- a/widget/src/mouse_area.rs
+++ b/widget/src/mouse_area.rs
@@ -8,7 +8,7 @@ use crate::core::renderer;
use crate::core::touch;
use crate::core::widget::{tree, Operation, Tree};
use crate::core::{
- Clipboard, Element, Layout, Length, Point, Rectangle, Shell, Widget,
+ Clipboard, Element, Layout, Length, Rectangle, Shell, Widget,
};
/// Emit messages on mouse events.
@@ -146,7 +146,7 @@ where
tree: &mut Tree,
event: Event,
layout: Layout<'_>,
- cursor_position: Point,
+ cursor: mouse::Cursor,
renderer: &Renderer,
clipboard: &mut dyn Clipboard,
shell: &mut Shell<'_, Message>,
@@ -155,7 +155,7 @@ where
&mut tree.children[0],
event.clone(),
layout,
- cursor_position,
+ cursor,
renderer,
clipboard,
shell,
@@ -163,21 +163,21 @@ where
return event::Status::Captured;
}
- update(self, &event, layout, cursor_position, shell)
+ update(self, &event, layout, cursor, shell)
}
fn mouse_interaction(
&self,
tree: &Tree,
layout: Layout<'_>,
- cursor_position: Point,
+ cursor: mouse::Cursor,
viewport: &Rectangle,
renderer: &Renderer,
) -> mouse::Interaction {
self.content.as_widget().mouse_interaction(
&tree.children[0],
layout,
- cursor_position,
+ cursor,
viewport,
renderer,
)
@@ -190,7 +190,7 @@ where
theme: &Renderer::Theme,
renderer_style: &renderer::Style,
layout: Layout<'_>,
- cursor_position: Point,
+ cursor: mouse::Cursor,
viewport: &Rectangle,
) {
self.content.as_widget().draw(
@@ -199,7 +199,7 @@ where
theme,
renderer_style,
layout,
- cursor_position,
+ cursor,
viewport,
);
}
@@ -237,10 +237,10 @@ fn update<Message: Clone, Renderer>(
widget: &mut MouseArea<'_, Message, Renderer>,
event: &Event,
layout: Layout<'_>,
- cursor_position: Point,
+ cursor: mouse::Cursor,
shell: &mut Shell<'_, Message>,
) -> event::Status {
- if !layout.bounds().contains(cursor_position) {
+ if !cursor.is_over(layout.bounds()) {
return event::Status::Ignored;
}
diff --git a/widget/src/overlay.rs b/widget/src/overlay.rs
index b9a0e3e0..bc0ed744 100644
--- a/widget/src/overlay.rs
+++ b/widget/src/overlay.rs
@@ -1 +1,2 @@
+//! Display interactive elements on top of other widgets.
pub mod menu;
diff --git a/widget/src/overlay/menu.rs b/widget/src/overlay/menu.rs
index 0acc6f79..ccf4dfb5 100644
--- a/widget/src/overlay/menu.rs
+++ b/widget/src/overlay/menu.rs
@@ -19,7 +19,7 @@ pub use iced_style::menu::{Appearance, StyleSheet};
/// A list of selectable options.
#[allow(missing_debug_implementations)]
-pub struct Menu<'a, T, Renderer = crate::Renderer>
+pub struct Menu<'a, T, Message, Renderer = crate::Renderer>
where
Renderer: text::Renderer,
Renderer::Theme: StyleSheet,
@@ -27,7 +27,7 @@ where
state: &'a mut State,
options: &'a [T],
hovered_option: &'a mut Option<usize>,
- last_selection: &'a mut Option<T>,
+ on_selected: Box<dyn FnMut(T) -> Message + 'a>,
width: f32,
padding: Padding,
text_size: Option<f32>,
@@ -37,9 +37,10 @@ where
style: <Renderer::Theme as StyleSheet>::Style,
}
-impl<'a, T, Renderer> Menu<'a, T, Renderer>
+impl<'a, T, Message, Renderer> Menu<'a, T, Message, Renderer>
where
T: ToString + Clone,
+ Message: 'a,
Renderer: text::Renderer + 'a,
Renderer::Theme:
StyleSheet + container::StyleSheet + scrollable::StyleSheet,
@@ -50,13 +51,13 @@ where
state: &'a mut State,
options: &'a [T],
hovered_option: &'a mut Option<usize>,
- last_selection: &'a mut Option<T>,
+ on_selected: impl FnMut(T) -> Message + 'a,
) -> Self {
Menu {
state,
options,
hovered_option,
- last_selection,
+ on_selected: Box::new(on_selected),
width: 0.0,
padding: Padding::ZERO,
text_size: None,
@@ -121,7 +122,7 @@ where
/// The `target_height` will be used to display the menu either on top
/// of the target or under it, depending on the screen position and the
/// dimensions of the [`Menu`].
- pub fn overlay<Message: 'a>(
+ pub fn overlay(
self,
position: Point,
target_height: f32,
@@ -174,7 +175,10 @@ where
Renderer::Theme:
StyleSheet + container::StyleSheet + scrollable::StyleSheet,
{
- pub fn new<T>(menu: Menu<'a, T, Renderer>, target_height: f32) -> Self
+ pub fn new<T>(
+ menu: Menu<'a, T, Message, Renderer>,
+ target_height: f32,
+ ) -> Self
where
T: Clone + ToString,
{
@@ -182,7 +186,7 @@ where
state,
options,
hovered_option,
- last_selection,
+ on_selected,
width,
padding,
font,
@@ -195,7 +199,7 @@ where
let container = Container::new(Scrollable::new(List {
options,
hovered_option,
- last_selection,
+ on_selected,
font,
text_size,
text_line_height,
@@ -259,36 +263,25 @@ where
&mut self,
event: Event,
layout: Layout<'_>,
- cursor_position: Point,
+ cursor: mouse::Cursor,
renderer: &Renderer,
clipboard: &mut dyn Clipboard,
shell: &mut Shell<'_, Message>,
) -> event::Status {
self.container.on_event(
- self.state,
- event,
- layout,
- cursor_position,
- renderer,
- clipboard,
- shell,
+ self.state, event, layout, cursor, renderer, clipboard, shell,
)
}
fn mouse_interaction(
&self,
layout: Layout<'_>,
- cursor_position: Point,
+ cursor: mouse::Cursor,
viewport: &Rectangle,
renderer: &Renderer,
) -> mouse::Interaction {
- self.container.mouse_interaction(
- self.state,
- layout,
- cursor_position,
- viewport,
- renderer,
- )
+ self.container
+ .mouse_interaction(self.state, layout, cursor, viewport, renderer)
}
fn draw(
@@ -297,7 +290,7 @@ where
theme: &Renderer::Theme,
style: &renderer::Style,
layout: Layout<'_>,
- cursor_position: Point,
+ cursor: mouse::Cursor,
) {
let appearance = theme.appearance(&self.style);
let bounds = layout.bounds();
@@ -307,31 +300,24 @@ where
bounds,
border_color: appearance.border_color,
border_width: appearance.border_width,
- border_radius: appearance.border_radius.into(),
+ border_radius: appearance.border_radius,
},
appearance.background,
);
- self.container.draw(
- self.state,
- renderer,
- theme,
- style,
- layout,
- cursor_position,
- &bounds,
- );
+ self.container
+ .draw(self.state, renderer, theme, style, layout, cursor, &bounds);
}
}
-struct List<'a, T, Renderer>
+struct List<'a, T, Message, Renderer>
where
Renderer: text::Renderer,
Renderer::Theme: StyleSheet,
{
options: &'a [T],
hovered_option: &'a mut Option<usize>,
- last_selection: &'a mut Option<T>,
+ on_selected: Box<dyn FnMut(T) -> Message + 'a>,
padding: Padding,
text_size: Option<f32>,
text_line_height: text::LineHeight,
@@ -341,7 +327,7 @@ where
}
impl<'a, T, Message, Renderer> Widget<Message, Renderer>
- for List<'a, T, Renderer>
+ for List<'a, T, Message, Renderer>
where
T: Clone + ToString,
Renderer: text::Renderer,
@@ -387,27 +373,26 @@ where
_state: &mut Tree,
event: Event,
layout: Layout<'_>,
- cursor_position: Point,
+ cursor: mouse::Cursor,
renderer: &Renderer,
_clipboard: &mut dyn Clipboard,
- _shell: &mut Shell<'_, Message>,
+ shell: &mut Shell<'_, Message>,
) -> event::Status {
match event {
Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) => {
- let bounds = layout.bounds();
-
- if bounds.contains(cursor_position) {
+ if cursor.is_over(layout.bounds()) {
if let Some(index) = *self.hovered_option {
if let Some(option) = self.options.get(index) {
- *self.last_selection = Some(option.clone());
+ shell.publish((self.on_selected)(option.clone()));
+ return event::Status::Captured;
}
}
}
}
Event::Mouse(mouse::Event::CursorMoved { .. }) => {
- let bounds = layout.bounds();
-
- if bounds.contains(cursor_position) {
+ if let Some(cursor_position) =
+ cursor.position_in(layout.bounds())
+ {
let text_size = self
.text_size
.unwrap_or_else(|| renderer.default_size());
@@ -416,16 +401,14 @@ where
self.text_line_height.to_absolute(Pixels(text_size)),
) + self.padding.vertical();
- *self.hovered_option = Some(
- ((cursor_position.y - bounds.y) / option_height)
- as usize,
- );
+ *self.hovered_option =
+ Some((cursor_position.y / option_height) as usize);
}
}
Event::Touch(touch::Event::FingerPressed { .. }) => {
- let bounds = layout.bounds();
-
- if bounds.contains(cursor_position) {
+ if let Some(cursor_position) =
+ cursor.position_in(layout.bounds())
+ {
let text_size = self
.text_size
.unwrap_or_else(|| renderer.default_size());
@@ -434,14 +417,13 @@ where
self.text_line_height.to_absolute(Pixels(text_size)),
) + self.padding.vertical();
- *self.hovered_option = Some(
- ((cursor_position.y - bounds.y) / option_height)
- as usize,
- );
+ *self.hovered_option =
+ Some((cursor_position.y / option_height) as usize);
if let Some(index) = *self.hovered_option {
if let Some(option) = self.options.get(index) {
- *self.last_selection = Some(option.clone());
+ shell.publish((self.on_selected)(option.clone()));
+ return event::Status::Captured;
}
}
}
@@ -456,11 +438,11 @@ where
&self,
_state: &Tree,
layout: Layout<'_>,
- cursor_position: Point,
+ cursor: mouse::Cursor,
_viewport: &Rectangle,
_renderer: &Renderer,
) -> mouse::Interaction {
- let is_mouse_over = layout.bounds().contains(cursor_position);
+ let is_mouse_over = cursor.is_over(layout.bounds());
if is_mouse_over {
mouse::Interaction::Pointer
@@ -476,7 +458,7 @@ where
theme: &Renderer::Theme,
_style: &renderer::Style,
layout: Layout<'_>,
- _cursor_position: Point,
+ _cursor: mouse::Cursor,
viewport: &Rectangle,
) {
let appearance = theme.appearance(&self.style);
@@ -515,7 +497,7 @@ where
},
border_color: Color::TRANSPARENT,
border_width: 0.0,
- border_radius: appearance.border_radius.into(),
+ border_radius: appearance.border_radius,
},
appearance.selected_background,
);
@@ -545,7 +527,7 @@ where
}
}
-impl<'a, T, Message, Renderer> From<List<'a, T, Renderer>>
+impl<'a, T, Message, Renderer> From<List<'a, T, Message, Renderer>>
for Element<'a, Message, Renderer>
where
T: ToString + Clone,
@@ -553,7 +535,7 @@ where
Renderer: 'a + text::Renderer,
Renderer::Theme: StyleSheet,
{
- fn from(list: List<'a, T, Renderer>) -> Self {
+ fn from(list: List<'a, T, Message, Renderer>) -> Self {
Element::new(list)
}
}
diff --git a/widget/src/pane_grid.rs b/widget/src/pane_grid.rs
index 67145e8e..040d6bb3 100644
--- a/widget/src/pane_grid.rs
+++ b/widget/src/pane_grid.rs
@@ -30,7 +30,7 @@ pub use split::Split;
pub use state::State;
pub use title_bar::TitleBar;
-pub use crate::style::pane_grid::{Line, StyleSheet};
+pub use crate::style::pane_grid::{Appearance, Line, StyleSheet};
use crate::container;
use crate::core::event::{self, Event};
@@ -313,7 +313,7 @@ where
tree: &mut Tree,
event: Event,
layout: Layout<'_>,
- cursor_position: Point,
+ cursor: mouse::Cursor,
renderer: &Renderer,
clipboard: &mut dyn Clipboard,
shell: &mut Shell<'_, Message>,
@@ -331,7 +331,7 @@ where
self.contents.layout(),
&event,
layout,
- cursor_position,
+ cursor,
shell,
self.spacing,
self.contents.iter(),
@@ -353,7 +353,7 @@ where
tree,
event.clone(),
layout,
- cursor_position,
+ cursor,
renderer,
clipboard,
shell,
@@ -367,7 +367,7 @@ where
&self,
tree: &Tree,
layout: Layout<'_>,
- cursor_position: Point,
+ cursor: mouse::Cursor,
viewport: &Rectangle,
renderer: &Renderer,
) -> mouse::Interaction {
@@ -375,7 +375,7 @@ where
tree.state.downcast_ref(),
self.contents.layout(),
layout,
- cursor_position,
+ cursor,
self.spacing,
self.on_resize.as_ref().map(|(leeway, _)| *leeway),
)
@@ -388,7 +388,7 @@ where
content.mouse_interaction(
tree,
layout,
- cursor_position,
+ cursor,
viewport,
renderer,
self.drag_enabled(),
@@ -406,14 +406,14 @@ where
theme: &Renderer::Theme,
style: &renderer::Style,
layout: Layout<'_>,
- cursor_position: Point,
+ cursor: mouse::Cursor,
viewport: &Rectangle,
) {
draw(
tree.state.downcast_ref(),
self.contents.layout(),
layout,
- cursor_position,
+ cursor,
renderer,
theme,
style,
@@ -425,20 +425,9 @@ where
.iter()
.zip(&tree.children)
.map(|((pane, content), tree)| (pane, (content, tree))),
- |(content, tree),
- renderer,
- style,
- layout,
- cursor_position,
- rectangle| {
+ |(content, tree), renderer, style, layout, cursor, rectangle| {
content.draw(
- tree,
- renderer,
- theme,
- style,
- layout,
- cursor_position,
- rectangle,
+ tree, renderer, theme, style, layout, cursor, rectangle,
);
},
)
@@ -520,7 +509,7 @@ pub fn update<'a, Message, T: Draggable>(
node: &Node,
event: &Event,
layout: Layout<'_>,
- cursor_position: Point,
+ cursor: mouse::Cursor,
shell: &mut Shell<'_, Message>,
spacing: f32,
contents: impl Iterator<Item = (Pane, T)>,
@@ -535,7 +524,7 @@ pub fn update<'a, Message, T: Draggable>(
| Event::Touch(touch::Event::FingerPressed { .. }) => {
let bounds = layout.bounds();
- if bounds.contains(cursor_position) {
+ if let Some(cursor_position) = cursor.position_over(bounds) {
event_status = event::Status::Captured;
match on_resize {
@@ -592,15 +581,24 @@ 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 = contents
- .zip(layout.children())
- .filter(|(_, layout)| {
- layout.bounds().contains(cursor_position)
+ let dropped_region =
+ cursor.position().and_then(|cursor_position| {
+ contents
+ .zip(layout.children())
+ .filter_map(|(target, layout)| {
+ layout_region(layout, cursor_position)
+ .map(|region| (target, region))
+ })
+ .next()
});
- let event = match dropped_region.next() {
- Some(((target, _), _)) if pane != target => {
- DragEvent::Dropped { pane, target }
+ let event = match dropped_region {
+ Some(((target, _), region)) if pane != target => {
+ DragEvent::Dropped {
+ pane,
+ target,
+ region,
+ }
}
_ => DragEvent::Canceled { pane },
};
@@ -629,24 +627,32 @@ pub fn update<'a, Message, T: Draggable>(
);
if let Some((axis, rectangle, _)) = splits.get(&split) {
- let ratio = match axis {
- Axis::Horizontal => {
- let position =
- cursor_position.y - bounds.y - rectangle.y;
-
- (position / rectangle.height).clamp(0.1, 0.9)
- }
- Axis::Vertical => {
- let position =
- cursor_position.x - bounds.x - rectangle.x;
-
- (position / rectangle.width).clamp(0.1, 0.9)
- }
- };
-
- shell.publish(on_resize(ResizeEvent { split, ratio }));
-
- event_status = event::Status::Captured;
+ if let Some(cursor_position) = cursor.position() {
+ let ratio = match axis {
+ Axis::Horizontal => {
+ let position = cursor_position.y
+ - bounds.y
+ - rectangle.y;
+
+ (position / rectangle.height)
+ .clamp(0.1, 0.9)
+ }
+ Axis::Vertical => {
+ let position = cursor_position.x
+ - bounds.x
+ - rectangle.x;
+
+ (position / rectangle.width).clamp(0.1, 0.9)
+ }
+ };
+
+ shell.publish(on_resize(ResizeEvent {
+ split,
+ ratio,
+ }));
+
+ event_status = event::Status::Captured;
+ }
}
}
}
@@ -657,6 +663,28 @@ pub fn update<'a, Message, T: Draggable>(
event_status
}
+fn layout_region(layout: Layout<'_>, cursor_position: Point) -> Option<Region> {
+ let bounds = layout.bounds();
+
+ if !bounds.contains(cursor_position) {
+ return None;
+ }
+
+ let region = if cursor_position.x < (bounds.x + bounds.width / 3.0) {
+ Region::Left
+ } else if cursor_position.x > (bounds.x + 2.0 * bounds.width / 3.0) {
+ Region::Right
+ } else if cursor_position.y < (bounds.y + bounds.height / 3.0) {
+ Region::Top
+ } else if cursor_position.y > (bounds.y + 2.0 * bounds.height / 3.0) {
+ Region::Bottom
+ } else {
+ Region::Center
+ };
+
+ Some(region)
+}
+
fn click_pane<'a, Message, T>(
action: &mut state::Action,
layout: Layout<'_>,
@@ -697,7 +725,7 @@ pub fn mouse_interaction(
action: &state::Action,
node: &Node,
layout: Layout<'_>,
- cursor_position: Point,
+ cursor: mouse::Cursor,
spacing: f32,
resize_leeway: Option<f32>,
) -> Option<mouse::Interaction> {
@@ -708,6 +736,7 @@ pub fn mouse_interaction(
let resize_axis =
action.picked_split().map(|(_, axis)| axis).or_else(|| {
resize_leeway.and_then(|leeway| {
+ let cursor_position = cursor.position()?;
let bounds = layout.bounds();
let splits = node.split_regions(spacing, bounds.size());
@@ -737,7 +766,7 @@ pub fn draw<Renderer, T>(
action: &state::Action,
node: &Node,
layout: Layout<'_>,
- cursor_position: Point,
+ cursor: mouse::Cursor,
renderer: &mut Renderer,
theme: &Renderer::Theme,
default_style: &renderer::Style,
@@ -751,7 +780,7 @@ pub fn draw<Renderer, T>(
&mut Renderer,
&renderer::Style,
Layout<'_>,
- Point,
+ mouse::Cursor,
&Rectangle,
),
) where
@@ -775,6 +804,7 @@ pub fn draw<Renderer, T>(
})
.or_else(|| match resize_leeway {
Some(leeway) => {
+ let cursor_position = cursor.position()?;
let bounds = layout.bounds();
let relative_cursor = Point::new(
@@ -795,12 +825,10 @@ pub fn draw<Renderer, T>(
None => None,
});
- let pane_cursor_position = if picked_pane.is_some() {
- // TODO: Remove once cursor availability is encoded in the type
- // system
- Point::new(-1.0, -1.0)
+ let pane_cursor = if picked_pane.is_some() {
+ mouse::Cursor::Unavailable
} else {
- cursor_position
+ cursor
};
let mut render_picked_pane = None;
@@ -810,13 +838,45 @@ pub fn draw<Renderer, T>(
Some((dragging, origin)) if id == dragging => {
render_picked_pane = Some((pane, origin, layout));
}
+ Some((dragging, _)) if id != dragging => {
+ draw_pane(
+ pane,
+ renderer,
+ default_style,
+ layout,
+ pane_cursor,
+ viewport,
+ );
+
+ if picked_pane.is_some() {
+ if let Some(region) =
+ cursor.position().and_then(|cursor_position| {
+ layout_region(layout, cursor_position)
+ })
+ {
+ let bounds = layout_region_bounds(layout, region);
+ let hovered_region_style = theme.hovered_region(style);
+
+ renderer.fill_quad(
+ renderer::Quad {
+ bounds,
+ border_radius: hovered_region_style
+ .border_radius,
+ border_width: hovered_region_style.border_width,
+ border_color: hovered_region_style.border_color,
+ },
+ theme.hovered_region(style).background,
+ );
+ }
+ }
+ }
_ => {
draw_pane(
pane,
renderer,
default_style,
layout,
- pane_cursor_position,
+ pane_cursor,
viewport,
);
}
@@ -825,25 +885,27 @@ pub fn draw<Renderer, T>(
// Render picked pane last
if let Some((pane, origin, layout)) = render_picked_pane {
- let bounds = layout.bounds();
-
- renderer.with_translation(
- cursor_position
- - Point::new(bounds.x + origin.x, bounds.y + origin.y),
- |renderer| {
- renderer.with_layer(bounds, |renderer| {
- draw_pane(
- pane,
- renderer,
- default_style,
- layout,
- pane_cursor_position,
- viewport,
- );
- });
- },
- );
- };
+ if let Some(cursor_position) = cursor.position() {
+ let bounds = layout.bounds();
+
+ renderer.with_translation(
+ cursor_position
+ - Point::new(bounds.x + origin.x, bounds.y + origin.y),
+ |renderer| {
+ renderer.with_layer(bounds, |renderer| {
+ draw_pane(
+ pane,
+ renderer,
+ default_style,
+ layout,
+ pane_cursor,
+ viewport,
+ );
+ });
+ },
+ );
+ }
+ }
if let Some((axis, split_region, is_picked)) = picked_split {
let highlight = if is_picked {
@@ -884,6 +946,32 @@ pub fn draw<Renderer, T>(
}
}
+fn layout_region_bounds(layout: Layout<'_>, region: Region) -> Rectangle {
+ let bounds = layout.bounds();
+
+ match region {
+ Region::Center => bounds,
+ Region::Top => Rectangle {
+ height: bounds.height / 2.0,
+ ..bounds
+ },
+ Region::Left => Rectangle {
+ width: bounds.width / 2.0,
+ ..bounds
+ },
+ Region::Right => Rectangle {
+ x: bounds.x + bounds.width / 2.0,
+ width: bounds.width / 2.0,
+ ..bounds
+ },
+ Region::Bottom => Rectangle {
+ y: bounds.y + bounds.height / 2.0,
+ height: bounds.height / 2.0,
+ ..bounds
+ },
+ }
+}
+
/// An event produced during a drag and drop interaction of a [`PaneGrid`].
#[derive(Debug, Clone, Copy)]
pub enum DragEvent {
@@ -900,6 +988,9 @@ pub enum DragEvent {
/// The [`Pane`] where the picked one was dropped on.
target: Pane,
+
+ /// The [`Region`] of the target [`Pane`] where the picked one was dropped on.
+ region: Region,
},
/// A [`Pane`] was picked and then dropped outside of other [`Pane`]
@@ -910,6 +1001,22 @@ pub enum DragEvent {
},
}
+/// The region of a [`Pane`].
+#[derive(Debug, Clone, Copy, Default)]
+pub enum Region {
+ /// Center region.
+ #[default]
+ Center,
+ /// Top region.
+ Top,
+ /// Left region.
+ Left,
+ /// Right region.
+ Right,
+ /// Bottom region.
+ Bottom,
+}
+
/// An event produced during a resize interaction of a [`PaneGrid`].
#[derive(Debug, Clone, Copy)]
pub struct ResizeEvent {
diff --git a/widget/src/pane_grid/content.rs b/widget/src/pane_grid/content.rs
index 035ef05b..c28ae6e3 100644
--- a/widget/src/pane_grid/content.rs
+++ b/widget/src/pane_grid/content.rs
@@ -95,7 +95,7 @@ where
theme: &Renderer::Theme,
style: &renderer::Style,
layout: Layout<'_>,
- cursor_position: Point,
+ cursor: mouse::Cursor,
viewport: &Rectangle,
) {
use container::StyleSheet;
@@ -113,7 +113,7 @@ where
let title_bar_layout = children.next().unwrap();
let body_layout = children.next().unwrap();
- let show_controls = bounds.contains(cursor_position);
+ let show_controls = cursor.is_over(bounds);
self.body.as_widget().draw(
&tree.children[0],
@@ -121,7 +121,7 @@ where
theme,
style,
body_layout,
- cursor_position,
+ cursor,
viewport,
);
@@ -131,7 +131,7 @@ where
theme,
style,
title_bar_layout,
- cursor_position,
+ cursor,
viewport,
show_controls,
);
@@ -142,7 +142,7 @@ where
theme,
style,
layout,
- cursor_position,
+ cursor,
viewport,
);
}
@@ -218,7 +218,7 @@ where
tree: &mut Tree,
event: Event,
layout: Layout<'_>,
- cursor_position: Point,
+ cursor: mouse::Cursor,
renderer: &Renderer,
clipboard: &mut dyn Clipboard,
shell: &mut Shell<'_, Message>,
@@ -233,7 +233,7 @@ where
&mut tree.children[1],
event.clone(),
children.next().unwrap(),
- cursor_position,
+ cursor,
renderer,
clipboard,
shell,
@@ -251,7 +251,7 @@ where
&mut tree.children[0],
event,
body_layout,
- cursor_position,
+ cursor,
renderer,
clipboard,
shell,
@@ -265,42 +265,48 @@ where
&self,
tree: &Tree,
layout: Layout<'_>,
- cursor_position: Point,
+ cursor: mouse::Cursor,
viewport: &Rectangle,
renderer: &Renderer,
drag_enabled: bool,
) -> mouse::Interaction {
- let (body_layout, title_bar_interaction) =
- if let Some(title_bar) = &self.title_bar {
- let mut children = layout.children();
- let title_bar_layout = children.next().unwrap();
-
- let is_over_pick_area = title_bar
- .is_over_pick_area(title_bar_layout, cursor_position);
-
- if is_over_pick_area && drag_enabled {
- return mouse::Interaction::Grab;
- }
-
- let mouse_interaction = title_bar.mouse_interaction(
- &tree.children[1],
- title_bar_layout,
- cursor_position,
- viewport,
- renderer,
- );
+ let (body_layout, title_bar_interaction) = if let Some(title_bar) =
+ &self.title_bar
+ {
+ let mut children = layout.children();
+ let title_bar_layout = children.next().unwrap();
+
+ let is_over_pick_area = cursor
+ .position()
+ .map(|cursor_position| {
+ title_bar
+ .is_over_pick_area(title_bar_layout, cursor_position)
+ })
+ .unwrap_or_default();
+
+ if is_over_pick_area && drag_enabled {
+ return mouse::Interaction::Grab;
+ }
- (children.next().unwrap(), mouse_interaction)
- } else {
- (layout, mouse::Interaction::default())
- };
+ let mouse_interaction = title_bar.mouse_interaction(
+ &tree.children[1],
+ title_bar_layout,
+ cursor,
+ viewport,
+ renderer,
+ );
+
+ (children.next().unwrap(), mouse_interaction)
+ } else {
+ (layout, mouse::Interaction::default())
+ };
self.body
.as_widget()
.mouse_interaction(
&tree.children[0],
body_layout,
- cursor_position,
+ cursor,
viewport,
renderer,
)
diff --git a/widget/src/pane_grid/draggable.rs b/widget/src/pane_grid/draggable.rs
index a9274dad..9d31feb5 100644
--- a/widget/src/pane_grid/draggable.rs
+++ b/widget/src/pane_grid/draggable.rs
@@ -4,9 +4,5 @@ use crate::core::{Layout, Point};
pub trait Draggable {
/// Returns whether the [`Draggable`] with the given [`Layout`] can be picked
/// at the provided cursor position.
- fn can_be_dragged_at(
- &self,
- layout: Layout<'_>,
- cursor_position: Point,
- ) -> bool;
+ fn can_be_dragged_at(&self, layout: Layout<'_>, cursor: Point) -> bool;
}
diff --git a/widget/src/pane_grid/state.rs b/widget/src/pane_grid/state.rs
index a6e2ec7f..1f034ca3 100644
--- a/widget/src/pane_grid/state.rs
+++ b/widget/src/pane_grid/state.rs
@@ -2,7 +2,9 @@
//!
//! [`PaneGrid`]: crate::widget::PaneGrid
use crate::core::{Point, Size};
-use crate::pane_grid::{Axis, Configuration, Direction, Node, Pane, Split};
+use crate::pane_grid::{
+ Axis, Configuration, Direction, Node, Pane, Region, Split,
+};
use std::collections::HashMap;
@@ -165,6 +167,43 @@ impl<T> State<T> {
Some((new_pane, new_split))
}
+ /// Split a target [`Pane`] with a given [`Pane`] on a given [`Region`].
+ ///
+ /// Panes will be swapped by default for [`Region::Center`].
+ pub fn split_with(&mut self, target: &Pane, pane: &Pane, region: Region) {
+ match region {
+ Region::Center => self.swap(pane, target),
+ Region::Top => {
+ self.split_and_swap(Axis::Horizontal, target, pane, true)
+ }
+ Region::Bottom => {
+ self.split_and_swap(Axis::Horizontal, target, pane, false)
+ }
+ Region::Left => {
+ self.split_and_swap(Axis::Vertical, target, pane, true)
+ }
+ Region::Right => {
+ self.split_and_swap(Axis::Vertical, target, pane, false)
+ }
+ }
+ }
+
+ fn split_and_swap(
+ &mut self,
+ axis: Axis,
+ target: &Pane,
+ pane: &Pane,
+ swap: bool,
+ ) {
+ if let Some((state, _)) = self.close(pane) {
+ if let Some((new_pane, _)) = self.split(axis, target, state) {
+ if swap {
+ self.swap(target, &new_pane);
+ }
+ }
+ }
+ }
+
/// Swaps the position of the provided panes in the [`State`].
///
/// If you want to swap panes on drag and drop in your [`PaneGrid`], you
diff --git a/widget/src/pane_grid/title_bar.rs b/widget/src/pane_grid/title_bar.rs
index 2129937b..2fe79f80 100644
--- a/widget/src/pane_grid/title_bar.rs
+++ b/widget/src/pane_grid/title_bar.rs
@@ -122,7 +122,7 @@ where
theme: &Renderer::Theme,
inherited_style: &renderer::Style,
layout: Layout<'_>,
- cursor_position: Point,
+ cursor: mouse::Cursor,
viewport: &Rectangle,
show_controls: bool,
) {
@@ -158,7 +158,7 @@ where
theme,
&inherited_style,
controls_layout,
- cursor_position,
+ cursor,
viewport,
);
}
@@ -171,7 +171,7 @@ where
theme,
&inherited_style,
title_layout,
- cursor_position,
+ cursor,
viewport,
);
}
@@ -300,7 +300,7 @@ where
tree: &mut Tree,
event: Event,
layout: Layout<'_>,
- cursor_position: Point,
+ cursor: mouse::Cursor,
renderer: &Renderer,
clipboard: &mut dyn Clipboard,
shell: &mut Shell<'_, Message>,
@@ -324,7 +324,7 @@ where
&mut tree.children[1],
event.clone(),
controls_layout,
- cursor_position,
+ cursor,
renderer,
clipboard,
shell,
@@ -338,7 +338,7 @@ where
&mut tree.children[0],
event,
title_layout,
- cursor_position,
+ cursor,
renderer,
clipboard,
shell,
@@ -354,7 +354,7 @@ where
&self,
tree: &Tree,
layout: Layout<'_>,
- cursor_position: Point,
+ cursor: mouse::Cursor,
viewport: &Rectangle,
renderer: &Renderer,
) -> mouse::Interaction {
@@ -367,7 +367,7 @@ where
let title_interaction = self.content.as_widget().mouse_interaction(
&tree.children[0],
title_layout,
- cursor_position,
+ cursor,
viewport,
renderer,
);
@@ -377,7 +377,7 @@ where
let controls_interaction = controls.as_widget().mouse_interaction(
&tree.children[1],
controls_layout,
- cursor_position,
+ cursor,
viewport,
renderer,
);
diff --git a/widget/src/pick_list.rs b/widget/src/pick_list.rs
index 8c445dda..832aae6b 100644
--- a/widget/src/pick_list.rs
+++ b/widget/src/pick_list.rs
@@ -11,8 +11,8 @@ use crate::core::text::{self, Text};
use crate::core::touch;
use crate::core::widget::tree::{self, Tree};
use crate::core::{
- Clipboard, Element, Layout, Length, Padding, Pixels, Point, Rectangle,
- Shell, Size, Widget,
+ Clipboard, Element, Layout, Length, Padding, Pixels, Rectangle, Shell,
+ Size, Widget,
};
use crate::overlay::menu::{self, Menu};
use crate::scrollable;
@@ -157,11 +157,11 @@ where
From<<Renderer::Theme as StyleSheet>::Style>,
{
fn tag(&self) -> tree::Tag {
- tree::Tag::of::<State<T>>()
+ tree::Tag::of::<State>()
}
fn state(&self) -> tree::State {
- tree::State::new(State::<T>::new())
+ tree::State::new(State::new())
}
fn width(&self) -> Length {
@@ -196,7 +196,7 @@ where
tree: &mut Tree,
event: Event,
layout: Layout<'_>,
- cursor_position: Point,
+ cursor: mouse::Cursor,
_renderer: &Renderer,
_clipboard: &mut dyn Clipboard,
shell: &mut Shell<'_, Message>,
@@ -204,12 +204,12 @@ where
update(
event,
layout,
- cursor_position,
+ cursor,
shell,
self.on_selected.as_ref(),
self.selected.as_ref(),
&self.options,
- || tree.state.downcast_mut::<State<T>>(),
+ || tree.state.downcast_mut::<State>(),
)
}
@@ -217,11 +217,11 @@ where
&self,
_tree: &Tree,
layout: Layout<'_>,
- cursor_position: Point,
+ cursor: mouse::Cursor,
_viewport: &Rectangle,
_renderer: &Renderer,
) -> mouse::Interaction {
- mouse_interaction(layout, cursor_position)
+ mouse_interaction(layout, cursor)
}
fn draw(
@@ -231,7 +231,7 @@ where
theme: &Renderer::Theme,
_style: &renderer::Style,
layout: Layout<'_>,
- cursor_position: Point,
+ cursor: mouse::Cursor,
_viewport: &Rectangle,
) {
let font = self.font.unwrap_or_else(|| renderer.default_font());
@@ -239,7 +239,7 @@ where
renderer,
theme,
layout,
- cursor_position,
+ cursor,
self.padding,
self.text_size,
self.text_line_height,
@@ -249,7 +249,7 @@ where
self.selected.as_ref(),
&self.handle,
&self.style,
- || tree.state.downcast_ref::<State<T>>(),
+ || tree.state.downcast_ref::<State>(),
)
}
@@ -259,7 +259,7 @@ where
layout: Layout<'_>,
renderer: &Renderer,
) -> Option<overlay::Element<'b, Message, Renderer>> {
- let state = tree.state.downcast_mut::<State<T>>();
+ let state = tree.state.downcast_mut::<State>();
overlay(
layout,
@@ -269,6 +269,7 @@ where
self.text_shaping,
self.font.unwrap_or_else(|| renderer.default_font()),
&self.options,
+ &self.on_selected,
self.style.clone(),
)
}
@@ -295,15 +296,14 @@ where
/// The local state of a [`PickList`].
#[derive(Debug)]
-pub struct State<T> {
+pub struct State {
menu: menu::State,
keyboard_modifiers: keyboard::Modifiers,
is_open: bool,
hovered_option: Option<usize>,
- last_selection: Option<T>,
}
-impl<T> State<T> {
+impl State {
/// Creates a new [`State`] for a [`PickList`].
pub fn new() -> Self {
Self {
@@ -311,12 +311,11 @@ impl<T> State<T> {
keyboard_modifiers: keyboard::Modifiers::default(),
is_open: bool::default(),
hovered_option: Option::default(),
- last_selection: Option::default(),
}
}
}
-impl<T> Default for State<T> {
+impl Default for State {
fn default() -> Self {
Self::new()
}
@@ -431,12 +430,12 @@ where
pub fn update<'a, T, Message>(
event: Event,
layout: Layout<'_>,
- cursor_position: Point,
+ cursor: mouse::Cursor,
shell: &mut Shell<'_, Message>,
on_selected: &dyn Fn(T) -> Message,
selected: Option<&T>,
options: &[T],
- state: impl FnOnce() -> &'a mut State<T>,
+ state: impl FnOnce() -> &'a mut State,
) -> event::Status
where
T: PartialEq + Clone + 'a,
@@ -446,13 +445,13 @@ where
| Event::Touch(touch::Event::FingerPressed { .. }) => {
let state = state();
- let event_status = if state.is_open {
+ if state.is_open {
// Event wasn't processed by overlay, so cursor was clicked either outside it's
// bounds or on the drop-down, either way we close the overlay.
state.is_open = false;
event::Status::Captured
- } else if layout.bounds().contains(cursor_position) {
+ } else if cursor.is_over(layout.bounds()) {
state.is_open = true;
state.hovered_option =
options.iter().position(|option| Some(option) == selected);
@@ -460,16 +459,6 @@ where
event::Status::Captured
} else {
event::Status::Ignored
- };
-
- if let Some(last_selection) = state.last_selection.take() {
- shell.publish((on_selected)(last_selection));
-
- state.is_open = false;
-
- event::Status::Captured
- } else {
- event_status
}
}
Event::Mouse(mouse::Event::WheelScrolled {
@@ -478,7 +467,7 @@ where
let state = state();
if state.keyboard_modifiers.command()
- && layout.bounds().contains(cursor_position)
+ && cursor.is_over(layout.bounds())
&& !state.is_open
{
fn find_next<'a, T: PartialEq>(
@@ -529,10 +518,10 @@ where
/// Returns the current [`mouse::Interaction`] of a [`PickList`].
pub fn mouse_interaction(
layout: Layout<'_>,
- cursor_position: Point,
+ cursor: mouse::Cursor,
) -> mouse::Interaction {
let bounds = layout.bounds();
- let is_mouse_over = bounds.contains(cursor_position);
+ let is_mouse_over = cursor.is_over(bounds);
if is_mouse_over {
mouse::Interaction::Pointer
@@ -544,12 +533,13 @@ pub fn mouse_interaction(
/// Returns the current overlay of a [`PickList`].
pub fn overlay<'a, T, Message, Renderer>(
layout: Layout<'_>,
- state: &'a mut State<T>,
+ state: &'a mut State,
padding: Padding,
text_size: Option<f32>,
text_shaping: text::Shaping,
font: Renderer::Font,
options: &'a [T],
+ on_selected: &'a dyn Fn(T) -> Message,
style: <Renderer::Theme as StyleSheet>::Style,
) -> Option<overlay::Element<'a, Message, Renderer>>
where
@@ -570,7 +560,11 @@ where
&mut state.menu,
options,
&mut state.hovered_option,
- &mut state.last_selection,
+ |option| {
+ state.is_open = false;
+
+ (on_selected)(option)
+ },
)
.width(bounds.width)
.padding(padding)
@@ -593,7 +587,7 @@ pub fn draw<'a, T, Renderer>(
renderer: &mut Renderer,
theme: &Renderer::Theme,
layout: Layout<'_>,
- cursor_position: Point,
+ cursor: mouse::Cursor,
padding: Padding,
text_size: Option<f32>,
text_line_height: text::LineHeight,
@@ -603,14 +597,14 @@ pub fn draw<'a, T, Renderer>(
selected: Option<&T>,
handle: &Handle<Renderer::Font>,
style: &<Renderer::Theme as StyleSheet>::Style,
- state: impl FnOnce() -> &'a State<T>,
+ state: impl FnOnce() -> &'a State,
) where
Renderer: text::Renderer,
Renderer::Theme: StyleSheet,
T: ToString + 'a,
{
let bounds = layout.bounds();
- let is_mouse_over = bounds.contains(cursor_position);
+ let is_mouse_over = cursor.is_over(bounds);
let is_selected = selected.is_some();
let style = if is_mouse_over {
@@ -624,7 +618,7 @@ pub fn draw<'a, T, Renderer>(
bounds,
border_color: style.border_color,
border_width: style.border_width,
- border_radius: style.border_radius.into(),
+ border_radius: style.border_radius,
},
style.background,
);
diff --git a/widget/src/progress_bar.rs b/widget/src/progress_bar.rs
index ef0d87d5..37c6bc72 100644
--- a/widget/src/progress_bar.rs
+++ b/widget/src/progress_bar.rs
@@ -1,10 +1,9 @@
//! Provide progress feedback to your users.
use crate::core::layout;
+use crate::core::mouse;
use crate::core::renderer;
use crate::core::widget::Tree;
-use crate::core::{
- Color, Element, Layout, Length, Point, Rectangle, Size, Widget,
-};
+use crate::core::{Color, Element, Layout, Length, Rectangle, Size, Widget};
use std::ops::RangeInclusive;
@@ -115,7 +114,7 @@ where
theme: &Renderer::Theme,
_style: &renderer::Style,
layout: Layout<'_>,
- _cursor_position: Point,
+ _cursor: mouse::Cursor,
_viewport: &Rectangle,
) {
let bounds = layout.bounds();
@@ -133,7 +132,7 @@ where
renderer.fill_quad(
renderer::Quad {
bounds: Rectangle { ..bounds },
- border_radius: style.border_radius.into(),
+ border_radius: style.border_radius,
border_width: 0.0,
border_color: Color::TRANSPARENT,
},
@@ -147,7 +146,7 @@ where
width: active_progress_width,
..bounds
},
- border_radius: style.border_radius.into(),
+ border_radius: style.border_radius,
border_width: 0.0,
border_color: Color::TRANSPARENT,
},
diff --git a/widget/src/qr_code.rs b/widget/src/qr_code.rs
index 7709125f..06be93c0 100644
--- a/widget/src/qr_code.rs
+++ b/widget/src/qr_code.rs
@@ -1,6 +1,7 @@
//! Encode and display information in a QR code.
use crate::canvas;
use crate::core::layout;
+use crate::core::mouse;
use crate::core::renderer::{self, Renderer as _};
use crate::core::widget::Tree;
use crate::core::{
@@ -74,7 +75,7 @@ impl<'a, Message, Theme> Widget<Message, Renderer<Theme>> for QRCode<'a> {
_theme: &Theme,
_style: &renderer::Style,
layout: Layout<'_>,
- _cursor_position: Point,
+ _cursor: mouse::Cursor,
_viewport: &Rectangle,
) {
let bounds = layout.bounds();
diff --git a/widget/src/radio.rs b/widget/src/radio.rs
index 9dad1e22..5b883147 100644
--- a/widget/src/radio.rs
+++ b/widget/src/radio.rs
@@ -8,8 +8,8 @@ use crate::core::text;
use crate::core::touch;
use crate::core::widget::Tree;
use crate::core::{
- Alignment, Clipboard, Color, Element, Layout, Length, Pixels, Point,
- Rectangle, Shell, Widget,
+ Alignment, Clipboard, Color, Element, Layout, Length, Pixels, Rectangle,
+ Shell, Widget,
};
use crate::{Row, Text};
@@ -229,7 +229,7 @@ where
_state: &mut Tree,
event: Event,
layout: Layout<'_>,
- cursor_position: Point,
+ cursor: mouse::Cursor,
_renderer: &Renderer,
_clipboard: &mut dyn Clipboard,
shell: &mut Shell<'_, Message>,
@@ -237,7 +237,7 @@ where
match event {
Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left))
| Event::Touch(touch::Event::FingerPressed { .. }) => {
- if layout.bounds().contains(cursor_position) {
+ if cursor.is_over(layout.bounds()) {
shell.publish(self.on_click.clone());
return event::Status::Captured;
@@ -253,11 +253,11 @@ where
&self,
_state: &Tree,
layout: Layout<'_>,
- cursor_position: Point,
+ cursor: mouse::Cursor,
_viewport: &Rectangle,
_renderer: &Renderer,
) -> mouse::Interaction {
- if layout.bounds().contains(cursor_position) {
+ if cursor.is_over(layout.bounds()) {
mouse::Interaction::Pointer
} else {
mouse::Interaction::default()
@@ -271,11 +271,10 @@ where
theme: &Renderer::Theme,
style: &renderer::Style,
layout: Layout<'_>,
- cursor_position: Point,
+ cursor: mouse::Cursor,
_viewport: &Rectangle,
) {
- let bounds = layout.bounds();
- let is_mouse_over = bounds.contains(cursor_position);
+ let is_mouse_over = cursor.is_over(layout.bounds());
let mut children = layout.children();
diff --git a/widget/src/row.rs b/widget/src/row.rs
index 3ce363f8..1db22416 100644
--- a/widget/src/row.rs
+++ b/widget/src/row.rs
@@ -6,8 +6,8 @@ use crate::core::overlay;
use crate::core::renderer;
use crate::core::widget::{Operation, Tree};
use crate::core::{
- Alignment, Clipboard, Element, Length, Padding, Pixels, Point, Rectangle,
- Shell, Widget,
+ Alignment, Clipboard, Element, Length, Padding, Pixels, Rectangle, Shell,
+ Widget,
};
/// A container that distributes its contents horizontally.
@@ -155,7 +155,7 @@ where
tree: &mut Tree,
event: Event,
layout: Layout<'_>,
- cursor_position: Point,
+ cursor: mouse::Cursor,
renderer: &Renderer,
clipboard: &mut dyn Clipboard,
shell: &mut Shell<'_, Message>,
@@ -169,7 +169,7 @@ where
state,
event.clone(),
layout,
- cursor_position,
+ cursor,
renderer,
clipboard,
shell,
@@ -182,7 +182,7 @@ where
&self,
tree: &Tree,
layout: Layout<'_>,
- cursor_position: Point,
+ cursor: mouse::Cursor,
viewport: &Rectangle,
renderer: &Renderer,
) -> mouse::Interaction {
@@ -192,11 +192,7 @@ where
.zip(layout.children())
.map(|((child, state), layout)| {
child.as_widget().mouse_interaction(
- state,
- layout,
- cursor_position,
- viewport,
- renderer,
+ state, layout, cursor, viewport, renderer,
)
})
.max()
@@ -210,7 +206,7 @@ where
theme: &Renderer::Theme,
style: &renderer::Style,
layout: Layout<'_>,
- cursor_position: Point,
+ cursor: mouse::Cursor,
viewport: &Rectangle,
) {
for ((child, state), layout) in self
@@ -219,15 +215,9 @@ where
.zip(&tree.children)
.zip(layout.children())
{
- child.as_widget().draw(
- state,
- renderer,
- theme,
- style,
- layout,
- cursor_position,
- viewport,
- );
+ child
+ .as_widget()
+ .draw(state, renderer, theme, style, layout, cursor, viewport);
}
}
diff --git a/widget/src/rule.rs b/widget/src/rule.rs
index 3749d7ce..d703e6ae 100644
--- a/widget/src/rule.rs
+++ b/widget/src/rule.rs
@@ -1,9 +1,10 @@
//! Display a horizontal or vertical rule for dividing content.
use crate::core::layout;
+use crate::core::mouse;
use crate::core::renderer;
use crate::core::widget::Tree;
use crate::core::{
- Color, Element, Layout, Length, Pixels, Point, Rectangle, Size, Widget,
+ Color, Element, Layout, Length, Pixels, Rectangle, Size, Widget,
};
pub use crate::style::rule::{Appearance, FillMode, StyleSheet};
@@ -86,7 +87,7 @@ where
theme: &Renderer::Theme,
_style: &renderer::Style,
layout: Layout<'_>,
- _cursor_position: Point,
+ _cursor: mouse::Cursor,
_viewport: &Rectangle,
) {
let bounds = layout.bounds();
@@ -125,7 +126,7 @@ where
renderer.fill_quad(
renderer::Quad {
bounds,
- border_radius: style.radius.into(),
+ border_radius: style.radius,
border_width: 0.0,
border_color: Color::TRANSPARENT,
},
diff --git a/widget/src/scrollable.rs b/widget/src/scrollable.rs
index fd51a6a8..010befac 100644
--- a/widget/src/scrollable.rs
+++ b/widget/src/scrollable.rs
@@ -128,9 +128,8 @@ impl Properties {
}
/// Sets the scrollbar width of the [`Scrollable`] .
- /// Silently enforces a minimum width of 1.
pub fn width(mut self, width: impl Into<Pixels>) -> Self {
- self.width = width.into().0.max(1.0);
+ self.width = width.into().0.max(0.0);
self
}
@@ -141,9 +140,8 @@ impl Properties {
}
/// Sets the scroller width of the [`Scrollable`] .
- /// Silently enforces a minimum width of 1.
pub fn scroller_width(mut self, scroller_width: impl Into<Pixels>) -> Self {
- self.scroller_width = scroller_width.into().0.max(1.0);
+ self.scroller_width = scroller_width.into().0.max(0.0);
self
}
}
@@ -224,7 +222,7 @@ where
tree: &mut Tree,
event: Event,
layout: Layout<'_>,
- cursor_position: Point,
+ cursor: mouse::Cursor,
renderer: &Renderer,
clipboard: &mut dyn Clipboard,
shell: &mut Shell<'_, Message>,
@@ -233,18 +231,18 @@ where
tree.state.downcast_mut::<State>(),
event,
layout,
- cursor_position,
+ cursor,
clipboard,
shell,
&self.vertical,
self.horizontal.as_ref(),
&self.on_scroll,
- |event, layout, cursor_position, clipboard, shell| {
+ |event, layout, cursor, clipboard, shell| {
self.content.as_widget_mut().on_event(
&mut tree.children[0],
event,
layout,
- cursor_position,
+ cursor,
renderer,
clipboard,
shell,
@@ -260,7 +258,7 @@ where
theme: &Renderer::Theme,
style: &renderer::Style,
layout: Layout<'_>,
- cursor_position: Point,
+ cursor: mouse::Cursor,
_viewport: &Rectangle,
) {
draw(
@@ -268,18 +266,18 @@ where
renderer,
theme,
layout,
- cursor_position,
+ cursor,
&self.vertical,
self.horizontal.as_ref(),
&self.style,
- |renderer, layout, cursor_position, viewport| {
+ |renderer, layout, cursor, viewport| {
self.content.as_widget().draw(
&tree.children[0],
renderer,
theme,
style,
layout,
- cursor_position,
+ cursor,
viewport,
)
},
@@ -290,21 +288,21 @@ where
&self,
tree: &Tree,
layout: Layout<'_>,
- cursor_position: Point,
+ cursor: mouse::Cursor,
_viewport: &Rectangle,
renderer: &Renderer,
) -> mouse::Interaction {
mouse_interaction(
tree.state.downcast_ref::<State>(),
layout,
- cursor_position,
+ cursor,
&self.vertical,
self.horizontal.as_ref(),
- |layout, cursor_position, viewport| {
+ |layout, cursor, viewport| {
self.content.as_widget().mouse_interaction(
&tree.children[0],
layout,
- cursor_position,
+ cursor,
viewport,
renderer,
)
@@ -430,7 +428,7 @@ pub fn update<Message>(
state: &mut State,
event: Event,
layout: Layout<'_>,
- cursor_position: Point,
+ cursor: mouse::Cursor,
clipboard: &mut dyn Clipboard,
shell: &mut Shell<'_, Message>,
vertical: &Properties,
@@ -439,13 +437,13 @@ pub fn update<Message>(
update_content: impl FnOnce(
Event,
Layout<'_>,
- Point,
+ mouse::Cursor,
&mut dyn Clipboard,
&mut Shell<'_, Message>,
) -> event::Status,
) -> event::Status {
let bounds = layout.bounds();
- let mouse_over_scrollable = bounds.contains(cursor_position);
+ let cursor_over_scrollable = cursor.position_over(bounds);
let content = layout.children().next().unwrap();
let content_bounds = content.bounds();
@@ -454,28 +452,21 @@ pub fn update<Message>(
Scrollbars::new(state, vertical, horizontal, bounds, content_bounds);
let (mouse_over_y_scrollbar, mouse_over_x_scrollbar) =
- scrollbars.is_mouse_over(cursor_position);
+ scrollbars.is_mouse_over(cursor);
let event_status = {
- let cursor_position = if mouse_over_scrollable
- && !(mouse_over_y_scrollbar || mouse_over_x_scrollbar)
- {
- cursor_position + state.offset(bounds, content_bounds)
- } else {
- // TODO: Make `cursor_position` an `Option<Point>` so we can encode
- // cursor availability.
- // This will probably happen naturally once we add multi-window
- // support.
- Point::new(-1.0, -1.0)
+ let cursor = match cursor_over_scrollable {
+ Some(cursor_position)
+ if !(mouse_over_x_scrollbar || mouse_over_y_scrollbar) =>
+ {
+ mouse::Cursor::Available(
+ cursor_position + state.offset(bounds, content_bounds),
+ )
+ }
+ _ => mouse::Cursor::Unavailable,
};
- update_content(
- event.clone(),
- content,
- cursor_position,
- clipboard,
- shell,
- )
+ update_content(event.clone(), content, cursor, clipboard, shell)
};
if let event::Status::Captured = event_status {
@@ -489,76 +480,79 @@ pub fn update<Message>(
return event::Status::Ignored;
}
- if mouse_over_scrollable {
- match event {
- Event::Mouse(mouse::Event::WheelScrolled { delta }) => {
- let delta = match delta {
- mouse::ScrollDelta::Lines { x, y } => {
- // TODO: Configurable speed/friction (?)
- let movement = if state.keyboard_modifiers.shift() {
- Vector::new(y, x)
- } else {
- Vector::new(x, y)
- };
+ match event {
+ Event::Mouse(mouse::Event::WheelScrolled { delta }) => {
+ if cursor_over_scrollable.is_none() {
+ return event::Status::Ignored;
+ }
- movement * 60.0
- }
- mouse::ScrollDelta::Pixels { x, y } => Vector::new(x, y),
- };
+ let delta = match delta {
+ mouse::ScrollDelta::Lines { x, y } => {
+ // TODO: Configurable speed/friction (?)
+ let movement = if state.keyboard_modifiers.shift() {
+ Vector::new(y, x)
+ } else {
+ Vector::new(x, y)
+ };
- state.scroll(delta, bounds, content_bounds);
+ movement * 60.0
+ }
+ mouse::ScrollDelta::Pixels { x, y } => Vector::new(x, y),
+ };
- notify_on_scroll(
- state,
- on_scroll,
- bounds,
- content_bounds,
- shell,
- );
+ state.scroll(delta, bounds, content_bounds);
+
+ notify_on_scroll(state, on_scroll, bounds, content_bounds, shell);
+
+ return event::Status::Captured;
+ }
+ Event::Touch(event)
+ if state.scroll_area_touched_at.is_some()
+ || !mouse_over_y_scrollbar && !mouse_over_x_scrollbar =>
+ {
+ match event {
+ touch::Event::FingerPressed { .. } => {
+ let Some(cursor_position) = cursor.position() else {
+ return event::Status::Ignored
+ };
+
+ state.scroll_area_touched_at = Some(cursor_position);
+ }
+ touch::Event::FingerMoved { .. } => {
+ if let Some(scroll_box_touched_at) =
+ state.scroll_area_touched_at
+ {
+ let Some(cursor_position) = cursor.position() else {
+ return event::Status::Ignored
+ };
+
+ let delta = Vector::new(
+ cursor_position.x - scroll_box_touched_at.x,
+ cursor_position.y - scroll_box_touched_at.y,
+ );
+
+ state.scroll(delta, bounds, content_bounds);
- return event::Status::Captured;
- }
- Event::Touch(event)
- if state.scroll_area_touched_at.is_some()
- || !mouse_over_y_scrollbar && !mouse_over_x_scrollbar =>
- {
- match event {
- touch::Event::FingerPressed { .. } => {
state.scroll_area_touched_at = Some(cursor_position);
- }
- touch::Event::FingerMoved { .. } => {
- if let Some(scroll_box_touched_at) =
- state.scroll_area_touched_at
- {
- let delta = Vector::new(
- cursor_position.x - scroll_box_touched_at.x,
- cursor_position.y - scroll_box_touched_at.y,
- );
-
- state.scroll(delta, bounds, content_bounds);
-
- state.scroll_area_touched_at =
- Some(cursor_position);
-
- notify_on_scroll(
- state,
- on_scroll,
- bounds,
- content_bounds,
- shell,
- );
- }
- }
- touch::Event::FingerLifted { .. }
- | touch::Event::FingerLost { .. } => {
- state.scroll_area_touched_at = None;
+
+ notify_on_scroll(
+ state,
+ on_scroll,
+ bounds,
+ content_bounds,
+ shell,
+ );
}
}
-
- return event::Status::Captured;
+ touch::Event::FingerLifted { .. }
+ | touch::Event::FingerLost { .. } => {
+ state.scroll_area_touched_at = None;
+ }
}
- _ => {}
+
+ return event::Status::Captured;
}
+ _ => {}
}
if let Some(scroller_grabbed_at) = state.y_scroller_grabbed_at {
@@ -573,6 +567,10 @@ pub fn update<Message>(
Event::Mouse(mouse::Event::CursorMoved { .. })
| Event::Touch(touch::Event::FingerMoved { .. }) => {
if let Some(scrollbar) = scrollbars.y {
+ let Some(cursor_position) = cursor.position() else {
+ return event::Status::Ignored
+ };
+
state.scroll_y_to(
scrollbar.scroll_percentage_y(
scroller_grabbed_at,
@@ -599,6 +597,10 @@ pub fn update<Message>(
match event {
Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left))
| Event::Touch(touch::Event::FingerPressed { .. }) => {
+ let Some(cursor_position) = cursor.position() else {
+ return event::Status::Ignored
+ };
+
if let (Some(scroller_grabbed_at), Some(scrollbar)) =
(scrollbars.grab_y_scroller(cursor_position), scrollbars.y)
{
@@ -639,6 +641,10 @@ pub fn update<Message>(
}
Event::Mouse(mouse::Event::CursorMoved { .. })
| Event::Touch(touch::Event::FingerMoved { .. }) => {
+ let Some(cursor_position) = cursor.position() else {
+ return event::Status::Ignored
+ };
+
if let Some(scrollbar) = scrollbars.x {
state.scroll_x_to(
scrollbar.scroll_percentage_x(
@@ -666,6 +672,10 @@ pub fn update<Message>(
match event {
Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left))
| Event::Touch(touch::Event::FingerPressed { .. }) => {
+ let Some(cursor_position) = cursor.position() else {
+ return event::Status::Ignored
+ };
+
if let (Some(scroller_grabbed_at), Some(scrollbar)) =
(scrollbars.grab_x_scroller(cursor_position), scrollbars.x)
{
@@ -702,17 +712,17 @@ pub fn update<Message>(
pub fn mouse_interaction(
state: &State,
layout: Layout<'_>,
- cursor_position: Point,
+ cursor: mouse::Cursor,
vertical: &Properties,
horizontal: Option<&Properties>,
content_interaction: impl FnOnce(
Layout<'_>,
- Point,
+ mouse::Cursor,
&Rectangle,
) -> mouse::Interaction,
) -> mouse::Interaction {
let bounds = layout.bounds();
- let mouse_over_scrollable = bounds.contains(cursor_position);
+ let cursor_over_scrollable = cursor.position_over(bounds);
let content_layout = layout.children().next().unwrap();
let content_bounds = content_layout.bounds();
@@ -721,7 +731,7 @@ pub fn mouse_interaction(
Scrollbars::new(state, vertical, horizontal, bounds, content_bounds);
let (mouse_over_y_scrollbar, mouse_over_x_scrollbar) =
- scrollbars.is_mouse_over(cursor_position);
+ scrollbars.is_mouse_over(cursor);
if (mouse_over_x_scrollbar || mouse_over_y_scrollbar)
|| state.scrollers_grabbed()
@@ -730,17 +740,18 @@ pub fn mouse_interaction(
} else {
let offset = state.offset(bounds, content_bounds);
- let cursor_position = if mouse_over_scrollable
- && !(mouse_over_y_scrollbar || mouse_over_x_scrollbar)
- {
- cursor_position + offset
- } else {
- Point::new(-1.0, -1.0)
+ let cursor = match cursor_over_scrollable {
+ Some(cursor_position)
+ if !(mouse_over_x_scrollbar || mouse_over_y_scrollbar) =>
+ {
+ mouse::Cursor::Available(cursor_position + offset)
+ }
+ _ => mouse::Cursor::Unavailable,
};
content_interaction(
content_layout,
- cursor_position,
+ cursor,
&Rectangle {
y: bounds.y + offset.y,
x: bounds.x + offset.x,
@@ -756,11 +767,11 @@ pub fn draw<Renderer>(
renderer: &mut Renderer,
theme: &Renderer::Theme,
layout: Layout<'_>,
- cursor_position: Point,
+ cursor: mouse::Cursor,
vertical: &Properties,
horizontal: Option<&Properties>,
style: &<Renderer::Theme as StyleSheet>::Style,
- draw_content: impl FnOnce(&mut Renderer, Layout<'_>, Point, &Rectangle),
+ draw_content: impl FnOnce(&mut Renderer, Layout<'_>, mouse::Cursor, &Rectangle),
) where
Renderer: crate::core::Renderer,
Renderer::Theme: StyleSheet,
@@ -772,18 +783,19 @@ pub fn draw<Renderer>(
let scrollbars =
Scrollbars::new(state, vertical, horizontal, bounds, content_bounds);
- let mouse_over_scrollable = bounds.contains(cursor_position);
+ let cursor_over_scrollable = cursor.position_over(bounds);
let (mouse_over_y_scrollbar, mouse_over_x_scrollbar) =
- scrollbars.is_mouse_over(cursor_position);
+ scrollbars.is_mouse_over(cursor);
let offset = state.offset(bounds, content_bounds);
- let cursor_position = if mouse_over_scrollable
- && !(mouse_over_x_scrollbar || mouse_over_y_scrollbar)
- {
- cursor_position + offset
- } else {
- Point::new(-1.0, -1.0)
+ let cursor = match cursor_over_scrollable {
+ Some(cursor_position)
+ if !(mouse_over_x_scrollbar || mouse_over_y_scrollbar) =>
+ {
+ mouse::Cursor::Available(cursor_position + offset)
+ }
+ _ => mouse::Cursor::Unavailable,
};
// Draw inner content
@@ -795,7 +807,7 @@ pub fn draw<Renderer>(
draw_content(
renderer,
content_layout,
- cursor_position,
+ cursor,
&Rectangle {
y: bounds.y + offset.y,
x: bounds.x + offset.x,
@@ -811,14 +823,16 @@ pub fn draw<Renderer>(
style: Scrollbar,
scrollbar: &internals::Scrollbar| {
//track
- if style.background.is_some()
- || (style.border_color != Color::TRANSPARENT
- && style.border_width > 0.0)
+ if scrollbar.bounds.width > 0.0
+ && scrollbar.bounds.height > 0.0
+ && (style.background.is_some()
+ || (style.border_color != Color::TRANSPARENT
+ && style.border_width > 0.0))
{
renderer.fill_quad(
renderer::Quad {
bounds: scrollbar.bounds,
- border_radius: style.border_radius.into(),
+ border_radius: style.border_radius,
border_width: style.border_width,
border_color: style.border_color,
},
@@ -829,14 +843,16 @@ pub fn draw<Renderer>(
}
//thumb
- if style.scroller.color != Color::TRANSPARENT
- || (style.scroller.border_color != Color::TRANSPARENT
- && style.scroller.border_width > 0.0)
+ if scrollbar.scroller.bounds.width > 0.0
+ && scrollbar.scroller.bounds.height > 0.0
+ && (style.scroller.color != Color::TRANSPARENT
+ || (style.scroller.border_color != Color::TRANSPARENT
+ && style.scroller.border_width > 0.0))
{
renderer.fill_quad(
renderer::Quad {
bounds: scrollbar.scroller.bounds,
- border_radius: style.scroller.border_radius.into(),
+ border_radius: style.scroller.border_radius,
border_width: style.scroller.border_width,
border_color: style.scroller.border_color,
},
@@ -856,7 +872,7 @@ pub fn draw<Renderer>(
if let Some(scrollbar) = scrollbars.y {
let style = if state.y_scroller_grabbed_at.is_some() {
theme.dragging(style)
- } else if mouse_over_scrollable {
+ } else if cursor_over_scrollable.is_some() {
theme.hovered(style, mouse_over_y_scrollbar)
} else {
theme.active(style)
@@ -869,7 +885,7 @@ pub fn draw<Renderer>(
if let Some(scrollbar) = scrollbars.x {
let style = if state.x_scroller_grabbed_at.is_some() {
theme.dragging_horizontal(style)
- } else if mouse_over_scrollable {
+ } else if cursor_over_scrollable.is_some() {
theme.hovered_horizontal(style, mouse_over_x_scrollbar)
} else {
theme.active_horizontal(style)
@@ -883,7 +899,7 @@ pub fn draw<Renderer>(
draw_content(
renderer,
content_layout,
- cursor_position,
+ cursor,
&Rectangle {
x: bounds.x + offset.x,
y: bounds.y + offset.y,
@@ -1281,17 +1297,21 @@ impl Scrollbars {
}
}
- fn is_mouse_over(&self, cursor_position: Point) -> (bool, bool) {
- (
- self.y
- .as_ref()
- .map(|scrollbar| scrollbar.is_mouse_over(cursor_position))
- .unwrap_or(false),
- self.x
- .as_ref()
- .map(|scrollbar| scrollbar.is_mouse_over(cursor_position))
- .unwrap_or(false),
- )
+ fn is_mouse_over(&self, cursor: mouse::Cursor) -> (bool, bool) {
+ if let Some(cursor_position) = cursor.position() {
+ (
+ self.y
+ .as_ref()
+ .map(|scrollbar| scrollbar.is_mouse_over(cursor_position))
+ .unwrap_or(false),
+ self.x
+ .as_ref()
+ .map(|scrollbar| scrollbar.is_mouse_over(cursor_position))
+ .unwrap_or(false),
+ )
+ } else {
+ (false, false)
+ }
}
fn grab_y_scroller(&self, cursor_position: Point) -> Option<f32> {
diff --git a/widget/src/slider.rs b/widget/src/slider.rs
index 18a49665..3ea4391b 100644
--- a/widget/src/slider.rs
+++ b/widget/src/slider.rs
@@ -183,7 +183,7 @@ where
tree: &mut Tree,
event: Event,
layout: Layout<'_>,
- cursor_position: Point,
+ cursor: mouse::Cursor,
_renderer: &Renderer,
_clipboard: &mut dyn Clipboard,
shell: &mut Shell<'_, Message>,
@@ -191,7 +191,7 @@ where
update(
event,
layout,
- cursor_position,
+ cursor,
shell,
tree.state.downcast_mut::<State>(),
&mut self.value,
@@ -209,13 +209,13 @@ where
theme: &Renderer::Theme,
_style: &renderer::Style,
layout: Layout<'_>,
- cursor_position: Point,
+ cursor: mouse::Cursor,
_viewport: &Rectangle,
) {
draw(
renderer,
layout,
- cursor_position,
+ cursor,
tree.state.downcast_ref::<State>(),
self.value,
&self.range,
@@ -228,15 +228,11 @@ where
&self,
tree: &Tree,
layout: Layout<'_>,
- cursor_position: Point,
+ cursor: mouse::Cursor,
_viewport: &Rectangle,
_renderer: &Renderer,
) -> mouse::Interaction {
- mouse_interaction(
- layout,
- cursor_position,
- tree.state.downcast_ref::<State>(),
- )
+ mouse_interaction(layout, cursor, tree.state.downcast_ref::<State>())
}
}
@@ -260,7 +256,7 @@ where
pub fn update<Message, T>(
event: Event,
layout: Layout<'_>,
- cursor_position: Point,
+ cursor: mouse::Cursor,
shell: &mut Shell<'_, Message>,
state: &mut State,
value: &mut T,
@@ -275,7 +271,7 @@ where
{
let is_dragging = state.is_dragging;
- let mut change = || {
+ let mut change = |cursor_position: Point| {
let bounds = layout.bounds();
let new_value = if cursor_position.x <= bounds.x {
*range.start()
@@ -309,8 +305,9 @@ where
match event {
Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left))
| Event::Touch(touch::Event::FingerPressed { .. }) => {
- if layout.bounds().contains(cursor_position) {
- change();
+ if let Some(cursor_position) = cursor.position_over(layout.bounds())
+ {
+ change(cursor_position);
state.is_dragging = true;
return event::Status::Captured;
@@ -331,7 +328,7 @@ where
Event::Mouse(mouse::Event::CursorMoved { .. })
| Event::Touch(touch::Event::FingerMoved { .. }) => {
if is_dragging {
- change();
+ let _ = cursor.position().map(change);
return event::Status::Captured;
}
@@ -346,7 +343,7 @@ where
pub fn draw<T, R>(
renderer: &mut R,
layout: Layout<'_>,
- cursor_position: Point,
+ cursor: mouse::Cursor,
state: &State,
value: T,
range: &RangeInclusive<T>,
@@ -358,7 +355,7 @@ pub fn draw<T, R>(
R::Theme: StyleSheet,
{
let bounds = layout.bounds();
- let is_mouse_over = bounds.contains(cursor_position);
+ let is_mouse_over = cursor.is_over(bounds);
let style = if state.is_dragging {
style_sheet.dragging(style)
@@ -368,16 +365,16 @@ pub fn draw<T, R>(
style_sheet.active(style)
};
- let (handle_width, handle_height, handle_border_radius) = match style
- .handle
- .shape
- {
- HandleShape::Circle { radius } => (radius * 2.0, radius * 2.0, radius),
- HandleShape::Rectangle {
- width,
- border_radius,
- } => (f32::from(width), bounds.height, border_radius),
- };
+ let (handle_width, handle_height, handle_border_radius) =
+ match style.handle.shape {
+ HandleShape::Circle { radius } => {
+ (radius * 2.0, radius * 2.0, radius.into())
+ }
+ HandleShape::Rectangle {
+ width,
+ border_radius,
+ } => (f32::from(width), bounds.height, border_radius),
+ };
let value = value.into() as f32;
let (range_start, range_end) = {
@@ -403,7 +400,7 @@ pub fn draw<T, R>(
width: offset + handle_width / 2.0,
height: style.rail.width,
},
- border_radius: Default::default(),
+ border_radius: style.rail.border_radius,
border_width: 0.0,
border_color: Color::TRANSPARENT,
},
@@ -418,7 +415,7 @@ pub fn draw<T, R>(
width: bounds.width - offset - handle_width / 2.0,
height: style.rail.width,
},
- border_radius: Default::default(),
+ border_radius: style.rail.border_radius,
border_width: 0.0,
border_color: Color::TRANSPARENT,
},
@@ -433,7 +430,7 @@ pub fn draw<T, R>(
width: handle_width,
height: handle_height,
},
- border_radius: handle_border_radius.into(),
+ border_radius: handle_border_radius,
border_width: style.handle.border_width,
border_color: style.handle.border_color,
},
@@ -444,11 +441,11 @@ pub fn draw<T, R>(
/// Computes the current [`mouse::Interaction`] of a [`Slider`].
pub fn mouse_interaction(
layout: Layout<'_>,
- cursor_position: Point,
+ cursor: mouse::Cursor,
state: &State,
) -> mouse::Interaction {
let bounds = layout.bounds();
- let is_mouse_over = bounds.contains(cursor_position);
+ let is_mouse_over = cursor.is_over(bounds);
if state.is_dragging {
mouse::Interaction::Grabbing
diff --git a/widget/src/space.rs b/widget/src/space.rs
index e1e09d5a..9a5385e8 100644
--- a/widget/src/space.rs
+++ b/widget/src/space.rs
@@ -1,9 +1,10 @@
//! Distribute content vertically.
use crate::core;
use crate::core::layout;
+use crate::core::mouse;
use crate::core::renderer;
use crate::core::widget::Tree;
-use crate::core::{Element, Layout, Length, Point, Rectangle, Size, Widget};
+use crate::core::{Element, Layout, Length, Rectangle, Size, Widget};
/// An amount of empty space.
///
@@ -69,7 +70,7 @@ where
_theme: &Renderer::Theme,
_style: &renderer::Style,
_layout: Layout<'_>,
- _cursor_position: Point,
+ _cursor: mouse::Cursor,
_viewport: &Rectangle,
) {
}
diff --git a/widget/src/svg.rs b/widget/src/svg.rs
index 89017fcf..1ccc5d62 100644
--- a/widget/src/svg.rs
+++ b/widget/src/svg.rs
@@ -1,10 +1,11 @@
//! Display vector graphics in your application.
use crate::core::layout;
+use crate::core::mouse;
use crate::core::renderer;
use crate::core::svg;
use crate::core::widget::Tree;
use crate::core::{
- ContentFit, Element, Layout, Length, Point, Rectangle, Size, Vector, Widget,
+ ContentFit, Element, Layout, Length, Rectangle, Size, Vector, Widget,
};
use std::path::PathBuf;
@@ -143,7 +144,7 @@ where
theme: &Renderer::Theme,
_style: &renderer::Style,
layout: Layout<'_>,
- _cursor_position: Point,
+ _cursor: mouse::Cursor,
_viewport: &Rectangle,
) {
let Size { width, height } = renderer.dimensions(&self.handle);
diff --git a/widget/src/text.rs b/widget/src/text.rs
index 04c31edc..ce4f44bd 100644
--- a/widget/src/text.rs
+++ b/widget/src/text.rs
@@ -1,4 +1,6 @@
+//! Draw and interact with text.
pub use crate::core::widget::text::*;
+/// A paragraph.
pub type Text<'a, Renderer = crate::Renderer> =
crate::core::widget::Text<'a, Renderer>;
diff --git a/widget/src/text_input.rs b/widget/src/text_input.rs
index bbc07dac..272263f9 100644
--- a/widget/src/text_input.rs
+++ b/widget/src/text_input.rs
@@ -207,14 +207,14 @@ where
renderer: &mut Renderer,
theme: &Renderer::Theme,
layout: Layout<'_>,
- cursor_position: Point,
+ cursor: mouse::Cursor,
value: Option<&Value>,
) {
draw(
renderer,
theme,
layout,
- cursor_position,
+ cursor,
tree.state.downcast_ref::<State>(),
value.unwrap_or(&self.value),
&self.placeholder,
@@ -298,7 +298,7 @@ where
tree: &mut Tree,
event: Event,
layout: Layout<'_>,
- cursor_position: Point,
+ cursor: mouse::Cursor,
renderer: &Renderer,
clipboard: &mut dyn Clipboard,
shell: &mut Shell<'_, Message>,
@@ -306,7 +306,7 @@ where
update(
event,
layout,
- cursor_position,
+ cursor,
renderer,
clipboard,
shell,
@@ -329,14 +329,14 @@ where
theme: &Renderer::Theme,
_style: &renderer::Style,
layout: Layout<'_>,
- cursor_position: Point,
+ cursor: mouse::Cursor,
_viewport: &Rectangle,
) {
draw(
renderer,
theme,
layout,
- cursor_position,
+ cursor,
tree.state.downcast_ref::<State>(),
&self.value,
&self.placeholder,
@@ -354,11 +354,11 @@ where
&self,
_state: &Tree,
layout: Layout<'_>,
- cursor_position: Point,
+ cursor: mouse::Cursor,
_viewport: &Rectangle,
_renderer: &Renderer,
) -> mouse::Interaction {
- mouse_interaction(layout, cursor_position, self.on_input.is_none())
+ mouse_interaction(layout, cursor, self.on_input.is_none())
}
}
@@ -528,7 +528,7 @@ where
pub fn update<'a, Message, Renderer>(
event: Event,
layout: Layout<'_>,
- cursor_position: Point,
+ cursor: mouse::Cursor,
renderer: &Renderer,
clipboard: &mut dyn Clipboard,
shell: &mut Shell<'_, Message>,
@@ -550,10 +550,14 @@ where
Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left))
| Event::Touch(touch::Event::FingerPressed { .. }) => {
let state = state();
- let is_clicked =
- layout.bounds().contains(cursor_position) && on_input.is_some();
- state.is_focused = if is_clicked {
+ let click_position = if on_input.is_some() {
+ cursor.position_over(layout.bounds())
+ } else {
+ None
+ };
+
+ state.is_focused = if click_position.is_some() {
state.is_focused.or_else(|| {
let now = Instant::now();
@@ -566,7 +570,7 @@ where
None
};
- if is_clicked {
+ if let Some(cursor_position) = click_position {
let text_layout = layout.children().next().unwrap();
let target = cursor_position.x - text_layout.bounds().x;
@@ -944,7 +948,7 @@ pub fn draw<Renderer>(
renderer: &mut Renderer,
theme: &Renderer::Theme,
layout: Layout<'_>,
- cursor_position: Point,
+ cursor: mouse::Cursor,
state: &State,
value: &Value,
placeholder: &str,
@@ -967,7 +971,7 @@ pub fn draw<Renderer>(
let mut children_layout = layout.children();
let text_bounds = children_layout.next().unwrap().bounds();
- let is_mouse_over = bounds.contains(cursor_position);
+ let is_mouse_over = cursor.is_over(bounds);
let appearance = if is_disabled {
theme.disabled(style)
@@ -982,7 +986,7 @@ pub fn draw<Renderer>(
renderer.fill_quad(
renderer::Quad {
bounds,
- border_radius: appearance.border_radius.into(),
+ border_radius: appearance.border_radius,
border_width: appearance.border_width,
border_color: appearance.border_color,
},
@@ -1154,10 +1158,10 @@ pub fn draw<Renderer>(
/// Computes the current [`mouse::Interaction`] of the [`TextInput`].
pub fn mouse_interaction(
layout: Layout<'_>,
- cursor_position: Point,
+ cursor: mouse::Cursor,
is_disabled: bool,
) -> mouse::Interaction {
- if layout.bounds().contains(cursor_position) {
+ if cursor.is_over(layout.bounds()) {
if is_disabled {
mouse::Interaction::NotAllowed
} else {
diff --git a/widget/src/toggler.rs b/widget/src/toggler.rs
index b1ba65c9..8b51f2d0 100644
--- a/widget/src/toggler.rs
+++ b/widget/src/toggler.rs
@@ -7,8 +7,8 @@ use crate::core::renderer;
use crate::core::text;
use crate::core::widget::Tree;
use crate::core::{
- Alignment, Clipboard, Element, Event, Layout, Length, Pixels, Point,
- Rectangle, Shell, Widget,
+ Alignment, Clipboard, Element, Event, Layout, Length, Pixels, Rectangle,
+ Shell, Widget,
};
use crate::{Row, Text};
@@ -202,14 +202,14 @@ where
_state: &mut Tree,
event: Event,
layout: Layout<'_>,
- cursor_position: Point,
+ cursor: mouse::Cursor,
_renderer: &Renderer,
_clipboard: &mut dyn Clipboard,
shell: &mut Shell<'_, Message>,
) -> event::Status {
match event {
Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) => {
- let mouse_over = layout.bounds().contains(cursor_position);
+ let mouse_over = cursor.is_over(layout.bounds());
if mouse_over {
shell.publish((self.on_toggle)(!self.is_toggled));
@@ -227,11 +227,11 @@ where
&self,
_state: &Tree,
layout: Layout<'_>,
- cursor_position: Point,
+ cursor: mouse::Cursor,
_viewport: &Rectangle,
_renderer: &Renderer,
) -> mouse::Interaction {
- if layout.bounds().contains(cursor_position) {
+ if cursor.is_over(layout.bounds()) {
mouse::Interaction::Pointer
} else {
mouse::Interaction::default()
@@ -245,7 +245,7 @@ where
theme: &Renderer::Theme,
style: &renderer::Style,
layout: Layout<'_>,
- cursor_position: Point,
+ cursor: mouse::Cursor,
_viewport: &Rectangle,
) {
/// Makes sure that the border radius of the toggler looks good at every size.
@@ -278,7 +278,7 @@ where
let toggler_layout = children.next().unwrap();
let bounds = toggler_layout.bounds();
- let is_mouse_over = bounds.contains(cursor_position);
+ let is_mouse_over = cursor.is_over(layout.bounds());
let style = if is_mouse_over {
theme.hovered(&self.style, self.is_toggled)
diff --git a/widget/src/tooltip.rs b/widget/src/tooltip.rs
index 084650d1..d425de01 100644
--- a/widget/src/tooltip.rs
+++ b/widget/src/tooltip.rs
@@ -9,7 +9,7 @@ use crate::core::renderer;
use crate::core::text;
use crate::core::widget::Tree;
use crate::core::{
- Clipboard, Element, Length, Padding, Pixels, Point, Rectangle, Shell, Size,
+ Clipboard, Element, Length, Padding, Pixels, Rectangle, Shell, Size,
Vector, Widget,
};
use crate::Text;
@@ -136,7 +136,7 @@ where
tree: &mut Tree,
event: Event,
layout: Layout<'_>,
- cursor_position: Point,
+ cursor: mouse::Cursor,
renderer: &Renderer,
clipboard: &mut dyn Clipboard,
shell: &mut Shell<'_, Message>,
@@ -145,7 +145,7 @@ where
&mut tree.children[0],
event,
layout,
- cursor_position,
+ cursor,
renderer,
clipboard,
shell,
@@ -156,14 +156,14 @@ where
&self,
tree: &Tree,
layout: Layout<'_>,
- cursor_position: Point,
+ cursor: mouse::Cursor,
viewport: &Rectangle,
renderer: &Renderer,
) -> mouse::Interaction {
self.content.as_widget().mouse_interaction(
&tree.children[0],
layout,
- cursor_position,
+ cursor,
viewport,
renderer,
)
@@ -176,7 +176,7 @@ where
theme: &Renderer::Theme,
inherited_style: &renderer::Style,
layout: Layout<'_>,
- cursor_position: Point,
+ cursor: mouse::Cursor,
viewport: &Rectangle,
) {
self.content.as_widget().draw(
@@ -185,7 +185,7 @@ where
theme,
inherited_style,
layout,
- cursor_position,
+ cursor,
viewport,
);
@@ -196,7 +196,7 @@ where
theme,
inherited_style,
layout,
- cursor_position,
+ cursor,
viewport,
self.position,
self.gap,
@@ -206,7 +206,7 @@ where
|renderer, limits| {
Widget::<(), Renderer>::layout(tooltip, renderer, limits)
},
- |renderer, defaults, layout, cursor_position, viewport| {
+ |renderer, defaults, layout, viewport| {
Widget::<(), Renderer>::draw(
tooltip,
&Tree::empty(),
@@ -214,7 +214,7 @@ where
theme,
defaults,
layout,
- cursor_position,
+ cursor,
viewport,
);
},
@@ -270,7 +270,7 @@ pub fn draw<Renderer>(
theme: &Renderer::Theme,
inherited_style: &renderer::Style,
layout: Layout<'_>,
- cursor_position: Point,
+ cursor: mouse::Cursor,
viewport: &Rectangle,
position: Position,
gap: f32,
@@ -278,13 +278,7 @@ pub fn draw<Renderer>(
snap_within_viewport: bool,
style: &<Renderer::Theme as container::StyleSheet>::Style,
layout_text: impl FnOnce(&Renderer, &layout::Limits) -> layout::Node,
- draw_text: impl FnOnce(
- &mut Renderer,
- &renderer::Style,
- Layout<'_>,
- Point,
- &Rectangle,
- ),
+ draw_text: impl FnOnce(&mut Renderer, &renderer::Style, Layout<'_>, &Rectangle),
) where
Renderer: core::Renderer,
Renderer::Theme: container::StyleSheet,
@@ -293,7 +287,7 @@ pub fn draw<Renderer>(
let bounds = layout.bounds();
- if bounds.contains(cursor_position) {
+ if let Some(cursor_position) = cursor.position_over(bounds) {
let style = theme.appearance(style);
let defaults = renderer::Style {
@@ -380,7 +374,6 @@ pub fn draw<Renderer>(
),
&text_layout,
),
- cursor_position,
viewport,
)
});
diff --git a/widget/src/vertical_slider.rs b/widget/src/vertical_slider.rs
index 2635611d..91f2b466 100644
--- a/widget/src/vertical_slider.rs
+++ b/widget/src/vertical_slider.rs
@@ -180,7 +180,7 @@ where
tree: &mut Tree,
event: Event,
layout: Layout<'_>,
- cursor_position: Point,
+ cursor: mouse::Cursor,
_renderer: &Renderer,
_clipboard: &mut dyn Clipboard,
shell: &mut Shell<'_, Message>,
@@ -188,7 +188,7 @@ where
update(
event,
layout,
- cursor_position,
+ cursor,
shell,
tree.state.downcast_mut::<State>(),
&mut self.value,
@@ -206,13 +206,13 @@ where
theme: &Renderer::Theme,
_style: &renderer::Style,
layout: Layout<'_>,
- cursor_position: Point,
+ cursor: mouse::Cursor,
_viewport: &Rectangle,
) {
draw(
renderer,
layout,
- cursor_position,
+ cursor,
tree.state.downcast_ref::<State>(),
self.value,
&self.range,
@@ -225,15 +225,11 @@ where
&self,
tree: &Tree,
layout: Layout<'_>,
- cursor_position: Point,
+ cursor: mouse::Cursor,
_viewport: &Rectangle,
_renderer: &Renderer,
) -> mouse::Interaction {
- mouse_interaction(
- layout,
- cursor_position,
- tree.state.downcast_ref::<State>(),
- )
+ mouse_interaction(layout, cursor, tree.state.downcast_ref::<State>())
}
}
@@ -257,7 +253,7 @@ where
pub fn update<Message, T>(
event: Event,
layout: Layout<'_>,
- cursor_position: Point,
+ cursor: mouse::Cursor,
shell: &mut Shell<'_, Message>,
state: &mut State,
value: &mut T,
@@ -272,8 +268,9 @@ where
{
let is_dragging = state.is_dragging;
- let mut change = || {
+ let mut change = |cursor_position: Point| {
let bounds = layout.bounds();
+
let new_value = if cursor_position.y >= bounds.y + bounds.height {
*range.start()
} else if cursor_position.y <= bounds.y {
@@ -307,8 +304,9 @@ where
match event {
Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left))
| Event::Touch(touch::Event::FingerPressed { .. }) => {
- if layout.bounds().contains(cursor_position) {
- change();
+ if let Some(cursor_position) = cursor.position_over(layout.bounds())
+ {
+ change(cursor_position);
state.is_dragging = true;
return event::Status::Captured;
@@ -329,7 +327,7 @@ where
Event::Mouse(mouse::Event::CursorMoved { .. })
| Event::Touch(touch::Event::FingerMoved { .. }) => {
if is_dragging {
- change();
+ let _ = cursor.position().map(change);
return event::Status::Captured;
}
@@ -344,7 +342,7 @@ where
pub fn draw<T, R>(
renderer: &mut R,
layout: Layout<'_>,
- cursor_position: Point,
+ cursor: mouse::Cursor,
state: &State,
value: T,
range: &RangeInclusive<T>,
@@ -356,7 +354,7 @@ pub fn draw<T, R>(
R::Theme: StyleSheet,
{
let bounds = layout.bounds();
- let is_mouse_over = bounds.contains(cursor_position);
+ let is_mouse_over = cursor.is_over(bounds);
let style = if state.is_dragging {
style_sheet.dragging(style)
@@ -366,16 +364,16 @@ pub fn draw<T, R>(
style_sheet.active(style)
};
- let (handle_width, handle_height, handle_border_radius) = match style
- .handle
- .shape
- {
- HandleShape::Circle { radius } => (radius * 2.0, radius * 2.0, radius),
- HandleShape::Rectangle {
- width,
- border_radius,
- } => (f32::from(width), bounds.width, border_radius),
- };
+ let (handle_width, handle_height, handle_border_radius) =
+ match style.handle.shape {
+ HandleShape::Circle { radius } => {
+ (radius * 2.0, radius * 2.0, radius.into())
+ }
+ HandleShape::Rectangle {
+ width,
+ border_radius,
+ } => (f32::from(width), bounds.width, border_radius),
+ };
let value = value.into() as f32;
let (range_start, range_end) = {
@@ -401,7 +399,7 @@ pub fn draw<T, R>(
width: style.rail.width,
height: offset + handle_width / 2.0,
},
- border_radius: Default::default(),
+ border_radius: style.rail.border_radius,
border_width: 0.0,
border_color: Color::TRANSPARENT,
},
@@ -416,7 +414,7 @@ pub fn draw<T, R>(
width: style.rail.width,
height: bounds.height - offset - handle_width / 2.0,
},
- border_radius: Default::default(),
+ border_radius: style.rail.border_radius,
border_width: 0.0,
border_color: Color::TRANSPARENT,
},
@@ -431,7 +429,7 @@ pub fn draw<T, R>(
width: handle_height,
height: handle_width,
},
- border_radius: handle_border_radius.into(),
+ border_radius: handle_border_radius,
border_width: style.handle.border_width,
border_color: style.handle.border_color,
},
@@ -442,11 +440,11 @@ pub fn draw<T, R>(
/// Computes the current [`mouse::Interaction`] of a [`VerticalSlider`].
pub fn mouse_interaction(
layout: Layout<'_>,
- cursor_position: Point,
+ cursor: mouse::Cursor,
state: &State,
) -> mouse::Interaction {
let bounds = layout.bounds();
- let is_mouse_over = bounds.contains(cursor_position);
+ let is_mouse_over = cursor.is_over(bounds);
if state.is_dragging {
mouse::Interaction::Grabbing
diff --git a/winit/Cargo.toml b/winit/Cargo.toml
index 58e13b3e..de7c1c62 100644
--- a/winit/Cargo.toml
+++ b/winit/Cargo.toml
@@ -23,14 +23,15 @@ wayland-dlopen = ["winit/wayland-dlopen"]
wayland-csd-adwaita = ["winit/wayland-csd-adwaita"]
[dependencies]
-window_clipboard = "0.2"
+window_clipboard = "0.3"
log = "0.4"
thiserror = "1.0"
+raw-window-handle = "0.5"
[dependencies.winit]
-version = "0.27"
+version = "0.28"
git = "https://github.com/iced-rs/winit.git"
-rev = "940457522e9fb9f5dac228b0ecfafe0138b4048c"
+rev = "c52db2045d0a2f1b8d9923870de1d4ab1994146e"
default-features = false
[dependencies.iced_runtime]
diff --git a/winit/src/application.rs b/winit/src/application.rs
index 3d7c6e5d..6e7b94ef 100644
--- a/winit/src/application.rs
+++ b/winit/src/application.rs
@@ -308,6 +308,8 @@ async fn run_instance<A, E, C>(
run_command(
&application,
+ &mut compositor,
+ &mut surface,
&mut cache,
&state,
&mut renderer,
@@ -318,7 +320,6 @@ async fn run_instance<A, E, C>(
&mut proxy,
&mut debug,
&window,
- || compositor.fetch_information(),
);
runtime.track(application.subscription().into_recipes());
@@ -356,7 +357,7 @@ async fn run_instance<A, E, C>(
let (interface_state, statuses) = user_interface.update(
&events,
- state.cursor_position(),
+ state.cursor(),
&mut renderer,
&mut clipboard,
&mut messages,
@@ -382,6 +383,8 @@ async fn run_instance<A, E, C>(
// Update application
update(
&mut application,
+ &mut compositor,
+ &mut surface,
&mut cache,
&state,
&mut renderer,
@@ -392,7 +395,6 @@ async fn run_instance<A, E, C>(
&mut debug,
&mut messages,
&window,
- || compositor.fetch_information(),
);
// Update window
@@ -422,7 +424,7 @@ async fn run_instance<A, E, C>(
let (interface_state, _) = user_interface.update(
&[redraw_event.clone()],
- state.cursor_position(),
+ state.cursor(),
&mut renderer,
&mut clipboard,
&mut messages,
@@ -435,7 +437,7 @@ async fn run_instance<A, E, C>(
&renderer::Style {
text_color: state.text_color(),
},
- state.cursor_position(),
+ state.cursor(),
);
debug.draw_finished();
@@ -508,7 +510,7 @@ async fn run_instance<A, E, C>(
&renderer::Style {
text_color: state.text_color(),
},
- state.cursor_position(),
+ state.cursor(),
);
if new_mouse_interaction != mouse_interaction {
@@ -645,8 +647,10 @@ where
/// Updates an [`Application`] by feeding it the provided messages, spawning any
/// resulting [`Command`], and tracking its [`Subscription`].
-pub fn update<A: Application, E: Executor>(
+pub fn update<A: Application, C, E: Executor>(
application: &mut A,
+ compositor: &mut C,
+ surface: &mut C::Surface,
cache: &mut user_interface::Cache,
state: &State<A>,
renderer: &mut A::Renderer,
@@ -657,8 +661,8 @@ pub fn update<A: Application, E: Executor>(
debug: &mut Debug,
messages: &mut Vec<A::Message>,
window: &winit::window::Window,
- graphics_info: impl FnOnce() -> compositor::Information + Copy,
) where
+ C: Compositor<Renderer = A::Renderer> + 'static,
<A::Renderer as core::Renderer>::Theme: StyleSheet,
{
for message in messages.drain(..) {
@@ -676,6 +680,8 @@ pub fn update<A: Application, E: Executor>(
run_command(
application,
+ compositor,
+ surface,
cache,
state,
renderer,
@@ -686,7 +692,6 @@ pub fn update<A: Application, E: Executor>(
proxy,
debug,
window,
- graphics_info,
);
}
@@ -695,8 +700,10 @@ pub fn update<A: Application, E: Executor>(
}
/// Runs the actions of a [`Command`].
-pub fn run_command<A, E>(
+pub fn run_command<A, C, E>(
application: &A,
+ compositor: &mut C,
+ surface: &mut C::Surface,
cache: &mut user_interface::Cache,
state: &State<A>,
renderer: &mut A::Renderer,
@@ -707,10 +714,10 @@ pub fn run_command<A, E>(
proxy: &mut winit::event_loop::EventLoopProxy<A::Message>,
debug: &mut Debug,
window: &winit::window::Window,
- _graphics_info: impl FnOnce() -> compositor::Information + Copy,
) where
A: Application,
E: Executor,
+ C: Compositor<Renderer = A::Renderer> + 'static,
<A::Renderer as core::Renderer>::Theme: StyleSheet,
{
use crate::runtime::command;
@@ -794,20 +801,36 @@ pub fn run_command<A, E>(
window::Action::GainFocus => {
window.focus_window();
}
- window::Action::ChangeAlwaysOnTop(on_top) => {
- window.set_always_on_top(on_top);
+ window::Action::ChangeLevel(level) => {
+ window.set_window_level(conversion::window_level(level));
}
window::Action::FetchId(tag) => {
proxy
.send_event(tag(window.id().into()))
.expect("Send message to event loop");
}
+ window::Action::Screenshot(tag) => {
+ let bytes = compositor.screenshot(
+ renderer,
+ surface,
+ state.viewport(),
+ state.background_color(),
+ &debug.overlay(),
+ );
+
+ proxy
+ .send_event(tag(window::Screenshot::new(
+ bytes,
+ state.physical_size(),
+ )))
+ .expect("Send message to event loop.")
+ }
},
command::Action::System(action) => match action {
system::Action::QueryInformation(_tag) => {
#[cfg(feature = "system")]
{
- let graphics_info = _graphics_info();
+ let graphics_info = compositor.fetch_information();
let proxy = proxy.clone();
let _ = std::thread::spawn(move || {
diff --git a/winit/src/application/state.rs b/winit/src/application/state.rs
index c37ccca6..967f43f2 100644
--- a/winit/src/application/state.rs
+++ b/winit/src/application/state.rs
@@ -1,7 +1,8 @@
use crate::application::{self, StyleSheet as _};
use crate::conversion;
use crate::core;
-use crate::core::{Color, Point, Size};
+use crate::core::mouse;
+use crate::core::{Color, Size};
use crate::graphics::Viewport;
use crate::runtime::Debug;
use crate::Application;
@@ -20,7 +21,7 @@ where
scale_factor: f64,
viewport: Viewport,
viewport_version: usize,
- cursor_position: winit::dpi::PhysicalPosition<f64>,
+ cursor_position: Option<winit::dpi::PhysicalPosition<f64>>,
modifiers: winit::event::ModifiersState,
theme: <A::Renderer as core::Renderer>::Theme,
appearance: application::Appearance,
@@ -52,8 +53,7 @@ where
scale_factor,
viewport,
viewport_version: 0,
- // TODO: Encode cursor availability in the type-system
- cursor_position: winit::dpi::PhysicalPosition::new(-1.0, -1.0),
+ cursor_position: None,
modifiers: winit::event::ModifiersState::default(),
theme,
appearance,
@@ -89,11 +89,16 @@ where
}
/// Returns the current cursor position of the [`State`].
- pub fn cursor_position(&self) -> Point {
- conversion::cursor_position(
- self.cursor_position,
- self.viewport.scale_factor(),
- )
+ pub fn cursor(&self) -> mouse::Cursor {
+ self.cursor_position
+ .map(|cursor_position| {
+ conversion::cursor_position(
+ cursor_position,
+ self.viewport.scale_factor(),
+ )
+ })
+ .map(mouse::Cursor::Available)
+ .unwrap_or(mouse::Cursor::Unavailable)
}
/// Returns the current keyboard modifiers of the [`State`].
@@ -153,12 +158,10 @@ where
| WindowEvent::Touch(Touch {
location: position, ..
}) => {
- self.cursor_position = *position;
+ self.cursor_position = Some(*position);
}
WindowEvent::CursorLeft { .. } => {
- // TODO: Encode cursor availability in the type-system
- self.cursor_position =
- winit::dpi::PhysicalPosition::new(-1.0, -1.0);
+ self.cursor_position = None;
}
WindowEvent::ModifiersChanged(new_modifiers) => {
self.modifiers = *new_modifiers;
diff --git a/winit/src/conversion.rs b/winit/src/conversion.rs
index a9262184..dcae7074 100644
--- a/winit/src/conversion.rs
+++ b/winit/src/conversion.rs
@@ -140,6 +140,19 @@ pub fn window_event(
}
}
+/// Converts a [`window::Level`] to a [`winit`] window level.
+///
+/// [`winit`]: https://github.com/rust-windowing/winit
+pub fn window_level(level: window::Level) -> winit::window::WindowLevel {
+ match level {
+ window::Level::Normal => winit::window::WindowLevel::Normal,
+ window::Level::AlwaysOnBottom => {
+ winit::window::WindowLevel::AlwaysOnBottom
+ }
+ window::Level::AlwaysOnTop => winit::window::WindowLevel::AlwaysOnTop,
+ }
+}
+
/// Converts a [`Position`] to a [`winit`] logical position for a given monitor.
///
/// [`winit`]: https://github.com/rust-windowing/winit
@@ -250,9 +263,7 @@ pub fn mouse_button(mouse_button: winit::event::MouseButton) -> mouse::Button {
winit::event::MouseButton::Left => mouse::Button::Left,
winit::event::MouseButton::Right => mouse::Button::Right,
winit::event::MouseButton::Middle => mouse::Button::Middle,
- winit::event::MouseButton::Other(other) => {
- mouse::Button::Other(other as u8)
- }
+ winit::event::MouseButton::Other(other) => mouse::Button::Other(other),
}
}
diff --git a/winit/src/lib.rs b/winit/src/lib.rs
index 9f6bcebb..4776ea2c 100644
--- a/winit/src/lib.rs
+++ b/winit/src/lib.rs
@@ -25,11 +25,12 @@
clippy::from_over_into,
clippy::needless_borrow,
clippy::new_without_default,
- clippy::useless_conversion
+ clippy::useless_conversion,
+ unsafe_code
)]
-#![forbid(rust_2018_idioms, unsafe_code)]
+#![forbid(rust_2018_idioms)]
#![allow(clippy::inherent_to_string, clippy::type_complexity)]
-#![cfg_attr(docsrs, feature(doc_cfg))]
+#![cfg_attr(docsrs, feature(doc_auto_cfg))]
pub use iced_graphics as graphics;
pub use iced_runtime as runtime;
pub use iced_runtime::core;
diff --git a/winit/src/settings.rs b/winit/src/settings.rs
index be0ab329..40b3d487 100644
--- a/winit/src/settings.rs
+++ b/winit/src/settings.rs
@@ -22,7 +22,7 @@ mod platform;
pub use platform::PlatformSpecific;
use crate::conversion;
-use crate::core::window::Icon;
+use crate::core::window::{Icon, Level};
use crate::Position;
use winit::monitor::MonitorHandle;
@@ -81,8 +81,8 @@ pub struct Window {
/// Whether the window should be transparent.
pub transparent: bool,
- /// Whether the window will always be on top of other windows.
- pub always_on_top: bool,
+ /// The window [`Level`].
+ pub level: Level,
/// The window icon, which is also usually used in the taskbar
pub icon: Option<Icon>,
@@ -102,7 +102,7 @@ impl fmt::Debug for Window {
.field("resizable", &self.resizable)
.field("decorations", &self.decorations)
.field("transparent", &self.transparent)
- .field("always_on_top", &self.always_on_top)
+ .field("level", &self.level)
.field("icon", &self.icon.is_some())
.field("platform_specific", &self.platform_specific)
.finish()
@@ -128,7 +128,7 @@ impl Window {
.with_decorations(self.decorations)
.with_transparent(self.transparent)
.with_window_icon(self.icon.and_then(conversion::icon))
- .with_always_on_top(self.always_on_top)
+ .with_window_level(conversion::window_level(self.level))
.with_visible(self.visible);
if let Some(position) = conversion::position(
@@ -157,7 +157,9 @@ impl Window {
target_os = "openbsd"
))]
{
- use ::winit::platform::unix::WindowBuilderExtUnix;
+ // `with_name` is available on both `WindowBuilderExtWayland` and `WindowBuilderExtX11` and they do
+ // exactly the same thing. We arbitrarily choose `WindowBuilderExtWayland` here.
+ use ::winit::platform::wayland::WindowBuilderExtWayland;
if let Some(id) = _id {
window_builder = window_builder.with_name(id.clone(), id);
@@ -167,11 +169,11 @@ impl Window {
#[cfg(target_os = "windows")]
{
use winit::platform::windows::WindowBuilderExtWindows;
-
- if let Some(parent) = self.platform_specific.parent {
- window_builder = window_builder.with_parent_window(parent);
+ #[allow(unsafe_code)]
+ unsafe {
+ window_builder = window_builder
+ .with_parent_window(self.platform_specific.parent);
}
-
window_builder = window_builder
.with_drag_and_drop(self.platform_specific.drag_and_drop);
}
@@ -205,7 +207,7 @@ impl Default for Window {
resizable: true,
decorations: true,
transparent: false,
- always_on_top: false,
+ level: Level::default(),
icon: None,
platform_specific: Default::default(),
}
diff --git a/winit/src/settings/windows.rs b/winit/src/settings/windows.rs
index ff03a9c5..45d753bd 100644
--- a/winit/src/settings/windows.rs
+++ b/winit/src/settings/windows.rs
@@ -1,10 +1,11 @@
//! Platform specific settings for Windows.
+use raw_window_handle::RawWindowHandle;
/// The platform specific window settings of an application.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct PlatformSpecific {
/// Parent window
- pub parent: Option<winit::platform::windows::HWND>,
+ pub parent: Option<RawWindowHandle>,
/// Drag and drop support
pub drag_and_drop: bool,