blockly 7.20211209.4 → 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.
Files changed (262) hide show
  1. package/blockly.d.ts +18963 -18432
  2. package/blockly.min.js +852 -844
  3. package/blockly_compressed.js +669 -664
  4. package/blockly_compressed.js.map +1 -1
  5. package/blocks/blocks.js +47 -0
  6. package/blocks/colour.js +13 -3
  7. package/blocks/lists.js +22 -13
  8. package/blocks/logic.js +13 -3
  9. package/blocks/loops.js +24 -11
  10. package/blocks/math.js +12 -3
  11. package/blocks/procedures.js +41 -27
  12. package/blocks/text.js +22 -13
  13. package/blocks/variables.js +14 -3
  14. package/blocks/variables_dynamic.js +13 -3
  15. package/blocks_compressed.js +146 -141
  16. package/blocks_compressed.js.map +1 -1
  17. package/core/block.js +1869 -1814
  18. package/core/block_drag_surface.js +201 -200
  19. package/core/block_dragger.js +377 -373
  20. package/core/block_svg.js +1593 -1479
  21. package/core/blockly.js +8 -22
  22. package/core/blocks.js +9 -2
  23. package/core/browser_events.js +22 -5
  24. package/core/bubble.js +841 -797
  25. package/core/bubble_dragger.js +213 -206
  26. package/core/bump_objects.js +2 -2
  27. package/core/clipboard.js +9 -9
  28. package/core/comment.js +353 -332
  29. package/core/common.js +46 -17
  30. package/core/component_manager.js +181 -174
  31. package/core/config.js +87 -0
  32. package/core/connection.js +595 -584
  33. package/core/connection_checker.js +242 -244
  34. package/core/connection_db.js +235 -230
  35. package/core/contextmenu.js +9 -6
  36. package/core/contextmenu_items.js +1 -2
  37. package/core/contextmenu_registry.js +93 -89
  38. package/core/css.js +474 -474
  39. package/core/delete_area.js +45 -42
  40. package/core/drag_target.js +57 -56
  41. package/core/dropdowndiv.js +153 -163
  42. package/core/events/events.js +2 -2
  43. package/core/events/events_abstract.js +89 -77
  44. package/core/events/events_block_base.js +37 -36
  45. package/core/events/events_block_change.js +130 -124
  46. package/core/events/events_block_create.js +73 -71
  47. package/core/events/events_block_delete.js +84 -82
  48. package/core/events/events_block_drag.js +50 -49
  49. package/core/events/events_block_move.js +147 -140
  50. package/core/events/events_bubble_open.js +51 -50
  51. package/core/events/events_click.js +48 -44
  52. package/core/events/events_comment_base.js +72 -69
  53. package/core/events/events_comment_change.js +63 -61
  54. package/core/events/events_comment_create.js +44 -42
  55. package/core/events/events_comment_delete.js +42 -40
  56. package/core/events/events_comment_move.js +106 -104
  57. package/core/events/events_marker_move.js +65 -64
  58. package/core/events/events_selected.js +46 -45
  59. package/core/events/events_theme_change.js +36 -35
  60. package/core/events/events_toolbox_item_select.js +46 -45
  61. package/core/events/events_trashcan_open.js +37 -36
  62. package/core/events/events_ui.js +47 -46
  63. package/core/events/events_ui_base.js +30 -29
  64. package/core/events/events_var_base.js +37 -36
  65. package/core/events/events_var_create.js +50 -48
  66. package/core/events/events_var_delete.js +50 -48
  67. package/core/events/events_var_rename.js +51 -49
  68. package/core/events/events_viewport.js +66 -65
  69. package/core/events/utils.js +29 -14
  70. package/core/events/workspace_events.js +49 -55
  71. package/core/extensions.js +4 -3
  72. package/core/field.js +1061 -997
  73. package/core/field_angle.js +462 -442
  74. package/core/field_checkbox.js +194 -182
  75. package/core/field_colour.js +519 -505
  76. package/core/field_dropdown.js +617 -598
  77. package/core/field_image.js +229 -220
  78. package/core/field_label.js +102 -91
  79. package/core/field_label_serializable.js +42 -41
  80. package/core/field_multilineinput.js +372 -358
  81. package/core/field_number.js +272 -253
  82. package/core/field_textinput.js +499 -467
  83. package/core/field_variable.js +458 -420
  84. package/core/flyout_base.js +1005 -952
  85. package/core/flyout_button.js +277 -260
  86. package/core/flyout_horizontal.js +304 -302
  87. package/core/flyout_metrics_manager.js +64 -64
  88. package/core/flyout_vertical.js +306 -300
  89. package/core/generator.js +459 -446
  90. package/core/gesture.js +829 -813
  91. package/core/grid.js +166 -163
  92. package/core/icon.js +168 -159
  93. package/core/inject.js +7 -5
  94. package/core/input.js +257 -248
  95. package/core/insertion_marker_manager.js +655 -624
  96. package/core/internal_constants.js +0 -129
  97. package/core/keyboard_nav/ast_node.js +605 -596
  98. package/core/keyboard_nav/basic_cursor.js +166 -165
  99. package/core/keyboard_nav/cursor.js +99 -97
  100. package/core/keyboard_nav/marker.js +83 -79
  101. package/core/keyboard_nav/tab_navigate_cursor.js +18 -23
  102. package/core/marker_manager.js +153 -141
  103. package/core/menu.js +377 -372
  104. package/core/menuitem.js +223 -217
  105. package/core/metrics_manager.js +403 -390
  106. package/core/mutator.js +468 -437
  107. package/core/names.js +229 -188
  108. package/core/options.js +290 -284
  109. package/core/procedures.js +29 -17
  110. package/core/registry.js +19 -16
  111. package/core/rendered_connection.js +482 -463
  112. package/core/renderers/common/block_rendering.js +9 -3
  113. package/core/renderers/common/constants.js +1119 -1112
  114. package/core/renderers/common/debug.js +14 -0
  115. package/core/renderers/common/debugger.js +338 -316
  116. package/core/renderers/common/drawer.js +380 -370
  117. package/core/renderers/common/i_path_object.js +2 -2
  118. package/core/renderers/common/info.js +626 -618
  119. package/core/renderers/common/marker_svg.js +579 -541
  120. package/core/renderers/common/path_object.js +203 -200
  121. package/core/renderers/common/renderer.js +220 -218
  122. package/core/renderers/geras/constants.js +36 -36
  123. package/core/renderers/geras/drawer.js +155 -147
  124. package/core/renderers/geras/highlight_constants.js +244 -238
  125. package/core/renderers/geras/highlighter.js +231 -179
  126. package/core/renderers/geras/info.js +392 -369
  127. package/core/renderers/geras/measurables/inline_input.js +25 -19
  128. package/core/renderers/geras/measurables/statement_input.js +23 -17
  129. package/core/renderers/geras/path_object.js +106 -121
  130. package/core/renderers/geras/renderer.js +96 -98
  131. package/core/renderers/measurables/base.js +30 -18
  132. package/core/renderers/measurables/bottom_row.js +83 -80
  133. package/core/renderers/measurables/connection.js +22 -15
  134. package/core/renderers/measurables/external_value_input.js +35 -22
  135. package/core/renderers/measurables/field.js +35 -20
  136. package/core/renderers/measurables/hat.js +18 -13
  137. package/core/renderers/measurables/icon.js +24 -17
  138. package/core/renderers/measurables/in_row_spacer.js +15 -13
  139. package/core/renderers/measurables/inline_input.js +43 -33
  140. package/core/renderers/measurables/input_connection.js +41 -28
  141. package/core/renderers/measurables/input_row.js +50 -44
  142. package/core/renderers/measurables/jagged_edge.js +14 -12
  143. package/core/renderers/measurables/next_connection.js +16 -14
  144. package/core/renderers/measurables/output_connection.js +26 -20
  145. package/core/renderers/measurables/previous_connection.js +16 -15
  146. package/core/renderers/measurables/round_corner.js +20 -18
  147. package/core/renderers/measurables/row.js +184 -168
  148. package/core/renderers/measurables/spacer_row.js +38 -23
  149. package/core/renderers/measurables/square_corner.js +18 -16
  150. package/core/renderers/measurables/statement_input.js +23 -20
  151. package/core/renderers/measurables/top_row.js +88 -85
  152. package/core/renderers/minimalist/constants.js +8 -7
  153. package/core/renderers/minimalist/drawer.js +11 -10
  154. package/core/renderers/minimalist/info.js +18 -18
  155. package/core/renderers/minimalist/renderer.js +40 -39
  156. package/core/renderers/thrasos/info.js +258 -248
  157. package/core/renderers/thrasos/renderer.js +20 -20
  158. package/core/renderers/zelos/constants.js +898 -873
  159. package/core/renderers/zelos/drawer.js +186 -169
  160. package/core/renderers/zelos/info.js +502 -479
  161. package/core/renderers/zelos/marker_svg.js +129 -115
  162. package/core/renderers/zelos/measurables/bottom_row.js +31 -30
  163. package/core/renderers/zelos/measurables/inputs.js +22 -21
  164. package/core/renderers/zelos/measurables/row_elements.js +14 -13
  165. package/core/renderers/zelos/measurables/top_row.js +34 -33
  166. package/core/renderers/zelos/path_object.js +181 -180
  167. package/core/renderers/zelos/renderer.js +91 -92
  168. package/core/scrollbar.js +759 -713
  169. package/core/scrollbar_pair.js +250 -245
  170. package/core/serialization/blocks.js +19 -9
  171. package/core/serialization/workspaces.js +3 -2
  172. package/core/shortcut_registry.js +286 -277
  173. package/core/sprites.js +31 -0
  174. package/core/theme.js +135 -141
  175. package/core/theme_manager.js +147 -143
  176. package/core/toolbox/category.js +602 -576
  177. package/core/toolbox/collapsible_category.js +226 -227
  178. package/core/toolbox/separator.js +70 -61
  179. package/core/toolbox/toolbox.js +934 -927
  180. package/core/toolbox/toolbox_item.js +115 -99
  181. package/core/tooltip.js +108 -35
  182. package/core/touch.js +8 -3
  183. package/core/touch_gesture.js +254 -251
  184. package/core/trashcan.js +606 -595
  185. package/core/utils/coordinate.js +97 -95
  186. package/core/utils/dom.js +2 -2
  187. package/core/utils/global.js +2 -0
  188. package/core/utils/rect.js +41 -37
  189. package/core/utils/sentinel.js +25 -0
  190. package/core/utils/size.js +30 -27
  191. package/core/utils/svg.js +18 -16
  192. package/core/variable_map.js +325 -341
  193. package/core/variable_model.js +55 -54
  194. package/core/variables.js +9 -2
  195. package/core/variables_dynamic.js +3 -1
  196. package/core/warning.js +126 -120
  197. package/core/widgetdiv.js +4 -4
  198. package/core/workspace.js +685 -664
  199. package/core/workspace_audio.js +124 -118
  200. package/core/workspace_comment.js +308 -298
  201. package/core/workspace_comment_svg.js +1029 -951
  202. package/core/workspace_drag_surface_svg.js +147 -140
  203. package/core/workspace_dragger.js +70 -71
  204. package/core/workspace_svg.js +2322 -2297
  205. package/core/xml.js +30 -20
  206. package/core/zoom_controls.js +431 -439
  207. package/dart_compressed.js +40 -43
  208. package/dart_compressed.js.map +1 -1
  209. package/generators/dart/colour.js +56 -64
  210. package/generators/dart/lists.js +61 -50
  211. package/generators/dart/math.js +160 -148
  212. package/generators/dart/text.js +83 -61
  213. package/generators/javascript/colour.js +37 -34
  214. package/generators/javascript/lists.js +50 -43
  215. package/generators/javascript/math.js +123 -139
  216. package/generators/javascript/text.js +67 -81
  217. package/generators/lua/colour.js +25 -23
  218. package/generators/lua/lists.js +97 -69
  219. package/generators/lua/logic.js +1 -2
  220. package/generators/lua/math.js +182 -144
  221. package/generators/lua/text.js +116 -99
  222. package/generators/php/colour.js +38 -32
  223. package/generators/php/lists.js +109 -89
  224. package/generators/php/math.js +90 -81
  225. package/generators/php/text.js +63 -61
  226. package/generators/python/colour.js +18 -18
  227. package/generators/python/lists.js +38 -30
  228. package/generators/python/loops.js +12 -8
  229. package/generators/python/math.js +104 -106
  230. package/generators/python/text.js +34 -30
  231. package/javascript_compressed.js +37 -39
  232. package/javascript_compressed.js.map +1 -1
  233. package/lua_compressed.js +39 -42
  234. package/lua_compressed.js.map +1 -1
  235. package/msg/az.js +2 -2
  236. package/msg/be.js +4 -4
  237. package/msg/cs.js +15 -15
  238. package/msg/de.js +1 -1
  239. package/msg/diq.js +1 -1
  240. package/msg/eo.js +1 -1
  241. package/msg/es.js +1 -1
  242. package/msg/fa.js +1 -1
  243. package/msg/fr.js +4 -4
  244. package/msg/he.js +1 -1
  245. package/msg/hr.js +2 -2
  246. package/msg/hy.js +2 -2
  247. package/msg/id.js +12 -12
  248. package/msg/inh.js +14 -14
  249. package/msg/ja.js +7 -7
  250. package/msg/lv.js +29 -29
  251. package/msg/pa.js +3 -3
  252. package/msg/smn.js +436 -0
  253. package/msg/te.js +1 -1
  254. package/msg/yue.js +1 -1
  255. package/msg/zh-hans.js +3 -3
  256. package/msg/zh-hant.js +3 -3
  257. package/package.json +7 -6
  258. package/php_compressed.js +38 -42
  259. package/php_compressed.js.map +1 -1
  260. package/python_compressed.js +26 -25
  261. package/python_compressed.js.map +1 -1
  262. package/blocks/all.js +0 -23
