littlejsengine 1.18.2 → 1.18.7

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 (172) hide show
  1. package/COPYRIGHT.txt +38 -0
  2. package/FAQ.md +633 -0
  3. package/LICENSE +5 -27
  4. package/README.md +19 -1
  5. package/dist/littlejs.d.ts +550 -57
  6. package/dist/littlejs.esm.js +2208 -492
  7. package/dist/littlejs.esm.min.js +1 -1
  8. package/dist/littlejs.js +2165 -480
  9. package/dist/littlejs.min.js +1 -1
  10. package/dist/littlejs.release.js +2166 -481
  11. package/package.json +8 -1
  12. package/plugins/box2d.js +36 -9
  13. package/{src/engineMedals.js → plugins/medalSystem.js} +248 -196
  14. package/plugins/pathFinder.js +758 -0
  15. package/plugins/pluginExport.js +26 -0
  16. package/plugins/tween.js +509 -0
  17. package/plugins/tweenSystem.js +509 -0
  18. package/plugins/uiSystem.js +151 -12
  19. package/src/engine.js +9 -145
  20. package/src/engineAudio.js +8 -5
  21. package/src/engineBuild.mjs +4 -1
  22. package/src/engineDebug.js +1 -1
  23. package/src/engineDraw.js +154 -5
  24. package/src/engineExport.js +17 -12
  25. package/src/engineInput.js +27 -1
  26. package/src/engineLogo.js +146 -0
  27. package/src/engineMath.js +10 -3
  28. package/src/engineObject.js +11 -5
  29. package/src/engineParticles.js +6 -3
  30. package/src/engineRelease.js +1 -1
  31. package/src/engineSettings.js +17 -47
  32. package/src/engineUtilities.js +7 -2
  33. package/src/engineWebGL.js +31 -8
  34. package/src/jsconfig.json +3 -1
  35. package/.claude/settings.local.json +0 -7
  36. package/.github/workflows/test.yml +0 -17
  37. package/AI.md +0 -172
  38. package/CLAUDE.md +0 -1
  39. package/examples/box2d/game.js +0 -192
  40. package/examples/box2d/gameObjects.js +0 -564
  41. package/examples/box2d/index.html +0 -9
  42. package/examples/box2d/scenes.js +0 -194
  43. package/examples/box2d/tiles.png +0 -0
  44. package/examples/breakout/game.js +0 -175
  45. package/examples/breakout/gameObjects.js +0 -147
  46. package/examples/breakout/index.html +0 -8
  47. package/examples/breakout/tiles.png +0 -0
  48. package/examples/breakoutTutorial/README.md +0 -521
  49. package/examples/breakoutTutorial/game.js +0 -191
  50. package/examples/breakoutTutorial/images/1.png +0 -0
  51. package/examples/breakoutTutorial/images/10.png +0 -0
  52. package/examples/breakoutTutorial/images/11.png +0 -0
  53. package/examples/breakoutTutorial/images/2.png +0 -0
  54. package/examples/breakoutTutorial/images/3.png +0 -0
  55. package/examples/breakoutTutorial/images/4.png +0 -0
  56. package/examples/breakoutTutorial/images/5.png +0 -0
  57. package/examples/breakoutTutorial/images/6.png +0 -0
  58. package/examples/breakoutTutorial/images/7.png +0 -0
  59. package/examples/breakoutTutorial/images/8.png +0 -0
  60. package/examples/breakoutTutorial/images/9.png +0 -0
  61. package/examples/breakoutTutorial/index.html +0 -8
  62. package/examples/electron/build.mjs +0 -127
  63. package/examples/electron/electron.js +0 -35
  64. package/examples/electron/game.js +0 -54
  65. package/examples/electron/index.html +0 -13
  66. package/examples/electron/package.json +0 -5
  67. package/examples/electron/tiles.png +0 -0
  68. package/examples/empty/game.js +0 -51
  69. package/examples/empty/index.html +0 -5
  70. package/examples/empty/tiles.png +0 -0
  71. package/examples/favicon.png +0 -0
  72. package/examples/games.jpg +0 -0
  73. package/examples/htmlMenu/game.js +0 -82
  74. package/examples/htmlMenu/index.html +0 -45
  75. package/examples/htmlMenu/tiles.png +0 -0
  76. package/examples/index.html +0 -800
  77. package/examples/logo.png +0 -0
  78. package/examples/logo2.png +0 -0
  79. package/examples/module/build.mjs +0 -124
  80. package/examples/module/game.js +0 -132
  81. package/examples/module/index.html +0 -10
  82. package/examples/module/tiles.png +0 -0
  83. package/examples/particles/index.html +0 -426
  84. package/examples/particles/tiles.png +0 -0
  85. package/examples/platformer/data/gameLevelData.tmx +0 -143
  86. package/examples/platformer/data/gameLevelData.tsx +0 -4
  87. package/examples/platformer/game.js +0 -147
  88. package/examples/platformer/gameCharacter.js +0 -309
  89. package/examples/platformer/gameEffects.js +0 -278
  90. package/examples/platformer/gameLevel.js +0 -205
  91. package/examples/platformer/gameLevelData.json +0 -171
  92. package/examples/platformer/gameObjects.js +0 -378
  93. package/examples/platformer/gamePlayer.js +0 -35
  94. package/examples/platformer/index.html +0 -9
  95. package/examples/platformer/tiles.png +0 -0
  96. package/examples/platformer/tilesLevel.png +0 -0
  97. package/examples/puzzle/game.js +0 -331
  98. package/examples/puzzle/index.html +0 -8
  99. package/examples/puzzle/tiles.png +0 -0
  100. package/examples/screenshot.jpg +0 -0
  101. package/examples/shorts/animation.js +0 -18
  102. package/examples/shorts/base.html +0 -54
  103. package/examples/shorts/blending.js +0 -14
  104. package/examples/shorts/box2d.js +0 -51
  105. package/examples/shorts/box2dCar.js +0 -59
  106. package/examples/shorts/box2dPool.js +0 -107
  107. package/examples/shorts/box2dTileLayer.js +0 -48
  108. package/examples/shorts/cameraDrag.js +0 -22
  109. package/examples/shorts/clock.js +0 -21
  110. package/examples/shorts/colors.js +0 -25
  111. package/examples/shorts/debugDraw.js +0 -37
  112. package/examples/shorts/empty.js +0 -30
  113. package/examples/shorts/flappyGame.js +0 -55
  114. package/examples/shorts/fontImage.js +0 -17
  115. package/examples/shorts/fps.js +0 -90
  116. package/examples/shorts/helloWorld.js +0 -11
  117. package/examples/shorts/hillGlideGame.js +0 -63
  118. package/examples/shorts/input.js +0 -64
  119. package/examples/shorts/landerGame.js +0 -57
  120. package/examples/shorts/maze.js +0 -48
  121. package/examples/shorts/medals.js +0 -51
  122. package/examples/shorts/music.js +0 -78
  123. package/examples/shorts/musicPlayer.js +0 -137
  124. package/examples/shorts/nineSlice.js +0 -40
  125. package/examples/shorts/parallax.js +0 -72
  126. package/examples/shorts/particles.js +0 -28
  127. package/examples/shorts/piano.js +0 -44
  128. package/examples/shorts/platformer.js +0 -45
  129. package/examples/shorts/pongGame.js +0 -41
  130. package/examples/shorts/postProcess.js +0 -62
  131. package/examples/shorts/sequencer.js +0 -124
  132. package/examples/shorts/shader.js +0 -29
  133. package/examples/shorts/shapes.js +0 -21
  134. package/examples/shorts/slidingPuzzle.js +0 -52
  135. package/examples/shorts/song.mp3 +0 -0
  136. package/examples/shorts/sound.js +0 -36
  137. package/examples/shorts/spaceGame.js +0 -58
  138. package/examples/shorts/spriteAtlas.js +0 -31
  139. package/examples/shorts/starfield.js +0 -15
  140. package/examples/shorts/texture.js +0 -16
  141. package/examples/shorts/tileLayer.js +0 -48
  142. package/examples/shorts/tileRaycast.js +0 -39
  143. package/examples/shorts/tiles.png +0 -0
  144. package/examples/shorts/tiltedView.js +0 -63
  145. package/examples/shorts/timers.js +0 -53
  146. package/examples/shorts/topDown.js +0 -38
  147. package/examples/shorts/uiSystem.js +0 -61
  148. package/examples/shorts/video.webm +0 -0
  149. package/examples/shorts/videoPlayer.js +0 -34
  150. package/examples/starter/build.bat +0 -7
  151. package/examples/starter/build.mjs +0 -126
  152. package/examples/starter/game.js +0 -137
  153. package/examples/starter/index.html +0 -35
  154. package/examples/starter/tiles.png +0 -0
  155. package/examples/stress/index.html +0 -173
  156. package/examples/style.css +0 -150
  157. package/examples/typescript/build.mjs +0 -60
  158. package/examples/typescript/game.js +0 -100
  159. package/examples/typescript/game.ts +0 -132
  160. package/examples/typescript/index.html +0 -10
  161. package/examples/typescript/tiles.png +0 -0
  162. package/examples/typescript/tsconfig.json +0 -17
  163. package/examples/uiSystem/game.js +0 -139
  164. package/examples/uiSystem/index.html +0 -10
  165. package/examples/uiSystem/tiles.png +0 -0
  166. package/jsconfig.json +0 -12
  167. package/plugins/desktop.ini +0 -2
  168. package/reference.md +0 -447
  169. package/test/math.test.mjs +0 -774
  170. package/test/setup.mjs +0 -22
  171. package/test/smoke.test.mjs +0 -258
  172. package/test/util.test.mjs +0 -80
