@guinetik/gcanvas 1.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.
- package/.github/workflows/release.yaml +70 -0
- package/.jshintrc +4 -0
- package/.vscode/settings.json +22 -0
- package/CLAUDE.md +310 -0
- package/blackhole.jpg +0 -0
- package/demo.png +0 -0
- package/demos/CNAME +1 -0
- package/demos/animations.html +31 -0
- package/demos/basic.html +38 -0
- package/demos/baskara.html +31 -0
- package/demos/bezier.html +35 -0
- package/demos/beziersignature.html +29 -0
- package/demos/blackhole.html +28 -0
- package/demos/blob.html +35 -0
- package/demos/demos.css +289 -0
- package/demos/easing.html +28 -0
- package/demos/events.html +195 -0
- package/demos/fluent.html +647 -0
- package/demos/fractals.html +36 -0
- package/demos/genart.html +26 -0
- package/demos/gendream.html +26 -0
- package/demos/group.html +36 -0
- package/demos/home.html +587 -0
- package/demos/index.html +364 -0
- package/demos/isometric.html +34 -0
- package/demos/js/animations.js +452 -0
- package/demos/js/basic.js +204 -0
- package/demos/js/baskara.js +751 -0
- package/demos/js/bezier.js +692 -0
- package/demos/js/beziersignature.js +241 -0
- package/demos/js/blackhole/accretiondisk.obj.js +379 -0
- package/demos/js/blackhole/blackhole.obj.js +318 -0
- package/demos/js/blackhole/index.js +409 -0
- package/demos/js/blackhole/particle.js +56 -0
- package/demos/js/blackhole/starfield.obj.js +218 -0
- package/demos/js/blob.js +2263 -0
- package/demos/js/easing.js +477 -0
- package/demos/js/fluent.js +183 -0
- package/demos/js/fractals.js +931 -0
- package/demos/js/fractalworker.js +93 -0
- package/demos/js/genart.js +268 -0
- package/demos/js/gendream.js +209 -0
- package/demos/js/group.js +140 -0
- package/demos/js/info-toggle.js +25 -0
- package/demos/js/isometric.js +863 -0
- package/demos/js/kerr.js +1556 -0
- package/demos/js/lavalamp.js +590 -0
- package/demos/js/layout.js +354 -0
- package/demos/js/mondrian.js +285 -0
- package/demos/js/opacity.js +275 -0
- package/demos/js/painter.js +484 -0
- package/demos/js/particles-showcase.js +514 -0
- package/demos/js/particles.js +299 -0
- package/demos/js/patterns.js +397 -0
- package/demos/js/penrose/artifact.js +69 -0
- package/demos/js/penrose/blackhole.js +121 -0
- package/demos/js/penrose/constants.js +73 -0
- package/demos/js/penrose/game.js +943 -0
- package/demos/js/penrose/lore.js +278 -0
- package/demos/js/penrose/penrosescene.js +892 -0
- package/demos/js/penrose/ship.js +216 -0
- package/demos/js/penrose/sounds.js +211 -0
- package/demos/js/penrose/voidparticle.js +55 -0
- package/demos/js/penrose/voidscene.js +258 -0
- package/demos/js/penrose/voidship.js +144 -0
- package/demos/js/penrose/wormhole.js +46 -0
- package/demos/js/pipeline.js +555 -0
- package/demos/js/scene.js +304 -0
- package/demos/js/scenes.js +320 -0
- package/demos/js/schrodinger.js +410 -0
- package/demos/js/schwarzschild.js +1023 -0
- package/demos/js/shapes.js +628 -0
- package/demos/js/space/alien.js +171 -0
- package/demos/js/space/boom.js +98 -0
- package/demos/js/space/boss.js +353 -0
- package/demos/js/space/buff.js +73 -0
- package/demos/js/space/bullet.js +102 -0
- package/demos/js/space/constants.js +85 -0
- package/demos/js/space/game.js +1884 -0
- package/demos/js/space/hud.js +112 -0
- package/demos/js/space/laserbeam.js +179 -0
- package/demos/js/space/lightning.js +277 -0
- package/demos/js/space/minion.js +192 -0
- package/demos/js/space/missile.js +212 -0
- package/demos/js/space/player.js +430 -0
- package/demos/js/space/powerup.js +90 -0
- package/demos/js/space/starfield.js +58 -0
- package/demos/js/space/starpower.js +90 -0
- package/demos/js/spacetime.js +559 -0
- package/demos/js/svgtween.js +204 -0
- package/demos/js/tde/accretiondisk.js +418 -0
- package/demos/js/tde/blackhole.js +219 -0
- package/demos/js/tde/blackholescene.js +209 -0
- package/demos/js/tde/config.js +59 -0
- package/demos/js/tde/index.js +695 -0
- package/demos/js/tde/jets.js +290 -0
- package/demos/js/tde/lensedstarfield.js +147 -0
- package/demos/js/tde/tdestar.js +317 -0
- package/demos/js/tde/tidalstream.js +356 -0
- package/demos/js/tde_old/blackhole.obj.js +354 -0
- package/demos/js/tde_old/debris.obj.js +791 -0
- package/demos/js/tde_old/flare.obj.js +239 -0
- package/demos/js/tde_old/index.js +448 -0
- package/demos/js/tde_old/star.obj.js +812 -0
- package/demos/js/tiles.js +312 -0
- package/demos/js/tweendemo.js +79 -0
- package/demos/js/visibility.js +102 -0
- package/demos/kerr.html +28 -0
- package/demos/lavalamp.html +27 -0
- package/demos/layouts.html +37 -0
- package/demos/logo.svg +4 -0
- package/demos/loop.html +84 -0
- package/demos/mondrian.html +32 -0
- package/demos/og_image.png +0 -0
- package/demos/opacity.html +36 -0
- package/demos/painter.html +39 -0
- package/demos/particles-showcase.html +28 -0
- package/demos/particles.html +24 -0
- package/demos/patterns.html +33 -0
- package/demos/penrose-game.html +31 -0
- package/demos/pipeline.html +737 -0
- package/demos/scene.html +33 -0
- package/demos/scenes.html +96 -0
- package/demos/schrodinger.html +27 -0
- package/demos/schwarzschild.html +27 -0
- package/demos/shapes.html +16 -0
- package/demos/space.html +85 -0
- package/demos/spacetime.html +27 -0
- package/demos/svgtween.html +29 -0
- package/demos/tde.html +28 -0
- package/demos/tiles.html +28 -0
- package/demos/transforms.html +400 -0
- package/demos/tween.html +45 -0
- package/demos/visibility.html +33 -0
- package/disk_example.png +0 -0
- package/docs/README.md +222 -0
- package/docs/concepts/architecture-overview.md +204 -0
- package/docs/concepts/lifecycle.md +255 -0
- package/docs/concepts/rendering-pipeline.md +279 -0
- package/docs/concepts/tde-zorder.md +106 -0
- package/docs/concepts/two-layer-architecture.md +229 -0
- package/docs/getting-started/first-game.md +354 -0
- package/docs/getting-started/hello-world.md +269 -0
- package/docs/getting-started/installation.md +157 -0
- package/docs/modules/collision/README.md +453 -0
- package/docs/modules/fluent/README.md +1075 -0
- package/docs/modules/game/README.md +303 -0
- package/docs/modules/isometric-camera.md +210 -0
- package/docs/modules/isometric.md +275 -0
- package/docs/modules/painter/README.md +328 -0
- package/docs/modules/particle/README.md +559 -0
- package/docs/modules/shapes/README.md +221 -0
- package/docs/modules/shapes/base/euclidian.md +123 -0
- package/docs/modules/shapes/base/geometry2d.md +204 -0
- package/docs/modules/shapes/base/renderable.md +215 -0
- package/docs/modules/shapes/base/shape.md +262 -0
- package/docs/modules/shapes/base/transformable.md +243 -0
- package/docs/modules/shapes/hierarchy.md +218 -0
- package/docs/modules/state/README.md +577 -0
- package/docs/modules/util/README.md +99 -0
- package/docs/modules/util/camera3d.md +412 -0
- package/docs/modules/util/scene3d.md +395 -0
- package/index.html +17 -0
- package/jsdoc.json +50 -0
- package/package.json +55 -0
- package/readme.md +599 -0
- package/scripts/build-demo.js +69 -0
- package/scripts/bundle4llm.js +276 -0
- package/scripts/clearconsole.js +48 -0
- package/src/collision/collision-system.js +332 -0
- package/src/collision/collision.js +303 -0
- package/src/collision/index.js +10 -0
- package/src/fluent/fluent-game.js +430 -0
- package/src/fluent/fluent-go.js +1060 -0
- package/src/fluent/fluent-layer.js +152 -0
- package/src/fluent/fluent-scene.js +291 -0
- package/src/fluent/index.js +98 -0
- package/src/fluent/sketch.js +380 -0
- package/src/game/game.js +467 -0
- package/src/game/index.js +49 -0
- package/src/game/objects/go.js +220 -0
- package/src/game/objects/imagego.js +30 -0
- package/src/game/objects/index.js +54 -0
- package/src/game/objects/isometric-scene.js +260 -0
- package/src/game/objects/layoutscene.js +549 -0
- package/src/game/objects/scene.js +175 -0
- package/src/game/objects/scene3d.js +118 -0
- package/src/game/objects/text.js +221 -0
- package/src/game/objects/wrapper.js +232 -0
- package/src/game/pipeline.js +243 -0
- package/src/game/ui/button.js +396 -0
- package/src/game/ui/cursor.js +93 -0
- package/src/game/ui/fps.js +91 -0
- package/src/game/ui/index.js +5 -0
- package/src/game/ui/togglebutton.js +93 -0
- package/src/game/ui/tooltip.js +249 -0
- package/src/index.js +25 -0
- package/src/io/events.js +20 -0
- package/src/io/index.js +86 -0
- package/src/io/input.js +70 -0
- package/src/io/keys.js +152 -0
- package/src/io/mouse.js +61 -0
- package/src/io/touch.js +39 -0
- package/src/logger/debugtab.js +138 -0
- package/src/logger/index.js +3 -0
- package/src/logger/loggable.js +47 -0
- package/src/logger/logger.js +113 -0
- package/src/math/complex.js +37 -0
- package/src/math/constants.js +1 -0
- package/src/math/fractal.js +1271 -0
- package/src/math/gr.js +201 -0
- package/src/math/heat.js +202 -0
- package/src/math/index.js +12 -0
- package/src/math/noise.js +433 -0
- package/src/math/orbital.js +191 -0
- package/src/math/patterns.js +1339 -0
- package/src/math/penrose.js +259 -0
- package/src/math/quantum.js +115 -0
- package/src/math/random.js +195 -0
- package/src/math/tensor.js +1009 -0
- package/src/mixins/anchor.js +131 -0
- package/src/mixins/draggable.js +72 -0
- package/src/mixins/index.js +2 -0
- package/src/motion/bezier.js +132 -0
- package/src/motion/bounce.js +58 -0
- package/src/motion/easing.js +349 -0
- package/src/motion/float.js +130 -0
- package/src/motion/follow.js +125 -0
- package/src/motion/hop.js +52 -0
- package/src/motion/index.js +82 -0
- package/src/motion/motion.js +1124 -0
- package/src/motion/orbit.js +49 -0
- package/src/motion/oscillate.js +39 -0
- package/src/motion/parabolic.js +141 -0
- package/src/motion/patrol.js +147 -0
- package/src/motion/pendulum.js +48 -0
- package/src/motion/pulse.js +88 -0
- package/src/motion/shake.js +83 -0
- package/src/motion/spiral.js +144 -0
- package/src/motion/spring.js +150 -0
- package/src/motion/swing.js +47 -0
- package/src/motion/tween.js +92 -0
- package/src/motion/tweenetik.js +139 -0
- package/src/motion/waypoint.js +210 -0
- package/src/painter/index.js +8 -0
- package/src/painter/painter.colors.js +331 -0
- package/src/painter/painter.effects.js +230 -0
- package/src/painter/painter.img.js +229 -0
- package/src/painter/painter.js +295 -0
- package/src/painter/painter.lines.js +189 -0
- package/src/painter/painter.opacity.js +41 -0
- package/src/painter/painter.shapes.js +277 -0
- package/src/painter/painter.text.js +273 -0
- package/src/particle/emitter.js +124 -0
- package/src/particle/index.js +11 -0
- package/src/particle/particle-system.js +322 -0
- package/src/particle/particle.js +71 -0
- package/src/particle/updaters.js +170 -0
- package/src/shapes/arc.js +43 -0
- package/src/shapes/arrow.js +33 -0
- package/src/shapes/bezier.js +42 -0
- package/src/shapes/circle.js +62 -0
- package/src/shapes/clouds.js +56 -0
- package/src/shapes/cone.js +219 -0
- package/src/shapes/cross.js +70 -0
- package/src/shapes/cube.js +244 -0
- package/src/shapes/cylinder.js +254 -0
- package/src/shapes/diamond.js +48 -0
- package/src/shapes/euclidian.js +111 -0
- package/src/shapes/figure.js +115 -0
- package/src/shapes/geometry.js +220 -0
- package/src/shapes/group.js +375 -0
- package/src/shapes/heart.js +42 -0
- package/src/shapes/hexagon.js +26 -0
- package/src/shapes/image.js +192 -0
- package/src/shapes/index.js +111 -0
- package/src/shapes/line.js +29 -0
- package/src/shapes/pattern.js +90 -0
- package/src/shapes/pin.js +44 -0
- package/src/shapes/poly.js +31 -0
- package/src/shapes/prism.js +226 -0
- package/src/shapes/rect.js +35 -0
- package/src/shapes/renderable.js +333 -0
- package/src/shapes/ring.js +26 -0
- package/src/shapes/roundrect.js +95 -0
- package/src/shapes/shape.js +117 -0
- package/src/shapes/slice.js +26 -0
- package/src/shapes/sphere.js +314 -0
- package/src/shapes/sphere3d.js +537 -0
- package/src/shapes/square.js +15 -0
- package/src/shapes/star.js +99 -0
- package/src/shapes/svg.js +408 -0
- package/src/shapes/text.js +553 -0
- package/src/shapes/traceable.js +83 -0
- package/src/shapes/transform.js +357 -0
- package/src/shapes/transformable.js +172 -0
- package/src/shapes/triangle.js +26 -0
- package/src/sound/index.js +17 -0
- package/src/sound/sound.js +473 -0
- package/src/sound/synth.analyzer.js +149 -0
- package/src/sound/synth.effects.js +207 -0
- package/src/sound/synth.envelope.js +59 -0
- package/src/sound/synth.js +229 -0
- package/src/sound/synth.musical.js +160 -0
- package/src/sound/synth.noise.js +85 -0
- package/src/sound/synth.oscillators.js +293 -0
- package/src/state/index.js +10 -0
- package/src/state/state-machine.js +371 -0
- package/src/util/camera3d.js +438 -0
- package/src/util/index.js +6 -0
- package/src/util/isometric-camera.js +235 -0
- package/src/util/layout.js +317 -0
- package/src/util/position.js +147 -0
- package/src/util/tasks.js +47 -0
- package/src/util/zindex.js +287 -0
- package/src/webgl/index.js +9 -0
- package/src/webgl/shaders/sphere-shaders.js +994 -0
- package/src/webgl/webgl-renderer.js +388 -0
- package/tde.png +0 -0
- package/test/math/orbital.test.js +61 -0
- package/test/math/tensor.test.js +114 -0
- package/test/particle/emitter.test.js +204 -0
- package/test/particle/particle-system.test.js +310 -0
- package/test/particle/particle.test.js +116 -0
- package/test/particle/updaters.test.js +386 -0
- package/test/setup.js +120 -0
- package/test/shapes/euclidian.test.js +44 -0
- package/test/shapes/geometry.test.js +86 -0
- package/test/shapes/group.test.js +86 -0
- package/test/shapes/rectangle.test.js +64 -0
- package/test/shapes/transform.test.js +379 -0
- package/test/util/camera3d.test.js +428 -0
- package/test/util/scene3d.test.js +352 -0
- package/types/collision.d.ts +249 -0
- package/types/common.d.ts +155 -0
- package/types/game.d.ts +497 -0
- package/types/index.d.ts +309 -0
- package/types/io.d.ts +188 -0
- package/types/logger.d.ts +127 -0
- package/types/math.d.ts +268 -0
- package/types/mixins.d.ts +92 -0
- package/types/motion.d.ts +678 -0
- package/types/painter.d.ts +378 -0
- package/types/shapes.d.ts +864 -0
- package/types/sound.d.ts +672 -0
- package/types/state.d.ts +251 -0
- package/types/util.d.ts +253 -0
- package/vite.config.js +50 -0
- package/vitest.config.js +13 -0
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
import { GameObject } from "../objects/go.js";
|
|
2
|
+
import { Rectangle, TextShape, Group } from "../../shapes/index.js";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Tooltip
|
|
6
|
+
*
|
|
7
|
+
* A GameObject that displays text near the cursor when shown.
|
|
8
|
+
* Supports multiline text with automatic word wrapping.
|
|
9
|
+
*
|
|
10
|
+
* Usage:
|
|
11
|
+
* const tooltip = new Tooltip(game, { ... });
|
|
12
|
+
* game.pipeline.add(tooltip);
|
|
13
|
+
*
|
|
14
|
+
* // On hover:
|
|
15
|
+
* tooltip.show("Hello world", e.x, e.y);
|
|
16
|
+
*
|
|
17
|
+
* // On mouse out:
|
|
18
|
+
* tooltip.hide();
|
|
19
|
+
*/
|
|
20
|
+
export class Tooltip extends GameObject {
|
|
21
|
+
/**
|
|
22
|
+
* @param {Game} game - The main game instance.
|
|
23
|
+
* @param {object} [options={}] - Configuration options.
|
|
24
|
+
* @param {string} [options.font="12px monospace"] - Font for the text.
|
|
25
|
+
* @param {string} [options.textColor="#fff"] - Text color.
|
|
26
|
+
* @param {string} [options.bgColor="rgba(0,0,0,0.85)"] - Background color.
|
|
27
|
+
* @param {string} [options.borderColor="rgba(255,255,255,0.3)"] - Border color.
|
|
28
|
+
* @param {number} [options.padding=8] - Padding inside the tooltip.
|
|
29
|
+
* @param {number} [options.offsetX=15] - X offset from cursor.
|
|
30
|
+
* @param {number} [options.offsetY=15] - Y offset from cursor.
|
|
31
|
+
* @param {number} [options.maxWidth=300] - Maximum width before wrapping.
|
|
32
|
+
* @param {number} [options.lineHeight=1.4] - Line height multiplier.
|
|
33
|
+
*/
|
|
34
|
+
constructor(game, options = {}) {
|
|
35
|
+
super(game, { ...options, zIndex: 9999 }); // Always on top
|
|
36
|
+
|
|
37
|
+
this.font = options.font || "12px monospace";
|
|
38
|
+
this.textColor = options.textColor || "#fff";
|
|
39
|
+
this.bgColor = options.bgColor || "rgba(0,0,0,0.85)";
|
|
40
|
+
this.borderColor = options.borderColor || "rgba(255,255,255,0.3)";
|
|
41
|
+
this.padding = options.padding ?? 8;
|
|
42
|
+
this.offsetX = options.offsetX ?? 15;
|
|
43
|
+
this.offsetY = options.offsetY ?? 15;
|
|
44
|
+
this.maxWidth = options.maxWidth ?? 300;
|
|
45
|
+
this.lineHeightMultiplier = options.lineHeight ?? 1.4;
|
|
46
|
+
|
|
47
|
+
// Current text and wrapped lines
|
|
48
|
+
this._text = "";
|
|
49
|
+
this._lines = [];
|
|
50
|
+
this._visible = false;
|
|
51
|
+
|
|
52
|
+
// Create background shape
|
|
53
|
+
this.bg = new Rectangle({
|
|
54
|
+
width: 100,
|
|
55
|
+
height: 30,
|
|
56
|
+
color: this.bgColor,
|
|
57
|
+
stroke: this.borderColor,
|
|
58
|
+
lineWidth: 1,
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
// Line shapes will be created dynamically
|
|
62
|
+
this.lineShapes = [];
|
|
63
|
+
|
|
64
|
+
this.group = new Group();
|
|
65
|
+
this.group.add(this.bg);
|
|
66
|
+
|
|
67
|
+
// Follow mouse
|
|
68
|
+
this.game.events.on("inputmove", (e) => {
|
|
69
|
+
if (this._visible) {
|
|
70
|
+
this.updatePosition(e.x, e.y);
|
|
71
|
+
}
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Wrap text into multiple lines based on maxWidth.
|
|
77
|
+
* @param {string} text - Text to wrap.
|
|
78
|
+
* @returns {string[]} Array of lines.
|
|
79
|
+
*/
|
|
80
|
+
wrapText(text) {
|
|
81
|
+
const ctx = this.game.ctx;
|
|
82
|
+
ctx.font = this.font;
|
|
83
|
+
|
|
84
|
+
const lines = [];
|
|
85
|
+
// Split by explicit newlines first
|
|
86
|
+
const paragraphs = text.split("\n");
|
|
87
|
+
|
|
88
|
+
for (const paragraph of paragraphs) {
|
|
89
|
+
const words = paragraph.split(" ");
|
|
90
|
+
let currentLine = "";
|
|
91
|
+
|
|
92
|
+
for (const word of words) {
|
|
93
|
+
const testLine = currentLine ? `${currentLine} ${word}` : word;
|
|
94
|
+
const metrics = ctx.measureText(testLine);
|
|
95
|
+
|
|
96
|
+
if (metrics.width > this.maxWidth && currentLine) {
|
|
97
|
+
lines.push(currentLine);
|
|
98
|
+
currentLine = word;
|
|
99
|
+
} else {
|
|
100
|
+
currentLine = testLine;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
if (currentLine) {
|
|
105
|
+
lines.push(currentLine);
|
|
106
|
+
} else if (paragraph === "") {
|
|
107
|
+
lines.push(""); // Preserve empty lines
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
return lines;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Show the tooltip with the given text at the specified position.
|
|
116
|
+
* @param {string} text - Text to display (supports newlines and auto-wrapping).
|
|
117
|
+
* @param {number} [mouseX] - X position (defaults to current mouse).
|
|
118
|
+
* @param {number} [mouseY] - Y position (defaults to current mouse).
|
|
119
|
+
*/
|
|
120
|
+
show(text, mouseX, mouseY) {
|
|
121
|
+
this._text = text;
|
|
122
|
+
this._visible = true;
|
|
123
|
+
|
|
124
|
+
// Wrap text into lines
|
|
125
|
+
this._lines = this.wrapText(text);
|
|
126
|
+
|
|
127
|
+
// Create/update line shapes
|
|
128
|
+
this.updateLineShapes();
|
|
129
|
+
|
|
130
|
+
// Measure text to size background
|
|
131
|
+
this.updateSize();
|
|
132
|
+
|
|
133
|
+
if (mouseX !== undefined && mouseY !== undefined) {
|
|
134
|
+
this.updatePosition(mouseX, mouseY);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Create or update TextShape objects for each line.
|
|
140
|
+
*/
|
|
141
|
+
updateLineShapes() {
|
|
142
|
+
// Remove old line shapes from group
|
|
143
|
+
for (const shape of this.lineShapes) {
|
|
144
|
+
this.group.remove(shape);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// Create new line shapes
|
|
148
|
+
this.lineShapes = this._lines.map(
|
|
149
|
+
(line) =>
|
|
150
|
+
new TextShape(line, {
|
|
151
|
+
font: this.font,
|
|
152
|
+
color: this.textColor,
|
|
153
|
+
align: "left",
|
|
154
|
+
baseline: "top",
|
|
155
|
+
})
|
|
156
|
+
);
|
|
157
|
+
|
|
158
|
+
// Add to group
|
|
159
|
+
for (const shape of this.lineShapes) {
|
|
160
|
+
this.group.add(shape);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Hide the tooltip.
|
|
166
|
+
*/
|
|
167
|
+
hide() {
|
|
168
|
+
this._visible = false;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Update the tooltip position, keeping it on screen.
|
|
173
|
+
* Positions tooltip so its top-left is at cursor + offset.
|
|
174
|
+
* @param {number} mouseX - Mouse X position.
|
|
175
|
+
* @param {number} mouseY - Mouse Y position.
|
|
176
|
+
*/
|
|
177
|
+
updatePosition(mouseX, mouseY) {
|
|
178
|
+
const width = this.bg.width;
|
|
179
|
+
const height = this.bg.height;
|
|
180
|
+
|
|
181
|
+
// Position so top-left corner is at cursor + offset
|
|
182
|
+
// (since tooltip renders centered, add half width/height)
|
|
183
|
+
let x = mouseX + this.offsetX + width / 2;
|
|
184
|
+
let y = mouseY + this.offsetY + height / 2;
|
|
185
|
+
|
|
186
|
+
// Keep tooltip on screen - adjust if going off right edge
|
|
187
|
+
if (x + width / 2 > this.game.width) {
|
|
188
|
+
x = mouseX - this.offsetX - width / 2;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// Adjust if going off bottom edge
|
|
192
|
+
if (y + height / 2 > this.game.height) {
|
|
193
|
+
y = mouseY - this.offsetY - height / 2;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// Adjust if going off left edge
|
|
197
|
+
if (x - width / 2 < 0) {
|
|
198
|
+
x = width / 2 + 5;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// Adjust if going off top edge
|
|
202
|
+
if (y - height / 2 < 0) {
|
|
203
|
+
y = height / 2 + 5;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
this.x = x;
|
|
207
|
+
this.y = y;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* Update the background size based on wrapped text lines.
|
|
212
|
+
*/
|
|
213
|
+
updateSize() {
|
|
214
|
+
const ctx = this.game.ctx;
|
|
215
|
+
ctx.font = this.font;
|
|
216
|
+
|
|
217
|
+
// Find widest line
|
|
218
|
+
let maxLineWidth = 0;
|
|
219
|
+
for (const line of this._lines) {
|
|
220
|
+
const metrics = ctx.measureText(line);
|
|
221
|
+
maxLineWidth = Math.max(maxLineWidth, metrics.width);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
const textWidth = Math.min(maxLineWidth, this.maxWidth);
|
|
225
|
+
const fontSize = parseInt(this.font);
|
|
226
|
+
const lineHeight = fontSize * this.lineHeightMultiplier;
|
|
227
|
+
const textHeight = lineHeight * this._lines.length;
|
|
228
|
+
|
|
229
|
+
this.bg.width = textWidth + this.padding * 2;
|
|
230
|
+
this.bg.height = textHeight + this.padding * 2;
|
|
231
|
+
|
|
232
|
+
// Position each line inside bg
|
|
233
|
+
const startX = -this.bg.width / 2 + this.padding;
|
|
234
|
+
const startY = -this.bg.height / 2 + this.padding;
|
|
235
|
+
|
|
236
|
+
for (let i = 0; i < this.lineShapes.length; i++) {
|
|
237
|
+
this.lineShapes[i].x = startX;
|
|
238
|
+
this.lineShapes[i].y = startY + i * lineHeight;
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* Render the tooltip if visible.
|
|
244
|
+
*/
|
|
245
|
+
draw() {
|
|
246
|
+
if (!this._visible) return;
|
|
247
|
+
this.group.render();
|
|
248
|
+
}
|
|
249
|
+
}
|
package/src/index.js
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export * from "./util";
|
|
2
|
+
export * from "./math";
|
|
3
|
+
export * from "./logger";
|
|
4
|
+
export * from "./painter";
|
|
5
|
+
export * from "./shapes";
|
|
6
|
+
export * from "./io";
|
|
7
|
+
export * from "./game";
|
|
8
|
+
export * from "./motion";
|
|
9
|
+
export * from "./mixins";
|
|
10
|
+
|
|
11
|
+
// Fluent API
|
|
12
|
+
export * from "./fluent";
|
|
13
|
+
export * from "./sound";
|
|
14
|
+
|
|
15
|
+
// Collision detection
|
|
16
|
+
export * from "./collision";
|
|
17
|
+
|
|
18
|
+
// State management
|
|
19
|
+
export * from "./state";
|
|
20
|
+
|
|
21
|
+
// Particle system
|
|
22
|
+
export * from "./particle";
|
|
23
|
+
|
|
24
|
+
// WebGL (optional, for shader effects)
|
|
25
|
+
export * from "./webgl";
|
package/src/io/events.js
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export class EventEmitter {
|
|
2
|
+
constructor() {
|
|
3
|
+
this.listeners = {};
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
on(type, callback) {
|
|
7
|
+
if (!this.listeners[type]) this.listeners[type] = [];
|
|
8
|
+
this.listeners[type].push(callback);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
off(type, callback) {
|
|
12
|
+
if (!this.listeners[type]) return;
|
|
13
|
+
this.listeners[type] = this.listeners[type].filter((cb) => cb !== callback);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
emit(type, payload) {
|
|
17
|
+
if (!this.listeners[type]) return;
|
|
18
|
+
this.listeners[type].forEach((cb) => cb(payload));
|
|
19
|
+
}
|
|
20
|
+
}
|
package/src/io/index.js
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module io
|
|
3
|
+
* @description Input/Output handling system for user interactions and event management.
|
|
4
|
+
*
|
|
5
|
+
* This module provides a comprehensive set of classes for handling user input across
|
|
6
|
+
* different devices and input methods:
|
|
7
|
+
*
|
|
8
|
+
* - {@link EventEmitter}: Core event management system for subscribing to and triggering events
|
|
9
|
+
* - {@link Mouse}: Mouse input tracking and event normalization
|
|
10
|
+
* - {@link Keys}: Keyboard input with logical key mapping and state tracking
|
|
11
|
+
* - {@link Touch}: Touch input for mobile devices
|
|
12
|
+
* - {@link Input}: Unified input system that normalizes mouse and touch events
|
|
13
|
+
*
|
|
14
|
+
* The IO module serves as the intermediary between raw browser events and your game logic,
|
|
15
|
+
* providing consistent, normalized events regardless of input source.
|
|
16
|
+
*
|
|
17
|
+
* @example
|
|
18
|
+
* // Basic usage in a game class
|
|
19
|
+
* import { Game } from './core/game.js';
|
|
20
|
+
* import { Keys, Mouse } from './core/io';
|
|
21
|
+
*
|
|
22
|
+
* class MyGame extends Game {
|
|
23
|
+
* init() {
|
|
24
|
+
* super.init();
|
|
25
|
+
*
|
|
26
|
+
* // Listen for a specific key press
|
|
27
|
+
* this.events.on(Keys.SPACE, () => {
|
|
28
|
+
* this.player.jump();
|
|
29
|
+
* });
|
|
30
|
+
*
|
|
31
|
+
* // Check mouse position in update loop
|
|
32
|
+
* this.events.on("mousemove", () => {
|
|
33
|
+
* this.logger.log(`Mouse at ${Mouse.x}, ${Mouse.y}`);
|
|
34
|
+
* });
|
|
35
|
+
*
|
|
36
|
+
* // Listen for unified input events (works with both mouse and touch)
|
|
37
|
+
* this.events.on("inputdown", () => {
|
|
38
|
+
* this.player.shoot();
|
|
39
|
+
* });
|
|
40
|
+
* }
|
|
41
|
+
*
|
|
42
|
+
* update(dt) {
|
|
43
|
+
* super.update(dt);
|
|
44
|
+
*
|
|
45
|
+
* // Check if a key is currently held down
|
|
46
|
+
* if (Keys.isDown(Keys.RIGHT)) {
|
|
47
|
+
* this.player.moveRight(dt);
|
|
48
|
+
* }
|
|
49
|
+
* }
|
|
50
|
+
* }
|
|
51
|
+
*
|
|
52
|
+
* @example
|
|
53
|
+
* // Creating your own custom event system
|
|
54
|
+
* import { EventEmitter } from './core/io';
|
|
55
|
+
*
|
|
56
|
+
* class WeaponSystem {
|
|
57
|
+
* constructor() {
|
|
58
|
+
* this.events = new EventEmitter();
|
|
59
|
+
* this.ammo = 10;
|
|
60
|
+
* }
|
|
61
|
+
*
|
|
62
|
+
* fire() {
|
|
63
|
+
* if (this.ammo > 0) {
|
|
64
|
+
* this.ammo--;
|
|
65
|
+
* this.events.emit('fired', { ammo: this.ammo });
|
|
66
|
+
*
|
|
67
|
+
* if (this.ammo === 0) {
|
|
68
|
+
* this.events.emit('empty');
|
|
69
|
+
* }
|
|
70
|
+
* }
|
|
71
|
+
* }
|
|
72
|
+
* }
|
|
73
|
+
*
|
|
74
|
+
* const weapon = new WeaponSystem();
|
|
75
|
+
* weapon.events.on('fired', (data) => {
|
|
76
|
+
* this.logger.log(`Fired! Ammo remaining: ${data.ammo}`);
|
|
77
|
+
* });
|
|
78
|
+
* weapon.events.on('empty', () => {
|
|
79
|
+
* this.logger.log('Out of ammo! Reload!');
|
|
80
|
+
* });
|
|
81
|
+
*/
|
|
82
|
+
export {EventEmitter} from "./events.js";
|
|
83
|
+
export {Input} from "./input.js";
|
|
84
|
+
export {Mouse} from "./mouse.js";
|
|
85
|
+
export {Keys} from "./keys.js";
|
|
86
|
+
export {Touch} from "./touch.js";
|
package/src/io/input.js
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
export class Input {
|
|
2
|
+
static init(game) {
|
|
3
|
+
Input.game = game;
|
|
4
|
+
Input.x = 0;
|
|
5
|
+
Input.y = 0;
|
|
6
|
+
Input.down = false;
|
|
7
|
+
game.events.on("mousedown", Input._onDown);
|
|
8
|
+
game.events.on("mouseup", Input._onUp);
|
|
9
|
+
game.events.on("mousemove", Input._onMove);
|
|
10
|
+
game.events.on("touchstart", Input._onTouchStart);
|
|
11
|
+
game.events.on("touchend", Input._onTouchEnd);
|
|
12
|
+
game.events.on("touchmove", Input._onTouchMove);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
static _setPosition(x, y) {
|
|
16
|
+
Input.x = x;
|
|
17
|
+
Input.y = y;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
static _onDown = (e) => {
|
|
21
|
+
Input.down = true;
|
|
22
|
+
Input._setPosition(e.offsetX, e.offsetY);
|
|
23
|
+
Object.defineProperty(e, "x", { value: e.offsetX, configurable: true });
|
|
24
|
+
Object.defineProperty(e, "y", { value: e.offsetY, configurable: true });
|
|
25
|
+
Input.game.events.emit("inputdown", e);
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
static _onUp = (e) => {
|
|
29
|
+
Input.down = false;
|
|
30
|
+
Input._setPosition(e.offsetX, e.offsetY);
|
|
31
|
+
Object.defineProperty(e, "x", { value: e.offsetX, configurable: true });
|
|
32
|
+
Object.defineProperty(e, "y", { value: e.offsetY, configurable: true });
|
|
33
|
+
Input.game.events.emit("inputup", e);
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
static _onMove = (e) => {
|
|
37
|
+
Input._setPosition(e.offsetX, e.offsetY);
|
|
38
|
+
Object.defineProperty(e, "x", { value: e.offsetX, configurable: true });
|
|
39
|
+
Object.defineProperty(e, "y", { value: e.offsetY, configurable: true });
|
|
40
|
+
Input.game.events.emit("inputmove", e);
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
static _onTouchStart = (e) => {
|
|
44
|
+
const touch = e.touches[0];
|
|
45
|
+
const rect = Input.game.canvas.getBoundingClientRect();
|
|
46
|
+
Input.down = true;
|
|
47
|
+
const x = touch.clientX - rect.left;
|
|
48
|
+
const y = touch.clientY - rect.top;
|
|
49
|
+
Input._setPosition(x, y);
|
|
50
|
+
Object.defineProperty(e, "x", { value: x, configurable: true });
|
|
51
|
+
Object.defineProperty(e, "y", { value: y, configurable: true });
|
|
52
|
+
Input.game.events.emit("inputdown", e);
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
static _onTouchEnd = (e) => {
|
|
56
|
+
Input.down = false;
|
|
57
|
+
Input.game.events.emit("inputup", e);
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
static _onTouchMove = (e) => {
|
|
61
|
+
const touch = e.touches[0];
|
|
62
|
+
const rect = Input.game.canvas.getBoundingClientRect();
|
|
63
|
+
const x = touch.clientX - rect.left;
|
|
64
|
+
const y = touch.clientY - rect.top;
|
|
65
|
+
Input._setPosition(x, y);
|
|
66
|
+
Object.defineProperty(e, "x", { value: x, configurable: true });
|
|
67
|
+
Object.defineProperty(e, "y", { value: y, configurable: true });
|
|
68
|
+
Input.game.events.emit("inputmove", e);
|
|
69
|
+
};
|
|
70
|
+
}
|
package/src/io/keys.js
ADDED
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Keys.js
|
|
3
|
+
*
|
|
4
|
+
* Provides an abstraction layer over keyboard input. Instead of dealing with
|
|
5
|
+
* raw "KeyA" / "Space" codes, you can subscribe to logical names like Keys.W,
|
|
6
|
+
* Keys.SPACE, Keys.LEFT, etc.
|
|
7
|
+
*
|
|
8
|
+
* Example usage:
|
|
9
|
+
* // Initialization (usually in your Game constructor):
|
|
10
|
+
* Keys.init(this);
|
|
11
|
+
*
|
|
12
|
+
* // Listen for a press:
|
|
13
|
+
* this.events.on(Keys.W, (evt) => {
|
|
14
|
+
* this.logger.log("W pressed!", evt);
|
|
15
|
+
* });
|
|
16
|
+
*
|
|
17
|
+
* // Listen for release:
|
|
18
|
+
* this.events.on(Keys.W + "_up", (evt) => {
|
|
19
|
+
* this.logger.log("W released!");
|
|
20
|
+
* });
|
|
21
|
+
*
|
|
22
|
+
* // Check if pressed in your update(dt):
|
|
23
|
+
* if (Keys.isDown(Keys.SPACE)) {
|
|
24
|
+
* // Spacebar is currently held
|
|
25
|
+
* }
|
|
26
|
+
*/
|
|
27
|
+
export class Keys {
|
|
28
|
+
// Named constants for common game keys you might use:
|
|
29
|
+
static W = "W";
|
|
30
|
+
static A = "A";
|
|
31
|
+
static S = "S";
|
|
32
|
+
static D = "D";
|
|
33
|
+
static Q = "Q";
|
|
34
|
+
static E = "E";
|
|
35
|
+
static UP = "UP";
|
|
36
|
+
static DOWN = "DOWN";
|
|
37
|
+
static LEFT = "LEFT";
|
|
38
|
+
static RIGHT = "RIGHT";
|
|
39
|
+
static SPACE = "SPACE";
|
|
40
|
+
static SHIFT = "SHIFT";
|
|
41
|
+
static ENTER = "ENTER";
|
|
42
|
+
static ESC = "ESC";
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Mapping from DOM event.code to one of the above Keys.* constants.
|
|
46
|
+
* Customize this list as needed for your game.
|
|
47
|
+
*/
|
|
48
|
+
static _codeMap = {
|
|
49
|
+
// WASD + QE
|
|
50
|
+
KeyW: Keys.W,
|
|
51
|
+
KeyA: Keys.A,
|
|
52
|
+
KeyS: Keys.S,
|
|
53
|
+
KeyD: Keys.D,
|
|
54
|
+
KeyQ: Keys.Q,
|
|
55
|
+
KeyE: Keys.E,
|
|
56
|
+
|
|
57
|
+
// Arrows
|
|
58
|
+
ArrowUp: Keys.UP,
|
|
59
|
+
ArrowDown: Keys.DOWN,
|
|
60
|
+
ArrowLeft: Keys.LEFT,
|
|
61
|
+
ArrowRight: Keys.RIGHT,
|
|
62
|
+
|
|
63
|
+
// Space, Shift, Enter, Esc
|
|
64
|
+
Space: Keys.SPACE,
|
|
65
|
+
ShiftLeft: Keys.SHIFT,
|
|
66
|
+
ShiftRight: Keys.SHIFT,
|
|
67
|
+
Enter: Keys.ENTER,
|
|
68
|
+
NumpadEnter: Keys.ENTER,
|
|
69
|
+
Escape: Keys.ESC,
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* A Set of logical key names (Keys.W, Keys.SPACE, etc.) that are currently held down.
|
|
74
|
+
* @type {Set<string>}
|
|
75
|
+
* @private
|
|
76
|
+
*/
|
|
77
|
+
static _down = new Set();
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* A reference to the main game instance. We store it so we can emit events
|
|
81
|
+
* via game.events whenever a key is pressed or released.
|
|
82
|
+
* @type {Game}
|
|
83
|
+
* @private
|
|
84
|
+
*/
|
|
85
|
+
static game = null;
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Initialize keyboard event handling. This attaches global listeners on the
|
|
89
|
+
* window so that whenever a key is pressed or released, we can map it to
|
|
90
|
+
* one of our Keys.* constants and emit the corresponding events on the game's
|
|
91
|
+
* EventEmitter.
|
|
92
|
+
*
|
|
93
|
+
* @param {Game} game - Your main Game instance, which has a central event emitter.
|
|
94
|
+
*/
|
|
95
|
+
static init(game) {
|
|
96
|
+
Keys.game = game;
|
|
97
|
+
|
|
98
|
+
// Attach global keydown/keyup listeners
|
|
99
|
+
window.addEventListener("keydown", Keys._onKeyDown);
|
|
100
|
+
window.addEventListener("keyup", Keys._onKeyUp);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Returns true if the specified logical key (e.g. Keys.W) is currently held down.
|
|
105
|
+
*
|
|
106
|
+
* @param {string} logicalKey - One of the Keys.* constants.
|
|
107
|
+
* @returns {boolean} - True if that key is in the "down" set, false otherwise.
|
|
108
|
+
*/
|
|
109
|
+
static isDown(logicalKey) {
|
|
110
|
+
return Keys._down.has(logicalKey);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Internal method called whenever a key is pressed. We look up which
|
|
115
|
+
* logical key constant it corresponds to, add it to our _down set,
|
|
116
|
+
* and emit an event on the game (e.g. game.events.emit(Keys.W, e)).
|
|
117
|
+
*
|
|
118
|
+
* @param {KeyboardEvent} e - The raw DOM event.
|
|
119
|
+
* @private
|
|
120
|
+
*/
|
|
121
|
+
static _onKeyDown(e) {
|
|
122
|
+
const mappedKey = Keys._codeMap[e.code];
|
|
123
|
+
if (mappedKey) {
|
|
124
|
+
if (!Keys._down.has(mappedKey)) {
|
|
125
|
+
// Only emit this event the moment the key transitions from 'up' to 'down'
|
|
126
|
+
Keys._down.add(mappedKey);
|
|
127
|
+
Keys.game.events.emit(mappedKey, e);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
// Dispatch the raw event as well, in case anyone cares about that.
|
|
131
|
+
Keys.game.events.emit(e.type, e);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Internal method called whenever a key is released. If it was one of our
|
|
136
|
+
* mapped keys, remove it from the _down set and emit an "_up" event.
|
|
137
|
+
*
|
|
138
|
+
* @param {KeyboardEvent} e - The raw DOM event.
|
|
139
|
+
* @private
|
|
140
|
+
*/
|
|
141
|
+
static _onKeyUp(e) {
|
|
142
|
+
const mappedKey = Keys._codeMap[e.code];
|
|
143
|
+
if (mappedKey) {
|
|
144
|
+
if (Keys._down.has(mappedKey)) {
|
|
145
|
+
Keys._down.delete(mappedKey);
|
|
146
|
+
Keys.game.events.emit(mappedKey + "_up", e);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
// Dispatch the raw event as well, in case anyone cares about that.
|
|
150
|
+
Keys.game.events.emit(e.type, e);
|
|
151
|
+
}
|
|
152
|
+
}
|
package/src/io/mouse.js
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
export class Mouse {
|
|
2
|
+
static init(game) {
|
|
3
|
+
Mouse.game = game;
|
|
4
|
+
Mouse.canvas = game.canvas;
|
|
5
|
+
Mouse.x = 0;
|
|
6
|
+
Mouse.y = 0;
|
|
7
|
+
Mouse.leftDown = false;
|
|
8
|
+
Mouse.middleDown = false;
|
|
9
|
+
Mouse.rightDown = false;
|
|
10
|
+
|
|
11
|
+
Mouse.canvas.addEventListener("mousemove", Mouse._onMove);
|
|
12
|
+
Mouse.canvas.addEventListener("mousedown", Mouse._onDown);
|
|
13
|
+
Mouse.canvas.addEventListener("mouseup", Mouse._onUp);
|
|
14
|
+
Mouse.canvas.addEventListener("click", Mouse._onClick);
|
|
15
|
+
Mouse.canvas.addEventListener("wheel", Mouse._onWheel);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
static _updatePosition(e) {
|
|
19
|
+
const rect = Mouse.canvas.getBoundingClientRect();
|
|
20
|
+
Mouse.x = e.clientX - rect.left;
|
|
21
|
+
Mouse.y = e.clientY - rect.top;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
static _onMove = (e) => {
|
|
25
|
+
Mouse._updatePosition(e);
|
|
26
|
+
Mouse.game.events.emit("mousemove", e);
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
static _onDown = (e) => {
|
|
30
|
+
Mouse._updatePosition(e);
|
|
31
|
+
if (e.button === 0) Mouse.leftDown = true;
|
|
32
|
+
if (e.button === 1) Mouse.middleDown = true;
|
|
33
|
+
if (e.button === 2) Mouse.rightDown = true;
|
|
34
|
+
Mouse.game.events.emit("mousedown", e);
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
static _onUp = (e) => {
|
|
38
|
+
Mouse._updatePosition(e);
|
|
39
|
+
if (e.button === 0) Mouse.leftDown = false;
|
|
40
|
+
if (e.button === 1) Mouse.middleDown = false;
|
|
41
|
+
if (e.button === 2) Mouse.rightDown = false;
|
|
42
|
+
Mouse.game.events.emit("mouseup", e);
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
static _onClick = (e) => {
|
|
46
|
+
Mouse._updatePosition(e);
|
|
47
|
+
// Emit enhanced event with canvas-relative coordinates
|
|
48
|
+
// Note: e is a MouseEvent, add canvas-relative x/y directly
|
|
49
|
+
e.canvasX = Mouse.x;
|
|
50
|
+
e.canvasY = Mouse.y;
|
|
51
|
+
// Also set x/y for convenience (matches expected fluent API)
|
|
52
|
+
Object.defineProperty(e, 'x', { value: Mouse.x, writable: false });
|
|
53
|
+
Object.defineProperty(e, 'y', { value: Mouse.y, writable: false });
|
|
54
|
+
Mouse.game.events.emit("click", e);
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
static _onWheel = (e) => {
|
|
58
|
+
Mouse._updatePosition(e);
|
|
59
|
+
Mouse.game.events.emit("wheel", e);
|
|
60
|
+
};
|
|
61
|
+
}
|
package/src/io/touch.js
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
export class Touch {
|
|
2
|
+
static init(game) {
|
|
3
|
+
Touch.game = game;
|
|
4
|
+
Touch.canvas = game.canvas;
|
|
5
|
+
Touch.x = 0;
|
|
6
|
+
Touch.y = 0;
|
|
7
|
+
Touch.active = false;
|
|
8
|
+
|
|
9
|
+
Touch.canvas.addEventListener("touchstart", Touch._onStart);
|
|
10
|
+
Touch.canvas.addEventListener("touchend", Touch._onEnd);
|
|
11
|
+
Touch.canvas.addEventListener("touchmove", Touch._onMove);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
static _updatePosition(touch) {
|
|
15
|
+
const rect = Touch.canvas.getBoundingClientRect();
|
|
16
|
+
Touch.x = touch.clientX - rect.left;
|
|
17
|
+
Touch.y = touch.clientY - rect.top;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
static _onStart = (e) => {
|
|
21
|
+
if (e.touches.length > 0) {
|
|
22
|
+
Touch.active = true;
|
|
23
|
+
Touch._updatePosition(e.touches[0]);
|
|
24
|
+
Touch.game.events.emit("touchstart", e);
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
static _onEnd = (e) => {
|
|
29
|
+
Touch.active = false;
|
|
30
|
+
Touch.game.events.emit("touchend", e);
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
static _onMove = (e) => {
|
|
34
|
+
if (e.touches.length > 0) {
|
|
35
|
+
Touch._updatePosition(e.touches[0]);
|
|
36
|
+
Touch.game.events.emit("touchmove", e);
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
}
|