From 845e384ae06ca38565e79d0260db5e5f2ad39ca9 Mon Sep 17 00:00:00 2001 From: Theo Cavignac Date: Thu, 22 Dec 2022 21:17:49 +0100 Subject: [PATCH 01/12] Implement importing GIMP and .hex palettes. --- js/FileManager.js | 105 ++++++++++++++++++++++++++++++++++++++-------- views/holders.hbs | 4 +- 2 files changed, 89 insertions(+), 20 deletions(-) diff --git a/js/FileManager.js b/js/FileManager.js index fcae2b5..49827c2 100644 --- a/js/FileManager.js +++ b/js/FileManager.js @@ -295,28 +295,42 @@ const FileManager = (() => { function loadPalette() { if (browsePaletteHolder.files && browsePaletteHolder.files[0]) { //make sure file is allowed filetype - var fileContentType = browsePaletteHolder.files[0].type; - if (fileContentType == 'image/png' || fileContentType == 'image/gif') { + let file = browsePaletteHolder.files[0]; + var fileContentType = + file.type + || file.name.split('.').slice(-1)[0]; + + var fileReader = new FileReader(); + + let addPalette = (colors) => { + //add to palettes so that it can be loaded when they click okay + palettes['Loaded palette'] = {}; + palettes['Loaded palette'].colors = colors; + Util.setText('palette-button', 'Loaded palette'); + Util.setText('palette-button-splash', 'Loaded palette'); + Util.toggle('palette-menu-splash'); + } + switch (fileContentType) { + case 'image/png': + case 'image/gif': //load file - var fileReader = new FileReader(); fileReader.onload = function(e) { var img = new Image(); img.onload = function() { - //draw image onto the temporary canvas var loadPaletteCanvas = document.getElementById('load-palette-canvas-holder'); var loadPaletteContext = loadPaletteCanvas.getContext('2d'); - + loadPaletteCanvas.width = img.width; loadPaletteCanvas.height = img.height; - + loadPaletteContext.drawImage(img, 0, 0); - + //create array to hold found colors var colorPalette = []; var imagePixelData = loadPaletteContext.getImageData(0,0,this.width, this.height).data; - + //loop through pixels looking for colors to add to palette for (var i = 0; i < imagePixelData.length; i += 4) { const newColor = {r:imagePixelData[i],g:imagePixelData[i + 1],b:imagePixelData[i + 2]}; @@ -325,19 +339,74 @@ const FileManager = (() => { colorPalette.push(color); } } - - //add to palettes so that it can be loaded when they click okay - palettes['Loaded palette'] = {}; - palettes['Loaded palette'].colors = colorPalette; - Util.setText('palette-button', 'Loaded palette'); - Util.setText('palette-button-splash', 'Loaded palette'); - Util.toggle('palette-menu-splash'); + + addPalette(colorPalette); }; img.src = e.target.result; }; - fileReader.readAsDataURL(browsePaletteHolder.files[0]); + break; + case 'gpl': + fileReader.onload = function() { + file.text().then((content) => { + let colorPalette = content.split(/\r?\n/) + // Skip header line + .slice(1) + .map((line) => line.trim()) + .filter((line) => line != "") + // discard comment lines + .filter((line) => !line.startsWith('#')) + // discard meta data lines + .filter((line) => !line.includes(':')) + .map((line) => { + let components = line.split(/\s+/); + + if (components.length < 3) { + alert(`Invalid color specification ${line}.`); + return "#000000" + } + + let [r, g, b, ...rest] = components; + let color = { + r: parseInt(r), + g: parseInt(g), + b: parseInt(b), + }; + + if (isNaN(color.r) || isNaN(color.g) || isNaN(color.b)) { + alert(`Invalid color specification ${line}.`); + return "#000000" + } + + return '#' + Color.rgbToHex(color); + }); + + addPalette(colorPalette); + }); + }; + break; + case 'hex': + fileReader.onload = function() { + file.text().then((content) => { + let colorPalette = content.split(/\r?\n/) + .map((line) => line.trim()) + .filter((line) => line != "") + // discard comment lines + .filter((line) => !line.startsWith('#')) + .map((line) => { + if (line.match(/[0-9A-Fa-f]{6}/)) { + return '#' + line; + } + alert(`Invalid hex color ${line}.`); + return '#000000'; + }); + addPalette(colorPalette); + }); + }; + break; + default: + alert('Only PNG, GIF, .hex and .gpl files are supported at this time.'); } - else alert('Only PNG and GIF files are supported at this time.'); + fileReader.readAsDataURL(browsePaletteHolder.files[0]); } browsePaletteHolder.value = null; @@ -526,4 +595,4 @@ const FileManager = (() => { } }) return ret; -})(); \ No newline at end of file +})(); diff --git a/views/holders.hbs b/views/holders.hbs index a144583..054aaad 100644 --- a/views/holders.hbs +++ b/views/holders.hbs @@ -2,7 +2,7 @@ dl dl - + - \ No newline at end of file + From c08c7f2d0b6cc45bde16ac36ee03582f1a9e09f2 Mon Sep 17 00:00:00 2001 From: Theo Cavignac Date: Thu, 22 Dec 2022 23:37:38 +0100 Subject: [PATCH 02/12] Refactor palette loading. --- js/FileManager.js | 195 ++++++++++++++++++++++++---------------------- 1 file changed, 100 insertions(+), 95 deletions(-) diff --git a/js/FileManager.js b/js/FileManager.js index 49827c2..963491a 100644 --- a/js/FileManager.js +++ b/js/FileManager.js @@ -292,9 +292,9 @@ const FileManager = (() => { return JSON.stringify(dictionary); } + function loadPalette() { if (browsePaletteHolder.files && browsePaletteHolder.files[0]) { - //make sure file is allowed filetype let file = browsePaletteHolder.files[0]; var fileContentType = file.type @@ -302,116 +302,121 @@ const FileManager = (() => { var fileReader = new FileReader(); - let addPalette = (colors) => { - //add to palettes so that it can be loaded when they click okay - palettes['Loaded palette'] = {}; - palettes['Loaded palette'].colors = colors; - Util.setText('palette-button', 'Loaded palette'); - Util.setText('palette-button-splash', 'Loaded palette'); - Util.toggle('palette-menu-splash'); - } - + // dispatch on file type switch (fileContentType) { case 'image/png': case 'image/gif': - //load file - fileReader.onload = function(e) { - var img = new Image(); - img.onload = function() { - //draw image onto the temporary canvas - var loadPaletteCanvas = document.getElementById('load-palette-canvas-holder'); - var loadPaletteContext = loadPaletteCanvas.getContext('2d'); - - loadPaletteCanvas.width = img.width; - loadPaletteCanvas.height = img.height; - - loadPaletteContext.drawImage(img, 0, 0); - - //create array to hold found colors - var colorPalette = []; - var imagePixelData = loadPaletteContext.getImageData(0,0,this.width, this.height).data; - - //loop through pixels looking for colors to add to palette - for (var i = 0; i < imagePixelData.length; i += 4) { - const newColor = {r:imagePixelData[i],g:imagePixelData[i + 1],b:imagePixelData[i + 2]}; - var color = '#' + Color.rgbToHex(newColor); - if (colorPalette.indexOf(color) == -1) { - colorPalette.push(color); - } - } - - addPalette(colorPalette); - }; - img.src = e.target.result; - }; + fileReader.onload = loadPaletteFromImage; + fileReader.readAsDataURL(browsePaletteHolder.files[0]); break; case 'gpl': - fileReader.onload = function() { - file.text().then((content) => { - let colorPalette = content.split(/\r?\n/) - // Skip header line - .slice(1) - .map((line) => line.trim()) - .filter((line) => line != "") - // discard comment lines - .filter((line) => !line.startsWith('#')) - // discard meta data lines - .filter((line) => !line.includes(':')) - .map((line) => { - let components = line.split(/\s+/); - - if (components.length < 3) { - alert(`Invalid color specification ${line}.`); - return "#000000" - } - - let [r, g, b, ...rest] = components; - let color = { - r: parseInt(r), - g: parseInt(g), - b: parseInt(b), - }; - - if (isNaN(color.r) || isNaN(color.g) || isNaN(color.b)) { - alert(`Invalid color specification ${line}.`); - return "#000000" - } - - return '#' + Color.rgbToHex(color); - }); - - addPalette(colorPalette); - }); - }; + fileReader.onload = loadPaletteFromGimp; + fileReader.readAsText(browsePaletteHolder.files[0]); break; case 'hex': - fileReader.onload = function() { - file.text().then((content) => { - let colorPalette = content.split(/\r?\n/) - .map((line) => line.trim()) - .filter((line) => line != "") - // discard comment lines - .filter((line) => !line.startsWith('#')) - .map((line) => { - if (line.match(/[0-9A-Fa-f]{6}/)) { - return '#' + line; - } - alert(`Invalid hex color ${line}.`); - return '#000000'; - }); - addPalette(colorPalette); - }); - }; + fileReader.onload = loadPaletteFromHex; + fileReader.readAsText(browsePaletteHolder.files[0]); break; default: alert('Only PNG, GIF, .hex and .gpl files are supported at this time.'); } - fileReader.readAsDataURL(browsePaletteHolder.files[0]); } browsePaletteHolder.value = null; } + function addPalette(colors) { + //add to palettes so that it can be loaded when they click okay + palettes['Loaded palette'] = {}; + palettes['Loaded palette'].colors = colors; + Util.setText('palette-button', 'Loaded palette'); + Util.setText('palette-button-splash', 'Loaded palette'); + Util.toggle('palette-menu-splash'); + } + + function loadPaletteFromImage(e) { + var img = new Image(); + img.onload = function() { + //draw image onto the temporary canvas + var loadPaletteCanvas = document.getElementById('load-palette-canvas-holder'); + var loadPaletteContext = loadPaletteCanvas.getContext('2d'); + + loadPaletteCanvas.width = img.width; + loadPaletteCanvas.height = img.height; + + loadPaletteContext.drawImage(img, 0, 0); + + //create array to hold found colors + var colorPalette = []; + var imagePixelData = loadPaletteContext.getImageData(0,0,this.width, this.height).data; + + //loop through pixels looking for colors to add to palette + for (var i = 0; i < imagePixelData.length; i += 4) { + const newColor = {r:imagePixelData[i],g:imagePixelData[i + 1],b:imagePixelData[i + 2]}; + var color = '#' + Color.rgbToHex(newColor); + if (colorPalette.indexOf(color) == -1) { + colorPalette.push(color); + } + } + + addPalette(colorPalette); + }; + img.src = e.target.result; + } + + function loadPaletteFromGimp(e) { + let content = e.target.result; + let colorPalette = content.split(/\r?\n/) + // Skip header line + .slice(1) + .map((line) => line.trim()) + .filter((line) => line != "") + // discard comment lines + .filter((line) => !line.startsWith('#')) + // discard meta data lines + .filter((line) => !line.includes(':')) + .map((line) => { + let components = line.split(/\s+/); + + if (components.length < 3) { + alert(`Invalid color specification ${line}.`); + return "#000000" + } + + let [r, g, b, ...rest] = components; + let color = { + r: parseInt(r), + g: parseInt(g), + b: parseInt(b), + }; + + if (isNaN(color.r) || isNaN(color.g) || isNaN(color.b)) { + alert(`Invalid color specification ${line}.`); + return "#000000" + } + + return '#' + Color.rgbToHex(color); + }); + addPalette(colorPalette); + } + + function loadPaletteFromHex(e) { + let content = e.target.result; + let colorPalette = content.split(/\r?\n/) + .map((line) => line.trim()) + .filter((line) => line != "") + // discard comment lines + .filter((line) => !line.startsWith('#')) + .map((line) => { + if (line.match(/[0-9A-Fa-f]{6}/)) { + return '#' + line; + } + alert(`Invalid hex color ${line}.`); + return '#000000'; + }); + addPalette(colorPalette); + } + currentImportPivotElement = undefined; currentImportPivotPosition = 'middle'; isImportWindowInitialized = false; From 0e676184a946b950303f3964e7770abecfab8033 Mon Sep 17 00:00:00 2001 From: Theo Cavignac Date: Fri, 23 Dec 2022 00:00:54 +0100 Subject: [PATCH 03/12] Fix a bug due to non standard attribute. --- js/ToolManager.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/js/ToolManager.js b/js/ToolManager.js index caa6a53..2228611 100644 --- a/js/ToolManager.js +++ b/js/ToolManager.js @@ -47,8 +47,9 @@ const ToolManager = (() => { if (!EditorState.documentCreated || Dialogue.isOpen()) return; - const isHoveringMenuElement = !!mouseEvent.path.find(n=>n.id && n.id.includes("-menu")); - if(isHoveringMenuElement)return; + // Hovering a menu element + const path = mouseEvent.composedPath && mouseEvent.composedPath(); + if (path && !!path.find(n=>n.id && n.id.includes("-menu"))) return; let mousePos = Input.getCursorPosition(mouseEvent); tools["zoom"].onMouseWheel(mousePos, mouseEvent.deltaY < 0 ? 'in' : 'out'); @@ -164,4 +165,4 @@ const ToolManager = (() => { return { currentTool } -})(); \ No newline at end of file +})(); From a36281935b11a478cf55c1d10ca6653c4e054033 Mon Sep 17 00:00:00 2001 From: Theo Cavignac Date: Tue, 27 Dec 2022 18:18:38 +0100 Subject: [PATCH 04/12] Implement loading a palette from the menu. --- js/ColorModule.js | 10 ++++++---- js/FileManager.js | 25 +++++++++++++++++++------ js/TopMenuModule.js | 5 ++++- views/main-menu.hbs | 3 ++- 4 files changed, 31 insertions(+), 12 deletions(-) diff --git a/js/ColorModule.js b/js/ColorModule.js index c791bc5..44974e3 100644 --- a/js/ColorModule.js +++ b/js/ColorModule.js @@ -388,10 +388,12 @@ const ColorModule = (() => { * * @param {*} paletteColors The colours of the palette */ - function createColorPalette(paletteColors) { + function createColorPalette(paletteColors, clearCurrent=true) { //remove current palette - while (colorsMenu.childElementCount > 1) - colorsMenu.children[0].remove(); + if (clearCurrent) { + while (colorsMenu.childElementCount > 1) + colorsMenu.children[0].remove(); + } var lightestColor = new Color("hex", '#000000'); var darkestColor = new Color("hex", '#ffffff'); @@ -527,4 +529,4 @@ const ColorModule = (() => { updateCurrentColor, getSelectedColor, } -})(); \ No newline at end of file +})(); diff --git a/js/FileManager.js b/js/FileManager.js index 963491a..730181d 100644 --- a/js/FileManager.js +++ b/js/FileManager.js @@ -293,6 +293,13 @@ const FileManager = (() => { return JSON.stringify(dictionary); } + let fromMenu = false; + + function openImportPaletteWindow() { + fromMenu = true; + document.getElementById('load-palette-browse-holder').click(); + } + function loadPalette() { if (browsePaletteHolder.files && browsePaletteHolder.files[0]) { let file = browsePaletteHolder.files[0]; @@ -326,12 +333,17 @@ const FileManager = (() => { } function addPalette(colors) { - //add to palettes so that it can be loaded when they click okay - palettes['Loaded palette'] = {}; - palettes['Loaded palette'].colors = colors; - Util.setText('palette-button', 'Loaded palette'); - Util.setText('palette-button-splash', 'Loaded palette'); - Util.toggle('palette-menu-splash'); + if (fromMenu) { + ColorModule.createColorPalette(colors, clearCurrent=false); + } else { + // From splash screen + // add to palettes so that it can be loaded when they click okay + palettes['Loaded palette'] = {}; + palettes['Loaded palette'].colors = colors; + Util.setText('palette-button', 'Loaded palette'); + Util.setText('palette-button-splash', 'Loaded palette'); + Util.toggle('palette-menu-splash'); + } } function loadPaletteFromImage(e) { @@ -587,6 +599,7 @@ const FileManager = (() => { openPixelExportWindow, openSaveProjectWindow, openImportImageWindow, + openImportPaletteWindow, open } diff --git a/js/TopMenuModule.js b/js/TopMenuModule.js index 6b88ede..6dfa558 100644 --- a/js/TopMenuModule.js +++ b/js/TopMenuModule.js @@ -46,6 +46,9 @@ const TopMenuModule = (() => { case 'Import': Events.on('click', currSubmenuButton, FileManager.openImportImageWindow); break; + case 'Load palette': + Events.on('click', currSubmenuButton, FileManager.openImportPaletteWindow); + break; case 'Export': Events.on('click', currSubmenuButton, FileManager.openPixelExportWindow); break; @@ -139,4 +142,4 @@ const TopMenuModule = (() => { addInfoElement, resetInfos } -})(); \ No newline at end of file +})(); diff --git a/views/main-menu.hbs b/views/main-menu.hbs index 2b43857..059037f 100644 --- a/views/main-menu.hbs +++ b/views/main-menu.hbs @@ -7,6 +7,7 @@
  • +
  • Exit
  • @@ -74,4 +75,4 @@
  • {{> checkbox}}
  • - \ No newline at end of file + From 901fdced904536951acda56a382c5694a75e2ab4 Mon Sep 17 00:00:00 2001 From: amritrai5757 Date: Tue, 3 Jan 2023 06:29:19 +0000 Subject: [PATCH 05/12] Fixed removal of all the colors from palette --- js/PaletteBlock.js | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/js/PaletteBlock.js b/js/PaletteBlock.js index 3035e43..5189d79 100644 --- a/js/PaletteBlock.js +++ b/js/PaletteBlock.js @@ -109,10 +109,17 @@ const PaletteBlock = (() => { endIndex = tmp; } - for (let i=startIndex; i<=endIndex; i++) { - coloursList.removeChild(coloursList.children[startIndex]); + if(coloursList.childElementCount > Math.abs(startIndex-endIndex)+1) { + for (let i=startIndex; i<=endIndex; i++) { + coloursList.removeChild(coloursList.children[startIndex]); + } + clearBorders(); + } else if(coloursList.childElementCount > 1){ + for (let i=startIndex; i Date: Tue, 3 Jan 2023 12:58:14 +0000 Subject: [PATCH 06/12] Issue of removal of all colors from palette --- js/PaletteBlock.js | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/js/PaletteBlock.js b/js/PaletteBlock.js index 5189d79..c841856 100644 --- a/js/PaletteBlock.js +++ b/js/PaletteBlock.js @@ -108,18 +108,21 @@ const PaletteBlock = (() => { startIndex = endIndex; endIndex = tmp; } - + // If total colors in palette is more than colors selected -> remove all selected colors if(coloursList.childElementCount > Math.abs(startIndex-endIndex)+1) { for (let i=startIndex; i<=endIndex; i++) { coloursList.removeChild(coloursList.children[startIndex]); } clearBorders(); - } else if(coloursList.childElementCount > 1){ - for (let i=startIndex; i Remove last color + else if(coloursList.childElementCount > 1){ + coloursList.removeChild(coloursList.children[endIndex]); clearBorders(); } + + //To avoid removal of any color by just pressing remove button without selecting any color + currentSelection.startIndex=endIndex; } /** Starts selecting a ramp. Saves the data needed to draw the outline. From 153f0366d4aa588ed4ebaace4cf621bec365d925 Mon Sep 17 00:00:00 2001 From: amritrai5757 Date: Tue, 3 Jan 2023 13:57:55 +0000 Subject: [PATCH 07/12] Issue of removal of all colors from palette --- js/PaletteBlock.js | 26 ++++++++++++-------------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/js/PaletteBlock.js b/js/PaletteBlock.js index c841856..e6056ba 100644 --- a/js/PaletteBlock.js +++ b/js/PaletteBlock.js @@ -108,21 +108,19 @@ const PaletteBlock = (() => { startIndex = endIndex; endIndex = tmp; } - // If total colors in palette is more than colors selected -> remove all selected colors - if(coloursList.childElementCount > Math.abs(startIndex-endIndex)+1) { - for (let i=startIndex; i<=endIndex; i++) { - coloursList.removeChild(coloursList.children[startIndex]); - } - clearBorders(); - } - //else,if there is more than 1 color in palette & user wants to remove all colors -> Remove last color - else if(coloursList.childElementCount > 1){ - coloursList.removeChild(coloursList.children[endIndex]); - clearBorders(); + // If there is only 1 color in palette and user wants to remove it, do nothing + if(coloursList.childElementCount == 1) { + return; } - - //To avoid removal of any color by just pressing remove button without selecting any color - currentSelection.startIndex=endIndex; + + // If user wants to remove all colors of palette, remove all colors except last one + if(coloursList.childElementCount == endIndex-startIndex+1) { + endIndex--; + } + for (let i=startIndex; i<=endIndex; i++) { + coloursList.removeChild(coloursList.children[startIndex]); + } + clearBorders(); } /** Starts selecting a ramp. Saves the data needed to draw the outline. From 08eb130301af85218368450aac0a9d3215fe751d Mon Sep 17 00:00:00 2001 From: Amrit Rai <86003701+amritrai5757@users.noreply.github.com> Date: Thu, 9 Feb 2023 19:09:00 +0530 Subject: [PATCH 08/12] Resolved previous color selection i,e issue #111 --- js/ColorModule.js | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/js/ColorModule.js b/js/ColorModule.js index 44974e3..3b90a9b 100644 --- a/js/ColorModule.js +++ b/js/ColorModule.js @@ -158,14 +158,15 @@ const ColorModule = (() => { */ function addColorButtonEvent() { //generate random color - const newColor = new Color("hsv", Math.floor(Math.random()*360), Math.floor(Math.random()*100), Math.floor(Math.random()*100)).hex; - - //remove current color selection - document.querySelector('#colors-menu li.selected')?.classList.remove('selected'); + const newColor = new Color("hsv", Math.floor(Math.random()*360), Math.floor(Math.random()*100), Math.floor(Math.random()*100)).hex; //add new color and make it selected let addedColor = addColor(newColor); addedColor.classList.add('selected'); + + //remove previous color selection + document.querySelector('#colors-menu li.selected')?.classList.remove('selected'); + addedColor.style.width = squareSize + "px"; addedColor.style.height = squareSize + "px"; updateCurrentColor(newColor); From 9b89fd40648874d2439e6d4aefaec2c3cd48910c Mon Sep 17 00:00:00 2001 From: Amrit Rai <86003701+amritrai5757@users.noreply.github.com> Date: Fri, 10 Feb 2023 17:37:41 +0530 Subject: [PATCH 09/12] Add devcontainer for easy development setup --- .devcontainer/devcontainer.json | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 .devcontainer/devcontainer.json diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 0000000..71a27c7 --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,23 @@ +// For format details, see https://aka.ms/devcontainer.json. For config options, see the +// README at: https://github.com/devcontainers/templates/tree/main/src/javascript-node +{ + "name": "Node.js", + // Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile + "image": "mcr.microsoft.com/devcontainers/javascript-node:0-18", + + // Features to add to the dev container. More info: https://containers.dev/features. + // "features": {}, + + // Use 'forwardPorts' to make a list of ports inside the container available locally. + "forwardPorts": [3000], + + // Use 'postCreateCommand' to run commands after the container is created. + //write post create command npm install & npm run hot + "postCreateCommand": "npm install" + + // Configure tool-specific properties. + // "customizations": {}, + + // Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root. + // "remoteUser": "root" +} From aeb8892dc051d5e1536b1553a0a7ab2c3ca90205 Mon Sep 17 00:00:00 2001 From: Nicola <47360416+unsettledgames@users.noreply.github.com> Date: Mon, 13 Feb 2023 18:11:40 +0100 Subject: [PATCH 10/12] Added codespace instructions --- README.md | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 150242b..ba82bdf 100644 --- a/README.md +++ b/README.md @@ -44,13 +44,18 @@ Suggestions / Planned features: ## How to Contribute ### Requirements - -You must have node.js and git installed. +No requirements if you want to use Github's Codespaces. If you prefer to setup your environment on desktop, you'll need to have node.js and git installed. You also need `npm` in version 7 (because of 2nd version of lockfile which was introduced there) which comes with Node.js 15 or newer. To simplify installation of proper versions you can make use of [nvm](https://github.com/nvm-sh/nvm#installing-and-updating) and run `nvm install` – it will activate proper Node.js version in your current command prompt session. ### Contribution Workflow +#### Github Codespaces +1. Click **Fork** above. It will automatically create a copy of this repository and add it to your account. +2. At the top of this page, select the branch you want to work on. +3. Click on "Code". Select the "Codespaces" submenu and click on "Create codespace on **branch name**". +4. Run `npm install`. Then run `npm run hot`: it will open a popup containing the editor, so make sure to disable your adblock if you're using one. + 1. Click **Fork** above. It will automatically create a copy of this repository and add it to your account. 2. Clone the repository to your computer. 3. Open the folder in command prompt and run **`npm install`** From 8fcc50b1a4e2d688c2b2fa217304f8585f28a4af Mon Sep 17 00:00:00 2001 From: Nicola <47360416+unsettledgames@users.noreply.github.com> Date: Mon, 13 Feb 2023 18:12:20 +0100 Subject: [PATCH 11/12] Update README.md --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index ba82bdf..59d86e1 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ This is a browser based software for creating pixel art The tool can be viewed online here: https://lospec.com/pixel-editor -## How to contribute +## Before contributing Before starting to work, please open an issue for discussion so that we can organize the work without creating too many conflicts. If your contribution is going to fix a bug, please make a fork and use the bug-fixes branch. If you want to work on a new feature, please use the new-feature branch instead. @@ -56,6 +56,8 @@ You also need `npm` in version 7 (because of 2nd version of lockfile which was i 3. Click on "Code". Select the "Codespaces" submenu and click on "Create codespace on **branch name**". 4. Run `npm install`. Then run `npm run hot`: it will open a popup containing the editor, so make sure to disable your adblock if you're using one. +#### Desktop environment + 1. Click **Fork** above. It will automatically create a copy of this repository and add it to your account. 2. Clone the repository to your computer. 3. Open the folder in command prompt and run **`npm install`** From 9463d7e3913a82e4c1e34f5683fd35c3526c4668 Mon Sep 17 00:00:00 2001 From: TsegabAbreham Date: Mon, 24 Jun 2024 22:09:47 -0400 Subject: [PATCH 12/12] Update RectangularSelectionTool.js Fixed issue #120 --- js/tools/RectangularSelectionTool.js | 104 ++++++++++++++------------- 1 file changed, 54 insertions(+), 50 deletions(-) diff --git a/js/tools/RectangularSelectionTool.js b/js/tools/RectangularSelectionTool.js index f7bee0b..b0eb200 100644 --- a/js/tools/RectangularSelectionTool.js +++ b/js/tools/RectangularSelectionTool.js @@ -1,9 +1,9 @@ class RectangularSelectionTool extends SelectionTool { - - constructor (name, options, switchFunc, moveTool) { + constructor(name, options, switchFunc, moveTool) { super(name, options, switchFunc, moveTool); Events.on('click', this.mainButton, switchFunc, this); + // Tutorial setup this.resetTutorial(); this.addTutorialTitle("Rectangular selection tool"); this.addTutorialKey("M", " to select the rectangular selection tool"); @@ -20,82 +20,79 @@ class RectangularSelectionTool extends SelectionTool { onStart(mousePos, mouseTarget) { super.onStart(mousePos, mouseTarget); + // Validate initial position within the canvas if (Util.isChildOfByClass(mouseTarget, "editor-top-menu") || - !Util.cursorInCanvas(currFile.canvasSize, [mousePos[0]/currFile.zoom, mousePos[1]/currFile.zoom])) + !Util.cursorInCanvas(currFile.canvasSize, [mousePos[0] / currFile.zoom, mousePos[1] / currFile.zoom])) return; - // Avoiding external selections - if (this.startMousePos[0] < 0) { - this.startMousePos[0] = 0; - } - else if (this.startMousePos[0] > currFile.currentLayer.canvas.width) { - this.startMousePos[0] = currFile.currentLayer.canvas.width; - } + // Constrain start position to canvas boundaries + this.startMousePos[0] = Math.max(0, Math.min(this.startMousePos[0], currFile.currentLayer.canvas.width)); + this.startMousePos[1] = Math.max(0, Math.min(this.startMousePos[1], currFile.currentLayer.canvas.height)); - if (this.startMousePos[1] < 0) { - this.startMousePos[1] = 0; - } - else if (this.startMousePos[1] > currFile.currentLayer.canvas.height) { - this.startMousePos[1] = currFile.currentLayer.canvas.height; - } + // Initialize the endMousePos to startMousePos to draw an initial dot + this.endMousePos = [...this.startMousePos]; - // Drawing the rect - this.drawSelection(this.startMousePos[0], this.startMousePos[1]); + // Draw the initial selection rectangle + this.drawSelection(); } onDrag(mousePos, mouseTarget) { super.onDrag(mousePos, mouseTarget); + // Validate drag position within the canvas if (Util.isChildOfByClass(mouseTarget, "editor-top-menu") || - !Util.cursorInCanvas(currFile.canvasSize, [mousePos[0]/currFile.zoom, mousePos[1]/currFile.zoom])) + !Util.cursorInCanvas(currFile.canvasSize, [mousePos[0] / currFile.zoom, mousePos[1] / currFile.zoom])) return; - // Drawing the rect - this.endMousePos = [Math.floor(mousePos[0] / currFile.zoom), Math.floor(mousePos[1] / currFile.zoom)]; - this.drawSelection(Math.floor(mousePos[0] / currFile.zoom), Math.floor(mousePos[1] / currFile.zoom)); + // Update the end position with precise rounding + this.endMousePos = [Math.round(mousePos[0] / currFile.zoom), Math.round(mousePos[1] / currFile.zoom)]; + + // Draw the updated selection rectangle + this.drawSelection(); } onEnd(mousePos, mouseTarget) { super.onEnd(mousePos, mouseTarget); - + if (Util.isChildOfByClass(mouseTarget, "editor-top-menu")) return; new HistoryState().EditCanvas(); - // Getting the end position - this.endMousePos = [Math.floor(mousePos[0] / currFile.zoom), Math.floor(mousePos[1] / currFile.zoom)]; + // Finalize the end position with precise rounding + this.endMousePos = [Math.round(mousePos[0] / currFile.zoom), Math.round(mousePos[1] / currFile.zoom)]; - // Inverting end and start (start must always be the top left corner) + // Ensure startMousePos is top-left and endMousePos is bottom-right if (this.endMousePos[0] < this.startMousePos[0]) { - let tmp = this.endMousePos[0]; - this.endMousePos[0] = this.startMousePos[0]; - this.startMousePos[0] = tmp; + [this.startMousePos[0], this.endMousePos[0]] = [this.endMousePos[0], this.startMousePos[0]]; } - // Same for the y if (this.endMousePos[1] < this.startMousePos[1]) { - let tmp = this.endMousePos[1]; - this.endMousePos[1] = this.startMousePos[1]; - this.startMousePos[1] = tmp; + [this.startMousePos[1], this.endMousePos[1]] = [this.endMousePos[1], this.startMousePos[1]]; } - if (Util.cursorInCanvas(currFile.canvasSize, [mousePos[0]/currFile.zoom, mousePos[1]/currFile.zoom])) { - this.boundingBox.minX = this.startMousePos[0] - 1; - this.boundingBox.maxX = this.endMousePos[0] + 1; - this.boundingBox.minY = this.startMousePos[1] - 1; - this.boundingBox.maxY = this.endMousePos[1] + 1; + // Set the bounding box exactly within the selection without expanding + if (Util.cursorInCanvas(currFile.canvasSize, [mousePos[0] / currFile.zoom, mousePos[1] / currFile.zoom])) { + this.boundingBox.minX = this.startMousePos[0]; + this.boundingBox.maxX = this.endMousePos[0]; + this.boundingBox.minY = this.startMousePos[1]; + this.boundingBox.maxY = this.endMousePos[1]; } - // Switch to the move tool so that the user can move the selection + // Switch to the move tool to move the selection this.switchFunc(this.moveTool); - // Obtain the selected pixels this.moveTool.setSelectionData(this.getSelection(), this); } cutSelection() { super.cutSelection(); - currFile.currentLayer.context.clearRect(this.currSelection.left-0.5, this.currSelection.top-0.5, - this.currSelection.width, this.currSelection.height); + + // Clear the selected area without fractional offsets + currFile.currentLayer.context.clearRect( + this.currSelection.left, + this.currSelection.top, + this.currSelection.width, + this.currSelection.height + ); } onSelect() { @@ -107,15 +104,22 @@ class RectangularSelectionTool extends SelectionTool { } drawSelection() { - // Getting the vfx context + // Access the VFX context for visualizing the selection rectangle let vfxContext = currFile.VFXLayer.context; - - // Clearing the vfx canvas + + // Clear the VFX canvas to ensure no previous selection visuals are visible vfxContext.clearRect(0, 0, currFile.VFXLayer.canvas.width, currFile.VFXLayer.canvas.height); - currFile.VFXLayer.drawLine(this.startMousePos[0], this.startMousePos[1], this.endMousePos[0], this.startMousePos[1], 1); - currFile.VFXLayer.drawLine(this.endMousePos[0], this.startMousePos[1], this.endMousePos[0], this.endMousePos[1], 1); - currFile.VFXLayer.drawLine(this.endMousePos[0], this.endMousePos[1], this.startMousePos[0], this.endMousePos[1], 1); - currFile.VFXLayer.drawLine(this.startMousePos[0], this.endMousePos[1], this.startMousePos[0], this.startMousePos[1], 1); + // Draw the selection rectangle with precise lines + if (this.startMousePos && this.endMousePos) { + vfxContext.strokeStyle = 'rgba(0, 0, 255, 1)'; // Example color for the rectangle + vfxContext.lineWidth = 1; + vfxContext.strokeRect( + Math.min(this.startMousePos[0], this.endMousePos[0]), + Math.min(this.startMousePos[1], this.endMousePos[1]), + Math.abs(this.endMousePos[0] - this.startMousePos[0]), + Math.abs(this.endMousePos[1] - this.startMousePos[1]) + ); + } } -} \ No newline at end of file +}