litecanvas 0.98.3 → 0.99.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
@@ -151,6 +151,99 @@ function draw() {
151
151
  }
152
152
  ```
153
153
 
154
+ ### Drawing sprites
155
+
156
+ ```js
157
+ litecanvas({
158
+ width: 128
159
+ })
160
+
161
+ // you can create sprites with strings
162
+ // each visible char is a pixel
163
+ // numbers are colors
164
+ // dots are transparent pixels
165
+ const smile = `
166
+ .555555.
167
+ 55555555
168
+ 55055055
169
+ 55055055
170
+ 55555555
171
+ 50555505
172
+ 55000055
173
+ .555555.`
174
+
175
+ function draw() {
176
+ cls(0)
177
+
178
+ spr(
179
+ 0, 0, // position X Y
180
+ 8, 8, // sprite Width and Height
181
+ smile // the pixels
182
+ )
183
+ }
184
+ ```
185
+
186
+ ### Creating and drawing images
187
+
188
+ ```js
189
+ litecanvas()
190
+
191
+ // lets create the flag of the Japan
192
+ const japan = paint(
193
+ 48, 32, // image width and height
194
+ function () {
195
+ // the result of this drawings
196
+ // goes to an offscreen canvas
197
+ rectfill(0, 0, 48, 32, 3)
198
+ circfill(24, 16, 8, 4)
199
+ }, {
200
+ // you can scale your image
201
+ // by default, scale=1
202
+ scale: 4
203
+ }
204
+ )
205
+
206
+ function draw() {
207
+ cls(0)
208
+
209
+ // now the japan variable holds a image
210
+ image(W/2 - japan.width/2, H/2 - japan.height/2, japan)
211
+ }
212
+ ```
213
+
214
+ It's very useful when you need to draw something the same way every time. This way, you create an image of that drawing, working as a kind of cache.
215
+
216
+ You can also use the `image()` function to draw PNG/JPG images, but you'll need to load them first:
217
+
218
+ ```js
219
+ litecanvas()
220
+
221
+ let PngImage
222
+
223
+ function init() {
224
+ // load a image from its URL
225
+ const img = new Image()
226
+ img.onload = () => {
227
+ PngImage = img
228
+ }
229
+ img.src = 'https://litecanvas.js.org/icons/icon-128.png'
230
+ }
231
+
232
+ function draw() {
233
+ cls(0)
234
+
235
+ if (!PngImage) {
236
+ // if not loaded, show this message
237
+ text(10, 10, 'Loading image...')
238
+ } else {
239
+ // when loaded, draw the image file
240
+ image(0, 0, PngImage)
241
+ }
242
+ }
243
+ ```
244
+
245
+ To help you load multiple assets (images, fonts, music, etc.), you can I recommend you the [Asset Loader Plugin](https://github.com/litecanvas/plugin-asset-loader).
246
+
154
247
  ### Keyboard
155
248
 
156
249
  ```js
package/dist/dist.dev.js CHANGED
@@ -32,7 +32,7 @@
32
32
  };
33
33
 
34
34
  // src/version.js
35
- var version = "0.98.3";
35
+ var version = "0.99.0";
36
36
 
37
37
  // src/index.js
38
38
  function litecanvas(settings = {}) {
@@ -50,7 +50,7 @@
50
50
  keyboardEvents: true
51
51
  };
52
52
  settings = Object.assign(defaults, settings);
