teardown scene window when unused

This commit is contained in:
2026-09-07 01:18:50 +08:00
parent ce0fb10073
commit ad71b05dc8
7 changed files with 251 additions and 70 deletions
+72 -16
View File
@@ -80,7 +80,8 @@ pub fn apply_macos_decoration_window_policy(app_handle: &AppHandle, window_label
pub const WINDOW_LABEL: &str = "scene";
fn track_scene_hitboxes(app_handle: AppHandle, window: tauri::WebviewWindow) {
tauri::async_runtime::spawn(async move {
let tracked_window = window.clone();
let task = tauri::async_runtime::spawn(async move {
let mut ignores_cursor = true;
let mut interval = tokio::time::interval(crate::cursor::SYSTEM_CURSOR_POLL_INTERVAL);
interval.set_missed_tick_behavior(tokio::time::MissedTickBehavior::Skip);
@@ -101,14 +102,14 @@ fn track_scene_hitboxes(app_handle: AppHandle, window: tauri::WebviewWindow) {
continue;
}
};
let window_position = match window.outer_position() {
let window_position = match tracked_window.outer_position() {
Ok(position) => position,
Err(error) => {
eprintln!("Failed to read scene position for hit-testing: {error}");
continue;
}
};
let scale_factor = match window.scale_factor() {
let scale_factor = match tracked_window.scale_factor() {
Ok(scale_factor) => scale_factor,
Err(error) => {
eprintln!("Failed to read scene scale factor for hit-testing: {error}");
@@ -126,7 +127,7 @@ fn track_scene_hitboxes(app_handle: AppHandle, window: tauri::WebviewWindow) {
};
if should_ignore != ignores_cursor {
match window.set_ignore_cursor_events(should_ignore) {
match tracked_window.set_ignore_cursor_events(should_ignore) {
Ok(()) => ignores_cursor = should_ignore,
Err(error) => {
eprintln!("Failed to update scene cursor event policy: {error}")
@@ -135,6 +136,11 @@ fn track_scene_hitboxes(app_handle: AppHandle, window: tauri::WebviewWindow) {
}
}
});
window.on_window_event(move |event| {
if matches!(event, tauri::WindowEvent::Destroyed) {
task.abort();
}
});
}
pub fn overlay_fullscreen(
@@ -156,9 +162,8 @@ pub fn overlay_fullscreen(
Ok(())
}
pub fn open_window(app_handle: &AppHandle) -> Result<tauri::WebviewWindow, String> {
fn open_window(app_handle: &AppHandle) -> Result<tauri::WebviewWindow, String> {
if let Some(window) = app_handle.get_webview_window(WINDOW_LABEL) {
window.show().map_err(|e| e.to_string())?;
return Ok(window);
};
@@ -181,26 +186,77 @@ pub fn open_window(app_handle: &AppHandle) -> Result<tauri::WebviewWindow, Strin
.skip_taskbar(true)
.shadow(false)
.accept_first_mouse(true)
.visible(true)
.visible(false)
.focused(false);
let window = builder.build().map_err(|e: tauri::Error| e.to_string())?;
#[cfg(debug_assertions)]
window.open_devtools();
let configure = || -> Result<(), tauri::Error> {
overlay_fullscreen(app_handle, &window)?;
window.set_ignore_cursor_events(true)?;
window.show()?;
Ok(())
};
if let Err(error) = configure() {
let _ = window.destroy();
return Err(error.to_string());
}
apply_macos_decoration_window_policy(app_handle, WINDOW_LABEL.to_string());
track_scene_hitboxes(app_handle.clone(), window.clone());
Ok(window)
}
/// Serializes native create/destroy requests, including destruction acknowledgement.
#[derive(Default)]
struct SceneWindow(tokio::sync::Mutex<()>);
pub(crate) fn is_initialized(handle: &AppHandle) -> bool {
handle.try_state::<SceneWindow>().is_some()
}
/// Creates a fully configured scene, or destroys its webview and stops hit-testing.
pub(crate) async fn reconcile_window(handle: &AppHandle, open: bool) -> Result<(), String> {
let state = handle
.try_state::<SceneWindow>()
.ok_or("scene UI has not started")?;
let _guard = state.0.lock().await;
if open {
if handle.get_webview_window(WINDOW_LABEL).is_none() {
handle
.state::<SceneHitboxes>()
.0
.write()
.map_err(|error| error.to_string())?
.clear();
open_window(&handle)?;
}
} else if let Some(window) = handle.get_webview_window(WINDOW_LABEL) {
let (sender, receiver) = tokio::sync::oneshot::channel();
let sender = std::sync::Mutex::new(Some(sender));
window.on_window_event(move |event| {
if matches!(event, tauri::WindowEvent::Destroyed)
&& let Some(sender) = sender.lock().unwrap().take()
{
let _ = sender.send(());
}
});
window.destroy().map_err(|error| error.to_string())?;
receiver.await.map_err(|error| error.to_string())?;
handle
.state::<SceneHitboxes>()
.0
.write()
.map_err(|error| error.to_string())?
.clear();
}
Ok(())
}
pub fn init(app_handle: &AppHandle) {
app_handle.manage(SceneHitboxes::default());
let window = open_window(app_handle).expect("Scene window should be opened successfully");
overlay_fullscreen(app_handle, &window).expect("Scene window should be fullscreened");
apply_macos_decoration_window_policy(app_handle, WINDOW_LABEL.to_string());
window
.set_ignore_cursor_events(true)
.expect("Scene window needs to ignore cursor events");
track_scene_hitboxes(app_handle.clone(), window);
// The existing puppet tick opens the scene only after publishing its snapshot.
app_handle.manage(SceneWindow::default());
}
#[cfg(test)]