litecanvas 0.96.0 → 0.97.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.
package/README.md CHANGED
@@ -4,7 +4,7 @@
4
4
 
5
5
  # Litecanvas
6
6
 
7
- [![NPM Version](https://badgen.net/npm/v/litecanvas?scale=1.1&label=NPM&color=2f9e44&cache=3600)](https://www.npmjs.com/package/litecanvas/)  
7
+ [![NPM Version](https://badgen.net/npm/v/litecanvas?cache=300&scale=1.1&label=NPM&color=2f9e44)](https://www.npmjs.com/package/litecanvas/)  
8
8
  [![License](https://badgen.net/npm/license/litecanvas?scale=1.1)](LICENSE)
9
9
 
10
10
  Litecanvas is a lightweight HTML5 canvas 2D engine suitable for small web games, prototypes, game jams, animations, creative coding, learning game programming and game design, etc.
@@ -32,14 +32,10 @@ Litecanvas is a lightweight HTML5 canvas 2D engine suitable for small web games,
32
32
 
33
33
  ## Getting Started
34
34
 
35
- You can try our [online playground](https://litecanvas.github.io) or install the [basic template](https://github.com/litecanvas/template):
35
+ You can try our [online playground](https://litecanvas.github.io) or install the NPM package:
36
36
 
37
37
  ```sh
38
- # requires Node.js & NPM
39
- npx tiged litecanvas/template my-game
40
- cd my-game
41
- npm install
42
- npm run dev
38
+ npm i litecanvas
43
39
  ```
44
40
 
45
41
  or just use add a `<script>` tag with our CDN link:
package/dist/dist.dev.js CHANGED
@@ -32,27 +32,25 @@
32
32
  };
33
33
 
34
34
  // src/version.js
35
- var version = "0.96.0";
35
+ var version = "0.97.0";
36
36
 
37
37
  // src/index.js
38
38
  function litecanvas(settings = {}) {
39
39
  const root = window, math = Math, TWO_PI = math.PI * 2, raf = requestAnimationFrame, _browserEventListeners = [], on = (elem, evt, callback) => {
40
40
  elem.addEventListener(evt, callback, false);
41
41
  _browserEventListeners.push(() => elem.removeEventListener(evt, callback, false));
42
- }, preventDefault = (ev) => ev.preventDefault(), beginPath = (c) => c.beginPath(), isNumber = Number.isFinite, zzfx = setupZzFX(root), defaults = {
42
+ }, lowerCase = (str) => str.toLowerCase(), preventDefault = (ev) => ev.preventDefault(), beginPath = (c) => c.beginPath(), isNumber = Number.isFinite, zzfx = setupZzFX(root), defaults = {
43
43
  width: null,
44
44
  height: null,
45
45
  autoscale: true,
46
- pixelart: true,
47
46
  canvas: null,
48
47
  global: true,
49
48
  loop: null,
50
49
  tapEvents: true,
51
- keyboardEvents: true,
52
- animate: true
50
+ keyboardEvents: true
53
51
  };
54
52
  settings = Object.assign(defaults, settings);
55
- let _initialized = false, _plugins = [], _canvas, _scale = 1, _ctx, _outline_fix = 0.5, _timeScale = 1, _lastFrameTime, _fpsInterval = 1e3 / 60, _accumulated, _rafid, _fontFamily = "sans-serif", _fontSize = 20, _rngSeed = Date.now(), _colors = defaultPalette, _defaultSound = [0.5, 0, 1750, , , 0.3, 1, , , , 600, 0.1], _coreEvents = "init,update,draw,tap,untap,tapping,tapped,resized", _mathFunctions = "PI,sin,cos,atan2,hypot,tan,abs,ceil,floor,trunc,min,max,pow,sqrt,sign,exp", _eventListeners = {};
53
+ let _initialized = false, _plugins = [], _canvas, _scale = 1, _ctx, _outline_fix = 0.5, _timeScale = 1, _lastFrameTime, _fpsInterval = 1e3 / 60, _accumulated, _rafid, _fontFamily = "sans-serif", _fontSize = 20, _rngSeed = Date.now(), _currentPalette, _colors, _defaultSound = [0.5, 0, 1750, , , 0.3, 1, , , , 600, 0.1], _coreEvents = "init,update,draw,tap,untap,tapping,tapped,resized", _mathFunctions = "PI,sin,cos,atan2,hypot,tan,abs,ceil,floor,trunc,min,max,pow,sqrt,sign,exp", _eventListeners = {};
56
54
  const instance = {
57
55
  /** @type {number} */
58
56
  W: 0,
@@ -626,19 +624,44 @@
626
624
  DEV: assert(isNumber(y), "[litecanvas] image() 2nd param must be a number");
627
625
  _ctx.drawImage(source, ~~x, ~~y);
628
626
  },
627
+ /**
628
+ * Draw a sprite pxiel by pixel represented by a string. Each pixel must be a base 36 number (0-9 or a-z) or a dot.
629
+ *
630
+ * @param {number} x
631
+ * @param {number} y
632
+ * @param {number} width
633
+ * @param {number} height
634
+ * @param {string} pixels
635
+ */
636
+ spr(x, y, width, height, pixels) {
637
+ DEV: assert(isNumber(x), "[litecanvas] spr() 1st param must be a number");
638
+ DEV: assert(isNumber(y), "[litecanvas] spr() 2nd param must be a number");
639
+ DEV: assert(isNumber(width), "[litecanvas] spr() 3rd param must be a number");
640
+ DEV: assert(isNumber(height), "[litecanvas] spr() 4th param must be a number");
641
+ DEV: assert("string" === typeof pixels, "[litecanvas] spr() 5th param must be a string");
642
+ const chars = pixels.replace(/\s/g, "");
643
+ for (let gridx = 0; gridx < width; gridx++) {
644
+ for (let gridy = 0; gridy < height; gridy++) {
645
+ const char = chars[height * gridy + gridx] || ".";
646
+ if (char !== ".") {
647
+ instance.rectfill(x + gridx, y + gridy, 1, 1, parseInt(char, 16) || 0);
648
+ }
649
+ }
650
+ }
651
+ },
629
652
  /**
630
653
  * Draw in an OffscreenCanvas and returns its image.
631
654
  *
632
655
  * @param {number} width
633
656
  * @param {number} height
634
- * @param {string[]|drawCallback} drawing
657
+ * @param {drawCallback} callback
635
658
  * @param {object} [options]
636
659
  * @param {number} [options.scale=1]
637
660
  * @param {OffscreenCanvas} [options.canvas]
638
661
  * @returns {ImageBitmap}
639
662
  * @see https://developer.mozilla.org/en-US/docs/Web/API/OffscreenCanvas
640
663
  */
641
- paint(width, height, drawing, options = {}) {
664
+ paint(width, height, callback, options = {}) {
642
665
  DEV: assert(
643
666
  isNumber(width) && width >= 1,
644
667
  "[litecanvas] paint() 1st param must be a positive number"
@@ -648,7 +671,7 @@
648
671
  "[litecanvas] paint() 2nd param must be a positive number"
649
672
  );
650
673
  DEV: assert(
651
- "function" === typeof drawing || Array.isArray(drawing),
674
+ "function" === typeof callback,
652
675
  "[litecanvas] paint() 3rd param must be a function or array"
653
676
  );
654
677
  DEV: assert(
@@ -659,28 +682,13 @@
659
682
  options && null == options.canvas || options.canvas instanceof OffscreenCanvas,
660
683
  "[litecanvas] paint() 4th param (options.canvas) must be an OffscreenCanvas"
661
684
  );
662
- const canvas = options.canvas || new OffscreenCanvas(1, 1), scale = options.scale || 1, contextOriginal = _ctx;
685
+ const canvas = options.canvas || new OffscreenCanvas(1, 1), scale = options.scale || 1, currentContext = _ctx;
663
686
  canvas.width = width * scale;
664
687
  canvas.height = height * scale;
665
688
  _ctx = canvas.getContext("2d");
666
689
  _ctx.scale(scale, scale);
667
- if (Array.isArray(drawing)) {
668
- let x = 0, y = 0;
669
- _ctx.imageSmoothingEnabled = false;
670
- for (const str of drawing) {
671
- for (const color of str) {
672
- if (" " !== color && "." !== color) {
673
- instance.rectfill(x, y, 1, 1, parseInt(color, 16));
674
- }
675
- x++;
676
- }
677
- y++;
678
- x = 0;
679
- }
680
- } else {
681
- drawing(_ctx);
682
- }
683
- _ctx = contextOriginal;
690
+ callback(_ctx);
691
+ _ctx = currentContext;
684
692
  return canvas.transferToImageBitmap();
685
693
  },
686
694
  /** ADVANCED GRAPHICS API */
@@ -882,7 +890,7 @@
882
890
  "function" === typeof callback,
883
891
  "[litecanvas] listen() 2nd param must be a function"
884
892
  );
885
- eventName = eventName.toLowerCase();
893
+ eventName = lowerCase(eventName);
886
894
  _eventListeners[eventName] = _eventListeners[eventName] || /* @__PURE__ */ new Set();
887
895
  _eventListeners[eventName].add(callback);
888
896
  return () => _eventListeners && _eventListeners[eventName].delete(callback);
@@ -902,14 +910,14 @@
902
910
  "[litecanvas] emit() 1st param must be a string"
903
911
  );
904
912
  if (_initialized) {
905
- eventName = eventName.toLowerCase();
913
+ eventName = lowerCase(eventName);
906
914
  triggerEvent("before:" + eventName, arg1, arg2, arg3, arg4);
907
915
  triggerEvent(eventName, arg1, arg2, arg3, arg4);
908
916
  triggerEvent("after:" + eventName, arg1, arg2, arg3, arg4);
909
917
  }
910
918
  },
911
919
  /**
912
- * Set or reset the color palette
920
+ * Set or reset the color palette.
913
921
  *
914
922
  * @param {string[]} [colors]
915
923
  */
@@ -919,6 +927,31 @@
919
927
  "[litecanvas] pal() 1st param must be a array of strings"
920
928
  );
921
929
  _colors = colors;
930
+ _currentPalette = [...colors];
931
+ },
932
+ /**
933
+ * Swap two colors of the current palette.
934
+ *
935
+ * If called without arguments, reset the current palette.
936
+ *
937
+ * @param {number?} a
938
+ * @param {number?} b
939
+ */
940
+ palc(a, b) {
941
+ DEV: assert(
942
+ null == a || isNumber(a) && a >= 0,
943
+ "[litecanvas] palc() 1st param must be a positive number"
944
+ );
945
+ DEV: assert(
946
+ isNumber(a) ? isNumber(b) && b >= 0 : null == b,
947
+ "[litecanvas] palc() 2nd param must be a positive number"
948
+ );
949
+ if (a == null) {
950
+ _colors = [..._currentPalette];
951
+ } else {
952
+ ;
953
+ [_colors[a], _colors[b]] = [_colors[b], _colors[a]];
954
+ }
922
955
  },
923
956
  /**
924
957
  * Define or update a instance property.
@@ -1230,12 +1263,12 @@
1230
1263
  const _keysDown = /* @__PURE__ */ new Set();
1231
1264
  const _keysPress = /* @__PURE__ */ new Set();
1232
1265
  const keyCheck = (keySet, key = "") => {
1233
- key = key.toLowerCase();
1266
+ key = lowerCase(key);
1234
1267
  return !key ? keySet.size > 0 : keySet.has("space" === key ? " " : key);
1235
1268
  };
1236
1269
  let _lastKey = "";
1237
1270
  on(root, "keydown", (event) => {
1238
- const key = event.key.toLowerCase();
1271
+ const key = lowerCase(event.key);
1239
1272
  if (!_keysDown.has(key)) {
1240
1273
  _keysDown.add(key);
1241
1274
  _keysPress.add(key);
@@ -1243,7 +1276,7 @@
1243
1276
  }
1244
1277
  });
1245
1278
  on(root, "keyup", (event) => {
1246
- _keysDown.delete(event.key.toLowerCase());
1279
+ _keysDown.delete(lowerCase(event.key));
1247
1280
  });
1248
1281
  on(root, "blur", () => _keysDown.clear());
1249
1282
  instance.listen("after:update", () => _keysPress.clear());
@@ -1288,9 +1321,7 @@
1288
1321
  instance.resume();
1289
1322
  }
1290
1323
  function drawFrame() {
1291
- if (!settings.animate) {
1292
- return instance.emit("draw", _ctx);
1293
- }
1324
+ _rafid = raf(drawFrame);
1294
1325
  let now = Date.now();
1295
1326
  let updated = 0;
1296
1327
  let frameTime = now - _lastFrameTime;
@@ -1312,7 +1343,6 @@
1312
1343
  );
1313
1344
  }
1314
1345
  }
1315
- _rafid = raf(drawFrame);
1316
1346
  }
