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";
import { Vector } from "./lib/sylvester";
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 };
}
export interface PlainerViewState {
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: PlainerViewState | null = null;
static styles = [
css`
:host {
display: inline-block;
}
canvas {
width: 100%;
height: 100%;
}
`,
];
resizeObserver!: ResizeObserver;
render() {
return html``;
}
updated(changedProperties: PropertyValues) {
if (changedProperties.has("viewState")) {
this.redrawCursor();
}
}
connectedCallback() {
super.connectedCallback();
this.resizeObserver = new ResizeObserver(this.onResize.bind(this));
this.resizeObserver.observe(this);
}
firstUpdated() {
this.canvasEl = this.shadowRoot!.firstElementChild as HTMLCanvasElement;
this.ctx = this.canvasEl.getContext("2d")!;
}
disconnectedCallback() {
this.resizeObserver.unobserve(this);
}
onResize() {
log('onResize', this.clientWidth, this.clientHeight);
if (!this.canvasEl) {
return;
}
this.canvasEl.width = this.clientWidth;
this.canvasEl.height = this.clientHeight;
this.redrawCursor();
}
redrawCursor() {
const vs = this.viewState;
if (!vs) {
return;
}
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();
}
}
}