minimojs 1.0.0-alpha.1 → 1.0.0-alpha.2

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/input.js ADDED
@@ -0,0 +1,185 @@
1
+ export function createInputState() {
2
+ return {
3
+ keysDown: new Set(),
4
+ keysPressed: new Set(),
5
+ pointerDown: false,
6
+ pointerPressed: false,
7
+ pointerX: 0,
8
+ pointerY: 0,
9
+ mouseDown: false,
10
+ mousePressed: false,
11
+ mouseX: 0,
12
+ mouseY: 0,
13
+ touchPointers: new Map(),
14
+ primaryTouchId: null,
15
+ };
16
+ }
17
+ export function bindInputEvents(canvas, input) {
18
+ window.addEventListener("keydown", (e) => {
19
+ if (!input.keysDown.has(e.key)) {
20
+ input.keysPressed.add(e.key);
21
+ }
22
+ input.keysDown.add(e.key);
23
+ });
24
+ window.addEventListener("keyup", (e) => {
25
+ input.keysDown.delete(e.key);
26
+ });
27
+ const getCanvasPoint = (clientX, clientY) => {
28
+ const rect = canvas.getBoundingClientRect();
29
+ const scaleX = canvas.width / rect.width;
30
+ const scaleY = canvas.height / rect.height;
31
+ return {
32
+ x: (clientX - rect.left) * scaleX,
33
+ y: (clientY - rect.top) * scaleY,
34
+ };
35
+ };
36
+ canvas.addEventListener("mousedown", (e) => {
37
+ const p = getCanvasPoint(e.clientX, e.clientY);
38
+ input.mouseX = p.x;
39
+ input.mouseY = p.y;
40
+ input.mouseDown = true;
41
+ input.mousePressed = true;
42
+ syncPrimaryPointer(input);
43
+ });
44
+ canvas.addEventListener("mouseup", () => {
45
+ input.mouseDown = false;
46
+ syncPrimaryPointer(input);
47
+ });
48
+ canvas.addEventListener("mousemove", (e) => {
49
+ const p = getCanvasPoint(e.clientX, e.clientY);
50
+ input.mouseX = p.x;
51
+ input.mouseY = p.y;
52
+ syncPrimaryPointer(input);
53
+ });
54
+ canvas.addEventListener("touchstart", (e) => {
55
+ for (const touch of Array.from(e.changedTouches)) {
56
+ const p = getCanvasPoint(touch.clientX, touch.clientY);
57
+ input.touchPointers.set(touch.identifier, {
58
+ x: p.x,
59
+ y: p.y,
60
+ pressed: true,
61
+ });
62
+ if (input.primaryTouchId === null) {
63
+ input.primaryTouchId = touch.identifier;
64
+ }
65
+ }
66
+ syncPrimaryPointer(input);
67
+ e.preventDefault();
68
+ }, { passive: false });
69
+ const clearTouches = (e) => {
70
+ for (const touch of Array.from(e.changedTouches)) {
71
+ input.touchPointers.delete(touch.identifier);
72
+ if (input.primaryTouchId === touch.identifier) {
73
+ input.primaryTouchId = null;
74
+ }
75
+ }
76
+ syncPrimaryPointer(input);
77
+ e.preventDefault();
78
+ };
79
+ canvas.addEventListener("touchend", clearTouches, { passive: false });
80
+ canvas.addEventListener("touchcancel", clearTouches, { passive: false });
81
+ canvas.addEventListener("touchmove", (e) => {
82
+ for (const touch of Array.from(e.changedTouches)) {
83
+ const p = getCanvasPoint(touch.clientX, touch.clientY);
84
+ const existing = input.touchPointers.get(touch.identifier);
85
+ if (existing) {
86
+ existing.x = p.x;
87
+ existing.y = p.y;
88
+ }
89
+ else {
90
+ input.touchPointers.set(touch.identifier, {
91
+ x: p.x,
92
+ y: p.y,
93
+ pressed: false,
94
+ });
95
+ }
96
+ }
97
+ syncPrimaryPointer(input);
98
+ e.preventDefault();
99
+ }, { passive: false });
100
+ }
101
+ export function syncPrimaryPointer(input) {
102
+ if (input.primaryTouchId !== null) {
103
+ const primaryTouch = input.touchPointers.get(input.primaryTouchId);
104
+ if (primaryTouch) {
105
+ input.pointerX = primaryTouch.x;
106
+ input.pointerY = primaryTouch.y;
107
+ input.pointerDown = true;
108
+ input.pointerPressed = primaryTouch.pressed;
109
+ return;
110
+ }
111
+ input.primaryTouchId = null;
112
+ }
113
+ if (input.touchPointers.size > 0) {
114
+ const firstTouchEntry = input.touchPointers.entries().next().value;
115
+ if (firstTouchEntry) {
116
+ const [touchId, touch] = firstTouchEntry;
117
+ input.primaryTouchId = touchId;
118
+ input.pointerX = touch.x;
119
+ input.pointerY = touch.y;
120
+ input.pointerDown = true;
121
+ input.pointerPressed = touch.pressed;
122
+ return;
123
+ }
124
+ }
125
+ input.primaryTouchId = null;
126
+ input.pointerX = input.mouseX;
127
+ input.pointerY = input.mouseY;
128
+ input.pointerDown = input.mouseDown;
129
+ input.pointerPressed = input.mousePressed;
130
+ }
131
+ export function collectPointers(input) {
132
+ const pointers = [];
133
+ if (input.mouseDown) {
134
+ pointers.push({
135
+ id: "mouse",
136
+ kind: "mouse",
137
+ x: input.mouseX,
138
+ y: input.mouseY,
139
+ pressed: input.mousePressed,
140
+ });
141
+ }
142
+ for (const [id, pointer] of input.touchPointers.entries()) {
143
+ pointers.push({
144
+ id,
145
+ kind: "touch",
146
+ x: pointer.x,
147
+ y: pointer.y,
148
+ pressed: pointer.pressed,
149
+ });
150
+ }
151
+ return pointers;
152
+ }
153
+ export function isScreenPointOverSprite(x, y, sprite, radiusScale, scrollX, scrollY) {
154
+ const safeScale = Math.max(0, radiusScale);
155
+ const radius = sprite.size * safeScale;
156
+ const drawX = sprite.ignoreScroll ? sprite.x : sprite.x - scrollX;
157
+ const drawY = sprite.ignoreScroll ? sprite.y : sprite.y - scrollY;
158
+ const dx = x - drawX;
159
+ const dy = y - drawY;
160
+ return dx * dx + dy * dy <= radius * radius;
161
+ }
162
+ export function isPointerDownOverSprite(input, sprite, radiusScale, scrollX, scrollY) {
163
+ if (!sprite)
164
+ return false;
165
+ return collectPointers(input).some((pointer) => isScreenPointOverSprite(pointer.x, pointer.y, sprite, radiusScale, scrollX, scrollY));
166
+ }
167
+ export function isPointerPressedOverSprite(input, sprite, radiusScale, scrollX, scrollY) {
168
+ if (!sprite)
169
+ return false;
170
+ return collectPointers(input).some((pointer) => pointer.pressed &&
171
+ isScreenPointOverSprite(pointer.x, pointer.y, sprite, radiusScale, scrollX, scrollY));
172
+ }
173
+ export function getPointersOverSprite(input, sprite, radiusScale, scrollX, scrollY) {
174
+ if (!sprite)
175
+ return [];
176
+ return collectPointers(input).filter((pointer) => isScreenPointOverSprite(pointer.x, pointer.y, sprite, radiusScale, scrollX, scrollY));
177
+ }
178
+ export function resetTransientInput(input) {
179
+ input.keysPressed.clear();
180
+ input.mousePressed = false;
181
+ for (const pointer of input.touchPointers.values()) {
182
+ pointer.pressed = false;
183
+ }
184
+ syncPrimaryPointer(input);
185
+ }
@@ -0,0 +1,4 @@
1
+ // ---------------------------------------------------------------------------
2
+ // Internal types (stripped from .d.ts via @internal + stripInternal)
3
+ // ---------------------------------------------------------------------------
4
+ export {};
package/dist/minimo.d.ts CHANGED
@@ -155,6 +155,32 @@ export declare class Sprite {
155
155
  */
156
156
  constructor(sprite: string, x?: number, y?: number);
157
157
  }
