@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.
Files changed (349) hide show
  1. package/.github/workflows/release.yaml +70 -0
  2. package/.jshintrc +4 -0
  3. package/.vscode/settings.json +22 -0
  4. package/CLAUDE.md +310 -0
  5. package/blackhole.jpg +0 -0
  6. package/demo.png +0 -0
  7. package/demos/CNAME +1 -0
  8. package/demos/animations.html +31 -0
  9. package/demos/basic.html +38 -0
  10. package/demos/baskara.html +31 -0
  11. package/demos/bezier.html +35 -0
  12. package/demos/beziersignature.html +29 -0
  13. package/demos/blackhole.html +28 -0
  14. package/demos/blob.html +35 -0
  15. package/demos/demos.css +289 -0
  16. package/demos/easing.html +28 -0
  17. package/demos/events.html +195 -0
  18. package/demos/fluent.html +647 -0
  19. package/demos/fractals.html +36 -0
  20. package/demos/genart.html +26 -0
  21. package/demos/gendream.html +26 -0
  22. package/demos/group.html +36 -0
  23. package/demos/home.html +587 -0
  24. package/demos/index.html +364 -0
  25. package/demos/isometric.html +34 -0
  26. package/demos/js/animations.js +452 -0
  27. package/demos/js/basic.js +204 -0
  28. package/demos/js/baskara.js +751 -0
  29. package/demos/js/bezier.js +692 -0
  30. package/demos/js/beziersignature.js +241 -0
  31. package/demos/js/blackhole/accretiondisk.obj.js +379 -0
  32. package/demos/js/blackhole/blackhole.obj.js +318 -0
  33. package/demos/js/blackhole/index.js +409 -0
  34. package/demos/js/blackhole/particle.js +56 -0
  35. package/demos/js/blackhole/starfield.obj.js +218 -0
  36. package/demos/js/blob.js +2263 -0
  37. package/demos/js/easing.js +477 -0
  38. package/demos/js/fluent.js +183 -0
  39. package/demos/js/fractals.js +931 -0
  40. package/demos/js/fractalworker.js +93 -0
  41. package/demos/js/genart.js +268 -0
  42. package/demos/js/gendream.js +209 -0
  43. package/demos/js/group.js +140 -0
  44. package/demos/js/info-toggle.js +25 -0
  45. package/demos/js/isometric.js +863 -0
  46. package/demos/js/kerr.js +1556 -0
  47. package/demos/js/lavalamp.js +590 -0
  48. package/demos/js/layout.js +354 -0
  49. package/demos/js/mondrian.js +285 -0
  50. package/demos/js/opacity.js +275 -0
  51. package/demos/js/painter.js +484 -0
  52. package/demos/js/particles-showcase.js +514 -0
  53. package/demos/js/particles.js +299 -0
  54. package/demos/js/patterns.js +397 -0
  55. package/demos/js/penrose/artifact.js +69 -0
  56. package/demos/js/penrose/blackhole.js +121 -0
  57. package/demos/js/penrose/constants.js +73 -0
  58. package/demos/js/penrose/game.js +943 -0
  59. package/demos/js/penrose/lore.js +278 -0
  60. package/demos/js/penrose/penrosescene.js +892 -0
  61. package/demos/js/penrose/ship.js +216 -0
  62. package/demos/js/penrose/sounds.js +211 -0
  63. package/demos/js/penrose/voidparticle.js +55 -0
  64. package/demos/js/penrose/voidscene.js +258 -0
  65. package/demos/js/penrose/voidship.js +144 -0
  66. package/demos/js/penrose/wormhole.js +46 -0
  67. package/demos/js/pipeline.js +555 -0
  68. package/demos/js/scene.js +304 -0
  69. package/demos/js/scenes.js +320 -0
  70. package/demos/js/schrodinger.js +410 -0
  71. package/demos/js/schwarzschild.js +1023 -0
  72. package/demos/js/shapes.js +628 -0
  73. package/demos/js/space/alien.js +171 -0
  74. package/demos/js/space/boom.js +98 -0
  75. package/demos/js/space/boss.js +353 -0
  76. package/demos/js/space/buff.js +73 -0
  77. package/demos/js/space/bullet.js +102 -0
  78. package/demos/js/space/constants.js +85 -0
  79. package/demos/js/space/game.js +1884 -0
  80. package/demos/js/space/hud.js +112 -0
  81. package/demos/js/space/laserbeam.js +179 -0
  82. package/demos/js/space/lightning.js +277 -0
  83. package/demos/js/space/minion.js +192 -0
  84. package/demos/js/space/missile.js +212 -0
  85. package/demos/js/space/player.js +430 -0
  86. package/demos/js/space/powerup.js +90 -0
  87. package/demos/js/space/starfield.js +58 -0
  88. package/demos/js/space/starpower.js +90 -0
  89. package/demos/js/spacetime.js +559 -0
  90. package/demos/js/svgtween.js +204 -0
  91. package/demos/js/tde/accretiondisk.js +418 -0
  92. package/demos/js/tde/blackhole.js +219 -0
  93. package/demos/js/tde/blackholescene.js +209 -0
  94. package/demos/js/tde/config.js +59 -0
  95. package/demos/js/tde/index.js +695 -0
  96. package/demos/js/tde/jets.js +290 -0
  97. package/demos/js/tde/lensedstarfield.js +147 -0
  98. package/demos/js/tde/tdestar.js +317 -0
  99. package/demos/js/tde/tidalstream.js +356 -0
  100. package/demos/js/tde_old/blackhole.obj.js +354 -0
  101. package/demos/js/tde_old/debris.obj.js +791 -0
  102. package/demos/js/tde_old/flare.obj.js +239 -0
  103. package/demos/js/tde_old/index.js +448 -0
  104. package/demos/js/tde_old/star.obj.js +812 -0
  105. package/demos/js/tiles.js +312 -0
  106. package/demos/js/tweendemo.js +79 -0
  107. package/demos/js/visibility.js +102 -0
  108. package/demos/kerr.html +28 -0
  109. package/demos/lavalamp.html +27 -0
  110. package/demos/layouts.html +37 -0
  111. package/demos/logo.svg +4 -0
  112. package/demos/loop.html +84 -0
  113. package/demos/mondrian.html +32 -0
  114. package/demos/og_image.png +0 -0
  115. package/demos/opacity.html +36 -0
  116. package/demos/painter.html +39 -0
  117. package/demos/particles-showcase.html +28 -0
  118. package/demos/particles.html +24 -0
  119. package/demos/patterns.html +33 -0
  120. package/demos/penrose-game.html +31 -0
  121. package/demos/pipeline.html +737 -0
  122. package/demos/scene.html +33 -0
  123. package/demos/scenes.html +96 -0
  124. package/demos/schrodinger.html +27 -0
  125. package/demos/schwarzschild.html +27 -0
  126. package/demos/shapes.html +16 -0
  127. package/demos/space.html +85 -0
  128. package/demos/spacetime.html +27 -0
  129. package/demos/svgtween.html +29 -0
  130. package/demos/tde.html +28 -0
  131. package/demos/tiles.html +28 -0
  132. package/demos/transforms.html +400 -0
  133. package/demos/tween.html +45 -0
  134. package/demos/visibility.html +33 -0
  135. package/disk_example.png +0 -0
  136. package/docs/README.md +222 -0
  137. package/docs/concepts/architecture-overview.md +204 -0
  138. package/docs/concepts/lifecycle.md +255 -0
  139. package/docs/concepts/rendering-pipeline.md +279 -0
  140. package/docs/concepts/tde-zorder.md +106 -0
  141. package/docs/concepts/two-layer-architecture.md +229 -0
  142. package/docs/getting-started/first-game.md +354 -0
  143. package/docs/getting-started/hello-world.md +269 -0
  144. package/docs/getting-started/installation.md +157 -0
  145. package/docs/modules/collision/README.md +453 -0
  146. package/docs/modules/fluent/README.md +1075 -0
  147. package/docs/modules/game/README.md +303 -0
  148. package/docs/modules/isometric-camera.md +210 -0
  149. package/docs/modules/isometric.md +275 -0
  150. package/docs/modules/painter/README.md +328 -0
  151. package/docs/modules/particle/README.md +559 -0
  152. package/docs/modules/shapes/README.md +221 -0
  153. package/docs/modules/shapes/base/euclidian.md +123 -0
  154. package/docs/modules/shapes/base/geometry2d.md +204 -0
  155. package/docs/modules/shapes/base/renderable.md +215 -0
  156. package/docs/modules/shapes/base/shape.md +262 -0
  157. package/docs/modules/shapes/base/transformable.md +243 -0
  158. package/docs/modules/shapes/hierarchy.md +218 -0
  159. package/docs/modules/state/README.md +577 -0
  160. package/docs/modules/util/README.md +99 -0
  161. package/docs/modules/util/camera3d.md +412 -0
  162. package/docs/modules/util/scene3d.md +395 -0
  163. package/index.html +17 -0
  164. package/jsdoc.json +50 -0
  165. package/package.json +55 -0
  166. package/readme.md +599 -0
  167. package/scripts/build-demo.js +69 -0
  168. package/scripts/bundle4llm.js +276 -0
  169. package/scripts/clearconsole.js +48 -0
  170. package/src/collision/collision-system.js +332 -0
  171. package/src/collision/collision.js +303 -0
  172. package/src/collision/index.js +10 -0
  173. package/src/fluent/fluent-game.js +430 -0
  174. package/src/fluent/fluent-go.js +1060 -0
  175. package/src/fluent/fluent-layer.js +152 -0
  176. package/src/fluent/fluent-scene.js +291 -0
  177. package/src/fluent/index.js +98 -0
  178. package/src/fluent/sketch.js +380 -0
  179. package/src/game/game.js +467 -0
  180. package/src/game/index.js +49 -0
  181. package/src/game/objects/go.js +220 -0
  182. package/src/game/objects/imagego.js +30 -0
  183. package/src/game/objects/index.js +54 -0
  184. package/src/game/objects/isometric-scene.js +260 -0
  185. package/src/game/objects/layoutscene.js +549 -0
  186. package/src/game/objects/scene.js +175 -0
  187. package/src/game/objects/scene3d.js +118 -0
  188. package/src/game/objects/text.js +221 -0
  189. package/src/game/objects/wrapper.js +232 -0
  190. package/src/game/pipeline.js +243 -0
  191. package/src/game/ui/button.js +396 -0
  192. package/src/game/ui/cursor.js +93 -0
  193. package/src/game/ui/fps.js +91 -0
  194. package/src/game/ui/index.js +5 -0
  195. package/src/game/ui/togglebutton.js +93 -0
  196. package/src/game/ui/tooltip.js +249 -0
  197. package/src/index.js +25 -0
  198. package/src/io/events.js +20 -0
  199. package/src/io/index.js +86 -0
  200. package/src/io/input.js +70 -0
  201. package/src/io/keys.js +152 -0
  202. package/src/io/mouse.js +61 -0
  203. package/src/io/touch.js +39 -0
  204. package/src/logger/debugtab.js +138 -0
  205. package/src/logger/index.js +3 -0
  206. package/src/logger/loggable.js +47 -0
  207. package/src/logger/logger.js +113 -0
  208. package/src/math/complex.js +37 -0
  209. package/src/math/constants.js +1 -0
  210. package/src/math/fractal.js +1271 -0
  211. package/src/math/gr.js +201 -0
  212. package/src/math/heat.js +202 -0
  213. package/src/math/index.js +12 -0
  214. package/src/math/noise.js +433 -0
  215. package/src/math/orbital.js +191 -0
  216. package/src/math/patterns.js +1339 -0
  217. package/src/math/penrose.js +259 -0
  218. package/src/math/quantum.js +115 -0
  219. package/src/math/random.js +195 -0
  220. package/src/math/tensor.js +1009 -0
  221. package/src/mixins/anchor.js +131 -0
  222. package/src/mixins/draggable.js +72 -0
  223. package/src/mixins/index.js +2 -0
  224. package/src/motion/bezier.js +132 -0
  225. package/src/motion/bounce.js +58 -0
  226. package/src/motion/easing.js +349 -0
  227. package/src/motion/float.js +130 -0
  228. package/src/motion/follow.js +125 -0
  229. package/src/motion/hop.js +52 -0
  230. package/src/motion/index.js +82 -0
  231. package/src/motion/motion.js +1124 -0
  232. package/src/motion/orbit.js +49 -0
  233. package/src/motion/oscillate.js +39 -0
  234. package/src/motion/parabolic.js +141 -0
  235. package/src/motion/patrol.js +147 -0
  236. package/src/motion/pendulum.js +48 -0
  237. package/src/motion/pulse.js +88 -0
  238. package/src/motion/shake.js +83 -0
  239. package/src/motion/spiral.js +144 -0
  240. package/src/motion/spring.js +150 -0
  241. package/src/motion/swing.js +47 -0
  242. package/src/motion/tween.js +92 -0
  243. package/src/motion/tweenetik.js +139 -0
  244. package/src/motion/waypoint.js +210 -0
  245. package/src/painter/index.js +8 -0
  246. package/src/painter/painter.colors.js +331 -0
  247. package/src/painter/painter.effects.js +230 -0
  248. package/src/painter/painter.img.js +229 -0
  249. package/src/painter/painter.js +295 -0
  250. package/src/painter/painter.lines.js +189 -0
  251. package/src/painter/painter.opacity.js +41 -0
  252. package/src/painter/painter.shapes.js +277 -0
  253. package/src/painter/painter.text.js +273 -0
  254. package/src/particle/emitter.js +124 -0
  255. package/src/particle/index.js +11 -0
  256. package/src/particle/particle-system.js +322 -0
  257. package/src/particle/particle.js +71 -0
  258. package/src/particle/updaters.js +170 -0
  259. package/src/shapes/arc.js +43 -0
  260. package/src/shapes/arrow.js +33 -0
  261. package/src/shapes/bezier.js +42 -0
  262. package/src/shapes/circle.js +62 -0
  263. package/src/shapes/clouds.js +56 -0
  264. package/src/shapes/cone.js +219 -0
  265. package/src/shapes/cross.js +70 -0
  266. package/src/shapes/cube.js +244 -0
  267. package/src/shapes/cylinder.js +254 -0
  268. package/src/shapes/diamond.js +48 -0
  269. package/src/shapes/euclidian.js +111 -0
  270. package/src/shapes/figure.js +115 -0
  271. package/src/shapes/geometry.js +220 -0
  272. package/src/shapes/group.js +375 -0
  273. package/src/shapes/heart.js +42 -0
  274. package/src/shapes/hexagon.js +26 -0
  275. package/src/shapes/image.js +192 -0
  276. package/src/shapes/index.js +111 -0
  277. package/src/shapes/line.js +29 -0
  278. package/src/shapes/pattern.js +90 -0
  279. package/src/shapes/pin.js +44 -0
  280. package/src/shapes/poly.js +31 -0
  281. package/src/shapes/prism.js +226 -0
  282. package/src/shapes/rect.js +35 -0
  283. package/src/shapes/renderable.js +333 -0
  284. package/src/shapes/ring.js +26 -0
  285. package/src/shapes/roundrect.js +95 -0
  286. package/src/shapes/shape.js +117 -0
  287. package/src/shapes/slice.js +26 -0
  288. package/src/shapes/sphere.js +314 -0
  289. package/src/shapes/sphere3d.js +537 -0
  290. package/src/shapes/square.js +15 -0
  291. package/src/shapes/star.js +99 -0
  292. package/src/shapes/svg.js +408 -0
  293. package/src/shapes/text.js +553 -0
  294. package/src/shapes/traceable.js +83 -0
  295. package/src/shapes/transform.js +357 -0
  296. package/src/shapes/transformable.js +172 -0
  297. package/src/shapes/triangle.js +26 -0
  298. package/src/sound/index.js +17 -0
  299. package/src/sound/sound.js +473 -0
  300. package/src/sound/synth.analyzer.js +149 -0
  301. package/src/sound/synth.effects.js +207 -0
  302. package/src/sound/synth.envelope.js +59 -0
  303. package/src/sound/synth.js +229 -0
  304. package/src/sound/synth.musical.js +160 -0
  305. package/src/sound/synth.noise.js +85 -0
  306. package/src/sound/synth.oscillators.js +293 -0
  307. package/src/state/index.js +10 -0
  308. package/src/state/state-machine.js +371 -0
  309. package/src/util/camera3d.js +438 -0
  310. package/src/util/index.js +6 -0
  311. package/src/util/isometric-camera.js +235 -0
  312. package/src/util/layout.js +317 -0
  313. package/src/util/position.js +147 -0
  314. package/src/util/tasks.js +47 -0
  315. package/src/util/zindex.js +287 -0
  316. package/src/webgl/index.js +9 -0
  317. package/src/webgl/shaders/sphere-shaders.js +994 -0
  318. package/src/webgl/webgl-renderer.js +388 -0
  319. package/tde.png +0 -0
  320. package/test/math/orbital.test.js +61 -0
  321. package/test/math/tensor.test.js +114 -0
  322. package/test/particle/emitter.test.js +204 -0
  323. package/test/particle/particle-system.test.js +310 -0
  324. package/test/particle/particle.test.js +116 -0
  325. package/test/particle/updaters.test.js +386 -0
  326. package/test/setup.js +120 -0
  327. package/test/shapes/euclidian.test.js +44 -0
  328. package/test/shapes/geometry.test.js +86 -0
  329. package/test/shapes/group.test.js +86 -0
  330. package/test/shapes/rectangle.test.js +64 -0
  331. package/test/shapes/transform.test.js +379 -0
  332. package/test/util/camera3d.test.js +428 -0
  333. package/test/util/scene3d.test.js +352 -0
  334. package/types/collision.d.ts +249 -0
  335. package/types/common.d.ts +155 -0
  336. package/types/game.d.ts +497 -0
  337. package/types/index.d.ts +309 -0
  338. package/types/io.d.ts +188 -0
  339. package/types/logger.d.ts +127 -0
  340. package/types/math.d.ts +268 -0
  341. package/types/mixins.d.ts +92 -0
  342. package/types/motion.d.ts +678 -0
  343. package/types/painter.d.ts +378 -0
  344. package/types/shapes.d.ts +864 -0
  345. package/types/sound.d.ts +672 -0
  346. package/types/state.d.ts +251 -0
  347. package/types/util.d.ts +253 -0
  348. package/vite.config.js +50 -0
  349. package/vitest.config.js +13 -0
