Files @ 361c612e3c60
Branch filter:

Location: light9/web/timeline/adjustable.ts - annotation

drewp@bigasterisk.com
checkpoint show data
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
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),
    };
  }
}