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}