53
- let _initialized = false, _canvas, _scale = 1, _ctx, _outline_fix = 0.5, _timeScale = 1, _lastFrameTime, _fpsInterval = 1e3 / 60, _accumulated, _rafid, _fontFamily = "sans-serif", _fontSize = 20, _rngSeed = Date.now(), _colorPalette = defaultPalette, _colorPaletteState = [], _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, _paused = true, _canvas, _scale = 1, _ctx, _outline_fix = 0.5, _timeScale = 1, _lastFrameTime, _fpsInterval = 1e3 / 60, _accumulated, _rafid, _fontFamily = "sans-serif", _fontSize = 20, _rngSeed = Date.now(), _colorPalette = defaultPalette, _colorPaletteState = [], _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 = {};
54
54
  const instance = {
55
55
  /** @type {number} */
56
56
  W: 0,
@@ -283,8 +283,8 @@
283
283
  */
284
284
  rseed(value) {
285
285
  DEV: assert(
286
- null == value || isNumber(value) && value >= 0,
287
- "[litecanvas] rseed() 1st param must be a positive number or zero"
286
+ isNumber(value) && value >= 0,
287
+ "[litecanvas] rseed() 1st param must be a positive integer or zero"
288
288
  );
289
289
  _rngSeed = ~~value;
290
290
  },
@@ -995,7 +995,7 @@
995
995
  _fpsInterval = 1e3 / ~~value;
996
996
  },
997
997
  /**
998
- * Returns information about that engine instance.
998
+ * Returns information about the engine instance.
999
999
  *
1000
1000
  * @param {number|string} index
1001
1001
  * @returns {any}
@@ -1028,43 +1028,32 @@
1028
1028
  _rngSeed,
1029
1029
  // 10
1030
1030
  _fontSize,
1031
- // 11
1032
- _fontFamily
1031
+ // 11
1032
+ _fontFamily,
1033
+ // 12
1034
+ _colorPaletteState
1033
1035
  ];
1034
1036
  const data = { index, value: internals[index] };
1035
1037
  instance.emit("stat", data);
1036
1038
  return data.value;
1037
1039
  },
1038
- /**
1039
- * Stops the litecanvas instance and remove all event listeners.
1040
- */
1041
- quit() {
1042
- instance.pause();
1043
- instance.emit("quit");
1044
- _eventListeners = {};
1045
- for (const removeListener of _browserEventListeners) {
1046
- removeListener();
1047
- }
1048
- if (settings.global) {
1049
- for (const key in instance) {
1050
- delete root[key];
1051
- }
1052
- delete root.ENGINE;
1053
- }
1054
- _initialized = false;
1055
- },
1056
1040
  /**
1057
1041
  * Pauses the engine loop (update & draw).
1058
1042
  */
1059
1043
  pause() {
1044
+ _paused = true;
1060
1045
  cancelAnimationFrame(_rafid);
1061
- _rafid = 0;
1062
1046
  },
1063
1047
  /**
1064
1048
  * Resumes (if paused) the engine loop.
1065
1049
  */
