# HG changeset patch # User drewp@bigasterisk.com # Date 1654247987 25200 # Node ID e2ed5ce362537b7b08b1cb1ebb0b0dd716ff0e25 # Parent 1dc96b97a544b43ba863485336a8da9d96ca4b5d double spectrum views have a connected cursor diff -r 1dc96b97a544 -r e2ed5ce36253 light9/ascoltami/Light9AscoltamiUi.ts --- a/light9/ascoltami/Light9AscoltamiUi.ts Fri Jun 03 00:41:13 2022 -0700 +++ b/light9/ascoltami/Light9AscoltamiUi.ts Fri Jun 03 02:19:47 2022 -0700 @@ -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 @@ @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 @@
- - + + +
@@ -149,7 +167,22 @@ 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 -r 1dc96b97a544 -r e2ed5ce36253 light9/web/Light9CursorCanvas.ts --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/light9/web/Light9CursorCanvas.ts Fri Jun 03 02:19:47 2022 -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``; + } + + 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(); + } + } +} diff -r 1dc96b97a544 -r e2ed5ce36253 light9/web/drawing.ts --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/light9/web/drawing.ts Fri Jun 03 02:19:47 2022 -0700 @@ -0,0 +1,64 @@ + +export function svgPathFromPoints(pts: { forEach: (arg0: (p: any) => void) => void }) { + let out = ""; + pts.forEach(function (p: Number[] | { elements: Number[] }) { + let x, y; + if ((p as any).elements) { + // for vec2 + [x, y] = (p as any).elements; + } else { + [x, y] = p as Number[]; + } + if (out.length === 0) { + out = "M "; + } else { + out += "L "; + } + out += "" + x + "," + y + " "; + }); + return out; +}; + +export function line( + ctx: { moveTo: (arg0: any, arg1: any) => void; lineTo: (arg0: any, arg1: any) => any }, + p1: { e: (arg0: number) => any }, + p2: { e: (arg0: number) => any } +) { + ctx.moveTo(p1.e(1), p1.e(2)); + return ctx.lineTo(p2.e(1), p2.e(2)); +}; + +// http://stackoverflow.com/a/4959890 +export function roundRect( + ctx: { + beginPath: () => void; + moveTo: (arg0: any, arg1: any) => void; + lineTo: (arg0: number, arg1: number) => void; + arc: (arg0: number, arg1: number, arg2: any, arg3: number, arg4: number, arg5: boolean) => void; + closePath: () => any; + }, + sx: number, + sy: number, + ex: number, + ey: number, + r: number +) { + const d2r = Math.PI / 180; + if (ex - sx - 2 * r < 0) { + r = (ex - sx) / 2; + } // ensure that the radius isn't too large for x + if (ey - sy - 2 * r < 0) { + r = (ey - sy) / 2; + } // ensure that the radius isn't too large for y + ctx.beginPath(); + ctx.moveTo(sx + r, sy); + ctx.lineTo(ex - r, sy); + ctx.arc(ex - r, sy + r, r, d2r * 270, d2r * 360, false); + ctx.lineTo(ex, ey - r); + ctx.arc(ex - r, ey - r, r, d2r * 0, d2r * 90, false); + ctx.lineTo(sx + r, ey); + ctx.arc(sx + r, ey - r, r, d2r * 90, d2r * 180, false); + ctx.lineTo(sx, sy + r); + ctx.arc(sx + r, sy + r, r, d2r * 180, d2r * 270, false); + return ctx.closePath(); +}; diff -r 1dc96b97a544 -r e2ed5ce36253 light9/web/light9-timeline-audio.ts --- a/light9/web/light9-timeline-audio.ts Fri Jun 03 00:41:13 2022 -0700 +++ b/light9/web/light9-timeline-audio.ts Fri Jun 03 02:19:47 2022 -0700 @@ -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 @@ img { height: 100%; position: relative; - transition: left .1s linear; - + transition: left 0.1s linear; }
diff -r 1dc96b97a544 -r e2ed5ce36253 light9/web/timeline/adjusters.ts --- a/light9/web/timeline/adjusters.ts Fri Jun 03 00:41:13 2022 -0700 +++ b/light9/web/timeline/adjusters.ts Fri Jun 03 02:19:47 2022 -0700 @@ -4,7 +4,7 @@ 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"); diff -r 1dc96b97a544 -r e2ed5ce36253 light9/web/timeline/cursor_canvas.coffee --- a/light9/web/timeline/cursor_canvas.coffee Fri Jun 03 00:41:13 2022 -0700 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,69 +0,0 @@ -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(@)) - - onViewState: -> - ko.computed(@redrawCursor.bind(@)) - - onResize: (ev) -> - @$.canvas.width = @offsetWidth - @$.canvas.height = @offsetHeight - @redrawCursor() - - 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]) - } - @redraw() - - redraw: -> - return unless @ctx - @ctx.clearRect(0, 0, @$.canvas.width, @$.canvas.height) - - @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() - - if @cursorPath - @ctx.strokeStyle = '#ff0303' - @ctx.lineWidth = 1.5 - @ctx.beginPath() - Drawing.line(@ctx, @cursorPath.top0, @cursorPath.top1) - @ctx.stroke() - - @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 diff -r 1dc96b97a544 -r e2ed5ce36253 light9/web/timeline/drawing.ts --- a/light9/web/timeline/drawing.ts Fri Jun 03 00:41:13 2022 -0700 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,64 +0,0 @@ - -export function svgPathFromPoints(pts: { forEach: (arg0: (p: any) => void) => void }) { - let out = ""; - pts.forEach(function (p: Number[] | { elements: Number[] }) { - let x, y; - if ((p as any).elements) { - // for vec2 - [x, y] = (p as any).elements; - } else { - [x, y] = p as Number[]; - } - if (out.length === 0) { - out = "M "; - } else { - out += "L "; - } - out += "" + x + "," + y + " "; - }); - return out; -}; - -export function line( - ctx: { moveTo: (arg0: any, arg1: any) => void; lineTo: (arg0: any, arg1: any) => any }, - p1: { e: (arg0: number) => any }, - p2: { e: (arg0: number) => any } -) { - ctx.moveTo(p1.e(1), p1.e(2)); - return ctx.lineTo(p2.e(1), p2.e(2)); -}; - -// http://stackoverflow.com/a/4959890 -export function roundRect( - ctx: { - beginPath: () => void; - moveTo: (arg0: any, arg1: any) => void; - lineTo: (arg0: number, arg1: number) => void; - arc: (arg0: number, arg1: number, arg2: any, arg3: number, arg4: number, arg5: boolean) => void; - closePath: () => any; - }, - sx: number, - sy: number, - ex: number, - ey: number, - r: number -) { - const d2r = Math.PI / 180; - if (ex - sx - 2 * r < 0) { - r = (ex - sx) / 2; - } // ensure that the radius isn't too large for x - if (ey - sy - 2 * r < 0) { - r = (ey - sy) / 2; - } // ensure that the radius isn't too large for y - ctx.beginPath(); - ctx.moveTo(sx + r, sy); - ctx.lineTo(ex - r, sy); - ctx.arc(ex - r, sy + r, r, d2r * 270, d2r * 360, false); - ctx.lineTo(ex, ey - r); - ctx.arc(ex - r, ey - r, r, d2r * 0, d2r * 90, false); - ctx.lineTo(sx + r, ey); - ctx.arc(sx + r, ey - r, r, d2r * 90, d2r * 180, false); - ctx.lineTo(sx, sy + r); - ctx.arc(sx + r, sy + r, r, d2r * 180, d2r * 270, false); - return ctx.closePath(); -};