1317
1347
  function setupCanvas() {
1318
1348
  if ("string" === typeof settings.canvas) {
@@ -1331,11 +1361,11 @@
1331
1361
  );
1332
1362
  _ctx = _canvas.getContext("2d");
1333
1363
  on(_canvas, "click", () => focus());
1334
- _canvas.style = "";
1335
1364
  resizeCanvas();
1336
1365
  if (!_canvas.parentNode) {
1337
1366
  document.body.appendChild(_canvas);
1338
1367
  }
1368
+ _canvas.style.imageRendering = "pixelated";
1339
1369
  _canvas.oncontextmenu = () => false;
1340
1370
  }
1341
1371
  function resizeCanvas() {
@@ -1367,15 +1397,9 @@
1367
1397
  _canvas.style.width = width * _scale + "px";
1368
1398
  _canvas.style.height = height * _scale + "px";
1369
1399
  }
1370
- if (settings.pixelart) {
1371
- _ctx.imageSmoothingEnabled = false;
1372
- _canvas.style.imageRendering = "pixelated";
1373
- }
1400
+ _ctx.imageSmoothingEnabled = false;
1374
1401
  instance.textalign("start", "top");
1375
1402
  instance.emit("resized", _scale);
1376
- if (!settings.animate) {
1377
- raf(drawFrame);
1378
- }
1379
1403
  }
