ftui_layout/
visibility.rs1#![forbid(unsafe_code)]
2
3use super::Breakpoint;
41
42#[derive(Debug, Clone, Copy, PartialEq, Eq)]
52pub struct Visibility {
53 mask: u8,
55}
56
57impl Visibility {
62 pub const ALWAYS: Self = Self { mask: 0b11111 };
64
65 pub const NEVER: Self = Self { mask: 0 };
67}
68
69impl Visibility {
74 #[must_use]
78 pub const fn visible_above(bp: Breakpoint) -> Self {
79 let idx = bp as u8;
80 let mask = 0b11111u8 << idx;
82 Self {
83 mask: mask & 0b11111,
84 }
85 }
86
87 #[must_use]
91 pub const fn visible_below(bp: Breakpoint) -> Self {
92 let idx = bp as u8;
93 let mask = (1u8 << (idx + 1)) - 1;
95 Self { mask }
96 }
97
98 #[must_use]
100 pub const fn only(bp: Breakpoint) -> Self {
101 Self {
102 mask: 1u8 << (bp as u8),
103 }
104 }
105
106 #[must_use]
108 pub fn at(breakpoints: &[Breakpoint]) -> Self {
109 let mut mask = 0u8;
110 for &bp in breakpoints {
111 mask |= 1u8 << (bp as u8);
112 }
113 Self { mask }
114 }
115
116 #[must_use]
120 pub const fn hidden_below(bp: Breakpoint) -> Self {
121 Self::visible_above(bp)
122 }
123
124 #[must_use]
128 pub const fn hidden_above(bp: Breakpoint) -> Self {
129 let idx = bp as u8;
130 if idx == 0 {
132 return Self::NEVER;
133 }
134 let mask = (1u8 << idx) - 1;
135 Self { mask }
136 }
137
138 #[must_use]
140 pub const fn from_mask(mask: u8) -> Self {
141 Self {
142 mask: mask & 0b11111,
143 }
144 }
145}
146
147impl Visibility {
152 #[must_use]
154 pub const fn is_visible(self, bp: Breakpoint) -> bool {
155 self.mask & (1u8 << (bp as u8)) != 0
156 }
157
158 #[must_use]
160 pub const fn is_hidden(self, bp: Breakpoint) -> bool {
161 !self.is_visible(bp)
162 }
163
164 #[must_use]
166 pub const fn is_always(self) -> bool {
167 self.mask == 0b11111
168 }
169
170 #[must_use]
172 pub const fn is_never(self) -> bool {
173 self.mask == 0
174 }
175
176 #[must_use]
178 pub const fn mask(self) -> u8 {
179 self.mask
180 }
181
182 #[must_use]
184 pub const fn visible_count(self) -> u32 {
185 self.mask.count_ones()
186 }
187
188 pub fn visible_breakpoints(self) -> impl Iterator<Item = Breakpoint> {
190 Breakpoint::ALL
191 .into_iter()
192 .filter(move |&bp| self.is_visible(bp))
193 }
194}
195
196impl Visibility {
201 pub fn filter_rects<'a>(
209 visibilities: &'a [Visibility],
210 rects: &'a [super::Rect],
211 bp: Breakpoint,
212 ) -> Vec<(usize, super::Rect)> {
213 visibilities
214 .iter()
215 .zip(rects.iter())
216 .enumerate()
217 .filter(|(_, (vis, _))| vis.is_visible(bp))
218 .map(|(i, (_, rect))| (i, *rect))
219 .collect()
220 }
221
222 pub fn count_visible(visibilities: &[Visibility], bp: Breakpoint) -> usize {
224 visibilities.iter().filter(|v| v.is_visible(bp)).count()
225 }
226}
227
228impl Default for Visibility {
233 fn default() -> Self {
234 Self::ALWAYS
235 }
236}
237
238impl std::fmt::Display for Visibility {
239 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
240 if self.is_always() {
241 return f.write_str("always");
242 }
243 if self.is_never() {
244 return f.write_str("never");
245 }
246 let mut first = true;
247 for bp in Breakpoint::ALL {
248 if self.is_visible(bp) {
249 if !first {
250 f.write_str("+")?;
251 }
252 f.write_str(bp.label())?;
253 first = false;
254 }
255 }
256 Ok(())
257 }
258}
259
260#[cfg(test)]
265mod tests {
266 use super::*;
267 use crate::Rect;
268
269 #[test]
270 fn always_visible_at_all() {
271 for bp in Breakpoint::ALL {
272 assert!(Visibility::ALWAYS.is_visible(bp));
273 }
274 assert!(Visibility::ALWAYS.is_always());
275 assert!(!Visibility::ALWAYS.is_never());
276 }
277
278 #[test]
279 fn never_visible_at_none() {
280 for bp in Breakpoint::ALL {
281 assert!(Visibility::NEVER.is_hidden(bp));
282 }
283 assert!(Visibility::NEVER.is_never());
284 assert!(!Visibility::NEVER.is_always());
285 }
286
287 #[test]
288 fn visible_above() {
289 let vis = Visibility::visible_above(Breakpoint::Md);
290 assert!(!vis.is_visible(Breakpoint::Xs));
291 assert!(!vis.is_visible(Breakpoint::Sm));
292 assert!(vis.is_visible(Breakpoint::Md));
293 assert!(vis.is_visible(Breakpoint::Lg));
294 assert!(vis.is_visible(Breakpoint::Xl));
295 }
296
297 #[test]
298 fn visible_above_xs() {
299 let vis = Visibility::visible_above(Breakpoint::Xs);
300 assert!(vis.is_always());
301 }
302
303 #[test]
304 fn visible_above_xl() {
305 let vis = Visibility::visible_above(Breakpoint::Xl);
306 assert!(vis.is_visible(Breakpoint::Xl));
307 assert!(!vis.is_visible(Breakpoint::Lg));
308 assert_eq!(vis.visible_count(), 1);
309 }
310
311 #[test]
312 fn visible_below() {
313 let vis = Visibility::visible_below(Breakpoint::Md);
314 assert!(vis.is_visible(Breakpoint::Xs));
315 assert!(vis.is_visible(Breakpoint::Sm));
316 assert!(vis.is_visible(Breakpoint::Md));
317 assert!(!vis.is_visible(Breakpoint::Lg));
318 assert!(!vis.is_visible(Breakpoint::Xl));
319 }
320
321 #[test]
322 fn visible_below_xl() {
323 let vis = Visibility::visible_below(Breakpoint::Xl);
324 assert!(vis.is_always());
325 }
326
327 #[test]
328 fn visible_below_xs() {
329 let vis = Visibility::visible_below(Breakpoint::Xs);
330 assert!(vis.is_visible(Breakpoint::Xs));
331 assert!(!vis.is_visible(Breakpoint::Sm));
332 assert_eq!(vis.visible_count(), 1);
333 }
334
335 #[test]
336 fn only_single_breakpoint() {
337 let vis = Visibility::only(Breakpoint::Lg);
338 assert!(!vis.is_visible(Breakpoint::Xs));
339 assert!(!vis.is_visible(Breakpoint::Sm));
340 assert!(!vis.is_visible(Breakpoint::Md));
341 assert!(vis.is_visible(Breakpoint::Lg));
342 assert!(!vis.is_visible(Breakpoint::Xl));
343 assert_eq!(vis.visible_count(), 1);
344 }
345
346 #[test]
347 fn at_multiple() {
348 let vis = Visibility::at(&[Breakpoint::Xs, Breakpoint::Lg, Breakpoint::Xl]);
349 assert!(vis.is_visible(Breakpoint::Xs));
350 assert!(!vis.is_visible(Breakpoint::Sm));
351 assert!(!vis.is_visible(Breakpoint::Md));
352 assert!(vis.is_visible(Breakpoint::Lg));
353 assert!(vis.is_visible(Breakpoint::Xl));
354 assert_eq!(vis.visible_count(), 3);
355 }
356
357 #[test]
358 fn hidden_below() {
359 let vis = Visibility::hidden_below(Breakpoint::Md);
360 assert!(!vis.is_visible(Breakpoint::Xs));
362 assert!(!vis.is_visible(Breakpoint::Sm));
363 assert!(vis.is_visible(Breakpoint::Md));
364 }
365
366 #[test]
367 fn hidden_above() {
368 let vis = Visibility::hidden_above(Breakpoint::Md);
369 assert!(vis.is_visible(Breakpoint::Xs));
370 assert!(vis.is_visible(Breakpoint::Sm));
371 assert!(!vis.is_visible(Breakpoint::Md));
372 assert!(!vis.is_visible(Breakpoint::Lg));
373 }
374
375 #[test]
376 fn hidden_above_xs() {
377 let vis = Visibility::hidden_above(Breakpoint::Xs);
378 assert!(vis.is_never());
379 }
380
381 #[test]
382 fn from_mask() {
383 let vis = Visibility::from_mask(0b10101); assert!(vis.is_visible(Breakpoint::Xs));
385 assert!(!vis.is_visible(Breakpoint::Sm));
386 assert!(vis.is_visible(Breakpoint::Md));
387 assert!(!vis.is_visible(Breakpoint::Lg));
388 assert!(vis.is_visible(Breakpoint::Xl));
389 }
390
391 #[test]
392 fn from_mask_truncates() {
393 let vis = Visibility::from_mask(0xFF);
394 assert_eq!(vis.mask(), 0b11111);
395 }
396
397 #[test]
398 fn visible_breakpoints_iterator() {
399 let vis = Visibility::at(&[Breakpoint::Sm, Breakpoint::Lg]);
400 let bps: Vec<_> = vis.visible_breakpoints().collect();
401 assert_eq!(bps, vec![Breakpoint::Sm, Breakpoint::Lg]);
402 }
403
404 #[test]
405 fn filter_rects_basic() {
406 let rects = vec![
407 Rect::new(0, 0, 20, 10),
408 Rect::new(20, 0, 30, 10),
409 Rect::new(50, 0, 40, 10),
410 ];
411 let visibilities = vec![
412 Visibility::ALWAYS,
413 Visibility::hidden_below(Breakpoint::Md), Visibility::ALWAYS,
415 ];
416
417 let visible = Visibility::filter_rects(&visibilities, &rects, Breakpoint::Sm);
419 assert_eq!(visible.len(), 2);
420 assert_eq!(visible[0].0, 0); assert_eq!(visible[1].0, 2); let visible = Visibility::filter_rects(&visibilities, &rects, Breakpoint::Md);
425 assert_eq!(visible.len(), 3);
426 }
427
428 #[test]
429 fn count_visible_helper() {
430 let visibilities = vec![
431 Visibility::ALWAYS,
432 Visibility::only(Breakpoint::Xl),
433 Visibility::visible_above(Breakpoint::Lg),
434 ];
435
436 assert_eq!(Visibility::count_visible(&visibilities, Breakpoint::Xs), 1);
437 assert_eq!(Visibility::count_visible(&visibilities, Breakpoint::Lg), 2);
438 assert_eq!(Visibility::count_visible(&visibilities, Breakpoint::Xl), 3);
439 }
440
441 #[test]
442 fn default_is_always() {
443 assert_eq!(Visibility::default(), Visibility::ALWAYS);
444 }
445
446 #[test]
447 fn display_always() {
448 assert_eq!(format!("{}", Visibility::ALWAYS), "always");
449 }
450
451 #[test]
452 fn display_never() {
453 assert_eq!(format!("{}", Visibility::NEVER), "never");
454 }
455
456 #[test]
457 fn display_partial() {
458 let vis = Visibility::at(&[Breakpoint::Sm, Breakpoint::Lg]);
459 assert_eq!(format!("{}", vis), "sm+lg");
460 }
461
462 #[test]
463 fn equality() {
464 assert_eq!(
465 Visibility::visible_above(Breakpoint::Md),
466 Visibility::hidden_below(Breakpoint::Md)
467 );
468 }
469
470 #[test]
471 fn clone_independence() {
472 let a = Visibility::only(Breakpoint::Md);
473 let b = a;
474 assert_eq!(a, b);
475 }
476
477 #[test]
478 fn debug_format() {
479 let dbg = format!("{:?}", Visibility::ALWAYS);
480 assert!(dbg.contains("Visibility"));
481 }
482}