import debug from "debug"; import { css, html, LitElement, PropertyValues } from "lit"; import { customElement, property } from "lit/decorators.js"; import Sylvester from "sylvester"; import { line } from "./drawing"; const $V = Sylvester.Vector.create; const log = debug("cursor"); export interface PlainViewState { zoomSpec: { t1: () => number; t2: () => number }; fullZoomX: (t: number) => number; zoomInX: (t: number) => number; cursor: { t: () => number }; audioY: () => number; audioH: () => number; zoomedTimeY: () => number; // not what you think- it's the zone in between zoomedTimeH: () => number; mouse: { pos: () => Vector }; } // For cases where you have a zoomed-out view on top of a zoomed-in view, // overlay this element and it'll draw a time cursor on both views. @customElement("light9-cursor-canvas") export class Light9CursorCanvas extends LitElement { cursorPath: null | { top0: Vector; top1: Vector; mid0: Vector; mid1: Vector; mid2: Vector; mid3: Vector; bot0: Vector; bot1: Vector; } = null; canvasEl!: HTMLCanvasElement; ctx!: CanvasRenderingContext2D; offsetWidth: any; offsetHeight: any; @property() viewState: PlainViewState | null = null; static styles = [ css` :host { display: inline-block; } `, ]; render() { return html``; } updated(changedProperties: PropertyValues) { if (changedProperties.has("viewState")) { this.redrawCursor(); } } connectedCallback() { super.connectedCallback(); window.addEventListener("resize", this.onResize); this.onResize(); } firstUpdated() { this.canvasEl = this.shadowRoot!.firstElementChild as HTMLCanvasElement; this.onResize(); this.ctx = this.canvasEl.getContext("2d")!; } disconnectedCallback() { window.removeEventListener("resize", this.onResize); super.disconnectedCallback(); } // onViewState() { // ko.computed(this.redrawCursor.bind(this)); // } onResize() { if (!this.canvasEl) { return; } this.canvasEl.width = this.offsetWidth; this.canvasEl.height = this.offsetHeight; this.redrawCursor(); } redrawCursor() { const vs = this.viewState; if (!vs) { return; } const dependOn = [vs.zoomSpec.t1(), vs.zoomSpec.t2()]; const xZoomedOut = vs.fullZoomX(vs.cursor.t()); const xZoomedIn = vs.zoomInX(vs.cursor.t()); this.cursorPath = { top0: $V([xZoomedOut, vs.audioY()]), top1: $V([xZoomedOut, vs.audioY() + vs.audioH()]), mid0: $V([xZoomedIn + 2, vs.zoomedTimeY() + vs.zoomedTimeH()]), mid1: $V([xZoomedIn - 2, vs.zoomedTimeY() + vs.zoomedTimeH()]), mid2: $V([xZoomedOut - 1, vs.audioY() + vs.audioH()]), mid3: $V([xZoomedOut + 1, vs.audioY() + vs.audioH()]), bot0: $V([xZoomedIn, vs.zoomedTimeY() + vs.zoomedTimeH()]), bot1: $V([xZoomedIn, this.offsetHeight]), }; this.redraw(); } redraw() { if (!this.ctx || !this.viewState) { return; } this.ctx.clearRect(0, 0, this.canvasEl.width, this.canvasEl.height); this.ctx.strokeStyle = "#fff"; this.ctx.lineWidth = 0.5; this.ctx.beginPath(); const mouse = this.viewState.mouse.pos(); line(this.ctx, $V([0, mouse.e(2)]), $V([this.canvasEl.width, mouse.e(2)])); line(this.ctx, $V([mouse.e(1), 0]), $V([mouse.e(1), this.canvasEl.height])); this.ctx.stroke(); if (this.cursorPath) { this.ctx.strokeStyle = "#ff0303"; this.ctx.lineWidth = 1.5; this.ctx.beginPath(); line(this.ctx, this.cursorPath.top0, this.cursorPath.top1); this.ctx.stroke(); this.ctx.fillStyle = "#9c0303"; this.ctx.beginPath(); this.ctx.moveTo(this.cursorPath.mid0.e(1), this.cursorPath.mid0.e(2)); for (let p of [this.cursorPath.mid1, this.cursorPath.mid2, this.cursorPath.mid3]) { this.ctx.lineTo(p.e(1), p.e(2)); } this.ctx.fill(); this.ctx.strokeStyle = "#ff0303"; this.ctx.lineWidth = 3; this.ctx.beginPath(); line(this.ctx, this.cursorPath.bot0, this.cursorPath.bot1, "#ff0303", "3px"); this.ctx.stroke(); } } }