summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Cargo.toml2
-rw-r--r--core/src/element.rs4
-rw-r--r--core/src/image.rs85
-rw-r--r--core/src/layout/node.rs13
-rw-r--r--core/src/lib.rs2
-rw-r--r--core/src/overlay.rs2
-rw-r--r--core/src/overlay/element.rs4
-rw-r--r--core/src/overlay/group.rs2
-rw-r--r--core/src/rectangle.rs56
-rw-r--r--core/src/renderer/null.rs27
-rw-r--r--core/src/svg.rs69
-rw-r--r--core/src/widget.rs2
-rw-r--r--core/src/widget/operation.rs191
-rw-r--r--core/src/widget/operation/focusable.rs30
-rw-r--r--examples/layout/src/main.rs7
-rw-r--r--examples/screenshot/src/main.rs48
-rw-r--r--examples/solar_system/Cargo.toml2
-rw-r--r--examples/solar_system/assets/earth.pngbin0 -> 91888 bytes
-rw-r--r--examples/solar_system/assets/moon.pngbin0 -> 105100 bytes
-rw-r--r--examples/solar_system/assets/sun.pngbin0 -> 114689 bytes
-rw-r--r--examples/solar_system/src/main.rs63
-rw-r--r--examples/toast/src/main.rs4
-rw-r--r--graphics/Cargo.toml1
-rw-r--r--graphics/src/geometry.rs1
-rw-r--r--graphics/src/geometry/frame.rs31
-rw-r--r--graphics/src/image.rs45
-rw-r--r--renderer/src/fallback.rs56
-rw-r--r--runtime/src/lib.rs4
-rw-r--r--runtime/src/multi_window/state.rs2
-rw-r--r--runtime/src/overlay/nested.rs4
-rw-r--r--runtime/src/program/state.rs2
-rw-r--r--runtime/src/user_interface.rs2
-rw-r--r--tiny_skia/Cargo.toml2
-rw-r--r--tiny_skia/src/engine.rs32
-rw-r--r--tiny_skia/src/geometry.rs65
-rw-r--r--tiny_skia/src/layer.rs47
-rw-r--r--tiny_skia/src/lib.rs65
-rw-r--r--wgpu/Cargo.toml2
-rw-r--r--wgpu/src/geometry.rs72
-rw-r--r--wgpu/src/image/mod.rs58
-rw-r--r--wgpu/src/layer.rs43
-rw-r--r--wgpu/src/lib.rs101
-rw-r--r--wgpu/src/shader/image.wgsl12
-rw-r--r--widget/src/button.rs2
-rw-r--r--widget/src/canvas.rs4
-rw-r--r--widget/src/column.rs2
-rw-r--r--widget/src/combo_box.rs38
-rw-r--r--widget/src/container.rs2
-rw-r--r--widget/src/helpers.rs35
-rw-r--r--widget/src/image.rs13
-rw-r--r--widget/src/image/viewer.rs15
-rw-r--r--widget/src/keyed/column.rs2
-rw-r--r--widget/src/lazy.rs2
-rw-r--r--widget/src/lazy/component.rs6
-rw-r--r--widget/src/lazy/responsive.rs2
-rw-r--r--widget/src/mouse_area.rs2
-rw-r--r--widget/src/pane_grid.rs2
-rw-r--r--widget/src/pane_grid/content.rs2
-rw-r--r--widget/src/pane_grid/title_bar.rs2
-rw-r--r--widget/src/row.rs202
-rw-r--r--widget/src/scrollable.rs2
-rw-r--r--widget/src/stack.rs2
-rw-r--r--widget/src/svg.rs10
-rw-r--r--widget/src/text_editor.rs2
-rw-r--r--widget/src/text_input.rs2
-rw-r--r--widget/src/themer.rs4
66 files changed, 1122 insertions, 493 deletions
diff --git a/Cargo.toml b/Cargo.toml
index aa2d950e..65c5007e 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -175,7 +175,7 @@ unicode-segmentation = "1.0"
url = "2.5"
wasm-bindgen-futures = "0.4"
wasm-timer = "0.2"
-web-sys = "=0.3.67"
+web-sys = "0.3.69"
web-time = "1.1"
wgpu = "0.19"
winapi = "0.3"
diff --git a/core/src/element.rs b/core/src/element.rs
index 385d8295..6ebb8a15 100644
--- a/core/src/element.rs
+++ b/core/src/element.rs
@@ -304,7 +304,7 @@ where
tree: &mut Tree,
layout: Layout<'_>,
renderer: &Renderer,
- operation: &mut dyn widget::Operation<()>,
+ operation: &mut dyn widget::Operation,
) {
self.widget.operate(tree, layout, renderer, operation);
}
@@ -440,7 +440,7 @@ where
state: &mut Tree,
layout: Layout<'_>,
renderer: &Renderer,
- operation: &mut dyn widget::Operation<()>,
+ operation: &mut dyn widget::Operation,
) {
self.element
.widget
diff --git a/core/src/image.rs b/core/src/image.rs
index 82ecdd0f..f985636a 100644
--- a/core/src/image.rs
+++ b/core/src/image.rs
@@ -7,6 +7,73 @@ use rustc_hash::FxHasher;
use std::hash::{Hash, Hasher};
use std::path::{Path, PathBuf};
+/// A raster image that can be drawn.
+#[derive(Debug, Clone, PartialEq)]
+pub struct Image<H = Handle> {
+ /// The handle of the image.
+ pub handle: H,
+
+ /// The filter method of the image.
+ pub filter_method: FilterMethod,
+
+ /// The rotation to be applied to the image; on its center.
+ pub rotation: Radians,
+
+ /// The opacity of the image.
+ ///
+ /// 0 means transparent. 1 means opaque.
+ pub opacity: f32,
+
+ /// If set to `true`, the image will be snapped to the pixel grid.
+ ///
+ /// This can avoid graphical glitches, specially when using
+ /// [`FilterMethod::Nearest`].
+ pub snap: bool,
+}
+
+impl Image<Handle> {
+ /// Creates a new [`Image`] with the given handle.
+ pub fn new(handle: impl Into<Handle>) -> Self {
+ Self {
+ handle: handle.into(),
+ filter_method: FilterMethod::default(),
+ rotation: Radians(0.0),
+ opacity: 1.0,
+ snap: false,
+ }
+ }
+
+ /// Sets the filter method of the [`Image`].
+ pub fn filter_method(mut self, filter_method: FilterMethod) -> Self {
+ self.filter_method = filter_method;
+ self
+ }
+
+ /// Sets the rotation of the [`Image`].
+ pub fn rotation(mut self, rotation: impl Into<Radians>) -> Self {
+ self.rotation = rotation.into();
+ self
+ }
+
+ /// Sets the opacity of the [`Image`].
+ pub fn opacity(mut self, opacity: impl Into<f32>) -> Self {
+ self.opacity = opacity.into();
+ self
+ }
+
+ /// Sets whether the [`Image`] should be snapped to the pixel grid.
+ pub fn snap(mut self, snap: bool) -> Self {
+ self.snap = snap;
+ self
+ }
+}
+
+impl From<&Handle> for Image {
+ fn from(handle: &Handle) -> Self {
+ Image::new(handle.clone())
+ }
+}
+
/// A handle of some image data.
#[derive(Clone, PartialEq, Eq)]
pub enum Handle {
@@ -101,6 +168,12 @@ where
}
}
+impl From<&Handle> for Handle {
+ fn from(value: &Handle) -> Self {
+ value.clone()
+ }
+}
+
impl std::fmt::Debug for Handle {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
@@ -166,14 +239,6 @@ pub trait Renderer: crate::Renderer {
/// Returns the dimensions of an image for the given [`Handle`].
fn measure_image(&self, handle: &Self::Handle) -> Size<u32>;
- /// Draws an image with the given [`Handle`] and inside the provided
- /// `bounds`.
- fn draw_image(
- &mut self,
- handle: Self::Handle,
- filter_method: FilterMethod,
- bounds: Rectangle,
- rotation: Radians,
- opacity: f32,
- );
+ /// Draws an [`Image`] inside the provided `bounds`.
+ fn draw_image(&mut self, image: Image<Self::Handle>, bounds: Rectangle);
}
diff --git a/core/src/layout/node.rs b/core/src/layout/node.rs
index 5743a9bd..0c0f90fb 100644
--- a/core/src/layout/node.rs
+++ b/core/src/layout/node.rs
@@ -103,12 +103,13 @@ impl Node {
}
/// Translates the [`Node`] by the given translation.
- pub fn translate(self, translation: impl Into<Vector>) -> Self {
- let translation = translation.into();
+ pub fn translate(mut self, translation: impl Into<Vector>) -> Self {
+ self.translate_mut(translation);
+ self
+ }
- Self {
- bounds: self.bounds + translation,
- ..self
- }
+ /// Translates the [`Node`] by the given translation.
+ pub fn translate_mut(&mut self, translation: impl Into<Vector>) {
+ self.bounds = self.bounds + translation.into();
}
}
diff --git a/core/src/lib.rs b/core/src/lib.rs
index 40a288e5..df599f45 100644
--- a/core/src/lib.rs
+++ b/core/src/lib.rs
@@ -57,6 +57,7 @@ pub use element::Element;
pub use event::Event;
pub use font::Font;
pub use gradient::Gradient;
+pub use image::Image;
pub use layout::Layout;
pub use length::Length;
pub use overlay::Overlay;
@@ -69,6 +70,7 @@ pub use rotation::Rotation;
pub use shadow::Shadow;
pub use shell::Shell;
pub use size::Size;
+pub use svg::Svg;
pub use text::Text;
pub use theme::Theme;
pub use transformation::Transformation;
diff --git a/core/src/overlay.rs b/core/src/overlay.rs
index 3b79970e..f09de831 100644
--- a/core/src/overlay.rs
+++ b/core/src/overlay.rs
@@ -41,7 +41,7 @@ where
&mut self,
_layout: Layout<'_>,
_renderer: &Renderer,
- _operation: &mut dyn widget::Operation<()>,
+ _operation: &mut dyn widget::Operation,
) {
}
diff --git a/core/src/overlay/element.rs b/core/src/overlay/element.rs
index 61e75e8a..32e987a3 100644
--- a/core/src/overlay/element.rs
+++ b/core/src/overlay/element.rs
@@ -92,7 +92,7 @@ where
&mut self,
layout: Layout<'_>,
renderer: &Renderer,
- operation: &mut dyn widget::Operation<()>,
+ operation: &mut dyn widget::Operation,
) {
self.overlay.operate(layout, renderer, operation);
}
@@ -144,7 +144,7 @@ where
&mut self,
layout: Layout<'_>,
renderer: &Renderer,
- operation: &mut dyn widget::Operation<()>,
+ operation: &mut dyn widget::Operation,
) {
self.content.operate(layout, renderer, operation);
}
diff --git a/core/src/overlay/group.rs b/core/src/overlay/group.rs
index cd12eac9..6541d311 100644
--- a/core/src/overlay/group.rs
+++ b/core/src/overlay/group.rs
@@ -132,7 +132,7 @@ where
&mut self,
layout: Layout<'_>,
renderer: &Renderer,
- operation: &mut dyn widget::Operation<()>,
+ operation: &mut dyn widget::Operation,
) {
operation.container(None, layout.bounds(), &mut |operation| {
self.children.iter_mut().zip(layout.children()).for_each(
diff --git a/core/src/rectangle.rs b/core/src/rectangle.rs
index 155cfcbf..cff33991 100644
--- a/core/src/rectangle.rs
+++ b/core/src/rectangle.rs
@@ -47,6 +47,62 @@ impl Rectangle<f32> {
}
}
+ /// Creates a new square [`Rectangle`] with the center at the origin and
+ /// with the given radius.
+ pub fn with_radius(radius: f32) -> Self {
+ Self {
+ x: -radius,
+ y: -radius,
+ width: radius * 2.0,
+ height: radius * 2.0,
+ }
+ }
+
+ /// Creates a new axis-aligned [`Rectangle`] from the given vertices; returning the
+ /// rotation in [`Radians`] that must be applied to the axis-aligned [`Rectangle`]
+ /// to obtain the desired result.
+ pub fn with_vertices(
+ top_left: Point,
+ top_right: Point,
+ bottom_left: Point,
+ ) -> (Rectangle, Radians) {
+ let width = (top_right.x - top_left.x).hypot(top_right.y - top_left.y);
+
+ let height =
+ (bottom_left.x - top_left.x).hypot(bottom_left.y - top_left.y);
+
+ let rotation =
+ (top_right.y - top_left.y).atan2(top_right.x - top_left.x);
+
+ let rotation = if rotation < 0.0 {
+ 2.0 * std::f32::consts::PI + rotation
+ } else {
+ rotation
+ };
+
+ let position = {
+ let center = Point::new(
+ (top_right.x + bottom_left.x) / 2.0,
+ (top_right.y + bottom_left.y) / 2.0,
+ );
+
+ let rotation = -rotation - std::f32::consts::PI * 2.0;
+
+ Point::new(
+ center.x + (top_left.x - center.x) * rotation.cos()
+ - (top_left.y - center.y) * rotation.sin(),
+ center.y
+ + (top_left.x - center.x) * rotation.sin()
+ + (top_left.y - center.y) * rotation.cos(),
+ )
+ };
+
+ (
+ Rectangle::new(position, Size::new(width, height)),
+ Radians(rotation),
+ )
+ }
+
/// Returns the [`Point`] at the center of the [`Rectangle`].
pub fn center(&self) -> Point {
Point::new(self.center_x(), self.center_y())
diff --git a/core/src/renderer/null.rs b/core/src/renderer/null.rs
index 5c7513c6..e3a07280 100644
--- a/core/src/renderer/null.rs
+++ b/core/src/renderer/null.rs
@@ -1,11 +1,10 @@
use crate::alignment;
-use crate::image;
+use crate::image::{self, Image};
use crate::renderer::{self, Renderer};
use crate::svg;
use crate::text::{self, Text};
use crate::{
- Background, Color, Font, Pixels, Point, Radians, Rectangle, Size,
- Transformation,
+ Background, Color, Font, Pixels, Point, Rectangle, Size, Transformation,
};
impl Renderer for () {
@@ -178,21 +177,13 @@ impl text::Editor for () {
}
impl image::Renderer for () {
- type Handle = ();
+ type Handle = image::Handle;
fn measure_image(&self, _handle: &Self::Handle) -> Size<u32> {
Size::default()
}
- fn draw_image(
- &mut self,
- _handle: Self::Handle,
- _filter_method: image::FilterMethod,
- _bounds: Rectangle,
- _rotation: Radians,
- _opacity: f32,
- ) {
- }
+ fn draw_image(&mut self, _image: Image, _bounds: Rectangle) {}
}
impl svg::Renderer for () {
@@ -200,13 +191,5 @@ impl svg::Renderer for () {
Size::default()
}
- fn draw_svg(
- &mut self,
- _handle: svg::Handle,
- _color: Option<Color>,
- _bounds: Rectangle,
- _rotation: Radians,
- _opacity: f32,
- ) {
- }
+ fn draw_svg(&mut self, _svg: svg::Svg, _bounds: Rectangle) {}
}
diff --git a/core/src/svg.rs b/core/src/svg.rs
index 946b8156..ac19b223 100644
--- a/core/src/svg.rs
+++ b/core/src/svg.rs
@@ -7,6 +7,66 @@ use std::hash::{Hash, Hasher as _};
use std::path::PathBuf;
use std::sync::Arc;
+/// A raster image that can be drawn.
+#[derive(Debug, Clone, PartialEq)]
+pub struct Svg<H = Handle> {
+ /// The handle of the [`Svg`].
+ pub handle: H,
+
+ /// The [`Color`] filter to be applied to the [`Svg`].
+ ///
+ /// If some [`Color`] is set, the whole [`Svg`] will be
+ /// painted with it—ignoring any intrinsic colors.
+ ///
+ /// This can be useful for coloring icons programmatically
+ /// (e.g. with a theme).
+ pub color: Option<Color>,
+
+ /// The rotation to be applied to the image; on its center.
+ pub rotation: Radians,
+
+ /// The opacity of the [`Svg`].
+ ///
+ /// 0 means transparent. 1 means opaque.
+ pub opacity: f32,
+}
+
+impl Svg<Handle> {
+ /// Creates a new [`Svg`] with the given handle.
+ pub fn new(handle: impl Into<Handle>) -> Self {
+ Self {
+ handle: handle.into(),
+ color: None,
+ rotation: Radians(0.0),
+ opacity: 1.0,
+ }
+ }
+
+ /// Sets the [`Color`] filter of the [`Svg`].
+ pub fn color(mut self, color: impl Into<Color>) -> Self {
+ self.color = Some(color.into());
+ self
+ }
+
+ /// Sets the rotation of the [`Svg`].
+ pub fn rotation(mut self, rotation: impl Into<Radians>) -> Self {
+ self.rotation = rotation.into();
+ self
+ }
+
+ /// Sets the opacity of the [`Svg`].
+ pub fn opacity(mut self, opacity: impl Into<f32>) -> Self {
+ self.opacity = opacity.into();
+ self
+ }
+}
+
+impl From<&Handle> for Svg {
+ fn from(handle: &Handle) -> Self {
+ Svg::new(handle.clone())
+ }
+}
+
/// A handle of Svg data.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Handle {
@@ -95,12 +155,5 @@ pub trait Renderer: crate::Renderer {
fn measure_svg(&self, handle: &Handle) -> Size<u32>;
/// Draws an SVG with the given [`Handle`], an optional [`Color`] filter, and inside the provided `bounds`.
- fn draw_svg(
- &mut self,
- handle: Handle,
- color: Option<Color>,
- bounds: Rectangle,
- rotation: Radians,
- opacity: f32,
- );
+ fn draw_svg(&mut self, svg: Svg, bounds: Rectangle);
}
diff --git a/core/src/widget.rs b/core/src/widget.rs
index 08cfa55b..c5beea54 100644
--- a/core/src/widget.rs
+++ b/core/src/widget.rs
@@ -105,7 +105,7 @@ where
_state: &mut Tree,
_layout: Layout<'_>,
_renderer: &Renderer,
- _operation: &mut dyn Operation<()>,
+ _operation: &mut dyn Operation,
) {
}
diff --git a/core/src/widget/operation.rs b/core/src/widget/operation.rs
index 3e4ed618..741e1a5f 100644
--- a/core/src/widget/operation.rs
+++ b/core/src/widget/operation.rs
@@ -12,11 +12,12 @@ use crate::{Rectangle, Vector};
use std::any::Any;
use std::fmt;
+use std::marker::PhantomData;
use std::sync::Arc;
/// A piece of logic that can traverse the widget tree of an application in
/// order to query or update some widget state.
-pub trait Operation<T>: Send {
+pub trait Operation<T = ()>: Send {
/// Operates on a widget that contains other widgets.
///
/// The `operate_on_children` function can be called to return control to
@@ -53,6 +54,46 @@ pub trait Operation<T>: Send {
}
}
+impl<T, O> Operation<O> for Box<T>
+where
+ T: Operation<O> + ?Sized,
+{
+ fn container(
+ &mut self,
+ id: Option<&Id>,
+ bounds: Rectangle,
+ operate_on_children: &mut dyn FnMut(&mut dyn Operation<O>),
+ ) {
+ self.as_mut().container(id, bounds, operate_on_children);
+ }
+
+ fn focusable(&mut self, state: &mut dyn Focusable, id: Option<&Id>) {
+ self.as_mut().focusable(state, id);
+ }
+
+ fn scrollable(
+ &mut self,
+ state: &mut dyn Scrollable,
+ id: Option<&Id>,
+ bounds: Rectangle,
+ translation: Vector,
+ ) {
+ self.as_mut().scrollable(state, id, bounds, translation);
+ }
+
+ fn text_input(&mut self, state: &mut dyn TextInput, id: Option<&Id>) {
+ self.as_mut().text_input(state, id);
+ }
+
+ fn custom(&mut self, state: &mut dyn Any, id: Option<&Id>) {
+ self.as_mut().custom(state, id);
+ }
+
+ fn finish(&self) -> Outcome<O> {
+ self.as_ref().finish()
+ }
+}
+
/// The result of an [`Operation`].
pub enum Outcome<T> {
/// The [`Operation`] produced no result.
@@ -78,9 +119,62 @@ where
}
}
+/// Wraps the [`Operation`] in a black box, erasing its returning type.
+pub fn black_box<'a, T, O>(
+ operation: &'a mut dyn Operation<T>,
+) -> impl Operation<O> + 'a
+where
+ T: 'a,
+{
+ struct BlackBox<'a, T> {
+ operation: &'a mut dyn Operation<T>,
+ }
+
+ impl<'a, T, O> Operation<O> for BlackBox<'a, T> {
+ fn container(
+ &mut self,
+ id: Option<&Id>,
+ bounds: Rectangle,
+ operate_on_children: &mut dyn FnMut(&mut dyn Operation<O>),
+ ) {
+ self.operation.container(id, bounds, &mut |operation| {
+ operate_on_children(&mut BlackBox { operation });
+ });
+ }
+
+ fn focusable(&mut self, state: &mut dyn Focusable, id: Option<&Id>) {
+ self.operation.focusable(state, id);
+ }
+
+ fn scrollable(
+ &mut self,
+ state: &mut dyn Scrollable,
+ id: Option<&Id>,
+ bounds: Rectangle,
+ translation: Vector,
+ ) {
+ self.operation.scrollable(state, id, bounds, translation);
+ }
+
+ fn text_input(&mut self, state: &mut dyn TextInput, id: Option<&Id>) {
+ self.operation.text_input(state, id);
+ }
+
+ fn custom(&mut self, state: &mut dyn Any, id: Option<&Id>) {
+ self.operation.custom(state, id);
+ }
+
+ fn finish(&self) -> Outcome<O> {
+ Outcome::None
+ }
+ }
+
+ BlackBox { operation }
+}
+
/// Maps the output of an [`Operation`] using the given function.
pub fn map<A, B>(
- operation: Box<dyn Operation<A>>,
+ operation: impl Operation<A>,
f: impl Fn(A) -> B + Send + Sync + 'static,
) -> impl Operation<B>
where
@@ -88,13 +182,14 @@ where
B: 'static,
{
#[allow(missing_debug_implementations)]
- struct Map<A, B> {
- operation: Box<dyn Operation<A>>,
+ struct Map<O, A, B> {
+ operation: O,
f: Arc<dyn Fn(A) -> B + Send + Sync>,
}
- impl<A, B> Operation<B> for Map<A, B>
+ impl<O, A, B> Operation<B> for Map<O, A, B>
where
+ O: Operation<A>,
A: 'static,
B: 'static,
{
@@ -155,10 +250,7 @@ where
let Self { operation, .. } = self;
- MapRef {
- operation: operation.as_mut(),
- }
- .container(id, bounds, operate_on_children);
+ MapRef { operation }.container(id, bounds, operate_on_children);
}
fn focusable(&mut self, state: &mut dyn Focusable, id: Option<&Id>) {
@@ -201,6 +293,87 @@ where
}
}
+/// Chains the output of an [`Operation`] with the provided function to
+/// build a new [`Operation`].
+pub fn chain<A, B, O>(
+ operation: impl Operation<A> + 'static,
+ f: fn(A) -> O,
+) -> impl Operation<B>
+where
+ A: 'static,
+ B: Send + 'static,
+ O: Operation<B> + 'static,
+{
+ struct Chain<T, O, A, B>
+ where
+ T: Operation<A>,
+ O: Operation<B>,
+ {
+ operation: T,
+ next: fn(A) -> O,
+ _result: PhantomData<B>,
+ }
+
+ impl<T, O, A, B> Operation<B> for Chain<T, O, A, B>
+ where
+ T: Operation<A> + 'static,
+ O: Operation<B> + 'static,
+ A: 'static,
+ B: Send + 'static,
+ {
+ fn container(
+ &mut self,
+ id: Option<&Id>,
+ bounds: Rectangle,
+ operate_on_children: &mut dyn FnMut(&mut dyn Operation<B>),
+ ) {
+ self.operation.container(id, bounds, &mut |operation| {
+ operate_on_children(&mut black_box(operation));
+ });
+ }
+
+ fn focusable(&mut self, state: &mut dyn Focusable, id: Option<&Id>) {
+ self.operation.focusable(state, id);
+ }
+
+ fn scrollable(
+ &mut self,
+ state: &mut dyn Scrollable,
+ id: Option<&Id>,
+ bounds: Rectangle,
+ translation: crate::Vector,
+ ) {
+ self.operation.scrollable(state, id, bounds, translation);
+ }
+
+ fn text_input(&mut self, state: &mut dyn TextInput, id: Option<&Id>) {
+ self.operation.text_input(state, id);
+ }
+
+ fn custom(&mut self, state: &mut dyn std::any::Any, id: Option<&Id>) {
+ self.operation.custom(state, id);
+ }
+
+ fn finish(&self) -> Outcome<B> {
+ match self.operation.finish() {
+ Outcome::None => Outcome::None,
+ Outcome::Some(value) => {
+ Outcome::Chain(Box::new((self.next)(value)))
+ }
+ Outcome::Chain(operation) => {
+ Outcome::Chain(Box::new(chain(operation, self.next)))
+ }
+ }
+ }
+ }
+
+ Chain {
+ operation,
+ next: f,
+ _result: PhantomData,
+ }
+}
+
/// Produces an [`Operation`] that applies the given [`Operation`] to the
/// children of a container with the given [`Id`].
pub fn scope<T: 'static>(
diff --git a/core/src/widget/operation/focusable.rs b/core/src/widget/operation/focusable.rs
index 68c22faa..0a6f2e96 100644
--- a/core/src/widget/operation/focusable.rs
+++ b/core/src/widget/operation/focusable.rs
@@ -1,5 +1,5 @@
//! Operate on widgets that can be focused.
-use crate::widget::operation::{Operation, Outcome};
+use crate::widget::operation::{self, Operation, Outcome};
use crate::widget::Id;
use crate::Rectangle;
@@ -58,19 +58,12 @@ pub fn focus<T>(target: Id) -> impl Operation<T> {
/// Produces an [`Operation`] that generates a [`Count`] and chains it with the
/// provided function to build a new [`Operation`].
-pub fn count<T, O>(f: fn(Count) -> O) -> impl Operation<T>
-where
- O: Operation<T> + 'static,
-{
- struct CountFocusable<O> {
+pub fn count() -> impl Operation<Count> {
+ struct CountFocusable {
count: Count,
- next: fn(Count) -> O,
}
- impl<T, O> Operation<T> for CountFocusable<O>
- where
- O: Operation<T> + 'static,
- {
+ impl Operation<Count> for CountFocusable {
fn focusable(&mut self, state: &mut dyn Focusable, _id: Option<&Id>) {
if state.is_focused() {
self.count.focused = Some(self.count.total);
@@ -83,26 +76,25 @@ where
&mut self,
_id: Option<&Id>,
_bounds: Rectangle,
- operate_on_children: &mut dyn FnMut(&mut dyn Operation<T>),
+ operate_on_children: &mut dyn FnMut(&mut dyn Operation<Count>),
) {
operate_on_children(self);
}
- fn finish(&self) -> Outcome<T> {
- Outcome::Chain(Box::new((self.next)(self.count)))
+ fn finish(&self) -> Outcome<Count> {
+ Outcome::Some(self.count)
}
}
CountFocusable {
count: Count::default(),
- next: f,
}
}
/// Produces an [`Operation`] that searches for the current focused widget, and
/// - if found, focuses the previous focusable widget.
/// - if not found, focuses the last focusable widget.
-pub fn focus_previous<T>() -> impl Operation<T> {
+pub fn focus_previous() -> impl Operation {
struct FocusPrevious {
count: Count,
current: usize,
@@ -136,13 +128,13 @@ pub fn focus_previous<T>() -> impl Operation<T> {
}
}
- count(|count| FocusPrevious { count, current: 0 })
+ operation::chain(count(), |count| FocusPrevious { count, current: 0 })
}
/// Produces an [`Operation`] that searches for the current focused widget, and
/// - if found, focuses the next focusable widget.
/// - if not found, focuses the first focusable widget.
-pub fn focus_next<T>() -> impl Operation<T> {
+pub fn focus_next() -> impl Operation {
struct FocusNext {
count: Count,
current: usize,
@@ -170,7 +162,7 @@ pub fn focus_next<T>() -> impl Operation<T> {
}
}
- count(|count| FocusNext { count, current: 0 })
+ operation::chain(count(), |count| FocusNext { count, current: 0 })
}
/// Produces an [`Operation`] that searches for the current focused widget
diff --git a/examples/layout/src/main.rs b/examples/layout/src/main.rs
index f39e24f9..cb33369b 100644
--- a/examples/layout/src/main.rs
+++ b/examples/layout/src/main.rs
@@ -258,9 +258,10 @@ fn application<'a>() -> Element<'a, Message> {
scrollable(
column![
"Content!",
- square(400),
- square(200),
- square(400),
+ row((1..10).map(|i| square(if i % 2 == 0 { 80 } else { 160 })))
+ .spacing(20)
+ .align_y(Center)
+ .wrap(),
"The end"
]
.spacing(40)
diff --git a/examples/screenshot/src/main.rs b/examples/screenshot/src/main.rs
index 2d980dd9..5c105f6c 100644
--- a/examples/screenshot/src/main.rs
+++ b/examples/screenshot/src/main.rs
@@ -20,7 +20,7 @@ fn main() -> iced::Result {
#[derive(Default)]
struct Example {
- screenshot: Option<Screenshot>,
+ screenshot: Option<(Screenshot, image::Handle)>,
saved_png_path: Option<Result<String, PngError>>,
png_saving: bool,
crop_error: Option<screenshot::CropError>,
@@ -52,10 +52,17 @@ impl Example {
.map(Message::Screenshotted);
}
Message::Screenshotted(screenshot) => {
- self.screenshot = Some(screenshot);
+ self.screenshot = Some((
+ screenshot.clone(),
+ image::Handle::from_rgba(
+ screenshot.size.width,
+ screenshot.size.height,
+ screenshot.bytes,
+ ),
+ ));
}
Message::Png => {
- if let Some(screenshot) = &self.screenshot {
+ if let Some((screenshot, _handle)) = &self.screenshot {
self.png_saving = true;
return Task::perform(
@@ -81,7 +88,7 @@ impl Example {
self.height_input_value = new_value;
}
Message::Crop => {
- if let Some(screenshot) = &self.screenshot {
+ if let Some((screenshot, _handle)) = &self.screenshot {
let cropped = screenshot.crop(Rectangle::<u32> {
x: self.x_input_value.unwrap_or(0),
y: self.y_input_value.unwrap_or(0),
@@ -91,7 +98,14 @@ impl Example {
match cropped {
Ok(screenshot) => {
- self.screenshot = Some(screenshot);
+ self.screenshot = Some((
+ screenshot.clone(),
+ image::Handle::from_rgba(
+ screenshot.size.width,
+ screenshot.size.height,
+ screenshot.bytes,
+ ),
+ ));
self.crop_error = None;
}
Err(crop_error) => {
@@ -106,20 +120,16 @@ impl Example {
}
fn view(&self) -> Element<'_, Message> {
- let image: Element<Message> = if let Some(screenshot) = &self.screenshot
- {
- image(image::Handle::from_rgba(
- screenshot.size.width,
- screenshot.size.height,
- screenshot.clone(),
- ))
- .content_fit(ContentFit::Contain)
- .width(Fill)
- .height(Fill)
- .into()
- } else {
- text("Press the button to take a screenshot!").into()
- };
+ let image: Element<Message> =
+ if let Some((_screenshot, handle)) = &self.screenshot {
+ image(handle)
+ .content_fit(ContentFit::Contain)
+ .width(Fill)
+ .height(Fill)
+ .into()
+ } else {
+ text("Press the button to take a screenshot!").into()
+ };
let image = container(image)
.center_y(FillPortion(2))
diff --git a/examples/solar_system/Cargo.toml b/examples/solar_system/Cargo.toml
index ca64da14..e2c18c50 100644
--- a/examples/solar_system/Cargo.toml
+++ b/examples/solar_system/Cargo.toml
@@ -7,7 +7,7 @@ publish = false
[dependencies]
iced.workspace = true
-iced.features = ["debug", "canvas", "tokio"]
+iced.features = ["debug", "canvas", "image", "tokio"]
rand = "0.8.3"
tracing-subscriber = "0.3"
diff --git a/examples/solar_system/assets/earth.png b/examples/solar_system/assets/earth.png
new file mode 100644
index 00000000..e81321d9
--- /dev/null
+++ b/examples/solar_system/assets/earth.png
Binary files differ
diff --git a/examples/solar_system/assets/moon.png b/examples/solar_system/assets/moon.png
new file mode 100644
index 00000000..03f10cb7
--- /dev/null
+++ b/examples/solar_system/assets/moon.png
Binary files differ
diff --git a/examples/solar_system/assets/sun.png b/examples/solar_system/assets/sun.png
new file mode 100644
index 00000000..29a928a7
--- /dev/null
+++ b/examples/solar_system/assets/sun.png
Binary files differ
diff --git a/examples/solar_system/src/main.rs b/examples/solar_system/src/main.rs
index a6f1ba6f..1e74f2bd 100644
--- a/examples/solar_system/src/main.rs
+++ b/examples/solar_system/src/main.rs
@@ -7,10 +7,9 @@
//!
//! [1]: https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API/Tutorial/Basic_animations#An_animated_solar_system
use iced::mouse;
-use iced::widget::canvas;
-use iced::widget::canvas::gradient;
use iced::widget::canvas::stroke::{self, Stroke};
use iced::widget::canvas::{Geometry, Path};
+use iced::widget::{canvas, image};
use iced::window;
use iced::{
Color, Element, Fill, Point, Rectangle, Renderer, Size, Subscription,
@@ -66,6 +65,9 @@ impl SolarSystem {
#[derive(Debug)]
struct State {
+ sun: image::Handle,
+ earth: image::Handle,
+ moon: image::Handle,
space_cache: canvas::Cache,
system_cache: canvas::Cache,
start: Instant,
@@ -85,6 +87,15 @@ impl State {
let size = window::Settings::default().size;
State {
+ sun: image::Handle::from_bytes(
+ include_bytes!("../assets/sun.png").as_slice(),
+ ),
+ earth: image::Handle::from_bytes(
+ include_bytes!("../assets/earth.png").as_slice(),
+ ),
+ moon: image::Handle::from_bytes(
+ include_bytes!("../assets/moon.png").as_slice(),
+ ),
space_cache: canvas::Cache::default(),
system_cache: canvas::Cache::default(),
start: now,
@@ -132,6 +143,8 @@ impl<Message> canvas::Program<Message> for State {
let background =
self.space_cache.draw(renderer, bounds.size(), |frame| {
+ frame.fill_rectangle(Point::ORIGIN, frame.size(), Color::BLACK);
+
let stars = Path::new(|path| {
for (p, size) in &self.stars {
path.rectangle(*p, Size::new(*size, *size));
@@ -144,17 +157,18 @@ impl<Message> canvas::Program<Message> for State {
let system = self.system_cache.draw(renderer, bounds.size(), |frame| {
let center = frame.center();
+ frame.translate(Vector::new(center.x, center.y));
- let sun = Path::circle(center, Self::SUN_RADIUS);
- let orbit = Path::circle(center, Self::ORBIT_RADIUS);
+ frame.draw_image(
+ Rectangle::with_radius(Self::SUN_RADIUS),
+ &self.sun,
+ );
- frame.fill(&sun, Color::from_rgb8(0xF9, 0xD7, 0x1C));
+ let orbit = Path::circle(Point::ORIGIN, Self::ORBIT_RADIUS);
frame.stroke(
&orbit,
Stroke {
- style: stroke::Style::Solid(Color::from_rgba8(
- 0, 153, 255, 0.1,
- )),
+ style: stroke::Style::Solid(Color::WHITE.scale_alpha(0.1)),
width: 1.0,
line_dash: canvas::LineDash {
offset: 0,
@@ -168,30 +182,21 @@ impl<Message> canvas::Program<Message> for State {
let rotation = (2.0 * PI / 60.0) * elapsed.as_secs() as f32
+ (2.0 * PI / 60_000.0) * elapsed.subsec_millis() as f32;
- frame.with_save(|frame| {
- frame.translate(Vector::new(center.x, center.y));
- frame.rotate(rotation);
- frame.translate(Vector::new(Self::ORBIT_RADIUS, 0.0));
-
- let earth = Path::circle(Point::ORIGIN, Self::EARTH_RADIUS);
-
- 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.rotate(rotation);
+ frame.translate(Vector::new(Self::ORBIT_RADIUS, 0.0));
- frame.fill(&earth, earth_fill);
+ frame.draw_image(
+ Rectangle::with_radius(Self::EARTH_RADIUS),
+ canvas::Image::new(&self.earth).rotation(-rotation * 20.0),
+ );
- frame.with_save(|frame| {
- frame.rotate(rotation * 10.0);
- frame.translate(Vector::new(0.0, Self::MOON_DISTANCE));
+ frame.rotate(rotation * 10.0);
+ frame.translate(Vector::new(0.0, Self::MOON_DISTANCE));
- let moon = Path::circle(Point::ORIGIN, Self::MOON_RADIUS);
- frame.fill(&moon, Color::WHITE);
- });
- });
+ frame.draw_image(
+ Rectangle::with_radius(Self::MOON_RADIUS),
+ &self.moon,
+ );
});
vec![background, system]
diff --git a/examples/toast/src/main.rs b/examples/toast/src/main.rs
index 040c19bd..8f6a836e 100644
--- a/examples/toast/src/main.rs
+++ b/examples/toast/src/main.rs
@@ -347,7 +347,7 @@ mod toast {
state: &mut Tree,
layout: Layout<'_>,
renderer: &Renderer,
- operation: &mut dyn Operation<()>,
+ operation: &mut dyn Operation,
) {
operation.container(None, layout.bounds(), &mut |operation| {
self.content.as_widget().operate(
@@ -589,7 +589,7 @@ mod toast {
&mut self,
layout: Layout<'_>,
renderer: &Renderer,
- operation: &mut dyn widget::Operation<()>,
+ operation: &mut dyn widget::Operation,
) {
operation.container(None, layout.bounds(), &mut |operation| {
self.toasts
diff --git a/graphics/Cargo.toml b/graphics/Cargo.toml
index e8d27d07..7e2d767b 100644
--- a/graphics/Cargo.toml
+++ b/graphics/Cargo.toml
@@ -20,6 +20,7 @@ all-features = true
[features]
geometry = ["lyon_path"]
image = ["dep:image", "kamadak-exif"]
+svg = []
web-colors = []
fira-sans = []
diff --git a/graphics/src/geometry.rs b/graphics/src/geometry.rs
index ab4a7a36..2b4b45a6 100644
--- a/graphics/src/geometry.rs
+++ b/graphics/src/geometry.rs
@@ -16,6 +16,7 @@ pub use stroke::{LineCap, LineDash, LineJoin, Stroke};
pub use style::Style;
pub use text::Text;
+pub use crate::core::{Image, Svg};
pub use crate::gradient::{self, Gradient};
use crate::cache::Cached;
diff --git a/graphics/src/geometry/frame.rs b/graphics/src/geometry/frame.rs
index 377589d7..b5f2f139 100644
--- a/graphics/src/geometry/frame.rs
+++ b/graphics/src/geometry/frame.rs
@@ -1,6 +1,6 @@
//! Draw and generate geometry.
use crate::core::{Point, Radians, Rectangle, Size, Vector};
-use crate::geometry::{self, Fill, Path, Stroke, Text};
+use crate::geometry::{self, Fill, Image, Path, Stroke, Svg, Text};
/// The region of a surface that can be used to draw geometry.
#[allow(missing_debug_implementations)]
@@ -75,6 +75,18 @@ where
self.raw.fill_text(text);
}
+ /// Draws the given [`Image`] on the [`Frame`] inside the given bounds.
+ #[cfg(feature = "image")]
+ pub fn draw_image(&mut self, bounds: Rectangle, image: impl Into<Image>) {
+ self.raw.draw_image(bounds, image);
+ }
+
+ /// Draws the given [`Svg`] on the [`Frame`] inside the given bounds.
+ #[cfg(feature = "svg")]
+ pub fn draw_svg(&mut self, bounds: Rectangle, svg: impl Into<Svg>) {
+ self.raw.draw_svg(bounds, svg);
+ }
+
/// Stores the current transform of the [`Frame`] and executes the given
/// drawing operations, restoring the transform afterwards.
///
@@ -116,8 +128,7 @@ where
let mut frame = self.draft(region);
let result = f(&mut frame);
-
- self.paste(frame, Point::new(region.x, region.y));
+ self.paste(frame);
result
}
@@ -134,8 +145,8 @@ where
}
/// Draws the contents of the given [`Frame`] with origin at the given [`Point`].
- fn paste(&mut self, frame: Self, at: Point) {
- self.raw.paste(frame.raw, at);
+ fn paste(&mut self, frame: Self) {
+ self.raw.paste(frame.raw);
}
/// Applies a translation to the current transform of the [`Frame`].
@@ -186,7 +197,7 @@ pub trait Backend: Sized {
fn scale_nonuniform(&mut self, scale: impl Into<Vector>);
fn draft(&mut self, clip_bounds: Rectangle) -> Self;
- fn paste(&mut self, frame: Self, at: Point);
+ fn paste(&mut self, frame: Self);
fn stroke<'a>(&mut self, path: &Path, stroke: impl Into<Stroke<'a>>);
@@ -199,6 +210,9 @@ pub trait Backend: Sized {
fill: impl Into<Fill>,
);
+ fn draw_image(&mut self, bounds: Rectangle, image: impl Into<Image>);
+ fn draw_svg(&mut self, bounds: Rectangle, svg: impl Into<Svg>);
+
fn into_geometry(self) -> Self::Geometry;
}
@@ -231,7 +245,7 @@ impl Backend for () {
fn scale_nonuniform(&mut self, _scale: impl Into<Vector>) {}
fn draft(&mut self, _clip_bounds: Rectangle) -> Self {}
- fn paste(&mut self, _frame: Self, _at: Point) {}
+ fn paste(&mut self, _frame: Self) {}
fn stroke<'a>(&mut self, _path: &Path, _stroke: impl Into<Stroke<'a>>) {}
@@ -245,5 +259,8 @@ impl Backend for () {
) {
}
+ fn draw_image(&mut self, _bounds: Rectangle, _image: impl Into<Image>) {}
+ fn draw_svg(&mut self, _bounds: Rectangle, _svg: impl Into<Svg>) {}
+
fn into_geometry(self) -> Self::Geometry {}
}
diff --git a/graphics/src/image.rs b/graphics/src/image.rs
index 318592be..67a5e0cf 100644
--- a/graphics/src/image.rs
+++ b/graphics/src/image.rs
@@ -2,57 +2,26 @@
#[cfg(feature = "image")]
pub use ::image as image_rs;
-use crate::core::{image, svg, Color, Radians, Rectangle};
+use crate::core::image;
+use crate::core::svg;
+use crate::core::Rectangle;
/// A raster or vector image.
#[derive(Debug, Clone, PartialEq)]
pub enum Image {
/// A raster image.
- Raster {
- /// The handle of a raster image.
- handle: image::Handle,
+ Raster(image::Image, Rectangle),
- /// The filter method of a raster image.
- filter_method: image::FilterMethod,
-
- /// The bounds of the image.
- bounds: Rectangle,
-
- /// The rotation of the image.
- rotation: Radians,
-
- /// The opacity of the image.
- opacity: f32,
- },
/// A vector image.
- Vector {
- /// The handle of a vector image.
- handle: svg::Handle,
-
- /// The [`Color`] filter
- color: Option<Color>,
-
- /// The bounds of the image.
- bounds: Rectangle,
-
- /// The rotation of the image.
- rotation: Radians,
-
- /// The opacity of the image.
- opacity: f32,
- },
+ Vector(svg::Svg, Rectangle),
}
impl Image {
/// Returns the bounds of the [`Image`].
pub fn bounds(&self) -> Rectangle {
match self {
- Image::Raster {
- bounds, rotation, ..
- }
- | Image::Vector {
- bounds, rotation, ..
- } => bounds.rotate(*rotation),
+ Image::Raster(image, bounds) => bounds.rotate(image.rotation),
+ Image::Vector(svg, bounds) => bounds.rotate(svg.rotation),
}
}
}
diff --git a/renderer/src/fallback.rs b/renderer/src/fallback.rs
index 6a169692..fbd285db 100644
--- a/renderer/src/fallback.rs
+++ b/renderer/src/fallback.rs
@@ -3,7 +3,7 @@ use crate::core::image;
use crate::core::renderer;
use crate::core::svg;
use crate::core::{
- self, Background, Color, Point, Radians, Rectangle, Size, Transformation,
+ self, Background, Color, Image, Point, Rectangle, Size, Svg, Transformation,
};
use crate::graphics;
use crate::graphics::compositor;
@@ -149,25 +149,8 @@ where
delegate!(self, renderer, renderer.measure_image(handle))
}
- fn draw_image(
- &mut self,
- handle: Self::Handle,
- filter_method: image::FilterMethod,
- bounds: Rectangle,
- rotation: Radians,
- opacity: f32,
- ) {
- delegate!(
- self,
- renderer,
- renderer.draw_image(
- handle,
- filter_method,
- bounds,
- rotation,
- opacity
- )
- );
+ fn draw_image(&mut self, image: Image<A::Handle>, bounds: Rectangle) {
+ delegate!(self, renderer, renderer.draw_image(image, bounds));
}
}
@@ -180,19 +163,8 @@ where
delegate!(self, renderer, renderer.measure_svg(handle))
}
- fn draw_svg(
- &mut self,
- handle: svg::Handle,
- color: Option<Color>,
- bounds: Rectangle,
- rotation: Radians,
- opacity: f32,
- ) {
- delegate!(
- self,
- renderer,
- renderer.draw_svg(handle, color, bounds, rotation, opacity)
- );
+ fn draw_svg(&mut self, svg: Svg, bounds: Rectangle) {
+ delegate!(self, renderer, renderer.draw_svg(svg, bounds));
}
}
@@ -441,9 +413,9 @@ where
#[cfg(feature = "geometry")]
mod geometry {
use super::Renderer;
- use crate::core::{Point, Radians, Rectangle, Size, Vector};
+ use crate::core::{Point, Radians, Rectangle, Size, Svg, Vector};
use crate::graphics::cache::{self, Cached};
- use crate::graphics::geometry::{self, Fill, Path, Stroke, Text};
+ use crate::graphics::geometry::{self, Fill, Image, Path, Stroke, Text};
impl<A, B> geometry::Renderer for Renderer<A, B>
where
@@ -572,6 +544,14 @@ mod geometry {
delegate!(self, frame, frame.fill_text(text));
}
+ fn draw_image(&mut self, bounds: Rectangle, image: impl Into<Image>) {
+ delegate!(self, frame, frame.draw_image(bounds, image));
+ }
+
+ fn draw_svg(&mut self, bounds: Rectangle, svg: impl Into<Svg>) {
+ delegate!(self, frame, frame.draw_svg(bounds, svg));
+ }
+
fn push_transform(&mut self) {
delegate!(self, frame, frame.push_transform());
}
@@ -587,13 +567,13 @@ mod geometry {
}
}
- fn paste(&mut self, frame: Self, at: Point) {
+ fn paste(&mut self, frame: Self) {
match (self, frame) {
(Self::Primary(target), Self::Primary(source)) => {
- target.paste(source, at);
+ target.paste(source);
}
(Self::Secondary(target), Self::Secondary(source)) => {
- target.paste(source, at);
+ target.paste(source);
}
_ => unreachable!(),
}
diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs
index f27657d1..7230fc73 100644
--- a/runtime/src/lib.rs
+++ b/runtime/src/lib.rs
@@ -59,7 +59,7 @@ pub enum Action<T> {
},
/// Run a widget operation.
- Widget(Box<dyn widget::Operation<()>>),
+ Widget(Box<dyn widget::Operation>),
/// Run a clipboard action.
Clipboard(clipboard::Action),
@@ -79,7 +79,7 @@ pub enum Action<T> {
impl<T> Action<T> {
/// Creates a new [`Action::Widget`] with the given [`widget::Operation`].
- pub fn widget(operation: impl widget::Operation<()> + 'static) -> Self {
+ pub fn widget(operation: impl widget::Operation + 'static) -> Self {
Self::Widget(Box::new(operation))
}
diff --git a/runtime/src/multi_window/state.rs b/runtime/src/multi_window/state.rs
index 72ce6933..0bec555f 100644
--- a/runtime/src/multi_window/state.rs
+++ b/runtime/src/multi_window/state.rs
@@ -205,7 +205,7 @@ where
pub fn operate(
&mut self,
renderer: &mut P::Renderer,
- operations: impl Iterator<Item = Box<dyn Operation<()>>>,
+ operations: impl Iterator<Item = Box<dyn Operation>>,
bounds: Size,
debug: &mut Debug,
) {
diff --git a/runtime/src/overlay/nested.rs b/runtime/src/overlay/nested.rs
index 11eee41c..da3e6929 100644
--- a/runtime/src/overlay/nested.rs
+++ b/runtime/src/overlay/nested.rs
@@ -131,13 +131,13 @@ where
&mut self,
layout: Layout<'_>,
renderer: &Renderer,
- operation: &mut dyn widget::Operation<()>,
+ operation: &mut dyn widget::Operation,
) {
fn recurse<Message, Theme, Renderer>(
element: &mut overlay::Element<'_, Message, Theme, Renderer>,
layout: Layout<'_>,
renderer: &Renderer,
- operation: &mut dyn widget::Operation<()>,
+ operation: &mut dyn widget::Operation,
) where
Renderer: renderer::Renderer,
{
diff --git a/runtime/src/program/state.rs b/runtime/src/program/state.rs
index e51ad0cb..c377814a 100644
--- a/runtime/src/program/state.rs
+++ b/runtime/src/program/state.rs
@@ -178,7 +178,7 @@ where
pub fn operate(
&mut self,
renderer: &mut P::Renderer,
- operations: impl Iterator<Item = Box<dyn Operation<()>>>,
+ operations: impl Iterator<Item = Box<dyn Operation>>,
bounds: Size,
debug: &mut Debug,
) {
diff --git a/runtime/src/user_interface.rs b/runtime/src/user_interface.rs
index 858b1a2d..11ebb381 100644
--- a/runtime/src/user_interface.rs
+++ b/runtime/src/user_interface.rs
@@ -566,7 +566,7 @@ where
pub fn operate(
&mut self,
renderer: &Renderer,
- operation: &mut dyn widget::Operation<()>,
+ operation: &mut dyn widget::Operation,
) {
self.root.as_widget().operate(
&mut self.state,
diff --git a/tiny_skia/Cargo.toml b/tiny_skia/Cargo.toml
index 32ead3e0..323233f0 100644
--- a/tiny_skia/Cargo.toml
+++ b/tiny_skia/Cargo.toml
@@ -15,7 +15,7 @@ workspace = true
[features]
image = ["iced_graphics/image"]
-svg = ["resvg"]
+svg = ["iced_graphics/svg", "resvg"]
geometry = ["iced_graphics/geometry"]
[dependencies]
diff --git a/tiny_skia/src/engine.rs b/tiny_skia/src/engine.rs
index 898657c8..196c36cf 100644
--- a/tiny_skia/src/engine.rs
+++ b/tiny_skia/src/engine.rs
@@ -550,13 +550,7 @@ impl Engine {
) {
match image {
#[cfg(feature = "image")]
- Image::Raster {
- handle,
- filter_method,
- bounds,
- rotation,
- opacity,
- } => {
+ Image::Raster(raster, bounds) => {
let physical_bounds = *bounds * _transformation;
if !_clip_bounds.intersects(&physical_bounds) {
@@ -567,7 +561,7 @@ impl Engine {
.then_some(_clip_mask as &_);
let center = physical_bounds.center();
- let radians = f32::from(*rotation);
+ let radians = f32::from(raster.rotation);
let transform = into_transform(_transformation).post_rotate_at(
radians.to_degrees(),
@@ -576,23 +570,17 @@ impl Engine {
);
self.raster_pipeline.draw(
- handle,
- *filter_method,
+ &raster.handle,
+ raster.filter_method,
*bounds,
- *opacity,
+ raster.opacity,
_pixels,
transform,
clip_mask,
);
}
#[cfg(feature = "svg")]
- Image::Vector {
- handle,
- color,
- bounds,
- rotation,
- opacity,
- } => {
+ Image::Vector(svg, bounds) => {
let physical_bounds = *bounds * _transformation;
if !_clip_bounds.intersects(&physical_bounds) {
@@ -603,7 +591,7 @@ impl Engine {
.then_some(_clip_mask as &_);
let center = physical_bounds.center();
- let radians = f32::from(*rotation);
+ let radians = f32::from(svg.rotation);
let transform = into_transform(_transformation).post_rotate_at(
radians.to_degrees(),
@@ -612,10 +600,10 @@ impl Engine {
);
self.vector_pipeline.draw(
- handle,
- *color,
+ &svg.handle,
+ svg.color,
physical_bounds,
- *opacity,
+ svg.opacity,
_pixels,
transform,
clip_mask,
diff --git a/tiny_skia/src/geometry.rs b/tiny_skia/src/geometry.rs
index 02b6e1b9..659612d1 100644
--- a/tiny_skia/src/geometry.rs
+++ b/tiny_skia/src/geometry.rs
@@ -1,10 +1,10 @@
use crate::core::text::LineHeight;
-use crate::core::{Pixels, Point, Radians, Rectangle, Size, Vector};
+use crate::core::{self, Pixels, Point, Radians, Rectangle, Size, Svg, Vector};
use crate::graphics::cache::{self, Cached};
use crate::graphics::geometry::fill::{self, Fill};
use crate::graphics::geometry::stroke::{self, Stroke};
use crate::graphics::geometry::{self, Path, Style};
-use crate::graphics::{Gradient, Text};
+use crate::graphics::{self, Gradient, Image, Text};
use crate::Primitive;
use std::rc::Rc;
@@ -13,6 +13,7 @@ use std::rc::Rc;
pub enum Geometry {
Live {
text: Vec<Text>,
+ images: Vec<graphics::Image>,
primitives: Vec<Primitive>,
clip_bounds: Rectangle,
},
@@ -22,6 +23,7 @@ pub enum Geometry {
#[derive(Debug, Clone)]
pub struct Cache {
pub text: Rc<[Text]>,
+ pub images: Rc<[graphics::Image]>,
pub primitives: Rc<[Primitive]>,
pub clip_bounds: Rectangle,
}
@@ -37,10 +39,12 @@ impl Cached for Geometry {
match self {
Self::Live {
primitives,
+ images,
text,
clip_bounds,
} => Cache {
primitives: Rc::from(primitives),
+ images: Rc::from(images),
text: Rc::from(text),
clip_bounds,
},
@@ -55,6 +59,7 @@ pub struct Frame {
transform: tiny_skia::Transform,
stack: Vec<tiny_skia::Transform>,
primitives: Vec<Primitive>,
+ images: Vec<graphics::Image>,
text: Vec<Text>,
}
@@ -68,6 +73,7 @@ impl Frame {
clip_bounds,
stack: Vec::new(),
primitives: Vec::new(),
+ images: Vec::new(),
text: Vec::new(),
transform: tiny_skia::Transform::from_translate(
clip_bounds.x,
@@ -238,7 +244,7 @@ impl geometry::frame::Backend for Frame {
Self::with_clip(clip_bounds)
}
- fn paste(&mut self, frame: Self, _at: Point) {
+ fn paste(&mut self, frame: Self) {
self.primitives.extend(frame.primitives);
self.text.extend(frame.text);
}
@@ -269,10 +275,63 @@ impl geometry::frame::Backend for Frame {
fn into_geometry(self) -> Geometry {
Geometry::Live {
primitives: self.primitives,
+ images: self.images,
text: self.text,
clip_bounds: self.clip_bounds,
}
}
+
+ fn draw_image(&mut self, bounds: Rectangle, image: impl Into<core::Image>) {
+ let mut image = image.into();
+
+ let (bounds, external_rotation) =
+ transform_rectangle(bounds, self.transform);
+
+ image.rotation += external_rotation;
+
+ self.images.push(graphics::Image::Raster(image, bounds));
+ }
+
+ fn draw_svg(&mut self, bounds: Rectangle, svg: impl Into<Svg>) {
+ let mut svg = svg.into();
+
+ let (bounds, external_rotation) =
+ transform_rectangle(bounds, self.transform);
+
+ svg.rotation += external_rotation;
+
+ self.images.push(Image::Vector(svg, bounds));
+ }
+}
+
+fn transform_rectangle(
+ rectangle: Rectangle,
+ transform: tiny_skia::Transform,
+) -> (Rectangle, Radians) {
+ let mut top_left = tiny_skia::Point {
+ x: rectangle.x,
+ y: rectangle.y,
+ };
+
+ let mut top_right = tiny_skia::Point {
+ x: rectangle.x + rectangle.width,
+ y: rectangle.y,
+ };
+
+ let mut bottom_left = tiny_skia::Point {
+ x: rectangle.x,
+ y: rectangle.y + rectangle.height,
+ };
+
+ transform.map_point(&mut top_left);
+ transform.map_point(&mut top_right);
+ transform.map_point(&mut bottom_left);
+
+ Rectangle::with_vertices(
+ Point::new(top_left.x, top_left.y),
+ Point::new(top_right.x, top_right.y),
+ Point::new(bottom_left.x, bottom_left.y),
+ )
}
fn convert_path(path: &Path) -> Option<tiny_skia::Path> {
diff --git a/tiny_skia/src/layer.rs b/tiny_skia/src/layer.rs
index 48fca1d8..bdfd4d38 100644
--- a/tiny_skia/src/layer.rs
+++ b/tiny_skia/src/layer.rs
@@ -1,6 +1,6 @@
+use crate::core::renderer::Quad;
use crate::core::{
- image, renderer::Quad, svg, Background, Color, Point, Radians, Rectangle,
- Transformation,
+ self, Background, Color, Point, Rectangle, Svg, Transformation,
};
use crate::graphics::damage;
use crate::graphics::layer;
@@ -72,7 +72,7 @@ impl Layer {
pub fn draw_text(
&mut self,
- text: crate::core::Text,
+ text: core::Text,
position: Point,
color: Color,
clip_bounds: Rectangle,
@@ -115,42 +115,35 @@ impl Layer {
.push(Item::Cached(text, clip_bounds, transformation));
}
- pub fn draw_image(
+ pub fn draw_image(&mut self, image: Image, transformation: Transformation) {
+ match image {
+ Image::Raster(raster, bounds) => {
+ self.draw_raster(raster, bounds, transformation);
+ }
+ Image::Vector(svg, bounds) => {
+ self.draw_svg(svg, bounds, transformation);
+ }
+ }
+ }
+
+ pub fn draw_raster(
&mut self,
- handle: image::Handle,
- filter_method: image::FilterMethod,
+ image: core::Image,
bounds: Rectangle,
transformation: Transformation,
- rotation: Radians,
- opacity: f32,
) {
- let image = Image::Raster {
- handle,
- filter_method,
- bounds: bounds * transformation,
- rotation,
- opacity,
- };
+ let image = Image::Raster(image, bounds * transformation);
self.images.push(image);
}
pub fn draw_svg(
&mut self,
- handle: svg::Handle,
- color: Option<Color>,
+ svg: Svg,
bounds: Rectangle,
transformation: Transformation,
- rotation: Radians,
- opacity: f32,
) {
- let svg = Image::Vector {
- handle,
- color,
- bounds: bounds * transformation,
- rotation,
- opacity,
- };
+ let svg = Image::Vector(svg, bounds * transformation);
self.images.push(svg);
}
@@ -293,7 +286,7 @@ impl graphics::Layer for Layer {
fn flush(&mut self) {}
- fn resize(&mut self, bounds: graphics::core::Rectangle) {
+ fn resize(&mut self, bounds: Rectangle) {
self.bounds = bounds;
}
diff --git a/tiny_skia/src/lib.rs b/tiny_skia/src/lib.rs
index 1aabff00..758921d4 100644
--- a/tiny_skia/src/lib.rs
+++ b/tiny_skia/src/lib.rs
@@ -178,6 +178,16 @@ impl Renderer {
engine::adjust_clip_mask(clip_mask, clip_bounds);
}
+ for image in &layer.images {
+ self.engine.draw_image(
+ image,
+ Transformation::scale(scale_factor),
+ pixels,
+ clip_mask,
+ clip_bounds,
+ );
+ }
+
for group in &layer.text {
for text in group.as_slice() {
self.engine.draw_text(
@@ -190,16 +200,6 @@ impl Renderer {
);
}
}
-
- for image in &layer.images {
- self.engine.draw_image(
- image,
- Transformation::scale(scale_factor),
- pixels,
- clip_mask,
- clip_bounds,
- );
- }
}
if !overlay.is_empty() {
@@ -330,6 +330,7 @@ impl graphics::geometry::Renderer for Renderer {
match geometry {
Geometry::Live {
primitives,
+ images,
text,
clip_bounds,
} => {
@@ -339,6 +340,10 @@ impl graphics::geometry::Renderer for Renderer {
transformation,
);
+ for image in images {
+ layer.draw_image(image, transformation);
+ }
+
layer.draw_text_group(text, clip_bounds, transformation);
}
Geometry::Cache(cache) => {
@@ -348,6 +353,10 @@ impl graphics::geometry::Renderer for Renderer {
transformation,
);
+ for image in cache.images.iter() {
+ layer.draw_image(image.clone(), transformation);
+ }
+
layer.draw_text_cache(
cache.text,
cache.clip_bounds,
@@ -372,23 +381,9 @@ impl core::image::Renderer for Renderer {
self.engine.raster_pipeline.dimensions(handle)
}
- fn draw_image(
- &mut self,
- handle: Self::Handle,
- filter_method: core::image::FilterMethod,
- bounds: Rectangle,
- rotation: core::Radians,
- opacity: f32,
- ) {
+ fn draw_image(&mut self, image: core::Image, bounds: Rectangle) {
let (layer, transformation) = self.layers.current_mut();
- layer.draw_image(
- handle,
- filter_method,
- bounds,
- transformation,
- rotation,
- opacity,
- );
+ layer.draw_raster(image, bounds, transformation);
}
}
@@ -401,23 +396,9 @@ impl core::svg::Renderer for Renderer {
self.engine.vector_pipeline.viewport_dimensions(handle)
}
- fn draw_svg(
- &mut self,
- handle: core::svg::Handle,
- color: Option<Color>,
- bounds: Rectangle,
- rotation: core::Radians,
- opacity: f32,
- ) {
+ fn draw_svg(&mut self, svg: core::Svg, bounds: Rectangle) {
let (layer, transformation) = self.layers.current_mut();
- layer.draw_svg(
- handle,
- color,
- bounds,
- transformation,
- rotation,
- opacity,
- );
+ layer.draw_svg(svg, bounds, transformation);
}
}
diff --git a/wgpu/Cargo.toml b/wgpu/Cargo.toml
index 30545fa2..b13ecb36 100644
--- a/wgpu/Cargo.toml
+++ b/wgpu/Cargo.toml
@@ -20,7 +20,7 @@ all-features = true
[features]
geometry = ["iced_graphics/geometry", "lyon"]
image = ["iced_graphics/image"]
-svg = ["resvg/text"]
+svg = ["iced_graphics/svg", "resvg/text"]
web-colors = ["iced_graphics/web-colors"]
webgl = ["wgpu/webgl"]
diff --git a/wgpu/src/geometry.rs b/wgpu/src/geometry.rs
index f6213e1d..be65ba36 100644
--- a/wgpu/src/geometry.rs
+++ b/wgpu/src/geometry.rs
@@ -1,7 +1,7 @@
//! Build and draw geometry.
use crate::core::text::LineHeight;
use crate::core::{
- Pixels, Point, Radians, Rectangle, Size, Transformation, Vector,
+ self, Pixels, Point, Radians, Rectangle, Size, Svg, Transformation, Vector,
};
use crate::graphics::cache::{self, Cached};
use crate::graphics::color;
@@ -11,7 +11,7 @@ use crate::graphics::geometry::{
};
use crate::graphics::gradient::{self, Gradient};
use crate::graphics::mesh::{self, Mesh};
-use crate::graphics::{self, Text};
+use crate::graphics::{Image, Text};
use crate::text;
use crate::triangle;
@@ -19,16 +19,22 @@ use lyon::geom::euclid;
use lyon::tessellation;
use std::borrow::Cow;
+use std::sync::Arc;
#[derive(Debug)]
pub enum Geometry {
- Live { meshes: Vec<Mesh>, text: Vec<Text> },
+ Live {
+ meshes: Vec<Mesh>,
+ images: Vec<Image>,
+ text: Vec<Text>,
+ },
Cached(Cache),
}
#[derive(Debug, Clone)]
pub struct Cache {
pub meshes: Option<triangle::Cache>,
+ pub images: Option<Arc<[Image]>>,
pub text: Option<text::Cache>,
}
@@ -45,7 +51,17 @@ impl Cached for Geometry {
previous: Option<Self::Cache>,
) -> Self::Cache {
match self {
- Self::Live { meshes, text } => {
+ Self::Live {
+ meshes,
+ images,
+ text,
+ } => {
+ let images = if images.is_empty() {
+ None
+ } else {
+ Some(Arc::from(images))
+ };
+
if let Some(mut previous) = previous {
if let Some(cache) = &mut previous.meshes {
cache.update(meshes);
@@ -59,10 +75,13 @@ impl Cached for Geometry {
previous.text = text::Cache::new(group, text);
}
+ previous.images = images;
+
previous
} else {
Cache {
meshes: triangle::Cache::new(meshes),
+ images,
text: text::Cache::new(group, text),
}
}
@@ -78,6 +97,7 @@ pub struct Frame {
clip_bounds: Rectangle,
buffers: BufferStack,
meshes: Vec<Mesh>,
+ images: Vec<Image>,
text: Vec<Text>,
transforms: Transforms,
fill_tessellator: tessellation::FillTessellator,
@@ -96,6 +116,7 @@ impl Frame {
clip_bounds: bounds,
buffers: BufferStack::new(),
meshes: Vec::new(),
+ images: Vec::new(),
text: Vec::new(),
transforms: Transforms {
previous: Vec::new(),
@@ -270,7 +291,7 @@ impl geometry::frame::Backend for Frame {
height: f32::INFINITY,
};
- self.text.push(graphics::Text::Cached {
+ self.text.push(Text::Cached {
content: text.content,
bounds,
color: text.color,
@@ -335,10 +356,11 @@ impl geometry::frame::Backend for Frame {
Frame::with_clip(clip_bounds)
}
- fn paste(&mut self, frame: Frame, _at: Point) {
+ fn paste(&mut self, frame: Frame) {
self.meshes
.extend(frame.buffers.into_meshes(frame.clip_bounds));
+ self.images.extend(frame.images);
self.text.extend(frame.text);
}
@@ -348,9 +370,32 @@ impl geometry::frame::Backend for Frame {
Geometry::Live {
meshes: self.meshes,
+ images: self.images,
text: self.text,
}
}
+
+ fn draw_image(&mut self, bounds: Rectangle, image: impl Into<core::Image>) {
+ let mut image = image.into();
+
+ let (bounds, external_rotation) =
+ self.transforms.current.transform_rectangle(bounds);
+
+ image.rotation += external_rotation;
+
+ self.images.push(Image::Raster(image, bounds));
+ }
+
+ fn draw_svg(&mut self, bounds: Rectangle, svg: impl Into<Svg>) {
+ let mut svg = svg.into();
+
+ let (bounds, external_rotation) =
+ self.transforms.current.transform_rectangle(bounds);
+
+ svg.rotation += external_rotation;
+
+ self.images.push(Image::Vector(svg, bounds));
+ }
}
enum Buffer {
@@ -518,6 +563,21 @@ impl Transform {
gradient
}
+
+ fn transform_rectangle(
+ &self,
+ rectangle: Rectangle,
+ ) -> (Rectangle, Radians) {
+ let top_left = self.transform_point(rectangle.position());
+ let top_right = self.transform_point(
+ rectangle.position() + Vector::new(rectangle.width, 0.0),
+ );
+ let bottom_left = self.transform_point(
+ rectangle.position() + Vector::new(0.0, rectangle.height),
+ );
+
+ Rectangle::with_vertices(top_left, top_right, bottom_left)
+ }
}
struct GradientVertex2DBuilder {
gradient: gradient::Packed,
diff --git a/wgpu/src/image/mod.rs b/wgpu/src/image/mod.rs
index daa2fe16..1b16022a 100644
--- a/wgpu/src/image/mod.rs
+++ b/wgpu/src/image/mod.rs
@@ -149,6 +149,8 @@ impl Pipeline {
6 => Float32x2,
// Layer
7 => Sint32,
+ // Snap
+ 8 => Uint32,
),
}],
},
@@ -212,31 +214,24 @@ impl Pipeline {
transformation: Transformation,
scale: f32,
) {
- let transformation = transformation * Transformation::scale(scale);
-
let nearest_instances: &mut Vec<Instance> = &mut Vec::new();
let linear_instances: &mut Vec<Instance> = &mut Vec::new();
for image in images {
match &image {
#[cfg(feature = "image")]
- Image::Raster {
- handle,
- filter_method,
- bounds,
- rotation,
- opacity,
- } => {
+ Image::Raster(image, bounds) => {
if let Some(atlas_entry) =
- cache.upload_raster(device, encoder, handle)
+ cache.upload_raster(device, encoder, &image.handle)
{
add_instances(
[bounds.x, bounds.y],
[bounds.width, bounds.height],
- f32::from(*rotation),
- *opacity,
+ f32::from(image.rotation),
+ image.opacity,
+ image.snap,
atlas_entry,
- match filter_method {
+ match image.filter_method {
crate::core::image::FilterMethod::Nearest => {
nearest_instances
}
@@ -251,23 +246,23 @@ impl Pipeline {
Image::Raster { .. } => {}
#[cfg(feature = "svg")]
- Image::Vector {
- handle,
- color,
- bounds,
- rotation,
- opacity,
- } => {
+ Image::Vector(svg, bounds) => {
let size = [bounds.width, bounds.height];
if let Some(atlas_entry) = cache.upload_vector(
- device, encoder, handle, *color, size, scale,
+ device,
+ encoder,
+ &svg.handle,
+ svg.color,
+ size,
+ scale,
) {
add_instances(
[bounds.x, bounds.y],
size,
- f32::from(*rotation),
- *opacity,
+ f32::from(svg.rotation),
+ svg.opacity,
+ true,
atlas_entry,
nearest_instances,
);
@@ -300,6 +295,7 @@ impl Pipeline {
nearest_instances,
linear_instances,
transformation,
+ scale,
);
self.prepare_layer += 1;
@@ -375,9 +371,12 @@ impl Layer {
nearest_instances: &[Instance],
linear_instances: &[Instance],
transformation: Transformation,
+ scale_factor: f32,
) {
let uniforms = Uniforms {
transform: transformation.into(),
+ scale_factor,
+ _padding: [0.0; 3],
};
let bytes = bytemuck::bytes_of(&uniforms);
@@ -492,6 +491,7 @@ struct Instance {
_position_in_atlas: [f32; 2],
_size_in_atlas: [f32; 2],
_layer: u32,
+ _snap: u32,
}
impl Instance {
@@ -502,6 +502,10 @@ impl Instance {
#[derive(Debug, Clone, Copy, Zeroable, Pod)]
struct Uniforms {
transform: [f32; 16],
+ scale_factor: f32,
+ // Uniforms must be aligned to their largest member,
+ // this uses a mat4x4<f32> which aligns to 16, so align to that
+ _padding: [f32; 3],
}
fn add_instances(
@@ -509,6 +513,7 @@ fn add_instances(
image_size: [f32; 2],
rotation: f32,
opacity: f32,
+ snap: bool,
entry: &atlas::Entry,
instances: &mut Vec<Instance>,
) {
@@ -525,6 +530,7 @@ fn add_instances(
image_size,
rotation,
opacity,
+ snap,
allocation,
instances,
);
@@ -554,8 +560,8 @@ fn add_instances(
];
add_instance(
- position, center, size, rotation, opacity, allocation,
- instances,
+ position, center, size, rotation, opacity, snap,
+ allocation, instances,
);
}
}
@@ -569,6 +575,7 @@ fn add_instance(
size: [f32; 2],
rotation: f32,
opacity: f32,
+ snap: bool,
allocation: &atlas::Allocation,
instances: &mut Vec<Instance>,
) {
@@ -591,6 +598,7 @@ fn add_instance(
(height as f32 - 1.0) / atlas::SIZE as f32,
],
_layer: layer as u32,
+ _snap: snap as u32,
};
instances.push(instance);
diff --git a/wgpu/src/layer.rs b/wgpu/src/layer.rs
index 9551311d..68d5a015 100644
--- a/wgpu/src/layer.rs
+++ b/wgpu/src/layer.rs
@@ -1,5 +1,5 @@
use crate::core::{
- renderer, Background, Color, Point, Radians, Rectangle, Transformation,
+ self, renderer, Background, Color, Point, Rectangle, Svg, Transformation,
};
use crate::graphics;
use crate::graphics::color;
@@ -20,8 +20,8 @@ pub struct Layer {
pub quads: quad::Batch,
pub triangles: triangle::Batch,
pub primitives: primitive::Batch,
- pub text: text::Batch,
pub images: image::Batch,
+ pub text: text::Batch,
pending_meshes: Vec<Mesh>,
pending_text: Vec<Text>,
}
@@ -112,42 +112,35 @@ impl Layer {
self.pending_text.push(text);
}
- pub fn draw_image(
+ pub fn draw_image(&mut self, image: Image, transformation: Transformation) {
+ match image {
+ Image::Raster(image, bounds) => {
+ self.draw_raster(image, bounds, transformation);
+ }
+ Image::Vector(svg, bounds) => {
+ self.draw_svg(svg, bounds, transformation);
+ }
+ }
+ }
+
+ pub fn draw_raster(
&mut self,
- handle: crate::core::image::Handle,
- filter_method: crate::core::image::FilterMethod,
+ image: core::Image,
bounds: Rectangle,
transformation: Transformation,
- rotation: Radians,
- opacity: f32,
) {
- let image = Image::Raster {
- handle,
- filter_method,
- bounds: bounds * transformation,
- rotation,
- opacity,
- };
+ let image = Image::Raster(image, bounds * transformation);
self.images.push(image);
}
pub fn draw_svg(
&mut self,
- handle: crate::core::svg::Handle,
- color: Option<Color>,
+ svg: Svg,
bounds: Rectangle,
transformation: Transformation,
- rotation: Radians,
- opacity: f32,
) {
- let svg = Image::Vector {
- handle,
- color,
- bounds: bounds * transformation,
- rotation,
- opacity,
- };
+ let svg = Image::Vector(svg, bounds * transformation);
self.images.push(svg);
}
diff --git a/wgpu/src/lib.rs b/wgpu/src/lib.rs
index ad88ce3e..39167514 100644
--- a/wgpu/src/lib.rs
+++ b/wgpu/src/lib.rs
@@ -182,19 +182,6 @@ impl Renderer {
}
}
- if !layer.text.is_empty() {
- engine.text_pipeline.prepare(
- device,
- queue,
- &self.text_viewport,
- encoder,
- &mut self.text_storage,
- &layer.text,
- layer.bounds,
- Transformation::scale(scale_factor),
- );
- }
-
#[cfg(any(feature = "svg", feature = "image"))]
if !layer.images.is_empty() {
engine.image_pipeline.prepare(
@@ -207,6 +194,19 @@ impl Renderer {
scale_factor,
);
}
+
+ if !layer.text.is_empty() {
+ engine.text_pipeline.prepare(
+ device,
+ queue,
+ &self.text_viewport,
+ encoder,
+ &mut self.text_storage,
+ &layer.text,
+ layer.bounds,
+ Transformation::scale(scale_factor),
+ );
+ }
}
}
@@ -359,17 +359,6 @@ impl Renderer {
));
}
- if !layer.text.is_empty() {
- text_layer += engine.text_pipeline.render(
- &self.text_viewport,
- &self.text_storage,
- text_layer,
- &layer.text,
- scissor_rect,
- &mut render_pass,
- );
- }
-
#[cfg(any(feature = "svg", feature = "image"))]
if !layer.images.is_empty() {
engine.image_pipeline.render(
@@ -381,6 +370,17 @@ impl Renderer {
image_layer += 1;
}
+
+ if !layer.text.is_empty() {
+ text_layer += engine.text_pipeline.render(
+ &self.text_viewport,
+ &self.text_storage,
+ text_layer,
+ &layer.text,
+ scissor_rect,
+ &mut render_pass,
+ );
+ }
}
let _ = ManuallyDrop::into_inner(render_pass);
@@ -527,23 +527,9 @@ impl core::image::Renderer for Renderer {
self.image_cache.borrow_mut().measure_image(handle)
}
- fn draw_image(
- &mut self,
- handle: Self::Handle,
- filter_method: core::image::FilterMethod,
- bounds: Rectangle,
- rotation: core::Radians,
- opacity: f32,
- ) {
+ fn draw_image(&mut self, image: core::Image, bounds: Rectangle) {
let (layer, transformation) = self.layers.current_mut();
- layer.draw_image(
- handle,
- filter_method,
- bounds,
- transformation,
- rotation,
- opacity,
- );
+ layer.draw_raster(image, bounds, transformation);
}
}
@@ -553,23 +539,9 @@ impl core::svg::Renderer for Renderer {
self.image_cache.borrow_mut().measure_svg(handle)
}
- fn draw_svg(
- &mut self,
- handle: core::svg::Handle,
- color_filter: Option<Color>,
- bounds: Rectangle,
- rotation: core::Radians,
- opacity: f32,
- ) {
+ fn draw_svg(&mut self, svg: core::Svg, bounds: Rectangle) {
let (layer, transformation) = self.layers.current_mut();
- layer.draw_svg(
- handle,
- color_filter,
- bounds,
- transformation,
- rotation,
- opacity,
- );
+ layer.draw_svg(svg, bounds, transformation);
}
}
@@ -593,8 +565,17 @@ impl graphics::geometry::Renderer for Renderer {
let (layer, transformation) = self.layers.current_mut();
match geometry {
- Geometry::Live { meshes, text } => {
+ Geometry::Live {
+ meshes,
+ images,
+ text,
+ } => {
layer.draw_mesh_group(meshes, transformation);
+
+ for image in images {
+ layer.draw_image(image, transformation);
+ }
+
layer.draw_text_group(text, transformation);
}
Geometry::Cached(cache) => {
@@ -602,6 +583,12 @@ impl graphics::geometry::Renderer for Renderer {
layer.draw_mesh_cache(meshes, transformation);
}
+ if let Some(images) = cache.images {
+ for image in images.iter().cloned() {
+ layer.draw_image(image, transformation);
+ }
+ }
+
if let Some(text) = cache.text {
layer.draw_text_cache(text, transformation);
}
diff --git a/wgpu/src/shader/image.wgsl b/wgpu/src/shader/image.wgsl
index 0eeb100f..bc922838 100644
--- a/wgpu/src/shader/image.wgsl
+++ b/wgpu/src/shader/image.wgsl
@@ -1,5 +1,6 @@
struct Globals {
transform: mat4x4<f32>,
+ scale_factor: f32,
}
@group(0) @binding(0) var<uniform> globals: Globals;
@@ -16,6 +17,7 @@ struct VertexInput {
@location(5) atlas_pos: vec2<f32>,
@location(6) atlas_scale: vec2<f32>,
@location(7) layer: i32,
+ @location(8) snap: u32,
}
struct VertexOutput {
@@ -38,7 +40,7 @@ fn vs_main(input: VertexInput) -> VertexOutput {
out.opacity = input.opacity;
// Calculate the vertex position and move the center to the origin
- v_pos = round(input.pos) + v_pos * input.scale - input.center;
+ v_pos = input.pos + v_pos * input.scale - input.center;
// Apply the rotation around the center of the image
let cos_rot = cos(input.rotation);
@@ -51,7 +53,13 @@ fn vs_main(input: VertexInput) -> VertexOutput {
);
// Calculate the final position of the vertex
- out.position = globals.transform * (vec4<f32>(input.center, 0.0, 0.0) + rotate * vec4<f32>(v_pos, 0.0, 1.0));
+ out.position = vec4(vec2(globals.scale_factor), 1.0, 1.0) * (vec4<f32>(input.center, 0.0, 0.0) + rotate * vec4<f32>(v_pos, 0.0, 1.0));
+
+ if bool(input.snap) {
+ out.position = round(out.position);
+ }
+
+ out.position = globals.transform * out.position;
return out;
}
diff --git a/widget/src/button.rs b/widget/src/button.rs
index 64a639d2..eafa71b9 100644
--- a/widget/src/button.rs
+++ b/widget/src/button.rs
@@ -236,7 +236,7 @@ where
tree: &mut Tree,
layout: Layout<'_>,
renderer: &Renderer,
- operation: &mut dyn Operation<()>,
+ operation: &mut dyn Operation,
) {
operation.container(None, layout.bounds(), &mut |operation| {
self.content.as_widget().operate(
diff --git a/widget/src/canvas.rs b/widget/src/canvas.rs
index 73cef087..185fa082 100644
--- a/widget/src/canvas.rs
+++ b/widget/src/canvas.rs
@@ -8,8 +8,8 @@ pub use program::Program;
pub use crate::graphics::cache::Group;
pub use crate::graphics::geometry::{
- fill, gradient, path, stroke, Fill, Gradient, LineCap, LineDash, LineJoin,
- Path, Stroke, Style, Text,
+ fill, gradient, path, stroke, Fill, Gradient, Image, LineCap, LineDash,
+ LineJoin, Path, Stroke, Style, Text,
};
use crate::core;
diff --git a/widget/src/column.rs b/widget/src/column.rs
index ae82ccaa..d3ea4cf7 100644
--- a/widget/src/column.rs
+++ b/widget/src/column.rs
@@ -222,7 +222,7 @@ where
tree: &mut Tree,
layout: Layout<'_>,
renderer: &Renderer,
- operation: &mut dyn Operation<()>,
+ operation: &mut dyn Operation,
) {
operation.container(None, layout.bounds(), &mut |operation| {
self.children
diff --git a/widget/src/combo_box.rs b/widget/src/combo_box.rs
index 0a4624cb..62785b2c 100644
--- a/widget/src/combo_box.rs
+++ b/widget/src/combo_box.rs
@@ -208,12 +208,14 @@ where
/// The local state of a [`ComboBox`].
#[derive(Debug, Clone)]
-pub struct State<T>(RefCell<Inner<T>>);
+pub struct State<T> {
+ options: Vec<T>,
+ inner: RefCell<Inner<T>>,
+}
#[derive(Debug, Clone)]
struct Inner<T> {
value: String,
- options: Vec<T>,
option_matchers: Vec<String>,
filtered_options: Filtered<T>,
}
@@ -247,34 +249,44 @@ where
.collect(),
);
- Self(RefCell::new(Inner {
- value,
+ Self {
options,
- option_matchers,
- filtered_options,
- }))
+ inner: RefCell::new(Inner {
+ value,
+ option_matchers,
+ filtered_options,
+ }),
+ }
+ }
+
+ /// Returns the options of the [`State`].
+ ///
+ /// These are the options provided when the [`State`]
+ /// was constructed with [`State::new`].
+ pub fn options(&self) -> &[T] {
+ &self.options
}
fn value(&self) -> String {
- let inner = self.0.borrow();
+ let inner = self.inner.borrow();
inner.value.clone()
}
fn with_inner<O>(&self, f: impl FnOnce(&Inner<T>) -> O) -> O {
- let inner = self.0.borrow();
+ let inner = self.inner.borrow();
f(&inner)
}
fn with_inner_mut(&self, f: impl FnOnce(&mut Inner<T>)) {
- let mut inner = self.0.borrow_mut();
+ let mut inner = self.inner.borrow_mut();
f(&mut inner);
}
fn sync_filtered_options(&self, options: &mut Filtered<T>) {
- let inner = self.0.borrow();
+ let inner = self.inner.borrow();
inner.filtered_options.sync(options);
}
@@ -440,7 +452,7 @@ where
state.filtered_options.update(
search(
- &state.options,
+ &self.state.options,
&state.option_matchers,
&state.value,
)
@@ -589,7 +601,7 @@ where
if let Some(selection) = menu.new_selection.take() {
// Clear the value and reset the options and menu
state.value = String::new();
- state.filtered_options.update(state.options.clone());
+ state.filtered_options.update(self.state.options.clone());
menu.menu = menu::State::default();
// Notify the selection
diff --git a/widget/src/container.rs b/widget/src/container.rs
index 9224f2ce..54043ad0 100644
--- a/widget/src/container.rs
+++ b/widget/src/container.rs
@@ -245,7 +245,7 @@ where
tree: &mut Tree,
layout: Layout<'_>,
renderer: &Renderer,
- operation: &mut dyn Operation<()>,
+ operation: &mut dyn Operation,
) {
operation.container(
self.id.as_ref().map(|id| &id.0),
diff --git a/widget/src/helpers.rs b/widget/src/helpers.rs
index 0eb5f974..c3ffea45 100644
--- a/widget/src/helpers.rs
+++ b/widget/src/helpers.rs
@@ -4,7 +4,8 @@ use crate::checkbox::{self, Checkbox};
use crate::combo_box::{self, ComboBox};
use crate::container::{self, Container};
use crate::core;
-use crate::core::widget::operation;
+use crate::core::widget::operation::{self, Operation};
+use crate::core::window;
use crate::core::{Element, Length, Pixels, Widget};
use crate::keyed;
use crate::overlay;
@@ -289,7 +290,7 @@ where
state: &mut Tree,
layout: Layout<'_>,
renderer: &Renderer,
- operation: &mut dyn operation::Operation<()>,
+ operation: &mut dyn operation::Operation,
) {
self.content
.as_widget()
@@ -397,6 +398,7 @@ where
struct Hover<'a, Message, Theme, Renderer> {
base: Element<'a, Message, Theme, Renderer>,
top: Element<'a, Message, Theme, Renderer>,
+ is_top_focused: bool,
is_top_overlay_active: bool,
}
@@ -472,7 +474,9 @@ where
viewport,
);
- if cursor.is_over(layout.bounds()) || self.is_top_overlay_active
+ if cursor.is_over(layout.bounds())
+ || self.is_top_focused
+ || self.is_top_overlay_active
{
let (top_layout, top_tree) = children.next().unwrap();
@@ -491,7 +495,7 @@ where
tree: &mut Tree,
layout: Layout<'_>,
renderer: &Renderer,
- operation: &mut dyn operation::Operation<()>,
+ operation: &mut dyn operation::Operation,
) {
let children = [&self.base, &self.top]
.into_iter()
@@ -515,6 +519,24 @@ where
) -> event::Status {
let mut children = layout.children().zip(&mut tree.children);
let (base_layout, base_tree) = children.next().unwrap();
+ let (top_layout, top_tree) = children.next().unwrap();
+
+ if matches!(event, Event::Window(window::Event::RedrawRequested(_)))
+ {
+ let mut count_focused = operation::focusable::count();
+
+ self.top.as_widget_mut().operate(
+ top_tree,
+ top_layout,
+ renderer,
+ &mut operation::black_box(&mut count_focused),
+ );
+
+ self.is_top_focused = match count_focused.finish() {
+ operation::Outcome::Some(count) => count.focused.is_some(),
+ _ => false,
+ };
+ }
let top_status = if matches!(
event,
@@ -523,9 +545,9 @@ where
| mouse::Event::ButtonReleased(_)
)
) || cursor.is_over(layout.bounds())
+ || self.is_top_focused
+ || self.is_top_overlay_active
{
- let (top_layout, top_tree) = children.next().unwrap();
-
self.top.as_widget_mut().on_event(
top_tree,
event.clone(),
@@ -611,6 +633,7 @@ where
Element::new(Hover {
base: base.into(),
top: top.into(),
+ is_top_focused: false,
is_top_overlay_active: false,
})
}
diff --git a/widget/src/image.rs b/widget/src/image.rs
index 80e17263..e04f2d6f 100644
--- a/widget/src/image.rs
+++ b/widget/src/image.rs
@@ -43,7 +43,7 @@ pub struct Image<Handle> {
impl<Handle> Image<Handle> {
/// Creates a new [`Image`] with the given path.
- pub fn new<T: Into<Handle>>(handle: T) -> Self {
+ pub fn new(handle: impl Into<Handle>) -> Self {
Image {
handle: handle.into(),
width: Length::Shrink,
@@ -181,11 +181,14 @@ pub fn draw<Renderer, Handle>(
let render = |renderer: &mut Renderer| {
renderer.draw_image(
- handle.clone(),
- filter_method,
+ image::Image {
+ handle: handle.clone(),
+ filter_method,
+ rotation: rotation.radians(),
+ opacity,
+ snap: true,
+ },
drawing_bounds,
- rotation.radians(),
- opacity,
);
};
diff --git a/widget/src/image/viewer.rs b/widget/src/image/viewer.rs
index b8b69b60..b1aad22c 100644
--- a/widget/src/image/viewer.rs
+++ b/widget/src/image/viewer.rs
@@ -6,8 +6,8 @@ use crate::core::mouse;
use crate::core::renderer;
use crate::core::widget::tree::{self, Tree};
use crate::core::{
- Clipboard, ContentFit, Element, Layout, Length, Pixels, Point, Radians,
- Rectangle, Shell, Size, Vector, Widget,
+ Clipboard, ContentFit, Element, Image, Layout, Length, Pixels, Point,
+ Radians, Rectangle, Shell, Size, Vector, Widget,
};
/// A frame that displays an image with the ability to zoom in/out and pan.
@@ -349,11 +349,14 @@ where
let render = |renderer: &mut Renderer| {
renderer.with_translation(translation, |renderer| {
renderer.draw_image(
- self.handle.clone(),
- self.filter_method,
+ Image {
+ handle: self.handle.clone(),
+ filter_method: self.filter_method,
+ rotation: Radians(0.0),
+ opacity: 1.0,
+ snap: true,
+ },
drawing_bounds,
- Radians(0.0),
- 1.0,
);
});
};
diff --git a/widget/src/keyed/column.rs b/widget/src/keyed/column.rs
index 69991d1f..2c56c605 100644
--- a/widget/src/keyed/column.rs
+++ b/widget/src/keyed/column.rs
@@ -265,7 +265,7 @@ where
tree: &mut Tree,
layout: Layout<'_>,
renderer: &Renderer,
- operation: &mut dyn Operation<()>,
+ operation: &mut dyn Operation,
) {
operation.container(None, layout.bounds(), &mut |operation| {
self.children
diff --git a/widget/src/lazy.rs b/widget/src/lazy.rs
index 606da22d..4bcf8628 100644
--- a/widget/src/lazy.rs
+++ b/widget/src/lazy.rs
@@ -182,7 +182,7 @@ where
tree: &mut Tree,
layout: Layout<'_>,
renderer: &Renderer,
- operation: &mut dyn widget::Operation<()>,
+ operation: &mut dyn widget::Operation,
) {
self.with_element(|element| {
element.as_widget().operate(
diff --git a/widget/src/lazy/component.rs b/widget/src/lazy/component.rs
index f079c0df..1bf04195 100644
--- a/widget/src/lazy/component.rs
+++ b/widget/src/lazy/component.rs
@@ -59,7 +59,7 @@ pub trait Component<Message, Theme = crate::Theme, Renderer = crate::Renderer> {
fn operate(
&self,
_state: &mut Self::State,
- _operation: &mut dyn widget::Operation<()>,
+ _operation: &mut dyn widget::Operation,
) {
}
@@ -172,7 +172,7 @@ where
fn rebuild_element_with_operation(
&self,
- operation: &mut dyn widget::Operation<()>,
+ operation: &mut dyn widget::Operation,
) {
let heads = self.state.borrow_mut().take().unwrap().into_heads();
@@ -358,7 +358,7 @@ where
tree: &mut Tree,
layout: Layout<'_>,
renderer: &Renderer,
- operation: &mut dyn widget::Operation<()>,
+ operation: &mut dyn widget::Operation,
) {
self.rebuild_element_with_operation(operation);
diff --git a/widget/src/lazy/responsive.rs b/widget/src/lazy/responsive.rs
index 27f52617..2e24f2b3 100644
--- a/widget/src/lazy/responsive.rs
+++ b/widget/src/lazy/responsive.rs
@@ -161,7 +161,7 @@ where
tree: &mut Tree,
layout: Layout<'_>,
renderer: &Renderer,
- operation: &mut dyn widget::Operation<()>,
+ operation: &mut dyn widget::Operation,
) {
let state = tree.state.downcast_mut::<State>();
let mut content = self.content.borrow_mut();
diff --git a/widget/src/mouse_area.rs b/widget/src/mouse_area.rs
index 17cae53b..366335f4 100644
--- a/widget/src/mouse_area.rs
+++ b/widget/src/mouse_area.rs
@@ -178,7 +178,7 @@ where
tree: &mut Tree,
layout: Layout<'_>,
renderer: &Renderer,
- operation: &mut dyn Operation<()>,
+ operation: &mut dyn Operation,
) {
self.content.as_widget().operate(
&mut tree.children[0],
diff --git a/widget/src/pane_grid.rs b/widget/src/pane_grid.rs
index c3da3879..0aab1ab5 100644
--- a/widget/src/pane_grid.rs
+++ b/widget/src/pane_grid.rs
@@ -324,7 +324,7 @@ where
tree: &mut Tree,
layout: Layout<'_>,
renderer: &Renderer,
- operation: &mut dyn widget::Operation<()>,
+ operation: &mut dyn widget::Operation,
) {
operation.container(None, layout.bounds(), &mut |operation| {
self.contents
diff --git a/widget/src/pane_grid/content.rs b/widget/src/pane_grid/content.rs
index d45fc0cd..ec0676b1 100644
--- a/widget/src/pane_grid/content.rs
+++ b/widget/src/pane_grid/content.rs
@@ -214,7 +214,7 @@ where
tree: &mut Tree,
layout: Layout<'_>,
renderer: &Renderer,
- operation: &mut dyn widget::Operation<()>,
+ operation: &mut dyn widget::Operation,
) {
let body_layout = if let Some(title_bar) = &self.title_bar {
let mut children = layout.children();
diff --git a/widget/src/pane_grid/title_bar.rs b/widget/src/pane_grid/title_bar.rs
index c05f1252..791fab4a 100644
--- a/widget/src/pane_grid/title_bar.rs
+++ b/widget/src/pane_grid/title_bar.rs
@@ -278,7 +278,7 @@ where
tree: &mut Tree,
layout: Layout<'_>,
renderer: &Renderer,
- operation: &mut dyn widget::Operation<()>,
+ operation: &mut dyn widget::Operation,
) {
let mut children = layout.children();
let padded = children.next().unwrap();
diff --git a/widget/src/row.rs b/widget/src/row.rs
index 3feeaa7e..85af912f 100644
--- a/widget/src/row.rs
+++ b/widget/src/row.rs
@@ -142,6 +142,13 @@ where
) -> Self {
children.into_iter().fold(self, Self::push)
}
+
+ /// Turns the [`Row`] into a [`Wrapping`] row.
+ ///
+ /// The original alignment of the [`Row`] is preserved per row wrapped.
+ pub fn wrap(self) -> Wrapping<'a, Message, Theme, Renderer> {
+ Wrapping { row: self }
+ }
}
impl<'a, Message, Renderer> Default for Row<'a, Message, Renderer>
@@ -211,7 +218,7 @@ where
tree: &mut Tree,
layout: Layout<'_>,
renderer: &Renderer,
- operation: &mut dyn Operation<()>,
+ operation: &mut dyn Operation,
) {
operation.container(None, layout.bounds(), &mut |operation| {
self.children
@@ -339,3 +346,196 @@ where
Self::new(row)
}
}
+
+/// A [`Row`] that wraps its contents.
+///
+/// Create a [`Row`] first, and then call [`Row::wrap`] to
+/// obtain a [`Row`] that wraps its contents.
+///
+/// The original alignment of the [`Row`] is preserved per row wrapped.
+#[allow(missing_debug_implementations)]
+pub struct Wrapping<
+ 'a,
+ Message,
+ Theme = crate::Theme,
+ Renderer = crate::Renderer,
+> {
+ row: Row<'a, Message, Theme, Renderer>,
+}
+
+impl<'a, Message, Theme, Renderer> Widget<Message, Theme, Renderer>
+ for Wrapping<'a, Message, Theme, Renderer>
+where
+ Renderer: crate::core::Renderer,
+{
+ fn children(&self) -> Vec<Tree> {
+ self.row.children()
+ }
+
+ fn diff(&self, tree: &mut Tree) {
+ self.row.diff(tree);
+ }
+
+ fn size(&self) -> Size<Length> {
+ self.row.size()
+ }
+
+ fn layout(
+ &self,
+ tree: &mut Tree,
+ renderer: &Renderer,
+ limits: &layout::Limits,
+ ) -> layout::Node {
+ let limits = limits
+ .width(self.row.width)
+ .height(self.row.height)
+ .shrink(self.row.padding);
+
+ let spacing = self.row.spacing;
+ let max_width = limits.max().width;
+
+ let mut children: Vec<layout::Node> = Vec::new();
+ let mut intrinsic_size = Size::ZERO;
+ let mut row_start = 0;
+ let mut row_height = 0.0;
+ let mut x = 0.0;
+ let mut y = 0.0;
+
+ let align_factor = match self.row.align {
+ Alignment::Start => 0.0,
+ Alignment::Center => 2.0,
+ Alignment::End => 1.0,
+ };
+
+ let align = |row_start: std::ops::Range<usize>,
+ row_height: f32,
+ children: &mut Vec<layout::Node>| {
+ if align_factor != 0.0 {
+ for node in &mut children[row_start] {
+ let height = node.size().height;
+
+ node.translate_mut(Vector::new(
+ 0.0,
+ (row_height - height) / align_factor,
+ ));
+ }
+ }
+ };
+
+ for (i, child) in self.row.children.iter().enumerate() {
+ let node = child.as_widget().layout(
+ &mut tree.children[i],
+ renderer,
+ &limits,
+ );
+
+ let child_size = node.size();
+
+ if x != 0.0 && x + child_size.width > max_width {
+ intrinsic_size.width = intrinsic_size.width.max(x - spacing);
+
+ align(row_start..i, row_height, &mut children);
+
+ y += row_height + spacing;
+ x = 0.0;
+ row_start = i;
+ row_height = 0.0;
+ }
+
+ row_height = row_height.max(child_size.height);
+
+ children.push(node.move_to((
+ x + self.row.padding.left,
+ y + self.row.padding.top,
+ )));
+
+ x += child_size.width + spacing;
+ }
+
+ if x != 0.0 {
+ intrinsic_size.width = intrinsic_size.width.max(x - spacing);
+ }
+
+ intrinsic_size.height = (y - spacing).max(0.0) + row_height;
+ align(row_start..children.len(), row_height, &mut children);
+
+ let size =
+ limits.resolve(self.row.width, self.row.height, intrinsic_size);
+
+ layout::Node::with_children(size.expand(self.row.padding), children)
+ }
+
+ fn operate(
+ &self,
+ tree: &mut Tree,
+ layout: Layout<'_>,
+ renderer: &Renderer,
+ operation: &mut dyn Operation,
+ ) {
+ self.row.operate(tree, layout, renderer, operation);
+ }
+
+ fn on_event(
+ &mut self,
+ tree: &mut Tree,
+ event: Event,
+ layout: Layout<'_>,
+ cursor: mouse::Cursor,
+ renderer: &Renderer,
+ clipboard: &mut dyn Clipboard,
+ shell: &mut Shell<'_, Message>,
+ viewport: &Rectangle,
+ ) -> event::Status {
+ self.row.on_event(
+ tree, event, layout, cursor, renderer, clipboard, shell, viewport,
+ )
+ }
+
+ fn mouse_interaction(
+ &self,
+ tree: &Tree,
+ layout: Layout<'_>,
+ cursor: mouse::Cursor,
+ viewport: &Rectangle,
+ renderer: &Renderer,
+ ) -> mouse::Interaction {
+ self.row
+ .mouse_interaction(tree, layout, cursor, viewport, renderer)
+ }
+
+ fn draw(
+ &self,
+ tree: &Tree,
+ renderer: &mut Renderer,
+ theme: &Theme,
+ style: &renderer::Style,
+ layout: Layout<'_>,
+ cursor: mouse::Cursor,
+ viewport: &Rectangle,
+ ) {
+ self.row
+ .draw(tree, renderer, theme, style, layout, cursor, viewport);
+ }
+
+ fn overlay<'b>(
+ &'b mut self,
+ tree: &'b mut Tree,
+ layout: Layout<'_>,
+ renderer: &Renderer,
+ translation: Vector,
+ ) -> Option<overlay::Element<'b, Message, Theme, Renderer>> {
+ self.row.overlay(tree, layout, renderer, translation)
+ }
+}
+
+impl<'a, Message, Theme, Renderer> From<Wrapping<'a, Message, Theme, Renderer>>
+ for Element<'a, Message, Theme, Renderer>
+where
+ Message: 'a,
+ Theme: 'a,
+ Renderer: crate::core::Renderer + 'a,
+{
+ fn from(row: Wrapping<'a, Message, Theme, Renderer>) -> Self {
+ Self::new(row)
+ }
+}
diff --git a/widget/src/scrollable.rs b/widget/src/scrollable.rs
index 9ba8c39b..cf504eda 100644
--- a/widget/src/scrollable.rs
+++ b/widget/src/scrollable.rs
@@ -415,7 +415,7 @@ where
tree: &mut Tree,
layout: Layout<'_>,
renderer: &Renderer,
- operation: &mut dyn Operation<()>,
+ operation: &mut dyn Operation,
) {
let state = tree.state.downcast_mut::<State>();
diff --git a/widget/src/stack.rs b/widget/src/stack.rs
index efa9711d..001376ac 100644
--- a/widget/src/stack.rs
+++ b/widget/src/stack.rs
@@ -189,7 +189,7 @@ where
tree: &mut Tree,
layout: Layout<'_>,
renderer: &Renderer,
- operation: &mut dyn Operation<()>,
+ operation: &mut dyn Operation,
) {
operation.container(None, layout.bounds(), &mut |operation| {
self.children
diff --git a/widget/src/svg.rs b/widget/src/svg.rs
index 4551bcad..bec0090f 100644
--- a/widget/src/svg.rs
+++ b/widget/src/svg.rs
@@ -211,11 +211,13 @@ where
let render = |renderer: &mut Renderer| {
renderer.draw_svg(
- self.handle.clone(),
- style.color,
+ svg::Svg {
+ handle: self.handle.clone(),
+ color: style.color,
+ rotation: self.rotation.radians(),
+ opacity: self.opacity,
+ },
drawing_bounds,
- self.rotation.radians(),
- self.opacity,
);
};
diff --git a/widget/src/text_editor.rs b/widget/src/text_editor.rs
index e41c50d7..85332ba4 100644
--- a/widget/src/text_editor.rs
+++ b/widget/src/text_editor.rs
@@ -878,7 +878,7 @@ where
tree: &mut widget::Tree,
_layout: Layout<'_>,
_renderer: &Renderer,
- operation: &mut dyn widget::Operation<()>,
+ operation: &mut dyn widget::Operation,
) {
let state = tree.state.downcast_mut::<State<Highlighter>>();
diff --git a/widget/src/text_input.rs b/widget/src/text_input.rs
index 20e80ba5..173de136 100644
--- a/widget/src/text_input.rs
+++ b/widget/src/text_input.rs
@@ -542,7 +542,7 @@ where
tree: &mut Tree,
_layout: Layout<'_>,
_renderer: &Renderer,
- operation: &mut dyn Operation<()>,
+ operation: &mut dyn Operation,
) {
let state = tree.state.downcast_mut::<State<Renderer::Paragraph>>();
diff --git a/widget/src/themer.rs b/widget/src/themer.rs
index 9eb47d84..499a9fe8 100644
--- a/widget/src/themer.rs
+++ b/widget/src/themer.rs
@@ -104,7 +104,7 @@ where
tree: &mut Tree,
layout: Layout<'_>,
renderer: &Renderer,
- operation: &mut dyn Operation<()>,
+ operation: &mut dyn Operation,
) {
self.content
.as_widget()
@@ -236,7 +236,7 @@ where
&mut self,
layout: Layout<'_>,
renderer: &Renderer,
- operation: &mut dyn Operation<()>,
+ operation: &mut dyn Operation,
) {
self.content.operate(layout, renderer, operation);
}