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}