@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,295 @@
1
+ import { Logger } from "../logger/logger";
2
+ import { PainterColors } from "./painter.colors";
3
+ import { PainterEffects } from "./painter.effects";
4
+ import { PainterImages } from "./painter.img";
5
+ import { PainterLines } from "./painter.lines";
6
+ import { PainterOpacity } from "./painter.opacity";
7
+ import { PainterShapes } from "./painter.shapes";
8
+ import { PainterText } from "./painter.text";
9
+
10
+ /**
11
+ * Painter - Static utility class for all canvas drawing operations
12
+ * Provides a cleaner API for common canvas operations with additional utilities
13
+ */
14
+ export class Painter {
15
+ static #_colors = null;
16
+ static #_effects = null;
17
+ static #_img = null;
18
+ static #_lines = null;
19
+ static #_opacity = null;
20
+ static #_shapes = null;
21
+ static #_text = null;
22
+
23
+ static #checkInitialized(propName, value) {
24
+ if (!value) {
25
+ throw new Error(`Painter.${propName} is not initialized. Call Painter.init(ctx) first.`);
26
+ }
27
+ }
28
+
29
+ /**
30
+ * @type {PainterColors}
31
+ */
32
+ static get colors() {
33
+ this.#checkInitialized('colors', this.#_colors);
34
+ return this.#_colors;
35
+ }
36
+ /**
37
+ * @type {PainterEffects}
38
+ */
39
+ static get effects() {
40
+ this.#checkInitialized('effects', this.#_effects);
41
+ return this.#_effects;
42
+ }
43
+ /**
44
+ * @type {PainterImages}
45
+ */
46
+ static get img() {
47
+ this.#checkInitialized('img', this.#_img);
48
+ return this.#_img;
49
+ }
50
+ /**
51
+ * @type {PainterLines}
52
+ */
53
+ static get lines() {
54
+ this.#checkInitialized('lines', this.#_lines);
55
+ return this.#_lines;
56
+ }
57
+ /**
58
+ * @type {PainterOpacity}
59
+ */
60
+ static get opacity() {
61
+ this.#checkInitialized('opacity', this.#_opacity);
62
+ return this.#_opacity;
63
+ }
64
+ /**
65
+ * @type {PainterShapes}
66
+ */
67
+ static get shapes() {
68
+ this.#checkInitialized('shapes', this.#_shapes);
69
+ return this.#_shapes;
70
+ }
71
+ /**
72
+ * @type {PainterText}
73
+ */
74
+ static get text() {
75
+ this.#checkInitialized('text', this.#_text);
76
+ return this.#_text;
77
+ }
78
+ /**
79
+ * @type {Logger}
80
+ */
81
+ static logger;
82
+
83
+ static set ctx(ctx) {
84
+ this._ctx = ctx;
85
+ }
86
+
87
+ /**
88
+ * @type {CanvasRenderingContext2D}
89
+ */
90
+ static get ctx() {
91
+ if (!this._ctx) {
92
+ throw new Error("Cannot access Painter.ctx before initialization!");
93
+ }
94
+ return this._ctx;
95
+ }
96
+
97
+ /**
98
+ * Initialize the painter with a canvas context
99
+ * @param {CanvasRenderingContext2D} ctx - The canvas context
100
+ */
101
+ static init(ctx) {
102
+ this._ctx = ctx;
103
+ this.saveStack = [];
104
+ this.#_colors = PainterColors;
105
+ this.#_effects = PainterEffects;
106
+ this.#_img = PainterImages;
107
+ this.#_lines = PainterLines;
108
+ this.#_opacity = PainterOpacity;
109
+ this.#_shapes = PainterShapes;
110
+ this.#_text = PainterText;
111
+ Painter.logger = Logger.getLogger("Painter");
112
+ Painter.saveStack = [];
113
+ }
114
+
115
+ /**
116
+ * Switch the painter to a different canvas context.
117
+ * Used when multiple games/canvases exist on the same page.
118
+ * @param {CanvasRenderingContext2D} ctx - The canvas context to use
119
+ */
120
+ static setContext(ctx) {
121
+ this._ctx = ctx;
122
+ }
123
+
124
+ static save() {
125
+ // Extract just the method name from the stack trace
126
+ const stackLine = new Error().stack.split("\n")[2] || "";
127
+ const methodMatch = stackLine.match(/at\s+(\w+)\.(\w+)/);
128
+ const callerInfo = methodMatch
129
+ ? `${methodMatch[1]}.${methodMatch[2]}`
130
+ : "unknown";
131
+
132
+ // Add to tracking stack
133
+ this.saveStack.push(callerInfo);
134
+
135
+ // Log with caller info
136
+ this.logger.log(`Painter.save() by: ${callerInfo}`);
137
+
138
+ // Perform actual save
139
+ this.ctx.save();
140
+ Painter.opacity.saveOpacityState();
141
+ }
142
+
143
+ static restore() {
144
+ // Check for stack underflow
145
+ if (this.saveStack.length === 0) {
146
+ console.error("PAINTER ERROR: restore() without matching save()!");
147
+ return;
148
+ }
149
+
150
+ // Get the matching caller from stack
151
+ const caller = this.saveStack.pop();
152
+
153
+ // Log with caller info
154
+ this.logger.log(`Painter.restore() balancing save from: ${caller}`);
155
+
156
+ // Perform actual restore
157
+ // Note: ctx.restore() already restores fillStyle/strokeStyle from the save stack.
158
+ // Do NOT set fillStyle/strokeStyle to null here - it breaks subsequent fills.
159
+ this.ctx.restore();
160
+ Painter.opacity.restoreOpacityState();
161
+ }
162
+
163
+ static translateTo(x, y) {
164
+ // make sure x a valid number not NaN or undefined. default to 0 if so
165
+ if (isNaN(x) || x === undefined) {
166
+ x = 0;
167
+ }
168
+ if (isNaN(y) || y === undefined) {
169
+ y = 0;
170
+ }
171
+ this.logger.log("moveTo", x, y);
172
+ this.ctx.translate(x, y);
173
+ }
174
+
175
+ static resetPosition() {
176
+ // Reset just the translation portion of the transform
177
+ this.logger.log("resetPosition");
178
+ const transform = this.ctx.getTransform();
179
+ this.ctx.setTransform(
180
+ transform.a,
181
+ transform.b,
182
+ transform.c,
183
+ transform.d,
184
+ 0,
185
+ 0
186
+ );
187
+ }
188
+
189
+ static withPosition(x, y, callback) {
190
+ this.logger.log("withPosition", x, y);
191
+ this.save();
192
+ this.translateTo(x, y);
193
+ callback();
194
+ this.restore();
195
+ }
196
+
197
+ /**
198
+ * Clear the entire canvas or a specific rectangle
199
+ * @param {number} [x=0] - X coordinate
200
+ * @param {number} [y=0] - Y coordinate
201
+ * @param {number} [width=canvas.width] - Width
202
+ * @param {number} [height=canvas.height] - Height
203
+ * @returns {void}
204
+ */
205
+ static clear(
206
+ x = 0,
207
+ y = 0,
208
+ width = Painter.ctx.canvas.width,
209
+ height = Painter.ctx.canvas.height
210
+ ) {
211
+ Painter.ctx.clearRect(x, y, width, height);
212
+ }
213
+
214
+ /**
215
+ * Translate the canvas
216
+ * @param {number} x - X translation
217
+ * @param {number} y - Y translation
218
+ * @returns {void}
219
+ */
220
+ static translate(x, y) {
221
+ Painter.ctx.translate(x, y);
222
+ }
223
+
224
+ /**
225
+ * Rotate the canvas
226
+ * @param {number} angle - Rotation angle in radians
227
+ * @returns {void}
228
+ */
229
+ static rotate(angle) {
230
+ Painter.logger.log("Painter.rotate", angle);
231
+ Painter.ctx.rotate(angle);
232
+ }
233
+
234
+ /**
235
+ * Scale the canvas
236
+ * @param {number} x - X scale factor
237
+ * @param {number} y - Y scale factor
238
+ * @returns {void}
239
+ */
240
+ static scale(x, y) {
241
+ Painter.logger.log("Painter.scale", x, y);
242
+ Painter.ctx.scale(x, y);
243
+ }
244
+
245
+ /**
246
+ * Safely use the canvas context for direct drawing operations.
247
+ * Automatically handles path cleanup to prevent fill/stroke bleeding
248
+ * between different drawing operations.
249
+ *
250
+ * @param {function(CanvasRenderingContext2D): void} callback - Function that receives ctx
251
+ * @param {object} [options={}] - Options for context handling
252
+ * @param {boolean} [options.saveState=false] - Whether to save/restore full canvas state
253
+ * @returns {void}
254
+ *
255
+ * @example
256
+ * // Basic usage - draws a custom path with automatic cleanup
257
+ * Painter.useCtx((ctx) => {
258
+ * ctx.strokeStyle = "white";
259
+ * ctx.lineWidth = 2;
260
+ * ctx.moveTo(0, 0);
261
+ * ctx.lineTo(100, 100);
262
+ * ctx.stroke();
263
+ * });
264
+ *
265
+ * @example
266
+ * // With state preservation - restores all canvas state after
267
+ * Painter.useCtx((ctx) => {
268
+ * ctx.fillStyle = "red";
269
+ * ctx.fillRect(0, 0, 50, 50);
270
+ * }, { saveState: true });
271
+ */
272
+ static useCtx(callback, options = {}) {
273
+ const ctx = this.ctx;
274
+ const { saveState = false } = options;
275
+
276
+ // Optionally save full canvas state
277
+ if (saveState) {
278
+ this.save();
279
+ }
280
+
281
+ // Start with a fresh path
282
+ ctx.beginPath();
283
+
284
+ // Execute user's drawing code
285
+ callback(ctx);
286
+
287
+ // Clear path to prevent bleeding into subsequent renders
288
+ ctx.beginPath();
289
+
290
+ // Optionally restore full canvas state
291
+ if (saveState) {
292
+ this.restore();
293
+ }
294
+ }
295
+ }
@@ -0,0 +1,189 @@
1
+ import { Painter } from "./painter";
2
+
3
+ export class PainterLines {
4
+ static path(commands, fill, stroke, lineWidth = 1) {
5
+ const ctx = Painter.ctx;
6
+ ctx.beginPath();
7
+ for (const cmd of commands) {
8
+ const [type, ...args] = cmd;
9
+ if (type === "M") ctx.moveTo(...args);
10
+ else if (type === "L") ctx.lineTo(...args);
11
+ else if (type === "C") ctx.bezierCurveTo(...args);
12
+ else if (type === "Q") ctx.quadraticCurveTo(...args);
13
+ else if (type === "Z") ctx.closePath();
14
+ }
15
+ if (fill) {
16
+ ctx.fillStyle = fill;
17
+ Painter.colors.fill(fill);
18
+ }
19
+ if (stroke) {
20
+ ctx.strokeStyle = stroke;
21
+ ctx.lineWidth = lineWidth;
22
+ Painter.colors.stroke();
23
+ }
24
+ }
25
+
26
+ /**
27
+ * Draw a line
28
+ * @param {number} x1 - Start X
29
+ * @param {number} y1 - Start Y
30
+ * @param {number} x2 - End X
31
+ * @param {number} y2 - End Y
32
+ * @param {string|CanvasGradient} [color] - Line color
33
+ * @param {number} [lineWidth] - Line width
34
+ * @returns {void}
35
+ */
36
+ static line(x1, y1, x2, y2, color, lineWidth) {
37
+ Painter.ctx.beginPath();
38
+ Painter.ctx.moveTo(x1, y1);
39
+ Painter.ctx.lineTo(x2, y2);
40
+ Painter.colors.stroke(color, lineWidth);
41
+ }
42
+
43
+ /**
44
+ * Start a new path
45
+ * @returns {void}
46
+ */
47
+ static beginPath() {
48
+ Painter.ctx.beginPath();
49
+ }
50
+
51
+ /**
52
+ * Close the current path
53
+ * @returns {void}
54
+ */
55
+ static closePath() {
56
+ Painter.ctx.closePath();
57
+ }
58
+
59
+ /**
60
+ * Move to a point without drawing
61
+ * @param {number} x - X coordinate
62
+ * @param {number} y - Y coordinate
63
+ * @returns {void}
64
+ */
65
+ static moveTo(x, y) {
66
+ Painter.ctx.moveTo(x, y);
67
+ }
68
+
69
+ /**
70
+ * Draw a line to a point
71
+ * @param {number} x - X coordinate
72
+ * @param {number} y - Y coordinate
73
+ * @returns {void}
74
+ */
75
+ static lineTo(x, y) {
76
+ Painter.ctx.lineTo(x, y);
77
+ }
78
+
79
+ /**
80
+ * Add a bezier curve to the current path
81
+ * @param {number} cp1x - Control point 1 X
82
+ * @param {number} cp1y - Control point 1 Y
83
+ * @param {number} cp2x - Control point 2 X
84
+ * @param {number} cp2y - Control point 2 Y
85
+ * @param {number} x - End X
86
+ * @param {number} y - End Y
87
+ * @returns {void}
88
+ */
89
+ static bezierCurveTo(cp1x, cp1y, cp2x, cp2y, x, y) {
90
+ Painter.ctx.bezierCurveTo(cp1x, cp1y, cp2x, cp2y, x, y);
91
+ }
92
+
93
+ /**
94
+ * Draw a dashed line
95
+ * @param {number} x1 - Start X
96
+ * @param {number} y1 - Start Y
97
+ * @param {number} x2 - End X
98
+ * @param {number} y2 - End Y
99
+ * @param {Array<number>} dash - Dash pattern array [dash, gap, dash, gap, ...]
100
+ * @param {string|CanvasGradient} [color] - Line color
101
+ * @param {number} [lineWidth] - Line width
102
+ * @returns {void}
103
+ */
104
+ static dashedLine(x1, y1, x2, y2, dash, color, lineWidth) {
105
+ Painter.ctx.beginPath();
106
+ if (color) Painter.ctx.strokeStyle = color;
107
+ if (lineWidth !== undefined) Painter.ctx.lineWidth = lineWidth;
108
+
109
+ // Set the dash pattern
110
+ Painter.ctx.setLineDash(dash);
111
+
112
+ Painter.ctx.moveTo(x1, y1);
113
+ Painter.ctx.lineTo(x2, y2);
114
+ Painter.colors.stroke();
115
+
116
+ // Reset the dash pattern
117
+ Painter.ctx.setLineDash([]);
118
+ }
119
+
120
+ /**
121
+ * Draw a dotted line
122
+ * @param {number} x1 - Start X
123
+ * @param {number} y1 - Start Y
124
+ * @param {number} x2 - End X
125
+ * @param {number} y2 - End Y
126
+ * @param {number} dotSize - Size of dots
127
+ * @param {number} gap - Gap between dots
128
+ * @param {string|CanvasGradient} [color] - Line color
129
+ * @returns {void}
130
+ */
131
+ static dottedLine(x1, y1, x2, y2, dotSize = 2, gap = 5, color) {
132
+ return Painter.lines.dashedLine(
133
+ x1,
134
+ y1,
135
+ x2,
136
+ y2,
137
+ [dotSize, gap],
138
+ color,
139
+ dotSize
140
+ );
141
+ }
142
+
143
+ /**
144
+ * Set line dash pattern
145
+ * @param {Array<number>} segments - Array of line, gap lengths
146
+ * @returns {void}
147
+ */
148
+ static setLineDash(segments) {
149
+ Painter.ctx.setLineDash(segments);
150
+ }
151
+
152
+ /**
153
+ * Reset line dash to solid line
154
+ * @returns {void}
155
+ */
156
+ static resetLineDash() {
157
+ Painter.ctx.setLineDash([]);
158
+ }
159
+
160
+ /**
161
+ * Set line width
162
+ * @param {number} width - Line width
163
+ * @returns {void}
164
+ */
165
+ static setLineWidth(width) {
166
+ Painter.ctx.lineWidth = width;
167
+ }
168
+
169
+ /**
170
+ * Draw quadratic curve
171
+ * @param {number} x1 - Start X
172
+ * @param {number} y1 - Start Y
173
+ * @param {number} cpx - Control point X
174
+ * @param {number} cpy - Control point Y
175
+ * @param {number} x2 - End X
176
+ * @param {number} y2 - End Y
177
+ * @param {string|CanvasGradient} [color] - Stroke color
178
+ * @param {number} [lineWidth] - Line width
179
+ * @returns {void}
180
+ */
181
+ static quadraticCurve(x1, y1, cpx, cpy, x2, y2, color, lineWidth) {
182
+ Painter.ctx.beginPath();
183
+ Painter.ctx.moveTo(x1, y1);
184
+ Painter.ctx.quadraticCurveTo(cpx, cpy, x2, y2);
185
+ if (color) Painter.ctx.strokeStyle = color;
186
+ if (lineWidth !== undefined) Painter.ctx.lineWidth = lineWidth;
187
+ Painter.colors.stroke();
188
+ }
189
+ }
@@ -0,0 +1,41 @@
1
+ import { Painter } from "./painter";
2
+
3
+ export class PainterOpacity {
4
+ static _opacityStack = [1]; // Initialize with full opacity
5
+
6
+ static pushOpacity(opacity) {
7
+ // Compute compound opacity (multiply with current)
8
+ const currentOpacity = this._opacityStack[this._opacityStack.length - 1];
9
+ const newOpacity = currentOpacity * opacity;
10
+ // Push to stack and apply
11
+ this._opacityStack.push(newOpacity);
12
+ Painter.logger.log("NEXT OPACITY WILL BE", newOpacity);
13
+ Painter.effects.setAlpha(newOpacity);
14
+ }
15
+
16
+ static popOpacity() {
17
+ if (this._opacityStack.length > 1) {
18
+ this._opacityStack.pop();
19
+ const newOpacity = this._opacityStack[this._opacityStack.length - 1];
20
+ Painter.logger.log("NEXT OPACITY WILL BE", newOpacity);
21
+ Painter.effects.setAlpha(newOpacity);
22
+ }
23
+ }
24
+
25
+ static _clone() {
26
+ // Create a deep copy of the opacity stack
27
+ this._opacityStack = [...this._opacityStack];
28
+ }
29
+
30
+
31
+ static saveOpacityState() {
32
+ this._opacityStateBackup = [...this._opacityStack];
33
+ }
34
+
35
+ static restoreOpacityState() {
36
+ if (this._opacityStateBackup) {
37
+ this._opacityStack = this._opacityStateBackup;
38
+ delete this._opacityStateBackup;
39
+ }
40
+ }
41
+ }