diff --git a/core/bubbles/text_bubble.ts b/core/bubbles/text_bubble.ts new file mode 100644 index 000000000..adba5843b --- /dev/null +++ b/core/bubbles/text_bubble.ts @@ -0,0 +1,99 @@ +/** + * @license + * Copyright 2023 Google LLC + * SPDX-License-Identifier: Apache-2.0 + */ + +import {Bubble} from './bubble.js'; +import {Coordinate} from '../utils/coordinate.js'; +import * as dom from '../utils/dom.js'; +import {Rect} from '../utils/rect.js'; +import {Size} from '../utils/size.js'; +import {Svg} from '../utils/svg.js'; +import {WorkspaceSvg} from '../workspace_svg.js'; + +export class TextBubble extends Bubble { + private paragraph: SVGTextElement; + + constructor( + private text: string, + protected readonly workspace: WorkspaceSvg, + protected anchor: Coordinate, + protected ownerRect?: Rect + ) { + super(workspace, anchor, ownerRect); + this.paragraph = this.stringToSvg(text, this.contentContainer); + this.updateBubbleSize(); + } + + /** @return the current text of this text bubble. */ + getText(): string { + return this.text; + } + + /** Sets the current text of this text bubble, and updates the display. */ + setText(text: string) { + this.text = text; + dom.removeNode(this.paragraph); + this.paragraph = this.stringToSvg(text, this.contentContainer); + this.updateBubbleSize(); + } + + /** + * Converts the given string into an svg containing that string, + * broken up by newlines. + */ + private stringToSvg(text: string, container: SVGGElement) { + const paragraph = this.createParagraph(container); + const spans = this.createSpans(paragraph, text); + if (this.workspace.RTL) + this.rightAlignSpans(paragraph.getBBox().width, spans); + return paragraph; + } + + /** Creates the paragraph container for this bubble's view's spans. */ + private createParagraph(container: SVGGElement): SVGTextElement { + return dom.createSvgElement( + Svg.TEXT, + { + 'class': 'blocklyText blocklyBubbleText blocklyNoPointerEvents', + 'y': Bubble.BORDER_WIDTH, + }, + container + ); + } + + /** Creates the spans visualizing the text of this bubble. */ + private createSpans(parent: SVGTextElement, text: string): SVGTSpanElement[] { + return text.split('\n').map((line) => { + const tspan = dom.createSvgElement( + Svg.TSPAN, + {'dy': '1em', 'x': Bubble.BORDER_WIDTH}, + parent + ); + const textNode = document.createTextNode(line); + tspan.appendChild(textNode); + return tspan; + }); + } + + /** Right aligns the given spans. */ + private rightAlignSpans(maxWidth: number, spans: SVGTSpanElement[]) { + for (const span of spans) { + span.setAttribute('text-anchor', 'end'); + span.setAttribute('x', `${maxWidth + Bubble.BORDER_WIDTH}`); + } + } + + /** Updates the size of this bubble to account for the size of the text. */ + private updateBubbleSize() { + const bbox = this.paragraph.getBBox(); + this.setSize( + new Size( + bbox.width + Bubble.BORDER_WIDTH * 2, + bbox.height + Bubble.BORDER_WIDTH * 2 + ), + true + ); + } +}