// A frameworking class assigning some CSS classes to the canvas to make it integrate nicer with CSS. export class Frame extends HTMLCanvasElement { static fontFace = "RecVar"; static monoFontFace = "RecVarMono"; constructor() { super(); } async connectedCallback() { this.style.cssText = ` margin-top: 8px; margin-bottom: 4px; border-radius: 4px; max-width: 100%; `; this.ctx = this.getContext("2d"); requestAnimationFrame(this.#drawLoop.bind(this)); } #drawLoop() { this.ctx.font = "14px RecVar"; this.draw(); requestAnimationFrame(this.#drawLoop.bind(this)); } // Override this! draw() { throw new ReferenceError("draw() must be overridden"); } getTextPositionInBox(text, x, y, width, height, hAlign, vAlign) { let measurements = this.ctx.measureText(text); let leftX; switch (hAlign) { case "left": leftX = x; break; case "center": leftX = x + width / 2 - measurements.width / 2; break; case "right": leftX = x + width - measurements.width; break; } let textHeight = measurements.fontBoundingBoxAscent; let baselineY; switch (vAlign) { case "top": baselineY = y + textHeight; break; case "center": baselineY = y + height / 2 + textHeight / 2; break; case "bottom": baselineY = y + height; break; } return { leftX, baselineY }; } get scaleInViewportX() { return this.clientWidth / this.width; } get scaleInViewportY() { return this.clientHeight / this.height; } getMousePositionFromEvent(event) { return { x: event.offsetX / this.scaleInViewportX, y: event.offsetY / this.scaleInViewportY, }; } } export function defineFrame(elementName, claß) { // because `class` is a keyword. customElements.define(elementName, claß, { extends: "canvas" }); } defineFrame("tairu--frame", Frame);