feat(wasm): single-window web entry + trunk harness (issue #45)
The binary now compiles for wasm32 as a runnable web target: - Split the main-window render into `view_main`, bypassing the multi-window id dispatch the browser cannot use. - Add `run_web` / `boot_web`: a single-window `iced::application` entry for wasm (the browser canvas is the only window). Native keeps the multi-window `iced::daemon` via cfg-split in `main`. - Add a `console_error_panic_hook` (wasm-only) for readable panics. - Add `index.html` + `Trunk.toml` so `trunk serve` builds the wasm bundle (no-default-features → no solid3d) with COOP/COEP headers. Native build and all 75 tests unchanged. Browser runtime (rayon threads, canvas attach, real file I/O) is the next iteration. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
b2e20c95fe
commit
157d54a4d4
7 changed files with 93 additions and 1 deletions
11
Cargo.lock
generated
11
Cargo.lock
generated
|
|
@ -18,6 +18,7 @@ version = "0.5.7"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"acadrust",
|
"acadrust",
|
||||||
"bytemuck",
|
"bytemuck",
|
||||||
|
"console_error_panic_hook",
|
||||||
"cosmic-text",
|
"cosmic-text",
|
||||||
"flate2",
|
"flate2",
|
||||||
"fontdb",
|
"fontdb",
|
||||||
|
|
@ -977,6 +978,16 @@ dependencies = [
|
||||||
"crossbeam-utils",
|
"crossbeam-utils",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "console_error_panic_hook"
|
||||||
|
version = "0.1.7"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a06aeb73f470f66dcdbf7223caeebb85984942f22f1adb2a088cf9668146bbbc"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"wasm-bindgen",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "convert_case"
|
name = "convert_case"
|
||||||
version = "0.4.0"
|
version = "0.4.0"
|
||||||
|
|
|
||||||
|
|
@ -79,3 +79,6 @@ acadrust = { git = "https://github.com/HakanSeven12/acadrust", branch = "main" }
|
||||||
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
|
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
|
||||||
open = "5"
|
open = "5"
|
||||||
ureq = { version = "3", default-features = false, features = ["rustls"] }
|
ureq = { version = "3", default-features = false, features = ["rustls"] }
|
||||||
|
|
||||||
|
[target.'cfg(target_arch = "wasm32")'.dependencies]
|
||||||
|
console_error_panic_hook = "0.1"
|
||||||
|
|
|
||||||
16
Trunk.toml
Normal file
16
Trunk.toml
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
# Trunk config for the web (wasm) build of Open CAD Studio (issue #45).
|
||||||
|
#
|
||||||
|
# Build: trunk build --release
|
||||||
|
# Dev: trunk serve
|
||||||
|
#
|
||||||
|
# Requires: rustup target add wasm32-unknown-unknown
|
||||||
|
# cargo install trunk wasm-bindgen-cli
|
||||||
|
|
||||||
|
[build]
|
||||||
|
target = "index.html"
|
||||||
|
|
||||||
|
[serve]
|
||||||
|
# Cross-origin isolation headers enable SharedArrayBuffer, which wasm threads
|
||||||
|
# (rayon) need. GitHub Pages cannot set these; a self-hosted server (e.g. the
|
||||||
|
# Open-AEC host) can. Without them the app still runs single-threaded.
|
||||||
|
headers = { "Cross-Origin-Opener-Policy" = "same-origin", "Cross-Origin-Embedder-Policy" = "require-corp" }
|
||||||
16
index.html
Normal file
16
index.html
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title>Open CAD Studio</title>
|
||||||
|
<style>
|
||||||
|
html, body { margin: 0; padding: 0; width: 100%; height: 100%; overflow: hidden; background: #1e1e1e; }
|
||||||
|
canvas { display: block; }
|
||||||
|
</style>
|
||||||
|
<!-- Trunk builds the wasm32 binary without the `solid3d` feature (no 3-D
|
||||||
|
solid kernel / vtkio C deps on the web). See issue #45. -->
|
||||||
|
<link data-trunk rel="rust" data-bin="OpenCADStudio" data-no-default-features />
|
||||||
|
</head>
|
||||||
|
<body></body>
|
||||||
|
</html>
|
||||||
|
|
@ -1670,10 +1670,21 @@ impl OpenCADStudio {
|
||||||
Task::batch([open_main, check_update, focus_cmd, cli_open, assoc_prompt]),
|
Task::batch([open_main, check_update, focus_cmd, cli_open, assoc_prompt]),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Single-window boot for the web build: no OS-window creation (the browser
|
||||||
|
/// canvas is the only window), no file-association registration or CLI file
|
||||||
|
/// open. Secondary manager windows are unavailable on the web for now.
|
||||||
|
#[cfg(target_arch = "wasm32")]
|
||||||
|
fn boot_web() -> (Self, Task<Message>) {
|
||||||
|
let mut s = Self::new();
|
||||||
|
let focus = s.focus_cmd_input();
|
||||||
|
(s, focus)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
pub fn run() -> iced::Result {
|
pub fn run() -> iced::Result {
|
||||||
iced::daemon(
|
iced::daemon(
|
||||||
OpenCADStudio::boot,
|
OpenCADStudio::boot,
|
||||||
|
|
@ -1744,3 +1755,20 @@ pub fn run() -> iced::Result {
|
||||||
.theme(|state: &OpenCADStudio, _| state.active_theme.clone())
|
.theme(|state: &OpenCADStudio, _| state.active_theme.clone())
|
||||||
.run()
|
.run()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Single-window entry for the web (wasm) build. Uses `iced::application`
|
||||||
|
/// instead of `iced::daemon`: the browser canvas is the only window, so the
|
||||||
|
/// main-window view is rendered directly and the manager/dialog windows are
|
||||||
|
/// unavailable for now (see issue #45). Native keeps the multi-window `run`.
|
||||||
|
#[cfg(target_arch = "wasm32")]
|
||||||
|
pub fn run_web() -> iced::Result {
|
||||||
|
iced::application(
|
||||||
|
OpenCADStudio::boot_web,
|
||||||
|
OpenCADStudio::update,
|
||||||
|
OpenCADStudio::view_main,
|
||||||
|
)
|
||||||
|
.subscription(OpenCADStudio::subscription)
|
||||||
|
.title(|_state: &OpenCADStudio| "Open CAD Studio".to_string())
|
||||||
|
.theme(|state: &OpenCADStudio| state.active_theme.clone())
|
||||||
|
.run()
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -511,6 +511,13 @@ impl OpenCADStudio {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
self.view_main()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The primary window: viewport, ribbon, tab bar, status bar. Split out of
|
||||||
|
/// `view` so the single-window web build can render it directly, bypassing
|
||||||
|
/// the multi-window id dispatch above (the web build has no extra windows).
|
||||||
|
pub fn view_main(&self) -> Element<'_, Message> {
|
||||||
let i = self.active_tab;
|
let i = self.active_tab;
|
||||||
let tab = &self.tabs[i];
|
let tab = &self.tabs[i];
|
||||||
let is_paper = tab.scene.current_layout != "Model";
|
let is_paper = tab.scene.current_layout != "Model";
|
||||||
|
|
|
||||||
13
src/main.rs
13
src/main.rs
|
|
@ -28,5 +28,16 @@ fn main() -> iced::Result {
|
||||||
if std::env::var_os("WGPU_BACKEND").is_none() {
|
if std::env::var_os("WGPU_BACKEND").is_none() {
|
||||||
std::env::set_var("WGPU_BACKEND", "dx12,vulkan");
|
std::env::set_var("WGPU_BACKEND", "dx12,vulkan");
|
||||||
}
|
}
|
||||||
app::run()
|
|
||||||
|
// Web (wasm) uses the single-window entry; native uses the multi-window
|
||||||
|
// daemon. Trunk calls `main` from its generated JS bootstrap.
|
||||||
|
#[cfg(target_arch = "wasm32")]
|
||||||
|
{
|
||||||
|
console_error_panic_hook::set_once();
|
||||||
|
app::run_web()
|
||||||
|
}
|
||||||
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
|
{
|
||||||
|
app::run()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue