guise/layout/
breakpoint.rs1use gpui::Window;
14
15#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
18pub enum Breakpoint {
19 Xs,
20 Sm,
21 Md,
22 Lg,
23 Xl,
24}
25
26impl Breakpoint {
27 pub fn of(width: f32) -> Breakpoint {
29 match width {
30 w if w >= 1280.0 => Breakpoint::Xl,
31 w if w >= 1024.0 => Breakpoint::Lg,
32 w if w >= 768.0 => Breakpoint::Md,
33 w if w >= 640.0 => Breakpoint::Sm,
34 _ => Breakpoint::Xs,
35 }
36 }
37
38 pub fn from_window(window: &Window) -> Breakpoint {
40 Breakpoint::of(f32::from(window.viewport_size().width))
41 }
42
43 pub fn at_least(self, other: Breakpoint) -> bool {
46 self >= other
47 }
48}
49
50#[derive(Debug, Clone, Copy, PartialEq, Eq)]
53pub struct Responsive<T> {
54 base: T,
55 sm: Option<T>,
56 md: Option<T>,
57 lg: Option<T>,
58 xl: Option<T>,
59}
60
61impl<T: Clone> Responsive<T> {
62 pub fn new(base: T) -> Self {
64 Responsive {
65 base,
66 sm: None,
67 md: None,
68 lg: None,
69 xl: None,
70 }
71 }
72
73 pub fn sm(mut self, value: T) -> Self {
74 self.sm = Some(value);
75 self
76 }
77
78 pub fn md(mut self, value: T) -> Self {
79 self.md = Some(value);
80 self
81 }
82
83 pub fn lg(mut self, value: T) -> Self {
84 self.lg = Some(value);
85 self
86 }
87
88 pub fn xl(mut self, value: T) -> Self {
89 self.xl = Some(value);
90 self
91 }
92
93 pub fn resolve(&self, bp: Breakpoint) -> T {
96 let ladder = [
97 (Breakpoint::Xl, &self.xl),
98 (Breakpoint::Lg, &self.lg),
99 (Breakpoint::Md, &self.md),
100 (Breakpoint::Sm, &self.sm),
101 ];
102 for (level, value) in ladder {
103 if bp.at_least(level) {
104 if let Some(v) = value {
105 return v.clone();
106 }
107 }
108 }
109 self.base.clone()
110 }
111
112 pub fn for_window(&self, window: &Window) -> T {
114 self.resolve(Breakpoint::from_window(window))
115 }
116}
117
118#[cfg(test)]
119mod tests {
120 use super::*;
121
122 #[test]
123 fn widths_map_to_breakpoints() {
124 assert_eq!(Breakpoint::of(0.0), Breakpoint::Xs);
125 assert_eq!(Breakpoint::of(639.9), Breakpoint::Xs);
126 assert_eq!(Breakpoint::of(640.0), Breakpoint::Sm);
127 assert_eq!(Breakpoint::of(767.0), Breakpoint::Sm);
128 assert_eq!(Breakpoint::of(768.0), Breakpoint::Md);
129 assert_eq!(Breakpoint::of(1024.0), Breakpoint::Lg);
130 assert_eq!(Breakpoint::of(1280.0), Breakpoint::Xl);
131 assert_eq!(Breakpoint::of(3840.0), Breakpoint::Xl);
132 }
133
134 #[test]
135 fn at_least_is_mobile_first() {
136 assert!(Breakpoint::Lg.at_least(Breakpoint::Md));
137 assert!(Breakpoint::Md.at_least(Breakpoint::Md));
138 assert!(!Breakpoint::Sm.at_least(Breakpoint::Md));
139 }
140
141 #[test]
142 fn responsive_resolves_the_nearest_override_below() {
143 let cols = Responsive::new(1).md(2).xl(4);
144 assert_eq!(cols.resolve(Breakpoint::Xs), 1);
145 assert_eq!(cols.resolve(Breakpoint::Sm), 1);
146 assert_eq!(cols.resolve(Breakpoint::Md), 2);
147 assert_eq!(cols.resolve(Breakpoint::Lg), 2); assert_eq!(cols.resolve(Breakpoint::Xl), 4);
149 }
150
151 #[test]
152 fn base_only_is_constant() {
153 let pad = Responsive::new("tight");
154 assert_eq!(pad.resolve(Breakpoint::Xs), "tight");
155 assert_eq!(pad.resolve(Breakpoint::Xl), "tight");
156 }
157}