diff --git a/canvas.js b/canvas.js index 9b5ee87..979e2ff 100644 --- a/canvas.js +++ b/canvas.js @@ -8,7 +8,20 @@ var customColorToolbar=false; var fillTool = false; var floodFillInProgress=0; +var changedPixels = {}; //pixel data for all changed pixels in this step. +/* +STEPTYPE: +ADD //Pixel was added. Remove all pixels in this object. + //Each pixel will have an old_pos_X_Y indicating their old color. Do the reverse to undo. The pixel list itself will be in "PIXELS": X,Y,X2,Y2,X3,Y3, etc. +FILL //Fill was done. A color is provided by "backcolor" and a pixel containing the click and source color. To undo it, fill with the backcolor instead of the source color. +*/ + +var pixelStates = []; //Last 20 pixel states stored here. +var currentPixelState = -1; //Which pixel state we're on. Max out at 20. + document.addEventListener("DOMContentLoaded",()=>{ + var undoButton = document.createElement("button") + var redoButton = document.createElement("button") class Coordinate{ constructor(box) { @@ -24,6 +37,11 @@ document.addEventListener("DOMContentLoaded",()=>{ } } + //Returns a coordinate class. + var getCoordinates = (box)=>{ + return new Coordinate(box); + } + function insertAfter(newNode, existingNode) { existingNode.parentNode.insertBefore(newNode, existingNode.nextSibling); } @@ -35,6 +53,63 @@ document.addEventListener("DOMContentLoaded",()=>{ var rawColors = rgbvalue.replace("rgb(","").replace(")","").split(","); return "#" + componentToHex(Number(rawColors[0])) + componentToHex(Number(rawColors[1])) + componentToHex(Number(rawColors[2])); } + + function Undo() { + //Execute the reverse. + var state = pixelStates[currentPixelState]; + console.log("Undo "+JSON.stringify(state)) + switch (state["STEPTYPE"]) { + case "ADD":{ + //Take each pixel and color it its previous color. + var pixels = state["PIXELS"].split(",") + for (var i=0;i0) { + currentPixelState--; + undoButton.disabled = false; + } + } + + function Redo() { + //Execute the re-reverse. + var state = pixelStates[currentPixelState]; + console.log("Redo "+JSON.stringify(state)) + switch (state["STEPTYPE"]) { + case "ADD":{ + //Take each pixel and color it its previous color. + var pixels = state["PIXELS"].split(",") + for (var i=0;i{ var MouseListener = (e)=>{ e.preventDefault(); if (!fillTool) { - if (mouseState>=0) { - if (mouseState<2 && e.target.tagName==="TH") { - e.target.style.background=selectedColor; - } else { - e.target.style.background="white"; + if (e.target.tagName==="TH") { + if (mouseState>=0) { + var mycoords = getCoordinates(e.target) + if ("PIXELS" in changedPixels) { + changedPixels["PIXELS"]+=","+mycoords.x+","+mycoords.y + } else { + changedPixels["PIXELS"]=mycoords.x+","+mycoords.y + } + changedPixels["old_"+e.target.id]=e.target.style.background; + if (mouseState<2) { + e.target.style.background=selectedColor; + changedPixels[e.target.id]=selectedColor; + } else { + e.target.style.background="white"; + changedPixels[e.target.id]="white"; + } + changedPixels["STEPTYPE"]="ADD" } } } @@ -71,20 +158,60 @@ document.addEventListener("DOMContentLoaded",()=>{ if (e.target.tagName==="TH") { var coords = getCoordinates(e.target) floodFill(coords.x,coords.y,e.target.style.background,selectedColor) + changedPixels["STEPTYPE"]="FILL" + changedPixels["backcolor"]=e.target.style.background + changedPixels["newcolor"]=selectedColor + changedPixels["PIXELX"]=coords.x + changedPixels["PIXELY"]=coords.y } } toolbar.style.visibility = "visible"; + //console.log(changedPixels); + if ("STEPTYPE" in changedPixels) { + //Add changedPixels to the current pixelState. + if (currentPixelState!==-1 && currentPixelState!==pixelStates.length-1) { + //Delete everything after this. + if (currentPixelState===0) { + pixelStates = []; + } else { + pixelStates = pixelStates.slice(0,currentPixelState+1) + } + redoButton.disabled = true; + } + + if (pixelStates.length<20) { + pixelStates.push(changedPixels); + undoButton.disabled = false; + redoButton.disabled = true; + } else { + pixelStates = pixelStates.slice(1) + pixelStates.push(changedPixels) + } + changedPixels = {} + console.log(pixelStates) + currentPixelState = pixelStates.length-1; + } } var MouseStateDown = (e)=>{ e.preventDefault(); mouseState = e.button; if (!fillTool) { if (e.target.tagName==="TH") { + var mycoords = getCoordinates(e.target) + if ("PIXELS" in changedPixels) { + changedPixels["PIXELS"]+=","+mycoords.x+","+mycoords.y + } else { + changedPixels["PIXELS"]=mycoords.x+","+mycoords.y + } + changedPixels["old_"+e.target.id]=e.target.style.background; if (e.button===0) { e.target.style.background=selectedColor; + changedPixels[e.target.id]=selectedColor; } else { e.target.style.background="white"; + changedPixels[e.target.id]="white"; } + changedPixels["STEPTYPE"]="ADD" } } toolbar.style.visibility = "hidden"; @@ -282,7 +409,7 @@ document.addEventListener("DOMContentLoaded",()=>{ finalData["ROWS"]=ROWS; finalData["COLS"]=COLS; finalData["CUSTOMCOLOR"]=document.getElementsByTagName("img")[0].style.background; - //console.log(JSON.stringify(finalData)) + console.log(JSON.stringify(finalData)) localStorage.setItem("save_pixelart",JSON.stringify(finalData)) var consoleText = document.createElement("span") consoleText.innerHTML = "Saved!" @@ -331,6 +458,18 @@ document.addEventListener("DOMContentLoaded",()=>{ saveButton.addEventListener("click",SaveData) toolbar.appendChild(loadButton); toolbar.appendChild(saveButton); + undoButton.type = "button" + undoButton.innerText = "Undo" + undoButton.classList.add("undobutton") + undoButton.disabled = true + undoButton.addEventListener("click",Undo) + redoButton.type = "button" + redoButton.innerText = "Redo" + redoButton.classList.add("redobutton") + redoButton.addEventListener("click",Redo) + redoButton.disabled = true + toolbar.appendChild(undoButton); + toolbar.appendChild(redoButton); var toggleGridButton = document.createElement("img"); toggleGridButton.src = "gridtoggle.png"; toggleGridButton.classList.add("tinybutton"); @@ -361,53 +500,48 @@ document.addEventListener("DOMContentLoaded",()=>{ toolbar.appendChild(fillToolButton); } - //Returns a coordinate class. - var getCoordinates = (box)=>{ - return new Coordinate(box); - } - - var floodFill = (startx,starty,baseColor,newColor)=>{ - console.log("Flood fill at "+startx+","+starty) + var floodFill = (startx,starty,baseColor,newColor,force=false)=>{ + //console.log("Flood fill at "+startx+","+starty) //Start a flood fill in 4 cardinations directions and the current spot. //Set the base color to what the dot currently is. Then all around this spot, fill in any dots that are also this color. Don't spread the dot if there's not a color of that type. - floodFillInProgress++; + if (!force) {if (!force) {floodFillInProgress++;}} if (baseColor===newColor) { floodFillInProgress--; return } startx = Number(startx) starty = Number(starty) - floodFillInProgress++; + if (!force) {floodFillInProgress++;} setTimeout(()=>{if (document.getElementById("pos_"+(startx)+"_"+(starty)).style.background===baseColor) { document.getElementById("pos_"+(startx)+"_"+(starty)).style.background = newColor - floodFill(startx,starty,baseColor,newColor) + floodFill(startx,starty,baseColor,newColor,force) } - floodFillInProgress--;},0) - floodFillInProgress++; + if (!force) {floodFillInProgress--;}},0) + if (!force) {floodFillInProgress++;} setTimeout(()=>{if (startx+1 < COLS && document.getElementById("pos_"+(startx+1)+"_"+(starty+0)).style.background===baseColor) { document.getElementById("pos_"+(startx+1)+"_"+(starty+0)).style.background = newColor - floodFill(startx+1,starty,baseColor,newColor) + floodFill(startx+1,starty,baseColor,newColor,force) } - floodFillInProgress--;},0) - floodFillInProgress++; + if (!force) {floodFillInProgress--;}},0) + if (!force) {floodFillInProgress++;} setTimeout(()=>{if (startx-1 >= 0 && document.getElementById("pos_"+(startx-1)+"_"+(starty+0)).style.background===baseColor) { document.getElementById("pos_"+(startx-1)+"_"+(starty+0)).style.background = newColor - floodFill(startx-1,starty,baseColor,newColor) + floodFill(startx-1,starty,baseColor,newColor,force) } - floodFillInProgress--;},0) - floodFillInProgress++; + if (!force) {floodFillInProgress--;}},0) + if (!force) {floodFillInProgress++;} setTimeout(()=>{if (starty+1 < ROWS && document.getElementById("pos_"+(startx)+"_"+(starty+1)).style.background===baseColor) { document.getElementById("pos_"+(startx)+"_"+(starty+1)).style.background = newColor - floodFill(startx,starty+1,baseColor,newColor) + floodFill(startx,starty+1,baseColor,newColor,force) } - floodFillInProgress--;},0) - floodFillInProgress++; + if (!force) {floodFillInProgress--;}},0) + if (!force) {floodFillInProgress++;} setTimeout(()=>{if (starty-1 >= 0 && document.getElementById("pos_"+(startx)+"_"+(starty-1)).style.background===baseColor) { document.getElementById("pos_"+(startx)+"_"+(starty-1)).style.background = newColor - floodFill(startx,starty-1,baseColor,newColor) + floodFill(startx,starty-1,baseColor,newColor,force) } - floodFillInProgress--;},0) - floodFillInProgress--; + if (!force) {floodFillInProgress--;}},0) + if (!force) {floodFillInProgress--;} } generateTable(ROWS,COLS); diff --git a/style.css b/style.css index 03776f9..afff6f7 100644 --- a/style.css +++ b/style.css @@ -34,6 +34,14 @@ th{ margin: 0px 2px 0px 2px; background-color: #FF9999; } +.undobutton{ + margin: 0px 2px 0px 2px; + background-color: #99FF99; +} +.redobutton{ + margin: 0px 2px 0px 2px; + background-color: #FFFF99; +} .console{ font-style: strong; margin-left: 4px;