Skip to main content

whisker_css/
ext.rs

1//! Numeric-literal extension traits and free constructors.
2//!
3//! These keep call sites concise:
4//!
5//! ```
6//! use whisker_css::ext::*;
7//!
8//! let _ = px(12);
9//! let _ = 12.px();
10//! let _ = 0.5.rem();
11//! let _ = 100.percent();
12//! ```
13
14use crate::data_type::{Angle, Length, Percentage, Time};
15
16/// Internal: anything that can be widened to `f32` for value
17/// construction.
18///
19/// `i32 → f32` is not in `std` as `From` because it can lose
20/// precision; for CSS lengths the precision loss is irrelevant, so
21/// the trait is provided locally.
22pub trait IntoF32: Copy {
23    /// Convert to `f32`.
24    fn into_f32(self) -> f32;
25}
26
27impl IntoF32 for f32 {
28    fn into_f32(self) -> f32 {
29        self
30    }
31}
32
33impl IntoF32 for i32 {
34    fn into_f32(self) -> f32 {
35        self as f32
36    }
37}
38
39impl IntoF32 for u32 {
40    fn into_f32(self) -> f32 {
41        self as f32
42    }
43}
44
45// ---------- Length ----------
46
47/// Construct a [`Length::Px`] (logical pixels).
48pub fn px(v: impl IntoF32) -> Length {
49    Length::Px(v.into_f32())
50}
51
52/// Construct a [`Length::Rpx`] (Lynx responsive pixel; 750rpx = device width).
53pub fn rpx(v: impl IntoF32) -> Length {
54    Length::Rpx(v.into_f32())
55}
56
57/// Construct a [`Length::Ppx`] (physical pixel).
58pub fn ppx(v: impl IntoF32) -> Length {
59    Length::Ppx(v.into_f32())
60}
61
62/// Construct a [`Length::Em`].
63pub fn em(v: impl IntoF32) -> Length {
64    Length::Em(v.into_f32())
65}
66
67/// Construct a [`Length::Rem`].
68pub fn rem(v: impl IntoF32) -> Length {
69    Length::Rem(v.into_f32())
70}
71
72/// Construct a [`Length::Vh`].
73pub fn vh(v: impl IntoF32) -> Length {
74    Length::Vh(v.into_f32())
75}
76
77/// Construct a [`Length::Vw`].
78pub fn vw(v: impl IntoF32) -> Length {
79    Length::Vw(v.into_f32())
80}
81
82/// The unit-less zero — the only length CSS allows without a unit.
83pub const ZERO: Length = Length::Zero;
84
85/// Method-style length constructors for primitive numbers.
86///
87/// Implemented for both `f32` and `i32`. `i32` widens silently to
88/// `f32`, so `8.px()` and `8.0.px()` are interchangeable.
89pub trait LengthExt: Copy {
90    /// `<self>px`.
91    fn px(self) -> Length;
92    /// `<self>rpx`.
93    fn rpx(self) -> Length;
94    /// `<self>ppx`.
95    fn ppx(self) -> Length;
96    /// `<self>em`.
97    fn em(self) -> Length;
98    /// `<self>rem`.
99    fn rem(self) -> Length;
100    /// `<self>vh`.
101    fn vh(self) -> Length;
102    /// `<self>vw`.
103    fn vw(self) -> Length;
104}
105
106impl<T: IntoF32> LengthExt for T {
107    fn px(self) -> Length {
108        Length::Px(self.into_f32())
109    }
110    fn rpx(self) -> Length {
111        Length::Rpx(self.into_f32())
112    }
113    fn ppx(self) -> Length {
114        Length::Ppx(self.into_f32())
115    }
116    fn em(self) -> Length {
117        Length::Em(self.into_f32())
118    }
119    fn rem(self) -> Length {
120        Length::Rem(self.into_f32())
121    }
122    fn vh(self) -> Length {
123        Length::Vh(self.into_f32())
124    }
125    fn vw(self) -> Length {
126        Length::Vw(self.into_f32())
127    }
128}
129
130// ---------- Percentage ----------
131
132/// Construct a [`Percentage`] from `n` (so `percent(50)` == `50%`).
133pub fn percent(v: impl IntoF32) -> Percentage {
134    Percentage(v.into_f32())
135}
136
137/// Method-style percentage constructor.
138pub trait PercentExt: Copy {
139    /// `<self>%`.
140    fn percent(self) -> Percentage;
141}
142
143impl<T: IntoF32> PercentExt for T {
144    fn percent(self) -> Percentage {
145        Percentage(self.into_f32())
146    }
147}
148
149// ---------- Angle ----------
150
151/// Construct an [`Angle::Deg`].
152pub fn deg(v: impl IntoF32) -> Angle {
153    Angle::Deg(v.into_f32())
154}
155
156/// Construct an [`Angle::Rad`].
157pub fn rad(v: impl IntoF32) -> Angle {
158    Angle::Rad(v.into_f32())
159}
160
161/// Construct an [`Angle::Turn`].
162pub fn turn(v: impl IntoF32) -> Angle {
163    Angle::Turn(v.into_f32())
164}
165
166/// Method-style angle constructors.
167pub trait AngleExt: Copy {
168    /// `<self>deg`.
169    fn deg(self) -> Angle;
170    /// `<self>rad`.
171    fn rad(self) -> Angle;
172    /// `<self>turn`.
173    fn turn(self) -> Angle;
174}
175
176impl<T: IntoF32> AngleExt for T {
177    fn deg(self) -> Angle {
178        Angle::Deg(self.into_f32())
179    }
180    fn rad(self) -> Angle {
181        Angle::Rad(self.into_f32())
182    }
183    fn turn(self) -> Angle {
184        Angle::Turn(self.into_f32())
185    }
186}
187
188// ---------- Time ----------
189
190/// Construct a [`Time::S`] (seconds).
191pub fn s(v: impl IntoF32) -> Time {
192    Time::S(v.into_f32())
193}
194
195/// Construct a [`Time::Ms`] (milliseconds).
196pub fn ms(v: impl IntoF32) -> Time {
197    Time::Ms(v.into_f32())
198}
199
200/// Method-style time constructors.
201pub trait TimeExt: Copy {
202    /// `<self>s`.
203    fn s(self) -> Time;
204    /// `<self>ms`.
205    fn ms(self) -> Time;
206}
207
208impl<T: IntoF32> TimeExt for T {
209    fn s(self) -> Time {
210        Time::S(self.into_f32())
211    }
212    fn ms(self) -> Time {
213        Time::Ms(self.into_f32())
214    }
215}
216
217#[cfg(test)]
218mod tests {
219    use super::*;
220    use crate::to_css::ToCss;
221
222    #[test]
223    fn length_free_fns() {
224        assert_eq!(px(8).to_css_string(), "8px");
225        assert_eq!(rpx(750).to_css_string(), "750rpx");
226        assert_eq!(ppx(2).to_css_string(), "2ppx");
227        assert_eq!(em(1.5).to_css_string(), "1.5em");
228        assert_eq!(rem(2).to_css_string(), "2rem");
229        assert_eq!(vh(50).to_css_string(), "50vh");
230        assert_eq!(vw(100).to_css_string(), "100vw");
231    }
232
233    #[test]
234    fn length_methods_on_i32() {
235        assert_eq!(8.px().to_css_string(), "8px");
236        assert_eq!(750.rpx().to_css_string(), "750rpx");
237        assert_eq!(2.ppx().to_css_string(), "2ppx");
238        assert_eq!(1.em().to_css_string(), "1em");
239        assert_eq!(2.rem().to_css_string(), "2rem");
240        assert_eq!(50.vh().to_css_string(), "50vh");
241        assert_eq!(100.vw().to_css_string(), "100vw");
242    }
243
244    #[test]
245    fn length_methods_on_f32() {
246        assert_eq!(0.5_f32.px().to_css_string(), "0.5px");
247        assert_eq!(1.5_f32.em().to_css_string(), "1.5em");
248        assert_eq!(0.5_f32.rem().to_css_string(), "0.5rem");
249        assert_eq!(0.5_f32.vh().to_css_string(), "0.5vh");
250        assert_eq!(0.5_f32.vw().to_css_string(), "0.5vw");
251        assert_eq!(0.5_f32.rpx().to_css_string(), "0.5rpx");
252        assert_eq!(0.5_f32.ppx().to_css_string(), "0.5ppx");
253    }
254
255    #[test]
256    fn percent_helpers() {
257        assert_eq!(percent(50).to_css_string(), "50%");
258        assert_eq!(50.percent().to_css_string(), "50%");
259        assert_eq!(33.3_f32.percent().to_css_string(), "33.3%");
260    }
261
262    #[test]
263    fn angle_helpers() {
264        assert_eq!(deg(90).to_css_string(), "90deg");
265        assert_eq!(rad(1).to_css_string(), "1rad");
266        assert_eq!(turn(1).to_css_string(), "1turn");
267        assert_eq!(45.deg().to_css_string(), "45deg");
268        assert_eq!(2.rad().to_css_string(), "2rad");
269        assert_eq!(0.5_f32.turn().to_css_string(), "0.5turn");
270        assert_eq!(0.5_f32.deg().to_css_string(), "0.5deg");
271        assert_eq!(0.5_f32.rad().to_css_string(), "0.5rad");
272    }
273
274    #[test]
275    fn time_helpers() {
276        assert_eq!(s(1).to_css_string(), "1s");
277        assert_eq!(ms(300).to_css_string(), "300ms");
278        assert_eq!(1.s().to_css_string(), "1s");
279        assert_eq!(300.ms().to_css_string(), "300ms");
280        assert_eq!(0.5_f32.s().to_css_string(), "0.5s");
281        assert_eq!(1.5_f32.ms().to_css_string(), "1.5ms");
282    }
283
284    #[test]
285    fn into_f32_for_u32() {
286        assert_eq!(IntoF32::into_f32(7_u32), 7.0);
287        assert_eq!(7_u32.px().to_css_string(), "7px");
288    }
289
290    #[test]
291    fn zero_constant() {
292        assert_eq!(ZERO.to_css_string(), "0");
293    }
294}