@omnipad/core 0.1.1-alpha.1 → 0.2.0-alpha.3
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/dist/index.d.mts +301 -155
- package/dist/index.d.ts +301 -155
- package/dist/index.js +377 -38
- package/dist/index.mjs +373 -37
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -117,7 +117,7 @@ var STANDARD_KEYS = {
|
|
|
117
117
|
var KEYS = STANDARD_KEYS;
|
|
118
118
|
|
|
119
119
|
// src/types/index.ts
|
|
120
|
-
var
|
|
120
|
+
var CMP_TYPES = {
|
|
121
121
|
// --- Zones ---
|
|
122
122
|
/** Area responsible for capturing touches and spawning dynamic widgets */
|
|
123
123
|
INPUT_ZONE: "input-zone",
|
|
@@ -252,14 +252,17 @@ var applyAxialDeadzone = (v, threshold, max) => {
|
|
|
252
252
|
};
|
|
253
253
|
|
|
254
254
|
// src/utils/dom.ts
|
|
255
|
-
var getDeepElement = (x, y) => {
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
255
|
+
var getDeepElement = (x, y, ignoreClass = "omnipad-target-zone") => {
|
|
256
|
+
const elements = document.elementsFromPoint(x, y);
|
|
257
|
+
let target = elements.find((el) => !el.classList.contains(ignoreClass));
|
|
258
|
+
if (!target) return null;
|
|
259
|
+
while (target && target.shadowRoot) {
|
|
260
|
+
const nestedElements = target.shadowRoot.elementsFromPoint(x, y);
|
|
261
|
+
const nestedTarget = nestedElements.find((el) => !el.classList.contains(ignoreClass));
|
|
262
|
+
if (!nestedTarget || nestedTarget === target) break;
|
|
263
|
+
target = nestedTarget;
|
|
264
|
+
}
|
|
265
|
+
return target;
|
|
263
266
|
};
|
|
264
267
|
var getDeepActiveElement = () => {
|
|
265
268
|
let el = document.activeElement;
|
|
@@ -303,7 +306,7 @@ var dispatchPointerEventAtPos = (type, x, y, opts = {}) => {
|
|
|
303
306
|
target.dispatchEvent(
|
|
304
307
|
new PointerEvent(type, {
|
|
305
308
|
isPrimary: true,
|
|
306
|
-
pointerId:
|
|
309
|
+
pointerId: 9999,
|
|
307
310
|
pointerType: "mouse",
|
|
308
311
|
// Emulate mouse behavior for Flash MouseOver/Down logic
|
|
309
312
|
...commonProps
|
|
@@ -635,12 +638,32 @@ var SimpleEmitter = class {
|
|
|
635
638
|
}
|
|
636
639
|
};
|
|
637
640
|
|
|
641
|
+
// src/utils/performance.ts
|
|
642
|
+
function createRafThrottler(callback) {
|
|
643
|
+
let ticking = false;
|
|
644
|
+
let latestPayload = null;
|
|
645
|
+
return function(payload) {
|
|
646
|
+
latestPayload = payload;
|
|
647
|
+
if (!ticking) {
|
|
648
|
+
ticking = true;
|
|
649
|
+
window.requestAnimationFrame(() => {
|
|
650
|
+
if (latestPayload !== null) {
|
|
651
|
+
callback(latestPayload);
|
|
652
|
+
}
|
|
653
|
+
ticking = false;
|
|
654
|
+
});
|
|
655
|
+
}
|
|
656
|
+
};
|
|
657
|
+
}
|
|
658
|
+
|
|
638
659
|
// src/imputManager/index.ts
|
|
639
660
|
var INPUT_MANAGER_KEY = /* @__PURE__ */ Symbol.for("omnipad.input_manager.instance");
|
|
640
661
|
var InputManager = class _InputManager {
|
|
641
662
|
constructor() {
|
|
642
663
|
/** Internal flag to prevent multiple event registrations */
|
|
643
664
|
__publicField(this, "_isListening", false);
|
|
665
|
+
/** A throttled version of the reset logic */
|
|
666
|
+
__publicField(this, "throttledReset");
|
|
644
667
|
/**
|
|
645
668
|
* Manually triggers a system-wide input reset via Registry.
|
|
646
669
|
*/
|
|
@@ -650,6 +673,20 @@ var InputManager = class _InputManager {
|
|
|
650
673
|
}
|
|
651
674
|
Registry.getInstance().resetAll();
|
|
652
675
|
});
|
|
676
|
+
__publicField(this, "handleResizeReset", () => {
|
|
677
|
+
this.throttledReset(null);
|
|
678
|
+
});
|
|
679
|
+
__publicField(this, "handleBlurReset", () => {
|
|
680
|
+
this.handleGlobalReset();
|
|
681
|
+
});
|
|
682
|
+
__publicField(this, "handleVisibilityChangeReset", () => {
|
|
683
|
+
if (document.visibilityState === "hidden") {
|
|
684
|
+
this.handleGlobalReset();
|
|
685
|
+
}
|
|
686
|
+
});
|
|
687
|
+
this.throttledReset = createRafThrottler(() => {
|
|
688
|
+
this.handleGlobalReset();
|
|
689
|
+
});
|
|
653
690
|
}
|
|
654
691
|
/**
|
|
655
692
|
* Retrieves the global instance of the InputManager.
|
|
@@ -668,8 +705,9 @@ var InputManager = class _InputManager {
|
|
|
668
705
|
*/
|
|
669
706
|
init() {
|
|
670
707
|
if (this._isListening) return;
|
|
671
|
-
window.addEventListener("resize", this.
|
|
672
|
-
window.addEventListener("blur", this.
|
|
708
|
+
window.addEventListener("resize", this.handleResizeReset);
|
|
709
|
+
window.addEventListener("blur", this.handleBlurReset);
|
|
710
|
+
document.addEventListener("visibilitychange", this.handleVisibilityChangeReset);
|
|
673
711
|
this._isListening = true;
|
|
674
712
|
if (import.meta.env?.DEV) {
|
|
675
713
|
console.log("[OmniPad-Core] Global InputManager monitoring started.");
|
|
@@ -703,8 +741,9 @@ var InputManager = class _InputManager {
|
|
|
703
741
|
* Detaches all global listeners.
|
|
704
742
|
*/
|
|
705
743
|
destroy() {
|
|
706
|
-
window.removeEventListener("resize", this.
|
|
707
|
-
window.removeEventListener("blur", this.
|
|
744
|
+
window.removeEventListener("resize", this.handleResizeReset);
|
|
745
|
+
window.removeEventListener("blur", this.handleBlurReset);
|
|
746
|
+
window.removeEventListener("visibilitychange", this.handleVisibilityChangeReset);
|
|
708
747
|
this._isListening = false;
|
|
709
748
|
}
|
|
710
749
|
};
|
|
@@ -716,7 +755,7 @@ var BaseEntity = class {
|
|
|
716
755
|
__publicField(this, "type");
|
|
717
756
|
__publicField(this, "config");
|
|
718
757
|
__publicField(this, "state");
|
|
719
|
-
__publicField(this, "
|
|
758
|
+
__publicField(this, "rectProvider", null);
|
|
720
759
|
// 内部状态发射器,负责处理状态订阅逻辑 / Internal emitter for state subscription logic
|
|
721
760
|
__publicField(this, "stateEmitter", new SimpleEmitter());
|
|
722
761
|
this.uid = uid;
|
|
@@ -745,8 +784,14 @@ var BaseEntity = class {
|
|
|
745
784
|
this.stateEmitter.clear();
|
|
746
785
|
Registry.getInstance().unregister(this.uid);
|
|
747
786
|
}
|
|
748
|
-
|
|
749
|
-
this.
|
|
787
|
+
bindRectProvider(provider) {
|
|
788
|
+
this.rectProvider = provider;
|
|
789
|
+
}
|
|
790
|
+
/**
|
|
791
|
+
* Called when triggering interactions, this subclass fetches the latest boundaries in real time.
|
|
792
|
+
*/
|
|
793
|
+
getRect() {
|
|
794
|
+
return this.rectProvider ? this.rectProvider() : null;
|
|
750
795
|
}
|
|
751
796
|
updateConfig(newConfig) {
|
|
752
797
|
this.config = { ...this.config, ...newConfig };
|
|
@@ -768,12 +813,13 @@ var INITIAL_STATE = {
|
|
|
768
813
|
};
|
|
769
814
|
var InputZoneCore = class extends BaseEntity {
|
|
770
815
|
constructor(uid, config) {
|
|
771
|
-
super(uid,
|
|
816
|
+
super(uid, CMP_TYPES.INPUT_ZONE, config, INITIAL_STATE);
|
|
772
817
|
}
|
|
773
818
|
onPointerDown(e) {
|
|
774
819
|
if (this.state.isDynamicActive) return;
|
|
775
820
|
if (e.target !== e.currentTarget) return;
|
|
776
821
|
if (e.cancelable) e.preventDefault();
|
|
822
|
+
e.stopPropagation();
|
|
777
823
|
const pos = this.calculateRelativePosition(e.clientX, e.clientY);
|
|
778
824
|
this.setState({
|
|
779
825
|
isDynamicActive: true,
|
|
@@ -785,6 +831,7 @@ var InputZoneCore = class extends BaseEntity {
|
|
|
785
831
|
if (!this.state.isDynamicActive || e.pointerId !== this.state.dynamicPointerId) return;
|
|
786
832
|
}
|
|
787
833
|
onPointerUp(e) {
|
|
834
|
+
if (e.cancelable) e.preventDefault();
|
|
788
835
|
this.handleRelease(e);
|
|
789
836
|
}
|
|
790
837
|
onPointerCancel(e) {
|
|
@@ -810,10 +857,11 @@ var InputZoneCore = class extends BaseEntity {
|
|
|
810
857
|
* Converts viewport pixels to percentage coordinates relative to the zone.
|
|
811
858
|
*/
|
|
812
859
|
calculateRelativePosition(clientX, clientY) {
|
|
813
|
-
|
|
860
|
+
const rect = this.getRect();
|
|
861
|
+
if (!rect) return { x: 0, y: 0 };
|
|
814
862
|
return {
|
|
815
|
-
x: pxToPercent(clientX -
|
|
816
|
-
y: pxToPercent(clientY -
|
|
863
|
+
x: pxToPercent(clientX - rect.left, rect.width),
|
|
864
|
+
y: pxToPercent(clientY - rect.top, rect.height)
|
|
817
865
|
};
|
|
818
866
|
}
|
|
819
867
|
/**
|
|
@@ -845,11 +893,12 @@ var KeyboardButtonCore = class extends BaseEntity {
|
|
|
845
893
|
* @param config - The flat configuration object for the button.
|
|
846
894
|
*/
|
|
847
895
|
constructor(uid, config) {
|
|
848
|
-
super(uid,
|
|
896
|
+
super(uid, CMP_TYPES.KEYBOARD_BUTTON, config, INITIAL_STATE2);
|
|
849
897
|
}
|
|
850
898
|
// --- IPointerHandler Implementation ---
|
|
851
899
|
onPointerDown(e) {
|
|
852
900
|
if (e.cancelable) e.preventDefault();
|
|
901
|
+
e.stopPropagation();
|
|
853
902
|
e.target.setPointerCapture(e.pointerId);
|
|
854
903
|
this.setState({
|
|
855
904
|
isActive: true,
|
|
@@ -904,7 +953,7 @@ var KeyboardButtonCore = class extends BaseEntity {
|
|
|
904
953
|
target.handleSignal(signal);
|
|
905
954
|
} else {
|
|
906
955
|
if (import.meta.env?.DEV) {
|
|
907
|
-
console.warn(`[OmniPad-Core]
|
|
956
|
+
console.warn(`[OmniPad-Core] KeyboardButton ${this.uid} target not found: ${targetId}`);
|
|
908
957
|
}
|
|
909
958
|
}
|
|
910
959
|
}
|
|
@@ -917,20 +966,108 @@ var KeyboardButtonCore = class extends BaseEntity {
|
|
|
917
966
|
}
|
|
918
967
|
};
|
|
919
968
|
|
|
920
|
-
// src/entities/
|
|
969
|
+
// src/entities/MouseButtonCore.ts
|
|
921
970
|
var INITIAL_STATE3 = {
|
|
971
|
+
isActive: false,
|
|
972
|
+
isPressed: false,
|
|
973
|
+
pointerId: null,
|
|
974
|
+
value: 0
|
|
975
|
+
};
|
|
976
|
+
var MouseButtonCore = class extends BaseEntity {
|
|
977
|
+
constructor(uid, config) {
|
|
978
|
+
super(uid, CMP_TYPES.MOUSE_BUTTON, config, INITIAL_STATE3);
|
|
979
|
+
}
|
|
980
|
+
// --- IPointerHandler Implementation ---
|
|
981
|
+
onPointerDown(e) {
|
|
982
|
+
if (e.cancelable) e.preventDefault();
|
|
983
|
+
e.stopPropagation();
|
|
984
|
+
e.target.setPointerCapture(e.pointerId);
|
|
985
|
+
this.setState({
|
|
986
|
+
isActive: true,
|
|
987
|
+
isPressed: true,
|
|
988
|
+
pointerId: e.pointerId
|
|
989
|
+
});
|
|
990
|
+
this.sendInputSignal(ACTION_TYPES.MOUSEDOWN);
|
|
991
|
+
}
|
|
992
|
+
onPointerUp(e) {
|
|
993
|
+
if (e.cancelable) e.preventDefault();
|
|
994
|
+
this.handleRelease(e, true);
|
|
995
|
+
}
|
|
996
|
+
onPointerCancel(e) {
|
|
997
|
+
this.handleRelease(e, false);
|
|
998
|
+
}
|
|
999
|
+
onPointerMove(e) {
|
|
1000
|
+
if (e.cancelable) e.preventDefault();
|
|
1001
|
+
}
|
|
1002
|
+
// --- Internal Logic ---
|
|
1003
|
+
/**
|
|
1004
|
+
* Handles the release of the button.
|
|
1005
|
+
*
|
|
1006
|
+
* @param e - The pointer event.
|
|
1007
|
+
* @param isNormalRelease - If true, a 'click' event will also be dispatched.
|
|
1008
|
+
*/
|
|
1009
|
+
handleRelease(e, isNormalRelease) {
|
|
1010
|
+
const isCancelEvent = e.type === "pointercancel" || e.type === "lostpointercapture";
|
|
1011
|
+
if (!isCancelEvent && this.state.pointerId !== e.pointerId) return;
|
|
1012
|
+
if (e.target.hasPointerCapture(e.pointerId)) {
|
|
1013
|
+
e.target.releasePointerCapture(e.pointerId);
|
|
1014
|
+
}
|
|
1015
|
+
this.setState(INITIAL_STATE3);
|
|
1016
|
+
this.sendInputSignal(ACTION_TYPES.MOUSEUP);
|
|
1017
|
+
if (isNormalRelease) {
|
|
1018
|
+
this.sendInputSignal(ACTION_TYPES.CLICK);
|
|
1019
|
+
}
|
|
1020
|
+
}
|
|
1021
|
+
/**
|
|
1022
|
+
* Dispatches input signals to the registered target stage.
|
|
1023
|
+
*
|
|
1024
|
+
* @param type - The action type (mousedown, mouseup, or click).
|
|
1025
|
+
*/
|
|
1026
|
+
sendInputSignal(type) {
|
|
1027
|
+
const targetId = this.config.targetStageId;
|
|
1028
|
+
if (!targetId) return;
|
|
1029
|
+
const target = Registry.getInstance().getEntity(targetId);
|
|
1030
|
+
if (target && typeof target.handleSignal === "function") {
|
|
1031
|
+
const signal = {
|
|
1032
|
+
targetStageId: targetId,
|
|
1033
|
+
type,
|
|
1034
|
+
payload: {
|
|
1035
|
+
// 传递配置中的鼠标按键索引 (0:左键, 1:中键, 2:右键)
|
|
1036
|
+
button: this.config.button ?? 0,
|
|
1037
|
+
// 如果配置了固定坐标,一并传递给 TargetZone
|
|
1038
|
+
point: this.config.fixedPoint
|
|
1039
|
+
}
|
|
1040
|
+
};
|
|
1041
|
+
target.handleSignal(signal);
|
|
1042
|
+
} else {
|
|
1043
|
+
if (import.meta.env?.DEV) {
|
|
1044
|
+
console.warn(`[OmniPad-Core] MouseButton ${this.uid} target not found: ${targetId}`);
|
|
1045
|
+
}
|
|
1046
|
+
}
|
|
1047
|
+
}
|
|
1048
|
+
// --- IResettable Implementation ---
|
|
1049
|
+
reset() {
|
|
1050
|
+
if (this.state.isPressed) {
|
|
1051
|
+
this.sendInputSignal(ACTION_TYPES.MOUSEUP);
|
|
1052
|
+
}
|
|
1053
|
+
this.setState(INITIAL_STATE3);
|
|
1054
|
+
}
|
|
1055
|
+
};
|
|
1056
|
+
|
|
1057
|
+
// src/entities/RootLayerCore.ts
|
|
1058
|
+
var INITIAL_STATE4 = {
|
|
922
1059
|
isHighlighted: false
|
|
923
1060
|
};
|
|
924
1061
|
var RootLayerCore = class extends BaseEntity {
|
|
925
1062
|
constructor(uid, config) {
|
|
926
|
-
super(uid,
|
|
1063
|
+
super(uid, CMP_TYPES.ROOT_LAYER, config, INITIAL_STATE4);
|
|
927
1064
|
}
|
|
928
1065
|
reset() {
|
|
929
1066
|
}
|
|
930
1067
|
};
|
|
931
1068
|
|
|
932
1069
|
// src/entities/TargetZoneCore.ts
|
|
933
|
-
var
|
|
1070
|
+
var INITIAL_STATE5 = {
|
|
934
1071
|
position: { x: 50, y: 50 },
|
|
935
1072
|
isVisible: false,
|
|
936
1073
|
isPointerDown: false,
|
|
@@ -938,9 +1075,48 @@ var INITIAL_STATE4 = {
|
|
|
938
1075
|
};
|
|
939
1076
|
var TargetZoneCore = class extends BaseEntity {
|
|
940
1077
|
constructor(uid, config) {
|
|
941
|
-
super(uid,
|
|
1078
|
+
super(uid, CMP_TYPES.TARGET_ZONE, config, INITIAL_STATE5);
|
|
942
1079
|
__publicField(this, "hideTimer", null);
|
|
943
1080
|
__publicField(this, "focusFeedbackTimer", null);
|
|
1081
|
+
__publicField(this, "throttledPointerMove");
|
|
1082
|
+
this.throttledPointerMove = createRafThrottler((e) => {
|
|
1083
|
+
this.processPhysicalEvent(e, ACTION_TYPES.MOUSEMOVE);
|
|
1084
|
+
});
|
|
1085
|
+
}
|
|
1086
|
+
// --- IPointerHandler Implementation ---
|
|
1087
|
+
onPointerDown(e) {
|
|
1088
|
+
if (e.cancelable) e.preventDefault();
|
|
1089
|
+
e.stopPropagation();
|
|
1090
|
+
this.processPhysicalEvent(e, ACTION_TYPES.MOUSEDOWN);
|
|
1091
|
+
}
|
|
1092
|
+
onPointerMove(e) {
|
|
1093
|
+
if (e.cancelable) e.preventDefault();
|
|
1094
|
+
e.stopPropagation();
|
|
1095
|
+
this.throttledPointerMove(e);
|
|
1096
|
+
}
|
|
1097
|
+
onPointerUp(e) {
|
|
1098
|
+
if (e.cancelable) e.preventDefault();
|
|
1099
|
+
this.processPhysicalEvent(e, ACTION_TYPES.MOUSEUP);
|
|
1100
|
+
this.processPhysicalEvent(e, ACTION_TYPES.CLICK);
|
|
1101
|
+
}
|
|
1102
|
+
onPointerCancel(e) {
|
|
1103
|
+
this.processPhysicalEvent(e, ACTION_TYPES.MOUSEUP);
|
|
1104
|
+
}
|
|
1105
|
+
/**
|
|
1106
|
+
* Convert physical DOM events into internal signals
|
|
1107
|
+
*/
|
|
1108
|
+
processPhysicalEvent(e, type) {
|
|
1109
|
+
const rect = this.getRect();
|
|
1110
|
+
if (!rect) return;
|
|
1111
|
+
const point = {
|
|
1112
|
+
x: pxToPercent(e.clientX - rect.left, rect.width),
|
|
1113
|
+
y: pxToPercent(e.clientY - rect.top, rect.height)
|
|
1114
|
+
};
|
|
1115
|
+
this.handleSignal({
|
|
1116
|
+
targetStageId: this.uid,
|
|
1117
|
+
type,
|
|
1118
|
+
payload: { point, button: e.button }
|
|
1119
|
+
});
|
|
944
1120
|
}
|
|
945
1121
|
// --- ISignalReceiver Implementation ---
|
|
946
1122
|
handleSignal(signal) {
|
|
@@ -954,13 +1130,18 @@ var TargetZoneCore = class extends BaseEntity {
|
|
|
954
1130
|
case ACTION_TYPES.MOUSEMOVE:
|
|
955
1131
|
if (payload.point) {
|
|
956
1132
|
this.updateCursorPosition(payload.point);
|
|
957
|
-
|
|
958
|
-
this.
|
|
1133
|
+
} else if (payload.delta) {
|
|
1134
|
+
this.updateCursorPositionByDelta(payload.delta);
|
|
959
1135
|
}
|
|
1136
|
+
if (this.config.cursorEnabled) this.showCursor();
|
|
1137
|
+
this.executeMouseAction(ACTION_TYPES.POINTERMOVE, payload);
|
|
960
1138
|
break;
|
|
961
1139
|
case ACTION_TYPES.MOUSEDOWN:
|
|
962
1140
|
case ACTION_TYPES.MOUSEUP:
|
|
963
1141
|
case ACTION_TYPES.CLICK:
|
|
1142
|
+
if (payload.point) {
|
|
1143
|
+
this.updateCursorPosition(payload.point);
|
|
1144
|
+
}
|
|
964
1145
|
if (this.config.cursorEnabled) this.showCursor();
|
|
965
1146
|
this.executeMouseAction(
|
|
966
1147
|
type.startsWith(ACTION_TYPES.MOUSE) ? type.replace(ACTION_TYPES.MOUSE, ACTION_TYPES.POINTER) : type,
|
|
@@ -977,12 +1158,13 @@ var TargetZoneCore = class extends BaseEntity {
|
|
|
977
1158
|
* @param payload - Data containing point coordinates or button info.
|
|
978
1159
|
*/
|
|
979
1160
|
executeMouseAction(pointerType, payload) {
|
|
980
|
-
|
|
1161
|
+
const rect = this.getRect();
|
|
1162
|
+
if (!rect) return;
|
|
981
1163
|
if (pointerType === ACTION_TYPES.POINTERDOWN) this.setState({ isPointerDown: true });
|
|
982
1164
|
if (pointerType === ACTION_TYPES.POINTERUP) this.setState({ isPointerDown: false });
|
|
983
1165
|
const target = payload.point || this.state.position;
|
|
984
|
-
const px =
|
|
985
|
-
const py =
|
|
1166
|
+
const px = rect.left + percentToPx(target.x, rect.width);
|
|
1167
|
+
const py = rect.top + percentToPx(target.y, rect.height);
|
|
986
1168
|
dispatchPointerEventAtPos(pointerType, px, py, {
|
|
987
1169
|
button: payload.button ?? 0,
|
|
988
1170
|
buttons: this.state.isPointerDown ? 1 : 0
|
|
@@ -993,9 +1175,10 @@ var TargetZoneCore = class extends BaseEntity {
|
|
|
993
1175
|
* Checks if the target element under the virtual cursor has focus, and reclaims it if lost.
|
|
994
1176
|
*/
|
|
995
1177
|
ensureFocus() {
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
const
|
|
1178
|
+
const rect = this.getRect();
|
|
1179
|
+
if (!rect) return;
|
|
1180
|
+
const px = rect.left + percentToPx(this.state.position.x, rect.width);
|
|
1181
|
+
const py = rect.top + percentToPx(this.state.position.y, rect.height);
|
|
999
1182
|
const target = getDeepElement(px, py);
|
|
1000
1183
|
if (!target) return;
|
|
1001
1184
|
if (getDeepActiveElement() !== target) {
|
|
@@ -1016,8 +1199,23 @@ var TargetZoneCore = class extends BaseEntity {
|
|
|
1016
1199
|
* Updates the internal virtual cursor coordinates.
|
|
1017
1200
|
*/
|
|
1018
1201
|
updateCursorPosition(point) {
|
|
1202
|
+
if (isVec2Equal(point, this.state.position)) return;
|
|
1019
1203
|
this.setState({ position: { ...point } });
|
|
1020
1204
|
}
|
|
1205
|
+
/**
|
|
1206
|
+
* Updates the internal virtual cursor coordinates by delta.
|
|
1207
|
+
*/
|
|
1208
|
+
updateCursorPositionByDelta(delta) {
|
|
1209
|
+
if (isVec2Equal(delta, { x: 0, y: 0 })) return;
|
|
1210
|
+
const rect = this.getRect();
|
|
1211
|
+
if (!rect) return;
|
|
1212
|
+
const dxPercent = pxToPercent(delta.x, rect.width);
|
|
1213
|
+
const dyPercent = pxToPercent(delta.y, rect.height);
|
|
1214
|
+
this.updateCursorPosition({
|
|
1215
|
+
x: clamp(this.state.position.x + dxPercent, 0, 100),
|
|
1216
|
+
y: clamp(this.state.position.y + dyPercent, 0, 100)
|
|
1217
|
+
});
|
|
1218
|
+
}
|
|
1021
1219
|
/**
|
|
1022
1220
|
* Makes the virtual cursor visible and sets a timeout for auto-hiding.
|
|
1023
1221
|
*/
|
|
@@ -1046,26 +1244,164 @@ var TargetZoneCore = class extends BaseEntity {
|
|
|
1046
1244
|
}
|
|
1047
1245
|
};
|
|
1048
1246
|
|
|
1247
|
+
// src/entities/TrackpadCore.ts
|
|
1248
|
+
var GESTURE = {
|
|
1249
|
+
TAP_TIME: 200,
|
|
1250
|
+
// 200ms 以内抬起视为轻点 / Release within 200ms counts as a tap
|
|
1251
|
+
TAP_DISTANCE: 10,
|
|
1252
|
+
// 位移小于 10px 视为点击判定 / Movement within 10px counts as a tap
|
|
1253
|
+
DOUBLE_TAP_GAP: 300
|
|
1254
|
+
// 两次点击间隔小于 300ms 视为双击触发 / Interval within 300ms counts as double-tap
|
|
1255
|
+
};
|
|
1256
|
+
var INITIAL_STATE6 = {
|
|
1257
|
+
isActive: false,
|
|
1258
|
+
isPressed: false,
|
|
1259
|
+
pointerId: null,
|
|
1260
|
+
value: 0
|
|
1261
|
+
};
|
|
1262
|
+
var TrackpadCore = class extends BaseEntity {
|
|
1263
|
+
/**
|
|
1264
|
+
* Creates an instance of TrackpadCore.
|
|
1265
|
+
*
|
|
1266
|
+
* @param uid - Unique entity ID.
|
|
1267
|
+
* @param config - Configuration for the trackpad.
|
|
1268
|
+
*/
|
|
1269
|
+
constructor(uid, config) {
|
|
1270
|
+
super(uid, CMP_TYPES.TRACKPAD, config, INITIAL_STATE6);
|
|
1271
|
+
__publicField(this, "lastPointerPos", { x: 0, y: 0 });
|
|
1272
|
+
__publicField(this, "startTime", 0);
|
|
1273
|
+
__publicField(this, "startPos", { x: 0, y: 0 });
|
|
1274
|
+
// 连击状态追踪 / State tracking for consecutive taps
|
|
1275
|
+
__publicField(this, "lastClickTime", 0);
|
|
1276
|
+
__publicField(this, "isDragMode", false);
|
|
1277
|
+
__publicField(this, "throttledPointerMove");
|
|
1278
|
+
this.throttledPointerMove = createRafThrottler((e) => {
|
|
1279
|
+
this.processPointerMove(e);
|
|
1280
|
+
});
|
|
1281
|
+
}
|
|
1282
|
+
// --- IPointerHandler Implementation ---
|
|
1283
|
+
onPointerDown(e) {
|
|
1284
|
+
if (e.cancelable) e.preventDefault();
|
|
1285
|
+
e.stopPropagation();
|
|
1286
|
+
e.target.setPointerCapture(e.pointerId);
|
|
1287
|
+
const now = Date.now();
|
|
1288
|
+
this.startTime = now;
|
|
1289
|
+
this.startPos = { x: e.clientX, y: e.clientY };
|
|
1290
|
+
this.lastPointerPos = { x: e.clientX, y: e.clientY };
|
|
1291
|
+
if (now - this.lastClickTime < GESTURE.DOUBLE_TAP_GAP) {
|
|
1292
|
+
this.isDragMode = true;
|
|
1293
|
+
this.sendSignal(ACTION_TYPES.MOUSEDOWN);
|
|
1294
|
+
this.setState({ isPressed: true });
|
|
1295
|
+
}
|
|
1296
|
+
this.setState({ isActive: true, pointerId: e.pointerId });
|
|
1297
|
+
}
|
|
1298
|
+
onPointerMove(e) {
|
|
1299
|
+
if (e.cancelable) e.preventDefault();
|
|
1300
|
+
e.stopPropagation();
|
|
1301
|
+
if (this.state.pointerId !== e.pointerId) return;
|
|
1302
|
+
this.throttledPointerMove(e);
|
|
1303
|
+
}
|
|
1304
|
+
/**
|
|
1305
|
+
* Internal logic for processing pointer movement.
|
|
1306
|
+
* Calculates displacement and emits relative move signals.
|
|
1307
|
+
*
|
|
1308
|
+
* @param e - The pointer event.
|
|
1309
|
+
*/
|
|
1310
|
+
processPointerMove(e) {
|
|
1311
|
+
const dx = e.clientX - this.lastPointerPos.x;
|
|
1312
|
+
const dy = e.clientY - this.lastPointerPos.y;
|
|
1313
|
+
const rect = this.getRect();
|
|
1314
|
+
if (!rect) return;
|
|
1315
|
+
const deltaX = dx / rect.width * 100 * this.config.sensitivity;
|
|
1316
|
+
const deltaY = dy / rect.height * 100 * this.config.sensitivity;
|
|
1317
|
+
if (Math.abs(dx) > 0 || Math.abs(dy) > 0) {
|
|
1318
|
+
this.sendSignal(ACTION_TYPES.MOUSEMOVE, { delta: { x: deltaX, y: deltaY } });
|
|
1319
|
+
}
|
|
1320
|
+
this.lastPointerPos = { x: e.clientX, y: e.clientY };
|
|
1321
|
+
}
|
|
1322
|
+
onPointerUp(e) {
|
|
1323
|
+
if (this.state.pointerId !== e.pointerId) return;
|
|
1324
|
+
if (e.cancelable) e.preventDefault();
|
|
1325
|
+
const duration = Date.now() - this.startTime;
|
|
1326
|
+
const dist = Math.hypot(e.clientX - this.startPos.x, e.clientY - this.startPos.y);
|
|
1327
|
+
if (this.isDragMode) {
|
|
1328
|
+
this.sendSignal(ACTION_TYPES.MOUSEUP);
|
|
1329
|
+
this.isDragMode = false;
|
|
1330
|
+
} else if (duration < GESTURE.TAP_TIME && dist < GESTURE.TAP_DISTANCE) {
|
|
1331
|
+
this.sendSignal(ACTION_TYPES.CLICK);
|
|
1332
|
+
this.lastClickTime = Date.now();
|
|
1333
|
+
}
|
|
1334
|
+
this.handleRelease(e);
|
|
1335
|
+
}
|
|
1336
|
+
onPointerCancel(e) {
|
|
1337
|
+
this.handleRelease(e);
|
|
1338
|
+
}
|
|
1339
|
+
// --- IResettable Implementation ---
|
|
1340
|
+
reset() {
|
|
1341
|
+
if (this.isDragMode) this.sendSignal(ACTION_TYPES.MOUSEUP);
|
|
1342
|
+
this.isDragMode = false;
|
|
1343
|
+
this.setState(INITIAL_STATE6);
|
|
1344
|
+
}
|
|
1345
|
+
// --- Internal Helpers ---
|
|
1346
|
+
/**
|
|
1347
|
+
* Clean up pointer capture and reset interaction state.
|
|
1348
|
+
*/
|
|
1349
|
+
handleRelease(e) {
|
|
1350
|
+
if (e.target.hasPointerCapture(e.pointerId)) {
|
|
1351
|
+
try {
|
|
1352
|
+
e.target.releasePointerCapture(e.pointerId);
|
|
1353
|
+
} catch (err) {
|
|
1354
|
+
}
|
|
1355
|
+
}
|
|
1356
|
+
this.setState(INITIAL_STATE6);
|
|
1357
|
+
}
|
|
1358
|
+
/**
|
|
1359
|
+
* Helper to send signals to the target stage via Registry.
|
|
1360
|
+
*
|
|
1361
|
+
* @param type - Signal action type.
|
|
1362
|
+
* @param extraPayload - Additional data like delta or point.
|
|
1363
|
+
*/
|
|
1364
|
+
sendSignal(type, extraPayload = {}) {
|
|
1365
|
+
const targetId = this.config.targetStageId;
|
|
1366
|
+
if (!targetId) return;
|
|
1367
|
+
const target = Registry.getInstance().getEntity(targetId);
|
|
1368
|
+
if (target && typeof target.handleSignal === "function") {
|
|
1369
|
+
target.handleSignal({
|
|
1370
|
+
targetStageId: targetId,
|
|
1371
|
+
type,
|
|
1372
|
+
payload: {
|
|
1373
|
+
button: 0,
|
|
1374
|
+
// 触摸板操作默认模拟左键 / Trackpad defaults to left button
|
|
1375
|
+
...extraPayload
|
|
1376
|
+
}
|
|
1377
|
+
});
|
|
1378
|
+
}
|
|
1379
|
+
}
|
|
1380
|
+
};
|
|
1381
|
+
|
|
1049
1382
|
// src/index.ts
|
|
1050
1383
|
var OmniPad = {
|
|
1384
|
+
ActionTypes: ACTION_TYPES,
|
|
1051
1385
|
Context: CONTEXT,
|
|
1052
1386
|
Keys: KEYS,
|
|
1053
|
-
Types:
|
|
1387
|
+
Types: CMP_TYPES
|
|
1054
1388
|
};
|
|
1055
1389
|
export {
|
|
1056
1390
|
ACTION_TYPES,
|
|
1057
1391
|
BaseEntity,
|
|
1392
|
+
CMP_TYPES,
|
|
1058
1393
|
CONTEXT,
|
|
1059
1394
|
InputManager,
|
|
1060
1395
|
InputZoneCore,
|
|
1061
1396
|
KEYS,
|
|
1062
1397
|
KeyboardButtonCore,
|
|
1398
|
+
MouseButtonCore,
|
|
1063
1399
|
OmniPad,
|
|
1064
1400
|
Registry,
|
|
1065
1401
|
RootLayerCore,
|
|
1066
1402
|
SimpleEmitter,
|
|
1067
|
-
TYPES,
|
|
1068
1403
|
TargetZoneCore,
|
|
1404
|
+
TrackpadCore,
|
|
1069
1405
|
addVec,
|
|
1070
1406
|
applyAxialDeadzone,
|
|
1071
1407
|
applyRadialDeadzone,
|