mirror of
https://github.com/google/blockly.git
synced 2026-01-06 16:40:07 +01:00
219 lines
6.8 KiB
TypeScript
219 lines
6.8 KiB
TypeScript
/**
|
|
* @license
|
|
* Copyright 2018 Google LLC
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
/**
|
|
* @fileoverview Methods animating a block on connection and disconnection.
|
|
*/
|
|
'use strict';
|
|
|
|
/**
|
|
* Methods animating a block on connection and disconnection.
|
|
* @namespace Blockly.blockAnimations
|
|
*/
|
|
goog.module('Blockly.blockAnimations');
|
|
|
|
const dom = goog.require('Blockly.utils.dom');
|
|
/* eslint-disable-next-line no-unused-vars */
|
|
const {BlockSvg} = goog.requireType('Blockly.BlockSvg');
|
|
const {Svg} = goog.require('Blockly.utils.Svg');
|
|
|
|
/**
|
|
* A bounding box for a cloned block.
|
|
* @typedef {{
|
|
* x: number,
|
|
* y: number,
|
|
* width: number,
|
|
* height: number
|
|
* }}
|
|
*/
|
|
let CloneRect; // eslint-disable-line no-unused-vars
|
|
|
|
/**
|
|
* PID of disconnect UI animation. There can only be one at a time.
|
|
* @type {number}
|
|
*/
|
|
let disconnectPid = 0;
|
|
|
|
/**
|
|
* SVG group of wobbling block. There can only be one at a time.
|
|
* @type {Element}
|
|
*/
|
|
let disconnectGroup = null;
|
|
|
|
/**
|
|
* Play some UI effects (sound, animation) when disposing of a block.
|
|
* @param {!BlockSvg} block The block being disposed of.
|
|
* @alias Blockly.blockAnimations.disposeUiEffect
|
|
* @package
|
|
*/
|
|
const disposeUiEffect = function(block) {
|
|
const workspace = block.workspace;
|
|
const svgGroup = block.getSvgRoot();
|
|
workspace.getAudioManager().play('delete');
|
|
|
|
const xy = workspace.getSvgXY(svgGroup);
|
|
// Deeply clone the current block.
|
|
const clone = svgGroup.cloneNode(true);
|
|
clone.setAttribute('transform', 'translate(' + xy.x + ',' + xy.y + ')');
|
|
workspace.getParentSvg().appendChild(clone);
|
|
const cloneRect =
|
|
{'x': xy.x, 'y': xy.y, 'width': block.width, 'height': block.height};
|
|
// Start the animation.
|
|
disposeUiStep(clone, cloneRect, workspace.RTL, new Date, workspace.scale);
|
|
};
|
|
exports.disposeUiEffect = disposeUiEffect;
|
|
|
|
/**
|
|
* 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 {!CloneRect} rect Starting rect of the clone.
|
|
* @param {boolean} rtl True if RTL, false if LTR.
|
|
* @param {!Date} start Date of animation's start.
|
|
* @param {number} workspaceScale Scale of workspace.
|
|
*/
|
|
const disposeUiStep = function(clone, rect, rtl, start, workspaceScale) {
|
|
const ms = (new Date).getTime() - start.getTime();
|
|
const percent = ms / 150;
|
|
if (percent > 1) {
|
|
dom.removeNode(clone);
|
|
} else {
|
|
const x =
|
|
rect.x + (rtl ? -1 : 1) * rect.width * workspaceScale / 2 * percent;
|
|
const y = rect.y + rect.height * workspaceScale * percent;
|
|
const scale = (1 - percent) * workspaceScale;
|
|
clone.setAttribute(
|
|
'transform',
|
|
'translate(' + x + ',' + y + ')' +
|
|
' scale(' + scale + ')');
|
|
setTimeout(disposeUiStep, 10, clone, rect, rtl, start, workspaceScale);
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Play some UI effects (sound, ripple) after a connection has been established.
|
|
* @param {!BlockSvg} block The block being connected.
|
|
* @alias Blockly.blockAnimations.connectionUiEffect
|
|
* @package
|
|
*/
|
|
const connectionUiEffect = function(block) {
|
|
const workspace = block.workspace;
|
|
const 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.
|
|
const 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;
|
|
}
|
|
const ripple = dom.createSvgElement(
|
|
Svg.CIRCLE, {
|
|
'cx': xy.x,
|
|
'cy': xy.y,
|
|
'r': 0,
|
|
'fill': 'none',
|
|
'stroke': '#888',
|
|
'stroke-width': 10,
|
|
},
|
|
workspace.getParentSvg());
|
|
// Start the animation.
|
|
connectionUiStep(ripple, new Date, scale);
|
|
};
|
|
exports.connectionUiEffect = connectionUiEffect;
|
|
|
|
/**
|
|
* Expand a ripple around a connection.
|
|
* @param {!SVGElement} ripple Element to animate.
|
|
* @param {!Date} start Date of animation's start.
|
|
* @param {number} scale Scale of workspace.
|
|
*/
|
|
const connectionUiStep = function(ripple, start, scale) {
|
|
const ms = (new Date).getTime() - start.getTime();
|
|
const percent = ms / 150;
|
|
if (percent > 1) {
|
|
dom.removeNode(ripple);
|
|
} else {
|
|
ripple.setAttribute('r', percent * 25 * scale);
|
|
ripple.style.opacity = 1 - percent;
|
|
disconnectPid = setTimeout(connectionUiStep, 10, ripple, start, scale);
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Play some UI effects (sound, animation) when disconnecting a block.
|
|
* @param {!BlockSvg} block The block being disconnected.
|
|
* @alias Blockly.blockAnimations.disconnectUiEffect
|
|
* @package
|
|
*/
|
|
const 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.
|
|
const DISPLACEMENT = 10;
|
|
// Scale magnitude of skew to height of block.
|
|
const height = block.getHeightWidth().height;
|
|
let magnitude = Math.atan(DISPLACEMENT / height) / Math.PI * 180;
|
|
if (!block.RTL) {
|
|
magnitude *= -1;
|
|
}
|
|
// Start the animation.
|
|
disconnectUiStep(block.getSvgRoot(), magnitude, new Date);
|
|
};
|
|
exports.disconnectUiEffect = disconnectUiEffect;
|
|
|
|
/**
|
|
* Animate a brief wiggle of a disconnected block.
|
|
* @param {!SVGElement} group SVG element to animate.
|
|
* @param {number} magnitude Maximum degrees skew (reversed for RTL).
|
|
* @param {!Date} start Date of animation's start.
|
|
*/
|
|
const disconnectUiStep = function(group, magnitude, start) {
|
|
const DURATION = 200; // Milliseconds.
|
|
const WIGGLES = 3; // Half oscillations.
|
|
|
|
const ms = (new Date).getTime() - start.getTime();
|
|
const percent = ms / DURATION;
|
|
|
|
if (percent > 1) {
|
|
(/** @type {?} */ (group)).skew_ = '';
|
|
} else {
|
|
const skew = Math.round(
|
|
Math.sin(percent * Math.PI * WIGGLES) * (1 - percent) * magnitude);
|
|
(/** @type {?} */ (group)).skew_ = 'skewX(' + skew + ')';
|
|
disconnectGroup = group;
|
|
disconnectPid = setTimeout(disconnectUiStep, 10, group, magnitude, start);
|
|
}
|
|
group.setAttribute(
|
|
'transform',
|
|
(/** @type {?} */ (group)).translate_ + (/** @type {?} */ (group)).skew_);
|
|
};
|
|
|
|
/**
|
|
* Stop the disconnect UI animation immediately.
|
|
* @alias Blockly.blockAnimations.disconnectUiStop
|
|
* @package
|
|
*/
|
|
const disconnectUiStop = function() {
|
|
if (disconnectGroup) {
|
|
clearTimeout(disconnectPid);
|
|
const group = disconnectGroup;
|
|
(/** @type {?} */ (group)).skew_ = '';
|
|
group.setAttribute('transform', (/** @type {?} */ (group)).translate_);
|
|
disconnectGroup = null;
|
|
}
|
|
};
|
|
exports.disconnectUiStop = disconnectUiStop;
|