CurveEditor.hx 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674
  1. package hide.comp;
  2. typedef CurveKey = hrt.prefab.Curve.CurveKey;
  3. class CurveEditor extends Component {
  4. public var xScale = 200.;
  5. public var yScale = 30.;
  6. public var xOffset = 0.;
  7. public var yOffset = 0.;
  8. public var curve(default, set) : hrt.prefab.Curve;
  9. public var undo : hide.ui.UndoHistory;
  10. public var lockViewX = false;
  11. public var lockViewY = false;
  12. public var lockKeyX = false;
  13. public var maxLength = 0.0;
  14. var svg : hide.comp.SVG;
  15. var width = 0;
  16. var height = 0;
  17. var gridGroup : Element;
  18. var graphGroup : Element;
  19. var selectGroup : Element;
  20. var refreshTimer : haxe.Timer = null;
  21. var lastValue : Dynamic;
  22. var selectedKeys: Array<CurveKey> = [];
  23. public function new(undo, ?parent) {
  24. super(parent,null);
  25. this.undo = undo;
  26. element.addClass("hide-curve-editor");
  27. element.attr({ tabindex: "1" });
  28. element.css({ width: "100%", height: "100%" });
  29. svg = new hide.comp.SVG(element);
  30. var div = this.element;
  31. var root = svg.element;
  32. height = Math.round(svg.element.height());
  33. if(height == 0 && parent != null)
  34. height = Math.round(parent.height());
  35. width = Math.round(svg.element.width());
  36. gridGroup = svg.group(root, "grid");
  37. graphGroup = svg.group(root, "graph");
  38. selectGroup = svg.group(root, "selection-overlay");
  39. root.resize((e) -> refresh());
  40. root.addClass("hide-curve-editor");
  41. root.mousedown(function(e) {
  42. var offset = root.offset();
  43. var px = e.clientX - offset.left;
  44. var py = e.clientY - offset.top;
  45. e.preventDefault();
  46. e.stopPropagation();
  47. div.focus();
  48. if(e.which == 1) {
  49. if(e.ctrlKey) {
  50. addKey(ixt(px), iyt(py));
  51. }
  52. else {
  53. startSelectRect(px, py);
  54. }
  55. }
  56. else if(e.which == 2) {
  57. // Pan
  58. startPan(e);
  59. }
  60. });
  61. element.keydown(function(e) {
  62. if(e.key == "z") {
  63. zoomAll();
  64. refresh();
  65. }
  66. });
  67. root.contextmenu(function(e) {
  68. e.preventDefault();
  69. return false;
  70. });
  71. root.on("mousewheel", function(e : js.jquery.Event) {
  72. var step = (e:Dynamic).originalEvent.wheelDelta > 0 ? 1.0 : -1.0;
  73. var changed = false;
  74. if(e.shiftKey) {
  75. if(!lockViewY) {
  76. yScale *= Math.pow(1.125, step);
  77. changed = true;
  78. }
  79. }
  80. else {
  81. if(!lockViewX) {
  82. xScale *= Math.pow(1.125, step);
  83. changed = true;
  84. }
  85. }
  86. if(changed) {
  87. e.preventDefault();
  88. e.stopPropagation();
  89. refresh();
  90. }
  91. });
  92. div.keydown(function(e) {
  93. if(curve == null) return;
  94. if(e.keyCode == 46) {
  95. beforeChange();
  96. var newVal = [for(k in curve.keys) if(selectedKeys.indexOf(k) < 0) k];
  97. curve.keys = newVal;
  98. selectedKeys = [];
  99. e.preventDefault();
  100. e.stopPropagation();
  101. afterChange();
  102. }
  103. if(e.key == "z") {
  104. zoomAll();
  105. }
  106. });
  107. }
  108. public dynamic function onChange(anim: Bool) {
  109. }
  110. public dynamic function onKeyMove(key: CurveKey, prevTime: Float, prevVal: Float) {
  111. }
  112. function set_curve(curve: hrt.prefab.Curve) {
  113. this.curve = curve;
  114. lastValue = haxe.Json.parse(haxe.Json.stringify(curve.save()));
  115. var view = getDisplayState("view");
  116. if(view != null) {
  117. if(!lockViewX) {
  118. xOffset = view.xOffset;
  119. xScale = view.xScale;
  120. }
  121. if(!lockViewY) {
  122. yOffset = view.yOffset;
  123. yScale = view.yScale;
  124. }
  125. }
  126. else {
  127. zoomAll();
  128. }
  129. refresh();
  130. return curve;
  131. }
  132. function addKey(time: Float, ?val: Float) {
  133. beforeChange();
  134. if(curve.clampMin != curve.clampMax)
  135. val = hxd.Math.clamp(val, curve.clampMin, curve.clampMax);
  136. curve.addKey(time, val, curve.keyMode);
  137. afterChange();
  138. }
  139. function fixKey(key : CurveKey) {
  140. var index = curve.keys.indexOf(key);
  141. var prev = curve.keys[index-1];
  142. var next = curve.keys[index+1];
  143. inline function addPrevH() {
  144. if(key.prevHandle == null)
  145. key.prevHandle = new hrt.prefab.Curve.CurveHandle(prev != null ? (prev.time - key.time) / 3 : -0.5, 0);
  146. }
  147. inline function addNextH() {
  148. if(key.nextHandle == null)
  149. key.nextHandle = new hrt.prefab.Curve.CurveHandle(next != null ? (next.time - key.time) / 3 : -0.5, 0);
  150. }
  151. switch(key.mode) {
  152. case Aligned:
  153. addPrevH();
  154. addNextH();
  155. var pa = hxd.Math.atan2(key.prevHandle.dv, key.prevHandle.dt);
  156. var na = hxd.Math.atan2(key.nextHandle.dv, key.nextHandle.dt);
  157. if(hxd.Math.abs(hxd.Math.angle(pa - na)) < Math.PI - (1./180.)) {
  158. key.nextHandle.dt = -key.prevHandle.dt;
  159. key.nextHandle.dv = -key.prevHandle.dv;
  160. }
  161. case Free:
  162. addPrevH();
  163. addNextH();
  164. case Linear:
  165. key.nextHandle = null;
  166. key.prevHandle = null;
  167. case Constant:
  168. key.nextHandle = null;
  169. key.prevHandle = null;
  170. }
  171. if(key.time < 0)
  172. key.time = 0;
  173. if(maxLength > 0 && key.time > maxLength)
  174. key.time = maxLength;
  175. if(prev != null && key.time < prev.time)
  176. key.time = prev.time + 0.01;
  177. if(next != null && key.time > next.time)
  178. key.time = next.time - 0.01;
  179. if(curve.clampMin != curve.clampMax) {
  180. key.value = hxd.Math.clamp(key.value, curve.clampMin, curve.clampMax);
  181. }
  182. if(false) {
  183. // TODO: This sorta works but is annoying.
  184. // Doesn't yet prevent backwards handles
  185. if(next != null && key.nextHandle != null) {
  186. var slope = key.nextHandle.dv / key.nextHandle.dt;
  187. slope = hxd.Math.clamp(slope, -1000, 1000);
  188. if(key.nextHandle.dt + key.time > next.time) {
  189. key.nextHandle.dt = next.time - key.time;
  190. key.nextHandle.dv = slope * key.nextHandle.dt;
  191. }
  192. }
  193. if(prev != null && key.prevHandle != null) {
  194. var slope = key.prevHandle.dv / key.prevHandle.dt;
  195. slope = hxd.Math.clamp(slope, -1000, 1000);
  196. if(key.prevHandle.dt + key.time < prev.time) {
  197. key.prevHandle.dt = prev.time - key.time;
  198. key.prevHandle.dv = slope * key.prevHandle.dt;
  199. }
  200. }
  201. }
  202. }
  203. function startSelectRect(p1x: Float, p1y: Float) {
  204. var offset = element.offset();
  205. var selX = p1x;
  206. var selY = p1y;
  207. var selW = 0.;
  208. var selH = 0.;
  209. startDrag(function(e) {
  210. var p2x = e.clientX - offset.left;
  211. var p2y = e.clientY - offset.top;
  212. selX = hxd.Math.min(p1x, p2x);
  213. selY = hxd.Math.min(p1y, p2y);
  214. selW = hxd.Math.abs(p2x-p1x);
  215. selH = hxd.Math.abs(p2y-p1y);
  216. selectGroup.empty();
  217. svg.rect(selectGroup, selX, selY, selW, selH);
  218. }, function(e) {
  219. selectGroup.empty();
  220. var minT = ixt(selX);
  221. var minV = iyt(selY + selH);
  222. var maxT = ixt(selX + selW);
  223. var maxV = iyt(selY);
  224. selectedKeys = [for(key in curve.keys)
  225. if(key.time >= minT && key.time <= maxT && key.value >= minV && key.value <= maxV) key];
  226. refreshGraph();
  227. });
  228. }
  229. function saveView() {
  230. saveDisplayState("view", {
  231. xOffset: xOffset,
  232. yOffset: yOffset,
  233. xScale: xScale,
  234. yScale: yScale
  235. });
  236. }
  237. function startPan(e) {
  238. var lastX = e.clientX;
  239. var lastY = e.clientY;
  240. startDrag(function(e) {
  241. var dt = (e.clientX - lastX) / xScale;
  242. var dv = (e.clientY - lastY) / yScale;
  243. if(!lockViewX)
  244. xOffset -= dt;
  245. if(!lockViewY)
  246. yOffset += dv;
  247. lastX = e.clientX;
  248. lastY = e.clientY;
  249. setPan(xOffset, yOffset);
  250. }, function(e) {
  251. saveView();
  252. });
  253. }
  254. public function setPan(xoff, yoff) {
  255. xOffset = xoff;
  256. yOffset = yoff;
  257. refreshGrid();
  258. graphGroup.attr({transform: 'translate(${xt(0)},${yt(0)})'});
  259. }
  260. public function setYZoom(yMin: Float, yMax: Float) {
  261. var margin = 20;
  262. yScale = (height - margin*2) / (yMax - yMin);
  263. yOffset = (yMax + yMin) / 2.0;
  264. }
  265. public function setXZoom(xMin: Float, xMax: Float) {
  266. var margin = 20;
  267. xScale = (width - margin*2) / (xMax - xMin);
  268. xOffset = xMin;
  269. }
  270. public function zoomAll() {
  271. var bounds = curve.getBounds();
  272. if(bounds.width <= 0) {
  273. bounds.xMin = 0.0;
  274. bounds.xMax = 1.0;
  275. }
  276. if(bounds.height <= 0) {
  277. if(curve.clampMax != curve.clampMin) {
  278. bounds.yMin = curve.clampMin;
  279. bounds.yMax = curve.clampMax;
  280. }
  281. else {
  282. bounds.yMin = -1.0;
  283. bounds.yMax = 1.0;
  284. }
  285. }
  286. if(!lockViewY) {
  287. setYZoom(bounds.yMin, bounds.yMax);
  288. }
  289. if(!lockViewX) {
  290. setYZoom(bounds.xMax, bounds.xMax);
  291. }
  292. saveView();
  293. }
  294. inline function xt(x: Float) return Math.round((x - xOffset) * xScale);
  295. inline function yt(y: Float) return Math.round((-y + yOffset) * yScale + height/2);
  296. inline function ixt(px: Float) return px / xScale + xOffset;
  297. inline function iyt(py: Float) return -(py - height/2) / yScale + yOffset;
  298. function startDrag(onMove: js.jquery.Event->Void, onStop: js.jquery.Event->Void) {
  299. var el = new Element(element[0].ownerDocument.body);
  300. el.on("mousemove.curveeditor", onMove);
  301. el.on("mouseup.curveeditor", function(e: js.jquery.Event) {
  302. el.off("mousemove.curveeditor");
  303. el.off("mouseup.curveeditor");
  304. e.preventDefault();
  305. e.stopPropagation();
  306. onStop(e);
  307. });
  308. }
  309. function copyKey(key: CurveKey): CurveKey {
  310. return cast haxe.Json.parse(haxe.Json.stringify(key));
  311. }
  312. function beforeChange() {
  313. lastValue = haxe.Json.parse(haxe.Json.stringify(curve.save()));
  314. }
  315. function afterChange() {
  316. var newVal = haxe.Json.parse(haxe.Json.stringify(curve.save()));
  317. var oldVal = lastValue;
  318. undo.change(Custom(function(undo) {
  319. if(undo) {
  320. curve.load(oldVal);
  321. }
  322. else {
  323. curve.load(newVal);
  324. }
  325. lastValue = haxe.Json.parse(haxe.Json.stringify(curve.save()));
  326. selectedKeys = [];
  327. refresh();
  328. onChange(false);
  329. }));
  330. refresh();
  331. onChange(false);
  332. }
  333. public function refresh(?anim: Bool) {
  334. refreshGrid();
  335. refreshGraph(anim);
  336. if(!anim)
  337. saveView();
  338. }
  339. public function refreshGrid() {
  340. width = Math.round(svg.element.width());
  341. height = Math.round(svg.element.height());
  342. gridGroup.empty();
  343. var minX = Math.floor(ixt(0));
  344. var maxX = Math.ceil(ixt(width));
  345. var hgrid = svg.group(gridGroup, "hgrid");
  346. for(ix in minX...(maxX+1)) {
  347. var l = svg.line(hgrid, xt(ix), 0, xt(ix), height).attr({
  348. "shape-rendering": "crispEdges"
  349. });
  350. if(ix == 0)
  351. l.addClass("axis");
  352. }
  353. var minY = Math.floor(iyt(height));
  354. var maxY = Math.ceil(iyt(0));
  355. var vgrid = svg.group(gridGroup, "vgrid");
  356. var vstep = 0.1;
  357. while((maxY - minY) / vstep > 20)
  358. vstep *= 10;
  359. inline function hline(iy) {
  360. return svg.line(vgrid, 0, yt(iy), width, yt(iy)).attr({
  361. "shape-rendering": "crispEdges"
  362. });
  363. }
  364. inline function hlabel(str, iy) {
  365. svg.text(vgrid, 1, yt(iy), str);
  366. }
  367. var minS = Math.floor(minY / vstep);
  368. var maxS = Math.ceil(maxY / vstep);
  369. for(i in minS...(maxS+1)) {
  370. var iy = i * vstep;
  371. var l = hline(iy);
  372. if(iy == 0)
  373. l.addClass("axis");
  374. hlabel("" + hxd.Math.fmt(iy), iy);
  375. }
  376. if(maxLength > 0)
  377. svg.rect(gridGroup, xt(maxLength), 0, width - xt(maxLength), height, { opacity: 0.4});
  378. }
  379. public function refreshGraph(?anim: Bool = false, ?animKey: CurveKey) {
  380. if(curve == null)
  381. return;
  382. graphGroup.empty();
  383. var graphOffX = xt(0);
  384. var graphOffY = yt(0);
  385. graphGroup.attr({transform: 'translate($graphOffX, $graphOffY)'});
  386. var curveGroup = svg.group(graphGroup, "curve");
  387. var vectorsGroup = svg.group(graphGroup, "vectors");
  388. var handlesGroup = svg.group(graphGroup, "handles");
  389. var tangentsHandles = svg.group(handlesGroup, "tangents");
  390. var keyHandles = svg.group(handlesGroup, "keys");
  391. var selection = svg.group(graphGroup, "selection");
  392. var size = 7;
  393. // Draw curve
  394. if(curve.keys.length > 0) {
  395. var keys = curve.keys;
  396. var lines = ['M ${xScale*(keys[0].time)},${-yScale*(keys[0].value)}'];
  397. for(ik in 1...keys.length) {
  398. var prev = keys[ik-1];
  399. var cur = keys[ik];
  400. if(prev.mode == Constant) {
  401. lines.push('L ${xScale*(prev.time)} ${-yScale*(prev.value)}
  402. L ${xScale*(cur.time)} ${-yScale*(prev.value)}
  403. L ${xScale*(cur.time)} ${-yScale*(cur.value)}');
  404. }
  405. else {
  406. lines.push('C
  407. ${xScale*(prev.time + (prev.nextHandle != null ? prev.nextHandle.dt : 0.))},${-yScale*(prev.value + (prev.nextHandle != null ? prev.nextHandle.dv : 0.))}
  408. ${xScale*(cur.time + (cur.prevHandle != null ? cur.prevHandle.dt : 0.))}, ${-yScale*(cur.value + (cur.prevHandle != null ? cur.prevHandle.dv : 0.))}
  409. ${xScale*(cur.time)}, ${-yScale*(cur.value)} ');
  410. }
  411. }
  412. svg.make(curveGroup, "path", {d: lines.join("")});
  413. // var pts = curve.sample(200);
  414. // var poly = [];
  415. // for(i in 0...pts.length) {
  416. // var x = xScale * (curve.duration * i / (pts.length - 1));
  417. // var y = yScale * (pts[i]);
  418. // poly.push(new h2d.col.Point(x, y));
  419. // }
  420. // svg.polygon(curveGroup, poly);
  421. }
  422. function addRect(group, x: Float, y: Float) {
  423. return svg.rect(group, x - Math.floor(size/2), y - Math.floor(size/2), size, size).attr({
  424. "shape-rendering": "crispEdges"
  425. });
  426. }
  427. function editPopup(key: CurveKey, top: Float, left: Float) {
  428. var popup = new Element('<div class="keyPopup">
  429. <div class="line"><label>Time</label><input class="x" type="number" value="0" step="0.1"/></div>
  430. <div class="line"><label>Value</label><input class="y" type="number" value="0" step="0.1"/></div>
  431. </div>').appendTo(element);
  432. popup.css({top: top, left: left});
  433. popup.focusout(function(e) {
  434. haxe.Timer.delay(function() {
  435. if(popup.find(':focus').length == 0)
  436. popup.remove();
  437. }, 0);
  438. });
  439. function afterEdit() {
  440. refreshGraph(false);
  441. onChange(false);
  442. }
  443. var xel = popup.find(".x");
  444. xel.val(hxd.Math.fmt(key.time));
  445. xel.change(function(e) {
  446. var f = Std.parseFloat(xel.val());
  447. if(f != null) {
  448. undo.change(Field(key, "time", key.time), afterEdit);
  449. key.time = f;
  450. afterEdit();
  451. }
  452. });
  453. var yel = popup.find(".y");
  454. yel.val(hxd.Math.fmt(key.value));
  455. yel.change(function(e) {
  456. var f = Std.parseFloat(yel.val());
  457. if(f != null) {
  458. undo.change(Field(key, "value", key.value), afterEdit);
  459. key.value = f;
  460. afterEdit();
  461. }
  462. });
  463. popup.find("input").first().focus();
  464. popup.focus();
  465. return popup;
  466. }
  467. for(key in curve.keys) {
  468. var kx = xScale*(key.time);
  469. var ky = -yScale*(key.value);
  470. var keyHandle = addRect(keyHandles, kx, ky);
  471. var selected = selectedKeys.indexOf(key) >= 0;
  472. if(selected)
  473. keyHandle.addClass("selected");
  474. if(!anim) {
  475. keyHandle.mousedown(function(e) {
  476. if(e.which != 1) return;
  477. e.preventDefault();
  478. e.stopPropagation();
  479. var offset = element.offset();
  480. beforeChange();
  481. var popup = editPopup(key, e.clientY - offset.top - 20, e.clientX - offset.left + 10);
  482. startDrag(function(e) {
  483. var lx = e.clientX - offset.left;
  484. var ly = e.clientY - offset.top;
  485. var nkx = ixt(lx);
  486. var nky = iyt(ly);
  487. var prevTime = key.time;
  488. var prevVal = key.value;
  489. key.time = nkx;
  490. key.value = nky;
  491. if(e.ctrlKey) {
  492. key.time = Math.round(key.time * 10) / 10.;
  493. key.value = Math.round(key.value * 10) / 10.;
  494. }
  495. if(lockKeyX)
  496. key.time = prevTime;
  497. popup.remove();
  498. fixKey(key);
  499. refreshGraph(true, key);
  500. onKeyMove(key, prevTime, prevVal);
  501. onChange(true);
  502. }, function(e) {
  503. selectedKeys = [key];
  504. fixKey(key);
  505. afterChange();
  506. });
  507. selectedKeys = [key];
  508. refreshGraph();
  509. });
  510. keyHandle.contextmenu(function(e) {
  511. e.preventDefault();
  512. function setMode(m: hrt.prefab.Curve.CurveKeyMode) {
  513. key.mode = m;
  514. curve.keyMode = m;
  515. fixKey(key);
  516. refreshGraph();
  517. }
  518. new ContextMenu([
  519. { label : "Mode", menu :[
  520. { label : "Aligned", checked: key.mode == Aligned, click : setMode.bind(Aligned) },
  521. { label : "Free", checked: key.mode == Free, click : setMode.bind(Free) },
  522. { label : "Linear", checked: key.mode == Linear, click : setMode.bind(Linear) },
  523. { label : "Constant", checked: key.mode == Constant, click : setMode.bind(Constant) },
  524. ] }
  525. ]);
  526. return false;
  527. });
  528. }
  529. function addHandle(next: Bool) {
  530. var handle = next ? key.nextHandle : key.prevHandle;
  531. var other = next ? key.prevHandle : key.nextHandle;
  532. if(handle == null) return null;
  533. var px = xScale*(key.time + handle.dt);
  534. var py = -yScale*(key.value + handle.dv);
  535. var line = svg.line(vectorsGroup, kx, ky, px, py);
  536. var circle = svg.circle(tangentsHandles, px, py, size/2);
  537. if(selected) {
  538. line.addClass("selected");
  539. circle.addClass("selected");
  540. }
  541. if(anim)
  542. return circle;
  543. circle.mousedown(function(e) {
  544. if(e.which != 1) return;
  545. e.preventDefault();
  546. e.stopPropagation();
  547. var offset = element.offset();
  548. var otherLen = hxd.Math.distance(other.dt * xScale, other.dv * yScale);
  549. beforeChange();
  550. startDrag(function(e) {
  551. var lx = e.clientX - offset.left;
  552. var ly = e.clientY - offset.top;
  553. var abskx = xt(key.time);
  554. var absky = yt(key.value);
  555. if(next && lx < abskx || !next && lx > abskx)
  556. lx = kx;
  557. var ndt = ixt(lx) - key.time;
  558. var ndv = iyt(ly) - key.value;
  559. handle.dt = ndt;
  560. handle.dv = ndv;
  561. if(key.mode == Aligned) {
  562. var angle = Math.atan2(absky - ly, lx - abskx);
  563. other.dt = Math.cos(angle + Math.PI) * otherLen / xScale;
  564. other.dv = Math.sin(angle + Math.PI) * otherLen / yScale;
  565. }
  566. fixKey(key);
  567. refreshGraph(true, key);
  568. onChange(true);
  569. }, function(e) {
  570. afterChange();
  571. });
  572. });
  573. return circle;
  574. }
  575. if(!anim || animKey == key) {
  576. var pHandle = addHandle(false);
  577. var nHandle = addHandle(true);
  578. }
  579. }
  580. if(selectedKeys.length > 1) {
  581. var bounds = new h2d.col.Bounds();
  582. for(key in selectedKeys)
  583. bounds.addPoint(new h2d.col.Point(xScale*(key.time), -yScale*(key.value)));
  584. var margin = 12.5;
  585. bounds.xMin -= margin;
  586. bounds.yMin -= margin;
  587. bounds.xMax += margin;
  588. bounds.yMax += margin;
  589. var rect = svg.rect(selection, bounds.x, bounds.y, bounds.width, bounds.height).attr({
  590. "shape-rendering": "crispEdges"
  591. });
  592. if(!anim) {
  593. beforeChange();
  594. rect.mousedown(function(e) {
  595. if(e.which != 1) return;
  596. e.preventDefault();
  597. e.stopPropagation();
  598. var lastX = e.clientX;
  599. var lastY = e.clientY;
  600. startDrag(function(e) {
  601. var dx = e.clientX - lastX;
  602. var dy = e.clientY - lastY;
  603. for(key in selectedKeys) {
  604. key.time += dx / xScale;
  605. key.value -= dy / yScale;
  606. }
  607. lastX = e.clientX;
  608. lastY = e.clientY;
  609. refreshGraph(true);
  610. onChange(true);
  611. }, function(e) {
  612. afterChange();
  613. });
  614. refreshGraph();
  615. });
  616. }
  617. }
  618. }
  619. }