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("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}