158
+ /**
159
+ * Snapshot of an active pointer tracked by the engine.
160
+ *
161
+ * Coordinates are in canvas/screen space, matching {@link Game.pointerX} and
162
+ * {@link Game.pointerY}. For world-space comparisons, use helpers such as
163
+ * {@link Game.isPointerDownOverSprite} or {@link Game.getPointersOverSprite}.
164
+ */
165
+ export interface PointerInfo {
166
+ /**
167
+ * Unique pointer id.
168
+ * - Mouse always uses `"mouse"`.
169
+ * - Touch pointers use the browser touch identifier.
170
+ */
171
+ id: "mouse" | number;
172
+ /** Pointer source kind. */
173
+ kind: "mouse" | "touch";
174
+ /** Current X position in canvas/screen space, in pixels. */
175
+ x: number;
176
+ /** Current Y position in canvas/screen space, in pixels. */
177
+ y: number;
178
+ /**
179
+ * `true` only on the frame this pointer started pressing.
180
+ * For held pointers after the first frame, this becomes `false`.
181
+ */
182
+ pressed: boolean;
183
+ }
158
184
  /**
159
185
  * # MinimoJS v1 — AI Agent Integration Guide
160
186
  *
@@ -685,6 +711,103 @@ export declare class Game {
685
711
  * ```
686
712
  */
