text.rs

  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("The quick brown fox jumps over the lazy dog");
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).into();
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).into(),
139                    AbsoluteLength::Pixels(absolute_len) => absolute_len.into(),
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            .text_color(theme.fg)
155            .text_size(px(font_size * scale))
156            .line_height(relative(line_height))
157            .p(px(10.0))
158            .child(self.string)
159    }
160}
161
162#[derive(Debug, Clone, PartialEq, IntoElement)]
163struct CharacterGrid {
164    scale: f32,
165    invert: bool,
166    text_style: Option<TextStyle>,
167}
168
169impl CharacterGrid {
170    pub fn new() -> Self {
171        Self {
172            scale: 1.0,
173            invert: false,
174            text_style: None,
175        }
176    }
177
178    pub fn scale(mut self, scale: f32) -> Self {
179        self.scale = scale;
180        self
181    }
182}
183
184impl RenderOnce for CharacterGrid {
185    fn render(self, _window: &mut Window, _cx: &mut App) -> impl IntoElement {
186        let mut theme = SpecimenTheme::default();
187
188        if self.invert {
189            theme = theme.invert();
190        }
191
192        let characters = vec![
193            "1", "2", "3", "4", "5", "6", "7", "8", "9", "0", "A", "B", "C", "D", "E", "F", "G",
194            "H", "I", "J", "K", "L", "M", "N", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y",
195            "Z", "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "p", "q",
196            "r", "s", "t", "u", "v", "w", "x", "y", "z", "", "ſ", "ß", "ð", "Þ", "þ", "α", "β",
197            "Γ", "γ", "Δ", "δ", "η", "θ", "ι", "κ", "Λ", "λ", "μ", "ν", "ξ", "π", "τ", "υ", "φ",
198            "χ", "ψ", "", "а", "в", "Ж", "ж", "З", "з", "К", "к", "л", "м", "Н", "н", "Р", "р",
199            "У", "у", "ф", "ч", "ь", "ы", "Э", "э", "Я", "я", "ij", "öẋ", ".,", "⣝⣑", "~", "*",
200            "_", "^", "`", "'", "(", "{", "«", "#", "&", "@", "$", "¢", "%", "|", "?", "", "µ",
201            "", "<=", "!=", "==", "--", "++", "=>", "->", "🏀", "🎊", "😍", "❤️", "👍", "👎",
202        ];
203
204        let columns = 11;
205        let rows = characters.len().div_ceil(columns);
206
207        let grid_rows = (0..rows).map(|row_idx| {
208            let start_idx = row_idx * columns;
209            let end_idx = (start_idx + columns).min(characters.len());
210
211            div()
212                .w_full()
213                .flex()
214                .flex_row()
215                .children((start_idx..end_idx).map(|i| {
216                    div()
217                        .text_center()
218                        .size(px(62.))
219                        .bg(theme.bg)
220                        .text_color(theme.fg)
221                        .text_size(px(24.0))
222                        .line_height(relative(1.0))
223                        .child(characters[i])
224                }))
225                .when(end_idx - start_idx < columns, |d| {
226                    d.children(
227                        iter::repeat_with(|| div().flex_1()).take(columns - (end_idx - start_idx)),
228                    )
229                })
230        });
231
232        div().p_4().gap_2().flex().flex_col().children(grid_rows)
233    }
234}
235
236struct TextExample {
237    next_id: usize,
238}
239
240impl TextExample {
241    fn next_id(&mut self) -> usize {
242        self.next_id += 1;
243        self.next_id
244    }
245}
246
247impl Render for TextExample {
248    fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
249        let tcx = cx.text_context();
250        let colors = cx.default_colors().clone();
251
252        let type_scale = tcx.type_scale;
253
254        let step_down_2 = 1.0 / (type_scale * type_scale);
255        let step_down_1 = 1.0 / type_scale;
256        let base = 1.0;
257        let step_up_1 = base * type_scale;
258        let step_up_2 = step_up_1 * type_scale;
259        let step_up_3 = step_up_2 * type_scale;
260        let step_up_4 = step_up_3 * type_scale;
261        let step_up_5 = step_up_4 * type_scale;
262        let step_up_6 = step_up_5 * type_scale;
263
264        div()
265            .size_full()
266            .child(
267                div()
268                    .id("text-example")
269                    .overflow_y_scroll()
270                    .overflow_x_hidden()
271                    .bg(rgb(0xffffff))
272                    .size_full()
273                    .child(div().child(CharacterGrid::new().scale(base)))
274                    .child(
275                        div()
276                            .child(Specimen::new(self.next_id()).scale(step_down_2))
277                            .child(Specimen::new(self.next_id()).scale(step_down_2).invert())
278                            .child(Specimen::new(self.next_id()).scale(step_down_1))
279                            .child(Specimen::new(self.next_id()).scale(step_down_1).invert())
280                            .child(Specimen::new(self.next_id()).scale(base))
281                            .child(Specimen::new(self.next_id()).scale(base).invert())
282                            .child(Specimen::new(self.next_id()).scale(step_up_1))
283                            .child(Specimen::new(self.next_id()).scale(step_up_1).invert())
284                            .child(Specimen::new(self.next_id()).scale(step_up_2))
285                            .child(Specimen::new(self.next_id()).scale(step_up_2).invert())
286                            .child(Specimen::new(self.next_id()).scale(step_up_3))
287                            .child(Specimen::new(self.next_id()).scale(step_up_3).invert())
288                            .child(Specimen::new(self.next_id()).scale(step_up_4))
289                            .child(Specimen::new(self.next_id()).scale(step_up_4).invert())
290                            .child(Specimen::new(self.next_id()).scale(step_up_5))
291                            .child(Specimen::new(self.next_id()).scale(step_up_5).invert())
292                            .child(Specimen::new(self.next_id()).scale(step_up_6))
293                            .child(Specimen::new(self.next_id()).scale(step_up_6).invert()),
294                    ),
295            )
296            .child(div().w(px(240.)).h_full().bg(colors.container))
297    }
298}
299
300fn main() {
301    Application::new().run(|cx: &mut App| {
302        cx.set_menus(vec![Menu {
303            name: "GPUI Typography".into(),
304            items: vec![],
305        }]);
306
307        cx.init_colors();
308        cx.set_global(GlobalTextContext(Arc::new(TextContext::default())));
309
310        let window = cx
311            .open_window(
312                WindowOptions {
313                    titlebar: Some(TitlebarOptions {
314                        title: Some("GPUI Typography".into()),
315                        ..Default::default()
316                    }),
317                    window_bounds: Some(WindowBounds::Windowed(bounds(
318                        point(px(0.0), px(0.0)),
319                        size(px(920.), px(720.)),
320                    ))),
321                    ..Default::default()
322                },
323                |_window, cx| cx.new(|_cx| TextExample { next_id: 0 }),
324            )
325            .unwrap();
326
327        window
328            .update(cx, |_view, _window, cx| {
329                cx.activate(true);
330            })
331            .unwrap();
332    });
333}