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; +}