Skip to content

Commit 29f8951

Browse files
committed
fix: capture pet drag pointer / 捕获桌宠拖拽指针
1 parent 38c8a42 commit 29f8951

2 files changed

Lines changed: 108 additions & 12 deletions

File tree

assets/inject/pet-real-mouse-inject.js

Lines changed: 98 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
(() => {
2-
const runtimeVersion = "5";
2+
const runtimeVersion = "6";
33
const existing = window.__codexPlusPetRealMouseLook;
44
if (existing?.version === runtimeVersion && existing?.isReady?.()) return;
55
existing?.stop?.();
@@ -9,6 +9,54 @@
99
const activationRadius = 480;
1010
const movementThreshold = 2;
1111
const movementHoldMs = 1400;
12+
const interactionOwner = "real-mouse";
13+
14+
const interaction = (() => {
15+
const key = "__codexPlusPetInteraction";
16+
const current = window[key];
17+
if (current?.version === 1 && typeof current.begin === "function") return current;
18+
const state = { pointerId: null, owner: "", captureTarget: null };
19+
const next = {
20+
version: 1,
21+
begin(event, owner, captureTarget) {
22+
if (state.pointerId != null) return state.pointerId === event.pointerId && state.owner === owner;
23+
state.pointerId = event.pointerId;
24+
state.owner = owner;
25+
state.captureTarget = captureTarget || event.target;
26+
try {
27+
state.captureTarget?.setPointerCapture?.(event.pointerId);
28+
} catch {
29+
}
30+
return true;
31+
},
32+
owns(event, owner) {
33+
return state.pointerId === event.pointerId && state.owner === owner;
34+
},
35+
active() {
36+
return state.pointerId != null;
37+
},
38+
end(event, owner) {
39+
if (!next.owns(event, owner)) return false;
40+
try {
41+
state.captureTarget?.releasePointerCapture?.(event.pointerId);
42+
} catch {
43+
}
44+
state.pointerId = null;
45+
state.owner = "";
46+
state.captureTarget = null;
47+
return true;
48+
},
49+
cancel(owner) {
50+
if (state.owner !== owner) return false;
51+
state.pointerId = null;
52+
state.owner = "";
53+
state.captureTarget = null;
54+
return true;
55+
},
56+
};
57+
window[key] = next;
58+
return next;
59+
})();
1260

1361
let stopped = false;
1462
let acceptsUpdates = true;
@@ -123,7 +171,7 @@
123171
if (stopped || !acceptsUpdates || updateInFlight) return;
124172
const mascot = document.querySelector(mascotSelector);
125173
runtime.mascotHovered = false;
126-
if (!mascot || document.visibilityState !== "visible" || dragging || nativeCursorActive) {
174+
if (!mascot || document.visibilityState !== "visible" || interaction.active() || nativeCursorActive) {
127175
if (!nativeCursorActive) clearSyntheticPoint();
128176
return;
129177
}
@@ -154,7 +202,7 @@
154202
lastMoveAt = Date.now();
155203
}
156204
const active = distance <= activationRadius && Date.now() - lastMoveAt <= movementHoldMs;
157-
if (!active || nativeCursorActive || dragging) {
205+
if (!active || nativeCursorActive || interaction.active()) {
158206
clearSyntheticPoint();
159207
return;
160208
}
@@ -168,16 +216,53 @@
168216
}
169217
}
170218

219+
function mascotAtPoint(event) {
220+
if (!Number.isFinite(event?.clientX) || !Number.isFinite(event?.clientY)) return null;
221+
const mascot = document.querySelector(mascotSelector);
222+
if (!mascot) return null;
223+
const bounds = mascot.getBoundingClientRect();
224+
return event.clientX >= bounds.left
225+
&& event.clientX <= bounds.right
226+
&& event.clientY >= bounds.top
227+
&& event.clientY <= bounds.bottom
228+
? mascot
229+
: null;
230+
}
231+
171232
function onPointerDown(event) {
172-
if (!(event.target instanceof Element) || !event.target.closest(mascotSelector)) return;
233+
const target = event.target instanceof Element ? event.target : null;
234+
const mascot = target?.closest(mascotSelector) || mascotAtPoint(event);
235+
if (!mascot) return;
236+
const ownsPointer = interaction.begin(event, interactionOwner, target || mascot);
237+
if (!ownsPointer) return;
173238
dragging = true;
174239
clearSyntheticPoint();
240+
event.preventDefault?.();
241+
}
242+
243+
function onPointerMove(event) {
244+
if (!dragging || !interaction.owns(event, interactionOwner)) return;
245+
event.preventDefault?.();
175246
}
176247

177-
function onPointerUp() {
248+
function releaseDragging(event) {
249+
if (!dragging) return;
250+
if (event) {
251+
if (interaction.owns(event, interactionOwner)) interaction.end(event, interactionOwner);
252+
} else {
253+
interaction.cancel(interactionOwner);
254+
}
178255
dragging = false;
179256
}
180257

258+
function onPointerUp(event) {
259+
releaseDragging(event);
260+
}
261+
262+
function onWindowBlur() {
263+
releaseDragging();
264+
}
265+
181266
function stop() {
182267
if (stopped) return;
183268
stopped = true;
@@ -195,21 +280,24 @@
195280
}
196281
syntheticActive = false;
197282
document.removeEventListener("pointerdown", onPointerDown, true);
283+
document.removeEventListener("pointermove", onPointerMove, true);
198284
document.removeEventListener("pointerup", onPointerUp, true);
199285
document.removeEventListener("pointercancel", onPointerUp, true);
200286
document.removeEventListener("lostpointercapture", onPointerUp, true);
201-
window.removeEventListener("blur", onPointerUp);
287+
window.removeEventListener("blur", onWindowBlur);
288+
interaction.cancel(interactionOwner);
202289
unsubscribe?.();
203290
if (window.__codexPlusPetRealMouseLook?.version === runtimeVersion) {
204291
delete window.__codexPlusPetRealMouseLook;
205292
}
206293
}
207294

208295
document.addEventListener("pointerdown", onPointerDown, true);
296+
document.addEventListener("pointermove", onPointerMove, true);
209297
document.addEventListener("pointerup", onPointerUp, true);
210298
document.addEventListener("pointercancel", onPointerUp, true);
211299
document.addEventListener("lostpointercapture", onPointerUp, true);
212-
window.addEventListener("blur", onPointerUp);
300+
window.addEventListener("blur", onWindowBlur);
213301
const runtime = {
214302
version: runtimeVersion,
215303
transport: "cdp-push",
@@ -219,6 +307,9 @@
219307
isReady() {
220308
return !stopped && acceptsUpdates;
221309
},
310+
isVisualOverrideActive() {
311+
return nativeCursorActive || syntheticActive || interaction.active() || this.mascotHovered;
312+
},
222313
stop,
223314
updateScreenPoint(point) {
224315
if (!this.isReady()) return false;

crates/codex-plus-core/tests/cdp_bridge.rs

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -76,21 +76,26 @@ fn pet_real_mouse_script_uses_cdp_push_and_native_avatar_event() {
7676
assert!(script.contains("localPoint.y <= bounds.bottom"));
7777
assert!(!script.contains("document.elementFromPoint"));
7878
assert!(script.contains("if (mascotHovered)"));
79-
assert!(
80-
script
81-
.contains("document.visibilityState !== \"visible\" || dragging || nativeCursorActive")
82-
);
79+
assert!(script.contains(
80+
"document.visibilityState !== \"visible\" || interaction.active() || nativeCursorActive"
81+
));
8382
assert!(script.contains("sendPoint(null).catch(disableUpdates)"));
8483
assert!(script.contains("void cleared.catch(disableUpdates)"));
8584
assert!(script.contains("dispatcher.dispatchHostMessage({ type: eventType, point: null })"));
85+
assert!(script.contains("__codexPlusPetInteraction"));
86+
assert!(script.contains("setPointerCapture"));
87+
assert!(script.contains("releasePointerCapture"));
88+
assert!(script.contains("mascotAtPoint"));
89+
assert!(script.contains("if (!ownsPointer) return"));
90+
assert!(script.contains("document.addEventListener(\"pointermove\", onPointerMove, true)"));
8691
assert!(script.contains("movementHoldMs = 1400"));
8792
assert!(script.contains("activationRadius = 480"));
8893
assert!(!script.contains("/pet/cursor-position"));
8994
assert!(!script.contains("X-Codex-Plus-Pet-Token"));
9095
assert!(script.contains("delete window.__codexPlusPetRealMouseLook"));
9196
assert!(script.contains("retired during dispatcher setup"));
9297
assert!(script.contains("nextUnsubscribe?.()"));
93-
assert!(script.contains("const runtimeVersion = \"5\""));
98+
assert!(script.contains("const runtimeVersion = \"6\""));
9499
}
95100

96101
#[test]

0 commit comments

Comments
 (0)