diff web/timeline/adjustable.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/adjustable.ts@d991f7c3485a
children
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/web/timeline/adjustable.ts	Sun May 12 19:02:10 2024 -0700
@@ -0,0 +1,269 @@
+import * as d3 from "d3";
+import { debug } from "debug";
+import * as ko from "knockout";
+const log = debug("adjustable");
+
+interface Config {
+  //   getTarget -> vec2 of current target position
+  getTarget: () => Vector;
+  //   getSuggestedTargetOffset -> vec2 pixel offset from target
+  getSuggestedTargetOffset: () => Vector;
+  //   emptyBox -> true if you want no value display
+  emptyBox: boolean;
+}
+
+export class Adjustable {
+  config: any;
+  handle: any;
+  initialTarget: any;
+  targetDraggedTo: any;
+  root: any;
+  // Some value you can edit in the UI, probably by dragging
+  // stuff. Drawn by light9-adjusters-canvas. This object does the
+  // layout and positioning.
+  //
+  // The way dragging should work is that you start in the yellow *adj
+  // widget*, wherever it is, but your drag is moving the *target*. The
+  // adj will travel around too, but it may do extra moves to not bump
+  // into stuff or to get out from under your finger.
+
+  constructor(config: any) {
+    this.config = config;
+    this.ctor2();
+  }
+
+  ctor2() {
+    // updated later by layout algoritm
+    return (this.handle = $V([0, 0]));
+  }
+
+  getDisplayValue() {
+    if (this.config.emptyBox) {
+      return "";
+    }
+    const defaultFormat = d3.format(".4g")(this._getValue());
+    if (this.config.getDisplayValue != null) {
+      return this.config.getDisplayValue(this._getValue(), defaultFormat);
+    }
+    return defaultFormat;
+  }
+  _getValue(): any {
+    throw new Error("Method not implemented.");
+  }
+
+  getSuggestedHandle() {
+    return this.getTarget().add(this.config.getSuggestedTargetOffset());
+  }
+
+  getHandle() {
+    // vec2 of pixels
+    return this.handle;
+  }
+
+  getTarget() {
+    // vec2 of pixels
+    return this.config.getTarget();
+  }
+
+  subscribe(onChange: any) {
+    // change could be displayValue or center or target. This likely
+    // calls onChange right away if there's any data yet.
+    throw new Error("not implemented");
+  }
+
+  startDrag() {
+    return (this.initialTarget = this.getTarget());
+  }
+
+  continueDrag(pos: { add: (arg0: any) => any }) {
+    //# pos is vec2 of pixels relative to the drag start
+    return (this.targetDraggedTo = pos.add(this.initialTarget));
+  }
+
+  endDrag() {}
+  // override
+
+  _editorCoordinates() {
+    // vec2 of mouse relative to <l9-t-editor>
+    let rootElem: { getBoundingClientRect: () => any };
+    return this.targetDraggedTo;
+    // let ev = d3.event.sourceEvent;
+
+    // if (ev.target.tagName === "LIGHT9-TIMELINE-EDITOR") {
+    //   rootElem = ev.target;
+    // } else {
+    //   rootElem = ev.target.closest("light9-timeline-editor");
+    // }
+
+    // if (ev.touches != null ? ev.touches.length : undefined) {
+    //   ev = ev.touches[0];
+    // }
+
+    // // storing root on the object to remember it across calls in case
+    // // you drag outside the editor.
+    // if (rootElem) {
+    //   this.root = rootElem.getBoundingClientRect();
+    // }
+    // const offsetParentPos = $V([ev.pageX - this.root.left, ev.pageY - this.root.top]);
+
+    // return offsetParentPos;
+  }
+}
+
+class AdjustableFloatObservable extends Adjustable {
+  constructor(config: any) {
+    // config also has:
+    //   observable -> ko.observable we will read and write
+    //   getValueForPos(pos) -> what should we set to if the user
+    //                          moves target to this coord?
+    this.config = config;
+    super();
+    this.ctor2();
+  }
+
+  _getValue() {
+    return this.config.observable();
+  }
+
+  continueDrag(pos: any) {
+    // pos is vec2 of pixels relative to the drag start.
+    super.continueDrag(pos);
+    const epos = this._editorCoordinates();
+    const newValue = this.config.getValueForPos(epos);
+    return this.config.observable(newValue);
+  }
+
+  subscribe(onChange: () => any) {
+    log("AdjustableFloatObservable subscribe", this.config);
+    return ko.computed(() => {
+      this.config.observable();
+      return onChange();
+    });
+  }
+}
+
+class AdjustableFloatObject extends Adjustable {
+  _currentValue: any;
+  _onChange: any;
+  constructor(config: any) {
+    // config also has:
+    //   graph
+    //   subj
+    //   pred
+    //   ctx
+    //   getTargetPosForValue(value) -> getTarget result for value
+    //   getValueForPos
+    this.config = config;
+    super();
+    this.ctor2();
+    if (this.config.ctx == null) {
+      throw new Error("missing ctx");
+    }
+    // this seems to not fire enough.
+    this.config.graph.runHandler(this._syncValue.bind(this), `adj sync ${this.config.subj.value} ${this.config.pred.value}`);
+  }
+
+  _syncValue() {
+    this._currentValue = this.config.graph.floatValue(this.config.subj, this.config.pred);
+    if (this._onChange) {
+      return this._onChange();
+    }
+  }
+
+  _getValue() {
+    // this is a big speedup- callers use _getValue about 4x as much as
+    // the graph changes and graph.floatValue is slow
+    return this._currentValue;
+  }
+
+  getTarget() {
+    return this.config.getTargetPosForValue(this._getValue());
+  }
+
+  subscribe(onChange: any) {
+    // only works on one subscription at a time
+    if (this._onChange) {
+      throw new Error("multi subscribe not implemented");
+    }
+    return (this._onChange = onChange);
+  }
+
+  continueDrag(pos: any) {
+    // pos is vec2 of pixels relative to the drag start
+    super.continueDrag(pos);
+    const newValue = this.config.getValueForPos(this._editorCoordinates());
+
+    return this.config.graph.patchObject(this.config.subj, this.config.pred, this.config.graph.LiteralRoundedFloat(newValue), this.config.ctx);
+    //@_syncValue()
+  }
+}
+
+class AdjustableFade extends Adjustable {
+  yForV: any;
+  zoomInX: any;
+  i0: any;
+  i1: any;
+  note: any;
+  constructor(yForV: any, zoomInX: any, i0: any, i1: any, note: any, offset: any, ctx: any) {
+    this.yForV = yForV;
+    this.zoomInX = zoomInX;
+    this.i0 = i0;
+    this.i1 = i1;
+    this.note = note;
+    super();
+    this.config = {
+      getSuggestedTargetOffset() {
+        return offset;
+      },
+      getTarget: this.getTarget.bind(this),
+      ctx,
+    };
+    this.ctor2();
+  }
+
+  getTarget() {
+    const mid = this.note.midPoint(this.i0, this.i1);
+    return $V([this.zoomInX(mid.e(1)), this.yForV(mid.e(2))]);
+  }
+
+  _getValue() {
+    return this.note.midPoint(this.i0, this.i1).e(1);
+  }
+
+  continueDrag(pos: { e: (arg0: number) => any }) {
+    // pos is vec2 of pixels relative to the drag start
+    super.continueDrag(pos);
+    const { graph } = this.note;
+    const U = (x: string) => graph.Uri(x);
+
+    const goalCenterSec = this.zoomInX.invert(this.initialTarget.e(1) + pos.e(1));
+
+    const diamSec = this.note.worldPts[this.i1].e(1) - this.note.worldPts[this.i0].e(1);
+    const newSec0 = goalCenterSec - diamSec / 2;
+    const newSec1 = goalCenterSec + diamSec / 2;
+
+    const originSec = graph.floatValue(this.note.uri, U(":originTime"));
+
+    const p0 = this._makePatch(graph, this.i0, newSec0, originSec, this.config.ctx);
+    const p1 = this._makePatch(graph, this.i1, newSec1, originSec, this.config.ctx);
+
+    return graph.applyAndSendPatch(this._addPatches(p0, p1));
+  }
+
+  _makePatch(
+    graph: { getObjectPatch: (arg0: any, arg1: any, arg2: any, arg3: any) => any; Uri: (arg0: string) => any; LiteralRoundedFloat: (arg0: number) => any },
+    idx: string | number,
+    newSec: number,
+    originSec: number,
+    ctx: any
+  ) {
+    return graph.getObjectPatch(this.note.worldPts[idx].uri, graph.Uri(":time"), graph.LiteralRoundedFloat(newSec - originSec), ctx);
+  }
+
+  _addPatches(p0: { addQuads: { concat: (arg0: any) => any }; delQuads: { concat: (arg0: any) => any } }, p1: { addQuads: any; delQuads: any }) {
+    return {
+      addQuads: p0.addQuads.concat(p1.addQuads),
+      delQuads: p0.delQuads.concat(p1.delQuads),
+    };
+  }
+}