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();
}
}
}