@scratch/scratch-vm 11.0.0-UEPR-176

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (178) hide show
  1. package/LICENSE +12 -0
  2. package/README.md +120 -0
  3. package/dist/node/extension-worker.js +2 -0
  4. package/dist/node/extension-worker.js.map +1 -0
  5. package/dist/node/scratch-vm.js +3 -0
  6. package/dist/node/scratch-vm.js.LICENSE.txt +205 -0
  7. package/dist/node/scratch-vm.js.map +1 -0
  8. package/dist/web/extension-worker.js +2 -0
  9. package/dist/web/extension-worker.js.map +1 -0
  10. package/dist/web/scratch-vm.js +3 -0
  11. package/dist/web/scratch-vm.js.LICENSE.txt +42 -0
  12. package/dist/web/scratch-vm.js.map +1 -0
  13. package/package.json +108 -0
  14. package/src/.eslintrc.js +7 -0
  15. package/src/blocks/scratch3_control.js +206 -0
  16. package/src/blocks/scratch3_core_example.js +69 -0
  17. package/src/blocks/scratch3_data.js +257 -0
  18. package/src/blocks/scratch3_event.js +137 -0
  19. package/src/blocks/scratch3_looks.js +612 -0
  20. package/src/blocks/scratch3_motion.js +288 -0
  21. package/src/blocks/scratch3_operators.js +154 -0
  22. package/src/blocks/scratch3_procedures.js +79 -0
  23. package/src/blocks/scratch3_sensing.js +336 -0
  24. package/src/blocks/scratch3_sound.js +350 -0
  25. package/src/dispatch/central-dispatch.js +143 -0
  26. package/src/dispatch/shared-dispatch.js +235 -0
  27. package/src/dispatch/worker-dispatch.js +110 -0
  28. package/src/engine/adapter.js +176 -0
  29. package/src/engine/block-utility.js +242 -0
  30. package/src/engine/blocks-execute-cache.js +19 -0
  31. package/src/engine/blocks-runtime-cache.js +78 -0
  32. package/src/engine/blocks.js +1299 -0
  33. package/src/engine/comment.js +56 -0
  34. package/src/engine/execute.js +580 -0
  35. package/src/engine/monitor-record.js +23 -0
  36. package/src/engine/mutation-adapter.js +48 -0
  37. package/src/engine/profiler.js +390 -0
  38. package/src/engine/runtime.js +2686 -0
  39. package/src/engine/scratch-blocks-constants.js +27 -0
  40. package/src/engine/sequencer.js +361 -0
  41. package/src/engine/stage-layering.js +29 -0
  42. package/src/engine/target.js +799 -0
  43. package/src/engine/thread.js +404 -0
  44. package/src/engine/variable.js +70 -0
  45. package/src/extension-support/argument-type.js +47 -0
  46. package/src/extension-support/block-type.js +50 -0
  47. package/src/extension-support/define-messages.js +18 -0
  48. package/src/extension-support/extension-manager.js +440 -0
  49. package/src/extension-support/extension-metadata.js +64 -0
  50. package/src/extension-support/extension-worker.js +59 -0
  51. package/src/extension-support/reporter-scope.js +18 -0
  52. package/src/extension-support/target-type.js +17 -0
  53. package/src/extensions/scratch3_boost/index.js +2113 -0
  54. package/src/extensions/scratch3_ev3/index.js +1355 -0
  55. package/src/extensions/scratch3_gdx_for/index.js +981 -0
  56. package/src/extensions/scratch3_gdx_for/scratch-link-device-adapter.js +44 -0
  57. package/src/extensions/scratch3_makeymakey/index.js +396 -0
  58. package/src/extensions/scratch3_microbit/index.js +984 -0
  59. package/src/extensions/scratch3_music/assets/drums/1-snare.mp3 +0 -0
  60. package/src/extensions/scratch3_music/assets/drums/10-wood-block.mp3 +0 -0
  61. package/src/extensions/scratch3_music/assets/drums/11-cowbell.mp3 +0 -0
  62. package/src/extensions/scratch3_music/assets/drums/12-triangle.mp3 +0 -0
  63. package/src/extensions/scratch3_music/assets/drums/13-bongo.mp3 +0 -0
  64. package/src/extensions/scratch3_music/assets/drums/14-conga.mp3 +0 -0
  65. package/src/extensions/scratch3_music/assets/drums/15-cabasa.mp3 +0 -0
  66. package/src/extensions/scratch3_music/assets/drums/16-guiro.mp3 +0 -0
  67. package/src/extensions/scratch3_music/assets/drums/17-vibraslap.mp3 +0 -0
  68. package/src/extensions/scratch3_music/assets/drums/18-cuica.mp3 +0 -0
  69. package/src/extensions/scratch3_music/assets/drums/2-bass-drum.mp3 +0 -0
  70. package/src/extensions/scratch3_music/assets/drums/3-side-stick.mp3 +0 -0
  71. package/src/extensions/scratch3_music/assets/drums/4-crash-cymbal.mp3 +0 -0
  72. package/src/extensions/scratch3_music/assets/drums/5-open-hi-hat.mp3 +0 -0
  73. package/src/extensions/scratch3_music/assets/drums/6-closed-hi-hat.mp3 +0 -0
  74. package/src/extensions/scratch3_music/assets/drums/7-tambourine.mp3 +0 -0
  75. package/src/extensions/scratch3_music/assets/drums/8-hand-clap.mp3 +0 -0
  76. package/src/extensions/scratch3_music/assets/drums/9-claves.mp3 +0 -0
  77. package/src/extensions/scratch3_music/assets/instruments/1-piano/108.mp3 +0 -0
  78. package/src/extensions/scratch3_music/assets/instruments/1-piano/24.mp3 +0 -0
  79. package/src/extensions/scratch3_music/assets/instruments/1-piano/36.mp3 +0 -0
  80. package/src/extensions/scratch3_music/assets/instruments/1-piano/48.mp3 +0 -0
  81. package/src/extensions/scratch3_music/assets/instruments/1-piano/60.mp3 +0 -0
  82. package/src/extensions/scratch3_music/assets/instruments/1-piano/72.mp3 +0 -0
  83. package/src/extensions/scratch3_music/assets/instruments/1-piano/84.mp3 +0 -0
  84. package/src/extensions/scratch3_music/assets/instruments/1-piano/96.mp3 +0 -0
  85. package/src/extensions/scratch3_music/assets/instruments/10-clarinet/48.mp3 +0 -0
  86. package/src/extensions/scratch3_music/assets/instruments/10-clarinet/60.mp3 +0 -0
  87. package/src/extensions/scratch3_music/assets/instruments/11-saxophone/36.mp3 +0 -0
  88. package/src/extensions/scratch3_music/assets/instruments/11-saxophone/60.mp3 +0 -0
  89. package/src/extensions/scratch3_music/assets/instruments/11-saxophone/84.mp3 +0 -0
  90. package/src/extensions/scratch3_music/assets/instruments/12-flute/60.mp3 +0 -0
  91. package/src/extensions/scratch3_music/assets/instruments/12-flute/72.mp3 +0 -0
  92. package/src/extensions/scratch3_music/assets/instruments/13-wooden-flute/60.mp3 +0 -0
  93. package/src/extensions/scratch3_music/assets/instruments/13-wooden-flute/72.mp3 +0 -0
  94. package/src/extensions/scratch3_music/assets/instruments/14-bassoon/36.mp3 +0 -0
  95. package/src/extensions/scratch3_music/assets/instruments/14-bassoon/48.mp3 +0 -0
  96. package/src/extensions/scratch3_music/assets/instruments/14-bassoon/60.mp3 +0 -0
  97. package/src/extensions/scratch3_music/assets/instruments/15-choir/48.mp3 +0 -0
  98. package/src/extensions/scratch3_music/assets/instruments/15-choir/60.mp3 +0 -0
  99. package/src/extensions/scratch3_music/assets/instruments/15-choir/72.mp3 +0 -0
  100. package/src/extensions/scratch3_music/assets/instruments/16-vibraphone/60.mp3 +0 -0
  101. package/src/extensions/scratch3_music/assets/instruments/16-vibraphone/72.mp3 +0 -0
  102. package/src/extensions/scratch3_music/assets/instruments/17-music-box/60.mp3 +0 -0
  103. package/src/extensions/scratch3_music/assets/instruments/18-steel-drum/60.mp3 +0 -0
  104. package/src/extensions/scratch3_music/assets/instruments/19-marimba/60.mp3 +0 -0
  105. package/src/extensions/scratch3_music/assets/instruments/2-electric-piano/60.mp3 +0 -0
  106. package/src/extensions/scratch3_music/assets/instruments/20-synth-lead/60.mp3 +0 -0
  107. package/src/extensions/scratch3_music/assets/instruments/21-synth-pad/60.mp3 +0 -0
  108. package/src/extensions/scratch3_music/assets/instruments/3-organ/60.mp3 +0 -0
  109. package/src/extensions/scratch3_music/assets/instruments/4-guitar/60.mp3 +0 -0
  110. package/src/extensions/scratch3_music/assets/instruments/5-electric-guitar/60.mp3 +0 -0
  111. package/src/extensions/scratch3_music/assets/instruments/6-bass/36.mp3 +0 -0
  112. package/src/extensions/scratch3_music/assets/instruments/6-bass/48.mp3 +0 -0
  113. package/src/extensions/scratch3_music/assets/instruments/7-pizzicato/60.mp3 +0 -0
  114. package/src/extensions/scratch3_music/assets/instruments/8-cello/36.mp3 +0 -0
  115. package/src/extensions/scratch3_music/assets/instruments/8-cello/48.mp3 +0 -0
  116. package/src/extensions/scratch3_music/assets/instruments/8-cello/60.mp3 +0 -0
  117. package/src/extensions/scratch3_music/assets/instruments/9-trombone/36.mp3 +0 -0
  118. package/src/extensions/scratch3_music/assets/instruments/9-trombone/48.mp3 +0 -0
  119. package/src/extensions/scratch3_music/assets/instruments/9-trombone/60.mp3 +0 -0
  120. package/src/extensions/scratch3_music/index.js +1333 -0
  121. package/src/extensions/scratch3_music/manifest.js +63 -0
  122. package/src/extensions/scratch3_pen/index.js +770 -0
  123. package/src/extensions/scratch3_speech2text/index.js +700 -0
  124. package/src/extensions/scratch3_text2speech/index.js +766 -0
  125. package/src/extensions/scratch3_translate/index.js +286 -0
  126. package/src/extensions/scratch3_video_sensing/debug.js +13 -0
  127. package/src/extensions/scratch3_video_sensing/index.js +594 -0
  128. package/src/extensions/scratch3_video_sensing/library.js +384 -0
  129. package/src/extensions/scratch3_video_sensing/math.js +76 -0
  130. package/src/extensions/scratch3_video_sensing/view.js +509 -0
  131. package/src/extensions/scratch3_wedo2/index.js +1616 -0
  132. package/src/import/load-costume.js +416 -0
  133. package/src/import/load-sound.js +116 -0
  134. package/src/index.js +10 -0
  135. package/src/io/ble.js +256 -0
  136. package/src/io/bt.js +202 -0
  137. package/src/io/clock.js +39 -0
  138. package/src/io/cloud.js +169 -0
  139. package/src/io/keyboard.js +146 -0
  140. package/src/io/mouse.js +145 -0
  141. package/src/io/mouseWheel.js +28 -0
  142. package/src/io/userData.js +24 -0
  143. package/src/io/video.js +210 -0
  144. package/src/playground/benchmark.css +83 -0
  145. package/src/playground/benchmark.js +753 -0
  146. package/src/playground/index.html +111 -0
  147. package/src/playground/suite.css +78 -0
  148. package/src/playground/suite.html +25 -0
  149. package/src/playground/suite.js +544 -0
  150. package/src/playground/video-sensing.html +18 -0
  151. package/src/playground/video-sensing.js +133 -0
  152. package/src/serialization/deserialize-assets.js +178 -0
  153. package/src/serialization/sb2.js +1295 -0
  154. package/src/serialization/sb2_specmap.js +1818 -0
  155. package/src/serialization/sb3.js +1320 -0
  156. package/src/serialization/serialize-assets.js +60 -0
  157. package/src/sprites/rendered-target.js +1115 -0
  158. package/src/sprites/sprite.js +177 -0
  159. package/src/util/base64-util.js +48 -0
  160. package/src/util/cast.js +218 -0
  161. package/src/util/clone.js +17 -0
  162. package/src/util/color.js +204 -0
  163. package/src/util/fetch-with-timeout.js +53 -0
  164. package/src/util/get-monitor-id.js +33 -0
  165. package/src/util/jsonrpc.js +114 -0
  166. package/src/util/log.js +4 -0
  167. package/src/util/math-util.js +122 -0
  168. package/src/util/maybe-format-message.js +18 -0
  169. package/src/util/new-block-ids.js +33 -0
  170. package/src/util/rateLimiter.js +73 -0
  171. package/src/util/scratch-link-websocket.js +133 -0
  172. package/src/util/string-util.js +94 -0
  173. package/src/util/task-queue.js +203 -0
  174. package/src/util/timer.js +103 -0
  175. package/src/util/uid.js +29 -0
  176. package/src/util/variable-util.js +48 -0
  177. package/src/util/xml-escape.js +33 -0
  178. package/src/virtual-machine.js +1586 -0
