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    /// Apply control-specific presentation when this element is hosted by a
186    /// toolbar. Most controls keep their normal presentation; command buttons
187    /// override this hook to use the toolbar's quiet, compact treatment.
188    #[doc(hidden)]
189    fn prepare_for_toolbar(self) -> Self {
190        self
191    }
192
193    /// Set to Size::XSmall
194    #[inline(always)]
195    fn xsmall(self) -> Self {
196        self.with_size(Size::XSmall)
197    }
198
199    /// Set to Size::Small
200    #[inline(always)]
201    fn small(self) -> Self {
202        self.with_size(Size::Small)
203    }
204
205    /// Set to Size::Large
206    #[inline(always)]
207    fn large(self) -> Self {
208        self.with_size(Size::Large)
209    }
210}
211
212#[allow(unused)]
213pub trait StyleSized<T: Styled> {
214    fn input_text_size(self, size: Size) -> Self;
215    fn input_size(self, size: Size) -> Self;
216    fn input_pl(self, size: Size) -> Self;
217    fn input_pr(self, size: Size) -> Self;
218    fn input_px(self, size: Size) -> Self;
219    fn input_py(self, size: Size) -> Self;
220    fn input_h(self, size: Size) -> Self;
221    fn list_size(self, size: Size) -> Self;
222    fn list_px(self, size: Size) -> Self;
223    fn list_py(self, size: Size) -> Self;
224    /// Apply size with the given `Size`.
225    fn size_with(self, size: Size) -> Self;
226    /// Apply the table cell size (Font size, padding) with the given `Size`.
227    fn table_cell_size(self, size: Size) -> Self;
228    fn button_text_size(self, size: Size) -> Self;
229}
230
231impl<T: Styled> StyleSized<T> for T {
232    #[inline]
233    fn input_text_size(self, size: Size) -> Self {
234        match size {
235            Size::XSmall => self.text_xs(),
236            Size::Small => self.text_sm(),
237            Size::Medium => self.text_sm(),
238            Size::Large => self.text_base(),
239            Size::Size(size) => self.text_size(size * 0.875),
240        }
241    }
242
243    #[inline]
244    fn input_size(self, size: Size) -> Self {
245        self.input_px(size).input_py(size).input_h(size)
246    }
247
248    #[inline]
249    fn input_pl(self, size: Size) -> Self {
250        self.pl(size.input_px())
251    }
252
253    #[inline]
254    fn input_pr(self, size: Size) -> Self {
255        self.pr(size.input_px())
256    }
257
258    #[inline]
259    fn input_px(self, size: Size) -> Self {
260        self.px(size.input_px())
261    }
262
263    #[inline]
264    fn input_py(self, size: Size) -> Self {
265        self.py(size.input_py())
266    }
267
268    #[inline]
269    fn input_h(self, size: Size) -> Self {
270        match size {
271            Size::Large => self.h_11(),
272            Size::Medium => self.h_8(),
273            Size::Small => self.h_6(),
274            Size::XSmall => self.h_5(),
275            _ => self.h_6(),
276        }
277    }
278
279    #[inline]
280    fn list_size(self, size: Size) -> Self {
281        self.list_px(size).list_py(size).input_text_size(size)
282    }
283
284    #[inline]
285    fn list_px(self, size: Size) -> Self {
286        match size {
287            Size::Small => self.px_2(),
288            _ => self.px_3(),
289        }
290    }
291
292    #[inline]
293    fn list_py(self, size: Size) -> Self {
294        match size {
295            Size::Large => self.py_2(),
296            Size::Medium => self.py_1(),
297            Size::Small => self.py_0p5(),
298            _ => self.py_1(),
299        }
300    }
301
302    #[inline]
303    fn size_with(self, size: Size) -> Self {
304        match size {
305            Size::Large => self.size_11(),
306            Size::Medium => self.size_8(),
307            Size::Small => self.size_5(),
308            Size::XSmall => self.size_4(),
309            Size::Size(size) => self.size(size),
310        }
311    }
312
313    #[inline]
314    fn table_cell_size(self, size: Size) -> Self {
315        let padding = size.table_cell_padding();
316        match size {
317            Size::XSmall => self.text_sm(),
318            Size::Small => self.text_sm(),
319            _ => self,
320        }
321        .pl(padding.left)
322        .pr(padding.right)
323        .pt(padding.top)
324        .pb(padding.bottom)
325    }
326
327    fn button_text_size(self, size: Size) -> Self {
328        match size {
329            Size::XSmall => self.text_xs(),
330            Size::Small => self.text_sm(),
331            _ => self.text_base(),
332        }
333    }
334}
335#[cfg(test)]
336mod tests {
337    use gpui::px;
338
339    use crate::Size;
340
341    #[test]
342    fn test_size_max_min() {
343        assert_eq!(Size::Small.min(Size::XSmall), Size::Small);
344        assert_eq!(Size::XSmall.min(Size::Small), Size::Small);
345        assert_eq!(Size::Small.min(Size::Medium), Size::Medium);
346        assert_eq!(Size::Medium.min(Size::Large), Size::Large);
347        assert_eq!(Size::Large.min(Size::Small), Size::Large);
348
349        assert_eq!(
350            Size::Size(px(10.)).min(Size::Size(px(20.))),
351            Size::Size(px(20.))
352        );
353
354        // Min
355        assert_eq!(Size::Small.max(Size::XSmall), Size::XSmall);
356        assert_eq!(Size::XSmall.max(Size::Small), Size::XSmall);
357        assert_eq!(Size::Small.max(Size::Medium), Size::Small);
358        assert_eq!(Size::Medium.max(Size::Large), Size::Medium);
359        assert_eq!(Size::Large.max(Size::Small), Size::Small);
360
361        assert_eq!(
362            Size::Size(px(10.)).max(Size::Size(px(20.))),
363            Size::Size(px(10.))
364        );
365    }
366
367    #[test]
368    fn test_size_as_str() {
369        assert_eq!(Size::XSmall.as_str(), "xs");
370        assert_eq!(Size::Small.as_str(), "sm");
371        assert_eq!(Size::Medium.as_str(), "md");
372        assert_eq!(Size::Large.as_str(), "lg");
373        assert_eq!(Size::Size(px(15.)).as_str(), "custom");
374    }
375
376    #[test]
377    fn test_table_row_height() {
378        assert_eq!(Size::XSmall.table_row_height(), px(26.));
379        assert_eq!(Size::Small.table_row_height(), px(30.));
380        assert_eq!(Size::Medium.table_row_height(), px(32.));
381        assert_eq!(Size::Large.table_row_height(), px(40.));
382        assert_eq!(Size::Size(px(48.)).table_row_height(), px(48.));
383    }
384
385    #[test]
386    fn test_size_from_str() {
387        assert_eq!(Size::from_str("xs"), Size::XSmall);
388        assert_eq!(Size::from_str("xsmall"), Size::XSmall);
389        assert_eq!(Size::from_str("sm"), Size::Small);
390        assert_eq!(Size::from_str("small"), Size::Small);
391        assert_eq!(Size::from_str("md"), Size::Medium);
392        assert_eq!(Size::from_str("medium"), Size::Medium);
393        assert_eq!(Size::from_str("lg"), Size::Large);
394        assert_eq!(Size::from_str("large"), Size::Large);
395        assert_eq!(Size::from_str("unknown"), Size::Medium);
396
397        // Case insensitive
398        assert_eq!(Size::from_str("XS"), Size::XSmall);
399        assert_eq!(Size::from_str("SMALL"), Size::Small);
400        assert_eq!(Size::from_str("Md"), Size::Medium);
401    }
402}