q5play 4.2.0 → 4.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (4) hide show
  1. package/package.json +1 -1
  2. package/q5play.d.ts +13 -16
  3. package/q5play.js +420 -118
  4. package/q5play.pyi +12 -12
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "q5play",
3
- "version": "4.2.0",
3
+ "version": "4.2.2",
4
4
  "author": "quinton-ashley",
5
5
  "license": "SEE LICENSE IN LICENSE.md",
6
6
  "description": "A beginner friendly, web-based game engine that uses q5.js WebGPU for graphics and Box2D v3 WASM for physics.",
package/q5play.d.ts CHANGED
@@ -84,26 +84,26 @@ declare global {
84
84
  renderStats: boolean;
85
85
 
86
86
  /**
87
- * "Made with q5play" [splash screen](https://en.wikipedia.org/wiki/Splash_screen) displayed during
88
- * initial page load by default.
87
+ * "Made with q5play" [splash screen](https://en.wikipedia.org/wiki/Splash_screen)
88
+ * displayed during initial page load by default.
89
89
  */
90
90
  splashScreen(): Promise<void>;
91
91
 
92
92
  /**
93
- * Runs automatically before each draw function call.
93
+ * Runs automatically before each q5.draw function call.
94
94
  */
95
95
  update(): void;
96
96
 
97
97
  /**
98
- * Runs automatically after each draw function call.
98
+ * Runs automatically after each q5.draw function call.
99
99
  */
100
- postDraw(): void;
100
+ draw(): void;
101
101
  }
102
102
  const q5play: Q5Play;
103
103
 
104
104
  /**
105
- * Box2D v3 ported to WASM is the physics engine that
106
- * q5play uses for its physics simulation.
105
+ * Box2D v3 ported to WASM is used by
106
+ * q5play to simulate physics.
107
107
  *
108
108
  * This variable enables direct access to the Box2D API for
109
109
  * advanced users who want to do things that aren't wrapped
@@ -1111,8 +1111,11 @@ declare global {
1111
1111
 
1112
1112
  /**
1113
1113
  * Scales the the sprite.
1114
- * @param x scaleX or uniform scale factor
1115
- * @param y scaleY
1114
+ *
1115
+ * Components can be negative to flip/mirror the sprite on an axis.
1116
+ *
1117
+ * @param x horizontal scale factor or uniform scale factor for both axes
1118
+ * @param y vertical scale factor
1116
1119
  */
1117
1120
  scaleBy(x: number, y?: number): void;
1118
1121
 
