# HG changeset patch # User drewp@bigasterisk.com # Date 2022-06-03 09:19:47 # Node ID e2ed5ce362537b7b08b1cb1ebb0b0dd716ff0e25 # Parent 1dc96b97a544b43ba863485336a8da9d96ca4b5d double spectrum views have a connected cursor diff --git a/light9/ascoltami/Light9AscoltamiUi.ts b/light9/ascoltami/Light9AscoltamiUi.ts --- a/light9/ascoltami/Light9AscoltamiUi.ts +++ b/light9/ascoltami/Light9AscoltamiUi.ts @@ -1,14 +1,19 @@ import debug from "debug"; import { css, html, LitElement } from "lit"; import { customElement, property } from "lit/decorators.js"; +import { classMap } from "lit/directives/class-map.js"; import { NamedNode } from "n3"; +import Sylvester from "sylvester"; +import { Zoom } from "../web/light9-timeline-audio"; +import { PlainViewState } from "../web/Light9CursorCanvas"; import { getTopGraph } from "../web/RdfdbSyncedGraph"; import { SyncedGraph } from "../web/SyncedGraph"; -export { RdfdbSyncedGraph } from "../web/RdfdbSyncedGraph"; +import { TimingUpdate } from "./main"; export { Light9TimelineAudio } from "../web/light9-timeline-audio"; -import { classMap } from "lit/directives/class-map.js"; -import { TimingUpdate } from "./main"; -import { Zoom } from "../web/light9-timeline-audio"; +export { Light9CursorCanvas } from "../web/Light9CursorCanvas"; +export { RdfdbSyncedGraph } from "../web/RdfdbSyncedGraph"; + +const $V = Sylvester.Vector.create; debug.enable("*"); const log = debug("asco"); @@ -31,18 +36,30 @@ export class Light9AscoltamiUi extends L @property() isPlaying: boolean = false; @property() show: NamedNode | null = null; @property() song: NamedNode | null = null; - @property() t: number = 0; @property() currentDuration: number = 0; @property() zoom: Zoom; @property() overviewZoom: Zoom; + @property() viewState: PlainViewState | null = null; static styles = [ css` .timeRow { margin: 14px; + position: relative; } - light9-timeline-audio { + #overview { + height: 60px; + } + #zoomed { + margin-top: 40px; height: 80px; } + #cursor { + position: absolute; + left: 0; + top: 0; + width: 100%; + height: 100%; + } `, ]; render() { @@ -54,8 +71,9 @@ export class Light9AscoltamiUi extends L
- - + + +
@@ -149,7 +167,22 @@ export class Light9AscoltamiUi extends L this.currentDuration = data.duration; this.song = new NamedNode(data.song); this.overviewZoom = { duration: data.duration, t1: 0, t2: data.duration }; - this.zoom = { duration: data.duration, t1: data.t - 2, t2: data.t + 20 }; + const t1 = data.t - 2, + t2 = data.t + 20; + this.zoom = { duration: data.duration, t1, t2 }; + const timeRow = this.shadowRoot!.querySelector(".timeRow") as HTMLDivElement; + const w = timeRow.offsetWidth; + this.viewState = { + zoomSpec: { t1: () => t1, t2: () => t2 }, + cursor: { t: () => data.t }, + audioY: () => 0, + audioH: () => 60, + zoomedTimeY: () => 60, + zoomedTimeH: () => 40, + fullZoomX: (sec: number) => (sec / data.duration) * w, + zoomInX: (sec: number) => ((sec - t1) / (t2 - t1)) * w, + mouse: { pos: () => $V([0, 0]) }, + }; }); } diff --git a/light9/web/timeline/cursor_canvas.coffee b/light9/web/Light9CursorCanvas.ts rename from light9/web/timeline/cursor_canvas.coffee rename to light9/web/Light9CursorCanvas.ts --- a/light9/web/timeline/cursor_canvas.coffee +++ b/light9/web/Light9CursorCanvas.ts @@ -1,69 +1,146 @@ -coffeeElementSetup(class CursorCanvas extends Polymer.mixinBehaviors([Polymer.IronResizableBehavior], Polymer.Element) - @is: 'light9-cursor-canvas' - @getter_properties: - viewState: { type: Object, notify: true, observer: "onViewState" } - ready: -> - super.ready() - @cursorPath = null - @ctx = @$.canvas.getContext('2d') - @onResize() - @addEventListener('iron-resize', @onResize.bind(@)) +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"); - onViewState: -> - ko.computed(@redrawCursor.bind(@)) +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 }; +} - onResize: (ev) -> - @$.canvas.width = @offsetWidth - @$.canvas.height = @offsetHeight - @redrawCursor() +// 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``; + } - redrawCursor: -> - vs = @viewState - dependOn = [vs.zoomSpec.t1(), vs.zoomSpec.t2()] - xZoomedOut = vs.fullZoomX(vs.cursor.t()) - xZoomedIn = vs.zoomInX(vs.cursor.t()) - - @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, @offsetHeight]) + updated(changedProperties: PropertyValues) { + if (changedProperties.has("viewState")) { + this.redrawCursor(); } - @redraw() + } + connectedCallback() { + super.connectedCallback(); + window.addEventListener("resize", this.onResize); + this.onResize(); + } - redraw: -> - return unless @ctx - @ctx.clearRect(0, 0, @$.canvas.width, @$.canvas.height) + firstUpdated() { + this.canvasEl = this.shadowRoot!.firstElementChild as HTMLCanvasElement; + this.onResize(); + this.ctx = this.canvasEl.getContext("2d")!; + } + + disconnectedCallback() { + window.removeEventListener("resize", this.onResize); + super.disconnectedCallback(); + } - @ctx.strokeStyle = '#fff' - @ctx.lineWidth = 0.5 - @ctx.beginPath() - mouse = @viewState.mouse.pos() - Drawing.line(@ctx, $V([0, mouse.e(2)]), $V([@$.canvas.width, mouse.e(2)])) - Drawing.line(@ctx, $V([mouse.e(1), 0]), $V([mouse.e(1), @$.canvas.height])) - @ctx.stroke() + // 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()); - if @cursorPath - @ctx.strokeStyle = '#ff0303' - @ctx.lineWidth = 1.5 - @ctx.beginPath() - Drawing.line(@ctx, @cursorPath.top0, @cursorPath.top1) - @ctx.stroke() + 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); - @ctx.fillStyle = '#9c0303' - @ctx.beginPath() - @ctx.moveTo(@cursorPath.mid0.e(1), @cursorPath.mid0.e(2)) - @ctx.lineTo(p.e(1), p.e(2)) for p in [ - @cursorPath.mid1, @cursorPath.mid2, @cursorPath.mid3] - @ctx.fill() - - @ctx.strokeStyle = '#ff0303' - @ctx.lineWidth = 3 - @ctx.beginPath() - Drawing.line(@ctx, @cursorPath.bot0, @cursorPath.bot1, '#ff0303', '3px') - @ctx.stroke() -) \ No newline at end of file + 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(); + } + } +} diff --git a/light9/web/timeline/drawing.ts b/light9/web/drawing.ts rename from light9/web/timeline/drawing.ts rename to light9/web/drawing.ts diff --git a/light9/web/light9-timeline-audio.ts b/light9/web/light9-timeline-audio.ts --- a/light9/web/light9-timeline-audio.ts +++ b/light9/web/light9-timeline-audio.ts @@ -1,9 +1,7 @@ import { debug } from "debug"; - -import { css, html, LitElement, PropertyValues, TemplateResult } from "lit"; +import { html, LitElement, PropertyValues } from "lit"; import { customElement, property, state } from "lit/decorators.js"; import { NamedNode } from "n3"; -import { loadConfigFromFile } from "vite"; import { getTopGraph } from "./RdfdbSyncedGraph"; import { SyncedGraph } from "./SyncedGraph"; @@ -46,8 +44,7 @@ export class Light9TimelineAudio extends img { height: 100%; position: relative; - transition: left .1s linear; - + transition: left 0.1s linear; }
diff --git a/light9/web/timeline/adjusters.ts b/light9/web/timeline/adjusters.ts --- a/light9/web/timeline/adjusters.ts +++ b/light9/web/timeline/adjusters.ts @@ -4,7 +4,7 @@ import { customElement } from "lit/decor import { throttle } from "underscore"; import * as d3 from "d3"; import { Adjustable } from "./adjustable"; -import * as Drawing from "./drawing"; +import * as Drawing from "../drawing"; // https://www.npmjs.com/package/@types/sylvester Global values: $L, $M, $P, $V, Line, Matrix, Plane, Sylvester, Vector const log = debug("adjusters");