1380
1404
  function triggerEvent(eventName, arg1, arg2, arg3, arg4) {
1381
1405
  if (!_eventListeners[eventName]) return;
@@ -1403,6 +1427,7 @@
1403
1427
  DEV: console.info(`[litecanvas] version ${version} started`);
1404
1428
  DEV: console.debug(`[litecanvas] litecanvas() options =`, settings);
1405
1429
  setupCanvas();
1430
+ instance.pal();
1406
1431
  if ("loading" === document.readyState) {
1407
1432
  on(root, "DOMContentLoaded", () => raf(init));
1408
1433
  } else {
package/dist/dist.js CHANGED
@@ -31,20 +31,18 @@
31
31
  const root = window, math = Math, TWO_PI = math.PI * 2, raf = requestAnimationFrame, _browserEventListeners = [], on = (elem, evt, callback) => {
32
32
  elem.addEventListener(evt, callback, false);
33
33
  _browserEventListeners.push(() => elem.removeEventListener(evt, callback, false));
34
- }, preventDefault = (ev) => ev.preventDefault(), beginPath = (c) => c.beginPath(), isNumber = Number.isFinite, zzfx = setupZzFX(root), defaults = {
34
+ }, lowerCase = (str) => str.toLowerCase(), preventDefault = (ev) => ev.preventDefault(), beginPath = (c) => c.beginPath(), isNumber = Number.isFinite, zzfx = setupZzFX(root), defaults = {
35
35
  width: null,
36
36
  height: null,
37
37
  autoscale: true,
38
- pixelart: true,
39
38
  canvas: null,
40
39
  global: true,
41
40
  loop: null,
42
41
  tapEvents: true,
43
- keyboardEvents: true,
44
- animate: true
42
+ keyboardEvents: true
45
43
  };
46
44
  settings = Object.assign(defaults, settings);
47
- let _initialized = false, _plugins = [], _canvas, _scale = 1, _ctx, _outline_fix = 0.5, _timeScale = 1, _lastFrameTime, _fpsInterval = 1e3 / 60, _accumulated, _rafid, _fontFamily = "sans-serif", _fontSize = 20, _rngSeed = Date.now(), _colors = defaultPalette, _defaultSound = [0.5, 0, 1750, , , 0.3, 1, , , , 600, 0.1], _coreEvents = "init,update,draw,tap,untap,tapping,tapped,resized", _mathFunctions = "PI,sin,cos,atan2,hypot,tan,abs,ceil,floor,trunc,min,max,pow,sqrt,sign,exp", _eventListeners = {};
45
+ let _initialized = false, _plugins = [], _canvas, _scale = 1, _ctx, _outline_fix = 0.5, _timeScale = 1, _lastFrameTime, _fpsInterval = 1e3 / 60, _accumulated, _rafid, _fontFamily = "sans-serif", _fontSize = 20, _rngSeed = Date.now(), _currentPalette, _colors, _defaultSound = [0.5, 0, 1750, , , 0.3, 1, , , , 600, 0.1], _coreEvents = "init,update,draw,tap,untap,tapping,tapped,resized", _mathFunctions = "PI,sin,cos,atan2,hypot,tan,abs,ceil,floor,trunc,min,max,pow,sqrt,sign,exp", _eventListeners = {};
48
46
  const instance = {
49
47
  /** @type {number} */
50
48
  W: 0,
@@ -417,41 +415,46 @@
417
415
  image(x, y, source) {
418
416
  _ctx.drawImage(source, ~~x, ~~y);
419
417
  },
418
+ /**
419
+ * Draw a sprite pxiel by pixel represented by a string. Each pixel must be a base 36 number (0-9 or a-z) or a dot.
420
+ *
421
+ * @param {number} x
422
+ * @param {number} y
423
+ * @param {number} width
424
+ * @param {number} height
425
+ * @param {string} pixels
426
+ */
427
+ spr(x, y, width, height, pixels) {
428
+ const chars = pixels.replace(/\s/g, "");
429
+ for (let gridx = 0; gridx < width; gridx++) {
430
+ for (let gridy = 0; gridy < height; gridy++) {
431
+ const char = chars[height * gridy + gridx] || ".";
432
+ if (char !== ".") {
433
+ instance.rectfill(x + gridx, y + gridy, 1, 1, parseInt(char, 16) || 0);
434
+ }
435
+ }
436
+ }
437
+ },
420
438
  /**
421
439
  * Draw in an OffscreenCanvas and returns its image.
422
440
  *
423
441
  * @param {number} width
424
442
  * @param {number} height
425
- * @param {string[]|drawCallback} drawing
443
+ * @param {drawCallback} callback
426
444
  * @param {object} [options]
427
445
  * @param {number} [options.scale=1]
428
446
  * @param {OffscreenCanvas} [options.canvas]
429
447
  * @returns {ImageBitmap}
430
448
  * @see https://developer.mozilla.org/en-US/docs/Web/API/OffscreenCanvas
431
449
  */
432
- paint(width, height, drawing, options = {}) {
433
- const canvas = options.canvas || new OffscreenCanvas(1, 1), scale = options.scale || 1, contextOriginal = _ctx;
450
+ paint(width, height, callback, options = {}) {
451
+ const canvas = options.canvas || new OffscreenCanvas(1, 1), scale = options.scale || 1, currentContext = _ctx;
434
452
  canvas.width = width * scale;
435
453
  canvas.height = height * scale;
436
454
  _ctx = canvas.getContext("2d");
437
455
  _ctx.scale(scale, scale);
438
- if (Array.isArray(drawing)) {
439
- let x = 0, y = 0;
440
- _ctx.imageSmoothingEnabled = false;
441
- for (const str of drawing) {
442
- for (const color of str) {
443
- if (" " !== color && "." !== color) {
444
- instance.rectfill(x, y, 1, 1, parseInt(color, 16));
445
- }
446
- x++;
447
- }
448
- y++;
449
- x = 0;
450
- }
451
- } else {
452
- drawing(_ctx);
453
- }
454
- _ctx = contextOriginal;
456
+ callback(_ctx);
457
+ _ctx = currentContext;
455
458
  return canvas.transferToImageBitmap();
456
459
  },
457
460
  /** ADVANCED GRAPHICS API */
@@ -609,7 +612,7 @@
609
612
  * @returns {Function} a function to remove the listener
610
613
  */
611
614
  listen(eventName, callback) {
612
- eventName = eventName.toLowerCase();
615
+ eventName = lowerCase(eventName);
613
616
  _eventListeners[eventName] = _eventListeners[eventName] || /* @__PURE__ */ new Set();
614
617
  _eventListeners[eventName].add(callback);
615
618
  return () => _eventListeners && _eventListeners[eventName].delete(callback);
@@ -625,19 +628,36 @@
625
628
  */
626
629
  emit(eventName, arg1, arg2, arg3, arg4) {
627
630
  if (_initialized) {
628
- eventName = eventName.toLowerCase();
631
+ eventName = lowerCase(eventName);
629
632
  triggerEvent("before:" + eventName, arg1, arg2, arg3, arg4);
630
633
  triggerEvent(eventName, arg1, arg2, arg3, arg4);
631
634
  triggerEvent("after:" + eventName, arg1, arg2, arg3, arg4);
632
635
  }
633
636
  },
634
637
  /**
635
- * Set or reset the color palette
638
+ * Set or reset the color palette.
636
639
  *
637
640
  * @param {string[]} [colors]
638
641
  */
639
642
  pal(colors = defaultPalette) {
640
643
  _colors = colors;
644
+ _currentPalette = [...colors];
645
+ },
646
+ /**
647
+ * Swap two colors of the current palette.
648
+ *
649
+ * If called without arguments, reset the current palette.
650
+ *
651
+ * @param {number?} a
652
+ * @param {number?} b
653
+ */
654
+ palc(a, b) {
655
+ if (a == null) {
656
+ _colors = [..._currentPalette];
657
+ } else {
658
+ ;
659
+ [_colors[a], _colors[b]] = [_colors[b], _colors[a]];
660
+ }
641
661
  },
642
662
  /**
643
663
  * Define or update a instance property.
@@ -928,12 +948,12 @@
928
948
  const _keysDown = /* @__PURE__ */ new Set();
929
949
  const _keysPress = /* @__PURE__ */ new Set();
930
950
  const keyCheck = (keySet, key = "") => {
931
- key = key.toLowerCase();
951
+ key = lowerCase(key);
932
952
  return !key ? keySet.size > 0 : keySet.has("space" === key ? " " : key);
933
953
  };
934
954
  let _lastKey = "";
935
955
  on(root, "keydown", (event) => {
936
- const key = event.key.toLowerCase();
956
+ const key = lowerCase(event.key);
937
957
  if (!_keysDown.has(key)) {
938
958
  _keysDown.add(key);
939
959
  _keysPress.add(key);
@@ -941,7 +961,7 @@
941
961
  }
942
962
  });
943
963
  on(root, "keyup", (event) => {
944
- _keysDown.delete(event.key.toLowerCase());
964
+ _keysDown.delete(lowerCase(event.key));
945
965
  });
946
966
  on(root, "blur", () => _keysDown.clear());
947
967
  instance.listen("after:update", () => _keysPress.clear());
@@ -978,9 +998,7 @@
978
998
  instance.resume();
979
999
  }
980
1000
  function drawFrame() {
981
- if (!settings.animate) {
982
- return instance.emit("draw", _ctx);
983
- }
1001
+ _rafid = raf(drawFrame);
984
1002
  let now = Date.now();
985
1003
  let updated = 0;
986
1004
  let frameTime = now - _lastFrameTime;
@@ -999,7 +1017,6 @@
999
1017
  _accumulated = 0;
1000
1018
  }
1001
1019
  }
1002
- _rafid = raf(drawFrame);
1003
1020
  }
1004
1021
  function setupCanvas() {
1005
1022
  if ("string" === typeof settings.canvas) {
@@ -1010,11 +1027,11 @@
1010
1027
  _canvas = _canvas || document.createElement("canvas");
1011
1028
  _ctx = _canvas.getContext("2d");
1012
1029
  on(_canvas, "click", () => focus());
1013
- _canvas.style = "";
1014
1030
  resizeCanvas();
1015
1031
  if (!_canvas.parentNode) {
1016
1032
  document.body.appendChild(_canvas);
1017
1033
  }
1034
+ _canvas.style.imageRendering = "pixelated";
1018
1035
  _canvas.oncontextmenu = () => false;
1019
1036
  }
1020
1037
  function resizeCanvas() {
@@ -1034,15 +1051,9 @@
1034
1051
  _canvas.style.width = width * _scale + "px";
1035
1052
  _canvas.style.height = height * _scale + "px";
1036
1053
  }
1037
- if (settings.pixelart) {
1038
- _ctx.imageSmoothingEnabled = false;
1039
- _canvas.style.imageRendering = "pixelated";
1040
- }
1054
+ _ctx.imageSmoothingEnabled = false;
1041
1055
  instance.textalign("start", "top");
1042
1056
  instance.emit("resized", _scale);
1043
- if (!settings.animate) {
1044
- raf(drawFrame);
1045
- }
1046
1057
  }
1047
1058
  function triggerEvent(eventName, arg1, arg2, arg3, arg4) {
1048
1059
  if (!_eventListeners[eventName]) return;
@@ -1064,6 +1075,7 @@
1064
1075
  root.ENGINE = instance;
1065
1076
  }
1066
1077
  setupCanvas();
1078
+ instance.pal();
1067
1079
  if ("loading" === document.readyState) {
1068
1080
  on(root, "DOMContentLoaded", () => raf(init));
1069
1081
  } else {
package/dist/dist.min.js CHANGED
@@ -1 +1 @@
1
- (()=>{var e=["#111","#6a7799","#aec2c2","#FFF1E8","#e83b3b","#fabc20","#155fd9","#3cbcfc","#327345","#63c64d","#6c2c1f","#ac7c00"];globalThis.litecanvas=function(t={}){let a=window,l=Math,i=2*l.PI,n=requestAnimationFrame,o=[],r=(e,t,a)=>{e.addEventListener(t,a,!1),o.push(()=>e.removeEventListener(t,a,!1))},s=e=>e.preventDefault(),f=e=>e.beginPath(),c=(e=>{let t=new AudioContext;return e.zzfxV=1,(a=1,l=.05,i=220,n=0,o=0,r=.1,s=0,f=1,c=0,d=0,u=0,p=0,h=0,m=0,g=0,w=0,v=0,x=1,y=0,b=0,k=0)=>{let E=Math,z=2*E.PI,T=c*=500*z/44100/44100,C=i*=(1-l+2*l*E.random(l=[]))*z/44100,I=0,A=0,L=0,S=1,D=0,M=0,N=0,P=k<0?-1:1,F=z*P*k*2/44100,q=E.cos(F),B=E.sin,H=B(F)/4,O=1+H,V=-2*q/O,W=(1-H)/O,R=(1+P*q)/2/O,G=-(P+q)/O,X=0,Y=0,$=0,j=0;for(n=44100*n+9,y*=44100,o*=44100,r*=44100,v*=44100,d*=500*z/85766121e6,g*=z/44100,u*=z/44100,p*=44100,h=44100*h|0,a*=.3*e.zzfxV,P=n+y+o+r+v|0;L<P;l[L++]=N*a)++M%(100*w|0)||(N=s?1<s?2<s?3<s?B(I*I):E.max(E.min(E.tan(I),1),-1):1-(2*I/z%2+2)%2:1-4*E.abs(E.round(I/z)-I/z):B(I),N=(h?1-b+b*B(z*L/h):1)*(N<0?-1:1)*E.abs(N)**f*(L<n?L/n:L<n+y?1-(L-n)/y*(1-x):L<n+y+o?x:L<P-v?(P-L-v)/r*x:0),N=v?N/2+(v>L?0:(L<P-v?1:(P-L)/v)*l[L-v|0]/2/a):N,k&&(N=j=R*X+G*(X=Y)+R*(Y=N)-W*$-V*($=j))),I+=(F=(i+=c+=d)*E.cos(g*A++))+F*m*B(L**5),S&&++S>p&&(i+=u,C+=u,S=0),!h||++D%h||(i=C,c=T,S=S||1);(a=t.createBuffer(1,P,44100)).getChannelData(0).set(l),(i=t.createBufferSource()).buffer=a,i.connect(t.destination),i.start()}})(a);t=Object.assign({width:null,height:null,autoscale:!0,pixelart:!0,canvas:null,global:!0,loop:null,tapEvents:!0,keyboardEvents:!0,animate:!0},t);let d=!1,u=[],p,h=1,m,g=.5,w=1,v,x=1e3/60,y,b,k="sans-serif",E=20,z=Date.now(),T=e,C=[.5,0,1750,,,.3,1,,,,600,.1],I={},A={W:0,H:0,T:0,MX:-1,MY:-1,TWO_PI:i,HALF_PI:i/4,lerp:(e,t,a)=>a*(t-e)+e,deg2rad:e=>l.PI/180*e,rad2deg:e=>180/l.PI*e,round:(e,t=0)=>{if(!t)return l.round(e);let a=10**t;return l.round(e*a)/a},clamp:(e,t,a)=>e<t?t:e>a?a:e,wrap:(e,t,a)=>e-(a-t)*l.floor((e-t)/(a-t)),map(e,t,a,l,i,n){let o=(e-t)/(a-t)*(i-l)+l;return n?A.clamp(o,l,i):o},norm:(e,t,a)=>A.map(e,t,a,0,1),wave:(e,t,a,l=Math.sin)=>e+(l(a)+1)/2*(t-e),rand:(e=0,t=1)=>(z=(1664525*z+0x3c6ef35f)%0x100000000)/0x100000000*(t-e)+e,randi:(e=0,t=1)=>l.floor(A.rand(e,t+1)),rseed(e){z=~~e},cls(e){null==e?m.clearRect(0,0,m.canvas.width,m.canvas.height):A.rectfill(0,0,m.canvas.width,m.canvas.height,e)},rect(e,t,a,l,i,n){f(m),m[n?"roundRect":"rect"](~~e-g,~~t-g,~~a+2*g,~~l+2*g,n),A.stroke(i)},rectfill(e,t,a,l,i,n){f(m),m[n?"roundRect":"rect"](~~e,~~t,~~a,~~l,n),A.fill(i)},circ(e,t,a,l){f(m),m.arc(~~e,~~t,~~a,0,i),A.stroke(l)},circfill(e,t,a,l){f(m),m.arc(~~e,~~t,~~a,0,i),A.fill(l)},oval(e,t,a,l,n){f(m),m.ellipse(~~e,~~t,~~a,~~l,0,0,i),A.stroke(n)},ovalfill(e,t,a,l,n){f(m),m.ellipse(~~e,~~t,~~a,~~l,0,0,i),A.fill(n)},line(e,t,a,l,i){f(m);let n=.5*(0!==g&&~~e==~~a),o=.5*(0!==g&&~~t==~~l);m.moveTo(~~e+n,~~t+o),m.lineTo(~~a+n,~~l+o),A.stroke(i)},linewidth(e){m.lineWidth=~~e,g=.5*(0!=~~e%2)},linedash(e,t=0){m.setLineDash(e),m.lineDashOffset=t},text(e,t,a,l=3,i="normal"){m.font=`${i} ${E}px ${k}`,m.fillStyle=T[~~l%T.length],m.fillText(a,~~e,~~t)},textfont(e){k=e},textsize(e){E=e},textalign(e,t){e&&(m.textAlign=e),t&&(m.textBaseline=t)},image(e,t,a){m.drawImage(a,~~e,~~t)},paint(e,t,a,l={}){let i=l.canvas||new OffscreenCanvas(1,1),n=l.scale||1,o=m;if(i.width=e*n,i.height=t*n,(m=i.getContext("2d")).scale(n,n),Array.isArray(a)){let e=0,t=0;for(let l of(m.imageSmoothingEnabled=!1,a)){for(let a of l)" "!==a&&"."!==a&&A.rectfill(e,t,1,1,parseInt(a,16)),e++;t++,e=0}}else a(m);return m=o,i.transferToImageBitmap()},ctx:e=>(e&&(m=e),m),push(){m.save()},pop(){m.restore()},translate(e,t){m.translate(~~e,~~t)},scale(e,t){m.scale(e,t||e)},rotate(e){m.rotate(e)},alpha(e){m.globalAlpha=A.clamp(e,0,1)},fill(e){m.fillStyle=T[~~e%T.length],m.fill()},stroke(e){m.strokeStyle=T[~~e%T.length],m.stroke()},clip(e){f(m),e(m),m.clip()},sfx:(e,t=0,l=1)=>!!a.zzfxV&&(!navigator.userActivation||!!navigator.userActivation.hasBeenActive)&&(e=e||C,(0!==t||1!==l)&&((e=e.slice())[0]=l*(e[0]||1),e[10]=~~e[10]+t),c.apply(0,e),e),volume(e){a.zzfxV=e},canvas:()=>p,use(e,t={}){d?N(e,t):u.push([e,t])},listen:(e,t)=>(I[e=e.toLowerCase()]=I[e]||new Set,I[e].add(t),()=>I&&I[e].delete(t)),emit(e,t,a,l,i){d&&(M("before:"+(e=e.toLowerCase()),t,a,l,i),M(e,t,a,l,i),M("after:"+e,t,a,l,i))},pal(t=e){T=t},def(e,l){A[e]=l,t.global&&(a[e]=l)},timescale(e){w=e},framerate(e){x=1e3/~~e},stat(e){let l={index:e,value:[t,d,x/1e3,h,I,T,C,w,a.zzfxV,z,E,k][e]};return A.emit("stat",l),l.value},quit(){for(let e of(A.pause(),A.emit("quit"),I={},o))e();if(t.global){for(let e in A)delete a[e];delete a.ENGINE}d=!1},pause(){cancelAnimationFrame(b),b=0},resume(){d&&!b&&(y=x,v=Date.now(),b=n(S))},paused:()=>!b};for(let e of"PI,sin,cos,atan2,hypot,tan,abs,ceil,floor,trunc,min,max,pow,sqrt,sign,exp".split(","))A[e]=l[e];function L(){let e=t.loop?t.loop:a;for(let t of"init,update,draw,tap,untap,tapping,tapped,resized".split(","))e[t]&&A.listen(t,e[t]);for(let[e,t]of u)N(e,t);if(t.autoscale&&r(a,"resize",D),t.tapEvents){let e=e=>[(e.pageX-p.offsetLeft)/h,(e.pageY-p.offsetTop)/h],t=new Map,l=(e,a,l)=>{let i={x:a,y:l,xi:a,yi:l,t:Date.now()};return t.set(e,i),i},i=(e,a,i)=>{let n=t.get(e)||l(e);n.x=a,n.y=i},n=e=>e&&Date.now()-e.t<=300,o=!1;r(p,"mousedown",t=>{if(0===t.button){s(t);let[a,i]=e(t);A.emit("tap",a,i,0),l(0,a,i),o=!0}}),r(p,"mouseup",a=>{if(0===a.button){s(a);let l=t.get(0),[i,r]=e(a);n(l)&&A.emit("tapped",l.xi,l.yi,0),A.emit("untap",i,r,0),t.delete(0),o=!1}}),r(a,"mousemove",t=>{s(t);let[a,l]=e(t);A.def("MX",a),A.def("MY",l),o&&(A.emit("tapping",a,l,0),i(0,a,l))}),r(p,"touchstart",t=>{for(let a of(s(t),t.changedTouches)){let[t,i]=e(a);A.emit("tap",t,i,a.identifier+1),l(a.identifier+1,t,i)}}),r(p,"touchmove",t=>{for(let a of(s(t),t.changedTouches)){let[t,l]=e(a);A.emit("tapping",t,l,a.identifier+1),i(a.identifier+1,t,l)}});let f=e=>{s(e);let a=[];if(e.targetTouches.length>0)for(let t of e.targetTouches)a.push(t.identifier+1);for(let[e,l]of t)a.includes(e)||(n(l)&&A.emit("tapped",l.xi,l.yi,e),A.emit("untap",l.x,l.y,e),t.delete(e))};r(p,"touchend",f),r(p,"touchcancel",f),r(a,"blur",()=>{for(let[e,a]of(o=!1,t))A.emit("untap",a.x,a.y,e),t.delete(e)})}if(t.keyboardEvents){let e=new Set,t=new Set,l=(e,t="")=>(t=t.toLowerCase())?e.has("space"===t?" ":t):e.size>0,i="";r(a,"keydown",a=>{let l=a.key.toLowerCase();e.has(l)||(e.add(l),t.add(l),i=" "===l?"space":l)}),r(a,"keyup",t=>{e.delete(t.key.toLowerCase())}),r(a,"blur",()=>e.clear()),A.listen("after:update",()=>t.clear()),A.def("iskeydown",t=>l(e,t)),A.def("iskeypressed",e=>l(t,e)),A.def("lastkey",()=>i)}d=!0,A.emit("init",A),A.resume()}function S(){if(!t.animate)return A.emit("draw",m);let e=Date.now(),a=0,l=e-v;for(v=e,y+=l<100?l:x;y>=x;){a++,y-=x;let e=x/1e3*w;A.emit("update",e,a),A.def("T",A.T+e)}a&&(A.emit("draw",m),a>1&&(y=0)),b=n(S)}function D(){let e=t.width>0?t.width:innerWidth,a=t.width>0?t.height||t.width:innerHeight;if(A.def("W",e),A.def("H",a),p.width=e,p.height=a,t.autoscale){let i=+t.autoscale;p.style.display||(p.style.display="block",p.style.margin="auto"),h=l.min(innerWidth/e,innerHeight/a),h=i>1&&h>i?i:h,p.style.width=e*h+"px",p.style.height=a*h+"px"}t.pixelart&&(m.imageSmoothingEnabled=!1,p.style.imageRendering="pixelated"),A.textalign("start","top"),A.emit("resized",h),t.animate||n(S)}function M(e,t,a,l,i){if(I[e])for(let n of I[e])n(t,a,l,i)}function N(e,t){let a=e(A,t);for(let e in a)A.def(e,a[e])}if(t.global){if(a.ENGINE)throw Error("only one global litecanvas is allowed");Object.assign(a,A),a.ENGINE=A}return m=(p=(p="string"==typeof t.canvas?document.querySelector(t.canvas):t.canvas)||document.createElement("canvas")).getContext("2d"),r(p,"click",()=>focus()),p.style="",D(),p.parentNode||document.body.appendChild(p),p.oncontextmenu=()=>!1,"loading"===document.readyState?r(a,"DOMContentLoaded",()=>n(L)):n(L),A}})();
1
+ (()=>{var e=["#111","#6a7799","#aec2c2","#FFF1E8","#e83b3b","#fabc20","#155fd9","#3cbcfc","#327345","#63c64d","#6c2c1f","#ac7c00"];globalThis.litecanvas=function(t={}){let a=window,l=Math,n=2*l.PI,i=requestAnimationFrame,o=[],r=(e,t,a)=>{e.addEventListener(t,a,!1),o.push(()=>e.removeEventListener(t,a,!1))},s=e=>e.toLowerCase(),c=e=>e.preventDefault(),f=e=>e.beginPath(),d=(e=>{let t=new AudioContext;return e.zzfxV=1,(a=1,l=.05,n=220,i=0,o=0,r=.1,s=0,c=1,f=0,d=0,u=0,p=0,h=0,m=0,g=0,v=0,w=0,x=1,y=0,b=0,k=0)=>{let E=Math,z=2*E.PI,T=f*=500*z/44100/44100,I=n*=(1-l+2*l*E.random(l=[]))*z/44100,D=0,S=0,A=0,M=1,C=0,L=0,N=0,P=k<0?-1:1,F=z*P*k*2/44100,q=E.cos(F),B=E.sin,H=B(F)/4,O=1+H,V=-2*q/O,W=(1-H)/O,R=(1+P*q)/2/O,G=-(P+q)/O,X=0,Y=0,$=0,j=0;for(i=44100*i+9,y*=44100,o*=44100,r*=44100,w*=44100,d*=500*z/85766121e6,g*=z/44100,u*=z/44100,p*=44100,h=44100*h|0,a*=.3*e.zzfxV,P=i+y+o+r+w|0;A<P;l[A++]=N*a)++L%(100*v|0)||(N=s?1<s?2<s?3<s?B(D*D):E.max(E.min(E.tan(D),1),-1):1-(2*D/z%2+2)%2:1-4*E.abs(E.round(D/z)-D/z):B(D),N=(h?1-b+b*B(z*A/h):1)*(N<0?-1:1)*E.abs(N)**c*(A<i?A/i:A<i+y?1-(A-i)/y*(1-x):A<i+y+o?x:A<P-w?(P-A-w)/r*x:0),N=w?N/2+(w>A?0:(A<P-w?1:(P-A)/w)*l[A-w|0]/2/a):N,k&&(N=j=R*X+G*(X=Y)+R*(Y=N)-W*$-V*($=j))),D+=(F=(n+=f+=d)*E.cos(g*S++))+F*m*B(A**5),M&&++M>p&&(n+=u,I+=u,M=0),!h||++C%h||(n=I,f=T,M=M||1);(a=t.createBuffer(1,P,44100)).getChannelData(0).set(l),(n=t.createBufferSource()).buffer=a,n.connect(t.destination),n.start()}})(a);t=Object.assign({width:null,height:null,autoscale:!0,canvas:null,global:!0,loop:null,tapEvents:!0,keyboardEvents:!0},t);let u=!1,p=[],h,m=1,g,v=.5,w=1,x,y=1e3/60,b,k,E="sans-serif",z=20,T=Date.now(),I,D,S=[.5,0,1750,,,.3,1,,,,600,.1],A={},M={W:0,H:0,T:0,MX:-1,MY:-1,TWO_PI:n,HALF_PI:n/4,lerp:(e,t,a)=>a*(t-e)+e,deg2rad:e=>l.PI/180*e,rad2deg:e=>180/l.PI*e,round:(e,t=0)=>{if(!t)return l.round(e);let a=10**t;return l.round(e*a)/a},clamp:(e,t,a)=>e<t?t:e>a?a:e,wrap:(e,t,a)=>e-(a-t)*l.floor((e-t)/(a-t)),map(e,t,a,l,n,i){let o=(e-t)/(a-t)*(n-l)+l;return i?M.clamp(o,l,n):o},norm:(e,t,a)=>M.map(e,t,a,0,1),wave:(e,t,a,l=Math.sin)=>e+(l(a)+1)/2*(t-e),rand:(e=0,t=1)=>(T=(1664525*T+0x3c6ef35f)%0x100000000)/0x100000000*(t-e)+e,randi:(e=0,t=1)=>l.floor(M.rand(e,t+1)),rseed(e){T=~~e},cls(e){null==e?g.clearRect(0,0,g.canvas.width,g.canvas.height):M.rectfill(0,0,g.canvas.width,g.canvas.height,e)},rect(e,t,a,l,n,i){f(g),g[i?"roundRect":"rect"](~~e-v,~~t-v,~~a+2*v,~~l+2*v,i),M.stroke(n)},rectfill(e,t,a,l,n,i){f(g),g[i?"roundRect":"rect"](~~e,~~t,~~a,~~l,i),M.fill(n)},circ(e,t,a,l){f(g),g.arc(~~e,~~t,~~a,0,n),M.stroke(l)},circfill(e,t,a,l){f(g),g.arc(~~e,~~t,~~a,0,n),M.fill(l)},oval(e,t,a,l,i){f(g),g.ellipse(~~e,~~t,~~a,~~l,0,0,n),M.stroke(i)},ovalfill(e,t,a,l,i){f(g),g.ellipse(~~e,~~t,~~a,~~l,0,0,n),M.fill(i)},line(e,t,a,l,n){f(g);let i=.5*(0!==v&&~~e==~~a),o=.5*(0!==v&&~~t==~~l);g.moveTo(~~e+i,~~t+o),g.lineTo(~~a+i,~~l+o),M.stroke(n)},linewidth(e){g.lineWidth=~~e,v=.5*(0!=~~e%2)},linedash(e,t=0){g.setLineDash(e),g.lineDashOffset=t},text(e,t,a,l=3,n="normal"){g.font=`${n} ${z}px ${E}`,g.fillStyle=D[~~l%D.length],g.fillText(a,~~e,~~t)},textfont(e){E=e},textsize(e){z=e},textalign(e,t){e&&(g.textAlign=e),t&&(g.textBaseline=t)},image(e,t,a){g.drawImage(a,~~e,~~t)},spr(e,t,a,l,n){let i=n.replace(/\s/g,"");for(let n=0;n<a;n++)for(let a=0;a<l;a++){let o=i[l*a+n]||".";"."!==o&&M.rectfill(e+n,t+a,1,1,parseInt(o,16)||0)}},paint(e,t,a,l={}){let n=l.canvas||new OffscreenCanvas(1,1),i=l.scale||1,o=g;return n.width=e*i,n.height=t*i,(g=n.getContext("2d")).scale(i,i),a(g),g=o,n.transferToImageBitmap()},ctx:e=>(e&&(g=e),g),push(){g.save()},pop(){g.restore()},translate(e,t){g.translate(~~e,~~t)},scale(e,t){g.scale(e,t||e)},rotate(e){g.rotate(e)},alpha(e){g.globalAlpha=M.clamp(e,0,1)},fill(e){g.fillStyle=D[~~e%D.length],g.fill()},stroke(e){g.strokeStyle=D[~~e%D.length],g.stroke()},clip(e){f(g),e(g),g.clip()},sfx:(e,t=0,l=1)=>!!a.zzfxV&&(!navigator.userActivation||!!navigator.userActivation.hasBeenActive)&&(e=e||S,(0!==t||1!==l)&&((e=e.slice())[0]=l*(e[0]||1),e[10]=~~e[10]+t),d.apply(0,e),e),volume(e){a.zzfxV=e},canvas:()=>h,use(e,t={}){u?F(e,t):p.push([e,t])},listen:(e,t)=>(A[e=s(e)]=A[e]||new Set,A[e].add(t),()=>A&&A[e].delete(t)),emit(e,t,a,l,n){u&&(P("before:"+(e=s(e)),t,a,l,n),P(e,t,a,l,n),P("after:"+e,t,a,l,n))},pal(t=e){D=t,I=[...t]},palc(e,t){null==e?D=[...I]:[D[e],D[t]]=[D[t],D[e]]},def(e,l){M[e]=l,t.global&&(a[e]=l)},timescale(e){w=e},framerate(e){y=1e3/~~e},stat(e){let l={index:e,value:[t,u,y/1e3,m,A,D,S,w,a.zzfxV,T,z,E][e]};return M.emit("stat",l),l.value},quit(){for(let e of(M.pause(),M.emit("quit"),A={},o))e();if(t.global){for(let e in M)delete a[e];delete a.ENGINE}u=!1},pause(){cancelAnimationFrame(k),k=0},resume(){u&&!k&&(b=y,x=Date.now(),k=i(L))},paused:()=>!k};for(let e of"PI,sin,cos,atan2,hypot,tan,abs,ceil,floor,trunc,min,max,pow,sqrt,sign,exp".split(","))M[e]=l[e];function C(){let e=t.loop?t.loop:a;for(let t of"init,update,draw,tap,untap,tapping,tapped,resized".split(","))e[t]&&M.listen(t,e[t]);for(let[e,t]of p)F(e,t);if(t.autoscale&&r(a,"resize",N),t.tapEvents){let e=e=>[(e.pageX-h.offsetLeft)/m,(e.pageY-h.offsetTop)/m],t=new Map,l=(e,a,l)=>{let n={x:a,y:l,xi:a,yi:l,t:Date.now()};return t.set(e,n),n},n=(e,a,n)=>{let i=t.get(e)||l(e);i.x=a,i.y=n},i=e=>e&&Date.now()-e.t<=300,o=!1;r(h,"mousedown",t=>{if(0===t.button){c(t);let[a,n]=e(t);M.emit("tap",a,n,0),l(0,a,n),o=!0}}),r(h,"mouseup",a=>{if(0===a.button){c(a);let l=t.get(0),[n,r]=e(a);i(l)&&M.emit("tapped",l.xi,l.yi,0),M.emit("untap",n,r,0),t.delete(0),o=!1}}),r(a,"mousemove",t=>{c(t);let[a,l]=e(t);M.def("MX",a),M.def("MY",l),o&&(M.emit("tapping",a,l,0),n(0,a,l))}),r(h,"touchstart",t=>{for(let a of(c(t),t.changedTouches)){let[t,n]=e(a);M.emit("tap",t,n,a.identifier+1),l(a.identifier+1,t,n)}}),r(h,"touchmove",t=>{for(let a of(c(t),t.changedTouches)){let[t,l]=e(a);M.emit("tapping",t,l,a.identifier+1),n(a.identifier+1,t,l)}});let s=e=>{c(e);let a=[];if(e.targetTouches.length>0)for(let t of e.targetTouches)a.push(t.identifier+1);for(let[e,l]of t)a.includes(e)||(i(l)&&M.emit("tapped",l.xi,l.yi,e),M.emit("untap",l.x,l.y,e),t.delete(e))};r(h,"touchend",s),r(h,"touchcancel",s),r(a,"blur",()=>{for(let[e,a]of(o=!1,t))M.emit("untap",a.x,a.y,e),t.delete(e)})}if(t.keyboardEvents){let e=new Set,t=new Set,l=(e,t="")=>(t=s(t))?e.has("space"===t?" ":t):e.size>0,n="";r(a,"keydown",a=>{let l=s(a.key);e.has(l)||(e.add(l),t.add(l),n=" "===l?"space":l)}),r(a,"keyup",t=>{e.delete(s(t.key))}),r(a,"blur",()=>e.clear()),M.listen("after:update",()=>t.clear()),M.def("iskeydown",t=>l(e,t)),M.def("iskeypressed",e=>l(t,e)),M.def("lastkey",()=>n)}u=!0,M.emit("init",M),M.resume()}function L(){k=i(L);let e=Date.now(),t=0,a=e-x;for(x=e,b+=a<100?a:y;b>=y;){t++,b-=y;let e=y/1e3*w;M.emit("update",e,t),M.def("T",M.T+e)}t&&(M.emit("draw",g),t>1&&(b=0))}function N(){let e=t.width>0?t.width:innerWidth,a=t.width>0?t.height||t.width:innerHeight;if(M.def("W",e),M.def("H",a),h.width=e,h.height=a,t.autoscale){let n=+t.autoscale;h.style.display||(h.style.display="block",h.style.margin="auto"),m=l.min(innerWidth/e,innerHeight/a),m=n>1&&m>n?n:m,h.style.width=e*m+"px",h.style.height=a*m+"px"}g.imageSmoothingEnabled=!1,M.textalign("start","top"),M.emit("resized",m)}function P(e,t,a,l,n){if(A[e])for(let i of A[e])i(t,a,l,n)}function F(e,t){let a=e(M,t);for(let e in a)M.def(e,a[e])}if(t.global){if(a.ENGINE)throw Error("only one global litecanvas is allowed");Object.assign(a,M),a.ENGINE=M}return g=(h=(h="string"==typeof t.canvas?document.querySelector(t.canvas):t.canvas)||document.createElement("canvas")).getContext("2d"),r(h,"click",()=>focus()),N(),h.parentNode||document.body.appendChild(h),h.style.imageRendering="pixelated",h.oncontextmenu=()=>!1,M.pal(),"loading"===document.readyState?r(a,"DOMContentLoaded",()=>i(C)):i(C),M}})();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "litecanvas",
3
- "version": "0.96.0",
3
+ "version": "0.97.0",
4
4
  "description": "Lightweight HTML5 canvas 2D game engine suitable for small projects and creative coding. Inspired by PICO-8 and P5/Processing.",
5
5
  "license": "MIT",
6
6
  "author": "Luiz Bills <luizbills@pm.me>",
@@ -33,7 +33,7 @@
33
33
  "devDependencies": {
34
34
  "@litecanvas/jsdom-extras": "^2.0.1",
35
35
  "@size-limit/preset-small-lib": "^11.2.0",
36
- "@swc/core": "^1.13.2",
36
+ "@swc/core": "^1.13.3",
37
37
  "@types/jsdom": "^21.1.7",
38
38
  "ava": "^6.4.1",
39
39
  "esbuild": "^0.25.8",
@@ -55,8 +55,8 @@
55
55
  "test:watch": "ava --watch",
56
56
  "dev": "esbuild src/web.js --bundle --watch --outfile=samples/dist.js --servedir=samples",
57
57
  "build": "npm run genversion && node script/build.js && size-limit",
58
- "format": "prettier -w src/* samples/* types/* script/* types/*",
59
- "check-types": "npx ts types/*",
58
+ "format": "prettier -w src/* samples/* types/* script/* tests/*",
59
+ "check-types": "npx tsc ./types/*",
60
60
  "genversion": "genversion --es6 src/version.js"
61
61
  },
62
62
  "files": [
package/src/index.js CHANGED
@@ -23,6 +23,8 @@ export default function litecanvas(settings = {}) {
23
23
  elem.addEventListener(evt, callback, false)
24
24
  _browserEventListeners.push(() => elem.removeEventListener(evt, callback, false))
25
25
  },
26
+ /** @type {(str: string) => string} */
27
+ lowerCase = (str) => str.toLowerCase(),
26
28
  /** @type {(ev: Event) => void} */
27
29
  preventDefault = (ev) => ev.preventDefault(),
28
30
  /** @type {(c: CanvasRenderingContext2D | OffscreenCanvasRenderingContext2D) => void} */
@@ -34,13 +36,11 @@ export default function litecanvas(settings = {}) {
34
36
  width: null,
35
37
  height: null,
36
38
  autoscale: true,
37
- pixelart: true,
38
39
  canvas: null,
39
40
  global: true,
40
41
  loop: null,
41
42
  tapEvents: true,
42
43
  keyboardEvents: true,
43
- animate: true,
44
44
  }
45
45
 
46
46
  // setup the settings default values
@@ -75,7 +75,9 @@ export default function litecanvas(settings = {}) {
75
75
  /** @type {number} */
76
76
  _rngSeed = Date.now(),
77
77
  /** @type {string[]} */
78
- _colors = defaultPalette,
78
+ _currentPalette,
79
+ /** @type {string[]} */
80
+ _colors,
79
81
  /** @type {number[]} */
80
82
  _defaultSound = [0.5, 0, 1750, , , 0.3, 1, , , , 600, 0.1],
81
83
  /** @type {string} */
@@ -728,19 +730,46 @@ export default function litecanvas(settings = {}) {
728
730
  _ctx.drawImage(source, ~~x, ~~y)
729
731
  },
730
732
 
733
+ /**
734
+ * Draw a sprite pxiel by pixel represented by a string. Each pixel must be a base 36 number (0-9 or a-z) or a dot.
735
+ *
736
+ * @param {number} x
737
+ * @param {number} y
738
+ * @param {number} width
739
+ * @param {number} height
740
+ * @param {string} pixels
741
+ */
742
+ spr(x, y, width, height, pixels) {
743
+ DEV: assert(isNumber(x), '[litecanvas] spr() 1st param must be a number')
744
+ DEV: assert(isNumber(y), '[litecanvas] spr() 2nd param must be a number')
745
+ DEV: assert(isNumber(width), '[litecanvas] spr() 3rd param must be a number')
746
+ DEV: assert(isNumber(height), '[litecanvas] spr() 4th param must be a number')
747
+ DEV: assert('string' === typeof pixels, '[litecanvas] spr() 5th param must be a string')
748
+
749
+ const chars = pixels.replace(/\s/g, '')
750
+ for (let gridx = 0; gridx < width; gridx++) {
751
+ for (let gridy = 0; gridy < height; gridy++) {
752
+ const char = chars[height * gridy + gridx] || '.'
753
+ if (char !== '.') {
754
+ instance.rectfill(x + gridx, y + gridy, 1, 1, parseInt(char, 16) || 0)
755
+ }
756
+ }
757
+ }
758
+ },
759
+
731
760
  /**
732
761
  * Draw in an OffscreenCanvas and returns its image.
733
762
  *
734
763
  * @param {number} width
735
764
  * @param {number} height
736
- * @param {string[]|drawCallback} drawing
765
+ * @param {drawCallback} callback
737
766
  * @param {object} [options]
738
767
  * @param {number} [options.scale=1]
739
768
  * @param {OffscreenCanvas} [options.canvas]
740
769
  * @returns {ImageBitmap}
741
770
  * @see https://developer.mozilla.org/en-US/docs/Web/API/OffscreenCanvas
742
771
  */
743
- paint(width, height, drawing, options = {}) {
772
+ paint(width, height, callback, options = {}) {
744
773
  DEV: assert(
745
774
  isNumber(width) && width >= 1,
746
775
  '[litecanvas] paint() 1st param must be a positive number'
@@ -750,7 +779,7 @@ export default function litecanvas(settings = {}) {
750
779
  '[litecanvas] paint() 2nd param must be a positive number'
751
780
  )
752
781
  DEV: assert(
753
- 'function' === typeof drawing || Array.isArray(drawing),
782
+ 'function' === typeof callback,
754
783
  '[litecanvas] paint() 3rd param must be a function or array'
755
784
  )
756
785
  DEV: assert(
@@ -765,37 +794,16 @@ export default function litecanvas(settings = {}) {
765
794
  const /** @type {OffscreenCanvas} */
766
795
  canvas = options.canvas || new OffscreenCanvas(1, 1),
767
796
  scale = options.scale || 1,
768
- contextOriginal = _ctx
797
+ currentContext = _ctx // context backup
769
798
 
770
799
  canvas.width = width * scale
771
800
  canvas.height = height * scale
772
801
 
773
802
  _ctx = canvas.getContext('2d')
774
803
  _ctx.scale(scale, scale)
804
+ callback(_ctx)
775
805
 
776
- // draw pixel art if `draw` is a array
777
- if (Array.isArray(drawing)) {
778
- let x = 0,
779
- y = 0
780
-
781
- _ctx.imageSmoothingEnabled = false
782
-
783
- for (const str of drawing) {
784
- for (const color of str) {
785
- if (' ' !== color && '.' !== color) {
786
- // support for 16-color palette using hex (from 0 to f)
787
- instance.rectfill(x, y, 1, 1, parseInt(color, 16))
788
- }
789
- x++
790
- }
791
- y++
792
- x = 0
793
- }
794
- } else {
795
- drawing(_ctx)
796
- }
797
-
798
- _ctx = contextOriginal // restore the context
806
+ _ctx = currentContext // restore the context
799
807
 
800
808
  return canvas.transferToImageBitmap()
801
809
  },
@@ -1034,7 +1042,7 @@ export default function litecanvas(settings = {}) {
1034
1042
  '[litecanvas] listen() 2nd param must be a function'
1035
1043
  )
1036
1044
 
1037
- eventName = eventName.toLowerCase()
1045
+ eventName = lowerCase(eventName)
1038
1046
 
1039
1047
  _eventListeners[eventName] = _eventListeners[eventName] || new Set()
1040
1048
  _eventListeners[eventName].add(callback)
@@ -1058,7 +1066,7 @@ export default function litecanvas(settings = {}) {
1058
1066
  '[litecanvas] emit() 1st param must be a string'
1059
1067
  )
1060
1068
  if (_initialized) {
1061
- eventName = eventName.toLowerCase()
1069
+ eventName = lowerCase(eventName)
1062
1070
 
1063
1071
  triggerEvent('before:' + eventName, arg1, arg2, arg3, arg4)
1064
1072
  triggerEvent(eventName, arg1, arg2, arg3, arg4)
@@ -1067,7 +1075,7 @@ export default function litecanvas(settings = {}) {
1067
1075
  },
1068
1076
 
1069
1077
  /**
1070
- * Set or reset the color palette
1078
+ * Set or reset the color palette.
1071
1079
  *
1072
1080
  * @param {string[]} [colors]
1073
1081
  */
@@ -1077,6 +1085,31 @@ export default function litecanvas(settings = {}) {
1077
1085
  '[litecanvas] pal() 1st param must be a array of strings'
1078
1086
  )
1079
1087
  _colors = colors
1088
+ _currentPalette = [...colors]
1089
+ },
1090
+
1091
+ /**
1092
+ * Swap two colors of the current palette.
1093
+ *
1094
+ * If called without arguments, reset the current palette.
1095
+ *
1096
+ * @param {number?} a
1097
+ * @param {number?} b
1098
+ */
1099
+ palc(a, b) {
1100
+ DEV: assert(
1101
+ null == a || (isNumber(a) && a >= 0),
1102
+ '[litecanvas] palc() 1st param must be a positive number'
1103
+ )
1104
+ DEV: assert(
1105
+ isNumber(a) ? isNumber(b) && b >= 0 : null == b,
1106
+ '[litecanvas] palc() 2nd param must be a positive number'
1107
+ )
1108
+ if (a == null) {
1109
+ _colors = [..._currentPalette]
1110
+ } else {
1111
+ ;[_colors[a], _colors[b]] = [_colors[b], _colors[a]]
1112
+ }
1080
1113
  },
1081
1114
 
1082
1115
  /**
@@ -1454,7 +1487,7 @@ export default function litecanvas(settings = {}) {
1454
1487
  * @returns {boolean}
1455
1488
  */
1456
1489
  const keyCheck = (keySet, key = '') => {
1457
- key = key.toLowerCase()
1490
+ key = lowerCase(key)
1458
1491
  return !key ? keySet.size > 0 : keySet.has('space' === key ? ' ' : key)
1459
1492
  }
1460
1493
 
@@ -1462,7 +1495,7 @@ export default function litecanvas(settings = {}) {
1462
1495
  let _lastKey = ''
1463
1496
 
1464
1497
  on(root, 'keydown', (/** @type {KeyboardEvent} */ event) => {
1465
- const key = event.key.toLowerCase()
1498
+ const key = lowerCase(event.key)
1466
1499
  if (!_keysDown.has(key)) {
1467
1500
  _keysDown.add(key)
1468
1501
  _keysPress.add(key)
@@ -1471,7 +1504,7 @@ export default function litecanvas(settings = {}) {
1471
1504
  })
1472
1505
 
1473
1506
  on(root, 'keyup', (/** @type {KeyboardEvent} */ event) => {
1474
- _keysDown.delete(event.key.toLowerCase())
1507
+ _keysDown.delete(lowerCase(event.key))
1475
1508
  })
1476
1509
 
1477
1510
  on(root, 'blur', () => _keysDown.clear())
@@ -1523,9 +1556,8 @@ export default function litecanvas(settings = {}) {
1523
1556
  }
1524
1557
 
1525
1558
  function drawFrame() {
1526
- if (!settings.animate) {
1527
- return instance.emit('draw', _ctx)
1528
- }
1559
+ // request the next frame
1560
+ _rafid = raf(drawFrame)
1529
1561
 
1530
1562
  let now = Date.now()
1531
1563
  let updated = 0
@@ -1555,9 +1587,6 @@ export default function litecanvas(settings = {}) {
1555
1587
  )
1556
1588
  }
1557
1589
  }
1558
-
1559
- // request the next frame
1560
- _rafid = raf(drawFrame)
1561
1590
  }
1562
1591
 
1563
1592
  function setupCanvas() {
@@ -1582,15 +1611,13 @@ export default function litecanvas(settings = {}) {
1582
1611
 
1583
1612
  on(_canvas, 'click', () => focus())
1584
1613
 
1585
- /** @ts-ignore */
1586
- _canvas.style = ''
1587
-
1588
1614
  resizeCanvas()
1589
1615
 
1590
1616
  if (!_canvas.parentNode) {
1591
1617
  document.body.appendChild(_canvas)
1592
1618
  }
1593
1619
 
1620
+ _canvas.style.imageRendering = 'pixelated'
1594
1621
  _canvas.oncontextmenu = () => false
1595
1622
  }
1596
1623
 
@@ -1632,10 +1659,7 @@ export default function litecanvas(settings = {}) {
1632
1659
  }
1633
1660
 
1634
1661
  // set canvas image rendering properties
1635
- if (settings.pixelart) {
1636
- _ctx.imageSmoothingEnabled = false
1637
- _canvas.style.imageRendering = 'pixelated'
1638
- }
1662
+ _ctx.imageSmoothingEnabled = false
1639
1663
 
1640
1664
  // set the default text align and baseline
1641
1665
  instance.textalign('start', 'top')
@@ -1643,11 +1667,6 @@ export default function litecanvas(settings = {}) {
1643
1667
  // trigger "resized" event
1644
1668
  // note: not triggered before the "init" event
1645
1669
  instance.emit('resized', _scale)
1646
-
1647
- // force redraw when the canvas is not animated
1648
- if (!settings.animate) {
1649
- raf(drawFrame)
1650
- }
1651
1670
  }
1652
1671
 
1653
1672
  /**
@@ -1694,6 +1713,9 @@ export default function litecanvas(settings = {}) {
1694
1713
 
1695
1714
  setupCanvas()
1696
1715
 
1716
+ // init the color palette
1717
+ instance.pal()
1718
+
1697
1719
  if ('loading' === document.readyState) {
1698
1720
  on(root, 'DOMContentLoaded', () => raf(init))
1699
1721
  } else {
package/src/version.js CHANGED
@@ -1,2 +1,2 @@
1
1
  // Generated by genversion.
2
- export const version = '0.96.0'
2
+ export const version = '0.97.0'
package/types/global.d.ts CHANGED
@@ -356,7 +356,7 @@ declare global {
356
356
  */
357
357
  function textalign(align: CanvasTextAlign, baseline: CanvasTextBaseline): void
358
358
 
359
- /** IMAGE GRAPHICS API */
359
+ /** BASIC GRAPHICS API */
360
360
  /**
361
361
  * Draw an image
362
362
  *
@@ -365,19 +365,33 @@ declare global {
365
365
  * @param source
366
366
  */
367
367
  function image(x: number, y: number, source: CanvasImageSource): void
368
+ /**
369
+ * Draw a sprite pxiel by pixel represented by a string. Each pixel must be a base 36 number or a dot:
370
+ *
371
+ * - A base 36 number (`0-9` or `a-z`) represent a pixel color (supporting color palettes with max 36 colors).
372
+ * - A dot (`.`) represent a transparent pixel.
373
+ * - Spaces and lines breaks are ignored and can be used to improve the visualization.
374
+ *
375
+ * @param x the position X of the first pixel
376
+ * @param y the position Y of the first pixel
377
+ * @param width the width of the sprite
378
+ * @param height the height of the sprite
379
+ * @param pixels
380
+ */
381
+ function spr(x: number, y: number, width: number, height: number, pixels: string): void
368
382
  /**
369
383
  * Draw in an OffscreenCanvas and returns its image.
370
384
  *
371
385
  * @param width
372
386
  * @param height
373
- * @param drawing
387
+ * @param callback
374
388
  * @param [options]
375
389
  * @see https://developer.mozilla.org/en-US/docs/Web/API/OffscreenCanvas
376
390
  */
377
391
  function paint(
378
392
  width: number,
379
393
  height: number,
380
- drawing: string[] | drawCallback,
394
+ callback: drawCallback,
381
395
  options?: {
382
396
  scale?: number
383
397
  canvas?: OffscreenCanvas
@@ -531,11 +545,22 @@ declare global {
531
545
  */
532
546
  function emit(event: string, arg1?: any, arg2?: any, arg3?: any, arg4?: any): void
533
547
  /**
534
- * Set or reset the color palette
548
+ * Set or reset the color palette.
535
549
  *
536
550
  * @param [colors]
537
551
  */
538
552
  function pal(colors?: string[]): void
553
+ /**
554
+ * Swap two colors of the current palette.
555
+ *
556
+ * If called without arguments, reset the current palette.
557
+ *
558
+ * Note: `palc()` don't affect drawings made with `image()`.
559
+ *
560
+ * @param a
561
+ * @param b
562
+ */
563
+ function palc(a?: number, b?: number): void
539
564
  /**
540
565
  * Define or update a instance property
541
566
  *
package/types/types.d.ts CHANGED
@@ -350,7 +350,7 @@ type LitecanvasInstance = {
350
350
  */
351
351
  textalign(align: CanvasTextAlign, baseline: CanvasTextBaseline): void
352
352
 
353
- /** IMAGE GRAPHICS API */
353
+ /** BASIC GRAPHICS API */
354
354
  /**
355
355
  * Draw an image
356
356
  *
@@ -359,19 +359,33 @@ type LitecanvasInstance = {
359
359
  * @param source
360
360
  */
361
361
  image(x: number, y: number, source: CanvasImageSource): void
362
+ /**
363
+ * Draw a sprite pxiel by pixel represented by a string. Each pixel must be a base 36 number or a dot:
364
+ *
365
+ * - A base 36 number (`0-9` or `a-z`) represent a pixel color (supporting color palettes with max 36 colors).
366
+ * - A dot (`.`) represent a transparent pixel.
367
+ * - Spaces and lines breaks are ignored and can be used to improve the visualization.
368
+ *
369
+ * @param x the position X of the first pixel
370
+ * @param y the position Y of the first pixel
371
+ * @param width the width of the sprite
372
+ * @param height the height of the sprite
373
+ * @param pixels
374
+ */
375
+ spr(x: number, y: number, width: number, height: number, pixels: string): void
362
376
  /**
363
377
  * Draw in an OffscreenCanvas and returns its image.
364
378
  *
365
379
  * @param width
366
380
  * @param height
367
- * @param drawing
381
+ * @param callback
368
382
  * @param [options]
369
383
  * @see https://developer.mozilla.org/en-US/docs/Web/API/OffscreenCanvas
370
384
  */
371
385
  paint(
372
386
  width: number,
373
387
  height: number,
374
- drawing: string[] | drawCallback,
388
+ callback: drawCallback,
375
389
  options?: {
376
390
  scale?: number
377
391
  canvas?: OffscreenCanvas
@@ -528,11 +542,22 @@ type LitecanvasInstance = {
528
542
  */
529
543
  def(key: string, value: any): void
530
544
  /**
531
- * Set or reset the color palette
545
+ * Set or reset the color palette.
532
546
  *
533
547
  * @param [colors]
534
548
  */
535
549
  pal(colors?: string[]): void
550
+ /**
551
+ * Swap two colors of the current palette.
552
+ *
553
+ * If called without arguments, reset the current palette.
554
+ *
555
+ * Note: `palc()` don't affect drawings made with `image()`.
556
+ *
557
+ * @param a
558
+ * @param b
559
+ */
560
+ palc(a?: number, b?: number): void
536
561
  /**
537
562
  * The scale of the game's delta time (dt).
538
563
  * Values higher than 1 increase the speed of time, while values smaller than 1 decrease it.
@@ -605,13 +630,6 @@ type LitecanvasOptions = {
605
630
  * Note: Only works if the option "width" was specified.
606
631
  */
607
632
  autoscale?: boolean | number
608
- /**
609
- * If `true`, the pixel art images won't look blurry.
610
- * Also, disables canvas built-in antialias.
611
- *
612
- * Default: `true`
613
- */
614
- pixelart?: boolean
615
633
  /**
616
634
  * If `true` (default), all methods and properties of the engine will be exposed to the global scope (window).
617
635
  */
@@ -646,12 +664,6 @@ type LitecanvasOptions = {
646
664
  * Useful when you want to implement your keyboard handler.
647
665
  */
648
666
  keyboardEvents?: boolean
649
- /**
650
- * default: `true`
651
- *
652
- * if `false` stops the code in `update()` and `draw()` from running repeatedly. By default, tries to run these functions 60 times per second.
653
- */
654
- animate?: boolean
655
667
  }
656
668
 
657
669
  type LitecanvasGameLoop = {