text.rs

  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}