text.rs

  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}