use-spacing 0.1.0

Spacing scale and box-edge primitives for RustUse UI
Documentation
#![forbid(unsafe_code)]
#![doc = include_str!("../README.md")]

/// A named or numeric spacing step.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Ord, PartialOrd)]
pub enum SpacingStep {
    None,
    Xs,
    Sm,
    Md,
    Lg,
    Xl,
    Xxl,
    Custom(u32),
}

impl SpacingStep {
    pub fn units(self) -> u32 {
        match self {
            Self::None => 0,
            Self::Xs => 1,
            Self::Sm => 2,
            Self::Md => 3,
            Self::Lg => 4,
            Self::Xl => 6,
            Self::Xxl => 8,
            Self::Custom(value) => value,
        }
    }
}

/// A spacing value stored as scale units.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Ord, PartialOrd)]
pub struct SpacingValue(u32);

impl SpacingValue {
    pub fn new(units: u32) -> Self {
        Self(units)
    }

    pub fn from_step(step: SpacingStep) -> Self {
        Self(step.units())
    }

    pub fn units(self) -> u32 {
        self.0
    }
}

/// A numeric spacing scale.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Ord, PartialOrd)]
pub struct SpacingScale {
    unit: u32,
}

impl SpacingScale {
    pub fn new(unit: u32) -> Self {
        Self { unit }
    }

    pub fn unit(self) -> u32 {
        self.unit
    }

    pub fn resolve(self, value: SpacingValue) -> u32 {
        self.unit.saturating_mul(value.units())
    }
}

/// Inner edge spacing.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct Inset {
    pub top: SpacingValue,
    pub right: SpacingValue,
    pub bottom: SpacingValue,
    pub left: SpacingValue,
}

impl Inset {
    pub fn all(value: SpacingValue) -> Self {
        Self {
            top: value,
            right: value,
            bottom: value,
            left: value,
        }
    }

    pub fn symmetric(vertical: SpacingValue, horizontal: SpacingValue) -> Self {
        Self {
            top: vertical,
            right: horizontal,
            bottom: vertical,
            left: horizontal,
        }
    }
}

/// Outer edge spacing.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct Outset {
    pub top: SpacingValue,
    pub right: SpacingValue,
    pub bottom: SpacingValue,
    pub left: SpacingValue,
}

impl Outset {
    pub fn all(value: SpacingValue) -> Self {
        Self {
            top: value,
            right: value,
            bottom: value,
            left: value,
        }
    }
}

/// Gap between repeated items.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct Gap(SpacingValue);

impl Gap {
    pub fn new(value: SpacingValue) -> Self {
        Self(value)
    }

    pub fn value(self) -> SpacingValue {
        self.0
    }
}

/// Padding metadata.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct Padding(Inset);

impl Padding {
    pub fn new(inset: Inset) -> Self {
        Self(inset)
    }

    pub fn inset(self) -> Inset {
        self.0
    }
}

/// Margin metadata.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct Margin(Outset);

impl Margin {
    pub fn new(outset: Outset) -> Self {
        Self(outset)
    }

    pub fn outset(self) -> Outset {
        self.0
    }
}

#[cfg(test)]
mod tests {
    use super::{Gap, Inset, Margin, Outset, Padding, SpacingScale, SpacingStep, SpacingValue};

    #[test]
    fn resolves_named_spacing_values() {
        let scale = SpacingScale::new(4);
        let medium = SpacingValue::from_step(SpacingStep::Md);

        assert_eq!(SpacingStep::None.units(), 0);
        assert_eq!(medium.units(), 3);
        assert_eq!(scale.resolve(medium), 12);
        assert_eq!(scale.resolve(SpacingValue::from_step(SpacingStep::Xxl)), 32);
    }

    #[test]
    fn creates_box_spacing_primitives() {
        let small = SpacingValue::from_step(SpacingStep::Sm);
        let large = SpacingValue::from_step(SpacingStep::Lg);
        let inset = Inset::symmetric(small, large);
        let padding = Padding::new(inset);
        let margin = Margin::new(Outset::all(small));
        let gap = Gap::new(large);

        assert_eq!(padding.inset().top, small);
        assert_eq!(padding.inset().right, large);
        assert_eq!(margin.outset().left, small);
        assert_eq!(gap.value(), large);
    }
}