const commandBarElement = document.getElementById('menu-bar');
const toolBarElement = document.getElementById('tool-bar');
const layerBarElement = document.getElementById('layer-bar');
const studioElement = document.getElementById('studio');
const infoBarElement = document.getElementById('info-bar');
const easelElement = document.getElementById('easel');
const brushPreviewElement = document.getElementById('brush-preview');
const dZoom = 0.001;
const dBrushSize = 0.5;
const initialWidth = 800;
const initialHeight = 600;
const maxBrushSize = 500;
let brushColor = 'rgb(0, 0, 0)';
let brushShape = 'square'
let brushSize = 1;
let zoom = 1;
let currentTool = 'brush'
let prevTool = 'brush'
let startX = 0;
let startY = 0;
let endX = 0;
let endY = 0;
let dX = 0;
let dY = 0;
let canvasStartX = 0;
let canvasStartY = 0;
let canvasEndX = 0;
let canvasEndY = 0;
let canvasDX = 0;
let canvasDY = 0;
let isKeyDown = false;
let isMouseDown = false;
// HELPERS {{{
function hexToRgbArray(hex) {
if (hex.startsWith('#')) {
hex = hex.slice(1);
}
if (hex.length === 3) {
hex = hex.split('').map(char => char + char).join('');
}
const bigint = parseInt(hex, 16);
return [(bigint >> 16) & 255, (bigint >> 8) & 255, bigint & 255];
}
function colorsMatch(a, b) {
return a[0] === b[0] && a[1] === b[1] && a[2] === b[2] && a[3] === b[3];
}
function makeButtonElement({icon, name, clickFunction, key}) {
if (!icon) throw new Error('No icon provided');
if (!name) throw new Error('No name provided');
if (!clickFunction) throw new Error('No click function provided');
if (!key) throw new Error('No key provided');
const buttonElement = document.createElement('div');
buttonElement.className = 'button';
buttonElement.innerHTML = icon;
buttonElement.title = name;
buttonElement.addEventListener('click', clickFunction);
if (key) {
const keyHint = document.createElement('span');
keyHint.className = 'key-hint';
keyHint.innerHTML = key;
buttonElement.appendChild(keyHint);
}
return buttonElement;
}
// }}}
// LAYERS {{{
// factory {{{
function makeCanvas({height=600, width=800}) { // {{{
const canvas = document.createElement('canvas');
easelElement.appendChild(canvas);
canvas.style.imageRendering = 'pixelated';
canvas.ctx = canvas.getContext('2d');
canvas.tempCanvas = document.createElement('canvas');
canvas.tempCtx = canvas.tempCanvas.getContext('2d');
canvas.disableImageSmoothing = function(ctx) {
ctx.imageSmoothingEnabled = false;
if (ctx.imageSmoothingEnabled !== false) {
ctx.mozImageSmoothingEnabled = false;
ctx.webkitImageSmoothingEnabled = false;
ctx.msImageSmoothingEnabled = false;
}
};
canvas.saveTempCanvas = function() {
canvas.ctx.save();
canvas.tempCanvas.width = canvas.width;
canvas.tempCanvas.height = canvas.height;
canvas.disableTempImageSmoothing(canvas.tempCtx);
canvas.tempCtx.drawImage(canvas, 0, 0);
}
canvas.restoreTempCanvas = function() {
canvas.ctx.clearRect(0, 0, canvas.width, canvas.height);
canvas.ctx.drawImage(canvas.tempCanvas, 0, 0);
canvas.ctx.restore();
}
canvas.setHeight = function(height) {
canvas.height = height;
canvas.disableImageSmoothing(canvas.ctx);
};
canvas.setWidth = function(width) {
canvas.width = width;
canvas.disableImageSmoothing(canvas.ctx);
};
canvas.getPositionOnCanvas = function(e) {
const rect = canvas.getBoundingClientRect();
return {
x: Math.round((e.clientX - rect.left) / zoom),
y: Math.round((e.clientY - rect.top) / zoom),
};
}
canvas.drawPixel = function(x, y, color) {
console.log({x, y, color});
canvas.ctx.fillStyle = color;
canvas.ctx.fillRect(x, y, 1, 1);
}
canvas.drawLineWithPixels = function(x1, y1, x2, y2, color) {
const dx = Math.abs(x2 - x1);
const dy = Math.abs(y2 - y1);
const sx = x1 < x2 ? 1 : -1;
const sy = y1 < y2 ? 1 : -1;
let err = dx - dy;
while (true) {
canvas.drawPixel(x1, y1, color); // Draw each pixel along the line
if (x1 === x2 && y1 === y2) break;
const e2 = err * 2;
if (e2 > -dy) { err -= dy; x1 += sx; }
if (e2 < dx) { err += dx; y1 += sy; }
}
}
canvas.drawShape = function(x, y, shape, size, color) {
if (size == 1) {
canvas.drawPixel(x, y, color);
return;
}
canvas.ctx.fillStyle = color;
if (shape === 'square') {
canvas.ctx.fillRect(x - Math.floor(size / 2), y - Math.floor(size / 2), size, size);
}
}
canvas.getColorAtPixel = function(data, x, y) {
const index = (y * canvas.width + x) * 4;
return [data[index], data[index + 1], data[index + 2], data[index + 3]];
}
canvas.setColorAtPixel = function(data, x, y, color) {
const index = (y * canvas.width + x) * 4;
data[index] = color[0];
data[index + 1] = color[1];
data[index + 2] = color[2];
data[index + 3] = 255;
}
canvas.fill = function(color) {
canvas.ctx.fillStyle = color;
canvas.ctx.fillRect(0, 0, canvas.width, canvas.height);
}
canvas.floodFill = function(x, y, color) {
const imageData = canvas.ctx.getImageData(0, 0, canvas.width, canvas.height);
const data = imageData.data;
const targetColor = canvas.getColorAtPixel(data, x, y);
const fillColorArray = hexToRgbArray(color);
if (colorsMatch(targetColor, fillColorArray)) {
return;
}
const stack = [{x, y}];
while (stack.length > 0) {
const {x, y} = stack.pop();
const currentColor = canvas.getColorAtPixel(data, x, y);
if (colorsMatch(currentColor, targetColor)) {
canvas.setColorAtPixel(data, x, y, fillColorArray);
if (x > 0) stack.push({x: x - 1, y});
if (x < canvas.width - 1) stack.push({x: x + 1, y});
if (y > 0) stack.push({x, y: y - 1});
if (y < canvas.height - 1) stack.push({x, y: y + 1});
}
}
canvas.ctx.putImageData(imageData, 0, 0);
}
canvas.toDataUrl = function() {
const dataURL = canvas.toDataURL();
const dimensions = `${canvas.width}x${canvas.height}`;
return {dataURL, dimensions};
}
canvas.fromDataUrl = function(dataURL, dimensions) {
const img = new Image();
img.src = dataURL;
img.onload = function() {
canvas.width = dimensions.split('x')[0];
canvas.height = dimensions.split('x')[1];
canvas.style.width = canvas.width * zoom + 'px';
canvas.style.height = canvas.height * zoom + 'px';
canvas.ctx.drawImage(img, 0, 0);
}
}
canvas.deleteCanvas = function() {
canvas.remove();
}
canvas.setWidth(width);
canvas.setHeight(height);
return canvas;
} // }}}
function makeLayer({height=600, width=800}) { // {{{
const layer = {}
layer.canvas = makeCanvas({height, width});
layer.active = false;
layer.opacity = 1;
layer.controllerElement = document.createElement('div');
layer.controllerElement.className = 'layer-controller';
layer.controllerElement.addEventListener('click', () => {
layers.setActive(layer);
});
layer.activate = function() {
layer.active = true;
layer.controllerElement.classList.add('active');
}
layer.deactivate = function() {
layer.active = false;
layer.controllerElement.classList.remove('active');
}
return layer;
} // }}}
function makeLayers({height=600, width=800}) { // {{{
const layers = [];
layers.height = height;
layers.width = width;
layers.setHeight = function(height) {
layers.height = height;
easelElement.style.height = height + 2 + 'px';
}
layers.setHeight(height);
layers.setWidth = function(width) {
layers.width = width;
easelElement.style.width = width + 2 + 'px';
}
layers.setWidth(width);
layers.resetPosition = function() {
const studioRect = studioElement.getBoundingClientRect();
easelElement.style.left = `${studioRect.left}px`;
easelElement.style.top = `${studioRect.top}px`;
}
layers.updateControllers = function() {
layerBarElement.innerHTML = '';
layers.forEach(layer => {
layerBarElement.appendChild(layer.controllerElement);
});
}
layers.add = function() {
const layer = makeLayer({
height: layers.height,
width: layers.width,
});
layers.push(layer);
layer.activate();
layers.updateControllers();
}
layers.delete = function(layer) {
layer.canvas.deleteCanvas();
layers.splice(layers.indexOf(layer), 1);
layers.updateControllers();
}
layers.deleteAll = function() {
layers.forEach(layer => layer.deleteCanvas());
// TODO
}
layers.move = function(layer, index) {
layers.splice(layers.indexOf(layer), 1);
layers.splice(index, 0, layer);
}
layers.setActive = function(layer) {
layers.forEach(layer => layer.deactivate());
layer.activate();
}
layers.getActive = function() {
return layers.find(layer => layer.active);
}
return layers;
} // }}}
// }}}
const layers = makeLayers({height: initialHeight, width: initialWidth});
layers.add();
layers.add();
layers[0].canvas.fill('rgb(255, 255, 255)');
layers.setActive(layers[1]);
// }}}
// COLOR PREVIEW {{{
function makeColorPreview() {
const colorPreview = {}
colorPreview.element = document.createElement('div');
colorPreview.element.id = 'color-preview';
colorPreview.element.className = 'puck';
colorPreview.element.style.backgroundColor = brushColor;
commandBarElement.appendChild(colorPreview.element);
colorPreview.update = function() {
colorPreview.element.style.backgroundColor = brushColor;
}
}
const colorPreview = makeColorPreview();
// }}}
// COMMANDS {{{
// factory {{{
function makeCommand({name, key, icon, clickFunction}) {
if (!name) throw new Error('No name provided');
if (!icon) throw new Error('No icon provided');
if (!clickFunction) throw new Error('No click function provided');
if (!key) throw new Error('No key provided');
const command = {};
command.name = name;
command.key = key;
command.buttonElement = makeButtonElement({icon, name, clickFunction, key});
commandBarElement.appendChild(command.buttonElement);
return command
}
function makeCommands() {
const commands = [];
commands.add = function({name, key, icon, clickFunction}) {
const command = makeCommand({name, key, icon, clickFunction});
commands.push(command);
}
return commands;
}
// }}}
const commands = makeCommands();
commands.add({ // flip-horizontally {{{
name: 'flip-horizontally',
key: 'f',
icon: '',
clickFunction: function flipCanvasHorizontally() {
const canvas = layers.getActive().canvas;
const ctx = canvas.ctx;
ctx.save();
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.scale(-1, 1);
ctx.translate(-canvas.width, 0);
canvas.restoreTempCanvas();
ctx.restore();
}
}); // }}}
commands.add({ // flip-vertically {{{
name: 'flip-vertically',
key: 'v',
icon: '',
clickFunction: function flipCanvasVertically() {
const canvas = layers.getActive().canvas;
const ctx = canvas.ctx;
ctx.save();
canvas.saveTempCanvas();
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.scale(1, -1);
ctx.translate(0, -canvas.height);
canvas.restoreTempCanvas();
ctx.restore();
}
}); // }}}
commands.add({ // export {{{
name: 'export',
key: 'e',
icon: '',
clickFunction: function exportCanvas() {
const canvas = layers.getActive().canvas;
const link = document.createElement('a');
link.download = 'canvas.png';
link.href = canvas.toDataURL();
link.click();
}
}); // }}}
commands.add({ // import {{{
name: 'import',
key: 'i',
icon: '',
clickFunction: function importCanvas() {
const canvas = layers.getActive().canvas;
const ctx = canvas.ctx;
const input = document.createElement('input');
input.type = 'file';
input.accept = 'image/*';
input.onchange = (e) => {
const file = e.target.files[0];
const reader = new FileReader();
reader.onload = (e) => {
const img = new Image();
img.onload = () => {
canvas.width = img.width;
canvas.height = img.height;
ctx.drawImage(img, 0, 0);
}
img.src = e.target.result;
}
reader.readAsDataURL(file);
}
input.click();
}
}); // }}}
commands.add({ // clear {{{
name: 'clear',
key: 'c',
icon: '',
clickFunction: function clearCanvas() {
const canvas = layers.getActive().canvas;
const ctx = canvas.ctx;
canvas.saveTempCanvas();
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = 'white';
ctx.fillRect(0, 0, canvas.width, canvas.height);
}
}); // }}}
commands.add({ // reset {{{
name: 'reset',
key: 't',
icon: '',
clickFunction: function resetZoom() {
layers.resetPosition();
// zoom = 1;
// canvas.style.width = canvas.width * zoom + 'px';
// canvas.style.height = canvas.height * zoom + 'px';
// TODO
}
}); // }}}
// }}}
// TOOLS {{{
// factory {{{
function makeTool({name, key, icon, mouseDown, mouseMove, mouseUp}) {
if (!name) throw new Error('No name provided');
if (!key) throw new Error('No key provided');
if (!icon) throw new Error('No icon provided');
const tool = {};
tool.name = name;
tool.key = key;
tool.icon = icon;
tool.mouseDown = mouseDown;
tool.mouseMove = mouseMove;
tool.mouseUp = mouseUp;
tool.active = false;
tool.buttonElement = makeButtonElement({
icon: tool.icon,
name: tool.name,
key: tool.key,
clickFunction: function() {
tools.activate(tool);
tool.buttonElement.classList.add('active');
}
});
toolBarElement.appendChild(tool.buttonElement);
tool.activate = function() {
currentTool = tool.name;
tool.active = true;
}
tool.deactivate = function() {
tool.active = false;
tool.buttonElement.classList.remove('active');
}
return tool;
}
function makeTools() {
const tools = [];
tools.add = function({name, key, icon, mouseDown, mouseMove, mouseUp}) {
const tool = makeTool({name, key, icon, mouseDown, mouseMove, mouseUp});
tools.push(tool);
}
tools.activate = function(tool) {
tools.forEach(tool => tool.deactivate());
tool.activate();
}
return tools;
}
// }}}
const tools = makeTools();
tools.add({ // brush {{{
name: 'brush',
key: 'b',
icon: '',
mouseDown: function(e) {
const canvas = layers.getActive().canvas;
if (brushSize == 1) {
canvas.drawPixel(canvasStartX, canvasStartY, brushColor);
} else {
canvas.drawShape(canvasStartX, canvasStartY, brushShape, brushSize, brushColor);
}
},
mouseMove: function(e) {
const canvas = layers.getActive().canvas;
if (brushSize == 1) {
canvas.drawLineWithPixels(canvasStartX, canvasStartY, canvasEndX, canvasEndY, brushColor);
return;
} else {
canvas.drawShape(canvasEndX, canvasEndY, brushShape, brushSize, brushColor);
}
canvasStartX = canvasEndX;
canvasStartY = canvasEndY;
},
}); // }}}
tools.add({ // content-move {{{
name: 'content-move',
key: 'h',
icon: '',
mouseMove: function(e) {
const canvas = layers.getActive().canvas;
canvas.ctx.clearRect(0, 0, canvas.width, canvas.height);
canvas.ctx.fillStyle = 'white';
canvas.ctx.fillRect(0, 0, canvas.width, canvas.height);
canvas.restoreTempCanvas();
},
}); // }}}
tools.add({ // move {{{
name: 'move',
key: 'm',
icon: '',
mouseDown: function(e) {
startX = e.clientX - easelElement.offsetLeft;
startY = e.clientY - easelElement.offsetTop;
},
mouseMove: function(e) {
easelElement.style.left = dX + 'px';
easelElement.style.top = dY + 'px';
},
}); // }}}
tools.add({ // zoom {{{
name: 'zoom',
key: 'z',
icon: '',
mouseMove: function(e) {
// TODO all canvases
// const canvas = layers.getActive().canvas;
zoom += dX * dZoom;
if (zoom < 0.1) zoom = 0.1;
// canvas.style.height = canvasHeight * zoom + 'px';
// canvas.style.width = canvasWidth * zoom + 'px';
startX = endX;
}
}); // }}}
tools.add({ // bucket-fill {{{
name: 'bucket-fill',
key: 'k',
icon: '',
mouseDown: function(e) {
// canvas = layers.getActive().canvas;
// canvas.floodFill(canvasStartX, canvasStartY, brushColor);
}
}); // }}}
tools.add({ // color-picker {{{
name: 'color-picker',
key: 'a',
icon: '',
mouseDown: function(e) {
const canvas = layers.getActive().canvas;
const imageData = canvas.ctx.getImageData(canvasStartX, canvasStartY, 1, 1).data;
const pickedColor = `rgb(${imageData[0]}, ${imageData[1]}, ${imageData[2]})`;
brushColor = pickedColor;
colorPreview.update();
}
}); // }}}
tools.add({ // brush-size {{{
name: 'brush-size',
key: 'd',
icon: '',
mouseMove: function(e) {
brushSize += dX * dBrushSize;
if (brushSize < 1) brushSize = 1;
if (brushSize > maxBrushSize) brushSize = maxBrushSize;
startX = endX;
}
}); // }}}
tools.add({ // resize {{{
name: 'resize',
key: 'r',
icon: '',
mouseMove: function(e) {
// const canvas = layers.getActive().canvas;
// let newWidth = canvasWidth + dX / zoom;
// let newHeight = canvasHeight + dY / zoom;
// if (newWidth > 0 && newHeight > 0) {
// canvas.setWidth(newWidth);
// canvas.setHeight(newHeight);
// canvas.style.width = newWidth * zoom + 'px';
// canvas.style.height = newHeight * zoom + 'px';
// canvas.ctx.clearRect(0, 0, canvas.width, canvas.height);
// canvas.ctx.fillStyle = backgroundColor;
// canvas.ctx.fillRect(0, 0, canvas.width, canvas.height);
// canvas.ctx.drawImage(tempCanvas, 0, 0);
// }
}
}); // }}}
tools.add({ // color-mix {{{
name: 'color-mix',
key: 'x',
icon: '',
mouseMove: function(e) {
// const canvas = layers.getActive().canvas;
const imageData = ctx.getImageData(canvasEndX, canvasEndY, 1, 1).data;
const canvasColor = `rgb(${imageData[0]}, ${imageData[1]}, ${imageData[2]})`;
const distance = Math.sqrt(Math.pow(e.clientX - startX, 2) + Math.pow(e.clientY - startY, 2));
const t = Math.min(1, distance / 300);
const mixedColor = mixbox.lerp(brushColor, canvasColor, t);
brushColor = mixedColor;
startX = e.clientX;
startY = e.clientY;
}
}); // }}}
// }}}
// PUCKS {{{
// factory {{{
function createPuck({puckColor, key, editable=true}) {
if (!puckColor) throw new Error('No puck color provided');
const puck = {}
puck.element = document.createElement('div');
puck.element.style.backgroundColor = puckColor;
puck.element.className = 'puck';
if (editable) {
const deleteHandle = document.createElement('div');
deleteHandle.className = 'delete-handle';
deleteHandle.innerHTML = '';
puck.element.appendChild(deleteHandle);
deleteHandle.addEventListener('click', () => {
puck.element.remove();
});
}
if (key) {
const keyHint = document.createElement('div');
keyHint.className = 'key-hint';
keyHint.innerHTML = key;
puck.element.appendChild(keyHint);
}
function mixx(startTime) {
var interval = setInterval(() => {
const elapsedTime = Date.now() - startTime;
const t = Math.min(1, elapsedTime / 10000);
const mixedColor = mixbox.lerp(brushColor, puck.style.backgroundColor, t);
brushColor = mixedColor;
colorPreview.update();
infos.update();
}, 50);
return interval;
}
puck.element.addEventListener('mousedown', () => {
const startTime = Date.now();
var interval = mixx(startTime);
function onMouseUp() {
clearInterval(interval);
document.removeEventListener('mouseup', onMouseUp);
}
document.addEventListener('mouseup', onMouseUp);
});
puck.keydown = function(e) {
if (e.key == key) {
const startTime = Date.now();
var interval = mixx(startTime);
function onKeyUp() {
clearInterval(interval);
document.removeEventListener('keyup', onKeyUp);
}
document.addEventListener('keyup', onKeyUp);
}
}
commandBarElement.appendChild(puck.element);
}
function makePucks() {
const pucks = [];
pucks.add = function({puckColor, key, editable}) {
const puck = createPuck({puckColor, key, editable});
pucks.push(puck);
}
return pucks;
}
// }}}
const pucks = makePucks();
pucks.add({
puckColor: 'rgb(0, 0, 0)',
key: '1',
editable: false,
});
pucks.add({
puckColor: 'rgb(255, 255, 255)',
key: '2',
editable: false,
});
pucks.add({
puckColor: 'rgb(255, 0, 0)',
key: '3',
editable: false,
});
pucks.add({
puckColor: 'rgb(0, 255, 0)',
key: '4',
editable: false,
});
pucks.add({
puckColor: 'rgb(0, 0, 255)',
key: '5',
editable: false,
});
// }}}
// INFO {{{
function makeInfo({name, updateFunction}) {
if (!name) throw new Error('No name provided');
if (!updateFunction) throw new Error('No update function provided');
const info = {};
info.name = name;
info.updateFunction = updateFunction;
info.element = document.createElement('span');
info.element.className = 'info';
const key = document.createElement('span');
key.className = 'key';
key.innerHTML = info.name + ':';
const value = document.createElement('span');
value.className = 'value';
value.innerHTML = '0';
info.element.appendChild(key);
info.element.appendChild(value);
infoBarElement.appendChild(info.element);
info.update = function() {
let v = updateFunction();
if (v === undefined) v = '?';
value.innerHTML = v;
}
return info;
}
function makeInfos() {
const infos = []
infos.add = function({name, updateFunction}) {
const info = makeInfo({name, updateFunction});
infos.push(info);
}
infos.update = function() {
infos.forEach(function(info){
info.update();
});
}
return infos;
}
const infos = makeInfos();
infos.add({
name: 'zoom',
updateFunction: function() {
var percent = zoom * 100;
return percent.toFixed(0) + '%';
}
});
infos.add({
name: 'brush',
updateFunction: function() {
return brushSize;
}
});
infos.add({
name: 'x',
updateFunction: function() {
return canvasEndX;
}
});
infos.add({
name: 'y',
updateFunction: function() {
return canvasEndY;
}
});
infos.add({
name: 'color',
updateFunction: function() {
return brushColor;
}
});
infos.add({
name: 'width',
updateFunction: function() {
return "width";
}
});
infos.add({
name: 'height',
updateFunction: function() {
return "height";
}
});
// }}}
// MOUSE EVENT LISTENERS {{{
studioElement.addEventListener('mousedown', (e) => {
const canvas = layers.getActive().canvas;
isMouseDown = true;
startX = e.clientX;
startY = e.clientY;
canvasStartX = canvas.getPositionOnCanvas(e).x;
canvasStartY = canvas.getPositionOnCanvas(e).y;
for (var i = 0; i < tools.length; i++) {
var tool = tools[i];
if (tool.name === currentTool) {
if (tool.mouseDown) {
tool.mouseDown(e);
break;
}
}
}
infos.update();
});
studioElement.addEventListener('mousemove', (e) => {
const canvas = layers.getActive().canvas;
endX = e.clientX;
endY = e.clientY;
dX = endX - startX;
dY = endY - startY;
canvasEndX = canvas.getPositionOnCanvas(e).x;
canvasEndY = canvas.getPositionOnCanvas(e).y;
canvasDX = canvasEndX - canvasStartX;
canvasDY = canvasEndY - canvasStartY;
if (currentTool == 'brush-size') {
brushPreviewElement.style.display = 'block';
brushPreviewElement.style.width = brushSize + 'px';
brushPreviewElement.style.height = brushSize + 'px';
brushPreviewElement.style.left = e.clientX - brushSize / 2 + 'px';
brushPreviewElement.style.top = e.clientY - brushSize / 2 + 'px';
}
if (isMouseDown) {
for (var i = 0; i < tools.length; i++) {
var tool = tools[i];
if (tool.name === currentTool) {
if (tool.mouseMove) {
tool.mouseMove(e);
break;
}
}
}
}
infos.update();
});
studioElement.addEventListener('mouseup', () => {
isMouseDown = false;
infos.update();
});
studioElement.addEventListener('mouseleave', () => {
isMouseDown = false;
brushPreviewElement.style.display = 'none';
infos.update();
});
// }}}
// KEYBINDINGS {{{
document.addEventListener('keydown', (e) => {
if (isKeyDown) return;
tools.forEach(tool => {
if (tool.key.toLowerCase() === e.key.toLowerCase()) {
prevTool = currentTool;
currentTool = tool.name;
}
});
commands.forEach(command => {
if (command.key.toLowerCase() === e.key.toLowerCase()) {
command.clickFunction();
}
});
pucks.filter(puck => puck.key).forEach(puck => {
if (puck.key.toLowerCase() === e.key.toLowerCase()) {
puck.keydown(e);
}
});
isKeyDown = true;
});
document.addEventListener('keyup', (e) => {
tools.forEach(tool => {
if (tool.key.toLowerCase() === e.key) {
currentTool = prevTool;
}
});
isKeyDown = false;
});
// }}}
layers.resetPosition();