Line Drawing Tool

Press Space to snap to right angles

  1. // ==UserScript==
  2. // @name Line Drawing Tool
  3. // @namespace https://greasyfork.org/users/281093
  4. // @match https://sketchful.io/
  5. // @grant none
  6. // @version 1.3.3
  7. // @license MIT
  8. // @author Bell
  9. // @description Press Space to snap to right angles
  10. // ==/UserScript==
  11. /* jshint esversion: 6 */
  12.  
  13. const canvas = document.querySelector('#canvas');
  14.  
  15. const lineCanvas = document.createElement('canvas');
  16. const lineCtx = lineCanvas.getContext('2d');
  17. lineCanvas.style.position = 'absolute';
  18. lineCanvas.style.cursor = 'crosshair';
  19. lineCanvas.style.width = '100%';
  20. lineCanvas.style.display = 'none';
  21. lineCanvas.style.userSelect = 'none';
  22. lineCanvas.style.zIndex = '2';
  23. lineCanvas.oncontextmenu = () => { return false; };
  24. [lineCanvas.width, lineCanvas.height] = [canvas.width, canvas.height];
  25. canvas.parentElement.insertBefore(lineCanvas, canvas);
  26.  
  27. lineCanvas.clear = () => {
  28. lineCtx.clearRect(0, 0, lineCanvas.width, lineCanvas.height);
  29. };
  30.  
  31. let origin = {};
  32. let realOrigin = {};
  33. let previewPos = {};
  34. let realPos = {};
  35. let canvasHidden = true;
  36. let drawingLine = false;
  37. let snap = false;
  38.  
  39. document.addEventListener('keydown', (e) => {
  40. if (!isDrawing()) return;
  41. if (e.code === 'ShiftLeft' && canvasHidden) {
  42. lineCanvas.style.display = '';
  43. disableScroll();
  44. canvasHidden = false;
  45. }
  46. else if (e.code === 'Space' && !snap && !canvasHidden) {
  47. snap = true;
  48. document.dispatchEvent(createMouseEvent('pointermove', realPos));
  49. }
  50. });
  51.  
  52. document.addEventListener('keyup', (e) => {
  53. if (e.code === 'ShiftLeft' && !canvasHidden) {
  54. lineCanvas.style.display = 'none';
  55. canvasHidden = true;
  56. enableScroll();
  57. resetLineCanvas();
  58. document.removeEventListener('pointermove', savePos);
  59. document.removeEventListener('pointerup', pointerUpDraw);
  60. }
  61. else if (e.code === 'Space' && snap) {
  62. snap = false;
  63. document.dispatchEvent(createMouseEvent('pointermove', realPos));
  64. }
  65. });
  66.  
  67. function savePos(e) {
  68. previewPos = getPos(e);
  69. realPos = getRealPos(e);
  70.  
  71. if (canvasHidden || !drawingLine) return;
  72. lineCanvas.clear();
  73. drawPreviewLine(previewPos);
  74. e.preventDefault();
  75.  
  76. }
  77.  
  78. lineCanvas.addEventListener('pointerdown', (e) => {
  79. origin = getPos(e);
  80. realOrigin = getRealPos(e);
  81. drawingLine = true;
  82. document.addEventListener('pointerup', pointerUpDraw);
  83. document.addEventListener('pointermove', savePos);
  84. });
  85.  
  86.  
  87. function pointerUpDraw(e) {
  88. document.removeEventListener('pointermove', savePos);
  89. document.removeEventListener('pointerup', pointerUpDraw);
  90. drawLine(realOrigin.x, realOrigin.y, realPos.x, realPos.y);
  91. previewPos = getPos(e);
  92. realPos = getRealPos(e);
  93. resetLineCanvas();
  94. }
  95.  
  96. function resetLineCanvas() {
  97. drawingLine = false;
  98. lineCanvas.clear();
  99. }
  100.  
  101. function getPos(event) {
  102. const canvasRect = canvas.getBoundingClientRect();
  103. const canvasScale = canvas.width / canvasRect.width;
  104. return {
  105. x: (event.clientX - canvasRect.left) * canvasScale,
  106. y: (event.clientY - canvasRect.top) * canvasScale
  107. };
  108. }
  109.  
  110. function getRealPos(event) {
  111. return {
  112. x: event.clientX,
  113. y: event.clientY
  114. };
  115. }
  116.  
  117. function drawPreviewLine(pos) {
  118. lineCtx.beginPath();
  119. lineCtx.moveTo(origin.x, origin.y);
  120.  
  121. if (snap) {
  122. if (Math.abs(pos.x - origin.x) < Math.abs(pos.y - origin.y)) {
  123. lineCtx.lineTo(origin.x, pos.y);
  124. }
  125. else {
  126. lineCtx.lineTo(pos.x, origin.y);
  127. }
  128. }
  129. else {
  130. lineCtx.lineTo(pos.x, pos.y);
  131. }
  132.  
  133. lineCtx.stroke();
  134. }
  135.  
  136. function drawLine(x1, y1, x2, y2) {
  137. const coords = { x: x1, y: y1 };
  138. const newCoords = { x: x2, y: y2 };
  139.  
  140. if (snap) {
  141. if (Math.abs(x2 - x1) < Math.abs(y2 - y1)) {
  142. newCoords.x = x1;
  143. }
  144. else {
  145. newCoords.y = y1;
  146. }
  147. }
  148.  
  149. canvas.dispatchEvent(createMouseEvent('pointerdown', coords));
  150. canvas.dispatchEvent(createMouseEvent('pointermove', newCoords));
  151. canvas.dispatchEvent(createMouseEvent('pointerup', newCoords, true));
  152. }
  153.  
  154. function createMouseEvent(name, pos, bubbles = false) {
  155. return new MouseEvent(name, {
  156. bubbles: bubbles,
  157. clientX: pos.x,
  158. clientY: pos.y,
  159. button: 0
  160. });
  161. }
  162.  
  163. const keys = { 32: 1, 37: 1, 38: 1, 39: 1, 40: 1 };
  164.  
  165. function preventDefault(e) {
  166. e.preventDefault();
  167. }
  168.  
  169. function preventDefaultForScrollKeys(e) {
  170. if (keys[e.keyCode]) {
  171. preventDefault(e);
  172. return false;
  173. }
  174. }
  175.  
  176. function isDrawing() {
  177. return document.querySelector('#gameTools').style.display !== 'none' &&
  178. document.querySelector('body > div.game').style.display !== 'none' &&
  179. document.activeElement.tagName !== 'INPUT';
  180. }
  181.  
  182. let supportsPassive = false;
  183. try {
  184. window.addEventListener('test', null, Object.defineProperty({}, 'passive', {
  185. get: function() {
  186. supportsPassive = true;
  187. return true;
  188. }
  189. }));
  190. }
  191. catch(e) {
  192. console.log(e);
  193. }
  194.  
  195. const wheelOpt = supportsPassive ? { passive: false } : false;
  196. const wheelEvent = 'onwheel' in document.createElement('div') ? 'wheel' : 'mousewheel';
  197.  
  198. function disableScroll() {
  199. window.addEventListener('DOMMouseScroll', preventDefault, false);
  200. window.addEventListener(wheelEvent, preventDefault, wheelOpt);
  201. window.addEventListener('touchmove', preventDefault, wheelOpt);
  202. window.addEventListener('keydown', preventDefaultForScrollKeys, false);
  203. }
  204.  
  205. function enableScroll() {
  206. window.removeEventListener('DOMMouseScroll', preventDefault, false);
  207. window.removeEventListener(wheelEvent, preventDefault, wheelOpt);
  208. window.removeEventListener('touchmove', preventDefault, wheelOpt);
  209. window.removeEventListener('keydown', preventDefaultForScrollKeys, false);
  210. }