Mercurial > code > home > repos > light9
annotate web/timeline/adjusters.ts @ 2435:207fe0670952
+ bin/rdf2dot
author | drewp@bigasterisk.com |
---|---|
date | Wed, 29 May 2024 14:56:58 -0700 |
parents | 4556eebe5d73 |
children |
rev | line source |
---|---|
2062 | 1 import { debug } from "debug"; |
2 import { LitElement } from "lit"; | |
3 import { customElement } from "lit/decorators.js"; | |
4 import { throttle } from "underscore"; | |
5 import * as d3 from "d3"; | |
6 import { Adjustable } from "./adjustable"; | |
2128
e2ed5ce36253
double spectrum views have a connected cursor
drewp@bigasterisk.com
parents:
2062
diff
changeset
|
7 import * as Drawing from "../drawing"; |
2062 | 8 // https://www.npmjs.com/package/@types/sylvester Global values: $L, $M, $P, $V, Line, Matrix, Plane, Sylvester, Vector |
9 const log = debug("adjusters"); | |
1716 | 10 |
2062 | 11 const maxDist = 60; |
1756
a73468c02bce
new adjuster layout algorithm
Drew Perttula <drewp@bigasterisk.com>
parents:
1755
diff
changeset
|
12 |
2062 | 13 interface Drag { |
14 start: Vector; | |
15 adj: Adjustable; | |
16 cur?: Vector; | |
17 } | |
18 type QTreeData = Vector & { adj: Adjustable }; | |
19 @customElement("light9-adjusters-canvas") | |
20 class AdjustersCanvas extends LitElement { | |
21 static getter_properties: { setAdjuster: { type: any; notify: boolean } }; | |
22 static getter_observers: {}; | |
23 redraw: any; | |
24 adjs: { [id: string | number]: Adjustable }; | |
25 hoveringNear: any; | |
26 ctx: any; | |
27 $: any; | |
28 setAdjuster: any; | |
29 offsetParent: any; | |
30 currentDrag?: Drag; | |
31 qt?: d3.Quadtree<QTreeData>; | |
32 canvasCenter: any; | |
33 static initClass() { | |
34 this.getter_properties = { setAdjuster: { type: Function, notify: true } }; | |
35 this.getter_observers = ["updateAllCoords(adjs)"]; | |
36 } | |
37 constructor() { | |
38 super(); | |
39 this.redraw = throttle(this._throttledRedraw.bind(this), 30, { leading: false }); | |
40 this.adjs = {}; | |
41 this.hoveringNear = null; | |
42 } | |
1737
849599175e99
adjusters start displaying again. just timeline zoom ones.
Drew Perttula <drewp@bigasterisk.com>
parents:
1736
diff
changeset
|
43 |
2062 | 44 ready() { |
45 this.addEventListener("iron-resize", this.resizeUpdate.bind(this)); | |
46 this.ctx = this.$.canvas.getContext("2d"); | |
47 | |
48 this.redraw(); | |
49 this.setAdjuster = this._setAdjuster.bind(this); | |
1766
5f9d22f9c85b
fix adjuster-drag coordinate bug. highlight nearby adj.
drewp@bigasterisk.com
parents:
1761
diff
changeset
|
50 |
2062 | 51 // These don't fire; TimelineEditor calls the handlers for us. |
52 this.addEventListener("mousedown", this.onDown.bind(this)); | |
53 this.addEventListener("mousemove", this.onMove.bind(this)); | |
54 return this.addEventListener("mouseup", this.onUp.bind(this)); | |
55 } | |
56 addEventListener(arg0: string, arg1: any) { | |
57 throw new Error("Method not implemented."); | |
58 } | |
59 | |
60 _mousePos(ev: MouseEvent) { | |
61 return $V([ev.clientX, ev.clientY - this.offsetParent.offsetTop]); | |
62 } | |
1716 | 63 |
2062 | 64 onDown(ev: MouseEvent) { |
65 if (ev.buttons === 1) { | |
66 const start = this._mousePos(ev); | |
67 const adj = this._adjAtPoint(start); | |
68 if (adj) { | |
69 ev.stopPropagation(); | |
70 this.currentDrag = { start, adj }; | |
71 return adj.startDrag(); | |
72 } | |
73 } | |
74 } | |
75 | |
76 onMove(ev: MouseEvent) { | |
77 const pos = this._mousePos(ev); | |
78 if (this.currentDrag) { | |
79 this.hoveringNear = null; | |
80 this.currentDrag.cur = pos; | |
81 this.currentDrag.adj.continueDrag(this.currentDrag.cur.subtract(this.currentDrag.start)); | |
82 this.redraw(); | |
83 } else { | |
84 const near = this._adjAtPoint(pos); | |
85 if (this.hoveringNear !== near) { | |
86 this.hoveringNear = near; | |
87 this.redraw(); | |
88 } | |
89 } | |
90 } | |
1716 | 91 |
2062 | 92 onUp(ev: any) { |
93 if (!this.currentDrag) { | |
94 return; | |
95 } | |
96 this.currentDrag.adj.endDrag(); | |
97 this.currentDrag = undefined; | |
98 } | |
1716 | 99 |
2062 | 100 _setAdjuster(adjId: string | number, makeAdjustable?: () => Adjustable) { |
101 // callers register/unregister the Adjustables they want us to make | |
102 // adjuster elements for. Caller invents adjId. makeAdjustable is | |
103 // a function returning the Adjustable or it is undefined to clear any | |
104 // adjusters with this id. | |
105 if (makeAdjustable == null) { | |
106 if (this.adjs[adjId]) { | |
107 delete this.adjs[adjId]; | |
108 } | |
109 } else { | |
110 // this might be able to reuse an existing one a bit | |
111 const adj = makeAdjustable(); | |
112 this.adjs[adjId] = adj; | |
113 adj.id = adjId; | |
114 } | |
1716 | 115 |
2062 | 116 this.redraw(); |
117 | |
118 (window as any).debug_adjsCount = Object.keys(this.adjs).length; | |
119 } | |
1716 | 120 |
2062 | 121 updateAllCoords() { |
122 this.redraw(); | |
123 } | |
124 | |
125 _adjAtPoint(pt: Vector): Adjustable|undefined { | |
126 const nearest = this.qt!.find(pt.e(1), pt.e(2)); | |
127 if (nearest == null || nearest.distanceFrom(pt) > maxDist) { | |
128 return undefined; | |
129 } | |
130 return nearest != null ? nearest.adj : undefined; | |
131 } | |
1716 | 132 |
2062 | 133 resizeUpdate(ev: { target: { offsetWidth: any; offsetHeight: any } }) { |
134 this.$.canvas.width = ev.target.offsetWidth; | |
135 this.$.canvas.height = ev.target.offsetHeight; | |
136 this.canvasCenter = $V([this.$.canvas.width / 2, this.$.canvas.height / 2]); | |
137 return this.redraw(); | |
138 } | |
1716 | 139 |
2062 | 140 _throttledRedraw() { |
141 if (this.ctx == null) { | |
142 return; | |
143 } | |
144 console.time("adjs redraw"); | |
145 this._layoutCenters(); | |
146 | |
147 this.ctx.clearRect(0, 0, this.$.canvas.width, this.$.canvas.height); | |
148 | |
149 for (let adjId in this.adjs) { | |
150 const adj = this.adjs[adjId]; | |
151 const ctr = adj.getHandle(); | |
152 const target = adj.getTarget(); | |
153 if (this._isOffScreen(target)) { | |
154 continue; | |
155 } | |
156 this._drawConnector(ctr, target); | |
1716 | 157 |
2062 | 158 this._drawAdjuster(adj.getDisplayValue(), ctr.e(1) - 20, ctr.e(2) - 10, ctr.e(1) + 20, ctr.e(2) + 10, adj === this.hoveringNear); |
159 } | |
160 return console.timeEnd("adjs redraw"); | |
161 } | |
1761
3ddd5b4964b3
fix time adjs crossing each other. more simulation steps for adjs.
drewp@bigasterisk.com
parents:
1756
diff
changeset
|
162 |
2062 | 163 _layoutCenters() { |
164 // push Adjustable centers around to avoid overlaps | |
165 // Todo: also don't overlap inlineattr boxes | |
166 // Todo: don't let their connector lines cross each other | |
167 const qt = d3.quadtree<QTreeData>( | |
168 [], | |
169 (d: QTreeData) => d.e(1), | |
170 (d: QTreeData) => d.e(2) | |
171 ); | |
172 this.qt = qt; | |
1716 | 173 |
2062 | 174 qt.extent([ |
175 [0, 0], | |
176 [8000, 8000], | |
177 ]); | |
178 | |
179 let _: string | number, adj: { handle: any; getSuggestedHandle: () => any }; | |
180 for (_ in this.adjs) { | |
181 adj = this.adjs[_]; | |
182 adj.handle = this._clampOnScreen(adj.getSuggestedHandle()); | |
183 } | |
1761
3ddd5b4964b3
fix time adjs crossing each other. more simulation steps for adjs.
drewp@bigasterisk.com
parents:
1756
diff
changeset
|
184 |
2062 | 185 const numTries = 8; |
186 for (let tryn = 0; tryn < numTries; tryn++) { | |
187 for (_ in this.adjs) { | |
188 adj = this.adjs[_]; | |
189 let current = adj.handle; | |
190 qt.remove(current); | |
191 const nearest = qt.find(current.e(1), current.e(2), maxDist); | |
192 if (nearest) { | |
193 const dist = current.distanceFrom(nearest); | |
194 if (dist < maxDist) { | |
195 current = this._stepAway(current, nearest, 1 / numTries); | |
196 adj.handle = current; | |
197 } | |
198 } | |
199 current.adj = adj; | |
200 qt.add(current); | |
201 } | |
202 } | |
203 //if -50 < output.e(1) < 20 # mostly for zoom-left | |
204 // output.setElements([ | |
205 // Math.max(20, output.e(1)), | |
206 // output.e(2)]) | |
207 } | |
1761
3ddd5b4964b3
fix time adjs crossing each other. more simulation steps for adjs.
drewp@bigasterisk.com
parents:
1756
diff
changeset
|
208 |
2062 | 209 |
210 _stepAway( | |
211 current: Vector, | |
212 nearest: Vector, | |
213 dx: number | |
214 ) { | |
215 const away = current.subtract(nearest).toUnitVector(); | |
216 const toScreenCenter = this.canvasCenter.subtract(current).toUnitVector(); | |
217 const goalSpacingPx = 20; | |
218 return this._clampOnScreen(current.add(away.x(goalSpacingPx * dx))); | |
219 } | |
220 | |
221 _isOffScreen(pos: Vector):boolean { | |
222 return pos.e(1) < 0 || pos.e(1) > this.$.canvas.width || pos.e(2) < 0 || pos.e(2) > this.$.canvas.height; | |
223 } | |
224 | |
225 _clampOnScreen(pos: Vector): Vector { | |
226 const marg = 30; | |
227 return $V([Math.max(marg, Math.min(this.$.canvas.width - marg, pos.e(1))), Math.max(marg, Math.min(this.$.canvas.height - marg, pos.e(2)))]); | |
228 } | |
1716 | 229 |
2062 | 230 _drawConnector(ctr: Vector, target: Vector) { |
231 this.ctx.strokeStyle = "#aaa"; | |
232 this.ctx.lineWidth = 2; | |
233 this.ctx.beginPath(); | |
234 Drawing.line(this.ctx, ctr, target); | |
235 this.ctx.stroke(); | |
236 } | |
237 | |
238 _drawAdjuster(label: any, x1: number, y1: number, x2: number, y2: number, hover: boolean) { | |
239 const radius = 8; | |
240 | |
241 this.ctx.shadowColor = "black"; | |
242 this.ctx.shadowBlur = 15; | |
243 this.ctx.shadowOffsetX = 5; | |
244 this.ctx.shadowOffsetY = 9; | |
245 | |
246 this.ctx.fillStyle = hover ? "#ffff88" : "rgba(255, 255, 0, 0.5)"; | |
247 this.ctx.beginPath(); | |
248 Drawing.roundRect(this.ctx, x1, y1, x2, y2, radius); | |
249 this.ctx.fill(); | |
1716 | 250 |
2062 | 251 this.ctx.shadowColor = "rgba(0,0,0,0)"; |
252 | |
253 this.ctx.strokeStyle = "yellow"; | |
254 this.ctx.lineWidth = 2; | |
255 this.ctx.setLineDash([3, 3]); | |
256 this.ctx.beginPath(); | |
257 Drawing.roundRect(this.ctx, x1, y1, x2, y2, radius); | |
258 this.ctx.stroke(); | |
259 this.ctx.setLineDash([]); | |
1716 | 260 |
2062 | 261 this.ctx.font = "12px sans"; |
262 this.ctx.fillStyle = "#000"; | |
263 this.ctx.fillText(label, x1 + 5, y2 - 5, x2 - x1 - 10); | |
1716 | 264 |
2062 | 265 // coords from a center that's passed in |
266 // # special layout for the thaeter ones with middinh | |
267 // l/r arrows | |
268 // mouse arrow cursor upon hover, and accent the hovered adjuster | |
269 // connector | |
270 } | |
271 } | |
272 | |
273 |