// CONSTANTS {{{ const commandBarElement = document.getElementById('menu-bar'); const toolBarElement = document.getElementById('tool-bar'); const layerControllersElement = document.getElementById('layer-controllers'); 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; const tolerance = 1; const shapes = ['circle', 'square']; // }}} // VARS {{{ let brushShape = 'circle' let brushSize = 15; let zoom = 1; 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 disableImageSmoothing(ctx) { ctx.imageSmoothingEnabled = false; if (ctx.imageSmoothingEnabled !== false) { ctx.mozImageSmoothingEnabled = false; ctx.webkitImageSmoothingEnabled = false; ctx.msImageSmoothingEnabled = false; } }; function hexToRgbArray(hex) { hex = hex.replace(/^#/, ''); const bigint = parseInt(hex, 16); const r = (bigint >> 16) & 255; const g = (bigint >> 8) & 255; const b = bigint & 255; return [r, g, b, 255]; // Add 255 for full opacity } function colorsMatch(color1, color2, tolerance = 0) { return Math.abs(color1[0] - color2[0]) <= tolerance && Math.abs(color1[1] - color2[1]) <= tolerance && Math.abs(color1[2] - color2[2]) <= tolerance && Math.abs(color1[3] - color2[3]) <= tolerance; // Include alpha comparison } function makeIconElement(htmlString) { const parentElement = document.createElement('div'); parentElement.innerHTML = htmlString; const iconElement = parentElement.firstChild; return iconElement; } function makeButtonElement({icon, name, func, key}) { if (!icon) throw new Error('No icon provided'); if (!name) throw new Error('No name provided'); if (!func) throw new Error('No click function provided'); if (!key) throw new Error('No key provided'); const button = {}; button.name = name; button.key = key; button.icon = icon; button.element = document.createElement('div'); button.element.className = 'button'; button.element.addEventListener('click', func); button.refresh = function() { button.element.innerHTML = ''; const iconElement = makeIconElement(button.icon); button.element.appendChild(iconElement); button.element.title = button.name; if (button.key) { const keyHint = document.createElement('span'); keyHint.className = 'key-hint'; keyHint.innerHTML = key; button.element.appendChild(keyHint); } } button.refresh(); return button; } // }}} // COLOR {{{ function makeColor(rgb) { const color = {}; color.color = rgb; color.toRgb = function() { return color.rgb; } color.toHex = function() { color.r = parseInt(rgb[0]); color.g = parseInt(rgb[1]); color.b = parseInt(rgb[2]); return `#${color.r.toString(16)}${color.g.toString(16)}${color.b.toString(16)}`; } color.mix = function(color2, t) { const color1 = color.color; const newColor = mixbox.lerp(color1, color2, t); color.color = newColor; console.log(color.color); colorPreview.update(); } return color; } const color = makeColor({r: 0, g: 0, b: 0}); // }}} // LAYERS {{{ // FACTORY {{{ function makeCanvas({height=600, width=800}) { // {{{ const canvas = document.createElement('canvas'); canvas.style.imageRendering = 'pixelated'; canvas.ctx = canvas.getContext('2d'); canvas.tempCanvas = document.createElement('canvas'); canvas.tempCtx = canvas.tempCanvas.getContext('2d'); canvas.saveCanvas = function() { canvas.ctx.save(); canvas.tempCanvas.width = canvas.width; canvas.tempCanvas.height = canvas.height; canvas.tempCtx.clearRect(0, 0, canvas.width, canvas.height); disableImageSmoothing(canvas.tempCtx); canvas.tempCtx.drawImage(canvas, 0, 0); } canvas.clearCanvas = function() { canvas.ctx.clearRect(0, 0, canvas.width, canvas.height); } canvas.restoreCanvas = function(x=0, y=0) { canvas.ctx.drawImage(canvas.tempCanvas, x, y); } canvas.setHeight = function(height) { canvas.height = height; disableImageSmoothing(canvas.ctx); }; canvas.setWidth = function(width) { canvas.width = width; disableImageSmoothing(canvas.ctx); }; canvas.resize = function(width, height) { canvas.width = width; canvas.height = height; 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) { 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) { x = Math.round(x); y = Math.round(y); 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); } else if (shape === 'circle') { let radius = Math.floor(size / 1); let radiusSquared = radius * radius; for (let y1 = -radius; y1 <= radius; y1++) { for (let x1 = -radius; x1 <= radius; x1++) { // Adjust the condition to avoid the outcrop if ((x1 * x1 + y1 * y1) <= radiusSquared - radius) { canvas.ctx.fillRect(x + x1, y + y1, 1, 1); } } } } else if (shape === 'empty-circle') { let radius = Math.floor(size / 2); let x1 = radius; let y1 = 0; let radiusError = 1 - x1; while (x1 >= y1) { // Draw the 8 octants of the circle canvas.ctx.fillRect(x + x1, y + y1, 1, 1); canvas.ctx.fillRect(x + y1, y + x1, 1, 1); canvas.ctx.fillRect(x - y1, y + x1, 1, 1); canvas.ctx.fillRect(x - x1, y + y1, 1, 1); canvas.ctx.fillRect(x - x1, y - y1, 1, 1); canvas.ctx.fillRect(x - y1, y - x1, 1, 1); canvas.ctx.fillRect(x + y1, y - x1, 1, 1); canvas.ctx.fillRect(x + x1, y - y1, 1, 1); y1++; if (radiusError < 0) { radiusError += 2 * y1 + 1; } else { x1--; radiusError += 2 * (y1 - x1 + 1); } } } } canvas.drawLineWithShape = function(x1, y1, x2, y2, shape, size, color) { const dx = x2 - x1; const dy = y2 - y1; const distance = Math.sqrt(dx * dx + dy * dy); const steps = Math.ceil(distance / (size / 2)); for (let i = 0; i <= steps; i++) { const x = Math.round(x1 + (dx * i) / steps); const y = Math.round(y1 + (dy * i) / steps); canvas.drawShape(x, y, shape, size, color); } } canvas.fill = function(color) { canvas.ctx.fillStyle = color; canvas.ctx.fillRect(0, 0, canvas.width, canvas.height); } canvas.getColorAtPixelData = function(x, y, data) { const index = (y * canvas.width + x) * 4; const color = [data[index], data[index + 1], data[index + 2], data[index + 3]]; return color; } canvas.setColorAtPixelData = function(x, y, color, data) { const index = (y * canvas.width + x) * 4; data[index] = color[0]; data[index + 1] = color[1]; data[index + 2] = color[2]; data[index + 3] = color[3]; } canvas.floodFill = function(x, y, color) { console.log('flood fill'); const imageData = canvas.ctx.getImageData(0, 0, canvas.width, canvas.height); const data = imageData.data; const targetColor = canvas.getColorAtPixelData(x, y, data); const fillColorArray = hexToRgbArray(color); if (colorsMatch(targetColor, fillColorArray, tolerance)) { return; } const stack = [{x, y}]; while (stack.length > 0) { const {x, y} = stack.pop(); const currentColor = canvas.getColorAtPixelData(x, y, data); if (colorsMatch(currentColor, targetColor, tolerance)) { canvas.setColorAtPixelData(x, y, fillColorArray, data); 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.getColorAtPixel = function(x, y) { const data = canvas.ctx.getImageData(0, 0, canvas.width, canvas.height).data; return canvas.getColorAtPixelData(x, y, data); } canvas.setColorAtPixel = function(x, y, color) { const imageData = canvas.ctx.getImageData(0, 0, canvas.width, canvas.height); const data = imageData.data; canvas.setColorAtPixelData(x, y, color, data); canvas.ctx.putImageData(new ImageData(data, canvas.width, canvas.height), 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.innerHTML = ''; const moveUpHandle = document.createElement('div'); moveUpHandle.classList.add('handle'); moveUpHandle.classList.add('top-right'); moveUpHandle.innerHTML = ''; moveUpHandle.addEventListener('click', () => { const index = layers.indexOf(layer); if (index > 0) { layers.move(layer, index - 1); } }); layer.controllerElement.appendChild(moveUpHandle); const moveDownHandle = document.createElement('div'); moveDownHandle.classList.add('handle'); moveDownHandle.classList.add('bottom-right'); moveDownHandle.innerHTML = ''; moveDownHandle.addEventListener('click', () => { const index = layers.indexOf(layer); if (index < layers.length - 1) { layers.move(layer, index + 1); } }); layer.controllerElement.appendChild(moveDownHandle); 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.addButton = document.createElement('div'); layers.addButton.className = 'layer-add-button'; layers.addButton.innerHTML = ''; layers.addButton.addEventListener('click', () => { layers.add(); }); 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.refreshControllers = function() { layerControllersElement.innerHTML = ''; layers.forEach(layer => { layerControllersElement.appendChild(layer.controllerElement); }); layerControllersElement.appendChild(layers.addButton); } layers.refreshLayers = function() { easelElement.innerHTML = ''; layers.forEach(layer => { easelElement.appendChild(layer.canvas); }); } layers.refresh = function() { layers.refreshControllers(); layers.refreshLayers(); } layers.add = function() { const layer = makeLayer({ height: layers.height, width: layers.width, }); layers.push(layer); layer.activate(); layers.refresh(); } layers.delete = function(layer) { layer.canvas.deleteCanvas(); layers.splice(layers.indexOf(layer), 1); layers.refresh(); } 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 = color.color; commandBarElement.appendChild(colorPreview.element); colorPreview.update = function() { colorPreview.element.style.backgroundColor = color.color; } return colorPreview; } const colorPreview = makeColorPreview(); // }}} // COMMANDS {{{ // FACTORY {{{ function makeCommand({name, key, icon, func}) { if (!name) throw new Error('No name provided'); if (!icon) throw new Error('No icon provided'); if (!func) throw new Error('No click function provided'); if (!key) throw new Error('No key provided'); const command = {}; command.name = name; command.key = key; command.func = function() { func(); infos.update(); } command.button = makeButtonElement({ icon: icon, name: name, key: key, func: command.func, }); commandBarElement.appendChild(command.button.element); return command } function makeCommands() { const commands = []; commands.add = function({name, key, icon, func}) { const command = makeCommand({name, key, icon, func}); commands.push(command); } commands.get = function(name) { return commands.find(command => command.name === name); } commands.click = function(name) { const command = commands.get(name); command.func(); } return commands; } // }}} const commands = makeCommands(); commands.add({ // flip-horizontally {{{ name: 'flip-horizontally', key: 'f', icon: '', func: 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.restoreCanvas(); ctx.restore(); } }); // }}} commands.add({ // flip-vertically {{{ name: 'flip-vertically', key: 'v', icon: '', func: function flipCanvasVertically() { const canvas = layers.getActive().canvas; const ctx = canvas.ctx; ctx.save(); canvas.saveCanvas(); ctx.clearRect(0, 0, canvas.width, canvas.height); ctx.scale(1, -1); ctx.translate(0, -canvas.height); canvas.restoreCanvas(); ctx.restore(); } }); // }}} commands.add({ // export {{{ name: 'export', key: 'e', icon: '', func: 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: '', func: 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: '', func: function clearCanvas() { const canvas = layers.getActive().canvas; const ctx = canvas.ctx; canvas.saveCanvas(); ctx.clearRect(0, 0, canvas.width, canvas.height); ctx.fillStyle = 'white'; ctx.fillRect(0, 0, canvas.width, canvas.height); } }); // }}} commands.add({ // change-shape {{{ name: 'change-shape', key: 's', icon: ``, func: function changeShape() { const currentIndex = shapes.indexOf(brushShape); brushShape = shapes[(currentIndex + 1) % shapes.length]; } }); // }}} // }}} // 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.activate = function() { tool.active = true; tool.button.element.classList.add('active'); } tool.deactivate = function() { tool.active = false; tool.button.element.classList.remove('active'); } tool.button = makeButtonElement({ icon: tool.icon, name: tool.name, key: tool.key, func: function() { tools.activate(tool.name); } }); toolBarElement.appendChild(tool.button.element); return tool; } function makeTools() { const tools = []; tools.prevToolName = 'na'; tools.add = function({name, key, icon, mouseDown, mouseMove, mouseUp}) { const tool = makeTool({name, key, icon, mouseDown, mouseMove, mouseUp}); tools.push(tool); } tools.get = function(name) { return tools.find(tool => tool.name === name); } tools.getActive = function() { return tools.find(tool => tool.active); } tools.activate = function(name) { const tool = tools.get(name); if (tool.active) return; if (tools.getActive()) { tools.prevToolName = tools.getActive().name; tools.forEach(tool => tool.deactivate()); } tool.activate(); } tools.restore = function() { const tool = tools.get(tools.prevToolName); 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, color.color); } else { canvas.drawShape(canvasStartX, canvasStartY, brushShape, brushSize, color.color); } }, mouseMove: function(e) { const canvas = layers.getActive().canvas; if (brushSize === 1) { canvas.drawLineWithPixels(canvasStartX, canvasStartY, canvasEndX, canvasEndY, color.color); } else { canvas.drawLineWithShape(canvasStartX, canvasStartY, canvasEndX, canvasEndY, brushShape, brushSize, color.color); } canvasStartX = canvasEndX; canvasStartY = canvasEndY; }, }); // }}} tools.add({ // content-move {{{ name: 'content-move', key: 'h', icon: '', mouseDown: function(e) { const canvas = layers.getActive().canvas; canvas.saveCanvas(); }, mouseMove: function(e) { const canvas = layers.getActive().canvas; canvas.clearCanvas(); canvas.restoreCanvas(dX, dY); }, }); // }}} 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) { const canvas = layers.getActive().canvas; canvas.floodFill(canvasStartX, canvasStartY, color.color); } }); // }}} 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]})`; color.color = 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 canvasColor = canvas.getColorAtPixel(canvasStartX, canvasStartY); const distance = Math.sqrt(Math.pow(e.clientX - startX, 2) + Math.pow(e.clientY - startY, 2)); const t = Math.min(1, distance / 300); color.mix(canvasColor, t); startX = endX; startY = endY; } }); // }}} // }}} // PUCKS {{{ // FACTORY {{{ function makePuck({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) { puck.key = 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 puckColor = puck.element.style.backgroundColor; // const mixedColor = mixbox.lerp(color.color, puck.element.style.backgroundColor, t); // color.color = 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); return puck; } function makePucks() { const pucks = []; pucks.add = function({puckColor, key, editable}) { const puck = makePuck({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 {{{ // FACTORY {{{ 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 color.color; } }); infos.add({ name: 'width', updateFunction: function() { return "width"; } }); infos.add({ name: 'height', updateFunction: function() { return "height"; } }); infos.add({ name: 'shape', updateFunction: function() { return brushShape; } }); infos.add({ name: 'tool', updateFunction: function() { return tools.getActive().name; } }); // }}} // 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.active) { 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 (tools.getActive().name === '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.active) { 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; console.log(e.key); tools.forEach(tool => { if (tool.key.toLowerCase() === e.key.toLowerCase()) { tools.activate(tool.name); } }); commands.forEach(command => { if (command.key.toLowerCase() === e.key.toLowerCase()) { command.func(); } }); pucks.filter(puck => puck.key !== undefined).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) { tools.restore(); } }); isKeyDown = false; }); // }}} layers.resetPosition(); tools.activate('brush');