/** * @license * Copyright 2019 Google LLC * SPDX-License-Identifier: Apache-2.0 */ import * as goog from '../../../closure/goog/goog.js'; goog.declareModuleId('Blockly.zelos.PathObject'); import type {BlockSvg} from '../../block_svg.js'; import type {Connection} from '../../connection.js'; import type {BlockStyle} from '../../theme.js'; import * as dom from '../../utils/dom.js'; import {Svg} from '../../utils/svg.js'; import {PathObject as BasePathObject} from '../common/path_object.js'; import type {ConstantProvider} from './constants.js'; /** * An object that handles creating and setting each of the SVG elements * used by the renderer. */ export class PathObject extends BasePathObject { /** The selected path of the block. */ private svgPathSelected_: SVGElement|null = null; /** The outline paths on the block. */ private readonly outlines = new Map(); /** * A set used to determine which outlines were used during a draw pass. The * set is initialized with a reference to all the outlines in * `this.outlines`. Every time we use an outline during the draw pass, the * reference is removed from this set. */ private remainingOutlines = new Set(); /** * The type of block's output connection shape. This is set when a block * with an output connection is drawn. */ outputShapeType: number|null = null; public override constants: ConstantProvider; /** * @param root The root SVG element. * @param style The style object to use for colouring. * @param constants The renderer's constants. */ constructor( root: SVGElement, style: BlockStyle, constants: ConstantProvider) { super(root, style, constants); this.constants = constants; } override setPath(pathString: string) { super.setPath(pathString); if (this.svgPathSelected_) { this.svgPathSelected_.setAttribute('d', pathString); } } override applyColour(block: BlockSvg) { super.applyColour(block); // Set shadow stroke colour. const parent = block.getParent(); if (block.isShadow() && parent) { this.svgPath.setAttribute('stroke', parent.style.colourTertiary); } // Apply colour to outlines. for (const outline of this.outlines.values()) { outline.setAttribute('fill', this.style.colourTertiary); } } override flipRTL() { super.flipRTL(); // Mirror each input outline path. for (const outline of this.outlines.values()) { outline.setAttribute('transform', 'scale(-1 1)'); } } override updateSelected(enable: boolean) { this.setClass_('blocklySelected', enable); if (enable) { if (!this.svgPathSelected_) { this.svgPathSelected_ = this.svgPath.cloneNode(true) as SVGElement; this.svgPathSelected_.setAttribute('fill', 'none'); this.svgPathSelected_.setAttribute( 'filter', 'url(#' + this.constants.selectedGlowFilterId + ')'); this.svgRoot.appendChild(this.svgPathSelected_); } } else { if (this.svgPathSelected_) { this.svgRoot.removeChild(this.svgPathSelected_); this.svgPathSelected_ = null; } } } override updateReplacementFade(enable: boolean) { this.setClass_('blocklyReplaceable', enable); if (enable) { this.svgPath.setAttribute( 'filter', 'url(#' + this.constants.replacementGlowFilterId + ')'); } else { this.svgPath.removeAttribute('filter'); } } override updateShapeForInputHighlight(conn: Connection, enable: boolean) { const name = conn.getParentInput()!.name; const outlinePath = this.getOutlinePath_(name); if (!outlinePath) { return; } if (enable) { outlinePath.setAttribute( 'filter', 'url(#' + this.constants.replacementGlowFilterId + ')'); } else { outlinePath.removeAttribute('filter'); } } /** * Method that's called when the drawer is about to draw the block. */ beginDrawing() { this.remainingOutlines.clear(); for (const key of this.outlines.keys()) { this.remainingOutlines.add(key); } } /** * Method that's called when the drawer is done drawing. */ endDrawing() { // Go through all remaining outlines that were not used this draw pass, and // remove them. if (this.remainingOutlines.size) { for (const key of this.remainingOutlines) { this.removeOutlinePath_(key); } } this.remainingOutlines.clear(); } /** * Set the path generated by the renderer for an outline path on the * respective outline path SVG element. * * @param name The input name. * @param pathString The path. */ setOutlinePath(name: string, pathString: string) { const outline = this.getOutlinePath_(name); outline.setAttribute('d', pathString); outline.setAttribute('fill', this.style.colourTertiary); } /** * Create's an outline path for the specified input. * * @param name The input name. * @returns The SVG outline path. */ private getOutlinePath_(name: string): SVGElement { if (!this.outlines.has(name)) { this.outlines.set( name, dom.createSvgElement( Svg.PATH, { 'class': 'blocklyOutlinePath', // IE doesn't like paths without the // data definition, set empty // default 'd': '', }, this.svgRoot)); } this.remainingOutlines.delete(name); return this.outlines.get(name)!; } /** * Remove an outline path that is associated with the specified input. * * @param name The input name. */ private removeOutlinePath_(name: string) { this.outlines.get(name)?.parentNode?.removeChild(this.outlines.get(name)!); this.outlines.delete(name); } }