zimjs 20.0.3 → 20.0.4

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/src/zim.js CHANGED
@@ -2747,41 +2747,99 @@ color1 - |ZIM VEE| (default null) the first color as an HTML string or hex color
2747
2747
  color2 - |ZIM VEE| (default black) the second color as an HTML string or hex color (case insensitive)
2748
2748
  ratio - (default .5) the ratio where 0 is the first color and 1 the second color
2749
2749
 
2750
- RETURNS a hex color string
2750
+ RETURNS a hex color string or rgba if alpha is not 1
2751
2751
  --*///+27.6
2752
- zim.colorRange = function(color1, color2, ratio) {
2752
+ zim.colorRange = function(c1, c2, ratio) {
2753
2753
  if (!zim.colorRangeCheck) {z_d("27.6"); zim.colorRangeCheck=true;}
2754
- // thanks Chris Dolphin - StackOverflow
2755
- // modified by Dan Zen to use hex input and output
2756
- // possibly converting and converting back - but not quite...
2757
- if (zot(ratio)) ratio = .5;
2754
+ if (zot(c1)) c1 = "white";
2755
+ if (zot(c2)) c2 = "black";
2756
+ c1 = zik(c1);
2757
+ c2 = zik(c2);
2758
+ if (ratio == null) ratio = 0.5;
2758
2759
  ratio = Math.max(0, Math.min(1, ratio));
2759
- if (zot(color1)) color1 = "white";
2760
- if (zot(color2)) color2 = "black";
2761
- color1 = zik(color1);
2762
- color2 = zik(color2);
2763
- var color1O = color1;
2764
- var color2O = color2;
2765
- var c1 = zim.convertColor(color1, "rgb");
2766
- var c2 = zim.convertColor(color2, "rgb");
2767
- color1 = c1.substring(4, c1.length - 1).split(',');
2768
- color2 = c2.substring(4, c2.length - 1).split(',');
2769
- var difference;
2770
- var newColor = "#";
2771
- var c;
2772
- if (ratio==0) {
2773
- newColor = color1O;
2774
- } else if (ratio==1) {
2775
- newColor = color2O;
2776
- } else {
2777
- for (var i=0; i<color1.length; i++) {
2778
- difference = color2[i] - color1[i];
2779
- c = Math.floor(parseInt(color1[i], 10) + difference * ratio).toString(16);
2780
- if (c.length < 2) c = "0"+c;
2781
- newColor += c;
2760
+
2761
+ function parseColor(c) {
2762
+ if (!c) return [0, 0, 0, 1];
2763
+ if (typeof c !== "string") c = String(c);
2764
+ c = c.trim();
2765
+
2766
+ // 1. Match rgba(r, g, b, a) or rgb(r, g, b)
2767
+ var rgbaMatch = c.match(/rgba?\s*\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)(?:\s*,\s*([\d.]+))?\s*\)/i);
2768
+ if (rgbaMatch) {
2769
+ return [
2770
+ parseFloat(rgbaMatch[1]),
2771
+ parseFloat(rgbaMatch[2]),
2772
+ parseFloat(rgbaMatch[3]),
2773
+ rgbaMatch[4] != null ? parseFloat(rgbaMatch[4]) : 1
2774
+ ];
2775
+ }
2776
+
2777
+ // 2. Special ZIM / CSS keywords with built-in alpha
2778
+ if (c === "clear" || c === "transparent") return [0, 0, 0, 0];
2779
+ if (c === "faint") return [0, 0, 0, 0.05];
2780
+
2781
+ // 3. Hex formats (#rgb, #rgba, #rrggbb, #rrggbbaa)
2782
+ if (c.charAt(0) === "#") {
2783
+ var hex = c.slice(1);
2784
+ if (hex.length === 3) {
2785
+ return [
2786
+ parseInt(hex[0] + hex[0], 16),
2787
+ parseInt(hex[1] + hex[1], 16),
2788
+ parseInt(hex[2] + hex[2], 16),
2789
+ 1
2790
+ ];
2791
+ } else if (hex.length === 4) {
2792
+ return [
2793
+ parseInt(hex[0] + hex[0], 16),
2794
+ parseInt(hex[1] + hex[1], 16),
2795
+ parseInt(hex[2] + hex[2], 16),
2796
+ parseInt(hex[3] + hex[3], 16) / 255
2797
+ ];
2798
+ } else if (hex.length === 6) {
2799
+ return [
2800
+ parseInt(hex.substr(0, 2), 16),
2801
+ parseInt(hex.substr(2, 2), 16),
2802
+ parseInt(hex.substr(4, 2), 16),
2803
+ 1
2804
+ ];
2805
+ } else if (hex.length === 8) {
2806
+ return [
2807
+ parseInt(hex.substr(0, 2), 16),
2808
+ parseInt(hex.substr(2, 2), 16),
2809
+ parseInt(hex.substr(4, 2), 16),
2810
+ parseInt(hex.substr(6, 2), 16) / 255
2811
+ ];
2812
+ }
2813
+ }
2814
+
2815
+ // 4. Fallback for named CSS colors via temporary canvas context or lookup
2816
+ // If zim.color / zim.toColor is available, resolve it first
2817
+ if (zim.toColor && c in zim) c = zim[c];
2818
+
2819
+ // Simple 3-byte fallback if hex number without hash
2820
+ var num = parseInt(c, 16);
2821
+ if (!isNaN(num) && c.length === 6) {
2822
+ return [(num >> 16) & 255, (num >> 8) & 255, num & 255, 1];
2782
2823
  }
2824
+
2825
+ return [0, 0, 0, 1];
2826
+ }
2827
+
2828
+ var col1 = parseColor(c1);
2829
+ var col2 = parseColor(c2);
2830
+
2831
+ var r = Math.round(col1[0] + (col2[0] - col1[0]) * ratio);
2832
+ var g = Math.round(col1[1] + (col2[1] - col1[1]) * ratio);
2833
+ var b = Math.round(col1[2] + (col2[2] - col1[2]) * ratio);
2834
+ var a = col1[3] + (col2[3] - col1[3]) * ratio;
2835
+ a = Math.round(a * 1000) / 1000;
2836
+
2837
+ if (a >= 0.999) {
2838
+ // Return standard 6-character hex for opaque colors
2839
+ return "#" + ((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1);
2783
2840
  }
2784
- return newColor;
2841
+ // Return rgba only when alpha is fractional
2842
+ return "rgba(" + r + "," + g + "," + b + "," + a + ")";
2785
2843
  };//-27.6
2786
2844
 
2787
2845
  /*--
@@ -18277,6 +18335,7 @@ lastSelected - access to the last selected (or created) control container
18277
18335
  lastindex - the index number of the last selected controls
18278
18336
  controlsVisible - get or set the visibility of the controls - or use showControls() and hideControls()
18279
18337
  toggled - read-only Boolean property as to whether picker is showing
18338
+ controlType - get or set control types for new points
18280
18339
  types - get or set the general array for the types ["mirror", "straight", "free", "none"]
18281
18340
  changing this or removing a type will adjust the order when the user double clicks the points to change their type
18282
18341
  this is not an array of types for each point - see the points property to access the types of each point
@@ -18291,11 +18350,13 @@ selectionManager - access to the SelectionManager
18291
18350
  selectedBalls - access to the SelectionSet of control circles
18292
18351
  selectedRect1s - access to the SelectionSet of control rectangles
18293
18352
  selectedRect2s - access to the SelectionSet of control rectangles
18353
+ editPoints - get or set whether points can be added and removed
18354
+ holdPoints - get or set whether points can be held to be removed (default is editPoints)
18294
18355
  interactive - get or set whether the shape is interactive - toggle, move, change or add controls, etc.
18295
18356
  keyFocus - get or set the keyboard focus on the DisplayObject - see also zim.KEYFOCUS
18296
- will be set to true if this DisplayObject is the first made or DisplayObject is the last to be used with keyboard
18357
+ will be set to true if this DisplayObject is the first made or DisplayObject is the last to be used with keyboard
18297
18358
  veeObj - an object with ZIM VEE original parameters:value allowing the ZIM VEE values to be referenced
18298
- for instance, obj.prop = Pick.choose(obj.veeObj.prop); will reset the the prop to the result of the original ZIM VEE value
18359
+ for instance, obj.prop = Pick.choose(obj.veeObj.prop); will reset the the prop to the result of the original ZIM VEE value
18299
18360
 
18300
18361
  ALSO: see ZIM Container for properties such as:
18301
18362
  width, height, widthOnly, heightOnly, draggable, level, depth, group
@@ -18318,7 +18379,7 @@ See the CreateJS Easel Docs for Container events such as:
18318
18379
  added, click, dblclick, mousedown, mouseout, mouseover, pressdown (ZIM), pressmove, pressup, removed, rollout, rollover
18319
18380
 
18320
18381
  MORE
18321
- https://zimjs.com/squiggle
18382
+ https://zimjs.com/zapp/E_4QEJ4
18322
18383
  https://www.youtube.com/watch?v=BA1bGBU4itI&list=PLCIzupgRt1pYtMlYPtNTKCtztFBeOtyc0
18323
18384
  Note the points property has been split into points and pointObjects (and there have been a few property changes) since the time of the video
18324
18385
  --*///+53.2
@@ -18391,7 +18452,7 @@ Note the points property has been split into points and pointObjects (and there
18391
18452
  this.num = num;
18392
18453
  this.onTop = onTop;
18393
18454
  this.move = move;
18394
- this.editPoints = editPoints;
18455
+ this.editPoints = this.holdPoints = editPoints;
18395
18456
  this.allowToggle = allowToggle;
18396
18457
  this.lockControlType = lockControlType;
18397
18458
  this.selectPoints = selectPoints;
@@ -18459,6 +18520,52 @@ Note the points property has been split into points and pointObjects (and there
18459
18520
  that.selectionManager = new zim.SelectionManager(null, "ctrl");
18460
18521
  }
18461
18522
 
18523
+ that.selectionManager.on("keydown", function (e) {
18524
+ if (!that.selectPoints) return;
18525
+ if (!that.keyFocus) return;
18526
+ if (e.keyCode >= 37 && e.keyCode <= 40) {
18527
+ var currentSelected = getCurrentSelected();
18528
+ if (currentSelected.length > 0) {
18529
+ for(var i=0; i<currentSelected.length; i++) {
18530
+ var pointObj = currentSelected[i];
18531
+ if (e.keyCode == 37) pointObj.x -= that.selectionManager.shiftKey?10:1;
18532
+ else if (e.keyCode == 39) pointObj.x += that.selectionManager.shiftKey?10:1;
18533
+ else if (e.keyCode == 38) pointObj.y -= that.selectionManager.shiftKey?10:1;
18534
+ else if (e.keyCode == 40) pointObj.y += that.selectionManager.shiftKey?10:1;
18535
+ mapMove(pointObj);
18536
+ }
18537
+ that.drawShape();
18538
+ that.dispatchEvent("update");
18539
+ if (that.stage) that.stage.update();
18540
+ }
18541
+ }
18542
+ }, null, null, null, null, mID);
18543
+
18544
+ that.selectionManager.on("keyup", function (e) {
18545
+ if (!that.selectPoints) return;
18546
+ if (!that.keyFocus) return;
18547
+ if (e.keyCode >= 37 && e.keyCode <= 40) {
18548
+ var currentSelected = getCurrentSelected();
18549
+ if (currentSelected.length > 0) {
18550
+ for(var i=0; i<currentSelected.length; i++) {
18551
+ replaceControls(currentSelected[i]);
18552
+ }
18553
+ }
18554
+ }
18555
+ }, null, null, null, null, mID);
18556
+
18557
+ that.selectionManager.on("undo", function () {
18558
+ if (!that.selectPoints) return;
18559
+ if (!that.keyFocus) return;
18560
+ if (that.lastPoints) {
18561
+ var tempPoints = zim.copy(that.lastPoints);
18562
+ that.lastPoints = zim.copy(that.points);
18563
+ that.points = tempPoints;
18564
+ if (that.stage) that.stage.update();
18565
+ }
18566
+ }, null, null, null, null, mID);
18567
+ // }
18568
+
18462
18569
  var mobile = zim.mobile();
18463
18570
 
18464
18571
  sets = that.controls = new zim.Container({style:false}).addTo(that); // sets - a set contains a ball and two rects
@@ -19356,34 +19463,42 @@ Note the points property has been split into points and pointObjects (and there
19356
19463
  }, null, null, null, null, mID);
19357
19464
 
19358
19465
  // remove point
19359
- that.controls.on("click", function (e) {
19466
+ that.controlsClickEvent = that.controls.on("click", function (e) {
19360
19467
  that.lastSelected = e.target.parent;
19361
19468
  that.lastindex = that.controls.getChildIndex(e.target.parent);
19362
19469
  if (!that.editPoints) return;
19363
19470
  if (that.selectionManager.shiftKey) { // remove
19364
- // if (that.selectionManager.currentSet == that.selectedBalls && that.selectedBalls.selections.length > 0) return;
19471
+ if (that.controls.noHold) that.controls.noHold();
19365
19472
  removeControl(e);
19366
19473
  }
19367
19474
  }, null, null, null, null, mID);
19368
19475
 
19369
- function removeControl(e) {
19370
- if (e.target.type == "Circle") {
19371
- var index = that.lastindex = that.controls.getChildIndex(e.target.parent);
19372
- if (that.controls.numChildren <= 2) return;
19373
- var points = that.points;
19374
- if (that.selectPoints) that.lastPoints = zim.copy(points);
19375
- points.splice(index, 1); // remove the point at the index
19376
- that.points = points;
19377
- that.stage.update();
19378
- that.lastSelected = that.lastindex = null;
19379
- }
19380
- }
19381
- if (that.editPoints) that.controls.hold(removeControl);
19476
+ that.controls.hold(removeControl);
19382
19477
 
19383
19478
  if (!_controls) that.hideControls();
19384
19479
  that.dispatchEvent("update");
19385
19480
  } // end of init()
19386
19481
 
19482
+ function removeControl(e) {
19483
+ if (!that.editPoints || !that.holdPoints || !e || !e.target) return;
19484
+ if (e.target.type == "Circle" && e.target.parent && e.target.parent.parent == that.controls) {
19485
+ var index = that.lastindex = that.controls.getChildIndex(e.target.parent);
19486
+ if (index < 0 || that.controls.numChildren <= 2) return;
19487
+ if (that.controls.noHold) that.controls.noHold();
19488
+ var points = that.points;
19489
+ if (that.selectPoints) that.lastPoints = zim.copy(points);
19490
+ points.splice(index, 1); // remove the point at the index
19491
+ that.points = points;
19492
+ var ev = new createjs.Event("change");
19493
+ ev.controlType = "pointDelete";
19494
+ ev.index = index;
19495
+ that.dispatchEvent(ev);
19496
+ that.dispatchEvent("pointdelete");
19497
+ if (that.stage) that.stage.update();
19498
+ that.lastSelected = that.lastindex = null;
19499
+ }
19500
+ }
19501
+
19387
19502
  // if (that.selectPoints) {
19388
19503
  function getCurrentSelected() {
19389
19504
  var answer = [];
@@ -19424,54 +19539,10 @@ Note the points property has been split into points and pointObjects (and there
19424
19539
  ball.y = 0;
19425
19540
  }
19426
19541
 
19427
- that.selectionManager.on("keydown", function (e) {
19428
- if (!that.selectPoints) return;
19429
- if (!that.keyFocus) return;
19430
- if (e.keyCode >= 37 && e.keyCode <= 40) {
19431
- var currentSelected = getCurrentSelected();
19432
- if (currentSelected.length > 0) {
19433
- for(var i=0; i<currentSelected.length; i++) {
19434
- var pointObj = currentSelected[i];
19435
- if (e.keyCode == 37) pointObj.x -= that.selectionManager.shiftKey?10:1;
19436
- else if (e.keyCode == 39) pointObj.x += that.selectionManager.shiftKey?10:1;
19437
- else if (e.keyCode == 38) pointObj.y -= that.selectionManager.shiftKey?10:1;
19438
- else if (e.keyCode == 40) pointObj.y += that.selectionManager.shiftKey?10:1;
19439
- mapMove(pointObj);
19440
- }
19441
- that.drawShape();
19442
- that.dispatchEvent("update");
19443
- if (that.stage) that.stage.update();
19444
- }
19445
- }
19446
- }, null, null, null, null, mID);
19447
-
19448
- that.selectionManager.on("keyup", function (e) {
19449
- if (!that.selectPoints) return;
19450
- if (!that.keyFocus) return;
19451
- if (e.keyCode >= 37 && e.keyCode <= 40) {
19452
- var currentSelected = getCurrentSelected();
19453
- if (currentSelected.length > 0) {
19454
- for(var i=0; i<currentSelected.length; i++) {
19455
- replaceControls(currentSelected[i]);
19456
- }
19457
- }
19458
- }
19459
- }, null, null, null, null, mID);
19460
-
19461
- that.selectionManager.on("undo", function () {
19462
- if (!that.selectPoints) return;
19463
- if (!that.keyFocus) return;
19464
- if (that.lastPoints) {
19465
- var tempPoints = zim.copy(that.lastPoints);
19466
- that.lastPoints = zim.copy(that.points);
19467
- that.points = tempPoints;
19468
- if (that.stage) that.stage.update();
19469
- }
19470
- }, null, null, null, null, mID);
19471
- // }
19542
+
19472
19543
 
19473
19544
 
19474
- mapMove = function (target) {
19545
+ function mapMove(target) {
19475
19546
  if (that.lockControls) return;
19476
19547
  var ball;
19477
19548
  if (target.rect1) { // pressmove on ball
@@ -19641,7 +19712,8 @@ Note the points property has been split into points and pointObjects (and there
19641
19712
  zim.decimals(p[3].x),
19642
19713
  zim.decimals(p[3].y)
19643
19714
  ];
19644
- if (p[4] && p[4]!=="mirror") point.push(p[4]);
19715
+ // if (p[4] && p[4]!=="mirror") point.push(p[4]);
19716
+ if (p[4]) point.push(p[4]);
19645
19717
  points.push(point);
19646
19718
  }
19647
19719
  return points;
@@ -19757,6 +19829,16 @@ Note the points property has been split into points and pointObjects (and there
19757
19829
  }
19758
19830
  });
19759
19831
 
19832
+ Object.defineProperty(that, 'controlType', {
19833
+ get: function() {
19834
+ return controlType;
19835
+ },
19836
+ set: function(value) {
19837
+ controlType = value;
19838
+ originalControlType = value;
19839
+ }
19840
+ });
19841
+
19760
19842
  // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
19761
19843
  // path manipulation and animating to path methods
19762
19844
 
@@ -19996,17 +20078,28 @@ Note the points property has been split into points and pointObjects (and there
19996
20078
  var index = insertPointData(points, controls, ratios, percent, controlType);
19997
20079
  that.points = points;
19998
20080
  that.num = points.length;
20081
+ var ev = new createjs.Event("change");
20082
+ ev.controlType = "pointAdd";
20083
+ ev.index = index;
20084
+ that.dispatchEvent(ev);
20085
+ that.dispatchEvent("pointadd");
20086
+ if (that.stage) that.stage.update();
19999
20087
  return index;
20000
20088
  };
20001
20089
 
20002
20090
  this.removePoint = function(index) {
20003
- if (zot(index) || index < 0) return;
20091
+ if (zot(index) || index < 0 || that.points.length <= 2 || index >= that.points.length) return that;
20004
20092
  var currentPoints = zim.copy(that.points);
20005
20093
  currentPoints.splice(index, 1);
20006
20094
  that.points = currentPoints;
20095
+ var ev = new createjs.Event("change");
20096
+ ev.controlType = "pointDelete";
20097
+ ev.index = index;
20098
+ that.dispatchEvent(ev);
20099
+ that.dispatchEvent("pointdelete");
20007
20100
  if (that.stage) that.stage.update();
20008
20101
  return that;
20009
- }
20102
+ };
20010
20103
 
20011
20104
  this.addPoints = function(num, controlType, startPoint, spread, dataOnly, points, even) {
20012
20105
  var sig = "num, controlType, startPoint, spread, dataOnly, points, even";
@@ -20464,10 +20557,12 @@ segmentRatios - a read-only array of cumulative ratio lengths of segments
20464
20557
  used internally to animate to the path and attribute proportional time to each segment
20465
20558
  controls - access to the container that holds the sets of controls
20466
20559
  each control is given a read-only num property
20560
+ use blob.controls.noHold() to remove holding down a point to delete it
20467
20561
  sticks - access to the Shape that has the control sticks
20468
20562
  lastSelected - access to the last selected (or created) control container
20469
20563
  lastindex - the index number of the last selected controls
20470
20564
  controlsVisible - get or set the visibility of the controls - or use showControls() and hideControls()
20565
+ controlType - get or set control types for new points
20471
20566
  types - get or set the general array for the types ["mirror", "straight", "free", "none"]
20472
20567
  changing this or removing a type will adjust the order when the user double clicks the points to change their type
20473
20568
  this is not an array of types for each point - see the points property to access the types of each point
@@ -20482,11 +20577,13 @@ selectionManager - access to the SelectionManager
20482
20577
  selectedBalls - access to the SelectionSet of control circles
20483
20578
  selectedRect1s - access to the SelectionSet of control rectangles
20484
20579
  selectedRect2s - access to the SelectionSet of control rectangles
20580
+ editPoints - get or set whether points can be added and removed
20581
+ holdPoints - get or set whether points can be held to be removed (default is editPoints)
20485
20582
  interactive - get or set whether the shape is interactive - toggle, move, change or add controls, etc.
20486
20583
  keyFocus - get or set the keyboard focus on the DisplayObject - see also zim.KEYFOCUS
20487
- will be set to true if this DisplayObject is the first made or DisplayObject is the last to be used with keyboard
20584
+ will be set to true if this DisplayObject is the first made or DisplayObject is the last to be used with keyboard
20488
20585
  veeObj - an object with ZIM VEE original parameters:value allowing the ZIM VEE values to be referenced
20489
- for instance, obj.prop = Pick.choose(obj.veeObj.prop); will reset the the prop to the result of the original ZIM VEE value
20586
+ for instance, obj.prop = Pick.choose(obj.veeObj.prop); will reset the the prop to the result of the original ZIM VEE value
20490
20587
 
20491
20588
  ALSO: see ZIM Container for properties such as:
20492
20589
  width, height, widthOnly, heightOnly, draggable, level, depth, group
@@ -20572,7 +20669,7 @@ added, click, dblclick, mousedown, mouseout, mouseover, pressdown (ZIM), pressmo
20572
20669
  var that = this;
20573
20670
  this.interactive = interactive;
20574
20671
  this.num = num;
20575
- this.editPoints = editPoints;
20672
+ this.editPoints = this.holdPoints = editPoints;
20576
20673
  this.selectPoints = selectPoints;
20577
20674
  this.lockControls = lockControls;
20578
20675
  this.onTop = onTop;
@@ -20601,9 +20698,10 @@ added, click, dblclick, mousedown, mouseout, mouseover, pressdown (ZIM), pressmo
20601
20698
  var min = 2; // distance within which to count as click to add point
20602
20699
 
20603
20700
  var mapMove;
20604
- var sets;
20701
+ var sets;
20605
20702
 
20606
20703
  points = checkForShape(points);
20704
+
20607
20705
  function checkForShape(shape) {
20608
20706
  if (shape=="circle" || shape == "rectangle" || shape =="triangle") {
20609
20707
  if (shape == "circle") shape = new zim.Circle(radius);
@@ -20734,6 +20832,7 @@ added, click, dblclick, mousedown, mouseout, mouseover, pressdown (ZIM), pressmo
20734
20832
  points.pop();
20735
20833
  }
20736
20834
  }
20835
+
20737
20836
 
20738
20837
  if (originalControlType && typeof points != "number") {
20739
20838
  // override controlType
@@ -20743,7 +20842,7 @@ added, click, dblclick, mousedown, mouseout, mouseover, pressdown (ZIM), pressmo
20743
20842
  point[4]=point[5]=point[6]=point[7]=0;
20744
20843
  }
20745
20844
  });
20746
- }
20845
+ }
20747
20846
 
