use crate::core::Size;
use crate::widget::metrics::dimensions::{BREAKPOINT_COMPACT_MAX, BREAKPOINT_MEDIUM_MAX};
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum Breakpoint {
Compact,
Medium,
Expanded,
}
impl Breakpoint {
pub fn of(available: Size) -> Self {
if available.width <= BREAKPOINT_COMPACT_MAX {
Breakpoint::Compact
} else if available.width <= BREAKPOINT_MEDIUM_MAX {
Breakpoint::Medium
} else {
Breakpoint::Expanded
}
}
pub fn current() -> Self {
CURRENT.try_with(|cell| cell.get()).unwrap_or(Breakpoint::Expanded)
}
}
impl Default for Breakpoint {
fn default() -> Self {
Breakpoint::Expanded
}
}
thread_local! {
#[allow(clippy::missing_const_for_thread_local)]
static CURRENT: core::cell::Cell<Breakpoint> = const { core::cell::Cell::new(Breakpoint::Expanded) };
}
pub fn with_breakpoint<R>(breakpoint: Breakpoint, f: impl FnOnce() -> R) -> R {
struct Restore(Breakpoint);
impl Drop for Restore {
fn drop(&mut self) {
let _ = CURRENT.try_with(|cell| cell.set(self.0));
}
}
let previous = CURRENT.try_with(|cell| cell.replace(breakpoint)).unwrap_or(breakpoint);
let _restore = Restore(previous);
f()
}
pub fn is_current(breakpoint: Breakpoint) -> bool {
Breakpoint::current() == breakpoint
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn the_width_classifies_into_exactly_one_tier() {
assert_eq!(Breakpoint::of(Size::new(0, 1000)), Breakpoint::Compact);
assert_eq!(Breakpoint::of(Size::new(320, 640)), Breakpoint::Compact);
assert_eq!(
Breakpoint::of(Size::new(BREAKPOINT_COMPACT_MAX, 1000)),
Breakpoint::Compact,
"the compact bound is inclusive"
);
assert_eq!(
Breakpoint::of(Size::new(BREAKPOINT_COMPACT_MAX + 1, 1000)),
Breakpoint::Medium,
"one pixel past it is medium, with no gap"
);
assert_eq!(
Breakpoint::of(Size::new(BREAKPOINT_MEDIUM_MAX, 1000)),
Breakpoint::Medium,
"the medium bound is inclusive"
);
assert_eq!(
Breakpoint::of(Size::new(BREAKPOINT_MEDIUM_MAX + 1, 1000)),
Breakpoint::Expanded,
"and one past it is expanded"
);
}
#[test]
fn a_tall_window_is_not_mistaken_for_a_wide_one() {
assert_eq!(Breakpoint::of(Size::new(320, 4000)), Breakpoint::Compact);
assert_eq!(Breakpoint::of(Size::new(1200, 200)), Breakpoint::Expanded);
}
#[test]
fn a_build_scope_declares_the_tier_in_force() {
assert_eq!(Breakpoint::current(), Breakpoint::Expanded, "the neutral default");
let inside = with_breakpoint(Breakpoint::Compact, Breakpoint::current);
assert_eq!(inside, Breakpoint::Compact, "the scope's tier is in force inside");
assert!(is_current(Breakpoint::Expanded), "and the previous one is restored after");
assert!(!is_current(Breakpoint::Compact));
}
#[test]
fn nested_scopes_restore_their_parent() {
with_breakpoint(Breakpoint::Medium, || {
assert_eq!(Breakpoint::current(), Breakpoint::Medium);
with_breakpoint(Breakpoint::Compact, || {
assert_eq!(Breakpoint::current(), Breakpoint::Compact);
});
assert_eq!(Breakpoint::current(), Breakpoint::Medium, "the parent is restored");
});
assert_eq!(Breakpoint::current(), Breakpoint::Expanded);
}
}