diff options
author | 2024-05-08 19:16:20 +0900 | |
---|---|---|
committer | 2024-05-08 19:16:20 +0900 | |
commit | ff26fb7df43bd241253af85e0621e2cc030e9cec (patch) | |
tree | 2b3272bb178f8757169511966589fe6a106733f4 /core | |
parent | 0ebe0629cef37aee5c48b9409fc36618a3a3e60d (diff) | |
parent | 477887b3870aa5fbdab96c3a06f3b930462d7842 (diff) | |
download | iced-ff26fb7df43bd241253af85e0621e2cc030e9cec.tar.gz iced-ff26fb7df43bd241253af85e0621e2cc030e9cec.tar.bz2 iced-ff26fb7df43bd241253af85e0621e2cc030e9cec.zip |
Merge branch 'iced-rs-master' into viewer_content_fit
Diffstat (limited to 'core')
-rw-r--r-- | core/Cargo.toml | 1 | ||||
-rw-r--r-- | core/src/angle.rs | 75 | ||||
-rw-r--r-- | core/src/content_fit.rs | 17 | ||||
-rw-r--r-- | core/src/image.rs | 211 | ||||
-rw-r--r-- | core/src/layout.rs | 2 | ||||
-rw-r--r-- | core/src/lib.rs | 2 | ||||
-rw-r--r-- | core/src/mouse/interaction.rs | 1 | ||||
-rw-r--r-- | core/src/overlay.rs | 2 | ||||
-rw-r--r-- | core/src/rectangle.rs | 32 | ||||
-rw-r--r-- | core/src/renderer/null.rs | 7 | ||||
-rw-r--r-- | core/src/rotation.rs | 72 | ||||
-rw-r--r-- | core/src/size.rs | 29 | ||||
-rw-r--r-- | core/src/svg.rs | 4 | ||||
-rw-r--r-- | core/src/vector.rs | 3 | ||||
-rw-r--r-- | core/src/widget.rs | 2 |
15 files changed, 334 insertions, 126 deletions
diff --git a/core/Cargo.toml b/core/Cargo.toml index 7bd37021..3c557bca 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -19,6 +19,7 @@ advanced = [] [dependencies] bitflags.workspace = true +bytes.workspace = true glam.workspace = true log.workspace = true num-traits.workspace = true 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 dc74e5c1..82ecdd0f 100644 --- a/core/src/image.rs +++ b/core/src/image.rs @@ -1,16 +1,45 @@ //! Load and draw raster graphics. -use crate::{Rectangle, Size}; +pub use bytes::Bytes; + +use crate::{Radians, Rectangle, Size}; use rustc_hash::FxHasher; -use std::hash::{Hash, Hasher as _}; -use std::path::PathBuf; -use std::sync::Arc; +use std::hash::{Hash, Hasher}; +use std::path::{Path, PathBuf}; /// A handle of some image data. -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct Handle { - id: u64, - data: Data, +#[derive(Clone, PartialEq, Eq)] +pub enum Handle { + /// A file handle. The image data will be read + /// from the file path. + /// + /// Use [`from_path`] to create this variant. + /// + /// [`from_path`]: Self::from_path + Path(Id, PathBuf), + + /// A handle pointing to some encoded image bytes in-memory. + /// + /// Use [`from_bytes`] to create this variant. + /// + /// [`from_bytes`]: Self::from_bytes + Bytes(Id, Bytes), + + /// A handle pointing to decoded image pixels in RGBA format. + /// + /// Use [`from_rgba`] to create this variant. + /// + /// [`from_rgba`]: Self::from_rgba + Rgba { + /// The id of this handle. + id: Id, + /// The width of the image. + width: u32, + /// The height of the image. + height: u32, + /// The pixels. + pixels: Bytes, + }, } impl Handle { @@ -18,56 +47,48 @@ impl Handle { /// /// Makes an educated guess about the image format by examining the data in the file. pub fn from_path<T: Into<PathBuf>>(path: T) -> Handle { - Self::from_data(Data::Path(path.into())) - } + let path = path.into(); - /// Creates an image [`Handle`] containing the image pixels directly. This - /// function expects the input data to be provided as a `Vec<u8>` of RGBA - /// pixels. - /// - /// This is useful if you have already decoded your image. - pub fn from_pixels( - width: u32, - height: u32, - pixels: impl AsRef<[u8]> + Send + Sync + 'static, - ) -> Handle { - Self::from_data(Data::Rgba { - width, - height, - pixels: Bytes::new(pixels), - }) + Self::Path(Id::path(&path), path) } - /// Creates an image [`Handle`] containing the image data directly. + /// Creates an image [`Handle`] containing the encoded image data directly. /// /// Makes an educated guess about the image format by examining the given data. /// /// This is useful if you already have your image loaded in-memory, maybe /// because you downloaded or generated it procedurally. - pub fn from_memory( - bytes: impl AsRef<[u8]> + Send + Sync + 'static, - ) -> Handle { - Self::from_data(Data::Bytes(Bytes::new(bytes))) + pub fn from_bytes(bytes: impl Into<Bytes>) -> Handle { + Self::Bytes(Id::unique(), bytes.into()) } - fn from_data(data: Data) -> Handle { - let mut hasher = FxHasher::default(); - data.hash(&mut hasher); - - Handle { - id: hasher.finish(), - data, + /// Creates an image [`Handle`] containing the decoded image pixels directly. + /// + /// This function expects the pixel data to be provided as a collection of [`Bytes`] + /// of RGBA pixels. Therefore, the length of the pixel data should always be + /// `width * height * 4`. + /// + /// This is useful if you have already decoded your image. + pub fn from_rgba( + width: u32, + height: u32, + pixels: impl Into<Bytes>, + ) -> Handle { + Self::Rgba { + id: Id::unique(), + width, + height, + pixels: pixels.into(), } } /// Returns the unique identifier of the [`Handle`]. - pub fn id(&self) -> u64 { - self.id - } - - /// Returns a reference to the image [`Data`]. - pub fn data(&self) -> &Data { - &self.data + pub fn id(&self) -> Id { + match self { + Handle::Path(id, _) + | Handle::Bytes(id, _) + | Handle::Rgba { id, .. } => *id, + } } } @@ -80,90 +101,46 @@ where } } -impl Hash for Handle { - fn hash<H: std::hash::Hasher>(&self, state: &mut H) { - self.id.hash(state); - } -} - -/// A wrapper around raw image data. -/// -/// It behaves like a `&[u8]`. -#[derive(Clone)] -pub struct Bytes(Arc<dyn AsRef<[u8]> + Send + Sync + 'static>); - -impl Bytes { - /// Creates new [`Bytes`] around `data`. - pub fn new(data: impl AsRef<[u8]> + Send + Sync + 'static) -> Self { - Self(Arc::new(data)) - } -} - -impl std::fmt::Debug for Bytes { +impl std::fmt::Debug for Handle { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - self.0.as_ref().as_ref().fmt(f) + match self { + Self::Path(_, path) => write!(f, "Path({path:?})"), + Self::Bytes(_, _) => write!(f, "Bytes(...)"), + Self::Rgba { width, height, .. } => { + write!(f, "Pixels({width} * {height})") + } + } } } -impl std::hash::Hash for Bytes { - fn hash<H: std::hash::Hasher>(&self, state: &mut H) { - self.0.as_ref().as_ref().hash(state); - } -} +/// The unique identifier of some [`Handle`] data. +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct Id(_Id); -impl PartialEq for Bytes { - fn eq(&self, other: &Self) -> bool { - let a = self.as_ref(); - let b = other.as_ref(); - core::ptr::eq(a, b) || a == b - } +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +enum _Id { + Unique(u64), + Hash(u64), } -impl Eq for Bytes {} - -impl AsRef<[u8]> for Bytes { - fn as_ref(&self) -> &[u8] { - self.0.as_ref().as_ref() - } -} +impl Id { + fn unique() -> Self { + use std::sync::atomic::{self, AtomicU64}; -impl std::ops::Deref for Bytes { - type Target = [u8]; + static NEXT_ID: AtomicU64 = AtomicU64::new(0); - fn deref(&self) -> &[u8] { - self.0.as_ref().as_ref() + Self(_Id::Unique(NEXT_ID.fetch_add(1, atomic::Ordering::Relaxed))) } -} -/// The data of a raster image. -#[derive(Clone, PartialEq, Eq, Hash)] -pub enum Data { - /// File data - Path(PathBuf), + fn path(path: impl AsRef<Path>) -> Self { + let hash = { + let mut hasher = FxHasher::default(); + path.as_ref().hash(&mut hasher); - /// In-memory data - Bytes(Bytes), - - /// Decoded image pixels in RGBA format. - Rgba { - /// The width of the image. - width: u32, - /// The height of the image. - height: u32, - /// The pixels. - pixels: Bytes, - }, -} + hasher.finish() + }; -impl std::fmt::Debug for Data { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - Data::Path(path) => write!(f, "Path({path:?})"), - Data::Bytes(_) => write!(f, "Bytes(...)"), - Data::Rgba { width, height, .. } => { - write!(f, "Pixels({width} * {height})") - } - } + Self(_Id::Hash(hash)) } } @@ -184,7 +161,7 @@ pub trait Renderer: crate::Renderer { /// The image Handle to be displayed. Iced exposes its own default implementation of a [`Handle`] /// /// [`Handle`]: Self::Handle - type Handle: Clone + Hash; + type Handle: Clone; /// Returns the dimensions of an image for the given [`Handle`]. fn measure_image(&self, handle: &Self::Handle) -> Size<u32>; @@ -196,5 +173,7 @@ pub trait Renderer: crate::Renderer { handle: Self::Handle, filter_method: FilterMethod, bounds: Rectangle, + rotation: Radians, + opacity: f32, ); } diff --git a/core/src/layout.rs b/core/src/layout.rs index 95720aba..98d05602 100644 --- a/core/src/layout.rs +++ b/core/src/layout.rs @@ -54,7 +54,7 @@ impl<'a> Layout<'a> { } /// Returns an iterator over the [`Layout`] of the children of a [`Node`]. - pub fn children(self) -> impl Iterator<Item = Layout<'a>> { + pub fn children(self) -> impl DoubleEndedIterator<Item = Layout<'a>> { self.node.children().iter().map(move |node| { Layout::with_offset( Vector::new(self.position.x, self.position.y), 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/mouse/interaction.rs b/core/src/mouse/interaction.rs index 6ad66229..065eb8e7 100644 --- a/core/src/mouse/interaction.rs +++ b/core/src/mouse/interaction.rs @@ -3,6 +3,7 @@ #[allow(missing_docs)] pub enum Interaction { #[default] + None, Idle, Pointer, Grab, diff --git a/core/src/overlay.rs b/core/src/overlay.rs index 03076a30..3a57fe16 100644 --- a/core/src/overlay.rs +++ b/core/src/overlay.rs @@ -79,7 +79,7 @@ where _viewport: &Rectangle, _renderer: &Renderer, ) -> mouse::Interaction { - mouse::Interaction::Idle + mouse::Interaction::None } /// Returns true if the cursor is over the [`Overlay`]. 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..e8709dbc 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,8 @@ impl image::Renderer for () { _handle: Self::Handle, _filter_method: image::FilterMethod, _bounds: Rectangle, + _rotation: Radians, + _opacity: f32, ) { } } @@ -185,6 +188,8 @@ impl svg::Renderer for () { _handle: svg::Handle, _color: Option<Color>, _bounds: Rectangle, + _rotation: Radians, + _opacity: f32, ) { } } 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..946b8156 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,7 @@ pub trait Renderer: crate::Renderer { handle: Handle, color: Option<Color>, bounds: Rectangle, + rotation: Radians, + opacity: f32, ); } 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/core/src/widget.rs b/core/src/widget.rs index 58a9f19b..b02e3a4f 100644 --- a/core/src/widget.rs +++ b/core/src/widget.rs @@ -137,7 +137,7 @@ where _viewport: &Rectangle, _renderer: &Renderer, ) -> mouse::Interaction { - mouse::Interaction::Idle + mouse::Interaction::None } /// Returns the overlay of the [`Widget`], if there is any. |