use std::fmt;
use std::ops::{Add, Div, Mul, Neg, Sub};
use crate::geometry::{Rect, Size, Vec2};
#[derive(Clone, Copy, Default, PartialEq)]
#[repr(transparent)]
pub struct Dp(pub f32);
impl Dp {
pub const ZERO: Dp = Dp(0.0);
pub const HAIRLINE: Dp = Dp(0.0);
pub const INFINITY: Dp = Dp(f32::INFINITY);
pub const UNSPECIFIED: Dp = Dp(f32::NAN);
#[inline]
pub const fn from_px_raw(v: f32) -> Dp {
Dp(v)
}
#[inline]
pub const fn value(self) -> f32 {
self.0
}
#[inline]
pub fn is_specified(self) -> bool {
!self.0.is_nan()
}
#[inline]
pub fn is_unspecified(self) -> bool {
self.0.is_nan()
}
#[inline]
pub fn take_or_else(self, block: impl FnOnce() -> Dp) -> Dp {
if self.is_specified() { self } else { block() }
}
#[inline]
pub fn is_finite(self) -> bool {
self.0.is_finite()
}
#[inline]
pub fn abs(self) -> Dp {
Dp(self.0.abs())
}
#[inline]
pub fn min(self, other: Dp) -> Dp {
Dp(self.0.min(other.0))
}
#[inline]
pub fn max(self, other: Dp) -> Dp {
Dp(self.0.max(other.0))
}
#[inline]
pub fn clamp(self, min: Dp, max: Dp) -> Dp {
Dp(self.0.clamp(min.0, max.0))
}
#[inline]
pub fn coerce_in(self, min: Dp, max: Dp) -> Dp {
Dp(self.0.clamp(min.0, max.0))
}
#[inline]
pub fn coerce_at_least(self, min: Dp) -> Dp {
Dp(self.0.max(min.0))
}
#[inline]
pub fn coerce_at_most(self, max: Dp) -> Dp {
Dp(self.0.min(max.0))
}
#[inline]
pub fn to_px(self) -> Px {
Px(self.0 * crate::locals::effective_density_scale())
}
#[inline]
pub fn to_px_with_scale(self, scale: f32) -> Px {
Px(self.0 * scale)
}
#[inline]
pub fn round_to_px(self) -> i32 {
self.to_px().0.round() as i32
}
#[inline]
pub fn to_sp(self) -> Sp {
let fs = crate::locals::text_scale().0.max(0.0001);
Sp(self.0 / fs)
}
#[inline]
pub fn to_bits(self) -> u32 {
hash_f32_bits(self.0)
}
}
impl fmt::Debug for Dp {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if self.is_unspecified() {
write!(f, "Dp.Unspecified")
} else {
write!(f, "{}dp", self.0)
}
}
}
impl fmt::Display for Dp {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if self.is_unspecified() {
write!(f, "Dp.Unspecified")
} else {
write!(f, "{}dp", self.0)
}
}
}
impl std::hash::Hash for Dp {
#[inline]
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
hash_f32_bits(self.0).hash(state);
}
}
#[inline]
fn hash_f32_bits(v: f32) -> u32 {
let mut bits = v.to_bits();
if bits == 0x8000_0000 {
bits = 0;
}
if v.is_nan() {
bits = 0x7FC0_0000;
}
bits
}
impl Add for Dp {
type Output = Dp;
#[inline]
fn add(self, other: Dp) -> Dp {
Dp(self.0 + other.0)
}
}
impl Sub for Dp {
type Output = Dp;
#[inline]
fn sub(self, other: Dp) -> Dp {
Dp(self.0 - other.0)
}
}
impl Neg for Dp {
type Output = Dp;
#[inline]
fn neg(self) -> Dp {
Dp(-self.0)
}
}
impl Mul<f32> for Dp {
type Output = Dp;
#[inline]
fn mul(self, other: f32) -> Dp {
Dp(self.0 * other)
}
}
impl Mul<Dp> for f32 {
type Output = Dp;
#[inline]
fn mul(self, other: Dp) -> Dp {
Dp(self * other.0)
}
}
impl Div<f32> for Dp {
type Output = Dp;
#[inline]
fn div(self, other: f32) -> Dp {
Dp(self.0 / other)
}
}
impl Div<Dp> for Dp {
type Output = f32;
#[inline]
fn div(self, other: Dp) -> f32 {
self.0 / other.0
}
}
impl PartialOrd for Dp {
#[inline]
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
if self.0.is_nan() || other.0.is_nan() {
Some(std::cmp::Ordering::Equal)
} else {
self.0.partial_cmp(&other.0)
}
}
}
#[inline]
pub fn lerp_dp(start: Dp, stop: Dp, fraction: f32) -> Dp {
Dp(start.0 + (stop.0 - start.0) * fraction)
}
#[inline]
pub fn min_dp(a: Dp, b: Dp) -> Dp {
Dp(a.0.min(b.0))
}
#[inline]
pub fn max_dp(a: Dp, b: Dp) -> Dp {
Dp(a.0.max(b.0))
}
#[derive(Clone, Copy, Default, PartialEq)]
#[repr(transparent)]
pub struct Px(pub f32);
impl Px {
pub const ZERO: Px = Px(0.0);
pub const INFINITY: Px = Px(f32::INFINITY);
pub const UNSPECIFIED: Px = Px(f32::NAN);
#[inline]
pub const fn value(self) -> f32 {
self.0
}
#[inline]
pub fn is_specified(self) -> bool {
!self.0.is_nan()
}
#[inline]
pub fn is_finite(self) -> bool {
self.0.is_finite()
}
#[inline]
pub fn abs(self) -> Px {
Px(self.0.abs())
}
#[inline]
pub fn min(self, other: Px) -> Px {
Px(self.0.min(other.0))
}
#[inline]
pub fn max(self, other: Px) -> Px {
Px(self.0.max(other.0))
}
#[inline]
pub fn to_dp(self) -> Dp {
let scale = crate::locals::effective_density_scale();
if scale <= 0.0001 {
Dp(0.0)
} else {
Dp(self.0 / scale)
}
}
#[inline]
pub fn to_dp_with_scale(self, scale: f32) -> Dp {
if scale <= 0.0001 {
Dp(0.0)
} else {
Dp(self.0 / scale)
}
}
#[inline]
pub fn to_sp(self) -> Sp {
self.to_dp().to_sp()
}
}
impl fmt::Debug for Px {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}px", self.0)
}
}
impl fmt::Display for Px {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}px", self.0)
}
}
impl std::hash::Hash for Px {
#[inline]
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
hash_f32_bits(self.0).hash(state);
}
}
impl Add for Px {
type Output = Px;
#[inline]
fn add(self, other: Px) -> Px {
Px(self.0 + other.0)
}
}
impl Sub for Px {
type Output = Px;
#[inline]
fn sub(self, other: Px) -> Px {
Px(self.0 - other.0)
}
}
impl Neg for Px {
type Output = Px;
#[inline]
fn neg(self) -> Px {
Px(-self.0)
}
}
impl Mul<f32> for Px {
type Output = Px;
#[inline]
fn mul(self, other: f32) -> Px {
Px(self.0 * other)
}
}
impl Mul<Px> for f32 {
type Output = Px;
#[inline]
fn mul(self, other: Px) -> Px {
Px(self * other.0)
}
}
impl Div<f32> for Px {
type Output = Px;
#[inline]
fn div(self, other: f32) -> Px {
Px(self.0 / other)
}
}
impl Div<Px> for Px {
type Output = f32;
#[inline]
fn div(self, other: Px) -> f32 {
self.0 / other.0
}
}
impl PartialOrd for Px {
#[inline]
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
self.0.partial_cmp(&other.0)
}
}
#[inline]
pub fn lerp_px(start: Px, stop: Px, fraction: f32) -> Px {
Px(start.0 + (stop.0 - start.0) * fraction)
}
#[derive(Clone, Copy, Default, PartialEq)]
#[repr(transparent)]
pub struct Sp(pub f32);
impl Sp {
pub const ZERO: Sp = Sp(0.0);
pub const UNSPECIFIED: Sp = Sp(f32::NAN);
#[inline]
pub const fn value(self) -> f32 {
self.0
}
#[inline]
pub fn is_specified(self) -> bool {
!self.0.is_nan()
}
#[inline]
pub fn abs(self) -> Sp {
Sp(self.0.abs())
}
#[inline]
pub fn min(self, other: Sp) -> Sp {
Sp(self.0.min(other.0))
}
#[inline]
pub fn max(self, other: Sp) -> Sp {
Sp(self.0.max(other.0))
}
#[inline]
pub fn to_px(self) -> Px {
Px(self.0
* crate::locals::effective_density_scale()
* crate::locals::text_scale().0.max(0.0))
}
#[inline]
pub fn to_dp(self) -> Dp {
Dp(self.0 * crate::locals::text_scale().0.max(0.0))
}
pub fn take_or_else(self, block: impl FnOnce() -> Sp) -> Sp {
if self.is_specified() { self } else { block() }
}
}
impl fmt::Debug for Sp {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if self.is_specified() {
write!(f, "{}sp", self.0)
} else {
write!(f, "Sp.Unspecified")
}
}
}
impl fmt::Display for Sp {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if self.is_specified() {
write!(f, "{}sp", self.0)
} else {
write!(f, "Sp.Unspecified")
}
}
}
impl std::hash::Hash for Sp {
#[inline]
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
hash_f32_bits(self.0).hash(state);
}
}
impl Add for Sp {
type Output = Sp;
#[inline]
fn add(self, other: Sp) -> Sp {
Sp(self.0 + other.0)
}
}
impl Sub for Sp {
type Output = Sp;
#[inline]
fn sub(self, other: Sp) -> Sp {
Sp(self.0 - other.0)
}
}
impl Neg for Sp {
type Output = Sp;
#[inline]
fn neg(self) -> Sp {
Sp(-self.0)
}
}
impl Mul<f32> for Sp {
type Output = Sp;
#[inline]
fn mul(self, other: f32) -> Sp {
Sp(self.0 * other)
}
}
impl Div<f32> for Sp {
type Output = Sp;
#[inline]
fn div(self, other: f32) -> Sp {
Sp(self.0 / other)
}
}
impl PartialOrd for Sp {
#[inline]
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
self.0.partial_cmp(&other.0)
}
}
#[inline]
pub fn lerp_sp(start: Sp, stop: Sp, fraction: f32) -> Sp {
Sp(start.0 + (stop.0 - start.0) * fraction)
}
pub trait UnitExt {
fn dp(self) -> Dp;
fn px(self) -> Px;
fn sp(self) -> Sp;
}
impl UnitExt for f32 {
#[inline]
fn dp(self) -> Dp {
Dp(self)
}
#[inline]
fn px(self) -> Px {
Px(self)
}
#[inline]
fn sp(self) -> Sp {
Sp(self)
}
}
impl UnitExt for f64 {
#[inline]
fn dp(self) -> Dp {
Dp(self as f32)
}
#[inline]
fn px(self) -> Px {
Px(self as f32)
}
#[inline]
fn sp(self) -> Sp {
Sp(self as f32)
}
}
impl UnitExt for i32 {
#[inline]
fn dp(self) -> Dp {
Dp(self as f32)
}
#[inline]
fn px(self) -> Px {
Px(self as f32)
}
#[inline]
fn sp(self) -> Sp {
Sp(self as f32)
}
}
impl UnitExt for u32 {
#[inline]
fn dp(self) -> Dp {
Dp(self as f32)
}
#[inline]
fn px(self) -> Px {
Px(self as f32)
}
#[inline]
fn sp(self) -> Sp {
Sp(self as f32)
}
}
impl From<f32> for Dp {
#[inline]
fn from(v: f32) -> Self {
Dp(v)
}
}
impl From<f64> for Dp {
#[inline]
fn from(v: f64) -> Self {
Dp(v as f32)
}
}
impl From<i32> for Dp {
#[inline]
fn from(v: i32) -> Self {
Dp(v as f32)
}
}
impl From<u32> for Dp {
#[inline]
fn from(v: u32) -> Self {
Dp(v as f32)
}
}
impl From<Dp> for f32 {
#[inline]
fn from(v: Dp) -> Self {
v.0
}
}
impl From<f32> for Sp {
#[inline]
fn from(v: f32) -> Self {
Sp(v)
}
}
impl From<f64> for Sp {
#[inline]
fn from(v: f64) -> Self {
Sp(v as f32)
}
}
impl From<i32> for Sp {
#[inline]
fn from(v: i32) -> Self {
Sp(v as f32)
}
}
impl From<u32> for Sp {
#[inline]
fn from(v: u32) -> Self {
Sp(v as f32)
}
}
impl From<Sp> for f32 {
#[inline]
fn from(v: Sp) -> Self {
v.0
}
}
impl From<f32> for Px {
#[inline]
fn from(v: f32) -> Self {
Px(v)
}
}
impl From<f64> for Px {
#[inline]
fn from(v: f64) -> Self {
Px(v as f32)
}
}
impl From<i32> for Px {
#[inline]
fn from(v: i32) -> Self {
Px(v as f32)
}
}
impl From<u32> for Px {
#[inline]
fn from(v: u32) -> Self {
Px(v as f32)
}
}
impl From<Px> for f32 {
#[inline]
fn from(v: Px) -> Self {
v.0
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq)]
pub struct DpOffset {
pub x: Dp,
pub y: Dp,
}
impl DpOffset {
pub const ZERO: DpOffset = DpOffset {
x: Dp::ZERO,
y: Dp::ZERO,
};
pub const UNSPECIFIED: DpOffset = DpOffset {
x: Dp::UNSPECIFIED,
y: Dp::UNSPECIFIED,
};
#[inline]
pub fn new(x: Dp, y: Dp) -> Self {
Self { x, y }
}
#[inline]
pub fn is_specified(self) -> bool {
self.x.is_specified() && self.y.is_specified()
}
#[inline]
pub fn to_px(self) -> Vec2 {
Vec2 {
x: self.x.to_px().0,
y: self.y.to_px().0,
}
}
}
impl Add for DpOffset {
type Output = DpOffset;
#[inline]
fn add(self, other: DpOffset) -> DpOffset {
DpOffset::new(self.x + other.x, self.y + other.y)
}
}
impl Sub for DpOffset {
type Output = DpOffset;
#[inline]
fn sub(self, other: DpOffset) -> DpOffset {
DpOffset::new(self.x - other.x, self.y - other.y)
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq)]
pub struct DpSize {
pub width: Dp,
pub height: Dp,
}
impl DpSize {
pub const ZERO: DpSize = DpSize {
width: Dp::ZERO,
height: Dp::ZERO,
};
#[inline]
pub fn new(width: Dp, height: Dp) -> Self {
Self { width, height }
}
#[inline]
pub fn to_px(self) -> Size {
Size {
width: self.width.to_px().0,
height: self.height.to_px().0,
}
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq)]
pub struct DpRect {
pub left: Dp,
pub top: Dp,
pub right: Dp,
pub bottom: Dp,
}
impl DpRect {
#[inline]
pub fn new(left: Dp, top: Dp, right: Dp, bottom: Dp) -> Self {
Self {
left,
top,
right,
bottom,
}
}
#[inline]
pub fn width(self) -> Dp {
self.right - self.left
}
#[inline]
pub fn height(self) -> Dp {
self.bottom - self.top
}
#[inline]
pub fn to_px(self) -> Rect {
Rect {
x: self.left.to_px().0,
y: self.top.to_px().0,
w: (self.right - self.left).to_px().0,
h: (self.bottom - self.top).to_px().0,
}
}
}
#[inline]
pub fn size_px_to_dp(size: Size) -> DpSize {
DpSize::new(Px(size.width).to_dp(), Px(size.height).to_dp())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn dp_arithmetic_matches_compose() {
assert_eq!(Dp(2.0) + Dp(3.0), Dp(5.0));
assert_eq!(Dp(5.0) - Dp(3.0), Dp(2.0));
assert_eq!(-Dp(2.0), Dp(-2.0));
assert_eq!(Dp(2.0) * 3.0, Dp(6.0));
assert_eq!(Dp(6.0) / 3.0, Dp(2.0));
assert_eq!(Dp(6.0) / Dp(3.0), 2.0);
}
#[test]
fn dp_unspecified_semantics() {
assert!(!Dp::UNSPECIFIED.is_specified());
assert!(Dp(1.0).is_specified());
assert_eq!(Dp::UNSPECIFIED.take_or_else(|| Dp(4.0)), Dp(4.0));
assert!(Dp::UNSPECIFIED != Dp::UNSPECIFIED);
assert_eq!(
Dp::UNSPECIFIED.partial_cmp(&Dp(1.0)),
Some(std::cmp::Ordering::Equal)
);
}
#[test]
fn unit_ext_constructors() {
assert_eq!(16.0f32.dp(), Dp(16.0));
assert_eq!(10i32.dp(), Dp(10.0));
assert_eq!(14.0f32.sp(), Sp(14.0));
assert_eq!(2.0f32.px(), Px(2.0));
}
#[test]
fn std_from_conversions() {
assert_eq!(Dp::from(16.0f32), Dp(16.0));
assert_eq!(Dp::from(10i32), Dp(10.0));
assert_eq!(Sp::from(14.0f32), Sp(14.0));
assert_eq!(Px::from(2.0f32), Px(2.0));
assert_eq!(f32::from(Dp(3.0)), 3.0);
let v: Dp = 8.0f32.into();
assert_eq!(v, Dp(8.0));
}
#[test]
fn dp_rect_dimensions() {
let r = DpRect::new(Dp(0.0), Dp(0.0), Dp(10.0), Dp(20.0));
assert_eq!(r.width(), Dp(10.0));
assert_eq!(r.height(), Dp(20.0));
}
#[test]
fn lerp_midpoint() {
assert_eq!(lerp_dp(Dp(0.0), Dp(10.0), 0.5), Dp(5.0));
assert_eq!(lerp_sp(Sp(0.0), Sp(10.0), 0.5), Sp(10.0 * 0.5));
}
}