@guinetik/gcanvas 1.0.4 → 1.0.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/CNAME +1 -0
- package/dist/animations.html +31 -0
- package/dist/basic.html +38 -0
- package/dist/baskara.html +31 -0
- package/dist/bezier.html +35 -0
- package/dist/beziersignature.html +29 -0
- package/dist/blackhole.html +28 -0
- package/dist/blob.html +35 -0
- package/dist/coordinates.html +698 -0
- package/dist/cube3d.html +23 -0
- package/dist/demos.css +303 -0
- package/dist/dino.html +42 -0
- package/dist/easing.html +28 -0
- package/dist/events.html +195 -0
- package/dist/fluent.html +647 -0
- package/dist/fluid-simple.html +22 -0
- package/dist/fluid.html +37 -0
- package/dist/fractals.html +36 -0
- package/dist/gameobjects.html +626 -0
- package/dist/gcanvas.es.js +517 -0
- package/dist/gcanvas.es.min.js +1 -1
- package/dist/gcanvas.umd.js +1 -1
- package/dist/gcanvas.umd.min.js +1 -1
- package/dist/genart.html +26 -0
- package/dist/gendream.html +26 -0
- package/dist/group.html +36 -0
- package/dist/home.html +587 -0
- package/dist/hyperbolic001.html +23 -0
- package/dist/hyperbolic002.html +23 -0
- package/dist/hyperbolic003.html +23 -0
- package/dist/hyperbolic004.html +23 -0
- package/dist/hyperbolic005.html +22 -0
- package/dist/index.html +398 -0
- package/dist/isometric.html +34 -0
- package/dist/js/animations.js +452 -0
- package/dist/js/basic.js +204 -0
- package/dist/js/baskara.js +751 -0
- package/dist/js/bezier.js +692 -0
- package/dist/js/beziersignature.js +241 -0
- package/dist/js/blackhole/accretiondisk.obj.js +379 -0
- package/dist/js/blackhole/blackhole.obj.js +318 -0
- package/dist/js/blackhole/index.js +409 -0
- package/dist/js/blackhole/particle.js +56 -0
- package/dist/js/blackhole/starfield.obj.js +218 -0
- package/dist/js/blob.js +2276 -0
- package/dist/js/coordinates.js +840 -0
- package/dist/js/cube3d.js +789 -0
- package/dist/js/dino.js +1420 -0
- package/dist/js/easing.js +477 -0
- package/dist/js/fluent.js +183 -0
- package/dist/js/fluid-simple.js +253 -0
- package/dist/js/fluid.js +527 -0
- package/dist/js/fractals.js +932 -0
- package/dist/js/fractalworker.js +93 -0
- package/dist/js/gameobjects.js +176 -0
- package/dist/js/genart.js +268 -0
- package/dist/js/gendream.js +209 -0
- package/dist/js/group.js +140 -0
- package/dist/js/hyperbolic001.js +310 -0
- package/dist/js/hyperbolic002.js +388 -0
- package/dist/js/hyperbolic003.js +319 -0
- package/dist/js/hyperbolic004.js +345 -0
- package/dist/js/hyperbolic005.js +340 -0
- package/dist/js/info-toggle.js +25 -0
- package/dist/js/isometric.js +863 -0
- package/dist/js/kerr.js +1547 -0
- package/dist/js/lavalamp.js +590 -0
- package/dist/js/layout.js +354 -0
- package/dist/js/mondrian.js +285 -0
- package/dist/js/opacity.js +275 -0
- package/dist/js/painter.js +484 -0
- package/dist/js/particles-showcase.js +514 -0
- package/dist/js/particles.js +299 -0
- package/dist/js/patterns.js +397 -0
- package/dist/js/penrose/artifact.js +69 -0
- package/dist/js/penrose/blackhole.js +121 -0
- package/dist/js/penrose/constants.js +73 -0
- package/dist/js/penrose/game.js +943 -0
- package/dist/js/penrose/lore.js +278 -0
- package/dist/js/penrose/penrosescene.js +892 -0
- package/dist/js/penrose/ship.js +216 -0
- package/dist/js/penrose/sounds.js +211 -0
- package/dist/js/penrose/voidparticle.js +55 -0
- package/dist/js/penrose/voidscene.js +258 -0
- package/dist/js/penrose/voidship.js +144 -0
- package/dist/js/penrose/wormhole.js +46 -0
- package/dist/js/pipeline.js +555 -0
- package/dist/js/plane3d.js +256 -0
- package/dist/js/platformer.js +1579 -0
- package/dist/js/scene.js +304 -0
- package/dist/js/scenes.js +320 -0
- package/dist/js/schrodinger.js +410 -0
- package/dist/js/schwarzschild.js +1015 -0
- package/dist/js/shapes.js +628 -0
- package/dist/js/space/alien.js +171 -0
- package/dist/js/space/boom.js +98 -0
- package/dist/js/space/boss.js +353 -0
- package/dist/js/space/buff.js +73 -0
- package/dist/js/space/bullet.js +102 -0
- package/dist/js/space/constants.js +85 -0
- package/dist/js/space/game.js +1884 -0
- package/dist/js/space/hud.js +112 -0
- package/dist/js/space/laserbeam.js +179 -0
- package/dist/js/space/lightning.js +277 -0
- package/dist/js/space/minion.js +192 -0
- package/dist/js/space/missile.js +212 -0
- package/dist/js/space/player.js +430 -0
- package/dist/js/space/powerup.js +90 -0
- package/dist/js/space/starfield.js +58 -0
- package/dist/js/space/starpower.js +90 -0
- package/dist/js/spacetime.js +559 -0
- package/dist/js/sphere3d.js +229 -0
- package/dist/js/sprite.js +473 -0
- package/dist/js/starfaux/config.js +118 -0
- package/dist/js/starfaux/enemy.js +353 -0
- package/dist/js/starfaux/hud.js +78 -0
- package/dist/js/starfaux/index.js +482 -0
- package/dist/js/starfaux/laser.js +182 -0
- package/dist/js/starfaux/player.js +468 -0
- package/dist/js/starfaux/terrain.js +560 -0
- package/dist/js/study001.js +275 -0
- package/dist/js/study002.js +366 -0
- package/dist/js/study003.js +331 -0
- package/dist/js/study004.js +389 -0
- package/dist/js/study005.js +209 -0
- package/dist/js/study006.js +194 -0
- package/dist/js/study007.js +192 -0
- package/dist/js/study008.js +413 -0
- package/dist/js/svgtween.js +204 -0
- package/dist/js/tde/accretiondisk.js +471 -0
- package/dist/js/tde/blackhole.js +219 -0
- package/dist/js/tde/blackholescene.js +209 -0
- package/dist/js/tde/config.js +59 -0
- package/dist/js/tde/index.js +820 -0
- package/dist/js/tde/jets.js +290 -0
- package/dist/js/tde/lensedstarfield.js +154 -0
- package/dist/js/tde/tdestar.js +297 -0
- package/dist/js/tde/tidalstream.js +372 -0
- package/dist/js/tde_old/blackhole.obj.js +354 -0
- package/dist/js/tde_old/debris.obj.js +791 -0
- package/dist/js/tde_old/flare.obj.js +239 -0
- package/dist/js/tde_old/index.js +448 -0
- package/dist/js/tde_old/star.obj.js +812 -0
- package/dist/js/tetris/config.js +157 -0
- package/dist/js/tetris/grid.js +286 -0
- package/dist/js/tetris/index.js +1195 -0
- package/dist/js/tetris/renderer.js +634 -0
- package/dist/js/tetris/tetrominos.js +280 -0
- package/dist/js/tiles.js +312 -0
- package/dist/js/tweendemo.js +79 -0
- package/dist/js/visibility.js +102 -0
- package/dist/kerr.html +28 -0
- package/dist/lavalamp.html +27 -0
- package/dist/layouts.html +37 -0
- package/dist/logo.svg +4 -0
- package/dist/loop.html +84 -0
- package/dist/mondrian.html +32 -0
- package/dist/og_image.png +0 -0
- package/dist/opacity.html +36 -0
- package/dist/painter.html +39 -0
- package/dist/particles-showcase.html +28 -0
- package/dist/particles.html +24 -0
- package/dist/patterns.html +33 -0
- package/dist/penrose-game.html +31 -0
- package/dist/pipeline.html +737 -0
- package/dist/plane3d.html +24 -0
- package/dist/platformer.html +43 -0
- package/dist/scene.html +33 -0
- package/dist/scenes.html +96 -0
- package/dist/schrodinger.html +27 -0
- package/dist/schwarzschild.html +27 -0
- package/dist/shapes.html +16 -0
- package/dist/space.html +85 -0
- package/dist/spacetime.html +27 -0
- package/dist/sphere3d.html +24 -0
- package/dist/sprite.html +18 -0
- package/dist/starfaux.html +22 -0
- package/dist/study001.html +23 -0
- package/dist/study002.html +23 -0
- package/dist/study003.html +23 -0
- package/dist/study004.html +23 -0
- package/dist/study005.html +22 -0
- package/dist/study006.html +24 -0
- package/dist/study007.html +24 -0
- package/dist/study008.html +22 -0
- package/dist/svgtween.html +29 -0
- package/dist/tde.html +28 -0
- package/dist/tetris3d.html +25 -0
- package/dist/tiles.html +28 -0
- package/dist/transforms.html +400 -0
- package/dist/tween.html +45 -0
- package/dist/visibility.html +33 -0
- package/package.json +1 -1
|
@@ -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 "/gcanvas.es.min.js";
|
|
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 "/gcanvas.es.min.js";
|
|
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
|
+
}
|