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