20748
20847
  init();
20749
20848
  function init() {
@@ -20763,6 +20862,51 @@ added, click, dblclick, mousedown, mouseout, mouseover, pressdown (ZIM), pressmo
20763
20862
  that.selectionManager = new zim.SelectionManager(null, "ctrl");
20764
20863
  }
20765
20864
 
20865
+ that.selectionManager.on("keydown", function (e) {
20866
+ if (!that.selectPoints) return;
20867
+ if (!that.keyFocus) return;
20868
+ if (e.keyCode >= 37 && e.keyCode <= 40) {
20869
+ var currentSelected = getCurrentSelected();
20870
+ if (currentSelected.length > 0) {
20871
+ for(var i=0; i<currentSelected.length; i++) {
20872
+ var pointObj = currentSelected[i];
20873
+ if (e.keyCode == 37) pointObj.x -= that.selectionManager.shiftKey?10:1;
20874
+ else if (e.keyCode == 39) pointObj.x += that.selectionManager.shiftKey?10:1;
20875
+ else if (e.keyCode == 38) pointObj.y -= that.selectionManager.shiftKey?10:1;
20876
+ else if (e.keyCode == 40) pointObj.y += that.selectionManager.shiftKey?10:1;
20877
+ mapMove(pointObj);
20878
+ }
20879
+ that.drawShape();
20880
+ that.dispatchEvent("update");
20881
+ if (that.stage) that.stage.update();
20882
+ }
20883
+ }
20884
+ }, null, null, null, null, mID);
20885
+
20886
+ that.selectionManager.on("keyup", function (e) {
20887
+ if (!that.selectPoints) return;
20888
+ if (!that.keyFocus) return;
20889
+ if (e.keyCode >= 37 && e.keyCode <= 40) {
20890
+ var currentSelected = getCurrentSelected();
20891
+ if (currentSelected.length > 0) {
20892
+ for(var i=0; i<currentSelected.length; i++) {
20893
+ replaceControls(currentSelected[i]);
20894
+ }
20895
+ }
20896
+ }
20897
+ }, null, null, null, null, mID);
20898
+
20899
+ that.selectionManager.on("undo", function () {
20900
+ if (!that.selectPoints) return;
20901
+ if (!that.keyFocus) return;
20902
+ if (that.lastPoints) {
20903
+ var tempPoints = zim.copy(that.lastPoints);
20904
+ that.lastPoints = zim.copy(that.points);
20905
+ that.points = tempPoints;
20906
+ if (that.stage) that.stage.update();
20907
+ }
20908
+ }, null, null, null, null, mID);
20909
+
20766
20910
  num = typeof points == "number" ? points : points.length;
20767
20911
  if (num <= 0) return;
20768
20912
  if (zot(controlLengthOriginal)) controlLength = radius * 4 / num;
@@ -20788,6 +20932,7 @@ added, click, dblclick, mousedown, mouseout, mouseover, pressdown (ZIM), pressmo
20788
20932
  _pointCircles = [];
20789
20933
 
20790
20934
  var i, point, temp, set, rect1, rect2, ball, type, setInfo;
20935
+
20791
20936
 
20792
20937
  for (i=0; i<num; i++) {
20793
20938
  set = new zim.Container({style:false}).addTo(sets);
@@ -20837,7 +20982,6 @@ added, click, dblclick, mousedown, mouseout, mouseover, pressdown (ZIM), pressmo
20837
20982
 
20838
20983
  // balls are relative to blob but handles are relative to ball
20839
20984
  // points is an array of [[setX, setY, ballX, ballY, handleX, handleY, handle2X, handle2Y, type], etc.]
20840
-
20841
20985
  setInfo = points[i];
20842
20986
  type = setInfo[8] ? setInfo[8] : controlType;
20843
20987
  set.loc({x:setInfo[0], y:setInfo[1]});
@@ -20886,7 +21030,7 @@ added, click, dblclick, mousedown, mouseout, mouseover, pressdown (ZIM), pressmo
20886
21030
  point = [set, ball, rect1, rect2, setInfo?setInfo[8]:controlType];
20887
21031
  _points.push(point);
20888
21032
  _pointCircles.push(ball);
20889
- _pointControls.push(set);
21033
+ _pointControls.push(set);
20890
21034
  }
20891
21035
 
20892
21036
  var tappedTwice = false;
@@ -21668,6 +21812,11 @@ added, click, dblclick, mousedown, mouseout, mouseover, pressdown (ZIM), pressmo
21668
21812
  points.splice(pointBefore+1, 0, [point.x, point.y, 0,0, 0,0, 0,0]);
21669
21813
  that.points = points;
21670
21814
  that.changeControl({index:pointBefore+1, type:"mirror", update:true});
21815
+ var ev = new createjs.Event("change");
21816
+ ev.controlType = "pointAdd";
21817
+ ev.index = pointBefore+1;
21818
+ that.dispatchEvent(ev);
21819
+ that.dispatchEvent("pointadd");
21671
21820
  } else { // only on edge
21672
21821
  // test close enough to edge otherwise return
21673
21822
  var p = that.pointsAdjusted;
@@ -21706,28 +21855,38 @@ added, click, dblclick, mousedown, mouseout, mouseover, pressdown (ZIM), pressmo
21706
21855
  that.lastindex = that.controls.getChildIndex(e.target.parent);
21707
21856
  if (!that.editPoints) return;
21708
21857
  if (that.selectionManager.shiftKey) { // remove
21858
+ if (that.controls.noHold) that.controls.noHold();
21709
21859
  removeControl(e);
21710
21860
  }
21711
21861
  }, null, null, null, null, mID);
21712
21862
 
21713
- function removeControl(e) {
21714
- if (e.target.type == "Circle") {
21715
- var index = that.lastindex = that.controls.getChildIndex(e.target.parent);
21716
- if (that.controls.numChildren <= 2) return;
21717
- var points = that.points;
21718
- if (that.selectPoints) that.lastPoints = zim.copy(points);
21719
- points.splice(index, 1); // remove the point at the index
21720
- that.points = points;
21721
- that.stage.update();
21722
- that.lastSelected = that.lastindex = null;
21723
- }
21724
- }
21725
- if (that.editPoints) that.controls.hold(removeControl);
21863
+ that.controls.hold(removeControl);
21726
21864
 
21727
21865
  if (!_controls) that.hideControls();
21728
21866
  that.dispatchEvent("update");
21729
21867
  } // end of init()
21730
21868
 
