@m2c2kit/addons 0.3.15 → 0.3.17

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,4 +1,4 @@
1
- import { Composite, WebColors, Shape, Label, CanvasKitHelpers, M2EventType, MutablePath, Easings, Story, Transition, TransitionDirection, LabelHorizontalAlignmentMode, Scene, Dimensions, Sprite, Action } from '@m2c2kit/core';
1
+ import { M2c2KitHelpers, Composite, WebColors, Shape, EventStoreMode, Equal, Label, CanvasKitHelpers, M2EventType, Timer, MutablePath, Easings, Story, Transition, TransitionDirection, LabelHorizontalAlignmentMode, Scene, Dimensions, Sprite, Action } from '@m2c2kit/core';
2
2
 
3
3
  class Grid extends Composite {
4
4
  /**
@@ -13,16 +13,16 @@ class Grid extends Composite {
13
13
  */
14
14
  constructor(options) {
15
15
  super(options);
16
- this.compositeType = "grid";
16
+ this.compositeType = "Grid";
17
17
  // Grid options
18
- // TODO: make getter, setter for these so they can be changed after initial construction
19
- this.rows = 0;
20
- this.columns = 0;
21
- // default Grid is: transparent gray, red lines, line width 1
22
- this.gridBackgroundColor = [0, 0, 233, 0.25];
23
- this.gridLineColor = WebColors.Red;
24
- this.gridLineWidth = 1;
25
- this.gridChildren = new Array();
18
+ this._rows = 0;
19
+ this._columns = 0;
20
+ // default Grid is: transparent blue, red lines, line width 1
21
+ this._gridBackgroundColor = [0, 0, 255, 0.25];
22
+ this._gridLineColor = WebColors.Red;
23
+ this._gridLineWidth = 1;
24
+ this._gridChildren = new Array();
25
+ this.cellContainers = new Array();
26
26
  if (options.size) {
27
27
  this.size = options.size;
28
28
  } else {
@@ -32,19 +32,19 @@ class Grid extends Composite {
32
32
  if (options.rows >= 1) {
33
33
  this.rows = options.rows;
34
34
  } else {
35
- throw new Error("rows must be at least 1");
35
+ throw new Error("grid rows must be at least 1");
36
36
  }
37
37
  } else {
38
- throw new Error("rows must be specified");
38
+ throw new Error("grid rows must be specified");
39
39
  }
40
40
  if (options.columns) {
41
41
  if (options.columns >= 1) {
42
42
  this.columns = options.columns;
43
43
  } else {
44
- throw new Error("columns must be at least 1");
44
+ throw new Error("grid columns must be at least 1");
45
45
  }
46
46
  } else {
47
- throw new Error("columns must be specified");
47
+ throw new Error("grid columns must be specified");
48
48
  }
49
49
  if (options.backgroundColor) {
50
50
  this.gridBackgroundColor = options.backgroundColor;
@@ -57,56 +57,114 @@ class Grid extends Composite {
57
57
  }
58
58
  this.cellWidth = this.size.width / this.columns;
59
59
  this.cellHeight = this.size.height / this.rows;
60
+ this.saveNodeNewEvent();
61
+ }
62
+ get completeNodeOptions() {
63
+ return {
64
+ ...this.options,
65
+ ...this.getNodeOptions(),
66
+ ...this.getDrawableOptions(),
67
+ rows: this.rows,
68
+ columns: this.columns,
69
+ size: this.size,
70
+ backgroundColor: this.gridBackgroundColor,
71
+ gridLineWidth: this.gridLineWidth,
72
+ gridLineColor: this.gridLineColor
73
+ };
60
74
  }
61
75
  initialize() {
62
- super.removeAllChildren();
76
+ this.descendants.forEach((d) => {
77
+ if (d.parent === this) {
78
+ super.removeChild(d);
79
+ } else {
80
+ d.parent?.removeChild(d);
81
+ }
82
+ });
63
83
  this.gridBackground = new Shape({
64
- name: "_" + this.name + "-gridBackground",
84
+ name: "__" + this.name + "-gridRectangle",
65
85
  rect: { size: this.size },
66
- //size: this.size,
67
86
  fillColor: this.gridBackgroundColor,
68
87
  strokeColor: this.gridLineColor,
69
88
  lineWidth: this.gridLineWidth,
70
- isUserInteractionEnabled: this.isUserInteractionEnabled
89
+ isUserInteractionEnabled: this.isUserInteractionEnabled,
90
+ suppressEvents: true
71
91
  });
72
- this.addChild(this.gridBackground);
92
+ super.addChild(this.gridBackground);
73
93
  this.gridBackground.isUserInteractionEnabled = this.isUserInteractionEnabled;
74
94
  for (let col = 1; col < this.columns; col++) {
75
95
  const verticalLine = new Shape({
76
- name: "_" + this.name + "-gridVerticalLine-" + col,
96
+ name: "__" + this.name + "-gridVerticalLine-" + (col - 1),
77
97
  rect: {
78
98
  size: { width: this.gridLineWidth, height: this.size.height },
79
99
  origin: { x: -this.size.width / 2 + this.cellWidth * col, y: 0 }
80
100
  },
81
- fillColor: this.gridLineColor
101
+ fillColor: this.gridLineColor,
102
+ suppressEvents: true
82
103
  });
83
104
  this.gridBackground.addChild(verticalLine);
84
105
  }
85
106
  for (let row = 1; row < this.rows; row++) {
86
107
  const horizontalLine = new Shape({
87
- name: "_" + this.name + "-gridHorizontalLine-" + row,
108
+ name: "__" + this.name + "-gridHorizontalLine-" + (row - 1),
88
109
  rect: {
89
110
  size: { width: this.size.width, height: this.gridLineWidth },
90
111
  origin: { x: 0, y: -this.size.height / 2 + this.cellHeight * row }
91
112
  },
92
- fillColor: this.gridLineColor
113
+ fillColor: this.gridLineColor,
114
+ suppressEvents: true
93
115
  });
94
116
  this.gridBackground.addChild(horizontalLine);
95
117
  }
96
- if (this.gridChildren) {
118
+ this.cellContainers = new Array(this.rows).fill([]).map(() => new Array(this.columns));
119
+ for (let row = 0; row < this.rows; row++) {
120
+ for (let col = 0; col < this.columns; col++) {
121
+ const cellContainer = new Shape({
122
+ name: "__" + this.name + "-gridCellContainer-" + row + "-" + col,
123
+ rect: {
124
+ size: { width: this.cellWidth, height: this.cellHeight },
125
+ origin: {
126
+ x: -this.size.width / 2 + this.cellWidth * col + this.cellWidth / 2,
127
+ y: -this.size.height / 2 + this.cellHeight * row + this.cellHeight / 2
128
+ }
129
+ },
130
+ fillColor: WebColors.Transparent,
131
+ lineWidth: 0,
132
+ suppressEvents: true
133
+ });
134
+ this.gridBackground.addChild(cellContainer);
135
+ this.cellContainers[row][col] = cellContainer;
136
+ }
137
+ }
138
+ if (this.gridChildren.length > 0) {
97
139
  this.gridChildren.forEach((gridChild) => {
98
140
  if (!this.cellWidth || !this.cellHeight || !this.gridBackground) {
99
141
  throw new Error(
100
142
  "cellWidth, cellHeight, or gridBackground undefined or null"
101
143
  );
102
144
  }
103
- const x = -this.size.width / 2 + this.cellWidth / 2 + gridChild.column * this.cellWidth;
104
- const y = -this.size.height / 2 + this.cellHeight / 2 + gridChild.row * this.cellHeight;
105
- gridChild.node.position = {
106
- x: x + gridChild.node.position.x,
107
- y: y + gridChild.node.position.y
108
- };
109
- this.gridBackground.addChild(gridChild.node);
145
+ if (this.game.eventStore.mode === EventStoreMode.Replay) {
146
+ const childNode = [
147
+ ...this.game.nodes,
148
+ ...this.game.materializedNodes
149
+ ].find(
150
+ (n) => (
151
+ // gridChild.node is the uuid of the child node here!
152
+ n.uuid === gridChild.node
153
+ )
154
+ );
155
+ if (!childNode) {
156
+ throw new Error("grid: child node not found");
157
+ }
158
+ childNode?.parent?.removeChild(childNode);
159
+ this.cellContainers[gridChild.row][gridChild.column].addChild(
160
+ childNode
161
+ );
162
+ } else {
163
+ gridChild.node.parent?.removeChild(gridChild.node);
164
+ this.cellContainers[gridChild.row][gridChild.column].addChild(
165
+ gridChild.node
166
+ );
167
+ }
110
168
  });
111
169
  }
112
170
  this.needsInitialization = false;
@@ -120,6 +178,60 @@ class Grid extends Composite {
120
178
  set gridBackground(gridBackground) {
121
179
  this._gridBackground = gridBackground;
122
180
  }
181
+ /**
182
+ * note: below we do not have getter and setter for size because the getter
183
+ * and setter in M2Node will handle it.
184
+ */
185
+ get rows() {
186
+ return this._rows;
187
+ }
188
+ set rows(rows) {
189
+ if (Equal.value(this._rows, rows)) {
190
+ return;
191
+ }
192
+ this._rows = rows;
193
+ this.needsInitialization = true;
194
+ }
195
+ get columns() {
196
+ return this._columns;
197
+ }
198
+ set columns(columns) {
199
+ if (Equal.value(this._columns, columns)) {
200
+ return;
201
+ }
202
+ this._columns = columns;
203
+ this.needsInitialization = true;
204
+ }
205
+ get gridBackgroundColor() {
206
+ return this._gridBackgroundColor;
207
+ }
208
+ set gridBackgroundColor(backgroundColor) {
209
+ if (Equal.value(this._gridBackgroundColor, backgroundColor)) {
210
+ return;
211
+ }
212
+ this._gridBackgroundColor = backgroundColor;
213
+ this.needsInitialization = true;
214
+ }
215
+ get gridLineWidth() {
216
+ return this._gridLineWidth;
217
+ }
218
+ set gridLineWidth(gridLineWidth) {
219
+ if (Equal.value(this._gridLineWidth, gridLineWidth)) {
220
+ return;
221
+ }
222
+ this._gridLineWidth = gridLineWidth;
223
+ this.needsInitialization = true;
224
+ }
225
+ get gridLineColor() {
226
+ return this._gridLineColor;
227
+ }
228
+ set gridLineColor(gridLineColor) {
229
+ if (Equal.value(this._gridLineColor, gridLineColor)) {
230
+ return;
231
+ }
232
+ this._gridLineColor = gridLineColor;
233
+ this.needsInitialization = true;
234
+ }
123
235
  // all nodes that make up grid are added as children, so they
124
236
  // have their own dispose methods
125
237
  // eslint-disable-next-line @typescript-eslint/no-empty-function
@@ -168,25 +280,47 @@ class Grid extends Composite {
168
280
  child.warmup(canvas);
169
281
  });
170
282
  }
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
283
  /**
174
- * Removes all children from the grid, but retains grid lines.
284
+ * The child nodes that have been added to the grid.
285
+ *
286
+ * @remarks Do not set this property directly. Use the methods for adding
287
+ * and removing grid children, such as `addAtCell()`, `removeAllAtCell()`,
288
+ * `removeGridChild()`, and `removeAllGridChildren()`.
175
289
  */
176
- removeAllChildren() {
290
+ get gridChildren() {
291
+ return this._gridChildren;
292
+ }
293
+ set gridChildren(gridChildren) {
294
+ this._gridChildren = gridChildren;
295
+ this.needsInitialization = true;
296
+ this.savePropertyChangeEvent(
297
+ "gridChildren",
298
+ this.gridChildren.map(
299
+ (gridChild) => ({
300
+ node: gridChild.node.uuid,
301
+ row: gridChild.row,
302
+ column: gridChild.column
303
+ })
304
+ )
305
+ );
306
+ }
307
+ /**
308
+ * Removes all grid children from the grid.
309
+ *
310
+ * @remarks This retains grid lines and grid appearance.
311
+ */
312
+ removeAllGridChildren() {
177
313
  if (this.gridChildren.length === 0) {
178
314
  return;
179
315
  }
180
316
  while (this.gridChildren.length) {
181
- const gridChild = this.gridChildren.pop();
182
- if (gridChild) {
183
- this.gridBackground.removeChild(gridChild.node);
184
- }
317
+ this.gridChildren = this.gridChildren.slice(0, -1);
185
318
  }
186
319
  this.needsInitialization = true;
187
320
  }
188
321
  /**
189
- * Adds a node to the grid at the specified row and column position.
322
+ * Adds a node as a grid child to the grid at the specified row and column
323
+ * position.
190
324
  *
191
325
  * @param node - node to add to the grid
192
326
  * @param row - row position within grid to add node; zero-based indexing
@@ -198,49 +332,68 @@ class Grid extends Composite {
198
332
  `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
333
  );
200
334
  }
201
- this.gridChildren.push({ node, row, column });
335
+ this.gridChildren = [
336
+ ...this.gridChildren,
337
+ { node, row, column }
338
+ ];
202
339
  this.needsInitialization = true;
203
340
  }
204
341
  /**
205
- * Removes all child nodes at the specified row and column position.
342
+ * Removes all grid child nodes at the specified row and column position.
206
343
  *
207
- * @param row - row position within grid at which to remove children; zero-based indexing
208
- * @param column - column position within grid at which to remove children; zero-based indexing
344
+ * @param row - row position within grid at which to remove grid children; zero-based indexing
345
+ * @param column - column position within grid at which to remove grid children; zero-based indexing
209
346
  */
210
347
  removeAllAtCell(row, column) {
211
- const gridChildrenToRemove = this.gridChildren.filter(
212
- (gridChild) => gridChild.row === row && gridChild.column === column
213
- );
214
- if (gridChildrenToRemove.length === 0) {
215
- return;
216
- }
217
- this.gridBackground.removeChildren(
218
- gridChildrenToRemove.map((gridChild) => gridChild.node)
219
- );
220
348
  this.gridChildren = this.gridChildren.filter(
221
349
  (gridChild) => gridChild.row !== row && gridChild.column !== column
222
350
  );
223
351
  this.needsInitialization = true;
224
352
  }
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
353
  /**
228
- * Removes the child node from the grid.
354
+ * Removes the grid child node from the grid.
229
355
  *
230
356
  * @param node - node to remove
231
357
  */
232
- removeChild(node) {
233
- this.gridBackground.removeChild(node);
358
+ removeGridChild(node) {
234
359
  this.gridChildren = this.gridChildren.filter(
235
360
  (gridChild) => gridChild.node != node
236
361
  );
237
362
  this.needsInitialization = true;
238
363
  }
364
+ // The Grid manages its own children (background, lines, and cell
365
+ // containers). It is probably a mistake when the user tries to add or remove
366
+ // these children. The user probably meant to add or remove grid children
367
+ // instead. Warn the user about this.
368
+ addChild(child) {
369
+ console.warn(
370
+ "Grid.addChild() was called -- did you mean to call addAtCell() instead?"
371
+ );
372
+ super.addChild(child);
373
+ }
374
+ removeAllChildren() {
375
+ console.warn(
376
+ "Grid.removeAllChildren() was called -- did you mean to call removeAllGridChildren() instead?"
377
+ );
378
+ super.removeAllChildren();
379
+ }
380
+ removeChild(child) {
381
+ console.warn(
382
+ "Grid.removeChild() was called -- did you mean to call removeGridChild() instead?"
383
+ );
384
+ super.removeChild(child);
385
+ }
386
+ removeChildren(children) {
387
+ console.warn(
388
+ "Grid.removeChildren() was called -- did you mean to call removeGridChild() instead?"
389
+ );
390
+ super.removeChildren(children);
391
+ }
239
392
  }
393
+ M2c2KitHelpers.registerM2NodeClass(Grid);
240
394
 
241
395
  class Button extends Composite {
242
- // todo: add getters/setters so button can respond to changes in its options
243
- // todo: add default "behaviors" (?) like button click animation?
396
+ // TODO: add default "behaviors" (?) like button click animation?
244
397
  /**
245
398
  * A simple button of rectangle with text centered inside.
246
399
  *
@@ -252,12 +405,12 @@ class Button extends Composite {
252
405
  */
253
406
  constructor(options) {
254
407
  super(options);
255
- this.compositeType = "button";
408
+ this.compositeType = "Button";
409
+ this.isText = true;
256
410
  // Button options
257
411
  this._backgroundColor = WebColors.Black;
258
- this.size = { width: 200, height: 50 };
259
- this.cornerRadius = 9;
260
- this.fontSize = 20;
412
+ this._cornerRadius = 9;
413
+ this._fontSize = 20;
261
414
  this._text = "";
262
415
  this._fontColor = WebColors.White;
263
416
  this._interpolation = {};
@@ -267,6 +420,8 @@ class Button extends Composite {
267
420
  }
268
421
  if (options.size) {
269
422
  this.size = options.size;
423
+ } else {
424
+ this.size = { width: 200, height: 50 };
270
425
  }
271
426
  if (options.cornerRadius !== void 0) {
272
427
  this.cornerRadius = options.cornerRadius;
@@ -292,6 +447,19 @@ class Button extends Composite {
292
447
  if (options.localize !== void 0) {
293
448
  this.localize = options.localize;
294
449
  }
450
+ this.saveNodeNewEvent();
451
+ }
452
+ get completeNodeOptions() {
453
+ return {
454
+ ...this.options,
455
+ ...this.getNodeOptions(),
456
+ ...this.getDrawableOptions(),
457
+ ...this.getTextOptions(),
458
+ size: this.size,
459
+ cornerRadius: this.cornerRadius,
460
+ backgroundColor: this.backgroundColor,
461
+ fontNames: this.fontNames
462
+ };
295
463
  }
296
464
  initialize() {
297
465
  this.removeAllChildren();
@@ -306,19 +474,23 @@ class Button extends Composite {
306
474
  );
307
475
  this.backgroundPaint.setStyle(this.canvasKit.PaintStyle.Fill);
308
476
  const buttonRectangle = new Shape({
477
+ name: "__" + this.name + "-buttonRectangle",
309
478
  rect: { size: this.size },
310
479
  cornerRadius: this.cornerRadius,
311
- fillColor: this._backgroundColor
480
+ fillColor: this._backgroundColor,
481
+ suppressEvents: true
312
482
  });
313
483
  this.addChild(buttonRectangle);
314
484
  const buttonLabel = new Label({
485
+ name: "__" + this.name + "-buttonLabel",
315
486
  text: this.text,
316
487
  localize: this.localize,
317
488
  interpolation: this.interpolation,
318
489
  fontName: this.fontName,
319
490
  fontNames: this.fontNames,
320
491
  fontSize: this.fontSize,
321
- fontColor: this.fontColor
492
+ fontColor: this.fontColor,
493
+ suppressEvents: true
322
494
  });
323
495
  buttonRectangle.addChild(buttonLabel);
324
496
  this.needsInitialization = false;
@@ -330,51 +502,101 @@ class Button extends Composite {
330
502
  return this._text;
331
503
  }
332
504
  set text(text) {
505
+ if (Equal.value(this._text, text)) {
506
+ return;
507
+ }
333
508
  this._text = text;
334
509
  this.needsInitialization = true;
510
+ this.savePropertyChangeEvent("text", text);
335
511
  }
336
512
  get backgroundColor() {
337
513
  return this._backgroundColor;
338
514
  }
339
515
  set backgroundColor(backgroundColor) {
516
+ if (Equal.value(this._backgroundColor, backgroundColor)) {
517
+ return;
518
+ }
340
519
  this._backgroundColor = backgroundColor;
341
520
  this.needsInitialization = true;
521
+ this.savePropertyChangeEvent("backgroundColor", backgroundColor);
342
522
  }
343
523
  get fontColor() {
344
524
  return this._fontColor;
345
525
  }
346
526
  set fontColor(fontColor) {
527
+ if (Equal.value(this._fontColor, fontColor)) {
528
+ return;
529
+ }
347
530
  this._fontColor = fontColor;
348
531
  this.needsInitialization = true;
532
+ this.savePropertyChangeEvent("fontColor", fontColor);
349
533
  }
350
534
  get fontName() {
351
535
  return this._fontName;
352
536
  }
353
537
  set fontName(fontName) {
538
+ if (this._fontName === fontName) {
539
+ return;
540
+ }
354
541
  this._fontName = fontName;
355
542
  this.needsInitialization = true;
543
+ this.savePropertyChangeEvent("fontName", fontName);
356
544
  }
357
545
  get fontNames() {
358
546
  return this._fontNames;
359
547
  }
360
548
  set fontNames(fontNames) {
549
+ if (Equal.value(this._fontNames, fontNames)) {
550
+ return;
551
+ }
361
552
  this._fontNames = fontNames;
362
553
  this.needsInitialization = true;
554
+ this.savePropertyChangeEvent("fontNames", fontNames);
555
+ }
556
+ get cornerRadius() {
557
+ return this._cornerRadius;
558
+ }
559
+ set cornerRadius(cornerRadius) {
560
+ if (Equal.value(this._cornerRadius, cornerRadius)) {
561
+ return;
562
+ }
563
+ this._cornerRadius = cornerRadius;
564
+ this.needsInitialization = true;
565
+ this.savePropertyChangeEvent("cornerRadius", cornerRadius);
566
+ }
567
+ get fontSize() {
568
+ return this._fontSize;
569
+ }
570
+ set fontSize(fontSize) {
571
+ if (Equal.value(this._fontSize, fontSize)) {
572
+ return;
573
+ }
574
+ this._fontSize = fontSize;
575
+ this.needsInitialization = true;
576
+ this.savePropertyChangeEvent("fontSize", fontSize);
363
577
  }
364
578
  get interpolation() {
365
579
  return this._interpolation;
366
580
  }
367
581
  set interpolation(interpolation) {
582
+ if (Equal.value(this._interpolation, interpolation)) {
583
+ return;
584
+ }
368
585
  this._interpolation = interpolation;
369
586
  Object.freeze(this._interpolation);
370
587
  this.needsInitialization = true;
588
+ this.savePropertyChangeEvent("interpolation", interpolation);
371
589
  }
372
590
  get localize() {
373
591
  return this._localize;
374
592
  }
375
593
  set localize(localize) {
594
+ if (Equal.value(this._localize, localize)) {
595
+ return;
596
+ }
376
597
  this._localize = localize;
377
598
  this.needsInitialization = true;
599
+ this.savePropertyChangeEvent("localize", localize);
378
600
  }
379
601
  /**
380
602
  * Duplicates a node using deep copy.
@@ -423,6 +645,7 @@ class Button extends Composite {
423
645
  });
424
646
  }
425
647
  }
648
+ M2c2KitHelpers.registerM2NodeClass(Button);
426
649
 
427
650
  var DialogResult = /* @__PURE__ */ ((DialogResult2) => {
428
651
  DialogResult2["Dismiss"] = "Dismiss";
@@ -442,9 +665,9 @@ class Dialog extends Composite {
442
665
  this.contentText = "";
443
666
  this.positiveButtonText = "";
444
667
  this.negativeButtonText = "";
668
+ this._fontColor = WebColors.White;
445
669
  this.zPosition = Number.MAX_VALUE;
446
670
  this.hidden = true;
447
- this._fontColor = WebColors.White;
448
671
  if (!options) {
449
672
  return;
450
673
  }
@@ -478,7 +701,7 @@ class Dialog extends Composite {
478
701
  }
479
702
  onDialogResult(callback, options) {
480
703
  const eventListener = {
481
- type: M2EventType.CompositeCustom,
704
+ type: M2EventType.Composite,
482
705
  compositeType: "DialogResult",
483
706
  nodeUuid: this.uuid,
484
707
  callback
@@ -494,10 +717,10 @@ class Dialog extends Composite {
494
717
  this.removeAllChildren();
495
718
  const overlay = new Shape({
496
719
  rect: {
497
- width: Globals.canvasCssWidth,
498
- height: Globals.canvasCssHeight,
499
- x: Globals.canvasCssWidth / 2,
500
- y: Globals.canvasCssHeight / 2
720
+ width: m2c2Globals.canvasCssWidth,
721
+ height: m2c2Globals.canvasCssHeight,
722
+ x: m2c2Globals.canvasCssWidth / 2,
723
+ y: m2c2Globals.canvasCssHeight / 2
501
724
  },
502
725
  fillColor: [0, 0, 0, this.overlayAlpha],
503
726
  zPosition: -1,
@@ -507,12 +730,14 @@ class Dialog extends Composite {
507
730
  e.handled = true;
508
731
  this.hidden = true;
509
732
  if (this.eventListeners.length > 0) {
510
- this.eventListeners.filter((listener) => listener.type === M2EventType.CompositeCustom).forEach((listener) => {
733
+ this.eventListeners.filter((listener) => listener.type === M2EventType.Composite).forEach((listener) => {
511
734
  const dialogEvent = {
512
- type: M2EventType.CompositeCustom,
735
+ type: M2EventType.Composite,
513
736
  target: this,
514
737
  handled: false,
515
- dialogResult: "Dismiss" /* Dismiss */
738
+ dialogResult: "Dismiss" /* Dismiss */,
739
+ timestamp: Timer.now(),
740
+ iso8601Timestamp: (/* @__PURE__ */ new Date()).toISOString()
516
741
  };
517
742
  listener.callback(dialogEvent);
518
743
  });
@@ -523,8 +748,8 @@ class Dialog extends Composite {
523
748
  rect: {
524
749
  width: 300,
525
750
  height: 150,
526
- x: Globals.canvasCssWidth / 2,
527
- y: Globals.canvasCssHeight / 2
751
+ x: m2c2Globals.canvasCssWidth / 2,
752
+ y: m2c2Globals.canvasCssHeight / 2
528
753
  },
529
754
  cornerRadius: this.cornerRadius,
530
755
  fillColor: this.backgroundColor,
@@ -555,12 +780,14 @@ class Dialog extends Composite {
555
780
  e.handled = true;
556
781
  this.hidden = true;
557
782
  if (this.eventListeners.length > 0) {
558
- this.eventListeners.filter((listener) => listener.type === M2EventType.CompositeCustom).forEach((listener) => {
783
+ this.eventListeners.filter((listener) => listener.type === M2EventType.Composite).forEach((listener) => {
559
784
  const dialogEvent = {
560
- type: M2EventType.CompositeCustom,
785
+ type: M2EventType.Composite,
561
786
  target: this,
562
787
  handled: false,
563
- dialogResult: "Negative" /* Negative */
788
+ dialogResult: "Negative" /* Negative */,
789
+ timestamp: Timer.now(),
790
+ iso8601Timestamp: (/* @__PURE__ */ new Date()).toISOString()
564
791
  };
565
792
  listener.callback(dialogEvent);
566
793
  });
@@ -577,12 +804,14 @@ class Dialog extends Composite {
577
804
  e.handled = true;
578
805
  this.hidden = true;
579
806
  if (this.eventListeners.length > 0) {
580
- this.eventListeners.filter((listener) => listener.type === M2EventType.CompositeCustom).forEach((listener) => {
807
+ this.eventListeners.filter((listener) => listener.type === M2EventType.Composite).forEach((listener) => {
581
808
  const dialogEvent = {
582
- type: M2EventType.CompositeCustom,
809
+ type: M2EventType.Composite,
583
810
  target: this,
584
811
  handled: false,
585
- dialogResult: "Positive" /* Positive */
812
+ dialogResult: "Positive" /* Positive */,
813
+ timestamp: Timer.now(),
814
+ iso8601Timestamp: (/* @__PURE__ */ new Date()).toISOString()
586
815
  };
587
816
  listener.callback(dialogEvent);
588
817
  });
@@ -606,6 +835,13 @@ class Dialog extends Composite {
606
835
  this._fontColor = fontColor;
607
836
  this.needsInitialization = true;
608
837
  }
838
+ get hidden() {
839
+ return this._hidden;
840
+ }
841
+ set hidden(hidden) {
842
+ this._hidden = hidden;
843
+ this.needsInitialization;
844
+ }
609
845
  /**
610
846
  * Duplicates a node using deep copy.
611
847
  *
@@ -617,7 +853,7 @@ class Dialog extends Composite {
617
853
  * provided, name will be the new uuid
618
854
  */
619
855
  duplicate(newName) {
620
- throw new Error("duplicate not implemented");
856
+ throw new Error(`duplicate not implemented. ${newName}`);
621
857
  }
622
858
  update() {
623
859
  super.update();
@@ -668,6 +904,7 @@ class DrawPad extends Composite {
668
904
  * of all interactions with each DrawPadStroke.
669
905
  */
670
906
  this.strokes = new Array();
907
+ this.originalOptions = JSON.parse(JSON.stringify(options));
671
908
  if (options.isUserInteractionEnabled === void 0) {
672
909
  this.isUserInteractionEnabled = true;
673
910
  }
@@ -699,6 +936,15 @@ class DrawPad extends Composite {
699
936
  if (options.continuousDrawingOnlyExceptionDistance !== void 0) {
700
937
  this.continuousDrawingOnlyExceptionDistance = options.continuousDrawingOnlyExceptionDistance;
701
938
  }
939
+ this.saveNodeNewEvent();
940
+ }
941
+ get completeNodeOptions() {
942
+ return {
943
+ ...this.options,
944
+ ...this.getNodeOptions(),
945
+ ...this.getDrawableOptions(),
946
+ ...this.originalOptions
947
+ };
702
948
  }
703
949
  initialize() {
704
950
  this.initializeDrawShape();
@@ -721,7 +967,8 @@ class DrawPad extends Composite {
721
967
  if (!this.drawArea) {
722
968
  this.drawArea = new Shape({
723
969
  rect: { size: this.size },
724
- isUserInteractionEnabled: true
970
+ isUserInteractionEnabled: true,
971
+ suppressEvents: true
725
972
  });
726
973
  this.addChild(this.drawArea);
727
974
  this.drawArea.onTapDown((e) => {
@@ -765,7 +1012,8 @@ class DrawPad extends Composite {
765
1012
  type: DrawPadEventType.StrokeStart,
766
1013
  target: this,
767
1014
  handled: false,
768
- position: e.point
1015
+ position: e.point,
1016
+ ...M2c2KitHelpers.createTimestamps()
769
1017
  };
770
1018
  this.strokes.push([
771
1019
  {
@@ -796,7 +1044,8 @@ class DrawPad extends Composite {
796
1044
  type: DrawPadEventType.StrokeMove,
797
1045
  target: this,
798
1046
  handled: false,
799
- position: interpolatedPoint
1047
+ position: interpolatedPoint,
1048
+ ...M2c2KitHelpers.createTimestamps()
800
1049
  };
801
1050
  this.strokes[strokeCount - 1].push({
802
1051
  type: DrawPadEventType.StrokeMove,
@@ -826,7 +1075,8 @@ class DrawPad extends Composite {
826
1075
  type: DrawPadEventType.StrokeEnd,
827
1076
  position: this.strokes[strokeCount - 1][strokeInteractionCount - 1].position,
828
1077
  target: this,
829
- handled: false
1078
+ handled: false,
1079
+ ...M2c2KitHelpers.createTimestamps()
830
1080
  };
831
1081
  this.strokes[strokeCount - 1].push({
832
1082
  type: DrawPadEventType.StrokeEnd,
@@ -854,7 +1104,8 @@ class DrawPad extends Composite {
854
1104
  type: DrawPadEventType.StrokeEnd,
855
1105
  position: this.strokes[strokeCount - 1][strokeInteractionCount - 1].position,
856
1106
  target: this,
857
- handled: false
1107
+ handled: false,
1108
+ ...M2c2KitHelpers.createTimestamps()
858
1109
  };
859
1110
  this.strokes[strokeCount - 1].push({
860
1111
  type: DrawPadEventType.StrokeEnd,
@@ -882,7 +1133,8 @@ class DrawPad extends Composite {
882
1133
  type: DrawPadEventType.StrokeMove,
883
1134
  target: this,
884
1135
  handled: false,
885
- position: e.point
1136
+ position: e.point,
1137
+ ...M2c2KitHelpers.createTimestamps()
886
1138
  };
887
1139
  const strokeCount = this.strokes.length;
888
1140
  this.strokes[strokeCount - 1].push({
@@ -1037,7 +1289,8 @@ class DrawPad extends Composite {
1037
1289
  node.isStrokeWithinBounds = true;
1038
1290
  const drawPadItemEvent = {
1039
1291
  type: DrawPadItemEventType.StrokeEnter,
1040
- target: node
1292
+ target: node,
1293
+ ...M2c2KitHelpers.createTimestamps()
1041
1294
  };
1042
1295
  this.raiseDrawPadItemEvent(node, drawPadItemEvent);
1043
1296
  }
@@ -1049,7 +1302,8 @@ class DrawPad extends Composite {
1049
1302
  node.isStrokeWithinBounds = true;
1050
1303
  const drawPadItemEvent = {
1051
1304
  type: DrawPadItemEventType.StrokeEnter,
1052
- target: node
1305
+ target: node,
1306
+ ...M2c2KitHelpers.createTimestamps()
1053
1307
  };
1054
1308
  this.raiseDrawPadItemEvent(node, drawPadItemEvent);
1055
1309
  }
@@ -1061,7 +1315,8 @@ class DrawPad extends Composite {
1061
1315
  node.isStrokeWithinBounds = false;
1062
1316
  const drawPadItemEvent = {
1063
1317
  type: DrawPadItemEventType.StrokeLeave,
1064
- target: node
1318
+ target: node,
1319
+ ...M2c2KitHelpers.createTimestamps()
1065
1320
  };
1066
1321
  this.raiseDrawPadItemEvent(node, drawPadItemEvent);
1067
1322
  }
@@ -1072,7 +1327,8 @@ class DrawPad extends Composite {
1072
1327
  node.isStrokeWithinBounds = false;
1073
1328
  const drawPadItemEvent = {
1074
1329
  type: DrawPadItemEventType.StrokeLeave,
1075
- target: node
1330
+ target: node,
1331
+ ...M2c2KitHelpers.createTimestamps()
1076
1332
  };
1077
1333
  this.raiseDrawPadItemEvent(node, drawPadItemEvent);
1078
1334
  }
@@ -1095,10 +1351,10 @@ class DrawPad extends Composite {
1095
1351
  if (!drawArea) {
1096
1352
  throw new Error("DrawPad.takeScreenshot(): no drawArea");
1097
1353
  }
1098
- const sx = (drawArea.absolutePosition.x - drawArea.size.width / 2) * Globals.canvasScale;
1099
- const sy = (drawArea.absolutePosition.y - drawArea.size.height / 2) * Globals.canvasScale;
1100
- const sw = drawArea.size.width * Globals.canvasScale;
1101
- const sh = drawArea.size.height * Globals.canvasScale;
1354
+ const sx = (drawArea.absolutePosition.x - drawArea.size.width / 2) * m2c2Globals.canvasScale;
1355
+ const sy = (drawArea.absolutePosition.y - drawArea.size.height / 2) * m2c2Globals.canvasScale;
1356
+ const sw = drawArea.size.width * m2c2Globals.canvasScale;
1357
+ const sh = drawArea.size.height * m2c2Globals.canvasScale;
1102
1358
  const imageInfo = {
1103
1359
  alphaType: this.game.canvasKit.AlphaType.Unpremul,
1104
1360
  colorType: this.game.canvasKit.ColorType.RGBA_8888,
@@ -1237,9 +1493,10 @@ class DrawPad extends Composite {
1237
1493
  this.needsInitialization = true;
1238
1494
  }
1239
1495
  duplicate(newName) {
1240
- throw new Error("DrawPad.duplicate(): Method not implemented.");
1496
+ throw new Error(`DrawPad.duplicate(): Method not implemented. ${newName}`);
1241
1497
  }
1242
1498
  }
1499
+ M2c2KitHelpers.registerM2NodeClass(DrawPad);
1243
1500
 
1244
1501
  class VirtualKeyboard extends Composite {
1245
1502
  /**
@@ -1250,11 +1507,14 @@ class VirtualKeyboard extends Composite {
1250
1507
  constructor(options) {
1251
1508
  super(options);
1252
1509
  this.compositeType = "VirtualKeyboard";
1253
- this.rowsConfiguration = new Array();
1510
+ this.keyboardRows = new Array();
1254
1511
  this.shiftActivated = false;
1255
1512
  this.keyShapes = new Array();
1256
- // VirtualKeyboard defaults to being user interactive.
1257
- this._isUserInteractionEnabled = true;
1513
+ this.keyLabels = new Array();
1514
+ this.originalOptions = JSON.parse(JSON.stringify(options));
1515
+ if (options.isUserInteractionEnabled === void 0) {
1516
+ this._isUserInteractionEnabled = true;
1517
+ }
1258
1518
  this.size = options.size;
1259
1519
  this.position = options.position ?? { x: 0, y: 0 };
1260
1520
  this.keyboardHorizontalPaddingPercent = options.keyboardHorizontalPaddingPercent ?? 0.02;
@@ -1262,11 +1522,12 @@ class VirtualKeyboard extends Composite {
1262
1522
  this.keyHorizontalPaddingPercent = options.keyHorizontalPaddingPercent ?? 0.1;
1263
1523
  this.keyVerticalPaddingPercent = options.keyVerticalPaddingPercent ?? 0.1;
1264
1524
  if (options.rows !== void 0) {
1265
- this.rowsConfiguration = options.rows.map((row) => {
1525
+ this.keyboardRows = options.rows.map((row) => {
1266
1526
  const internalRow = row.map((key) => {
1267
1527
  if (key instanceof Object && !Array.isArray(key)) {
1268
1528
  const internalKeyConfig = key;
1269
1529
  if (key.keyIconShapeOptions) {
1530
+ key.keyIconShapeOptions.suppressEvents = true;
1270
1531
  internalKeyConfig.keyIcon = new Shape(key.keyIconShapeOptions);
1271
1532
  internalKeyConfig.keyIconShapeOptions = void 0;
1272
1533
  }
@@ -1288,157 +1549,48 @@ class VirtualKeyboard extends Composite {
1288
1549
  this.specialKeyDownColor = options.specialKeyDownColor ?? WebColors.LightSteelBlue;
1289
1550
  this.backgroundColor = options.backgroundColor ?? [242, 240, 244, 1];
1290
1551
  this.showKeyDownPreview = options.showKeyDownPreview ?? true;
1552
+ this.saveNodeNewEvent();
1553
+ }
1554
+ get completeNodeOptions() {
1555
+ return {
1556
+ ...this.options,
1557
+ ...this.getNodeOptions(),
1558
+ ...this.getDrawableOptions(),
1559
+ ...this.originalOptions
1560
+ };
1291
1561
  }
1292
1562
  initialize() {
1293
- if (this.rowsConfiguration.length === 0) {
1294
- const numKeys = [
1295
- ["1", "!"],
1296
- ["2", "@"],
1297
- ["3", "#"],
1298
- ["4", "$"],
1299
- ["5", "%"],
1300
- ["6", "^"],
1301
- ["7", "&"],
1302
- ["8", "*"],
1303
- ["9", "("],
1304
- ["0", ")"]
1305
- ];
1306
- const row1 = [
1307
- "q",
1308
- "w",
1309
- "e",
1310
- "r",
1311
- "t",
1312
- "y",
1313
- "u",
1314
- "i",
1315
- "o",
1316
- "p"
1317
- ];
1318
- const row2 = [
1319
- "a",
1320
- "s",
1321
- "d",
1322
- "f",
1323
- "g",
1324
- "h",
1325
- "j",
1326
- "k",
1327
- "l"
1328
- ];
1329
- const shiftArrowShapeOptions = {
1330
- path: {
1331
- // Public Domain from https://www.freesvg.org
1332
- pathString: "m288-6.6849e-14 -288 288h144v288h288v-288h144l-288-288z",
1333
- width: 24
1334
- },
1335
- lineWidth: 2,
1336
- strokeColor: WebColors.Black,
1337
- fillColor: WebColors.Transparent
1338
- };
1339
- const backspaceShapeOptions = {
1340
- path: {
1341
- // CC0 from https://www.svgrepo.com
1342
- pathString: "M10.625 5.09 0 22.09l10.625 17H44.18v-34H10.625zm31.555 32H11.734l-9.375-15 9.375-15H42.18v30zm-23.293-6.293 7.293-7.293 7.293 7.293 1.414-1.414-7.293-7.293 7.293-7.293-1.414-1.414-7.293 7.293-7.293-7.293-1.414 1.414 7.293 7.293-7.293 7.293",
1343
- width: 24
1344
- },
1345
- lineWidth: 1,
1346
- strokeColor: WebColors.Black,
1347
- fillColor: WebColors.Red
1348
- };
1349
- const row3 = [
1350
- {
1351
- code: "Shift",
1352
- isShift: true,
1353
- widthRatio: 1.5,
1354
- keyIcon: new Shape(shiftArrowShapeOptions)
1355
- },
1356
- "z",
1357
- "x",
1358
- "c",
1359
- "v",
1360
- "b",
1361
- "n",
1362
- "m",
1363
- {
1364
- code: "Backspace",
1365
- widthRatio: 1.5,
1366
- keyIcon: new Shape(backspaceShapeOptions)
1367
- }
1368
- ];
1369
- const row4 = [
1370
- { code: " ", labelText: "SPACE", widthRatio: 5 }
1371
- ];
1372
- const keyboardRows = [
1373
- numKeys,
1374
- row1,
1375
- row2,
1376
- row3,
1377
- row4
1378
- ];
1379
- this.rowsConfiguration = keyboardRows;
1380
- this.keysPerRow = this.rowsConfiguration.reduce(
1563
+ if (this.game.eventStore.mode === EventStoreMode.Replay) {
1564
+ this._isUserInteractionEnabled = false;
1565
+ }
1566
+ if (this.keyboardRows.length === 0) {
1567
+ this.keyboardRows = this.createDefaultKeyboardRows();
1568
+ this.keysPerRow = this.keyboardRows.reduce(
1381
1569
  (max, row) => Math.max(max, row.length),
1382
1570
  0
1383
1571
  );
1384
- this.fontSize = this.size.height / this.rowsConfiguration.length / 2.5;
1572
+ this.fontSize = this.size.height / this.keyboardRows.length / 2.5;
1385
1573
  }
1386
1574
  const keyboardRectangle = new Shape({
1387
1575
  rect: { size: this.size },
1388
- fillColor: this.backgroundColor
1576
+ fillColor: this.backgroundColor,
1577
+ suppressEvents: true
1389
1578
  });
1390
1579
  this.addChild(keyboardRectangle);
1391
- const rows = this.rowsConfiguration.map((row) => {
1392
- return row.map((key) => {
1393
- let widthRatio = 1;
1394
- const heightRatio = 1;
1395
- let code;
1396
- let label;
1397
- let labelShifted;
1398
- let keyIcon;
1399
- let isShift = false;
1400
- if (typeof key === "string") {
1401
- code = key;
1402
- if (this.capitalLettersOnly) {
1403
- label = code.toUpperCase();
1404
- } else {
1405
- label = code;
1406
- }
1407
- labelShifted = code.toUpperCase();
1408
- } else if (Array.isArray(key)) {
1409
- code = key[0];
1410
- label = code;
1411
- labelShifted = key[1];
1412
- } else {
1413
- code = key.code;
1414
- label = key.labelText ?? "";
1415
- labelShifted = key.labelTextShifted ?? label;
1416
- widthRatio = key.widthRatio ?? 1;
1417
- keyIcon = key.keyIcon;
1418
- isShift = key.isShift ?? false;
1419
- }
1420
- return {
1421
- widthRatio,
1422
- heightRatio,
1423
- code,
1424
- labelText: label,
1425
- labelTextShifted: labelShifted,
1426
- keyIcon,
1427
- isShift
1428
- };
1429
- });
1430
- });
1580
+ const keyboard = this.internalKeyboardRowsToInternalKeyboardConfiguration(
1581
+ this.keyboardRows
1582
+ );
1431
1583
  const keyboardOrigin = {
1432
1584
  x: -keyboardRectangle.size.width / 2,
1433
1585
  y: -keyboardRectangle.size.height / 2
1434
1586
  };
1435
1587
  const keyboardVerticalPadding = (this.keyboardVerticalPaddingPercent ?? 0.025) * this.size.height;
1436
1588
  const keyboardHorizontalPadding = (this.keyboardHorizontalPaddingPercent ?? 0.02) * this.size.width;
1437
- const keyBoxHeight = (this.size.height - 2 * keyboardVerticalPadding) / rows.length;
1589
+ const keyBoxHeight = (this.size.height - 2 * keyboardVerticalPadding) / keyboard.length;
1438
1590
  const keyBoxWidth = (this.size.width - 2 * keyboardHorizontalPadding) / this.keysPerRow;
1439
1591
  this.keyShapes = [];
1440
- for (let r = 0; r < rows.length; r++) {
1441
- const row = rows[r];
1592
+ for (let r = 0; r < keyboard.length; r++) {
1593
+ const row = keyboard[r];
1442
1594
  const rowSumKeyWidths = row.reduce(
1443
1595
  (sum, key) => sum + (key.widthRatio ?? 1),
1444
1596
  0
@@ -1449,7 +1601,7 @@ class VirtualKeyboard extends Composite {
1449
1601
  }
1450
1602
  for (let k = 0; k < row.length; k++) {
1451
1603
  const key = row[k];
1452
- if (this.hiddenKeys?.split(",").map((s) => s.trim()).includes(key.code)) {
1604
+ if (this.hiddenKeys?.split(",").map((s) => s === " " ? " " : s.trim()).includes(key.code)) {
1453
1605
  continue;
1454
1606
  }
1455
1607
  const keyBoxWidthsSoFar = row.slice(0, k).reduce((sum, key2) => sum + (key2.widthRatio ?? 1), 0) * keyBoxWidth;
@@ -1466,7 +1618,8 @@ class VirtualKeyboard extends Composite {
1466
1618
  position: {
1467
1619
  x: extraPadding + keyboardOrigin.x + keyboardHorizontalPadding + keyBoxWidthsSoFar + (key.widthRatio ?? 1) * keyBoxWidth / 2,
1468
1620
  y: keyboardOrigin.y + keyboardVerticalPadding + r * keyBoxHeight + keyBoxHeight / 2
1469
- }
1621
+ },
1622
+ suppressEvents: true
1470
1623
  });
1471
1624
  const keyWidth = keyBoxWidth * (key.widthRatio ?? 1) - 2 * this.keyHorizontalPaddingPercent * keyBoxWidth;
1472
1625
  const keyHeight = keyBoxHeight - (key.heightRatio ?? 1) - 2 * this.keyVerticalPaddingPercent * keyBoxHeight;
@@ -1475,164 +1628,50 @@ class VirtualKeyboard extends Composite {
1475
1628
  cornerRadius: 4,
1476
1629
  fillColor: this.keyColor,
1477
1630
  lineWidth: 0,
1478
- isUserInteractionEnabled: this.isUserInteractionEnabled
1631
+ isUserInteractionEnabled: this.isUserInteractionEnabled,
1632
+ suppressEvents: true
1479
1633
  });
1634
+ keyShape.userData = { code: key.code };
1480
1635
  keyBox.addChild(keyShape);
1481
1636
  this.keyShapes.push(keyShape);
1482
- keyShape.onTapUp((tapEvent) => {
1483
- let keyAsString = "";
1484
- if (!key.isShift) {
1485
- if (this.shiftActivated) {
1486
- this.shiftActivated = false;
1487
- if (this.shiftKeyShape) {
1488
- this.shiftKeyShape.fillColor = this.keyColor;
1489
- if (key.keyIcon) {
1490
- key.keyIcon.fillColor = WebColors.Transparent;
1491
- }
1492
- }
1493
- rows.flatMap((k2) => k2).forEach((k2) => {
1494
- if (k2.keyLabel?.text !== void 0) {
1495
- k2.keyLabel.text = k2.labelText ?? "";
1496
- }
1497
- });
1498
- keyAsString = key.labelTextShifted ?? key.code;
1499
- } else {
1500
- keyAsString = key.labelText ?? key.code;
1501
- }
1502
- if (key.code === " " || key.code === "Backspace") {
1503
- keyAsString = key.code;
1504
- }
1505
- keyShape.fillColor = this.keyColor;
1506
- } else {
1507
- if (!this.shiftActivated) {
1508
- keyShape.fillColor = this.keyColor;
1509
- } else {
1510
- keyShape.fillColor = this.specialKeyDownColor;
1511
- }
1512
- keyAsString = key.code;
1513
- }
1514
- letterCircle.hidden = true;
1515
- if (this.eventListeners.length > 0) {
1516
- this.eventListeners.filter(
1517
- (listener) => listener.type === M2EventType.CompositeCustom && listener.compositeType === "VirtualKeyboardKeyUp"
1518
- ).forEach((listener) => {
1519
- const virtualKeyboardEvent = {
1520
- type: M2EventType.CompositeCustom,
1521
- target: this,
1522
- handled: false,
1523
- key: keyAsString,
1524
- code: key.code,
1525
- shiftKey: this.shiftActivated,
1526
- keyTapMetadata: {
1527
- size: keyShape.size,
1528
- point: tapEvent.point,
1529
- buttons: tapEvent.buttons
1530
- }
1531
- };
1532
- listener.callback(virtualKeyboardEvent);
1533
- });
1534
- }
1535
- });
1536
- keyShape.onTapDown((tapEvent) => {
1537
- if (key.isShift) {
1538
- this.shiftActivated = !this.shiftActivated;
1539
- if (this.shiftActivated) {
1540
- keyShape.fillColor = this.specialKeyDownColor;
1541
- if (key.keyIcon) {
1542
- key.keyIcon.fillColor = WebColors.Black;
1543
- }
1544
- rows.flatMap((k2) => k2).forEach((k2) => {
1545
- if (k2.keyLabel?.text !== void 0) {
1546
- k2.keyLabel.text = k2.labelTextShifted ?? k2.labelText ?? "";
1547
- }
1548
- });
1549
- } else {
1550
- keyShape.fillColor = this.keyColor;
1551
- if (key.keyIcon) {
1552
- key.keyIcon.fillColor = WebColors.Transparent;
1553
- }
1554
- rows.flatMap((k2) => k2).forEach((k2) => {
1555
- if (k2.keyLabel?.text !== void 0) {
1556
- k2.keyLabel.text = k2.labelText ?? "";
1557
- }
1558
- });
1559
- }
1560
- }
1561
- let keyAsString = "";
1562
- if (key.isShift || key.code === " " || key.code === "Backspace") {
1563
- keyShape.fillColor = this.specialKeyDownColor;
1564
- keyAsString = key.code;
1565
- } else {
1566
- keyShape.fillColor = this.keyDownColor;
1567
- if (this.showKeyDownPreview) {
1568
- letterCircle.hidden = false;
1569
- letterCircle.position.x = keyBox.position.x;
1570
- letterCircle.position.y = keyBox.position.y - keyHeight * 1.2;
1571
- if (this.shiftActivated) {
1572
- letterCircleLabel.text = key.labelTextShifted ?? key.code;
1573
- } else {
1574
- letterCircleLabel.text = key.labelText ?? key.code;
1575
- }
1576
- }
1577
- if (this.shiftActivated) {
1578
- keyAsString = key.labelTextShifted ?? key.code;
1579
- } else {
1580
- keyAsString = key.labelText ?? key.code;
1581
- }
1582
- }
1583
- if (this.eventListeners.length > 0) {
1584
- this.eventListeners.filter(
1585
- (listener) => listener.type === M2EventType.CompositeCustom && listener.compositeType === "VirtualKeyboardKeyDown"
1586
- ).forEach((listener) => {
1587
- const virtualKeyboardEvent = {
1588
- type: M2EventType.CompositeCustom,
1589
- target: this,
1590
- handled: false,
1591
- key: keyAsString,
1592
- code: key.code,
1593
- shiftKey: this.shiftActivated,
1594
- keyTapMetadata: {
1595
- size: keyShape.size,
1596
- point: tapEvent.point,
1597
- buttons: tapEvent.buttons
1598
- }
1599
- };
1600
- listener.callback(virtualKeyboardEvent);
1601
- });
1602
- }
1603
- });
1604
- keyShape.onTapLeave(() => {
1605
- keyShape.fillColor = this.keyColor;
1606
- letterCircle.hidden = true;
1607
- });
1608
1637
  const keyLabel = new Label({
1609
1638
  text: key.labelText,
1610
1639
  fontSize: this.fontSize,
1611
- fontNames: this.fontNames
1640
+ fontNames: this.fontNames,
1641
+ suppressEvents: true
1612
1642
  });
1643
+ keyLabel.userData = { code: key.code };
1613
1644
  keyBox.addChild(keyLabel);
1614
- key.keyLabel = keyLabel;
1615
- if (key.isShift) {
1616
- this.shiftKeyShape = keyShape;
1617
- }
1645
+ this.keyLabels.push(keyLabel);
1618
1646
  if (key.keyIcon) {
1619
1647
  keyBox.addChild(key.keyIcon);
1620
1648
  }
1621
1649
  keyboardRectangle.addChild(keyBox);
1650
+ keyShape.onTapUp((tapEvent) => {
1651
+ this.handleKeyShapeTapUp(key, keyShape, tapEvent);
1652
+ });
1653
+ keyShape.onTapDown((tapEvent) => {
1654
+ this.handleKeyShapeTapDown(key, keyShape, tapEvent);
1655
+ });
1656
+ keyShape.onTapLeave((tapEvent) => {
1657
+ this.handleKeyShapeTapLeave(key, keyShape, tapEvent);
1658
+ });
1622
1659
  }
1623
1660
  }
1624
- const letterCircle = new Shape({
1661
+ this.letterCircle = new Shape({
1625
1662
  circleOfRadius: 28,
1626
1663
  fillColor: WebColors.Silver,
1627
- hidden: true
1664
+ hidden: true,
1665
+ suppressEvents: true
1628
1666
  });
1629
- keyboardRectangle.addChild(letterCircle);
1630
- const letterCircleLabel = new Label({
1667
+ keyboardRectangle.addChild(this.letterCircle);
1668
+ this.letterCircleLabel = new Label({
1631
1669
  text: "",
1632
1670
  fontSize: this.fontSize,
1633
- fontNames: this.fontNames
1671
+ fontNames: this.fontNames,
1672
+ suppressEvents: true
1634
1673
  });
1635
- letterCircle.addChild(letterCircleLabel);
1674
+ this.letterCircle.addChild(this.letterCircleLabel);
1636
1675
  this.needsInitialization = false;
1637
1676
  }
1638
1677
  /**
@@ -1643,8 +1682,9 @@ class VirtualKeyboard extends Composite {
1643
1682
  */
1644
1683
  onKeyDown(callback, options) {
1645
1684
  const eventListener = {
1646
- type: M2EventType.CompositeCustom,
1647
- compositeType: "VirtualKeyboardKeyDown",
1685
+ type: M2EventType.Composite,
1686
+ compositeEventType: "VirtualKeyboardKeyDown",
1687
+ compositeType: this.compositeType,
1648
1688
  nodeUuid: this.uuid,
1649
1689
  callback
1650
1690
  };
@@ -1658,15 +1698,435 @@ class VirtualKeyboard extends Composite {
1658
1698
  */
1659
1699
  onKeyUp(callback, options) {
1660
1700
  const eventListener = {
1661
- type: M2EventType.CompositeCustom,
1662
- compositeType: "VirtualKeyboardKeyUp",
1701
+ type: M2EventType.Composite,
1702
+ compositeEventType: "VirtualKeyboardKeyUp",
1703
+ compositeType: this.compositeType,
1663
1704
  nodeUuid: this.uuid,
1664
1705
  callback
1665
1706
  };
1666
1707
  this.addVirtualKeyboardEventListener(eventListener, options);
1667
1708
  }
1668
- addVirtualKeyboardEventListener(eventListener, options) {
1669
- if (options?.replaceExisting) {
1709
+ /**
1710
+ * Executes a callback when the user has pressed a key with the pointer, but
1711
+ * moves the pointer outside the key bounds before releasing the pointer.
1712
+ *
1713
+ * @remarks Typically, this event will not be used, since it is a user's
1714
+ * inaccurate interaction with the keyboard. However, it can be used to
1715
+ * provide feedback to the user that they have moved the pointer outside the
1716
+ * key bounds, and thus the key stroke will not be registered.
1717
+ *
1718
+ * @param callback - function to execute
1719
+ * @param options
1720
+ */
1721
+ onKeyLeave(callback, options) {
1722
+ const eventListener = {
1723
+ type: M2EventType.Composite,
1724
+ compositeEventType: "VirtualKeyboardKeyLeave",
1725
+ compositeType: this.compositeType,
1726
+ nodeUuid: this.uuid,
1727
+ callback
1728
+ };
1729
+ this.addVirtualKeyboardEventListener(eventListener, options);
1730
+ }
1731
+ update() {
1732
+ super.update();
1733
+ }
1734
+ draw(canvas) {
1735
+ super.drawChildren(canvas);
1736
+ }
1737
+ warmup(canvas) {
1738
+ this.initialize();
1739
+ this.children.filter((child) => child.isDrawable).forEach((child) => {
1740
+ child.warmup(canvas);
1741
+ });
1742
+ }
1743
+ duplicate(newName) {
1744
+ throw new Error(`Method not implemented. ${newName}`);
1745
+ }
1746
+ handleKeyShapeTapDown(key, keyShape, tapEvent) {
1747
+ if (key.isShift) {
1748
+ this.shiftActivated = !this.shiftActivated;
1749
+ }
1750
+ const keyAsString = this.getKeyAsString(key);
1751
+ const virtualKeyboardEvent = {
1752
+ type: M2EventType.Composite,
1753
+ compositeType: "VirtualKeyboard",
1754
+ compositeEventType: "VirtualKeyboardKeyDown",
1755
+ target: this,
1756
+ handled: false,
1757
+ key: keyAsString,
1758
+ code: key.code,
1759
+ shiftKey: this.shiftActivated,
1760
+ keyTapMetadata: {
1761
+ size: keyShape.size,
1762
+ point: tapEvent.point,
1763
+ buttons: tapEvent.buttons
1764
+ },
1765
+ ...M2c2KitHelpers.createTimestamps()
1766
+ };
1767
+ this.handleCompositeEvent(virtualKeyboardEvent);
1768
+ this.saveEvent(virtualKeyboardEvent);
1769
+ if (this.eventListeners.length > 0) {
1770
+ this.eventListeners.filter(
1771
+ (listener) => listener.type === M2EventType.Composite && listener.compositeType === "VirtualKeyboard" && listener.compositeEventType === "VirtualKeyboardKeyDown"
1772
+ ).forEach((listener) => {
1773
+ listener.callback(virtualKeyboardEvent);
1774
+ });
1775
+ }
1776
+ }
1777
+ handleKeyShapeTapUp(key, keyShape, tapEvent) {
1778
+ const keyAsString = this.getKeyAsString(key);
1779
+ const virtualKeyboardEvent = {
1780
+ type: M2EventType.Composite,
1781
+ compositeType: "VirtualKeyboard",
1782
+ compositeEventType: "VirtualKeyboardKeyUp",
1783
+ target: this,
1784
+ handled: false,
1785
+ key: keyAsString,
1786
+ code: key.code,
1787
+ shiftKey: this.shiftActivated,
1788
+ keyTapMetadata: {
1789
+ size: keyShape.size,
1790
+ point: tapEvent.point,
1791
+ buttons: tapEvent.buttons
1792
+ },
1793
+ ...M2c2KitHelpers.createTimestamps()
1794
+ };
1795
+ this.handleCompositeEvent(virtualKeyboardEvent);
1796
+ this.saveEvent(virtualKeyboardEvent);
1797
+ if (this.eventListeners.length > 0) {
1798
+ this.eventListeners.filter(
1799
+ (listener) => listener.type === M2EventType.Composite && listener.compositeType === "VirtualKeyboard" && listener.compositeEventType === "VirtualKeyboardKeyUp"
1800
+ ).forEach((listener) => {
1801
+ listener.callback(virtualKeyboardEvent);
1802
+ });
1803
+ }
1804
+ }
1805
+ handleKeyShapeTapLeave(key, keyShape, tapEvent) {
1806
+ const keyAsString = this.getKeyAsString(key);
1807
+ const virtualKeyboardEvent = {
1808
+ type: M2EventType.Composite,
1809
+ compositeType: "VirtualKeyboard",
1810
+ compositeEventType: "VirtualKeyboardKeyLeave",
1811
+ target: this,
1812
+ handled: false,
1813
+ key: keyAsString,
1814
+ code: key.code,
1815
+ shiftKey: this.shiftActivated,
1816
+ keyTapMetadata: {
1817
+ size: keyShape.size,
1818
+ point: tapEvent.point,
1819
+ buttons: tapEvent.buttons
1820
+ },
1821
+ ...M2c2KitHelpers.createTimestamps()
1822
+ };
1823
+ this.handleCompositeEvent(virtualKeyboardEvent);
1824
+ this.saveEvent(virtualKeyboardEvent);
1825
+ if (this.eventListeners.length > 0) {
1826
+ this.eventListeners.filter(
1827
+ (listener) => listener.type === M2EventType.Composite && listener.compositeType === "VirtualKeyboard" && listener.compositeEventType === "VirtualKeyboardKeyLeave"
1828
+ ).forEach((listener) => {
1829
+ listener.callback(virtualKeyboardEvent);
1830
+ });
1831
+ }
1832
+ }
1833
+ getKeyAsString(key) {
1834
+ if (key.isShift || key.code === " " || key.code === "Backspace") {
1835
+ return key.code;
1836
+ } else {
1837
+ if (this.shiftActivated) {
1838
+ return key.labelTextShifted ?? key.code;
1839
+ } else {
1840
+ return key.labelText ?? key.code;
1841
+ }
1842
+ }
1843
+ }
1844
+ /**
1845
+ * Converts the keyboard rows to the internal keyboard configuration.
1846
+ *
1847
+ * @remarks This normalizes the keyboard rows so that each key is a
1848
+ * full `KeyConfigurationWithShape` object, instead of a string or array of
1849
+ * strings.
1850
+ *
1851
+ * @param keyboardRows
1852
+ * @returns the keyboard for internal use
1853
+ */
1854
+ internalKeyboardRowsToInternalKeyboardConfiguration(keyboardRows) {
1855
+ return keyboardRows.map((row) => {
1856
+ return row.map((key) => {
1857
+ let widthRatio = 1;
1858
+ const heightRatio = 1;
1859
+ let code;
1860
+ let label;
1861
+ let labelShifted;
1862
+ let keyIcon;
1863
+ let isShift = false;
1864
+ if (typeof key === "string") {
1865
+ code = key;
1866
+ if (this.capitalLettersOnly) {
1867
+ label = code.toUpperCase();
1868
+ } else {
1869
+ label = code;
1870
+ }
1871
+ labelShifted = code.toUpperCase();
1872
+ } else if (Array.isArray(key)) {
1873
+ code = key[0];
1874
+ label = code;
1875
+ labelShifted = key[1];
1876
+ } else {
1877
+ code = key.code;
1878
+ label = key.labelText ?? "";
1879
+ labelShifted = key.labelTextShifted ?? label;
1880
+ widthRatio = key.widthRatio ?? 1;
1881
+ keyIcon = key.keyIcon;
1882
+ isShift = key.isShift ?? false;
1883
+ }
1884
+ return {
1885
+ widthRatio,
1886
+ heightRatio,
1887
+ code,
1888
+ labelText: label,
1889
+ labelTextShifted: labelShifted,
1890
+ keyIcon,
1891
+ isShift
1892
+ };
1893
+ });
1894
+ });
1895
+ }
1896
+ handleCompositeEvent(event) {
1897
+ const keyboard = this.internalKeyboardRowsToInternalKeyboardConfiguration(
1898
+ this.keyboardRows
1899
+ );
1900
+ const keyShape = this.keyShapes.find((k) => k.userData.code === event.code);
1901
+ if (!keyShape) {
1902
+ throw new Error("keyShape is not defined");
1903
+ }
1904
+ this.shiftActivated = event.shiftKey;
1905
+ switch (event.compositeEventType) {
1906
+ case "VirtualKeyboardKeyDown": {
1907
+ this.handleKeyDownEvent(event, keyboard, keyShape);
1908
+ break;
1909
+ }
1910
+ case "VirtualKeyboardKeyUp": {
1911
+ this.handleKeyUpEvent(event, keyboard, keyShape);
1912
+ break;
1913
+ }
1914
+ case "VirtualKeyboardKeyLeave": {
1915
+ this.handleKeyLeaveEvent(event, keyboard, keyShape);
1916
+ break;
1917
+ }
1918
+ default: {
1919
+ throw new Error(
1920
+ `Unknown VirtualKeyboardEvent: ${event.compositeEventType}`
1921
+ );
1922
+ }
1923
+ }
1924
+ }
1925
+ handleKeyDownEvent(event, keyboard, keyShape) {
1926
+ if (event.code.toLowerCase().includes("shift")) {
1927
+ if (event.shiftKey) {
1928
+ this.showKeyboardShifted(keyboard);
1929
+ } else {
1930
+ this.showKeyboardNotShifted(keyboard);
1931
+ }
1932
+ } else if (event.code === " " || event.code === "Backspace") {
1933
+ keyShape.fillColor = this.specialKeyDownColor;
1934
+ } else {
1935
+ keyShape.fillColor = this.keyDownColor;
1936
+ if (this.showKeyDownPreview) {
1937
+ if (!this.letterCircle || !this.letterCircleLabel) {
1938
+ throw new Error("letterCircle is not defined");
1939
+ }
1940
+ this.letterCircle.hidden = false;
1941
+ const keyBox = keyShape.parent;
1942
+ this.letterCircle.position.x = keyBox.position.x;
1943
+ if (keyShape.rect?.size?.height === void 0) {
1944
+ throw new Error("keyShape.rect.height is undefined");
1945
+ }
1946
+ this.letterCircle.position.y = keyBox.position.y - keyShape.rect.size.height * 1.2;
1947
+ const keyboard2 = this.internalKeyboardRowsToInternalKeyboardConfiguration(
1948
+ this.keyboardRows
1949
+ );
1950
+ const key = keyboard2.flat().find((k) => k.code === event.code);
1951
+ if (!key) {
1952
+ throw new Error("key is not defined");
1953
+ }
1954
+ if (this.shiftActivated) {
1955
+ this.letterCircleLabel.text = key.labelTextShifted ?? key.code;
1956
+ } else {
1957
+ this.letterCircleLabel.text = key.labelText ?? key.code;
1958
+ }
1959
+ }
1960
+ }
1961
+ }
1962
+ handleKeyUpEvent(event, keyboard, keyShape) {
1963
+ if (event.code.toLowerCase().includes("shift") && event.shiftKey) {
1964
+ return;
1965
+ }
1966
+ if (event.code.toLowerCase().includes("shift") && !event.shiftKey) {
1967
+ this.shiftActivated = false;
1968
+ this.showKeyboardNotShifted(keyboard);
1969
+ return;
1970
+ }
1971
+ keyShape.fillColor = this.keyColor;
1972
+ if (!this.letterCircle) {
1973
+ throw new Error("letterCircle is not defined");
1974
+ }
1975
+ this.letterCircle.hidden = true;
1976
+ if (!event.code.toLowerCase().includes("shift") && event.shiftKey) {
1977
+ this.shiftActivated = false;
1978
+ this.showKeyboardNotShifted(keyboard);
1979
+ }
1980
+ }
1981
+ handleKeyLeaveEvent(event, keyboard, keyShape) {
1982
+ if (event.code.toLowerCase().includes("shift")) {
1983
+ if (event.shiftKey) {
1984
+ this.showKeyboardNotShifted(keyboard);
1985
+ this.shiftActivated = false;
1986
+ } else {
1987
+ this.showKeyboardShifted(keyboard);
1988
+ this.shiftActivated = true;
1989
+ }
1990
+ return;
1991
+ }
1992
+ keyShape.fillColor = this.keyColor;
1993
+ if (!this.letterCircle) {
1994
+ throw new Error("letterCircle is not defined");
1995
+ }
1996
+ this.letterCircle.hidden = true;
1997
+ }
1998
+ showKeyboardShifted(keyboard) {
1999
+ const shiftKeyShapes = this.keyShapes.filter(
2000
+ (shape) => shape.userData.code.toLowerCase().includes("shift")
2001
+ );
2002
+ shiftKeyShapes.forEach((shape) => {
2003
+ shape.fillColor = this.specialKeyDownColor;
2004
+ });
2005
+ const shiftKeys = keyboard.flat().filter((k) => k.isShift);
2006
+ shiftKeys.forEach((k) => {
2007
+ if (k.keyIcon) {
2008
+ k.keyIcon.fillColor = WebColors.Black;
2009
+ }
2010
+ });
2011
+ keyboard.flatMap((k) => k).forEach((k) => {
2012
+ const keyLabel = this.keyLabels.find((l) => l.userData.code === k.code);
2013
+ if (!keyLabel) {
2014
+ throw new Error("keyLabel is not defined");
2015
+ }
2016
+ if (keyLabel.text !== void 0) {
2017
+ keyLabel.text = k.labelTextShifted ?? "";
2018
+ }
2019
+ });
2020
+ }
2021
+ showKeyboardNotShifted(keyboard) {
2022
+ const shiftKeyShapes = this.keyShapes.filter(
2023
+ (shape) => shape.userData.code.toLowerCase().includes("shift")
2024
+ );
2025
+ shiftKeyShapes.forEach((shape) => {
2026
+ shape.fillColor = this.keyColor;
2027
+ });
2028
+ const shiftKeys = keyboard.flat().filter((k) => k.isShift);
2029
+ shiftKeys.forEach((k) => {
2030
+ if (k.keyIcon) {
2031
+ k.keyIcon.fillColor = WebColors.Transparent;
2032
+ }
2033
+ });
2034
+ keyboard.flatMap((k) => k).forEach((k) => {
2035
+ const keyLabel = this.keyLabels.find((l) => l.userData.code === k.code);
2036
+ if (!keyLabel) {
2037
+ throw new Error("keyLabel is not defined");
2038
+ }
2039
+ if (keyLabel.text !== void 0) {
2040
+ keyLabel.text = k.labelText ?? "";
2041
+ }
2042
+ });
2043
+ }
2044
+ createDefaultKeyboardRows() {
2045
+ const numKeys = [
2046
+ ["1", "!"],
2047
+ ["2", "@"],
2048
+ ["3", "#"],
2049
+ ["4", "$"],
2050
+ ["5", "%"],
2051
+ ["6", "^"],
2052
+ ["7", "&"],
2053
+ ["8", "*"],
2054
+ ["9", "("],
2055
+ ["0", ")"]
2056
+ ];
2057
+ const row1 = [
2058
+ "q",
2059
+ "w",
2060
+ "e",
2061
+ "r",
2062
+ "t",
2063
+ "y",
2064
+ "u",
2065
+ "i",
2066
+ "o",
2067
+ "p"
2068
+ ];
2069
+ const row2 = [
2070
+ "a",
2071
+ "s",
2072
+ "d",
2073
+ "f",
2074
+ "g",
2075
+ "h",
2076
+ "j",
2077
+ "k",
2078
+ "l"
2079
+ ];
2080
+ const shiftArrowShapeOptions = {
2081
+ path: {
2082
+ // Public Domain from https://www.freesvg.org
2083
+ pathString: "m288-6.6849e-14 -288 288h144v288h288v-288h144l-288-288z",
2084
+ width: 24
2085
+ },
2086
+ lineWidth: 2,
2087
+ strokeColor: WebColors.Black,
2088
+ fillColor: WebColors.Transparent,
2089
+ suppressEvents: true
2090
+ };
2091
+ const backspaceShapeOptions = {
2092
+ path: {
2093
+ // CC0 from https://www.svgrepo.com
2094
+ pathString: "M10.625 5.09 0 22.09l10.625 17H44.18v-34H10.625zm31.555 32H11.734l-9.375-15 9.375-15H42.18v30zm-23.293-6.293 7.293-7.293 7.293 7.293 1.414-1.414-7.293-7.293 7.293-7.293-1.414-1.414-7.293 7.293-7.293-7.293-1.414 1.414 7.293 7.293-7.293 7.293",
2095
+ width: 24
2096
+ },
2097
+ lineWidth: 1,
2098
+ strokeColor: WebColors.Black,
2099
+ fillColor: WebColors.Red,
2100
+ suppressEvents: true
2101
+ };
2102
+ const row3 = [
2103
+ {
2104
+ code: "Shift",
2105
+ isShift: true,
2106
+ widthRatio: 1.5,
2107
+ keyIcon: new Shape(shiftArrowShapeOptions)
2108
+ },
2109
+ "z",
2110
+ "x",
2111
+ "c",
2112
+ "v",
2113
+ "b",
2114
+ "n",
2115
+ "m",
2116
+ {
2117
+ code: "Backspace",
2118
+ widthRatio: 1.5,
2119
+ keyIcon: new Shape(backspaceShapeOptions)
2120
+ }
2121
+ ];
2122
+ const row4 = [
2123
+ { code: " ", labelText: "SPACE", widthRatio: 5 }
2124
+ ];
2125
+ const keyboardRows = [numKeys, row1, row2, row3, row4];
2126
+ return keyboardRows;
2127
+ }
2128
+ addVirtualKeyboardEventListener(eventListener, options) {
2129
+ if (options?.replaceExisting) {
1670
2130
  this.eventListeners = this.eventListeners.filter(
1671
2131
  (listener) => !(listener.nodeUuid === eventListener.nodeUuid && listener.type === eventListener.type && listener.compositeType === eventListener.compositeType)
1672
2132
  );
@@ -1684,26 +2144,14 @@ class VirtualKeyboard extends Composite {
1684
2144
  */
1685
2145
  set isUserInteractionEnabled(isUserInteractionEnabled) {
1686
2146
  this._isUserInteractionEnabled = isUserInteractionEnabled;
1687
- this.keyShapes.forEach((keyShape) => {
2147
+ this.keyShapes?.forEach((keyShape) => {
1688
2148
  keyShape.isUserInteractionEnabled = isUserInteractionEnabled;
1689
2149
  });
1690
2150
  }
1691
- update() {
1692
- super.update();
1693
- }
1694
- draw(canvas) {
1695
- super.drawChildren(canvas);
1696
- }
1697
- warmup(canvas) {
1698
- this.initialize();
1699
- this.children.filter((child) => child.isDrawable).forEach((child) => {
1700
- child.warmup(canvas);
1701
- });
1702
- }
1703
- duplicate(newName) {
1704
- throw new Error(`Method not implemented. ${newName}`);
1705
- }
1706
2151
  }
2152
+ M2c2KitHelpers.registerM2NodeClass(
2153
+ VirtualKeyboard
2154
+ );
1707
2155
 
1708
2156
  const SCENE_TRANSITION_EASING$1 = Easings.sinusoidalInOut;
1709
2157
  const SCENE_TRANSITION_DURATION = 500;
@@ -1948,7 +2396,7 @@ class CountdownScene extends Scene {
1948
2396
  bottomToBottomOf: this,
1949
2397
  startToStartOf: this,
1950
2398
  endToEndOf: this,
1951
- verticalBias: options?.timerShape?.verticalBias ?? 0.5
2399
+ verticalBias: options?.shapeVerticalBias ?? 0.5
1952
2400
  }
1953
2401
  },
1954
2402
  fillColor: options?.timerShape?.fillColor ?? WebColors.RoyalBlue
@@ -1967,7 +2415,7 @@ class CountdownScene extends Scene {
1967
2415
  bottomToBottomOf: this,
1968
2416
  startToStartOf: this,
1969
2417
  endToEndOf: this,
1970
- verticalBias: options?.timerShape?.verticalBias ?? 0.5
2418
+ verticalBias: options.shapeVerticalBias ?? 0.5
1971
2419
  }
1972
2420
  },
1973
2421
  fillColor: options?.timerShape?.fillColor ?? WebColors.RoyalBlue
@@ -2015,7 +2463,7 @@ class CountdownScene extends Scene {
2015
2463
  countdownSequence.push(
2016
2464
  Action.custom({
2017
2465
  callback: () => {
2018
- timerNumberLabel.text = options?.timerZeroString ?? "0";
2466
+ timerNumberLabel.text = options?.zeroString ?? "0";
2019
2467
  }
2020
2468
  })
2021
2469
  );
@@ -2079,7 +2527,6 @@ class LocalePicker extends Composite {
2079
2527
  constructor(options) {
2080
2528
  super(options);
2081
2529
  this.compositeType = "LocalePicker";
2082
- this.zPosition = Number.MAX_VALUE;
2083
2530
  this.DEFAULT_FONT_SIZE = 24;
2084
2531
  this.automaticallyChangeLocale = true;
2085
2532
  this._localeOptions = new Array();
@@ -2107,6 +2554,7 @@ class LocalePicker extends Composite {
2107
2554
  */
2108
2555
  this.LEFT_SELECTION_INDICATOR = "\xAB";
2109
2556
  this.RIGHT_SELECTION_INDICATOR = "\xBB";
2557
+ this.zPosition = Number.MAX_VALUE;
2110
2558
  if (!options) {
2111
2559
  return;
2112
2560
  }
@@ -2146,7 +2594,7 @@ class LocalePicker extends Composite {
2146
2594
  */
2147
2595
  onResult(callback, options) {
2148
2596
  const eventListener = {
2149
- type: M2EventType.CompositeCustom,
2597
+ type: M2EventType.Composite,
2150
2598
  compositeType: "LocalePickerResult",
2151
2599
  nodeUuid: this.uuid,
2152
2600
  callback
@@ -2200,10 +2648,10 @@ class LocalePicker extends Composite {
2200
2648
  }
2201
2649
  const overlay = new Shape({
2202
2650
  rect: {
2203
- width: Globals.canvasCssWidth,
2204
- height: Globals.canvasCssHeight,
2205
- x: Globals.canvasCssWidth / 2,
2206
- y: Globals.canvasCssHeight / 2
2651
+ width: m2c2Globals.canvasCssWidth,
2652
+ height: m2c2Globals.canvasCssHeight,
2653
+ x: m2c2Globals.canvasCssWidth / 2,
2654
+ y: m2c2Globals.canvasCssHeight / 2
2207
2655
  },
2208
2656
  fillColor: [0, 0, 0, this.overlayAlpha],
2209
2657
  zPosition: -1,
@@ -2215,12 +2663,16 @@ class LocalePicker extends Composite {
2215
2663
  if (this.eventListeners.length > 0) {
2216
2664
  this.eventListeners.filter((listener) => listener.type === "LocalePickerResult").forEach((listener) => {
2217
2665
  const languagePickerEvent = {
2218
- type: "LocalePickerResult",
2666
+ type: M2EventType.Composite,
2667
+ compositeType: this.compositeType,
2668
+ compositeEventType: "LocalePickerResult",
2219
2669
  target: this,
2220
2670
  handled: false,
2221
2671
  result: {
2222
2672
  locale: void 0
2223
- }
2673
+ },
2674
+ timestamp: Timer.now(),
2675
+ iso8601Timestamp: (/* @__PURE__ */ new Date()).toISOString()
2224
2676
  };
2225
2677
  listener.callback(languagePickerEvent);
2226
2678
  });
@@ -2230,10 +2682,10 @@ class LocalePicker extends Composite {
2230
2682
  this.addChild(overlay);
2231
2683
  const lineHeight = this.fontSize / this.DEFAULT_FONT_SIZE * 50;
2232
2684
  const dialogHeight = this.localeOptions.length * lineHeight;
2233
- const dialogWidth = Globals.canvasCssWidth / 2;
2685
+ const dialogWidth = m2c2Globals.canvasCssWidth / 2;
2234
2686
  const sceneCenter = {
2235
- x: Globals.canvasCssWidth / 2,
2236
- y: Globals.canvasCssHeight / 2
2687
+ x: m2c2Globals.canvasCssWidth / 2,
2688
+ y: m2c2Globals.canvasCssHeight / 2
2237
2689
  };
2238
2690
  const localeDialog = new Shape({
2239
2691
  rect: {
@@ -2335,16 +2787,19 @@ class LocalePicker extends Composite {
2335
2787
  e.handled = true;
2336
2788
  if (this.eventListeners.length > 0) {
2337
2789
  this.eventListeners.filter(
2338
- (listener) => listener.type === M2EventType.CompositeCustom && listener.compositeType === "LocalePickerResult" && listener.nodeUuid == this.uuid
2790
+ (listener) => listener.type === M2EventType.Composite && listener.compositeType === "LocalePickerResult" && listener.nodeUuid == this.uuid
2339
2791
  ).forEach((listener) => {
2340
2792
  const languagePickerEvent = {
2341
- type: M2EventType.CompositeCustom,
2342
- compositeType: "LocalePickerResult",
2793
+ type: M2EventType.Composite,
2794
+ compositeType: this.compositeType,
2795
+ compositeEventType: "LocalePickerResult",
2343
2796
  target: this,
2344
2797
  handled: false,
2345
2798
  result: {
2346
2799
  locale: localeOption.locale
2347
- }
2800
+ },
2801
+ timestamp: Timer.now(),
2802
+ iso8601Timestamp: (/* @__PURE__ */ new Date()).toISOString()
2348
2803
  };
2349
2804
  listener.callback(languagePickerEvent);
2350
2805
  });
@@ -2508,7 +2963,394 @@ class LocalePicker extends Composite {
2508
2963
  }
2509
2964
  }
2510
2965
 
2511
- console.log("\u26AA @m2c2kit/addons version 0.3.15 (b3a70752)");
2966
+ class CountdownTimer extends Composite {
2967
+ /**
2968
+ * A countdown timer displays a number that counts down to zero.
2969
+ *
2970
+ * @param options
2971
+ */
2972
+ constructor(options) {
2973
+ super(options);
2974
+ this.compositeType = "CountdownTimer";
2975
+ this._milliseconds = 3e3;
2976
+ this._tickIntervalMilliseconds = 1e3;
2977
+ this._fontSize = 50;
2978
+ this._fontColor = WebColors.White;
2979
+ this._zeroString = "0";
2980
+ this._timerShape = {
2981
+ circle: {
2982
+ radius: 100
2983
+ },
2984
+ fillColor: WebColors.RoyalBlue
2985
+ };
2986
+ this._textVerticalBias = 0.5;
2987
+ this.countdownSequence = new Array();
2988
+ this._isRunning = false;
2989
+ this.hasStopped = false;
2990
+ this.originalOptions = JSON.parse(JSON.stringify(options));
2991
+ if (options.milliseconds) {
2992
+ this.milliseconds = options.milliseconds;
2993
+ }
2994
+ if (options.tickIntervalMilliseconds) {
2995
+ this.tickIntervalMilliseconds = options.tickIntervalMilliseconds;
2996
+ }
2997
+ if (options.fontName) {
2998
+ this.fontName = options.fontName;
2999
+ }
3000
+ if (options.fontSize !== void 0) {
3001
+ this.fontSize = options.fontSize;
3002
+ }
3003
+ if (options.fontColor) {
3004
+ this.fontColor = options.fontColor;
3005
+ }
3006
+ if (options.zeroString !== void 0) {
3007
+ this.zeroString = options.zeroString;
3008
+ }
3009
+ if (options.timerShape) {
3010
+ this.timerShape = options.timerShape;
3011
+ }
3012
+ if (options.textVerticalBias !== void 0) {
3013
+ this.textVerticalBias = options.textVerticalBias;
3014
+ }
3015
+ this.saveNodeNewEvent();
3016
+ }
3017
+ get completeNodeOptions() {
3018
+ return {
3019
+ ...this.options,
3020
+ ...this.getNodeOptions(),
3021
+ ...this.getDrawableOptions(),
3022
+ ...this.originalOptions
3023
+ };
3024
+ }
3025
+ initialize() {
3026
+ this.removeAllChildren();
3027
+ if (this.timerShape?.circle === void 0 && this.timerShape?.rectangle === void 0 || this.timerShape?.circle !== void 0) {
3028
+ this.timerShapeNode = new Shape({
3029
+ circleOfRadius: this.timerShape.circle?.radius ?? 100,
3030
+ fillColor: this.timerShape?.fillColor ?? WebColors.RoyalBlue
3031
+ });
3032
+ this.addChild(this.timerShapeNode);
3033
+ } else if (this.timerShape?.rectangle !== void 0) {
3034
+ this.timerShapeNode = new Shape({
3035
+ rect: {
3036
+ width: this.timerShape?.rectangle?.width ?? 200,
3037
+ height: this.timerShape?.rectangle?.height ?? 200
3038
+ },
3039
+ cornerRadius: this.timerShape?.rectangle?.cornerRadius,
3040
+ fillColor: this.timerShape?.fillColor ?? WebColors.RoyalBlue
3041
+ });
3042
+ this.addChild(this.timerShapeNode);
3043
+ } else {
3044
+ throw new Error("Invalid timer shape options.");
3045
+ }
3046
+ this.size = this.timerShapeNode.size;
3047
+ if (this.milliseconds % 1e3 !== 0) {
3048
+ throw new Error(
3049
+ "CountdownTimer milliseconds must be a multiple of 1000."
3050
+ );
3051
+ }
3052
+ const timerInitialNumber = Math.floor(this.milliseconds / 1e3);
3053
+ this.timerNumberLabel = new Label({
3054
+ text: timerInitialNumber.toString(),
3055
+ fontSize: this.fontSize,
3056
+ fontName: this._fontName,
3057
+ fontColor: this.fontColor,
3058
+ layout: {
3059
+ constraints: {
3060
+ topToTopOf: this.timerShapeNode,
3061
+ bottomToBottomOf: this.timerShapeNode,
3062
+ startToStartOf: this.timerShapeNode,
3063
+ endToEndOf: this.timerShapeNode,
3064
+ verticalBias: this.textVerticalBias
3065
+ }
3066
+ }
3067
+ });
3068
+ this.timerShapeNode.addChild(this.timerNumberLabel);
3069
+ this.countdownSequence = new Array();
3070
+ for (let i = this.milliseconds; i > this.tickIntervalMilliseconds; i = i - this.tickIntervalMilliseconds) {
3071
+ this.countdownSequence.push(
3072
+ Action.wait({ duration: this.tickIntervalMilliseconds })
3073
+ );
3074
+ this.countdownSequence.push(
3075
+ Action.custom({
3076
+ callback: () => {
3077
+ this.tick(i - this.tickIntervalMilliseconds);
3078
+ }
3079
+ })
3080
+ );
3081
+ }
3082
+ this.countdownSequence.push(
3083
+ Action.wait({ duration: this.tickIntervalMilliseconds })
3084
+ );
3085
+ this.countdownSequence.push(
3086
+ Action.custom({
3087
+ callback: () => {
3088
+ this.tick(0);
3089
+ const countdownTimerEvent = {
3090
+ type: M2EventType.Composite,
3091
+ compositeType: this.compositeType,
3092
+ compositeEventType: "CountdownTimerComplete",
3093
+ target: this,
3094
+ handled: false,
3095
+ millisecondsRemaining: 0,
3096
+ ...M2c2KitHelpers.createTimestamps()
3097
+ };
3098
+ this.handleCompositeEvent(countdownTimerEvent);
3099
+ this.saveEvent(countdownTimerEvent);
3100
+ if (this.eventListeners.length > 0) {
3101
+ this.eventListeners.filter(
3102
+ (listener) => listener.type === M2EventType.Composite && listener.compositeType === "CountdownTimer" && listener.compositeEventType === "CountdownTimerComplete"
3103
+ ).forEach((listener) => {
3104
+ listener.callback(countdownTimerEvent);
3105
+ });
3106
+ }
3107
+ }
3108
+ })
3109
+ );
3110
+ this.needsInitialization = false;
3111
+ }
3112
+ tick(millisecondsRemaining) {
3113
+ const countdownTimerEvent = {
3114
+ type: M2EventType.Composite,
3115
+ compositeType: this.compositeType,
3116
+ compositeEventType: "CountdownTimerTick",
3117
+ target: this,
3118
+ handled: false,
3119
+ millisecondsRemaining,
3120
+ ...M2c2KitHelpers.createTimestamps()
3121
+ };
3122
+ this.handleCompositeEvent(countdownTimerEvent);
3123
+ this.saveEvent(countdownTimerEvent);
3124
+ if (this.eventListeners.length > 0) {
3125
+ this.eventListeners.filter(
3126
+ (listener) => listener.type === M2EventType.Composite && listener.compositeType === "CountdownTimer" && listener.compositeEventType === "CountdownTimerTick"
3127
+ ).forEach((listener) => {
3128
+ listener.callback(countdownTimerEvent);
3129
+ });
3130
+ }
3131
+ }
3132
+ /**
3133
+ * Starts the countdown timer.
3134
+ *
3135
+ * @remarks Calling start on a running timer or a stopped timer will raise
3136
+ * an error.
3137
+ */
3138
+ start() {
3139
+ if (this.isRunning) {
3140
+ throw new Error("CountdownTimer: cannot start. It is already running.");
3141
+ }
3142
+ if (this.hasStopped) {
3143
+ throw new Error(
3144
+ "CountdownTimer: It has stopped. You cannot start a stopped CountdownTimer. Instead, create a new CountdownTimer."
3145
+ );
3146
+ }
3147
+ this.run(
3148
+ Action.sequence(this.countdownSequence),
3149
+ "__countdownSequenceAction"
3150
+ );
3151
+ this._isRunning = true;
3152
+ }
3153
+ /**
3154
+ * Stops the countdown timer.
3155
+ *
3156
+ * @remarks This method is idempotent. Calling stop() on a stopped timer has
3157
+ * no effect and will not raise an error.
3158
+ */
3159
+ stop() {
3160
+ if (this.isRunning) {
3161
+ this.removeAction("__countdownSequenceAction");
3162
+ this._isRunning = false;
3163
+ this.hasStopped = true;
3164
+ }
3165
+ }
3166
+ /**
3167
+ * Returns true if the countdown timer is running.
3168
+ */
3169
+ get isRunning() {
3170
+ return this._isRunning;
3171
+ }
3172
+ handleCompositeEvent(event) {
3173
+ if (!this.timerNumberLabel) {
3174
+ throw new Error("Timer number label not found.");
3175
+ }
3176
+ switch (event.compositeEventType) {
3177
+ case "CountdownTimerTick": {
3178
+ this.timerNumberLabel.text = Math.ceil(
3179
+ event.millisecondsRemaining / 1e3
3180
+ ).toString();
3181
+ break;
3182
+ }
3183
+ case "CountdownTimerComplete": {
3184
+ this.timerNumberLabel.text = this.zeroString;
3185
+ break;
3186
+ }
3187
+ default:
3188
+ throw new Error(
3189
+ `Invalid TimerCountdown event type: ${event.compositeEventType}`
3190
+ );
3191
+ }
3192
+ }
3193
+ /**
3194
+ * Executes a callback when the timer ticks.
3195
+ *
3196
+ * @remarks The callback is also executed when the timer completes.
3197
+ *
3198
+ * @param callback - function to execute
3199
+ * @param options
3200
+ */
3201
+ onTick(callback, options) {
3202
+ const eventListener = {
3203
+ type: M2EventType.Composite,
3204
+ compositeEventType: "CountdownTimerTick",
3205
+ compositeType: this.compositeType,
3206
+ nodeUuid: this.uuid,
3207
+ callback
3208
+ };
3209
+ this.addCountdownTimerEventListener(eventListener, options);
3210
+ }
3211
+ /**
3212
+ * Executes a callback when the timer completes.
3213
+ *
3214
+ * @remarks This is the last tick of the timer.
3215
+ *
3216
+ * @param callback - function to execute.
3217
+ * @param options
3218
+ */
3219
+ onComplete(callback, options) {
3220
+ const eventListener = {
3221
+ type: M2EventType.Composite,
3222
+ compositeEventType: "CountdownTimerComplete",
3223
+ compositeType: this.compositeType,
3224
+ nodeUuid: this.uuid,
3225
+ callback
3226
+ };
3227
+ this.addCountdownTimerEventListener(eventListener, options);
3228
+ }
3229
+ addCountdownTimerEventListener(eventListener, options) {
3230
+ if (options?.replaceExisting) {
3231
+ this.eventListeners = this.eventListeners.filter(
3232
+ (listener) => !(listener.nodeUuid === eventListener.nodeUuid && listener.type === eventListener.type && listener.compositeType === eventListener.compositeType)
3233
+ );
3234
+ }
3235
+ this.eventListeners.push(eventListener);
3236
+ }
3237
+ get milliseconds() {
3238
+ return this._milliseconds;
3239
+ }
3240
+ set milliseconds(milliseconds) {
3241
+ if (Equal.value(this._milliseconds, milliseconds)) {
3242
+ return;
3243
+ }
3244
+ this._milliseconds = milliseconds;
3245
+ this.savePropertyChangeEvent("milliseconds", milliseconds);
3246
+ }
3247
+ get tickIntervalMilliseconds() {
3248
+ return this._tickIntervalMilliseconds;
3249
+ }
3250
+ set tickIntervalMilliseconds(tickIntervalMilliseconds) {
3251
+ if (Equal.value(this._tickIntervalMilliseconds, tickIntervalMilliseconds)) {
3252
+ return;
3253
+ }
3254
+ this._tickIntervalMilliseconds = tickIntervalMilliseconds;
3255
+ this.savePropertyChangeEvent(
3256
+ "tickIntervalMilliseconds",
3257
+ tickIntervalMilliseconds
3258
+ );
3259
+ }
3260
+ get fontColor() {
3261
+ return this._fontColor;
3262
+ }
3263
+ set fontColor(fontColor) {
3264
+ if (Equal.value(this._fontColor, fontColor)) {
3265
+ return;
3266
+ }
3267
+ this._fontColor = fontColor;
3268
+ this.needsInitialization = true;
3269
+ this.savePropertyChangeEvent("fontColor", fontColor);
3270
+ }
3271
+ get fontName() {
3272
+ return this._fontName;
3273
+ }
3274
+ set fontName(fontName) {
3275
+ if (this._fontName === fontName) {
3276
+ return;
3277
+ }
3278
+ this._fontName = fontName;
3279
+ this.needsInitialization = true;
3280
+ this.savePropertyChangeEvent("fontName", fontName);
3281
+ }
3282
+ get fontSize() {
3283
+ return this._fontSize;
3284
+ }
3285
+ set fontSize(fontSize) {
3286
+ if (Equal.value(this._fontSize, fontSize)) {
3287
+ return;
3288
+ }
3289
+ this._fontSize = fontSize;
3290
+ this.needsInitialization = true;
3291
+ this.savePropertyChangeEvent("fontSize", fontSize);
3292
+ }
3293
+ get zeroString() {
3294
+ return this._zeroString;
3295
+ }
3296
+ set zeroString(zeroString) {
3297
+ if (this._zeroString === zeroString) {
3298
+ return;
3299
+ }
3300
+ this._zeroString = zeroString;
3301
+ this.savePropertyChangeEvent("zeroString", zeroString);
3302
+ }
3303
+ get timerShape() {
3304
+ return this._timerShape;
3305
+ }
3306
+ set timerShape(shape) {
3307
+ if (Equal.value(this._timerShape, shape)) {
3308
+ return;
3309
+ }
3310
+ this._timerShape = shape;
3311
+ this.savePropertyChangeEvent("timerShape", shape);
3312
+ }
3313
+ get textVerticalBias() {
3314
+ return this._textVerticalBias;
3315
+ }
3316
+ set textVerticalBias(textVerticalBias) {
3317
+ if (Equal.value(this._textVerticalBias, textVerticalBias)) {
3318
+ return;
3319
+ }
3320
+ this._textVerticalBias = textVerticalBias;
3321
+ this.savePropertyChangeEvent("textVerticalBias", textVerticalBias);
3322
+ }
3323
+ /**
3324
+ * Duplicates a node using deep copy.
3325
+ *
3326
+ * @remarks This is a deep recursive clone (node and children).
3327
+ * The uuid property of all duplicated nodes will be newly created,
3328
+ * because uuid must be unique.
3329
+ *
3330
+ * @param newName - optional name of the new, duplicated node. If not
3331
+ * provided, name will be the new uuid
3332
+ */
3333
+ duplicate(newName) {
3334
+ throw new Error(`Method not implemented. ${newName}`);
3335
+ }
3336
+ update() {
3337
+ super.update();
3338
+ }
3339
+ draw(canvas) {
3340
+ super.drawChildren(canvas);
3341
+ }
3342
+ warmup(canvas) {
3343
+ this.initialize();
3344
+ this.children.filter((child) => child.isDrawable).forEach((child) => {
3345
+ child.warmup(canvas);
3346
+ });
3347
+ }
3348
+ }
3349
+ M2c2KitHelpers.registerM2NodeClass(
3350
+ CountdownTimer
3351
+ );
3352
+
3353
+ console.log("\u26AA @m2c2kit/addons version 0.3.17 (faa531ea)");
2512
3354
 
2513
- export { Button, CountdownScene, Dialog, DialogResult, DrawPad, DrawPadEventType, DrawPadItemEventType, Grid, Instructions, LocalePicker, VirtualKeyboard };
3355
+ export { Button, CountdownScene, CountdownTimer, Dialog, DialogResult, DrawPad, DrawPadEventType, DrawPadItemEventType, Grid, Instructions, LocalePicker, VirtualKeyboard };
2514
3356
  //# sourceMappingURL=index.js.map