| /** | |
| * @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.js'; | |
| 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); |