Add: config for controlling debug overlay visibility of tauri

This commit is contained in:
DaZuo0122
2026-02-13 22:31:22 +08:00
parent 875bc54c4f
commit e5e123cc84
9 changed files with 148 additions and 26 deletions

View File

@@ -158,12 +158,14 @@ pub enum FrontendBackend {
#[serde(default)] #[serde(default)]
pub struct FrontendConfig { pub struct FrontendConfig {
pub backend: FrontendBackend, pub backend: FrontendBackend,
pub debug_overlay_visible: bool,
} }
impl Default for FrontendConfig { impl Default for FrontendConfig {
fn default() -> Self { fn default() -> Self {
Self { Self {
backend: FrontendBackend::Bevy, backend: FrontendBackend::Bevy,
debug_overlay_visible: false,
} }
} }
} }
@@ -222,10 +224,12 @@ mod tests {
let mut config = AppConfig::default(); let mut config = AppConfig::default();
config.window.x = 42.0; config.window.x = 42.0;
config.frontend.backend = super::FrontendBackend::Tauri; config.frontend.backend = super::FrontendBackend::Tauri;
config.frontend.debug_overlay_visible = true;
save(&path, &config).expect("save"); save(&path, &config).expect("save");
let (_, loaded) = load_or_create_at(&path).expect("reload"); let (_, loaded) = load_or_create_at(&path).expect("reload");
assert!((loaded.window.x - 42.0).abs() < f32::EPSILON); assert!((loaded.window.x - 42.0).abs() < f32::EPSILON);
assert_eq!(loaded.frontend.backend, super::FrontendBackend::Tauri); assert_eq!(loaded.frontend.backend, super::FrontendBackend::Tauri);
assert!(loaded.frontend.debug_overlay_visible);
} }
} }

View File

@@ -82,6 +82,28 @@ impl RuntimeCore {
self.command_tx.clone() self.command_tx.clone()
} }
pub fn frontend_debug_overlay_visible(&self) -> Result<bool, RuntimeCoreError> {
let guard = self
.config
.read()
.map_err(|_| RuntimeCoreError::ConfigPoisoned)?;
Ok(guard.frontend.debug_overlay_visible)
}
pub fn set_frontend_debug_overlay_visible(
&self,
visible: bool,
) -> Result<(), RuntimeCoreError> {
{
let mut guard = self
.config
.write()
.map_err(|_| RuntimeCoreError::ConfigPoisoned)?;
guard.frontend.debug_overlay_visible = visible;
}
self.persist_config()
}
pub fn api_config(&self) -> ApiConfig { pub fn api_config(&self) -> ApiConfig {
self.api_config.clone() self.api_config.clone()
} }
@@ -283,4 +305,14 @@ mod tests {
let config = core.config().read().expect("config lock").clone(); let config = core.config().read().expect("config lock").clone();
assert!(!config.window.click_through); assert!(!config.window.click_through);
} }
#[test]
fn frontend_debug_overlay_visibility_roundtrips() {
let temp = TempDir::new().expect("tempdir");
let path = temp.path().join("config.toml");
let core = RuntimeCore::new_with_config(path, AppConfig::default(), CapabilityFlags::default())
.expect("core init");
core.set_frontend_debug_overlay_visible(true).expect("set");
assert!(core.frontend_debug_overlay_visible().expect("get"));
}
} }

View File

