Implemented merge, rename and flatten options

This commit is contained in:
unsettledgames 2020-06-23 17:34:03 +02:00
parent 50b962a7f5
commit 6ad27323e5
6 changed files with 180 additions and 31 deletions

View File

@ -56,7 +56,6 @@ body {
li button:hover {
background-color:color(menu, background);
}
}
.preview-canvas {

View File

@ -1,27 +1,15 @@
var spacePressed = false;
/**
Copy / paste / cut logic:
- The user selects an area
- Pressing ctrl+c copies the selection
- Pressing ctrl+v ends the current selection and copies the clipboard in the tmp layer:
the editor enters move mode and lets the user move the copied selection around.
Pressing ctrl+v while moving a copy has the same effect of pressing ctrl+v after a ctrl+c
- The behaviour of ctrl+v is the same and doesn't depend on how the selected area was obtained
(with ctrl+c or with ctrl+v)
- Selecting a different tool while moving the copied or cut selection has the same effect of selecting
a different tool while moving a standard selection
- You can paste at any other time
BUGS:
-
*/
function KeyPress(e) {
var keyboardEvent = window.event? event : e;
//if the user is typing in an input field, ignore these hotkeys
if (document.activeElement.tagName == 'INPUT') return;
//if the user is typing in an input field or renaming a layer, ignore these hotkeys, unless it's an enter key
if (document.activeElement.tagName == 'INPUT' || isRenamingLayer) {
if (e.keyCode == 13) {
currentLayer.closeOptionsMenu();
}
return;
}
//if no document has been created yet,
//orthere is a dialog box open

View File

@ -1,13 +1,6 @@
/** TODO LIST FOR LAYERS
GENERAL REQUIREMENTS:
- The user shouldn't be able to draw on a hidden or locked layer
- Must delete the selected layer when right clicking on a layer and selecting that option
* We should think about selecting more than one layer at once.
* Rename layer
* Merge with bottom layer option
* Flatten visible option
* Flatten everything option
- When saving an artwork, the layers must be flattened to a temporary layer, which is then exported and deleted
- Saving the state of an artwork to a .lospec file so that people can work on it later keeping
the layers they created? That'd be cool, even for the app users, that could just double click on a lospec
@ -38,6 +31,9 @@
- Resize canvas (must make a simple editor to preview the changes)
- Resize sprite
- Refactor the code so that every instance of "canvas" and "context" is replaced by currentLayer.canvas
and currentLayer.context
- Refactor and replace the merge layer algorithm with a general function in _pixelEditorUtility.js
*/
// Instead of saving the whole entry, just save their IDs and swap the elements at the end of the drop
@ -58,6 +54,8 @@ let currentID = layerCount;
let idToDelete;
let layerOptions = document.getElementById("layer-properties-menu");
let isRenamingLayer = false;
on('click',"add-layer-button", function(){
// Creating a new canvas
let newCanvas = document.createElement("canvas");
@ -241,6 +239,8 @@ class Layer {
closeOptionsMenu(event) {
layerOptions.style.visibility = "hidden";
currentLayer.menuEntry.getElementsByTagName("p")[0].setAttribute("contenteditable", false);
isRenamingLayer = false;
}
selectLayer(layer) {
@ -260,6 +260,9 @@ class Layer {
layer.menuEntry.classList.add("selected-layer");
currentLayer = layer;
}
canvas = currentLayer.canvas;
context = currentLayer.context;
}
toggleLock() {
@ -348,6 +351,122 @@ class Layer {
}
}
function flatten(onlyVisible) {
if (!onlyVisible) {
// Selecting the first layer
let firstLayer = layerList.firstElementChild;
getLayerByID(firstLayer.id).selectLayer();
for (let i = 0; i < layerList.childElementCount - 1; i++) {
merge();
}
}
else {
// Getting all the visible layers
let visibleLayers = [];
for (let i=0; i<layers.length; i++) {
if (layers[i].menuEntry != null && layers[i].isVisible) {
visibleLayers.push(layers[i]);
}
}
// Sorting them by z-index
visibleLayers.sort((a, b) => (a.canvas.zIndex > b.canvas.zIndex) ? 1 : -1);
// Selecting the last visible layer (the only one that won't get deleted)
visibleLayers[visibleLayers.length - 1].selectLayer();
// Merging all the layer but the last one
for (let i=0; i<visibleLayers.length - 1; i++) {
// Copying the above content on the layerBelow
let belowImageData = visibleLayers[i].context.getImageData(0, 0, canvas.width, canvas.height);
let toMergeImageData = visibleLayers[i + 1].context.getImageData(0, 0, canvas.width, canvas.height);
for (let i=0; i<belowImageData.data.length; i+=4) {
let currentMovePixel = [
toMergeImageData.data[i], toMergeImageData.data[i+1],
toMergeImageData.data[i+2], toMergeImageData.data[i+3]
];
let currentUnderlyingPixel = [
belowImageData.data[i], belowImageData.data[i+1],
belowImageData.data[i+2], belowImageData.data[i+3]
];
if (isPixelEmpty(currentMovePixel)) {
if (!isPixelEmpty(belowImageData)) {
toMergeImageData.data[i] = currentUnderlyingPixel[0];
toMergeImageData.data[i+1] = currentUnderlyingPixel[1];
toMergeImageData.data[i+2] = currentUnderlyingPixel[2];
toMergeImageData.data[i+3] = currentUnderlyingPixel[3];
}
}
}
visibleLayers[i + 1].context.putImageData(toMergeImageData, 0, 0);
// Deleting the above layer
visibleLayers[i].canvas.remove();
visibleLayers[i].menuEntry.remove();
layers.splice(layers.indexOf(visibleLayers[i]), 1);
}
// Updating the layer preview
currentLayer.updateLayerPreview();
}
}
function merge(event) {
// Saving the layer that should be merged
let toMerge = currentLayer;
let toMergeIndex = layers.indexOf(toMerge);
// Getting layer below
let layerBelow = getLayerByID(currentLayer.menuEntry.nextElementSibling.id);
console.log("YEEEEUUE");
console.log(layerBelow);
// If I have something to merge with
if (layerBelow != null) {
// Selecting that layer
layerBelow.selectLayer();
// Copying the above content on the layerBelow
let belowImageData = currentLayer.context.getImageData(0, 0, canvas.width, canvas.height);
let toMergeImageData = toMerge.context.getImageData(0, 0, canvas.width, canvas.height);
for (let i=0; i<belowImageData.data.length; i+=4) {
let currentMovePixel = [
toMergeImageData.data[i], toMergeImageData.data[i+1],
toMergeImageData.data[i+2], toMergeImageData.data[i+3]
];
let currentUnderlyingPixel = [
belowImageData.data[i], belowImageData.data[i+1],
belowImageData.data[i+2], belowImageData.data[i+3]
];
if (isPixelEmpty(currentMovePixel)) {
if (!isPixelEmpty(belowImageData)) {
toMergeImageData.data[i] = currentUnderlyingPixel[0];
toMergeImageData.data[i+1] = currentUnderlyingPixel[1];
toMergeImageData.data[i+2] = currentUnderlyingPixel[2];
toMergeImageData.data[i+3] = currentUnderlyingPixel[3];
}
}
}
currentLayer.context.putImageData(toMergeImageData, 0, 0);
// Deleting the above layer
toMerge.canvas.remove();
toMerge.menuEntry.remove();
layers.splice(toMergeIndex, 1);
// Updating the layer preview
currentLayer.updateLayerPreview();
}
}
function deleteLayer(event) {
// Cannot delete all the layers
if (layers.length != 4) {
@ -362,19 +481,34 @@ function deleteLayer(event) {
}
// or the previous one if the next one doesn't exist
else {
layers[layerIndex - 1].selectLayer();
layers[layerIndex - 1].selectLayer();
}
// Deleting canvas and entry
toDelete.canvas.remove();
toDelete.menuEntry.remove();
// Removing the layer from the list
layers.splice(layerIndex, 1);
}
// Closing the menu
currentLayer.closeOptionsMenu();
}
function renameLayer(event) {
let layerIndex = layers.indexOf(currentLayer);
let toRename = currentLayer;
let p = currentLayer.menuEntry.getElementsByTagName("p")[0];
p.setAttribute("contenteditable", true);
p.classList.add("layer-name-editable");
p.focus();
simulateInput(65, true, false, false);
isRenamingLayer = true;
}
// Swap two layer entries in the layer menu
function swapLayerEntries(id1, id2) {

View File

@ -68,7 +68,7 @@ window.addEventListener("mouseup", function (mouseEvent) {
closeMenu();
if (!isChildOfByClass(mouseEvent.target, "layers-menu-entry")) {
if (currentLayer != null && !isChildOfByClass(mouseEvent.target, "layers-menu-entry")) {
currentLayer.closeOptionsMenu();
}

View File

@ -1,3 +1,22 @@
function simulateInput(keyCode, ctrl, alt, shift) {
let keyboardEvent = document.createEvent("KeyboardEvent");
let initMethod = typeof keyboardEvent.initKeyboardEvent !== 'undefined' ? "initKeyboardEvent" : "initKeyEvent";
keyboardEvent[initMethod](
"keydown", // event type: keydown, keyup, keypress
true, // bubbles
true, // cancelable
window, // view: should be window
ctrl, // ctrlKey
alt, // altKey
shift, // shiftKey
false, // metaKey
keyCode, // keyCode: unsigned long - the virtual key code, else 0
keyCode // charCode: unsigned long - the Unicode character associated with the depressed key, else 0
);
document.dispatchEvent(keyboardEvent);
}
function isPixelEmpty(pixel) {
if (pixel == null || pixel === undefined) {
return false;

View File

@ -156,10 +156,19 @@
<ul id = "layer-properties-menu">
<li>
<button id = "delete-layer" onclick = "deleteLayer()">Delete</button>
<button onclick = "deleteLayer()">Delete</button>
</li>
<li>
<button id = "rename-layer">Rename</button>
<button onclick = "renameLayer()">Rename</button>
</li>
<li>
<button onclick = "merge()">Merge below</button>
</li>
<li>
<button onclick = "flatten(true)">Flatten visible</button>
</li>
<li>
<button onclick = "flatten(false)">Flatten all</button>
</li>
</ul>
</ul>