21869
+ function removeControl(e) {
21870
+ if (!that.editPoints || !that.holdPoints || !e || !e.target) return;
21871
+ if (e.target.type == "Circle" && e.target.parent && e.target.parent.parent == that.controls) {
21872
+ var index = that.lastindex = that.controls.getChildIndex(e.target.parent);
21873
+ if (index < 0 || that.controls.numChildren <= 2) return;
21874
+ if (that.controls.noHold) that.controls.noHold();
21875
+ var points = that.points;
21876
+ if (that.selectPoints) that.lastPoints = zim.copy(points);
21877
+ points.splice(index, 1); // remove the point at the index
21878
+ that.points = points;
21879
+ var ev = new createjs.Event("change");
21880
+ ev.controlType = "pointDelete";
21881
+ ev.index = index;
21882
+ that.dispatchEvent(ev);
21883
+ that.dispatchEvent("pointdelete");
21884
+ if (that.stage) that.stage.update();
21885
+ that.lastSelected = that.lastindex = null;
21886
+ }
21887
+ }
21888
+
21889
+
21731
21890
  function getCurrentSelected() {
21732
21891
  var answer = [];
21733
21892
  var currentSet = that.selectionManager.currentSet;
@@ -21767,53 +21926,7 @@ added, click, dblclick, mousedown, mouseout, mouseover, pressdown (ZIM), pressmo
21767
21926
  ball.y = 0;
21768
21927
  }
21769
21928
 
21770
- that.selectionManager.on("keydown", function (e) {
21771
- if (!that.selectPoints) return;
21772
- if (!that.keyFocus) return;
21773
- if (e.keyCode >= 37 && e.keyCode <= 40) {
21774
- var currentSelected = getCurrentSelected();
21775
- if (currentSelected.length > 0) {
21776
- for(var i=0; i<currentSelected.length; i++) {
21777
- var pointObj = currentSelected[i];
21778
- if (e.keyCode == 37) pointObj.x -= that.selectionManager.shiftKey?10:1;
21779
- else if (e.keyCode == 39) pointObj.x += that.selectionManager.shiftKey?10:1;
21780
- else if (e.keyCode == 38) pointObj.y -= that.selectionManager.shiftKey?10:1;
21781
- else if (e.keyCode == 40) pointObj.y += that.selectionManager.shiftKey?10:1;
21782
- mapMove(pointObj);
21783
- }
21784
- that.drawShape();
21785
- that.dispatchEvent("update");
21786
- if (that.stage) that.stage.update();
21787
- }
21788
- }
21789
- }, null, null, null, null, mID);
21790
-
21791
- that.selectionManager.on("keyup", function (e) {
21792
- if (!that.selectPoints) return;
21793
- if (!that.keyFocus) return;
21794
- if (e.keyCode >= 37 && e.keyCode <= 40) {
21795
- var currentSelected = getCurrentSelected();
21796
- if (currentSelected.length > 0) {
21797
- for(var i=0; i<currentSelected.length; i++) {
21798
- replaceControls(currentSelected[i]);
21799
- }
21800
- }
21801
- }
21802
- }, null, null, null, null, mID);
21803
-
21804
- that.selectionManager.on("undo", function () {
21805
- if (!that.selectPoints) return;
21806
- if (!that.keyFocus) return;
21807
- if (that.lastPoints) {
21808
- var tempPoints = zim.copy(that.lastPoints);
21809
- that.lastPoints = zim.copy(that.points);
21810
- that.points = tempPoints;
21811
- if (that.stage) that.stage.update();
21812
- }
21813
- }, null, null, null, null, mID);
21814
-
21815
-
21816
- mapMove = function (target) {
21929
+ function mapMove (target) {
21817
21930
  if (that.lockControls) return;
21818
21931
  var ball;
21819
21932
  if (target.rect1) { // pressmove on ball
@@ -21948,6 +22061,16 @@ added, click, dblclick, mousedown, mouseout, mouseover, pressdown (ZIM), pressmo
21948
22061
  });
21949
22062
  that.lockControls = _lockControls;
21950
22063
 
22064
+ Object.defineProperty(that, 'controlType', {
22065
+ get: function() {
22066
+ return controlType;
22067
+ },
22068
+ set: function(value) {
22069
+ controlType = value;
22070
+ originalControlType = value;
22071
+ }
22072
+ });
22073
+
21951
22074
  // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
21952
22075
  // NOTE: extends ZIM CustomShape for more properties and a few functions.
21953
22076
  // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -21963,7 +22086,7 @@ added, click, dblclick, mousedown, mouseout, mouseover, pressdown (ZIM), pressmo
21963
22086
  });
21964
22087
  if (that.selectPoints && !zim.KEYFOCUS) setFocus();
21965
22088
  function setFocus() {that.keyFocus = true; var d=document.activeElement; if (d) d.blur();}
21966
-
22089
+
21967
22090
 
21968
22091
  Object.defineProperty(that, 'points', {
21969
22092
  get: function() {
@@ -21981,9 +22104,10 @@ added, click, dblclick, mousedown, mouseout, mouseover, pressdown (ZIM), pressmo
21981
22104
  zim.decimals(p[3].x),
21982
22105
  zim.decimals(p[3].y)
21983
22106
  ];
21984
- if (p[4] && p[4]!=="straight") point.push(p[4]);
22107
+ if (p[4]) point.push(p[4]);
22108
+ // if (p[4] && p[4]!=="straight") point.push(p[4]);
21985
22109
  points.push(point);
21986
- }
22110
+ }
21987
22111
  return points;
21988
22112
  },
21989
22113
  set: function(value) {
@@ -22137,6 +22261,7 @@ added, click, dblclick, mousedown, mouseout, mouseover, pressdown (ZIM), pressmo
22137
22261
  if (zon) {zogy("Blob() - segmentRatios is read only");}
22138
22262
  }
22139
22263
  });
22264
+
22140
22265
 
22141
22266
  that.approximateBounds = function(num, showPoints) {
22142
22267
  if (zot(num)) num = 80;
@@ -22339,17 +22464,28 @@ added, click, dblclick, mousedown, mouseout, mouseover, pressdown (ZIM), pressmo
22339
22464
  var index = insertPointData(points, controls, ratios, percent, controlType);
22340
22465
  that.points = points;
22341
22466
  that.num = points.length;
22467
+ var ev = new createjs.Event("change");
22468
+ ev.controlType = "pointAdd";
22469
+ ev.index = index;
22470
+ that.dispatchEvent(ev);
22471
+ that.dispatchEvent("pointadd");
22472
+ if (that.stage) that.stage.update();
22342
22473
  return index;
22343
22474
  };
22344
22475
 
22345
22476
  this.removePoint = function(index) {
22346
- if (zot(index) || index < 0) return;
22477
+ if (zot(index) || index < 0 || that.points.length <= 2 || index >= that.points.length) return that;
22347
22478
  var currentPoints = zim.copy(that.points);
22348
22479
  currentPoints.splice(index, 1);
22349
22480
  that.points = currentPoints;
22481
+ var ev = new createjs.Event("change");
22482
+ ev.controlType = "pointDelete";
22483
+ ev.index = index;
22484
+ that.dispatchEvent(ev);
22485
+ that.dispatchEvent("pointdelete");
22350
22486
  if (that.stage) that.stage.update();
22351
22487
  return that;
22352
- }
22488
+ };
22353
22489
 
