Skip to main content

ui/styles/
typography.rs

1use crate::prelude::*;
2use gpui::{
3    AnyElement, App, IntoElement, ParentElement, Rems, RenderOnce, SharedString, Styled, Window,
4    div, rems,
5};
6use theme::ActiveTheme;
7
8use crate::{Color, rems_from_px};
9
10/// Extends [`gpui::Styled`] with typography-related styling methods.
11pub trait StyledTypography: Styled + Sized {
12    /// Sets the font family to the buffer font.
13    fn font_buffer(self, cx: &App) -> Self {
14        let settings = theme::theme_settings(cx);
15        let buffer_font_family = settings.buffer_font(cx).family.clone();
16
17        self.font_family(buffer_font_family)
18    }
19
20    /// Sets the font family to the UI font.
21    fn font_ui(self, cx: &App) -> Self {
22        let settings = theme::theme_settings(cx);
23        let ui_font_family = settings.ui_font(cx).family.clone();
24
25        self.font_family(ui_font_family)
26    }
27
28    /// Sets the text size using a [`TextSize`].
29    fn text_ui_size(self, size: TextSize, cx: &App) -> Self {
30        self.text_size(size.rems(cx))
31    }
32
33    /// The large size for UI text.
34    ///
35    /// `1rem` or `16px` at the default scale of `1rem` = `16px`.
36    ///
37    /// Note: The absolute size of this text will change based on a user's `ui_scale` setting.
38    ///
39    /// Use `text_ui` for regular-sized text.
40    fn text_ui_lg(self, cx: &App) -> Self {
41        self.text_size(TextSize::Large.rems(cx))
42    }
43
44    /// The default size for UI text.
45    ///
46    /// `0.825rem` or `14px` at the default scale of `1rem` = `16px`.
47    ///
48    /// Note: The absolute size of this text will change based on a user's `ui_scale` setting.
49    ///
50    /// Use `text_ui_sm` for smaller text.
51    fn text_ui(self, cx: &App) -> Self {
52        self.text_size(TextSize::default().rems(cx))
53    }
54
55    /// The small size for UI text.
56    ///
57    /// `0.75rem` or `12px` at the default scale of `1rem` = `16px`.
58    ///
59    /// Note: The absolute size of this text will change based on a user's `ui_scale` setting.
60    ///
61    /// Use `text_ui` for regular-sized text.
62    fn text_ui_sm(self, cx: &App) -> Self {
63        self.text_size(TextSize::Small.rems(cx))
64    }
65
66    /// The extra small size for UI text.
67    ///
68    /// `0.625rem` or `10px` at the default scale of `1rem` = `16px`.
69    ///
70    /// Note: The absolute size of this text will change based on a user's `ui_scale` setting.
71    ///
72    /// Use `text_ui` for regular-sized text.
73    fn text_ui_xs(self, cx: &App) -> Self {
74        self.text_size(TextSize::XSmall.rems(cx))
75    }
76
77    /// The font size for buffer text.
78    ///
79    /// Retrieves the default font size, or the user's custom font size if set.
80    ///
81    /// This should only be used for text that is displayed in a buffer,
82    /// or other places that text needs to match the user's buffer font size.
83    fn text_buffer(self, cx: &App) -> Self {
84        let settings = theme::theme_settings(cx);
85        self.text_size(settings.buffer_font_size(cx))
86    }
87}
88
89impl<E: Styled> StyledTypography for E {}
90
91/// A utility for getting the size of various semantic text sizes.
92#[derive(Debug, Default, Clone)]
93pub enum TextSize {
94    /// The default size for UI text.
95    ///
96    /// `0.825rem` or `14px` at the default scale of `1rem` = `16px`.
97    ///
98    /// Note: The absolute size of this text will change based on a user's `ui_scale` setting.
99    #[default]
100    Default,
101    /// The large size for UI text.
102    ///
103    /// `1rem` or `16px` at the default scale of `1rem` = `16px`.
104    ///
105    /// Note: The absolute size of this text will change based on a user's `ui_scale` setting.
106    Large,
107
108    /// The small size for UI text.
109    ///
110    /// `0.75rem` or `12px` at the default scale of `1rem` = `16px`.
111    ///
112    /// Note: The absolute size of this text will change based on a user's `ui_scale` setting.
113    Small,
114
115    /// The extra small size for UI text.
116    ///
117    /// `0.625rem` or `10px` at the default scale of `1rem` = `16px`.
118    ///
119    /// Note: The absolute size of this text will change based on a user's `ui_scale` setting.
120    XSmall,
121
122    /// The `ui_font_size` set by the user.
123    Ui,
124    /// The `buffer_font_size` set by the user.
125    Editor,
126    // TODO: The terminal settings will need to be passed to
127    // ThemeSettings before we can enable this.
128    //// The `terminal.font_size` set by the user.
129    // Terminal,
130}
131
132impl TextSize {
133    /// Returns the text size in rems.
134    pub fn rems(self, cx: &App) -> Rems {
135        let settings = theme::theme_settings(cx);
136
137        match self {
138            Self::Large => rems_from_px(16.),
139            Self::Default => rems_from_px(14.),
140            Self::Small => rems_from_px(12.),
141            Self::XSmall => rems_from_px(10.),
142            Self::Ui => rems_from_px(settings.ui_font_size(cx)),
143            Self::Editor => rems_from_px(settings.buffer_font_size(cx)),
144        }
145    }
146
147    pub fn pixels(self, cx: &App) -> Pixels {
148        let settings = theme::theme_settings(cx);
149
150        match self {
151            Self::Large => px(16.),
152            Self::Default => px(14.),
153            Self::Small => px(12.),
154            Self::XSmall => px(10.),
155            Self::Ui => settings.ui_font_size(cx),
156            Self::Editor => settings.buffer_font_size(cx),
157        }
158    }
159}
160
161/// The size of a [`Headline`] element
162///
163/// Defaults to a Major Second scale.
164#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy, Default)]
165pub enum HeadlineSize {
166    /// An extra small headline - `~14px` @16px/rem
167    XSmall,
168    /// A small headline - `16px` @16px/rem
169    Small,
170    #[default]
171    /// A medium headline - `~18px` @16px/rem
172    Medium,
173    /// A large headline - `~20px` @16px/rem
174    Large,
175    /// An extra large headline - `~22px` @16px/rem
176    XLarge,
177}
178
179impl HeadlineSize {
180    /// Returns the headline size in rems.
181    pub fn rems(self) -> Rems {
182        match self {
183            Self::XSmall => rems(0.88),
184            Self::Small => rems(1.0),
185            Self::Medium => rems(1.125),
186            Self::Large => rems(1.27),
187            Self::XLarge => rems(1.43),
188        }
189    }
190
191    /// Returns the line height for the headline size.
192    pub fn line_height(self) -> Rems {
193        match self {
194            Self::XSmall => rems(1.6),
195            Self::Small => rems(1.6),
196            Self::Medium => rems(1.6),
197            Self::Large => rems(1.6),
198            Self::XLarge => rems(1.6),
199        }
200    }
201}
202
203/// A headline element, used to emphasize some text and
204/// create a visual hierarchy.
205#[derive(IntoElement, RegisterComponent)]
206pub struct Headline {
207    size: HeadlineSize,
208    text: SharedString,
209    color: Color,
210}
211
212impl RenderOnce for Headline {
213    fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
214        let ui_font = theme::theme_settings(cx).ui_font(cx).clone();
215
216        div()
217            .font(ui_font)
218            .line_height(self.size.line_height())
219            .text_size(self.size.rems())
220            .text_color(cx.theme().colors().text)
221            .child(self.text)
222    }
223}
224
225impl Headline {
226    /// Create a new headline element.
227    pub fn new(text: impl Into<SharedString>) -> Self {
228        Self {
229            size: HeadlineSize::default(),
230            text: text.into(),
231            color: Color::default(),
232        }
233    }
234
235    /// Set the size of the headline.
236    pub fn size(mut self, size: HeadlineSize) -> Self {
237        self.size = size;
238        self
239    }
240
241    /// Set the color of the headline.
242    pub fn color(mut self, color: Color) -> Self {
243        self.color = color;
244        self
245    }
246}
247
248impl Component for Headline {
249    fn scope() -> ComponentScope {
250        ComponentScope::Typography
251    }
252
253    fn description() -> Option<&'static str> {
254        Some("A headline element used to emphasize text and create visual hierarchy in the UI.")
255    }
256
257    fn preview(_window: &mut Window, _cx: &mut App) -> Option<AnyElement> {
258        Some(
259            v_flex()
260                .gap_1()
261                .children(vec![
262                    single_example(
263                        "XLarge",
264                        Headline::new("XLarge Headline")
265                            .size(HeadlineSize::XLarge)
266                            .into_any_element(),
267                    ),
268                    single_example(
269                        "Large",
270                        Headline::new("Large Headline")
271                            .size(HeadlineSize::Large)
272                            .into_any_element(),
273                    ),
274                    single_example(
275                        "Medium (Default)",
276                        Headline::new("Medium Headline").into_any_element(),
277                    ),
278                    single_example(
279                        "Small",
280                        Headline::new("Small Headline")
281                            .size(HeadlineSize::Small)
282                            .into_any_element(),
283                    ),
284                    single_example(
285                        "XSmall",
286                        Headline::new("XSmall Headline")
287                            .size(HeadlineSize::XSmall)
288                            .into_any_element(),
289                    ),
290                ])
291                .into_any_element(),
292        )
293    }
294}