diff --git a/render.js b/render.js
index 87ba792..2534771 100644
--- a/render.js
+++ b/render.js
@@ -20,12 +20,9 @@ const shapes = ['circle', 'square'];
// VARS {{{
-let brushColor = 'rgb(0, 0, 0)';
let brushShape = 'circle'
-let brushSize = 15;
+let brushSize = 10;
let zoom = 1;
-let currentTool;
-let prevTool = 'brush';
let startX = 0;
let startY = 0;
@@ -43,6 +40,8 @@ let canvasDY = 0;
let isKeyDown = false;
let isMouseDown = false;
+let interval;
+
// }}}
// HELPERS {{{
@@ -57,22 +56,19 @@ function disableImageSmoothing(ctx) {
};
function hexToRgbArray(hex) {
- if (hex.startsWith('#')) {
- hex = hex.slice(1);
- }
-
- if (hex.length === 3) {
- hex = hex.split('').map(char => char + char).join('');
- }
-
+ hex = hex.replace(/^#/, '');
const bigint = parseInt(hex, 16);
- return [(bigint >> 16) & 255, (bigint >> 8) & 255, bigint & 255];
+ 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[2] - color2[2]) <= tolerance &&
+ Math.abs(color1[3] - color2[3]) <= tolerance; // Include alpha comparison
}
function makeIconElement(htmlString) {
@@ -117,6 +113,38 @@ function makeButtonElement({icon, name, func, key}) {
// }}}
+// 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;
+ colorPreview.update();
+ }
+
+ return color;
+}
+
+const color = makeColor('rgb(0, 0, 0)');
+
+// }}}
+
// LAYERS {{{
// FACTORY {{{
@@ -203,7 +231,7 @@ function makeCanvas({height=600, width=800}) { // {{{
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 / 2);
+ let radius = Math.floor(size / 1);
let radiusSquared = radius * radius;
for (let y1 = -radius; y1 <= radius; y1++) {
@@ -260,25 +288,25 @@ function makeCanvas({height=600, width=800}) { // {{{
canvas.ctx.fillRect(0, 0, canvas.width, canvas.height);
}
- canvas.getColorAtPixel = function(data, x, y) {
+ canvas.getColorAtPixelData = function(x, y, data) {
const index = (y * canvas.width + x) * 4;
- return [data[index], data[index + 1], data[index + 2], data[index + 3]];
+ const color = [data[index], data[index + 1], data[index + 2], data[index + 3]];
+ return color;
}
- canvas.setColorAtPixel = function(data, x, y, 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] = 255;
+ 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.getColorAtPixel(data, x, y);
+ const targetColor = canvas.getColorAtPixelData(x, y, data);
const fillColorArray = hexToRgbArray(color);
if (colorsMatch(targetColor, fillColorArray, tolerance)) {
@@ -289,10 +317,10 @@ function makeCanvas({height=600, width=800}) { // {{{
while (stack.length > 0) {
const {x, y} = stack.pop();
- const currentColor = canvas.getColorAtPixel(data, x, y);
+ const currentColor = canvas.getColorAtPixelData(x, y, data);
if (colorsMatch(currentColor, targetColor, tolerance)) {
- canvas.setColorAtPixel(data, x, y, fillColorArray);
+ 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});
@@ -304,6 +332,19 @@ function makeCanvas({height=600, width=800}) { // {{{
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}`;
@@ -491,11 +532,13 @@ function makeColorPreview() {
colorPreview.element = document.createElement('div');
colorPreview.element.id = 'color-preview';
colorPreview.element.className = 'puck';
- colorPreview.element.style.backgroundColor = brushColor;
+ colorPreview.element.style.backgroundColor = color.color;
commandBarElement.appendChild(colorPreview.element);
colorPreview.update = function() {
- colorPreview.element.style.backgroundColor = brushColor;
+ colorPreview.element.style.backgroundColor = color.color;
}
+
+ return colorPreview;
}
const colorPreview = makeColorPreview();
@@ -674,7 +717,6 @@ function makeTool({name, key, icon, mouseDown, mouseMove, mouseUp}) {
tool.active = false;
tool.activate = function() {
- currentTool = tool.name;
tool.active = true;
tool.button.element.classList.add('active');
}
@@ -701,6 +743,8 @@ function makeTool({name, key, icon, mouseDown, mouseMove, mouseUp}) {
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);
@@ -710,8 +754,22 @@ function makeTools() {
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();
}
@@ -730,17 +788,17 @@ tools.add({ // brush {{{
mouseDown: function(e) {
const canvas = layers.getActive().canvas;
if (brushSize === 1) {
- canvas.drawPixel(canvasStartX, canvasStartY, brushColor);
+ canvas.drawPixel(canvasStartX, canvasStartY, color.color);
} else {
- canvas.drawShape(canvasStartX, canvasStartY, brushShape, brushSize, brushColor);
+ 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, brushColor);
+ canvas.drawLineWithPixels(canvasStartX, canvasStartY, canvasEndX, canvasEndY, color.color);
} else {
- canvas.drawLineWithShape(canvasStartX, canvasStartY, canvasEndX, canvasEndY, brushShape, brushSize, brushColor);
+ canvas.drawLineWithShape(canvasStartX, canvasStartY, canvasEndX, canvasEndY, brushShape, brushSize, color.color);
}
canvasStartX = canvasEndX;
canvasStartY = canvasEndY;
@@ -797,7 +855,7 @@ tools.add({ // bucket-fill {{{
icon: '',
mouseDown: function(e) {
const canvas = layers.getActive().canvas;
- canvas.floodFill(canvasStartX, canvasStartY, brushColor);
+ canvas.floodFill(canvasStartX, canvasStartY, color.color);
}
}); // }}}
@@ -809,7 +867,7 @@ tools.add({ // color-picker {{{
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;
+ color.color = pickedColor;
colorPreview.update();
}
}); // }}}
@@ -847,25 +905,29 @@ tools.add({ // resize {{{
}
}); // }}}
+
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;
+ mouseDown: function(e) {
+ const startTime = Date.now();
+ const canvas = layers.getActive().canvas;
+ interval = setInterval(() => {
+ let canvasColor = canvas.getColorAtPixel(canvasEndX, canvasEndX);
+ console.log({canvasEndX, canvasEndY, canvasColor});
+ const elapsedTime = Date.now() - startTime;
+ const t = Math.min(1, elapsedTime / 10000);
+ color.mix(canvasColor, t);
+ }, 50);
+ },
+ mouseUp: function(e) {
+ clearInterval(interval);
+ },
+ mouseLeave: function(e) {
+ clearInterval(interval);
+ }
- startX = e.clientX;
- startY = e.clientY;
- }
}); // }}}
// }}}
@@ -901,38 +963,32 @@ function makePuck({puckColor, key, editable=true}) {
puck.element.appendChild(keyHint);
}
- function mixx(startTime) {
- var interval = setInterval(() => {
+
+ puck.element.addEventListener('mousedown', (e) => {
+ const startTime = Date.now();
+ 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();
+ color.mix(puck.element.style.backgroundColor, t);
}, 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.element.addEventListener('mouseup', (e) => {
+ clearInterval(interval);
});
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);
+ const startTime = Date.now();
+ var interval = setInterval(() => {
+ const elapsedTime = Date.now() - startTime;
+ const t = Math.min(1, elapsedTime / 10000);
+ color.mix(puck.element.style.backgroundColor, t);
+ }, 50);
+ function onKeyUp() {
+ clearInterval(interval);
+ document.removeEventListener('keyup', onKeyUp);
}
+ document.addEventListener('keyup', onKeyUp);
}
commandBarElement.appendChild(puck.element);
@@ -1078,7 +1134,7 @@ infos.add({
infos.add({
name: 'color',
updateFunction: function() {
- return brushColor;
+ return color.color;
}
});
@@ -1103,6 +1159,13 @@ infos.add({
}
});
+infos.add({
+ name: 'tool',
+ updateFunction: function() {
+ return tools.getActive().name;
+ }
+});
+
// }}}
// MOUSE EVENT LISTENERS {{{
@@ -1114,10 +1177,12 @@ studioElement.addEventListener('mousedown', (e) => {
startY = e.clientY;
canvasStartX = canvas.getPositionOnCanvas(e).x;
canvasStartY = canvas.getPositionOnCanvas(e).y;
+ canvasEndX = canvas.getPositionOnCanvas(e).x;
+ canvasEndX = canvas.getPositionOnCanvas(e).y;
for (var i = 0; i < tools.length; i++) {
var tool = tools[i];
- if (tool.name === currentTool) {
+ if (tool.active) {
if (tool.mouseDown) {
tool.mouseDown(e);
break;
@@ -1140,7 +1205,7 @@ studioElement.addEventListener('mousemove', (e) => {
canvasDX = canvasEndX - canvasStartX;
canvasDY = canvasEndY - canvasStartY;
- if (currentTool == 'brush-size') {
+ if (tools.getActive().name === 'brush-size') {
brushPreviewElement.style.display = 'block';
brushPreviewElement.style.width = brushSize + 'px';
brushPreviewElement.style.height = brushSize + 'px';
@@ -1151,7 +1216,7 @@ studioElement.addEventListener('mousemove', (e) => {
if (isMouseDown) {
for (var i = 0; i < tools.length; i++) {
var tool = tools[i];
- if (tool.name === currentTool) {
+ if (tool.active) {
if (tool.mouseMove) {
tool.mouseMove(e);
break;
@@ -1166,11 +1231,33 @@ studioElement.addEventListener('mousemove', (e) => {
studioElement.addEventListener('mouseup', () => {
isMouseDown = false;
+
+ for (var i = 0; i < tools.length; i++) {
+ var tool = tools[i];
+ if (tool.active) {
+ if (tool.mouseUp) {
+ tool.mouseUp();
+ break;
+ }
+ }
+ }
+
infos.update();
});
studioElement.addEventListener('mouseleave', () => {
isMouseDown = false;
+
+ for (var i = 0; i < tools.length; i++) {
+ var tool = tools[i];
+ if (tool.active) {
+ if (tool.mouseLeave) {
+ tool.mouseLeave();
+ break;
+ }
+ }
+ }
+
brushPreviewElement.style.display = 'none';
infos.update();
});
@@ -1184,8 +1271,7 @@ document.addEventListener('keydown', (e) => {
tools.forEach(tool => {
if (tool.key.toLowerCase() === e.key.toLowerCase()) {
- prevTool = currentTool;
- currentTool = tool.name;
+ tools.activate(tool.name);
}
});
@@ -1208,7 +1294,7 @@ document.addEventListener('keydown', (e) => {
document.addEventListener('keyup', (e) => {
tools.forEach(tool => {
if (tool.key.toLowerCase() === e.key) {
- currentTool = prevTool;
+ tools.restore();
}
});
diff --git a/temp2.js b/temp2.js
new file mode 100644
index 0000000..1fcc457
--- /dev/null
+++ b/temp2.js
@@ -0,0 +1,1282 @@
+// 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');