|
|
|
// 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 dZoom = 0.001;
|
|
|
|
const dBrushSize = 0.5;
|
|
|
|
const dOpacity = 0.001;
|
|
|
|
const initialWidth = 800;
|
|
|
|
const initialHeight = 600;
|
|
|
|
const maxBrushSize = 500;
|
|
|
|
const tolerance = 1;
|
|
|
|
const shapes = ['circle', 'square'];
|
|
|
|
|
|
|
|
// }}}
|
|
|
|
|
|
|
|
// VARS {{{
|
|
|
|
|
|
|
|
let brushShape = 'circle'
|
|
|
|
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;
|
|
|
|
var startTime;
|
|
|
|
|
|
|
|
// }}}
|
|
|
|
|
|
|
|
// HELPERS {{{
|
|
|
|
|
|
|
|
function disableImageSmoothing(ctx) {
|
|
|
|
ctx.imageSmoothingEnabled = false;
|
|
|
|
if (ctx.imageSmoothingEnabled !== false) {
|
|
|
|
ctx.mozImageSmoothingEnabled = false;
|
|
|
|
ctx.webkitImageSmoothingEnabled = false;
|
|
|
|
ctx.msImageSmoothingEnabled = false;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
function hexToRgbArray(hex) {
|
|
|
|
const r = parseInt(hex.substring(1, 3), 16);
|
|
|
|
const g = parseInt(hex.substring(3, 5), 16);
|
|
|
|
const b = parseInt(hex.substring(5, 7), 16);
|
|
|
|
return [r, g, b, 255];
|
|
|
|
}
|
|
|
|
|
|
|
|
function colorsMatch(color1, color2, tolerance = 0) {
|
|
|
|
return color1[0] === color2[0] && color1[1] === color2[1] && color1[2] === color2[2] && color1[3] === color2[3];
|
|
|
|
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 closeRgbArray(color1, color2, tolerance) {
|
|
|
|
return Math.abs(color1[0] - color2[0]) <= tolerance && Math.abs(color1[1] - color2[1]) <= tolerance && Math.abs(color1[2] - color2[2]) <= tolerance;
|
|
|
|
}
|
|
|
|
|
|
|
|
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.classList.add('button');
|
|
|
|
button.element.classList.add('bar-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.fromRgba = function(rgba) {
|
|
|
|
color.r = rgba.split(',')[0].split('(')[1];
|
|
|
|
color.g = rgba.split(',')[1];
|
|
|
|
color.b = rgba.split(',')[2];
|
|
|
|
color.a = rgba.split(',')[3].split(')')[0];
|
|
|
|
}
|
|
|
|
|
|
|
|
color.toRgba = function() {
|
|
|
|
return `rgba(${color.r}, ${color.g}, ${color.b}, ${color.a})`;
|
|
|
|
}
|
|
|
|
|
|
|
|
color.fromRgb = function(rgb) {
|
|
|
|
color.r = rgb.split(',')[0].split('(')[1];
|
|
|
|
color.g = rgb.split(',')[1];
|
|
|
|
color.b = rgb.split(',')[2].split(')')[0];
|
|
|
|
color.a = 255;
|
|
|
|
}
|
|
|
|
|
|
|
|
color.toRgb = function() {
|
|
|
|
return `rgb(${color.r}, ${color.g}, ${color.b})`;
|
|
|
|
}
|
|
|
|
|
|
|
|
color.fromHex = function(hex) {
|
|
|
|
color.r = parseInt(hex.substring(1, 3), 16);
|
|
|
|
color.g = parseInt(hex.substring(3, 5), 16);
|
|
|
|
color.b = parseInt(hex.substring(5, 7), 16);
|
|
|
|
color.a = 255;
|
|
|
|
}
|
|
|
|
|
|
|
|
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.fromRgbaArray = function(array) {
|
|
|
|
color.r = array[0];
|
|
|
|
color.g = array[1];
|
|
|
|
color.b = array[2];
|
|
|
|
color.a = array[3];
|
|
|
|
}
|
|
|
|
|
|
|
|
color.toRgbaArray = function() {
|
|
|
|
return [color.r, color.g, color.b, color.a];
|
|
|
|
}
|
|
|
|
|
|
|
|
color.fromRgbArray = function(array) {
|
|
|
|
color.r = array[0];
|
|
|
|
color.g = array[1];
|
|
|
|
color.b = array[2];
|
|
|
|
color.a = 255;
|
|
|
|
}
|
|
|
|
|
|
|
|
color.toRgbArray = function() {
|
|
|
|
return [color.r, color.g, color.b];
|
|
|
|
}
|
|
|
|
|
|
|
|
color.mixxRgbArray = function(color2RgbArray, t) {
|
|
|
|
const color1RgbArray = color.toRgbArray();
|
|
|
|
if (color1RgbArray === color2RgbArray) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
var newColorRgbArray = color2RgbArray;
|
|
|
|
if (!closeRgbArray(color1RgbArray, color2RgbArray, 2)) {
|
|
|
|
console.log('mixxing');
|
|
|
|
newColorRgbArray = mixbox.lerp(color1RgbArray, color2RgbArray, t);
|
|
|
|
}
|
|
|
|
color.fromRgbArray(newColorRgbArray);
|
|
|
|
}
|
|
|
|
|
|
|
|
color.mixxRgb = function(color2Rgb, t) {
|
|
|
|
const result = color2Rgb.match(/rgb\((\d+), (\d+), (\d+)\)/);
|
|
|
|
if (result) {
|
|
|
|
const color2RgbArray = [result[1], result[2], result[3]];
|
|
|
|
color.mixxRgbArray(color2RgbArray, t);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
color.mixxRgbaArray = function(color2RgbaArray, t) {
|
|
|
|
const color2a = color2RgbaArray[3];
|
|
|
|
if (color2a !== 225) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
const color2RgbArray = color2RgbaArray.slice(0, 3);
|
|
|
|
color.mixxRgbArray(color2RgbArray, t);
|
|
|
|
}
|
|
|
|
|
|
|
|
color.mixxRgba = function(color2Rgba, t) {
|
|
|
|
const result = color2Rgba.match(/rgba\((\d+), (\d+), (\d+), (\d+)\)/);
|
|
|
|
if (result) {
|
|
|
|
const color2RgbaArray = [result[1], result[2], result[3], result[4]];
|
|
|
|
color.mixxRgbaArray(color2RgbaArray, t);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
color.mixx = function(color2, t) {
|
|
|
|
if (color2.a === 255) {
|
|
|
|
color.mixxRgbArray(color2.toRgbArray(), t);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
color.isOpaque = function() {
|
|
|
|
return color.a === 255;
|
|
|
|
}
|
|
|
|
|
|
|
|
color.match = function(color2) {
|
|
|
|
return color.r === color2.r && color.g === color2.g && color.b === color2.b && color.a === color2.a;
|
|
|
|
}
|
|
|
|
|
|
|
|
color.copy = function(color2) {
|
|
|
|
color.r = color2.r;
|
|
|
|
color.g = color2.g;
|
|
|
|
color.b = color2.b;
|
|
|
|
color.a = color2.a;
|
|
|
|
}
|
|
|
|
|
|
|
|
color.fromRgb(rgb);
|
|
|
|
|
|
|
|
return color;
|
|
|
|
}
|
|
|
|
|
|
|
|
const brushColor = makeColor('rgb(0, 0, 0)');
|
|
|
|
const canvasColor = makeColor('rgb(0, 0, 0)');
|
|
|
|
const tempColor = makeColor('rgb(0, 0, 0)');
|
|
|
|
|
|
|
|
// }}}
|
|
|
|
|
|
|
|
// LAYERS {{{
|
|
|
|
|
|
|
|
// FACTORY {{{
|
|
|
|
|
|
|
|
function makeCanvas({height=600, width=800, background=false}) { // {{{
|
|
|
|
const canvas = document.createElement('canvas');
|
|
|
|
canvas.style.imageRendering = 'pixelated';
|
|
|
|
canvas.ctx = canvas.getContext('2d');
|
|
|
|
canvas.background = background;
|
|
|
|
|
|
|
|
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() {
|
|
|
|
if (!canvas.background) {
|
|
|
|
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.saveCanvas();
|
|
|
|
canvas.clearCanvas();
|
|
|
|
canvas.width = width;
|
|
|
|
canvas.height = height;
|
|
|
|
canvas.style.width = width * zoom + 'px';
|
|
|
|
canvas.style.height = height * zoom + 'px';
|
|
|
|
disableImageSmoothing(canvas.ctx);
|
|
|
|
canvas.restoreCanvas();
|
|
|
|
}
|
|
|
|
|
|
|
|
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) {
|
|
|
|
if (!canvas.background) {
|
|
|
|
canvas.ctx.fillStyle = color;
|
|
|
|
canvas.ctx.fillRect(x, y, 1, 1);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
canvas.drawLineWithPixels = function(x1, y1, x2, y2, color) {
|
|
|
|
if (!canvas.background) {
|
|
|
|
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 (!canvas.background) {
|
|
|
|
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 / 2);
|
|
|
|
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) {
|
|
|
|
if (!canvas.background) {
|
|
|
|
const dx = x2 - x1;
|
|
|
|
const dy = y2 - y1;
|
|
|
|
const distance = Math.sqrt(dx * dx + dy * dy);
|
|
|
|
const steps = Math.ceil(distance / (size / 3));
|
|
|
|
|
|
|
|
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.getRgbaArrayColorAtPixelData = 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.setRgbaArrayColorAtPixelData = 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, colorRgbaArray) {
|
|
|
|
if (!canvas.background) {
|
|
|
|
const imageData = canvas.ctx.getImageData(0, 0, canvas.width, canvas.height);
|
|
|
|
const data = imageData.data;
|
|
|
|
|
|
|
|
const targetColor = canvas.getRgbaArrayColorAtPixelData(x, y, data);
|
|
|
|
const fillColorArray = colorRgbaArray;
|
|
|
|
|
|
|
|
if (colorsMatch(targetColor, fillColorArray, tolerance)) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
const stack = [{x, y}];
|
|
|
|
|
|
|
|
while (stack.length > 0) {
|
|
|
|
const {x, y} = stack.pop();
|
|
|
|
const currentColor = canvas.getRgbaArrayColorAtPixelData(x, y, data);
|
|
|
|
|
|
|
|
if (colorsMatch(currentColor, targetColor, tolerance)) {
|
|
|
|
canvas.setRgbaArrayColorAtPixelData(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.getRgbaColorArrayAtPixel = function(x, y) {
|
|
|
|
const data = canvas.ctx.getImageData(0, 0, canvas.width, canvas.height).data;
|
|
|
|
return canvas.getRgbaArrayColorAtPixelData(x, y, data);
|
|
|
|
}
|
|
|
|
|
|
|
|
canvas.getRgbColorAtPixel = function(x, y) {
|
|
|
|
const color = canvas.getRgbaColorArrayAtPixel(x, y);
|
|
|
|
return `rgb(${color[0]}, ${color[1]}, ${color[2]})`;
|
|
|
|
}
|
|
|
|
|
|
|
|
canvas.setColorAtPixel = function(x, y, color) {
|
|
|
|
const imageData = canvas.ctx.getImageData(0, 0, canvas.width, canvas.height);
|
|
|
|
const data = imageData.data;
|
|
|
|
canvas.setRgbaArrayColorAtPixelData(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() {
|
|
|
|
if (!background) {
|
|
|
|
canvas.remove();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
canvas.add = function(canvas2) {
|
|
|
|
canvas.ctx.drawImage(canvas2, 0, 0);
|
|
|
|
}
|
|
|
|
|
|
|
|
canvas.setWidth(width);
|
|
|
|
canvas.setHeight(height);
|
|
|
|
|
|
|
|
return canvas;
|
|
|
|
|
|
|
|
} // }}}
|
|
|
|
|
|
|
|
function makeLayer({height=600, width=800, background=undefined}) { // {{{
|
|
|
|
const layer = {}
|
|
|
|
layer.canvas = makeCanvas({height, width, background});
|
|
|
|
layer.active = false;
|
|
|
|
layer.opacity = 1;
|
|
|
|
layer.background = background;
|
|
|
|
|
|
|
|
layer.controllerElement = document.createElement('div');
|
|
|
|
layer.controllerElement.className = 'layer-controller';
|
|
|
|
|
|
|
|
layer.previewElement = document.createElement('img');
|
|
|
|
layer.previewElement.className = 'layer-preview';
|
|
|
|
layer.previewElement.src = layer.canvas.toDataURL();
|
|
|
|
layer.previewElement.addEventListener('click', () => {
|
|
|
|
layers.setActive(layer);
|
|
|
|
});
|
|
|
|
layer.controllerElement.appendChild(layer.previewElement);
|
|
|
|
|
|
|
|
if (!layer.background) {
|
|
|
|
|
|
|
|
layer.moveButtons = document.createElement('div');
|
|
|
|
layer.moveButtons.classList.add('button');
|
|
|
|
layer.moveButtons.classList.add('layer-move-buttons');
|
|
|
|
layer.moveButtons.className = 'layer-move-buttons';
|
|
|
|
|
|
|
|
layer.moveUpButton = document.createElement('div');
|
|
|
|
layer.moveUpButton.classList.add('button');
|
|
|
|
layer.moveUpButton.classList.add('layer-move-button');
|
|
|
|
layer.moveUpButton.innerHTML = '<i class="fa-solid fa-arrow-up"></i>';
|
|
|
|
layer.moveUpButton.addEventListener('click', () => {
|
|
|
|
layers.moveUp(layer);
|
|
|
|
});
|
|
|
|
|
|
|
|
layer.moveButtons.appendChild(layer.moveUpButton);
|
|
|
|
|
|
|
|
layer.moveDownButton = document.createElement('div');
|
|
|
|
layer.moveDownButton.classList.add('button');
|
|
|
|
layer.moveDownButton.classList.add('layer-move-button');
|
|
|
|
layer.moveDownButton.innerHTML = '<i class="fa-solid fa-arrow-down"></i>';
|
|
|
|
layer.moveDownButton.addEventListener('click', () => {
|
|
|
|
layers.moveDown(layer);
|
|
|
|
});
|
|
|
|
|
|
|
|
layer.moveButtons.appendChild(layer.moveDownButton);
|
|
|
|
|
|
|
|
layer.controllerElement.appendChild(layer.moveButtons);
|
|
|
|
|
|
|
|
layer.mergeButtons = document.createElement('div');
|
|
|
|
layer.mergeButtons.classList.add('button');
|
|
|
|
layer.mergeButtons.classList.add('layer-merge-buttons');
|
|
|
|
layer.mergeButtons.className = 'layer-merge-buttons';
|
|
|
|
|
|
|
|
layer.mergeUpButton = document.createElement('div');
|
|
|
|
layer.mergeUpButton.classList.add('button');
|
|
|
|
layer.mergeUpButton.classList.add('layer-merge-button');
|
|
|
|
layer.mergeUpButton.innerHTML = '<i class="fa-solid fa-angles-up"></i>';
|
|
|
|
layer.mergeUpButton.addEventListener('click', () => {
|
|
|
|
layers.mergeUp(layer);
|
|
|
|
});
|
|
|
|
|
|
|
|
layer.mergeButtons.appendChild(layer.mergeUpButton);
|
|
|
|
|
|
|
|
layer.controllerElement.appendChild(layer.mergeButtons);
|
|
|
|
|
|
|
|
layer.deleteButton = document.createElement('div');
|
|
|
|
layer.deleteButton.classList.add('button');
|
|
|
|
layer.deleteButton.classList.add('layer-delete-button');
|
|
|
|
layer.deleteButton.innerHTML = '<i class="fa-solid fa-trash-can"></i>';
|
|
|
|
layer.deleteButton.addEventListener('click', () => {
|
|
|
|
layers.delete(layer);
|
|
|
|
});
|
|
|
|
|
|
|
|
layer.controllerElement.appendChild(layer.deleteButton);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
layer.activate = function() {
|
|
|
|
layer.active = true;
|
|
|
|
layer.controllerElement.classList.add('active');
|
|
|
|
}
|
|
|
|
|
|
|
|
layer.deactivate = function() {
|
|
|
|
layer.active = false;
|
|
|
|
layer.controllerElement.classList.remove('active');
|
|
|
|
}
|
|
|
|
|
|
|
|
layer.refreshPreview = function() {
|
|
|
|
layer.previewElement.src = layer.canvas.toDataURL();
|
|
|
|
}
|
|
|
|
|
|
|
|
layer.changeOpacity = function(opacity) {
|
|
|
|
console.log({opacity});
|
|
|
|
layer.opacity = opacity;
|
|
|
|
layer.canvas.style.opacity = opacity
|
|
|
|
}
|
|
|
|
|
|
|
|
return layer;
|
|
|
|
} // }}}
|
|
|
|
|
|
|
|
function makeLayers({height=600, width=800, backgroundColor='rgb(255, 255, 255)'}) { // {{{
|
|
|
|
const layers = [];
|
|
|
|
layers.height = height;
|
|
|
|
layers.width = width;
|
|
|
|
|
|
|
|
layers.addButton = document.createElement('div');
|
|
|
|
layers.addButton.classList.add('button');
|
|
|
|
layers.addButton.classList.add('layer-add-button');
|
|
|
|
layers.addButton.innerHTML = '<i class="fa-solid fa-plus"></i>';
|
|
|
|
layers.addButton.addEventListener('click', () => {
|
|
|
|
layers.add();
|
|
|
|
});
|
|
|
|
|
|
|
|
layers.setHeight = function(height) {
|
|
|
|
layers.height = height;
|
|
|
|
layers.forEach(layer => layer.canvas.setHeight(height));
|
|
|
|
easelElement.style.height = height + 2 + 'px';
|
|
|
|
}
|
|
|
|
|
|
|
|
layers.setHeight(height);
|
|
|
|
|
|
|
|
layers.setWidth = function(width) {
|
|
|
|
layers.width = width;
|
|
|
|
layers.forEach(layer => layer.canvas.setWidth(width));
|
|
|
|
easelElement.style.width = width + 2 + 'px';
|
|
|
|
}
|
|
|
|
|
|
|
|
layers.setWidth(width);
|
|
|
|
|
|
|
|
layers.resize = function(width, height) {
|
|
|
|
layers.height = height;
|
|
|
|
layers.width = width;
|
|
|
|
easelElement.style.height = height * zoom + 2 + 'px';
|
|
|
|
easelElement.style.width = width * zoom + 2 + 'px';
|
|
|
|
layers.forEach(layer => layer.canvas.resize(width, height));
|
|
|
|
layers[0].canvas.fill(backgroundColor);
|
|
|
|
}
|
|
|
|
|
|
|
|
layers.zoom = function(newZoom) {
|
|
|
|
easelElement.style.height = layers.height * zoom + 2 + 'px';
|
|
|
|
easelElement.style.width = layers.width * zoom + 2 + 'px';
|
|
|
|
layers.forEach(layer => {
|
|
|
|
layer.canvas.style.height = layers.height * zoom + 'px';
|
|
|
|
layer.canvas.style.width = layers.width * zoom + 'px';
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
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.refreshPreviews = function() {
|
|
|
|
layers.forEach(layer => {
|
|
|
|
layer.refreshPreview();
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
layers.refresh = function() {
|
|
|
|
layers.refreshControllers();
|
|
|
|
layers.refreshLayers();
|
|
|
|
layers.refreshPreviews();
|
|
|
|
}
|
|
|
|
|
|
|
|
layers.add = function() {
|
|
|
|
const layer = makeLayer({
|
|
|
|
height: layers.height,
|
|
|
|
width: layers.width,
|
|
|
|
});
|
|
|
|
layers.push(layer);
|
|
|
|
layers.setActive(layer);
|
|
|
|
layers.refresh();
|
|
|
|
}
|
|
|
|
|
|
|
|
layers.delete = function(layer) {
|
|
|
|
if (!layer.background) {
|
|
|
|
layer.canvas.deleteCanvas();
|
|
|
|
layers.splice(layers.indexOf(layer), 1);
|
|
|
|
layers.refresh();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
layers.deleteAll = function() {
|
|
|
|
layers.forEach(function(layer) {
|
|
|
|
if (!layer.background) {
|
|
|
|
layer.canvas.deleteCanvas();
|
|
|
|
layers.splice(layers.indexOf(layer), 1);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
layers.moveDown = function(layer) {
|
|
|
|
if (layer.background) return;
|
|
|
|
if (layers.indexOf(layer) === layers.length - 1) return;
|
|
|
|
const index = layers.indexOf(layer);
|
|
|
|
const temp = layers[index + 1];
|
|
|
|
layers[index + 1] = layer;
|
|
|
|
layers[index] = temp;
|
|
|
|
layers.refresh();
|
|
|
|
}
|
|
|
|
|
|
|
|
layers.moveUp = function(layer) {
|
|
|
|
if (layer.background) return;
|
|
|
|
if (layers.indexOf(layer) === 1) return;
|
|
|
|
const index = layers.indexOf(layer);
|
|
|
|
const temp = layers[index - 1];
|
|
|
|
layers[index - 1] = layer;
|
|
|
|
layers[index] = temp;
|
|
|
|
layers.refresh();
|
|
|
|
}
|
|
|
|
|
|
|
|
layers.mergeUp = function(layer) {
|
|
|
|
if (layer.background) return;
|
|
|
|
const index = layers.indexOf(layer);
|
|
|
|
const belowLayer = layers[index - 1];
|
|
|
|
if (belowLayer.background) return;
|
|
|
|
belowLayer.canvas.add(layer.canvas);
|
|
|
|
layers.delete(layer);
|
|
|
|
layers.setActive(belowLayer);
|
|
|
|
}
|
|
|
|
|
|
|
|
layers.mergeDown = function(layer) {
|
|
|
|
if (layer.background) return;
|
|
|
|
const index = layers.indexOf(layer);
|
|
|
|
if (index === layers.length - 1) return;
|
|
|
|
const aboveLayer = layers[index + 1];
|
|
|
|
aboveLayer.canvas.add(layer.canvas);
|
|
|
|
layers.delete(layer);
|
|
|
|
layers.setActive(aboveLayer);
|
|
|
|
}
|
|
|
|
|
|
|
|
layers.mergeAll = function() {
|
|
|
|
const backgroundLayer = layers[0];
|
|
|
|
layers.forEach(layer => {
|
|
|
|
if (layer !== backgroundLayer) {
|
|
|
|
backgroundLayer.canvas.add(layer.canvas);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
layers.deleteAll();
|
|
|
|
}
|
|
|
|
|
|
|
|
layers.tempMergeAll = function() {
|
|
|
|
const backgroundLayerCopy = makeLayer({
|
|
|
|
height: layers.height,
|
|
|
|
width: layers.width,
|
|
|
|
background: true,
|
|
|
|
});
|
|
|
|
// backgroundLayerCopy.canvas.fill(backgroundColor);
|
|
|
|
layers.forEach(layer => {
|
|
|
|
if (!layer.background) {
|
|
|
|
backgroundLayerCopy.canvas.add(layer.canvas);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
return backgroundLayerCopy;
|
|
|
|
}
|
|
|
|
|
|
|
|
layers.setActive = function(layer) {
|
|
|
|
layers.forEach(layer => layer.deactivate());
|
|
|
|
layer.activate();
|
|
|
|
}
|
|
|
|
|
|
|
|
layers.getActive = function() {
|
|
|
|
return layers.find(layer => layer.active);
|
|
|
|
}
|
|
|
|
|
|
|
|
layers.push(makeLayer({height, width, background: true}));
|
|
|
|
layers[0].canvas.fill(backgroundColor);
|
|
|
|
|
|
|
|
return layers;
|
|
|
|
} // }}}
|
|
|
|
|
|
|
|
// }}}
|
|
|
|
|
|
|
|
const layers = makeLayers({height: initialHeight, width: initialWidth});
|
|
|
|
layers.add();
|
|
|
|
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.toRgb();
|
|
|
|
commandBarElement.appendChild(colorPreview.element);
|
|
|
|
colorPreview.update = function() {
|
|
|
|
colorPreview.element.style.backgroundColor = brushColor.toRgb();
|
|
|
|
}
|
|
|
|
|
|
|
|
return colorPreview;
|
|
|
|
}
|
|
|
|
|
|
|
|
const colorPreview = makeColorPreview();
|
|
|
|
|
|
|
|
// }}}
|
|
|
|
|
|
|
|
// BRUSH PREVIEW {{{
|
|
|
|
|
|
|
|
function makeBrushPreview() {
|
|
|
|
const brushPreview = {};
|
|
|
|
brushPreview.element = document.createElement('div');
|
|
|
|
brushPreview.element.id = 'brush-preview';
|
|
|
|
brushPreview.element.style.width = brushSize + 'px';
|
|
|
|
brushPreview.element.style.height = brushSize + 'px';
|
|
|
|
brushPreview.element.style.position = 'absolute';
|
|
|
|
brushPreview.element.style.display = 'none';
|
|
|
|
brushPreview.element.style.pointerEvents = 'none';
|
|
|
|
brushPreview.element.style.border = '1px solid black';
|
|
|
|
brushPreview.element.style.zIndex = '1000';
|
|
|
|
|
|
|
|
document.body.appendChild(brushPreview.element);
|
|
|
|
|
|
|
|
brushPreview.update = function() {
|
|
|
|
brushPreview.element.style.width = brushSize * zoom + 'px';
|
|
|
|
brushPreview.element.style.height = brushSize * zoom + 'px';
|
|
|
|
if (brushShape === 'circle') {
|
|
|
|
brushPreview.element.style.borderRadius = '50%';
|
|
|
|
} else {
|
|
|
|
brushPreview.element.style.borderRadius = '0';
|
|
|
|
}
|
|
|
|
if (brushSize < 3) {
|
|
|
|
brushPreview.element.style.visibility = 'hidden';
|
|
|
|
} else {
|
|
|
|
brushPreview.element.style.visibility = 'visible';
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
brushPreview.setPosition = function(x, y) {
|
|
|
|
brushPreview.element.style.left = x - brushSize * zoom / 2 + 'px';
|
|
|
|
brushPreview.element.style.top = y - brushSize * zoom / 2 + 'px';
|
|
|
|
}
|
|
|
|
|
|
|
|
brushPreview.show = function() {
|
|
|
|
brushPreview.element.style.display = 'block';
|
|
|
|
}
|
|
|
|
|
|
|
|
brushPreview.hide = function() {
|
|
|
|
brushPreview.element.style.display = 'none';
|
|
|
|
}
|
|
|
|
|
|
|
|
brushPreview.update();
|
|
|
|
|
|
|
|
return brushPreview;
|
|
|
|
}
|
|
|
|
|
|
|
|
const brushPreview = makeBrushPreview();
|
|
|
|
|
|
|
|
|
|
|
|
// }}}
|
|
|
|
|
|
|
|
// 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: '<i class="fa-solid fa-left-right"></i>',
|
|
|
|
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: '<i class="fa-solid fa-up-down"></i>',
|
|
|
|
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: '<i class="fa-solid fa-floppy-disk"></i>',
|
|
|
|
func: function exportCanvas() {
|
|
|
|
const mergedCanvas = layers.tempMergeAll().canvas;
|
|
|
|
const link = document.createElement('a');
|
|
|
|
link.download = 'canvas.png';
|
|
|
|
link.href = mergedCanvas.toDataURL();
|
|
|
|
link.click();
|
|
|
|
}
|
|
|
|
}); // }}}
|
|
|
|
|
|
|
|
commands.add({ // import {{{
|
|
|
|
name: 'import',
|
|
|
|
key: 'i',
|
|
|
|
icon: '<i class="fa-regular fa-folder-open"></i>',
|
|
|
|
func: function importCanvas() {
|
|
|
|
const input = document.createElement('input');
|
|
|
|
input.type = 'file';
|
|
|
|
input.accept = 'image/*';
|
|
|
|
input.onchange = function(e) {
|
|
|
|
const file = e.target.files[0];
|
|
|
|
const reader = new FileReader();
|
|
|
|
reader.onload = function(e) {
|
|
|
|
const dataURL = e.target.result;
|
|
|
|
const img = new Image();
|
|
|
|
img.src = dataURL;
|
|
|
|
img.onload = function() {
|
|
|
|
layers.add();
|
|
|
|
const canvas = layers.getActive().canvas;
|
|
|
|
canvas.fromDataUrl(dataURL, `${img.width}x${img.height}`);
|
|
|
|
layers.setWidth(img.width);
|
|
|
|
layers.setHeight(img.height);
|
|
|
|
layers.refresh();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
reader.readAsDataURL(file);
|
|
|
|
}
|
|
|
|
input.click();
|
|
|
|
}
|
|
|
|
}); // }}}
|
|
|
|
|
|
|
|
commands.add({ // clear {{{
|
|
|
|
name: 'clear',
|
|
|
|
key: 'c',
|
|
|
|
icon: '<i class="fa-solid fa-trash-can"></i>',
|
|
|
|
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: `<i class="fa-solid fa-shapes"></i>`,
|
|
|
|
func: function changeShape() {
|
|
|
|
const currentIndex = shapes.indexOf(brushShape);
|
|
|
|
brushShape = shapes[(currentIndex + 1) % shapes.length];
|
|
|
|
brushPreview.update();
|
|
|
|
}
|
|
|
|
}); // }}}
|
|
|
|
|
|
|
|
commands.add({ //reset {{{
|
|
|
|
name: 'reset',
|
|
|
|
key: 'r',
|
|
|
|
icon: '<i class="fa-solid fa-home"></i>',
|
|
|
|
func: function resetCanvas() {
|
|
|
|
zoom = 1;
|
|
|
|
layers.zoom();
|
|
|
|
layers.resetPosition();
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
// }}}
|
|
|
|
|
|
|
|
// }}}
|
|
|
|
|
|
|
|
// TOOLS {{{
|
|
|
|
|
|
|
|
// FACTORY {{{
|
|
|
|
|
|
|
|
function makeTool({name, key, icon, mouseDown, mouseMove, mouseUp, mouseDrag, mouseLeave}) {
|
|
|
|
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.mouseDrag = mouseDrag;
|
|
|
|
tool.mouseLeave = mouseLeave;
|
|
|
|
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, mouseDrag, mouseLeave}) {
|
|
|
|
const tool = makeTool({name, key, icon, mouseDown, mouseMove, mouseUp, mouseDrag, mouseLeave});
|
|
|
|
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: '<i class="fa-solid fa-paintbrush"></i>',
|
|
|
|
mouseDown: function(e) {
|
|
|
|
const canvas = layers.getActive().canvas;
|
|
|
|
if (brushSize === 1) {
|
|
|
|
canvas.drawPixel(canvasStartX, canvasStartY, brushColor.toRgb());
|
|
|
|
} else {
|
|
|
|
canvas.drawShape(canvasStartX, canvasStartY, brushShape, brushSize, brushColor.toRgb());
|
|
|
|
}
|
|
|
|
},
|
|
|
|
mouseMove: function(e) {
|
|
|
|
brushPreview.show();
|
|
|
|
brushPreview.setPosition(e.clientX, e.clientY);
|
|
|
|
},
|
|
|
|
mouseDrag: function(e) {
|
|
|
|
const canvas = layers.getActive().canvas;
|
|
|
|
if (brushSize === 1) {
|
|
|
|
canvas.drawLineWithPixels(canvasStartX, canvasStartY, canvasEndX, canvasEndY, brushColor.toRgb());
|
|
|
|
} else {
|
|
|
|
canvas.drawLineWithShape(canvasStartX, canvasStartY, canvasEndX, canvasEndY, brushShape, brushSize, brushColor.toRgb());
|
|
|
|
}
|
|
|
|
canvasStartX = canvasEndX;
|
|
|
|
canvasStartY = canvasEndY;
|
|
|
|
},
|
|
|
|
mouseUp: function(e) {
|
|
|
|
layers.getActive().refreshPreview();
|
|
|
|
},
|
|
|
|
mouseLeave: function(e) {
|
|
|
|
brushPreview.hide();
|
|
|
|
}
|
|
|
|
}); // }}}
|
|
|
|
|
|
|
|
tools.add({ // content-move {{{
|
|
|
|
name: 'content-move',
|
|
|
|
key: 'h',
|
|
|
|
icon: '<i class="fa-regular fa-hand"></i>',
|
|
|
|
mouseDown: function(e) {
|
|
|
|
const canvas = layers.getActive().canvas;
|
|
|
|
canvas.saveCanvas();
|
|
|
|
},
|
|
|
|
mouseDrag: function(e) {
|
|
|
|
const canvas = layers.getActive().canvas;
|
|
|
|
canvas.clearCanvas();
|
|
|
|
canvas.restoreCanvas(dX, dY);
|
|
|
|
},
|
|
|
|
mouseUp: function(e) {
|
|
|
|
layers.getActive().refreshPreview();
|
|
|
|
},
|
|
|
|
}); // }}}
|
|
|
|
|
|
|
|
tools.add({ // move {{{
|
|
|
|
name: 'move',
|
|
|
|
key: 'm',
|
|
|
|
icon: '<i class="fa-solid fa-arrows-up-down-left-right"></i>',
|
|
|
|
mouseDown: function(e) {
|
|
|
|
startX = e.clientX - easelElement.offsetLeft;
|
|
|
|
startY = e.clientY - easelElement.offsetTop;
|
|
|
|
},
|
|
|
|
mouseDrag: function(e) {
|
|
|
|
easelElement.style.left = dX + 'px';
|
|
|
|
easelElement.style.top = dY + 'px';
|
|
|
|
},
|
|
|
|
cursor: 'fontawesome/png/arrows-alt-solid.png',
|
|
|
|
}); // }}}
|
|
|
|
|
|
|
|
tools.add({ // zoom {{{
|
|
|
|
name: 'zoom',
|
|
|
|
key: 'z',
|
|
|
|
icon: '<i class="fa-solid fa-magnifying-glass"></i>',
|
|
|
|
mouseDrag: function(e) {
|
|
|
|
zoom += dX * dZoom;
|
|
|
|
if (zoom < 0.1) zoom = 0.1;
|
|
|
|
layers.zoom();
|
|
|
|
brushPreview.update();
|
|
|
|
startX = endX;
|
|
|
|
}
|
|
|
|
}); // }}}
|
|
|
|
|
|
|
|
tools.add({ // bucket-fill {{{
|
|
|
|
name: 'bucket-fill',
|
|
|
|
key: 'k',
|
|
|
|
icon: '<i class="fa-solid fa-fill"></i>',
|
|
|
|
mouseDown: function(e) {
|
|
|
|
const canvas = layers.getActive().canvas;
|
|
|
|
canvas.floodFill(canvasStartX, canvasStartY, brushColor.toRgbaArray());
|
|
|
|
},
|
|
|
|
mouseUp: function(e) {
|
|
|
|
layers.getActive().refreshPreview();
|
|
|
|
}
|
|
|
|
}); // }}}
|
|
|
|
|
|
|
|
tools.add({ // color-picker {{{
|
|
|
|
name: 'color-picker',
|
|
|
|
key: 'a',
|
|
|
|
icon: '<i class="fa-solid fa-eye-dropper"></i>',
|
|
|
|
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.fromRgb(pickedColor);
|
|
|
|
colorPreview.update();
|
|
|
|
}
|
|
|
|
}); // }}}
|
|
|
|
|
|
|
|
tools.add({ // brush-size {{{
|
|
|
|
name: 'brush-size',
|
|
|
|
key: 'd',
|
|
|
|
icon: '<i class="fa-regular fa-circle-dot"></i>',
|
|
|
|
mouseMove: function(e) {
|
|
|
|
brushPreview.show();
|
|
|
|
brushPreview.setPosition(e.clientX, e.clientY);
|
|
|
|
},
|
|
|
|
mouseDrag: function(e) {
|
|
|
|
brushSize += dX * dBrushSize;
|
|
|
|
if (brushSize < 1) brushSize = 1;
|
|
|
|
if (brushSize > maxBrushSize) brushSize = maxBrushSize;
|
|
|
|
startX = endX;
|
|
|
|
brushPreview.update();
|
|
|
|
},
|
|
|
|
mouseLeave: function(e) {
|
|
|
|
brushPreview.hide();
|
|
|
|
}
|
|
|
|
}); // }}}
|
|
|
|
|
|
|
|
tools.add({ // resize {{{
|
|
|
|
name: 'resize',
|
|
|
|
key: 'r',
|
|
|
|
icon: '<i class="fa-solid fa-ruler-combined"></i>',
|
|
|
|
mouseDrag: function(e) {
|
|
|
|
let newWidth = layers.width + dX / zoom;
|
|
|
|
let newHeight = layers.height + dY / zoom;
|
|
|
|
layers.resize(newWidth, newHeight);
|
|
|
|
startX = endX;
|
|
|
|
startY = endY;
|
|
|
|
},
|
|
|
|
mouseUp: function(e) {
|
|
|
|
layers.refreshPreviews();
|
|
|
|
}
|
|
|
|
}); // }}}
|
|
|
|
|
|
|
|
tools.add({ // color-mix {{{
|
|
|
|
name: 'color-mix',
|
|
|
|
key: 'x',
|
|
|
|
icon: '<i class="fa-solid fa-mortar-pestle"></i>',
|
|
|
|
mouseDown: function(e) {
|
|
|
|
tempColor.copy(canvasColor);
|
|
|
|
startTime = Date.now();
|
|
|
|
interval = setInterval(() => {
|
|
|
|
if (!tempColor.match(canvasColor)) {
|
|
|
|
startTime = Date.now();
|
|
|
|
tempColor.copy(canvasColor);
|
|
|
|
}
|
|
|
|
if (!canvasColor.isOpaque()) {
|
|
|
|
startTime = Date.now();
|
|
|
|
} else {
|
|
|
|
const elapsedTime = Date.now() - startTime;
|
|
|
|
const t = Math.min(1, elapsedTime / 10000);
|
|
|
|
brushColor.mixx(canvasColor, t);
|
|
|
|
colorPreview.update();
|
|
|
|
if (!isMouseDown) {
|
|
|
|
clearInterval(interval);
|
|
|
|
startTime = Date.now();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}, 50);
|
|
|
|
},
|
|
|
|
mouseUp: function(e) {
|
|
|
|
clearInterval(interval);
|
|
|
|
},
|
|
|
|
mouseLeave: function(e) {
|
|
|
|
clearInterval(interval);
|
|
|
|
}
|
|
|
|
}); // }}}
|
|
|
|
|
|
|
|
// tools.add({ // opacity {{{
|
|
|
|
// name: 'opacity',
|
|
|
|
// key: 'o',
|
|
|
|
// icon: '<i class="fa-solid fa-ghost"></i>',
|
|
|
|
// mouseDrag: function(e) {
|
|
|
|
// layer = layers.getActive();
|
|
|
|
// var opacity = layer.opacity += dX * dOpacity;
|
|
|
|
// if (opacity < 0) opacity = 0;
|
|
|
|
// if (opacity > 1) opacity = 1;
|
|
|
|
// layer.changeOpacity(opacity);
|
|
|
|
// startX = endX;
|
|
|
|
// },
|
|
|
|
// }); // }}}
|
|
|
|
|
|
|
|
// }}}
|
|
|
|
|
|
|
|
// PUCKS {{{
|
|
|
|
|
|
|
|
// FACTORY {{{
|
|
|
|
|
|
|
|
function makePuck({puckColor, key}) {
|
|
|
|
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 (key) {
|
|
|
|
puck.key = key;
|
|
|
|
const keyHint = document.createElement('div');
|
|
|
|
keyHint.className = 'key-hint';
|
|
|
|
keyHint.innerHTML = key;
|
|
|
|
puck.element.appendChild(keyHint);
|
|
|
|
}
|
|
|
|
|
|
|
|
puck.element.addEventListener('mousedown', (e) => {
|
|
|
|
const startTime = Date.now();
|
|
|
|
interval = setInterval(() => {
|
|
|
|
const elapsedTime = Date.now() - startTime;
|
|
|
|
const t = Math.min(1, elapsedTime / 10000);
|
|
|
|
brushColor.mixxRgb(puck.element.style.backgroundColor, t);
|
|
|
|
colorPreview.update();
|
|
|
|
}, 50);
|
|
|
|
});
|
|
|
|
|
|
|
|
puck.element.addEventListener('mouseup', (e) => {
|
|
|
|
clearInterval(interval);
|
|
|
|
});
|
|
|
|
|
|
|
|
puck.element.addEventListener('mouseleave', (e) => {
|
|
|
|
clearInterval(interval);
|
|
|
|
});
|
|
|
|
|
|
|
|
puck.keydown = function(e) {
|
|
|
|
startTime = Date.now();
|
|
|
|
var interval = setInterval(() => {
|
|
|
|
const elapsedTime = Date.now() - startTime;
|
|
|
|
const t = Math.min(1, elapsedTime / 10000);
|
|
|
|
brushColor.mixxRgb(puck.element.style.backgroundColor, t);
|
|
|
|
colorPreview.update();
|
|
|
|
}, 50);
|
|
|
|
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}) {
|
|
|
|
const puck = makePuck({puckColor, key});
|
|
|
|
pucks.push(puck);
|
|
|
|
}
|
|
|
|
|
|
|
|
return pucks;
|
|
|
|
}
|
|
|
|
|
|
|
|
// }}}
|
|
|
|
|
|
|
|
const pucks = makePucks();
|
|
|
|
|
|
|
|
|
|
|
|
pucks.add({ // black
|
|
|
|
puckColor: 'rgb(0, 0, 0)',
|
|
|
|
});
|
|
|
|
|
|
|
|
pucks.add({ // white
|
|
|
|
puckColor: 'rgb(255, 255, 255)',
|
|
|
|
});
|
|
|
|
|
|
|
|
pucks.add({ // Cadmium Yellow
|
|
|
|
puckColor: 'rgb(254, 236, 0)',
|
|
|
|
});
|
|
|
|
pucks.add({ // Hansa Yellow
|
|
|
|
puckColor: 'rgb(252, 211, 0)',
|
|
|
|
});
|
|
|
|
pucks.add({ // Cadmium Orange
|
|
|
|
puckColor: 'rgb(255, 105, 0)',
|
|
|
|
});
|
|
|
|
pucks.add({ // Cadmium Red
|
|
|
|
puckColor: 'rgb(255, 39, 2)',
|
|
|
|
});
|
|
|
|
pucks.add({ // Quinacridone Magenta
|
|
|
|
puckColor: 'rgb(128, 2, 46)',
|
|
|
|
});
|
|
|
|
pucks.add({ // Cobalt Violet
|
|
|
|
puckColor: 'rgb(78, 0, 66)',
|
|
|
|
});
|
|
|
|
pucks.add({ // Ultramarine Blue
|
|
|
|
puckColor: 'rgb(25, 0, 89)',
|
|
|
|
});
|
|
|
|
pucks.add({ // Cobalt Blue
|
|
|
|
puckColor: 'rgb(0, 33, 133)',
|
|
|
|
});
|
|
|
|
pucks.add({ // Phthalo Blue
|
|
|
|
puckColor: 'rgb(13, 27, 68)',
|
|
|
|
});
|
|
|
|
pucks.add({ // Phthalo Green
|
|
|
|
puckColor: 'rgb(0, 60, 50)',
|
|
|
|
});
|
|
|
|
pucks.add({ // Permanent Green
|
|
|
|
puckColor: 'rgb(7, 109, 22)',
|
|
|
|
});
|
|
|
|
pucks.add({ // Sap Green
|
|
|
|
puckColor: 'rgb(107, 148, 4)',
|
|
|
|
});
|
|
|
|
pucks.add({ // Burnt Sienna
|
|
|
|
puckColor: 'rgb(123, 72, 0)',
|
|
|
|
});
|
|
|
|
pucks.add({ // red
|
|
|
|
puckColor: 'rgb(255, 0, 0)',
|
|
|
|
});
|
|
|
|
pucks.add({ // green
|
|
|
|
puckColor: 'rgb(0, 255, 0)',
|
|
|
|
});
|
|
|
|
pucks.add({ // blue
|
|
|
|
puckColor: 'rgb(0, 0, 255)',
|
|
|
|
});
|
|
|
|
pucks.add({ // cyan
|
|
|
|
puckColor: 'rgb(0, 255, 255)',
|
|
|
|
});
|
|
|
|
pucks.add({ // yellow
|
|
|
|
puckColor: 'rgb(255, 255, 0)',
|
|
|
|
});
|
|
|
|
pucks.add({ // magenta
|
|
|
|
puckColor: 'rgb(255, 0, 255)',
|
|
|
|
});
|
|
|
|
|
|
|
|
// }}}
|
|
|
|
|
|
|
|
// 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 brushColor.toRgb();
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
infos.add({
|
|
|
|
name: 'cursor-color',
|
|
|
|
updateFunction: function() {
|
|
|
|
return canvasColor;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
// }}}
|
|
|
|
|
|
|
|
// 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;
|
|
|
|
canvasEndX = canvas.getPositionOnCanvas(e).x;
|
|
|
|
canvasEndX = canvas.getPositionOnCanvas(e).y;
|
|
|
|
canvasColor.fromRgbaArray(canvas.getRgbaColorArrayAtPixel(canvasStartX, canvasStartY));
|
|
|
|
|
|
|
|
for (var i = 0; i < tools.length; i++) {
|
|
|
|
var tool = tools[i];
|
|
|
|
if (tool.active) {
|
|
|
|
if (tool.mouseDown) {
|
|
|
|
tool.mouseDown(e);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
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;
|
|
|
|
canvasColor.fromRgbaArray(canvas.getRgbaColorArrayAtPixel(canvasEndX, canvasEndY));
|
|
|
|
|
|
|
|
for (var i = 0; i < tools.length; i++) {
|
|
|
|
var tool = tools[i];
|
|
|
|
if (tool.active) {
|
|
|
|
if (tool.mouseMove) {
|
|
|
|
tool.mouseMove(e);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (isMouseDown) {
|
|
|
|
for (var i = 0; i < tools.length; i++) {
|
|
|
|
var tool = tools[i];
|
|
|
|
if (tool.active) {
|
|
|
|
if (tool.mouseDrag) {
|
|
|
|
tool.mouseDrag(e);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
infos.update();
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
infos.update();
|
|
|
|
});
|
|
|
|
|
|
|
|
// }}}
|
|
|
|
|
|
|
|
// KEYBINDINGS {{{
|
|
|
|
|
|
|
|
document.addEventListener('keydown', (e) => {
|
|
|
|
if (isKeyDown) return;
|
|
|
|
|
|
|
|
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');
|