點穿改由主程序決定並加入滑鼠診斷:修不能拖曳、不能右鍵
根本原因: 點穿狀態一直是 renderer 判定後再用 IPC 叫主程序切換。renderer 只要例外、 卡住或狀態錯亂(例如視窗永遠不取得焦點、Windows 對背景視窗的 capture 有限制, 放開的那一下落在視窗外就收不到 mouseup,pressed 從此卡在 true),主程序就 再也收不到切換指令,寵物永遠點不到。另外 2.0.8 起每 2 秒的置頂重宣告會在 右鍵選單開著時把寵物視窗抬到選單上面,2.0.10 改成整個方框接滑鼠後,被蓋住 的選單項目就點不到了。 影響: 偶發「不能拖曳、不能右鍵」,而且發生時沒有任何可查的狀態或紀錄。 修法: 1. 主程序每 50 ms 自己輪詢游標,對照 renderer 回報的人物方框與氣泡矩形 (pet:hit-rects)直接呼叫 setIgnoreMouseEvents;決策邏輯抽成 lib/hit-test.js。 renderer 沒回報或掛掉時用版面公式算方框當備援,並自動重載。 2. 拖曳不管因 mouseup、逾時(20 秒)或其他原因結束,都送 pet:drag-ended 讓 renderer 清掉按住/拖曳狀態。 3. 右鍵選單開著時暫停置頂重宣告。 4. /state 回傳 bounds 與 mouse 診斷(是否點穿、游標相對座標、是否在方框/ 氣泡、矩形來源、切換與輪詢次數、renderer 錯誤、拖曳結束原因);renderer 例外、拖曳開始/結束、選單開關寫入事件紀錄。 版號 2.0.11。 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
+30
-24
@@ -148,7 +148,6 @@ function startAnim(anim, now) {
|
||||
|
||||
let bubbleTimer = null;
|
||||
let bubbleSticky = false;
|
||||
let ignoringMouse = true;
|
||||
let hovered = false; // 游標是否在人物方框內
|
||||
let lastPoint = null; // 最後已知的游標位置(視窗相對),主程序輪詢或 mousemove 都會更新
|
||||
let pressed = null;
|
||||
@@ -158,6 +157,10 @@ function log(msg) {
|
||||
try { window.pet.send("pet:log", `[renderer] ${msg}`); } catch { /* ignore */ }
|
||||
}
|
||||
|
||||
// renderer 的例外以前只會出現在 DevTools,包成 exe 後根本看不到;寫進事件紀錄才查得到
|
||||
window.addEventListener("error", (e) => log(`error: ${e.message} (${e.filename}:${e.lineno})`));
|
||||
window.addEventListener("unhandledrejection", (e) => log(`error: unhandled rejection: ${e.reason?.message || e.reason}`));
|
||||
|
||||
// ---------- 載入 ----------
|
||||
|
||||
function loadPet(pet) {
|
||||
@@ -186,6 +189,7 @@ function applyScale(s) {
|
||||
canvas.height = Math.round(PET_H * s);
|
||||
canvas.style.width = `${canvas.width}px`;
|
||||
canvas.style.height = `${canvas.height}px`;
|
||||
reportHitRects();
|
||||
}
|
||||
|
||||
// 量一格的人物輪廓(用來偵測圖檔裡整列被畫小的缺陷)
|
||||
@@ -383,6 +387,7 @@ function say(text, ms, sticky = false, title = "") {
|
||||
bubble.title = title ? `${title}\n${text}` : text;
|
||||
bubble.classList.remove("hidden");
|
||||
bubbleSticky = sticky;
|
||||
reportHitRects();
|
||||
if (ms > 0) bubbleTimer = setTimeout(hideBubble, ms);
|
||||
}
|
||||
|
||||
@@ -390,6 +395,7 @@ function hideBubble() {
|
||||
clearTimeout(bubbleTimer);
|
||||
bubble.classList.add("hidden");
|
||||
bubbleSticky = false;
|
||||
reportHitRects();
|
||||
}
|
||||
|
||||
// ---------- 事件 ----------
|
||||
@@ -621,16 +627,18 @@ setInterval(() => {
|
||||
|
||||
// ---------- 滑鼠:點穿 / 拖曳 / 點擊 / 右鍵 ----------
|
||||
|
||||
function overBubble(x, y) {
|
||||
if (bubble.classList.contains("hidden")) return false;
|
||||
const r = bubble.getBoundingClientRect();
|
||||
return x >= r.left && x <= r.right && y >= r.top && y <= r.bottom;
|
||||
// 主程序自己輪詢游標並決定點穿(見 main.js 的 updateIgnore),這裡只要回報「哪裡算是寵物」:
|
||||
// 人物方框與(顯示中的)氣泡的視窗相對矩形。版面一變(縮放、氣泡出現/消失/改字)就重報一次。
|
||||
// 用方框不用逐像素 alpha:sprite 每一格的輪廓不同,人物的手腳與邊緣會隨動畫在透明/不透明之間
|
||||
// 切換,逐像素會讓點穿跟著動畫抖,按下去那一瞬間剛好是透明格就穿到底下去。Codex 也是用矩形。
|
||||
function reportHitRects() {
|
||||
const rect = (r) => ({ x: Math.round(r.left), y: Math.round(r.top), w: Math.round(r.width), h: Math.round(r.height) });
|
||||
const b = bubble.classList.contains("hidden") ? null : bubble.getBoundingClientRect();
|
||||
window.pet.send("pet:hit-rects", { box: rect(canvas.getBoundingClientRect()), bubble: b ? rect(b) : null });
|
||||
}
|
||||
window.addEventListener("resize", reportHitRects);
|
||||
|
||||
// hover 與點穿都用人物方框,不用逐像素 alpha:sprite 每一格的輪廓不同,人物的手腳與邊緣
|
||||
// 會隨動畫在透明/不透明之間切換,逐像素會讓游標停著不動時也在 true/false 之間高速抖動;
|
||||
// 點穿跟著抖,按下去那一瞬間剛好是透明格,滑鼠事件就穿到底下的視窗去了(看起來就是
|
||||
// 「又被穿透了、不能拖曳也不能右鍵」)。Codex 的 pet-area-hover 同樣是用矩形算的。
|
||||
// hover 判定同樣用人物方框(Codex 的 pet-area-hover 也是 rect)
|
||||
function overPetArea(x, y) {
|
||||
const r = canvas.getBoundingClientRect();
|
||||
return x >= r.left && x < r.right && y >= r.top && y < r.bottom;
|
||||
@@ -655,21 +663,10 @@ function setHovered(on) {
|
||||
if (overlay?.fromHover) overlay = null;
|
||||
}
|
||||
|
||||
// 命中判定集中在這裡:mousemove 與主程序輪詢都走同一條路。
|
||||
// 主程序每 50 ms 一定會送座標過來,所以就算 mousemove 沒被轉發進來也能復原。
|
||||
// 命中判定只剩 hover(跳三下):mousemove 與主程序輪詢都走同一條路
|
||||
function refreshHitTest() {
|
||||
if (!lastPoint || pressed || drag) return; // 拖曳中不要動點穿狀態
|
||||
const { x, y } = lastPoint;
|
||||
const inArea = overPetArea(x, y);
|
||||
setHovered(inArea);
|
||||
setIgnore(!(inArea || overBubble(x, y)));
|
||||
}
|
||||
|
||||
function setIgnore(ignore) {
|
||||
if (ignore === ignoringMouse) return;
|
||||
ignoringMouse = ignore;
|
||||
window.pet.send("pet:ignore-mouse", ignore);
|
||||
canvas.classList.toggle("grab", !ignore);
|
||||
if (!lastPoint || pressed || drag) return;
|
||||
setHovered(overPetArea(lastPoint.x, lastPoint.y));
|
||||
}
|
||||
|
||||
window.addEventListener("mousemove", (e) => {
|
||||
@@ -690,7 +687,6 @@ document.addEventListener("mouseout", (e) => {
|
||||
if (e.relatedTarget || pressed) return;
|
||||
lastPoint = null;
|
||||
setHovered(false);
|
||||
setIgnore(true);
|
||||
});
|
||||
|
||||
window.addEventListener("mousedown", (e) => {
|
||||
@@ -727,6 +723,16 @@ window.pet.on("pet:drag-move", ({ dx }) => {
|
||||
if (drag && Math.abs(dx) >= 4) drag.dir = dx < 0 ? "left" : "right";
|
||||
});
|
||||
|
||||
// 主程序結束了拖曳(mouseup、逾時或其他原因):不管這邊有沒有收到 mouseup,一律把狀態清掉
|
||||
window.pet.on("pet:drag-ended", () => {
|
||||
pressed = null;
|
||||
drag = null;
|
||||
canvas.classList.remove("grabbing");
|
||||
});
|
||||
|
||||
// 主程序切了點穿狀態:只負責換游標樣式
|
||||
window.pet.on("pet:ignore-state", (ignore) => canvas.classList.toggle("grab", !ignore));
|
||||
|
||||
function onClick() {
|
||||
if (!bubble.classList.contains("hidden") && !bubbleSticky) {
|
||||
hideBubble();
|
||||
|
||||
Reference in New Issue
Block a user