Add Image rotation support
Co-authored-by: DKolter <68352124+DKolter@users.noreply.github.com>
This commit is contained in:
parent
aae8e4f5cf
commit
09a6bcfffc
19 changed files with 374 additions and 84 deletions
|
|
@ -173,5 +173,7 @@ pub trait Renderer: crate::Renderer {
|
|||
handle: Self::Handle,
|
||||
filter_method: FilterMethod,
|
||||
bounds: Rectangle,
|
||||
rotation: f32,
|
||||
scale: Size,
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
) {
|
||||
}
|
||||
}
|
||||
|
|
|
|||
37
core/src/rotation.rs
Normal file
37
core/src/rotation.rs
Normal file
|
|
@ -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(),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -100,5 +100,7 @@ pub trait Renderer: crate::Renderer {
|
|||
handle: Handle,
|
||||
color: Option<Color>,
|
||||
bounds: Rectangle,
|
||||
rotation: f32,
|
||||
scale: Size,
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
},
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 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 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 drawing_bounds = Rectangle {
|
||||
width: adjusted_fit.width,
|
||||
height: adjusted_fit.height,
|
||||
..bounds
|
||||
let render = |renderer: &mut Renderer| {
|
||||
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,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -341,6 +341,8 @@ where
|
|||
y: bounds.y,
|
||||
..Rectangle::with_size(image_size)
|
||||
},
|
||||
0.0,
|
||||
Size::UNIT,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue