Skip to main content

gpui_component/
sizing.rs

1use gpui::{Edges, Pixels, Styled, px};
2use serde::{Deserialize, Serialize};
3
4/// A size for elements.
5#[derive(Clone, Default, Copy, PartialEq, Eq, Debug, Deserialize, Serialize)]
6pub enum Size {
7    Size(Pixels),
8    XSmall,
9    Small,
10    #[default]
11    Medium,
12    Large,
13}
14
15impl Size {
16    fn as_f32(&self) -> f32 {
17        match self {
18            Size::Size(val) => val.as_f32(),
19            Size::XSmall => 0.,
20            Size::Small => 1.,
21            Size::Medium => 2.,
22            Size::Large => 3.,
23        }
24    }
25
26    /// Returns the size as a static string.
27    pub fn as_str(&self) -> &'static str {
28        match self {
29            Size::XSmall => "xs",
30            Size::Small => "sm",
31            Size::Medium => "md",
32            Size::Large => "lg",
33            Size::Size(_) => "custom",
34        }
35    }
36
37    /// Create a Size from a static string.
38    ///
39    /// - "xs" or "xsmall"
40    /// - "sm" or "small"
41    /// - "md" or "medium"
42    /// - "lg" or "large"
43    ///
44    /// Any other value will return Size::Medium.
45    pub fn from_str(size: &str) -> Self {
46        match size.to_lowercase().as_str() {
47            "xs" | "xsmall" => Size::XSmall,
48            "sm" | "small" => Size::Small,
49            "md" | "medium" => Size::Medium,
50            "lg" | "large" => Size::Large,
51            _ => Size::Medium,
52        }
53    }
54
55    /// Returns the height for table row.
56    #[inline]
57    pub fn table_row_height(&self) -> Pixels {
58        match self {
59            Size::Size(size) => *size,
60            Size::XSmall => px(26.),
61            Size::Small => px(30.),
62            Size::Large => px(40.),
63            _ => px(32.),
64        }
65    }
66
67    /// Returns the padding for a table cell.
68    #[inline]
69    pub fn table_cell_padding(&self) -> Edges<Pixels> {
70        match self {
71            Size::XSmall => Edges {
72                top: px(2.),
73                bottom: px(2.),
74                left: px(4.),
75                right: px(4.),
76            },
77            Size::Small => Edges {
78                top: px(3.),
79                bottom: px(3.),
80                left: px(6.),
81                right: px(6.),
82            },
83            Size::Large => Edges {
84                top: px(8.),
85                bottom: px(8.),
86                left: px(12.),
87                right: px(12.),
88            },
89            _ => Edges {
90                top: px(4.),
91                bottom: px(4.),
92                left: px(8.),
93                right: px(8.),
94            },
95        }
96    }
97
98    /// Returns a smaller size.
99    pub fn smaller(&self) -> Self {
100        match self {
101            Size::XSmall => Size::XSmall,
102            Size::Small => Size::XSmall,
103            Size::Medium => Size::Small,
104            Size::Large => Size::Medium,
105            Size::Size(val) => Size::Size(*val * 0.2),
106        }
107    }
108
109    /// Returns a larger size.
110    pub fn larger(&self) -> Self {
111        match self {
112            Size::XSmall => Size::Small,
113            Size::Small => Size::Medium,
114            Size::Medium => Size::Large,
115            Size::Large => Size::Large,
116            Size::Size(val) => Size::Size(*val * 1.2),
117        }
118    }
119
120    /// Return the max size between two sizes.
121    ///
122    /// e.g. `Size::XSmall.max(Size::Small)` will return `Size::XSmall`.
123    pub fn max(&self, other: Self) -> Self {
124        match (self, other) {
125            (Size::Size(a), Size::Size(b)) => Size::Size(px(a.as_f32().min(b.as_f32()))),
126            (Size::Size(a), _) => Size::Size(*a),
127            (_, Size::Size(b)) => Size::Size(b),
128            (a, b) if a.as_f32() < b.as_f32() => *a,
129            _ => other,
130        }
131    }
132
133    /// Return the min size between two sizes.
134    ///
135    /// e.g. `Size::XSmall.min(Size::Small)` will return `Size::Small`.
136    pub fn min(&self, other: Self) -> Self {
137        match (self, other) {
138            (Size::Size(a), Size::Size(b)) => Size::Size(px(a.as_f32().max(b.as_f32()))),
139            (Size::Size(a), _) => Size::Size(*a),
140            (_, Size::Size(b)) => Size::Size(b),
141            (a, b) if a.as_f32() > b.as_f32() => *a,
142            _ => other,
143        }
144    }
145
146    /// Returns the horizontal input padding.
147    pub fn input_px(&self) -> Pixels {
148        match self {
149            Self::Large => px(12.),
150            Self::Medium => px(10.),
151            Self::Small => px(8.),
152            Self::XSmall => px(4.),
153            _ => px(8.),
154        }
155    }
156
157    /// Returns the vertical input padding.
158    pub fn input_py(&self) -> Pixels {
159        match self {
160            Size::Large => px(10.),
161            Size::Medium => px(8.),
162            Size::Small => px(2.),
163            Size::XSmall => px(0.),
164            _ => px(2.),
165        }
166    }
167}
168
169impl From<Pixels> for Size {
170    fn from(size: Pixels) -> Self {
171        Size::Size(size)
172    }
173}
174
175/// A trait for setting the size of an element.
176/// Size::Medium is use by default.
177#[allow(patterns_in_fns_without_body)]
178pub trait Sizable: Sized {
179    /// Set the ui::Size of this element.
180    ///
181    /// Also can receive a `ButtonSize` to convert to `IconSize`,
182    /// Or a `Pixels` to set a custom size: `px(30.)`
183    fn with_size(mut self, size: impl Into<Size>) -> Self;
184
185    /// Set to Size::XSmall
186    #[inline(always)]
187    fn xsmall(self) -> Self {
188        self.with_size(Size::XSmall)
189    }
190
191    /// Set to Size::Small
192    #[inline(always)]
193    fn small(self) -> Self {
194        self.with_size(Size::Small)
195    }
196
197    /// Set to Size::Large
198    #[inline(always)]
199    fn large(self) -> Self {
200        self.with_size(Size::Large)
201    }
202}
203
204#[allow(unused)]
205pub trait StyleSized<T: Styled> {
206    fn input_text_size(self, size: Size) -> Self;
207    fn input_size(self, size: Size) -> Self;
208    fn input_pl(self, size: Size) -> Self;
209    fn input_pr(self, size: Size) -> Self;
210    fn input_px(self, size: Size) -> Self;
211    fn input_py(self, size: Size) -> Self;
212    fn input_h(self, size: Size) -> Self;
213    fn list_size(self, size: Size) -> Self;
214    fn list_px(self, size: Size) -> Self;
215    fn list_py(self, size: Size) -> Self;
216    /// Apply size with the given `Size`.
217    fn size_with(self, size: Size) -> Self;
218    /// Apply the table cell size (Font size, padding) with the given `Size`.
219    fn table_cell_size(self, size: Size) -> Self;
220    fn button_text_size(self, size: Size) -> Self;
221}
222
223impl<T: Styled> StyleSized<T> for T {
224    #[inline]
225    fn input_text_size(self, size: Size) -> Self {
226        match size {
227            Size::XSmall => self.text_xs(),
228            Size::Small => self.text_sm(),
229            Size::Medium => self.text_sm(),
230            Size::Large => self.text_base(),
231            Size::Size(size) => self.text_size(size * 0.875),
232        }
233    }
234
235    #[inline]
236    fn input_size(self, size: Size) -> Self {
237        self.input_px(size).input_py(size).input_h(size)
238    }
239
240    #[inline]
241    fn input_pl(self, size: Size) -> Self {
242        self.pl(size.input_px())
243    }
244
245    #[inline]
246    fn input_pr(self, size: Size) -> Self {
247        self.pr(size.input_px())
248    }
249
250    #[inline]
251    fn input_px(self, size: Size) -> Self {
252        self.px(size.input_px())
253    }
254
255    #[inline]
256    fn input_py(self, size: Size) -> Self {
257        self.py(size.input_py())
258    }
259
260    #[inline]
261    fn input_h(self, size: Size) -> Self {
262        match size {
263            Size::Large => self.h_11(),
264            Size::Medium => self.h_8(),
265            Size::Small => self.h_6(),
266            Size::XSmall => self.h_5(),
267            _ => self.h_6(),
268        }
269    }
270
271    #[inline]
272    fn list_size(self, size: Size) -> Self {
273        self.list_px(size).list_py(size).input_text_size(size)
274    }
275
276    #[inline]
277    fn list_px(self, size: Size) -> Self {
278        match size {
279            Size::Small => self.px_2(),
280            _ => self.px_3(),
281        }
282    }
283
284    #[inline]
285    fn list_py(self, size: Size) -> Self {
286        match size {
287            Size::Large => self.py_2(),
288            Size::Medium => self.py_1(),
289            Size::Small => self.py_0p5(),
290            _ => self.py_1(),
291        }
292    }
293
294    #[inline]
295    fn size_with(self, size: Size) -> Self {
296        match size {
297            Size::Large => self.size_11(),
298            Size::Medium => self.size_8(),
299            Size::Small => self.size_5(),
300            Size::XSmall => self.size_4(),
301            Size::Size(size) => self.size(size),
302        }
303    }
304
305    #[inline]
306    fn table_cell_size(self, size: Size) -> Self {
307        let padding = size.table_cell_padding();
308        match size {
309            Size::XSmall => self.text_sm(),
310            Size::Small => self.text_sm(),
311            _ => self,
312        }
313        .pl(padding.left)
314        .pr(padding.right)
315        .pt(padding.top)
316        .pb(padding.bottom)
317    }
318
319    fn button_text_size(self, size: Size) -> Self {
320        match size {
321            Size::XSmall => self.text_xs(),
322            Size::Small => self.text_sm(),
323            _ => self.text_base(),
324        }
325    }
326}
327#[cfg(test)]
328mod tests {
329    use gpui::px;
330
331    use crate::Size;
332
333    #[test]
334    fn test_size_max_min() {
335        assert_eq!(Size::Small.min(Size::XSmall), Size::Small);
336        assert_eq!(Size::XSmall.min(Size::Small), Size::Small);
337        assert_eq!(Size::Small.min(Size::Medium), Size::Medium);
338        assert_eq!(Size::Medium.min(Size::Large), Size::Large);
339        assert_eq!(Size::Large.min(Size::Small), Size::Large);
340
341        assert_eq!(
342            Size::Size(px(10.)).min(Size::Size(px(20.))),
343            Size::Size(px(20.))
344        );
345
346        // Min
347        assert_eq!(Size::Small.max(Size::XSmall), Size::XSmall);
348        assert_eq!(Size::XSmall.max(Size::Small), Size::XSmall);
349        assert_eq!(Size::Small.max(Size::Medium), Size::Small);
350        assert_eq!(Size::Medium.max(Size::Large), Size::Medium);
351        assert_eq!(Size::Large.max(Size::Small), Size::Small);
352
353        assert_eq!(
354            Size::Size(px(10.)).max(Size::Size(px(20.))),
355            Size::Size(px(10.))
356        );
357    }
358
359    #[test]
360    fn test_size_as_str() {
361        assert_eq!(Size::XSmall.as_str(), "xs");
362        assert_eq!(Size::Small.as_str(), "sm");
363        assert_eq!(Size::Medium.as_str(), "md");
364        assert_eq!(Size::Large.as_str(), "lg");
365        assert_eq!(Size::Size(px(15.)).as_str(), "custom");
366    }
367
368    #[test]
369    fn test_table_row_height() {
370        assert_eq!(Size::XSmall.table_row_height(), px(26.));
371        assert_eq!(Size::Small.table_row_height(), px(30.));
372        assert_eq!(Size::Medium.table_row_height(), px(32.));
373        assert_eq!(Size::Large.table_row_height(), px(40.));
374        assert_eq!(Size::Size(px(48.)).table_row_height(), px(48.));
375    }
376
377    #[test]
378    fn test_size_from_str() {
379        assert_eq!(Size::from_str("xs"), Size::XSmall);
380        assert_eq!(Size::from_str("xsmall"), Size::XSmall);
381        assert_eq!(Size::from_str("sm"), Size::Small);
382        assert_eq!(Size::from_str("small"), Size::Small);
383        assert_eq!(Size::from_str("md"), Size::Medium);
384        assert_eq!(Size::from_str("medium"), Size::Medium);
385        assert_eq!(Size::from_str("lg"), Size::Large);
386        assert_eq!(Size::from_str("large"), Size::Large);
387        assert_eq!(Size::from_str("unknown"), Size::Medium);
388
389        // Case insensitive
390        assert_eq!(Size::from_str("XS"), Size::XSmall);
391        assert_eq!(Size::from_str("SMALL"), Size::Small);
392        assert_eq!(Size::from_str("Md"), Size::Medium);
393    }
394}