use std::fmt::{self, Display};
use topcoat_core::context::Cx;
use crate::{AttributeValueViewParts, PartsWriter};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum LengthUnit {
Px,
Cm,
Mm,
Q,
In,
Pc,
Pt,
Em,
Rem,
Ex,
Rex,
Cap,
Rcap,
Ch,
Rch,
Ic,
Ric,
Lh,
Rlh,
Vw,
Vh,
Vi,
Vb,
Vmin,
Vmax,
Svw,
Svh,
Svi,
Svb,
Svmin,
Svmax,
Lvw,
Lvh,
Lvi,
Lvb,
Lvmin,
Lvmax,
Dvw,
Dvh,
Dvi,
Dvb,
Dvmin,
Dvmax,
Cqw,
Cqh,
Cqi,
Cqb,
Cqmin,
Cqmax,
}
impl LengthUnit {
#[must_use]
pub const fn as_str(self) -> &'static str {
match self {
Self::Px => "px",
Self::Cm => "cm",
Self::Mm => "mm",
Self::Q => "Q",
Self::In => "in",
Self::Pc => "pc",
Self::Pt => "pt",
Self::Em => "em",
Self::Rem => "rem",
Self::Ex => "ex",
Self::Rex => "rex",
Self::Cap => "cap",
Self::Rcap => "rcap",
Self::Ch => "ch",
Self::Rch => "rch",
Self::Ic => "ic",
Self::Ric => "ric",
Self::Lh => "lh",
Self::Rlh => "rlh",
Self::Vw => "vw",
Self::Vh => "vh",
Self::Vi => "vi",
Self::Vb => "vb",
Self::Vmin => "vmin",
Self::Vmax => "vmax",
Self::Svw => "svw",
Self::Svh => "svh",
Self::Svi => "svi",
Self::Svb => "svb",
Self::Svmin => "svmin",
Self::Svmax => "svmax",
Self::Lvw => "lvw",
Self::Lvh => "lvh",
Self::Lvi => "lvi",
Self::Lvb => "lvb",
Self::Lvmin => "lvmin",
Self::Lvmax => "lvmax",
Self::Dvw => "dvw",
Self::Dvh => "dvh",
Self::Dvi => "dvi",
Self::Dvb => "dvb",
Self::Dvmin => "dvmin",
Self::Dvmax => "dvmax",
Self::Cqw => "cqw",
Self::Cqh => "cqh",
Self::Cqi => "cqi",
Self::Cqb => "cqb",
Self::Cqmin => "cqmin",
Self::Cqmax => "cqmax",
}
}
}
impl Display for LengthUnit {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_str())
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Length {
value: f32,
unit: LengthUnit,
}
impl Length {
#[must_use]
pub const fn new(value: f32, unit: LengthUnit) -> Self {
Self { value, unit }
}
#[must_use]
pub const fn value(self) -> f32 {
self.value
}
#[must_use]
pub const fn unit(self) -> LengthUnit {
self.unit
}
#[must_use]
pub const fn px(value: f32) -> Self {
Self::new(value, LengthUnit::Px)
}
#[must_use]
pub const fn cm(value: f32) -> Self {
Self::new(value, LengthUnit::Cm)
}
#[must_use]
pub const fn mm(value: f32) -> Self {
Self::new(value, LengthUnit::Mm)
}
#[must_use]
pub const fn q(value: f32) -> Self {
Self::new(value, LengthUnit::Q)
}
#[must_use]
pub const fn r#in(value: f32) -> Self {
Self::new(value, LengthUnit::In)
}
#[must_use]
pub const fn pc(value: f32) -> Self {
Self::new(value, LengthUnit::Pc)
}
#[must_use]
pub const fn pt(value: f32) -> Self {
Self::new(value, LengthUnit::Pt)
}
#[must_use]
pub const fn em(value: f32) -> Self {
Self::new(value, LengthUnit::Em)
}
#[must_use]
pub const fn rem(value: f32) -> Self {
Self::new(value, LengthUnit::Rem)
}
#[must_use]
pub const fn ex(value: f32) -> Self {
Self::new(value, LengthUnit::Ex)
}
#[must_use]
pub const fn rex(value: f32) -> Self {
Self::new(value, LengthUnit::Rex)
}
#[must_use]
pub const fn cap(value: f32) -> Self {
Self::new(value, LengthUnit::Cap)
}
#[must_use]
pub const fn rcap(value: f32) -> Self {
Self::new(value, LengthUnit::Rcap)
}
#[must_use]
pub const fn ch(value: f32) -> Self {
Self::new(value, LengthUnit::Ch)
}
#[must_use]
pub const fn rch(value: f32) -> Self {
Self::new(value, LengthUnit::Rch)
}
#[must_use]
pub const fn ic(value: f32) -> Self {
Self::new(value, LengthUnit::Ic)
}
#[must_use]
pub const fn ric(value: f32) -> Self {
Self::new(value, LengthUnit::Ric)
}
#[must_use]
pub const fn lh(value: f32) -> Self {
Self::new(value, LengthUnit::Lh)
}
#[must_use]
pub const fn rlh(value: f32) -> Self {
Self::new(value, LengthUnit::Rlh)
}
#[must_use]
pub const fn vw(value: f32) -> Self {
Self::new(value, LengthUnit::Vw)
}
#[must_use]
pub const fn vh(value: f32) -> Self {
Self::new(value, LengthUnit::Vh)
}
#[must_use]
pub const fn vi(value: f32) -> Self {
Self::new(value, LengthUnit::Vi)
}
#[must_use]
pub const fn vb(value: f32) -> Self {
Self::new(value, LengthUnit::Vb)
}
#[must_use]
pub const fn vmin(value: f32) -> Self {
Self::new(value, LengthUnit::Vmin)
}
#[must_use]
pub const fn vmax(value: f32) -> Self {
Self::new(value, LengthUnit::Vmax)
}
#[must_use]
pub const fn svw(value: f32) -> Self {
Self::new(value, LengthUnit::Svw)
}
#[must_use]
pub const fn svh(value: f32) -> Self {
Self::new(value, LengthUnit::Svh)
}
#[must_use]
pub const fn svi(value: f32) -> Self {
Self::new(value, LengthUnit::Svi)
}
#[must_use]
pub const fn svb(value: f32) -> Self {
Self::new(value, LengthUnit::Svb)
}
#[must_use]
pub const fn svmin(value: f32) -> Self {
Self::new(value, LengthUnit::Svmin)
}
#[must_use]
pub const fn svmax(value: f32) -> Self {
Self::new(value, LengthUnit::Svmax)
}
#[must_use]
pub const fn lvw(value: f32) -> Self {
Self::new(value, LengthUnit::Lvw)
}
#[must_use]
pub const fn lvh(value: f32) -> Self {
Self::new(value, LengthUnit::Lvh)
}
#[must_use]
pub const fn lvi(value: f32) -> Self {
Self::new(value, LengthUnit::Lvi)
}
#[must_use]
pub const fn lvb(value: f32) -> Self {
Self::new(value, LengthUnit::Lvb)
}
#[must_use]
pub const fn lvmin(value: f32) -> Self {
Self::new(value, LengthUnit::Lvmin)
}
#[must_use]
pub const fn lvmax(value: f32) -> Self {
Self::new(value, LengthUnit::Lvmax)
}
#[must_use]
pub const fn dvw(value: f32) -> Self {
Self::new(value, LengthUnit::Dvw)
}
#[must_use]
pub const fn dvh(value: f32) -> Self {
Self::new(value, LengthUnit::Dvh)
}
#[must_use]
pub const fn dvi(value: f32) -> Self {
Self::new(value, LengthUnit::Dvi)
}
#[must_use]
pub const fn dvb(value: f32) -> Self {
Self::new(value, LengthUnit::Dvb)
}
#[must_use]
pub const fn dvmin(value: f32) -> Self {
Self::new(value, LengthUnit::Dvmin)
}
#[must_use]
pub const fn dvmax(value: f32) -> Self {
Self::new(value, LengthUnit::Dvmax)
}
#[must_use]
pub const fn cqw(value: f32) -> Self {
Self::new(value, LengthUnit::Cqw)
}
#[must_use]
pub const fn cqh(value: f32) -> Self {
Self::new(value, LengthUnit::Cqh)
}
#[must_use]
pub const fn cqi(value: f32) -> Self {
Self::new(value, LengthUnit::Cqi)
}
#[must_use]
pub const fn cqb(value: f32) -> Self {
Self::new(value, LengthUnit::Cqb)
}
#[must_use]
pub const fn cqmin(value: f32) -> Self {
Self::new(value, LengthUnit::Cqmin)
}
#[must_use]
pub const fn cqmax(value: f32) -> Self {
Self::new(value, LengthUnit::Cqmax)
}
}
impl Display for Length {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.value)?;
f.write_str(self.unit.as_str())?;
Ok(())
}
}
impl From<u16> for Length {
fn from(value: u16) -> Self {
Self::px(f32::from(value))
}
}
impl From<f32> for Length {
fn from(value: f32) -> Self {
Self::px(value)
}
}
impl AttributeValueViewParts for Length {
fn attribute_present(&self) -> bool {
true
}
fn into_view_parts(self, _cx: &Cx, parts: &mut PartsWriter<'_>) {
parts.push_f32(self.value);
parts.push_static_str_unescaped(self.unit.as_str());
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::internal::Builder;
const UNITS: &[(Length, &str)] = &[
(Length::px(2.0), "2px"),
(Length::cm(2.0), "2cm"),
(Length::mm(2.0), "2mm"),
(Length::q(2.0), "2Q"),
(Length::r#in(2.0), "2in"),
(Length::pc(2.0), "2pc"),
(Length::pt(2.0), "2pt"),
(Length::em(2.0), "2em"),
(Length::rem(2.0), "2rem"),
(Length::ex(2.0), "2ex"),
(Length::rex(2.0), "2rex"),
(Length::cap(2.0), "2cap"),
(Length::rcap(2.0), "2rcap"),
(Length::ch(2.0), "2ch"),
(Length::rch(2.0), "2rch"),
(Length::ic(2.0), "2ic"),
(Length::ric(2.0), "2ric"),
(Length::lh(2.0), "2lh"),
(Length::rlh(2.0), "2rlh"),
(Length::vw(2.0), "2vw"),
(Length::vh(2.0), "2vh"),
(Length::vi(2.0), "2vi"),
(Length::vb(2.0), "2vb"),
(Length::vmin(2.0), "2vmin"),
(Length::vmax(2.0), "2vmax"),
(Length::svw(2.0), "2svw"),
(Length::svh(2.0), "2svh"),
(Length::svi(2.0), "2svi"),
(Length::svb(2.0), "2svb"),
(Length::svmin(2.0), "2svmin"),
(Length::svmax(2.0), "2svmax"),
(Length::lvw(2.0), "2lvw"),
(Length::lvh(2.0), "2lvh"),
(Length::lvi(2.0), "2lvi"),
(Length::lvb(2.0), "2lvb"),
(Length::lvmin(2.0), "2lvmin"),
(Length::lvmax(2.0), "2lvmax"),
(Length::dvw(2.0), "2dvw"),
(Length::dvh(2.0), "2dvh"),
(Length::dvi(2.0), "2dvi"),
(Length::dvb(2.0), "2dvb"),
(Length::dvmin(2.0), "2dvmin"),
(Length::dvmax(2.0), "2dvmax"),
(Length::cqw(2.0), "2cqw"),
(Length::cqh(2.0), "2cqh"),
(Length::cqi(2.0), "2cqi"),
(Length::cqb(2.0), "2cqb"),
(Length::cqmin(2.0), "2cqmin"),
(Length::cqmax(2.0), "2cqmax"),
];
fn render(value: impl AttributeValueViewParts) -> String {
let cx = Cx::default();
Builder::build(&cx, |b| b.attribute_value(value)).render(&cx)
}
#[test]
fn displays_with_unit() {
for &(length, expected) in UNITS {
assert_eq!(length.to_string(), expected, "Display for {length:?}");
}
}
#[test]
fn renders_view_parts_with_unit() {
for &(length, expected) in UNITS {
assert_eq!(render(length), expected, "view parts for {length:?}");
}
}
#[test]
fn unit_reports_its_suffix() {
assert_eq!(LengthUnit::Px.as_str(), "px");
assert_eq!(LengthUnit::Q.as_str(), "Q");
assert_eq!(LengthUnit::In.as_str(), "in");
assert_eq!(LengthUnit::Cqmax.as_str(), "cqmax");
assert_eq!(LengthUnit::Rem.to_string(), "rem");
}
#[test]
fn getters_expose_value_and_unit() {
let length = Length::rem(1.5);
assert_eq!(length.unit(), LengthUnit::Rem);
assert_eq!(Length::new(length.value(), length.unit()), length);
}
#[test]
fn formats_fractional_and_negative_values() {
assert_eq!(Length::px(1.5).to_string(), "1.5px");
assert_eq!(render(Length::px(1.5)), "1.5px");
assert_eq!(Length::em(-0.5).to_string(), "-0.5em");
assert_eq!(render(Length::em(-0.5)), "-0.5em");
assert_eq!(Length::rem(0.0).to_string(), "0rem");
assert_eq!(render(Length::rem(0.0)), "0rem");
}
#[test]
fn formats_whole_values_without_a_trailing_zero() {
assert_eq!(Length::px(16.0).to_string(), "16px");
assert_eq!(render(Length::px(16.0)), "16px");
}
#[test]
fn numbers_convert_to_pixels() {
assert_eq!(Length::from(24u16), Length::px(24.0));
assert_eq!(Length::from(1.5f32), Length::px(1.5));
}
#[test]
fn attribute_is_always_present() {
assert!(Length::px(0.0).attribute_present());
assert!(Length::cqmax(1.0).attribute_present());
}
}