q5 4.5.3 → 4.6.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
@@ -17,13 +17,13 @@ circle(0, 0, 80);
17
17
 
18
18
  ## Documentation
19
19
 
20
- The [q5 learn pages](https://q5js.org/learn) are organized into sections with interactive mini-examples.
20
+ The [q5 learn pages](https://q5js.org/learn) have interactive mini-examples.
21
21
 
22
22
  See the [wiki](https://github.com/q5js/q5.js/wiki) for extended documentation.
23
23
 
24
24
  ## Support q5 💙
25
25
 
26
- q5 is open source and anyone can use it for free under the terms of the LGPL. 🎉
26
+ q5 is open source. Anyone can use it for free under the terms of the LGPL. 🎉
27
27
 
28
28
  We need your support though! If you enjoy using q5, please donate via [GitHub Sponsors](https://github.com/sponsors/quinton-ashley), [ko-fi](https://ko-fi.com/q5play), or [Patreon](https://www.patreon.com/q5play).
29
29
 
@@ -47,7 +47,9 @@ p5.js is licensed under the LGPLv2, small sections of p5' code directly copied i
47
47
 
48
48
  q5 was inspired by the incredible work of [Ben Fry](https://benfry.com) and [Casey Reas](https://x.com/REAS) on Java [Processing](https://processingfoundation.org/) from 2001 to 2023, [Lauren McCarthy](http://lauren-mccarthy.com)'s work on [p5.js](https://p5js.org) from 2013 to 2019, and all contributors to these projects.
49
49
 
50
- ## Code Excerpt Sources
50
+ Huge thanks to all the [q5 contributors](https://github.com/q5js/q5.js/graphs/contributors)!
51
+
52
+ @evanalulu, @Tezumie, @keturn, @ormaq, @bertubi, @RedWilly, @Dukemz, @LingDong-
51
53
 
52
54
  WebGPU MSDF text rendering:
53
55
  https://webgpu.github.io/webgpu-samples/?sample=textRenderingMsdf
package/deno.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@q5/q5",
3
- "version": "4.5.3",
3
+ "version": "4.6.0",
4
4
  "license": "LGPL-3.0-only",
5
5
  "description": "Beginner friendly graphics powered by WebGPU, optimized for interactive art!",
6
6
  "author": "quinton-ashley",
package/package.json CHANGED
@@ -1,15 +1,8 @@
1
1
  {
2
2
  "name": "q5",
3
- "version": "4.5.3",
3
+ "version": "4.6.0",
4
4
  "description": "Beginner friendly graphics powered by WebGPU, optimized for interactive art!",
5
5
  "author": "quinton-ashley",
6
- "contributors": [
7
- "evanalulu",
8
- "Tezumie",
9
- "ormaq",
10
- "Dukemz",
11
- "LingDong-"
12
- ],
13
6
  "license": "LGPL-3.0-only",
14
7
  "homepage": "https://q5js.org/home",
15
8
  "types": "q5.d.ts",
package/q5.js CHANGED
@@ -2,7 +2,7 @@
2
2
  * q5.js
3
3
  * @version 4.5
4
4
  * @author quinton-ashley
5
- * @contributors evanalulu, Tezumie, ormaq, Dukemz, LingDong-
5
+ * @contributors evanalulu, Tezumie, keturn, ormaq, bertubi, RedWilly, Dukemz, LingDong-
6
6
  * @license LGPL-3.0
7
7
  * @class Q5
8
8
  */
@@ -393,6 +393,7 @@ Q5._esm = this === undefined;
393
393
 
394
394
  Q5._instanceCount = 0;
395
395
  Q5.instances = [];
396
+ Q5.errorTolerant = false;
396
397
  Q5._friendlyError = (msg, func) => {
397
398
  if (!Q5.disableFriendlyErrors) console.error(func + ': ' + msg);
398
399
  };
@@ -9591,43 +9592,32 @@ const runPython = async function () {
9591
9592
  }
9592
9593
 
9593
9594
  const useWebGPU = !code.slice(0, code.indexOf('\n')).includes('C2D'),
9594
- q5py = useWebGPU ? await Q5.WebGPU() : new Q5();
9595
+ q = useWebGPU ? await Q5.WebGPU() : new Q5();
9595
9596
 
9596
9597
  // `window.Canvas` returns a promise that resolves when Q5 is ready
9597
9598
  // but `q5py.Canvas` returns the renderer synchronously
9598
9599
  // so to make Brython happy with `await Canvas()` we need to make it async
9599
- const Canvas = q5py.Canvas;
9600
- q5py.Canvas = async (...a) => Canvas(...a);
9600
+ const Canvas = q.Canvas;
9601
+ q.Canvas = async (...a) => Canvas(...a);
9601
9602
 
9602
- code = code.replaceAll('\n', '\n\t');
9603
+ // add a tab before each line of code to nest it inside the __run function
9604
+ // but not within triple-quoted strings
9605
+ code = code
9606
+ .split(/(\"\"\"[\s\S]*?\"\"\"|\'\'\'[\s\S]*?\'\'\')/g)
9607
+ .map((part, i) => (i % 2 === 0 ? part.replaceAll('\n', '\n\t') : part))
9608
+ .join('');
9603
9609
 
9604
9610
  code = `
9605
- async def __run():
9611
+ async def __run(q):
9606
9612
  ${code}
9607
9613
 
9608
- _q5_state_vars = ["mouseX", "mouseY", "pmouseX", "pmouseY", "width", "height", "frameCount", "deltaTime", "mouseIsPressed", "mouseButton", "keyIsPressed", "key", "keyCode", "touches", "movedX", "movedY"]
9609
-
9610
- def _sync_and_call(fn):
9611
- def _wrapper(*args):
9612
- try:
9613
- for var in _q5_state_vars:
9614
- if hasattr(q5py, var):
9615
- ns[var] = getattr(q5py, var)
9616
- return fn(*args)
9617
- except Exception as e:
9618
- window._pyErr(_err())
9619
- raise e
9620
- return _wrapper
9621
-
9622
- for fn_name in ["update", "draw", "mousePressed", "mouseReleased", "mouseMoved", "mouseDragged", "mouseClicked", "doubleClicked", "mouseWheel", "keyPressed", "keyReleased", "keyTyped", "touchStarted", "touchMoved", "touchEnded", "windowResized"]:
9623
- if fn_name in locals():
9624
- setattr(window, fn_name, _sync_and_call(locals()[fn_name]))
9614
+ _wrap_fns(q, locals(), ns)
9625
9615
  `;
9626
9616
 
9627
9617
  window._pyErr = (err, lineNum) => {
9628
9618
  if (typeof err === 'string' && err.includes('Traceback')) {
9629
9619
  let lines = err.split('\n');
9630
- for (let i = 0; i < lines.length; i++) {
9620
+ for (let i = lines.length - 1; i > 0; i--) {
9631
9621
  const match = lines[i].match(/File "<string>", line (\d+)/);
9632
9622
  if (match) {
9633
9623
  lineNum = parseInt(match[1]);
@@ -9639,6 +9629,9 @@ async def __run():
9639
9629
  for (let j = 0; j < Math.min(2, lines.length); j++) {
9640
9630
  lines[j] = lines[j].slice(indent.length);
9641
9631
  }
9632
+ } else {
9633
+ let line = code.split('\n')[lineNum - 1].trim();
9634
+ lines.unshift(line, '');
9642
9635
  }
9643
9636
  err = lines.join('\n');
9644
9637
  break;
@@ -9646,7 +9639,9 @@ async def __run():
9646
9639
  }
9647
9640
  }
9648
9641
 
9649
- let file = scripts[0].src.split('/').at(-1);
9642
+ let file = scripts[0].src || scripts[0]['data-filename'] || 'sketch.py';
9643
+ file = file.split('/').at(-1);
9644
+
9650
9645
  lineNum -= 2; // adjust for the wrapper code lines
9651
9646
  if (Q5.friendlyError) Q5.friendlyError(file, lineNum, err);
9652
9647
  else console.error(`Error in ${file} on line ${lineNum}:\n\n${err}`);
@@ -9663,41 +9658,72 @@ from browser import window, aio
9663
9658
  import traceback
9664
9659
  import io
9665
9660
 
9661
+ _state_vars = ["frameCount", "deltaTime", "width", "height", "halfWidth", "halfHeight", "windowWidth", "windowHeight", "mouseX", "mouseY", "pmouseX", "pmouseY", "movedX", "movedY", "mouseIsPressed", "mouseButton", "keyIsPressed", "key", "keyCode", "touches", "recording"]
9662
+
9663
+ _usr_fns = ["update", "draw", "postProcess", "mousePressed", "mouseReleased", "mouseMoved", "mouseDragged", "mouseClicked", "doubleClicked", "mouseWheel", "keyPressed", "keyReleased", "keyTyped", "touchStarted", "touchMoved", "touchEnded", "windowResized"]
9664
+
9666
9665
  def _err():
9667
9666
  f = io.StringIO()
9668
9667
  traceback.print_exc(file=f)
9669
9668
  return f.getvalue()
9670
9669
 
9671
- async def _run_py(q5py, code):
9670
+ def _sync_state(q, ns):
9671
+ for var in _state_vars:
9672
+ if hasattr(q, var):
9673
+ ns[var] = getattr(q, var)
9674
+
9675
+ def _sync_and_call(q, fn, ns):
9676
+ def _wrapper(*args):
9677
+ try:
9678
+ _sync_state(q, ns)
9679
+ return fn(*args)
9680
+ except Exception as e:
9681
+ window._pyErr(_err(), None, q)
9682
+ if not window.Q5.errorTolerant: q.noLoop()
9683
+ return _wrapper
9684
+
9685
+ def _wrap_fns(q, locs, ns):
9686
+ for fn_name in _usr_fns:
9687
+ if fn_name in locs:
9688
+ setattr(q, fn_name, _sync_and_call(q, locs[fn_name], ns))
9689
+
9690
+ async def _run_py(q, code):
9672
9691
  ns = globals().copy()
9673
9692
  ns['ns'] = ns
9674
- ns['q5py'] = q5py
9693
+ ns['Q5'] = window.Q5
9675
9694
 
9676
- for attr in dir(q5py):
9695
+ for attr in dir(q):
9677
9696
  if not attr.startswith('_'):
9678
9697
  try:
9679
- ns[attr] = getattr(q5py, attr)
9698
+ ns[attr] = getattr(q, attr)
9680
9699
  except Exception:
9681
9700
  pass
9682
9701
 
9702
+ _orig_Canvas = ns['Canvas']
9703
+ async def _canvas_wrapper(*args):
9704
+ result = await _orig_Canvas(*args)
9705
+ _sync_state(q, ns)
9706
+ return result
9707
+ ns['Canvas'] = ns['createCanvas'] = _canvas_wrapper
9708
+
9683
9709
  try:
9684
9710
  exec(code, ns)
9685
9711
  except SyntaxError as e:
9686
- return window._pyErr(_err(), e.lineno)
9712
+ return window._pyErr(_err(), e.lineno, q)
9687
9713
  except Exception as e:
9688
- return window._pyErr(_err())
9689
-
9714
+ return window._pyErr(_err(), 0, q)
9715
+
9690
9716
  try:
9691
- await ns["__run"]()
9717
+ await ns["__run"](q)
9692
9718
  except Exception as e:
9693
- window._pyErr(_err())
9719
+ window._pyErr(_err(), 0, q)
9694
9720
 
9695
9721
  window._runPy = _run_py
9696
9722
  `);
9697
9723
 
9698
9724
  console.log = log;
9699
9725
 
9700
- await window._runPy(q5py, code);
9726
+ await window._runPy(q, code);
9701
9727
  };
9702
9728
 
9703
9729
  if (typeof document == 'object') {