// ==UserScript==
// @name Line Drawing Tool
// @namespace https://greasyfork.org/users/281093
// @match https://sketchful.io/
// @grant none
// @version 1.3.3
// @license MIT
// @author Bell
// @description Press Space to snap to right angles
// ==/UserScript==
/* jshint esversion: 6 */
const canvas = document.querySelector('#canvas');
const lineCanvas = document.createElement('canvas');
const lineCtx = lineCanvas.getContext('2d');
lineCanvas.style.position = 'absolute';
lineCanvas.style.cursor = 'crosshair';
lineCanvas.style.width = '100%';
lineCanvas.style.display = 'none';
lineCanvas.style.userSelect = 'none';
lineCanvas.style.zIndex = '2';
lineCanvas.oncontextmenu = () => { return false; };
[lineCanvas.width, lineCanvas.height] = [canvas.width, canvas.height];
canvas.parentElement.insertBefore(lineCanvas, canvas);
lineCanvas.clear = () => {
lineCtx.clearRect(0, 0, lineCanvas.width, lineCanvas.height);
};
let origin = {};
let realOrigin = {};
let previewPos = {};
let realPos = {};
let canvasHidden = true;
let drawingLine = false;
let snap = false;
document.addEventListener('keydown', (e) => {
if (!isDrawing()) return;
if (e.code === 'ShiftLeft' && canvasHidden) {
lineCanvas.style.display = '';
disableScroll();
canvasHidden = false;
}
else if (e.code === 'Space' && !snap && !canvasHidden) {
snap = true;
document.dispatchEvent(createMouseEvent('pointermove', realPos));
}
});
document.addEventListener('keyup', (e) => {
if (e.code === 'ShiftLeft' && !canvasHidden) {
lineCanvas.style.display = 'none';
canvasHidden = true;
enableScroll();
resetLineCanvas();
document.removeEventListener('pointermove', savePos);
document.removeEventListener('pointerup', pointerUpDraw);
}
else if (e.code === 'Space' && snap) {
snap = false;
document.dispatchEvent(createMouseEvent('pointermove', realPos));
}
});
function savePos(e) {
previewPos = getPos(e);
realPos = getRealPos(e);
if (canvasHidden || !drawingLine) return;
lineCanvas.clear();
drawPreviewLine(previewPos);
e.preventDefault();
}
lineCanvas.addEventListener('pointerdown', (e) => {
origin = getPos(e);
realOrigin = getRealPos(e);
drawingLine = true;
document.addEventListener('pointerup', pointerUpDraw);
document.addEventListener('pointermove', savePos);
});
function pointerUpDraw(e) {
document.removeEventListener('pointermove', savePos);
document.removeEventListener('pointerup', pointerUpDraw);
drawLine(realOrigin.x, realOrigin.y, realPos.x, realPos.y);
previewPos = getPos(e);
realPos = getRealPos(e);
resetLineCanvas();
}
function resetLineCanvas() {
drawingLine = false;
lineCanvas.clear();
}
function getPos(event) {
const canvasRect = canvas.getBoundingClientRect();
const canvasScale = canvas.width / canvasRect.width;
return {
x: (event.clientX - canvasRect.left) * canvasScale,
y: (event.clientY - canvasRect.top) * canvasScale
};
}
function getRealPos(event) {
return {
x: event.clientX,
y: event.clientY
};
}
function drawPreviewLine(pos) {
lineCtx.beginPath();
lineCtx.moveTo(origin.x, origin.y);
if (snap) {
if (Math.abs(pos.x - origin.x) < Math.abs(pos.y - origin.y)) {
lineCtx.lineTo(origin.x, pos.y);
}
else {
lineCtx.lineTo(pos.x, origin.y);
}
}
else {
lineCtx.lineTo(pos.x, pos.y);
}
lineCtx.stroke();
}
function drawLine(x1, y1, x2, y2) {
const coords = { x: x1, y: y1 };
const newCoords = { x: x2, y: y2 };
if (snap) {
if (Math.abs(x2 - x1) < Math.abs(y2 - y1)) {
newCoords.x = x1;
}
else {
newCoords.y = y1;
}
}
canvas.dispatchEvent(createMouseEvent('pointerdown', coords));
canvas.dispatchEvent(createMouseEvent('pointermove', newCoords));
canvas.dispatchEvent(createMouseEvent('pointerup', newCoords, true));
}
function createMouseEvent(name, pos, bubbles = false) {
return new MouseEvent(name, {
bubbles: bubbles,
clientX: pos.x,
clientY: pos.y,
button: 0
});
}
const keys = { 32: 1, 37: 1, 38: 1, 39: 1, 40: 1 };
function preventDefault(e) {
e.preventDefault();
}
function preventDefaultForScrollKeys(e) {
if (keys[e.keyCode]) {
preventDefault(e);
return false;
}
}
function isDrawing() {
return document.querySelector('#gameTools').style.display !== 'none' &&
document.querySelector('body > div.game').style.display !== 'none' &&
document.activeElement.tagName !== 'INPUT';
}
let supportsPassive = false;
try {
window.addEventListener('test', null, Object.defineProperty({}, 'passive', {
get: function() {
supportsPassive = true;
return true;
}
}));
}
catch(e) {
console.log(e);
}
const wheelOpt = supportsPassive ? { passive: false } : false;
const wheelEvent = 'onwheel' in document.createElement('div') ? 'wheel' : 'mousewheel';
function disableScroll() {
window.addEventListener('DOMMouseScroll', preventDefault, false);
window.addEventListener(wheelEvent, preventDefault, wheelOpt);
window.addEventListener('touchmove', preventDefault, wheelOpt);
window.addEventListener('keydown', preventDefaultForScrollKeys, false);
}
function enableScroll() {
window.removeEventListener('DOMMouseScroll', preventDefault, false);
window.removeEventListener(wheelEvent, preventDefault, wheelOpt);
window.removeEventListener('touchmove', preventDefault, wheelOpt);
window.removeEventListener('keydown', preventDefaultForScrollKeys, false);
}