comparison 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
comparison
equal deleted inserted replaced
2375:623836db99af 2376:4556eebe5d73
1 import * as d3 from "d3";
2 import { debug } from "debug";
3 import * as ko from "knockout";
4 const log = debug("adjustable");
5
6 interface Config {
7 // getTarget -> vec2 of current target position
8 getTarget: () => Vector;
9 // getSuggestedTargetOffset -> vec2 pixel offset from target
10 getSuggestedTargetOffset: () => Vector;
11 // emptyBox -> true if you want no value display
12 emptyBox: boolean;
13 }
14
15 export class Adjustable {
16 config: any;
17 handle: any;
18 initialTarget: any;
19 targetDraggedTo: any;
20 root: any;
21 // Some value you can edit in the UI, probably by dragging
22 // stuff. Drawn by light9-adjusters-canvas. This object does the
23 // layout and positioning.
24 //
25 // The way dragging should work is that you start in the yellow *adj
26 // widget*, wherever it is, but your drag is moving the *target*. The
27 // adj will travel around too, but it may do extra moves to not bump
28 // into stuff or to get out from under your finger.
29
30 constructor(config: any) {
31 this.config = config;
32 this.ctor2();
33 }
34
35 ctor2() {
36 // updated later by layout algoritm
37 return (this.handle = $V([0, 0]));
38 }
39
40 getDisplayValue() {
41 if (this.config.emptyBox) {
42 return "";
43 }
44 const defaultFormat = d3.format(".4g")(this._getValue());
45 if (this.config.getDisplayValue != null) {
46 return this.config.getDisplayValue(this._getValue(), defaultFormat);
47 }
48 return defaultFormat;
49 }
50 _getValue(): any {
51 throw new Error("Method not implemented.");
52 }
53
54 getSuggestedHandle() {
55 return this.getTarget().add(this.config.getSuggestedTargetOffset());
56 }
57
58 getHandle() {
59 // vec2 of pixels
60 return this.handle;
61 }
62
63 getTarget() {
64 // vec2 of pixels
65 return this.config.getTarget();
66 }
67
68 subscribe(onChange: any) {
69 // change could be displayValue or center or target. This likely
70 // calls onChange right away if there's any data yet.
71 throw new Error("not implemented");
72 }
73
74 startDrag() {
75 return (this.initialTarget = this.getTarget());
76 }
77
78 continueDrag(pos: { add: (arg0: any) => any }) {
79 //# pos is vec2 of pixels relative to the drag start
80 return (this.targetDraggedTo = pos.add(this.initialTarget));
81 }
82
83 endDrag() {}
84 // override
85
86 _editorCoordinates() {
87 // vec2 of mouse relative to <l9-t-editor>
88 let rootElem: { getBoundingClientRect: () => any };
89 return this.targetDraggedTo;
90 // let ev = d3.event.sourceEvent;
91
92 // if (ev.target.tagName === "LIGHT9-TIMELINE-EDITOR") {
93 // rootElem = ev.target;
94 // } else {
95 // rootElem = ev.target.closest("light9-timeline-editor");
96 // }
97
98 // if (ev.touches != null ? ev.touches.length : undefined) {
99 // ev = ev.touches[0];
100 // }
101
102 // // storing root on the object to remember it across calls in case
103 // // you drag outside the editor.
104 // if (rootElem) {
105 // this.root = rootElem.getBoundingClientRect();
106 // }
107 // const offsetParentPos = $V([ev.pageX - this.root.left, ev.pageY - this.root.top]);
108
109 // return offsetParentPos;
110 }
111 }
112
113 class AdjustableFloatObservable extends Adjustable {
114 constructor(config: any) {
115 // config also has:
116 // observable -> ko.observable we will read and write
117 // getValueForPos(pos) -> what should we set to if the user
118 // moves target to this coord?
119 this.config = config;
120 super();
121 this.ctor2();
122 }
123
124 _getValue() {
125 return this.config.observable();
126 }
127
128 continueDrag(pos: any) {
129 // pos is vec2 of pixels relative to the drag start.
130 super.continueDrag(pos);
131 const epos = this._editorCoordinates();
132 const newValue = this.config.getValueForPos(epos);
133 return this.config.observable(newValue);
134 }
135
136 subscribe(onChange: () => any) {
137 log("AdjustableFloatObservable subscribe", this.config);
138 return ko.computed(() => {
139 this.config.observable();
140 return onChange();
141 });
142 }
143 }
144
145 class AdjustableFloatObject extends Adjustable {
146 _currentValue: any;
147 _onChange: any;
148 constructor(config: any) {
149 // config also has:
150 // graph
151 // subj
152 // pred
153 // ctx
154 // getTargetPosForValue(value) -> getTarget result for value
155 // getValueForPos
156 this.config = config;
157 super();
158 this.ctor2();
159 if (this.config.ctx == null) {
160 throw new Error("missing ctx");
161 }
162 // this seems to not fire enough.
163 this.config.graph.runHandler(this._syncValue.bind(this), `adj sync ${this.config.subj.value} ${this.config.pred.value}`);
164 }
165
166 _syncValue() {
167 this._currentValue = this.config.graph.floatValue(this.config.subj, this.config.pred);
168 if (this._onChange) {
169 return this._onChange();
170 }
171 }
172
173 _getValue() {
174 // this is a big speedup- callers use _getValue about 4x as much as
175 // the graph changes and graph.floatValue is slow
176 return this._currentValue;
177 }
178
179 getTarget() {
180 return this.config.getTargetPosForValue(this._getValue());
181 }
182
183 subscribe(onChange: any) {
184 // only works on one subscription at a time
185 if (this._onChange) {
186 throw new Error("multi subscribe not implemented");
187 }
188 return (this._onChange = onChange);
189 }
190
191 continueDrag(pos: any) {
192 // pos is vec2 of pixels relative to the drag start
193 super.continueDrag(pos);
194 const newValue = this.config.getValueForPos(this._editorCoordinates());
195
196 return this.config.graph.patchObject(this.config.subj, this.config.pred, this.config.graph.LiteralRoundedFloat(newValue), this.config.ctx);
197 //@_syncValue()
198 }
199 }
200
201 class AdjustableFade extends Adjustable {
202 yForV: any;
203 zoomInX: any;
204 i0: any;
205 i1: any;
206 note: any;
207 constructor(yForV: any, zoomInX: any, i0: any, i1: any, note: any, offset: any, ctx: any) {
208 this.yForV = yForV;
209 this.zoomInX = zoomInX;
210 this.i0 = i0;
211 this.i1 = i1;
212 this.note = note;
213 super();
214 this.config = {
215 getSuggestedTargetOffset() {
216 return offset;
217 },
218 getTarget: this.getTarget.bind(this),
219 ctx,
220 };
221 this.ctor2();
222 }
223
224 getTarget() {
225 const mid = this.note.midPoint(this.i0, this.i1);
226 return $V([this.zoomInX(mid.e(1)), this.yForV(mid.e(2))]);
227 }
228
229 _getValue() {
230 return this.note.midPoint(this.i0, this.i1).e(1);
231 }
232
233 continueDrag(pos: { e: (arg0: number) => any }) {
234 // pos is vec2 of pixels relative to the drag start
235 super.continueDrag(pos);
236 const { graph } = this.note;
237 const U = (x: string) => graph.Uri(x);
238
239 const goalCenterSec = this.zoomInX.invert(this.initialTarget.e(1) + pos.e(1));
240
241 const diamSec = this.note.worldPts[this.i1].e(1) - this.note.worldPts[this.i0].e(1);
242 const newSec0 = goalCenterSec - diamSec / 2;
243 const newSec1 = goalCenterSec + diamSec / 2;
244
245 const originSec = graph.floatValue(this.note.uri, U(":originTime"));
246
247 const p0 = this._makePatch(graph, this.i0, newSec0, originSec, this.config.ctx);
248 const p1 = this._makePatch(graph, this.i1, newSec1, originSec, this.config.ctx);
249
250 return graph.applyAndSendPatch(this._addPatches(p0, p1));
251 }
252
253 _makePatch(
254 graph: { getObjectPatch: (arg0: any, arg1: any, arg2: any, arg3: any) => any; Uri: (arg0: string) => any; LiteralRoundedFloat: (arg0: number) => any },
255 idx: string | number,
256 newSec: number,
257 originSec: number,
258 ctx: any
259 ) {
260 return graph.getObjectPatch(this.note.worldPts[idx].uri, graph.Uri(":time"), graph.LiteralRoundedFloat(newSec - originSec), ctx);
261 }
262
263 _addPatches(p0: { addQuads: { concat: (arg0: any) => any }; delQuads: { concat: (arg0: any) => any } }, p1: { addQuads: any; delQuads: any }) {
264 return {
265 addQuads: p0.addQuads.concat(p1.addQuads),
266 delQuads: p0.delQuads.concat(p1.delQuads),
267 };
268 }
269 }