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 })