Skip to content

Commit

Permalink
Merge pull request #2334 from DKolter/image-rotation
Browse files Browse the repository at this point in the history
Adding feature: Image rotation
  • Loading branch information
hecrj authored May 3, 2024
2 parents fe240a9 + 4010e39 commit 1cefe6b
Show file tree
Hide file tree
Showing 26 changed files with 695 additions and 109 deletions.
75 changes: 74 additions & 1 deletion core/src/angle.rs
Original file line number Diff line number Diff line change
@@ -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)
Expand All @@ -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);
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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)
Expand Down
17 changes: 16 additions & 1 deletion core/src/content_fit.rs
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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.
///
Expand All @@ -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.
Expand Down Expand Up @@ -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",
})
}
}
3 changes: 2 additions & 1 deletion core/src/image.rs
Original file line number Diff line number Diff line change
@@ -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};
Expand Down Expand Up @@ -173,5 +173,6 @@ pub trait Renderer: crate::Renderer {
handle: Self::Handle,
filter_method: FilterMethod,
bounds: Rectangle,
rotation: Radians,
);
}
2 changes: 2 additions & 0 deletions core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ mod padding;
mod pixels;
mod point;
mod rectangle;
mod rotation;
mod shadow;
mod shell;
mod size;
Expand All @@ -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;
Expand Down
32 changes: 30 additions & 2 deletions core/src/rectangle.rs
Original file line number Diff line number Diff line change
@@ -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.
Expand Down Expand Up @@ -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> {
Expand Down Expand Up @@ -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,
}
}
}
5 changes: 4 additions & 1 deletion core/src/renderer/null.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 () {
Expand Down Expand Up @@ -171,6 +172,7 @@ impl image::Renderer for () {
_handle: Self::Handle,
_filter_method: image::FilterMethod,
_bounds: Rectangle,
_rotation: Radians,
) {
}
}
Expand All @@ -185,6 +187,7 @@ impl svg::Renderer for () {
_handle: svg::Handle,
_color: Option<Color>,
_bounds: Rectangle,
_rotation: Radians,
) {
}
}
72 changes: 72 additions & 0 deletions core/src/rotation.rs
Original file line number Diff line number Diff line change
@@ -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))
}
}
29 changes: 28 additions & 1 deletion core/src/size.rs
Original file line number Diff line number Diff line change
@@ -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)]
Expand Down Expand Up @@ -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> {
Expand Down Expand Up @@ -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,
}
}
}
3 changes: 2 additions & 1 deletion core/src/svg.rs
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -100,5 +100,6 @@ pub trait Renderer: crate::Renderer {
handle: Handle,
color: Option<Color>,
bounds: Rectangle,
rotation: Radians,
);
}
Loading

0 comments on commit 1cefe6b

Please sign in to comment.