diff --git a/fontawesome/png/arrows-alt-solid.png b/fontawesome/png/arrows-alt-solid.png
new file mode 100644
index 0000000..2bc68a0
Binary files /dev/null and b/fontawesome/png/arrows-alt-solid.png differ
diff --git a/fontawesome/png/hand-grab-o.png b/fontawesome/png/hand-grab-o.png
new file mode 100644
index 0000000..212cefb
Binary files /dev/null and b/fontawesome/png/hand-grab-o.png differ
diff --git a/fontawesome/png/hand.png b/fontawesome/png/hand.png
new file mode 100644
index 0000000..83b4714
Binary files /dev/null and b/fontawesome/png/hand.png differ
diff --git a/render.js b/render.js
index dda3d59..f93fd7d 100644
--- a/render.js
+++ b/render.js
@@ -9,6 +9,7 @@ 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;
@@ -92,7 +93,8 @@ function makeButtonElement({icon, name, func, key}) {
button.key = key;
button.icon = icon;
button.element = document.createElement('div');
- button.element.className = 'button';
+ button.element.classList.add('button');
+ button.element.classList.add('bar-button');
button.element.addEventListener('click', func);
@@ -393,7 +395,7 @@ function makeCanvas({height=600, width=800, background=false}) { // {{{
const dx = x2 - x1;
const dy = y2 - y1;
const distance = Math.sqrt(dx * dx + dy * dy);
- const steps = Math.ceil(distance / (size / 2));
+ const steps = Math.ceil(distance / (size / 3));
for (let i = 0; i <= steps; i++) {
const x = Math.round(x1 + (dx * i) / steps);
@@ -472,11 +474,11 @@ function makeCanvas({height=600, width=800, background=false}) { // {{{
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.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();
@@ -496,6 +498,10 @@ function makeCanvas({height=600, width=800, background=false}) { // {{{
}
}
+ canvas.add = function(canvas2) {
+ canvas.ctx.drawImage(canvas2, 0, 0);
+ }
+
canvas.setWidth(width);
canvas.setHeight(height);
@@ -512,11 +518,74 @@ function makeLayer({height=600, width=800, background=undefined}) { // {{{
layer.controllerElement = document.createElement('div');
layer.controllerElement.className = 'layer-controller';
- layer.controllerElement.innerHTML = '';
- layer.controllerElement.addEventListener('click', () => {
+ 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 = '';
+ 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 = '';
+ 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 = '';
+ 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 = '';
+ layer.deleteButton.addEventListener('click', () => {
+ layers.delete(layer);
+ });
+
+ layer.controllerElement.appendChild(layer.deleteButton);
+
+ }
+
+
layer.activate = function() {
layer.active = true;
@@ -528,6 +597,16 @@ function makeLayer({height=600, width=800, background=undefined}) { // {{{
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;
} // }}}
@@ -537,7 +616,8 @@ function makeLayers({height=600, width=800, backgroundColor='rgb(255, 255, 255)'
layers.width = width;
layers.addButton = document.createElement('div');
- layers.addButton.className = 'layer-add-button';
+ layers.addButton.classList.add('button');
+ layers.addButton.classList.add('layer-add-button');
layers.addButton.innerHTML = '';
layers.addButton.addEventListener('click', () => {
layers.add();
@@ -598,9 +678,16 @@ function makeLayers({height=600, width=800, backgroundColor='rgb(255, 255, 255)'
});
}
+ layers.refreshPreviews = function() {
+ layers.forEach(layer => {
+ layer.refreshPreview();
+ });
+ }
+
layers.refresh = function() {
layers.refreshControllers();
layers.refreshLayers();
+ layers.refreshPreviews();
}
layers.add = function() {
@@ -609,7 +696,7 @@ function makeLayers({height=600, width=800, backgroundColor='rgb(255, 255, 255)'
width: layers.width,
});
layers.push(layer);
- layer.activate();
+ layers.setActive(layer);
layers.refresh();
}
@@ -630,7 +717,7 @@ function makeLayers({height=600, width=800, backgroundColor='rgb(255, 255, 255)'
});
}
- layers.moveUp = function(layer) {
+ layers.moveDown = function(layer) {
if (layer.background) return;
if (layers.indexOf(layer) === layers.length - 1) return;
const index = layers.indexOf(layer);
@@ -640,7 +727,7 @@ function makeLayers({height=600, width=800, backgroundColor='rgb(255, 255, 255)'
layers.refresh();
}
- layers.moveDown = function(layer) {
+ layers.moveUp = function(layer) {
if (layer.background) return;
if (layers.indexOf(layer) === 1) return;
const index = layers.indexOf(layer);
@@ -650,6 +737,51 @@ function makeLayers({height=600, width=800, backgroundColor='rgb(255, 255, 255)'
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();
@@ -838,10 +970,10 @@ commands.add({ // export {{{
key: 'e',
icon: '',
func: function exportCanvas() {
- const canvas = layers.getActive().canvas;
+ const mergedCanvas = layers.tempMergeAll().canvas;
const link = document.createElement('a');
link.download = 'canvas.png';
- link.href = canvas.toDataURL();
+ link.href = mergedCanvas.toDataURL();
link.click();
}
}); // }}}
@@ -851,22 +983,24 @@ commands.add({ // 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) => {
+ input.onchange = function(e) {
const file = e.target.files[0];
const reader = new FileReader();
- reader.onload = (e) => {
+ reader.onload = function(e) {
+ const dataURL = e.target.result;
const img = new Image();
- img.onload = () => {
- canvas.width = img.width;
- canvas.height = img.height;
- ctx.drawImage(img, 0, 0);
+ 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();
}
- img.src = e.target.result;
}
reader.readAsDataURL(file);
}
@@ -899,7 +1033,7 @@ commands.add({ // change-shape {{{
}
}); // }}}
-commands.add({
+commands.add({ //reset {{{
name: 'reset',
key: 'r',
icon: '',
@@ -912,6 +1046,8 @@ commands.add({
// }}}
+// }}}
+
// TOOLS {{{
// FACTORY {{{
@@ -962,7 +1098,7 @@ function makeTools() {
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});
+ const tool = makeTool({name, key, icon, mouseDown, mouseMove, mouseUp, mouseDrag, mouseLeave});
tools.push(tool);
}
@@ -1023,6 +1159,9 @@ tools.add({ // brush {{{
canvasStartX = canvasEndX;
canvasStartY = canvasEndY;
},
+ mouseUp: function(e) {
+ layers.getActive().refreshPreview();
+ },
mouseLeave: function(e) {
brushPreview.hide();
}
@@ -1041,6 +1180,9 @@ tools.add({ // content-move {{{
canvas.clearCanvas();
canvas.restoreCanvas(dX, dY);
},
+ mouseUp: function(e) {
+ layers.getActive().refreshPreview();
+ },
}); // }}}
tools.add({ // move {{{
@@ -1055,6 +1197,7 @@ tools.add({ // move {{{
easelElement.style.left = dX + 'px';
easelElement.style.top = dY + 'px';
},
+ cursor: 'fontawesome/png/arrows-alt-solid.png',
}); // }}}
tools.add({ // zoom {{{
@@ -1077,6 +1220,9 @@ tools.add({ // bucket-fill {{{
mouseDown: function(e) {
const canvas = layers.getActive().canvas;
canvas.floodFill(canvasStartX, canvasStartY, brushColor.toRgbaArray());
+ },
+ mouseUp: function(e) {
+ layers.getActive().refreshPreview();
}
}); // }}}
@@ -1123,6 +1269,9 @@ tools.add({ // resize {{{
layers.resize(newWidth, newHeight);
startX = endX;
startY = endY;
+ },
+ mouseUp: function(e) {
+ layers.refreshPreviews();
}
}); // }}}
@@ -1160,13 +1309,27 @@ tools.add({ // color-mix {{{
}
}); // }}}
+// tools.add({ // opacity {{{
+// name: 'opacity',
+// key: 'o',
+// icon: '',
+// 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, editable=true}) {
+function makePuck({puckColor, key}) {
if (!puckColor) throw new Error('No puck color provided');
@@ -1175,16 +1338,6 @@ function makePuck({puckColor, key, editable=true}) {
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');
@@ -1234,8 +1387,8 @@ function makePuck({puckColor, key, editable=true}) {
function makePucks() {
const pucks = [];
- pucks.add = function({puckColor, key, editable}) {
- const puck = makePuck({puckColor, key, editable});
+ pucks.add = function({puckColor, key}) {
+ const puck = makePuck({puckColor, key});
pucks.push(puck);
}
@@ -1249,50 +1402,68 @@ const pucks = makePucks();
pucks.add({ // black
puckColor: 'rgb(0, 0, 0)',
- key: '1',
- editable: false,
});
pucks.add({ // white
puckColor: 'rgb(255, 255, 255)',
- key: '2',
- editable: false,
});
+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)',
- key: '3',
- editable: false,
});
-
+pucks.add({ // green
+ puckColor: 'rgb(0, 255, 0)',
+});
pucks.add({ // blue
puckColor: 'rgb(0, 0, 255)',
- key: '5',
- editable: false,
});
-
-pucks.add({ // yellow
- puckColor: 'rgb(255, 255, 0)',
- key: '6',
- editable: false,
-});
-
pucks.add({ // cyan
puckColor: 'rgb(0, 255, 255)',
- key: '7',
- editable: false,
});
-
+pucks.add({ // yellow
+ puckColor: 'rgb(255, 255, 0)',
+});
pucks.add({ // magenta
puckColor: 'rgb(255, 0, 255)',
- key: '8',
- editable: false,
-});
-
-pucks.add({ // green
- puckColor: 'rgb(0, 255, 0)',
- key: '4',
- editable: false,
});
// }}}
diff --git a/style.css b/style.css
index 4e326e6..728ec7f 100644
--- a/style.css
+++ b/style.css
@@ -1,3 +1,13 @@
+:root {
+ --white: #FFFFFF;
+ --button: #DDE4E7;
+ --hover: #B3BBBD;
+ --active: #8F9598;
+ /* --background: #7E888B; */
+ --background: #949C9E;
+ --black: #000000;
+}
+
@font-face {
font-family: 'VT323';
src: url('fonts/VT323-Regular.ttf') format('truetype');
@@ -15,7 +25,8 @@ body {
height: 100vh;
width: 100vw;
font-family: 'VT323', monospace;
- background-color: darkgray;
+ background-color: var(--background);
+ color: var(--black);
}
#menu-bar,
@@ -31,7 +42,7 @@ body {
#menu-bar, #info-bar {
padding: 10px;
- background-color: #ddd;
+ background-color: var(--button);
display: flex;
flex-direction: row;
gap: 10px;
@@ -59,7 +70,7 @@ body {
padding: 10px;
padding-top: 0;
padding-bottom: 0;
- background-color: #ddd;
+ background-color: var(--button);
gap: 10px;
height: 100%;
flex-direction: column;
@@ -79,7 +90,6 @@ body {
z-index: -1;
}
-
canvas {
position: absolute;
top: 0;
@@ -87,19 +97,30 @@ canvas {
}
.button {
- position: relative;
- flex-shrink: 0;
- background-color: #ddd;
+ background-color: var(--button);
border: 1px solid;
border-radius: 2px;
+ cursor: pointer;
+ display: flex;
+ text-align: center;
+ justify-content: center;
+}
+
+.button:hover {
+ background-color: var(--hover);
+}
+
+.button.active, .button:active {
+ background-color: var(--active);
+}
+
+.bar-button {
+ position: relative;
+ flex-shrink: 0;
width: 30px;
height: 30px;
padding: 5px;
- display: flex;
flex-direction: column;
- text-align: center;
- justify-content: center;
- cursor: pointer;
}
.puck {
@@ -111,136 +132,72 @@ canvas {
border-radius: 2px;
}
-.select-handle {
- position: absolute;
- bottom: 0;
- right: 0;
- font-size: .5em;
- background-color: black;
- color: white;
- padding: 3px 0 1px 4px;
- display: flex;
- align-items: center;
- justify-content: center;
- text-align: center;
- border-radius: 10px 0 0 0;
- border-left: 1px solid white;
- border-top: 1px solid white;
- cursor: pointer;
-}
-
-.delete-handle {
- position: absolute;
- top: 0;
- right: 0;
- font-size: .6em;
- background-color: black;
- color: white;
- padding: 1px 0 3px 4px;
- display: flex;
- align-items: center;
- justify-content: center;
- text-align: center;
- border-radius: 0 0 0 10px;
- border-left: 1px solid white;
- border-bottom: 1px solid white;
- cursor: pointer;
-}
-
-
.key-hint {
display: block;
height: 13px;
width: 9px;
line-height: 8px;
- /* width: 12px; */
position: absolute;
top: 2px;
left: 1px;
font-size: 1em;
- /* padding: 2px 2px 2px 2px; */
- /* display: flex; */
- /* align-items: center; */
justify-content: center;
text-align: center;
border-radius: 0 0 5px 0;
- /* border-right: 1px solid; */
- /* border-bottom: 1px solid; */
text-shadow:
1px 1px 0 #000,
-1px 1px 0 #000,
-1px -1px 0 #000,
1px -1px 0 #000;
- color: white;
+ color: var(--white);
pointer-events: none;
}
-
-.button.active, .button:active {
- background-color: darkgray;
-}
-
#layer-controllers {
- background-color: darkgrey;
+ background-color: var(--background);
border: 1px solid;
height: 100%;
}
.layer-controller {
- background-color: lightgray;
- height: 30px;
- width: 60px;
+ background-color: var(--button);
+ height: 40px;
+ width: 90px;
border: 1px solid;
border-radius: 2px;
- padding: 5px;
+ padding: 2px;
position: relative;
margin: 1px;
+ display: flex;
+ flex-direction: row;
+ gap: 2px;
+ align-items: center;
+ justify-content: space-between;
}
-.layer-controller > i {
- color: darkgray;
- cursor: pointer;
+.layer-controller.active {
+ background-color: var(--active);
}
-.layer-controller.active > i {
- color: black;
-}
-
-.handle {
- display: block;
- font-size: 9px;
- height: 11px;
- width: 24px;
- position: absolute;
- justify-content: center;
- text-align: center;
- cursor: pointer;
- border-radius: 2px;
- border: 1px solid;
- margin: 2px;
+.layer-controller:hover {
+ background-color: var(--hover);
}
.layer-add-button {
- background-color: lightgray;
+ background-color: var(--button);
height: 12px;
- width: 60px;
+ width: 100%;
border: 1px solid;
border-radius: 2px;
position: relative;
margin: 1px;
text-align: center;
- font-size: 10px;
+ font-size: .7em;
cursor: pointer;
}
.layer-add-button:hover {
- background-color: darkgray;
-}
-
-
-
-.handle:hover {
- background-color: darkgray;
+ background-color: var(--hover);
}
.top-left {
@@ -262,3 +219,37 @@ canvas {
bottom: 0;
left: 0;
}
+
+.layer-preview {
+ width: 30px;
+ height: 30px;
+ border: 1px solid;
+ object-fit: contain;
+ background-color: var(--background);
+ cursor: pointer;
+}
+
+.layer-move-buttons, .layer-merge-buttons {
+ font-size: .7em;
+ display: flex;
+ flex-direction: column;
+ gap: 2px;
+ flex-grow: 1;
+ height: 100%;
+}
+
+.layer-delete-button {
+ font-size: .7em;
+ flex-grow: 1;
+ height: 100%;
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+}
+
+.layer-move-button, .layer-merge-button {
+ flex-grow: 1;
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+}