Mercurial > code > home > repos > light9
diff web/timeline/viewstate.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/timeline/viewstate.ts@d991f7c3485a |
children | 06da5db2fafe |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/web/timeline/viewstate.ts Sun May 12 19:02:10 2024 -0700 @@ -0,0 +1,128 @@ +import * as ko from "knockout"; +import * as d3 from "d3"; +import debug from "debug"; + +const log = debug("viewstate"); +export class ViewState { + zoomSpec: { + duration: ko.Observable<number>; // current song duration + t1: ko.Observable<number>; + t2: ko.Observable<number>; + }; + cursor: { t: ko.Observable<number> }; + mouse: { pos: ko.Observable<Vector> }; + width: ko.Observable<number>; + coveredByDiagramTop: ko.Observable<number>; + audioY: ko.Observable<number>; + audioH: ko.Observable<number>; + zoomedTimeY: ko.Observable<number>; + zoomedTimeH: ko.Observable<number>; + rowsY: ko.Observable<number>; + fullZoomX: d3.ScaleLinear<number, number>; + zoomInX: d3.ScaleLinear<number, number>; + zoomAnimSec: number; + constructor() { + // caller updates all these observables + this.zoomSpec = { + duration: ko.observable(100), // current song duration + t1: ko.observable(0), + t2: ko.observable(100), + }; + this.cursor = { t: ko.observable(20) }; // songTime + this.mouse = { pos: ko.observable($V([0, 0])) }; + this.width = ko.observable(500); + this.coveredByDiagramTop = ko.observable(0); // page coords + // all these are relative to #coveredByDiagram: + this.audioY = ko.observable(0); + this.audioH = ko.observable(0); + this.zoomedTimeY = ko.observable(0); + this.zoomedTimeH = ko.observable(0); + this.rowsY = ko.observable(0); + + this.fullZoomX = d3.scaleLinear(); + this.zoomInX = d3.scaleLinear(); + + this.zoomAnimSec = 0.1; + + ko.computed(this.maintainZoomLimitsAndScales.bind(this)); + } + + setWidth(w: any) { + this.width(w); + this.maintainZoomLimitsAndScales(); // before other handlers run + } + + maintainZoomLimitsAndScales() { + // not for cursor updates + + if (this.zoomSpec.t1() < 0) { + this.zoomSpec.t1(0); + } + if (this.zoomSpec.duration() && this.zoomSpec.t2() > this.zoomSpec.duration()) { + this.zoomSpec.t2(this.zoomSpec.duration()); + } + + const rightPad = 5; // don't let time adjuster fall off right edge + this.fullZoomX.domain([0, this.zoomSpec.duration()]); + this.fullZoomX.range([0, this.width() - rightPad]); + + this.zoomInX.domain([this.zoomSpec.t1(), this.zoomSpec.t2()]); + this.zoomInX.range([0, this.width() - rightPad]); + } + + latestMouseTime(): number { + return this.zoomInX.invert(this.mouse.pos().e(1)); + } + + onMouseWheel(deltaY: any) { + const zs = this.zoomSpec; + + const center = this.latestMouseTime(); + const left = center - zs.t1(); + const right = zs.t2() - center; + const scale = Math.pow(1.005, deltaY); + + zs.t1(center - left * scale); + zs.t2(center + right * scale); + log("view to", ko.toJSON(this)); + } + + frameCursor() { + const zs = this.zoomSpec; + const visSeconds = zs.t2() - zs.t1(); + const margin = visSeconds * 0.4; + // buggy: really needs t1/t2 to limit their ranges + if (this.cursor.t() < zs.t1() || this.cursor.t() > zs.t2() - visSeconds * 0.6) { + const newCenter = this.cursor.t() + margin; + this.animatedZoom(newCenter - visSeconds / 2, newCenter + visSeconds / 2, this.zoomAnimSec); + } + } + frameToEnd() { + this.animatedZoom(this.cursor.t() - 2, this.zoomSpec.duration(), this.zoomAnimSec); + } + frameAll() { + this.animatedZoom(0, this.zoomSpec.duration(), this.zoomAnimSec); + } + animatedZoom(newT1: number, newT2: number, secs: number) { + const fps = 30; + const oldT1 = this.zoomSpec.t1(); + const oldT2 = this.zoomSpec.t2(); + let lastTime = 0; + for (let step = 0; step < secs * fps; step++) { + const frac = step / (secs * fps); + ((frac) => { + const gotoStep = () => { + this.zoomSpec.t1((1 - frac) * oldT1 + frac * newT1); + return this.zoomSpec.t2((1 - frac) * oldT2 + frac * newT2); + }; + const delay = frac * secs * 1000; + setTimeout(gotoStep, delay); + lastTime = delay; + })(frac); + } + setTimeout(() => { + this.zoomSpec.t1(newT1); + return this.zoomSpec.t2(newT2); + }, lastTime + 10); + } +}