@guinetik/gcanvas 1.0.4 → 2.0.0

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.
Files changed (261) hide show
  1. package/dist/CNAME +1 -0
  2. package/dist/aizawa.html +27 -0
  3. package/dist/animations.html +31 -0
  4. package/dist/basic.html +38 -0
  5. package/dist/baskara.html +31 -0
  6. package/dist/bezier.html +35 -0
  7. package/dist/beziersignature.html +29 -0
  8. package/dist/blackhole.html +28 -0
  9. package/dist/blob.html +35 -0
  10. package/dist/clifford.html +25 -0
  11. package/dist/cmb.html +24 -0
  12. package/dist/coordinates.html +698 -0
  13. package/dist/cube3d.html +23 -0
  14. package/dist/dadras.html +26 -0
  15. package/dist/dejong.html +25 -0
  16. package/dist/demos.css +303 -0
  17. package/dist/dino.html +42 -0
  18. package/dist/easing.html +28 -0
  19. package/dist/events.html +195 -0
  20. package/dist/fluent.html +647 -0
  21. package/dist/fluid-simple.html +22 -0
  22. package/dist/fluid.html +37 -0
  23. package/dist/fractals.html +36 -0
  24. package/dist/gameobjects.html +626 -0
  25. package/dist/gcanvas.es.js +14368 -9093
  26. package/dist/gcanvas.es.min.js +1 -1
  27. package/dist/gcanvas.umd.js +1 -1
  28. package/dist/gcanvas.umd.min.js +1 -1
  29. package/dist/genart.html +26 -0
  30. package/dist/gendream.html +26 -0
  31. package/dist/group.html +36 -0
  32. package/dist/halvorsen.html +27 -0
  33. package/dist/home.html +587 -0
  34. package/dist/hyperbolic001.html +23 -0
  35. package/dist/hyperbolic002.html +23 -0
  36. package/dist/hyperbolic003.html +23 -0
  37. package/dist/hyperbolic004.html +23 -0
  38. package/dist/hyperbolic005.html +22 -0
  39. package/dist/index.html +446 -0
  40. package/dist/isometric.html +34 -0
  41. package/dist/js/aizawa.js +425 -0
  42. package/dist/js/animations.js +452 -0
  43. package/dist/js/basic.js +204 -0
  44. package/dist/js/baskara.js +751 -0
  45. package/dist/js/bezier.js +692 -0
  46. package/dist/js/beziersignature.js +241 -0
  47. package/dist/js/blackhole/accretiondisk.obj.js +379 -0
  48. package/dist/js/blackhole/blackhole.obj.js +318 -0
  49. package/dist/js/blackhole/index.js +409 -0
  50. package/dist/js/blackhole/particle.js +56 -0
  51. package/dist/js/blackhole/starfield.obj.js +218 -0
  52. package/dist/js/blob.js +2276 -0
  53. package/dist/js/clifford.js +236 -0
  54. package/dist/js/cmb.js +594 -0
  55. package/dist/js/coordinates.js +840 -0
  56. package/dist/js/cube3d.js +789 -0
  57. package/dist/js/dadras.js +405 -0
  58. package/dist/js/dejong.js +257 -0
  59. package/dist/js/dino.js +1420 -0
  60. package/dist/js/easing.js +477 -0
  61. package/dist/js/fluent.js +183 -0
  62. package/dist/js/fluid-simple.js +253 -0
  63. package/dist/js/fluid.js +527 -0
  64. package/dist/js/fractals.js +932 -0
  65. package/dist/js/fractalworker.js +93 -0
  66. package/dist/js/gameobjects.js +176 -0
  67. package/dist/js/genart.js +268 -0
  68. package/dist/js/gendream.js +209 -0
  69. package/dist/js/group.js +140 -0
  70. package/dist/js/halvorsen.js +405 -0
  71. package/dist/js/hyperbolic001.js +310 -0
  72. package/dist/js/hyperbolic002.js +388 -0
  73. package/dist/js/hyperbolic003.js +319 -0
  74. package/dist/js/hyperbolic004.js +345 -0
  75. package/dist/js/hyperbolic005.js +340 -0
  76. package/dist/js/info-toggle.js +25 -0
  77. package/dist/js/isometric.js +851 -0
  78. package/dist/js/kerr.js +1547 -0
  79. package/dist/js/lavalamp.js +590 -0
  80. package/dist/js/layout.js +354 -0
  81. package/dist/js/lorenz.js +425 -0
  82. package/dist/js/mondrian.js +285 -0
  83. package/dist/js/opacity.js +275 -0
  84. package/dist/js/painter.js +484 -0
  85. package/dist/js/particles-showcase.js +514 -0
  86. package/dist/js/particles.js +299 -0
  87. package/dist/js/patterns.js +397 -0
  88. package/dist/js/penrose/artifact.js +69 -0
  89. package/dist/js/penrose/blackhole.js +121 -0
  90. package/dist/js/penrose/constants.js +73 -0
  91. package/dist/js/penrose/game.js +943 -0
  92. package/dist/js/penrose/lore.js +278 -0
  93. package/dist/js/penrose/penrosescene.js +892 -0
  94. package/dist/js/penrose/ship.js +216 -0
  95. package/dist/js/penrose/sounds.js +211 -0
  96. package/dist/js/penrose/voidparticle.js +55 -0
  97. package/dist/js/penrose/voidscene.js +258 -0
  98. package/dist/js/penrose/voidship.js +144 -0
  99. package/dist/js/penrose/wormhole.js +46 -0
  100. package/dist/js/pipeline.js +555 -0
  101. package/dist/js/plane3d.js +256 -0
  102. package/dist/js/platformer.js +1579 -0
  103. package/dist/js/rossler.js +480 -0
  104. package/dist/js/scene.js +304 -0
  105. package/dist/js/scenes.js +320 -0
  106. package/dist/js/schrodinger.js +706 -0
  107. package/dist/js/schwarzschild.js +1015 -0
  108. package/dist/js/shapes.js +628 -0
  109. package/dist/js/space/alien.js +171 -0
  110. package/dist/js/space/boom.js +98 -0
  111. package/dist/js/space/boss.js +353 -0
  112. package/dist/js/space/buff.js +73 -0
  113. package/dist/js/space/bullet.js +102 -0
  114. package/dist/js/space/constants.js +85 -0
  115. package/dist/js/space/game.js +1884 -0
  116. package/dist/js/space/hud.js +112 -0
  117. package/dist/js/space/laserbeam.js +179 -0
  118. package/dist/js/space/lightning.js +277 -0
  119. package/dist/js/space/minion.js +192 -0
  120. package/dist/js/space/missile.js +212 -0
  121. package/dist/js/space/player.js +430 -0
  122. package/dist/js/space/powerup.js +90 -0
  123. package/dist/js/space/starfield.js +58 -0
  124. package/dist/js/space/starpower.js +90 -0
  125. package/dist/js/spacetime.js +559 -0
  126. package/dist/js/sphere3d.js +229 -0
  127. package/dist/js/sprite.js +473 -0
  128. package/dist/js/starfaux/config.js +118 -0
  129. package/dist/js/starfaux/enemy.js +353 -0
  130. package/dist/js/starfaux/hud.js +78 -0
  131. package/dist/js/starfaux/index.js +482 -0
  132. package/dist/js/starfaux/laser.js +182 -0
  133. package/dist/js/starfaux/player.js +468 -0
  134. package/dist/js/starfaux/terrain.js +560 -0
  135. package/dist/js/study001.js +275 -0
  136. package/dist/js/study002.js +366 -0
  137. package/dist/js/study003.js +331 -0
  138. package/dist/js/study004.js +389 -0
  139. package/dist/js/study005.js +209 -0
  140. package/dist/js/study006.js +194 -0
  141. package/dist/js/study007.js +192 -0
  142. package/dist/js/study008.js +413 -0
  143. package/dist/js/svgtween.js +204 -0
  144. package/dist/js/tde/accretiondisk.js +471 -0
  145. package/dist/js/tde/blackhole.js +219 -0
  146. package/dist/js/tde/blackholescene.js +209 -0
  147. package/dist/js/tde/config.js +59 -0
  148. package/dist/js/tde/index.js +820 -0
  149. package/dist/js/tde/jets.js +290 -0
  150. package/dist/js/tde/lensedstarfield.js +154 -0
  151. package/dist/js/tde/tdestar.js +297 -0
  152. package/dist/js/tde/tidalstream.js +372 -0
  153. package/dist/js/tde_old/blackhole.obj.js +354 -0
  154. package/dist/js/tde_old/debris.obj.js +791 -0
  155. package/dist/js/tde_old/flare.obj.js +239 -0
  156. package/dist/js/tde_old/index.js +448 -0
  157. package/dist/js/tde_old/star.obj.js +812 -0
  158. package/dist/js/tetris/config.js +157 -0
  159. package/dist/js/tetris/grid.js +286 -0
  160. package/dist/js/tetris/index.js +1195 -0
  161. package/dist/js/tetris/renderer.js +634 -0
  162. package/dist/js/tetris/tetrominos.js +280 -0
  163. package/dist/js/thomas.js +394 -0
  164. package/dist/js/tiles.js +312 -0
  165. package/dist/js/tweendemo.js +79 -0
  166. package/dist/js/visibility.js +102 -0
  167. package/dist/kerr.html +28 -0
  168. package/dist/lavalamp.html +27 -0
  169. package/dist/layouts.html +37 -0
  170. package/dist/logo.svg +4 -0
  171. package/dist/loop.html +84 -0
  172. package/dist/lorenz.html +27 -0
  173. package/dist/mondrian.html +32 -0
  174. package/dist/og_image.png +0 -0
  175. package/dist/opacity.html +36 -0
  176. package/dist/painter.html +39 -0
  177. package/dist/particles-showcase.html +28 -0
  178. package/dist/particles.html +24 -0
  179. package/dist/patterns.html +33 -0
  180. package/dist/penrose-game.html +31 -0
  181. package/dist/pipeline.html +737 -0
  182. package/dist/plane3d.html +24 -0
  183. package/dist/platformer.html +43 -0
  184. package/dist/rossler.html +27 -0
  185. package/dist/scene-interactivity-test.html +220 -0
  186. package/dist/scene.html +33 -0
  187. package/dist/scenes.html +96 -0
  188. package/dist/schrodinger.html +27 -0
  189. package/dist/schwarzschild.html +27 -0
  190. package/dist/shapes.html +16 -0
  191. package/dist/space.html +85 -0
  192. package/dist/spacetime.html +27 -0
  193. package/dist/sphere3d.html +24 -0
  194. package/dist/sprite.html +18 -0
  195. package/dist/starfaux.html +22 -0
  196. package/dist/study001.html +23 -0
  197. package/dist/study002.html +23 -0
  198. package/dist/study003.html +23 -0
  199. package/dist/study004.html +23 -0
  200. package/dist/study005.html +22 -0
  201. package/dist/study006.html +24 -0
  202. package/dist/study007.html +24 -0
  203. package/dist/study008.html +22 -0
  204. package/dist/svgtween.html +29 -0
  205. package/dist/tde.html +28 -0
  206. package/dist/tetris3d.html +25 -0
  207. package/dist/thomas.html +27 -0
  208. package/dist/tiles.html +28 -0
  209. package/dist/transforms.html +400 -0
  210. package/dist/tween.html +45 -0
  211. package/dist/visibility.html +33 -0
  212. package/package.json +1 -1
  213. package/readme.md +30 -22
  214. package/src/game/objects/go.js +7 -0
  215. package/src/game/objects/index.js +2 -0
  216. package/src/game/objects/isometric-scene.js +53 -3
  217. package/src/game/objects/layoutscene.js +57 -0
  218. package/src/game/objects/mask.js +241 -0
  219. package/src/game/objects/scene.js +19 -0
  220. package/src/game/objects/wrapper.js +14 -2
  221. package/src/game/pipeline.js +17 -0
  222. package/src/game/ui/button.js +101 -16
  223. package/src/game/ui/theme.js +0 -6
  224. package/src/game/ui/togglebutton.js +25 -14
  225. package/src/game/ui/tooltip.js +12 -4
  226. package/src/index.js +3 -0
  227. package/src/io/gesture.js +409 -0
  228. package/src/io/index.js +4 -1
  229. package/src/io/keys.js +9 -1
  230. package/src/io/screen.js +476 -0
  231. package/src/math/attractors.js +664 -0
  232. package/src/math/heat.js +106 -0
  233. package/src/math/index.js +1 -0
  234. package/src/mixins/draggable.js +15 -19
  235. package/src/painter/painter.shapes.js +11 -5
  236. package/src/particle/particle-system.js +165 -1
  237. package/src/physics/index.js +26 -0
  238. package/src/physics/physics-updaters.js +333 -0
  239. package/src/physics/physics.js +375 -0
  240. package/src/shapes/image.js +5 -5
  241. package/src/shapes/index.js +2 -0
  242. package/src/shapes/parallelogram.js +147 -0
  243. package/src/shapes/righttriangle.js +115 -0
  244. package/src/shapes/svg.js +281 -100
  245. package/src/shapes/text.js +22 -6
  246. package/src/shapes/transformable.js +5 -0
  247. package/src/sound/effects.js +807 -0
  248. package/src/sound/index.js +13 -0
  249. package/src/webgl/index.js +7 -0
  250. package/src/webgl/shaders/clifford-point-shaders.js +131 -0
  251. package/src/webgl/shaders/dejong-point-shaders.js +131 -0
  252. package/src/webgl/shaders/point-sprite-shaders.js +152 -0
  253. package/src/webgl/webgl-clifford-renderer.js +477 -0
  254. package/src/webgl/webgl-dejong-renderer.js +472 -0
  255. package/src/webgl/webgl-line-renderer.js +391 -0
  256. package/src/webgl/webgl-particle-renderer.js +410 -0
  257. package/types/index.d.ts +30 -2
  258. package/types/io.d.ts +217 -0
  259. package/types/physics.d.ts +299 -0
  260. package/types/shapes.d.ts +8 -0
  261. package/types/webgl.d.ts +188 -109