@@ -0,0 +1,396 @@
1
+ /***************************************************************
2
+ * Button.js
3
+ *
4
+ * A basic UI Button GameObject that combines a Rectangle shape
5
+ * and a Text label using a Group. It supports hover and press
6
+ * states, and fires an optional onClick callback when clicked.
7
+ ***************************************************************/
8
+
9
+ import { Group, Rectangle, TextShape, Shape } from "../../shapes";
10
+ import { GameObject } from "../objects/go";
11
+
12
+ /**
13
+ * Button - A clickable UI element as a GameObject.
14
+ *
15
+ * The Button uses:
16
+ * - A background Rectangle (or a custom shape if supplied).
17
+ * - A TextShape (label).
18
+ * - A Group to position both.
19
+ *
20
+ * The button responds to pointer events and has states:
21
+ * "default", "hover", and "pressed". An optional onClick callback
22
+ * is fired when the user completes a press on the button.
23
+ *
24
+ * Example usage:
25
+ * ```js
26
+ * const btn = new Button(game, {
27
+ * x: 200,
28
+ * y: 100,
29
+ * width: 150,
30
+ * height: 50,
31
+ * text: "Click Me",
32
+ * onClick: () => this.logger.log("Button pressed!")
33
+ * });
34
+ * game.pipeline.add(btn);
35
+ * ```
36
+ */
37
+ export class Button extends GameObject {
38
+ /**
39
+ * Create a Button instance.
40
+ * @param {Game} game - The main game instance.
41
+ * @param {object} [options={}] - Configuration for the Button.
42
+ * @param {number} [options.x=0] - X-position of the Button (center).
43
+ * @param {number} [options.y=0] - Y-position of the Button (center).
44
+ * @param {number} [options.width=120] - Width of the Button.
45
+ * @param {number} [options.height=40] - Height of the Button.
46
+ * @param {string} [options.text="Button"] - Label text for the Button.
47
+ * @param {string} [options.font="14px monospace"] - Font for the text.
48
+ * @param {string} [options.textColor="#000"] - Text color for the Button.
49
+ * @param {string} [options.textAlign="center"] - Alignment of the text.
50
+ * @param {string} [options.textBaseline="middle"] - Baseline of the text.
51
+ * @param {Rectangle|Shape} [options.shape=null] - Custom shape for the Button background. Defaults to a Rectangle.
52
+ * @param {TextShape} [options.label=null] - Custom text shape. Defaults to a center-aligned TextShape.
53
+ * @param {Function} [options.onClick=null] - Callback to invoke upon button click.
54
+ * @param {Function} [options.onHover=null] - Callback to invoke upon button hover.
55
+ * @param {Function} [options.onPressed=null] - Callback to invoke upon button pressed.
56
+ * @param {Function} [options.onRelease=null] - Callback to invoke upon button release.
57
+ * @param {string} [options.anchor] - Optional anchor for positioning (e.g. "top-left").
58
+ * @param {number} [options.padding] - Extra padding if using anchor.
59
+ * @param {string} [options.colorDefaultBg="#eee"] - Background color in default state.
60
+ * @param {string} [options.colorDefaultStroke="#999"] - Stroke color in default state.
61
+ * @param {string} [options.colorDefaultText="#333"] - Text color in default state.
62
+ * @param {string} [options.colorHoverBg="#222"] - Background color in hover state.
63
+ * @param {string} [options.colorHoverStroke="#16F529"] - Stroke color in hover state.
64
+ * @param {string} [options.colorHoverText="#16F529"] - Text color in hover state.
65
+ * @param {string} [options.colorPressedBg="#111"] - Background color in pressed state.
66
+ * @param {string} [options.colorPressedStroke="#00aaff"] - Stroke color in pressed state.
67
+ * @param {string} [options.colorPressedText="#00aaff"] - Text color in pressed state.
68
+ * @param {...any} rest - Additional properties passed to the superclass.
69
+ */
70
+ constructor(game, options = {}) {
71
+ // Pass options to the GameObject constructor
72
+ super(game, options);
73
+
74
+ // Extract button-specific config
75
+ const {
76
+ x = 0,
77
+ y = 0,
78
+ width = 120,
79
+ height = 40,
80
+ text = "Button",
81
+ font = "14px monospace",
82
+ textColor = "#000",
83
+ textAlign = "center",
84
+ textBaseline = "middle",
85
+ shape = null,
86
+ label = null,
87
+ onClick = null,
88
+ onHover = null,
89
+ onPressed = null,
90
+ onRelease = null,
91
+ padding = 10,
92
+ colorDefaultBg = "#eee",
93
+ colorDefaultStroke = "#999",
94
+ colorDefaultText = "#333",
95
+ colorHoverBg = "#222",
96
+ colorHoverStroke = "#16F529",
97
+ colorHoverText = "#16F529",
98
+ colorPressedBg = "#111",
99
+ colorPressedStroke = "#00aaff",
100
+ colorPressedText = "#00aaff",
101
+ } = options;
102
+
103
+ // Basic position and sizing
104
+ this.x = x;
105
+ this.y = y;
106
+ this.width = width;
107
+ this.height = height;
108
+ this.padding = padding;
109
+ this.textAlign = textAlign;
110
+ this.textBaseline = textBaseline;
111
+
112
+ // Initialize the button components
113
+ this.initColorScheme({
114
+ colorDefaultBg,
115
+ colorDefaultStroke,
116
+ colorDefaultText,
117
+ colorHoverBg,
118
+ colorHoverStroke,
119
+ colorHoverText,
120
+ colorPressedBg,
121
+ colorPressedStroke,
122
+ colorPressedText
123
+ });
124
+
125
+ this.initBackground(shape);
126
+ this.initLabel(text, font, textColor, label);
127
+ this.initGroup();
128
+ this.initEvents(onClick, onHover, onPressed, onRelease);
129
+
130
+ // Initialize to default state
131
+ this.setState("default");
132
+ }
133
+
134
+ /**
135
+ * Initialize the color scheme for different button states
136
+ * @param {object} colors - Configuration for button colors in different states
137
+ * @private
138
+ */
139
+ initColorScheme(colors) {
140
+ this.colors = {
141
+ default: {
142
+ bg: colors.colorDefaultBg,
143
+ stroke: colors.colorDefaultStroke,
144
+ text: colors.colorDefaultText
145
+ },
146
+ hover: {
147
+ bg: colors.colorHoverBg,
148
+ stroke: colors.colorHoverStroke,
149
+ text: colors.colorHoverText
150
+ },
151
+ pressed: {
152
+ bg: colors.colorPressedBg,
153
+ stroke: colors.colorPressedStroke,
154
+ text: colors.colorPressedText
155
+ }
156
+ };
157
+ }
158
+
159
+ /**
160
+ * Initialize the background shape of the button
161
+ * @param {Shape|null} shape - Custom shape or null to use default Rectangle
162
+ * @private
163
+ */
164
+ initBackground(shape) {
165
+ this.bg = shape ?? new Rectangle({
166
+ width: this.width,
167
+ height: this.height,
168
+ color: this.colors.default.bg,
169
+ stroke: this.colors.default.stroke,
170
+ lineWidth: 2
171
+ });
172
+ }
173
+
174
+ /**
175
+ * Initialize the text label of the button
176
+ * @param {string} text - Button text
177
+ * @param {string} font - Text font
178
+ * @param {string} textColor - Text color
179
+ * @param {TextShape|null} label - Custom label or null to create a new one
180
+ * @private
181
+ */
182
+ initLabel(text, font, textColor, label) {
183
+ this.label = label ?? new TextShape(text, {
184
+ font: font,
185
+ color: textColor,
186
+ align: this.textAlign,
187
+ baseline: this.textBaseline
188
+ });
189
+
190
+ this.alignText();
191
+ }
192
+
193
+ /**
194
+ * Update label position based on alignment and baseline settings
195
+ * @private
196
+ */
197
+ alignText() {
198
+ if (!this.label) return;
199
+
200
+ const halfWidth = this.width / 2;
201
+ const halfHeight = this.height / 2;
202
+
203
+ // Horizontal alignment
204
+ switch (this.textAlign) {
205
+ case "left":
206
+ this.label.x = -halfWidth + this.padding;
207
+ break;
208
+ case "right":
209
+ this.label.x = halfWidth - this.padding;
210
+ break;
211
+ case "center":
212
+ default:
213
+ this.label.x = 0; // Center aligned means x=0 in center-based coords
214
+ break;
215
+ }
216
+
217
+ // Vertical alignment
218
+ switch (this.textBaseline) {
219
+ case "top":
220
+ this.label.y = -halfHeight + this.padding;
221
+ break;
222
+ case "bottom":
223
+ this.label.y = halfHeight - this.padding;
224
+ break;
225
+ case "middle":
226
+ default:
227
+ this.label.y = 0; // Middle aligned means y=0 in center-based coords
228
+ break;
229
+ }
230
+ }
231
+
232
+ /**
233
+ * Initialize the group that will contain both background and label
234
+ * @private
235
+ */
236
+ initGroup() {
237
+ this.group = new Group();
238
+ this.group.add(this.bg);
239
+ this.group.add(this.label);
240
+ }
241
+
242
+ /**
243
+ * Set up event handlers for mouse/touch interactions
244
+ * @param {Function|null} onClick - Callback to execute when button is clicked
245
+ * @private
246
+ */
247
+ initEvents(onClick, onHover, onPressed, onRelease) {
248
+ this.interactive = true;
249
+ this.onHover = onHover;
250
+ this.onPressed = onPressed;
251
+ this.onRelease = onRelease;
252
+
253
+ this.on("mouseover", this.setState.bind(this, "hover"));
254
+ this.on("mouseout", this.setState.bind(this, "default"));
255
+ this.on("inputdown", this.setState.bind(this, "pressed"));
256
+ this.on("inputup", () => {
257
+ // Fire onClick if user was in "pressed" state
258
+ if (this.state === "pressed" && typeof onClick === "function") {
259
+ onClick();
260
+ }
261
+ // Return to hover state if the pointer is still over the button
262
+ this.setState("hover");
263
+ });
264
+ }
265
+
266
+ /**
267
+ * Set the Button's state and update its visual appearance.
268
+ * @param {string} state - "default", "hover", or "pressed".
269
+ */
270
+ setState(state) {
271
+ if (this.state === state) return;
272
+ this.state = state;
273
+
274
+ // Adjust shape & label appearance based on the new state
275
+ switch (state) {
276
+ case "default":
277
+ if (this.game.cursor) {
278
+ setTimeout(() => {
279
+ this.game.cursor.activate();
280
+ }, 0);
281
+ }
282
+ this.bg.color = this.colors.default.bg;
283
+ this.bg.stroke = this.colors.default.stroke;
284
+ this.label.color = this.colors.default.text;
285
+ this.game.canvas.style.cursor = "default";
286
+ this.onRelease?.();
287
+ break;
288
+
289
+ case "hover":
290
+ if (this.game.cursor) {
291
+ this.game.cursor.deactivate();
292
+ }
293
+ this.bg.color = this.colors.hover.bg;
294
+ this.bg.stroke = this.colors.hover.stroke;
295
+ this.label.color = this.colors.hover.text;
296
+ this.game.canvas.style.cursor = "pointer";
297
+ this.onHover?.();
298
+ break;
299
+
300
+ case "pressed":
301
+ if (this.game.cursor) {
302
+ this.game.cursor.deactivate();
303
+ }
304
+ this.bg.color = this.colors.pressed.bg;
305
+ this.bg.stroke = this.colors.pressed.stroke;
306
+ this.label.color = this.colors.pressed.text;
307
+ this.game.canvas.style.cursor = "pointer";
308
+ this.onPressed?.();
309
+ break;
310
+ }
311
+ }
312
+
313
+ /**
314
+ * Update method called each frame
315
+ * @param {number} dt - Delta time in seconds
316
+ */
317
+ update(dt) {
318
+ super.update(dt);
319
+
320
+ // Check if we need to reposition the label
321
+ if (this._boundsDirty) {
322
+ this.alignText();
323
+ }
324
+ }
325
+
326
+ /**
327
+ * Get the text of the Button.
328
+ * @returns {string} The text of the Button.
329
+ */
330
+ get text() {
331
+ return this.label.text;
332
+ }
333
+
334
+ /**
335
+ * Set the text of the Button.
336
+ * @param {string} text - The new text for the Button.
337
+ */
338
+ set text(text) {
339
+ this.label.text = text;
340
+ this._boundsDirty = true;
341
+ }
342
+
343
+ /**
344
+ * Change text alignment
345
+ * @param {string} align - New text alignment: "left", "center", or "right"
346
+ */
347
+ setTextAlign(align) {
348
+ this.textAlign = align;
349
+ this.label.align = align;
350
+ this._boundsDirty = true;
351
+ }
352
+
353
+ /**
354
+ * Change text baseline
355
+ * @param {string} baseline - New text baseline: "top", "middle", or "bottom"
356
+ */
357
+ setTextBaseline(baseline) {
358
+ this.textBaseline = baseline;
359
+ this.label.baseline = baseline;
360
+ this._boundsDirty = true;
361
+ }
362
+
363
+ /**
364
+ * Set the font for the button label
365
+ * @param {string} font - CSS font string (e.g., "14px Arial")
366
+ */
367
+ setFont(font) {
368
+ this.label.font = font;
369
+ this._boundsDirty = true;
370
+ }
371
+
372
+ /**
373
+ * Resize the button and update child elements
374
+ * @param {number} width - New button width
375
+ * @param {number} height - New button height
376
+ */
377
+ resize(width, height) {
378
+ this.width = width;
379
+ this.height = height;
380
+
381
+ // Update background size
382
+ this.bg.width = width;
383
+ this.bg.height = height;
384
+
385
+ // Reposition the label
386
+ this._boundsDirty = true;
387
+ }
388
+
389
+ /**
390
+ * Render the Button each frame by drawing the underlying Group.
391
+ */
392
+ draw() {
393
+ super.draw();
394
+ this.group.render();
395
+ }
396
+ }
@@ -0,0 +1,93 @@
1
+ import { GameObject } from "../objects/go.js";
2
+
3
+ /**
4
+ * Cursor
5
+ *
6
+ * A GameObject that replaces the native mouse pointer with a custom shape.
7
+ * - Provide a normal shape (e.g. small Circle or custom icon).
8
+ * - Optionally provide a pressed shape to show while the mouse/touch is down.
9
+ *
10
+ * Usage:
11
+ * const cursor = new Cursor(game, normalShape, pressedShape);
12
+ * myScene.add(cursor);
13
+ */
14
+ export class Cursor extends GameObject {
15
+ /**
16
+ * @param {Game} game - The main game instance.
17
+ * @param {Shape} normalShape - The shape to display when not pressed.
18
+ * @param {Shape} [pressedShape] - Optional shape to display when pressed.
19
+ */
20
+ constructor(game, normalShape, pressedShape = null, options = {}) {
21
+ super(game, options);
22
+ /**
23
+ * Shape shown when the mouse is not pressed.
24
+ * @type {Shape}
25
+ */
26
+ this.normalShape = normalShape;
27
+ /**
28
+ * Shape shown when the mouse is pressed.
29
+ * Fallback to normalShape if not provided.
30
+ * @type {Shape}
31
+ */
32
+ this.pressedShape = pressedShape || normalShape;
33
+ /**
34
+ * If the cursor is active.
35
+ */
36
+ this.active = false;
37
+ /**
38
+ * How far the cursor is offset from the mouse position.
39
+ */
40
+ this.offsetX = 0.0;
41
+ /**
42
+ * How far the cursor is offset from the mouse position.
43
+ */
44
+ this.offsetY = 0.0;
45
+ /**
46
+ * Whether the mouse (or touch) is currently pressed.
47
+ * @type {boolean}
48
+ */
49
+ this.isDown = false;
50
+ // Listen for pointer movements on the entire game
51
+ this.game.events.on("inputmove", (e) => {
52
+ this.x = e.x;
53
+ this.y = e.y;
54
+ });
55
+ this.game.events.on("inputdown", () => {
56
+ this.isDown = true;
57
+ });
58
+ this.game.events.on("inputup", () => {
59
+ this.isDown = false;
60
+ });
61
+ this.game.events.on("mouseover", () => {
62
+ this.visible = false;
63
+ });
64
+ this.game.events.on("mouseout", () => {
65
+ this.visible = true;
66
+ });
67
+ }
68
+
69
+ activate() {
70
+ this.active = true;
71
+ // Hide the native cursor
72
+ this.game.canvas.style.cursor = "none";
73
+ }
74
+
75
+ deactivate() {
76
+ this.active = false;
77
+ // Show the native cursor
78
+ this.game.canvas.style.cursor = "default";
79
+ }
80
+ /**
81
+ * Renders whichever shape is appropriate based on mouse pressed state.
82
+ */
83
+ draw() {
84
+ super.draw();
85
+ //this.logger.log("render cursor", this.normalShape);
86
+ if (!this.active) return;
87
+ // Decide which shape to draw
88
+ const shape = this.isDown && this.pressedShape ? this.pressedShape : this.normalShape;
89
+ if (!shape) return;
90
+ // Draw it
91
+ shape.render();
92
+ }
93
+ }
@@ -0,0 +1,91 @@
1
+ import { Text } from "../objects";
2
+
3
+ /**
4
+ * FPSCounter - A simple text object displaying frames per second
5
+ * @extends Text
6
+ */
7
+ export class FPSCounter extends Text {
8
+ /**
9
+ * Create an FPS counter
10
+ * @param {Game} game - The main game instance
11
+ * @param {Object} [options={}] - Configuration options
12
+ */
13
+ constructor(game, options = {}) {
14
+ // Spread the user options but provide defaults for FPS counter
15
+ super(game, "0 FPS", {
16
+ x: 0,
17
+ y: 0,
18
+ font: "12px monospace",
19
+ color: "#0f0", // Default to green
20
+ align: "center",
21
+ baseline: "middle",
22
+ debug:false,
23
+ ...options, // This will override defaults with user provided values
24
+ });
25
+
26
+ // FPS tracking properties
27
+ this.fps = 0;
28
+ this._frames = 0;
29
+ this._accum = 0;
30
+ }
31
+
32
+ /**
33
+ * Update the FPS counter
34
+ * @param {number} dt - Delta time in seconds
35
+ */
36
+ update(dt) {
37
+ const fps = this.game.actualFps;
38
+ if (!fps) return;
39
+
40
+ this._frames++;
41
+ this._accum += dt;
42
+
43
+ if (this._accum >= 0.5) {
44
+ this.fps = Math.round(fps);
45
+ this.text = `${this.fps} FPS`;
46
+ this._accum = 0;
47
+ this._frames = 0;
48
+ }
49
+ super.update(dt);
50
+ }
51
+
52
+ /**
53
+ * Override getBounds to return correct bounding box
54
+ * @returns {Object} Bounding box with x, y, width, and height
55
+ */
56
+ getBounds() {
57
+ // Use the shape's getTextBounds method if available
58
+ if (this.shape && this.shape.getTextBounds) {
59
+ const bounds = this.shape.getTextBounds();
60
+
61
+ // Return bounds with the current object's x and y
62
+ return {
63
+ x: bounds.x,
64
+ y: bounds.y,
65
+ width: bounds.width,
66
+ height: bounds.height
67
+ };
68
+ }
69
+
70
+ // Fallback to parent implementation
71
+ return super.getBounds();
72
+ }
73
+
74
+ getDebugBounds() {
75
+ // Use the shape's getTextBounds method if available
76
+ if (this.shape && this.shape.getDebugBounds) {
77
+ const bounds = this.shape.getDebugBounds();
78
+
79
+ // Return bounds with the current object's x and y
80
+ return {
81
+ x: bounds.x,
82
+ y: bounds.y,
83
+ width: bounds.width,
84
+ height: bounds.height
85
+ };
86
+ }
87
+
88
+ // Fallback to parent implementation
89
+ return super.getDebugBounds();
90
+ }
91
+ }
@@ -0,0 +1,5 @@
1
+ export { Button } from "./button.js";
2
+ export { Cursor } from "./cursor.js";
3
+ export { ToggleButton } from "./togglebutton.js";
4
+ export { Tooltip } from "./tooltip.js";
5
+ export { FPSCounter } from "./fps.js";
@@ -0,0 +1,93 @@
1
+ import { Button } from "./button.js";
2
+
3
+ /**
4
+ * ToggleButton - A variant of Button with a persistent "toggled" (active) state.
5
+ *
6
+ * Usage:
7
+ * const myToggle = new ToggleButton(game, {
8
+ * text: "Tool 1",
9
+ * startToggled: true, // if you want it initially on
10
+ * onToggle: (isOn) => {
11
+ * this.logger.log("Tool 1 toggled?", isOn);
12
+ * },
13
+ * onClick: () => {
14
+ * this.logger.log("A normal click as well");
15
+ * }
16
+ * });
17
+ */
18
+ export class ToggleButton extends Button {
19
+ constructor(game, options = {}) {
20
+ // We'll intercept the user's onClick, so we can handle toggling
21
+ const userOnClick = options.onClick;
22
+
23
+
24
+ super(game, {
25
+ ...options,
26
+ onClick: () => {
27
+ // Flip the toggled state
28
+ this.toggled = !this.toggled;
29
+
30
+ // If there's an onToggle callback, call it
31
+ if (typeof options.onToggle === "function") {
32
+ options.onToggle(this.toggled);
33
+ }
34
+
35
+ // Also call the user's original onClick if provided
36
+ if (typeof userOnClick === "function") {
37
+ userOnClick();
38
+ }
39
+
40
+ // Update our visual style for toggled vs. not
41
+ this.refreshToggleVisual();
42
+ },
43
+ });
44
+ this.colorActiveBg = options.colorActiveBg || "#444";
45
+ this.colorActiveStroke = options.colorActiveStroke || "#0f0";
46
+ this.colorActiveText = options.colorActiveText || "#0f0";
47
+ // Track toggled state. Default is false unless 'startToggled' is set
48
+ this.toggled = !!options.startToggled;
49
+
50
+ // Apply the initial style according to toggled or not
51
+ this.refreshToggleVisual();
52
+ }
53
+
54
+ toggle(v) {
55
+ // Toggle the button state and refresh visuals
56
+ this.toggled = v;
57
+ this.refreshToggleVisual();
58
+ }
59
+
60
+ /**
61
+ * Decide how this ToggleButton looks when toggled vs. not toggled.
62
+ */
63
+ refreshToggleVisual() {
64
+ if (this.toggled) {
65
+ // E.g. "active" styling
66
+ this.bg.fillColor = this.colorActiveBg;
67
+ this.bg.strokeColor = this.colorActiveStroke;
68
+ this.label.color = this.colorActiveText;
69
+ } else {
70
+ // Revert to normal styling
71
+ this.bg.fillColor = this.colors.default.bg;
72
+ this.bg.strokeColor = this.colors.default.stroke;
73
+ this.label.color = this.colors.default.text;
74
+ }
75
+ }
76
+
77
+ /**
78
+ * If we want ephemeral states (hover, pressed) to remain visible briefly,
79
+ * we can let parent setState run, then immediately re-apply toggled style
80
+ * so we don't lose the "toggled" color. This is optional.
81
+ */
82
+ setState(state) {
83
+ super.setState(state);
84
+
85
+ // If we're toggled on, ensure it stays in the toggled visuals
86
+ // after the parent sets hover/pressed colors.
87
+ if (this.toggled) {
88
+ this.bg.fillColor = this.colorActiveBg;
89
+ this.bg.strokeColor = this.colorActiveStroke;
90
+ this.label.color = this.colorActiveText;
91
+ }
92
+ }
93
+ }