diff --git a/blockly_uncompressed.js b/blockly_uncompressed.js index 860d5eef8..97cfc3be7 100644 --- a/blockly_uncompressed.js +++ b/blockly_uncompressed.js @@ -62,7 +62,7 @@ goog.addDependency("../../../" + dir + "/core/toolbox.js", ['Blockly.Toolbox'], goog.addDependency("../../../" + dir + "/core/options.js", ['Blockly.Options'], []); goog.addDependency("../../../" + dir + "/core/block.js", ['Blockly.Block'], ['Blockly.Blocks', 'Blockly.Comment', 'Blockly.Connection', 'Blockly.Extensions', 'Blockly.Input', 'Blockly.Mutator', 'Blockly.Warning', 'Blockly.Workspace', 'Blockly.Xml', 'goog.array', 'goog.asserts', 'goog.math.Coordinate', 'goog.string']); goog.addDependency("../../../" + dir + "/core/workspace_audio.js", ['Blockly.WorkspaceAudio'], []); -goog.addDependency("../../../" + dir + "/core/block_svg.js", ['Blockly.BlockSvg'], ['Blockly.Block', 'Blockly.ContextMenu', 'Blockly.RenderedConnection', 'Blockly.Touch', 'Blockly.utils', 'goog.Timer', 'goog.asserts', 'goog.dom', 'goog.math.Coordinate', 'goog.userAgent']); +goog.addDependency("../../../" + dir + "/core/block_svg.js", ['Blockly.BlockSvg'], ['Blockly.Block', 'Blockly.ContextMenu', 'Blockly.Grid', 'Blockly.RenderedConnection', 'Blockly.Touch', 'Blockly.utils', 'goog.Timer', 'goog.asserts', 'goog.dom', 'goog.math.Coordinate', 'goog.userAgent']); goog.addDependency("../../../" + dir + "/core/flyout_dragger.js", ['Blockly.FlyoutDragger'], ['Blockly.WorkspaceDragger', 'goog.asserts', 'goog.math.Coordinate']); goog.addDependency("../../../" + dir + "/core/field_dropdown.js", ['Blockly.FieldDropdown'], ['Blockly.Field', 'goog.dom', 'goog.events', 'goog.style', 'goog.ui.Menu', 'goog.ui.MenuItem', 'goog.userAgent']); goog.addDependency("../../../" + dir + "/core/gesture.js", ['Blockly.Gesture'], ['Blockly.BlockDragger', 'Blockly.constants', 'Blockly.FlyoutDragger', 'Blockly.Tooltip', 'Blockly.Touch', 'Blockly.WorkspaceDragger', 'goog.asserts', 'goog.math.Coordinate']); @@ -78,11 +78,11 @@ goog.addDependency("../../../" + dir + "/core/constants.js", ['Blockly.constants goog.addDependency("../../../" + dir + "/core/rendered_connection.js", ['Blockly.RenderedConnection'], ['Blockly.Connection']); goog.addDependency("../../../" + dir + "/core/field_colour.js", ['Blockly.FieldColour'], ['Blockly.Field', 'goog.dom', 'goog.events', 'goog.style', 'goog.ui.ColorPicker']); goog.addDependency("../../../" + dir + "/core/field_image.js", ['Blockly.FieldImage'], ['Blockly.Field', 'goog.dom', 'goog.math.Size', 'goog.userAgent']); -goog.addDependency("../../../" + dir + "/core/field_variable.js", ['Blockly.FieldVariable'], ['Blockly.FieldDropdown', 'Blockly.Msg', 'Blockly.Variables', 'goog.asserts', 'goog.string']); +goog.addDependency("../../../" + dir + "/core/field_variable.js", ['Blockly.FieldVariable'], ['Blockly.FieldDropdown', 'Blockly.Msg', 'Blockly.VariableModel', 'Blockly.Variables', 'goog.asserts', 'goog.string']); goog.addDependency("../../../" + dir + "/core/input.js", ['Blockly.Input'], ['Blockly.Connection', 'Blockly.FieldLabel', 'goog.asserts']); goog.addDependency("../../../" + dir + "/core/field_number.js", ['Blockly.FieldNumber'], ['Blockly.FieldTextInput', 'goog.math']); -goog.addDependency("../../../" + dir + "/core/variables.js", ['Blockly.Variables'], ['Blockly.Blocks', 'Blockly.constants', 'Blockly.Workspace', 'goog.string']); -goog.addDependency("../../../" + dir + "/core/workspace_svg.js", ['Blockly.WorkspaceSvg'], ['Blockly.ConnectionDB', 'Blockly.constants', 'Blockly.Gesture', 'Blockly.Options', 'Blockly.ScrollbarPair', 'Blockly.Touch', 'Blockly.Trashcan', 'Blockly.Workspace', 'Blockly.WorkspaceAudio', 'Blockly.WorkspaceDragSurfaceSvg', 'Blockly.Xml', 'Blockly.ZoomControls', 'goog.array', 'goog.dom', 'goog.math.Coordinate', 'goog.userAgent']); +goog.addDependency("../../../" + dir + "/core/variables.js", ['Blockly.Variables'], ['Blockly.Blocks', 'Blockly.constants', 'Blockly.VariableModel', 'Blockly.Workspace', 'goog.string']); +goog.addDependency("../../../" + dir + "/core/workspace_svg.js", ['Blockly.WorkspaceSvg'], ['Blockly.ConnectionDB', 'Blockly.constants', 'Blockly.Gesture', 'Blockly.Grid', 'Blockly.Options', 'Blockly.ScrollbarPair', 'Blockly.Touch', 'Blockly.Trashcan', 'Blockly.Workspace', 'Blockly.WorkspaceAudio', 'Blockly.WorkspaceDragSurfaceSvg', 'Blockly.Xml', 'Blockly.ZoomControls', 'goog.array', 'goog.dom', 'goog.math.Coordinate', 'goog.userAgent']); goog.addDependency("../../../" + dir + "/core/bubble.js", ['Blockly.Bubble'], ['Blockly.Touch', 'Blockly.Workspace', 'goog.dom', 'goog.math', 'goog.math.Coordinate', 'goog.userAgent']); goog.addDependency("../../../" + dir + "/core/procedures.js", ['Blockly.Procedures'], ['Blockly.Blocks', 'Blockly.constants', 'Blockly.Field', 'Blockly.Names', 'Blockly.Workspace']); goog.addDependency("../../../" + dir + "/core/flyout.js", ['Blockly.Flyout'], ['Blockly.Block', 'Blockly.Comment', 'Blockly.Events', 'Blockly.FlyoutButton', 'Blockly.Gesture', 'Blockly.Touch', 'Blockly.WorkspaceSvg', 'goog.dom', 'goog.events', 'goog.math.Rect', 'goog.userAgent']); @@ -93,10 +93,11 @@ goog.addDependency("../../../" + dir + "/core/tooltip.js", ['Blockly.Tooltip'], goog.addDependency("../../../" + dir + "/core/field_angle.js", ['Blockly.FieldAngle'], ['Blockly.FieldTextInput', 'goog.math', 'goog.userAgent']); goog.addDependency("../../../" + dir + "/core/zoom_controls.js", ['Blockly.ZoomControls'], ['Blockly.Touch', 'goog.dom']); goog.addDependency("../../../" + dir + "/core/workspace.js", ['Blockly.Workspace'], ['Blockly.VariableMap', 'goog.array', 'goog.math']); +goog.addDependency("../../../" + dir + "/core/grid.js", ['Blockly.Grid'], ['Blockly.utils', 'goog.userAgent']); goog.addDependency("../../../" + dir + "/core/field_date.js", ['Blockly.FieldDate'], ['Blockly.Field', 'goog.date', 'goog.dom', 'goog.events', 'goog.i18n.DateTimeSymbols', 'goog.i18n.DateTimeSymbols_he', 'goog.style', 'goog.ui.DatePicker']); goog.addDependency("../../../" + dir + "/core/touch.js", ['Blockly.Touch'], ['goog.events', 'goog.events.BrowserFeature', 'goog.string']); goog.addDependency("../../../" + dir + "/core/generator.js", ['Blockly.Generator'], ['Blockly.Block', 'goog.asserts']); -goog.addDependency("../../../" + dir + "/core/inject.js", ['Blockly.inject'], ['Blockly.BlockDragSurfaceSvg', 'Blockly.Css', 'Blockly.Options', 'Blockly.WorkspaceSvg', 'Blockly.WorkspaceDragSurfaceSvg', 'goog.dom', 'goog.ui.Component', 'goog.userAgent']); +goog.addDependency("../../../" + dir + "/core/inject.js", ['Blockly.inject'], ['Blockly.BlockDragSurfaceSvg', 'Blockly.Css', 'Blockly.Grid', 'Blockly.Options', 'Blockly.WorkspaceSvg', 'Blockly.WorkspaceDragSurfaceSvg', 'goog.dom', 'goog.ui.Component', 'goog.userAgent']); goog.addDependency("../../../" + dir + "/core/connection_db.js", ['Blockly.ConnectionDB'], ['Blockly.Connection']); goog.addDependency("../../browser_capabilities.js", [], []); goog.addDependency("../../protractor_spec.js", [], []); @@ -1694,6 +1695,7 @@ goog.require('Blockly.FlyoutButton'); goog.require('Blockly.FlyoutDragger'); goog.require('Blockly.Generator'); goog.require('Blockly.Gesture'); +goog.require('Blockly.Grid'); goog.require('Blockly.Icon'); goog.require('Blockly.Input'); goog.require('Blockly.Msg'); diff --git a/core/block_svg.js b/core/block_svg.js index 74040ff2f..0aec6f96c 100644 --- a/core/block_svg.js +++ b/core/block_svg.js @@ -28,6 +28,7 @@ goog.provide('Blockly.BlockSvg'); goog.require('Blockly.Block'); goog.require('Blockly.ContextMenu'); +goog.require('Blockly.Grid'); goog.require('Blockly.RenderedConnection'); goog.require('Blockly.Touch'); goog.require('Blockly.utils'); @@ -404,11 +405,11 @@ Blockly.BlockSvg.prototype.snapToGrid = function() { if (this.isInFlyout) { return; // Don't move blocks around in a flyout. } - if (!this.workspace.options.gridOptions || - !this.workspace.options.gridOptions['snap']) { + var grid = this.workspace.getGrid(); + if (!grid || !grid.shouldSnap()) { return; // Config says no snapping. } - var spacing = this.workspace.options.gridOptions['spacing']; + var spacing = grid.getSpacing(); var half = spacing / 2; var xy = this.getRelativeToSurfaceXY(); var dx = Math.round((xy.x - half) / spacing) * spacing + half - xy.x; diff --git a/core/grid.js b/core/grid.js new file mode 100644 index 000000000..e87df6021 --- /dev/null +++ b/core/grid.js @@ -0,0 +1,222 @@ +/** + * @license + * Visual Blocks Editor + * + * Copyright 2017 Google Inc. + * https://developers.google.com/blockly/ + * + * 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 for configuring and updating a workspace grid in + * Blockly. + * @author fenichel@google.com (Rachel Fenichel) + */ +'use strict'; + +goog.provide('Blockly.Grid'); + +goog.require('Blockly.utils'); + +goog.require('goog.userAgent'); + + +/** + * Class for a workspace's grid. + * @param {!SVGElement} pattern The grid's SVG pattern, created during injection. + * @param {!Object} options A dictionary of normalized options for the grid. + * See grid documentation: + * https://developers.google.com/blockly/guides/configure/web/grid + * @constructor + */ +Blockly.Grid = function(pattern, options) { + /** + * The grid's SVG pattern, created during injection. + * @type {!SVGElement} + * @private + */ + this.gridPattern_ = pattern; + + /** + * The spacing of the grid lines (in px). + * @type {number} + * @private + */ + this.spacing_ = options['spacing']; + + /** + * How long the grid lines should be (in px). + * @type {number} + * @private + */ + this.length_ = options['length']; + + /** + * The horizontal grid line, if it exists. + * @type {SVGElement} + * @private + */ + this.line1_ = pattern.firstChild; + + /** + * The vertical grid line, if it exists. + * @type {SVGElement} + * @private + */ + this.line2_ = this.line1_ && this.line1_.nextSibling; + + /** + * Whether blocks should snap to the grid. + * @type {boolean} + * @private + */ + this.snapToGrid_ = options['snap']; +}; + +/** + * The scale of the grid, used to set stroke width on grid lines. + * This should always be the same as the workspace scale. + * @type {number} + * @private + */ +Blockly.Grid.prototype.scale_ = 1; + +/** + * Dispose of this grid and unlink from the DOM. + * @package + */ +Blockly.Grid.prototype.dispose = function() { + this.gridPattern_ = null; +}; + +/** + * Whether blocks should snap to the grid, based on the initial configuration. + * @return {boolean} True if blocks should snap, false otherwise. + * @package + */ +Blockly.Grid.prototype.shouldSnap = function() { + return this.snapToGrid_; +}; + +/** + * Get the spacing of the grid points (in px). + * @return {number} The spacing of the grid points. + * @package + */ +Blockly.Grid.prototype.getSpacing = function() { + return this.spacing_; +}; + +/** + * Get the id of the pattern element, which should be randomized to avoid + * conflicts with other Blockly instances on the page. + * @return {string} The pattern id. + * @package + */ +Blockly.Grid.prototype.getPatternId = function() { + return this.gridPattern_.id; +}; + +/** + * Update the grid with a new scale. + * @param {number} scale The new workspace scale. + * @package + */ +Blockly.Grid.prototype.update = function(scale) { + this.scale_ = scale; + // MSIE freaks if it sees a 0x0 pattern, so set empty patterns to 100x100. + var safeSpacing = (this.spacing_ * scale) || 100; + + this.gridPattern_.setAttribute('width', safeSpacing); + this.gridPattern_.setAttribute('height', safeSpacing); + + var half = Math.floor(this.spacing_ / 2) + 0.5; + var start = half - this.length_ / 2; + var end = half + this.length_ / 2; + + half *= scale; + start *= scale; + end *= scale; + + this.setLineAttributes_(this.line1_, scale, start, end, half, half); + this.setLineAttributes_(this.line2_, scale, half, half, start, end); +}; + +/** + * 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 {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). + * @param {number} y1 The new y start position of the line (in px). + * @param {number} y2 The new y end position of the line (in px). + * @private + */ +Blockly.Grid.prototype.setLineAttributes_ = function(line, width, x1, x2, y1, y2) { + if (line) { + line.setAttribute('stroke-width', width); + line.setAttribute('x1', x1); + line.setAttribute('y1', y1); + line.setAttribute('x2', x2); + line.setAttribute('y2', y2); + } +}; + +/** + * Move the grid to a new x and y position, and make sure that change is visible. + * @param {number} x The new x position of the grid (in px). + * @param {number} y The new y position ofthe grid (in px). + * @package + */ +Blockly.Grid.prototype.moveTo = function(x, y) { + this.gridPattern_.setAttribute('x', x); + this.gridPattern_.setAttribute('y', y); + + if (goog.userAgent.IE || goog.userAgent.EDGE) { + // IE/Edge doesn't notice that the x/y offsets have changed. + // Force an update. + this.update(this.scale_); + } +}; + +/** + * Create the DOM for the grid described by options. + * @param {string} rnd A random ID to append to the pattern's ID. + * @param {!Object} gridOptions The object containing grid configuration. + * @param {!SVGElement} defs The root SVG element for this workspace's defs. + * @return {!SVGElement} The SVG element for the grid pattern. + * @package + */ +Blockly.Grid.createDom = function(rnd, gridOptions, defs) { + /* + + + + + */ + var gridPattern = Blockly.utils.createSvgElement('pattern', + {'id': 'blocklyGridPattern' + rnd, + 'patternUnits': 'userSpaceOnUse'}, defs); + if (gridOptions['length'] > 0 && gridOptions['spacing'] > 0) { + Blockly.utils.createSvgElement('line', + {'stroke': gridOptions['colour']}, gridPattern); + if (gridOptions['length'] > 1) { + Blockly.utils.createSvgElement('line', + {'stroke': gridOptions['colour']}, gridPattern); + } + // x1, y1, x1, x2 properties will be set later in update. + } + return gridPattern; +}; diff --git a/core/inject.js b/core/inject.js index cc24a5fe0..ff196202e 100644 --- a/core/inject.js +++ b/core/inject.js @@ -28,6 +28,7 @@ goog.provide('Blockly.inject'); goog.require('Blockly.BlockDragSurfaceSvg'); goog.require('Blockly.Css'); +goog.require('Blockly.Grid'); goog.require('Blockly.Options'); goog.require('Blockly.WorkspaceSvg'); goog.require('Blockly.WorkspaceDragSurfaceSvg'); @@ -164,27 +165,8 @@ Blockly.createDom_ = function(container, options) { Blockly.utils.createSvgElement('path', {'d': 'M 0 0 L 10 10 M 10 0 L 0 10', 'stroke': '#cc0'}, disabledPattern); options.disabledPatternId = disabledPattern.id; - /* - - - - - */ - var gridPattern = Blockly.utils.createSvgElement('pattern', - {'id': 'blocklyGridPattern' + rnd, - 'patternUnits': 'userSpaceOnUse'}, defs); - if (options.gridOptions['length'] > 0 && options.gridOptions['spacing'] > 0) { - Blockly.utils.createSvgElement('line', - {'stroke': options.gridOptions['colour']}, - gridPattern); - if (options.gridOptions['length'] > 1) { - Blockly.utils.createSvgElement('line', - {'stroke': options.gridOptions['colour']}, - gridPattern); - } - // x1, y1, x1, x2 properties will be set later in updateGridPattern_. - } - options.gridPattern = gridPattern; + + options.gridPattern = Blockly.Grid.createDom(rnd, options.gridOptions, defs); return svg; }; diff --git a/core/workspace_svg.js b/core/workspace_svg.js index 461163ed2..38e63aefb 100644 --- a/core/workspace_svg.js +++ b/core/workspace_svg.js @@ -31,6 +31,7 @@ goog.provide('Blockly.WorkspaceSvg'); goog.require('Blockly.ConnectionDB'); goog.require('Blockly.constants'); goog.require('Blockly.Gesture'); +goog.require('Blockly.Grid'); goog.require('Blockly.Options'); goog.require('Blockly.ScrollbarPair'); goog.require('Blockly.Touch'); @@ -93,6 +94,14 @@ Blockly.WorkspaceSvg = function(options, opt_blockDragSurface, opt_wsDragSurface */ this.audioManager_ = new Blockly.WorkspaceAudio(options.parentWorkspace); + /** + * This workspace's grid object or null. + * @type {Blockly.Grid} + * @private + */ + this.grid_ = this.options.gridPattern ? + new Blockly.Grid(options.gridPattern, options.gridOptions) : null; + this.registerToolboxCategoryCallback(Blockly.VARIABLE_CATEGORY_NAME, Blockly.Variables.flyoutCategory); this.registerToolboxCategoryCallback(Blockly.PROCEDURE_CATEGORY_NAME, @@ -342,9 +351,9 @@ Blockly.WorkspaceSvg.prototype.createDom = function(opt_backgroundClass) { {'height': '100%', 'width': '100%', 'class': opt_backgroundClass}, this.svgGroup_); - if (opt_backgroundClass == 'blocklyMainBackground') { + if (opt_backgroundClass == 'blocklyMainBackground' && this.grid_) { this.svgBackground_.style.fill = - 'url(#' + this.options.gridPattern.id + ')'; + 'url(#' + this.grid_.getPatternId() + ')'; } } /** @type {SVGElement} */ @@ -380,7 +389,9 @@ Blockly.WorkspaceSvg.prototype.createDom = function(opt_backgroundClass) { */ this.toolbox_ = new Blockly.Toolbox(this); } - this.updateGridPattern_(); + if (this.grid_) { + this.grid_.update(this.scale); + } this.recordDeleteAreas(); return this.svgGroup_; }; @@ -428,6 +439,11 @@ Blockly.WorkspaceSvg.prototype.dispose = function() { this.audioManager_ = null; } + if (this.grid_) { + this.grid_.dispose(); + this.grid_ = null; + } + if (this.toolboxCategoryCallbacks_) { this.toolboxCategoryCallbacks_ = null; } @@ -1395,7 +1411,9 @@ Blockly.WorkspaceSvg.prototype.setScale = function(newScale) { newScale = this.options.zoomOptions.minScale; } this.scale = newScale; - this.updateGridPattern_(); + if (this.grid_) { + this.grid_.update(this.scale); + } if (this.scrollbar) { this.scrollbar.resize(); } else { @@ -1408,43 +1426,6 @@ Blockly.WorkspaceSvg.prototype.setScale = function(newScale) { } }; -/** - * Updates the grid pattern. - * @private - */ -Blockly.WorkspaceSvg.prototype.updateGridPattern_ = function() { - if (!this.options.gridPattern) { - return; // No grid. - } - // MSIE freaks if it sees a 0x0 pattern, so set empty patterns to 100x100. - var safeSpacing = (this.options.gridOptions['spacing'] * this.scale) || 100; - this.options.gridPattern.setAttribute('width', safeSpacing); - this.options.gridPattern.setAttribute('height', safeSpacing); - var half = Math.floor(this.options.gridOptions['spacing'] / 2) + 0.5; - var start = half - this.options.gridOptions['length'] / 2; - var end = half + this.options.gridOptions['length'] / 2; - var line1 = this.options.gridPattern.firstChild; - var line2 = line1 && line1.nextSibling; - half *= this.scale; - start *= this.scale; - end *= this.scale; - if (line1) { - line1.setAttribute('stroke-width', this.scale); - line1.setAttribute('x1', start); - line1.setAttribute('y1', half); - line1.setAttribute('x2', end); - line1.setAttribute('y2', half); - } - if (line2) { - line2.setAttribute('stroke-width', this.scale); - line2.setAttribute('x1', half); - line2.setAttribute('y1', start); - line2.setAttribute('x2', half); - line2.setAttribute('y2', end); - } -}; - - /** * Return an object with all the metrics required to size scrollbars for a * top level workspace. The following properties are computed: @@ -1560,14 +1541,8 @@ Blockly.WorkspaceSvg.setTopLevelWorkspaceMetrics_ = function(xyRatio) { var x = this.scrollX + metrics.absoluteLeft; var y = this.scrollY + metrics.absoluteTop; this.translate(x, y); - if (this.options.gridPattern) { - this.options.gridPattern.setAttribute('x', x); - this.options.gridPattern.setAttribute('y', y); - if (goog.userAgent.IE || goog.userAgent.EDGE) { - // IE/Edge doesn't notice that the x/y offsets have changed. - // Force an update. - this.updateGridPattern_(); - } + if (this.grid_) { + this.grid_.moveTo(x, y); } }; @@ -1729,6 +1704,15 @@ Blockly.WorkspaceSvg.prototype.getAudioManager = function() { return this.audioManager_; }; +/** + * Get the grid object for this workspace, or null if there is none. + * @return {Blockly.Grid} The grid object for this workspace. + * @package + */ +Blockly.WorkspaceSvg.prototype.getGrid = function() { + return this.grid_; +}; + // Export symbols that would otherwise be renamed by Closure compiler. Blockly.WorkspaceSvg.prototype['setVisible'] = Blockly.WorkspaceSvg.prototype.setVisible;