mirror of
https://github.com/google/blockly.git
synced 2026-01-09 18:10:08 +01:00
This reverts commit 332c0fd2f2.
This commit is contained in:
committed by
GitHub
parent
c0934216f8
commit
cdb1215d95
245
core/block_drag_surface.ts
Normal file
245
core/block_drag_surface.ts
Normal file
@@ -0,0 +1,245 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2016 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @class
|
||||
*/
|
||||
import * as goog from '../closure/goog/goog.js';
|
||||
goog.declareModuleId('Blockly.BlockDragSurfaceSvg');
|
||||
|
||||
import {Coordinate} from './utils/coordinate.js';
|
||||
import * as deprecation from './utils/deprecation.js';
|
||||
import * as dom from './utils/dom.js';
|
||||
import {Svg} from './utils/svg.js';
|
||||
import * as svgMath from './utils/svg_math.js';
|
||||
|
||||
|
||||
/**
|
||||
* Class for a drag surface for the currently dragged block. This is a separate
|
||||
* SVG that contains only the currently moving block, or nothing.
|
||||
*
|
||||
* @alias Blockly.BlockDragSurfaceSvg
|
||||
*/
|
||||
export class BlockDragSurfaceSvg {
|
||||
/**
|
||||
* The root element of the drag surface.
|
||||
*/
|
||||
private svg: SVGElement;
|
||||
|
||||
/**
|
||||
* This is where blocks live while they are being dragged if the drag
|
||||
* surface is enabled.
|
||||
*/
|
||||
private dragGroup: SVGElement;
|
||||
|
||||
/**
|
||||
* Cached value for the scale of the drag surface.
|
||||
* Used to set/get the correct translation during and after a drag.
|
||||
*/
|
||||
private 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.
|
||||
*/
|
||||
private surfaceXY = new Coordinate(0, 0);
|
||||
|
||||
/**
|
||||
* Cached value for the translation of the child drag surface in pixel
|
||||
* units. Since the child drag surface tracks the translation of the
|
||||
* workspace this is ultimately the translation of the workspace.
|
||||
*/
|
||||
private readonly childSurfaceXY = new Coordinate(0, 0);
|
||||
|
||||
/** @param container Containing element. */
|
||||
constructor(private readonly container: Element) {
|
||||
this.svg = dom.createSvgElement(
|
||||
Svg.SVG, {
|
||||
'xmlns': dom.SVG_NS,
|
||||
'xmlns:html': dom.HTML_NS,
|
||||
'xmlns:xlink': dom.XLINK_NS,
|
||||
'version': '1.1',
|
||||
'class': 'blocklyBlockDragSurface',
|
||||
},
|
||||
this.container);
|
||||
|
||||
this.dragGroup = dom.createSvgElement(Svg.G, {}, this.svg);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the drag surface and inject it into the container.
|
||||
*
|
||||
* @deprecated The DOM is automatically created by the constructor.
|
||||
*/
|
||||
createDom() {
|
||||
// No alternative provided, because now the dom is just automatically
|
||||
// created in the constructor now.
|
||||
deprecation.warn('BlockDragSurfaceSvg createDom', 'June 2022', 'June 2023');
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 blocks Block or group of blocks to place on the drag surface.
|
||||
*/
|
||||
setBlocksAndShow(blocks: SVGElement) {
|
||||
if (this.dragGroup.childNodes.length) {
|
||||
throw Error('Already dragging a block.');
|
||||
}
|
||||
// appendChild removes the blocks from the previous parent
|
||||
this.dragGroup.appendChild(blocks);
|
||||
this.svg.style.display = 'block';
|
||||
this.surfaceXY = new Coordinate(0, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Translate and scale the entire drag surface group to the given position, to
|
||||
* keep in sync with the workspace.
|
||||
*
|
||||
* @param x X translation in pixel coordinates.
|
||||
* @param y Y translation in pixel coordinates.
|
||||
* @param scale Scale of the group.
|
||||
*/
|
||||
translateAndScaleGroup(x: number, y: number, scale: number) {
|
||||
this.scale = scale;
|
||||
// Make sure the svg exists on a pixel boundary so that it is not fuzzy.
|
||||
const roundX = Math.round(x);
|
||||
const roundY = Math.round(y);
|
||||
this.childSurfaceXY.x = roundX;
|
||||
this.childSurfaceXY.y = roundY;
|
||||
this.dragGroup.setAttribute(
|
||||
'transform',
|
||||
'translate(' + roundX + ',' + roundY + ') scale(' + scale + ')');
|
||||
}
|
||||
|
||||
/**
|
||||
* Translate the drag surface's SVG based on its internal state.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
translateSurfaceInternal_() {
|
||||
// Make sure the svg exists on a pixel boundary so that it is not fuzzy.
|
||||
const x = Math.round(this.surfaceXY.x);
|
||||
const y = Math.round(this.surfaceXY.y);
|
||||
this.svg.style.display = 'block';
|
||||
dom.setCssTransform(this.svg, 'translate3d(' + x + 'px, ' + y + 'px, 0)');
|
||||
}
|
||||
|
||||
/**
|
||||
* Translates the entire surface by a relative offset.
|
||||
*
|
||||
* @param deltaX Horizontal offset in pixel units.
|
||||
* @param deltaY Vertical offset in pixel units.
|
||||
*/
|
||||
translateBy(deltaX: number, deltaY: number) {
|
||||
const x = this.surfaceXY.x + deltaX;
|
||||
const y = this.surfaceXY.y + deltaY;
|
||||
this.surfaceXY = new Coordinate(x, y);
|
||||
this.translateSurfaceInternal_();
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 x X translation for the entire surface.
|
||||
* @param y Y translation for the entire surface.
|
||||
*/
|
||||
translateSurface(x: number, y: number) {
|
||||
this.surfaceXY = new 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.
|
||||
*
|
||||
* @returns Current translation of the surface.
|
||||
*/
|
||||
getSurfaceTranslation(): Coordinate {
|
||||
const xy = svgMath.getRelativeXY(this.svg);
|
||||
return new Coordinate(xy.x / this.scale, xy.y / this.scale);
|
||||
}
|
||||
|
||||
/**
|
||||
* Provide a reference to the drag group (primarily for
|
||||
* BlockSvg.getRelativeToSurfaceXY).
|
||||
*
|
||||
* @returns Drag surface group element.
|
||||
*/
|
||||
getGroup(): SVGElement {
|
||||
return this.dragGroup;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the SVG drag surface.
|
||||
*
|
||||
* @returns The SVG drag surface.
|
||||
*/
|
||||
getSvgRoot(): SVGElement {
|
||||
return this.svg;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current blocks on the drag surface, if any (primarily
|
||||
* for BlockSvg.getRelativeToSurfaceXY).
|
||||
*
|
||||
* @returns Drag surface block DOM element, or null if no blocks exist.
|
||||
*/
|
||||
getCurrentBlock(): Element|null {
|
||||
return this.dragGroup.firstChild as Element;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the translation of the child block surface
|
||||
* This surface is in charge of keeping track of how much the workspace has
|
||||
* moved.
|
||||
*
|
||||
* @returns The amount the workspace has been moved.
|
||||
*/
|
||||
getWsTranslation(): Coordinate {
|
||||
// Returning a copy so the coordinate can not be changed outside this class.
|
||||
return this.childSurfaceXY.clone();
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 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.
|
||||
*/
|
||||
clearAndHide(opt_newSurface?: Element) {
|
||||
const currentBlockElement = this.getCurrentBlock();
|
||||
if (currentBlockElement) {
|
||||
if (opt_newSurface) {
|
||||
// appendChild removes the node from this.dragGroup
|
||||
opt_newSurface.appendChild(currentBlockElement);
|
||||
} else {
|
||||
this.dragGroup.removeChild(currentBlockElement);
|
||||
}
|
||||
}
|
||||
this.svg.style.display = 'none';
|
||||
if (this.dragGroup.childNodes.length) {
|
||||
throw Error('Drag group was not cleared.');
|
||||
}
|
||||
this.surfaceXY = new Coordinate(0, 0);
|
||||
}
|
||||
}
|
||||
@@ -92,7 +92,7 @@ export class BlockDragger implements IBlockDragger {
|
||||
}
|
||||
|
||||
/**
|
||||
* Start dragging a block.
|
||||
* Start dragging a block. This includes moving it to the drag surface.
|
||||
*
|
||||
* @param currentDragDeltaXY How far the pointer has moved from the position
|
||||
* at mouse down, in pixel units.
|
||||
@@ -122,6 +122,10 @@ export class BlockDragger implements IBlockDragger {
|
||||
this.disconnectBlock_(healStack, currentDragDeltaXY);
|
||||
}
|
||||
this.draggingBlock_.setDragging(true);
|
||||
// For future consideration: we may be able to put moveToDragSurface inside
|
||||
// the block dragger, which would also let the block not track the block
|
||||
// drag surface.
|
||||
this.draggingBlock_.moveToDragSurface();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -215,11 +219,16 @@ export class BlockDragger implements IBlockDragger {
|
||||
|
||||
const preventMove = !!this.dragTarget_ &&
|
||||
this.dragTarget_.shouldPreventMove(this.draggingBlock_);
|
||||
let newLoc: Coordinate;
|
||||
let delta: Coordinate|null = null;
|
||||
if (!preventMove) {
|
||||
if (preventMove) {
|
||||
newLoc = this.startXY_;
|
||||
} else {
|
||||
const newValues = this.getNewLocationAfterDrag_(currentDragDeltaXY);
|
||||
delta = newValues.delta;
|
||||
newLoc = newValues.newLocation;
|
||||
}
|
||||
this.draggingBlock_.moveOffDragSurface(newLoc);
|
||||
|
||||
if (this.dragTarget_) {
|
||||
this.dragTarget_.onDrop(this.draggingBlock_);
|
||||
@@ -427,8 +436,6 @@ function initIconData(block: BlockSvg): IconPositionData[] {
|
||||
for (let i = 0, descendant; descendant = descendants[i]; i++) {
|
||||
const icons = descendant.getIcons();
|
||||
for (let j = 0; j < icons.length; j++) {
|
||||
// Only bother to track icons whose bubble is visible.
|
||||
if (!icons[j].isVisible()) continue;
|
||||
const data = {
|
||||
// Coordinate with x and y properties (workspace
|
||||
// coordinates).
|
||||
|
||||
@@ -140,6 +140,7 @@ export class BlockSvg extends Block implements IASTNodeLocationSvg,
|
||||
override nextConnection!: RenderedConnection;
|
||||
// TODO(b/109816955): remove '!', see go/strict-prop-init-fix.
|
||||
override previousConnection!: RenderedConnection;
|
||||
private readonly useDragSurface_: boolean;
|
||||
|
||||
private translation = '';
|
||||
|
||||
@@ -178,6 +179,12 @@ export class BlockSvg extends Block implements IASTNodeLocationSvg,
|
||||
this.pathObject =
|
||||
workspace.getRenderer().makePathObject(this.svgGroup_, this.style);
|
||||
|
||||
/**
|
||||
* Whether to move the block to the drag surface when it is dragged.
|
||||
* True if it should move, false if it should be translated directly.
|
||||
*/
|
||||
this.useDragSurface_ = !!workspace.getBlockDragSurface();
|
||||
|
||||
const svgPath = this.pathObject.svgPath;
|
||||
(svgPath as any).tooltip = this;
|
||||
Tooltip.bindMouseEvents(svgPath);
|
||||
@@ -351,6 +358,10 @@ export class BlockSvg extends Block implements IASTNodeLocationSvg,
|
||||
let x = 0;
|
||||
let y = 0;
|
||||
|
||||
const dragSurfaceGroup = this.useDragSurface_ ?
|
||||
this.workspace.getBlockDragSurface()!.getGroup() :
|
||||
null;
|
||||
|
||||
let element: SVGElement = this.getSvgRoot();
|
||||
if (element) {
|
||||
do {
|
||||
@@ -358,8 +369,19 @@ export class BlockSvg extends Block implements IASTNodeLocationSvg,
|
||||
const xy = svgMath.getRelativeXY(element);
|
||||
x += xy.x;
|
||||
y += xy.y;
|
||||
// If this element is the current element on the drag surface, include
|
||||
// the translation of the drag surface itself.
|
||||
if (this.useDragSurface_ &&
|
||||
this.workspace.getBlockDragSurface()!.getCurrentBlock() ===
|
||||
element) {
|
||||
const surfaceTranslation =
|
||||
this.workspace.getBlockDragSurface()!.getSurfaceTranslation();
|
||||
x += surfaceTranslation.x;
|
||||
y += surfaceTranslation.y;
|
||||
}
|
||||
element = element.parentNode as SVGElement;
|
||||
} while (element && element !== this.workspace.getCanvas());
|
||||
} while (element && element !== this.workspace.getCanvas() &&
|
||||
element !== dragSurfaceGroup);
|
||||
}
|
||||
return new Coordinate(x, y);
|
||||
}
|
||||
@@ -411,6 +433,31 @@ export class BlockSvg extends Block implements IASTNodeLocationSvg,
|
||||
return this.translation;
|
||||
}
|
||||
|
||||
/**
|
||||
* Move this block to its workspace's drag surface, accounting for
|
||||
* positioning. Generally should be called at the same time as
|
||||
* setDragging_(true). Does nothing if useDragSurface_ is false.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
moveToDragSurface() {
|
||||
if (!this.useDragSurface_) {
|
||||
return;
|
||||
}
|
||||
// The translation for drag surface blocks,
|
||||
// is equal to the current relative-to-surface position,
|
||||
// to keep the position in sync as it move on/off the surface.
|
||||
// This is in workspace coordinates.
|
||||
const xy = this.getRelativeToSurfaceXY();
|
||||
this.clearTransformAttributes_();
|
||||
this.workspace.getBlockDragSurface()!.translateSurface(xy.x, xy.y);
|
||||
// Execute the move on the top-level SVG component
|
||||
const svg = this.getSvgRoot();
|
||||
if (svg) {
|
||||
this.workspace.getBlockDragSurface()!.setBlocksAndShow(svg);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Move a block to a position.
|
||||
*
|
||||
@@ -422,15 +469,40 @@ export class BlockSvg extends Block implements IASTNodeLocationSvg,
|
||||
}
|
||||
|
||||
/**
|
||||
* Move this block during a drag.
|
||||
* Move this block back to the workspace block canvas.
|
||||
* Generally should be called at the same time as setDragging_(false).
|
||||
* Does nothing if useDragSurface_ is false.
|
||||
*
|
||||
* @param newXY The position the block should take on on the workspace canvas,
|
||||
* in workspace coordinates.
|
||||
* @internal
|
||||
*/
|
||||
moveOffDragSurface(newXY: Coordinate) {
|
||||
if (!this.useDragSurface_) {
|
||||
return;
|
||||
}
|
||||
// Translate to current position, turning off 3d.
|
||||
this.translate(newXY.x, newXY.y);
|
||||
this.workspace.getBlockDragSurface()!.clearAndHide(
|
||||
this.workspace.getCanvas());
|
||||
}
|
||||
|
||||
/**
|
||||
* Move this block during a drag, taking into account whether we are using a
|
||||
* drag surface to translate blocks.
|
||||
* This block must be a top-level block.
|
||||
*
|
||||
* @param newLoc The location to translate to, in workspace coordinates.
|
||||
* @internal
|
||||
*/
|
||||
moveDuringDrag(newLoc: Coordinate) {
|
||||
this.translate(newLoc.x, newLoc.y);
|
||||
this.getSvgRoot().setAttribute('transform', this.getTranslation());
|
||||
if (this.useDragSurface_) {
|
||||
this.workspace.getBlockDragSurface()!.translateSurface(
|
||||
newLoc.x, newLoc.y);
|
||||
} else {
|
||||
this.translate(newLoc.x, newLoc.y);
|
||||
this.getSvgRoot().setAttribute('transform', this.getTranslation());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -25,6 +25,7 @@ import './events/events_var_create.js';
|
||||
|
||||
import {Block} from './block.js';
|
||||
import * as blockAnimations from './block_animations.js';
|
||||
import {BlockDragSurfaceSvg} from './block_drag_surface.js';
|
||||
import {BlockDragger} from './block_dragger.js';
|
||||
import {BlockSvg} from './block_svg.js';
|
||||
import {BlocklyOptions} from './blockly_options.js';
|
||||
@@ -161,6 +162,7 @@ import {Workspace} from './workspace.js';
|
||||
import {WorkspaceAudio} from './workspace_audio.js';
|
||||
import {WorkspaceComment} from './workspace_comment.js';
|
||||
import {WorkspaceCommentSvg} from './workspace_comment_svg.js';
|
||||
import {WorkspaceDragSurfaceSvg} from './workspace_drag_surface_svg.js';
|
||||
import {WorkspaceDragger} from './workspace_dragger.js';
|
||||
import {resizeSvgContents as realResizeSvgContents, WorkspaceSvg} from './workspace_svg.js';
|
||||
import * as Xml from './xml.js';
|
||||
@@ -591,6 +593,7 @@ export {BasicCursor};
|
||||
export {Block};
|
||||
export {BlocklyOptions};
|
||||
export {BlockDragger};
|
||||
export {BlockDragSurfaceSvg};
|
||||
export {BlockSvg};
|
||||
export {Blocks};
|
||||
export {Bubble};
|
||||
@@ -732,6 +735,7 @@ export {Workspace};
|
||||
export {WorkspaceAudio};
|
||||
export {WorkspaceComment};
|
||||
export {WorkspaceCommentSvg};
|
||||
export {WorkspaceDragSurfaceSvg};
|
||||
export {WorkspaceDragger};
|
||||
export {WorkspaceSvg};
|
||||
export {ZoomControls};
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
import * as goog from '../closure/goog/goog.js';
|
||||
goog.declareModuleId('Blockly.Bubble');
|
||||
|
||||
import type {BlockDragSurfaceSvg} from './block_drag_surface.js';
|
||||
import type {BlockSvg} from './block_svg.js';
|
||||
import * as browserEvents from './browser_events.js';
|
||||
import type {IBubble} from './interfaces/i_bubble.js';
|
||||
@@ -782,13 +783,20 @@ export class Bubble implements IBubble {
|
||||
}
|
||||
|
||||
/**
|
||||
* Move this bubble during a drag.
|
||||
* Move this bubble during a drag, taking into account whether or not there is
|
||||
* a drag surface.
|
||||
*
|
||||
* @param dragSurface The surface that carries rendered items during a drag,
|
||||
* or null if no drag surface is in use.
|
||||
* @param newLoc The location to translate to, in workspace coordinates.
|
||||
* @internal
|
||||
*/
|
||||
moveDuringDrag(newLoc: Coordinate) {
|
||||
this.moveTo(newLoc.x, newLoc.y);
|
||||
moveDuringDrag(dragSurface: BlockDragSurfaceSvg, newLoc: Coordinate) {
|
||||
if (dragSurface) {
|
||||
dragSurface.translateSurface(newLoc.x, newLoc.y);
|
||||
} else {
|
||||
this.moveTo(newLoc.x, newLoc.y);
|
||||
}
|
||||
if (this.workspace_.RTL) {
|
||||
this.relativeLeft = this.anchorXY.x - newLoc.x - this.width;
|
||||
} else {
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
import * as goog from '../closure/goog/goog.js';
|
||||
goog.declareModuleId('Blockly.BubbleDragger');
|
||||
|
||||
import type {BlockDragSurfaceSvg} from './block_drag_surface.js';
|
||||
import {ComponentManager} from './component_manager.js';
|
||||
import type {CommentMove} from './events/events_comment_move.js';
|
||||
import * as eventUtils from './events/utils.js';
|
||||
@@ -35,6 +36,7 @@ export class BubbleDragger {
|
||||
/** Whether the bubble would be deleted if dropped immediately. */
|
||||
private wouldDeleteBubble_ = false;
|
||||
private readonly startXY_: Coordinate;
|
||||
private dragSurface_: BlockDragSurfaceSvg|null;
|
||||
|
||||
/**
|
||||
* @param bubble The item on the bubble canvas to drag.
|
||||
@@ -46,10 +48,16 @@ export class BubbleDragger {
|
||||
* beginning of the drag, in workspace coordinates.
|
||||
*/
|
||||
this.startXY_ = this.bubble.getRelativeToSurfaceXY();
|
||||
|
||||
/**
|
||||
* The drag surface to move bubbles to during a drag, or null if none should
|
||||
* be used. Block dragging and bubble dragging use the same surface.
|
||||
*/
|
||||
this.dragSurface_ = workspace.getBlockDragSurface();
|
||||
}
|
||||
|
||||
/**
|
||||
* Start dragging a bubble.
|
||||
* Start dragging a bubble. This includes moving it to the drag surface.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
@@ -60,6 +68,12 @@ export class BubbleDragger {
|
||||
|
||||
this.workspace.setResizesEnabled(false);
|
||||
this.bubble.setAutoLayout(false);
|
||||
if (this.dragSurface_) {
|
||||
this.bubble.moveTo(0, 0);
|
||||
this.dragSurface_.translateSurface(this.startXY_.x, this.startXY_.y);
|
||||
// Execute the move on the top-level SVG component.
|
||||
this.dragSurface_.setBlocksAndShow(this.bubble.getSvgRoot());
|
||||
}
|
||||
|
||||
this.bubble.setDragging && this.bubble.setDragging(true);
|
||||
}
|
||||
@@ -76,7 +90,7 @@ export class BubbleDragger {
|
||||
dragBubble(e: PointerEvent, currentDragDeltaXY: Coordinate) {
|
||||
const delta = this.pixelsToWorkspaceUnits_(currentDragDeltaXY);
|
||||
const newLoc = Coordinate.sum(this.startXY_, delta);
|
||||
this.bubble.moveDuringDrag(newLoc);
|
||||
this.bubble.moveDuringDrag(this.dragSurface_, newLoc);
|
||||
|
||||
const oldDragTarget = this.dragTarget_;
|
||||
this.dragTarget_ = this.workspace.getDragTarget(e);
|
||||
@@ -156,6 +170,9 @@ export class BubbleDragger {
|
||||
this.bubble.dispose();
|
||||
} else {
|
||||
// Put everything back onto the bubble canvas.
|
||||
if (this.dragSurface_) {
|
||||
this.dragSurface_.clearAndHide(this.workspace.getBubbleCanvas());
|
||||
}
|
||||
if (this.bubble.setDragging) {
|
||||
this.bubble.setDragging(false);
|
||||
}
|
||||
|
||||
38
core/css.ts
38
core/css.ts
@@ -94,6 +94,31 @@ let content = `
|
||||
-webkit-user-select: none;
|
||||
}
|
||||
|
||||
.blocklyWsDragSurface {
|
||||
display: none;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
}
|
||||
|
||||
/* Added as a separate rule with multiple classes to make it more specific
|
||||
than a bootstrap rule that selects svg:root. See issue #1275 for context.
|
||||
*/
|
||||
.blocklyWsDragSurface.blocklyOverflowVisible {
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
.blocklyBlockDragSurface {
|
||||
display: none;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
overflow: visible !important;
|
||||
z-index: 50; /* Display below toolbox, but above everything else. */
|
||||
}
|
||||
|
||||
.blocklyBlockCanvas.blocklyCanvasTransitioning,
|
||||
.blocklyBubbleCanvas.blocklyCanvasTransitioning {
|
||||
transition: transform .5s;
|
||||
@@ -229,6 +254,16 @@ let content = `
|
||||
cursor: -webkit-grabbing;
|
||||
}
|
||||
|
||||
/* Change the cursor on the whole drag surface in case the mouse gets
|
||||
ahead of block during a drag. This way the cursor is still a closed hand.
|
||||
*/
|
||||
.blocklyBlockDragSurface .blocklyDraggable {
|
||||
/* backup for browsers (e.g. IE11) that don't support grabbing */
|
||||
cursor: url("<<<PATH>>>/handclosed.cur"), auto;
|
||||
cursor: grabbing;
|
||||
cursor: -webkit-grabbing;
|
||||
}
|
||||
|
||||
.blocklyDragging.blocklyDraggingDelete {
|
||||
cursor: url("<<<PATH>>>/handdelete.cur"), auto;
|
||||
}
|
||||
@@ -281,7 +316,8 @@ let content = `
|
||||
Don't allow users to select text. It gets annoying when trying to
|
||||
drag a block and selected text moves instead.
|
||||
*/
|
||||
.blocklySvg text {
|
||||
.blocklySvg text,
|
||||
.blocklyBlockDragSurface text {
|
||||
user-select: none;
|
||||
-ms-user-select: none;
|
||||
-webkit-user-select: none;
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
import * as goog from '../closure/goog/goog.js';
|
||||
goog.declareModuleId('Blockly.inject');
|
||||
|
||||
import {BlockDragSurfaceSvg} from './block_drag_surface.js';
|
||||
import type {BlocklyOptions} from './blockly_options.js';
|
||||
import * as browserEvents from './browser_events.js';
|
||||
import * as bumpObjects from './bump_objects.js';
|
||||
@@ -30,6 +31,7 @@ import * as dom from './utils/dom.js';
|
||||
import {Svg} from './utils/svg.js';
|
||||
import * as userAgent from './utils/useragent.js';
|
||||
import * as WidgetDiv from './widgetdiv.js';
|
||||
import {WorkspaceDragSurfaceSvg} from './workspace_drag_surface_svg.js';
|
||||
import {WorkspaceSvg} from './workspace_svg.js';
|
||||
|
||||
|
||||
@@ -66,7 +68,14 @@ export function inject(
|
||||
(container as AnyDuringMigration).appendChild(subContainer);
|
||||
const svg = createDom(subContainer, options);
|
||||
|
||||
const workspace = createMainWorkspace(svg, options);
|
||||
// Create surfaces for dragging things. These are optimizations
|
||||
// so that the browser does not repaint during the drag.
|
||||
const blockDragSurface = new BlockDragSurfaceSvg(subContainer);
|
||||
|
||||
const workspaceDragSurface = new WorkspaceDragSurfaceSvg(subContainer);
|
||||
|
||||
const workspace =
|
||||
createMainWorkspace(svg, options, blockDragSurface, workspaceDragSurface);
|
||||
|
||||
init(workspace);
|
||||
|
||||
@@ -144,11 +153,16 @@ function createDom(container: Element, options: Options): SVGElement {
|
||||
*
|
||||
* @param svg SVG element with pattern defined.
|
||||
* @param options Dictionary of options.
|
||||
* @param blockDragSurface Drag surface SVG for the blocks.
|
||||
* @param workspaceDragSurface Drag surface SVG for the workspace.
|
||||
* @returns Newly created main workspace.
|
||||
*/
|
||||
function createMainWorkspace(svg: SVGElement, options: Options): WorkspaceSvg {
|
||||
function createMainWorkspace(
|
||||
svg: SVGElement, options: Options, blockDragSurface: BlockDragSurfaceSvg,
|
||||
workspaceDragSurface: WorkspaceDragSurfaceSvg): WorkspaceSvg {
|
||||
options.parentWorkspace = null;
|
||||
const mainWorkspace = new WorkspaceSvg(options);
|
||||
const mainWorkspace =
|
||||
new WorkspaceSvg(options, blockDragSurface, workspaceDragSurface);
|
||||
const wsOptions = mainWorkspace.options;
|
||||
mainWorkspace.scale = wsOptions.zoomOptions.startScale;
|
||||
svg.appendChild(mainWorkspace.createDom('blocklyMainBackground'));
|
||||
|
||||
@@ -19,7 +19,7 @@ goog.declareModuleId('Blockly.IBlockDragger');
|
||||
*/
|
||||
export interface IBlockDragger {
|
||||
/**
|
||||
* Start dragging a block.
|
||||
* Start dragging a block. This includes moving it to the drag surface.
|
||||
*
|
||||
* @param currentDragDeltaXY How far the pointer has moved from the position
|
||||
* at mouse down, in pixel units.
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
*/
|
||||
import * as goog from '../../closure/goog/goog.js';
|
||||
import type {Coordinate} from '../utils/coordinate.js';
|
||||
import type {BlockDragSurfaceSvg} from '../block_drag_surface.js';
|
||||
goog.declareModuleId('Blockly.IBubble');
|
||||
|
||||
import type {IContextMenu} from './i_contextmenu.js';
|
||||
@@ -53,11 +54,15 @@ export interface IBubble extends IDraggable, IContextMenu {
|
||||
setDragging(dragging: boolean): void;
|
||||
|
||||
/**
|
||||
* Move this bubble during a drag.
|
||||
* Move this bubble during a drag, taking into account whether or not there is
|
||||
* a drag surface.
|
||||
*
|
||||
* @param dragSurface The surface that carries rendered items during a drag,
|
||||
* or null if no drag surface is in use.
|
||||
* @param newLoc The location to translate to, in workspace coordinates.
|
||||
*/
|
||||
moveDuringDrag(newLoc: Coordinate): void;
|
||||
moveDuringDrag(dragSurface: BlockDragSurfaceSvg|null, newLoc: Coordinate):
|
||||
void;
|
||||
|
||||
/**
|
||||
* Move the bubble to the specified location in workspace coordinates.
|
||||
|
||||
@@ -713,6 +713,11 @@ export class Scrollbar {
|
||||
// Look up the current translation and record it.
|
||||
this.startDragHandle = this.handlePosition;
|
||||
|
||||
// Tell the workspace to setup its drag surface since it is about to move.
|
||||
// onMouseMoveHandle will call onScroll which actually tells the workspace
|
||||
// to move.
|
||||
this.workspace.setupDragSurface();
|
||||
|
||||
// Record the current mouse position.
|
||||
this.startDragMouse = this.horizontal ? e.clientX : e.clientY;
|
||||
this.onMouseUpWrapper_ = browserEvents.conditionalBind(
|
||||
@@ -739,6 +744,8 @@ export class Scrollbar {
|
||||
|
||||
/** Release the scrollbar handle and reset state accordingly. */
|
||||
private onMouseUpHandle() {
|
||||
// Tell the workspace to clean up now that the workspace is done moving.
|
||||
this.workspace.resetDragSurface();
|
||||
Touch.clearTouchIdentifier();
|
||||
this.cleanUp();
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ goog.declareModuleId('Blockly.WorkspaceCommentSvg');
|
||||
// Unused import preserved for side-effects. Remove if unneeded.
|
||||
import './events/events_selected.js';
|
||||
|
||||
import type {BlockDragSurfaceSvg} from './block_drag_surface.js';
|
||||
import * as browserEvents from './browser_events.js';
|
||||
import * as common from './common.js';
|
||||
// import * as ContextMenu from './contextmenu.js';
|
||||
@@ -90,6 +91,7 @@ export class WorkspaceCommentSvg extends WorkspaceComment implements
|
||||
|
||||
/** Whether the comment is rendered onscreen and is a part of the DOM. */
|
||||
private rendered_ = false;
|
||||
private readonly useDragSurface_: boolean;
|
||||
|
||||
/**
|
||||
* @param workspace The block's workspace.
|
||||
@@ -115,6 +117,12 @@ export class WorkspaceCommentSvg extends WorkspaceComment implements
|
||||
});
|
||||
this.svgGroup_.appendChild(this.svgRect_);
|
||||
|
||||
/**
|
||||
* Whether to move the comment to the drag surface when it is dragged.
|
||||
* True if it should move, false if it should be translated directly.
|
||||
*/
|
||||
this.useDragSurface_ = !!workspace.getBlockDragSurface();
|
||||
|
||||
this.render();
|
||||
}
|
||||
|
||||
@@ -298,6 +306,10 @@ export class WorkspaceCommentSvg extends WorkspaceComment implements
|
||||
let x = 0;
|
||||
let y = 0;
|
||||
|
||||
const dragSurfaceGroup = this.useDragSurface_ ?
|
||||
this.workspace.getBlockDragSurface()!.getGroup() :
|
||||
null;
|
||||
|
||||
let element: Node|null = this.getSvgRoot();
|
||||
if (element) {
|
||||
do {
|
||||
@@ -305,9 +317,20 @@ export class WorkspaceCommentSvg extends WorkspaceComment implements
|
||||
const xy = svgMath.getRelativeXY(element as Element);
|
||||
x += xy.x;
|
||||
y += xy.y;
|
||||
// If this element is the current element on the drag surface, include
|
||||
// the translation of the drag surface itself.
|
||||
if (this.useDragSurface_ &&
|
||||
this.workspace.getBlockDragSurface()!.getCurrentBlock() ===
|
||||
element) {
|
||||
const surfaceTranslation =
|
||||
this.workspace.getBlockDragSurface()!.getSurfaceTranslation();
|
||||
x += surfaceTranslation.x;
|
||||
y += surfaceTranslation.y;
|
||||
}
|
||||
|
||||
element = element.parentNode;
|
||||
} while (element && element !== this.workspace.getBubbleCanvas() &&
|
||||
element !== null);
|
||||
element !== dragSurfaceGroup);
|
||||
}
|
||||
this.xy_ = new Coordinate(x, y);
|
||||
return this.xy_;
|
||||
@@ -347,14 +370,43 @@ export class WorkspaceCommentSvg extends WorkspaceComment implements
|
||||
}
|
||||
|
||||
/**
|
||||
* Move this comment during a drag.
|
||||
* Move this comment to its workspace's drag surface, accounting for
|
||||
* positioning. Generally should be called at the same time as
|
||||
* setDragging(true). Does nothing if useDragSurface_ is false.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
moveToDragSurface() {
|
||||
if (!this.useDragSurface_) {
|
||||
return;
|
||||
}
|
||||
// The translation for drag surface blocks,
|
||||
// is equal to the current relative-to-surface position,
|
||||
// to keep the position in sync as it move on/off the surface.
|
||||
// This is in workspace coordinates.
|
||||
const xy = this.getRelativeToSurfaceXY();
|
||||
this.clearTransformAttributes_();
|
||||
this.workspace.getBlockDragSurface()!.translateSurface(xy.x, xy.y);
|
||||
// Execute the move on the top-level SVG component
|
||||
this.workspace.getBlockDragSurface()!.setBlocksAndShow(this.getSvgRoot());
|
||||
}
|
||||
|
||||
/**
|
||||
* Move this comment during a drag, taking into account whether we are using a
|
||||
* drag surface to translate blocks.
|
||||
*
|
||||
* @param dragSurface The surface that carries rendered items during a drag,
|
||||
* or null if no drag surface is in use.
|
||||
* @param newLoc The location to translate to, in workspace coordinates.
|
||||
* @internal
|
||||
*/
|
||||
moveDuringDrag(newLoc: Coordinate) {
|
||||
const translation = `translate(${newLoc.x}, ${newLoc.y})`;
|
||||
this.getSvgRoot().setAttribute('transform', translation);
|
||||
moveDuringDrag(dragSurface: BlockDragSurfaceSvg, newLoc: Coordinate) {
|
||||
if (dragSurface) {
|
||||
dragSurface.translateSurface(newLoc.x, newLoc.y);
|
||||
} else {
|
||||
const translation = `translate(${newLoc.x}, ${newLoc.y})`;
|
||||
this.getSvgRoot().setAttribute('transform', translation);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
176
core/workspace_drag_surface_svg.ts
Normal file
176
core/workspace_drag_surface_svg.ts
Normal file
@@ -0,0 +1,176 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2016 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* An SVG that floats on top of the workspace.
|
||||
* Blocks are moved into this SVG during a drag, improving performance.
|
||||
* The entire SVG is translated using CSS translation instead of SVG so the
|
||||
* blocks are never repainted during drag improving performance.
|
||||
*
|
||||
* @class
|
||||
*/
|
||||
import * as goog from '../closure/goog/goog.js';
|
||||
goog.declareModuleId('Blockly.WorkspaceDragSurfaceSvg');
|
||||
|
||||
import type {Coordinate} from './utils/coordinate.js';
|
||||
import * as dom from './utils/dom.js';
|
||||
import {Svg} from './utils/svg.js';
|
||||
import * as svgMath from './utils/svg_math.js';
|
||||
|
||||
|
||||
/**
|
||||
* Blocks are moved into this SVG during a drag, improving performance.
|
||||
* The entire SVG is translated using CSS transforms instead of SVG so the
|
||||
* blocks are never repainted during drag improving performance.
|
||||
*
|
||||
* @alias Blockly.WorkspaceDragSurfaceSvg
|
||||
*/
|
||||
export class WorkspaceDragSurfaceSvg {
|
||||
/**
|
||||
* The SVG drag surface. Set once by WorkspaceDragSurfaceSvg.createDom.
|
||||
*/
|
||||
private SVG!: SVGElement;
|
||||
|
||||
/**
|
||||
* The element to insert the block canvas and bubble canvas after when it
|
||||
* goes back in the DOM at the end of a drag.
|
||||
*/
|
||||
private previousSibling: Element|null = null;
|
||||
|
||||
/** @param container Containing element. */
|
||||
constructor(private readonly container: Element) {
|
||||
this.createDom();
|
||||
}
|
||||
|
||||
/** Create the drag surface and inject it into the container. */
|
||||
createDom() {
|
||||
if (this.SVG) {
|
||||
return; // Already created.
|
||||
}
|
||||
/**
|
||||
* Dom structure when the workspace is being dragged. If there is no drag in
|
||||
* progress, the SVG is empty and display: none.
|
||||
* <svg class="blocklyWsDragSurface" style=transform:translate3d(...)>
|
||||
* <g class="blocklyBlockCanvas"></g>
|
||||
* <g class="blocklyBubbleCanvas">/g>
|
||||
* </svg>
|
||||
*/
|
||||
this.SVG = dom.createSvgElement(Svg.SVG, {
|
||||
'xmlns': dom.SVG_NS,
|
||||
'xmlns:html': dom.HTML_NS,
|
||||
'xmlns:xlink': dom.XLINK_NS,
|
||||
'version': '1.1',
|
||||
'class': 'blocklyWsDragSurface blocklyOverflowVisible',
|
||||
});
|
||||
this.container.appendChild(this.SVG);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 x X translation for the entire surface
|
||||
* @param y Y translation for the entire surface
|
||||
* @internal
|
||||
*/
|
||||
translateSurface(x: number, y: number) {
|
||||
// Make sure the svg exists on a pixel boundary so that it is not fuzzy.
|
||||
const fixedX = Math.round(x);
|
||||
const fixedY = Math.round(y);
|
||||
|
||||
this.SVG.style.display = 'block';
|
||||
dom.setCssTransform(
|
||||
this.SVG, 'translate3d(' + fixedX + 'px, ' + fixedY + 'px, 0)');
|
||||
}
|
||||
|
||||
/**
|
||||
* Reports the surface translation in scaled workspace coordinates.
|
||||
* Use this when finishing a drag to return blocks to the correct position.
|
||||
*
|
||||
* @returns Current translation of the surface
|
||||
* @internal
|
||||
*/
|
||||
getSurfaceTranslation(): Coordinate {
|
||||
return svgMath.getRelativeXY((this.SVG));
|
||||
}
|
||||
|
||||
/**
|
||||
* Move the blockCanvas and bubbleCanvas out of the surface SVG and on to
|
||||
* newSurface.
|
||||
*
|
||||
* @param newSurface The element to put the drag surface contents into.
|
||||
* @internal
|
||||
*/
|
||||
clearAndHide(newSurface: SVGElement) {
|
||||
if (!newSurface) {
|
||||
throw Error(
|
||||
'Couldn\'t clear and hide the drag surface: missing new surface.');
|
||||
}
|
||||
const blockCanvas = this.SVG.childNodes[0] as Element;
|
||||
const bubbleCanvas = this.SVG.childNodes[1] as Element;
|
||||
if (!blockCanvas || !bubbleCanvas ||
|
||||
!(blockCanvas.classList.contains('blocklyBlockCanvas') ||
|
||||
!bubbleCanvas.classList.contains('blocklyBubbleCanvas'))) {
|
||||
throw Error(
|
||||
'Couldn\'t clear and hide the drag surface. A node was missing.');
|
||||
}
|
||||
|
||||
// If there is a previous sibling, put the blockCanvas back right
|
||||
// afterwards, otherwise insert it as the first child node in newSurface.
|
||||
if (this.previousSibling !== null) {
|
||||
dom.insertAfter(blockCanvas, this.previousSibling);
|
||||
} else {
|
||||
newSurface.insertBefore(blockCanvas, newSurface.firstChild);
|
||||
}
|
||||
|
||||
// Reattach the bubble canvas after the blockCanvas.
|
||||
dom.insertAfter(bubbleCanvas, blockCanvas);
|
||||
// Hide the drag surface.
|
||||
this.SVG.style.display = 'none';
|
||||
if (this.SVG.childNodes.length) {
|
||||
throw Error('Drag surface was not cleared.');
|
||||
}
|
||||
dom.setCssTransform(this.SVG, '');
|
||||
this.previousSibling = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the SVG to have the block canvas and bubble canvas in it and then
|
||||
* show the surface.
|
||||
*
|
||||
* @param blockCanvas The block canvas <g> element from the
|
||||
* workspace.
|
||||
* @param bubbleCanvas The <g> element that contains the
|
||||
bubbles.
|
||||
* @param previousSibling The element to insert the block canvas and
|
||||
bubble canvas after when it goes back in the DOM at the end of a
|
||||
drag.
|
||||
* @param width The width of the workspace SVG element.
|
||||
* @param height The height of the workspace SVG element.
|
||||
* @param scale The scale of the workspace being dragged.
|
||||
* @internal
|
||||
*/
|
||||
setContentsAndShow(
|
||||
blockCanvas: SVGElement, bubbleCanvas: SVGElement,
|
||||
previousSibling: Element, width: number, height: number, scale: number) {
|
||||
if (this.SVG.childNodes.length) {
|
||||
throw Error('Already dragging a block.');
|
||||
}
|
||||
this.previousSibling = previousSibling;
|
||||
// Make sure the blocks and bubble canvas are scaled appropriately.
|
||||
blockCanvas.setAttribute(
|
||||
'transform', 'translate(0, 0) scale(' + scale + ')');
|
||||
bubbleCanvas.setAttribute(
|
||||
'transform', 'translate(0, 0) scale(' + scale + ')');
|
||||
this.SVG.setAttribute('width', String(width));
|
||||
this.SVG.setAttribute('height', String(height));
|
||||
this.SVG.appendChild(blockCanvas);
|
||||
this.SVG.appendChild(bubbleCanvas);
|
||||
this.SVG.style.display = 'block';
|
||||
}
|
||||
}
|
||||
@@ -20,6 +20,9 @@ import type {WorkspaceSvg} from './workspace_svg.js';
|
||||
/**
|
||||
* Class for a workspace dragger. It moves the workspace around when it is
|
||||
* being dragged by a mouse or touch.
|
||||
* Note that the workspace itself manages whether or not it has a drag surface
|
||||
* and how to do translations based on that. This simply passes the right
|
||||
* commands based on events.
|
||||
*/
|
||||
export class WorkspaceDragger {
|
||||
private readonly horizontalScrollEnabled_: boolean;
|
||||
@@ -62,6 +65,7 @@ export class WorkspaceDragger {
|
||||
if (common.getSelected()) {
|
||||
common.getSelected()!.unselect();
|
||||
}
|
||||
this.workspace.setupDragSurface();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -74,6 +78,7 @@ export class WorkspaceDragger {
|
||||
endDrag(currentDragDeltaXY: Coordinate) {
|
||||
// Make sure everything is up to date.
|
||||
this.drag(currentDragDeltaXY);
|
||||
this.workspace.resetDragSurface();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -20,6 +20,7 @@ import './events/events_theme_change.js';
|
||||
import './events/events_viewport.js';
|
||||
|
||||
import type {Block} from './block.js';
|
||||
import type {BlockDragSurfaceSvg} from './block_drag_surface.js';
|
||||
import type {BlockSvg} from './block_svg.js';
|
||||
import type {BlocklyOptions} from './blockly_options.js';
|
||||
import * as browserEvents from './browser_events.js';
|
||||
@@ -74,6 +75,7 @@ import {Workspace} from './workspace.js';
|
||||
import {WorkspaceAudio} from './workspace_audio.js';
|
||||
import {WorkspaceComment} from './workspace_comment.js';
|
||||
import {WorkspaceCommentSvg} from './workspace_comment_svg.js';
|
||||
import type {WorkspaceDragSurfaceSvg} from './workspace_drag_surface_svg.js';
|
||||
import * as Xml from './xml.js';
|
||||
import {ZoomControls} from './zoom_controls.js';
|
||||
import {ContextMenuOption} from './contextmenu_registry.js';
|
||||
@@ -221,6 +223,26 @@ export class WorkspaceSvg extends Workspace implements IASTNodeLocationSvg {
|
||||
*/
|
||||
currentGesture_: Gesture|null = null;
|
||||
|
||||
/** This workspace's surface for dragging blocks, if it exists. */
|
||||
private readonly blockDragSurface: BlockDragSurfaceSvg|null = null;
|
||||
|
||||
/** This workspace's drag surface, if it exists. */
|
||||
private readonly workspaceDragSurface: WorkspaceDragSurfaceSvg|null = null;
|
||||
|
||||
/**
|
||||
* Whether to move workspace to the drag surface when it is dragged.
|
||||
* True if it should move, false if it should be translated directly.
|
||||
*/
|
||||
private readonly useWorkspaceDragSurface;
|
||||
|
||||
/**
|
||||
* Whether the drag surface is actively in use. When true, calls to
|
||||
* translate will translate the drag surface instead of the translating the
|
||||
* workspace directly.
|
||||
* This is set to true in setupDragSurface and to false in resetDragSurface.
|
||||
*/
|
||||
private isDragSurfaceActive = false;
|
||||
|
||||
/**
|
||||
* The first parent div with 'injectionDiv' in the name, or null if not set.
|
||||
* Access this with getInjectionDiv.
|
||||
@@ -314,8 +336,12 @@ export class WorkspaceSvg extends Workspace implements IASTNodeLocationSvg {
|
||||
|
||||
/**
|
||||
* @param options Dictionary of options.
|
||||
* @param opt_blockDragSurface Drag surface for blocks.
|
||||
* @param opt_wsDragSurface Drag surface for the workspace.
|
||||
*/
|
||||
constructor(options: Options) {
|
||||
constructor(
|
||||
options: Options, opt_blockDragSurface?: BlockDragSurfaceSvg,
|
||||
opt_wsDragSurface?: WorkspaceDragSurfaceSvg) {
|
||||
super(options);
|
||||
|
||||
const MetricsManagerClass = registry.getClassFromOptions(
|
||||
@@ -335,6 +361,16 @@ export class WorkspaceSvg extends Workspace implements IASTNodeLocationSvg {
|
||||
|
||||
this.connectionDBList = ConnectionDB.init(this.connectionChecker);
|
||||
|
||||
if (opt_blockDragSurface) {
|
||||
this.blockDragSurface = opt_blockDragSurface;
|
||||
}
|
||||
|
||||
if (opt_wsDragSurface) {
|
||||
this.workspaceDragSurface = opt_wsDragSurface;
|
||||
}
|
||||
|
||||
this.useWorkspaceDragSurface = !!this.workspaceDragSurface;
|
||||
|
||||
/**
|
||||
* Object in charge of loading, storing, and playing audio for a workspace.
|
||||
*/
|
||||
@@ -1114,10 +1150,18 @@ export class WorkspaceSvg extends Workspace implements IASTNodeLocationSvg {
|
||||
* the Blockly div.
|
||||
*/
|
||||
translate(x: number, y: number) {
|
||||
const translation = 'translate(' + x + ',' + y + ') ' +
|
||||
'scale(' + this.scale + ')';
|
||||
this.svgBlockCanvas_.setAttribute('transform', translation);
|
||||
this.svgBubbleCanvas_.setAttribute('transform', translation);
|
||||
if (this.useWorkspaceDragSurface && this.isDragSurfaceActive) {
|
||||
this.workspaceDragSurface?.translateSurface(x, y);
|
||||
} else {
|
||||
const translation = 'translate(' + x + ',' + y + ') ' +
|
||||
'scale(' + this.scale + ')';
|
||||
this.svgBlockCanvas_.setAttribute('transform', translation);
|
||||
this.svgBubbleCanvas_.setAttribute('transform', translation);
|
||||
}
|
||||
// Now update the block drag surface if we're using one.
|
||||
if (this.blockDragSurface) {
|
||||
this.blockDragSurface.translateAndScaleGroup(x, y, this.scale);
|
||||
}
|
||||
// And update the grid if we're using one.
|
||||
if (this.grid) {
|
||||
this.grid.moveTo(x, y);
|
||||
@@ -1126,6 +1170,75 @@ export class WorkspaceSvg extends Workspace implements IASTNodeLocationSvg {
|
||||
this.maybeFireViewportChangeEvent();
|
||||
}
|
||||
|
||||
/**
|
||||
* Called at the end of a workspace drag to take the contents
|
||||
* out of the drag surface and put them back into the workspace SVG.
|
||||
* Does nothing if the workspace drag surface is not enabled.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
resetDragSurface() {
|
||||
// Don't do anything if we aren't using a drag surface.
|
||||
if (!this.useWorkspaceDragSurface) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.isDragSurfaceActive = false;
|
||||
|
||||
const trans = this.workspaceDragSurface!.getSurfaceTranslation();
|
||||
this.workspaceDragSurface!.clearAndHide(this.svgGroup_);
|
||||
const translation = 'translate(' + trans.x + ',' + trans.y + ') ' +
|
||||
'scale(' + this.scale + ')';
|
||||
this.svgBlockCanvas_.setAttribute('transform', translation);
|
||||
this.svgBubbleCanvas_.setAttribute('transform', translation);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called at the beginning of a workspace drag to move contents of
|
||||
* the workspace to the drag surface.
|
||||
* Does nothing if the drag surface is not enabled.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
setupDragSurface() {
|
||||
// Don't do anything if we aren't using a drag surface.
|
||||
if (!this.useWorkspaceDragSurface) {
|
||||
return;
|
||||
}
|
||||
|
||||
// This can happen if the user starts a drag, mouses up outside of the
|
||||
// document where the mouseup listener is registered (e.g. outside of an
|
||||
// iframe) and then moves the mouse back in the workspace. On mobile and
|
||||
// ff, we get the mouseup outside the frame. On chrome and safari desktop we
|
||||
// do not.
|
||||
if (this.isDragSurfaceActive) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.isDragSurfaceActive = true;
|
||||
|
||||
// Figure out where we want to put the canvas back. The order
|
||||
// in the is important because things are layered.
|
||||
const previousElement = this.svgBlockCanvas_.previousSibling as Element;
|
||||
const width = parseInt(this.getParentSvg().getAttribute('width') ?? '0');
|
||||
const height = parseInt(this.getParentSvg().getAttribute('height') ?? '0');
|
||||
const coord = svgMath.getRelativeXY(this.getCanvas());
|
||||
this.workspaceDragSurface!.setContentsAndShow(
|
||||
this.getCanvas(), this.getBubbleCanvas(), previousElement, width,
|
||||
height, this.scale);
|
||||
this.workspaceDragSurface!.translateSurface(coord.x, coord.y);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the drag surface blocks are moved to when a drag is started.
|
||||
*
|
||||
* @returns This workspace's block drag surface, if one is in use.
|
||||
* @internal
|
||||
*/
|
||||
getBlockDragSurface(): BlockDragSurfaceSvg|null {
|
||||
return this.blockDragSurface;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the horizontal offset of the workspace.
|
||||
* Intended for LTR/RTL compatibility in XML.
|
||||
|
||||
Reference in New Issue
Block a user