@blockdia/scratch-blocks 0.1.0
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/.nvmrc +1 -0
- package/LICENSE +674 -0
- package/README.md +57 -0
- package/TRADEMARK +1 -0
- package/blockly_compressed_horizontal.js +2268 -0
- package/blockly_compressed_vertical.js +2296 -0
- package/blockly_uncompressed_horizontal.js +1212 -0
- package/blockly_uncompressed_vertical.js +1212 -0
- package/blocks_common/colour.js +61 -0
- package/blocks_common/math.js +159 -0
- package/blocks_common/matrix.js +54 -0
- package/blocks_common/note.js +58 -0
- package/blocks_common/text.js +57 -0
- package/blocks_compressed.js +37 -0
- package/blocks_compressed_horizontal.js +68 -0
- package/blocks_compressed_vertical.js +216 -0
- package/blocks_horizontal/control.js +212 -0
- package/blocks_horizontal/default_toolbox.js +139 -0
- package/blocks_horizontal/event.js +190 -0
- package/blocks_horizontal/wedo.js +325 -0
- package/blocks_vertical/control.js +532 -0
- package/blocks_vertical/data.js +666 -0
- package/blocks_vertical/default_toolbox.js +571 -0
- package/blocks_vertical/event.js +329 -0
- package/blocks_vertical/extensions.js +294 -0
- package/blocks_vertical/looks.js +591 -0
- package/blocks_vertical/motion.js +587 -0
- package/blocks_vertical/operators.js +470 -0
- package/blocks_vertical/procedures.js +1051 -0
- package/blocks_vertical/sensing.js +555 -0
- package/blocks_vertical/sound.js +246 -0
- package/blocks_vertical/vertical_extensions.js +289 -0
- package/build/gen_blocks.js +1 -0
- package/build/test_expect.js +1 -0
- package/build/test_input.js +1 -0
- package/build.py +647 -0
- package/cleanup.sh +101 -0
- package/core/block.js +1837 -0
- package/core/block_animations.js +107 -0
- package/core/block_drag_surface.js +299 -0
- package/core/block_dragger.js +424 -0
- package/core/block_events.js +531 -0
- package/core/block_render_svg_horizontal.js +892 -0
- package/core/block_render_svg_vertical.js +1734 -0
- package/core/block_svg.js +1355 -0
- package/core/blockly.js +620 -0
- package/core/blocks.js +37 -0
- package/core/bubble.js +664 -0
- package/core/bubble_dragger.js +285 -0
- package/core/colours.js +177 -0
- package/core/comment.js +293 -0
- package/core/comment_events.js +539 -0
- package/core/connection.js +776 -0
- package/core/connection_db.js +300 -0
- package/core/constants.js +408 -0
- package/core/contextmenu.js +534 -0
- package/core/css.js +1354 -0
- package/core/data_category.js +490 -0
- package/core/dragged_connection_manager.js +260 -0
- package/core/dropdowndiv.js +408 -0
- package/core/events.js +429 -0
- package/core/events_abstract.js +113 -0
- package/core/extensions.js +450 -0
- package/core/field.js +810 -0
- package/core/field_angle.js +410 -0
- package/core/field_checkbox.js +133 -0
- package/core/field_colour.js +253 -0
- package/core/field_colour_slider.js +387 -0
- package/core/field_date.js +353 -0
- package/core/field_dropdown.js +447 -0
- package/core/field_iconmenu.js +309 -0
- package/core/field_image.js +200 -0
- package/core/field_label.js +136 -0
- package/core/field_label_serializable.js +125 -0
- package/core/field_matrix.js +566 -0
- package/core/field_note.js +850 -0
- package/core/field_number.js +366 -0
- package/core/field_numberdropdown.js +77 -0
- package/core/field_textdropdown.js +164 -0
- package/core/field_textinput.js +675 -0
- package/core/field_textinput_removable.js +105 -0
- package/core/field_variable.js +385 -0
- package/core/field_variable_getter.js +185 -0
- package/core/field_vertical_separator.js +161 -0
- package/core/flyout_base.js +935 -0
- package/core/flyout_button.js +324 -0
- package/core/flyout_dragger.js +83 -0
- package/core/flyout_extension_category_header.js +159 -0
- package/core/flyout_horizontal.js +475 -0
- package/core/flyout_vertical.js +770 -0
- package/core/generator.js +426 -0
- package/core/gesture.js +1010 -0
- package/core/grid.js +227 -0
- package/core/icon.js +205 -0
- package/core/inject.js +496 -0
- package/core/input.js +285 -0
- package/core/insertion_marker_manager.js +678 -0
- package/core/intersection_observer.js +102 -0
- package/core/msg.js +62 -0
- package/core/mutator.js +426 -0
- package/core/names.js +198 -0
- package/core/options.js +244 -0
- package/core/procedures.js +739 -0
- package/core/rendered_connection.js +417 -0
- package/core/scratch_block_comment.js +646 -0
- package/core/scratch_blocks_utils.js +246 -0
- package/core/scratch_bubble.js +699 -0
- package/core/scratch_events.js +131 -0
- package/core/scratch_msgs.js +85 -0
- package/core/scrollbar.js +875 -0
- package/core/toolbox.js +803 -0
- package/core/tooltip.js +337 -0
- package/core/touch.js +226 -0
- package/core/trashcan.js +343 -0
- package/core/ui_events.js +91 -0
- package/core/ui_menu_utils.js +68 -0
- package/core/utils.js +825 -0
- package/core/variable_events.js +259 -0
- package/core/variable_map.js +415 -0
- package/core/variable_model.js +116 -0
- package/core/variables.js +674 -0
- package/core/warning.js +199 -0
- package/core/widgetdiv.js +344 -0
- package/core/workspace.js +673 -0
- package/core/workspace_audio.js +170 -0
- package/core/workspace_comment.js +426 -0
- package/core/workspace_comment_render_svg.js +706 -0
- package/core/workspace_comment_svg.js +609 -0
- package/core/workspace_drag_surface_svg.js +195 -0
- package/core/workspace_dragger.js +132 -0
- package/core/workspace_svg.js +2382 -0
- package/core/xml.js +930 -0
- package/core/zoom_controls.js +301 -0
- package/dist/horizontal.js +222 -0
- package/dist/vertical.js +222 -0
- package/dist/web/horizontal.js +232 -0
- package/dist/web/vertical.js +232 -0
- package/i18n/common.py +234 -0
- package/i18n/create_messages.py +162 -0
- package/i18n/create_scratch_msgs.js +69 -0
- package/i18n/dedup_json.py +73 -0
- package/i18n/js_to_json.js +46 -0
- package/i18n/js_to_json.py +136 -0
- package/i18n/json_to_js.js +52 -0
- package/i18n/json_to_js.py +185 -0
- package/i18n/sync_tx_translations.js +111 -0
- package/i18n/test_scratch_msgs.js +87 -0
- package/i18n/tests.py +47 -0
- package/i18n/xliff_to_json.py +232 -0
- package/local_build.sh +70 -0
- package/media/click.mp3 +0 -0
- package/media/click.ogg +0 -0
- package/media/click.wav +0 -0
- package/media/comment-arrow-down.svg +10 -0
- package/media/comment-arrow-up.svg +10 -0
- package/media/delete-x.svg +10 -0
- package/media/delete.mp3 +0 -0
- package/media/delete.ogg +0 -0
- package/media/delete.wav +0 -0
- package/media/dropdown-arrow-dark.svg +1 -0
- package/media/dropdown-arrow.svg +1 -0
- package/media/extensions/microbit-block-icon.svg +130 -0
- package/media/extensions/music-block-icon.svg +17 -0
- package/media/extensions/pen-block-icon.svg +19 -0
- package/media/extensions/wedo2-block-icon.svg +36 -0
- package/media/eyedropper.svg +22 -0
- package/media/green-flag.svg +17 -0
- package/media/handclosed.cur +0 -0
- package/media/handdelete.cur +0 -0
- package/media/handopen.cur +0 -0
- package/media/icons/arrow.svg +12 -0
- package/media/icons/arrow_button.svg +1 -0
- package/media/icons/control_forever.svg +1 -0
- package/media/icons/control_repeat.svg +1 -0
- package/media/icons/control_stop.svg +1 -0
- package/media/icons/control_wait.svg +1 -0
- package/media/icons/event_broadcast_blue.svg +1 -0
- package/media/icons/event_broadcast_coral.svg +1 -0
- package/media/icons/event_broadcast_green.svg +1 -0
- package/media/icons/event_broadcast_magenta.svg +1 -0
- package/media/icons/event_broadcast_orange.svg +1 -0
- package/media/icons/event_broadcast_purple.svg +1 -0
- package/media/icons/event_when-broadcast-received_blue.svg +1 -0
- package/media/icons/event_when-broadcast-received_coral.svg +1 -0
- package/media/icons/event_when-broadcast-received_green.svg +1 -0
- package/media/icons/event_when-broadcast-received_magenta.svg +1 -0
- package/media/icons/event_when-broadcast-received_orange.svg +1 -0
- package/media/icons/event_when-broadcast-received_purple.svg +1 -0
- package/media/icons/event_whenflagclicked.svg +1 -0
- package/media/icons/remove.svg +19 -0
- package/media/icons/set-led_blue.svg +1 -0
- package/media/icons/set-led_coral.svg +1 -0
- package/media/icons/set-led_green.svg +1 -0
- package/media/icons/set-led_magenta.svg +1 -0
- package/media/icons/set-led_mystery.svg +1 -0
- package/media/icons/set-led_orange.svg +1 -0
- package/media/icons/set-led_purple.svg +1 -0
- package/media/icons/set-led_white.svg +1 -0
- package/media/icons/set-led_yellow.svg +1 -0
- package/media/icons/wedo_motor-clockwise.svg +1 -0
- package/media/icons/wedo_motor-counterclockwise.svg +1 -0
- package/media/icons/wedo_motor-speed_fast.svg +1 -0
- package/media/icons/wedo_motor-speed_med.svg +1 -0
- package/media/icons/wedo_motor-speed_slow.svg +1 -0
- package/media/icons/wedo_when-distance_close.svg +1 -0
- package/media/icons/wedo_when-tilt-backward.svg +1 -0
- package/media/icons/wedo_when-tilt-forward.svg +1 -0
- package/media/icons/wedo_when-tilt-left.svg +1 -0
- package/media/icons/wedo_when-tilt-right.svg +1 -0
- package/media/icons/wedo_when-tilt.svg +1 -0
- package/media/repeat.svg +18 -0
- package/media/rotate-left.svg +1 -0
- package/media/rotate-right.svg +1 -0
- package/media/sprites.png +0 -0
- package/media/status-not-ready.svg +13 -0
- package/media/status-ready.svg +13 -0
- package/media/zoom-in.svg +1 -0
- package/media/zoom-out.svg +1 -0
- package/media/zoom-reset.svg +1 -0
- package/msg/js/en.js +290 -0
- package/msg/json/en.json +285 -0
- package/msg/messages.js +359 -0
- package/msg/scratch_msgs.js +22969 -0
- package/package.json +60 -0
- package/pull_from_blockly.sh +151 -0
- package/renovate.json5 +15 -0
- package/shim/blockly_compressed_horizontal-blocks_compressed.js +1 -0
- package/shim/blockly_compressed_horizontal.Blockly.js +1 -0
- package/shim/blockly_compressed_horizontal.goog.js +1 -0
- package/shim/blockly_compressed_horizontal.js +1 -0
- package/shim/blockly_compressed_vertical-blocks_compressed.js +1 -0
- package/shim/blockly_compressed_vertical.Blockly.js +1 -0
- package/shim/blockly_compressed_vertical.goog.js +1 -0
- package/shim/blockly_compressed_vertical.js +1 -0
- package/shim/blocks_compressed_horizontal-blockly_compressed_horizontal-messages.js +1 -0
- package/shim/blocks_compressed_horizontal.js +1 -0
- package/shim/blocks_compressed_vertical-blockly_compressed_vertical-messages.js +1 -0
- package/shim/blocks_compressed_vertical.js +1 -0
- package/shim/gh-pages.js +1 -0
- package/shim/horizontal.js +1 -0
- package/shim/index.js +17 -0
- package/shim/vertical.js +1 -0
- package/universal-python.js +82 -0
|
@@ -0,0 +1,850 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Visual Blocks Editor
|
|
4
|
+
*
|
|
5
|
+
* Copyright 2018 Massachusetts Institute of Technology
|
|
6
|
+
* All rights reserved.
|
|
7
|
+
*
|
|
8
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
9
|
+
* you may not use this file except in compliance with the License.
|
|
10
|
+
* You may obtain a copy of the License at
|
|
11
|
+
*
|
|
12
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
13
|
+
*
|
|
14
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
15
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
16
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
17
|
+
* See the License for the specific language governing permissions and
|
|
18
|
+
* limitations under the License.
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* @fileoverview Note input field, for selecting a musical note on a piano.
|
|
23
|
+
* @author ericr@media.mit.edu (Eric Rosenbaum)
|
|
24
|
+
*/
|
|
25
|
+
'use strict';
|
|
26
|
+
|
|
27
|
+
goog.provide('Blockly.FieldNote');
|
|
28
|
+
|
|
29
|
+
goog.require('Blockly.DropDownDiv');
|
|
30
|
+
goog.require('Blockly.FieldTextInput');
|
|
31
|
+
goog.require('goog.math');
|
|
32
|
+
goog.require('goog.userAgent');
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Class for a note input field, for selecting a musical note on a piano.
|
|
36
|
+
* @param {(string|number)=} opt_value The initial content of the field. The
|
|
37
|
+
* value should cast to a number, and if it does not, '0' will be used.
|
|
38
|
+
* @param {Function=} opt_validator An optional function that is called
|
|
39
|
+
* to validate any constraints on what the user entered. Takes the new
|
|
40
|
+
* text as an argument and returns the accepted text or null to abort
|
|
41
|
+
* the change.
|
|
42
|
+
* @extends {Blockly.FieldTextInput}
|
|
43
|
+
* @constructor
|
|
44
|
+
*/
|
|
45
|
+
Blockly.FieldNote = function(opt_value, opt_validator) {
|
|
46
|
+
opt_value = (opt_value && !isNaN(opt_value)) ? String(opt_value) : '0';
|
|
47
|
+
Blockly.FieldNote.superClass_.constructor.call(
|
|
48
|
+
this, opt_value, opt_validator);
|
|
49
|
+
this.addArgType('note');
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Width of the field. Computed when drawing it, and used for animation.
|
|
53
|
+
* @type {number}
|
|
54
|
+
* @private
|
|
55
|
+
*/
|
|
56
|
+
this.fieldEditorWidth_ = 0;
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Height of the field. Computed when drawing it.
|
|
60
|
+
* @type {number}
|
|
61
|
+
* @private
|
|
62
|
+
*/
|
|
63
|
+
this.fieldEditorHeight_ = 0;
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* The piano SVG.
|
|
67
|
+
* @type {SVGElement}
|
|
68
|
+
* @private
|
|
69
|
+
*/
|
|
70
|
+
this.pianoSVG_ = null;
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Array of SVG elements representing the clickable piano keys.
|
|
74
|
+
* @type {!Array<SVGElement>}
|
|
75
|
+
* @private
|
|
76
|
+
*/
|
|
77
|
+
this.keySVGs_ = [];
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Note name indicator at the top of the field.
|
|
81
|
+
* @type {SVGElement}
|
|
82
|
+
* @private
|
|
83
|
+
*/
|
|
84
|
+
this.noteNameText_ = null;
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Note name indicator on the low C key.
|
|
88
|
+
* @type {SVGElement}
|
|
89
|
+
* @private
|
|
90
|
+
*/
|
|
91
|
+
this.lowCText_ = null;
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Note name indicator on the low C key.
|
|
95
|
+
* @type {SVGElement}
|
|
96
|
+
* @private
|
|
97
|
+
*/
|
|
98
|
+
this.highCText_ = null;
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Octave number of the currently displayed range of keys.
|
|
102
|
+
* @type {number}
|
|
103
|
+
* @private
|
|
104
|
+
*/
|
|
105
|
+
this.displayedOctave_ = null;
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Current animation position of the piano SVG, as it shifts left or right to
|
|
109
|
+
* change octaves.
|
|
110
|
+
* @type {number}
|
|
111
|
+
* @private
|
|
112
|
+
*/
|
|
113
|
+
this.animationPos_ = 0;
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Target position for the animation as the piano SVG shifts left or right.
|
|
117
|
+
* @type {number}
|
|
118
|
+
* @private
|
|
119
|
+
*/
|
|
120
|
+
this.animationTarget_ = 0;
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* A flag indicating that the mouse is currently down. Used in combination with
|
|
124
|
+
* mouse enter events to update the key selection while dragging.
|
|
125
|
+
* @type {boolean}
|
|
126
|
+
* @private
|
|
127
|
+
*/
|
|
128
|
+
this.mouseIsDown_ = false;
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* An array of wrappers for mouse down events on piano keys.
|
|
132
|
+
* @type {!Array.<!Array>}
|
|
133
|
+
* @private
|
|
134
|
+
*/
|
|
135
|
+
this.mouseDownWrappers_ = [];
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* A wrapper for the mouse up event.
|
|
139
|
+
* @type {!Array.<!Array>}
|
|
140
|
+
* @private
|
|
141
|
+
*/
|
|
142
|
+
this.mouseUpWrapper_ = null;
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* An array of wrappers for mouse enter events on piano keys.
|
|
146
|
+
* @type {!Array.<!Array>}
|
|
147
|
+
* @private
|
|
148
|
+
*/
|
|
149
|
+
this.mouseEnterWrappers_ = [];
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* A wrapper for the mouse down event on the octave down button.
|
|
153
|
+
* @type {!Array.<!Array>}
|
|
154
|
+
* @private
|
|
155
|
+
*/
|
|
156
|
+
this.octaveDownMouseDownWrapper_ = null;
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* A wrapper for the mouse down event on the octave up button.
|
|
160
|
+
* @type {!Array.<!Array>}
|
|
161
|
+
* @private
|
|
162
|
+
*/
|
|
163
|
+
this.octaveUpMouseDownWrapper_ = null;
|
|
164
|
+
};
|
|
165
|
+
goog.inherits(Blockly.FieldNote, Blockly.FieldTextInput);
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Inset in pixels of content displayed in the field, caused by parent properties.
|
|
169
|
+
* The inset is actually determined by the CSS property blocklyDropDownDiv- it is
|
|
170
|
+
* the sum of the padding and border thickness.
|
|
171
|
+
*/
|
|
172
|
+
Blockly.FieldNote.INSET = 5;
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Height of the top area of the field, in px.
|
|
176
|
+
* @type {number}
|
|
177
|
+
* @const
|
|
178
|
+
*/
|
|
179
|
+
Blockly.FieldNote.TOP_MENU_HEIGHT = 32 - Blockly.FieldNote.INSET;
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Padding on the top and sides of the field, in px.
|
|
183
|
+
* @type {number}
|
|
184
|
+
* @const
|
|
185
|
+
*/
|
|
186
|
+
Blockly.FieldNote.EDGE_PADDING = 1;
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Height of the drop shadow on the piano, in px.
|
|
190
|
+
* @type {number}
|
|
191
|
+
* @const
|
|
192
|
+
*/
|
|
193
|
+
Blockly.FieldNote.SHADOW_HEIGHT = 4;
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Color for the shadow on the piano.
|
|
197
|
+
* @type {string}
|
|
198
|
+
* @const
|
|
199
|
+
*/
|
|
200
|
+
Blockly.FieldNote.SHADOW_COLOR = '#000';
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* Opacity for the shadow on the piano.
|
|
204
|
+
* @type {string}
|
|
205
|
+
* @const
|
|
206
|
+
*/
|
|
207
|
+
Blockly.FieldNote.SHADOW_OPACITY = .2;
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* A color for the white piano keys.
|
|
211
|
+
* @type {string}
|
|
212
|
+
* @const
|
|
213
|
+
*/
|
|
214
|
+
Blockly.FieldNote.WHITE_KEY_COLOR = '#FFFFFF';
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* A color for the black piano keys.
|
|
218
|
+
* @type {string}
|
|
219
|
+
* @const
|
|
220
|
+
*/
|
|
221
|
+
Blockly.FieldNote.BLACK_KEY_COLOR = '#323133';
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* A color for stroke around black piano keys.
|
|
225
|
+
* @type {string}
|
|
226
|
+
* @const
|
|
227
|
+
*/
|
|
228
|
+
Blockly.FieldNote.BLACK_KEY_STROKE = '#555555';
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* A color for the selected state of a piano key.
|
|
232
|
+
* @type {string}
|
|
233
|
+
* @const
|
|
234
|
+
*/
|
|
235
|
+
Blockly.FieldNote.KEY_SELECTED_COLOR = '#b0d6ff';
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* The number of white keys in one octave on the piano.
|
|
239
|
+
* @type {number}
|
|
240
|
+
* @const
|
|
241
|
+
*/
|
|
242
|
+
Blockly.FieldNote.NUM_WHITE_KEYS = 8;
|
|
243
|
+
|
|
244
|
+
/**
|
|
245
|
+
* Height of a white piano key, in px.
|
|
246
|
+
* @type {string}
|
|
247
|
+
* @const
|
|
248
|
+
*/
|
|
249
|
+
Blockly.FieldNote.WHITE_KEY_HEIGHT = 72;
|
|
250
|
+
|
|
251
|
+
/**
|
|
252
|
+
* Width of a white piano key, in px.
|
|
253
|
+
* @type {string}
|
|
254
|
+
* @const
|
|
255
|
+
*/
|
|
256
|
+
Blockly.FieldNote.WHITE_KEY_WIDTH = 40;
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* Height of a black piano key, in px.
|
|
260
|
+
* @type {string}
|
|
261
|
+
* @const
|
|
262
|
+
*/
|
|
263
|
+
Blockly.FieldNote.BLACK_KEY_HEIGHT = 40;
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* Width of a black piano key, in px.
|
|
267
|
+
* @type {string}
|
|
268
|
+
* @const
|
|
269
|
+
*/
|
|
270
|
+
Blockly.FieldNote.BLACK_KEY_WIDTH = 32;
|
|
271
|
+
|
|
272
|
+
/**
|
|
273
|
+
* Radius of the curved bottom corner of a piano key, in px.
|
|
274
|
+
* @type {string}
|
|
275
|
+
* @const
|
|
276
|
+
*/
|
|
277
|
+
Blockly.FieldNote.KEY_RADIUS = 6;
|
|
278
|
+
|
|
279
|
+
/**
|
|
280
|
+
* Bottom padding for the labels on C keys.
|
|
281
|
+
* @type {string}
|
|
282
|
+
* @const
|
|
283
|
+
*/
|
|
284
|
+
Blockly.FieldNote.KEY_LABEL_PADDING = 8;
|
|
285
|
+
|
|
286
|
+
/**
|
|
287
|
+
* An array of objects with data describing the keys on the piano.
|
|
288
|
+
* @type {Array.<{name: String, pitch: Number, isBlack: boolean}>}
|
|
289
|
+
* @const
|
|
290
|
+
*/
|
|
291
|
+
Blockly.FieldNote.KEY_INFO = [
|
|
292
|
+
{name: 'C', pitch: 0},
|
|
293
|
+
{name: 'C♯', pitch: 1, isBlack: true},
|
|
294
|
+
{name: 'D', pitch: 2},
|
|
295
|
+
{name: 'E♭', pitch: 3, isBlack: true},
|
|
296
|
+
{name: 'E', pitch: 4},
|
|
297
|
+
{name: 'F', pitch: 5},
|
|
298
|
+
{name: 'F♯', pitch: 6, isBlack: true},
|
|
299
|
+
{name: 'G', pitch: 7},
|
|
300
|
+
{name: 'G♯', pitch: 8, isBlack: true},
|
|
301
|
+
{name: 'A', pitch: 9},
|
|
302
|
+
{name: 'B♭', pitch: 10, isBlack: true},
|
|
303
|
+
{name: 'B', pitch: 11},
|
|
304
|
+
{name: 'C', pitch: 12}
|
|
305
|
+
];
|
|
306
|
+
|
|
307
|
+
/**
|
|
308
|
+
* The MIDI note number of the highest note selectable on the piano.
|
|
309
|
+
* @type {number}
|
|
310
|
+
* @const
|
|
311
|
+
*/
|
|
312
|
+
Blockly.FieldNote.MAX_NOTE = 130;
|
|
313
|
+
|
|
314
|
+
/**
|
|
315
|
+
* The fraction of the distance to the target location to move the piano at each
|
|
316
|
+
* step of the animation.
|
|
317
|
+
* @type {number}
|
|
318
|
+
* @const
|
|
319
|
+
*/
|
|
320
|
+
Blockly.FieldNote.ANIMATION_FRACTION = 0.2;
|
|
321
|
+
|
|
322
|
+
/**
|
|
323
|
+
* Path to the arrow svg icon, used on the octave buttons.
|
|
324
|
+
* @type {string}
|
|
325
|
+
* @const
|
|
326
|
+
*/
|
|
327
|
+
Blockly.FieldNote.ARROW_SVG_PATH = 'icons/arrow_button.svg';
|
|
328
|
+
|
|
329
|
+
/**
|
|
330
|
+
* The size of the square octave buttons.
|
|
331
|
+
* @type {number}
|
|
332
|
+
* @const
|
|
333
|
+
*/
|
|
334
|
+
Blockly.FieldNote.OCTAVE_BUTTON_SIZE = 32;
|
|
335
|
+
|
|
336
|
+
/**
|
|
337
|
+
* Construct a FieldNote from a JSON arg object.
|
|
338
|
+
* @param {!Object} options A JSON object with options.
|
|
339
|
+
* @returns {!Blockly.FieldNote} The new field instance.
|
|
340
|
+
* @package
|
|
341
|
+
* @nocollapse
|
|
342
|
+
*/
|
|
343
|
+
Blockly.FieldNote.fromJson = function(options) {
|
|
344
|
+
return new Blockly.FieldNote(options['note']);
|
|
345
|
+
};
|
|
346
|
+
|
|
347
|
+
/**
|
|
348
|
+
* Clean up this FieldNote, as well as the inherited FieldTextInput.
|
|
349
|
+
* @return {!Function} Closure to call on destruction of the WidgetDiv.
|
|
350
|
+
* @private
|
|
351
|
+
*/
|
|
352
|
+
Blockly.FieldNote.prototype.dispose_ = function() {
|
|
353
|
+
var thisField = this;
|
|
354
|
+
return function() {
|
|
355
|
+
Blockly.FieldNote.superClass_.dispose_.call(thisField)();
|
|
356
|
+
thisField.mouseDownWrappers_.forEach(function(wrapper) {
|
|
357
|
+
Blockly.unbindEvent_(wrapper);
|
|
358
|
+
});
|
|
359
|
+
thisField.mouseEnterWrappers_.forEach(function(wrapper) {
|
|
360
|
+
Blockly.unbindEvent_(wrapper);
|
|
361
|
+
});
|
|
362
|
+
if (thisField.mouseUpWrapper_) {
|
|
363
|
+
Blockly.unbindEvent_(thisField.mouseUpWrapper_);
|
|
364
|
+
}
|
|
365
|
+
if (thisField.octaveDownMouseDownWrapper_) {
|
|
366
|
+
Blockly.unbindEvent_(thisField.octaveDownMouseDownWrapper_);
|
|
367
|
+
}
|
|
368
|
+
if (thisField.octaveUpMouseDownWrapper_) {
|
|
369
|
+
Blockly.unbindEvent_(thisField.octaveUpMouseDownWrapper_);
|
|
370
|
+
}
|
|
371
|
+
this.pianoSVG_ = null;
|
|
372
|
+
this.keySVGs_.length = 0;
|
|
373
|
+
this.noteNameText_ = null;
|
|
374
|
+
this.lowCText_ = null;
|
|
375
|
+
this.highCText_ = null;
|
|
376
|
+
};
|
|
377
|
+
};
|
|
378
|
+
|
|
379
|
+
/**
|
|
380
|
+
* Show a field with piano keys.
|
|
381
|
+
* @private
|
|
382
|
+
*/
|
|
383
|
+
Blockly.FieldNote.prototype.showEditor_ = function() {
|
|
384
|
+
// Mobile browsers have issues with in-line textareas (focus & keyboards).
|
|
385
|
+
Blockly.FieldNote.superClass_.showEditor_.call(this, this.useTouchInteraction_);
|
|
386
|
+
|
|
387
|
+
// If there is an existing drop-down someone else owns, hide it immediately and clear it.
|
|
388
|
+
Blockly.DropDownDiv.hideWithoutAnimation();
|
|
389
|
+
Blockly.DropDownDiv.clearContent();
|
|
390
|
+
|
|
391
|
+
// Build the SVG DOM.
|
|
392
|
+
var div = Blockly.DropDownDiv.getContentDiv();
|
|
393
|
+
|
|
394
|
+
this.fieldEditorWidth_ = Blockly.FieldNote.NUM_WHITE_KEYS * Blockly.FieldNote.WHITE_KEY_WIDTH +
|
|
395
|
+
Blockly.FieldNote.EDGE_PADDING;
|
|
396
|
+
this.fieldEditorHeight_ = Blockly.FieldNote.TOP_MENU_HEIGHT +
|
|
397
|
+
Blockly.FieldNote.WHITE_KEY_HEIGHT +
|
|
398
|
+
Blockly.FieldNote.EDGE_PADDING;
|
|
399
|
+
|
|
400
|
+
var svg = Blockly.utils.createSvgElement('svg', {
|
|
401
|
+
'xmlns': 'http://www.w3.org/2000/svg',
|
|
402
|
+
'xmlns:html': 'http://www.w3.org/1999/xhtml',
|
|
403
|
+
'xmlns:xlink': 'http://www.w3.org/1999/xlink',
|
|
404
|
+
'version': '1.1',
|
|
405
|
+
'height': this.fieldEditorHeight_ + 'px',
|
|
406
|
+
'width': this.fieldEditorWidth_ + 'px'
|
|
407
|
+
}, div);
|
|
408
|
+
|
|
409
|
+
// Add the white and black keys
|
|
410
|
+
// Since we are adding the keys from left to right in order, they need
|
|
411
|
+
// to be in two groups in order to layer correctly.
|
|
412
|
+
this.pianoSVG_ = Blockly.utils.createSvgElement('g', {}, svg);
|
|
413
|
+
var whiteKeyGroup = Blockly.utils.createSvgElement('g', {}, this.pianoSVG_);
|
|
414
|
+
var blackKeyGroup = Blockly.utils.createSvgElement('g', {}, this.pianoSVG_);
|
|
415
|
+
|
|
416
|
+
// Add three piano octaves, so we can animate moving up or down an octave.
|
|
417
|
+
// Only the middle octave gets bound to events.
|
|
418
|
+
this.keySVGs_ = [];
|
|
419
|
+
this.addPianoOctave_(-this.fieldEditorWidth_ + Blockly.FieldNote.EDGE_PADDING,
|
|
420
|
+
whiteKeyGroup, blackKeyGroup, null);
|
|
421
|
+
this.addPianoOctave_(0, whiteKeyGroup, blackKeyGroup, this.keySVGs_);
|
|
422
|
+
this.addPianoOctave_(this.fieldEditorWidth_ - Blockly.FieldNote.EDGE_PADDING,
|
|
423
|
+
whiteKeyGroup, blackKeyGroup, null);
|
|
424
|
+
|
|
425
|
+
// Note name indicator at the top of the field
|
|
426
|
+
this.noteNameText_ = Blockly.utils.createSvgElement('text',
|
|
427
|
+
{
|
|
428
|
+
'x': this.fieldEditorWidth_ / 2,
|
|
429
|
+
'y': Blockly.FieldNote.TOP_MENU_HEIGHT / 2,
|
|
430
|
+
'class': 'blocklyText',
|
|
431
|
+
'text-anchor': 'middle',
|
|
432
|
+
'dominant-baseline': 'middle',
|
|
433
|
+
}, svg);
|
|
434
|
+
|
|
435
|
+
// Note names on the low and high C keys
|
|
436
|
+
var lowCX = Blockly.FieldNote.WHITE_KEY_WIDTH / 2;
|
|
437
|
+
this.lowCText_ = this.addCKeyLabel_(lowCX, svg);
|
|
438
|
+
var highCX = lowCX + (Blockly.FieldNote.WHITE_KEY_WIDTH *
|
|
439
|
+
(Blockly.FieldNote.NUM_WHITE_KEYS - 1));
|
|
440
|
+
this.highCText_ = this.addCKeyLabel_(highCX, svg);
|
|
441
|
+
|
|
442
|
+
// Horizontal line at the top of the keys
|
|
443
|
+
Blockly.utils.createSvgElement('line',
|
|
444
|
+
{
|
|
445
|
+
'stroke': this.sourceBlock_.getColourTertiary(),
|
|
446
|
+
'x1': 0,
|
|
447
|
+
'y1': Blockly.FieldNote.TOP_MENU_HEIGHT,
|
|
448
|
+
'x2': this.fieldEditorWidth_,
|
|
449
|
+
'y2': Blockly.FieldNote.TOP_MENU_HEIGHT
|
|
450
|
+
}, svg);
|
|
451
|
+
|
|
452
|
+
// Drop shadow at the top of the keys
|
|
453
|
+
Blockly.utils.createSvgElement('rect',
|
|
454
|
+
{
|
|
455
|
+
'x': 0,
|
|
456
|
+
'y': Blockly.FieldNote.TOP_MENU_HEIGHT,
|
|
457
|
+
'width': this.fieldEditorWidth_,
|
|
458
|
+
'height': Blockly.FieldNote.SHADOW_HEIGHT,
|
|
459
|
+
'fill': Blockly.FieldNote.SHADOW_COLOR,
|
|
460
|
+
'fill-opacity': Blockly.FieldNote.SHADOW_OPACITY
|
|
461
|
+
}, svg);
|
|
462
|
+
|
|
463
|
+
// Octave buttons
|
|
464
|
+
this.octaveDownButton = this.addOctaveButton_(0, true, svg);
|
|
465
|
+
this.octaveUpButton = this.addOctaveButton_(
|
|
466
|
+
(this.fieldEditorWidth_ + Blockly.FieldNote.INSET * 2) -
|
|
467
|
+
Blockly.FieldNote.OCTAVE_BUTTON_SIZE, false, svg);
|
|
468
|
+
|
|
469
|
+
this.octaveDownMouseDownWrapper_ =
|
|
470
|
+
Blockly.bindEvent_(this.octaveDownButton, 'mousedown', this, function() {
|
|
471
|
+
this.changeOctaveBy_(-1);
|
|
472
|
+
});
|
|
473
|
+
this.octaveUpMouseDownWrapper_ =
|
|
474
|
+
Blockly.bindEvent_(this.octaveUpButton, 'mousedown', this,function() {
|
|
475
|
+
this.changeOctaveBy_(1);
|
|
476
|
+
});
|
|
477
|
+
Blockly.DropDownDiv.setColour(this.sourceBlock_.parentBlock_.getColour(),
|
|
478
|
+
this.sourceBlock_.getColourTertiary());
|
|
479
|
+
Blockly.DropDownDiv.setCategory(this.sourceBlock_.parentBlock_.getCategory());
|
|
480
|
+
Blockly.DropDownDiv.showPositionedByBlock(this, this.sourceBlock_);
|
|
481
|
+
|
|
482
|
+
this.updateSelection_();
|
|
483
|
+
};
|
|
484
|
+
|
|
485
|
+
/**
|
|
486
|
+
* Add one octave of piano keys drawn using SVG.
|
|
487
|
+
* @param {number} x The x position of the left edge of this octave of keys.
|
|
488
|
+
* @param {SVGElement} whiteKeyGroup The group for all white piano keys.
|
|
489
|
+
* @param {SvgElement} blackKeyGroup The group for all black piano keys.
|
|
490
|
+
* @param {!Array.<SvgElement>} keySVGarray An array containing all the key SVGs.
|
|
491
|
+
* @private
|
|
492
|
+
*/
|
|
493
|
+
Blockly.FieldNote.prototype.addPianoOctave_ = function(x, whiteKeyGroup, blackKeyGroup, keySVGarray) {
|
|
494
|
+
var xIncrement, width, height, fill, stroke, group;
|
|
495
|
+
x += Blockly.FieldNote.EDGE_PADDING / 2;
|
|
496
|
+
var y = Blockly.FieldNote.TOP_MENU_HEIGHT;
|
|
497
|
+
for (var i = 0; i < Blockly.FieldNote.KEY_INFO.length; i++) {
|
|
498
|
+
// Draw a black or white key
|
|
499
|
+
if (Blockly.FieldNote.KEY_INFO[i].isBlack) {
|
|
500
|
+
// Black keys are shifted back half a key
|
|
501
|
+
x -= Blockly.FieldNote.BLACK_KEY_WIDTH / 2;
|
|
502
|
+
xIncrement = Blockly.FieldNote.BLACK_KEY_WIDTH / 2;
|
|
503
|
+
width = Blockly.FieldNote.BLACK_KEY_WIDTH;
|
|
504
|
+
height = Blockly.FieldNote.BLACK_KEY_HEIGHT;
|
|
505
|
+
fill = Blockly.FieldNote.BLACK_KEY_COLOR;
|
|
506
|
+
stroke = Blockly.FieldNote.BLACK_KEY_STROKE;
|
|
507
|
+
group = blackKeyGroup;
|
|
508
|
+
} else {
|
|
509
|
+
xIncrement = Blockly.FieldNote.WHITE_KEY_WIDTH;
|
|
510
|
+
width = Blockly.FieldNote.WHITE_KEY_WIDTH;
|
|
511
|
+
height = Blockly.FieldNote.WHITE_KEY_HEIGHT;
|
|
512
|
+
fill = Blockly.FieldNote.WHITE_KEY_COLOR;
|
|
513
|
+
stroke = this.sourceBlock_.getColourTertiary();
|
|
514
|
+
group = whiteKeyGroup;
|
|
515
|
+
}
|
|
516
|
+
var attr = {
|
|
517
|
+
'd': this.getPianoKeyPath_(x, y, width, height),
|
|
518
|
+
'fill': fill,
|
|
519
|
+
'stroke': stroke
|
|
520
|
+
};
|
|
521
|
+
x += xIncrement;
|
|
522
|
+
|
|
523
|
+
var keySVG = Blockly.utils.createSvgElement('path', attr, group);
|
|
524
|
+
|
|
525
|
+
if (keySVGarray) {
|
|
526
|
+
keySVGarray[i] = keySVG;
|
|
527
|
+
keySVG.setAttribute('data-pitch', Blockly.FieldNote.KEY_INFO[i].pitch);
|
|
528
|
+
keySVG.setAttribute('data-name', Blockly.FieldNote.KEY_INFO[i].name);
|
|
529
|
+
keySVG.setAttribute('data-isBlack', Blockly.FieldNote.KEY_INFO[i].isBlack);
|
|
530
|
+
|
|
531
|
+
this.mouseDownWrappers_[i] =
|
|
532
|
+
Blockly.bindEvent_(keySVG, 'mousedown', this, this.onMouseDownOnKey_);
|
|
533
|
+
this.mouseEnterWrappers_[i] =
|
|
534
|
+
Blockly.bindEvent_(keySVG, 'mouseenter', this, this.onMouseEnter_);
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
};
|
|
538
|
+
|
|
539
|
+
/**
|
|
540
|
+
* Construct the SVG path string for a piano key shape: a rectangle with rounded
|
|
541
|
+
* corners at the bottom.
|
|
542
|
+
* @param {number} x the x position for the key.
|
|
543
|
+
* @param {number} y the y position for the key.
|
|
544
|
+
* @param {number} width the width of the key.
|
|
545
|
+
* @param {number} height the height of the key.
|
|
546
|
+
* @returns {string} the SVG path as a string.
|
|
547
|
+
* @private
|
|
548
|
+
*/
|
|
549
|
+
Blockly.FieldNote.prototype.getPianoKeyPath_ = function(x, y, width, height) {
|
|
550
|
+
return 'M' + x + ' ' + y + ' ' +
|
|
551
|
+
'L' + x + ' ' + (y + height - Blockly.FieldNote.KEY_RADIUS) + ' ' +
|
|
552
|
+
'Q' + x + ' ' + (y + height) + ' ' +
|
|
553
|
+
(x + Blockly.FieldNote.KEY_RADIUS) + ' ' + (y + height) + ' ' +
|
|
554
|
+
'L' + (x + width - Blockly.FieldNote.KEY_RADIUS) + ' ' + (y + height) + ' ' +
|
|
555
|
+
'Q' + (x + width) + ' ' + (y + height) + ' ' +
|
|
556
|
+
(x + width) + ' ' + (y + height - Blockly.FieldNote.KEY_RADIUS) + ' ' +
|
|
557
|
+
'L' + (x + width) + ' ' + y + ' ' +
|
|
558
|
+
'L' + x + ' ' + y;
|
|
559
|
+
};
|
|
560
|
+
|
|
561
|
+
/**
|
|
562
|
+
* Add a button for switching the displayed octave of the piano up or down.
|
|
563
|
+
* @param {number} x The x position of the button.
|
|
564
|
+
* @param {boolean} flipped If true, the icon should be flipped.
|
|
565
|
+
* @param {SvgElement} svg The svg element to add the buttons to.
|
|
566
|
+
* @returns {SvgElement} A group containing the button SVG elements.
|
|
567
|
+
* @private
|
|
568
|
+
*/
|
|
569
|
+
Blockly.FieldNote.prototype.addOctaveButton_ = function(x, flipped, svg) {
|
|
570
|
+
var group = Blockly.utils.createSvgElement('g', {}, svg);
|
|
571
|
+
var imageSize = Blockly.FieldNote.OCTAVE_BUTTON_SIZE;
|
|
572
|
+
var arrow = Blockly.utils.createSvgElement('image',
|
|
573
|
+
{
|
|
574
|
+
'width': imageSize,
|
|
575
|
+
'height': imageSize,
|
|
576
|
+
'x': x - Blockly.FieldNote.INSET,
|
|
577
|
+
'y': -1 * Blockly.FieldNote.INSET
|
|
578
|
+
}, group);
|
|
579
|
+
arrow.setAttributeNS(
|
|
580
|
+
'http://www.w3.org/1999/xlink',
|
|
581
|
+
'xlink:href',
|
|
582
|
+
Blockly.mainWorkspace.options.pathToMedia + Blockly.FieldNote.ARROW_SVG_PATH
|
|
583
|
+
);
|
|
584
|
+
Blockly.utils.createSvgElement('line',
|
|
585
|
+
{
|
|
586
|
+
'stroke': this.sourceBlock_.getColourTertiary(),
|
|
587
|
+
'x1': x - Blockly.FieldNote.INSET,
|
|
588
|
+
'y1': 0,
|
|
589
|
+
'x2': x - Blockly.FieldNote.INSET,
|
|
590
|
+
'y2': Blockly.FieldNote.TOP_MENU_HEIGHT - Blockly.FieldNote.INSET
|
|
591
|
+
}, group);
|
|
592
|
+
if (flipped) {
|
|
593
|
+
var translateX = -1 * Blockly.FieldNote.OCTAVE_BUTTON_SIZE + (Blockly.FieldNote.INSET * 2);
|
|
594
|
+
group.setAttribute('transform', 'scale(-1, 1) ' +
|
|
595
|
+
'translate(' + translateX + ', 0)');
|
|
596
|
+
}
|
|
597
|
+
return group;
|
|
598
|
+
};
|
|
599
|
+
|
|
600
|
+
/**
|
|
601
|
+
* Add an SVG text label for display on the C keys of the piano.
|
|
602
|
+
* @param {number} x The x position for the label.
|
|
603
|
+
* @param {SvgElement} svg The SVG element to add the label to.
|
|
604
|
+
* @returns {SvgElement} The SVG element containing the label.
|
|
605
|
+
* @private
|
|
606
|
+
*/
|
|
607
|
+
Blockly.FieldNote.prototype.addCKeyLabel_ = function(x, svg) {
|
|
608
|
+
return Blockly.utils.createSvgElement('text',
|
|
609
|
+
{
|
|
610
|
+
'x': x,
|
|
611
|
+
'y': Blockly.FieldNote.TOP_MENU_HEIGHT + Blockly.FieldNote.WHITE_KEY_HEIGHT -
|
|
612
|
+
Blockly.FieldNote.KEY_LABEL_PADDING,
|
|
613
|
+
'class': 'scratchNotePickerKeyLabel',
|
|
614
|
+
'text-anchor': 'middle'
|
|
615
|
+
}, svg);
|
|
616
|
+
};
|
|
617
|
+
|
|
618
|
+
/**
|
|
619
|
+
* Set the visibility of the C key labels.
|
|
620
|
+
* @param {boolean} visible If true, set labels to be visible.
|
|
621
|
+
* @private
|
|
622
|
+
*/
|
|
623
|
+
Blockly.FieldNote.prototype.setCKeyLabelsVisible_ = function(visible) {
|
|
624
|
+
if (visible) {
|
|
625
|
+
this.fadeSvgToOpacity_(this.lowCText_, 1);
|
|
626
|
+
this.fadeSvgToOpacity_(this.highCText_, 1);
|
|
627
|
+
} else {
|
|
628
|
+
this.fadeSvgToOpacity_(this.lowCText_, 0);
|
|
629
|
+
this.fadeSvgToOpacity_(this.highCText_, 0);
|
|
630
|
+
}
|
|
631
|
+
};
|
|
632
|
+
|
|
633
|
+
/**
|
|
634
|
+
* Animate an SVG to fade it in or out to a target opacity.
|
|
635
|
+
* @param {SvgElement} svg The SVG element to apply the fade to.
|
|
636
|
+
* @param {number} opacity The target opacity.
|
|
637
|
+
* @private
|
|
638
|
+
*/
|
|
639
|
+
Blockly.FieldNote.prototype.fadeSvgToOpacity_ = function(svg, opacity) {
|
|
640
|
+
svg.setAttribute('style', 'opacity: ' + opacity + '; transition: opacity 0.1s;');
|
|
641
|
+
};
|
|
642
|
+
|
|
643
|
+
/**
|
|
644
|
+
* Handle the mouse down event on a piano key.
|
|
645
|
+
* @param {!Event} e Mouse down event.
|
|
646
|
+
* @private
|
|
647
|
+
*/
|
|
648
|
+
Blockly.FieldNote.prototype.onMouseDownOnKey_ = function(e) {
|
|
649
|
+
this.mouseIsDown_ = true;
|
|
650
|
+
this.mouseUpWrapper_ = Blockly.bindEvent_(document.body, 'mouseup', this, this.onMouseUp_);
|
|
651
|
+
this.selectNoteWithMouseEvent_(e);
|
|
652
|
+
};
|
|
653
|
+
|
|
654
|
+
/**
|
|
655
|
+
* Handle the mouse up event following a mouse down on a piano key.
|
|
656
|
+
* @private
|
|
657
|
+
*/
|
|
658
|
+
Blockly.FieldNote.prototype.onMouseUp_ = function() {
|
|
659
|
+
this.mouseIsDown_ = false;
|
|
660
|
+
Blockly.unbindEvent_(this.mouseUpWrapper_);
|
|
661
|
+
};
|
|
662
|
+
|
|
663
|
+
/**
|
|
664
|
+
* Handle the event when the mouse enters a piano key.
|
|
665
|
+
* @param {!Event} e Mouse enter event.
|
|
666
|
+
* @private
|
|
667
|
+
*/
|
|
668
|
+
Blockly.FieldNote.prototype.onMouseEnter_ = function(e) {
|
|
669
|
+
if (this.mouseIsDown_) {
|
|
670
|
+
this.selectNoteWithMouseEvent_(e);
|
|
671
|
+
}
|
|
672
|
+
};
|
|
673
|
+
|
|
674
|
+
/**
|
|
675
|
+
* Use the data in a mouse event to select a new note, and play it.
|
|
676
|
+
* @param {!Event} e Mouse event.
|
|
677
|
+
* @private
|
|
678
|
+
*/
|
|
679
|
+
Blockly.FieldNote.prototype.selectNoteWithMouseEvent_ = function(e) {
|
|
680
|
+
var newNoteNum = Number(e.target.getAttribute('data-pitch')) + this.displayedOctave_ * 12;
|
|
681
|
+
this.setNoteNum_(newNoteNum);
|
|
682
|
+
this.playNoteInternal_();
|
|
683
|
+
};
|
|
684
|
+
|
|
685
|
+
/**
|
|
686
|
+
* Play a note, by calling the externally overriden play note function.
|
|
687
|
+
* @private
|
|
688
|
+
*/
|
|
689
|
+
Blockly.FieldNote.prototype.playNoteInternal_ = function() {
|
|
690
|
+
if (Blockly.FieldNote.playNote_) {
|
|
691
|
+
Blockly.FieldNote.playNote_(
|
|
692
|
+
this.getValue(),
|
|
693
|
+
this.sourceBlock_.parentBlock_.getCategory()
|
|
694
|
+
);
|
|
695
|
+
}
|
|
696
|
+
};
|
|
697
|
+
|
|
698
|
+
/**
|
|
699
|
+
* Function to play a musical note corresponding to the key selected.
|
|
700
|
+
* Overridden externally.
|
|
701
|
+
* @param {number} noteNum the MIDI note number to play.
|
|
702
|
+
* @param {string} id An id to select a scratch extension to play the note.
|
|
703
|
+
* @private
|
|
704
|
+
*/
|
|
705
|
+
Blockly.FieldNote.playNote_ = function(/* noteNum, id*/) {
|
|
706
|
+
return;
|
|
707
|
+
};
|
|
708
|
+
|
|
709
|
+
/**
|
|
710
|
+
* Change the selected note by a number of octaves, and start the animation.
|
|
711
|
+
* @param {number} octaves The number of octaves to change by.
|
|
712
|
+
* @private
|
|
713
|
+
*/
|
|
714
|
+
Blockly.FieldNote.prototype.changeOctaveBy_ = function(octaves) {
|
|
715
|
+
this.displayedOctave_ += octaves;
|
|
716
|
+
if (this.displayedOctave_ < 0) {
|
|
717
|
+
this.displayedOctave_ = 0;
|
|
718
|
+
return;
|
|
719
|
+
}
|
|
720
|
+
var maxOctave = Math.floor(Blockly.FieldNote.MAX_NOTE / 12);
|
|
721
|
+
if (this.displayedOctave_ > maxOctave) {
|
|
722
|
+
this.displayedOctave_ = maxOctave;
|
|
723
|
+
return;
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
var newNote = Number(this.getText()) + (octaves * 12);
|
|
727
|
+
this.setNoteNum_(newNote);
|
|
728
|
+
|
|
729
|
+
this.animationTarget_ = this.fieldEditorWidth_ * octaves * -1;
|
|
730
|
+
this.animationPos_ = 0;
|
|
731
|
+
this.stepOctaveAnimation_();
|
|
732
|
+
this.setCKeyLabelsVisible_(false);
|
|
733
|
+
};
|
|
734
|
+
|
|
735
|
+
/**
|
|
736
|
+
* Animate the piano up or down an octave by sliding it to the left or right.
|
|
737
|
+
* @private
|
|
738
|
+
*/
|
|
739
|
+
Blockly.FieldNote.prototype.stepOctaveAnimation_ = function() {
|
|
740
|
+
var absDiff = Math.abs(this.animationPos_ - this.animationTarget_);
|
|
741
|
+
if (absDiff < 1) {
|
|
742
|
+
this.pianoSVG_.setAttribute('transform', 'translate(0, 0)');
|
|
743
|
+
this.setCKeyLabelsVisible_(true);
|
|
744
|
+
this.playNoteInternal_();
|
|
745
|
+
return;
|
|
746
|
+
}
|
|
747
|
+
this.animationPos_ += (this.animationTarget_ - this.animationPos_) *
|
|
748
|
+
Blockly.FieldNote.ANIMATION_FRACTION;
|
|
749
|
+
this.pianoSVG_.setAttribute('transform', 'translate(' + this.animationPos_ + ',0)');
|
|
750
|
+
requestAnimationFrame(this.stepOctaveAnimation_.bind(this));
|
|
751
|
+
};
|
|
752
|
+
|
|
753
|
+
/**
|
|
754
|
+
* Set the selected note number, and update the piano display and the input field.
|
|
755
|
+
* @param {number} noteNum The MIDI note number to select.
|
|
756
|
+
* @private
|
|
757
|
+
*/
|
|
758
|
+
Blockly.FieldNote.prototype.setNoteNum_ = function(noteNum) {
|
|
759
|
+
noteNum = this.callValidator(noteNum);
|
|
760
|
+
this.setValue(noteNum);
|
|
761
|
+
Blockly.FieldTextInput.htmlInput_.value = noteNum;
|
|
762
|
+
};
|
|
763
|
+
|
|
764
|
+
/**
|
|
765
|
+
* Sets the text in this field. Triggers a rerender of the source block, and
|
|
766
|
+
* updates the selection on the field.
|
|
767
|
+
* @param {?string} text New text.
|
|
768
|
+
*/
|
|
769
|
+
Blockly.FieldNote.prototype.setText = function(text) {
|
|
770
|
+
Blockly.FieldNote.superClass_.setText.call(this, text);
|
|
771
|
+
if (!this.textElement_) {
|
|
772
|
+
// Not rendered yet.
|
|
773
|
+
return;
|
|
774
|
+
}
|
|
775
|
+
this.updateSelection_();
|
|
776
|
+
// Cached width is obsolete. Clear it.
|
|
777
|
+
this.size_.width = 0;
|
|
778
|
+
};
|
|
779
|
+
|
|
780
|
+
/**
|
|
781
|
+
* For a MIDI note number, find the index of the corresponding piano key.
|
|
782
|
+
* @param {number} noteNum The note number.
|
|
783
|
+
* @returns {number} The index of the piano key.
|
|
784
|
+
* @private
|
|
785
|
+
*/
|
|
786
|
+
Blockly.FieldNote.prototype.noteNumToKeyIndex_ = function(noteNum) {
|
|
787
|
+
return Math.floor(noteNum) - (this.displayedOctave_ * 12);
|
|
788
|
+
};
|
|
789
|
+
|
|
790
|
+
/**
|
|
791
|
+
* Update the selected note and labels on the field.
|
|
792
|
+
* @private
|
|
793
|
+
*/
|
|
794
|
+
Blockly.FieldNote.prototype.updateSelection_ = function() {
|
|
795
|
+
var noteNum = Number(this.getText());
|
|
796
|
+
|
|
797
|
+
// If the note is outside the currently displayed octave, update it
|
|
798
|
+
if (this.displayedOctave_ == null ||
|
|
799
|
+
noteNum > ((this.displayedOctave_ * 12) + 12) ||
|
|
800
|
+
noteNum < (this.displayedOctave_ * 12)) {
|
|
801
|
+
this.displayedOctave_ = Math.floor(noteNum / 12);
|
|
802
|
+
}
|
|
803
|
+
|
|
804
|
+
var index = this.noteNumToKeyIndex_(noteNum);
|
|
805
|
+
|
|
806
|
+
// Clear the highlight on all keys
|
|
807
|
+
this.keySVGs_.forEach(function(svg) {
|
|
808
|
+
var isBlack = svg.getAttribute('data-isBlack');
|
|
809
|
+
if (isBlack === 'true') {
|
|
810
|
+
svg.setAttribute('fill', Blockly.FieldNote.BLACK_KEY_COLOR);
|
|
811
|
+
} else {
|
|
812
|
+
svg.setAttribute('fill', Blockly.FieldNote.WHITE_KEY_COLOR);
|
|
813
|
+
}
|
|
814
|
+
});
|
|
815
|
+
// Set the highlight on the selected key
|
|
816
|
+
if (this.keySVGs_[index]) {
|
|
817
|
+
this.keySVGs_[index].setAttribute('fill', Blockly.FieldNote.KEY_SELECTED_COLOR);
|
|
818
|
+
// Update the note name text
|
|
819
|
+
var noteName = Blockly.FieldNote.KEY_INFO[index].name;
|
|
820
|
+
this.noteNameText_.textContent = noteName + ' (' + Math.floor(noteNum) + ')';
|
|
821
|
+
// Update the low and high C note names
|
|
822
|
+
var lowCNum = this.displayedOctave_ * 12;
|
|
823
|
+
this.lowCText_.textContent = 'C(' + lowCNum + ')';
|
|
824
|
+
this.highCText_.textContent = 'C(' + (lowCNum + 12) + ')';
|
|
825
|
+
}
|
|
826
|
+
};
|
|
827
|
+
|
|
828
|
+
/**
|
|
829
|
+
* Ensure that only a valid MIDI note number may be entered.
|
|
830
|
+
* @param {string} text The user's text.
|
|
831
|
+
* @return {?string} A string representing a valid note number, or null if invalid.
|
|
832
|
+
*/
|
|
833
|
+
Blockly.FieldNote.prototype.classValidator = function(text) {
|
|
834
|
+
if (text === null) {
|
|
835
|
+
return null;
|
|
836
|
+
}
|
|
837
|
+
var n = parseFloat(text || 0);
|
|
838
|
+
if (isNaN(n)) {
|
|
839
|
+
return null;
|
|
840
|
+
}
|
|
841
|
+
if (n < 0) {
|
|
842
|
+
n = 0;
|
|
843
|
+
}
|
|
844
|
+
if (n > Blockly.FieldNote.MAX_NOTE) {
|
|
845
|
+
n = Blockly.FieldNote.MAX_NOTE;
|
|
846
|
+
}
|
|
847
|
+
return String(n);
|
|
848
|
+
};
|
|
849
|
+
|
|
850
|
+
Blockly.Field.register('field_note', Blockly.FieldNote);
|