22354
22490
  this.addPoints = function(num, controlType, startPoint, spread, dataOnly, points, even) {
22355
22491
  var sig = "num, controlType, startPoint, spread, dataOnly, points, even";
@@ -29425,7 +29561,7 @@ added, click, dblclick, mousedown, mouseout, mouseover, pressdown (ZIM), pressmo
29425
29561
  that.toggled = false;
29426
29562
  var ch,i;
29427
29563
  var tar = that.content||that.contentContainer||that;
29428
- if (tar.type != "Container") tar = that.contentContainer||that;
29564
+ if (tar.type != "Container" && tar.type != "Tile") tar = that.contentContainer||that;
29429
29565
  for (i=0; i<tar.numChildren; i++) { // record depths first
29430
29566
  ch = tar.getChildAt(i);
29431
29567
  if (ch.type == "TextArea" || ch.type == "Loader" || ch.type == "Tag") {
@@ -29513,7 +29649,7 @@ added, click, dblclick, mousedown, mouseout, mouseover, pressdown (ZIM), pressmo
29513
29649
  this.on("added", show2, null, null, null, null, mID);
29514
29650
  function show2() {
29515
29651
  var tar = that.content||that.contentContainer||that;
29516
- if (tar.type != "Container") tar = that.contentContainer||that;
29652
+ if (tar.type != "Container" && tar.type != "Tile") tar = that.contentContainer||that;
29517
29653
  for (var i=0; i<htmlList.length; i++) {
29518
29654
  tar.addChildAt(htmlList.values[i].obj, htmlList.values[i].depth);
29519
29655
  }
@@ -29727,7 +29863,6 @@ panelHeight - get and set the height of the panel without scaling it as height d
29727
29863
  titleBar - access to the titleBar container - which also has a backing property
29728
29864
  titleBarlabel - access to the label of the current panel
29729
29865
  text - access to the text of the current panel
29730
- titleBarLabel - gives access to the titleBar label
29731
29866
  closeIcon - access to the close button
29732
29867
  collapseIcon - access to the ZIM Shape if there is a collapse triangle
29733
29868
  collapsed - get or set whether the panel is collapsed - must start with collapse parameter set to true
@@ -29907,7 +30042,7 @@ added, click, dblclick, mousedown, mouseout, mouseover, pressdown (ZIM), pressmo
29907
30042
  that.drag({rect:boundary, currentTarget:true, onTop:onTop});
29908
30043
  var ch,i;
29909
30044
  var tar = that.content||that.contentContainer||that;
29910
- if (tar.type != "Container") tar = that.contentContainer||that;
30045
+ if (tar.type != "Container" && tar.type != "Tile") tar = that.contentContainer||that;
29911
30046
  for (i=0; i<tar.numChildren; i++) { // record depths first
29912
30047
  ch = tar.getChildAt(i);
29913
30048
  if (ch.type == "TextArea" || ch.type == "Loader" || ch.type == "Tag") {
@@ -30009,7 +30144,7 @@ added, click, dblclick, mousedown, mouseout, mouseover, pressdown (ZIM), pressmo
30009
30144
  function hide2() {
30010
30145
  var ch,i;
30011
30146
  var tar = that.content||that.contentContainer||that;
30012
- if (tar.type != "Container") tar = that.contentContainer||that;
30147
+ if (tar.type != "Container" && tar.type != "Tile") tar = that.contentContainer||that;
30013
30148
  for (i=0; i<tar.numChildren; i++) { // record depths first
30014
30149
  ch = tar.getChildAt(i);
30015
30150
  if (ch.type == "TextArea" || ch.type == "Loader" || ch.type == "Tag") {
@@ -30904,7 +31039,7 @@ added, click, dblclick, mousedown, mouseout, mouseover, pressdown (ZIM), pressmo
30904
31039
  function hide2() {
30905
31040
  var ch,i;
30906
31041
  var tar = that.content||that.contentContainer||that;
30907
- if (tar.type != "Container") tar = that.contentContainer||that;
31042
+ if (tar.type != "Container" && tar.type != "Tile") tar = that.contentContainer||that;
30908
31043
  for (i=0; i<tar.numChildren; i++) { // record depths first
30909
31044
  ch = tar.getChildAt(i);
30910
31045
  if (ch.type == "TextArea" || ch.type == "Loader" || ch.type == "Tag") {
@@ -37115,49 +37250,49 @@ added, click, dblclick, mousedown, mouseout, mouseover, pressdown (ZIM), pressmo
37115
37250
  setLabel(stepperType=="number"?numVal:list[index], stepperType=="number"?numVal:index);
37116
37251
 
37117
37252
  function doStep(n) {
37118
- var nextIndex;
37119
- if (stepperType == "number") {
37120
- numVal = Number(numVal);
37121
- var lastNumVal = numVal;
37122
- numVal += Number(actualStep * n * numDir);
37123
- numVal = zim.decimals(numVal, decimals);
37124
- if (!continuous) {
37125
- if (numVal > that.max) {
37126
- numVal = step==1?that.max:lastNumVal;
37127
- if (display) box.cur("default");
37128
- } else {
37129
- if (display) box.cur("pointer");
37130
- }
37131
- if (numVal < that.min) {
37132
- numVal = step==1?that.min:lastNumVal;
37133
- }
37253
+ var lastValue = that.value; // <-- CAPTURE CURRENT VALUE BEFORE STEPPING
37254
+ var nextIndex;
37255
+ if (stepperType == "number") {
37256
+ numVal = Number(numVal);
37257
+ var lastNumVal = numVal;
37258
+ numVal += Number(actualStep * n * numDir);
37259
+ numVal = zim.decimals(numVal, decimals);
37260
+ if (!continuous) {
37261
+ if (numVal > that.max) {
37262
+ numVal = step==1?that.max:lastNumVal;
37263
+ if (display) box.cur("default");
37134
37264
  } else {
37135
- if (numVal > that.max) {
37136
- numVal = that.min;
37137
- } else if (numVal < that.min) {
37138
- numVal = that.max;
37139
- }
37265
+ if (display) box.cur("pointer");
37266
+ }
37267
+ if (numVal < that.min) {
37268
+ numVal = step==1?that.min:lastNumVal;
37140
37269
  }
37141
37270
  } else {
37142
- nextIndex = index + n;
37143
- if (!continuous) {
37144
- if (nextIndex > list.length-1) {
37145
- if (display) box.cur("default");
37146
- return;
37147
- } else {
37148
- if (display) box.cur("pointer");
37149
- }
37150
- if (nextIndex < 0) return;
37271
+ if (numVal > that.max) {
37272
+ numVal = that.min;
37273
+ } else if (numVal < that.min) {
37274
+ numVal = that.max;
37275
+ }
37276
+ }
37277
+ } else {
37278
+ nextIndex = index + n;
37279
+ if (!continuous) {
37280
+ if (nextIndex > list.length-1) {
37281
+ if (display) box.cur("default");
37282
+ return;
37151
37283
  } else {
37152
- if (nextIndex > list.length-1) nextIndex = 0;
37153
- if (nextIndex < 0) nextIndex = list.length-1;
37284
+ if (display) box.cur("pointer");
37154
37285
  }
37155
- index = nextIndex;
37286
+ if (nextIndex < 0) return;
37287
+ } else {
37288
+ if (nextIndex > list.length-1) nextIndex = 0;
37289
+ if (nextIndex < 0) nextIndex = list.length-1;
37156
37290
  }
37157
- setLabel(stepperType=="number"?numVal:list[index], stepperType=="number"?numVal:index);
37158
- if (that.value != lastValue) that.dispatchEvent("change");
37159
- lastValue = that.value;
37291
+ index = nextIndex;
37160
37292
  }
37293
+ setLabel(stepperType=="number"?numVal:list[index], stepperType=="number"?numVal:index);
37294
+ if (that.value != lastValue) that.dispatchEvent("change");
37295
+ }
37161
37296
 
37162
37297
 
37163
37298
  Object.defineProperty(this, 'index', {
@@ -37395,6 +37530,7 @@ added, click, dblclick, mousedown, mouseout, mouseover, pressdown (ZIM), pressmo
37395
37530
 
37396
37531
  function setLabel(text, n, update) {
37397
37532
  index = n;
37533
+ lastValue = that.value; // <-- KEEPS OUTER lastValue IN SYNC ON PROGRAMMATIC SET
37398
37534
  if (zot(update)) update = true;
37399
37535
  if (display) {
37400
37536
  if (stepperType == "number") {
@@ -37404,8 +37540,6 @@ added, click, dblclick, mousedown, mouseout, mouseover, pressdown (ZIM), pressmo
37404
37540
  }
37405
37541
  label.text = text;
37406
37542
  label.center(that);
37407
- // label.x = box.x+box.getBounds().width/2;
37408
- // label.y = box.y+(box.getBounds().height-label.getBounds().height)/2;
37409
37543
  }
37410
37544
  if (arrows) setArrows();
37411
37545
  if (update && next && ((!zim.OPTIMIZE&&(zns||!WW.OPTIMIZE)) && next.stage)) {
@@ -57641,7 +57775,7 @@ zim.noGesture = function(obj, move, scale, rotate) {
57641
57775
  };//-34.6
57642
57776
 
57643
57777
  /*--
57644
- obj.gestureBoundary = function(boundary, new)
57778
+ obj.gestureBoundary = function(boundary, update)
57645
57779
 
57646
57780
  gestureBoundary
57647
57781
  zim DisplayObject method
@@ -57682,7 +57816,260 @@ RETURNS obj for chaining
57682
57816
  return obj;
57683
57817
  };//-34.7
57684
57818
 
57685
- //
57819
+
57820
+ /*--
57821
+ obj.tilt = function(boundary, damp, factor, mode, type, call)
57822
+
57823
+ tilt
57824
+ zim DisplayObject method
57825
+
57826
+ DESCRIPTION
57827
+ Moves or positions an object based on device sensors (deviceorientation or devicemotion).
57828
+ Automatically manages user sensor permission via ZIM PermissionAsk if permission has not yet been requested.
57829
+
57830
+ NOTE
57831
+ If permission has already been granted, tilt activates immediately.
57832
+ If multiple objects call tilt() before permission is granted, they are queued and activated together once the user accepts.
57833
+ ALSO see noTilt() to remove tilt behaviors.
57834
+ ALSO see tiltBoundary() to dynamically change or clear the boundary rectangle.
57835
+
57836
+ EXAMPLE
57837
+ // Basic rolling ball constrained to stage bounds
57838
+ const ball = new Circle(30, red)
57839
+ .center()
57840
+ .tilt(true);
57841
+ END EXAMPLE
57842
+
57843
+ EXAMPLE
57844
+ // Custom damping, sensitivity factor, and boundary
57845
+ new Rectangle(80, 80, blue)
57846
+ .center()
57847
+ .tilt({
57848
+ boundary: new Boundary(50, 50, W - 100, H - 100),
57849
+ damp: 0.08,
57850
+ factor: 0.75,
57851
+ call: (success, obj) => {
57852
+ if (!success) new Pane("Device orientation not available", yellow).show();
57853
+ }
57854
+ });
57855
+ END EXAMPLE
57856
+
57857
+ PARAMETERS
57858
+ ** supports DUO - parameters or single object with properties below
57859
+ boundary - (default null) a ZIM Boundary object, an Array of [x, y, width, height], or true for the Stage dimensions
57860
+ damp - (default 0.05) smoothing factor via ZIM Damp, or null / false for immediate direct tracking
57861
+ factor - (default 0.5) sensitivity factor multiplier, or an Object with {x, y} for independent axis sensitivity
57862
+ mode - (default "move") "move" for continuous velocity / rolling behavior, or "position" for tilt offset relative to starting coordinates
57863
+ type - (default "deviceorientation") the sensor event type ("deviceorientation" or "devicemotion")
57864
+ call - (default null) callback function called when permission resolves, receiving (success:Boolean, obj:DisplayObject)
57865
+
57866
+ RETURNS obj for chaining
57867
+ --*///+34.75
57868
+ zim.tilt = function(obj, boundary, damp, factor, mode, type, call) {
57869
+ var sig = "obj, boundary, damp, factor, mode, type, call";
57870
+ var duo; if (duo = zob(zim.tilt, arguments, sig)) return duo;
57871
+ if ((obj.type=="AC"||(obj.type=="Emoji"&&obj.svg&&obj.svg.type=="AC"))&&WW.zdf) {WW.zdf.ac("tilt", arguments); return obj;}
57872
+ z_d("34.75");
57873
+
57874
+ // MONITOR
57875
+ var mID = "z~tilt";
57876
+ if (obj && obj.mID && ((obj.mID[2] && obj.mID[2]=="-") || obj.mID[0]=="-")) mID = "z~-";
57877
+
57878
+ if (zot(obj)) return;
57879
+
57880
+ // Clean up any existing tilt on this object first
57881
+ if (obj._tiltTicker) zim.noTilt(obj);
57882
+
57883
+ var f = obj.frame || (typeof zdf != "undefined" ? zdf : WW.zdf);
57884
+ var s = obj.stage || (f ? f.stage : null);
57885
+
57886
+ // Set initial boundary
57887
+ zim.tiltBoundary(obj, boundary);
57888
+
57889
+ if (zot(damp)) damp = 0.05;
57890
+ if (zot(factor)) factor = 0.5;
57891
+ var factorX = typeof factor === "object" ? factor.x : factor;
57892
+ var factorY = typeof factor === "object" ? factor.y : factor;
57893
+ if (zot(mode)) mode = "move"; // "move" | "position" | "rotation"
57894
+ if (zot(type)) type = "deviceorientation";
57895
+
57896
+ // Damp angles for "move" mode (starts at 0) or positions for "position" mode
57897
+ var dampX = damp ? new zim.Damp(mode == "position" ? obj.x : 0, damp) : null;
57898
+ var dampY = damp ? new zim.Damp(mode == "position" ? obj.y : 0, damp) : null;
57899
+ var currentRot = {x: 0, y: 0, z: 0};
57900
+
57901
+ function updateTilt() {
57902
+ if (mode == "move") {
57903
+ // Damping the rotation angle gives smooth acceleration & deceleration
57904
+ var rotY = dampX ? dampX.convert(currentRot.y) : currentRot.y;
57905
+ var rotX = dampY ? dampY.convert(currentRot.x) : currentRot.x;
57906
+ obj.x += rotY * factorX;
57907
+ obj.y += rotX * factorY;
57908
+ } else if (mode == "position") {
57909
+ var targetX = (obj._tiltStartX != null ? obj._tiltStartX : obj.x) + currentRot.y * factorX * 10;
57910
+ var targetY = (obj._tiltStartY != null ? obj._tiltStartY : obj.y) + currentRot.x * factorY * 10;
57911
+ obj.x = dampX ? dampX.convert(targetX) : targetX;
57912
+ obj.y = dampY ? dampY.convert(targetY) : targetY;
57913
+ }
57914
+
57915
+ // Dynamically applies boundary constraint
57916
+ if (obj._tiltBoundary) {
57917
+ obj.x = zim.constrain(obj.x, obj._tiltBoundary.x, obj._tiltBoundary.x + obj._tiltBoundary.width);
57918
+ obj.y = zim.constrain(obj.y, obj._tiltBoundary.y, obj._tiltBoundary.y + obj._tiltBoundary.height);
57919
+ }
57920
+
57921
+ if (s) s.update();
57922
+ }
57923
+
57924
+ function activate() {
57925
+ obj._tiltStartX = obj.x;
57926
+ obj._tiltStartY = obj.y;
57927
+ if (mode == "position" && dampX && dampY) {
57928
+ dampX.immediate(obj.x);
57929
+ dampY.immediate(obj.y);
57930
+ }
57931
+
57932
+ obj._tiltType = type;
57933
+ obj._tiltListener = function(e) {
57934
+ if (e.rotation) {
57935
+ currentRot.x = e.rotation.x || 0;
57936
+ currentRot.y = e.rotation.y || 0;
57937
+ currentRot.z = e.rotation.z || 0;
57938
+ } else {
57939
+ currentRot.x = e.beta || 0;
57940
+ currentRot.y = e.gamma || 0;
57941
+ currentRot.z = e.alpha || 0;
57942
+ }
57943
+ };
57944
+ f.on(type, obj._tiltListener);
57945
+
57946
+ obj._tiltTicker = zim.Ticker.add(updateTilt);
57947
+ if (call) call(true, obj);
57948
+ }
57949
+
57950
+ // Permission check & queueing
57951
+ if (f._permissionGranted) {
57952
+ activate();
57953
+ } else if (f._permissionPending) {
57954
+ f._permissionQueue.push(activate);
57955
+ } else {
57956
+ f._permissionPending = true;
57957
+ f._permissionQueue = [activate];
57958
+
57959
+ new zim.PermissionAsk(function(yes) {
57960
+ f._permissionPending = false;
57961
+ if (yes) {
57962
+ f._permissionGranted = true;
57963
+ zim.loop(f._permissionQueue, function(fn) { fn(); });
57964
+ f._permissionQueue = [];
57965
+ } else {
57966
+ if (call) call(false, obj);
57967
+ }
57968
+ }, type);
57969
+ }
57970
+
57971
+ return obj;
57972
+ };//-34.75
57973
+
57974
+
57975
+ /*--
57976
+ obj.noTilt = function()
57977
+
57978
+ noTilt
57979
+ zim DisplayObject method
57980
+
57981
+ DESCRIPTION
57982
+ Removes tilt listeners, removes internal ticker updates, and clears tilt boundary properties from the object.
57983
+
57984
+ EXAMPLE
57985
+ const player = new Circle(25, green).center().tilt(true);
57986
+
57987
+ // Stop tilt control when game ends or is paused
57988
+ player.noTilt();
57989
+ END EXAMPLE
57990
+
57991
+ RETURNS obj for chaining
57992
+ --*///+34.76
57993
+ zim.noTilt = function(obj) {
57994
+ var sig = "obj";
57995
+ var duo; if (duo = zob(zim.noTilt, arguments, sig)) return duo;
57996
+ z_d("34.76");
57997
+
57998
+ if (zot(obj) || !obj.on || !obj.zimTouch) return;
57999
+
58000
+ // MONITOR
58001
+ var mID = "z~noTilt";
58002
+ if (obj && obj.mID && ((obj.mID[2] && obj.mID[2]=="-") || obj.mID[0]=="-")) mID = "z~-";
58003
+
58004
+ if (zot(obj)) return;
58005
+ var f = obj.frame || (typeof zdf != "undefined" ? zdf : WW.zdf);
58006
+
58007
+ if (obj._tiltListener && f) {
58008
+ f.off(obj._tiltType || "deviceorientation", obj._tiltListener);
58009
+ obj._tiltListener = null;
58010
+ }
58011
+ if (obj._tiltTicker) {
58012
+ zim.Ticker.remove(obj._tiltTicker);
58013
+ obj._tiltTicker = null;
58014
+ }
58015
+ obj._tiltBoundary = null;
58016
+
58017
+ return obj;
58018
+
58019
+ };//-34.76
58020
+
58021
+ /*--
58022
+ obj.tiltBoundary = function(boundary, y, width, height)
58023
+
58024
+ tiltBoundary
58025
+ zim DisplayObject method
58026
+
58027
+ DESCRIPTION
58028
+ Dynamically sets, updates, or removes the boundary rectangle for an object with tilt().
58029
+
58030
+ EXAMPLE
58031
+ const marble = new Circle(20, purple).center().tilt(true);
58032
+
58033
+ // Update boundary dynamically later (e.g., on resize or game level change)
58034
+ marble.tiltBoundary(new Boundary(50, 50, W - 100, H - 100));
58035
+
58036
+ // Or clear the boundary
58037
+ // marble.tiltBoundary(null);
58038
+ END EXAMPLE
58039
+
58040
+ PARAMETERS
58041
+ ** supports DUO - parameters or single object with properties below
58042
+ boundary - (default null) a ZIM Boundary object, an Array of [x, y, width, height], true for Frame bounds, null / false to remove boundary, or the x position if passing (x, y, width, height) positionally
58043
+ y - (default null) y coordinate if boundary parameter is used as x
58044
+ width - (default null) width if boundary parameter is used as x
58045
+ height - (default null) height if boundary parameter is used as x
58046
+
58047
+ RETURNS obj for chaining
58048
+ --*///+34.77
58049
+ zim.tiltBoundary = function(obj, boundary, y, width, height) {
58050
+ var sig = "obj, boundary, y, width, height";
58051
+ var duo; if (duo = zob(zim.tiltBoundary, arguments, sig)) return duo;
58052
+ z_d("34.77");
58053
+ if (zot(obj) || !obj.on) return;
58054
+ var f = obj.frame || (typeof zdf != "undefined" ? zdf : WW.zdf);
58055
+
58056
+ if (zot(boundary) || boundary === false || boundary === null) {
58057
+ obj._tiltBoundary = null;
58058
+ } else if (boundary === true && f) {
58059
+ obj._tiltBoundary = new zim.Boundary(0, 0, f.width, f.height);
58060
+ } else if (Array.isArray(boundary)) {
58061
+ obj._tiltBoundary = new zim.Boundary(boundary[0], boundary[1], boundary[2], boundary[3]);
58062
+ } else if (typeof boundary === "number" && !zot(y)) {
58063
+ obj._tiltBoundary = new zim.Boundary(boundary, y, width, height);
58064
+ } else if (boundary && boundary.type == "Boundary") {
58065
+ obj._tiltBoundary = boundary;
58066
+ } else {
58067
+ obj._tiltBoundary = null;
58068
+ }
58069
+
58070
+ return obj;
58071
+ };//-34.77
58072
+
57686
58073
  /*--
57687
58074
  obj.effect = function(effect, x, y, width, height)
57688
58075
 
@@ -75313,6 +75700,7 @@ multiple - true if the multipleSelect key is being pressed otherwise false
75313
75700
  ctrlKey - true if the ctrlKey key is being pressed otherwise false
75314
75701
  shiftKey - true if the shiftKey key is being pressed otherwise false
75315
75702
  metaKey - true if the metaKey key is being pressed otherwise false
75703
+ undoKey - true set to false to not handle undo
75316
75704
  enabled - set to false to disable and true to enable - will remember selections when enabled
75317
75705
 
75318
75706
  EVENTS
@@ -75326,6 +75714,7 @@ dispatches an "undo" event if a CTRL or META plus the U key is pressed
75326
75714
  // MONITOR
75327
75715
  this.mID = "z~"+this.type;
75328
75716
 
75717
+
75329
75718
  if (zot(sets)) sets = [];
75330
75719
  if (zot(multipleSets)) multipleSets = true;
75331
75720
  if (!Array.isArray(sets)) sets = [sets];
@@ -75333,6 +75722,7 @@ dispatches an "undo" event if a CTRL or META plus the U key is pressed
75333
75722
  this.multipleKey = multipleKey;
75334
75723
  this.multipleSets = multipleSets;
75335
75724
  this.multiple = false;
75725
+ this.undoKey = true;
75336
75726
  var that = this;
75337
75727
  var _enabled = true;
75338
75728
  for (var i=0; i<sets.length; i++) {
@@ -75361,7 +75751,7 @@ dispatches an "undo" event if a CTRL or META plus the U key is pressed
75361
75751
  that.metaKey = e.metaKey;
75362
75752
  if (!_enabled) return;
75363
75753
  that.dispatchEvent(e);
75364
- if (e.keyCode==90 && (that.ctrlKey || that.metaKey)) {
75754
+ if (that.undoKey && e.keyCode==90 && (that.ctrlKey || that.metaKey)) {
75365
75755
  that.dispatchEvent("undo");
75366
75756
  }
75367
75757
  };
@@ -81218,7 +81608,7 @@ alpha, cursor, shadow, name, mouseChildren, mouseEnabled, parent, numChildren, e
81218
81608
  //-69.9747
81219
81609
 
81220
81610
  /*--
81221
- zim.Perspective = function(obj, points, interactive, showControls, allowToggle, move, borderColor, borderWidth, dashed, plane, fade, dynamic, frame, resolution, maskShape, feather, onTop, overlay, clipping, passthrough, passthroughScale, passthroughShiftX, passthroughShiftY, style, group, inherit)
81611
+ zim.Perspective = function(obj, points, interactive, showControls, allowToggle, move, borderColor, borderWidth, dashed, plane, fade, dynamic, frame, resolution, maskShape, feather, onTop, overlay, clipping, passthrough, passthroughScale, passthroughShiftX, passthroughShiftY, passthroughRotation, style, group, inherit)
81222
81612
 
81223
81613
  Perspective
81224
81614
  zim class extends ZIM Container which extends CreateJS Container
@@ -81357,8 +81747,9 @@ fade - (default true) if a plane is set, this will fade the distance
81357
81747
  dynamic - (default true unless obj is Pic, then false) set to false if a Container, etc. does not change or animate
81358
81748
  frame - (default zimDefaultFrame) set the zim Frame the perspective is in if in a different frame than the default frame
81359
81749
  resolution - (default 1) the resolution multiplier for the internal WebGL shader canvas (e.g. 0.5 for half-resolution to boost GPU performance on laptops and mobile).
81360
- maskShape - (default "rect") the cutout mask shape: "rect" (standard 4-corner quad), "circle" (1:1 true circle with aspect-ratio correction), or "ellipse" (stretches to the quad bounds)
81750
+ maskShape - (default eye) the cutout mask shape: "rect" (standard 4-corner quad), "circle" (1:1 true circle with aspect-ratio correction), or "ellipse" (stretches to the quad bounds)
81361
81751
  or a ZIM DisplayObject (such as a Blob, Circle, or Container) to use as a dynamic GPU alpha mask texture (passed into iChannel1 with multi-tap blur spread).
81752
+ or true to get a Blob in eye shape points radius min of width and height / 2
81362
81753
  feather - (default 0) edge softness from 0 to 1 applied to the quad edges, circle/ellipse borders, or texture mask in the GPU shader.
81363
81754
  onTop - (default true) set to false to not bring the object to the top of its container when pressed
81364
81755
  overlay - (default false) set to true to use a DOM ShaderOverlay (Tag) rather than an in-canvas Shader (Bitmap)
@@ -81372,6 +81763,7 @@ passthrough - (default false) set to true to display the asset unwarped (at orig
81372
81763
  passthroughScale - (default 1) scale multiplier for the asset when passthrough is true, scaling from the center of the asset.
81373
81764
  passthroughShiftX - (default 0) horizontal pixel offset for the asset beneath the crop window when passthrough is true.
81374
81765
  passthroughShiftY - (default 0) vertical pixel offset for the asset beneath the crop window when passthrough is true.
81766
+ passthroughRotation - (default 0) rotation for the asset beneath the crop window when passthrough is true.
81375
81767
  style - (default true) set to false to ignore styles set with the STYLE - will receive original parameter defaults
81376
81768
  group - (default null) set to String (or comma delimited String) so STYLE can set default styles to the group(s) (like a CSS class)
81377
81769
  inherit - (default null) used internally but can receive an {} of styles directly
@@ -81417,7 +81809,6 @@ toggled - get control state - true if showing and false if not - see toggle() me
81417
81809
  maskShape - get or set the cutout shape "rect", "circle", "ellipse", or the DisplayObject provided
81418
81810
  feather - get or set the edge softness / feathering amount (0 to 1).
81419
81811
  resolution - the resolution multiplier of the shader canvas.
81420
- mask - reference to the mask DisplayObject container if a mask was provided.
81421
81812
  onTop - get or set whether the perspective comes to the top of its container when pressed
81422
81813
  overlay - get or set whether to render as a DOM ShaderOverlay - see overlay parameter
81423
81814
  clipping - get or set whether the overlay canvas bounding box is clamped to the stage boundaries
@@ -81435,8 +81826,8 @@ x, y, rotation, scaleX, scaleY, regX, regY, skewX, skewY,
81435
81826
  alpha, cursor, shadow, name, mouseChildren, mouseEnabled, parent, numChildren, etc.
81436
81827
 
81437
81828
  --*///+69.9748
81438
- zim.Perspective = function(obj, points, interactive, showControls, allowToggle, move, borderColor, borderWidth, dashed, plane, fade, dynamic, frame, resolution, maskShape, feather, onTop, overlay, clipping, passthrough, passthroughScale, passthroughShiftX, passthroughShiftY, style, group, inherit) {
81439
- var sig = "obj, points, interactive, showControls, allowToggle, move, borderColor, borderWidth, dashed, plane, fade, dynamic, frame, resolution, maskShape, feather, onTop, overlay, clipping, passthrough, passthroughScale, passthroughShiftX, passthroughShiftY, style, group, inherit";
81829
+ zim.Perspective = function(obj, points, interactive, showControls, allowToggle, move, borderColor, borderWidth, dashed, plane, fade, dynamic, frame, resolution, maskShape, feather, onTop, overlay, clipping, passthrough, passthroughScale, passthroughShiftX, passthroughShiftY, passthroughRotation, style, group, inherit) {
81830
+ var sig = "obj, points, interactive, showControls, allowToggle, move, borderColor, borderWidth, dashed, plane, fade, dynamic, frame, resolution, maskShape, feather, onTop, overlay, clipping, passthrough, passthroughScale, passthroughShiftX, passthroughShiftY, passthroughRotation, style, group, inherit";
81440
81831
  var duo; if (duo = zob(zim.Perspective, arguments, sig, this)) return duo;
81441
81832
  z_d("69.9770");
81442
81833
  this.group = group;
@@ -81459,6 +81850,28 @@ zim.Perspective = function(obj, points, interactive, showControls, allowToggle,
81459
81850
  if (zot(frame)) frame = DS.frame != null ? DS.frame : zdf;
81460
81851
  if (zot(resolution)) resolution = DS.resolution != null ? DS.resolution : 1;
81461
81852
  if (zot(maskShape)) maskShape = DS.maskShape != null ? DS.maskShape : "rect";
81853
+ if (maskShape === true) {
81854
+ maskShape = new zim.Blob({
81855
+ points:[
81856
+ [0,90,0,0,-80,0,80,0,"straight"],
81857
+ [150,0,0,0,0,0,0,0,"none"],
81858
+ [0,-90,0,0,80,0,-80,0,"straight"],
81859
+ [-150,0,0,0,0,0,0,0,"none"]
81860
+ ],
81861
+ interactive: false
81862
+ })
81863
+ if (obj && obj.width) {
81864
+ var targetW = obj.width;
81865
+ var targetH = obj.height || 1;
81866
+ var factor = Math.min(targetW / maskShape.width, targetH / maskShape.height);
81867
+ maskShape.transformPoints("scale", factor);
81868
+ }
81869
+ } else if (maskShape && maskShape.type=="Blob" && maskShape.scale != 1) {
81870
+ var factor = maskShape.scale;
81871
+ maskShape.scale = 1;
81872
+ maskShape.transformPoints("scale", factor);
81873
+ }
81874
+ if (maskShape && maskShape.type=="Blob") maskShape.hideControls();
81462
81875
  if (zot(feather)) feather = DS.feather != null ? DS.feather : 0;
81463
81876
  if (zot(onTop)) onTop = DS.onTop != null ? DS.onTop : true;
81464
81877
  if (zot(overlay)) overlay = DS.overlay != null ? DS.overlay : false;
@@ -81467,6 +81880,7 @@ zim.Perspective = function(obj, points, interactive, showControls, allowToggle,
81467
81880
  if (zot(passthroughScale)) passthroughScale = DS.passthroughScale != null ? DS.passthroughScale : 1;
81468
81881
  if (zot(passthroughShiftX)) passthroughShiftX = DS.passthroughShiftX != null ? DS.passthroughShiftX : 0;
81469
81882
  if (zot(passthroughShiftY)) passthroughShiftY = DS.passthroughShiftY != null ? DS.passthroughShiftY : 0;
81883
+ if (zot(passthroughRotation)) passthroughRotation = DS.passthroughRotation != null ? DS.passthroughRotation : 0;
81470
81884
 
81471
81885
  var size = Math.max(frame.width, frame.height);
81472
81886
 
@@ -81484,7 +81898,7 @@ zim.Perspective = function(obj, points, interactive, showControls, allowToggle,
81484
81898
  var frag = "uniform vec2 a; uniform vec2 b; uniform vec2 c; uniform vec2 d;\n";
81485
81899
  frag += "uniform float plane;\nuniform float fade;\n";
81486
81900
  frag += "uniform float shape;\nuniform float feather;\nuniform float aspect;\nuniform float useMaskTex;\n";
81487
- frag += "uniform float passthrough;\nuniform float passthroughScale;\nuniform vec2 passthroughCenter;\nuniform vec2 passthroughShift;\nuniform vec2 assetNormSize;\n";
81901
+ frag += "uniform float passthrough;\nuniform float passthroughScale;\nuniform float passthroughRotation;\nuniform vec2 passthroughCenter;\nuniform vec2 passthroughShift;\nuniform vec2 assetNormSize;\n";
81488
81902
  frag += "#define map(screenpos) ((2.0*(screenpos)-iResolution.xy)/iResolution.y)\n";
81489
81903
  frag += "mat3 rproject(vec3 p0, vec3 p1, vec3 p2, vec3 p3) {\n";
81490
81904
  frag += " mat3 m = inverse(mat3(p0,p1,p2));\n";
@@ -81516,7 +81930,12 @@ zim.Perspective = function(obj, points, interactive, showControls, allowToggle,
81516
81930
  frag += " vec2 texUV;\n";
81517
81931
  frag += " if (passthrough > 0.5) {\n";
81518
81932
  frag += " vec2 pScale = max(vec2(0.0001), assetNormSize * passthroughScale);\n";
81519
- frag += " texUV = (p - passthroughCenter - passthroughShift) / pScale + vec2(0.5);\n";
81933
+ frag += " vec2 pDiff = p - passthroughCenter - passthroughShift;\n";
81934
+ frag += " float rad = radians(-passthroughRotation);\n";
81935
+ frag += " float cosA = cos(rad);\n";
81936
+ frag += " float sinA = sin(rad);\n";
81937
+ frag += " pDiff = mat2(cosA, -sinA, sinA, cosA) * pDiff;\n";
81938
+ frag += " texUV = pDiff / pScale + vec2(0.5);\n";
81520
81939
  frag += " if (texUV.x < 0.0 || texUV.x > 1.0 || texUV.y < 0.0 || texUV.y > 1.0) {\n";
81521
81940
  frag += " fragColor = vec4(0.0);\n";
81522
81941
  frag += " return;\n";
@@ -81525,9 +81944,13 @@ zim.Perspective = function(obj, points, interactive, showControls, allowToggle,
81525
81944
  frag += " texUV = tileUV;\n";
81526
81945
  frag += " }\n";
81527
81946
  frag += " vec4 texColor = texture(iChannel0, texUV);\n";
81947
+ frag += " float alpha = texColor.a;\n";
81948
+ frag += " if (alpha <= 0.0) {\n";
81949
+ frag += " fragColor = vec4(0.0);\n";
81950
+ frag += " return;\n";
81951
+ frag += " }\n";
81528
81952
  frag += " vec3 color = pow(texColor.rgb, vec3(2.2));\n";
81529
81953
  frag += " color = pow(color, vec3(0.4545));\n";
81530
- frag += " float alpha = texColor.a;\n";
81531
81954
  frag += " // 3. Circle / Ellipse Feather (applied to tileUV)\n";
81532
81955
  frag += " if (shape > 0.5) {\n";
81533
81956
  frag += " vec2 centerCoord = (tileUV - vec2(0.5)) * 2.0;\n";
@@ -81578,7 +82001,7 @@ zim.Perspective = function(obj, points, interactive, showControls, allowToggle,
81578
82001
  frag += " color *= distFade;\n";
81579
82002
  frag += " alpha *= distFade;\n";
81580
82003
  frag += " }\n";
81581
- frag += " fragColor = vec4(color, alpha);\n";
82004
+ frag += " fragColor = vec4(color * alpha, alpha);\n";
81582
82005
  frag += "}\n";
81583
82006
 
81584
82007
  this.zimContainer_constructor(size, size, null, null, false);
@@ -81594,6 +82017,7 @@ zim.Perspective = function(obj, points, interactive, showControls, allowToggle,
81594
82017
  var _maskShape = maskShape;
81595
82018
  var _passthrough = Boolean(passthrough);
81596
82019
  var _passthroughScale = Number(passthroughScale) || 1;
82020
+ var _passthroughRotation = Number(passthroughRotation) || 0;
81597
82021
  var _passthroughShiftX = Number(passthroughShiftX) || 0;
81598
82022
  var _passthroughShiftY = Number(passthroughShiftY) || 0;
81599
82023
  var _passthroughCenter = null;
@@ -81744,6 +82168,10 @@ zim.Perspective = function(obj, points, interactive, showControls, allowToggle,
81744
82168
  var objH = obj.height || (obj.getBounds ? (obj.getBounds() ? obj.getBounds().height : size) : size);
81745
82169
  var aspect = objW / objH;
81746
82170
 
82171
+ // FIX: Shift registration to the internal buffer center and set bounds to the actual object size
82172
+ that.reg(size/2, size/2);
82173
+ that.setBounds(size/2 - objW/2, size/2 - objH/2, objW, objH);
82174
+
81747
82175
  var shapeVal = 0;
81748
82176
  var useMaskTexVal = 0;
81749
82177
  if (typeof _maskShape === "string") {
@@ -81769,6 +82197,7 @@ zim.Perspective = function(obj, points, interactive, showControls, allowToggle,
81769
82197
  useMaskTex: useMaskTexVal,
81770
82198
  passthrough: _passthrough ? 1 : 0,
81771
82199
  passthroughScale: _passthroughScale,
82200
+ passthroughRotation: _passthroughRotation,
81772
82201
  passthroughCenter: [initCx, initCy],
81773
82202
  passthroughShift: [_passthroughShiftX / size, -_passthroughShiftY / size],
81774
82203
  assetNormSize: [objW / size, objH / size]
@@ -81936,6 +82365,9 @@ zim.Perspective = function(obj, points, interactive, showControls, allowToggle,
81936
82365
  uniforms.useMaskTex = 1;
81937
82366
  setupMaskSource(value);
81938
82367
  if (that.shader) that.shader.replaceChannel(maskContainer, 1, false);
82368
+ } else {
82369
+ uniforms.shape = 0;
82370
+ uniforms.useMaskTex = 0;
81939
82371
  }
81940
82372
  }
81941
82373
  });
@@ -81970,6 +82402,16 @@ zim.Perspective = function(obj, points, interactive, showControls, allowToggle,
81970
82402
  }
81971
82403
  });
81972
82404
 
82405
+ Object.defineProperty(this, "passthroughRotation", {
82406
+ get: function() {
82407
+ return _passthroughRotation;
82408
+ },
82409
+ set: function(value) {
82410
+ _passthroughRotation = Number(value) || 0;
82411
+ uniforms.passthroughRotation = _passthroughRotation;
82412
+ }
82413
+ });
82414
+
81973
82415
  Object.defineProperty(this, "passthroughShiftX", {
81974
82416
  get: function() {
81975
82417
  return _passthroughShiftX;
@@ -82296,7 +82738,7 @@ zim.Perspective = function(obj, points, interactive, showControls, allowToggle,
82296
82738
  if (style !== false) zim.styleTransforms(this, DS);
82297
82739
 
82298
82740
  this.clone = function(exact) {
82299
- return that.cloneProps(new zim.Perspective((exact || !zim.isPick(oa[0])) ? obj : oa[0], points, interactive, showControls, allowToggle, move, (exact || !zim.isPick(oa[1])) ? borderColor : oa[1], (exact || !zim.isPick(oa[2])) ? borderWidth : oa[2], (exact || !zim.isPick(oa[3])) ? dashed : oa[3], plane, fade, dynamic, frame, resolution, _maskShape, feather, _onTop, _overlay, _clipping, _passthrough, _passthroughScale, _passthroughShiftX, _passthroughShiftY, style, this.group, inherit));
82741
+ return that.cloneProps(new zim.Perspective((exact || !zim.isPick(oa[0])) ? obj : oa[0], points, interactive, showControls, allowToggle, move, (exact || !zim.isPick(oa[1])) ? borderColor : oa[1], (exact || !zim.isPick(oa[2])) ? borderWidth : oa[2], (exact || !zim.isPick(oa[3])) ? dashed : oa[3], plane, fade, dynamic, frame, resolution, _maskShape, feather, _onTop, _overlay, _clipping, _passthrough, _passthroughScale, _passthroughShiftX, _passthroughShiftY, _passthroughRotation, style, this.group, inherit));
82300
82742
  };
82301
82743
  };
82302
82744
  zim.extend(zim.Perspective, zim.Container, ["clone", "dispose"], "zimContainer", false);
@@ -93556,6 +93998,13 @@ zim.Frame = function(scaling, width, height, color, outerColor, ready, assets, p
93556
93998
  var imagesNoCORS = [];
93557
93999
  var mainCount = 0;
93558
94000
  var firstSoundCheck = true;
94001
+
94002
+ var videoRegex = /\.(mp4|mkv|webm|avi|mov|wmv|flv|m4v|3gp|ts)(?=[?#]|$)/i;
94003
+ function isVideoUrl(url) {
94004
+ if (url && url.src) url = url.src;
94005
+ if (typeof url !== 'string') return false;
94006
+ return videoRegex.test(url);
94007
+ }
93559
94008
 
93560
94009
  // 018 TRYING TO FIX CREATEJS ERROR IF SOUND LOADED AGAIN - BUT BREAKS LAZY LOAD
93561
94010
  // var emptyAssets = false;
@@ -93570,7 +94019,7 @@ zim.Frame = function(scaling, width, height, color, outerColor, ready, assets, p
93570
94019
  // // }
93571
94020
  // }
93572
94021
 
93573
-
94022
+
93574
94023
 
93575
94024
  var match;
93576
94025
  if (a.match) match = a.match(/^noto_(.*)/);
@@ -93586,6 +94035,7 @@ zim.Frame = function(scaling, width, height, color, outerColor, ready, assets, p
93586
94035
  for (j=0; j<a.assets.length; j++) {
93587
94036
  var aj = a.assets[j];
93588
94037
  match = null;
94038
+ if (isVideoUrl(aj)) continue;
93589
94039
  if (aj.match) match = aj.match(/^noto_(.*)/);
93590
94040
  if (match) {
93591
94041
  aj = {id:aj, src:"https://raw.githubusercontent.com/googlefonts/noto-emoji/main/svg/" + getNotoFilename(match[1])}
@@ -93619,7 +94069,8 @@ zim.Frame = function(scaling, width, height, color, outerColor, ready, assets, p
93619
94069
  }
93620
94070
  a = assetMulti[0];
93621
94071
  if (a==undefined) continue;
93622
- }
94072
+ }
94073
+ if (isVideoUrl(a))continue;
93623
94074
 
93624
94075
  // catch {src} without id and just replace it with src unless google font
93625
94076
  if (a.constructor == {}.constructor) {
@@ -94005,6 +94456,11 @@ zim.Frame = function(scaling, width, height, color, outerColor, ready, assets, p
94005
94456
  if (!queueOnly) that.dispatchEvent(completeEventObject);
94006
94457
  }, time*(timeType=="s"?1000:1));
94007
94458
  }
94459
+
94460
+ if (queue.loadAssetsCount == 0) {
94461
+ endAssetLoad();
94462
+ }
94463
+
94008
94464
  return queue;
94009
94465
  };
94010
94466
 
@@ -96366,11 +96822,12 @@ bitmap - (default true) this will show the SVG as a Bitmap (still nicely scalabl
96366
96822
  or set to false to use an SVGContainer() which converts the SVG to Blob, Squiggle and ZIM Shapes with transforms.
96367
96823
  This allows for editable paths - but perhaps not all aspects of SVGs are supported such as CSS styles on SVGs.
96368
96824
  Alternatively, a single SVG path can be passed to a ZIM Blob() or Squiggle() and SVG() can be avoided.
96825
+ Note: when color, borderColor, or order is set then bitmap is set to false
96369
96826
  ** these parameters are NOT for the bitmap option but rather for the SVGContainer
96370
96827
  splitTypes - (default false) - set to true to split different types of paths into separate objects
96371
96828
  geometric - (default true) - set to false to load Rectangle and Circle objects as Blob objects
96372
96829
  showControls - (default true) set to false to start with controls not showing
96373
- interactive - (default true) for bitmap:false, set to false to turn off controls, move, toggle, select, edit - leaving just the shapes
96830
+ interactive - (default true or false if bitmap or colored below) set to false to turn off controls, move, toggle, select, edit - leaving just the shapes
96374
96831
  color - |ZIM VEE| (default null) for bitmap:false, set to a color or an array of colors to apply to fill of children - in order of the children
96375
96832
  usually a series() would be used here for colors in order, or an array for random colors
96376
96833
  borderColor - |ZIM VEE| (default null) set to a color or an array of colors to apply to border of children - in order of the children
@@ -96437,10 +96894,16 @@ added, click, dblclick, mousedown, mouseout, mouseover, pressdown (ZIM), pressmo
96437
96894
  if (zot(splitTypes)) splitTypes = DS.splitTypes!=null?DS.splitTypes:false;
96438
96895
  if (zot(geometric)) geometric = DS.geometric!=null?DS.geometric:true;
96439
96896
  if (zot(showControls)) showControls = DS.showControls!=null?DS.showControls:null;
96440
- if (zot(interactive)) interactive = DS.interactive!=null?DS.interactive:true;
96441
96897
  if (zot(color)) color = DS.color!=null?DS.color:null;
96442
96898
  if (zot(borderColor)) borderColor = DS.borderColor!=null?DS.borderColor:null;
96443
96899
  if (zot(order)) order = DS.order!=null?DS.order:null;
96900
+
96901
+ var colored = false;
96902
+ if (color || borderColor || order) {
96903
+ bitmap = false;
96904
+ colored = true;
96905
+ }
96906
+ if (zot(interactive)) interactive = DS.interactive!=null?DS.interactive:!colored; // don't set to interactive if colored
96444
96907
 
96445
96908
  var setColors = [];
96446
96909
  var setBorderColors = [];
@@ -96871,277 +97334,606 @@ Note: there are more features to the Web Speech API - see the HTML docs
96871
97334
  //-83.095
96872
97335
 
96873
97336
  /*--
96874
- Hardware = function(callback, baudRate, delimiter)
97337
+ Hardware = function(preset, callback, baudRate, delimiter, mode, autoParse)
96875
97338
 
96876
97339
  Hardware
96877
97340
  zim class - extends a createjs.EventDispatcher
97341
+ Supports DUO - pass parameters positionally or as a single configuration object {preset: "microblocks", callback: ...}
96878
97342
 
96879
97343
  DESCRIPTION
96880
- Reads from and writes to a device such as an Ardiuno
96881
- Wraps the JS Web Serial API
96882
- https://developer.mozilla.org/en-US/docs/Web/API/Web_Serial_API
96883
- Coded primarily by Gemini AI
97344
+ Connects interactive ZIM canvas applications directly to physical microcontrollers, sensors, dials, and actuators.
97345
+ Communicates via standard USB cables using the JS Web Serial API or wirelessly via the JS Web Bluetooth (BLE UART) API.
97346
+ Works with MicroBlocks, BBC micro:bit, Raspberry Pi Pico / RP2040, Arduino, Adafruit Circuit Playground / Feather (CircuitPython / MicroPython), ESP32, and LEGO SPIKE Prime.
97347
+ Automatically parses incoming data into JavaScript numbers, key-value pairs, or JSON objects.
96884
97348
 
96885
97349
  See: https://zimjs.com/hardware/
96886
97350
 
96887
- NOTE: as of ZIM 5.5.0 the zim namespace is no longer required (unless zns is set to true before running zim)
97351
+ NOTE: Browser security requires that hardware.connect() be called inside a user gesture (such as a Button tap or Pane close). Supported in Chromium-based browsers (Chrome, Edge, Opera) on desktop, Android, and Chromebooks.
96888
97352
 
96889
97353
  EXAMPLE
96890
- // USING ZIM as INPUT to ARDUINO
96891
-
96892
- // would need an arduino connected
96893
- // with the following .ino file loaded onto it
96894
-
96895
- // ------------------------
96896
- // ---- Arduinin Code -----
97354
+ // -------------------------------------------------------
97355
+ // ---- EXAMPLE 1: ARDUINO (Bidirectional LED & Dial) ----
97356
+ // -------------------------------------------------------
97357
+ // On Arduino Uno/Nano: connect a potentiometer to A0 and built-in LED on Pin 13
97358
+ // Upload the following Arduino C++ code:
96897
97359
 
96898
- const int LED_PIN = 13; // Built-in LED on your Uno R3 board
97360
+ const int potPin = A0;
97361
+ const int ledPin = 13;
96899
97362
  void setup() {
96900
- Serial.begin(9600); // Initialize background channel at matching speed
96901
- pinMode(LED_PIN, OUTPUT); // Configure Pin 13 as an output light source
97363
+ Serial.begin(9600);
97364
+ pinMode(ledPin, OUTPUT);
96902
97365
  }
96903
97366
  void loop() {
96904
- // Check if ZIM has sent a data character over the USB cable
97367
+ // Read Potentiometer and send to ZIM
97368
+ int potValue = analogRead(potPin);
97369
+ Serial.println(potValue); // sends number + \n
97370
+
97371
+ // Receive LED command from ZIM
96905
97372
  if (Serial.available() > 0) {
96906
- char incomingChar = Serial.read(); // Read the character data fragment
96907
- if (incomingChar == '1') {
96908
- digitalWrite(LED_PIN, HIGH); // Turn physical light ON
96909
- }
96910
- else if (incomingChar == '0') {
96911
- digitalWrite(LED_PIN, LOW); // Turn physical light OFF
96912
- }
97373
+ char c = Serial.read();
97374
+ if (c == '1') digitalWrite(ledPin, HIGH);
97375
+ else if (c == '0') digitalWrite(ledPin, LOW);
96913
97376
  }
97377
+ delay(30);
96914
97378
  }
96915
97379
 
96916
- // -------------------
96917
- // ---- ZIM Code -----
97380
+ // ZIM Front-End Code:
97381
+ // Preset "arduino" sets baud rate to 9600 automatically
97382
+ const device = new Hardware("arduino", data => {
97383
+ dial.currentValue = data; // data automatically parsed as Number (0-1023)
97384
+ S.update();
97385
+ });
96918
97386
 
96919
- const device = new Hardware();
97387
+ const dial = new Dial({min: 0, max: 1023}).center();
96920
97388
 
96921
- const toggle = new Toggle({
96922
- label: "LED POWER",
96923
- toggleBackgroundColor: green,
96924
- color: white
96925
- })
96926
- .center()
97389
+ const toggle = new Toggle({label: "ARDUINO LED", toggleBackgroundColor: green})
97390
+ .pos(0, 100, CENTER, BOTTOM)
96927
97391
  .change(() => {
96928
- if (toggle.toggled) {
96929
- device.write("1"); // note a \n will be automatically added if missing
96930
- } else {
96931
- device.write("0");
96932
- }
97392
+ device.write(toggle.toggled ? "1" : "0");
96933
97393
  });
96934
97394
 
96935
- // must interact to connect
96936
- const pane = new Pane("CONNECT", yellow).show(() => {
96937
- device.connect()
96938
- });
96939
-
96940
- device.on("disconnected", () => {
96941
- pane.show(() => { // remember to add the callback again
97395
+ // Connect on user interaction
97396
+ new Button({label: "CONNECT", width:AUTO})
97397
+ .pos(30, 30, RIGHT, TOP)
97398
+ .tap(e => {
96942
97399
  device.connect();
97400
+ e.target.removeFrom();
97401
+ S.update();
96943
97402
  });
97403
+ END EXAMPLE
97404
+
97405
+ EXAMPLE
97406
+ // ----------------------------------------------------
97407
+ // ---- EXAMPLE 2: MICROBLOCKS (Live Block Scripting) -
97408
+ // ----------------------------------------------------
97409
+ // MicroBlocks (https://microblocks.fun) runs live on micro:bit, ESP32, RP2040, etc.
97410
+ // In MicroBlocks, use the Serial library blocks:
97411
+ // [when started]
97412
+ // [forever]
97413
+ // [serial write line (join "pot:" (analog pin 1))]
97414
+ // [wait 50 ms]
97415
+ // [when serial received]
97416
+ // [if ((last serial line) == "beep")]
97417
+ // [play tone 440 for 200 ms]
97418
+
97419
+ // ZIM Front-End Code:
97420
+ // Default preset is "microblocks" (115200 baud)
97421
+ const hw = new Hardware("microblocks", data => {
97422
+ // Key-value pairs like "pot:512" automatically parse into hw.data.pot or hw.get("pot")
97423
+ circle.sca(map(hw.get("pot"), 0, 1023, 0.5, 3));
97424
+ S.update();
97425
+ });
97426
+
97427
+ const circle = new Circle(60, orange).center();
97428
+
97429
+ circle.tap(() => {
97430
+ hw.write("beep"); // Triggers tone on the microcontroller
97431
+ });
97432
+
97433
+ new Button({label: "CONNECT", width:AUTO}).pos(30, 30, RIGHT, TOP).tap(e => {
97434
+ hw.connect();
97435
+ e.target.removeFrom();
97436
+ S.update();
96944
97437
  });
96945
97438
  END EXAMPLE
96946
97439
 
96947
97440
  EXAMPLE
96948
- // USING ZIM AS OUTPUT FROM ARDUINO
96949
- See: https://zimjs.com/hardware/
97441
+ // ----------------------------------------------------
97442
+ // ---- EXAMPLE 3: BBC MICRO:BIT (MakeCode Serial) ----
97443
+ // ----------------------------------------------------
97444
+ // In MakeCode (https://makecode.microbit.org):
97445
+ // basic.forever(function () {
97446
+ // serial.writeLine("x:" + input.acceleration(Dimension.X))
97447
+ // basic.pause(50)
97448
+ // })
97449
+ // serial.onDataReceived(serial.delimiters(Delimiters.NewLine), function () {
97450
+ // let msg = serial.readString()
97451
+ // if (msg.includes("icon")) basic.showIcon(IconNames.Heart)
97452
+ // })
97453
+
97454
+ // ZIM Front-End Code:
97455
+ const mb = new Hardware("microbit");
97456
+ const pd = new ProportionDamp(-1023, 1023, 0, W);
97457
+ mb.on("data", e => {
97458
+ if (e.key === "x") {
97459
+ character.x = pd.convert(e.val);
97460
+ S.update();
97461
+ }
97462
+ });
96950
97463
 
96951
- // would need an arduino connected
96952
- // with a potentiometer (pot) dial
96953
- // (probably on a breadboard)
96954
- // with outside pins goint to ground and 5V
96955
- // and middle pin goint to analogue A0
96956
- // with the following .ino file loaded onto it
97464
+ const character = new Rectangle(60, 60, purple).centerReg();
96957
97465
 
96958
- // ------------------------
96959
- // ---- Arduinin Code -----
97466
+ character.tap(() => {
97467
+ mb.write("icon");
97468
+ });
97469
+
97470
+ new Button({label: "CONNECT MICRO:BIT", width:AUTO}).center().tap(e => {
97471
+ mb.connect();
97472
+ e.target.removeFrom();
97473
+ S.update();
97474
+ });
97475
+ END EXAMPLE
97476
+
97477
+ EXAMPLE
97478
+ // ----------------------------------------------------
97479
+ // ---- EXAMPLE 4: CIRCUITPYTHON / MICROPYTHON (PICO) -
97480
+ // ----------------------------------------------------
97481
+ // On Raspberry Pi Pico / Adafruit boards running CircuitPython or MicroPython (code.py):
97482
+
97483
+ import time
97484
+ import json
97485
+ import board
97486
+ import analogio
97487
+
97488
+ pot = analogio.AnalogIn(board.A0)
97489
+ while True:
97490
+ # Send JSON packet
97491
+ data = {"pot": pot.value, "time": time.monotonic()}
97492
+ print(json.dumps(data))
97493
+ time.sleep(0.05)
96960
97494
 
96961
- const int potPin = A0;
96962
- void setup() {
96963
- Serial.begin(9600);
96964
- }
96965
- void loop() {
96966
- int potValue = analogRead(potPin);
96967
- Serial.println(potValue);
96968
- delay(20);
96969
- }
96970
97495
 
96971
- // -------------------
96972
- // ---- ZIM Code -----
97496
+ // ZIM Front-End Code:
97497
+ const pico = new Hardware("pico", data => {
97498
+ // JSON is automatically parsed into a native object:
97499
+ slider.currentValue = data.pot;
97500
+ S.update();
97501
+ });
96973
97502
 
96974
- const device = new Hardware(data => {
96975
- zog(data); // gives values from 0-1023
97503
+ const slider = new Slider({min: 0, max: 65535}).center();
97504
+
97505
+ new Button({label: "CONNECT PICO", width:AUTO}).center().tap(e => {
97506
+ pico.connect();
97507
+ e.target.removeFrom();
97508
+ S.update();
96976
97509
  });
97510
+ END EXAMPLE
96977
97511
 
96978
- // must interact to connect
96979
- const pane = new Pane("CONNECT", yellow).show(()=>{
96980
- device.connect();
97512
+ EXAMPLE
97513
+ // ----------------------------------------------------
97514
+ // ---- EXAMPLE 5: WIRELESS BLUETOOTH (BLE UART) ------
97515
+ // ----------------------------------------------------
97516
+ // Connects wirelessly to BBC micro:bit, ESP32, or Adafruit Bluefruit boards advertising Nordic UART Service
97517
+ const wireless = new Hardware("ble", data => {
97518
+ label.text = "Received: " + data;
97519
+ S.update();
96981
97520
  });
96982
97521
 
96983
- device.on("disconnected", () => {
96984
- pane.show(()=>{ // remember to add the callback again
96985
- device.connect();
96986
- });
97522
+ const label = new Label("Waiting for BLE...").center();
97523
+
97524
+ new Button({label: "CONNECT BLUETOOTH", width:AUTO}).pos(0, 100, CENTER, BOTTOM).tap(e => {
97525
+ wireless.connect();
97526
+ e.target.removeFrom();
97527
+ S.update();
96987
97528
  });
96988
- END EXAMPLE
97529
+ END EXAMPLE
96989
97530
 
96990
- PARAMETERS
96991
- callback - the function to call when data is received and will receive the data in its single parameter
96992
- baudRate - (default 9600) Port speed matching the microcontroller
96993
- delimiter - (default \n) Character used to split data lines
97531
+ PARAMETERS
97532
+ ** supports DUO - parameters or single object with properties below
97533
+ preset - (default "arduino") target hardware profile ("arduino", "microblocks", "microbit", "pico", "circuitpython", "micropython", "lego", "spike", "ble", "bluetooth")
97534
+ Note: if a function is passed as the first parameter, it is treated as the callback. If a number is passed, it is treated as baudRate.
97535
+ callback - (default null) function called when data arrives: callback(data, raw, event)
97536
+ baudRate - (default 9600 for "arduino", 115200 for modern platforms) serial transmission speed
97537
+ delimiter - (default "\n") character or regex string used to split incoming messages and appended to outgoing writes
97538
+ mode - (default "serial") transport layer: "serial" (Web Serial via USB) or "bluetooth" (Web Bluetooth BLE UART)
97539
+ autoParse - (default true) automatically parses incoming lines into Numbers, Key-Value objects, or JSON objects
96994
97540
 
96995
97541
  METHODS
96996
- connect() - must interact before connecting - will receive a connected event
96997
- write(data) - write to the device - also see the data event for reading from the device
96998
- disconnect() - disconnect from the device
97542
+ connect() - triggers browser connection dialog to choose device.
97543
+ Must be called inside user interaction
97544
+ For example, use a ZIM Pane().show(()={// put connect here})
97545
+ Dispatches "connected" event. Returns this for chaining.
97546
+ write(data) - sends string or number to hardware. Automatically appends delimiter if missing. Returns this for chaining.
97547
+ disconnect() - cleanly shuts down streams and closes connection. Dispatches "disconnected" event. Returns this for chaining.
97548
+ get(name) - returns latest value received for a given key name (e.g. hw.get("pot") or hw.get("x")).
96999
97549
 
97000
97550
  PROPERTIES
97001
- type - holds the class name as a String
97002
- port - readonly the port the hardware is connected on
97003
- reader - readonly the textDecoder.readable.getReader()
97004
- writer - readonly the textEncoder.writable.getWriter()
97005
- textDecoder - readonly the JavaScript TextEncoderStream()
97006
- textEncoder - readonly the JavaScript TextEncoderStream()
97007
- isListening - whether actively reading on port
97008
- baudRate - readonly baud rate - see parameter
97009
- delimiter - end character for outgoing data
97551
+ type - holds the class name as a String ("Hardware")
97552
+ preset - the hardware profile name in use
97553
+ callback - the function assigned to receive incoming data
97554
+ baudRate - the baud rate in use
97555
+ delimiter - the delimiter string
97556
+ mode - "serial" or "bluetooth"
97557
+ autoParse - boolean indicating if auto-parsing is active
97558
+ connected - boolean indicating if currently connected to hardware
97559
+ isListening - boolean indicating if reading loop is active
97560
+ data - object holding latest key-value state received
97561
+ raw - String of the most recent raw unparsed line
97562
+ value - most recent parsed value (Number, Object, or String)
97563
+ port - readonly Web Serial port instance (serial mode)
97564
+ reader - readonly stream reader instance (serial mode)
97565
+ writer - readonly stream writer instance (serial mode)
97566
+ bleDevice - readonly Web Bluetooth device instance (bluetooth mode)
97010
97567
 
97011
97568
  EVENTS
97012
- dispatches "connected" when connected
97013
- dispatches "disconnected" when disconnected
97569
+ dispatches "connected" when hardware connection opens successfully
97570
+ dispatches "disconnected" when hardware is manually disconnected or cable is pulled
97571
+ dispatches "data" when a line/packet of data is received. Event object contains:
97572
+ e.data (or e.value) - parsed data (Number, Object, String)
97573
+ e.raw - original raw line String
97574
+ e.key - key String if received as key:value or key=value
97575
+ e.val - value if received as key:value or key=value
97576
+ dispatches "error" if connection or transmission fails
97014
97577
  --*///+83.097
97015
- zim.Hardware = function(callback, baudRate, delimiter) {
97016
- z_d("83.097");
97017
- this.cjsEventDispatcher_constructor();
97018
- this.type = "Hardware";
97019
- var that = this;
97578
+ zim.Hardware = function(preset, callback, baudRate, delimiter, mode, autoParse) {
97579
+ var sig = "preset, callback, baudRate, delimiter, mode, autoParse";
97580
+ var duo; if (duo = zot(arguments, sig, this)) return duo;
97020
97581
 
97021
- // MONITOR
97022
- this.mID = "z~"+this.type;
97582
+ this.cjsEventDispatcher_constructor();
97583
+ this.type = "Hardware";
97584
+ var that = this;
97023
97585
 
97024
- // Core Properties
97025
- that.baudRate = baudRate || 9600;
97026
- that.delimiter = delimiter || "\n";
97027
-
97028
- that.port = null
97029
- that.reader = null
97030
- that.writer = null
97031
- that.textDecoder = null
97032
- that.isListening = false
97033
-
97034
- // GLOBAL HARDWARE DISCONNECT MONITOR
97035
- if (navigator.serial) {
97036
- navigator.serial.addEventListener("disconnect", function (event) {
97037
- if (event.target === that.port) {
97038
- zogy("ZIM Harware - hardware physically disconnected!")
97039
- that.isListening = false;
97586
+ // MONITOR
97587
+ this.mID = "z~" + this.type;
97588
+
97589
+ // Backward Compatibility & Flexible Parameter Detection
97590
+ if (typeof preset === "function") {
97591
+ callback = preset;
97592
+ preset = "arduino";
97593
+ } else if (typeof preset === "number") {
97594
+ baudRate = preset;
97595
+ preset = "custom";
97596
+ }
97597
+ if (typeof callback === "number") {
97598
+ baudRate = callback;
97599
+ callback = null;
97600
+ }
97040
97601
 
97041
- // Clean break: release locks and clear active streams
97042
- if (that.reader) {
97043
- try { that.reader.releaseLock() } catch (e) { }
97044
- that.reader = null
97045
- }
97046
- if (that.writer) {
97047
- try { that.writer.releaseLock() } catch (e) { }
97048
- that.writer = null
97049
- }
97050
- that.port = null;
97051
- that.dispatchEvent("disconnected");
97052
- }
97602
+ // Platform Presets (Default baud rates & transport modes)
97603
+ var presets = {
97604
+ "microblocks": { baudRate: 115200, delimiter: "\n", mode: "serial" },
97605
+ "microbit": { baudRate: 115200, delimiter: "\n", mode: "serial" },
97606
+ "pico": { baudRate: 115200, delimiter: "\n", mode: "serial" },
97607
+ "circuitpython": { baudRate: 115200, delimiter: "\n", mode: "serial" },
97608
+ "micropython": { baudRate: 115200, delimiter: "\n", mode: "serial" },
97609
+ "lego": { baudRate: 115200, delimiter: "\n", mode: "serial" },
97610
+ "spike": { baudRate: 115200, delimiter: "\n", mode: "serial" },
97611
+ "arduino": { baudRate: 9600, delimiter: "\n", mode: "serial" },
97612
+ "ble": { baudRate: null, delimiter: "\n", mode: "bluetooth" },
97613
+ "bluetooth": { baudRate: null, delimiter: "\n", mode: "bluetooth" }
97614
+ };
97615
+
97616
+ var pKey = preset ? String(preset).toLowerCase() : "arduino";
97617
+ var profile = presets[pKey] || presets["arduino"];
97618
+
97619
+ // Core Properties
97620
+ that.preset = preset || "arduino";
97621
+ that.callback = callback;
97622
+ that.baudRate = baudRate || profile.baudRate || 115200;
97623
+ that.delimiter = delimiter != null ? delimiter : profile.delimiter;
97624
+ that.mode = (mode || profile.mode || "serial").toLowerCase();
97625
+ that.autoParse = autoParse != null ? autoParse : true;
97626
+
97627
+ // Connection State
97628
+ that.port = null;
97629
+ that.reader = null;
97630
+ that.writer = null;
97631
+ that.textDecoder = null;
97632
+ that.isListening = false;
97633
+ that.connected = false;
97634
+
97635
+ // Bluetooth Properties
97636
+ that.bleDevice = null;
97637
+ that.bleServer = null;
97638
+ that.bleRxCharacteristic = null; // write
97639
+ that.bleTxCharacteristic = null; // notify/read
97640
+
97641
+ // Data Cache
97642
+ that.data = {};
97643
+ that.raw = "";
97644
+ that.value = null;
97645
+ that._buffer = "";
97646
+
97647
+ // Universal Nordic UART Service UUIDs (micro:bit, Adafruit Bluefruit, ESP32, Arduino BLE)
97648
+ var UART_SERVICE_UUID = "6e400001-b5a3-f393-e0a9-e50e24dcca9e";
97649
+ var UART_RX_UUID = "6e400002-b5a3-f393-e0a9-e50e24dcca9e";
97650
+ var UART_TX_UUID = "6e400003-b5a3-f393-e0a9-e50e24dcca9e";
97651
+
97652
+ // GLOBAL HARDWARE DISCONNECT MONITOR (Web Serial)
97653
+ if (navigator.serial) {
97654
+ navigator.serial.addEventListener("disconnect", function(event) {
97655
+ if (event.target === that.port) {
97656
+ zogy("ZIM Hardware: serial device physically disconnected");
97657
+ that._handleDisconnect();
97658
+ }
97659
+ });
97660
+ }
97661
+
97662
+ // CONNECT METHOD (Web Serial or Web Bluetooth)
97663
+ that.connect = function() {
97664
+ if (that.mode === "bluetooth" || that.mode === "ble") {
97665
+ that._connectBluetooth();
97666
+ } else {
97667
+ that._connectSerial();
97668
+ }
97669
+ return that;
97670
+ };
97671
+
97672
+ // SERIAL CONNECTION
97673
+ that._connectSerial = function() {
97674
+ if (!navigator.serial) {
97675
+ console.error("ZIM Hardware: Web Serial API is not supported in this browser. Please use Chrome, Edge, or Opera.");
97676
+ that.dispatchEvent({ type: "error", error: "Web Serial not supported" });
97677
+ return;
97678
+ }
97679
+
97680
+ navigator.serial.requestPort()
97681
+ .then(function(selectedPort) {
97682
+ that.port = selectedPort;
97683
+ return that.port.open({ baudRate: that.baudRate });
97684
+ })
97685
+ .then(function() {
97686
+ zogy("ZIM Hardware: serial connected (" + that.preset + " @ " + that.baudRate + " baud)");
97687
+ that.isListening = true;
97688
+ that.connected = true;
97689
+ that._buffer = "";
97690
+
97691
+ // Output Stream (Browser to Microcontroller)
97692
+ var textEncoder = new TextEncoderStream();
97693
+ textEncoder.readable.pipeTo(that.port.writable);
97694
+ that.writer = textEncoder.writable.getWriter();
97695
+
97696
+ // Input Stream (Microcontroller to Browser)
97697
+ that.textDecoder = new TextDecoderStream();
97698
+ that.port.readable.pipeTo(that.textDecoder.writable);
97699
+ that.reader = that.textDecoder.readable.getReader();
97700
+
97701
+ // Start background reader
97702
+ that._readLoop();
97703
+ that.dispatchEvent("connected");
97704
+ })
97705
+ .catch(function(error) {
97706
+ console.error("ZIM Hardware: serial connection failed:", error);
97707
+ that.dispatchEvent({ type: "error", error: error });
97053
97708
  });
97709
+ };
97710
+
97711
+ // BLUETOOTH (BLE UART) CONNECTION
97712
+ that._connectBluetooth = function() {
97713
+ if (!navigator.bluetooth) {
97714
+ console.error("ZIM Hardware: Web Bluetooth API is not supported in this browser. Please use Chrome or Edge.");
97715
+ that.dispatchEvent({ type: "error", error: "Web Bluetooth not supported" });
97716
+ return;
97054
97717
  }
97055
97718
 
97056
- // CONNECT METHOD
97057
- that.connect = function () {
97058
- if (!navigator.serial) {
97059
- console.error("Web Serial API is not supported in this browser. Please use Chrome or Edge.")
97060
- return;
97061
- }
97719
+ navigator.bluetooth.requestDevice({
97720
+ filters: [
97721
+ { services: [UART_SERVICE_UUID] },
97722
+ { namePrefix: "BBC micro:bit" },
97723
+ { namePrefix: "micro:bit" },
97724
+ { namePrefix: "MicroBlocks" },
97725
+ { namePrefix: "CircuitPlayground" }
97726
+ ],
97727
+ optionalServices: [UART_SERVICE_UUID, 0xffe0]
97728
+ })
97729
+ .then(function(device) {
97730
+ that.bleDevice = device;
97731
+ that.bleDevice.addEventListener("gattserverdisconnected", function() {
97732
+ zogy("ZIM Hardware: Bluetooth device disconnected");
97733
+ that._handleDisconnect();
97734
+ });
97735
+ return device.gatt.connect();
97736
+ })
97737
+ .then(function(server) {
97738
+ that.bleServer = server;
97739
+ return server.getPrimaryService(UART_SERVICE_UUID);
97740
+ })
97741
+ .then(function(service) {
97742
+ return Promise.all([
97743
+ service.getCharacteristic(UART_RX_UUID),
97744
+ service.getCharacteristic(UART_TX_UUID)
97745
+ ]);
97746
+ })
97747
+ .then(function(characteristics) {
97748
+ that.bleRxCharacteristic = characteristics[0];
97749
+ that.bleTxCharacteristic = characteristics[1];
97750
+ return that.bleTxCharacteristic.startNotifications();
97751
+ })
97752
+ .then(function() {
97753
+ that.bleTxCharacteristic.addEventListener("characteristicvaluechanged", function(event) {
97754
+ var decoder = new TextDecoder();
97755
+ var chunk = decoder.decode(event.target.value);
97756
+ that._processChunk(chunk);
97757
+ });
97062
97758
 
97063
- navigator.serial.requestPort()
97064
- .then(function (selectedPort) {
97065
- that.port = selectedPort;
97066
- return that.port.open({ baudRate: that.baudRate });s
97067
- })
97068
- .then(function () {
97069
- zogy("ZIM Hardware - connected successfully!")
97070
- that.isListening = true
97071
-
97072
- // Set up Output Stream (Browser to Microcontroller)
97073
- var textEncoder = new TextEncoderStream()
97074
- textEncoder.readable.pipeTo(that.port.writable)
97075
- that.writer = textEncoder.writable.getWriter()
97076
-
97077
- // Set up Input Stream (Microcontroller to Browser)
97078
- that.textDecoder = new TextDecoderStream()
97079
- that.port.readable.pipeTo(that.textDecoder.writable)
97080
- that.reader = that.textDecoder.readable.getReader()
97081
-
97082
- // Start recursive background reader
97083
- that._readLoop("");
97084
- that.dispatchEvent("connected");
97085
- })
97086
- .catch(function (error) {
97087
- console.error("Hardware connection failed:", error)
97088
- })
97759
+ zogy("ZIM Hardware: Bluetooth connected successfully");
97760
+ that.isListening = true;
97761
+ that.connected = true;
97762
+ that._buffer = "";
97763
+ that.dispatchEvent("connected");
97764
+ })
97765
+ .catch(function(error) {
97766
+ console.error("ZIM Hardware: Bluetooth connection failed:", error);
97767
+ that.dispatchEvent({ type: "error", error: error });
97768
+ });
97769
+ };
97770
+
97771
+ // TRANSMIT DATA METHOD (Hardware.write)
97772
+ that.write = function(data) {
97773
+ var message = String(data);
97774
+ if (typeof that.delimiter === "string" && !message.endsWith(that.delimiter)) {
97775
+ message += that.delimiter;
97089
97776
  }
97090
97777
 
97091
- that.write = function (data) {
97092
- if (that.writer && that.isListening) {
97093
- var message = String(data);
97094
- if (message.charAt(str.length - 1) != that.delimeter) {
97095
- message += that.delimiter;
97096
- }
97097
- that.writer.write(message)
97098
- .catch(function (error) {
97099
- console.error("Failed to transmit data to hardware:", error);
97100
- })
97101
- } else {
97102
- console.warn("Cannot write data: Hardware is not connected.");
97103
- }
97778
+ if (that.mode === "bluetooth" && that.bleRxCharacteristic) {
97779
+ var encoder = new TextEncoder();
97780
+ that.bleRxCharacteristic.writeValue(encoder.encode(message))
97781
+ .catch(function(error) {
97782
+ console.error("ZIM Hardware: failed to transmit BLE data:", error);
97783
+ });
97784
+ } else if (that.writer && that.isListening) {
97785
+ that.writer.write(message)
97786
+ .catch(function(error) {
97787
+ console.error("ZIM Hardware: failed to transmit serial data:", error);
97788
+ });
97789
+ } else {
97790
+ console.warn("ZIM Hardware: cannot write data, device is not connected.");
97104
97791
  }
97792
+ return that;
97793
+ };
97794
+
97795
+ // DISCONNECT METHOD (Manual Shutdown)
97796
+ that.disconnect = function() {
97797
+ that.isListening = false;
97798
+ that.connected = false;
97105
97799
 
97106
- // DISCONNECT METHOD (Manual Shutdown)
97107
- that.disconnect = function () {
97108
- that.isListening = false
97109
- if (that.reader && that.writer) {
97110
- that.reader.cancel()
97111
- .then(function () { return that.writer.close() })
97112
- .then(function () { if (that.port) return that.port.close() })
97113
- .then(function () { zogy("ZIM Hardware: cleanly disconnected.") })
97114
- .catch(function (err) { console.error("Error during disconnect process:", err) })
97800
+ if (that.mode === "bluetooth" && that.bleDevice) {
97801
+ if (that.bleDevice.gatt && that.bleDevice.gatt.connected) {
97802
+ that.bleDevice.gatt.disconnect();
97115
97803
  }
97804
+ that._handleDisconnect();
97805
+ return that;
97806
+ }
97807
+
97808
+ if (that.reader && that.writer) {
97809
+ that.reader.cancel()
97810
+ .then(function() { return that.writer.close(); })
97811
+ .then(function() { if (that.port) return that.port.close(); })
97812
+ .then(function() {
97813
+ that._handleDisconnect();
97814
+ zogy("ZIM Hardware: cleanly disconnected");
97815
+ })
97816
+ .catch(function(err) {
97817
+ console.error("ZIM Hardware: error during disconnect:", err);
97818
+ that._handleDisconnect();
97819
+ });
97116
97820
  }
97821
+ return that;
97822
+ };
97117
97823
 
97118
- // INTERNAL RECURSIVE LOOP METHOD
97119
- that._readLoop = function (buffer) {
97120
- if (!that.isListening) return;
97824
+ // DISCONNECT CLEANUP HELPER
97825
+ that._handleDisconnect = function() {
97826
+ that.isListening = false;
97827
+ that.connected = false;
97121
97828
 
97122
- that.reader.read()
97123
- .then(function (result) {
97124
- if (result.done) {
97125
- that.isListening = false;
97126
- return;
97829
+ if (that.reader) {
97830
+ try { that.reader.releaseLock(); } catch (e) {}
97831
+ that.reader = null;
97832
+ }
97833
+ if (that.writer) {
97834
+ try { that.writer.releaseLock(); } catch (e) {}
97835
+ that.writer = null;
97836
+ }
97837
+ that.port = null;
97838
+ that.bleDevice = null;
97839
+ that.bleServer = null;
97840
+ that.bleRxCharacteristic = null;
97841
+ that.bleTxCharacteristic = null;
97842
+ that._buffer = "";
97843
+
97844
+ that.dispatchEvent("disconnected");
97845
+ };
97846
+
97847
+ // SERIAL RECURSIVE STREAM READER
97848
+ that._readLoop = function() {
97849
+ if (!that.isListening || !that.reader) return;
97850
+
97851
+ that.reader.read()
97852
+ .then(function(result) {
97853
+ if (result.done) {
97854
+ that.isListening = false;
97855
+ return;
97856
+ }
97857
+ that._processChunk(result.value);
97858
+ that._readLoop();
97859
+ })
97860
+ .catch(function(error) {
97861
+ that.isListening = false;
97862
+ });
97863
+ };
97864
+
97865
+ // PARSING & DISPATCH PIPELINE
97866
+ that._processChunk = function(chunk) {
97867
+ that._buffer += chunk;
97868
+ var delimiterPattern = that.delimiter === "\n" ? /\r?\n/ : (that.delimiter instanceof RegExp ? that.delimiter : new RegExp(that.delimiter));
97869
+ var lines = that._buffer.split(delimiterPattern);
97870
+ that._buffer = lines.pop(); // Retain incomplete tail segment
97871
+
97872
+ for (var i = 0; i < lines.length; i++) {
97873
+ var cleanLine = lines[i].trim();
97874
+ if (cleanLine.length === 0) continue;
97875
+
97876
+ that.raw = cleanLine;
97877
+ var parsed = cleanLine;
97878
+ var key = null;
97879
+ var val = null;
97880
+
97881
+ if (that.autoParse) {
97882
+ // 1. JSON Payload Parsing
97883
+ if ((cleanLine.charAt(0) === "{" && cleanLine.charAt(cleanLine.length - 1) === "}") ||
97884
+ (cleanLine.charAt(0) === "[" && cleanLine.charAt(cleanLine.length - 1) === "]")) {
97885
+ try {
97886
+ parsed = JSON.parse(cleanLine);
97887
+ if (typeof parsed === "object" && parsed !== null && !Array.isArray(parsed)) {
97888
+ for (var k in parsed) {
97889
+ that.data[k] = parsed[k];
97890
+ }
97891
+ }
97892
+ } catch (e) {
97893
+ parsed = cleanLine;
97127
97894
  }
97128
- buffer += result.value;
97129
- var delimiterPattern = that.delimiter === "\n" ? /\r?\n/ : new RegExp(that.delimiter)
97130
- var lines = buffer.split(delimiterPattern)
97131
- buffer = lines.pop()
97132
- for (var i = 0; i < lines.length; i++) {
97133
- var cleanLine = lines[i].trim()
97134
- if (cleanLine.length > 0 && typeof callback === "function") (callback)(cleanLine);
97895
+ }
97896
+ // 2. Pure Numeric String Parsing
97897
+ else if (!isNaN(cleanLine)) {
97898
+ parsed = Number(cleanLine);
97899
+ }
97900
+ // 3. Key-Value Formatting (e.g. "pot:512", "x=10", "btn: 1")
97901
+ else {
97902
+ var match = cleanLine.match(/^([a-zA-Z0-9_.-]+)\s*[:=]\s*(.*)$/);
97903
+ if (match) {
97904
+ key = match[1];
97905
+ var rawVal = match[2].trim();
97906
+ val = (!isNaN(rawVal) && rawVal !== "") ? Number(rawVal) : rawVal;
97907
+ that.data[key] = val;
97908
+ parsed = val;
97135
97909
  }
97136
- that._readLoop(buffer);
97137
- })
97138
- .catch(function (error) {
97139
- // If it crashes due to a sudden cable pull, let the event listener handle the cleanup
97140
- that.isListening = false
97141
- });
97910
+ }
97911
+ }
97912
+
97913
+ that.value = parsed;
97914
+
97915
+ var eventObj = {
97916
+ type: "data",
97917
+ data: parsed,
97918
+ value: parsed,
97919
+ raw: cleanLine,
97920
+ key: key,
97921
+ val: val,
97922
+ target: that
97923
+ };
97924
+
97925
+ that.dispatchEvent(eventObj);
97926
+ if (typeof that.callback === "function") (that.callback)(parsed, cleanLine, eventObj);
97142
97927
  }
97143
- }
97144
- zim.extend(zim.Hardware, createjs.EventDispatcher, null, "cjsEventDispatcher", false);
97928
+ };
97929
+
97930
+ // VALUE QUERY HELPER (e.g. hardware.get("pot"))
97931
+ that.get = function(name) {
97932
+ return that.data[name];
97933
+ };
97934
+ };
97935
+
97936
+ zim.extend(zim.Hardware, createjs.EventDispatcher, null, "cjsEventDispatcher", false);
97145
97937
  //-83.097
97146
97938
 
97147
97939
 
@@ -97287,11 +98079,15 @@ DESCRIPTION
97287
98079
  The Frame has a "deviceorientation" event to capture tilt or device rotation (like a compass)
97288
98080
  and a "devicemotion" event to capture shaking the device with the accelerometer.
97289
98081
 
98082
+ SEE: the tilt() method (just after gesture()) for a one line version
98083
+
97290
98084
  SEE: Shake - for the "devicemotion" event for accelerometer motion
97291
98085
 
97292
98086
  SEE: the PermissionAsk() class which will handle asking for permissions on devices.
97293
98087
 
97294
98088
  EXAMPLE
98089
+ // SEE ALSO THE TILT METHOD FOR A ONE LINE VERSION
98090
+
97295
98091
  // TILT OR COMPASS - https://zimjs.com/zapp/Z_Q5UYS
97296
98092
  // for capturing tilt on device (rotation about an axis)
97297
98093
  // be on a mobile device or a device with sensors
@@ -99493,6 +100289,19 @@ function zimify(obj, a, b, c, d, list) {
99493
100289
  gestureRect:function(boundary, update) {
99494
100290
  return zim.gestureBoundary(this, boundary, update);
99495
100291
  },
100292
+
100293
+ tilt:function(boundary, damp, factor, mode, type, call) {
100294
+ if (isDUO(arguments)) {arguments[0].obj = this; return zim.tilt(arguments[0]);}
100295
+ else {return zim.tilt(this, boundary, damp, factor, mode, type, call);}
100296
+ },
100297
+ noTilt:function() {
100298
+ if (isDUO(arguments)) {arguments[0].obj = this; return zim.noTilt(arguments[0]);}
100299
+ else {return zim.noTilt(this);}
100300
+ },
100301
+ tiltBoundary:function(boundary, update) {
100302
+ return zim.tiltBoundary(this, boundary, update);
100303
+ },
100304
+
99496
100305
  addPhysics:function(dynamic, contract, shape, friction, linear, angular, density, restitution, maskBits, categoryBits, physics) {
99497
100306
  if (isDUO(arguments)) {arguments[0].obj = this; return zim.addPhysics(arguments[0]);}
99498
100307
  else {return zim.addPhysics(this, dynamic, contract, shape, friction, linear, angular, density, restitution, maskBits, categoryBits, physics);}
@@ -106252,7 +107061,7 @@ for (z_i = 0; z_i < globalFunctions.length; z_i++) {
106252
107061
  ["FILL", zim.FILL],
106253
107062
  ["FULL", zim.FULL],
106254
107063
  ["LEFT", zim.LEFT],
106255
- ["RIGHT", zim.RIGHT],s
107064
+ ["RIGHT", zim.RIGHT],
106256
107065
  ["CENTER", zim.CENTER],
106257
107066
  ["MIDDLE", zim.MIDDLE],
106258
107067
  ["JUSTIFY", zim.JUSTIFY],
@@ -106527,6 +107336,9 @@ export let transform = zim.transform;
106527
107336
  export let gesture = zim.gesture;
106528
107337
  export let noGesture = zim.noGesture;
106529
107338
  export let gestureBoundary = zim.gestureBoundary;
107339
+ export let tilt = zim.tilt;
107340
+ export let noTilt = zim.noTilt;
107341
+ export let tiltBoundary = zim.tiltBoundary;
106530
107342
  export let effect = zim.effect;
106531
107343
  export let updateEffects = zim.updateEffects;
106532
107344
  export let noEffect = zim.noEffect;