@@ -0,0 +1,409 @@
1
+ /**
2
+ * @module io/gesture
3
+ * @description High-level gesture recognition for zoom, pan, and tap across mouse and touch.
4
+ *
5
+ * Provides unified gesture handling that works seamlessly on both desktop and mobile:
6
+ * - Mouse wheel → zoom
7
+ * - Pinch (two fingers) → zoom
8
+ * - Mouse drag → pan
9
+ * - Single finger drag → pan
10
+ * - Quick tap/click → tap event
11
+ *
12
+ * @example
13
+ * // Basic usage
14
+ * import { Gesture } from '@guinetik/gcanvas';
15
+ *
16
+ * const gesture = new Gesture(canvas, {
17
+ * onZoom: (delta, center) => {
18
+ * this.zoom *= delta > 0 ? 1.1 : 0.9;
19
+ * },
20
+ * onPan: (dx, dy) => {
21
+ * this.offsetX += dx;
22
+ * this.offsetY += dy;
23
+ * },
24
+ * onTap: (x, y) => {
25
+ * this.handleClick(x, y);
26
+ * }
27
+ * });
28
+ *
29
+ * // Cleanup when done
30
+ * gesture.destroy();
31
+ */
32
+
33
+ /**
34
+ * Gesture class for handling zoom, pan, and tap gestures
35
+ */
36
+ export class Gesture {
37
+ /**
38
+ * Create a new Gesture handler
39
+ * @param {HTMLCanvasElement} canvas - The canvas element to attach gestures to
40
+ * @param {Object} options - Configuration options
41
+ * @param {Function} [options.onZoom] - Callback for zoom: (delta, centerX, centerY) => void
42
+ * delta > 0 = zoom in, delta < 0 = zoom out
43
+ * @param {Function} [options.onPan] - Callback for pan: (dx, dy) => void
44
+ * @param {Function} [options.onTap] - Callback for tap/click: (x, y) => void
45
+ * @param {Function} [options.onDragStart] - Callback when drag starts: (x, y) => void
46
+ * @param {Function} [options.onDragEnd] - Callback when drag ends: () => void
47
+ * @param {number} [options.wheelZoomFactor=0.1] - Zoom sensitivity for mouse wheel
48
+ * @param {number} [options.pinchZoomFactor=1] - Zoom sensitivity for pinch
49
+ * @param {number} [options.panScale=1] - Scale factor for pan deltas
50
+ * @param {number} [options.tapThreshold=10] - Max movement (px) to still count as tap
51
+ * @param {number} [options.tapTimeout=300] - Max duration (ms) for tap
52
+ * @param {boolean} [options.preventDefault=true] - Prevent default browser behavior
53
+ */
54
+ constructor(canvas, options = {}) {
55
+ this.canvas = canvas;
56
+
57
+ // Callbacks
58
+ this.onZoom = options.onZoom || null;
59
+ this.onPan = options.onPan || null;
60
+ this.onTap = options.onTap || null;
61
+ this.onDragStart = options.onDragStart || null;
62
+ this.onDragEnd = options.onDragEnd || null;
63
+
64
+ // Config
65
+ this.wheelZoomFactor = options.wheelZoomFactor ?? 0.1;
66
+ this.pinchZoomFactor = options.pinchZoomFactor ?? 1;
67
+ this.panScale = options.panScale ?? 1;
68
+ this.tapThreshold = options.tapThreshold ?? 10;
69
+ this.tapTimeout = options.tapTimeout ?? 300;
70
+ this.preventDefault = options.preventDefault ?? true;
71
+
72
+ // State
73
+ this._isDragging = false;
74
+ this._hasMoved = false;
75
+ this._startTime = 0;
76
+ this._startX = 0;
77
+ this._startY = 0;
78
+ this._lastX = 0;
79
+ this._lastY = 0;
80
+
81
+ // Touch state
82
+ this._touches = new Map();
83
+ this._lastPinchDist = 0;
84
+ this._lastPinchCenterX = 0;
85
+ this._lastPinchCenterY = 0;
86
+
87
+ // Bind handlers
88
+ this._onMouseDown = this._onMouseDown.bind(this);
89
+ this._onMouseMove = this._onMouseMove.bind(this);
90
+ this._onMouseUp = this._onMouseUp.bind(this);
91
+ this._onMouseLeave = this._onMouseLeave.bind(this);
92
+ this._onWheel = this._onWheel.bind(this);
93
+ this._onTouchStart = this._onTouchStart.bind(this);
94
+ this._onTouchMove = this._onTouchMove.bind(this);
95
+ this._onTouchEnd = this._onTouchEnd.bind(this);
96
+ this._onTouchCancel = this._onTouchCancel.bind(this);
97
+
98
+ // Attach listeners
99
+ this._attachListeners();
100
+ }
101
+
102
+ /**
103
+ * Check if currently dragging
104
+ * @returns {boolean}
105
+ */
106
+ get isDragging() {
107
+ return this._isDragging;
108
+ }
109
+
110
+ /**
111
+ * Attach all event listeners
112
+ * @private
113
+ */
114
+ _attachListeners() {
115
+ const canvas = this.canvas;
116
+ const passive = !this.preventDefault;
117
+
118
+ // Mouse
119
+ canvas.addEventListener('mousedown', this._onMouseDown);
120
+ canvas.addEventListener('mousemove', this._onMouseMove);
121
+ canvas.addEventListener('mouseup', this._onMouseUp);
122
+ canvas.addEventListener('mouseleave', this._onMouseLeave);
123
+ canvas.addEventListener('wheel', this._onWheel, { passive });
124
+
125
+ // Touch
126
+ canvas.addEventListener('touchstart', this._onTouchStart, { passive });
127
+ canvas.addEventListener('touchmove', this._onTouchMove, { passive });
128
+ canvas.addEventListener('touchend', this._onTouchEnd, { passive });
129
+ canvas.addEventListener('touchcancel', this._onTouchCancel);
130
+ }
131
+
132
+ /**
133
+ * Remove all event listeners
134
+ */
135
+ destroy() {
136
+ const canvas = this.canvas;
137
+
138
+ canvas.removeEventListener('mousedown', this._onMouseDown);
139
+ canvas.removeEventListener('mousemove', this._onMouseMove);
140
+ canvas.removeEventListener('mouseup', this._onMouseUp);
141
+ canvas.removeEventListener('mouseleave', this._onMouseLeave);
142
+ canvas.removeEventListener('wheel', this._onWheel);
143
+
144
+ canvas.removeEventListener('touchstart', this._onTouchStart);
145
+ canvas.removeEventListener('touchmove', this._onTouchMove);
146
+ canvas.removeEventListener('touchend', this._onTouchEnd);
147
+ canvas.removeEventListener('touchcancel', this._onTouchCancel);
148
+
149
+ this._touches.clear();
150
+ }
151
+
152
+ /**
153
+ * Get canvas-relative coordinates from a mouse event
154
+ * @private
155
+ */
156
+ _getMousePos(e) {
157
+ const rect = this.canvas.getBoundingClientRect();
158
+ return {
159
+ x: e.clientX - rect.left,
160
+ y: e.clientY - rect.top
161
+ };
162
+ }
163
+
164
+ /**
165
+ * Get canvas-relative coordinates from a touch
166
+ * @private
167
+ */
168
+ _getTouchPos(touch) {
169
+ const rect = this.canvas.getBoundingClientRect();
170
+ return {
171
+ x: touch.clientX - rect.left,
172
+ y: touch.clientY - rect.top
173
+ };
174
+ }
175
+
176
+ // ─────────────────────────────────────────────────────────────────────────
177
+ // MOUSE HANDLERS
178
+ // ─────────────────────────────────────────────────────────────────────────
179
+
180
+ _onMouseDown(e) {
181
+ const pos = this._getMousePos(e);
182
+ this._isDragging = true;
183
+ this._hasMoved = false;
184
+ this._startTime = Date.now();
185
+ this._startX = pos.x;
186
+ this._startY = pos.y;
187
+ this._lastX = pos.x;
188
+ this._lastY = pos.y;
189
+
190
+ if (this.onDragStart) {
191
+ this.onDragStart(pos.x, pos.y);
192
+ }
193
+ }
194
+
195
+ _onMouseMove(e) {
196
+ if (!this._isDragging) return;
197
+
198
+ const pos = this._getMousePos(e);
199
+ const dx = pos.x - this._lastX;
200
+ const dy = pos.y - this._lastY;
201
+
202
+ // Check if moved enough to count as drag
203
+ const totalDx = Math.abs(pos.x - this._startX);
204
+ const totalDy = Math.abs(pos.y - this._startY);
205
+ if (totalDx > this.tapThreshold || totalDy > this.tapThreshold) {
206
+ this._hasMoved = true;
207
+ }
208
+
209
+ if (this.onPan && this._hasMoved) {
210
+ this.onPan(dx * this.panScale, dy * this.panScale);
211
+ }
212
+
213
+ this._lastX = pos.x;
214
+ this._lastY = pos.y;
215
+ }
216
+
217
+ _onMouseUp(e) {
218
+ if (!this._isDragging) return;
219
+
220
+ const now = Date.now();
221
+ const duration = now - this._startTime;
222
+
223
+ // Check for tap (quick, no movement)
224
+ if (!this._hasMoved && duration < this.tapTimeout && this.onTap) {
225
+ const pos = this._getMousePos(e);
226
+ this.onTap(pos.x, pos.y);
227
+ }
228
+
229
+ this._isDragging = false;
230
+
231
+ if (this.onDragEnd) {
232
+ this.onDragEnd();
233
+ }
234
+ }
235
+
236
+ _onMouseLeave() {
237
+ if (this._isDragging) {
238
+ this._isDragging = false;
239
+ if (this.onDragEnd) {
240
+ this.onDragEnd();
241
+ }
242
+ }
243
+ }
244
+
245
+ _onWheel(e) {
246
+ if (this.preventDefault) {
247
+ e.preventDefault();
248
+ }
249
+
250
+ if (this.onZoom) {
251
+ const pos = this._getMousePos(e);
252
+ const delta = e.deltaY > 0 ? -this.wheelZoomFactor : this.wheelZoomFactor;
253
+ this.onZoom(delta, pos.x, pos.y);
254
+ }
255
+ }
256
+
257
+ // ─────────────────────────────────────────────────────────────────────────
258
+ // TOUCH HANDLERS
259
+ // ─────────────────────────────────────────────────────────────────────────
260
+
261
+ _onTouchStart(e) {
262
+ if (this.preventDefault) {
263
+ e.preventDefault();
264
+ }
265
+
266
+ this._startTime = Date.now();
267
+ this._hasMoved = false;
268
+
269
+ // Track all touches
270
+ for (const touch of e.changedTouches) {
271
+ const pos = this._getTouchPos(touch);
272
+ this._touches.set(touch.identifier, { x: pos.x, y: pos.y });
273
+ }
274
+
275
+ // Two fingers - initialize pinch
276
+ if (this._touches.size === 2) {
277
+ const [t1, t2] = Array.from(this._touches.values());
278
+ this._lastPinchDist = Math.hypot(t2.x - t1.x, t2.y - t1.y);
279
+ this._lastPinchCenterX = (t1.x + t2.x) / 2;
280
+ this._lastPinchCenterY = (t1.y + t2.y) / 2;
281
+ // Stop single-finger drag when pinching
282
+ this._isDragging = false;
283
+ }
284
+
285
+ // Single finger - start drag
286
+ if (this._touches.size === 1) {
287
+ const touch = e.touches[0];
288
+ const pos = this._getTouchPos(touch);
289
+ this._isDragging = true;
290
+ this._startX = pos.x;
291
+ this._startY = pos.y;
292
+ this._lastX = pos.x;
293
+ this._lastY = pos.y;
294
+
295
+ if (this.onDragStart) {
296
+ this.onDragStart(pos.x, pos.y);
297
+ }
298
+ }
299
+ }
300
+
301
+ _onTouchMove(e) {
302
+ if (this.preventDefault) {
303
+ e.preventDefault();
304
+ }
305
+
306
+ // Update touch positions
307
+ for (const touch of e.changedTouches) {
308
+ if (this._touches.has(touch.identifier)) {
309
+ const pos = this._getTouchPos(touch);
310
+ this._touches.set(touch.identifier, { x: pos.x, y: pos.y });
311
+ }
312
+ }
313
+
314
+ // Two-finger pinch zoom + pan
315
+ if (this._touches.size === 2) {
316
+ const [t1, t2] = Array.from(this._touches.values());
317
+ const pinchDist = Math.hypot(t2.x - t1.x, t2.y - t1.y);
318
+ const pinchCenterX = (t1.x + t2.x) / 2;
319
+ const pinchCenterY = (t1.y + t2.y) / 2;
320
+
321
+ if (this._lastPinchDist > 0) {
322
+ // Zoom based on pinch distance change
323
+ if (this.onZoom) {
324
+ const pinchRatio = pinchDist / this._lastPinchDist;
325
+ const delta = (pinchRatio - 1) * this.pinchZoomFactor;
326
+ this.onZoom(delta, pinchCenterX, pinchCenterY);
327
+ }
328
+
329
+ // Pan based on pinch center movement
330
+ if (this.onPan) {
331
+ const dx = pinchCenterX - this._lastPinchCenterX;
332
+ const dy = pinchCenterY - this._lastPinchCenterY;
333
+ this.onPan(dx * this.panScale, dy * this.panScale);
334
+ }
335
+
336
+ this._hasMoved = true;
337
+ }
338
+
339
+ this._lastPinchDist = pinchDist;
340
+ this._lastPinchCenterX = pinchCenterX;
341
+ this._lastPinchCenterY = pinchCenterY;
342
+ }
343
+ // Single finger drag (pan)
344
+ else if (this._touches.size === 1 && this._isDragging) {
345
+ const touch = e.touches[0];
346
+ const pos = this._getTouchPos(touch);
347
+
348
+ const dx = pos.x - this._lastX;
349
+ const dy = pos.y - this._lastY;
350
+
351
+ // Check if moved enough
352
+ const totalDx = Math.abs(pos.x - this._startX);
353
+ const totalDy = Math.abs(pos.y - this._startY);
354
+ if (totalDx > this.tapThreshold || totalDy > this.tapThreshold) {
355
+ this._hasMoved = true;
356
+ }
357
+
358
+ if (this.onPan && this._hasMoved) {
359
+ this.onPan(dx * this.panScale, dy * this.panScale);
360
+ }
361
+
362
+ this._lastX = pos.x;
363
+ this._lastY = pos.y;
364
+ }
365
+ }
366
+
367
+ _onTouchEnd(e) {
368
+ if (this.preventDefault) {
369
+ e.preventDefault();
370
+ }
371
+
372
+ // Remove ended touches
373
+ for (const touch of e.changedTouches) {
374
+ this._touches.delete(touch.identifier);
375
+ }
376
+
377
+ // Reset pinch if no longer two fingers
378
+ if (this._touches.size < 2) {
379
+ this._lastPinchDist = 0;
380
+ }
381
+
382
+ // All touches released
383
+ if (this._touches.size === 0) {
384
+ // Check for tap
385
+ const now = Date.now();
386
+ const duration = now - this._startTime;
387
+
388
+ if (!this._hasMoved && duration < this.tapTimeout && this.onTap) {
389
+ this.onTap(this._startX, this._startY);
390
+ }
391
+
392
+ this._isDragging = false;
393
+
394
+ if (this.onDragEnd) {
395
+ this.onDragEnd();
396
+ }
397
+ }
398
+ }
399
+
400
+ _onTouchCancel() {
401
+ this._touches.clear();
402
+ this._lastPinchDist = 0;
403
+ this._isDragging = false;
404
+
405
+ if (this.onDragEnd) {
406
+ this.onDragEnd();
407
+ }
408
+ }
409
+ }
package/src/io/index.js CHANGED
@@ -10,6 +10,7 @@
10
10
  * - {@link Keys}: Keyboard input with logical key mapping and state tracking