@@ -1154,6 +1157,7 @@ declare global {
1154
1157
  /**
1155
1158
  * The sprite's speed along the surface of its collider(s),
1156
1159
  * like a conveyor belt.
1160
+ * Requires friction to be greater than 1 to have an effect.
1157
1161
  * @default 0
1158
1162
  */
1159
1163
  get surfaceSpeed(): number;
@@ -3767,13 +3771,6 @@ declare global {
3767
3771
  */
3768
3772
  y: number;
3769
3773
 
3770
- /**
3771
- * The mouse's absolute position on the canvas.
3772
- * @property {Number} x
3773
- * @property {Number} y
3774
- */
3775
- canvasPos: { x: number; y: number };
3776
-
3777
3774
  /**
3778
3775
  * The mouse's left button.
3779
3776
  */
package/q5play.js CHANGED
@@ -19,14 +19,12 @@
19
19
 
20
20
  let q5play_version = '4.2';
21
21
 
22
- if (typeof globalThis.Q5 == 'undefined') {
22
+ if (typeof globalThis.Q5 == 'undefined' && typeof globalThis.p5 == 'undefined') {
23
23
  console.error('q5play requires q5.js to be loaded first. Visit https://q5js.org to learn more.');
24
- if (typeof globalThis.p5 != 'undefined') {
25
- console.error('p5.js is not compatible with q5play. https://github.com/processing/p5.js/issues/7737');
26
- }
27
24
  }
28
25
 
29
- let box2dPromise;
26
+ let box2dPromise,
27
+ using_p5 = false;
30
28
 
31
29
  // called when a new instance of Q5 is created
32
30
  async function q5playPreSetup(q) {
@@ -150,6 +148,7 @@ async function q5playPreSetup(q) {
150
148
  b2CreateChain,
151
149
  b2Chain_GetSegmentCount,
152
150
  b2Chain_GetSegments,
151
+ b2DestroyChain,
153
152
 
154
153
  /* Body */
155
154
  b2BodyType,
@@ -249,7 +248,7 @@ async function q5playPreSetup(q) {
249
248
  this.context = 'web';
250
249
 
251
250
  this.update = () => q5playUpdate.call($, q);
252
- this.postdraw = () => q5playPostDraw.call($, q);
251
+ this.draw = () => q5playPostDraw.call($, q);
253
252
 
254
253
  if (window.matchMedia) {
255
254
  this.hasMouse = window.matchMedia('(any-hover: none)').matches ? false : true;
@@ -327,7 +326,7 @@ async function q5playPreSetup(q) {
327
326
 
328
327
  async splashScreen() {
329
328
  if (document.getElementById('made-with-q5play')) return;
330
- if (!using_p5v2) $._incrementPreload();
329
+ if (!using_p5) $._incrementPreload();
331
330
  let d = document.createElement('div');
332
331
  d.id = 'made-with-q5play';
333
332
  d.style =
@@ -359,27 +358,30 @@ async function q5playPreSetup(q) {
359
358
  d.style.display = 'none';
360
359
  d.remove();
361
360
  document.getElementById('made-with-q5play')?.remove();
362
- if (!using_p5v2) $._decrementPreload();
361
+ if (!using_p5) $._decrementPreload();
363
362
  }
364
363
  };
365
364
 
366
365
  $.q5play = new $.Q5Play();
367
366
  delete $.Q5Play;
368
367
 
369
- let using_p5v1 = !$._q5 && p5.VERSION[0] == 1;
370
- let using_p5v2 = !$._q5 && p5.VERSION[0] == 2;
368
+ using_p5 = !$._q5;
369
+ if (using_p5 && p5.VERSION[0] != 2) {
370
+ throw new Error(`q5play requires q5.js or p5.js v2. Detected version: ${p5.VERSION}. Please upgrade.`);
371
+ }
371
372
 
372
- // in q5play the default angle mode is degrees
373
373
  const DEGREES = $.DEGREES,
374
374
  DEGTORAD = Math.PI / 180,
375
375
  RADTODEG = 180 / Math.PI;
376
+
377
+ // in q5play the default angle mode is degrees
376
378
  $.angleMode(DEGREES);
377
379
 
378
380
  // in q5play the default color mode is float RGB
379
- $.colorMode($.RGB, 1);
381
+ if (!using_p5) $.colorMode($.RGB, 1);
380
382
 
381
383
  // in q5play the default image mode is center
382
- $.imageMode($.CENTER);
384
+ if (!using_p5) $.imageMode($.CENTER);
383
385
 
384
386
  const ZERO_VEC = new b2Vec2(0, 0),
385
387
  ZERO_ROT = b2MakeRot(0),
@@ -492,13 +494,13 @@ async function q5playPreSetup(q) {
492
494
  geom = this.geom;
493
495
 
494
496
  if (type == 0) {
495
- let hw = geom._hw * scaleX,
496
- hh = geom._hh * scaleY,
497
+ let hw = geom._hw * Math.abs(scaleX),
498
+ hh = geom._hh * Math.abs(scaleY),
497
499
  rr;
498
500
 
499
501
  if (!geom._rr) geom = b2MakeBox(hw, hh);
500
502
  else {
501
- rr = geom._rr * Math.min(scaleX, scaleY);
503
+ rr = geom._rr * Math.min(Math.abs(scaleX), Math.abs(scaleY));
502
504
  geom = b2MakeRoundedBox(hw, hh, rr);
503
505
  }
504
506
  b2Shape_SetPolygon(id, geom);
@@ -506,11 +508,44 @@ async function q5playPreSetup(q) {
506
508
  geom._hh = hh;
507
509
  geom._rr = rr;
508
510
  this.geom = geom;
511
+ } else if (type == 1) {
512
+ // convex polygon
513
+ geom = b2Shape_GetPolygon(id);
514
+ let verts = [];
515
+ for (let i = 0; i < geom.count; i++) {
516
+ const v = geom.GetVertex(i);
517
+ verts.push({ x: v.x * scaleX, y: v.y * scaleY });
518
+ }
519
+ let hull = b2ComputeHull(verts);
520
+ let rr = geom.radius * Math.min(scaleX, scaleY);
521
+ geom = rr ? b2MakeOffsetRoundedPolygon(hull, ZERO_VEC, ZERO_ROT, rr) : b2MakePolygon(hull, 0);
522
+ b2Shape_SetPolygon(id, geom);
523
+ this.geom = geom;
509
524
  } else if (type == 3) {
510
- geom.radius *= scaleX;
525
+ geom.radius *= Math.abs(scaleX);
511
526
  b2Shape_SetCircle(id, geom);
512
527
  this.geom = geom;
528
+ } else if (type == 4 || type == 7) {
529
+ // capsule (single or capsule chain segment)
530
+ geom = b2Shape_GetCapsule(id);
531
+ geom.center1.x *= scaleX;
532
+ geom.center1.y *= scaleY;
533
+ geom.center2.x *= scaleX;
534
+ geom.center2.y *= scaleY;
535
+ geom.radius *= Math.min(scaleX, scaleY);
536
+ b2Shape_SetCapsule(id, geom);
537
+ this.geom = geom;
538
+ } else if (type == 5) {
539
+ // segment
540
+ geom = b2Shape_GetSegment(id);
541
+ geom.point1.x *= scaleX;
542
+ geom.point1.y *= scaleY;
543
+ geom.point2.x *= scaleX;
544
+ geom.point2.y *= scaleY;
545
+ b2Shape_SetSegment(id, geom);
546
+ this.geom = geom;
513
547
  }
548
+ // type 6 (chain) - handled by sprite's scaleBy, which rescales and sets all segments
514
549
  }
515
550
 
516
551
  _enableContactEvents(val = true) {
@@ -837,7 +872,13 @@ async function q5playPreSetup(q) {
837
872
  if (flipY) ani.scale.y = -ani.scale.y;
838
873
 
839
874
  if (start < 0) start = ani.length + start;
840
- if (start !== undefined) ani._frame = start;
875
+ if (start !== undefined) {
876
+ ani._frame = start;
877
+ } else {
878
+ // reset so recycled animations don't immediately resolve
879
+ ani._frame = 0;
880
+ ani.playing = true;
881
+ }
841
882
 
842
883
  if (end !== undefined) ani.goToFrame(end);
843
884
  else if (ani._frame == ani.lastFrame) resolve();
@@ -954,7 +995,7 @@ async function q5playPreSetup(q) {
954
995
 
955
996
  this._posX = x;
956
997
  this._posY = y;
957
- this._pos = $.createVector.call($);
998
+ this._pos = $.createVector.call($, 0, 0);
958
999
 
959
1000
  let _this = this;
960
1001
  Object.defineProperties(this._pos, {
@@ -976,7 +1017,7 @@ async function q5playPreSetup(q) {
976
1017
  }
977
1018
  });
978
1019
 
979
- this._canvasPos = $.createVector.call($);
1020
+ this._canvasPos = $.createVector.call($, 0, 0);
980
1021
 
981
1022
  Object.defineProperties(this._canvasPos, {
982
1023
  x: {
@@ -996,16 +1037,16 @@ async function q5playPreSetup(q) {
996
1037
  });
997
1038
 
998
1039
  this._direction = 0;
999
- this._velX = 0;
1000
- this._velY = 0;
1040
+ this.vx = 0;
1041
+ this.vy = 0;
1001
1042
  this._velSynced = true;
1002
- this._vel = $.createVector.call($);
1043
+ this._vel = $.createVector.call($, 0, 0);
1003
1044
  this._vel._useCache = true;
1004
1045
 
1005
1046
  this._syncVel = () => {
1006
1047
  let v = b2Body_GetLinearVelocity(this.bdID);
1007
- this._velX = v.x;
1008
- this._velY = v.y;
1048
+ this.vx = v.x;
1049
+ this.vy = v.y;
1009
1050
  this._velSynced = true;
1010
1051
  };
1011
1052
 
@@ -1015,13 +1056,13 @@ async function q5playPreSetup(q) {
1015
1056
  if (!_this._velSynced && _this._physicsEnabled) {
1016
1057
  _this._syncVel();
1017
1058
  }
1018
- return _this._velX;
1059
+ return _this.vx;
1019
1060
  },
1020
1061
  set(val) {
1021
1062
  if (_this._physicsEnabled) {
1022
1063
  b2Body_SetLinearVelocity(_this.bdID, new b2Vec2(val, this.y));
1023
1064
  }
1024
- _this._velX = val;
1065
+ _this.vx = val;
1025
1066
  this._magCached = this._directionCached = false;
1026
1067
  }
1027
1068
  },
@@ -1030,13 +1071,13 @@ async function q5playPreSetup(q) {
1030
1071
  if (!_this._velSynced && _this._physicsEnabled) {
1031
1072
  _this._syncVel();
1032
1073
  }
1033
- return _this._velY;
1074
+ return _this.vy;
1034
1075
  },
1035
1076
  set(val) {
1036
1077
  if (_this._physicsEnabled) {
1037
1078
  b2Body_SetLinearVelocity(_this.bdID, new b2Vec2(this.x, val));
1038
1079
  }
1039
- _this._velY = val;
1080
+ _this.vy = val;
1040
1081
  this._magCached = this._directionCached = false;
1041
1082
  }
1042
1083
  }
@@ -1155,8 +1196,7 @@ async function q5playPreSetup(q) {
1155
1196
  set(val) {
1156
1197
  if (!val || val == this._x) return;
1157
1198
  if (_this.watch) _this.mod[26] = true;
1158
- let scaleX = Math.abs(val / this._x);
1159
- _this.scaleBy(scaleX, 1);
1199
+ _this.scaleBy(val / this._x, 1);
1160
1200
  this._x = val;
1161
1201
  this._avg = (this._x + this._y) * 0.5;
1162
1202
  _this._shouldScale = this._avg != 1;
@@ -1170,8 +1210,7 @@ async function q5playPreSetup(q) {
1170
1210
  set(val) {
1171
1211
  if (!val || val == this._y) return;
1172
1212
  if (_this.watch) _this.mod[26] = true;
1173
- let scaleY = Math.abs(val / this._y);
1174
- _this.scaleBy(1, scaleY);
1213
+ _this.scaleBy(1, val / this._y);
1175
1214
  this._y = val;
1176
1215
  this._avg = (this._x + this._y) * 0.5;
1177
1216
  _this._shouldScale = this._avg != 1;
@@ -1514,10 +1553,10 @@ async function q5playPreSetup(q) {
1514
1553
  geom.center2 = vecs[i];
1515
1554
  geom.radius = rr ? rr / meterSize : 0.02;
1516
1555
  id = b2CreateCapsuleShape(bdID, shape.def, geom);
1517
- let shapePart = new Collider(this);
1518
- shape._init(id, 7, geom);
1519
- shapes.push(shapePart);
1520
- shapeDict[id.index1] = shapePart;
1556
+ let sh = new Collider(this);
1557
+ sh._init(id, 7, geom);
1558
+ shapes.push(sh);
1559
+ shapeDict[id.index1] = sh;
1521
1560
  }
1522
1561
  shape = null;
1523
1562
  this.isSuperFast = true;
@@ -1535,12 +1574,17 @@ async function q5playPreSetup(q) {
1535
1574
 
1536
1575
  id = b2CreateChain(bdID, shape.def);
1537
1576
  shape._init(id, 6);
1577
+ shape.friction = 0.5;
1578
+ shape._points = vecs.map((v) => ({ x: v.x, y: v.y }));
1579
+ shape._isLoopChain = vecs.isLoop;
1580
+ this._chain = shape;
1538
1581
 
1539
1582
  let count = b2Chain_GetSegmentCount(id);
1540
1583
  let segments = b2Chain_GetSegments(id, count);
1541
1584
  for (let segID of segments) {
1542
1585
  let sh = new Collider(this);
1543
1586
  sh._init(segID, 6);
1587
+ sh.friction = 0.5;
1544
1588
  shapes.push(sh);
1545
1589
  shapeDict[segID.index1] = sh;
1546
1590
  }
@@ -2090,17 +2134,85 @@ async function q5playPreSetup(q) {
2090
2134
  scaleBy(x, y) {
2091
2135
  if (y === undefined) y = x;
2092
2136
 
2137
+ const ax = Math.abs(x),
2138
+ ay = Math.abs(y);
2139
+
2093
2140
  if (this._shapes) {
2094
2141
  for (let shape of this._shapes) {
2095
2142
  shape.scaleBy(x, y);
2096
2143
  }
2097
2144
  }
2098
2145
 
2099
- this._w *= x;
2100
- this._hw *= x;
2146
+ if (this._hasChain) this._rebuildChain(x, y);
2147
+
2148
+ this._w *= ax;
2149
+ this._hw *= ax;
2101
2150
  if (this._h) {
2102
- this._h *= y;
2103
- this._hh *= y;
2151
+ this._h *= ay;
2152
+ this._hh *= ay;
2153
+ }
2154
+ }
2155
+
2156
+ _rebuildChain(scaleX, scaleY) {
2157
+ const chain = this._chain;
2158
+ const shapes = this._shapes;
2159
+
2160
+ // save properties from existing segments before removing them
2161
+ const firstCollider = this.colliders[0];
2162
+ const savedFriction = firstCollider?._friction ?? 0.5;
2163
+ const savedSurfaceSpeed = firstCollider?._tangentSpeed ?? 0;
2164
+ const savedBounciness = firstCollider?._restitution ?? 0.2;
2165
+ // negate surfaceSpeed when the chain is flipped (odd number of axes negated)
2166
+ const newSurfaceSpeed = scaleX * scaleY < 0 ? -savedSurfaceSpeed : savedSurfaceSpeed;
2167
+
2168
+ // scale the stored points (already in Box2D meter coordinates)
2169
+ for (let p of chain._points) {
2170
+ p.x *= scaleX;
2171
+ p.y *= scaleY;
2172
+ }
2173
+
2174
+ // when an odd number of axes are negated, the winding order reverses,
2175
+ // flipping the collision normal; reverse the point order to compensate
2176
+ if (scaleX * scaleY < 0) chain._points.reverse();
2177
+
2178
+ // clean up old chain segment JS references
2179
+ for (let i = shapes.length - 1; i >= 0; i--) {
2180
+ if (shapes[i].type === 6) {
2181
+ delete shapeDict[shapes[i].id.index1];
2182
+ this.colliders.splice(this.colliders.indexOf(shapes[i]), 1);
2183
+ shapes.splice(i, 1);
2184
+ }
2185
+ }
2186
+
2187
+ // destroy old chain (Box2D frees all segment physics objects)
2188
+ b2DestroyChain(chain.id);
2189
+
2190
+ // recompute packed material data
2191
+ const packedData = ((chain._isFirstShape ? 1 : 0) << 26) | this._uid;
2192
+
2193
+ // create new chain with scaled points
2194
+ const chainDef = new b2DefaultChainDef();
2195
+ chainDef.SetPoints([chain._points[0], ...chain._points, chain._points.at(-1)]);
2196
+ chainDef.isLoop = chain._isLoopChain;
2197
+ chainDef.SetMaterials([{ customColor: packedData }]);
2198
+
2199
+ const newId = b2CreateChain(this.bdID, chainDef);
2200
+ chain._init(newId, 6);
2201
+ chain.friction = savedFriction;
2202
+ chain._tangentSpeed = newSurfaceSpeed;
2203
+
2204
+ // register new chain segments with restored properties
2205
+ const count = b2Chain_GetSegmentCount(newId);
2206
+ const segments = b2Chain_GetSegments(newId, count);
2207
+ for (let segID of segments) {
2208
+ let sh = new Collider(this);
2209
+ sh._init(segID, 6);
2210
+ sh.friction = savedFriction;
2211
+ sh.bounciness = savedBounciness;
2212
+ if (newSurfaceSpeed) sh.surfaceSpeed = newSurfaceSpeed;
2213
+ shapes.push(sh);
2214
+ this.colliders.push(sh);
2215
+ shapeDict[segID.index1] = sh;
2104
2216
  }
2105
2217
  }
2106
2218
 
@@ -2124,10 +2236,7 @@ async function q5playPreSetup(q) {
2124
2236
 
2125
2237
  if (this.watch) this.mod[26] = true;
2126
2238
 
2127
- let scaleX = Math.abs(x / sc._x);
2128
- let scaleY = Math.abs(y / sc._y);
2129
-
2130
- this.scaleBy(scaleX, scaleY);
2239
+ this.scaleBy(x / sc._x, y / sc._y);
2131
2240
 
2132
2241
  sc._x = x;
2133
2242
  sc._y = y;
@@ -2181,6 +2290,10 @@ async function q5playPreSetup(q) {
2181
2290
  }
2182
2291
  set surfaceSpeed(val) {
2183
2292
  if (this.watch) this.mod[21] = true;
2293
+ if (this._hasCapsuleChain) {
2294
+ return console.error('Can not set surfaceSpeed of a capsule chain.');
2295
+ }
2296
+ if (this._hasChain) this._chain.surfaceSpeed = val;
2184
2297
  for (let collider of this.colliders) {
2185
2298
  collider.surfaceSpeed = val;
2186
2299
  }
@@ -2412,12 +2525,19 @@ async function q5playPreSetup(q) {
2412
2525
  this._setVel(val[0] ?? val.x, val[1] ?? val.y);
2413
2526
  }
2414
2527
 
2528
+ get velocity() {
2529
+ return this._vel;
2530
+ }
2531
+ set velocity(val) {
2532
+ this.vel = val;
2533
+ }
2534
+
2415
2535
  _setVel(x, y) {
2416
2536
  if (this._physicsEnabled) {
2417
2537
  b2Body_SetLinearVelocity(this.bdID, new b2Vec2(x, y));
2418
2538
  }
2419
- this._velX = x;
2420
- this._velY = y;
2539
+ this.vx = x;
2540
+ this.vy = y;
2421
2541
  this._velSynced = true;
2422
2542
  this._vel._magCached = this._vel._directionCached = false;
2423
2543
  }
@@ -2426,18 +2546,11 @@ async function q5playPreSetup(q) {
2426
2546
  if (this._physicsEnabled) {
2427
2547
  b2Body_SetLinearVelocity(this.bdID, new b2Vec2(x, y));
2428
2548
  }
2429
- this._velX = x;
2430
- this._velY = y;
2549
+ this.vx = x;
2550
+ this.vy = y;
2431
2551
  this._velSynced = true;
2432
2552
  }
2433
2553
 
2434
- get velocity() {
2435
- return this._vel;
2436
- }
2437
- set velocity(val) {
2438
- this.vel = val;
2439
- }
2440
-
2441
2554
  get grabbable() {
2442
2555
  return this._grabbable;
2443
2556
  }
@@ -2468,8 +2581,8 @@ async function q5playPreSetup(q) {
2468
2581
  if (this._life <= 0) {
2469
2582
  this.delete();
2470
2583
  } else if (!this._physicsEnabled || !usePhysics) {
2471
- this._posX += this._velX * timeScale;
2472
- this._posY += this._velY * timeScale;
2584
+ this._posX += this.vx * timeScale;
2585
+ this._posY += this.vy * timeScale;
2473
2586
  this._rotation += this._rotationSpeed * timeScale;
2474
2587
  }
2475
2588
 
@@ -2776,8 +2889,9 @@ async function q5playPreSetup(q) {
2776
2889
  args[2] = args[1];
2777
2890
  args[1] = undefined;
2778
2891
  }
2779
- let o = {};
2780
- o.forceVector = new b2Vec2(args[0], args[1]);
2892
+ const v = this._args2Vec(args[0], args[1]),
2893
+ o = {};
2894
+ o.forceVector = new b2Vec2(v.x, v.y);
2781
2895
  if (args[2] !== undefined) {
2782
2896
  o.poa = this._args2Vec(args[2], args[3]);
2783
2897
  o.poa = scaleTo(o.poa.x, o.poa.y);
@@ -2853,34 +2967,19 @@ async function q5playPreSetup(q) {
2853
2967
  wind.delete();
2854
2968
  }
2855
2969
 
2856
- moveTowards(x, y, tracking) {
2857
- if (x === undefined) return;
2970
+ move(distance, direction, speed) {
2971
+ if (!distance) return;
2858
2972
 
2859
- if (typeof x != 'number' && x !== null) {
2860
- let pos = x;
2861
- if (pos == $.mouse && !$.mouse.isActive) return;
2862
- tracking = y;
2863
- y = pos.y;
2864
- x = pos.x;
2973
+ if (typeof direction == 'string') {
2974
+ directionNamed = true;
2975
+ this._heading = direction;
2976
+ direction = this._getDirectionAngle(direction);
2865
2977
  }
2866
- tracking ??= 0.1;
2978
+ direction ??= this.direction;
2867
2979
 
2868
- let velX, velY;
2869
-
2870
- if (x !== null && x !== undefined) {
2871
- let diffX = x - this.x;
2872
- if (!isSlop(diffX)) {
2873
- velX = diffX * tracking;
2874
- } else velX = 0;
2875
- } else velX = this._velX;
2876
- if (y !== null && y !== undefined) {
2877
- let diffY = y - this.y;
2878
- if (!isSlop(diffY)) {
2879
- velY = diffY * tracking;
2880
- } else velY = 0;
2881
- } else velY = this._velY;
2882
-
2883
- this._setVel(velX, velY);
2980
+ const x = $.cos(direction) * distance + this.x,
2981
+ y = $.sin(direction) * distance + this.y;
2982
+ return this.moveTo(x, y, speed);
2884
2983
  }
2885
2984
 
2886
2985
  moveTo(x, y, speed) {
@@ -2919,12 +3018,14 @@ async function q5playPreSetup(q) {
2919
3018
  if (moveX && moveY) {
2920
3019
  this.setSpeedAndDirection(speed, $.atan2(y - this.y, x - this.x));
2921
3020
  } else if (moveX) {
2922
- this._setVel(x > this._posX ? speed : -speed, this._velY);
3021
+ this._setVel(x > this._posX ? speed : -speed, this.vy);
2923
3022
  } else {
2924
- this._setVel(this._velX, y > this._posY ? speed : -speed);
3023
+ this._setVel(this.vx, y > this._posY ? speed : -speed);
2925
3024
  }
2926
3025
  }
2927
3026
 
3027
+ if ($._py) return new Promise((resolve) => (this._destResolve = resolve));
3028
+
2928
3029
  return {
2929
3030
  then: (onFulfilled) => {
2930
3031
  this._destResolve = onFulfilled;
@@ -2932,6 +3033,36 @@ async function q5playPreSetup(q) {
2932
3033
  };
2933
3034
  }
2934
3035
 
3036
+ moveTowards(x, y, tracking) {
3037
+ if (x === undefined) return;
3038
+
3039
+ if (typeof x != 'number' && x !== null) {
3040
+ let pos = x;
3041
+ if (pos == $.mouse && !$.mouse.isActive) return;
3042
+ tracking = y;
3043
+ y = pos.y;
3044
+ x = pos.x;
3045
+ }
3046
+ tracking ??= 0.1;
3047
+
3048
+ let velX, velY;
3049
+
3050
+ if (x !== null && x !== undefined) {
3051
+ let diffX = x - this.x;
3052
+ if (!isSlop(diffX)) {
3053
+ velX = diffX * tracking;
3054
+ } else velX = 0;
3055
+ } else velX = this.vx;
3056
+ if (y !== null && y !== undefined) {
3057
+ let diffY = y - this.y;
3058
+ if (!isSlop(diffY)) {
3059
+ velY = diffY * tracking;
3060
+ } else velY = 0;
3061
+ } else velY = this.vy;
3062
+
3063
+ this._setVel(velX, velY);
3064
+ }
3065
+
2935
3066
  angleTo(x, y, facing = 0) {
2936
3067
  if (typeof x == 'object') {
2937
3068
  facing = y || 0;
@@ -3023,7 +3154,7 @@ async function q5playPreSetup(q) {
3023
3154
 
3024
3155
  this.rotationSpeed = speed;
3025
3156
 
3026
- log(this.rotation, this._destRot, angleDist, speed, this._destRotArrivalTime);
3157
+ if ($._py) return new Promise((resolve) => (this._destRotationResolve = resolve));
3027
3158
 
3028
3159
  return {
3029
3160
  then: (onFulfilled) => {
@@ -4295,7 +4426,7 @@ async function q5playPreSetup(q) {
4295
4426
  for (let vecProp of vecProps) {
4296
4427
  vecProp = '_' + vecProp;
4297
4428
  if (vecProp != 'vel') this[vecProp] = {};
4298
- else this[vecProp] = $.createVector.call($);
4429
+ else this[vecProp] = $.createVector.call($, 0, 0);
4299
4430
  this[vecProp]._x = undefined;
4300
4431
  this[vecProp]._y = undefined;
4301
4432
  for (let prop of ['x', 'y']) {
@@ -4755,6 +4886,21 @@ async function q5playPreSetup(q) {
4755
4886
  for (let s of this) {
4756
4887
  thenables.push(s.rotateTo(...arguments));
4757
4888
  }
4889
+
4890
+ if ($._py) {
4891
+ return new Promise((resolve) => {
4892
+ let pending = thenables.length;
4893
+ if (!pending) return resolve(true);
4894
+ let allReached = true;
4895
+ for (let t of thenables) {
4896
+ t.then((reached) => {
4897
+ if (!reached) allReached = false;
4898
+ if (--pending === 0) resolve(allReached);
4899
+ });
4900
+ }
4901
+ });
4902
+ }
4903
+
4758
4904
  return {
4759
4905
  then: (onFulfilled) => {
4760
4906
  let pending = thenables.length;
@@ -4775,6 +4921,21 @@ async function q5playPreSetup(q) {
4775
4921
  for (let s of this) {
4776
4922
  thenables.push(s.rotate(...arguments));
4777
4923
  }
4924
+
4925
+ if ($._py) {
4926
+ return new Promise((resolve) => {
4927
+ let pending = thenables.length;
4928
+ if (!pending) return resolve(true);
4929
+ let allReached = true;
4930
+ for (let t of thenables) {
4931
+ t.then((reached) => {
4932
+ if (!reached) allReached = false;
4933
+ if (--pending === 0) resolve(allReached);
4934
+ });
4935
+ }
4936
+ });
4937
+ }
4938
+
4778
4939
  return {
4779
4940
  then: (onFulfilled) => {
4780
4941
  let pending = thenables.length;
@@ -4795,6 +4956,21 @@ async function q5playPreSetup(q) {
4795
4956
  for (let s of this) {
4796
4957
  thenables.push(s.rotateMinTo(...arguments));
4797
4958
  }
4959
+
4960
+ if ($._py) {
4961
+ return new Promise((resolve) => {
4962
+ let pending = thenables.length;
4963
+ if (!pending) return resolve(true);
4964
+ let allReached = true;
4965
+ for (let t of thenables) {
4966
+ t.then((reached) => {
4967
+ if (!reached) allReached = false;
4968
+ if (--pending === 0) resolve(allReached);
4969
+ });
4970
+ }
4971
+ });
4972
+ }
4973
+
4798
4974
  return {
4799
4975
  then: (onFulfilled) => {
4800
4976
  let pending = thenables.length;
@@ -4857,6 +5033,20 @@ async function q5playPreSetup(q) {
4857
5033
  thenables.push(s.moveTo(tx, ty, speed));
4858
5034
  }
4859
5035
 
5036
+ if ($._py) {
5037
+ return new Promise((resolve) => {
5038
+ let pending = thenables.length;
5039
+ if (!pending) return resolve(true);
5040
+ let allReached = true;
5041
+ for (let t of thenables) {
5042
+ t.then((reached) => {
5043
+ if (!reached) allReached = false;
5044
+ if (--pending === 0) resolve(allReached);
5045
+ });
5046
+ }
5047
+ });
5048
+ }
5049
+
4860
5050
  return {
4861
5051
  then: (onFulfilled) => {
4862
5052
  let pending = thenables.length;
@@ -5686,7 +5876,7 @@ async function q5playPreSetup(q) {
5686
5876
  $.Camera = class {
5687
5877
  constructor() {
5688
5878
  // camera position
5689
- this._pos = $.createVector.call($);
5879
+ this._pos = $.createVector.call($, 0, 0);
5690
5880
 
5691
5881
  // camera translation
5692
5882
  this.__pos = { x: 0, y: 0, rounded: {} };
@@ -6956,10 +7146,18 @@ async function q5playPreSetup(q) {
6956
7146
  };
6957
7147
 
6958
7148
  $.delay = (milliseconds) => {
6959
- if (!milliseconds) return new Promise(requestAnimationFrame);
7149
+ if (!milliseconds) {
7150
+ return new Promise((resolve) => {
7151
+ requestAnimationFrame(() => {
7152
+ if (!$._removed) resolve();
7153
+ });
7154
+ });
7155
+ }
6960
7156
  // else it wraps setTimeout in a Promise
6961
7157
  return new Promise((resolve) => {
6962
- setTimeout(resolve, milliseconds);
7158
+ setTimeout(() => {
7159
+ if (!$._removed) resolve();
7160
+ }, milliseconds);
6963
7161
  });
6964
7162
  };
6965
7163
 
@@ -7009,6 +7207,8 @@ async function q5playPreSetup(q) {
7009
7207
  $.Canvas = $.createCanvas = function (w, h) {
7010
7208
  let args = [...arguments];
7011
7209
 
7210
+ if (using_p5 && !didCreateCanvas && w == 100 && h == 100) return _createCanvas.call($, ...args);
7211
+
7012
7212
  // prevent p5 v1 overriding the user's canvas with a new default canvas
7013
7213
  if (didCreateCanvas && w == 100 && h == 100) return;
7014
7214
 
@@ -7039,7 +7239,7 @@ async function q5playPreSetup(q) {
7039
7239
  let rend = _createCanvas.call($, ...args);
7040
7240
  $.ctx = $.drawingContext;
7041
7241
  let c = rend.canvas || rend;
7042
- window.canvas = c; // for p5 v2
7242
+ if (using_p5) window.canvas = c;
7043
7243
  if (rend.GL) {
7044
7244
  c.renderer = 'webgl';
7045
7245
  $._webgl = true;
@@ -7106,7 +7306,7 @@ async function q5playPreSetup(q) {
7106
7306
  return rend;
7107
7307
  };
7108
7308
 
7109
- $.canvas = $.canvas;
7309
+ $.canvas = $.canvas; // for brython
7110
7310
 
7111
7311
  const _resizeCanvas = $.resizeCanvas;
7112
7312
 
@@ -7234,7 +7434,7 @@ async function q5playPreSetup(q) {
7234
7434
 
7235
7435
  $.allSprites = new $.Group();
7236
7436
  $.world = new $.World();
7237
- $.camera = new $.Camera();
7437
+ $.camera = $._camera = new $.Camera();
7238
7438
 
7239
7439
  $.InputDevice = class {
7240
7440
  constructor() {
@@ -7309,7 +7509,6 @@ async function q5playPreSetup(q) {
7309
7509
 
7310
7510
  this.x = 0;
7311
7511
  this.y = 0;
7312
- this.canvasPos = {};
7313
7512
  this.isOnCanvas = false;
7314
7513
  this.isActive = false;
7315
7514
  this.left = 0;
@@ -7321,7 +7520,7 @@ async function q5playPreSetup(q) {
7321
7520
  let _this = this;
7322
7521
 
7323
7522
  // this.x and this.y store the actual position values of the mouse
7324
- this._pos = $.createVector.call($);
7523
+ this._pos = $.createVector.call($, 0, 0);
7325
7524
 
7326
7525
  Object.defineProperty(this._pos, 'x', {
7327
7526
  get() {
@@ -7362,10 +7561,20 @@ async function q5playPreSetup(q) {
7362
7561
  }
7363
7562
 
7364
7563
  _update() {
7365
- let cam = $.camera;
7366
- let m = this;
7367
- m.x = $.mouseX / cam.zoom + cam.x;
7368
- m.y = $.mouseY / cam.zoom + cam.y;
7564
+ let cam = $.camera,
7565
+ m = this,
7566
+ mx = $.mouseX,
7567
+ my = $.mouseY;
7568
+
7569
+ if (using_p5) {
7570
+ if ($._webgpuFallback) {
7571
+ mx -= $.halfWidth;
7572
+ my -= $.halfHeight;
7573
+ }
7574
+ }
7575
+
7576
+ m.x = mx / cam.zoom + cam.x;
7577
+ m.y = my / cam.zoom + cam.y;
7369
7578
 
7370
7579
  if (m.scroll < 0) m.scroll = 0;
7371
7580
  if (m.scrollDelta.x == 0 && m.scrollDelta.y == 0) {
@@ -8099,7 +8308,7 @@ async function q5playPreSetup(q) {
8099
8308
  this[indexB] = tmp;
8100
8309
  if (indexA == 0 || indexB == 0) {
8101
8310
  $.contro = this[0];
8102
- if (!$._q5 && $._isGlobal) {
8311
+ if (using_p5 && $._isGlobal) {
8103
8312
  window.contro = this[0];
8104
8313
  }
8105
8314
  }
@@ -8186,7 +8395,7 @@ async function q5playPreSetup(q) {
8186
8395
  fpsPos = 0,
8187
8396
  fpsMin = 60,
8188
8397
  fpsMax = 240;
8189
- let statsColor = $.color('lime');
8398
+ let statsColor = $._q5 ? $.color('lime') : 'lime';
8190
8399
 
8191
8400
  $.renderStats = () => {
8192
8401
  let rs = $.q5play._renderStats;
@@ -8288,12 +8497,22 @@ async function q5playPreSetup(q) {
8288
8497
  shapeStack = [];
8289
8498
 
8290
8499
  const colorMax = $._colorFormat,
8291
- debugGreen = $.color(0, colorMax, 0, colorMax * 0.9),
8292
- debugGreenFill = $.color(0, colorMax, 0, colorMax * 0.1),
8293
- debugYellow = $.color(colorMax, colorMax, 0, colorMax * 0.9),
8294
- debugYellowFill = $.color(colorMax, colorMax, 0, colorMax * 0.1);
8295
-
8296
- if ($.canvas.c2d) {
8500
+ debugGreen = $._q5 ? $.color(0, colorMax, 0, colorMax * 0.9) : 'lime',
8501
+ debugGreenFill = $._q5 ? $.color(0, colorMax, 0, colorMax * 0.1) : 'lime',
8502
+ debugYellow = $._q5 ? $.color(colorMax, colorMax, 0, colorMax * 0.9) : 'yellow',
8503
+ debugYellowFill = $._q5 ? $.color(colorMax, colorMax, 0, colorMax * 0.1) : 'yellow';
8504
+
8505
+ if (using_p5) {
8506
+ $._getFillIdx = () => $._renderer.states.fillColor;
8507
+ $._setFillIdx = (v) => $.fill(v);
8508
+ $._getStrokeIdx = () => $._renderer.states.strokeColor;
8509
+ $._setStrokeIdx = (v) => {
8510
+ if ($._renderer.states.strokeSet) $.stroke(v);
8511
+ };
8512
+ $._getStrokeWeight = () => [$._renderer.states.strokeWeight];
8513
+ $._setStrokeWeight = (v) => $.strokeWeight(...v);
8514
+ $._getImageMode = () => $._renderer.states.imageMode;
8515
+ } else if ($.canvas.c2d) {
8297
8516
  // polyfill for q5 WebGPU high efficiency functions
8298
8517
  $._getFillIdx = () => $._fill;
8299
8518
  $._setFillIdx = (v) => ($._fill = v);
@@ -8479,12 +8698,17 @@ async function q5playPreSetup(q) {
8479
8698
  };
8480
8699
 
8481
8700
  // prettier-ignore
8482
- let q5playGlobals = ['q5play','Box2D','DYN','DYNAMIC','STA','STATIC','KIN','KINEMATIC','Sprite','Group','allSprites','Ani','Anis','Visual','Visuals','camera','Joint','GlueJoint','DistanceJoint','WheelJoint','HingeJoint','SliderJoint','GrabberJoint','world','kb','keyboard','mouse','contro','contros','controllers','pointer','pointers','spriteArt','EmojiImage','getFPS','animation','parseTextureAtlas','delay'];
8701
+ let q5playGlobals = ['q5play','Box2D','DYN','DYNAMIC','STA','STATIC','KIN','KINEMATIC','Sprite','Group','allSprites','Ani','Anis','Visual','Visuals','Joint','GlueJoint','DistanceJoint','WheelJoint','HingeJoint','SliderJoint','GrabberJoint','world','kb','keyboard','mouse','contro','contros','controllers','pointer','pointers','spriteArt','EmojiImage','getFPS','animation','parseTextureAtlas','delay'];
8483
8702
 
8484
8703
  // manually propagate q5play stuff to the global window object
8485
8704
  if ($._isGlobal) {
8486
8705
  for (let p of q5playGlobals) {
8487
- window[p] = $[p];
8706
+ Object.defineProperty(window, p, {
8707
+ value: $[p],
8708
+ configurable: true,
8709
+ writable: false,
8710
+ enumerable: true
8711
+ });
8488
8712
  }
8489
8713
  }
8490
8714
 
@@ -8495,10 +8719,31 @@ async function q5playPreSetup(q) {
8495
8719
  function q5playPostSetup() {
8496
8720
  const $ = this;
8497
8721
 
8498
- if ($._isGlobal && window.update) {
8499
- $.update = window.update;
8722
+ if ($._isGlobal && window.update) $.update = window.update;
8723
+
8724
+ if (using_p5) {
8500
8725
  // p5 won't run the draw loop without a draw function defined
8501
- if (!$._q5) window.draw = () => {};
8726
+ window.draw = () => {};
8727
+
8728
+ $.loge = $.log;
8729
+ $.log = console.log;
8730
+ $.camera = $._camera;
8731
+
8732
+ if ($._isGlobal) {
8733
+ Object.defineProperty(window, 'log', {
8734
+ value: console.log
8735
+ });
8736
+ Object.defineProperty(window, 'loge', {
8737
+ value: $.loge
8738
+ });
8739
+ $.camera3D = window.camera;
8740
+ Object.defineProperty(window, 'camera3D', {
8741
+ value: $.camera3D
8742
+ });
8743
+ Object.defineProperty(window, 'camera', {
8744
+ value: $.camera
8745
+ });
8746
+ }
8502
8747
  }
8503
8748
 
8504
8749
  $.update ??= $.clear;
@@ -8510,9 +8755,11 @@ function q5playPostSetup() {
8510
8755
  function q5playUpdate() {
8511
8756
  const $ = this;
8512
8757
 
8513
- if (!$._q5) {
8758
+ if (using_p5) {
8514
8759
  $.q5play._preDrawFrameTime = performance.now();
8760
+ $.resetMatrix();
8515
8761
  }
8762
+
8516
8763
  $.q5play.spritesDrawn = 0;
8517
8764
 
8518
8765
  $.contros._update();
@@ -8655,7 +8902,7 @@ function q5playPostDraw() {
8655
8902
  else if ($.kb[k] > 0) $.kb[k]++;
8656
8903
  }
8657
8904
 
8658
- if (!$._q5) {
8905
+ if (using_p5) {
8659
8906
  $.q5play._postDrawFrameTime = performance.now();
8660
8907
  $.q5play._fps = Math.round(1000 / ($.q5play._postDrawFrameTime - $.q5play._preDrawFrameTime)) || 1;
8661
8908
  }
@@ -8663,6 +8910,7 @@ function q5playPostDraw() {
8663
8910
  }
8664
8911
 
8665
8912
  function q5playRemove() {
8913
+ this._removed = true;
8666
8914
  this.world?.delete();
8667
8915
  }
8668
8916
 
@@ -8747,15 +8995,19 @@ addAnis -> es:añadirAnis
8747
8995
  changeAni -> es:cambiarAni
8748
8996
  playAni -> es:reproducirAni
8749
8997
  playAnis -> es:reproducirAnis
8998
+ moveTo -> es:moverA
8750
8999
  moveTowards -> es:moverHacia
9000
+ rotateTo -> es:rotarA
8751
9001
  rotateTowards -> es:rotarHacia
9002
+ transformTowards -> es:transformarHacia
8752
9003
  applyForce -> es:aplicarFuerza
8753
9004
  applyForceScaled -> es:aplicarFuerzaEscalada
8754
9005
  attractTo -> es:atraerA
8755
9006
  repelFrom -> es:repelerDe
8756
9007
  applyTorque -> es:aplicarTorque
8757
- angleTo -> es:ánguloHacia
8758
- angleDistTo -> es:distÁnguloHacia
9008
+ applyWind -> es:aplicarViento
9009
+ angleTo -> es:ánguloA
9010
+ angleDistTo -> es:distÁnguloA
8759
9011
  setSpeedAndDirection -> es:establecerVelocidadYDirección
8760
9012
  scaleBy -> es:escalarPor
8761
9013
  resetMass -> es:reiniciarMasa
@@ -8859,6 +9111,56 @@ pressure -> es:presión
8859
9111
  };
8860
9112
  q5playClassLangs.Group += q5playClassLangs.Sprite;
8861
9113
 
9114
+ if (typeof globalThis.Q5 == 'undefined') {
9115
+ console.warn('p5.js v2 is not fully compatible with q5play. Consider using q5 instead: https://q5js.org');
9116
+
9117
+ p5.addHook = (hook, fn) => {
9118
+ p5.registerAddon((p5, proto, lifecycles) => {
9119
+ lifecycles[hook] = fn;
9120
+ });
9121
+ };
9122
+
9123
+ // p5.js v2 compatibility layer
9124
+ globalThis.Canvas = (...args) => {
9125
+ return new Promise((resolve) => {
9126
+ window.setup = async function () {
9127
+ const $ = p5.instance;
9128
+ $._webgpu = $._webgpuFallback = true;
9129
+
9130
+ $.Canvas(...args);
9131
+
9132
+ // q5play defaults
9133
+ colorMode(RGB, 1);
9134
+ imageMode(CENTER);
9135
+
9136
+ $.halfWidth = width / 2;
9137
+ $.halfHeight = height / 2;
9138
+
9139
+ let _resetMatrix = $.resetMatrix;
9140
+
9141
+ $.resetMatrix = () => {
9142
+ _resetMatrix.call($);
9143
+ $.translate($.halfWidth, $.halfHeight);
9144
+ };
9145
+
9146
+ Object.defineProperty(p5, 'update', {
9147
+ set(fn) {
9148
+ $.update = fn;
9149
+ },
9150
+ get() {
9151
+ return $.update;
9152
+ },
9153
+ configurable: true
9154
+ });
9155
+
9156
+ resolve();
9157
+ };
9158
+ });
9159
+ };
9160
+
9161
+ globalThis.Q5 = globalThis.q5 = p5;
9162
+ }
9163
+
8862
9164
  Q5.addHook('presetup', q5playPreSetup);
8863
9165
  Q5.addHook('postsetup', q5playPostSetup);
8864
9166
  Q5.addHook('predraw', q5playUpdate);
package/q5play.pyi CHANGED
@@ -86,25 +86,25 @@ class Q5Play:
86
86
 
87
87
  def splashScreen(self) -> Awaitable[None]:
88
88
  """
89
- "Made with q5play" [splash screen](https://en.wikipedia.org/wiki/Splash_screen) displayed during
90
- initial page load by default.
89
+ "Made with q5play" [splash screen](https://en.wikipedia.org/wiki/Splash_screen)
90
+ displayed during initial page load by default.
91
91
  """
92
92
  ...
93
93
 
94
94
  def update(self) -> None:
95
- """Runs automatically before each draw function call."""
95
+ """Runs automatically before each q5.draw function call."""
96
96
  ...
97
97
 
98
- def postDraw(self) -> None:
99
- """Runs automatically after each draw function call."""
98
+ def draw(self) -> None:
99
+ """Runs automatically after each q5.draw function call."""
100
100
  ...
101
101
 
102
102
  q5play: Q5Play
103
103
 
104
104
  Box2D: Any
105
105
  """
106
- Box2D v3 ported to WASM is the physics engine that
107
- q5play uses for its physics simulation.
106
+ Box2D v3 ported to WASM is used by
107
+ q5play to simulate physics.
108
108
 
109
109
  This variable enables direct access to the Box2D API for
110
110
  advanced users who want to do things that aren't wrapped
@@ -1271,9 +1271,11 @@ class Sprite(Visual):
1271
1271
  """
1272
1272
  Scales the the sprite.
1273
1273
 
1274
+ Components can be negative to flip/mirror the sprite on an axis.
1275
+
1274
1276
  Args:
1275
- x: scaleX or uniform scale factor
1276
- y: scaleY
1277
+ x: horizontal scale factor or uniform scale factor for both axes
1278
+ y: vertical scale factor
1277
1279
  """
1278
1280
  ...
1279
1281
 
@@ -1328,6 +1330,7 @@ class Sprite(Visual):
1328
1330
  """
1329
1331
  The sprite's speed along the surface of its collider(s),
1330
1332
  like a conveyor belt.
1333
+ Requires friction to be greater than 1 to have an effect.
1331
1334
 
1332
1335
  Default: `0`
1333
1336
  """
@@ -4398,9 +4401,6 @@ class _Mouse(InputDevice):
4398
4401
  y: float
4399
4402
  """The mouse's y position in the world."""
4400
4403
 
4401
- canvasPos: dict
4402
- """The mouse's absolute position on the canvas."""
4403
-
4404
4404
  left: float
4405
4405
  """The mouse's left button."""
4406
4406