1use std::{
2 ops::{Deref, DerefMut},
3 sync::Arc,
4};
5
6use gpui::{
7 AbsoluteLength, App, Context, DefiniteLength, ElementId, Global, Hsla, Menu, SharedString,
8 TextStyle, TitlebarOptions, Window, WindowBounds, WindowOptions, bounds, colors::DefaultColors,
9 div, point, prelude::*, px, relative, rgb, size,
10};
11use gpui_platform::application;
12use std::iter;
13
14#[derive(Clone, Debug)]
15pub struct TextContext {
16 font_size: f32,
17 line_height: f32,
18 type_scale: f32,
19}
20
21impl Default for TextContext {
22 fn default() -> Self {
23 TextContext {
24 font_size: 16.0,
25 line_height: 1.3,
26 type_scale: 1.33,
27 }
28 }
29}
30
31impl TextContext {
32 pub fn get_global(cx: &App) -> &Arc<TextContext> {
33 &cx.global::<GlobalTextContext>().0
34 }
35}
36
37#[derive(Clone, Debug)]
38pub struct GlobalTextContext(pub Arc<TextContext>);
39
40impl Deref for GlobalTextContext {
41 type Target = Arc<TextContext>;
42
43 fn deref(&self) -> &Self::Target {
44 &self.0
45 }
46}
47
48impl DerefMut for GlobalTextContext {
49 fn deref_mut(&mut self) -> &mut Self::Target {
50 &mut self.0
51 }
52}
53
54impl Global for GlobalTextContext {}
55
56pub trait ActiveTextContext {
57 fn text_context(&self) -> &Arc<TextContext>;
58}
59
60impl ActiveTextContext for App {
61 fn text_context(&self) -> &Arc<TextContext> {
62 &self.global::<GlobalTextContext>().0
63 }
64}
65
66#[derive(Clone, PartialEq)]
67pub struct SpecimenTheme {
68 pub bg: Hsla,
69 pub fg: Hsla,
70}
71
72impl Default for SpecimenTheme {
73 fn default() -> Self {
74 Self {
75 bg: gpui::white(),
76 fg: gpui::black(),
77 }
78 }
79}
80
81impl SpecimenTheme {
82 pub fn invert(&self) -> Self {
83 Self {
84 bg: self.fg,
85 fg: self.bg,
86 }
87 }
88}
89
90#[derive(Debug, Clone, PartialEq, IntoElement)]
91struct Specimen {
92 id: ElementId,
93 scale: f32,
94 text_style: Option<TextStyle>,
95 string: SharedString,
96 invert: bool,
97}
98
99impl Specimen {
100 pub fn new(id: usize) -> Self {
101 let string = SharedString::new_static("The quick brown fox jumps over the lazy dog");
102 let id_string = format!("specimen-{}", id);
103 let id = ElementId::Name(id_string.into());
104 Self {
105 id,
106 scale: 1.0,
107 text_style: None,
108 string,
109 invert: false,
110 }
111 }
112
113 pub fn invert(mut self) -> Self {
114 self.invert = !self.invert;
115 self
116 }
117
118 pub fn scale(mut self, scale: f32) -> Self {
119 self.scale = scale;
120 self
121 }
122}
123
124impl RenderOnce for Specimen {
125 fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement {
126 let rem_size = window.rem_size();
127 let scale = self.scale;
128 let global_style = cx.text_context();
129
130 let style_override = self.text_style;
131
132 let mut font_size = global_style.font_size;
133 let mut line_height = global_style.line_height;
134
135 if let Some(style_override) = style_override {
136 font_size = style_override.font_size.to_pixels(rem_size).into();
137 line_height = match style_override.line_height {
138 DefiniteLength::Absolute(absolute_len) => match absolute_len {
139 AbsoluteLength::Rems(absolute_len) => absolute_len.to_pixels(rem_size).into(),
140 AbsoluteLength::Pixels(absolute_len) => absolute_len.into(),
141 },
142 DefiniteLength::Fraction(value) => value,
143 };
144 }
145
146 let mut theme = SpecimenTheme::default();
147
148 if self.invert {
149 theme = theme.invert();
150 }
151
152 div()
153 .id(self.id)
154 .bg(theme.bg)
155 .text_color(theme.fg)
156 .text_size(px(font_size * scale))
157 .line_height(relative(line_height))
158 .p(px(10.0))
159 .child(self.string)
160 }
161}
162
163#[derive(Debug, Clone, PartialEq, IntoElement)]
164struct CharacterGrid {
165 scale: f32,
166 invert: bool,
167 text_style: Option<TextStyle>,
168}
169
170impl CharacterGrid {
171 pub fn new() -> Self {
172 Self {
173 scale: 1.0,
174 invert: false,
175 text_style: None,
176 }
177 }
178
179 pub fn scale(mut self, scale: f32) -> Self {
180 self.scale = scale;
181 self
182 }
183}
184
185impl RenderOnce for CharacterGrid {
186 fn render(self, _window: &mut Window, _cx: &mut App) -> impl IntoElement {
187 let mut theme = SpecimenTheme::default();
188
189 if self.invert {
190 theme = theme.invert();
191 }
192
193 let characters = vec![
194 "1", "2", "3", "4", "5", "6", "7", "8", "9", "0", "A", "B", "C", "D", "E", "F", "G",
195 "H", "I", "J", "K", "L", "M", "N", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y",
196 "Z", "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "p", "q",
197 "r", "s", "t", "u", "v", "w", "x", "y", "z", "ẞ", "ſ", "ß", "ð", "Þ", "þ", "α", "β",
198 "Γ", "γ", "Δ", "δ", "η", "θ", "ι", "κ", "Λ", "λ", "μ", "ν", "ξ", "π", "τ", "υ", "φ",
199 "χ", "ψ", "∂", "а", "в", "Ж", "ж", "З", "з", "К", "к", "л", "м", "Н", "н", "Р", "р",
200 "У", "у", "ф", "ч", "ь", "ы", "Э", "э", "Я", "я", "ij", "öẋ", ".,", "⣝⣑", "~", "*",
201 "_", "^", "`", "'", "(", "{", "«", "#", "&", "@", "$", "¢", "%", "|", "?", "¶", "µ",
202 "❮", "<=", "!=", "==", "--", "++", "=>", "->", "🏀", "🎊", "😍", "❤️", "👍", "👎",
203 ];
204
205 let columns = 11;
206 let rows = characters.len().div_ceil(columns);
207
208 let grid_rows = (0..rows).map(|row_idx| {
209 let start_idx = row_idx * columns;
210 let end_idx = (start_idx + columns).min(characters.len());
211
212 div()
213 .w_full()
214 .flex()
215 .flex_row()
216 .children((start_idx..end_idx).map(|i| {
217 div()
218 .text_center()
219 .size(px(62.))
220 .bg(theme.bg)
221 .text_color(theme.fg)
222 .text_size(px(24.0))
223 .line_height(relative(1.0))
224 .child(characters[i])
225 }))
226 .when(end_idx - start_idx < columns, |d| {
227 d.children(
228 iter::repeat_with(|| div().flex_1()).take(columns - (end_idx - start_idx)),
229 )
230 })
231 });
232
233 div().p_4().gap_2().flex().flex_col().children(grid_rows)
234 }
235}
236
237struct TextExample {
238 next_id: usize,
239}
240
241impl TextExample {
242 fn next_id(&mut self) -> usize {
243 self.next_id += 1;
244 self.next_id
245 }
246}
247
248impl Render for TextExample {
249 fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
250 let tcx = cx.text_context();
251 let colors = cx.default_colors().clone();
252
253 let type_scale = tcx.type_scale;
254
255 let step_down_2 = 1.0 / (type_scale * type_scale);
256 let step_down_1 = 1.0 / type_scale;
257 let base = 1.0;
258 let step_up_1 = base * type_scale;
259 let step_up_2 = step_up_1 * type_scale;
260 let step_up_3 = step_up_2 * type_scale;
261 let step_up_4 = step_up_3 * type_scale;
262 let step_up_5 = step_up_4 * type_scale;
263 let step_up_6 = step_up_5 * type_scale;
264
265 div()
266 .size_full()
267 .child(
268 div()
269 .id("text-example")
270 .overflow_y_scroll()
271 .overflow_x_hidden()
272 .bg(rgb(0xffffff))
273 .size_full()
274 .child(div().child(CharacterGrid::new().scale(base)))
275 .child(
276 div()
277 .child(Specimen::new(self.next_id()).scale(step_down_2))
278 .child(Specimen::new(self.next_id()).scale(step_down_2).invert())
279 .child(Specimen::new(self.next_id()).scale(step_down_1))
280 .child(Specimen::new(self.next_id()).scale(step_down_1).invert())
281 .child(Specimen::new(self.next_id()).scale(base))
282 .child(Specimen::new(self.next_id()).scale(base).invert())
283 .child(Specimen::new(self.next_id()).scale(step_up_1))
284 .child(Specimen::new(self.next_id()).scale(step_up_1).invert())
285 .child(Specimen::new(self.next_id()).scale(step_up_2))
286 .child(Specimen::new(self.next_id()).scale(step_up_2).invert())
287 .child(Specimen::new(self.next_id()).scale(step_up_3))
288 .child(Specimen::new(self.next_id()).scale(step_up_3).invert())
289 .child(Specimen::new(self.next_id()).scale(step_up_4))
290 .child(Specimen::new(self.next_id()).scale(step_up_4).invert())
291 .child(Specimen::new(self.next_id()).scale(step_up_5))
292 .child(Specimen::new(self.next_id()).scale(step_up_5).invert())
293 .child(Specimen::new(self.next_id()).scale(step_up_6))
294 .child(Specimen::new(self.next_id()).scale(step_up_6).invert()),
295 ),
296 )
297 .child(div().w(px(240.)).h_full().bg(colors.container))
298 }
299}
300
301fn main() {
302 application().run(|cx: &mut App| {
303 cx.set_menus(vec![Menu {
304 name: "GPUI Typography".into(),
305 items: vec![],
306 }]);
307
308 cx.init_colors();
309 cx.set_global(GlobalTextContext(Arc::new(TextContext::default())));
310
311 let window = cx
312 .open_window(
313 WindowOptions {
314 titlebar: Some(TitlebarOptions {
315 title: Some("GPUI Typography".into()),
316 ..Default::default()
317 }),
318 window_bounds: Some(WindowBounds::Windowed(bounds(
319 point(px(0.0), px(0.0)),
320 size(px(920.), px(720.)),
321 ))),
322 ..Default::default()
323 },
324 |_window, cx| cx.new(|_cx| TextExample { next_id: 0 }),
325 )
326 .unwrap();
327
328 window
329 .update(cx, |_view, _window, cx| {
330 cx.activate(true);
331 })
332 .unwrap();
333 });
334}