use crate::{
macros::ff32,
utils::FloatWriter,
};
use std::{
fmt::{
Display,
Formatter,
Write,
},
ops::{
Add,
Sub,
},
};
use strict_num::FiniteF32;
use taffy::prelude::{
TaffyAuto,
TaffyZero,
};
#[derive(Debug, Hash, Eq, PartialEq, Clone, Copy, Default)]
enum LengthInner {
#[default]
Zero,
Auto,
Absolute(FiniteF32),
Percent(FiniteF32),
}
impl LengthInner {
fn negate(self) -> Self {
match self {
LengthInner::Zero => self,
LengthInner::Auto => self,
LengthInner::Absolute(x) => Self::Absolute(ff32!(-x.get())),
LengthInner::Percent(x) => Self::Percent(ff32!(-x.get())),
}
}
}
#[derive(Debug, Hash, Eq, PartialEq, Clone, Copy, Default)]
pub struct Length<const AUTO: bool = true, const PERCENT: bool = true>(LengthInner);
impl<const PERCENT: bool> Length<true, PERCENT> {
#[must_use]
pub const fn auto() -> Self {
Self(LengthInner::Auto)
}
}
impl<const AUTO: bool> Length<AUTO, true> {
#[must_use]
pub fn percent(value: f32) -> Self {
Self(LengthInner::Percent(ff32!(value / 100.0)))
}
#[must_use]
pub fn percent_normalized(value: f32) -> Self {
Self(LengthInner::Percent(ff32!(value)))
}
}
impl<const AUTO: bool, const PERCENT: bool> Length<AUTO, PERCENT> {
#[must_use]
pub const fn zero() -> Self {
Self(LengthInner::Zero)
}
#[must_use]
pub fn units(value: f32) -> Self {
Self(LengthInner::Absolute(ff32!(value)))
}
pub(crate) fn is_zero(&self) -> bool {
match self.0 {
LengthInner::Zero => true,
LengthInner::Absolute(x) | LengthInner::Percent(x) => x.get() == 0.0,
_ => false,
}
}
pub(crate) fn resolve_abs(&self, full: f32) -> Option<f32> {
match self.0 {
LengthInner::Absolute(value) => Some(value.get()),
LengthInner::Percent(value) => Some(value.get() * full),
_ => None,
}
}
}
impl Add for Length<false, false> {
type Output = Self;
fn add(self, rhs: Self) -> Self::Output {
match (self.0, rhs.0) {
(LengthInner::Zero, b) => Self(b),
(a, LengthInner::Zero) => Self(a),
(LengthInner::Absolute(a), LengthInner::Absolute(b)) => Self::units(a.get() + b.get()),
_ => unreachable!(),
}
}
}
impl Sub for Length<false, false> {
type Output = Self;
fn sub(self, rhs: Self) -> Self::Output {
match (self.0, rhs.0) {
(LengthInner::Zero, b) => Self(b.negate()),
(a, LengthInner::Zero) => Self(a),
(LengthInner::Absolute(a), LengthInner::Absolute(b)) => Self::units(a.get() - b.get()),
_ => unreachable!(),
}
}
}
impl<const AUTO: bool, const PERCENT: bool> Display for Length<AUTO, PERCENT> {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self.0 {
LengthInner::Zero => f.write_char('0'),
LengthInner::Auto => f.write_str("auto"),
LengthInner::Absolute(value) => f.write_float(value.get()),
LengthInner::Percent(value) => {
f.write_float(value.get() * 100.0)?;
f.write_char('%')
}
}
}
}
impl<const PERCENT: bool> From<Length<false, PERCENT>> for taffy::LengthPercentage {
fn from(value: Length<false, PERCENT>) -> Self {
match value.0 {
LengthInner::Zero => taffy::LengthPercentage::ZERO,
LengthInner::Absolute(value) => taffy::LengthPercentage::length(value.get()),
LengthInner::Percent(value) => taffy::LengthPercentage::percent(value.get()),
LengthInner::Auto => unreachable!(),
}
}
}
impl<const AUTO: bool, const PERCENT: bool> From<Length<AUTO, PERCENT>>
for taffy::LengthPercentageAuto
{
fn from(value: Length<AUTO, PERCENT>) -> Self {
match value.0 {
LengthInner::Auto => taffy::LengthPercentageAuto::AUTO,
LengthInner::Zero => taffy::LengthPercentageAuto::ZERO,
LengthInner::Absolute(value) => taffy::LengthPercentageAuto::length(value.get()),
LengthInner::Percent(value) => taffy::LengthPercentageAuto::percent(value.get()),
}
}
}
impl<const AUTO: bool, const PERCENT: bool> From<Length<AUTO, PERCENT>> for taffy::Dimension {
fn from(value: Length<AUTO, PERCENT>) -> Self {
let length: taffy::LengthPercentageAuto = value.into();
taffy::Dimension::from(length)
}
}
pub trait IntoOptionalLength<const AUTO: bool = true, const PERCENT: bool = true> {
fn into_optional_length(self) -> Option<Length<AUTO, PERCENT>>;
}
impl<const AUTO: bool, const PERCENT: bool> IntoOptionalLength<AUTO, PERCENT>
for Option<Length<AUTO, PERCENT>>
{
fn into_optional_length(self) -> Option<Length<AUTO, PERCENT>> {
self
}
}
impl<const AUTO: bool, const PERCENT: bool, T> IntoOptionalLength<AUTO, PERCENT> for T
where
T: Into<Length<AUTO, PERCENT>> + Copy,
{
fn into_optional_length(self) -> Option<Length<AUTO, PERCENT>> {
Some(self.into())
}
}
macro_rules! impl_into_unit_length {
($($dtype:ty),*) => {
$(impl<const AUTO: bool, const PERCENT: bool> From<$dtype> for Length<AUTO, PERCENT> {
fn from(value: $dtype) -> Self {
Self::units(value as f32)
}
})*
};
}
impl_into_unit_length!(u8, u16, u32, u64, usize, i8, i16, i32, i64, isize, f32, f64);
pub(super) mod helpers {
use super::Length;
#[must_use]
pub const fn zero<const AUTO: bool, const PERCENT: bool>() -> Length<AUTO, PERCENT> {
Length::zero()
}
#[must_use]
pub const fn auto<const PERCENT: bool>() -> Length<true, PERCENT> {
Length::auto()
}
#[must_use]
pub fn units<T, const AUTO: bool, const PERCENT: bool>(value: T) -> Length<AUTO, PERCENT>
where
T: Into<f64>,
{
Length::units(value.into() as f32)
}
#[must_use]
pub fn pct<T, const AUTO: bool>(value: T) -> Length<AUTO, true>
where
T: Into<f64>,
{
Length::percent(value.into() as f32)
}
pub trait LengthExtension<const AUTO: bool, const PERCENT: bool>: Sized + Copy {
#[must_use]
fn units(self) -> Length<AUTO, PERCENT>;
#[must_use]
fn pct(self) -> Length<AUTO, true>;
}
macro_rules! impl_length_ext {
($($dtype:ty),*) => {
$(impl<const AUTO: bool, const PERCENT: bool> LengthExtension<AUTO, PERCENT> for $dtype {
fn units(self) -> Length<AUTO, PERCENT> {
Length::units(self as f32)
}
fn pct(self) -> Length<AUTO, true> {
Length::percent(self as f32)
}
})*
};
}
impl_length_ext!(u8, u16, u32, u64, usize, i8, i16, i32, i64, isize, f32, f64);
}
#[cfg(test)]
mod tests {
use super::{
helpers::*,
*,
};
type Len = Length<true, true>;
type LenNoAutoNoPct = Length<false, false>;
#[test]
fn units_constructor() {
let x = Len::units(5.5);
assert_eq!(x, Length(LengthInner::Absolute(ff32!(5.5))));
assert_eq!(x.resolve_abs(100.0), Some(5.5));
assert!(!x.is_zero());
}
#[test]
fn percent_constructor() {
let x = Len::percent(50.0);
assert_eq!(x, Length(LengthInner::Percent(ff32!(0.5))));
assert_eq!(x.resolve_abs(200.0), Some(100.0));
assert!(!x.is_zero());
}
#[test]
fn percent_normalized_constructor() {
assert_eq!(
Len::percent_normalized(0.25).resolve_abs(400.0),
Some(100.0)
);
}
#[test]
fn is_zero() {
assert!(Len::zero().is_zero());
assert!(Len::units(0.0).is_zero());
assert!(Len::percent(0.0).is_zero());
}
#[test]
fn resolves_absolute() {
assert_eq!(Len::units(50.0).resolve_abs(100.0), Some(50.0));
assert_eq!(Len::percent(25.0).resolve_abs(400.0), Some(100.0));
}
#[test]
fn adds_zero() {
let a = LenNoAutoNoPct::units(10.0);
let b = LenNoAutoNoPct::zero();
assert_eq!(a + b, a);
assert_eq!(b + a, a);
}
#[test]
fn adds_absolute() {
let a = LenNoAutoNoPct::units(10.0);
let b = LenNoAutoNoPct::units(5.0);
assert_eq!(a + b, LenNoAutoNoPct::units(15.0));
}
#[test]
fn subs_zero() {
let a = LenNoAutoNoPct::units(10.0);
let b = LenNoAutoNoPct::zero();
assert_eq!(a - b, a);
}
#[test]
fn subs_absolute() {
let a = LenNoAutoNoPct::units(6.0);
let b = LenNoAutoNoPct::units(4.0);
assert_eq!(a - b, LenNoAutoNoPct::units(2.0));
}
#[test]
fn sub_from_zero_negates() {
let a = LenNoAutoNoPct::zero();
let b = LenNoAutoNoPct::units(5.0);
assert_eq!(a - b, LenNoAutoNoPct::units(-5.0));
}
#[test]
fn renders_zero() {
assert_eq!(Len::zero().to_string(), "0");
}
#[test]
fn renders_auto() {
assert_eq!(Len::auto().to_string(), "auto");
}
#[test]
fn renders_units() {
assert_eq!(Len::units(7.5).to_string(), "7.5");
}
#[test]
fn renders_percent() {
assert_eq!(Len::percent(50.0).to_string(), "50%");
}
#[test]
fn units_from_integer() {
assert_eq!(Len::from(10_u32), Len::units(10.0));
}
#[test]
fn units_from_float() {
assert_eq!(Len::from(2.5_f32), Len::units(2.5));
}
#[test]
fn from_zero_helper() {
assert!(zero::<true, true>().is_zero());
}
#[test]
fn from_auto_helper() {
assert_eq!(auto::<true>(), Length::auto());
}
#[test]
fn from_units_helper() {
assert_eq!(units::<_, true, true>(5), Len::units(5.0));
}
#[test]
fn from_percent_helper() {
assert_eq!(pct::<_, true>(50), Len::percent(50.0));
}
}