mirror of
https://github.com/google/blockly.git
synced 2025-12-16 14:20:10 +01:00
fix!: remove MarkerSvg and uses (#8991)
* fix: delete MarkerSvg (marker drawer) * fix: delete marker and cursor SVG elements * chore: format * chore: lint
This commit is contained in:
@@ -50,7 +50,6 @@ import type {IFocusableTree} from './interfaces/i_focusable_tree.js';
|
||||
import {IIcon} from './interfaces/i_icon.js';
|
||||
import type {INavigable} from './interfaces/i_navigable.js';
|
||||
import * as internalConstants from './internal_constants.js';
|
||||
import {MarkerManager} from './marker_manager.js';
|
||||
import {Msg} from './msg.js';
|
||||
import * as renderManagement from './render_management.js';
|
||||
import {RenderedConnection} from './rendered_connection.js';
|
||||
@@ -1679,7 +1678,6 @@ export class BlockSvg
|
||||
this.tightenChildrenEfficiently();
|
||||
|
||||
dom.stopTextWidthCache();
|
||||
this.updateMarkers_();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1699,44 +1697,6 @@ export class BlockSvg
|
||||
if (this.nextConnection) this.nextConnection.tightenEfficiently();
|
||||
}
|
||||
|
||||
/** Redraw any attached marker or cursor svgs if needed. */
|
||||
protected updateMarkers_() {
|
||||
if (this.workspace.keyboardAccessibilityMode && this.pathObject.cursorSvg) {
|
||||
this.workspace.getCursor()!.draw();
|
||||
}
|
||||
if (this.workspace.keyboardAccessibilityMode && this.pathObject.markerSvg) {
|
||||
// TODO(#4592): Update all markers on the block.
|
||||
this.workspace.getMarker(MarkerManager.LOCAL_MARKER)!.draw();
|
||||
}
|
||||
for (const input of this.inputList) {
|
||||
for (const field of input.fieldRow) {
|
||||
field.updateMarkers_();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the cursor SVG to this block's SVG group.
|
||||
*
|
||||
* @param cursorSvg The SVG root of the cursor to be added to the block SVG
|
||||
* group.
|
||||
* @internal
|
||||
*/
|
||||
setCursorSvg(cursorSvg: SVGElement) {
|
||||
this.pathObject.setCursorSvg(cursorSvg);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the marker SVG to this block's SVG group.
|
||||
*
|
||||
* @param markerSvg The SVG root of the marker to be added to the block SVG
|
||||
* group.
|
||||
* @internal
|
||||
*/
|
||||
setMarkerSvg(markerSvg: SVGElement) {
|
||||
this.pathObject.setMarkerSvg(markerSvg);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a bounding box describing the dimensions of this block
|
||||
* and any blocks stacked below it.
|
||||
|
||||
@@ -31,7 +31,6 @@ import type {IKeyboardAccessible} from './interfaces/i_keyboard_accessible.js';
|
||||
import type {INavigable} from './interfaces/i_navigable.js';
|
||||
import type {IRegistrable} from './interfaces/i_registrable.js';
|
||||
import {ISerializable} from './interfaces/i_serializable.js';
|
||||
import {MarkerManager} from './marker_manager.js';
|
||||
import type {ConstantProvider} from './renderers/common/constants.js';
|
||||
import type {KeyboardShortcut} from './shortcut_registry.js';
|
||||
import * as Tooltip from './tooltip.js';
|
||||
@@ -113,18 +112,6 @@ export abstract class Field<T = any>
|
||||
private tooltip: Tooltip.TipInfo | null = null;
|
||||
protected size_: Size;
|
||||
|
||||
/**
|
||||
* Holds the cursors svg element when the cursor is attached to the field.
|
||||
* This is null if there is no cursor on the field.
|
||||
*/
|
||||
private cursorSvg: SVGElement | null = null;
|
||||
|
||||
/**
|
||||
* Holds the markers svg element when the marker is attached to the field.
|
||||
* This is null if there is no marker on the field.
|
||||
*/
|
||||
private markerSvg: SVGElement | null = null;
|
||||
|
||||
/** The rendered field's SVG group element. */
|
||||
protected fieldGroup_: SVGGElement | null = null;
|
||||
|
||||
@@ -1358,64 +1345,6 @@ export abstract class Field<T = any>
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the cursor SVG to this fields SVG group.
|
||||
*
|
||||
* @param cursorSvg The SVG root of the cursor to be added to the field group.
|
||||
* @internal
|
||||
*/
|
||||
setCursorSvg(cursorSvg: SVGElement) {
|
||||
if (!cursorSvg) {
|
||||
this.cursorSvg = null;
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.fieldGroup_) {
|
||||
throw new Error(`The field group is ${this.fieldGroup_}.`);
|
||||
}
|
||||
this.fieldGroup_.appendChild(cursorSvg);
|
||||
this.cursorSvg = cursorSvg;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the marker SVG to this fields SVG group.
|
||||
*
|
||||
* @param markerSvg The SVG root of the marker to be added to the field group.
|
||||
* @internal
|
||||
*/
|
||||
setMarkerSvg(markerSvg: SVGElement) {
|
||||
if (!markerSvg) {
|
||||
this.markerSvg = null;
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.fieldGroup_) {
|
||||
throw new Error(`The field group is ${this.fieldGroup_}.`);
|
||||
}
|
||||
this.fieldGroup_.appendChild(markerSvg);
|
||||
this.markerSvg = markerSvg;
|
||||
}
|
||||
|
||||
/**
|
||||
* Redraw any attached marker or cursor svgs if needed.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
updateMarkers_() {
|
||||
const block = this.getSourceBlock();
|
||||
if (!block) {
|
||||
throw new UnattachedFieldError();
|
||||
}
|
||||
const workspace = block.workspace as WorkspaceSvg;
|
||||
if (workspace.keyboardAccessibilityMode && this.cursorSvg) {
|
||||
workspace.getCursor()!.draw();
|
||||
}
|
||||
if (workspace.keyboardAccessibilityMode && this.markerSvg) {
|
||||
// TODO(#4592): Update all markers on the field.
|
||||
workspace.getMarker(MarkerManager.LOCAL_MARKER)!.draw();
|
||||
}
|
||||
}
|
||||
|
||||
/** See IFocusableNode.getFocusableElement. */
|
||||
getFocusableElement(): HTMLElement | SVGElement {
|
||||
if (!this.fieldGroup_) {
|
||||
|
||||
@@ -348,15 +348,6 @@ export class FlyoutButton
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Required by IASTNodeLocationSvg, but not used. A marker cannot be set on a
|
||||
* button. If the 'mark' shortcut is used on a button, its associated callback
|
||||
* function is triggered.
|
||||
*/
|
||||
setMarkerSvg() {
|
||||
throw new Error('Attempted to set a marker on a button.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Do something when the button is clicked.
|
||||
*
|
||||
|
||||
@@ -11,18 +11,4 @@ import type {IASTNodeLocation} from './i_ast_node_location.js';
|
||||
/**
|
||||
* An AST node location SVG interface.
|
||||
*/
|
||||
export interface IASTNodeLocationSvg extends IASTNodeLocation {
|
||||
/**
|
||||
* Add the marker SVG to this node's SVG group.
|
||||
*
|
||||
* @param markerSvg The SVG root of the marker to be added to the SVG group.
|
||||
*/
|
||||
setMarkerSvg(markerSvg: SVGElement | null): void;
|
||||
|
||||
/**
|
||||
* Add the cursor SVG to this node's SVG group.
|
||||
*
|
||||
* @param cursorSvg The SVG root of the cursor to be added to the SVG group.
|
||||
*/
|
||||
setCursorSvg(cursorSvg: SVGElement | null): void;
|
||||
}
|
||||
export interface IASTNodeLocationSvg extends IASTNodeLocation {}
|
||||
|
||||
@@ -30,12 +30,8 @@ import {isFocusableNode} from '../interfaces/i_focusable_node.js';
|
||||
import type {INavigable} from '../interfaces/i_navigable.js';
|
||||
import * as registry from '../registry.js';
|
||||
import {RenderedConnection} from '../rendered_connection.js';
|
||||
import type {MarkerSvg} from '../renderers/common/marker_svg.js';
|
||||
import type {PathObject} from '../renderers/zelos/path_object.js';
|
||||
import {Renderer} from '../renderers/zelos/renderer.js';
|
||||
import * as dom from '../utils/dom.js';
|
||||
import {WorkspaceSvg} from '../workspace_svg.js';
|
||||
import {ASTNode} from './ast_node.js';
|
||||
import {BlockNavigationPolicy} from './block_navigation_policy.js';
|
||||
import {ConnectionNavigationPolicy} from './connection_navigation_policy.js';
|
||||
import {FieldNavigationPolicy} from './field_navigation_policy.js';
|
||||
@@ -574,40 +570,6 @@ export class LineCursor extends Marker {
|
||||
return super.getCurNode();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the object in charge of drawing the marker.
|
||||
*
|
||||
* We want to customize drawing, so rather than directly setting the given
|
||||
* object, we instead set a wrapper proxy object that passes through all
|
||||
* method calls and property accesses except for draw(), which it delegates
|
||||
* to the drawMarker() method in this class.
|
||||
*
|
||||
* @param drawer The object ~in charge of drawing the marker.
|
||||
*/
|
||||
override setDrawer(drawer: MarkerSvg) {
|
||||
const altDraw = function (
|
||||
this: LineCursor,
|
||||
oldNode: ASTNode | null,
|
||||
curNode: ASTNode | null,
|
||||
) {
|
||||
// Pass the unproxied, raw drawer object so that drawMarker can call its
|
||||
// `draw()` method without triggering infinite recursion.
|
||||
this.drawMarker(oldNode, curNode, drawer);
|
||||
}.bind(this);
|
||||
|
||||
super.setDrawer(
|
||||
new Proxy(drawer, {
|
||||
get(target: typeof drawer, prop: keyof typeof drawer) {
|
||||
if (prop === 'draw') {
|
||||
return altDraw;
|
||||
}
|
||||
|
||||
return target[prop];
|
||||
},
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the location of the cursor and draw it.
|
||||
*
|
||||
@@ -631,124 +593,6 @@ export class LineCursor extends Marker {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Draw this cursor's marker.
|
||||
*
|
||||
* This is a wrapper around this.drawer.draw (usually implemented by
|
||||
* MarkerSvg.prototype.draw) that will, if newNode is a BLOCK node,
|
||||
* instead call `setSelected` to select it (if it's a regular block)
|
||||
* or `addSelect` (if it's a shadow block, since shadow blocks can't
|
||||
* be selected) instead of using the normal drawer logic.
|
||||
*
|
||||
* TODO(#142): The selection and fake-selection code was originally
|
||||
* a hack added for testing on October 28 2024, because the default
|
||||
* drawer (MarkerSvg) behaviour in Zelos was to draw a box around
|
||||
* the block and all attached child blocks, which was confusing when
|
||||
* navigating stacks.
|
||||
*
|
||||
* Since then we have decided that we probably _do_ in most cases
|
||||
* want navigating to a block to select the block, but more
|
||||
* particularly that we want navigation to move _focus_. Replace
|
||||
* this selection hack with non-hacky changing of focus once that's
|
||||
* possible.
|
||||
*
|
||||
* @param oldNode The previous node.
|
||||
* @param curNode The current node.
|
||||
* @param realDrawer The object ~in charge of drawing the marker.
|
||||
*/
|
||||
private drawMarker(
|
||||
oldNode: ASTNode | null,
|
||||
curNode: ASTNode | null,
|
||||
realDrawer: MarkerSvg,
|
||||
) {
|
||||
// If old node was a block, unselect it or remove fake selection.
|
||||
if (oldNode?.getType() === ASTNode.types.BLOCK) {
|
||||
const block = oldNode.getLocation() as BlockSvg;
|
||||
if (!block.isShadow()) {
|
||||
// Selection should already be in sync.
|
||||
} else {
|
||||
block.removeSelect();
|
||||
}
|
||||
}
|
||||
|
||||
if (this.isZelos && oldNode && this.isValueInputConnection(oldNode)) {
|
||||
this.hideAtInput(oldNode);
|
||||
}
|
||||
|
||||
const curNodeType = curNode?.getType();
|
||||
const isZelosInputConnection =
|
||||
this.isZelos && curNode && this.isValueInputConnection(curNode);
|
||||
|
||||
// If drawing can't be handled locally, just use the drawer.
|
||||
if (curNodeType !== ASTNode.types.BLOCK && !isZelosInputConnection) {
|
||||
realDrawer.draw(oldNode, curNode);
|
||||
return;
|
||||
}
|
||||
|
||||
// Hide any visible marker SVG and instead do some manual rendering.
|
||||
realDrawer.hide();
|
||||
|
||||
if (isZelosInputConnection) {
|
||||
this.showAtInput(curNode);
|
||||
} else if (curNode && curNodeType === ASTNode.types.BLOCK) {
|
||||
const block = curNode.getLocation() as BlockSvg;
|
||||
if (!block.isShadow()) {
|
||||
// Selection should already be in sync.
|
||||
} else {
|
||||
block.addSelect();
|
||||
block.getParent()?.removeSelect();
|
||||
}
|
||||
}
|
||||
|
||||
// Call MarkerSvg.prototype.fireMarkerEvent like
|
||||
// MarkerSvg.prototype.draw would (even though it's private).
|
||||
(realDrawer as any)?.fireMarkerEvent?.(oldNode, curNode);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether the node represents a value input connection.
|
||||
*
|
||||
* @param node The node to check
|
||||
* @returns True if the node represents a value input connection.
|
||||
*/
|
||||
private isValueInputConnection(node: ASTNode) {
|
||||
if (node?.getType() !== ASTNode.types.INPUT) return false;
|
||||
const connection = node.getLocation() as RenderedConnection;
|
||||
return connection.type === ConnectionType.INPUT_VALUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Hide the cursor rendering at the given input node.
|
||||
*
|
||||
* @param node The input node to hide.
|
||||
*/
|
||||
private hideAtInput(node: ASTNode) {
|
||||
const inputConnection = node.getLocation() as RenderedConnection;
|
||||
const sourceBlock = inputConnection.getSourceBlock() as BlockSvg;
|
||||
const input = inputConnection.getParentInput();
|
||||
if (input) {
|
||||
const pathObject = sourceBlock.pathObject as PathObject;
|
||||
const outlinePath = pathObject.getOutlinePath(input.name);
|
||||
dom.removeClass(outlinePath, 'inputActiveFocus');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the cursor rendering at the given input node.
|
||||
*
|
||||
* @param node The input node to show.
|
||||
*/
|
||||
private showAtInput(node: ASTNode) {
|
||||
const inputConnection = node.getLocation() as RenderedConnection;
|
||||
const sourceBlock = inputConnection.getSourceBlock() as BlockSvg;
|
||||
const input = inputConnection.getParentInput();
|
||||
if (input) {
|
||||
const pathObject = sourceBlock.pathObject as PathObject;
|
||||
const outlinePath = pathObject.getOutlinePath(input.name);
|
||||
dom.addClass(outlinePath, 'inputActiveFocus');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the current node to match the selection.
|
||||
*
|
||||
|
||||
@@ -17,7 +17,6 @@ import {Field} from '../field.js';
|
||||
import {FlyoutButton} from '../flyout_button.js';
|
||||
import type {INavigable} from '../interfaces/i_navigable.js';
|
||||
import {RenderedConnection} from '../rendered_connection.js';
|
||||
import type {MarkerSvg} from '../renderers/common/marker_svg.js';
|
||||
import {Coordinate} from '../utils/coordinate.js';
|
||||
import {WorkspaceSvg} from '../workspace_svg.js';
|
||||
import {ASTNode} from './ast_node.js';
|
||||
@@ -33,33 +32,9 @@ export class Marker {
|
||||
/** The current location of the marker. */
|
||||
protected curNode: INavigable<any> | null = null;
|
||||
|
||||
/**
|
||||
* The object in charge of drawing the visual representation of the current
|
||||
* node.
|
||||
*/
|
||||
private drawer: MarkerSvg | null = null;
|
||||
|
||||
/** The type of the marker. */
|
||||
type = 'marker';
|
||||
|
||||
/**
|
||||
* Sets the object in charge of drawing the marker.
|
||||
*
|
||||
* @param drawer The object in charge of drawing the marker.
|
||||
*/
|
||||
setDrawer(drawer: MarkerSvg) {
|
||||
this.drawer = drawer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current drawer for the marker.
|
||||
*
|
||||
* @returns The object in charge of drawing the marker.
|
||||
*/
|
||||
getDrawer(): MarkerSvg | null {
|
||||
return this.drawer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the current location of the marker.
|
||||
*
|
||||
@@ -75,30 +50,11 @@ export class Marker {
|
||||
* @param newNode The new location of the marker, or null to remove it.
|
||||
*/
|
||||
setCurNode(newNode: INavigable<any> | null) {
|
||||
const oldNode = this.curNode;
|
||||
this.curNode = newNode;
|
||||
this.drawer?.draw(this.toASTNode(oldNode), this.toASTNode(this.curNode));
|
||||
}
|
||||
|
||||
/**
|
||||
* Redraw the current marker.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
draw() {
|
||||
const node = this.toASTNode(this.curNode);
|
||||
this.drawer?.draw(node, node);
|
||||
}
|
||||
|
||||
/** Hide the marker SVG. */
|
||||
hide() {
|
||||
this.drawer?.hide();
|
||||
}
|
||||
|
||||
/** Dispose of this marker. */
|
||||
dispose() {
|
||||
this.drawer?.dispose();
|
||||
this.drawer = null;
|
||||
this.curNode = null;
|
||||
}
|
||||
|
||||
|
||||
@@ -25,15 +25,9 @@ export class MarkerManager {
|
||||
/** The cursor. */
|
||||
private cursor: LineCursor | null = null;
|
||||
|
||||
/** The cursor's SVG element. */
|
||||
private cursorSvg: SVGElement | null = null;
|
||||
|
||||
/** The map of markers for the workspace. */
|
||||
private markers = new Map<string, Marker>();
|
||||
|
||||
/** The marker's SVG element. */
|
||||
private markerSvg: SVGElement | null = null;
|
||||
|
||||
/**
|
||||
* @param workspace The workspace for the marker manager.
|
||||
* @internal
|
||||
@@ -50,11 +44,6 @@ export class MarkerManager {
|
||||
if (this.markers.has(id)) {
|
||||
this.unregisterMarker(id);
|
||||
}
|
||||
const drawer = this.workspace
|
||||
.getRenderer()
|
||||
.makeMarkerDrawer(this.workspace, marker);
|
||||
marker.setDrawer(drawer);
|
||||
this.setMarkerSvg(drawer.createDom());
|
||||
this.markers.set(id, marker);
|
||||
}
|
||||
|
||||
@@ -105,67 +94,7 @@ export class MarkerManager {
|
||||
* @param cursor The cursor used to move around this workspace.
|
||||
*/
|
||||
setCursor(cursor: LineCursor) {
|
||||
this.cursor?.getDrawer()?.dispose();
|
||||
this.cursor = cursor;
|
||||
if (this.cursor) {
|
||||
const drawer = this.workspace
|
||||
.getRenderer()
|
||||
.makeMarkerDrawer(this.workspace, this.cursor);
|
||||
this.cursor.setDrawer(drawer);
|
||||
this.setCursorSvg(drawer.createDom());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the cursor SVG to this workspace SVG group.
|
||||
*
|
||||
* @param cursorSvg The SVG root of the cursor to be added to the workspace
|
||||
* SVG group.
|
||||
* @internal
|
||||
*/
|
||||
setCursorSvg(cursorSvg: SVGElement | null) {
|
||||
if (!cursorSvg) {
|
||||
this.cursorSvg = null;
|
||||
return;
|
||||
}
|
||||
|
||||
this.workspace.getBlockCanvas()!.appendChild(cursorSvg);
|
||||
this.cursorSvg = cursorSvg;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the marker SVG to this workspaces SVG group.
|
||||
*
|
||||
* @param markerSvg The SVG root of the marker to be added to the workspace
|
||||
* SVG group.
|
||||
* @internal
|
||||
*/
|
||||
setMarkerSvg(markerSvg: SVGElement | null) {
|
||||
if (!markerSvg) {
|
||||
this.markerSvg = null;
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.workspace.getBlockCanvas()) {
|
||||
if (this.cursorSvg) {
|
||||
this.workspace
|
||||
.getBlockCanvas()!
|
||||
.insertBefore(markerSvg, this.cursorSvg);
|
||||
} else {
|
||||
this.workspace.getBlockCanvas()!.appendChild(markerSvg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Redraw the attached cursor SVG if needed.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
updateMarkers() {
|
||||
if (this.workspace.keyboardAccessibilityMode && this.cursorSvg) {
|
||||
this.workspace.getCursor()!.draw();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -33,7 +33,6 @@ import {Types} from '../measurables/types.js';
|
||||
import {Drawer} from './drawer.js';
|
||||
import type {IPathObject} from './i_path_object.js';
|
||||
import {RenderInfo} from './info.js';
|
||||
import {MarkerSvg} from './marker_svg.js';
|
||||
import {PathObject} from './path_object.js';
|
||||
import {Renderer} from './renderer.js';
|
||||
|
||||
@@ -94,7 +93,6 @@ export {
|
||||
InRowSpacer,
|
||||
IPathObject,
|
||||
JaggedEdge,
|
||||
MarkerSvg,
|
||||
Measurable,
|
||||
NextConnection,
|
||||
OutputConnection,
|
||||
|
||||
@@ -30,18 +30,6 @@ export interface IPathObject {
|
||||
/** The primary path of the block. */
|
||||
style: BlockStyle;
|
||||
|
||||
/**
|
||||
* Holds the cursors SVG element when the cursor is attached to the block.
|
||||
* This is null if there is no cursor on the block.
|
||||
*/
|
||||
cursorSvg: SVGElement | null;
|
||||
|
||||
/**
|
||||
* Holds the markers SVG element when the marker is attached to the block.
|
||||
* This is null if there is no marker on the block.
|
||||
*/
|
||||
markerSvg: SVGElement | null;
|
||||
|
||||
/**
|
||||
* Set the path generated by the renderer onto the respective SVG element.
|
||||
*
|
||||
@@ -54,22 +42,6 @@ export interface IPathObject {
|
||||
*/
|
||||
flipRTL(): void;
|
||||
|
||||
/**
|
||||
* Add the cursor SVG to this block's SVG group.
|
||||
*
|
||||
* @param cursorSvg The SVG root of the cursor to be added to the block SVG
|
||||
* group.
|
||||
*/
|
||||
setCursorSvg(cursorSvg: SVGElement): void;
|
||||
|
||||
/**
|
||||
* Add the marker SVG to this block's SVG group.
|
||||
*
|
||||
* @param markerSvg The SVG root of the marker to be added to the block SVG
|
||||
* group.
|
||||
*/
|
||||
setMarkerSvg(markerSvg: SVGElement): void;
|
||||
|
||||
/**
|
||||
* Set whether the block shows a highlight or not. Block highlighting is
|
||||
* often used to visually mark blocks currently being executed.
|
||||
|
||||
@@ -1,767 +0,0 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2019 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
// Former goog.module ID: Blockly.blockRendering.MarkerSvg
|
||||
|
||||
// Unused import preserved for side-effects. Remove if unneeded.
|
||||
import '../../events/events_marker_move.js';
|
||||
|
||||
import type {BlockSvg} from '../../block_svg.js';
|
||||
import type {Connection} from '../../connection.js';
|
||||
import {ConnectionType} from '../../connection_type.js';
|
||||
import {EventType} from '../../events/type.js';
|
||||
import * as eventUtils from '../../events/utils.js';
|
||||
import type {Field} from '../../field.js';
|
||||
import {FlyoutButton} from '../../flyout_button.js';
|
||||
import type {IASTNodeLocationSvg} from '../../interfaces/i_ast_node_location_svg.js';
|
||||
import {ASTNode} from '../../keyboard_nav/ast_node.js';
|
||||
import type {Marker} from '../../keyboard_nav/marker.js';
|
||||
import type {RenderedConnection} from '../../rendered_connection.js';
|
||||
import * as dom from '../../utils/dom.js';
|
||||
import {Svg} from '../../utils/svg.js';
|
||||
import * as svgPaths from '../../utils/svg_paths.js';
|
||||
import type {WorkspaceSvg} from '../../workspace_svg.js';
|
||||
import type {ConstantProvider, Notch, PuzzleTab} from './constants.js';
|
||||
|
||||
/** The name of the CSS class for a cursor. */
|
||||
const CURSOR_CLASS = 'blocklyCursor';
|
||||
|
||||
/** The name of the CSS class for a marker. */
|
||||
const MARKER_CLASS = 'blocklyMarker';
|
||||
|
||||
/**
|
||||
* What we multiply the height by to get the height of the marker.
|
||||
* Only used for the block and block connections.
|
||||
*/
|
||||
const HEIGHT_MULTIPLIER = 3 / 4;
|
||||
|
||||
/**
|
||||
* Class for a marker, containing methods for graphically rendering a marker as
|
||||
* SVG.
|
||||
*/
|
||||
export class MarkerSvg {
|
||||
/**
|
||||
* The workspace, field, or block that the marker SVG element should be
|
||||
* attached to.
|
||||
*/
|
||||
protected parent: IASTNodeLocationSvg | null = null;
|
||||
|
||||
/** The current SVG element for the marker. */
|
||||
currentMarkerSvg: SVGElement | null = null;
|
||||
colour_: string;
|
||||
|
||||
/** The root SVG group containing the marker. */
|
||||
protected markerSvg_: SVGGElement | null = null;
|
||||
protected svgGroup_: SVGGElement | null = null;
|
||||
|
||||
protected markerBlock_: SVGPathElement | null = null;
|
||||
|
||||
protected markerInput_: SVGPathElement | null = null;
|
||||
protected markerSvgLine_: SVGRectElement | null = null;
|
||||
|
||||
protected markerSvgRect_: SVGRectElement | null = null;
|
||||
|
||||
/** The constants necessary to draw the marker. */
|
||||
protected constants_: ConstantProvider;
|
||||
|
||||
/**
|
||||
* @param workspace The workspace the marker belongs to.
|
||||
* @param constants The constants for the renderer.
|
||||
* @param marker The marker to draw.
|
||||
*/
|
||||
constructor(
|
||||
protected readonly workspace: WorkspaceSvg,
|
||||
constants: ConstantProvider,
|
||||
protected readonly marker: Marker,
|
||||
) {
|
||||
this.constants_ = constants;
|
||||
|
||||
const defaultColour = this.isCursor()
|
||||
? this.constants_.CURSOR_COLOUR
|
||||
: this.constants_.MARKER_COLOUR;
|
||||
|
||||
/** The colour of the marker. */
|
||||
this.colour_ = marker.colour || defaultColour;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the root node of the SVG or null if none exists.
|
||||
*
|
||||
* @returns The root SVG node.
|
||||
*/
|
||||
getSvgRoot(): SVGElement | null {
|
||||
return this.svgGroup_;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the marker.
|
||||
*
|
||||
* @returns The marker to draw for.
|
||||
*/
|
||||
getMarker(): Marker {
|
||||
return this.marker;
|
||||
}
|
||||
|
||||
/**
|
||||
* True if the marker should be drawn as a cursor, false otherwise.
|
||||
* A cursor is drawn as a flashing line. A marker is drawn as a solid line.
|
||||
*
|
||||
* @returns True if the marker is a cursor, false otherwise.
|
||||
*/
|
||||
isCursor(): boolean {
|
||||
return this.marker.type === 'cursor';
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the DOM element for the marker.
|
||||
*
|
||||
* @returns The marker controls SVG group.
|
||||
*/
|
||||
createDom(): SVGElement {
|
||||
const className = this.isCursor() ? CURSOR_CLASS : MARKER_CLASS;
|
||||
|
||||
this.svgGroup_ = dom.createSvgElement(Svg.G, {'class': className});
|
||||
|
||||
this.createDomInternal_();
|
||||
return this.svgGroup_;
|
||||
}
|
||||
|
||||
/**
|
||||
* Attaches the SVG root of the marker to the SVG group of the parent.
|
||||
*
|
||||
* @param newParent The workspace, field, or block that the marker SVG element
|
||||
* should be attached to.
|
||||
*/
|
||||
protected setParent_(newParent: IASTNodeLocationSvg) {
|
||||
if (!this.isCursor()) {
|
||||
if (this.parent) {
|
||||
this.parent.setMarkerSvg(null);
|
||||
}
|
||||
newParent.setMarkerSvg(this.getSvgRoot());
|
||||
} else {
|
||||
if (this.parent) {
|
||||
this.parent.setCursorSvg(null);
|
||||
}
|
||||
newParent.setCursorSvg(this.getSvgRoot());
|
||||
}
|
||||
this.parent = newParent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the marker.
|
||||
*
|
||||
* @param oldNode The previous node the marker was on or null.
|
||||
* @param curNode The node that we want to draw the marker for.
|
||||
*/
|
||||
draw(oldNode: ASTNode | null, curNode: ASTNode | null) {
|
||||
if (!curNode) {
|
||||
this.hide();
|
||||
return;
|
||||
}
|
||||
|
||||
this.constants_ = this.workspace.getRenderer().getConstants();
|
||||
|
||||
const defaultColour = this.isCursor()
|
||||
? this.constants_.CURSOR_COLOUR
|
||||
: this.constants_.MARKER_COLOUR;
|
||||
this.colour_ = this.marker.colour || defaultColour;
|
||||
this.applyColour_(curNode);
|
||||
|
||||
this.showAtLocation_(curNode);
|
||||
|
||||
this.fireMarkerEvent(oldNode, curNode);
|
||||
|
||||
// Ensures the marker will be visible immediately after the move.
|
||||
const animate = this.currentMarkerSvg!.childNodes[0];
|
||||
if (
|
||||
animate !== undefined &&
|
||||
(animate as SVGAnimationElement).beginElement
|
||||
) {
|
||||
(animate as SVGAnimationElement).beginElement();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the marker's visible state based on the type of curNode..
|
||||
*
|
||||
* @param curNode The node that we want to draw the marker for.
|
||||
*/
|
||||
protected showAtLocation_(curNode: ASTNode) {
|
||||
const curNodeAsConnection = curNode.getLocation() as Connection;
|
||||
const connectionType = curNodeAsConnection.type;
|
||||
if (curNode.getType() === ASTNode.types.BLOCK) {
|
||||
this.showWithBlock_(curNode);
|
||||
} else if (curNode.getType() === ASTNode.types.OUTPUT) {
|
||||
this.showWithOutput_(curNode);
|
||||
} else if (connectionType === ConnectionType.INPUT_VALUE) {
|
||||
this.showWithInput_(curNode);
|
||||
} else if (connectionType === ConnectionType.NEXT_STATEMENT) {
|
||||
this.showWithNext_(curNode);
|
||||
} else if (curNode.getType() === ASTNode.types.PREVIOUS) {
|
||||
this.showWithPrevious_(curNode);
|
||||
} else if (curNode.getType() === ASTNode.types.FIELD) {
|
||||
this.showWithField_(curNode);
|
||||
} else if (curNode.getType() === ASTNode.types.WORKSPACE) {
|
||||
this.showWithCoordinates_(curNode);
|
||||
} else if (curNode.getType() === ASTNode.types.STACK) {
|
||||
this.showWithStack_(curNode);
|
||||
} else if (curNode.getType() === ASTNode.types.BUTTON) {
|
||||
this.showWithButton_(curNode);
|
||||
}
|
||||
}
|
||||
|
||||
/**************************
|
||||
* Display
|
||||
**************************/
|
||||
|
||||
/**
|
||||
* Show the marker as a combination of the previous connection and block,
|
||||
* the output connection and block, or just the block.
|
||||
*
|
||||
* @param curNode The node to draw the marker for.
|
||||
*/
|
||||
protected showWithBlockPrevOutput(curNode: ASTNode) {
|
||||
const block = curNode.getSourceBlock() as BlockSvg;
|
||||
const width = block.width;
|
||||
const height = block.height;
|
||||
const markerHeight = height * HEIGHT_MULTIPLIER;
|
||||
const markerOffset = this.constants_.CURSOR_BLOCK_PADDING;
|
||||
|
||||
if (block.previousConnection) {
|
||||
const connectionShape = this.constants_.shapeFor(
|
||||
block.previousConnection,
|
||||
) as Notch;
|
||||
this.positionPrevious_(
|
||||
width,
|
||||
markerOffset,
|
||||
markerHeight,
|
||||
connectionShape,
|
||||
);
|
||||
} else if (block.outputConnection) {
|
||||
const connectionShape = this.constants_.shapeFor(
|
||||
block.outputConnection,
|
||||
) as PuzzleTab;
|
||||
this.positionOutput_(width, height, connectionShape);
|
||||
} else {
|
||||
this.positionBlock_(width, markerOffset, markerHeight);
|
||||
}
|
||||
this.setParent_(block);
|
||||
this.showCurrent_();
|
||||
}
|
||||
|
||||
/**
|
||||
* Position and display the marker for a block.
|
||||
*
|
||||
* @param curNode The node to draw the marker for.
|
||||
*/
|
||||
protected showWithBlock_(curNode: ASTNode) {
|
||||
this.showWithBlockPrevOutput(curNode);
|
||||
}
|
||||
|
||||
/**
|
||||
* Position and display the marker for a previous connection.
|
||||
*
|
||||
* @param curNode The node to draw the marker for.
|
||||
*/
|
||||
protected showWithPrevious_(curNode: ASTNode) {
|
||||
this.showWithBlockPrevOutput(curNode);
|
||||
}
|
||||
|
||||
/**
|
||||
* Position and display the marker for an output connection.
|
||||
*
|
||||
* @param curNode The node to draw the marker for.
|
||||
*/
|
||||
protected showWithOutput_(curNode: ASTNode) {
|
||||
this.showWithBlockPrevOutput(curNode);
|
||||
}
|
||||
|
||||
/**
|
||||
* Position and display the marker for a workspace coordinate.
|
||||
* This is a horizontal line.
|
||||
*
|
||||
* @param curNode The node to draw the marker for.
|
||||
*/
|
||||
protected showWithCoordinates_(curNode: ASTNode) {
|
||||
const wsCoordinate = curNode.getWsCoordinate();
|
||||
let x = wsCoordinate?.x ?? 0;
|
||||
const y = wsCoordinate?.y ?? 0;
|
||||
|
||||
if (this.workspace.RTL) {
|
||||
x -= this.constants_.CURSOR_WS_WIDTH;
|
||||
}
|
||||
|
||||
this.positionLine_(x, y, this.constants_.CURSOR_WS_WIDTH);
|
||||
this.setParent_(this.workspace);
|
||||
this.showCurrent_();
|
||||
}
|
||||
|
||||
/**
|
||||
* Position and display the marker for a field.
|
||||
* This is a box around the field.
|
||||
*
|
||||
* @param curNode The node to draw the marker for.
|
||||
*/
|
||||
protected showWithField_(curNode: ASTNode) {
|
||||
const field = curNode.getLocation() as Field;
|
||||
const width = field.getSize().width;
|
||||
const height = field.getSize().height;
|
||||
|
||||
this.positionRect_(0, 0, width, height);
|
||||
this.setParent_(field);
|
||||
this.showCurrent_();
|
||||
}
|
||||
|
||||
/**
|
||||
* Position and display the marker for an input.
|
||||
* This is a puzzle piece.
|
||||
*
|
||||
* @param curNode The node to draw the marker for.
|
||||
*/
|
||||
protected showWithInput_(curNode: ASTNode) {
|
||||
const connection = curNode.getLocation() as RenderedConnection;
|
||||
const sourceBlock = connection.getSourceBlock();
|
||||
|
||||
this.positionInput_(connection);
|
||||
this.setParent_(sourceBlock);
|
||||
this.showCurrent_();
|
||||
}
|
||||
|
||||
/**
|
||||
* Position and display the marker for a next connection.
|
||||
* This is a horizontal line.
|
||||
*
|
||||
* @param curNode The node to draw the marker for.
|
||||
*/
|
||||
protected showWithNext_(curNode: ASTNode) {
|
||||
const connection = curNode.getLocation() as RenderedConnection;
|
||||
const targetBlock = connection.getSourceBlock();
|
||||
let x = 0;
|
||||
const y = connection.getOffsetInBlock().y;
|
||||
const width = targetBlock.getHeightWidth().width;
|
||||
if (this.workspace.RTL) {
|
||||
x = -width;
|
||||
}
|
||||
this.positionLine_(x, y, width);
|
||||
this.setParent_(targetBlock);
|
||||
this.showCurrent_();
|
||||
}
|
||||
|
||||
/**
|
||||
* Position and display the marker for a stack.
|
||||
* This is a box with extra padding around the entire stack of blocks.
|
||||
*
|
||||
* @param curNode The node to draw the marker for.
|
||||
*/
|
||||
protected showWithStack_(curNode: ASTNode) {
|
||||
const block = curNode.getLocation() as BlockSvg;
|
||||
|
||||
// Gets the height and width of entire stack.
|
||||
const heightWidth = block.getHeightWidth();
|
||||
|
||||
// Add padding so that being on a stack looks different than being on a
|
||||
// block.
|
||||
const width = heightWidth.width + this.constants_.CURSOR_STACK_PADDING;
|
||||
const height = heightWidth.height + this.constants_.CURSOR_STACK_PADDING;
|
||||
|
||||
// Shift the rectangle slightly to upper left so padding is equal on all
|
||||
// sides.
|
||||
const xPadding = -this.constants_.CURSOR_STACK_PADDING / 2;
|
||||
const yPadding = -this.constants_.CURSOR_STACK_PADDING / 2;
|
||||
|
||||
let x = xPadding;
|
||||
const y = yPadding;
|
||||
|
||||
if (this.workspace.RTL) {
|
||||
x = -(width + xPadding);
|
||||
}
|
||||
this.positionRect_(x, y, width, height);
|
||||
this.setParent_(block);
|
||||
this.showCurrent_();
|
||||
}
|
||||
|
||||
/**
|
||||
* Position and display the marker for a flyout button.
|
||||
* This is a box with extra padding around the button.
|
||||
*
|
||||
* @param curNode The node to draw the marker for.
|
||||
*/
|
||||
protected showWithButton_(curNode: ASTNode) {
|
||||
const button = curNode.getLocation() as FlyoutButton;
|
||||
|
||||
// Gets the height and width of entire stack.
|
||||
const heightWidth = {height: button.height, width: button.width};
|
||||
|
||||
// Add padding so that being on a button looks similar to being on a stack.
|
||||
const width = heightWidth.width + this.constants_.CURSOR_STACK_PADDING;
|
||||
const height = heightWidth.height + this.constants_.CURSOR_STACK_PADDING;
|
||||
|
||||
// Shift the rectangle slightly to upper left so padding is equal on all
|
||||
// sides.
|
||||
const xPadding = -this.constants_.CURSOR_STACK_PADDING / 2;
|
||||
const yPadding = -this.constants_.CURSOR_STACK_PADDING / 2;
|
||||
|
||||
let x = xPadding;
|
||||
const y = yPadding;
|
||||
|
||||
if (this.workspace.RTL) {
|
||||
x = -(width + xPadding);
|
||||
}
|
||||
this.positionRect_(x, y, width, height);
|
||||
this.setParent_(button);
|
||||
this.showCurrent_();
|
||||
}
|
||||
|
||||
/** Show the current marker. */
|
||||
protected showCurrent_() {
|
||||
this.hide();
|
||||
if (this.currentMarkerSvg) {
|
||||
this.currentMarkerSvg.style.display = '';
|
||||
}
|
||||
}
|
||||
|
||||
/**************************
|
||||
* Position
|
||||
**************************/
|
||||
|
||||
/**
|
||||
* Position the marker for a block.
|
||||
* Displays an outline of the top half of a rectangle around a block.
|
||||
*
|
||||
* @param width The width of the block.
|
||||
* @param markerOffset The extra padding for around the block.
|
||||
* @param markerHeight The height of the marker.
|
||||
*/
|
||||
protected positionBlock_(
|
||||
width: number,
|
||||
markerOffset: number,
|
||||
markerHeight: number,
|
||||
) {
|
||||
const markerPath =
|
||||
svgPaths.moveBy(-markerOffset, markerHeight) +
|
||||
svgPaths.lineOnAxis('V', -markerOffset) +
|
||||
svgPaths.lineOnAxis('H', width + markerOffset * 2) +
|
||||
svgPaths.lineOnAxis('V', markerHeight);
|
||||
if (!this.markerBlock_) {
|
||||
throw new Error(
|
||||
'createDom should be called before positioning the marker',
|
||||
);
|
||||
}
|
||||
this.markerBlock_.setAttribute('d', markerPath);
|
||||
if (this.workspace.RTL) {
|
||||
this.flipRtl(this.markerBlock_);
|
||||
}
|
||||
this.currentMarkerSvg = this.markerBlock_;
|
||||
}
|
||||
|
||||
/**
|
||||
* Position the marker for an input connection.
|
||||
* Displays a filled in puzzle piece.
|
||||
*
|
||||
* @param connection The connection to position marker around.
|
||||
*/
|
||||
protected positionInput_(connection: RenderedConnection) {
|
||||
const x = connection.getOffsetInBlock().x;
|
||||
const y = connection.getOffsetInBlock().y;
|
||||
|
||||
const path =
|
||||
svgPaths.moveTo(0, 0) +
|
||||
(this.constants_.shapeFor(connection) as PuzzleTab).pathDown;
|
||||
|
||||
this.markerInput_!.setAttribute('d', path);
|
||||
this.markerInput_!.setAttribute(
|
||||
'transform',
|
||||
'translate(' +
|
||||
x +
|
||||
',' +
|
||||
y +
|
||||
')' +
|
||||
(this.workspace.RTL ? ' scale(-1 1)' : ''),
|
||||
);
|
||||
this.currentMarkerSvg = this.markerInput_;
|
||||
}
|
||||
|
||||
/**
|
||||
* Move and show the marker at the specified coordinate in workspace units.
|
||||
* Displays a horizontal line.
|
||||
*
|
||||
* @param x The new x, in workspace units.
|
||||
* @param y The new y, in workspace units.
|
||||
* @param width The new width, in workspace units.
|
||||
*/
|
||||
protected positionLine_(x: number, y: number, width: number) {
|
||||
if (!this.markerSvgLine_) {
|
||||
throw new Error('createDom should be called before positioning the line');
|
||||
}
|
||||
this.markerSvgLine_.setAttribute('x', `${x}`);
|
||||
this.markerSvgLine_.setAttribute('y', `${y}`);
|
||||
this.markerSvgLine_.setAttribute('width', `${width}`);
|
||||
this.currentMarkerSvg = this.markerSvgLine_;
|
||||
}
|
||||
|
||||
/**
|
||||
* Position the marker for an output connection.
|
||||
* Displays a puzzle outline and the top and bottom path.
|
||||
*
|
||||
* @param width The width of the block.
|
||||
* @param height The height of the block.
|
||||
* @param connectionShape The shape object for the connection.
|
||||
*/
|
||||
protected positionOutput_(
|
||||
width: number,
|
||||
height: number,
|
||||
connectionShape: PuzzleTab,
|
||||
) {
|
||||
if (!this.markerBlock_) {
|
||||
throw new Error(
|
||||
'createDom should be called before positioning the output',
|
||||
);
|
||||
}
|
||||
const markerPath =
|
||||
svgPaths.moveBy(width, 0) +
|
||||
svgPaths.lineOnAxis('h', -(width - connectionShape.width)) +
|
||||
svgPaths.lineOnAxis('v', this.constants_.TAB_OFFSET_FROM_TOP) +
|
||||
connectionShape.pathDown +
|
||||
svgPaths.lineOnAxis('V', height) +
|
||||
svgPaths.lineOnAxis('H', width);
|
||||
this.markerBlock_.setAttribute('d', markerPath);
|
||||
if (this.workspace.RTL) {
|
||||
this.flipRtl(this.markerBlock_);
|
||||
}
|
||||
this.currentMarkerSvg = this.markerBlock_;
|
||||
}
|
||||
|
||||
/**
|
||||
* Position the marker for a previous connection.
|
||||
* Displays a half rectangle with a notch in the top to represent the previous
|
||||
* connection.
|
||||
*
|
||||
* @param width The width of the block.
|
||||
* @param markerOffset The offset of the marker from around the block.
|
||||
* @param markerHeight The height of the marker.
|
||||
* @param connectionShape The shape object for the connection.
|
||||
*/
|
||||
protected positionPrevious_(
|
||||
width: number,
|
||||
markerOffset: number,
|
||||
markerHeight: number,
|
||||
connectionShape: Notch,
|
||||
) {
|
||||
if (!this.markerBlock_) {
|
||||
throw new Error(
|
||||
'createDom should be called before positioning the previous connection marker',
|
||||
);
|
||||
}
|
||||
const markerPath =
|
||||
svgPaths.moveBy(-markerOffset, markerHeight) +
|
||||
svgPaths.lineOnAxis('V', -markerOffset) +
|
||||
svgPaths.lineOnAxis('H', this.constants_.NOTCH_OFFSET_LEFT) +
|
||||
connectionShape.pathLeft +
|
||||
svgPaths.lineOnAxis('H', width + markerOffset * 2) +
|
||||
svgPaths.lineOnAxis('V', markerHeight);
|
||||
this.markerBlock_.setAttribute('d', markerPath);
|
||||
if (this.workspace.RTL) {
|
||||
this.flipRtl(this.markerBlock_);
|
||||
}
|
||||
this.currentMarkerSvg = this.markerBlock_;
|
||||
}
|
||||
|
||||
/**
|
||||
* Move and show the marker at the specified coordinate in workspace units.
|
||||
* Displays a filled in rectangle.
|
||||
*
|
||||
* @param x The new x, in workspace units.
|
||||
* @param y The new y, in workspace units.
|
||||
* @param width The new width, in workspace units.
|
||||
* @param height The new height, in workspace units.
|
||||
*/
|
||||
protected positionRect_(x: number, y: number, width: number, height: number) {
|
||||
if (!this.markerSvgRect_) {
|
||||
throw new Error('createDom should be called before positioning the rect');
|
||||
}
|
||||
this.markerSvgRect_.setAttribute('x', `${x}`);
|
||||
this.markerSvgRect_.setAttribute('y', `${y}`);
|
||||
this.markerSvgRect_.setAttribute('width', `${width}`);
|
||||
this.markerSvgRect_.setAttribute('height', `${height}`);
|
||||
this.currentMarkerSvg = this.markerSvgRect_;
|
||||
}
|
||||
|
||||
/**
|
||||
* Flip the SVG paths in RTL.
|
||||
*
|
||||
* @param markerSvg The marker that we want to flip.
|
||||
*/
|
||||
private flipRtl(markerSvg: SVGElement) {
|
||||
markerSvg.setAttribute('transform', 'scale(-1 1)');
|
||||
}
|
||||
|
||||
/** Hide the marker. */
|
||||
hide() {
|
||||
if (
|
||||
!this.markerSvgLine_ ||
|
||||
!this.markerSvgRect_ ||
|
||||
!this.markerInput_ ||
|
||||
!this.markerBlock_
|
||||
) {
|
||||
throw new Error('createDom should be called before hiding the marker');
|
||||
}
|
||||
this.markerSvgLine_.style.display = 'none';
|
||||
this.markerSvgRect_.style.display = 'none';
|
||||
this.markerInput_.style.display = 'none';
|
||||
this.markerBlock_.style.display = 'none';
|
||||
}
|
||||
|
||||
/**
|
||||
* Fire event for the marker or marker.
|
||||
*
|
||||
* @param oldNode The old node the marker used to be on.
|
||||
* @param curNode The new node the marker is currently on.
|
||||
*/
|
||||
protected fireMarkerEvent(oldNode: ASTNode | null, curNode: ASTNode) {
|
||||
const curBlock = curNode.getSourceBlock();
|
||||
const event = new (eventUtils.get(EventType.MARKER_MOVE))(
|
||||
curBlock,
|
||||
this.isCursor(),
|
||||
oldNode,
|
||||
curNode,
|
||||
);
|
||||
eventUtils.fire(event);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the properties to make a marker blink.
|
||||
*
|
||||
* @returns The object holding attributes to make the marker blink.
|
||||
*/
|
||||
protected getBlinkProperties_(): {[key: string]: string} {
|
||||
return {
|
||||
'attributeType': 'XML',
|
||||
'attributeName': 'fill',
|
||||
'dur': '1s',
|
||||
'values': this.colour_ + ';transparent;transparent;',
|
||||
'repeatCount': 'indefinite',
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the marker SVG.
|
||||
*
|
||||
* @returns The SVG node created.
|
||||
*/
|
||||
protected createDomInternal_(): Element {
|
||||
/* This markup will be generated and added to the .svgGroup_:
|
||||
<g>
|
||||
<rect width="100" height="5">
|
||||
<animate attributeType="XML" attributeName="fill" dur="1s"
|
||||
values="transparent;transparent;#fff;transparent"
|
||||
repeatCount="indefinite" />
|
||||
</rect>
|
||||
</g>
|
||||
*/
|
||||
|
||||
this.markerSvg_ = dom.createSvgElement(
|
||||
Svg.G,
|
||||
{
|
||||
'width': this.constants_.CURSOR_WS_WIDTH,
|
||||
'height': this.constants_.WS_CURSOR_HEIGHT,
|
||||
},
|
||||
this.svgGroup_,
|
||||
);
|
||||
|
||||
// A horizontal line used to represent a workspace coordinate or next
|
||||
// connection.
|
||||
this.markerSvgLine_ = dom.createSvgElement(
|
||||
Svg.RECT,
|
||||
{
|
||||
'width': this.constants_.CURSOR_WS_WIDTH,
|
||||
'height': this.constants_.WS_CURSOR_HEIGHT,
|
||||
},
|
||||
this.markerSvg_,
|
||||
);
|
||||
|
||||
// A filled in rectangle used to represent a stack.
|
||||
this.markerSvgRect_ = dom.createSvgElement(
|
||||
Svg.RECT,
|
||||
{
|
||||
'class': 'blocklyVerticalMarker',
|
||||
'rx': 10,
|
||||
'ry': 10,
|
||||
},
|
||||
this.markerSvg_,
|
||||
);
|
||||
|
||||
// A filled in puzzle piece used to represent an input value.
|
||||
this.markerInput_ = dom.createSvgElement(
|
||||
Svg.PATH,
|
||||
{'transform': ''},
|
||||
this.markerSvg_,
|
||||
);
|
||||
|
||||
// A path used to represent a previous connection and a block, an output
|
||||
// connection and a block, or a block.
|
||||
this.markerBlock_ = dom.createSvgElement(
|
||||
Svg.PATH,
|
||||
{
|
||||
'transform': '',
|
||||
'fill': 'none',
|
||||
'stroke-width': this.constants_.CURSOR_STROKE_WIDTH,
|
||||
},
|
||||
this.markerSvg_,
|
||||
);
|
||||
|
||||
this.hide();
|
||||
|
||||
// Markers and stack markers don't blink.
|
||||
if (this.isCursor()) {
|
||||
const blinkProperties = this.getBlinkProperties_();
|
||||
dom.createSvgElement(Svg.ANIMATE, blinkProperties, this.markerSvgLine_);
|
||||
dom.createSvgElement(Svg.ANIMATE, blinkProperties, this.markerInput_);
|
||||
dom.createSvgElement(
|
||||
Svg.ANIMATE,
|
||||
{...blinkProperties, attributeName: 'stroke'},
|
||||
this.markerBlock_,
|
||||
);
|
||||
}
|
||||
|
||||
return this.markerSvg_;
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply the marker's colour.
|
||||
*
|
||||
* @param _curNode The node that we want to draw the marker for.
|
||||
*/
|
||||
protected applyColour_(_curNode: ASTNode) {
|
||||
if (
|
||||
!this.markerSvgLine_ ||
|
||||
!this.markerSvgRect_ ||
|
||||
!this.markerInput_ ||
|
||||
!this.markerBlock_
|
||||
) {
|
||||
throw new Error(
|
||||
'createDom should be called before applying color to the markerj',
|
||||
);
|
||||
}
|
||||
this.markerSvgLine_.setAttribute('fill', this.colour_);
|
||||
this.markerSvgRect_.setAttribute('stroke', this.colour_);
|
||||
this.markerInput_.setAttribute('fill', this.colour_);
|
||||
this.markerBlock_.setAttribute('stroke', this.colour_);
|
||||
|
||||
if (this.isCursor()) {
|
||||
const values = this.colour_ + ';transparent;transparent;';
|
||||
this.markerSvgLine_.firstElementChild!.setAttribute('values', values);
|
||||
this.markerInput_.firstElementChild!.setAttribute('values', values);
|
||||
this.markerBlock_.firstElementChild!.setAttribute('values', values);
|
||||
}
|
||||
}
|
||||
|
||||
/** Dispose of this marker. */
|
||||
dispose() {
|
||||
if (this.svgGroup_) {
|
||||
dom.removeNode(this.svgGroup_);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -24,18 +24,6 @@ export class PathObject implements IPathObject {
|
||||
svgRoot: SVGElement;
|
||||
svgPath: SVGElement;
|
||||
|
||||
/**
|
||||
* Holds the cursors svg element when the cursor is attached to the block.
|
||||
* This is null if there is no cursor on the block.
|
||||
*/
|
||||
cursorSvg: SVGElement | null = null;
|
||||
|
||||
/**
|
||||
* Holds the markers svg element when the marker is attached to the block.
|
||||
* This is null if there is no marker on the block.
|
||||
*/
|
||||
markerSvg: SVGElement | null = null;
|
||||
|
||||
constants: ConstantProvider;
|
||||
style: BlockStyle;
|
||||
|
||||
@@ -86,42 +74,6 @@ export class PathObject implements IPathObject {
|
||||
this.svgPath.setAttribute('transform', 'scale(-1 1)');
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the cursor SVG to this block's SVG group.
|
||||
*
|
||||
* @param cursorSvg The SVG root of the cursor to be added to the block SVG
|
||||
* group.
|
||||
*/
|
||||
setCursorSvg(cursorSvg: SVGElement) {
|
||||
if (!cursorSvg) {
|
||||
this.cursorSvg = null;
|
||||
return;
|
||||
}
|
||||
|
||||
this.svgRoot.appendChild(cursorSvg);
|
||||
this.cursorSvg = cursorSvg;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the marker SVG to this block's SVG group.
|
||||
*
|
||||
* @param markerSvg The SVG root of the marker to be added to the block SVG
|
||||
* group.
|
||||
*/
|
||||
setMarkerSvg(markerSvg: SVGElement) {
|
||||
if (!markerSvg) {
|
||||
this.markerSvg = null;
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.cursorSvg) {
|
||||
this.svgRoot.insertBefore(markerSvg, this.cursorSvg);
|
||||
} else {
|
||||
this.svgRoot.appendChild(markerSvg);
|
||||
}
|
||||
this.markerSvg = markerSvg;
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply the stored colours to the block's path, taking into account whether
|
||||
* the paths belong to a shadow block.
|
||||
|
||||
@@ -11,14 +11,11 @@ import type {BlockSvg} from '../../block_svg.js';
|
||||
import {Connection} from '../../connection.js';
|
||||
import {ConnectionType} from '../../connection_type.js';
|
||||
import type {IRegistrable} from '../../interfaces/i_registrable.js';
|
||||
import type {Marker} from '../../keyboard_nav/marker.js';
|
||||
import type {BlockStyle, Theme} from '../../theme.js';
|
||||
import type {WorkspaceSvg} from '../../workspace_svg.js';
|
||||
import {ConstantProvider} from './constants.js';
|
||||
import {Drawer} from './drawer.js';
|
||||
import type {IPathObject} from './i_path_object.js';
|
||||
import {RenderInfo} from './info.js';
|
||||
import {MarkerSvg} from './marker_svg.js';
|
||||
import {PathObject} from './path_object.js';
|
||||
|
||||
/**
|
||||
@@ -167,17 +164,6 @@ export class Renderer implements IRegistrable {
|
||||
return new Drawer(block, info);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new instance of the renderer's marker drawer.
|
||||
*
|
||||
* @param workspace The workspace the marker belongs to.
|
||||
* @param marker The marker.
|
||||
* @returns The object in charge of drawing the marker.
|
||||
*/
|
||||
makeMarkerDrawer(workspace: WorkspaceSvg, marker: Marker): MarkerSvg {
|
||||
return new MarkerSvg(workspace, this.getConstants(), marker);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new instance of a renderer path object.
|
||||
*
|
||||
|
||||
@@ -1,144 +0,0 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2019 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
// Former goog.module ID: Blockly.zelos.MarkerSvg
|
||||
|
||||
import type {BlockSvg} from '../../block_svg.js';
|
||||
import type {ASTNode} from '../../keyboard_nav/ast_node.js';
|
||||
import type {Marker} from '../../keyboard_nav/marker.js';
|
||||
import type {RenderedConnection} from '../../rendered_connection.js';
|
||||
import * as dom from '../../utils/dom.js';
|
||||
import {Svg} from '../../utils/svg.js';
|
||||
import type {WorkspaceSvg} from '../../workspace_svg.js';
|
||||
import type {ConstantProvider as BaseConstantProvider} from '../common/constants.js';
|
||||
import {MarkerSvg as BaseMarkerSvg} from '../common/marker_svg.js';
|
||||
import type {ConstantProvider as ZelosConstantProvider} from './constants.js';
|
||||
|
||||
/**
|
||||
* Class to draw a marker.
|
||||
*/
|
||||
export class MarkerSvg extends BaseMarkerSvg {
|
||||
// TODO(b/109816955): remove '!', see go/strict-prop-init-fix.
|
||||
constants_!: ZelosConstantProvider;
|
||||
|
||||
private markerCircle: SVGCircleElement | null = null;
|
||||
|
||||
/**
|
||||
* @param workspace The workspace the marker belongs to.
|
||||
* @param constants The constants for the renderer.
|
||||
* @param marker The marker to draw.
|
||||
*/
|
||||
constructor(
|
||||
workspace: WorkspaceSvg,
|
||||
constants: BaseConstantProvider,
|
||||
marker: Marker,
|
||||
) {
|
||||
super(workspace, constants, marker);
|
||||
}
|
||||
|
||||
/**
|
||||
* Position and display the marker for an input or an output connection.
|
||||
*
|
||||
* @param curNode The node to draw the marker for.
|
||||
*/
|
||||
private showWithInputOutput(curNode: ASTNode) {
|
||||
const block = curNode.getSourceBlock() as BlockSvg;
|
||||
const connection = curNode.getLocation() as RenderedConnection;
|
||||
const offsetInBlock = connection.getOffsetInBlock();
|
||||
|
||||
this.positionCircle(offsetInBlock.x, offsetInBlock.y);
|
||||
this.setParent_(block);
|
||||
this.showCurrent_();
|
||||
}
|
||||
|
||||
override showWithOutput_(curNode: ASTNode) {
|
||||
this.showWithInputOutput(curNode);
|
||||
}
|
||||
|
||||
override showWithInput_(curNode: ASTNode) {
|
||||
this.showWithInputOutput(curNode);
|
||||
}
|
||||
|
||||
/**
|
||||
* Draw a rectangle around the block.
|
||||
*
|
||||
* @param curNode The current node of the marker.
|
||||
*/
|
||||
override showWithBlock_(curNode: ASTNode) {
|
||||
const block = curNode.getLocation() as BlockSvg;
|
||||
|
||||
// Gets the height and width of entire stack.
|
||||
const heightWidth = block.getHeightWidth();
|
||||
// Add padding so that being on a stack looks different than being on a
|
||||
// block.
|
||||
this.positionRect_(0, 0, heightWidth.width, heightWidth.height);
|
||||
this.setParent_(block);
|
||||
this.showCurrent_();
|
||||
}
|
||||
|
||||
/**
|
||||
* Position the circle we use for input and output connections.
|
||||
*
|
||||
* @param x The x position of the circle.
|
||||
* @param y The y position of the circle.
|
||||
*/
|
||||
private positionCircle(x: number, y: number) {
|
||||
this.markerCircle?.setAttribute('cx', `${x}`);
|
||||
this.markerCircle?.setAttribute('cy', `${y}`);
|
||||
this.currentMarkerSvg = this.markerCircle;
|
||||
}
|
||||
|
||||
override hide() {
|
||||
super.hide();
|
||||
if (this.markerCircle) {
|
||||
this.markerCircle.style.display = 'none';
|
||||
}
|
||||
}
|
||||
|
||||
override createDomInternal_() {
|
||||
/* clang-format off */
|
||||
/* This markup will be generated and added to the .svgGroup_:
|
||||
<g>
|
||||
<rect width="100" height="5">
|
||||
<animate attributeType="XML" attributeName="fill" dur="1s"
|
||||
values="transparent;transparent;#fff;transparent" repeatCount="indefinite" />
|
||||
</rect>
|
||||
</g>
|
||||
*/
|
||||
/* clang-format on */
|
||||
super.createDomInternal_();
|
||||
|
||||
this.markerCircle = dom.createSvgElement(
|
||||
Svg.CIRCLE,
|
||||
{
|
||||
'r': this.constants_.CURSOR_RADIUS,
|
||||
'stroke-width': this.constants_.CURSOR_STROKE_WIDTH,
|
||||
},
|
||||
this.markerSvg_,
|
||||
);
|
||||
this.hide();
|
||||
|
||||
// Markers and stack cursors don't blink.
|
||||
if (this.isCursor()) {
|
||||
const blinkProperties = this.getBlinkProperties_();
|
||||
dom.createSvgElement(Svg.ANIMATE, blinkProperties, this.markerCircle!);
|
||||
}
|
||||
|
||||
return this.markerSvg_!;
|
||||
}
|
||||
|
||||
override applyColour_(curNode: ASTNode) {
|
||||
super.applyColour_(curNode);
|
||||
|
||||
this.markerCircle?.setAttribute('fill', this.colour_);
|
||||
this.markerCircle?.setAttribute('stroke', this.colour_);
|
||||
|
||||
if (this.isCursor()) {
|
||||
const values = this.colour_ + ';transparent;transparent;';
|
||||
this.markerCircle?.firstElementChild!.setAttribute('values', values);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -7,16 +7,13 @@
|
||||
// Former goog.module ID: Blockly.zelos.Renderer
|
||||
|
||||
import type {BlockSvg} from '../../block_svg.js';
|
||||
import type {Marker} from '../../keyboard_nav/marker.js';
|
||||
import type {BlockStyle} from '../../theme.js';
|
||||
import type {WorkspaceSvg} from '../../workspace_svg.js';
|
||||
import * as blockRendering from '../common/block_rendering.js';
|
||||
import type {RenderInfo as BaseRenderInfo} from '../common/info.js';
|
||||
import {Renderer as BaseRenderer} from '../common/renderer.js';
|
||||
import {ConstantProvider} from './constants.js';
|
||||
import {Drawer} from './drawer.js';
|
||||
import {RenderInfo} from './info.js';
|
||||
import {MarkerSvg} from './marker_svg.js';
|
||||
import {PathObject} from './path_object.js';
|
||||
|
||||
/**
|
||||
@@ -69,20 +66,6 @@ export class Renderer extends BaseRenderer {
|
||||
return new Drawer(block, info as RenderInfo);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new instance of the renderer's cursor drawer.
|
||||
*
|
||||
* @param workspace The workspace the cursor belongs to.
|
||||
* @param marker The marker.
|
||||
* @returns The object in charge of drawing the marker.
|
||||
*/
|
||||
override makeMarkerDrawer(
|
||||
workspace: WorkspaceSvg,
|
||||
marker: Marker,
|
||||
): MarkerSvg {
|
||||
return new MarkerSvg(workspace, this.getConstants(), marker);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new instance of a renderer path object.
|
||||
*
|
||||
|
||||
@@ -11,7 +11,6 @@
|
||||
import {ConstantProvider} from './constants.js';
|
||||
import {Drawer} from './drawer.js';
|
||||
import {RenderInfo} from './info.js';
|
||||
import {MarkerSvg} from './marker_svg.js';
|
||||
import {BottomRow} from './measurables/bottom_row.js';
|
||||
import {StatementInput} from './measurables/inputs.js';
|
||||
import {RightConnectionShape} from './measurables/row_elements.js';
|
||||
@@ -23,7 +22,6 @@ export {
|
||||
BottomRow,
|
||||
ConstantProvider,
|
||||
Drawer,
|
||||
MarkerSvg,
|
||||
PathObject,
|
||||
Renderer,
|
||||
RenderInfo,
|
||||
|
||||
@@ -64,7 +64,6 @@ import {Navigator} from './navigator.js';
|
||||
import {Options} from './options.js';
|
||||
import * as Procedures from './procedures.js';
|
||||
import * as registry from './registry.js';
|
||||
import * as renderManagement from './render_management.js';
|
||||
import * as blockRendering from './renderers/common/block_rendering.js';
|
||||
import type {Renderer} from './renderers/common/renderer.js';
|
||||
import type {ScrollbarPair} from './scrollbar_pair.js';
|
||||
@@ -474,28 +473,6 @@ export class WorkspaceSvg
|
||||
return this.componentManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the cursor SVG to this workspaces SVG group.
|
||||
*
|
||||
* @param cursorSvg The SVG root of the cursor to be added to the workspace
|
||||
* SVG group.
|
||||
* @internal
|
||||
*/
|
||||
setCursorSvg(cursorSvg: SVGElement) {
|
||||
this.markerManager.setCursorSvg(cursorSvg);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the marker SVG to this workspaces SVG group.
|
||||
*
|
||||
* @param markerSvg The SVG root of the marker to be added to the workspace
|
||||
* SVG group.
|
||||
* @internal
|
||||
*/
|
||||
setMarkerSvg(markerSvg: SVGElement) {
|
||||
this.markerManager.setMarkerSvg(markerSvg);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the marker with the given ID.
|
||||
*
|
||||
@@ -1340,10 +1317,6 @@ export class WorkspaceSvg
|
||||
.flatMap((block) => block.getDescendants(false))
|
||||
.filter((block) => block.isInsertionMarker())
|
||||
.forEach((block) => block.queueRender());
|
||||
|
||||
renderManagement
|
||||
.finishQueuedRenders()
|
||||
.then(() => void this.markerManager.updateMarkers());
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user