11
11
  * - {@link Touch}: Touch input for mobile devices
12
12
  * - {@link Input}: Unified input system that normalizes mouse and touch events
13
+ * - {@link Screen}: Screen/device detection and responsive utilities
13
14
  *
14
15
  * The IO module serves as the intermediary between raw browser events and your game logic,
15
16
  * providing consistent, normalized events regardless of input source.
@@ -83,4 +84,6 @@ export {EventEmitter} from "./events.js";
83
84
  export {Input} from "./input.js";
84
85
  export {Mouse} from "./mouse.js";
85
86
  export {Keys} from "./keys.js";
86
- export {Touch} from "./touch.js";
87
+ export {Touch} from "./touch.js";
88
+ export {Screen} from "./screen.js";
89
+ export {Gesture} from "./gesture.js";
package/src/io/keys.js CHANGED
@@ -34,6 +34,10 @@ export class Keys {
34
34
  static E = "E";
35
35
  static R = "R";
36
36
  static F = "F";
37
+ static G = "G";
38
+ static J = "J";
39
+ static K = "K";
40
+ static L = "L";
37
41
  static Z = "Z";
38
42
  static C = "C";
39
43
  static UP = "UP";
@@ -50,7 +54,7 @@ export class Keys {
50
54
  * Customize this list as needed for your game.
51
55
  */
52
56
  static _codeMap = {
53
- // WASD + QE + RFZC
57
+ // WASD + QE + RFZC + GJKL
54
58
  KeyW: Keys.W,
55
59
  KeyA: Keys.A,
56
60
  KeyS: Keys.S,
@@ -59,6 +63,10 @@ export class Keys {
59
63
  KeyE: Keys.E,
60
64
  KeyR: Keys.R,
61
65
  KeyF: Keys.F,
66
+ KeyG: Keys.G,
67
+ KeyJ: Keys.J,
68
+ KeyK: Keys.K,
69
+ KeyL: Keys.L,
62
70
  KeyZ: Keys.Z,
63
71
  KeyC: Keys.C,
64
72