diff web/Light9CursorCanvas.ts @ 2376:4556eebe5d73

topdir reorgs; let pdm have its src/ dir; separate vite area from light9/
author drewp@bigasterisk.com
date Sun, 12 May 2024 19:02:10 -0700
parents light9/web/Light9CursorCanvas.ts@e2ed5ce36253
children 06da5db2fafe
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/web/Light9CursorCanvas.ts	Sun May 12 19:02:10 2024 -0700
@@ -0,0 +1,146 @@
+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`<canvas></canvas>`;
+  }
+
+  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();
+    }
+  }
+}