@forgeax/engine-input 0.1.20 → 0.1.21
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +15 -0
- package/dist/.tsbuildinfo +1 -1
- package/dist/__tests__/browser-backend-edge-latch.test.d.ts +2 -0
- package/dist/__tests__/browser-backend-edge-latch.test.d.ts.map +1 -0
- package/dist/__tests__/browser-edge-latch.browser.test.d.ts +2 -0
- package/dist/__tests__/browser-edge-latch.browser.test.d.ts.map +1 -0
- package/dist/__tests__/pointer-lock.browser.test.d.ts +2 -0
- package/dist/__tests__/pointer-lock.browser.test.d.ts.map +1 -0
- package/dist/browser-backend.d.ts.map +1 -1
- package/dist/composite-backend.d.ts +4 -1
- package/dist/composite-backend.d.ts.map +1 -1
- package/dist/frame-start-scan-system.d.ts +2 -2
- package/dist/index.mjs +118 -35
- package/dist/index.mjs.map +1 -1
- package/dist/input-snapshot.d.ts +13 -3
- package/dist/input-snapshot.d.ts.map +1 -1
- package/package.json +3 -3
- package/src/__tests__/browser-backend-edge-latch.test.ts +154 -0
- package/src/__tests__/browser-backend-focus-loss.test.ts +4 -0
- package/src/__tests__/browser-backend-lock-error.test.ts +25 -11
- package/src/__tests__/browser-backend-lock-gate.test.ts +8 -1
- package/src/__tests__/browser-edge-latch.browser.test.ts +42 -0
- package/src/__tests__/composite-backend.test.ts +18 -0
- package/src/__tests__/input-snapshot.test.ts +35 -0
- package/src/__tests__/pointer-lock.browser.test.ts +59 -0
- package/src/browser-backend.ts +71 -19
- package/src/composite-backend.ts +70 -3
- package/src/frame-start-scan-system.ts +2 -2
- package/src/input-snapshot.ts +45 -25
package/dist/index.mjs
CHANGED
|
@@ -596,7 +596,11 @@ function attachBrowserInputBackend(canvas, options = {}) {
|
|
|
596
596
|
const upEdges = /* @__PURE__ */ new Set();
|
|
597
597
|
const heldCodes = /* @__PURE__ */ new Set();
|
|
598
598
|
const upCodeEdges = /* @__PURE__ */ new Set();
|
|
599
|
+
const pressedKeys = /* @__PURE__ */ new Set();
|
|
600
|
+
const pressedCodes = /* @__PURE__ */ new Set();
|
|
599
601
|
const buttons = [false, false, false];
|
|
602
|
+
const pressedButtons = [false, false, false];
|
|
603
|
+
const releasedButtons = [false, false, false];
|
|
600
604
|
let mvx = 0;
|
|
601
605
|
let mvy = 0;
|
|
602
606
|
let wheelAccum = 0;
|
|
@@ -605,12 +609,18 @@ function attachBrowserInputBackend(canvas, options = {}) {
|
|
|
605
609
|
let w3cLocked = false;
|
|
606
610
|
let providerLocked = false;
|
|
607
611
|
let gameGate = true;
|
|
612
|
+
function emitLockError(path, cause) {
|
|
613
|
+
try {
|
|
614
|
+
options.onLockError?.({ path, cause });
|
|
615
|
+
} catch {
|
|
616
|
+
}
|
|
617
|
+
}
|
|
608
618
|
function releaseProviderLock() {
|
|
609
619
|
if (providerLocked && options.lockProvider?.exitLock) {
|
|
610
620
|
try {
|
|
611
621
|
options.lockProvider.exitLock();
|
|
612
622
|
} catch (err) {
|
|
613
|
-
|
|
623
|
+
emitLockError("provider", err);
|
|
614
624
|
}
|
|
615
625
|
}
|
|
616
626
|
providerLocked = false;
|
|
@@ -679,11 +689,11 @@ function attachBrowserInputBackend(canvas, options = {}) {
|
|
|
679
689
|
clear();
|
|
680
690
|
return;
|
|
681
691
|
}
|
|
692
|
+
if (!heldKeys.has(ev.key)) pressedKeys.add(ev.key);
|
|
682
693
|
heldKeys.add(ev.key);
|
|
683
|
-
upEdges.delete(ev.key);
|
|
684
694
|
if (ev.code) {
|
|
695
|
+
if (!heldCodes.has(ev.code)) pressedCodes.add(ev.code);
|
|
685
696
|
heldCodes.add(ev.code);
|
|
686
|
-
upCodeEdges.delete(ev.code);
|
|
687
697
|
}
|
|
688
698
|
if (ev.key === "Escape" && providerLocked) {
|
|
689
699
|
releaseProviderLock();
|
|
@@ -707,6 +717,7 @@ function attachBrowserInputBackend(canvas, options = {}) {
|
|
|
707
717
|
}
|
|
708
718
|
if (ev.pointerType === "mouse") {
|
|
709
719
|
if (ev.button === 0 || ev.button === 1 || ev.button === 2) {
|
|
720
|
+
if (!buttons[ev.button]) pressedButtons[ev.button] = true;
|
|
710
721
|
buttons[ev.button] = true;
|
|
711
722
|
}
|
|
712
723
|
}
|
|
@@ -762,6 +773,7 @@ function attachBrowserInputBackend(canvas, options = {}) {
|
|
|
762
773
|
if (isUiEvent(ev)) return;
|
|
763
774
|
if (ev.pointerType === "mouse") {
|
|
764
775
|
if (ev.button === 0 || ev.button === 1 || ev.button === 2) {
|
|
776
|
+
if (buttons[ev.button]) releasedButtons[ev.button] = true;
|
|
765
777
|
buttons[ev.button] = false;
|
|
766
778
|
}
|
|
767
779
|
}
|
|
@@ -843,6 +855,14 @@ function attachBrowserInputBackend(canvas, options = {}) {
|
|
|
843
855
|
function onBlur() {
|
|
844
856
|
upEdges.clear();
|
|
845
857
|
upCodeEdges.clear();
|
|
858
|
+
pressedKeys.clear();
|
|
859
|
+
pressedCodes.clear();
|
|
860
|
+
pressedButtons[0] = false;
|
|
861
|
+
pressedButtons[1] = false;
|
|
862
|
+
pressedButtons[2] = false;
|
|
863
|
+
releasedButtons[0] = false;
|
|
864
|
+
releasedButtons[1] = false;
|
|
865
|
+
releasedButtons[2] = false;
|
|
846
866
|
heldKeys.clear();
|
|
847
867
|
heldCodes.clear();
|
|
848
868
|
buttons[0] = false;
|
|
@@ -895,6 +915,10 @@ function attachBrowserInputBackend(canvas, options = {}) {
|
|
|
895
915
|
if (wasLocked && !w3cLocked && !providerLocked) onBlur();
|
|
896
916
|
}
|
|
897
917
|
safeAdd(doc, "pointerlockchange", onPointerLockChange);
|
|
918
|
+
function onPointerLockError(event) {
|
|
919
|
+
emitLockError("w3c", event);
|
|
920
|
+
}
|
|
921
|
+
safeAdd(doc, "pointerlockerror", onPointerLockError);
|
|
898
922
|
let _prevTouchAction;
|
|
899
923
|
if (canvas.style) {
|
|
900
924
|
_prevTouchAction = canvas.style.touchAction;
|
|
@@ -903,7 +927,6 @@ function attachBrowserInputBackend(canvas, options = {}) {
|
|
|
903
927
|
function onCanvasClick() {
|
|
904
928
|
if (!gameGate) return;
|
|
905
929
|
if (options.pointerLockAllowed && !options.pointerLockAllowed()) return;
|
|
906
|
-
if (typeof doc.hasFocus === "function" && !doc.hasFocus()) return;
|
|
907
930
|
if (options.lockProvider) {
|
|
908
931
|
providerLocked = true;
|
|
909
932
|
try {
|
|
@@ -911,28 +934,26 @@ function attachBrowserInputBackend(canvas, options = {}) {
|
|
|
911
934
|
if (result && typeof result.catch === "function") {
|
|
912
935
|
result.catch((cause) => {
|
|
913
936
|
providerLocked = false;
|
|
914
|
-
|
|
915
|
-
options.onLockError({ path: "provider", cause });
|
|
916
|
-
}
|
|
937
|
+
emitLockError("provider", cause);
|
|
917
938
|
});
|
|
918
939
|
}
|
|
919
940
|
} catch (cause) {
|
|
920
941
|
providerLocked = false;
|
|
921
|
-
|
|
922
|
-
options.onLockError({ path: "provider", cause });
|
|
923
|
-
}
|
|
942
|
+
emitLockError("provider", cause);
|
|
924
943
|
}
|
|
925
944
|
return;
|
|
926
945
|
}
|
|
927
946
|
const fn = canvas.requestPointerLock;
|
|
928
947
|
if (typeof fn !== "function") return;
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
r.catch
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
}
|
|
935
|
-
}
|
|
948
|
+
try {
|
|
949
|
+
const r = fn.call(canvas);
|
|
950
|
+
if (r && typeof r.catch === "function") {
|
|
951
|
+
r.catch((cause) => {
|
|
952
|
+
emitLockError("w3c", cause);
|
|
953
|
+
});
|
|
954
|
+
}
|
|
955
|
+
} catch (cause) {
|
|
956
|
+
emitLockError("w3c", cause);
|
|
936
957
|
}
|
|
937
958
|
}
|
|
938
959
|
safeAdd(canvas, "click", onCanvasClick);
|
|
@@ -1007,7 +1028,11 @@ function attachBrowserInputBackend(canvas, options = {}) {
|
|
|
1007
1028
|
upKeys: new Set(upEdges),
|
|
1008
1029
|
downCodes: new Set(heldCodes),
|
|
1009
1030
|
upCodes: new Set(upCodeEdges),
|
|
1031
|
+
pressedKeys: new Set(pressedKeys),
|
|
1032
|
+
pressedCodes: new Set(pressedCodes),
|
|
1010
1033
|
buttons: [buttons[0], buttons[1], buttons[2]],
|
|
1034
|
+
pressedButtons: [pressedButtons[0], pressedButtons[1], pressedButtons[2]],
|
|
1035
|
+
releasedButtons: [releasedButtons[0], releasedButtons[1], releasedButtons[2]],
|
|
1011
1036
|
movementX: mvx,
|
|
1012
1037
|
movementY: mvy,
|
|
1013
1038
|
...mouseX !== void 0 && mouseY !== void 0 ? { mouseX, mouseY } : {},
|
|
@@ -1025,6 +1050,14 @@ function attachBrowserInputBackend(canvas, options = {}) {
|
|
|
1025
1050
|
};
|
|
1026
1051
|
upEdges.clear();
|
|
1027
1052
|
upCodeEdges.clear();
|
|
1053
|
+
pressedKeys.clear();
|
|
1054
|
+
pressedCodes.clear();
|
|
1055
|
+
pressedButtons[0] = false;
|
|
1056
|
+
pressedButtons[1] = false;
|
|
1057
|
+
pressedButtons[2] = false;
|
|
1058
|
+
releasedButtons[0] = false;
|
|
1059
|
+
releasedButtons[1] = false;
|
|
1060
|
+
releasedButtons[2] = false;
|
|
1028
1061
|
mvx = 0;
|
|
1029
1062
|
mvy = 0;
|
|
1030
1063
|
wheelAccum = 0;
|
|
@@ -1044,8 +1077,10 @@ function attachBrowserInputBackend(canvas, options = {}) {
|
|
|
1044
1077
|
function clear() {
|
|
1045
1078
|
heldKeys.clear();
|
|
1046
1079
|
upEdges.clear();
|
|
1080
|
+
pressedKeys.clear();
|
|
1047
1081
|
heldCodes.clear();
|
|
1048
1082
|
upCodeEdges.clear();
|
|
1083
|
+
pressedCodes.clear();
|
|
1049
1084
|
pointerMap.clear();
|
|
1050
1085
|
phaseQueue.length = 0;
|
|
1051
1086
|
prevGamepadFrame.clear();
|
|
@@ -1054,6 +1089,12 @@ function attachBrowserInputBackend(canvas, options = {}) {
|
|
|
1054
1089
|
buttons[0] = false;
|
|
1055
1090
|
buttons[1] = false;
|
|
1056
1091
|
buttons[2] = false;
|
|
1092
|
+
pressedButtons[0] = false;
|
|
1093
|
+
pressedButtons[1] = false;
|
|
1094
|
+
pressedButtons[2] = false;
|
|
1095
|
+
releasedButtons[0] = false;
|
|
1096
|
+
releasedButtons[1] = false;
|
|
1097
|
+
releasedButtons[2] = false;
|
|
1057
1098
|
mvx = 0;
|
|
1058
1099
|
mvy = 0;
|
|
1059
1100
|
wheelAccum = 0;
|
|
@@ -1076,6 +1117,7 @@ function attachBrowserInputBackend(canvas, options = {}) {
|
|
|
1076
1117
|
safeRemove(canvas, "wheel", onWheel);
|
|
1077
1118
|
safeRemove(doc, "visibilitychange", onVisibilityChange);
|
|
1078
1119
|
safeRemove(doc, "pointerlockchange", onPointerLockChange);
|
|
1120
|
+
safeRemove(doc, "pointerlockerror", onPointerLockError);
|
|
1079
1121
|
safeRemove(canvas, "click", onCanvasClick);
|
|
1080
1122
|
clear();
|
|
1081
1123
|
if (canvas.style && _prevTouchAction !== void 0) {
|
|
@@ -1156,35 +1198,33 @@ function buildGamepadReader(slot) {
|
|
|
1156
1198
|
function snapshotFromSample(sample, actionStates, inputMap, previousSnapshot) {
|
|
1157
1199
|
const heldKeys = new Set(sample.downKeys);
|
|
1158
1200
|
const upEdges = new Set(sample.upKeys);
|
|
1159
|
-
const justPressedKeys =
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
}
|
|
1201
|
+
const justPressedKeys = sample.pressedKeys === void 0 ? new Set(
|
|
1202
|
+
[...heldKeys].filter(
|
|
1203
|
+
(key) => previousSnapshot === void 0 || !previousSnapshot.keyboard.down(key)
|
|
1204
|
+
)
|
|
1205
|
+
) : new Set(sample.pressedKeys);
|
|
1165
1206
|
const heldCodes = new Set(sample.downCodes ?? []);
|
|
1166
1207
|
const upCodeEdges = new Set(sample.upCodes ?? []);
|
|
1167
|
-
const justPressedCodes =
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
}
|
|
1208
|
+
const justPressedCodes = sample.pressedCodes === void 0 ? new Set(
|
|
1209
|
+
[...heldCodes].filter(
|
|
1210
|
+
(code) => previousSnapshot === void 0 || !previousSnapshot.keyboard.downCode(code)
|
|
1211
|
+
)
|
|
1212
|
+
) : new Set(sample.pressedCodes);
|
|
1173
1213
|
const buttons = [
|
|
1174
1214
|
sample.buttons[0],
|
|
1175
1215
|
sample.buttons[1],
|
|
1176
1216
|
sample.buttons[2]
|
|
1177
1217
|
];
|
|
1178
|
-
const justPressedButtons = [
|
|
1218
|
+
const justPressedButtons = sample.pressedButtons === void 0 ? [
|
|
1179
1219
|
buttons[0] && (previousSnapshot === void 0 || !previousSnapshot.mouse.button(0)),
|
|
1180
1220
|
buttons[1] && (previousSnapshot === void 0 || !previousSnapshot.mouse.button(1)),
|
|
1181
1221
|
buttons[2] && (previousSnapshot === void 0 || !previousSnapshot.mouse.button(2))
|
|
1182
|
-
];
|
|
1183
|
-
const justReleasedButtons = [
|
|
1222
|
+
] : [sample.pressedButtons[0], sample.pressedButtons[1], sample.pressedButtons[2]];
|
|
1223
|
+
const justReleasedButtons = sample.releasedButtons === void 0 ? [
|
|
1184
1224
|
sample.focusReset !== true && previousSnapshot?.mouse.button(0) === true && !buttons[0],
|
|
1185
1225
|
sample.focusReset !== true && previousSnapshot?.mouse.button(1) === true && !buttons[1],
|
|
1186
1226
|
sample.focusReset !== true && previousSnapshot?.mouse.button(2) === true && !buttons[2]
|
|
1187
|
-
];
|
|
1227
|
+
] : [sample.releasedButtons[0], sample.releasedButtons[1], sample.releasedButtons[2]];
|
|
1188
1228
|
const movementDelta = Object.freeze({ x: sample.movementX, y: sample.movementY });
|
|
1189
1229
|
const mousePosition = sample.mouseX === void 0 || sample.mouseY === void 0 ? void 0 : Object.freeze({ x: sample.mouseX, y: sample.mouseY });
|
|
1190
1230
|
const wheelDelta = sample.wheelDelta;
|
|
@@ -1371,11 +1411,14 @@ function makeCompositeBackend(inner, options) {
|
|
|
1371
1411
|
const heldKeys = /* @__PURE__ */ new Set();
|
|
1372
1412
|
const buttons = [false, false, false];
|
|
1373
1413
|
const upEdges = /* @__PURE__ */ new Set();
|
|
1414
|
+
const pressedKeys = /* @__PURE__ */ new Set();
|
|
1415
|
+
const pressedButtons = [false, false, false];
|
|
1416
|
+
const releasedButtons = [false, false, false];
|
|
1374
1417
|
let mvx = 0;
|
|
1375
1418
|
let mvy = 0;
|
|
1376
1419
|
let wheel = 0;
|
|
1377
1420
|
function injectionActive() {
|
|
1378
|
-
return heldKeys.size > 0 || upEdges.size > 0 || buttons.some((b) => b);
|
|
1421
|
+
return heldKeys.size > 0 || upEdges.size > 0 || pressedKeys.size > 0 || buttons.some((b) => b) || pressedButtons.some((b) => b) || releasedButtons.some((b) => b);
|
|
1379
1422
|
}
|
|
1380
1423
|
function sample() {
|
|
1381
1424
|
const base = inner.sample();
|
|
@@ -1389,18 +1432,38 @@ function makeCompositeBackend(inner, options) {
|
|
|
1389
1432
|
if (yieldToHuman && base.downKeys.has(k)) continue;
|
|
1390
1433
|
mergedUp.add(k);
|
|
1391
1434
|
}
|
|
1435
|
+
const mergedPressed = base.pressedKeys === void 0 && pressedKeys.size === 0 ? void 0 : /* @__PURE__ */ new Set([...base.pressedKeys ?? [], ...pressedKeys]);
|
|
1436
|
+
if (yieldToHuman && base.downKeys.size > 0 && mergedPressed !== void 0) {
|
|
1437
|
+
for (const key of pressedKeys) {
|
|
1438
|
+
if (base.downKeys.has(key)) mergedPressed.delete(key);
|
|
1439
|
+
}
|
|
1440
|
+
}
|
|
1392
1441
|
const mergedButtons = [
|
|
1393
1442
|
base.buttons[0] || buttons[0],
|
|
1394
1443
|
base.buttons[1] || buttons[1],
|
|
1395
1444
|
base.buttons[2] || buttons[2]
|
|
1396
1445
|
];
|
|
1446
|
+
const mergedPressedButtons = base.pressedButtons === void 0 && !pressedButtons.some(Boolean) ? void 0 : [
|
|
1447
|
+
(base.pressedButtons?.[0] ?? false) || pressedButtons[0],
|
|
1448
|
+
(base.pressedButtons?.[1] ?? false) || pressedButtons[1],
|
|
1449
|
+
(base.pressedButtons?.[2] ?? false) || pressedButtons[2]
|
|
1450
|
+
];
|
|
1451
|
+
const mergedReleasedButtons = base.releasedButtons === void 0 && !releasedButtons.some(Boolean) ? void 0 : [
|
|
1452
|
+
(base.releasedButtons?.[0] ?? false) || releasedButtons[0],
|
|
1453
|
+
(base.releasedButtons?.[1] ?? false) || releasedButtons[1],
|
|
1454
|
+
(base.releasedButtons?.[2] ?? false) || releasedButtons[2]
|
|
1455
|
+
];
|
|
1397
1456
|
const focused = base.focused || injectionActive();
|
|
1398
1457
|
const out = {
|
|
1399
1458
|
...base,
|
|
1400
1459
|
// carry inner optional fields (pointers/gamepads/gestures/...) untouched
|
|
1401
1460
|
downKeys,
|
|
1402
1461
|
upKeys: mergedUp,
|
|
1462
|
+
...mergedPressed === void 0 ? {} : { pressedKeys: mergedPressed },
|
|
1463
|
+
...base.pressedCodes === void 0 ? {} : { pressedCodes: base.pressedCodes },
|
|
1403
1464
|
buttons: mergedButtons,
|
|
1465
|
+
...mergedPressedButtons === void 0 ? {} : { pressedButtons: mergedPressedButtons },
|
|
1466
|
+
...mergedReleasedButtons === void 0 ? {} : { releasedButtons: mergedReleasedButtons },
|
|
1404
1467
|
movementX: base.movementX + mvx,
|
|
1405
1468
|
movementY: base.movementY + mvy,
|
|
1406
1469
|
wheelDelta: base.wheelDelta + wheel,
|
|
@@ -1409,6 +1472,13 @@ function makeCompositeBackend(inner, options) {
|
|
|
1409
1472
|
// AI never fabricates a lock
|
|
1410
1473
|
};
|
|
1411
1474
|
upEdges.clear();
|
|
1475
|
+
pressedKeys.clear();
|
|
1476
|
+
pressedButtons[0] = false;
|
|
1477
|
+
pressedButtons[1] = false;
|
|
1478
|
+
pressedButtons[2] = false;
|
|
1479
|
+
releasedButtons[0] = false;
|
|
1480
|
+
releasedButtons[1] = false;
|
|
1481
|
+
releasedButtons[2] = false;
|
|
1412
1482
|
mvx = 0;
|
|
1413
1483
|
mvy = 0;
|
|
1414
1484
|
wheel = 0;
|
|
@@ -1421,13 +1491,15 @@ function makeCompositeBackend(inner, options) {
|
|
|
1421
1491
|
// Teardown belongs to the human backend -- forward it.
|
|
1422
1492
|
detach: () => inner.detach(),
|
|
1423
1493
|
press(key) {
|
|
1494
|
+
if (!heldKeys.has(key)) pressedKeys.add(key);
|
|
1424
1495
|
heldKeys.add(key);
|
|
1425
|
-
upEdges.delete(key);
|
|
1426
1496
|
},
|
|
1427
1497
|
release(key) {
|
|
1428
1498
|
if (heldKeys.delete(key)) upEdges.add(key);
|
|
1429
1499
|
},
|
|
1430
1500
|
setButton(slot, down) {
|
|
1501
|
+
if (down && !buttons[slot]) pressedButtons[slot] = true;
|
|
1502
|
+
if (!down && buttons[slot]) releasedButtons[slot] = true;
|
|
1431
1503
|
buttons[slot] = down;
|
|
1432
1504
|
},
|
|
1433
1505
|
addMovement(dx, dy) {
|
|
@@ -1438,8 +1510,19 @@ function makeCompositeBackend(inner, options) {
|
|
|
1438
1510
|
wheel += notches;
|
|
1439
1511
|
},
|
|
1440
1512
|
clearInjected() {
|
|
1513
|
+
upEdges.clear();
|
|
1514
|
+
pressedKeys.clear();
|
|
1441
1515
|
for (const k of heldKeys) upEdges.add(k);
|
|
1442
1516
|
heldKeys.clear();
|
|
1517
|
+
pressedButtons[0] = false;
|
|
1518
|
+
pressedButtons[1] = false;
|
|
1519
|
+
pressedButtons[2] = false;
|
|
1520
|
+
releasedButtons[0] = false;
|
|
1521
|
+
releasedButtons[1] = false;
|
|
1522
|
+
releasedButtons[2] = false;
|
|
1523
|
+
if (buttons[0]) releasedButtons[0] = true;
|
|
1524
|
+
if (buttons[1]) releasedButtons[1] = true;
|
|
1525
|
+
if (buttons[2]) releasedButtons[2] = true;
|
|
1443
1526
|
buttons[0] = buttons[1] = buttons[2] = false;
|
|
1444
1527
|
mvx = 0;
|
|
1445
1528
|
mvy = 0;
|