// CONSTANTS {{{ const toolTipElement = document.getElementById('tool-tip'); const colorTipElement = document.getElementById('color-tip'); const commandsElement = document.getElementById('commands'); const toolsElement = document.getElementById('tools'); const brushColorElement = document.getElementById('brush-color'); const canvasColorElement = document.getElementById('canvas-color'); const studioElement = document.getElementById('studio'); const easelElement = document.getElementById('easel'); const layersElement = document.getElementById('layers'); const colorsElement = document.getElementById('colors'); const dZoom = 0.001; const dBrushSize = 0.5; const dOpacity = 0.001; const initialWidth = 800; const initialHeight = 600; const maxBrushSize = 500; const tolerance = 1; // }}} // VARIABLES {{{ let brushSize = 10; 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; let interval; let startTime; // }}} // HELPERS {{{ function home() { const rect = studioElement.getBoundingClientRect(); easelElement.style.left = `${rect.left}px`; easelElement.style.top = `${rect.top}px`; } // }}} // LAYERS {{{ const layers = makeLayers({ controllerElement: layersElement, easelElement: easelElement, height: initialHeight, width: initialWidth }); layers.loadFromLocalStorage().refresh(); // }}} // COLORS {{{ const black = makeColor({r: 0, g: 0, b: 0}) const white = makeColor({r: 255, g: 255, b: 255}) const cadmiumYellow = makeColor({r: 254, g: 236, b: 0}) const hansaYellow = makeColor({r: 252, g: 211, b: 0}) const cadmiumOrange = makeColor({r: 255, g: 105, b: 0}) const cadmiumRed = makeColor({r: 255, g: 39, b: 2}) const quinacridoneMagenta = makeColor({r: 128, g: 2, b: 46}) const cobaltViolet = makeColor({r: 78, g: 0, b: 66}) const ultramarineBlue = makeColor({r: 25, g: 0, b: 89}) const cobaltBlue = makeColor({r: 0, g: 33, b: 133}) const phthaloBlue = makeColor({r: 13, g: 27, b: 68}) const phthaloGreen = makeColor({r: 0, g: 60, b: 50}) const permanentGreen = makeColor({r: 7, g: 109, b: 22}) const sapGreen = makeColor({r: 107, g: 148, b: 4}) const burntSienna = makeColor({r: 123, g: 72, b: 0}) const red = makeColor({r: 255, g: 0, b: 0}) const green = makeColor({r: 0, g: 255, b: 0}) const blue = makeColor({r: 0, g: 0, b: 255}) const cyan = makeColor({r: 0, g: 255, b: 255}) const yellow = makeColor({r: 255, g: 255, b: 0}) const magenta = makeColor({r: 255, g: 0, b: 255}) const tempColor = makeColor({r: 0, g: 0, b: 0}); const brushColor = makeColor({r: 0, g: 0, b: 0, controllerElement: brushColorElement}).refresh(); const canvasColor = makeColor({r: 0, g: 0, b: 0, controllerElement: canvasColorElement}).refresh(); // }}} // PUCKS {{{ const pucks = makePucks({ controllerElement: colorsElement, brushColor: brushColor, interval: interval }); pucks.push({color: black}); pucks.push({color: white}); pucks.push({color: cadmiumYellow}); pucks.push({color: hansaYellow}); pucks.push({color: cadmiumOrange}); pucks.push({color: cadmiumRed}); pucks.push({color: quinacridoneMagenta}); pucks.push({color: cobaltViolet}); pucks.push({color: ultramarineBlue}); pucks.push({color: cobaltBlue}); pucks.push({color: phthaloBlue}); pucks.push({color: phthaloGreen}); pucks.push({color: permanentGreen}); pucks.push({color: sapGreen}); pucks.push({color: burntSienna}); pucks.push({color: red}); pucks.push({color: green}); pucks.push({color: blue}); pucks.push({color: cyan}); pucks.push({color: yellow}); pucks.push({color: magenta}); pucks.refresh(); // }}} // COMMANDS {{{ const commands = makeCommands({ controllerElement: commandsElement }); commands.add({ // undo {{{ name: 'Undo', key: 'z', iconPath: 'icons/solid/rotate-left.svg', func: () => { layers.undo().updateActive().refresh(); } }); // }}} commands.add({ // redo {{{ name: 'Redo', key: 'y', iconPath: 'icons/solid/rotate-right.svg', func: () => { layers.redo().updateActive().refresh(); } }); // }}} commands.add({ // reset {{{ name: 'Reset', iconPath: 'icons/regular/trash-can.svg', func: () => { layers.reset().save().updateActive().refresh(); } }); // }}} commands.add({ // clear {{{ name: 'Clear', key: 'c', iconPath: 'icons/solid/broom.svg', func: () => { layers.getActive().drawCanvas.clear(); layers.save().refresh(); } }); // }}} commands.add({ // save {{{ name: 'Save', iconPath: 'icons/solid/file-arrow-down.svg', func: () => { layers.exportPng(); } }); // }}} commands.add({ // home {{{ name: 'Home', iconPath: 'icons/solid/house.svg', func: () => { home(); } }); // }}} commands.refresh(); // }}} // TOOLS {{{ const tools = makeTools({ controllerElement: toolsElement, toolTipElement: toolTipElement }); tools.push(makeTool({ // brush {{{ name: 'Brush', key: 'b', iconPath: 'icons/solid/pen.svg', mouseMove: (e) => { layers.getActive() .selectCanvas .clear() .drawEmptyCircle({ x: canvasEndX, y: canvasEndY, diameter: brushSize, color: white }) .drawPixel({ x: canvasEndX, y: canvasEndY, color: white }); }, mouseDown: (e) => { layers.getActive() .drawCanvas .drawCircle({ x: canvasEndX, y: canvasEndY, diameter: brushSize, color: brushColor }); }, mouseDrag: (e) => { layers.getActive() .drawCanvas .drawLineWithCircles({ x1: canvasStartX, y1: canvasStartY, x2: canvasEndX, y2: canvasEndY, diameter: brushSize, color: brushColor }); startX = endX; startY = endY; canvasStartX = canvasEndX; canvasStartY = canvasEndY; }, mouseUp: (e) => { layers.refreshPreviews().save(); }, mouseLeave: (e) => { layers.getActive() .selectCanvas .clear(); } })); // }}} tools.push(makeTool({ // eraser {{{ name: 'Eraser', key: 'e', iconPath: 'icons/solid/eraser.svg', mouseMove: (e) => { layers.getActive() .selectCanvas .clear() .drawEmptyCircle({ x: canvasEndX, y: canvasEndY, diameter: brushSize, color: white }) .drawPixel({ x: canvasEndX, y: canvasEndY, color: white }); }, mouseDown: (e) => { layers.getActive() .drawCanvas .drawCircle({ x: canvasEndX, y: canvasEndY, diameter: brushSize, erase: true }); }, mouseDrag: (e) => { layers.getActive() .drawCanvas .drawLineWithCircles({ x1: canvasStartX, y1: canvasStartY, x2: canvasEndX, y2: canvasEndY, diameter: brushSize, erase: true }); startX = endX; startY = endY; canvasStartX = canvasEndX; canvasStartY = canvasEndY; }, mouseUp: (e) => { layers .save() .refreshPreviews(); }, mouseLeave: (e) => { layers.getActive() .selectCanvas .clear(); } })); // }}} tools.push(makeTool({ // brush-size {{{ name: 'Brush Size', key: 's', iconPath: 'icons/regular/circle-dot.svg', mouseMove: (e) => { layers.getActive() .selectCanvas .clear() .drawEmptyCircle({ x: canvasEndX, y: canvasEndY, diameter: brushSize, color: white }) .drawPixel({ x: canvasEndX, y: canvasEndY, color: white }); }, mouseDrag: (e) => { brushSize += dX * dBrushSize; brushSize = Math.min(Math.max(brushSize, 1), maxBrushSize); startX = endX; }, mouseLeave: (e) => { layers.getActive() .selectCanvas .clear(); } })); // }}} tools.push(makeTool({ // bucket {{{ name: 'Bucket', key: 'k', iconPath: 'icons/solid/fill.svg', mouseMove: (e) => { layers.getActive() .selectCanvas .clear() .drawCrossHairs({ x: canvasEndX, y: canvasEndY, color: white }); }, mouseDown: (e) => { layers.getActive() .drawCanvas .floodFill({ x: canvasEndX, y: canvasEndY, color: brushColor }); layers.refreshPreviews().save(); }, mouseLeave: (e) => { layers.getActive() .selectCanvas .clear(); } })); // }}} tools.push(makeTool({ // move {{{ name: 'Move', key: 'm', iconPath: 'icons/solid/arrows-up-down-left-right.svg', mouseDown: (e) => { startX = e.clientX - easelElement.offsetLeft; startY = e.clientY - easelElement.offsetTop; }, mouseDrag: (e) => { easelElement.style.left = dX + 'px'; easelElement.style.top = dY + 'px'; } })); // }}} tools.push(makeTool({ // resize {{{ name: 'Resize', key: 'r', iconPath: 'icons/solid/ruler-combined.svg', mouseDrag: (e) => { let newWidth = layers.width + dX; let newHeight = layers.height + dY; layers.resize({height: newHeight, width: newWidth, save: false}); startX = endX; startY = endY; }, mouseUp: (e) => { layers.save().refreshPreviews(); } })); // }}} tools.push(makeTool({ // content-move {{{ name: 'Content Move', key: 'h', iconPath: 'icons/regular/hand.svg', mouseDown: (e) => { layers.getActive() .drawCanvas .save(); }, mouseDrag: (e) => { layers.getActive() .drawCanvas .clear() .restore({x: dX, y: dY}); }, mouseUp: (e) => { layers.save().refreshPreviews(); } })); // }}} tools.push(makeTool({ // color-mix {{{ name: 'color-mix', key: 'x', iconPath: 'icons/solid/mortar-pestle.svg', mouseMove: (e) => { layers.getActive() .selectCanvas .clear() .drawCrossHairs({ x: canvasEndX, y: canvasEndY, color: white }); }, mouseDown: function(e) { tempColor.copy({color2: canvasColor}); startTime = Date.now(); interval = setInterval(() => { if (!tempColor.match({color2: canvasColor})) { startTime = Date.now(); tempColor.copy({color2: canvasColor}); } if (!canvasColor.isOpaque()) { startTime = Date.now(); } else { const elapsedTime = Date.now() - startTime; const t = Math.min(1, elapsedTime / 10000); brushColor.mixxColor({color2: tempColor, t}).refresh(); if (!isMouseDown) { clearInterval(interval); startTime = Date.now(); } } }, 50); }, mouseUp: function(e) { clearInterval(interval); }, mouseLeave: function(e) { clearInterval(interval); } })); // }}} tools.refresh(); // }}} // MOUSE EVENTS {{{ studioElement.addEventListener('mousemove', (e) => { // {{{ endX = e.clientX; endY = e.clientY; dX = endX - startX; dY = endY - startY; const canvas = layers.getActive().drawCanvas; canvasEndX = canvas.getPositionOnCanvas(e).x; canvasEndY = canvas.getPositionOnCanvas(e).y; canvasDX = canvasEndX - canvasStartX; canvasDY = canvasEndY - canvasStartY; canvasColor.copy({color2: canvas.getPixel({x: canvasEndX, y: canvasEndY})}).refresh(); if (tools.active) { if (tools.active.mouseMove) { tools.active.mouseMove(e); } if (isMouseDown) { if (tools.active.mouseDrag) { tools.active.mouseDrag(e); } } } toolTipElement.style.display = 'block'; toolTipElement.style.left = `${e.clientX + 3}px`; toolTipElement.style.top = `${e.clientY - 16 - 3}px`; }); // }}} studioElement.addEventListener('mousedown', (e) => { // {{{ isMouseDown = true; startX = e.clientX; startY = e.clientY; const canvas = layers[0].selectCanvas; canvasStartX = canvas.getPositionOnCanvas(e).x; canvasStartY = canvas.getPositionOnCanvas(e).y; if (tools.active) { if (tools.active.mouseDown) { tools.active.mouseDown(e); } } }); // }}} studioElement.addEventListener('mouseup', (e) => { // {{{ isMouseDown = false; startX = e.clientX; startY = e.clientY; const canvas = layers[0].selectCanvas; canvasStartX = canvas.getPositionOnCanvas(e).x; canvasStartY = canvas.getPositionOnCanvas(e).y; if (tools.active) { if (tools.active.mouseUp) { tools.active.mouseUp(e); } } }); // }}} studioElement.addEventListener('mouseleave', (e) => { // {{{ isMouseDown = false; startX = e.clientX; startY = e.clientY; const canvas = layers[0].selectCanvas; canvasStartX = canvas.getPositionOnCanvas(e).x; canvasStartY = canvas.getPositionOnCanvas(e).y; if (tools.active) { if (tools.active.mouseLeave) { tools.active.mouseLeave(e); } } toolTipElement.style.display = 'none'; }); // }}} // }}} // KEY EVENTS {{{ document.addEventListener('keydown', (e) => { if (isKeyDown) { return; } tools.forEach(tool => { if (tool.key === e.key.toLowerCase()) { console.log(`Key down: ${e.key} activates tool: ${tool.name}`); tools.activate({tool}).refresh(); layers.getActive().selectCanvas.clear(); isKeyDown = true; } }); commands.forEach(command => { if (command.key) { if (command.key.toLowerCase() === e.key.toLowerCase()) { command.func(); } } }); }); document.addEventListener('keyup', (e) => { tools.forEach(tool => { if (tool.key === e.key.toLowerCase()) { isKeyDown = false; if (tool.key === e.key) { console.log(`Key up: ${e.key} reverts from tool: ${tool.name}`); tools.revert().refresh(); layers.getActive().selectCanvas.clear(); } } }); }); // }}} // INIT {{{ home(); tools.activateByName({name: 'Brush'}).refresh(); // }}}