2013-10-30 14:46:03 -07:00
|
|
|
/**
|
2014-01-28 03:00:09 -08:00
|
|
|
* @license
|
2013-10-30 14:46:03 -07:00
|
|
|
* Visual Blocks Editor
|
|
|
|
*
|
|
|
|
* Copyright 2011 Google Inc.
|
2014-10-07 13:09:55 -07:00
|
|
|
* https://developers.google.com/blockly/
|
2013-10-30 14:46:03 -07:00
|
|
|
*
|
|
|
|
* 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 Core JavaScript library for Blockly.
|
|
|
|
* @author fraser@google.com (Neil Fraser)
|
|
|
|
*/
|
|
|
|
'use strict';
|
|
|
|
|
|
|
|
// Top level object for Blockly.
|
|
|
|
goog.provide('Blockly');
|
|
|
|
|
2014-12-23 11:22:02 -08:00
|
|
|
goog.require('Blockly.BlockSvg');
|
2016-01-12 16:47:18 -08:00
|
|
|
goog.require('Blockly.Events');
|
2013-10-30 14:46:03 -07:00
|
|
|
goog.require('Blockly.FieldAngle');
|
|
|
|
goog.require('Blockly.FieldCheckbox');
|
|
|
|
goog.require('Blockly.FieldColour');
|
2015-02-25 01:06:10 -08:00
|
|
|
// Date picker commented out since it increases footprint by 60%.
|
|
|
|
// Add it only if you need it.
|
|
|
|
//goog.require('Blockly.FieldDate');
|
2013-10-30 14:46:03 -07:00
|
|
|
goog.require('Blockly.FieldDropdown');
|
|
|
|
goog.require('Blockly.FieldImage');
|
|
|
|
goog.require('Blockly.FieldTextInput');
|
|
|
|
goog.require('Blockly.FieldVariable');
|
|
|
|
goog.require('Blockly.Generator');
|
|
|
|
goog.require('Blockly.Msg');
|
|
|
|
goog.require('Blockly.Procedures');
|
|
|
|
goog.require('Blockly.Toolbox');
|
|
|
|
goog.require('Blockly.WidgetDiv');
|
2014-12-23 11:22:02 -08:00
|
|
|
goog.require('Blockly.WorkspaceSvg');
|
2013-10-30 14:46:03 -07:00
|
|
|
goog.require('Blockly.inject');
|
|
|
|
goog.require('Blockly.utils');
|
|
|
|
goog.require('goog.color');
|
2015-09-14 16:17:36 -07:00
|
|
|
goog.require('goog.userAgent');
|
2013-10-30 14:46:03 -07:00
|
|
|
|
|
|
|
|
2015-09-21 18:05:34 -07:00
|
|
|
// Turn off debugging when compiled.
|
|
|
|
var CLOSURE_DEFINES = {'goog.DEBUG': false};
|
|
|
|
|
2013-10-30 14:46:03 -07:00
|
|
|
/**
|
|
|
|
* Required name space for SVG elements.
|
|
|
|
* @const
|
|
|
|
*/
|
|
|
|
Blockly.SVG_NS = 'http://www.w3.org/2000/svg';
|
|
|
|
/**
|
|
|
|
* Required name space for HTML elements.
|
|
|
|
* @const
|
|
|
|
*/
|
|
|
|
Blockly.HTML_NS = 'http://www.w3.org/1999/xhtml';
|
|
|
|
|
|
|
|
/**
|
|
|
|
* The richness of block colours, regardless of the hue.
|
|
|
|
* Must be in the range of 0 (inclusive) to 1 (exclusive).
|
|
|
|
*/
|
|
|
|
Blockly.HSV_SATURATION = 0.45;
|
|
|
|
/**
|
|
|
|
* The intensity of block colours, regardless of the hue.
|
|
|
|
* Must be in the range of 0 (inclusive) to 1 (exclusive).
|
|
|
|
*/
|
|
|
|
Blockly.HSV_VALUE = 0.65;
|
|
|
|
|
2014-09-08 14:26:52 -07:00
|
|
|
/**
|
|
|
|
* Sprited icons and images.
|
|
|
|
*/
|
|
|
|
Blockly.SPRITE = {
|
2015-08-19 17:21:05 -07:00
|
|
|
width: 96,
|
|
|
|
height: 124,
|
2014-11-09 16:02:24 -08:00
|
|
|
url: 'sprites.png'
|
2014-09-08 14:26:52 -07:00
|
|
|
};
|
|
|
|
|
2013-10-30 14:46:03 -07:00
|
|
|
/**
|
|
|
|
* Convert a hue (HSV model) into an RGB hex triplet.
|
|
|
|
* @param {number} hue Hue on a colour wheel (0-360).
|
|
|
|
* @return {string} RGB code, e.g. '#5ba65b'.
|
|
|
|
*/
|
2015-12-13 19:13:05 +01:00
|
|
|
Blockly.hueToRgb = function(hue) {
|
2013-10-30 14:46:03 -07:00
|
|
|
return goog.color.hsvToHex(hue, Blockly.HSV_SATURATION,
|
2014-12-05 11:44:47 -06:00
|
|
|
Blockly.HSV_VALUE * 255);
|
2013-10-30 14:46:03 -07:00
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
2015-01-27 15:57:45 -08:00
|
|
|
* ENUM for a right-facing value input. E.g. 'set item to' or 'return'.
|
2013-10-30 14:46:03 -07:00
|
|
|
* @const
|
|
|
|
*/
|
|
|
|
Blockly.INPUT_VALUE = 1;
|
|
|
|
/**
|
2015-01-27 15:57:45 -08:00
|
|
|
* ENUM for a left-facing value output. E.g. 'random fraction'.
|
2013-10-30 14:46:03 -07:00
|
|
|
* @const
|
|
|
|
*/
|
|
|
|
Blockly.OUTPUT_VALUE = 2;
|
|
|
|
/**
|
2015-01-27 15:57:45 -08:00
|
|
|
* ENUM for a down-facing block stack. E.g. 'if-do' or 'else'.
|
2013-10-30 14:46:03 -07:00
|
|
|
* @const
|
|
|
|
*/
|
|
|
|
Blockly.NEXT_STATEMENT = 3;
|
|
|
|
/**
|
2015-01-27 15:57:45 -08:00
|
|
|
* ENUM for an up-facing block stack. E.g. 'break out of loop'.
|
2013-10-30 14:46:03 -07:00
|
|
|
* @const
|
|
|
|
*/
|
|
|
|
Blockly.PREVIOUS_STATEMENT = 4;
|
|
|
|
/**
|
2013-12-20 16:25:26 -08:00
|
|
|
* ENUM for an dummy input. Used to add field(s) with no input.
|
2013-10-30 14:46:03 -07:00
|
|
|
* @const
|
|
|
|
*/
|
|
|
|
Blockly.DUMMY_INPUT = 5;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* ENUM for left alignment.
|
|
|
|
* @const
|
|
|
|
*/
|
|
|
|
Blockly.ALIGN_LEFT = -1;
|
|
|
|
/**
|
|
|
|
* ENUM for centre alignment.
|
|
|
|
* @const
|
|
|
|
*/
|
|
|
|
Blockly.ALIGN_CENTRE = 0;
|
|
|
|
/**
|
|
|
|
* ENUM for right alignment.
|
|
|
|
* @const
|
|
|
|
*/
|
|
|
|
Blockly.ALIGN_RIGHT = 1;
|
|
|
|
|
2016-02-17 16:19:40 -08:00
|
|
|
/**
|
|
|
|
* ENUM for toolbox and flyout at top of screen.
|
|
|
|
* @const
|
|
|
|
*/
|
|
|
|
Blockly.TOOLBOX_AT_TOP = 0;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* ENUM for toolbox and flyout at bottom of screen.
|
|
|
|
* @const
|
|
|
|
*/
|
|
|
|
Blockly.TOOLBOX_AT_BOTTOM = 1;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* ENUM for toolbox and flyout at left of screen.
|
|
|
|
* @const
|
|
|
|
*/
|
|
|
|
Blockly.TOOLBOX_AT_LEFT = 2;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* ENUM for toolbox and flyout at right of screen.
|
|
|
|
* @const
|
|
|
|
*/
|
|
|
|
Blockly.TOOLBOX_AT_RIGHT = 3;
|
|
|
|
|
2013-10-30 14:46:03 -07:00
|
|
|
/**
|
|
|
|
* Lookup table for determining the opposite type of a connection.
|
|
|
|
* @const
|
|
|
|
*/
|
|
|
|
Blockly.OPPOSITE_TYPE = [];
|
|
|
|
Blockly.OPPOSITE_TYPE[Blockly.INPUT_VALUE] = Blockly.OUTPUT_VALUE;
|
|
|
|
Blockly.OPPOSITE_TYPE[Blockly.OUTPUT_VALUE] = Blockly.INPUT_VALUE;
|
|
|
|
Blockly.OPPOSITE_TYPE[Blockly.NEXT_STATEMENT] = Blockly.PREVIOUS_STATEMENT;
|
|
|
|
Blockly.OPPOSITE_TYPE[Blockly.PREVIOUS_STATEMENT] = Blockly.NEXT_STATEMENT;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Currently selected block.
|
|
|
|
* @type {Blockly.Block}
|
|
|
|
*/
|
|
|
|
Blockly.selected = null;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Currently highlighted connection (during a drag).
|
|
|
|
* @type {Blockly.Connection}
|
|
|
|
* @private
|
|
|
|
*/
|
|
|
|
Blockly.highlightedConnection_ = null;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Connection on dragged block that matches the highlighted connection.
|
|
|
|
* @type {Blockly.Connection}
|
|
|
|
* @private
|
|
|
|
*/
|
|
|
|
Blockly.localConnection_ = null;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Number of pixels the mouse must move before a drag starts.
|
|
|
|
*/
|
|
|
|
Blockly.DRAG_RADIUS = 5;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Maximum misalignment between connections for them to snap together.
|
|
|
|
*/
|
2014-09-08 14:26:52 -07:00
|
|
|
Blockly.SNAP_RADIUS = 20;
|
2013-10-30 14:46:03 -07:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Delay in ms between trigger and bumping unconnected block out of alignment.
|
|
|
|
*/
|
|
|
|
Blockly.BUMP_DELAY = 250;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Number of characters to truncate a collapsed block to.
|
|
|
|
*/
|
|
|
|
Blockly.COLLAPSE_CHARS = 30;
|
|
|
|
|
2015-03-17 15:37:33 -07:00
|
|
|
/**
|
|
|
|
* Length in ms for a touch to become a long press.
|
|
|
|
*/
|
|
|
|
Blockly.LONGPRESS = 750;
|
|
|
|
|
2013-10-30 14:46:03 -07:00
|
|
|
/**
|
2015-04-28 13:51:25 -07:00
|
|
|
* The main workspace most recently used.
|
|
|
|
* Set by Blockly.WorkspaceSvg.prototype.markFocused
|
2013-10-30 14:46:03 -07:00
|
|
|
* @type {Blockly.Workspace}
|
|
|
|
*/
|
|
|
|
Blockly.mainWorkspace = null;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Contents of the local clipboard.
|
|
|
|
* @type {Element}
|
|
|
|
* @private
|
|
|
|
*/
|
2015-04-28 13:51:25 -07:00
|
|
|
Blockly.clipboardXml_ = null;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Source of the local clipboard.
|
2015-09-11 21:50:44 -07:00
|
|
|
* @type {Blockly.WorkspaceSvg}
|
2015-04-28 13:51:25 -07:00
|
|
|
* @private
|
|
|
|
*/
|
|
|
|
Blockly.clipboardSource_ = null;
|
2013-10-30 14:46:03 -07:00
|
|
|
|
2014-12-23 11:22:02 -08:00
|
|
|
/**
|
|
|
|
* Is the mouse dragging a block?
|
|
|
|
* 0 - No drag operation.
|
|
|
|
* 1 - Still inside the sticky DRAG_RADIUS.
|
|
|
|
* 2 - Freely draggable.
|
|
|
|
* @private
|
|
|
|
*/
|
|
|
|
Blockly.dragMode_ = 0;
|
|
|
|
|
2014-09-08 14:26:52 -07:00
|
|
|
/**
|
|
|
|
* Wrapper function called when a touch mouseUp occurs during a drag operation.
|
|
|
|
* @type {Array.<!Array>}
|
|
|
|
* @private
|
|
|
|
*/
|
|
|
|
Blockly.onTouchUpWrapper_ = null;
|
|
|
|
|
2013-10-30 14:46:03 -07:00
|
|
|
/**
|
2015-04-28 13:51:25 -07:00
|
|
|
* Returns the dimensions of the specified SVG image.
|
|
|
|
* @param {!Element} svg SVG image.
|
2014-02-01 03:00:04 -08:00
|
|
|
* @return {!Object} Contains width and height properties.
|
2013-10-30 14:46:03 -07:00
|
|
|
*/
|
2015-04-28 13:51:25 -07:00
|
|
|
Blockly.svgSize = function(svg) {
|
|
|
|
return {width: svg.cachedWidth_,
|
|
|
|
height: svg.cachedHeight_};
|
2013-10-30 14:46:03 -07:00
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
2014-09-08 14:26:52 -07:00
|
|
|
* Size the SVG image to completely fill its container.
|
|
|
|
* Record the height/width of the SVG image.
|
2015-04-28 13:51:25 -07:00
|
|
|
* @param {!Blockly.WorkspaceSvg} workspace Any workspace in the SVG.
|
2013-10-30 14:46:03 -07:00
|
|
|
*/
|
2015-04-28 13:51:25 -07:00
|
|
|
Blockly.svgResize = function(workspace) {
|
|
|
|
var mainWorkspace = workspace;
|
|
|
|
while (mainWorkspace.options.parentWorkspace) {
|
|
|
|
mainWorkspace = mainWorkspace.options.parentWorkspace;
|
|
|
|
}
|
2016-01-07 17:01:01 -08:00
|
|
|
var svg = mainWorkspace.getParentSvg();
|
2013-10-30 14:46:03 -07:00
|
|
|
var div = svg.parentNode;
|
2015-04-28 13:51:25 -07:00
|
|
|
if (!div) {
|
|
|
|
// Workspace deteted, or something.
|
|
|
|
return;
|
|
|
|
}
|
2013-10-30 14:46:03 -07:00
|
|
|
var width = div.offsetWidth;
|
|
|
|
var height = div.offsetHeight;
|
|
|
|
if (svg.cachedWidth_ != width) {
|
|
|
|
svg.setAttribute('width', width + 'px');
|
|
|
|
svg.cachedWidth_ = width;
|
|
|
|
}
|
|
|
|
if (svg.cachedHeight_ != height) {
|
|
|
|
svg.setAttribute('height', height + 'px');
|
|
|
|
svg.cachedHeight_ = height;
|
|
|
|
}
|
2015-04-28 17:55:45 -07:00
|
|
|
mainWorkspace.resize();
|
2013-10-30 14:46:03 -07:00
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
2014-09-08 14:26:52 -07:00
|
|
|
* Handle a mouse-up anywhere on the page.
|
2013-10-30 14:46:03 -07:00
|
|
|
* @param {!Event} e Mouse up event.
|
|
|
|
* @private
|
|
|
|
*/
|
|
|
|
Blockly.onMouseUp_ = function(e) {
|
2015-04-28 13:51:25 -07:00
|
|
|
var workspace = Blockly.getMainWorkspace();
|
2014-11-26 12:03:21 -08:00
|
|
|
Blockly.Css.setCursor(Blockly.Css.Cursor.OPEN);
|
2015-04-28 13:51:25 -07:00
|
|
|
workspace.isScrolling = false;
|
2014-09-08 14:26:52 -07:00
|
|
|
|
|
|
|
// Unbind the touch event if it exists.
|
|
|
|
if (Blockly.onTouchUpWrapper_) {
|
|
|
|
Blockly.unbindEvent_(Blockly.onTouchUpWrapper_);
|
|
|
|
Blockly.onTouchUpWrapper_ = null;
|
|
|
|
}
|
|
|
|
if (Blockly.onMouseMoveWrapper_) {
|
|
|
|
Blockly.unbindEvent_(Blockly.onMouseMoveWrapper_);
|
|
|
|
Blockly.onMouseMoveWrapper_ = null;
|
|
|
|
}
|
2013-10-30 14:46:03 -07:00
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Handle a mouse-move on SVG drawing surface.
|
|
|
|
* @param {!Event} e Mouse move event.
|
|
|
|
* @private
|
|
|
|
*/
|
|
|
|
Blockly.onMouseMove_ = function(e) {
|
2015-08-21 19:37:57 -07:00
|
|
|
if (e.touches && e.touches.length >= 2) {
|
2015-08-21 14:54:11 -07:00
|
|
|
return; // Multi-touch gestures won't have e.clientX.
|
2015-08-20 15:18:58 -07:00
|
|
|
}
|
2015-04-28 13:51:25 -07:00
|
|
|
var workspace = Blockly.getMainWorkspace();
|
|
|
|
if (workspace.isScrolling) {
|
2013-10-30 14:46:03 -07:00
|
|
|
Blockly.removeAllRanges();
|
2015-04-28 13:51:25 -07:00
|
|
|
var dx = e.clientX - workspace.startDragMouseX;
|
|
|
|
var dy = e.clientY - workspace.startDragMouseY;
|
|
|
|
var metrics = workspace.startDragMetrics;
|
|
|
|
var x = workspace.startScrollX + dx;
|
|
|
|
var y = workspace.startScrollY + dy;
|
2013-10-30 14:46:03 -07:00
|
|
|
x = Math.min(x, -metrics.contentLeft);
|
|
|
|
y = Math.min(y, -metrics.contentTop);
|
|
|
|
x = Math.max(x, metrics.viewWidth - metrics.contentLeft -
|
|
|
|
metrics.contentWidth);
|
|
|
|
y = Math.max(y, metrics.viewHeight - metrics.contentTop -
|
|
|
|
metrics.contentHeight);
|
|
|
|
|
|
|
|
// Move the scrollbars and the page will scroll automatically.
|
2015-04-28 13:51:25 -07:00
|
|
|
workspace.scrollbar.set(-x - metrics.contentLeft,
|
2015-08-19 17:21:05 -07:00
|
|
|
-y - metrics.contentTop);
|
2015-03-17 15:37:33 -07:00
|
|
|
// Cancel the long-press if the drag has moved too far.
|
2015-08-19 17:21:05 -07:00
|
|
|
if (Math.sqrt(dx * dx + dy * dy) > Blockly.DRAG_RADIUS) {
|
2015-03-17 15:37:33 -07:00
|
|
|
Blockly.longStop_();
|
|
|
|
}
|
2014-09-08 14:26:52 -07:00
|
|
|
e.stopPropagation();
|
2013-10-30 14:46:03 -07:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Handle a key-down on SVG drawing surface.
|
|
|
|
* @param {!Event} e Key down event.
|
|
|
|
* @private
|
|
|
|
*/
|
|
|
|
Blockly.onKeyDown_ = function(e) {
|
|
|
|
if (Blockly.isTargetInput_(e)) {
|
|
|
|
// When focused on an HTML text input widget, don't trap any keys.
|
|
|
|
return;
|
|
|
|
}
|
2015-09-21 15:08:35 -07:00
|
|
|
var deleteBlock = false;
|
2013-10-30 14:46:03 -07:00
|
|
|
if (e.keyCode == 27) {
|
|
|
|
// Pressing esc closes the context menu.
|
|
|
|
Blockly.hideChaff();
|
|
|
|
} else if (e.keyCode == 8 || e.keyCode == 46) {
|
|
|
|
// Delete or backspace.
|
|
|
|
try {
|
|
|
|
if (Blockly.selected && Blockly.selected.isDeletable()) {
|
2015-09-21 15:08:35 -07:00
|
|
|
deleteBlock = true;
|
2013-10-30 14:46:03 -07:00
|
|
|
}
|
|
|
|
} finally {
|
|
|
|
// Stop the browser from going back to the previous page.
|
|
|
|
// Use a finally so that any error in delete code above doesn't disappear
|
|
|
|
// from the console when the page rolls back.
|
|
|
|
e.preventDefault();
|
|
|
|
}
|
|
|
|
} else if (e.altKey || e.ctrlKey || e.metaKey) {
|
2014-09-08 14:26:52 -07:00
|
|
|
if (Blockly.selected &&
|
2015-04-28 13:51:25 -07:00
|
|
|
Blockly.selected.isDeletable() && Blockly.selected.isMovable()) {
|
2013-10-30 14:46:03 -07:00
|
|
|
if (e.keyCode == 67) {
|
|
|
|
// 'c' for copy.
|
2015-09-21 15:08:35 -07:00
|
|
|
Blockly.hideChaff();
|
2013-10-30 14:46:03 -07:00
|
|
|
Blockly.copy_(Blockly.selected);
|
|
|
|
} else if (e.keyCode == 88) {
|
|
|
|
// 'x' for cut.
|
|
|
|
Blockly.copy_(Blockly.selected);
|
2015-09-21 15:08:35 -07:00
|
|
|
deleteBlock = true;
|
2013-10-30 14:46:03 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
if (e.keyCode == 86) {
|
|
|
|
// 'v' for paste.
|
2015-04-28 13:51:25 -07:00
|
|
|
if (Blockly.clipboardXml_) {
|
|
|
|
Blockly.clipboardSource_.paste(Blockly.clipboardXml_);
|
2013-10-30 14:46:03 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2015-09-21 15:08:35 -07:00
|
|
|
if (deleteBlock) {
|
|
|
|
// Common code for delete and cut.
|
|
|
|
Blockly.hideChaff();
|
|
|
|
var heal = Blockly.dragMode_ != 2;
|
|
|
|
Blockly.selected.dispose(heal, true);
|
|
|
|
if (Blockly.highlightedConnection_) {
|
|
|
|
Blockly.highlightedConnection_.unhighlight();
|
|
|
|
Blockly.highlightedConnection_ = null;
|
|
|
|
}
|
|
|
|
}
|
2013-10-30 14:46:03 -07:00
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Stop binding to the global mouseup and mousemove events.
|
|
|
|
* @private
|
|
|
|
*/
|
|
|
|
Blockly.terminateDrag_ = function() {
|
2014-12-23 11:22:02 -08:00
|
|
|
Blockly.BlockSvg.terminateDrag_();
|
2013-10-30 14:46:03 -07:00
|
|
|
Blockly.Flyout.terminateDrag_();
|
|
|
|
};
|
|
|
|
|
2015-03-17 15:37:33 -07:00
|
|
|
/**
|
|
|
|
* PID of queued long-press task.
|
|
|
|
* @private
|
|
|
|
*/
|
|
|
|
Blockly.longPid_ = 0;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Context menus on touch devices are activated using a long-press.
|
|
|
|
* Unfortunately the contextmenu touch event is currently (2015) only suported
|
|
|
|
* by Chrome. This function is fired on any touchstart event, queues a task,
|
|
|
|
* which after about a second opens the context menu. The tasks is killed
|
|
|
|
* if the touch event terminates early.
|
|
|
|
* @param {!Event} e Touch start event.
|
2015-07-13 17:48:50 -07:00
|
|
|
* @param {!Blockly.Block|!Blockly.WorkspaceSvg} uiObject The block or workspace
|
|
|
|
* under the touchstart event.
|
2015-03-17 15:37:33 -07:00
|
|
|
* @private
|
|
|
|
*/
|
2015-07-13 17:48:50 -07:00
|
|
|
Blockly.longStart_ = function(e, uiObject) {
|
2015-03-17 15:37:33 -07:00
|
|
|
Blockly.longStop_();
|
|
|
|
Blockly.longPid_ = setTimeout(function() {
|
|
|
|
e.button = 2; // Simulate a right button click.
|
2015-07-13 17:48:50 -07:00
|
|
|
uiObject.onMouseDown_(e);
|
2015-03-17 15:37:33 -07:00
|
|
|
}, Blockly.LONGPRESS);
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Nope, that's not a long-press. Either touchend or touchcancel was fired,
|
|
|
|
* or a drag hath begun. Kill the queued long-press task.
|
|
|
|
* @private
|
|
|
|
*/
|
|
|
|
Blockly.longStop_ = function() {
|
|
|
|
if (Blockly.longPid_) {
|
|
|
|
clearTimeout(Blockly.longPid_);
|
|
|
|
Blockly.longPid_ = 0;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2013-10-30 14:46:03 -07:00
|
|
|
/**
|
|
|
|
* Copy a block onto the local clipboard.
|
|
|
|
* @param {!Blockly.Block} block Block to be copied.
|
|
|
|
* @private
|
|
|
|
*/
|
|
|
|
Blockly.copy_ = function(block) {
|
2016-01-15 15:36:06 -08:00
|
|
|
var xmlBlock = Blockly.Xml.blockToDom(block);
|
2015-09-21 15:08:35 -07:00
|
|
|
if (Blockly.dragMode_ != 2) {
|
|
|
|
Blockly.Xml.deleteNext(xmlBlock);
|
|
|
|
}
|
2013-10-30 14:46:03 -07:00
|
|
|
// Encode start position in XML.
|
|
|
|
var xy = block.getRelativeToSurfaceXY();
|
2015-04-28 13:51:25 -07:00
|
|
|
xmlBlock.setAttribute('x', block.RTL ? -xy.x : xy.x);
|
2013-10-30 14:46:03 -07:00
|
|
|
xmlBlock.setAttribute('y', xy.y);
|
2015-04-28 13:51:25 -07:00
|
|
|
Blockly.clipboardXml_ = xmlBlock;
|
|
|
|
Blockly.clipboardSource_ = block.workspace;
|
2013-10-30 14:46:03 -07:00
|
|
|
};
|
|
|
|
|
2015-09-18 18:46:19 -07:00
|
|
|
/**
|
|
|
|
* Duplicate this block and its children.
|
|
|
|
* @param {!Blockly.Block} block Block to be copied.
|
|
|
|
* @private
|
|
|
|
*/
|
|
|
|
Blockly.duplicate_ = function(block) {
|
|
|
|
// Save the clipboard.
|
|
|
|
var clipboardXml = Blockly.clipboardXml_;
|
|
|
|
var clipboardSource = Blockly.clipboardSource_;
|
|
|
|
|
|
|
|
// Create a duplicate via a copy/paste operation.
|
|
|
|
Blockly.copy_(block);
|
|
|
|
block.workspace.paste(Blockly.clipboardXml_);
|
|
|
|
|
|
|
|
// Restore the clipboard.
|
|
|
|
Blockly.clipboardXml_ = clipboardXml;
|
|
|
|
Blockly.clipboardSource_ = clipboardSource;
|
|
|
|
};
|
|
|
|
|
2013-10-30 14:46:03 -07:00
|
|
|
/**
|
|
|
|
* Cancel the native context menu, unless the focus is on an HTML input widget.
|
|
|
|
* @param {!Event} e Mouse down event.
|
|
|
|
* @private
|
|
|
|
*/
|
|
|
|
Blockly.onContextMenu_ = function(e) {
|
2014-09-08 14:26:52 -07:00
|
|
|
if (!Blockly.isTargetInput_(e)) {
|
2013-10-30 14:46:03 -07:00
|
|
|
// When focused on an HTML text input widget, don't cancel the context menu.
|
|
|
|
e.preventDefault();
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Close tooltips, context menus, dropdown selections, etc.
|
|
|
|
* @param {boolean=} opt_allowToolbox If true, don't close the toolbox.
|
|
|
|
*/
|
|
|
|
Blockly.hideChaff = function(opt_allowToolbox) {
|
2014-09-08 14:26:52 -07:00
|
|
|
Blockly.Tooltip.hide();
|
2013-10-30 14:46:03 -07:00
|
|
|
Blockly.WidgetDiv.hide();
|
2015-04-28 13:51:25 -07:00
|
|
|
if (!opt_allowToolbox) {
|
|
|
|
var workspace = Blockly.getMainWorkspace();
|
|
|
|
if (workspace.toolbox_ &&
|
|
|
|
workspace.toolbox_.flyout_ &&
|
|
|
|
workspace.toolbox_.flyout_.autoClose) {
|
|
|
|
workspace.toolbox_.clearSelection();
|
|
|
|
}
|
2013-10-30 14:46:03 -07:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Return an object with all the metrics required to size scrollbars for the
|
|
|
|
* main workspace. The following properties are computed:
|
|
|
|
* .viewHeight: Height of the visible rectangle,
|
|
|
|
* .viewWidth: Width of the visible rectangle,
|
|
|
|
* .contentHeight: Height of the contents,
|
|
|
|
* .contentWidth: Width of the content,
|
|
|
|
* .viewTop: Offset of top edge of visible rectangle from parent,
|
|
|
|
* .viewLeft: Offset of left edge of visible rectangle from parent,
|
|
|
|
* .contentTop: Offset of the top-most content from the y=0 coordinate,
|
|
|
|
* .contentLeft: Offset of the left-most content from the x=0 coordinate.
|
|
|
|
* .absoluteTop: Top-edge of view.
|
|
|
|
* .absoluteLeft: Left-edge of view.
|
|
|
|
* @return {Object} Contains size and position metrics of main workspace.
|
|
|
|
* @private
|
2015-04-28 13:51:25 -07:00
|
|
|
* @this Blockly.WorkspaceSvg
|
2013-10-30 14:46:03 -07:00
|
|
|
*/
|
|
|
|
Blockly.getMainWorkspaceMetrics_ = function() {
|
2016-01-07 17:01:01 -08:00
|
|
|
var svgSize = Blockly.svgSize(this.getParentSvg());
|
2015-04-28 13:51:25 -07:00
|
|
|
if (this.toolbox_) {
|
|
|
|
svgSize.width -= this.toolbox_.width;
|
2014-11-29 15:41:27 -08:00
|
|
|
}
|
2015-09-21 16:23:33 -07:00
|
|
|
// Set the margin to match the flyout's margin so that the workspace does
|
|
|
|
// not jump as blocks are added.
|
|
|
|
var MARGIN = Blockly.Flyout.prototype.CORNER_RADIUS - 1;
|
|
|
|
var viewWidth = svgSize.width - MARGIN;
|
|
|
|
var viewHeight = svgSize.height - MARGIN;
|
2013-10-30 14:46:03 -07:00
|
|
|
try {
|
2015-04-28 13:51:25 -07:00
|
|
|
var blockBox = this.getCanvas().getBBox();
|
2013-10-30 14:46:03 -07:00
|
|
|
} catch (e) {
|
|
|
|
// Firefox has trouble with hidden elements (Bug 528969).
|
|
|
|
return null;
|
|
|
|
}
|
2015-08-19 17:21:05 -07:00
|
|
|
// Fix scale.
|
2015-08-20 14:09:14 -07:00
|
|
|
var contentWidth = blockBox.width * this.scale;
|
|
|
|
var contentHeight = blockBox.height * this.scale;
|
2015-08-19 17:21:05 -07:00
|
|
|
var contentX = blockBox.x * this.scale;
|
|
|
|
var contentY = blockBox.y * this.scale;
|
2015-04-28 13:51:25 -07:00
|
|
|
if (this.scrollbar) {
|
2013-10-30 14:46:03 -07:00
|
|
|
// Add a border around the content that is at least half a screenful wide.
|
2014-01-16 03:00:02 -08:00
|
|
|
// Ensure border is wide enough that blocks can scroll over entire screen.
|
2015-08-19 17:21:05 -07:00
|
|
|
var leftEdge = Math.min(contentX - viewWidth / 2,
|
|
|
|
contentX + contentWidth - viewWidth);
|
|
|
|
var rightEdge = Math.max(contentX + contentWidth + viewWidth / 2,
|
|
|
|
contentX + viewWidth);
|
|
|
|
var topEdge = Math.min(contentY - viewHeight / 2,
|
|
|
|
contentY + contentHeight - viewHeight);
|
|
|
|
var bottomEdge = Math.max(contentY + contentHeight + viewHeight / 2,
|
|
|
|
contentY + viewHeight);
|
2013-10-30 14:46:03 -07:00
|
|
|
} else {
|
|
|
|
var leftEdge = blockBox.x;
|
|
|
|
var rightEdge = leftEdge + blockBox.width;
|
|
|
|
var topEdge = blockBox.y;
|
|
|
|
var bottomEdge = topEdge + blockBox.height;
|
|
|
|
}
|
2014-11-29 15:41:27 -08:00
|
|
|
var absoluteLeft = 0;
|
2016-02-17 16:19:40 -08:00
|
|
|
if (this.toolbox_ && this.toolbox_.toolboxPosition == Blockly.TOOLBOX_AT_LEFT) {
|
2015-04-28 13:51:25 -07:00
|
|
|
absoluteLeft = this.toolbox_.width;
|
2014-11-29 15:41:27 -08:00
|
|
|
}
|
2014-01-16 03:00:02 -08:00
|
|
|
var metrics = {
|
2013-10-30 14:46:03 -07:00
|
|
|
viewHeight: svgSize.height,
|
|
|
|
viewWidth: svgSize.width,
|
|
|
|
contentHeight: bottomEdge - topEdge,
|
|
|
|
contentWidth: rightEdge - leftEdge,
|
2015-04-28 13:51:25 -07:00
|
|
|
viewTop: -this.scrollY,
|
|
|
|
viewLeft: -this.scrollX,
|
2013-10-30 14:46:03 -07:00
|
|
|
contentTop: topEdge,
|
|
|
|
contentLeft: leftEdge,
|
|
|
|
absoluteTop: 0,
|
|
|
|
absoluteLeft: absoluteLeft
|
|
|
|
};
|
2014-01-16 03:00:02 -08:00
|
|
|
return metrics;
|
2013-10-30 14:46:03 -07:00
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Sets the X/Y translations of the main 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.
|
|
|
|
* @private
|
2015-04-28 13:51:25 -07:00
|
|
|
* @this Blockly.WorkspaceSvg
|
2013-10-30 14:46:03 -07:00
|
|
|
*/
|
|
|
|
Blockly.setMainWorkspaceMetrics_ = function(xyRatio) {
|
2015-04-28 13:51:25 -07:00
|
|
|
if (!this.scrollbar) {
|
2013-10-30 14:46:03 -07:00
|
|
|
throw 'Attempt to set main workspace scroll without scrollbars.';
|
|
|
|
}
|
2015-04-28 13:51:25 -07:00
|
|
|
var metrics = this.getMetrics();
|
2013-10-30 14:46:03 -07:00
|
|
|
if (goog.isNumber(xyRatio.x)) {
|
2015-05-22 13:10:58 -07:00
|
|
|
this.scrollX = -metrics.contentWidth * xyRatio.x - metrics.contentLeft;
|
2013-10-30 14:46:03 -07:00
|
|
|
}
|
|
|
|
if (goog.isNumber(xyRatio.y)) {
|
2015-05-22 13:10:58 -07:00
|
|
|
this.scrollY = -metrics.contentHeight * xyRatio.y - metrics.contentTop;
|
2013-10-30 14:46:03 -07:00
|
|
|
}
|
2015-04-28 13:51:25 -07:00
|
|
|
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);
|
2015-09-14 16:17:36 -07:00
|
|
|
if (goog.userAgent.IE) {
|
|
|
|
// IE doesn't notice that the x/y offsets have changed. Force an update.
|
|
|
|
this.updateGridPattern_();
|
|
|
|
}
|
2015-04-28 13:51:25 -07:00
|
|
|
}
|
2013-10-30 14:46:03 -07:00
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* When something in Blockly's workspace changes, call a function.
|
|
|
|
* @param {!Function} func Function to call.
|
|
|
|
* @return {!Array.<!Array>} Opaque data that can be passed to
|
|
|
|
* removeChangeListener.
|
2015-04-28 13:51:25 -07:00
|
|
|
* @deprecated April 2015
|
2013-10-30 14:46:03 -07:00
|
|
|
*/
|
|
|
|
Blockly.addChangeListener = function(func) {
|
2015-04-28 13:51:25 -07:00
|
|
|
// Backwards compatability from before there could be multiple workspaces.
|
|
|
|
console.warn('Deprecated call to Blockly.addChangeListener, ' +
|
|
|
|
'use workspace.addChangeListener instead.');
|
|
|
|
return Blockly.getMainWorkspace().addChangeListener(func);
|
2013-10-30 14:46:03 -07:00
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
2015-04-28 13:51:25 -07:00
|
|
|
* Returns the main workspace. Returns the last used main workspace (based on
|
|
|
|
* focus).
|
2013-10-30 14:46:03 -07:00
|
|
|
* @return {!Blockly.Workspace} The main workspace.
|
|
|
|
*/
|
|
|
|
Blockly.getMainWorkspace = function() {
|
|
|
|
return Blockly.mainWorkspace;
|
|
|
|
};
|
|
|
|
|
|
|
|
// Export symbols that would otherwise be renamed by Closure compiler.
|
2014-12-23 16:44:00 -08:00
|
|
|
if (!goog.global['Blockly']) {
|
|
|
|
goog.global['Blockly'] = {};
|
2014-09-08 14:26:52 -07:00
|
|
|
}
|
2014-12-23 16:44:00 -08:00
|
|
|
goog.global['Blockly']['getMainWorkspace'] = Blockly.getMainWorkspace;
|
|
|
|
goog.global['Blockly']['addChangeListener'] = Blockly.addChangeListener;
|