1066
1050
  resume() {
1067
- if (_initialized && !_rafid) {
1051
+ DEV: assert(
1052
+ _initialized,
1053
+ '[litecanvas] resume() cannot be called before the "init" event and neither after the quit() function'
1054
+ );
1055
+ if (_initialized && _paused) {
1056
+ _paused = false;
1068
1057
  _accumulated = _fpsInterval;
1069
1058
  _lastFrameTime = Date.now();
1070
1059
  _rafid = raf(drawFrame);
@@ -1076,7 +1065,26 @@
1076
1065
  * @returns {boolean}
1077
1066
  */
1078
1067
  paused() {
1079
- return !_rafid;
1068
+ return _paused;
1069
+ },
1070
+ /**
1071
+ * Shutdown the litecanvas instance and remove all event listeners.
1072
+ */
1073
+ quit() {
1074
+ instance.emit("quit");
1075
+ instance.pause();
1076
+ _initialized = false;
1077
+ _eventListeners = {};
1078
+ for (const removeListener of _browserEventListeners) {
1079
+ removeListener();
1080
+ }
1081
+ if (settings.global) {
1082
+ for (const key in instance) {
1083
+ delete root[key];
1084
+ }
1085
+ delete root.ENGINE;
1086
+ }
1087
+ DEV: console.warn("[litecanvas] quit() terminated a Litecanvas instance.");
1080
1088
  }
1081
1089
  };
1082
1090
  for (const k of _mathFunctions.split(",")) {
@@ -1428,7 +1436,7 @@
1428
1436
  if ("loading" === document.readyState) {
1429
1437
  on(root, "DOMContentLoaded", () => raf(init));
1430
1438
  } else {
1431
- raf(init);
1439
+ _rafid = raf(init);
1432
1440
  }
1433
1441
  return instance;
1434
1442
  }
package/dist/dist.js CHANGED
@@ -42,7 +42,7 @@
42
42
  keyboardEvents: true
43
43
  };
44
44
  settings = Object.assign(defaults, settings);
45
- let _initialized = false, _canvas, _scale = 1, _ctx, _outline_fix = 0.5, _timeScale = 1, _lastFrameTime, _fpsInterval = 1e3 / 60, _accumulated, _rafid, _fontFamily = "sans-serif", _fontSize = 20, _rngSeed = Date.now(), _colorPalette = defaultPalette, _colorPaletteState = [], _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, _paused = true, _canvas, _scale = 1, _ctx, _outline_fix = 0.5, _timeScale = 1, _lastFrameTime, _fpsInterval = 1e3 / 60, _accumulated, _rafid, _fontFamily = "sans-serif", _fontSize = 20, _rngSeed = Date.now(), _colorPalette = defaultPalette, _colorPaletteState = [], _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 = {};
46
46
  const instance = {
47
47
  /** @type {number} */
48
48
  W: 0,
@@ -687,7 +687,7 @@
687
687
  _fpsInterval = 1e3 / ~~value;
688
688
  },
689
689
  /**
690
- * Returns information about that engine instance.
690
+ * Returns information about the engine instance.
691
691
  *
692
692
  * @param {number|string} index
693
693
  * @returns {any}
@@ -716,43 +716,28 @@
716
716
  _rngSeed,
717
717
  // 10
718
718
  _fontSize,
719
- // 11
720
- _fontFamily
719
+ // 11
720
+ _fontFamily,
721
+ // 12
722
+ _colorPaletteState
721
723
  ];
722
724
  const data = { index, value: internals[index] };
723
725
  instance.emit("stat", data);
724
726
  return data.value;
725
727
  },
726
- /**
727
- * Stops the litecanvas instance and remove all event listeners.
728
- */
729
- quit() {
730
- instance.pause();
731
- instance.emit("quit");
732
- _eventListeners = {};
733
- for (const removeListener of _browserEventListeners) {
734
- removeListener();
735
- }
736
- if (settings.global) {
737
- for (const key in instance) {
738
- delete root[key];
739
- }
740
- delete root.ENGINE;
741
- }
742
- _initialized = false;
743
- },
744
728
  /**
745
729
  * Pauses the engine loop (update & draw).
746
730
  */
747
731
  pause() {
732
+ _paused = true;
748
733
  cancelAnimationFrame(_rafid);
749
- _rafid = 0;
750
734
  },
751
735
  /**
752
736
  * Resumes (if paused) the engine loop.
753
737
  */
754
738
  resume() {
755
- if (_initialized && !_rafid) {
739
+ if (_initialized && _paused) {
740
+ _paused = false;
756
741
  _accumulated = _fpsInterval;
757
742
  _lastFrameTime = Date.now();
758
743
  _rafid = raf(drawFrame);
@@ -764,7 +749,25 @@
764
749
  * @returns {boolean}
765
750
  */
766
751
  paused() {
767
- return !_rafid;
752
+ return _paused;
753
+ },
754
+ /**
755
+ * Shutdown the litecanvas instance and remove all event listeners.
756
+ */
757
+ quit() {
758
+ instance.emit("quit");
759
+ instance.pause();
760
+ _initialized = false;
761
+ _eventListeners = {};
762
+ for (const removeListener of _browserEventListeners) {
763
+ removeListener();
764
+ }
765
+ if (settings.global) {
766
+ for (const key in instance) {
767
+ delete root[key];
768
+ }
769
+ delete root.ENGINE;
770
+ }
768
771
  }
769
772
  };
770
773
  for (const k of _mathFunctions.split(",")) {
@@ -1076,7 +1079,7 @@
1076
1079
  if ("loading" === document.readyState) {
1077
1080
  on(root, "DOMContentLoaded", () => raf(init));
1078
1081
  } else {
1079
- raf(init);
1082
+ _rafid = raf(init);
1080
1083
  }
1081
1084
  return instance;
1082
1085
  }
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,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=1,m,g=.5,v=1,w,x=1e3/60,y,b,k="sans-serif",E=20,z=Date.now(),T=e,I=[],D=[.5,0,1750,,,.3,1,,,,600,.1],S={},A={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?A.clamp(o,l,n):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,n,i){f(m),m[i?"roundRect":"rect"](~~e-g,~~t-g,~~a+2*g,~~l+2*g,i),A.stroke(n)},rectfill(e,t,a,l,n,i){f(m),m[i?"roundRect":"rect"](~~e,~~t,~~a,~~l,i),A.fill(n)},circ(e,t,a,l){f(m),m.arc(~~e,~~t,~~a,0,n),A.stroke(l)},circfill(e,t,a,l){f(m),m.arc(~~e,~~t,~~a,0,n),A.fill(l)},oval(e,t,a,l,i){f(m),m.ellipse(~~e,~~t,~~a,~~l,0,0,n),A.stroke(i)},ovalfill(e,t,a,l,i){f(m),m.ellipse(~~e,~~t,~~a,~~l,0,0,n),A.fill(i)},line(e,t,a,l,n){f(m);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),A.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=3,n="normal"){m.font=`${n} ${E}px ${k}`,m.fillStyle=P(l),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)},spr(e,t,a,l,n){let i=n.replace(/\s/g,"");for(let n=0;n<a;n++)for(let o=0;o<l;o++){let l=i[a*o+n]||".";"."!==l&&A.rectfill(e+n,t+o,1,1,parseInt(l,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=A.clamp(e,0,1)},fill(e){m.fillStyle=P(e),m.fill()},stroke(e){m.strokeStyle=P(e),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||D,(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:()=>p,use(e,t={}){var a=e,l=t;let n=a(A,l);for(let e in n)A.def(e,n[e])},listen:(e,t)=>(S[e=s(e)]=S[e]||new Set,S[e].add(t),()=>S&&S[e].delete(t)),emit(e,t,a,l,n){u&&(N("before:"+(e=s(e)),t,a,l,n),N(e,t,a,l,n),N("after:"+e,t,a,l,n))},pal(t=e){T=t,I=[]},palc(e,t){null==e?I=[]:I[e]=t},def(e,l){A[e]=l,t.global&&(a[e]=l)},timescale(e){v=e},framerate(e){x=1e3/~~e},stat(e){let l={index:e,value:[t,u,x/1e3,h,S,T,D,v,a.zzfxV,z,E,k][e]};return A.emit("stat",l),l.value},quit(){for(let e of(A.pause(),A.emit("quit"),S={},o))e();if(t.global){for(let e in A)delete a[e];delete a.ENGINE}u=!1},pause(){cancelAnimationFrame(b),b=0},resume(){u&&!b&&(y=x,w=Date.now(),b=i(C))},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 M(){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]);if(t.autoscale&&r(a,"resize",L),t.tapEvents){let e=e=>[(e.pageX-p.offsetLeft)/h,(e.pageY-p.offsetTop)/h],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(p,"mousedown",t=>{if(0===t.button){c(t);let[a,n]=e(t);A.emit("tap",a,n,0),l(0,a,n),o=!0}}),r(p,"mouseup",a=>{if(0===a.button){c(a);let l=t.get(0),[n,r]=e(a);i(l)&&A.emit("tapped",l.xi,l.yi,0),A.emit("untap",n,r,0),t.delete(0),o=!1}}),r(a,"mousemove",t=>{c(t);let[a,l]=e(t);A.def("MX",a),A.def("MY",l),o&&(A.emit("tapping",a,l,0),n(0,a,l))}),r(p,"touchstart",t=>{for(let a of(c(t),t.changedTouches)){let[t,n]=e(a);A.emit("tap",t,n,a.identifier+1),l(a.identifier+1,t,n)}}),r(p,"touchmove",t=>{for(let a of(c(t),t.changedTouches)){let[t,l]=e(a);A.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)&&A.emit("tapped",l.xi,l.yi,e),A.emit("untap",l.x,l.y,e),t.delete(e))};r(p,"touchend",s),r(p,"touchcancel",s),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=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()),A.listen("after:update",()=>t.clear()),A.def("iskeydown",t=>l(e,t)),A.def("iskeypressed",e=>l(t,e)),A.def("lastkey",()=>n)}u=!0,A.emit("init",A),A.resume()}function C(){b=i(C);let e=Date.now(),t=0,a=e-w;for(w=e,y+=a<100?a:x;y>=x;){t++,y-=x;let e=x/1e3*v;A.emit("update",e,t),A.def("T",A.T+e)}t&&(A.emit("draw",m),t>1&&(y=0))}function L(){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 n=+t.autoscale;p.style.display||(p.style.display="block",p.style.margin="auto"),h=l.min(innerWidth/e,innerHeight/a),h=n>1&&h>n?n:h,p.style.width=e*h+"px",p.style.height=a*h+"px"}m.imageSmoothingEnabled=!1,A.textalign("start","top"),A.emit("resized",h)}function N(e,t,a,l,n){if(S[e])for(let i of S[e])i(t,a,l,n)}function P(e){return T[~~(I[e]??e)%T.length]}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()),L(),p.parentNode||document.body.appendChild(p),p.style.imageRendering="pixelated",p.oncontextmenu=()=>!1,"loading"===document.readyState?r(a,"DOMContentLoaded",()=>i(M)):i(M),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=!0,h,m=1,g,v=.5,w=1,x,y=1e3/60,b,k,E="sans-serif",z=20,T=Date.now(),I=e,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=F(l),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 o=0;o<l;o++){let l=i[a*o+n]||".";"."!==l&&M.rectfill(e+n,t+o,1,1,parseInt(l,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=M.clamp(e,0,1)},fill(e){g.fillStyle=F(e),g.fill()},stroke(e){g.strokeStyle=F(e),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={}){var a=e,l=t;let n=a(M,l);for(let e in n)M.def(e,n[e])},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){I=t,D=[]},palc(e,t){null==e?D=[]:D[e]=t},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,I,S,w,a.zzfxV,T,z,E,D][e]};return M.emit("stat",l),l.value},pause(){p=!0,cancelAnimationFrame(k)},resume(){u&&p&&(p=!1,b=y,x=Date.now(),k=i(L))},paused:()=>p,quit(){for(let e of(M.emit("quit"),M.pause(),u=!1,A={},o))e();if(t.global){for(let e in M)delete a[e];delete a.ENGINE}}};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]);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){return I[~~(D[e]??e)%I.length]}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,"loading"===document.readyState?r(a,"DOMContentLoaded",()=>i(C)):k=i(C),M}})();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "litecanvas",
3
- "version": "0.98.3",
3
+ "version": "0.99.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>",
package/src/index.js CHANGED
@@ -48,6 +48,8 @@ export default function litecanvas(settings = {}) {
48
48
 
49
49
  let /** @type {boolean} */
50
50
  _initialized = false,
51
+ /** @type {boolean} */
52
+ _paused = true,
51
53
  /** @type {HTMLCanvasElement} _canvas */
52
54
  _canvas,
53
55
  /** @type {number} */
@@ -351,10 +353,9 @@ export default function litecanvas(settings = {}) {
351
353
  */
352
354
  rseed(value) {
353
355
  DEV: assert(
354
- null == value || (isNumber(value) && value >= 0),
355
- '[litecanvas] rseed() 1st param must be a positive number or zero'
356
+ isNumber(value) && value >= 0,
357
+ '[litecanvas] rseed() 1st param must be a positive integer or zero'
356
358
  )
357
-
358
359
  _rngSeed = ~~value
359
360
  },
360
361
 
@@ -1057,6 +1058,7 @@ export default function litecanvas(settings = {}) {
1057
1058
  'string' === typeof eventName,
1058
1059
  '[litecanvas] emit() 1st param must be a string'
1059
1060
  )
1061
+
1060
1062
  if (_initialized) {
1061
1063
  eventName = lowerCase(eventName)
1062
1064
 
@@ -1157,7 +1159,7 @@ export default function litecanvas(settings = {}) {
1157
1159
  },
1158
1160
 
1159
1161
  /**
1160
- * Returns information about that engine instance.
1162
+ * Returns information about the engine instance.
1161
1163
  *
1162
1164
  * @param {number|string} index
1163
1165
  * @returns {any}
@@ -1191,8 +1193,10 @@ export default function litecanvas(settings = {}) {
1191
1193
  _rngSeed,
1192
1194
  // 10
1193
1195
  _fontSize,
1194
- // 11
1196
+ // 11
1195
1197
  _fontFamily,
1198
+ // 12
1199
+ _colorPaletteState,
1196
1200
  ]
1197
1201
 
1198
1202
  const data = { index, value: internals[index] }
@@ -1203,49 +1207,24 @@ export default function litecanvas(settings = {}) {
1203
1207
  return data.value
1204
1208
  },
1205
1209
 
1206
- /**
1207
- * Stops the litecanvas instance and remove all event listeners.
1208
- */
1209
- quit() {
1210
- // stop the game loop (update & draw)
1211
- instance.pause()
1212
-
1213
- // emit "quit" event to manual clean ups
1214
- instance.emit('quit')
1215
-
1216
- // clear all engine event listeners
1217
- _eventListeners = {}
1218
-
1219
- // clear all browser event listeners
1220
- for (const removeListener of _browserEventListeners) {
1221
- removeListener()
1222
- }
1223
-
1224
- // maybe clear global context
1225
- if (settings.global) {
1226
- for (const key in instance) {
1227
- delete root[key]
1228
- }
1229
- delete root.ENGINE
1230
- }
1231
-
1232
- // unset that flag
1233
- _initialized = false
1234
- },
1235
-
1236
1210
  /**
1237
1211
  * Pauses the engine loop (update & draw).
1238
1212
  */
1239
1213
  pause() {
1214
+ _paused = true
1240
1215
  cancelAnimationFrame(_rafid)
1241
- _rafid = 0
1242
1216
  },
1243
1217
 
1244
1218
  /**
1245
1219
  * Resumes (if paused) the engine loop.
1246
1220
  */
1247
1221
  resume() {
1248
- if (_initialized && !_rafid) {
1222
+ DEV: assert(
1223
+ _initialized,
1224
+ '[litecanvas] resume() cannot be called before the "init" event and neither after the quit() function'
1225
+ )
1226
+ if (_initialized && _paused) {
1227
+ _paused = false
1249
1228
  _accumulated = _fpsInterval
1250
1229
  _lastFrameTime = Date.now()
1251
1230
  _rafid = raf(drawFrame)
@@ -1258,7 +1237,39 @@ export default function litecanvas(settings = {}) {
1258
1237
  * @returns {boolean}
1259
1238
  */
1260
1239
  paused() {
1261
- return !_rafid
1240
+ return _paused
1241
+ },
1242
+
1243
+ /**
1244
+ * Shutdown the litecanvas instance and remove all event listeners.
1245
+ */
1246
+ quit() {
1247
+ // emit "quit" event to manual clean ups
1248
+ instance.emit('quit')
1249
+
1250
+ // stop the game loop (update & draw)
1251
+ instance.pause()
1252
+
1253
+ // deinitialize the engine
1254
+ _initialized = false
1255
+
1256
+ // clear all engine event listeners
1257
+ _eventListeners = {}
1258
+
1259
+ // clear all browser event listeners
1260
+ for (const removeListener of _browserEventListeners) {
1261
+ removeListener()
1262
+ }
1263
+
1264
+ // maybe clear global context
1265
+ if (settings.global) {
1266
+ for (const key in instance) {
1267
+ delete root[key]
1268
+ }
1269
+ delete root.ENGINE
1270
+ }
1271
+
1272
+ DEV: console.warn('[litecanvas] quit() terminated a Litecanvas instance.')
1262
1273
  },
1263
1274
  }
1264
1275
 
@@ -1714,7 +1725,7 @@ export default function litecanvas(settings = {}) {
1714
1725
  if ('loading' === document.readyState) {
1715
1726
  on(root, 'DOMContentLoaded', () => raf(init))
1716
1727
  } else {
1717
- raf(init)
1728
+ _rafid = raf(init)
1718
1729
  }
1719
1730
 
1720
1731
  return instance
package/src/version.js CHANGED
@@ -1,2 +1,2 @@
1
1
  // Generated by genversion.
2
- export const version = '0.98.3'
2
+ export const version = '0.99.0'
package/types/global.d.ts CHANGED
@@ -583,9 +583,9 @@ declare global {
583
583
  */
584
584
  function framerate(fps: number): void
585
585
  /**
586
- * Returns information about that engine instance.
586
+ * Returns information about the engine instance.
587
587
  *
588
- * - n = 0: the settings passed to that instance
588
+ * - n = 0: the settings passed to this instance
589
589
  * - n = 1: returns true if the "init" event has already been emitted
590
590
  * - n = 2: the current delta time (dt)
591
591
  * - n = 3: the current canvas element scale (not the context 2D scale)
@@ -597,15 +597,12 @@ declare global {
597
597
  * - n = 9: the current RNG state
598
598
  * - n = 10: the current font size
599
599
  * - n = 11: the current font family
600
+ * - n = 12: the current state of the color palette
600
601
  * - n = *any other value*: probably returns undefined
601
602
  *
602
- * @param n
603
+ * @param index
603
604
  */
604
- function stat(n: number): any
605
- /**
606
- * Shutdown the litecanvas instance and remove all event listeners.
607
- */
608
- function quit(): void
605
+ function stat(index: number | string): any
609
606
  /**
610
607
  * Pauses the engine loop (update & draw).
611
608
  */
@@ -618,4 +615,8 @@ declare global {
618
615
  * Returns `true` if the engine loop is paused.
619
616
  */
620
617
  function paused(): boolean
618
+ /**
619
+ * Shutdown the litecanvas instance and remove all event listeners.
620
+ */
621
+ function quit(): void
621
622
  }
package/types/types.d.ts CHANGED
@@ -573,9 +573,9 @@ type LitecanvasInstance = {
573
573
  */
574
574
  framerate(fps: number): void
575
575
  /**
576
- * Returns information about that engine instance.
576
+ * Returns information about the engine instance.
577
577
  *
578
- * - n = 0: the settings passed to that instance
578
+ * - n = 0: the settings passed to this instance
579
579
  * - n = 1: returns true if the "init" event has already been emitted
580
580
  * - n = 2: the current delta time (dt)
581
581
  * - n = 3: the current canvas element scale (not the context 2D scale)
@@ -587,15 +587,12 @@ type LitecanvasInstance = {
587
587
  * - n = 9: the current RNG state
588
588
  * - n = 10: the current font size
589
589
  * - n = 11: the current font family
590
+ * - n = 12: the current state of the color palette
590
591
  * - n = *any other value*: probably returns undefined
591
592
  *
592
- * @param n
593
+ * @param index
593
594
  */
594
- stat(n: number): any
595
- /**
596
- * Stops the litecanvas instance and remove all event listeners.
597
- */
598
- quit(): void
595
+ stat(index: number | string): any
599
596
  /**
600
597
  * Pauses the engine loop (update & draw).
601
598
  */
@@ -608,6 +605,10 @@ type LitecanvasInstance = {
608
605
  * Returns `true` if the engine loop is paused.
609
606
  */
610
607
  paused(): boolean
608
+ /**
609
+ * Shutdown the litecanvas instance and remove all event listeners.
610
+ */
611
+ quit(): void
611
612
  }
612
613
 
613
614
  type LitecanvasOptions = {