Merge pull request #3611 from google/rc_2020_1

Rc 2020 1
This commit is contained in:
alschmiedt
2020-01-23 14:48:32 -08:00
committed by GitHub
382 changed files with 18321 additions and 9968 deletions

View File

@@ -61,8 +61,6 @@
"space-infix-ops": ["error"],
"_comment": "Blockly uses 'use strict' in files",
"strict": ["off"],
"_comment": "Blockly often uses cond-assignment in loops",
"no-cond-assign": ["off"],
"_comment": "Closure style allows redeclarations",
"no-redeclare": ["off"],
"valid-jsdoc": ["error", {"requireReturn": false}],
@@ -74,7 +72,8 @@
"balanced": true
},
"exceptions": ["*"]
}]
}],
"es5/no-es6-methods": ["warn"]
},
"env": {
"browser": true
@@ -83,5 +82,8 @@
"Blockly": true,
"goog": true
},
"extends": "eslint:recommended"
"extends": [
"eslint:recommended",
"plugin:es5/no-es2015"
]
}

View File

@@ -4,6 +4,7 @@ dist: xenial
node_js:
- 8
- 10
- 12
addons:
chrome: stable
firefox: latest

File diff suppressed because it is too large Load Diff

View File

@@ -1,4 +1,4 @@
// Do not edit this file; automatically generated by build.py.
// Do not edit this file; automatically generated by gulp.
'use strict';
this.IS_NODE_JS = !!(typeof module !== 'undefined' && module.exports);
@@ -21,122 +21,133 @@ this.BLOCKLY_DIR = (function(root) {
this.BLOCKLY_BOOT = function(root) {
// Execute after Closure has loaded.
goog.addDependency("../../core/block.js", ['Blockly.Block'], ['Blockly.Blocks', 'Blockly.Connection', 'Blockly.Events', 'Blockly.Events.BlockChange', 'Blockly.Events.BlockCreate', 'Blockly.Events.BlockDelete', 'Blockly.Events.BlockMove', 'Blockly.Extensions', 'Blockly.Input', 'Blockly.utils', 'Blockly.utils.Coordinate', 'Blockly.utils.colour', 'Blockly.utils.object', 'Blockly.fieldRegistry', 'Blockly.utils.string', 'Blockly.Workspace']);
goog.addDependency("../../core/block_animations.js", ['Blockly.blockAnimations'], ['Blockly.utils.dom']);
goog.addDependency("../../core/block_drag_surface.js", ['Blockly.BlockDragSurfaceSvg'], ['Blockly.utils', 'Blockly.utils.Coordinate', 'Blockly.utils.dom']);
goog.addDependency("../../core/block_dragger.js", ['Blockly.BlockDragger'], ['Blockly.blockAnimations', 'Blockly.Events', 'Blockly.Events.BlockMove', 'Blockly.InsertionMarkerManager', 'Blockly.utils.Coordinate', 'Blockly.utils.dom']);
goog.addDependency("../../core/block_dragger.js", ['Blockly.BlockDragger'], ['Blockly.Events', 'Blockly.Events.BlockMove', 'Blockly.Events.Ui', 'Blockly.InsertionMarkerManager', 'Blockly.blockAnimations', 'Blockly.utils.Coordinate', 'Blockly.utils.dom']);
goog.addDependency("../../core/block_events.js", ['Blockly.Events.BlockBase', 'Blockly.Events.BlockChange', 'Blockly.Events.BlockCreate', 'Blockly.Events.BlockDelete', 'Blockly.Events.BlockMove', 'Blockly.Events.Change', 'Blockly.Events.Create', 'Blockly.Events.Delete', 'Blockly.Events.Move'], ['Blockly.Events', 'Blockly.Events.Abstract', 'Blockly.utils.Coordinate', 'Blockly.utils.object', 'Blockly.utils.xml']);
goog.addDependency("../../core/block_svg.js", ['Blockly.BlockSvg'], ['Blockly.Block', 'Blockly.blockAnimations', 'Blockly.blockRendering.IPathObject', 'Blockly.ContextMenu', 'Blockly.Events', 'Blockly.Events.Ui', 'Blockly.Events.BlockMove', 'Blockly.Msg', 'Blockly.RenderedConnection', 'Blockly.Tooltip', 'Blockly.Touch', 'Blockly.utils', 'Blockly.utils.Coordinate', 'Blockly.utils.dom', 'Blockly.utils.object', 'Blockly.utils.Rect', 'Blockly.Warning']);
goog.addDependency("../../core/blockly.js", ['Blockly'], ['Blockly.constants', 'Blockly.Events', 'Blockly.Events.Ui', 'Blockly.inject', 'Blockly.navigation', 'Blockly.Procedures', 'Blockly.Tooltip', 'Blockly.Touch', 'Blockly.utils', 'Blockly.utils.colour', 'Blockly.Variables', 'Blockly.WidgetDiv', 'Blockly.WorkspaceSvg', 'Blockly.Xml']);
goog.addDependency("../../core/block_svg.js", ['Blockly.BlockSvg'], ['Blockly.ASTNode', 'Blockly.Block', 'Blockly.ContextMenu', 'Blockly.Events', 'Blockly.Events.BlockMove', 'Blockly.Events.Ui', 'Blockly.Msg', 'Blockly.RenderedConnection', 'Blockly.TabNavigateCursor', 'Blockly.Tooltip', 'Blockly.Touch', 'Blockly.blockAnimations', 'Blockly.blockRendering.IPathObject', 'Blockly.navigation', 'Blockly.utils', 'Blockly.utils.Coordinate', 'Blockly.utils.Rect', 'Blockly.utils.dom', 'Blockly.utils.object']);
goog.addDependency("../../core/block.js", ['Blockly.Block'], ['Blockly.Blocks', 'Blockly.Connection', 'Blockly.Events', 'Blockly.Events.BlockChange', 'Blockly.Events.BlockCreate', 'Blockly.Events.BlockDelete', 'Blockly.Events.BlockMove', 'Blockly.Extensions', 'Blockly.Input', 'Blockly.Workspace', 'Blockly.fieldRegistry', 'Blockly.navigation', 'Blockly.utils', 'Blockly.utils.Coordinate', 'Blockly.utils.object', 'Blockly.utils.string']);
goog.addDependency("../../core/blockly.js", ['Blockly'], ['Blockly.Events', 'Blockly.Events.Ui', 'Blockly.Procedures', 'Blockly.Tooltip', 'Blockly.Touch', 'Blockly.Variables', 'Blockly.WidgetDiv', 'Blockly.WorkspaceSvg', 'Blockly.Xml', 'Blockly.constants', 'Blockly.inject', 'Blockly.navigation', 'Blockly.utils', 'Blockly.utils.colour']);
goog.addDependency("../../core/blocks.js", ['Blockly.Blocks'], []);
goog.addDependency("../../core/bubble.js", ['Blockly.Bubble'], ['Blockly.Scrollbar', 'Blockly.Touch', 'Blockly.utils', 'Blockly.utils.Coordinate', 'Blockly.utils.dom', 'Blockly.utils.math', 'Blockly.utils.userAgent', 'Blockly.Workspace']);
goog.addDependency("../../core/bubble_dragger.js", ['Blockly.BubbleDragger'], ['Blockly.Bubble', 'Blockly.Events', 'Blockly.Events.CommentMove', 'Blockly.utils', 'Blockly.utils.Coordinate']);
goog.addDependency("../../core/comment.js", ['Blockly.Comment'], ['Blockly.Bubble', 'Blockly.Events', 'Blockly.Events.BlockChange', 'Blockly.Events.Ui', 'Blockly.Icon', 'Blockly.utils.dom', 'Blockly.utils.object', 'Blockly.utils.userAgent', 'Blockly.Warning']);
goog.addDependency("../../core/components/component.js", ['Blockly.Component', 'Blockly.Component.Error'], ['Blockly.utils.dom', 'Blockly.utils.IdGenerator', 'Blockly.utils.style']);
goog.addDependency("../../core/components/menu/menu.js", ['Blockly.Menu'], ['Blockly.Component', 'Blockly.utils.aria', 'Blockly.utils.dom', 'Blockly.utils.object']);
goog.addDependency("../../core/bubble.js", ['Blockly.Bubble'], ['Blockly.Scrollbar', 'Blockly.Touch', 'Blockly.Workspace', 'Blockly.utils', 'Blockly.utils.Coordinate', 'Blockly.utils.dom', 'Blockly.utils.math', 'Blockly.utils.userAgent']);
goog.addDependency("../../core/comment.js", ['Blockly.Comment'], ['Blockly.Bubble', 'Blockly.Css', 'Blockly.Events', 'Blockly.Events.BlockChange', 'Blockly.Events.Ui', 'Blockly.Icon', 'Blockly.Warning', 'Blockly.utils.dom', 'Blockly.utils.object', 'Blockly.utils.userAgent']);
goog.addDependency("../../core/components/component.js", ['Blockly.Component', 'Blockly.Component.Error'], ['Blockly.utils.IdGenerator', 'Blockly.utils.dom', 'Blockly.utils.style']);
goog.addDependency("../../core/components/menu/menu.js", ['Blockly.Menu'], ['Blockly.Component', 'Blockly.utils.Coordinate', 'Blockly.utils.aria', 'Blockly.utils.dom', 'Blockly.utils.object']);
goog.addDependency("../../core/components/menu/menuitem.js", ['Blockly.MenuItem'], ['Blockly.Component', 'Blockly.utils.aria', 'Blockly.utils.dom', 'Blockly.utils.object']);
goog.addDependency("../../core/components/tree/basenode.js", ['Blockly.tree.BaseNode'], ['Blockly.Component', 'Blockly.utils.aria', 'Blockly.utils.object', 'Blockly.utils.KeyCodes', 'Blockly.utils.style']);
goog.addDependency("../../core/components/tree/treecontrol.js", ['Blockly.tree.TreeControl'], ['Blockly.tree.TreeNode', 'Blockly.tree.BaseNode', 'Blockly.utils.aria', 'Blockly.utils.object', 'Blockly.utils.style']);
goog.addDependency("../../core/components/tree/treenode.js", ['Blockly.tree.TreeNode'], ['Blockly.tree.BaseNode', 'Blockly.utils.object', 'Blockly.utils.KeyCodes']);
goog.addDependency("../../core/components/tree/basenode.js", ['Blockly.tree.BaseNode'], ['Blockly.Component', 'Blockly.utils.KeyCodes', 'Blockly.utils.aria', 'Blockly.utils.object', 'Blockly.utils.style']);
goog.addDependency("../../core/components/tree/treecontrol.js", ['Blockly.tree.TreeControl'], ['Blockly.tree.BaseNode', 'Blockly.tree.TreeNode', 'Blockly.utils.aria', 'Blockly.utils.object', 'Blockly.utils.style']);
goog.addDependency("../../core/components/tree/treenode.js", ['Blockly.tree.TreeNode'], ['Blockly.tree.BaseNode', 'Blockly.utils.KeyCodes', 'Blockly.utils.object']);
goog.addDependency("../../core/connection_db.js", ['Blockly.ConnectionDB'], ['Blockly.RenderedConnection']);
goog.addDependency("../../core/connection.js", ['Blockly.Connection'], ['Blockly.Events', 'Blockly.Events.BlockMove', 'Blockly.Xml']);
goog.addDependency("../../core/connection_db.js", ['Blockly.ConnectionDB'], ['Blockly.Connection']);
goog.addDependency("../../core/constants.js", ['Blockly.constants'], []);
goog.addDependency("../../core/contextmenu.js", ['Blockly.ContextMenu'], ['Blockly.Events', 'Blockly.Events.BlockCreate', 'Blockly.Menu', 'Blockly.MenuItem', 'Blockly.Msg', 'Blockly.utils', 'Blockly.utils.Coordinate', 'Blockly.utils.dom', 'Blockly.utils.uiMenu', 'Blockly.utils.userAgent', 'Blockly.Xml']);
goog.addDependency("../../core/contextmenu.js", ['Blockly.ContextMenu'], ['Blockly.Events', 'Blockly.Events.BlockCreate', 'Blockly.Menu', 'Blockly.MenuItem', 'Blockly.Msg', 'Blockly.Xml', 'Blockly.utils', 'Blockly.utils.Coordinate', 'Blockly.utils.dom', 'Blockly.utils.uiMenu', 'Blockly.utils.userAgent']);
goog.addDependency("../../core/css.js", ['Blockly.Css'], []);
goog.addDependency("../../core/dropdowndiv.js", ['Blockly.DropDownDiv'], ['Blockly.utils.dom', 'Blockly.utils.math', 'Blockly.utils.style']);
goog.addDependency("../../core/events.js", ['Blockly.Events'], ['Blockly.utils']);
goog.addDependency("../../core/events_abstract.js", ['Blockly.Events.Abstract'], ['Blockly.Events']);
goog.addDependency("../../core/events.js", ['Blockly.Events'], ['Blockly.utils']);
goog.addDependency("../../core/extensions.js", ['Blockly.Extensions'], ['Blockly.utils']);
goog.addDependency("../../core/field.js", ['Blockly.Field'], ['Blockly.Events', 'Blockly.Events.BlockChange', 'Blockly.Gesture', 'Blockly.utils', 'Blockly.utils.dom', 'Blockly.utils.Size', 'Blockly.utils.userAgent', 'Blockly.utils.style']);
goog.addDependency("../../core/field_angle.js", ['Blockly.FieldAngle'], ['Blockly.Css', 'Blockly.DropDownDiv', 'Blockly.fieldRegistry', 'Blockly.FieldTextInput', 'Blockly.utils.dom', 'Blockly.utils.math', 'Blockly.utils.object', 'Blockly.utils.userAgent']);
goog.addDependency("../../core/field_checkbox.js", ['Blockly.FieldCheckbox'], ['Blockly.Events', 'Blockly.Events.BlockChange', 'Blockly.Field', 'Blockly.fieldRegistry', 'Blockly.utils.dom', 'Blockly.utils.object', 'Blockly.utils.Size']);
goog.addDependency("../../core/field_colour.js", ['Blockly.FieldColour'], ['Blockly.Css', 'Blockly.DropDownDiv', 'Blockly.Events', 'Blockly.Events.BlockChange', 'Blockly.Field', 'Blockly.fieldRegistry', 'Blockly.navigation', 'Blockly.utils.aria', 'Blockly.utils.colour', 'Blockly.utils.dom', 'Blockly.utils.IdGenerator', 'Blockly.utils.KeyCodes', 'Blockly.utils.object', 'Blockly.utils.Size']);
goog.addDependency("../../core/field_angle.js", ['Blockly.FieldAngle'], ['Blockly.Css', 'Blockly.DropDownDiv', 'Blockly.FieldTextInput', 'Blockly.fieldRegistry', 'Blockly.utils.dom', 'Blockly.utils.math', 'Blockly.utils.object', 'Blockly.utils.userAgent']);
goog.addDependency("../../core/field_checkbox.js", ['Blockly.FieldCheckbox'], ['Blockly.Events', 'Blockly.Events.BlockChange', 'Blockly.Field', 'Blockly.fieldRegistry', 'Blockly.utils.Size', 'Blockly.utils.dom', 'Blockly.utils.object']);
goog.addDependency("../../core/field_colour.js", ['Blockly.FieldColour'], ['Blockly.Css', 'Blockly.DropDownDiv', 'Blockly.Events', 'Blockly.Events.BlockChange', 'Blockly.Field', 'Blockly.fieldRegistry', 'Blockly.navigation', 'Blockly.utils.IdGenerator', 'Blockly.utils.KeyCodes', 'Blockly.utils.Size', 'Blockly.utils.aria', 'Blockly.utils.colour', 'Blockly.utils.dom', 'Blockly.utils.object']);
goog.addDependency("../../core/field_date.js", ['Blockly.FieldDate'], ['Blockly.Css', 'Blockly.Events', 'Blockly.Field', 'Blockly.fieldRegistry', 'Blockly.utils.dom', 'Blockly.utils.object', 'Blockly.utils.string', 'goog.date', 'goog.date.DateTime', 'goog.events', 'goog.i18n.DateTimeSymbols', 'goog.i18n.DateTimeSymbols_he', 'goog.ui.DatePicker']);
goog.addDependency("../../core/field_dropdown.js", ['Blockly.FieldDropdown'], ['Blockly.Events', 'Blockly.Events.BlockChange', 'Blockly.Field', 'Blockly.fieldRegistry', 'Blockly.Menu', 'Blockly.MenuItem', 'Blockly.navigation', 'Blockly.utils', 'Blockly.utils.dom', 'Blockly.utils.object', 'Blockly.utils.Size', 'Blockly.utils.string', 'Blockly.utils.userAgent']);
goog.addDependency("../../core/field_image.js", ['Blockly.FieldImage'], ['Blockly.Field', 'Blockly.fieldRegistry', 'Blockly.utils', 'Blockly.utils.dom', 'Blockly.utils.object', 'Blockly.utils.Size']);
goog.addDependency("../../core/field_label.js", ['Blockly.FieldLabel'], ['Blockly.Field', 'Blockly.fieldRegistry', 'Blockly.utils', 'Blockly.utils.dom', 'Blockly.utils.object', 'Blockly.utils.Size']);
goog.addDependency("../../core/field_dropdown.js", ['Blockly.FieldDropdown'], ['Blockly.Events', 'Blockly.Events.BlockChange', 'Blockly.Field', 'Blockly.Menu', 'Blockly.MenuItem', 'Blockly.fieldRegistry', 'Blockly.navigation', 'Blockly.utils', 'Blockly.utils.Coordinate', 'Blockly.utils.Size', 'Blockly.utils.aria', 'Blockly.utils.dom', 'Blockly.utils.object', 'Blockly.utils.string', 'Blockly.utils.userAgent']);
goog.addDependency("../../core/field_image.js", ['Blockly.FieldImage'], ['Blockly.Field', 'Blockly.fieldRegistry', 'Blockly.utils', 'Blockly.utils.Size', 'Blockly.utils.dom', 'Blockly.utils.object']);
goog.addDependency("../../core/field_label_serializable.js", ['Blockly.FieldLabelSerializable'], ['Blockly.FieldLabel', 'Blockly.fieldRegistry', 'Blockly.utils', 'Blockly.utils.object']);
goog.addDependency("../../core/field_multilineinput.js", ['Blockly.FieldMultilineInput'], ['Blockly.Css', 'Blockly.DropDownDiv', 'Blockly.FieldTextInput', 'Blockly.utils', 'Blockly.utils.Coordinate', 'Blockly.utils.dom', 'Blockly.utils.KeyCodes', 'Blockly.utils.object', 'Blockly.utils.userAgent']);
goog.addDependency("../../core/field_number.js", ['Blockly.FieldNumber'], ['Blockly.fieldRegistry', 'Blockly.FieldTextInput', 'Blockly.utils.object']);
goog.addDependency("../../core/field_label.js", ['Blockly.FieldLabel'], ['Blockly.Field', 'Blockly.fieldRegistry', 'Blockly.utils', 'Blockly.utils.Size', 'Blockly.utils.dom', 'Blockly.utils.object']);
goog.addDependency("../../core/field_multilineinput.js", ['Blockly.FieldMultilineInput'], ['Blockly.Css', 'Blockly.DropDownDiv', 'Blockly.FieldTextInput', 'Blockly.utils', 'Blockly.utils.Coordinate', 'Blockly.utils.KeyCodes', 'Blockly.utils.aria', 'Blockly.utils.dom', 'Blockly.utils.object', 'Blockly.utils.userAgent']);
goog.addDependency("../../core/field_number.js", ['Blockly.FieldNumber'], ['Blockly.FieldTextInput', 'Blockly.fieldRegistry', 'Blockly.utils.aria', 'Blockly.utils.object']);
goog.addDependency("../../core/field_registry.js", ['Blockly.fieldRegistry'], []);
goog.addDependency("../../core/field_textinput.js", ['Blockly.FieldTextInput'], ['Blockly.Events', 'Blockly.Events.BlockChange', 'Blockly.Field', 'Blockly.fieldRegistry', 'Blockly.Msg', 'Blockly.utils', 'Blockly.utils.aria', 'Blockly.utils.Coordinate', 'Blockly.utils.dom', 'Blockly.utils.KeyCodes', 'Blockly.utils.object', 'Blockly.utils.Size', 'Blockly.utils.userAgent']);
goog.addDependency("../../core/field_variable.js", ['Blockly.FieldVariable'], ['Blockly.Events', 'Blockly.Events.BlockChange', 'Blockly.FieldDropdown', 'Blockly.fieldRegistry', 'Blockly.Msg', 'Blockly.utils', 'Blockly.utils.object', 'Blockly.utils.Size', 'Blockly.VariableModel', 'Blockly.Variables', 'Blockly.Xml']);
goog.addDependency("../../core/flyout_base.js", ['Blockly.Flyout'], ['Blockly.Block', 'Blockly.blockRendering', 'Blockly.Events', 'Blockly.Events.BlockCreate', 'Blockly.Events.VarCreate', 'Blockly.FlyoutCursor', 'Blockly.Gesture', 'Blockly.MarkerCursor', 'Blockly.Scrollbar', 'Blockly.Tooltip', 'Blockly.Touch', 'Blockly.utils', 'Blockly.utils.Coordinate', 'Blockly.utils.dom', 'Blockly.WorkspaceSvg', 'Blockly.Xml']);
goog.addDependency("../../core/field_textinput.js", ['Blockly.FieldTextInput'], ['Blockly.Events', 'Blockly.Events.BlockChange', 'Blockly.Field', 'Blockly.Msg', 'Blockly.fieldRegistry', 'Blockly.utils', 'Blockly.utils.Coordinate', 'Blockly.utils.KeyCodes', 'Blockly.utils.Size', 'Blockly.utils.aria', 'Blockly.utils.dom', 'Blockly.utils.object', 'Blockly.utils.userAgent']);
goog.addDependency("../../core/field_variable.js", ['Blockly.FieldVariable'], ['Blockly.Events', 'Blockly.Events.BlockChange', 'Blockly.FieldDropdown', 'Blockly.Msg', 'Blockly.VariableModel', 'Blockly.Variables', 'Blockly.Xml', 'Blockly.fieldRegistry', 'Blockly.utils', 'Blockly.utils.Size', 'Blockly.utils.object']);
goog.addDependency("../../core/field.js", ['Blockly.Field'], ['Blockly.Events', 'Blockly.Events.BlockChange', 'Blockly.Gesture', 'Blockly.utils', 'Blockly.utils.Size', 'Blockly.utils.dom', 'Blockly.utils.style', 'Blockly.utils.userAgent']);
goog.addDependency("../../core/flyout_base.js", ['Blockly.Flyout'], ['Blockly.Block', 'Blockly.Events', 'Blockly.Events.BlockCreate', 'Blockly.Events.VarCreate', 'Blockly.FlyoutCursor', 'Blockly.Gesture', 'Blockly.Marker', 'Blockly.Scrollbar', 'Blockly.Tooltip', 'Blockly.Touch', 'Blockly.WorkspaceSvg', 'Blockly.Xml', 'Blockly.blockRendering', 'Blockly.utils', 'Blockly.utils.Coordinate', 'Blockly.utils.dom']);
goog.addDependency("../../core/flyout_button.js", ['Blockly.FlyoutButton'], ['Blockly.Css', 'Blockly.utils', 'Blockly.utils.Coordinate', 'Blockly.utils.dom']);
goog.addDependency("../../core/flyout_dragger.js", ['Blockly.FlyoutDragger'], ['Blockly.utils.object', 'Blockly.WorkspaceDragger']);
goog.addDependency("../../core/flyout_horizontal.js", ['Blockly.HorizontalFlyout'], ['Blockly.Block', 'Blockly.Flyout', 'Blockly.Scrollbar', 'Blockly.utils', 'Blockly.utils.object', 'Blockly.utils.Rect', 'Blockly.WidgetDiv']);
goog.addDependency("../../core/flyout_vertical.js", ['Blockly.VerticalFlyout'], ['Blockly.Block', 'Blockly.Flyout', 'Blockly.Scrollbar', 'Blockly.utils', 'Blockly.utils.object', 'Blockly.utils.Rect', 'Blockly.utils.userAgent', 'Blockly.WidgetDiv']);
goog.addDependency("../../core/flyout_dragger.js", ['Blockly.FlyoutDragger'], ['Blockly.WorkspaceDragger', 'Blockly.utils.object']);
goog.addDependency("../../core/flyout_horizontal.js", ['Blockly.HorizontalFlyout'], ['Blockly.Block', 'Blockly.Flyout', 'Blockly.Scrollbar', 'Blockly.WidgetDiv', 'Blockly.utils', 'Blockly.utils.Rect', 'Blockly.utils.object']);
goog.addDependency("../../core/flyout_vertical.js", ['Blockly.VerticalFlyout'], ['Blockly.Block', 'Blockly.Flyout', 'Blockly.Scrollbar', 'Blockly.WidgetDiv', 'Blockly.utils', 'Blockly.utils.Rect', 'Blockly.utils.object', 'Blockly.utils.userAgent']);
goog.addDependency("../../core/generator.js", ['Blockly.Generator'], ['Blockly.Block']);
goog.addDependency("../../core/gesture.js", ['Blockly.Gesture'], ['Blockly.blockAnimations', 'Blockly.BlockDragger', 'Blockly.BubbleDragger', 'Blockly.constants', 'Blockly.Events', 'Blockly.Events.Ui', 'Blockly.FlyoutDragger', 'Blockly.Tooltip', 'Blockly.Touch', 'Blockly.utils', 'Blockly.utils.Coordinate', 'Blockly.WorkspaceDragger']);
goog.addDependency("../../core/gesture.js", ['Blockly.Gesture'], ['Blockly.ASTNode', 'Blockly.BlockDragger', 'Blockly.BubbleDragger', 'Blockly.Events', 'Blockly.Events.Ui', 'Blockly.FlyoutDragger', 'Blockly.Tooltip', 'Blockly.Touch', 'Blockly.WorkspaceDragger', 'Blockly.blockAnimations', 'Blockly.constants', 'Blockly.navigation', 'Blockly.utils', 'Blockly.utils.Coordinate']);
goog.addDependency("../../core/grid.js", ['Blockly.Grid'], ['Blockly.utils.dom', 'Blockly.utils.userAgent']);
goog.addDependency("../../core/icon.js", ['Blockly.Icon'], ['Blockly.utils', 'Blockly.utils.Coordinate', 'Blockly.utils.dom', 'Blockly.utils.Size']);
goog.addDependency("../../core/inject.js", ['Blockly.inject'], ['Blockly.BlockDragSurfaceSvg', 'Blockly.Component', 'Blockly.Css', 'Blockly.DropDownDiv', 'Blockly.Events', 'Blockly.Grid', 'Blockly.Options', 'Blockly.ScrollbarPair', 'Blockly.Tooltip', 'Blockly.utils', 'Blockly.utils.dom', 'Blockly.utils.userAgent', 'Blockly.WorkspaceDragSurfaceSvg', 'Blockly.WorkspaceSvg']);
goog.addDependency("../../core/icon.js", ['Blockly.Icon'], ['Blockly.utils', 'Blockly.utils.Coordinate', 'Blockly.utils.Size', 'Blockly.utils.dom']);
goog.addDependency("../../core/inject.js", ['Blockly.inject'], ['Blockly.BlockDragSurfaceSvg', 'Blockly.Component', 'Blockly.Css', 'Blockly.DropDownDiv', 'Blockly.Events', 'Blockly.Grid', 'Blockly.Msg', 'Blockly.Options', 'Blockly.ScrollbarPair', 'Blockly.Tooltip', 'Blockly.WorkspaceDragSurfaceSvg', 'Blockly.WorkspaceSvg', 'Blockly.user.keyMap', 'Blockly.utils', 'Blockly.utils.dom', 'Blockly.utils.userAgent']);
goog.addDependency("../../core/input.js", ['Blockly.Input'], ['Blockly.Connection', 'Blockly.FieldLabel']);
goog.addDependency("../../core/insertion_marker_manager.js", ['Blockly.InsertionMarkerManager'], ['Blockly.blockAnimations', 'Blockly.Events']);
goog.addDependency("../../core/insertion_marker_manager.js", ['Blockly.InsertionMarkerManager'], ['Blockly.Events', 'Blockly.blockAnimations']);
goog.addDependency("../../core/keyboard_nav/action.js", ['Blockly.Action'], []);
goog.addDependency("../../core/keyboard_nav/ast_node.js", ['Blockly.ASTNode'], []);
goog.addDependency("../../core/keyboard_nav/cursor.js", ['Blockly.Cursor'], []);
goog.addDependency("../../core/keyboard_nav/cursor_svg.js", ['Blockly.CursorSvg'], ['Blockly.Cursor', 'Blockly.utils.object']);
goog.addDependency("../../core/keyboard_nav/flyout_cursor.js", ['Blockly.FlyoutCursor'], ['Blockly.Cursor', 'Blockly.utils.object']);
goog.addDependency("../../core/keyboard_nav/ast_node.js", ['Blockly.ASTNode'], ['Blockly.utils.Coordinate']);
goog.addDependency("../../core/keyboard_nav/basic_cursor.js", ['Blockly.BasicCursor'], ['Blockly.ASTNode', 'Blockly.Cursor']);
goog.addDependency("../../core/keyboard_nav/cursor.js", ['Blockly.Cursor'], ['Blockly.ASTNode', 'Blockly.Action', 'Blockly.Marker', 'Blockly.navigation', 'Blockly.utils.object']);
goog.addDependency("../../core/keyboard_nav/flyout_cursor.js", ['Blockly.FlyoutCursor'], ['Blockly.Cursor', 'Blockly.navigation', 'Blockly.utils.object']);
goog.addDependency("../../core/keyboard_nav/key_map.js", ['Blockly.user.keyMap'], ['Blockly.utils.KeyCodes', 'Blockly.utils.object']);
goog.addDependency("../../core/keyboard_nav/marker_cursor.js", ['Blockly.MarkerCursor'], ['Blockly.Cursor', 'Blockly.utils.object']);
goog.addDependency("../../core/keyboard_nav/navigation.js", ['Blockly.navigation'], ['Blockly.Action', 'Blockly.ASTNode', 'Blockly.utils.Coordinate', 'Blockly.user.keyMap']);
goog.addDependency("../../core/keyboard_nav/marker.js", ['Blockly.Marker'], ['Blockly.ASTNode', 'Blockly.navigation']);
goog.addDependency("../../core/keyboard_nav/navigation.js", ['Blockly.navigation'], ['Blockly.ASTNode', 'Blockly.Action', 'Blockly.user.keyMap', 'Blockly.utils.Coordinate']);
goog.addDependency("../../core/keyboard_nav/tab_navigate_cursor.js", ['Blockly.TabNavigateCursor'], ['Blockly.ASTNode', 'Blockly.BasicCursor', 'Blockly.utils.object']);
goog.addDependency("../../core/marker_manager.js", ['Blockly.MarkerManager'], ['Blockly.Cursor', 'Blockly.Marker']);
goog.addDependency("../../core/msg.js", ['Blockly.Msg'], ['Blockly.utils.global']);
goog.addDependency("../../core/mutator.js", ['Blockly.Mutator'], ['Blockly.Bubble', 'Blockly.Events', 'Blockly.Events.BlockChange', 'Blockly.Events.Ui', 'Blockly.Icon', 'Blockly.utils', 'Blockly.utils.dom', 'Blockly.utils.global', 'Blockly.utils.object', 'Blockly.utils.xml', 'Blockly.WorkspaceSvg', 'Blockly.Xml']);
goog.addDependency("../../core/mutator.js", ['Blockly.Mutator'], ['Blockly.Bubble', 'Blockly.Events', 'Blockly.Events.BlockChange', 'Blockly.Events.Ui', 'Blockly.Icon', 'Blockly.WorkspaceSvg', 'Blockly.Xml', 'Blockly.navigation', 'Blockly.utils', 'Blockly.utils.dom', 'Blockly.utils.global', 'Blockly.utils.object', 'Blockly.utils.xml']);
goog.addDependency("../../core/names.js", ['Blockly.Names'], ['Blockly.Msg']);
goog.addDependency("../../core/options.js", ['Blockly.Options'], ['Blockly.utils.userAgent', 'Blockly.Xml']);
goog.addDependency("../../core/procedures.js", ['Blockly.Procedures'], ['Blockly.Blocks', 'Blockly.constants', 'Blockly.Events', 'Blockly.Events.BlockChange', 'Blockly.Field', 'Blockly.Msg', 'Blockly.Names', 'Blockly.utils.xml', 'Blockly.Workspace', 'Blockly.Xml']);
goog.addDependency("../../core/options.js", ['Blockly.Options'], ['Blockly.Theme', 'Blockly.Themes.Classic', 'Blockly.Xml', 'Blockly.user.keyMap', 'Blockly.utils.userAgent']);
goog.addDependency("../../core/procedures.js", ['Blockly.Procedures'], ['Blockly.Blocks', 'Blockly.Events', 'Blockly.Events.BlockChange', 'Blockly.Field', 'Blockly.Msg', 'Blockly.Names', 'Blockly.Workspace', 'Blockly.Xml', 'Blockly.constants', 'Blockly.utils.xml']);
goog.addDependency("../../core/rendered_connection.js", ['Blockly.RenderedConnection'], ['Blockly.Connection', 'Blockly.Events', 'Blockly.utils', 'Blockly.utils.Coordinate', 'Blockly.utils.dom', 'Blockly.utils.object']);
goog.addDependency("../../core/renderers/common/block_rendering.js", ['Blockly.blockRendering'], ['Blockly.utils.object']);
goog.addDependency("../../core/renderers/common/constants.js", ['Blockly.blockRendering.ConstantProvider'], ['Blockly.utils.svgPaths']);
goog.addDependency("../../core/renderers/common/constants.js", ['Blockly.blockRendering.ConstantProvider'], ['Blockly.utils', 'Blockly.utils.colour', 'Blockly.utils.dom', 'Blockly.utils.svgPaths', 'Blockly.utils.userAgent']);
goog.addDependency("../../core/renderers/common/debugger.js", ['Blockly.blockRendering.Debug'], ['Blockly.blockRendering.BottomRow', 'Blockly.blockRendering.InputRow', 'Blockly.blockRendering.Measurable', 'Blockly.blockRendering.RenderInfo', 'Blockly.blockRendering.Row', 'Blockly.blockRendering.SpacerRow', 'Blockly.blockRendering.TopRow', 'Blockly.blockRendering.Types']);
goog.addDependency("../../core/renderers/common/drawer.js", ['Blockly.blockRendering.Drawer'], ['Blockly.blockRendering.BottomRow', 'Blockly.blockRendering.InputRow', 'Blockly.blockRendering.Measurable', 'Blockly.blockRendering.RenderInfo', 'Blockly.blockRendering.Row', 'Blockly.blockRendering.SpacerRow', 'Blockly.blockRendering.TopRow', 'Blockly.blockRendering.Types', 'Blockly.utils.svgPaths']);
goog.addDependency("../../core/renderers/common/info.js", ['Blockly.blockRendering.RenderInfo'], ['Blockly.blockRendering.BottomRow', 'Blockly.blockRendering.ExternalValueInput', 'Blockly.blockRendering.Hat', 'Blockly.blockRendering.InlineInput', 'Blockly.blockRendering.InRowSpacer', 'Blockly.blockRendering.InputRow', 'Blockly.blockRendering.Measurable', 'Blockly.blockRendering.NextConnection', 'Blockly.blockRendering.OutputConnection', 'Blockly.blockRendering.PreviousConnection', 'Blockly.blockRendering.RoundCorner', 'Blockly.blockRendering.Row', 'Blockly.blockRendering.SpacerRow', 'Blockly.blockRendering.StatementInput', 'Blockly.blockRendering.SquareCorner', 'Blockly.blockRendering.TopRow', 'Blockly.blockRendering.Types']);
goog.addDependency("../../core/renderers/common/path_object.js", ['Blockly.blockRendering.IPathObject', 'Blockly.blockRendering.PathObject'], ['Blockly.utils.dom']);
goog.addDependency("../../core/renderers/common/renderer.js", ['Blockly.blockRendering.Renderer'], ['Blockly.blockRendering.ConstantProvider', 'Blockly.blockRendering.Drawer', 'Blockly.blockRendering.IPathObject', 'Blockly.blockRendering.PathObject', 'Blockly.blockRendering.RenderInfo', 'Blockly.CursorSvg']);
goog.addDependency("../../core/renderers/common/i_path_object.js", ['Blockly.blockRendering.IPathObject'], []);
goog.addDependency("../../core/renderers/common/info.js", ['Blockly.blockRendering.RenderInfo'], ['Blockly.blockRendering.BottomRow', 'Blockly.blockRendering.ExternalValueInput', 'Blockly.blockRendering.Hat', 'Blockly.blockRendering.InRowSpacer', 'Blockly.blockRendering.InlineInput', 'Blockly.blockRendering.InputRow', 'Blockly.blockRendering.Measurable', 'Blockly.blockRendering.NextConnection', 'Blockly.blockRendering.OutputConnection', 'Blockly.blockRendering.PreviousConnection', 'Blockly.blockRendering.RoundCorner', 'Blockly.blockRendering.Row', 'Blockly.blockRendering.SpacerRow', 'Blockly.blockRendering.SquareCorner', 'Blockly.blockRendering.StatementInput', 'Blockly.blockRendering.TopRow', 'Blockly.blockRendering.Types']);
goog.addDependency("../../core/renderers/common/marker_svg.js", ['Blockly.blockRendering.MarkerSvg'], ['Blockly.ASTNode']);
goog.addDependency("../../core/renderers/common/path_object.js", ['Blockly.blockRendering.PathObject'], ['Blockly.Theme', 'Blockly.blockRendering.ConstantProvider', 'Blockly.blockRendering.IPathObject', 'Blockly.utils.dom']);
goog.addDependency("../../core/renderers/common/renderer.js", ['Blockly.blockRendering.Renderer'], ['Blockly.blockRendering.ConstantProvider', 'Blockly.blockRendering.Drawer', 'Blockly.blockRendering.IPathObject', 'Blockly.blockRendering.MarkerSvg', 'Blockly.blockRendering.PathObject', 'Blockly.blockRendering.RenderInfo']);
goog.addDependency("../../core/renderers/geras/constants.js", ['Blockly.geras.ConstantProvider'], ['Blockly.blockRendering.ConstantProvider', 'Blockly.utils.object']);
goog.addDependency("../../core/renderers/geras/drawer.js", ['Blockly.geras.Drawer'], ['Blockly.blockRendering.ConstantProvider', 'Blockly.blockRendering.Drawer', 'Blockly.geras.Highlighter', 'Blockly.geras.PathObject', 'Blockly.geras.RenderInfo', 'Blockly.utils.object', 'Blockly.utils.svgPaths']);
goog.addDependency("../../core/renderers/geras/drawer.js", ['Blockly.geras.Drawer'], ['Blockly.blockRendering.ConstantProvider', 'Blockly.blockRendering.Drawer', 'Blockly.geras.Highlighter', 'Blockly.geras.RenderInfo', 'Blockly.utils.object', 'Blockly.utils.svgPaths']);
goog.addDependency("../../core/renderers/geras/highlight_constants.js", ['Blockly.geras.HighlightConstantProvider'], ['Blockly.blockRendering.ConstantProvider', 'Blockly.utils.svgPaths']);
goog.addDependency("../../core/renderers/geras/highlighter.js", ['Blockly.geras.Highlighter'], ['Blockly.blockRendering.BottomRow', 'Blockly.blockRendering.InputRow', 'Blockly.blockRendering.Measurable', 'Blockly.blockRendering.RenderInfo', 'Blockly.blockRendering.Row', 'Blockly.blockRendering.SpacerRow', 'Blockly.blockRendering.TopRow', 'Blockly.blockRendering.Types', 'Blockly.utils.svgPaths']);
goog.addDependency("../../core/renderers/geras/info.js", ['Blockly.geras', 'Blockly.geras.RenderInfo'], ['Blockly.blockRendering.BottomRow', 'Blockly.blockRendering.InputRow', 'Blockly.blockRendering.Measurable', 'Blockly.blockRendering.NextConnection', 'Blockly.blockRendering.OutputConnection', 'Blockly.blockRendering.PreviousConnection', 'Blockly.blockRendering.RenderInfo', 'Blockly.blockRendering.BottomRow', 'Blockly.blockRendering.InputRow', 'Blockly.blockRendering.Measurable', 'Blockly.blockRendering.NextConnection', 'Blockly.blockRendering.OutputConnection', 'Blockly.blockRendering.PreviousConnection', 'Blockly.blockRendering.Types', 'Blockly.blockRendering.ExternalValueInput', 'Blockly.geras.InlineInput', 'Blockly.geras.StatementInput', 'Blockly.utils.object']);
goog.addDependency("../../core/renderers/geras/measurables/inputs.js", ['Blockly.geras.InlineInput', 'Blockly.geras.StatementInput'], ['Blockly.blockRendering.Connection', 'Blockly.utils.object']);
goog.addDependency("../../core/renderers/geras/path_object.js", ['Blockly.geras.PathObject'], ['Blockly.blockRendering.IPathObject', 'Blockly.utils.dom']);
goog.addDependency("../../core/renderers/geras/info.js", ['Blockly.geras', 'Blockly.geras.RenderInfo'], ['Blockly.blockRendering.BottomRow', 'Blockly.blockRendering.BottomRow', 'Blockly.blockRendering.ExternalValueInput', 'Blockly.blockRendering.InputRow', 'Blockly.blockRendering.InputRow', 'Blockly.blockRendering.Measurable', 'Blockly.blockRendering.Measurable', 'Blockly.blockRendering.NextConnection', 'Blockly.blockRendering.NextConnection', 'Blockly.blockRendering.OutputConnection', 'Blockly.blockRendering.OutputConnection', 'Blockly.blockRendering.PreviousConnection', 'Blockly.blockRendering.PreviousConnection', 'Blockly.blockRendering.RenderInfo', 'Blockly.blockRendering.Types', 'Blockly.geras.InlineInput', 'Blockly.geras.StatementInput', 'Blockly.utils.object']);
goog.addDependency("../../core/renderers/geras/measurables/inputs.js", ['Blockly.geras.InlineInput', 'Blockly.geras.StatementInput'], ['Blockly.utils.object']);
goog.addDependency("../../core/renderers/geras/path_object.js", ['Blockly.geras.PathObject'], ['Blockly.Theme', 'Blockly.blockRendering.PathObject', 'Blockly.geras.ConstantProvider', 'Blockly.utils.dom', 'Blockly.utils.object']);
goog.addDependency("../../core/renderers/geras/renderer.js", ['Blockly.geras.Renderer'], ['Blockly.blockRendering', 'Blockly.blockRendering.Renderer', 'Blockly.geras.ConstantProvider', 'Blockly.geras.Drawer', 'Blockly.geras.HighlightConstantProvider', 'Blockly.geras.PathObject', 'Blockly.geras.RenderInfo', 'Blockly.utils.object']);
goog.addDependency("../../core/renderers/measurables/base.js", ['Blockly.blockRendering.Measurable'], ['Blockly.blockRendering.Types']);
goog.addDependency("../../core/renderers/measurables/connections.js", ['Blockly.blockRendering.Connection', 'Blockly.blockRendering.NextConnection', 'Blockly.blockRendering.OutputConnection', 'Blockly.blockRendering.PreviousConnection'], ['Blockly.blockRendering.Measurable', 'Blockly.blockRendering.Types', 'Blockly.utils.object']);
goog.addDependency("../../core/renderers/measurables/inputs.js", ['Blockly.blockRendering.ExternalValueInput', 'Blockly.blockRendering.InlineInput', 'Blockly.blockRendering.InputConnection', 'Blockly.blockRendering.StatementInput'], ['Blockly.blockRendering.Connection', 'Blockly.blockRendering.Measurable', 'Blockly.blockRendering.Types', 'Blockly.utils.object']);
goog.addDependency("../../core/renderers/measurables/row_elements.js", ['Blockly.blockRendering.Field', 'Blockly.blockRendering.Hat', 'Blockly.blockRendering.Icon', 'Blockly.blockRendering.InRowSpacer', 'Blockly.blockRendering.JaggedEdge', 'Blockly.blockRendering.RoundCorner', 'Blockly.blockRendering.SquareCorner'], ['Blockly.blockRendering.Measurable', 'Blockly.blockRendering.Types', 'Blockly.utils.object']);
goog.addDependency("../../core/renderers/measurables/rows.js", ['Blockly.blockRendering.BottomRow', 'Blockly.blockRendering.InputRow', 'Blockly.blockRendering.Row', 'Blockly.blockRendering.SpacerRow', 'Blockly.blockRendering.TopRow'], ['Blockly.blockRendering.InputConnection', 'Blockly.blockRendering.InRowSpacer', 'Blockly.blockRendering.Measurable', 'Blockly.blockRendering.NextConnection', 'Blockly.blockRendering.PreviousConnection', 'Blockly.blockRendering.Types', 'Blockly.utils.object']);
goog.addDependency("../../core/renderers/measurables/rows.js", ['Blockly.blockRendering.BottomRow', 'Blockly.blockRendering.InputRow', 'Blockly.blockRendering.Row', 'Blockly.blockRendering.SpacerRow', 'Blockly.blockRendering.TopRow'], ['Blockly.blockRendering.InRowSpacer', 'Blockly.blockRendering.InputConnection', 'Blockly.blockRendering.Measurable', 'Blockly.blockRendering.NextConnection', 'Blockly.blockRendering.PreviousConnection', 'Blockly.blockRendering.Types', 'Blockly.utils.object']);
goog.addDependency("../../core/renderers/measurables/types.js", ['Blockly.blockRendering.Types'], []);
goog.addDependency("../../core/renderers/minimalist/constants.js", ['Blockly.minimalist.ConstantProvider'], ['Blockly.blockRendering.ConstantProvider', 'Blockly.utils.object']);
goog.addDependency("../../core/renderers/minimalist/drawer.js", ['Blockly.minimalist.Drawer'], ['Blockly.blockRendering.Drawer', 'Blockly.utils.object', 'Blockly.minimalist.RenderInfo']);
goog.addDependency("../../core/renderers/minimalist/drawer.js", ['Blockly.minimalist.Drawer'], ['Blockly.blockRendering.Drawer', 'Blockly.minimalist.RenderInfo', 'Blockly.utils.object']);
goog.addDependency("../../core/renderers/minimalist/info.js", ['Blockly.minimalist', 'Blockly.minimalist.RenderInfo'], ['Blockly.utils.object']);
goog.addDependency("../../core/renderers/minimalist/renderer.js", ['Blockly.minimalist.Renderer'], ['Blockly.blockRendering', 'Blockly.blockRendering.Renderer', 'Blockly.utils.object', 'Blockly.minimalist.ConstantProvider', 'Blockly.minimalist.Drawer', 'Blockly.minimalist.RenderInfo']);
goog.addDependency("../../core/renderers/minimalist/renderer.js", ['Blockly.minimalist.Renderer'], ['Blockly.blockRendering', 'Blockly.blockRendering.Renderer', 'Blockly.minimalist.ConstantProvider', 'Blockly.minimalist.Drawer', 'Blockly.minimalist.RenderInfo', 'Blockly.utils.object']);
goog.addDependency("../../core/renderers/thrasos/info.js", ['Blockly.thrasos', 'Blockly.thrasos.RenderInfo'], ['Blockly.blockRendering.BottomRow', 'Blockly.blockRendering.ExternalValueInput', 'Blockly.blockRendering.InlineInput', 'Blockly.blockRendering.InputRow', 'Blockly.blockRendering.Measurable', 'Blockly.blockRendering.NextConnection', 'Blockly.blockRendering.OutputConnection', 'Blockly.blockRendering.PreviousConnection', 'Blockly.blockRendering.RenderInfo', 'Blockly.blockRendering.Row', 'Blockly.blockRendering.SpacerRow', 'Blockly.blockRendering.StatementInput', 'Blockly.blockRendering.TopRow', 'Blockly.blockRendering.Types', 'Blockly.utils.object']);
goog.addDependency("../../core/renderers/thrasos/renderer.js", ['Blockly.thrasos.Renderer'], ['Blockly.blockRendering', 'Blockly.blockRendering.Renderer', 'Blockly.thrasos.RenderInfo', 'Blockly.utils.object']);
goog.addDependency("../../core/renderers/zelos/constants.js", ['Blockly.zelos.ConstantProvider'], ['Blockly.blockRendering.ConstantProvider', 'Blockly.utils.object', 'Blockly.utils.svgPaths']);
goog.addDependency("../../core/renderers/zelos/constants.js", ['Blockly.zelos.ConstantProvider'], ['Blockly.blockRendering.ConstantProvider', 'Blockly.utils.dom', 'Blockly.utils.object', 'Blockly.utils.svgPaths']);
goog.addDependency("../../core/renderers/zelos/drawer.js", ['Blockly.zelos.Drawer'], ['Blockly.blockRendering.ConstantProvider', 'Blockly.blockRendering.Drawer', 'Blockly.blockRendering.Types', 'Blockly.utils.object', 'Blockly.zelos.RenderInfo']);
goog.addDependency("../../core/renderers/zelos/info.js", ['Blockly.zelos', 'Blockly.zelos.RenderInfo'], ['Blockly.blockRendering.BottomRow', 'Blockly.blockRendering.ExternalValueInput', 'Blockly.blockRendering.InlineInput', 'Blockly.blockRendering.InputRow', 'Blockly.blockRendering.Measurable', 'Blockly.blockRendering.NextConnection', 'Blockly.blockRendering.OutputConnection', 'Blockly.blockRendering.PreviousConnection', 'Blockly.blockRendering.RenderInfo', 'Blockly.blockRendering.RoundCorner', 'Blockly.blockRendering.Row', 'Blockly.blockRendering.SquareCorner', 'Blockly.blockRendering.SpacerRow', 'Blockly.blockRendering.StatementInput', 'Blockly.blockRendering.TopRow', 'Blockly.blockRendering.Types', 'Blockly.utils.object', 'Blockly.zelos.AfterStatementSpacerRow', 'Blockly.zelos.BeforeStatementSpacerRow', 'Blockly.zelos.BottomRow', 'Blockly.zelos.TopRow']);
goog.addDependency("../../core/renderers/zelos/measurables/rows.js", ['Blockly.zelos.BottomRow', 'Blockly.zelos.TopRow', 'Blockly.zelos.AfterStatementSpacerRow', 'Blockly.zelos.BeforeStatementSpacerRow'], ['Blockly.blockRendering.BottomRow', 'Blockly.blockRendering.TopRow', 'Blockly.blockRendering.SpacerRow', 'Blockly.utils.object']);
goog.addDependency("../../core/renderers/zelos/renderer.js", ['Blockly.zelos.Renderer'], ['Blockly.blockRendering', 'Blockly.blockRendering.Renderer', 'Blockly.utils.object', 'Blockly.zelos.ConstantProvider', 'Blockly.zelos.Drawer', 'Blockly.zelos.RenderInfo']);
goog.addDependency("../../core/requires.js", ['Blockly.requires'], ['Blockly', 'Blockly.Comment', 'Blockly.HorizontalFlyout', 'Blockly.VerticalFlyout', 'Blockly.FlyoutButton', 'Blockly.Generator', 'Blockly.Toolbox', 'Blockly.Trashcan', 'Blockly.VariablesDynamic', 'Blockly.ZoomControls', 'Blockly.Mutator', 'Blockly.FieldAngle', 'Blockly.FieldCheckbox', 'Blockly.FieldColour', 'Blockly.FieldDropdown', 'Blockly.FieldLabelSerializable', 'Blockly.FieldImage', 'Blockly.FieldTextInput', 'Blockly.FieldMultilineInput', 'Blockly.FieldNumber', 'Blockly.FieldVariable', 'Blockly.geras.Renderer', 'Blockly.Themes.Classic', 'Blockly.Themes.Dark', 'Blockly.Themes.HighContrast', 'Blockly.Themes.Modern']);
goog.addDependency("../../core/renderers/zelos/info.js", ['Blockly.zelos', 'Blockly.zelos.RenderInfo'], ['Blockly.blockRendering.BottomRow', 'Blockly.blockRendering.ExternalValueInput', 'Blockly.blockRendering.InlineInput', 'Blockly.blockRendering.InputRow', 'Blockly.blockRendering.Measurable', 'Blockly.blockRendering.NextConnection', 'Blockly.blockRendering.OutputConnection', 'Blockly.blockRendering.PreviousConnection', 'Blockly.blockRendering.RenderInfo', 'Blockly.blockRendering.RoundCorner', 'Blockly.blockRendering.Row', 'Blockly.blockRendering.SpacerRow', 'Blockly.blockRendering.SquareCorner', 'Blockly.blockRendering.TopRow', 'Blockly.blockRendering.Types', 'Blockly.utils.object', 'Blockly.zelos.BottomRow', 'Blockly.zelos.RightConnectionShape', 'Blockly.zelos.StatementInput', 'Blockly.zelos.TopRow']);
goog.addDependency("../../core/renderers/zelos/marker_svg.js", ['Blockly.zelos.MarkerSvg'], ['Blockly.blockRendering.MarkerSvg']);
goog.addDependency("../../core/renderers/zelos/measurables/inputs.js", ['Blockly.zelos.StatementInput'], ['Blockly.blockRendering.StatementInput', 'Blockly.utils.object']);
goog.addDependency("../../core/renderers/zelos/measurables/row_elements.js", ['Blockly.zelos.RightConnectionShape'], ['Blockly.blockRendering.Measurable', 'Blockly.blockRendering.Types', 'Blockly.utils.object']);
goog.addDependency("../../core/renderers/zelos/measurables/rows.js", ['Blockly.zelos.BottomRow', 'Blockly.zelos.TopRow'], ['Blockly.blockRendering.BottomRow', 'Blockly.blockRendering.SpacerRow', 'Blockly.blockRendering.TopRow', 'Blockly.utils.object']);
goog.addDependency("../../core/renderers/zelos/path_object.js", ['Blockly.zelos.PathObject'], ['Blockly.blockRendering.PathObject', 'Blockly.utils.dom', 'Blockly.utils.object', 'Blockly.zelos.ConstantProvider']);
goog.addDependency("../../core/renderers/zelos/renderer.js", ['Blockly.zelos.Renderer'], ['Blockly.blockRendering', 'Blockly.blockRendering.Renderer', 'Blockly.utils.object', 'Blockly.zelos.ConstantProvider', 'Blockly.zelos.Drawer', 'Blockly.zelos.MarkerSvg', 'Blockly.zelos.PathObject', 'Blockly.zelos.RenderInfo']);
goog.addDependency("../../core/requires.js", ['Blockly.requires'], ['Blockly', 'Blockly.Comment', 'Blockly.FieldAngle', 'Blockly.FieldCheckbox', 'Blockly.FieldColour', 'Blockly.FieldDropdown', 'Blockly.FieldImage', 'Blockly.FieldLabelSerializable', 'Blockly.FieldMultilineInput', 'Blockly.FieldNumber', 'Blockly.FieldTextInput', 'Blockly.FieldVariable', 'Blockly.FlyoutButton', 'Blockly.Generator', 'Blockly.HorizontalFlyout', 'Blockly.Mutator', 'Blockly.Themes.Classic', 'Blockly.Themes.Dark', 'Blockly.Themes.Deuteranopia', 'Blockly.Themes.HighContrast', 'Blockly.Themes.Tritanopia', 'Blockly.Toolbox', 'Blockly.Trashcan', 'Blockly.VariablesDynamic', 'Blockly.VerticalFlyout', 'Blockly.Warning', 'Blockly.ZoomControls', 'Blockly.geras.Renderer', 'Blockly.thrasos.Renderer', 'Blockly.zelos.Renderer']);
goog.addDependency("../../core/scrollbar.js", ['Blockly.Scrollbar', 'Blockly.ScrollbarPair'], ['Blockly.Touch', 'Blockly.utils', 'Blockly.utils.Coordinate', 'Blockly.utils.dom']);
goog.addDependency("../../core/theme.js", ['Blockly.Theme'], []);
goog.addDependency("../../core/theme_manager.js", ['Blockly.ThemeManager'], ['Blockly.Theme']);
goog.addDependency("../../core/theme.js", ['Blockly.Theme'], ['Blockly.utils', 'Blockly.utils.colour']);
goog.addDependency("../../core/theme/classic.js", ['Blockly.Themes.Classic'], ['Blockly.Theme']);
goog.addDependency("../../core/theme/dark.js", ['Blockly.Themes.Dark'], ['Blockly.Theme']);
goog.addDependency("../../core/theme/dark.js", ['Blockly.Themes.Dark'], ['Blockly.Css', 'Blockly.Theme']);
goog.addDependency("../../core/theme/deuteranopia.js", ['Blockly.Themes.Deuteranopia'], ['Blockly.Theme']);
goog.addDependency("../../core/theme/highcontrast.js", ['Blockly.Themes.HighContrast'], ['Blockly.Theme']);
goog.addDependency("../../core/theme/modern.js", ['Blockly.Themes.Modern'], ['Blockly.Theme']);
goog.addDependency("../../core/theme_manager.js", ['Blockly.ThemeManager'], ['Blockly.Theme']);
goog.addDependency("../../core/toolbox.js", ['Blockly.Toolbox'], ['Blockly.Css', 'Blockly.Events', 'Blockly.Events.Ui', 'Blockly.navigation', 'Blockly.Touch', 'Blockly.tree.TreeControl', 'Blockly.tree.TreeNode', 'Blockly.utils', 'Blockly.utils.aria', 'Blockly.utils.colour', 'Blockly.utils.dom', 'Blockly.utils.object', 'Blockly.utils.Rect']);
goog.addDependency("../../core/theme/tritanopia.js", ['Blockly.Themes.Tritanopia'], ['Blockly.Theme']);
goog.addDependency("../../core/theme/zelos.js", ['Blockly.Themes.Zelos'], ['Blockly.Theme']);
goog.addDependency("../../core/toolbox.js", ['Blockly.Toolbox'], ['Blockly.Css', 'Blockly.Events', 'Blockly.Events.Ui', 'Blockly.Touch', 'Blockly.navigation', 'Blockly.tree.TreeControl', 'Blockly.tree.TreeNode', 'Blockly.utils', 'Blockly.utils.Rect', 'Blockly.utils.aria', 'Blockly.utils.colour', 'Blockly.utils.dom', 'Blockly.utils.object']);
goog.addDependency("../../core/tooltip.js", ['Blockly.Tooltip'], ['Blockly.utils.string']);
goog.addDependency("../../core/touch.js", ['Blockly.Touch'], ['Blockly.utils', 'Blockly.utils.global', 'Blockly.utils.string']);
goog.addDependency("../../core/touch_gesture.js", ['Blockly.TouchGesture'], ['Blockly.Gesture', 'Blockly.utils', 'Blockly.utils.Coordinate', 'Blockly.utils.object']);
goog.addDependency("../../core/trashcan.js", ['Blockly.Trashcan'], ['Blockly.Scrollbar', 'Blockly.utils.dom', 'Blockly.utils.Rect', 'Blockly.Xml']);
goog.addDependency("../../core/touch.js", ['Blockly.Touch'], ['Blockly.utils', 'Blockly.utils.global', 'Blockly.utils.string']);
goog.addDependency("../../core/trashcan.js", ['Blockly.Trashcan'], ['Blockly.Scrollbar', 'Blockly.Xml', 'Blockly.utils.Rect', 'Blockly.utils.dom']);
goog.addDependency("../../core/ui_events.js", ['Blockly.Events.Ui'], ['Blockly.Events', 'Blockly.Events.Abstract', 'Blockly.utils.object']);
goog.addDependency("../../core/ui_menu_utils.js", ['Blockly.utils.uiMenu'], ['Blockly.utils.style']);
goog.addDependency("../../core/utils.js", ['Blockly.utils'], ['Blockly.Msg', 'Blockly.utils.Coordinate', 'Blockly.utils.global', 'Blockly.utils.string', 'Blockly.utils.style', 'Blockly.utils.userAgent']);
goog.addDependency("../../core/utils.js", ['Blockly.utils'], ['Blockly.Msg', 'Blockly.constants', 'Blockly.utils.Coordinate', 'Blockly.utils.colour', 'Blockly.utils.global', 'Blockly.utils.string', 'Blockly.utils.style', 'Blockly.utils.userAgent']);
goog.addDependency("../../core/utils/aria.js", ['Blockly.utils.aria'], []);
goog.addDependency("../../core/utils/colour.js", ['Blockly.utils.colour'], ['Blockly.utils']);
goog.addDependency("../../core/utils/colour.js", ['Blockly.utils.colour'], []);
goog.addDependency("../../core/utils/coordinate.js", ['Blockly.utils.Coordinate'], []);
goog.addDependency("../../core/utils/dom.js", ['Blockly.utils.dom'], ['Blockly.utils.userAgent']);
goog.addDependency("../../core/utils/global.js", ['Blockly.utils.global'], []);
@@ -152,21 +163,21 @@ goog.addDependency("../../core/utils/svg_paths.js", ['Blockly.utils.svgPaths'],
goog.addDependency("../../core/utils/useragent.js", ['Blockly.utils.userAgent'], ['Blockly.utils.global']);
goog.addDependency("../../core/utils/xml.js", ['Blockly.utils.xml'], []);
goog.addDependency("../../core/variable_events.js", ['Blockly.Events.VarBase', 'Blockly.Events.VarCreate', 'Blockly.Events.VarDelete', 'Blockly.Events.VarRename'], ['Blockly.Events', 'Blockly.Events.Abstract', 'Blockly.utils.object']);
goog.addDependency("../../core/variable_map.js", ['Blockly.VariableMap'], ['Blockly.Events', 'Blockly.Events.VarDelete', 'Blockly.Events.VarRename', 'Blockly.Msg', 'Blockly.utils']);
goog.addDependency("../../core/variable_map.js", ['Blockly.VariableMap'], ['Blockly.Events', 'Blockly.Events.VarDelete', 'Blockly.Events.VarRename', 'Blockly.Msg', 'Blockly.utils', 'Blockly.utils.object']);
goog.addDependency("../../core/variable_model.js", ['Blockly.VariableModel'], ['Blockly.Events', 'Blockly.Events.VarCreate', 'Blockly.utils']);
goog.addDependency("../../core/variables.js", ['Blockly.Variables'], ['Blockly.Blocks', 'Blockly.Msg', 'Blockly.utils', 'Blockly.utils.xml', 'Blockly.VariableModel', 'Blockly.Xml']);
goog.addDependency("../../core/variables_dynamic.js", ['Blockly.VariablesDynamic'], ['Blockly.Variables', 'Blockly.Blocks', 'Blockly.Msg', 'Blockly.utils.xml', 'Blockly.VariableModel']);
goog.addDependency("../../core/variables_dynamic.js", ['Blockly.VariablesDynamic'], ['Blockly.Blocks', 'Blockly.Msg', 'Blockly.VariableModel', 'Blockly.Variables', 'Blockly.utils.xml']);
goog.addDependency("../../core/variables.js", ['Blockly.Variables'], ['Blockly.Blocks', 'Blockly.Msg', 'Blockly.VariableModel', 'Blockly.Xml', 'Blockly.utils', 'Blockly.utils.xml']);
goog.addDependency("../../core/warning.js", ['Blockly.Warning'], ['Blockly.Bubble', 'Blockly.Events', 'Blockly.Events.Ui', 'Blockly.Icon', 'Blockly.utils.dom', 'Blockly.utils.object']);
goog.addDependency("../../core/widgetdiv.js", ['Blockly.WidgetDiv'], ['Blockly.utils.style']);
goog.addDependency("../../core/workspace.js", ['Blockly.Workspace'], ['Blockly.Cursor', 'Blockly.MarkerCursor', 'Blockly.Events', 'Blockly.ThemeManager', 'Blockly.Themes.Classic', 'Blockly.utils', 'Blockly.utils.math', 'Blockly.VariableMap']);
goog.addDependency("../../core/workspace_audio.js", ['Blockly.WorkspaceAudio'], ['Blockly.utils', 'Blockly.utils.global', 'Blockly.utils.userAgent']);
goog.addDependency("../../core/workspace_comment.js", ['Blockly.WorkspaceComment'], ['Blockly.Events', 'Blockly.Events.CommentChange', 'Blockly.Events.CommentCreate', 'Blockly.Events.CommentDelete', 'Blockly.Events.CommentMove', 'Blockly.utils', 'Blockly.utils.Coordinate', 'Blockly.utils.xml']);
goog.addDependency("../../core/workspace_comment_render_svg.js", ['Blockly.WorkspaceCommentSvg.render'], ['Blockly.utils', 'Blockly.utils.Coordinate', 'Blockly.utils.dom']);
goog.addDependency("../../core/workspace_comment_svg.js", ['Blockly.WorkspaceCommentSvg'], ['Blockly.Events', 'Blockly.Events.CommentCreate', 'Blockly.Events.CommentDelete', 'Blockly.Events.CommentMove', 'Blockly.Events.Ui', 'Blockly.utils', 'Blockly.utils.Coordinate', 'Blockly.utils.dom', 'Blockly.utils.object', 'Blockly.utils.Rect', 'Blockly.WorkspaceComment']);
goog.addDependency("../../core/workspace_comment_svg.js", ['Blockly.WorkspaceCommentSvg'], ['Blockly.Css', 'Blockly.Events', 'Blockly.Events.CommentCreate', 'Blockly.Events.CommentDelete', 'Blockly.Events.CommentMove', 'Blockly.Events.Ui', 'Blockly.WorkspaceComment', 'Blockly.utils', 'Blockly.utils.Coordinate', 'Blockly.utils.Rect', 'Blockly.utils.dom', 'Blockly.utils.object']);
goog.addDependency("../../core/workspace_comment.js", ['Blockly.WorkspaceComment'], ['Blockly.Events', 'Blockly.Events.CommentChange', 'Blockly.Events.CommentCreate', 'Blockly.Events.CommentDelete', 'Blockly.Events.CommentMove', 'Blockly.utils', 'Blockly.utils.Coordinate', 'Blockly.utils.xml']);
goog.addDependency("../../core/workspace_drag_surface_svg.js", ['Blockly.WorkspaceDragSurfaceSvg'], ['Blockly.utils', 'Blockly.utils.dom']);
goog.addDependency("../../core/workspace_dragger.js", ['Blockly.WorkspaceDragger'], ['Blockly.utils.Coordinate']);
goog.addDependency("../../core/workspace_events.js", ['Blockly.Events.FinishedLoading'], ['Blockly.Events', 'Blockly.Events.Abstract', 'Blockly.utils.object']);
goog.addDependency("../../core/workspace_svg.js", ['Blockly.WorkspaceSvg'], ['Blockly.BlockSvg', 'Blockly.blockRendering', 'Blockly.ConnectionDB', 'Blockly.constants', 'Blockly.Events', 'Blockly.Events.BlockCreate', 'Blockly.Gesture', 'Blockly.Grid', 'Blockly.Msg', 'Blockly.Options', 'Blockly.TouchGesture', 'Blockly.utils', 'Blockly.utils.Coordinate', 'Blockly.utils.dom', 'Blockly.utils.object', 'Blockly.utils.Rect', 'Blockly.Workspace', 'Blockly.WorkspaceAudio', 'Blockly.WorkspaceDragSurfaceSvg', 'Blockly.Xml']);
goog.addDependency("../../core/workspace_events.js", ['Blockly.Events.FinishedLoading'], ['Blockly.Events', 'Blockly.Events.Ui', 'Blockly.utils.object']);
goog.addDependency("../../core/workspace_svg.js", ['Blockly.WorkspaceSvg'], ['Blockly.BlockSvg', 'Blockly.ConnectionDB', 'Blockly.Events', 'Blockly.Events.BlockCreate', 'Blockly.Gesture', 'Blockly.Grid', 'Blockly.MarkerManager', 'Blockly.Msg', 'Blockly.Options', 'Blockly.ThemeManager', 'Blockly.Themes.Classic', 'Blockly.TouchGesture', 'Blockly.Workspace', 'Blockly.WorkspaceAudio', 'Blockly.WorkspaceDragSurfaceSvg', 'Blockly.Xml', 'Blockly.blockRendering', 'Blockly.constants', 'Blockly.navigation', 'Blockly.utils', 'Blockly.utils.Coordinate', 'Blockly.utils.Rect', 'Blockly.utils.dom', 'Blockly.utils.object']);
goog.addDependency("../../core/workspace.js", ['Blockly.Workspace'], ['Blockly.Events', 'Blockly.Options', 'Blockly.VariableMap', 'Blockly.utils', 'Blockly.utils.math']);
goog.addDependency("../../core/ws_comment_events.js", ['Blockly.Events.CommentBase', 'Blockly.Events.CommentChange', 'Blockly.Events.CommentCreate', 'Blockly.Events.CommentDelete', 'Blockly.Events.CommentMove'], ['Blockly.Events', 'Blockly.Events.Abstract', 'Blockly.utils.Coordinate', 'Blockly.utils.object', 'Blockly.utils.xml']);
goog.addDependency("../../core/xml.js", ['Blockly.Xml'], ['Blockly.Events', 'Blockly.Events.BlockCreate', 'Blockly.Events.FinishedLoading', 'Blockly.Events.VarCreate', 'Blockly.utils', 'Blockly.utils.dom', 'Blockly.utils.global', 'Blockly.utils.xml']);
goog.addDependency("../../core/zoom_controls.js", ['Blockly.ZoomControls'], ['Blockly.Css', 'Blockly.Scrollbar', 'Blockly.Touch', 'Blockly.utils.dom']);

View File

@@ -554,14 +554,14 @@ Blockly.Constants.Logic.LOGIC_COMPARE_ONCHANGE_MIXIN = {
var blockB = this.getInputTargetBlock('B');
// Disconnect blocks that existed prior to this change if they don't match.
if (blockA && blockB &&
!blockA.outputConnection.checkType_(blockB.outputConnection)) {
!blockA.outputConnection.checkType(blockB.outputConnection)) {
// Mismatch between two inputs. Revert the block connections,
// bumping away the newly connected block(s).
Blockly.Events.setGroup(e.group);
var prevA = this.prevBlocks_[0];
if (prevA !== blockA) {
blockA.unplug();
if (prevA && !prevA.isShadow()) {
if (prevA && !prevA.isDisposed() && !prevA.isShadow()) {
// The shadow block is automatically replaced during unplug().
this.getInput('A').connection.connect(prevA.outputConnection);
}
@@ -569,7 +569,7 @@ Blockly.Constants.Logic.LOGIC_COMPARE_ONCHANGE_MIXIN = {
var prevB = this.prevBlocks_[1];
if (prevB !== blockB) {
blockB.unplug();
if (prevB && !prevB.isShadow()) {
if (prevB && !prevB.isDisposed() && !prevB.isShadow()) {
// The shadow block is automatically replaced during unplug().
this.getInput('B').connection.connect(prevB.outputConnection);
}
@@ -621,7 +621,7 @@ Blockly.Constants.Logic.LOGIC_TERNARY_ONCHANGE_MIXIN = {
if ((blockA || blockB) && parentConnection) {
for (var i = 0; i < 2; i++) {
var block = (i == 1) ? blockA : blockB;
if (block && !block.outputConnection.checkType_(parentConnection)) {
if (block && !block.outputConnection.checkType(parentConnection)) {
// Ensure that any disconnections are grouped with the causing event.
Blockly.Events.setGroup(e.group);
if (parentConnection === this.prevParentConnection_) {

View File

@@ -35,6 +35,7 @@ goog.require('Blockly.FieldDropdown');
goog.require('Blockly.FieldLabel');
goog.require('Blockly.FieldNumber');
goog.require('Blockly.FieldVariable');
goog.require('Blockly.Warning');
/**

View File

@@ -30,6 +30,7 @@ goog.require('Blockly.FieldCheckbox');
goog.require('Blockly.FieldLabel');
goog.require('Blockly.FieldTextInput');
goog.require('Blockly.Mutator');
goog.require('Blockly.Warning');
Blockly.Blocks['procedures_defnoreturn'] = {
@@ -139,7 +140,7 @@ Blockly.Blocks['procedures_defnoreturn'] = {
domToMutation: function(xmlElement) {
this.arguments_ = [];
this.argumentVarModels_ = [];
for (var i = 0, childNode; childNode = xmlElement.childNodes[i]; i++) {
for (var i = 0, childNode; (childNode = xmlElement.childNodes[i]); i++) {
if (childNode.nodeName.toLowerCase() == 'arg') {
var varName = childNode.getAttribute('name');
var varId = childNode.getAttribute('varid') || childNode.getAttribute('varId');
@@ -354,7 +355,7 @@ Blockly.Blocks['procedures_defnoreturn'] = {
// Update the mutator's variables if the mutator is open.
if (this.mutator && this.mutator.isVisible()) {
var blocks = this.mutator.workspace_.getAllBlocks(false);
for (var i = 0, block; block = blocks[i]; i++) {
for (var i = 0, block; (block = blocks[i]); i++) {
if (block.type == 'procedures_mutatorarg' &&
Blockly.Names.equals(oldName, block.getFieldValue('NAME'))) {
block.setFieldValue(newName, 'NAME');
@@ -482,71 +483,6 @@ Blockly.Blocks['procedures_mutatorcontainer'] = {
this.setTooltip(Blockly.Msg['PROCEDURES_MUTATORCONTAINER_TOOLTIP']);
this.contextMenu = false;
},
// TODO: Move this to a validator on the arg blocks, that way it can be
// tested.
/**
* This will create & delete variables and in dialogs workspace to ensure
* that when a new block is dragged out it will have a unique parameter name.
* @param {!Blockly.Events.Abstract} event Change event.
* @this {Blockly.Block}
*/
onchange: function(event) {
if (!this.workspace || this.workspace.isFlyout ||
(event.type != Blockly.Events.BLOCK_DELETE && event.type != Blockly.Events.BLOCK_CREATE)) {
return;
}
var blocks = this.workspace.getAllBlocks();
var allVariables = this.workspace.getAllVariables();
if (event.type == Blockly.Events.BLOCK_DELETE) {
var variableNamesToKeep = [];
for (var i = 0; i < blocks.length; i += 1) {
if (blocks[i].getFieldValue('NAME')) {
variableNamesToKeep.push(blocks[i].getFieldValue('NAME'));
}
}
for (var k = 0; k < allVariables.length; k += 1) {
if (variableNamesToKeep.indexOf(allVariables[k].name) == -1) {
this.workspace.deleteVariableById(allVariables[k].getId());
}
}
return;
}
if (event.type != Blockly.Events.BLOCK_CREATE) {
return;
}
var block = this.workspace.getBlockById(event.blockId);
// This is to handle the one none variable block
// Happens when all the blocks are regenerated
if (!block.getField('NAME')) {
return;
}
var varName = block.getFieldValue('NAME');
var variable = this.workspace.getVariable(varName);
if (!variable) {
// This means the parameter name is not in use and we can create the variable.
variable = this.workspace.createVariable(varName);
}
// If the blocks are connected we don't have to check duplicate variables
// This only happens if the dialog box is open
if (block.previousConnection.isConnected() || block.nextConnection.isConnected()) {
return;
}
for (var j = 0; j < blocks.length; j += 1) {
// filter block that was created
if (block.id != blocks[j].id && blocks[j].getFieldValue('NAME') == variable.name) {
// generate new name and set name field
varName = Blockly.Variables.generateUniqueName(this.workspace);
variable = this.workspace.createVariable(varName);
block.setFieldValue(variable.name, 'NAME');
return;
}
}
}
};
Blockly.Blocks['procedures_mutatorarg'] = {
@@ -555,7 +491,8 @@ Blockly.Blocks['procedures_mutatorarg'] = {
* @this {Blockly.Block}
*/
init: function() {
var field = new Blockly.FieldTextInput('x', this.validator_);
var field = new Blockly.FieldTextInput(
Blockly.Procedures.DEFAULT_ARG, this.validator_);
// Hack: override showEditor to do just a little bit more work.
// We don't have a good place to hook into the start of a text edit.
field.oldShowEditorFn_ = field.showEditor_;
@@ -602,7 +539,9 @@ Blockly.Blocks['procedures_mutatorarg'] = {
}
// Prevents duplicate parameter names in functions
var blocks = sourceBlock.workspace.getAllBlocks();
var workspace = sourceBlock.workspace.targetWorkspace ||
sourceBlock.workspace;
var blocks = workspace.getAllBlocks(false);
for (var i = 0; i < blocks.length; i++) {
if (blocks[i].id == this.getSourceBlock().id) {
continue;
@@ -612,6 +551,12 @@ Blockly.Blocks['procedures_mutatorarg'] = {
}
}
// Don't create variables for arg blocks that
// only exist in the mutator's flyout.
if (sourceBlock.isInFlyout) {
return varName;
}
var model = outerWs.getVariable(varName, '');
if (model && model.name != varName) {
// Rename the variable (case change)
@@ -625,6 +570,7 @@ Blockly.Blocks['procedures_mutatorarg'] = {
}
return varName;
},
/**
* Called when focusing away from the text field.
* Deletes all variables that were created as the user typed their intended
@@ -859,7 +805,7 @@ Blockly.Blocks['procedures_callnoreturn'] = {
this.renameProcedure(this.getProcedureCall(), name);
var args = [];
var paramIds = [];
for (var i = 0, childNode; childNode = xmlElement.childNodes[i]; i++) {
for (var i = 0, childNode; (childNode = xmlElement.childNodes[i]); i++) {
if (childNode.nodeName.toLowerCase() == 'arg') {
args.push(childNode.getAttribute('name'));
paramIds.push(childNode.getAttribute('paramId'));
@@ -942,7 +888,7 @@ Blockly.Blocks['procedures_callnoreturn'] = {
var def = Blockly.Procedures.getDefinition(name, this.workspace);
if (!def) {
Blockly.Events.setGroup(event.group);
this.dispose(true, false);
this.dispose(true);
Blockly.Events.setGroup(false);
}
} else if (event.type == Blockly.Events.CHANGE && event.element == 'disabled') {

View File

@@ -638,8 +638,8 @@ Blockly.Constants.Text.QUOTE_IMAGE_MIXIN = {
* @this {Blockly.Block}
*/
quoteField_: function(fieldName) {
for (var i = 0, input; input = this.inputList[i]; i++) {
for (var j = 0, field; field = input.fieldRow[j]; j++) {
for (var i = 0, input; (input = this.inputList[i]); i++) {
for (var j = 0, field; (field = input.fieldRow[j]); j++) {
if (fieldName == field.name) {
input.insertFieldAt(j, this.newQuote_(true));
input.insertFieldAt(j + 2, this.newQuote_(false));

View File

@@ -1,4 +1,4 @@
// Do not edit this file; automatically generated by build.py.
// Do not edit this file; automatically generated by gulp.
'use strict';
@@ -10,7 +10,7 @@ Blockly.defineBlocksWithJsonArray([{type:"lists_create_empty",message0:"%{BKY_LI
message0:"%{BKY_LISTS_REVERSE_MESSAGE0}",args0:[{type:"input_value",name:"LIST",check:"Array"}],output:"Array",inputsInline:!0,style:"list_blocks",tooltip:"%{BKY_LISTS_REVERSE_TOOLTIP}",helpUrl:"%{BKY_LISTS_REVERSE_HELPURL}"},{type:"lists_isEmpty",message0:"%{BKY_LISTS_ISEMPTY_TITLE}",args0:[{type:"input_value",name:"VALUE",check:["String","Array"]}],output:"Boolean",style:"list_blocks",tooltip:"%{BKY_LISTS_ISEMPTY_TOOLTIP}",helpUrl:"%{BKY_LISTS_ISEMPTY_HELPURL}"},{type:"lists_length",message0:"%{BKY_LISTS_LENGTH_TITLE}",
args0:[{type:"input_value",name:"VALUE",check:["String","Array"]}],output:"Number",style:"list_blocks",tooltip:"%{BKY_LISTS_LENGTH_TOOLTIP}",helpUrl:"%{BKY_LISTS_LENGTH_HELPURL}"}]);
Blockly.Blocks.lists_create_with={init:function(){this.setHelpUrl(Blockly.Msg.LISTS_CREATE_WITH_HELPURL);this.setStyle("list_blocks");this.itemCount_=3;this.updateShape_();this.setOutput(!0,"Array");this.setMutator(new Blockly.Mutator(["lists_create_with_item"]));this.setTooltip(Blockly.Msg.LISTS_CREATE_WITH_TOOLTIP)},mutationToDom:function(){var a=Blockly.utils.xml.createElement("mutation");a.setAttribute("items",this.itemCount_);return a},domToMutation:function(a){this.itemCount_=parseInt(a.getAttribute("items"),
10);this.updateShape_()},decompose:function(a){var b=a.newBlock("lists_create_with_container");b.initSvg();for(var c=b.getInput("STACK").connection,d=0;d<this.itemCount_;d++){var e=a.newBlock("lists_create_with_item");e.initSvg();c.connect(e.previousConnection);c=e.nextConnection}return b},compose:function(a){var b=a.getInputTargetBlock("STACK");for(a=[];b;)a.push(b.valueConnection_),b=b.nextConnection&&b.nextConnection.targetBlock();for(b=0;b<this.itemCount_;b++){var c=this.getInput("ADD"+b).connection.targetConnection;
10);this.updateShape_()},decompose:function(a){var b=a.newBlock("lists_create_with_container");b.initSvg();for(var c=b.getInput("STACK").connection,e=0;e<this.itemCount_;e++){var d=a.newBlock("lists_create_with_item");d.initSvg();c.connect(d.previousConnection);c=d.nextConnection}return b},compose:function(a){var b=a.getInputTargetBlock("STACK");for(a=[];b;)a.push(b.valueConnection_),b=b.nextConnection&&b.nextConnection.targetBlock();for(b=0;b<this.itemCount_;b++){var c=this.getInput("ADD"+b).connection.targetConnection;
c&&-1==a.indexOf(c)&&c.disconnect()}this.itemCount_=a.length;this.updateShape_();for(b=0;b<this.itemCount_;b++)Blockly.Mutator.reconnect(a[b],this,"ADD"+b)},saveConnections:function(a){a=a.getInputTargetBlock("STACK");for(var b=0;a;){var c=this.getInput("ADD"+b);a.valueConnection_=c&&c.connection.targetConnection;b++;a=a.nextConnection&&a.nextConnection.targetBlock()}},updateShape_:function(){this.itemCount_&&this.getInput("EMPTY")?this.removeInput("EMPTY"):this.itemCount_||this.getInput("EMPTY")||
this.appendDummyInput("EMPTY").appendField(Blockly.Msg.LISTS_CREATE_EMPTY_TITLE);for(var a=0;a<this.itemCount_;a++)if(!this.getInput("ADD"+a)){var b=this.appendValueInput("ADD"+a);0==a&&b.appendField(Blockly.Msg.LISTS_CREATE_WITH_INPUT_WITH)}for(;this.getInput("ADD"+a);)this.removeInput("ADD"+a),a++}};
Blockly.Blocks.lists_create_with_container={init:function(){this.setStyle("list_blocks");this.appendDummyInput().appendField(Blockly.Msg.LISTS_CREATE_WITH_CONTAINER_TITLE_ADD);this.appendStatementInput("STACK");this.setTooltip(Blockly.Msg.LISTS_CREATE_WITH_CONTAINER_TOOLTIP);this.contextMenu=!1}};
@@ -19,21 +19,21 @@ Blockly.Blocks.lists_indexOf={init:function(){var a=[[Blockly.Msg.LISTS_INDEX_OF
b.workspace.options.oneBasedIndex?"0":"-1")})}};
Blockly.Blocks.lists_getIndex={init:function(){var a=[[Blockly.Msg.LISTS_GET_INDEX_GET,"GET"],[Blockly.Msg.LISTS_GET_INDEX_GET_REMOVE,"GET_REMOVE"],[Blockly.Msg.LISTS_GET_INDEX_REMOVE,"REMOVE"]];this.WHERE_OPTIONS=[[Blockly.Msg.LISTS_GET_INDEX_FROM_START,"FROM_START"],[Blockly.Msg.LISTS_GET_INDEX_FROM_END,"FROM_END"],[Blockly.Msg.LISTS_GET_INDEX_FIRST,"FIRST"],[Blockly.Msg.LISTS_GET_INDEX_LAST,"LAST"],[Blockly.Msg.LISTS_GET_INDEX_RANDOM,"RANDOM"]];this.setHelpUrl(Blockly.Msg.LISTS_GET_INDEX_HELPURL);this.setStyle("list_blocks");
a=new Blockly.FieldDropdown(a,function(a){a="REMOVE"==a;this.getSourceBlock().updateStatement_(a)});this.appendValueInput("VALUE").setCheck("Array").appendField(Blockly.Msg.LISTS_GET_INDEX_INPUT_IN_LIST);this.appendDummyInput().appendField(a,"MODE").appendField("","SPACE");this.appendDummyInput("AT");Blockly.Msg.LISTS_GET_INDEX_TAIL&&this.appendDummyInput("TAIL").appendField(Blockly.Msg.LISTS_GET_INDEX_TAIL);this.setInputsInline(!0);this.setOutput(!0);this.updateAt_(!0);var b=this;this.setTooltip(function(){var a=
b.getFieldValue("MODE"),d=b.getFieldValue("WHERE"),e="";switch(a+" "+d){case "GET FROM_START":case "GET FROM_END":e=Blockly.Msg.LISTS_GET_INDEX_TOOLTIP_GET_FROM;break;case "GET FIRST":e=Blockly.Msg.LISTS_GET_INDEX_TOOLTIP_GET_FIRST;break;case "GET LAST":e=Blockly.Msg.LISTS_GET_INDEX_TOOLTIP_GET_LAST;break;case "GET RANDOM":e=Blockly.Msg.LISTS_GET_INDEX_TOOLTIP_GET_RANDOM;break;case "GET_REMOVE FROM_START":case "GET_REMOVE FROM_END":e=Blockly.Msg.LISTS_GET_INDEX_TOOLTIP_GET_REMOVE_FROM;break;case "GET_REMOVE FIRST":e=
Blockly.Msg.LISTS_GET_INDEX_TOOLTIP_GET_REMOVE_FIRST;break;case "GET_REMOVE LAST":e=Blockly.Msg.LISTS_GET_INDEX_TOOLTIP_GET_REMOVE_LAST;break;case "GET_REMOVE RANDOM":e=Blockly.Msg.LISTS_GET_INDEX_TOOLTIP_GET_REMOVE_RANDOM;break;case "REMOVE FROM_START":case "REMOVE FROM_END":e=Blockly.Msg.LISTS_GET_INDEX_TOOLTIP_REMOVE_FROM;break;case "REMOVE FIRST":e=Blockly.Msg.LISTS_GET_INDEX_TOOLTIP_REMOVE_FIRST;break;case "REMOVE LAST":e=Blockly.Msg.LISTS_GET_INDEX_TOOLTIP_REMOVE_LAST;break;case "REMOVE RANDOM":e=
Blockly.Msg.LISTS_GET_INDEX_TOOLTIP_REMOVE_RANDOM}if("FROM_START"==d||"FROM_END"==d)e+=" "+("FROM_START"==d?Blockly.Msg.LISTS_INDEX_FROM_START_TOOLTIP:Blockly.Msg.LISTS_INDEX_FROM_END_TOOLTIP).replace("%1",b.workspace.options.oneBasedIndex?"#1":"#0");return e})},mutationToDom:function(){var a=Blockly.utils.xml.createElement("mutation");a.setAttribute("statement",!this.outputConnection);var b=this.getInput("AT").type==Blockly.INPUT_VALUE;a.setAttribute("at",b);return a},domToMutation:function(a){var b=
b.getFieldValue("MODE"),e=b.getFieldValue("WHERE"),d="";switch(a+" "+e){case "GET FROM_START":case "GET FROM_END":d=Blockly.Msg.LISTS_GET_INDEX_TOOLTIP_GET_FROM;break;case "GET FIRST":d=Blockly.Msg.LISTS_GET_INDEX_TOOLTIP_GET_FIRST;break;case "GET LAST":d=Blockly.Msg.LISTS_GET_INDEX_TOOLTIP_GET_LAST;break;case "GET RANDOM":d=Blockly.Msg.LISTS_GET_INDEX_TOOLTIP_GET_RANDOM;break;case "GET_REMOVE FROM_START":case "GET_REMOVE FROM_END":d=Blockly.Msg.LISTS_GET_INDEX_TOOLTIP_GET_REMOVE_FROM;break;case "GET_REMOVE FIRST":d=
Blockly.Msg.LISTS_GET_INDEX_TOOLTIP_GET_REMOVE_FIRST;break;case "GET_REMOVE LAST":d=Blockly.Msg.LISTS_GET_INDEX_TOOLTIP_GET_REMOVE_LAST;break;case "GET_REMOVE RANDOM":d=Blockly.Msg.LISTS_GET_INDEX_TOOLTIP_GET_REMOVE_RANDOM;break;case "REMOVE FROM_START":case "REMOVE FROM_END":d=Blockly.Msg.LISTS_GET_INDEX_TOOLTIP_REMOVE_FROM;break;case "REMOVE FIRST":d=Blockly.Msg.LISTS_GET_INDEX_TOOLTIP_REMOVE_FIRST;break;case "REMOVE LAST":d=Blockly.Msg.LISTS_GET_INDEX_TOOLTIP_REMOVE_LAST;break;case "REMOVE RANDOM":d=
Blockly.Msg.LISTS_GET_INDEX_TOOLTIP_REMOVE_RANDOM}if("FROM_START"==e||"FROM_END"==e)d+=" "+("FROM_START"==e?Blockly.Msg.LISTS_INDEX_FROM_START_TOOLTIP:Blockly.Msg.LISTS_INDEX_FROM_END_TOOLTIP).replace("%1",b.workspace.options.oneBasedIndex?"#1":"#0");return d})},mutationToDom:function(){var a=Blockly.utils.xml.createElement("mutation");a.setAttribute("statement",!this.outputConnection);var b=this.getInput("AT").type==Blockly.INPUT_VALUE;a.setAttribute("at",b);return a},domToMutation:function(a){var b=
"true"==a.getAttribute("statement");this.updateStatement_(b);a="false"!=a.getAttribute("at");this.updateAt_(a)},updateStatement_:function(a){a!=!this.outputConnection&&(this.unplug(!0,!0),a?(this.setOutput(!1),this.setPreviousStatement(!0),this.setNextStatement(!0)):(this.setPreviousStatement(!1),this.setNextStatement(!1),this.setOutput(!0)))},updateAt_:function(a){this.removeInput("AT");this.removeInput("ORDINAL",!0);a?(this.appendValueInput("AT").setCheck("Number"),Blockly.Msg.ORDINAL_NUMBER_SUFFIX&&
this.appendDummyInput("ORDINAL").appendField(Blockly.Msg.ORDINAL_NUMBER_SUFFIX)):this.appendDummyInput("AT");var b=new Blockly.FieldDropdown(this.WHERE_OPTIONS,function(b){var c="FROM_START"==b||"FROM_END"==b;if(c!=a){var e=this.getSourceBlock();e.updateAt_(c);e.setFieldValue(b,"WHERE");return null}});this.getInput("AT").appendField(b,"WHERE");Blockly.Msg.LISTS_GET_INDEX_TAIL&&this.moveInputBefore("TAIL",null)}};
this.appendDummyInput("ORDINAL").appendField(Blockly.Msg.ORDINAL_NUMBER_SUFFIX)):this.appendDummyInput("AT");var b=new Blockly.FieldDropdown(this.WHERE_OPTIONS,function(b){var c="FROM_START"==b||"FROM_END"==b;if(c!=a){var d=this.getSourceBlock();d.updateAt_(c);d.setFieldValue(b,"WHERE");return null}});this.getInput("AT").appendField(b,"WHERE");Blockly.Msg.LISTS_GET_INDEX_TAIL&&this.moveInputBefore("TAIL",null)}};
Blockly.Blocks.lists_setIndex={init:function(){var a=[[Blockly.Msg.LISTS_SET_INDEX_SET,"SET"],[Blockly.Msg.LISTS_SET_INDEX_INSERT,"INSERT"]];this.WHERE_OPTIONS=[[Blockly.Msg.LISTS_GET_INDEX_FROM_START,"FROM_START"],[Blockly.Msg.LISTS_GET_INDEX_FROM_END,"FROM_END"],[Blockly.Msg.LISTS_GET_INDEX_FIRST,"FIRST"],[Blockly.Msg.LISTS_GET_INDEX_LAST,"LAST"],[Blockly.Msg.LISTS_GET_INDEX_RANDOM,"RANDOM"]];this.setHelpUrl(Blockly.Msg.LISTS_SET_INDEX_HELPURL);this.setStyle("list_blocks");this.appendValueInput("LIST").setCheck("Array").appendField(Blockly.Msg.LISTS_SET_INDEX_INPUT_IN_LIST);
this.appendDummyInput().appendField(new Blockly.FieldDropdown(a),"MODE").appendField("","SPACE");this.appendDummyInput("AT");this.appendValueInput("TO").appendField(Blockly.Msg.LISTS_SET_INDEX_INPUT_TO);this.setInputsInline(!0);this.setPreviousStatement(!0);this.setNextStatement(!0);this.setTooltip(Blockly.Msg.LISTS_SET_INDEX_TOOLTIP);this.updateAt_(!0);var b=this;this.setTooltip(function(){var a=b.getFieldValue("MODE"),d=b.getFieldValue("WHERE"),e="";switch(a+" "+d){case "SET FROM_START":case "SET FROM_END":e=
Blockly.Msg.LISTS_SET_INDEX_TOOLTIP_SET_FROM;break;case "SET FIRST":e=Blockly.Msg.LISTS_SET_INDEX_TOOLTIP_SET_FIRST;break;case "SET LAST":e=Blockly.Msg.LISTS_SET_INDEX_TOOLTIP_SET_LAST;break;case "SET RANDOM":e=Blockly.Msg.LISTS_SET_INDEX_TOOLTIP_SET_RANDOM;break;case "INSERT FROM_START":case "INSERT FROM_END":e=Blockly.Msg.LISTS_SET_INDEX_TOOLTIP_INSERT_FROM;break;case "INSERT FIRST":e=Blockly.Msg.LISTS_SET_INDEX_TOOLTIP_INSERT_FIRST;break;case "INSERT LAST":e=Blockly.Msg.LISTS_SET_INDEX_TOOLTIP_INSERT_LAST;
break;case "INSERT RANDOM":e=Blockly.Msg.LISTS_SET_INDEX_TOOLTIP_INSERT_RANDOM}if("FROM_START"==d||"FROM_END"==d)e+=" "+Blockly.Msg.LISTS_INDEX_FROM_START_TOOLTIP.replace("%1",b.workspace.options.oneBasedIndex?"#1":"#0");return e})},mutationToDom:function(){var a=Blockly.utils.xml.createElement("mutation"),b=this.getInput("AT").type==Blockly.INPUT_VALUE;a.setAttribute("at",b);return a},domToMutation:function(a){a="false"!=a.getAttribute("at");this.updateAt_(a)},updateAt_:function(a){this.removeInput("AT");
this.removeInput("ORDINAL",!0);a?(this.appendValueInput("AT").setCheck("Number"),Blockly.Msg.ORDINAL_NUMBER_SUFFIX&&this.appendDummyInput("ORDINAL").appendField(Blockly.Msg.ORDINAL_NUMBER_SUFFIX)):this.appendDummyInput("AT");var b=new Blockly.FieldDropdown(this.WHERE_OPTIONS,function(b){var c="FROM_START"==b||"FROM_END"==b;if(c!=a){var e=this.getSourceBlock();e.updateAt_(c);e.setFieldValue(b,"WHERE");return null}});this.moveInputBefore("AT","TO");this.getInput("ORDINAL")&&this.moveInputBefore("ORDINAL",
this.appendDummyInput().appendField(new Blockly.FieldDropdown(a),"MODE").appendField("","SPACE");this.appendDummyInput("AT");this.appendValueInput("TO").appendField(Blockly.Msg.LISTS_SET_INDEX_INPUT_TO);this.setInputsInline(!0);this.setPreviousStatement(!0);this.setNextStatement(!0);this.setTooltip(Blockly.Msg.LISTS_SET_INDEX_TOOLTIP);this.updateAt_(!0);var b=this;this.setTooltip(function(){var a=b.getFieldValue("MODE"),e=b.getFieldValue("WHERE"),d="";switch(a+" "+e){case "SET FROM_START":case "SET FROM_END":d=
Blockly.Msg.LISTS_SET_INDEX_TOOLTIP_SET_FROM;break;case "SET FIRST":d=Blockly.Msg.LISTS_SET_INDEX_TOOLTIP_SET_FIRST;break;case "SET LAST":d=Blockly.Msg.LISTS_SET_INDEX_TOOLTIP_SET_LAST;break;case "SET RANDOM":d=Blockly.Msg.LISTS_SET_INDEX_TOOLTIP_SET_RANDOM;break;case "INSERT FROM_START":case "INSERT FROM_END":d=Blockly.Msg.LISTS_SET_INDEX_TOOLTIP_INSERT_FROM;break;case "INSERT FIRST":d=Blockly.Msg.LISTS_SET_INDEX_TOOLTIP_INSERT_FIRST;break;case "INSERT LAST":d=Blockly.Msg.LISTS_SET_INDEX_TOOLTIP_INSERT_LAST;
break;case "INSERT RANDOM":d=Blockly.Msg.LISTS_SET_INDEX_TOOLTIP_INSERT_RANDOM}if("FROM_START"==e||"FROM_END"==e)d+=" "+Blockly.Msg.LISTS_INDEX_FROM_START_TOOLTIP.replace("%1",b.workspace.options.oneBasedIndex?"#1":"#0");return d})},mutationToDom:function(){var a=Blockly.utils.xml.createElement("mutation"),b=this.getInput("AT").type==Blockly.INPUT_VALUE;a.setAttribute("at",b);return a},domToMutation:function(a){a="false"!=a.getAttribute("at");this.updateAt_(a)},updateAt_:function(a){this.removeInput("AT");
this.removeInput("ORDINAL",!0);a?(this.appendValueInput("AT").setCheck("Number"),Blockly.Msg.ORDINAL_NUMBER_SUFFIX&&this.appendDummyInput("ORDINAL").appendField(Blockly.Msg.ORDINAL_NUMBER_SUFFIX)):this.appendDummyInput("AT");var b=new Blockly.FieldDropdown(this.WHERE_OPTIONS,function(b){var c="FROM_START"==b||"FROM_END"==b;if(c!=a){var d=this.getSourceBlock();d.updateAt_(c);d.setFieldValue(b,"WHERE");return null}});this.moveInputBefore("AT","TO");this.getInput("ORDINAL")&&this.moveInputBefore("ORDINAL",
"TO");this.getInput("AT").appendField(b,"WHERE")}};
Blockly.Blocks.lists_getSublist={init:function(){this.WHERE_OPTIONS_1=[[Blockly.Msg.LISTS_GET_SUBLIST_START_FROM_START,"FROM_START"],[Blockly.Msg.LISTS_GET_SUBLIST_START_FROM_END,"FROM_END"],[Blockly.Msg.LISTS_GET_SUBLIST_START_FIRST,"FIRST"]];this.WHERE_OPTIONS_2=[[Blockly.Msg.LISTS_GET_SUBLIST_END_FROM_START,"FROM_START"],[Blockly.Msg.LISTS_GET_SUBLIST_END_FROM_END,"FROM_END"],[Blockly.Msg.LISTS_GET_SUBLIST_END_LAST,"LAST"]];this.setHelpUrl(Blockly.Msg.LISTS_GET_SUBLIST_HELPURL);this.setStyle("list_blocks");
this.appendValueInput("LIST").setCheck("Array").appendField(Blockly.Msg.LISTS_GET_SUBLIST_INPUT_IN_LIST);this.appendDummyInput("AT1");this.appendDummyInput("AT2");Blockly.Msg.LISTS_GET_SUBLIST_TAIL&&this.appendDummyInput("TAIL").appendField(Blockly.Msg.LISTS_GET_SUBLIST_TAIL);this.setInputsInline(!0);this.setOutput(!0,"Array");this.updateAt_(1,!0);this.updateAt_(2,!0);this.setTooltip(Blockly.Msg.LISTS_GET_SUBLIST_TOOLTIP)},mutationToDom:function(){var a=Blockly.utils.xml.createElement("mutation"),
b=this.getInput("AT1").type==Blockly.INPUT_VALUE;a.setAttribute("at1",b);b=this.getInput("AT2").type==Blockly.INPUT_VALUE;a.setAttribute("at2",b);return a},domToMutation:function(a){var b="true"==a.getAttribute("at1");a="true"==a.getAttribute("at2");this.updateAt_(1,b);this.updateAt_(2,a)},updateAt_:function(a,b){this.removeInput("AT"+a);this.removeInput("ORDINAL"+a,!0);b?(this.appendValueInput("AT"+a).setCheck("Number"),Blockly.Msg.ORDINAL_NUMBER_SUFFIX&&this.appendDummyInput("ORDINAL"+a).appendField(Blockly.Msg.ORDINAL_NUMBER_SUFFIX)):
this.appendDummyInput("AT"+a);var c=new Blockly.FieldDropdown(this["WHERE_OPTIONS_"+a],function(c){var d="FROM_START"==c||"FROM_END"==c;if(d!=b){var f=this.getSourceBlock();f.updateAt_(a,d);f.setFieldValue(c,"WHERE"+a);return null}});this.getInput("AT"+a).appendField(c,"WHERE"+a);1==a&&(this.moveInputBefore("AT1","AT2"),this.getInput("ORDINAL1")&&this.moveInputBefore("ORDINAL1","AT2"));Blockly.Msg.LISTS_GET_SUBLIST_TAIL&&this.moveInputBefore("TAIL",null)}};
this.appendDummyInput("AT"+a);var c=new Blockly.FieldDropdown(this["WHERE_OPTIONS_"+a],function(c){var d="FROM_START"==c||"FROM_END"==c;if(d!=b){var e=this.getSourceBlock();e.updateAt_(a,d);e.setFieldValue(c,"WHERE"+a);return null}});this.getInput("AT"+a).appendField(c,"WHERE"+a);1==a&&(this.moveInputBefore("AT1","AT2"),this.getInput("ORDINAL1")&&this.moveInputBefore("ORDINAL1","AT2"));Blockly.Msg.LISTS_GET_SUBLIST_TAIL&&this.moveInputBefore("TAIL",null)}};
Blockly.Blocks.lists_sort={init:function(){this.jsonInit({message0:Blockly.Msg.LISTS_SORT_TITLE,args0:[{type:"field_dropdown",name:"TYPE",options:[[Blockly.Msg.LISTS_SORT_TYPE_NUMERIC,"NUMERIC"],[Blockly.Msg.LISTS_SORT_TYPE_TEXT,"TEXT"],[Blockly.Msg.LISTS_SORT_TYPE_IGNORECASE,"IGNORE_CASE"]]},{type:"field_dropdown",name:"DIRECTION",options:[[Blockly.Msg.LISTS_SORT_ORDER_ASCENDING,"1"],[Blockly.Msg.LISTS_SORT_ORDER_DESCENDING,"-1"]]},{type:"input_value",name:"LIST",check:"Array"}],output:"Array",style:"list_blocks",
tooltip:Blockly.Msg.LISTS_SORT_TOOLTIP,helpUrl:Blockly.Msg.LISTS_SORT_HELPURL})}};
Blockly.Blocks.lists_split={init:function(){var a=this,b=new Blockly.FieldDropdown([[Blockly.Msg.LISTS_SPLIT_LIST_FROM_TEXT,"SPLIT"],[Blockly.Msg.LISTS_SPLIT_TEXT_FROM_LIST,"JOIN"]],function(b){a.updateType_(b)});this.setHelpUrl(Blockly.Msg.LISTS_SPLIT_HELPURL);this.setStyle("list_blocks");this.appendValueInput("INPUT").setCheck("String").appendField(b,"MODE");this.appendValueInput("DELIM").setCheck("String").appendField(Blockly.Msg.LISTS_SPLIT_WITH_DELIMITER);this.setInputsInline(!0);this.setOutput(!0,
@@ -49,16 +49,16 @@ Blockly.defineBlocksWithJsonArray([{type:"controls_if_if",message0:"%{BKY_CONTRO
enableContextMenu:!1,style:"logic_blocks",tooltip:"%{BKY_CONTROLS_IF_ELSE_TOOLTIP}"}]);Blockly.Constants.Logic.TOOLTIPS_BY_OP={EQ:"%{BKY_LOGIC_COMPARE_TOOLTIP_EQ}",NEQ:"%{BKY_LOGIC_COMPARE_TOOLTIP_NEQ}",LT:"%{BKY_LOGIC_COMPARE_TOOLTIP_LT}",LTE:"%{BKY_LOGIC_COMPARE_TOOLTIP_LTE}",GT:"%{BKY_LOGIC_COMPARE_TOOLTIP_GT}",GTE:"%{BKY_LOGIC_COMPARE_TOOLTIP_GTE}",AND:"%{BKY_LOGIC_OPERATION_TOOLTIP_AND}",OR:"%{BKY_LOGIC_OPERATION_TOOLTIP_OR}"};
Blockly.Extensions.register("logic_op_tooltip",Blockly.Extensions.buildTooltipForDropdown("OP",Blockly.Constants.Logic.TOOLTIPS_BY_OP));
Blockly.Constants.Logic.CONTROLS_IF_MUTATOR_MIXIN={elseifCount_:0,elseCount_:0,suppressPrefixSuffix:!0,mutationToDom:function(){if(!this.elseifCount_&&!this.elseCount_)return null;var a=Blockly.utils.xml.createElement("mutation");this.elseifCount_&&a.setAttribute("elseif",this.elseifCount_);this.elseCount_&&a.setAttribute("else",1);return a},domToMutation:function(a){this.elseifCount_=parseInt(a.getAttribute("elseif"),10)||0;this.elseCount_=parseInt(a.getAttribute("else"),10)||0;this.rebuildShape_()},
decompose:function(a){var b=a.newBlock("controls_if_if");b.initSvg();for(var c=b.nextConnection,d=1;d<=this.elseifCount_;d++){var e=a.newBlock("controls_if_elseif");e.initSvg();c.connect(e.previousConnection);c=e.nextConnection}this.elseCount_&&(a=a.newBlock("controls_if_else"),a.initSvg(),c.connect(a.previousConnection));return b},compose:function(a){a=a.nextConnection.targetBlock();this.elseCount_=this.elseifCount_=0;for(var b=[null],c=[null],d=null;a;){switch(a.type){case "controls_if_elseif":this.elseifCount_++;
b.push(a.valueConnection_);c.push(a.statementConnection_);break;case "controls_if_else":this.elseCount_++;d=a.statementConnection_;break;default:throw TypeError("Unknown block type: "+a.type);}a=a.nextConnection&&a.nextConnection.targetBlock()}this.updateShape_();this.reconnectChildBlocks_(b,c,d)},saveConnections:function(a){a=a.nextConnection.targetBlock();for(var b=1;a;){switch(a.type){case "controls_if_elseif":var c=this.getInput("IF"+b),d=this.getInput("DO"+b);a.valueConnection_=c&&c.connection.targetConnection;
a.statementConnection_=d&&d.connection.targetConnection;b++;break;case "controls_if_else":d=this.getInput("ELSE");a.statementConnection_=d&&d.connection.targetConnection;break;default:throw TypeError("Unknown block type: "+a.type);}a=a.nextConnection&&a.nextConnection.targetBlock()}},rebuildShape_:function(){var a=[null],b=[null],c=null;this.getInput("ELSE")&&(c=this.getInput("ELSE").connection.targetConnection);for(var d=1;this.getInput("IF"+d);){var e=this.getInput("IF"+d),f=this.getInput("DO"+
d);a.push(e.connection.targetConnection);b.push(f.connection.targetConnection);d++}this.updateShape_();this.reconnectChildBlocks_(a,b,c)},updateShape_:function(){this.getInput("ELSE")&&this.removeInput("ELSE");for(var a=1;this.getInput("IF"+a);)this.removeInput("IF"+a),this.removeInput("DO"+a),a++;for(a=1;a<=this.elseifCount_;a++)this.appendValueInput("IF"+a).setCheck("Boolean").appendField(Blockly.Msg.CONTROLS_IF_MSG_ELSEIF),this.appendStatementInput("DO"+a).appendField(Blockly.Msg.CONTROLS_IF_MSG_THEN);
this.elseCount_&&this.appendStatementInput("ELSE").appendField(Blockly.Msg.CONTROLS_IF_MSG_ELSE)},reconnectChildBlocks_:function(a,b,c){for(var d=1;d<=this.elseifCount_;d++)Blockly.Mutator.reconnect(a[d],this,"IF"+d),Blockly.Mutator.reconnect(b[d],this,"DO"+d);Blockly.Mutator.reconnect(c,this,"ELSE")}};Blockly.Extensions.registerMutator("controls_if_mutator",Blockly.Constants.Logic.CONTROLS_IF_MUTATOR_MIXIN,null,["controls_if_elseif","controls_if_else"]);
decompose:function(a){var b=a.newBlock("controls_if_if");b.initSvg();for(var c=b.nextConnection,e=1;e<=this.elseifCount_;e++){var d=a.newBlock("controls_if_elseif");d.initSvg();c.connect(d.previousConnection);c=d.nextConnection}this.elseCount_&&(a=a.newBlock("controls_if_else"),a.initSvg(),c.connect(a.previousConnection));return b},compose:function(a){a=a.nextConnection.targetBlock();this.elseCount_=this.elseifCount_=0;for(var b=[null],c=[null],e=null;a;){switch(a.type){case "controls_if_elseif":this.elseifCount_++;
b.push(a.valueConnection_);c.push(a.statementConnection_);break;case "controls_if_else":this.elseCount_++;e=a.statementConnection_;break;default:throw TypeError("Unknown block type: "+a.type);}a=a.nextConnection&&a.nextConnection.targetBlock()}this.updateShape_();this.reconnectChildBlocks_(b,c,e)},saveConnections:function(a){a=a.nextConnection.targetBlock();for(var b=1;a;){switch(a.type){case "controls_if_elseif":var c=this.getInput("IF"+b),e=this.getInput("DO"+b);a.valueConnection_=c&&c.connection.targetConnection;
a.statementConnection_=e&&e.connection.targetConnection;b++;break;case "controls_if_else":e=this.getInput("ELSE");a.statementConnection_=e&&e.connection.targetConnection;break;default:throw TypeError("Unknown block type: "+a.type);}a=a.nextConnection&&a.nextConnection.targetBlock()}},rebuildShape_:function(){var a=[null],b=[null],c=null;this.getInput("ELSE")&&(c=this.getInput("ELSE").connection.targetConnection);for(var e=1;this.getInput("IF"+e);){var d=this.getInput("IF"+e),f=this.getInput("DO"+
e);a.push(d.connection.targetConnection);b.push(f.connection.targetConnection);e++}this.updateShape_();this.reconnectChildBlocks_(a,b,c)},updateShape_:function(){this.getInput("ELSE")&&this.removeInput("ELSE");for(var a=1;this.getInput("IF"+a);)this.removeInput("IF"+a),this.removeInput("DO"+a),a++;for(a=1;a<=this.elseifCount_;a++)this.appendValueInput("IF"+a).setCheck("Boolean").appendField(Blockly.Msg.CONTROLS_IF_MSG_ELSEIF),this.appendStatementInput("DO"+a).appendField(Blockly.Msg.CONTROLS_IF_MSG_THEN);
this.elseCount_&&this.appendStatementInput("ELSE").appendField(Blockly.Msg.CONTROLS_IF_MSG_ELSE)},reconnectChildBlocks_:function(a,b,c){for(var e=1;e<=this.elseifCount_;e++)Blockly.Mutator.reconnect(a[e],this,"IF"+e),Blockly.Mutator.reconnect(b[e],this,"DO"+e);Blockly.Mutator.reconnect(c,this,"ELSE")}};Blockly.Extensions.registerMutator("controls_if_mutator",Blockly.Constants.Logic.CONTROLS_IF_MUTATOR_MIXIN,null,["controls_if_elseif","controls_if_else"]);
Blockly.Constants.Logic.CONTROLS_IF_TOOLTIP_EXTENSION=function(){this.setTooltip(function(){if(this.elseifCount_||this.elseCount_){if(!this.elseifCount_&&this.elseCount_)return Blockly.Msg.CONTROLS_IF_TOOLTIP_2;if(this.elseifCount_&&!this.elseCount_)return Blockly.Msg.CONTROLS_IF_TOOLTIP_3;if(this.elseifCount_&&this.elseCount_)return Blockly.Msg.CONTROLS_IF_TOOLTIP_4}else return Blockly.Msg.CONTROLS_IF_TOOLTIP_1;return""}.bind(this))};Blockly.Extensions.register("controls_if_tooltip",Blockly.Constants.Logic.CONTROLS_IF_TOOLTIP_EXTENSION);
Blockly.Constants.Logic.LOGIC_COMPARE_ONCHANGE_MIXIN={onchange:function(a){this.prevBlocks_||(this.prevBlocks_=[null,null]);var b=this.getInputTargetBlock("A"),c=this.getInputTargetBlock("B");b&&c&&!b.outputConnection.checkType_(c.outputConnection)&&(Blockly.Events.setGroup(a.group),a=this.prevBlocks_[0],a!==b&&(b.unplug(),a&&!a.isShadow()&&this.getInput("A").connection.connect(a.outputConnection)),b=this.prevBlocks_[1],b!==c&&(c.unplug(),b&&!b.isShadow()&&this.getInput("B").connection.connect(b.outputConnection)),
Blockly.Constants.Logic.LOGIC_COMPARE_ONCHANGE_MIXIN={onchange:function(a){this.prevBlocks_||(this.prevBlocks_=[null,null]);var b=this.getInputTargetBlock("A"),c=this.getInputTargetBlock("B");b&&c&&!b.outputConnection.checkType(c.outputConnection)&&(Blockly.Events.setGroup(a.group),a=this.prevBlocks_[0],a!==b&&(b.unplug(),!a||a.isDisposed()||a.isShadow()||this.getInput("A").connection.connect(a.outputConnection)),b=this.prevBlocks_[1],b!==c&&(c.unplug(),!b||b.isDisposed()||b.isShadow()||this.getInput("B").connection.connect(b.outputConnection)),
this.bumpNeighbours(),Blockly.Events.setGroup(!1));this.prevBlocks_[0]=this.getInputTargetBlock("A");this.prevBlocks_[1]=this.getInputTargetBlock("B")}};Blockly.Constants.Logic.LOGIC_COMPARE_EXTENSION=function(){this.mixin(Blockly.Constants.Logic.LOGIC_COMPARE_ONCHANGE_MIXIN)};Blockly.Extensions.register("logic_compare",Blockly.Constants.Logic.LOGIC_COMPARE_EXTENSION);
Blockly.Constants.Logic.LOGIC_TERNARY_ONCHANGE_MIXIN={prevParentConnection_:null,onchange:function(a){var b=this.getInputTargetBlock("THEN"),c=this.getInputTargetBlock("ELSE"),d=this.outputConnection.targetConnection;if((b||c)&&d)for(var e=0;2>e;e++){var f=1==e?b:c;f&&!f.outputConnection.checkType_(d)&&(Blockly.Events.setGroup(a.group),d===this.prevParentConnection_?(this.unplug(),d.getSourceBlock().bumpNeighbours()):(f.unplug(),f.bumpNeighbours()),Blockly.Events.setGroup(!1))}this.prevParentConnection_=
d}};Blockly.Extensions.registerMixin("logic_ternary",Blockly.Constants.Logic.LOGIC_TERNARY_ONCHANGE_MIXIN);Blockly.Blocks.loops={};Blockly.Constants.Loops={};Blockly.Constants.Loops.HUE=120;
Blockly.Constants.Logic.LOGIC_TERNARY_ONCHANGE_MIXIN={prevParentConnection_:null,onchange:function(a){var b=this.getInputTargetBlock("THEN"),c=this.getInputTargetBlock("ELSE"),e=this.outputConnection.targetConnection;if((b||c)&&e)for(var d=0;2>d;d++){var f=1==d?b:c;f&&!f.outputConnection.checkType(e)&&(Blockly.Events.setGroup(a.group),e===this.prevParentConnection_?(this.unplug(),e.getSourceBlock().bumpNeighbours()):(f.unplug(),f.bumpNeighbours()),Blockly.Events.setGroup(!1))}this.prevParentConnection_=
e}};Blockly.Extensions.registerMixin("logic_ternary",Blockly.Constants.Logic.LOGIC_TERNARY_ONCHANGE_MIXIN);Blockly.Blocks.loops={};Blockly.Constants.Loops={};Blockly.Constants.Loops.HUE=120;
Blockly.defineBlocksWithJsonArray([{type:"controls_repeat_ext",message0:"%{BKY_CONTROLS_REPEAT_TITLE}",args0:[{type:"input_value",name:"TIMES",check:"Number"}],message1:"%{BKY_CONTROLS_REPEAT_INPUT_DO} %1",args1:[{type:"input_statement",name:"DO"}],previousStatement:null,nextStatement:null,style:"loop_blocks",tooltip:"%{BKY_CONTROLS_REPEAT_TOOLTIP}",helpUrl:"%{BKY_CONTROLS_REPEAT_HELPURL}"},{type:"controls_repeat",message0:"%{BKY_CONTROLS_REPEAT_TITLE}",args0:[{type:"field_number",name:"TIMES",value:10,
min:0,precision:1}],message1:"%{BKY_CONTROLS_REPEAT_INPUT_DO} %1",args1:[{type:"input_statement",name:"DO"}],previousStatement:null,nextStatement:null,style:"loop_blocks",tooltip:"%{BKY_CONTROLS_REPEAT_TOOLTIP}",helpUrl:"%{BKY_CONTROLS_REPEAT_HELPURL}"},{type:"controls_whileUntil",message0:"%1 %2",args0:[{type:"field_dropdown",name:"MODE",options:[["%{BKY_CONTROLS_WHILEUNTIL_OPERATOR_WHILE}","WHILE"],["%{BKY_CONTROLS_WHILEUNTIL_OPERATOR_UNTIL}","UNTIL"]]},{type:"input_value",name:"BOOL",check:"Boolean"}],
message1:"%{BKY_CONTROLS_REPEAT_INPUT_DO} %1",args1:[{type:"input_statement",name:"DO"}],previousStatement:null,nextStatement:null,style:"loop_blocks",helpUrl:"%{BKY_CONTROLS_WHILEUNTIL_HELPURL}",extensions:["controls_whileUntil_tooltip"]},{type:"controls_for",message0:"%{BKY_CONTROLS_FOR_TITLE}",args0:[{type:"field_variable",name:"VAR",variable:null},{type:"input_value",name:"FROM",check:"Number",align:"RIGHT"},{type:"input_value",name:"TO",check:"Number",align:"RIGHT"},{type:"input_value",name:"BY",
@@ -66,7 +66,7 @@ check:"Number",align:"RIGHT"}],message1:"%{BKY_CONTROLS_REPEAT_INPUT_DO} %1",arg
args1:[{type:"input_statement",name:"DO"}],previousStatement:null,nextStatement:null,style:"loop_blocks",helpUrl:"%{BKY_CONTROLS_FOREACH_HELPURL}",extensions:["contextMenu_newGetVariableBlock","controls_forEach_tooltip"]},{type:"controls_flow_statements",message0:"%1",args0:[{type:"field_dropdown",name:"FLOW",options:[["%{BKY_CONTROLS_FLOW_STATEMENTS_OPERATOR_BREAK}","BREAK"],["%{BKY_CONTROLS_FLOW_STATEMENTS_OPERATOR_CONTINUE}","CONTINUE"]]}],previousStatement:null,style:"loop_blocks",helpUrl:"%{BKY_CONTROLS_FLOW_STATEMENTS_HELPURL}",
extensions:["controls_flow_tooltip","controls_flow_in_loop_check"]}]);Blockly.Constants.Loops.WHILE_UNTIL_TOOLTIPS={WHILE:"%{BKY_CONTROLS_WHILEUNTIL_TOOLTIP_WHILE}",UNTIL:"%{BKY_CONTROLS_WHILEUNTIL_TOOLTIP_UNTIL}"};Blockly.Extensions.register("controls_whileUntil_tooltip",Blockly.Extensions.buildTooltipForDropdown("MODE",Blockly.Constants.Loops.WHILE_UNTIL_TOOLTIPS));Blockly.Constants.Loops.BREAK_CONTINUE_TOOLTIPS={BREAK:"%{BKY_CONTROLS_FLOW_STATEMENTS_TOOLTIP_BREAK}",CONTINUE:"%{BKY_CONTROLS_FLOW_STATEMENTS_TOOLTIP_CONTINUE}"};
Blockly.Extensions.register("controls_flow_tooltip",Blockly.Extensions.buildTooltipForDropdown("FLOW",Blockly.Constants.Loops.BREAK_CONTINUE_TOOLTIPS));
Blockly.Constants.Loops.CUSTOM_CONTEXT_MENU_CREATE_VARIABLES_GET_MIXIN={customContextMenu:function(a){if(!this.isInFlyout){var b=this.getField("VAR").getVariable(),c=b.name;if(!this.isCollapsed()&&null!=c){var d={enabled:!0};d.text=Blockly.Msg.VARIABLES_SET_CREATE_GET.replace("%1",c);b=Blockly.Variables.generateVariableFieldDom(b);c=Blockly.utils.xml.createElement("block");c.setAttribute("type","variables_get");c.appendChild(b);d.callback=Blockly.ContextMenu.callbackFactory(this,c);a.push(d)}}}};
Blockly.Constants.Loops.CUSTOM_CONTEXT_MENU_CREATE_VARIABLES_GET_MIXIN={customContextMenu:function(a){if(!this.isInFlyout){var b=this.getField("VAR").getVariable(),c=b.name;if(!this.isCollapsed()&&null!=c){var e={enabled:!0};e.text=Blockly.Msg.VARIABLES_SET_CREATE_GET.replace("%1",c);b=Blockly.Variables.generateVariableFieldDom(b);c=Blockly.utils.xml.createElement("block");c.setAttribute("type","variables_get");c.appendChild(b);e.callback=Blockly.ContextMenu.callbackFactory(this,c);a.push(e)}}}};
Blockly.Extensions.registerMixin("contextMenu_newGetVariableBlock",Blockly.Constants.Loops.CUSTOM_CONTEXT_MENU_CREATE_VARIABLES_GET_MIXIN);Blockly.Extensions.register("controls_for_tooltip",Blockly.Extensions.buildTooltipWithFieldText("%{BKY_CONTROLS_FOR_TOOLTIP}","VAR"));Blockly.Extensions.register("controls_forEach_tooltip",Blockly.Extensions.buildTooltipWithFieldText("%{BKY_CONTROLS_FOREACH_TOOLTIP}","VAR"));
Blockly.Constants.Loops.CONTROL_FLOW_IN_LOOP_CHECK_MIXIN={LOOP_TYPES:["controls_repeat","controls_repeat_ext","controls_forEach","controls_for","controls_whileUntil"],suppressPrefixSuffix:!0,getSurroundLoop:function(a){do{if(-1!=Blockly.Constants.Loops.CONTROL_FLOW_IN_LOOP_CHECK_MIXIN.LOOP_TYPES.indexOf(a.type))return a;a=a.getSurroundParent()}while(a);return null},onchange:function(a){this.workspace.isDragging&&!this.workspace.isDragging()&&(Blockly.Constants.Loops.CONTROL_FLOW_IN_LOOP_CHECK_MIXIN.getSurroundLoop(this)?
(this.setWarningText(null),this.isInFlyout||this.setEnabled(!0)):(this.setWarningText(Blockly.Msg.CONTROLS_FLOW_STATEMENTS_WARNING),this.isInFlyout||this.getInheritedDisabled()||this.setEnabled(!1)))}};Blockly.Extensions.registerMixin("controls_flow_in_loop_check",Blockly.Constants.Loops.CONTROL_FLOW_IN_LOOP_CHECK_MIXIN);Blockly.Blocks.math={};Blockly.Constants.Math={};Blockly.Constants.Math.HUE=230;
@@ -90,36 +90,34 @@ Blockly.Constants.Math.LIST_MODES_MUTATOR_MIXIN={updateType_:function(a){"MODE"=
Blockly.Extensions.registerMutator("math_modes_of_list_mutator",Blockly.Constants.Math.LIST_MODES_MUTATOR_MIXIN,Blockly.Constants.Math.LIST_MODES_MUTATOR_EXTENSION);Blockly.Blocks.procedures={};
Blockly.Blocks.procedures_defnoreturn={init:function(){var a=new Blockly.FieldTextInput("",Blockly.Procedures.rename);a.setSpellcheck(!1);this.appendDummyInput().appendField(Blockly.Msg.PROCEDURES_DEFNORETURN_TITLE).appendField(a,"NAME").appendField("","PARAMS");this.setMutator(new Blockly.Mutator(["procedures_mutatorarg"]));(this.workspace.options.comments||this.workspace.options.parentWorkspace&&this.workspace.options.parentWorkspace.options.comments)&&Blockly.Msg.PROCEDURES_DEFNORETURN_COMMENT&&this.setCommentText(Blockly.Msg.PROCEDURES_DEFNORETURN_COMMENT);
this.setStyle("procedure_blocks");this.setTooltip(Blockly.Msg.PROCEDURES_DEFNORETURN_TOOLTIP);this.setHelpUrl(Blockly.Msg.PROCEDURES_DEFNORETURN_HELPURL);this.arguments_=[];this.argumentVarModels_=[];this.setStatements_(!0);this.statementConnection_=null},setStatements_:function(a){this.hasStatements_!==a&&(a?(this.appendStatementInput("STACK").appendField(Blockly.Msg.PROCEDURES_DEFNORETURN_DO),this.getInput("RETURN")&&this.moveInputBefore("STACK","RETURN")):this.removeInput("STACK",!0),this.hasStatements_=
a)},updateParams_:function(){var a="";this.arguments_.length&&(a=Blockly.Msg.PROCEDURES_BEFORE_PARAMS+" "+this.arguments_.join(", "));Blockly.Events.disable();try{this.setFieldValue(a,"PARAMS")}finally{Blockly.Events.enable()}},mutationToDom:function(a){var b=Blockly.utils.xml.createElement("mutation");a&&b.setAttribute("name",this.getFieldValue("NAME"));for(var c=0;c<this.argumentVarModels_.length;c++){var d=Blockly.utils.xml.createElement("arg"),e=this.argumentVarModels_[c];d.setAttribute("name",
e.name);d.setAttribute("varid",e.getId());a&&this.paramIds_&&d.setAttribute("paramId",this.paramIds_[c]);b.appendChild(d)}this.hasStatements_||b.setAttribute("statements","false");return b},domToMutation:function(a){this.arguments_=[];this.argumentVarModels_=[];for(var b=0,c;c=a.childNodes[b];b++)if("arg"==c.nodeName.toLowerCase()){var d=c.getAttribute("name");c=c.getAttribute("varid")||c.getAttribute("varId");this.arguments_.push(d);c=Blockly.Variables.getOrCreateVariablePackage(this.workspace,c,
d,"");null!=c?this.argumentVarModels_.push(c):console.log("Failed to create a variable with name "+d+", ignoring.")}this.updateParams_();Blockly.Procedures.mutateCallers(this);this.setStatements_("false"!==a.getAttribute("statements"))},decompose:function(a){var b=Blockly.utils.xml.createElement("block");b.setAttribute("type","procedures_mutatorcontainer");var c=Blockly.utils.xml.createElement("statement");c.setAttribute("name","STACK");b.appendChild(c);for(var d=0;d<this.arguments_.length;d++){var e=
Blockly.utils.xml.createElement("block");e.setAttribute("type","procedures_mutatorarg");var f=Blockly.utils.xml.createElement("field");f.setAttribute("name","NAME");var g=Blockly.utils.xml.createTextNode(this.arguments_[d]);f.appendChild(g);e.appendChild(f);f=Blockly.utils.xml.createElement("next");e.appendChild(f);c.appendChild(e);c=f}a=Blockly.Xml.domToBlock(b,a);"procedures_defreturn"==this.type?a.setFieldValue(this.hasStatements_,"STATEMENTS"):a.removeInput("STATEMENT_INPUT");Blockly.Procedures.mutateCallers(this);
a)},updateParams_:function(){var a="";this.arguments_.length&&(a=Blockly.Msg.PROCEDURES_BEFORE_PARAMS+" "+this.arguments_.join(", "));Blockly.Events.disable();try{this.setFieldValue(a,"PARAMS")}finally{Blockly.Events.enable()}},mutationToDom:function(a){var b=Blockly.utils.xml.createElement("mutation");a&&b.setAttribute("name",this.getFieldValue("NAME"));for(var c=0;c<this.argumentVarModels_.length;c++){var e=Blockly.utils.xml.createElement("arg"),d=this.argumentVarModels_[c];e.setAttribute("name",
d.name);e.setAttribute("varid",d.getId());a&&this.paramIds_&&e.setAttribute("paramId",this.paramIds_[c]);b.appendChild(e)}this.hasStatements_||b.setAttribute("statements","false");return b},domToMutation:function(a){this.arguments_=[];this.argumentVarModels_=[];for(var b=0,c;c=a.childNodes[b];b++)if("arg"==c.nodeName.toLowerCase()){var e=c.getAttribute("name");c=c.getAttribute("varid")||c.getAttribute("varId");this.arguments_.push(e);c=Blockly.Variables.getOrCreateVariablePackage(this.workspace,c,
e,"");null!=c?this.argumentVarModels_.push(c):console.log("Failed to create a variable with name "+e+", ignoring.")}this.updateParams_();Blockly.Procedures.mutateCallers(this);this.setStatements_("false"!==a.getAttribute("statements"))},decompose:function(a){var b=Blockly.utils.xml.createElement("block");b.setAttribute("type","procedures_mutatorcontainer");var c=Blockly.utils.xml.createElement("statement");c.setAttribute("name","STACK");b.appendChild(c);for(var e=0;e<this.arguments_.length;e++){var d=
Blockly.utils.xml.createElement("block");d.setAttribute("type","procedures_mutatorarg");var f=Blockly.utils.xml.createElement("field");f.setAttribute("name","NAME");var g=Blockly.utils.xml.createTextNode(this.arguments_[e]);f.appendChild(g);d.appendChild(f);f=Blockly.utils.xml.createElement("next");d.appendChild(f);c.appendChild(d);c=f}a=Blockly.Xml.domToBlock(b,a);"procedures_defreturn"==this.type?a.setFieldValue(this.hasStatements_,"STATEMENTS"):a.removeInput("STATEMENT_INPUT");Blockly.Procedures.mutateCallers(this);
return a},compose:function(a){this.arguments_=[];this.paramIds_=[];this.argumentVarModels_=[];for(var b=a.getInputTargetBlock("STACK");b;){var c=b.getFieldValue("NAME");this.arguments_.push(c);c=this.workspace.getVariable(c,"");this.argumentVarModels_.push(c);this.paramIds_.push(b.id);b=b.nextConnection&&b.nextConnection.targetBlock()}this.updateParams_();Blockly.Procedures.mutateCallers(this);a=a.getFieldValue("STATEMENTS");if(null!==a&&(a="TRUE"==a,this.hasStatements_!=a))if(a)this.setStatements_(!0),
Blockly.Mutator.reconnect(this.statementConnection_,this,"STACK"),this.statementConnection_=null;else{a=this.getInput("STACK").connection;if(this.statementConnection_=a.targetConnection)a=a.targetBlock(),a.unplug(),a.bumpNeighbours();this.setStatements_(!1)}},getProcedureDef:function(){return[this.getFieldValue("NAME"),this.arguments_,!1]},getVars:function(){return this.arguments_},getVarModels:function(){return this.argumentVarModels_},renameVarById:function(a,b){var c=this.workspace.getVariableById(a);
if(""==c.type){c=c.name;for(var d=this.workspace.getVariableById(b),e=!1,f=0;f<this.argumentVarModels_.length;f++)this.argumentVarModels_[f].getId()==a&&(this.arguments_[f]=d.name,this.argumentVarModels_[f]=d,e=!0);e&&(this.displayRenamedVar_(c,d.name),Blockly.Procedures.mutateCallers(this))}},updateVarName:function(a){for(var b=a.name,c=!1,d=0;d<this.argumentVarModels_.length;d++)if(this.argumentVarModels_[d].getId()==a.getId()){var e=this.arguments_[d];this.arguments_[d]=b;c=!0}c&&(this.displayRenamedVar_(e,
b),Blockly.Procedures.mutateCallers(this))},displayRenamedVar_:function(a,b){this.updateParams_();if(this.mutator&&this.mutator.isVisible())for(var c=this.mutator.workspace_.getAllBlocks(!1),d=0,e;e=c[d];d++)"procedures_mutatorarg"==e.type&&Blockly.Names.equals(a,e.getFieldValue("NAME"))&&e.setFieldValue(b,"NAME")},customContextMenu:function(a){if(!this.isInFlyout){var b={enabled:!0},c=this.getFieldValue("NAME");b.text=Blockly.Msg.PROCEDURES_CREATE_DO.replace("%1",c);var d=Blockly.utils.xml.createElement("mutation");
d.setAttribute("name",c);for(c=0;c<this.arguments_.length;c++){var e=Blockly.utils.xml.createElement("arg");e.setAttribute("name",this.arguments_[c]);d.appendChild(e)}c=Blockly.utils.xml.createElement("block");c.setAttribute("type",this.callType_);c.appendChild(d);b.callback=Blockly.ContextMenu.callbackFactory(this,c);a.push(b);if(!this.isCollapsed())for(c=0;c<this.argumentVarModels_.length;c++)b={enabled:!0},d=this.argumentVarModels_[c],b.text=Blockly.Msg.VARIABLES_SET_CREATE_GET.replace("%1",d.name),
d=Blockly.Variables.generateVariableFieldDom(d),e=Blockly.utils.xml.createElement("block"),e.setAttribute("type","variables_get"),e.appendChild(d),b.callback=Blockly.ContextMenu.callbackFactory(this,e),a.push(b)}},callType_:"procedures_callnoreturn"};
if(""==c.type){c=c.name;b=this.workspace.getVariableById(b);for(var e=!1,d=0;d<this.argumentVarModels_.length;d++)this.argumentVarModels_[d].getId()==a&&(this.arguments_[d]=b.name,this.argumentVarModels_[d]=b,e=!0);e&&(this.displayRenamedVar_(c,b.name),Blockly.Procedures.mutateCallers(this))}},updateVarName:function(a){for(var b=a.name,c=!1,e=0;e<this.argumentVarModels_.length;e++)if(this.argumentVarModels_[e].getId()==a.getId()){var d=this.arguments_[e];this.arguments_[e]=b;c=!0}c&&(this.displayRenamedVar_(d,
b),Blockly.Procedures.mutateCallers(this))},displayRenamedVar_:function(a,b){this.updateParams_();if(this.mutator&&this.mutator.isVisible())for(var c=this.mutator.workspace_.getAllBlocks(!1),e=0,d;d=c[e];e++)"procedures_mutatorarg"==d.type&&Blockly.Names.equals(a,d.getFieldValue("NAME"))&&d.setFieldValue(b,"NAME")},customContextMenu:function(a){if(!this.isInFlyout){var b={enabled:!0},c=this.getFieldValue("NAME");b.text=Blockly.Msg.PROCEDURES_CREATE_DO.replace("%1",c);var e=Blockly.utils.xml.createElement("mutation");
e.setAttribute("name",c);for(c=0;c<this.arguments_.length;c++){var d=Blockly.utils.xml.createElement("arg");d.setAttribute("name",this.arguments_[c]);e.appendChild(d)}c=Blockly.utils.xml.createElement("block");c.setAttribute("type",this.callType_);c.appendChild(e);b.callback=Blockly.ContextMenu.callbackFactory(this,c);a.push(b);if(!this.isCollapsed())for(c=0;c<this.argumentVarModels_.length;c++)b={enabled:!0},e=this.argumentVarModels_[c],b.text=Blockly.Msg.VARIABLES_SET_CREATE_GET.replace("%1",e.name),
e=Blockly.Variables.generateVariableFieldDom(e),d=Blockly.utils.xml.createElement("block"),d.setAttribute("type","variables_get"),d.appendChild(e),b.callback=Blockly.ContextMenu.callbackFactory(this,d),a.push(b)}},callType_:"procedures_callnoreturn"};
Blockly.Blocks.procedures_defreturn={init:function(){var a=new Blockly.FieldTextInput("",Blockly.Procedures.rename);a.setSpellcheck(!1);this.appendDummyInput().appendField(Blockly.Msg.PROCEDURES_DEFRETURN_TITLE).appendField(a,"NAME").appendField("","PARAMS");this.appendValueInput("RETURN").setAlign(Blockly.ALIGN_RIGHT).appendField(Blockly.Msg.PROCEDURES_DEFRETURN_RETURN);this.setMutator(new Blockly.Mutator(["procedures_mutatorarg"]));(this.workspace.options.comments||this.workspace.options.parentWorkspace&&
this.workspace.options.parentWorkspace.options.comments)&&Blockly.Msg.PROCEDURES_DEFRETURN_COMMENT&&this.setCommentText(Blockly.Msg.PROCEDURES_DEFRETURN_COMMENT);this.setStyle("procedure_blocks");this.setTooltip(Blockly.Msg.PROCEDURES_DEFRETURN_TOOLTIP);this.setHelpUrl(Blockly.Msg.PROCEDURES_DEFRETURN_HELPURL);this.arguments_=[];this.argumentVarModels_=[];this.setStatements_(!0);this.statementConnection_=null},setStatements_:Blockly.Blocks.procedures_defnoreturn.setStatements_,updateParams_:Blockly.Blocks.procedures_defnoreturn.updateParams_,
mutationToDom:Blockly.Blocks.procedures_defnoreturn.mutationToDom,domToMutation:Blockly.Blocks.procedures_defnoreturn.domToMutation,decompose:Blockly.Blocks.procedures_defnoreturn.decompose,compose:Blockly.Blocks.procedures_defnoreturn.compose,getProcedureDef:function(){return[this.getFieldValue("NAME"),this.arguments_,!0]},getVars:Blockly.Blocks.procedures_defnoreturn.getVars,getVarModels:Blockly.Blocks.procedures_defnoreturn.getVarModels,renameVarById:Blockly.Blocks.procedures_defnoreturn.renameVarById,
updateVarName:Blockly.Blocks.procedures_defnoreturn.updateVarName,displayRenamedVar_:Blockly.Blocks.procedures_defnoreturn.displayRenamedVar_,customContextMenu:Blockly.Blocks.procedures_defnoreturn.customContextMenu,callType_:"procedures_callreturn"};
Blockly.Blocks.procedures_mutatorcontainer={init:function(){this.appendDummyInput().appendField(Blockly.Msg.PROCEDURES_MUTATORCONTAINER_TITLE);this.appendStatementInput("STACK");this.appendDummyInput("STATEMENT_INPUT").appendField(Blockly.Msg.PROCEDURES_ALLOW_STATEMENTS).appendField(new Blockly.FieldCheckbox("TRUE"),"STATEMENTS");this.setStyle("procedure_blocks");this.setTooltip(Blockly.Msg.PROCEDURES_MUTATORCONTAINER_TOOLTIP);this.contextMenu=!1},onchange:function(a){if(this.workspace&&!this.workspace.isFlyout&&
(a.type==Blockly.Events.BLOCK_DELETE||a.type==Blockly.Events.BLOCK_CREATE)){var b=this.workspace.getAllBlocks(),c=this.workspace.getAllVariables();if(a.type==Blockly.Events.BLOCK_DELETE){a=[];for(var d=0;d<b.length;d+=1)b[d].getFieldValue("NAME")&&a.push(b[d].getFieldValue("NAME"));for(b=0;b<c.length;b+=1)-1==a.indexOf(c[b].name)&&this.workspace.deleteVariableById(c[b].getId())}else if(a.type==Blockly.Events.BLOCK_CREATE&&(c=this.workspace.getBlockById(a.blockId),c.getField("NAME")&&(d=c.getFieldValue("NAME"),
(a=this.workspace.getVariable(d))||(a=this.workspace.createVariable(d)),!c.previousConnection.isConnected()&&!c.nextConnection.isConnected())))for(d=0;d<b.length;d+=1)if(c.id!=b[d].id&&b[d].getFieldValue("NAME")==a.name){d=Blockly.Variables.generateUniqueName(this.workspace);a=this.workspace.createVariable(d);c.setFieldValue(a.name,"NAME");break}}}};
Blockly.Blocks.procedures_mutatorarg={init:function(){var a=new Blockly.FieldTextInput("x",this.validator_);a.oldShowEditorFn_=a.showEditor_;a.showEditor_=function(){this.createdVariables_=[];this.oldShowEditorFn_()};this.appendDummyInput().appendField(Blockly.Msg.PROCEDURES_MUTATORARG_TITLE).appendField(a,"NAME");this.setPreviousStatement(!0);this.setNextStatement(!0);this.setStyle("procedure_blocks");this.setTooltip(Blockly.Msg.PROCEDURES_MUTATORARG_TOOLTIP);this.contextMenu=!1;a.onFinishEditing_=
this.deleteIntermediateVars_;a.createdVariables_=[];a.onFinishEditing_("x")},validator_:function(a){var b=this.getSourceBlock(),c=Blockly.Mutator.findParentWs(b.workspace);a=a.replace(/[\s\xa0]+/g," ").replace(/^ | $/g,"");if(!a)return null;b=b.workspace.getAllBlocks();for(var d=0;d<b.length;d++)if(b[d].id!=this.getSourceBlock().id&&b[d].getFieldValue("NAME")==a)return null;(b=c.getVariable(a,""))&&b.name!=a&&c.renameVariableById(b.getId(),a);b||(b=c.createVariable(a,""))&&this.createdVariables_&&
this.createdVariables_.push(b);return a},deleteIntermediateVars_:function(a){var b=Blockly.Mutator.findParentWs(this.getSourceBlock().workspace);if(b)for(var c=0;c<this.createdVariables_.length;c++){var d=this.createdVariables_[c];d.name!=a&&b.deleteVariableById(d.getId())}}};
Blockly.Blocks.procedures_mutatorcontainer={init:function(){this.appendDummyInput().appendField(Blockly.Msg.PROCEDURES_MUTATORCONTAINER_TITLE);this.appendStatementInput("STACK");this.appendDummyInput("STATEMENT_INPUT").appendField(Blockly.Msg.PROCEDURES_ALLOW_STATEMENTS).appendField(new Blockly.FieldCheckbox("TRUE"),"STATEMENTS");this.setStyle("procedure_blocks");this.setTooltip(Blockly.Msg.PROCEDURES_MUTATORCONTAINER_TOOLTIP);this.contextMenu=!1}};
Blockly.Blocks.procedures_mutatorarg={init:function(){var a=new Blockly.FieldTextInput(Blockly.Procedures.DEFAULT_ARG,this.validator_);a.oldShowEditorFn_=a.showEditor_;a.showEditor_=function(){this.createdVariables_=[];this.oldShowEditorFn_()};this.appendDummyInput().appendField(Blockly.Msg.PROCEDURES_MUTATORARG_TITLE).appendField(a,"NAME");this.setPreviousStatement(!0);this.setNextStatement(!0);this.setStyle("procedure_blocks");this.setTooltip(Blockly.Msg.PROCEDURES_MUTATORARG_TOOLTIP);this.contextMenu=
!1;a.onFinishEditing_=this.deleteIntermediateVars_;a.createdVariables_=[];a.onFinishEditing_("x")},validator_:function(a){var b=this.getSourceBlock(),c=Blockly.Mutator.findParentWs(b.workspace);a=a.replace(/[\s\xa0]+/g," ").replace(/^ | $/g,"");if(!a)return null;for(var e=(b.workspace.targetWorkspace||b.workspace).getAllBlocks(!1),d=0;d<e.length;d++)if(e[d].id!=this.getSourceBlock().id&&e[d].getFieldValue("NAME")==a)return null;if(b.isInFlyout)return a;(b=c.getVariable(a,""))&&b.name!=a&&c.renameVariableById(b.getId(),
a);b||(b=c.createVariable(a,""))&&this.createdVariables_&&this.createdVariables_.push(b);return a},deleteIntermediateVars_:function(a){var b=Blockly.Mutator.findParentWs(this.getSourceBlock().workspace);if(b)for(var c=0;c<this.createdVariables_.length;c++){var e=this.createdVariables_[c];e.name!=a&&b.deleteVariableById(e.getId())}}};
Blockly.Blocks.procedures_callnoreturn={init:function(){this.appendDummyInput("TOPROW").appendField(this.id,"NAME");this.setPreviousStatement(!0);this.setNextStatement(!0);this.setStyle("procedure_blocks");this.setHelpUrl(Blockly.Msg.PROCEDURES_CALLNORETURN_HELPURL);this.arguments_=[];this.argumentVarModels_=[];this.quarkConnections_={};this.quarkIds_=null;this.previousEnabledState_=!0},getProcedureCall:function(){return this.getFieldValue("NAME")},renameProcedure:function(a,b){Blockly.Names.equals(a,
this.getProcedureCall())&&(this.setFieldValue(b,"NAME"),this.setTooltip((this.outputConnection?Blockly.Msg.PROCEDURES_CALLRETURN_TOOLTIP:Blockly.Msg.PROCEDURES_CALLNORETURN_TOOLTIP).replace("%1",b)))},setProcedureParameters_:function(a,b){var c=Blockly.Procedures.getDefinition(this.getProcedureCall(),this.workspace),d=c&&c.mutator&&c.mutator.isVisible();d||(this.quarkConnections_={},this.quarkIds_=null);if(b)if(a.join("\n")==this.arguments_.join("\n"))this.quarkIds_=b;else{if(b.length!=a.length)throw RangeError("paramNames and paramIds must be the same length.");
this.setCollapsed(!1);this.quarkIds_||(this.quarkConnections_={},this.quarkIds_=[]);c=this.rendered;this.rendered=!1;for(var e=0;e<this.arguments_.length;e++){var f=this.getInput("ARG"+e);f&&(f=f.connection.targetConnection,this.quarkConnections_[this.quarkIds_[e]]=f,d&&f&&-1==b.indexOf(this.quarkIds_[e])&&(f.disconnect(),f.getSourceBlock().bumpNeighbours()))}this.arguments_=[].concat(a);this.argumentVarModels_=[];for(e=0;e<this.arguments_.length;e++)d=Blockly.Variables.getOrCreateVariablePackage(this.workspace,
null,this.arguments_[e],""),this.argumentVarModels_.push(d);this.updateShape_();if(this.quarkIds_=b)for(e=0;e<this.arguments_.length;e++)d=this.quarkIds_[e],d in this.quarkConnections_&&(f=this.quarkConnections_[d],Blockly.Mutator.reconnect(f,this,"ARG"+e)||delete this.quarkConnections_[d]);(this.rendered=c)&&this.render()}},updateShape_:function(){for(var a=0;a<this.arguments_.length;a++){var b=this.getField("ARGNAME"+a);if(b){Blockly.Events.disable();try{b.setValue(this.arguments_[a])}finally{Blockly.Events.enable()}}else b=
this.getProcedureCall())&&(this.setFieldValue(b,"NAME"),this.setTooltip((this.outputConnection?Blockly.Msg.PROCEDURES_CALLRETURN_TOOLTIP:Blockly.Msg.PROCEDURES_CALLNORETURN_TOOLTIP).replace("%1",b)))},setProcedureParameters_:function(a,b){var c=Blockly.Procedures.getDefinition(this.getProcedureCall(),this.workspace),e=c&&c.mutator&&c.mutator.isVisible();e||(this.quarkConnections_={},this.quarkIds_=null);if(b)if(a.join("\n")==this.arguments_.join("\n"))this.quarkIds_=b;else{if(b.length!=a.length)throw RangeError("paramNames and paramIds must be the same length.");
this.setCollapsed(!1);this.quarkIds_||(this.quarkConnections_={},this.quarkIds_=[]);c=this.rendered;this.rendered=!1;for(var d=0;d<this.arguments_.length;d++){var f=this.getInput("ARG"+d);f&&(f=f.connection.targetConnection,this.quarkConnections_[this.quarkIds_[d]]=f,e&&f&&-1==b.indexOf(this.quarkIds_[d])&&(f.disconnect(),f.getSourceBlock().bumpNeighbours()))}this.arguments_=[].concat(a);this.argumentVarModels_=[];for(d=0;d<this.arguments_.length;d++)a=Blockly.Variables.getOrCreateVariablePackage(this.workspace,
null,this.arguments_[d],""),this.argumentVarModels_.push(a);this.updateShape_();if(this.quarkIds_=b)for(d=0;d<this.arguments_.length;d++)b=this.quarkIds_[d],b in this.quarkConnections_&&(f=this.quarkConnections_[b],Blockly.Mutator.reconnect(f,this,"ARG"+d)||delete this.quarkConnections_[b]);(this.rendered=c)&&this.render()}},updateShape_:function(){for(var a=0;a<this.arguments_.length;a++){var b=this.getField("ARGNAME"+a);if(b){Blockly.Events.disable();try{b.setValue(this.arguments_[a])}finally{Blockly.Events.enable()}}else b=
new Blockly.FieldLabel(this.arguments_[a]),this.appendValueInput("ARG"+a).setAlign(Blockly.ALIGN_RIGHT).appendField(b,"ARGNAME"+a).init()}for(;this.getInput("ARG"+a);)this.removeInput("ARG"+a),a++;if(a=this.getInput("TOPROW"))this.arguments_.length?this.getField("WITH")||(a.appendField(Blockly.Msg.PROCEDURES_CALL_BEFORE_PARAMS,"WITH"),a.init()):this.getField("WITH")&&a.removeField("WITH")},mutationToDom:function(){var a=Blockly.utils.xml.createElement("mutation");a.setAttribute("name",this.getProcedureCall());
for(var b=0;b<this.arguments_.length;b++){var c=Blockly.utils.xml.createElement("arg");c.setAttribute("name",this.arguments_[b]);a.appendChild(c)}return a},domToMutation:function(a){var b=a.getAttribute("name");this.renameProcedure(this.getProcedureCall(),b);b=[];for(var c=[],d=0,e;e=a.childNodes[d];d++)"arg"==e.nodeName.toLowerCase()&&(b.push(e.getAttribute("name")),c.push(e.getAttribute("paramId")));this.setProcedureParameters_(b,c)},getVarModels:function(){return this.argumentVarModels_},onchange:function(a){if(this.workspace&&
!this.workspace.isFlyout&&a.recordUndo)if(a.type==Blockly.Events.BLOCK_CREATE&&-1!=a.ids.indexOf(this.id)){var b=this.getProcedureCall();b=Blockly.Procedures.getDefinition(b,this.workspace);!b||b.type==this.defType_&&JSON.stringify(b.arguments_)==JSON.stringify(this.arguments_)||(b=null);if(!b){Blockly.Events.setGroup(a.group);a=Blockly.utils.xml.createElement("xml");b=Blockly.utils.xml.createElement("block");b.setAttribute("type",this.defType_);var c=this.getRelativeToSurfaceXY(),d=c.y+2*Blockly.SNAP_RADIUS;
b.setAttribute("x",c.x+Blockly.SNAP_RADIUS*(this.RTL?-1:1));b.setAttribute("y",d);c=this.mutationToDom();b.appendChild(c);c=Blockly.utils.xml.createElement("field");c.setAttribute("name","NAME");c.appendChild(Blockly.utils.xml.createTextNode(this.getProcedureCall()));b.appendChild(c);a.appendChild(b);Blockly.Xml.domToWorkspace(a,this.workspace);Blockly.Events.setGroup(!1)}}else a.type==Blockly.Events.BLOCK_DELETE?(b=this.getProcedureCall(),b=Blockly.Procedures.getDefinition(b,this.workspace),b||(Blockly.Events.setGroup(a.group),
this.dispose(!0,!1),Blockly.Events.setGroup(!1))):a.type==Blockly.Events.CHANGE&&"disabled"==a.element&&(b=this.getProcedureCall(),(b=Blockly.Procedures.getDefinition(b,this.workspace))&&b.id==a.blockId&&((b=Blockly.Events.getGroup())&&console.log("Saw an existing group while responding to a definition change"),Blockly.Events.setGroup(a.group),a.newValue?(this.previousEnabledState_=this.isEnabled(),this.setEnabled(!1)):this.setEnabled(this.previousEnabledState_),Blockly.Events.setGroup(b)))},customContextMenu:function(a){if(this.workspace.isMovable()){var b=
{enabled:!0};b.text=Blockly.Msg.PROCEDURES_HIGHLIGHT_DEF;var c=this.getProcedureCall(),d=this.workspace;b.callback=function(){var a=Blockly.Procedures.getDefinition(c,d);a&&(d.centerOnBlock(a.id),a.select())};a.push(b)}},defType_:"procedures_defnoreturn"};
for(var b=0;b<this.arguments_.length;b++){var c=Blockly.utils.xml.createElement("arg");c.setAttribute("name",this.arguments_[b]);a.appendChild(c)}return a},domToMutation:function(a){var b=a.getAttribute("name");this.renameProcedure(this.getProcedureCall(),b);b=[];for(var c=[],e=0,d;d=a.childNodes[e];e++)"arg"==d.nodeName.toLowerCase()&&(b.push(d.getAttribute("name")),c.push(d.getAttribute("paramId")));this.setProcedureParameters_(b,c)},getVarModels:function(){return this.argumentVarModels_},onchange:function(a){if(this.workspace&&
!this.workspace.isFlyout&&a.recordUndo)if(a.type==Blockly.Events.BLOCK_CREATE&&-1!=a.ids.indexOf(this.id)){var b=this.getProcedureCall();b=Blockly.Procedures.getDefinition(b,this.workspace);!b||b.type==this.defType_&&JSON.stringify(b.arguments_)==JSON.stringify(this.arguments_)||(b=null);if(!b){Blockly.Events.setGroup(a.group);a=Blockly.utils.xml.createElement("xml");b=Blockly.utils.xml.createElement("block");b.setAttribute("type",this.defType_);var c=this.getRelativeToSurfaceXY(),e=c.y+2*Blockly.SNAP_RADIUS;
b.setAttribute("x",c.x+Blockly.SNAP_RADIUS*(this.RTL?-1:1));b.setAttribute("y",e);c=this.mutationToDom();b.appendChild(c);c=Blockly.utils.xml.createElement("field");c.setAttribute("name","NAME");c.appendChild(Blockly.utils.xml.createTextNode(this.getProcedureCall()));b.appendChild(c);a.appendChild(b);Blockly.Xml.domToWorkspace(a,this.workspace);Blockly.Events.setGroup(!1)}}else a.type==Blockly.Events.BLOCK_DELETE?(b=this.getProcedureCall(),b=Blockly.Procedures.getDefinition(b,this.workspace),b||(Blockly.Events.setGroup(a.group),
this.dispose(!0),Blockly.Events.setGroup(!1))):a.type==Blockly.Events.CHANGE&&"disabled"==a.element&&(b=this.getProcedureCall(),(b=Blockly.Procedures.getDefinition(b,this.workspace))&&b.id==a.blockId&&((b=Blockly.Events.getGroup())&&console.log("Saw an existing group while responding to a definition change"),Blockly.Events.setGroup(a.group),a.newValue?(this.previousEnabledState_=this.isEnabled(),this.setEnabled(!1)):this.setEnabled(this.previousEnabledState_),Blockly.Events.setGroup(b)))},customContextMenu:function(a){if(this.workspace.isMovable()){var b=
{enabled:!0};b.text=Blockly.Msg.PROCEDURES_HIGHLIGHT_DEF;var c=this.getProcedureCall(),e=this.workspace;b.callback=function(){var a=Blockly.Procedures.getDefinition(c,e);a&&(e.centerOnBlock(a.id),a.select())};a.push(b)}},defType_:"procedures_defnoreturn"};
Blockly.Blocks.procedures_callreturn={init:function(){this.appendDummyInput("TOPROW").appendField("","NAME");this.setOutput(!0);this.setStyle("procedure_blocks");this.setHelpUrl(Blockly.Msg.PROCEDURES_CALLRETURN_HELPURL);this.arguments_=[];this.quarkConnections_={};this.quarkIds_=null;this.previousEnabledState_=!0},getProcedureCall:Blockly.Blocks.procedures_callnoreturn.getProcedureCall,renameProcedure:Blockly.Blocks.procedures_callnoreturn.renameProcedure,setProcedureParameters_:Blockly.Blocks.procedures_callnoreturn.setProcedureParameters_,
updateShape_:Blockly.Blocks.procedures_callnoreturn.updateShape_,mutationToDom:Blockly.Blocks.procedures_callnoreturn.mutationToDom,domToMutation:Blockly.Blocks.procedures_callnoreturn.domToMutation,getVarModels:Blockly.Blocks.procedures_callnoreturn.getVarModels,onchange:Blockly.Blocks.procedures_callnoreturn.onchange,customContextMenu:Blockly.Blocks.procedures_callnoreturn.customContextMenu,defType_:"procedures_defreturn"};
Blockly.Blocks.procedures_ifreturn={init:function(){this.appendValueInput("CONDITION").setCheck("Boolean").appendField(Blockly.Msg.CONTROLS_IF_MSG_IF);this.appendValueInput("VALUE").appendField(Blockly.Msg.PROCEDURES_DEFRETURN_RETURN);this.setInputsInline(!0);this.setPreviousStatement(!0);this.setNextStatement(!0);this.setStyle("procedure_blocks");this.setTooltip(Blockly.Msg.PROCEDURES_IFRETURN_TOOLTIP);this.setHelpUrl(Blockly.Msg.PROCEDURES_IFRETURN_HELPURL);this.hasReturnValue_=!0},mutationToDom:function(){var a=
@@ -135,7 +133,7 @@ args0:[{type:"input_value",name:"VALUE",check:"String"},{type:"field_dropdown",n
Blockly.Blocks.text_getSubstring={init:function(){this.WHERE_OPTIONS_1=[[Blockly.Msg.TEXT_GET_SUBSTRING_START_FROM_START,"FROM_START"],[Blockly.Msg.TEXT_GET_SUBSTRING_START_FROM_END,"FROM_END"],[Blockly.Msg.TEXT_GET_SUBSTRING_START_FIRST,"FIRST"]];this.WHERE_OPTIONS_2=[[Blockly.Msg.TEXT_GET_SUBSTRING_END_FROM_START,"FROM_START"],[Blockly.Msg.TEXT_GET_SUBSTRING_END_FROM_END,"FROM_END"],[Blockly.Msg.TEXT_GET_SUBSTRING_END_LAST,"LAST"]];this.setHelpUrl(Blockly.Msg.TEXT_GET_SUBSTRING_HELPURL);this.setStyle("text_blocks");
this.appendValueInput("STRING").setCheck("String").appendField(Blockly.Msg.TEXT_GET_SUBSTRING_INPUT_IN_TEXT);this.appendDummyInput("AT1");this.appendDummyInput("AT2");Blockly.Msg.TEXT_GET_SUBSTRING_TAIL&&this.appendDummyInput("TAIL").appendField(Blockly.Msg.TEXT_GET_SUBSTRING_TAIL);this.setInputsInline(!0);this.setOutput(!0,"String");this.updateAt_(1,!0);this.updateAt_(2,!0);this.setTooltip(Blockly.Msg.TEXT_GET_SUBSTRING_TOOLTIP)},mutationToDom:function(){var a=Blockly.utils.xml.createElement("mutation"),
b=this.getInput("AT1").type==Blockly.INPUT_VALUE;a.setAttribute("at1",b);b=this.getInput("AT2").type==Blockly.INPUT_VALUE;a.setAttribute("at2",b);return a},domToMutation:function(a){var b="true"==a.getAttribute("at1");a="true"==a.getAttribute("at2");this.updateAt_(1,b);this.updateAt_(2,a)},updateAt_:function(a,b){this.removeInput("AT"+a);this.removeInput("ORDINAL"+a,!0);b?(this.appendValueInput("AT"+a).setCheck("Number"),Blockly.Msg.ORDINAL_NUMBER_SUFFIX&&this.appendDummyInput("ORDINAL"+a).appendField(Blockly.Msg.ORDINAL_NUMBER_SUFFIX)):
this.appendDummyInput("AT"+a);2==a&&Blockly.Msg.TEXT_GET_SUBSTRING_TAIL&&(this.removeInput("TAIL",!0),this.appendDummyInput("TAIL").appendField(Blockly.Msg.TEXT_GET_SUBSTRING_TAIL));var c=new Blockly.FieldDropdown(this["WHERE_OPTIONS_"+a],function(c){var d="FROM_START"==c||"FROM_END"==c;if(d!=b){var f=this.getSourceBlock();f.updateAt_(a,d);f.setFieldValue(c,"WHERE"+a);return null}});this.getInput("AT"+a).appendField(c,"WHERE"+a);1==a&&(this.moveInputBefore("AT1","AT2"),this.getInput("ORDINAL1")&&
this.appendDummyInput("AT"+a);2==a&&Blockly.Msg.TEXT_GET_SUBSTRING_TAIL&&(this.removeInput("TAIL",!0),this.appendDummyInput("TAIL").appendField(Blockly.Msg.TEXT_GET_SUBSTRING_TAIL));var c=new Blockly.FieldDropdown(this["WHERE_OPTIONS_"+a],function(c){var d="FROM_START"==c||"FROM_END"==c;if(d!=b){var e=this.getSourceBlock();e.updateAt_(a,d);e.setFieldValue(c,"WHERE"+a);return null}});this.getInput("AT"+a).appendField(c,"WHERE"+a);1==a&&(this.moveInputBefore("AT1","AT2"),this.getInput("ORDINAL1")&&
this.moveInputBefore("ORDINAL1","AT2"))}};Blockly.Blocks.text_changeCase={init:function(){var a=[[Blockly.Msg.TEXT_CHANGECASE_OPERATOR_UPPERCASE,"UPPERCASE"],[Blockly.Msg.TEXT_CHANGECASE_OPERATOR_LOWERCASE,"LOWERCASE"],[Blockly.Msg.TEXT_CHANGECASE_OPERATOR_TITLECASE,"TITLECASE"]];this.setHelpUrl(Blockly.Msg.TEXT_CHANGECASE_HELPURL);this.setStyle("text_blocks");this.appendValueInput("TEXT").setCheck("String").appendField(new Blockly.FieldDropdown(a),"CASE");this.setOutput(!0,"String");this.setTooltip(Blockly.Msg.TEXT_CHANGECASE_TOOLTIP)}};
Blockly.Blocks.text_trim={init:function(){var a=[[Blockly.Msg.TEXT_TRIM_OPERATOR_BOTH,"BOTH"],[Blockly.Msg.TEXT_TRIM_OPERATOR_LEFT,"LEFT"],[Blockly.Msg.TEXT_TRIM_OPERATOR_RIGHT,"RIGHT"]];this.setHelpUrl(Blockly.Msg.TEXT_TRIM_HELPURL);this.setStyle("text_blocks");this.appendValueInput("TEXT").setCheck("String").appendField(new Blockly.FieldDropdown(a),"MODE");this.setOutput(!0,"String");this.setTooltip(Blockly.Msg.TEXT_TRIM_TOOLTIP)}};
Blockly.Blocks.text_print={init:function(){this.jsonInit({message0:Blockly.Msg.TEXT_PRINT_TITLE,args0:[{type:"input_value",name:"TEXT"}],previousStatement:null,nextStatement:null,style:"text_blocks",tooltip:Blockly.Msg.TEXT_PRINT_TOOLTIP,helpUrl:Blockly.Msg.TEXT_PRINT_HELPURL})}};
@@ -147,10 +145,10 @@ Blockly.Blocks.text_count={init:function(){this.jsonInit({message0:Blockly.Msg.T
Blockly.Blocks.text_replace={init:function(){this.jsonInit({message0:Blockly.Msg.TEXT_REPLACE_MESSAGE0,args0:[{type:"input_value",name:"FROM",check:"String"},{type:"input_value",name:"TO",check:"String"},{type:"input_value",name:"TEXT",check:"String"}],output:"String",inputsInline:!0,style:"text_blocks",tooltip:Blockly.Msg.TEXT_REPLACE_TOOLTIP,helpUrl:Blockly.Msg.TEXT_REPLACE_HELPURL})}};
Blockly.Blocks.text_reverse={init:function(){this.jsonInit({message0:Blockly.Msg.TEXT_REVERSE_MESSAGE0,args0:[{type:"input_value",name:"TEXT",check:"String"}],output:"String",inputsInline:!0,style:"text_blocks",tooltip:Blockly.Msg.TEXT_REVERSE_TOOLTIP,helpUrl:Blockly.Msg.TEXT_REVERSE_HELPURL})}};
Blockly.Constants.Text.QUOTE_IMAGE_MIXIN={QUOTE_IMAGE_LEFT_DATAURI:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAwAAAAKCAQAAAAqJXdxAAAAn0lEQVQI1z3OMa5BURSF4f/cQhAKjUQhuQmFNwGJEUi0RKN5rU7FHKhpjEH3TEMtkdBSCY1EIv8r7nFX9e29V7EBAOvu7RPjwmWGH/VuF8CyN9/OAdvqIXYLvtRaNjx9mMTDyo+NjAN1HNcl9ZQ5oQMM3dgDUqDo1l8DzvwmtZN7mnD+PkmLa+4mhrxVA9fRowBWmVBhFy5gYEjKMfz9AylsaRRgGzvZAAAAAElFTkSuQmCC",QUOTE_IMAGE_RIGHT_DATAURI:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAwAAAAKCAQAAAAqJXdxAAAAqUlEQVQI1z3KvUpCcRiA8ef9E4JNHhI0aFEacm1o0BsI0Slx8wa8gLauoDnoBhq7DcfWhggONDmJJgqCPA7neJ7p934EOOKOnM8Q7PDElo/4x4lFb2DmuUjcUzS3URnGib9qaPNbuXvBO3sGPHJDRG6fGVdMSeWDP2q99FQdFrz26Gu5Tq7dFMzUvbXy8KXeAj57cOklgA+u1B5AoslLtGIHQMaCVnwDnADZIFIrXsoXrgAAAABJRU5ErkJggg==",
QUOTE_IMAGE_WIDTH:12,QUOTE_IMAGE_HEIGHT:12,quoteField_:function(a){for(var b=0,c;c=this.inputList[b];b++)for(var d=0,e;e=c.fieldRow[d];d++)if(a==e.name){c.insertFieldAt(d,this.newQuote_(!0));c.insertFieldAt(d+2,this.newQuote_(!1));return}console.warn('field named "'+a+'" not found in '+this.toDevString())},newQuote_:function(a){a=this.RTL?!a:a;return new Blockly.FieldImage(a?this.QUOTE_IMAGE_LEFT_DATAURI:this.QUOTE_IMAGE_RIGHT_DATAURI,this.QUOTE_IMAGE_WIDTH,this.QUOTE_IMAGE_HEIGHT,a?"\u201c":"\u201d")}};
QUOTE_IMAGE_WIDTH:12,QUOTE_IMAGE_HEIGHT:12,quoteField_:function(a){for(var b=0,c;c=this.inputList[b];b++)for(var e=0,d;d=c.fieldRow[e];e++)if(a==d.name){c.insertFieldAt(e,this.newQuote_(!0));c.insertFieldAt(e+2,this.newQuote_(!1));return}console.warn('field named "'+a+'" not found in '+this.toDevString())},newQuote_:function(a){a=this.RTL?!a:a;return new Blockly.FieldImage(a?this.QUOTE_IMAGE_LEFT_DATAURI:this.QUOTE_IMAGE_RIGHT_DATAURI,this.QUOTE_IMAGE_WIDTH,this.QUOTE_IMAGE_HEIGHT,a?"\u201c":"\u201d")}};
Blockly.Constants.Text.TEXT_QUOTES_EXTENSION=function(){this.mixin(Blockly.Constants.Text.QUOTE_IMAGE_MIXIN);this.quoteField_("TEXT")};
Blockly.Constants.Text.TEXT_JOIN_MUTATOR_MIXIN={mutationToDom:function(){var a=Blockly.utils.xml.createElement("mutation");a.setAttribute("items",this.itemCount_);return a},domToMutation:function(a){this.itemCount_=parseInt(a.getAttribute("items"),10);this.updateShape_()},decompose:function(a){var b=a.newBlock("text_create_join_container");b.initSvg();for(var c=b.getInput("STACK").connection,d=0;d<this.itemCount_;d++){var e=a.newBlock("text_create_join_item");e.initSvg();c.connect(e.previousConnection);
c=e.nextConnection}return b},compose:function(a){var b=a.getInputTargetBlock("STACK");for(a=[];b;)a.push(b.valueConnection_),b=b.nextConnection&&b.nextConnection.targetBlock();for(b=0;b<this.itemCount_;b++){var c=this.getInput("ADD"+b).connection.targetConnection;c&&-1==a.indexOf(c)&&c.disconnect()}this.itemCount_=a.length;this.updateShape_();for(b=0;b<this.itemCount_;b++)Blockly.Mutator.reconnect(a[b],this,"ADD"+b)},saveConnections:function(a){a=a.getInputTargetBlock("STACK");for(var b=0;a;){var c=
Blockly.Constants.Text.TEXT_JOIN_MUTATOR_MIXIN={mutationToDom:function(){var a=Blockly.utils.xml.createElement("mutation");a.setAttribute("items",this.itemCount_);return a},domToMutation:function(a){this.itemCount_=parseInt(a.getAttribute("items"),10);this.updateShape_()},decompose:function(a){var b=a.newBlock("text_create_join_container");b.initSvg();for(var c=b.getInput("STACK").connection,e=0;e<this.itemCount_;e++){var d=a.newBlock("text_create_join_item");d.initSvg();c.connect(d.previousConnection);
c=d.nextConnection}return b},compose:function(a){var b=a.getInputTargetBlock("STACK");for(a=[];b;)a.push(b.valueConnection_),b=b.nextConnection&&b.nextConnection.targetBlock();for(b=0;b<this.itemCount_;b++){var c=this.getInput("ADD"+b).connection.targetConnection;c&&-1==a.indexOf(c)&&c.disconnect()}this.itemCount_=a.length;this.updateShape_();for(b=0;b<this.itemCount_;b++)Blockly.Mutator.reconnect(a[b],this,"ADD"+b)},saveConnections:function(a){a=a.getInputTargetBlock("STACK");for(var b=0;a;){var c=
this.getInput("ADD"+b);a.valueConnection_=c&&c.connection.targetConnection;b++;a=a.nextConnection&&a.nextConnection.targetBlock()}},updateShape_:function(){this.itemCount_&&this.getInput("EMPTY")?this.removeInput("EMPTY"):this.itemCount_||this.getInput("EMPTY")||this.appendDummyInput("EMPTY").appendField(this.newQuote_(!0)).appendField(this.newQuote_(!1));for(var a=0;a<this.itemCount_;a++)if(!this.getInput("ADD"+a)){var b=this.appendValueInput("ADD"+a);0==a&&b.appendField(Blockly.Msg.TEXT_JOIN_TITLE_CREATEWITH)}for(;this.getInput("ADD"+
a);)this.removeInput("ADD"+a),a++}};Blockly.Constants.Text.TEXT_JOIN_EXTENSION=function(){this.mixin(Blockly.Constants.Text.QUOTE_IMAGE_MIXIN);this.itemCount_=2;this.updateShape_();this.setMutator(new Blockly.Mutator(["text_create_join_item"]))};Blockly.Extensions.register("text_append_tooltip",Blockly.Extensions.buildTooltipWithFieldText("%{BKY_TEXT_APPEND_TOOLTIP}","VAR"));
Blockly.Constants.Text.TEXT_INDEXOF_TOOLTIP_EXTENSION=function(){var a=this;this.setTooltip(function(){return Blockly.Msg.TEXT_INDEXOF_TOOLTIP.replace("%1",a.workspace.options.oneBasedIndex?"0":"-1")})};
@@ -160,13 +158,12 @@ Blockly.Constants.Text.TEXT_CHARAT_EXTENSION=function(){this.getField("WHERE").s
"#1":"#0"));return c})};Blockly.Extensions.register("text_indexOf_tooltip",Blockly.Constants.Text.TEXT_INDEXOF_TOOLTIP_EXTENSION);Blockly.Extensions.register("text_quotes",Blockly.Constants.Text.TEXT_QUOTES_EXTENSION);Blockly.Extensions.registerMutator("text_join_mutator",Blockly.Constants.Text.TEXT_JOIN_MUTATOR_MIXIN,Blockly.Constants.Text.TEXT_JOIN_EXTENSION);Blockly.Extensions.registerMutator("text_charAt_mutator",Blockly.Constants.Text.TEXT_CHARAT_MUTATOR_MIXIN,Blockly.Constants.Text.TEXT_CHARAT_EXTENSION);Blockly.Blocks.variables={};Blockly.Constants.Variables={};Blockly.Constants.Variables.HUE=330;
Blockly.defineBlocksWithJsonArray([{type:"variables_get",message0:"%1",args0:[{type:"field_variable",name:"VAR",variable:"%{BKY_VARIABLES_DEFAULT_NAME}"}],output:null,style:"variable_blocks",helpUrl:"%{BKY_VARIABLES_GET_HELPURL}",tooltip:"%{BKY_VARIABLES_GET_TOOLTIP}",extensions:["contextMenu_variableSetterGetter"]},{type:"variables_set",message0:"%{BKY_VARIABLES_SET}",args0:[{type:"field_variable",name:"VAR",variable:"%{BKY_VARIABLES_DEFAULT_NAME}"},{type:"input_value",name:"VALUE"}],previousStatement:null,
nextStatement:null,style:"variable_blocks",tooltip:"%{BKY_VARIABLES_SET_TOOLTIP}",helpUrl:"%{BKY_VARIABLES_SET_HELPURL}",extensions:["contextMenu_variableSetterGetter"]}]);
Blockly.Constants.Variables.CUSTOM_CONTEXT_MENU_VARIABLE_GETTER_SETTER_MIXIN={customContextMenu:function(a){if(!this.isInFlyout){if("variables_get"==this.type)var b="variables_set",c=Blockly.Msg.VARIABLES_GET_CREATE_SET;else b="variables_get",c=Blockly.Msg.VARIABLES_SET_CREATE_GET;var d={enabled:0<this.workspace.remainingCapacity()},e=this.getField("VAR").getText();d.text=c.replace("%1",e);c=Blockly.utils.xml.createElement("field");c.setAttribute("name","VAR");c.appendChild(Blockly.utils.xml.createTextNode(e));
e=Blockly.utils.xml.createElement("block");e.setAttribute("type",b);e.appendChild(c);d.callback=Blockly.ContextMenu.callbackFactory(this,e);a.push(d)}else if("variables_get"==this.type||"variables_get_reporter"==this.type)b={text:Blockly.Msg.RENAME_VARIABLE,enabled:!0,callback:Blockly.Constants.Variables.RENAME_OPTION_CALLBACK_FACTORY(this)},e=this.getField("VAR").getText(),d={text:Blockly.Msg.DELETE_VARIABLE.replace("%1",e),enabled:!0,callback:Blockly.Constants.Variables.DELETE_OPTION_CALLBACK_FACTORY(this)},
a.unshift(b),a.unshift(d)}};Blockly.Constants.Variables.RENAME_OPTION_CALLBACK_FACTORY=function(a){return function(){var b=a.workspace,c=a.getField("VAR").getVariable();Blockly.Variables.renameVariable(b,c)}};Blockly.Constants.Variables.DELETE_OPTION_CALLBACK_FACTORY=function(a){return function(){var b=a.workspace,c=a.getField("VAR").getVariable();b.deleteVariableById(c.getId());b.refreshToolboxSelection()}};Blockly.Extensions.registerMixin("contextMenu_variableSetterGetter",Blockly.Constants.Variables.CUSTOM_CONTEXT_MENU_VARIABLE_GETTER_SETTER_MIXIN);
Blockly.Constants.VariablesDynamic={};Blockly.Constants.VariablesDynamic.HUE=310;
Blockly.Constants.Variables.CUSTOM_CONTEXT_MENU_VARIABLE_GETTER_SETTER_MIXIN={customContextMenu:function(a){if(!this.isInFlyout){if("variables_get"==this.type)var b="variables_set",c=Blockly.Msg.VARIABLES_GET_CREATE_SET;else b="variables_get",c=Blockly.Msg.VARIABLES_SET_CREATE_GET;var e={enabled:0<this.workspace.remainingCapacity()},d=this.getField("VAR").getText();e.text=c.replace("%1",d);c=Blockly.utils.xml.createElement("field");c.setAttribute("name","VAR");c.appendChild(Blockly.utils.xml.createTextNode(d));
d=Blockly.utils.xml.createElement("block");d.setAttribute("type",b);d.appendChild(c);e.callback=Blockly.ContextMenu.callbackFactory(this,d);a.push(e)}else if("variables_get"==this.type||"variables_get_reporter"==this.type)b={text:Blockly.Msg.RENAME_VARIABLE,enabled:!0,callback:Blockly.Constants.Variables.RENAME_OPTION_CALLBACK_FACTORY(this)},d=this.getField("VAR").getText(),e={text:Blockly.Msg.DELETE_VARIABLE.replace("%1",d),enabled:!0,callback:Blockly.Constants.Variables.DELETE_OPTION_CALLBACK_FACTORY(this)},
a.unshift(b),a.unshift(e)}};Blockly.Constants.Variables.RENAME_OPTION_CALLBACK_FACTORY=function(a){return function(){var b=a.workspace,c=a.getField("VAR").getVariable();Blockly.Variables.renameVariable(b,c)}};Blockly.Constants.Variables.DELETE_OPTION_CALLBACK_FACTORY=function(a){return function(){var b=a.workspace,c=a.getField("VAR").getVariable();b.deleteVariableById(c.getId());b.refreshToolboxSelection()}};Blockly.Extensions.registerMixin("contextMenu_variableSetterGetter",Blockly.Constants.Variables.CUSTOM_CONTEXT_MENU_VARIABLE_GETTER_SETTER_MIXIN);Blockly.Constants.VariablesDynamic={};Blockly.Constants.VariablesDynamic.HUE=310;
Blockly.defineBlocksWithJsonArray([{type:"variables_get_dynamic",message0:"%1",args0:[{type:"field_variable",name:"VAR",variable:"%{BKY_VARIABLES_DEFAULT_NAME}"}],output:null,style:"variable_dynamic_blocks",helpUrl:"%{BKY_VARIABLES_GET_HELPURL}",tooltip:"%{BKY_VARIABLES_GET_TOOLTIP}",extensions:["contextMenu_variableDynamicSetterGetter"]},{type:"variables_set_dynamic",message0:"%{BKY_VARIABLES_SET}",args0:[{type:"field_variable",name:"VAR",variable:"%{BKY_VARIABLES_DEFAULT_NAME}"},{type:"input_value",
name:"VALUE"}],previousStatement:null,nextStatement:null,style:"variable_dynamic_blocks",tooltip:"%{BKY_VARIABLES_SET_TOOLTIP}",helpUrl:"%{BKY_VARIABLES_SET_HELPURL}",extensions:["contextMenu_variableDynamicSetterGetter"]}]);
Blockly.Constants.VariablesDynamic.CUSTOM_CONTEXT_MENU_VARIABLE_GETTER_SETTER_MIXIN={customContextMenu:function(a){if(!this.isInFlyout){var b=this.getFieldValue("VAR");var c=this.workspace.getVariableById(b).type;if("variables_get_dynamic"==this.type){b="variables_set_dynamic";var d=Blockly.Msg.VARIABLES_GET_CREATE_SET}else b="variables_get_dynamic",d=Blockly.Msg.VARIABLES_SET_CREATE_GET;var e={enabled:0<this.workspace.remainingCapacity()},f=this.getField("VAR").getText();e.text=d.replace("%1",f);
d=Blockly.utils.xml.createElement("field");d.setAttribute("name","VAR");d.setAttribute("variabletype",c);d.appendChild(Blockly.utils.xml.createTextNode(f));f=Blockly.utils.xml.createElement("block");f.setAttribute("type",b);f.appendChild(d);e.callback=Blockly.ContextMenu.callbackFactory(this,f);a.push(e)}else if("variables_get_dynamic"==this.type||"variables_get_reporter_dynamic"==this.type)b={text:Blockly.Msg.RENAME_VARIABLE,enabled:!0,callback:Blockly.Constants.Variables.RENAME_OPTION_CALLBACK_FACTORY(this)},
f=this.getField("VAR").getText(),e={text:Blockly.Msg.DELETE_VARIABLE.replace("%1",f),enabled:!0,callback:Blockly.Constants.Variables.DELETE_OPTION_CALLBACK_FACTORY(this)},a.unshift(b),a.unshift(e)},onchange:function(a){a=this.getFieldValue("VAR");a=Blockly.Variables.getVariable(this.workspace,a);"variables_get_dynamic"==this.type?this.outputConnection.setCheck(a.type):this.getInput("VALUE").connection.setCheck(a.type)}};
Blockly.Constants.VariablesDynamic.RENAME_OPTION_CALLBACK_FACTORY=function(a){return function(){var b=a.workspace,c=a.getField("VAR").getVariable();Blockly.Variables.renameVariable(b,c)}};Blockly.Constants.VariablesDynamic.DELETE_OPTION_CALLBACK_FACTORY=function(a){return function(){var b=a.workspace,c=a.getField("VAR").getVariable();b.deleteVariableById(c.getId());b.refreshToolboxSelection()}};Blockly.Extensions.registerMixin("contextMenu_variableDynamicSetterGetter",Blockly.Constants.VariablesDynamic.CUSTOM_CONTEXT_MENU_VARIABLE_GETTER_SETTER_MIXIN);
Blockly.Constants.VariablesDynamic.CUSTOM_CONTEXT_MENU_VARIABLE_GETTER_SETTER_MIXIN={customContextMenu:function(a){if(!this.isInFlyout){var b=this.getFieldValue("VAR");var c=this.workspace.getVariableById(b).type;if("variables_get_dynamic"==this.type){b="variables_set_dynamic";var e=Blockly.Msg.VARIABLES_GET_CREATE_SET}else b="variables_get_dynamic",e=Blockly.Msg.VARIABLES_SET_CREATE_GET;var d={enabled:0<this.workspace.remainingCapacity()},f=this.getField("VAR").getText();d.text=e.replace("%1",f);
e=Blockly.utils.xml.createElement("field");e.setAttribute("name","VAR");e.setAttribute("variabletype",c);e.appendChild(Blockly.utils.xml.createTextNode(f));f=Blockly.utils.xml.createElement("block");f.setAttribute("type",b);f.appendChild(e);d.callback=Blockly.ContextMenu.callbackFactory(this,f);a.push(d)}else if("variables_get_dynamic"==this.type||"variables_get_reporter_dynamic"==this.type)b={text:Blockly.Msg.RENAME_VARIABLE,enabled:!0,callback:Blockly.Constants.Variables.RENAME_OPTION_CALLBACK_FACTORY(this)},
f=this.getField("VAR").getText(),d={text:Blockly.Msg.DELETE_VARIABLE.replace("%1",f),enabled:!0,callback:Blockly.Constants.Variables.DELETE_OPTION_CALLBACK_FACTORY(this)},a.unshift(b),a.unshift(d)},onchange:function(a){a=this.getFieldValue("VAR");a=Blockly.Variables.getVariable(this.workspace,a);"variables_get_dynamic"==this.type?this.outputConnection.setCheck(a.type):this.getInput("VALUE").connection.setCheck(a.type)}};
Blockly.Constants.VariablesDynamic.RENAME_OPTION_CALLBACK_FACTORY=function(a){return function(){var b=a.workspace,c=a.getField("VAR").getVariable();Blockly.Variables.renameVariable(b,c)}};Blockly.Constants.VariablesDynamic.DELETE_OPTION_CALLBACK_FACTORY=function(a){return function(){var b=a.workspace,c=a.getField("VAR").getVariable();b.deleteVariableById(c.getId());b.refreshToolboxSelection()}};Blockly.Extensions.registerMixin("contextMenu_variableDynamicSetterGetter",Blockly.Constants.VariablesDynamic.CUSTOM_CONTEXT_MENU_VARIABLE_GETTER_SETTER_MIXIN);

View File

@@ -202,6 +202,8 @@ class Gen_compressed(threading.Thread):
if filename == "core/blockly.js":
code = code.replace("Blockly.VERSION = 'uncompiled';",
"Blockly.VERSION = '%s';" % blocklyVersion)
# Strip out all requireType calls.
code = re.sub(r"goog.requireType(.*)", "", code)
params.append(("js_code", code.encode("utf-8")))
f.close()
@@ -235,6 +237,7 @@ goog.provide('Blockly.FieldNumber');
goog.provide('Blockly.FieldTextInput');
goog.provide('Blockly.FieldVariable');
goog.provide('Blockly.Mutator');
goog.provide('Blockly.Warning');
"""))
# Read in all the source files.
filenames = glob.glob(os.path.join("blocks", "*.js"))
@@ -266,6 +269,7 @@ goog.provide('Blockly.Mutator');
# with the compiler.
params.append(("js_code", """
goog.provide('Blockly.Generator');
goog.provide('Blockly.utils.global');
goog.provide('Blockly.utils.string');
"""))
filenames = glob.glob(
@@ -280,7 +284,7 @@ goog.provide('Blockly.utils.string');
# Remove Blockly.Generator and Blockly.utils.string to be compatible
# with Blockly.
remove = r"var Blockly=\{[^;]*\};\s*Blockly.utils.string={};\n?"
remove = r"var Blockly=\{[^;]*\};\s*Blockly.utils.global={};\s*Blockly.utils.string={};\n?"
self.do_compile(params, target_filename, filenames, remove)
def do_compile(self, params, target_filename, filenames, remove):

View File

@@ -250,6 +250,28 @@ goog.require = function(namespace) {
return null;
};
/**
* Requires a symbol for its type information. This is an indication to the
* compiler that the symbol may appear in type annotations, yet it is not
* referenced at runtime.
*
* When called within a goog.module or ES6 module file, the return value may be
* assigned to or destructured into a variable, but it may not be otherwise used
* in code outside of a type annotation.
*
* Note that all calls to goog.requireType will be stripped by the compiler.
*
* @param {string} namespace Namespace (as was given in goog.provide,
* goog.module, or goog.declareModuleId) in the form
* "goog.package.part".
* @return {?}
*/
goog.requireType = function(namespace) {
// Return an empty object so that single-level destructuring of the return
// value doesn't crash at runtime when using the debug loader. Multi-level
// destructuring isn't supported.
return {};
};
/**
* Path for included scripts.

View File

@@ -31,12 +31,12 @@ goog.require('Blockly.Events.BlockCreate');
goog.require('Blockly.Events.BlockDelete');
goog.require('Blockly.Events.BlockMove');
goog.require('Blockly.Extensions');
goog.require('Blockly.fieldRegistry');
goog.require('Blockly.Input');
goog.require('Blockly.navigation');
goog.require('Blockly.utils');
goog.require('Blockly.utils.Coordinate');
goog.require('Blockly.utils.colour');
goog.require('Blockly.utils.object');
goog.require('Blockly.fieldRegistry');
goog.require('Blockly.utils.string');
goog.require('Blockly.Workspace');
@@ -63,7 +63,7 @@ Blockly.Block = function(workspace, prototypeName, opt_id) {
/** @type {string} */
this.id = (opt_id && !workspace.getBlockById(opt_id)) ?
opt_id : Blockly.utils.genUid();
workspace.blockDB_[this.id] = this;
workspace.setBlockById(this.id, this);
/** @type {Blockly.Connection} */
this.outputConnection = null;
/** @type {Blockly.Connection} */
@@ -126,6 +126,12 @@ Blockly.Block = function(workspace, prototypeName, opt_id) {
*/
this.collapsed_ = false;
/**
* @type {?number}
* @protected
*/
this.outputShape_ = null;
/**
* A string representing the comment attached to this block.
* @type {string|Blockly.Comment}
@@ -252,33 +258,31 @@ Blockly.Block.prototype.hue_ = null;
/**
* Colour of the block in '#RRGGBB' format.
* @type {string}
* @private
* @protected
*/
Blockly.Block.prototype.colour_ = '#000000';
/**
* Secondary colour of the block.
* Colour of shadow blocks.
* @type {?string}
* @private
*/
Blockly.Block.prototype.colourSecondary_ = null;
/**
* Tertiary colour of the block.
* Colour of the block's border.
* @type {?string}
* @private
*/
Blockly.Block.prototype.colourTertiary_ = null;
/**
* Name of the block style.
* @type {?string}
* @private
* @protected
*/
Blockly.Block.prototype.styleName_ = null;
/**
* An optional method called during initialization.
* @type {?function()}
*/
Blockly.Block.prototype.init;
/**
* An optional callback method to use whenever the block's parent workspace
* changes. This is usually only called from the constructor, the block type
* initializer function, or an extension initializer function.
* @type {?function(Blockly.Events.Abstract)}
*/
Blockly.Block.prototype.onchange;
/**
* An optional serialization method for defining how to serialize the
* mutation state. This must be coupled with defining `domToMutation`.
@@ -300,11 +304,20 @@ Blockly.Block.prototype.domToMutation;
*/
Blockly.Block.prototype.suppressPrefixSuffix;
/**
* An optional property for declaring developer variables. Return a list of
* variable names for use by generators. Developer variables are never shown to
* the user, but are declared as global variables in the generated code.
* @type {?function():!Array.<string>}
*/
Blockly.Block.prototype.getDeveloperVariables;
/**
* Dispose of this block.
* @param {boolean} healStack If true, then try to heal any gap by connecting
* the next statement with the previous statement. Otherwise, dispose of
* all children of this block.
* @suppress {checkTypes}
*/
Blockly.Block.prototype.dispose = function(healStack) {
if (!this.workspace) {
@@ -316,11 +329,6 @@ Blockly.Block.prototype.dispose = function(healStack) {
this.workspace.removeChangeListener(this.onchangeWrapper_);
}
if (Blockly.keyboardAccessibilityMode) {
// No-op if this is called from the block_svg class.
Blockly.navigation.moveCursorOnBlockDelete(this);
}
this.unplug(healStack);
if (Blockly.Events.isEnabled()) {
Blockly.Events.fire(new Blockly.Events.BlockDelete(this));
@@ -334,7 +342,7 @@ Blockly.Block.prototype.dispose = function(healStack) {
this.workspace.removeTopBlock(this);
this.workspace.removeTypedBlock(this);
// Remove from block database.
delete this.workspace.blockDB_[this.id];
this.workspace.removeBlockById(this.id);
this.workspace = null;
}
@@ -352,13 +360,13 @@ Blockly.Block.prototype.dispose = function(healStack) {
}
// Then dispose of myself.
// Dispose of all inputs and their fields.
for (var i = 0, input; input = this.inputList[i]; i++) {
for (var i = 0, input; (input = this.inputList[i]); i++) {
input.dispose();
}
this.inputList.length = 0;
// Dispose of any remaining connections (next/previous/output).
var connections = this.getConnections_(true);
for (var i = 0, connection; connection = connections[i]; i++) {
for (var i = 0, connection; (connection = connections[i]); i++) {
connection.dispose();
}
} finally {
@@ -377,8 +385,8 @@ Blockly.Block.prototype.dispose = function(healStack) {
* @public
*/
Blockly.Block.prototype.initModel = function() {
for (var i = 0, input; input = this.inputList[i]; i++) {
for (var j = 0, field; field = input.fieldRow[j]; j++) {
for (var i = 0, input; (input = this.inputList[i]); i++) {
for (var j = 0, field; (field = input.fieldRow[j]); j++) {
if (field.initModel) {
field.initModel();
}
@@ -433,7 +441,7 @@ Blockly.Block.prototype.unplugFromRow_ = function(opt_healStack) {
// Disconnect the child block.
childConnection.disconnect();
// Connect child to the parent if possible, otherwise bump away.
if (childConnection.checkType_(parentConnection)) {
if (childConnection.checkType(parentConnection)) {
parentConnection.connect(childConnection);
} else {
childConnection.onFailedConnect(parentConnection);
@@ -485,7 +493,7 @@ Blockly.Block.prototype.unplugFromStack_ = function(opt_healStack) {
// Disconnect the next statement.
var nextTarget = this.nextConnection.targetConnection;
nextTarget.disconnect();
if (previousTarget && previousTarget.checkType_(nextTarget)) {
if (previousTarget && previousTarget.checkType(nextTarget)) {
// Attach the next statement to the previous statement.
previousTarget.connect(nextTarget);
}
@@ -496,7 +504,7 @@ Blockly.Block.prototype.unplugFromStack_ = function(opt_healStack) {
* Returns all connections originating from this block.
* @param {boolean} _all If true, return all connections even hidden ones.
* @return {!Array.<!Blockly.Connection>} Array of connections.
* @private
* @package
*/
Blockly.Block.prototype.getConnections_ = function(_all) {
var myConnections = [];
@@ -509,7 +517,7 @@ Blockly.Block.prototype.getConnections_ = function(_all) {
if (this.nextConnection) {
myConnections.push(this.nextConnection);
}
for (var i = 0, input; input = this.inputList[i]; i++) {
for (var i = 0, input; (input = this.inputList[i]); i++) {
if (input.connection) {
myConnections.push(input.connection);
}
@@ -560,7 +568,7 @@ Blockly.Block.prototype.getParent = function() {
* @return {Blockly.Input} The input that connects to the specified block.
*/
Blockly.Block.prototype.getInputWithBlock = function(block) {
for (var i = 0, input; input = this.inputList[i]; i++) {
for (var i = 0, input; (input = this.inputList[i]); i++) {
if (input.connection && input.connection.targetBlock() == block) {
return input;
}
@@ -611,7 +619,7 @@ Blockly.Block.prototype.getPreviousBlock = function() {
* @package
*/
Blockly.Block.prototype.getFirstStatementConnection = function() {
for (var i = 0, input; input = this.inputList[i]; i++) {
for (var i = 0, input; (input = this.inputList[i]); i++) {
if (input.connection && input.connection.type == Blockly.NEXT_STATEMENT) {
return input.connection;
}
@@ -639,7 +647,7 @@ Blockly.Block.prototype.getRootBlock = function() {
* the top block of the sub stack. If we are nested in a statement input only
* find the top-most nested block. Do not go all the way to the root block.
* @return {!Blockly.Block} The top block in a stack.
* @private
* @package
*/
Blockly.Block.prototype.getTopStackBlock = function() {
var block = this;
@@ -662,7 +670,7 @@ Blockly.Block.prototype.getChildren = function(ordered) {
return this.childBlocks_;
}
var blocks = [];
for (var i = 0, input; input = this.inputList[i]; i++) {
for (var i = 0, input; (input = this.inputList[i]); i++) {
if (input.connection) {
var child = input.connection.targetBlock();
if (child) {
@@ -725,7 +733,7 @@ Blockly.Block.prototype.setParent = function(newParent) {
Blockly.Block.prototype.getDescendants = function(ordered) {
var blocks = [this];
var childBlocks = this.getChildren(ordered);
for (var child, i = 0; child = childBlocks[i]; i++) {
for (var child, i = 0; (child = childBlocks[i]); i++) {
blocks.push.apply(blocks, child.getDescendants(ordered));
}
return blocks;
@@ -829,13 +837,21 @@ Blockly.Block.prototype.isEditable = function() {
*/
Blockly.Block.prototype.setEditable = function(editable) {
this.editable_ = editable;
for (var i = 0, input; input = this.inputList[i]; i++) {
for (var j = 0, field; field = input.fieldRow[j]; j++) {
for (var i = 0, input; (input = this.inputList[i]); i++) {
for (var j = 0, field; (field = input.fieldRow[j]); j++) {
field.updateEditable();
}
}
};
/**
* Returns if this block has been disposed of / deleted.
* @return {boolean} True if this block has been disposed of / deleted.
*/
Blockly.Block.prototype.isDisposed = function() {
return this.disposed;
};
/**
* Find the connection on this block that corresponds to the given connection
* on the other block.
@@ -885,60 +901,6 @@ Blockly.Block.prototype.getColour = function() {
return this.colour_;
};
/**
* Get the secondary colour of a block.
* @return {?string} #RRGGBB string.
*/
Blockly.Block.prototype.getColourSecondary = function() {
return this.colourSecondary_;
};
/**
* Get the tertiary colour of a block.
* @return {?string} #RRGGBB string.
*/
Blockly.Block.prototype.getColourTertiary = function() {
return this.colourTertiary_;
};
/**
* Get the shadow colour of a block.
* @return {?string} #RRGGBB string.
*/
Blockly.Block.prototype.getColourShadow = function() {
var colourSecondary = this.getColourSecondary();
if (colourSecondary) {
return colourSecondary;
}
return Blockly.utils.colour.blend('#fff', this.getColour(), 0.6);
};
/**
* Get the border colour(s) of a block.
* @return {{colourDark, colourLight, colourBorder}} An object containing
* colour values for the border(s) of the block. If the block is using a
* style the colourBorder will be defined and equal to the tertiary colour
* of the style (#RRGGBB string). Otherwise the colourDark and colourLight
* attributes will be defined (#RRGGBB strings).
* @package
*/
Blockly.Block.prototype.getColourBorder = function() {
var colourTertiary = this.getColourTertiary();
if (colourTertiary) {
return {
colourBorder: colourTertiary,
colourLight: null,
colourDark: null
};
}
var colour = this.getColour();
return {
colourBorder: null,
colourLight: Blockly.utils.colour.blend('#fff', colour, 0.3),
colourDark: Blockly.utils.colour.blend('#000', colour, 0.2)
};
};
/**
* Get the name of the block style.
* @return {?string} Name of the block style.
@@ -961,48 +923,17 @@ Blockly.Block.prototype.getHue = function() {
* or a message reference string pointing to one of those two values.
*/
Blockly.Block.prototype.setColour = function(colour) {
var dereferenced = (typeof colour == 'string') ?
Blockly.utils.replaceMessageReferences(colour) : colour;
var hue = Number(dereferenced);
if (!isNaN(hue) && 0 <= hue && hue <= 360) {
this.hue_ = hue;
this.colour_ = Blockly.hueToHex(hue);
} else {
var hex = Blockly.utils.colour.parse(dereferenced);
if (hex) {
this.colour_ = hex;
// Only store hue if colour is set as a hue.
this.hue_ = null;
} else {
var errorMsg = 'Invalid colour: "' + dereferenced + '"';
if (colour != dereferenced) {
errorMsg += ' (from "' + colour + '")';
}
throw Error(errorMsg);
}
}
var parsed = Blockly.utils.parseBlockColour(colour);
this.hue_ = parsed.hue;
this.colour_ = parsed.hex;
};
/**
* Set the style and colour values of a block.
* @param {string} blockStyleName Name of the block style
* @throws {Error} if the block style does not exist.
*/
Blockly.Block.prototype.setStyle = function(blockStyleName) {
var theme = this.workspace.getTheme();
var blockStyle = theme.getBlockStyle(blockStyleName);
this.styleName_ = blockStyleName;
if (blockStyle) {
this.colourSecondary_ = blockStyle['colourSecondary'];
this.colourTertiary_ = blockStyle['colourTertiary'];
this.hat = blockStyle.hat;
// Set colour will trigger an updateColour() on a block_svg
this.setColour(blockStyle['colourPrimary']);
} else {
throw Error('Invalid style name: ' + blockStyleName);
}
};
/**
@@ -1034,8 +965,8 @@ Blockly.Block.prototype.setOnChange = function(onchangeFn) {
* @return {Blockly.Field} Named field, or null if field does not exist.
*/
Blockly.Block.prototype.getField = function(name) {
for (var i = 0, input; input = this.inputList[i]; i++) {
for (var j = 0, field; field = input.fieldRow[j]; j++) {
for (var i = 0, input; (input = this.inputList[i]); i++) {
for (var j = 0, field; (field = input.fieldRow[j]); j++) {
if (field.name == name) {
return field;
}
@@ -1051,8 +982,8 @@ Blockly.Block.prototype.getField = function(name) {
*/
Blockly.Block.prototype.getVars = function() {
var vars = [];
for (var i = 0, input; input = this.inputList[i]; i++) {
for (var j = 0, field; field = input.fieldRow[j]; j++) {
for (var i = 0, input; (input = this.inputList[i]); i++) {
for (var j = 0, field; (field = input.fieldRow[j]); j++) {
if (field.referencesVariables()) {
vars.push(field.getValue());
}
@@ -1068,8 +999,8 @@ Blockly.Block.prototype.getVars = function() {
*/
Blockly.Block.prototype.getVarModels = function() {
var vars = [];
for (var i = 0, input; input = this.inputList[i]; i++) {
for (var j = 0, field; field = input.fieldRow[j]; j++) {
for (var i = 0, input; (input = this.inputList[i]); i++) {
for (var j = 0, field; (field = input.fieldRow[j]); j++) {
if (field.referencesVariables()) {
var model = this.workspace.getVariableById(
/** @type {string} */ (field.getValue()));
@@ -1091,8 +1022,8 @@ Blockly.Block.prototype.getVarModels = function() {
* @package
*/
Blockly.Block.prototype.updateVarName = function(variable) {
for (var i = 0, input; input = this.inputList[i]; i++) {
for (var j = 0, field; field = input.fieldRow[j]; j++) {
for (var i = 0, input; (input = this.inputList[i]); i++) {
for (var j = 0, field; (field = input.fieldRow[j]); j++) {
if (field.referencesVariables() &&
variable.getId() == field.getValue()) {
field.refreshVariableName();
@@ -1109,8 +1040,8 @@ Blockly.Block.prototype.updateVarName = function(variable) {
* an updated name.
*/
Blockly.Block.prototype.renameVarById = function(oldId, newId) {
for (var i = 0, input; input = this.inputList[i]; i++) {
for (var j = 0, field; field = input.fieldRow[j]; j++) {
for (var i = 0, input; (input = this.inputList[i]); i++) {
for (var j = 0, field; (field = input.fieldRow[j]); j++) {
if (field.referencesVariables() &&
oldId == field.getValue()) {
field.setValue(newId);
@@ -1274,6 +1205,22 @@ Blockly.Block.prototype.getInputsInline = function() {
return false;
};
/**
* Set the block's output shape.
* @param {?number} outputShape Value representing an output shape.
*/
Blockly.Block.prototype.setOutputShape = function(outputShape) {
this.outputShape_ = outputShape;
};
/**
* Get the block's output shape.
* @return {?number} Value representing output shape if one exists.
*/
Blockly.Block.prototype.getOutputShape = function() {
return this.outputShape_;
};
/**
* Set whether the block is disabled or not.
* @param {boolean} disabled True if disabled.
@@ -1355,8 +1302,8 @@ Blockly.Block.prototype.toString = function(opt_maxLength, opt_emptyToken) {
if (this.collapsed_) {
text.push(this.getInput('_TEMP_COLLAPSED_INPUT').fieldRow[0].getText());
} else {
for (var i = 0, input; input = this.inputList[i]; i++) {
for (var j = 0, field; field = input.fieldRow[j]; j++) {
for (var i = 0, input; (input = this.inputList[i]); i++) {
for (var j = 0, field; (field = input.fieldRow[j]); j++) {
text.push(field.getText());
}
if (input.connection) {
@@ -1445,7 +1392,7 @@ Blockly.Block.prototype.jsonInit = function(json) {
var i = 0;
while (json['message' + i] !== undefined) {
this.interpolate_(json['message' + i], json['args' + i] || [],
json['lastDummyAlign' + i]);
json['lastDummyAlign' + i], warningPrefix);
i++;
}
@@ -1456,6 +1403,9 @@ Blockly.Block.prototype.jsonInit = function(json) {
if (json['output'] !== undefined) {
this.setOutput(true, json['output']);
}
if (json['outputShape'] !== undefined) {
this.setOutputShape(json['outputShape']);
}
if (json['previousStatement'] !== undefined) {
this.setPreviousStatement(true, json['previousStatement']);
}
@@ -1569,9 +1519,11 @@ Blockly.Block.prototype.mixin = function(mixinObj, opt_disableCheck) {
* @param {!Array} args Array of arguments to be interpolated.
* @param {string|undefined} lastDummyAlign If a dummy input is added at the
* end, how should it be aligned?
* @param {string} warningPrefix Warning prefix string identifying block.
* @private
*/
Blockly.Block.prototype.interpolate_ = function(message, args, lastDummyAlign) {
Blockly.Block.prototype.interpolate_ = function(message, args, lastDummyAlign,
warningPrefix) {
var tokens = Blockly.utils.tokenizeInterpolation(message);
// Interpolate the arguments. Build a list of elements.
var indexDup = [];
@@ -1616,7 +1568,8 @@ Blockly.Block.prototype.interpolate_ = function(message, args, lastDummyAlign) {
var alignmentLookup = {
'LEFT': Blockly.ALIGN_LEFT,
'RIGHT': Blockly.ALIGN_RIGHT,
'CENTRE': Blockly.ALIGN_CENTRE
'CENTRE': Blockly.ALIGN_CENTRE,
'CENTER': Blockly.ALIGN_CENTRE
};
// Populate block with inputs and fields.
var fieldStack = [];
@@ -1648,11 +1601,9 @@ Blockly.Block.prototype.interpolate_ = function(message, args, lastDummyAlign) {
field = Blockly.fieldRegistry.fromJson(element);
// Unknown field.
if (!field) {
if (element['alt']) {
element = element['alt'];
altRepeat = true;
}
if (!field && element['alt']) {
element = element['alt'];
altRepeat = true;
}
}
}
@@ -1664,7 +1615,13 @@ Blockly.Block.prototype.interpolate_ = function(message, args, lastDummyAlign) {
input.setCheck(element['check']);
}
if (element['align']) {
input.setAlign(alignmentLookup[element['align']]);
var alignment = alignmentLookup[element['align'].toUpperCase()];
if (alignment === undefined) {
console.warn(warningPrefix + 'Illegal align value: ',
element['align']);
} else {
input.setAlign(alignment);
}
}
for (var j = 0; j < fieldStack.length; j++) {
input.appendField(fieldStack[j][0], fieldStack[j][1]);
@@ -1708,7 +1665,7 @@ Blockly.Block.prototype.moveInputBefore = function(name, refName) {
// Find both inputs.
var inputIndex = -1;
var refIndex = refName ? -1 : this.inputList.length;
for (var i = 0, input; input = this.inputList[i]; i++) {
for (var i = 0, input; (input = this.inputList[i]); i++) {
if (input.name == name) {
inputIndex = i;
if (refIndex != -1) {
@@ -1761,11 +1718,10 @@ Blockly.Block.prototype.moveNumberedInputBefore = function(
* Remove an input from this block.
* @param {string} name The name of the input.
* @param {boolean=} opt_quiet True to prevent error if input is not present.
* @throws {Error} if the input is not present and
* opt_quiet is not true.
* @throws {Error} if the input is not present and opt_quiet is not true.
*/
Blockly.Block.prototype.removeInput = function(name, opt_quiet) {
for (var i = 0, input; input = this.inputList[i]; i++) {
for (var i = 0, input; (input = this.inputList[i]); i++) {
if (input.name == name) {
input.dispose();
this.inputList.splice(i, 1);
@@ -1783,7 +1739,7 @@ Blockly.Block.prototype.removeInput = function(name, opt_quiet) {
* @return {Blockly.Input} The input object, or null if input does not exist.
*/
Blockly.Block.prototype.getInput = function(name) {
for (var i = 0, input; input = this.inputList[i]; i++) {
for (var i = 0, input; (input = this.inputList[i]); i++) {
if (input.name == name) {
return input;
}
@@ -1805,7 +1761,7 @@ Blockly.Block.prototype.getInputTargetBlock = function(name) {
/**
* Returns the comment on this block (or null if there is no comment).
* @return {string} Block's comment.
* @return {?string} Block's comment.
*/
Blockly.Block.prototype.getCommentText = function() {
return this.commentModel.text;
@@ -1872,7 +1828,7 @@ Blockly.Block.prototype.moveBy = function(dx, dy) {
* Create a connection of the specified type.
* @param {number} type The type of the connection to create.
* @return {!Blockly.Connection} A new connection of the specified type.
* @private
* @protected
*/
Blockly.Block.prototype.makeConnection_ = function(type) {
return new Blockly.Connection(this, type);
@@ -1895,7 +1851,7 @@ Blockly.Block.prototype.allInputsFilled = function(opt_shadowBlocksAreFilled) {
}
// Recursively check each input block of the current block.
for (var i = 0, input; input = this.inputList[i]; i++) {
for (var i = 0, input; (input = this.inputList[i]); i++) {
if (!input.connection) {
continue;
}

View File

@@ -173,7 +173,7 @@ Blockly.BlockDragSurfaceSvg.prototype.translateSurface = function(x, y) {
* @return {!Blockly.utils.Coordinate} Current translation of the surface.
*/
Blockly.BlockDragSurfaceSvg.prototype.getSurfaceTranslation = function() {
var xy = Blockly.utils.getRelativeXY(this.SVG_);
var xy = Blockly.utils.getRelativeXY(/** @type {!SVGElement} */ (this.SVG_));
return new Blockly.utils.Coordinate(xy.x / this.scale_, xy.y / this.scale_);
};
@@ -189,11 +189,11 @@ Blockly.BlockDragSurfaceSvg.prototype.getGroup = function() {
/**
* Get the current blocks on the drag surface, if any (primarily
* for BlockSvg.getRelativeToSurfaceXY).
* @return {!Element|undefined} Drag surface block DOM element, or undefined
* if no blocks exist.
* @return {Element} Drag surface block DOM element, or undefined if no blocks
* exist.
*/
Blockly.BlockDragSurfaceSvg.prototype.getCurrentBlock = function() {
return this.dragGroup_.firstChild;
return /** @type {Element} */ (this.dragGroup_.firstChild);
};
/**

View File

@@ -26,6 +26,7 @@ goog.provide('Blockly.BlockDragger');
goog.require('Blockly.blockAnimations');
goog.require('Blockly.Events');
goog.require('Blockly.Events.BlockMove');
goog.require('Blockly.Events.Ui');
goog.require('Blockly.InsertionMarkerManager');
goog.require('Blockly.utils.Coordinate');
goog.require('Blockly.utils.dom');
@@ -100,14 +101,10 @@ Blockly.BlockDragger = function(block, workspace) {
* @package
*/
Blockly.BlockDragger.prototype.dispose = function() {
this.draggingBlock_ = null;
this.workspace_ = null;
this.startWorkspace_ = null;
this.dragIconData_.length = 0;
if (this.draggedConnectionManager_) {
this.draggedConnectionManager_.dispose();
this.draggedConnectionManager_ = null;
}
};
@@ -123,7 +120,7 @@ Blockly.BlockDragger.initIconData_ = function(block) {
// Build a list of icons that need to be moved and where they started.
var dragIconData = [];
var descendants = block.getDescendants(false);
for (var i = 0, descendant; descendant = descendants[i]; i++) {
for (var i = 0, descendant; (descendant = descendants[i]); i++) {
var icons = descendant.getIcons();
for (var j = 0; j < icons.length; j++) {
var data = {
@@ -151,6 +148,7 @@ Blockly.BlockDragger.prototype.startBlockDrag = function(currentDragDeltaXY,
if (!Blockly.Events.getGroup()) {
Blockly.Events.setGroup(true);
}
this.fireDragStartEvent_();
// Mutators don't have the same type of z-ordering as the normal workspace
// during a drag. They have to rely on the order of the blocks in the SVG.
@@ -180,7 +178,7 @@ Blockly.BlockDragger.prototype.startBlockDrag = function(currentDragDeltaXY,
// For future consideration: we may be able to put moveToDragSurface inside
// the block dragger, which would also let the block not track the block drag
// surface.
this.draggingBlock_.moveToDragSurface_();
this.draggingBlock_.moveToDragSurface();
var toolbox = this.workspace_.getToolbox();
if (toolbox) {
@@ -190,6 +188,16 @@ Blockly.BlockDragger.prototype.startBlockDrag = function(currentDragDeltaXY,
}
};
/**
* Fire a UI event at the start of a block drag.
* @private
*/
Blockly.BlockDragger.prototype.fireDragStartEvent_ = function() {
var event = new Blockly.Events.Ui(this.draggingBlock_, 'dragStart',
null, this.draggingBlock_.getDescendants(false));
Blockly.Events.fire(event);
};
/**
* Execute a step of block dragging, based on the given event. Update the
* display accordingly.
@@ -222,19 +230,20 @@ Blockly.BlockDragger.prototype.endBlockDrag = function(e, currentDragDeltaXY) {
// Make sure internal state is fresh.
this.dragBlock(e, currentDragDeltaXY);
this.dragIconData_ = [];
this.fireDragEndEvent_();
Blockly.utils.dom.stopTextWidthCache();
Blockly.blockAnimations.disconnectUiStop();
var delta = this.pixelsToWorkspaceUnits_(currentDragDeltaXY);
var newLoc = Blockly.utils.Coordinate.sum(this.startXY_, delta);
this.draggingBlock_.moveOffDragSurface_(newLoc);
this.draggingBlock_.moveOffDragSurface(newLoc);
var deleted = this.maybeDeleteBlock_();
if (!deleted) {
// These are expensive and don't need to be done if we're deleting.
this.draggingBlock_.moveConnections_(delta.x, delta.y);
this.draggingBlock_.moveConnections(delta.x, delta.y);
this.draggingBlock_.setDragging(false);
this.fireMoveEvent_();
if (this.draggedConnectionManager_.wouldConnectBlock()) {
@@ -256,6 +265,16 @@ Blockly.BlockDragger.prototype.endBlockDrag = function(e, currentDragDeltaXY) {
Blockly.Events.setGroup(false);
};
/**
* Fire a UI event at the end of a block drag.
* @private
*/
Blockly.BlockDragger.prototype.fireDragEndEvent_ = function() {
var event = new Blockly.Events.Ui(this.draggingBlock_, 'dragStop',
this.draggingBlock_.getDescendants(false), null);
Blockly.Events.fire(event);
};
/**
* Fire a move event at the end of a block drag.
* @private
@@ -301,12 +320,12 @@ Blockly.BlockDragger.prototype.updateCursorDuringBlockDrag_ = function() {
if (this.wouldDeleteBlock_) {
this.draggingBlock_.setDeleteStyle(true);
if (this.deleteArea_ == Blockly.DELETE_AREA_TRASH && trashcan) {
trashcan.setOpen_(true);
trashcan.setOpen(true);
}
} else {
this.draggingBlock_.setDeleteStyle(false);
if (trashcan) {
trashcan.setOpen_(false);
trashcan.setOpen(false);
}
}
};

View File

@@ -273,7 +273,7 @@ Blockly.Events.Create.prototype.run = function(forward) {
xml.appendChild(this.xml);
Blockly.Xml.domToWorkspace(xml, workspace);
} else {
for (var i = 0, id; id = this.ids[i]; i++) {
for (var i = 0, id; (id = this.ids[i]); i++) {
var block = workspace.getBlockById(id);
if (block) {
block.dispose(false);
@@ -349,7 +349,7 @@ Blockly.Events.Delete.prototype.fromJson = function(json) {
Blockly.Events.Delete.prototype.run = function(forward) {
var workspace = this.getEventWorkspace_();
if (forward) {
for (var i = 0, id; id = this.ids[i]; i++) {
for (var i = 0, id; (id = this.ids[i]); i++) {
var block = workspace.getBlockById(id);
if (block) {
block.dispose(false);

File diff suppressed because it is too large Load Diff

View File

@@ -66,24 +66,12 @@ Blockly.mainWorkspace = null;
*/
Blockly.selected = null;
/**
* Current cursor.
* @type {Blockly.Cursor}
*/
Blockly.cursor = null;
/**
* Whether or not we're currently in keyboard accessibility mode.
* @type {boolean}
*/
Blockly.keyboardAccessibilityMode = false;
/**
* All of the connections on blocks that are currently being dragged.
* @type {!Array.<!Blockly.Connection>}
* @private
* @package
*/
Blockly.draggingConnections_ = [];
Blockly.draggingConnections = [];
/**
* Contents of the local clipboard.
@@ -113,9 +101,16 @@ Blockly.clipboardTypeCounts_ = null;
*/
Blockly.cache3dSupported_ = null;
/**
* Blockly opaque event data used to unbind events when using
* `Blockly.bindEvent_` and `Blockly.bindEventWithChecks_`.
* @typedef {!Array.<!Array>}
*/
Blockly.EventData;
/**
* Returns the dimensions of the specified SVG image.
* @param {!Element} svg SVG image.
* @param {!SVGElement} svg SVG image.
* @return {!Object} Contains width and height properties.
*/
Blockly.svgSize = function(svg) {
@@ -170,12 +165,15 @@ Blockly.svgResize = function(workspace) {
* Handle a key-down on SVG drawing surface. Does nothing if the main workspace
* is not visible.
* @param {!Event} e Key down event.
* @private
* @package
*/
// TODO (https://github.com/google/blockly/issues/1998) handle cases where there
// are multiple workspaces and non-main workspaces are able to accept input.
Blockly.onKeyDown_ = function(e) {
Blockly.onKeyDown = function(e) {
var mainWorkspace = Blockly.mainWorkspace;
if (!mainWorkspace) {
return;
}
if (Blockly.utils.isTargetInput(e) ||
(mainWorkspace.rendered && !mainWorkspace.isVisible())) {
@@ -262,7 +260,8 @@ Blockly.onKeyDown_ = function(e) {
if (deleteBlock && !Blockly.selected.workspace.isFlyout) {
Blockly.Events.setGroup(true);
Blockly.hideChaff();
Blockly.selected.dispose(/* heal */ true, true);
var selected = /** @type {!Blockly.BlockSvg} */ (Blockly.selected);
selected.dispose(/* heal */ true, true);
Blockly.Events.setGroup(false);
}
};
@@ -295,9 +294,9 @@ Blockly.copy_ = function(toCopy) {
* Duplicate this block and its children, or a workspace comment.
* @param {!Blockly.Block | !Blockly.WorkspaceComment} toDuplicate Block or
* Workspace Comment to be copied.
* @private
* @package
*/
Blockly.duplicate_ = function(toDuplicate) {
Blockly.duplicate = function(toDuplicate) {
// Save the clipboard.
var clipboardXml = Blockly.clipboardXml_;
var clipboardSource = Blockly.clipboardSource_;
@@ -336,13 +335,14 @@ Blockly.hideChaff = function(opt_allowToolbox) {
// For now the trashcan flyout always autocloses because it overlays the
// trashcan UI (no trashcan to click to close it).
if (workspace.trashcan &&
workspace.trashcan.flyout_) {
workspace.trashcan.flyout_.hide();
workspace.trashcan.flyout) {
workspace.trashcan.flyout.hide();
}
if (workspace.toolbox_ &&
workspace.toolbox_.flyout_ &&
workspace.toolbox_.flyout_.autoClose) {
workspace.toolbox_.clearSelection();
var toolbox = workspace.getToolbox();
if (toolbox &&
toolbox.getFlyout() &&
toolbox.getFlyout().autoClose) {
toolbox.clearSelection();
}
}
};
@@ -354,7 +354,7 @@ Blockly.hideChaff = function(opt_allowToolbox) {
* @return {!Blockly.Workspace} The main workspace.
*/
Blockly.getMainWorkspace = function() {
return Blockly.mainWorkspace;
return /** @type {!Blockly.Workspace} */ (Blockly.mainWorkspace);
};
/**
@@ -387,7 +387,7 @@ Blockly.confirm = function(message, callback) {
* recommend testing mobile when overriding this.
* @param {string} message The message to display to the user.
* @param {string} defaultValue The value to initialize the prompt with.
* @param {!function(string)} callback The callback for handling user response.
* @param {!function(?string)} callback The callback for handling user response.
*/
Blockly.prompt = function(message, defaultValue, callback) {
callback(prompt(message, defaultValue));
@@ -454,7 +454,7 @@ Blockly.defineBlocksWithJsonArray = function(jsonArray) {
* should prevent the default handler. False by default. If
* opt_noPreventDefault is provided, opt_noCaptureIdentifier must also be
* provided.
* @return {!Array.<!Array>} Opaque data that can be passed to unbindEvent_.
* @return {!Blockly.EventData} Opaque data that can be passed to unbindEvent_.
*/
Blockly.bindEventWithChecks_ = function(node, name, thisObject, func,
opt_noCaptureIdentifier, opt_noPreventDefault) {
@@ -464,7 +464,7 @@ Blockly.bindEventWithChecks_ = function(node, name, thisObject, func,
// Handle each touch point separately. If the event was a mouse event, this
// will hand back an array with one element, which we're fine handling.
var events = Blockly.Touch.splitEventByTouches(e);
for (var i = 0, event; event = events[i]; i++) {
for (var i = 0, event; (event = events[i]); i++) {
if (captureIdentifier && !Blockly.Touch.shouldHandleEvent(event)) {
continue;
}
@@ -481,7 +481,7 @@ Blockly.bindEventWithChecks_ = function(node, name, thisObject, func,
var bindData = [];
if (Blockly.utils.global['PointerEvent'] &&
(name in Blockly.Touch.TOUCH_MAP)) {
for (var i = 0, type; type = Blockly.Touch.TOUCH_MAP[name][i]; i++) {
for (var i = 0, type; (type = Blockly.Touch.TOUCH_MAP[name][i]); i++) {
node.addEventListener(type, wrapFunc, false);
bindData.push([node, type, wrapFunc]);
}
@@ -500,7 +500,7 @@ Blockly.bindEventWithChecks_ = function(node, name, thisObject, func,
e.preventDefault();
}
};
for (var i = 0, type; type = Blockly.Touch.TOUCH_MAP[name][i]; i++) {
for (var i = 0, type; (type = Blockly.Touch.TOUCH_MAP[name][i]); i++) {
node.addEventListener(type, touchWrapFunc, false);
bindData.push([node, type, touchWrapFunc]);
}
@@ -513,14 +513,13 @@ Blockly.bindEventWithChecks_ = function(node, name, thisObject, func,
/**
* Bind an event to a function call. Handles multitouch events by using the
* coordinates of the first changed touch, and doesn't do any safety checks for
* simultaneous event processing.
* @deprecated in favor of bindEventWithChecks_, but preserved for external
* users.
* simultaneous event processing. In most cases prefer is to use
* `Blockly.bindEventWithChecks_`.
* @param {!EventTarget} node Node upon which to listen.
* @param {string} name Event name to listen to (e.g. 'mousedown').
* @param {Object} thisObject The value of 'this' in the function.
* @param {!Function} func Function to call when event is triggered.
* @return {!Array.<!Array>} Opaque data that can be passed to unbindEvent_.
* @return {!Blockly.EventData} Opaque data that can be passed to unbindEvent_.
*/
Blockly.bindEvent_ = function(node, name, thisObject, func) {
var wrapFunc = function(e) {
@@ -534,7 +533,7 @@ Blockly.bindEvent_ = function(node, name, thisObject, func) {
var bindData = [];
if (Blockly.utils.global['PointerEvent'] &&
(name in Blockly.Touch.TOUCH_MAP)) {
for (var i = 0, type; type = Blockly.Touch.TOUCH_MAP[name][i]; i++) {
for (var i = 0, type; (type = Blockly.Touch.TOUCH_MAP[name][i]); i++) {
node.addEventListener(type, wrapFunc, false);
bindData.push([node, type, wrapFunc]);
}
@@ -557,7 +556,7 @@ Blockly.bindEvent_ = function(node, name, thisObject, func) {
// Stop the browser from scrolling/zooming the page.
e.preventDefault();
};
for (var i = 0, type; type = Blockly.Touch.TOUCH_MAP[name][i]; i++) {
for (var i = 0, type; (type = Blockly.Touch.TOUCH_MAP[name][i]); i++) {
node.addEventListener(type, touchWrapFunc, false);
bindData.push([node, type, touchWrapFunc]);
}

View File

@@ -27,7 +27,7 @@
*/
goog.provide('Blockly.Blocks');
/*
/**
* A mapping of block type names to block prototype objects.
* @type {!Object.<string,Object>}
*/

View File

@@ -51,6 +51,42 @@ Blockly.Bubble = function(workspace, content, shape, anchorXY,
this.content_ = content;
this.shape_ = shape;
/**
* Method to call on resize of bubble.
* @type {?function()}
* @private
*/
this.resizeCallback_ = null;
/**
* Method to call on move of bubble.
* @type {?function()}
* @private
*/
this.moveCallback_ = null;
/**
* Mouse down on bubbleBack_ event data.
* @type {?Blockly.EventData}
* @private
*/
this.onMouseDownBubbleWrapper_ = null;
/**
* Mouse down on resizeGroup_ event data.
* @type {?Blockly.EventData}
* @private
*/
this.onMouseDownResizeWrapper_ = null;
/**
* Describes whether this bubble has been disposed of (nodes and event
* listeners removed from the page) or not.
* @type {boolean}
* @package
*/
this.disposed = false;
var angle = Blockly.Bubble.ARROW_ANGLE;
if (this.workspace_.RTL) {
angle = -angle;
@@ -72,15 +108,6 @@ Blockly.Bubble = function(workspace, content, shape, anchorXY,
this.positionBubble_();
this.renderArrow_();
this.rendered_ = true;
if (!workspace.options.readOnly) {
Blockly.bindEventWithChecks_(
this.bubbleBack_, 'mousedown', this, this.bubbleMouseDown_);
if (this.resizeGroup_) {
Blockly.bindEventWithChecks_(
this.resizeGroup_, 'mousedown', this, this.resizeMouseDown_);
}
}
};
/**
@@ -110,25 +137,19 @@ Blockly.Bubble.ARROW_BEND = 4;
Blockly.Bubble.ANCHOR_RADIUS = 8;
/**
* Wrapper function called when a mouseUp occurs during a drag operation.
* @type {Array.<!Array>}
* Mouse up event data.
* @type {?Blockly.EventData}
* @private
*/
Blockly.Bubble.onMouseUpWrapper_ = null;
/**
* Wrapper function called when a mouseMove occurs during a drag operation.
* @type {Array.<!Array>}
* Mouse move event data.
* @type {?Blockly.EventData}
* @private
*/
Blockly.Bubble.onMouseMoveWrapper_ = null;
/**
* Function to call on resize of bubble.
* @type {Function}
*/
Blockly.Bubble.prototype.resizeCallback_ = null;
/**
* Stop binding to the global mouseup and mousemove events.
* @private
@@ -144,12 +165,12 @@ Blockly.Bubble.unbindDragEvents_ = function() {
}
};
/*
/**
* Handle a mouse-up event while dragging a bubble's border or resize handle.
* @param {!Event} e Mouse up event.
* @param {!Event} _e Mouse up event.
* @private
*/
Blockly.Bubble.bubbleMouseUp_ = function(/* e */) {
Blockly.Bubble.bubbleMouseUp_ = function(_e) {
Blockly.Touch.clearTouchIdentifier();
Blockly.Bubble.unbindDragEvents_();
};
@@ -224,7 +245,8 @@ Blockly.Bubble.prototype.createDom_ = function(content, hasResize) {
*/
this.bubbleGroup_ = Blockly.utils.dom.createSvgElement('g', {}, null);
var filter =
{'filter': 'url(#' + this.workspace_.options.embossFilterId + ')'};
{'filter': 'url(#' +
this.workspace_.getRenderer().getConstants().embossFilterId + ')'};
if (Blockly.utils.userAgent.JAVA_FX) {
// Multiple reports that JavaFX can't handle filters.
// https://github.com/google/blockly/issues/99
@@ -232,7 +254,8 @@ Blockly.Bubble.prototype.createDom_ = function(content, hasResize) {
}
var bubbleEmboss = Blockly.utils.dom.createSvgElement('g',
filter, this.bubbleGroup_);
this.bubbleArrow_ = Blockly.utils.dom.createSvgElement('path', {}, bubbleEmboss);
this.bubbleArrow_ = Blockly.utils.dom.createSvgElement('path', {},
bubbleEmboss);
this.bubbleBack_ = Blockly.utils.dom.createSvgElement('rect',
{
'class': 'blocklyDraggable',
@@ -268,6 +291,15 @@ Blockly.Bubble.prototype.createDom_ = function(content, hasResize) {
} else {
this.resizeGroup_ = null;
}
if (!this.workspace_.options.readOnly) {
this.onMouseDownBubbleWrapper_ = Blockly.bindEventWithChecks_(
this.bubbleBack_, 'mousedown', this, this.bubbleMouseDown_);
if (this.resizeGroup_) {
this.onMouseDownResizeWrapper_ = Blockly.bindEventWithChecks_(
this.resizeGroup_, 'mousedown', this, this.resizeMouseDown_);
}
}
this.bubbleGroup_.appendChild(content);
return this.bubbleGroup_;
};
@@ -305,9 +337,9 @@ Blockly.Bubble.prototype.bubbleMouseDown_ = function(e) {
/**
* Show the context menu for this bubble.
* @param {!Event} _e Mouse event.
* @private
* @package
*/
Blockly.Bubble.prototype.showContextMenu_ = function(_e) {
Blockly.Bubble.prototype.showContextMenu = function(_e) {
// NOP on bubbles, but used by the bubble dragger to pass events to
// workspace comments.
};
@@ -327,7 +359,7 @@ Blockly.Bubble.prototype.isDeletable = function() {
* @private
*/
Blockly.Bubble.prototype.resizeMouseDown_ = function(e) {
this.promote_();
this.promote();
Blockly.Bubble.unbindDragEvents_();
if (Blockly.utils.isRightButton(e)) {
// No right-click.
@@ -370,12 +402,20 @@ Blockly.Bubble.prototype.registerResizeEvent = function(callback) {
this.resizeCallback_ = callback;
};
/**
* Register a function as a callback event for when the bubble is moved.
* @param {!Function} callback The function to call on move.
*/
Blockly.Bubble.prototype.registerMoveEvent = function(callback) {
this.moveCallback_ = callback;
};
/**
* Move this bubble to the top of the stack.
* @return {boolean} Whether or not the bubble has been moved.
* @private
* @package
*/
Blockly.Bubble.prototype.promote_ = function() {
Blockly.Bubble.prototype.promote = function() {
var svgGroup = this.bubbleGroup_.parentNode;
if (svgGroup.lastChild !== this.bubbleGroup_) {
svgGroup.appendChild(this.bubbleGroup_);
@@ -412,8 +452,11 @@ Blockly.Bubble.prototype.layoutBubble_ = function() {
var optimalTop = this.getOptimalRelativeTop_(metrics);
var bbox = this.shape_.getBBox();
var topPosition = {x: optimalLeft,
y: -this.height_ - Blockly.BlockSvg.MIN_BLOCK_Y};
var topPosition = {
x: optimalLeft,
y: -this.height_ -
this.workspace_.getRenderer().getConstants().MIN_BLOCK_HEIGHT
};
var startPosition = {x: -this.width_ - 30, y: optimalTop};
var endPosition = {x: bbox.width, y: optimalTop};
var bottomPosition = {x: optimalLeft, y: bbox.height};
@@ -455,12 +498,10 @@ Blockly.Bubble.prototype.layoutBubble_ = function() {
/**
* Calculate the what percentage of the bubble overlaps with the visible
* workspace (what percentage of the bubble is visible).
* @param {!Object} relativeMin The position of the top-left corner of the
* bubble relative to the anchor point.
* @param {number} relativeMin.x The x-position of the relativeMin.
* @param {number} relativeMin.y The y-position of the relativeMin.
* @param {!{x: number, y: number}} relativeMin The position of the top-left
* corner of the bubble relative to the anchor point.
* @param {!Object} metrics The metrics of the workspace the bubble will
* appear in.
* appear in.
* @return {number} The percentage of the bubble that is visible.
* @private
*/
@@ -621,6 +662,17 @@ Blockly.Bubble.prototype.moveTo = function(x, y) {
this.bubbleGroup_.setAttribute('transform', 'translate(' + x + ',' + y + ')');
};
/**
* Triggers a move callback if one exists at the end of a drag.
* @param {boolean} adding True if adding, false if removing.
* @package
*/
Blockly.Bubble.prototype.setDragging = function(adding) {
if (!adding && this.moveCallback_) {
this.moveCallback_();
}
};
/**
* Get the dimensions of this bubble.
* @return {!Blockly.utils.Size} The height and width of the bubble.
@@ -755,16 +807,15 @@ Blockly.Bubble.prototype.setColour = function(hexColour) {
* Dispose of this bubble.
*/
Blockly.Bubble.prototype.dispose = function() {
if (this.onMouseDownBubbleWrapper_) {
Blockly.unbindEvent_(this.onMouseDownBubbleWrapper_);
}
if (this.onMouseDownResizeWrapper_) {
Blockly.unbindEvent_(this.onMouseDownResizeWrapper_);
}
Blockly.Bubble.unbindDragEvents_();
// Dispose of and unlink the bubble.
Blockly.utils.dom.removeNode(this.bubbleGroup_);
this.bubbleGroup_ = null;
this.bubbleArrow_ = null;
this.bubbleBack_ = null;
this.resizeGroup_ = null;
this.workspace_ = null;
this.content_ = null;
this.shape_ = null;
this.disposed = true;
};
/**
@@ -798,7 +849,9 @@ Blockly.Bubble.prototype.moveDuringDrag = function(dragSurface, newLoc) {
*/
Blockly.Bubble.prototype.getRelativeToSurfaceXY = function() {
return new Blockly.utils.Coordinate(
this.anchorXY_.x + this.relativeLeft_,
this.workspace_.RTL ?
-this.relativeLeft_ + this.anchorXY_.x - this.width_ :
this.anchorXY_.x + this.relativeLeft_,
this.anchorXY_.y + this.relativeTop_);
};

View File

@@ -92,6 +92,7 @@ Blockly.BubbleDragger = function(bubble, workspace) {
/**
* Sever all links from this object.
* @package
* @suppress {checkTypes}
*/
Blockly.BubbleDragger.prototype.dispose = function() {
this.draggingBubble_ = null;
@@ -178,12 +179,12 @@ Blockly.BubbleDragger.prototype.updateCursorDuringBubbleDrag_ = function() {
if (this.wouldDeleteBubble_) {
this.draggingBubble_.setDeleteStyle(true);
if (this.deleteArea_ == Blockly.DELETE_AREA_TRASH && trashcan) {
trashcan.setOpen_(true);
trashcan.setOpen(true);
}
} else {
this.draggingBubble_.setDeleteStyle(false);
if (trashcan) {
trashcan.setOpen_(false);
trashcan.setOpen(false);
}
}
};
@@ -218,10 +219,10 @@ Blockly.BubbleDragger.prototype.endBubbleDrag = function(
}
this.workspace_.setResizesEnabled(true);
if (this.workspace_.toolbox_) {
if (this.workspace_.getToolbox()) {
var style = this.draggingBubble_.isDeletable() ? 'blocklyToolboxDelete' :
'blocklyToolboxGrab';
this.workspace_.toolbox_.removeStyle(style);
this.workspace_.getToolbox().removeStyle(style);
}
Blockly.Events.setGroup(false);
};
@@ -232,7 +233,8 @@ Blockly.BubbleDragger.prototype.endBubbleDrag = function(
*/
Blockly.BubbleDragger.prototype.fireMoveEvent_ = function() {
if (this.draggingBubble_.isComment) {
var event = new Blockly.Events.CommentMove(this.draggingBubble_);
var event = new Blockly.Events.CommentMove(
/** @type {!Blockly.WorkspaceCommentSvg} */ (this.draggingBubble_));
event.setOldCoordinate(this.startXY_);
event.recordNew();
Blockly.Events.fire(event);
@@ -265,6 +267,7 @@ Blockly.BubbleDragger.prototype.pixelsToWorkspaceUnits_ = function(pixelCoord) {
}
return result;
};
/**
* Move the bubble onto the drag surface at the beginning of a drag. Move the
* drag surface to preserve the apparent location of the bubble.

View File

@@ -24,6 +24,7 @@
goog.provide('Blockly.Comment');
goog.require('Blockly.Bubble');
goog.require('Blockly.Css');
goog.require('Blockly.Events');
goog.require('Blockly.Events.BlockChange');
goog.require('Blockly.Events.Ui');
@@ -61,6 +62,34 @@ Blockly.Comment = function(block) {
*/
this.cachedText_ = '';
/**
* Mouse up event data.
* @type {?Blockly.EventData}
* @private
*/
this.onMouseUpWrapper_ = null;
/**
* Wheel event data.
* @type {?Blockly.EventData}
* @private
*/
this.onWheelWrapper_ = null;
/**
* Change event data.
* @type {?Blockly.EventData}
* @private
*/
this.onChangeWrapper_ = null;
/**
* Input event data.
* @type {?Blockly.EventData}
* @private
*/
this.onInputWrapper_ = null;
this.createIcon();
};
Blockly.utils.object.inherits(Blockly.Comment, Blockly.Icon);
@@ -137,21 +166,24 @@ Blockly.Comment.prototype.createEditor_ = function() {
// Ideally this would be hooked to the focus event for the comment.
// However doing so in Firefox swallows the cursor for unknown reasons.
// So this is hooked to mouseup instead. No big deal.
Blockly.bindEventWithChecks_(textarea, 'mouseup', this, this.startEdit_,
true, true);
this.onMouseUpWrapper_ = Blockly.bindEventWithChecks_(
textarea, 'mouseup', this, this.startEdit_, true, true);
// Don't zoom with mousewheel.
Blockly.bindEventWithChecks_(textarea, 'wheel', this, function(e) {
e.stopPropagation();
});
Blockly.bindEventWithChecks_(textarea, 'change', this, function(_e) {
if (this.cachedText_ != this.model_.text) {
Blockly.Events.fire(new Blockly.Events.BlockChange(
this.block_, 'comment', null, this.cachedText_, this.model_.text));
}
});
Blockly.bindEventWithChecks_(textarea, 'input', this, function(_e) {
this.model_.text = textarea.value;
});
this.onWheelWrapper_ = Blockly.bindEventWithChecks_(
textarea, 'wheel', this, function(e) {
e.stopPropagation();
});
this.onChangeWrapper_ = Blockly.bindEventWithChecks_(
textarea, 'change', this, function(_e) {
if (this.cachedText_ != this.model_.text) {
Blockly.Events.fire(new Blockly.Events.BlockChange(
this.block_, 'comment', null, this.cachedText_, this.model_.text));
}
});
this.onInputWrapper_ = Blockly.bindEventWithChecks_(
textarea, 'input', this, function(_e) {
this.model_.text = textarea.value;
});
setTimeout(textarea.focus.bind(textarea), 0);
@@ -241,17 +273,19 @@ Blockly.Comment.prototype.createBubble_ = function() {
Blockly.Comment.prototype.createEditableBubble_ = function() {
this.bubble_ = new Blockly.Bubble(
/** @type {!Blockly.WorkspaceSvg} */ (this.block_.workspace),
this.createEditor_(), this.block_.svgPath_,
this.iconXY_, this.model_.size.width, this.model_.size.height);
this.createEditor_(), this.block_.pathObject.svgPath,
/** @type {!Blockly.utils.Coordinate} */ (this.iconXY_),
this.model_.size.width, this.model_.size.height);
// Expose this comment's block's ID on its top-level SVG group.
this.bubble_.setSvgId(this.block_.id);
this.bubble_.registerResizeEvent(this.onBubbleResize_.bind(this));
this.updateColour();
this.applyColour();
};
/**
* Show a non-editable bubble.
* @private
* @suppress {checkTypes} Suppress `this` type mismatch.
*/
Blockly.Comment.prototype.createNonEditableBubble_ = function() {
// TODO (#2917): It would be great if the comment could support line breaks.
@@ -261,6 +295,7 @@ Blockly.Comment.prototype.createNonEditableBubble_ = function() {
/**
* Dispose of the bubble.
* @private
* @suppress {checkTypes} Suppress `this` type mismatch.
*/
Blockly.Comment.prototype.disposeBubble_ = function() {
if (this.paragraphElement_) {
@@ -268,7 +303,22 @@ Blockly.Comment.prototype.disposeBubble_ = function() {
Blockly.Warning.prototype.disposeBubble.call(this);
return;
}
if (this.onMouseUpWrapper_) {
Blockly.unbindEvent_(this.onMouseUpWrapper_);
this.onMouseUpWrapper_ = null;
}
if (this.onWheelWrapper_) {
Blockly.unbindEvent_(this.onWheelWrapper_);
this.onWheelWrapper_ = null;
}
if (this.onChangeWrapper_) {
Blockly.unbindEvent_(this.onChangeWrapper_);
this.onChangeWrapper_ = null;
}
if (this.onInputWrapper_) {
Blockly.unbindEvent_(this.onInputWrapper_);
this.onInputWrapper_ = null;
}
this.bubble_.dispose();
this.bubble_ = null;
this.textarea_ = null;
@@ -284,7 +334,7 @@ Blockly.Comment.prototype.disposeBubble_ = function() {
* @private
*/
Blockly.Comment.prototype.startEdit_ = function(_e) {
if (this.bubble_.promote_()) {
if (this.bubble_.promote()) {
// Since the act of moving this node within the DOM causes a loss of focus,
// we need to reapply the focus.
this.textarea_.focus();
@@ -364,3 +414,21 @@ Blockly.Comment.prototype.dispose = function() {
this.block_.comment = null;
Blockly.Icon.prototype.dispose.call(this);
};
/**
* CSS for block comment. See css.js for use.
*/
Blockly.Css.register([
/* eslint-disable indent */
'.blocklyCommentTextarea {',
'background-color: #fef49c;',
'border: 0;',
'outline: 0;',
'margin: 0;',
'padding: 3px;',
'resize: none;',
'display: block;',
'overflow: hidden;',
'}'
/* eslint-enable indent */
]);

View File

@@ -339,7 +339,7 @@ Blockly.Component.prototype.exitDocument = function() {
/**
* Disposes of the object. If the object hasn't already been disposed of, calls
* {@link #disposeInternal}.
* @protected
* @package
*/
Blockly.Component.prototype.dispose = function() {
if (!this.disposed_) {

View File

@@ -25,6 +25,7 @@ goog.provide('Blockly.Menu');
goog.require('Blockly.Component');
goog.require('Blockly.utils.aria');
goog.require('Blockly.utils.Coordinate');
goog.require('Blockly.utils.dom');
goog.require('Blockly.utils.object');
@@ -37,6 +38,15 @@ goog.require('Blockly.utils.object');
Blockly.Menu = function() {
Blockly.Component.call(this);
/**
* Coordinates of the mousedown event that caused this menu to open. Used to
* prevent the consequent mouseup event due to a simple click from activating
* a menu item immediately.
* @type {?Blockly.utils.Coordinate}
* @package
*/
this.openingCoords = null;
/**
* This is the element that we will listen to the real focus events on.
* A value of -1 means no menuitem is highlighted.
@@ -44,6 +54,41 @@ Blockly.Menu = function() {
* @private
*/
this.highlightedIndex_ = -1;
/**
* Mouse over event data.
* @type {?Blockly.EventData}
* @private
*/
this.mouseOverHandler_ = null;
/**
* Click event data.
* @type {?Blockly.EventData}
* @private
*/
this.clickHandler_ = null;
/**
* Mouse enter event data.
* @type {?Blockly.EventData}
* @private
*/
this.mouseEnterHandler_ = null;
/**
* Mouse leave event data.
* @type {?Blockly.EventData}
* @private
*/
this.mouseLeaveHandler_ = null;
/**
* Key down event data.
* @type {?Blockly.EventData}
* @private
*/
this.onKeyDownWrapper_ = null;
};
Blockly.utils.object.inherits(Blockly.Menu, Blockly.Component);
@@ -73,7 +118,7 @@ Blockly.Menu.prototype.createDom = function() {
Blockly.Menu.prototype.focus = function() {
var el = this.getElement();
if (el) {
el.focus();
el.focus({preventScroll:true});
Blockly.utils.dom.addClass(el, 'focused');
}
};
@@ -92,7 +137,7 @@ Blockly.Menu.prototype.blur = function() {
/**
* Set the menu accessibility role.
* @param {!Blockly.utils.aria.Role|string} roleName role name.
* @param {!Blockly.utils.aria.Role} roleName role name.
* @package
*/
Blockly.Menu.prototype.setRole = function(roleName) {
@@ -147,7 +192,6 @@ Blockly.Menu.prototype.attachEvents_ = function() {
'mouseenter', this, this.handleMouseEnter_, true);
this.mouseLeaveHandler_ = Blockly.bindEventWithChecks_(el,
'mouseleave', this, this.handleMouseLeave_, true);
this.onKeyDownWrapper_ = Blockly.bindEventWithChecks_(el,
'keydown', this, this.handleKeyEvent);
};
@@ -157,11 +201,26 @@ Blockly.Menu.prototype.attachEvents_ = function() {
* @private
*/
Blockly.Menu.prototype.detachEvents_ = function() {
Blockly.unbindEvent_(this.mouseOverHandler_);
Blockly.unbindEvent_(this.clickHandler_);
Blockly.unbindEvent_(this.mouseEnterHandler_);
Blockly.unbindEvent_(this.mouseLeaveHandler_);
Blockly.unbindEvent_(this.onKeyDownWrapper_);
if (this.mouseOverHandler_) {
Blockly.unbindEvent_(this.mouseOverHandler_);
this.mouseOverHandler_ = null;
}
if (this.clickHandler_) {
Blockly.unbindEvent_(this.clickHandler_);
this.clickHandler_ = null;
}
if (this.mouseEnterHandler_) {
Blockly.unbindEvent_(this.mouseEnterHandler_);
this.mouseEnterHandler_ = null;
}
if (this.mouseLeaveHandler_) {
Blockly.unbindEvent_(this.mouseLeaveHandler_);
this.mouseLeaveHandler_ = null;
}
if (this.onKeyDownWrapper_) {
Blockly.unbindEvent_(this.onKeyDownWrapper_);
this.onKeyDownWrapper_ = null;
}
};
// Child component management.
@@ -383,6 +442,20 @@ Blockly.Menu.prototype.handleMouseOver_ = function(e) {
* @private
*/
Blockly.Menu.prototype.handleClick_ = function(e) {
var oldCoords = this.openingCoords;
// Clear out the saved opening coords immediately so they're not used twice.
this.openingCoords = null;
if (oldCoords && typeof e.clientX === 'number') {
var newCoords = new Blockly.utils.Coordinate(e.clientX, e.clientY);
if (Blockly.utils.Coordinate.distance(oldCoords, newCoords) < 1) {
// This menu was opened by a mousedown and we're handling the consequent
// click event. The coords haven't changed, meaning this was the same
// opening event. Don't do the usual behavior because the menu just popped
// up under the mouse and the user didn't mean to activate this item.
return;
}
}
var menuItem = this.getMenuItem(/** @type {Node} */ (e.target));
if (menuItem && menuItem.handleClick(e)) {

View File

@@ -95,8 +95,8 @@ Blockly.MenuItem.prototype.createDom = function() {
Blockly.utils.aria.setRole(element, this.roleName_ || (this.checkable_ ?
Blockly.utils.aria.Role.MENUITEMCHECKBOX :
Blockly.utils.aria.Role.MENUITEM));
Blockly.utils.aria.setState(element,
Blockly.utils.aria.State.SELECTED, (this.checkable_ && this.checked_) || false);
Blockly.utils.aria.setState(element, Blockly.utils.aria.State.SELECTED,
(this.checkable_ && this.checked_) || false);
};
/**
@@ -164,7 +164,7 @@ Blockly.MenuItem.prototype.getValue = function() {
/**
* Set the menu accessibility role.
* @param {!Blockly.utils.aria.Role|string} roleName role name.
* @param {!Blockly.utils.aria.Role} roleName Role name.
* @package
*/
Blockly.MenuItem.prototype.setRole = function(roleName) {

View File

@@ -188,10 +188,8 @@ Blockly.tree.BaseNode.prototype.initAccessibility = function() {
label.id = this.getId() + '.label';
}
Blockly.utils.aria.setRole(el,
Blockly.utils.aria.Role.TREEITEM);
Blockly.utils.aria.setState(el,
Blockly.utils.aria.State.SELECTED, false);
Blockly.utils.aria.setRole(el, Blockly.utils.aria.Role.TREEITEM);
Blockly.utils.aria.setState(el, Blockly.utils.aria.State.SELECTED, false);
Blockly.utils.aria.setState(el,
Blockly.utils.aria.State.LEVEL, this.getDepth());
if (label) {

View File

@@ -44,6 +44,34 @@ goog.require('Blockly.utils.style');
Blockly.tree.TreeControl = function(toolbox, config) {
this.toolbox_ = toolbox;
/**
* Focus event data.
* @type {?Blockly.EventData}
* @private
*/
this.onFocusWrapper_ = null;
/**
* Blur event data.
* @type {?Blockly.EventData}
* @private
*/
this.onBlurWrapper_ = null;
/**
* Click event data.
* @type {?Blockly.EventData}
* @private
*/
this.onClickWrapper_ = null;
/**
* Key down event data.
* @type {?Blockly.EventData}
* @private
*/
this.onKeydownWrapper_ = null;
Blockly.tree.BaseNode.call(this, '', config);
// The root is open and selected by default.
@@ -263,7 +291,6 @@ Blockly.tree.TreeControl.prototype.exitDocument = function() {
/**
* Adds the event listeners to the tree.
* @private
* @suppress {deprecated} Suppress deprecated bindEvent_ call.
*/
Blockly.tree.TreeControl.prototype.attachEvents_ = function() {
var el = this.getElement();
@@ -273,10 +300,8 @@ Blockly.tree.TreeControl.prototype.attachEvents_ = function() {
'focus', this, this.handleFocus_);
this.onBlurWrapper_ = Blockly.bindEvent_(el,
'blur', this, this.handleBlur_);
this.onClickWrapper_ = Blockly.bindEventWithChecks_(el,
'click', this, this.handleMouseEvent_);
this.onKeydownWrapper_ = Blockly.bindEvent_(el,
'keydown', this, this.handleKeyEvent_);
};
@@ -286,10 +311,22 @@ Blockly.tree.TreeControl.prototype.attachEvents_ = function() {
* @private
*/
Blockly.tree.TreeControl.prototype.detachEvents_ = function() {
Blockly.unbindEvent_(this.onFocusWrapper_);
Blockly.unbindEvent_(this.onBlurWrapper_);
Blockly.unbindEvent_(this.onClickWrapper_);
Blockly.unbindEvent_(this.onKeydownWrapper_);
if (this.onFocusWrapper_) {
Blockly.unbindEvent_(this.onFocusWrapper_);
this.onFocusWrapper_ = null;
}
if (this.onBlurWrapper_) {
Blockly.unbindEvent_(this.onBlurWrapper_);
this.onBlurWrapper_ = null;
}
if (this.onClickWrapper_) {
Blockly.unbindEvent_(this.onClickWrapper_);
this.onClickWrapper_ = null;
}
if (this.onKeydownWrapper_) {
Blockly.unbindEvent_(this.onKeydownWrapper_);
this.onKeydownWrapper_ = null;
}
};
/**

View File

@@ -85,16 +85,16 @@ Blockly.Connection.prototype.shadowDom_ = null;
/**
* Horizontal location of this connection.
* @type {number}
* @protected
* @package
*/
Blockly.Connection.prototype.x_ = 0;
Blockly.Connection.prototype.x = 0;
/**
* Vertical location of this connection.
* @type {number}
* @protected
* @package
*/
Blockly.Connection.prototype.y_ = 0;
Blockly.Connection.prototype.y = 0;
/**
* Connect two connections together. This is the connection on the superior
@@ -121,7 +121,7 @@ Blockly.Connection.prototype.connect_ = function(childConnection) {
if (orphanBlock.isShadow()) {
// Save the shadow block so that field values are preserved.
shadowDom = Blockly.Xml.blockToDom(orphanBlock);
orphanBlock.dispose();
orphanBlock.dispose(false);
orphanBlock = null;
} else if (parentConnection.type == Blockly.INPUT_VALUE) {
// Value connections.
@@ -132,7 +132,7 @@ Blockly.Connection.prototype.connect_ = function(childConnection) {
// Attempt to reattach the orphan at the end of the newly inserted
// block. Since this block may be a row, walk down to the end
// or to the first (and only) shadow block.
var connection = Blockly.Connection.lastConnectionInRow_(
var connection = Blockly.Connection.lastConnectionInRow(
childBlock, orphanBlock);
if (connection) {
orphanBlock.outputConnection.connect(connection);
@@ -153,7 +153,7 @@ Blockly.Connection.prototype.connect_ = function(childConnection) {
if (nextBlock && !nextBlock.isShadow()) {
newBlock = nextBlock;
} else {
if (orphanBlock.previousConnection.checkType_(
if (orphanBlock.previousConnection.checkType(
newBlock.nextConnection)) {
newBlock.nextConnection.connect(orphanBlock.previousConnection);
orphanBlock = null;
@@ -201,8 +201,7 @@ Blockly.Connection.prototype.connect_ = function(childConnection) {
};
/**
* Dispose of this connection. Deal with connected blocks and remove this
* connection from the database.
* Dispose of this connection and deal with connected blocks.
* @package
*/
Blockly.Connection.prototype.dispose = function() {
@@ -213,7 +212,7 @@ Blockly.Connection.prototype.dispose = function() {
var targetBlock = this.targetBlock();
if (targetBlock.isShadow()) {
// Destroy the attached shadow block & its children.
targetBlock.dispose();
targetBlock.dispose(false);
} else {
// Disconnect the attached normal block.
targetBlock.unplug();
@@ -225,7 +224,7 @@ Blockly.Connection.prototype.dispose = function() {
/**
* Get the source block for this connection.
* @return {Blockly.Block} The source block, or null if there is none.
* @return {!Blockly.Block} The source block.
*/
Blockly.Connection.prototype.getSourceBlock = function() {
return this.sourceBlock_;
@@ -254,9 +253,9 @@ Blockly.Connection.prototype.isConnected = function() {
* @param {Blockly.Connection} target Connection to check compatibility with.
* @return {number} Blockly.Connection.CAN_CONNECT if the connection is legal,
* an error code otherwise.
* @private
* @package
*/
Blockly.Connection.prototype.canConnectWithReason_ = function(target) {
Blockly.Connection.prototype.canConnectWithReason = function(target) {
if (!target) {
return Blockly.Connection.REASON_TARGET_NULL;
}
@@ -273,7 +272,7 @@ Blockly.Connection.prototype.canConnectWithReason_ = function(target) {
return Blockly.Connection.REASON_WRONG_TYPE;
} else if (blockA && blockB && blockA.workspace !== blockB.workspace) {
return Blockly.Connection.REASON_DIFFERENT_WORKSPACES;
} else if (!this.checkType_(target)) {
} else if (!this.checkType(target)) {
return Blockly.Connection.REASON_CHECKS_FAILED;
} else if (blockA.isShadow() && !blockB.isShadow()) {
return Blockly.Connection.REASON_SHADOW_PARENT;
@@ -286,10 +285,10 @@ Blockly.Connection.prototype.canConnectWithReason_ = function(target) {
* and throws an exception if they are not.
* @param {Blockly.Connection} target The connection to check compatibility
* with.
* @private
* @package
*/
Blockly.Connection.prototype.checkConnection_ = function(target) {
switch (this.canConnectWithReason_(target)) {
Blockly.Connection.prototype.checkConnection = function(target) {
switch (this.canConnectWithReason(target)) {
case Blockly.Connection.CAN_CONNECT:
break;
case Blockly.Connection.REASON_SELF_CONNECTION:
@@ -329,7 +328,7 @@ Blockly.Connection.prototype.canConnectToPrevious_ = function(candidate) {
}
// Don't let blocks try to connect to themselves or ones they nest.
if (Blockly.draggingConnections_.indexOf(candidate) != -1) {
if (Blockly.draggingConnections.indexOf(candidate) != -1) {
return false;
}
@@ -359,7 +358,7 @@ Blockly.Connection.prototype.isConnectionAllowed = function(candidate) {
return false;
}
// Type checking.
var canConnect = this.canConnectWithReason_(candidate);
var canConnect = this.canConnectWithReason(candidate);
if (canConnect != Blockly.Connection.CAN_CONNECT) {
return false;
}
@@ -406,7 +405,7 @@ Blockly.Connection.prototype.isConnectionAllowed = function(candidate) {
}
// Don't let blocks try to connect to themselves or ones they nest.
if (Blockly.draggingConnections_.indexOf(candidate) != -1) {
if (Blockly.draggingConnections.indexOf(candidate) != -1) {
return false;
}
@@ -415,7 +414,7 @@ Blockly.Connection.prototype.isConnectionAllowed = function(candidate) {
/**
* Behavior after a connection attempt fails.
* @param {Blockly.Connection} _otherConnection Connection that this connection
* @param {!Blockly.Connection} _otherConnection Connection that this connection
* failed to connect to.
* @package
*/
@@ -432,7 +431,7 @@ Blockly.Connection.prototype.connect = function(otherConnection) {
// Already connected together. NOP.
return;
}
this.checkConnection_(otherConnection);
this.checkConnection(otherConnection);
var eventGroup = Blockly.Events.getGroup();
if (!eventGroup) {
Blockly.Events.setGroup(true);
@@ -474,11 +473,11 @@ Blockly.Connection.connectReciprocally_ = function(first, second) {
* @private
*/
Blockly.Connection.singleConnection_ = function(block, orphanBlock) {
var connection = false;
var connection = null;
for (var i = 0; i < block.inputList.length; i++) {
var thisConnection = block.inputList[i].connection;
if (thisConnection && thisConnection.type == Blockly.INPUT_VALUE &&
orphanBlock.outputConnection.checkType_(thisConnection)) {
orphanBlock.outputConnection.checkType(thisConnection)) {
if (connection) {
return null; // More than one connection.
}
@@ -497,15 +496,14 @@ Blockly.Connection.singleConnection_ = function(block, orphanBlock) {
* @param {!Blockly.Block} startBlock The block on which to start the search.
* @param {!Blockly.Block} orphanBlock The block that is looking for a home.
* @return {Blockly.Connection} The suitable connection point on the chain
* of blocks, or null.
* @private
* of blocks, or null.
* @package
*/
Blockly.Connection.lastConnectionInRow_ = function(startBlock, orphanBlock) {
Blockly.Connection.lastConnectionInRow = function(startBlock, orphanBlock) {
var newBlock = startBlock;
var connection;
while (connection = Blockly.Connection.singleConnection_(
/** @type {!Blockly.Block} */ (newBlock), orphanBlock)) {
// '=' is intentional in line above.
while ((connection = Blockly.Connection.singleConnection_(
/** @type {!Blockly.Block} */ (newBlock), orphanBlock))) {
newBlock = connection.targetBlock();
if (!newBlock || newBlock.isShadow()) {
return connection;
@@ -607,9 +605,8 @@ Blockly.Connection.prototype.targetBlock = function() {
* value type system. E.g. square_root("Hello") is not compatible.
* @param {!Blockly.Connection} otherConnection Connection to compare against.
* @return {boolean} True if the connections share a type.
* @protected
*/
Blockly.Connection.prototype.checkType_ = function(otherConnection) {
Blockly.Connection.prototype.checkType = function(otherConnection) {
if (!this.check_ || !otherConnection.check_) {
// One or both sides are promiscuous enough that anything will fit.
return true;
@@ -625,12 +622,27 @@ Blockly.Connection.prototype.checkType_ = function(otherConnection) {
};
/**
* Function to be called when this connection's compatible types have changed.
* Is this connection compatible with another connection with respect to the
* value type system. E.g. square_root("Hello") is not compatible.
* @param {!Blockly.Connection} otherConnection Connection to compare against.
* @return {boolean} True if the connections share a type.
* @private
* @deprecated October 2019, use connection.checkType instead.
*/
Blockly.Connection.prototype.checkType_ = function(otherConnection) {
console.warn('Deprecated call to Blockly.Connection.prototype.checkType_, ' +
'use Blockly.Connection.prototype.checkType instead.');
return this.checkType(otherConnection);
};
/**
* Function to be called when this connection's compatible types have changed.
* @protected
*/
Blockly.Connection.prototype.onCheckChanged_ = function() {
// The new value type may not be compatible with the existing connection.
if (this.isConnected() && !this.checkType_(this.targetConnection)) {
if (this.isConnected() && (!this.targetConnection ||
!this.checkType(this.targetConnection))) {
var child = this.isSuperior() ? this.targetBlock() : this.sourceBlock_;
child.unplug();
}
@@ -638,8 +650,8 @@ Blockly.Connection.prototype.onCheckChanged_ = function() {
/**
* Change a connection's compatibility.
* @param {string|!Array<string>} check Compatible value type or list of value
* types. Null if all types are compatible.
* @param {?(string|!Array<string>)} check Compatible value type or list of
* value types. Null if all types are compatible.
* @return {!Blockly.Connection} The connection being modified
* (to allow chaining).
*/
@@ -693,9 +705,9 @@ Blockly.Connection.prototype.getShadowDom = function() {
* computed from the rendered positioning.
* @param {number} _maxLimit The maximum radius to another connection.
* @return {!Array.<!Blockly.Connection>} List of connections.
* @private
* @package
*/
Blockly.Connection.prototype.neighbours_ = function(_maxLimit) {
Blockly.Connection.prototype.neighbours = function(_maxLimit) {
return [];
};
@@ -736,7 +748,7 @@ Blockly.Connection.prototype.toString = function() {
msg = 'Next Connection of ';
} else {
var parentInput = null;
for (var i = 0, input; input = block.inputList[i]; i++) {
for (var i = 0, input; (input = block.inputList[i]); i++) {
if (input.connection == this) {
parentInput = input;
break;

View File

@@ -16,14 +16,16 @@
*/
/**
* @fileoverview Components for managing connections between blocks.
* @fileoverview A database of all the rendered connections that could
* possibly be connected to (i.e. not collapsed, etc).
* Sorted by y coordinate.
* @author fraser@google.com (Neil Fraser)
*/
'use strict';
goog.provide('Blockly.ConnectionDB');
goog.require('Blockly.Connection');
goog.require('Blockly.RenderedConnection');
/**
@@ -34,54 +36,52 @@ goog.require('Blockly.Connection');
*/
Blockly.ConnectionDB = function() {
/**
* Array of connections sorted by y coordinate.
* @type {!Array.<!Blockly.Connection>}
* Array of connections sorted by y position in workspace units.
* @type {!Array.<!Blockly.RenderedConnection>}
* @private
*/
this.connections_ = [];
};
/**
* Add a connection to the database. Must not already exist in DB.
* @param {!Blockly.Connection} connection The connection to be added.
* Add a connection to the database. Should not already exist in the database.
* @param {!Blockly.RenderedConnection} connection The connection to be added.
* @param {number} yPos The y position used to decide where to insert the
* connection.
* @package
*/
Blockly.ConnectionDB.prototype.addConnection = function(connection) {
if (connection.inDB_) {
throw Error('Connection already in database.');
}
if (connection.getSourceBlock().isInFlyout) {
// Don't bother maintaining a database of connections in a flyout.
return;
}
var position = this.findPositionForConnection_(connection);
this.connections_.splice(position, 0, connection);
connection.inDB_ = true;
Blockly.ConnectionDB.prototype.addConnection = function(connection, yPos) {
var index = this.calculateIndexForYPos_(yPos);
this.connections_.splice(index, 0, connection);
};
/**
* Find the given connection.
* Finds the index of the given connection.
*
* Starts by doing a binary search to find the approximate location, then
* linearly searches nearby for the exact connection.
* @param {!Blockly.Connection} conn The connection to find.
* linearly searches nearby for the exact connection.
* @param {!Blockly.RenderedConnection} conn The connection to find.
* @param {number} yPos The y position used to find the index of the connection.
* @return {number} The index of the connection, or -1 if the connection was
* not found.
* @private
*/
Blockly.ConnectionDB.prototype.findConnection = function(conn) {
Blockly.ConnectionDB.prototype.findIndexOfConnection_ = function(conn, yPos) {
if (!this.connections_.length) {
return -1;
}
var bestGuess = this.findPositionForConnection_(conn);
var bestGuess = this.calculateIndexForYPos_(yPos);
if (bestGuess >= this.connections_.length) {
// Not in list
return -1;
}
var yPos = conn.y_;
yPos = conn.y;
// Walk forward and back on the y axis looking for the connection.
var pointerMin = bestGuess;
var pointerMax = bestGuess;
while (pointerMin >= 0 && this.connections_[pointerMin].y_ == yPos) {
while (pointerMin >= 0 && this.connections_[pointerMin].y == yPos) {
if (this.connections_[pointerMin] == conn) {
return pointerMin;
}
@@ -89,7 +89,7 @@ Blockly.ConnectionDB.prototype.findConnection = function(conn) {
}
while (pointerMax < this.connections_.length &&
this.connections_[pointerMax].y_ == yPos) {
this.connections_[pointerMax].y == yPos) {
if (this.connections_[pointerMax] == conn) {
return pointerMax;
}
@@ -99,15 +99,13 @@ Blockly.ConnectionDB.prototype.findConnection = function(conn) {
};
/**
* Finds a candidate position for inserting this connection into the list.
* This will be in the correct y order but makes no guarantees about ordering in
* the x axis.
* @param {!Blockly.Connection} connection The connection to insert.
* Finds the correct index for the given y position.
* @param {number} yPos The y position used to decide where to
* insert the connection.
* @return {number} The candidate index.
* @private
*/
Blockly.ConnectionDB.prototype.findPositionForConnection_ = function(
connection) {
Blockly.ConnectionDB.prototype.calculateIndexForYPos_ = function(yPos) {
if (!this.connections_.length) {
return 0;
}
@@ -115,9 +113,9 @@ Blockly.ConnectionDB.prototype.findPositionForConnection_ = function(
var pointerMax = this.connections_.length;
while (pointerMin < pointerMax) {
var pointerMid = Math.floor((pointerMin + pointerMax) / 2);
if (this.connections_[pointerMid].y_ < connection.y_) {
if (this.connections_[pointerMid].y < yPos) {
pointerMin = pointerMid + 1;
} else if (this.connections_[pointerMid].y_ > connection.y_) {
} else if (this.connections_[pointerMid].y > yPos) {
pointerMax = pointerMid;
} else {
pointerMin = pointerMid;
@@ -129,40 +127,37 @@ Blockly.ConnectionDB.prototype.findPositionForConnection_ = function(
/**
* Remove a connection from the database. Must already exist in DB.
* @param {!Blockly.Connection} connection The connection to be removed.
* @private
* @param {!Blockly.RenderedConnection} connection The connection to be removed.
* @param {number} yPos The y position used to find the index of the connection.
* @throws {Error} If the connection cannot be found in the database.
*/
Blockly.ConnectionDB.prototype.removeConnection_ = function(connection) {
if (!connection.inDB_) {
throw Error('Connection not in database.');
}
var removalIndex = this.findConnection(connection);
if (removalIndex == -1) {
Blockly.ConnectionDB.prototype.removeConnection = function(connection, yPos) {
var index = this.findIndexOfConnection_(connection, yPos);
if (index == -1) {
throw Error('Unable to find connection in connectionDB.');
}
connection.inDB_ = false;
this.connections_.splice(removalIndex, 1);
this.connections_.splice(index, 1);
};
/**
* Find all nearby connections to the given connection.
* Type checking does not apply, since this function is used for bumping.
* @param {!Blockly.Connection} connection The connection whose neighbours
* should be returned.
* @param {!Blockly.RenderedConnection} connection The connection whose
* neighbours should be returned.
* @param {number} maxRadius The maximum radius to another connection.
* @return {!Array.<!Blockly.Connection>} List of connections.
* @return {!Array.<!Blockly.RenderedConnection>} List of connections.
*/
Blockly.ConnectionDB.prototype.getNeighbours = function(connection, maxRadius) {
var db = this.connections_;
var currentX = connection.x_;
var currentY = connection.y_;
var currentX = connection.x;
var currentY = connection.y;
// Binary search to find the closest y location.
var pointerMin = 0;
var pointerMax = db.length - 2;
var pointerMid = pointerMax;
while (pointerMin < pointerMid) {
if (db[pointerMid].y_ < currentY) {
if (db[pointerMid].y < currentY) {
pointerMin = pointerMid;
} else {
pointerMax = pointerMid;
@@ -180,8 +175,8 @@ Blockly.ConnectionDB.prototype.getNeighbours = function(connection, maxRadius) {
* the other connection is less than the allowed radius.
*/
function checkConnection_(yIndex) {
var dx = currentX - db[yIndex].x_;
var dy = currentY - db[yIndex].y_;
var dx = currentX - db[yIndex].x;
var dy = currentY - db[yIndex].y;
var r = Math.sqrt(dx * dx + dy * dy);
if (r <= maxRadius) {
neighbours.push(db[yIndex]);
@@ -204,7 +199,6 @@ Blockly.ConnectionDB.prototype.getNeighbours = function(connection, maxRadius) {
return neighbours;
};
/**
* Is the candidate connection close to the reference connection.
* Extremely fast; only looks at Y distance.
@@ -215,20 +209,20 @@ Blockly.ConnectionDB.prototype.getNeighbours = function(connection, maxRadius) {
* @private
*/
Blockly.ConnectionDB.prototype.isInYRange_ = function(index, baseY, maxRadius) {
return (Math.abs(this.connections_[index].y_ - baseY) <= maxRadius);
return (Math.abs(this.connections_[index].y - baseY) <= maxRadius);
};
/**
* Find the closest compatible connection to this connection.
* @param {!Blockly.Connection} conn The connection searching for a compatible
* @param {!Blockly.RenderedConnection} conn The connection searching for a compatible
* mate.
* @param {number} maxRadius The maximum radius to another connection.
* @param {!Blockly.utils.Coordinate} dxy Offset between this connection's
* location in the database and the current location (as a result of
* dragging).
* @return {!{connection: ?Blockly.Connection, radius: number}} Contains two
* properties:' connection' which is either another connection or null,
* and 'radius' which is the distance.
* @return {!{connection: Blockly.RenderedConnection, radius: number}}
* Contains two properties: 'connection' which is either another
* connection or null, and 'radius' which is the distance.
*/
Blockly.ConnectionDB.prototype.searchForClosest = function(conn, maxRadius,
dxy) {
@@ -238,16 +232,16 @@ Blockly.ConnectionDB.prototype.searchForClosest = function(conn, maxRadius,
}
// Stash the values of x and y from before the drag.
var baseY = conn.y_;
var baseX = conn.x_;
var baseY = conn.y;
var baseX = conn.x;
conn.x_ = baseX + dxy.x;
conn.y_ = baseY + dxy.y;
conn.x = baseX + dxy.x;
conn.y = baseY + dxy.y;
// findPositionForConnection finds an index for insertion, which is always
// calculateIndexForYPos_ finds an index for insertion, which is always
// after any block with the same y index. We want to search both forward
// and back, so search on both sides of the index.
var closestIndex = this.findPositionForConnection_(conn);
var closestIndex = this.calculateIndexForYPos_(conn.y);
var bestConnection = null;
var bestRadius = maxRadius;
@@ -255,7 +249,7 @@ Blockly.ConnectionDB.prototype.searchForClosest = function(conn, maxRadius,
// Walk forward and back on the y axis looking for the closest x,y point.
var pointerMin = closestIndex - 1;
while (pointerMin >= 0 && this.isInYRange_(pointerMin, conn.y_, maxRadius)) {
while (pointerMin >= 0 && this.isInYRange_(pointerMin, conn.y, maxRadius)) {
temp = this.connections_[pointerMin];
if (conn.isConnectionAllowed(temp, bestRadius)) {
bestConnection = temp;
@@ -266,7 +260,7 @@ Blockly.ConnectionDB.prototype.searchForClosest = function(conn, maxRadius,
var pointerMax = closestIndex;
while (pointerMax < this.connections_.length &&
this.isInYRange_(pointerMax, conn.y_, maxRadius)) {
this.isInYRange_(pointerMax, conn.y, maxRadius)) {
temp = this.connections_[pointerMax];
if (conn.isConnectionAllowed(temp, bestRadius)) {
bestConnection = temp;
@@ -276,8 +270,8 @@ Blockly.ConnectionDB.prototype.searchForClosest = function(conn, maxRadius,
}
// Reset the values of x and y.
conn.x_ = baseX;
conn.y_ = baseY;
conn.x = baseX;
conn.y = baseY;
// If there were no valid connections, bestConnection will be null.
return {connection: bestConnection, radius: bestRadius};

View File

@@ -89,7 +89,7 @@ Blockly.ContextMenu.populate_ = function(options, rtl) {
*/
var menu = new Blockly.Menu();
menu.setRightToLeft(rtl);
for (var i = 0, option; option = options[i]; i++) {
for (var i = 0, option; (option = options[i]); i++) {
var menuItem = new Blockly.MenuItem(option.text);
menuItem.setRightToLeft(rtl);
menu.addChild(menuItem, true);
@@ -166,6 +166,7 @@ Blockly.ContextMenu.hide = function() {
Blockly.ContextMenu.currentBlock = null;
if (Blockly.ContextMenu.eventWrapper_) {
Blockly.unbindEvent_(Blockly.ContextMenu.eventWrapper_);
Blockly.ContextMenu.eventWrapper_ = null;
}
};
@@ -243,7 +244,7 @@ Blockly.ContextMenu.blockHelpOption = function(block) {
enabled: !!url,
text: Blockly.Msg['HELP'],
callback: function() {
block.showHelp_();
block.showHelp();
}
};
return helpOption;
@@ -261,7 +262,7 @@ Blockly.ContextMenu.blockDuplicateOption = function(block) {
text: Blockly.Msg['DUPLICATE_BLOCK'],
enabled: enabled,
callback: function() {
Blockly.duplicate_(block);
Blockly.duplicate(block);
}
};
return duplicateOption;
@@ -279,7 +280,7 @@ Blockly.ContextMenu.blockCommentOption = function(block) {
enabled: !Blockly.utils.userAgent.IE
};
// If there's already a comment, add an option to delete it.
if (block.comment) {
if (block.getCommentIcon()) {
commentOption.text = Blockly.Msg['REMOVE_COMMENT'];
commentOption.callback = function() {
block.setCommentText(null);
@@ -303,7 +304,7 @@ Blockly.ContextMenu.blockCommentOption = function(block) {
*/
Blockly.ContextMenu.commentDeleteOption = function(comment) {
var deleteOption = {
text: Blockly.Msg.REMOVE_COMMENT,
text: Blockly.Msg['REMOVE_COMMENT'],
enabled: true,
callback: function() {
Blockly.Events.setGroup(true);
@@ -323,10 +324,10 @@ Blockly.ContextMenu.commentDeleteOption = function(comment) {
*/
Blockly.ContextMenu.commentDuplicateOption = function(comment) {
var duplicateOption = {
text: Blockly.Msg.DUPLICATE_COMMENT,
text: Blockly.Msg['DUPLICATE_COMMENT'],
enabled: true,
callback: function() {
Blockly.duplicate_(comment);
Blockly.duplicate(comment);
}
};
return duplicateOption;
@@ -339,6 +340,8 @@ Blockly.ContextMenu.commentDuplicateOption = function(comment) {
* @param {!Event} e The right-click mouse event.
* @return {!Object} A menu option, containing text, enabled, and a callback.
* @package
* @suppress {strictModuleDepCheck,checkTypes} Suppress checks while workspace
* comments are not bundled in.
*/
Blockly.ContextMenu.workspaceCommentOption = function(ws, e) {
if (!Blockly.WorkspaceCommentSvg) {
@@ -348,7 +351,7 @@ Blockly.ContextMenu.workspaceCommentOption = function(ws, e) {
// location of the mouse event.
var addWsComment = function() {
var comment = new Blockly.WorkspaceCommentSvg(
ws, Blockly.Msg.WORKSPACE_COMMENT_DEFAULT_TEXT,
ws, Blockly.Msg['WORKSPACE_COMMENT_DEFAULT_TEXT'],
Blockly.WorkspaceCommentSvg.DEFAULT_SIZE,
Blockly.WorkspaceCommentSvg.DEFAULT_SIZE);
@@ -388,7 +391,7 @@ Blockly.ContextMenu.workspaceCommentOption = function(ws, e) {
// that they won't be able to edit.
enabled: !Blockly.utils.userAgent.IE
};
wsCommentOption.text = Blockly.Msg.ADD_COMMENT;
wsCommentOption.text = Blockly.Msg['ADD_COMMENT'];
wsCommentOption.callback = function() {
addWsComment();
};

View File

@@ -77,6 +77,7 @@ Blockly.Css.inject = function(hasCss, pathToMedia) {
// Inject CSS tag at start of head.
var cssNode = document.createElement('style');
cssNode.id = 'blockly-common-style';
var cssTextNode = document.createTextNode(text);
cssNode.appendChild(cssTextNode);
document.head.insertBefore(cssNode, document.head.firstChild);
@@ -169,12 +170,14 @@ Blockly.Css.CONTENT = [
'}',
'.blocklyDropDownDiv {',
'position: fixed;',
'position: absolute;',
'left: 0;',
'top: 0;',
'z-index: 1000;',
'display: none;',
'border: 1px solid;',
'border-color: #dadce0;',
'background-color: #fff;',
'border-radius: 2px;',
'padding: 4px;',
'box-shadow: 0px 0px 3px 1px rgba(0,0,0,.3);',
@@ -213,14 +216,14 @@ Blockly.Css.CONTENT = [
'cursor: pointer;',
'}',
'.arrowTop {',
'.blocklyArrowTop {',
'border-top: 1px solid;',
'border-left: 1px solid;',
'border-top-left-radius: 4px;',
'border-color: inherit;',
'}',
'.arrowBottom {',
'.blocklyArrowBottom {',
'border-bottom: 1px solid;',
'border-right: 1px solid;',
'border-bottom-right-radius: 4px;',
@@ -254,11 +257,6 @@ Blockly.Css.CONTENT = [
'stroke-width: 1;',
'}',
'.blocklySelected>.blocklyPath {',
'stroke: #fc3;',
'stroke-width: 3px;',
'}',
'.blocklySelected>.blocklyPathLight {',
'display: none;',
'}',
@@ -325,22 +323,6 @@ Blockly.Css.CONTENT = [
'stroke: none',
'}',
'.blocklyReplaceable .blocklyPath {',
'fill-opacity: .5;',
'}',
'.blocklyReplaceable .blocklyPathLight,',
'.blocklyReplaceable .blocklyPathDark {',
'display: none;',
'}',
'.blocklyText {',
'cursor: default;',
'fill: #fff;',
'font-family: sans-serif;',
'font-size: 11pt;',
'}',
'.blocklyMultilineText {',
'font-family: monospace;',
'}',
@@ -349,22 +331,6 @@ Blockly.Css.CONTENT = [
'pointer-events: none;',
'}',
'.blocklyNonEditableText>rect,',
'.blocklyEditableText>rect {',
'fill: #fff;',
'fill-opacity: .6;',
'}',
'.blocklyNonEditableText>text,',
'.blocklyEditableText>text {',
'fill: #000;',
'}',
'.blocklyEditableText:hover>rect {',
'stroke: #fff;',
'stroke-width: 2;',
'}',
'.blocklyBubbleText {',
'fill: #000;',
'}',
@@ -374,6 +340,10 @@ Blockly.Css.CONTENT = [
'z-index: 20;',
'}',
'.blocklyText text {',
'cursor: default;',
'}',
/*
Don't allow users to select text. It gets annoying when trying to
drag a block and selected text moves instead.
@@ -417,86 +387,17 @@ Blockly.Css.CONTENT = [
'padding: 0;',
'}',
'.blocklyCommentForeignObject {',
'position: relative;',
'z-index: 0;',
'}',
'.blocklyCommentRect {',
'fill: #E7DE8E;',
'stroke: #bcA903;',
'stroke-width: 1px',
'}',
'.blocklyCommentTarget {',
'fill: transparent;',
'stroke: #bcA903;',
'}',
'.blocklyCommentTargetFocused {',
'fill: none;',
'}',
'.blocklyCommentHandleTarget {',
'fill: none;',
'}',
'.blocklyCommentHandleTargetFocused {',
'fill: transparent;',
'}',
'.blocklyFocused>.blocklyCommentRect {',
'fill: #B9B272;',
'stroke: #B9B272;',
'}',
'.blocklySelected>.blocklyCommentTarget {',
'stroke: #fc3;',
'stroke-width: 3px;',
'}',
'.blocklyCommentTextarea {',
'background-color: #fef49c;',
'border: 0;',
'outline: 0;',
'margin: 0;',
'padding: 3px;',
'resize: none;',
'display: block;',
'overflow: hidden;',
'}',
'.blocklyCommentDeleteIcon {',
'cursor: pointer;',
'fill: #000;',
'display: none',
'}',
'.blocklySelected > .blocklyCommentDeleteIcon {',
'display: block',
'}',
'.blocklyDeleteIconShape {',
'fill: #000;',
'stroke: #000;',
'stroke-width: 1px;',
'}',
'.blocklyDeleteIconShape.blocklyDeleteIconHighlighted {',
'stroke: #fc3;',
'}',
'.blocklyHtmlInput {',
'border: none;',
'border-radius: 4px;',
'font-family: sans-serif;',
'height: 100%;',
'margin: 0;',
'outline: none;',
'padding: 0;',
'width: 100%;',
'text-align: center;',
'display: block;',
'box-sizing: border-box;',
'}',
/* Edge and IE introduce a close icon when the input value is longer than a
@@ -588,7 +489,7 @@ Blockly.Css.CONTENT = [
'padding-right: 28px;',
'}',
'.blocklyVerticalCursor {',
'.blocklyVerticalMarker {',
'stroke-width: 3px;',
'fill: rgba(255,255,255,.5);',
'}',
@@ -698,7 +599,8 @@ Blockly.Css.CONTENT = [
'.blocklyWidgetDiv .goog-menuitem-content,',
'.blocklyDropDownDiv .goog-menuitem-content {',
'font: normal 13px Arial, sans-serif;',
'font-family: Arial, sans-serif;',
'font-size: 13px;',
'}',
'.blocklyWidgetDiv .goog-menuitem-content {',
@@ -772,6 +674,16 @@ Blockly.Css.CONTENT = [
'.blocklyDropDownDiv .goog-menuitem-rtl .goog-menuitem-icon {',
'float: right;',
'margin-right: -24px;',
'}'
'}',
'.blocklyComputeCanvas {',
'position: absolute;',
'width: 0;',
'height: 0;',
'}',
'.blocklyNoPointerEvents {',
'pointer-events: none;',
'}',
/* eslint-enable indent */
];

View File

@@ -34,17 +34,11 @@ goog.require('Blockly.utils.style');
/**
* Class for drop-down div.
* @constructor
* @package
*/
Blockly.DropDownDiv = function() {
};
/**
* The div element. Set once by Blockly.DropDownDiv.createDom.
* @type {Element}
* @private
*/
Blockly.DropDownDiv.DIV_ = null;
/**
* Drop-downs will appear within the bounds of this element if possible.
* Set in Blockly.DropDownDiv.setBoundsElement.
@@ -68,21 +62,24 @@ Blockly.DropDownDiv.owner_ = null;
Blockly.DropDownDiv.positionToField_ = null;
/**
* Arrow size in px. Should match the value in CSS (need to position pre-render).
* Arrow size in px. Should match the value in CSS
* (need to position pre-render).
* @type {number}
* @const
*/
Blockly.DropDownDiv.ARROW_SIZE = 16;
/**
* Drop-down border size in px. Should match the value in CSS (need to position the arrow).
* Drop-down border size in px. Should match the value in CSS (need to position
* the arrow).
* @type {number}
* @const
*/
Blockly.DropDownDiv.BORDER_SIZE = 1;
/**
* Amount the arrow must be kept away from the edges of the main drop-down div, in px.
* Amount the arrow must be kept away from the edges of the main drop-down div,
* in px.
* @type {number}
* @const
*/
@@ -102,35 +99,38 @@ Blockly.DropDownDiv.PADDING_Y = 16;
*/
Blockly.DropDownDiv.ANIMATION_TIME = 0.25;
/**
* The default dropdown div border color.
* @type {string}
* @const
*/
Blockly.DropDownDiv.DEFAULT_DROPDOWN_BORDER_COLOR = '#dadce0';
/**
* The default dropdown div color.
* @type {string}
* @const
*/
Blockly.DropDownDiv.DEFAULT_DROPDOWN_COLOR = '#fff';
/**
* Timer for animation out, to be cleared if we need to immediately hide
* without disrupting new shows.
* @type {?number}
* @private
*/
Blockly.DropDownDiv.animateOutTimer_ = null;
/**
* Callback for when the drop-down is hidden.
* @type {?Function}
* @private
*/
Blockly.DropDownDiv.onHide_ = null;
/**
* A class name representing the current owner's workspace renderer.
* @type {?string}
* @private
*/
Blockly.DropDownDiv.rendererClassName_ = null;
/**
* A class name representing the current owner's workspace theme.
* @type {?string}
* @private
*/
Blockly.DropDownDiv.themeClassName_ = null;
/**
* Create and insert the DOM element for this div.
* @package
*/
Blockly.DropDownDiv.createDom = function() {
if (Blockly.DropDownDiv.DIV_) {
@@ -138,19 +138,32 @@ Blockly.DropDownDiv.createDom = function() {
}
var div = document.createElement('div');
div.className = 'blocklyDropDownDiv';
div.style.backgroundColor = Blockly.DropDownDiv.DEFAULT_DROPDOWN_COLOR;
div.style.borderColor = Blockly.DropDownDiv.DEFAULT_DROPDOWN_BORDER_COLOR;
document.body.appendChild(div);
/**
* The div element.
* @type {!Element}
* @private
*/
Blockly.DropDownDiv.DIV_ = div;
var content = document.createElement('div');
content.className = 'blocklyDropDownContent';
div.appendChild(content);
/**
* The content element.
* @type {!Element}
* @private
*/
Blockly.DropDownDiv.content_ = content;
var arrow = document.createElement('div');
arrow.className = 'blocklyDropDownArrow';
div.appendChild(arrow);
/**
* The arrow element.
* @type {!Element}
* @private
*/
Blockly.DropDownDiv.arrow_ = arrow;
Blockly.DropDownDiv.DIV_.style.opacity = 0;
@@ -205,14 +218,6 @@ Blockly.DropDownDiv.setColour = function(backgroundColour, borderColour) {
Blockly.DropDownDiv.DIV_.style.borderColor = borderColour;
};
/**
* Set the category for the drop-down.
* @param {string} category The new category for the drop-down.
*/
Blockly.DropDownDiv.setCategory = function(category) {
Blockly.DropDownDiv.DIV_.setAttribute('data-category', category);
};
/**
* Shortcut to show and place the drop-down with positioning determined
* by a particular block. The primary position will be below the block,
@@ -228,25 +233,9 @@ Blockly.DropDownDiv.setCategory = function(category) {
*/
Blockly.DropDownDiv.showPositionedByBlock = function(field, block,
opt_onHide, opt_secondaryYOffset) {
var scale = block.workspace.scale;
var bBox = {width: block.width, height: block.height};
bBox.width *= scale;
bBox.height *= scale;
var position = block.getSvgRoot().getBoundingClientRect();
// If we can fit it, render below the block.
var primaryX = position.left + bBox.width / 2;
var primaryY = position.top + bBox.height;
// If we can't fit it, render above the entire parent block.
var secondaryX = primaryX;
var secondaryY = position.top;
if (opt_secondaryYOffset) {
secondaryY += opt_secondaryYOffset;
}
// Set bounds to workspace; show the drop-down.
Blockly.DropDownDiv.setBoundsElement(
block.workspace.getParentSvg().parentNode);
return Blockly.DropDownDiv.show(
field, block.RTL, primaryX, primaryY, secondaryX, secondaryY, opt_onHide);
return Blockly.DropDownDiv.showPositionedByRect_(
Blockly.DropDownDiv.getScaledBboxOfBlock_(block),
field, opt_onHide, opt_secondaryYOffset);
};
/**
@@ -263,19 +252,68 @@ Blockly.DropDownDiv.showPositionedByBlock = function(field, block,
*/
Blockly.DropDownDiv.showPositionedByField = function(field,
opt_onHide, opt_secondaryYOffset) {
var position = field.getSvgRoot().getBoundingClientRect();
Blockly.DropDownDiv.positionToField_ = true;
return Blockly.DropDownDiv.showPositionedByRect_(
Blockly.DropDownDiv.getScaledBboxOfField_(field),
field, opt_onHide, opt_secondaryYOffset);
};
/**
* Get the scaled bounding box of a block.
* @param {!Blockly.Block} block The block.
* @return {!Blockly.utils.Rect} The scaled bounding box of the block.
* @private
*/
Blockly.DropDownDiv.getScaledBboxOfBlock_ = function(block) {
var blockSvg = block.getSvgRoot();
var bBox = blockSvg.getBBox();
var scale = block.workspace.scale;
var scaledHeight = bBox.height * scale;
var scaledWidth = bBox.width * scale;
var xy = Blockly.utils.style.getPageOffset(blockSvg);
return new Blockly.utils.Rect(
xy.y, xy.y + scaledHeight, xy.x, xy.x + scaledWidth);
};
/**
* Get the scaled bounding box of a field.
* @param {!Blockly.Field} field The field.
* @return {!Blockly.utils.Rect} The scaled bounding box of the field.
* @private
*/
Blockly.DropDownDiv.getScaledBboxOfField_ = function(field) {
var bBox = field.getScaledBBox();
return new Blockly.utils.Rect(
bBox.top, bBox.bottom, bBox.left, bBox.right);
};
/**
* Helper method to show and place the drop-down with positioning determined
* by a scaled bounding box. The primary position will be below the rect,
* and the secondary position above the rect. Drop-down will be constrained to
* the block's workspace.
* @param {!Blockly.utils.Rect} bBox The scaled bounding box.
* @param {!Blockly.Field} field The field to position the dropdown against.
* @param {Function=} opt_onHide Optional callback for when the drop-down is
* hidden.
* @param {number=} opt_secondaryYOffset Optional Y offset for above-block
* positioning.
* @return {boolean} True if the menu rendered below block; false if above.
* @private
*/
Blockly.DropDownDiv.showPositionedByRect_ = function(bBox, field,
opt_onHide, opt_secondaryYOffset) {
// If we can fit it, render below the block.
var primaryX = position.left + position.width / 2;
var primaryY = position.bottom;
var primaryX = bBox.left + (bBox.right - bBox.left) / 2;
var primaryY = bBox.bottom;
// If we can't fit it, render above the entire parent block.
var secondaryX = primaryX;
var secondaryY = position.top;
var secondaryY = bBox.top;
if (opt_secondaryYOffset) {
secondaryY += opt_secondaryYOffset;
}
var sourceBlock = field.getSourceBlock();
// Set bounds to workspace; show the drop-down.
Blockly.DropDownDiv.positionToField_ = true;
Blockly.DropDownDiv.setBoundsElement(
sourceBlock.workspace.getParentSvg().parentNode);
return Blockly.DropDownDiv.show(
@@ -286,18 +324,21 @@ Blockly.DropDownDiv.showPositionedByField = function(field,
/**
* Show and place the drop-down.
* The drop-down is placed with an absolute "origin point" (x, y) - i.e.,
* the arrow will point at this origin and box will positioned below or above it.
* If we can maintain the container bounds at the primary point, the arrow will
* point there, and the container will be positioned below it.
* If we can't maintain the container bounds at the primary point, fall-back to the
* secondary point and position above.
* the arrow will point at this origin and box will positioned below or above
* it. If we can maintain the container bounds at the primary point, the arrow
* will point there, and the container will be positioned below it.
* If we can't maintain the container bounds at the primary point, fall-back to
* the secondary point and position above.
* @param {Object} owner The object showing the drop-down
* @param {boolean} rtl Right-to-left (true) or left-to-right (false).
* @param {number} primaryX Desired origin point x, in absolute px
* @param {number} primaryY Desired origin point y, in absolute px
* @param {number} secondaryX Secondary/alternative origin point x, in absolute px
* @param {number} secondaryY Secondary/alternative origin point y, in absolute px
* @param {Function=} opt_onHide Optional callback for when the drop-down is hidden
* @param {number} primaryX Desired origin point x, in absolute px.
* @param {number} primaryY Desired origin point y, in absolute px.
* @param {number} secondaryX Secondary/alternative origin point x, in absolute
* px.
* @param {number} secondaryY Secondary/alternative origin point y, in absolute
* px.
* @param {Function=} opt_onHide Optional callback for when the drop-down is
* hidden.
* @return {boolean} True if the menu rendered at the primary origin point.
* @package
*/
@@ -305,21 +346,16 @@ Blockly.DropDownDiv.show = function(owner, rtl, primaryX, primaryY,
secondaryX, secondaryY, opt_onHide) {
Blockly.DropDownDiv.owner_ = owner;
Blockly.DropDownDiv.onHide_ = opt_onHide || null;
var metrics = Blockly.DropDownDiv.getPositionMetrics(primaryX, primaryY,
secondaryX, secondaryY);
// Update arrow CSS.
if (metrics.arrowVisible) {
Blockly.DropDownDiv.arrow_.style.display = '';
Blockly.DropDownDiv.arrow_.style.transform = 'translate(' +
metrics.arrowX + 'px,' + metrics.arrowY + 'px) rotate(45deg)';
Blockly.DropDownDiv.arrow_.setAttribute('class', metrics.arrowAtTop ?
'blocklyDropDownArrow arrowTop' : 'blocklyDropDownArrow arrowBottom');
} else {
Blockly.DropDownDiv.arrow_.style.display = 'none';
}
// Set direction.
Blockly.DropDownDiv.DIV_.style.direction = rtl ? 'rtl' : 'ltr';
var div = Blockly.DropDownDiv.DIV_;
div.style.direction = rtl ? 'rtl' : 'ltr';
Blockly.DropDownDiv.rendererClassName_ =
Blockly.getMainWorkspace().getRenderer().name + '-renderer';
Blockly.DropDownDiv.themeClassName_ =
Blockly.getMainWorkspace().getTheme().name + '-theme';
Blockly.utils.dom.addClass(div, Blockly.DropDownDiv.rendererClassName_);
Blockly.utils.dom.addClass(div, Blockly.DropDownDiv.themeClassName_);
// When we change `translate` multiple times in close succession,
// Chrome may choose to wait and apply them all at once.
@@ -330,10 +366,8 @@ Blockly.DropDownDiv.show = function(owner, rtl, primaryX, primaryY,
// Using both `left`, `top` for the initial translation and then `translate`
// for the animated transition to final X, Y is a workaround.
Blockly.DropDownDiv.positionInternal_(
metrics.initialX, metrics.initialY,
metrics.finalX, metrics.finalY);
return metrics.arrowAtTop;
return Blockly.DropDownDiv.positionInternal_(
primaryX, primaryY, secondaryX, secondaryY);
};
/**
@@ -343,14 +377,16 @@ Blockly.DropDownDiv.show = function(owner, rtl, primaryX, primaryY,
* @private
*/
Blockly.DropDownDiv.getBoundsInfo_ = function() {
var boundPosition = Blockly.DropDownDiv.boundsElement_.getBoundingClientRect();
var boundSize = Blockly.utils.style.getSize(Blockly.DropDownDiv.boundsElement_);
var boundPosition = Blockly.utils.style.getPageOffset(
/** @type {!Element} */ (Blockly.DropDownDiv.boundsElement_));
var boundSize = Blockly.utils.style.getSize(
/** @type {!Element} */ (Blockly.DropDownDiv.boundsElement_));
return {
left: boundPosition.left,
right: boundPosition.left + boundSize.width,
top: boundPosition.top,
bottom: boundPosition.top + boundSize.height,
left: boundPosition.x,
right: boundPosition.x + boundSize.width,
top: boundPosition.y,
bottom: boundPosition.y + boundSize.height,
width: boundSize.width,
height: boundSize.height
};
@@ -367,8 +403,9 @@ Blockly.DropDownDiv.getBoundsInfo_ = function() {
* in absolute px.
* @return {Object} Various final metrics, including rendered positions
* for drop-down and arrow.
* @private
*/
Blockly.DropDownDiv.getPositionMetrics = function(primaryX, primaryY,
Blockly.DropDownDiv.getPositionMetrics_ = function(primaryX, primaryY,
secondaryX, secondaryY) {
var boundsInfo = Blockly.DropDownDiv.getBoundsInfo_();
var divSize = Blockly.utils.style.getSize(
@@ -376,27 +413,27 @@ Blockly.DropDownDiv.getPositionMetrics = function(primaryX, primaryY,
// Can we fit in-bounds below the target?
if (primaryY + divSize.height < boundsInfo.bottom) {
return Blockly.DropDownDiv.getPositionBelowMetrics(
return Blockly.DropDownDiv.getPositionBelowMetrics_(
primaryX, primaryY, boundsInfo, divSize);
}
// Can we fit in-bounds above the target?
if (secondaryY - divSize.height > boundsInfo.top) {
return Blockly.DropDownDiv.getPositionAboveMetrics(
return Blockly.DropDownDiv.getPositionAboveMetrics_(
secondaryX, secondaryY, boundsInfo, divSize);
}
// Can we fit outside the workspace bounds (but inside the window) below?
if (primaryY + divSize.height < document.documentElement.clientHeight) {
return Blockly.DropDownDiv.getPositionBelowMetrics(
return Blockly.DropDownDiv.getPositionBelowMetrics_(
primaryX, primaryY, boundsInfo, divSize);
}
// Can we fit outside the workspace bounds (but inside the window) above?
if (secondaryY - divSize.height > document.documentElement.clientTop) {
return Blockly.DropDownDiv.getPositionAboveMetrics(
return Blockly.DropDownDiv.getPositionAboveMetrics_(
secondaryX, secondaryY, boundsInfo, divSize);
}
// Last resort, render at top of page.
return Blockly.DropDownDiv.getPositionTopOfPageMetrics(
return Blockly.DropDownDiv.getPositionTopOfPageMetrics_(
primaryX, boundsInfo, divSize);
};
@@ -410,8 +447,9 @@ Blockly.DropDownDiv.getPositionMetrics = function(primaryX, primaryY,
* of the DropDownDiv (width & height).
* @return {Object} Various final metrics, including rendered positions
* for drop-down and arrow.
* @private
*/
Blockly.DropDownDiv.getPositionBelowMetrics = function(
Blockly.DropDownDiv.getPositionBelowMetrics_ = function(
primaryX, primaryY, boundsInfo, divSize) {
var xCoords = Blockly.DropDownDiv.getPositionX(
@@ -445,8 +483,9 @@ Blockly.DropDownDiv.getPositionBelowMetrics = function(
* of the DropDownDiv (width & height).
* @return {Object} Various final metrics, including rendered positions
* for drop-down and arrow.
* @private
*/
Blockly.DropDownDiv.getPositionAboveMetrics = function(
Blockly.DropDownDiv.getPositionAboveMetrics_ = function(
secondaryX, secondaryY, boundsInfo, divSize) {
var xCoords = Blockly.DropDownDiv.getPositionX(
@@ -478,8 +517,9 @@ Blockly.DropDownDiv.getPositionAboveMetrics = function(
* of the DropDownDiv (width & height).
* @return {Object} Various final metrics, including rendered positions
* for drop-down and arrow.
* @private
*/
Blockly.DropDownDiv.getPositionTopOfPageMetrics = function(
Blockly.DropDownDiv.getPositionTopOfPageMetrics_ = function(
sourceX, boundsInfo, divSize) {
var xCoords = Blockly.DropDownDiv.getPositionX(
@@ -506,6 +546,7 @@ Blockly.DropDownDiv.getPositionTopOfPageMetrics = function(
* @param {number} divWidth The width of the div in px.
* @return {{divX: number, arrowX: number}} An object containing metrics for
* the x positions of the left side of the DropDownDiv and the arrow.
* @package
*/
Blockly.DropDownDiv.getPositionX = function(
sourceX, boundsLeft, boundsRight, divWidth) {
@@ -600,8 +641,8 @@ Blockly.DropDownDiv.hideWithoutAnimation = function() {
div.style.top = '';
div.style.opacity = 0;
div.style.display = 'none';
div.style.backgroundColor = Blockly.DropDownDiv.DEFAULT_DROPDOWN_COLOR;
div.style.borderColor = Blockly.DropDownDiv.DEFAULT_DROPDOWN_BORDER_COLOR;
div.style.backgroundColor = '';
div.style.borderColor = '';
if (Blockly.DropDownDiv.onHide_) {
Blockly.DropDownDiv.onHide_();
@@ -609,25 +650,50 @@ Blockly.DropDownDiv.hideWithoutAnimation = function() {
}
Blockly.DropDownDiv.clearContent();
Blockly.DropDownDiv.owner_ = null;
if (Blockly.DropDownDiv.rendererClassName_) {
Blockly.utils.dom.removeClass(div, Blockly.DropDownDiv.rendererClassName_);
Blockly.DropDownDiv.rendererClassName_ = null;
}
if (Blockly.DropDownDiv.themeClassName_) {
Blockly.utils.dom.removeClass(div, Blockly.DropDownDiv.themeClassName_);
Blockly.DropDownDiv.themeClassName_ = null;
}
Blockly.getMainWorkspace().markFocused();
};
/**
* Set the dropdown div's position.
* @param {number} initialX Initial Horizontal location
* (window coordinates, not body).
* @param {number} initialY Initial Vertical location
* (window coordinates, not body).
* @param {number} finalX Final Horizontal location
* (window coordinates, not body).
* @param {number} finalY Final Vertical location
* (window coordinates, not body).
* @param {number} primaryX Desired origin point x, in absolute px.
* @param {number} primaryY Desired origin point y, in absolute px.
* @param {number} secondaryX Secondary/alternative origin point x,
* in absolute px.
* @param {number} secondaryY Secondary/alternative origin point y,
* in absolute px.
* @return {boolean} True if the menu rendered at the primary origin point.
* @private
*/
Blockly.DropDownDiv.positionInternal_ = function(initialX, initialY, finalX, finalY) {
initialX = Math.floor(initialX);
initialY = Math.floor(initialY);
finalX = Math.floor(finalX);
finalY = Math.floor(finalY);
Blockly.DropDownDiv.positionInternal_ = function(
primaryX, primaryY, secondaryX, secondaryY) {
var metrics = Blockly.DropDownDiv.getPositionMetrics_(primaryX, primaryY,
secondaryX, secondaryY);
// Update arrow CSS.
if (metrics.arrowVisible) {
Blockly.DropDownDiv.arrow_.style.display = '';
Blockly.DropDownDiv.arrow_.style.transform = 'translate(' +
metrics.arrowX + 'px,' + metrics.arrowY + 'px) rotate(45deg)';
Blockly.DropDownDiv.arrow_.setAttribute('class', metrics.arrowAtTop ?
'blocklyDropDownArrow blocklyArrowTop' :
'blocklyDropDownArrow blocklyArrowBottom');
} else {
Blockly.DropDownDiv.arrow_.style.display = 'none';
}
var initialX = Math.floor(metrics.initialX);
var initialY = Math.floor(metrics.initialY);
var finalX = Math.floor(metrics.finalX);
var finalY = Math.floor(metrics.finalY);
var div = Blockly.DropDownDiv.DIV_;
// First apply initial translation.
@@ -643,42 +709,35 @@ Blockly.DropDownDiv.positionInternal_ = function(initialX, initialY, finalX, fin
var dx = finalX - initialX;
var dy = finalY - initialY;
div.style.transform = 'translate(' + dx + 'px,' + dy + 'px)';
return metrics.arrowAtTop;
};
/**
* Repositions the dropdownDiv on window resize. If it doesn't know how to
* calculate the new position, it will just hide it instead.
* Repositions the dropdownDiv on window resize. If it doesn't know how to
* calculate the new position, it will just hide it instead.
* @package
*/
Blockly.DropDownDiv.repositionForWindowResize = function() {
// This condition mainly catches the dropdown div when it is being used as a
// dropdown. It is important not to close it in this case because on Android,
// when a field is focused, the soft keyboard opens triggering a window resize
// event and we want the dropdown div to stick around so users can type into it.
// event and we want the dropdown div to stick around so users can type into
// it.
if (Blockly.DropDownDiv.owner_) {
var field = /** @type {!Blockly.Field} */ (Blockly.DropDownDiv.owner_);
var block = Blockly.DropDownDiv.owner_.getSourceBlock();
var scale = block.workspace.scale;
var bBox = {
width: Blockly.DropDownDiv.positionToField_ ?
Blockly.DropDownDiv.owner_.size_.width : block.width,
height: Blockly.DropDownDiv.positionToField_ ?
Blockly.DropDownDiv.owner_.size_.height : block.height
};
bBox.width *= scale;
bBox.height *= scale;
var position = Blockly.DropDownDiv.positionToField_ ?
Blockly.DropDownDiv.owner_.fieldGroup_.getBoundingClientRect() :
block.getSvgRoot().getBoundingClientRect();
var bBox = Blockly.DropDownDiv.positionToField_ ?
Blockly.DropDownDiv.getScaledBboxOfField_(field) :
Blockly.DropDownDiv.getScaledBboxOfBlock_(block);
// If we can fit it, render below the block.
var primaryX = position.left + bBox.width / 2;
var primaryY = position.top + bBox.height;
var primaryX = bBox.left + (bBox.right - bBox.left) / 2;
var primaryY = bBox.bottom;
// If we can't fit it, render above the entire parent block.
var secondaryX = primaryX;
var secondaryY = position.top;
var metrics = Blockly.DropDownDiv.getPositionMetrics(
primaryX, primaryY, secondaryX, secondaryY);
var secondaryY = bBox.top;
Blockly.DropDownDiv.positionInternal_(
metrics.initialX, metrics.initialY,
metrics.finalX, metrics.finalY);
primaryX, primaryY, secondaryX, secondaryY);
} else {
Blockly.DropDownDiv.hide();
}

View File

@@ -194,7 +194,7 @@ Blockly.Events.fire = function(event) {
Blockly.Events.fireNow_ = function() {
var queue = Blockly.Events.filter(Blockly.Events.FIRE_QUEUE_, true);
Blockly.Events.FIRE_QUEUE_.length = 0;
for (var i = 0, event; event = queue[i]; i++) {
for (var i = 0, event; (event = queue[i]); i++) {
if (!event.workspaceId) {
continue;
}
@@ -220,7 +220,7 @@ Blockly.Events.filter = function(queueIn, forward) {
var mergedQueue = [];
var hash = Object.create(null);
// Merge duplicates.
for (var i = 0, event; event = queue[i]; i++) {
for (var i = 0, event; (event = queue[i]); i++) {
if (!event.isNull()) {
var key = [event.type, event.blockId, event.workspaceId].join(' ');
@@ -266,7 +266,7 @@ Blockly.Events.filter = function(queueIn, forward) {
}
// Move mutation events to the top of the queue.
// Intentionally skip first event.
for (var i = 1, event; event = queue[i]; i++) {
for (var i = 1, event; (event = queue[i]); i++) {
if (event.type == Blockly.Events.CHANGE &&
event.element == 'mutation') {
queue.unshift(queue.splice(i, 1)[0]);
@@ -280,7 +280,7 @@ Blockly.Events.filter = function(queueIn, forward) {
* in the undo stack. Called by Blockly.Workspace.clearUndo.
*/
Blockly.Events.clearPendingUndo = function() {
for (var i = 0, event; event = Blockly.Events.FIRE_QUEUE_[i]; i++) {
for (var i = 0, event; (event = Blockly.Events.FIRE_QUEUE_[i]); i++) {
event.recordUndo = false;
}
};
@@ -338,7 +338,7 @@ Blockly.Events.setGroup = function(state) {
Blockly.Events.getDescendantIds = function(block) {
var ids = [];
var descendants = block.getDescendants(false);
for (var i = 0, descendant; descendant = descendants[i]; i++) {
for (var i = 0, descendant; (descendant = descendants[i]); i++) {
ids[i] = descendant.id;
}
return ids;
@@ -390,6 +390,9 @@ Blockly.Events.fromJson = function(json, workspace) {
case Blockly.Events.COMMENT_DELETE:
event = new Blockly.Events.CommentDelete(null);
break;
case Blockly.Events.FINISHED_LOADING:
event = new Blockly.Events.FinishedLoading(workspace);
break;
default:
throw Error('Unknown event type.');
}
@@ -417,7 +420,7 @@ Blockly.Events.disableOrphans = function(event) {
var parent = block.getParent();
if (parent && parent.isEnabled()) {
var children = block.getDescendants(false);
for (var i = 0, child; child = children[i]; i++) {
for (var i = 0, child; (child = children[i]); i++) {
child.setEnabled(true);
}
} else if ((block.outputConnection || block.previousConnection) &&

View File

@@ -87,7 +87,7 @@ Blockly.Extensions.registerMixin = function(name, mixinObj) {
* @param {!Object} mixinObj The values to mix in.
* @param {(function())=} opt_helperFn An optional function to apply after
* mixing in the object.
* @param {Array.<string>=} opt_blockList A list of blocks to appear in the
* @param {!Array.<string>=} opt_blockList A list of blocks to appear in the
* flyout of the mutator dialog.
* @throws {Error} if the mutation is invalid or can't be applied to the block.
*/
@@ -114,7 +114,7 @@ Blockly.Extensions.registerMutator = function(name, mixinObj, opt_helperFn,
if (!Blockly.Mutator) {
throw Error(errorPrefix + 'Missing require for Blockly.Mutator');
}
this.setMutator(new Blockly.Mutator(opt_blockList));
this.setMutator(new Blockly.Mutator(opt_blockList || []));
}
// Mixin the object.
this.mixin(mixinObj);
@@ -165,7 +165,8 @@ Blockly.Extensions.apply = function(name, block, isMutator) {
var errorPrefix = 'Error after applying mutator "' + name + '": ';
Blockly.Extensions.checkBlockHasMutatorProperties_(errorPrefix, block);
} else {
if (!Blockly.Extensions.mutatorPropertiesMatch_(mutatorProperties, block)) {
if (!Blockly.Extensions.mutatorPropertiesMatch_(
/** @type {!Array.<Object>} */ (mutatorProperties), block)) {
throw Error('Error when applying extension "' + name + '": ' +
'mutation properties changed when applying a non-mutator extension.');
}
@@ -359,7 +360,7 @@ Blockly.Extensions.buildTooltipForDropdown = function(dropdownName,
}
this.setTooltip(function() {
var value = this.getFieldValue(dropdownName);
var value = String(this.getFieldValue(dropdownName));
var tooltip = lookupTable[value];
if (tooltip == null) {
if (blockTypesChecked.indexOf(this.type) == -1) {

View File

@@ -31,9 +31,10 @@ goog.require('Blockly.Gesture');
goog.require('Blockly.utils');
goog.require('Blockly.utils.dom');
goog.require('Blockly.utils.Size');
goog.require('Blockly.utils.style');
goog.require('Blockly.utils.userAgent');
goog.require('Blockly.utils.style');
goog.requireType('Blockly.blockRendering.ConstantProvider');
/**
@@ -94,46 +95,53 @@ Blockly.Field = function(value, opt_validator, opt_config) {
*/
this.markerSvg_ = null;
/**
* The rendered field's SVG group element.
* @type {SVGGElement}
* @protected
*/
this.fieldGroup_ = null;
/**
* The rendered field's SVG border element.
* @type {SVGRectElement}
* @protected
*/
this.borderRect_ = null;
/**
* The rendered field's SVG text element.
* @type {SVGTextElement}
* @protected
*/
this.textElement_ = null;
/**
* The rendered field's text content element.
* @type {Text}
* @protected
*/
this.textContent_ = null;
/**
* Mouse down event listener data.
* @type {?Blockly.EventData}
* @private
*/
this.mouseDownWrapper_ = null;
/**
* Constants associated with the source block's renderer.
* @type {Blockly.blockRendering.ConstantProvider}
* @protected
*/
this.constants_ = null;
opt_config && this.configure_(opt_config);
this.setValue(value);
opt_validator && this.setValidator(opt_validator);
};
/**
* The default height of the border rect on any field.
* @type {number}
* @package
*/
Blockly.Field.BORDER_RECT_DEFAULT_HEIGHT = 16;
/**
* The default height of the text element on any field.
* @type {number}
* @package
*/
Blockly.Field.TEXT_DEFAULT_HEIGHT = 12.5;
/**
* The padding added to the width by the border rect, if it exists.
* @type {number}
* @package
*/
Blockly.Field.X_PADDING = 10;
/**
* The padding added to the height by the border rect, if it exists.
* @type {number}
* @package
*/
Blockly.Field.Y_PADDING = 10;
/**
* The default offset between the left of the text element and the left of the
* border rect, if the border rect exists.
* @type {number}
*/
Blockly.Field.DEFAULT_TEXT_OFFSET = Blockly.Field.X_PADDING / 2;
/**
* Name of field. Unique within each block.
* Static labels are usually unnamed.
@@ -178,7 +186,7 @@ Blockly.Field.prototype.visible_ = true;
/**
* The element the click handler is bound to.
* @type {Element}
* @private
* @protected
*/
Blockly.Field.prototype.clickTarget_ = null;
@@ -186,11 +194,23 @@ Blockly.Field.prototype.clickTarget_ = null;
* A developer hook to override the returned text of this field.
* Override if the text representation of the value of this field
* is not just a string cast of its value.
* Return null to resort to a string cast.
* @return {?string} Current text. Return null to resort to a string cast.
* @protected
*/
Blockly.Field.prototype.getText_;
/**
* An optional method that can be defined to show an editor when the field is
* clicked. Blockly will automatically set the field as clickable if this
* method is defined.
* @param {Event=} opt_e Optional mouse event that triggered the field to open,
* or undefined if triggered programatically.
* @return {void}
* @protected
*/
Blockly.Field.prototype.showEditor_;
/**
* Non-breaking space.
* @const
@@ -240,6 +260,9 @@ Blockly.Field.prototype.setSourceBlock = function(block) {
throw Error('Field already bound to a block.');
}
this.sourceBlock_ = block;
if (block.workspace.rendered) {
this.constants_ = block.workspace.getRenderer().getConstants();
}
};
/**
@@ -260,11 +283,13 @@ Blockly.Field.prototype.init = function() {
// Field has already been initialized once.
return;
}
this.fieldGroup_ = Blockly.utils.dom.createSvgElement('g', {}, null);
this.fieldGroup_ = /** @type {!SVGGElement} **/
(Blockly.utils.dom.createSvgElement('g', {}, null));
if (!this.isVisible()) {
this.fieldGroup_.style.display = 'none';
}
this.sourceBlock_.getSvgRoot().appendChild(this.fieldGroup_);
var sourceBlockSvg = /** @type {!Blockly.BlockSvg} **/ (this.sourceBlock_);
sourceBlockSvg.getSvgRoot().appendChild(this.fieldGroup_);
this.initView();
this.updateEditable();
this.setTooltip(this.tooltip_);
@@ -297,18 +322,20 @@ Blockly.Field.prototype.initModel = function() {
*/
Blockly.Field.prototype.createBorderRect_ = function() {
this.size_.height =
Math.max(this.size_.height, Blockly.Field.BORDER_RECT_DEFAULT_HEIGHT);
Math.max(this.size_.height, this.constants_.FIELD_BORDER_RECT_HEIGHT);
this.size_.width =
Math.max(this.size_.width, Blockly.Field.X_PADDING);
this.borderRect_ = Blockly.utils.dom.createSvgElement('rect',
{
'rx': 4,
'ry': 4,
'x': 0,
'y': 0,
'height': this.size_.height,
'width': this.size_.width
}, this.fieldGroup_);
Math.max(this.size_.width, this.constants_.FIELD_BORDER_RECT_X_PADDING * 2);
this.borderRect_ = /** @type {!SVGRectElement} **/
(Blockly.utils.dom.createSvgElement('rect',
{
'rx': this.constants_.FIELD_BORDER_RECT_RADIUS,
'ry': this.constants_.FIELD_BORDER_RECT_RADIUS,
'x': 0,
'y': 0,
'height': this.size_.height,
'width': this.size_.width,
'class': 'blocklyFieldRect'
}, this.fieldGroup_));
};
/**
@@ -318,14 +345,26 @@ Blockly.Field.prototype.createBorderRect_ = function() {
* @protected
*/
Blockly.Field.prototype.createTextElement_ = function() {
var xOffset = this.borderRect_ ? Blockly.Field.DEFAULT_TEXT_OFFSET : 0;
this.textElement_ = Blockly.utils.dom.createSvgElement('text',
{
'class': 'blocklyText',
// The y position is the baseline of the text.
'y': Blockly.Field.TEXT_DEFAULT_HEIGHT,
'x': xOffset
}, this.fieldGroup_);
var xOffset = this.borderRect_ ?
this.constants_.FIELD_BORDER_RECT_X_PADDING : 0;
var baselineCenter = this.constants_.FIELD_TEXT_BASELINE_CENTER;
var baselineY = this.constants_.FIELD_TEXT_BASELINE_Y;
this.size_.height = Math.max(this.size_.height, baselineCenter ?
this.constants_.FIELD_TEXT_HEIGHT : baselineY);
if (this.size_.height > this.constants_.FIELD_TEXT_HEIGHT) {
baselineY += (this.size_.height - baselineY) / 2;
}
this.textElement_ = /** @type {!SVGTextElement} **/
(Blockly.utils.dom.createSvgElement('text',
{
'class': 'blocklyText',
'y': baselineCenter ? this.size_.height / 2 : baselineY,
'dy': this.constants_.FIELD_TEXT_Y_OFFSET,
'x': xOffset
}, this.fieldGroup_));
if (baselineCenter) {
this.textElement_.setAttribute('dominant-baseline', 'central');
}
this.textContent_ = document.createTextNode('');
this.textElement_.appendChild(this.textContent_);
};
@@ -386,7 +425,7 @@ Blockly.Field.prototype.dispose = function() {
* Add or remove the UI indicating if this field is editable or not.
*/
Blockly.Field.prototype.updateEditable = function() {
var group = this.getClickTarget_();
var group = this.fieldGroup_;
if (!this.EDITABLE || !group) {
return;
}
@@ -534,18 +573,18 @@ Blockly.Field.prototype.callValidator = function(text) {
/**
* Gets the group element for this editable field.
* Used for measuring the size and for positioning.
* @return {!SVGElement} The group element.
* @return {!SVGGElement} The group element.
*/
Blockly.Field.prototype.getSvgRoot = function() {
return /** @type {!SVGElement} */ (this.fieldGroup_);
return /** @type {!SVGGElement} */ (this.fieldGroup_);
};
/**
* Updates the field to match the colour/style of the block. Should only be
* called by BlockSvg.updateColour().
* called by BlockSvg.applyColour().
* @package
*/
Blockly.Field.prototype.updateColour = function() {
Blockly.Field.prototype.applyColour = function() {
// Non-abstract sub-classes may wish to implement this. See FieldDropdown.
};
@@ -563,6 +602,18 @@ Blockly.Field.prototype.render_ = function() {
}
};
/**
* Show an editor when the field is clicked only if the field is clickable.
* @param {Event=} opt_e Optional mouse event that triggered the field to open,
* or undefined if triggered programatically.
* @package
*/
Blockly.Field.prototype.showEditor = function(opt_e) {
if (this.isClickable()) {
this.showEditor_(opt_e);
}
};
/**
* Updates the width of the field. Redirects to updateSize_().
* @deprecated May 2019 Use Blockly.Field.updateSize_() to force an update
@@ -582,10 +633,14 @@ Blockly.Field.prototype.updateWidth = function() {
* @protected
*/
Blockly.Field.prototype.updateSize_ = function() {
var textWidth = Blockly.utils.dom.getTextWidth(this.textElement_);
var textWidth = Blockly.utils.dom.getFastTextWidth(
/** @type {!SVGTextElement} */ (this.textElement_),
this.constants_.FIELD_TEXT_FONTSIZE,
this.constants_.FIELD_TEXT_FONTWEIGHT,
this.constants_.FIELD_TEXT_FONTFAMILY);
var totalWidth = textWidth;
if (this.borderRect_) {
totalWidth += Blockly.Field.X_PADDING;
totalWidth += this.constants_.FIELD_BORDER_RECT_X_PADDING * 2;
this.borderRect_.setAttribute('width', totalWidth);
}
this.size_.width = totalWidth;
@@ -620,13 +675,38 @@ Blockly.Field.prototype.getSize = function() {
* scaling.
* @return {!Object} An object with top, bottom, left, and right in pixels
* relative to the top left corner of the page (window coordinates).
* @protected
* @package
*/
Blockly.Field.prototype.getScaledBBox_ = function() {
var bBox = this.borderRect_.getBBox();
var scaledHeight = bBox.height * this.sourceBlock_.workspace.scale;
var scaledWidth = bBox.width * this.sourceBlock_.workspace.scale;
var xy = this.getAbsoluteXY_();
Blockly.Field.prototype.getScaledBBox = function() {
if (!this.borderRect_) {
// Browsers are inconsistent in what they return for a bounding box.
// - Webkit / Blink: fill-box / object bounding box
// - Gecko / Triden / EdgeHTML: stroke-box
var bBox = this.sourceBlock_.getHeightWidth();
var scale = this.sourceBlock_.workspace.scale;
var xy = this.getAbsoluteXY_();
var scaledWidth = bBox.width * scale;
var scaledHeight = bBox.height * scale;
if (Blockly.utils.userAgent.GECKO) {
xy.x += 1.5 * scale;
xy.y += 1.5 * scale;
scaledWidth += 1 * scale;
scaledHeight += 1 * scale;
} else {
if (!Blockly.utils.userAgent.EDGE && !Blockly.utils.userAgent.IE) {
xy.x -= 0.5 * scale;
xy.y -= 0.5 * scale;
}
scaledWidth += 1 * scale;
scaledHeight += 1 * scale;
}
} else {
var bBox = this.borderRect_.getBoundingClientRect();
var xy = Blockly.utils.style.getPageOffset(this.borderRect_);
var scaledWidth = bBox.width;
var scaledHeight = bBox.height;
}
return {
top: xy.y,
bottom: xy.y + scaledHeight,
@@ -868,7 +948,7 @@ Blockly.Field.prototype.setTooltip = function(newTip) {
* to the SVG root of the field. When this element is
* clicked on an editable field, the editor will open.
* @return {!Element} Element to bind click handler to.
* @private
* @protected
*/
Blockly.Field.prototype.getClickTarget_ = function() {
return this.clickTarget_ || this.getSvgRoot();
@@ -878,10 +958,11 @@ Blockly.Field.prototype.getClickTarget_ = function() {
* Return the absolute coordinates of the top-left corner of this field.
* The origin (0,0) is the top-left corner of the page body.
* @return {!Blockly.utils.Coordinate} Object with .x and .y properties.
* @private
* @protected
*/
Blockly.Field.prototype.getAbsoluteXY_ = function() {
return Blockly.utils.style.getPageOffset(this.borderRect_);
return Blockly.utils.style.getPageOffset(
/** @type {!SVGRectElement} */ (this.getClickTarget_()));
};
/**

View File

@@ -83,6 +83,39 @@ Blockly.FieldAngle = function(opt_value, opt_validator, opt_config) {
Blockly.FieldAngle.superClass_.constructor.call(
this, opt_value || 0, opt_validator, opt_config);
/**
* The angle picker's gauge path depending on the value.
* @type {SVGElement}
*/
this.gauge_ = null;
/**
* The angle picker's line drawn representing the value's angle.
* @type {SVGElement}
*/
this.line_ = null;
/**
* Wrapper click event data.
* @type {?Blockly.EventData}
* @private
*/
this.clickWrapper_ = null;
/**
* Surface click event data.
* @type {?Blockly.EventData}
* @private
*/
this.clickSurfaceWrapper_ = null;
/**
* Surface mouse move event data.
* @type {?Blockly.EventData}
* @private
*/
this.moveSurfaceWrapper_ = null;
};
Blockly.utils.object.inherits(Blockly.FieldAngle, Blockly.FieldTextInput);
@@ -173,6 +206,7 @@ Blockly.FieldAngle.prototype.configure_ = function(config) {
this.clockwise_ = clockwise;
}
// If these are passed as null then we should leave them on the default.
var offset = config['offset'];
if (offset != null) {
offset = Number(offset);
@@ -222,22 +256,23 @@ Blockly.FieldAngle.prototype.render_ = function() {
/**
* Create and show the angle field's editor.
* @param {Event=} opt_e Optional mouse event that triggered the field to open,
* or undefined if triggered programatically.
* @private
*/
Blockly.FieldAngle.prototype.showEditor_ = function() {
Blockly.FieldAngle.prototype.showEditor_ = function(opt_e) {
// Mobile browsers have issues with in-line textareas (focus & keyboards).
var noFocus =
Blockly.utils.userAgent.MOBILE ||
Blockly.utils.userAgent.ANDROID ||
Blockly.utils.userAgent.IPAD;
Blockly.FieldAngle.superClass_.showEditor_.call(this, noFocus);
Blockly.FieldAngle.superClass_.showEditor_.call(this, opt_e, noFocus);
var editor = this.dropdownCreate_();
Blockly.DropDownDiv.getContentDiv().appendChild(editor);
var border = this.sourceBlock_.getColourBorder();
border = border.colourBorder || border.colourLight;
Blockly.DropDownDiv.setColour(this.sourceBlock_.getColour(), border);
Blockly.DropDownDiv.setColour(this.sourceBlock_.style.colourPrimary,
this.sourceBlock_.style.colourTertiary);
Blockly.DropDownDiv.showPositionedByField(
this, this.dropdownDispose_.bind(this));
@@ -297,20 +332,33 @@ Blockly.FieldAngle.prototype.dropdownCreate_ = function() {
// a click handler on the drag surface to update the value if the surface
// is clicked.
this.clickSurfaceWrapper_ =
Blockly.bindEventWithChecks_(circle, 'click', this, this.onMouseMove, true, true);
Blockly.bindEventWithChecks_(circle, 'click', this, this.onMouseMove_,
true, true);
this.moveSurfaceWrapper_ =
Blockly.bindEventWithChecks_(circle, 'mousemove', this, this.onMouseMove, true, true);
Blockly.bindEventWithChecks_(circle, 'mousemove', this, this.onMouseMove_,
true, true);
return svg;
};
/**
* Dispose of events belonging to the angle editor.
* Disposes of events and dom-references belonging to the angle editor.
* @private
*/
Blockly.FieldAngle.prototype.dropdownDispose_ = function() {
Blockly.unbindEvent_(this.clickWrapper_);
Blockly.unbindEvent_(this.clickSurfaceWrapper_);
Blockly.unbindEvent_(this.moveSurfaceWrapper_);
if (this.clickWrapper_) {
Blockly.unbindEvent_(this.clickWrapper_);
this.clickWrapper_ = null;
}
if (this.clickSurfaceWrapper_) {
Blockly.unbindEvent_(this.clickSurfaceWrapper_);
this.clickSurfaceWrapper_ = null;
}
if (this.moveSurfaceWrapper_) {
Blockly.unbindEvent_(this.moveSurfaceWrapper_);
this.moveSurfaceWrapper_ = null;
}
this.gauge_ = null;
this.line_ = null;
};
/**
@@ -325,8 +373,9 @@ Blockly.FieldAngle.prototype.hide_ = function() {
/**
* Set the angle to match the mouse's position.
* @param {!Event} e Mouse move event.
* @protected
*/
Blockly.FieldAngle.prototype.onMouseMove = function(e) {
Blockly.FieldAngle.prototype.onMouseMove_ = function(e) {
// Calculate angle.
var bBox = this.gauge_.ownerSVGElement.getBoundingClientRect();
var dx = e.clientX - bBox.left - Blockly.FieldAngle.HALF;

View File

@@ -60,9 +60,6 @@ Blockly.FieldCheckbox = function(opt_value, opt_validator, opt_config) {
}
Blockly.FieldCheckbox.superClass_.constructor.call(
this, opt_value, opt_validator, opt_config);
this.size_.width = Blockly.FieldCheckbox.WIDTH;
};
Blockly.utils.object.inherits(Blockly.FieldCheckbox, Blockly.Field);
@@ -77,13 +74,6 @@ Blockly.FieldCheckbox.fromJson = function(options) {
return new Blockly.FieldCheckbox(options['checked'], undefined, options);
};
/**
* The width of a checkbox field.
* @type {number}
* @const
*/
Blockly.FieldCheckbox.WIDTH = 15;
/**
* Default character for the checkmark.
* @type {string}
@@ -91,20 +81,6 @@ Blockly.FieldCheckbox.WIDTH = 15;
*/
Blockly.FieldCheckbox.CHECK_CHAR = '\u2713';
/**
* Used to correctly position the check mark.
* @type {number}
* @const
*/
Blockly.FieldCheckbox.CHECK_X_OFFSET = Blockly.Field.DEFAULT_TEXT_OFFSET - 3;
/**
* Used to correctly position the check mark.
* @type {number}
* @const
*/
Blockly.FieldCheckbox.CHECK_Y_OFFSET = 14;
/**
* Serializable fields are saved by the XML renderer, non-serializable fields
* are not. Editable fields should also be serializable.
@@ -143,10 +119,12 @@ Blockly.FieldCheckbox.prototype.configure_ = function(config) {
* @package
*/
Blockly.FieldCheckbox.prototype.initView = function() {
this.size_.width = this.constants_.FIELD_CHECKBOX_DEFAULT_WIDTH;
Blockly.FieldCheckbox.superClass_.initView.call(this);
this.textElement_.setAttribute('x', Blockly.FieldCheckbox.CHECK_X_OFFSET);
this.textElement_.setAttribute('y', Blockly.FieldCheckbox.CHECK_Y_OFFSET);
this.textElement_.setAttribute('x', this.constants_.FIELD_CHECKBOX_X_OFFSET);
this.textElement_.setAttribute('y', this.constants_.FIELD_CHECKBOX_Y_OFFSET);
this.textElement_.removeAttribute('dominant-baseline');
Blockly.utils.dom.addClass(this.textElement_, 'blocklyCheckbox');
this.textContent_.nodeValue =

View File

@@ -58,13 +58,53 @@ Blockly.FieldColour = function(opt_value, opt_validator, opt_config) {
opt_validator, opt_config);
/**
* The size of the area rendered by the field.
* @type {Blockly.utils.Size}
* @protected
* @override
* The field's colour picker element.
* @type {Element}
* @private
*/
this.size_ = new Blockly.utils.Size(Blockly.FieldColour.DEFAULT_WIDTH,
Blockly.FieldColour.DEFAULT_HEIGHT);
this.picker_ = null;
/**
* Index of the currently highlighted element.
* @type {?number}
* @private
*/
this.highlightedIndex_ = null;
/**
* Mouse click event data.
* @type {?Blockly.EventData}
* @private
*/
this.onClickWrapper_ = null;
/**
* Mouse move event data.
* @type {?Blockly.EventData}
* @private
*/
this.onMouseMoveWrapper_ = null;
/**
* Mouse enter event data.
* @type {?Blockly.EventData}
* @private
*/
this.onMouseEnterWrapper_ = null;
/**
* Mouse leave event data.
* @type {?Blockly.EventData}
* @private
*/
this.onMouseLeaveWrapper_ = null;
/**
* Key down event data.
* @type {?Blockly.EventData}
* @private
*/
this.onKeyDownWrapper_ = null;
};
Blockly.utils.object.inherits(Blockly.FieldColour, Blockly.Field);
@@ -79,22 +119,6 @@ Blockly.FieldColour.fromJson = function(options) {
return new Blockly.FieldColour(options['colour'], undefined, options);
};
/**
* Default width of a colour field.
* @type {number}
* @private
* @const
*/
Blockly.FieldColour.DEFAULT_WIDTH = 26;
/**
* Default height of a colour field.
* @type {number}
* @private
* @const
*/
Blockly.FieldColour.DEFAULT_HEIGHT = Blockly.Field.BORDER_RECT_DEFAULT_HEIGHT;
/**
* Serializable fields are saved by the XML renderer, non-serializable fields
* are not. Editable fields should also be serializable.
@@ -159,9 +183,29 @@ Blockly.FieldColour.prototype.configure_ = function(config) {
* @package
*/
Blockly.FieldColour.prototype.initView = function() {
this.createBorderRect_();
this.borderRect_.style['fillOpacity'] = 1;
this.borderRect_.style.fill = this.value_;
this.size_ = new Blockly.utils.Size(
this.constants_.FIELD_COLOUR_DEFAULT_WIDTH,
this.constants_.FIELD_COLOUR_DEFAULT_HEIGHT);
if (!this.constants_.FIELD_COLOUR_FULL_BLOCK) {
this.createBorderRect_();
this.borderRect_.style['fillOpacity'] = '1';
} else {
this.clickTarget_ = this.sourceBlock_.getSvgRoot();
}
};
/**
* @override
*/
Blockly.FieldColour.prototype.applyColour = function() {
if (!this.constants_.FIELD_COLOUR_FULL_BLOCK) {
if (this.borderRect_) {
this.borderRect_.style.fill = this.getValue();
}
} else {
this.sourceBlock_.pathObject.svgPath.setAttribute('fill', this.getValue());
this.sourceBlock_.pathObject.svgPath.setAttribute('stroke', '#fff');
}
};
/**
@@ -187,6 +231,9 @@ Blockly.FieldColour.prototype.doValueUpdate_ = function(newValue) {
this.value_ = newValue;
if (this.borderRect_) {
this.borderRect_.style.fill = newValue;
} else if (this.sourceBlock_) {
this.sourceBlock_.pathObject.svgPath.setAttribute('fill', newValue);
this.sourceBlock_.pathObject.svgPath.setAttribute('stroke', '#fff');
}
};
@@ -285,7 +332,7 @@ Blockly.FieldColour.prototype.showEditor_ = function() {
this, this.dropdownDispose_.bind(this));
// Focus so we can start receiving keyboard events.
this.picker_.focus();
this.picker_.focus({preventScroll:true});
};
/**
@@ -415,7 +462,7 @@ Blockly.FieldColour.prototype.moveHighlightBy_ = function(dx, dy) {
}
// Move the highlight to the new coordinates.
var cell = this.picker_.childNodes[y].childNodes[x];
var cell = /** @type {!Element} */ (this.picker_.childNodes[y].childNodes[x]);
var index = (y * columns) + x;
this.setHighlightedCell_(cell, index);
};
@@ -427,9 +474,9 @@ Blockly.FieldColour.prototype.moveHighlightBy_ = function(dx, dy) {
*/
Blockly.FieldColour.prototype.onMouseMove_ = function(e) {
var cell = /** @type {!Element} */ (e.target);
var index = cell && cell.getAttribute('data-index');
var index = cell && Number(cell.getAttribute('data-index'));
if (index !== null && index !== this.highlightedIndex_) {
this.setHighlightedCell_(cell, Number(index));
this.setHighlightedCell_(cell, index);
}
};
@@ -438,7 +485,7 @@ Blockly.FieldColour.prototype.onMouseMove_ = function(e) {
* @private
*/
Blockly.FieldColour.prototype.onMouseEnter_ = function() {
this.picker_.focus();
this.picker_.focus({preventScroll:true});
};
/**
@@ -456,7 +503,7 @@ Blockly.FieldColour.prototype.onMouseLeave_ = function() {
/**
* Returns the currently highlighted item (if any).
* @return {Element} Highlighted item (null if none).
* @return {HTMLElement} Highlighted item (null if none).
* @private
*/
Blockly.FieldColour.prototype.getHighlighted_ = function() {
@@ -467,7 +514,7 @@ Blockly.FieldColour.prototype.getHighlighted_ = function() {
if (!row) {
return null;
}
var col = row.childNodes[x];
var col = /** @type {HTMLElement} */ (row.childNodes[x]);
return col;
};
@@ -489,7 +536,7 @@ Blockly.FieldColour.prototype.setHighlightedCell_ = function(cell, index) {
this.highlightedIndex_ = index;
// Update accessibility roles.
Blockly.utils.aria.setState(this.picker_,
Blockly.utils.aria.setState(/** @type {!Element} */ (this.picker_),
Blockly.utils.aria.State.ACTIVEDESCENDANT, cell.getAttribute('id'));
};
@@ -508,13 +555,12 @@ Blockly.FieldColour.prototype.dropdownCreate_ = function() {
table.className = 'blocklyColourTable';
table.tabIndex = 0;
table.dir = 'ltr';
Blockly.utils.aria.setRole(table,
Blockly.utils.aria.Role.GRID);
Blockly.utils.aria.setState(table,
Blockly.utils.aria.State.EXPANDED, true);
Blockly.utils.aria.setState(table, 'rowcount',
Blockly.utils.aria.setRole(table, Blockly.utils.aria.Role.GRID);
Blockly.utils.aria.setState(table, Blockly.utils.aria.State.EXPANDED, true);
Blockly.utils.aria.setState(table, Blockly.utils.aria.State.ROWCOUNT,
Math.floor(colours.length / columns));
Blockly.utils.aria.setState(table, 'colcount', columns);
Blockly.utils.aria.setState(table, Blockly.utils.aria.State.COLCOUNT,
columns);
var row;
for (var i = 0; i < colours.length; i++) {
if (i % columns == 0) {
@@ -556,16 +602,32 @@ Blockly.FieldColour.prototype.dropdownCreate_ = function() {
};
/**
* Dispose of events belonging to the colour editor.
* Disposes of events and dom-references belonging to the colour editor.
* @private
*/
Blockly.FieldColour.prototype.dropdownDispose_ = function() {
Blockly.unbindEvent_(this.onClickWrapper_);
Blockly.unbindEvent_(this.onMouseMoveWrapper_);
Blockly.unbindEvent_(this.onMouseEnterWrapper_);
Blockly.unbindEvent_(this.onMouseLeaveWrapper_);
Blockly.unbindEvent_(this.onKeyDownWrapper_);
if (this.onClickWrapper_) {
Blockly.unbindEvent_(this.onClickWrapper_);
this.onClickWrapper_ = null;
}
if (this.onMouseMoveWrapper_) {
Blockly.unbindEvent_(this.onMouseMoveWrapper_);
this.onMouseMoveWrapper_ = null;
}
if (this.onMouseEnterWrapper_) {
Blockly.unbindEvent_(this.onMouseEnterWrapper_);
this.onMouseEnterWrapper_ = null;
}
if (this.onMouseLeaveWrapper_) {
Blockly.unbindEvent_(this.onMouseLeaveWrapper_);
this.onMouseLeaveWrapper_ = null;
}
if (this.onKeyDownWrapper_) {
Blockly.unbindEvent_(this.onKeyDownWrapper_);
this.onKeyDownWrapper_ = null;
}
this.picker_ = null;
this.highlightedIndex_ = null;
};
/**

View File

@@ -129,9 +129,9 @@ Blockly.FieldDate.prototype.render_ = function() {
* Updates the field's colours to match those of the block.
* @package
*/
Blockly.FieldDate.prototype.updateColour = function() {
this.todayColour_ = this.sourceBlock_.getColour();
this.selectedColour_ = this.sourceBlock_.getColourShadow();
Blockly.FieldDate.prototype.applyColour = function() {
this.todayColour_ = this.sourceBlock_.style.colourPrimary;
this.selectedColour_ = this.sourceBlock_.style.colourSecondary;
this.updateEditor_();
};

View File

@@ -33,6 +33,8 @@ goog.require('Blockly.Menu');
goog.require('Blockly.MenuItem');
goog.require('Blockly.navigation');
goog.require('Blockly.utils');
goog.require('Blockly.utils.aria');
goog.require('Blockly.utils.Coordinate');
goog.require('Blockly.utils.dom');
goog.require('Blockly.utils.object');
goog.require('Blockly.utils.Size');
@@ -76,27 +78,19 @@ Blockly.FieldDropdown = function(menuGenerator, opt_validator, opt_config) {
*/
this.generatedOptions_ = null;
this.trimOptions_();
/**
* The currently selected index. The field is initialized with the
* The currently selected option. The field is initialized with the
* first option selected.
* @type {number}
* @type {!Object}
* @private
*/
this.selectedIndex_ = 0;
this.trimOptions_();
var firstTuple = this.getOptions(false)[0];
this.selectedOption_ = this.getOptions(false)[0];
// Call parent's constructor.
Blockly.FieldDropdown.superClass_.constructor.call(
this, firstTuple[1], opt_validator, opt_config);
/**
* SVG image element if currently selected option is an image, or null.
* @type {SVGElement}
* @private
*/
this.imageElement_ = null;
this, this.selectedOption_[1], opt_validator, opt_config);
/**
* A reference to the currently selected menu item.
@@ -104,6 +98,34 @@ Blockly.FieldDropdown = function(menuGenerator, opt_validator, opt_config) {
* @private
*/
this.selectedMenuItem_ = null;
/**
* The dropdown menu.
* @type {Blockly.Menu}
* @private
*/
this.menu_ = null;
/**
* SVG image element if currently selected option is an image, or null.
* @type {SVGImageElement}
* @private
*/
this.imageElement_ = null;
/**
* Tspan based arrow element.
* @type {SVGTSpanElement}
* @private
*/
this.arrow_ = null;
/**
* SVG based arrow element.
* @type {SVGElement}
* @private
*/
this.svgArrow_ = null;
};
Blockly.utils.object.inherits(Blockly.FieldDropdown, Blockly.Field);
@@ -180,14 +202,45 @@ Blockly.FieldDropdown.prototype.CURSOR = 'default';
* @package
*/
Blockly.FieldDropdown.prototype.initView = function() {
Blockly.FieldDropdown.superClass_.initView.call(this);
if (this.shouldAddBorderRect_()) {
this.createBorderRect_();
} else {
this.clickTarget_ = this.sourceBlock_.getSvgRoot();
}
this.createTextElement_();
this.imageElement_ = Blockly.utils.dom.createSvgElement( 'image',
{
'y': Blockly.FieldDropdown.IMAGE_Y_OFFSET
}, this.fieldGroup_);
this.imageElement_ = /** @type {!SVGImageElement} */
(Blockly.utils.dom.createSvgElement('image', {}, this.fieldGroup_));
this.arrow_ = Blockly.utils.dom.createSvgElement('tspan', {}, this.textElement_);
if (this.constants_.FIELD_DROPDOWN_SVG_ARROW) {
this.createSVGArrow_();
} else {
this.createTextArrow_();
}
if (this.borderRect_) {
Blockly.utils.dom.addClass(this.borderRect_, 'blocklyDropdownRect');
}
};
/**
* Whether or not the dropdown should add a border rect.
* @return {boolean} True if the dropdown field should add a border rect.
* @protected
*/
Blockly.FieldDropdown.prototype.shouldAddBorderRect_ = function() {
return !this.constants_.FIELD_DROPDOWN_NO_BORDER_RECT_SHADOW ||
(this.constants_.FIELD_DROPDOWN_NO_BORDER_RECT_SHADOW &&
!this.sourceBlock_.isShadow());
};
/**
* Create a tspan based arrow.
* @protected
*/
Blockly.FieldDropdown.prototype.createTextArrow_ = function() {
this.arrow_ = /** @type {!SVGTSpanElement} */
(Blockly.utils.dom.createSvgElement('tspan', {}, this.textElement_));
this.arrow_.appendChild(document.createTextNode(
this.sourceBlock_.RTL ?
Blockly.FieldDropdown.ARROW_CHAR + ' ' :
@@ -199,17 +252,48 @@ Blockly.FieldDropdown.prototype.initView = function() {
}
};
/**
* Create an SVG based arrow.
* @protected
*/
Blockly.FieldDropdown.prototype.createSVGArrow_ = function() {
this.svgArrow_ = Blockly.utils.dom.createSvgElement('image', {
'height': this.constants_.FIELD_DROPDOWN_SVG_ARROW_SIZE + 'px',
'width': this.constants_.FIELD_DROPDOWN_SVG_ARROW_SIZE + 'px'
}, this.fieldGroup_);
this.svgArrow_.setAttributeNS(Blockly.utils.dom.XLINK_NS, 'xlink:href',
this.constants_.FIELD_DROPDOWN_SVG_ARROW_DATAURI);
};
/**
* Create a dropdown menu under the text.
* @param {Event=} opt_e Optional mouse event that triggered the field to open,
* or undefined if triggered programatically.
* @private
*/
Blockly.FieldDropdown.prototype.showEditor_ = function() {
Blockly.FieldDropdown.prototype.showEditor_ = function(opt_e) {
this.menu_ = this.dropdownCreate_();
if (opt_e && typeof opt_e.clientX === 'number') {
this.menu_.openingCoords =
new Blockly.utils.Coordinate(opt_e.clientX, opt_e.clientY);
} else {
this.menu_.openingCoords = null;
}
// Element gets created in render.
this.menu_.render(Blockly.DropDownDiv.getContentDiv());
Blockly.utils.dom.addClass(
/** @type {!Element} */ (this.menu_.getElement()), 'blocklyDropdownMenu');
if (this.constants_.FIELD_DROPDOWN_COLOURED_DIV) {
var primaryColour = (this.sourceBlock_.isShadow()) ?
this.sourceBlock_.getParent().getColour() :
this.sourceBlock_.getColour();
var borderColour = (this.sourceBlock_.isShadow()) ?
this.sourceBlock_.getParent().style.colourTertiary :
this.sourceBlock_.style.colourTertiary;
Blockly.DropDownDiv.setColour(primaryColour, borderColour);
}
Blockly.DropDownDiv.showPositionedByField(
this, this.dropdownDispose_.bind(this));
@@ -224,17 +308,19 @@ Blockly.FieldDropdown.prototype.showEditor_ = function() {
/** @type {!Element} */ (this.selectedMenuItem_.getElement()),
/** @type {!Element} */ (this.menu_.getElement()));
}
this.applyColour();
};
/**
* Create the dropdown editor.
* @return {Blockly.Menu} The newly created dropdown menu.
* @return {!Blockly.Menu} The newly created dropdown menu.
* @private
*/
Blockly.FieldDropdown.prototype.dropdownCreate_ = function() {
var menu = new Blockly.Menu();
menu.setRightToLeft(this.sourceBlock_.RTL);
menu.setRole('listbox');
menu.setRole(Blockly.utils.aria.Role.LISTBOX);
var options = this.getOptions(false);
this.selectedMenuItem_ = null;
@@ -249,7 +335,7 @@ Blockly.FieldDropdown.prototype.dropdownCreate_ = function() {
content = image;
}
var menuItem = new Blockly.MenuItem(content);
menuItem.setRole('option');
menuItem.setRole(Blockly.utils.aria.Role.OPTION);
menuItem.setRightToLeft(this.sourceBlock_.RTL);
menuItem.setValue(value);
menuItem.setCheckable(true);
@@ -269,12 +355,16 @@ Blockly.FieldDropdown.prototype.dropdownCreate_ = function() {
};
/**
* Dispose of events belonging to the dropdown editor.
* Disposes of events and dom-references belonging to the dropdown editor.
* @private
*/
Blockly.FieldDropdown.prototype.dropdownDispose_ = function() {
this.menu_.dispose();
if (this.menu_) {
this.menu_.dispose();
}
this.menu_ = null;
this.selectedMenuItem_ = null;
this.applyColour();
};
/**
@@ -284,15 +374,16 @@ Blockly.FieldDropdown.prototype.dropdownDispose_ = function() {
*/
Blockly.FieldDropdown.prototype.handleMenuActionEvent_ = function(menuItem) {
Blockly.DropDownDiv.hideIfOwner(this, true);
this.onItemSelected(this.menu_, menuItem);
this.onItemSelected_(/** @type {!Blockly.Menu} */ (this.menu_), menuItem);
};
/**
* Handle the selection of an item in the dropdown menu.
* @param {!Blockly.Menu} menu The Menu component clicked.
* @param {!Blockly.MenuItem} menuItem The MenuItem selected within menu.
* @protected
*/
Blockly.FieldDropdown.prototype.onItemSelected = function(menu, menuItem) {
Blockly.FieldDropdown.prototype.onItemSelected_ = function(menu, menuItem) {
this.setValue(menuItem.getValue());
};
@@ -408,7 +499,7 @@ Blockly.FieldDropdown.prototype.getOptions = function(opt_useCache) {
Blockly.FieldDropdown.prototype.doClassValidation_ = function(opt_newValue) {
var isValueValid = false;
var options = this.getOptions(true);
for (var i = 0, option; option = options[i]; i++) {
for (var i = 0, option; (option = options[i]); i++) {
// Options are tuples of human-readable text and language-neutral values.
if (option[1] == opt_newValue) {
isValueValid = true;
@@ -435,9 +526,9 @@ Blockly.FieldDropdown.prototype.doClassValidation_ = function(opt_newValue) {
Blockly.FieldDropdown.prototype.doValueUpdate_ = function(newValue) {
Blockly.FieldDropdown.superClass_.doValueUpdate_.call(this, newValue);
var options = this.getOptions(true);
for (var i = 0, option; option = options[i]; i++) {
for (var i = 0, option; (option = options[i]); i++) {
if (option[1] == this.value_) {
this.selectedIndex_ = i;
this.selectedOption_ = option;
}
}
};
@@ -446,13 +537,23 @@ Blockly.FieldDropdown.prototype.doValueUpdate_ = function(newValue) {
* Updates the dropdown arrow to match the colour/style of the block.
* @package
*/
Blockly.FieldDropdown.prototype.updateColour = function() {
Blockly.FieldDropdown.prototype.applyColour = function() {
if (this.borderRect_) {
this.borderRect_.setAttribute('stroke',
this.sourceBlock_.style.colourTertiary);
if (this.menu_) {
this.borderRect_.setAttribute('fill',
this.sourceBlock_.style.colourTertiary);
} else {
this.borderRect_.setAttribute('fill', 'transparent');
}
}
// Update arrow's colour.
if (this.sourceBlock_ && this.arrow_) {
if (this.sourceBlock_.isShadow()) {
this.arrow_.style.fill = this.sourceBlock_.getColourShadow();
this.arrow_.style.fill = this.sourceBlock_.style.colourSecondary;
} else {
this.arrow_.style.fill = this.sourceBlock_.getColour();
this.arrow_.style.fill = this.sourceBlock_.style.colourPrimary;
}
}
};
@@ -467,16 +568,17 @@ Blockly.FieldDropdown.prototype.render_ = function() {
this.imageElement_.style.display = 'none';
// Show correct element.
var options = this.getOptions(true);
var selectedOption = this.selectedIndex_ >= 0 &&
options[this.selectedIndex_][0];
if (selectedOption && typeof selectedOption == 'object') {
this.renderSelectedImage_(selectedOption);
var option = this.selectedOption_ && this.selectedOption_[0];
if (option && typeof option == 'object') {
this.renderSelectedImage_(
/** @type {!Blockly.FieldDropdown.ImageProperties} */ (option));
} else {
this.renderSelectedText_();
}
this.borderRect_.setAttribute('height', this.size_.height);
this.borderRect_.setAttribute('width', this.size_.width);
if (this.borderRect_) {
this.borderRect_.setAttribute('height', this.size_.height);
this.borderRect_.setAttribute('width', this.size_.width);
}
};
/**
@@ -492,27 +594,41 @@ Blockly.FieldDropdown.prototype.renderSelectedImage_ = function(imageJson) {
this.imageElement_.setAttribute('height', imageJson.height);
this.imageElement_.setAttribute('width', imageJson.width);
var arrowWidth = Blockly.utils.dom.getTextWidth(this.arrow_);
var imageHeight = Number(imageJson.height);
var imageWidth = Number(imageJson.width);
// Height and width include the border rect.
this.size_.height = imageHeight + Blockly.FieldDropdown.IMAGE_Y_PADDING;
this.size_.width = imageWidth + arrowWidth + Blockly.Field.X_PADDING;
var hasBorder = !!this.borderRect_;
this.size_.height = Math.max(
hasBorder ? this.constants_.FIELD_DROPDOWN_BORDER_RECT_HEIGHT : 0,
imageHeight + Blockly.FieldDropdown.IMAGE_Y_PADDING);
var halfHeight = this.size_.height / 2;
var xPadding = hasBorder ? this.constants_.FIELD_BORDER_RECT_X_PADDING : 0;
var arrowWidth = 0;
if (this.svgArrow_) {
arrowWidth = this.positionSVGArrow_(imageWidth + xPadding, halfHeight -
this.constants_.FIELD_DROPDOWN_SVG_ARROW_SIZE / 2);
} else {
arrowWidth = Blockly.utils.dom.getFastTextWidth(
/** @type {!SVGTSpanElement} */ (this.arrow_),
this.constants_.FIELD_TEXT_FONTSIZE,
this.constants_.FIELD_TEXT_FONTWEIGHT,
this.constants_.FIELD_TEXT_FONTFAMILY);
}
this.size_.width = imageWidth + arrowWidth + xPadding * 2;
if (this.sourceBlock_.RTL) {
var imageX = Blockly.Field.DEFAULT_TEXT_OFFSET + arrowWidth;
var arrowX = Blockly.Field.DEFAULT_TEXT_OFFSET - 1;
var imageX = xPadding + arrowWidth;
var arrowX = xPadding - 1;
this.imageElement_.setAttribute('x', imageX);
this.textElement_.setAttribute('x', arrowX);
} else {
var arrowX =
imageWidth + arrowWidth + Blockly.Field.DEFAULT_TEXT_OFFSET + 1;
var arrowX = imageWidth + arrowWidth + xPadding + 1;
this.textElement_.setAttribute('text-anchor', 'end');
this.textElement_.setAttribute('x', arrowX);
this.imageElement_.setAttribute('x', Blockly.Field.DEFAULT_TEXT_OFFSET);
this.imageElement_.setAttribute('x', xPadding);
}
this.imageElement_.setAttribute('y', halfHeight - imageHeight / 2);
};
/**
@@ -522,32 +638,77 @@ Blockly.FieldDropdown.prototype.renderSelectedImage_ = function(imageJson) {
Blockly.FieldDropdown.prototype.renderSelectedText_ = function() {
// Retrieves the selected option to display through getText_.
this.textContent_.nodeValue = this.getDisplayText_();
Blockly.utils.dom.addClass(/** @type {!Element} */ (this.textElement_),
'blocklyDropdownText');
this.textElement_.setAttribute('text-anchor', 'start');
this.textElement_.setAttribute('x', Blockly.Field.DEFAULT_TEXT_OFFSET);
// Height and width include the border rect.
this.size_.height = Blockly.Field.BORDER_RECT_DEFAULT_HEIGHT;
this.size_.width = Blockly.utils.dom.getTextWidth(this.textElement_) +
Blockly.Field.X_PADDING;
var hasBorder = !!this.borderRect_;
this.size_.height = Math.max(
hasBorder ? this.constants_.FIELD_DROPDOWN_BORDER_RECT_HEIGHT : 0,
this.constants_.FIELD_TEXT_HEIGHT);
var halfHeight = this.size_.height / 2;
var textWidth = Blockly.utils.dom.getFastTextWidth(this.textElement_,
this.constants_.FIELD_TEXT_FONTSIZE,
this.constants_.FIELD_TEXT_FONTWEIGHT,
this.constants_.FIELD_TEXT_FONTFAMILY);
var xPadding = hasBorder ? this.constants_.FIELD_BORDER_RECT_X_PADDING : 0;
var arrowWidth = 0;
if (this.svgArrow_) {
arrowWidth = this.positionSVGArrow_(textWidth + xPadding, halfHeight -
this.constants_.FIELD_DROPDOWN_SVG_ARROW_SIZE / 2);
}
this.size_.width = textWidth + arrowWidth + xPadding * 2;
this.textElement_.setAttribute('x', this.sourceBlock_.RTL ?
this.size_.width - textWidth - xPadding : xPadding);
this.textElement_.setAttribute('y', halfHeight);
if (!this.constants_.FIELD_TEXT_BASELINE_CENTER) {
this.textElement_.setAttribute('dy',
this.constants_.FIELD_TEXT_BASELINE_Y -
this.constants_.FIELD_TEXT_HEIGHT / 2 +
this.constants_.FIELD_TEXT_Y_OFFSET);
}
};
/**
* Use the `getText_` developer hook to override the field's text representation.
* Get the selected option text. If the selected option is an image
* we return the image alt text.
* Position a drop-down arrow at the appropriate location at render-time.
* @param {number} x X position the arrow is being rendered at, in px.
* @param {number} y Y position the arrow is being rendered at, in px.
* @return {number} Amount of space the arrow is taking up, in px.
* @private
*/
Blockly.FieldDropdown.prototype.positionSVGArrow_ = function(x, y) {
if (!this.svgArrow_) {
return 0;
}
var hasBorder = !!this.borderRect_;
var xPadding = hasBorder ? this.constants_.FIELD_BORDER_RECT_X_PADDING : 0;
var textPadding = this.constants_.FIELD_DROPDOWN_SVG_ARROW_PADDING;
var svgArrowSize = this.constants_.FIELD_DROPDOWN_SVG_ARROW_SIZE;
var arrowX = this.sourceBlock_.RTL ? xPadding : x + textPadding;
this.svgArrow_.setAttribute('transform',
'translate(' + arrowX + ',' + y + ')');
return svgArrowSize + textPadding;
};
/**
* Use the `getText_` developer hook to override the field's text
* representation. Get the selected option text. If the selected option is an
* image we return the image alt text.
* @return {?string} Selected option text.
* @protected
* @override
*/
Blockly.FieldDropdown.prototype.getText_ = function() {
if (this.selectedIndex_ < 0) {
if (!this.selectedOption_) {
return null;
}
var options = this.getOptions(true);
var selectedOption = options[this.selectedIndex_][0];
if (typeof selectedOption == 'object') {
return selectedOption['alt'];
var option = this.selectedOption_[0];
if (typeof option == 'object') {
return option['alt'];
}
return selectedOption;
return option;
};
/**
@@ -611,4 +772,5 @@ Blockly.FieldDropdown.prototype.onBlocklyAction = function(action) {
return Blockly.FieldDropdown.superClass_.onBlocklyAction.call(this, action);
};
Blockly.fieldRegistry.register('field_dropdown', Blockly.FieldDropdown);

View File

@@ -115,6 +115,13 @@ Blockly.FieldImage = function(src, width, height,
if (typeof opt_onClick == 'function') {
this.clickHandler_ = opt_onClick;
}
/**
* The rendered field's image element.
* @type {SVGImageElement}
* @private
*/
this.imageElement_ = null;
};
Blockly.utils.object.inherits(Blockly.FieldImage, Blockly.Field);
@@ -173,14 +180,15 @@ Blockly.FieldImage.prototype.configure_ = function(config) {
* @package
*/
Blockly.FieldImage.prototype.initView = function() {
this.imageElement_ = Blockly.utils.dom.createSvgElement(
'image',
{
'height': this.imageHeight_ + 'px',
'width': this.size_.width + 'px',
'alt': this.altText_
},
this.fieldGroup_);
this.imageElement_ = /** @type {!SVGImageElement} */
(Blockly.utils.dom.createSvgElement(
'image',
{
'height': this.imageHeight_ + 'px',
'width': this.size_.width + 'px',
'alt': this.altText_
},
this.fieldGroup_));
this.imageElement_.setAttributeNS(Blockly.utils.dom.XLINK_NS,
'xlink:href', /** @type {string} */ (this.value_));
};
@@ -208,7 +216,7 @@ Blockly.FieldImage.prototype.doValueUpdate_ = function(newValue) {
this.value_ = newValue;
if (this.imageElement_) {
this.imageElement_.setAttributeNS(Blockly.utils.dom.XLINK_NS,
'xlink:href', this.value_ || '');
'xlink:href', String(this.value_));
}
};
@@ -239,6 +247,7 @@ Blockly.FieldImage.prototype.setAlt = function(alt) {
/**
* If field click is called, and click handler defined,
* call the handler.
* @protected
*/
Blockly.FieldImage.prototype.showEditor_ = function() {
if (this.clickHandler_) {

View File

@@ -60,14 +60,6 @@ Blockly.FieldLabel = function(opt_value, opt_class, opt_config) {
if (!opt_config) { // If the config was not passed use old configuration.
this.class_ = opt_class || null;
}
/**
* The size of the area rendered by the field.
* @type {Blockly.utils.Size}
* @protected
* @override
*/
this.size_ = new Blockly.utils.Size(0, Blockly.Field.TEXT_DEFAULT_HEIGHT);
};
Blockly.utils.object.inherits(Blockly.FieldLabel, Blockly.Field);
@@ -105,10 +97,9 @@ Blockly.FieldLabel.prototype.configure_ = function(config) {
*/
Blockly.FieldLabel.prototype.initView = function() {
this.createTextElement_();
// The y attribute of an SVG text element is the baseline.
this.textElement_.setAttribute('y', this.size_.height);
if (this.class_) {
Blockly.utils.dom.addClass(this.textElement_, this.class_);
Blockly.utils.dom.addClass(
/** @type {!SVGTextElement} */ (this.textElement_), this.class_);
}
};

View File

@@ -29,6 +29,7 @@ goog.require('Blockly.Css');
goog.require('Blockly.DropDownDiv');
goog.require('Blockly.FieldTextInput');
goog.require('Blockly.utils');
goog.require('Blockly.utils.aria');
goog.require('Blockly.utils.Coordinate');
goog.require('Blockly.utils.dom');
goog.require('Blockly.utils.KeyCodes');
@@ -58,6 +59,13 @@ Blockly.FieldMultilineInput = function(opt_value, opt_validator, opt_config) {
}
Blockly.FieldMultilineInput.superClass_.constructor.call(this,
opt_value, opt_validator, opt_config);
/**
* The SVG group element that will contain a text element for each text row
* when initialized.
* @type {SVGGElement}
*/
this.textGroup_ = null;
};
Blockly.utils.object.inherits(Blockly.FieldMultilineInput,
Blockly.FieldTextInput);
@@ -89,10 +97,11 @@ Blockly.FieldMultilineInput.fromJson = function(options) {
*/
Blockly.FieldMultilineInput.prototype.initView = function() {
this.createBorderRect_();
this.textGroup_ = Blockly.utils.dom.createSvgElement('g',
{
'class': 'blocklyEditableText',
}, this.fieldGroup_);
this.textGroup_ = /** @type {!SVGGElement} **/
(Blockly.utils.dom.createSvgElement('g',
{
'class': 'blocklyEditableText',
}, this.fieldGroup_));
};
/**
@@ -137,19 +146,18 @@ Blockly.FieldMultilineInput.prototype.getDisplayText_ = function() {
Blockly.FieldMultilineInput.prototype.render_ = function() {
// Remove all text group children.
var currentChild;
while (currentChild = this.textGroup_.firstChild) {
while ((currentChild = this.textGroup_.firstChild)) {
this.textGroup_.removeChild(currentChild);
}
// Add in text elements into the group.
var lines = this.getDisplayText_().split('\n');
var yOffset = Blockly.Field.Y_PADDING / 2;
var y = 0;
for (var i = 0; i < lines.length; i++) {
var span = Blockly.utils.dom.createSvgElement('text', {
'class': 'blocklyText blocklyMultilineText',
x: Blockly.Field.DEFAULT_TEXT_OFFSET,
y: y + yOffset,
x: this.constants_.FIELD_BORDER_RECT_X_PADDING,
y: y + this.constants_.FIELD_BORDER_RECT_Y_PADDING,
dy: Blockly.FieldMultilineInput.LINE_HEIGHT / 2
}, this.textGroup_);
span.appendChild(document.createTextNode(lines[i]));
@@ -167,12 +175,15 @@ Blockly.FieldMultilineInput.prototype.render_ = function() {
} else {
this.resizeEditor_();
}
var htmlInput = /** @type {!HTMLElement} */(this.htmlInput_);
if (!this.isTextValid_) {
Blockly.utils.dom.addClass(this.htmlInput_, 'blocklyInvalidInput');
Blockly.utils.aria.setState(this.htmlInput_, 'invalid', true);
Blockly.utils.dom.addClass(htmlInput, 'blocklyInvalidInput');
Blockly.utils.aria.setState(htmlInput,
Blockly.utils.aria.State.INVALID, true);
} else {
Blockly.utils.dom.removeClass(this.htmlInput_, 'blocklyInvalidInput');
Blockly.utils.aria.setState(this.htmlInput_, 'invalid', false);
Blockly.utils.dom.removeClass(htmlInput, 'blocklyInvalidInput');
Blockly.utils.aria.setState(htmlInput,
Blockly.utils.aria.State.INVALID, false);
}
}
};
@@ -186,7 +197,7 @@ Blockly.FieldMultilineInput.prototype.updateSize_ = function() {
var totalWidth = 0;
var totalHeight = 0;
for (var i = 0; i < nodes.length; i++) {
var tspan = nodes[i];
var tspan = /** @type {!Element} */ (nodes[i]);
var textWidth = Blockly.utils.dom.getTextWidth(tspan);
if (textWidth > totalWidth) {
totalWidth = textWidth;
@@ -194,7 +205,7 @@ Blockly.FieldMultilineInput.prototype.updateSize_ = function() {
totalHeight += Blockly.FieldMultilineInput.LINE_HEIGHT;
}
if (this.borderRect_) {
totalWidth += Blockly.Field.X_PADDING;
totalWidth += this.constants_.FIELD_BORDER_RECT_X_PADDING * 2;
this.borderRect_.setAttribute('width', totalWidth);
this.borderRect_.setAttribute('height', totalHeight);
}
@@ -208,7 +219,7 @@ Blockly.FieldMultilineInput.prototype.updateSize_ = function() {
*/
Blockly.FieldMultilineInput.prototype.resizeEditor_ = function() {
var div = Blockly.WidgetDiv.DIV;
var bBox = this.getScaledBBox_();
var bBox = this.getScaledBBox();
div.style.width = bBox.right - bBox.left + 'px';
div.style.height = bBox.bottom - bBox.top + 'px';
@@ -233,12 +244,12 @@ Blockly.FieldMultilineInput.prototype.widgetCreate_ = function() {
var htmlInput = /** @type {HTMLTextAreaElement} */ (document.createElement('textarea'));
htmlInput.className = 'blocklyHtmlInput blocklyHtmlTextAreaInput';
htmlInput.setAttribute('spellcheck', this.spellcheck_);
var fontSize = (Blockly.FieldTextInput.FONTSIZE * scale) + 'pt';
var fontSize = (this.constants_.FIELD_TEXT_FONTSIZE * scale) + 'pt';
div.style.fontSize = fontSize;
htmlInput.style.fontSize = fontSize;
var borderRadius = (Blockly.FieldTextInput.BORDERRADIUS * scale) + 'px';
htmlInput.style.borderRadius = borderRadius;
var padding = Blockly.Field.DEFAULT_TEXT_OFFSET * scale;
var padding = this.constants_.FIELD_BORDER_RECT_X_PADDING * scale;
htmlInput.style.paddingLeft = padding + 'px';
htmlInput.style.width = 'calc(100% - ' + padding + 'px)';
htmlInput.style.lineHeight =

View File

@@ -25,6 +25,7 @@ goog.provide('Blockly.FieldNumber');
goog.require('Blockly.fieldRegistry');
goog.require('Blockly.FieldTextInput');
goog.require('Blockly.utils.aria');
goog.require('Blockly.utils.object');
@@ -274,6 +275,8 @@ Blockly.FieldNumber.prototype.doClassValidation_ = function(opt_newValue) {
newValue = newValue.replace(/O/ig, '0');
// Strip out thousands separators.
newValue = newValue.replace(/,/g, '');
// Ignore case of 'Infinity'.
newValue = newValue.replace(/infinity/i, 'Infinity');
// Clean up number.
var n = Number(newValue || 0);

View File

@@ -64,6 +64,33 @@ Blockly.FieldTextInput = function(opt_value, opt_validator, opt_config) {
}
Blockly.FieldTextInput.superClass_.constructor.call(this,
opt_value, opt_validator, opt_config);
/**
* The HTML input element.
* @type {HTMLElement}
*/
this.htmlInput_ = null;
/**
* Key down event data.
* @type {?Blockly.EventData}
* @private
*/
this.onKeyDownWrapper_ = null;
/**
* Key input event data.
* @type {?Blockly.EventData}
* @private
*/
this.onKeyInputWrapper_ = null;
/**
* Whether the field should consider the whole parent block to be its click
* target.
* @type {?boolean}
*/
this.fullBlockClickTarget_ = false;
};
Blockly.utils.object.inherits(Blockly.FieldTextInput, Blockly.Field);
@@ -87,11 +114,6 @@ Blockly.FieldTextInput.fromJson = function(options) {
*/
Blockly.FieldTextInput.prototype.SERIALIZABLE = true;
/**
* Point size of text. Should match blocklyText's font-size in CSS.
*/
Blockly.FieldTextInput.FONTSIZE = 11;
/**
* Pixel size of input border radius.
* Should match blocklyText's border-radius in CSS.
@@ -113,6 +135,41 @@ Blockly.FieldTextInput.prototype.configure_ = function(config) {
}
};
/**
* @override
*/
Blockly.FieldTextInput.prototype.initView = function() {
if (this.constants_.FULL_BLOCK_FIELDS) {
// Step one: figure out if this is the only field on this block.
// Rendering is quite different in that case.
var nFields = 0;
var nConnections = 0;
// Count the number of fields, excluding text fields
for (var i = 0, input; (input = this.sourceBlock_.inputList[i]); i++) {
for (var j = 0; (input.fieldRow[j]); j++) {
nFields ++;
}
if (input.connection) {
nConnections++;
}
}
// The special case is when this is the only non-label field on the block
// and it has an output but no inputs.
this.fullBlockClickTarget_ =
nFields <= 1 && this.sourceBlock_.outputConnection && !nConnections;
} else {
this.fullBlockClickTarget_ = false;
}
if (this.fullBlockClickTarget_) {
this.clickTarget_ = this.sourceBlock_.getSvgRoot();
} else {
this.createBorderRect_();
}
this.createTextElement_();
};
/**
* Ensure that the input value casts to a valid string.
* @param {*=} opt_newValue The input value.
@@ -165,6 +222,22 @@ Blockly.FieldTextInput.prototype.doValueUpdate_ = function(newValue) {
}
};
/**
* Updates text field to match the colour/style of the block.
* @package
*/
Blockly.FieldTextInput.prototype.applyColour = function() {
if (this.sourceBlock_ && this.constants_.FULL_BLOCK_FIELDS) {
if (this.borderRect_) {
this.borderRect_.setAttribute('stroke',
this.sourceBlock_.style.colourTertiary);
} else {
this.sourceBlock_.pathObject.svgPath.setAttribute('fill',
this.constants_.FIELD_BORDER_RECT_COLOUR);
}
}
};
/**
* Updates the colour of the htmlInput given the current validity of the
* field's value.
@@ -175,20 +248,16 @@ Blockly.FieldTextInput.prototype.render_ = function() {
// This logic is done in render_ rather than doValueInvalid_ or
// doValueUpdate_ so that the code is more centralized.
if (this.isBeingEdited_) {
if (this.sourceBlock_.RTL) {
// in RTL, we need to let the browser reflow before resizing
// in order to get the correct bounding box of the borderRect
// avoiding issue #2777.
setTimeout(this.resizeEditor_.bind(this), 0);
} else {
this.resizeEditor_();
}
this.resizeEditor_();
var htmlInput = /** @type {!HTMLElement} */(this.htmlInput_);
if (!this.isTextValid_) {
Blockly.utils.dom.addClass(this.htmlInput_, 'blocklyInvalidInput');
Blockly.utils.aria.setState(this.htmlInput_, 'invalid', true);
Blockly.utils.dom.addClass(htmlInput, 'blocklyInvalidInput');
Blockly.utils.aria.setState(htmlInput,
Blockly.utils.aria.State.INVALID, true);
} else {
Blockly.utils.dom.removeClass(this.htmlInput_, 'blocklyInvalidInput');
Blockly.utils.aria.setState(this.htmlInput_, 'invalid', false);
Blockly.utils.dom.removeClass(htmlInput, 'blocklyInvalidInput');
Blockly.utils.aria.setState(htmlInput,
Blockly.utils.aria.State.INVALID, false);
}
}
};
@@ -209,11 +278,14 @@ Blockly.FieldTextInput.prototype.setSpellcheck = function(check) {
/**
* Show the inline free-text editor on top of the text.
* @param {Event=} _opt_e Optional mouse event that triggered the field to open,
* or undefined if triggered programatically.
* @param {boolean=} opt_quietInput True if editor should be created without
* focus. Defaults to false.
* @protected
*/
Blockly.FieldTextInput.prototype.showEditor_ = function(opt_quietInput) {
Blockly.FieldTextInput.prototype.showEditor_ = function(_opt_e,
opt_quietInput) {
this.workspace_ = this.sourceBlock_.workspace;
var quietInput = opt_quietInput || false;
if (!quietInput && (Blockly.utils.userAgent.MOBILE ||
@@ -251,7 +323,7 @@ Blockly.FieldTextInput.prototype.showInlineEditor_ = function(quietInput) {
this.isBeingEdited_ = true;
if (!quietInput) {
this.htmlInput_.focus();
this.htmlInput_.focus({preventScroll:true});
this.htmlInput_.select();
}
};
@@ -264,27 +336,45 @@ Blockly.FieldTextInput.prototype.showInlineEditor_ = function(quietInput) {
Blockly.FieldTextInput.prototype.widgetCreate_ = function() {
var div = Blockly.WidgetDiv.DIV;
Blockly.utils.dom.addClass(this.getClickTarget_(), 'editing');
var htmlInput = /** @type {HTMLInputElement} */ (document.createElement('input'));
htmlInput.className = 'blocklyHtmlInput';
htmlInput.setAttribute('spellcheck', this.spellcheck_);
var scale = this.workspace_.scale;
var fontSize =
(Blockly.FieldTextInput.FONTSIZE * this.workspace_.scale) + 'pt';
(this.constants_.FIELD_TEXT_FONTSIZE * scale) + 'pt';
div.style.fontSize = fontSize;
htmlInput.style.fontSize = fontSize;
var borderRadius =
(Blockly.FieldTextInput.BORDERRADIUS * this.workspace_.scale) + 'px';
(Blockly.FieldTextInput.BORDERRADIUS * scale) + 'px';
if (this.fullBlockClickTarget_) {
var bBox = this.getScaledBBox();
// Override border radius.
borderRadius = (bBox.bottom - bBox.top) / 2 + 'px';
// Pull stroke colour from the existing shadow block
var strokeColour = this.sourceBlock_.getParent() ?
this.sourceBlock_.getParent().style.colourTertiary :
this.sourceBlock_.style.colourTertiary;
htmlInput.style.border = (1 * scale) + 'px solid ' + strokeColour;
div.style.borderRadius = borderRadius;
div.style.transition = 'box-shadow 0.25s ease 0s';
if (this.constants_.FIELD_TEXTINPUT_BOX_SHADOW) {
div.style.boxShadow = 'rgba(255, 255, 255, 0.3) 0px 0px 0px ' +
4 * scale + 'px';
}
}
htmlInput.style.borderRadius = borderRadius;
div.appendChild(htmlInput);
htmlInput.value = htmlInput.defaultValue = this.getEditorText_(this.value_);
htmlInput.untypedDefaultValue_ = this.value_;
htmlInput.oldValue_ = null;
if (Blockly.utils.userAgent.GECKO) {
// In FF, ensure the browser reflows before resizing to avoid issue #2777.
setTimeout(this.resizeEditor_.bind(this), 0);
} else {
this.resizeEditor_();
}
this.resizeEditor_();
this.bindInputEvents_(htmlInput);
@@ -292,34 +382,32 @@ Blockly.FieldTextInput.prototype.widgetCreate_ = function() {
};
/**
* Close the editor, save the results, and dispose any events bound to the
* text input's editor.
* Closes the editor, saves the results, and disposes of any events or
* dom-references belonging to the editor.
* @private
*/
Blockly.FieldTextInput.prototype.widgetDispose_ = function() {
// Finalize value.
// Non-disposal related things that we do when the editor closes.
this.isBeingEdited_ = false;
this.isTextValid_ = true;
// Always re-render when the we close the editor as value
// set on the field's node may be inconsistent with the field's
// internal value.
// Make sure the field's node matches the field's internal value.
this.forceRerender();
// Call onFinishEditing
// TODO: Get rid of this or make it less of a hack.
// TODO(#2496): Make this less of a hack.
if (this.onFinishEditing_) {
this.onFinishEditing_(this.value_);
}
// Remove htmlInput events.
// Actual disposal.
this.unbindInputEvents_();
// Delete style properties.
var style = Blockly.WidgetDiv.DIV.style;
style.width = 'auto';
style.height = 'auto';
style.fontSize = '';
style.transition = '';
style.boxShadow = '';
this.htmlInput_ = null;
Blockly.utils.dom.removeClass(this.getClickTarget_(), 'editing');
};
/**
@@ -344,8 +432,14 @@ Blockly.FieldTextInput.prototype.bindInputEvents_ = function(htmlInput) {
* @private
*/
Blockly.FieldTextInput.prototype.unbindInputEvents_ = function() {
Blockly.unbindEvent_(this.onKeyDownWrapper_);
Blockly.unbindEvent_(this.onKeyInputWrapper_);
if (this.onKeyDownWrapper_) {
Blockly.unbindEvent_(this.onKeyDownWrapper_);
this.onKeyDownWrapper_ = null;
}
if (this.onKeyInputWrapper_) {
Blockly.unbindEvent_(this.onKeyInputWrapper_);
this.onKeyInputWrapper_ = null;
}
};
/**
@@ -386,6 +480,7 @@ Blockly.FieldTextInput.prototype.onHtmlInputChange_ = function(_e) {
var value = this.getValueFromEditorText_(text);
this.setValue(value);
this.forceRerender();
this.resizeEditor_();
Blockly.Events.setGroup(false);
}
};
@@ -415,7 +510,7 @@ Blockly.FieldTextInput.prototype.setEditorValue_ = function(newValue) {
*/
Blockly.FieldTextInput.prototype.resizeEditor_ = function() {
var div = Blockly.WidgetDiv.DIV;
var bBox = this.getScaledBBox_();
var bBox = this.getScaledBBox();
div.style.width = bBox.right - bBox.left + 'px';
div.style.height = bBox.bottom - bBox.top + 'px';
@@ -424,17 +519,6 @@ Blockly.FieldTextInput.prototype.resizeEditor_ = function() {
var x = this.sourceBlock_.RTL ? bBox.right - div.offsetWidth : bBox.left;
var xy = new Blockly.utils.Coordinate(x, bBox.top);
// Shift by a few pixels to line up exactly.
xy.y += 1;
if (Blockly.utils.userAgent.GECKO && Blockly.WidgetDiv.DIV.style.top) {
// Firefox mis-reports the location of the border by a pixel
// once the WidgetDiv is moved into position.
xy.x -= 1;
xy.y -= 1;
}
if (Blockly.utils.userAgent.WEBKIT) {
xy.y -= 3;
}
div.style.left = xy.x + 'px';
div.style.top = xy.y + 'px';
};
@@ -507,7 +591,7 @@ Blockly.FieldTextInput.prototype.getText_ = function() {
* than the field's value. This should be coupled with an override of
* `getValueFromEditorText_`.
* @param {*} value The value stored in this field.
* @returns {string} The text to show on the html input.
* @return {string} The text to show on the html input.
* @protected
*/
Blockly.FieldTextInput.prototype.getEditorText_ = function(value) {
@@ -521,7 +605,7 @@ Blockly.FieldTextInput.prototype.getEditorText_ = function(value) {
* than the field's value. This should be coupled with an override of
* `getEditorText_`.
* @param {string} text Text received from the html input.
* @returns {*} The value to store.
* @return {*} The value to store.
* @protected
*/
Blockly.FieldTextInput.prototype.getValueFromEditorText_ = function(text) {

View File

@@ -63,7 +63,8 @@ Blockly.FieldVariable = function(varName, opt_validator, opt_variableTypes,
/**
* An array of options for a dropdown list,
* or a function which generates these options.
* @type {!function(this:Blockly.FieldVariable): !Array.<!Array>}
* @type {(!Array.<!Array>|
* !function(this:Blockly.FieldDropdown): !Array.<!Array>)}
* @protected
*/
this.menuGenerator_ = Blockly.FieldVariable.dropdownCreate;
@@ -82,7 +83,7 @@ Blockly.FieldVariable = function(varName, opt_validator, opt_variableTypes,
* @protected
* @override
*/
this.size_ = new Blockly.utils.Size(0, Blockly.BlockSvg.MIN_BLOCK_Y);
this.size_ = new Blockly.utils.Size(0, 0);
opt_config && this.configure_(opt_config);
opt_validator && this.setValidator(opt_validator);
@@ -146,11 +147,17 @@ Blockly.FieldVariable.prototype.initModel = function() {
this.sourceBlock_.workspace, null,
this.defaultVariableName, this.defaultType_);
// Don't fire a change event for this setValue. It would have null as the
// old value, which is not valid.
Blockly.Events.disable();
this.setValue(variable.getId());
Blockly.Events.enable();
// Don't call setValue because we don't want to cause a rerender.
this.doValueUpdate_(variable.getId());
};
/**
* @override
*/
Blockly.FieldVariable.prototype.shouldAddBorderRect_ = function() {
return Blockly.FieldVariable.superClass_.shouldAddBorderRect_.call(this) &&
(!this.constants_.FIELD_DROPDOWN_NO_BORDER_RECT_SHADOW ||
this.sourceBlock_.type != 'variables_get');
};
/**
@@ -440,8 +447,9 @@ Blockly.FieldVariable.dropdownCreate = function() {
* In the rename case, prompt the user for a new name.
* @param {!Blockly.Menu} menu The Menu component clicked.
* @param {!Blockly.MenuItem} menuItem The MenuItem selected within menu.
* @protected
*/
Blockly.FieldVariable.prototype.onItemSelected = function(menu, menuItem) {
Blockly.FieldVariable.prototype.onItemSelected_ = function(menu, menuItem) {
var id = menuItem.getValue();
// Handle special cases.
if (this.sourceBlock_ && this.sourceBlock_.workspace) {

View File

@@ -30,7 +30,7 @@ goog.require('Blockly.Events.BlockCreate');
goog.require('Blockly.Events.VarCreate');
goog.require('Blockly.FlyoutCursor');
goog.require('Blockly.Gesture');
goog.require('Blockly.MarkerCursor');
goog.require('Blockly.Marker');
goog.require('Blockly.Scrollbar');
goog.require('Blockly.Tooltip');
goog.require('Blockly.Touch');
@@ -43,7 +43,8 @@ goog.require('Blockly.Xml');
/**
* Class for a flyout.
* @param {!Object} workspaceOptions Dictionary of options for the workspace.
* @param {!Blockly.Options} workspaceOptions Dictionary of options for the
* workspace.
* @constructor
*/
Blockly.Flyout = function(workspaceOptions) {
@@ -56,8 +57,6 @@ Blockly.Flyout = function(workspaceOptions) {
*/
this.workspace_ = new Blockly.WorkspaceSvg(workspaceOptions);
this.workspace_.isFlyout = true;
this.workspace_.setCursor(new Blockly.FlyoutCursor());
this.workspace_.setMarker(new Blockly.MarkerCursor());
/**
* Is RTL vs LTR.
@@ -111,6 +110,7 @@ Blockly.Flyout = function(workspaceOptions) {
/**
* Width of output tab.
* @type {number}
* @protected
* @const
*/
this.tabWidth_ = this.workspace_.getRenderer().getConstants().TAB_WIDTH;
@@ -227,15 +227,18 @@ Blockly.Flyout.prototype.createDom = function(tagName) {
this.svgBackground_ = Blockly.utils.dom.createSvgElement('path',
{'class': 'blocklyFlyoutBackground'}, this.svgGroup_);
this.svgGroup_.appendChild(this.workspace_.createDom());
this.workspace_.getThemeManager().subscribe(this.svgBackground_, 'flyout', 'fill');
this.workspace_.getThemeManager().subscribe(this.svgBackground_, 'flyoutOpacity', 'fill-opacity');
this.workspace_.getThemeManager().subscribe(
this.svgBackground_, 'flyoutBackgroundColour', 'fill');
this.workspace_.getThemeManager().subscribe(
this.svgBackground_, 'flyoutOpacity', 'fill-opacity');
this.workspace_.getMarkerManager().setCursor(new Blockly.FlyoutCursor());
return this.svgGroup_;
};
/**
* Initializes the flyout.
* @param {!Blockly.Workspace} targetWorkspace The workspace in which to create
* new blocks.
* @param {!Blockly.WorkspaceSvg} targetWorkspace The workspace in which to
* create new blocks.
*/
Blockly.Flyout.prototype.init = function(targetWorkspace) {
this.targetWorkspace_ = targetWorkspace;
@@ -264,7 +267,7 @@ Blockly.Flyout.prototype.init = function(targetWorkspace) {
this.targetWorkspace_.getGesture.bind(this.targetWorkspace_);
// Get variables from the main workspace rather than the target workspace.
this.workspace_.variableMap_ = this.targetWorkspace_.getVariableMap();
this.workspace_.setVariableMap(this.targetWorkspace_.getVariableMap());
this.workspace_.createPotentialVariableMap();
};
@@ -272,6 +275,7 @@ Blockly.Flyout.prototype.init = function(targetWorkspace) {
/**
* Dispose of this flyout.
* Unlink from all DOM elements to prevent memory leaks.
* @suppress {checkTypes}
*/
Blockly.Flyout.prototype.dispose = function() {
this.hide();
@@ -417,7 +421,7 @@ Blockly.Flyout.prototype.hide = function() {
}
this.setVisible(false);
// Delete all the event listeners.
for (var i = 0, listen; listen = this.listeners_[i]; i++) {
for (var i = 0, listen; (listen = this.listeners_[i]); i++) {
Blockly.unbindEvent_(listen);
}
this.listeners_.length = 0;
@@ -431,7 +435,7 @@ Blockly.Flyout.prototype.hide = function() {
/**
* Show and populate the flyout.
* @param {!Array|string} xmlList List of blocks to show.
* @param {!Array|!NodeList|string} xmlList List of blocks to show.
* Variables and procedures have a custom set of blocks.
*/
Blockly.Flyout.prototype.show = function(xmlList) {
@@ -461,7 +465,7 @@ Blockly.Flyout.prototype.show = function(xmlList) {
var gaps = [];
this.permanentlyDisabled_.length = 0;
var default_gap = this.horizontalLayout_ ? this.GAP_X : this.GAP_Y;
for (var i = 0, xml; xml = xmlList[i]; i++) {
for (var i = 0, xml; (xml = xmlList[i]); i++) {
if (!xml.tagName) {
continue;
}
@@ -512,7 +516,7 @@ Blockly.Flyout.prototype.show = function(xmlList) {
// When the mouse is over the background, deselect all blocks.
var deselectAll = function() {
var topBlocks = this.workspace_.getTopBlocks(false);
for (var i = 0, block; block = topBlocks[i]; i++) {
for (var i = 0, block; (block = topBlocks[i]); i++) {
block.removeSelect();
}
};
@@ -544,7 +548,7 @@ Blockly.Flyout.prototype.show = function(xmlList) {
Blockly.Flyout.prototype.clearOldBlocks_ = function() {
// Delete any blocks from a previous showing.
var oldBlocks = this.workspace_.getTopBlocks(false);
for (var i = 0, block; block = oldBlocks[i]; i++) {
for (var i = 0, block; (block = oldBlocks[i]); i++) {
if (block.workspace == this.workspace_) {
block.dispose(false, false);
}
@@ -558,7 +562,7 @@ Blockly.Flyout.prototype.clearOldBlocks_ = function() {
}
this.mats_.length = 0;
// Delete any buttons from a previous showing.
for (var i = 0, button; button = this.buttons_[i]; i++) {
for (var i = 0, button; (button = this.buttons_[i]); i++) {
button.dispose();
}
this.buttons_.length = 0;
@@ -580,13 +584,13 @@ Blockly.Flyout.prototype.addBlockListeners_ = function(root, block, rect) {
this.blockMouseDown_(block)));
this.listeners_.push(Blockly.bindEventWithChecks_(rect, 'mousedown', null,
this.blockMouseDown_(block)));
this.listeners_.push(Blockly.bindEvent_(root, 'mouseover', block,
this.listeners_.push(Blockly.bindEvent_(root, 'mouseenter', block,
block.addSelect));
this.listeners_.push(Blockly.bindEvent_(root, 'mouseout', block,
this.listeners_.push(Blockly.bindEvent_(root, 'mouseleave', block,
block.removeSelect));
this.listeners_.push(Blockly.bindEvent_(rect, 'mouseover', block,
this.listeners_.push(Blockly.bindEvent_(rect, 'mouseenter', block,
block.addSelect));
this.listeners_.push(Blockly.bindEvent_(rect, 'mouseout', block,
this.listeners_.push(Blockly.bindEvent_(rect, 'mouseleave', block,
block.removeSelect));
};
@@ -751,7 +755,7 @@ Blockly.Flyout.prototype.moveRectToBlock_ = function(rect, block) {
*/
Blockly.Flyout.prototype.filterForCapacity_ = function() {
var blocks = this.workspace_.getTopBlocks(false);
for (var i = 0, block; block = blocks[i]; i++) {
for (var i = 0, block; (block = blocks[i]); i++) {
if (this.permanentlyDisabled_.indexOf(block) == -1) {
var enable = this.targetWorkspace_
.isCapacityAvailable(Blockly.utils.getBlockTypeCounts(block));
@@ -787,8 +791,8 @@ Blockly.Flyout.prototype.isScrollable = function() {
/**
* Copy a block from the flyout to the workspace and position it correctly.
* @param {!Blockly.Block} oldBlock The flyout block to copy.
* @return {!Blockly.Block} The new block in the main workspace.
* @param {!Blockly.BlockSvg} oldBlock The flyout block to copy.
* @return {!Blockly.BlockSvg} The new block in the main workspace.
* @private
*/
Blockly.Flyout.prototype.placeNewBlock_ = function(oldBlock) {
@@ -806,7 +810,8 @@ Blockly.Flyout.prototype.placeNewBlock_ = function(oldBlock) {
// Using domToBlock instead of domToWorkspace means that the new block will be
// placed at position (0, 0) in main workspace units.
var block = Blockly.Xml.domToBlock(xml, targetWorkspace);
var block = /** @type {!Blockly.BlockSvg} */
(Blockly.Xml.domToBlock(xml, targetWorkspace));
var svgRootNew = block.getSvgRoot();
if (!svgRootNew) {
throw Error('block is not rendered.');
@@ -841,3 +846,15 @@ Blockly.Flyout.prototype.placeNewBlock_ = function(oldBlock) {
block.moveBy(finalOffset.x, finalOffset.y);
return block;
};
/**
* Handles the given action.
* This is only triggered when keyboard accessibility mode is enabled.
* @param {!Blockly.Action} action The action to be handled.
* @return {boolean} True if the flyout handled the action, false otherwise.
* @package
*/
Blockly.Flyout.prototype.onBlocklyAction = function(action) {
var cursor = this.workspace_.getCursor();
return cursor.onBlocklyAction(action);
};

View File

@@ -87,6 +87,13 @@ Blockly.FlyoutButton = function(workspace, targetWorkspace, xml, isLabel) {
* @private
*/
this.cssClass_ = xml.getAttribute('web-class') || null;
/**
* Mouse up event data.
* @type {?Blockly.EventData}
* @private
*/
this.onMouseUpWrapper_ = null;
};
/**
@@ -106,13 +113,6 @@ Blockly.FlyoutButton.prototype.width = 0;
*/
Blockly.FlyoutButton.prototype.height = 0;
/**
* Opaque data that can be passed to Blockly.unbindEvent_.
* @type {Array.<!Array>}
* @private
*/
Blockly.FlyoutButton.prototype.onMouseUpWrapper_ = null;
/**
* Create the button elements.
* @return {!SVGElement} The button's SVG group.
@@ -152,10 +152,16 @@ Blockly.FlyoutButton.prototype.createDom = function() {
'text-anchor': 'middle'
},
this.svgGroup_);
svgText.textContent = Blockly.utils.replaceMessageReferences(this.text_);
var text = Blockly.utils.replaceMessageReferences(this.text_);
if (this.workspace_.RTL) {
// Force text to be RTL by adding an RLM.
text += '\u200F';
}
svgText.textContent = text;
if (this.isLabel_) {
this.svgText_ = svgText;
this.workspace_.getThemeManager().subscribe(this.svgText_, 'flyoutText', 'fill');
this.workspace_.getThemeManager().subscribe(this.svgText_,
'flyoutForegroundColour', 'fill');
}
this.width = Blockly.utils.dom.getTextWidth(svgText);

View File

@@ -34,12 +34,14 @@ goog.require('Blockly.WidgetDiv');
/**
* Class for a flyout.
* @param {!Object} workspaceOptions Dictionary of options for the workspace.
* @param {!Blockly.Options} workspaceOptions Dictionary of options for the
* workspace.
* @extends {Blockly.Flyout}
* @constructor
*/
Blockly.HorizontalFlyout = function(workspaceOptions) {
workspaceOptions.getMetrics = this.getMetrics_.bind(this);
workspaceOptions.getMetrics = /** @type {function():!Object} */ (
this.getMetrics_.bind(this));
workspaceOptions.setMetrics = this.setMetrics_.bind(this);
Blockly.HorizontalFlyout.superClass_.constructor.call(this, workspaceOptions);
@@ -203,7 +205,7 @@ Blockly.HorizontalFlyout.prototype.setBackgroundPath_ = function(width,
// Bottom.
path.push('a', this.CORNER_RADIUS, this.CORNER_RADIUS, 0, 0, 1,
-this.CORNER_RADIUS, this.CORNER_RADIUS);
path.push('h', -1 * width);
path.push('h', -width);
// Left.
path.push('a', this.CORNER_RADIUS, this.CORNER_RADIUS, 0, 0, 1,
-this.CORNER_RADIUS, -this.CORNER_RADIUS);
@@ -352,7 +354,7 @@ Blockly.HorizontalFlyout.prototype.getClientRect = function() {
var height = flyoutRect.height;
return new Blockly.utils.Rect(-BIG_NUM, top + height, -BIG_NUM, BIG_NUM);
} else { // Bottom.
return new Blockly.utils.Rect(top, -BIG_NUM, -BIG_NUM, BIG_NUM);
return new Blockly.utils.Rect(top, BIG_NUM, -BIG_NUM, BIG_NUM);
}
};

View File

@@ -35,12 +35,14 @@ goog.require('Blockly.WidgetDiv');
/**
* Class for a flyout.
* @param {!Object} workspaceOptions Dictionary of options for the workspace.
* @param {!Blockly.Options} workspaceOptions Dictionary of options for the
* workspace.
* @extends {Blockly.Flyout}
* @constructor
*/
Blockly.VerticalFlyout = function(workspaceOptions) {
workspaceOptions.getMetrics = this.getMetrics_.bind(this);
workspaceOptions.getMetrics = /** @type {function():!Object} */ (
this.getMetrics_.bind(this));
workspaceOptions.setMetrics = this.setMetrics_.bind(this);
Blockly.VerticalFlyout.superClass_.constructor.call(this, workspaceOptions);
@@ -257,11 +259,11 @@ Blockly.VerticalFlyout.prototype.layout_ = function(contents, gaps) {
var cursorX = this.RTL ? margin : margin + this.tabWidth_;
var cursorY = margin;
for (var i = 0, item; item = contents[i]; i++) {
for (var i = 0, item; (item = contents[i]); i++) {
if (item.type == 'block') {
var block = item.block;
var allBlocks = block.getDescendants(false);
for (var j = 0, child; child = allBlocks[j]; j++) {
for (var j = 0, child; (child = allBlocks[j]); j++) {
// Mark blocks as being inside a flyout. This is used to detect and
// prevent the closure of the flyout if the user right-clicks on such a
// block.
@@ -331,30 +333,6 @@ Blockly.VerticalFlyout.prototype.getClientRect = function() {
var width = flyoutRect.width;
return new Blockly.utils.Rect(-BIG_NUM, BIG_NUM, -BIG_NUM, left + width);
} else { // Right
// Firefox sometimes reports the wrong value for the client rect.
// See https://github.com/google/blockly/issues/1425 and
// https://bugzilla.mozilla.org/show_bug.cgi?id=1066435
if (Blockly.utils.userAgent.GECKO &&
this.targetWorkspace_ && this.targetWorkspace_.isMutator) {
// The position of the left side of the mutator workspace in pixels
// relative to the window origin.
var targetWsLeftPixels =
this.targetWorkspace_.svgGroup_.getBoundingClientRect().x;
// The client rect is in pixels relative to the window origin. When the
// browser gets the wrong value it reports that the flyout left is the
// same as the mutator workspace left.
// We know that in a mutator workspace with the flyout on the right, the
// visible area of the workspace should be more than ten pixels wide. If
// the browser reports that the flyout is within ten pixels of the left
// side of the workspace, ignore it and manually calculate the value.
if (Math.abs(targetWsLeftPixels - left) < 10) {
// If we're in a mutator, its scale is always 1, purely because of some
// oddities in our rendering optimizations. The actual scale is the
// same as the scale on the parent workspace.
var scale = this.targetWorkspace_.options.parentWorkspace.scale;
left += this.leftEdge_ * scale;
}
}
return new Blockly.utils.Rect(-BIG_NUM, BIG_NUM, left, BIG_NUM);
}
};
@@ -383,7 +361,7 @@ Blockly.VerticalFlyout.prototype.reflowInternal_ = function() {
flyoutWidth += Blockly.Scrollbar.scrollbarThickness;
if (this.width_ != flyoutWidth) {
for (var i = 0, block; block = blocks[i]; i++) {
for (var i = 0, block; (block = blocks[i]); i++) {
if (this.RTL) {
// With the flyoutWidth known, right-align the blocks.
var oldX = block.getRelativeToSurfaceXY().x;

View File

@@ -101,7 +101,7 @@ Blockly.Generator.prototype.workspaceToCode = function(workspace) {
var code = [];
this.init(workspace);
var blocks = workspace.getTopBlocks(true);
for (var i = 0, block; block = blocks[i]; i++) {
for (var i = 0, block; (block = blocks[i]); i++) {
var line = this.blockToCode(block);
if (Array.isArray(line)) {
// Value blocks return tuples of code and operator order.
@@ -351,7 +351,7 @@ Blockly.Generator.prototype.injectId = function(msg, block) {
/**
* Comma-separated list of reserved words.
* @type {string}
* @private
* @protected
*/
Blockly.Generator.prototype.RESERVED_WORDS_ = '';
@@ -370,10 +370,32 @@ Blockly.Generator.prototype.addReservedWords = function(words) {
* legitimately appear in a function definition (or comment), and it must
* not confuse the regular expression parser.
* @type {string}
* @private
* @protected
*/
Blockly.Generator.prototype.FUNCTION_NAME_PLACEHOLDER_ = '{leCUI8hutHZI4480Dc}';
/**
* A dictionary of definitions to be printed before the code.
* @type {Object}
* @protected
*/
Blockly.Generator.prototype.definitions_;
/**
* A dictionary mapping desired function names in definitions_ to actual
* function names (to avoid collisions with user functions).
* @type {Object}
* @protected
*/
Blockly.Generator.prototype.functionNames_;
/**
* A database of variable names.
* @type {Blockly.Names}
* @protected
*/
Blockly.Generator.prototype.variableDB_;
/**
* Define a function to be included in the generated code.
* The first time this is called with a given desiredName, the code is
@@ -389,12 +411,12 @@ Blockly.Generator.prototype.FUNCTION_NAME_PLACEHOLDER_ = '{leCUI8hutHZI4480Dc}';
* @param {!Array.<string>} code A list of statements. Use ' ' for indents.
* @return {string} The actual name of the new function. This may differ
* from desiredName if the former has already been taken by the user.
* @private
* @protected
*/
Blockly.Generator.prototype.provideFunction_ = function(desiredName, code) {
if (!this.definitions_[desiredName]) {
var functionName = this.variableDB_.getDistinctName(desiredName,
Blockly.Procedures.NAME_TYPE);
Blockly.PROCEDURE_CATEGORY_NAME);
this.functionNames_[desiredName] = functionName;
var codeText = code.join('\n').replace(
this.FUNCTION_NAME_PLACEHOLDER_REGEXP_, functionName);
@@ -431,10 +453,12 @@ Blockly.Generator.prototype.init = function(_workspace) {
* value blocks.
* @param {!Blockly.Block} _block The current block.
* @param {string} code The code created for this block.
* @param {boolean=} _opt_thisOnly True to generate code for only this
* statement.
* @return {string} Code with comments and subsequent blocks added.
* @private
* @protected
*/
Blockly.Generator.prototype.scrub_ = function(_block, code) {
Blockly.Generator.prototype.scrub_ = function(_block, code, _opt_thisOnly) {
// Optionally override
return code;
};

View File

@@ -24,6 +24,7 @@
goog.provide('Blockly.Gesture');
goog.require('Blockly.ASTNode');
goog.require('Blockly.blockAnimations');
goog.require('Blockly.BlockDragger');
goog.require('Blockly.BubbleDragger');
@@ -31,6 +32,7 @@ goog.require('Blockly.constants');
goog.require('Blockly.Events');
goog.require('Blockly.Events.Ui');
goog.require('Blockly.FlyoutDragger');
goog.require('Blockly.navigation');
goog.require('Blockly.Tooltip');
goog.require('Blockly.Touch');
goog.require('Blockly.utils');
@@ -38,7 +40,7 @@ goog.require('Blockly.utils.Coordinate');
goog.require('Blockly.WorkspaceDragger');
/*
/**
* Note: In this file "start" refers to touchstart, mousedown, and pointerstart
* events. "End" refers to touchend, mouseup, and pointerend events.
*/
@@ -57,16 +59,17 @@ Blockly.Gesture = function(e, creatorWorkspace) {
* The position of the mouse when the gesture started. Units are CSS pixels,
* with (0, 0) at the top left of the browser window (mouseEvent clientX/Y).
* @type {Blockly.utils.Coordinate}
* @private
*/
this.mouseDownXY_ = null;
/**
* How far the mouse has moved during this drag, in pixel units.
* (0, 0) is at this.mouseDownXY_.
* @type {Blockly.utils.Coordinate}
* @type {!Blockly.utils.Coordinate}
* @private
*/
this.currentDragDeltaXY_ = null;
this.currentDragDeltaXY_ = new Blockly.utils.Coordinate(0, 0);
/**
* The bubble that the gesture started on, or null if it did not start on a
@@ -116,7 +119,7 @@ Blockly.Gesture = function(e, creatorWorkspace) {
* to the gesture, which will need to be cleared at deletion.
* This may be different from the start workspace. For instance, a flyout is
* a workspace, but its parent workspace manages gestures for it.
* @type {Blockly.WorkspaceSvg}
* @type {!Blockly.WorkspaceSvg}
* @private
*/
this.creatorWorkspace_ = creatorWorkspace;
@@ -161,7 +164,7 @@ Blockly.Gesture = function(e, creatorWorkspace) {
/**
* A handle to use to unbind a mouse move listener at the end of a drag.
* Opaque data returned from Blockly.bindEventWithChecks_.
* @type {Array.<!Array>}
* @type {?Blockly.EventData}
* @protected
*/
this.onMoveWrapper_ = null;
@@ -169,7 +172,7 @@ Blockly.Gesture = function(e, creatorWorkspace) {
/**
* A handle to use to unbind a mouse up listener at the end of a drag.
* Opaque data returned from Blockly.bindEventWithChecks_.
* @type {Array.<!Array>}
* @type {?Blockly.EventData}
* @protected
*/
this.onUpWrapper_ = null;
@@ -250,24 +253,14 @@ Blockly.Gesture.prototype.dispose = function() {
Blockly.unbindEvent_(this.onUpWrapper_);
}
this.startField_ = null;
this.startBlock_ = null;
this.targetBlock_ = null;
this.startWorkspace_ = null;
this.flyout_ = null;
if (this.blockDragger_) {
this.blockDragger_.dispose();
this.blockDragger_ = null;
}
if (this.workspaceDragger_) {
this.workspaceDragger_.dispose();
this.workspaceDragger_ = null;
}
if (this.bubbleDragger_) {
this.bubbleDragger_.dispose();
this.bubbleDragger_ = null;
}
};
@@ -297,7 +290,7 @@ Blockly.Gesture.prototype.updateFromEvent_ = function(e) {
*/
Blockly.Gesture.prototype.updateDragDelta_ = function(currentXY) {
this.currentDragDeltaXY_ = Blockly.utils.Coordinate.difference(currentXY,
this.mouseDownXY_);
/** @type {!Blockly.utils.Coordinate} */ (this.mouseDownXY_));
if (!this.hasExceededDragRadius_) {
var currentDragDelta = Blockly.utils.Coordinate.magnitude(
@@ -324,6 +317,9 @@ Blockly.Gesture.prototype.updateDragDelta_ = function(currentXY) {
* @private
*/
Blockly.Gesture.prototype.updateIsDraggingFromFlyout_ = function() {
if (!this.targetBlock_) {
return false;
}
if (!this.flyout_.isBlockCreatable_(this.targetBlock_)) {
return false;
}
@@ -410,7 +406,8 @@ Blockly.Gesture.prototype.updateIsDraggingWorkspace_ = function() {
if (this.flyout_) {
this.workspaceDragger_ = new Blockly.FlyoutDragger(this.flyout_);
} else {
this.workspaceDragger_ = new Blockly.WorkspaceDragger(this.startWorkspace_);
this.workspaceDragger_ = new Blockly.WorkspaceDragger(
/** @type {!Blockly.WorkspaceSvg} */ (this.startWorkspace_));
}
this.isDraggingWorkspace_ = true;
@@ -447,8 +444,9 @@ Blockly.Gesture.prototype.updateIsDragging_ = function() {
* @private
*/
Blockly.Gesture.prototype.startDraggingBlock_ = function() {
this.blockDragger_ = new Blockly.BlockDragger(this.targetBlock_,
this.startWorkspace_);
this.blockDragger_ = new Blockly.BlockDragger(
/** @type {!Blockly.BlockSvg} */ (this.targetBlock_),
/** @type {!Blockly.WorkspaceSvg} */ (this.startWorkspace_));
this.blockDragger_.startBlockDrag(this.currentDragDeltaXY_, this.healStack_);
this.blockDragger_.dragBlock(this.mostRecentEvent_,
this.currentDragDeltaXY_);
@@ -460,8 +458,9 @@ Blockly.Gesture.prototype.startDraggingBlock_ = function() {
*/
// TODO (fenichel): Possibly combine this and startDraggingBlock_.
Blockly.Gesture.prototype.startDraggingBubble_ = function() {
this.bubbleDragger_ = new Blockly.BubbleDragger(this.startBubble_,
this.startWorkspace_);
this.bubbleDragger_ = new Blockly.BubbleDragger(
/** @type {!Blockly.Bubble} */ (this.startBubble_),
/** @type {!Blockly.WorkspaceSvg} */ (this.startWorkspace_));
this.bubbleDragger_.startBubbleDrag();
this.bubbleDragger_.dragBubble(this.mostRecentEvent_,
this.currentDragDeltaXY_);
@@ -486,17 +485,20 @@ Blockly.Gesture.prototype.doStart = function(e) {
// dragged, the block was moved, the parent workspace zoomed, etc.
this.startWorkspace_.resize();
}
this.startWorkspace_.markFocused();
this.mostRecentEvent_ = e;
// Hide chaff also hides the flyout, so don't do it if the click is in a
// flyout.
Blockly.hideChaff(!!this.flyout_);
this.startWorkspace_.markFocused();
this.mostRecentEvent_ = e;
Blockly.Tooltip.block();
if (this.targetBlock_) {
if (!this.targetBlock_.isInFlyout && e.shiftKey) {
Blockly.navigation.enableKeyboardAccessibility();
if (!this.targetBlock_.isInFlyout &&
e.shiftKey &&
this.targetBlock_.workspace.keyboardAccessibilityMode) {
this.creatorWorkspace_.getCursor().setCurNode(
Blockly.navigation.getTopNode(this.targetBlock_));
} else {
@@ -512,7 +514,7 @@ Blockly.Gesture.prototype.doStart = function(e) {
if ((e.type.toLowerCase() == 'touchstart' ||
e.type.toLowerCase() == 'pointerdown') &&
e.pointerType != 'mouse') {
Blockly.longStart_(e, this);
Blockly.longStart(e, this);
}
this.mouseDownXY_ = new Blockly.utils.Coordinate(e.clientX, e.clientY);
@@ -630,13 +632,13 @@ Blockly.Gesture.prototype.cancel = function() {
Blockly.Gesture.prototype.handleRightClick = function(e) {
if (this.targetBlock_) {
this.bringBlockToFront_();
Blockly.hideChaff(this.flyout_);
this.targetBlock_.showContextMenu_(e);
Blockly.hideChaff(!!this.flyout_);
this.targetBlock_.showContextMenu(e);
} else if (this.startBubble_) {
this.startBubble_.showContextMenu_(e);
this.startBubble_.showContextMenu(e);
} else if (this.startWorkspace_ && !this.flyout_) {
Blockly.hideChaff();
this.startWorkspace_.showContextMenu_(e);
this.startWorkspace_.showContextMenu(e);
}
// TODO: Handle right-click on a bubble.
@@ -649,7 +651,7 @@ Blockly.Gesture.prototype.handleRightClick = function(e) {
/**
* Handle a mousedown/touchstart event on a workspace.
* @param {!Event} e A mouse down or touch start event.
* @param {!Blockly.Workspace} ws The workspace the event hit.
* @param {!Blockly.WorkspaceSvg} ws The workspace the event hit.
* @package
*/
Blockly.Gesture.prototype.handleWsStart = function(e, ws) {
@@ -660,7 +662,7 @@ Blockly.Gesture.prototype.handleWsStart = function(e, ws) {
this.setStartWorkspace_(ws);
this.mostRecentEvent_ = e;
this.doStart(e);
if (Blockly.keyboardAccessibilityMode) {
if (this.startWorkspace_.keyboardAccessibilityMode) {
Blockly.navigation.setState(Blockly.navigation.STATE_WS);
}
};
@@ -729,7 +731,7 @@ Blockly.Gesture.prototype.doBubbleClick_ = function() {
* @private
*/
Blockly.Gesture.prototype.doFieldClick_ = function() {
this.startField_.showEditor_();
this.startField_.showEditor(this.mostRecentEvent_);
this.bringBlockToFront_();
};
@@ -764,8 +766,7 @@ Blockly.Gesture.prototype.doBlockClick_ = function() {
*/
Blockly.Gesture.prototype.doWorkspaceClick_ = function(e) {
var ws = this.creatorWorkspace_;
if (e.shiftKey) {
Blockly.navigation.enableKeyboardAccessibility();
if (e.shiftKey && ws.keyboardAccessibilityMode) {
var screenCoord = new Blockly.utils.Coordinate(e.clientX, e.clientY);
var wsCoord = Blockly.utils.screenToWsCoordinates(ws, screenCoord);
var wsNode = Blockly.ASTNode.createWorkspaceNode(ws, wsCoord);
@@ -975,7 +976,7 @@ Blockly.Gesture.prototype.getInsertionMarkers = function() {
*/
Blockly.Gesture.inProgress = function() {
var workspaces = Blockly.Workspace.getAll();
for (var i = 0, workspace; workspace = workspaces[i]; i++) {
for (var i = 0, workspace; (workspace = workspaces[i]); i++) {
if (workspace.currentGesture_) {
return true;
}

View File

@@ -64,14 +64,15 @@ Blockly.Grid = function(pattern, options) {
* @type {SVGElement}
* @private
*/
this.line1_ = pattern.firstChild;
this.line1_ = /** @type {SVGElement} */ (pattern.firstChild);
/**
* The vertical grid line, if it exists.
* @type {SVGElement}
* @private
*/
this.line2_ = this.line1_ && this.line1_.nextSibling;
this.line2_ = this.line1_ &&
(/** @type {SVGElement} */ (this.line1_.nextSibling));
/**
* Whether blocks should snap to the grid.
@@ -92,6 +93,7 @@ Blockly.Grid.prototype.scale_ = 1;
/**
* Dispose of this grid and unlink from the DOM.
* @package
* @suppress {checkTypes}
*/
Blockly.Grid.prototype.dispose = function() {
this.gridPattern_ = null;
@@ -153,7 +155,7 @@ Blockly.Grid.prototype.update = function(scale) {
/**
* Set the attributes on one of the lines in the grid. Use this to update the
* length and stroke width of the grid lines.
* @param {!SVGElement} line Which line to update.
* @param {SVGElement} line Which line to update.
* @param {number} width The new stroke size (in px).
* @param {number} x1 The new x start position of the line (in px).
* @param {number} x2 The new x end position of the line (in px).

View File

@@ -139,9 +139,9 @@ Blockly.Icon.prototype.iconClick_ = function(e) {
/**
* Change the colour of the associated bubble to match its block.
*/
Blockly.Icon.prototype.updateColour = function() {
Blockly.Icon.prototype.applyColour = function() {
if (this.isVisible()) {
this.bubble_.setColour(this.block_.getColour());
this.bubble_.setColour(this.block_.style.colourPrimary);
}
};
@@ -174,8 +174,8 @@ Blockly.Icon.prototype.computeIconLocation = function() {
/**
* Returns the center of the block's icon relative to the surface.
* @return {!Blockly.utils.Coordinate} Object with x and y properties in workspace
* coordinates.
* @return {Blockly.utils.Coordinate} Object with x and y properties in
* workspace coordinates.
*/
Blockly.Icon.prototype.getIconLocation = function() {
return this.iconXY_;

View File

@@ -29,9 +29,11 @@ goog.require('Blockly.Css');
goog.require('Blockly.DropDownDiv');
goog.require('Blockly.Events');
goog.require('Blockly.Grid');
goog.require('Blockly.Msg');
goog.require('Blockly.Options');
goog.require('Blockly.ScrollbarPair');
goog.require('Blockly.Tooltip');
goog.require('Blockly.user.keyMap');
goog.require('Blockly.utils');
goog.require('Blockly.utils.dom');
goog.require('Blockly.utils.userAgent');
@@ -41,10 +43,10 @@ goog.require('Blockly.WorkspaceSvg');
/**
* Inject a Blockly editor into the specified container element (usually a div).
* @param {!Element|string} container Containing element, or its ID,
* @param {Element|string} container Containing element, or its ID,
* or a CSS selector.
* @param {Object=} opt_options Optional dictionary of options.
* @return {!Blockly.Workspace} Newly created main workspace.
* @param {Blockly.BlocklyOptions=} opt_options Optional dictionary of options.
* @return {!Blockly.WorkspaceSvg} Newly created main workspace.
*/
Blockly.inject = function(container, opt_options) {
Blockly.checkBlockColourConstants();
@@ -54,12 +56,17 @@ Blockly.inject = function(container, opt_options) {
document.querySelector(container);
}
// Verify that the container is in document.
if (!Blockly.utils.dom.containsNode(document, container)) {
if (!container || !Blockly.utils.dom.containsNode(document, container)) {
throw Error('Error: container is not in current document.');
}
var options = new Blockly.Options(opt_options || {});
var options = new Blockly.Options(opt_options ||
(/** @type {!Blockly.BlocklyOptions} */ ({})));
var subContainer = document.createElement('div');
subContainer.className = 'injectionDiv';
subContainer.tabIndex = 0;
Blockly.utils.aria.setState(subContainer,
Blockly.utils.aria.State.LABEL, Blockly.Msg['WORKSPACE_ARIA_LABEL']);
container.appendChild(subContainer);
var svg = Blockly.createDom_(subContainer, options);
@@ -73,10 +80,16 @@ Blockly.inject = function(container, opt_options) {
Blockly.user.keyMap.setKeyMap(options.keyMap);
Blockly.init_(workspace);
// Keep focus on the first workspace so entering keyboard navigation looks correct.
Blockly.mainWorkspace = workspace;
Blockly.svgResize(workspace);
subContainer.addEventListener('focusin', function() {
Blockly.mainWorkspace = workspace;
});
return workspace;
};
@@ -114,7 +127,8 @@ Blockly.createDom_ = function(container, options) {
'xmlns:html': Blockly.utils.dom.HTML_NS,
'xmlns:xlink': Blockly.utils.dom.XLINK_NS,
'version': '1.1',
'class': 'blocklySvg'
'class': 'blocklySvg',
'tabindex': '0'
}, container);
/*
<defs>
@@ -126,73 +140,6 @@ Blockly.createDom_ = function(container, options) {
// instances on a page. Browser behaviour becomes undefined otherwise.
// https://neil.fraser.name/news/2015/11/01/
var rnd = String(Math.random()).substring(2);
/*
<filter id="blocklyEmbossFilter837493">
<feGaussianBlur in="SourceAlpha" stdDeviation="1" result="blur" />
<feSpecularLighting in="blur" surfaceScale="1" specularConstant="0.5"
specularExponent="10" lighting-color="white"
result="specOut">
<fePointLight x="-5000" y="-10000" z="20000" />
</feSpecularLighting>
<feComposite in="specOut" in2="SourceAlpha" operator="in"
result="specOut" />
<feComposite in="SourceGraphic" in2="specOut" operator="arithmetic"
k1="0" k2="1" k3="1" k4="0" />
</filter>
*/
var embossFilter = Blockly.utils.dom.createSvgElement('filter',
{'id': 'blocklyEmbossFilter' + rnd}, defs);
Blockly.utils.dom.createSvgElement('feGaussianBlur',
{'in': 'SourceAlpha', 'stdDeviation': 1, 'result': 'blur'}, embossFilter);
var feSpecularLighting = Blockly.utils.dom.createSvgElement('feSpecularLighting',
{
'in': 'blur',
'surfaceScale': 1,
'specularConstant': 0.5,
'specularExponent': 10,
'lighting-color': 'white',
'result': 'specOut'
},
embossFilter);
Blockly.utils.dom.createSvgElement('fePointLight',
{'x': -5000, 'y': -10000, 'z': 20000}, feSpecularLighting);
Blockly.utils.dom.createSvgElement('feComposite',
{
'in': 'specOut',
'in2': 'SourceAlpha',
'operator': 'in',
'result': 'specOut'
}, embossFilter);
Blockly.utils.dom.createSvgElement('feComposite',
{
'in': 'SourceGraphic',
'in2': 'specOut',
'operator': 'arithmetic',
'k1': 0,
'k2': 1,
'k3': 1,
'k4': 0
}, embossFilter);
options.embossFilterId = embossFilter.id;
/*
<pattern id="blocklyDisabledPattern837493" patternUnits="userSpaceOnUse"
width="10" height="10">
<rect width="10" height="10" fill="#aaa" />
<path d="M 0 0 L 10 10 M 10 0 L 0 10" stroke="#cc0" />
</pattern>
*/
var disabledPattern = Blockly.utils.dom.createSvgElement('pattern',
{
'id': 'blocklyDisabledPattern' + rnd,
'patternUnits': 'userSpaceOnUse',
'width': 10,
'height': 10
}, defs);
Blockly.utils.dom.createSvgElement('rect',
{'width': 10, 'height': 10, 'fill': '#aaa'}, disabledPattern);
Blockly.utils.dom.createSvgElement('path',
{'d': 'M 0 0 L 10 10 M 10 0 L 0 10', 'stroke': '#cc0'}, disabledPattern);
options.disabledPatternId = disabledPattern.id;
options.gridPattern = Blockly.Grid.createDom(rnd, options.gridOptions, defs);
return svg;
@@ -206,7 +153,7 @@ Blockly.createDom_ = function(container, options) {
* for the blocks.
* @param {!Blockly.WorkspaceDragSurfaceSvg} workspaceDragSurface Drag surface
* SVG for the workspace.
* @return {!Blockly.Workspace} Newly created main workspace.
* @return {!Blockly.WorkspaceSvg} Newly created main workspace.
* @private
*/
Blockly.createMainWorkspace_ = function(svg, options, blockDragSurface,
@@ -214,28 +161,35 @@ Blockly.createMainWorkspace_ = function(svg, options, blockDragSurface,
options.parentWorkspace = null;
var mainWorkspace =
new Blockly.WorkspaceSvg(options, blockDragSurface, workspaceDragSurface);
mainWorkspace.scale = options.zoomOptions.startScale;
var wsOptions = mainWorkspace.options;
mainWorkspace.scale = wsOptions.zoomOptions.startScale;
svg.appendChild(mainWorkspace.createDom('blocklyMainBackground'));
if (!options.hasCategories && options.languageTree) {
// Set the theme name and renderer name onto the injection div.
Blockly.utils.dom.addClass(mainWorkspace.getInjectionDiv(),
(wsOptions.renderer || 'geras') + '-renderer');
Blockly.utils.dom.addClass(mainWorkspace.getInjectionDiv(),
mainWorkspace.getTheme().name + '-theme');
if (!wsOptions.hasCategories && wsOptions.languageTree) {
// Add flyout as an <svg> that is a sibling of the workspace svg.
var flyout = mainWorkspace.addFlyout_('svg');
var flyout = mainWorkspace.addFlyout('svg');
Blockly.utils.dom.insertAfter(flyout, svg);
}
if (options.hasTrashcan) {
if (wsOptions.hasTrashcan) {
mainWorkspace.addTrashcan();
}
if (options.zoomOptions && options.zoomOptions.controls) {
if (wsOptions.zoomOptions && wsOptions.zoomOptions.controls) {
mainWorkspace.addZoomControls();
}
// Register the workspace svg as a UI component.
mainWorkspace.getThemeManager().subscribe(svg, 'workspace', 'background-color');
mainWorkspace.getThemeManager().subscribe(svg, 'workspaceBackgroundColour',
'background-color');
// A null translation will also apply the correct initial scale.
mainWorkspace.translate(0, 0);
Blockly.mainWorkspace = mainWorkspace;
if (!options.readOnly && !mainWorkspace.isMovable()) {
if (!wsOptions.readOnly && !mainWorkspace.isMovable()) {
// Helper function for the workspaceChanged callback.
// TODO (#2300): Move metrics math back to the WorkspaceSvg.
var getWorkspaceMetrics = function() {
@@ -303,7 +257,9 @@ Blockly.createMainWorkspace_ = function(svg, options, blockDragSurface,
case Blockly.Events.BLOCK_CREATE:
case Blockly.Events.BLOCK_MOVE:
var object = mainWorkspace.getBlockById(e.blockId);
object = object.getRootBlock();
if (object) {
object = object.getRootBlock();
}
break;
case Blockly.Events.COMMENT_CREATE:
case Blockly.Events.COMMENT_MOVE:
@@ -361,7 +317,7 @@ Blockly.createMainWorkspace_ = function(svg, options, blockDragSurface,
object.moveBy(deltaX, deltaY);
}
if (e) {
if (!e.group) {
if (!e.group && object) {
console.log('WARNING: Moved object in bounds but there was no' +
' event group. This may break undo.');
}
@@ -385,7 +341,7 @@ Blockly.createMainWorkspace_ = function(svg, options, blockDragSurface,
/**
* Initialize Blockly with various handlers.
* @param {!Blockly.Workspace} mainWorkspace Newly created main workspace.
* @param {!Blockly.WorkspaceSvg} mainWorkspace Newly created main workspace.
* @private
*/
Blockly.init_ = function(mainWorkspace) {
@@ -393,7 +349,8 @@ Blockly.init_ = function(mainWorkspace) {
var svg = mainWorkspace.getParentSvg();
// Suppress the browser's context menu.
Blockly.bindEventWithChecks_(svg.parentNode, 'contextmenu', null,
Blockly.bindEventWithChecks_(
/** @type {!Element} */ (svg.parentNode), 'contextmenu', null,
function(e) {
if (!Blockly.utils.isTargetInput(e)) {
e.preventDefault();
@@ -411,13 +368,15 @@ Blockly.init_ = function(mainWorkspace) {
Blockly.inject.bindDocumentEvents_();
if (options.languageTree) {
if (mainWorkspace.toolbox_) {
mainWorkspace.toolbox_.init(mainWorkspace);
} else if (mainWorkspace.flyout_) {
var toolbox = mainWorkspace.getToolbox();
var flyout = mainWorkspace.getFlyout(true);
if (toolbox) {
toolbox.init();
} else if (flyout) {
// Build a fixed flyout with the root blocks.
mainWorkspace.flyout_.init(mainWorkspace);
mainWorkspace.flyout_.show(options.languageTree.childNodes);
mainWorkspace.flyout_.scrollToStart();
flyout.init(mainWorkspace);
flyout.show(options.languageTree.childNodes);
flyout.scrollToStart();
}
}
@@ -457,13 +416,13 @@ Blockly.inject.bindDocumentEvents_ = function() {
if (!Blockly.documentEventsBound_) {
Blockly.bindEventWithChecks_(document, 'scroll', null, function() {
var workspaces = Blockly.Workspace.getAll();
for (var i = 0, workspace; workspace = workspaces[i]; i++) {
for (var i = 0, workspace; (workspace = workspaces[i]); i++) {
if (workspace.updateInverseScreenCTM) {
workspace.updateInverseScreenCTM();
}
}
});
Blockly.bindEventWithChecks_(document, 'keydown', null, Blockly.onKeyDown_);
Blockly.bindEventWithChecks_(document, 'keydown', null, Blockly.onKeyDown);
// longStop needs to run to stop the context menu from showing up. It
// should run regardless of what other touch event handlers have run.
Blockly.bindEvent_(document, 'touchend', null, Blockly.longStop_);
@@ -473,7 +432,8 @@ Blockly.inject.bindDocumentEvents_ = function() {
Blockly.bindEventWithChecks_(window, 'orientationchange', document,
function() {
// TODO (#397): Fix for multiple Blockly workspaces.
Blockly.svgResize(Blockly.getMainWorkspace());
Blockly.svgResize(/** @type {!Blockly.WorkspaceSvg} */
(Blockly.getMainWorkspace()));
});
}
}

View File

@@ -144,7 +144,7 @@ Blockly.Input.prototype.insertFieldAt = function(index, field, opt_name) {
* @throws {Error} if the field is not present.
*/
Blockly.Input.prototype.removeField = function(name) {
for (var i = 0, field; field = this.fieldRow[i]; i++) {
for (var i = 0, field; (field = this.fieldRow[i]); i++) {
if (field.name === name) {
field.dispose();
this.fieldRow.splice(i, 1);
@@ -185,15 +185,15 @@ Blockly.Input.prototype.setVisible = function(visible) {
this.visible_ = visible;
var display = visible ? 'block' : 'none';
for (var y = 0, field; field = this.fieldRow[y]; y++) {
for (var y = 0, field; (field = this.fieldRow[y]); y++) {
field.setVisible(visible);
}
if (this.connection) {
// Has a connection.
if (visible) {
renderList = this.connection.unhideAll();
renderList = this.connection.startTrackingAll();
} else {
this.connection.hideAll();
this.connection.stopTrackingAll();
}
var child = this.connection.targetBlock();
if (child) {
@@ -211,7 +211,7 @@ Blockly.Input.prototype.setVisible = function(visible) {
* @package
*/
Blockly.Input.prototype.markDirty = function() {
for (var y = 0, field; field = this.fieldRow[y]; y++) {
for (var y = 0, field; (field = this.fieldRow[y]); y++) {
field.markDirty();
}
};
@@ -258,9 +258,10 @@ Blockly.Input.prototype.init = function() {
/**
* Sever all links to this input.
* @suppress {checkTypes}
*/
Blockly.Input.prototype.dispose = function() {
for (var i = 0, field; field = this.fieldRow[i]; i++) {
for (var i = 0, field; (field = this.fieldRow[i]); i++) {
field.dispose();
}
if (this.connection) {

View File

@@ -40,7 +40,7 @@ Blockly.InsertionMarkerManager = function(block) {
/**
* The top block in the stack being dragged.
* Does not change during a drag.
* @type {!Blockly.Block}
* @type {!Blockly.BlockSvg}
* @private
*/
this.topBlock_ = block;
@@ -146,27 +146,19 @@ Blockly.InsertionMarkerManager = function(block) {
* @package
*/
Blockly.InsertionMarkerManager.prototype.dispose = function() {
this.topBlock_ = null;
this.workspace_ = null;
this.availableConnections_.length = 0;
this.closestConnection_ = null;
this.localConnection_ = null;
Blockly.Events.disable();
try {
if (this.firstMarker_) {
this.firstMarker_.dispose();
this.firstMarker_ = null;
}
if (this.lastMarker_) {
this.lastMarker_.dispose();
this.lastMarker_ = null;
}
} finally {
Blockly.Events.enable();
}
this.highlightedBlock_ = null;
};
/**
@@ -255,14 +247,14 @@ Blockly.InsertionMarkerManager.prototype.createMarkerBlock_ = function(sourceBlo
Blockly.Events.disable();
try {
var result = this.workspace_.newBlock(imType);
result.setInsertionMarker(true, sourceBlock.width);
result.setCollapsed(sourceBlock.isCollapsed());
result.setInsertionMarker(true);
if (sourceBlock.mutationToDom) {
var oldMutationDom = sourceBlock.mutationToDom();
if (oldMutationDom) {
result.domToMutation(oldMutationDom);
}
}
result.setCollapsed(sourceBlock.isCollapsed());
// Copy field values from the other block. These values may impact the
// rendered size of the insertion marker. Note that we do not care about
// child blocks here.
@@ -301,7 +293,7 @@ Blockly.InsertionMarkerManager.prototype.initAvailableConnections_ = function()
if (lastOnStack && lastOnStack != this.topBlock_.nextConnection) {
available.push(lastOnStack);
this.lastOnStack_ = lastOnStack;
this.lastMarker_ = this.createMarkerBlock_(lastOnStack.sourceBlock_);
this.lastMarker_ = this.createMarkerBlock_(lastOnStack.getSourceBlock());
}
return available;
};
@@ -332,8 +324,8 @@ Blockly.InsertionMarkerManager.prototype.shouldUpdatePreviews_ = function(
this.localConnection_ == candidateLocal) {
return false;
}
var xDiff = this.localConnection_.x_ + dxy.x - this.closestConnection_.x_;
var yDiff = this.localConnection_.y_ + dxy.y - this.closestConnection_.y_;
var xDiff = this.localConnection_.x + dxy.x - this.closestConnection_.x;
var yDiff = this.localConnection_.y + dxy.y - this.closestConnection_.y;
var curDistance = Math.sqrt(xDiff * xDiff + yDiff * yDiff);
// Slightly prefer the existing preview over a new preview.
return !(candidateClosest && radius > curDistance -
@@ -360,6 +352,7 @@ Blockly.InsertionMarkerManager.prototype.shouldUpdatePreviews_ = function(
* in workspace units.
* @return {!Object} An object containing a local connection, a closest
* connection, and a radius.
* @private
*/
Blockly.InsertionMarkerManager.prototype.getCandidate_ = function(dxy) {
var radius = this.getStartRadius_();
@@ -415,9 +408,9 @@ Blockly.InsertionMarkerManager.prototype.shouldReplace_ = function() {
// Dragging a block over an existing block in an input.
if (local.type == Blockly.OUTPUT_VALUE) {
// Insert the dragged block into the stack if possible.
if (!closest.isConnected() ||
Blockly.Connection.lastConnectionInRow_(this.topBlock_,
closest.targetConnection.getSourceBlock())) {
if (closest &&
this.workspace_.getRenderer()
.shouldInsertDraggedBlock(this.topBlock_, closest)) {
return false; // Insert.
}
// Otherwise replace the existing block and bump it out.
@@ -426,7 +419,7 @@ Blockly.InsertionMarkerManager.prototype.shouldReplace_ = function() {
// Connecting to a statement input of c-block is an insertion, even if that
// c-block is terminal (e.g. forever).
if (local == local.sourceBlock_.getFirstStatementConnection()) {
if (local == local.getSourceBlock().getFirstStatementConnection()) {
return false; // Insert.
}
@@ -489,7 +482,7 @@ Blockly.InsertionMarkerManager.prototype.maybeShowPreview_ = function(candidate)
// Something went wrong and we're trying to connect to an invalid connection.
if (closest == this.closestConnection_ ||
closest.sourceBlock_.isInsertionMarker()) {
closest.getSourceBlock().isInsertionMarker()) {
console.log('Trying to connect to an insertion marker');
return;
}
@@ -511,7 +504,9 @@ Blockly.InsertionMarkerManager.prototype.showPreview_ = function() {
this.connectMarker_();
}
// Also highlight the actual connection, as a nod to previous behaviour.
if (this.closestConnection_) {
if (this.closestConnection_ && this.closestConnection_.targetBlock() &&
this.workspace_.getRenderer()
.shouldHighlightConnection(this.closestConnection_)) {
this.closestConnection_.highlight();
}
};
@@ -555,7 +550,9 @@ Blockly.InsertionMarkerManager.prototype.maybeHidePreview_ = function(candidate)
* @private
*/
Blockly.InsertionMarkerManager.prototype.hidePreview_ = function() {
if (this.closestConnection_) {
if (this.closestConnection_ && this.closestConnection_.targetBlock() &&
this.workspace_.getRenderer()
.shouldHighlightConnection(this.closestConnection_)) {
this.closestConnection_.unhighlight();
}
if (this.highlightingBlock_) {
@@ -567,6 +564,7 @@ Blockly.InsertionMarkerManager.prototype.hidePreview_ = function() {
/**
* Add highlighting showing which block will be replaced.
* @private
*/
Blockly.InsertionMarkerManager.prototype.highlightBlock_ = function() {
var closest = this.closestConnection_;
@@ -575,15 +573,15 @@ Blockly.InsertionMarkerManager.prototype.highlightBlock_ = function() {
this.highlightedBlock_ = closest.targetBlock();
closest.targetBlock().highlightForReplacement(true);
} else if (local.type == Blockly.OUTPUT_VALUE) {
this.highlightedBlock_ = closest.sourceBlock_;
// TODO: remove?
closest.sourceBlock_.highlightShapeForInput(closest, true);
this.highlightedBlock_ = closest.getSourceBlock();
closest.getSourceBlock().highlightShapeForInput(closest, true);
}
this.highlightingBlock_ = true;
};
/**
* Get rid of the highlighting marking the block that will be replaced.
* @private
*/
Blockly.InsertionMarkerManager.prototype.unhighlightBlock_ = function() {
var closest = this.closestConnection_;
@@ -610,7 +608,7 @@ Blockly.InsertionMarkerManager.prototype.disconnectMarker_ = function() {
}
var imConn = this.markerConnection_;
var imBlock = imConn.sourceBlock_;
var imBlock = imConn.getSourceBlock();
var markerNext = imBlock.nextConnection;
var markerPrev = imBlock.previousConnection;
var markerOutput = imBlock.outputConnection;
@@ -628,7 +626,7 @@ Blockly.InsertionMarkerManager.prototype.disconnectMarker_ = function() {
// Inside of a C-block, first statement connection.
else if (imConn.type == Blockly.NEXT_STATEMENT && imConn != markerNext) {
var innerConnection = imConn.targetConnection;
innerConnection.sourceBlock_.unplug(false);
innerConnection.getSourceBlock().unplug(false);
var previousBlockNextConnection =
markerPrev ? markerPrev.targetConnection : null;
@@ -660,7 +658,7 @@ Blockly.InsertionMarkerManager.prototype.connectMarker_ = function() {
var isLastInStack = this.lastOnStack_ && local == this.lastOnStack_;
var imBlock = isLastInStack ? this.lastMarker_ : this.firstMarker_;
var imConn = imBlock.getMatchingConnection(local.sourceBlock_, local);
var imConn = imBlock.getMatchingConnection(local.getSourceBlock(), local);
if (imConn == this.markerConnection_) {
throw Error('Made it to connectMarker_ even though the marker isn\'t ' +
@@ -673,11 +671,15 @@ Blockly.InsertionMarkerManager.prototype.connectMarker_ = function() {
imBlock.rendered = true;
imBlock.getSvgRoot().setAttribute('visibility', 'visible');
// Position based on the calculated connection locations.
imBlock.positionNewBlock(imBlock, imConn, closest);
if (imConn && closest) {
// Position so that the existing block doesn't move.
imBlock.positionNearConnection(imConn, closest);
}
if (closest) {
// Connect() also renders the insertion marker.
imConn.connect(closest);
}
// Connect() also renders the insertion marker.
imConn.connect(closest);
this.markerConnection_ = imConn;
};

View File

@@ -23,6 +23,8 @@
goog.provide('Blockly.ASTNode');
goog.require('Blockly.utils.Coordinate');
/**
* Class for an AST node.
@@ -30,7 +32,7 @@ goog.provide('Blockly.ASTNode');
* creating a node directly.
* @param {string} type The type of the location.
* Must be in Bockly.ASTNode.types.
* @param {Blockly.Block|Blockly.Connection|Blockly.Field|Blockly.Workspace}
* @param {!(Blockly.Block|Blockly.Connection|Blockly.Field|Blockly.Workspace)}
* location The position in the AST.
* @param {!Object=} opt_params Optional dictionary of options.
* @constructor
@@ -80,6 +82,12 @@ Blockly.ASTNode.types = {
WORKSPACE: 'workspace'
};
/**
* True to navigate to all fields. False to only navigate to clickable fields.
* @type {boolean}
*/
Blockly.ASTNode.NAVIGATE_ALL_FIELDS = false;
/**
* The default y offset to use when moving the cursor from a stack to the
* workspace.
@@ -107,10 +115,13 @@ Blockly.ASTNode.isConnectionType_ = function(type) {
/**
* Create an AST node pointing to a field.
* @param {!Blockly.Field} field The location of the AST node.
* @return {!Blockly.ASTNode} An AST node pointing to a field.
* @param {Blockly.Field} field The location of the AST node.
* @return {Blockly.ASTNode} An AST node pointing to a field.
*/
Blockly.ASTNode.createFieldNode = function(field) {
if (!field) {
return null;
}
return new Blockly.ASTNode(Blockly.ASTNode.types.FIELD, field);
};
@@ -144,10 +155,10 @@ Blockly.ASTNode.createConnectionNode = function(connection) {
* Creates an AST node pointing to an input. Stores the input connection as the
* location.
* @param {Blockly.Input} input The input used to create an AST node.
* @return {!Blockly.ASTNode} An AST node pointing to a input.
* @return {Blockly.ASTNode} An AST node pointing to a input.
*/
Blockly.ASTNode.createInputNode = function(input) {
if (!input) {
if (!input || !input.connection) {
return null;
}
return new Blockly.ASTNode(Blockly.ASTNode.types.INPUT, input.connection);
@@ -155,22 +166,28 @@ Blockly.ASTNode.createInputNode = function(input) {
/**
* Creates an AST node pointing to a block.
* @param {!Blockly.Block} block The block used to create an AST node.
* @return {!Blockly.ASTNode} An AST node pointing to a block.
* @param {Blockly.Block} block The block used to create an AST node.
* @return {Blockly.ASTNode} An AST node pointing to a block.
*/
Blockly.ASTNode.createBlockNode = function(block) {
if (!block) {
return null;
}
return new Blockly.ASTNode(Blockly.ASTNode.types.BLOCK, block);
};
/**
* Create an AST node of type stack. A stack, represented by its top block, is
* the set of all blocks connected to a top block, including the top block.
* @param {!Blockly.Block} topBlock A top block has no parent and can be found
* @param {Blockly.Block} topBlock A top block has no parent and can be found
* in the list returned by workspace.getTopBlocks().
* @return {!Blockly.ASTNode} An AST node of type stack that points to the top
* @return {Blockly.ASTNode} An AST node of type stack that points to the top
* block on the stack.
*/
Blockly.ASTNode.createStackNode = function(topBlock) {
if (!topBlock) {
return null;
}
return new Blockly.ASTNode(Blockly.ASTNode.types.STACK, topBlock);
};
@@ -179,10 +196,13 @@ Blockly.ASTNode.createStackNode = function(topBlock) {
* @param {!Blockly.Workspace} workspace The workspace that we are on.
* @param {Blockly.utils.Coordinate} wsCoordinate The position on the workspace
* for this node.
* @return {!Blockly.ASTNode} An AST node pointing to a workspace and a position
* @return {Blockly.ASTNode} An AST node pointing to a workspace and a position
* on the workspace.
*/
Blockly.ASTNode.createWorkspaceNode = function(workspace, wsCoordinate) {
if (!wsCoordinate || !workspace) {
return null;
}
var params = {
wsCoordinate: wsCoordinate
};
@@ -242,33 +262,6 @@ Blockly.ASTNode.prototype.isConnection = function() {
return this.isConnection_;
};
/**
* Get either the previous editable field, or get the first editable field for
* the given input.
* @param {!(Blockly.Field|Blockly.Connection)} location The current location of
* the cursor, which must be a field or connection.
* @param {!Blockly.Input} parentInput The parentInput of the field.
* @param {boolean=} opt_last If true find the last editable field otherwise get
* the previous field.
* @return {Blockly.ASTNode} The AST node holding the previous or last field or
* null if no previous field exists.
* @private
*/
Blockly.ASTNode.prototype.findPreviousEditableField_ = function(location,
parentInput, opt_last) {
var fieldRow = parentInput.fieldRow;
var fieldIdx = fieldRow.indexOf(location);
var previousField = null;
var startIdx = (opt_last ? fieldRow.length : fieldIdx) - 1;
for (var i = startIdx, field; field = fieldRow[i]; i--) {
if (field.EDITABLE) {
previousField = field;
return Blockly.ASTNode.createFieldNode(previousField);
}
}
return null;
};
/**
* Given an input find the next editable field or an input with a non null
* connection in the same block. The current location must be an input
@@ -282,10 +275,10 @@ Blockly.ASTNode.prototype.findNextForInput_ = function() {
var parentInput = this.location_.getParentInput();
var block = parentInput.getSourceBlock();
var curIdx = block.inputList.indexOf(parentInput);
for (var i = curIdx + 1, input; input = block.inputList[i]; i++) {
for (var i = curIdx + 1, input; (input = block.inputList[i]); i++) {
var fieldRow = input.fieldRow;
for (var j = 0, field; field = fieldRow[j]; j++) {
if (field.EDITABLE) {
for (var j = 0, field; (field = fieldRow[j]); j++) {
if (field.isClickable() || Blockly.ASTNode.NAVIGATE_ALL_FIELDS) {
return Blockly.ASTNode.createFieldNode(field);
}
}
@@ -305,15 +298,15 @@ Blockly.ASTNode.prototype.findNextForInput_ = function() {
* @private
*/
Blockly.ASTNode.prototype.findNextForField_ = function() {
var location = this.location_;
var location = /** @type {!Blockly.Field} */ (this.location_);
var input = location.getParentInput();
var block = location.getSourceBlock();
var curIdx = block.inputList.indexOf(input);
var curIdx = block.inputList.indexOf(/** @type {!Blockly.Input} */ (input));
var fieldIdx = input.fieldRow.indexOf(location) + 1;
for (var i = curIdx, newInput; newInput = block.inputList[i]; i++) {
for (var i = curIdx, newInput; (newInput = block.inputList[i]); i++) {
var fieldRow = newInput.fieldRow;
while (fieldIdx < fieldRow.length) {
if (fieldRow[fieldIdx].EDITABLE) {
if (fieldRow[fieldIdx].isClickable() || Blockly.ASTNode.NAVIGATE_ALL_FIELDS) {
return Blockly.ASTNode.createFieldNode(fieldRow[fieldIdx]);
}
fieldIdx++;
@@ -338,13 +331,13 @@ Blockly.ASTNode.prototype.findPrevForInput_ = function() {
var location = this.location_.getParentInput();
var block = location.getSourceBlock();
var curIdx = block.inputList.indexOf(location);
for (var i = curIdx, input; input = block.inputList[i]; i--) {
for (var i = curIdx, input; (input = block.inputList[i]); i--) {
if (input.connection && input !== location) {
return Blockly.ASTNode.createInputNode(input);
}
var fieldRow = input.fieldRow;
for (var j = fieldRow.length - 1, field; field = fieldRow[j]; j--) {
if (field.EDITABLE) {
for (var j = fieldRow.length - 1, field; (field = fieldRow[j]); j--) {
if (field.isClickable() || Blockly.ASTNode.NAVIGATE_ALL_FIELDS) {
return Blockly.ASTNode.createFieldNode(field);
}
}
@@ -359,18 +352,19 @@ Blockly.ASTNode.prototype.findPrevForInput_ = function() {
* @private
*/
Blockly.ASTNode.prototype.findPrevForField_ = function() {
var location = this.location_;
var location = /** @type {!Blockly.Field} */ (this.location_);
var parentInput = location.getParentInput();
var block = location.getSourceBlock();
var curIdx = block.inputList.indexOf(parentInput);
var curIdx = block.inputList.indexOf(
/** @type {!Blockly.Input} */ (parentInput));
var fieldIdx = parentInput.fieldRow.indexOf(location) - 1;
for (var i = curIdx, input; input = block.inputList[i]; i--) {
for (var i = curIdx, input; (input = block.inputList[i]); i--) {
if (input.connection && input !== parentInput) {
return Blockly.ASTNode.createInputNode(input);
}
var fieldRow = input.fieldRow;
while (fieldIdx > -1) {
if (fieldRow[fieldIdx].EDITABLE) {
if (fieldRow[fieldIdx].isClickable() || Blockly.ASTNode.NAVIGATE_ALL_FIELDS) {
return Blockly.ASTNode.createFieldNode(fieldRow[fieldIdx]);
}
fieldIdx--;
@@ -400,7 +394,7 @@ Blockly.ASTNode.prototype.navigateBetweenStacks_ = function(forward) {
}
var curRoot = curLocation.getRootBlock();
var topBlocks = curRoot.workspace.getTopBlocks(true);
for (var i = 0, topBlock; topBlock = topBlocks[i]; i++) {
for (var i = 0, topBlock; (topBlock = topBlocks[i]); i++) {
if (curRoot.id == topBlock.id) {
var offset = forward ? 1 : -1;
var resultIndex = i + offset;
@@ -425,9 +419,11 @@ Blockly.ASTNode.prototype.navigateBetweenStacks_ = function(forward) {
Blockly.ASTNode.prototype.findTopASTNodeForBlock_ = function(block) {
var topConnection = block.previousConnection || block.outputConnection;
if (topConnection) {
return Blockly.ASTNode.createConnectionNode(topConnection);
return /** @type {!Blockly.ASTNode} */ (Blockly.ASTNode.createConnectionNode(
topConnection));
} else {
return Blockly.ASTNode.createBlockNode(block);
return /** @type {!Blockly.ASTNode} */ (Blockly.ASTNode.createBlockNode(
block));
}
};
@@ -469,10 +465,10 @@ Blockly.ASTNode.prototype.getOutAstNodeForBlock_ = function(block) {
*/
Blockly.ASTNode.prototype.findFirstFieldOrInput_ = function(block) {
var inputs = block.inputList;
for (var i = 0, input; input = inputs[i]; i++) {
for (var i = 0, input; (input = inputs[i]); i++) {
var fieldRow = input.fieldRow;
for (var j = 0, field; field = fieldRow[j]; j++) {
if (field.EDITABLE) {
for (var j = 0, field; (field = fieldRow[j]); j++) {
if (field.isClickable() || Blockly.ASTNode.NAVIGATE_ALL_FIELDS) {
return Blockly.ASTNode.createFieldNode(field);
}
}
@@ -483,6 +479,23 @@ Blockly.ASTNode.prototype.findFirstFieldOrInput_ = function(block) {
return null;
};
/**
* Finds the source block of the location of this node.
* @return {Blockly.Block} The source block of the location, or null if the node
* is of type workspace.
*/
Blockly.ASTNode.prototype.getSourceBlock = function() {
if (this.getType() === Blockly.ASTNode.types.BLOCK) {
return /** @type {Blockly.Block} */ (this.getLocation());
} else if (this.getType() === Blockly.ASTNode.types.STACK) {
return /** @type {Blockly.Block} */ (this.getLocation());
} else if (this.getType() === Blockly.ASTNode.types.WORKSPACE) {
return null;
} else {
return this.getLocation().getSourceBlock();
}
};
/**
* Find the element to the right of the current element in the AST.
* @return {Blockly.ASTNode} An AST node that wraps the next field, connection,
@@ -504,20 +517,14 @@ Blockly.ASTNode.prototype.next = function() {
case Blockly.ASTNode.types.BLOCK:
var nextConnection = this.location_.nextConnection;
if (nextConnection) {
return Blockly.ASTNode.createConnectionNode(nextConnection);
}
break;
return Blockly.ASTNode.createConnectionNode(nextConnection);
case Blockly.ASTNode.types.PREVIOUS:
return Blockly.ASTNode.createBlockNode(this.location_.getSourceBlock());
case Blockly.ASTNode.types.NEXT:
var targetConnection = this.location_.targetConnection;
if (targetConnection) {
return Blockly.ASTNode.createConnectionNode(targetConnection);
}
break;
return Blockly.ASTNode.createConnectionNode(targetConnection);
}
return null;
@@ -544,14 +551,11 @@ Blockly.ASTNode.prototype.in = function() {
case Blockly.ASTNode.types.BLOCK:
var block = /** @type {!Blockly.Block} */ (this.location_);
return this.findFirstFieldOrInput_(this.location_);
return this.findFirstFieldOrInput_(block);
case Blockly.ASTNode.types.INPUT:
var targetConnection = this.location_.targetConnection;
if (targetConnection) {
return Blockly.ASTNode.createConnectionNode(targetConnection);
}
break;
return Blockly.ASTNode.createConnectionNode(targetConnection);
}
return null;
@@ -578,13 +582,9 @@ Blockly.ASTNode.prototype.prev = function() {
return this.findPrevForInput_();
case Blockly.ASTNode.types.BLOCK:
var prevConnection = this.location_.previousConnection;
var outputConnection = this.location_.outputConnection;
var topConnection = prevConnection || outputConnection;
if (topConnection) {
return Blockly.ASTNode.createConnectionNode(topConnection);
}
break;
var block = this.location_;
var topConnection = block.previousConnection || block.outputConnection;
return Blockly.ASTNode.createConnectionNode(topConnection);
case Blockly.ASTNode.types.PREVIOUS:
var targetConnection = this.location_.targetConnection;

View File

@@ -22,6 +22,11 @@
*/
'use strict';
goog.provide('Blockly.BasicCursor');
goog.require('Blockly.ASTNode');
goog.require('Blockly.Cursor');
/**
* Class for a basic cursor.
@@ -35,12 +40,131 @@ Blockly.BasicCursor = function() {
};
Blockly.utils.object.inherits(Blockly.BasicCursor, Blockly.Cursor);
/**
* Find the next node in the pre order traversal.
* @return {Blockly.ASTNode} The next node, or null if the current node is
* not set or there is no next value.
* @override
*/
Blockly.BasicCursor.prototype.next = function() {
var curNode = this.getCurNode();
if (!curNode) {
return null;
}
var newNode = this.getNextNode_(curNode, this.validNode_);
if (newNode) {
this.setCurNode(newNode);
}
return newNode;
};
/**
* For a basic cursor we only have the ability to go next and previous, so
* in will also allow the user to get to the next node in the pre order traversal.
* @return {Blockly.ASTNode} The next node, or null if the current node is
* not set or there is no next value.
* @override
*/
Blockly.BasicCursor.prototype.in = function() {
return this.next();
};
/**
* Find the previous node in the pre order traversal.
* @return {Blockly.ASTNode} The previous node, or null if the current node
* is not set or there is no previous value.
* @override
*/
Blockly.BasicCursor.prototype.prev = function() {
var curNode = this.getCurNode();
if (!curNode) {
return null;
}
var newNode = this.getPreviousNode_(curNode, this.validNode_);
if (newNode) {
this.setCurNode(newNode);
}
return newNode;
};
/**
* For a basic cursor we only have the ability to go next and previou, so
* out will allow the user to get to the previous node in the pre order traversal.
* @return {Blockly.ASTNode} The previous node, or null if the current node is
* not set or there is no previous value.
* @override
*/
Blockly.BasicCursor.prototype.out = function() {
return this.prev();
};
/**
* Uses pre order traversal to navigate the Blockly AST. This will allow
* a user to easily navigate the entire Blockly AST without having to go in
* and out levels on the tree.
* @param {Blockly.ASTNode} node The current position in the AST.
* @param {!function(Blockly.ASTNode) : boolean} isValid A function true/false
* depending on whether the given node should be traversed.
* @return {Blockly.ASTNode} The next node in the traversal.
* @protected
*/
Blockly.BasicCursor.prototype.getNextNode_ = function(node, isValid) {
if (!node) {
return null;
}
var newNode = node.in() || node.next();
if (isValid(newNode)) {
return newNode;
} else if (newNode) {
return this.getNextNode_(newNode, isValid);
}
var siblingOrParent = this.findSiblingOrParent_(node.out());
if (isValid(siblingOrParent)) {
return siblingOrParent;
} else if (siblingOrParent) {
return this.getNextNode_(siblingOrParent, isValid);
}
return null;
};
/**
* Reverses the pre order traversal in order to find the previous node. This will
* allow a user to easily navigate the entire Blockly AST without having to go in
* and out levels on the tree.
* @param {Blockly.ASTNode} node The current position in the AST.
* @param {!function(Blockly.ASTNode) : boolean} isValid A function true/false
* depending on whether the given node should be traversed.
* @return {Blockly.ASTNode} The previous node in the traversal or null if no
* previous node exists.
* @protected
*/
Blockly.BasicCursor.prototype.getPreviousNode_ = function(node, isValid) {
if (!node) {
return null;
}
var newNode = node.prev();
if (newNode) {
newNode = this.getRightMostChild_(newNode);
} else {
newNode = node.out();
}
if (isValid(newNode)) {
return newNode;
} else if (newNode) {
return this.getPreviousNode_(newNode, isValid);
}
return null;
};
/**
* Decides what nodes to traverse and which ones to skip. Currently, it
* skips output, stack and workspace nodes.
* @param {Blockly.ASTNode} node The AST node to check whether it is valid.
* @return {boolean} True if the node should be visited, false otherwise.
* @private
* @protected
*/
Blockly.BasicCursor.prototype.validNode_ = function(node) {
var isValid = false;
@@ -74,32 +198,6 @@ Blockly.BasicCursor.prototype.findSiblingOrParent_ = function(node) {
return this.findSiblingOrParent_(node.out());
};
/**
* Uses pre order traversal to navigate the Blockly AST. This will allow
* a user to easily navigate the entire Blockly AST without having to go in
* and out levels on the tree.
* @param {Blockly.ASTNode} node The current position in the AST.
* @return {Blockly.ASTNode} The next node in the traversal.
* @private
*/
Blockly.BasicCursor.prototype.getNextNode_ = function(node) {
if (!node) {
return null;
}
var newNode = node.in() || node.next();
if (this.validNode_(newNode)) {
return newNode;
} else if (newNode) {
return this.getNextNode_(newNode);
}
var siblingOrParent = this.findSiblingOrParent_(node.out());
if (this.validNode_(siblingOrParent)) {
return siblingOrParent;
} else if (siblingOrParent) {
return this.getNextNode_(siblingOrParent);
}
return null;
};
/**
* Get the right most child of a node.
@@ -119,91 +217,3 @@ Blockly.BasicCursor.prototype.getRightMostChild_ = function(node) {
return this.getRightMostChild_(newNode);
};
/**
* Reverses the pre order traversal in order to find the previous node. This will
* allow a user to easily navigate the entire Blockly AST without having to go in
* and out levels on the tree.
* @param {Blockly.ASTNode} node The current position in the AST.
* @return {Blockly.ASTNode} The previous node in the traversal or null if no
* previous node exists.
* @private
*/
Blockly.BasicCursor.prototype.getPreviousNode_ = function(node) {
if (!node) {
return null;
}
var newNode = node.prev();
if (newNode) {
newNode = this.getRightMostChild_(newNode);
} else {
newNode = node.out();
}
if (this.validNode_(newNode)) {
return newNode;
} else if (newNode) {
return this.getPreviousNode_(newNode);
}
return null;
};
/**
* Find the next node in the pre order traversal.
* @return {Blockly.ASTNode} The next node, or null if the current node is
* not set or there is no next value.
* @override
*/
Blockly.BasicCursor.prototype.next = function() {
var curNode = this.getCurNode();
if (!curNode) {
return null;
}
var newNode = this.getNextNode_(curNode);
if (newNode) {
this.setCurNode(newNode);
}
return newNode;
};
/**
* For a basic cursor we only have the ability to go next and previous, so
* in will also allow the user to get to the next node in the pre order traversal.
* @return {Blockly.ASTNode} The next node, or null if the current node is
* not set or there is no next value.
* @override
*/
Blockly.BasicCursor.prototype.in = function() {
return this.next();
};
/**
* Find the previous node in the pre order traversal.
* @return {Blockly.ASTNode} The previous node, or null if the current node
* is not set or there is no previous value.
* @override
*/
Blockly.BasicCursor.prototype.prev = function() {
var curNode = this.getCurNode();
if (!curNode) {
return null;
}
var newNode = this.getPreviousNode_(curNode);
if (newNode) {
this.setCurNode(newNode);
}
return newNode;
};
/**
* For a basic cursor we only have the ability to go next and previou, so
* out will allow the user to get to the previous node in the pre order traversal.
* @return {Blockly.ASTNode} The previous node, or null if the current node is
* not set or there is no previous value.
* @override
*/
Blockly.BasicCursor.prototype.out = function() {
return this.prev();
};

View File

@@ -24,79 +24,34 @@
goog.provide('Blockly.Cursor');
goog.require('Blockly.Action');
goog.require('Blockly.ASTNode');
goog.require('Blockly.Marker');
goog.require('Blockly.navigation');
goog.require('Blockly.utils.object');
/**
* Class for a cursor.
* A cursor controls how a user navigates the Blockly AST.
* @constructor
* @extends {Blockly.Marker}
*/
Blockly.Cursor = function() {
/*
* The current location of the cursor.
* @type {Blockly.ASTNode}
* @private
*/
this.curNode_ = null;
Blockly.Cursor.superClass_.constructor.call(this);
/**
* The object in charge of drawing the visual representation of the current node.
* @type {Blockly.CursorSvg}
* @private
* @override
*/
this.drawer_ = null;
};
/**
* Sets the object in charge of drawing the cursor.
* @param {Blockly.CursorSvg} drawer The object in charge of drawing the cursor.
*/
Blockly.Cursor.prototype.setDrawer = function(drawer) {
this.drawer_ = drawer;
};
/**
* Get the current drawer for the cursor.
* @return {Blockly.CursorSvg} The object in charge of drawing the cursor.
*/
Blockly.Cursor.prototype.getDrawer = function() {
return this.drawer_;
};
/**
* Gets the current location of the cursor.
* @return {Blockly.ASTNode} The current field, connection, or block the cursor
* is on.
*/
Blockly.Cursor.prototype.getCurNode = function() {
return this.curNode_;
};
/**
* Set the location of the cursor and call the update method.
* Setting isStack to true will only work if the newLocation is the top most
* output or previous connection on a stack.
* @param {Blockly.ASTNode} newNode The new location of the cursor.
*/
Blockly.Cursor.prototype.setCurNode = function(newNode) {
this.curNode_ = newNode;
if (this.drawer_) {
this.drawer_.draw(this.getCurNode());
}
};
/**
* Hide the cursor SVG.
*/
Blockly.Cursor.prototype.hide = function() {
if (this.drawer_) {
this.drawer_.hide();
}
this.type = 'cursor';
};
Blockly.utils.object.inherits(Blockly.Cursor, Blockly.Marker);
/**
* Find the next connection, field, or block.
* @return {Blockly.ASTNode} The next element, or null if the current node is
* not set or there is no next value.
* @protected
*/
Blockly.Cursor.prototype.next = function() {
var curNode = this.getCurNode();
@@ -121,6 +76,7 @@ Blockly.Cursor.prototype.next = function() {
* Find the in connection or field.
* @return {Blockly.ASTNode} The in element, or null if the current node is
* not set or there is no in value.
* @protected
*/
Blockly.Cursor.prototype.in = function() {
var curNode = this.getCurNode();
@@ -145,6 +101,7 @@ Blockly.Cursor.prototype.in = function() {
* Find the previous connection, field, or block.
* @return {Blockly.ASTNode} The previous element, or null if the current node
* is not set or there is no previous value.
* @protected
*/
Blockly.Cursor.prototype.prev = function() {
var curNode = this.getCurNode();
@@ -169,6 +126,7 @@ Blockly.Cursor.prototype.prev = function() {
* Find the out connection, field, or block.
* @return {Blockly.ASTNode} The out element, or null if the current node is
* not set or there is no out value.
* @protected
*/
Blockly.Cursor.prototype.out = function() {
var curNode = this.getCurNode();
@@ -186,3 +144,34 @@ Blockly.Cursor.prototype.out = function() {
}
return newNode;
};
/**
* Handles the given action.
* This is only triggered when keyboard navigation is enabled.
* @param {!Blockly.Action} action The action to be handled.
* @return {boolean} True if the action has been handled, false otherwise.
*/
Blockly.Cursor.prototype.onBlocklyAction = function(action) {
// If we are on a field give it the option to handle the action
if (this.getCurNode() &&
this.getCurNode().getType() === Blockly.ASTNode.types.FIELD &&
this.getCurNode().getLocation().onBlocklyAction(action)) {
return true;
}
switch (action.name) {
case Blockly.navigation.actionNames.PREVIOUS:
this.prev();
return true;
case Blockly.navigation.actionNames.OUT:
this.out();
return true;
case Blockly.navigation.actionNames.NEXT:
this.next();
return true;
case Blockly.navigation.actionNames.IN:
this.in();
return true;
default:
return false;
}
};

View File

@@ -1,634 +0,0 @@
/**
* @license
* Copyright 2019 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @fileoverview Methods for graphically rendering a cursor as SVG.
* @author samelh@microsoft.com (Sam El-Husseini)
*/
'use strict';
goog.provide('Blockly.CursorSvg');
goog.require('Blockly.Cursor');
goog.require('Blockly.utils.object');
/**
* Class for a cursor.
* @param {!Blockly.WorkspaceSvg} workspace The workspace the cursor belongs to.
* @param {boolean=} opt_marker True if the cursor is a marker. A marker is used
* to save a location and is an immovable cursor. False or undefined if the
* cursor is not a marker.
* @constructor
*/
Blockly.CursorSvg = function(workspace, opt_marker) {
/**
* The workspace the cursor belongs to.
* @type {!Blockly.WorkspaceSvg}
* @private
*/
this.workspace_ = workspace;
/**
* True if the cursor should be drawn as a marker, false otherwise.
* A marker is drawn as a solid blue line, while the cursor is drawns as a
* flashing red one.
* @type {boolean|undefined}
* @private
*/
this.isMarker_ = opt_marker;
/**
* The workspace, field, or block that the cursor SVG element should be
* attached to.
* @type {Blockly.WorkspaceSvg|Blockly.Field|Blockly.BlockSvg}
* @private
*/
this.parent_ = null;
/**
* The constants necessary to draw the cursor.
* @type {Blockly.blockRendering.ConstantProvider}
* @private
*/
this.constants_ = workspace.getRenderer().getConstants();
};
/**
* Height of the horizontal cursor.
* @type {number}
* @const
*/
Blockly.CursorSvg.CURSOR_HEIGHT = 5;
/**
* Width of the horizontal cursor.
* @type {number}
* @const
*/
Blockly.CursorSvg.CURSOR_WIDTH = 100;
/**
* The start length of the notch.
* @type {number}
* @const
*/
Blockly.CursorSvg.NOTCH_START_LENGTH = 24;
/**
* Padding around the input.
* @type {number}
* @const
*/
Blockly.CursorSvg.VERTICAL_PADDING = 5;
/**
* Padding around a stack.
* @type {number}
* @const
*/
Blockly.CursorSvg.STACK_PADDING = 10;
/**
* Padding around a block.
* @type {number}
* @const
*/
Blockly.CursorSvg.BLOCK_PADDING = 2;
/**
* What we multiply the height by to get the height of the cursor.
* Only used for the block and block connections.
* @type {number}
* @const
*/
Blockly.CursorSvg.HEIGHT_MULTIPLIER = 3 / 4;
/**
* Cursor color.
* @type {string}
* @const
*/
Blockly.CursorSvg.CURSOR_COLOR = '#cc0a0a';
/**
* Immovable marker color.
* @type {string}
* @const
*/
Blockly.CursorSvg.MARKER_COLOR = '#4286f4';
/**
* The name of the CSS class for a cursor.
* @const {string}
*/
Blockly.CursorSvg.CURSOR_CLASS = 'blocklyCursor';
/**
* The name of the CSS class for a marker.
* @const {string}
*/
Blockly.CursorSvg.MARKER_CLASS = 'blocklyMarker';
/**
* The current SVG element for the cursor.
* @type {Element}
*/
Blockly.CursorSvg.prototype.currentCursorSvg = null;
/**
* Return the root node of the SVG or null if none exists.
* @return {SVGElement} The root SVG node.
*/
Blockly.CursorSvg.prototype.getSvgRoot = function() {
return this.svgGroup_;
};
/**
* Create the DOM element for the cursor.
* @return {!SVGElement} The cursor controls SVG group.
* @package
*/
Blockly.CursorSvg.prototype.createDom = function() {
var className = this.isMarker_ ?
Blockly.CursorSvg.MARKER_CLASS : Blockly.CursorSvg.CURSOR_CLASS;
this.svgGroup_ =
Blockly.utils.dom.createSvgElement('g', {
'class': className
}, null);
this.createCursorSvg_();
return this.svgGroup_;
};
/**
* Attaches the SVG root of the cursor to the SVG group of the parent.
* @param {!Blockly.WorkspaceSvg|!Blockly.Field|!Blockly.BlockSvg} newParent
* The workspace, field, or block that the cursor SVG element should be
* attached to.
* @private
*/
Blockly.CursorSvg.prototype.setParent_ = function(newParent) {
if (this.isMarker_) {
if (this.parent_) {
this.parent_.setMarkerSvg(null);
}
newParent.setMarkerSvg(this.getSvgRoot());
} else {
if (this.parent_) {
this.parent_.setCursorSvg(null);
}
newParent.setCursorSvg(this.getSvgRoot());
}
this.parent_ = newParent;
};
/**************************
* Display
**************************/
/**
* Show the cursor as a combination of the previous connection and block,
* the output connection and block, or just the block.
* @param {Blockly.BlockSvg} block The block the cursor is currently on.
* @private
*/
Blockly.CursorSvg.prototype.showWithBlockPrevOutput_ = function(block) {
if (!block) {
return;
}
var width = block.width;
var height = block.height;
var cursorHeight = height * Blockly.CursorSvg.HEIGHT_MULTIPLIER;
var cursorOffset = Blockly.CursorSvg.BLOCK_PADDING;
if (block.previousConnection) {
this.positionPrevious_(width, cursorOffset, cursorHeight);
} else if (block.outputConnection) {
this.positionOutput_(width, height);
} else {
this.positionBlock_(width, cursorOffset, cursorHeight);
}
this.setParent_(block);
this.showCurrent_();
};
/**
* Show the visual representation of a workspace coordinate.
* This is a horizontal line.
* @param {!Blockly.ASTNode} curNode The node that we want to draw the cursor for.
* @private
*/
Blockly.CursorSvg.prototype.showWithCoordinates_ = function(curNode) {
var wsCoordinate = curNode.getWsCoordinate();
var x = wsCoordinate.x;
var y = wsCoordinate.y;
if (this.workspace_.RTL) {
x -= Blockly.CursorSvg.CURSOR_WIDTH;
}
this.positionLine_(x, y, Blockly.CursorSvg.CURSOR_WIDTH);
this.setParent_(this.workspace_);
this.showCurrent_();
};
/**
* Show the visual representation of a field.
* This is a box around the field.
* @param {!Blockly.ASTNode} curNode The node that we want to draw the cursor for.
* @private
*/
Blockly.CursorSvg.prototype.showWithField_ = function(curNode) {
var field = /** @type {Blockly.Field} */ (curNode.getLocation());
var width = field.getSize().width;
var height = field.getSize().height;
this.positionRect_(0, 0, width, height);
this.setParent_(field);
this.showCurrent_();
};
/**
* Show the visual representation of an input.
* This is a puzzle piece.
* @param {!Blockly.ASTNode} curNode The node that we want to draw the cursor for.
* @private
*/
Blockly.CursorSvg.prototype.showWithInput_ = function(curNode) {
var connection = /** @type {Blockly.Connection} */
(curNode.getLocation());
var sourceBlock = /** @type {Blockly.BlockSvg} */ (connection.getSourceBlock());
this.positionInput_(connection);
this.setParent_(sourceBlock);
this.showCurrent_();
};
/**
* Show the visual representation of a next connection.
* This is a horizontal line.
* @param {!Blockly.ASTNode} curNode The node that we want to draw the cursor for.
* @private
*/
Blockly.CursorSvg.prototype.showWithNext_ = function(curNode) {
var connection = curNode.getLocation();
var targetBlock = /** @type {Blockly.BlockSvg} */ (connection.getSourceBlock());
var x = 0;
var y = connection.getOffsetInBlock().y;
var width = targetBlock.getHeightWidth().width;
if (this.workspace_.RTL) {
x = -width;
}
this.positionLine_(x, y, width);
this.setParent_(targetBlock);
this.showCurrent_();
};
/**
* Show the visual representation of a stack.
* This is a box with extra padding around the entire stack of blocks.
* @param {!Blockly.ASTNode} curNode The node that we want to draw the cursor for.
* @private
*/
Blockly.CursorSvg.prototype.showWithStack_ = function(curNode) {
var block = /** @type {Blockly.BlockSvg} */ (curNode.getLocation());
// Gets the height and width of entire stack.
var heightWidth = block.getHeightWidth();
// Add padding so that being on a stack looks different than being on a block.
var width = heightWidth.width + Blockly.CursorSvg.STACK_PADDING;
var height = heightWidth.height + Blockly.CursorSvg.STACK_PADDING;
// Shift the rectangle slightly to upper left so padding is equal on all sides.
var xPadding = -1 * Blockly.CursorSvg.STACK_PADDING / 2;
var yPadding = -1 * Blockly.CursorSvg.STACK_PADDING / 2;
var x = xPadding;
var y = yPadding;
if (this.workspace_.RTL) {
x = -(width + xPadding);
}
this.positionRect_(x, y, width, height);
this.setParent_(block);
this.showCurrent_();
};
/**
* Show the current cursor.
* @private
*/
Blockly.CursorSvg.prototype.showCurrent_ = function() {
this.hide();
this.currentCursorSvg.style.display = '';
};
/**************************
* Position
**************************/
/**
* Position the cursor for a block.
* Displays an outline of the top half of a rectangle around a block.
* @param {number} width The width of the block.
* @param {number} cursorOffset The extra padding for around the block.
* @param {number} cursorHeight The height of the cursor.
*/
Blockly.CursorSvg.prototype.positionBlock_ = function(width, cursorOffset, cursorHeight) {
var cursorPath = Blockly.utils.svgPaths.moveBy(-1 * cursorOffset, cursorHeight) +
Blockly.utils.svgPaths.lineOnAxis('V', -1 * cursorOffset) +
Blockly.utils.svgPaths.lineOnAxis('H', width + cursorOffset * 2) +
Blockly.utils.svgPaths.lineOnAxis('V', cursorHeight);
this.cursorBlock_.setAttribute('d', cursorPath);
if (this.workspace_.RTL) {
this.flipRtl_(this.cursorBlock_);
}
this.currentCursorSvg = this.cursorBlock_;
};
/**
* Position the cursor for an input connection.
* Displays a filled in puzzle piece.
* @param {!Blockly.Connection} connection The connection to position cursor around.
* @private
*/
Blockly.CursorSvg.prototype.positionInput_ = function(connection) {
var x = connection.getOffsetInBlock().x;
var y = connection.getOffsetInBlock().y;
var path = Blockly.utils.svgPaths.moveTo(0,0) +
this.constants_.PUZZLE_TAB.pathDown;
this.cursorInput_.setAttribute('d', path);
this.cursorInput_.setAttribute('transform',
'translate(' + x + ',' + y + ')' + (this.workspace_.RTL ? ' scale(-1 1)' : ''));
this.currentCursorSvg = this.cursorInput_;
};
/**
* Move and show the cursor at the specified coordinate in workspace units.
* Displays a horizontal line.
* @param {number} x The new x, in workspace units.
* @param {number} y The new y, in workspace units.
* @param {number} width The new width, in workspace units.
* @private
*/
Blockly.CursorSvg.prototype.positionLine_ = function(x, y, width) {
this.cursorSvgLine_.setAttribute('x', x);
this.cursorSvgLine_.setAttribute('y', y);
this.cursorSvgLine_.setAttribute('width', width);
this.currentCursorSvg = this.cursorSvgLine_;
};
/**
* Position the cursor for an output connection.
* Displays a puzzle outline and the top and bottom path.
* @param {number} width The width of the block.
* @param {number} height The height of the block.
* @private
*/
Blockly.CursorSvg.prototype.positionOutput_ = function(width, height) {
var cursorPath = Blockly.utils.svgPaths.moveBy(width, 0) +
Blockly.utils.svgPaths.lineOnAxis('h', -1 * (width - this.constants_.PUZZLE_TAB.width)) +
Blockly.utils.svgPaths.lineOnAxis('v', this.constants_.TAB_OFFSET_FROM_TOP) +
this.constants_.PUZZLE_TAB.pathDown +
Blockly.utils.svgPaths.lineOnAxis('V', height) +
Blockly.utils.svgPaths.lineOnAxis('H', width);
this.cursorBlock_.setAttribute('d', cursorPath);
if (this.workspace_.RTL) {
this.flipRtl_(this.cursorBlock_);
}
this.currentCursorSvg = this.cursorBlock_;
};
/**
* Position the cursor for a previous connection.
* Displays a half rectangle with a notch in the top to represent the previous
* connection.
* @param {number} width The width of the block.
* @param {number} cursorOffset The offset of the cursor from around the block.
* @param {number} cursorHeight The height of the cursor.
* @private
*/
Blockly.CursorSvg.prototype.positionPrevious_ = function(width, cursorOffset, cursorHeight) {
var cursorPath = Blockly.utils.svgPaths.moveBy(-1 * cursorOffset, cursorHeight) +
Blockly.utils.svgPaths.lineOnAxis('V', -1 * cursorOffset) +
Blockly.utils.svgPaths.lineOnAxis('H', this.constants_.NOTCH_OFFSET_LEFT) +
this.constants_.NOTCH.pathLeft +
Blockly.utils.svgPaths.lineOnAxis('H', width + cursorOffset * 2) +
Blockly.utils.svgPaths.lineOnAxis('V', cursorHeight);
this.cursorBlock_.setAttribute('d', cursorPath);
if (this.workspace_.RTL) {
this.flipRtl_(this.cursorBlock_);
}
this.currentCursorSvg = this.cursorBlock_;
};
/**
* Move and show the cursor at the specified coordinate in workspace units.
* Displays a filled in rectangle.
* @param {number} x The new x, in workspace units.
* @param {number} y The new y, in workspace units.
* @param {number} width The new width, in workspace units.
* @param {number} height The new height, in workspace units.
* @private
*/
Blockly.CursorSvg.prototype.positionRect_ = function(x, y, width, height) {
this.cursorSvgRect_.setAttribute('x', x);
this.cursorSvgRect_.setAttribute('y', y);
this.cursorSvgRect_.setAttribute('width', width);
this.cursorSvgRect_.setAttribute('height', height);
this.currentCursorSvg = this.cursorSvgRect_;
};
/**
* Flip the SVG paths in RTL.
* @param {!SVGElement} cursor The cursor that we want to flip.
* @private
*/
Blockly.CursorSvg.prototype.flipRtl_ = function(cursor) {
cursor.setAttribute('transform', 'scale(-1 1)');
};
/**
* Hide the cursor.
* @package
*/
Blockly.CursorSvg.prototype.hide = function() {
this.cursorSvgLine_.style.display = 'none';
this.cursorSvgRect_.style.display = 'none';
this.cursorInput_.style.display = 'none';
this.cursorBlock_.style.display = 'none';
};
/**
* Update the cursor.
* @param {Blockly.ASTNode} curNode The node that we want to draw the cursor for.
* @package
*/
Blockly.CursorSvg.prototype.draw = function(curNode) {
if (!curNode) {
this.hide();
return;
}
if (curNode.getType() === Blockly.ASTNode.types.BLOCK) {
var block = /** @type {Blockly.BlockSvg} */ (curNode.getLocation());
this.showWithBlockPrevOutput_(block);
} else if (curNode.getType() === Blockly.ASTNode.types.OUTPUT) {
var outputBlock = /** @type {Blockly.BlockSvg} */ (curNode.getLocation().getSourceBlock());
this.showWithBlockPrevOutput_(outputBlock);
} else if (curNode.getLocation().type === Blockly.INPUT_VALUE) {
this.showWithInput_(curNode);
} else if (curNode.getLocation().type === Blockly.NEXT_STATEMENT) {
this.showWithNext_(curNode);
} else if (curNode.getType() === Blockly.ASTNode.types.PREVIOUS) {
var previousBlock = /** @type {Blockly.BlockSvg} */ (curNode.getLocation().getSourceBlock());
this.showWithBlockPrevOutput_(previousBlock);
} else if (curNode.getType() === Blockly.ASTNode.types.FIELD) {
this.showWithField_(curNode);
} else if (curNode.getType() === Blockly.ASTNode.types.WORKSPACE) {
this.showWithCoordinates_(curNode);
} else if (curNode.getType() === Blockly.ASTNode.types.STACK) {
this.showWithStack_(curNode);
}
};
/**
* Create the cursor SVG.
* @return {Element} The SVG node created.
* @private
*/
Blockly.CursorSvg.prototype.createCursorSvg_ = function() {
/* This markup will be generated and added to the .svgGroup_:
<g>
<rect width="100" height="5">
<animate attributeType="XML" attributeName="fill" dur="1s"
values="transparent;transparent;#fff;transparent" repeatCount="indefinite" />
</rect>
</g>
*/
var colour = this.isMarker_ ? Blockly.CursorSvg.MARKER_COLOR :
Blockly.CursorSvg.CURSOR_COLOR;
this.cursorSvg_ = Blockly.utils.dom.createSvgElement('g',
{
'width': Blockly.CursorSvg.CURSOR_WIDTH,
'height': Blockly.CursorSvg.CURSOR_HEIGHT
}, this.svgGroup_);
// A horizontal line used to represent a workspace coordinate or next connection.
this.cursorSvgLine_ = Blockly.utils.dom.createSvgElement('rect',
{
'x': '0',
'y': '0',
'fill': colour,
'width': Blockly.CursorSvg.CURSOR_WIDTH,
'height': Blockly.CursorSvg.CURSOR_HEIGHT,
'style': 'display: none;'
},
this.cursorSvg_);
// A filled in rectangle used to represent a stack.
this.cursorSvgRect_ = Blockly.utils.dom.createSvgElement('rect',
{
'class': 'blocklyVerticalCursor',
'x': '0',
'y': '0',
'rx': '10', 'ry': '10',
'style': 'display: none;',
'stroke': colour
},
this.cursorSvg_);
// A filled in puzzle piece used to represent an input value.
this.cursorInput_ = Blockly.utils.dom.createSvgElement(
'path',
{
'width': Blockly.CursorSvg.CURSOR_WIDTH,
'height': Blockly.CursorSvg.CURSOR_HEIGHT,
'transform':'',
'style':'display: none;',
'fill': colour
},
this.cursorSvg_);
// A path used to repreesent a previous connection and a block, an output
// connection and a block, or a block.
this.cursorBlock_ = Blockly.utils.dom.createSvgElement(
'path',
{
'width': Blockly.CursorSvg.CURSOR_WIDTH,
'height': Blockly.CursorSvg.CURSOR_HEIGHT,
'transform':'',
'style':'display: none;',
'fill': 'none',
'stroke': colour,
'stroke-width': '4'
},
this.cursorSvg_);
// Markers and stack cursors don't blink.
if (!this.isMarker_) {
Blockly.utils.dom.createSvgElement('animate',
{
'attributeType': 'XML',
'attributeName': 'fill',
'dur': '1s',
'values': Blockly.CursorSvg.CURSOR_COLOR + ';transparent;transparent;',
'repeatCount': 'indefinite'
},
this.cursorSvgLine_);
Blockly.utils.dom.createSvgElement('animate',
{
'attributeType': 'XML',
'attributeName': 'fill',
'dur': '1s',
'values': Blockly.CursorSvg.CURSOR_COLOR + ';transparent;transparent;',
'repeatCount': 'indefinite'
},
this.cursorInput_);
Blockly.utils.dom.createSvgElement('animate',
{
'attributeType': 'XML',
'attributeName': 'stroke',
'dur': '1s',
'values': Blockly.CursorSvg.CURSOR_COLOR + ';transparent;transparent;',
'repeatCount': 'indefinite'
},
this.cursorBlock_);
}
return this.cursorSvg_;
};
/**
* Dispose of this cursor.
* @package
*/
Blockly.CursorSvg.prototype.dispose = function() {
if (this.svgGroup_) {
Blockly.utils.dom.removeNode(this.svgGroup_);
}
};

View File

@@ -25,6 +25,7 @@
goog.provide('Blockly.FlyoutCursor');
goog.require('Blockly.Cursor');
goog.require('Blockly.navigation');
goog.require('Blockly.utils.object');
@@ -39,6 +40,26 @@ Blockly.FlyoutCursor = function() {
};
Blockly.utils.object.inherits(Blockly.FlyoutCursor, Blockly.Cursor);
/**
* Handles the given action.
* This is only triggered when keyboard navigation is enabled.
* @param {!Blockly.Action} action The action to be handled.
* @return {boolean} True if the action has been handled, false otherwise.
* @override
*/
Blockly.FlyoutCursor.prototype.onBlocklyAction = function(action) {
switch (action.name) {
case Blockly.navigation.actionNames.PREVIOUS:
this.prev();
return true;
case Blockly.navigation.actionNames.NEXT:
this.next();
return true;
default:
return false;
}
};
/**
* Find the next connection, field, or block.
* @return {Blockly.ASTNode} The next element, or null if the current node is

View File

@@ -24,13 +24,16 @@
goog.provide('Blockly.user.keyMap');
// TODO: Fix circular dependency.
// goog.require('Blockly.navigation');
goog.require('Blockly.utils.KeyCodes');
goog.require('Blockly.utils.object');
/**
* Holds the serialized key to key action mapping.
* @type {Object<string, Blockly.Action>}
* @type {!Object<string, Blockly.Action>}
* @private
*/
Blockly.user.keyMap.map_ = {};
@@ -50,7 +53,6 @@ Blockly.user.keyMap.modifierKeys = {
* @param {string} keyCode The key code serialized by the serializeKeyEvent.
* @param {!Blockly.Action} action The action to be executed when the keys
* corresponding to the serialized key code is pressed.
* @package
*/
Blockly.user.keyMap.setActionForKey = function(keyCode, action) {
var oldKey = Blockly.user.keyMap.getKeyByAction(action);
@@ -63,9 +65,8 @@ Blockly.user.keyMap.setActionForKey = function(keyCode, action) {
/**
* Creates a new key map.
* @param {Object<string, Blockly.Action>} keyMap The object holding the key
* @param {!Object<string, Blockly.Action>} keyMap The object holding the key
* to action mapping.
* @package
*/
Blockly.user.keyMap.setKeyMap = function(keyMap) {
Blockly.user.keyMap.map_ = keyMap;
@@ -75,7 +76,6 @@ Blockly.user.keyMap.setKeyMap = function(keyMap) {
* Gets the current key map.
* @return {Object<string,Blockly.Action>} The object holding the key to
* action mapping.
* @package
*/
Blockly.user.keyMap.getKeyMap = function() {
var map = {};
@@ -88,7 +88,6 @@ Blockly.user.keyMap.getKeyMap = function() {
* @param {string} keyCode The serialized key code.
* @return {Blockly.Action|undefined} The action holding the function to
* call when the given keyCode is used or undefined if no action exists.
* @package
*/
Blockly.user.keyMap.getActionByKeyCode = function(keyCode) {
return Blockly.user.keyMap.map_[keyCode];
@@ -100,11 +99,10 @@ Blockly.user.keyMap.getActionByKeyCode = function(keyCode) {
* the key.
* @return {?string} The serialized key or null if the action does not have
* a key mapping.
* @package
*/
Blockly.user.keyMap.getKeyByAction = function(action) {
var keys = Object.keys(Blockly.user.keyMap.map_);
for (var i = 0, key; key = keys[i]; i++) {
for (var i = 0, key; (key = keys[i]); i++) {
if (Blockly.user.keyMap.map_[key].name === action.name) {
return key;
}
@@ -116,11 +114,12 @@ Blockly.user.keyMap.getKeyByAction = function(action) {
* Serialize the key event.
* @param {!Event} e A key up event holding the key code.
* @return {string} A string containing the serialized key event.
* @package
*/
Blockly.user.keyMap.serializeKeyEvent = function(e) {
var modifiers = Blockly.utils.object.values(Blockly.user.keyMap.modifierKeys);
var key = '';
for (var i = 0, keyName; keyName = modifiers[i]; i++) {
for (var i = 0, keyName; (keyName = modifiers[i]); i++) {
if (e.getModifierState(keyName)) {
key += keyName;
}
@@ -129,6 +128,21 @@ Blockly.user.keyMap.serializeKeyEvent = function(e) {
return key;
};
/**
* Checks whether any of the given modifiers are not valid.
* @param {!Array.<string>} modifiers List of modifiers to be used with the key.
* @param {!Array.<string>} validModifiers List of modifiers we support.
* @throws {Error} if the modifier is not in the valid modifiers list.
* @private
*/
Blockly.user.keyMap.checkModifiers_ = function(modifiers, validModifiers) {
for (var i = 0, modifier; (modifier = modifiers[i]); i++) {
if (validModifiers.indexOf(modifier) < 0) {
throw Error(modifier + ' is not a valid modifier key.');
}
}
};
/**
* Create the serialized key code that will be used in the key map.
* @param {number} keyCode Number code representing the key.
@@ -139,11 +153,10 @@ Blockly.user.keyMap.serializeKeyEvent = function(e) {
Blockly.user.keyMap.createSerializedKey = function(keyCode, modifiers) {
var key = '';
var validModifiers = Blockly.utils.object.values(Blockly.user.keyMap.modifierKeys);
for (var i = 0, keyName; keyName = modifiers[i]; i++) {
if (validModifiers.indexOf(keyName) > -1) {
key += keyName;
} else {
throw Error(keyName + ' is not a valid modifier key.');
Blockly.user.keyMap.checkModifiers_(modifiers, validModifiers);
for (var i = 0, validModifier; (validModifier = validModifiers[i]); i++) {
if (modifiers.indexOf(validModifier) > -1) {
key += validModifier;
}
}
key += keyCode;
@@ -158,7 +171,16 @@ Blockly.user.keyMap.createSerializedKey = function(keyCode, modifiers) {
Blockly.user.keyMap.createDefaultKeyMap = function() {
var map = {};
var controlK = Blockly.user.keyMap.createSerializedKey(
Blockly.utils.KeyCodes.K, [Blockly.user.keyMap.modifierKeys.CONTROL]);
Blockly.utils.KeyCodes.K, [Blockly.user.keyMap.modifierKeys.CONTROL,
Blockly.user.keyMap.modifierKeys.SHIFT]);
var shiftW = Blockly.user.keyMap.createSerializedKey(
Blockly.utils.KeyCodes.W, [Blockly.user.keyMap.modifierKeys.SHIFT]);
var shiftA = Blockly.user.keyMap.createSerializedKey(
Blockly.utils.KeyCodes.A, [Blockly.user.keyMap.modifierKeys.SHIFT]);
var shiftS = Blockly.user.keyMap.createSerializedKey(
Blockly.utils.KeyCodes.S, [Blockly.user.keyMap.modifierKeys.SHIFT]);
var shiftD = Blockly.user.keyMap.createSerializedKey(
Blockly.utils.KeyCodes.D, [Blockly.user.keyMap.modifierKeys.SHIFT]);
map[Blockly.utils.KeyCodes.W] = Blockly.navigation.ACTION_PREVIOUS;
map[Blockly.utils.KeyCodes.A] = Blockly.navigation.ACTION_OUT;
@@ -171,5 +193,9 @@ Blockly.user.keyMap.createDefaultKeyMap = function() {
map[Blockly.utils.KeyCodes.E] = Blockly.navigation.ACTION_EXIT;
map[Blockly.utils.KeyCodes.ESC] = Blockly.navigation.ACTION_EXIT;
map[controlK] = Blockly.navigation.ACTION_TOGGLE_KEYBOARD_NAV;
map[shiftW] = Blockly.navigation.ACTION_MOVE_WS_CURSOR_UP;
map[shiftA] = Blockly.navigation.ACTION_MOVE_WS_CURSOR_LEFT;
map[shiftS] = Blockly.navigation.ACTION_MOVE_WS_CURSOR_DOWN;
map[shiftD] = Blockly.navigation.ACTION_MOVE_WS_CURSOR_RIGHT;
return map;
};

133
core/keyboard_nav/marker.js Normal file
View File

@@ -0,0 +1,133 @@
/**
* @license
* Copyright 2019 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @fileoverview The class representing a marker.
* Used primarily for keyboard navigation to show a marked location.
* @author aschmiedt@google.com (Abby Schmiedt)
*/
'use strict';
goog.provide('Blockly.Marker');
goog.require('Blockly.ASTNode');
goog.require('Blockly.navigation');
/**
* Class for a marker.
* This is used in keyboard navigation to save a location in the Blockly AST.
* @constructor
*/
Blockly.Marker = function() {
/**
* The colour of the marker.
* @type {?string}
*/
this.colour = null;
/**
* The current location of the marker.
* @type {Blockly.ASTNode}
* @private
*/
this.curNode_ = null;
/**
* The object in charge of drawing the visual representation of the current node.
* @type {Blockly.blockRendering.MarkerSvg}
* @private
*/
this.drawer_ = null;
/**
* The type of the marker.
* @type {string}
*/
this.type = 'marker';
};
/**
* Sets the object in charge of drawing the marker.
* @param {Blockly.blockRendering.MarkerSvg} drawer The object in charge of
* drawing the marker.
*/
Blockly.Marker.prototype.setDrawer = function(drawer) {
this.drawer_ = drawer;
};
/**
* Get the current drawer for the marker.
* @return {Blockly.blockRendering.MarkerSvg} The object in charge of drawing
* the marker.
*/
Blockly.Marker.prototype.getDrawer = function() {
return this.drawer_;
};
/**
* Gets the current location of the marker.
* @return {Blockly.ASTNode} The current field, connection, or block the marker
* is on.
*/
Blockly.Marker.prototype.getCurNode = function() {
return this.curNode_;
};
/**
* Set the location of the marker and call the update method.
* Setting isStack to true will only work if the newLocation is the top most
* output or previous connection on a stack.
* @param {Blockly.ASTNode} newNode The new location of the marker.
*/
Blockly.Marker.prototype.setCurNode = function(newNode) {
var oldNode = this.curNode_;
this.curNode_ = newNode;
if (this.drawer_) {
this.drawer_.draw(oldNode, this.curNode_);
}
};
/**
* Redraw the current marker.
* @package
*/
Blockly.Marker.prototype.draw = function() {
if (this.drawer_) {
this.drawer_.draw(this.curNode_, this.curNode_);
}
};
/**
* Hide the marker SVG.
*/
Blockly.Marker.prototype.hide = function() {
if (this.drawer_) {
this.drawer_.hide();
}
};
/**
* Dispose of this marker.
*/
Blockly.Marker.prototype.dispose = function() {
if (this.getDrawer()) {
this.getDrawer().dispose();
}
};

View File

@@ -1,76 +0,0 @@
/**
* @license
* Copyright 2019 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @fileoverview The class representing a cursor used for marking a location.
* Used primarily for keyboard navigation.
* @author aschmiedt@google.com (Abby Schmiedt)
*/
'use strict';
goog.provide('Blockly.MarkerCursor');
goog.require('Blockly.Cursor');
goog.require('Blockly.utils.object');
/**
* Class for a marker.
* This is used in keyboard navigation to save a location in the Blockly AST.
* @constructor
* @extends {Blockly.Cursor}
*/
Blockly.MarkerCursor = function() {
Blockly.MarkerCursor.superClass_.constructor.call(this);
};
Blockly.utils.object.inherits(Blockly.MarkerCursor, Blockly.Cursor);
/**
* This is a no-op since markers do not move.
* @return {null} Always null.
* @override
*/
Blockly.MarkerCursor.prototype.next = function() {
return null;
};
/**
* This is a no-op since markers do not move.
* @return {null} Always null.
* @override
*/
Blockly.MarkerCursor.prototype.in = function() {
return null;
};
/**
* This is a no-op since markers do not move.
* @return {null} Always null.
* @override
*/
Blockly.MarkerCursor.prototype.prev = function() {
return null;
};
/**
* This is a no-op since markers do not move.
* @return {null} Always null.
* @override
*/
Blockly.MarkerCursor.prototype.out = function() {
return null;
};

View File

@@ -45,21 +45,31 @@ Blockly.navigation.loggingCallback = null;
/**
* State indicating focus is currently on the flyout.
* @type {number}
* @const
*/
Blockly.navigation.STATE_FLYOUT = 1;
/**
* State indicating focus is currently on the workspace.
* @type {number}
* @const
*/
Blockly.navigation.STATE_WS = 2;
/**
* State indicating focus is currently on the toolbox.
* @type {number}
* @const
*/
Blockly.navigation.STATE_TOOLBOX = 3;
/**
* The distance to move the cursor on the workspace.
* @type {number}
* @const
*/
Blockly.navigation.WS_MOVE_DISTANCE = 40;
/**
* The current state the user is in.
* Initialized to workspace state since a user enters navigation mode by shift
@@ -83,17 +93,35 @@ Blockly.navigation.actionNames = {
DISCONNECT: 'disconnect',
TOOLBOX: 'toolbox',
EXIT: 'exit',
TOGGLE_KEYBOARD_NAV: 'toggle_keyboard_nav'
TOGGLE_KEYBOARD_NAV: 'toggle_keyboard_nav',
MOVE_WS_CURSOR_UP: 'move workspace cursor up',
MOVE_WS_CURSOR_DOWN: 'move workspace cursor down',
MOVE_WS_CURSOR_LEFT: 'move workspace cursor left',
MOVE_WS_CURSOR_RIGHT: 'move workspace cursor right'
};
/**
* The name of the marker reserved for internal use.
* @type {string}
* @const
*/
Blockly.navigation.MARKER_NAME = 'local_marker_1';
/** ****** */
/** Focus */
/** ****** */
/**
* Get the local marker.
* @return {!Blockly.Marker} The local marker for the main workspace.
*/
Blockly.navigation.getMarker = function() {
return Blockly.getMainWorkspace().getMarker(Blockly.navigation.MARKER_NAME);
};
/**
* If a toolbox exists, set the navigation state to toolbox and select the first
* category in the toolbox.
* category.
* @private
*/
Blockly.navigation.focusToolbox_ = function() {
@@ -103,7 +131,7 @@ Blockly.navigation.focusToolbox_ = function() {
Blockly.navigation.currentState_ = Blockly.navigation.STATE_TOOLBOX;
Blockly.navigation.resetFlyout_(false /* shouldHide */);
if (!workspace.getMarker().getCurNode()) {
if (!Blockly.navigation.getMarker().getCurNode()) {
Blockly.navigation.markAtCursor_();
}
toolbox.selectFirstCategory();
@@ -121,7 +149,7 @@ Blockly.navigation.focusFlyout_ = function() {
var toolbox = workspace.getToolbox();
var flyout = toolbox ? toolbox.flyout_ : workspace.getFlyout();
if (!workspace.getMarker().getCurNode()) {
if (!Blockly.navigation.getMarker().getCurNode()) {
Blockly.navigation.markAtCursor_();
}
@@ -202,10 +230,10 @@ Blockly.navigation.insertFromFlyout = function() {
var newBlock = flyout.createBlock(curBlock);
// Render to get the sizing right.
newBlock.render();
// Connections are hidden when the block is first created. Normally there's
// enough time for them to become unhidden in the user's mouse movements,
// but not here.
newBlock.setConnectionsHidden(false);
// Connections are not tracked when the block is first created. Normally
// there's enough time for them to become tracked in the user's mouse
// movements, but not here.
newBlock.setConnectionTracking(true);
workspace.getCursor().setCurNode(
Blockly.ASTNode.createBlockNode(newBlock));
if (!Blockly.navigation.modify_()) {
@@ -242,7 +270,7 @@ Blockly.navigation.resetFlyout_ = function(shouldHide) {
* @private
*/
Blockly.navigation.modifyWarn_ = function() {
var markerNode = Blockly.getMainWorkspace().getMarker().getCurNode();
var markerNode = Blockly.navigation.getMarker().getCurNode();
var cursorNode = Blockly.getMainWorkspace().getCursor().getCurNode();
if (!markerNode) {
@@ -283,7 +311,7 @@ Blockly.navigation.modifyWarn_ = function() {
/**
* Disconnect the block from its parent and move to the position of the
* workspace node.
* @param {!Blockly.Block} block The block to be moved to the workspace.
* @param {Blockly.Block} block The block to be moved to the workspace.
* @param {!Blockly.ASTNode} wsNode The workspace node holding the position the
* block will be moved to.
* @return {boolean} True if the block can be moved to the workspace,
@@ -291,6 +319,9 @@ Blockly.navigation.modifyWarn_ = function() {
* @private
*/
Blockly.navigation.moveBlockToWorkspace_ = function(block, wsNode) {
if (!block) {
return false;
}
if (block.isShadow()) {
Blockly.navigation.warn_('Cannot move a shadow block to the workspace.');
return false;
@@ -310,7 +341,7 @@ Blockly.navigation.moveBlockToWorkspace_ = function(block, wsNode) {
* @private
*/
Blockly.navigation.modify_ = function() {
var markerNode = Blockly.getMainWorkspace().getMarker().getCurNode();
var markerNode = Blockly.navigation.getMarker().getCurNode();
var cursorNode = Blockly.getMainWorkspace().getCursor().getCurNode();
if (!Blockly.navigation.modifyWarn_()) {
return false;
@@ -323,13 +354,17 @@ Blockly.navigation.modify_ = function() {
var markerLoc = markerNode.getLocation();
if (markerNode.isConnection() && cursorNode.isConnection()) {
cursorLoc = /** @type {!Blockly.Connection} */ (cursorLoc);
markerLoc = /** @type {!Blockly.Connection} */ (markerLoc);
return Blockly.navigation.connect_(cursorLoc, markerLoc);
} else if (markerNode.isConnection() &&
(cursorType == Blockly.ASTNode.types.BLOCK ||
cursorType == Blockly.ASTNode.types.STACK)) {
(cursorType == Blockly.ASTNode.types.BLOCK ||
cursorType == Blockly.ASTNode.types.STACK)) {
cursorLoc = /** @type {!Blockly.Block} */ (cursorLoc);
markerLoc = /** @type {!Blockly.Connection} */ (markerLoc);
return Blockly.navigation.insertBlock(cursorLoc, markerLoc);
} else if (markerType == Blockly.ASTNode.types.WORKSPACE) {
var block = Blockly.navigation.getSourceBlock_(cursorNode);
var block = cursorNode ? cursorNode.getSourceBlock() : null;
return Blockly.navigation.moveBlockToWorkspace_(block, markerNode);
}
Blockly.navigation.warn_('Unexpected state in Blockly.navigation.modify_.');
@@ -367,12 +402,12 @@ Blockly.navigation.disconnectChild_ = function(movingConnection, destConnection)
* @private
*/
Blockly.navigation.moveAndConnect_ = function(movingConnection, destConnection) {
if (!movingConnection || ! destConnection) {
if (!movingConnection || !destConnection) {
return false;
}
var movingBlock = movingConnection.getSourceBlock();
if (destConnection.canConnectWithReason_(movingConnection) ==
if (destConnection.canConnectWithReason(movingConnection) ==
Blockly.Connection.CAN_CONNECT) {
Blockly.navigation.disconnectChild_(movingConnection, destConnection);
@@ -458,7 +493,7 @@ Blockly.navigation.connect_ = function(movingConnection, destConnection) {
return true;
} else {
try {
destConnection.checkConnection_(movingConnection);
destConnection.checkConnection(movingConnection);
}
catch (e) {
// If nothing worked report the error from the original connections.
@@ -472,7 +507,7 @@ Blockly.navigation.connect_ = function(movingConnection, destConnection) {
* Tries to connect the given block to the destination connection, making an
* intelligent guess about which connection to use to on the moving block.
* @param {!Blockly.Block} block The block to move.
* @param {Blockly.Connection} destConnection The connection to connect to.
* @param {!Blockly.Connection} destConnection The connection to connect to.
* @return {boolean} Whether the connection was successful.
*/
Blockly.navigation.insertBlock = function(block, destConnection) {
@@ -525,7 +560,7 @@ Blockly.navigation.disconnectBlocks_ = function() {
Blockly.navigation.log_('Cannot disconnect blocks when the cursor is not on a connection');
return;
}
var curConnection = curNode.getLocation();
var curConnection = /** @type {!Blockly.Connection} */ (curNode.getLocation());
if (!curConnection.isConnected()) {
Blockly.navigation.log_('Cannot disconnect unconnected connection');
return;
@@ -541,7 +576,7 @@ Blockly.navigation.disconnectBlocks_ = function() {
return;
}
superiorConnection.disconnect();
inferiorConnection.bumpAwayFrom_(superiorConnection);
inferiorConnection.bumpAwayFrom(superiorConnection);
var rootBlock = superiorConnection.getSourceBlock().getRootBlock();
rootBlock.bringToFront();
@@ -559,8 +594,8 @@ Blockly.navigation.disconnectBlocks_ = function() {
* @private
*/
Blockly.navigation.markAtCursor_ = function() {
var workspace = Blockly.getMainWorkspace();
workspace.getMarker().setCurNode(workspace.getCursor().getCurNode());
Blockly.navigation.getMarker().setCurNode(
Blockly.getMainWorkspace().getCursor().getCurNode());
};
/**
@@ -568,9 +603,9 @@ Blockly.navigation.markAtCursor_ = function() {
* @private
*/
Blockly.navigation.removeMark_ = function() {
var workspace = Blockly.getMainWorkspace();
workspace.getMarker().setCurNode(null);
workspace.getMarker().hide();
var marker = Blockly.navigation.getMarker();
marker.setCurNode(null);
marker.hide();
};
/**
@@ -582,28 +617,6 @@ Blockly.navigation.setState = function(newState) {
Blockly.navigation.currentState_ = newState;
};
/**
* Finds the source block of the location on a given node.
* @param {Blockly.ASTNode} node The node to find the source block on.
* @return {Blockly.Block} The source block of the location on the given node,
* or null if the node is of type workspace.
* @private
*/
Blockly.navigation.getSourceBlock_ = function(node) {
if (!node) {
return null;
}
if (node.getType() === Blockly.ASTNode.types.BLOCK) {
return /** @type {Blockly.Block} */ (node.getLocation());
} else if (node.getType() === Blockly.ASTNode.types.STACK) {
return /** @type {Blockly.Block} */ (node.getLocation());
} else if (node.getType() === Blockly.ASTNode.types.WORKSPACE) {
return null;
} else {
return node.getLocation().getSourceBlock();
}
};
/**
* Gets the top node on a block.
* This is either the previous connection, output connection or the block.
@@ -635,7 +648,7 @@ Blockly.navigation.moveCursorOnBlockDelete = function(deletedBlock) {
var cursor = workspace.getCursor();
if (cursor) {
var curNode = cursor.getCurNode();
var block = Blockly.navigation.getSourceBlock_(curNode);
var block = curNode ? curNode.getSourceBlock() : null;
if (block === deletedBlock) {
// If the block has a parent move the cursor to their connection point.
@@ -652,7 +665,7 @@ Blockly.navigation.moveCursorOnBlockDelete = function(deletedBlock) {
}
// If the cursor is on a block whose parent is being deleted, move the
// cursor to the workspace.
} else if (deletedBlock.getChildren(false).indexOf(block) > -1) {
} else if (block && deletedBlock.getChildren(false).indexOf(block) > -1) {
cursor.setCurNode(Blockly.ASTNode.createWorkspaceNode(block.workspace,
block.getRelativeToSurfaceXY()));
}
@@ -669,7 +682,7 @@ Blockly.navigation.moveCursorOnBlockMutation = function(mutatedBlock) {
var cursor = Blockly.getMainWorkspace().getCursor();
if (cursor) {
var curNode = cursor.getCurNode();
var block = Blockly.navigation.getSourceBlock_(curNode);
var block = curNode ? curNode.getSourceBlock() : null;
if (block === mutatedBlock) {
cursor.setCurNode(Blockly.ASTNode.createBlockNode(block));
@@ -681,8 +694,8 @@ Blockly.navigation.moveCursorOnBlockMutation = function(mutatedBlock) {
* Enable accessibility mode.
*/
Blockly.navigation.enableKeyboardAccessibility = function() {
if (!Blockly.keyboardAccessibilityMode) {
Blockly.keyboardAccessibilityMode = true;
if (!Blockly.getMainWorkspace().keyboardAccessibilityMode) {
Blockly.getMainWorkspace().keyboardAccessibilityMode = true;
Blockly.navigation.focusWorkspace_();
}
};
@@ -691,11 +704,11 @@ Blockly.navigation.enableKeyboardAccessibility = function() {
* Disable accessibility mode.
*/
Blockly.navigation.disableKeyboardAccessibility = function() {
if (Blockly.keyboardAccessibilityMode) {
if (Blockly.getMainWorkspace().keyboardAccessibilityMode) {
var workspace = Blockly.getMainWorkspace();
Blockly.keyboardAccessibilityMode = false;
Blockly.getMainWorkspace().keyboardAccessibilityMode = false;
workspace.getCursor().hide();
workspace.getMarker().hide();
Blockly.navigation.getMarker().hide();
if (Blockly.navigation.getFlyoutCursor_()) {
Blockly.navigation.getFlyoutCursor_().hide();
}
@@ -764,8 +777,8 @@ Blockly.navigation.onKeyPress = function(e) {
};
/**
* Execute any actions on the flyout, workspace, or toolbox that correspond to
* the given action.
* Decides which actions to handle depending on keyboard navigation and readonly
* states.
* @param {!Blockly.Action} action The current action.
* @return {boolean} True if the action has been handled, false otherwise.
*/
@@ -773,7 +786,7 @@ Blockly.navigation.onBlocklyAction = function(action) {
var readOnly = Blockly.getMainWorkspace().options.readOnly;
var actionHandled = false;
if (Blockly.keyboardAccessibilityMode) {
if (Blockly.getMainWorkspace().keyboardAccessibilityMode) {
if (!readOnly) {
actionHandled = Blockly.navigation.handleActions_(action);
// If in readonly mode only handle valid actions.
@@ -790,92 +803,44 @@ Blockly.navigation.onBlocklyAction = function(action) {
/**
* Handles the action or dispatches to the appropriate action handler.
* @param {!Blockly.Action} action The current action
* @param {!Blockly.Action} action The action to handle.
* @return {boolean} True if the action has been handled, false otherwise.
* @private
*/
Blockly.navigation.handleActions_ = function(action) {
var workspace = Blockly.getMainWorkspace();
if (action.name === Blockly.navigation.actionNames.TOGGLE_KEYBOARD_NAV) {
if (action.name == Blockly.navigation.actionNames.TOOLBOX ||
Blockly.navigation.currentState_ == Blockly.navigation.STATE_TOOLBOX) {
return Blockly.navigation.toolboxOnAction_(action);
} else if (action.name == Blockly.navigation.actionNames.TOGGLE_KEYBOARD_NAV) {
Blockly.navigation.disableKeyboardAccessibility();
return true;
} else if (action.name === Blockly.navigation.actionNames.TOOLBOX) {
if (!workspace.getToolbox()) {
Blockly.navigation.focusFlyout_();
} else {
Blockly.navigation.focusToolbox_();
}
return true;
} else if (Blockly.navigation.currentState_ === Blockly.navigation.STATE_WS) {
var curNode = workspace.getCursor().getCurNode();
var actionHandled = false;
if (curNode && curNode.getType() === Blockly.ASTNode.types.FIELD) {
actionHandled = curNode.getLocation().onBlocklyAction(action);
}
if (!actionHandled) {
actionHandled = Blockly.navigation.workspaceOnAction_(action);
}
return actionHandled;
} else if (Blockly.navigation.currentState_ === Blockly.navigation.STATE_FLYOUT) {
} if (Blockly.navigation.currentState_ == Blockly.navigation.STATE_WS) {
return Blockly.navigation.workspaceOnAction_(action);
} else if (Blockly.navigation.currentState_ == Blockly.navigation.STATE_FLYOUT) {
return Blockly.navigation.flyoutOnAction_(action);
} else if (Blockly.navigation.currentState_ === Blockly.navigation.STATE_TOOLBOX) {
return Blockly.navigation.toolboxOnAction_(action);
}
return false;
};
/**
* Handle all actions performed on the workspace.
* @param {!Blockly.Action} action The action to handle.
* @return {boolean} True if the action has been handled, false otherwise.
* @private
*/
Blockly.navigation.workspaceOnAction_ = function(action) {
var workspace = Blockly.getMainWorkspace();
switch (action.name) {
case Blockly.navigation.actionNames.PREVIOUS:
workspace.getCursor().prev();
return true;
case Blockly.navigation.actionNames.OUT:
workspace.getCursor().out();
return true;
case Blockly.navigation.actionNames.NEXT:
workspace.getCursor().next();
return true;
case Blockly.navigation.actionNames.IN:
workspace.getCursor().in();
return true;
case Blockly.navigation.actionNames.INSERT:
Blockly.navigation.modify_();
return true;
case Blockly.navigation.actionNames.MARK:
Blockly.navigation.handleEnterForWS_();
return true;
case Blockly.navigation.actionNames.DISCONNECT:
Blockly.navigation.disconnectBlocks_();
return true;
default:
return false;
}
};
/**
* Handle all actions performed on the flyout.
* Handles the given action for the flyout.
* @param {!Blockly.Action} action The action to handle.
* @return {boolean} True if the action has been handled, false otherwise.
* @private
*/
Blockly.navigation.flyoutOnAction_ = function(action) {
var workspace = Blockly.getMainWorkspace();
var toolbox = workspace.getToolbox();
var flyout = toolbox ? toolbox.flyout_ : workspace.getFlyout();
if (flyout && flyout.onBlocklyAction(action)) {
return true;
}
switch (action.name) {
case Blockly.navigation.actionNames.PREVIOUS:
Blockly.navigation.getFlyoutCursor_().prev();
return true;
case Blockly.navigation.actionNames.OUT:
Blockly.navigation.focusToolbox_();
return true;
case Blockly.navigation.actionNames.NEXT:
Blockly.navigation.getFlyoutCursor_().next();
return true;
case Blockly.navigation.actionNames.MARK:
Blockly.navigation.insertFromFlyout();
return true;
@@ -888,24 +853,92 @@ Blockly.navigation.flyoutOnAction_ = function(action) {
};
/**
* Handle all actions performeed on the toolbox.
* Handles the given action for the toolbox.
* @param {!Blockly.Action} action The action to handle.
* @return {boolean} True if the action has been handled, false otherwise.
* @private
*/
Blockly.navigation.toolboxOnAction_ = function(action) {
if (action.name === Blockly.navigation.actionNames.EXIT) {
Blockly.navigation.focusWorkspace_();
return true;
}
var toolbox = Blockly.getMainWorkspace().getToolbox();
var handled = toolbox.onBlocklyAction(action);
if (!handled && action.name === Blockly.navigation.actionNames.IN) {
Blockly.navigation.focusFlyout_();
var workspace = Blockly.getMainWorkspace();
var toolbox = workspace.getToolbox();
var handled = toolbox ? toolbox.onBlocklyAction(action) : false;
if (handled) {
return true;
}
return handled;
if (action.name === Blockly.navigation.actionNames.TOOLBOX) {
if (!workspace.getToolbox()) {
Blockly.navigation.focusFlyout_();
} else {
Blockly.navigation.focusToolbox_();
}
return true;
} else if (action.name === Blockly.navigation.actionNames.IN) {
Blockly.navigation.focusFlyout_();
return true;
} else if (action.name === Blockly.navigation.actionNames.EXIT) {
Blockly.navigation.focusWorkspace_();
return true;
}
return false;
};
/**
* Move the workspace cursor in the given direction.
* @param {number} xDirection -1 to move cursor left. 1 to move cursor right.
* @param {number} yDirection -1 to move cursor up. 1 to move cursor down.
* @return {boolean} True if the current node is a workspace, false otherwise.
* @private
*/
Blockly.navigation.moveWSCursor_ = function(xDirection, yDirection) {
var cursor = Blockly.getMainWorkspace().getCursor();
var curNode = Blockly.getMainWorkspace().getCursor().getCurNode();
if (curNode.getType() !== Blockly.ASTNode.types.WORKSPACE) {
return false;
}
var wsCoord = curNode.getWsCoordinate();
var newX = xDirection * Blockly.navigation.WS_MOVE_DISTANCE + wsCoord.x;
var newY = yDirection * Blockly.navigation.WS_MOVE_DISTANCE + wsCoord.y;
cursor.setCurNode(Blockly.ASTNode.createWorkspaceNode(
Blockly.getMainWorkspace(), new Blockly.utils.Coordinate(newX, newY)));
return true;
};
/**
* Handles the given action for the workspace.
* @param {!Blockly.Action} action The action to handle.
* @return {boolean} True if the action has been handled, false otherwise.
* @private
*/
Blockly.navigation.workspaceOnAction_ = function(action) {
if (Blockly.getMainWorkspace().getCursor().onBlocklyAction(action)) {
return true;
}
switch (action.name) {
case Blockly.navigation.actionNames.INSERT:
Blockly.navigation.modify_();
return true;
case Blockly.navigation.actionNames.MARK:
Blockly.navigation.handleEnterForWS_();
return true;
case Blockly.navigation.actionNames.DISCONNECT:
Blockly.navigation.disconnectBlocks_();
return true;
case Blockly.navigation.actionNames.MOVE_WS_CURSOR_UP:
return Blockly.navigation.moveWSCursor_(0, -1);
case Blockly.navigation.actionNames.MOVE_WS_CURSOR_DOWN:
return Blockly.navigation.moveWSCursor_(0, 1);
case Blockly.navigation.actionNames.MOVE_WS_CURSOR_LEFT:
return Blockly.navigation.moveWSCursor_(-1, 0);
case Blockly.navigation.actionNames.MOVE_WS_CURSOR_RIGHT:
return Blockly.navigation.moveWSCursor_(1, 0);
default:
return false;
}
};
/**
@@ -917,7 +950,7 @@ Blockly.navigation.handleEnterForWS_ = function() {
var curNode = cursor.getCurNode();
var nodeType = curNode.getType();
if (nodeType == Blockly.ASTNode.types.FIELD) {
curNode.getLocation().showEditor_();
curNode.getLocation().showEditor();
} else if (curNode.isConnection() ||
nodeType == Blockly.ASTNode.types.WORKSPACE) {
Blockly.navigation.markAtCursor_();
@@ -944,7 +977,8 @@ Blockly.navigation.ACTION_PREVIOUS = new Blockly.Action(
* @type {!Blockly.Action}
*/
Blockly.navigation.ACTION_OUT = new Blockly.Action(
Blockly.navigation.actionNames.OUT, 'Go to the parent of the current location.');
Blockly.navigation.actionNames.OUT,
'Go to the parent of the current location.');
/**
* The next action.
@@ -958,7 +992,8 @@ Blockly.navigation.ACTION_NEXT = new Blockly.Action(
* @type {!Blockly.Action}
*/
Blockly.navigation.ACTION_IN = new Blockly.Action(
Blockly.navigation.actionNames.IN, 'Go to the first child of the current location.');
Blockly.navigation.actionNames.IN,
'Go to the first child of the current location.');
/**
* The action to try to insert a block.
@@ -980,8 +1015,8 @@ Blockly.navigation.ACTION_MARK = new Blockly.Action(
* @type {!Blockly.Action}
*/
Blockly.navigation.ACTION_DISCONNECT = new Blockly.Action(
Blockly.navigation.actionNames.DISCONNECT, 'Dicsonnect the block at the' +
'current location from its parent.');
Blockly.navigation.actionNames.DISCONNECT,
'Disconnect the block at the current location from its parent.');
/**
* The action to open the toolbox.
@@ -995,14 +1030,49 @@ Blockly.navigation.ACTION_TOOLBOX = new Blockly.Action(
* @type {!Blockly.Action}
*/
Blockly.navigation.ACTION_EXIT = new Blockly.Action(
Blockly.navigation.actionNames.EXIT, 'Close the current modal, such as a toolbox or field editor.');
Blockly.navigation.actionNames.EXIT,
'Close the current modal, such as a toolbox or field editor.');
/**
* The action to toggle keyboard navigation mode on and off.
* @type {!Blockly.Action}
*/
Blockly.navigation.ACTION_TOGGLE_KEYBOARD_NAV = new Blockly.Action(
Blockly.navigation.actionNames.TOGGLE_KEYBOARD_NAV, 'Turns on and off keyboard navigation.');
Blockly.navigation.actionNames.TOGGLE_KEYBOARD_NAV,
'Turns on and off keyboard navigation.');
/**
* The action to move the cursor to the keft on a worksapce.
* @type {!Blockly.Action}
*/
Blockly.navigation.ACTION_MOVE_WS_CURSOR_LEFT = new Blockly.Action(
Blockly.navigation.actionNames.MOVE_WS_CURSOR_LEFT,
'Move the workspace cursor to the lefts.');
/**
* The action to move the cursor to the right on a worksapce.
* @type {!Blockly.Action}
*/
Blockly.navigation.ACTION_MOVE_WS_CURSOR_RIGHT = new Blockly.Action(
Blockly.navigation.actionNames.MOVE_WS_CURSOR_RIGHT,
'Move the workspace cursor to the right.');
/**
* The action to move the cursor up on a worksapce.
* @type {!Blockly.Action}
*/
Blockly.navigation.ACTION_MOVE_WS_CURSOR_UP = new Blockly.Action(
Blockly.navigation.actionNames.MOVE_WS_CURSOR_UP,
'Move the workspace cursor up.');
/**
* The action to move the cursor down on a worksapce.
* @type {!Blockly.Action}
*/
Blockly.navigation.ACTION_MOVE_WS_CURSOR_DOWN = new Blockly.Action(
Blockly.navigation.actionNames.MOVE_WS_CURSOR_DOWN,
'Move the workspace cursor down.');
/**
* List of actions that can be performed in read only mode.

View File

@@ -0,0 +1,60 @@
/**
* @license
* Copyright 2019 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @fileoverview The class representing a cursor that is used to navigate
* between tab navigable fields.
* @author samelh@google.com (Sam El-Husseini)
*/
'use strict';
goog.provide('Blockly.TabNavigateCursor');
goog.require('Blockly.ASTNode');
goog.require('Blockly.BasicCursor');
goog.require('Blockly.utils.object');
/**
* A cursor for navigating between tab navigable fields.
* @constructor
* @extends {Blockly.BasicCursor}
*/
Blockly.TabNavigateCursor = function() {
Blockly.TabNavigateCursor.superClass_.constructor.call(this);
};
Blockly.utils.object.inherits(Blockly.TabNavigateCursor, Blockly.BasicCursor);
/**
* Skip all nodes except for tab navigable fields.
* @param {Blockly.ASTNode} node The AST node to check whether it is valid.
* @return {boolean} True if the node should be visited, false otherwise.
* @override
*/
Blockly.TabNavigateCursor.prototype.validNode_ = function(node) {
var isValid = false;
var type = node && node.getType();
if (node) {
var location = node.getLocation();
if (type == Blockly.ASTNode.types.FIELD &&
location && location.isTabNavigable() &&
(/** @type {!Blockly.Field} */ (location)).isClickable()) {
isValid = true;
}
}
return isValid;
};

182
core/marker_manager.js Normal file
View File

@@ -0,0 +1,182 @@
/**
* @license
* Copyright 2019 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @fileoverview Object in charge of managing markers and the cursor.
* @author aschmiedt@google.com (Abby Schmiedt)
*/
'use strict';
goog.provide('Blockly.MarkerManager');
goog.require('Blockly.Cursor');
goog.require('Blockly.Marker');
/**
* Class to manage the multiple markers and the cursor on a workspace.
* @param {!Blockly.WorkspaceSvg} workspace The workspace for the marker manager.
* @constructor
* @package
*/
Blockly.MarkerManager = function(workspace){
/**
* The cursor.
* @type {Blockly.Cursor}
* @private
*/
this.cursor_ = null;
/**
* The cursor's svg element.
* @type {SVGElement}
* @private
*/
this.cursorSvg_ = null;
/**
* The map of markers for the workspace.
* @type {!Object<string, !Blockly.Marker>}
* @private
*/
this.markers_ = {};
/**
* The workspace this marker manager is associated with.
* @type {!Blockly.WorkspaceSvg}
* @private
*/
this.workspace_ = workspace;
};
/**
* Register the marker by adding it to the map of markers.
* @param {string} id A unique identifier for the marker.
* @param {!Blockly.Marker} marker The marker to register.
*/
Blockly.MarkerManager.prototype.registerMarker = function(id, marker) {
if (this.markers_[id]) {
this.unregisterMarker(id);
}
marker.setDrawer(this.workspace_.getRenderer()
.makeMarkerDrawer(this.workspace_, marker));
this.setMarkerSvg(marker.getDrawer().createDom());
this.markers_[id] = marker;
};
/**
* Unregister the marker by removing it from the map of markers.
* @param {string} id The id of the marker to unregister.
*/
Blockly.MarkerManager.prototype.unregisterMarker = function(id) {
var marker = this.markers_[id];
if (marker) {
marker.dispose();
delete this.markers_[id];
} else {
throw Error('Marker with id ' + id + ' does not exist. Can only unregister' +
'markers that exist.');
}
};
/**
* Get the cursor for the workspace.
* @return {Blockly.Cursor} The cursor for this workspace.
*/
Blockly.MarkerManager.prototype.getCursor = function() {
return this.cursor_;
};
/**
* Get a single marker that corresponds to the given id.
* @param {string} id A unique identifier for the marker.
* @return {Blockly.Marker} The marker that corresponds to the given id, or null
* if none exists.
*/
Blockly.MarkerManager.prototype.getMarker = function(id) {
return this.markers_[id];
};
/**
* Sets the cursor and initializes the drawer for use with keyboard navigation.
* @param {Blockly.Cursor} cursor The cursor used to move around this workspace.
*/
Blockly.MarkerManager.prototype.setCursor = function(cursor) {
if (this.cursor_ && this.cursor_.getDrawer()) {
this.cursor_.getDrawer().dispose();
}
this.cursor_ = cursor;
if (this.cursor_) {
var drawer = this.workspace_.getRenderer()
.makeMarkerDrawer(this.workspace_, this.cursor_);
this.cursor_.setDrawer(drawer);
this.setCursorSvg(this.cursor_.getDrawer().createDom());
}
};
/**
* Add the cursor svg to this workspace svg group.
* @param {SVGElement} cursorSvg The svg root of the cursor to be added to the
* workspace svg group.
* @package
*/
Blockly.MarkerManager.prototype.setCursorSvg = function(cursorSvg) {
if (!cursorSvg) {
this.cursorSvg_ = null;
return;
}
this.workspace_.getBlockCanvas().appendChild(cursorSvg);
this.cursorSvg_ = cursorSvg;
};
/**
* Add the marker svg to this workspaces svg group.
* @param {SVGElement} markerSvg The svg root of the marker to be added to the
* workspace svg group.
* @package
*/
Blockly.MarkerManager.prototype.setMarkerSvg = function(markerSvg) {
if (!markerSvg) {
this.markerSvg_ = null;
return;
}
if (this.workspace_.getBlockCanvas()) {
if (this.cursorSvg_) {
this.workspace_.getBlockCanvas().insertBefore(markerSvg, this.cursorSvg_);
} else {
this.workspace_.getBlockCanvas().appendChild(markerSvg);
}
}
};
/**
* Dispose of the marker manager.
* Go through and delete all markers associated with this marker manager.
* @suppress {checkTypes}
* @package
*/
Blockly.MarkerManager.prototype.dispose = function() {
var markerIds = Object.keys(this.markers_);
for (var i = 0, markerId; (markerId = markerIds[i]); i++) {
this.unregisterMarker(markerId);
}
this.markers_ = null;
this.cursor_.dispose();
this.cursor_ = null;
};

View File

@@ -29,6 +29,7 @@ goog.require('Blockly.Events');
goog.require('Blockly.Events.BlockChange');
goog.require('Blockly.Events.Ui');
goog.require('Blockly.Icon');
goog.require('Blockly.navigation');
goog.require('Blockly.utils');
goog.require('Blockly.utils.dom');
goog.require('Blockly.utils.global');
@@ -62,6 +63,25 @@ Blockly.Mutator.prototype.workspaceWidth_ = 0;
*/
Blockly.Mutator.prototype.workspaceHeight_ = 0;
/**
* Set the block this mutator is associated with.
* @param {Blockly.BlockSvg} block The block associated with this mutator.
* @package
*/
Blockly.Mutator.prototype.setBlock = function(block) {
this.block_ = block;
};
/**
* Returns the workspace inside this mutator icon's bubble.
* @return {Blockly.WorkspaceSvg} The workspace inside this mutator icon's
* bubble.
* @package
*/
Blockly.Mutator.prototype.getWorkspace = function() {
return this.workspace_;
};
/**
* Draw the mutator icon.
* @param {!Element} group The icon group.
@@ -133,7 +153,7 @@ Blockly.Mutator.prototype.createEditor_ = function() {
// Convert the list of names into a list of XML objects for the flyout.
if (this.quarkNames_.length) {
var quarkXml = Blockly.utils.xml.createElement('xml');
for (var i = 0, quarkName; quarkName = this.quarkNames_[i]; i++) {
for (var i = 0, quarkName; (quarkName = this.quarkNames_[i]); i++) {
var element = Blockly.utils.xml.createElement('block');
element.setAttribute('type', quarkName);
quarkXml.appendChild(element);
@@ -141,22 +161,22 @@ Blockly.Mutator.prototype.createEditor_ = function() {
} else {
var quarkXml = null;
}
var workspaceOptions = {
// If you want to enable disabling, also remove the
// event filter from workspaceChanged_ .
disable: false,
disabledPatternId: this.block_.workspace.options.disabledPatternId,
languageTree: quarkXml,
parentWorkspace: this.block_.workspace,
pathToMedia: this.block_.workspace.options.pathToMedia,
RTL: this.block_.RTL,
toolboxPosition: this.block_.RTL ? Blockly.TOOLBOX_AT_RIGHT :
Blockly.TOOLBOX_AT_LEFT,
horizontalLayout: false,
getMetrics: this.getFlyoutMetrics_.bind(this),
setMetrics: null,
renderer: this.block_.workspace.options.renderer
};
var workspaceOptions = new Blockly.Options(
/** @type {!Blockly.BlocklyOptions} */
({
// If you want to enable disabling, also remove the
// event filter from workspaceChanged_ .
'disable': false,
'parentWorkspace': this.block_.workspace,
'media': this.block_.workspace.options.pathToMedia,
'rtl': this.block_.RTL,
'horizontalLayout': false,
'renderer': this.block_.workspace.options.renderer
}));
workspaceOptions.toolboxPosition = this.block_.RTL ? Blockly.TOOLBOX_AT_RIGHT :
Blockly.TOOLBOX_AT_LEFT;
workspaceOptions.languageTree = quarkXml;
workspaceOptions.getMetrics = this.getFlyoutMetrics_.bind(this);
this.workspace_ = new Blockly.WorkspaceSvg(workspaceOptions);
this.workspace_.isMutator = true;
this.workspace_.addChangeListener(Blockly.Events.disableOrphans);
@@ -165,7 +185,7 @@ Blockly.Mutator.prototype.createEditor_ = function() {
// a top level svg. Instead of handling scale themselves, mutators
// inherit scale from the parent workspace.
// To fix this, scale needs to be applied at a different level in the dom.
var flyoutSvg = this.workspace_.addFlyout_('g');
var flyoutSvg = this.workspace_.addFlyout('g');
var background = this.workspace_.createDom('blocklyMutatorBackground');
// Insert the flyout after the <rect> but before the block canvas so that
@@ -215,8 +235,9 @@ Blockly.Mutator.prototype.resizeBubble_ = function() {
width = workspaceSize.width + workspaceSize.x;
}
var height = workspaceSize.height + doubleBorderWidth * 3;
if (this.workspace_.flyout_) {
var flyoutMetrics = this.workspace_.flyout_.getMetrics_();
var flyout = this.workspace_.getFlyout();
if (flyout) {
var flyoutMetrics = flyout.getMetrics_();
height = Math.max(height, flyoutMetrics.contentHeight + 20);
}
width += doubleBorderWidth * 3;
@@ -241,6 +262,16 @@ Blockly.Mutator.prototype.resizeBubble_ = function() {
this.workspace_.resize();
};
/**
* A method handler for when the bubble is moved.
* @private
*/
Blockly.Mutator.prototype.onBubbleMove_ = function() {
if (this.workspace_) {
this.workspace_.recordDeleteAreas();
}
};
/**
* Show or hide the mutator bubble.
* @param {boolean} visible True if the bubble should be visible.
@@ -256,26 +287,29 @@ Blockly.Mutator.prototype.setVisible = function(visible) {
// Create the bubble.
this.bubble_ = new Blockly.Bubble(
/** @type {!Blockly.WorkspaceSvg} */ (this.block_.workspace),
this.createEditor_(), this.block_.svgPath_, this.iconXY_, null, null);
this.createEditor_(), this.block_.pathObject.svgPath,
/** @type {!Blockly.utils.Coordinate} */ (this.iconXY_), null, null);
// Expose this mutator's block's ID on its top-level SVG group.
this.bubble_.setSvgId(this.block_.id);
this.bubble_.registerMoveEvent(this.onBubbleMove_.bind(this));
var tree = this.workspace_.options.languageTree;
var flyout = this.workspace_.getFlyout();
if (tree) {
this.workspace_.flyout_.init(this.workspace_);
this.workspace_.flyout_.show(tree.childNodes);
flyout.init(this.workspace_);
flyout.show(tree.childNodes);
}
this.rootBlock_ = this.block_.decompose(this.workspace_);
var blocks = this.rootBlock_.getDescendants(false);
for (var i = 0, child; child = blocks[i]; i++) {
for (var i = 0, child; (child = blocks[i]); i++) {
child.render();
}
// The root block should not be dragable or deletable.
this.rootBlock_.setMovable(false);
this.rootBlock_.setDeletable(false);
if (this.workspace_.flyout_) {
var margin = this.workspace_.flyout_.CORNER_RADIUS * 2;
var x = this.workspace_.getFlyout().getWidth() + margin;
if (flyout) {
var margin = flyout.CORNER_RADIUS * 2;
var x = flyout.getWidth() + margin;
} else {
var margin = 16;
var x = margin;
@@ -287,16 +321,19 @@ Blockly.Mutator.prototype.setVisible = function(visible) {
// Save the initial connections, then listen for further changes.
if (this.block_.saveConnections) {
var thisMutator = this;
this.block_.saveConnections(this.rootBlock_);
var mutatorBlock =
/** @type {{saveConnections: function(!Blockly.Block)}} */ (
this.block_);
mutatorBlock.saveConnections(this.rootBlock_);
this.sourceListener_ = function() {
thisMutator.block_.saveConnections(thisMutator.rootBlock_);
mutatorBlock.saveConnections(thisMutator.rootBlock_);
};
this.block_.workspace.addChangeListener(this.sourceListener_);
}
this.resizeBubble_();
// When the mutator's workspace changes, update the source block.
this.workspace_.addChangeListener(this.workspaceChanged_.bind(this));
this.updateColour();
this.applyColour();
} else {
// Dispose of the bubble.
this.svgDialog_ = null;
@@ -330,7 +367,7 @@ Blockly.Mutator.prototype.workspaceChanged_ = function(e) {
if (!this.workspace_.isDragging()) {
var blocks = this.workspace_.getTopBlocks(false);
var MARGIN = 20;
for (var b = 0, block; block = blocks[b]; b++) {
for (var b = 0, block; (block = blocks[b]); b++) {
var blockXY = block.getRelativeToSurfaceXY();
var blockHW = block.getHeightWidth();
if (blockXY.y + blockHW.height < MARGIN) {
@@ -346,15 +383,14 @@ Blockly.Mutator.prototype.workspaceChanged_ = function(e) {
var block = this.block_;
var oldMutationDom = block.mutationToDom();
var oldMutation = oldMutationDom && Blockly.Xml.domToText(oldMutationDom);
// Switch off rendering while the source block is rebuilt.
var savedRendered = block.rendered;
block.rendered = false;
// Allow the source block to rebuild itself.
block.compose(this.rootBlock_);
// Restore rendering and show the changes.
block.rendered = savedRendered;
// Mutation may have added some elements that need initializing.
block.initSvg();
block.render();
if (Blockly.getMainWorkspace().keyboardAccessibilityMode) {
Blockly.navigation.moveCursorOnBlockMutation(block);
}
var newMutationDom = block.mutationToDom();
var newMutation = newMutationDom && Blockly.Xml.domToText(newMutationDom);
if (oldMutation != newMutation) {
@@ -368,13 +404,7 @@ Blockly.Mutator.prototype.workspaceChanged_ = function(e) {
Blockly.Events.setGroup(false);
}, Blockly.BUMP_DELAY);
}
if (block.rendered) {
block.render();
}
if (oldMutation != newMutation && Blockly.keyboardAccessibilityMode) {
Blockly.navigation.moveCursorOnBlockMutation(block);
}
// Don't update the bubble until the drag has ended, to avoid moving blocks
// under the cursor.
if (!this.workspace_.isDragging()) {
@@ -420,14 +450,14 @@ Blockly.Mutator.prototype.dispose = function() {
Blockly.Mutator.prototype.updateBlockStyle = function() {
var ws = this.workspace_;
if (ws && ws.getAllBlocks()) {
var workspaceBlocks = ws.getAllBlocks();
if (ws && ws.getAllBlocks(false)) {
var workspaceBlocks = ws.getAllBlocks(false);
for (var i = 0; i < workspaceBlocks.length; i++) {
var block = workspaceBlocks[i];
block.setStyle(block.getStyleName());
}
var flyoutBlocks = ws.flyout_.workspace_.getAllBlocks();
var flyoutBlocks = ws.getFlyout().workspace_.getAllBlocks(false);
for (var i = 0; i < flyoutBlocks.length; i++) {
var block = flyoutBlocks[i];
block.setStyle(block.getStyleName());

View File

@@ -76,7 +76,6 @@ Blockly.Names.prototype.reset = function() {
/**
* Set the variable map that maps from variable name to variable object.
* @param {!Blockly.VariableMap} map The map to track.
* @package
*/
Blockly.Names.prototype.setVariableMap = function(map) {
this.variableMap_ = map;
@@ -84,7 +83,8 @@ Blockly.Names.prototype.setVariableMap = function(map) {
/**
* Get the name for a user-defined variable, based on its ID.
* This should only be used for variables of type Blockly.Variables.NAME_TYPE.
* This should only be used for variables of type
* Blockly.VARIABLE_CATEGORY_NAME.
* @param {string} id The ID to look up in the variable map.
* @return {?string} The name of the referenced variable, or null if there was
* no variable map or the variable was not found in the map.
@@ -113,10 +113,9 @@ Blockly.Names.prototype.getNameForUserVariable_ = function(id) {
* @param {string} type The type of entity in Blockly
* ('VARIABLE', 'PROCEDURE', 'BUILTIN', etc...).
* @return {string} An entity name that is legal in the exported language.
* @suppress {deprecated} Suppress deprecated Blockly.Variables.NAME_TYPE.
*/
Blockly.Names.prototype.getName = function(name, type) {
if (type == Blockly.Variables.NAME_TYPE) {
if (type == Blockly.VARIABLE_CATEGORY_NAME) {
var varName = this.getNameForUserVariable_(name);
if (varName) {
name = varName;
@@ -124,7 +123,7 @@ Blockly.Names.prototype.getName = function(name, type) {
}
var normalized = name.toLowerCase() + '_' + type;
var isVarType = type == Blockly.Variables.NAME_TYPE ||
var isVarType = type == Blockly.VARIABLE_CATEGORY_NAME ||
type == Blockly.Names.DEVELOPER_VARIABLE_TYPE;
var prefix = isVarType ? this.variablePrefix_ : '';
@@ -156,7 +155,7 @@ Blockly.Names.prototype.getDistinctName = function(name, type) {
}
safeName += i;
this.dbReverse_[safeName] = true;
var isVarType = type == Blockly.Variables.NAME_TYPE ||
var isVarType = type == Blockly.VARIABLE_CATEGORY_NAME ||
type == Blockly.Names.DEVELOPER_VARIABLE_TYPE;
var prefix = isVarType ? this.variablePrefix_ : '';
return prefix + safeName;

View File

@@ -23,6 +23,9 @@
goog.provide('Blockly.Options');
goog.require('Blockly.Theme');
goog.require('Blockly.Themes.Classic');
goog.require('Blockly.user.keyMap');
goog.require('Blockly.utils.userAgent');
goog.require('Blockly.Xml');
@@ -30,8 +33,8 @@ goog.require('Blockly.Xml');
/**
* Parse the user-specified options, using reasonable defaults where behaviour
* is unspecified.
* @param {!Object} options Dictionary of options. Specification:
* https://developers.google.com/blockly/guides/get-started/web#configuration
* @param {!Blockly.BlocklyOptions} options Dictionary of options.
* Specification: https://developers.google.com/blockly/guides/get-started/web#configuration
* @constructor
*/
Blockly.Options = function(options) {
@@ -84,11 +87,7 @@ Blockly.Options = function(options) {
horizontalLayout = false;
}
var toolboxAtStart = options['toolboxPosition'];
if (toolboxAtStart === 'end') {
toolboxAtStart = false;
} else {
toolboxAtStart = true;
}
toolboxAtStart = toolboxAtStart !== 'end';
if (horizontalLayout) {
var toolboxPosition = toolboxAtStart ?
@@ -114,7 +113,6 @@ Blockly.Options = function(options) {
} else {
var oneBasedIndex = !!options['oneBasedIndex'];
}
var theme = options['theme'];
var keyMap = options['keyMap'] || Blockly.user.keyMap.createDefaultKeyMap();
var renderer = options['renderer'] || 'geras';
@@ -141,27 +139,45 @@ Blockly.Options = function(options) {
this.gridOptions = Blockly.Options.parseGridOptions_(options);
this.zoomOptions = Blockly.Options.parseZoomOptions_(options);
this.toolboxPosition = toolboxPosition;
this.theme = theme;
this.theme = Blockly.Options.parseThemeOptions_(options);
this.keyMap = keyMap;
this.renderer = renderer;
/**
* The SVG element for the grid pattern.
* Created during injection.
* @type {!SVGElement}
*/
this.gridPattern = undefined;
/**
* The parent of the current workspace, or null if there is no parent
* workspace.
* @type {Blockly.Workspace}
*/
this.parentWorkspace = options['parentWorkspace'];
};
/**
* The parent of the current workspace, or null if there is no parent workspace.
* @type {Blockly.Workspace}
* Blockly options.
* This interface is further described in `typings/blockly-interfaces.d.ts`.
* @interface
*/
Blockly.Options.prototype.parentWorkspace = null;
Blockly.BlocklyOptions = function() {};
/**
* If set, sets the translation of the workspace to match the scrollbars.
* @param {!Object} xyRatio Contains an x and/or y property which is a float
* between 0 and 1 specifying the degree of scrolling.
* @return {void}
*/
Blockly.Options.prototype.setMetrics = null;
Blockly.Options.prototype.setMetrics;
/**
* Return an object with the metrics required to size the workspace.
* @return {Object} Contains size and position metrics, or null.
* @return {!Object} Contains size and position metrics.
*/
Blockly.Options.prototype.getMetrics = null;
Blockly.Options.prototype.getMetrics;
/**
* Parse the user-specified move options, using reasonable defaults where
@@ -237,6 +253,11 @@ Blockly.Options.parseZoomOptions_ = function(options) {
} else {
zoomOptions.scaleSpeed = Number(zoom['scaleSpeed']);
}
if (zoom['pinch'] === undefined) {
zoomOptions.pinch = zoomOptions.wheel || zoomOptions.controls;
} else {
zoomOptions.pinch = !!zoom['pinch'];
}
return zoomOptions;
};
@@ -253,11 +274,28 @@ Blockly.Options.parseGridOptions_ = function(options) {
var gridOptions = {};
gridOptions.spacing = Number(grid['spacing']) || 0;
gridOptions.colour = grid['colour'] || '#888';
gridOptions.length = Number(grid['length']) || 1;
gridOptions.length =
(grid['length'] === undefined) ? 1 : Number(grid['length']);
gridOptions.snap = gridOptions.spacing > 0 && !!grid['snap'];
return gridOptions;
};
/**
* Parse the user-specified theme options, using the classic theme as a default.
* https://developers.google.com/blockly/guides/configure/web/themes
* @param {!Object} options Dictionary of options.
* @return {!Blockly.Theme} A Blockly Theme.
* @private
*/
Blockly.Options.parseThemeOptions_ = function(options) {
var theme = options['theme'] || Blockly.Themes.Classic;
if (theme instanceof Blockly.Theme) {
return /** @type {!Blockly.Theme} */ (theme);
}
return new Blockly.Theme('builtin',
theme['blockStyles'], theme['categoryStyles'], theme['componentStyles']);
};
/**
* Parse the provided toolbox tree into a consistent DOM format.
* @param {Node|string} tree DOM tree of blocks, or text representation of same.

View File

@@ -46,6 +46,22 @@ goog.require('Blockly.Xml');
*/
Blockly.Procedures.NAME_TYPE = Blockly.PROCEDURE_CATEGORY_NAME;
/**
* The default argument for a procedures_mutatorarg block.
* @type {string}
*/
Blockly.Procedures.DEFAULT_ARG = 'x';
/**
* Procedure block type.
* @typedef {{
* getProcedureCall: function():string,
* renameProcedure: function(string,string),
* getProcedureDef: function():!Array
* }}
*/
Blockly.Procedures.ProcedureBlock;
/**
* Find all user-created procedure definitions in a workspace.
* @param {!Blockly.Workspace} root Root workspace.
@@ -60,7 +76,9 @@ Blockly.Procedures.allProcedures = function(root) {
var proceduresNoReturn = [];
for (var i = 0; i < blocks.length; i++) {
if (blocks[i].getProcedureDef) {
var tuple = blocks[i].getProcedureDef();
var procedureBlock = /** @type {!Blockly.Procedures.ProcedureBlock} */ (
blocks[i]);
var tuple = procedureBlock.getProcedureDef();
if (tuple) {
if (tuple[2]) {
proceduresReturn.push(tuple);
@@ -143,7 +161,9 @@ Blockly.Procedures.isNameUsed = function(name, workspace, opt_exclude) {
continue;
}
if (blocks[i].getProcedureDef) {
var procName = blocks[i].getProcedureDef();
var procedureBlock = /** @type {!Blockly.Procedures.ProcedureBlock} */ (
blocks[i]);
var procName = procedureBlock.getProcedureDef();
if (Blockly.Names.equals(procName[0], name)) {
return true;
}
@@ -162,14 +182,18 @@ Blockly.Procedures.rename = function(name) {
// Strip leading and trailing whitespace. Beyond this, all names are legal.
name = name.trim();
var legalName = Blockly.Procedures.findLegalName(name, this.getSourceBlock());
var legalName = Blockly.Procedures.findLegalName(name,
/** @type {!Blockly.Block} */ (this.getSourceBlock()));
var oldName = this.getValue();
if (oldName != name && oldName != legalName) {
// Rename any callers.
var blocks = this.getSourceBlock().workspace.getAllBlocks(false);
for (var i = 0; i < blocks.length; i++) {
if (blocks[i].renameProcedure) {
blocks[i].renameProcedure(oldName, legalName);
var procedureBlock = /** @type {!Blockly.Procedures.ProcedureBlock} */ (
blocks[i]);
procedureBlock.renameProcedure(
/** @type {string} */ (oldName), legalName);
}
}
}
@@ -253,6 +277,77 @@ Blockly.Procedures.flyoutCategory = function(workspace) {
return xmlList;
};
/**
* Updates the procedure mutator's flyout so that the arg block is not a
* duplicate of another arg.
* @param {!Blockly.Workspace} workspace The procedure mutator's workspace. This
* workspace's flyout is what is being updated.
* @private
*/
Blockly.Procedures.updateMutatorFlyout_ = function(workspace) {
var usedNames = [];
var blocks = workspace.getBlocksByType('procedures_mutatorarg', false);
for (var i = 0, block; (block = blocks[i]); i++) {
usedNames.push(block.getFieldValue('NAME'));
}
var xml = Blockly.utils.xml.createElement('xml');
var argBlock = Blockly.utils.xml.createElement('block');
argBlock.setAttribute('type', 'procedures_mutatorarg');
var nameField = Blockly.utils.xml.createElement('field');
nameField.setAttribute('name', 'NAME');
var argValue = Blockly.Variables.generateUniqueNameFromOptions(
Blockly.Procedures.DEFAULT_ARG, usedNames);
var fieldContent = Blockly.utils.xml.createTextNode(argValue);
nameField.appendChild(fieldContent);
argBlock.appendChild(nameField);
xml.appendChild(argBlock);
workspace.updateToolbox(xml);
};
/**
* Listens for when a procedure mutator is opened. Then it triggers a flyout
* update and adds a mutator change listener to the mutator workspace.
* @param {!Blockly.Events.Abstract} e The event that triggered this listener.
* @package
*/
Blockly.Procedures.mutatorOpenListener = function(e) {
if (e.type != Blockly.Events.UI || e.element != 'mutatorOpen' ||
!e.newValue) {
return;
}
var workspaceId = /** @type {string} */ (e.workspaceId);
var block = Blockly.Workspace.getById(workspaceId)
.getBlockById(e.blockId);
var type = block.type;
if (type != 'procedures_defnoreturn' && type != 'procedures_defreturn') {
return;
}
var workspace = block.mutator.getWorkspace();
Blockly.Procedures.updateMutatorFlyout_(workspace);
workspace.addChangeListener(Blockly.Procedures.mutatorChangeListener_);
};
/**
* Listens for changes in a procedure mutator and triggers flyout updates when
* necessary.
* @param {!Blockly.Events.Abstract} e The event that triggered this listener.
* @private
*/
Blockly.Procedures.mutatorChangeListener_ = function(e) {
if (e.type != Blockly.Events.BLOCK_CREATE &&
e.type != Blockly.Events.BLOCK_DELETE &&
e.type != Blockly.Events.BLOCK_CHANGE) {
return;
}
var workspaceId = /** @type {string} */ (e.workspaceId);
var workspace = /** @type {!Blockly.WorkspaceSvg} */
(Blockly.Workspace.getById(workspaceId));
Blockly.Procedures.updateMutatorFlyout_(workspace);
};
/**
* Find all the callers of a named procedure.
* @param {string} name Name of procedure.
@@ -265,7 +360,9 @@ Blockly.Procedures.getCallers = function(name, workspace) {
// Iterate through every block and check the name.
for (var i = 0; i < blocks.length; i++) {
if (blocks[i].getProcedureCall) {
var procName = blocks[i].getProcedureCall();
var procedureBlock = /** @type {!Blockly.Procedures.ProcedureBlock} */ (
blocks[i]);
var procName = procedureBlock.getProcedureCall();
// Procedure name may be null if the block is only half-built.
if (procName && Blockly.Names.equals(procName, name)) {
callers.push(blocks[i]);
@@ -282,10 +379,12 @@ Blockly.Procedures.getCallers = function(name, workspace) {
*/
Blockly.Procedures.mutateCallers = function(defBlock) {
var oldRecordUndo = Blockly.Events.recordUndo;
var name = defBlock.getProcedureDef()[0];
var procedureBlock = /** @type {!Blockly.Procedures.ProcedureBlock} */ (
defBlock);
var name = procedureBlock.getProcedureDef()[0];
var xmlElement = defBlock.mutationToDom(true);
var callers = Blockly.Procedures.getCallers(name, defBlock.workspace);
for (var i = 0, caller; caller = callers[i]; i++) {
for (var i = 0, caller; (caller = callers[i]); i++) {
var oldMutationDom = caller.mutationToDom();
var oldMutation = oldMutationDom && Blockly.Xml.domToText(oldMutationDom);
caller.domToMutation(xmlElement);
@@ -314,7 +413,9 @@ Blockly.Procedures.getDefinition = function(name, workspace) {
var blocks = workspace.getTopBlocks(false);
for (var i = 0; i < blocks.length; i++) {
if (blocks[i].getProcedureDef) {
var tuple = blocks[i].getProcedureDef();
var procedureBlock = /** @type {!Blockly.Procedures.ProcedureBlock} */ (
blocks[i]);
var tuple = procedureBlock.getProcedureDef();
if (tuple && Blockly.Names.equals(tuple[0], name)) {
return blocks[i];
}

View File

@@ -65,31 +65,65 @@ Blockly.RenderedConnection = function(source, type) {
this.offsetInBlock_ = new Blockly.utils.Coordinate(0, 0);
/**
* Has this connection been added to the connection database?
* @type {boolean}
* Describes the state of this connection's tracked-ness.
* @type {Blockly.RenderedConnection.TrackedState}
* @private
*/
this.inDB_ = false;
/**
* Whether this connections is hidden (not tracked in a database) or not.
* @type {boolean}
* @private
*/
this.hidden_ = !this.db_;
this.trackedState_ = Blockly.RenderedConnection.TrackedState.WILL_TRACK;
};
Blockly.utils.object.inherits(Blockly.RenderedConnection, Blockly.Connection);
/**
* Enum for different kinds of tracked states.
*
* WILL_TRACK means that this connection will add itself to
* the db on the next moveTo call it receives.
*
* UNTRACKED means that this connection will not add
* itself to the database until setTracking(true) is explicitly called.
*
* TRACKED means that this connection is currently being tracked.
* @enum {number}
*/
Blockly.RenderedConnection.TrackedState = {
WILL_TRACK: -1,
UNTRACKED: 0,
TRACKED: 1
};
/**
* Dispose of this connection. Remove it from the database (if it is
* tracked) and call the super-function to deal with connected blocks.
* @override
* @package
*/
Blockly.RenderedConnection.prototype.dispose = function() {
Blockly.RenderedConnection.superClass_.dispose.call(this);
if (this.inDB_) {
this.db_.removeConnection_(this);
if (this.trackedState_ == Blockly.RenderedConnection.TrackedState.TRACKED) {
this.db_.removeConnection(this, this.y);
}
};
/**
* Get the source block for this connection.
* @return {!Blockly.BlockSvg} The source block.
* @override
*/
Blockly.RenderedConnection.prototype.getSourceBlock = function() {
return /** @type {!Blockly.BlockSvg} */ (
Blockly.RenderedConnection.superClass_.getSourceBlock.call(this));
};
/**
* Returns the block that this connection connects to.
* @return {Blockly.BlockSvg} The connected block or null if none is connected.
* @override
*/
Blockly.RenderedConnection.prototype.targetBlock = function() {
return /** @type {Blockly.BlockSvg} */ (
Blockly.RenderedConnection.superClass_.targetBlock.call(this));
};
/**
* Returns the distance between this connection and another connection in
* workspace units.
@@ -98,8 +132,8 @@ Blockly.RenderedConnection.prototype.dispose = function() {
* @return {number} The distance between connections, in workspace units.
*/
Blockly.RenderedConnection.prototype.distanceFrom = function(otherConnection) {
var xDiff = this.x_ - otherConnection.x_;
var yDiff = this.y_ - otherConnection.y_;
var xDiff = this.x - otherConnection.x;
var yDiff = this.y - otherConnection.y;
return Math.sqrt(xDiff * xDiff + yDiff * yDiff);
};
@@ -108,9 +142,9 @@ Blockly.RenderedConnection.prototype.distanceFrom = function(otherConnection) {
* visually interfere with the specified connection.
* @param {!Blockly.Connection} staticConnection The connection to move away
* from.
* @private
* @package
*/
Blockly.RenderedConnection.prototype.bumpAwayFrom_ = function(staticConnection) {
Blockly.RenderedConnection.prototype.bumpAwayFrom = function(staticConnection) {
if (this.sourceBlock_.workspace.isDragging()) {
// Don't move blocks around while the user is doing the same.
return;
@@ -136,17 +170,17 @@ Blockly.RenderedConnection.prototype.bumpAwayFrom_ = function(staticConnection)
// Raise it to the top for extra visibility.
var selected = Blockly.selected == rootBlock;
selected || rootBlock.addSelect();
var dx = (staticConnection.x_ + Blockly.SNAP_RADIUS +
Math.floor(Math.random() * Blockly.BUMP_RANDOMNESS)) - this.x_;
var dy = (staticConnection.y_ + Blockly.SNAP_RADIUS +
Math.floor(Math.random() * Blockly.BUMP_RANDOMNESS)) - this.y_;
var dx = (staticConnection.x + Blockly.SNAP_RADIUS +
Math.floor(Math.random() * Blockly.BUMP_RANDOMNESS)) - this.x;
var dy = (staticConnection.y + Blockly.SNAP_RADIUS +
Math.floor(Math.random() * Blockly.BUMP_RANDOMNESS)) - this.y;
if (reverse) {
// When reversing a bump due to an uneditable block, bump up.
dy = -dy;
}
if (rootBlock.RTL) {
dx = (staticConnection.x_ - Blockly.SNAP_RADIUS -
Math.floor(Math.random() * Blockly.BUMP_RANDOMNESS)) - this.x_;
dx = (staticConnection.x - Blockly.SNAP_RADIUS -
Math.floor(Math.random() * Blockly.BUMP_RANDOMNESS)) - this.x;
}
rootBlock.moveBy(dx, dy);
selected || rootBlock.removeSelect();
@@ -158,16 +192,16 @@ Blockly.RenderedConnection.prototype.bumpAwayFrom_ = function(staticConnection)
* @param {number} y New absolute y coordinate, in workspace coordinates.
*/
Blockly.RenderedConnection.prototype.moveTo = function(x, y) {
// Remove it from its old location in the database (if already present)
if (this.inDB_) {
this.db_.removeConnection_(this);
}
this.x_ = x;
this.y_ = y;
// Insert it into its new location in the database.
if (!this.hidden_) {
this.db_.addConnection(this);
if (this.trackedState_ == Blockly.RenderedConnection.TrackedState.WILL_TRACK) {
this.db_.addConnection(this, y);
this.trackedState_ = Blockly.RenderedConnection.TrackedState.TRACKED;
} else if (this.trackedState_ == Blockly.RenderedConnection
.TrackedState.TRACKED) {
this.db_.removeConnection(this, this.y);
this.db_.addConnection(this, y);
}
this.x = x;
this.y = y;
};
/**
@@ -176,14 +210,14 @@ Blockly.RenderedConnection.prototype.moveTo = function(x, y) {
* @param {number} dy Change to y coordinate, in workspace units.
*/
Blockly.RenderedConnection.prototype.moveBy = function(dx, dy) {
this.moveTo(this.x_ + dx, this.y_ + dy);
this.moveTo(this.x + dx, this.y + dy);
};
/**
* Move this connection to the location given by its offset within the block and
* the location of the block's top left corner.
* @param {!Blockly.utils.Coordinate} blockTL The location of the top left corner
* of the block, in workspace coordinates.
* @param {!Blockly.utils.Coordinate} blockTL The location of the top left
* corner of the block, in workspace coordinates.
*/
Blockly.RenderedConnection.prototype.moveToOffset = function(blockTL) {
this.moveTo(blockTL.x + this.offsetInBlock_.x,
@@ -211,11 +245,11 @@ Blockly.RenderedConnection.prototype.getOffsetInBlock = function() {
/**
* Move the blocks on either side of this connection right next to each other.
* @private
* @package
*/
Blockly.RenderedConnection.prototype.tighten_ = function() {
var dx = this.targetConnection.x_ - this.x_;
var dy = this.targetConnection.y_ - this.y_;
Blockly.RenderedConnection.prototype.tighten = function() {
var dx = this.targetConnection.x - this.x;
var dy = this.targetConnection.y - this.y;
if (dx != 0 || dy != 0) {
var block = this.targetBlock();
var svgRoot = block.getSvgRoot();
@@ -226,7 +260,7 @@ Blockly.RenderedConnection.prototype.tighten_ = function() {
var xy = Blockly.utils.getRelativeXY(svgRoot);
block.getSvgRoot().setAttribute('transform',
'translate(' + (xy.x - dx) + ',' + (xy.y - dy) + ')');
block.moveConnections_(-dx, -dy);
block.moveConnections(-dx, -dy);
}
};
@@ -250,26 +284,27 @@ Blockly.RenderedConnection.prototype.closest = function(maxLimit, dxy) {
Blockly.RenderedConnection.prototype.highlight = function() {
var steps;
var sourceBlockSvg = /** @type {!Blockly.BlockSvg} */ (this.sourceBlock_);
var renderingConstants =
sourceBlockSvg.workspace.getRenderer().getConstants();
var renderConstants = sourceBlockSvg.workspace.getRenderer().getConstants();
var shape = renderConstants.shapeFor(this);
if (this.type == Blockly.INPUT_VALUE || this.type == Blockly.OUTPUT_VALUE) {
// Vertical line, puzzle tab, vertical line.
var yLen = 5;
var yLen = renderConstants.TAB_OFFSET_FROM_TOP;
steps = Blockly.utils.svgPaths.moveBy(0, -yLen) +
Blockly.utils.svgPaths.lineOnAxis('v', yLen) +
renderingConstants.PUZZLE_TAB.pathDown +
shape.pathDown +
Blockly.utils.svgPaths.lineOnAxis('v', yLen);
} else {
var xLen = 5;
var xLen =
renderConstants.NOTCH_OFFSET_LEFT - renderConstants.CORNER_RADIUS;
// Horizontal line, notch, horizontal line.
steps = Blockly.utils.svgPaths.moveBy(-xLen, 0) +
Blockly.utils.svgPaths.lineOnAxis('h', xLen) +
renderingConstants.NOTCH.pathLeft +
shape.pathLeft +
Blockly.utils.svgPaths.lineOnAxis('h', xLen);
}
var xy = this.sourceBlock_.getRelativeToSurfaceXY();
var x = this.x_ - xy.x;
var y = this.y_ - xy.y;
var x = this.x - xy.x;
var y = this.y - xy.y;
Blockly.Connection.highlightedPath_ = Blockly.utils.dom.createSvgElement(
'path',
{
@@ -282,17 +317,79 @@ Blockly.RenderedConnection.prototype.highlight = function() {
};
/**
* Unhide this connection, as well as all down-stream connections on any block
* attached to this connection. This happens when a block is expanded.
* Also unhides down-stream comments.
* Remove the highlighting around this connection.
*/
Blockly.RenderedConnection.prototype.unhighlight = function() {
Blockly.utils.dom.removeNode(Blockly.Connection.highlightedPath_);
delete Blockly.Connection.highlightedPath_;
};
/**
* Set whether this connections is tracked in the database or not.
* @param {boolean} doTracking If true, start tracking. If false, stop tracking.
* @package
*/
Blockly.RenderedConnection.prototype.setTracking = function(doTracking) {
if ((doTracking && this.trackedState_ ==
Blockly.RenderedConnection.TrackedState.TRACKED) ||
(!doTracking && this.trackedState_ ==
Blockly.RenderedConnection.TrackedState.UNTRACKED)) {
return;
}
if (this.sourceBlock_.isInFlyout) {
// Don't bother maintaining a database of connections in a flyout.
return;
}
if (doTracking) {
this.db_.addConnection(this, this.y);
this.trackedState_ = Blockly.RenderedConnection.TrackedState.TRACKED;
return;
}
if (this.trackedState_ == Blockly.RenderedConnection.TrackedState.TRACKED) {
this.db_.removeConnection(this, this.y);
}
this.trackedState_ = Blockly.RenderedConnection.TrackedState.UNTRACKED;
};
/**
* Stop tracking this connection, as well as all down-stream connections on
* any block attached to this connection. This happens when a block is
* collapsed.
*
* Also closes down-stream icons/bubbles.
* @package
*/
Blockly.RenderedConnection.prototype.stopTrackingAll = function() {
this.setTracking(false);
if (this.targetConnection) {
var blocks = this.targetBlock().getDescendants(false);
for (var i = 0; i < blocks.length; i++) {
var block = blocks[i];
// Stop tracking connections of all children.
var connections = block.getConnections_(true);
for (var j = 0; j < connections.length; j++) {
connections[j].setTracking(false);
}
// Close all bubbles of all children.
var icons = block.getIcons();
for (var j = 0; j < icons.length; j++) {
icons[j].setVisible(false);
}
}
}
};
/**
* Start tracking this connection, as well as all down-stream connections on
* any block attached to this connection. This happens when a block is expanded.
* @return {!Array.<!Blockly.Block>} List of blocks to render.
*/
Blockly.RenderedConnection.prototype.unhideAll = function() {
this.setHidden(false);
// All blocks that need unhiding must be unhidden before any rendering takes
// place, since rendering requires knowing the dimensions of lower blocks.
// Also, since rendering a block renders all its parents, we only need to
// render the leaf nodes.
Blockly.RenderedConnection.prototype.startTrackingAll = function() {
this.setTracking(true);
// All blocks that are not tracked must start tracking before any
// rendering takes place, since rendering requires knowing the dimensions
// of lower blocks. Also, since rendering a block renders all its parents,
// we only need to render the leaf nodes.
var renderList = [];
if (this.type != Blockly.INPUT_VALUE && this.type != Blockly.NEXT_STATEMENT) {
// Only spider down.
@@ -312,7 +409,7 @@ Blockly.RenderedConnection.prototype.unhideAll = function() {
connections = block.getConnections_(true);
}
for (var i = 0; i < connections.length; i++) {
renderList.push.apply(renderList, connections[i].unhideAll());
renderList.push.apply(renderList, connections[i].startTrackingAll());
}
if (!renderList.length) {
// Leaf block.
@@ -322,52 +419,6 @@ Blockly.RenderedConnection.prototype.unhideAll = function() {
return renderList;
};
/**
* Remove the highlighting around this connection.
*/
Blockly.RenderedConnection.prototype.unhighlight = function() {
Blockly.utils.dom.removeNode(Blockly.Connection.highlightedPath_);
delete Blockly.Connection.highlightedPath_;
};
/**
* Set whether this connections is hidden (not tracked in a database) or not.
* @param {boolean} hidden True if connection is hidden.
*/
Blockly.RenderedConnection.prototype.setHidden = function(hidden) {
this.hidden_ = hidden;
if (hidden && this.inDB_) {
this.db_.removeConnection_(this);
} else if (!hidden && !this.inDB_) {
this.db_.addConnection(this);
}
};
/**
* Hide this connection, as well as all down-stream connections on any block
* attached to this connection. This happens when a block is collapsed.
* Also hides down-stream comments.
*/
Blockly.RenderedConnection.prototype.hideAll = function() {
this.setHidden(true);
if (this.targetConnection) {
var blocks = this.targetBlock().getDescendants(false);
for (var i = 0; i < blocks.length; i++) {
var block = blocks[i];
// Hide all connections of all children.
var connections = block.getConnections_(true);
for (var j = 0; j < connections.length; j++) {
connections[j].setHidden(true);
}
// Close all bubbles of all children.
var icons = block.getIcons();
for (var j = 0; j < icons.length; j++) {
icons[j].setVisible(false);
}
}
}
};
/**
* Check if the two connections can be dragged to connect to each other.
* @param {!Blockly.Connection} candidate A nearby connection to check.
@@ -387,13 +438,13 @@ Blockly.RenderedConnection.prototype.isConnectionAllowed = function(candidate,
/**
* Behavior after a connection attempt fails.
* @param {Blockly.Connection} otherConnection Connection that this connection
* @param {!Blockly.Connection} otherConnection Connection that this connection
* failed to connect to.
* @package
*/
Blockly.RenderedConnection.prototype.onFailedConnect = function(
otherConnection) {
this.bumpAwayFrom_(otherConnection);
this.bumpAwayFrom(otherConnection);
};
@@ -446,9 +497,9 @@ Blockly.RenderedConnection.prototype.respawnShadow_ = function() {
* @param {number} maxLimit The maximum radius to another connection, in
* workspace units.
* @return {!Array.<!Blockly.Connection>} List of connections.
* @private
* @package
*/
Blockly.RenderedConnection.prototype.neighbours_ = function(maxLimit) {
Blockly.RenderedConnection.prototype.neighbours = function(maxLimit) {
return this.dbOpposite_.getNeighbours(this, maxLimit);
};
@@ -456,7 +507,7 @@ Blockly.RenderedConnection.prototype.neighbours_ = function(maxLimit) {
* Connect two connections together. This is the connection on the superior
* block. Rerender blocks as needed.
* @param {!Blockly.Connection} childConnection Connection on inferior block.
* @private
* @protected
*/
Blockly.RenderedConnection.prototype.connect_ = function(childConnection) {
Blockly.RenderedConnection.superClass_.connect_.call(this, childConnection);
@@ -487,11 +538,12 @@ Blockly.RenderedConnection.prototype.connect_ = function(childConnection) {
/**
* Function to be called when this connection's compatible types have changed.
* @private
* @protected
*/
Blockly.RenderedConnection.prototype.onCheckChanged_ = function() {
// The new value type may not be compatible with the existing connection.
if (this.isConnected() && !this.checkType_(this.targetConnection)) {
if (this.isConnected() && (!this.targetConnection ||
!this.checkType(this.targetConnection))) {
var child = this.isSuperior() ? this.targetBlock() : this.sourceBlock_;
child.unplug();
// Bump away.

View File

@@ -101,15 +101,16 @@ Blockly.blockRendering.init = function(name) {
/**
* Wrap the renderer constructor into a temporary constructor
* function so the closure compiler treats it as a constructor.
* @param {string} name The renderer name.
* @constructor
* @extends {Blockly.blockRendering.Renderer}
*/
var rendererCtor = function() {
rendererCtor.superClass_.constructor.call(this);
var rendererCtor = function(name) {
rendererCtor.superClass_.constructor.call(this, name);
};
Blockly.utils.object.inherits(rendererCtor,
Blockly.blockRendering.rendererMap_[name]);
var renderer = new rendererCtor();
var renderer = new rendererCtor(name);
renderer.init();
return renderer;
};

View File

@@ -23,7 +23,11 @@
goog.provide('Blockly.blockRendering.ConstantProvider');
goog.require('Blockly.utils');
goog.require('Blockly.utils.colour');
goog.require('Blockly.utils.dom');
goog.require('Blockly.utils.svgPaths');
goog.require('Blockly.utils.userAgent');
/**
@@ -32,30 +36,86 @@ goog.require('Blockly.utils.svgPaths');
* @package
*/
Blockly.blockRendering.ConstantProvider = function() {
/**
* The size of an empty spacer.
* @type {number}
*/
this.NO_PADDING = 0;
/**
* The size of small padding.
* @type {number}
*/
this.SMALL_PADDING = 3;
/**
* The size of medium padding.
* @type {number}
*/
this.MEDIUM_PADDING = 5;
/**
* The size of medium-large padding.
* @type {number}
*/
this.MEDIUM_LARGE_PADDING = 8;
/**
* The size of large padding.
* @type {number}
*/
this.LARGE_PADDING = 10;
// Offset from the top of the row for placing fields on inline input rows
// and statement input rows.
// Matches existing rendering (in 2019).
/**
* Offset from the top of the row for placing fields on inline input rows
* and statement input rows.
* Matches existing rendering (in 2019).
* @type {number}
*/
this.TALL_INPUT_FIELD_OFFSET_Y = this.MEDIUM_PADDING;
/**
* The height of the puzzle tab used for input and output connections.
* @type {number}
*/
this.TAB_HEIGHT = 15;
/**
* The offset from the top of the block at which a puzzle tab is positioned.
* @type {number}
*/
this.TAB_OFFSET_FROM_TOP = 5;
/**
* Vertical overlap of the puzzle tab, used to make it look more like a puzzle
* piece.
* @type {number}
*/
this.TAB_VERTICAL_OVERLAP = 2.5;
/**
* The width of the puzzle tab used for input and output connections.
* @type {number}
*/
this.TAB_WIDTH = 8;
/**
* The width of the notch used for previous and next connections.
* @type {number}
*/
this.NOTCH_WIDTH = 15;
/**
* The height of the notch used for previous and next connections.
* @type {number}
*/
this.NOTCH_HEIGHT = 4;
// This is the minimum width of a block measuring from the end of a rounded
// corner
/**
* The minimum width of the block.
* @type {number}
*/
this.MIN_BLOCK_WIDTH = 12;
this.EMPTY_BLOCK_SPACER_HEIGHT = 16;
@@ -66,35 +126,81 @@ Blockly.blockRendering.ConstantProvider = function() {
*/
this.DUMMY_INPUT_MIN_HEIGHT = this.TAB_HEIGHT;
/**
* The minimum height of a dummy input row in a shadow block.
* @type {number}
*/
this.DUMMY_INPUT_SHADOW_MIN_HEIGHT = this.TAB_HEIGHT;
/**
* Rounded corner radius.
* @type {number}
*/
this.CORNER_RADIUS = 8;
// Offset from the left side of a block or the inside of a statement input to
// the left side of the notch.
/**
* Offset from the left side of a block or the inside of a statement input to
* the left side of the notch.
* @type {number}
*/
this.NOTCH_OFFSET_LEFT = 15;
/**
* Additional offset added to the statement input's width to account for the
* notch.
* @type {number}
*/
this.STATEMENT_INPUT_NOTCH_OFFSET = this.NOTCH_OFFSET_LEFT;
this.STATEMENT_BOTTOM_SPACER = 0;
this.STATEMENT_INPUT_PADDING_LEFT = 20;
/**
* Vertical padding between consecutive statement inputs.
* @type {number}
*/
this.BETWEEN_STATEMENT_PADDING_Y = 4;
// This is the max width of a bottom row that follows a statement input and
// has inputs inline.
this.MAX_BOTTOM_WIDTH = 66.5;
/**
* The top row's minimum height.
* @type {number}
*/
this.TOP_ROW_MIN_HEIGHT = this.MEDIUM_PADDING;
/**
* The top row's minimum height if it precedes a statement.
* @type {number}
*/
this.TOP_ROW_PRECEDES_STATEMENT_MIN_HEIGHT = this.LARGE_PADDING;
/**
* The bottom row's minimum height.
* @type {number}
*/
this.BOTTOM_ROW_MIN_HEIGHT = this.MEDIUM_PADDING;
/**
* The bottom row's minimum height if it follows a statement input.
* @type {number}
*/
this.BOTTOM_ROW_AFTER_STATEMENT_MIN_HEIGHT = this.LARGE_PADDING;
/**
* Whether to add a 'hat' on top of all blocks with no previous or output
* connections. Can be overridden by 'hat' property on Theme.BlockStyle.
* @type {boolean}
*/
this.ADD_START_HATS = false;
/**
* Height of the top hat.
* @const
* @private
* @type {number}
*/
this.START_HAT_HEIGHT = 15;
/**
* Width of the top hat.
* @const
* @private
* @type {number}
*/
this.START_HAT_WIDTH = 100;
@@ -104,6 +210,10 @@ Blockly.blockRendering.ConstantProvider = function() {
this.EMPTY_INLINE_INPUT_PADDING = 14.5;
/**
* The height of an empty inline input.
* @type {number}
*/
this.EMPTY_INLINE_INPUT_HEIGHT = this.TAB_HEIGHT + 11;
this.EXTERNAL_VALUE_INPUT_PADDING = 2;
@@ -113,7 +223,6 @@ Blockly.blockRendering.ConstantProvider = function() {
* varies slightly depending on whether the block has external or inline inputs.
* In the new rendering this is consistent. It seems unlikely that the old
* behaviour was intentional.
* @const
* @type {number}
*/
this.EMPTY_STATEMENT_INPUT_HEIGHT = this.MIN_BLOCK_HEIGHT;
@@ -122,15 +231,290 @@ Blockly.blockRendering.ConstantProvider = function() {
/**
* Height of SVG path for jagged teeth at the end of collapsed blocks.
* @const
* @type {number}
*/
this.JAGGED_TEETH_HEIGHT = 12;
/**
* Width of SVG path for jagged teeth at the end of collapsed blocks.
* @const
* @type {number}
*/
this.JAGGED_TEETH_WIDTH = 6;
/**
* Point size of text.
* @type {number}
*/
this.FIELD_TEXT_FONTSIZE = 11;
/**
* Height of text.
* @type {number}
*/
this.FIELD_TEXT_HEIGHT = 16;
/**
* Text font weight.
* @type {string}
*/
this.FIELD_TEXT_FONTWEIGHT = 'normal';
/**
* Text font family.
* @type {string}
*/
this.FIELD_TEXT_FONTFAMILY = 'sans-serif';
/**
* A field's border rect corner radius.
* @type {number}
*/
this.FIELD_BORDER_RECT_RADIUS = 4;
/**
* A field's border rect default height.
* @type {number}
*/
this.FIELD_BORDER_RECT_HEIGHT = 16;
/**
* A field's border rect X padding.
* @type {number}
*/
this.FIELD_BORDER_RECT_X_PADDING = 5;
/**
* A field's border rect Y padding.
* @type {number}
*/
this.FIELD_BORDER_RECT_Y_PADDING = 3;
/**
* The backing colour of a field's border rect.
* @type {string}
* @package
*/
this.FIELD_BORDER_RECT_COLOUR = '#fff';
/**
* Field text baseline.
* This is only used if `FIELD_TEXT_BASELINE_CENTER` is false.
* @type {number}
*/
this.FIELD_TEXT_BASELINE_Y = Blockly.utils.userAgent.GECKO ? 12 : 13.09;
/**
* An text offset adjusting the Y position of text after positioning.
* @type {number}
*/
this.FIELD_TEXT_Y_OFFSET = 0;
/**
* A field's text element's dominant baseline.
* @type {boolean}
*/
this.FIELD_TEXT_BASELINE_CENTER =
!Blockly.utils.userAgent.IE && !Blockly.utils.userAgent.EDGE;
/**
* A dropdown field's border rect height.
* @type {number}
*/
this.FIELD_DROPDOWN_BORDER_RECT_HEIGHT = this.FIELD_BORDER_RECT_HEIGHT;
/**
* Whether or not a dropdown field should add a border rect when in a shadow
* block.
* @type {boolean}
*/
this.FIELD_DROPDOWN_NO_BORDER_RECT_SHADOW = false;
/**
* Whether or not a dropdown field's div should be coloured to match the
* block colours.
* @type {boolean}
*/
this.FIELD_DROPDOWN_COLOURED_DIV = false;
/**
* Whether or not a dropdown field uses a text or SVG arrow.
* @type {boolean}
*/
this.FIELD_DROPDOWN_SVG_ARROW = false;
/**
* A dropdown field's SVG arrow padding.
* @type {number}
*/
this.FIELD_DROPDOWN_SVG_ARROW_PADDING = this.FIELD_BORDER_RECT_X_PADDING;
/**
* A dropdown field's SVG arrow size.
* @type {number}
*/
this.FIELD_DROPDOWN_SVG_ARROW_SIZE = 12;
/**
* A dropdown field's SVG arrow datauri.
* @type {string}
*/
this.FIELD_DROPDOWN_SVG_ARROW_DATAURI =
'data:image/svg+xml;base64,PHN2ZyBpZD0iTGF5ZXJfMSIgZGF0YS1uYW1lPSJMYXllci' +
'AxIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMi43MSIgaG' +
'VpZ2h0PSI4Ljc5IiB2aWV3Qm94PSIwIDAgMTIuNzEgOC43OSI+PHRpdGxlPmRyb3Bkb3duLW' +
'Fycm93PC90aXRsZT48ZyBvcGFjaXR5PSIwLjEiPjxwYXRoIGQ9Ik0xMi43MSwyLjQ0QTIuND' +
'EsMi40MSwwLDAsMSwxMiw0LjE2TDguMDgsOC4wOGEyLjQ1LDIuNDUsMCwwLDEtMy40NSwwTD' +
'AuNzIsNC4xNkEyLjQyLDIuNDIsMCwwLDEsMCwyLjQ0LDIuNDgsMi40OCwwLDAsMSwuNzEuNz' +
'FDMSwwLjQ3LDEuNDMsMCw2LjM2LDBTMTEuNzUsMC40NiwxMiwuNzFBMi40NCwyLjQ0LDAsMC' +
'wxLDEyLjcxLDIuNDRaIiBmaWxsPSIjMjMxZjIwIi8+PC9nPjxwYXRoIGQ9Ik02LjM2LDcuNz' +
'lhMS40MywxLjQzLDAsMCwxLTEtLjQyTDEuNDIsMy40NWExLjQ0LDEuNDQsMCwwLDEsMC0yYz' +
'AuNTYtLjU2LDkuMzEtMC41Niw5Ljg3LDBhMS40NCwxLjQ0LDAsMCwxLDAsMkw3LjM3LDcuMz' +
'dBMS40MywxLjQzLDAsMCwxLDYuMzYsNy43OVoiIGZpbGw9IiNmZmYiLz48L3N2Zz4=';
/**
* Whether or not to show a box shadow around the widget div. This is only a
* feature of full block fields.
* @type {boolean}
*/
this.FIELD_TEXTINPUT_BOX_SHADOW = false;
/**
* Whether or not the colour field should display its colour value on the
* entire block.
* @type {boolean}
*/
this.FIELD_COLOUR_FULL_BLOCK = false;
/**
* A colour field's default width.
* @type {number}
*/
this.FIELD_COLOUR_DEFAULT_WIDTH = 26;
/**
* A colour field's default height.
* @type {number}
*/
this.FIELD_COLOUR_DEFAULT_HEIGHT = this.FIELD_BORDER_RECT_HEIGHT;
/**
* A checkbox field's X offset.
* @type {number}
*/
this.FIELD_CHECKBOX_X_OFFSET = this.FIELD_BORDER_RECT_X_PADDING - 3;
/**
* A checkbox field's Y offset.
* @type {number}
*/
this.FIELD_CHECKBOX_Y_OFFSET = 14;
/**
* A checkbox field's default width.
* @type {number}
*/
this.FIELD_CHECKBOX_DEFAULT_WIDTH = 15;
/**
* A random identifier used to ensure a unique ID is used for each
* filter/pattern for the case of multiple Blockly instances on a page.
* @type {string}
* @protected
*/
this.randomIdentifier_ = String(Math.random()).substring(2);
/**
* The ID of the emboss filter, or the empty string if no filter is set.
* @type {string}
* @package
*/
this.embossFilterId = '';
/**
* The <filter> element to use for highlighting, or null if not set.
* @type {SVGElement}
* @private
*/
this.embossFilter_ = null;
/**
* The ID of the disabled pattern, or the empty string if no pattern is set.
* @type {string}
* @package
*/
this.disabledPatternId = '';
/**
* The <pattern> element to use for disabled blocks, or null if not set.
* @type {SVGElement}
* @private
*/
this.disabledPattern_ = null;
/**
* Cursor colour.
* @type {string}
* @package
*/
this.CURSOR_COLOUR = '#cc0a0a';
/**
* Immovable marker colour.
* @type {string}
* @package
*/
this.MARKER_COLOUR = '#4286f4';
/**
* Width of the horizontal cursor.
* @type {number}
* @package
*/
this.CURSOR_WS_WIDTH = 100;
/**
* Height of the horizontal cursor.
* @type {number}
* @package
*/
this.WS_CURSOR_HEIGHT = 5;
/**
* Padding around a stack.
* @type {number}
* @package
*/
this.CURSOR_STACK_PADDING = 10;
/**
* Padding around a block.
* @type {number}
* @package
*/
this.CURSOR_BLOCK_PADDING = 2;
/**
* Stroke of the cursor.
* @type {number}
* @package
*/
this.CURSOR_STROKE_WIDTH = 4;
/**
* Whether text input and colour fields fill up the entire source block.
* @type {boolean}
* @package
*/
this.FULL_BLOCK_FIELDS = false;
/**
* Enum for connection shapes.
* @enum {number}
*/
this.SHAPES = {
PUZZLE: 1,
NOTCH: 2
};
};
/**
@@ -177,6 +561,147 @@ Blockly.blockRendering.ConstantProvider.prototype.init = function() {
this.OUTSIDE_CORNERS = this.makeOutsideCorners();
};
/**
* Refresh constants properties that depend on the theme.
* @param {!Blockly.Theme} theme The current workspace theme.
* @package
*/
Blockly.blockRendering.ConstantProvider.prototype.refreshTheme = function(
theme) {
/**
* The block styles map.
* @type {Object.<string, Blockly.Theme.BlockStyle>}
* @package
*/
this.blockStyles = {};
var blockStyles = theme.blockStyles;
for (var key in blockStyles) {
this.blockStyles[key] = this.validatedBlockStyle_(blockStyles[key]);
}
};
/**
* Get or create a block style based on a single colour value. Generate a name
* for the style based on the colour.
* @param {string} colour #RRGGBB colour string.
* @return {{style: !Blockly.Theme.BlockStyle, name: string}} An object
* containing the style and an autogenerated name for that style.
* @package
*/
Blockly.blockRendering.ConstantProvider.prototype.getBlockStyleForColour =
function(colour) {
/* eslint-disable indent */
var name = 'auto_' + colour;
if (!this.blockStyles[name]) {
this.blockStyles[name] = this.createBlockStyle_(colour);
}
return {style: this.blockStyles[name], name: name};
}; /* eslint-enable indent */
/**
* Gets the BlockStyle for the given block style name.
* @param {?string} blockStyleName The name of the block style.
* @return {!Blockly.Theme.BlockStyle} The named block style, or a default style
* if no style with the given name was found.
*/
Blockly.blockRendering.ConstantProvider.prototype.getBlockStyle = function(
blockStyleName) {
return this.blockStyles[blockStyleName || ''] ||
this.createBlockStyle_('#000000');
};
/**
* Create a block style object based on the given colour.
* @param {string} colour #RRGGBB colour string.
* @return {!Blockly.Theme.BlockStyle} A populated block style based on the
* given colour.
* @protected
*/
Blockly.blockRendering.ConstantProvider.prototype.createBlockStyle_ = function(
colour) {
return this.validatedBlockStyle_({
'colourPrimary': colour
});
};
/**
* Get a full block style object based on the input style object. Populate
* any missing values.
* @param {{
* colourPrimary:string,
* colourSecondary:(string|undefined),
* colourTertiary:(string|undefined),
* hat:(string|undefined)
* }} blockStyle A full or partial block style object.
* @return {!Blockly.Theme.BlockStyle} A full block style object, with all
* required properties populated.
* @protected
*/
Blockly.blockRendering.ConstantProvider.prototype.validatedBlockStyle_ =
function(blockStyle) {
/* eslint-disable indent */
// Make a new object with all of the same properties.
var valid = /** @type {!Blockly.Theme.BlockStyle} */ ({});
if (blockStyle) {
Blockly.utils.object.mixin(valid, blockStyle);
}
// Validate required properties.
var parsedColour = Blockly.utils.parseBlockColour(
valid['colourPrimary'] || '#000');
valid.colourPrimary = parsedColour.hex;
valid.colourSecondary = valid['colourSecondary'] ?
Blockly.utils.parseBlockColour(valid['colourSecondary']).hex :
this.generateSecondaryColour_(valid.colourPrimary);
valid.colourTertiary = valid['colourTertiary'] ?
Blockly.utils.parseBlockColour(valid['colourTertiary']).hex :
this.generateTertiaryColour_(valid.colourPrimary);
valid.hat = valid['hat'] || '';
return valid;
}; /* eslint-enable indent */
/**
* Generate a secondary colour from the passed in primary colour.
* @param {string} colour Primary colour.
* @return {string} The generated secondary colour.
* @protected
*/
Blockly.blockRendering.ConstantProvider.prototype.generateSecondaryColour_ =
function(colour) {
/* eslint-disable indent */
return Blockly.utils.colour.blend('#fff', colour, 0.6) || colour;
}; /* eslint-enable indent */
/**
* Generate a tertiary colour from the passed in primary colour.
* @param {string} colour Primary colour.
* @return {string} The generated tertiary colour.
* @protected
*/
Blockly.blockRendering.ConstantProvider.prototype.generateTertiaryColour_ =
function(colour) {
/* eslint-disable indent */
return Blockly.utils.colour.blend('#fff', colour, 0.3) || colour;
}; /* eslint-enable indent */
/**
* Dispose of this constants provider.
* Delete all DOM elements that this provider created.
* @package
*/
Blockly.blockRendering.ConstantProvider.prototype.dispose = function() {
if (this.embossFilter_) {
Blockly.utils.dom.removeNode(this.embossFilter_);
}
if (this.disabledPattern_) {
Blockly.utils.dom.removeNode(this.disabledPattern_);
}
};
/**
* @return {!Object} An object containing sizing and path information about
* collapsed block indicators.
@@ -189,9 +714,9 @@ Blockly.blockRendering.ConstantProvider.prototype.makeJaggedTeeth = function() {
var mainPath =
Blockly.utils.svgPaths.line(
[
Blockly.utils.svgPaths.point(6, 3),
Blockly.utils.svgPaths.point(-12, 6),
Blockly.utils.svgPaths.point(6, 3)
Blockly.utils.svgPaths.point(width, height / 4),
Blockly.utils.svgPaths.point(-width * 2, height / 2),
Blockly.utils.svgPaths.point(width, height / 4)
]);
return {
height: height,
@@ -269,6 +794,7 @@ Blockly.blockRendering.ConstantProvider.prototype.makePuzzleTab = function() {
var pathDown = makeMainPath(false);
return {
type: this.SHAPES.PUZZLE,
width: width,
height: height,
pathDown: pathDown,
@@ -294,11 +820,11 @@ Blockly.blockRendering.ConstantProvider.prototype.makeNotch = function() {
Blockly.utils.svgPaths.point(dir * outerWidth, -height)
]);
}
// TODO: Find a relationship between width and path
var pathLeft = makeMainPath(1);
var pathRight = makeMainPath(-1);
return {
type: this.SHAPES.NOTCH,
width: width,
height: height,
pathLeft: pathLeft,
@@ -344,12 +870,34 @@ Blockly.blockRendering.ConstantProvider.prototype.makeOutsideCorners = function(
Blockly.utils.svgPaths.arc('a', '0 0,1', radius,
Blockly.utils.svgPaths.point(radius, -radius));
/**
* SVG path for drawing the rounded top-right corner.
* @const
*/
var topRight =
Blockly.utils.svgPaths.arc('a', '0 0,1', radius,
Blockly.utils.svgPaths.point(radius, radius));
/**
* SVG path for drawing the rounded bottom-left corner.
* @const
*/
var bottomLeft = Blockly.utils.svgPaths.arc('a', '0 0,1', radius,
Blockly.utils.svgPaths.point(-radius, -radius));
/**
* SVG path for drawing the rounded bottom-right corner.
* @const
*/
var bottomRight = Blockly.utils.svgPaths.arc('a', '0 0,1', radius,
Blockly.utils.svgPaths.point(-radius, radius));
return {
topLeft: topLeft,
bottomLeft: bottomLeft
topRight: topRight,
bottomRight: bottomRight,
bottomLeft: bottomLeft,
rightHeight: radius
};
};
@@ -374,3 +922,172 @@ Blockly.blockRendering.ConstantProvider.prototype.shapeFor = function(
throw Error('Unknown connection type');
}
};
/**
* Create any DOM elements that this renderer needs (filters, patterns, etc).
* @param {!SVGElement} svg The root of the workspace's SVG.
* @package
*/
Blockly.blockRendering.ConstantProvider.prototype.createDom = function(svg) {
/*
<defs>
... filters go here ...
</defs>
*/
var defs = Blockly.utils.dom.createSvgElement('defs', {}, svg);
/*
<filter id="blocklyEmbossFilter837493">
<feGaussianBlur in="SourceAlpha" stdDeviation="1" result="blur" />
<feSpecularLighting in="blur" surfaceScale="1" specularConstant="0.5"
specularExponent="10" lighting-color="white"
result="specOut">
<fePointLight x="-5000" y="-10000" z="20000" />
</feSpecularLighting>
<feComposite in="specOut" in2="SourceAlpha" operator="in"
result="specOut" />
<feComposite in="SourceGraphic" in2="specOut" operator="arithmetic"
k1="0" k2="1" k3="1" k4="0" />
</filter>
*/
var embossFilter = Blockly.utils.dom.createSvgElement('filter',
{'id': 'blocklyEmbossFilter' + this.randomIdentifier_}, defs);
Blockly.utils.dom.createSvgElement('feGaussianBlur',
{'in': 'SourceAlpha', 'stdDeviation': 1, 'result': 'blur'}, embossFilter);
var feSpecularLighting = Blockly.utils.dom.createSvgElement('feSpecularLighting',
{
'in': 'blur',
'surfaceScale': 1,
'specularConstant': 0.5,
'specularExponent': 10,
'lighting-color': 'white',
'result': 'specOut'
},
embossFilter);
Blockly.utils.dom.createSvgElement('fePointLight',
{'x': -5000, 'y': -10000, 'z': 20000}, feSpecularLighting);
Blockly.utils.dom.createSvgElement('feComposite',
{
'in': 'specOut',
'in2': 'SourceAlpha',
'operator': 'in',
'result': 'specOut'
}, embossFilter);
Blockly.utils.dom.createSvgElement('feComposite',
{
'in': 'SourceGraphic',
'in2': 'specOut',
'operator': 'arithmetic',
'k1': 0,
'k2': 1,
'k3': 1,
'k4': 0
}, embossFilter);
this.embossFilterId = embossFilter.id;
this.embossFilter_ = embossFilter;
/*
<pattern id="blocklyDisabledPattern837493" patternUnits="userSpaceOnUse"
width="10" height="10">
<rect width="10" height="10" fill="#aaa" />
<path d="M 0 0 L 10 10 M 10 0 L 0 10" stroke="#cc0" />
</pattern>
*/
var disabledPattern = Blockly.utils.dom.createSvgElement('pattern',
{
'id': 'blocklyDisabledPattern' + this.randomIdentifier_,
'patternUnits': 'userSpaceOnUse',
'width': 10,
'height': 10
}, defs);
Blockly.utils.dom.createSvgElement('rect',
{'width': 10, 'height': 10, 'fill': '#aaa'}, disabledPattern);
Blockly.utils.dom.createSvgElement('path',
{'d': 'M 0 0 L 10 10 M 10 0 L 0 10', 'stroke': '#cc0'}, disabledPattern);
this.disabledPatternId = disabledPattern.id;
this.disabledPattern_ = disabledPattern;
};
/**
* Inject renderer specific CSS into the page.
* @param {string} name Name of the renderer.
* @package
*/
Blockly.blockRendering.ConstantProvider.prototype.injectCSS = function(
name) {
var cssArray = this.getCSS_(name);
var cssNodeId = 'blockly-renderer-style-' + name;
if (document.getElementById(cssNodeId)) {
// Already injected.
return;
}
var text = cssArray.join('\n');
// Inject CSS tag at start of head.
var cssNode = document.createElement('style');
cssNode.id = cssNodeId;
var cssTextNode = document.createTextNode(text);
cssNode.appendChild(cssTextNode);
document.head.insertBefore(cssNode, document.head.firstChild);
};
/**
* Get any renderer specific CSS to inject when the renderer is initialized.
* @param {string} name Name of the renderer.
* @return {!Array.<string>} Array of CSS strings.
* @protected
*/
Blockly.blockRendering.ConstantProvider.prototype.getCSS_ = function(name) {
var selector = '.' + name + '-renderer';
return [
/* eslint-disable indent */
// Fields.
selector + ' .blocklyText {',
'fill: #fff;',
'font-family: ' + this.FIELD_TEXT_FONTFAMILY + ';',
'font-size: ' + this.FIELD_TEXT_FONTSIZE + 'pt;',
'font-weight: ' + this.FIELD_TEXT_FONTWEIGHT + ';',
'}',
selector + ' .blocklyNonEditableText>rect,',
selector + ' .blocklyEditableText>rect {',
'fill: ' + this.FIELD_BORDER_RECT_COLOUR + ';',
'fill-opacity: .6;',
'stroke: none;',
'}',
selector + ' .blocklyNonEditableText>text,',
selector + ' .blocklyEditableText>text {',
'fill: #000;',
'}',
// Editable field hover.
selector + ' .blocklyEditableText:not(.editing):hover>rect {',
'stroke: #fff;',
'stroke-width: 2;',
'}',
// Text field input.
selector + ' .blocklyHtmlInput {',
'font-family: ' + this.FIELD_TEXT_FONTFAMILY + ';',
'font-weight: ' + this.FIELD_TEXT_FONTWEIGHT + ';',
'}',
// Selection highlight.
selector + ' .blocklySelected>.blocklyPath {',
'stroke: #fc3;',
'stroke-width: 3px;',
'}',
// Connection highlight.
selector + ' .blocklyHighlightedConnectionPath {',
'stroke: #fc3;',
'}',
// Replacable highlight.
selector + ' .blocklyReplaceable .blocklyPath {',
'fill-opacity: .5;',
'}',
selector + ' .blocklyReplaceable .blocklyPathLight,',
selector + ' .blocklyReplaceable .blocklyPathDark {',
'display: none;',
'}',
/* eslint-enable indent */
];
};

View File

@@ -42,6 +42,7 @@ Blockly.blockRendering.Debug = function() {
/**
* An array of SVG elements that have been created by this object.
* @type {Array.<!SVGElement>}
* @private
*/
this.debugElements_ = [];
@@ -49,6 +50,7 @@ Blockly.blockRendering.Debug = function() {
* The SVG root of the block that is being rendered. Debug elements will
* be attached to this root.
* @type {SVGElement}
* @private
*/
this.svgRoot_ = null;
};
@@ -92,14 +94,20 @@ Blockly.blockRendering.Debug.prototype.drawSpacerRow = function(row, cursorY, is
return;
}
var height = Math.abs(row.height);
var isNegativeSpacing = row.height < 0;
if (isNegativeSpacing) {
cursorY -= height;
}
this.debugElements_.push(Blockly.utils.dom.createSvgElement('rect',
{
'class': 'rowSpacerRect blockRenderDebug',
'x': isRtl ? -(row.xPos + row.width) : row.xPos,
'y': cursorY,
'width': row.width,
'height': row.height,
'stroke': 'blue',
'height': height,
'stroke': isNegativeSpacing ? 'black' : 'blue',
'fill': 'blue',
'fill-opacity': '0.5',
'stroke-width': '1px'
@@ -119,9 +127,11 @@ Blockly.blockRendering.Debug.prototype.drawSpacerElem = function(elem, rowHeight
return;
}
var xPos = elem.xPos;
var width = Math.abs(elem.width);
var isNegativeSpacing = elem.width < 0;
var xPos = isNegativeSpacing ? elem.xPos - width : elem.xPos;
if (isRtl) {
xPos = -(xPos + elem.width);
xPos = -(xPos + width);
}
var yPos = elem.centerline - elem.height / 2;
this.debugElements_.push(Blockly.utils.dom.createSvgElement('rect',
@@ -129,10 +139,10 @@ Blockly.blockRendering.Debug.prototype.drawSpacerElem = function(elem, rowHeight
'class': 'elemSpacerRect blockRenderDebug',
'x': xPos,
'y': yPos,
'width': elem.width,
'width': width,
'height': elem.height,
'stroke': 'pink',
'fill': 'pink',
'fill': isNegativeSpacing ? 'black' : 'pink',
'fill-opacity': '0.5',
'stroke-width': '1px'
},
@@ -169,15 +179,17 @@ Blockly.blockRendering.Debug.prototype.drawRenderedElem = function(elem, isRtl)
if (Blockly.blockRendering.Types.isInput(elem) &&
Blockly.blockRendering.Debug.config.connections) {
this.drawConnection(elem.connection);
this.drawConnection(elem.connectionModel);
}
};
/**
* Draw a circle at the location of the given connection. Inputs and outputs
* share the same colors, as do previous and next. When positioned correctly
* share the same colours, as do previous and next. When positioned correctly
* a connected pair will look like a bullseye.
* @param {Blockly.RenderedConnection} conn The connection to circle.
* @suppress {visibility} Suppress visibility of conn.offsetInBlock_ since this
* is a debug module.
* @package
*/
Blockly.blockRendering.Debug.prototype.drawConnection = function(conn) {
@@ -270,10 +282,15 @@ Blockly.blockRendering.Debug.prototype.drawRenderedRow = function(row, cursorY,
* @package
*/
Blockly.blockRendering.Debug.prototype.drawRowWithElements = function(row, cursorY, isRtl) {
for (var i = 0, elem; (elem = row.elements[i]); i++) {
for (var i = 0, l = row.elements.length; i < l; i++) {
var elem = row.elements[i];
if (!elem) {
console.warn('A row has an undefined or null element.', row, elem);
continue;
}
if (Blockly.blockRendering.Types.isSpacer(elem)) {
this.drawSpacerElem(
/** @type {Blockly.blockRendering.InRowSpacer} */ (elem),
/** @type {!Blockly.blockRendering.InRowSpacer} */ (elem),
row.height, isRtl);
} else {
this.drawRenderedElem(elem, isRtl);
@@ -360,6 +377,9 @@ Blockly.blockRendering.Debug.prototype.drawDebug = function(block, info) {
if (block.outputConnection) {
this.drawConnection(block.outputConnection);
}
if (info.rightSide) {
this.drawRenderedElem(info.rightSide, info.RTL);
}
this.drawBoundingBox(info);
};

View File

@@ -72,7 +72,7 @@ Blockly.blockRendering.Drawer.prototype.draw = function() {
this.drawOutline_();
this.drawInternals_();
this.block_.pathObject.setPaths(this.outlinePath_ + '\n' + this.inlinePath_);
this.block_.pathObject.setPath(this.outlinePath_ + '\n' + this.inlinePath_);
if (this.info_.RTL) {
this.block_.pathObject.flipRTL();
}
@@ -144,6 +144,9 @@ Blockly.blockRendering.Drawer.prototype.drawTop_ = function() {
if (Blockly.blockRendering.Types.isLeftRoundedCorner(elem)) {
this.outlinePath_ +=
this.constants_.OUTSIDE_CORNERS.topLeft;
} else if (Blockly.blockRendering.Types.isRightRoundedCorner(elem)) {
this.outlinePath_ +=
this.constants_.OUTSIDE_CORNERS.topRight;
} else if (Blockly.blockRendering.Types.isPreviousConnection(elem)) {
this.outlinePath_ += elem.shape.pathLeft;
} else if (Blockly.blockRendering.Types.isHat(elem)) {
@@ -242,20 +245,26 @@ Blockly.blockRendering.Drawer.prototype.drawBottom_ = function() {
var elems = bottomRow.elements;
this.positionNextConnection_();
this.outlinePath_ +=
Blockly.utils.svgPaths.lineOnAxis('V', bottomRow.baseline);
var rightCornerYOffset = 0;
var outlinePath = '';
for (var i = elems.length - 1, elem; (elem = elems[i]); i--) {
if (Blockly.blockRendering.Types.isNextConnection(elem)) {
this.outlinePath_ += elem.shape.pathRight;
outlinePath += elem.shape.pathRight;
} else if (Blockly.blockRendering.Types.isLeftSquareCorner(elem)) {
this.outlinePath_ += Blockly.utils.svgPaths.lineOnAxis('H', bottomRow.xPos);
outlinePath += Blockly.utils.svgPaths.lineOnAxis('H', bottomRow.xPos);
} else if (Blockly.blockRendering.Types.isLeftRoundedCorner(elem)) {
this.outlinePath_ += this.constants_.OUTSIDE_CORNERS.bottomLeft;
outlinePath += this.constants_.OUTSIDE_CORNERS.bottomLeft;
} else if (Blockly.blockRendering.Types.isRightRoundedCorner(elem)) {
outlinePath += this.constants_.OUTSIDE_CORNERS.bottomRight;
rightCornerYOffset = this.constants_.OUTSIDE_CORNERS.rightHeight;
} else if (Blockly.blockRendering.Types.isSpacer(elem)) {
this.outlinePath_ += Blockly.utils.svgPaths.lineOnAxis('h', elem.width * -1);
outlinePath += Blockly.utils.svgPaths.lineOnAxis('h', elem.width * -1);
}
}
this.outlinePath_ += Blockly.utils.svgPaths.lineOnAxis('V',
bottomRow.baseline - rightCornerYOffset);
this.outlinePath_ += outlinePath;
};
/**
@@ -381,13 +390,14 @@ Blockly.blockRendering.Drawer.prototype.drawInlineInput_ = function(input) {
Blockly.blockRendering.Drawer.prototype.positionInlineInputConnection_ = function(input) {
var yPos = input.centerline - input.height / 2;
// Move the connection.
if (input.connection) {
if (input.connectionModel) {
// xPos already contains info about startX
var connX = input.xPos + input.connectionWidth;
var connX = input.xPos + input.connectionWidth + input.connectionOffsetX;
if (this.info_.RTL) {
connX *= -1;
}
input.connection.setOffsetInBlock(connX, yPos + input.connectionOffsetY);
input.connectionModel.setOffsetInBlock(connX,
yPos + input.connectionOffsetY);
}
};
@@ -400,12 +410,12 @@ Blockly.blockRendering.Drawer.prototype.positionInlineInputConnection_ = functio
*/
Blockly.blockRendering.Drawer.prototype.positionStatementInputConnection_ = function(row) {
var input = row.getLastInput();
if (input.connection) {
if (input.connectionModel) {
var connX = row.xPos + row.statementEdge + input.notchOffset;
if (this.info_.RTL) {
connX *= -1;
}
input.connection.setOffsetInBlock(connX, row.yPos);
input.connectionModel.setOffsetInBlock(connX, row.yPos);
}
};
@@ -418,12 +428,12 @@ Blockly.blockRendering.Drawer.prototype.positionStatementInputConnection_ = func
*/
Blockly.blockRendering.Drawer.prototype.positionExternalValueConnection_ = function(row) {
var input = row.getLastInput();
if (input.connection) {
if (input.connectionModel) {
var connX = row.xPos + row.width;
if (this.info_.RTL) {
connX *= -1;
}
input.connection.setOffsetInBlock(connX, row.yPos);
input.connectionModel.setOffsetInBlock(connX, row.yPos);
}
};
@@ -451,8 +461,7 @@ Blockly.blockRendering.Drawer.prototype.positionNextConnection_ = function() {
var connInfo = bottomRow.connection;
var x = connInfo.xPos; // Already contains info about startX
var connX = (this.info_.RTL ? -x : x);
connInfo.connectionModel.setOffsetInBlock(
connX, (connInfo.centerline - connInfo.height / 2));
connInfo.connectionModel.setOffsetInBlock(connX, bottomRow.baseline);
}
};
@@ -462,7 +471,7 @@ Blockly.blockRendering.Drawer.prototype.positionNextConnection_ = function() {
*/
Blockly.blockRendering.Drawer.prototype.positionOutputConnection_ = function() {
if (this.info_.outputConnection) {
var x = this.info_.startX;
var x = this.info_.startX + this.info_.outputConnection.connectionOffsetX;
var connX = this.info_.RTL ? -x : x;
this.block_.outputConnection.setOffsetInBlock(connX,
this.info_.outputConnection.connectionOffsetY);

View File

@@ -0,0 +1,130 @@
/**
* @license
* Copyright 2019 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @fileoverview The interface for an object that owns a block's rendering SVG
* elements.
* @author fenichel@google.com (Rachel Fenichel)
*/
'use strict';
goog.provide('Blockly.blockRendering.IPathObject');
goog.requireType('Blockly.blockRendering.ConstantProvider');
goog.requireType('Blockly.Theme');
/**
* An interface for a block's path object.
* @param {!SVGElement} _root The root SVG element.
* @param {!Blockly.blockRendering.ConstantProvider} _constants The renderer's
* constants.
* @interface
*/
Blockly.blockRendering.IPathObject = function(_root, _constants) {};
/**
* Set the path generated by the renderer onto the respective SVG element.
* @param {string} pathString The path.
* @package
*/
Blockly.blockRendering.IPathObject.prototype.setPath;
/**
* Apply the stored colours to the block's path, taking into account whether
* the paths belong to a shadow block.
* @param {!Blockly.Block} block The source block.
* @package
*/
Blockly.blockRendering.IPathObject.prototype.applyColour;
/**
* Update the style.
* @param {!Blockly.Theme.BlockStyle} blockStyle The block style to use.
* @package
*/
Blockly.blockRendering.IPathObject.prototype.setStyle;
/**
* Flip the SVG paths in RTL.
* @package
*/
Blockly.blockRendering.IPathObject.prototype.flipRTL;
/**
* Add the cursor svg to this block's svg group.
* @param {SVGElement} cursorSvg The svg root of the cursor to be added to the
* block svg group.
* @package
*/
Blockly.blockRendering.IPathObject.prototype.setCursorSvg;
/**
* Add the marker svg to this block's svg group.
* @param {SVGElement} markerSvg The svg root of the marker to be added to the
* block svg group.
* @package
*/
Blockly.blockRendering.IPathObject.prototype.setMarkerSvg;
/**
* Set whether the block shows a highlight or not. Block highlighting is
* often used to visually mark blocks currently being executed.
* @param {boolean} highlighted True if highlighted.
* @package
*/
Blockly.blockRendering.IPathObject.prototype.updateHighlighted;
/**
* Add or remove styling showing that a block is selected.
* @param {boolean} enable True if selection is enabled, false otherwise.
* @package
*/
Blockly.blockRendering.IPathObject.prototype.updateSelected;
/**
* Add or remove styling showing that a block is dragged over a delete area.
* @param {boolean} enable True if the block is being dragged over a delete
* area, false otherwise.
* @package
*/
Blockly.blockRendering.IPathObject.prototype.updateDraggingDelete;
/**
* Add or remove styling showing that a block is an insertion marker.
* @param {boolean} enable True if the block is an insertion marker, false
* otherwise.
* @package
*/
Blockly.blockRendering.IPathObject.prototype.updateInsertionMarker;
/**
* Add or remove styling showing that a block is movable.
* @param {boolean} enable True if the block is movable, false otherwise.
* @package
*/
Blockly.blockRendering.IPathObject.prototype.updateMovable;
/**
* Add or remove styling that shows that if the dragging block is dropped, this
* block will be replaced. If a shadow block, it will disappear. Otherwise it
* will bump.
* @param {boolean} enable True if styling should be added.
* @package
*/
Blockly.blockRendering.IPathObject.prototype.updateReplacementHighlight;

View File

@@ -138,6 +138,13 @@ Blockly.blockRendering.RenderInfo = function(renderer, block) {
*/
this.rows = [];
/**
* The total number of input rows added onto the block.
* @type {number}
* @protected
*/
this.inputRowNum_ = 1;
/**
* An array of measurable objects containing hidden icons.
* @type {!Array.<!Blockly.blockRendering.Icon>}
@@ -184,9 +191,9 @@ Blockly.blockRendering.RenderInfo.prototype.getRenderer = function() {
Blockly.blockRendering.RenderInfo.prototype.measure = function() {
this.createRows_();
this.addElemSpacing_();
this.addRowSpacing_();
this.computeBounds_();
this.alignRowElements_();
this.addRowSpacing_();
this.finalize_();
};
@@ -224,6 +231,7 @@ Blockly.blockRendering.RenderInfo.prototype.createRows_ = function() {
// Finish this row and create a new one.
this.rows.push(activeRow);
activeRow = new Blockly.blockRendering.InputRow(this.constants_);
this.inputRowNum_ ++;
}
// All of the fields in an input go on the same row.
@@ -254,8 +262,8 @@ Blockly.blockRendering.RenderInfo.prototype.createRows_ = function() {
*/
Blockly.blockRendering.RenderInfo.prototype.populateTopRow_ = function() {
var hasPrevious = !!this.block_.previousConnection;
var hasHat = (this.block_.hat ?
this.block_.hat === 'cap' : Blockly.BlockSvg.START_HAT) &&
var hasHat = (typeof this.block_.hat !== 'undefined' ?
this.block_.hat === 'cap' : this.constants_.ADD_START_HATS) &&
!this.outputConnection && !hasPrevious;
var leftSquareCorner = this.topRow.hasLeftSquareCorner(this.block_);
@@ -286,9 +294,20 @@ Blockly.blockRendering.RenderInfo.prototype.populateTopRow_ = function() {
// This is the minimum height for the row. If one of its elements has a
// greater height it will be overwritten in the compute pass.
if (precedesStatement && !this.block_.isCollapsed()) {
this.topRow.minHeight = this.constants_.LARGE_PADDING;
this.topRow.minHeight =
this.constants_.TOP_ROW_PRECEDES_STATEMENT_MIN_HEIGHT;
} else {
this.topRow.minHeight = this.constants_.MEDIUM_PADDING;
this.topRow.minHeight = this.constants_.TOP_ROW_MIN_HEIGHT;
}
var rightSquareCorner = this.topRow.hasRightSquareCorner(this.block_);
if (rightSquareCorner) {
this.topRow.elements.push(
new Blockly.blockRendering.SquareCorner(this.constants_, 'right'));
} else {
this.topRow.elements.push(
new Blockly.blockRendering.RoundCorner(this.constants_, 'right'));
}
};
@@ -307,9 +326,10 @@ Blockly.blockRendering.RenderInfo.prototype.populateBottomRow_ = function() {
// This is the minimum height for the row. If one of its elements has a
// greater height it will be overwritten in the compute pass.
if (followsStatement) {
this.bottomRow.minHeight = this.constants_.LARGE_PADDING;
this.bottomRow.minHeight =
this.constants_.BOTTOM_ROW_AFTER_STATEMENT_MIN_HEIGHT;
} else {
this.bottomRow.minHeight = this.constants_.MEDIUM_PADDING - 1;
this.bottomRow.minHeight = this.constants_.BOTTOM_ROW_MIN_HEIGHT;
}
var leftSquareCorner = this.bottomRow.hasLeftSquareCorner(this.block_);
@@ -328,6 +348,16 @@ Blockly.blockRendering.RenderInfo.prototype.populateBottomRow_ = function() {
/** @type {Blockly.RenderedConnection} */ (this.block_.nextConnection));
this.bottomRow.elements.push(this.bottomRow.connection);
}
var rightSquareCorner = this.bottomRow.hasRightSquareCorner(this.block_);
if (rightSquareCorner) {
this.bottomRow.elements.push(
new Blockly.blockRendering.SquareCorner(this.constants_, 'right'));
} else {
this.bottomRow.elements.push(
new Blockly.blockRendering.RoundCorner(this.constants_, 'right'));
}
};
/**
@@ -356,10 +386,14 @@ Blockly.blockRendering.RenderInfo.prototype.addInput_ = function(input, activeRo
// Dummy inputs have no visual representation, but the information is still
// important.
activeRow.minHeight = Math.max(activeRow.minHeight,
input.getSourceBlock() && input.getSourceBlock().isShadow() ?
this.constants_.DUMMY_INPUT_SHADOW_MIN_HEIGHT :
this.constants_.DUMMY_INPUT_MIN_HEIGHT);
activeRow.hasDummyInput = true;
}
activeRow.align = input.align;
if (activeRow.align == null) {
activeRow.align = input.align;
}
};
/**
@@ -401,6 +435,9 @@ Blockly.blockRendering.RenderInfo.prototype.addElemSpacing_ = function() {
row.elements.push(new Blockly.blockRendering.InRowSpacer(
this.constants_, this.getInRowSpacing_(null, oldElems[0])));
}
if (!oldElems.length) {
continue;
}
for (var e = 0; e < oldElems.length - 1; e++) {
row.elements.push(oldElems[e]);
var spacing = this.getInRowSpacing_(oldElems[e], oldElems[e + 1]);
@@ -428,6 +465,12 @@ Blockly.blockRendering.RenderInfo.prototype.addElemSpacing_ = function() {
* @protected
*/
Blockly.blockRendering.RenderInfo.prototype.getInRowSpacing_ = function(prev, next) {
if (!prev) {
// Statement input padding.
if (next && Blockly.blockRendering.Types.isStatementInput(next)) {
return this.constants_.STATEMENT_INPUT_PADDING_LEFT;
}
}
// Between inputs and the end of the row.
if (prev && Blockly.blockRendering.Types.isInput(prev) && !next) {
if (Blockly.blockRendering.Types.isExternalInput(prev)) {
@@ -480,9 +523,7 @@ Blockly.blockRendering.RenderInfo.prototype.computeBounds_ = function() {
Math.max(widestRowWithConnectedBlocks, row.widthWithConnectedBlocks);
}
this.statementEdge = widestStatementRowFields;
this.width = blockWidth;
for (var i = 0, row; (row = this.rows[i]); i++) {
@@ -513,15 +554,26 @@ Blockly.blockRendering.RenderInfo.prototype.alignRowElements_ = function() {
/** @type {!Blockly.blockRendering.InputRow} */ (row));
} else {
var currentWidth = row.width;
var desiredWidth = this.width - this.startX;
var desiredWidth = this.getDesiredRowWidth_(row);
var missingSpace = desiredWidth - currentWidth;
if (missingSpace) {
if (missingSpace > 0) {
this.addAlignmentPadding_(row, missingSpace);
}
}
}
};
/**
* Calculate the desired width of an input row.
* @param {!Blockly.blockRendering.Row} _row The input row.
* @return {number} The desired width of the input row.
* @protected
*/
Blockly.blockRendering.RenderInfo.prototype.getDesiredRowWidth_ = function(
_row) {
return this.width - this.startX;
};
/**
* Modify the given row to add the given amount of padding around its fields.
* The exact location of the padding is based on the alignment property of the
@@ -532,11 +584,28 @@ Blockly.blockRendering.RenderInfo.prototype.alignRowElements_ = function() {
*/
Blockly.blockRendering.RenderInfo.prototype.addAlignmentPadding_ = function(row,
missingSpace) {
var firstSpacer = row.getFirstSpacer();
var lastSpacer = row.getLastSpacer();
if (lastSpacer) {
lastSpacer.width += missingSpace;
row.width += missingSpace;
if (row.hasExternalInput || row.hasStatement) {
row.widthWithConnectedBlocks += missingSpace;
}
// Decide where the extra padding goes.
if (row.align == Blockly.ALIGN_LEFT) {
// Add padding to the end of the row.
lastSpacer.width += missingSpace;
} else if (row.align == Blockly.ALIGN_CENTRE) {
// Split the padding between the beginning and end of the row.
firstSpacer.width += missingSpace / 2;
lastSpacer.width += missingSpace / 2;
} else if (row.align == Blockly.ALIGN_RIGHT) {
// Add padding at the beginning of the row.
firstSpacer.width += missingSpace;
} else {
// Default to left-aligning.
lastSpacer.width += missingSpace;
}
row.width += missingSpace;
};
/**
@@ -551,15 +620,15 @@ Blockly.blockRendering.RenderInfo.prototype.alignStatementRow_ = function(row) {
var desiredWidth = this.statementEdge;
// Add padding before the statement input.
var missingSpace = desiredWidth - currentWidth;
if (missingSpace) {
if (missingSpace > 0) {
this.addAlignmentPadding_(row, missingSpace);
}
// Also widen the statement input to reach to the right side of the
// block. Note that this does not add padding.
currentWidth = row.width;
var rightCornerWidth = this.constants_.INSIDE_CORNERS.rightWidth || 0;
desiredWidth = this.width - this.startX - rightCornerWidth;
desiredWidth = this.getDesiredRowWidth_(row);
statementInput.width += (desiredWidth - currentWidth);
statementInput.height = Math.max(statementInput.height, row.height);
row.width += (desiredWidth - currentWidth);
row.widthWithConnectedBlocks = Math.max(row.width,
this.statementEdge + row.connectedBlockWidths);
@@ -596,6 +665,9 @@ Blockly.blockRendering.RenderInfo.prototype.makeSpacerRow_ = function(prev, next
if (prev.hasStatement) {
spacer.followsStatement = true;
}
if (next.hasStatement) {
spacer.precedesStatement = true;
}
return spacer;
};
@@ -659,7 +731,7 @@ Blockly.blockRendering.RenderInfo.prototype.getElemCenterline_ = function(row,
* Record final position information on elements on the given row, for use in
* drawing. At minimum this records xPos and centerline on each element.
* @param {!Blockly.blockRendering.Row} row The row containing the elements.
* @private
* @protected
*/
Blockly.blockRendering.RenderInfo.prototype.recordElemPositions_ = function(
row) {
@@ -695,8 +767,16 @@ Blockly.blockRendering.RenderInfo.prototype.finalize_ = function() {
Math.max(widestRowWithConnectedBlocks, row.widthWithConnectedBlocks);
this.recordElemPositions_(row);
}
if (this.outputConnection && this.block_.nextConnection &&
this.block_.nextConnection.isConnected()) {
// Include width of connected block in value to stack width measurement.
widestRowWithConnectedBlocks =
Math.max(widestRowWithConnectedBlocks,
this.block_.nextConnection.targetBlock().getHeightWidth().width);
}
this.widthWithChildren = widestRowWithConnectedBlocks + this.startX;
this.widthWithChildren = Math.max(this.widthWithChildren,
widestRowWithConnectedBlocks + this.startX);
this.height = yCursor;
this.startY = this.topRow.capline;

View File

@@ -0,0 +1,625 @@
/**
* @license
* Copyright 2019 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @fileoverview Methods for graphically rendering a marker as SVG.
* @author samelh@microsoft.com (Sam El-Husseini)
*/
'use strict';
goog.provide('Blockly.blockRendering.MarkerSvg');
goog.require('Blockly.ASTNode');
/**
* Class for a marker.
* @param {!Blockly.WorkspaceSvg} workspace The workspace the marker belongs to.
* @param {!Blockly.blockRendering.ConstantProvider} constants The constants for
* the renderer.
* @param {!Blockly.Marker} marker The marker to draw.
* @constructor
*/
Blockly.blockRendering.MarkerSvg = function(workspace, constants, marker) {
/**
* The workspace the marker belongs to.
* @type {!Blockly.WorkspaceSvg}
* @private
*/
this.workspace_ = workspace;
/**
* The marker to draw.
* @type {!Blockly.Marker}
* @private
*/
this.marker_ = marker;
/**
* The workspace, field, or block that the marker SVG element should be
* attached to.
* @type {Blockly.WorkspaceSvg|Blockly.Field|Blockly.BlockSvg}
* @private
*/
this.parent_ = null;
/**
* The constants necessary to draw the marker.
* @type {Blockly.blockRendering.ConstantProvider}
* @protected
*/
this.constants_ = constants;
/**
* The current SVG element for the marker.
* @type {Element}
*/
this.currentMarkerSvg = null;
var defaultColour = this.isCursor() ? this.constants_.CURSOR_COLOUR :
this.constants_.MARKER_COLOUR;
/**
* The colour of the marker.
* @type {string}
*/
this.colour_ = marker.colour || defaultColour;
};
/**
* The name of the CSS class for a cursor.
* @const {string}
*/
Blockly.blockRendering.MarkerSvg.CURSOR_CLASS = 'blocklyCursor';
/**
* The name of the CSS class for a marker.
* @const {string}
*/
Blockly.blockRendering.MarkerSvg.MARKER_CLASS = 'blocklyMarker';
/**
* What we multiply the height by to get the height of the marker.
* Only used for the block and block connections.
* @type {number}
* @const
*/
Blockly.blockRendering.MarkerSvg.HEIGHT_MULTIPLIER = 3 / 4;
/**
* Return the root node of the SVG or null if none exists.
* @return {SVGElement} The root SVG node.
*/
Blockly.blockRendering.MarkerSvg.prototype.getSvgRoot = function() {
return this.svgGroup_;
};
/**
* True if the marker should be drawn as a cursor, false otherwise.
* A cursor is drawn as a flashing line. A marker is drawn as a solid line.
* @return {boolean} True if the marker is a cursor, false otherwise.
*/
Blockly.blockRendering.MarkerSvg.prototype.isCursor = function() {
return this.marker_.type == 'cursor';
};
/**
* Create the DOM element for the marker.
* @return {!SVGElement} The marker controls SVG group.
* @package
*/
Blockly.blockRendering.MarkerSvg.prototype.createDom = function() {
var className = this.isCursor() ?
Blockly.blockRendering.MarkerSvg.CURSOR_CLASS :
Blockly.blockRendering.MarkerSvg.MARKER_CLASS;
this.svgGroup_ =
Blockly.utils.dom.createSvgElement('g', {
'class': className
}, null);
this.createDomInternal_();
return this.svgGroup_;
};
/**
* Attaches the SVG root of the marker to the SVG group of the parent.
* @param {!Blockly.WorkspaceSvg|!Blockly.Field|!Blockly.BlockSvg} newParent
* The workspace, field, or block that the marker SVG element should be
* attached to.
* @protected
*/
Blockly.blockRendering.MarkerSvg.prototype.setParent_ = function(newParent) {
if (!this.isCursor()) {
if (this.parent_) {
this.parent_.setMarkerSvg(null);
}
newParent.setMarkerSvg(this.getSvgRoot());
} else {
if (this.parent_) {
this.parent_.setCursorSvg(null);
}
newParent.setCursorSvg(this.getSvgRoot());
}
this.parent_ = newParent;
};
/**************************
* Display
**************************/
/**
* Show the marker as a combination of the previous connection and block,
* the output connection and block, or just the block.
* @param {Blockly.BlockSvg} block The block the marker is currently on.
* @protected
*/
Blockly.blockRendering.MarkerSvg.prototype.showWithBlockPrevOutput_ = function(block) {
if (!block) {
return;
}
var width = block.width;
var height = block.height;
var markerHeight = height * Blockly.blockRendering.MarkerSvg.HEIGHT_MULTIPLIER;
var markerOffset = this.constants_.CURSOR_BLOCK_PADDING;
if (block.previousConnection) {
var connectionShape = this.constants_.shapeFor(block.previousConnection);
this.positionPrevious_(width, markerOffset, markerHeight, connectionShape);
} else if (block.outputConnection) {
var connectionShape = this.constants_.shapeFor(block.outputConnection);
this.positionOutput_(width, height, connectionShape);
} else {
this.positionBlock_(width, markerOffset, markerHeight);
}
this.setParent_(block);
this.showCurrent_();
};
/**
* Show the visual representation of a workspace coordinate.
* This is a horizontal line.
* @param {!Blockly.ASTNode} curNode The node that we want to draw the marker for.
* @protected
*/
Blockly.blockRendering.MarkerSvg.prototype.showWithCoordinates_ = function(curNode) {
var wsCoordinate = curNode.getWsCoordinate();
var x = wsCoordinate.x;
var y = wsCoordinate.y;
if (this.workspace_.RTL) {
x -= this.constants_.CURSOR_WS_WIDTH;
}
this.positionLine_(x, y, this.constants_.CURSOR_WS_WIDTH);
this.setParent_(this.workspace_);
this.showCurrent_();
};
/**
* Show the visual representation of a field.
* This is a box around the field.
* @param {!Blockly.ASTNode} curNode The node that we want to draw the marker for.
* @protected
*/
Blockly.blockRendering.MarkerSvg.prototype.showWithField_ = function(curNode) {
var field = /** @type {Blockly.Field} */ (curNode.getLocation());
var width = field.getSize().width;
var height = field.getSize().height;
this.positionRect_(0, 0, width, height);
this.setParent_(field);
this.showCurrent_();
};
/**
* Show the visual representation of an input.
* This is a puzzle piece.
* @param {!Blockly.ASTNode} curNode The node that we want to draw the marker for.
* @protected
*/
Blockly.blockRendering.MarkerSvg.prototype.showWithInput_ = function(curNode) {
var connection = /** @type {Blockly.RenderedConnection} */
(curNode.getLocation());
var sourceBlock = /** @type {!Blockly.BlockSvg} */ (connection.getSourceBlock());
this.positionInput_(connection);
this.setParent_(sourceBlock);
this.showCurrent_();
};
/**
* Show the visual representation of a next connection.
* This is a horizontal line.
* @param {!Blockly.ASTNode} curNode The node that we want to draw the marker for.
* @protected
*/
Blockly.blockRendering.MarkerSvg.prototype.showWithNext_ = function(curNode) {
var connection = curNode.getLocation();
var targetBlock = /** @type {Blockly.BlockSvg} */ (connection.getSourceBlock());
var x = 0;
var y = connection.getOffsetInBlock().y;
var width = targetBlock.getHeightWidth().width;
if (this.workspace_.RTL) {
x = -width;
}
this.positionLine_(x, y, width);
this.setParent_(targetBlock);
this.showCurrent_();
};
/**
* Show the visual representation of a stack.
* This is a box with extra padding around the entire stack of blocks.
* @param {!Blockly.ASTNode} curNode The node that we want to draw the marker for.
* @protected
*/
Blockly.blockRendering.MarkerSvg.prototype.showWithStack_ = function(curNode) {
var block = /** @type {Blockly.BlockSvg} */ (curNode.getLocation());
// Gets the height and width of entire stack.
var heightWidth = block.getHeightWidth();
// Add padding so that being on a stack looks different than being on a block.
var width = heightWidth.width + this.constants_.CURSOR_STACK_PADDING;
var height = heightWidth.height + this.constants_.CURSOR_STACK_PADDING;
// Shift the rectangle slightly to upper left so padding is equal on all sides.
var xPadding = -this.constants_.CURSOR_STACK_PADDING / 2;
var yPadding = -this.constants_.CURSOR_STACK_PADDING / 2;
var x = xPadding;
var y = yPadding;
if (this.workspace_.RTL) {
x = -(width + xPadding);
}
this.positionRect_(x, y, width, height);
this.setParent_(block);
this.showCurrent_();
};
/**
* Show the current marker.
* @protected
*/
Blockly.blockRendering.MarkerSvg.prototype.showCurrent_ = function() {
this.hide();
this.currentMarkerSvg.style.display = '';
};
/**************************
* Position
**************************/
/**
* Position the marker for a block.
* Displays an outline of the top half of a rectangle around a block.
* @param {number} width The width of the block.
* @param {number} markerOffset The extra padding for around the block.
* @param {number} markerHeight The height of the marker.
* @private
*/
Blockly.blockRendering.MarkerSvg.prototype.positionBlock_ = function(
width, markerOffset, markerHeight) {
var markerPath = Blockly.utils.svgPaths.moveBy(-markerOffset, markerHeight) +
Blockly.utils.svgPaths.lineOnAxis('V', -markerOffset) +
Blockly.utils.svgPaths.lineOnAxis('H', width + markerOffset * 2) +
Blockly.utils.svgPaths.lineOnAxis('V', markerHeight);
this.markerBlock_.setAttribute('d', markerPath);
if (this.workspace_.RTL) {
this.flipRtl_(this.markerBlock_);
}
this.currentMarkerSvg = this.markerBlock_;
};
/**
* Position the marker for an input connection.
* Displays a filled in puzzle piece.
* @param {!Blockly.RenderedConnection} connection The connection to position marker around.
* @private
*/
Blockly.blockRendering.MarkerSvg.prototype.positionInput_ = function(connection) {
var x = connection.getOffsetInBlock().x;
var y = connection.getOffsetInBlock().y;
var path = Blockly.utils.svgPaths.moveTo(0, 0) +
this.constants_.shapeFor(connection).pathDown;
this.markerInput_.setAttribute('d', path);
this.markerInput_.setAttribute('transform',
'translate(' + x + ',' + y + ')' + (this.workspace_.RTL ? ' scale(-1 1)' : ''));
this.currentMarkerSvg = this.markerInput_;
};
/**
* Move and show the marker at the specified coordinate in workspace units.
* Displays a horizontal line.
* @param {number} x The new x, in workspace units.
* @param {number} y The new y, in workspace units.
* @param {number} width The new width, in workspace units.
* @protected
*/
Blockly.blockRendering.MarkerSvg.prototype.positionLine_ = function(x, y, width) {
this.markerSvgLine_.setAttribute('x', x);
this.markerSvgLine_.setAttribute('y', y);
this.markerSvgLine_.setAttribute('width', width);
this.currentMarkerSvg = this.markerSvgLine_;
};
/**
* Position the marker for an output connection.
* Displays a puzzle outline and the top and bottom path.
* @param {number} width The width of the block.
* @param {number} height The height of the block.
* @param {!Object} connectionShape The shape object for the connection.
* @private
*/
Blockly.blockRendering.MarkerSvg.prototype.positionOutput_ = function(
width, height, connectionShape) {
var markerPath = Blockly.utils.svgPaths.moveBy(width, 0) +
Blockly.utils.svgPaths.lineOnAxis(
'h', -(width - connectionShape.width)) +
Blockly.utils.svgPaths.lineOnAxis(
'v', this.constants_.TAB_OFFSET_FROM_TOP) +
connectionShape.pathDown +
Blockly.utils.svgPaths.lineOnAxis('V', height) +
Blockly.utils.svgPaths.lineOnAxis('H', width);
this.markerBlock_.setAttribute('d', markerPath);
if (this.workspace_.RTL) {
this.flipRtl_(this.markerBlock_);
}
this.currentMarkerSvg = this.markerBlock_;
};
/**
* Position the marker for a previous connection.
* Displays a half rectangle with a notch in the top to represent the previous
* connection.
* @param {number} width The width of the block.
* @param {number} markerOffset The offset of the marker from around the block.
* @param {number} markerHeight The height of the marker.
* @param {!Object} connectionShape The shape object for the connection.
* @private
*/
Blockly.blockRendering.MarkerSvg.prototype.positionPrevious_ = function(
width, markerOffset, markerHeight, connectionShape) {
var markerPath = Blockly.utils.svgPaths.moveBy(-markerOffset, markerHeight) +
Blockly.utils.svgPaths.lineOnAxis('V', -markerOffset) +
Blockly.utils.svgPaths.lineOnAxis(
'H', this.constants_.NOTCH_OFFSET_LEFT) +
connectionShape.pathLeft +
Blockly.utils.svgPaths.lineOnAxis(
'H', width + markerOffset * 2) +
Blockly.utils.svgPaths.lineOnAxis('V', markerHeight);
this.markerBlock_.setAttribute('d', markerPath);
if (this.workspace_.RTL) {
this.flipRtl_(this.markerBlock_);
}
this.currentMarkerSvg = this.markerBlock_;
};
/**
* Move and show the marker at the specified coordinate in workspace units.
* Displays a filled in rectangle.
* @param {number} x The new x, in workspace units.
* @param {number} y The new y, in workspace units.
* @param {number} width The new width, in workspace units.
* @param {number} height The new height, in workspace units.
* @protected
*/
Blockly.blockRendering.MarkerSvg.prototype.positionRect_ = function(x, y, width, height) {
this.markerSvgRect_.setAttribute('x', x);
this.markerSvgRect_.setAttribute('y', y);
this.markerSvgRect_.setAttribute('width', width);
this.markerSvgRect_.setAttribute('height', height);
this.currentMarkerSvg = this.markerSvgRect_;
};
/**
* Flip the SVG paths in RTL.
* @param {!SVGElement} markerSvg The marker that we want to flip.
* @private
*/
Blockly.blockRendering.MarkerSvg.prototype.flipRtl_ = function(markerSvg) {
markerSvg.setAttribute('transform', 'scale(-1 1)');
};
/**
* Hide the marker.
* @package
*/
Blockly.blockRendering.MarkerSvg.prototype.hide = function() {
this.markerSvgLine_.style.display = 'none';
this.markerSvgRect_.style.display = 'none';
this.markerInput_.style.display = 'none';
this.markerBlock_.style.display = 'none';
};
/**
* Update the marker.
* @param {Blockly.ASTNode} oldNode The previous node the marker was on or null.
* @param {Blockly.ASTNode} curNode The node that we want to draw the marker for.
* @package
*/
Blockly.blockRendering.MarkerSvg.prototype.draw = function(oldNode, curNode) {
if (!curNode) {
this.hide();
return;
}
this.showAtLocation_(curNode);
this.firemarkerEvent_(oldNode, curNode);
// Ensures the marker will be visible immediately after the move.
var animate = this.currentMarkerSvg.childNodes[0];
if (animate !== undefined) {
animate.beginElement && animate.beginElement();
}
};
/**
* Update the marker's visible state based on the type of curNode..
* @param {Blockly.ASTNode} curNode The node that we want to draw the marker for.
* @protected
*/
Blockly.blockRendering.MarkerSvg.prototype.showAtLocation_ = function(curNode) {
if (curNode.getType() == Blockly.ASTNode.types.BLOCK) {
var block = /** @type {Blockly.BlockSvg} */ (curNode.getLocation());
this.showWithBlockPrevOutput_(block);
} else if (curNode.getType() == Blockly.ASTNode.types.OUTPUT) {
var outputBlock = /** @type {Blockly.BlockSvg} */ (curNode.getLocation().getSourceBlock());
this.showWithBlockPrevOutput_(outputBlock);
} else if (curNode.getLocation().type == Blockly.INPUT_VALUE) {
this.showWithInput_(curNode);
} else if (curNode.getLocation().type == Blockly.NEXT_STATEMENT) {
this.showWithNext_(curNode);
} else if (curNode.getType() == Blockly.ASTNode.types.PREVIOUS) {
var previousBlock = /** @type {Blockly.BlockSvg} */ (curNode.getLocation().getSourceBlock());
this.showWithBlockPrevOutput_(previousBlock);
} else if (curNode.getType() == Blockly.ASTNode.types.FIELD) {
this.showWithField_(curNode);
} else if (curNode.getType() == Blockly.ASTNode.types.WORKSPACE) {
this.showWithCoordinates_(curNode);
} else if (curNode.getType() == Blockly.ASTNode.types.STACK) {
this.showWithStack_(curNode);
}
};
/**
* Fire event for the marker or marker.
* @param {Blockly.ASTNode} oldNode The old node the marker used to be on.
* @param {!Blockly.ASTNode} curNode The new node the marker is currently on.
* @private
*/
Blockly.blockRendering.MarkerSvg.prototype.firemarkerEvent_ = function(oldNode, curNode) {
var curBlock = curNode.getSourceBlock();
var eventType = this.isCursor() ? 'cursorMove' : 'markerMove';
var event = new Blockly.Events.Ui(curBlock, eventType, oldNode, curNode);
if (curNode.getType() == Blockly.ASTNode.types.WORKSPACE) {
event.workspaceId = curNode.getLocation().id;
}
Blockly.Events.fire(event);
};
/**
* Get the properties to make a marker blink.
* @return {!Object} The object holding attributes to make the marker blink.
* @protected
*/
Blockly.blockRendering.MarkerSvg.prototype.getBlinkProperties_ = function() {
return {
'attributeType': 'XML',
'attributeName': 'fill',
'dur': '1s',
'values': this.colour_ + ';transparent;transparent;',
'repeatCount': 'indefinite'
};
};
/**
* Create the marker SVG.
* @return {Element} The SVG node created.
* @protected
*/
Blockly.blockRendering.MarkerSvg.prototype.createDomInternal_ = function() {
/* This markup will be generated and added to the .svgGroup_:
<g>
<rect width="100" height="5">
<animate attributeType="XML" attributeName="fill" dur="1s"
values="transparent;transparent;#fff;transparent" repeatCount="indefinite" />
</rect>
</g>
*/
this.markerSvg_ = Blockly.utils.dom.createSvgElement('g',
{
'width': this.constants_.CURSOR_WS_WIDTH,
'height': this.constants_.WS_CURSOR_HEIGHT
}, this.svgGroup_);
// A horizontal line used to represent a workspace coordinate or next connection.
this.markerSvgLine_ = Blockly.utils.dom.createSvgElement('rect',
{
'fill': this.colour_,
'width': this.constants_.CURSOR_WS_WIDTH,
'height': this.constants_.WS_CURSOR_HEIGHT,
'style': 'display: none'
},
this.markerSvg_);
// A filled in rectangle used to represent a stack.
this.markerSvgRect_ = Blockly.utils.dom.createSvgElement('rect',
{
'class': 'blocklyVerticalMarker',
'rx': 10, 'ry': 10,
'style': 'display: none',
'stroke': this.colour_
},
this.markerSvg_);
// A filled in puzzle piece used to represent an input value.
this.markerInput_ = Blockly.utils.dom.createSvgElement('path',
{
'transform': '',
'style': 'display: none',
'fill': this.colour_
},
this.markerSvg_);
// A path used to represent a previous connection and a block, an output
// connection and a block, or a block.
this.markerBlock_ = Blockly.utils.dom.createSvgElement('path',
{
'transform': '',
'style': 'display: none',
'fill': 'none',
'stroke': this.colour_,
'stroke-width': this.constants_.CURSOR_STROKE_WIDTH
},
this.markerSvg_);
// Markers and stack markers don't blink.
if (this.isCursor()) {
var blinkProperties = this.getBlinkProperties_();
Blockly.utils.dom.createSvgElement('animate', this.getBlinkProperties_(),
this.markerSvgLine_);
Blockly.utils.dom.createSvgElement('animate', blinkProperties,
this.markerInput_);
blinkProperties['attributeName'] = 'stroke';
Blockly.utils.dom.createSvgElement('animate', blinkProperties,
this.markerBlock_);
}
return this.markerSvg_;
};
/**
* Dispose of this marker.
* @package
*/
Blockly.blockRendering.MarkerSvg.prototype.dispose = function() {
if (this.svgGroup_) {
Blockly.utils.dom.removeNode(this.svgGroup_);
}
};

View File

@@ -22,29 +22,34 @@
'use strict';
goog.provide('Blockly.blockRendering.IPathObject');
goog.provide('Blockly.blockRendering.PathObject');
goog.require('Blockly.blockRendering.ConstantProvider');
goog.require('Blockly.blockRendering.IPathObject');
goog.require('Blockly.Theme');
goog.require('Blockly.utils.dom');
/**
* An interface for a block's path object.
* @param {!SVGElement} _root The root SVG element.
* @interface
* @package
*/
Blockly.blockRendering.IPathObject = function(_root) {};
/**
* An object that handles creating and setting each of the SVG elements
* used by the renderer.
* @param {!SVGElement} root The root SVG element.
* @param {!Blockly.Theme.BlockStyle} style The style object to use for
* colouring.
* @param {!Blockly.blockRendering.ConstantProvider} constants The renderer's
* constants.
* @constructor
* @implements {Blockly.blockRendering.IPathObject}
* @package
*/
Blockly.blockRendering.PathObject = function(root) {
Blockly.blockRendering.PathObject = function(root, style, constants) {
/**
* The renderer's constant provider.
* @type {!Blockly.blockRendering.ConstantProvider}
* @protected
*/
this.constants_ = constants;
this.svgRoot = root;
/**
@@ -55,26 +60,28 @@ Blockly.blockRendering.PathObject = function(root) {
this.svgPath = Blockly.utils.dom.createSvgElement('path',
{'class': 'blocklyPath'}, this.svgRoot);
// The light and dark paths need to exist (for now) because there is colouring
// code in block_svg that depends on them. But we will always set them to
// display: none, and eventually we want to remove them entirely.
/**
* The light path of the block.
* @type {SVGElement}
* The style object to use when colouring block paths.
* @type {!Blockly.Theme.BlockStyle}
* @package
*/
this.svgPathLight = Blockly.utils.dom.createSvgElement('path',
{'class': 'blocklyPathLight'}, this.svgRoot);
this.style = style;
/**
* The dark path of the block.
* Holds the cursors svg element when the cursor is attached to the block.
* This is null if there is no cursor on the block.
* @type {SVGElement}
* @package
* @private
*/
this.svgPathDark = Blockly.utils.dom.createSvgElement('path',
{'class': 'blocklyPathDark', 'transform': 'translate(1,1)'},
this.svgRoot);
this.cursorSvg_ = null;
/**
* Holds the markers svg element when the marker is attached to the block.
* This is null if there is no marker on the block.
* @type {SVGElement}
* @private
*/
this.markerSvg_ = null;
};
/**
@@ -82,10 +89,8 @@ Blockly.blockRendering.PathObject = function(root) {
* @param {string} pathString The path.
* @package
*/
Blockly.blockRendering.PathObject.prototype.setPaths = function(pathString) {
Blockly.blockRendering.PathObject.prototype.setPath = function(pathString) {
this.svgPath.setAttribute('d', pathString);
this.svgPathLight.style.display = 'none';
this.svgPathDark.style.display = 'none';
};
/**
@@ -96,3 +101,188 @@ Blockly.blockRendering.PathObject.prototype.flipRTL = function() {
// Mirror the block's path.
this.svgPath.setAttribute('transform', 'scale(-1 1)');
};
/**
* Add the cursor svg to this block's svg group.
* @param {SVGElement} cursorSvg The svg root of the cursor to be added to the
* block svg group.
* @package
*/
Blockly.blockRendering.PathObject.prototype.setCursorSvg = function(cursorSvg) {
if (!cursorSvg) {
this.cursorSvg_ = null;
return;
}
this.svgRoot.appendChild(cursorSvg);
this.cursorSvg_ = cursorSvg;
};
/**
* Add the marker svg to this block's svg group.
* @param {SVGElement} markerSvg The svg root of the marker to be added to the
* block svg group.
* @package
*/
Blockly.blockRendering.PathObject.prototype.setMarkerSvg = function(markerSvg) {
if (!markerSvg) {
this.markerSvg_ = null;
return;
}
if (this.cursorSvg_) {
this.svgRoot.insertBefore(markerSvg, this.cursorSvg_);
} else {
this.svgRoot.appendChild(markerSvg);
}
this.markerSvg_ = markerSvg;
};
/**
* Apply the stored colours to the block's path, taking into account whether
* the paths belong to a shadow block.
* @param {!Blockly.Block} block The source block.
* @package
*/
Blockly.blockRendering.PathObject.prototype.applyColour = function(block) {
this.svgPath.setAttribute('stroke', this.style.colourTertiary);
this.svgPath.setAttribute('fill', this.style.colourPrimary);
this.updateShadow_(block.isShadow());
this.updateDisabled_(!block.isEnabled() || block.getInheritedDisabled());
};
/**
* Set the style.
* @param {!Blockly.Theme.BlockStyle} blockStyle The block style to use.
* @package
*/
Blockly.blockRendering.PathObject.prototype.setStyle = function(blockStyle) {
this.style = blockStyle;
};
/**
* Add or remove the given CSS class on the path object's root SVG element.
* @param {string} className The name of the class to add or remove
* @param {boolean} add True if the class should be added. False if it should
* be removed.
* @protected
*/
Blockly.blockRendering.PathObject.prototype.setClass_ = function(
className, add) {
if (add) {
Blockly.utils.dom.addClass(/** @type {!Element} */ (this.svgRoot),
className);
} else {
Blockly.utils.dom.removeClass(/** @type {!Element} */ (this.svgRoot),
className);
}
};
/**
* Set whether the block shows a highlight or not. Block highlighting is
* often used to visually mark blocks currently being executed.
* @param {boolean} enable True if highlighted.
* @package
*/
Blockly.blockRendering.PathObject.prototype.updateHighlighted = function(
enable) {
if (enable) {
this.svgPath.setAttribute('filter',
'url(#' + this.constants_.embossFilterId + ')');
} else {
this.svgPath.setAttribute('filter', 'none');
}
};
/**
* Updates the look of the block to reflect a shadow state.
* @param {boolean} shadow True if the block is a shadow block.
* @protected
*/
Blockly.blockRendering.PathObject.prototype.updateShadow_ = function(shadow) {
if (shadow) {
this.svgPath.setAttribute('stroke', 'none');
this.svgPath.setAttribute('fill', this.style.colourSecondary);
}
};
/**
* Updates the look of the block to reflect a disabled state.
* @param {boolean} disabled True if disabled.
* @protected
*/
Blockly.blockRendering.PathObject.prototype.updateDisabled_ = function(
disabled) {
this.setClass_('blocklyDisabled', disabled);
if (disabled) {
this.svgPath.setAttribute('fill',
'url(#' + this.constants_.disabledPatternId + ')');
}
};
/**
* Add or remove styling showing that a block is selected.
* @param {boolean} enable True if selection is enabled, false otherwise.
* @package
*/
Blockly.blockRendering.PathObject.prototype.updateSelected = function(enable) {
this.setClass_('blocklySelected', enable);
};
/**
* Add or remove styling showing that a block is dragged over a delete area.
* @param {boolean} enable True if the block is being dragged over a delete
* area, false otherwise.
* @package
*/
Blockly.blockRendering.PathObject.prototype.updateDraggingDelete = function(
enable) {
this.setClass_('blocklyDraggingDelete', enable);
};
/**
* Add or remove styling showing that a block is an insertion marker.
* @param {boolean} enable True if the block is an insertion marker, false
* otherwise.
* @package
*/
Blockly.blockRendering.PathObject.prototype.updateInsertionMarker = function(
enable) {
this.setClass_('blocklyInsertionMarker', enable);
};
/**
* Add or remove styling showing that a block is movable.
* @param {boolean} enable True if the block is movable, false otherwise.
* @package
*/
Blockly.blockRendering.PathObject.prototype.updateMovable = function(enable) {
this.setClass_('blocklyDraggable', enable);
};
/**
* Add or remove styling that shows that if the dragging block is dropped, this
* block will be replaced. If a shadow block, it will disappear. Otherwise it
* will bump.
* @param {boolean} enable True if styling should be added.
* @package
*/
Blockly.blockRendering.PathObject.prototype.updateReplacementHighlight =
function(enable) {
/* eslint-disable indent */
this.setClass_('blocklyReplaceable', enable);
}; /* eslint-enable indent */
/**
* Add or remove styling that shows that if the dragging block is dropped, this
* block will be connected to the input.
* @param {Blockly.Connection} _conn The connection on the input to highlight.
* @param {boolean} _enable True if styling should be added.
* @package
*/
Blockly.blockRendering.PathObject.prototype.updateShapeForInputHighlight =
function(_conn, _enable) {
/* eslint-disable indent */
// NOP
}; /* eslint-enable indent */

View File

@@ -24,19 +24,29 @@
goog.provide('Blockly.blockRendering.Renderer');
goog.require('Blockly.blockRendering.ConstantProvider');
goog.require('Blockly.blockRendering.MarkerSvg');
goog.require('Blockly.blockRendering.Drawer');
goog.require('Blockly.blockRendering.IPathObject');
goog.require('Blockly.blockRendering.PathObject');
goog.require('Blockly.blockRendering.RenderInfo');
goog.require('Blockly.CursorSvg');
goog.requireType('Blockly.blockRendering.Debug');
/**
* The base class for a block renderer.
* @param {string} name The renderer name.
* @package
* @constructor
*/
Blockly.blockRendering.Renderer = function() {
Blockly.blockRendering.Renderer = function(name) {
/**
* The renderer name.
* @type {string}
* @package
*/
this.name = name;
/**
* The renderer's constant provider.
@@ -89,6 +99,7 @@ Blockly.blockRendering.Renderer.prototype.makeDrawer_ = function(block, info) {
/**
* Create a new instance of the renderer's debugger.
* @return {!Blockly.blockRendering.Debug} The renderer debugger.
* @suppress {strictModuleDepCheck} Debug renderer only included in playground.
* @protected
*/
Blockly.blockRendering.Renderer.prototype.makeDebugger_ = function() {
@@ -99,27 +110,31 @@ Blockly.blockRendering.Renderer.prototype.makeDebugger_ = function() {
};
/**
* Create a new instance of the renderer's cursor drawer.
* @param {!Blockly.WorkspaceSvg} workspace The workspace the cursor belongs to.
* @param {boolean=} opt_marker True if the cursor is a marker. A marker is used
* to save a location and is an immovable cursor. False or undefined if the
* cursor is not a marker.
* @return {!Blockly.CursorSvg} The cursor drawer.
* Create a new instance of the renderer's marker drawer.
* @param {!Blockly.WorkspaceSvg} workspace The workspace the marker belongs to.
* @param {!Blockly.Marker} marker The marker.
* @return {!Blockly.blockRendering.MarkerSvg} The object in charge of drawing
* the marker.
* @package
*/
Blockly.blockRendering.Renderer.prototype.makeCursorDrawer = function(
workspace, opt_marker) {
return new Blockly.CursorSvg(workspace, opt_marker);
Blockly.blockRendering.Renderer.prototype.makeMarkerDrawer = function(
workspace, marker) {
return new Blockly.blockRendering.MarkerSvg(workspace, this.getConstants(), marker);
};
/**
* Create a new instance of a renderer path object.
* @param {!SVGElement} root The root SVG element.
* @param {!Blockly.Theme.BlockStyle} style The style object to use for
* colouring.
* @return {!Blockly.blockRendering.IPathObject} The renderer path object.
* @package
*/
Blockly.blockRendering.Renderer.prototype.makePathObject = function(root) {
return new Blockly.blockRendering.PathObject(root);
Blockly.blockRendering.Renderer.prototype.makePathObject = function(root,
style) {
return new Blockly.blockRendering.PathObject(root, style,
/** @type {!Blockly.blockRendering.ConstantProvider} */ (this.constants_));
};
/**
@@ -134,6 +149,34 @@ Blockly.blockRendering.Renderer.prototype.getConstants = function() {
(this.constants_));
};
/**
* Determine whether or not to highlight a connection.
* @param {Blockly.Connection} _conn The connection to determine whether or not
* to highlight.
* @return {boolean} True if we should highlight the connection.
* @package
*/
Blockly.blockRendering.Renderer.prototype.shouldHighlightConnection =
function(_conn) {
/* eslint-disable indent */
return true;
}; /* eslint-enable indent */
/**
* Determine whether or not to insert a dragged block into a stack.
* @param {!Blockly.Block} block The target block.
* @param {!Blockly.Connection} conn The closest connection.
* @return {boolean} True if we should insert the dragged block into the stack.
* @package
*/
Blockly.blockRendering.Renderer.prototype.shouldInsertDraggedBlock =
function(block, conn) {
/* eslint-disable indent */
return !conn.isConnected() ||
!!Blockly.Connection.lastConnectionInRow(block,
conn.targetConnection.getSourceBlock());
}; /* eslint-enable indent */
/**
* Render the block.
* @param {!Blockly.BlockSvg} block The block to render.

View File

@@ -37,9 +37,21 @@ goog.require('Blockly.utils.object');
Blockly.geras.ConstantProvider = function() {
Blockly.geras.ConstantProvider.superClass_.constructor.call(this);
/**
* @override
*/
this.FIELD_TEXT_BASELINE_CENTER = false;
// The dark/shadow path in classic rendering is the same as the normal block
// path, but translated down one and right one.
this.DARK_PATH_OFFSET = 1;
/**
* The maximum width of a bottom row that follows a statement input and has
* inputs inline.
* @type {number}
*/
this.MAX_BOTTOM_WIDTH = 30;
};
Blockly.utils.object.inherits(Blockly.geras.ConstantProvider,
Blockly.blockRendering.ConstantProvider);

View File

@@ -26,11 +26,12 @@ goog.provide('Blockly.geras.Drawer');
goog.require('Blockly.blockRendering.ConstantProvider');
goog.require('Blockly.blockRendering.Drawer');
goog.require('Blockly.geras.Highlighter');
goog.require('Blockly.geras.PathObject');
goog.require('Blockly.geras.RenderInfo');
goog.require('Blockly.utils.object');
goog.require('Blockly.utils.svgPaths');
goog.requireType('Blockly.geras.PathObject');
/**
* An object that draws a block based on the given rendering information.
@@ -57,10 +58,12 @@ Blockly.geras.Drawer.prototype.draw = function() {
this.drawOutline_();
this.drawInternals_();
this.block_.pathObject.setPaths(this.outlinePath_ + '\n' + this.inlinePath_,
this.highlighter_.getPath());
var pathObject =
/** @type {!Blockly.geras.PathObject} */ (this.block_.pathObject);
pathObject.setPath(this.outlinePath_ + '\n' + this.inlinePath_);
pathObject.setHighlightPath(this.highlighter_.getPath());
if (this.info_.RTL) {
this.block_.pathObject.flipRTL();
pathObject.flipRTL();
}
if (Blockly.blockRendering.useDebugger) {
this.block_.renderingDebugger.drawDebug(this.block_, this.info_);
@@ -110,7 +113,10 @@ Blockly.geras.Drawer.prototype.drawStatementInput_ = function(row) {
*/
Blockly.geras.Drawer.prototype.drawRightSideRow_ = function(row) {
this.highlighter_.drawRightSideRow(row);
Blockly.geras.Drawer.superClass_.drawRightSideRow_.call(this, row);
this.outlinePath_ +=
Blockly.utils.svgPaths.lineOnAxis('H', row.xPos + row.width) +
Blockly.utils.svgPaths.lineOnAxis('V', row.yPos + row.height);
};
/**
@@ -148,14 +154,14 @@ Blockly.geras.Drawer.prototype.drawInlineInput_ = function(input) {
Blockly.geras.Drawer.prototype.positionInlineInputConnection_ = function(input) {
var yPos = input.centerline - input.height / 2;
// Move the connection.
if (input.connection) {
if (input.connectionModel) {
// xPos already contains info about startX
var connX = input.xPos + input.connectionWidth +
this.constants_.DARK_PATH_OFFSET;
if (this.info_.RTL) {
connX *= -1;
}
input.connection.setOffsetInBlock(
input.connectionModel.setOffsetInBlock(
connX, yPos + input.connectionOffsetY +
this.constants_.DARK_PATH_OFFSET);
}
@@ -166,14 +172,14 @@ Blockly.geras.Drawer.prototype.positionInlineInputConnection_ = function(input)
*/
Blockly.geras.Drawer.prototype.positionStatementInputConnection_ = function(row) {
var input = row.getLastInput();
if (input.connection) {
if (input.connectionModel) {
var connX = row.xPos + row.statementEdge + input.notchOffset;
if (this.info_.RTL) {
connX *= -1;
} else {
connX += this.constants_.DARK_PATH_OFFSET;
}
input.connection.setOffsetInBlock(connX,
input.connectionModel.setOffsetInBlock(connX,
row.yPos + this.constants_.DARK_PATH_OFFSET);
}
};
@@ -183,13 +189,13 @@ Blockly.geras.Drawer.prototype.positionStatementInputConnection_ = function(row)
*/
Blockly.geras.Drawer.prototype.positionExternalValueConnection_ = function(row) {
var input = row.getLastInput();
if (input.connection) {
if (input.connectionModel) {
var connX = row.xPos + row.width +
this.constants_.DARK_PATH_OFFSET;
if (this.info_.RTL) {
connX *= -1;
}
input.connection.setOffsetInBlock(connX, row.yPos);
input.connectionModel.setOffsetInBlock(connX, row.yPos);
}
};
@@ -205,7 +211,6 @@ Blockly.geras.Drawer.prototype.positionNextConnection_ = function() {
var connX = (this.info_.RTL ? -x : x) +
(this.constants_.DARK_PATH_OFFSET / 2);
connInfo.connectionModel.setOffsetInBlock(
connX, (connInfo.centerline - connInfo.height / 2) +
this.constants_.DARK_PATH_OFFSET);
connX, bottomRow.baseline + this.constants_.DARK_PATH_OFFSET);
}
};

View File

@@ -237,7 +237,6 @@ Blockly.geras.Highlighter.prototype.drawInlineInput = function(input) {
var startY = yPos + offset;
if (this.RTL_) {
// TODO: Check if this is different when the inline input is populated.
var aboveTabHeight = input.connectionOffsetY - offset;
var belowTabHeight = input.height -
(input.connectionOffsetY + input.connectionHeight) + offset;

View File

@@ -73,6 +73,28 @@ Blockly.geras.RenderInfo.prototype.getRenderer = function() {
return /** @type {!Blockly.geras.Renderer} */ (this.renderer_);
};
/**
* @override
*/
Blockly.geras.RenderInfo.prototype.populateBottomRow_ = function() {
Blockly.geras.RenderInfo.superClass_.populateBottomRow_.call(this);
var followsStatement =
this.block_.inputList.length &&
this.block_.inputList[this.block_.inputList.length - 1]
.type == Blockly.NEXT_STATEMENT;
// The minimum height of the bottom row is smaller in Geras than in other
// renderers, because the dark path adds a pixel.
// If one of the row's elements has a greater height this will be overwritten
// in the compute pass.
if (!followsStatement) {
this.bottomRow.minHeight =
this.constants_.MEDIUM_PADDING - this.constants_.DARK_PATH_OFFSET;
}
};
/**
* @override
*/
@@ -97,7 +119,10 @@ Blockly.geras.RenderInfo.prototype.addInput_ = function(input, activeRow) {
this.constants_.DUMMY_INPUT_MIN_HEIGHT);
activeRow.hasDummyInput = true;
}
activeRow.align = input.align;
// Ignore row alignment if inline.
if (!this.isInline && activeRow.align == null) {
activeRow.align = input.align;
}
};
/**
@@ -119,6 +144,9 @@ Blockly.geras.RenderInfo.prototype.addElemSpacing_ = function() {
row.elements.push(new Blockly.blockRendering.InRowSpacer(
this.constants_, this.getInRowSpacing_(null, oldElems[0])));
}
if (!oldElems.length) {
continue;
}
for (var e = 0; e < oldElems.length - 1; e++) {
row.elements.push(oldElems[e]);
var spacing = this.getInRowSpacing_(oldElems[e], oldElems[e + 1]);
@@ -144,7 +172,8 @@ Blockly.geras.RenderInfo.prototype.addElemSpacing_ = function() {
Blockly.geras.RenderInfo.prototype.getInRowSpacing_ = function(prev, next) {
if (!prev) {
// Between an editable field and the beginning of the row.
if (next && Blockly.blockRendering.Types.isField(next) && next.isEditable) {
if (next && Blockly.blockRendering.Types.isField(next) &&
(/** @type Blockly.blockRendering.Field */ (next)).isEditable) {
return this.constants_.MEDIUM_PADDING;
}
// Inline input at the beginning of the row.
@@ -158,10 +187,12 @@ Blockly.geras.RenderInfo.prototype.getInRowSpacing_ = function(prev, next) {
return this.constants_.LARGE_PADDING;
}
// Spacing between a non-input and the end of the row.
if (!Blockly.blockRendering.Types.isInput(prev) && !next) {
// Spacing between a non-input and the end of the row or a statement input.
if (!Blockly.blockRendering.Types.isInput(prev) && (!next ||
Blockly.blockRendering.Types.isStatementInput(next))) {
// Between an editable field and the end of the row.
if (Blockly.blockRendering.Types.isField(prev) && prev.isEditable) {
if (Blockly.blockRendering.Types.isField(prev) &&
(/** @type Blockly.blockRendering.Field */ (prev)).isEditable) {
return this.constants_.MEDIUM_PADDING;
}
// Padding at the end of an icon-only row to make the block shape clearer.
@@ -202,7 +233,8 @@ Blockly.geras.RenderInfo.prototype.getInRowSpacing_ = function(prev, next) {
if (!Blockly.blockRendering.Types.isInput(prev) &&
next && Blockly.blockRendering.Types.isInput(next)) {
// Between an editable field and an input.
if (prev.isEditable) {
if (Blockly.blockRendering.Types.isField(prev) &&
(/** @type Blockly.blockRendering.Field */ (prev)).isEditable) {
if (Blockly.blockRendering.Types.isInlineInput(next)) {
return this.constants_.SMALL_PADDING;
} else if (Blockly.blockRendering.Types.isExternalInput(next)) {
@@ -228,9 +260,9 @@ Blockly.geras.RenderInfo.prototype.getInRowSpacing_ = function(prev, next) {
// Spacing between an inline input and a field.
if (Blockly.blockRendering.Types.isInlineInput(prev) &&
next && !Blockly.blockRendering.Types.isInput(next)) {
next && Blockly.blockRendering.Types.isField(next)) {
// Editable field after inline input.
if (next.isEditable) {
if ((/** @type Blockly.blockRendering.Field */ (next)).isEditable) {
return this.constants_.MEDIUM_PADDING;
} else {
// Noneditable field after inline input.
@@ -269,9 +301,10 @@ Blockly.geras.RenderInfo.prototype.getInRowSpacing_ = function(prev, next) {
}
// Spacing between two fields of the same editability.
if (!Blockly.blockRendering.Types.isInput(prev) &&
next && !Blockly.blockRendering.Types.isInput(next) &&
(prev.isEditable == next.isEditable)) {
if (Blockly.blockRendering.Types.isField(prev) &&
next && Blockly.blockRendering.Types.isField(next) &&
((/** @type Blockly.blockRendering.Field */ (prev)).isEditable ==
(/** @type Blockly.blockRendering.Field */ (next)).isEditable)) {
return this.constants_.LARGE_PADDING;
}
@@ -283,34 +316,6 @@ Blockly.geras.RenderInfo.prototype.getInRowSpacing_ = function(prev, next) {
return this.constants_.MEDIUM_PADDING;
};
/**
* @override
*/
Blockly.geras.RenderInfo.prototype.addAlignmentPadding_ = function(row, missingSpace) {
var firstSpacer = row.getFirstSpacer();
var lastSpacer = row.getLastSpacer();
if (row.hasExternalInput || row.hasStatement) {
row.widthWithConnectedBlocks += missingSpace;
}
// Decide where the extra padding goes.
if (row.align == Blockly.ALIGN_LEFT) {
// Add padding to the end of the row.
lastSpacer.width += missingSpace;
} else if (row.align == Blockly.ALIGN_CENTRE) {
// Split the padding between the beginning and end of the row.
firstSpacer.width += missingSpace / 2;
lastSpacer.width += missingSpace / 2;
} else if (row.align == Blockly.ALIGN_RIGHT) {
// Add padding at the beginning of the row.
firstSpacer.width += missingSpace;
} else {
// Default to left-aligning.
lastSpacer.width += missingSpace;
}
row.width += missingSpace;
};
/**
* @override
*/
@@ -380,6 +385,68 @@ Blockly.geras.RenderInfo.prototype.getElemCenterline_ = function(row, elem) {
return result;
};
/**
* @override
*/
Blockly.geras.RenderInfo.prototype.alignRowElements_ = function() {
if (!this.isInline) {
Blockly.geras.RenderInfo.superClass_.alignRowElements_.call(this);
return;
}
// Walk backgrounds through rows on the block, keeping track of the right
// input edge.
var nextRightEdge = 0;
var prevInput = null;
for (var i = this.rows.length - 1, row; (row = this.rows[i]); i--) {
row.nextRightEdge = nextRightEdge;
if (Blockly.blockRendering.Types.isInputRow(row)) {
if (row.hasStatement) {
this.alignStatementRow_(
/** @type {!Blockly.blockRendering.InputRow} */ (row));
}
if (prevInput && prevInput.hasStatement && row.width < prevInput.width) {
row.nextRightEdge = prevInput.width;
} else {
nextRightEdge = row.width;
}
prevInput = row;
}
}
// Walk down each row from the top, comparing the prev and next right input
// edges and setting the desired width to the max of the two.
var prevRightEdge = 0;
for (var i = 0, row; (row = this.rows[i]); i++) {
if (row.hasStatement) {
prevRightEdge = this.getDesiredRowWidth_(row);
} else if (Blockly.blockRendering.Types.isSpacer(row)) {
// Set the spacer row to the max of the prev or next input width.
row.width = Math.max(prevRightEdge, row.nextRightEdge);
} else {
var currentWidth = row.width;
var desiredWidth = Math.max(prevRightEdge, row.nextRightEdge);
var missingSpace = desiredWidth - currentWidth;
if (missingSpace > 0) {
this.addAlignmentPadding_(row, missingSpace);
}
prevRightEdge = row.width;
}
}
};
/**
* @override
*/
Blockly.geras.RenderInfo.prototype.getDesiredRowWidth_ = function(
row) {
// Limit the width of a statement row when a block is inline.
if (this.isInline && row.hasStatement) {
return this.statementEdge + this.constants_.MAX_BOTTOM_WIDTH + this.startX;
}
return Blockly.geras.RenderInfo.superClass_.getDesiredRowWidth_.call(this,
row);
};
/**
* @override
*/
@@ -407,6 +474,15 @@ Blockly.geras.RenderInfo.prototype.finalize_ = function() {
}
this.recordElemPositions_(row);
}
if (this.outputConnection && this.block_.nextConnection &&
this.block_.nextConnection.isConnected()) {
// Include width of connected block in value to stack width measurement.
widestRowWithConnectedBlocks =
Math.max(widestRowWithConnectedBlocks,
this.block_.nextConnection.targetBlock().getHeightWidth().width -
this.constants_.DARK_PATH_OFFSET);
}
this.bottomRow.baseline = yCursor - this.bottomRow.descenderHeight;
// The dark (lowlight) adds to the size of the block in both x and y.

View File

@@ -25,9 +25,9 @@
goog.provide('Blockly.geras.InlineInput');
goog.provide('Blockly.geras.StatementInput');
goog.require('Blockly.blockRendering.Connection');
goog.require('Blockly.utils.object');
/**
* An object containing information about the space an inline input takes up
* during rendering
@@ -37,7 +37,7 @@ goog.require('Blockly.utils.object');
* information for.
* @package
* @constructor
* @extends {Blockly.blockRendering.InputConnection}
* @extends {Blockly.blockRendering.InlineInput}
*/
Blockly.geras.InlineInput = function(constants, input) {
Blockly.geras.InlineInput.superClass_.constructor.call(
@@ -62,7 +62,7 @@ Blockly.utils.object.inherits(Blockly.geras.InlineInput,
* information for.
* @package
* @constructor
* @extends {Blockly.blockRendering.InputConnection}
* @extends {Blockly.blockRendering.StatementInput}
*/
Blockly.geras.StatementInput = function(constants, input) {
Blockly.geras.StatementInput.superClass_.constructor.call(

View File

@@ -24,19 +24,31 @@
goog.provide('Blockly.geras.PathObject');
goog.require('Blockly.blockRendering.IPathObject');
goog.require('Blockly.blockRendering.PathObject');
goog.require('Blockly.geras.ConstantProvider');
goog.require('Blockly.Theme');
goog.require('Blockly.utils.dom');
goog.require('Blockly.utils.object');
/**
* An object that handles creating and setting each of the SVG elements
* used by the renderer.
* @param {!SVGElement} root The root SVG element.
* @param {!Blockly.Theme.BlockStyle} style The style object to use for
* colouring.
* @param {!Blockly.geras.ConstantProvider} constants The renderer's constants.
* @constructor
* @implements {Blockly.blockRendering.IPathObject}
* @extends {Blockly.blockRendering.PathObject}
* @package
*/
Blockly.geras.PathObject = function(root) {
Blockly.geras.PathObject = function(root, style, constants) {
/**
* The renderer's constant provider.
* @type {!Blockly.geras.ConstantProvider}
*/
this.constants_ = constants;
this.svgRoot = root;
// The order of creation for these next three matters, because that
@@ -66,23 +78,43 @@ Blockly.geras.PathObject = function(root) {
*/
this.svgPathLight = Blockly.utils.dom.createSvgElement('path',
{'class': 'blocklyPathLight'}, this.svgRoot);
/**
* The colour of the dark path on the block in '#RRGGBB' format.
* @type {string}
* @package
*/
this.colourDark = '#000000';
/**
* The style object to use when colouring block paths.
* @type {!Blockly.Theme.BlockStyle}
* @package
*/
this.style = style;
};
Blockly.utils.object.inherits(Blockly.geras.PathObject,
Blockly.blockRendering.PathObject);
/**
* @override
*/
Blockly.geras.PathObject.prototype.setPath = function(mainPath) {
this.svgPath.setAttribute('d', mainPath);
this.svgPathDark.setAttribute('d', mainPath);
};
/**
* Set each of the paths generated by the renderer onto the respective SVG element.
* @param {string} mainPath The main path.
* Set the highlight path generated by the renderer onto the SVG element.
* @param {string} highlightPath The highlight path.
* @package
*/
Blockly.geras.PathObject.prototype.setPaths = function(mainPath, highlightPath) {
this.svgPath.setAttribute('d', mainPath);
this.svgPathDark.setAttribute('d', mainPath);
Blockly.geras.PathObject.prototype.setHighlightPath = function(highlightPath) {
this.svgPathLight.setAttribute('d', highlightPath);
};
/**
* Flip the SVG paths in RTL.
* @package
* @override
*/
Blockly.geras.PathObject.prototype.flipRTL = function() {
// Mirror the block's path.
@@ -90,3 +122,63 @@ Blockly.geras.PathObject.prototype.flipRTL = function() {
this.svgPathLight.setAttribute('transform', 'scale(-1 1)');
this.svgPathDark.setAttribute('transform', 'translate(1,1) scale(-1 1)');
};
/**
* @override
*/
Blockly.geras.PathObject.prototype.applyColour = function(block) {
this.svgPathLight.style.display = '';
this.svgPathDark.style.display = '';
this.svgPathLight.setAttribute('stroke', this.style.colourTertiary);
this.svgPathDark.setAttribute('fill', this.colourDark);
Blockly.geras.PathObject.superClass_.applyColour.call(this, block);
this.svgPath.setAttribute('stroke', 'none');
};
/**
* @override
*/
Blockly.geras.PathObject.prototype.setStyle = function(blockStyle) {
this.style = blockStyle;
this.colourDark =
Blockly.utils.colour.blend('#000', this.style.colourPrimary, 0.2) ||
this.colourDark;
};
/**
* @override
*/
Blockly.geras.PathObject.prototype.updateHighlighted = function(highlighted) {
if (highlighted) {
this.svgPath.setAttribute('filter',
'url(#' + this.constants_.embossFilterId + ')');
this.svgPathLight.style.display = 'none';
} else {
this.svgPath.setAttribute('filter', 'none');
this.svgPathLight.style.display = 'inline';
}
};
/**
* @override
*/
Blockly.geras.PathObject.prototype.updateShadow_ = function(shadow) {
if (shadow) {
this.svgPathLight.style.display = 'none';
this.svgPathDark.setAttribute('fill', this.style.colourSecondary);
this.svgPath.setAttribute('stroke', 'none');
this.svgPath.setAttribute('fill', this.style.colourSecondary);
}
};
/**
* @override
*/
Blockly.geras.PathObject.prototype.updateDisabled_ = function(disabled) {
Blockly.geras.PathObject.superClass_.updateDisabled_.call(this, disabled);
if (disabled) {
this.svgPath.setAttribute('stroke', 'none');
}
};

View File

@@ -35,12 +35,13 @@ goog.require('Blockly.utils.object');
/**
* The geras renderer.
* @param {string} name The renderer name.
* @package
* @constructor
* @extends {Blockly.blockRendering.Renderer}
*/
Blockly.geras.Renderer = function() {
Blockly.geras.Renderer.superClass_.constructor.call(this);
Blockly.geras.Renderer = function(name) {
Blockly.geras.Renderer.superClass_.constructor.call(this, name);
/**
* The renderer's highlight constant provider.
@@ -98,12 +99,15 @@ Blockly.geras.Renderer.prototype.makeDrawer_ = function(block, info) {
/**
* Create a new instance of a renderer path object.
* @param {!SVGElement} root The root SVG element.
* @param {!Blockly.Theme.BlockStyle} style The style object to use for
* colouring.
* @return {!Blockly.geras.PathObject} The renderer path object.
* @package
* @override
*/
Blockly.geras.Renderer.prototype.makePathObject = function(root) {
return new Blockly.geras.PathObject(root);
Blockly.geras.Renderer.prototype.makePathObject = function(root, style) {
return new Blockly.geras.PathObject(root, style,
/** @type {!Blockly.geras.ConstantProvider} */ (this.getConstants()));
};
/**
@@ -112,7 +116,7 @@ Blockly.geras.Renderer.prototype.makePathObject = function(root) {
* provider.
* @protected
*/
Blockly.blockRendering.Renderer.prototype.makeHighlightConstants_ = function() {
Blockly.geras.Renderer.prototype.makeHighlightConstants_ = function() {
return new Blockly.geras.HighlightConstantProvider(
/** @type {!Blockly.blockRendering.ConstantProvider} */
(this.getConstants()));

View File

@@ -36,7 +36,7 @@ goog.require('Blockly.utils.object');
* the block.
* @param {!Blockly.blockRendering.ConstantProvider} constants The rendering
* constants provider.
* @param {Blockly.RenderedConnection} connectionModel The connection object on
* @param {!Blockly.RenderedConnection} connectionModel The connection object on
* the block that this represents.
* @package
* @constructor
@@ -47,6 +47,7 @@ Blockly.blockRendering.Connection = function(constants, connectionModel) {
constants);
this.connectionModel = connectionModel;
this.shape = this.constants_.shapeFor(connectionModel);
this.isDynamicShape = !!this.shape['isDynamic'];
this.type |= Blockly.blockRendering.Types.CONNECTION;
};
Blockly.utils.object.inherits(Blockly.blockRendering.Connection,
@@ -67,22 +68,17 @@ Blockly.blockRendering.OutputConnection = function(constants, connectionModel) {
Blockly.blockRendering.OutputConnection.superClass_.constructor.call(this,
constants, connectionModel);
this.type |= Blockly.blockRendering.Types.OUTPUT_CONNECTION;
this.height = this.shape.height;
this.width = this.shape.width;
this.connectionOffsetY = this.constants_.TAB_OFFSET_FROM_TOP;
this.height = !this.isDynamicShape ? this.shape.height : 0;
this.width = !this.isDynamicShape ? this.shape.width : 0;
this.startX = this.width;
this.connectionOffsetY = this.constants_.TAB_OFFSET_FROM_TOP;
this.connectionOffsetX = 0;
};
Blockly.utils.object.inherits(Blockly.blockRendering.OutputConnection,
Blockly.blockRendering.Connection);
/**
* Whether or not the connection shape is dynamic. Dynamic shapes get their
* height from the block.
* @return {boolean} True if the connection shape is dynamic.
*/
Blockly.blockRendering.OutputConnection.prototype.isDynamic = function() {
return this.shape.isDynamic;
};
/**
* An object containing information about the space a previous connection takes

View File

@@ -61,8 +61,6 @@ Blockly.blockRendering.InputConnection = function(constants, input) {
this.connectedBlockHeight = 0;
}
// TODO: change references to connectionModel, since that's on Connection.
this.connection = input.connection;
this.connectionOffsetX = 0;
this.connectionOffsetY = 0;
};
@@ -87,8 +85,7 @@ Blockly.blockRendering.InlineInput = function(constants, input) {
if (!this.connectedBlock) {
this.height = this.constants_.EMPTY_INLINE_INPUT_HEIGHT;
this.width = this.shape.width +
this.constants_.EMPTY_INLINE_INPUT_PADDING;
this.width = this.constants_.EMPTY_INLINE_INPUT_PADDING;
} else {
// We allow the dark path to show on the parent block so that the child
// block looks embossed. This takes up an extra pixel in both x and y.
@@ -96,9 +93,18 @@ Blockly.blockRendering.InlineInput = function(constants, input) {
this.height = this.connectedBlockHeight;
}
this.connectionOffsetY = this.constants_.TAB_OFFSET_FROM_TOP;
this.connectionHeight = this.shape.height;
this.connectionWidth = this.shape.width;
this.connectionHeight = !this.isDynamicShape ? this.shape.height :
this.shape.height(this.height);
this.connectionWidth = !this.isDynamicShape ? this.shape.width :
this.shape.width(this.height);
if (!this.connectedBlock) {
this.width += this.connectionWidth * (this.isDynamicShape ? 2 : 1);
}
this.connectionOffsetY = this.isDynamicShape ?
this.shape.connectionOffsetY(this.connectionHeight) :
this.constants_.TAB_OFFSET_FROM_TOP;
this.connectionOffsetX = this.isDynamicShape ?
this.shape.connectionOffsetX(this.connectionWidth) : 0;
};
Blockly.utils.object.inherits(Blockly.blockRendering.InlineInput,
Blockly.blockRendering.InputConnection);
@@ -127,8 +133,7 @@ Blockly.blockRendering.StatementInput = function(constants, input) {
this.height =
this.connectedBlockHeight + this.constants_.STATEMENT_BOTTOM_SPACER;
}
this.width = this.constants_.NOTCH_OFFSET_LEFT +
this.shape.width;
this.width = this.constants_.STATEMENT_INPUT_NOTCH_OFFSET + this.shape.width;
};
Blockly.utils.object.inherits(Blockly.blockRendering.StatementInput,
Blockly.blockRendering.InputConnection);
@@ -148,12 +153,12 @@ Blockly.blockRendering.ExternalValueInput = function(constants, input) {
Blockly.blockRendering.ExternalValueInput.superClass_.constructor.call(this,
constants, input);
this.type |= Blockly.blockRendering.Types.EXTERNAL_VALUE_INPUT;
if (!this.connectedBlock) {
this.height = this.shape.height;
} else {
this.height =
this.connectedBlockHeight - 2 * this.constants_.TAB_OFFSET_FROM_TOP;
this.connectedBlockHeight - this.constants_.TAB_OFFSET_FROM_TOP -
this.constants_.MEDIUM_PADDING;
}
this.width = this.shape.width +
this.constants_.EXTERNAL_VALUE_INPUT_PADDING;

View File

@@ -91,7 +91,7 @@ Blockly.utils.object.inherits(Blockly.blockRendering.JaggedEdge,
Blockly.blockRendering.Field = function(constants, field, parentInput) {
Blockly.blockRendering.Field.superClass_.constructor.call(this, constants);
this.field = field;
this.isEditable = field.isCurrentlyEditable();
this.isEditable = field.EDITABLE;
this.flipRtl = field.getFlipRtl();
this.type |= Blockly.blockRendering.Types.FIELD;

View File

@@ -154,6 +154,13 @@ Blockly.blockRendering.Row = function(constants) {
this.constants_ = constants;
this.notchOffset = this.constants_.NOTCH_OFFSET_LEFT;
/**
* Alignment of the row.
* @package
* @type {?number}
*/
this.align = null;
};
/**
@@ -280,17 +287,28 @@ Blockly.utils.object.inherits(Blockly.blockRendering.TopRow,
/**
* Returns whether or not the top row has a left square corner.
* @param {!Blockly.BlockSvg} block The block whose top row this represents.
* @returns {boolean} Whether or not the top row has a left square corner.
* @return {boolean} Whether or not the top row has a left square corner.
*/
Blockly.blockRendering.TopRow.prototype.hasLeftSquareCorner = function(block) {
var hasHat = (block.hat ? block.hat === 'cap' : Blockly.BlockSvg.START_HAT) &&
!block.outputConnection && !block.previousConnection;
var hasHat = (typeof block.hat !== 'undefined' ?
block.hat === 'cap' : this.constants_.ADD_START_HATS) &&
!block.outputConnection && !block.previousConnection;
var prevBlock = block.getPreviousBlock();
return !!block.outputConnection ||
hasHat || (prevBlock ? prevBlock.getNextBlock() == block : false);
};
/**
* Returns whether or not the top row has a right square corner.
* @param {!Blockly.BlockSvg} _block The block whose top row this represents.
* @return {boolean} Whether or not the top row has a right square corner.
*/
Blockly.blockRendering.TopRow.prototype.hasRightSquareCorner = function(
_block) {
return true;
};
/**
* @override
*/
@@ -322,6 +340,13 @@ Blockly.blockRendering.TopRow.prototype.startsWithElemSpacer = function() {
return false;
};
/**
* @override
*/
Blockly.blockRendering.TopRow.prototype.endsWithElemSpacer = function() {
return false;
};
/**
* An object containing information about what elements are in the bottom row of
* a block as well as spacing information for the top row.
@@ -373,13 +398,23 @@ Blockly.utils.object.inherits(Blockly.blockRendering.BottomRow,
/**
* Returns whether or not the bottom row has a left square corner.
* @param {!Blockly.BlockSvg} block The block whose bottom row this represents.
* @returns {boolean} Whether or not the bottom row has a left square corner.
* @return {boolean} Whether or not the bottom row has a left square corner.
*/
Blockly.blockRendering.BottomRow.prototype.hasLeftSquareCorner = function(
block) {
return !!block.outputConnection || !!block.getNextBlock();
};
/**
* Returns whether or not the bottom row has a right square corner.
* @param {!Blockly.BlockSvg} _block The block whose bottom row this represents.
* @return {boolean} Whether or not the bottom row has a right square corner.
*/
Blockly.blockRendering.BottomRow.prototype.hasRightSquareCorner = function(
_block) {
return true;
};
/**
* @override
*/
@@ -412,6 +447,13 @@ Blockly.blockRendering.BottomRow.prototype.startsWithElemSpacer = function() {
return false;
};
/**
* @override
*/
Blockly.blockRendering.BottomRow.prototype.endsWithElemSpacer = function() {
return false;
};
/**
* An object containing information about a spacer between two rows.
* @param {!Blockly.blockRendering.ConstantProvider} constants The rendering
@@ -481,7 +523,8 @@ Blockly.blockRendering.InputRow.prototype.measure = function() {
connectedBlockWidths += elem.connectedBlockWidth;
} else if (Blockly.blockRendering.Types.isExternalInput(elem) &&
elem.connectedBlockWidth != 0) {
connectedBlockWidths += (elem.connectedBlockWidth - elem.connectionWidth);
connectedBlockWidths += (elem.connectedBlockWidth -
elem.connectionWidth);
}
}
if (!(Blockly.blockRendering.Types.isSpacer(elem))) {

View File

@@ -28,34 +28,33 @@ goog.provide('Blockly.blockRendering.Types');
/**
* Types of rendering elements.
* @enum {number}
* @package
*/
Blockly.blockRendering.Types = {
NONE: 0, // None
FIELD: 1 << 0, // Field.
HAT: 1 << 1, // Hat.
ICON: 1 << 2, // Icon.
SPACER: 1 << 3, // Spacer.
BETWEEN_ROW_SPACER: 1 << 4, // Between Row Spacer.
IN_ROW_SPACER: 1 << 5, // In Row Spacer.
NONE: 0, // None
FIELD: 1 << 0, // Field.
HAT: 1 << 1, // Hat.
ICON: 1 << 2, // Icon.
SPACER: 1 << 3, // Spacer.
BETWEEN_ROW_SPACER: 1 << 4, // Between Row Spacer.
IN_ROW_SPACER: 1 << 5, // In Row Spacer.
EXTERNAL_VALUE_INPUT: 1 << 6, // External Value Input.
INPUT: 1 << 7, // Input
INLINE_INPUT: 1 << 8, // Inline Input.
STATEMENT_INPUT: 1 << 9, // Statement Input.
CONNECTION: 1 << 10, // Connection.
INPUT: 1 << 7, // Input.
INLINE_INPUT: 1 << 8, // Inline Input.
STATEMENT_INPUT: 1 << 9, // Statement Input.
CONNECTION: 1 << 10, // Connection.
PREVIOUS_CONNECTION: 1 << 11, // Previous Connection.
NEXT_CONNECTION: 1 << 12, // Next Connection.
OUTPUT_CONNECTION: 1 << 13, // Output Connection.
CORNER: 1 << 14, // Corner.
LEFT_SQUARE_CORNER: 1 << 15, // Square Corner.
LEFT_ROUND_CORNER: 1 << 16, // Round Corner.
NEXT_CONNECTION: 1 << 12, // Next Connection.
OUTPUT_CONNECTION: 1 << 13, // Output Connection.
CORNER: 1 << 14, // Corner.
LEFT_SQUARE_CORNER: 1 << 15, // Square Corner.
LEFT_ROUND_CORNER: 1 << 16, // Round Corner.
RIGHT_SQUARE_CORNER: 1 << 17, // Right Square Corner.
RIGHT_ROUND_CORNER: 1 << 18, // Right Round Corner.
JAGGED_EDGE: 1 << 19, // Jagged Edge.
ROW: 1 << 20, // Row
TOP_ROW: 1 << 21, // Top Row.
BOTTOM_ROW: 1 << 22, // Bototm Row.
INPUT_ROW: 1 << 23, // Input Row.
RIGHT_ROUND_CORNER: 1 << 18, // Right Round Corner.
JAGGED_EDGE: 1 << 19, // Jagged Edge.
ROW: 1 << 20, // Row.
TOP_ROW: 1 << 21, // Top Row.
BOTTOM_ROW: 1 << 22, // Bottom Row.
INPUT_ROW: 1 << 23 // Input Row.
};
/**
@@ -135,7 +134,8 @@ Blockly.blockRendering.Types.isIcon = function(elem) {
/**
* Whether a measurable stores information about a spacer.
* @param {!Blockly.blockRendering.Measurable} elem The element to check.
* @param {!Blockly.blockRendering.Measurable|!Blockly.blockRendering.Row} elem
* The element to check.
* @return {number} 1 if the object stores information about a spacer.
* @package
*/

View File

@@ -32,12 +32,13 @@ goog.require('Blockly.minimalist.RenderInfo');
/**
* The minimalist renderer.
* @param {string} name The renderer name.
* @package
* @constructor
* @extends {Blockly.blockRendering.Renderer}
*/
Blockly.minimalist.Renderer = function() {
Blockly.minimalist.Renderer.superClass_.constructor.call(this);
Blockly.minimalist.Renderer = function(name) {
Blockly.minimalist.Renderer.superClass_.constructor.call(this, name);
};
Blockly.utils.object.inherits(Blockly.minimalist.Renderer,
Blockly.blockRendering.Renderer);

View File

@@ -114,7 +114,8 @@ Blockly.thrasos.RenderInfo.prototype.addElemSpacing_ = function() {
Blockly.thrasos.RenderInfo.prototype.getInRowSpacing_ = function(prev, next) {
if (!prev) {
// Between an editable field and the beginning of the row.
if (next && Blockly.blockRendering.Types.isField(next) && next.isEditable) {
if (next && Blockly.blockRendering.Types.isField(next) &&
(/** @type Blockly.blockRendering.Field */ (next)).isEditable) {
return this.constants_.MEDIUM_PADDING;
}
// Inline input at the beginning of the row.
@@ -131,7 +132,8 @@ Blockly.thrasos.RenderInfo.prototype.getInRowSpacing_ = function(prev, next) {
// Spacing between a non-input and the end of the row.
if (!Blockly.blockRendering.Types.isInput(prev) && !next) {
// Between an editable field and the end of the row.
if (Blockly.blockRendering.Types.isField(prev) && prev.isEditable) {
if (Blockly.blockRendering.Types.isField(prev) &&
(/** @type Blockly.blockRendering.Field */ (prev)).isEditable) {
return this.constants_.MEDIUM_PADDING;
}
// Padding at the end of an icon-only row to make the block shape clearer.
@@ -172,7 +174,8 @@ Blockly.thrasos.RenderInfo.prototype.getInRowSpacing_ = function(prev, next) {
if (!Blockly.blockRendering.Types.isInput(prev) &&
next && Blockly.blockRendering.Types.isInput(next)) {
// Between an editable field and an input.
if (prev.isEditable) {
if (Blockly.blockRendering.Types.isField(prev) &&
(/** @type Blockly.blockRendering.Field */ (prev)).isEditable) {
if (Blockly.blockRendering.Types.isInlineInput(next)) {
return this.constants_.SMALL_PADDING;
} else if (Blockly.blockRendering.Types.isExternalInput(next)) {
@@ -198,9 +201,9 @@ Blockly.thrasos.RenderInfo.prototype.getInRowSpacing_ = function(prev, next) {
// Spacing between an inline input and a field.
if (Blockly.blockRendering.Types.isInlineInput(prev) &&
next && !Blockly.blockRendering.Types.isInput(next)) {
next && Blockly.blockRendering.Types.isField(next)) {
// Editable field after inline input.
if (next.isEditable) {
if ((/** @type Blockly.blockRendering.Field */ (next)).isEditable) {
return this.constants_.MEDIUM_PADDING;
} else {
// Noneditable field after inline input.
@@ -226,9 +229,10 @@ Blockly.thrasos.RenderInfo.prototype.getInRowSpacing_ = function(prev, next) {
}
// Spacing between two fields of the same editability.
if (!Blockly.blockRendering.Types.isInput(prev) &&
next && !Blockly.blockRendering.Types.isInput(next) &&
(prev.isEditable == next.isEditable)) {
if (Blockly.blockRendering.Types.isField(prev) &&
next && Blockly.blockRendering.Types.isField(next) &&
((/** @type Blockly.blockRendering.Field */ (prev)).isEditable ==
(/** @type Blockly.blockRendering.Field */ (next)).isEditable)) {
return this.constants_.LARGE_PADDING;
}
@@ -240,34 +244,6 @@ Blockly.thrasos.RenderInfo.prototype.getInRowSpacing_ = function(prev, next) {
return this.constants_.MEDIUM_PADDING;
};
/**
* @override
*/
Blockly.thrasos.RenderInfo.prototype.addAlignmentPadding_ = function(row, missingSpace) {
var firstSpacer = row.getFirstSpacer();
var lastSpacer = row.getLastSpacer();
if (row.hasExternalInput || row.hasStatement) {
row.widthWithConnectedBlocks += missingSpace;
}
// Decide where the extra padding goes.
if (row.align == Blockly.ALIGN_LEFT) {
// Add padding to the end of the row.
lastSpacer.width += missingSpace;
} else if (row.align == Blockly.ALIGN_CENTRE) {
// Split the padding between the beginning and end of the row.
firstSpacer.width += missingSpace / 2;
lastSpacer.width += missingSpace / 2;
} else if (row.align == Blockly.ALIGN_RIGHT) {
// Add padding at the beginning of the row.
firstSpacer.width += missingSpace;
} else {
// Default to left-aligning.
lastSpacer.width += missingSpace;
}
row.width += missingSpace;
};
/**
* @override
*/
@@ -357,6 +333,13 @@ Blockly.thrasos.RenderInfo.prototype.finalize_ = function() {
}
this.recordElemPositions_(row);
}
if (this.outputConnection && this.block_.nextConnection &&
this.block_.nextConnection.isConnected()) {
// Include width of connected block in value to stack width measurement.
widestRowWithConnectedBlocks =
Math.max(widestRowWithConnectedBlocks,
this.block_.nextConnection.targetBlock().getHeightWidth().width);
}
this.bottomRow.baseline = yCursor - this.bottomRow.descenderHeight;
this.widthWithChildren = widestRowWithConnectedBlocks + this.startX;

View File

@@ -30,12 +30,13 @@ goog.require('Blockly.utils.object');
/**
* The thrasos renderer.
* @param {string} name The renderer name.
* @package
* @constructor
* @extends {Blockly.blockRendering.Renderer}
*/
Blockly.thrasos.Renderer = function() {
Blockly.thrasos.Renderer.superClass_.constructor.call(this);
Blockly.thrasos.Renderer = function(name) {
Blockly.thrasos.Renderer.superClass_.constructor.call(this, name);
};
Blockly.utils.object.inherits(Blockly.thrasos.Renderer,
Blockly.blockRendering.Renderer);

View File

@@ -25,6 +25,7 @@
goog.provide('Blockly.zelos.ConstantProvider');
goog.require('Blockly.blockRendering.ConstantProvider');
goog.require('Blockly.utils.dom');
goog.require('Blockly.utils.object');
goog.require('Blockly.utils.svgPaths');
@@ -40,6 +41,26 @@ Blockly.zelos.ConstantProvider = function() {
this.GRID_UNIT = 4;
/**
* @override
*/
this.SMALL_PADDING = this.GRID_UNIT;
/**
* @override
*/
this.MEDIUM_PADDING = 2 * this.GRID_UNIT;
/**
* @override
*/
this.MEDIUM_LARGE_PADDING = 3 * this.GRID_UNIT;
/**
* @override
*/
this.LARGE_PADDING = 4 * this.GRID_UNIT;
/**
* @override
*/
@@ -59,17 +80,313 @@ Blockly.zelos.ConstantProvider = function() {
* @override
*/
this.NOTCH_OFFSET_LEFT = 3 * this.GRID_UNIT;
/**
* @override
*/
this.STATEMENT_INPUT_NOTCH_OFFSET = this.NOTCH_OFFSET_LEFT;
/**
* @override
*/
this.MIN_BLOCK_WIDTH = 2 * this.GRID_UNIT;
/**
* @override
*/
this.MIN_BLOCK_HEIGHT = 12 * this.GRID_UNIT;
/**
* @override
*/
this.EMPTY_STATEMENT_INPUT_HEIGHT = 6 * this.GRID_UNIT;
/**
* @override
*/
this.TAB_OFFSET_FROM_TOP = 0;
/**
* @override
*/
this.TOP_ROW_MIN_HEIGHT = this.GRID_UNIT;
/**
* @override
*/
this.TOP_ROW_PRECEDES_STATEMENT_MIN_HEIGHT = this.LARGE_PADDING;
/**
* @override
*/
this.BOTTOM_ROW_MIN_HEIGHT = this.GRID_UNIT;
/**
* @override
*/
this.BOTTOM_ROW_AFTER_STATEMENT_MIN_HEIGHT = 6 * this.GRID_UNIT;
/**
* @override
*/
this.STATEMENT_BOTTOM_SPACER = -this.NOTCH_HEIGHT;
/**
* Minimum statement input spacer width.
* @type {number}
*/
this.STATEMENT_INPUT_SPACER_MIN_WIDTH = 40 * this.GRID_UNIT;
/**
* @override
*/
this.STATEMENT_INPUT_PADDING_LEFT = 4 * this.GRID_UNIT;
/**
* @override
*/
this.EMPTY_INLINE_INPUT_PADDING = 4 * this.GRID_UNIT;
/**
* @override
*/
this.EMPTY_INLINE_INPUT_HEIGHT = 8 * this.GRID_UNIT;
/**
* @override
*/
this.DUMMY_INPUT_MIN_HEIGHT = 8 * this.GRID_UNIT;
/**
* @override
*/
this.DUMMY_INPUT_SHADOW_MIN_HEIGHT = 6 * this.GRID_UNIT;
/**
* @override
*/
this.CURSOR_WS_WIDTH = 20 * this.GRID_UNIT;
/**
* @override
*/
this.CURSOR_COLOUR = '#ffa200';
/**
* Radius of the cursor for input and output connections.
* @type {number}
* @package
*/
this.CURSOR_RADIUS = 5;
/**
* @override
*/
this.JAGGED_TEETH_HEIGHT = 0;
/**
* @override
*/
this.JAGGED_TEETH_WIDTH = 0;
/**
* @override
*/
this.START_HAT_HEIGHT = 22;
/**
* @override
*/
this.START_HAT_WIDTH = 96;
/**
* @enum {number}
* @override
*/
this.SHAPES = {
HEXAGONAL: 1,
ROUND: 2,
SQUARE: 3,
PUZZLE: 4,
NOTCH: 5
};
/**
* Map of output/input shapes and the amount they should cause a block to be
* padded. Outer key is the outer shape, inner key is the inner shape.
* When a block with the outer shape contains an input block with the inner
* shape on its left or right edge, the block elements are aligned such that
* the padding specified is reached.
* @package
*/
this.SHAPE_IN_SHAPE_PADDING = {
1: { // Outer shape: hexagon.
0: 5 * this.GRID_UNIT, // Field in hexagon.
1: 2 * this.GRID_UNIT, // Hexagon in hexagon.
2: 5 * this.GRID_UNIT, // Round in hexagon.
3: 5 * this.GRID_UNIT // Square in hexagon.
},
2: { // Outer shape: round.
0: 3 * this.GRID_UNIT, // Field in round.
1: 3 * this.GRID_UNIT, // Hexagon in round.
2: 1 * this.GRID_UNIT, // Round in round.
3: 2 * this.GRID_UNIT // Square in round.
},
3: { // Outer shape: square.
0: 2 * this.GRID_UNIT, // Field in square.
1: 2 * this.GRID_UNIT, // Hexagon in square.
2: 2 * this.GRID_UNIT, // Round in square.
3: 2 * this.GRID_UNIT // Square in square.
}
};
/**
* @override
*/
this.FULL_BLOCK_FIELDS = true;
/**
* @override
*/
this.FIELD_TEXT_FONTSIZE = 3 * this.GRID_UNIT;
/**
* @override
*/
this.FIELD_TEXT_FONTWEIGHT = 'bold';
/**
* @override
*/
this.FIELD_TEXT_FONTFAMILY =
'"Helvetica Neue", "Segoe UI", Helvetica, sans-serif';
/**
* @override
*/
this.FIELD_TEXT_HEIGHT = 13.1;
/**
* Used by positioning text on IE and Edge as they don't support
* dominant-baseline:center.
* @override
*/
this.FIELD_TEXT_BASELINE_Y = 13.1;
/**
* @override
*/
this.FIELD_BORDER_RECT_RADIUS = this.CORNER_RADIUS;
/**
* @override
*/
this.FIELD_BORDER_RECT_X_PADDING = 2 * this.GRID_UNIT;
/**
* @override
*/
this.FIELD_BORDER_RECT_Y_PADDING = 1 * this.GRID_UNIT;
/**
* @override
*/
this.FIELD_BORDER_RECT_HEIGHT = 8 * this.GRID_UNIT;
/**
* @override
*/
this.FIELD_DROPDOWN_BORDER_RECT_HEIGHT = 8 * this.GRID_UNIT;
/**
* @override
*/
this.FIELD_DROPDOWN_NO_BORDER_RECT_SHADOW = true;
/**
* @override
*/
this.FIELD_DROPDOWN_COLOURED_DIV = true;
/**
* @override
*/
this.FIELD_DROPDOWN_SVG_ARROW = true;
/**
* @override
*/
this.FIELD_DROPDOWN_SVG_ARROW_PADDING = this.FIELD_BORDER_RECT_X_PADDING;
/**
* @override
*/
this.FIELD_TEXTINPUT_BOX_SHADOW = true;
/**
* @override
*/
this.FIELD_TEXT_Y_OFFSET = Blockly.utils.userAgent.CHROME ? -.45 : 0;
/**
* @override
*/
this.FIELD_COLOUR_FULL_BLOCK = true;
/**
* @override
*/
this.FIELD_COLOUR_DEFAULT_WIDTH = 2 * this.GRID_UNIT;
/**
* @override
*/
this.FIELD_COLOUR_DEFAULT_HEIGHT = 4 * this.GRID_UNIT;
/**
* @override
*/
this.FIELD_CHECKBOX_X_OFFSET = this.FIELD_BORDER_RECT_X_PADDING - 3;
/**
* @override
*/
this.FIELD_CHECKBOX_Y_OFFSET = 22;
/**
* @override
*/
this.FIELD_CHECKBOX_DEFAULT_WIDTH = 6 * this.GRID_UNIT;
/**
* The ID of the highlight glow filter, or the empty string if no filter is
* set.
* @type {string}
* @package
*/
this.highlightGlowFilterId = '';
/**
* The <filter> element to use for a higlight glow, or null if not set.
* @type {SVGElement}
* @private
*/
this.highlightGlowFilter_ = null;
/**
* The ID of the highlight glow filter, or the empty string if no filter is
* set.
* @type {string}
* @package
*/
this.replacementGlowFilterId = '';
/**
* The <filter> element to use for a higlight glow, or null if not set.
* @type {SVGElement}
* @private
*/
this.replacementGlowFilter_ = null;
};
Blockly.utils.object.inherits(Blockly.zelos.ConstantProvider,
Blockly.blockRendering.ConstantProvider);
@@ -81,30 +398,78 @@ Blockly.zelos.ConstantProvider.prototype.init = function() {
Blockly.zelos.ConstantProvider.superClass_.init.call(this);
this.HEXAGONAL = this.makeHexagonal();
this.ROUNDED = this.makeRounded();
this.SQUARED = this.makeSquared();
this.STATEMENT_INPUT_NOTCH_OFFSET += this.INSIDE_CORNERS.rightWidth;
};
/**
* @override
*/
Blockly.zelos.ConstantProvider.prototype.dispose = function() {
Blockly.zelos.ConstantProvider.superClass_.dispose.call(this);
if (this.highlightGlowFilter_) {
Blockly.utils.dom.removeNode(this.highlightGlowFilter_);
}
};
/**
* @override
*/
Blockly.zelos.ConstantProvider.prototype.makeStartHat = function() {
var height = this.START_HAT_HEIGHT;
var width = this.START_HAT_WIDTH;
var mainPath =
Blockly.utils.svgPaths.curve('c',
[
Blockly.utils.svgPaths.point(25, -height),
Blockly.utils.svgPaths.point(71, -height),
Blockly.utils.svgPaths.point(width, 0)
]);
return {
height: height,
width: width,
path: mainPath
};
};
/**
* Create sizing and path information about a hexagonal shape.
* @return {!Object} An object containing sizing and path information about
* a hexagonal shape for connections.
* @package
*/
Blockly.zelos.ConstantProvider.prototype.makeHexagonal = function() {
// The main path for the hexagonal connection shape is made out of two lines.
// The lines are defined with relative positons and require the block height.
// The 'up' and 'down' versions of the paths are the same, but the Y sign
// flips. Forward and back are the signs to use to move the cursor in the
// direction that the path is being drawn.
// flips. The 'left' and 'right' versions of the path are also the same, but
// the X sign flips.
function makeMainPath(height, up, right) {
var width = height / 2;
var forward = up ? -1 : 1;
var direction = right ? -1 : 1;
return Blockly.utils.svgPaths.lineTo(-1 * direction * width, forward * height / 2) +
Blockly.utils.svgPaths.lineTo(direction * width, forward * height / 2);
var dy = forward * height / 2;
return Blockly.utils.svgPaths.lineTo(-direction * width, dy) +
Blockly.utils.svgPaths.lineTo(direction * width, dy);
}
return {
width: 0,
height: 0,
type: this.SHAPES.HEXAGONAL,
isDynamic: true,
width: function(height) {
return height / 2;
},
height: function(height) {
return height;
},
connectionOffsetY: function(connectionHeight) {
return connectionHeight / 2;
},
connectionOffsetX: function(connectionWidth) {
return - connectionWidth;
},
pathDown: function(height) {
return makeMainPath(height, false, false);
},
@@ -121,14 +486,17 @@ Blockly.zelos.ConstantProvider.prototype.makeHexagonal = function() {
};
/**
* Create sizing and path information about a rounded shape.
* @return {!Object} An object containing sizing and path information about
* a rounded shape for connections.
* @package
*/
Blockly.zelos.ConstantProvider.prototype.makeRounded = function() {
// The main path for the rounded connection shape is made out of a single arc.
// The arc is defined with relative positions and requires the block height.
// The 'up' and 'down' versions of the paths are the same, but the Y sign
// flips. Forward and back are the signs to use to move the cursor in the
// direction that the path is being drawn.
// flips. The 'up' and 'right' versions of the path flip the sweep-flag
// which moves the arc at negative angles.
function makeMainPath(height, up, right) {
var edgeWidth = height / 2;
return Blockly.utils.svgPaths.arc('a', '0 0 ' + (up || right ? 1 : 0), edgeWidth,
@@ -136,9 +504,74 @@ Blockly.zelos.ConstantProvider.prototype.makeRounded = function() {
}
return {
width: 0,
height: 0,
type: this.SHAPES.ROUND,
isDynamic: true,
width: function(height) {
return height / 2;
},
height: function(height) {
return height;
},
connectionOffsetY: function(connectionHeight) {
return connectionHeight / 2;
},
connectionOffsetX: function(connectionWidth) {
return - connectionWidth;
},
pathDown: function(height) {
return makeMainPath(height, false, false);
},
pathUp: function(height) {
return makeMainPath(height, true, false);
},
pathRightDown: function(height) {
return makeMainPath(height, false, true);
},
pathRightUp: function(height) {
return makeMainPath(height, false, true);
},
};
};
/**
* Create sizing and path information about a squared shape.
* @return {!Object} An object containing sizing and path information about
* a squared shape for connections.
* @package
*/
Blockly.zelos.ConstantProvider.prototype.makeSquared = function() {
var radius = this.CORNER_RADIUS;
// The main path for the squared connection shape is made out of two corners
// and a single line in-between (a and v). These are defined in relative
// positions and require the height of the block.
// The 'left' and 'right' versions of the paths are the same, but the Y sign
// flips. The 'up' and 'down' versions of the path determine where the corner
// point is placed and in-turn the direction of the corners.
function makeMainPath(height, up, right) {
var innerHeight = height - radius * 2;
return Blockly.utils.svgPaths.arc('a', '0 0,1', radius,
Blockly.utils.svgPaths.point((up ? -1 : 1) * radius, (up ? -1 : 1) * radius)) +
Blockly.utils.svgPaths.lineOnAxis('v', (right ? 1 : -1) * innerHeight) +
Blockly.utils.svgPaths.arc('a', '0 0,1', radius,
Blockly.utils.svgPaths.point((up ? 1 : -1) * radius, (up ? -1 : 1) * radius));
}
return {
type: this.SHAPES.SQUARE,
isDynamic: true,
width: function(_height) {
return radius;
},
height: function(height) {
return height;
},
connectionOffsetY: function(connectionHeight) {
return connectionHeight / 2;
},
connectionOffsetX: function(connectionWidth) {
return - connectionWidth;
},
pathDown: function(height) {
return makeMainPath(height, false, false);
},
@@ -160,9 +593,21 @@ Blockly.zelos.ConstantProvider.prototype.makeRounded = function() {
Blockly.zelos.ConstantProvider.prototype.shapeFor = function(
connection) {
var checks = connection.getCheck();
if (!checks && connection.targetConnection) {
checks = connection.targetConnection.getCheck();
}
switch (connection.type) {
case Blockly.INPUT_VALUE:
case Blockly.OUTPUT_VALUE:
var outputShape = connection.getSourceBlock().getOutputShape();
// If the block has an ouput shape set, use that instead.
if (outputShape != null) {
switch (outputShape) {
case this.SHAPES.HEXAGONAL: return this.HEXAGONAL;
case this.SHAPES.ROUND: return this.ROUNDED;
case this.SHAPES.SQUARE: return this.SQUARED;
}
}
// Includes doesn't work in IE.
if (checks && checks.indexOf('Boolean') != -1) {
return this.HEXAGONAL;
@@ -173,7 +618,7 @@ Blockly.zelos.ConstantProvider.prototype.shapeFor = function(
if (checks && checks.indexOf('String') != -1) {
return this.ROUNDED;
}
return this.PUZZLE_TAB;
return this.ROUNDED;
case Blockly.PREVIOUS_STATEMENT:
case Blockly.NEXT_STATEMENT:
return this.NOTCH;
@@ -241,11 +686,11 @@ Blockly.zelos.ConstantProvider.prototype.makeNotch = function() {
);
}
// TODO: Find a relationship between width and path
var pathLeft = makeMainPath(1);
var pathRight = makeMainPath(-1);
return {
type: this.SHAPES.NOTCH,
width: width,
height: height,
pathLeft: pathLeft,
@@ -253,52 +698,6 @@ Blockly.zelos.ConstantProvider.prototype.makeNotch = function() {
};
};
/**
* @return {!Object} An object containing sizing and path information about
* outside corners.
* @package
*/
Blockly.zelos.ConstantProvider.prototype.makeOutsideCorners = function() {
var radius = this.CORNER_RADIUS;
/**
* SVG path for drawing the rounded top-left corner.
* @const
*/
var topLeft =
Blockly.utils.svgPaths.moveBy(0, radius) +
Blockly.utils.svgPaths.arc('a', '0 0,1', radius,
Blockly.utils.svgPaths.point(radius, -radius));
/**
* SVG path for drawing the rounded top-right corner.
* @const
*/
var topRight =
Blockly.utils.svgPaths.arc('a', '0 0,1', radius,
Blockly.utils.svgPaths.point(radius, radius));
/**
* SVG path for drawing the rounded bottom-left corner.
* @const
*/
var bottomLeft = Blockly.utils.svgPaths.arc('a', '0 0,1', radius,
Blockly.utils.svgPaths.point(-radius, -radius));
/**
* SVG path for drawing the rounded bottom-right corner.
* @const
*/
var bottomRight = Blockly.utils.svgPaths.arc('a', '0 0,1', radius,
Blockly.utils.svgPaths.point(-radius, radius));
return {
topLeft: topLeft,
topRight: topRight,
bottomRight: bottomRight,
bottomLeft: bottomLeft
};
};
/**
* @override
*/
@@ -328,3 +727,188 @@ Blockly.zelos.ConstantProvider.prototype.makeInsideCorners = function() {
pathBottomRight: innerBottomRightCorner
};
};
/**
* @override
*/
Blockly.zelos.ConstantProvider.prototype.generateSecondaryColour_ = function(
colour) {
return Blockly.utils.colour.blend('#000', colour, 0.15) || colour;
};
/**
* @override
*/
Blockly.zelos.ConstantProvider.prototype.generateTertiaryColour_ = function(
colour) {
return Blockly.utils.colour.blend('#000', colour, 0.25) || colour;
};
/**
* @override
*/
Blockly.zelos.ConstantProvider.prototype.createDom = function(svg) {
Blockly.zelos.ConstantProvider.superClass_.createDom.call(this, svg);
/*
<defs>
... filters go here ...
</defs>
*/
var defs = Blockly.utils.dom.createSvgElement('defs', {}, svg);
// Using a dilate distorts the block shape.
// Instead use a gaussian blur, and then set all alpha to 1 with a transfer.
var highlightGlowFilter = Blockly.utils.dom.createSvgElement('filter',
{
'id': 'blocklyHighlightGlowFilter' + this.randomIdentifier_,
'height': '160%',
'width': '180%',
y: '-30%',
x: '-40%'
},
defs);
Blockly.utils.dom.createSvgElement('feGaussianBlur',
{
'in': 'SourceGraphic',
'stdDeviation': 0.5 // TODO: configure size in theme.
},
highlightGlowFilter);
// Set all gaussian blur pixels to 1 opacity before applying flood
var highlightComponentTransfer = Blockly.utils.dom.createSvgElement(
'feComponentTransfer', {'result': 'outBlur'}, highlightGlowFilter);
Blockly.utils.dom.createSvgElement('feFuncA',
{
'type': 'table', 'tableValues': '0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1'
},
highlightComponentTransfer);
// Color the highlight
Blockly.utils.dom.createSvgElement('feFlood',
{
'flood-color': '#fff200', // TODO: configure colour in theme.
'flood-opacity': 1,
'result': 'outColor'
},
highlightGlowFilter);
Blockly.utils.dom.createSvgElement('feComposite',
{
'in': 'outColor', 'in2': 'outBlur',
'operator': 'in', 'result': 'outGlow'
},
highlightGlowFilter);
this.highlightGlowFilterId = highlightGlowFilter.id;
this.highlightGlowFilter_ = highlightGlowFilter;
// Using a dilate distorts the block shape.
// Instead use a gaussian blur, and then set all alpha to 1 with a transfer.
var replacementGlowFilter = Blockly.utils.dom.createSvgElement('filter',
{
'id': 'blocklyReplacementGlowFilter' + this.randomIdentifier_,
'height': '160%',
'width': '180%',
y: '-30%',
x: '-40%'
},
defs);
Blockly.utils.dom.createSvgElement('feGaussianBlur',
{
'in': 'SourceGraphic',
'stdDeviation': 2 // TODO: configure size in theme.
},
replacementGlowFilter);
// Set all gaussian blur pixels to 1 opacity before applying flood
var replacementComponentTransfer = Blockly.utils.dom.createSvgElement(
'feComponentTransfer', {'result': 'outBlur'}, replacementGlowFilter);
Blockly.utils.dom.createSvgElement('feFuncA',
{
'type': 'table', 'tableValues': '0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1'
},
replacementComponentTransfer);
// Color the highlight
Blockly.utils.dom.createSvgElement('feFlood',
{
'flood-color': '#fff200', // TODO: configure colour in theme.
'flood-opacity': 1,
'result': 'outColor'
},
replacementGlowFilter);
Blockly.utils.dom.createSvgElement('feComposite',
{
'in': 'outColor', 'in2': 'outBlur',
'operator': 'in', 'result': 'outGlow'
},
replacementGlowFilter);
Blockly.utils.dom.createSvgElement('feComposite',
{
'in': 'SourceGraphic', 'in2': 'outGlow',
'operator': 'over',
},
replacementGlowFilter);
this.replacementGlowFilterId = replacementGlowFilter.id;
this.replacementGlowFilter_ = replacementGlowFilter;
};
/**
* @override
*/
Blockly.zelos.ConstantProvider.prototype.getCSS_ = function(name) {
var selector = '.' + name + '-renderer';
return [
/* eslint-disable indent */
// Fields.
selector + ' .blocklyText {',
'fill: #fff;',
'font-family: ' + this.FIELD_TEXT_FONTFAMILY + ';',
'font-size: ' + this.FIELD_TEXT_FONTSIZE + 'pt;',
'font-weight: ' + this.FIELD_TEXT_FONTWEIGHT + ';',
'}',
selector + ' .blocklyNonEditableText>rect:not(.blocklyDropdownRect),',
selector + ' .blocklyEditableText>rect:not(.blocklyDropdownRect) {',
'fill: ' + this.FIELD_BORDER_RECT_COLOUR + ';',
'}',
selector + ' .blocklyNonEditableText>text,',
selector + ' .blocklyEditableText>text,',
selector + ' .blocklyNonEditableText>g>text,',
selector + ' .blocklyEditableText>g>text {',
'fill: #575E75;',
'}',
// Editable field hover.
selector + ' .blocklyDraggable:not(.blocklyDisabled)',
' .blocklyEditableText:not(.editing):hover>rect ,',
selector + ' .blocklyDraggable:not(.blocklyDisabled)',
' .blocklyEditableText:not(.editing):hover>.blocklyPath {',
'stroke: #fff;',
'stroke-width: 2;',
'}',
// Text field input.
selector + ' .blocklyHtmlInput {',
'font-family: ' + this.FIELD_TEXT_FONTFAMILY + ';',
'font-weight: ' + this.FIELD_TEXT_FONTWEIGHT + ';',
'color: #575E75;',
'}',
// Dropdown field.
selector + ' .blocklyDropdownText {',
'fill: #fff !important;',
'}',
// Widget and Dropdown Div
selector + '.blocklyWidgetDiv .goog-menuitem,',
selector + '.blocklyDropDownDiv .goog-menuitem {',
'font-family: ' + this.FIELD_TEXT_FONTFAMILY + ';',
'}',
selector + '.blocklyDropDownDiv .goog-menuitem-content {',
'color: #fff;',
'}',
// Connection highlight.
selector + ' .blocklyHighlightedConnectionPath {',
'stroke: #fff200;',
'}',
// Disabled outline paths.
selector + ' .blocklyDisabled > .blocklyOutlinePath {',
'fill: url(#blocklyDisabledPattern' + this.randomIdentifier_ + ')',
'}',
/* eslint-enable indent */
];
};

View File

@@ -29,6 +29,8 @@ goog.require('Blockly.blockRendering.Types');
goog.require('Blockly.utils.object');
goog.require('Blockly.zelos.RenderInfo');
goog.requireType('Blockly.zelos.PathObject');
/**
* An object that draws a block based on the given rendering information.
@@ -46,12 +48,39 @@ Blockly.utils.object.inherits(Blockly.zelos.Drawer,
Blockly.blockRendering.Drawer);
/**
* @override
*/
Blockly.zelos.Drawer.prototype.draw = function() {
var pathObject =
/** @type {!Blockly.zelos.PathObject} */ (this.block_.pathObject);
pathObject.beginDrawing();
this.hideHiddenIcons_();
this.drawOutline_();
this.drawInternals_();
pathObject.setPath(this.outlinePath_ + '\n' + this.inlinePath_);
if (this.info_.RTL) {
pathObject.flipRTL();
}
if (Blockly.blockRendering.useDebugger) {
this.block_.renderingDebugger.drawDebug(this.block_, this.info_);
}
this.recordSizeOnBlock_();
if (this.info_.outputConnection) {
// Store the output connection shape type for parent blocks to use during
// rendering.
pathObject.outputShapeType = this.info_.outputConnection.shape.type;
}
pathObject.endDrawing();
};
/**
* @override
*/
Blockly.zelos.Drawer.prototype.drawOutline_ = function() {
if (this.info_.outputConnection &&
this.info_.outputConnection.isDynamic()) {
this.info_.outputConnection.isDynamicShape) {
this.drawFlatTop_();
this.drawRightDynamicConnection_();
this.drawFlatBottom_();
@@ -61,71 +90,6 @@ Blockly.zelos.Drawer.prototype.drawOutline_ = function() {
}
};
/**
* Add steps for the top corner of the block, taking into account
* details such as hats and rounded corners.
* @protected
*/
Blockly.zelos.Drawer.prototype.drawTop_ = function() {
var topRow = this.info_.topRow;
var elements = topRow.elements;
this.positionPreviousConnection_();
this.outlinePath_ +=
Blockly.utils.svgPaths.moveBy(topRow.xPos, this.info_.startY);
for (var i = 0, elem; (elem = elements[i]); i++) {
if (Blockly.blockRendering.Types.isLeftRoundedCorner(elem)) {
this.outlinePath_ +=
this.constants_.OUTSIDE_CORNERS.topLeft;
} else if (Blockly.blockRendering.Types.isRightRoundedCorner(elem)) {
this.outlinePath_ +=
this.constants_.OUTSIDE_CORNERS.topRight;
} else if (Blockly.blockRendering.Types.isPreviousConnection(elem)) {
this.outlinePath_ += elem.shape.pathLeft;
} else if (Blockly.blockRendering.Types.isHat(elem)) {
this.outlinePath_ += this.constants_.START_HAT.path;
} else if (Blockly.blockRendering.Types.isSpacer(elem)) {
this.outlinePath_ += Blockly.utils.svgPaths.lineOnAxis('h', elem.width);
}
// No branch for a square corner because it's a no-op.
}
this.outlinePath_ += Blockly.utils.svgPaths.lineOnAxis('v', topRow.height);
};
/**
* Add steps for the bottom edge of a block, possibly including a notch
* for the next connection
* @protected
*/
Blockly.zelos.Drawer.prototype.drawBottom_ = function() {
var bottomRow = this.info_.bottomRow;
var elems = bottomRow.elements;
this.positionNextConnection_();
var rightCornerYOffset = 0;
var outlinePath = '';
for (var i = elems.length - 1, elem; (elem = elems[i]); i--) {
if (Blockly.blockRendering.Types.isNextConnection(elem)) {
outlinePath += elem.shape.pathRight;
} else if (Blockly.blockRendering.Types.isLeftSquareCorner(elem)) {
outlinePath += Blockly.utils.svgPaths.lineOnAxis('H', bottomRow.xPos);
} else if (Blockly.blockRendering.Types.isLeftRoundedCorner(elem)) {
outlinePath += this.constants_.OUTSIDE_CORNERS.bottomLeft;
} else if (Blockly.blockRendering.Types.isRightRoundedCorner(elem)) {
outlinePath += this.constants_.OUTSIDE_CORNERS.bottomRight;
rightCornerYOffset = this.constants_.INSIDE_CORNERS.rightHeight;
} else if (Blockly.blockRendering.Types.isSpacer(elem)) {
outlinePath += Blockly.utils.svgPaths.lineOnAxis('h', elem.width * -1);
}
}
this.outlinePath_ +=
Blockly.utils.svgPaths.lineOnAxis('V',
bottomRow.baseline - rightCornerYOffset);
this.outlinePath_ += outlinePath;
};
/**
* Add steps for the right side of a row that does not have value or
* statement input connections.
@@ -134,18 +98,21 @@ Blockly.zelos.Drawer.prototype.drawBottom_ = function() {
* @protected
*/
Blockly.zelos.Drawer.prototype.drawRightSideRow_ = function(row) {
if (row.type & Blockly.blockRendering.Types.getType('BEFORE_STATEMENT_SPACER_ROW')) {
var remainingHeight = row.height - this.constants_.INSIDE_CORNERS.rightWidth;
if (row.height <= 0) {
return;
}
if (row.precedesStatement || row.followsStatement) {
var cornerHeight = this.constants_.INSIDE_CORNERS.rightHeight;
var remainingHeight = row.height -
(row.precedesStatement ? cornerHeight : 0);
this.outlinePath_ +=
(row.followsStatement ?
this.constants_.INSIDE_CORNERS.pathBottomRight : '') +
(remainingHeight > 0 ?
Blockly.utils.svgPaths.lineOnAxis('V', row.yPos + remainingHeight) : '') +
this.constants_.INSIDE_CORNERS.pathTopRight;
} else if (row.type & Blockly.blockRendering.Types.getType('AFTER_STATEMENT_SPACER_ROW')) {
var remainingHeight = row.height - this.constants_.INSIDE_CORNERS.rightWidth;
this.outlinePath_ +=
this.constants_.INSIDE_CORNERS.pathBottomRight +
(remainingHeight > 0 ?
Blockly.utils.svgPaths.lineOnAxis('V', row.yPos + row.height) : '');
Blockly.utils.svgPaths
.lineOnAxis('V', row.yPos + remainingHeight) : '') +
(row.precedesStatement ?
this.constants_.INSIDE_CORNERS.pathTopRight : '');
} else {
this.outlinePath_ +=
Blockly.utils.svgPaths.lineOnAxis('V', row.yPos + row.height);
@@ -208,6 +175,56 @@ Blockly.zelos.Drawer.prototype.drawFlatBottom_ = function() {
* @override
*/
Blockly.zelos.Drawer.prototype.drawInlineInput_ = function(input) {
// Don't draw an inline input.
this.positionInlineInputConnection_(input);
var inputName = input.input.name;
if (input.connectedBlock || this.info_.isInsertionMarker) {
return;
}
var width = input.width - (input.connectionWidth * 2);
var height = input.height;
var yPos = input.centerline - height / 2;
var connectionRight = input.xPos + input.connectionWidth;
var outlinePath = Blockly.utils.svgPaths.moveTo(connectionRight, yPos) +
Blockly.utils.svgPaths.lineOnAxis('h', width) +
input.shape.pathRightDown(input.height) +
Blockly.utils.svgPaths.lineOnAxis('h', -width) +
input.shape.pathUp(input.height) +
'z';
this.block_.pathObject.setOutlinePath(inputName, outlinePath);
};
/**
* @override
*/
Blockly.zelos.Drawer.prototype.drawStatementInput_ = function(row) {
var input = row.getLastInput();
// Where to start drawing the notch, which is on the right side in LTR.
var x = input.xPos + input.notchOffset + input.shape.width;
var innerTopLeftCorner =
input.shape.pathRight +
Blockly.utils.svgPaths.lineOnAxis('h',
-(input.notchOffset - this.constants_.INSIDE_CORNERS.width)) +
this.constants_.INSIDE_CORNERS.pathTop;
var innerHeight =
row.height - (2 * this.constants_.INSIDE_CORNERS.height);
var innerBottomLeftCorner =
this.constants_.INSIDE_CORNERS.pathBottom +
Blockly.utils.svgPaths.lineOnAxis('h',
(input.notchOffset - this.constants_.INSIDE_CORNERS.width)) +
(input.connectedBottomNextConnection ? '' : input.shape.pathLeft);
this.outlinePath_ += Blockly.utils.svgPaths.lineOnAxis('H', x) +
innerTopLeftCorner +
Blockly.utils.svgPaths.lineOnAxis('v', innerHeight) +
innerBottomLeftCorner +
Blockly.utils.svgPaths.lineOnAxis('H', row.xPos + row.width);
this.positionStatementInputConnection_(row);
};

View File

@@ -38,13 +38,12 @@ goog.require('Blockly.blockRendering.RoundCorner');
goog.require('Blockly.blockRendering.Row');
goog.require('Blockly.blockRendering.SquareCorner');
goog.require('Blockly.blockRendering.SpacerRow');
goog.require('Blockly.blockRendering.StatementInput');
goog.require('Blockly.blockRendering.TopRow');
goog.require('Blockly.blockRendering.Types');
goog.require('Blockly.utils.object');
goog.require('Blockly.zelos.AfterStatementSpacerRow');
goog.require('Blockly.zelos.BeforeStatementSpacerRow');
goog.require('Blockly.zelos.BottomRow');
goog.require('Blockly.zelos.RightConnectionShape');
goog.require('Blockly.zelos.StatementInput');
goog.require('Blockly.zelos.TopRow');
@@ -77,6 +76,25 @@ Blockly.zelos.RenderInfo = function(renderer, block) {
* @override
*/
this.bottomRow = new Blockly.zelos.BottomRow(this.constants_);
/**
* @override
*/
this.isInline = true;
/**
* Whether the block should be rendered as a multi-line block, either because
* it's not inline or because it has been collapsed.
* @type {boolean}
*/
this.isMultiRow = !block.getInputsInline() || block.isCollapsed();
/**
* An object with rendering information about the right connection shape.
* @type {Blockly.zelos.RightConnectionShape}
*/
this.rightSide = this.outputConnection ?
new Blockly.zelos.RightConnectionShape(this.constants_) : null;
};
Blockly.utils.object.inherits(Blockly.zelos.RenderInfo,
Blockly.blockRendering.RenderInfo);
@@ -91,41 +109,52 @@ Blockly.zelos.RenderInfo.prototype.getRenderer = function() {
};
/**
* Create all non-spacer elements that belong on the top row.
* @package
* @override
*/
Blockly.zelos.RenderInfo.prototype.populateTopRow_ = function() {
Blockly.zelos.RenderInfo.superClass_.populateTopRow_.call(this);
var rightSquareCorner = this.topRow.hasRightSquareCorner(this.block_);
if (rightSquareCorner) {
this.topRow.elements.push(
new Blockly.blockRendering.SquareCorner(this.constants_, 'right'));
} else {
this.topRow.elements.push(
new Blockly.blockRendering.RoundCorner(this.constants_, 'right'));
}
Blockly.zelos.RenderInfo.prototype.measure = function() {
// Modifing parent measure method to add `adjustXPosition_`.
this.createRows_();
this.addElemSpacing_();
this.addRowSpacing_();
this.adjustXPosition_();
this.computeBounds_();
this.alignRowElements_();
this.finalize_();
};
/**
* Create all non-spacer elements that belong on the bottom row.
* @package
* @override
*/
Blockly.zelos.RenderInfo.prototype.populateBottomRow_ = function() {
Blockly.zelos.RenderInfo.superClass_.populateBottomRow_.call(this);
var rightSquareCorner = this.bottomRow.hasRightSquareCorner(this.block_);
if (rightSquareCorner) {
this.bottomRow.elements.push(
new Blockly.blockRendering.SquareCorner(this.constants_, 'right'));
} else {
this.bottomRow.elements.push(
new Blockly.blockRendering.RoundCorner(this.constants_, 'right'));
Blockly.zelos.RenderInfo.prototype.shouldStartNewRow_ = function(input,
lastInput) {
// If this is the first input, just add to the existing row.
// That row is either empty or has some icons in it.
if (!lastInput) {
return false;
}
// A statement input or an input following one always gets a new row.
if (input.type == Blockly.NEXT_STATEMENT ||
lastInput.type == Blockly.NEXT_STATEMENT) {
return true;
}
// Value and dummy inputs get new row if inputs are not inlined.
if (input.type == Blockly.INPUT_VALUE || input.type == Blockly.DUMMY_INPUT) {
return !this.isInline || this.isMultiRow;
}
return false;
};
/**
* @override
*/
Blockly.zelos.RenderInfo.prototype.getDesiredRowWidth_ = function(row) {
if (row.hasStatement) {
var rightCornerWidth = this.constants_.INSIDE_CORNERS.rightWidth || 0;
return this.width - this.startX - rightCornerWidth;
}
return Blockly.zelos.RenderInfo.superClass_.getDesiredRowWidth_.call(this,
row);
};
/**
@@ -135,178 +164,31 @@ Blockly.zelos.RenderInfo.prototype.getInRowSpacing_ = function(prev, next) {
if (!prev || !next) {
// No need for padding at the beginning or end of the row if the
// output shape is dynamic.
if (this.outputConnection && this.outputConnection.isDynamic()) {
if (this.outputConnection && this.outputConnection.isDynamicShape) {
return this.constants_.NO_PADDING;
}
}
if (!prev) {
// Between an editable field and the beginning of the row.
if (next && Blockly.blockRendering.Types.isField(next) && next.isEditable) {
return this.constants_.MEDIUM_PADDING;
}
// Inline input at the beginning of the row.
if (next && Blockly.blockRendering.Types.isInlineInput(next)) {
return this.constants_.MEDIUM_LARGE_PADDING;
}
// Statement input padding.
if (next && Blockly.blockRendering.Types.isStatementInput(next)) {
return this.constants_.STATEMENT_INPUT_PADDING_LEFT;
}
// Anything else at the beginning of the row.
return this.constants_.LARGE_PADDING;
}
// Spacing between a non-input and the end of the row.
if (!Blockly.blockRendering.Types.isInput(prev) && !next) {
// Between an editable field and the end of the row.
if (Blockly.blockRendering.Types.isField(prev) && prev.isEditable) {
return this.constants_.MEDIUM_PADDING;
}
// Padding at the end of an icon-only row to make the block shape clearer.
if (Blockly.blockRendering.Types.isIcon(prev)) {
return (this.constants_.LARGE_PADDING * 2) + 1;
}
if (Blockly.blockRendering.Types.isHat(prev)) {
return this.constants_.NO_PADDING;
}
// Establish a minimum width for a block with a previous or next connection.
if (Blockly.blockRendering.Types.isPreviousOrNextConnection(prev)) {
return this.constants_.LARGE_PADDING;
}
// Between rounded corner and the end of the row.
if (Blockly.blockRendering.Types.isLeftRoundedCorner(prev)) {
return this.constants_.MIN_BLOCK_WIDTH;
}
// Between a right rounded corner and the end of the row.
if (Blockly.blockRendering.Types.isRightRoundedCorner(prev)) {
return this.constants_.NO_PADDING;
}
// Between a jagged edge and the end of the row.
if (Blockly.blockRendering.Types.isJaggedEdge(prev)) {
return this.constants_.NO_PADDING;
}
// Between noneditable fields and icons and the end of the row.
return this.constants_.LARGE_PADDING;
}
// Between inputs and the end of the row.
if (Blockly.blockRendering.Types.isInput(prev) && !next) {
if (Blockly.blockRendering.Types.isExternalInput(prev)) {
return this.constants_.NO_PADDING;
} else if (Blockly.blockRendering.Types.isInlineInput(prev)) {
return this.constants_.LARGE_PADDING;
} else if (Blockly.blockRendering.Types.isStatementInput(prev)) {
return this.constants_.NO_PADDING;
}
}
// Spacing between a non-input and an input.
if (!Blockly.blockRendering.Types.isInput(prev) &&
next && Blockly.blockRendering.Types.isInput(next)) {
// Between an editable field and an input.
if (prev.isEditable) {
if (Blockly.blockRendering.Types.isInlineInput(next)) {
return this.constants_.SMALL_PADDING;
} else if (Blockly.blockRendering.Types.isExternalInput(next)) {
return this.constants_.SMALL_PADDING;
}
} else {
if (Blockly.blockRendering.Types.isInlineInput(next)) {
return this.constants_.MEDIUM_LARGE_PADDING;
} else if (Blockly.blockRendering.Types.isExternalInput(next)) {
return this.constants_.MEDIUM_LARGE_PADDING;
} else if (Blockly.blockRendering.Types.isStatementInput(next)) {
return this.constants_.LARGE_PADDING;
}
}
return this.constants_.LARGE_PADDING - 1;
}
// Spacing between an icon and an icon or field.
if (Blockly.blockRendering.Types.isIcon(prev) &&
next && !Blockly.blockRendering.Types.isInput(next)) {
return this.constants_.LARGE_PADDING;
}
// Spacing between an inline input and a field.
if (Blockly.blockRendering.Types.isInlineInput(prev) &&
next && !Blockly.blockRendering.Types.isInput(next)) {
// Editable field after inline input.
if (next.isEditable) {
return this.constants_.MEDIUM_PADDING;
} else {
// Noneditable field after inline input.
return this.constants_.LARGE_PADDING;
}
}
if (Blockly.blockRendering.Types.isLeftSquareCorner(prev) && next) {
// Spacing between a hat and a corner
if (Blockly.blockRendering.Types.isHat(next)) {
return this.constants_.NO_PADDING;
}
// Spacing between a square corner and a previous or next connection
if (Blockly.blockRendering.Types.isPreviousConnection(next) ||
Blockly.blockRendering.Types.isNextConnection(next)) {
return next.notchOffset;
}
}
// Spacing between a rounded corner and a previous or next connection.
if (Blockly.blockRendering.Types.isLeftRoundedCorner(prev) && next) {
if (prev && Blockly.blockRendering.Types.isLeftRoundedCorner(prev) && next) {
if (Blockly.blockRendering.Types.isPreviousConnection(next) ||
Blockly.blockRendering.Types.isNextConnection(next)) {
Blockly.blockRendering.Types.isNextConnection(next)) {
return next.notchOffset - this.constants_.CORNER_RADIUS;
}
}
// Spacing between two fields of the same editability.
if (!Blockly.blockRendering.Types.isInput(prev) &&
next && !Blockly.blockRendering.Types.isInput(next) &&
(prev.isEditable == next.isEditable)) {
return this.constants_.LARGE_PADDING;
// Spacing between a square corner and a hat.
if (prev && Blockly.blockRendering.Types.isLeftSquareCorner(prev) && next &&
Blockly.blockRendering.Types.isHat(next)) {
return this.constants_.NO_PADDING;
}
// Spacing between anything and a jagged edge.
if (next && Blockly.blockRendering.Types.isJaggedEdge(next)) {
return this.constants_.LARGE_PADDING;
}
return this.constants_.MEDIUM_PADDING;
};
/**
* Create a spacer row to go between prev and next, and set its size.
* @param {?Blockly.blockRendering.Row} prev The previous row, or null.
* @param {?Blockly.blockRendering.Row} next The next row, or null.
* @return {!Blockly.blockRendering.SpacerRow} The newly created spacer row.
* @protected
*/
Blockly.zelos.RenderInfo.prototype.makeSpacerRow_ = function(prev, next) {
var height = this.getSpacerRowHeight_(prev, next);
var width = this.getSpacerRowWidth_(prev, next);
if (Blockly.blockRendering.Types.isInputRow(next) && next.hasStatement) {
var spacer =
new Blockly.zelos.BeforeStatementSpacerRow(
this.constants_,
Math.max(height, this.constants_.INSIDE_CORNERS.rightHeight || 0),
width);
} else if (Blockly.blockRendering.Types.isInputRow(prev) && prev.hasStatement) {
var spacer =
new Blockly.zelos.AfterStatementSpacerRow(
this.constants_,
Math.max(height, this.constants_.INSIDE_CORNERS.rightHeight || 0),
width);
} else {
var spacer = new Blockly.blockRendering.SpacerRow(
this.constants_, height, width);
}
if (prev.hasStatement) {
spacer.followsStatement = true;
}
return spacer;
};
/**
* @override
*/
@@ -317,30 +199,375 @@ Blockly.zelos.RenderInfo.prototype.getSpacerRowHeight_ = function(
Blockly.blockRendering.Types.isBottomRow(next)) {
return this.constants_.EMPTY_BLOCK_SPACER_HEIGHT;
}
var followsStatement =
Blockly.blockRendering.Types.isInputRow(prev) && prev.hasStatement;
var precedesStatement =
Blockly.blockRendering.Types.isInputRow(next) && next.hasStatement;
if (precedesStatement || followsStatement) {
var cornerHeight = this.constants_.INSIDE_CORNERS.rightHeight || 0;
var height = Math.max(this.constants_.MEDIUM_PADDING,
Math.max(this.constants_.NOTCH_HEIGHT, cornerHeight));
return precedesStatement && followsStatement ?
Math.max(height, this.constants_.DUMMY_INPUT_MIN_HEIGHT) : height;
}
// Top and bottom rows act as a spacer so we don't need any extra padding.
if ((Blockly.blockRendering.Types.isTopRow(prev) && !prev.hasPreviousConnection)) {
if ((Blockly.blockRendering.Types.isTopRow(prev))) {
if (!prev.hasPreviousConnection && !this.outputConnection) {
return this.constants_.SMALL_PADDING;
}
return this.constants_.NO_PADDING;
}
if ((Blockly.blockRendering.Types.isBottomRow(next) && !next.hasNextConnection)) {
if ((Blockly.blockRendering.Types.isBottomRow(next))) {
if (!this.outputConnection) {
return this.constants_.SMALL_PADDING;
}
return this.constants_.NO_PADDING;
}
return this.constants_.MEDIUM_PADDING;
};
/**
* Modify the given row to add the given amount of padding around its fields.
* The exact location of the padding is based on the alignment property of the
* last input in the field.
* @param {Blockly.blockRendering.Row} row The row to add padding to.
* @param {number} missingSpace How much padding to add.
* @protected
* @override
*/
Blockly.zelos.RenderInfo.prototype.getSpacerRowWidth_ = function(prev, next) {
var width = this.width - this.startX;
if ((Blockly.blockRendering.Types.isInputRow(prev) && prev.hasStatement) ||
(Blockly.blockRendering.Types.isInputRow(next) && next.hasStatement)) {
return Math.max(width, this.constants_.STATEMENT_INPUT_SPACER_MIN_WIDTH);
}
return width;
};
/**
* @override
*/
Blockly.zelos.RenderInfo.prototype.getElemCenterline_ = function(row, elem) {
if (row.hasStatement && !Blockly.blockRendering.Types.isSpacer(elem)) {
return row.yPos + this.constants_.EMPTY_STATEMENT_INPUT_HEIGHT / 2;
}
return Blockly.zelos.RenderInfo.superClass_.getElemCenterline_.call(this,
row, elem);
};
/**
* @override
*/
Blockly.zelos.RenderInfo.prototype.addInput_ = function(input, activeRow) {
// If we have two dummy inputs on the same row, one aligned left and the other
// right, keep track of the right aligned dummy input so we can add padding
// later.
if (input.type == Blockly.DUMMY_INPUT && activeRow.hasDummyInput &&
activeRow.align == Blockly.ALIGN_LEFT &&
input.align == Blockly.ALIGN_RIGHT) {
activeRow.rightAlignedDummyInput = input;
}
// Non-dummy inputs have visual representations onscreen.
if (this.isInline && input.type == Blockly.INPUT_VALUE) {
activeRow.elements.push(
new Blockly.blockRendering.InlineInput(this.constants_, input));
activeRow.hasInlineInput = true;
} else if (input.type == Blockly.NEXT_STATEMENT) {
activeRow.elements.push(
new Blockly.zelos.StatementInput(this.constants_, input));
activeRow.hasStatement = true;
} else if (input.type == Blockly.INPUT_VALUE) {
activeRow.elements.push(
new Blockly.blockRendering.ExternalValueInput(this.constants_, input));
activeRow.hasExternalInput = true;
} else if (input.type == Blockly.DUMMY_INPUT) {
// Dummy inputs have no visual representation, but the information is still
// important.
activeRow.minHeight = Math.max(activeRow.minHeight,
input.getSourceBlock() && input.getSourceBlock().isShadow() ?
this.constants_.DUMMY_INPUT_SHADOW_MIN_HEIGHT :
this.constants_.DUMMY_INPUT_MIN_HEIGHT);
activeRow.hasDummyInput = true;
}
if (activeRow.align == null) {
activeRow.align = input.align;
}
};
/**
* @override
*/
Blockly.zelos.RenderInfo.prototype.addAlignmentPadding_ = function(row,
missingSpace) {
var lastSpacer = row.getLastSpacer();
if (lastSpacer) {
lastSpacer.width += missingSpace;
row.width += missingSpace;
if (row.rightAlignedDummyInput) {
var alignmentDivider;
for (var i = 0, elem; (elem = row.elements[i]); i++) {
if (Blockly.blockRendering.Types.isSpacer(elem)) {
alignmentDivider = elem;
}
if (Blockly.blockRendering.Types.isField(elem) &&
elem.parentInput == row.rightAlignedDummyInput) {
break;
}
}
if (alignmentDivider) {
alignmentDivider.width += missingSpace;
row.width += missingSpace;
return;
}
}
Blockly.zelos.RenderInfo.superClass_.addAlignmentPadding_.call(this, row,
missingSpace);
};
/**
* Adjust the x position of fields to bump all non-label fields in the first row
* past the notch position. This must be called before ``computeBounds`` is
* called.
* @protected
*/
Blockly.zelos.RenderInfo.prototype.adjustXPosition_ = function() {
var notchTotalWidth = this.constants_.NOTCH_OFFSET_LEFT +
this.constants_.NOTCH_WIDTH;
var minXPos = notchTotalWidth;
// Run through every input row on the block and only apply bump logic to the
// first input row (if the block has prev connection) and every input row that
// has a prev and next notch.
for (var i = 2; i < this.rows.length - 1; i += 2) {
var prevSpacer = this.rows[i - 1];
var row = this.rows[i];
var nextSpacer = this.rows[i + 1];
var hasPrevNotch = i == 2 ?
!!this.topRow.hasPreviousConnection : !!prevSpacer.followsStatement;
var hasNextNotch = i + 2 >= this.rows.length - 1 ?
!!this.bottomRow.hasNextConnection : !!nextSpacer.precedesStatement;
if (Blockly.blockRendering.Types.isInputRow(row) && row.hasStatement) {
row.measure();
minXPos = row.width - row.getLastInput().width + notchTotalWidth;
} else if (hasPrevNotch && (i == 2 || hasNextNotch) &&
Blockly.blockRendering.Types.isInputRow(row) && !row.hasStatement) {
var xCursor = row.xPos;
var prevInRowSpacer = null;
for (var j = 0, elem; (elem = row.elements[j]); j++) {
if (Blockly.blockRendering.Types.isSpacer(elem)) {
prevInRowSpacer = elem;
}
if (prevInRowSpacer && (Blockly.blockRendering.Types.isField(elem) ||
Blockly.blockRendering.Types.isInput(elem))) {
if (xCursor < minXPos &&
!(Blockly.blockRendering.Types.isField(elem) &&
(elem.field instanceof Blockly.FieldLabel ||
elem.field instanceof Blockly.FieldImage))) {
var difference = minXPos - xCursor;
prevInRowSpacer.width += difference;
}
}
xCursor += elem.width;
}
}
}
};
/**
* Finalize the output connection info. In particular, set the height of the
* output connection to match that of the block. For the right side, add a
* right connection shape element and have it match the dimensions of the
* output connection.
* @protected
*/
Blockly.zelos.RenderInfo.prototype.finalizeOutputConnection_ = function() {
// Dynamic output connections depend on the height of the block.
if (!this.outputConnection || !this.outputConnection.isDynamicShape) {
return;
}
var yCursor = 0;
// Determine the block height.
for (var i = 0, row; (row = this.rows[i]); i++) {
row.yPos = yCursor;
yCursor += row.height;
}
this.height = yCursor;
// Adjust the height of the output connection.
var connectionHeight = this.outputConnection.shape.height(yCursor);
var connectionWidth = this.outputConnection.shape.width(yCursor);
this.outputConnection.height = connectionHeight;
this.outputConnection.width = connectionWidth;
this.outputConnection.startX = connectionWidth;
this.outputConnection.connectionOffsetY =
this.outputConnection.shape.connectionOffsetY(connectionHeight);
this.outputConnection.connectionOffsetX =
this.outputConnection.shape.connectionOffsetX(connectionWidth);
// Adjust right side measurable.
this.rightSide.height = connectionHeight;
this.rightSide.width = connectionWidth;
this.rightSide.centerline = connectionHeight / 2;
this.rightSide.xPos = this.width + connectionWidth;
this.startX = connectionWidth;
this.width += connectionWidth * 2;
this.widthWithChildren += connectionWidth * 2;
};
/**
* Finalize horizontal alignment of elements on the block. In particular,
* reduce the implicit spacing created by the left and right output connection
* shapes by adding setting negative spacing onto the leftmost and rightmost
* spacers.
* @protected
*/
Blockly.zelos.RenderInfo.prototype.finalizeHorizontalAlignment_ = function() {
if (!this.outputConnection) {
return;
}
var totalNegativeSpacing = 0;
for (var i = 0, row; (row = this.rows[i]); i++) {
if (!Blockly.blockRendering.Types.isInputRow(row)) {
continue;
}
var firstElem = row.elements[1];
var lastElem = row.elements[row.elements.length - 2];
var leftNegPadding = this.getNegativeSpacing_(firstElem);
var rightNegPadding = this.getNegativeSpacing_(lastElem);
totalNegativeSpacing = leftNegPadding + rightNegPadding;
var minBlockWidth = this.constants_.MIN_BLOCK_WIDTH +
this.outputConnection.width * 2;
if (this.width - totalNegativeSpacing < minBlockWidth) {
// Maintain a minimum block width, split negative spacing between left
// and right edge.
totalNegativeSpacing = this.width - minBlockWidth;
leftNegPadding = totalNegativeSpacing / 2;
rightNegPadding = totalNegativeSpacing / 2;
}
// Add a negative spacer on the start and end of the block.
row.elements.unshift(new Blockly.blockRendering.InRowSpacer(this.constants_,
-leftNegPadding));
row.elements.push(new Blockly.blockRendering.InRowSpacer(this.constants_,
-rightNegPadding));
}
if (totalNegativeSpacing) {
this.width -= totalNegativeSpacing;
this.widthWithChildren -= totalNegativeSpacing;
this.rightSide.xPos -= totalNegativeSpacing;
for (var i = 0, row; (row = this.rows[i]); i++) {
if (Blockly.blockRendering.Types.isTopRow(row) ||
Blockly.blockRendering.Types.isBottomRow(row)) {
row.elements[1].width -= totalNegativeSpacing;
}
row.width -= totalNegativeSpacing;
row.widthWithConnectedBlocks -= totalNegativeSpacing;
}
}
};
/**
* Calculate the spacing to reduce the left and right edges by based on the
* outer and inner connection shape.
* @param {Blockly.blockRendering.Measurable} elem The first or last element on
* a block.
* @return {number} The amount of spacing to reduce the first or last spacer.
* @protected
*/
Blockly.zelos.RenderInfo.prototype.getNegativeSpacing_ = function(elem) {
if (!elem) {
return 0;
}
var connectionWidth = this.outputConnection.width;
var outerShape = this.outputConnection.shape.type;
var constants =
/** @type {!Blockly.zelos.ConstantProvider} */ (this.constants_);
if (this.isMultiRow && this.inputRowNum_ > 1) {
switch (outerShape) {
case constants.SHAPES.ROUND:
// Special case for multi-row round reporter blocks.
var radius = this.height / 2;
var topPadding = this.constants_.SMALL_PADDING;
var roundPadding = radius *
(1 - Math.sin(Math.acos((radius - topPadding) / radius)));
return connectionWidth - roundPadding;
default:
return 0;
}
}
if (Blockly.blockRendering.Types.isInlineInput(elem)) {
var innerShape = elem.connectedBlock ?
elem.connectedBlock.pathObject.outputShapeType :
elem.shape.type;
// Special case for hexagonal output.
if (outerShape == constants.SHAPES.HEXAGONAL &&
outerShape != innerShape) {
return 0;
}
return connectionWidth -
this.constants_.SHAPE_IN_SHAPE_PADDING[outerShape][innerShape];
} else if (Blockly.blockRendering.Types.isField(elem)) {
// Special case for text inputs.
if (outerShape == constants.SHAPES.ROUND &&
elem.field instanceof Blockly.FieldTextInput) {
return connectionWidth - (2.75 * constants.GRID_UNIT);
}
return connectionWidth -
this.constants_.SHAPE_IN_SHAPE_PADDING[outerShape][0];
} else if (Blockly.blockRendering.Types.isIcon(elem)) {
return this.constants_.SMALL_PADDING;
}
return 0;
};
/**
* Finalize vertical alignment of rows on a block. In particular, reduce the
* implicit spacing when a non-shadow block is connected to any of an input
* row's inline inputs.
* @protected
*/
Blockly.zelos.RenderInfo.prototype.finalizeVerticalAlignment_ = function() {
if (this.outputConnection) {
return;
}
// Run through every input row on the block and only apply tight nesting logic
// to input rows that have a prev and next notch.
for (var i = 2; i < this.rows.length - 1; i += 2) {
var prevSpacer = this.rows[i - 1];
var row = this.rows[i];
var nextSpacer = this.rows[i + 1];
var firstRow = i == 2;
var hasPrevNotch = firstRow ?
!!this.topRow.hasPreviousConnection : !!prevSpacer.followsStatement;
var hasNextNotch = i + 2 >= this.rows.length - 1 ?
!!this.bottomRow.hasNextConnection : !!nextSpacer.precedesStatement;
if (hasPrevNotch) {
var hasSingleTextOrImageField = row.elements.length == 3 &&
(row.elements[1].field instanceof Blockly.FieldLabel ||
row.elements[1].field instanceof Blockly.FieldImage);
if (!firstRow && hasSingleTextOrImageField) {
// Remove some padding if we have a single image or text field.
prevSpacer.height -= this.constants_.SMALL_PADDING;
nextSpacer.height -= this.constants_.SMALL_PADDING;
row.height -= this.constants_.MEDIUM_PADDING;
} else if (!firstRow && !hasNextNotch) {
// Add a small padding so the notch doesn't clash with inputs/fields.
prevSpacer.height += this.constants_.SMALL_PADDING;
} else if (hasNextNotch) {
// Determine if the input row has non-shadow connected blocks.
var hasNonShadowConnectedBlocks = false;
var MIN_VERTICAL_TIGHTNESTING_HEIGHT = 40;
for (var j = 0, elem; (elem = row.elements[j]); j++) {
if (Blockly.blockRendering.Types.isInlineInput(elem) &&
elem.connectedBlock && !elem.connectedBlock.isShadow() &&
elem.connectedBlock.getHeightWidth().height >=
MIN_VERTICAL_TIGHTNESTING_HEIGHT) {
hasNonShadowConnectedBlocks = true;
break;
}
}
// Apply tight-nesting if we have both a prev and next notch and the
// block has non-shadow connected blocks.
if (hasNonShadowConnectedBlocks) {
prevSpacer.height -= this.constants_.SMALL_PADDING;
nextSpacer.height -= this.constants_.SMALL_PADDING;
}
}
}
}
};
@@ -348,38 +575,8 @@ Blockly.zelos.RenderInfo.prototype.addAlignmentPadding_ = function(row,
* @override
*/
Blockly.zelos.RenderInfo.prototype.finalize_ = function() {
// Performance note: this could be combined with the draw pass, if the time
// that this takes is excessive. But it shouldn't be, because it only
// accesses and sets properties that already exist on the objects.
var yCursor = 0;
for (var i = 0, row; (row = this.rows[i]); i++) {
row.yPos = yCursor;
yCursor += row.height;
}
// Dynamic output connections depend on the height of the block. Adjust the
// height and width of the connection, and then adjust the startX and width of the
// block accordingly.
var outputConnectionWidth = 0;
if (this.outputConnection && !this.outputConnection.height) {
this.outputConnection.height = yCursor;
outputConnectionWidth = yCursor; // Twice the width to account for the right side.
this.outputConnection.width = outputConnectionWidth / 2;
}
this.startX += outputConnectionWidth / 2;
this.width += outputConnectionWidth;
var widestRowWithConnectedBlocks = 0;
for (var i = 0, row; (row = this.rows[i]); i++) {
row.xPos = this.startX;
widestRowWithConnectedBlocks =
Math.max(widestRowWithConnectedBlocks, row.widthWithConnectedBlocks);
this.recordElemPositions_(row);
}
this.widthWithChildren = widestRowWithConnectedBlocks + this.startX;
this.height = yCursor;
this.startY = this.topRow.capline;
this.bottomRow.baseline = yCursor - this.bottomRow.descenderHeight;
this.finalizeOutputConnection_();
this.finalizeHorizontalAlignment_();
this.finalizeVerticalAlignment_();
Blockly.zelos.RenderInfo.superClass_.finalize_.call(this);
};

View File

@@ -0,0 +1,148 @@
/**
* @license
* Copyright 2019 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @fileoverview Methods for graphically rendering a marker as SVG.
* @author samelh@microsoft.com (Sam El-Husseini)
*/
'use strict';
goog.provide('Blockly.zelos.MarkerSvg');
goog.require('Blockly.blockRendering.MarkerSvg');
/**
* Class to draw a marker.
* @param {!Blockly.WorkspaceSvg} workspace The workspace the marker belongs to.
* @param {!Blockly.blockRendering.ConstantProvider} constants The constants for
* the renderer.
* @param {!Blockly.Marker} marker The marker to draw.
* @constructor
* @extends {Blockly.blockRendering.MarkerSvg}
*/
Blockly.zelos.MarkerSvg = function(workspace, constants, marker) {
Blockly.zelos.MarkerSvg.superClass_.constructor.call(
this, workspace, constants, marker);
};
Blockly.utils.object.inherits(Blockly.zelos.MarkerSvg,
Blockly.blockRendering.MarkerSvg);
/**
* @override
*/
Blockly.zelos.MarkerSvg.prototype.showWithInput_ = function(curNode) {
var block = /** @type {!Blockly.BlockSvg} */ (curNode.getSourceBlock());
var connection = curNode.getLocation();
var offsetInBlock = connection.getOffsetInBlock();
var y = offsetInBlock.y + this.constants_.CURSOR_RADIUS;
this.positionCircle_(offsetInBlock.x, y);
this.setParent_(block);
this.showCurrent_();
};
/**
* Draw a rectangle around the block.
* @param {!Blockly.ASTNode} curNode The current node of the marker.
*/
Blockly.zelos.MarkerSvg.prototype.showWithBlock_ = function(curNode) {
var block = /** @type {!Blockly.BlockSvg} */ (curNode.getLocation());
// Gets the height and width of entire stack.
var heightWidth = block.getHeightWidth();
// Add padding so that being on a stack looks different than being on a block.
this.positionRect_(0, 0, heightWidth.width, heightWidth.height);
this.setParent_(block);
this.showCurrent_();
};
/**
* Position the circle we use for input and output connections.
* @param {number} x The x position of the circle.
* @param {number} y The y position of the circle.
* @private
*/
Blockly.zelos.MarkerSvg.prototype.positionCircle_ = function(x, y) {
this.markerCircle_.setAttribute('cx', x);
this.markerCircle_.setAttribute('cy', y);
this.currentMarkerSvg = this.markerCircle_;
};
/**
* @override
*/
Blockly.zelos.MarkerSvg.prototype.showAtLocation_ = function(curNode) {
var handled = false;
if (curNode.getType() == Blockly.ASTNode.types.OUTPUT) {
// Inputs and outputs are drawn the same.
this.showWithInput_(curNode);
handled = true;
} else if (curNode.getType() == Blockly.ASTNode.types.BLOCK) {
this.showWithBlock_(curNode);
handled = true;
}
if (!handled) {
Blockly.zelos.MarkerSvg.superClass_.showAtLocation_.call(this, curNode);
}
};
/**
* @override
*/
Blockly.zelos.MarkerSvg.prototype.hide = function() {
Blockly.zelos.MarkerSvg.superClass_.hide.call(this);
this.markerCircle_.style.display = 'none';
};
/**
* @override
*/
Blockly.zelos.MarkerSvg.prototype.createDomInternal_ = function() {
/* This markup will be generated and added to the .svgGroup_:
<g>
<rect width="100" height="5">
<animate attributeType="XML" attributeName="fill" dur="1s"
values="transparent;transparent;#fff;transparent" repeatCount="indefinite" />
</rect>
</g>
*/
Blockly.zelos.MarkerSvg.superClass_.createDomInternal_.call(this);
this.markerCircle_ = Blockly.utils.dom.createSvgElement('circle', {
'r': this.constants_.CURSOR_RADIUS,
'style': 'display: none',
'fill': this.colour_,
'stroke': this.colour_,
'stroke-width': this.constants_.CURSOR_STROKE_WIDTH
},
this.markerSvg_);
// Markers and stack cursors don't blink.
if (this.isCursor()) {
var blinkProperties = this.getBlinkProperties_();
Blockly.utils.dom.createSvgElement('animate', blinkProperties,
this.markerCircle_);
}
return this.markerSvg_;
};

Some files were not shown because too many files have changed in this diff Show More