@@ -57,711 +57,719 @@ const {inputTypes} = goog.require('Blockly.inputTypes');
57
57
  * This measure pass does not propagate changes to the block (although fields
58
58
  * may choose to rerender when getSize() is called). However, calling it
59
59
  * repeatedly may be expensive.
60
- *
61
- * @param {!Renderer} renderer The renderer in use.
62
- * @param {!BlockSvg} block The block to measure.
63
- * @constructor
64
- * @package
65
60
  * @alias Blockly.blockRendering.RenderInfo
66
61
  */
67
- const RenderInfo = function(renderer, block) {
68
- this.block_ = block;
69
-
70
- /**
71
- * The block renderer in use.
72
- * @type {!Renderer}
73
- * @protected
74
- */
75
- this.renderer_ = renderer;
76
-
62
+ class RenderInfo {
77
63
  /**
78
- * The renderer's constant provider.
79
- * @type {!ConstantProvider}
80
- * @protected
64
+ * @param {!Renderer} renderer The renderer in use.
65
+ * @param {!BlockSvg} block The block to measure.
66
+ * @package
81
67
  */
82
- this.constants_ = this.renderer_.getConstants();
68
+ constructor(renderer, block) {
69
+ this.block_ = block;
70
+
71
+ /**
72
+ * The block renderer in use.
73
+ * @type {!Renderer}
74
+ * @protected
75
+ */
76
+ this.renderer_ = renderer;
77
+
78
+ /**
79
+ * The renderer's constant provider.
80
+ * @type {!ConstantProvider}
81
+ * @protected
82
+ */
83
+ this.constants_ = this.renderer_.getConstants();
84
+
85
+ /**
86
+ * A measurable representing the output connection if the block has one.
87
+ * Otherwise null.
88
+ * @type {OutputConnection}
89
+ */
90
+ this.outputConnection = !block.outputConnection ?
91
+ null :
92
+ new OutputConnection(
93
+ this.constants_,
94
+ /** @type {!RenderedConnection} */ (block.outputConnection));
95
+
96
+ /**
97
+ * Whether the block should be rendered as a single line, either because
98
+ * it's inline or because it has been collapsed.
99
+ * @type {boolean}
100
+ */
101
+ this.isInline = block.getInputsInline() && !block.isCollapsed();
102
+
103
+ /**
104
+ * Whether the block is collapsed.
105
+ * @type {boolean}
106
+ */
107
+ this.isCollapsed = block.isCollapsed();
108
+
109
+ /**
110
+ * Whether the block is an insertion marker. Insertion markers are the same
111
+ * shape as normal blocks, but don't show fields.
112
+ * @type {boolean}
113
+ */
114
+ this.isInsertionMarker = block.isInsertionMarker();
115
+
116
+ /**
117
+ * True if the block should be rendered right-to-left.
118
+ * @type {boolean}
119
+ */
120
+ this.RTL = block.RTL;
121
+
122
+ /**
123
+ * The height of the rendered block, including child blocks.
124
+ * @type {number}
125
+ */
126
+ this.height = 0;
127
+
128
+ /**
129
+ * The width of the rendered block, including child blocks.
130
+ * @type {number}
131
+ */
132
+ this.widthWithChildren = 0;
133
+
134
+ /**
135
+ * The width of the rendered block, excluding child blocks. This is the
136
+ * right edge of the block when rendered LTR.
137
+ * @type {number}
138
+ */
139
+ this.width = 0;
140
+
141
+ /**
142
+ *
143
+ * @type {number}
144
+ */
145
+ this.statementEdge = 0;
146
+
147
+ /**
148
+ * An array of Row objects containing sizing information.
149
+ * @type {!Array<!Row>}
150
+ */
151
+ this.rows = [];
152
+
153
+ /**
154
+ * An array of input rows on the block.
155
+ * @type {!Array<!InputRow>}
156
+ */
157
+ this.inputRows = [];
158
+
159
+ /**
160
+ * An array of measurable objects containing hidden icons.
161
+ * @type {!Array<!Icon>}
162
+ */
163
+ this.hiddenIcons = [];
164
+
165
+ /**
166
+ * An object with rendering information about the top row of the block.
167
+ * @type {!TopRow}
168
+ */
169
+ this.topRow = new TopRow(this.constants_);
170
+
171
+ /**
172
+ * An object with rendering information about the bottom row of the block.
173
+ * @type {!BottomRow}
174
+ */
175
+ this.bottomRow = new BottomRow(this.constants_);
176
+
177
+ // The position of the start point for drawing, relative to the block's
178
+ // location.
179
+ this.startX = 0;
180
+ this.startY = 0;
181
+ }
83
182
 
84
183
  /**
85
- * A measurable representing the output connection if the block has one.
86
- * Otherwise null.
87
- * @type {OutputConnection}
184
+ * Get the block renderer in use.
185
+ * @return {!Renderer} The block renderer in use.
186
+ * @package
88
187
  */
89
- this.outputConnection = !block.outputConnection ?
90
- null :
91
- new OutputConnection(
92
- this.constants_,
93
- /** @type {RenderedConnection} */ (block.outputConnection));
188
+ getRenderer() {
189
+ return this.renderer_;
190
+ }
94
191
 
95
192
  /**
96
- * Whether the block should be rendered as a single line, either because it's
97
- * inline or because it has been collapsed.
98
- * @type {boolean}
193
+ * Populate and return an object containing all sizing information needed to
194
+ * draw this block.
195
+ *
196
+ * This measure pass does not propagate changes to the block (although fields
197
+ * may choose to rerender when getSize() is called). However, calling it
198
+ * repeatedly may be expensive.
199
+ *
200
+ * @package
99
201
  */
100
- this.isInline = block.getInputsInline() && !block.isCollapsed();
202
+ measure() {
203
+ this.createRows_();
204
+ this.addElemSpacing_();
205
+ this.addRowSpacing_();
206
+ this.computeBounds_();
207
+ this.alignRowElements_();
208
+ this.finalize_();
209
+ }
101
210
 
102
211
  /**
103
- * Whether the block is collapsed.
104
- * @type {boolean}
212
+ * Create rows of Measurable objects representing all renderable parts of the
213
+ * block.
214
+ * @protected
105
215
  */
106
- this.isCollapsed = block.isCollapsed();
216
+ createRows_() {
217
+ this.populateTopRow_();
218
+ this.rows.push(this.topRow);
219
+ let activeRow = new InputRow(this.constants_);
220
+ this.inputRows.push(activeRow);
221
+
222
+ // Icons always go on the first row, before anything else.
223
+ const icons = this.block_.getIcons();
224
+ for (let i = 0, icon; (icon = icons[i]); i++) {
225
+ const iconInfo = new Icon(this.constants_, icon);
226
+ if (this.isCollapsed && icon.collapseHidden) {
227
+ this.hiddenIcons.push(iconInfo);
228
+ } else {
229
+ activeRow.elements.push(iconInfo);
230
+ }
231
+ }
107
232
 
108
- /**
109
- * Whether the block is an insertion marker. Insertion markers are the same
110
- * shape as normal blocks, but don't show fields.
111
- * @type {boolean}
112
- */
113
- this.isInsertionMarker = block.isInsertionMarker();
233
+ let lastInput = null;
234
+ // Loop across all of the inputs on the block, creating objects for anything
235
+ // that needs to be rendered and breaking the block up into visual rows.
236
+ for (let i = 0, input; (input = this.block_.inputList[i]); i++) {
237
+ if (!input.isVisible()) {
238
+ continue;
239
+ }
240
+ if (this.shouldStartNewRow_(input, lastInput)) {
241
+ // Finish this row and create a new one.
242
+ this.rows.push(activeRow);
243
+ activeRow = new InputRow(this.constants_);
244
+ this.inputRows.push(activeRow);
245
+ }
114
246
 
115
- /**
116
- * True if the block should be rendered right-to-left.
117
- * @type {boolean}
118
- */
119
- this.RTL = block.RTL;
247
+ // All of the fields in an input go on the same row.
248
+ for (let j = 0, field; (field = input.fieldRow[j]); j++) {
249
+ activeRow.elements.push(new Field(this.constants_, field, input));
250
+ }
251
+ this.addInput_(input, activeRow);
252
+ lastInput = input;
253
+ }
120
254
 
121
- /**
122
- * The height of the rendered block, including child blocks.
123
- * @type {number}
124
- */
125
- this.height = 0;
255
+ if (this.isCollapsed) {
256
+ activeRow.hasJaggedEdge = true;
257
+ activeRow.elements.push(new JaggedEdge(this.constants_));
258
+ }
126
259
 
127
- /**
128
- * The width of the rendered block, including child blocks.
129
- * @type {number}
130
- */
131
- this.widthWithChildren = 0;
260
+ if (activeRow.elements.length || activeRow.hasDummyInput) {
261
+ this.rows.push(activeRow);
262
+ }
263
+ this.populateBottomRow_();
264
+ this.rows.push(this.bottomRow);
265
+ }
132
266
 
133
267
  /**
134
- * The width of the rendered block, excluding child blocks. This is the right
135
- * edge of the block when rendered LTR.
136
- * @type {number}
268
+ * Create all non-spacer elements that belong on the top row.
269
+ * @package
137
270
  */
138
- this.width = 0;
271
+ populateTopRow_() {
272
+ const hasPrevious = !!this.block_.previousConnection;
273
+ const hasHat = (this.block_.hat ? this.block_.hat === 'cap' :
274
+ this.constants_.ADD_START_HATS) &&
275
+ !this.outputConnection && !hasPrevious;
276
+
277
+ let cornerClass = this.topRow.hasLeftSquareCorner(this.block_) ?
278
+ SquareCorner :
279
+ RoundCorner;
280
+ this.topRow.elements.push(new cornerClass(this.constants_));
281
+
282
+ if (hasHat) {
283
+ const hat = new Hat(this.constants_);
284
+ this.topRow.elements.push(hat);
285
+ this.topRow.capline = hat.ascenderHeight;
286
+ } else if (hasPrevious) {
287
+ this.topRow.hasPreviousConnection = true;
288
+ this.topRow.connection = new PreviousConnection(
289
+ this.constants_,
290
+ /** @type {!RenderedConnection} */
291
+ (this.block_.previousConnection));
292
+ this.topRow.elements.push(this.topRow.connection);
293
+ }
139
294
 
140
- /**
141
- *
142
- * @type {number}
143
- */
144
- this.statementEdge = 0;
295
+ const precedesStatement = this.block_.inputList.length &&
296
+ this.block_.inputList[0].type === inputTypes.STATEMENT;
145
297
 
146
- /**
147
- * An array of Row objects containing sizing information.
148
- * @type {!Array<!Row>}
149
- */
150
- this.rows = [];
298
+ // This is the minimum height for the row. If one of its elements has a
299
+ // greater height it will be overwritten in the compute pass.
300
+ if (precedesStatement && !this.block_.isCollapsed()) {
301
+ this.topRow.minHeight =
302
+ this.constants_.TOP_ROW_PRECEDES_STATEMENT_MIN_HEIGHT;
303
+ } else {
304
+ this.topRow.minHeight = this.constants_.TOP_ROW_MIN_HEIGHT;
305
+ }
151
306
 
152
- /**
153
- * An array of input rows on the block.
154
- * @type {!Array<!InputRow>}
155
- */
156
- this.inputRows = [];
307
+ cornerClass = this.topRow.hasRightSquareCorner(this.block_) ? SquareCorner :
308
+ RoundCorner;
309
+ this.topRow.elements.push(new cornerClass(this.constants_, 'right'));
310
+ }
157
311
 
158
312
  /**
159
- * An array of measurable objects containing hidden icons.
160
- * @type {!Array<!Icon>}
313
+ * Create all non-spacer elements that belong on the bottom row.
314
+ * @package
161
315
  */
162
- this.hiddenIcons = [];
316
+ populateBottomRow_() {
317
+ this.bottomRow.hasNextConnection = !!this.block_.nextConnection;
318
+
319
+ const followsStatement = this.block_.inputList.length &&
320
+ this.block_.inputList[this.block_.inputList.length - 1].type ===
321
+ inputTypes.STATEMENT;
322
+
323
+ // This is the minimum height for the row. If one of its elements has a
324
+ // greater height it will be overwritten in the compute pass.
325
+ if (followsStatement) {
326
+ this.bottomRow.minHeight =
327
+ this.constants_.BOTTOM_ROW_AFTER_STATEMENT_MIN_HEIGHT;
328
+ } else {
329
+ this.bottomRow.minHeight = this.constants_.BOTTOM_ROW_MIN_HEIGHT;
330
+ }
163
331
 
164
- /**
165
- * An object with rendering information about the top row of the block.
166
- * @type {!TopRow}
167
- */
168
- this.topRow = new TopRow(this.constants_);
332
+ const leftSquareCorner = this.bottomRow.hasLeftSquareCorner(this.block_);
169
333
 
170
- /**
171
- * An object with rendering information about the bottom row of the block.
172
- * @type {!BottomRow}
173
- */
174
- this.bottomRow = new BottomRow(this.constants_);
334
+ if (leftSquareCorner) {
335
+ this.bottomRow.elements.push(new SquareCorner(this.constants_));
336
+ } else {
337
+ this.bottomRow.elements.push(new RoundCorner(this.constants_));
338
+ }
175
339
 
176
- // The position of the start point for drawing, relative to the block's
177
- // location.
178
- this.startX = 0;
179
- this.startY = 0;
180
- };
340
+ if (this.bottomRow.hasNextConnection) {
341
+ this.bottomRow.connection = new NextConnection(
342
+ this.constants_,
343
+ /** @type {!RenderedConnection} */ (this.block_.nextConnection));
344
+ this.bottomRow.elements.push(this.bottomRow.connection);
345
+ }
181
346
 
182
- /**
183
- * Get the block renderer in use.
184
- * @return {!Renderer} The block renderer in use.
185
- * @package
186
- */
187
- RenderInfo.prototype.getRenderer = function() {
188
- return this.renderer_;
189
- };
347
+ const rightSquareCorner = this.bottomRow.hasRightSquareCorner(this.block_);
190
348
 
191
- /**
192
- * Populate and return an object containing all sizing information needed to
193
- * draw this block.
194
- *
195
- * This measure pass does not propagate changes to the block (although fields
196
- * may choose to rerender when getSize() is called). However, calling it
197
- * repeatedly may be expensive.
198
- *
199
- * @package
200
- */
201
- RenderInfo.prototype.measure = function() {
202
- this.createRows_();
203
- this.addElemSpacing_();
204
- this.addRowSpacing_();
205
- this.computeBounds_();
206
- this.alignRowElements_();
207
- this.finalize_();
208
- };
209
-
210
- /**
211
- * Create rows of Measurable objects representing all renderable parts of the
212
- * block.
213
- * @protected
214
- */
215
- RenderInfo.prototype.createRows_ = function() {
216
- this.populateTopRow_();
217
- this.rows.push(this.topRow);
218
- let activeRow = new InputRow(this.constants_);
219
- this.inputRows.push(activeRow);
220
-
221
- // Icons always go on the first row, before anything else.
222
- const icons = this.block_.getIcons();
223
- for (let i = 0, icon; (icon = icons[i]); i++) {
224
- const iconInfo = new Icon(this.constants_, icon);
225
- if (this.isCollapsed && icon.collapseHidden) {
226
- this.hiddenIcons.push(iconInfo);
349
+ if (rightSquareCorner) {
350
+ this.bottomRow.elements.push(new SquareCorner(this.constants_, 'right'));
227
351
  } else {
228
- activeRow.elements.push(iconInfo);
352
+ this.bottomRow.elements.push(new RoundCorner(this.constants_, 'right'));
229
353
  }
230
354
  }
231
355
 
232
- let lastInput = null;
233
- // Loop across all of the inputs on the block, creating objects for anything
234
- // that needs to be rendered and breaking the block up into visual rows.
235
- for (let i = 0, input; (input = this.block_.inputList[i]); i++) {
236
- if (!input.isVisible()) {
237
- continue;
238
- }
239
- if (this.shouldStartNewRow_(input, lastInput)) {
240
- // Finish this row and create a new one.
241
- this.rows.push(activeRow);
242
- activeRow = new InputRow(this.constants_);
243
- this.inputRows.push(activeRow);
356
+ /**
357
+ * Add an input element to the active row, if needed, and record the type of
358
+ * the input on the row.
359
+ * @param {!Input} input The input to record information about.
360
+ * @param {!Row} activeRow The row that is currently being
361
+ * populated.
362
+ * @protected
363
+ */
364
+ addInput_(input, activeRow) {
365
+ // Non-dummy inputs have visual representations onscreen.
366
+ if (this.isInline && input.type === inputTypes.VALUE) {
367
+ activeRow.elements.push(new InlineInput(this.constants_, input));
368
+ activeRow.hasInlineInput = true;
369
+ } else if (input.type === inputTypes.STATEMENT) {
370
+ activeRow.elements.push(new StatementInput(this.constants_, input));
371
+ activeRow.hasStatement = true;
372
+ } else if (input.type === inputTypes.VALUE) {
373
+ activeRow.elements.push(new ExternalValueInput(this.constants_, input));
374
+ activeRow.hasExternalInput = true;
375
+ } else if (input.type === inputTypes.DUMMY) {
376
+ // Dummy inputs have no visual representation, but the information is
377
+ // still important.
378
+ activeRow.minHeight = Math.max(
379
+ activeRow.minHeight,
380
+ input.getSourceBlock() && input.getSourceBlock().isShadow() ?
381
+ this.constants_.DUMMY_INPUT_SHADOW_MIN_HEIGHT :
382
+ this.constants_.DUMMY_INPUT_MIN_HEIGHT);
383
+ activeRow.hasDummyInput = true;
244
384
  }
245
-
246
- // All of the fields in an input go on the same row.
247
- for (let j = 0, field; (field = input.fieldRow[j]); j++) {
248
- activeRow.elements.push(new Field(this.constants_, field, input));
385
+ if (activeRow.align === null) {
386
+ activeRow.align = input.align;
249
387
  }
250
- this.addInput_(input, activeRow);
251
- lastInput = input;
252
- }
253
-
254
- if (this.isCollapsed) {
255
- activeRow.hasJaggedEdge = true;
256
- activeRow.elements.push(new JaggedEdge(this.constants_));
257
388
  }
258
389
 
259
- if (activeRow.elements.length || activeRow.hasDummyInput) {
260
- this.rows.push(activeRow);
390
+ /**
391
+ * Decide whether to start a new row between the two Blockly.Inputs.
392
+ * @param {!Input} input The first input to consider
393
+ * @param {Input} lastInput The input that follows.
394
+ * @return {boolean} True if the next input should be rendered on a new row.
395
+ * @protected
396
+ */
397
+ shouldStartNewRow_(input, lastInput) {
398
+ // If this is the first input, just add to the existing row.
399
+ // That row is either empty or has some icons in it.
400
+ if (!lastInput) {
401
+ return false;
402
+ }
403
+ // A statement input or an input following one always gets a new row.
404
+ if (input.type === inputTypes.STATEMENT ||
405
+ lastInput.type === inputTypes.STATEMENT) {
406
+ return true;
407
+ }
408
+ // Value and dummy inputs get new row if inputs are not inlined.
409
+ if (input.type === inputTypes.VALUE || input.type === inputTypes.DUMMY) {
410
+ return !this.isInline;
411
+ }
412
+ return false;
261
413
  }
262
- this.populateBottomRow_();
263
- this.rows.push(this.bottomRow);
264
- };
265
414
 
266
- /**
267
- * Create all non-spacer elements that belong on the top row.
268
- * @package
269
- */
270
- RenderInfo.prototype.populateTopRow_ = function() {
271
- const hasPrevious = !!this.block_.previousConnection;
272
- const hasHat = (this.block_.hat ? this.block_.hat === 'cap' :
273
- this.constants_.ADD_START_HATS) &&
274
- !this.outputConnection && !hasPrevious;
275
-
276
- let cornerClass =
277
- this.topRow.hasLeftSquareCorner(this.block_) ? SquareCorner : RoundCorner;
278
- this.topRow.elements.push(new cornerClass(this.constants_));
279
-
280
- if (hasHat) {
281
- const hat = new Hat(this.constants_);
282
- this.topRow.elements.push(hat);
283
- this.topRow.capline = hat.ascenderHeight;
284
- } else if (hasPrevious) {
285
- this.topRow.hasPreviousConnection = true;
286
- this.topRow.connection = new PreviousConnection(
287
- this.constants_,
288
- /** @type {RenderedConnection} */
289
- (this.block_.previousConnection));
290
- this.topRow.elements.push(this.topRow.connection);
415
+ /**
416
+ * Add horizontal spacing between and around elements within each row.
417
+ * @protected
418
+ */
419
+ addElemSpacing_() {
420
+ for (let i = 0, row; (row = this.rows[i]); i++) {
421
+ const oldElems = row.elements;
422
+ row.elements = [];
423
+ // No spacing needed before the corner on the top row or the bottom row.
424
+ if (row.startsWithElemSpacer()) {
425
+ // There's a spacer before the first element in the row.
426
+ row.elements.push(new InRowSpacer(
427
+ this.constants_, this.getInRowSpacing_(null, oldElems[0])));
428
+ }
429
+ if (!oldElems.length) {
430
+ continue;
431
+ }
432
+ for (let e = 0; e < oldElems.length - 1; e++) {
433
+ row.elements.push(oldElems[e]);
434
+ const spacing = this.getInRowSpacing_(oldElems[e], oldElems[e + 1]);
435
+ row.elements.push(new InRowSpacer(this.constants_, spacing));
436
+ }
437
+ row.elements.push(oldElems[oldElems.length - 1]);
438
+ if (row.endsWithElemSpacer()) {
439
+ // There's a spacer after the last element in the row.
440
+ row.elements.push(new InRowSpacer(
441
+ this.constants_,
442
+ this.getInRowSpacing_(oldElems[oldElems.length - 1], null)));
443
+ }
444
+ }
291
445
  }
292
446
 
293
- const precedesStatement = this.block_.inputList.length &&
294
- this.block_.inputList[0].type === inputTypes.STATEMENT;
447
+ /**
448
+ * Calculate the width of a spacer element in a row based on the previous and
449
+ * next elements in that row. For instance, extra padding is added between
450
+ * two editable fields.
451
+ * @param {Measurable} prev The element before the
452
+ * spacer.
453
+ * @param {Measurable} next The element after the spacer.
454
+ * @return {number} The size of the spacing between the two elements.
455
+ * @protected
456
+ */
457
+ getInRowSpacing_(prev, next) {
458
+ if (!prev) {
459
+ // Statement input padding.
460
+ if (next && Types.isStatementInput(next)) {
461
+ return this.constants_.STATEMENT_INPUT_PADDING_LEFT;
462
+ }
463
+ }
464
+ // Between inputs and the end of the row.
465
+ if (prev && Types.isInput(prev) && !next) {
466
+ if (Types.isExternalInput(prev)) {
467
+ return this.constants_.NO_PADDING;
468
+ } else if (Types.isInlineInput(prev)) {
469
+ return this.constants_.LARGE_PADDING;
470
+ } else if (Types.isStatementInput(prev)) {
471
+ return this.constants_.NO_PADDING;
472
+ }
473
+ }
295
474
 
296
- // This is the minimum height for the row. If one of its elements has a
297
- // greater height it will be overwritten in the compute pass.
298
- if (precedesStatement && !this.block_.isCollapsed()) {
299
- this.topRow.minHeight =
300
- this.constants_.TOP_ROW_PRECEDES_STATEMENT_MIN_HEIGHT;
301
- } else {
302
- this.topRow.minHeight = this.constants_.TOP_ROW_MIN_HEIGHT;
303
- }
475
+ // Spacing between a square corner and a previous or next connection
476
+ if (prev && Types.isLeftSquareCorner(prev) && next) {
477
+ if (Types.isPreviousConnection(next) || Types.isNextConnection(next)) {
478
+ return next.notchOffset;
479
+ }
480
+ }
304
481
 
305
- cornerClass = this.topRow.hasRightSquareCorner(this.block_) ? SquareCorner :
306
- RoundCorner;
307
- this.topRow.elements.push(new cornerClass(this.constants_, 'right'));
308
- };
482
+ // Spacing between a rounded corner and a previous or next connection.
483
+ if (prev && Types.isLeftRoundedCorner(prev) && next) {
484
+ if (Types.isPreviousConnection(next) || Types.isNextConnection(next)) {
485
+ return next.notchOffset - this.constants_.CORNER_RADIUS;
486
+ }
487
+ }
309
488
 
310
- /**
311
- * Create all non-spacer elements that belong on the bottom row.
312
- * @package
313
- */
314
- RenderInfo.prototype.populateBottomRow_ = function() {
315
- this.bottomRow.hasNextConnection = !!this.block_.nextConnection;
316
-
317
- const followsStatement = this.block_.inputList.length &&
318
- this.block_.inputList[this.block_.inputList.length - 1].type ===
319
- inputTypes.STATEMENT;
320
-
321
- // This is the minimum height for the row. If one of its elements has a
322
- // greater height it will be overwritten in the compute pass.
323
- if (followsStatement) {
324
- this.bottomRow.minHeight =
325
- this.constants_.BOTTOM_ROW_AFTER_STATEMENT_MIN_HEIGHT;
326
- } else {
327
- this.bottomRow.minHeight = this.constants_.BOTTOM_ROW_MIN_HEIGHT;
489
+ return this.constants_.MEDIUM_PADDING;
328
490
  }
329
491
 
330
- const leftSquareCorner = this.bottomRow.hasLeftSquareCorner(this.block_);
492
+ /**
493
+ * Figure out where the right edge of the block and right edge of statement
494
+ * inputs should be placed.
495
+ * @protected
496
+ */
497
+ computeBounds_() {
498
+ let widestStatementRowFields = 0;
499
+ let blockWidth = 0;
500
+ let widestRowWithConnectedBlocks = 0;
501
+ for (let i = 0, row; (row = this.rows[i]); i++) {
502
+ row.measure();
503
+ blockWidth = Math.max(blockWidth, row.width);
504
+ if (row.hasStatement) {
505
+ const statementInput = row.getLastInput();
506
+ const innerWidth = row.width - statementInput.width;
507
+ widestStatementRowFields =
508
+ Math.max(widestStatementRowFields, innerWidth);
509
+ }
510
+ widestRowWithConnectedBlocks =
511
+ Math.max(widestRowWithConnectedBlocks, row.widthWithConnectedBlocks);
512
+ }
331
513
 
332
- if (leftSquareCorner) {
333
- this.bottomRow.elements.push(new SquareCorner(this.constants_));
334
- } else {
335
- this.bottomRow.elements.push(new RoundCorner(this.constants_));
336
- }
514
+ this.statementEdge = widestStatementRowFields;
515
+ this.width = blockWidth;
337
516
 
338
- if (this.bottomRow.hasNextConnection) {
339
- this.bottomRow.connection = new NextConnection(
340
- this.constants_,
341
- /** @type {RenderedConnection} */ (this.block_.nextConnection));
342
- this.bottomRow.elements.push(this.bottomRow.connection);
343
- }
517
+ for (let i = 0, row; (row = this.rows[i]); i++) {
518
+ if (row.hasStatement) {
519
+ row.statementEdge = this.statementEdge;
520
+ }
521
+ }
344
522
 
345
- const rightSquareCorner = this.bottomRow.hasRightSquareCorner(this.block_);
523
+ this.widthWithChildren = Math.max(blockWidth, widestRowWithConnectedBlocks);
346
524
 
347
- if (rightSquareCorner) {
348
- this.bottomRow.elements.push(new SquareCorner(this.constants_, 'right'));
349
- } else {
350
- this.bottomRow.elements.push(new RoundCorner(this.constants_, 'right'));
525
+ if (this.outputConnection) {
526
+ this.startX = this.outputConnection.width;
527
+ this.width += this.outputConnection.width;
528
+ this.widthWithChildren += this.outputConnection.width;
529
+ }
351
530
  }
352
- };
353
531
 
354
- /**
355
- * Add an input element to the active row, if needed, and record the type of the
356
- * input on the row.
357
- * @param {!Input} input The input to record information about.
358
- * @param {!Row} activeRow The row that is currently being
359
- * populated.
360
- * @protected
361
- */
362
- RenderInfo.prototype.addInput_ = function(input, activeRow) {
363
- // Non-dummy inputs have visual representations onscreen.
364
- if (this.isInline && input.type === inputTypes.VALUE) {
365
- activeRow.elements.push(new InlineInput(this.constants_, input));
366
- activeRow.hasInlineInput = true;
367
- } else if (input.type === inputTypes.STATEMENT) {
368
- activeRow.elements.push(new StatementInput(this.constants_, input));
369
- activeRow.hasStatement = true;
370
- } else if (input.type === inputTypes.VALUE) {
371
- activeRow.elements.push(new ExternalValueInput(this.constants_, input));
372
- activeRow.hasExternalInput = true;
373
- } else if (input.type === inputTypes.DUMMY) {
374
- // Dummy inputs have no visual representation, but the information is still
375
- // important.
376
- activeRow.minHeight = Math.max(
377
- activeRow.minHeight,
378
- input.getSourceBlock() && input.getSourceBlock().isShadow() ?
379
- this.constants_.DUMMY_INPUT_SHADOW_MIN_HEIGHT :
380
- this.constants_.DUMMY_INPUT_MIN_HEIGHT);
381
- activeRow.hasDummyInput = true;
382
- }
383
- if (activeRow.align === null) {
384
- activeRow.align = input.align;
532
+ /**
533
+ * Extra spacing may be necessary to make sure that the right sides of all
534
+ * rows line up. This can only be calculated after a first pass to calculate
535
+ * the sizes of all rows.
536
+ * @protected
537
+ */
538
+ alignRowElements_() {
539
+ for (let i = 0, row; (row = this.rows[i]); i++) {
540
+ if (row.hasStatement) {
541
+ this.alignStatementRow_(
542
+ /** @type {!InputRow} */ (row));
543
+ } else {
544
+ const currentWidth = row.width;
545
+ const desiredWidth = this.getDesiredRowWidth_(row);
546
+ const missingSpace = desiredWidth - currentWidth;
547
+ if (missingSpace > 0) {
548
+ this.addAlignmentPadding_(row, missingSpace);
549
+ }
550
+ if (Types.isTopOrBottomRow(row)) {
551
+ row.widthWithConnectedBlocks = row.width;
552
+ }
553
+ }
554
+ }
385
555
  }
386
- };
387
556
 
388
- /**
389
- * Decide whether to start a new row between the two Blockly.Inputs.
390
- * @param {!Input} input The first input to consider
391
- * @param {Input} lastInput The input that follows.
392
- * @return {boolean} True if the next input should be rendered on a new row.
393
- * @protected
394
- */
395
- RenderInfo.prototype.shouldStartNewRow_ = function(input, lastInput) {
396
- // If this is the first input, just add to the existing row.
397
- // That row is either empty or has some icons in it.
398
- if (!lastInput) {
399
- return false;
400
- }
401
- // A statement input or an input following one always gets a new row.
402
- if (input.type === inputTypes.STATEMENT ||
403
- lastInput.type === inputTypes.STATEMENT) {
404
- return true;
405
- }
406
- // Value and dummy inputs get new row if inputs are not inlined.
407
- if (input.type === inputTypes.VALUE || input.type === inputTypes.DUMMY) {
408
- return !this.isInline;
557
+ /**
558
+ * Calculate the desired width of an input row.
559
+ * @param {!Row} _row The input row.
560
+ * @return {number} The desired width of the input row.
561
+ * @protected
562
+ */
563
+ getDesiredRowWidth_(_row) {
564
+ return this.width - this.startX;
409
565
  }
410
- return false;
411
- };
412
566
 
413
- /**
414
- * Add horizontal spacing between and around elements within each row.
415
- * @protected
416
- */
417
- RenderInfo.prototype.addElemSpacing_ = function() {
418
- for (let i = 0, row; (row = this.rows[i]); i++) {
419
- const oldElems = row.elements;
420
- row.elements = [];
421
- // No spacing needed before the corner on the top row or the bottom row.
422
- if (row.startsWithElemSpacer()) {
423
- // There's a spacer before the first element in the row.
424
- row.elements.push(new InRowSpacer(
425
- this.constants_, this.getInRowSpacing_(null, oldElems[0])));
426
- }
427
- if (!oldElems.length) {
428
- continue;
429
- }
430
- for (let e = 0; e < oldElems.length - 1; e++) {
431
- row.elements.push(oldElems[e]);
432
- const spacing = this.getInRowSpacing_(oldElems[e], oldElems[e + 1]);
433
- row.elements.push(new InRowSpacer(this.constants_, spacing));
434
- }
435
- row.elements.push(oldElems[oldElems.length - 1]);
436
- if (row.endsWithElemSpacer()) {
437
- // There's a spacer after the last element in the row.
438
- row.elements.push(new InRowSpacer(
439
- this.constants_,
440
- this.getInRowSpacing_(oldElems[oldElems.length - 1], null)));
567
+ /**
568
+ * Modify the given row to add the given amount of padding around its fields.
569
+ * The exact location of the padding is based on the alignment property of the
570
+ * last input in the field.
571
+ * @param {!Row} row The row to add padding to.
572
+ * @param {number} missingSpace How much padding to add.
573
+ * @protected
574
+ */
575
+ addAlignmentPadding_(row, missingSpace) {
576
+ const firstSpacer = row.getFirstSpacer();
577
+ const lastSpacer = row.getLastSpacer();
578
+ if (row.hasExternalInput || row.hasStatement) {
579
+ row.widthWithConnectedBlocks += missingSpace;
441
580
  }
442
- }
443
- };
444
581
 
445
- /**
446
- * Calculate the width of a spacer element in a row based on the previous and
447
- * next elements in that row. For instance, extra padding is added between two
448
- * editable fields.
449
- * @param {Measurable} prev The element before the
450
- * spacer.
451
- * @param {Measurable} next The element after the spacer.
452
- * @return {number} The size of the spacing between the two elements.
453
- * @protected
454
- */
455
- RenderInfo.prototype.getInRowSpacing_ = function(prev, next) {
456
- if (!prev) {
457
- // Statement input padding.
458
- if (next && Types.isStatementInput(next)) {
459
- return this.constants_.STATEMENT_INPUT_PADDING_LEFT;
460
- }
461
- }
462
- // Between inputs and the end of the row.
463
- if (prev && Types.isInput(prev) && !next) {
464
- if (Types.isExternalInput(prev)) {
465
- return this.constants_.NO_PADDING;
466
- } else if (Types.isInlineInput(prev)) {
467
- return this.constants_.LARGE_PADDING;
468
- } else if (Types.isStatementInput(prev)) {
469
- return this.constants_.NO_PADDING;
582
+ // Decide where the extra padding goes.
583
+ if (row.align === Align.LEFT) {
584
+ // Add padding to the end of the row.
585
+ lastSpacer.width += missingSpace;
586
+ } else if (row.align === Align.CENTRE) {
587
+ // Split the padding between the beginning and end of the row.
588
+ firstSpacer.width += missingSpace / 2;
589
+ lastSpacer.width += missingSpace / 2;
590
+ } else if (row.align === Align.RIGHT) {
591
+ // Add padding at the beginning of the row.
592
+ firstSpacer.width += missingSpace;
593
+ } else {
594
+ // Default to left-aligning.
595
+ lastSpacer.width += missingSpace;
470
596
  }
597
+ row.width += missingSpace;
471
598
  }
472
599
 
473
- // Spacing between a square corner and a previous or next connection
474
- if (prev && Types.isLeftSquareCorner(prev) && next) {
475
- if (Types.isPreviousConnection(next) || Types.isNextConnection(next)) {
476
- return next.notchOffset;
600
+ /**
601
+ * Align the elements of a statement row based on computed bounds.
602
+ * Unlike other types of rows, statement rows add space in multiple places.
603
+ * @param {!InputRow} row The statement row to resize.
604
+ * @protected
605
+ */
606
+ alignStatementRow_(row) {
607
+ const statementInput = row.getLastInput();
608
+ let currentWidth = row.width - statementInput.width;
609
+ let desiredWidth = this.statementEdge;
610
+ // Add padding before the statement input.
611
+ const missingSpace = desiredWidth - currentWidth;
612
+ if (missingSpace > 0) {
613
+ this.addAlignmentPadding_(row, missingSpace);
477
614
  }
615
+ // Also widen the statement input to reach to the right side of the
616
+ // block. Note that this does not add padding.
617
+ currentWidth = row.width;
618
+ desiredWidth = this.getDesiredRowWidth_(row);
619
+ statementInput.width += (desiredWidth - currentWidth);
620
+ statementInput.height = Math.max(statementInput.height, row.height);
621
+ row.width += (desiredWidth - currentWidth);
622
+ row.widthWithConnectedBlocks =
623
+ Math.max(row.width, this.statementEdge + row.connectedBlockWidths);
478
624
  }
479
625
 
480
- // Spacing between a rounded corner and a previous or next connection.
481
- if (prev && Types.isLeftRoundedCorner(prev) && next) {
482
- if (Types.isPreviousConnection(next) || Types.isNextConnection(next)) {
483
- return next.notchOffset - this.constants_.CORNER_RADIUS;
626
+ /**
627
+ * Add spacers between rows and set their sizes.
628
+ * @protected
629
+ */
630
+ addRowSpacing_() {
631
+ const oldRows = this.rows;
632
+ this.rows = [];
633
+
634
+ for (let r = 0; r < oldRows.length; r++) {
635
+ this.rows.push(oldRows[r]);
636
+ if (r !== oldRows.length - 1) {
637
+ this.rows.push(this.makeSpacerRow_(oldRows[r], oldRows[r + 1]));
638
+ }
484
639
  }
485
640
  }
486
641
 
487
- return this.constants_.MEDIUM_PADDING;
488
- };
489
-
490
- /**
491
- * Figure out where the right edge of the block and right edge of statement
492
- * inputs should be placed.
493
- * @protected
494
- */
495
- // TODO: More cleanup.
496
- RenderInfo.prototype.computeBounds_ = function() {
497
- let widestStatementRowFields = 0;
498
- let blockWidth = 0;
499
- let widestRowWithConnectedBlocks = 0;
500
- for (let i = 0, row; (row = this.rows[i]); i++) {
501
- row.measure();
502
- blockWidth = Math.max(blockWidth, row.width);
503
- if (row.hasStatement) {
504
- const statementInput = row.getLastInput();
505
- const innerWidth = row.width - statementInput.width;
506
- widestStatementRowFields = Math.max(widestStatementRowFields, innerWidth);
507
- }
508
- widestRowWithConnectedBlocks =
509
- Math.max(widestRowWithConnectedBlocks, row.widthWithConnectedBlocks);
510
- }
511
-
512
- this.statementEdge = widestStatementRowFields;
513
- this.width = blockWidth;
514
-
515
- for (let i = 0, row; (row = this.rows[i]); i++) {
516
- if (row.hasStatement) {
517
- row.statementEdge = this.statementEdge;
642
+ /**
643
+ * Create a spacer row to go between prev and next, and set its size.
644
+ * @param {!Row} prev The previous row.
645
+ * @param {!Row} next The next row.
646
+ * @return {!SpacerRow} The newly created spacer row.
647
+ * @protected
648
+ */
649
+ makeSpacerRow_(prev, next) {
650
+ const height = this.getSpacerRowHeight_(prev, next);
651
+ const width = this.getSpacerRowWidth_(prev, next);
652
+ const spacer = new SpacerRow(this.constants_, height, width);
653
+ if (prev.hasStatement) {
654
+ spacer.followsStatement = true;
518
655
  }
656
+ if (next.hasStatement) {
657
+ spacer.precedesStatement = true;
658
+ }
659
+ return spacer;
519
660
  }
520
661
 
521
- this.widthWithChildren = Math.max(blockWidth, widestRowWithConnectedBlocks);
662
+ /**
663
+ * Calculate the width of a spacer row.
664
+ * @param {!Row} _prev The row before the spacer.
665
+ * @param {!Row} _next The row after the spacer.
666
+ * @return {number} The desired width of the spacer row between these two
667
+ * rows.
668
+ * @protected
669
+ */
670
+ getSpacerRowWidth_(_prev, _next) {
671
+ return this.width - this.startX;
672
+ }
522
673
 
523
- if (this.outputConnection) {
524
- this.startX = this.outputConnection.width;
525
- this.width += this.outputConnection.width;
526
- this.widthWithChildren += this.outputConnection.width;
674
+ /**
675
+ * Calculate the height of a spacer row.
676
+ * @param {!Row} _prev The row before the spacer.
677
+ * @param {!Row} _next The row after the spacer.
678
+ * @return {number} The desired height of the spacer row between these two
679
+ * rows.
680
+ * @protected
681
+ */
682
+ getSpacerRowHeight_(_prev, _next) {
683
+ return this.constants_.MEDIUM_PADDING;
527
684
  }
528
- };
529
685
 
530
- /**
531
- * Extra spacing may be necessary to make sure that the right sides of all
532
- * rows line up. This can only be calculated after a first pass to calculate
533
- * the sizes of all rows.
534
- * @protected
535
- */
536
- RenderInfo.prototype.alignRowElements_ = function() {
537
- for (let i = 0, row; (row = this.rows[i]); i++) {
538
- if (row.hasStatement) {
539
- this.alignStatementRow_(
540
- /** @type {!InputRow} */ (row));
541
- } else {
542
- const currentWidth = row.width;
543
- const desiredWidth = this.getDesiredRowWidth_(row);
544
- const missingSpace = desiredWidth - currentWidth;
545
- if (missingSpace > 0) {
546
- this.addAlignmentPadding_(row, missingSpace);
686
+ /**
687
+ * Calculate the centerline of an element in a rendered row.
688
+ * This base implementation puts the centerline at the middle of the row
689
+ * vertically, with no special cases. You will likely need extra logic to
690
+ * handle (at minimum) top and bottom rows.
691
+ * @param {!Row} row The row containing the element.
692
+ * @param {!Measurable} elem The element to place.
693
+ * @return {number} The desired centerline of the given element, as an offset
694
+ * from the top left of the block.
695
+ * @protected
696
+ */
697
+ getElemCenterline_(row, elem) {
698
+ if (Types.isSpacer(elem)) {
699
+ return row.yPos + elem.height / 2;
700
+ }
701
+ if (Types.isBottomRow(row)) {
702
+ const bottomRow = /** @type {!BottomRow} */ (row);
703
+ const baseline =
704
+ bottomRow.yPos + bottomRow.height - bottomRow.descenderHeight;
705
+ if (Types.isNextConnection(elem)) {
706
+ return baseline + elem.height / 2;
547
707
  }
548
- if (Types.isTopOrBottomRow(row)) {
549
- row.widthWithConnectedBlocks = row.width;
708
+ return baseline - elem.height / 2;
709
+ }
710
+ if (Types.isTopRow(row)) {
711
+ const topRow = /** @type {!TopRow} */ (row);
712
+ if (Types.isHat(elem)) {
713
+ return topRow.capline - elem.height / 2;
550
714
  }
715
+ return topRow.capline + elem.height / 2;
551
716
  }
552
- }
553
- };
554
-
555
- /**
556
- * Calculate the desired width of an input row.
557
- * @param {!Row} _row The input row.
558
- * @return {number} The desired width of the input row.
559
- * @protected
560
- */
561
- RenderInfo.prototype.getDesiredRowWidth_ = function(_row) {
562
- return this.width - this.startX;
563
- };
564
-
565
- /**
566
- * Modify the given row to add the given amount of padding around its fields.
567
- * The exact location of the padding is based on the alignment property of the
568
- * last input in the field.
569
- * @param {Row} row The row to add padding to.
570
- * @param {number} missingSpace How much padding to add.
571
- * @protected
572
- */
573
- RenderInfo.prototype.addAlignmentPadding_ = function(row, missingSpace) {
574
- const firstSpacer = row.getFirstSpacer();
575
- const lastSpacer = row.getLastSpacer();
576
- if (row.hasExternalInput || row.hasStatement) {
577
- row.widthWithConnectedBlocks += missingSpace;
717
+ return row.yPos + row.height / 2;
578
718
  }
579
719
 
580
- // Decide where the extra padding goes.
581
- if (row.align === Align.LEFT) {
582
- // Add padding to the end of the row.
583
- lastSpacer.width += missingSpace;
584
- } else if (row.align === Align.CENTRE) {
585
- // Split the padding between the beginning and end of the row.
586
- firstSpacer.width += missingSpace / 2;
587
- lastSpacer.width += missingSpace / 2;
588
- } else if (row.align === Align.RIGHT) {
589
- // Add padding at the beginning of the row.
590
- firstSpacer.width += missingSpace;
591
- } else {
592
- // Default to left-aligning.
593
- lastSpacer.width += missingSpace;
594
- }
595
- row.width += missingSpace;
596
- };
597
-
598
- /**
599
- * Align the elements of a statement row based on computed bounds.
600
- * Unlike other types of rows, statement rows add space in multiple places.
601
- * @param {!InputRow} row The statement row to resize.
602
- * @protected
603
- */
604
- RenderInfo.prototype.alignStatementRow_ = function(row) {
605
- const statementInput = row.getLastInput();
606
- let currentWidth = row.width - statementInput.width;
607
- let desiredWidth = this.statementEdge;
608
- // Add padding before the statement input.
609
- const missingSpace = desiredWidth - currentWidth;
610
- if (missingSpace > 0) {
611
- this.addAlignmentPadding_(row, missingSpace);
612
- }
613
- // Also widen the statement input to reach to the right side of the
614
- // block. Note that this does not add padding.
615
- currentWidth = row.width;
616
- desiredWidth = this.getDesiredRowWidth_(row);
617
- statementInput.width += (desiredWidth - currentWidth);
618
- statementInput.height = Math.max(statementInput.height, row.height);
619
- row.width += (desiredWidth - currentWidth);
620
- row.widthWithConnectedBlocks =
621
- Math.max(row.width, this.statementEdge + row.connectedBlockWidths);
622
- };
623
-
624
- /**
625
- * Add spacers between rows and set their sizes.
626
- * @protected
627
- */
628
- RenderInfo.prototype.addRowSpacing_ = function() {
629
- const oldRows = this.rows;
630
- this.rows = [];
631
-
632
- for (let r = 0; r < oldRows.length; r++) {
633
- this.rows.push(oldRows[r]);
634
- if (r !== oldRows.length - 1) {
635
- this.rows.push(this.makeSpacerRow_(oldRows[r], oldRows[r + 1]));
720
+ /**
721
+ * Record final position information on elements on the given row, for use in
722
+ * drawing. At minimum this records xPos and centerline on each element.
723
+ * @param {!Row} row The row containing the elements.
724
+ * @protected
725
+ */
726
+ recordElemPositions_(row) {
727
+ let xCursor = row.xPos;
728
+ for (let j = 0, elem; (elem = row.elements[j]); j++) {
729
+ // Now that row heights are finalized, make spacers use the row height.
730
+ if (Types.isSpacer(elem)) {
731
+ elem.height = row.height;
732
+ }
733
+ elem.xPos = xCursor;
734
+ elem.centerline = this.getElemCenterline_(row, elem);
735
+ xCursor += elem.width;
636
736
  }
637
737
  }
638
- };
639
-
640
- /**
641
- * Create a spacer row to go between prev and next, and set its size.
642
- * @param {!Row} prev The previous row.
643
- * @param {!Row} next The next row.
644
- * @return {!SpacerRow} The newly created spacer row.
645
- * @protected
646
- */
647
- RenderInfo.prototype.makeSpacerRow_ = function(prev, next) {
648
- const height = this.getSpacerRowHeight_(prev, next);
649
- const width = this.getSpacerRowWidth_(prev, next);
650
- const spacer = new SpacerRow(this.constants_, height, width);
651
- if (prev.hasStatement) {
652
- spacer.followsStatement = true;
653
- }
654
- if (next.hasStatement) {
655
- spacer.precedesStatement = true;
656
- }
657
- return spacer;
658
- };
659
-
660
- /**
661
- * Calculate the width of a spacer row.
662
- * @param {!Row} _prev The row before the spacer.
663
- * @param {!Row} _next The row after the spacer.
664
- * @return {number} The desired width of the spacer row between these two rows.
665
- * @protected
666
- */
667
- RenderInfo.prototype.getSpacerRowWidth_ = function(_prev, _next) {
668
- return this.width - this.startX;
669
- };
670
738
 
671
- /**
672
- * Calculate the height of a spacer row.
673
- * @param {!Row} _prev The row before the spacer.
674
- * @param {!Row} _next The row after the spacer.
675
- * @return {number} The desired height of the spacer row between these two rows.
676
- * @protected
677
- */
678
- RenderInfo.prototype.getSpacerRowHeight_ = function(_prev, _next) {
679
- return this.constants_.MEDIUM_PADDING;
680
- };
681
-
682
- /**
683
- * Calculate the centerline of an element in a rendered row.
684
- * This base implementation puts the centerline at the middle of the row
685
- * vertically, with no special cases. You will likely need extra logic to
686
- * handle (at minimum) top and bottom rows.
687
- * @param {!Row} row The row containing the element.
688
- * @param {!Measurable} elem The element to place.
689
- * @return {number} The desired centerline of the given element, as an offset
690
- * from the top left of the block.
691
- * @protected
692
- */
693
- RenderInfo.prototype.getElemCenterline_ = function(row, elem) {
694
- if (Types.isSpacer(elem)) {
695
- return row.yPos + elem.height / 2;
696
- }
697
- if (Types.isBottomRow(row)) {
698
- const baseline = row.yPos + row.height - row.descenderHeight;
699
- if (Types.isNextConnection(elem)) {
700
- return baseline + elem.height / 2;
739
+ /**
740
+ * Make any final changes to the rendering information object. In particular,
741
+ * store the y position of each row, and record the height of the full block.
742
+ * @protected
743
+ */
744
+ finalize_() {
745
+ // Performance note: this could be combined with the draw pass, if the time
746
+ // that this takes is excessive. But it shouldn't be, because it only
747
+ // accesses and sets properties that already exist on the objects.
748
+ let widestRowWithConnectedBlocks = 0;
749
+ let yCursor = 0;
750
+ for (let i = 0, row; (row = this.rows[i]); i++) {
751
+ row.yPos = yCursor;
752
+ row.xPos = this.startX;
753
+ yCursor += row.height;
754
+
755
+ widestRowWithConnectedBlocks =
756
+ Math.max(widestRowWithConnectedBlocks, row.widthWithConnectedBlocks);
757
+ this.recordElemPositions_(row);
701
758
  }
702
- return baseline - elem.height / 2;
703
- }
704
- if (Types.isTopRow(row)) {
705
- if (Types.isHat(elem)) {
706
- return row.capline - elem.height / 2;
759
+ if (this.outputConnection && this.block_.nextConnection &&
760
+ this.block_.nextConnection.isConnected()) {
761
+ // Include width of connected block in value to stack width measurement.
762
+ widestRowWithConnectedBlocks = Math.max(
763
+ widestRowWithConnectedBlocks,
764
+ this.block_.nextConnection.targetBlock().getHeightWidth().width);
707
765
  }
708
- return row.capline + elem.height / 2;
709
- }
710
- return row.yPos + row.height / 2;
711
- };
712
766
 
713
- /**
714
- * Record final position information on elements on the given row, for use in
715
- * drawing. At minimum this records xPos and centerline on each element.
716
- * @param {!Row} row The row containing the elements.
717
- * @protected
718
- */
719
- RenderInfo.prototype.recordElemPositions_ = function(row) {
720
- let xCursor = row.xPos;
721
- for (let j = 0, elem; (elem = row.elements[j]); j++) {
722
- // Now that row heights are finalized, make spacers use the row height.
723
- if (Types.isSpacer(elem)) {
724
- elem.height = row.height;
725
- }
726
- elem.xPos = xCursor;
727
- elem.centerline = this.getElemCenterline_(row, elem);
728
- xCursor += elem.width;
729
- }
730
- };
767
+ this.widthWithChildren = widestRowWithConnectedBlocks + this.startX;
731
768
 
732
- /**
733
- * Make any final changes to the rendering information object. In particular,
734
- * store the y position of each row, and record the height of the full block.
735
- * @protected
736
- */
737
- RenderInfo.prototype.finalize_ = function() {
738
- // Performance note: this could be combined with the draw pass, if the time
739
- // that this takes is excessive. But it shouldn't be, because it only
740
- // accesses and sets properties that already exist on the objects.
741
- let widestRowWithConnectedBlocks = 0;
742
- let yCursor = 0;
743
- for (let i = 0, row; (row = this.rows[i]); i++) {
744
- row.yPos = yCursor;
745
- row.xPos = this.startX;
746
- yCursor += row.height;
747
-
748
- widestRowWithConnectedBlocks =
749
- Math.max(widestRowWithConnectedBlocks, row.widthWithConnectedBlocks);
750
- this.recordElemPositions_(row);
769
+ this.height = yCursor;
770
+ this.startY = this.topRow.capline;
771
+ this.bottomRow.baseline = yCursor - this.bottomRow.descenderHeight;
751
772
  }
752
- if (this.outputConnection && this.block_.nextConnection &&
753
- this.block_.nextConnection.isConnected()) {
754
- // Include width of connected block in value to stack width measurement.
755
- widestRowWithConnectedBlocks = Math.max(
756
- widestRowWithConnectedBlocks,
757
- this.block_.nextConnection.targetBlock().getHeightWidth().width);
758
- }
759
-
760
- this.widthWithChildren = widestRowWithConnectedBlocks + this.startX;
761
-
762
- this.height = yCursor;
763
- this.startY = this.topRow.capline;
764
- this.bottomRow.baseline = yCursor - this.bottomRow.descenderHeight;
765
- };
773
+ }
766
774
 
767
775
  exports.RenderInfo = RenderInfo;