kozan_primitives/
units.rs1#[derive(Clone, Copy, Debug, PartialEq, Default)]
16pub enum Dimension {
17 Px(f32),
19 Percent(f32),
21 Em(f32),
23 Rem(f32),
25 Vw(f32),
27 Vh(f32),
29 Vmin(f32),
31 Vmax(f32),
33 #[default]
35 Auto,
36 MinContent,
38 MaxContent,
40 FitContent,
43}
44
45impl Dimension {
46 #[must_use]
47 pub fn is_auto(self) -> bool {
48 matches!(self, Dimension::Auto)
49 }
50
51 #[must_use]
54 pub fn is_definite(self) -> bool {
55 matches!(
56 self,
57 Dimension::Px(_)
58 | Dimension::Percent(_)
59 | Dimension::Em(_)
60 | Dimension::Rem(_)
61 | Dimension::Vw(_)
62 | Dimension::Vh(_)
63 | Dimension::Vmin(_)
64 | Dimension::Vmax(_)
65 )
66 }
67
68 #[must_use]
70 pub fn is_intrinsic(self) -> bool {
71 matches!(
72 self,
73 Dimension::Auto | Dimension::MinContent | Dimension::MaxContent | Dimension::FitContent
74 )
75 }
76
77 #[must_use]
83 pub fn resolve(self, parent: f32) -> Option<f32> {
84 match self {
85 Dimension::Px(v) => Some(v),
86 Dimension::Percent(pct) => Some(parent * pct / 100.0),
87 _ => None,
88 }
89 }
90
91 #[must_use]
93 pub fn resolve_full(self, ctx: &ResolveContext) -> Option<f32> {
94 match self {
95 Dimension::Px(v) => Some(v),
96 Dimension::Percent(pct) => Some(ctx.parent * pct / 100.0),
97 Dimension::Em(v) => Some(v * ctx.font_size),
98 Dimension::Rem(v) => Some(v * ctx.root_font_size),
99 Dimension::Vw(v) => Some(v * ctx.viewport_width / 100.0),
100 Dimension::Vh(v) => Some(v * ctx.viewport_height / 100.0),
101 Dimension::Vmin(v) => Some(v * ctx.viewport_width.min(ctx.viewport_height) / 100.0),
102 Dimension::Vmax(v) => Some(v * ctx.viewport_width.max(ctx.viewport_height) / 100.0),
103 Dimension::Auto
104 | Dimension::MinContent
105 | Dimension::MaxContent
106 | Dimension::FitContent => None,
107 }
108 }
109
110 #[must_use]
112 pub fn resolve_or(self, parent: f32, fallback: f32) -> f32 {
113 self.resolve(parent).unwrap_or(fallback)
114 }
115}
116
117#[derive(Clone, Copy, Debug)]
119pub struct ResolveContext {
120 pub parent: f32,
122 pub font_size: f32,
124 pub root_font_size: f32,
126 pub viewport_width: f32,
128 pub viewport_height: f32,
130}
131
132#[must_use]
134pub fn px(value: f32) -> Dimension {
135 Dimension::Px(value)
136}
137
138#[must_use]
140pub fn pct(value: f32) -> Dimension {
141 Dimension::Percent(value)
142}
143
144#[must_use]
146pub fn em(value: f32) -> Dimension {
147 Dimension::Em(value)
148}
149
150#[must_use]
152pub fn rem(value: f32) -> Dimension {
153 Dimension::Rem(value)
154}
155
156#[must_use]
158pub fn vw(value: f32) -> Dimension {
159 Dimension::Vw(value)
160}
161
162#[must_use]
164pub fn vh(value: f32) -> Dimension {
165 Dimension::Vh(value)
166}
167
168#[cfg(test)]
169mod tests {
170 use super::*;
171
172 #[test]
173 fn px_resolves_to_itself() {
174 assert_eq!(px(100.0).resolve(500.0), Some(100.0));
175 }
176
177 #[test]
178 fn percent_resolves_against_parent() {
179 assert_eq!(pct(50.0).resolve(200.0), Some(100.0));
180 }
181
182 #[test]
183 fn auto_resolves_to_none() {
184 assert_eq!(Dimension::Auto.resolve(500.0), None);
185 }
186
187 #[test]
188 fn resolve_or_with_fallback() {
189 assert_eq!(Dimension::Auto.resolve_or(500.0, 0.0), 0.0);
190 assert_eq!(px(42.0).resolve_or(500.0, 0.0), 42.0);
191 }
192
193 #[test]
194 fn default_is_auto() {
195 assert_eq!(Dimension::default(), Dimension::Auto);
196 }
197
198 #[test]
199 fn is_definite() {
200 assert!(px(10.0).is_definite());
201 assert!(pct(50.0).is_definite());
202 assert!(em(1.5).is_definite());
203 assert!(vw(100.0).is_definite());
204 assert!(!Dimension::Auto.is_definite());
205 assert!(!Dimension::MinContent.is_definite());
206 }
207
208 #[test]
209 fn is_intrinsic() {
210 assert!(Dimension::Auto.is_intrinsic());
211 assert!(Dimension::MinContent.is_intrinsic());
212 assert!(Dimension::MaxContent.is_intrinsic());
213 assert!(Dimension::FitContent.is_intrinsic());
214 assert!(!px(10.0).is_intrinsic());
215 }
216
217 #[test]
218 fn resolve_full_em_and_rem() {
219 let ctx = ResolveContext {
220 parent: 500.0,
221 font_size: 16.0,
222 root_font_size: 18.0,
223 viewport_width: 1920.0,
224 viewport_height: 1080.0,
225 };
226
227 assert_eq!(em(2.0).resolve_full(&ctx), Some(32.0));
228 assert_eq!(rem(2.0).resolve_full(&ctx), Some(36.0));
229 }
230
231 #[test]
232 fn resolve_full_viewport_units() {
233 let ctx = ResolveContext {
234 parent: 0.0,
235 font_size: 16.0,
236 root_font_size: 16.0,
237 viewport_width: 1920.0,
238 viewport_height: 1080.0,
239 };
240
241 assert_eq!(vw(50.0).resolve_full(&ctx), Some(960.0));
242 assert_eq!(vh(100.0).resolve_full(&ctx), Some(1080.0));
243 assert_eq!(Dimension::Vmin(50.0).resolve_full(&ctx), Some(540.0));
244 assert_eq!(Dimension::Vmax(50.0).resolve_full(&ctx), Some(960.0));
245 }
246
247 #[test]
248 fn intrinsic_values_dont_resolve() {
249 let ctx = ResolveContext {
250 parent: 500.0,
251 font_size: 16.0,
252 root_font_size: 16.0,
253 viewport_width: 1920.0,
254 viewport_height: 1080.0,
255 };
256
257 assert!(Dimension::Auto.resolve_full(&ctx).is_none());
258 assert!(Dimension::MinContent.resolve_full(&ctx).is_none());
259 }
260}