From bdeeaa290d480b904a47a74655e5d78893ac9808 Mon Sep 17 00:00:00 2001 From: amrbashir Date: Tue, 27 Feb 2024 01:28:41 +0200 Subject: [PATCH] dpi: add `LogicalUnit`, `PhysicalUnit`, and `Unit` Part-off: https://github.com/rust-windowing/winit/issues/2395 --- dpi/CHANGELOG.md | 14 +++ dpi/src/lib.rs | 273 ++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 285 insertions(+), 2 deletions(-) create mode 100644 dpi/CHANGELOG.md diff --git a/dpi/CHANGELOG.md b/dpi/CHANGELOG.md new file mode 100644 index 0000000000..94fdfcdcb8 --- /dev/null +++ b/dpi/CHANGELOG.md @@ -0,0 +1,14 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +Please keep one empty line before and after all headers. (This is required for +`git` to produce a conflict when a release is made while a PR is open and the +PR's changelog entry would go into the wrong section). + +And please only add new entries to the top of this list, right below the `# +Unreleased` header. + +# Unreleased + +- Add `LogicalUnit`, `PhysicalUnit` and `PixelUnit` types and related functions. diff --git a/dpi/src/lib.rs b/dpi/src/lib.rs index aeb1038a88..0faf475f7f 100644 --- a/dpi/src/lib.rs +++ b/dpi/src/lib.rs @@ -35,8 +35,8 @@ //! //! ### Position and Size types //! -//! The [`PhysicalPosition`] / [`PhysicalSize`] types correspond with the actual pixels on the -//! device, and the [`LogicalPosition`] / [`LogicalSize`] types correspond to the physical pixels +//! The [`PhysicalPosition`] / [`PhysicalSize`] / [`PhysicalUnit`] types correspond with the actual pixels on the +//! device, and the [`LogicalPosition`] / [`LogicalSize`] / [`LogicalUnit`] types correspond to the physical pixels //! divided by the scale factor. //! //! The position and size types are generic over their exact pixel type, `P`, to allow the @@ -128,6 +128,238 @@ pub fn validate_scale_factor(scale_factor: f64) -> bool { scale_factor.is_sign_positive() && scale_factor.is_normal() } +/// A logical pixel unit. +#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Default, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +pub struct LogicalUnit

(pub P); + +impl

LogicalUnit

