#![forbid(unsafe_code)]
use super::Breakpoint;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Visibility {
mask: u8,
}
impl Visibility {
pub const ALWAYS: Self = Self { mask: 0b11111 };
pub const NEVER: Self = Self { mask: 0 };
}
impl Visibility {
#[must_use]
pub const fn visible_above(bp: Breakpoint) -> Self {
let idx = bp as u8;
let mask = 0b11111u8 << idx;
Self {
mask: mask & 0b11111,
}
}
#[must_use]
pub const fn visible_below(bp: Breakpoint) -> Self {
let idx = bp as u8;
let mask = (1u8 << (idx + 1)) - 1;
Self { mask }
}
#[must_use]
pub const fn only(bp: Breakpoint) -> Self {
Self {
mask: 1u8 << (bp as u8),
}
}
#[must_use]
pub fn at(breakpoints: &[Breakpoint]) -> Self {
let mut mask = 0u8;
for &bp in breakpoints {
mask |= 1u8 << (bp as u8);
}
Self { mask }
}
#[must_use]
pub const fn hidden_below(bp: Breakpoint) -> Self {
Self::visible_above(bp)
}
#[must_use]
pub const fn hidden_above(bp: Breakpoint) -> Self {
let idx = bp as u8;
if idx == 0 {
return Self::NEVER;
}
let mask = (1u8 << idx) - 1;
Self { mask }
}
#[must_use]
pub const fn from_mask(mask: u8) -> Self {
Self {
mask: mask & 0b11111,
}
}
}
impl Visibility {
#[must_use]
pub const fn is_visible(self, bp: Breakpoint) -> bool {
self.mask & (1u8 << (bp as u8)) != 0
}
#[must_use]
pub const fn is_hidden(self, bp: Breakpoint) -> bool {
!self.is_visible(bp)
}
#[must_use]
pub const fn is_always(self) -> bool {
self.mask == 0b11111
}
#[must_use]
pub const fn is_never(self) -> bool {
self.mask == 0
}
#[must_use]
pub const fn mask(self) -> u8 {
self.mask
}
#[must_use]
pub const fn visible_count(self) -> u32 {
self.mask.count_ones()
}
pub fn visible_breakpoints(self) -> impl Iterator<Item = Breakpoint> {
Breakpoint::ALL
.into_iter()
.filter(move |&bp| self.is_visible(bp))
}
}
impl Visibility {
pub fn filter_rects<'a>(
visibilities: &'a [Visibility],
rects: &'a [super::Rect],
bp: Breakpoint,
) -> Vec<(usize, super::Rect)> {
visibilities
.iter()
.zip(rects.iter())
.enumerate()
.filter(|(_, (vis, _))| vis.is_visible(bp))
.map(|(i, (_, rect))| (i, *rect))
.collect()
}
pub fn count_visible(visibilities: &[Visibility], bp: Breakpoint) -> usize {
visibilities.iter().filter(|v| v.is_visible(bp)).count()
}
}
impl Default for Visibility {
fn default() -> Self {
Self::ALWAYS
}
}
impl std::fmt::Display for Visibility {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
if self.is_always() {
return f.write_str("always");
}
if self.is_never() {
return f.write_str("never");
}
let mut first = true;
for bp in Breakpoint::ALL {
if self.is_visible(bp) {
if !first {
f.write_str("+")?;
}
f.write_str(bp.label())?;
first = false;
}
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::Rect;
#[test]
fn always_visible_at_all() {
for bp in Breakpoint::ALL {
assert!(Visibility::ALWAYS.is_visible(bp));
}
assert!(Visibility::ALWAYS.is_always());
assert!(!Visibility::ALWAYS.is_never());
}
#[test]
fn never_visible_at_none() {
for bp in Breakpoint::ALL {
assert!(Visibility::NEVER.is_hidden(bp));
}
assert!(Visibility::NEVER.is_never());
assert!(!Visibility::NEVER.is_always());
}
#[test]
fn visible_above() {
let vis = Visibility::visible_above(Breakpoint::Md);
assert!(!vis.is_visible(Breakpoint::Xs));
assert!(!vis.is_visible(Breakpoint::Sm));
assert!(vis.is_visible(Breakpoint::Md));
assert!(vis.is_visible(Breakpoint::Lg));
assert!(vis.is_visible(Breakpoint::Xl));
}
#[test]
fn visible_above_xs() {
let vis = Visibility::visible_above(Breakpoint::Xs);
assert!(vis.is_always());
}
#[test]
fn visible_above_xl() {
let vis = Visibility::visible_above(Breakpoint::Xl);
assert!(vis.is_visible(Breakpoint::Xl));
assert!(!vis.is_visible(Breakpoint::Lg));
assert_eq!(vis.visible_count(), 1);
}
#[test]
fn visible_below() {
let vis = Visibility::visible_below(Breakpoint::Md);
assert!(vis.is_visible(Breakpoint::Xs));
assert!(vis.is_visible(Breakpoint::Sm));
assert!(vis.is_visible(Breakpoint::Md));
assert!(!vis.is_visible(Breakpoint::Lg));
assert!(!vis.is_visible(Breakpoint::Xl));
}
#[test]
fn visible_below_xl() {
let vis = Visibility::visible_below(Breakpoint::Xl);
assert!(vis.is_always());
}
#[test]
fn visible_below_xs() {
let vis = Visibility::visible_below(Breakpoint::Xs);
assert!(vis.is_visible(Breakpoint::Xs));
assert!(!vis.is_visible(Breakpoint::Sm));
assert_eq!(vis.visible_count(), 1);
}
#[test]
fn only_single_breakpoint() {
let vis = Visibility::only(Breakpoint::Lg);
assert!(!vis.is_visible(Breakpoint::Xs));
assert!(!vis.is_visible(Breakpoint::Sm));
assert!(!vis.is_visible(Breakpoint::Md));
assert!(vis.is_visible(Breakpoint::Lg));
assert!(!vis.is_visible(Breakpoint::Xl));
assert_eq!(vis.visible_count(), 1);
}
#[test]
fn at_multiple() {
let vis = Visibility::at(&[Breakpoint::Xs, Breakpoint::Lg, Breakpoint::Xl]);
assert!(vis.is_visible(Breakpoint::Xs));
assert!(!vis.is_visible(Breakpoint::Sm));
assert!(!vis.is_visible(Breakpoint::Md));
assert!(vis.is_visible(Breakpoint::Lg));
assert!(vis.is_visible(Breakpoint::Xl));
assert_eq!(vis.visible_count(), 3);
}
#[test]
fn hidden_below() {
let vis = Visibility::hidden_below(Breakpoint::Md);
assert!(!vis.is_visible(Breakpoint::Xs));
assert!(!vis.is_visible(Breakpoint::Sm));
assert!(vis.is_visible(Breakpoint::Md));
}
#[test]
fn hidden_above() {
let vis = Visibility::hidden_above(Breakpoint::Md);
assert!(vis.is_visible(Breakpoint::Xs));
assert!(vis.is_visible(Breakpoint::Sm));
assert!(!vis.is_visible(Breakpoint::Md));
assert!(!vis.is_visible(Breakpoint::Lg));
}
#[test]
fn hidden_above_xs() {
let vis = Visibility::hidden_above(Breakpoint::Xs);
assert!(vis.is_never());
}
#[test]
fn from_mask() {
let vis = Visibility::from_mask(0b10101); assert!(vis.is_visible(Breakpoint::Xs));
assert!(!vis.is_visible(Breakpoint::Sm));
assert!(vis.is_visible(Breakpoint::Md));
assert!(!vis.is_visible(Breakpoint::Lg));
assert!(vis.is_visible(Breakpoint::Xl));
}
#[test]
fn from_mask_truncates() {
let vis = Visibility::from_mask(0xFF);
assert_eq!(vis.mask(), 0b11111);
}
#[test]
fn visible_breakpoints_iterator() {
let vis = Visibility::at(&[Breakpoint::Sm, Breakpoint::Lg]);
let bps: Vec<_> = vis.visible_breakpoints().collect();
assert_eq!(bps, vec![Breakpoint::Sm, Breakpoint::Lg]);
}
#[test]
fn filter_rects_basic() {
let rects = vec![
Rect::new(0, 0, 20, 10),
Rect::new(20, 0, 30, 10),
Rect::new(50, 0, 40, 10),
];
let visibilities = vec![
Visibility::ALWAYS,
Visibility::hidden_below(Breakpoint::Md), Visibility::ALWAYS,
];
let visible = Visibility::filter_rects(&visibilities, &rects, Breakpoint::Sm);
assert_eq!(visible.len(), 2);
assert_eq!(visible[0].0, 0); assert_eq!(visible[1].0, 2);
let visible = Visibility::filter_rects(&visibilities, &rects, Breakpoint::Md);
assert_eq!(visible.len(), 3);
}
#[test]
fn count_visible_helper() {
let visibilities = vec![
Visibility::ALWAYS,
Visibility::only(Breakpoint::Xl),
Visibility::visible_above(Breakpoint::Lg),
];
assert_eq!(Visibility::count_visible(&visibilities, Breakpoint::Xs), 1);
assert_eq!(Visibility::count_visible(&visibilities, Breakpoint::Lg), 2);
assert_eq!(Visibility::count_visible(&visibilities, Breakpoint::Xl), 3);
}
#[test]
fn default_is_always() {
assert_eq!(Visibility::default(), Visibility::ALWAYS);
}
#[test]
fn display_always() {
assert_eq!(format!("{}", Visibility::ALWAYS), "always");
}
#[test]
fn display_never() {
assert_eq!(format!("{}", Visibility::NEVER), "never");
}
#[test]
fn display_partial() {
let vis = Visibility::at(&[Breakpoint::Sm, Breakpoint::Lg]);
assert_eq!(format!("{}", vis), "sm+lg");
}
#[test]
fn equality() {
assert_eq!(
Visibility::visible_above(Breakpoint::Md),
Visibility::hidden_below(Breakpoint::Md)
);
}
#[test]
fn clone_independence() {
let a = Visibility::only(Breakpoint::Md);
let b = a;
assert_eq!(a, b);
}
#[test]
fn debug_format() {
let dbg = format!("{:?}", Visibility::ALWAYS);
assert!(dbg.contains("Visibility"));
}
}