This commit is contained in:
Brandon Thomas committed 2026-10-31 10:54:51 -04:00
commit aac9c18989
4 files changed
+44 -1

No files matched your search

+15 -1
View File
@@ -90,6 +90,8 @@ pub struct WebApp {
/// Started on the demo by the config: the snapshot stays off, so the demo never replaces the
/// last visit's project, until the user opens a project from a file or edits the demo.
pub demo_start: bool,
/// The last title written to `document.title`.
pub document_title: String,
}
impl eframe::App for WebApp {
@@ -119,6 +121,11 @@ impl eframe::App for WebApp {
f(&mut self.app);
}
self.app.tick(ctx, frame);
// eframe on the web ignores `ViewportCommand::Title`: the tab title follows the open document here.
if self.app.window_title() != self.document_title {
self.document_title = self.app.window_title().to_owned();
set_document_title(&self.document_title);
}
api::poll_replies();
// Render Queue outputs written this frame (here or by a worker) → downloads.
files::flush_downloads();
@@ -146,6 +153,13 @@ impl eframe::App for WebApp {
}
}
/// The browser tab's title.
fn set_document_title(title: &str) {
if let Some(doc) = web_sys::window().and_then(|w| w.document()) {
doc.set_title(title);
}
}
fn query() -> Option<web_sys::UrlSearchParams> {
web_sys::window().and_then(|w| w.location().search().ok()).and_then(|s| web_sys::UrlSearchParams::new_with_str(&s).ok())
}
@@ -305,7 +319,7 @@ pub async fn start(canvas_id: String) -> Result<(), JsValue> {
app = app.with_control(rx);
let backend = cc.wgpu_render_state.as_ref().map(|rs| format!("{:?}", rs.adapter.get_info().backend));
api::set_info("backend", json!(backend));
Ok(Box::new(WebApp { app, persist: keep_session, demo_start: !empty && !keep_session }))
Ok(Box::new(WebApp { app, persist: keep_session, demo_start: !empty && !keep_session, document_title: String::new() }))
}),
)
.await?;
+7
View File
@@ -20,6 +20,13 @@ pub use gpu::{GPU_PREPARING, GpuBuild};
pub(crate) use screenshot::{MAX_PENDING_SHOTS, SCREENSHOT_SETTLE_PASSES, SCREENSHOT_SETTLE_S, SCREENSHOT_TIMEOUT_S};
impl EffectcraftApp {
/// The window title as of the last tick (`EffectCraft - <project>`, ` *` while modified). The
/// app sends it as `ViewportCommand::Title`, which eframe on the web ignores: that host writes
/// it to `document.title`.
pub fn window_title(&self) -> &str {
&self.window_title
}
/// Build the app around `session`.
/// Loads and applies the workspaces saved in the session's config directory (a failed load is reported in
/// `ui.status`), initializes the default UI, playback and render state, and registers the frontend's commands as
+1
View File
@@ -106,6 +106,7 @@ mod ui_visual_review;
mod ui_web_gpu_storage;
mod ui_web_job_gpu;
mod ui_web_offload;
mod ui_window_title;
mod ui_workflow_selection;
mod ui_workspace_focus;
mod viewer_geometry;
@@ -0,0 +1,21 @@
//! The window title the shared app reports (project name, ` *` while modified), which hosts that
//! cannot use `ViewportCommand::Title` (the browser tab) read through `window_title`.
use effectcraft_engine::Session;
use effectcraft_ui_egui::EffectcraftApp;
use egui::vec2;
use egui_kittest::Harness;
use serde_json::json;
#[test]
fn the_reported_title_follows_the_open_document() {
let mut h = Harness::builder().with_size(vec2(800.0, 600.0)).build_eframe(|_| EffectcraftApp::new(Session::default()));
h.run_steps(3);
let clean = h.state().window_title().to_owned();
assert!(clean.starts_with("EffectCraft - ") && !clean.ends_with(" *"), "{clean}");
h.state_mut().session.execute("comp.new", json!({"name": "Edited", "width": 64, "height": 64, "duration": 1})).unwrap();
h.run_steps(3);
let dirty = h.state().window_title().to_owned();
assert!(dirty.starts_with("EffectCraft - ") && dirty.ends_with(" *"), "{dirty}");
}