|  | /** | 
|  | * @module modules/debug-view-sk | 
|  | * @description Container and manager of the wasm-linked main canvas for the debugger. | 
|  | *   Contains several CSS resizing buttons that do not alter the surface size. | 
|  | * | 
|  | * @evt move-cursor: Emitted when the user has moved the cursor by clicking or hovering. | 
|  | */ | 
|  | import { html } from 'lit-html'; | 
|  | import { define } from '../../../elements-sk/modules/define'; | 
|  | import { ElementDocSk } from '../element-doc-sk/element-doc-sk'; | 
|  | import { | 
|  | CursorEventDetail, | 
|  | ToggleBackgroundEventDetail, | 
|  | MoveCursorEvent, | 
|  | Point, | 
|  | RenderCursorEvent, | 
|  | ToggleBackgroundEvent, | 
|  | } from '../events'; | 
|  |  | 
|  | export type FitStyle = 'natural' | 'fit' | 'right' | 'bottom'; | 
|  |  | 
|  | export class DebugViewSk extends ElementDocSk { | 
|  | private static template = (ele: DebugViewSk) => | 
|  | html` <div class="horizontal-flex"> | 
|  | <button | 
|  | title="Original size." | 
|  | @click=${() => (ele.fitStyle = 'natural')}> | 
|  | <img src="/dist/image.png" /> | 
|  | </button> | 
|  | <button title="Fit in page." @click=${() => (ele.fitStyle = 'fit')}> | 
|  | <img src="/dist/both.png" /> | 
|  | </button> | 
|  | <button title="Fit to width." @click=${() => (ele.fitStyle = 'right')}> | 
|  | <img src="/dist/right.png" /> | 
|  | </button> | 
|  | <button | 
|  | title="Fit to height." | 
|  | @click=${() => (ele.fitStyle = 'bottom')}> | 
|  | <img src="/dist/bottom.png" /> | 
|  | </button> | 
|  | </div> | 
|  | <div id="backdrop" class="${ele._backdropStyle} grid"> | 
|  | ${ele._renderCanvas | 
|  | ? html`<canvas | 
|  | id="main-canvas" | 
|  | class=${ele._fitStyle} | 
|  | width=${ele._width} | 
|  | height=${ele._height}></canvas>` | 
|  | : ''} | 
|  | <canvas | 
|  | id="crosshair-canvas" | 
|  | class=${ele._fitStyle} | 
|  | width=${ele._width} | 
|  | height=${ele._height} | 
|  | @click=${ele._canvasClicked} | 
|  | @mousemove=${ele._canvasMouseMove}></canvas> | 
|  | </div>`; | 
|  |  | 
|  | // the native width and height of the main canvas, before css is applied | 
|  | private _width: number = 400; | 
|  |  | 
|  | private _height: number = 400; | 
|  |  | 
|  | // the css class used to size the canvas. | 
|  | private _fitStyle: FitStyle = 'fit'; | 
|  |  | 
|  | private _backdropStyle = 'light-checkerboard'; | 
|  |  | 
|  | private _crossHairActive = false; | 
|  |  | 
|  | private _renderCanvas = true; | 
|  |  | 
|  | get crosshairActive(): boolean { | 
|  | return this._crossHairActive; | 
|  | } | 
|  |  | 
|  | constructor() { | 
|  | super(DebugViewSk.template); | 
|  | } | 
|  |  | 
|  | connectedCallback(): void { | 
|  | super.connectedCallback(); | 
|  | this._render(); | 
|  |  | 
|  | this.addDocumentEventListener(RenderCursorEvent, (e) => { | 
|  | const detail = (e as CustomEvent<CursorEventDetail>).detail; | 
|  | if (!this._crossHairActive || detail.onlyData) { | 
|  | return; | 
|  | } | 
|  | this._drawCrossHairAt(detail.position); | 
|  | }); | 
|  |  | 
|  | this.addDocumentEventListener(ToggleBackgroundEvent, (e) => { | 
|  | this._backdropStyle = ( | 
|  | e as CustomEvent<ToggleBackgroundEventDetail> | 
|  | ).detail.mode; | 
|  | this._render(); | 
|  | }); | 
|  | } | 
|  |  | 
|  | // Pass one of the CSS classes for sizing the debug view canvas. | 
|  | // It doesn't change the pixel size of the SkSurface, use resize for that. | 
|  | set fitStyle(fs: FitStyle) { | 
|  | this._fitStyle = fs; | 
|  | this._render(); | 
|  | } | 
|  |  | 
|  | get canvas(): HTMLCanvasElement { | 
|  | this._render(); | 
|  | return this.querySelector<HTMLCanvasElement>('#main-canvas')!; | 
|  | } | 
|  |  | 
|  | // Replace the main canvas element, changing its native size | 
|  | resize(width = 400, height = 400): HTMLCanvasElement { | 
|  | this._width = width; | 
|  | this._height = height; | 
|  | this._renderCanvas = false; | 
|  | this._render(); // delete it to clear it's rendering context. | 
|  | this._renderCanvas = true; | 
|  | this._render(); // template makes a fresh one. | 
|  | return this.querySelector('canvas')!; | 
|  | } | 
|  |  | 
|  | private _visibleSize(): Point { | 
|  | const element = this.querySelector<HTMLCanvasElement>('#main-canvas')!; | 
|  | const strW = window.getComputedStyle(element, null).width; | 
|  | const strH = window.getComputedStyle(element, null).height; | 
|  | // Trim 'px' off the end of the style string and convert to a number. | 
|  | const visibleWidth = parseFloat(strW.substring(0, strW.length - 2)); | 
|  | const visibleHeight = parseFloat(strH.substring(0, strH.length - 2)); | 
|  | return [visibleWidth, visibleHeight]; | 
|  | } | 
|  |  | 
|  | private _mouseOffsetToCanvasPoint(e: MouseEvent): Point { | 
|  | // The element changes size occasionally, compute visible size just before use. | 
|  | const size = this._visibleSize(); | 
|  | return [ | 
|  | Math.round((e.offsetX / size[0]) * this._width), | 
|  | Math.round((e.offsetY / size[1]) * this._height), | 
|  | ]; | 
|  | } | 
|  |  | 
|  | private _sendCursorMove(p: Point) { | 
|  | this.dispatchEvent( | 
|  | new CustomEvent<CursorEventDetail>(MoveCursorEvent, { | 
|  | detail: { position: p, onlyData: false }, | 
|  | bubbles: true, | 
|  | }) | 
|  | ); | 
|  | } | 
|  |  | 
|  | private _drawCrossHairAt(p: Point) { | 
|  | const chCanvas = | 
|  | this.querySelector<HTMLCanvasElement>('#crosshair-canvas')!; | 
|  | const chx = chCanvas.getContext('2d')!; | 
|  | chx.clearRect(0, 0, chCanvas.width, chCanvas.height); | 
|  |  | 
|  | chx.lineWidth = this._width / this._visibleSize()[0]; | 
|  | chx.strokeStyle = '#F00'; | 
|  | chx.beginPath(); | 
|  | chx.moveTo(0, p[1] - 0.5); | 
|  | chx.lineTo(chCanvas.width + 1, p[1] - 0.5); | 
|  | chx.moveTo(p[0] - 0.5, 0); | 
|  | chx.lineTo(p[0] - 0.5, chCanvas.height + 1); | 
|  | chx.stroke(); | 
|  | } | 
|  |  | 
|  | private _canvasClicked(e: MouseEvent) { | 
|  | if (e.offsetX < 0) { | 
|  | return; | 
|  | } // border | 
|  | const coords = this._mouseOffsetToCanvasPoint(e); | 
|  | if (this._crossHairActive) { | 
|  | this._crossHairActive = false; | 
|  | this._drawCrossHairAt([-5, -5]); // lazy clear | 
|  | this._sendCursorMove(coords); | 
|  | } else { | 
|  | this._crossHairActive = true; | 
|  | this._sendCursorMove(coords); | 
|  | } | 
|  | } | 
|  |  | 
|  | private _canvasMouseMove(e: MouseEvent) { | 
|  | if (e.offsetX < 0) { | 
|  | return; | 
|  | } // border | 
|  | if (this._crossHairActive) { | 
|  | return; | 
|  | } | 
|  | this._sendCursorMove(this._mouseOffsetToCanvasPoint(e)); | 
|  | } | 
|  | } | 
|  |  | 
|  | define('debug-view-sk', DebugViewSk); |