litecanvas 0.103.7 → 0.201.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/dist/dist.dev.js CHANGED
@@ -115,7 +115,7 @@
115
115
  var assert = (condition, message = "Assertion failed") => {
116
116
  if (!condition) throw new Error("[litecanvas] " + message);
117
117
  };
118
- var version = "0.103.7";
118
+ var version = "0.201.0";
119
119
  function litecanvas(settings = {}) {
120
120
  const root = window,
121
121
  math = Math,
@@ -165,7 +165,6 @@
165
165
  _colorPalette = defaultPalette,
166
166
  _colorPaletteState = [],
167
167
  _defaultSound = [0.5, 0, 1750, , , 0.3, 1, , , , 600, 0.1],
168
- _coreEvents = "init,update,draw,tap,untap,tapping,tapped,resized",
169
168
  _mathFunctions =
170
169
  "PI,sin,cos,atan2,hypot,tan,abs,ceil,floor,trunc,min,max,pow,sqrt,sign,exp",
171
170
  _eventListeners = {};
@@ -686,7 +685,7 @@
686
685
  if (align) _ctx.textAlign = align;
687
686
  if (baseline) _ctx.textBaseline = baseline;
688
687
  },
689
- image(x, y, source2) {
688
+ image(x, y, source) {
690
689
  DEV: assert(
691
690
  isNumber(x),
692
691
  loggerPrefix + "image() 1st param must be a number",
@@ -695,7 +694,7 @@
695
694
  isNumber(y),
696
695
  loggerPrefix + "image() 2nd param must be a number",
697
696
  );
698
- _ctx.drawImage(source2, ~~x, ~~y);
697
+ _ctx.drawImage(source, ~~x, ~~y);
699
698
  },
700
699
  spr(x, y, pixels) {
701
700
  DEV: assert(
@@ -881,7 +880,7 @@
881
880
  );
882
881
  loadPlugin(callback, config);
883
882
  },
884
- listen(eventName, callback) {
883
+ listen: (eventName, callback) => {
885
884
  DEV: assert(
886
885
  "string" === typeof eventName,
887
886
  loggerPrefix + "listen() 1st param must be a string",
@@ -893,7 +892,20 @@
893
892
  eventName = lowerCase(eventName);
894
893
  _eventListeners[eventName] = _eventListeners[eventName] || new Set();
895
894
  _eventListeners[eventName].add(callback);
896
- return () => _eventListeners[eventName]?.delete(callback);
895
+ },
896
+ unlisten: (eventName, callback) => {
897
+ DEV: assert(
898
+ "string" === typeof eventName,
899
+ loggerPrefix + "unlisten() 1st param must be a string",
900
+ );
901
+ DEV: assert(
902
+ "function" === typeof callback,
903
+ loggerPrefix + "unlisten() 2nd param must be a function",
904
+ );
905
+ eventName = lowerCase(eventName);
906
+ if (_eventListeners[eventName]) {
907
+ _eventListeners[eventName].delete(callback);
908
+ }
897
909
  },
898
910
  emit(eventName, arg1, arg2, arg3, arg4) {
899
911
  DEV: assert(
@@ -903,6 +915,9 @@
903
915
  if (_initialized) {
904
916
  eventName = lowerCase(eventName);
905
917
  triggerEvent("before:" + eventName, arg1, arg2, arg3, arg4);
918
+ if (!settings.loop && "function" === typeof root[eventName]) {
919
+ root[eventName](arg1, arg2, arg3, arg4);
920
+ }
906
921
  triggerEvent(eventName, arg1, arg2, arg3, arg4);
907
922
  triggerEvent("after:" + eventName, arg1, arg2, arg3, arg4);
908
923
  }
@@ -1216,9 +1231,9 @@
1216
1231
  }
1217
1232
  _canvas = _canvas || document.createElement("canvas");
1218
1233
  DEV: assert(
1219
- "CANVAS" === _canvas.tagName,
1234
+ _canvas instanceof HTMLElement && "CANVAS" === _canvas.tagName,
1220
1235
  loggerPrefix +
1221
- 'litecanvas() option "canvas" should be a canvas element or string (CSS selector)',
1236
+ 'litecanvas() option "canvas" should be a canvas element or string (CSS selector of a canvas)',
1222
1237
  );
1223
1238
  _ctx = _canvas.getContext("2d");
1224
1239
  on(_canvas, "click", () => focus());
@@ -1271,9 +1286,10 @@
1271
1286
  instance.emit("resized", _canvasScale);
1272
1287
  }
1273
1288
  function triggerEvent(eventName, arg1, arg2, arg3, arg4) {
1274
- if (!_eventListeners[eventName]) return;
1275
- for (const callback of _eventListeners[eventName]) {
1276
- callback(arg1, arg2, arg3, arg4);
1289
+ if (_eventListeners[eventName]) {
1290
+ for (const callback of _eventListeners[eventName]) {
1291
+ callback(arg1, arg2, arg3, arg4);
1292
+ }
1277
1293
  }
1278
1294
  }
1279
1295
  function loadPlugin(callback, config) {
@@ -1301,12 +1317,11 @@
1301
1317
  DEV: console.info(loggerPrefix + `version ${version} started`);
1302
1318
  DEV: console.debug(loggerPrefix + `litecanvas() options =`, settings);
1303
1319
  setupCanvas();
1304
- const source = settings.loop ? settings.loop : root;
1305
- for (const event of _coreEvents.split(",")) {
1306
- DEV: if (root === source && source[event]) {
1307
- console.info(loggerPrefix + `using window.${event}()`);
1320
+ if (settings.loop) {
1321
+ for (const eventName in settings.loop) {
1322
+ if (settings.loop[eventName])
1323
+ instance.listen(eventName, settings.loop[eventName]);
1308
1324
  }
1309
- if (source[event]) instance.listen(event, source[event]);
1310
1325
  }
1311
1326
  if ("loading" === document.readyState) {
1312
1327
  on(root, "DOMContentLoaded", () => raf(init));
package/dist/dist.js CHANGED
@@ -161,7 +161,6 @@
161
161
  _colorPalette = defaultPalette,
162
162
  _colorPaletteState = [],
163
163
  _defaultSound = [0.5, 0, 1750, , , 0.3, 1, , , , 600, 0.1],
164
- _coreEvents = "init,update,draw,tap,untap,tapping,tapped,resized",
165
164
  _mathFunctions =
166
165
  "PI,sin,cos,atan2,hypot,tan,abs,ceil,floor,trunc,min,max,pow,sqrt,sign,exp",
167
166
  _eventListeners = {};
@@ -316,8 +315,8 @@
316
315
  if (align) _ctx.textAlign = align;
317
316
  if (baseline) _ctx.textBaseline = baseline;
318
317
  },
319
- image(x, y, source2) {
320
- _ctx.drawImage(source2, ~~x, ~~y);
318
+ image(x, y, source) {
319
+ _ctx.drawImage(source, ~~x, ~~y);
321
320
  },
322
321
  spr(x, y, pixels) {
323
322
  const rows = pixels.trim().split("\n");
@@ -409,16 +408,24 @@
409
408
  use(callback, config = {}) {
410
409
  loadPlugin(callback, config);
411
410
  },
412
- listen(eventName, callback) {
411
+ listen: (eventName, callback) => {
413
412
  eventName = lowerCase(eventName);
414
413
  _eventListeners[eventName] = _eventListeners[eventName] || new Set();
415
414
  _eventListeners[eventName].add(callback);
416
- return () => _eventListeners[eventName]?.delete(callback);
415
+ },
416
+ unlisten: (eventName, callback) => {
417
+ eventName = lowerCase(eventName);
418
+ if (_eventListeners[eventName]) {
419
+ _eventListeners[eventName].delete(callback);
420
+ }
417
421
  },
418
422
  emit(eventName, arg1, arg2, arg3, arg4) {
419
423
  if (_initialized) {
420
424
  eventName = lowerCase(eventName);
421
425
  triggerEvent("before:" + eventName, arg1, arg2, arg3, arg4);
426
+ if (!settings.loop && "function" === typeof root[eventName]) {
427
+ root[eventName](arg1, arg2, arg3, arg4);
428
+ }
422
429
  triggerEvent(eventName, arg1, arg2, arg3, arg4);
423
430
  triggerEvent("after:" + eventName, arg1, arg2, arg3, arg4);
424
431
  }
@@ -697,9 +704,10 @@
697
704
  instance.emit("resized", _canvasScale);
698
705
  }
699
706
  function triggerEvent(eventName, arg1, arg2, arg3, arg4) {
700
- if (!_eventListeners[eventName]) return;
701
- for (const callback of _eventListeners[eventName]) {
702
- callback(arg1, arg2, arg3, arg4);
707
+ if (_eventListeners[eventName]) {
708
+ for (const callback of _eventListeners[eventName]) {
709
+ callback(arg1, arg2, arg3, arg4);
710
+ }
703
711
  }
704
712
  }
705
713
  function loadPlugin(callback, config) {
@@ -720,9 +728,11 @@
720
728
  root.ENGINE = instance;
721
729
  }
722
730
  setupCanvas();
723
- const source = settings.loop ? settings.loop : root;
724
- for (const event of _coreEvents.split(",")) {
725
- if (source[event]) instance.listen(event, source[event]);
731
+ if (settings.loop) {
732
+ for (const eventName in settings.loop) {
733
+ if (settings.loop[eventName])
734
+ instance.listen(eventName, settings.loop[eventName]);
735
+ }
726
736
  }
727
737
  if ("loading" === document.readyState) {
728
738
  on(root, "DOMContentLoaded", () => raf(init));
package/dist/dist.min.js CHANGED
@@ -1 +1 @@
1
- (()=>{var e=["#211e20","#555568","#a0a08b","#e9efec"];window.litecanvas=function(t={}){let a,l=window,n=Math,i=performance,o=2*n.PI,r=requestAnimationFrame,s=[],f=(e,t,a)=>{e.addEventListener(t,a,!1),s.push(()=>e.removeEventListener(t,a,!1))},d=(a=new AudioContext,l.zzfxV=1,(e=1,t=.05,n=220,i=0,o=0,r=.1,s=0,f=1,d=0,c=0,p=0,u=0,h=0,g=0,m=0,v=0,w=0,x=1,y=0,b=0,k=0)=>{let z=Math,E=2*z.PI,P=d*=500*E/44100/44100,T=n*=(1-t+2*t*z.random(t=[]))*E/44100,C=0,I=0,D=0,L=1,S=0,A=0,M=0,H=k<0?-1:1,N=E*H*k*2/44100,W=z.cos(N),q=z.sin,B=q(N)/4,O=1+B,V=-2*W/O,R=(1-B)/O,F=(1+H*W)/2/O,G=-(H+W)/O,X=0,Y=0,$=0,j=0;for(i=44100*i+9,y*=44100,o*=44100,r*=44100,w*=44100,c*=500*E/85766121e6,m*=E/44100,p*=E/44100,u*=44100,h=44100*h|0,e*=.3*l.zzfxV,H=i+y+o+r+w|0;D<H;t[D++]=M*e)++A%(100*v|0)||(M=s?1<s?2<s?3<s?q(C*C):z.max(z.min(z.tan(C),1),-1):1-(2*C/E%2+2)%2:1-4*z.abs(z.round(C/E)-C/E):q(C),M=(h?1-b+b*q(E*D/h):1)*(M<0?-1:1)*z.abs(M)**f*(D<i?D/i:D<i+y?1-(D-i)/y*(1-x):D<i+y+o?x:D<H-w?(H-D-w)/r*x:0),M=w?M/2+(w>D?0:(D<H-w?1:(H-D)/w)*t[D-w|0]/2/e):M,k&&(M=j=F*X+G*(X=Y)+F*(Y=M)-R*$-V*($=j))),C+=(N=(n+=d+=c)*z.cos(m*I++))+N*g*q(D**5),L&&++L>u&&(n+=p,T+=p,L=0),!h||++S%h||(n=T,d=P,L=L||1);(e=a.createBuffer(1,H,44100)).getChannelData(0).set(t),(n=a.createBufferSource()).buffer=e,n.connect(a.destination),n.start()});t=Object.assign({width:null,height:null,autoscale:!0,canvas:null,global:!0,loop:null,tapEvents:!0,keyboardEvents:!0},t);let c=!1,p=!0,u,h=1,g,m=.5,v=1,w,x=1e3/60,y,b,k=3,z="sans-serif",E=20,P=1.2,T=Date.now(),C=e,I=[],D=[.5,0,1750,,,.3,1,,,,600,.1],L={},S={W:0,H:0,T:0,MX:-1,MY:-1,TWO_PI:o,HALF_PI:o/4,lerp:(e,t,a)=>e+a*(t-e),deg2rad:e=>n.PI/180*e,rad2deg:e=>180/n.PI*e,round:(e,t=0)=>{if(!t)return n.round(e);let a=10**t;return n.round(e*a)/a},clamp:(e,t,a)=>e<t?t:e>a?a:e,dist:(e,t,a,l)=>n.hypot(a-e,l-t),wrap:(e,t,a)=>e-(a-t)*n.floor((e-t)/(a-t)),map(e,t,a,l,n,i){let o=(e-t)/(a-t)*(n-l)+l;return i?S.clamp(o,l,n):o},norm:(e,t,a)=>S.map(e,t,a,0,1),rand:(e=0,t=1)=>(T=(1664525*T+0x3c6ef35f)%0x100000000)/0x100000000*(t-e)+e,randi:(e=0,t=1)=>~~S.rand(e,t+1),rseed(e){T=~~e},cls(e){null==e?g.clearRect(0,0,S.W,S.H):S.rectfill(0,0,S.W,S.H,e)},rect(e,t,a,l,n,i){g.beginPath(),g[i?"roundRect":"rect"](~~e-m,~~t-m,~~a+2*m,~~l+2*m,i),S.stroke(n)},rectfill(e,t,a,l,n,i){g.beginPath(),g[i?"roundRect":"rect"](~~e,~~t,~~a,~~l,i),S.fill(n)},circ(e,t,a,l){g.beginPath(),g.arc(~~e,~~t,~~a,0,o),S.stroke(l)},circfill(e,t,a,l){g.beginPath(),g.arc(~~e,~~t,~~a,0,o),S.fill(l)},oval(e,t,a,l,n){g.beginPath(),g.ellipse(~~e,~~t,~~a,~~l,0,0,o),S.stroke(n)},ovalfill(e,t,a,l,n){g.beginPath(),g.ellipse(~~e,~~t,~~a,~~l,0,0,o),S.fill(n)},shape(e){g.beginPath();for(let t=0;t<e.length;t+=2)0===t?g.moveTo(~~e[t],~~e[t+1]):g.lineTo(~~e[t],~~e[t+1]);g.lineTo(~~e[0],~~e[1])},line(e,t,a,l,n){g.beginPath();let i=.5*(0!==m&&~~e==~~a),o=.5*(0!==m&&~~t==~~l);g.moveTo(~~e+i,~~t+o),g.lineTo(~~a+i,~~l+o),S.stroke(n)},linewidth(e){g.lineWidth=~~e,m=.5*(0!=~~e%2)},linedash(e,t=0){g.setLineDash(e),g.lineDashOffset=t},text(e,t,a,l=k,n="normal"){g.font=`${n} ${E}px ${z}`,g.fillStyle=W(l);let i=(""+a).split("\n");for(let a=0;a<i.length;a++)g.fillText(i[a],~~e,~~t+E*P*a)},textgap(e){P=e},textfont(e){z=e},textsize(e){E=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){let l=a.trim().split("\n");for(let a=0;a<l.length;a++){let n=l[a].trim();for(let l=0;l<n.length;l++){let i=n[l];"."!==i&&" "!==i&&S.rectfill(e+l,t+a,1,1,parseInt(i,36)||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=S.clamp(e,0,1)},fill(e){g.fillStyle=W(e),g.fill()},stroke(e){g.strokeStyle=W(e),g.stroke()},clip(e){g.beginPath(),e(g),g.clip()},sfx:(e,t=0,a=1)=>!!l.zzfxV&&(!navigator.userActivation||!!navigator.userActivation.hasBeenActive)&&(e=e||D,(0!==t||1!==a)&&((e=e.slice())[0]=a*(e[0]||1),e[10]=~~e[10]+t),d.apply(0,e),e),volume(e){l.zzfxV=e},canvas:()=>u,use(e,t={}){var a=e,l=t;let n=a(S,l);for(let e in n)S.def(e,n[e])},listen:(e,t)=>(L[e=e.toLowerCase()]=L[e]||new Set,L[e].add(t),()=>L[e]?.delete(t)),emit(e,t,a,l,n){c&&(N("before:"+(e=e.toLowerCase()),t,a,l,n),N(e,t,a,l,n),N("after:"+e,t,a,l,n))},pal(t,a=3){C=t||e,I=[],k=a},palc(e,t){null==e?I=[]:I[e]=t},def(e,a){S[e]=a,t.global&&(l[e]=a)},timescale(e){v=e},framerate(e){x=1e3/~~e},stat(e){let a={index:e,value:[t,c,x/1e3,h,L,C,D,v,l.zzfxV,T,E,z,I,P][e]};return S.emit("stat",a),a.value},pause(){p=!0,cancelAnimationFrame(b)},resume(){c&&p&&(p=!1,y=x,w=i.now(),b=r(M))},paused:()=>p,quit(){for(let e of(S.emit("quit"),S.pause(),c=!1,L={},s))e();if(t.global){for(let e in S)delete l[e];delete l.ENGINE}}};for(let e of"PI,sin,cos,atan2,hypot,tan,abs,ceil,floor,trunc,min,max,pow,sqrt,sign,exp".split(","))S[e]=n[e];function A(){if(t.autoscale&&f(l,"resize",H),t.tapEvents){let e=e=>[(e.pageX-u.offsetLeft)/h,(e.pageY-u.offsetTop)/h],t=new Map,a=(e,a,l)=>{let n={x:a,y:l,xi:a,yi:l,t:i.now()};return t.set(e,n),n},n=(e,l,n)=>{let i=t.get(e)||a(e);i.x=l,i.y=n},o=e=>e&&i.now()-e.t<=300,r=!1;f(u,"mousedown",t=>{if(0===t.button){t.preventDefault();let[l,n]=e(t);S.emit("tap",l,n,0),a(0,l,n),r=!0}}),f(u,"mouseup",a=>{if(0===a.button){a.preventDefault();let l=t.get(0),[n,i]=e(a);o(l)&&S.emit("tapped",l.xi,l.yi,0),S.emit("untap",n,i,0),t.delete(0),r=!1}}),f(l,"mousemove",t=>{t.preventDefault();let[a,l]=e(t);S.def("MX",a),S.def("MY",l),r&&(S.emit("tapping",a,l,0),n(0,a,l))}),f(u,"touchstart",t=>{for(let l of(t.preventDefault(),t.changedTouches)){let[t,n]=e(l);S.emit("tap",t,n,l.identifier+1),a(l.identifier+1,t,n)}}),f(u,"touchmove",t=>{for(let a of(t.preventDefault(),t.changedTouches)){let[t,l]=e(a);S.emit("tapping",t,l,a.identifier+1),n(a.identifier+1,t,l)}});let s=e=>{e.preventDefault();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)||(o(l)&&S.emit("tapped",l.xi,l.yi,e),S.emit("untap",l.x,l.y,e),t.delete(e))};f(u,"touchend",s),f(u,"touchcancel",s),f(l,"blur",()=>{for(let[e,a]of(r=!1,t))S.emit("untap",a.x,a.y,e),t.delete(e)})}if(t.keyboardEvents){let e=new Set,t=new Set,a=(e,t="")=>(t=t.toLowerCase())?e.has("space"===t?" ":t):e.size>0,n="";f(l,"keydown",a=>{let l=a.key.toLowerCase();e.has(l)||(e.add(l),t.add(l),n=" "===l?"space":l)}),f(l,"keyup",t=>{e.delete(t.key.toLowerCase())}),f(l,"blur",()=>e.clear()),S.listen("after:update",()=>t.clear()),S.def("iskeydown",t=>a(e,t)),S.def("iskeypressed",e=>a(t,e)),S.def("lastkey",()=>n)}c=!0,S.resume(),S.emit("init",S)}function M(){b=r(M);let e=i.now(),t=0,a=e-w;for(w=e,y+=a<100?a:x;y>=x;){t++,y-=x;let e=x/1e3*v;S.emit("update",e,t),S.def("T",S.T+e)}t&&(S.emit("draw",g),t>1&&(y=0))}function H(){let e=t.width>0?t.width:innerWidth,a=t.width>0?t.height||t.width:innerHeight;if(S.def("W",e),S.def("H",a),u.width=e,u.height=a,t.autoscale){let l=+t.autoscale;u.style.display||(u.style.display="block",u.style.margin="auto"),h=n.min(innerWidth/e,innerHeight/a),h=l>1&&h>l?l:h,u.style.width=e*h+"px",u.style.height=a*h+"px"}g.imageSmoothingEnabled=!1,S.textalign("start","top"),S.emit("resized",h)}function N(e,t,a,l,n){if(L[e])for(let i of L[e])i(t,a,l,n)}function W(e){return C[~~(I[e]??e)%C.length]}if(t.global){if(l.ENGINE)throw Error("only one global litecanvas is allowed");Object.assign(l,S),l.ENGINE=S}g=(u=(u="string"==typeof t.canvas?document.querySelector(t.canvas):t.canvas)||document.createElement("canvas")).getContext("2d"),f(u,"click",()=>focus()),H(),u.parentNode||document.body.appendChild(u),u.style.imageRendering="pixelated",u.oncontextmenu=()=>!1;let q=t.loop?t.loop:l;for(let e of"init,update,draw,tap,untap,tapping,tapped,resized".split(","))q[e]&&S.listen(e,q[e]);return"loading"===document.readyState?f(l,"DOMContentLoaded",()=>r(A)):b=r(A),S}})();
1
+ (()=>{var e=["#211e20","#555568","#a0a08b","#e9efec"];window.litecanvas=function(t={}){let a,l=window,n=Math,i=performance,o=2*n.PI,r=requestAnimationFrame,s=[],f=(e,t,a)=>{e.addEventListener(t,a,!1),s.push(()=>e.removeEventListener(t,a,!1))},d=(a=new AudioContext,l.zzfxV=1,(e=1,t=.05,n=220,i=0,o=0,r=.1,s=0,f=1,d=0,c=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,P=2*E.PI,T=d*=500*P/44100/44100,z=n*=(1-t+2*t*E.random(t=[]))*P/44100,C=0,I=0,L=0,D=1,S=0,A=0,M=0,H=k<0?-1:1,N=P*H*k*2/44100,W=E.cos(N),q=E.sin,B=q(N)/4,O=1+B,V=-2*W/O,R=(1-B)/O,F=(1+H*W)/2/O,G=-(H+W)/O,X=0,Y=0,$=0,j=0;for(i=44100*i+9,y*=44100,o*=44100,r*=44100,w*=44100,c*=500*P/85766121e6,g*=P/44100,u*=P/44100,p*=44100,h=44100*h|0,e*=.3*l.zzfxV,H=i+y+o+r+w|0;L<H;t[L++]=M*e)++A%(100*v|0)||(M=s?1<s?2<s?3<s?q(C*C):E.max(E.min(E.tan(C),1),-1):1-(2*C/P%2+2)%2:1-4*E.abs(E.round(C/P)-C/P):q(C),M=(h?1-b+b*q(P*L/h):1)*(M<0?-1:1)*E.abs(M)**f*(L<i?L/i:L<i+y?1-(L-i)/y*(1-x):L<i+y+o?x:L<H-w?(H-L-w)/r*x:0),M=w?M/2+(w>L?0:(L<H-w?1:(H-L)/w)*t[L-w|0]/2/e):M,k&&(M=j=F*X+G*(X=Y)+F*(Y=M)-R*$-V*($=j))),C+=(N=(n+=d+=c)*E.cos(g*I++))+N*m*q(L**5),D&&++D>p&&(n+=u,z+=u,D=0),!h||++S%h||(n=z,d=T,D=D||1);(e=a.createBuffer(1,H,44100)).getChannelData(0).set(t),(n=a.createBufferSource()).buffer=e,n.connect(a.destination),n.start()});t=Object.assign({width:null,height:null,autoscale:!0,canvas:null,global:!0,loop:null,tapEvents:!0,keyboardEvents:!0},t);let c=!1,u=!0,p,h=1,m,g=.5,v=1,w,x=1e3/60,y,b,k=3,E="sans-serif",P=20,T=1.2,z=Date.now(),C=e,I=[],L=[.5,0,1750,,,.3,1,,,,600,.1],D={},S={W:0,H:0,T:0,MX:-1,MY:-1,TWO_PI:o,HALF_PI:o/4,lerp:(e,t,a)=>e+a*(t-e),deg2rad:e=>n.PI/180*e,rad2deg:e=>180/n.PI*e,round:(e,t=0)=>{if(!t)return n.round(e);let a=10**t;return n.round(e*a)/a},clamp:(e,t,a)=>e<t?t:e>a?a:e,dist:(e,t,a,l)=>n.hypot(a-e,l-t),wrap:(e,t,a)=>e-(a-t)*n.floor((e-t)/(a-t)),map(e,t,a,l,n,i){let o=(e-t)/(a-t)*(n-l)+l;return i?S.clamp(o,l,n):o},norm:(e,t,a)=>S.map(e,t,a,0,1),rand:(e=0,t=1)=>(z=(1664525*z+0x3c6ef35f)%0x100000000)/0x100000000*(t-e)+e,randi:(e=0,t=1)=>~~S.rand(e,t+1),rseed(e){z=~~e},cls(e){null==e?m.clearRect(0,0,S.W,S.H):S.rectfill(0,0,S.W,S.H,e)},rect(e,t,a,l,n,i){m.beginPath(),m[i?"roundRect":"rect"](~~e-g,~~t-g,~~a+2*g,~~l+2*g,i),S.stroke(n)},rectfill(e,t,a,l,n,i){m.beginPath(),m[i?"roundRect":"rect"](~~e,~~t,~~a,~~l,i),S.fill(n)},circ(e,t,a,l){m.beginPath(),m.arc(~~e,~~t,~~a,0,o),S.stroke(l)},circfill(e,t,a,l){m.beginPath(),m.arc(~~e,~~t,~~a,0,o),S.fill(l)},oval(e,t,a,l,n){m.beginPath(),m.ellipse(~~e,~~t,~~a,~~l,0,0,o),S.stroke(n)},ovalfill(e,t,a,l,n){m.beginPath(),m.ellipse(~~e,~~t,~~a,~~l,0,0,o),S.fill(n)},shape(e){m.beginPath();for(let t=0;t<e.length;t+=2)0===t?m.moveTo(~~e[t],~~e[t+1]):m.lineTo(~~e[t],~~e[t+1]);m.lineTo(~~e[0],~~e[1])},line(e,t,a,l,n){m.beginPath();let i=.5*(0!==g&&~~e==~~a),o=.5*(0!==g&&~~t==~~l);m.moveTo(~~e+i,~~t+o),m.lineTo(~~a+i,~~l+o),S.stroke(n)},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=k,n="normal"){m.font=`${n} ${P}px ${E}`,m.fillStyle=W(l);let i=(""+a).split("\n");for(let a=0;a<i.length;a++)m.fillText(i[a],~~e,~~t+P*T*a)},textgap(e){T=e},textfont(e){E=e},textsize(e){P=e},textalign(e,t){e&&(m.textAlign=e),t&&(m.textBaseline=t)},image(e,t,a){m.drawImage(a,~~e,~~t)},spr(e,t,a){let l=a.trim().split("\n");for(let a=0;a<l.length;a++){let n=l[a].trim();for(let l=0;l<n.length;l++){let i=n[l];"."!==i&&" "!==i&&S.rectfill(e+l,t+a,1,1,parseInt(i,36)||0)}}},paint(e,t,a,l={}){let n=l.canvas||new OffscreenCanvas(1,1),i=l.scale||1,o=m;return n.width=e*i,n.height=t*i,(m=n.getContext("2d")).scale(i,i),a(m),m=o,n.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=S.clamp(e,0,1)},fill(e){m.fillStyle=W(e),m.fill()},stroke(e){m.strokeStyle=W(e),m.stroke()},clip(e){m.beginPath(),e(m),m.clip()},sfx:(e,t=0,a=1)=>!!l.zzfxV&&(!navigator.userActivation||!!navigator.userActivation.hasBeenActive)&&(e=e||L,(0!==t||1!==a)&&((e=e.slice())[0]=a*(e[0]||1),e[10]=~~e[10]+t),d.apply(0,e),e),volume(e){l.zzfxV=e},canvas:()=>p,use(e,t={}){var a=e,l=t;let n=a(S,l);for(let e in n)S.def(e,n[e])},listen:(e,t)=>{D[e=e.toLowerCase()]=D[e]||new Set,D[e].add(t)},unlisten:(e,t)=>{D[e=e.toLowerCase()]&&D[e].delete(t)},emit(e,a,n,i,o){c&&(N("before:"+(e=e.toLowerCase()),a,n,i,o),t.loop||"function"!=typeof l[e]||l[e](a,n,i,o),N(e,a,n,i,o),N("after:"+e,a,n,i,o))},pal(t,a=3){C=t||e,I=[],k=a},palc(e,t){null==e?I=[]:I[e]=t},def(e,a){S[e]=a,t.global&&(l[e]=a)},timescale(e){v=e},framerate(e){x=1e3/~~e},stat(e){let a={index:e,value:[t,c,x/1e3,h,D,C,L,v,l.zzfxV,z,P,E,I,T][e]};return S.emit("stat",a),a.value},pause(){u=!0,cancelAnimationFrame(b)},resume(){c&&u&&(u=!1,y=x,w=i.now(),b=r(M))},paused:()=>u,quit(){for(let e of(S.emit("quit"),S.pause(),c=!1,D={},s))e();if(t.global){for(let e in S)delete l[e];delete l.ENGINE}}};for(let e of"PI,sin,cos,atan2,hypot,tan,abs,ceil,floor,trunc,min,max,pow,sqrt,sign,exp".split(","))S[e]=n[e];function A(){if(t.autoscale&&f(l,"resize",H),t.tapEvents){let e=e=>[(e.pageX-p.offsetLeft)/h,(e.pageY-p.offsetTop)/h],t=new Map,a=(e,a,l)=>{let n={x:a,y:l,xi:a,yi:l,t:i.now()};return t.set(e,n),n},n=(e,l,n)=>{let i=t.get(e)||a(e);i.x=l,i.y=n},o=e=>e&&i.now()-e.t<=300,r=!1;f(p,"mousedown",t=>{if(0===t.button){t.preventDefault();let[l,n]=e(t);S.emit("tap",l,n,0),a(0,l,n),r=!0}}),f(p,"mouseup",a=>{if(0===a.button){a.preventDefault();let l=t.get(0),[n,i]=e(a);o(l)&&S.emit("tapped",l.xi,l.yi,0),S.emit("untap",n,i,0),t.delete(0),r=!1}}),f(l,"mousemove",t=>{t.preventDefault();let[a,l]=e(t);S.def("MX",a),S.def("MY",l),r&&(S.emit("tapping",a,l,0),n(0,a,l))}),f(p,"touchstart",t=>{for(let l of(t.preventDefault(),t.changedTouches)){let[t,n]=e(l);S.emit("tap",t,n,l.identifier+1),a(l.identifier+1,t,n)}}),f(p,"touchmove",t=>{for(let a of(t.preventDefault(),t.changedTouches)){let[t,l]=e(a);S.emit("tapping",t,l,a.identifier+1),n(a.identifier+1,t,l)}});let s=e=>{e.preventDefault();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)||(o(l)&&S.emit("tapped",l.xi,l.yi,e),S.emit("untap",l.x,l.y,e),t.delete(e))};f(p,"touchend",s),f(p,"touchcancel",s),f(l,"blur",()=>{for(let[e,a]of(r=!1,t))S.emit("untap",a.x,a.y,e),t.delete(e)})}if(t.keyboardEvents){let e=new Set,t=new Set,a=(e,t="")=>(t=t.toLowerCase())?e.has("space"===t?" ":t):e.size>0,n="";f(l,"keydown",a=>{let l=a.key.toLowerCase();e.has(l)||(e.add(l),t.add(l),n=" "===l?"space":l)}),f(l,"keyup",t=>{e.delete(t.key.toLowerCase())}),f(l,"blur",()=>e.clear()),S.listen("after:update",()=>t.clear()),S.def("iskeydown",t=>a(e,t)),S.def("iskeypressed",e=>a(t,e)),S.def("lastkey",()=>n)}c=!0,S.resume(),S.emit("init",S)}function M(){b=r(M);let e=i.now(),t=0,a=e-w;for(w=e,y+=a<100?a:x;y>=x;){t++,y-=x;let e=x/1e3*v;S.emit("update",e,t),S.def("T",S.T+e)}t&&(S.emit("draw",m),t>1&&(y=0))}function H(){let e=t.width>0?t.width:innerWidth,a=t.width>0?t.height||t.width:innerHeight;if(S.def("W",e),S.def("H",a),p.width=e,p.height=a,t.autoscale){let l=+t.autoscale;p.style.display||(p.style.display="block",p.style.margin="auto"),h=n.min(innerWidth/e,innerHeight/a),h=l>1&&h>l?l:h,p.style.width=e*h+"px",p.style.height=a*h+"px"}m.imageSmoothingEnabled=!1,S.textalign("start","top"),S.emit("resized",h)}function N(e,t,a,l,n){if(D[e])for(let i of D[e])i(t,a,l,n)}function W(e){return C[~~(I[e]??e)%C.length]}if(t.global){if(l.ENGINE)throw Error("only one global litecanvas is allowed");Object.assign(l,S),l.ENGINE=S}if(m=(p=(p="string"==typeof t.canvas?document.querySelector(t.canvas):t.canvas)||document.createElement("canvas")).getContext("2d"),f(p,"click",()=>focus()),H(),p.parentNode||document.body.appendChild(p),p.style.imageRendering="pixelated",p.oncontextmenu=()=>!1,t.loop)for(let e in t.loop)t.loop[e]&&S.listen(e,t.loop[e]);return"loading"===document.readyState?f(l,"DOMContentLoaded",()=>r(A)):b=r(A),S}})();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "litecanvas",
3
- "version": "0.103.7",
3
+ "version": "0.201.0",
4
4
  "description": "Lightweight HTML5 canvas 2D game engine suitable for small projects and creative coding. Inspired by PICO-8 and p5.js/Processing.",
5
5
  "license": "MIT",
6
6
  "author": "Luiz Bills <luizbills@pm.me>",
@@ -32,7 +32,7 @@
32
32
  "creative coding"
33
33
  ],
34
34
  "devDependencies": {
35
- "@happy-dom/global-registrator": "^20.8.3",
35
+ "@happy-dom/global-registrator": "^20.8.4",
36
36
  "@size-limit/preset-small-lib": "^12.0.1",
37
37
  "@swc/core": "^1.15.18",
38
38
  "ava": "^7.0.0",
package/src/index.js CHANGED
@@ -86,8 +86,6 @@ export default function litecanvas(settings = {}) {
86
86
  _colorPaletteState = [],
87
87
  /** @type {number[]} */
88
88
  _defaultSound = [0.5, 0, 1750, , , 0.3, 1, , , , 600, 0.1],
89
- /** @type {string} */
90
- _coreEvents = 'init,update,draw,tap,untap,tapping,tapped,resized',
91
89
  /** @type {string} list of functions copied from `Math` module*/
92
90
  _mathFunctions =
93
91
  'PI,sin,cos,atan2,hypot,tan,abs,ceil,floor,trunc,min,max,pow,sqrt,sign,exp',
@@ -1075,13 +1073,12 @@ export default function litecanvas(settings = {}) {
1075
1073
  },
1076
1074
 
1077
1075
  /**
1078
- * Add a game event listener
1076
+ * Add a game event listener.
1079
1077
  *
1080
1078
  * @param {string} eventName the event type name
1081
1079
  * @param {Function} callback the function that is called when the event occurs
1082
- * @returns {Function} a function to remove the listener
1083
1080
  */
1084
- listen(eventName, callback) {
1081
+ listen: (eventName, callback) => {
1085
1082
  DEV: assert(
1086
1083
  'string' === typeof eventName,
1087
1084
  loggerPrefix + 'listen() 1st param must be a string'
@@ -1095,19 +1092,43 @@ export default function litecanvas(settings = {}) {
1095
1092
 
1096
1093
  _eventListeners[eventName] = _eventListeners[eventName] || new Set()
1097
1094
  _eventListeners[eventName].add(callback)
1095
+ },
1098
1096
 
1099
- // return a function to remove this event listener
1100
- return () => _eventListeners[eventName]?.delete(callback)
1097
+ /**
1098
+ * Remove a game event listener.
1099
+ *
1100
+ * @param {string} eventName the event type name
1101
+ * @param {Function} callback the function that is called when the event occurs
1102
+ */
1103
+ unlisten: (eventName, callback) => {
1104
+ DEV: assert(
1105
+ 'string' === typeof eventName,
1106
+ loggerPrefix + 'unlisten() 1st param must be a string'
1107
+ )
1108
+ DEV: assert(
1109
+ 'function' === typeof callback,
1110
+ loggerPrefix + 'unlisten() 2nd param must be a function'
1111
+ )
1112
+
1113
+ eventName = lowerCase(eventName)
1114
+
1115
+ if (_eventListeners[eventName]) {
1116
+ _eventListeners[eventName].delete(callback)
1117
+ }
1101
1118
  },
1102
1119
 
1103
1120
  /**
1104
- * Call all listeners attached to a game event
1121
+ * Call all listeners attached to a game event.
1122
+ *
1123
+ * Note: when the `litecanvas()` "loop" option is `null` (default),
1124
+ * `emit()` will first call a global function matching the event name (if it exists).
1125
+ * E.g: `emit("boom")` calls `window.boom()`.
1105
1126
  *
1106
1127
  * @param {string} eventName The event type name
1107
- * @param {*} [arg1] any data to be passed over the listeners
1108
- * @param {*} [arg2] any data to be passed over the listeners
1109
- * @param {*} [arg3] any data to be passed over the listeners
1110
- * @param {*} [arg4] any data to be passed over the listeners
1128
+ * @param {any} [arg1] any data to be passed over the listeners
1129
+ * @param {any} [arg2] any data to be passed over the listeners
1130
+ * @param {any} [arg3] any data to be passed over the listeners
1131
+ * @param {any} [arg4] any data to be passed over the listeners
1111
1132
  */
1112
1133
  emit(eventName, arg1, arg2, arg3, arg4) {
1113
1134
  DEV: assert(
@@ -1119,7 +1140,12 @@ export default function litecanvas(settings = {}) {
1119
1140
  eventName = lowerCase(eventName)
1120
1141
 
1121
1142
  triggerEvent('before:' + eventName, arg1, arg2, arg3, arg4)
1143
+
1144
+ if (!settings.loop && 'function' === typeof root[eventName]) {
1145
+ root[eventName](arg1, arg2, arg3, arg4)
1146
+ }
1122
1147
  triggerEvent(eventName, arg1, arg2, arg3, arg4)
1148
+
1123
1149
  triggerEvent('after:' + eventName, arg1, arg2, arg3, arg4)
1124
1150
  }
1125
1151
  },
@@ -1175,8 +1201,13 @@ export default function litecanvas(settings = {}) {
1175
1201
  /**
1176
1202
  * Define or update a instance property.
1177
1203
  *
1178
- * @param {string} key
1179
- * @param {*} value
1204
+ * Note: when the `litecanvas()` option "global" is `true` (default),
1205
+ * `def()` with set/update a global property.
1206
+ *
1207
+ * E.g: `def('ONE', 1)` do `window.ONE = 1`.
1208
+ *
1209
+ * @param {string} key the property name
1210
+ * @param {any} value the property value
1180
1211
  */
1181
1212
  def(key, value) {
1182
1213
  DEV: assert('string' === typeof key, loggerPrefix + 'def() 1st param must be a string')
@@ -1663,9 +1694,9 @@ export default function litecanvas(settings = {}) {
1663
1694
  _canvas = _canvas || document.createElement('canvas')
1664
1695
 
1665
1696
  DEV: assert(
1666
- 'CANVAS' === _canvas.tagName,
1697
+ _canvas instanceof HTMLElement && 'CANVAS' === _canvas.tagName,
1667
1698
  loggerPrefix +
1668
- 'litecanvas() option "canvas" should be a canvas element or string (CSS selector)'
1699
+ 'litecanvas() option "canvas" should be a canvas element or string (CSS selector of a canvas)'
1669
1700
  )
1670
1701
 
1671
1702
  _ctx = _canvas.getContext('2d')
@@ -1679,6 +1710,8 @@ export default function litecanvas(settings = {}) {
1679
1710
  }
1680
1711
 
1681
1712
  _canvas.style.imageRendering = 'pixelated'
1713
+
1714
+ // disable default browser's right click in canvas
1682
1715
  _canvas.oncontextmenu = () => false
1683
1716
  }
1684
1717
 
@@ -1733,21 +1766,22 @@ export default function litecanvas(settings = {}) {
1733
1766
 
1734
1767
  /**
1735
1768
  * @param {string} eventName
1736
- * @param {*} arg1
1737
- * @param {*} arg2
1738
- * @param {*} arg3
1739
- * @param {*} arg4
1769
+ * @param {any} [arg1]
1770
+ * @param {any} [arg2]
1771
+ * @param {any} [arg3]
1772
+ * @param {any} [arg4]
1740
1773
  */
1741
1774
  function triggerEvent(eventName, arg1, arg2, arg3, arg4) {
1742
- if (!_eventListeners[eventName]) return
1743
- for (const callback of _eventListeners[eventName]) {
1744
- callback(arg1, arg2, arg3, arg4)
1775
+ if (_eventListeners[eventName]) {
1776
+ for (const callback of _eventListeners[eventName]) {
1777
+ callback(arg1, arg2, arg3, arg4)
1778
+ }
1745
1779
  }
1746
1780
  }
1747
1781
 
1748
1782
  /**
1749
1783
  * @param {pluginCallback} callback
1750
- * @param {*} config
1784
+ * @param {any} config
1751
1785
  */
1752
1786
  function loadPlugin(callback, config) {
1753
1787
  const pluginData = callback(instance, config)
@@ -1786,12 +1820,10 @@ export default function litecanvas(settings = {}) {
1786
1820
  setupCanvas()
1787
1821
 
1788
1822
  // setup default event listeners
1789
- const source = settings.loop ? settings.loop : root
1790
- for (const event of _coreEvents.split(',')) {
1791
- DEV: if (root === source && source[event]) {
1792
- console.info(loggerPrefix + `using window.${event}()`)
1823
+ if (settings.loop) {
1824
+ for (const eventName in settings.loop) {
1825
+ if (settings.loop[eventName]) instance.listen(eventName, settings.loop[eventName])
1793
1826
  }
1794
- if (source[event]) instance.listen(event, source[event])
1795
1827
  }
1796
1828
 
1797
1829
  // init the engine (async)
package/src/version.js CHANGED
@@ -1,2 +1,2 @@
1
1
  // Generated by genversion.
2
- export const version = '0.103.7'
2
+ export const version = '0.201.0'
package/types/global.d.ts CHANGED
@@ -547,15 +547,25 @@ declare global {
547
547
  */
548
548
  function use(callback: pluginCallback): void
549
549
  /**
550
- * Add a game loop event listener
550
+ * Add a game loop event listener.
551
551
  *
552
552
  * @param event The game event type
553
553
  * @param callback the function that is called when the event occurs
554
- * @returns a function to remove the listener
555
554
  */
556
- function listen(event: string, callback: Function): Function
555
+ function listen(event: string, callback: Function): void
557
556
  /**
558
- * Call all listeners attached to a game event
557
+ * Remove a game loop event listener.
558
+ *
559
+ * @param event The game event type
560
+ * @param callback the function that is called when the event occurs
561
+ */
562
+ function unlisten(event: string, callback: Function): void
563
+ /**
564
+ * Call all listeners attached to a game event.
565
+ *
566
+ * Note: when the `litecanvas()` "loop" option is `null` (default),
567
+ * `emit()` will first call a global function matching the event name (if it exists).
568
+ * E.g: `emit("boom", 10)` calls `window.boom(10)`.
559
569
  *
560
570
  * @param event The game event type
561
571
  * @param [arg1] any data to be passed over the listeners
@@ -583,10 +593,14 @@ declare global {
583
593
  */
584
594
  function palc(a?: number, b?: number): void
585
595
  /**
586
- * Define or update a instance property
596
+ * Define or update a instance property.
587
597
  *
588
- * @param key
589
- * @param value
598
+ * Note: when the `litecanvas()` option "global" is `true` (default),
599
+ * `def()` with set/update a global property.
600
+ * E.g: `def('ONE', 1)` also do `window.ONE = 1`.
601
+ *
602
+ * @param key the property name
603
+ * @param value the property value
590
604
  */
591
605
  function def(key: string, value: any): void
592
606
  /**
package/types/types.d.ts CHANGED
@@ -531,15 +531,25 @@ type LitecanvasInstance = {
531
531
  */
532
532
  use(callback: pluginCallback): void
533
533
  /**
534
- * Add a game loop event listener
534
+ * Add a game loop event listener.
535
535
  *
536
536
  * @param event The game event type
537
537
  * @param callback the function that is called when the event occurs
538
- * @returns a function to remove the listener
539
538
  */
540
- listen(event: string, callback: Function): Function
539
+ listen(event: string, callback: Function): void
541
540
  /**
542
- * Call all listeners attached to a game event
541
+ * Remove a game loop event listener.
542
+ *
543
+ * @param event The game event type
544
+ * @param callback the function that is called when the event occurs
545
+ */
546
+ unlisten(event: string, callback: Function): void
547
+ /**
548
+ * Call all listeners attached to a game event.
549
+ *
550
+ * Note: when the `litecanvas()` "loop" option is `null` (default),
551
+ * `emit()` will first call a global function matching the event name (if it exists).
552
+ * E.g: `emit("boom", 10)` calls `window.boom(10)`.
543
553
  *
544
554
  * @param event The game event type
545
555
  * @param [arg1] any data to be passed over the listeners
@@ -549,10 +559,14 @@ type LitecanvasInstance = {
549
559
  */
550
560
  emit(event: string, arg1?: any, arg2?: any, arg3?: any, arg4?: any): void
551
561
  /**
552
- * Define or update a instance property
562
+ * Define or update a instance property.
553
563
  *
554
- * @param key
555
- * @param value
564
+ * Note: when the `litecanvas()` option "global" is `true` (default),
565
+ * `def()` with set/update a global property.
566
+ * E.g: `def('ONE', 1)` also do `window.ONE = 1`.
567
+ *
568
+ * @param key the property name
569
+ * @param value the property value
556
570
  */
557
571
  def(key: string, value: any): void
558
572
  /**
@@ -652,19 +666,13 @@ type LitecanvasOptions = {
652
666
  */
653
667
  global?: boolean
654
668
  /**
655
- * Specify your game loop callbacks.
656
- * By default use that global functions (if they exist):
657
- * - `window.init(instance: LitecanvasInstance): void`
658
- * - `window.update(dt: number): void`
659
- * - `window.draw(ctx: CanvasRenderingContext2D): void`
660
- * - `window.resized(scale: number): void`
661
- * - `window.tap(tapX: number, tapY: number, touchId: number): void`
662
- * - `window.untap(tapX: number, tapY: number, touchId: number): void`
663
- * - `window.tapped(tapX: number, tapY: number, touchId: number): void`
664
- * - `window.tapping(tapX: number, tapY: number, touchId: number): void`
669
+ * Specify your game loop listener callbacks.
670
+ *
671
+ * By default, it uses global functions with the same name as the events (if they exist).
672
+ *
673
+ * Example: `window.init`, `window.update`, `window.draw`, etc
665
674
  */
666
675
  loop?: LitecanvasGameLoop
667
-
668
676
  /**
669
677
  * default: `true`
670
678
  *