(self, on_finish_launching: F)
where
F: 'static + FnOnce(&mut App),
@@ -238,6 +241,9 @@ impl Application {
let cx = &mut *this.borrow_mut();
on_finish_launching(cx);
}));
+
+ #[cfg(target_family = "wasm")]
+ std::mem::forget(self);
}
/// Start the application for an embedder that drives the run loop itself.
diff --git a/crates/gpui_web/Cargo.toml b/crates/gpui_web/Cargo.toml
index 610da14f0d..f1f4e36b24 100644
--- a/crates/gpui_web/Cargo.toml
+++ b/crates/gpui_web/Cargo.toml
@@ -16,6 +16,11 @@ multithreaded = ["dep:wasm_thread", "scheduler/wasm-threads"]
[lib]
path = "src/gpui_web.rs"
+[dependencies]
+unicode-properties = "=0.1.3"
+unicode-script = "0.5.7"
+unicode-segmentation = "1.10"
+
[target.'cfg(target_family = "wasm")'.dependencies]
gpui.workspace = true
scheduler.workspace = true
@@ -32,6 +37,7 @@ web-time.workspace = true
console_error_panic_hook.workspace = true
js-sys.workspace = true
raw-window-handle.workspace = true
+smallvec.workspace = true
# Forked because upstream is unmaintained and its worker bootstrap still calls
# the wasm-bindgen init function with the positional arguments deprecated in 0.2.93.
wasm_thread = { git = "https://github.com/zed-industries/wasm_thread", rev = "0cf96c7708dfb97ccf3da50347e25edcf75d6937", version = "0.3", features = [
@@ -60,6 +66,7 @@ web-sys = { version = "0.3", features = [
"HtmlTextAreaElement",
"IdleDeadline",
"IdleRequestOptions",
+ "ImageData",
"InputEvent",
"KeyboardEvent",
"Location",
@@ -67,6 +74,8 @@ web-sys = { version = "0.3", features = [
"MediaQueryListEvent",
"MouseEvent",
"Navigator",
+ "OffscreenCanvas",
+ "OffscreenCanvasRenderingContext2d",
"PointerEvent",
"ReadableStream",
"ReadableStreamDefaultReader",
@@ -78,6 +87,7 @@ web-sys = { version = "0.3", features = [
"ResizeObserverOptions",
"Screen",
"Storage",
+ "TextMetrics",
"VisualViewport",
"Headers",
"Request",
@@ -89,5 +99,10 @@ web-sys = { version = "0.3", features = [
"Window",
] }
+[dev-dependencies]
+anyhow.workspace = true
+gpui.workspace = true
+smallvec.workspace = true
+
[package.metadata.cargo-shear]
ignored = ["scheduler"]
diff --git a/crates/gpui_web/examples/hello_web/Cargo.toml b/crates/gpui_web/examples/hello_web/Cargo.toml
index 92a11dbd71..f6f96de0c0 100644
--- a/crates/gpui_web/examples/hello_web/Cargo.toml
+++ b/crates/gpui_web/examples/hello_web/Cargo.toml
@@ -10,8 +10,30 @@ publish = false
name = "hello_web"
path = "main.rs"
+[[bin]]
+name = "hello_world"
+path = "../../../gpui/examples/hello_world.rs"
+
+[[bin]]
+name = "text"
+path = "../../../gpui/examples/text.rs"
+
+[[bin]]
+name = "text_layout"
+path = "../../../gpui/examples/text_layout.rs"
+
+[[bin]]
+name = "text_wrapper"
+path = "../../../gpui/examples/text_wrapper.rs"
+
+[[bin]]
+name = "input"
+path = "../../../gpui/examples/input.rs"
+
[dependencies]
gpui = { path = "../../../gpui" }
gpui_platform = { path = "../../../gpui_platform" }
+unicode-segmentation = "1"
+wasm-bindgen = "0.2"
web-sys = { version = "0.3", features = ["console", "Location", "Window"] }
web-time = "1"
diff --git a/crates/gpui_web/examples/hello_web/gallery.js b/crates/gpui_web/examples/hello_web/gallery.js
new file mode 100644
index 0000000000..57b40f491f
--- /dev/null
+++ b/crates/gpui_web/examples/hello_web/gallery.js
@@ -0,0 +1,42 @@
+const route = window.location.pathname.replace(/\/+$/, "") || "/";
+const gallery = document.getElementById("gallery");
+const back = document.getElementById("back");
+const status = document.getElementById("status");
+const example = [...gallery.querySelectorAll("a[data-module]")].find(
+ (link) => new URL(link.href).pathname === route,
+);
+
+function showError(error) {
+ console.error(error);
+ status.hidden = false;
+ status.textContent = `Unable to start example: ${error.message ?? error}`;
+ status.setAttribute("role", "alert");
+}
+
+if (route !== "/") {
+ gallery.hidden = true;
+ back.hidden = false;
+ status.hidden = false;
+
+ if (!example) {
+ showError(new Error(`Unknown example path "${route}". Choose an example from the gallery.`));
+ } else {
+ const title = example.querySelector("strong").textContent;
+ document.title = `${title} — GPUI Web Examples`;
+ status.textContent = `Loading ${title}…`;
+
+ try {
+ if (!window.crossOriginIsolated) {
+ throw new Error("Shared-memory WASM requires cross-origin isolation. Serve this page with trunk serve.");
+ }
+
+ const bindings = await import(`/${example.dataset.module}.js`);
+ await bindings.default();
+ window.wasmBindings = bindings;
+ // GPUI chooses WebGPU or WebGL2 and reports initialization failures itself.
+ status.hidden = true;
+ } catch (error) {
+ showError(error);
+ }
+ }
+}
diff --git a/crates/gpui_web/examples/hello_web/index.html b/crates/gpui_web/examples/hello_web/index.html
index 692f3edf06..c0f9060f5b 100644
--- a/crates/gpui_web/examples/hello_web/index.html
+++ b/crates/gpui_web/examples/hello_web/index.html
@@ -3,8 +3,16 @@
- GPUI Web: hello_web
+ GPUI Web Examples
+
+
+
+
+
+
+
-
+
+
+ GPUI Web Examples
+ Each example runs in its own WebAssembly instance. Choose an example to open it.
+
+
+ All examples
+
+