fix(stage-tamagotchi): harden caption overlay follow+mouse handling (#1402)

This commit is contained in:
Iro
2026-03-17 14:23:04 +08:00
committed by GitHub
parent c1dad52537
commit eb53744c08
4 changed files with 75 additions and 19 deletions
@@ -194,9 +194,12 @@ export function setupCaptionWindowManager(params: {
const settleTo = (toX: number, toY: number) => {
if (win.isDestroyed())
return
if (!Number.isFinite(toX) || !Number.isFinite(toY))
return
const b = win.getBounds()
state.x = b.x
state.y = b.y
state.x = Number.isFinite(b.x) ? b.x : 0
state.y = Number.isFinite(b.y) ? b.y : 0
animation?.pause()
animation = animate(state, {
x: toX,
@@ -207,8 +210,13 @@ export function setupCaptionWindowManager(params: {
onRender: () => {
if (win.isDestroyed())
return
if (!Number.isFinite(state.x) || !Number.isFinite(state.y))
return
const toX = Math.round(state.x)
const toY = Math.round(state.y)
lastProgrammaticMoveAt = Date.now()
win.setPosition(state.x, state.y)
win.setPosition(toX, toY)
},
})
}
@@ -219,6 +227,9 @@ export function setupCaptionWindowManager(params: {
let lastAppliedTy = Number.NaN
const moveThrottled = throttle(() => {
if (win.isDestroyed())
return
const stored = getConfig()?.matrices[matrixHash]?.relativeToMain ?? initialOffset
const main = params.mainWindow.getBounds()
const b = win.getBounds()
@@ -277,6 +288,18 @@ export function setupCaptionWindowManager(params: {
}
}
function applyIgnoreMouseEvents(win: BrowserWindow, ignore: boolean) {
try {
if (ignore)
win.setIgnoreMouseEvents(true, { forward: true })
else
win.setIgnoreMouseEvents(false)
}
catch {
// ignore failures during early window lifecycle
}
}
const reusable = createReusableWindow(async () => {
// TODO: once we refactored eventa to support window-namespaced contexts,
// we can remove the setMaxListeners call below since eventa will be able to dispatch and
@@ -290,6 +313,8 @@ export function setupCaptionWindowManager(params: {
await setupBaseWindowElectronInvokes({ context, window, serverChannel: params.serverChannel, i18n: params.i18n })
applyIgnoreMouseEvents(window, isFollowing)
const cfg = getConfig()
const saved = cfg?.matrices?.[matrixHash]?.bounds
@@ -359,22 +384,26 @@ export function setupCaptionWindowManager(params: {
async function setFollowWindow(isFollowingWindow: boolean) {
isFollowing = isFollowingWindow
const window = await reusable.getWindow()
applyIgnoreMouseEvents(window, isFollowing)
if (isFollowing) {
// Compute and persist current relative offset based on existing positions
const rel = computeRelativeOffset(window)
const cfg = getConfig() ?? { isFollowing, matrices: {} }
cfg.matrices[matrixHash] = { ...cfg.matrices[matrixHash], relativeToMain: rel }
updateConfig(cfg)
const config = getConfig() ?? { isFollowing, matrices: {} }
config.isFollowing = isFollowing
config.matrices[matrixHash] = { ...config.matrices[matrixHash], relativeToMain: rel }
updateConfig(config)
// Start following main without re-docking; keep current position
followMainWindow(window)
}
else {
detachFromMain()
}
const config = getConfig() ?? { isFollowing, matrices: {} }
config.isFollowing = isFollowing
updateConfig(config)
const config = getConfig() ?? { isFollowing, matrices: {} }
config.isFollowing = isFollowing
updateConfig(config)
}
// Keep window visible after toggle
window.show()
@@ -399,6 +428,8 @@ export function setupCaptionWindowManager(params: {
async function resetToSide() {
const window = await reusable.getWindow()
applyIgnoreMouseEvents(window, isFollowing)
// Prevent user-move persistence from overwriting our programmatic move
lastProgrammaticMoveAt = Date.now()
const initialBounds = computeInitialCaptionBounds({ mainWindow: params.mainWindow })
@@ -21,7 +21,7 @@ menu:
close_caption: Close Caption...
caption_overlay: Lớp phủ phụ đề
follow_window: Theo cửa sổ
reset_position: Đặt lại vị trí
reset_position: Đặt lại vị trí
devtools: Công cụ phát triển
troubleshoot_beatsync: Sửa lỗi BeatSync...
quit: Thoát ra
@@ -1,5 +1,5 @@
import type { VRM } from '@pixiv/three-vrm'
import type { AnimationMixer, Material, WebGLRenderer } from 'three'
import type { AnimationMixer, Material, Object3D, WebGLRenderer } from 'three'
import type {
ThreeRendererMemorySnapshot,
@@ -74,7 +74,7 @@ export function createVrmSceneSummarySnapshot(input?: VrmSceneSnapshotInput | VR
const materials = new Set<Material>()
const textures = new Set<Texture>()
activeVrm.scene.traverse((child) => {
activeVrm.scene.traverse((child: Object3D) => {
if (child instanceof Mesh)
meshCount += 1
+30 -5
View File
@@ -1836,7 +1836,7 @@ importers:
version: 5.0.0(vue@3.5.29(typescript@5.9.3))
'@intlify/unplugin-vue-i18n':
specifier: ^11.0.7
version: 11.0.7(@vue/compiler-dom@3.5.29)(eslint@9.39.3(jiti@2.6.1))(rollup@4.59.0)(typescript@5.9.3)(vite@8.0.0-beta.15(@types/node@24.10.14)(esbuild@0.27.2)(jiti@2.6.1)(less@4.5.1)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2))(vue-i18n@11.2.8(vue@3.5.29(typescript@5.9.3)))(vue@3.5.29(typescript@5.9.3))
version: 11.0.7(@vue/compiler-dom@3.5.29)(eslint@9.39.3(jiti@2.6.1))(rollup@4.59.0)(typescript@5.9.3)(vite@7.3.1(@types/node@24.10.14)(jiti@2.6.1)(less@4.5.1)(lightningcss@1.31.1)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2))(vue-i18n@11.2.8(vue@3.5.29(typescript@5.9.3)))(vue@3.5.29(typescript@5.9.3))
'@mdit/plugin-footnote':
specifier: ^0.22.4
version: 0.22.4(markdown-it@14.1.1)
@@ -1896,7 +1896,7 @@ importers:
version: 0.1.3
unplugin-yaml:
specifier: ^4.0.0
version: 4.0.0(esbuild@0.27.2)(rolldown@1.0.0-rc.3)(rollup@4.59.0)(vite@8.0.0-beta.15(@types/node@24.10.14)(esbuild@0.27.2)(jiti@2.6.1)(less@4.5.1)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2))
version: 4.0.0(@nuxt/kit@3.20.2(magicast@0.5.2))(esbuild@0.27.2)(rolldown@1.0.0-rc.3)(rollup@4.59.0)(vite@7.3.1(@types/node@24.10.14)(jiti@2.6.1)(less@4.5.1)(lightningcss@1.31.1)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2))
vitepress:
specifier: ^2.0.0-alpha.16
version: 2.0.0-alpha.16(@types/node@24.10.14)(change-case@5.4.4)(fuse.js@7.1.0)(idb-keyval@6.2.2)(jiti@2.6.1)(less@4.5.1)(lightningcss@1.31.1)(nprogress@0.2.0)(postcss@8.5.6)(terser@5.46.0)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2)
@@ -3234,7 +3234,7 @@ importers:
version: 14.1.0(vue@3.5.29(typescript@5.9.3))
'@wxt-dev/module-vue':
specifier: ^1.0.3
version: 1.0.3(vite@8.0.0-beta.15(@types/node@24.10.14)(esbuild@0.27.2)(jiti@2.6.1)(less@4.5.1)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2))(vue@3.5.29(typescript@5.9.3))(wxt@0.20.18(@types/node@24.10.14)(canvas@3.2.1)(eslint@9.39.3(jiti@2.6.1))(jiti@2.6.1)(less@4.5.1)(lightningcss@1.31.1)(rollup@4.59.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2))
version: 1.0.3(vite@7.3.1(@types/node@24.10.14)(jiti@2.6.1)(less@4.5.1)(lightningcss@1.31.1)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2))(vue@3.5.29(typescript@5.9.3))(wxt@0.20.18(@types/node@24.10.14)(canvas@3.2.1)(eslint@9.39.3(jiti@2.6.1))(jiti@2.6.1)(less@4.5.1)(lightningcss@1.31.1)(rollup@4.59.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2))
nanoid:
specifier: ^5.1.6
version: 5.1.6
@@ -19667,6 +19667,31 @@ snapshots:
- supports-color
- typescript
'@intlify/unplugin-vue-i18n@11.0.7(@vue/compiler-dom@3.5.29)(eslint@9.39.3(jiti@2.6.1))(rollup@4.59.0)(typescript@5.9.3)(vite@7.3.1(@types/node@24.10.14)(jiti@2.6.1)(less@4.5.1)(lightningcss@1.31.1)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2))(vue-i18n@11.2.8(vue@3.5.29(typescript@5.9.3)))(vue@3.5.29(typescript@5.9.3))':
dependencies:
'@eslint-community/eslint-utils': 4.9.1(eslint@9.39.3(jiti@2.6.1))
'@intlify/bundle-utils': 11.0.7(vue-i18n@11.2.8(vue@3.5.29(typescript@5.9.3)))
'@intlify/shared': 11.2.8
'@intlify/vue-i18n-extensions': 8.0.0(@intlify/shared@11.2.8)(@vue/compiler-dom@3.5.29)(vue-i18n@11.2.8(vue@3.5.29(typescript@5.9.3)))(vue@3.5.29(typescript@5.9.3))
'@rollup/pluginutils': 5.3.0(rollup@4.59.0)
'@typescript-eslint/scope-manager': 8.56.1
'@typescript-eslint/typescript-estree': 8.56.1(typescript@5.9.3)
debug: 4.4.3
fast-glob: 3.3.3
pathe: 2.0.3
picocolors: 1.1.1
unplugin: 2.3.11
vite: 7.3.1(@types/node@24.10.14)(jiti@2.6.1)(less@4.5.1)(lightningcss@1.31.1)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2)
vue: 3.5.29(typescript@5.9.3)
optionalDependencies:
vue-i18n: 11.2.8(vue@3.5.29(typescript@5.9.3))
transitivePeerDependencies:
- '@vue/compiler-dom'
- eslint
- rollup
- supports-color
- typescript
'@intlify/unplugin-vue-i18n@11.0.7(@vue/compiler-dom@3.5.29)(eslint@9.39.3(jiti@2.6.1))(rollup@4.59.0)(typescript@5.9.3)(vite@8.0.0-beta.15(@types/node@24.10.14)(esbuild@0.27.2)(jiti@2.6.1)(less@4.5.1)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2))(vue-i18n@11.2.8(vue@3.5.29(typescript@5.9.3)))(vue@3.5.29(typescript@5.9.3))':
dependencies:
'@eslint-community/eslint-utils': 4.9.1(eslint@9.39.3(jiti@2.6.1))
@@ -23891,9 +23916,9 @@ snapshots:
'@types/filesystem': 0.0.36
'@types/har-format': 1.2.16
'@wxt-dev/module-vue@1.0.3(vite@8.0.0-beta.15(@types/node@24.10.14)(esbuild@0.27.2)(jiti@2.6.1)(less@4.5.1)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2))(vue@3.5.29(typescript@5.9.3))(wxt@0.20.18(@types/node@24.10.14)(canvas@3.2.1)(eslint@9.39.3(jiti@2.6.1))(jiti@2.6.1)(less@4.5.1)(lightningcss@1.31.1)(rollup@4.59.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2))':
'@wxt-dev/module-vue@1.0.3(vite@7.3.1(@types/node@24.10.14)(jiti@2.6.1)(less@4.5.1)(lightningcss@1.31.1)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2))(vue@3.5.29(typescript@5.9.3))(wxt@0.20.18(@types/node@24.10.14)(canvas@3.2.1)(eslint@9.39.3(jiti@2.6.1))(jiti@2.6.1)(less@4.5.1)(lightningcss@1.31.1)(rollup@4.59.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2))':
dependencies:
'@vitejs/plugin-vue': 6.0.4(vite@8.0.0-beta.15(@types/node@24.10.14)(esbuild@0.27.2)(jiti@2.6.1)(less@4.5.1)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2))(vue@3.5.29(typescript@5.9.3))
'@vitejs/plugin-vue': 6.0.4(vite@7.3.1(@types/node@24.10.14)(jiti@2.6.1)(less@4.5.1)(lightningcss@1.31.1)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2))(vue@3.5.29(typescript@5.9.3))
wxt: 0.20.18(@types/node@24.10.14)(canvas@3.2.1)(eslint@9.39.3(jiti@2.6.1))(jiti@2.6.1)(less@4.5.1)(lightningcss@1.31.1)(rollup@4.59.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2)
transitivePeerDependencies:
- vite