From 4e3b1148324ec1073e43d008302057ac8441cd71 Mon Sep 17 00:00:00 2001 From: Rachel Fenichel Date: Mon, 16 Apr 2018 15:28:52 -0700 Subject: [PATCH] Move block animation code to a new file and rebuild --- blockly_compressed.js | 25 ++-- blockly_uncompressed.js | 10 +- blocks_compressed.js | 6 +- core/block_animations.js | 213 +++++++++++++++++++++++++++++ core/block_dragger.js | 7 +- core/block_svg.js | 176 +----------------------- core/dragged_connection_manager.js | 4 +- core/gesture.js | 3 +- 8 files changed, 245 insertions(+), 199 deletions(-) create mode 100644 core/block_animations.js diff --git a/blockly_compressed.js b/blockly_compressed.js index d89c342da..f544695e9 100644 --- a/blockly_compressed.js +++ b/blockly_compressed.js @@ -1025,7 +1025,13 @@ Blockly.ConnectionDB.prototype.getNeighbours=function(a,b){function c(a){var c=e Blockly.ConnectionDB.prototype.searchForClosest=function(a,b,c){if(!this.length)return{connection:null,radius:b};var d=a.y_,e=a.x_;a.x_=e+c.x;a.y_=d+c.y;var f=this.findPositionForConnection_(a);c=null;for(var g=b,h,k=f-1;0<=k&&this.isInYRange_(k,a.y_,b);)h=this[k],a.isConnectionAllowed(h,g)&&(c=h,g=h.distanceFrom(a)),k--;for(;fc)){var d=b.getSvgXY(a.getSvgRoot());a.outputConnection?(d.x+=(a.RTL?3:-3)*c,d.y+=13*c):a.previousConnection&&(d.x+=(a.RTL?-23:23)*c,d.y+=3*c);a=Blockly.utils.createSvgElement("circle",{cx:d.x,cy:d.y,r:0,fill:"none",stroke:"#888","stroke-width":10},b.getParentSvg());Blockly.BlockAnimations.connectionUiStep_(a,new Date,c)}}; +Blockly.BlockAnimations.connectionUiStep_=function(a,b,c){var d=(new Date-b)/150;1a.workspace.scale)){var b=a.getHeightWidth().height;b=Math.atan(10/b)/Math.PI*180;a.RTL||(b*=-1);Blockly.BlockAnimations.disconnectUiStep_(a.getSvgRoot(),b,new Date)}}; +Blockly.BlockAnimations.disconnectUiStep_=function(a,b,c){var d=(new Date-c)/200;1this.workspace.scale)){var a=this.workspace.getSvgXY(this.svgGroup_);this.outputConnection?(a.x+=(this.RTL?3:-3)*this.workspace.scale,a.y+=13*this.workspace.scale):this.previousConnection&&(a.x+=(this.RTL?-23:23)*this.workspace.scale,a.y+=3*this.workspace.scale);a=Blockly.utils.createSvgElement("circle",{cx:a.x,cy:a.y,r:0,fill:"none",stroke:"#888","stroke-width":10},this.workspace.getParentSvg()); -Blockly.BlockSvg.connectionUiStep_(a,new Date,this.workspace.scale)}};Blockly.BlockSvg.connectionUiStep_=function(a,b,c){var d=(new Date-b)/150;1this.workspace.scale)){var a=this.getHeightWidth().height;a=Math.atan(10/a)/Math.PI*180;this.RTL||(a*=-1);Blockly.BlockSvg.disconnectUiStep_(this.svgGroup_,a,new Date)}}; -Blockly.BlockSvg.disconnectUiStep_=function(a,b,c){var d=(new Date-c)/200;1\u200f",GTE:"\u200f\u2265\u200f"},b=this.getField("OP");if(b){b=b.getOptions();for(var c=0;ce;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.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,colour:"%{BKY_LOOPS_HUE}",tooltip:"%{BKY_CONTROLS_REPEAT_TOOLTIP}",helpUrl:"%{BKY_CONTROLS_REPEAT_HELPURL}"},{type:"controls_repeat",message0:"%{BKY_CONTROLS_REPEAT_TITLE}",args0:[{type:"field_number",name:"TIMES", diff --git a/core/block_animations.js b/core/block_animations.js new file mode 100644 index 000000000..63031d515 --- /dev/null +++ b/core/block_animations.js @@ -0,0 +1,213 @@ +/** + * @license + * Visual Blocks Editor + * + * Copyright 2018 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 Methods animating a block on connection and disconnection. + * @author fenichel@google.com (Rachel Fenichel) + */ +'use strict'; + +goog.provide('Blockly.BlockAnimations'); + + +/** + * PID of disconnect UI animation. There can only be one at a time. + * @type {number} + * @private + */ +Blockly.BlockAnimations.disconnectPid_ = 0; + +/** + * SVG group of wobbling block. There can only be one at a time. + * @type {Element} + * @private + */ +Blockly.BlockAnimations.disconnectGroup_ = null; + +/** + * Play some UI effects (sound, animation) when disposing of a block. + * @param {!Blockly.BlockSvg} block The block being disposed of. + * @package + */ +Blockly.BlockAnimations.disposeUiEffect = function(block) { + var workspace = block.workspace; + var svgGroup = block.getSvgRoot(); + workspace.getAudioManager().play('delete'); + + var xy = workspace.getSvgXY(svgGroup); + // Deeply clone the current block. + var clone = svgGroup.cloneNode(true); + clone.translateX_ = xy.x; + clone.translateY_ = xy.y; + clone.setAttribute('transform', 'translate(' + xy.x + ',' + xy.y + ')'); + workspace.getParentSvg().appendChild(clone); + clone.bBox_ = clone.getBBox(); + // Start the animation. + Blockly.BlockAnimations.disposeUiStep_(clone, workspace.RTL, new Date, + workspace.scale); +}; + +/** + * Animate a cloned block and eventually dispose of it. + * This is a class method, not an instance method since the original block has + * been destroyed and is no longer accessible. + * @param {!Element} clone SVG element to animate and dispose of. + * @param {boolean} rtl True if RTL, false if LTR. + * @param {!Date} start Date of animation's start. + * @param {number} workspaceScale Scale of workspace. + * @private + */ +Blockly.BlockAnimations.disposeUiStep_ = function(clone, rtl, start, + workspaceScale) { + var ms = new Date - start; + var percent = ms / 150; + if (percent > 1) { + goog.dom.removeNode(clone); + } else { + var x = clone.translateX_ + + (rtl ? -1 : 1) * clone.bBox_.width * workspaceScale / 2 * percent; + var y = clone.translateY_ + clone.bBox_.height * workspaceScale * percent; + var scale = (1 - percent) * workspaceScale; + clone.setAttribute('transform', 'translate(' + x + ',' + y + ')' + + ' scale(' + scale + ')'); + setTimeout(Blockly.BlockAnimations.disposeUiStep_, 10, clone, rtl, start, + workspaceScale); + } +}; + +/** + * Play some UI effects (sound, ripple) after a connection has been established. + * @param {!Blockly.BlockSvg} block The block being connected. + * @package + */ +Blockly.BlockAnimations.connectionUiEffect = function(block) { + var workspace = block.workspace; + var scale = workspace.scale; + workspace.getAudioManager().play('click'); + if (scale < 1) { + return; // Too small to care about visual effects. + } + // Determine the absolute coordinates of the inferior block. + var xy = workspace.getSvgXY(block.getSvgRoot()); + // Offset the coordinates based on the two connection types, fix scale. + if (block.outputConnection) { + xy.x += (block.RTL ? 3 : -3) * scale; + xy.y += 13 * scale; + } else if (block.previousConnection) { + xy.x += (block.RTL ? -23 : 23) * scale; + xy.y += 3 * scale; + } + var ripple = Blockly.utils.createSvgElement('circle', + { + 'cx': xy.x, + 'cy': xy.y, + 'r': 0, + 'fill': 'none', + 'stroke': '#888', + 'stroke-width': 10 + }, + workspace.getParentSvg()); + // Start the animation. + Blockly.BlockAnimations.connectionUiStep_(ripple, new Date, scale); +}; + +/** + * Expand a ripple around a connection. + * @param {!Element} ripple Element to animate. + * @param {!Date} start Date of animation's start. + * @param {number} scale Scale of workspace. + * @private + */ +Blockly.BlockAnimations.connectionUiStep_ = function(ripple, start, scale) { + var ms = new Date - start; + var percent = ms / 150; + if (percent > 1) { + goog.dom.removeNode(ripple); + } else { + ripple.setAttribute('r', percent * 25 * scale); + ripple.style.opacity = 1 - percent; + Blockly.BlockAnimations.disconnectPid_ = setTimeout( + Blockly.BlockAnimations.connectionUiStep_, 10, ripple, start, scale); + } +}; + +/** + * Play some UI effects (sound, animation) when disconnecting a block. + * @param {!Blockly.BlockSvg} block The block being disconnected. + * @package + */ +Blockly.BlockAnimations.disconnectUiEffect = function(block) { + block.workspace.getAudioManager().play('disconnect'); + if (block.workspace.scale < 1) { + return; // Too small to care about visual effects. + } + // Horizontal distance for bottom of block to wiggle. + var DISPLACEMENT = 10; + // Scale magnitude of skew to height of block. + var height = block.getHeightWidth().height; + var magnitude = Math.atan(DISPLACEMENT / height) / Math.PI * 180; + if (!block.RTL) { + magnitude *= -1; + } + // Start the animation. + Blockly.BlockAnimations.disconnectUiStep_( + block.getSvgRoot(), magnitude, new Date); +}; +/** + * Animate a brief wiggle of a disconnected block. + * @param {!Element} group SVG element to animate. + * @param {number} magnitude Maximum degrees skew (reversed for RTL). + * @param {!Date} start Date of animation's start. + * @private + */ +Blockly.BlockAnimations.disconnectUiStep_ = function(group, magnitude, start) { + var DURATION = 200; // Milliseconds. + var WIGGLES = 3; // Half oscillations. + + var ms = new Date - start; + var percent = ms / DURATION; + + if (percent > 1) { + group.skew_ = ''; + } else { + var skew = Math.round( + Math.sin(percent * Math.PI * WIGGLES) * (1 - percent) * magnitude); + group.skew_ = 'skewX(' + skew + ')'; + Blockly.BlockAnimations.disconnectGroup_ = group; + Blockly.BlockAnimations.disconnectPid_ = + setTimeout(Blockly.BlockAnimations.disconnectUiStep_, 10, group, + magnitude, start); + } + group.setAttribute('transform', group.translate_ + group.skew_); +}; + +/** + * Stop the disconnect UI animation immediately. + * @package + */ +Blockly.BlockAnimations.disconnectUiStop = function() { + if (Blockly.BlockAnimations.disconnectGroup_) { + clearTimeout(Blockly.BlockAnimations.disconnectPid_); + var group = Blockly.BlockAnimations.disconnectGroup_; + group.skew_ = ''; + group.setAttribute('transform', group.translate_); + Blockly.BlockAnimations.disconnectGroup_ = null; + } +}; diff --git a/core/block_dragger.js b/core/block_dragger.js index dd1feba98..f785106e1 100644 --- a/core/block_dragger.js +++ b/core/block_dragger.js @@ -26,6 +26,7 @@ goog.provide('Blockly.BlockDragger'); +goog.require('Blockly.BlockAnimations'); goog.require('Blockly.DraggedConnectionManager'); goog.require('Blockly.Events.BlockMove'); @@ -153,7 +154,7 @@ Blockly.BlockDragger.prototype.startBlockDrag = function(currentDragDeltaXY, hea } this.workspace_.setResizesEnabled(false); - Blockly.BlockSvg.disconnectUiStop_(); + Blockly.BlockAnimations.disconnectUiStop(); if (this.draggingBlock_.getParent() || (healStack && this.draggingBlock_.nextConnection && @@ -163,7 +164,7 @@ Blockly.BlockDragger.prototype.startBlockDrag = function(currentDragDeltaXY, hea var newLoc = goog.math.Coordinate.sum(this.startXY_, delta); this.draggingBlock_.translate(newLoc.x, newLoc.y); - this.draggingBlock_.disconnectUiEffect(); + Blockly.BlockAnimations.disconnectUiEffect(this.draggingBlock_); } this.draggingBlock_.setDragging(true); // For future consideration: we may be able to put moveToDragSurface inside @@ -212,7 +213,7 @@ Blockly.BlockDragger.prototype.endBlockDrag = function(e, currentDragDeltaXY) { this.dragBlock(e, currentDragDeltaXY); this.dragIconData_ = []; - Blockly.BlockSvg.disconnectUiStop_(); + Blockly.BlockAnimations.disconnectUiStop(); var delta = this.pixelsToWorkspaceUnits_(currentDragDeltaXY); var newLoc = goog.math.Coordinate.sum(this.startXY_, delta); diff --git a/core/block_svg.js b/core/block_svg.js index 2e5727367..c6b2a93e0 100644 --- a/core/block_svg.js +++ b/core/block_svg.js @@ -27,6 +27,7 @@ goog.provide('Blockly.BlockSvg'); goog.require('Blockly.Block'); +goog.require('Blockly.BlockAnimations'); goog.require('Blockly.ContextMenu'); goog.require('Blockly.Events.Ui'); goog.require('Blockly.Events.BlockMove'); @@ -807,7 +808,7 @@ Blockly.BlockSvg.prototype.dispose = function(healStack, animate) { if (animate && this.rendered) { this.unplug(healStack); - this.disposeUiEffect(); + Blockly.BlockAnimations.disposeUiEffect(this); } // Stop rerendering. this.rendered = false; @@ -841,179 +842,6 @@ Blockly.BlockSvg.prototype.dispose = function(healStack, animate) { Blockly.Field.stopCache(); }; -/** - * Play some UI effects (sound, animation) when disposing of a block. - */ -Blockly.BlockSvg.prototype.disposeUiEffect = function() { - this.workspace.getAudioManager().play('delete'); - - var xy = this.workspace.getSvgXY(/** @type {!Element} */ (this.svgGroup_)); - // Deeply clone the current block. - var clone = this.svgGroup_.cloneNode(true); - clone.translateX_ = xy.x; - clone.translateY_ = xy.y; - clone.setAttribute('transform', - 'translate(' + clone.translateX_ + ',' + clone.translateY_ + ')'); - this.workspace.getParentSvg().appendChild(clone); - clone.bBox_ = clone.getBBox(); - // Start the animation. - Blockly.BlockSvg.disposeUiStep_(clone, this.RTL, new Date, - this.workspace.scale); -}; - -/** - * Animate a cloned block and eventually dispose of it. - * This is a class method, not an instance method since the original block has - * been destroyed and is no longer accessible. - * @param {!Element} clone SVG element to animate and dispose of. - * @param {boolean} rtl True if RTL, false if LTR. - * @param {!Date} start Date of animation's start. - * @param {number} workspaceScale Scale of workspace. - * @private - */ -Blockly.BlockSvg.disposeUiStep_ = function(clone, rtl, start, workspaceScale) { - var ms = new Date - start; - var percent = ms / 150; - if (percent > 1) { - goog.dom.removeNode(clone); - } else { - var x = clone.translateX_ + - (rtl ? -1 : 1) * clone.bBox_.width * workspaceScale / 2 * percent; - var y = clone.translateY_ + clone.bBox_.height * workspaceScale * percent; - var scale = (1 - percent) * workspaceScale; - clone.setAttribute('transform', 'translate(' + x + ',' + y + ')' + - ' scale(' + scale + ')'); - setTimeout( - Blockly.BlockSvg.disposeUiStep_, 10, clone, rtl, start, workspaceScale); - } -}; - -/** - * Play some UI effects (sound, ripple) after a connection has been established. - */ -Blockly.BlockSvg.prototype.connectionUiEffect = function() { - this.workspace.getAudioManager().play('click'); - if (this.workspace.scale < 1) { - return; // Too small to care about visual effects. - } - // Determine the absolute coordinates of the inferior block. - var xy = this.workspace.getSvgXY(/** @type {!Element} */ (this.svgGroup_)); - // Offset the coordinates based on the two connection types, fix scale. - if (this.outputConnection) { - xy.x += (this.RTL ? 3 : -3) * this.workspace.scale; - xy.y += 13 * this.workspace.scale; - } else if (this.previousConnection) { - xy.x += (this.RTL ? -23 : 23) * this.workspace.scale; - xy.y += 3 * this.workspace.scale; - } - var ripple = Blockly.utils.createSvgElement('circle', - { - 'cx': xy.x, - 'cy': xy.y, - 'r': 0, - 'fill': 'none', - 'stroke': '#888', - 'stroke-width': 10 - }, - this.workspace.getParentSvg()); - // Start the animation. - Blockly.BlockSvg.connectionUiStep_(ripple, new Date, this.workspace.scale); -}; - -/** - * Expand a ripple around a connection. - * @param {!Element} ripple Element to animate. - * @param {!Date} start Date of animation's start. - * @param {number} workspaceScale Scale of workspace. - * @private - */ -Blockly.BlockSvg.connectionUiStep_ = function(ripple, start, workspaceScale) { - var ms = new Date - start; - var percent = ms / 150; - if (percent > 1) { - goog.dom.removeNode(ripple); - } else { - ripple.setAttribute('r', percent * 25 * workspaceScale); - ripple.style.opacity = 1 - percent; - Blockly.BlockSvg.disconnectUiStop_.pid_ = setTimeout( - Blockly.BlockSvg.connectionUiStep_, 10, ripple, start, workspaceScale); - } -}; - -/** - * Play some UI effects (sound, animation) when disconnecting a block. - */ -Blockly.BlockSvg.prototype.disconnectUiEffect = function() { - this.workspace.getAudioManager().play('disconnect'); - if (this.workspace.scale < 1) { - return; // Too small to care about visual effects. - } - // Horizontal distance for bottom of block to wiggle. - var DISPLACEMENT = 10; - // Scale magnitude of skew to height of block. - var height = this.getHeightWidth().height; - var magnitude = Math.atan(DISPLACEMENT / height) / Math.PI * 180; - if (!this.RTL) { - magnitude *= -1; - } - // Start the animation. - Blockly.BlockSvg.disconnectUiStep_(this.svgGroup_, magnitude, new Date); -}; - -/** - * Animate a brief wiggle of a disconnected block. - * @param {!Element} group SVG element to animate. - * @param {number} magnitude Maximum degrees skew (reversed for RTL). - * @param {!Date} start Date of animation's start. - * @private - */ -Blockly.BlockSvg.disconnectUiStep_ = function(group, magnitude, start) { - var DURATION = 200; // Milliseconds. - var WIGGLES = 3; // Half oscillations. - - var ms = new Date - start; - var percent = ms / DURATION; - - if (percent > 1) { - group.skew_ = ''; - } else { - var skew = Math.round( - Math.sin(percent * Math.PI * WIGGLES) * (1 - percent) * magnitude); - group.skew_ = 'skewX(' + skew + ')'; - Blockly.BlockSvg.disconnectUiStop_.group = group; - Blockly.BlockSvg.disconnectUiStop_.pid = - setTimeout( - Blockly.BlockSvg.disconnectUiStep_, 10, group, magnitude, start); - } - group.setAttribute('transform', group.translate_ + group.skew_); -}; - -/** - * Stop the disconnect UI animation immediately. - * @private - */ -Blockly.BlockSvg.disconnectUiStop_ = function() { - if (Blockly.BlockSvg.disconnectUiStop_.group) { - clearTimeout(Blockly.BlockSvg.disconnectUiStop_.pid); - var group = Blockly.BlockSvg.disconnectUiStop_.group; - group.skew_ = ''; - group.setAttribute('transform', group.translate_); - Blockly.BlockSvg.disconnectUiStop_.group = null; - } -}; - -/** - * PID of disconnect UI animation. There can only be one at a time. - * @type {number} - */ -Blockly.BlockSvg.disconnectUiStop_.pid = 0; - -/** - * SVG group of wobbling block. There can only be one at a time. - * @type {Element} - */ -Blockly.BlockSvg.disconnectUiStop_.group = null; - /** * Change the colour of a block. */ diff --git a/core/dragged_connection_manager.js b/core/dragged_connection_manager.js index c51d31eab..5763a9a9d 100644 --- a/core/dragged_connection_manager.js +++ b/core/dragged_connection_manager.js @@ -26,6 +26,7 @@ goog.provide('Blockly.DraggedConnectionManager'); +goog.require('Blockly.BlockAnimations'); goog.require('Blockly.RenderedConnection'); goog.require('goog.math.Coordinate'); @@ -138,7 +139,8 @@ Blockly.DraggedConnectionManager.prototype.applyConnections = function() { // Determine which connection is inferior (lower in the source stack). var inferiorConnection = this.localConnection_.isSuperior() ? this.closestConnection_ : this.localConnection_; - inferiorConnection.getSourceBlock().connectionUiEffect(); + Blockly.BlockAnimations.connectionUiEffect( + inferiorConnection.getSourceBlock()); // Bring the just-edited stack to the front. var rootBlock = this.topBlock_.getRootBlock(); rootBlock.bringToFront(); diff --git a/core/gesture.js b/core/gesture.js index 070e94ac7..deac26c06 100644 --- a/core/gesture.js +++ b/core/gesture.js @@ -27,6 +27,7 @@ goog.provide('Blockly.Gesture'); +goog.require('Blockly.BlockAnimations'); goog.require('Blockly.BlockDragger'); goog.require('Blockly.BubbleDragger'); goog.require('Blockly.constants'); @@ -482,7 +483,7 @@ Blockly.Gesture.prototype.doStart = function(e) { } this.hasStarted_ = true; - Blockly.BlockSvg.disconnectUiStop_(); + Blockly.BlockAnimations.disconnectUiStop(); this.startWorkspace_.updateScreenCalculationsIfScrolled(); if (this.startWorkspace_.isMutator) { // Mutator's coordinate system could be out of date because the bubble was