@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,354 @@
1
+ import {
2
+ Game,
3
+ Scene,
4
+ FPSCounter,
5
+ GameObject,
6
+ HorizontalLayout,
7
+ VerticalLayout,
8
+ Button,
9
+ ShapeGOFactory,
10
+ Rectangle,
11
+ Position,
12
+ Painter,
13
+ TileLayout,
14
+ GridLayout,
15
+ } from "../../src/index";
16
+
17
+ export class LayoutDemo extends Game {
18
+ constructor(canvas) {
19
+ super(canvas);
20
+ this.enableFluidSize();
21
+ this.backgroundColor = "black";
22
+ this.cellSize = 110;
23
+ this.maxColumns = 6;
24
+ }
25
+
26
+ getResponsiveColumns() {
27
+ const margin = 100; // space for UI
28
+ const availableWidth = this.width - margin;
29
+ return Math.min(
30
+ this.maxColumns,
31
+ Math.max(1, Math.floor(availableWidth / this.cellSize))
32
+ );
33
+ }
34
+
35
+ isMobile() {
36
+ return this.width < 600;
37
+ }
38
+
39
+ getLayoutYOffset() {
40
+ // On mobile (no info bar), move layout up; on desktop keep lower to avoid info bar
41
+ return this.isMobile() ? -80 : 0;
42
+ }
43
+
44
+ /**
45
+ * Get viewport dimensions based on current layout mode.
46
+ * - Horizontal: fixed height (item height + padding), width = screen - margins
47
+ * - Vertical: fixed width (item width + padding), height = screen - nav - margins
48
+ * - Tile/Grid: square-ish viewport with margins on sides, scrolls vertically
49
+ */
50
+ getViewportDimensions() {
51
+ const navHeight = 320; // bottom nav (two rows at -30 and -100 offset + button heights)
52
+ const margin = 50;
53
+
54
+ const layoutModes = this.getLayoutModes();
55
+ const modeConfig = layoutModes[this.mode];
56
+
57
+ switch (this.mode) {
58
+ case "horizontal":
59
+ // Horizontal: fixed height based on item height, scroll horizontally
60
+ return {
61
+ width: Math.max(200, this.width - margin * 2),
62
+ height: 100 + 20, // item height (100) + padding
63
+ };
64
+
65
+ case "vertical":
66
+ // Vertical: fixed width based on item width, scroll vertically
67
+ return {
68
+ width: 200 + 20, // item width (200) + padding
69
+ height: Math.max(200, this.height - navHeight - margin),
70
+ };
71
+
72
+ case "tile":
73
+ case "grid":
74
+ default:
75
+ // Tile/Grid: responsive square-ish viewport, scroll vertically
76
+ return {
77
+ width: Math.max(200, this.width - margin * 2),
78
+ height: Math.max(200, this.height - navHeight - margin),
79
+ };
80
+ }
81
+ }
82
+
83
+ // Define layout modes configuration (uses arrow functions to access `this`)
84
+ getLayoutModes() {
85
+ const columns = this.getResponsiveColumns();
86
+ return {
87
+ horizontal: {
88
+ layoutClass: HorizontalLayout,
89
+ itemDimensions: () => ({
90
+ width: 40 + Math.random() * 60,
91
+ height: 100,
92
+ }),
93
+ },
94
+ vertical: {
95
+ layoutClass: VerticalLayout,
96
+ itemDimensions: () => ({
97
+ width: 200,
98
+ height: 40 + Math.random() * 60,
99
+ }),
100
+ },
101
+ tile: {
102
+ layoutClass: TileLayout,
103
+ itemDimensions: () => ({
104
+ width: 100,
105
+ height: 100,
106
+ }),
107
+ layoutOptions: (baseOpts) => ({
108
+ ...baseOpts,
109
+ columns: columns,
110
+ }),
111
+ },
112
+ grid: {
113
+ layoutClass: GridLayout,
114
+ itemDimensions: () => {
115
+ // Randomly choose between portrait or landscape
116
+ const isPortrait = Math.random() > 0.5;
117
+ if (isPortrait) {
118
+ // Portrait: taller than wide
119
+ if (Math.random() > 0.3) {
120
+ return {
121
+ width: 100,
122
+ height: 200,
123
+ };
124
+ } else {
125
+ return {
126
+ width: 100,
127
+ height: 100,
128
+ };
129
+ }
130
+ } else {
131
+ if (Math.random() > 0.3) {
132
+ // Landscape: wider than tall
133
+ return {
134
+ width: 100,
135
+ height: 50,
136
+ };
137
+ } else {
138
+ return {
139
+ width: 100,
140
+ height: 100,
141
+ };
142
+ }
143
+ }
144
+ },
145
+ layoutOptions: (baseOpts) => ({
146
+ ...baseOpts,
147
+ columns: columns,
148
+ spacing: 10,
149
+ padding: 10,
150
+ centerItems: true,
151
+ }),
152
+ },
153
+ };
154
+ }
155
+
156
+ init() {
157
+ super.init();
158
+ // Config Options
159
+ this.items = [];
160
+ this.mode = "horizontal";
161
+ this.currentColumns = this.getResponsiveColumns();
162
+
163
+ // Create UI
164
+ this.ui = new Scene(this, {
165
+ debug: true,
166
+ debugColor: "pink",
167
+ anchor: Position.CENTER,
168
+ });
169
+ this.pipeline.add(this.ui);
170
+
171
+ // Add FPS Counter
172
+ this.pipeline.add(
173
+ new FPSCounter(this, { color: "#00FF00", anchor: "bottom-right" })
174
+ );
175
+
176
+ // Create bottom navigation - layout type selection (at very bottom)
177
+ this.layoutNav = new HorizontalLayout(this, {
178
+ debug: true,
179
+ debugColor: "purple",
180
+ anchor: Position.BOTTOM_CENTER,
181
+ anchorOffsetY: -30,
182
+ padding: 10,
183
+ spacing: 10,
184
+ });
185
+ this.ui.add(this.layoutNav);
186
+
187
+ // Add layout type buttons
188
+ const layoutModes = this.getLayoutModes();
189
+ Object.keys(layoutModes).forEach((mode) => {
190
+ this.layoutNav.add(
191
+ new Button(this, {
192
+ text: mode.charAt(0).toUpperCase() + mode.slice(1),
193
+ onClick: () => this.setLayout(mode),
194
+ })
195
+ );
196
+ });
197
+
198
+ // Create action buttons (add/remove) - positioned above layout nav
199
+ this.actionNav = new HorizontalLayout(this, {
200
+ anchor: Position.BOTTOM_CENTER,
201
+ debug: true,
202
+ debugColor: "cyan",
203
+ anchorOffsetY: -100, // Space above the layout nav
204
+ padding: 10,
205
+ spacing: 10,
206
+ });
207
+ this.actionNav.add(
208
+ new Button(this, {
209
+ text: "ADD",
210
+ onClick: this.addItem.bind(this),
211
+ })
212
+ );
213
+ this.actionNav.add(
214
+ new Button(this, {
215
+ text: "REMOVE",
216
+ onClick: () => this.removeItem(),
217
+ })
218
+ );
219
+ this.ui.add(this.actionNav);
220
+
221
+ // Create initial layout
222
+ this.createLayout();
223
+
224
+ // Add enough items to enable scrolling
225
+ for (let i = 0; i < 5; i++) {
226
+ this.addItem(i);
227
+ }
228
+ }
229
+
230
+ createLayout() {
231
+ if (this.layout) {
232
+ // Properly clear the layout
233
+ this.layout.clear();
234
+ this.pipeline.remove(this.layout);
235
+ this.layout = null;
236
+ }
237
+
238
+ const viewport = this.getViewportDimensions();
239
+
240
+ const baseOpts = {
241
+ anchor: Position.CENTER,
242
+ anchorOffsetY: this.getLayoutYOffset(),
243
+ spacing: 10,
244
+ padding: 10,
245
+ debug: true,
246
+ autoSize: true,
247
+ debugColor: Painter.colors.randomColorHSL(),
248
+ // Enable scrolling with responsive viewport
249
+ scrollable: true,
250
+ viewportWidth: viewport.width,
251
+ viewportHeight: viewport.height,
252
+ };
253
+
254
+ const layoutModes = this.getLayoutModes();
255
+ const modeConfig = layoutModes[this.mode];
256
+ const layoutOpts = modeConfig.layoutOptions
257
+ ? modeConfig.layoutOptions(baseOpts)
258
+ : baseOpts;
259
+
260
+ this.layout = new modeConfig.layoutClass(this, layoutOpts);
261
+ // Add layout to pipeline
262
+ this.pipeline.add(this.layout);
263
+ // Add items to layout
264
+ if (this.items.length > 0) {
265
+ // Reset each item properly
266
+ setTimeout(() => {
267
+ this.items.forEach((item) => {
268
+ const dimensions = modeConfig.itemDimensions();
269
+ item.width = dimensions.width;
270
+ item.height = dimensions.height;
271
+ // Add to the container
272
+ this.layout.add(item);
273
+ });
274
+ }, 10);
275
+ // Mark the layout as needing recalculation
276
+ this.layout.markBoundsDirty();
277
+ }
278
+ }
279
+
280
+ update(dt) {
281
+ super.update(dt);
282
+ // stuff that has to do with bound checking should be done after super.update.
283
+ if (this.boundsDirty) {
284
+ console.log("update", this.boundsDirty);
285
+ this.ui.width = this.width;
286
+ this.ui.height = this.height;
287
+ this.ui.markBoundsDirty();
288
+ this.layout.markBoundsDirty();
289
+ this.boundsDirty = false;
290
+ }
291
+ }
292
+
293
+ setLayout(mode) {
294
+ if (mode !== this.mode) {
295
+ this.mode = mode;
296
+ this.createLayout();
297
+ }
298
+ }
299
+
300
+ addItem(i) {
301
+ const layoutModes = this.getLayoutModes();
302
+ const modeConfig = layoutModes[this.mode];
303
+ const dimensions = modeConfig.itemDimensions();
304
+ const rect = new Rectangle({
305
+ width: dimensions.width,
306
+ height: dimensions.height,
307
+ color: Painter.colors.randomColorHSL(),
308
+ stroke: "white",
309
+ lineWidth: 2,
310
+ });
311
+ const go = ShapeGOFactory.create(this, rect);
312
+ go.name = "item " + i;
313
+ this.items.push(go);
314
+ this.layout.add(go);
315
+ }
316
+
317
+ removeItem() {
318
+ if (this.items.length > 0) {
319
+ const go = this.items.pop();
320
+ this.layout.remove(go);
321
+ }
322
+ }
323
+
324
+ onResize() {
325
+ if (!this.layout) return;
326
+
327
+ const newColumns = this.getResponsiveColumns();
328
+
329
+ // Recreate layout if columns changed (for tile/grid modes)
330
+ if (this.currentColumns !== newColumns) {
331
+ this.currentColumns = newColumns;
332
+ if (this.mode === "tile" || this.mode === "grid") {
333
+ this.createLayout();
334
+ return; // createLayout already sets up viewport
335
+ }
336
+ }
337
+
338
+ // Update viewport dimensions for scrolling (mode-specific)
339
+ const viewport = this.getViewportDimensions();
340
+ this.layout._viewportWidth = viewport.width;
341
+ this.layout._viewportHeight = viewport.height;
342
+
343
+ // Update layout Y offset based on mobile/desktop
344
+ this.layout.anchorOffsetY = this.getLayoutYOffset();
345
+
346
+ // Update UI dimensions
347
+ this.ui.width = this.width - 20;
348
+ this.ui.height = this.height - 20;
349
+ this.ui.markBoundsDirty();
350
+ this.layoutNav.markBoundsDirty();
351
+ this.actionNav.markBoundsDirty();
352
+ this.layout.markBoundsDirty();
353
+ }
354
+ }
@@ -0,0 +1,285 @@
1
+ import {
2
+ Button,
3
+ Game,
4
+ GridLayout,
5
+ HorizontalLayout,
6
+ Rectangle,
7
+ Scene,
8
+ ToggleButton,
9
+ VerticalLayout,
10
+ TextShape,
11
+ Position,
12
+ ShapeGOFactory,
13
+ Easing,
14
+ Tweenetik,
15
+ } from "../../src/index";
16
+
17
+ /**
18
+ * MondrianDemo - Interactive grid-based composition
19
+ * Inspired by the works of Piet Mondrian
20
+ */
21
+ export class MondrianDemo extends Game {
22
+ constructor(canvas) {
23
+ super(canvas);
24
+ this.enableFluidSize();
25
+ }
26
+
27
+ init() {
28
+ super.init();
29
+
30
+ // Setup
31
+ this.backgroundColor = "#ffffff";
32
+ this.gridScene = new Scene(this, { debugColor: "black" });
33
+
34
+ // Configure grid scene
35
+ const margin = 20;
36
+ this.gridScene.width = this.width - margin * 2;
37
+ this.gridScene.height = this.height - margin * 2;
38
+ this.gridScene.x = margin;
39
+ this.gridScene.y = margin;
40
+
41
+ // Generate initial composition
42
+ this.generateMondrianRectangles(
43
+ this.gridScene.width,
44
+ this.gridScene.height
45
+ );
46
+
47
+ // Add to pipeline
48
+ this.pipeline.add(this.gridScene);
49
+
50
+ // Handle click events
51
+ this.events.on("click", () => this.animateExplosion());
52
+ }
53
+
54
+ animateExplosion() {
55
+ const centerX = this.width / 2;
56
+ const centerY = this.height / 2;
57
+ const maxDimension = Math.max(this.width, this.height);
58
+ const children = this.gridScene._collection.getSortedChildren();
59
+
60
+ children.forEach((rect) => {
61
+ // Calculate angle and distance from center
62
+ const angle = Math.atan2(rect.y - centerY, rect.x - centerX);
63
+ const distanceFromCenter = Math.sqrt(
64
+ Math.pow(rect.x - centerX, 2) + Math.pow(rect.y - centerY, 2)
65
+ );
66
+ const normalizedDistance = Math.min(
67
+ 1,
68
+ distanceFromCenter / (Math.min(this.width, this.height) / 2)
69
+ );
70
+
71
+ // Calculate animation parameters
72
+ const flyDistance = maxDimension * 1.5;
73
+ const destX = centerX + Math.cos(angle) * flyDistance;
74
+ const destY = centerY + Math.sin(angle) * flyDistance;
75
+ const delay = (1 - normalizedDistance) * 0.5;
76
+ const rotation = (Math.random() * 2 - 1) * Math.PI * 4;
77
+
78
+ // Animate rectangle flying away
79
+ Tweenetik.to(
80
+ rect,
81
+ {
82
+ opacity: 0,
83
+ x: destX,
84
+ y: destY,
85
+ scaleX: 0.2,
86
+ scaleY: 0.2,
87
+ rotation: rotation,
88
+ },
89
+ 0.5 + normalizedDistance * 0.5,
90
+ Easing.easeInSine,
91
+ {
92
+ delay: delay,
93
+ onComplete: () => {
94
+ this.generateMondrianRectangles(
95
+ this.gridScene.width,
96
+ this.gridScene.height
97
+ );
98
+ },
99
+ }
100
+ );
101
+ });
102
+ }
103
+
104
+ generateMondrianRectangles(totalWidth, totalHeight, options = {}) {
105
+ // Clear existing rectangles
106
+ this.gridScene.clear();
107
+
108
+ // Default options
109
+ const {
110
+ lineWidth = 8,
111
+ step = totalHeight / 6,
112
+ splitProbability = 0.5,
113
+ } = options;
114
+
115
+ // Colors
116
+ const white = "#F2F5F1";
117
+ const colors = ["#D40920", "#1356A2", "#F7D842", "#999999"];
118
+
119
+ // Start with one rectangle covering the entire area
120
+ let squares = [
121
+ {
122
+ x: 0,
123
+ y: 0,
124
+ width: totalWidth,
125
+ height: totalHeight,
126
+ },
127
+ ];
128
+
129
+ // Generate split points based on grid
130
+ const splitPoints = Array.from(
131
+ { length: Math.ceil(Math.max(totalWidth, totalHeight) / step) },
132
+ (_, i) => i * step
133
+ );
134
+
135
+ // Split function
136
+ const splitSquaresAt = (coord) => {
137
+ const { x, y } = coord;
138
+
139
+ for (let i = squares.length - 1; i >= 0; i--) {
140
+ const square = squares[i];
141
+
142
+ // Split on x-coordinate
143
+ if (
144
+ x &&
145
+ x > square.x &&
146
+ x < square.x + square.width &&
147
+ Math.random() < splitProbability
148
+ ) {
149
+ squares.splice(i, 1);
150
+
151
+ squares.push(
152
+ {
153
+ x: square.x,
154
+ y: square.y,
155
+ width: x - square.x,
156
+ height: square.height,
157
+ },
158
+ {
159
+ x: x,
160
+ y: square.y,
161
+ width: square.width - (x - square.x),
162
+ height: square.height,
163
+ }
164
+ );
165
+ }
166
+
167
+ // Split on y-coordinate
168
+ if (
169
+ y &&
170
+ y > square.y &&
171
+ y < square.y + square.height &&
172
+ Math.random() < splitProbability
173
+ ) {
174
+ squares.splice(i, 1);
175
+
176
+ squares.push(
177
+ {
178
+ x: square.x,
179
+ y: square.y,
180
+ width: square.width,
181
+ height: y - square.y,
182
+ },
183
+ {
184
+ x: square.x,
185
+ y: y,
186
+ width: square.width,
187
+ height: square.height - (y - square.y),
188
+ }
189
+ );
190
+ }
191
+ }
192
+ };
193
+
194
+ // Apply splits
195
+ splitPoints.forEach((point) => {
196
+ splitSquaresAt({ y: point });
197
+ splitSquaresAt({ x: point });
198
+ });
199
+
200
+ // Assign colors to some squares
201
+ for (let i = 0; i < colors.length * 3; i++) {
202
+ const randomIndex = Math.floor(Math.random() * squares.length);
203
+ squares[randomIndex].color =
204
+ Math.random() < 0.8 ? colors[i % colors.length] : "black";
205
+ }
206
+
207
+ // Calculate center for animations
208
+ const centerX = this.width / 2;
209
+ const centerY = this.height / 2;
210
+ const maxDimension = Math.max(this.width, this.height);
211
+
212
+ // Create and animate rectangles
213
+ squares.forEach((square, i, allSquares) => {
214
+ // Create rectangle
215
+ const rect = new Rectangle({
216
+ width: square.width - lineWidth,
217
+ height: square.height - lineWidth,
218
+ color: square.color || white,
219
+ stroke: "#000000",
220
+ lineWidth: lineWidth,
221
+ crisp: false,
222
+ });
223
+
224
+ // Calculate positions and animation parameters
225
+ const finalX = square.x + square.width / 2;
226
+ const finalY = square.y + square.height / 2;
227
+ const angle = Math.atan2(finalY - centerY, finalX - centerX);
228
+ const flyDistance = maxDimension * 1.5;
229
+ const startX = centerX + Math.cos(angle) * flyDistance;
230
+ const startY = centerY + Math.sin(angle) * flyDistance;
231
+
232
+ // Create game object
233
+ const go = ShapeGOFactory.create(this, rect, {
234
+ x: startX,
235
+ y: startY,
236
+ scaleX: 0.0,
237
+ scaleY: 0.0,
238
+ crisp: false,
239
+ rotation: (Math.random() * 2 - 1) * Math.PI,
240
+ });
241
+
242
+ this.gridScene.add(go);
243
+
244
+ // Calculate animation timing
245
+ const distance = Math.sqrt(
246
+ Math.pow(finalX - centerX, 2) + Math.pow(finalY - centerY, 2)
247
+ );
248
+ const normalizedDistance = Math.min(
249
+ 1,
250
+ distance / (Math.min(this.width, this.height) / 2)
251
+ );
252
+
253
+ const middle = allSquares.length / 2;
254
+ const distanceFromMiddle = Math.abs(i - middle);
255
+ const delay = (distanceFromMiddle / middle) * 0.1;
256
+
257
+ // Animate rectangle flying in
258
+ Tweenetik.to(
259
+ go,
260
+ {
261
+ x: finalX,
262
+ y: finalY,
263
+ scaleX: 1,
264
+ scaleY: 1,
265
+ rotation: 0,
266
+ },
267
+ 0.5 + normalizedDistance * 0.5,
268
+ Easing.easeOutCirc,
269
+ { delay: delay }
270
+ );
271
+ });
272
+ }
273
+
274
+ /**
275
+ * Update function - called each frame
276
+ */
277
+ update(dt) {
278
+ if (this.boundsDirty) {
279
+ this.gridScene.width = this.width - 40;
280
+ this.gridScene.height = this.height - 40;
281
+ }
282
+
283
+ super.update(dt);
284
+ }
285
+ }