cloud-blocks
Version:
Cloud Blocks is a library for building scratch computing interfaces with Luxrobo MODI.
327 lines (303 loc) • 10.3 kB
JavaScript
/**
* @license
* Visual Blocks Editor
*
* Copyright 2016 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 A class that manages a surface for dragging blocks. When a
* block drag is started, we move the block (and children) to a separate DOM
* element that we move around using translate3d. At the end of the drag, the
* blocks are put back in into the SVG they came from. This helps performance by
* avoiding repainting the entire SVG on every mouse move while dragging blocks.
* @author picklesrus
*/
'use strict';
goog.provide('Blockly.BlockDragSurfaceSvg');
goog.require('Blockly.utils');
goog.require('goog.asserts');
goog.require('goog.math.Coordinate');
/**
* Class for a drag surface for the currently dragged block. This is a separate
* SVG that contains only the currently moving block, or nothing.
* @param {!Element} container Containing element.
* @constructor
*/
Blockly.BlockDragSurfaceSvg = function (container) {
/**
* @type {!Element}
* @private
*/
this.container_ = container;
this.createDom();
};
/**
* The SVG drag surface. Set once by Blockly.BlockDragSurfaceSvg.createDom.
* @type {Element}
* @private
*/
Blockly.BlockDragSurfaceSvg.prototype.SVG_ = null;
/**
* This is where blocks live while they are being dragged if the drag surface
* is enabled.
* @type {Element}
* @private
*/
Blockly.BlockDragSurfaceSvg.prototype.dragGroup_ = null;
/**
* Containing HTML element; parent of the workspace and the drag surface.
* @type {Element}
* @private
*/
Blockly.BlockDragSurfaceSvg.prototype.container_ = null;
/**
* Cached value for the scale of the drag surface.
* Used to set/get the correct translation during and after a drag.
* @type {number}
* @private
*/
Blockly.BlockDragSurfaceSvg.prototype.scale_ = 1;
/**
* Cached value for the translation of the drag surface.
* This translation is in pixel units, because the scale is applied to the
* drag group rather than the top-level SVG.
* @type {goog.math.Coordinate}
* @private
*/
Blockly.BlockDragSurfaceSvg.prototype.surfaceXY_ = null;
/**
* ID for the drag shadow filter, set in createDom.
* Belongs in Scratch Blocks but not Blockly.
* @type {string}
* @private
*/
Blockly.BlockDragSurfaceSvg.prototype.dragShadowFilterId_ = '';
/**
* Standard deviation for gaussian blur on drag shadow, in px.
* Belongs in Scratch Blocks but not Blockly.
* @type {number}
* @const
*/
Blockly.BlockDragSurfaceSvg.SHADOW_STD_DEVIATION = 6;
/**
* Create the drag surface and inject it into the container.
*/
Blockly.BlockDragSurfaceSvg.prototype.createDom = function () {
if (this.SVG_) {
return; // Already created.
}
this.SVG_ = Blockly.utils.createSvgElement(
'svg',
{
xmlns: Blockly.SVG_NS,
'xmlns:html': Blockly.HTML_NS,
'xmlns:xlink': 'http://www.w3.org/1999/xlink',
version: '1.1',
class: 'blocklyBlockDragSurface'
},
this.container_
);
this.dragGroup_ = Blockly.utils.createSvgElement('g', {}, this.SVG_);
// Belongs in Scratch Blocks, but not Blockly.
var defs = Blockly.utils.createSvgElement('defs', {}, this.SVG_);
this.dragShadowFilterId_ = this.createDropShadowDom_(defs);
this.dragGroup_.setAttribute(
'filter',
'url(#' + this.dragShadowFilterId_ + ')'
);
};
/**
* Scratch-specific: Create the SVG def for the drop shadow.
* @param {Element} defs Defs element to insert the shadow filter definition
* @return {string} ID for the filter element
* @private
*/
Blockly.BlockDragSurfaceSvg.prototype.createDropShadowDom_ = function (defs) {
var rnd = String(Math.random()).substring(2);
// Adjust these width/height, x/y properties to stop the shadow from clipping
var dragShadowFilter = Blockly.utils.createSvgElement(
'filter',
{
id: 'blocklyDragShadowFilter' + rnd,
height: '140%',
width: '140%',
y: '-20%',
x: '-20%'
},
defs
);
Blockly.utils.createSvgElement(
'feGaussianBlur',
{
in: 'SourceAlpha',
stdDeviation: Blockly.BlockDragSurfaceSvg.SHADOW_STD_DEVIATION
},
dragShadowFilter
);
var componentTransfer = Blockly.utils.createSvgElement(
'feComponentTransfer',
{ result: 'offsetBlur' },
dragShadowFilter
);
// Shadow opacity is specified in the adjustable colour library,
// since the darkness of the shadow largely depends on the workspace colour.
Blockly.utils.createSvgElement(
'feFuncA',
{
type: 'linear',
slope: Blockly.Colours.dragShadowOpacity
},
componentTransfer
);
Blockly.utils.createSvgElement(
'feComposite',
{
in: 'SourceGraphic',
in2: 'offsetBlur',
operator: 'over'
},
dragShadowFilter
);
return dragShadowFilter.id;
};
/**
* Set the SVG blocks on the drag surface's group and show the surface.
* Only one block group should be on the drag surface at a time.
* @param {!Element} blocks Block or group of blocks to place on the drag
* surface.
*/
Blockly.BlockDragSurfaceSvg.prototype.setBlocksAndShow = function (blocks) {
goog.asserts.assert(
this.dragGroup_.childNodes.length == 0,
'Already dragging a block.'
);
// appendChild removes the blocks from the previous parent
this.dragGroup_.appendChild(blocks);
this.SVG_.style.display = 'block';
this.surfaceXY_ = new goog.math.Coordinate(0, 0);
// This allows blocks to be dragged outside of the blockly svg space.
// This should be reset to hidden at the end of the block drag.
// Note that this behavior is different from blockly where block disappear
// "under" the blockly area.
var injectionDiv = document.getElementsByClassName('injectionDiv')[0];
injectionDiv.style.overflow = 'visible';
};
/**
* Translate and scale the entire drag surface group to the given position, to
* keep in sync with the workspace.
* @param {number} x X translation in workspace coordinates.
* @param {number} y Y translation in workspace coordinates.
* @param {number} scale Scale of the group.
*/
Blockly.BlockDragSurfaceSvg.prototype.translateAndScaleGroup = function (
x,
y,
scale
) {
this.scale_ = scale;
// This is a work-around to prevent a the blocks from rendering
// fuzzy while they are being dragged on the drag surface.
var fixedX = x.toFixed(0);
var fixedY = y.toFixed(0);
this.dragGroup_.setAttribute(
'transform',
'translate(' + fixedX + ',' + fixedY + ') scale(' + scale + ')'
);
};
/**
* Translate the drag surface's SVG based on its internal state.
* @private
*/
Blockly.BlockDragSurfaceSvg.prototype.translateSurfaceInternal_ = function () {
var x = this.surfaceXY_.x;
var y = this.surfaceXY_.y;
// This is a work-around to prevent a the blocks from rendering
// fuzzy while they are being dragged on the drag surface.
x = x.toFixed(0);
y = y.toFixed(0);
this.SVG_.style.display = 'block';
Blockly.utils.setCssTransform(
this.SVG_,
'translate3d(' + x + 'px, ' + y + 'px, 0px)'
);
};
/**
* Translate the entire drag surface during a drag.
* We translate the drag surface instead of the blocks inside the surface
* so that the browser avoids repainting the SVG.
* Because of this, the drag coordinates must be adjusted by scale.
* @param {number} x X translation for the entire surface.
* @param {number} y Y translation for the entire surface.
*/
Blockly.BlockDragSurfaceSvg.prototype.translateSurface = function (x, y) {
this.surfaceXY_ = new goog.math.Coordinate(x * this.scale_, y * this.scale_);
this.translateSurfaceInternal_();
};
/**
* Reports the surface translation in scaled workspace coordinates.
* Use this when finishing a drag to return blocks to the correct position.
* @return {!goog.math.Coordinate} Current translation of the surface.
*/
Blockly.BlockDragSurfaceSvg.prototype.getSurfaceTranslation = function () {
var xy = Blockly.utils.getRelativeXY(this.SVG_);
return new goog.math.Coordinate(xy.x / this.scale_, xy.y / this.scale_);
};
/**
* Provide a reference to the drag group (primarily for
* BlockSvg.getRelativeToSurfaceXY).
* @return {Element} Drag surface group element.
*/
Blockly.BlockDragSurfaceSvg.prototype.getGroup = function () {
return this.dragGroup_;
};
/**
* 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.
*/
Blockly.BlockDragSurfaceSvg.prototype.getCurrentBlock = function () {
return this.dragGroup_.firstChild;
};
/**
* Clear the group and hide the surface; move the blocks off onto the provided
* element.
* If the block is being deleted it doesn't need to go back to the original
* surface, since it would be removed immediately during dispose.
* @param {Element=} opt_newSurface Surface the dragging blocks should be moved
* to, or null if the blocks should be removed from this surface without
* being moved to a different surface.
*/
Blockly.BlockDragSurfaceSvg.prototype.clearAndHide = function (opt_newSurface) {
if (opt_newSurface) {
// appendChild removes the node from this.dragGroup_
opt_newSurface.appendChild(this.getCurrentBlock());
} else {
this.dragGroup_.removeChild(this.getCurrentBlock());
}
this.SVG_.style.display = 'none';
goog.asserts.assert(
this.dragGroup_.childNodes.length == 0,
'Drag group was not cleared.'
);
this.surfaceXY_ = null;
// Reset the overflow property back to hidden so that nothing appears outside
// of the blockly area.
// Note that this behavior is different from blockly. See note in
// setBlocksAndShow.
var injectionDiv = document.getElementsByClassName('injectionDiv')[0];
injectionDiv.style.overflow = 'hidden';
};