1use std::{
2 ops::{Deref, DerefMut},
3 sync::Arc,
4};
5
6use gpui::{
7 AbsoluteLength, App, Application, Context, DefiniteLength, ElementId, Global, Hsla, Menu,
8 SharedString, TextStyle, TitlebarOptions, Window, WindowBounds, WindowOptions, bounds,
9 colors::DefaultColors, div, point, prelude::*, px, relative, rgb, size,
10};
11use std::iter;
12
13#[derive(Clone, Debug)]
14pub struct TextContext {
15 font_size: f32,
16 line_height: f32,
17 type_scale: f32,
18}
19
20impl Default for TextContext {
21 fn default() -> Self {
22 TextContext {
23 font_size: 16.0,
24 line_height: 1.3,
25 type_scale: 1.33,
26 }
27 }
28}
29
30impl TextContext {
31 pub fn get_global(cx: &App) -> &Arc<TextContext> {
32 &cx.global::<GlobalTextContext>().0
33 }
34}
35
36#[derive(Clone, Debug)]
37pub struct GlobalTextContext(pub Arc<TextContext>);
38
39impl Deref for GlobalTextContext {
40 type Target = Arc<TextContext>;
41
42 fn deref(&self) -> &Self::Target {
43 &self.0
44 }
45}
46
47impl DerefMut for GlobalTextContext {
48 fn deref_mut(&mut self) -> &mut Self::Target {
49 &mut self.0
50 }
51}
52
53impl Global for GlobalTextContext {}
54
55pub trait ActiveTextContext {
56 fn text_context(&self) -> &Arc<TextContext>;
57}
58
59impl ActiveTextContext for App {
60 fn text_context(&self) -> &Arc<TextContext> {
61 &self.global::<GlobalTextContext>().0
62 }
63}
64
65#[derive(Clone, PartialEq)]
66pub struct SpecimenTheme {
67 pub bg: Hsla,
68 pub fg: Hsla,
69}
70
71impl Default for SpecimenTheme {
72 fn default() -> Self {
73 Self {
74 bg: gpui::white(),
75 fg: gpui::black(),
76 }
77 }
78}
79
80impl SpecimenTheme {
81 pub fn invert(&self) -> Self {
82 Self {
83 bg: self.fg,
84 fg: self.bg,
85 }
86 }
87}
88
89#[derive(Debug, Clone, PartialEq, IntoElement)]
90struct Specimen {
91 id: ElementId,
92 scale: f32,
93 text_style: Option<TextStyle>,
94 string: SharedString,
95 invert: bool,
96}
97
98impl Specimen {
99 pub fn new(id: usize) -> Self {
100 let string = SharedString::new_static("many thihngs could be written here");
101 let id_string = format!("specimen-{}", id);
102 let id = ElementId::Name(id_string.into());
103 Self {
104 id,
105 scale: 1.0,
106 text_style: None,
107 string,
108 invert: false,
109 }
110 }
111
112 pub fn invert(mut self) -> Self {
113 self.invert = !self.invert;
114 self
115 }
116
117 pub fn scale(mut self, scale: f32) -> Self {
118 self.scale = scale;
119 self
120 }
121}
122
123impl RenderOnce for Specimen {
124 fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement {
125 let rem_size = window.rem_size();
126 let scale = self.scale;
127 let global_style = cx.text_context();
128
129 let style_override = self.text_style;
130
131 let mut font_size = global_style.font_size;
132 let mut line_height = global_style.line_height;
133
134 if let Some(style_override) = style_override {
135 font_size = style_override.font_size.to_pixels(rem_size).0;
136 line_height = match style_override.line_height {
137 DefiniteLength::Absolute(absolute_len) => match absolute_len {
138 AbsoluteLength::Rems(absolute_len) => absolute_len.to_pixels(rem_size).0,
139 AbsoluteLength::Pixels(absolute_len) => absolute_len.0,
140 },
141 DefiniteLength::Fraction(value) => value,
142 };
143 }
144
145 let mut theme = SpecimenTheme::default();
146
147 if self.invert {
148 theme = theme.invert();
149 }
150
151 div()
152 .id(self.id)
153 .bg(theme.bg)
154 .font_family("Zed Plex Mono")
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.clone())
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::new().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}