1#![forbid(unsafe_code)]
2
3use super::Breakpoint;
38
39#[derive(Debug, Clone, PartialEq, Eq)]
45pub struct Responsive<T> {
46 values: [Option<T>; 5],
49}
50
51impl<T: Clone> Responsive<T> {
52 #[must_use]
56 pub fn new(base: T) -> Self {
57 Self {
58 values: [Some(base), None, None, None, None],
59 }
60 }
61
62 #[must_use]
64 pub fn at(mut self, bp: Breakpoint, value: T) -> Self {
65 self.values[bp as usize] = Some(value);
66 self
67 }
68
69 pub fn set(&mut self, bp: Breakpoint, value: T) {
71 self.values[bp as usize] = Some(value);
72 }
73
74 pub fn clear(&mut self, bp: Breakpoint) {
78 if bp != Breakpoint::Xs {
79 self.values[bp as usize] = None;
80 }
81 }
82
83 #[must_use]
88 pub fn resolve(&self, bp: Breakpoint) -> &T {
89 let idx = bp as usize;
90 for i in (0..=idx).rev() {
91 if let Some(ref v) = self.values[i] {
92 return v;
93 }
94 }
95 self.values[0].as_ref().expect("Xs always has a value")
97 }
98
99 #[must_use]
101 pub fn resolve_cloned(&self, bp: Breakpoint) -> T {
102 self.resolve(bp).clone()
103 }
104
105 #[must_use]
107 pub fn has_explicit(&self, bp: Breakpoint) -> bool {
108 self.values[bp as usize].is_some()
109 }
110
111 pub fn explicit_values(&self) -> impl Iterator<Item = (Breakpoint, &T)> {
113 Breakpoint::ALL
114 .iter()
115 .zip(self.values.iter())
116 .filter_map(|(&bp, v)| v.as_ref().map(|val| (bp, val)))
117 }
118
119 #[must_use]
121 pub fn map<U: Clone>(&self, f: impl Fn(&T) -> U) -> Responsive<U> {
122 Responsive {
123 values: [
124 self.values[0].as_ref().map(&f),
125 self.values[1].as_ref().map(&f),
126 self.values[2].as_ref().map(&f),
127 self.values[3].as_ref().map(&f),
128 self.values[4].as_ref().map(&f),
129 ],
130 }
131 }
132}
133
134impl<T: Clone + Default> Default for Responsive<T> {
135 fn default() -> Self {
136 Self::new(T::default())
137 }
138}
139
140impl<T: Clone + std::fmt::Display> std::fmt::Display for Responsive<T> {
141 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
142 write!(f, "Responsive(")?;
143 let mut first = true;
144 for (bp, val) in self.explicit_values() {
145 if !first {
146 write!(f, ", ")?;
147 }
148 write!(f, "{}={}", bp, val)?;
149 first = false;
150 }
151 write!(f, ")")
152 }
153}
154
155#[cfg(test)]
160mod tests {
161 use super::*;
162
163 #[test]
164 fn base_value_at_all_breakpoints() {
165 let r = Responsive::new(42);
166 for bp in Breakpoint::ALL {
167 assert_eq!(r.resolve(bp), &42);
168 }
169 }
170
171 #[test]
172 fn override_single_breakpoint() {
173 let r = Responsive::new(1).at(Breakpoint::Md, 2);
174
175 assert_eq!(r.resolve(Breakpoint::Xs), &1);
176 assert_eq!(r.resolve(Breakpoint::Sm), &1); assert_eq!(r.resolve(Breakpoint::Md), &2); assert_eq!(r.resolve(Breakpoint::Lg), &2); assert_eq!(r.resolve(Breakpoint::Xl), &2); }
181
182 #[test]
183 fn override_multiple_breakpoints() {
184 let r = Responsive::new(0)
185 .at(Breakpoint::Sm, 1)
186 .at(Breakpoint::Lg, 3);
187
188 assert_eq!(r.resolve(Breakpoint::Xs), &0);
189 assert_eq!(r.resolve(Breakpoint::Sm), &1);
190 assert_eq!(r.resolve(Breakpoint::Md), &1); assert_eq!(r.resolve(Breakpoint::Lg), &3);
192 assert_eq!(r.resolve(Breakpoint::Xl), &3); }
194
195 #[test]
196 fn set_mutating() {
197 let mut r = Responsive::new(0);
198 r.set(Breakpoint::Xl, 5);
199 assert_eq!(r.resolve(Breakpoint::Xl), &5);
200 }
201
202 #[test]
203 fn clear_reverts_to_inheritance() {
204 let mut r = Responsive::new(1).at(Breakpoint::Md, 2);
205 assert_eq!(r.resolve(Breakpoint::Md), &2);
206
207 r.clear(Breakpoint::Md);
208 assert_eq!(r.resolve(Breakpoint::Md), &1); }
210
211 #[test]
212 fn clear_xs_is_noop() {
213 let mut r = Responsive::new(42);
214 r.clear(Breakpoint::Xs);
215 assert_eq!(r.resolve(Breakpoint::Xs), &42);
216 }
217
218 #[test]
219 fn has_explicit() {
220 let r = Responsive::new(0).at(Breakpoint::Lg, 3);
221
222 assert!(r.has_explicit(Breakpoint::Xs));
223 assert!(!r.has_explicit(Breakpoint::Sm));
224 assert!(!r.has_explicit(Breakpoint::Md));
225 assert!(r.has_explicit(Breakpoint::Lg));
226 assert!(!r.has_explicit(Breakpoint::Xl));
227 }
228
229 #[test]
230 fn explicit_values_iterator() {
231 let r = Responsive::new(0)
232 .at(Breakpoint::Md, 2)
233 .at(Breakpoint::Xl, 4);
234
235 let explicit: Vec<_> = r.explicit_values().collect();
236 assert_eq!(explicit.len(), 3);
237 assert_eq!(explicit[0], (Breakpoint::Xs, &0));
238 assert_eq!(explicit[1], (Breakpoint::Md, &2));
239 assert_eq!(explicit[2], (Breakpoint::Xl, &4));
240 }
241
242 #[test]
243 fn map_values() {
244 let r = Responsive::new(10).at(Breakpoint::Lg, 20);
245 let doubled = r.map(|v| v * 2);
246
247 assert_eq!(doubled.resolve(Breakpoint::Xs), &20);
248 assert_eq!(doubled.resolve(Breakpoint::Lg), &40);
249 }
250
251 #[test]
252 fn resolve_cloned() {
253 let r = Responsive::new("hello".to_string());
254 let val: String = r.resolve_cloned(Breakpoint::Md);
255 assert_eq!(val, "hello");
256 }
257
258 #[test]
259 fn default() {
260 let r: Responsive<i32> = Responsive::default();
261 assert_eq!(r.resolve(Breakpoint::Xs), &0);
262 }
263
264 #[test]
265 fn clone_independence() {
266 let r1 = Responsive::new(1);
267 let mut r2 = r1.clone();
268 r2.set(Breakpoint::Md, 99);
269
270 assert_eq!(r1.resolve(Breakpoint::Md), &1);
271 assert_eq!(r2.resolve(Breakpoint::Md), &99);
272 }
273
274 #[test]
275 fn display_format() {
276 let r = Responsive::new(0).at(Breakpoint::Md, 2);
277 let s = format!("{}", r);
278 assert!(s.contains("xs=0"));
279 assert!(s.contains("md=2"));
280 }
281
282 #[test]
283 fn string_responsive() {
284 let r = Responsive::new("compact".to_string())
285 .at(Breakpoint::Md, "standard".to_string())
286 .at(Breakpoint::Xl, "expanded".to_string());
287
288 assert_eq!(r.resolve(Breakpoint::Xs), "compact");
289 assert_eq!(r.resolve(Breakpoint::Sm), "compact");
290 assert_eq!(r.resolve(Breakpoint::Md), "standard");
291 assert_eq!(r.resolve(Breakpoint::Lg), "standard");
292 assert_eq!(r.resolve(Breakpoint::Xl), "expanded");
293 }
294
295 #[test]
296 fn all_breakpoints_overridden() {
297 let r = Responsive::new(0)
298 .at(Breakpoint::Sm, 1)
299 .at(Breakpoint::Md, 2)
300 .at(Breakpoint::Lg, 3)
301 .at(Breakpoint::Xl, 4);
302
303 assert_eq!(r.resolve(Breakpoint::Xs), &0);
304 assert_eq!(r.resolve(Breakpoint::Sm), &1);
305 assert_eq!(r.resolve(Breakpoint::Md), &2);
306 assert_eq!(r.resolve(Breakpoint::Lg), &3);
307 assert_eq!(r.resolve(Breakpoint::Xl), &4);
308 }
309
310 #[test]
311 fn equality() {
312 let r1 = Responsive::new(1).at(Breakpoint::Md, 2);
313 let r2 = Responsive::new(1).at(Breakpoint::Md, 2);
314 assert_eq!(r1, r2);
315 }
316}