summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorLibravatar Héctor Ramón <hector@hecrj.dev>2024-05-03 07:31:34 +0200
committerLibravatar GitHub <noreply@github.com>2024-05-03 07:31:34 +0200
commit1cefe6be210cdae8c6769673e8d23c6781a988f1 (patch)
tree7d4a383412e6bd69d0cc1b32f996ba7cf6ef892e
parentfe240a93aacd15bd3fa75876054753a53bda9054 (diff)
parent4010e3983d40e24a5d7b590d8fec9801651199ce (diff)
downloadiced-1cefe6be210cdae8c6769673e8d23c6781a988f1.tar.gz
iced-1cefe6be210cdae8c6769673e8d23c6781a988f1.tar.bz2
iced-1cefe6be210cdae8c6769673e8d23c6781a988f1.zip
Merge pull request #2334 from DKolter/image-rotation
Adding feature: Image rotation
-rw-r--r--core/src/angle.rs75
-rw-r--r--core/src/content_fit.rs17
-rw-r--r--core/src/image.rs3
-rw-r--r--core/src/lib.rs2
-rw-r--r--core/src/rectangle.rs32
-rw-r--r--core/src/renderer/null.rs5
-rw-r--r--core/src/rotation.rs72
-rw-r--r--core/src/size.rs29
-rw-r--r--core/src/svg.rs3
-rw-r--r--core/src/vector.rs3
-rw-r--r--examples/ferris/Cargo.toml10
-rw-r--r--examples/ferris/src/main.rs201
-rw-r--r--graphics/src/image.rs17
-rw-r--r--renderer/src/fallback.rs12
-rw-r--r--src/lib.rs4
-rw-r--r--tiny_skia/src/engine.rs51
-rw-r--r--tiny_skia/src/layer.rs12
-rw-r--r--tiny_skia/src/lib.rs14
-rw-r--r--tiny_skia/src/vector.rs4
-rw-r--r--wgpu/src/image/mod.rs41
-rw-r--r--wgpu/src/layer.rs9
-rw-r--r--wgpu/src/lib.rs16
-rw-r--r--wgpu/src/shader/image.wgsl32
-rw-r--r--widget/src/image.rs67
-rw-r--r--widget/src/image/viewer.rs5
-rw-r--r--widget/src/svg.rs68
26 files changed, 695 insertions, 109 deletions
diff --git a/core/src/angle.rs b/core/src/angle.rs
index dc3c0e93..9c8a9b24 100644
--- a/core/src/angle.rs
+++ b/core/src/angle.rs
@@ -1,12 +1,17 @@
use crate::{Point, Rectangle, Vector};
use std::f32::consts::{FRAC_PI_2, PI};
-use std::ops::{Add, AddAssign, Div, Mul, RangeInclusive, Sub, SubAssign};
+use std::ops::{Add, AddAssign, Div, Mul, RangeInclusive, Rem, Sub, SubAssign};
/// Degrees
#[derive(Debug, Copy, Clone, PartialEq, PartialOrd)]
pub struct Degrees(pub f32);
+impl Degrees {
+ /// The range of degrees of a circle.
+ pub const RANGE: RangeInclusive<Self> = Self(0.0)..=Self(360.0);
+}
+
impl PartialEq<f32> for Degrees {
fn eq(&self, other: &f32) -> bool {
self.0.eq(other)
@@ -19,6 +24,52 @@ impl PartialOrd<f32> for Degrees {
}
}
+impl From<f32> for Degrees {
+ fn from(degrees: f32) -> Self {
+ Self(degrees)
+ }
+}
+
+impl From<u8> for Degrees {
+ fn from(degrees: u8) -> Self {
+ Self(f32::from(degrees))
+ }
+}
+
+impl From<Degrees> for f32 {
+ fn from(degrees: Degrees) -> Self {
+ degrees.0
+ }
+}
+
+impl From<Degrees> for f64 {
+ fn from(degrees: Degrees) -> Self {
+ Self::from(degrees.0)
+ }
+}
+
+impl Mul<f32> for Degrees {
+ type Output = Degrees;
+
+ fn mul(self, rhs: f32) -> Self::Output {
+ Self(self.0 * rhs)
+ }
+}
+
+impl num_traits::FromPrimitive for Degrees {
+ fn from_i64(n: i64) -> Option<Self> {
+ Some(Self(n as f32))
+ }
+
+ fn from_u64(n: u64) -> Option<Self> {
+ Some(Self(n as f32))
+ }
+
+ fn from_f64(n: f64) -> Option<Self> {
+ Some(Self(n as f32))
+ }
+}
+
/// Radians
#[derive(Debug, Copy, Clone, PartialEq, PartialOrd)]
pub struct Radians(pub f32);
@@ -65,6 +116,12 @@ impl From<u8> for Radians {
}
}
+impl From<Radians> for f32 {
+ fn from(radians: Radians) -> Self {
+ radians.0
+ }
+}
+
impl From<Radians> for f64 {
fn from(radians: Radians) -> Self {
Self::from(radians.0)
@@ -107,6 +164,14 @@ impl Add for Radians {
}
}
+impl Add<Degrees> for Radians {
+ type Output = Self;
+
+ fn add(self, rhs: Degrees) -> Self::Output {
+ Self(self.0 + rhs.0.to_radians())
+ }
+}
+
impl AddAssign for Radians {
fn add_assign(&mut self, rhs: Radians) {
self.0 = self.0 + rhs.0;
@@ -153,6 +218,14 @@ impl Div for Radians {
}
}
+impl Rem for Radians {
+ type Output = Self;
+
+ fn rem(self, rhs: Self) -> Self::Output {
+ Self(self.0 % rhs.0)
+ }
+}
+
impl PartialEq<f32> for Radians {
fn eq(&self, other: &f32) -> bool {
self.0.eq(other)
diff --git a/core/src/content_fit.rs b/core/src/content_fit.rs
index 6bbedc7a..19642716 100644
--- a/core/src/content_fit.rs
+++ b/core/src/content_fit.rs
@@ -1,6 +1,8 @@
//! Control the fit of some content (like an image) within a space.
use crate::Size;
+use std::fmt;
+
/// The strategy used to fit the contents of a widget to its bounding box.
///
/// Each variant of this enum is a strategy that can be applied for resolving
@@ -11,7 +13,7 @@ use crate::Size;
/// in CSS, see [Mozilla's docs][1], or run the `tour` example
///
/// [1]: https://developer.mozilla.org/en-US/docs/Web/CSS/object-fit
-#[derive(Debug, Hash, Clone, Copy, PartialEq, Eq)]
+#[derive(Debug, Hash, Clone, Copy, PartialEq, Eq, Default)]
pub enum ContentFit {
/// Scale as big as it can be without needing to crop or hide parts.
///
@@ -23,6 +25,7 @@ pub enum ContentFit {
/// This is a great fit for when you need to display an image without losing
/// any part of it, particularly when the image itself is the focus of the
/// screen.
+ #[default]
Contain,
/// Scale the image to cover all of the bounding box, cropping if needed.
@@ -117,3 +120,15 @@ impl ContentFit {
}
}
}
+
+impl fmt::Display for ContentFit {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ f.write_str(match self {
+ ContentFit::Contain => "Contain",
+ ContentFit::Cover => "Cover",
+ ContentFit::Fill => "Fill",
+ ContentFit::None => "None",
+ ContentFit::ScaleDown => "Scale Down",
+ })
+ }
+}
diff --git a/core/src/image.rs b/core/src/image.rs
index c38239bc..91a7fd36 100644
--- a/core/src/image.rs
+++ b/core/src/image.rs
@@ -1,7 +1,7 @@
//! Load and draw raster graphics.
pub use bytes::Bytes;
-use crate::{Rectangle, Size};
+use crate::{Radians, Rectangle, Size};
use rustc_hash::FxHasher;
use std::hash::{Hash, Hasher};
@@ -173,5 +173,6 @@ pub trait Renderer: crate::Renderer {
handle: Self::Handle,
filter_method: FilterMethod,
bounds: Rectangle,
+ rotation: Radians,
);
}
diff --git a/core/src/lib.rs b/core/src/lib.rs
index feda4fb4..32156441 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::Rotation;
pub use shadow::Shadow;
pub use shell::Shell;
pub use size::Size;
diff --git a/core/src/rectangle.rs b/core/src/rectangle.rs
index 2ab50137..1556e072 100644
--- a/core/src/rectangle.rs
+++ b/core/src/rectangle.rs
@@ -1,6 +1,6 @@
-use crate::{Point, Size, Vector};
+use crate::{Point, Radians, Size, Vector};
-/// A rectangle.
+/// An axis-aligned rectangle.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct Rectangle<T = f32> {
/// X coordinate of the top-left corner.
@@ -172,6 +172,18 @@ impl Rectangle<f32> {
height: self.height + amount * 2.0,
}
}
+
+ /// Rotates the [`Rectangle`] and returns the smallest [`Rectangle`]
+ /// containing it.
+ pub fn rotate(self, rotation: Radians) -> Self {
+ let size = self.size().rotate(rotation);
+ let position = Point::new(
+ self.center_x() - size.width / 2.0,
+ self.center_y() - size.height / 2.0,
+ );
+
+ Self::new(position, size)
+ }
}
impl std::ops::Mul<f32> for Rectangle<f32> {
@@ -227,3 +239,19 @@ where
}
}
}
+
+impl<T> std::ops::Mul<Vector<T>> for Rectangle<T>
+where
+ T: std::ops::Mul<Output = T> + Copy,
+{
+ type Output = Rectangle<T>;
+
+ fn mul(self, scale: Vector<T>) -> Self {
+ Rectangle {
+ x: self.x * scale.x,
+ y: self.y * scale.y,
+ width: self.width * scale.x,
+ height: self.height * scale.y,
+ }
+ }
+}
diff --git a/core/src/renderer/null.rs b/core/src/renderer/null.rs
index fe38ec87..91519b40 100644
--- a/core/src/renderer/null.rs
+++ b/core/src/renderer/null.rs
@@ -4,7 +4,8 @@ use crate::renderer::{self, Renderer};
use crate::svg;
use crate::text::{self, Text};
use crate::{
- Background, Color, Font, Pixels, Point, Rectangle, Size, Transformation,
+ Background, Color, Font, Pixels, Point, Radians, Rectangle, Size,
+ Transformation,
};
impl Renderer for () {
@@ -171,6 +172,7 @@ impl image::Renderer for () {
_handle: Self::Handle,
_filter_method: image::FilterMethod,
_bounds: Rectangle,
+ _rotation: Radians,
) {
}
}
@@ -185,6 +187,7 @@ impl svg::Renderer for () {
_handle: svg::Handle,
_color: Option<Color>,
_bounds: Rectangle,
+ _rotation: Radians,
) {
}
}
diff --git a/core/src/rotation.rs b/core/src/rotation.rs
new file mode 100644
index 00000000..afa8d79e
--- /dev/null
+++ b/core/src/rotation.rs
@@ -0,0 +1,72 @@
+//! Control the rotation of some content (like an image) within a space.
+use crate::{Degrees, Radians, 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, Clone, Copy, PartialEq)]
+pub enum Rotation {
+ /// The element will float while rotating. The layout will be 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.
+ ///
+ /// This is the default.
+ Floating(Radians),
+ /// The element will be solid while rotating. The layout will be 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.
+ Solid(Radians),
+}
+
+impl Rotation {
+ /// Returns the angle of the [`Rotation`] in [`Radians`].
+ pub fn radians(self) -> Radians {
+ match self {
+ Rotation::Floating(radians) | Rotation::Solid(radians) => radians,
+ }
+ }
+
+ /// Returns a mutable reference to the angle of the [`Rotation`] in [`Radians`].
+ pub fn radians_mut(&mut self) -> &mut Radians {
+ match self {
+ Rotation::Floating(radians) | Rotation::Solid(radians) => radians,
+ }
+ }
+
+ /// Returns the angle of the [`Rotation`] in [`Degrees`].
+ pub fn degrees(self) -> Degrees {
+ Degrees(self.radians().0.to_degrees())
+ }
+
+ /// Applies the [`Rotation`] to the given [`Size`], returning
+ /// the minimum [`Size`] containing the rotated one.
+ pub fn apply(self, size: Size) -> Size {
+ match self {
+ Self::Floating(_) => size,
+ Self::Solid(rotation) => size.rotate(rotation),
+ }
+ }
+}
+
+impl Default for Rotation {
+ fn default() -> Self {
+ Self::Floating(Radians(0.0))
+ }
+}
+
+impl From<Radians> for Rotation {
+ fn from(radians: Radians) -> Self {
+ Self::Floating(radians)
+ }
+}
+
+impl From<f32> for Rotation {
+ fn from(radians: f32) -> Self {
+ Self::Floating(Radians(radians))
+ }
+}
diff --git a/core/src/size.rs b/core/src/size.rs
index c2b5671a..d7459355 100644
--- a/core/src/size.rs
+++ b/core/src/size.rs
@@ -1,4 +1,4 @@
-use crate::Vector;
+use crate::{Radians, Vector};
/// An amount of space in 2 dimensions.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
@@ -51,6 +51,19 @@ impl Size {
height: self.height + other.height,
}
}
+
+ /// Rotates the given [`Size`] and returns the minimum [`Size`]
+ /// containing it.
+ pub fn rotate(self, rotation: Radians) -> Size {
+ let radians = f32::from(rotation);
+
+ Size {
+ width: (self.width * radians.cos()).abs()
+ + (self.height * radians.sin()).abs(),
+ height: (self.width * radians.sin()).abs()
+ + (self.height * radians.cos()).abs(),
+ }
+ }
}
impl<T> From<[T; 2]> for Size<T> {
@@ -113,3 +126,17 @@ where
}
}
}
+
+impl<T> std::ops::Mul<Vector<T>> for Size<T>
+where
+ T: std::ops::Mul<Output = T> + Copy,
+{
+ type Output = Size<T>;
+
+ fn mul(self, scale: Vector<T>) -> Self::Output {
+ Size {
+ width: self.width * scale.x,
+ height: self.height * scale.y,
+ }
+ }
+}
diff --git a/core/src/svg.rs b/core/src/svg.rs
index 0106e0c2..01f102e3 100644
--- a/core/src/svg.rs
+++ b/core/src/svg.rs
@@ -1,5 +1,5 @@
//! Load and draw vector graphics.
-use crate::{Color, Rectangle, Size};
+use crate::{Color, Radians, Rectangle, Size};
use rustc_hash::FxHasher;
use std::borrow::Cow;
@@ -100,5 +100,6 @@ pub trait Renderer: crate::Renderer {
handle: Handle,
color: Option<Color>,
bounds: Rectangle,
+ rotation: Radians,
);
}
diff --git a/core/src/vector.rs b/core/src/vector.rs
index 1380c3b3..049e648f 100644
--- a/core/src/vector.rs
+++ b/core/src/vector.rs
@@ -18,6 +18,9 @@ impl<T> Vector<T> {
impl Vector {
/// The zero [`Vector`].
pub const ZERO: Self = Self::new(0.0, 0.0);
+
+ /// The unit [`Vector`].
+ pub const UNIT: Self = Self::new(0.0, 0.0);
}
impl<T> std::ops::Add for Vector<T>
diff --git a/examples/ferris/Cargo.toml b/examples/ferris/Cargo.toml
new file mode 100644
index 00000000..e98341d2
--- /dev/null
+++ b/examples/ferris/Cargo.toml
@@ -0,0 +1,10 @@
+[package]
+name = "ferris"
+version = "0.1.0"
+authors = ["Héctor Ramón Jiménez <hector0193@gmail.com>"]
+edition = "2021"
+publish = false
+
+[dependencies]
+iced.workspace = true
+iced.features = ["image", "tokio", "debug"]
diff --git a/examples/ferris/src/main.rs b/examples/ferris/src/main.rs
new file mode 100644
index 00000000..b7536740
--- /dev/null
+++ b/examples/ferris/src/main.rs
@@ -0,0 +1,201 @@
+use iced::time::Instant;
+use iced::widget::{
+ checkbox, column, container, image, pick_list, row, slider, text,
+};
+use iced::window;
+use iced::{
+ Alignment, Color, ContentFit, Degrees, Element, Length, Radians, Rotation,
+ Subscription, Theme,
+};
+
+pub fn main() -> iced::Result {
+ iced::program("Ferris - Iced", Image::update, Image::view)
+ .subscription(Image::subscription)
+ .theme(|_| Theme::TokyoNight)
+ .run()
+}
+
+struct Image {
+ width: f32,
+ rotation: Rotation,
+ content_fit: ContentFit,
+ spin: bool,
+ last_tick: Instant,
+}
+
+#[derive(Debug, Clone, Copy)]
+enum Message {
+ WidthChanged(f32),
+ RotationStrategyChanged(RotationStrategy),
+ RotationChanged(Degrees),
+ ContentFitChanged(ContentFit),
+ SpinToggled(bool),
+ RedrawRequested(Instant),
+}
+
+impl Image {
+ fn update(&mut self, message: Message) {
+ match message {
+ Message::WidthChanged(width) => {
+ self.width = width;
+ }
+ Message::RotationStrategyChanged(strategy) => {
+ self.rotation = match strategy {
+ RotationStrategy::Floating => {
+ Rotation::Floating(self.rotation.radians())
+ }
+ RotationStrategy::Solid => {
+ Rotation::Solid(self.rotation.radians())
+ }
+ };
+ }
+ Message::RotationChanged(rotation) => {
+ self.rotation = match self.rotation {
+ Rotation::Floating(_) => {
+ Rotation::Floating(rotation.into())
+ }
+ Rotation::Solid(_) => Rotation::Solid(rotation.into()),
+ };
+ }
+ Message::ContentFitChanged(content_fit) => {
+ self.content_fit = content_fit;
+ }
+ Message::SpinToggled(spin) => {
+ self.spin = spin;
+ self.last_tick = Instant::now();
+ }
+ Message::RedrawRequested(now) => {
+ const ROTATION_SPEED: Degrees = Degrees(360.0);
+
+ let delta = (now - self.last_tick).as_millis() as f32 / 1_000.0;
+
+ *self.rotation.radians_mut() = (self.rotation.radians()
+ + ROTATION_SPEED * delta)
+ % (2.0 * Radians::PI);
+
+ self.last_tick = now;
+ }
+ }
+ }
+
+ fn subscription(&self) -> Subscription<Message> {
+ if self.spin {
+ window::frames().map(Message::RedrawRequested)
+ } else {
+ Subscription::none()
+ }
+ }
+
+ fn view(&self) -> Element<Message> {
+ let i_am_ferris = container(
+ column![
+ "Hello!",
+ Element::from(
+ image(format!(
+ "{}/../tour/images/ferris.png",
+ env!("CARGO_MANIFEST_DIR")
+ ))
+ .width(self.width)
+ .content_fit(self.content_fit)
+ .rotation(self.rotation)
+ )
+ .explain(Color::WHITE),
+ "I am Ferris!"
+ ]
+ .spacing(20)
+ .align_items(Alignment::Center),
+ )
+ .width(Length::Fill)
+ .height(Length::Fill)
+ .center_x()
+ .center_y();
+
+ let sizing = row![
+ pick_list(
+ [
+ ContentFit::Contain,
+ ContentFit::Cover,
+ ContentFit::Fill,
+ ContentFit::None,
+ ContentFit::ScaleDown
+ ],
+ Some(self.content_fit),
+ Message::ContentFitChanged
+ )
+ .width(Length::Fill),
+ column![
+ slider(100.0..=500.0, self.width, Message::WidthChanged),
+ text(format!("Width: {}px", self.width))
+ .size(14)
+ .line_height(1.0)
+ ]
+ .spacing(5)
+ .align_items(Alignment::Center)
+ ]
+ .spacing(10);
+
+ let rotation = row![
+ pick_list(
+ [RotationStrategy::Floating, RotationStrategy::Solid],
+ Some(match self.rotation {
+ Rotation::Floating(_) => RotationStrategy::Floating,
+ Rotation::Solid(_) => RotationStrategy::Solid,
+ }),
+ Message::RotationStrategyChanged,
+ )
+ .width(Length::Fill),
+ row![
+ column![
+ slider(
+ Degrees::RANGE,
+ self.rotation.degrees(),
+ Message::RotationChanged
+ ),
+ text(format!(
+ "Rotation: {:.0}°",
+ f32::from(self.rotation.degrees())
+ ))
+ .size(14)
+ .line_height(1.0)
+ ]
+ .spacing(5)
+ .align_items(Alignment::Center),
+ checkbox("Spin!", self.spin).on_toggle(Message::SpinToggled)
+ ]
+ .spacing(5)
+ .align_items(Alignment::Center)
+ ]
+ .spacing(10);
+
+ container(column![i_am_ferris, sizing, rotation].spacing(10))
+ .padding(10)
+ .into()
+ }
+}
+
+impl Default for Image {
+ fn default() -> Self {
+ Self {
+ width: 300.0,
+ rotation: Rotation::default(),
+ content_fit: ContentFit::default(),
+ spin: false,
+ last_tick: Instant::now(),
+ }
+ }
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+enum RotationStrategy {
+ Floating,
+ Solid,
+}
+
+impl std::fmt::Display for RotationStrategy {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ f.write_str(match self {
+ Self::Floating => "Floating",
+ Self::Solid => "Solid",
+ })
+ }
+}
diff --git a/graphics/src/image.rs b/graphics/src/image.rs
index 04c45057..9d09bf4c 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, Radians, Rectangle};
/// A raster or vector image.
#[derive(Debug, Clone, PartialEq)]
@@ -19,6 +17,9 @@ pub enum Image {
/// The bounds of the image.
bounds: Rectangle,
+
+ /// The rotation of the image in radians
+ rotation: Radians,
},
/// A vector image.
Vector {
@@ -30,6 +31,9 @@ pub enum Image {
/// The bounds of the image.
bounds: Rectangle,
+
+ /// The rotation of the image in radians
+ rotation: Radians,
},
}
@@ -37,9 +41,12 @@ impl Image {
/// Returns the bounds of the [`Image`].
pub fn bounds(&self) -> Rectangle {
match self {
- Image::Raster { bounds, .. } | Image::Vector { bounds, .. } => {
- *bounds
+ Image::Raster {
+ bounds, rotation, ..
}
+ | Image::Vector {
+ bounds, rotation, ..
+ } => bounds.rotate(*rotation),
}
}
}
diff --git a/renderer/src/fallback.rs b/renderer/src/fallback.rs
index 5f69b420..a077031b 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, Rectangle, Size, Transformation,
+ self, Background, Color, Point, Radians, Rectangle, Size, Transformation,
};
use crate::graphics;
use crate::graphics::compositor;
@@ -154,11 +154,12 @@ where
handle: Self::Handle,
filter_method: image::FilterMethod,
bounds: Rectangle,
+ rotation: Radians,
) {
delegate!(
self,
renderer,
- renderer.draw_image(handle, filter_method, bounds)
+ renderer.draw_image(handle, filter_method, bounds, rotation)
);
}
}
@@ -177,8 +178,13 @@ where
handle: svg::Handle,
color: Option<Color>,
bounds: Rectangle,
+ rotation: Radians,
) {
- delegate!(self, renderer, renderer.draw_svg(handle, color, bounds));
+ delegate!(
+ self,
+ renderer,
+ renderer.draw_svg(handle, color, bounds, rotation)
+ );
}
}
diff --git a/src/lib.rs b/src/lib.rs
index 7517dd14..50ee7ecc 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, Rotation, Shadow, Size,
+ Theme, Transformation, Vector,
};
pub mod clipboard {
diff --git a/tiny_skia/src/engine.rs b/tiny_skia/src/engine.rs
index fbca1274..e9935bdb 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,32 @@ impl Engine {
handle,
filter_method,
bounds,
+ rotation,
} => {
- 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 radians = f32::from(*rotation);
+
+ let transform = into_transform(transformation).post_rotate_at(
+ radians.to_degrees(),
+ center.x,
+ center.y,
+ );
self.raster_pipeline.draw(
handle,
*filter_method,
*bounds,
- _pixels,
- into_transform(_transformation),
+ pixels,
+ transform,
clip_mask,
);
}
@@ -574,21 +584,32 @@ impl Engine {
handle,
color,
bounds,
+ rotation,
} => {
- 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 radians = f32::from(*rotation);
+
+ let transform = into_transform(transformation).post_rotate_at(
+ radians.to_degrees(),
+ 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..c907c93c 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, Radians, Rectangle,
+ Transformation,
+};
use crate::graphics::damage;
use crate::graphics::layer;
use crate::graphics::text::{Editor, Paragraph, Text};
@@ -121,11 +121,13 @@ impl Layer {
filter_method: image::FilterMethod,
bounds: Rectangle,
transformation: Transformation,
+ rotation: Radians,
) {
let image = Image::Raster {
handle,
filter_method,
bounds: bounds * transformation,
+ rotation,
};
self.images.push(image);
@@ -137,11 +139,13 @@ impl Layer {
color: Option<Color>,
bounds: Rectangle,
transformation: Transformation,
+ rotation: Radians,
) {
let svg = Image::Vector {
handle,
color,
bounds: bounds * transformation,
+ rotation,
};
self.images.push(svg);
diff --git a/tiny_skia/src/lib.rs b/tiny_skia/src/lib.rs
index 4c2c9430..75aaaf92 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, Radians, Rectangle, Transformation,
};
use crate::engine::Engine;
use crate::graphics::compositor;
@@ -377,9 +377,16 @@ impl core::image::Renderer for Renderer {
handle: Self::Handle,
filter_method: core::image::FilterMethod,
bounds: Rectangle,
+ rotation: Radians,
) {
let (layer, transformation) = self.layers.current_mut();
- layer.draw_image(handle, filter_method, bounds, transformation);
+ layer.draw_image(
+ handle,
+ filter_method,
+ bounds,
+ transformation,
+ rotation,
+ );
}
}
@@ -397,9 +404,10 @@ impl core::svg::Renderer for Renderer {
handle: core::svg::Handle,
color: Option<Color>,
bounds: Rectangle,
+ rotation: Radians,
) {
let (layer, transformation) = self.layers.current_mut();
- layer.draw_svg(handle, color, bounds, transformation);
+ layer.draw_svg(handle, color, bounds, transformation, rotation);
}
}
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..285eb2f6 100644
--- a/wgpu/src/image/mod.rs
+++ b/wgpu/src/image/mod.rs
@@ -135,14 +135,18 @@ impl Pipeline {
attributes: &wgpu::vertex_attr_array!(
// Position
0 => Float32x2,
- // Scale
+ // Center
1 => Float32x2,
- // Atlas position
+ // Image size
2 => Float32x2,
+ // Rotation
+ 3 => Float32,
+ // Atlas position
+ 4 => Float32x2,
// Atlas scale
- 3 => Float32x2,
+ 5 => Float32x2,
// Layer
- 4 => Sint32,
+ 6 => Sint32,
),
}],
},
@@ -224,6 +228,7 @@ impl Pipeline {
handle,
filter_method,
bounds,
+ rotation,
} => {
if let Some(atlas_entry) =
cache.upload_raster(device, encoder, handle)
@@ -231,6 +236,7 @@ impl Pipeline {
add_instances(
[bounds.x, bounds.y],
[bounds.width, bounds.height],
+ f32::from(*rotation),
atlas_entry,
match filter_method {
crate::core::image::FilterMethod::Nearest => {
@@ -251,6 +257,7 @@ impl Pipeline {
handle,
color,
bounds,
+ rotation,
} => {
let size = [bounds.width, bounds.height];
@@ -260,6 +267,7 @@ impl Pipeline {
add_instances(
[bounds.x, bounds.y],
size,
+ f32::from(*rotation),
atlas_entry,
nearest_instances,
);
@@ -487,7 +495,9 @@ impl Data {
#[derive(Debug, Clone, Copy, Zeroable, Pod)]
struct Instance {
_position: [f32; 2],
+ _center: [f32; 2],
_size: [f32; 2],
+ _rotation: f32,
_position_in_atlas: [f32; 2],
_size_in_atlas: [f32; 2],
_layer: u32,
@@ -506,12 +516,25 @@ struct Uniforms {
fn add_instances(
image_position: [f32; 2],
image_size: [f32; 2],
+ rotation: f32,
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,
+ allocation,
+ instances,
+ );
}
atlas::Entry::Fragmented { fragments, size } => {
let scaling_x = image_size[0] / size.width as f32;
@@ -537,7 +560,9 @@ fn add_instances(
fragment_height as f32 * scaling_y,
];
- add_instance(position, size, allocation, instances);
+ add_instance(
+ position, center, size, rotation, allocation, instances,
+ );
}
}
}
@@ -546,7 +571,9 @@ fn add_instances(
#[inline]
fn add_instance(
position: [f32; 2],
+ center: [f32; 2],
size: [f32; 2],
+ rotation: f32,
allocation: &atlas::Allocation,
instances: &mut Vec<Instance>,
) {
@@ -556,7 +583,9 @@ fn add_instance(
let instance = Instance {
_position: position,
+ _center: center,
_size: size,
+ _rotation: rotation,
_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..e0242c59 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, Radians, Rectangle, Transformation,
+};
use crate::graphics;
use crate::graphics::color;
use crate::graphics::layer;
@@ -117,11 +118,13 @@ impl Layer {
filter_method: crate::core::image::FilterMethod,
bounds: Rectangle,
transformation: Transformation,
+ rotation: Radians,
) {
let image = Image::Raster {
handle,
filter_method,
bounds: bounds * transformation,
+ rotation,
};
self.images.push(image);
@@ -133,11 +136,13 @@ impl Layer {
color: Option<Color>,
bounds: Rectangle,
transformation: Transformation,
+ rotation: Radians,
) {
let svg = Image::Vector {
handle,
color,
bounds: bounds * transformation,
+ rotation,
};
self.images.push(svg);
diff --git a/wgpu/src/lib.rs b/wgpu/src/lib.rs
index 178522de..6920067b 100644
--- a/wgpu/src/lib.rs
+++ b/wgpu/src/lib.rs
@@ -61,7 +61,8 @@ pub use settings::Settings;
pub use geometry::Geometry;
use crate::core::{
- Background, Color, Font, Pixels, Point, Rectangle, Size, Transformation,
+ Background, Color, Font, Pixels, Point, Radians, Rectangle, Size,
+ Transformation, Vector,
};
use crate::graphics::text::{Editor, Paragraph};
use crate::graphics::Viewport;
@@ -378,7 +379,6 @@ impl Renderer {
use crate::core::alignment;
use crate::core::text::Renderer as _;
use crate::core::Renderer as _;
- use crate::core::Vector;
self.with_layer(
Rectangle::with_size(viewport.logical_size()),
@@ -517,9 +517,16 @@ impl core::image::Renderer for Renderer {
handle: Self::Handle,
filter_method: core::image::FilterMethod,
bounds: Rectangle,
+ rotation: Radians,
) {
let (layer, transformation) = self.layers.current_mut();
- layer.draw_image(handle, filter_method, bounds, transformation);
+ layer.draw_image(
+ handle,
+ filter_method,
+ bounds,
+ transformation,
+ rotation,
+ );
}
}
@@ -534,9 +541,10 @@ impl core::svg::Renderer for Renderer {
handle: core::svg::Handle,
color_filter: Option<Color>,
bounds: Rectangle,
+ rotation: Radians,
) {
let (layer, transformation) = self.layers.current_mut();
- layer.draw_svg(handle, color_filter, bounds, transformation);
+ layer.draw_svg(handle, color_filter, bounds, transformation, rotation);
}
}
diff --git a/wgpu/src/shader/image.wgsl b/wgpu/src/shader/image.wgsl
index 7b2e5238..71bf939c 100644
--- a/wgpu/src/shader/image.wgsl
+++ b/wgpu/src/shader/image.wgsl
@@ -9,10 +9,12 @@ 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) scale: vec2<f32>,
+ @location(3) rotation: f32,
+ @location(4) atlas_pos: vec2<f32>,
+ @location(5) atlas_scale: vec2<f32>,
+ @location(6) layer: i32,
}
struct VertexOutput {
@@ -25,24 +27,34 @@ 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>(
- vec4<f32>(input.scale.x, 0.0, 0.0, 0.0),
- vec4<f32>(0.0, input.scale.y, 0.0, 0.0),
+ // Calculate the vertex position and move the center to the origin
+ 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);
+ 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>(input.pos, 0.0, 1.0)
+ vec4<f32>(0.0, 0.0, 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 * (vec4<f32>(input.center, 0.0, 0.0) + 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..45209a91 100644
--- a/widget/src/image.rs
+++ b/widget/src/image.rs
@@ -8,7 +8,8 @@ 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, Point, Rectangle, Rotation, Size,
+ Vector, Widget,
};
pub use image::{FilterMethod, Handle};
@@ -36,6 +37,7 @@ pub struct Image<Handle> {
height: Length,
content_fit: ContentFit,
filter_method: FilterMethod,
+ rotation: Rotation,
}
impl<Handle> Image<Handle> {
@@ -45,8 +47,9 @@ impl<Handle> Image<Handle> {
handle: handle.into(),
width: Length::Shrink,
height: Length::Shrink,
- content_fit: ContentFit::Contain,
+ content_fit: ContentFit::default(),
filter_method: FilterMethod::default(),
+ rotation: Rotation::default(),
}
}
@@ -75,6 +78,12 @@ impl<Handle> Image<Handle> {
self.filter_method = filter_method;
self
}
+
+ /// Applies the given [`Rotation`] to the [`Image`].
+ pub fn rotation(mut self, rotation: impl Into<Rotation>) -> Self {
+ self.rotation = rotation.into();
+ self
+ }
}
/// Computes the layout of an [`Image`].
@@ -85,22 +94,24 @@ pub fn layout<Renderer, Handle>(
width: Length,
height: Length,
content_fit: ContentFit,
+ rotation: Rotation,
) -> 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.apply(image_size);
// 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 +135,44 @@ pub fn draw<Renderer, Handle>(
handle: &Handle,
content_fit: ContentFit,
filter_method: FilterMethod,
+ rotation: Rotation,
) 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.apply(image_size);
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 = Vector::new(
+ adjusted_fit.width / rotated_size.width,
+ adjusted_fit.height / rotated_size.height,
+ );
+
+ let final_size = image_size * scale;
+
+ let position = match content_fit {
+ ContentFit::None => Point::new(
+ bounds.x + (rotated_size.width - adjusted_fit.width) / 2.0,
+ bounds.y + (rotated_size.height - adjusted_fit.height) / 2.0,
+ ),
+ _ => Point::new(
+ bounds.center_x() - final_size.width / 2.0,
+ bounds.center_y() - final_size.height / 2.0,
+ ),
+ };
- let drawing_bounds = Rectangle {
- width: adjusted_fit.width,
- height: adjusted_fit.height,
- ..bounds
- };
+ let drawing_bounds = Rectangle::new(position, final_size);
+ let render = |renderer: &mut Renderer| {
renderer.draw_image(
handle.clone(),
filter_method,
- drawing_bounds + offset,
+ drawing_bounds,
+ rotation.radians(),
);
};
@@ -187,6 +210,7 @@ where
self.width,
self.height,
self.content_fit,
+ self.rotation,
)
}
@@ -206,6 +230,7 @@ where
&self.handle,
self.content_fit,
self.filter_method,
+ self.rotation,
);
}
}
diff --git a/widget/src/image/viewer.rs b/widget/src/image/viewer.rs
index 2e6a7528..5eb76452 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, Element, Layout, Length, Pixels, Point, Rectangle, Shell, Size,
- Vector, Widget,
+ Clipboard, Element, 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.
@@ -341,6 +341,7 @@ where
y: bounds.y,
..Rectangle::with_size(image_size)
},
+ Radians(0.0),
);
});
});
diff --git a/widget/src/svg.rs b/widget/src/svg.rs
index eb142189..c1fccba1 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, Rotation,
+ Size, Theme, Vector, Widget,
};
use std::path::PathBuf;
@@ -29,6 +29,7 @@ where
height: Length,
content_fit: ContentFit,
class: Theme::Class<'a>,
+ rotation: Rotation,
}
impl<'a, Theme> Svg<'a, Theme>
@@ -43,6 +44,7 @@ where
height: Length::Shrink,
content_fit: ContentFit::Contain,
class: Theme::default(),
+ rotation: Rotation::default(),
}
}
@@ -95,6 +97,12 @@ where
self.class = class.into();
self
}
+
+ /// Applies the given [`Rotation`] to the [`Svg`].
+ pub fn rotation(mut self, rotation: impl Into<Rotation>) -> Self {
+ self.rotation = rotation.into();
+ self
+ }
}
impl<'a, Message, Theme, Renderer> Widget<Message, Theme, Renderer>
@@ -120,11 +128,14 @@ 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.apply(image_size);
+
// 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,35 +164,46 @@ 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.apply(image_size);
let bounds = layout.bounds();
- let adjusted_fit = self.content_fit.fit(image_size, bounds.size());
- let is_mouse_over = cursor.is_over(bounds);
+ let adjusted_fit = self.content_fit.fit(rotated_size, bounds.size());
+ let scale = Vector::new(
+ adjusted_fit.width / rotated_size.width,
+ adjusted_fit.height / rotated_size.height,
+ );
+
+ let final_size = image_size * scale;
+
+ let position = match self.content_fit {
+ ContentFit::None => Point::new(
+ bounds.x + (rotated_size.width - adjusted_fit.width) / 2.0,
+ bounds.y + (rotated_size.height - adjusted_fit.height) / 2.0,
+ ),
+ _ => Point::new(
+ bounds.center_x() - final_size.width / 2.0,
+ bounds.center_y() - final_size.height / 2.0,
+ ),
+ };
- 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::new(position, final_size);
- let drawing_bounds = Rectangle {
- width: adjusted_fit.width,
- height: adjusted_fit.height,
- ..bounds
- };
+ let is_mouse_over = cursor.is_over(bounds);
- let status = if is_mouse_over {
- Status::Hovered
- } else {
- Status::Idle
- };
+ let status = if is_mouse_over {
+ Status::Hovered
+ } else {
+ Status::Idle
+ };
- let style = theme.style(&self.class, status);
+ let style = theme.style(&self.class, status);
+ let render = |renderer: &mut Renderer| {
renderer.draw_svg(
self.handle.clone(),
style.color,
- drawing_bounds + offset,
+ drawing_bounds,
+ self.rotation.radians(),
);
};