1use std::fmt::{self, Display};
2
3use topcoat_core::context::Cx;
4
5use crate::{AttributeValueViewParts, PartsWriter};
6
7#[derive(Debug, Clone, Copy, PartialEq, Eq)]
9pub enum LengthUnit {
10 Px,
13 Cm,
15 Mm,
17 Q,
19 In,
21 Pc,
23 Pt,
25
26 Em,
29 Rem,
31 Ex,
33 Rex,
35 Cap,
37 Rcap,
39 Ch,
41 Rch,
43 Ic,
45 Ric,
47 Lh,
49 Rlh,
51
52 Vw,
55 Vh,
57 Vi,
59 Vb,
61 Vmin,
63 Vmax,
65
66 Svw,
69 Svh,
71 Svi,
73 Svb,
75 Svmin,
77 Svmax,
79
80 Lvw,
83 Lvh,
85 Lvi,
87 Lvb,
89 Lvmin,
91 Lvmax,
93
94 Dvw,
97 Dvh,
99 Dvi,
101 Dvb,
103 Dvmin,
105 Dvmax,
107
108 Cqw,
111 Cqh,
113 Cqi,
115 Cqb,
117 Cqmin,
119 Cqmax,
121}
122
123impl LengthUnit {
124 #[must_use]
126 pub const fn as_str(self) -> &'static str {
127 match self {
128 Self::Px => "px",
129 Self::Cm => "cm",
130 Self::Mm => "mm",
131 Self::Q => "Q",
132 Self::In => "in",
133 Self::Pc => "pc",
134 Self::Pt => "pt",
135 Self::Em => "em",
136 Self::Rem => "rem",
137 Self::Ex => "ex",
138 Self::Rex => "rex",
139 Self::Cap => "cap",
140 Self::Rcap => "rcap",
141 Self::Ch => "ch",
142 Self::Rch => "rch",
143 Self::Ic => "ic",
144 Self::Ric => "ric",
145 Self::Lh => "lh",
146 Self::Rlh => "rlh",
147 Self::Vw => "vw",
148 Self::Vh => "vh",
149 Self::Vi => "vi",
150 Self::Vb => "vb",
151 Self::Vmin => "vmin",
152 Self::Vmax => "vmax",
153 Self::Svw => "svw",
154 Self::Svh => "svh",
155 Self::Svi => "svi",
156 Self::Svb => "svb",
157 Self::Svmin => "svmin",
158 Self::Svmax => "svmax",
159 Self::Lvw => "lvw",
160 Self::Lvh => "lvh",
161 Self::Lvi => "lvi",
162 Self::Lvb => "lvb",
163 Self::Lvmin => "lvmin",
164 Self::Lvmax => "lvmax",
165 Self::Dvw => "dvw",
166 Self::Dvh => "dvh",
167 Self::Dvi => "dvi",
168 Self::Dvb => "dvb",
169 Self::Dvmin => "dvmin",
170 Self::Dvmax => "dvmax",
171 Self::Cqw => "cqw",
172 Self::Cqh => "cqh",
173 Self::Cqi => "cqi",
174 Self::Cqb => "cqb",
175 Self::Cqmin => "cqmin",
176 Self::Cqmax => "cqmax",
177 }
178 }
179}
180
181impl Display for LengthUnit {
182 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
183 f.write_str(self.as_str())
184 }
185}
186
187#[derive(Debug, Clone, Copy, PartialEq)]
194pub struct Length {
195 value: f32,
196 unit: LengthUnit,
197}
198
199impl Length {
200 #[must_use]
202 pub const fn new(value: f32, unit: LengthUnit) -> Self {
203 Self { value, unit }
204 }
205
206 #[must_use]
208 pub const fn value(self) -> f32 {
209 self.value
210 }
211
212 #[must_use]
214 pub const fn unit(self) -> LengthUnit {
215 self.unit
216 }
217
218 #[must_use]
220 pub const fn px(value: f32) -> Self {
221 Self::new(value, LengthUnit::Px)
222 }
223
224 #[must_use]
226 pub const fn cm(value: f32) -> Self {
227 Self::new(value, LengthUnit::Cm)
228 }
229
230 #[must_use]
232 pub const fn mm(value: f32) -> Self {
233 Self::new(value, LengthUnit::Mm)
234 }
235
236 #[must_use]
238 pub const fn q(value: f32) -> Self {
239 Self::new(value, LengthUnit::Q)
240 }
241
242 #[must_use]
244 pub const fn r#in(value: f32) -> Self {
245 Self::new(value, LengthUnit::In)
246 }
247
248 #[must_use]
250 pub const fn pc(value: f32) -> Self {
251 Self::new(value, LengthUnit::Pc)
252 }
253
254 #[must_use]
256 pub const fn pt(value: f32) -> Self {
257 Self::new(value, LengthUnit::Pt)
258 }
259
260 #[must_use]
262 pub const fn em(value: f32) -> Self {
263 Self::new(value, LengthUnit::Em)
264 }
265
266 #[must_use]
268 pub const fn rem(value: f32) -> Self {
269 Self::new(value, LengthUnit::Rem)
270 }
271
272 #[must_use]
274 pub const fn ex(value: f32) -> Self {
275 Self::new(value, LengthUnit::Ex)
276 }
277
278 #[must_use]
280 pub const fn rex(value: f32) -> Self {
281 Self::new(value, LengthUnit::Rex)
282 }
283
284 #[must_use]
286 pub const fn cap(value: f32) -> Self {
287 Self::new(value, LengthUnit::Cap)
288 }
289
290 #[must_use]
292 pub const fn rcap(value: f32) -> Self {
293 Self::new(value, LengthUnit::Rcap)
294 }
295
296 #[must_use]
298 pub const fn ch(value: f32) -> Self {
299 Self::new(value, LengthUnit::Ch)
300 }
301
302 #[must_use]
304 pub const fn rch(value: f32) -> Self {
305 Self::new(value, LengthUnit::Rch)
306 }
307
308 #[must_use]
310 pub const fn ic(value: f32) -> Self {
311 Self::new(value, LengthUnit::Ic)
312 }
313
314 #[must_use]
316 pub const fn ric(value: f32) -> Self {
317 Self::new(value, LengthUnit::Ric)
318 }
319
320 #[must_use]
322 pub const fn lh(value: f32) -> Self {
323 Self::new(value, LengthUnit::Lh)
324 }
325
326 #[must_use]
328 pub const fn rlh(value: f32) -> Self {
329 Self::new(value, LengthUnit::Rlh)
330 }
331
332 #[must_use]
334 pub const fn vw(value: f32) -> Self {
335 Self::new(value, LengthUnit::Vw)
336 }
337
338 #[must_use]
340 pub const fn vh(value: f32) -> Self {
341 Self::new(value, LengthUnit::Vh)
342 }
343
344 #[must_use]
346 pub const fn vi(value: f32) -> Self {
347 Self::new(value, LengthUnit::Vi)
348 }
349
350 #[must_use]
352 pub const fn vb(value: f32) -> Self {
353 Self::new(value, LengthUnit::Vb)
354 }
355
356 #[must_use]
358 pub const fn vmin(value: f32) -> Self {
359 Self::new(value, LengthUnit::Vmin)
360 }
361
362 #[must_use]
364 pub const fn vmax(value: f32) -> Self {
365 Self::new(value, LengthUnit::Vmax)
366 }
367
368 #[must_use]
370 pub const fn svw(value: f32) -> Self {
371 Self::new(value, LengthUnit::Svw)
372 }
373
374 #[must_use]
376 pub const fn svh(value: f32) -> Self {
377 Self::new(value, LengthUnit::Svh)
378 }
379
380 #[must_use]
382 pub const fn svi(value: f32) -> Self {
383 Self::new(value, LengthUnit::Svi)
384 }
385
386 #[must_use]
388 pub const fn svb(value: f32) -> Self {
389 Self::new(value, LengthUnit::Svb)
390 }
391
392 #[must_use]
394 pub const fn svmin(value: f32) -> Self {
395 Self::new(value, LengthUnit::Svmin)
396 }
397
398 #[must_use]
400 pub const fn svmax(value: f32) -> Self {
401 Self::new(value, LengthUnit::Svmax)
402 }
403
404 #[must_use]
406 pub const fn lvw(value: f32) -> Self {
407 Self::new(value, LengthUnit::Lvw)
408 }
409
410 #[must_use]
412 pub const fn lvh(value: f32) -> Self {
413 Self::new(value, LengthUnit::Lvh)
414 }
415
416 #[must_use]
418 pub const fn lvi(value: f32) -> Self {
419 Self::new(value, LengthUnit::Lvi)
420 }
421
422 #[must_use]
424 pub const fn lvb(value: f32) -> Self {
425 Self::new(value, LengthUnit::Lvb)
426 }
427
428 #[must_use]
430 pub const fn lvmin(value: f32) -> Self {
431 Self::new(value, LengthUnit::Lvmin)
432 }
433
434 #[must_use]
436 pub const fn lvmax(value: f32) -> Self {
437 Self::new(value, LengthUnit::Lvmax)
438 }
439
440 #[must_use]
442 pub const fn dvw(value: f32) -> Self {
443 Self::new(value, LengthUnit::Dvw)
444 }
445
446 #[must_use]
448 pub const fn dvh(value: f32) -> Self {
449 Self::new(value, LengthUnit::Dvh)
450 }
451
452 #[must_use]
454 pub const fn dvi(value: f32) -> Self {
455 Self::new(value, LengthUnit::Dvi)
456 }
457
458 #[must_use]
460 pub const fn dvb(value: f32) -> Self {
461 Self::new(value, LengthUnit::Dvb)
462 }
463
464 #[must_use]
466 pub const fn dvmin(value: f32) -> Self {
467 Self::new(value, LengthUnit::Dvmin)
468 }
469
470 #[must_use]
472 pub const fn dvmax(value: f32) -> Self {
473 Self::new(value, LengthUnit::Dvmax)
474 }
475
476 #[must_use]
478 pub const fn cqw(value: f32) -> Self {
479 Self::new(value, LengthUnit::Cqw)
480 }
481
482 #[must_use]
484 pub const fn cqh(value: f32) -> Self {
485 Self::new(value, LengthUnit::Cqh)
486 }
487
488 #[must_use]
490 pub const fn cqi(value: f32) -> Self {
491 Self::new(value, LengthUnit::Cqi)
492 }
493
494 #[must_use]
496 pub const fn cqb(value: f32) -> Self {
497 Self::new(value, LengthUnit::Cqb)
498 }
499
500 #[must_use]
502 pub const fn cqmin(value: f32) -> Self {
503 Self::new(value, LengthUnit::Cqmin)
504 }
505
506 #[must_use]
508 pub const fn cqmax(value: f32) -> Self {
509 Self::new(value, LengthUnit::Cqmax)
510 }
511}
512
513impl Display for Length {
514 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
515 write!(f, "{}", self.value)?;
516 f.write_str(self.unit.as_str())?;
517 Ok(())
518 }
519}
520
521impl From<u16> for Length {
522 fn from(value: u16) -> Self {
523 Self::px(f32::from(value))
524 }
525}
526
527impl From<f32> for Length {
528 fn from(value: f32) -> Self {
529 Self::px(value)
530 }
531}
532
533impl AttributeValueViewParts for Length {
534 fn attribute_present(&self) -> bool {
535 true
536 }
537
538 fn into_view_parts(self, _cx: &Cx, parts: &mut PartsWriter<'_>) {
539 parts.push_f32(self.value);
540 parts.push_static_str_unescaped(self.unit.as_str());
541 }
542}
543
544#[cfg(test)]
545mod tests {
546 use super::*;
547 use crate::internal::Builder;
548
549 const UNITS: &[(Length, &str)] = &[
552 (Length::px(2.0), "2px"),
553 (Length::cm(2.0), "2cm"),
554 (Length::mm(2.0), "2mm"),
555 (Length::q(2.0), "2Q"),
556 (Length::r#in(2.0), "2in"),
557 (Length::pc(2.0), "2pc"),
558 (Length::pt(2.0), "2pt"),
559 (Length::em(2.0), "2em"),
560 (Length::rem(2.0), "2rem"),
561 (Length::ex(2.0), "2ex"),
562 (Length::rex(2.0), "2rex"),
563 (Length::cap(2.0), "2cap"),
564 (Length::rcap(2.0), "2rcap"),
565 (Length::ch(2.0), "2ch"),
566 (Length::rch(2.0), "2rch"),
567 (Length::ic(2.0), "2ic"),
568 (Length::ric(2.0), "2ric"),
569 (Length::lh(2.0), "2lh"),
570 (Length::rlh(2.0), "2rlh"),
571 (Length::vw(2.0), "2vw"),
572 (Length::vh(2.0), "2vh"),
573 (Length::vi(2.0), "2vi"),
574 (Length::vb(2.0), "2vb"),
575 (Length::vmin(2.0), "2vmin"),
576 (Length::vmax(2.0), "2vmax"),
577 (Length::svw(2.0), "2svw"),
578 (Length::svh(2.0), "2svh"),
579 (Length::svi(2.0), "2svi"),
580 (Length::svb(2.0), "2svb"),
581 (Length::svmin(2.0), "2svmin"),
582 (Length::svmax(2.0), "2svmax"),
583 (Length::lvw(2.0), "2lvw"),
584 (Length::lvh(2.0), "2lvh"),
585 (Length::lvi(2.0), "2lvi"),
586 (Length::lvb(2.0), "2lvb"),
587 (Length::lvmin(2.0), "2lvmin"),
588 (Length::lvmax(2.0), "2lvmax"),
589 (Length::dvw(2.0), "2dvw"),
590 (Length::dvh(2.0), "2dvh"),
591 (Length::dvi(2.0), "2dvi"),
592 (Length::dvb(2.0), "2dvb"),
593 (Length::dvmin(2.0), "2dvmin"),
594 (Length::dvmax(2.0), "2dvmax"),
595 (Length::cqw(2.0), "2cqw"),
596 (Length::cqh(2.0), "2cqh"),
597 (Length::cqi(2.0), "2cqi"),
598 (Length::cqb(2.0), "2cqb"),
599 (Length::cqmin(2.0), "2cqmin"),
600 (Length::cqmax(2.0), "2cqmax"),
601 ];
602
603 fn render(value: impl AttributeValueViewParts) -> String {
604 let cx = Cx::default();
605 Builder::build(&cx, |b| b.attribute_value(value)).render(&cx)
606 }
607
608 #[test]
609 fn displays_with_unit() {
610 for &(length, expected) in UNITS {
611 assert_eq!(length.to_string(), expected, "Display for {length:?}");
612 }
613 }
614
615 #[test]
616 fn renders_view_parts_with_unit() {
617 for &(length, expected) in UNITS {
618 assert_eq!(render(length), expected, "view parts for {length:?}");
619 }
620 }
621
622 #[test]
623 fn unit_reports_its_suffix() {
624 assert_eq!(LengthUnit::Px.as_str(), "px");
625 assert_eq!(LengthUnit::Q.as_str(), "Q");
626 assert_eq!(LengthUnit::In.as_str(), "in");
627 assert_eq!(LengthUnit::Cqmax.as_str(), "cqmax");
628 assert_eq!(LengthUnit::Rem.to_string(), "rem");
629 }
630
631 #[test]
632 fn getters_expose_value_and_unit() {
633 let length = Length::rem(1.5);
634 assert_eq!(length.unit(), LengthUnit::Rem);
635 assert_eq!(Length::new(length.value(), length.unit()), length);
638 }
639
640 #[test]
641 fn formats_fractional_and_negative_values() {
642 assert_eq!(Length::px(1.5).to_string(), "1.5px");
643 assert_eq!(render(Length::px(1.5)), "1.5px");
644 assert_eq!(Length::em(-0.5).to_string(), "-0.5em");
645 assert_eq!(render(Length::em(-0.5)), "-0.5em");
646 assert_eq!(Length::rem(0.0).to_string(), "0rem");
647 assert_eq!(render(Length::rem(0.0)), "0rem");
648 }
649
650 #[test]
651 fn formats_whole_values_without_a_trailing_zero() {
652 assert_eq!(Length::px(16.0).to_string(), "16px");
653 assert_eq!(render(Length::px(16.0)), "16px");
654 }
655
656 #[test]
657 fn numbers_convert_to_pixels() {
658 assert_eq!(Length::from(24u16), Length::px(24.0));
659 assert_eq!(Length::from(1.5f32), Length::px(1.5));
660 }
661
662 #[test]
663 fn attribute_is_always_present() {
664 assert!(Length::px(0.0).attribute_present());
665 assert!(Length::cqmax(1.0).attribute_present());
666 }
667}