You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

hand.js 15KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522
  1. /**
  2. * WHITEBOPHIR
  3. *********************************************************
  4. * @licstart The following is the entire license notice for the
  5. * JavaScript code in this page.
  6. *
  7. * Copyright (C) 2013 Ophir LOJKINE
  8. *
  9. *
  10. * The JavaScript code in this page is free software: you can
  11. * redistribute it and/or modify it under the terms of the GNU
  12. * General Public License (GNU GPL) as published by the Free Software
  13. * Foundation, either version 3 of the License, or (at your option)
  14. * any later version. The code is distributed WITHOUT ANY WARRANTY;
  15. * without even the implied warranty of MERCHANTABILITY or FITNESS
  16. * FOR A PARTICULAR PURPOSE. See the GNU GPL for more details.
  17. *
  18. * As additional permission under GNU GPL version 3 section 7, you
  19. * may distribute non-source (e.g., minimized or compacted) forms of
  20. * that code without the copy of the GNU GPL normally required by
  21. * section 4, provided you include this license notice and a URL
  22. * through which recipients can access the Corresponding Source.
  23. *
  24. * @licend
  25. */
  26. (function hand() { //Code isolation
  27. var selectorStates = {
  28. pointing: 0,
  29. selecting: 1,
  30. transform: 2
  31. }
  32. var selected = null;
  33. var selected_els = [];
  34. var selectionRect = createSelectorRect();
  35. var selectionRectTransform;
  36. var currentTransform = null;
  37. var transform_elements = [];
  38. var selectorState = selectorStates.pointing;
  39. var last_sent = 0;
  40. var blockedSelectionButtons = Tools.server_config.BLOCKED_SELECTION_BUTTONS;
  41. var selectionButtons = [
  42. createButton("delete", "delete", 24, 24,
  43. function (me, bbox, s) {
  44. me.width.baseVal.value = me.origWidth / s;
  45. me.height.baseVal.value = me.origHeight / s;
  46. me.x.baseVal.value = bbox.r[0];
  47. me.y.baseVal.value = bbox.r[1] - (me.origHeight + 3) / s;
  48. me.style.display = "";
  49. },
  50. deleteSelection),
  51. createButton("duplicate", "duplicate", 24, 24,
  52. function (me, bbox, s) {
  53. me.width.baseVal.value = me.origWidth / s;
  54. me.height.baseVal.value = me.origHeight / s;
  55. me.x.baseVal.value = bbox.r[0] + (me.origWidth + 2) / s;
  56. me.y.baseVal.value = bbox.r[1] - (me.origHeight + 3) / s;
  57. me.style.display = "";
  58. },
  59. duplicateSelection),
  60. createButton("scaleHandle", "handle", 14, 14,
  61. function (me, bbox, s) {
  62. me.width.baseVal.value = me.origWidth / s;
  63. me.height.baseVal.value = me.origHeight / s;
  64. me.x.baseVal.value = bbox.r[0] + bbox.a[0] - me.origWidth / (2 * s);
  65. me.y.baseVal.value = bbox.r[1] + bbox.b[1] - me.origHeight / (2 * s);
  66. me.style.display = "";
  67. },
  68. startScalingTransform)
  69. ];
  70. for (i in blockedSelectionButtons) {
  71. delete selectionButtons[blockedSelectionButtons[i]];
  72. }
  73. var getScale = Tools.getScale;
  74. function getParentMathematics(el) {
  75. var target;
  76. var a = el;
  77. var els = [];
  78. while (a) {
  79. els.unshift(a);
  80. a = a.parentElement;
  81. }
  82. var parentMathematics = els.find(function (el) {
  83. return el.getAttribute("class") === "MathElement";
  84. });
  85. if ((parentMathematics) && parentMathematics.tagName === "svg") {
  86. target = parentMathematics;
  87. }
  88. return target || el;
  89. }
  90. function deleteSelection() {
  91. var msgs = selected_els.map(function (el) {
  92. return ({
  93. "type": "delete",
  94. "id": el.id
  95. });
  96. });
  97. var data = {
  98. _children: msgs
  99. }
  100. Tools.drawAndSend(data);
  101. selected_els = [];
  102. hideSelectionUI();
  103. }
  104. function duplicateSelection() {
  105. if (!(selectorState == selectorStates.pointing)
  106. || (selected_els.length == 0)) return;
  107. var msgs = [];
  108. var newids = [];
  109. for (var i = 0; i < selected_els.length; i++) {
  110. var id = selected_els[i].id;
  111. msgs[i] = {
  112. type: "copy",
  113. id: id,
  114. newid: Tools.generateUID(id[0])
  115. };
  116. newids[i] = id;
  117. }
  118. Tools.drawAndSend({ _children: msgs });
  119. selected_els = newids.map(function (id) {
  120. return Tools.svg.getElementById(id);
  121. });
  122. }
  123. function createSelectorRect() {
  124. var shape = Tools.createSVGElement("rect");
  125. shape.id = "selectionRect";
  126. shape.x.baseVal.value = 0;
  127. shape.y.baseVal.value = 0;
  128. shape.width.baseVal.value = 0;
  129. shape.height.baseVal.value = 0;
  130. shape.setAttribute("stroke", "black");
  131. shape.setAttribute("stroke-width", 1);
  132. shape.setAttribute("vector-effect", "non-scaling-stroke");
  133. shape.setAttribute("fill", "none");
  134. shape.setAttribute("stroke-dasharray", "5 5");
  135. shape.setAttribute("opacity", 1);
  136. Tools.svg.appendChild(shape);
  137. return shape;
  138. }
  139. function createButton(name, icon, width, height, drawCallback, clickCallback) {
  140. var shape = Tools.createSVGElement("use", {href: "tools/hand/" + icon + ".svg#root"});
  141. shape.style.display = "none";
  142. shape.origWidth = width;
  143. shape.origHeight = height;
  144. shape.drawCallback = drawCallback;
  145. shape.clickCallback = clickCallback;
  146. Tools.svg.appendChild(shape);
  147. return shape;
  148. }
  149. function showSelectionButtons() {
  150. var scale = getScale();
  151. var selectionBBox = selectionRect.transformedBBox();
  152. for (var i = 0; i < selectionButtons.length; i++) {
  153. selectionButtons[i].drawCallback(selectionButtons[i],
  154. selectionBBox,
  155. scale);
  156. }
  157. }
  158. function hideSelectionButtons() {
  159. for (var i = 0; i < selectionButtons.length; i++) {
  160. selectionButtons[i].style.display = "none";
  161. }
  162. }
  163. function hideSelectionUI() {
  164. hideSelectionButtons();
  165. selectionRect.style.display = "none";
  166. }
  167. function startMovingElements(x, y, evt) {
  168. evt.preventDefault();
  169. selectorState = selectorStates.transform;
  170. currentTransform = moveSelection;
  171. selected = { x: x, y: y };
  172. // Some of the selected elements could have been deleted
  173. selected_els = selected_els.filter(function (el) {
  174. return Tools.svg.getElementById(el.id) !== null;
  175. });
  176. transform_elements = selected_els.map(function (el) {
  177. var tmatrix = get_transform_matrix(el);
  178. return {
  179. a: tmatrix.a, b: tmatrix.b, c: tmatrix.c,
  180. d: tmatrix.d, e: tmatrix.e, f: tmatrix.f
  181. };
  182. });
  183. var tmatrix = get_transform_matrix(selectionRect);
  184. selectionRectTransform = { x: tmatrix.e, y: tmatrix.f };
  185. }
  186. function startScalingTransform(x, y, evt) {
  187. evt.preventDefault();
  188. hideSelectionButtons();
  189. selectorState = selectorStates.transform;
  190. var bbox = selectionRect.transformedBBox();
  191. selected = {
  192. x: bbox.r[0],
  193. y: bbox.r[1],
  194. w: bbox.a[0],
  195. h: bbox.b[1],
  196. };
  197. transform_elements = selected_els.map(function (el) {
  198. var tmatrix = get_transform_matrix(el);
  199. return {
  200. a: tmatrix.a, b: tmatrix.b, c: tmatrix.c,
  201. d: tmatrix.d, e: tmatrix.e, f: tmatrix.f
  202. };
  203. });
  204. var tmatrix = get_transform_matrix(selectionRect);
  205. selectionRectTransform = {
  206. a: tmatrix.a, d: tmatrix.d,
  207. e: tmatrix.e, f: tmatrix.f
  208. };
  209. currentTransform = scaleSelection;
  210. }
  211. function startSelector(x, y, evt) {
  212. evt.preventDefault();
  213. selected = { x: x, y: y };
  214. selected_els = [];
  215. selectorState = selectorStates.selecting;
  216. selectionRect.x.baseVal.value = x;
  217. selectionRect.y.baseVal.value = y;
  218. selectionRect.width.baseVal.value = 0;
  219. selectionRect.height.baseVal.value = 0;
  220. selectionRect.style.display = "";
  221. tmatrix = get_transform_matrix(selectionRect);
  222. tmatrix.e = 0;
  223. tmatrix.f = 0;
  224. }
  225. function calculateSelection() {
  226. var selectionTBBox = selectionRect.transformedBBox();
  227. var elements = Tools.drawingArea.children;
  228. var selected = [];
  229. for (var i = 0; i < elements.length; i++) {
  230. if (transformedBBoxIntersects(selectionTBBox, elements[i].transformedBBox()))
  231. selected.push(Tools.drawingArea.children[i]);
  232. }
  233. return selected;
  234. }
  235. function moveSelection(x, y) {
  236. var dx = x - selected.x;
  237. var dy = y - selected.y;
  238. var msgs = selected_els.map(function (el, i) {
  239. var oldTransform = transform_elements[i];
  240. return {
  241. type: "update",
  242. id: el.id,
  243. transform: {
  244. a: oldTransform.a,
  245. b: oldTransform.b,
  246. c: oldTransform.c,
  247. d: oldTransform.d,
  248. e: dx + oldTransform.e,
  249. f: dy + oldTransform.f
  250. }
  251. };
  252. })
  253. var msg = {
  254. _children: msgs
  255. };
  256. var tmatrix = get_transform_matrix(selectionRect);
  257. tmatrix.e = dx + selectionRectTransform.x;
  258. tmatrix.f = dy + selectionRectTransform.y;
  259. var now = performance.now();
  260. if (now - last_sent > 70) {
  261. last_sent = now;
  262. Tools.drawAndSend(msg);
  263. } else {
  264. draw(msg);
  265. }
  266. }
  267. function scaleSelection(x, y) {
  268. var rx = (x - selected.x) / (selected.w);
  269. var ry = (y - selected.y) / (selected.h);
  270. var msgs = selected_els.map(function (el, i) {
  271. var oldTransform = transform_elements[i];
  272. var x = el.transformedBBox().r[0];
  273. var y = el.transformedBBox().r[1];
  274. var a = oldTransform.a * rx;
  275. var d = oldTransform.d * ry;
  276. var e = selected.x * (1 - rx) - x * a +
  277. (x * oldTransform.a + oldTransform.e) * rx
  278. var f = selected.y * (1 - ry) - y * d +
  279. (y * oldTransform.d + oldTransform.f) * ry
  280. return {
  281. type: "update",
  282. id: el.id,
  283. transform: {
  284. a: a,
  285. b: oldTransform.b,
  286. c: oldTransform.c,
  287. d: d,
  288. e: e,
  289. f: f
  290. }
  291. };
  292. })
  293. var msg = {
  294. _children: msgs
  295. };
  296. var tmatrix = get_transform_matrix(selectionRect);
  297. tmatrix.a = rx;
  298. tmatrix.d = ry;
  299. tmatrix.e = selectionRectTransform.e +
  300. selectionRect.x.baseVal.value * (selectionRectTransform.a - rx)
  301. tmatrix.f = selectionRectTransform.f +
  302. selectionRect.y.baseVal.value * (selectionRectTransform.d - ry)
  303. var now = performance.now();
  304. if (now - last_sent > 70) {
  305. last_sent = now;
  306. Tools.drawAndSend(msg);
  307. } else {
  308. draw(msg);
  309. }
  310. }
  311. function updateRect(x, y, rect) {
  312. rect.x.baseVal.value = Math.min(x, selected.x);
  313. rect.y.baseVal.value = Math.min(y, selected.y);
  314. rect.width.baseVal.value = Math.abs(x - selected.x);
  315. rect.height.baseVal.value = Math.abs(y - selected.y);
  316. }
  317. function resetSelectionRect() {
  318. var bbox = selectionRect.transformedBBox();
  319. var tmatrix = get_transform_matrix(selectionRect);
  320. selectionRect.x.baseVal.value = bbox.r[0];
  321. selectionRect.y.baseVal.value = bbox.r[1];
  322. selectionRect.width.baseVal.value = bbox.a[0];
  323. selectionRect.height.baseVal.value = bbox.b[1];
  324. tmatrix.a = 1; tmatrix.b = 0; tmatrix.c = 0;
  325. tmatrix.d = 1; tmatrix.e = 0; tmatrix.f = 0;
  326. }
  327. function get_transform_matrix(elem) {
  328. // Returns the first translate or transform matrix or makes one
  329. var transform = null;
  330. for (var i = 0; i < elem.transform.baseVal.numberOfItems; ++i) {
  331. var baseVal = elem.transform.baseVal[i];
  332. // quick tests showed that even if one changes only the fields e and f or uses createSVGTransformFromMatrix
  333. // the brower may add a SVG_TRANSFORM_MATRIX instead of a SVG_TRANSFORM_TRANSLATE
  334. if (baseVal.type === SVGTransform.SVG_TRANSFORM_MATRIX) {
  335. transform = baseVal;
  336. break;
  337. }
  338. }
  339. if (transform == null) {
  340. transform = elem.transform.baseVal.createSVGTransformFromMatrix(Tools.svg.createSVGMatrix());
  341. elem.transform.baseVal.appendItem(transform);
  342. }
  343. return transform.matrix;
  344. }
  345. function draw(data) {
  346. if (data._children) {
  347. batchCall(draw, data._children);
  348. }
  349. else {
  350. switch (data.type) {
  351. case "update":
  352. var elem = Tools.svg.getElementById(data.id);
  353. if (!elem) throw new Error("Mover: Tried to move an element that does not exist.");
  354. var tmatrix = get_transform_matrix(elem);
  355. for (i in data.transform) {
  356. tmatrix[i] = data.transform[i]
  357. }
  358. break;
  359. case "copy":
  360. var newElement = Tools.svg.getElementById(data.id).cloneNode(true);
  361. newElement.id = data.newid;
  362. Tools.drawingArea.appendChild(newElement);
  363. break;
  364. case "delete":
  365. data.tool = "Eraser";
  366. messageForTool(data);
  367. break;
  368. default:
  369. throw new Error("Mover: 'move' instruction with unknown type. ", data);
  370. }
  371. }
  372. }
  373. function clickSelector(x, y, evt) {
  374. selectionRect = selectionRect || createSelectorRect();
  375. for (var i = 0; i < selectionButtons.length; i++) {
  376. if (selectionButtons[i].contains(evt.target)) {
  377. var button = selectionButtons[i];
  378. }
  379. }
  380. if (button) {
  381. button.clickCallback(x, y, evt);
  382. } else if (pointInTransformedBBox([x, y], selectionRect.transformedBBox())) {
  383. hideSelectionButtons();
  384. startMovingElements(x, y, evt);
  385. } else if (Tools.drawingArea.contains(evt.target)) {
  386. hideSelectionUI();
  387. selected_els = [getParentMathematics(evt.target)];
  388. startMovingElements(x, y, evt);
  389. } else {
  390. hideSelectionButtons();
  391. startSelector(x, y, evt);
  392. }
  393. }
  394. function releaseSelector(x, y, evt) {
  395. if (selectorState == selectorStates.selecting) {
  396. selected_els = calculateSelection();
  397. if (selected_els.length == 0) {
  398. hideSelectionUI();
  399. }
  400. } else if (selectorState == selectorStates.transform)
  401. resetSelectionRect();
  402. if (selected_els.length != 0) showSelectionButtons();
  403. transform_elements = [];
  404. selectorState = selectorStates.pointing;
  405. }
  406. function moveSelector(x, y, evt) {
  407. if (selectorState == selectorStates.selecting) {
  408. updateRect(x, y, selectionRect);
  409. } else if (selectorState == selectorStates.transform && currentTransform) {
  410. currentTransform(x, y);
  411. }
  412. }
  413. function startHand(x, y, evt, isTouchEvent) {
  414. if (!isTouchEvent) {
  415. selected = {
  416. x: document.documentElement.scrollLeft + evt.clientX,
  417. y: document.documentElement.scrollTop + evt.clientY,
  418. }
  419. }
  420. }
  421. function moveHand(x, y, evt, isTouchEvent) {
  422. if (selected && !isTouchEvent) { //Let the browser handle touch to scroll
  423. window.scrollTo(selected.x - evt.clientX, selected.y - evt.clientY);
  424. }
  425. }
  426. function press(x, y, evt, isTouchEvent) {
  427. if (!handTool.secondary.active) startHand(x, y, evt, isTouchEvent);
  428. else clickSelector(x, y, evt, isTouchEvent);
  429. }
  430. function move(x, y, evt, isTouchEvent) {
  431. if (!handTool.secondary.active) moveHand(x, y, evt, isTouchEvent);
  432. else moveSelector(x, y, evt, isTouchEvent);
  433. }
  434. function release(x, y, evt, isTouchEvent) {
  435. move(x, y, evt, isTouchEvent);
  436. if (handTool.secondary.active) releaseSelector(x, y, evt, isTouchEvent);
  437. selected = null;
  438. }
  439. function deleteShortcut(e) {
  440. if (e.key == "Delete" &&
  441. !e.target.matches("input[type=text], textarea"))
  442. deleteSelection();
  443. }
  444. function duplicateShortcut(e) {
  445. if (e.key == "d" &&
  446. !e.target.matches("input[type=text], textarea"))
  447. duplicateSelection();
  448. }
  449. function switchTool() {
  450. onquit();
  451. if (handTool.secondary.active) {
  452. window.addEventListener("keydown", deleteShortcut);
  453. window.addEventListener("keydown", duplicateShortcut);
  454. }
  455. }
  456. function onquit() {
  457. selected = null;
  458. hideSelectionUI();
  459. window.removeEventListener("keydown", deleteShortcut);
  460. window.removeEventListener("keydown", duplicateShortcut);
  461. }
  462. var handTool = { //The new tool
  463. "name": "Hand",
  464. "shortcut": "h",
  465. "listeners": {
  466. "press": press,
  467. "move": move,
  468. "release": release,
  469. },
  470. "onquit": onquit,
  471. "secondary": {
  472. "name": "Selector",
  473. "icon": "tools/hand/selector.svg",
  474. "active": false,
  475. "switch": switchTool,
  476. },
  477. "draw": draw,
  478. "icon": "tools/hand/hand.svg",
  479. "mouseCursor": "move",
  480. "showMarker": true,
  481. };
  482. Tools.add(handTool);
  483. Tools.change("Hand"); // Use the hand tool by default
  484. })(); //End of code isolation