@@ -121,6 +121,26 @@ fn load_active_sprite_pack(state: tauri::State<'_, AppState>) -> Result<UiSprite
}) })
} }
#[tauri::command]
fn debug_overlay_visible(state: tauri::State<'_, AppState>) -> Result<bool, String> {
state
.runtime_core
.frontend_debug_overlay_visible()
.map_err(|err| err.to_string())
}
#[tauri::command]
fn set_debug_overlay_visible(
state: tauri::State<'_, AppState>,
visible: bool,
) -> Result<bool, String> {
state
.runtime_core
.set_frontend_debug_overlay_visible(visible)
.map_err(|err| err.to_string())?;
Ok(visible)
}
fn main() -> Result<(), AppError> { fn main() -> Result<(), AppError> {
tracing_subscriber::fmt() tracing_subscriber::fmt()
.with_env_filter("sprimo=info") .with_env_filter("sprimo=info")
@@ -141,7 +161,12 @@ fn main() -> Result<(), AppError> {
tauri::Builder::default() tauri::Builder::default()
.plugin(tauri_plugin_global_shortcut::Builder::new().build()) .plugin(tauri_plugin_global_shortcut::Builder::new().build())
.manage(state) .manage(state)
.invoke_handler(tauri::generate_handler![current_state, load_active_sprite_pack]) .invoke_handler(tauri::generate_handler![
current_state,
load_active_sprite_pack,
debug_overlay_visible,
set_debug_overlay_visible
])
.setup(|app| { .setup(|app| {
let app_state: tauri::State<'_, AppState> = app.state(); let app_state: tauri::State<'_, AppState> = app.state();
let runtime_core = Arc::clone(&app_state.runtime_core); let runtime_core = Arc::clone(&app_state.runtime_core);

View File

@@ -39,6 +39,7 @@ recovery_hotkey = "Ctrl+Alt+P"
[frontend] [frontend]
backend = "bevy" backend = "bevy"
debug_overlay_visible = false
``` ```
## Notes ## Notes
@@ -48,3 +49,4 @@ backend = "bevy"
- `window.click_through` is deprecated and ignored at runtime; it is always forced to `false`. - `window.click_through` is deprecated and ignored at runtime; it is always forced to `false`.
- On Windows, `recovery_hotkey` now forces `visible = true` and `always_on_top = true` for recovery. - On Windows, `recovery_hotkey` now forces `visible = true` and `always_on_top = true` for recovery.
- `frontend.backend` selects runtime frontend implementation (`bevy` or `tauri`). - `frontend.backend` selects runtime frontend implementation (`bevy` or `tauri`).
- `frontend.debug_overlay_visible` controls whether tauri window debug diagnostics panel is shown.

View File

@@ -19,7 +19,7 @@ Date: 2026-02-12
| Random backend API tester | Implemented | `scripts/random_backend_tester.py` with `just random-backend-test` and strict variant | | Random backend API tester | Implemented | `scripts/random_backend_tester.py` with `just random-backend-test` and strict variant |
| QA/documentation workflow | Implemented | `docs/QA_WORKFLOW.md`, issue/evidence templates, and `scripts/qa_validate.py` with `just qa-validate` | | QA/documentation workflow | Implemented | `docs/QA_WORKFLOW.md`, issue/evidence templates, and `scripts/qa_validate.py` with `just qa-validate` |
| Shared runtime core | Implemented | `sprimo-runtime-core` now backs both Tauri and Bevy startup, snapshot/config ownership, and API wiring | | Shared runtime core | Implemented | `sprimo-runtime-core` now backs both Tauri and Bevy startup, snapshot/config ownership, and API wiring |
| Tauri alternative frontend | In progress | `sprimo-tauri` now runs runtime-core/API + PixiJS sprite rendering shell; scale updates now auto-fit window to avoid top clipping | | Tauri alternative frontend | In progress | `sprimo-tauri` now runs runtime-core/API + PixiJS sprite rendering shell; scale auto-fit and persisted debug-overlay toggle are implemented |
| Tauri runtime testing workflow | Implemented | `docs/TAURI_RUNTIME_TESTING.md` defines strict workspace testing; packaged mode pending packaging support | | Tauri runtime testing workflow | Implemented | `docs/TAURI_RUNTIME_TESTING.md` defines strict workspace testing; packaged mode pending packaging support |
## Next Major Gaps ## Next Major Gaps

View File

@@ -47,6 +47,7 @@ Frontend:
- Tauri backend exposes: - Tauri backend exposes:
- `current_state` command (structured snapshot DTO) - `current_state` command (structured snapshot DTO)
- `load_active_sprite_pack` command (manifest + atlas as base64 data URL) - `load_active_sprite_pack` command (manifest + atlas as base64 data URL)
- `debug_overlay_visible` / `set_debug_overlay_visible` commands for persisted debug panel control
- `runtime:snapshot` event after command application. - `runtime:snapshot` event after command application.
- React/Vite frontend now renders sprite atlas frames with PixiJS and updates animation/scale - React/Vite frontend now renders sprite atlas frames with PixiJS and updates animation/scale
from runtime snapshot events. from runtime snapshot events.

View File

@@ -74,6 +74,10 @@ An issue touching Tauri runtime behaviors must satisfy all requirements before `
- left-mouse drag moves the window - left-mouse drag moves the window
- window remains non-resizable - window remains non-resizable
- moved position is reflected in runtime snapshot state (`x`, `y`) and persists after restart - moved position is reflected in runtime snapshot state (`x`, `y`) and persists after restart
9. Verify debug-overlay visibility control:
- default startup behavior follows `frontend.debug_overlay_visible` config
- `debug_overlay_visible`/`set_debug_overlay_visible` invoke commands toggle panel at runtime
- toggle state persists after restart
## API + Runtime Contract Checklist ## API + Runtime Contract Checklist
@@ -91,6 +95,7 @@ An issue touching Tauri runtime behaviors must satisfy all requirements before `
- `current_state` output parsed successfully. - `current_state` output parsed successfully.
- `load_active_sprite_pack` returns expected fields. - `load_active_sprite_pack` returns expected fields.
- `runtime:snapshot` event received on runtime command changes. - `runtime:snapshot` event received on runtime command changes.
- `debug_overlay_visible` and `set_debug_overlay_visible` invoke commands work and persist config.
## Evidence Requirements ## Evidence Requirements

View File

@@ -48,6 +48,7 @@ async function fitWindowForScale(pack: UiSpritePack, scale: number): Promise<voi
function App(): JSX.Element { function App(): JSX.Element {
const [snapshot, setSnapshot] = React.useState<UiSnapshot | null>(null); const [snapshot, setSnapshot] = React.useState<UiSnapshot | null>(null);
const [error, setError] = React.useState<string | null>(null); const [error, setError] = React.useState<string | null>(null);
const [debugOverlayVisible, setDebugOverlayVisible] = React.useState(false);
const hostRef = React.useRef<HTMLDivElement | null>(null); const hostRef = React.useRef<HTMLDivElement | null>(null);
const rendererRef = React.useRef<PixiPetRenderer | null>(null); const rendererRef = React.useRef<PixiPetRenderer | null>(null);
const scaleFitRef = React.useRef<number | null>(null); const scaleFitRef = React.useRef<number | null>(null);
@@ -58,13 +59,15 @@ function App(): JSX.Element {
let activePack: UiSpritePack | null = null; let activePack: UiSpritePack | null = null;
Promise.all([ Promise.all([
invoke<UiSpritePack>("load_active_sprite_pack"), invoke<UiSpritePack>("load_active_sprite_pack"),
invoke<UiSnapshot>("current_state") invoke<UiSnapshot>("current_state"),
invoke<boolean>("debug_overlay_visible")
]) ])
.then(async ([pack, initialSnapshot]) => { .then(async ([pack, initialSnapshot, showDebug]) => {
if (!mounted) { if (!mounted) {
return; return;
} }
activePack = pack; activePack = pack;
setDebugOverlayVisible(showDebug);
setSnapshot(initialSnapshot); setSnapshot(initialSnapshot);
if (hostRef.current !== null) { if (hostRef.current !== null) {
rendererRef.current = await PixiPetRenderer.create( rendererRef.current = await PixiPetRenderer.create(
@@ -115,6 +118,32 @@ function App(): JSX.Element {
}; };
}, []); }, []);
const toggleDebugOverlay = React.useCallback(async () => {
try {
const next = !debugOverlayVisible;
const persisted = await invoke<boolean>("set_debug_overlay_visible", {
visible: next
});
setDebugOverlayVisible(persisted);
} catch (err) {
setError(String(err));
}
}, [debugOverlayVisible]);
React.useEffect(() => {
const onKeyDown = (event: KeyboardEvent): void => {
if (!event.ctrlKey || !event.shiftKey || event.code !== "KeyD") {
return;
}
event.preventDefault();
void toggleDebugOverlay();
};
window.addEventListener("keydown", onKeyDown);
return () => {
window.removeEventListener("keydown", onKeyDown);
};
}, [toggleDebugOverlay]);
const onMouseDown = React.useCallback((event: React.MouseEvent<HTMLElement>) => { const onMouseDown = React.useCallback((event: React.MouseEvent<HTMLElement>) => {
if (event.button !== 0) { if (event.button !== 0) {
return; return;
@@ -127,8 +156,11 @@ function App(): JSX.Element {
return ( return (
<main className="app" onMouseDown={onMouseDown}> <main className="app" onMouseDown={onMouseDown}>
<div className="canvas-host" ref={hostRef} /> <div className="canvas-host" ref={hostRef} />
{error !== null && !debugOverlayVisible ? <p className="error-banner">{error}</p> : null}
{debugOverlayVisible ? (
<section className="debug-panel"> <section className="debug-panel">
<h1>sprimo-tauri</h1> <h1>sprimo-tauri</h1>
<p className="hint">Toggle: Ctrl+Shift+D</p>
{error !== null ? <p className="error">{error}</p> : null} {error !== null ? <p className="error">{error}</p> : null}
{snapshot === null ? ( {snapshot === null ? (
<p>Loading snapshot...</p> <p>Loading snapshot...</p>
@@ -149,6 +181,7 @@ function App(): JSX.Element {
</dl> </dl>
)} )}
</section> </section>
) : null}
</main> </main>
); );
} }

View File

@@ -53,3 +53,23 @@ dd {
.error { .error {
color: #fecaca; color: #fecaca;
} }
.hint {
margin: 0 0 8px;
font-size: 12px;
opacity: 0.8;
}
.error-banner {
position: absolute;
top: 8px;
left: 8px;
max-width: 320px;
margin: 0;
padding: 8px 10px;
border: 1px solid rgba(255, 255, 255, 0.2);
background: rgba(127, 29, 29, 0.75);
border-radius: 8px;
color: #fee2e2;
font-size: 12px;
}