summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--core/src/image.rs2
-rw-r--r--core/src/lib.rs2
-rw-r--r--core/src/renderer/null.rs4
-rw-r--r--core/src/rotation.rs37
-rw-r--r--core/src/svg.rs2
-rw-r--r--graphics/src/image.rs16
-rw-r--r--renderer/src/fallback.rs12
-rw-r--r--src/lib.rs4
-rw-r--r--tiny_skia/src/engine.rs49
-rw-r--r--tiny_skia/src/layer.rs32
-rw-r--r--tiny_skia/src/lib.rs17
-rw-r--r--tiny_skia/src/vector.rs4
-rw-r--r--wgpu/src/image/mod.rs65
-rw-r--r--wgpu/src/layer.rs13
-rw-r--r--wgpu/src/lib.rs22
-rw-r--r--wgpu/src/shader/image.wgsl37
-rw-r--r--widget/src/image.rs73
-rw-r--r--widget/src/image/viewer.rs2
-rw-r--r--widget/src/svg.rs65
19 files changed, 374 insertions, 84 deletions
diff --git a/core/src/image.rs b/core/src/image.rs
index c38239bc..5d1ab441 100644
--- a/core/src/image.rs
+++ b/core/src/image.rs
@@ -173,5 +173,7 @@ pub trait Renderer: crate::Renderer {
handle: Self::Handle,
filter_method: FilterMethod,
bounds: Rectangle,
+ rotation: f32,
+ scale: Size,
);
}
diff --git a/core/src/lib.rs b/core/src/lib.rs
index feda4fb4..da3ddcac 100644
--- a/core/src/lib.rs
+++ b/core/src/lib.rs
@@ -39,6 +39,7 @@ mod padding;
mod pixels;
mod point;
mod rectangle;
+mod rotation;
mod shadow;
mod shell;
mod size;
@@ -64,6 +65,7 @@ pub use pixels::Pixels;
pub use point::Point;
pub use rectangle::Rectangle;
pub use renderer::Renderer;
+pub use rotation::RotationLayout;
pub use shadow::Shadow;
pub use shell::Shell;
pub use size::Size;
diff --git a/core/src/renderer/null.rs b/core/src/renderer/null.rs
index fe38ec87..d2dcfe4d 100644
--- a/core/src/renderer/null.rs
+++ b/core/src/renderer/null.rs
@@ -171,6 +171,8 @@ impl image::Renderer for () {
_handle: Self::Handle,
_filter_method: image::FilterMethod,
_bounds: Rectangle,
+ _rotation: f32,
+ _scale: Size,
) {
}
}
@@ -185,6 +187,8 @@ impl svg::Renderer for () {
_handle: svg::Handle,
_color: Option<Color>,
_bounds: Rectangle,
+ _rotation: f32,
+ _scale: Size,
) {
}
}
diff --git a/core/src/rotation.rs b/core/src/rotation.rs
new file mode 100644
index 00000000..821aa494
--- /dev/null
+++ b/core/src/rotation.rs
@@ -0,0 +1,37 @@
+//! Control the rotation of some content (like an image) with the `RotationLayout` within a
+//! space.
+use crate::Size;
+
+/// The strategy used to rotate the content.
+///
+/// This is used to control the behavior of the layout when the content is rotated
+/// by a certain angle.
+#[derive(Debug, Hash, Clone, Copy, PartialEq, Eq)]
+pub enum RotationLayout {
+ /// The layout is kept exactly as it was before the rotation.
+ ///
+ /// This is especially useful when used for animations, as it will avoid the
+ /// layout being shifted or resized when smoothly i.e. an icon.
+ Keep,
+ /// The layout is adjusted to fit the rotated content.
+ ///
+ /// This allows you to rotate an image and have the layout adjust to fit the new
+ /// size of the image.
+ Change,
+}
+
+impl RotationLayout {
+ /// Applies the rotation to the layout while respecting the [`RotationLayout`] strategy.
+ /// The rotation is given in radians.
+ pub fn apply_to_size(&self, size: Size, rotation: f32) -> Size {
+ match self {
+ Self::Keep => size,
+ Self::Change => Size {
+ width: (size.width * rotation.cos()).abs()
+ + (size.height * rotation.sin()).abs(),
+ height: (size.width * rotation.sin()).abs()
+ + (size.height * rotation.cos()).abs(),
+ },
+ }
+ }
+}
diff --git a/core/src/svg.rs b/core/src/svg.rs
index 0106e0c2..74dd7f4a 100644
--- a/core/src/svg.rs
+++ b/core/src/svg.rs
@@ -100,5 +100,7 @@ pub trait Renderer: crate::Renderer {
handle: Handle,
color: Option<Color>,
bounds: Rectangle,
+ rotation: f32,
+ scale: Size,
);
}
diff --git a/graphics/src/image.rs b/graphics/src/image.rs
index 04c45057..083248bf 100644
--- a/graphics/src/image.rs
+++ b/graphics/src/image.rs
@@ -2,9 +2,7 @@
#[cfg(feature = "image")]
pub use ::image as image_rs;
-use crate::core::image;
-use crate::core::svg;
-use crate::core::{Color, Rectangle};
+use crate::core::{image, svg, Color, Rectangle, Size};
/// A raster or vector image.
#[derive(Debug, Clone, PartialEq)]
@@ -19,6 +17,12 @@ pub enum Image {
/// The bounds of the image.
bounds: Rectangle,
+
+ /// The rotation of the image in radians
+ rotation: f32,
+
+ /// The scale of the image after rotation
+ scale: Size,
},
/// A vector image.
Vector {
@@ -30,6 +34,12 @@ pub enum Image {
/// The bounds of the image.
bounds: Rectangle,
+
+ /// The rotation of the image in radians
+ rotation: f32,
+
+ /// The scale of the image after rotation
+ scale: Size,
},
}
diff --git a/renderer/src/fallback.rs b/renderer/src/fallback.rs
index 5f69b420..37e6ac43 100644
--- a/renderer/src/fallback.rs
+++ b/renderer/src/fallback.rs
@@ -154,11 +154,13 @@ where
handle: Self::Handle,
filter_method: image::FilterMethod,
bounds: Rectangle,
+ rotation: f32,
+ scale: Size,
) {
delegate!(
self,
renderer,
- renderer.draw_image(handle, filter_method, bounds)
+ renderer.draw_image(handle, filter_method, bounds, rotation, scale)
);
}
}
@@ -177,8 +179,14 @@ where
handle: svg::Handle,
color: Option<Color>,
bounds: Rectangle,
+ rotation: f32,
+ scale: Size,
) {
- delegate!(self, renderer, renderer.draw_svg(handle, color, bounds));
+ delegate!(
+ self,
+ renderer,
+ renderer.draw_svg(handle, color, bounds, rotation, scale)
+ );
}
}
diff --git a/src/lib.rs b/src/lib.rs
index 7517dd14..9d07fed6 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -200,8 +200,8 @@ pub use crate::core::gradient;
pub use crate::core::theme;
pub use crate::core::{
Alignment, Background, Border, Color, ContentFit, Degrees, Gradient,
- Length, Padding, Pixels, Point, Radians, Rectangle, Shadow, Size, Theme,
- Transformation, Vector,
+ Length, Padding, Pixels, Point, Radians, Rectangle, RotationLayout, Shadow,
+ Size, Theme, Transformation, Vector,
};
pub mod clipboard {
diff --git a/tiny_skia/src/engine.rs b/tiny_skia/src/engine.rs
index fbca1274..564d3752 100644
--- a/tiny_skia/src/engine.rs
+++ b/tiny_skia/src/engine.rs
@@ -539,10 +539,10 @@ impl Engine {
pub fn draw_image(
&mut self,
image: &Image,
- _transformation: Transformation,
- _pixels: &mut tiny_skia::PixmapMut<'_>,
- _clip_mask: &mut tiny_skia::Mask,
- _clip_bounds: Rectangle,
+ transformation: Transformation,
+ pixels: &mut tiny_skia::PixmapMut<'_>,
+ clip_mask: &mut tiny_skia::Mask,
+ clip_bounds: Rectangle,
) {
match image {
#[cfg(feature = "image")]
@@ -550,22 +550,31 @@ impl Engine {
handle,
filter_method,
bounds,
+ rotation,
+ scale,
} => {
- let physical_bounds = *bounds * _transformation;
+ let physical_bounds = *bounds * transformation;
- if !_clip_bounds.intersects(&physical_bounds) {
+ if !clip_bounds.intersects(&physical_bounds) {
return;
}
- let clip_mask = (!physical_bounds.is_within(&_clip_bounds))
- .then_some(_clip_mask as &_);
+ let clip_mask = (!physical_bounds.is_within(&clip_bounds))
+ .then_some(clip_mask as &_);
+
+ let center = physical_bounds.center();
+ let transform = into_transform(transformation)
+ .post_rotate_at(rotation.to_degrees(), center.x, center.y)
+ .post_translate(-center.x, -center.y)
+ .post_scale(scale.width, scale.height)
+ .post_translate(center.x, center.y);
self.raster_pipeline.draw(
handle,
*filter_method,
*bounds,
- _pixels,
- into_transform(_transformation),
+ pixels,
+ transform,
clip_mask,
);
}
@@ -574,21 +583,31 @@ impl Engine {
handle,
color,
bounds,
+ rotation,
+ scale,
} => {
- let physical_bounds = *bounds * _transformation;
+ let physical_bounds = *bounds * transformation;
- if !_clip_bounds.intersects(&physical_bounds) {
+ if !clip_bounds.intersects(&physical_bounds) {
return;
}
- let clip_mask = (!physical_bounds.is_within(&_clip_bounds))
- .then_some(_clip_mask as &_);
+ let clip_mask = (!physical_bounds.is_within(&clip_bounds))
+ .then_some(clip_mask as &_);
+
+ let center = physical_bounds.center();
+ let transform = into_transform(transformation)
+ .post_rotate_at(rotation.to_degrees(), center.x, center.y)
+ .post_translate(-center.x, -center.y)
+ .post_scale(scale.width, scale.height)
+ .post_translate(center.x, center.y);
self.vector_pipeline.draw(
handle,
*color,
physical_bounds,
- _pixels,
+ pixels,
+ transform,
clip_mask,
);
}
diff --git a/tiny_skia/src/layer.rs b/tiny_skia/src/layer.rs
index 3e42e4aa..e9651814 100644
--- a/tiny_skia/src/layer.rs
+++ b/tiny_skia/src/layer.rs
@@ -1,7 +1,7 @@
-use crate::core::image;
-use crate::core::renderer::Quad;
-use crate::core::svg;
-use crate::core::{Background, Color, Point, Rectangle, Transformation};
+use crate::core::{
+ image, renderer::Quad, svg, Background, Color, Point, Rectangle, Size,
+ Transformation,
+};
use crate::graphics::damage;
use crate::graphics::layer;
use crate::graphics::text::{Editor, Paragraph, Text};
@@ -121,11 +121,15 @@ impl Layer {
filter_method: image::FilterMethod,
bounds: Rectangle,
transformation: Transformation,
+ rotation: f32,
+ scale: Size,
) {
let image = Image::Raster {
handle,
filter_method,
bounds: bounds * transformation,
+ rotation,
+ scale,
};
self.images.push(image);
@@ -137,11 +141,15 @@ impl Layer {
color: Option<Color>,
bounds: Rectangle,
transformation: Transformation,
+ rotation: f32,
+ scale: Size,
) {
let svg = Image::Vector {
handle,
color,
bounds: bounds * transformation,
+ rotation,
+ scale,
};
self.images.push(svg);
@@ -256,6 +264,22 @@ impl Layer {
Image::eq,
);
+ // let center = bounds.center();
+ // let rotated_size = RotationLayout::Change
+ // .apply_to_size(bounds.size(), *rotation);
+ //
+ // let scaled_size = Size::new(
+ // rotated_size.width * scale.width,
+ // rotated_size.height * scale.height,
+ // );
+ //
+ // let top_left = Point::new(
+ // center.x - scaled_size.width / 2.0,
+ // center.y - scaled_size.height / 2.0,
+ // );
+ //
+ // Rectangle::new(top_left, scaled_size).expand(1.0)
+
damage.extend(text);
damage.extend(primitives);
damage.extend(images);
diff --git a/tiny_skia/src/lib.rs b/tiny_skia/src/lib.rs
index 4c2c9430..4e3ebad3 100644
--- a/tiny_skia/src/lib.rs
+++ b/tiny_skia/src/lib.rs
@@ -29,7 +29,7 @@ pub use geometry::Geometry;
use crate::core::renderer;
use crate::core::{
- Background, Color, Font, Pixels, Point, Rectangle, Transformation,
+ Background, Color, Font, Pixels, Point, Rectangle, Size, Transformation,
};
use crate::engine::Engine;
use crate::graphics::compositor;
@@ -377,9 +377,18 @@ impl core::image::Renderer for Renderer {
handle: Self::Handle,
filter_method: core::image::FilterMethod,
bounds: Rectangle,
+ rotation: f32,
+ scale: Size,
) {
let (layer, transformation) = self.layers.current_mut();
- layer.draw_image(handle, filter_method, bounds, transformation);
+ layer.draw_image(
+ handle,
+ filter_method,
+ bounds,
+ transformation,
+ rotation,
+ scale,
+ );
}
}
@@ -397,9 +406,11 @@ impl core::svg::Renderer for Renderer {
handle: core::svg::Handle,
color: Option<Color>,
bounds: Rectangle,
+ rotation: f32,
+ scale: Size,
) {
let (layer, transformation) = self.layers.current_mut();
- layer.draw_svg(handle, color, bounds, transformation);
+ layer.draw_svg(handle, color, bounds, transformation, rotation, scale);
}
}
diff --git a/tiny_skia/src/vector.rs b/tiny_skia/src/vector.rs
index 5150cffe..8e3463f2 100644
--- a/tiny_skia/src/vector.rs
+++ b/tiny_skia/src/vector.rs
@@ -4,6 +4,7 @@ use crate::graphics::text;
use resvg::usvg::{self, TreeTextToPath};
use rustc_hash::{FxHashMap, FxHashSet};
+use tiny_skia::Transform;
use std::cell::RefCell;
use std::collections::hash_map;
@@ -34,6 +35,7 @@ impl Pipeline {
color: Option<Color>,
bounds: Rectangle,
pixels: &mut tiny_skia::PixmapMut<'_>,
+ transform: Transform,
clip_mask: Option<&tiny_skia::Mask>,
) {
if let Some(image) = self.cache.borrow_mut().draw(
@@ -46,7 +48,7 @@ impl Pipeline {
bounds.y as i32,
image,
&tiny_skia::PixmapPaint::default(),
- tiny_skia::Transform::identity(),
+ transform,
clip_mask,
);
}
diff --git a/wgpu/src/image/mod.rs b/wgpu/src/image/mod.rs
index 8b831a3c..69f8a8ca 100644
--- a/wgpu/src/image/mod.rs
+++ b/wgpu/src/image/mod.rs
@@ -135,14 +135,20 @@ impl Pipeline {
attributes: &wgpu::vertex_attr_array!(
// Position
0 => Float32x2,
- // Scale
+ // Center
1 => Float32x2,
- // Atlas position
+ // Image size
2 => Float32x2,
+ // Rotation
+ 3 => Float32,
+ // Scale
+ 4 => Float32x2,
+ // Atlas position
+ 5 => Float32x2,
// Atlas scale
- 3 => Float32x2,
+ 6 => Float32x2,
// Layer
- 4 => Sint32,
+ 7 => Sint32,
),
}],
},
@@ -208,9 +214,10 @@ impl Pipeline {
belt: &mut wgpu::util::StagingBelt,
images: &Batch,
transformation: Transformation,
- scale: f32,
+ global_scale: f32,
) {
- let transformation = transformation * Transformation::scale(scale);
+ let transformation =
+ transformation * Transformation::scale(global_scale);
let nearest_instances: &mut Vec<Instance> = &mut Vec::new();
let linear_instances: &mut Vec<Instance> = &mut Vec::new();
@@ -224,6 +231,8 @@ impl Pipeline {
handle,
filter_method,
bounds,
+ rotation,
+ scale,
} => {
if let Some(atlas_entry) =
cache.upload_raster(device, encoder, handle)
@@ -231,6 +240,8 @@ impl Pipeline {
add_instances(
[bounds.x, bounds.y],
[bounds.width, bounds.height],
+ *rotation,
+ [scale.width, scale.height],
atlas_entry,
match filter_method {
crate::core::image::FilterMethod::Nearest => {
@@ -251,15 +262,24 @@ impl Pipeline {
handle,
color,
bounds,
+ rotation,
+ scale,
} => {
let size = [bounds.width, bounds.height];
if let Some(atlas_entry) = cache.upload_vector(
- device, encoder, handle, *color, size, scale,
+ device,
+ encoder,
+ handle,
+ *color,
+ size,
+ global_scale,
) {
add_instances(
[bounds.x, bounds.y],
size,
+ *rotation,
+ [scale.width, scale.height],
atlas_entry,
nearest_instances,
);
@@ -487,7 +507,10 @@ impl Data {
#[derive(Debug, Clone, Copy, Zeroable, Pod)]
struct Instance {
_position: [f32; 2],
+ _center: [f32; 2],
_size: [f32; 2],
+ _rotation: f32,
+ _scale: [f32; 2],
_position_in_atlas: [f32; 2],
_size_in_atlas: [f32; 2],
_layer: u32,
@@ -506,12 +529,27 @@ struct Uniforms {
fn add_instances(
image_position: [f32; 2],
image_size: [f32; 2],
+ rotation: f32,
+ scale: [f32; 2],
entry: &atlas::Entry,
instances: &mut Vec<Instance>,
) {
+ let center = [
+ image_position[0] + image_size[0] / 2.0,
+ image_position[1] + image_size[1] / 2.0,
+ ];
+
match entry {
atlas::Entry::Contiguous(allocation) => {
- add_instance(image_position, image_size, allocation, instances);
+ add_instance(
+ image_position,
+ center,
+ image_size,
+ rotation,
+ scale,
+ allocation,
+ instances,
+ );
}
atlas::Entry::Fragmented { fragments, size } => {
let scaling_x = image_size[0] / size.width as f32;
@@ -537,7 +575,10 @@ fn add_instances(
fragment_height as f32 * scaling_y,
];
- add_instance(position, size, allocation, instances);
+ add_instance(
+ position, center, size, rotation, scale, allocation,
+ instances,
+ );
}
}
}
@@ -546,7 +587,10 @@ fn add_instances(
#[inline]
fn add_instance(
position: [f32; 2],
+ center: [f32; 2],
size: [f32; 2],
+ rotation: f32,
+ scale: [f32; 2],
allocation: &atlas::Allocation,
instances: &mut Vec<Instance>,
) {
@@ -556,7 +600,10 @@ fn add_instance(
let instance = Instance {
_position: position,
+ _center: center,
_size: size,
+ _rotation: rotation,
+ _scale: scale,
_position_in_atlas: [
(x as f32 + 0.5) / atlas::SIZE as f32,
(y as f32 + 0.5) / atlas::SIZE as f32,
diff --git a/wgpu/src/layer.rs b/wgpu/src/layer.rs
index 9526c5a8..648ec476 100644
--- a/wgpu/src/layer.rs
+++ b/wgpu/src/layer.rs
@@ -1,5 +1,6 @@
-use crate::core::renderer;
-use crate::core::{Background, Color, Point, Rectangle, Transformation};
+use crate::core::{
+ renderer, Background, Color, Point, Rectangle, Size, Transformation,
+};
use crate::graphics;
use crate::graphics::color;
use crate::graphics::layer;
@@ -117,11 +118,15 @@ impl Layer {
filter_method: crate::core::image::FilterMethod,
bounds: Rectangle,
transformation: Transformation,
+ rotation: f32,
+ scale: Size,
) {
let image = Image::Raster {
handle,
filter_method,
bounds: bounds * transformation,
+ rotation,
+ scale,
};
self.images.push(image);
@@ -133,11 +138,15 @@ impl Layer {
color: Option<Color>,
bounds: Rectangle,
transformation: Transformation,
+ rotation: f32,
+ scale: Size,
) {
let svg = Image::Vector {
handle,
color,
bounds: bounds * transformation,
+ rotation,
+ scale,
};
self.images.push(svg);
diff --git a/wgpu/src/lib.rs b/wgpu/src/lib.rs
index 178522de..a42d71c3 100644
--- a/wgpu/src/lib.rs
+++ b/wgpu/src/lib.rs
@@ -517,9 +517,18 @@ impl core::image::Renderer for Renderer {
handle: Self::Handle,
filter_method: core::image::FilterMethod,
bounds: Rectangle,
+ rotation: f32,
+ scale: Size,
) {
let (layer, transformation) = self.layers.current_mut();
- layer.draw_image(handle, filter_method, bounds, transformation);
+ layer.draw_image(
+ handle,
+ filter_method,
+ bounds,
+ transformation,
+ rotation,
+ scale,
+ );
}
}
@@ -534,9 +543,18 @@ impl core::svg::Renderer for Renderer {
handle: core::svg::Handle,
color_filter: Option<Color>,
bounds: Rectangle,
+ rotation: f32,
+ scale: Size,
) {
let (layer, transformation) = self.layers.current_mut();
- layer.draw_svg(handle, color_filter, bounds, transformation);
+ layer.draw_svg(
+ handle,
+ color_filter,
+ bounds,
+ transformation,
+ rotation,
+ scale,
+ );
}
}
diff --git a/wgpu/src/shader/image.wgsl b/wgpu/src/shader/image.wgsl
index 7b2e5238..de962098 100644
--- a/wgpu/src/shader/image.wgsl
+++ b/wgpu/src/shader/image.wgsl
@@ -9,10 +9,13 @@ struct Globals {
struct VertexInput {
@builtin(vertex_index) vertex_index: u32,
@location(0) pos: vec2<f32>,
- @location(1) scale: vec2<f32>,
- @location(2) atlas_pos: vec2<f32>,
- @location(3) atlas_scale: vec2<f32>,
- @location(4) layer: i32,
+ @location(1) center: vec2<f32>,
+ @location(2) image_size: vec2<f32>,
+ @location(3) rotation: f32,
+ @location(4) scale: vec2<f32>,
+ @location(5) atlas_pos: vec2<f32>,
+ @location(6) atlas_scale: vec2<f32>,
+ @location(7) layer: i32,
}
struct VertexOutput {
@@ -25,24 +28,42 @@ struct VertexOutput {
fn vs_main(input: VertexInput) -> VertexOutput {
var out: VertexOutput;
- let v_pos = vertex_position(input.vertex_index);
+ // Generate a vertex position in the range [0, 1] from the vertex index.
+ var v_pos = vertex_position(input.vertex_index);
+ // Map the vertex position to the atlas texture.
out.uv = vec2<f32>(v_pos * input.atlas_scale + input.atlas_pos);
out.layer = f32(input.layer);
- var transform: mat4x4<f32> = mat4x4<f32>(
+ // Calculate the vertex position and move the center to the origin
+ v_pos = input.pos + v_pos * input.image_size - input.center;
+
+ // Apply the rotation around the center of the image
+ let cos_rot = cos(input.rotation);
+ let sin_rot = sin(input.rotation);
+ let rotate = mat4x4<f32>(
+ vec4<f32>(cos_rot, sin_rot, 0.0, 0.0),
+ vec4<f32>(-sin_rot, cos_rot, 0.0, 0.0),
+ vec4<f32>(0.0, 0.0, 1.0, 0.0),
+ vec4<f32>(0.0, 0.0, 0.0, 1.0)
+ );
+
+ // Scale the image and then translate to the final position by moving the center to the position
+ let scale_translate = mat4x4<f32>(
vec4<f32>(input.scale.x, 0.0, 0.0, 0.0),
vec4<f32>(0.0, input.scale.y, 0.0, 0.0),
vec4<f32>(0.0, 0.0, 1.0, 0.0),
- vec4<f32>(input.pos, 0.0, 1.0)
+ vec4<f32>(input.center, 0.0, 1.0)
);
- out.position = globals.transform * transform * vec4<f32>(v_pos, 0.0, 1.0);
+ // Calculate the final position of the vertex
+ out.position = globals.transform * scale_translate * rotate * vec4<f32>(v_pos, 0.0, 1.0);
return out;
}
@fragment
fn fs_main(input: VertexOutput) -> @location(0) vec4<f32> {
+ // Sample the texture at the given UV coordinate and layer.
return textureSample(u_texture, u_sampler, input.uv, i32(input.layer));
}
diff --git a/widget/src/image.rs b/widget/src/image.rs
index 21d371b7..3a0a5e53 100644
--- a/widget/src/image.rs
+++ b/widget/src/image.rs
@@ -1,5 +1,6 @@
//! Display images in your user interface.
pub mod viewer;
+use iced_renderer::core::{Point, RotationLayout};
pub use viewer::Viewer;
use crate::core::image;
@@ -8,7 +9,7 @@ use crate::core::mouse;
use crate::core::renderer;
use crate::core::widget::Tree;
use crate::core::{
- ContentFit, Element, Layout, Length, Rectangle, Size, Vector, Widget,
+ ContentFit, Element, Layout, Length, Rectangle, Size, Widget,
};
pub use image::{FilterMethod, Handle};
@@ -36,6 +37,8 @@ pub struct Image<Handle> {
height: Length,
content_fit: ContentFit,
filter_method: FilterMethod,
+ rotation: f32,
+ rotation_layout: RotationLayout,
}
impl<Handle> Image<Handle> {
@@ -47,6 +50,8 @@ impl<Handle> Image<Handle> {
height: Length::Shrink,
content_fit: ContentFit::Contain,
filter_method: FilterMethod::default(),
+ rotation: 0.0,
+ rotation_layout: RotationLayout::Change,
}
}
@@ -75,6 +80,18 @@ impl<Handle> Image<Handle> {
self.filter_method = filter_method;
self
}
+
+ /// Rotates the [`Image`] by the given angle in radians.
+ pub fn rotation(mut self, degrees: f32) -> Self {
+ self.rotation = degrees;
+ self
+ }
+
+ /// Sets the [`RotationLayout`] of the [`Image`].
+ pub fn rotation_layout(mut self, rotation_layout: RotationLayout) -> Self {
+ self.rotation_layout = rotation_layout;
+ self
+ }
}
/// Computes the layout of an [`Image`].
@@ -85,22 +102,25 @@ pub fn layout<Renderer, Handle>(
width: Length,
height: Length,
content_fit: ContentFit,
+ rotation: f32,
+ rotation_layout: RotationLayout,
) -> layout::Node
where
Renderer: image::Renderer<Handle = Handle>,
{
// The raw w/h of the underlying image
- let image_size = {
- let Size { width, height } = renderer.measure_image(handle);
+ let image_size = renderer.measure_image(handle);
+ let image_size =
+ Size::new(image_size.width as f32, image_size.height as f32);
- Size::new(width as f32, height as f32)
- };
+ // The rotated size of the image
+ let rotated_size = rotation_layout.apply_to_size(image_size, rotation);
// The size to be available to the widget prior to `Shrink`ing
- let raw_size = limits.resolve(width, height, image_size);
+ let raw_size = limits.resolve(width, height, rotated_size);
// The uncropped size of the image when fit to the bounds above
- let full_size = content_fit.fit(image_size, raw_size);
+ let full_size = content_fit.fit(rotated_size, raw_size);
// Shrink the widget to fit the resized image, if requested
let final_size = Size {
@@ -124,32 +144,45 @@ pub fn draw<Renderer, Handle>(
handle: &Handle,
content_fit: ContentFit,
filter_method: FilterMethod,
+ rotation: f32,
+ rotation_layout: RotationLayout,
) where
Renderer: image::Renderer<Handle = Handle>,
Handle: Clone,
{
let Size { width, height } = renderer.measure_image(handle);
let image_size = Size::new(width as f32, height as f32);
+ let rotated_size = rotation_layout.apply_to_size(image_size, rotation);
let bounds = layout.bounds();
- let adjusted_fit = content_fit.fit(image_size, bounds.size());
+ let adjusted_fit = content_fit.fit(rotated_size, bounds.size());
+ let scale = Size::new(
+ adjusted_fit.width / rotated_size.width,
+ adjusted_fit.height / rotated_size.height,
+ );
let render = |renderer: &mut Renderer| {
- let offset = Vector::new(
- (bounds.width - adjusted_fit.width).max(0.0) / 2.0,
- (bounds.height - adjusted_fit.height).max(0.0) / 2.0,
- );
-
- let drawing_bounds = Rectangle {
- width: adjusted_fit.width,
- height: adjusted_fit.height,
- ..bounds
+ let position = match content_fit {
+ ContentFit::None => Point::new(
+ bounds.position().x
+ + (rotated_size.width - image_size.width) / 2.0,
+ bounds.position().y
+ + (rotated_size.height - image_size.height) / 2.0,
+ ),
+ _ => Point::new(
+ bounds.center_x() - image_size.width / 2.0,
+ bounds.center_y() - image_size.height / 2.0,
+ ),
};
+ let drawing_bounds = Rectangle::new(position, image_size);
+
renderer.draw_image(
handle.clone(),
filter_method,
- drawing_bounds + offset,
+ drawing_bounds,
+ rotation,
+ scale,
);
};
@@ -187,6 +220,8 @@ where
self.width,
self.height,
self.content_fit,
+ self.rotation,
+ self.rotation_layout,
)
}
@@ -206,6 +241,8 @@ where
&self.handle,
self.content_fit,
self.filter_method,
+ self.rotation,
+ self.rotation_layout,
);
}
}
diff --git a/widget/src/image/viewer.rs b/widget/src/image/viewer.rs
index 214cb996..ccdfdebb 100644
--- a/widget/src/image/viewer.rs
+++ b/widget/src/image/viewer.rs
@@ -341,6 +341,8 @@ where
y: bounds.y,
..Rectangle::with_size(image_size)
},
+ 0.0,
+ Size::UNIT,
);
});
});
diff --git a/widget/src/svg.rs b/widget/src/svg.rs
index eb142189..21946af8 100644
--- a/widget/src/svg.rs
+++ b/widget/src/svg.rs
@@ -5,8 +5,8 @@ use crate::core::renderer;
use crate::core::svg;
use crate::core::widget::Tree;
use crate::core::{
- Color, ContentFit, Element, Layout, Length, Rectangle, Size, Theme, Vector,
- Widget,
+ Color, ContentFit, Element, Layout, Length, Point, Rectangle,
+ RotationLayout, Size, Theme, Widget,
};
use std::path::PathBuf;
@@ -29,6 +29,8 @@ where
height: Length,
content_fit: ContentFit,
class: Theme::Class<'a>,
+ rotation: f32,
+ rotation_layout: RotationLayout,
}
impl<'a, Theme> Svg<'a, Theme>
@@ -43,6 +45,8 @@ where
height: Length::Shrink,
content_fit: ContentFit::Contain,
class: Theme::default(),
+ rotation: 0.0,
+ rotation_layout: RotationLayout::Change,
}
}
@@ -95,6 +99,18 @@ where
self.class = class.into();
self
}
+
+ /// Rotates the [`Svg`] by the given angle in radians.
+ pub fn rotation(mut self, degrees: f32) -> Self {
+ self.rotation = degrees;
+ self
+ }
+
+ /// Sets the [`RotationLayout`] of the [`Svg`].
+ pub fn rotation_layout(mut self, rotation_layout: RotationLayout) -> Self {
+ self.rotation_layout = rotation_layout;
+ self
+ }
}
impl<'a, Message, Theme, Renderer> Widget<Message, Theme, Renderer>
@@ -120,11 +136,16 @@ where
let Size { width, height } = renderer.measure_svg(&self.handle);
let image_size = Size::new(width as f32, height as f32);
+ // The rotated size of the svg
+ let rotated_size = self
+ .rotation_layout
+ .apply_to_size(image_size, self.rotation);
+
// The size to be available to the widget prior to `Shrink`ing
- let raw_size = limits.resolve(self.width, self.height, image_size);
+ let raw_size = limits.resolve(self.width, self.height, rotated_size);
// The uncropped size of the image when fit to the bounds above
- let full_size = self.content_fit.fit(image_size, raw_size);
+ let full_size = self.content_fit.fit(rotated_size, raw_size);
// Shrink the widget to fit the resized image, if requested
let final_size = Size {
@@ -153,23 +174,35 @@ where
) {
let Size { width, height } = renderer.measure_svg(&self.handle);
let image_size = Size::new(width as f32, height as f32);
+ let rotated_size = self
+ .rotation_layout
+ .apply_to_size(image_size, self.rotation);
let bounds = layout.bounds();
- let adjusted_fit = self.content_fit.fit(image_size, bounds.size());
+ let adjusted_fit = self.content_fit.fit(rotated_size, bounds.size());
+ let scale = Size::new(
+ adjusted_fit.width / rotated_size.width,
+ adjusted_fit.height / rotated_size.height,
+ );
+
let is_mouse_over = cursor.is_over(bounds);
let render = |renderer: &mut Renderer| {
- let offset = Vector::new(
- (bounds.width - adjusted_fit.width).max(0.0) / 2.0,
- (bounds.height - adjusted_fit.height).max(0.0) / 2.0,
- );
-
- let drawing_bounds = Rectangle {
- width: adjusted_fit.width,
- height: adjusted_fit.height,
- ..bounds
+ let position = match self.content_fit {
+ ContentFit::None => Point::new(
+ bounds.position().x
+ + (rotated_size.width - image_size.width) / 2.0,
+ bounds.position().y
+ + (rotated_size.height - image_size.height) / 2.0,
+ ),
+ _ => Point::new(
+ bounds.center_x() - image_size.width / 2.0,
+ bounds.center_y() - image_size.height / 2.0,
+ ),
};
+ let drawing_bounds = Rectangle::new(position, image_size);
+
let status = if is_mouse_over {
Status::Hovered
} else {
@@ -181,7 +214,9 @@ where
renderer.draw_svg(
self.handle.clone(),
style.color,
- drawing_bounds + offset,
+ drawing_bounds,
+ self.rotation,
+ scale,
);
};