@@ -0,0 +1,770 @@
1
+ const ArgumentType = require('../../extension-support/argument-type');
2
+ const BlockType = require('../../extension-support/block-type');
3
+ const TargetType = require('../../extension-support/target-type');
4
+ const Cast = require('../../util/cast');
5
+ const Clone = require('../../util/clone');
6
+ const Color = require('../../util/color');
7
+ const formatMessage = require('format-message');
8
+ const MathUtil = require('../../util/math-util');
9
+ const RenderedTarget = require('../../sprites/rendered-target');
10
+ const log = require('../../util/log');
11
+ const StageLayering = require('../../engine/stage-layering');
12
+
13
+ /**
14
+ * Icon svg to be displayed at the left edge of each extension block, encoded as a data URI.
15
+ * @type {string}
16
+ */
17
+ // eslint-disable-next-line max-len
18
+ const blockIconURI = 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNDAiIGhlaWdodD0iNDAiIHZpZXdCb3g9IjAgMCA0MCA0MCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48dGl0bGU+cGVuLWljb248L3RpdGxlPjxnIHN0cm9rZT0iIzU3NUU3NSIgZmlsbD0ibm9uZSIgZmlsbC1ydWxlPSJldmVub2RkIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiPjxwYXRoIGQ9Ik04Ljc1MyAzNC42MDJsLTQuMjUgMS43OCAxLjc4My00LjIzN2MxLjIxOC0yLjg5MiAyLjkwNy01LjQyMyA1LjAzLTcuNTM4TDMxLjA2NiA0LjkzYy44NDYtLjg0MiAyLjY1LS40MSA0LjAzMi45NjcgMS4zOCAxLjM3NSAxLjgxNiAzLjE3My45NyA0LjAxNUwxNi4zMTggMjkuNTljLTIuMTIzIDIuMTE2LTQuNjY0IDMuOC03LjU2NSA1LjAxMiIgZmlsbD0iI0ZGRiIvPjxwYXRoIGQ9Ik0yOS40MSA2LjExcy00LjQ1LTIuMzc4LTguMjAyIDUuNzcyYy0xLjczNCAzLjc2Ni00LjM1IDEuNTQ2LTQuMzUgMS41NDYiLz48cGF0aCBkPSJNMzYuNDIgOC44MjVjMCAuNDYzLS4xNC44NzMtLjQzMiAxLjE2NGwtOS4zMzUgOS4zYy4yODItLjI5LjQxLS42NjguNDEtMS4xMiAwLS44NzQtLjUwNy0xLjk2My0xLjQwNi0yLjg2OC0xLjM2Mi0xLjM1OC0zLjE0Ny0xLjgtNC4wMDItLjk5TDMwLjk5IDUuMDFjLjg0NC0uODQgMi42NS0uNDEgNC4wMzUuOTYuODk4LjkwNCAxLjM5NiAxLjk4MiAxLjM5NiAyLjg1NU0xMC41MTUgMzMuNzc0Yy0uNTczLjMwMi0xLjE1Ny41Ny0xLjc2NC44M0w0LjUgMzYuMzgybDEuNzg2LTQuMjM1Yy4yNTgtLjYwNC41My0xLjE4Ni44MzMtMS43NTcuNjkuMTgzIDEuNDQ4LjYyNSAyLjEwOCAxLjI4Mi42Ni42NTggMS4xMDIgMS40MTIgMS4yODcgMi4xMDIiIGZpbGw9IiM0Qzk3RkYiLz48cGF0aCBkPSJNMzYuNDk4IDguNzQ4YzAgLjQ2NC0uMTQuODc0LS40MzMgMS4xNjVsLTE5Ljc0MiAxOS42OGMtMi4xMyAyLjExLTQuNjczIDMuNzkzLTcuNTcyIDUuMDFMNC41IDM2LjM4bC45NzQtMi4zMTYgMS45MjUtLjgwOGMyLjg5OC0xLjIxOCA1LjQ0LTIuOSA3LjU3LTUuMDFsMTkuNzQzLTE5LjY4Yy4yOTItLjI5Mi40MzItLjcwMi40MzItMS4xNjUgMC0uNjQ2LS4yNy0xLjQtLjc4LTIuMTIyLjI1LjE3Mi41LjM3Ny43MzcuNjE0Ljg5OC45MDUgMS4zOTYgMS45ODMgMS4zOTYgMi44NTYiIGZpbGw9IiM1NzVFNzUiIG9wYWNpdHk9Ii4xNSIvPjxwYXRoIGQ9Ik0xOC40NSAxMi44M2MwIC41LS40MDQuOTA1LS45MDQuOTA1cy0uOTA1LS40MDUtLjkwNS0uOTA0YzAtLjUuNDA3LS45MDMuOTA2LS45MDMuNSAwIC45MDQuNDA0LjkwNC45MDR6IiBmaWxsPSIjNTc1RTc1Ii8+PC9nPjwvc3ZnPg==';
19
+
20
+ /**
21
+ * Enum for pen color parameter values.
22
+ * @readonly
23
+ * @enum {string}
24
+ */
25
+ const ColorParam = {
26
+ COLOR: 'color',
27
+ SATURATION: 'saturation',
28
+ BRIGHTNESS: 'brightness',
29
+ TRANSPARENCY: 'transparency'
30
+ };
31
+
32
+ /**
33
+ * @typedef {object} PenState - the pen state associated with a particular target.
34
+ * @property {Boolean} penDown - tracks whether the pen should draw for this target.
35
+ * @property {number} color - the current color (hue) of the pen.
36
+ * @property {PenAttributes} penAttributes - cached pen attributes for the renderer. This is the authoritative value for
37
+ * diameter but not for pen color.
38
+ */
39
+
40
+ /**
41
+ * Host for the Pen-related blocks in Scratch 3.0
42
+ * @param {Runtime} runtime - the runtime instantiating this block package.
43
+ * @constructor
44
+ */
45
+ class Scratch3PenBlocks {
46
+ constructor (runtime) {
47
+ /**
48
+ * The runtime instantiating this block package.
49
+ * @type {Runtime}
50
+ */
51
+ this.runtime = runtime;
52
+
53
+ /**
54
+ * The ID of the renderer Drawable corresponding to the pen layer.
55
+ * @type {int}
56
+ * @private
57
+ */
58
+ this._penDrawableId = -1;
59
+
60
+ /**
61
+ * The ID of the renderer Skin corresponding to the pen layer.
62
+ * @type {int}
63
+ * @private
64
+ */
65
+ this._penSkinId = -1;
66
+
67
+ this._onTargetCreated = this._onTargetCreated.bind(this);
68
+ this._onTargetMoved = this._onTargetMoved.bind(this);
69
+
70
+ runtime.on('targetWasCreated', this._onTargetCreated);
71
+ runtime.on('RUNTIME_DISPOSED', this.clear.bind(this));
72
+ }
73
+
74
+ /**
75
+ * The default pen state, to be used when a target has no existing pen state.
76
+ * @type {PenState}
77
+ */
78
+ static get DEFAULT_PEN_STATE () {
79
+ return {
80
+ penDown: false,
81
+ color: 66.66,
82
+ saturation: 100,
83
+ brightness: 100,
84
+ transparency: 0,
85
+ _shade: 50, // Used only for legacy `change shade by` blocks
86
+ penAttributes: {
87
+ color4f: [0, 0, 1, 1],
88
+ diameter: 1
89
+ }
90
+ };
91
+ }
92
+
93
+
94
+ /**
95
+ * The minimum and maximum allowed pen size.
96
+ * The maximum is twice the diagonal of the stage, so that even an
97
+ * off-stage sprite can fill it.
98
+ * @type {{min: number, max: number}}
99
+ */
100
+ static get PEN_SIZE_RANGE () {
101
+ return {min: 1, max: 1200};
102
+ }
103
+
104
+ /**
105
+ * The key to load & store a target's pen-related state.
106
+ * @type {string}
107
+ */
108
+ static get STATE_KEY () {
109
+ return 'Scratch.pen';
110
+ }
111
+
112
+ /**
113
+ * Clamp a pen size value to the range allowed by the pen.
114
+ * @param {number} requestedSize - the requested pen size.
115
+ * @returns {number} the clamped size.
116
+ * @private
117
+ */
118
+ _clampPenSize (requestedSize) {
119
+ return MathUtil.clamp(
120
+ requestedSize,
121
+ Scratch3PenBlocks.PEN_SIZE_RANGE.min,
122
+ Scratch3PenBlocks.PEN_SIZE_RANGE.max
123
+ );
124
+ }
125
+
126
+ /**
127
+ * Retrieve the ID of the renderer "Skin" corresponding to the pen layer. If
128
+ * the pen Skin doesn't yet exist, create it.
129
+ * @returns {int} the Skin ID of the pen layer, or -1 on failure.
130
+ * @private
131
+ */
132
+ _getPenLayerID () {
133
+ if (this._penSkinId < 0 && this.runtime.renderer) {
134
+ this._penSkinId = this.runtime.renderer.createPenSkin();
135
+ this._penDrawableId = this.runtime.renderer.createDrawable(StageLayering.PEN_LAYER);
136
+ this.runtime.renderer.updateDrawableSkinId(this._penDrawableId, this._penSkinId);
137
+ }
138
+ return this._penSkinId;
139
+ }
140
+
141
+ /**
142
+ * @param {Target} target - collect pen state for this target. Probably, but not necessarily, a RenderedTarget.
143
+ * @returns {PenState} the mutable pen state associated with that target. This will be created if necessary.
144
+ * @private
145
+ */
146
+ _getPenState (target) {
147
+ let penState = target.getCustomState(Scratch3PenBlocks.STATE_KEY);
148
+ if (!penState) {
149
+ penState = Clone.simple(Scratch3PenBlocks.DEFAULT_PEN_STATE);
150
+ target.setCustomState(Scratch3PenBlocks.STATE_KEY, penState);
151
+ }
152
+ return penState;
153
+ }
154
+
155
+ /**
156
+ * When a pen-using Target is cloned, clone the pen state.
157
+ * @param {Target} newTarget - the newly created target.
158
+ * @param {Target} [sourceTarget] - the target used as a source for the new clone, if any.
159
+ * @listens Runtime#event:targetWasCreated
160
+ * @private
161
+ */
162
+ _onTargetCreated (newTarget, sourceTarget) {
163
+ if (sourceTarget) {
164
+ const penState = sourceTarget.getCustomState(Scratch3PenBlocks.STATE_KEY);
165
+ if (penState) {
166
+ newTarget.setCustomState(Scratch3PenBlocks.STATE_KEY, Clone.simple(penState));
167
+ if (penState.penDown) {
168
+ newTarget.addListener(RenderedTarget.EVENT_TARGET_MOVED, this._onTargetMoved);
169
+ }
170
+ }
171
+ }
172
+ }
173
+
174
+ /**
175
+ * Handle a target which has moved. This only fires when the pen is down.
176
+ * @param {RenderedTarget} target - the target which has moved.
177
+ * @param {number} oldX - the previous X position.
178
+ * @param {number} oldY - the previous Y position.
179
+ * @param {boolean} isForce - whether the movement was forced.
180
+ * @private
181
+ */
182
+ _onTargetMoved (target, oldX, oldY, isForce) {
183
+ // Only move the pen if the movement isn't forced (ie. dragged).
184
+ if (!isForce) {
185
+ const penSkinId = this._getPenLayerID();
186
+ if (penSkinId >= 0) {
187
+ const penState = this._getPenState(target);
188
+ this.runtime.renderer.penLine(penSkinId, penState.penAttributes, oldX, oldY, target.x, target.y);
189
+ this.runtime.requestRedraw();
190
+ }
191
+ }
192
+ }
193
+
194
+ /**
195
+ * Wrap a color input into the range (0,100).
196
+ * @param {number} value - the value to be wrapped.
197
+ * @returns {number} the wrapped value.
198
+ * @private
199
+ */
200
+ _wrapColor (value) {
201
+ return MathUtil.wrapClamp(value, 0, 100);
202
+ }
203
+
204
+ /**
205
+ * Initialize color parameters menu with localized strings
206
+ * @returns {array} of the localized text and values for each menu element
207
+ * @private
208
+ */
209
+ _initColorParam () {
210
+ return [
211
+ {
212
+ text: formatMessage({
213
+ id: 'pen.colorMenu.color',
214
+ default: 'color',
215
+ description: 'label for color element in color picker for pen extension'
216
+ }),
217
+ value: ColorParam.COLOR
218
+ },
219
+ {
220
+ text: formatMessage({
221
+ id: 'pen.colorMenu.saturation',
222
+ default: 'saturation',
223
+ description: 'label for saturation element in color picker for pen extension'
224
+ }),
225
+ value: ColorParam.SATURATION
226
+ },
227
+ {
228
+ text: formatMessage({
229
+ id: 'pen.colorMenu.brightness',
230
+ default: 'brightness',
231
+ description: 'label for brightness element in color picker for pen extension'
232
+ }),
233
+ value: ColorParam.BRIGHTNESS
234
+ },
235
+ {
236
+ text: formatMessage({
237
+ id: 'pen.colorMenu.transparency',
238
+ default: 'transparency',
239
+ description: 'label for transparency element in color picker for pen extension'
240
+ }),
241
+ value: ColorParam.TRANSPARENCY
242
+
243
+ }
244
+ ];
245
+ }
246
+
247
+ /**
248
+ * Clamp a pen color parameter to the range (0,100).
249
+ * @param {number} value - the value to be clamped.
250
+ * @returns {number} the clamped value.
251
+ * @private
252
+ */
253
+ _clampColorParam (value) {
254
+ return MathUtil.clamp(value, 0, 100);
255
+ }
256
+
257
+ /**
258
+ * Convert an alpha value to a pen transparency value.
259
+ * Alpha ranges from 0 to 1, where 0 is transparent and 1 is opaque.
260
+ * Transparency ranges from 0 to 100, where 0 is opaque and 100 is transparent.
261
+ * @param {number} alpha - the input alpha value.
262
+ * @returns {number} the transparency value.
263
+ * @private
264
+ */
265
+ _alphaToTransparency (alpha) {
266
+ return (1.0 - alpha) * 100.0;
267
+ }
268
+
269
+ /**
270
+ * Convert a pen transparency value to an alpha value.
271
+ * Alpha ranges from 0 to 1, where 0 is transparent and 1 is opaque.
272
+ * Transparency ranges from 0 to 100, where 0 is opaque and 100 is transparent.
273
+ * @param {number} transparency - the input transparency value.
274
+ * @returns {number} the alpha value.
275
+ * @private
276
+ */
277
+ _transparencyToAlpha (transparency) {
278
+ return 1.0 - (transparency / 100.0);
279
+ }
280
+
281
+ /**
282
+ * @returns {object} metadata for this extension and its blocks.
283
+ */
284
+ getInfo () {
285
+ return {
286
+ id: 'pen',
287
+ name: formatMessage({
288
+ id: 'pen.categoryName',
289
+ default: 'Pen',
290
+ description: 'Label for the pen extension category'
291
+ }),
292
+ blockIconURI: blockIconURI,
293
+ blocks: [
294
+ {
295
+ opcode: 'clear',
296
+ blockType: BlockType.COMMAND,
297
+ text: formatMessage({
298
+ id: 'pen.clear',
299
+ default: 'erase all',
300
+ description: 'erase all pen trails and stamps'
301
+ })
302
+ },
303
+ {
304
+ opcode: 'stamp',
305
+ blockType: BlockType.COMMAND,
306
+ text: formatMessage({
307
+ id: 'pen.stamp',
308
+ default: 'stamp',
309
+ description: 'render current costume on the background'
310
+ }),
311
+ filter: [TargetType.SPRITE]
312
+ },
313
+ {
314
+ opcode: 'penDown',
315
+ blockType: BlockType.COMMAND,
316
+ text: formatMessage({
317
+ id: 'pen.penDown',
318
+ default: 'pen down',
319
+ description: 'start leaving a trail when the sprite moves'
320
+ }),
321
+ filter: [TargetType.SPRITE]
322
+ },
323
+ {
324
+ opcode: 'penUp',
325
+ blockType: BlockType.COMMAND,
326
+ text: formatMessage({
327
+ id: 'pen.penUp',
328
+ default: 'pen up',
329
+ description: 'stop leaving a trail behind the sprite'
330
+ }),
331
+ filter: [TargetType.SPRITE]
332
+ },
333
+ {
334
+ opcode: 'setPenColorToColor',
335
+ blockType: BlockType.COMMAND,
336
+ text: formatMessage({
337
+ id: 'pen.setColor',
338
+ default: 'set pen color to [COLOR]',
339
+ description: 'set the pen color to a particular (RGB) value'
340
+ }),
341
+ arguments: {
342
+ COLOR: {
343
+ type: ArgumentType.COLOR
344
+ }
345
+ },
346
+ filter: [TargetType.SPRITE]
347
+ },
348
+ {
349
+ opcode: 'changePenColorParamBy',
350
+ blockType: BlockType.COMMAND,
351
+ text: formatMessage({
352
+ id: 'pen.changeColorParam',
353
+ default: 'change pen [COLOR_PARAM] by [VALUE]',
354
+ description: 'change the state of a pen color parameter'
355
+ }),
356
+ arguments: {
357
+ COLOR_PARAM: {
358
+ type: ArgumentType.STRING,
359
+ menu: 'colorParam',
360
+ defaultValue: ColorParam.COLOR
361
+ },
362
+ VALUE: {
363
+ type: ArgumentType.NUMBER,
364
+ defaultValue: 10
365
+ }
366
+ },
367
+ filter: [TargetType.SPRITE]
368
+ },
369
+ {
370
+ opcode: 'setPenColorParamTo',
371
+ blockType: BlockType.COMMAND,
372
+ text: formatMessage({
373
+ id: 'pen.setColorParam',
374
+ default: 'set pen [COLOR_PARAM] to [VALUE]',
375
+ description: 'set the state for a pen color parameter e.g. saturation'
376
+ }),
377
+ arguments: {
378
+ COLOR_PARAM: {
379
+ type: ArgumentType.STRING,
380
+ menu: 'colorParam',
381
+ defaultValue: ColorParam.COLOR
382
+ },
383
+ VALUE: {
384
+ type: ArgumentType.NUMBER,
385
+ defaultValue: 50
386
+ }
387
+ },
388
+ filter: [TargetType.SPRITE]
389
+ },
390
+ {
391
+ opcode: 'changePenSizeBy',
392
+ blockType: BlockType.COMMAND,
393
+ text: formatMessage({
394
+ id: 'pen.changeSize',
395
+ default: 'change pen size by [SIZE]',
396
+ description: 'change the diameter of the trail left by a sprite'
397
+ }),
398
+ arguments: {
399
+ SIZE: {
400
+ type: ArgumentType.NUMBER,
401
+ defaultValue: 1
402
+ }
403
+ },
404
+ filter: [TargetType.SPRITE]
405
+ },
406
+ {
407
+ opcode: 'setPenSizeTo',
408
+ blockType: BlockType.COMMAND,
409
+ text: formatMessage({
410
+ id: 'pen.setSize',
411
+ default: 'set pen size to [SIZE]',
412
+ description: 'set the diameter of a trail left by a sprite'
413
+ }),
414
+ arguments: {
415
+ SIZE: {
416
+ type: ArgumentType.NUMBER,
417
+ defaultValue: 1
418
+ }
419
+ },
420
+ filter: [TargetType.SPRITE]
421
+ },
422
+ /* Legacy blocks, should not be shown in flyout */
423
+ {
424
+ opcode: 'setPenShadeToNumber',
425
+ blockType: BlockType.COMMAND,
426
+ text: formatMessage({
427
+ id: 'pen.setShade',
428
+ default: 'set pen shade to [SHADE]',
429
+ description: 'legacy pen blocks - set pen shade'
430
+ }),
431
+ arguments: {
432
+ SHADE: {
433
+ type: ArgumentType.NUMBER,
434
+ defaultValue: 1
435
+ }
436
+ },
437
+ hideFromPalette: true
438
+ },
439
+ {
440
+ opcode: 'changePenShadeBy',
441
+ blockType: BlockType.COMMAND,
442
+ text: formatMessage({
443
+ id: 'pen.changeShade',
444
+ default: 'change pen shade by [SHADE]',
445
+ description: 'legacy pen blocks - change pen shade'
446
+ }),
447
+ arguments: {
448
+ SHADE: {
449
+ type: ArgumentType.NUMBER,
450
+ defaultValue: 1
451
+ }
452
+ },
453
+ hideFromPalette: true
454
+ },
455
+ {
456
+ opcode: 'setPenHueToNumber',
457
+ blockType: BlockType.COMMAND,
458
+ text: formatMessage({
459
+ id: 'pen.setHue',
460
+ default: 'set pen color to [HUE]',
461
+ description: 'legacy pen blocks - set pen color to number'
462
+ }),
463
+ arguments: {
464
+ HUE: {
465
+ type: ArgumentType.NUMBER,
466
+ defaultValue: 1
467
+ }
468
+ },
469
+ hideFromPalette: true
470
+ },
471
+ {
472
+ opcode: 'changePenHueBy',
473
+ blockType: BlockType.COMMAND,
474
+ text: formatMessage({
475
+ id: 'pen.changeHue',
476
+ default: 'change pen color by [HUE]',
477
+ description: 'legacy pen blocks - change pen color'
478
+ }),
479
+ arguments: {
480
+ HUE: {
481
+ type: ArgumentType.NUMBER,
482
+ defaultValue: 1
483
+ }
484
+ },
485
+ hideFromPalette: true
486
+ }
487
+ ],
488
+ menus: {
489
+ colorParam: {
490
+ acceptReporters: true,
491
+ items: this._initColorParam()
492
+ }
493
+ }
494
+ };
495
+ }
496
+
497
+ /**
498
+ * The pen "clear" block clears the pen layer's contents.
499
+ */
500
+ clear () {
501
+ const penSkinId = this._getPenLayerID();
502
+ if (penSkinId >= 0) {
503
+ this.runtime.renderer.penClear(penSkinId);
504
+ this.runtime.requestRedraw();
505
+ }
506
+ }
507
+
508
+ /**
509
+ * The pen "stamp" block stamps the current drawable's image onto the pen layer.
510
+ * @param {object} args - the block arguments.
511
+ * @param {object} util - utility object provided by the runtime.
512
+ */
513
+ stamp (args, util) {
514
+ const penSkinId = this._getPenLayerID();
515
+ if (penSkinId >= 0) {
516
+ const target = util.target;
517
+ this.runtime.renderer.penStamp(penSkinId, target.drawableID);
518
+ this.runtime.requestRedraw();
519
+ }
520
+ }
521
+
522
+ /**
523
+ * The pen "pen down" block causes the target to leave pen trails on future motion.
524
+ * @param {object} args - the block arguments.
525
+ * @param {object} util - utility object provided by the runtime.
526
+ */
527
+ penDown (args, util) {
528
+ const target = util.target;
529
+ const penState = this._getPenState(target);
530
+
531
+ if (!penState.penDown) {
532
+ penState.penDown = true;
533
+ target.addListener(RenderedTarget.EVENT_TARGET_MOVED, this._onTargetMoved);
534
+ }
535
+
536
+ const penSkinId = this._getPenLayerID();
537
+ if (penSkinId >= 0) {
538
+ this.runtime.renderer.penPoint(penSkinId, penState.penAttributes, target.x, target.y);
539
+ this.runtime.requestRedraw();
540
+ }
541
+ }
542
+
543
+ /**
544
+ * The pen "pen up" block stops the target from leaving pen trails.
545
+ * @param {object} args - the block arguments.
546
+ * @param {object} util - utility object provided by the runtime.
547
+ */
548
+ penUp (args, util) {
549
+ const target = util.target;
550
+ const penState = this._getPenState(target);
551
+
552
+ if (penState.penDown) {
553
+ penState.penDown = false;
554
+ target.removeListener(RenderedTarget.EVENT_TARGET_MOVED, this._onTargetMoved);
555
+ }
556
+ }
557
+
558
+ /**
559
+ * The pen "set pen color to {color}" block sets the pen to a particular RGB color.
560
+ * The transparency is reset to 0.
561
+ * @param {object} args - the block arguments.
562
+ * @property {int} COLOR - the color to set, expressed as a 24-bit RGB value (0xRRGGBB).
563
+ * @param {object} util - utility object provided by the runtime.
564
+ */
565
+ setPenColorToColor (args, util) {
566
+ const penState = this._getPenState(util.target);
567
+ const rgb = Cast.toRgbColorObject(args.COLOR);
568
+ const hsv = Color.rgbToHsv(rgb);
569
+ penState.color = (hsv.h / 360) * 100;
570
+ penState.saturation = hsv.s * 100;
571
+ penState.brightness = hsv.v * 100;
572
+ if (Object.prototype.hasOwnProperty.call(rgb, 'a')) {
573
+ penState.transparency = 100 * (1 - (rgb.a / 255.0));
574
+ } else {
575
+ penState.transparency = 0;
576
+ }
577
+
578
+ // Set the legacy "shade" value the same way scratch 2 did.
579
+ penState._shade = penState.brightness / 2;
580
+
581
+ this._updatePenColor(penState);
582
+ }
583
+
584
+ /**
585
+ * Update the cached color from the color, saturation, brightness and transparency values
586
+ * in the provided PenState object.
587
+ * @param {PenState} penState - the pen state to update.
588
+ * @private
589
+ */
590
+ _updatePenColor (penState) {
591
+ const rgb = Color.hsvToRgb({
592
+ h: penState.color * 360 / 100,
593
+ s: penState.saturation / 100,
594
+ v: penState.brightness / 100
595
+ });
596
+ penState.penAttributes.color4f[0] = rgb.r / 255.0;
597
+ penState.penAttributes.color4f[1] = rgb.g / 255.0;
598
+ penState.penAttributes.color4f[2] = rgb.b / 255.0;
599
+ penState.penAttributes.color4f[3] = this._transparencyToAlpha(penState.transparency);
600
+ }
601
+
602
+ /**
603
+ * Set or change a single color parameter on the pen state, and update the pen color.
604
+ * @param {ColorParam} param - the name of the color parameter to set or change.
605
+ * @param {number} value - the value to set or change the param by.
606
+ * @param {PenState} penState - the pen state to update.
607
+ * @param {boolean} change - if true change param by value, if false set param to value.
608
+ * @private
609
+ */
610
+ _setOrChangeColorParam (param, value, penState, change) {
611
+ switch (param) {
612
+ case ColorParam.COLOR:
613
+ penState.color = this._wrapColor(value + (change ? penState.color : 0));
614
+ break;
615
+ case ColorParam.SATURATION:
616
+ penState.saturation = this._clampColorParam(value + (change ? penState.saturation : 0));
617
+ break;
618
+ case ColorParam.BRIGHTNESS:
619
+ penState.brightness = this._clampColorParam(value + (change ? penState.brightness : 0));
620
+ break;
621
+ case ColorParam.TRANSPARENCY:
622
+ penState.transparency = this._clampColorParam(value + (change ? penState.transparency : 0));
623
+ break;
624
+ default:
625
+ log.warn(`Tried to set or change unknown color parameter: ${param}`);
626
+ }
627
+ this._updatePenColor(penState);
628
+ }
629
+
630
+ /**
631
+ * The "change pen {ColorParam} by {number}" block changes one of the pen's color parameters
632
+ * by a given amound.
633
+ * @param {object} args - the block arguments.
634
+ * @property {ColorParam} COLOR_PARAM - the name of the selected color parameter.
635
+ * @property {number} VALUE - the amount to change the selected parameter by.
636
+ * @param {object} util - utility object provided by the runtime.
637
+ */
638
+ changePenColorParamBy (args, util) {
639
+ const penState = this._getPenState(util.target);
640
+ this._setOrChangeColorParam(args.COLOR_PARAM, Cast.toNumber(args.VALUE), penState, true);
641
+ }
642
+
643
+ /**
644
+ * The "set pen {ColorParam} to {number}" block sets one of the pen's color parameters
645
+ * to a given amound.
646
+ * @param {object} args - the block arguments.
647
+ * @property {ColorParam} COLOR_PARAM - the name of the selected color parameter.
648
+ * @property {number} VALUE - the amount to set the selected parameter to.
649
+ * @param {object} util - utility object provided by the runtime.
650
+ */
651
+ setPenColorParamTo (args, util) {
652
+ const penState = this._getPenState(util.target);
653
+ this._setOrChangeColorParam(args.COLOR_PARAM, Cast.toNumber(args.VALUE), penState, false);
654
+ }
655
+
656
+ /**
657
+ * The pen "change pen size by {number}" block changes the pen size by the given amount.
658
+ * @param {object} args - the block arguments.
659
+ * @property {number} SIZE - the amount of desired size change.
660
+ * @param {object} util - utility object provided by the runtime.
661
+ */
662
+ changePenSizeBy (args, util) {
663
+ const penAttributes = this._getPenState(util.target).penAttributes;
664
+ penAttributes.diameter = this._clampPenSize(penAttributes.diameter + Cast.toNumber(args.SIZE));
665
+ }
666
+
667
+ /**
668
+ * The pen "set pen size to {number}" block sets the pen size to the given amount.
669
+ * @param {object} args - the block arguments.
670
+ * @property {number} SIZE - the amount of desired size change.
671
+ * @param {object} util - utility object provided by the runtime.
672
+ */
673
+ setPenSizeTo (args, util) {
674
+ const penAttributes = this._getPenState(util.target).penAttributes;
675
+ penAttributes.diameter = this._clampPenSize(Cast.toNumber(args.SIZE));
676
+ }
677
+
678
+ /* LEGACY OPCODES */
679
+ /**
680
+ * Scratch 2 "hue" param is equivelant to twice the new "color" param.
681
+ * @param {object} args - the block arguments.
682
+ * @property {number} HUE - the amount to set the hue to.
683
+ * @param {object} util - utility object provided by the runtime.
684
+ */
685
+ setPenHueToNumber (args, util) {
686
+ const penState = this._getPenState(util.target);
687
+ const hueValue = Cast.toNumber(args.HUE);
688
+ const colorValue = hueValue / 2;
689
+ this._setOrChangeColorParam(ColorParam.COLOR, colorValue, penState, false);
690
+ this._setOrChangeColorParam(ColorParam.TRANSPARENCY, 0, penState, false);
691
+ this._legacyUpdatePenColor(penState);
692
+ }
693
+
694
+ /**
695
+ * Scratch 2 "hue" param is equivelant to twice the new "color" param.
696
+ * @param {object} args - the block arguments.
697
+ * @property {number} HUE - the amount of desired hue change.
698
+ * @param {object} util - utility object provided by the runtime.
699
+ */
700
+ changePenHueBy (args, util) {
701
+ const penState = this._getPenState(util.target);
702
+ const hueChange = Cast.toNumber(args.HUE);
703
+ const colorChange = hueChange / 2;
704
+ this._setOrChangeColorParam(ColorParam.COLOR, colorChange, penState, true);
705
+
706
+ this._legacyUpdatePenColor(penState);
707
+ }
708
+
709
+ /**
710
+ * Use legacy "set shade" code to calculate RGB value for shade,
711
+ * then convert back to HSV and store those components.
712
+ * It is important to also track the given shade in penState._shade
713
+ * because it cannot be accurately backed out of the new HSV later.
714
+ * @param {object} args - the block arguments.
715
+ * @property {number} SHADE - the amount to set the shade to.
716
+ * @param {object} util - utility object provided by the runtime.
717
+ */
718
+ setPenShadeToNumber (args, util) {
719
+ const penState = this._getPenState(util.target);
720
+ let newShade = Cast.toNumber(args.SHADE);
721
+
722
+ // Wrap clamp the new shade value the way scratch 2 did.
723
+ newShade = newShade % 200;
724
+ if (newShade < 0) newShade += 200;
725
+
726
+ // And store the shade that was used to compute this new color for later use.
727
+ penState._shade = newShade;
728
+
729
+ this._legacyUpdatePenColor(penState);
730
+ }
731
+
732
+ /**
733
+ * Because "shade" cannot be backed out of hsv consistently, use the previously
734
+ * stored penState._shade to make the shade change.
735
+ * @param {object} args - the block arguments.
736
+ * @property {number} SHADE - the amount of desired shade change.
737
+ * @param {object} util - utility object provided by the runtime.
738
+ */
739
+ changePenShadeBy (args, util) {
740
+ const penState = this._getPenState(util.target);
741
+ const shadeChange = Cast.toNumber(args.SHADE);
742
+ this.setPenShadeToNumber({SHADE: penState._shade + shadeChange}, util);
743
+ }
744
+
745
+ /**
746
+ * Update the pen state's color from its hue & shade values, Scratch 2.0 style.
747
+ * @param {object} penState - update the HSV & RGB values in this pen state from its hue & shade values.
748
+ * @private
749
+ */
750
+ _legacyUpdatePenColor (penState) {
751
+ // Create the new color in RGB using the scratch 2 "shade" model
752
+ let rgb = Color.hsvToRgb({h: penState.color * 360 / 100, s: 1, v: 1});
753
+ const shade = (penState._shade > 100) ? 200 - penState._shade : penState._shade;
754
+ if (shade < 50) {
755
+ rgb = Color.mixRgb(Color.RGB_BLACK, rgb, (10 + shade) / 60);
756
+ } else {
757
+ rgb = Color.mixRgb(rgb, Color.RGB_WHITE, (shade - 50) / 60);
758
+ }
759
+
760
+ // Update the pen state according to new color
761
+ const hsv = Color.rgbToHsv(rgb);
762
+ penState.color = 100 * hsv.h / 360;
763
+ penState.saturation = 100 * hsv.s;
764
+ penState.brightness = 100 * hsv.v;
765
+
766
+ this._updatePenColor(penState);
767
+ }
768
+ }
769
+
770
+ module.exports = Scratch3PenBlocks;