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