@@ -1,800 +0,0 @@
1
- <!DOCTYPE html><head>
2
- <title>LittleJS Example Browser</title>
3
- <link rel=icon type=image/png href=favicon.png>
4
- <link rel=stylesheet href=style.css?9>
5
- <meta charset=utf-8>
6
- <meta name='viewport' content='width=device-width, initial-scale=1.0'>
7
- <!-- meta tags for social media -->
8
- <meta name='twitter:title' content='LittleJS Example Browser'>
9
- <meta property='og:title' content='LittleJS Example Browser'>
10
- <meta name='description' content='Tiny fast HTML5 game engine!'>
11
- <meta name='twitter:description' content='Tiny fast HTML5 game engine!'>
12
- <meta property='og:description' content='Tiny fast HTML5 game engine!'>
13
- <meta name='twitter:image' content='https://3d2k.com/images/LittleJSExamples.png'>
14
- <meta property='og:image' content='https://3d2k.com/images/LittleJSExamples.png'>
15
- <meta name='keywords' content='JavaScript, HTML5, Game, Engine'>
16
- <meta name='twitter:creator' content='@KilledByAPixel'>
17
- <meta name='twitter:card' content='summary_large_image'>
18
- </head><body>
19
- <div id=container1>
20
- <div id=container3>
21
- <p id=titleInfo>LittleJS Example Browser</p>
22
- <p id=exampleInfo></p>
23
- <a id=exampleLink target='_blank'></a>
24
- <div id=divCodeOptions>
25
- <hr>
26
- Theme:
27
- <select style=width:120px onchange=loadTheme() id=selectTheme></select>
28
- <select style=width:40px onchange=loadTheme() id=selectFontSize>
29
- <option value=10px>XS</option>
30
- <option value=13px>S</option>
31
- <option value=16px selected>M</option>
32
- <option value=20px>L</option>
33
- <option value=24px>XL</option>
34
- </select>
35
- <span class=nowrap><input type=checkbox id=checkboxLiveEdit onchange=writeSaveData() checked>Live Edit</span>
36
- </div>
37
- <div id=divTextareas>
38
- <textarea id=textareaCode oninput=codeInput() spellcheck=false></textarea>
39
- <textarea id=textareaError readonly spellcheck=false></textarea>
40
- <textarea id=textareaConsole readonly spellcheck=false></textarea>
41
- </div>
42
- </div>
43
- <div id=container2>
44
- <div style='display:flex;justify-content:center'>
45
- <div id=iframeContainer></div>
46
- </div>
47
- <div>
48
- <input id=inputSearch placeholder='Search examples...' oninput=filterExamples() onkeydown='if(event.key===`Escape`)filterExamples(1)'>
49
- <button id=buttonRestart onclick=restartCode()>Restart</button>
50
- <span id=container4>
51
- <button id=buttonPause>Pause</button>
52
- <button id=buttonFullscreen>Fullscreen</button>
53
- <button id=buttonScreenshot>Screenshot</button>
54
- <span class=nowrap><input type=checkbox id=checkboxWebGL checked>WebGL</span>
55
- </span>
56
- </div>
57
- <span id=selectExampleText class=nowrap>Select an example...</span>
58
- <select id=selectExample onchange=setExample() size=2></select>
59
- </div>
60
- </div>
61
- <a href="https://github.com/KilledByAPixel/LittleJS" class="github-corner" aria-label="View source on GitHub"><svg width="80" height="80" viewBox="0 0 250 250" style="fill:#70B7FD; color:#fff; position: absolute; top: 0; border: 0; left: 0; transform: scale(-1, 1);" aria-hidden="true"><path d="M0,0 L115,115 L130,115 L142,142 L250,250 L250,0 Z"/><path d="M128.3,109.0 C113.8,99.7 119.0,89.6 119.0,89.6 C122.0,82.7 120.5,78.6 120.5,78.6 C119.2,72.0 123.4,76.3 123.4,76.3 C127.3,80.9 125.5,87.3 125.5,87.3 C122.9,97.6 130.6,101.9 134.4,103.2" fill="currentColor" style="transform-origin: 130px 106px;" class="octo-arm"/><path d="M115.0,115.0 C114.9,115.1 118.7,116.5 119.8,115.4 L133.7,101.6 C136.9,99.2 139.9,98.4 142.2,98.6 C133.8,88.0 127.5,74.4 143.8,58.0 C148.5,53.4 154.0,51.2 159.7,51.0 C160.3,49.4 163.2,43.6 171.4,40.1 C171.4,40.1 176.1,42.5 178.8,56.2 C183.1,58.6 187.2,61.8 190.9,65.4 C194.5,69.0 197.7,73.2 200.1,77.6 C213.8,80.2 216.3,84.9 216.3,84.9 C212.7,93.1 206.9,96.0 205.4,96.6 C205.1,102.4 203.0,107.8 198.3,112.5 C181.9,128.9 168.3,122.5 157.7,114.1 C157.9,116.9 156.7,120.9 152.7,124.9 L141.0,136.5 C139.8,137.7 141.6,141.9 141.8,141.8 Z" fill="currentColor" class="octo-body"/></svg></a><style>.github-corner:hover .octo-arm{animation:octocat-wave 560ms ease-in-out}@keyframes octocat-wave{0%,100%{transform:rotate(0)}20%,60%{transform:rotate(-25deg)}40%,80%{transform:rotate(10deg)}}@media (max-width:500px){.github-corner:hover .octo-arm{animation:none}.github-corner .octo-arm{animation:octocat-wave 560ms ease-in-out}}</style>
62
- <script>
63
- 'use strict';
64
-
65
- class ExampleInfo
66
- {
67
- constructor(name, filename, description='', largeExample=false, tags='')
68
- {
69
- if (filename && !largeExample)
70
- filename = 'shorts/' + filename;
71
- this.name = name;
72
- this.filename = filename;
73
- this.description = description;
74
- this.largeExample = largeExample;
75
- this.tags = tags;
76
- this.isHeading = !filename;
77
- this.text = this.name;
78
- if (this.description)
79
- this.text += ' - ' + this.description;
80
- this.selectText = this.text;
81
- if (this.tags)
82
- this.selectText += ' (' + this.tags + ')';
83
- }
84
- }
85
-
86
- const exampleList =
87
- [
88
- new ExampleInfo('--- BASIC ---'),
89
- new ExampleInfo('Hello World', 'helloWorld.js', 'Simple starter example', false, 'beginner, gradient, text, tiles'),
90
- new ExampleInfo('Empty', 'empty.js', 'Empty example template', false, 'beginner, text'),
91
- new ExampleInfo('Texture', 'texture.js', 'Texture display and manipulation', false, 'sprites, loading, tiles'),
92
- new ExampleInfo('Animation', 'animation.js', 'Sprite animation system', false, 'frames, loop, tiles, sprites'),
93
- new ExampleInfo('Shapes', 'shapes.js', 'Draw geometric shapes and primitives', false, 'circle, ellipse, rectangle, polygon, lines'),
94
- new ExampleInfo('Colors', 'colors.js', 'Color manipulation and HSL', false, 'hue, saturation, blending, rectangle'),
95
- new ExampleInfo('Sprite Atlas', 'spriteAtlas.js', 'Sprite atlas and tile rendering', false, 'sheet, frames, tiles'),
96
- new ExampleInfo('Blending', 'blending.js', 'Additive blending and transparency', false, 'alpha, color, tiles, smooth'),
97
- new ExampleInfo('Tile Layer', 'tileLayer.js', 'Tile layer rendering system', false, 'level, map, grid, particles'),
98
- new ExampleInfo('Particles', 'particles.js', 'Particle system', false, 'effects, emitter, physics, fire, smoke'),
99
- new ExampleInfo('Input', 'input.js', 'Input system demo for keyboard, mouse, touch, and gamepad.', false, 'input, control'),
100
- new ExampleInfo('Timers', 'timers.js', 'Timer objects and UI', false, 'delay, interval, slider'),
101
- new ExampleInfo('Sound Effects', 'sound.js', 'ZzFX sound effect generator', false, 'audio, volume, ui'),
102
- new ExampleInfo('Music', 'music.js', 'Basic load, play, pause, and stop', false, 'music, sound, audio, streaming, volume, ui'),
103
- new ExampleInfo('Video', 'videoPlayer.js', 'Basic video play, pause, and stop', false, 'movie, sound, audio, streaming, volume, ui'),
104
- new ExampleInfo('Font Image', 'fontImage.js', 'Bitmap font system with built-in system font', false, 'text, characters'),
105
- new ExampleInfo('Medals', 'medals.js', 'Achievement system', false, 'unlock, progress, newgrounds'),
106
- new ExampleInfo('Tile Raycast', 'tileRaycast.js', 'Example of the tile layer raycast collision', false, 'level, map, grid'),
107
- new ExampleInfo('Camera Mouse Drag', 'cameraDrag.js', 'Example of how to control camera with mouse', false, 'ui, input, control'),
108
- new ExampleInfo('Debug Drawing', 'debugDraw.js', 'Debug drawing system', false, 'debug, circle, line, rectangle'),
109
- new ExampleInfo('--- ADVANCED ---'),
110
- new ExampleInfo('Clock', 'clock.js', 'Animated analog clock', false, 'time, rotation, lines, rectangle'),
111
- new ExampleInfo('Starfield', 'starfield.js', 'Animated parallax starfield', false, 'space, movement, depth, rectangle'),
112
- new ExampleInfo('Parallax', 'parallax.js', 'Parallax scrolling mountains', false, 'generative, canvas, background'),
113
- new ExampleInfo('Maze Generator', 'maze.js', 'Procedural maze generation', false, 'generative, level, tiles, map, grid'),
114
- new ExampleInfo('Piano', 'piano.js', 'Interactive piano keyboard', false, 'music, sound, audio, notes, ui, instrument'),
115
- new ExampleInfo('Step Sequencer', 'sequencer.js', 'Simple music loop creation tool', false, 'music, sound, audio, notes, ui, instrument'),
116
- new ExampleInfo('Music Player', 'musicPlayer.js', 'Music player with audio seeking and drag and drop', false, 'sound, loading, audio, ui'),
117
- new ExampleInfo('--- GAMES ---'),
118
- new ExampleInfo('Pong Game', 'pongGame.js', 'Classic paddle ball bouncing', false, 'objects, collision'),
119
- new ExampleInfo('Platformer Game', 'platformer.js', 'Jump and run side view', false, 'objects, gravity, level, tiles, camera'),
120
- new ExampleInfo('Top Down Game', 'topDown.js', 'Top-down style camera', false, 'objects, movement, exploration'),
121
- new ExampleInfo('Tilted View Game', 'tiltedView.js', 'Pseudo-3D oblique view', false, 'isometric, depth'),
122
- new ExampleInfo('Flappy Game', 'flappyGame.js', 'Flappy bird style game', false, 'objects, obstacles'),
123
- new ExampleInfo('Lander Game', 'landerGame.js', 'Lunar lander style game', false, 'objects, physics'),
124
- new ExampleInfo('Hill Glide Game', 'hillGlideGame.js', 'Tiny wings style sliding game', false, 'objects, physics, speed'),
125
- new ExampleInfo('Sliding Puzzle', 'slidingPuzzle.js', '15 tile sliding puzzle', false, 'objects, numbers, ui'),
126
- new ExampleInfo('Space Game', 'spaceGame.js', 'Spaceship shooter with parallax', false, 'objects, weapons, stars, camera, rotation'),
127
- new ExampleInfo('3D FPS Game', 'fps.js', 'Pseudo 3D raycasting demo', false, '3D, FPS, maze, camera'),
128
- new ExampleInfo('--- PLUGINS ---'),
129
- new ExampleInfo('Box2d Demo', 'box2d.js', 'Box2D physics plugin', false, 'objects, mouse'),
130
- new ExampleInfo('Box2d Car', 'box2dCar.js', 'Drivable car with Box2D physics', false, 'objects, vehicle, suspension, wheels'),
131
- new ExampleInfo('Box2d Pool', 'box2dPool.js', 'Billiard table pool game with Box2d physics', false, 'objects, game'),
132
- new ExampleInfo('Box2d Tile Layer', 'box2dTileLayer.js', 'Tile layer with Box2d physics', false, 'objects, level, map, grid'),
133
- new ExampleInfo('WebGL Shader', 'shader.js', 'Full canvas webgl shader', false, 'webgl, visual, effect'),
134
- new ExampleInfo('Post Processing', 'postProcess.js', 'Shader effects and filters', false, 'webgl, visual, effect'),
135
- new ExampleInfo('UI System', 'uiSystem.js', 'Buttons, sliders, and checkboxes', false, 'objects, widgets, interactive'),
136
- new ExampleInfo('Nine Slice', 'nineSlice.js', 'Scalable UI panels', false, 'three slice, stretch, corners, text, tiles'),
137
- new ExampleInfo('--- FULL EXAMPLES ---'),
138
- new ExampleInfo('Starter', 'starter', 'Clean project template', true, 'base, empty, particles'),
139
- new ExampleInfo('Breakout Tutorial', 'breakoutTutorial', 'Step-by-step breakout game tutorial', true, 'objects, physics, score'),
140
- new ExampleInfo('Breakout Game', 'breakout', 'Complete breakout game', true, 'objects, physics, score'),
141
- new ExampleInfo('Platforming Game', 'platformer', 'Platformer with level loading', true, 'jump, world, tiles, pixel art, sprites'),
142
- new ExampleInfo('Puzzle Game', 'puzzle', 'Match 3 style puzzle game', true, 'match, swap, sprites'),
143
- new ExampleInfo('Stress Test', 'stress', 'Performance and music test', true, 'optimization, tiles, sprites'),
144
- new ExampleInfo('Box2D Plugin', 'box2d', 'Full Box2D physics demo', true, 'objects, bodies, joints'),
145
- new ExampleInfo('HTML Menus', 'htmlMenu', 'HTML UI integration', true, 'web, browser, overlay, button, slider, textbox'),
146
- new ExampleInfo('UI System Plugin Demo', 'uiSystem', 'Complete UI system demo', true, 'menu, overlay, button, slider, checkbox'),
147
- ];
148
-
149
- ///////////////////////////////////////////////////////////////////////////////
150
-
151
- // global variables
152
- let iframeExample; // iframe of the current loaded example
153
- let inputTimeout; // timeout to debounce input
154
- let consoleAutoScroll; // allow console to scroll automatically
155
-
156
- function initExampleBrowser()
157
- {
158
- // load the examples into the list
159
- filterExamples();
160
-
161
- // set the selected example from the URL parameters
162
- const urlParams = new URLSearchParams(window.location.search);
163
- const selectedExample = urlParams.get('example') || '';
164
- let exampleIndex = exampleList.findIndex((example)=> example.name === selectedExample);
165
- if (exampleIndex < 0)
166
- exampleIndex = 1;
167
- selectExample.selectedIndex = exampleIndex;
168
- setExample();
169
-
170
- // apply responsive layout
171
- addEventListener('resize', resizeWindow);
172
- resizeWindow();
173
- }
174
-
175
- function resizeWindow()
176
- {
177
- // tweak layout for touch devices
178
- const isTouchDevice = window.ontouchstart !== undefined;
179
- if (isTouchDevice)
180
- container4.style.display = 'none';
181
- else
182
- selectExampleText.style.display = 'none';
183
-
184
- const windowAspect = innerWidth / innerHeight;
185
- const verticalLayout = windowAspect < 1;
186
- if (verticalLayout)
187
- {
188
- // vertical layout for thin screens
189
- if (container2.parentNode != container3)
190
- container3.insertBefore(container2, divCodeOptions);
191
- // resize iframe to the window
192
- setFrameSize(innerWidth-35);
193
-
194
- // fix code mirror sizing glitch
195
- codeMirror && codeMirror.setSize(innerWidth-35, null);
196
- }
197
- else
198
- {
199
- // horizontal layout for wide screens
200
- if (container2.parentNode != container1)
201
- container1.appendChild(container2);
202
-
203
- // show full controls
204
- container4.style.display = '';
205
-
206
- // resize iframe to fit half the window
207
- setFrameSize(innerWidth/2);
208
-
209
- // fix code mirror sizing glitch
210
- codeMirror && codeMirror.setSize(innerWidth/2 - 35, null);
211
- }
212
-
213
- function setFrameSize(w)
214
- {
215
- const aspect = 16 / 9; // HD aspect ratio
216
- w = w | 0;
217
- const h = w / aspect | 0;
218
- iframeContainer.style.width = w + 'px';
219
- iframeContainer.style.height = h + 'px';
220
-
221
- if (iframeExample)
222
- {
223
- // ensure iframe fills container tightly
224
- iframeExample.style.width = w + 'px';
225
- iframeExample.style.height = h + 'px';
226
- }
227
-
228
- // fix code mirror after layout changes
229
- if (codeMirror)
230
- {
231
- // fix glitch with code mirror sizing
232
- setTimeout(()=>codeMirror.refresh(), 500);
233
- }
234
- }
235
- }
236
-
237
- ///////////////////////////////////////////////////////////////////////////////
238
- // setting examples
239
-
240
- function setExample()
241
- {
242
- // get the original index if this is a filtered result
243
- const selectedOption = selectExample.options[selectExample.selectedIndex];
244
- let exampleIndex = selectedOption && selectedOption.originalIndex ?
245
- parseInt(selectedOption.originalIndex) :
246
- selectExample.selectedIndex;
247
-
248
- // make sure we have a valid example
249
- if (exampleIndex < 0 || exampleIndex >= exampleList.length)
250
- exampleIndex = 1; // reset to default
251
- if (!exampleList[exampleIndex].filename)
252
- exampleIndex = 1;
253
-
254
- // load the example
255
- const example = exampleList[exampleIndex];
256
- exampleInfo.innerText = example.text;
257
- const filename = 'examples/' + example.filename;
258
- exampleLink.href = 'https://github.com/KilledByAPixel/LittleJS/tree/main/' + filename;
259
- exampleLink.innerText = 'View on GitHub: ' + example.filename;
260
-
261
- // update URL parameter
262
- const url = new URL(window.location);
263
- url.searchParams.set('example', example.name);
264
- window.history.replaceState({}, '', url);
265
-
266
- loadFile(example.filename, example.largeExample);
267
- }
268
-
269
- async function loadFile(filename, largeExample)
270
- {
271
- if (codeMirror)
272
- codeMirror.setOption('readOnly', largeExample);
273
- else
274
- textareaCode.disabled = largeExample;
275
-
276
- // disable buttons in large example mode
277
- setFrameControlsEnabled(!largeExample);
278
-
279
- if (largeExample)
280
- {
281
-
282
- // Show message in code view that full examples can't be edited
283
- const text = 'Code view not available for large examples.';
284
- setCode(text, filename);
285
- codeIsJS = false;
286
- if (codeMirror)
287
- {
288
- codeMirror.off('change', codeInput);
289
- codeMirror.setOption('mode', 'text');
290
- codeMirror.setValue(text);
291
- }
292
- else
293
- textareaCode.value = text;
294
- clearTimeout(inputTimeout);
295
- return;
296
- }
297
-
298
- try
299
- {
300
- const response = await fetch(filename);
301
- if (!response.ok)
302
- throw new Error('Could not load file: ' + filename);
303
- const text = await response.text();
304
-
305
- // set the code in both code mirror and textarea
306
- codeIsJS = true;
307
- if (codeMirror)
308
- {
309
- codeMirror.on('change', codeInput);
310
- codeMirror.setOption('mode', 'javascript');
311
- codeMirror.setValue(text);
312
- }
313
- else
314
- textareaCode.value = text;
315
- clearTimeout(inputTimeout);
316
- setCode(text);
317
- }
318
- catch (error)
319
- {
320
- setErrorMessage(error.message);
321
- }
322
- }
323
-
324
- function filterExamples(reset=0)
325
- {
326
- if (reset)
327
- inputSearch.value = '';
328
-
329
- // clear current options
330
- selectExample.options.length = 0;
331
-
332
- // filter and add matching examples
333
- const searchTerm = inputSearch.value.toLowerCase().trim();
334
-
335
- // first pass: find which examples match
336
- const matchingIndices = [];
337
- for (let i = 0; i < exampleList.length; i++)
338
- {
339
- const example = exampleList[i];
340
- if (!example.filename)
341
- continue;
342
-
343
- // Check if search term matches name, description, or tags
344
- if (example.name.toLowerCase().includes(searchTerm) ||
345
- example.filename.toLowerCase().includes(searchTerm) ||
346
- example.description.toLowerCase().includes(searchTerm) ||
347
- example.tags.toLowerCase().includes(searchTerm))
348
- matchingIndices.push(i);
349
- }
350
-
351
- // second pass: add headings and matching examples
352
- let lastHeadingIndex = -1;
353
- for (let i = 0; i < exampleList.length; i++)
354
- {
355
- const example = exampleList[i];
356
- if (example.isHeading)
357
- {
358
- // check if there are any matches between this and the next
359
- for (let j = i + 1; j < exampleList.length; j++)
360
- {
361
- if (exampleList[j].isHeading)
362
- break; // hit next heading
363
- if (matchingIndices.includes(j))
364
- {
365
- // only add heading if there are matches under it
366
- lastHeadingIndex = i;
367
- const o = new Option(example.text);
368
- o.disabled = true;
369
- selectExample.add(o);
370
- break;
371
- }
372
- }
373
- }
374
- else if (matchingIndices.includes(i))
375
- {
376
- // add matching example
377
- const o = new Option(example.selectText);
378
- o.originalIndex = i;
379
- selectExample.add(o);
380
- }
381
- }
382
-
383
- if (!selectExample.options.length)
384
- {
385
- // show a message if no matches were found
386
- const o = new Option(`No examples found matching '${searchTerm}'`);
387
- o.disabled = true;
388
- selectExample.add(o);
389
- }
390
- }
391
-
392
- ///////////////////////////////////////////////////////////////////////////////
393
- // setting code
394
-
395
- function codeInput()
396
- {
397
- if (!checkboxLiveEdit.checked)
398
- return;
399
-
400
- // debounce input - get content from code mirror if available, otherwise from textarea
401
- clearTimeout(inputTimeout);
402
- const code = codeMirror ? codeMirror.getValue() : textareaCode.value;
403
- inputTimeout = setTimeout(()=> setCode(code), 500);
404
- }
405
-
406
- function restartCode()
407
- {
408
- // manually restart/run the code regardless of live edit setting
409
- const code = codeMirror ? codeMirror.getValue() : textareaCode.value;
410
- setCode(code);
411
- }
412
-
413
- function setFrameControlsEnabled(enabled=true)
414
- {
415
- buttonPause.disabled = !enabled;
416
- buttonRestart.disabled = !enabled;
417
- buttonScreenshot.disabled = !enabled;
418
- buttonFullscreen.disabled = !enabled;
419
- checkboxWebGL.disabled = !enabled;
420
- }
421
-
422
- function setCode(code, filename)
423
- {
424
- const largeExample = !!filename;
425
- filename = filename || 'shorts/base.html';
426
-
427
- clearTimeout(inputTimeout);
428
- if (iframeExample)
429
- iframeContainer.removeChild(iframeExample);
430
-
431
- unsetErrorMessage();
432
- unsetConsoleMessage();
433
- iframeExample = document.createElement('iframe');
434
- iframeContainer.appendChild(iframeExample);
435
-
436
- iframeExample.onload = ()=>
437
- {
438
- if (largeExample)
439
- return;
440
-
441
- // get the iframe content window and document
442
- const iframeContent = iframeExample.contentWindow;
443
- const iframeDocument = iframeContent.document;
444
- if (!iframeContent.engineInit)
445
- {
446
- setErrorMessage(`Failed to load ${filename}`);
447
- return;
448
- }
449
-
450
- // intercept errors
451
- function getErrorLine(stack)
452
- {
453
- // try to extract line number from stack trace
454
- // look for <anonymous> or injectedScript to find user code
455
- const anonymousMatch = stack?.match(/(<anonymous>|injectedScript):(\d+)/);
456
- return anonymousMatch ? parseInt(anonymousMatch[2]) : -1;
457
- }
458
- iframeContent.onerror = (message, source, lineno, colno, error)=>
459
- {
460
- let text = message;
461
- if (lineno)
462
- text += ` (Line:${lineno}, Column:${colno})`
463
- if (error && error.stack)
464
- text += `\n` + error.stack;
465
- setErrorMessage(text);
466
- setErrorLine(lineno);
467
- }
468
- iframeContent.onunhandledrejection = (event)=>
469
- {
470
- let text = event.reason;
471
- setErrorMessage(text);
472
- if (event.reason && event.reason.stack)
473
- {
474
- text = event.reason.stack;
475
- const errorLine = getErrorLine(event.reason.stack);
476
- if (errorLine >= 0)
477
- setErrorLine(errorLine);
478
- }
479
- };
480
-
481
- // intercept asserts
482
- const originalAssert = iframeContent.console.assert;
483
- iframeContent.console.assert = function (condition, ...output)
484
- {
485
- if (!condition)
486
- {
487
- const stack = (new Error).stack;
488
- const errorLine = getErrorLine(stack);
489
- if (errorLine >= 0)
490
- setErrorLine(errorLine);
491
-
492
- // format output parameters properly
493
- const outputMessage = output.length > 0 ? output.map(m => stringifyMessage(m)).join(' ') : '';
494
- setErrorMessage('Assertion failed!\n' + outputMessage + '\n' + stack);
495
- }
496
- originalAssert.apply(this, arguments);
497
- };
498
-
499
- // intercept console prints
500
- const originalConsole = iframeContent.console;
501
- function interceptConsole(method)
502
- {
503
- const original = originalConsole[method];
504
- iframeContent.console[method] = function(...args)
505
- {
506
- const message = args.map(m=>stringifyMessage(m)).join('\n');
507
- setConsoleMessage(message);
508
- original.apply(originalConsole, args);
509
- };
510
- }
511
- ['log','info','warn','error','debug'].forEach(f=>interceptConsole(f));
512
-
513
- {
514
- // hook up buttons
515
- buttonScreenshot.onclick = ()=>
516
- {
517
- if (iframeContent.debugScreenshot)
518
- iframeContent.debugScreenshot();
519
- }
520
-
521
- // pause/resume functionality
522
- buttonPause.onclick = ()=>
523
- {
524
- if (!iframeContent.getPaused || !iframeContent.setPaused)
525
- return;
526
-
527
- const paused = !iframeContent.getPaused();
528
- iframeContent.setPaused(paused);
529
- buttonPause.textContent = paused ? 'Resume' : 'Pause';
530
- }
531
- buttonPause.textContent = 'Pause';
532
-
533
- // fullscreen functionality
534
- buttonFullscreen.onclick = ()=> iframeContent.toggleFullscreen();
535
- }
536
-
537
- // create a script element that overrides the default functions
538
- const overrideScript = iframeDocument.createElement('script');
539
- iframeDocument.body.appendChild(overrideScript);
540
- overrideScript.text = code;
541
-
542
- if (textareaError.style.display === 'block')
543
- return;
544
-
545
- // start LittleJS engine
546
- iframeContent.engineInit(iframeContent.gameInit, iframeContent.gameUpdate, iframeContent.gameUpdatePost, iframeContent.gameRender, iframeContent.gameRenderPost, ['tiles.png?'+Date.now()])
547
- .catch(error =>
548
- {
549
- const errorLine = error ? getErrorLine(error.stack) : -1;
550
- if (errorLine >= 0)
551
- setErrorLine(errorLine);
552
- let message = error;
553
- if (error && error.message)
554
- message = error.message;
555
- if (error && error.stack)
556
- message += '\n' + error.stack;
557
- setErrorMessage(message);
558
- throw error;
559
- })
560
- .then(()=>
561
- {
562
- // setup frame controls
563
- setFrameControlsEnabled();
564
- if (iframeContent.glCanEnable())
565
- iframeContent.setGLEnable(checkboxWebGL.checked);
566
- else
567
- checkboxWebGL.disabled = true;
568
- checkboxWebGL.onchange = ()=> iframeContent.setGLEnable(checkboxWebGL.checked);
569
- })
570
- }
571
-
572
- iframeExample.src = filename + '?' + Date.now();
573
- }
574
-
575
- ///////////////////////////////////////////////////////////////////////////////
576
- // error and console messages
577
-
578
- function stringifyMessage(message)
579
- {
580
- // make sure message is a string
581
- if (message === null)
582
- return 'null';
583
- if (Number.isNaN(message))
584
- return 'NaN';
585
- if (message === undefined)
586
- return 'undefined';
587
- if (message === 0)
588
- return '0';
589
- if (message === false)
590
- return 'false';
591
- return message;
592
- }
593
-
594
- function setMessage(message, element, clear=true)
595
- {
596
- message = stringifyMessage(message);
597
- if (clear || !element.value)
598
- element.value = message;
599
- else
600
- element.value += '\n' + message;
601
- element.style.display = message ? 'block' : '';
602
-
603
- if (element === textareaConsole)
604
- {
605
- const maxConsoleLines = 100;
606
- const lines = element.value.split('\n');
607
- if (lines.length > maxConsoleLines)
608
- {
609
- // limit max lines, prevents slowdown from too many lines
610
- const excessLines = lines.length - maxConsoleLines;
611
- element.value = lines.slice(excessLines).join('\n');
612
- }
613
-
614
- // auto scroll to bottom only if user hasn't manually scrolled
615
- if (consoleAutoScroll)
616
- element.scrollTop = element.scrollHeight;
617
- }
618
- }
619
-
620
- function unsetMessage(element)
621
- {
622
- element.style.display = '';
623
- element.value = '';
624
- }
625
-
626
- function clearErrorLine()
627
- {
628
- if (!codeMirror || !errorLineMarker)
629
- return;
630
- codeMirror.getDoc().removeLineClass(errorLineMarker, 'background', 'error-line');
631
- }
632
-
633
- function setErrorLine(lineNumber)
634
- {
635
- if (!lineNumber || lineNumber <= 0)
636
- return;
637
-
638
- if (codeMirror)
639
- {
640
- clearErrorLine();
641
- --lineNumber; // codeMirror uses 0-based line numbers
642
- errorLineMarker = codeMirror.getDoc().addLineClass(lineNumber, 'background', 'error-line');
643
- }
644
- }
645
-
646
- function setErrorMessage(message)
647
- {
648
- // prevent overwriting an existing error message
649
- const errorMessageIsVisible = textareaError.style.display === 'block';
650
- setFrameControlsEnabled(false);
651
- if (!errorMessageIsVisible)
652
- setMessage(message, textareaError);
653
- }
654
- function unsetErrorMessage() { unsetMessage(textareaError); clearErrorLine(); }
655
- function setConsoleMessage(message) { setMessage(message, textareaConsole, false); }
656
- function unsetConsoleMessage() { unsetMessage(textareaConsole); consoleAutoScroll = true; }
657
- function onScrollConsole()
658
- {
659
- // only auto scroll console is it is scrolled to bottom
660
- const tolerance = 5; // pixels of tolerance
661
- consoleAutoScroll = Math.abs(textareaConsole.scrollTop - (textareaConsole.scrollHeight - textareaConsole.clientHeight)) < tolerance;
662
- }
663
- textareaConsole.addEventListener('scroll', onScrollConsole);
664
-
665
- ///////////////////////////////////////////////////////////////////////////////
666
- // load saved preferences from localStorage
667
- const saveName = 'LittleJSExamples';
668
- let savedTheme;
669
-
670
- function readSaveData()
671
- {
672
- // load saved preferences
673
- const defaultTheme = 'littlejs';
674
- const defaultFontSize = '16px';
675
- const saveDataJSON = localStorage.getItem(saveName);
676
- const saveData = saveDataJSON ? JSON.parse(saveDataJSON) : {};
677
- selectTheme.value = savedTheme = saveData.theme ?? defaultTheme;
678
- selectFontSize.value = saveData.fontSize ?? defaultFontSize;
679
- }
680
- readSaveData();
681
-
682
- function writeSaveData()
683
- {
684
- // Save preferences to localStorage
685
- const theme = selectTheme.value;
686
- const fontSize = selectFontSize.value;
687
- const saveData =
688
- {
689
- theme,
690
- fontSize
691
- };
692
- const saveDataJSON = JSON.stringify(saveData);
693
- localStorage.setItem(saveName, saveDataJSON);
694
- }
695
-
696
- function resetDefaults()
697
- {
698
- localStorage.removeItem(saveName);
699
- readSaveData();
700
- loadTheme();
701
- }
702
-
703
- ///////////////////////////////////////////////////////////////////////////////
704
- // setup code mirror
705
-
706
- const useCodeMirror = true;
707
- let codeMirror; // code mirror instance
708
- let errorLineMarker; // marker for error line in code mirror
709
- let codeIsJS; // is the current code javascript
710
-
711
- const themes =
712
- [
713
- 'littlejs',
714
- '3024-night',
715
- 'abcdef',
716
- 'ambiance',
717
- 'blackboard',
718
- 'monokai',
719
- 'duotone-light',
720
- 'icecoder',
721
- 'lesser-dark',
722
- 'night',
723
- 'yonce'
724
- ];
725
-
726
- // load theme and font size
727
- for (const theme of themes)
728
- {
729
- if (theme != 'littlejs')
730
- addCodeMirrorElement(`theme/${theme}.min.css`, 'link', 'stylesheet');
731
- const o = new Option(theme);
732
- o.selected = theme === savedTheme;
733
- selectTheme.add(o);
734
- }
735
-
736
- if (useCodeMirror)
737
- {
738
- addCodeMirrorElement('codemirror.min.js', 'script').onload = ()=>
739
- addCodeMirrorElement('codemirror.min.css', 'link', 'stylesheet').onload = ()=>
740
- addCodeMirrorElement('addon/edit/matchbrackets.js', 'script').onload = ()=>
741
- addCodeMirrorElement('mode/javascript/javascript.min.js', 'script').onload = ()=>
742
- {
743
- if (codeMirror)
744
- return; // prevent duplicate initialization
745
-
746
- const textareaCode = document.getElementById('textareaCode');
747
- codeMirror = CodeMirror.fromTextArea(textareaCode,
748
- {
749
- theme: savedTheme,
750
- indentUnit: 4,
751
- mode: codeIsJS ? 'javascript' : 'text',
752
- lineNumbers: true,
753
- lineWrapping: true,
754
- matchBrackets: true,
755
- });
756
- codeMirror.on('change', codeInput);
757
- loadTheme();
758
- resizeWindow(); // fix cursor positioning issue on startup
759
- }
760
- }
761
-
762
- function addCodeMirrorElement(filename, type, rel)
763
- {
764
- // add element for code mirror
765
- const e = document.createElement(type);
766
- e.rel = rel;
767
- filename = 'https://cdnjs.cloudflare.com/ajax/libs/codemirror/6.65.7/' + filename;
768
- if (type === 'link')
769
- e.href = filename;
770
- else
771
- e.src = filename;
772
- e.crossOrigin = 'anonymous';
773
- document.head.appendChild(e);
774
- return e;
775
- }
776
-
777
- function loadTheme()
778
- {
779
- // Apply the theme and font size
780
- const theme = selectTheme.value;
781
- const fontSize = selectFontSize.value;
782
- textareaCode.style.fontSize = fontSize;
783
- if (codeMirror)
784
- {
785
- codeMirror.getWrapperElement().style.fontSize = fontSize;
786
- codeMirror.setOption('theme', theme);
787
- }
788
-
789
- // Save preferences to localStorage
790
- writeSaveData();
791
- }
792
-
793
- ///////////////////////////////////////////////////////////////////////////////
794
-
795
- // start up the browser
796
- initExampleBrowser()
797
-
798
- </script>
799
- </body>
800
- </html>