687
713
  isPointerPressed(): boolean;
714
+ /**
715
+ * Returns `true` while any active pointer is held down over the target sprite.
716
+ * Works with both mouse input and multiple simultaneous touches.
717
+ *
718
+ * Pointer hit testing uses a circular area centered on the sprite. The radius
719
+ * is `sprite.size * radiusScale`. World-space sprites are tested against the
720
+ * current camera scroll. HUD sprites with `ignoreScroll = true` are tested in
721
+ * screen space.
722
+ *
723
+ * Use this for continuous virtual buttons such as touch movement controls.
724
+ *
725
+ * @param sprite - Target sprite to test. If `null` / `undefined`, returns `false`.
726
+ * @param radiusScale - Multiplier applied to `sprite.size` to define the hit radius.
727
+ * Default: `0.5`.
728
+ * @returns `true` if any currently held pointer overlaps the sprite hit area.
729
+ *
730
+ * @example
731
+ * ```ts
732
+ * if (game.isPointerDownOverSprite(leftButton, 0.72)) {
733
+ * player.x -= 200 * dt;
734
+ * }
735
+ * ```
736
+ */
737
+ isPointerDownOverSprite(sprite: Sprite | null | undefined, radiusScale?: number): boolean;
738
+ /**
739
+ * Returns `true` only on the frame any pointer first pressed over the target
740
+ * sprite. Works with both mouse input and multiple simultaneous touches.
741
+ *
742
+ * Pointer hit testing uses a circular area centered on the sprite. The radius
743
+ * is `sprite.size * radiusScale`. World-space sprites are tested against the
744
+ * current camera scroll. HUD sprites with `ignoreScroll = true` are tested in
745
+ * screen space.
746
+ *
747
+ * Use this for one-shot virtual buttons such as menu taps.
748
+ *
749
+ * @param sprite - Target sprite to test. If `null` / `undefined`, returns `false`.
750
+ * @param radiusScale - Multiplier applied to `sprite.size` to define the hit radius.
751
+ * Default: `0.5`.
752
+ * @returns `true` if any pointer began pressing this frame over the sprite hit area.
753
+ *
754
+ * @example
755
+ * ```ts
756
+ * if (game.isPointerPressedOverSprite(startButton, 0.8)) {
757
+ * startGame();
758
+ * }
759
+ * ```
760
+ */
761
+ isPointerPressedOverSprite(sprite: Sprite | null | undefined, radiusScale?: number): boolean;
762
+ /**
763
+ * Returns a read-only snapshot of all currently active pointers.
764
+ *
765
+ * Mouse appears in the list only while the mouse button is held down. Touches
766
+ * appear while they remain active on the canvas. Coordinates are returned in
767
+ * canvas/screen space.
768
+ *
769
+ * Use this for advanced multitouch controls such as joysticks, drag handles,
770
+ * or gesture-like gameplay logic.
771
+ *
772
+ * @returns A read-only array of active pointer snapshots.
773
+ *
774
+ * @example
775
+ * ```ts
776
+ * const pointers = game.getPointers();
777
+ * if (pointers.length > 0) {
778
+ * const first = pointers[0];
779
+ * game.text(`Pointer: ${first.x}, ${first.y}`, 10, 10);
780
+ * }
781
+ * ```
782
+ */
783
+ getPointers(): readonly PointerInfo[];
784
+ /**
785
+ * Returns a read-only snapshot of all active pointers currently overlapping
786
+ * the target sprite.
787
+ *
788
+ * Pointer hit testing uses a circular area centered on the sprite. The radius
789
+ * is `sprite.size * radiusScale`. World-space sprites are tested against the
790
+ * current camera scroll. HUD sprites with `ignoreScroll = true` are tested in
791
+ * screen space.
792
+ *
793
+ * Use this when you need more than a boolean result, such as reading the exact
794
+ * pointer position over a virtual joystick or draggable control.
795
+ *
796
+ * @param sprite - Target sprite to test. If `null` / `undefined`, returns an empty array.
797
+ * @param radiusScale - Multiplier applied to `sprite.size` to define the hit radius.
798
+ * Default: `0.5`.
799
+ * @returns A read-only array of active pointer snapshots currently over the sprite.
800
+ *
801
+ * @example
802
+ * ```ts
803
+ * const touches = game.getPointersOverSprite(joystickBase, 1.0);
804
+ * if (touches.length > 0) {
805
+ * const p = touches[0];
806
+ * const dx = p.x - joystickBase.x;
807
+ * }
808
+ * ```
809
+ */
810
+ getPointersOverSprite(sprite: Sprite | null | undefined, radiusScale?: number): readonly PointerInfo[];
688
811
  /**
689
812
  * Returns `true` when the current device appears to be mobile/touch-first.
690
813
  *