@m2c2kit/addons 0.3.12 → 0.3.14

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/index.js CHANGED
@@ -1,12 +1,12 @@
1
- import { Composite, WebColors, Shape, Label, CanvasKitHelpers, EventType, MutablePath, Easings, Story, Transition, TransitionDirection, LabelHorizontalAlignmentMode, Scene, Dimensions, Sprite } from '@m2c2kit/core';
1
+ import { Composite, WebColors, Shape, Label, CanvasKitHelpers, M2EventType, MutablePath, Easings, Story, Transition, TransitionDirection, LabelHorizontalAlignmentMode, Scene, Dimensions, Sprite, Action } from '@m2c2kit/core';
2
2
 
3
3
  class Grid extends Composite {
4
4
  /**
5
- * A rectangular grid that supports placement of entities within the grid's
5
+ * A rectangular grid that supports placement of nodes within the grid's
6
6
  * cells.
7
7
  *
8
- * @remarks This composite entity is composed of rectangles and lines. It
9
- * has convenience functions for placing and clearing entities on the grid
8
+ * @remarks This composite node is composed of rectangles and lines. It
9
+ * has convenience functions for placing and clearing nodes on the grid
10
10
  * by row and column position (zero-based indexing)
11
11
  *
12
12
  * @param options - {@link GridOptions}
@@ -102,11 +102,11 @@ class Grid extends Composite {
102
102
  }
103
103
  const x = -this.size.width / 2 + this.cellWidth / 2 + gridChild.column * this.cellWidth;
104
104
  const y = -this.size.height / 2 + this.cellHeight / 2 + gridChild.row * this.cellHeight;
105
- gridChild.entity.position = {
106
- x: x + gridChild.entity.position.x,
107
- y: y + gridChild.entity.position.y
105
+ gridChild.node.position = {
106
+ x: x + gridChild.node.position.x,
107
+ y: y + gridChild.node.position.y
108
108
  };
109
- this.gridBackground.addChild(gridChild.entity);
109
+ this.gridBackground.addChild(gridChild.node);
110
110
  });
111
111
  }
112
112
  this.needsInitialization = false;
@@ -120,24 +120,24 @@ class Grid extends Composite {
120
120
  set gridBackground(gridBackground) {
121
121
  this._gridBackground = gridBackground;
122
122
  }
123
- // all entities that make up grid are added as children, so they
123
+ // all nodes that make up grid are added as children, so they
124
124
  // have their own dispose methods
125
125
  // eslint-disable-next-line @typescript-eslint/no-empty-function
126
126
  dispose() {
127
127
  }
128
128
  /**
129
- * Duplicates an entity using deep copy.
129
+ * Duplicates a node using deep copy.
130
130
  *
131
- * @remarks This is a deep recursive clone (entity and children).
132
- * The uuid property of all duplicated entities will be newly created,
131
+ * @remarks This is a deep recursive clone (node and children).
132
+ * The uuid property of all duplicated nodes will be newly created,
133
133
  * because uuid must be unique.
134
134
  *
135
- * @param newName - optional name of the new, duplicated entity. If not
135
+ * @param newName - optional name of the new, duplicated node. If not
136
136
  * provided, name will be the new uuid
137
137
  */
138
138
  duplicate(newName) {
139
139
  const dest = new Grid({
140
- ...this.getEntityOptions(),
140
+ ...this.getNodeOptions(),
141
141
  ...this.getDrawableOptions(),
142
142
  rows: this.rows,
143
143
  columns: this.columns,
@@ -168,8 +168,8 @@ class Grid extends Composite {
168
168
  child.warmup(canvas);
169
169
  });
170
170
  }
171
- // override Entity.RemoveAllChildren() so that when RemoveAllChildren() is called on a Grid,
172
- // it removes only entities added to the grid cells (what we call grid children), not the grid lines!
171
+ // override M2Node.RemoveAllChildren() so that when RemoveAllChildren() is called on a Grid,
172
+ // it removes only nodes added to the grid cells (what we call grid children), not the grid lines!
173
173
  /**
174
174
  * Removes all children from the grid, but retains grid lines.
175
175
  */
@@ -180,29 +180,29 @@ class Grid extends Composite {
180
180
  while (this.gridChildren.length) {
181
181
  const gridChild = this.gridChildren.pop();
182
182
  if (gridChild) {
183
- this.gridBackground.removeChild(gridChild.entity);
183
+ this.gridBackground.removeChild(gridChild.node);
184
184
  }
185
185
  }
186
186
  this.needsInitialization = true;
187
187
  }
188
188
  /**
189
- * Adds an entity to the grid at the specified row and column position.
189
+ * Adds a node to the grid at the specified row and column position.
190
190
  *
191
- * @param entity - entity to add to the grid
192
- * @param row - row position within grid to add entity; zero-based indexing
193
- * @param column - column position within grid to add entity; zero-based indexing
191
+ * @param node - node to add to the grid
192
+ * @param row - row position within grid to add node; zero-based indexing
193
+ * @param column - column position within grid to add node; zero-based indexing
194
194
  */
195
- addAtCell(entity, row, column) {
195
+ addAtCell(node, row, column) {
196
196
  if (row < 0 || row >= this.rows || column < 0 || column >= this.columns) {
197
197
  console.warn(
198
- `warning: addAtCell() requested to add entity at row ${row}, column ${column}. This is outside the bounds of grid ${this.name}, which is size ${this.rows}x${this.columns}. Note that addAtCell() uses zero-based indexing. AddAtCell() will proceed, but may draw entities outside the grid`
198
+ `warning: addAtCell() requested to add node at row ${row}, column ${column}. This is outside the bounds of grid ${this.name}, which is size ${this.rows}x${this.columns}. Note that addAtCell() uses zero-based indexing. AddAtCell() will proceed, but may draw nodes outside the grid`
199
199
  );
200
200
  }
201
- this.gridChildren.push({ entity, row, column });
201
+ this.gridChildren.push({ node, row, column });
202
202
  this.needsInitialization = true;
203
203
  }
204
204
  /**
205
- * Removes all child entities at the specified row and column position.
205
+ * Removes all child nodes at the specified row and column position.
206
206
  *
207
207
  * @param row - row position within grid at which to remove children; zero-based indexing
208
208
  * @param column - column position within grid at which to remove children; zero-based indexing
@@ -215,24 +215,24 @@ class Grid extends Composite {
215
215
  return;
216
216
  }
217
217
  this.gridBackground.removeChildren(
218
- gridChildrenToRemove.map((gridChild) => gridChild.entity)
218
+ gridChildrenToRemove.map((gridChild) => gridChild.node)
219
219
  );
220
220
  this.gridChildren = this.gridChildren.filter(
221
221
  (gridChild) => gridChild.row !== row && gridChild.column !== column
222
222
  );
223
223
  this.needsInitialization = true;
224
224
  }
225
- // override Entity.RemoveChild() so that when RemoveChild() is called on a Grid, it removes the
226
- // entity from the gridBackground rectangle AND our grid's own list of children (in gridChildren)
225
+ // override M2Node.RemoveChild() so that when RemoveChild() is called on a Grid, it removes the
226
+ // node from the gridBackground rectangle AND our grid's own list of children (in gridChildren)
227
227
  /**
228
- * Removes the child entity from the grid.
228
+ * Removes the child node from the grid.
229
229
  *
230
- * @param entity - entity to remove
230
+ * @param node - node to remove
231
231
  */
232
- removeChild(entity) {
233
- this.gridBackground.removeChild(entity);
232
+ removeChild(node) {
233
+ this.gridBackground.removeChild(node);
234
234
  this.gridChildren = this.gridChildren.filter(
235
- (gridChild) => gridChild.entity != entity
235
+ (gridChild) => gridChild.node != node
236
236
  );
237
237
  this.needsInitialization = true;
238
238
  }
@@ -244,7 +244,7 @@ class Button extends Composite {
244
244
  /**
245
245
  * A simple button of rectangle with text centered inside.
246
246
  *
247
- * @remarks This composite entity is composed of a rectangle and text. To
247
+ * @remarks This composite node is composed of a rectangle and text. To
248
248
  * respond to user taps, the isUserInteractionEnabled property must be set
249
249
  * to true and an appropriate callback must be set to handle the tap event.
250
250
  *
@@ -258,7 +258,7 @@ class Button extends Composite {
258
258
  this.size = { width: 200, height: 50 };
259
259
  this.cornerRadius = 9;
260
260
  this.fontSize = 20;
261
- this.text = "";
261
+ this._text = "";
262
262
  this._fontColor = WebColors.White;
263
263
  if (options.text) {
264
264
  this.text = options.text;
@@ -266,10 +266,10 @@ class Button extends Composite {
266
266
  if (options.size) {
267
267
  this.size = options.size;
268
268
  }
269
- if (options.cornerRadius) {
269
+ if (options.cornerRadius !== void 0) {
270
270
  this.cornerRadius = options.cornerRadius;
271
271
  }
272
- if (options.fontSize) {
272
+ if (options.fontSize !== void 0) {
273
273
  this.fontSize = options.fontSize;
274
274
  }
275
275
  if (options.fontColor) {
@@ -308,6 +308,13 @@ class Button extends Composite {
308
308
  dispose() {
309
309
  CanvasKitHelpers.Dispose([this.backgroundPaint]);
310
310
  }
311
+ get text() {
312
+ return this._text;
313
+ }
314
+ set text(text) {
315
+ this._text = text;
316
+ this.needsInitialization = true;
317
+ }
311
318
  get backgroundColor() {
312
319
  return this._backgroundColor;
313
320
  }
@@ -323,18 +330,18 @@ class Button extends Composite {
323
330
  this.needsInitialization = true;
324
331
  }
325
332
  /**
326
- * Duplicates an entity using deep copy.
333
+ * Duplicates a node using deep copy.
327
334
  *
328
- * @remarks This is a deep recursive clone (entity and children).
329
- * The uuid property of all duplicated entities will be newly created,
335
+ * @remarks This is a deep recursive clone (node and children).
336
+ * The uuid property of all duplicated nodes will be newly created,
330
337
  * because uuid must be unique.
331
338
  *
332
- * @param newName - optional name of the new, duplicated entity. If not
339
+ * @param newName - optional name of the new, duplicated node. If not
333
340
  * provided, name will be the new uuid
334
341
  */
335
342
  duplicate(newName) {
336
343
  const dest = new Button({
337
- ...this.getEntityOptions(),
344
+ ...this.getNodeOptions(),
338
345
  ...this.getDrawableOptions(),
339
346
  ...this.getTextOptions(),
340
347
  size: this.size,
@@ -377,7 +384,7 @@ class Dialog extends Composite {
377
384
  // todo: add default "behaviors" (?) like button click animation?
378
385
  constructor(options) {
379
386
  super(options);
380
- this.compositeType = "dialog";
387
+ this.compositeType = "Dialog";
381
388
  this._backgroundColor = WebColors.White;
382
389
  this.cornerRadius = 9;
383
390
  this.overlayAlpha = 0.5;
@@ -418,15 +425,16 @@ class Dialog extends Composite {
418
425
  show() {
419
426
  this.hidden = false;
420
427
  }
421
- onDialogResult(callback, replaceExistingCallback = true) {
428
+ onDialogResult(callback, options) {
422
429
  const eventListener = {
423
- type: EventType.CompositeCustom,
424
- entityUuid: this.uuid,
430
+ type: M2EventType.CompositeCustom,
431
+ compositeType: "DialogResult",
432
+ nodeUuid: this.uuid,
425
433
  callback
426
434
  };
427
- if (replaceExistingCallback) {
435
+ if (options?.replaceExisting) {
428
436
  this.eventListeners = this.eventListeners.filter(
429
- (listener) => !(listener.entityUuid === eventListener.entityUuid && listener.type === eventListener.type)
437
+ (listener) => !(listener.nodeUuid === eventListener.nodeUuid && listener.type === eventListener.type)
430
438
  );
431
439
  }
432
440
  this.eventListeners.push(eventListener);
@@ -448,9 +456,9 @@ class Dialog extends Composite {
448
456
  e.handled = true;
449
457
  this.hidden = true;
450
458
  if (this.eventListeners.length > 0) {
451
- this.eventListeners.filter((listener) => listener.type === EventType.CompositeCustom).forEach((listener) => {
459
+ this.eventListeners.filter((listener) => listener.type === M2EventType.CompositeCustom).forEach((listener) => {
452
460
  const dialogEvent = {
453
- type: EventType.CompositeCustom,
461
+ type: M2EventType.CompositeCustom,
454
462
  target: this,
455
463
  handled: false,
456
464
  dialogResult: "Dismiss" /* Dismiss */
@@ -496,9 +504,9 @@ class Dialog extends Composite {
496
504
  e.handled = true;
497
505
  this.hidden = true;
498
506
  if (this.eventListeners.length > 0) {
499
- this.eventListeners.filter((listener) => listener.type === EventType.CompositeCustom).forEach((listener) => {
507
+ this.eventListeners.filter((listener) => listener.type === M2EventType.CompositeCustom).forEach((listener) => {
500
508
  const dialogEvent = {
501
- type: EventType.CompositeCustom,
509
+ type: M2EventType.CompositeCustom,
502
510
  target: this,
503
511
  handled: false,
504
512
  dialogResult: "Negative" /* Negative */
@@ -518,9 +526,9 @@ class Dialog extends Composite {
518
526
  e.handled = true;
519
527
  this.hidden = true;
520
528
  if (this.eventListeners.length > 0) {
521
- this.eventListeners.filter((listener) => listener.type === EventType.CompositeCustom).forEach((listener) => {
529
+ this.eventListeners.filter((listener) => listener.type === M2EventType.CompositeCustom).forEach((listener) => {
522
530
  const dialogEvent = {
523
- type: EventType.CompositeCustom,
531
+ type: M2EventType.CompositeCustom,
524
532
  target: this,
525
533
  handled: false,
526
534
  dialogResult: "Positive" /* Positive */
@@ -548,13 +556,13 @@ class Dialog extends Composite {
548
556
  this.needsInitialization = true;
549
557
  }
550
558
  /**
551
- * Duplicates an entity using deep copy.
559
+ * Duplicates a node using deep copy.
552
560
  *
553
- * @remarks This is a deep recursive clone (entity and children).
554
- * The uuid property of all duplicated entities will be newly created,
561
+ * @remarks This is a deep recursive clone (node and children).
562
+ * The uuid property of all duplicated nodes will be newly created,
555
563
  * because uuid must be unique.
556
564
  *
557
- * @param newName - optional name of the new, duplicated entity. If not
565
+ * @param newName - optional name of the new, duplicated node. If not
558
566
  * provided, name will be the new uuid
559
567
  */
560
568
  duplicate(newName) {
@@ -587,7 +595,7 @@ class DrawPad extends Composite {
587
595
  /**
588
596
  * A rectangular area on which the user can draw strokes (lines).
589
597
  *
590
- * @remarks This composite entity is composed of a rectangle Shape and
598
+ * @remarks This composite node is composed of a rectangle Shape and
591
599
  * another Shape that is formed from a path of points.
592
600
  *
593
601
  * @param options - {@link DrawPadOptions}
@@ -674,8 +682,8 @@ class DrawPad extends Composite {
674
682
  this.drawArea.onTapUpAny(() => {
675
683
  this.handleTapUpAny();
676
684
  });
677
- this.drawArea.onTapLeave(() => {
678
- this.handleTapLeave();
685
+ this.drawArea.onTapLeave((e) => {
686
+ this.handleTapLeave(e);
679
687
  });
680
688
  }
681
689
  this.drawArea.fillColor = this.backgroundColor;
@@ -687,11 +695,10 @@ class DrawPad extends Composite {
687
695
  }
688
696
  handleTapDown(e) {
689
697
  if (this.isUserInteractionEnabled) {
690
- const drawShape = this.drawShape;
691
- if (!drawShape) {
692
- throw new Error("no draw shape");
698
+ if (!this.drawShape?.path) {
699
+ throw new Error("DrawPad.handleTapDown(): no drawShape.path");
693
700
  }
694
- const path = drawShape.path;
701
+ const path = this.drawShape.path;
695
702
  if (this.continuousDrawingOnly && path.subpaths.length !== 0) {
696
703
  const prevPoint = path.subpaths[path.subpaths.length - 1][path.subpaths[path.subpaths.length - 1].length - 1];
697
704
  const currentPoint = e.point;
@@ -700,6 +707,7 @@ class DrawPad extends Composite {
700
707
  return;
701
708
  }
702
709
  }
710
+ this.currentStrokesNotAllowed = false;
703
711
  this.isDrawingPointerDown = true;
704
712
  path.move(e.point);
705
713
  const drawPadEvent = {
@@ -712,13 +720,43 @@ class DrawPad extends Composite {
712
720
  {
713
721
  type: DrawPadEventType.StrokeStart,
714
722
  position: e.point,
715
- iso8601Timestamp: (/* @__PURE__ */ new Date()).toISOString()
723
+ iso8601Timestamp: (/* @__PURE__ */ new Date()).toISOString(),
724
+ interpolated: false
716
725
  }
717
726
  ]);
718
727
  this.raiseDrawPadEvent(drawPadEvent);
719
728
  }
720
729
  }
721
- handleTapLeave() {
730
+ addInterpolatedStrokeMove(point) {
731
+ const strokeCount = this.strokes.length;
732
+ const strokeInteractionCount = this.strokes[strokeCount - 1].length;
733
+ const previousPoint = this.strokes[this.strokes.length - 1][strokeInteractionCount - 1].position;
734
+ const interpolatedPoint = this.interpolateToDrawPadBorder(
735
+ point,
736
+ previousPoint,
737
+ this.size
738
+ );
739
+ if (!this.drawShape?.path) {
740
+ throw new Error("DrawPad.addInterpolatedStrokeMove(): no drawShape.path");
741
+ }
742
+ const path = this.drawShape.path;
743
+ path.addLine(interpolatedPoint);
744
+ const drawPadEvent = {
745
+ type: DrawPadEventType.StrokeMove,
746
+ target: this,
747
+ handled: false,
748
+ position: interpolatedPoint
749
+ };
750
+ this.strokes[strokeCount - 1].push({
751
+ type: DrawPadEventType.StrokeMove,
752
+ position: interpolatedPoint,
753
+ iso8601Timestamp: (/* @__PURE__ */ new Date()).toISOString(),
754
+ interpolated: true
755
+ });
756
+ this.raiseDrawPadEvent(drawPadEvent);
757
+ return interpolatedPoint;
758
+ }
759
+ handleTapLeave(e) {
722
760
  if (this.currentStrokesNotAllowed) {
723
761
  this.isDrawingPointerDown = false;
724
762
  return;
@@ -727,6 +765,12 @@ class DrawPad extends Composite {
727
765
  this.isDrawingPointerDown = false;
728
766
  const strokeCount = this.strokes.length;
729
767
  const strokeInteractionCount = this.strokes[strokeCount - 1].length;
768
+ let pointWasInterpolated = false;
769
+ let point = e.point;
770
+ if (!this.isPointWithinDrawPad(e.point, this.size)) {
771
+ point = this.addInterpolatedStrokeMove(e.point);
772
+ pointWasInterpolated = true;
773
+ }
730
774
  const drawPadEvent = {
731
775
  type: DrawPadEventType.StrokeEnd,
732
776
  position: this.strokes[strokeCount - 1][strokeInteractionCount - 1].position,
@@ -735,10 +779,12 @@ class DrawPad extends Composite {
735
779
  };
736
780
  this.strokes[strokeCount - 1].push({
737
781
  type: DrawPadEventType.StrokeEnd,
738
- position: this.strokes[strokeCount - 1][strokeInteractionCount - 1].position,
739
- iso8601Timestamp: (/* @__PURE__ */ new Date()).toISOString()
782
+ position: pointWasInterpolated ? point : this.strokes[strokeCount - 1][strokeInteractionCount - 1].position,
783
+ iso8601Timestamp: (/* @__PURE__ */ new Date()).toISOString(),
784
+ interpolated: pointWasInterpolated
740
785
  });
741
786
  this.raiseDrawPadEvent(drawPadEvent);
787
+ this.currentStrokesNotAllowed = true;
742
788
  } else {
743
789
  this.pointerIsDownAndPointerLeftDrawAreaWhenDown = true;
744
790
  }
@@ -762,18 +808,18 @@ class DrawPad extends Composite {
762
808
  this.strokes[strokeCount - 1].push({
763
809
  type: DrawPadEventType.StrokeEnd,
764
810
  position: this.strokes[strokeCount - 1][strokeInteractionCount - 1].position,
765
- iso8601Timestamp: (/* @__PURE__ */ new Date()).toISOString()
811
+ iso8601Timestamp: (/* @__PURE__ */ new Date()).toISOString(),
812
+ interpolated: false
766
813
  });
767
814
  this.raiseDrawPadEvent(drawPadEvent);
768
815
  }
769
816
  }
770
817
  handlePointerMove(e) {
771
818
  if (this.isUserInteractionEnabled && this.isDrawingPointerDown) {
772
- const drawShape = this.drawShape;
773
- if (!drawShape) {
774
- throw new Error("no draw shape");
819
+ if (!this.drawShape?.path) {
820
+ throw new Error("DrawPad.handlePointerMove(): no drawShape.path");
775
821
  }
776
- const path = drawShape.path;
822
+ const path = this.drawShape.path;
777
823
  if (this.isDrawingPointerDown && !this.pointerIsDownAndPointerLeftDrawAreaWhenDown) {
778
824
  path.addLine(e.point);
779
825
  }
@@ -791,7 +837,8 @@ class DrawPad extends Composite {
791
837
  this.strokes[strokeCount - 1].push({
792
838
  type: DrawPadEventType.StrokeMove,
793
839
  position: e.point,
794
- iso8601Timestamp: (/* @__PURE__ */ new Date()).toISOString()
840
+ iso8601Timestamp: (/* @__PURE__ */ new Date()).toISOString(),
841
+ interpolated: false
795
842
  });
796
843
  this.raiseDrawPadEvent(drawPadEvent);
797
844
  }
@@ -820,11 +867,10 @@ class DrawPad extends Composite {
820
867
  * Removes all strokes from the DrawPad.
821
868
  */
822
869
  clear() {
823
- const drawShape = this.drawShape;
824
- if (!drawShape) {
825
- throw new Error("no draw shape");
870
+ if (!this.drawShape?.path) {
871
+ throw new Error("DrawPad.clear(): no drawShape.path");
826
872
  }
827
- const path = drawShape.path;
873
+ const path = this.drawShape.path;
828
874
  path.clear();
829
875
  this.strokes = new Array();
830
876
  }
@@ -874,45 +920,45 @@ class DrawPad extends Composite {
874
920
  );
875
921
  }
876
922
  /**
877
- * Adds an entity to the DrawPad.
923
+ * Adds a node to the DrawPad.
878
924
  *
879
- * @remarks After the entity is added to the DrawPad, its
925
+ * @remarks After the node is added to the DrawPad, its
880
926
  * position is adjusted to be relative to the DrawPad's coordinate
881
927
  * system, and it is made interactive. The method returns an object
882
- * which is the entity as a DrawPadItem, which has additional methods,
883
- * properties, and events specific to it now being on a DrawPad. The entity
928
+ * which is the node as a DrawPadItem, which has additional methods,
929
+ * properties, and events specific to it now being on a DrawPad. The node
884
930
  * now **must** be manipulated only using the DrawPadItem object. Using
885
- * the original entity object will result in undefined behavior.
931
+ * the original node object will result in undefined behavior.
886
932
  *
887
- * @param entity - the entity to add to the DrawPad
888
- * @returns the entity as a DrawPadItem
933
+ * @param node - the node to add to the DrawPad
934
+ * @returns the node as a DrawPadItem
889
935
  */
890
- addItem(entity) {
891
- Object.defineProperty(entity, "drawPadPosition", {
936
+ addItem(node) {
937
+ Object.defineProperty(node, "drawPadPosition", {
892
938
  get: function() {
893
- const drawPad = entity.parent;
939
+ const drawPad = node.parent;
894
940
  return {
895
941
  get x() {
896
- return entity.position.x + drawPad.size.width / 2;
942
+ return node.position.x + drawPad.size.width / 2;
897
943
  },
898
944
  set x(value) {
899
- entity.position.x = value - drawPad.size.width / 2;
945
+ node.position.x = value - drawPad.size.width / 2;
900
946
  },
901
947
  get y() {
902
- return entity.position.y + drawPad.size.height / 2;
948
+ return node.position.y + drawPad.size.height / 2;
903
949
  },
904
950
  set y(value) {
905
- entity.position.y = value - drawPad.size.height / 2;
951
+ node.position.y = value - drawPad.size.height / 2;
906
952
  }
907
953
  };
908
954
  },
909
955
  set: function(value) {
910
- const drawPad = entity.parent;
911
- entity.position.x = value.x - drawPad.size.width / 2;
912
- entity.position.y = value.y - drawPad.size.height / 2;
956
+ const drawPad = node.parent;
957
+ node.position.x = value.x - drawPad.size.width / 2;
958
+ node.position.y = value.y - drawPad.size.height / 2;
913
959
  }
914
960
  });
915
- Object.defineProperty(entity, "onStrokeEnter", {
961
+ Object.defineProperty(node, "onStrokeEnter", {
916
962
  value: function(callback, options) {
917
963
  this.addEventListener(
918
964
  DrawPadItemEventType.StrokeEnter,
@@ -921,7 +967,7 @@ class DrawPad extends Composite {
921
967
  );
922
968
  }
923
969
  });
924
- Object.defineProperty(entity, "onStrokeLeave", {
970
+ Object.defineProperty(node, "onStrokeLeave", {
925
971
  value: function(callback, options) {
926
972
  this.addEventListener(
927
973
  DrawPadItemEventType.StrokeLeave,
@@ -930,62 +976,62 @@ class DrawPad extends Composite {
930
976
  );
931
977
  }
932
978
  });
933
- Object.defineProperty(entity, "isStrokeWithinBounds", {
979
+ Object.defineProperty(node, "isStrokeWithinBounds", {
934
980
  value: false,
935
981
  writable: true
936
982
  });
937
- entity.onPointerDown(() => {
983
+ node.onPointerDown(() => {
938
984
  if (this.isDrawingPointerDown) {
939
- if (entity.isStrokeWithinBounds === false) {
940
- entity.isStrokeWithinBounds = true;
985
+ if (node.isStrokeWithinBounds === false) {
986
+ node.isStrokeWithinBounds = true;
941
987
  const drawPadItemEvent = {
942
988
  type: DrawPadItemEventType.StrokeEnter,
943
- target: entity
989
+ target: node
944
990
  };
945
- this.raiseDrawPadItemEvent(entity, drawPadItemEvent);
991
+ this.raiseDrawPadItemEvent(node, drawPadItemEvent);
946
992
  }
947
993
  }
948
994
  });
949
- entity.onPointerMove(() => {
995
+ node.onPointerMove(() => {
950
996
  if (this.isDrawingPointerDown) {
951
- if (entity.isStrokeWithinBounds === false) {
952
- entity.isStrokeWithinBounds = true;
997
+ if (node.isStrokeWithinBounds === false) {
998
+ node.isStrokeWithinBounds = true;
953
999
  const drawPadItemEvent = {
954
1000
  type: DrawPadItemEventType.StrokeEnter,
955
- target: entity
1001
+ target: node
956
1002
  };
957
- this.raiseDrawPadItemEvent(entity, drawPadItemEvent);
1003
+ this.raiseDrawPadItemEvent(node, drawPadItemEvent);
958
1004
  }
959
1005
  }
960
1006
  });
961
- entity.onPointerLeave(() => {
1007
+ node.onPointerLeave(() => {
962
1008
  if (this.isDrawingPointerDown) {
963
- if (entity.isStrokeWithinBounds === true) {
964
- entity.isStrokeWithinBounds = false;
1009
+ if (node.isStrokeWithinBounds === true) {
1010
+ node.isStrokeWithinBounds = false;
965
1011
  const drawPadItemEvent = {
966
1012
  type: DrawPadItemEventType.StrokeLeave,
967
- target: entity
1013
+ target: node
968
1014
  };
969
- this.raiseDrawPadItemEvent(entity, drawPadItemEvent);
1015
+ this.raiseDrawPadItemEvent(node, drawPadItemEvent);
970
1016
  }
971
1017
  }
972
1018
  });
973
- entity.onPointerUp(() => {
974
- if (entity.isStrokeWithinBounds === true) {
975
- entity.isStrokeWithinBounds = false;
1019
+ node.onPointerUp(() => {
1020
+ if (node.isStrokeWithinBounds === true) {
1021
+ node.isStrokeWithinBounds = false;
976
1022
  const drawPadItemEvent = {
977
1023
  type: DrawPadItemEventType.StrokeLeave,
978
- target: entity
1024
+ target: node
979
1025
  };
980
- this.raiseDrawPadItemEvent(entity, drawPadItemEvent);
1026
+ this.raiseDrawPadItemEvent(node, drawPadItemEvent);
981
1027
  }
982
1028
  });
983
- this.addChild(entity);
984
- entity.zPosition = -1;
985
- entity.position.x = entity.position.x - this.size.width / 2;
986
- entity.position.y = entity.position.y - this.size.height / 2;
987
- entity.isUserInteractionEnabled = true;
988
- return entity;
1029
+ this.addChild(node);
1030
+ node.zPosition = -1;
1031
+ node.position.x = node.position.x - this.size.width / 2;
1032
+ node.position.y = node.position.y - this.size.height / 2;
1033
+ node.isUserInteractionEnabled = true;
1034
+ return node;
989
1035
  }
990
1036
  /**
991
1037
  * Takes a screenshot of the DrawPad.
@@ -994,13 +1040,9 @@ class DrawPad extends Composite {
994
1040
  * PNG format.
995
1041
  */
996
1042
  takeScreenshot() {
997
- const surface = this.game.surface;
998
- if (!surface) {
999
- throw new Error("no surface");
1000
- }
1001
1043
  const drawArea = this.drawArea;
1002
1044
  if (!drawArea) {
1003
- throw new Error("no draw area");
1045
+ throw new Error("DrawPad.takeScreenshot(): no drawArea");
1004
1046
  }
1005
1047
  const sx = (drawArea.absolutePosition.x - drawArea.size.width / 2) * Globals.canvasScale;
1006
1048
  const sy = (drawArea.absolutePosition.y - drawArea.size.height / 2) * Globals.canvasScale;
@@ -1021,15 +1063,85 @@ class DrawPad extends Composite {
1021
1063
  pixelData.length / sh
1022
1064
  );
1023
1065
  if (!croppedImage) {
1024
- throw new Error("no cropped image");
1066
+ throw new Error("DrawPad.takeScreenshot(): no croppedImage");
1025
1067
  }
1026
1068
  const bytes = croppedImage.encodeToBytes();
1027
1069
  if (!bytes) {
1028
- throw new Error("no bytes");
1070
+ throw new Error(
1071
+ "DrawPad.takeScreenshot(): croppedImage.encodeToBytes() failed"
1072
+ );
1029
1073
  }
1030
1074
  croppedImage.delete();
1031
1075
  return this.arrayBufferToBase64String(bytes);
1032
1076
  }
1077
+ /**
1078
+ * Determines whether a point is within the DrawPad.
1079
+ *
1080
+ * @param point - The point to check
1081
+ * @returns True - if the point is within the DrawPad, false otherwise
1082
+ */
1083
+ isPointWithinDrawPad(point, drawPadSize) {
1084
+ return point.x >= 0 && point.x <= drawPadSize.width && point.y >= 0 && point.y <= drawPadSize.height;
1085
+ }
1086
+ /**
1087
+ * Interpolates a point to the border of the DrawPad based on a line that
1088
+ * crosses the DrawPad border. The line is formed by the current "out of
1089
+ * bounds" point the and previous "within bounds" point.
1090
+ *
1091
+ * @param currentPoint - The current point
1092
+ * @param previousPoint - The previous point
1093
+ * @param drawPadSize - The size of the DrawPad
1094
+ * @returns A new point on the border of the DrawPad
1095
+ */
1096
+ interpolateToDrawPadBorder(currentPoint, previousPoint, drawPadSize) {
1097
+ const slope = (currentPoint.y - previousPoint.y) / (currentPoint.x - previousPoint.x);
1098
+ const intercept = currentPoint.y - slope * currentPoint.x;
1099
+ const newPoint = { x: 0, y: 0 };
1100
+ if (!Number.isFinite(slope)) {
1101
+ newPoint.x = currentPoint.x;
1102
+ if (currentPoint.y - previousPoint.y > 0) {
1103
+ newPoint.y = drawPadSize.height;
1104
+ return newPoint;
1105
+ }
1106
+ if (currentPoint.y - previousPoint.y < 0) {
1107
+ newPoint.y = 0;
1108
+ return newPoint;
1109
+ }
1110
+ }
1111
+ const yLeft = slope * 0 + intercept;
1112
+ const yRight = slope * drawPadSize.width + intercept;
1113
+ if (yLeft >= 0 && yLeft <= drawPadSize.height) {
1114
+ if (currentPoint.x - previousPoint.x < 0) {
1115
+ newPoint.x = 0;
1116
+ newPoint.y = yLeft;
1117
+ return newPoint;
1118
+ }
1119
+ }
1120
+ if (yRight >= 0 && yRight <= drawPadSize.height) {
1121
+ if (currentPoint.x - previousPoint.x > 0) {
1122
+ newPoint.x = drawPadSize.width;
1123
+ newPoint.y = yRight;
1124
+ return newPoint;
1125
+ }
1126
+ }
1127
+ const xTop = (0 - intercept) / slope;
1128
+ const xBottom = (drawPadSize.height - intercept) / slope;
1129
+ if (xTop >= 0 && xTop <= drawPadSize.width) {
1130
+ if (currentPoint.y - previousPoint.y < 0) {
1131
+ newPoint.x = xTop;
1132
+ newPoint.y = 0;
1133
+ return newPoint;
1134
+ }
1135
+ }
1136
+ if (xBottom >= 0 && xBottom <= drawPadSize.width) {
1137
+ if (currentPoint.y - previousPoint.y > 0) {
1138
+ newPoint.x = xBottom;
1139
+ newPoint.y = drawPadSize.height;
1140
+ return newPoint;
1141
+ }
1142
+ }
1143
+ return currentPoint;
1144
+ }
1033
1145
  arrayBufferToBase64String(buffer) {
1034
1146
  let binary = "";
1035
1147
  const bytes = new Uint8Array(buffer);
@@ -1074,7 +1186,7 @@ class DrawPad extends Composite {
1074
1186
  this.needsInitialization = true;
1075
1187
  }
1076
1188
  duplicate(newName) {
1077
- throw new Error("Method not implemented.");
1189
+ throw new Error("DrawPad.duplicate(): Method not implemented.");
1078
1190
  }
1079
1191
  }
1080
1192
 
@@ -1345,10 +1457,10 @@ class VirtualKeyboard extends Composite {
1345
1457
  letterCircle.hidden = true;
1346
1458
  if (this.eventListeners.length > 0) {
1347
1459
  this.eventListeners.filter(
1348
- (listener) => listener.type === EventType.CompositeCustom && listener.compositeType === "VirtualKeyboardKeyUp"
1460
+ (listener) => listener.type === M2EventType.CompositeCustom && listener.compositeType === "VirtualKeyboardKeyUp"
1349
1461
  ).forEach((listener) => {
1350
1462
  const virtualKeyboardEvent = {
1351
- type: EventType.CompositeCustom,
1463
+ type: M2EventType.CompositeCustom,
1352
1464
  target: this,
1353
1465
  handled: false,
1354
1466
  key: keyAsString,
@@ -1413,10 +1525,10 @@ class VirtualKeyboard extends Composite {
1413
1525
  }
1414
1526
  if (this.eventListeners.length > 0) {
1415
1527
  this.eventListeners.filter(
1416
- (listener) => listener.type === EventType.CompositeCustom && listener.compositeType === "VirtualKeyboardKeyDown"
1528
+ (listener) => listener.type === M2EventType.CompositeCustom && listener.compositeType === "VirtualKeyboardKeyDown"
1417
1529
  ).forEach((listener) => {
1418
1530
  const virtualKeyboardEvent = {
1419
- type: EventType.CompositeCustom,
1531
+ type: M2EventType.CompositeCustom,
1420
1532
  target: this,
1421
1533
  handled: false,
1422
1534
  key: keyAsString,
@@ -1470,50 +1582,36 @@ class VirtualKeyboard extends Composite {
1470
1582
  * Executes a callback when the user presses down on a key.
1471
1583
  *
1472
1584
  * @param callback - function to execute
1473
- * @param replaceExistingCallback - should the provided callback replace
1474
- * any existing callbacks of the same event type on this entity? Usually
1475
- * there should be only one callback defined, instead of chaining multiple
1476
- * ones. It is strongly recommended not to change this, unless you have a
1477
- * special use case. Default is true.
1585
+ * @param options
1478
1586
  */
1479
- onKeyDown(callback, replaceExistingCallback = true) {
1587
+ onKeyDown(callback, options) {
1480
1588
  const eventListener = {
1481
- type: EventType.CompositeCustom,
1589
+ type: M2EventType.CompositeCustom,
1482
1590
  compositeType: "VirtualKeyboardKeyDown",
1483
- entityUuid: this.uuid,
1591
+ nodeUuid: this.uuid,
1484
1592
  callback
1485
1593
  };
1486
- this.addVirtualKeyboardEventListener(
1487
- replaceExistingCallback,
1488
- eventListener
1489
- );
1594
+ this.addVirtualKeyboardEventListener(eventListener, options);
1490
1595
  }
1491
1596
  /**
1492
1597
  * Executes a callback when the user releases a key.
1493
1598
  *
1494
1599
  * @param callback - function to execute
1495
- * @param replaceExistingCallback - should the provided callback replace
1496
- * any existing callbacks of the same event type on this entity? Usually
1497
- * there should be only one callback defined, instead of chaining multiple
1498
- * ones. It is strongly recommended not to change this, unless you have a
1499
- * special use case. Default is true.
1600
+ * @param options
1500
1601
  */
1501
- onKeyUp(callback, replaceExistingCallback = true) {
1602
+ onKeyUp(callback, options) {
1502
1603
  const eventListener = {
1503
- type: EventType.CompositeCustom,
1604
+ type: M2EventType.CompositeCustom,
1504
1605
  compositeType: "VirtualKeyboardKeyUp",
1505
- entityUuid: this.uuid,
1606
+ nodeUuid: this.uuid,
1506
1607
  callback
1507
1608
  };
1508
- this.addVirtualKeyboardEventListener(
1509
- replaceExistingCallback,
1510
- eventListener
1511
- );
1609
+ this.addVirtualKeyboardEventListener(eventListener, options);
1512
1610
  }
1513
- addVirtualKeyboardEventListener(replaceExistingCallback, eventListener) {
1514
- if (replaceExistingCallback) {
1611
+ addVirtualKeyboardEventListener(eventListener, options) {
1612
+ if (options?.replaceExisting) {
1515
1613
  this.eventListeners = this.eventListeners.filter(
1516
- (listener) => !(listener.entityUuid === eventListener.entityUuid && listener.type === eventListener.type && listener.compositeType === eventListener.compositeType)
1614
+ (listener) => !(listener.nodeUuid === eventListener.nodeUuid && listener.type === eventListener.type && listener.compositeType === eventListener.compositeType)
1517
1615
  );
1518
1616
  }
1519
1617
  this.eventListeners.push(eventListener);
@@ -1535,27 +1633,27 @@ class VirtualKeyboard extends Composite {
1535
1633
  }
1536
1634
  }
1537
1635
 
1538
- const SCENE_TRANSITION_EASING = Easings.sinusoidalInOut;
1636
+ const SCENE_TRANSITION_EASING$1 = Easings.sinusoidalInOut;
1539
1637
  const SCENE_TRANSITION_DURATION = 500;
1540
1638
  class Instructions extends Story {
1541
1639
  /**
1542
- * Create an array of scenes containing instructions on how to complete the task
1640
+ * Creates an array of scenes containing instructions on how to complete the assessment
1543
1641
  *
1544
1642
  * @param options - {@link InstructionsOptions}
1545
- * @returns
1643
+ * @returns instruction scenes
1546
1644
  */
1547
- static Create(options) {
1645
+ static create(options) {
1548
1646
  const scenes = new Array();
1549
1647
  options.instructionScenes.forEach((s, i) => {
1550
1648
  const nextSceneTransition = s.nextSceneTransition ?? options.nextSceneTransition ?? Transition.slide({
1551
1649
  direction: TransitionDirection.Left,
1552
1650
  duration: SCENE_TRANSITION_DURATION,
1553
- easing: SCENE_TRANSITION_EASING
1651
+ easing: SCENE_TRANSITION_EASING$1
1554
1652
  });
1555
1653
  const backSceneTransition = s.backSceneTransition ?? options.backSceneTransition ?? Transition.slide({
1556
1654
  direction: TransitionDirection.Right,
1557
1655
  duration: SCENE_TRANSITION_DURATION,
1558
- easing: SCENE_TRANSITION_EASING
1656
+ easing: SCENE_TRANSITION_EASING$1
1559
1657
  });
1560
1658
  const backButtonText = s.backButtonText ?? options.backButtonText ?? "Back";
1561
1659
  const nextButtonText = s.nextButtonText ?? options.nextButtonText ?? "Next";
@@ -1740,9 +1838,150 @@ class Instructions extends Story {
1740
1838
  });
1741
1839
  return scenes;
1742
1840
  }
1841
+ /**
1842
+ * Creates an array of scenes containing instructions on how to complete the assessment
1843
+ *
1844
+ * @deprecated Use {@link Instructions.create} instead (lower case method name "create")
1845
+ *
1846
+ * @param options - {@link InstructionsOptions}
1847
+ * @returns instruction scenes
1848
+ */
1849
+ static Create(options) {
1850
+ return this.create(options);
1851
+ }
1852
+ }
1853
+
1854
+ const SCENE_TRANSITION_EASING = Easings.sinusoidalInOut;
1855
+ const SCENE_TRANSITION_DURATION_MS = 500;
1856
+ class CountdownScene extends Scene {
1857
+ /**
1858
+ * A scene that counts down from a specified number to zero, then transitions to the next scene.
1859
+ *
1860
+ * @param options - {@link CountdownSceneOptions}
1861
+ */
1862
+ constructor(options) {
1863
+ super(options);
1864
+ if (options?.transitionDurationMilliseconds !== void 0 && options?.transition) {
1865
+ throw new Error(
1866
+ "Both transition and transitionDurationMilliseconds options were provided. Only one should be provided. If using a custom transition, then the duration of that transition must be specified within the custom transition."
1867
+ );
1868
+ }
1869
+ let timerShape;
1870
+ if (options?.timerShape?.circle === void 0 && options?.timerShape?.rectangle === void 0 || options?.timerShape.circle !== void 0) {
1871
+ timerShape = new Shape({
1872
+ circleOfRadius: options?.timerShape?.circle?.radius ?? 100,
1873
+ layout: {
1874
+ constraints: {
1875
+ topToTopOf: this,
1876
+ bottomToBottomOf: this,
1877
+ startToStartOf: this,
1878
+ endToEndOf: this,
1879
+ verticalBias: options?.timerShape?.verticalBias ?? 0.5
1880
+ }
1881
+ },
1882
+ fillColor: options?.timerShape?.fillColor ?? WebColors.RoyalBlue
1883
+ });
1884
+ this.addChild(timerShape);
1885
+ } else if (options?.timerShape.rectangle !== void 0) {
1886
+ timerShape = new Shape({
1887
+ rect: {
1888
+ width: options?.timerShape?.rectangle?.width ?? 200,
1889
+ height: options?.timerShape?.rectangle?.height ?? 200
1890
+ },
1891
+ cornerRadius: options?.timerShape?.rectangle?.cornerRadius,
1892
+ layout: {
1893
+ constraints: {
1894
+ topToTopOf: this,
1895
+ bottomToBottomOf: this,
1896
+ startToStartOf: this,
1897
+ endToEndOf: this,
1898
+ verticalBias: options?.timerShape?.verticalBias ?? 0.5
1899
+ }
1900
+ },
1901
+ fillColor: options?.timerShape?.fillColor ?? WebColors.RoyalBlue
1902
+ });
1903
+ this.addChild(timerShape);
1904
+ } else {
1905
+ throw new Error("Invalid timer shape options.");
1906
+ }
1907
+ const timerInitialNumber = Math.floor(options.milliseconds / 1e3);
1908
+ const timerNumberLabel = new Label({
1909
+ // Number text will be set in onSetup()
1910
+ text: "",
1911
+ fontSize: options?.timerNumbersFontSize ?? 50,
1912
+ fontName: options?.timerNumbersFontName,
1913
+ fontColor: options?.timerNumbersFontColor ?? WebColors.White
1914
+ });
1915
+ timerShape.addChild(timerNumberLabel);
1916
+ const textLabel = new Label({
1917
+ text: options?.text ?? "GET READY",
1918
+ fontSize: options?.textFontSize ?? 50,
1919
+ fontName: options?.textFontName,
1920
+ fontColor: options?.textFontColor,
1921
+ layout: {
1922
+ marginTop: options?.textMarginTop ?? 32,
1923
+ constraints: {
1924
+ topToBottomOf: timerShape,
1925
+ startToStartOf: this,
1926
+ endToEndOf: this
1927
+ }
1928
+ }
1929
+ });
1930
+ this.addChild(textLabel);
1931
+ const countdownSequence = new Array();
1932
+ for (let i = timerInitialNumber - 1; i > 0; i--) {
1933
+ countdownSequence.push(Action.wait({ duration: 1e3 }));
1934
+ countdownSequence.push(
1935
+ Action.custom({
1936
+ callback: () => {
1937
+ timerNumberLabel.text = i.toString();
1938
+ }
1939
+ })
1940
+ );
1941
+ }
1942
+ countdownSequence.push(Action.wait({ duration: 1e3 }));
1943
+ countdownSequence.push(
1944
+ Action.custom({
1945
+ callback: () => {
1946
+ timerNumberLabel.text = options?.timerZeroString ?? "0";
1947
+ }
1948
+ })
1949
+ );
1950
+ if (options?.zeroDwellMilliseconds !== void 0) {
1951
+ countdownSequence.push(
1952
+ Action.wait({ duration: options.zeroDwellMilliseconds })
1953
+ );
1954
+ }
1955
+ countdownSequence.push(
1956
+ Action.custom({
1957
+ callback: () => {
1958
+ const game = this.game;
1959
+ const isLastScene = game.scenes.indexOf(this) === game.scenes.length - 1;
1960
+ if (isLastScene) {
1961
+ game.end();
1962
+ }
1963
+ const nextScene = game.scenes[game.scenes.indexOf(this) + 1];
1964
+ game.presentScene(
1965
+ nextScene,
1966
+ options?.transition ?? Transition.slide({
1967
+ direction: TransitionDirection.Left,
1968
+ duration: options?.transitionDurationMilliseconds ?? SCENE_TRANSITION_DURATION_MS,
1969
+ easing: SCENE_TRANSITION_EASING
1970
+ })
1971
+ );
1972
+ }
1973
+ })
1974
+ );
1975
+ this.onSetup(() => {
1976
+ timerNumberLabel.text = timerInitialNumber.toString();
1977
+ });
1978
+ this.onAppear(() => {
1979
+ this.run(Action.sequence(countdownSequence));
1980
+ });
1981
+ }
1743
1982
  }
1744
1983
 
1745
- console.log("\u26AA @m2c2kit/addons version 0.3.12 (38470c49)");
1984
+ console.log("\u26AA @m2c2kit/addons version 0.3.14 (ebbdc605)");
1746
1985
 
1747
- export { Button, Dialog, DialogResult, DrawPad, DrawPadEventType, DrawPadItemEventType, Grid, Instructions, VirtualKeyboard };
1986
+ export { Button, CountdownScene, Dialog, DialogResult, DrawPad, DrawPadEventType, DrawPadItemEventType, Grid, Instructions, VirtualKeyboard };
1748
1987
  //# sourceMappingURL=index.js.map