blockly 7.20211209.2 → 8.0.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/blockly.d.ts +18963 -18432
- package/blockly.min.js +852 -844
- package/blockly_compressed.js +669 -664
- package/blockly_compressed.js.map +1 -1
- package/blocks/blocks.js +47 -0
- package/blocks/colour.js +13 -3
- package/blocks/lists.js +22 -13
- package/blocks/logic.js +13 -3
- package/blocks/loops.js +24 -11
- package/blocks/math.js +12 -3
- package/blocks/procedures.js +41 -27
- package/blocks/text.js +22 -13
- package/blocks/variables.js +14 -3
- package/blocks/variables_dynamic.js +13 -3
- package/blocks_compressed.js +146 -141
- package/blocks_compressed.js.map +1 -1
- package/core/block.js +1869 -1814
- package/core/block_drag_surface.js +201 -200
- package/core/block_dragger.js +377 -373
- package/core/block_svg.js +1593 -1479
- package/core/blockly.js +8 -22
- package/core/blocks.js +9 -2
- package/core/browser_events.js +22 -5
- package/core/bubble.js +841 -797
- package/core/bubble_dragger.js +213 -206
- package/core/bump_objects.js +2 -2
- package/core/clipboard.js +9 -9
- package/core/comment.js +353 -332
- package/core/common.js +46 -17
- package/core/component_manager.js +181 -174
- package/core/config.js +87 -0
- package/core/connection.js +595 -584
- package/core/connection_checker.js +242 -244
- package/core/connection_db.js +235 -230
- package/core/contextmenu.js +9 -6
- package/core/contextmenu_items.js +1 -2
- package/core/contextmenu_registry.js +93 -89
- package/core/css.js +474 -474
- package/core/delete_area.js +45 -42
- package/core/drag_target.js +57 -56
- package/core/dropdowndiv.js +153 -163
- package/core/events/events.js +2 -2
- package/core/events/events_abstract.js +89 -77
- package/core/events/events_block_base.js +37 -36
- package/core/events/events_block_change.js +130 -124
- package/core/events/events_block_create.js +73 -71
- package/core/events/events_block_delete.js +84 -82
- package/core/events/events_block_drag.js +50 -49
- package/core/events/events_block_move.js +147 -140
- package/core/events/events_bubble_open.js +51 -50
- package/core/events/events_click.js +48 -44
- package/core/events/events_comment_base.js +72 -69
- package/core/events/events_comment_change.js +63 -61
- package/core/events/events_comment_create.js +44 -42
- package/core/events/events_comment_delete.js +42 -40
- package/core/events/events_comment_move.js +106 -104
- package/core/events/events_marker_move.js +65 -64
- package/core/events/events_selected.js +46 -45
- package/core/events/events_theme_change.js +36 -35
- package/core/events/events_toolbox_item_select.js +46 -45
- package/core/events/events_trashcan_open.js +37 -36
- package/core/events/events_ui.js +47 -46
- package/core/events/events_ui_base.js +30 -29
- package/core/events/events_var_base.js +37 -36
- package/core/events/events_var_create.js +50 -48
- package/core/events/events_var_delete.js +50 -48
- package/core/events/events_var_rename.js +51 -49
- package/core/events/events_viewport.js +66 -65
- package/core/events/utils.js +29 -14
- package/core/events/workspace_events.js +49 -55
- package/core/extensions.js +4 -3
- package/core/field.js +1061 -997
- package/core/field_angle.js +462 -442
- package/core/field_checkbox.js +194 -182
- package/core/field_colour.js +519 -505
- package/core/field_dropdown.js +617 -598
- package/core/field_image.js +229 -220
- package/core/field_label.js +102 -91
- package/core/field_label_serializable.js +42 -41
- package/core/field_multilineinput.js +372 -358
- package/core/field_number.js +272 -253
- package/core/field_textinput.js +499 -467
- package/core/field_variable.js +458 -420
- package/core/flyout_base.js +1005 -952
- package/core/flyout_button.js +277 -260
- package/core/flyout_horizontal.js +304 -302
- package/core/flyout_metrics_manager.js +64 -64
- package/core/flyout_vertical.js +306 -300
- package/core/generator.js +459 -446
- package/core/gesture.js +829 -813
- package/core/grid.js +166 -163
- package/core/icon.js +168 -159
- package/core/inject.js +7 -5
- package/core/input.js +257 -248
- package/core/insertion_marker_manager.js +655 -624
- package/core/internal_constants.js +0 -129
- package/core/keyboard_nav/ast_node.js +605 -596
- package/core/keyboard_nav/basic_cursor.js +166 -165
- package/core/keyboard_nav/cursor.js +99 -97
- package/core/keyboard_nav/marker.js +83 -79
- package/core/keyboard_nav/tab_navigate_cursor.js +18 -23
- package/core/marker_manager.js +153 -141
- package/core/menu.js +377 -372
- package/core/menuitem.js +223 -217
- package/core/metrics_manager.js +403 -390
- package/core/mutator.js +468 -437
- package/core/names.js +229 -188
- package/core/options.js +290 -284
- package/core/procedures.js +29 -17
- package/core/registry.js +19 -16
- package/core/rendered_connection.js +482 -463
- package/core/renderers/common/block_rendering.js +9 -3
- package/core/renderers/common/constants.js +1119 -1112
- package/core/renderers/common/debug.js +14 -0
- package/core/renderers/common/debugger.js +338 -316
- package/core/renderers/common/drawer.js +380 -370
- package/core/renderers/common/i_path_object.js +2 -2
- package/core/renderers/common/info.js +626 -618
- package/core/renderers/common/marker_svg.js +579 -541
- package/core/renderers/common/path_object.js +203 -200
- package/core/renderers/common/renderer.js +220 -218
- package/core/renderers/geras/constants.js +36 -36
- package/core/renderers/geras/drawer.js +155 -147
- package/core/renderers/geras/highlight_constants.js +244 -238
- package/core/renderers/geras/highlighter.js +231 -179
- package/core/renderers/geras/info.js +392 -369
- package/core/renderers/geras/measurables/inline_input.js +25 -19
- package/core/renderers/geras/measurables/statement_input.js +23 -17
- package/core/renderers/geras/path_object.js +106 -121
- package/core/renderers/geras/renderer.js +96 -98
- package/core/renderers/measurables/base.js +30 -18
- package/core/renderers/measurables/bottom_row.js +83 -80
- package/core/renderers/measurables/connection.js +22 -15
- package/core/renderers/measurables/external_value_input.js +35 -22
- package/core/renderers/measurables/field.js +35 -20
- package/core/renderers/measurables/hat.js +18 -13
- package/core/renderers/measurables/icon.js +24 -17
- package/core/renderers/measurables/in_row_spacer.js +15 -13
- package/core/renderers/measurables/inline_input.js +43 -33
- package/core/renderers/measurables/input_connection.js +41 -28
- package/core/renderers/measurables/input_row.js +50 -44
- package/core/renderers/measurables/jagged_edge.js +14 -12
- package/core/renderers/measurables/next_connection.js +16 -14
- package/core/renderers/measurables/output_connection.js +26 -20
- package/core/renderers/measurables/previous_connection.js +16 -15
- package/core/renderers/measurables/round_corner.js +20 -18
- package/core/renderers/measurables/row.js +184 -168
- package/core/renderers/measurables/spacer_row.js +38 -23
- package/core/renderers/measurables/square_corner.js +18 -16
- package/core/renderers/measurables/statement_input.js +23 -20
- package/core/renderers/measurables/top_row.js +88 -85
- package/core/renderers/minimalist/constants.js +8 -7
- package/core/renderers/minimalist/drawer.js +11 -10
- package/core/renderers/minimalist/info.js +18 -18
- package/core/renderers/minimalist/renderer.js +40 -39
- package/core/renderers/thrasos/info.js +258 -248
- package/core/renderers/thrasos/renderer.js +20 -20
- package/core/renderers/zelos/constants.js +898 -873
- package/core/renderers/zelos/drawer.js +186 -169
- package/core/renderers/zelos/info.js +502 -479
- package/core/renderers/zelos/marker_svg.js +129 -115
- package/core/renderers/zelos/measurables/bottom_row.js +31 -30
- package/core/renderers/zelos/measurables/inputs.js +22 -21
- package/core/renderers/zelos/measurables/row_elements.js +14 -13
- package/core/renderers/zelos/measurables/top_row.js +34 -33
- package/core/renderers/zelos/path_object.js +181 -180
- package/core/renderers/zelos/renderer.js +91 -92
- package/core/scrollbar.js +759 -713
- package/core/scrollbar_pair.js +250 -245
- package/core/serialization/blocks.js +19 -9
- package/core/serialization/workspaces.js +3 -2
- package/core/shortcut_registry.js +286 -277
- package/core/sprites.js +31 -0
- package/core/theme.js +135 -141
- package/core/theme_manager.js +147 -143
- package/core/toolbox/category.js +602 -576
- package/core/toolbox/collapsible_category.js +226 -227
- package/core/toolbox/separator.js +70 -61
- package/core/toolbox/toolbox.js +934 -927
- package/core/toolbox/toolbox_item.js +115 -99
- package/core/tooltip.js +108 -35
- package/core/touch.js +8 -3
- package/core/touch_gesture.js +254 -251
- package/core/trashcan.js +606 -595
- package/core/utils/coordinate.js +97 -95
- package/core/utils/dom.js +2 -2
- package/core/utils/global.js +2 -0
- package/core/utils/rect.js +41 -37
- package/core/utils/sentinel.js +25 -0
- package/core/utils/size.js +30 -27
- package/core/utils/svg.js +18 -16
- package/core/variable_map.js +325 -341
- package/core/variable_model.js +55 -54
- package/core/variables.js +9 -2
- package/core/variables_dynamic.js +3 -1
- package/core/warning.js +126 -120
- package/core/widgetdiv.js +4 -4
- package/core/workspace.js +685 -664
- package/core/workspace_audio.js +124 -118
- package/core/workspace_comment.js +308 -298
- package/core/workspace_comment_svg.js +1029 -951
- package/core/workspace_drag_surface_svg.js +147 -140
- package/core/workspace_dragger.js +70 -71
- package/core/workspace_svg.js +2322 -2297
- package/core/xml.js +30 -20
- package/core/zoom_controls.js +431 -439
- package/dart_compressed.js +40 -43
- package/dart_compressed.js.map +1 -1
- package/generators/dart/colour.js +56 -64
- package/generators/dart/lists.js +61 -50
- package/generators/dart/math.js +160 -148
- package/generators/dart/text.js +83 -61
- package/generators/javascript/colour.js +37 -34
- package/generators/javascript/lists.js +50 -43
- package/generators/javascript/math.js +123 -139
- package/generators/javascript/text.js +67 -81
- package/generators/lua/colour.js +25 -23
- package/generators/lua/lists.js +97 -69
- package/generators/lua/logic.js +1 -2
- package/generators/lua/math.js +182 -144
- package/generators/lua/text.js +116 -99
- package/generators/php/colour.js +38 -32
- package/generators/php/lists.js +109 -89
- package/generators/php/math.js +90 -81
- package/generators/php/text.js +63 -61
- package/generators/python/colour.js +18 -18
- package/generators/python/lists.js +38 -30
- package/generators/python/loops.js +12 -8
- package/generators/python/math.js +104 -106
- package/generators/python/text.js +34 -30
- package/javascript_compressed.js +37 -39
- package/javascript_compressed.js.map +1 -1
- package/lua_compressed.js +39 -42
- package/lua_compressed.js.map +1 -1
- package/msg/az.js +2 -2
- package/msg/be.js +4 -4
- package/msg/cs.js +15 -15
- package/msg/de.js +1 -1
- package/msg/diq.js +1 -1
- package/msg/eo.js +1 -1
- package/msg/es.js +1 -1
- package/msg/fa.js +1 -1
- package/msg/fr.js +4 -4
- package/msg/he.js +1 -1
- package/msg/hr.js +2 -2
- package/msg/hy.js +2 -2
- package/msg/id.js +12 -12
- package/msg/inh.js +14 -14
- package/msg/ja.js +7 -7
- package/msg/lv.js +29 -29
- package/msg/pa.js +3 -3
- package/msg/smn.js +436 -0
- package/msg/te.js +1 -1
- package/msg/yue.js +1 -1
- package/msg/zh-hans.js +3 -3
- package/msg/zh-hant.js +3 -3
- package/package.json +7 -6
- package/php_compressed.js +38 -42
- package/php_compressed.js.map +1 -1
- package/python_compressed.js +26 -25
- package/python_compressed.js.map +1 -1
- package/blocks/all.js +0 -23
package/core/field_angle.js
CHANGED
|
@@ -19,110 +19,493 @@ const Css = goog.require('Blockly.Css');
|
|
|
19
19
|
const WidgetDiv = goog.require('Blockly.WidgetDiv');
|
|
20
20
|
const browserEvents = goog.require('Blockly.browserEvents');
|
|
21
21
|
const dom = goog.require('Blockly.utils.dom');
|
|
22
|
+
const dropDownDiv = goog.require('Blockly.dropDownDiv');
|
|
22
23
|
const fieldRegistry = goog.require('Blockly.fieldRegistry');
|
|
23
24
|
const math = goog.require('Blockly.utils.math');
|
|
24
|
-
const object = goog.require('Blockly.utils.object');
|
|
25
25
|
const userAgent = goog.require('Blockly.utils.userAgent');
|
|
26
|
-
const {
|
|
26
|
+
const {Field} = goog.require('Blockly.Field');
|
|
27
27
|
const {FieldTextInput} = goog.require('Blockly.FieldTextInput');
|
|
28
28
|
const {KeyCodes} = goog.require('Blockly.utils.KeyCodes');
|
|
29
|
+
/* eslint-disable-next-line no-unused-vars */
|
|
30
|
+
const {Sentinel} = goog.requireType('Blockly.utils.Sentinel');
|
|
29
31
|
const {Svg} = goog.require('Blockly.utils.Svg');
|
|
30
32
|
|
|
31
33
|
|
|
32
34
|
/**
|
|
33
35
|
* Class for an editable angle field.
|
|
34
|
-
* @param {string|number=} opt_value The initial value of the field. Should cast
|
|
35
|
-
* to a number. Defaults to 0.
|
|
36
|
-
* @param {Function=} opt_validator A function that is called to validate
|
|
37
|
-
* changes to the field's value. Takes in a number & returns a
|
|
38
|
-
* validated number, or null to abort the change.
|
|
39
|
-
* @param {Object=} opt_config A map of options used to configure the field.
|
|
40
|
-
* See the [field creation documentation]{@link
|
|
41
|
-
* https://developers.google.com/blockly/guides/create-custom-blocks/fields/built-in-fields/angle#creation}
|
|
42
|
-
* for a list of properties this parameter supports.
|
|
43
36
|
* @extends {FieldTextInput}
|
|
44
|
-
* @constructor
|
|
45
37
|
* @alias Blockly.FieldAngle
|
|
46
38
|
*/
|
|
47
|
-
|
|
39
|
+
class FieldAngle extends FieldTextInput {
|
|
48
40
|
/**
|
|
49
|
-
*
|
|
50
|
-
*
|
|
51
|
-
*
|
|
52
|
-
*
|
|
53
|
-
*
|
|
41
|
+
* @param {(string|number|!Sentinel)=} opt_value The initial value of
|
|
42
|
+
* the field. Should cast to a number. Defaults to 0.
|
|
43
|
+
* Also accepts Field.SKIP_SETUP if you wish to skip setup (only used by
|
|
44
|
+
* subclasses that want to handle configuration and setting the field
|
|
45
|
+
* value after their own constructors have run).
|
|
46
|
+
* @param {Function=} opt_validator A function that is called to validate
|
|
47
|
+
* changes to the field's value. Takes in a number & returns a
|
|
48
|
+
* validated number, or null to abort the change.
|
|
49
|
+
* @param {Object=} opt_config A map of options used to configure the field.
|
|
50
|
+
* See the [field creation documentation]{@link
|
|
51
|
+
* https://developers.google.com/blockly/guides/create-custom-blocks/fields/built-in-fields/angle#creation}
|
|
52
|
+
* for a list of properties this parameter supports.
|
|
54
53
|
*/
|
|
55
|
-
|
|
54
|
+
constructor(opt_value, opt_validator, opt_config) {
|
|
55
|
+
super(Field.SKIP_SETUP);
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Should the angle increase as the angle picker is moved clockwise (true)
|
|
59
|
+
* or counterclockwise (false)
|
|
60
|
+
* @see FieldAngle.CLOCKWISE
|
|
61
|
+
* @type {boolean}
|
|
62
|
+
* @private
|
|
63
|
+
*/
|
|
64
|
+
this.clockwise_ = FieldAngle.CLOCKWISE;
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* The offset of zero degrees (and all other angles).
|
|
68
|
+
* @see FieldAngle.OFFSET
|
|
69
|
+
* @type {number}
|
|
70
|
+
* @private
|
|
71
|
+
*/
|
|
72
|
+
this.offset_ = FieldAngle.OFFSET;
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* The maximum angle to allow before wrapping.
|
|
76
|
+
* @see FieldAngle.WRAP
|
|
77
|
+
* @type {number}
|
|
78
|
+
* @private
|
|
79
|
+
*/
|
|
80
|
+
this.wrap_ = FieldAngle.WRAP;
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* The amount to round angles to when using a mouse or keyboard nav input.
|
|
84
|
+
* @see FieldAngle.ROUND
|
|
85
|
+
* @type {number}
|
|
86
|
+
* @private
|
|
87
|
+
*/
|
|
88
|
+
this.round_ = FieldAngle.ROUND;
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* The angle picker's SVG element.
|
|
92
|
+
* @type {?SVGElement}
|
|
93
|
+
* @private
|
|
94
|
+
*/
|
|
95
|
+
this.editor_ = null;
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* The angle picker's gauge path depending on the value.
|
|
99
|
+
* @type {?SVGElement}
|
|
100
|
+
*/
|
|
101
|
+
this.gauge_ = null;
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* The angle picker's line drawn representing the value's angle.
|
|
105
|
+
* @type {?SVGElement}
|
|
106
|
+
*/
|
|
107
|
+
this.line_ = null;
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* The degree symbol for this field.
|
|
111
|
+
* @type {SVGTSpanElement}
|
|
112
|
+
* @protected
|
|
113
|
+
*/
|
|
114
|
+
this.symbol_ = null;
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Wrapper click event data.
|
|
118
|
+
* @type {?browserEvents.Data}
|
|
119
|
+
* @private
|
|
120
|
+
*/
|
|
121
|
+
this.clickWrapper_ = null;
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Surface click event data.
|
|
125
|
+
* @type {?browserEvents.Data}
|
|
126
|
+
* @private
|
|
127
|
+
*/
|
|
128
|
+
this.clickSurfaceWrapper_ = null;
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Surface mouse move event data.
|
|
132
|
+
* @type {?browserEvents.Data}
|
|
133
|
+
* @private
|
|
134
|
+
*/
|
|
135
|
+
this.moveSurfaceWrapper_ = null;
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Serializable fields are saved by the serializer, non-serializable fields
|
|
139
|
+
* are not. Editable fields should also be serializable.
|
|
140
|
+
* @type {boolean}
|
|
141
|
+
*/
|
|
142
|
+
this.SERIALIZABLE = true;
|
|
143
|
+
|
|
144
|
+
if (opt_value === Field.SKIP_SETUP) return;
|
|
145
|
+
if (opt_config) this.configure_(opt_config);
|
|
146
|
+
this.setValue(opt_value);
|
|
147
|
+
if (opt_validator) this.setValidator(opt_validator);
|
|
148
|
+
}
|
|
56
149
|
|
|
57
150
|
/**
|
|
58
|
-
*
|
|
59
|
-
* @
|
|
60
|
-
* @
|
|
61
|
-
* @
|
|
151
|
+
* Configure the field based on the given map of options.
|
|
152
|
+
* @param {!Object} config A map of options to configure the field based on.
|
|
153
|
+
* @protected
|
|
154
|
+
* @override
|
|
155
|
+
*/
|
|
156
|
+
configure_(config) {
|
|
157
|
+
super.configure_(config);
|
|
158
|
+
|
|
159
|
+
switch (config['mode']) {
|
|
160
|
+
case 'compass':
|
|
161
|
+
this.clockwise_ = true;
|
|
162
|
+
this.offset_ = 90;
|
|
163
|
+
break;
|
|
164
|
+
case 'protractor':
|
|
165
|
+
// This is the default mode, so we could do nothing. But just to
|
|
166
|
+
// future-proof, we'll set it anyway.
|
|
167
|
+
this.clockwise_ = false;
|
|
168
|
+
this.offset_ = 0;
|
|
169
|
+
break;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// Allow individual settings to override the mode setting.
|
|
173
|
+
const clockwise = config['clockwise'];
|
|
174
|
+
if (typeof clockwise === 'boolean') {
|
|
175
|
+
this.clockwise_ = clockwise;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// If these are passed as null then we should leave them on the default.
|
|
179
|
+
let offset = config['offset'];
|
|
180
|
+
if (offset !== null) {
|
|
181
|
+
offset = Number(offset);
|
|
182
|
+
if (!isNaN(offset)) {
|
|
183
|
+
this.offset_ = offset;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
let wrap = config['wrap'];
|
|
187
|
+
if (wrap !== null) {
|
|
188
|
+
wrap = Number(wrap);
|
|
189
|
+
if (!isNaN(wrap)) {
|
|
190
|
+
this.wrap_ = wrap;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
let round = config['round'];
|
|
194
|
+
if (round !== null) {
|
|
195
|
+
round = Number(round);
|
|
196
|
+
if (!isNaN(round)) {
|
|
197
|
+
this.round_ = round;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* Create the block UI for this field.
|
|
204
|
+
* @package
|
|
62
205
|
*/
|
|
63
|
-
|
|
206
|
+
initView() {
|
|
207
|
+
super.initView();
|
|
208
|
+
// Add the degree symbol to the left of the number, even in RTL (issue
|
|
209
|
+
// #2380)
|
|
210
|
+
this.symbol_ = dom.createSvgElement(Svg.TSPAN, {}, null);
|
|
211
|
+
this.symbol_.appendChild(document.createTextNode('\u00B0'));
|
|
212
|
+
this.textElement_.appendChild(this.symbol_);
|
|
213
|
+
}
|
|
64
214
|
|
|
65
215
|
/**
|
|
66
|
-
*
|
|
67
|
-
* @
|
|
68
|
-
* @
|
|
69
|
-
* @private
|
|
216
|
+
* Updates the graph when the field rerenders.
|
|
217
|
+
* @protected
|
|
218
|
+
* @override
|
|
70
219
|
*/
|
|
71
|
-
|
|
220
|
+
render_() {
|
|
221
|
+
super.render_();
|
|
222
|
+
this.updateGraph_();
|
|
223
|
+
}
|
|
72
224
|
|
|
73
225
|
/**
|
|
74
|
-
*
|
|
75
|
-
* @
|
|
76
|
-
*
|
|
226
|
+
* Create and show the angle field's editor.
|
|
227
|
+
* @param {Event=} opt_e Optional mouse event that triggered the field to
|
|
228
|
+
* open, or undefined if triggered programmatically.
|
|
229
|
+
* @protected
|
|
230
|
+
*/
|
|
231
|
+
showEditor_(opt_e) {
|
|
232
|
+
// Mobile browsers have issues with in-line textareas (focus & keyboards).
|
|
233
|
+
const noFocus = userAgent.MOBILE || userAgent.ANDROID || userAgent.IPAD;
|
|
234
|
+
super.showEditor_(opt_e, noFocus);
|
|
235
|
+
|
|
236
|
+
this.dropdownCreate_();
|
|
237
|
+
dropDownDiv.getContentDiv().appendChild(this.editor_);
|
|
238
|
+
|
|
239
|
+
dropDownDiv.setColour(
|
|
240
|
+
this.sourceBlock_.style.colourPrimary,
|
|
241
|
+
this.sourceBlock_.style.colourTertiary);
|
|
242
|
+
|
|
243
|
+
dropDownDiv.showPositionedByField(this, this.dropdownDispose_.bind(this));
|
|
244
|
+
|
|
245
|
+
this.updateGraph_();
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* Create the angle dropdown editor.
|
|
77
250
|
* @private
|
|
78
251
|
*/
|
|
79
|
-
|
|
252
|
+
dropdownCreate_() {
|
|
253
|
+
const svg = dom.createSvgElement(
|
|
254
|
+
Svg.SVG, {
|
|
255
|
+
'xmlns': dom.SVG_NS,
|
|
256
|
+
'xmlns:html': dom.HTML_NS,
|
|
257
|
+
'xmlns:xlink': dom.XLINK_NS,
|
|
258
|
+
'version': '1.1',
|
|
259
|
+
'height': (FieldAngle.HALF * 2) + 'px',
|
|
260
|
+
'width': (FieldAngle.HALF * 2) + 'px',
|
|
261
|
+
'style': 'touch-action: none',
|
|
262
|
+
},
|
|
263
|
+
null);
|
|
264
|
+
const circle = dom.createSvgElement(
|
|
265
|
+
Svg.CIRCLE, {
|
|
266
|
+
'cx': FieldAngle.HALF,
|
|
267
|
+
'cy': FieldAngle.HALF,
|
|
268
|
+
'r': FieldAngle.RADIUS,
|
|
269
|
+
'class': 'blocklyAngleCircle',
|
|
270
|
+
},
|
|
271
|
+
svg);
|
|
272
|
+
this.gauge_ =
|
|
273
|
+
dom.createSvgElement(Svg.PATH, {'class': 'blocklyAngleGauge'}, svg);
|
|
274
|
+
this.line_ = dom.createSvgElement(
|
|
275
|
+
Svg.LINE, {
|
|
276
|
+
'x1': FieldAngle.HALF,
|
|
277
|
+
'y1': FieldAngle.HALF,
|
|
278
|
+
'class': 'blocklyAngleLine',
|
|
279
|
+
},
|
|
280
|
+
svg);
|
|
281
|
+
// Draw markers around the edge.
|
|
282
|
+
for (let angle = 0; angle < 360; angle += 15) {
|
|
283
|
+
dom.createSvgElement(
|
|
284
|
+
Svg.LINE, {
|
|
285
|
+
'x1': FieldAngle.HALF + FieldAngle.RADIUS,
|
|
286
|
+
'y1': FieldAngle.HALF,
|
|
287
|
+
'x2': FieldAngle.HALF + FieldAngle.RADIUS -
|
|
288
|
+
(angle % 45 === 0 ? 10 : 5),
|
|
289
|
+
'y2': FieldAngle.HALF,
|
|
290
|
+
'class': 'blocklyAngleMarks',
|
|
291
|
+
'transform': 'rotate(' + angle + ',' + FieldAngle.HALF + ',' +
|
|
292
|
+
FieldAngle.HALF + ')',
|
|
293
|
+
},
|
|
294
|
+
svg);
|
|
295
|
+
}
|
|
80
296
|
|
|
81
|
-
|
|
82
|
-
|
|
297
|
+
// The angle picker is different from other fields in that it updates on
|
|
298
|
+
// mousemove even if it's not in the middle of a drag. In future we may
|
|
299
|
+
// change this behaviour.
|
|
300
|
+
this.clickWrapper_ =
|
|
301
|
+
browserEvents.conditionalBind(svg, 'click', this, this.hide_);
|
|
302
|
+
// On touch devices, the picker's value is only updated with a drag. Add
|
|
303
|
+
// a click handler on the drag surface to update the value if the surface
|
|
304
|
+
// is clicked.
|
|
305
|
+
this.clickSurfaceWrapper_ = browserEvents.conditionalBind(
|
|
306
|
+
circle, 'click', this, this.onMouseMove_, true, true);
|
|
307
|
+
this.moveSurfaceWrapper_ = browserEvents.conditionalBind(
|
|
308
|
+
circle, 'mousemove', this, this.onMouseMove_, true, true);
|
|
309
|
+
this.editor_ = svg;
|
|
310
|
+
}
|
|
83
311
|
|
|
84
312
|
/**
|
|
85
|
-
*
|
|
86
|
-
* @type {?SVGElement}
|
|
313
|
+
* Disposes of events and DOM-references belonging to the angle editor.
|
|
87
314
|
* @private
|
|
88
315
|
*/
|
|
89
|
-
|
|
316
|
+
dropdownDispose_() {
|
|
317
|
+
if (this.clickWrapper_) {
|
|
318
|
+
browserEvents.unbind(this.clickWrapper_);
|
|
319
|
+
this.clickWrapper_ = null;
|
|
320
|
+
}
|
|
321
|
+
if (this.clickSurfaceWrapper_) {
|
|
322
|
+
browserEvents.unbind(this.clickSurfaceWrapper_);
|
|
323
|
+
this.clickSurfaceWrapper_ = null;
|
|
324
|
+
}
|
|
325
|
+
if (this.moveSurfaceWrapper_) {
|
|
326
|
+
browserEvents.unbind(this.moveSurfaceWrapper_);
|
|
327
|
+
this.moveSurfaceWrapper_ = null;
|
|
328
|
+
}
|
|
329
|
+
this.gauge_ = null;
|
|
330
|
+
this.line_ = null;
|
|
331
|
+
}
|
|
90
332
|
|
|
91
333
|
/**
|
|
92
|
-
*
|
|
93
|
-
* @
|
|
334
|
+
* Hide the editor.
|
|
335
|
+
* @private
|
|
94
336
|
*/
|
|
95
|
-
|
|
337
|
+
hide_() {
|
|
338
|
+
dropDownDiv.hideIfOwner(this);
|
|
339
|
+
WidgetDiv.hide();
|
|
340
|
+
}
|
|
96
341
|
|
|
97
342
|
/**
|
|
98
|
-
*
|
|
99
|
-
* @
|
|
343
|
+
* Set the angle to match the mouse's position.
|
|
344
|
+
* @param {!Event} e Mouse move event.
|
|
345
|
+
* @protected
|
|
100
346
|
*/
|
|
101
|
-
|
|
347
|
+
onMouseMove_(e) {
|
|
348
|
+
// Calculate angle.
|
|
349
|
+
const bBox = this.gauge_.ownerSVGElement.getBoundingClientRect();
|
|
350
|
+
const dx = e.clientX - bBox.left - FieldAngle.HALF;
|
|
351
|
+
const dy = e.clientY - bBox.top - FieldAngle.HALF;
|
|
352
|
+
let angle = Math.atan(-dy / dx);
|
|
353
|
+
if (isNaN(angle)) {
|
|
354
|
+
// This shouldn't happen, but let's not let this error propagate further.
|
|
355
|
+
return;
|
|
356
|
+
}
|
|
357
|
+
angle = math.toDegrees(angle);
|
|
358
|
+
// 0: East, 90: North, 180: West, 270: South.
|
|
359
|
+
if (dx < 0) {
|
|
360
|
+
angle += 180;
|
|
361
|
+
} else if (dy > 0) {
|
|
362
|
+
angle += 360;
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
// Do offsetting.
|
|
366
|
+
if (this.clockwise_) {
|
|
367
|
+
angle = this.offset_ + 360 - angle;
|
|
368
|
+
} else {
|
|
369
|
+
angle = 360 - (this.offset_ - angle);
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
this.displayMouseOrKeyboardValue_(angle);
|
|
373
|
+
}
|
|
102
374
|
|
|
103
375
|
/**
|
|
104
|
-
*
|
|
105
|
-
*
|
|
376
|
+
* Handles and displays values that are input via mouse or arrow key input.
|
|
377
|
+
* These values need to be rounded and wrapped before being displayed so
|
|
378
|
+
* that the text input's value is appropriate.
|
|
379
|
+
* @param {number} angle New angle.
|
|
106
380
|
* @private
|
|
107
381
|
*/
|
|
108
|
-
|
|
382
|
+
displayMouseOrKeyboardValue_(angle) {
|
|
383
|
+
if (this.round_) {
|
|
384
|
+
angle = Math.round(angle / this.round_) * this.round_;
|
|
385
|
+
}
|
|
386
|
+
angle = this.wrapValue_(angle);
|
|
387
|
+
if (angle !== this.value_) {
|
|
388
|
+
this.setEditorValue_(angle);
|
|
389
|
+
}
|
|
390
|
+
}
|
|
109
391
|
|
|
110
392
|
/**
|
|
111
|
-
*
|
|
112
|
-
* @type {?browserEvents.Data}
|
|
393
|
+
* Redraw the graph with the current angle.
|
|
113
394
|
* @private
|
|
114
395
|
*/
|
|
115
|
-
|
|
396
|
+
updateGraph_() {
|
|
397
|
+
if (!this.gauge_) {
|
|
398
|
+
return;
|
|
399
|
+
}
|
|
400
|
+
// Always display the input (i.e. getText) even if it is invalid.
|
|
401
|
+
let angleDegrees = Number(this.getText()) + this.offset_;
|
|
402
|
+
angleDegrees %= 360;
|
|
403
|
+
let angleRadians = math.toRadians(angleDegrees);
|
|
404
|
+
const path = ['M ', FieldAngle.HALF, ',', FieldAngle.HALF];
|
|
405
|
+
let x2 = FieldAngle.HALF;
|
|
406
|
+
let y2 = FieldAngle.HALF;
|
|
407
|
+
if (!isNaN(angleRadians)) {
|
|
408
|
+
const clockwiseFlag = Number(this.clockwise_);
|
|
409
|
+
const angle1 = math.toRadians(this.offset_);
|
|
410
|
+
const x1 = Math.cos(angle1) * FieldAngle.RADIUS;
|
|
411
|
+
const y1 = Math.sin(angle1) * -FieldAngle.RADIUS;
|
|
412
|
+
if (clockwiseFlag) {
|
|
413
|
+
angleRadians = 2 * angle1 - angleRadians;
|
|
414
|
+
}
|
|
415
|
+
x2 += Math.cos(angleRadians) * FieldAngle.RADIUS;
|
|
416
|
+
y2 -= Math.sin(angleRadians) * FieldAngle.RADIUS;
|
|
417
|
+
// Don't ask how the flag calculations work. They just do.
|
|
418
|
+
let largeFlag =
|
|
419
|
+
Math.abs(Math.floor((angleRadians - angle1) / Math.PI) % 2);
|
|
420
|
+
if (clockwiseFlag) {
|
|
421
|
+
largeFlag = 1 - largeFlag;
|
|
422
|
+
}
|
|
423
|
+
path.push(
|
|
424
|
+
' l ', x1, ',', y1, ' A ', FieldAngle.RADIUS, ',', FieldAngle.RADIUS,
|
|
425
|
+
' 0 ', largeFlag, ' ', clockwiseFlag, ' ', x2, ',', y2, ' z');
|
|
426
|
+
}
|
|
427
|
+
this.gauge_.setAttribute('d', path.join(''));
|
|
428
|
+
this.line_.setAttribute('x2', x2);
|
|
429
|
+
this.line_.setAttribute('y2', y2);
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
/**
|
|
433
|
+
* Handle key down to the editor.
|
|
434
|
+
* @param {!Event} e Keyboard event.
|
|
435
|
+
* @protected
|
|
436
|
+
* @override
|
|
437
|
+
*/
|
|
438
|
+
onHtmlInputKeyDown_(e) {
|
|
439
|
+
super.onHtmlInputKeyDown_(e);
|
|
440
|
+
|
|
441
|
+
let multiplier;
|
|
442
|
+
if (e.keyCode === KeyCodes.LEFT) {
|
|
443
|
+
// decrement (increment in RTL)
|
|
444
|
+
multiplier = this.sourceBlock_.RTL ? 1 : -1;
|
|
445
|
+
} else if (e.keyCode === KeyCodes.RIGHT) {
|
|
446
|
+
// increment (decrement in RTL)
|
|
447
|
+
multiplier = this.sourceBlock_.RTL ? -1 : 1;
|
|
448
|
+
} else if (e.keyCode === KeyCodes.DOWN) {
|
|
449
|
+
// decrement
|
|
450
|
+
multiplier = -1;
|
|
451
|
+
} else if (e.keyCode === KeyCodes.UP) {
|
|
452
|
+
// increment
|
|
453
|
+
multiplier = 1;
|
|
454
|
+
}
|
|
455
|
+
if (multiplier) {
|
|
456
|
+
const value = /** @type {number} */ (this.getValue());
|
|
457
|
+
this.displayMouseOrKeyboardValue_(value + (multiplier * this.round_));
|
|
458
|
+
e.preventDefault();
|
|
459
|
+
e.stopPropagation();
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
/**
|
|
464
|
+
* Ensure that the input value is a valid angle.
|
|
465
|
+
* @param {*=} opt_newValue The input value.
|
|
466
|
+
* @return {?number} A valid angle, or null if invalid.
|
|
467
|
+
* @protected
|
|
468
|
+
* @override
|
|
469
|
+
*/
|
|
470
|
+
doClassValidation_(opt_newValue) {
|
|
471
|
+
const value = Number(opt_newValue);
|
|
472
|
+
if (isNaN(value) || !isFinite(value)) {
|
|
473
|
+
return null;
|
|
474
|
+
}
|
|
475
|
+
return this.wrapValue_(value);
|
|
476
|
+
}
|
|
116
477
|
|
|
117
478
|
/**
|
|
118
|
-
*
|
|
119
|
-
* @
|
|
479
|
+
* Wraps the value so that it is in the range (-360 + wrap, wrap).
|
|
480
|
+
* @param {number} value The value to wrap.
|
|
481
|
+
* @return {number} The wrapped value.
|
|
120
482
|
* @private
|
|
121
483
|
*/
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
484
|
+
wrapValue_(value) {
|
|
485
|
+
value %= 360;
|
|
486
|
+
if (value < 0) {
|
|
487
|
+
value += 360;
|
|
488
|
+
}
|
|
489
|
+
if (value > this.wrap_) {
|
|
490
|
+
value -= 360;
|
|
491
|
+
}
|
|
492
|
+
return value;
|
|
493
|
+
}
|
|
125
494
|
|
|
495
|
+
/**
|
|
496
|
+
* Construct a FieldAngle from a JSON arg object.
|
|
497
|
+
* @param {!Object} options A JSON object with options (angle).
|
|
498
|
+
* @return {!FieldAngle} The new field instance.
|
|
499
|
+
* @package
|
|
500
|
+
* @nocollapse
|
|
501
|
+
* @override
|
|
502
|
+
*/
|
|
503
|
+
static fromJson(options) {
|
|
504
|
+
// `this` might be a subclass of FieldAngle if that class doesn't override
|
|
505
|
+
// the static fromJson method.
|
|
506
|
+
return new this(options['angle'], undefined, options);
|
|
507
|
+
}
|
|
508
|
+
}
|
|
126
509
|
|
|
127
510
|
/**
|
|
128
511
|
* The default value for this field.
|
|
@@ -131,26 +514,6 @@ object.inherits(FieldAngle, FieldTextInput);
|
|
|
131
514
|
*/
|
|
132
515
|
FieldAngle.prototype.DEFAULT_VALUE = 0;
|
|
133
516
|
|
|
134
|
-
/**
|
|
135
|
-
* Construct a FieldAngle from a JSON arg object.
|
|
136
|
-
* @param {!Object} options A JSON object with options (angle).
|
|
137
|
-
* @return {!FieldAngle} The new field instance.
|
|
138
|
-
* @package
|
|
139
|
-
* @nocollapse
|
|
140
|
-
*/
|
|
141
|
-
FieldAngle.fromJson = function(options) {
|
|
142
|
-
// `this` might be a subclass of FieldAngle if that class doesn't override
|
|
143
|
-
// the static fromJson method.
|
|
144
|
-
return new this(options['angle'], undefined, options);
|
|
145
|
-
};
|
|
146
|
-
|
|
147
|
-
/**
|
|
148
|
-
* Serializable fields are saved by the XML renderer, non-serializable fields
|
|
149
|
-
* are not. Editable fields should also be serializable.
|
|
150
|
-
* @type {boolean}
|
|
151
|
-
*/
|
|
152
|
-
FieldAngle.prototype.SERIALIZABLE = true;
|
|
153
|
-
|
|
154
517
|
/**
|
|
155
518
|
* The default amount to round angles to when using a mouse or keyboard nav
|
|
156
519
|
* input. Must be a positive integer to support keyboard navigation.
|
|
@@ -193,377 +556,34 @@ FieldAngle.WRAP = 360;
|
|
|
193
556
|
*/
|
|
194
557
|
FieldAngle.RADIUS = FieldAngle.HALF - 1;
|
|
195
558
|
|
|
196
|
-
/**
|
|
197
|
-
* Configure the field based on the given map of options.
|
|
198
|
-
* @param {!Object} config A map of options to configure the field based on.
|
|
199
|
-
* @protected
|
|
200
|
-
* @override
|
|
201
|
-
*/
|
|
202
|
-
FieldAngle.prototype.configure_ = function(config) {
|
|
203
|
-
FieldAngle.superClass_.configure_.call(this, config);
|
|
204
|
-
|
|
205
|
-
switch (config['mode']) {
|
|
206
|
-
case 'compass':
|
|
207
|
-
this.clockwise_ = true;
|
|
208
|
-
this.offset_ = 90;
|
|
209
|
-
break;
|
|
210
|
-
case 'protractor':
|
|
211
|
-
// This is the default mode, so we could do nothing. But just to
|
|
212
|
-
// future-proof, we'll set it anyway.
|
|
213
|
-
this.clockwise_ = false;
|
|
214
|
-
this.offset_ = 0;
|
|
215
|
-
break;
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
// Allow individual settings to override the mode setting.
|
|
219
|
-
const clockwise = config['clockwise'];
|
|
220
|
-
if (typeof clockwise === 'boolean') {
|
|
221
|
-
this.clockwise_ = clockwise;
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
// If these are passed as null then we should leave them on the default.
|
|
225
|
-
let offset = config['offset'];
|
|
226
|
-
if (offset !== null) {
|
|
227
|
-
offset = Number(offset);
|
|
228
|
-
if (!isNaN(offset)) {
|
|
229
|
-
this.offset_ = offset;
|
|
230
|
-
}
|
|
231
|
-
}
|
|
232
|
-
let wrap = config['wrap'];
|
|
233
|
-
if (wrap !== null) {
|
|
234
|
-
wrap = Number(wrap);
|
|
235
|
-
if (!isNaN(wrap)) {
|
|
236
|
-
this.wrap_ = wrap;
|
|
237
|
-
}
|
|
238
|
-
}
|
|
239
|
-
let round = config['round'];
|
|
240
|
-
if (round !== null) {
|
|
241
|
-
round = Number(round);
|
|
242
|
-
if (!isNaN(round)) {
|
|
243
|
-
this.round_ = round;
|
|
244
|
-
}
|
|
245
|
-
}
|
|
246
|
-
};
|
|
247
|
-
|
|
248
|
-
/**
|
|
249
|
-
* Create the block UI for this field.
|
|
250
|
-
* @package
|
|
251
|
-
*/
|
|
252
|
-
FieldAngle.prototype.initView = function() {
|
|
253
|
-
FieldAngle.superClass_.initView.call(this);
|
|
254
|
-
// Add the degree symbol to the left of the number, even in RTL (issue #2380)
|
|
255
|
-
this.symbol_ = dom.createSvgElement(Svg.TSPAN, {}, null);
|
|
256
|
-
this.symbol_.appendChild(document.createTextNode('\u00B0'));
|
|
257
|
-
this.textElement_.appendChild(this.symbol_);
|
|
258
|
-
};
|
|
259
|
-
|
|
260
|
-
/**
|
|
261
|
-
* Updates the graph when the field rerenders.
|
|
262
|
-
* @protected
|
|
263
|
-
* @override
|
|
264
|
-
*/
|
|
265
|
-
FieldAngle.prototype.render_ = function() {
|
|
266
|
-
FieldAngle.superClass_.render_.call(this);
|
|
267
|
-
this.updateGraph_();
|
|
268
|
-
};
|
|
269
|
-
|
|
270
|
-
/**
|
|
271
|
-
* Create and show the angle field's editor.
|
|
272
|
-
* @param {Event=} opt_e Optional mouse event that triggered the field to open,
|
|
273
|
-
* or undefined if triggered programmatically.
|
|
274
|
-
* @protected
|
|
275
|
-
*/
|
|
276
|
-
FieldAngle.prototype.showEditor_ = function(opt_e) {
|
|
277
|
-
// Mobile browsers have issues with in-line textareas (focus & keyboards).
|
|
278
|
-
const noFocus = userAgent.MOBILE || userAgent.ANDROID || userAgent.IPAD;
|
|
279
|
-
FieldAngle.superClass_.showEditor_.call(this, opt_e, noFocus);
|
|
280
|
-
|
|
281
|
-
this.dropdownCreate_();
|
|
282
|
-
DropDownDiv.getContentDiv().appendChild(this.editor_);
|
|
283
|
-
|
|
284
|
-
DropDownDiv.setColour(
|
|
285
|
-
this.sourceBlock_.style.colourPrimary,
|
|
286
|
-
this.sourceBlock_.style.colourTertiary);
|
|
287
|
-
|
|
288
|
-
DropDownDiv.showPositionedByField(this, this.dropdownDispose_.bind(this));
|
|
289
|
-
|
|
290
|
-
this.updateGraph_();
|
|
291
|
-
};
|
|
292
|
-
|
|
293
|
-
/**
|
|
294
|
-
* Create the angle dropdown editor.
|
|
295
|
-
* @private
|
|
296
|
-
*/
|
|
297
|
-
FieldAngle.prototype.dropdownCreate_ = function() {
|
|
298
|
-
const svg = dom.createSvgElement(
|
|
299
|
-
Svg.SVG, {
|
|
300
|
-
'xmlns': dom.SVG_NS,
|
|
301
|
-
'xmlns:html': dom.HTML_NS,
|
|
302
|
-
'xmlns:xlink': dom.XLINK_NS,
|
|
303
|
-
'version': '1.1',
|
|
304
|
-
'height': (FieldAngle.HALF * 2) + 'px',
|
|
305
|
-
'width': (FieldAngle.HALF * 2) + 'px',
|
|
306
|
-
'style': 'touch-action: none',
|
|
307
|
-
},
|
|
308
|
-
null);
|
|
309
|
-
const circle = dom.createSvgElement(
|
|
310
|
-
Svg.CIRCLE, {
|
|
311
|
-
'cx': FieldAngle.HALF,
|
|
312
|
-
'cy': FieldAngle.HALF,
|
|
313
|
-
'r': FieldAngle.RADIUS,
|
|
314
|
-
'class': 'blocklyAngleCircle',
|
|
315
|
-
},
|
|
316
|
-
svg);
|
|
317
|
-
this.gauge_ =
|
|
318
|
-
dom.createSvgElement(Svg.PATH, {'class': 'blocklyAngleGauge'}, svg);
|
|
319
|
-
this.line_ = dom.createSvgElement(
|
|
320
|
-
Svg.LINE, {
|
|
321
|
-
'x1': FieldAngle.HALF,
|
|
322
|
-
'y1': FieldAngle.HALF,
|
|
323
|
-
'class': 'blocklyAngleLine',
|
|
324
|
-
},
|
|
325
|
-
svg);
|
|
326
|
-
// Draw markers around the edge.
|
|
327
|
-
for (let angle = 0; angle < 360; angle += 15) {
|
|
328
|
-
dom.createSvgElement(
|
|
329
|
-
Svg.LINE, {
|
|
330
|
-
'x1': FieldAngle.HALF + FieldAngle.RADIUS,
|
|
331
|
-
'y1': FieldAngle.HALF,
|
|
332
|
-
'x2':
|
|
333
|
-
FieldAngle.HALF + FieldAngle.RADIUS - (angle % 45 === 0 ? 10 : 5),
|
|
334
|
-
'y2': FieldAngle.HALF,
|
|
335
|
-
'class': 'blocklyAngleMarks',
|
|
336
|
-
'transform': 'rotate(' + angle + ',' + FieldAngle.HALF + ',' +
|
|
337
|
-
FieldAngle.HALF + ')',
|
|
338
|
-
},
|
|
339
|
-
svg);
|
|
340
|
-
}
|
|
341
|
-
|
|
342
|
-
// The angle picker is different from other fields in that it updates on
|
|
343
|
-
// mousemove even if it's not in the middle of a drag. In future we may
|
|
344
|
-
// change this behaviour.
|
|
345
|
-
this.clickWrapper_ =
|
|
346
|
-
browserEvents.conditionalBind(svg, 'click', this, this.hide_);
|
|
347
|
-
// On touch devices, the picker's value is only updated with a drag. Add
|
|
348
|
-
// a click handler on the drag surface to update the value if the surface
|
|
349
|
-
// is clicked.
|
|
350
|
-
this.clickSurfaceWrapper_ = browserEvents.conditionalBind(
|
|
351
|
-
circle, 'click', this, this.onMouseMove_, true, true);
|
|
352
|
-
this.moveSurfaceWrapper_ = browserEvents.conditionalBind(
|
|
353
|
-
circle, 'mousemove', this, this.onMouseMove_, true, true);
|
|
354
|
-
this.editor_ = svg;
|
|
355
|
-
};
|
|
356
|
-
|
|
357
|
-
/**
|
|
358
|
-
* Disposes of events and DOM-references belonging to the angle editor.
|
|
359
|
-
* @private
|
|
360
|
-
*/
|
|
361
|
-
FieldAngle.prototype.dropdownDispose_ = function() {
|
|
362
|
-
if (this.clickWrapper_) {
|
|
363
|
-
browserEvents.unbind(this.clickWrapper_);
|
|
364
|
-
this.clickWrapper_ = null;
|
|
365
|
-
}
|
|
366
|
-
if (this.clickSurfaceWrapper_) {
|
|
367
|
-
browserEvents.unbind(this.clickSurfaceWrapper_);
|
|
368
|
-
this.clickSurfaceWrapper_ = null;
|
|
369
|
-
}
|
|
370
|
-
if (this.moveSurfaceWrapper_) {
|
|
371
|
-
browserEvents.unbind(this.moveSurfaceWrapper_);
|
|
372
|
-
this.moveSurfaceWrapper_ = null;
|
|
373
|
-
}
|
|
374
|
-
this.gauge_ = null;
|
|
375
|
-
this.line_ = null;
|
|
376
|
-
};
|
|
377
|
-
|
|
378
|
-
/**
|
|
379
|
-
* Hide the editor.
|
|
380
|
-
* @private
|
|
381
|
-
*/
|
|
382
|
-
FieldAngle.prototype.hide_ = function() {
|
|
383
|
-
DropDownDiv.hideIfOwner(this);
|
|
384
|
-
WidgetDiv.hide();
|
|
385
|
-
};
|
|
386
|
-
|
|
387
|
-
/**
|
|
388
|
-
* Set the angle to match the mouse's position.
|
|
389
|
-
* @param {!Event} e Mouse move event.
|
|
390
|
-
* @protected
|
|
391
|
-
*/
|
|
392
|
-
FieldAngle.prototype.onMouseMove_ = function(e) {
|
|
393
|
-
// Calculate angle.
|
|
394
|
-
const bBox = this.gauge_.ownerSVGElement.getBoundingClientRect();
|
|
395
|
-
const dx = e.clientX - bBox.left - FieldAngle.HALF;
|
|
396
|
-
const dy = e.clientY - bBox.top - FieldAngle.HALF;
|
|
397
|
-
let angle = Math.atan(-dy / dx);
|
|
398
|
-
if (isNaN(angle)) {
|
|
399
|
-
// This shouldn't happen, but let's not let this error propagate further.
|
|
400
|
-
return;
|
|
401
|
-
}
|
|
402
|
-
angle = math.toDegrees(angle);
|
|
403
|
-
// 0: East, 90: North, 180: West, 270: South.
|
|
404
|
-
if (dx < 0) {
|
|
405
|
-
angle += 180;
|
|
406
|
-
} else if (dy > 0) {
|
|
407
|
-
angle += 360;
|
|
408
|
-
}
|
|
409
|
-
|
|
410
|
-
// Do offsetting.
|
|
411
|
-
if (this.clockwise_) {
|
|
412
|
-
angle = this.offset_ + 360 - angle;
|
|
413
|
-
} else {
|
|
414
|
-
angle = 360 - (this.offset_ - angle);
|
|
415
|
-
}
|
|
416
|
-
|
|
417
|
-
this.displayMouseOrKeyboardValue_(angle);
|
|
418
|
-
};
|
|
419
|
-
|
|
420
|
-
/**
|
|
421
|
-
* Handles and displays values that are input via mouse or arrow key input.
|
|
422
|
-
* These values need to be rounded and wrapped before being displayed so
|
|
423
|
-
* that the text input's value is appropriate.
|
|
424
|
-
* @param {number} angle New angle.
|
|
425
|
-
* @private
|
|
426
|
-
*/
|
|
427
|
-
FieldAngle.prototype.displayMouseOrKeyboardValue_ = function(angle) {
|
|
428
|
-
if (this.round_) {
|
|
429
|
-
angle = Math.round(angle / this.round_) * this.round_;
|
|
430
|
-
}
|
|
431
|
-
angle = this.wrapValue_(angle);
|
|
432
|
-
if (angle !== this.value_) {
|
|
433
|
-
this.setEditorValue_(angle);
|
|
434
|
-
}
|
|
435
|
-
};
|
|
436
|
-
|
|
437
|
-
/**
|
|
438
|
-
* Redraw the graph with the current angle.
|
|
439
|
-
* @private
|
|
440
|
-
*/
|
|
441
|
-
FieldAngle.prototype.updateGraph_ = function() {
|
|
442
|
-
if (!this.gauge_) {
|
|
443
|
-
return;
|
|
444
|
-
}
|
|
445
|
-
// Always display the input (i.e. getText) even if it is invalid.
|
|
446
|
-
let angleDegrees = Number(this.getText()) + this.offset_;
|
|
447
|
-
angleDegrees %= 360;
|
|
448
|
-
let angleRadians = math.toRadians(angleDegrees);
|
|
449
|
-
const path = ['M ', FieldAngle.HALF, ',', FieldAngle.HALF];
|
|
450
|
-
let x2 = FieldAngle.HALF;
|
|
451
|
-
let y2 = FieldAngle.HALF;
|
|
452
|
-
if (!isNaN(angleRadians)) {
|
|
453
|
-
const clockwiseFlag = Number(this.clockwise_);
|
|
454
|
-
const angle1 = math.toRadians(this.offset_);
|
|
455
|
-
const x1 = Math.cos(angle1) * FieldAngle.RADIUS;
|
|
456
|
-
const y1 = Math.sin(angle1) * -FieldAngle.RADIUS;
|
|
457
|
-
if (clockwiseFlag) {
|
|
458
|
-
angleRadians = 2 * angle1 - angleRadians;
|
|
459
|
-
}
|
|
460
|
-
x2 += Math.cos(angleRadians) * FieldAngle.RADIUS;
|
|
461
|
-
y2 -= Math.sin(angleRadians) * FieldAngle.RADIUS;
|
|
462
|
-
// Don't ask how the flag calculations work. They just do.
|
|
463
|
-
let largeFlag = Math.abs(Math.floor((angleRadians - angle1) / Math.PI) % 2);
|
|
464
|
-
if (clockwiseFlag) {
|
|
465
|
-
largeFlag = 1 - largeFlag;
|
|
466
|
-
}
|
|
467
|
-
path.push(
|
|
468
|
-
' l ', x1, ',', y1, ' A ', FieldAngle.RADIUS, ',', FieldAngle.RADIUS,
|
|
469
|
-
' 0 ', largeFlag, ' ', clockwiseFlag, ' ', x2, ',', y2, ' z');
|
|
470
|
-
}
|
|
471
|
-
this.gauge_.setAttribute('d', path.join(''));
|
|
472
|
-
this.line_.setAttribute('x2', x2);
|
|
473
|
-
this.line_.setAttribute('y2', y2);
|
|
474
|
-
};
|
|
475
|
-
|
|
476
|
-
/**
|
|
477
|
-
* Handle key down to the editor.
|
|
478
|
-
* @param {!Event} e Keyboard event.
|
|
479
|
-
* @protected
|
|
480
|
-
* @override
|
|
481
|
-
*/
|
|
482
|
-
FieldAngle.prototype.onHtmlInputKeyDown_ = function(e) {
|
|
483
|
-
FieldAngle.superClass_.onHtmlInputKeyDown_.call(this, e);
|
|
484
|
-
|
|
485
|
-
let multiplier;
|
|
486
|
-
if (e.keyCode === KeyCodes.LEFT) {
|
|
487
|
-
// decrement (increment in RTL)
|
|
488
|
-
multiplier = this.sourceBlock_.RTL ? 1 : -1;
|
|
489
|
-
} else if (e.keyCode === KeyCodes.RIGHT) {
|
|
490
|
-
// increment (decrement in RTL)
|
|
491
|
-
multiplier = this.sourceBlock_.RTL ? -1 : 1;
|
|
492
|
-
} else if (e.keyCode === KeyCodes.DOWN) {
|
|
493
|
-
// decrement
|
|
494
|
-
multiplier = -1;
|
|
495
|
-
} else if (e.keyCode === KeyCodes.UP) {
|
|
496
|
-
// increment
|
|
497
|
-
multiplier = 1;
|
|
498
|
-
}
|
|
499
|
-
if (multiplier) {
|
|
500
|
-
const value = /** @type {number} */ (this.getValue());
|
|
501
|
-
this.displayMouseOrKeyboardValue_(value + (multiplier * this.round_));
|
|
502
|
-
e.preventDefault();
|
|
503
|
-
e.stopPropagation();
|
|
504
|
-
}
|
|
505
|
-
};
|
|
506
|
-
|
|
507
|
-
/**
|
|
508
|
-
* Ensure that the input value is a valid angle.
|
|
509
|
-
* @param {*=} opt_newValue The input value.
|
|
510
|
-
* @return {?number} A valid angle, or null if invalid.
|
|
511
|
-
* @protected
|
|
512
|
-
* @override
|
|
513
|
-
*/
|
|
514
|
-
FieldAngle.prototype.doClassValidation_ = function(opt_newValue) {
|
|
515
|
-
const value = Number(opt_newValue);
|
|
516
|
-
if (isNaN(value) || !isFinite(value)) {
|
|
517
|
-
return null;
|
|
518
|
-
}
|
|
519
|
-
return this.wrapValue_(value);
|
|
520
|
-
};
|
|
521
|
-
|
|
522
|
-
/**
|
|
523
|
-
* Wraps the value so that it is in the range (-360 + wrap, wrap).
|
|
524
|
-
* @param {number} value The value to wrap.
|
|
525
|
-
* @return {number} The wrapped value.
|
|
526
|
-
* @private
|
|
527
|
-
*/
|
|
528
|
-
FieldAngle.prototype.wrapValue_ = function(value) {
|
|
529
|
-
value %= 360;
|
|
530
|
-
if (value < 0) {
|
|
531
|
-
value += 360;
|
|
532
|
-
}
|
|
533
|
-
if (value > this.wrap_) {
|
|
534
|
-
value -= 360;
|
|
535
|
-
}
|
|
536
|
-
return value;
|
|
537
|
-
};
|
|
538
|
-
|
|
539
559
|
/**
|
|
540
560
|
* CSS for angle field. See css.js for use.
|
|
541
561
|
*/
|
|
542
562
|
Css.register(`
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
563
|
+
.blocklyAngleCircle {
|
|
564
|
+
stroke: #444;
|
|
565
|
+
stroke-width: 1;
|
|
566
|
+
fill: #ddd;
|
|
567
|
+
fill-opacity: .8;
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
.blocklyAngleMarks {
|
|
571
|
+
stroke: #444;
|
|
572
|
+
stroke-width: 1;
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
.blocklyAngleGauge {
|
|
576
|
+
fill: #f88;
|
|
577
|
+
fill-opacity: .8;
|
|
578
|
+
pointer-events: none;
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
.blocklyAngleLine {
|
|
582
|
+
stroke: #f00;
|
|
583
|
+
stroke-width: 2;
|
|
584
|
+
stroke-linecap: round;
|
|
585
|
+
pointer-events: none;
|
|
586
|
+
}
|
|
567
587
|
`);
|
|
568
588
|
|
|
569
589
|
fieldRegistry.register('field_angle', FieldAngle);
|