Skip to main content

tokenburn_core/utils/
layout.rs

1//! Responsive breakpoints shared by every front-end, so a "narrow" window means the
2//! same thing in the TUI, the GUI and the web dashboard:
3//!
4//! | class | window | terminal | what changes |
5//! |---|---|---|---|
6//! | **Wide** | ≥ 980 px | ≥ 110 columns | everything: all columns, charts side by side |
7//! | **Medium** | ≥ 700 px | ≥ 70 columns | the cache columns drop out, charts stack |
8//! | **Narrow** | below | below | compact header and controls; only the essential columns |
9//!
10//! The pixel and column thresholds are the same classes in different units; the
11//! front-ends decide what each class looks like.
12
13/// Window width classes, narrowest first (so `class >= LayoutClass::Medium` reads
14/// naturally).
15#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
16pub enum LayoutClass {
17    Narrow,
18    Medium,
19    Wide,
20}
21
22/// Minimum logical-pixel width of a wide / medium window (GUI and web).
23pub const WIDE_PX: f32 = 980.0;
24pub const MEDIUM_PX: f32 = 700.0;
25/// Minimum width in terminal columns of a wide / medium terminal (TUI).
26pub const WIDE_COLS: u16 = 110;
27pub const MEDIUM_COLS: u16 = 70;
28
29impl LayoutClass {
30    /// Class of a window `width` logical pixels wide.
31    pub fn from_px(width: f32) -> Self {
32        if width >= WIDE_PX {
33            Self::Wide
34        } else if width >= MEDIUM_PX {
35            Self::Medium
36        } else {
37            Self::Narrow
38        }
39    }
40
41    /// Class of a terminal `cols` columns wide.
42    pub fn from_cols(cols: u16) -> Self {
43        if cols >= WIDE_COLS {
44            Self::Wide
45        } else if cols >= MEDIUM_COLS {
46            Self::Medium
47        } else {
48            Self::Narrow
49        }
50    }
51
52    pub fn is_narrow(self) -> bool {
53        self == Self::Narrow
54    }
55
56    pub fn is_wide(self) -> bool {
57        self == Self::Wide
58    }
59
60    /// `"wide"`, `"medium"` or `"narrow"`.
61    pub fn label(self) -> &'static str {
62        match self {
63            Self::Wide => "wide",
64            Self::Medium => "medium",
65            Self::Narrow => "narrow",
66        }
67    }
68}
69
70#[cfg(test)]
71mod tests {
72    use super::*;
73
74    #[test]
75    fn pixel_widths_fall_into_the_documented_classes() {
76        assert_eq!(LayoutClass::from_px(1920.0), LayoutClass::Wide);
77        assert_eq!(
78            LayoutClass::from_px(980.0),
79            LayoutClass::Wide,
80            "the boundary is wide"
81        );
82        assert_eq!(LayoutClass::from_px(979.9), LayoutClass::Medium);
83        assert_eq!(LayoutClass::from_px(700.0), LayoutClass::Medium);
84        assert_eq!(LayoutClass::from_px(699.9), LayoutClass::Narrow);
85        assert_eq!(LayoutClass::from_px(0.0), LayoutClass::Narrow);
86        assert_eq!(
87            LayoutClass::from_px(f32::NAN),
88            LayoutClass::Narrow,
89            "garbage is narrow"
90        );
91    }
92
93    #[test]
94    fn terminal_columns_fall_into_the_same_classes() {
95        assert_eq!(LayoutClass::from_cols(200), LayoutClass::Wide);
96        assert_eq!(LayoutClass::from_cols(110), LayoutClass::Wide);
97        assert_eq!(LayoutClass::from_cols(109), LayoutClass::Medium);
98        assert_eq!(LayoutClass::from_cols(70), LayoutClass::Medium);
99        assert_eq!(LayoutClass::from_cols(69), LayoutClass::Narrow);
100        assert_eq!(LayoutClass::from_cols(0), LayoutClass::Narrow);
101        assert_eq!(
102            LayoutClass::from_cols(80),
103            LayoutClass::Medium,
104            "the classic 80x24"
105        );
106    }
107
108    #[test]
109    fn classes_order_from_narrow_to_wide() {
110        assert!(
111            LayoutClass::Narrow < LayoutClass::Medium && LayoutClass::Medium < LayoutClass::Wide
112        );
113        assert!(LayoutClass::Narrow.is_narrow() && LayoutClass::Wide.is_wide());
114        assert_eq!(LayoutClass::Medium.label(), "medium");
115    }
116}