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}