{ + /// Represents a minimum logical unit of [`f64::MAX`]. + pub const MIN: LogicalUnit = LogicalUnit::new(f64::MIN); + /// Represents a logical unit of `0_f64`. + pub const ZERO: LogicalUnit = LogicalUnit::new(0.0); + /// Represents a maximum logical unit that is equal to [`f64::MAX`]. + pub const MAX: LogicalUnit = LogicalUnit::new(f64::MAX); + + #[inline] + pub const fn new(v: P) -> Self { + LogicalUnit(v) + } +} + +impl LogicalUnit

{ + #[inline] + pub fn from_physical>, X: Pixel>( + physical: T, + scale_factor: f64, + ) -> Self { + physical.into().to_logical(scale_factor) + } + + #[inline] + pub fn to_physical(&self, scale_factor: f64) -> PhysicalUnit { + assert!(validate_scale_factor(scale_factor)); + PhysicalUnit::new(self.0.into() * scale_factor).cast() + } + + #[inline] + pub fn cast(&self) -> LogicalUnit { + LogicalUnit(self.0.cast()) + } +} + +impl From for LogicalUnit

{ + fn from(v: X) -> LogicalUnit

{ + LogicalUnit::new(v.cast()) + } +} + +impl From> for u8 { + fn from(v: LogicalUnit

) -> u8 { + v.0.cast() + } +} + +impl From> for u16 { + fn from(v: LogicalUnit

) -> u16 { + v.0.cast() + } +} + +impl From> for u32 { + fn from(v: LogicalUnit

) -> u32 { + v.0.cast() + } +} + +impl From> for i8 { + fn from(v: LogicalUnit

) -> i8 { + v.0.cast() + } +} + +impl From> for i16 { + fn from(v: LogicalUnit

) -> i16 { + v.0.cast() + } +} + +impl From> for i32 { + fn from(v: LogicalUnit

) -> i32 { + v.0.cast() + } +} + +impl From> for f32 { + fn from(v: LogicalUnit

) -> f32 { + v.0.cast() + } +} + +impl From> for f64 { + fn from(v: LogicalUnit

) -> f64 { + v.0.cast() + } +} + +/// A physical pixel unit. +#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Default, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +pub struct PhysicalUnit

(pub P); + +impl

PhysicalUnit

{ + /// Represents a minimum physical unit of [`f64::MAX`]. + pub const MIN: LogicalUnit = LogicalUnit::new(f64::MIN); + /// Represents a physical unit of `0_f64`. + pub const ZERO: LogicalUnit = LogicalUnit::new(0.0); + /// Represents a maximum physical unit that is equal to [`f64::MAX`]. + pub const MAX: LogicalUnit = LogicalUnit::new(f64::MAX); + + #[inline] + pub const fn new(v: P) -> Self { + PhysicalUnit(v) + } +} + +impl PhysicalUnit

{ + #[inline] + pub fn from_logical>, X: Pixel>(logical: T, scale_factor: f64) -> Self { + logical.into().to_physical(scale_factor) + } + + #[inline] + pub fn to_logical(&self, scale_factor: f64) -> LogicalUnit { + assert!(validate_scale_factor(scale_factor)); + LogicalUnit::new(self.0.into() * scale_factor).cast() + } + + #[inline] + pub fn cast(&self) -> PhysicalUnit { + PhysicalUnit(self.0.cast()) + } +} + +impl From for PhysicalUnit

{ + fn from(v: X) -> PhysicalUnit

{ + PhysicalUnit::new(v.cast()) + } +} + +impl From> for u8 { + fn from(v: PhysicalUnit

) -> u8 { + v.0.cast() + } +} + +impl From> for u16 { + fn from(v: PhysicalUnit

) -> u16 { + v.0.cast() + } +} + +impl From> for u32 { + fn from(v: PhysicalUnit

) -> u32 { + v.0.cast() + } +} + +impl From> for i8 { + fn from(v: PhysicalUnit

) -> i8 { + v.0.cast() + } +} + +impl From> for i16 { + fn from(v: PhysicalUnit

) -> i16 { + v.0.cast() + } +} + +impl From> for i32 { + fn from(v: PhysicalUnit

) -> i32 { + v.0.cast() + } +} + +impl From> for f32 { + fn from(v: PhysicalUnit

) -> f32 { + v.0.cast() + } +} + +impl From> for f64 { + fn from(v: PhysicalUnit

) -> f64 { + v.0.cast() + } +} + +/// A pixel unit that's either physical or logical. +pub enum PixelUnit { + Physical(PhysicalUnit), + Logical(LogicalUnit), +} + +impl PixelUnit { + /// Represents a minimum logical unit of [`f64::MAX`]. + pub const MIN: PixelUnit = PixelUnit::Logical(LogicalUnit::new(f64::MIN)); + /// Represents a logical unit of `0_f64`. + pub const ZERO: PixelUnit = PixelUnit::Logical(LogicalUnit::new(0.0)); + /// Represents a maximum logical unit that is equal to [`f64::MAX`]. + pub const MAX: PixelUnit = PixelUnit::Logical(LogicalUnit::new(f64::MAX)); + + pub fn new>(unit: S) -> PixelUnit { + unit.into() + } + + pub fn to_logical(&self, scale_factor: f64) -> LogicalUnit

{ + match *self { + PixelUnit::Physical(unit) => unit.to_logical(scale_factor), + PixelUnit::Logical(unit) => unit.cast(), + } + } + + pub fn to_physical(&self, scale_factor: f64) -> PhysicalUnit

{ + match *self { + PixelUnit::Physical(unit) => unit.cast(), + PixelUnit::Logical(unit) => unit.to_physical(scale_factor), + } + } +} + +impl From> for PixelUnit { + #[inline] + fn from(unit: PhysicalUnit

) -> PixelUnit { + PixelUnit::Physical(unit.cast()) + } +} + +impl From> for PixelUnit { + #[inline] + fn from(unit: LogicalUnit

) -> PixelUnit { + PixelUnit::Logical(unit.cast()) + } +} + /// A position represented in logical pixels. /// /// The position is stored as floats, so please be careful. Casting floats to integers truncates the @@ -730,6 +962,43 @@ mod tests { assert!(!validate_scale_factor(f64::NEG_INFINITY)); } + #[test] + fn test_logical_unity() { + let log_unit = LogicalUnit::new(1.0); + assert_eq!(log_unit.to_physical::(1.0), PhysicalUnit::new(1)); + assert_eq!(log_unit.to_physical::(2.0), PhysicalUnit::new(2)); + assert_eq!(log_unit.cast::(), LogicalUnit::new(1)); + assert_eq!( + log_unit, + LogicalUnit::from_physical(PhysicalUnit::new(1.0), 1.0) + ); + assert_eq!( + log_unit, + LogicalUnit::from_physical(PhysicalUnit::new(2.0), 2.0) + ); + assert_eq!(LogicalUnit::from(2.0), LogicalUnit::new(2.0)); + + let x: f64 = log_unit.into(); + assert_eq!(x, 1.0); + } + + #[test] + fn test_physical_unit() { + assert_eq!( + PhysicalUnit::from_logical(LogicalUnit::new(1.0), 1.0), + PhysicalUnit::new(1) + ); + assert_eq!( + PhysicalUnit::from_logical(LogicalUnit::new(2.0), 0.5), + PhysicalUnit::new(1) + ); + assert_eq!(PhysicalUnit::from(2.0), PhysicalUnit::new(2.0,)); + assert_eq!(PhysicalUnit::from(2.0), PhysicalUnit::new(2.0)); + + let x: f64 = PhysicalUnit::new(1).into(); + assert_eq!(x, 1.0); + } + #[test] fn test_logical_position() { let log_pos = LogicalPosition::new(1.0, 2.0);