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
149/// The size of a [`Headline`] element
150///
151/// Defaults to a Major Second scale.
152#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy, Default)]
153pub enum HeadlineSize {
154    /// An extra small headline - `~14px` @16px/rem
155    XSmall,
156    /// A small headline - `16px` @16px/rem
157    Small,
158    #[default]
159    /// A medium headline - `~18px` @16px/rem
160    Medium,
161    /// A large headline - `~20px` @16px/rem
162    Large,
163    /// An extra large headline - `~22px` @16px/rem
164    XLarge,
165}
166
167impl HeadlineSize {
168    /// Returns the headline size in rems.
169    pub const fn rems(self) -> Rems {
170        match self {
171            Self::XSmall => rems(0.88),
172            Self::Small => rems(1.0),
173            Self::Medium => rems(1.125),
174            Self::Large => rems(1.27),
175            Self::XLarge => rems(1.43),
176        }
177    }
178
179    /// Returns the line height for the headline size.
180    pub const fn line_height(self) -> Rems {
181        match self {
182            Self::XSmall => rems(1.6),
183            Self::Small => rems(1.6),
184            Self::Medium => rems(1.6),
185            Self::Large => rems(1.6),
186            Self::XLarge => rems(1.6),
187        }
188    }
189}
190
191/// A headline element, used to emphasize some text and
192/// create a visual hierarchy.
193#[derive(IntoElement, RegisterComponent)]
194pub struct Headline {
195    size: HeadlineSize,
196    text: SharedString,
197    color: Color,
198}
199
200impl RenderOnce for Headline {
201    fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
202        let ui_font = ThemeSettings::get_global(cx).ui_font.clone();
203
204        div()
205            .font(ui_font)
206            .line_height(self.size.line_height())
207            .text_size(self.size.rems())
208            .text_color(cx.theme().colors().text)
209            .child(self.text)
210    }
211}
212
213impl Headline {
214    /// Create a new headline element.
215    pub fn new(text: impl Into<SharedString>) -> Self {
216        Self {
217            size: HeadlineSize::default(),
218            text: text.into(),
219            color: Color::default(),
220        }
221    }
222
223    /// Set the size of the headline.
224    pub const fn size(mut self, size: HeadlineSize) -> Self {
225        self.size = size;
226        self
227    }
228
229    /// Set the color of the headline.
230    pub const fn color(mut self, color: Color) -> Self {
231        self.color = color;
232        self
233    }
234}
235
236impl Component for Headline {
237    fn scope() -> ComponentScope {
238        ComponentScope::Typography
239    }
240
241    fn description() -> Option<&'static str> {
242        Some("A headline element used to emphasize text and create visual hierarchy in the UI.")
243    }
244
245    fn preview(_window: &mut Window, _cx: &mut App) -> Option<AnyElement> {
246        Some(
247            v_flex()
248                .gap_1()
249                .children(vec![
250                    single_example(
251                        "XLarge",
252                        Headline::new("XLarge Headline")
253                            .size(HeadlineSize::XLarge)
254                            .into_any_element(),
255                    ),
256                    single_example(
257                        "Large",
258                        Headline::new("Large Headline")
259                            .size(HeadlineSize::Large)
260                            .into_any_element(),
261                    ),
262                    single_example(
263                        "Medium (Default)",
264                        Headline::new("Medium Headline").into_any_element(),
265                    ),
266                    single_example(
267                        "Small",
268                        Headline::new("Small Headline")
269                            .size(HeadlineSize::Small)
270                            .into_any_element(),
271                    ),
272                    single_example(
273                        "XSmall",
274                        Headline::new("XSmall Headline")
275                            .size(HeadlineSize::XSmall)
276                            .into_any_element(),
277                    ),
278                ])
279                .into_any_element(),
280        )
281    }
282}