From ab1c37a4efb08aeeebe1b890e2b4c224e5a98384 Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Mon, 31 Aug 2026 13:07:22 +0000 Subject: [PATCH] gpui_web: Let applications provide fonts (#63493) `gpui_web` currently embeds IBM Plex Sans and Lilex into every browser application during platform construction. Applications such as Delta already provide their own fonts, so those binaries contain overlapping font bundles and register the same faces twice. This change starts the web platform with an empty font database, matching the ownership boundary used by native applications: the application selects and registers its fonts before opening a window. Delta already follows that sequence. GPUI's browser-capable examples now load the former eight-face bundle explicitly through shared example support, while the standalone `hello_web` example demonstrates registering a minimal application font directly. Testing performed: - `cargo check -p gpui --examples --target wasm32-unknown-unknown` - `cargo check -p gpui --examples` - `cargo check -p gpui_web --target wasm32-unknown-unknown` - `cargo check --manifest-path crates/gpui_web/examples/hello_web/Cargo.toml --target wasm32-unknown-unknown` - `./script/clippy -p gpui_web --target wasm32-unknown-unknown` - `cargo fmt -p gpui -p gpui_web --check` Release Notes: - N/A --- Cargo.lock | 1 + crates/gpui/Cargo.toml | 1 + crates/gpui/examples/a11y.rs | 6 +++ crates/gpui/examples/anchor.rs | 6 +++ crates/gpui/examples/animation.rs | 6 +++ crates/gpui/examples/data_table.rs | 6 +++ crates/gpui/examples/drag_drop.rs | 6 +++ crates/gpui/examples/example_support/fonts.rs | 44 +++++++++++++++++++ crates/gpui/examples/focus_visible.rs | 6 +++ crates/gpui/examples/gif_viewer.rs | 6 +++ crates/gpui/examples/gradient.rs | 6 +++ crates/gpui/examples/grid_layout.rs | 6 +++ crates/gpui/examples/hello_world.rs | 6 +++ crates/gpui/examples/image/image.rs | 6 +++ crates/gpui/examples/image_gallery.rs | 6 +++ crates/gpui/examples/image_loading.rs | 6 +++ crates/gpui/examples/input.rs | 6 +++ crates/gpui/examples/list_example.rs | 6 +++ crates/gpui/examples/mouse_pressure.rs | 6 +++ .../examples/move_entity_between_windows.rs | 6 +++ crates/gpui/examples/on_window_close_quit.rs | 6 +++ crates/gpui/examples/opacity.rs | 6 +++ crates/gpui/examples/ownership_post.rs | 6 +++ crates/gpui/examples/painting.rs | 6 +++ crates/gpui/examples/paths_bench.rs | 6 +++ crates/gpui/examples/pattern.rs | 6 +++ crates/gpui/examples/popover.rs | 6 +++ crates/gpui/examples/scrollable.rs | 6 +++ crates/gpui/examples/set_menus.rs | 6 +++ crates/gpui/examples/shadow.rs | 6 +++ crates/gpui/examples/svg/svg.rs | 6 +++ crates/gpui/examples/system_notifications.rs | 6 +++ crates/gpui/examples/tab_stop.rs | 6 +++ crates/gpui/examples/testing.rs | 7 +++ crates/gpui/examples/text.rs | 6 +++ crates/gpui/examples/text_layout.rs | 6 +++ crates/gpui/examples/text_wrapper.rs | 6 +++ crates/gpui/examples/tree.rs | 7 +++ crates/gpui/examples/uniform_list.rs | 6 +++ .../view_example/view_example_main.rs | 6 +++ crates/gpui/examples/window.rs | 6 +++ crates/gpui/examples/window_movable.rs | 6 +++ crates/gpui/examples/window_positioning.rs | 6 +++ crates/gpui/examples/window_shadow.rs | 6 +++ crates/gpui_web/examples/hello_web/Cargo.toml | 2 +- crates/gpui_web/examples/hello_web/main.rs | 13 ++++++ crates/gpui_web/src/platform.rs | 23 ++-------- 47 files changed, 312 insertions(+), 20 deletions(-) create mode 100644 crates/gpui/examples/example_support/fonts.rs diff --git a/Cargo.lock b/Cargo.lock index 386fac6f3d..22e0de3845 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7573,6 +7573,7 @@ dependencies = [ "uuid", "waker-fn", "wasm-bindgen", + "web-sys", "web-time", "windows 0.61.3", "zed-font-kit", diff --git a/crates/gpui/Cargo.toml b/crates/gpui/Cargo.toml index 86d1460c93..025107fe53 100644 --- a/crates/gpui/Cargo.toml +++ b/crates/gpui/Cargo.toml @@ -149,6 +149,7 @@ reqwest_client = { workspace = true, features = ["test-support"] } [target.'cfg(target_family = "wasm")'.dev-dependencies] wasm-bindgen = { workspace = true } +web-sys = { version = "0.3", features = ["console"] } [build-dependencies] embed-resource = { version = "3.0", optional = true } diff --git a/crates/gpui/examples/a11y.rs b/crates/gpui/examples/a11y.rs index 6e37354ae8..ff389f330c 100644 --- a/crates/gpui/examples/a11y.rs +++ b/crates/gpui/examples/a11y.rs @@ -30,6 +30,9 @@ //! - "2. Run tests" //! - "3. Ship it" +#[path = "example_support/fonts.rs"] +mod example_support; + use gpui::{ AccessibleAction, App, Bounds, Context, FocusHandle, KeyBinding, Role, SharedString, Toggled, Window, WindowBounds, WindowOptions, actions, div, prelude::*, px, rgb, size, text, @@ -226,6 +229,9 @@ impl Render for A11yDemo { fn run_example() { application().run(|cx: &mut App| { + if !example_support::load_fonts(cx) { + return; + } cx.bind_keys([ KeyBinding::new("tab", Tab, None), KeyBinding::new("shift-tab", TabPrev, None), diff --git a/crates/gpui/examples/anchor.rs b/crates/gpui/examples/anchor.rs index 7aa9bb4cf9..b4b280b714 100644 --- a/crates/gpui/examples/anchor.rs +++ b/crates/gpui/examples/anchor.rs @@ -1,5 +1,8 @@ #![cfg_attr(target_family = "wasm", no_main)] +#[path = "example_support/fonts.rs"] +mod example_support; + use gpui::{ Anchor, AnchoredPositionMode, App, Axis, Bounds, Context, Half as _, InteractiveElement, ParentElement, Pixels, Point, Render, SharedString, Size, Window, WindowBounds, WindowOptions, @@ -168,6 +171,9 @@ impl Render for AnchorDemo { fn run_example() { application().run(|cx: &mut App| { + if !example_support::load_fonts(cx) { + return; + } cx.open_window( WindowOptions { window_bounds: Some(WindowBounds::Windowed(Bounds::centered( diff --git a/crates/gpui/examples/animation.rs b/crates/gpui/examples/animation.rs index 4e09d15c41..e055b7da47 100644 --- a/crates/gpui/examples/animation.rs +++ b/crates/gpui/examples/animation.rs @@ -1,5 +1,8 @@ #![cfg_attr(target_family = "wasm", no_main)] +#[path = "example_support/fonts.rs"] +mod example_support; + use std::time::Duration; use anyhow::Result; @@ -306,6 +309,9 @@ impl Render for AnimationExample { fn run_example() { application().with_assets(Assets {}).run(|cx: &mut App| { + if !example_support::load_fonts(cx) { + return; + } let options = WindowOptions { window_bounds: Some(WindowBounds::Windowed(Bounds::centered( None, diff --git a/crates/gpui/examples/data_table.rs b/crates/gpui/examples/data_table.rs index b3f8737ec4..2bee47634d 100644 --- a/crates/gpui/examples/data_table.rs +++ b/crates/gpui/examples/data_table.rs @@ -1,5 +1,8 @@ #![cfg_attr(target_family = "wasm", no_main)] +#[path = "example_support/fonts.rs"] +mod example_support; + use std::{ops::Range, rc::Rc, time::Duration}; use gpui::{ @@ -451,6 +454,9 @@ impl Render for DataTable { fn run_example() { application().run(|cx: &mut App| { + if !example_support::load_fonts(cx) { + return; + } cx.open_window( WindowOptions { focus: true, diff --git a/crates/gpui/examples/drag_drop.rs b/crates/gpui/examples/drag_drop.rs index b233bc4107..8f038ef330 100644 --- a/crates/gpui/examples/drag_drop.rs +++ b/crates/gpui/examples/drag_drop.rs @@ -1,5 +1,8 @@ #![cfg_attr(target_family = "wasm", no_main)] +#[path = "example_support/fonts.rs"] +mod example_support; + use gpui::{ App, Bounds, Context, Half, Hsla, Pixels, Point, Window, WindowBounds, WindowOptions, div, prelude::*, px, rgb, size, @@ -125,6 +128,9 @@ impl Render for DragDrop { fn run_example() { application().run(|cx: &mut App| { + if !example_support::load_fonts(cx) { + return; + } let bounds = Bounds::centered(None, size(px(800.), px(600.0)), cx); cx.open_window( WindowOptions { diff --git a/crates/gpui/examples/example_support/fonts.rs b/crates/gpui/examples/example_support/fonts.rs new file mode 100644 index 0000000000..39c3be1918 --- /dev/null +++ b/crates/gpui/examples/example_support/fonts.rs @@ -0,0 +1,44 @@ +#[cfg(target_family = "wasm")] +use std::borrow::Cow; + +use gpui::App; + +#[cfg(target_family = "wasm")] +pub fn load_fonts(cx: &App) -> bool { + let fonts = [ + Cow::Borrowed( + include_bytes!("../../../../assets/fonts/ibm-plex-sans/IBMPlexSans-Regular.ttf") + .as_slice(), + ), + Cow::Borrowed( + include_bytes!("../../../../assets/fonts/ibm-plex-sans/IBMPlexSans-Italic.ttf") + .as_slice(), + ), + Cow::Borrowed( + include_bytes!("../../../../assets/fonts/ibm-plex-sans/IBMPlexSans-SemiBold.ttf") + .as_slice(), + ), + Cow::Borrowed( + include_bytes!("../../../../assets/fonts/ibm-plex-sans/IBMPlexSans-SemiBoldItalic.ttf") + .as_slice(), + ), + Cow::Borrowed( + include_bytes!("../../../../assets/fonts/lilex/Lilex-Regular.ttf").as_slice(), + ), + Cow::Borrowed(include_bytes!("../../../../assets/fonts/lilex/Lilex-Bold.ttf").as_slice()), + Cow::Borrowed(include_bytes!("../../../../assets/fonts/lilex/Lilex-Italic.ttf").as_slice()), + Cow::Borrowed( + include_bytes!("../../../../assets/fonts/lilex/Lilex-BoldItalic.ttf").as_slice(), + ), + ]; + if let Err(error) = cx.text_system().add_fonts(fonts.into()) { + web_sys::console::error_1(&format!("failed to load application fonts: {error:#}").into()); + return false; + } + true +} + +#[cfg(not(target_family = "wasm"))] +pub fn load_fonts(_cx: &App) -> bool { + true +} diff --git a/crates/gpui/examples/focus_visible.rs b/crates/gpui/examples/focus_visible.rs index 02a171da21..c5b3d5dfec 100644 --- a/crates/gpui/examples/focus_visible.rs +++ b/crates/gpui/examples/focus_visible.rs @@ -1,5 +1,8 @@ #![cfg_attr(target_family = "wasm", no_main)] +#[path = "example_support/fonts.rs"] +mod example_support; + use gpui::{ App, Bounds, Context, Div, ElementId, FocusHandle, KeyBinding, SharedString, Stateful, Window, WindowBounds, WindowOptions, actions, div, prelude::*, px, size, @@ -196,6 +199,9 @@ impl Render for Example { fn run_example() { application().run(|cx: &mut App| { + if !example_support::load_fonts(cx) { + return; + } cx.bind_keys([ KeyBinding::new("tab", Tab, None), KeyBinding::new("shift-tab", TabPrev, None), diff --git a/crates/gpui/examples/gif_viewer.rs b/crates/gpui/examples/gif_viewer.rs index 59fb8d3794..80b56aa7c5 100644 --- a/crates/gpui/examples/gif_viewer.rs +++ b/crates/gpui/examples/gif_viewer.rs @@ -1,5 +1,8 @@ #![cfg_attr(target_family = "wasm", no_main)] +#[path = "example_support/fonts.rs"] +mod example_support; + use gpui::{App, Context, Render, Window, WindowOptions, div, img, prelude::*}; use gpui_platform::application; use std::path::PathBuf; @@ -27,6 +30,9 @@ impl Render for GifViewer { fn run_example() { application().run(|cx: &mut App| { + if !example_support::load_fonts(cx) { + return; + } let gif_path = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("examples/image/black-cat-typing.gif"); diff --git a/crates/gpui/examples/gradient.rs b/crates/gpui/examples/gradient.rs index 97321f1071..df1398fff5 100644 --- a/crates/gpui/examples/gradient.rs +++ b/crates/gpui/examples/gradient.rs @@ -1,5 +1,8 @@ #![cfg_attr(target_family = "wasm", no_main)] +#[path = "example_support/fonts.rs"] +mod example_support; + use gpui::{ App, Bounds, ColorSpace, Context, Half, Render, Window, WindowOptions, canvas, div, linear_color_stop, linear_gradient, point, prelude::*, px, size, @@ -247,6 +250,9 @@ impl Render for GradientViewer { fn run_example() { application().run(|cx: &mut App| { + if !example_support::load_fonts(cx) { + return; + } cx.open_window( WindowOptions { focus: true, diff --git a/crates/gpui/examples/grid_layout.rs b/crates/gpui/examples/grid_layout.rs index 9c6d723eba..51f9b13e41 100644 --- a/crates/gpui/examples/grid_layout.rs +++ b/crates/gpui/examples/grid_layout.rs @@ -1,5 +1,8 @@ #![cfg_attr(target_family = "wasm", no_main)] +#[path = "example_support/fonts.rs"] +mod example_support; + use gpui::{ App, Bounds, Context, Hsla, Window, WindowBounds, WindowOptions, container_query, div, prelude::*, px, rgb, size, @@ -63,6 +66,9 @@ impl Render for HolyGrailExample { fn run_example() { application().run(|cx: &mut App| { + if !example_support::load_fonts(cx) { + return; + } let bounds = Bounds::centered(None, size(px(500.), px(500.0)), cx); cx.open_window( WindowOptions { diff --git a/crates/gpui/examples/hello_world.rs b/crates/gpui/examples/hello_world.rs index aa67ada33f..d18cb04872 100644 --- a/crates/gpui/examples/hello_world.rs +++ b/crates/gpui/examples/hello_world.rs @@ -1,5 +1,8 @@ #![cfg_attr(target_family = "wasm", no_main)] +#[path = "example_support/fonts.rs"] +mod example_support; + use gpui::{ App, Bounds, Context, SharedString, Window, WindowBounds, WindowOptions, div, prelude::*, px, rgb, size, @@ -91,6 +94,9 @@ impl Render for HelloWorld { fn run_example() { application().run(|cx: &mut App| { + if !example_support::load_fonts(cx) { + return; + } let bounds = Bounds::centered(None, size(px(500.), px(500.0)), cx); cx.open_window( WindowOptions { diff --git a/crates/gpui/examples/image/image.rs b/crates/gpui/examples/image/image.rs index 2a885714c8..30160c65dd 100644 --- a/crates/gpui/examples/image/image.rs +++ b/crates/gpui/examples/image/image.rs @@ -1,5 +1,8 @@ #![cfg_attr(target_family = "wasm", no_main)] +#[path = "../example_support/fonts.rs"] +mod example_support; + use std::fs; use std::path::PathBuf; use std::sync::Arc; @@ -159,6 +162,9 @@ fn run_example() { base: manifest_dir.join("examples"), }) .run(move |cx: &mut App| { + if !example_support::load_fonts(cx) { + return; + } #[cfg(not(target_family = "wasm"))] { let http_client = ReqwestClient::user_agent("gpui example").unwrap(); diff --git a/crates/gpui/examples/image_gallery.rs b/crates/gpui/examples/image_gallery.rs index b4c2887755..16b9b17a3f 100644 --- a/crates/gpui/examples/image_gallery.rs +++ b/crates/gpui/examples/image_gallery.rs @@ -1,5 +1,8 @@ #![cfg_attr(target_family = "wasm", no_main)] +#[path = "example_support/fonts.rs"] +mod example_support; + use futures::FutureExt; use gpui::{ App, AppContext, Asset as _, AssetLogger, Bounds, ClickEvent, Context, ElementId, Entity, @@ -254,6 +257,9 @@ fn run_example() { let app = gpui_platform::single_threaded_web(); app.run(move |cx: &mut App| { + if !example_support::load_fonts(cx) { + return; + } #[cfg(not(target_family = "wasm"))] { let http_client = ReqwestClient::user_agent("gpui example").unwrap(); diff --git a/crates/gpui/examples/image_loading.rs b/crates/gpui/examples/image_loading.rs index c2aab95f12..b6828915bd 100644 --- a/crates/gpui/examples/image_loading.rs +++ b/crates/gpui/examples/image_loading.rs @@ -1,5 +1,8 @@ #![cfg_attr(target_family = "wasm", no_main)] +#[path = "example_support/fonts.rs"] +mod example_support; + use std::{path::Path, sync::Arc, time::Duration}; use gpui::{ @@ -196,6 +199,9 @@ impl Render for ImageLoadingExample { fn run_example() { application().with_assets(Assets {}).run(|cx: &mut App| { + if !example_support::load_fonts(cx) { + return; + } let options = WindowOptions { window_bounds: Some(WindowBounds::Windowed(Bounds::centered( None, diff --git a/crates/gpui/examples/input.rs b/crates/gpui/examples/input.rs index 370e27de7d..eadc2d2f39 100644 --- a/crates/gpui/examples/input.rs +++ b/crates/gpui/examples/input.rs @@ -1,5 +1,8 @@ #![cfg_attr(target_family = "wasm", no_main)] +#[path = "example_support/fonts.rs"] +mod example_support; + use std::ops::Range; use gpui::{ @@ -696,6 +699,9 @@ impl Render for InputExample { fn run_example() { application().run(|cx: &mut App| { + if !example_support::load_fonts(cx) { + return; + } let bounds = Bounds::centered(None, size(px(300.0), px(300.0)), cx); cx.bind_keys([ KeyBinding::new("backspace", Backspace, None), diff --git a/crates/gpui/examples/list_example.rs b/crates/gpui/examples/list_example.rs index 7aeff7c24e..542b7e320a 100644 --- a/crates/gpui/examples/list_example.rs +++ b/crates/gpui/examples/list_example.rs @@ -1,5 +1,8 @@ #![cfg_attr(target_family = "wasm", no_main)] +#[path = "example_support/fonts.rs"] +mod example_support; + use gpui::{ App, Bounds, Context, ListAlignment, ListState, Render, Window, WindowBounds, WindowOptions, div, list, prelude::*, px, rgb, size, @@ -143,6 +146,9 @@ impl Render for BottomListDemo { fn run_example() { application().run(|cx: &mut App| { + if !example_support::load_fonts(cx) { + return; + } let bounds = Bounds::centered(None, size(px(400.), px(500.)), cx); cx.open_window( WindowOptions { diff --git a/crates/gpui/examples/mouse_pressure.rs b/crates/gpui/examples/mouse_pressure.rs index d3519b2a54..3470bf1869 100644 --- a/crates/gpui/examples/mouse_pressure.rs +++ b/crates/gpui/examples/mouse_pressure.rs @@ -1,5 +1,8 @@ #![cfg_attr(target_family = "wasm", no_main)] +#[path = "example_support/fonts.rs"] +mod example_support; + use gpui::{ App, Bounds, Context, MousePressureEvent, PressureStage, Window, WindowBounds, WindowOptions, div, prelude::*, px, rgb, size, @@ -48,6 +51,9 @@ impl MousePressureExample { fn run_example() { application().run(|cx: &mut App| { + if !example_support::load_fonts(cx) { + return; + } let bounds = Bounds::centered(None, size(px(500.), px(500.0)), cx); cx.open_window( diff --git a/crates/gpui/examples/move_entity_between_windows.rs b/crates/gpui/examples/move_entity_between_windows.rs index eaefabfe29..e0f16467a6 100644 --- a/crates/gpui/examples/move_entity_between_windows.rs +++ b/crates/gpui/examples/move_entity_between_windows.rs @@ -8,6 +8,9 @@ #![cfg_attr(target_family = "wasm", no_main)] +#[path = "example_support/fonts.rs"] +mod example_support; + use std::time::Duration; use gpui::{ @@ -128,6 +131,9 @@ impl Render for HelloWorld { fn run_example() { application().run(|cx: &mut App| { + if !example_support::load_fonts(cx) { + return; + } let bounds = Bounds::centered(None, size(px(500.0), px(500.0)), cx); cx.open_window( WindowOptions { diff --git a/crates/gpui/examples/on_window_close_quit.rs b/crates/gpui/examples/on_window_close_quit.rs index 347401c6d9..d61e23e445 100644 --- a/crates/gpui/examples/on_window_close_quit.rs +++ b/crates/gpui/examples/on_window_close_quit.rs @@ -1,5 +1,8 @@ #![cfg_attr(target_family = "wasm", no_main)] +#[path = "example_support/fonts.rs"] +mod example_support; + use gpui::{ App, Bounds, Context, FocusHandle, KeyBinding, Window, WindowBounds, WindowOptions, actions, div, prelude::*, px, rgb, size, @@ -39,6 +42,9 @@ impl Render for ExampleWindow { fn run_example() { application().run(|cx: &mut App| { + if !example_support::load_fonts(cx) { + return; + } let mut bounds = Bounds::centered(None, size(px(500.), px(500.0)), cx); cx.bind_keys([KeyBinding::new("cmd-w", CloseWindow, None)]); diff --git a/crates/gpui/examples/opacity.rs b/crates/gpui/examples/opacity.rs index 413c40c826..c2af3424b6 100644 --- a/crates/gpui/examples/opacity.rs +++ b/crates/gpui/examples/opacity.rs @@ -1,5 +1,8 @@ #![cfg_attr(target_family = "wasm", no_main)] +#[path = "example_support/fonts.rs"] +mod example_support; + use std::{fs, path::PathBuf}; use anyhow::Result; @@ -163,6 +166,9 @@ fn run_example() { base: PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("examples"), }) .run(|cx: &mut App| { + if !example_support::load_fonts(cx) { + return; + } let bounds = Bounds::centered(None, size(px(500.0), px(500.0)), cx); cx.open_window( WindowOptions { diff --git a/crates/gpui/examples/ownership_post.rs b/crates/gpui/examples/ownership_post.rs index a4421b970b..04a0b6fe37 100644 --- a/crates/gpui/examples/ownership_post.rs +++ b/crates/gpui/examples/ownership_post.rs @@ -1,5 +1,8 @@ #![cfg_attr(target_family = "wasm", no_main)] +#[path = "example_support/fonts.rs"] +mod example_support; + use gpui::{App, Context, Entity, EventEmitter, prelude::*}; use gpui_platform::application; @@ -15,6 +18,9 @@ impl EventEmitter for Counter {} fn run_example() { application().run(|cx: &mut App| { + if !example_support::load_fonts(cx) { + return; + } let counter: Entity = cx.new(|_cx| Counter { count: 0 }); let subscriber = cx.new(|cx: &mut Context| { cx.subscribe(&counter, |subscriber, _emitter, event, _cx| { diff --git a/crates/gpui/examples/painting.rs b/crates/gpui/examples/painting.rs index 41cbcb2802..3d89b0ff52 100644 --- a/crates/gpui/examples/painting.rs +++ b/crates/gpui/examples/painting.rs @@ -1,5 +1,8 @@ #![cfg_attr(target_family = "wasm", no_main)] +#[path = "example_support/fonts.rs"] +mod example_support; + use gpui::{ Background, Bounds, ColorSpace, Context, MouseDownEvent, Path, PathBuilder, PathStyle, Pixels, Point, Render, StrokeOptions, Window, WindowOptions, canvas, div, linear_color_stop, @@ -443,6 +446,9 @@ impl Render for PaintingViewer { fn run_example() { application().run(|cx| { + if !example_support::load_fonts(cx) { + return; + } cx.open_window( WindowOptions { focus: true, diff --git a/crates/gpui/examples/paths_bench.rs b/crates/gpui/examples/paths_bench.rs index 4e12f1e50a..236e70e9fc 100644 --- a/crates/gpui/examples/paths_bench.rs +++ b/crates/gpui/examples/paths_bench.rs @@ -1,5 +1,8 @@ #![cfg_attr(target_family = "wasm", no_main)] +#[path = "example_support/fonts.rs"] +mod example_support; + use gpui::{ Background, Bounds, ColorSpace, Context, Path, PathBuilder, Pixels, Render, TitlebarOptions, Window, WindowBounds, WindowOptions, canvas, div, linear_color_stop, linear_gradient, point, @@ -73,6 +76,9 @@ impl Render for PaintingViewer { fn run_example() { application().run(|cx| { + if !example_support::load_fonts(cx) { + return; + } cx.open_window( WindowOptions { titlebar: Some(TitlebarOptions { diff --git a/crates/gpui/examples/pattern.rs b/crates/gpui/examples/pattern.rs index 3113d39d80..7d2e98d16e 100644 --- a/crates/gpui/examples/pattern.rs +++ b/crates/gpui/examples/pattern.rs @@ -1,5 +1,8 @@ #![cfg_attr(target_family = "wasm", no_main)] +#[path = "example_support/fonts.rs"] +mod example_support; + use gpui::{ App, AppContext, Bounds, Context, Window, WindowBounds, WindowOptions, div, linear_color_stop, linear_gradient, pattern_slash, prelude::*, px, rgb, size, @@ -103,6 +106,9 @@ impl Render for PatternExample { fn run_example() { application().run(|cx: &mut App| { + if !example_support::load_fonts(cx) { + return; + } let bounds = Bounds::centered(None, size(px(600.0), px(600.0)), cx); cx.open_window( WindowOptions { diff --git a/crates/gpui/examples/popover.rs b/crates/gpui/examples/popover.rs index e4f0ac0a3c..7678a6a3ed 100644 --- a/crates/gpui/examples/popover.rs +++ b/crates/gpui/examples/popover.rs @@ -1,5 +1,8 @@ #![cfg_attr(target_family = "wasm", no_main)] +#[path = "example_support/fonts.rs"] +mod example_support; + use gpui::{ Anchor, App, Context, Div, Hsla, Stateful, Window, WindowOptions, anchored, deferred, div, prelude::*, px, @@ -167,6 +170,9 @@ impl Render for HelloWorld { fn run_example() { application().run(|cx: &mut App| { + if !example_support::load_fonts(cx) { + return; + } cx.open_window(WindowOptions::default(), |_, cx| { cx.new(|_| HelloWorld { open: false, diff --git a/crates/gpui/examples/scrollable.rs b/crates/gpui/examples/scrollable.rs index 39864c834a..c817956188 100644 --- a/crates/gpui/examples/scrollable.rs +++ b/crates/gpui/examples/scrollable.rs @@ -1,5 +1,8 @@ #![cfg_attr(target_family = "wasm", no_main)] +#[path = "example_support/fonts.rs"] +mod example_support; + use gpui::{App, Bounds, Context, Window, WindowBounds, WindowOptions, div, prelude::*, px, size}; use gpui_platform::application; @@ -46,6 +49,9 @@ impl Render for Scrollable { fn run_example() { application().run(|cx: &mut App| { + if !example_support::load_fonts(cx) { + return; + } let bounds = Bounds::centered(None, size(px(500.), px(500.0)), cx); cx.open_window( WindowOptions { diff --git a/crates/gpui/examples/set_menus.rs b/crates/gpui/examples/set_menus.rs index a07f3c36ab..508a60e977 100644 --- a/crates/gpui/examples/set_menus.rs +++ b/crates/gpui/examples/set_menus.rs @@ -1,5 +1,8 @@ #![cfg_attr(target_family = "wasm", no_main)] +#[path = "example_support/fonts.rs"] +mod example_support; + use gpui::{ App, Context, Global, Menu, MenuItem, SharedString, SystemMenuType, Window, WindowOptions, actions, div, prelude::*, @@ -24,6 +27,9 @@ impl Render for SetMenus { fn run_example() { application().run(|cx: &mut App| { + if !example_support::load_fonts(cx) { + return; + } cx.set_global(AppState::new()); // Bring the menu bar to the foreground (so you can see the menu bar) diff --git a/crates/gpui/examples/shadow.rs b/crates/gpui/examples/shadow.rs index 375ccac68d..2d8dc09456 100644 --- a/crates/gpui/examples/shadow.rs +++ b/crates/gpui/examples/shadow.rs @@ -1,5 +1,8 @@ #![cfg_attr(target_family = "wasm", no_main)] +#[path = "example_support/fonts.rs"] +mod example_support; + use gpui::{ App, Bounds, BoxShadow, Context, Div, SharedString, Window, WindowBounds, WindowOptions, div, hsla, prelude::*, px, relative, rgb, size, @@ -589,6 +592,9 @@ impl Render for Shadow { fn run_example() { application().run(|cx: &mut App| { + if !example_support::load_fonts(cx) { + return; + } let bounds = Bounds::centered(None, size(px(1000.0), px(800.0)), cx); cx.open_window( WindowOptions { diff --git a/crates/gpui/examples/svg/svg.rs b/crates/gpui/examples/svg/svg.rs index e9d2341677..c5ac9b4e1b 100644 --- a/crates/gpui/examples/svg/svg.rs +++ b/crates/gpui/examples/svg/svg.rs @@ -1,5 +1,8 @@ #![cfg_attr(target_family = "wasm", no_main)] +#[path = "../example_support/fonts.rs"] +mod example_support; + use std::fs; use std::path::PathBuf; @@ -76,6 +79,9 @@ fn run_example() { base: PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("examples"), }) .run(|cx: &mut App| { + if !example_support::load_fonts(cx) { + return; + } let bounds = Bounds::centered(None, size(px(300.0), px(300.0)), cx); cx.open_window( WindowOptions { diff --git a/crates/gpui/examples/system_notifications.rs b/crates/gpui/examples/system_notifications.rs index 70071dc96f..faf6919120 100644 --- a/crates/gpui/examples/system_notifications.rs +++ b/crates/gpui/examples/system_notifications.rs @@ -2,6 +2,9 @@ //! Demonstrates posting, replacing, dismissing, and responding to system notifications. +#[path = "example_support/fonts.rs"] +mod example_support; + use gpui::{ App, Bounds, Context, Div, SharedString, Stateful, SystemNotification, SystemNotificationAction, SystemNotificationResponse, Window, WindowBounds, WindowOptions, div, @@ -97,6 +100,9 @@ fn button(id: &'static str, label: &'static str) -> Stateful
{ fn run_example() { application().run(|cx: &mut App| { + if !example_support::load_fonts(cx) { + return; + } cx.set_app_identity("dev.zed.gpui.system-notifications", "GPUI Notifications"); let view = cx.new(|_| SystemNotificationExample { diff --git a/crates/gpui/examples/tab_stop.rs b/crates/gpui/examples/tab_stop.rs index 3fec59ad9e..cc7c34a997 100644 --- a/crates/gpui/examples/tab_stop.rs +++ b/crates/gpui/examples/tab_stop.rs @@ -1,5 +1,8 @@ #![cfg_attr(target_family = "wasm", no_main)] +#[path = "example_support/fonts.rs"] +mod example_support; + use gpui::{ App, Bounds, Context, Div, ElementId, FocusHandle, KeyBinding, SharedString, Stateful, Window, WindowBounds, WindowOptions, actions, div, prelude::*, px, size, @@ -182,6 +185,9 @@ impl Render for Example { fn run_example() { application().run(|cx: &mut App| { + if !example_support::load_fonts(cx) { + return; + } cx.bind_keys([ KeyBinding::new("tab", Tab, None), KeyBinding::new("shift-tab", TabPrev, None), diff --git a/crates/gpui/examples/testing.rs b/crates/gpui/examples/testing.rs index f6e15791d6..76e3b77352 100644 --- a/crates/gpui/examples/testing.rs +++ b/crates/gpui/examples/testing.rs @@ -1,4 +1,5 @@ #![cfg_attr(target_family = "wasm", no_main)] + //! Example demonstrating GPUI's testing infrastructure. //! //! When run normally, this displays an interactive counter window. @@ -7,6 +8,9 @@ //! Run the app: cargo run -p gpui --example testing //! Run tests: cargo test -p gpui --example testing --features test-support +#[path = "example_support/fonts.rs"] +mod example_support; + use gpui::{ App, Bounds, Context, FocusHandle, Focusable, Render, Task, Window, WindowBounds, WindowOptions, actions, div, prelude::*, px, rgb, size, @@ -179,6 +183,9 @@ impl Render for Counter { fn run_example() { application().run(|cx: &mut App| { + if !example_support::load_fonts(cx) { + return; + } cx.bind_keys([ gpui::KeyBinding::new("up", Increment, Some("Counter")), gpui::KeyBinding::new("down", Decrement, Some("Counter")), diff --git a/crates/gpui/examples/text.rs b/crates/gpui/examples/text.rs index 418ebaabf6..a1244eba37 100644 --- a/crates/gpui/examples/text.rs +++ b/crates/gpui/examples/text.rs @@ -1,5 +1,8 @@ #![cfg_attr(target_family = "wasm", no_main)] +#[path = "example_support/fonts.rs"] +mod example_support; + use std::{ borrow::Cow, ops::{Deref, DerefMut}, @@ -348,6 +351,9 @@ impl Render for TextExample { fn run_example() { application().run(|cx: &mut App| { + if !example_support::load_fonts(cx) { + return; + } cx.set_menus(vec![Menu { name: "GPUI Typography".into(), disabled: false, diff --git a/crates/gpui/examples/text_layout.rs b/crates/gpui/examples/text_layout.rs index 4bb930e052..07f560ab6e 100644 --- a/crates/gpui/examples/text_layout.rs +++ b/crates/gpui/examples/text_layout.rs @@ -1,5 +1,8 @@ #![cfg_attr(target_family = "wasm", no_main)] +#[path = "example_support/fonts.rs"] +mod example_support; + use gpui::{ App, Bounds, Context, FontStyle, FontWeight, StyledText, Window, WindowBounds, WindowOptions, div, prelude::*, px, size, @@ -85,6 +88,9 @@ impl Render for HelloWorld { fn run_example() { application().run(|cx: &mut App| { + if !example_support::load_fonts(cx) { + return; + } let bounds = Bounds::centered(None, size(px(800.0), px(600.0)), cx); cx.open_window( WindowOptions { diff --git a/crates/gpui/examples/text_wrapper.rs b/crates/gpui/examples/text_wrapper.rs index 3750c3e32b..51e58fc7e3 100644 --- a/crates/gpui/examples/text_wrapper.rs +++ b/crates/gpui/examples/text_wrapper.rs @@ -1,5 +1,8 @@ #![cfg_attr(target_family = "wasm", no_main)] +#[path = "example_support/fonts.rs"] +mod example_support; + use gpui::{ App, Bounds, Context, TextOverflow, Window, WindowBounds, WindowOptions, div, prelude::*, px, size, @@ -112,6 +115,9 @@ impl Render for HelloWorld { fn run_example() { application().run(|cx: &mut App| { + if !example_support::load_fonts(cx) { + return; + } let bounds = Bounds::centered(None, size(px(800.0), px(600.0)), cx); cx.open_window( WindowOptions { diff --git a/crates/gpui/examples/tree.rs b/crates/gpui/examples/tree.rs index 9c4ea2cd12..891be4790a 100644 --- a/crates/gpui/examples/tree.rs +++ b/crates/gpui/examples/tree.rs @@ -1,6 +1,10 @@ #![cfg_attr(target_family = "wasm", no_main)] + //! Renders a div with deep children hierarchy. This example is useful to exemplify that Zed can //! handle deep hierarchies (even though it cannot just yet!). +#[path = "example_support/fonts.rs"] +mod example_support; + use std::sync::LazyLock; use gpui::{App, Bounds, Context, Window, WindowBounds, WindowOptions, div, prelude::*, px, size}; @@ -32,6 +36,9 @@ impl Render for Tree { fn run_example() { application().run(|cx: &mut App| { + if !example_support::load_fonts(cx) { + return; + } let bounds = Bounds::centered(None, size(px(300.0), px(300.0)), cx); cx.open_window( WindowOptions { diff --git a/crates/gpui/examples/uniform_list.rs b/crates/gpui/examples/uniform_list.rs index fabcde5c4b..4a2c4788b1 100644 --- a/crates/gpui/examples/uniform_list.rs +++ b/crates/gpui/examples/uniform_list.rs @@ -1,5 +1,8 @@ #![cfg_attr(target_family = "wasm", no_main)] +#[path = "example_support/fonts.rs"] +mod example_support; + use gpui::{ App, Bounds, Context, Window, WindowBounds, WindowOptions, div, prelude::*, px, rgb, size, uniform_list, @@ -40,6 +43,9 @@ impl Render for UniformListExample { fn run_example() { application().run(|cx: &mut App| { + if !example_support::load_fonts(cx) { + return; + } let bounds = Bounds::centered(None, size(px(300.0), px(300.0)), cx); cx.open_window( WindowOptions { diff --git a/crates/gpui/examples/view_example/view_example_main.rs b/crates/gpui/examples/view_example/view_example_main.rs index 0eac8494ff..d2e4991d53 100644 --- a/crates/gpui/examples/view_example/view_example_main.rs +++ b/crates/gpui/examples/view_example/view_example_main.rs @@ -13,6 +13,9 @@ //! //! Run: `cargo run -p gpui --example view_example` +#[path = "../example_support/fonts.rs"] +mod example_support; + mod example_editor; mod example_input; mod example_text_area; @@ -134,6 +137,9 @@ fn section(title: &str) -> Div { fn run_example() { application().run(|cx: &mut App| { + if !example_support::load_fonts(cx) { + return; + } let bounds = Bounds::centered(None, size(px(560.0), px(480.0)), cx); cx.bind_keys([ KeyBinding::new("backspace", Backspace, None), diff --git a/crates/gpui/examples/window.rs b/crates/gpui/examples/window.rs index 959ea4981c..51dbe7276d 100644 --- a/crates/gpui/examples/window.rs +++ b/crates/gpui/examples/window.rs @@ -1,5 +1,8 @@ #![cfg_attr(target_family = "wasm", no_main)] +#[path = "example_support/fonts.rs"] +mod example_support; + use gpui::{ App, Bounds, Context, KeyBinding, PromptButton, PromptLevel, Window, WindowBounds, WindowKind, WindowOptions, actions, div, prelude::*, px, rgb, size, @@ -310,6 +313,9 @@ actions!(window, [Quit]); fn run_example() { application().run(|cx: &mut App| { + if !example_support::load_fonts(cx) { + return; + } let bounds = Bounds::centered(None, size(px(800.0), px(600.0)), cx); cx.open_window( diff --git a/crates/gpui/examples/window_movable.rs b/crates/gpui/examples/window_movable.rs index 587a2dbfea..b5fb26030b 100644 --- a/crates/gpui/examples/window_movable.rs +++ b/crates/gpui/examples/window_movable.rs @@ -1,5 +1,8 @@ #![cfg_attr(target_family = "wasm", no_main)] +#[path = "example_support/fonts.rs"] +mod example_support; + use gpui::{ App, Bounds, Context, FocusHandle, Window, WindowBounds, WindowOptions, div, prelude::*, px, rgb, size, @@ -78,6 +81,9 @@ fn open_test_window( fn run_example() { application().run(|cx: &mut App| { + if !example_support::load_fonts(cx) { + return; + } let window_size = size(px(420.), px(280.0)); let base = Bounds::centered(None, window_size, cx); diff --git a/crates/gpui/examples/window_positioning.rs b/crates/gpui/examples/window_positioning.rs index 036a2fcdba..22c9b359c0 100644 --- a/crates/gpui/examples/window_positioning.rs +++ b/crates/gpui/examples/window_positioning.rs @@ -1,5 +1,8 @@ #![cfg_attr(target_family = "wasm", no_main)] +#[path = "example_support/fonts.rs"] +mod example_support; + use gpui::{ App, Bounds, Context, DisplayId, Hsla, Pixels, SharedString, Size, Window, WindowBackgroundAppearance, WindowBounds, WindowKind, WindowOptions, div, point, prelude::*, @@ -72,6 +75,9 @@ fn build_window_options(display_id: DisplayId, bounds: Bounds) -> Window fn run_example() { application().run(|cx: &mut App| { + if !example_support::load_fonts(cx) { + return; + } // Create several new windows, positioned in the top right corner of each screen let size = Size { width: px(350.), diff --git a/crates/gpui/examples/window_shadow.rs b/crates/gpui/examples/window_shadow.rs index f4a55b4bc3..fba5e91c0e 100644 --- a/crates/gpui/examples/window_shadow.rs +++ b/crates/gpui/examples/window_shadow.rs @@ -1,5 +1,8 @@ #![cfg_attr(target_family = "wasm", no_main)] +#[path = "example_support/fonts.rs"] +mod example_support; + use gpui::{ App, Bounds, Context, CursorStyle, Decorations, HitboxBehavior, Hsla, MouseButton, Pixels, Point, ResizeEdge, Size, Window, WindowBackgroundAppearance, WindowBounds, WindowDecorations, @@ -211,6 +214,9 @@ fn resize_edge(pos: Point, shadow_size: Pixels, size: Size) -> O fn run_example() { application().run(|cx: &mut App| { + if !example_support::load_fonts(cx) { + return; + } let bounds = Bounds::centered(None, size(px(600.0), px(600.0)), cx); cx.open_window( WindowOptions { diff --git a/crates/gpui_web/examples/hello_web/Cargo.toml b/crates/gpui_web/examples/hello_web/Cargo.toml index 925f47c71e..92a11dbd71 100644 --- a/crates/gpui_web/examples/hello_web/Cargo.toml +++ b/crates/gpui_web/examples/hello_web/Cargo.toml @@ -13,5 +13,5 @@ path = "main.rs" [dependencies] gpui = { path = "../../../gpui" } gpui_platform = { path = "../../../gpui_platform" } -web-sys = { version = "0.3", features = ["Location", "Window"] } +web-sys = { version = "0.3", features = ["console", "Location", "Window"] } web-time = "1" diff --git a/crates/gpui_web/examples/hello_web/main.rs b/crates/gpui_web/examples/hello_web/main.rs index 605fdf090e..abda6895cb 100644 --- a/crates/gpui_web/examples/hello_web/main.rs +++ b/crates/gpui_web/examples/hello_web/main.rs @@ -1,3 +1,5 @@ +use std::borrow::Cow; + use gpui::prelude::*; use gpui::{ App, Bounds, Context, ElementId, SharedString, Task, Window, WindowBounds, WindowOptions, div, @@ -429,6 +431,17 @@ fn requested_backend() -> gpui_platform::WebBackendPreference { fn main() { gpui_platform::web_init(); gpui_platform::application_with_web_backend(requested_backend()).run(|cx: &mut App| { + if let Err(error) = cx + .text_system() + .add_fonts(vec![Cow::Borrowed(include_bytes!( + "../../../../assets/fonts/ibm-plex-sans/IBMPlexSans-Regular.ttf" + ))]) + { + web_sys::console::error_1( + &format!("failed to load application fonts: {error:#}").into(), + ); + return; + } let bounds = Bounds::centered(None, size(px(640.), px(560.)), cx); cx.open_window( WindowOptions { diff --git a/crates/gpui_web/src/platform.rs b/crates/gpui_web/src/platform.rs index a546173198..70d643e542 100644 --- a/crates/gpui_web/src/platform.rs +++ b/crates/gpui_web/src/platform.rs @@ -15,7 +15,6 @@ use gpui::{ }; use gpui_wgpu::{PreparedWebGraphics, WebBackendPreference, WgpuContext, wgpu}; use std::{ - borrow::Cow, cell::{Cell, RefCell}, path::{Path, PathBuf}, rc::Rc, @@ -23,17 +22,10 @@ use std::{ }; use wasm_bindgen::prelude::*; -static BUNDLED_FONTS: &[&[u8]] = &[ - include_bytes!("../../../assets/fonts/ibm-plex-sans/IBMPlexSans-Regular.ttf"), - include_bytes!("../../../assets/fonts/ibm-plex-sans/IBMPlexSans-Italic.ttf"), - include_bytes!("../../../assets/fonts/ibm-plex-sans/IBMPlexSans-SemiBold.ttf"), - include_bytes!("../../../assets/fonts/ibm-plex-sans/IBMPlexSans-SemiBoldItalic.ttf"), - include_bytes!("../../../assets/fonts/lilex/Lilex-Regular.ttf"), - include_bytes!("../../../assets/fonts/lilex/Lilex-Bold.ttf"), - include_bytes!("../../../assets/fonts/lilex/Lilex-Italic.ttf"), - include_bytes!("../../../assets/fonts/lilex/Lilex-BoldItalic.ttf"), -]; - +/// Provides the GPUI platform implementation for web browsers. +/// +/// The platform starts with an empty font database. Applications must add fonts +/// through [`gpui::App::text_system`] before opening a window. pub struct WebPlatform { browser_window: web_sys::Window, dispatcher: Arc, @@ -135,13 +127,6 @@ impl WebPlatform { let text_system = Arc::new(gpui_wgpu::CosmicTextSystem::new_without_system_fonts( "IBM Plex Sans", )); - let fonts = BUNDLED_FONTS - .iter() - .map(|bytes| Cow::Borrowed(*bytes)) - .collect(); - if let Err(error) = text_system.add_fonts(fonts) { - log::error!("failed to load bundled fonts: {error:#}"); - } let text_system: Arc = text_system; let active_display: Rc = Rc::new(WebDisplay::new(browser_window.clone()));