Merge pull request #38 from unsettledgames/master
Palette block, colour picker, splash page, line tool, minor improvements, bug fixes
|
@ -0,0 +1,38 @@
|
||||||
|
---
|
||||||
|
name: Bug report
|
||||||
|
about: Create a report to help us improve
|
||||||
|
title: ''
|
||||||
|
labels: ''
|
||||||
|
assignees: ''
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Describe the bug**
|
||||||
|
A clear and concise description of what the bug is.
|
||||||
|
|
||||||
|
**To Reproduce**
|
||||||
|
Steps to reproduce the behavior:
|
||||||
|
1. Go to '...'
|
||||||
|
2. Click on '....'
|
||||||
|
3. Scroll down to '....'
|
||||||
|
4. See error
|
||||||
|
|
||||||
|
**Expected behavior**
|
||||||
|
A clear and concise description of what you expected to happen.
|
||||||
|
|
||||||
|
**Screenshots**
|
||||||
|
If applicable, add screenshots to help explain your problem.
|
||||||
|
|
||||||
|
**Desktop (please complete the following information):**
|
||||||
|
- OS: [e.g. iOS]
|
||||||
|
- Browser [e.g. chrome, safari]
|
||||||
|
- Version [e.g. 22]
|
||||||
|
|
||||||
|
**Smartphone (please complete the following information):**
|
||||||
|
- Device: [e.g. iPhone6]
|
||||||
|
- OS: [e.g. iOS8.1]
|
||||||
|
- Browser [e.g. stock browser, safari]
|
||||||
|
- Version [e.g. 22]
|
||||||
|
|
||||||
|
**Additional context**
|
||||||
|
Add any other context about the problem here.
|
|
@ -0,0 +1,10 @@
|
||||||
|
---
|
||||||
|
name: Custom issue template
|
||||||
|
about: Describe this issue template's purpose here.
|
||||||
|
title: ''
|
||||||
|
labels: ''
|
||||||
|
assignees: ''
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,17 @@
|
||||||
|
---
|
||||||
|
name: Feature request
|
||||||
|
about: Suggest an idea for this project
|
||||||
|
title: ''
|
||||||
|
labels: ''
|
||||||
|
assignees: ''
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Is your feature request related to a problem? Please describe.**
|
||||||
|
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
|
||||||
|
|
||||||
|
**Describe the solution you'd like**
|
||||||
|
A clear and concise description of what you want to happen.
|
||||||
|
|
||||||
|
**Additional context**
|
||||||
|
Add any other context or screenshots about the feature request here.
|
|
@ -1,4 +1,5 @@
|
||||||
_ext
|
/_ext/*
|
||||||
|
!/_ext/svg/
|
||||||
routes
|
routes
|
||||||
build
|
build
|
||||||
node_modules
|
node_modules
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
<mxfile host="app.diagrams.net" modified="2021-04-25T14:57:23.836Z" agent="5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.85 Safari/537.36 Edg/90.0.818.46" etag="K3S2uQVQjw_jQCK8DyHB" version="14.6.6" type="device"><diagram id="C5RBs43oDa-KdzZeNtuy" name="Page-1">7V1bc+K4Ev41VOU8kPI98BjIZGb3MHuyk9nM7KOCBWhiWxxZBJhfv5ItG2zJxgSMsx5XpSq4Jd/0tVqfultyzxz7m48ELBefsQu9nqG5m5551zMMfWAN2D8u2QqJMxSSOUGukO0Ej+gnFEJNSFfIhWGmIsXYo2iZFU5xEMApzcgAIXidrTbDXvauSzCHkuBxCjxZ+g25dBFLB8bNTv4JovkiuTN7wbjEB0ll8SbhArh4vScyP/TMMcGYxr/8zRh6vPWSdvn22/abN3lxPv7+Z/h/8Nfov1//eOrHF7s/5pT0FQgM6Jsv3X+6tz5PHp5mqz/+fnn83+Oy/xX3LUe8G90mDQZd1n7iEBO6wHMcAO/DTjoieBW4kF9WY0e7OhOMl0yoM+EPSOlWKANYUcxEC+p7opS9Btl+Zwd97VpLBX/zC15rw1RwtxG3iI+2+0cPkCAfUkiEMH4N/uw52A+0magX4hWZilN/vszuPz1ZP74vH+7/XGsfb59/74u21Sggc0hLGnRPM1ifgpg9I9myEwn0AEWv2acDQrfnab0dfuyHgFANZ9lTvgJvJe70lfW18DMMVhLO4Rr5Hgg4oDMc0ARy9vAj4KF5wH5PWdvxFh69QkIR61O3ooByoEfTBfLcCdjiFW+RkILpS3I0WmCCfrLLggRzVkyo0AjDydR45GcKHAkMWZ2HBDY9FU1ASEWdKfY8sAzRc/TAvIrPYEHBCFOK/eRCGSVN+250QAl+Sa2BnmpPVWXhrQE3e0jKcItSJ7GCifU0xfF6Z4p0R8gW+2bI0opVRNzuCzOXIJizRkjvZ5i5+1kV72c42dsBjwEfAApHvBlDSTHTV327rpqSrvbF2BBKusqam+7ppQdntFArwyWYomA+iercWTvJF/G6XITZuTMv0ogFcl0YRBpDAQWxUnE1WWIU0Kg97BH7Y6021q7tns0eaMyO9d0x++PVCR3jgCkXQJEWQaaxa8i1tkwZhVnUlFpY2skPa+E2C+6xSnA2s2RJUEsIeyiyRHuDhNxLD8DvMyCj8Ung/TUajfq6pBOmrBOmAn8PPEPvAYeIIsyvT+K6Ob04BH1doNpGNVAHNWFqKzAdcUyhx+wSH3aukPufHtcpU4+1mxczLpir0LKuXhfeTkVLPigZOKoCrqaKg0apIm//faJ4gCSekQ8qW6MqH9SdC/FB5d1tu1HMctTesP8loJlmo6DdNAHae2l7Y9hk2zuNzIffS9s7dpNtL09eH9AGspZGFBMJlm76mp++SlyiqgYVTl9T11fJ9NVQziTMEtr5r5u+qgd2SVv59NXD83CP8/YjQkwpo6R58ZLpBaRhhiBnKsyQB/MngSnvhveIN1rZebHDJyOmO0dQ9jmAxx5QdYYHtpCoTnERYO+5go/bkEK/+Elipg9dRpUpV8O2Uf1jZvUHOmexQTxmVq/ui5pVk8E2pC4gQdzyaX0NqFY1sWeY16vZpwziLzzNsyoyJ91okjnJ/rV4iOgo08UpUz/lLMe6/NOIaXs5k+wz7Ee9lxN8HnrO8QcU3kZ8p5hhxBSluDzmSxNeS8lN2AigKnydbVTi6QJOXyB5xoC4orxjNEeNfdaxjEYdHDPrYjROx2jOjqpe2XNdE6g3ClB5KAIHkXlhFu9KjlPggLXNXnHLOnp9YA+qgW0aNaE9kND+8MreaLSSo8odH6qfDzk5OmQZFa2BrrWeDg2VdIgpmffM1OHXzoJ4i2kanIVdnCMLQj1HlB3cHbs4FdULpkGoQZX9wAmBuIKvkddzvNepZaIBfUTjmh3LqAp6VUZZlkR3Guqy61M9IewoxiVcLo59kGOk3pWMqW9VlqVaU9VplgHwJW+Le9j9EoufUKwWefkET19g/irTJPhT5KNxCVgzMzUGwStQBMleEVwnZS0zj3XzoSKndEkASdVL6nO36F1iaA24qhwuSmBr40RFuaEUz5khjc0HoluF2yWuERsSRfFq6TJr+bBvFXK1WmYialQS2VGjVJKbuhw1tiWhddHcxGtNy4Yar7Ubo9fs0qOSFNJ3H4TUZed5xIm7xUdNZW+92fV20/7sLTkooIwnciG3Hh886HcpTG8YhQrS34/0ydXHQOVwQcdAT4a16YifLnvWY/4I3Dhwr6CWBPpYpAqoFy6tlh6DlJZU8SEboEeQ9V5l8YyNwmwqfOt5ituLQjG1Vj4fn7OnT98yO1SfKlaMR+pl2TenZUmqF2V3lKgBSjSwsxSlqqHSh613FBpyIKPIURg+isTuNs+862ZGRRtFHMmMrNqSu+UQR8eMTob1pmFmZMjhALFsmw8HKo9bqCIjIeu3qsooJ5SSqmLLUZBvFaaFLbMl9alTZXZTlxfPkF34Xzg/4A3Z0ZyGaI6uDd/Ic8z28xx1Evp0RXiTR5rSNvNTO5U5T1p3bU4eQ/ZMd1TmZFibdvIYRXndKJgS1gCQ92UF03BhaXEIIyNwxY1/x0Sqa0NFJuLURkRkn9/D/tLmjoZcnIbktw/op5sT7WvJQMULjNbzkORR8zwEe7iLQJ2TnBTsJ1NCTlQaWZufxVQlEHfk5ERYVX4WFax1kZNkeZMqAjWOuvhV3NOLAlEllXL1uXIA8pEAF7Emi8/QRdY5/20oOA6DZLSdrHwUcARViVhxlTtAXgIY5pOsdhUeAV0RwHWgsMoY+0scxI/mLxW1fD40YtI5fo7RbwXdUum3btXFt0zZk3if7jzTka2Lk61+nmylC+gO6kj7V9qZqozUbpA9ZISK9kE8kjvVNsiq/Dp8SAngmtsiVZhhCYOCohC8wgeCf6jjE3DDm3ivvBupqilJ4tU7pCSOU5OW2LoE1q+8B6op3GYHs4mtgvHmMtnEpuzd+4RCisWlO3ZxYXZh5iJK1b9k0P5tjUw5iTTaiJEAH3ar1o4eT26OJh1KPajNYaNKLu245Imw2hW7d11c0pLdsSKnJXCxMh1XKeYkUoxTHUk8Av2K0aNkt4fzwy97YQWM9yTKgOw4x+U5h2O/kXO0P1s3ceup1i/dse6v2idRbMOcDy5No2W0XeLLW/ZCO893l+y6qIqlyvbsqMqJsKqC2BelKs181GWD6Pe937E7wxZHO2cGP8gsl/6+fxCfpVvmAS9IMwute5JnpPjbTU05RqxGvg5T9sVOXXPeI5pJqvvhL84UZDZdCM9ho3jqDE4ni6c2OOSjfC+985gNSi/07SatUTS16+FwkOud9rvc1MKuiGbRxPFCn4OSafZd7uMn3cQwObjYxNDOfVVXHepWOqPbn1foqDd8E+sbEu2V9Lab6pXPCWJLcLpXuq4op9NtpVYDrE3vLusUfmV3gddJX77ia7RlVzTrdfBAlamHQ3jreUm1sPNZV9eNqutfnJIhp0A52CHBmO4PDAQsF/wTKbzGPw==</diagram></mxfile>
|
32
README.md
|
@ -12,16 +12,14 @@ The next version is mostly focused on adding missing essential features and port
|
||||||
|
|
||||||
Suggestions / Planned features:
|
Suggestions / Planned features:
|
||||||
|
|
||||||
|
- Documentation
|
||||||
|
|
||||||
|
- Possibility to hide and resize menus (layers, palette)
|
||||||
- Line tool
|
- Line tool
|
||||||
- Tiled mode
|
- Tiled mode
|
||||||
- Load palette from LPE file
|
- Load palette from LPE file
|
||||||
- Move colours in (advanced) palette editor
|
|
||||||
- Symmetry options
|
- Symmetry options
|
||||||
|
|
||||||
- Custom color picker
|
|
||||||
- custom code without dependencies
|
|
||||||
- more features such as sliders / color modes
|
|
||||||
|
|
||||||
- Mobile
|
- Mobile
|
||||||
- Touch equivalent for mouse clicks
|
- Touch equivalent for mouse clicks
|
||||||
- Hide or scale ui
|
- Hide or scale ui
|
||||||
|
@ -32,25 +30,29 @@ Suggestions / Planned features:
|
||||||
- Possibly add collaborate function
|
- Possibly add collaborate function
|
||||||
|
|
||||||
- Polish:
|
- Polish:
|
||||||
- ctrl + a to select everything / selection -> all, same for deselection
|
- ctrl a to select everything / selection -> all, same for deselection
|
||||||
- Show colors which would need to be added to palette
|
|
||||||
- Warning windows for wrong inputs
|
- Warning windows for wrong inputs
|
||||||
- Palette option remove unused colors
|
- Palette option remove unused colors
|
||||||
- Move selection with arrows
|
- Move selection with arrows
|
||||||
- Update pivot buttons when resizing canvas
|
|
||||||
- Update borders by dragging the canvas' edges with the mouse when resizing canvas
|
- Update borders by dragging the canvas' edges with the mouse when resizing canvas
|
||||||
- Move the canvases so they're centered after resizing the canvas (maybe a .center() method in layer class)
|
- Move the canvases so they're centered after resizing the canvas (maybe a .center() method in layer class)
|
||||||
- Trim canvas
|
- Scale selection
|
||||||
|
|
||||||
## How to Contribute
|
## How to Contribute
|
||||||
|
|
||||||
Requirements: you must have node.js and git installed.
|
### Requirements
|
||||||
|
|
||||||
|
You must 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
|
||||||
|
|
||||||
1. Click **Fork** above. It will automatically create a copy of this repository and add it to your account.
|
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.
|
2. Clone the repository to your computer.
|
||||||
3. Open the folder in command prompt and run **npm install**
|
3. Open the folder in command prompt and run **`npm install`**
|
||||||
4. Make any changes you would like to suggest.
|
4. Make any changes you would like to suggest.
|
||||||
5. In command prompt run **node build.js** which will compile it to the */build* folder, where you can make sure it works
|
5. In command prompt run **`npm run hot`** which will compile app to the `/build` folder, serve under [http://localhost:3000](http://localhost:3000), then open in your browser. Moreover, it restarts server every time you save your changes in a codebase. You can go even further by running `npm run hot:reload`, which will also trigger webpage reloads.
|
||||||
6. Add, Commit and Push your changes to your fork.
|
6. Add, Commit and Push your changes to your fork.
|
||||||
7. On the github page for your fork, click **New Pull Request** above the file list.
|
7. On the github page for your fork, click **New Pull Request** above the file list.
|
||||||
8. Change the **head repository** dropdown to your fork.
|
8. Change the **head repository** dropdown to your fork.
|
||||||
|
@ -59,6 +61,12 @@ Requirements: you must have node.js and git installed.
|
||||||
|
|
||||||
If you have any trouble, see this page: https://help.github.com/en/articles/creating-a-pull-request-from-a-fork
|
If you have any trouble, see this page: https://help.github.com/en/articles/creating-a-pull-request-from-a-fork
|
||||||
|
|
||||||
|
### Feature Toggles
|
||||||
|
|
||||||
|
Some feature might be hidden by default. Functions to enable/disable them are available inside global `featureToggles` and operate on a `window.localStorage`.
|
||||||
|
|
||||||
|
For example use `featureToggles.enableEllipseTool()` to make ellipse tool button visible. Then `featureToggles.disableEllipseTool()` to hide it.
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
This software may not be resold, redistributed, rehosted or otherwise conveyed to a third party.
|
This software may not be resold, redistributed, rehosted or otherwise conveyed to a third party.
|
||||||
|
|
|
@ -7,4 +7,4 @@ function getText(elementId) {
|
||||||
function setText(elementId, text) {
|
function setText(elementId, text) {
|
||||||
var element = (typeof elementId == 'string' ? document.getElementById(elementId) : elementId);
|
var element = (typeof elementId == 'string' ? document.getElementById(elementId) : elementId);
|
||||||
element.textContent = text;
|
element.textContent = text;
|
||||||
}
|
}
|
|
@ -0,0 +1,6 @@
|
||||||
|
<svg width="512" height="512" viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<g>
|
||||||
|
<ellipse stroke="#000" stroke-width="32" fill="none" cx="255.50001" cy="255.5" id="svg_20" rx="239" ry="187.5"/>
|
||||||
|
</g>
|
||||||
|
|
||||||
|
</svg>
|
After Width: | Height: | Size: 221 B |
|
@ -0,0 +1,22 @@
|
||||||
|
<svg width="512" height="512" viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<g>
|
||||||
|
<title>Layer 1</title>
|
||||||
|
<g id="svg_4"/>
|
||||||
|
<g id="svg_5"/>
|
||||||
|
<g id="svg_6"/>
|
||||||
|
<g id="svg_7"/>
|
||||||
|
<g id="svg_8"/>
|
||||||
|
<g id="svg_9"/>
|
||||||
|
<g id="svg_10"/>
|
||||||
|
<g id="svg_11"/>
|
||||||
|
<g id="svg_12"/>
|
||||||
|
<g id="svg_13"/>
|
||||||
|
<g id="svg_14"/>
|
||||||
|
<g id="svg_15"/>
|
||||||
|
<g id="svg_16"/>
|
||||||
|
<g id="svg_17"/>
|
||||||
|
<g id="svg_18"/>
|
||||||
|
<ellipse stroke="#000" stroke-width="32" fill="#000000" cx="255.50001" cy="255.5" id="svg_20" rx="239" ry="187.5"/>
|
||||||
|
</g>
|
||||||
|
|
||||||
|
</svg>
|
After Width: | Height: | Size: 528 B |
|
@ -0,0 +1,41 @@
|
||||||
|
<?xml version="1.0" encoding="iso-8859-1"?>
|
||||||
|
<!-- Generator: Adobe Illustrator 19.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||||
|
<svg version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||||
|
viewBox="0 0 512.001 512.001" style="enable-background:new 0 0 512.001 512.001;" xml:space="preserve">
|
||||||
|
<g>
|
||||||
|
<g>
|
||||||
|
<path d="M506.143,5.859c-7.811-7.811-20.475-7.811-28.285,0l-472,472c-7.811,7.811-7.811,20.474,0,28.284
|
||||||
|
c3.905,3.906,9.024,5.858,14.142,5.858s10.237-1.953,14.143-5.858l472-472C513.954,26.333,513.954,13.67,506.143,5.859z"/>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 799 B |
|
@ -0,0 +1 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="332" height="80"><path fill="#eaeaea" d="M7 16H6V0H0v20h13v-7H7zm7 4h13V0H14v20zm6-16h1v12h-1V4zm15 4V4h1v3h5V0H28v12h7v4h-1v-3h-6v7h13V8zm7 12h6v-5h7V0H42v20zm6-16h1v7h-1V4zm8 16h13v-7h-6v3h-1v-4h7V8h-7V4h1v3h6V0H56zM83 8V0H70v20h13v-7h-6v3h-1V4h1v4z" class="a"/></svg>
|
After Width: | Height: | Size: 317 B |
|
@ -0,0 +1,17 @@
|
||||||
|
|
||||||
|
<!-- Generator: Adobe Illustrator 19.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||||
|
<svg version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||||
|
viewBox="0 0 512 512" style="enable-background:new 0 0 512 512;" xml:space="preserve">
|
||||||
|
<g>
|
||||||
|
<g>
|
||||||
|
<path d="M456.606,154.394l-150-150C303.793,1.58,299.978,0,296,0H66c-8.284,0-15,6.716-15,15v482c0,8.284,6.716,15,15,15h380
|
||||||
|
c8.284,0,15-6.716,15-15V165C461,161.021,459.419,157.206,456.606,154.394z M431,482H81V30h208.787L431,171.213V482z"/>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
<g>
|
||||||
|
<path d="M306,241h-35v-35c0-8.284-6.716-15-15-15s-15,6.716-15,15v35h-35c-8.284,0-15,6.716-15,15s6.716,15,15,15h35v35
|
||||||
|
c0,8.284,6.716,15,15,15s15-6.716,15-15v-35h35c8.284,0,15-6.716,15-15S314.284,241,306,241z"/>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 815 B |
|
@ -0,0 +1,16 @@
|
||||||
|
|
||||||
|
<!-- Generator: Adobe Illustrator 17.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||||
|
<svg version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||||
|
viewBox="0 0 276.157 276.157" style="enable-background:new 0 0 276.157 276.157;" xml:space="preserve">
|
||||||
|
<path style="fill:#000002;" d="M273.081,101.378c-3.3-4.651-8.86-7.319-15.255-7.319h-24.34v-26.47c0-10.201-8.299-18.5-18.5-18.5
|
||||||
|
h-85.322c-3.63,0-9.295-2.876-11.436-5.806l-6.386-8.735c-4.982-6.814-15.104-11.954-23.546-11.954H58.731
|
||||||
|
c-9.293,0-18.639,6.608-21.738,15.372l-2.033,5.752c-0.958,2.71-4.721,5.371-7.596,5.371H18.5c-10.201,0-18.5,8.299-18.5,18.5
|
||||||
|
v167.07c0,0.885,0.161,1.73,0.443,2.519c0.152,3.306,1.18,6.424,3.053,9.064c3.3,4.652,8.86,7.319,15.255,7.319h188.486
|
||||||
|
c11.395,0,23.27-8.424,27.035-19.179l40.677-116.188C277.061,112.159,276.381,106.03,273.081,101.378z M18.5,64.089h8.864
|
||||||
|
c9.295,0,18.64-6.608,21.738-15.372l2.032-5.75c0.959-2.711,4.722-5.372,7.597-5.372h29.564c3.63,0,9.295,2.876,11.437,5.806
|
||||||
|
l6.386,8.734c4.982,6.815,15.104,11.954,23.546,11.954h85.322c1.898,0,3.5,1.603,3.5,3.5v26.47H69.34
|
||||||
|
c-11.395,0-23.27,8.424-27.035,19.179L15,191.231V67.589C15,65.692,16.603,64.089,18.5,64.089z M260.791,113.238l-40.677,116.188
|
||||||
|
c-1.674,4.781-7.812,9.135-12.877,9.135H18.751c-1.448,0-2.577-0.373-3.02-0.998c-0.443-0.625-0.423-1.814,0.056-3.181
|
||||||
|
l40.677-116.188c1.674-4.781,7.812-9.135,12.877-9.135h188.486c1.448,0,2.577,0.373,3.021,0.998
|
||||||
|
C261.29,110.682,261.27,111.871,260.791,113.238z"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 1.5 KiB |
14
build.js
|
@ -15,8 +15,16 @@ console.log('Building Pixel Editor');
|
||||||
|
|
||||||
|
|
||||||
function copy_images(){
|
function copy_images(){
|
||||||
gulp.src('./images/')
|
// Icons
|
||||||
.pipe(gulp.dest(path.join(BUILDDIR, SLUG)));
|
gulp.src('./images/*.png').pipe(gulp.dest(path.join(BUILDDIR, SLUG)));
|
||||||
|
// Splash images
|
||||||
|
gulp.src('./images/Splash images/*.png').pipe(gulp.dest(path.join(BUILDDIR, SLUG)));
|
||||||
|
// Logs images
|
||||||
|
gulp.src('./images/Logs/*.gif').pipe(gulp.dest(path.join(BUILDDIR, SLUG)));
|
||||||
|
}
|
||||||
|
|
||||||
|
function copy_logs() {
|
||||||
|
gulp.src('logs/*.html').pipe(gulp.dest(path.join(BUILDDIR, SLUG)));
|
||||||
}
|
}
|
||||||
|
|
||||||
function render_js(){
|
function render_js(){
|
||||||
|
@ -56,4 +64,4 @@ function compile_page(){
|
||||||
// empty the build folder, or create it
|
// empty the build folder, or create it
|
||||||
fs.rmdirSync(BUILDDIR, { recursive: true });
|
fs.rmdirSync(BUILDDIR, { recursive: true });
|
||||||
fs.mkdirSync(BUILDDIR);
|
fs.mkdirSync(BUILDDIR);
|
||||||
gulp.parallel(copy_images, render_js, render_css, compile_page)();
|
gulp.parallel(copy_images, copy_logs, render_js, render_css, compile_page)();
|
||||||
|
|
|
@ -40,6 +40,33 @@ body {
|
||||||
/* Disable Android and iOS callouts*/
|
/* Disable Android and iOS callouts*/
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#help {
|
||||||
|
max-height: 500px;
|
||||||
|
overflow-y:scroll;
|
||||||
|
|
||||||
|
li {
|
||||||
|
margin-top:5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fancy scrollbar
|
||||||
|
&::-webkit-scrollbar {
|
||||||
|
background: #232125;
|
||||||
|
width: 0.5em;
|
||||||
|
}
|
||||||
|
&::-webkit-scrollbar-track {
|
||||||
|
margin-top: -0.125em;
|
||||||
|
width: 0.5em;
|
||||||
|
}
|
||||||
|
&::-webkit-scrollbar-thumb {
|
||||||
|
background: #332f35;
|
||||||
|
border-radius: 0.25em;
|
||||||
|
border: solid 0.125em #232125; //same color as scrollbar back to fake padding
|
||||||
|
}
|
||||||
|
&::-webkit-scrollbar-corner {
|
||||||
|
background: #232125;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#layer-properties-menu {
|
#layer-properties-menu {
|
||||||
visibility: hidden;
|
visibility: hidden;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
|
@ -125,7 +152,7 @@ body {
|
||||||
z-index: 1120;
|
z-index: 1120;
|
||||||
list-style-type: none;
|
list-style-type: none;
|
||||||
overflow-y:scroll;
|
overflow-y:scroll;
|
||||||
overflow-x:hidden; // TODO: make the scroll bar a bit fancier
|
overflow-x:hidden;
|
||||||
#add-layer-button {
|
#add-layer-button {
|
||||||
path {
|
path {
|
||||||
fill: $baseicon;
|
fill: $baseicon;
|
||||||
|
@ -178,7 +205,7 @@ body {
|
||||||
|
|
||||||
.layers-menu-entry {
|
.layers-menu-entry {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
margin-top: 2px;
|
margin-bottom: 2px;
|
||||||
font-size: 1em;
|
font-size: 1em;
|
||||||
color: $basetext;
|
color: $basetext;
|
||||||
background-color: lighten($basecolor, 4%);
|
background-color: lighten($basecolor, 4%);
|
||||||
|
@ -301,7 +328,7 @@ svg {
|
||||||
}
|
}
|
||||||
|
|
||||||
#pixel-canvas {
|
#pixel-canvas {
|
||||||
z-index: 2;
|
z-index: 3;
|
||||||
background: transparent;
|
background: transparent;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -311,7 +338,7 @@ svg {
|
||||||
}
|
}
|
||||||
|
|
||||||
#tmp-canvas {
|
#tmp-canvas {
|
||||||
z-index: 4;
|
z-index: 5;
|
||||||
background: transparent;
|
background: transparent;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -354,7 +381,7 @@ svg {
|
||||||
left: 64px;
|
left: 64px;
|
||||||
right: 48px;
|
right: 48px;
|
||||||
top: 48px;
|
top: 48px;
|
||||||
cursor: none;
|
cursor: default;
|
||||||
position: fixed;
|
position: fixed;
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
|
@ -687,14 +714,18 @@ svg {
|
||||||
#tools-menu li button#pencil-bigger-button,
|
#tools-menu li button#pencil-bigger-button,
|
||||||
#tools-menu li button#zoom-in-button,
|
#tools-menu li button#zoom-in-button,
|
||||||
#tools-menu li button#eraser-bigger-button,
|
#tools-menu li button#eraser-bigger-button,
|
||||||
#tools-menu li button#rectangle-bigger-button {
|
#tools-menu li button#rectangle-bigger-button,
|
||||||
|
#tools-menu li button#ellipse-bigger-button,
|
||||||
|
#tools-menu li button#line-bigger-button {
|
||||||
left: 0;
|
left: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
#tools-menu li button#pencil-smaller-button,
|
#tools-menu li button#pencil-smaller-button,
|
||||||
#tools-menu li button#zoom-out-button,
|
#tools-menu li button#zoom-out-button,
|
||||||
#tools-menu li button#eraser-smaller-button,
|
#tools-menu li button#eraser-smaller-button,
|
||||||
#tools-menu li button#rectangle-smaller-button {
|
#tools-menu li button#rectangle-smaller-button,
|
||||||
|
#tools-menu li button#ellipse-smaller-button,
|
||||||
|
#tools-menu li button#line-smaller-button {
|
||||||
right: 0;
|
right: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -705,7 +736,11 @@ svg {
|
||||||
#tools-menu li.selected button#eraser-bigger-button,
|
#tools-menu li.selected button#eraser-bigger-button,
|
||||||
#tools-menu li.selected button#eraser-smaller-button,
|
#tools-menu li.selected button#eraser-smaller-button,
|
||||||
#tools-menu li.selected button#rectangle-bigger-button,
|
#tools-menu li.selected button#rectangle-bigger-button,
|
||||||
#tools-menu li.selected button#rectangle-smaller-button {
|
#tools-menu li.selected button#rectangle-smaller-button,
|
||||||
|
#tools-menu li.selected button#ellipse-bigger-button,
|
||||||
|
#tools-menu li.selected button#ellipse-smaller-button,
|
||||||
|
#tools-menu li.selected button#line-bigger-button,
|
||||||
|
#tools-menu li.selected button#line-smaller-button {
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -775,8 +810,32 @@ svg {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
div.update {
|
||||||
|
input {
|
||||||
|
background: $indent;
|
||||||
|
border: none;
|
||||||
|
border-radius: 4px;
|
||||||
|
color: $indenttext;
|
||||||
|
padding: 10px 20px;
|
||||||
|
margin: 0;
|
||||||
|
width: 60px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/*
|
||||||
|
input {
|
||||||
|
background: $indent;
|
||||||
|
border: none;
|
||||||
|
border-radius: 4px;
|
||||||
|
color: $indenttext;
|
||||||
|
padding: 10px 20px;
|
||||||
|
margin: 0;
|
||||||
|
width: 60px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
button.default {
|
button.default {
|
||||||
float: right;
|
|
||||||
background: $basehover;
|
background: $basehover;
|
||||||
border: none;
|
border: none;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
|
@ -789,17 +848,6 @@ svg {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
input {
|
|
||||||
background: $indent;
|
|
||||||
border: none;
|
|
||||||
border-radius: 4px;
|
|
||||||
color: $indenttext;
|
|
||||||
padding: 10px 20px;
|
|
||||||
margin: 0;
|
|
||||||
width: 60px;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dropdown-button {
|
.dropdown-button {
|
||||||
background: $basehover url('/pixel-editor/dropdown-arrow.png') right center no-repeat;
|
background: $basehover url('/pixel-editor/dropdown-arrow.png') right center no-repeat;
|
||||||
border: none;
|
border: none;
|
||||||
|
@ -1038,9 +1086,9 @@ svg {
|
||||||
input[type=number] {
|
input[type=number] {
|
||||||
position:relative;
|
position:relative;
|
||||||
margin-left:10px;
|
margin-left:10px;
|
||||||
height:15px;
|
height:15px !important;
|
||||||
width:40px;
|
width:40px !important;
|
||||||
padding:8px;
|
padding:8px !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
input[type=number]::-webkit-outer-spin-button,
|
input[type=number]::-webkit-outer-spin-button,
|
||||||
|
@ -1212,4 +1260,602 @@ svg {
|
||||||
background: $baseselected;
|
background: $baseselected;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
/***********************COLOUR PICKER*****************************/
|
||||||
|
#colour-picker {
|
||||||
|
background-color:$basecolor;
|
||||||
|
width:250px;
|
||||||
|
height:350px;
|
||||||
|
position:absolute;
|
||||||
|
display:inline-block;
|
||||||
|
|
||||||
|
input[type=text] {
|
||||||
|
background-color:$basetext;
|
||||||
|
color:$basecolor;
|
||||||
|
box-shadow:none;
|
||||||
|
border:none;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type=range] {
|
||||||
|
width: 100%;
|
||||||
|
margin: 2.2px 0;
|
||||||
|
background-color: transparent;
|
||||||
|
-webkit-appearance: none;
|
||||||
|
}
|
||||||
|
input[type=range]::-webkit-slider-runnable-track {
|
||||||
|
background: #484d4d;
|
||||||
|
border: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 25.6px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
input[type=range]::-webkit-slider-thumb {
|
||||||
|
margin-top: -2.2px;
|
||||||
|
width: 18px;
|
||||||
|
height: 30px;
|
||||||
|
background: $basetext;
|
||||||
|
border: 0;
|
||||||
|
cursor: pointer;
|
||||||
|
-webkit-appearance: none;
|
||||||
|
}
|
||||||
|
input[type=range]::-moz-range-track {
|
||||||
|
background: #484d4d;
|
||||||
|
border: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 25.6px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
input[type=range]::-moz-range-thumb {
|
||||||
|
width: 18px;
|
||||||
|
height: 30px;
|
||||||
|
background: $basetextweak;
|
||||||
|
border: 0;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*TODO: Use one of the selectors from https://stackoverflow.com/a/20541859/7077589 and figure out
|
||||||
|
how to remove the vertical space around the range input in IE*/
|
||||||
|
@supports (-ms-ime-align:auto) {
|
||||||
|
/* Pre-Chromium Edge only styles, selector taken from hhttps://stackoverflow.com/a/32202953/7077589 */
|
||||||
|
input[type=range].slider {
|
||||||
|
margin: 0;
|
||||||
|
/*Edge starts the margin from the thumb, not the track as other browsers do*/
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#cp-modes {
|
||||||
|
margin: 0 0 0 0;
|
||||||
|
font-size:0;
|
||||||
|
height:40px;
|
||||||
|
float:left;
|
||||||
|
display:flex;
|
||||||
|
font-family: 'Roboto', sans-serif;
|
||||||
|
background-color:$basetextweak;
|
||||||
|
width:100%;
|
||||||
|
|
||||||
|
button {
|
||||||
|
font-size:14px;
|
||||||
|
left:0;
|
||||||
|
right:0;
|
||||||
|
margin:0 0 0 0;
|
||||||
|
border: none;
|
||||||
|
border-radius: 0;
|
||||||
|
height:100%;
|
||||||
|
width:37x;
|
||||||
|
background-color:$basehover;
|
||||||
|
color:$basetext;
|
||||||
|
cursor:pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
button:hover {
|
||||||
|
background-color:$baseicon;
|
||||||
|
color:$basetext;
|
||||||
|
}
|
||||||
|
|
||||||
|
button.cp-selected-mode {
|
||||||
|
background-color:$baseicon;
|
||||||
|
color:$basetext;
|
||||||
|
}
|
||||||
|
|
||||||
|
input {
|
||||||
|
width:60px;
|
||||||
|
}
|
||||||
|
|
||||||
|
div {
|
||||||
|
background-color:yellow;
|
||||||
|
width:100%;
|
||||||
|
height:100%;
|
||||||
|
z-index:2;
|
||||||
|
position:relative;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#sliders-container {
|
||||||
|
padding:10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cp-slider-entry {
|
||||||
|
width:100%;
|
||||||
|
height:30px;
|
||||||
|
display:flex;
|
||||||
|
align-items:center;
|
||||||
|
margin-top:2px;
|
||||||
|
position:relative;
|
||||||
|
|
||||||
|
label {
|
||||||
|
width: 20px;
|
||||||
|
font-size:15px;
|
||||||
|
font-style: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type=text] {
|
||||||
|
text-align:center;
|
||||||
|
width: 30px;
|
||||||
|
overflow:visible;
|
||||||
|
margin-left:4px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.colour-picker-slider {
|
||||||
|
width:90%;
|
||||||
|
}
|
||||||
|
|
||||||
|
#cp-minipicker {
|
||||||
|
width:100%;
|
||||||
|
height:100px;
|
||||||
|
position:relative;
|
||||||
|
margin: 0 0 0 0;
|
||||||
|
z-index: inherit 2000;
|
||||||
|
|
||||||
|
input {
|
||||||
|
width:100%;
|
||||||
|
margin: none;
|
||||||
|
padding: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cp-colours-previews {
|
||||||
|
width:100%;
|
||||||
|
position:relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cp-colour-preview {
|
||||||
|
width:100%;
|
||||||
|
position:relative;
|
||||||
|
background-color:blue;
|
||||||
|
color:$basecolor;
|
||||||
|
float:left;
|
||||||
|
height:30px;
|
||||||
|
justify-content: center;
|
||||||
|
display:flex;
|
||||||
|
align-items: center;
|
||||||
|
font-size:12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#cp-colour-picking-modes {
|
||||||
|
width:100%;
|
||||||
|
position:relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
font-size:14px;
|
||||||
|
left:0;
|
||||||
|
right:0;
|
||||||
|
margin:0 0 0 0;
|
||||||
|
border: none;
|
||||||
|
border-radius: 0;
|
||||||
|
height:30px;
|
||||||
|
width:16.66666%;
|
||||||
|
float:left;
|
||||||
|
overflow:hidden;
|
||||||
|
background-color:$basehover;
|
||||||
|
color:$basetext;
|
||||||
|
cursor:pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
button:hover {
|
||||||
|
background-color:$baseicon;
|
||||||
|
color:$basetext;
|
||||||
|
}
|
||||||
|
|
||||||
|
button.cp-selected-mode {
|
||||||
|
background-color:$baseicon;
|
||||||
|
color:$basetext;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#cp-canvas-container {
|
||||||
|
width:100%;
|
||||||
|
height:100%;
|
||||||
|
position:relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
#cp-spectrum {
|
||||||
|
width:100%;
|
||||||
|
height:100px;
|
||||||
|
position:absolute;
|
||||||
|
background-color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cp-picker-icon{
|
||||||
|
width:16px;
|
||||||
|
height:16px;
|
||||||
|
border-radius:100%;
|
||||||
|
position:absolute;
|
||||||
|
background-color:white;
|
||||||
|
z-index:2;
|
||||||
|
border:2px solid black;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/***************PALETTE BLOCK****************/
|
||||||
|
div#palette-block {
|
||||||
|
z-index:1000;
|
||||||
|
position:relative;
|
||||||
|
resize: horizontal;
|
||||||
|
margin: 0 0 0 0;
|
||||||
|
width:600px;
|
||||||
|
height:400px;
|
||||||
|
}
|
||||||
|
|
||||||
|
div#palette-container {
|
||||||
|
display:inline-block;
|
||||||
|
background-color: #232125;
|
||||||
|
position:absolute;
|
||||||
|
scrollbar-color: #332f35 #232125;
|
||||||
|
scroll-behavior: smooth;
|
||||||
|
left:300px;
|
||||||
|
width:300px;
|
||||||
|
height:320px;
|
||||||
|
overflow-y: scroll;
|
||||||
|
|
||||||
|
&::-webkit-scrollbar {
|
||||||
|
background: #232125;
|
||||||
|
width: 0.5em;
|
||||||
|
}
|
||||||
|
&::-webkit-scrollbar-track {
|
||||||
|
margin-top: -0.125em;
|
||||||
|
width: 0.5em;
|
||||||
|
}
|
||||||
|
&::-webkit-scrollbar-thumb {
|
||||||
|
background: #332f35;
|
||||||
|
border-radius: 0.25em;
|
||||||
|
border: solid 0.125em #232125; //same color as scrollbar back to fake padding
|
||||||
|
}
|
||||||
|
&::-webkit-scrollbar-corner {
|
||||||
|
background: #232125;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ul#palette-list {
|
||||||
|
list-style:none;
|
||||||
|
margin: 0 0 0 0;
|
||||||
|
padding: 0 0 0 0;
|
||||||
|
position:relative;
|
||||||
|
display:inline-block;
|
||||||
|
|
||||||
|
li {
|
||||||
|
float:left;
|
||||||
|
width:50px;
|
||||||
|
height:50px;
|
||||||
|
border:none;
|
||||||
|
|
||||||
|
min-width:20px;
|
||||||
|
min-height:20px;
|
||||||
|
max-width:75px;
|
||||||
|
max-height:75px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
div#pb-options {
|
||||||
|
position:relative;
|
||||||
|
left:280px;
|
||||||
|
height:30px;
|
||||||
|
width:300px;
|
||||||
|
top:300px;
|
||||||
|
|
||||||
|
button {
|
||||||
|
border-radius:none;
|
||||||
|
position:relative;
|
||||||
|
float:left;
|
||||||
|
width:50%;
|
||||||
|
height:100%;
|
||||||
|
text-align:center;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size:16px;
|
||||||
|
background-color:$baseicon;
|
||||||
|
border:none;
|
||||||
|
}
|
||||||
|
|
||||||
|
button:hover {
|
||||||
|
color: $basehovertext;
|
||||||
|
background-color: $basehover;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/********SPLASH PAGE*************/
|
||||||
|
#splash {
|
||||||
|
width:100% !important;
|
||||||
|
height:100%!important;
|
||||||
|
|
||||||
|
background-color: #232125 !important;
|
||||||
|
opacity: 1 !important;
|
||||||
|
|
||||||
|
#splash-input {
|
||||||
|
width:74%;
|
||||||
|
height:100% !important;
|
||||||
|
color:$baselink;
|
||||||
|
|
||||||
|
#splash-menu {
|
||||||
|
position:relative;
|
||||||
|
height:100%;
|
||||||
|
left:0;
|
||||||
|
top:0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#editor-logo {
|
||||||
|
font-weight:bold;
|
||||||
|
text-transform:uppercase;
|
||||||
|
font-size:20px;
|
||||||
|
height:35vh;
|
||||||
|
width:100%;
|
||||||
|
position:relative;
|
||||||
|
|
||||||
|
background-image:url('https://cdn.discordapp.com/attachments/506277390050131978/795660870221955082/final.png');
|
||||||
|
background-size:cover;
|
||||||
|
background-position:center;
|
||||||
|
background-repeat:no-repeat;
|
||||||
|
}
|
||||||
|
|
||||||
|
#black {
|
||||||
|
width:100%;
|
||||||
|
height:100%;
|
||||||
|
position:relative;
|
||||||
|
background-color:rgba(0,0,0,0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
#sp-coverdata {
|
||||||
|
padding:20px;
|
||||||
|
|
||||||
|
p, a {
|
||||||
|
font-size:15px;
|
||||||
|
position:absolute;
|
||||||
|
text-transform:none;
|
||||||
|
right:20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
top:0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
font-size:17px;
|
||||||
|
bottom:20px;
|
||||||
|
text-decoration:underline;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#sp-quickstart-container {
|
||||||
|
height:100%;
|
||||||
|
max-height: 500px;
|
||||||
|
width:70%;
|
||||||
|
float:right;
|
||||||
|
padding:40px;
|
||||||
|
|
||||||
|
-webkit-box-sizing: border-box; /* Safari/Chrome, other WebKit */
|
||||||
|
-moz-box-sizing: border-box; /* Firefox, other Gecko */
|
||||||
|
box-sizing: border-box; /* Opera/IE 8+ */
|
||||||
|
|
||||||
|
overflow-y: scroll;
|
||||||
|
&::-webkit-scrollbar {
|
||||||
|
background: #232125;
|
||||||
|
width: 0.5em;
|
||||||
|
}
|
||||||
|
&::-webkit-scrollbar-track {
|
||||||
|
margin-top: -0.125em;
|
||||||
|
width: 0.5em;
|
||||||
|
}
|
||||||
|
&::-webkit-scrollbar-thumb {
|
||||||
|
background: #332f35;
|
||||||
|
border-radius: 0.25em;
|
||||||
|
border: solid 0.125em #232125; //same color as scrollbar back to fake padding
|
||||||
|
}
|
||||||
|
&::-webkit-scrollbar-corner {
|
||||||
|
background: #232125;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#sp-quickstart {
|
||||||
|
display:flex;
|
||||||
|
flex-direction: row;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
height:100%;
|
||||||
|
|
||||||
|
// Fancy scrollbar
|
||||||
|
&::-webkit-scrollbar {
|
||||||
|
background: #232125;
|
||||||
|
width: 0.5em;
|
||||||
|
}
|
||||||
|
&::-webkit-scrollbar-track {
|
||||||
|
margin-top: -0.125em;
|
||||||
|
width: 0.5em;
|
||||||
|
}
|
||||||
|
&::-webkit-scrollbar-thumb {
|
||||||
|
background: #332f35;
|
||||||
|
border-radius: 0.25em;
|
||||||
|
border: solid 0.125em #232125; //same color as scrollbar back to fake padding
|
||||||
|
}
|
||||||
|
&::-webkit-scrollbar-corner {
|
||||||
|
background: #232125;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#sp-quickstart-title {
|
||||||
|
font-size:27px;
|
||||||
|
text-transform: uppercase;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sp-template {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
text-transform: uppercase;
|
||||||
|
width:16%;
|
||||||
|
border-radius:5%;
|
||||||
|
margin-right:4%;
|
||||||
|
margin-top:4%;
|
||||||
|
background-color:$basecolor;
|
||||||
|
|
||||||
|
font-size: 18px;
|
||||||
|
text-align:center;
|
||||||
|
font-weight: bold;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
cursor:pointer;
|
||||||
|
background-color:$baseselected;
|
||||||
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
span {
|
||||||
|
display:block;
|
||||||
|
font-size:14px !important;
|
||||||
|
margin: 0 0 0 0;
|
||||||
|
padding: 0 0 0 0;
|
||||||
|
}
|
||||||
|
width:100%;
|
||||||
|
|
||||||
|
float:left;
|
||||||
|
position:relative;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.sp-template:before {
|
||||||
|
content:'';
|
||||||
|
float:left;
|
||||||
|
padding-top:100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
#sp-newpixel {
|
||||||
|
-webkit-box-sizing: border-box; /* Safari/Chrome, other WebKit */
|
||||||
|
-moz-box-sizing: border-box; /* Firefox, other Gecko */
|
||||||
|
box-sizing: border-box; /* Opera/IE 8+ */
|
||||||
|
display: inline-block;
|
||||||
|
width: 30% !important;
|
||||||
|
height:65vh;
|
||||||
|
padding:20px;
|
||||||
|
position:relative;
|
||||||
|
background-color:$basecolor;
|
||||||
|
|
||||||
|
#palette-button-splash {
|
||||||
|
left:5%;
|
||||||
|
width:90%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sp-np-entry {
|
||||||
|
width:100%;
|
||||||
|
text-align:center;
|
||||||
|
}
|
||||||
|
|
||||||
|
input {
|
||||||
|
border:none;
|
||||||
|
background-color: #232125;
|
||||||
|
color:$basetext;
|
||||||
|
font-size:14px;
|
||||||
|
width:40px;
|
||||||
|
padding:7px;
|
||||||
|
text-align:center;
|
||||||
|
}
|
||||||
|
|
||||||
|
#create-button {
|
||||||
|
font-size:18px;
|
||||||
|
width:150px;
|
||||||
|
margin-top:40px;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
#sp-mode-palette {
|
||||||
|
text-align: center;
|
||||||
|
position: relative;
|
||||||
|
float:bottom;
|
||||||
|
font-size:16px;
|
||||||
|
font-weight: bold;
|
||||||
|
|
||||||
|
div.button-menu {
|
||||||
|
border:2px solid $basetextweak;
|
||||||
|
border-radius:5px;
|
||||||
|
position:relative;
|
||||||
|
display:inline-block;
|
||||||
|
padding: 0 0 0 0;
|
||||||
|
text-align:left;
|
||||||
|
width:90%;
|
||||||
|
|
||||||
|
div {
|
||||||
|
border:none;
|
||||||
|
padding:none;
|
||||||
|
margin:none;
|
||||||
|
background-color:transparent;
|
||||||
|
width:50%;
|
||||||
|
float:left;
|
||||||
|
text-align: center;
|
||||||
|
height:25px;
|
||||||
|
cursor:pointer;
|
||||||
|
z-index:1;
|
||||||
|
|
||||||
|
p {
|
||||||
|
z-index:0;
|
||||||
|
-ms-transform: translateY(-60%);
|
||||||
|
transform: translateY(-60%);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.sp-interface-selected {
|
||||||
|
background-color: $basetextweak;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#splash-news {
|
||||||
|
box-sizing: border-box;
|
||||||
|
padding-left:20px;
|
||||||
|
width:26%;
|
||||||
|
height:100%;
|
||||||
|
background-color:#151516 !important;
|
||||||
|
float: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
#latest-update {
|
||||||
|
width:100%;
|
||||||
|
font-size:15px;
|
||||||
|
height:90%;
|
||||||
|
line-height: 1.5;
|
||||||
|
position:relative;
|
||||||
|
top:20px;
|
||||||
|
|
||||||
|
overflow-y:scroll;
|
||||||
|
box-sizing: border-box;
|
||||||
|
|
||||||
|
img {
|
||||||
|
width:100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
&::-webkit-scrollbar {
|
||||||
|
background: #232125;
|
||||||
|
width: 0.5em;
|
||||||
|
}
|
||||||
|
&::-webkit-scrollbar-track {
|
||||||
|
margin-top: -0.125em;
|
||||||
|
width: 0.5em;
|
||||||
|
}
|
||||||
|
&::-webkit-scrollbar-thumb {
|
||||||
|
background: #332f35;
|
||||||
|
border-radius: 0.25em;
|
||||||
|
border: solid 0.125em #232125; //same color as scrollbar back to fake padding
|
||||||
|
}
|
||||||
|
&::-webkit-scrollbar-corner {
|
||||||
|
background: #232125;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -0,0 +1 @@
|
||||||
|
[0114/110029.536:ERROR:directory_reader_win.cc(43)] FindFirstFile: The system cannot find the path specified. (0x3)
|
After Width: | Height: | Size: 325 KiB |
After Width: | Height: | Size: 1.3 MiB |
After Width: | Height: | Size: 561 KiB |
After Width: | Height: | Size: 1.9 MiB |
After Width: | Height: | Size: 2.9 MiB |
After Width: | Height: | Size: 442 KiB |
After Width: | Height: | Size: 21 KiB |
After Width: | Height: | Size: 14 KiB |
After Width: | Height: | Size: 24 KiB |
After Width: | Height: | Size: 36 KiB |
After Width: | Height: | Size: 39 KiB |
After Width: | Height: | Size: 13 KiB |
After Width: | Height: | Size: 10 KiB |
After Width: | Height: | Size: 59 KiB |
After Width: | Height: | Size: 36 KiB |
After Width: | Height: | Size: 89 KiB |
|
@ -0,0 +1,35 @@
|
||||||
|
// Acts as a public static class
|
||||||
|
class Util {
|
||||||
|
static getElement(elementOrElementId) {
|
||||||
|
return typeof elementOrElementId
|
||||||
|
? document.getElementById(elementOrElementId)
|
||||||
|
: elementOrElementId;
|
||||||
|
}
|
||||||
|
static getText(elementId) {
|
||||||
|
return this.getElement(elementId).textContent;
|
||||||
|
}
|
||||||
|
|
||||||
|
static setText(elementId, text) {
|
||||||
|
this.getElement(elementId).textContent = text;
|
||||||
|
}
|
||||||
|
static getValue(elementId) {
|
||||||
|
return this.getElement(elementId).value;
|
||||||
|
}
|
||||||
|
|
||||||
|
static setValue(elementId, value) {
|
||||||
|
this.getElement(elementId).value = value;
|
||||||
|
}
|
||||||
|
//add class .selected to specified element
|
||||||
|
static select(elementId) {
|
||||||
|
this.getElement(elementId).classList.add('selected');
|
||||||
|
}
|
||||||
|
|
||||||
|
//remove .selected class from specified element
|
||||||
|
static deselect(elementId) {
|
||||||
|
this.getElement(elementId).classList.remove('selected');
|
||||||
|
}
|
||||||
|
//toggle the status of the .selected class on the specified element
|
||||||
|
static toggle(elementId) {
|
||||||
|
this.getElement(elementId).classList.toggle('selected');
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,8 +1,10 @@
|
||||||
let currentPalette = [];
|
let currentPalette = [];
|
||||||
|
|
||||||
//adds the given color to the palette
|
/** Adds the given color to the palette
|
||||||
//input hex color string
|
*
|
||||||
//returns list item element
|
* @param {*} newColor the colour to add
|
||||||
|
* @return the list item containing the added colour
|
||||||
|
*/
|
||||||
function addColor (newColor) {
|
function addColor (newColor) {
|
||||||
//add # at beginning if not present
|
//add # at beginning if not present
|
||||||
if (newColor.charAt(0) != '#')
|
if (newColor.charAt(0) != '#')
|
||||||
|
@ -17,6 +19,7 @@ function addColor (newColor) {
|
||||||
button.style.backgroundColor = newColor;
|
button.style.backgroundColor = newColor;
|
||||||
button.addEventListener('mouseup', clickedColor);
|
button.addEventListener('mouseup', clickedColor);
|
||||||
listItem.appendChild(button);
|
listItem.appendChild(button);
|
||||||
|
listItem.classList.add("draggable-colour")
|
||||||
|
|
||||||
//insert new listItem element at the end of the colors menu (right before add button)
|
//insert new listItem element at the end of the colors menu (right before add button)
|
||||||
colorsMenu.insertBefore(listItem, colorsMenu.children[colorsMenu.children.length-1]);
|
colorsMenu.insertBefore(listItem, colorsMenu.children[colorsMenu.children.length-1]);
|
||||||
|
@ -35,11 +38,19 @@ function addColor (newColor) {
|
||||||
//hide edit button
|
//hide edit button
|
||||||
button.parentElement.lastChild.classList.add('hidden');
|
button.parentElement.lastChild.classList.add('hidden');
|
||||||
|
|
||||||
//show jscolor picker
|
//show jscolor picker, if basic mode is enabled
|
||||||
button.parentElement.firstChild.jscolor.show();
|
if (pixelEditorMode == 'Basic')
|
||||||
|
button.parentElement.firstChild.jscolor.show();
|
||||||
|
else
|
||||||
|
showDialogue("palette-block", false);
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log(currentPalette);
|
|
||||||
|
|
||||||
return listItem;
|
return listItem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
new Sortable(document.getElementById("colors-menu"), {
|
||||||
|
animation:100,
|
||||||
|
filter: ".noshrink",
|
||||||
|
draggable: ".draggable-colour",
|
||||||
|
onEnd: makeIsDraggingFalse
|
||||||
|
});
|
|
@ -1,4 +1,4 @@
|
||||||
//add color button
|
// add-color-button management
|
||||||
on('click', 'add-color-button', function(){
|
on('click', 'add-color-button', function(){
|
||||||
if (!documentCreated) return;
|
if (!documentCreated) return;
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,185 @@
|
||||||
|
// CONSTS
|
||||||
|
|
||||||
|
// Degrees to radiants
|
||||||
|
let degreesToRad = Math.PI / 180;
|
||||||
|
// I'm pretty sure that precision is necessary
|
||||||
|
let referenceWhite = {x: 95.05, y: 100, z: 108.89999999999999};
|
||||||
|
|
||||||
|
/**********************SECTION: COLOUR CONVERSIONS****************************** */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts an HSL color value to RGB. Conversion formula
|
||||||
|
* adapted from http://en.wikipedia.org/wiki/HSL_color_space.
|
||||||
|
* Assumes h, s, and l are contained in the set [0, 1] and
|
||||||
|
* returns r, g, and b in the set [0, 255].
|
||||||
|
*
|
||||||
|
* @param {number} h The hue
|
||||||
|
* @param {number} s The saturation
|
||||||
|
* @param {number} l The lightness
|
||||||
|
* @return {Array} The RGB representation
|
||||||
|
*/
|
||||||
|
function cpHslToRgb(h, s, l){
|
||||||
|
var r, g, b;
|
||||||
|
|
||||||
|
h /= 360;
|
||||||
|
s /= 100;
|
||||||
|
l /= 100;
|
||||||
|
|
||||||
|
if(s == 0){
|
||||||
|
r = g = b = l; // achromatic
|
||||||
|
}else{
|
||||||
|
var hue2rgb = function hue2rgb(p, q, t){
|
||||||
|
if(t < 0) t += 1;
|
||||||
|
if(t > 1) t -= 1;
|
||||||
|
if(t < 1/6) return p + (q - p) * 6 * t;
|
||||||
|
if(t < 1/2) return q;
|
||||||
|
if(t < 2/3) return p + (q - p) * (2/3 - t) * 6;
|
||||||
|
return p;
|
||||||
|
}
|
||||||
|
|
||||||
|
var q = l < 0.5 ? l * (1 + s) : l + s - l * s;
|
||||||
|
var p = 2 * l - q;
|
||||||
|
r = hue2rgb(p, q, h + 1/3);
|
||||||
|
g = hue2rgb(p, q, h);
|
||||||
|
b = hue2rgb(p, q, h - 1/3);
|
||||||
|
}
|
||||||
|
|
||||||
|
return [Math.round(r * 255), Math.round(g * 255), Math.round(b * 255)];
|
||||||
|
}
|
||||||
|
|
||||||
|
function hsvToRgb(h, s, v) {
|
||||||
|
var r, g, b;
|
||||||
|
|
||||||
|
h /= 360;
|
||||||
|
s /= 100;
|
||||||
|
v /= 100;
|
||||||
|
var i = Math.floor(h * 6);
|
||||||
|
var f = h * 6 - i;
|
||||||
|
var p = v * (1 - s);
|
||||||
|
var q = v * (1 - f * s);
|
||||||
|
var t = v * (1 - (1 - f) * s);
|
||||||
|
|
||||||
|
switch (i % 6) {
|
||||||
|
case 0: r = v, g = t, b = p; break;
|
||||||
|
case 1: r = q, g = v, b = p; break;
|
||||||
|
case 2: r = p, g = v, b = t; break;
|
||||||
|
case 3: r = p, g = q, b = v; break;
|
||||||
|
case 4: r = t, g = p, b = v; break;
|
||||||
|
case 5: r = v, g = p, b = q; break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return [ r * 255, g * 255, b * 255 ];
|
||||||
|
}
|
||||||
|
|
||||||
|
function hslToHex(h, s, l) {
|
||||||
|
h /= 360;
|
||||||
|
s /= 100;
|
||||||
|
l /= 100;
|
||||||
|
let r, g, b;
|
||||||
|
if (s === 0) {
|
||||||
|
r = g = b = l; // achromatic
|
||||||
|
} else {
|
||||||
|
const hue2rgb = (p, q, t) => {
|
||||||
|
if (t < 0) t += 1;
|
||||||
|
if (t > 1) t -= 1;
|
||||||
|
if (t < 1 / 6) return p + (q - p) * 6 * t;
|
||||||
|
if (t < 1 / 2) return q;
|
||||||
|
if (t < 2 / 3) return p + (q - p) * (2 / 3 - t) * 6;
|
||||||
|
return p;
|
||||||
|
};
|
||||||
|
const q = l < 0.5 ? l * (1 + s) : l + s - l * s;
|
||||||
|
const p = 2 * l - q;
|
||||||
|
r = hue2rgb(p, q, h + 1 / 3);
|
||||||
|
g = hue2rgb(p, q, h);
|
||||||
|
b = hue2rgb(p, q, h - 1 / 3);
|
||||||
|
}
|
||||||
|
const toHex = x => {
|
||||||
|
const hex = Math.round(x * 255).toString(16);
|
||||||
|
return hex.length === 1 ? '0' + hex : hex;
|
||||||
|
};
|
||||||
|
|
||||||
|
return `${toHex(r)}${toHex(g)}${toHex(b)}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function rgbToHsl(col) {
|
||||||
|
let r = col.r;
|
||||||
|
let g = col.g;
|
||||||
|
let b = col.b;
|
||||||
|
|
||||||
|
r /= 255, g /= 255, b /= 255;
|
||||||
|
|
||||||
|
let max = Math.max(r, g, b), min = Math.min(r, g, b);
|
||||||
|
let myH, myS, myL = (max + min) / 2;
|
||||||
|
|
||||||
|
if (max == min) {
|
||||||
|
myH = myS = 0; // achromatic
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
let d = max - min;
|
||||||
|
myS = myL > 0.5 ? d / (2 - max - min) : d / (max + min);
|
||||||
|
|
||||||
|
switch (max) {
|
||||||
|
case r: myH = (g - b) / d + (g < b ? 6 : 0); break;
|
||||||
|
case g: myH = (b - r) / d + 2; break;
|
||||||
|
case b: myH = (r - g) / d + 4; break;
|
||||||
|
}
|
||||||
|
|
||||||
|
myH /= 6;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {h: myH, s: myS, l: myL };
|
||||||
|
}
|
||||||
|
|
||||||
|
function rgbToHsv(col) {
|
||||||
|
let r = col.r;
|
||||||
|
let g = col.g;
|
||||||
|
let b = col.b;
|
||||||
|
|
||||||
|
r /= 255, g /= 255, b /= 255;
|
||||||
|
|
||||||
|
let max = Math.max(r, g, b), min = Math.min(r, g, b);
|
||||||
|
let myH, myS, myV = max;
|
||||||
|
|
||||||
|
let d = max - min;
|
||||||
|
myS = max == 0 ? 0 : d / max;
|
||||||
|
|
||||||
|
if (max == min) {
|
||||||
|
myH = 0; // achromatic
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
switch (max) {
|
||||||
|
case r: myH = (g - b) / d + (g < b ? 6 : 0); break;
|
||||||
|
case g: myH = (b - r) / d + 2; break;
|
||||||
|
case b: myH = (r - g) / d + 4; break;
|
||||||
|
}
|
||||||
|
|
||||||
|
myH /= 6;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {h: myH, s: myS, v: myV};
|
||||||
|
}
|
||||||
|
|
||||||
|
function RGBtoCIELAB(rgbColour) {
|
||||||
|
// Convert to XYZ first via matrix transformation
|
||||||
|
let x = 0.412453 * rgbColour.r + 0.357580 * rgbColour.g + 0.180423 * rgbColour.b;
|
||||||
|
let y = 0.212671 * rgbColour.r + 0.715160 * rgbColour.g + 0.072169 * rgbColour.b;
|
||||||
|
let z = 0.019334 * rgbColour.r + 0.119193 * rgbColour.g + 0.950227 * rgbColour.b;
|
||||||
|
|
||||||
|
let xFunc = CIELABconvF(x / referenceWhite.x);
|
||||||
|
let yFunc = CIELABconvF(y / referenceWhite.y);
|
||||||
|
let zFunc = CIELABconvF(z / referenceWhite.z);
|
||||||
|
|
||||||
|
let myL = 116 * yFunc - 16;
|
||||||
|
let myA = 500 * (xFunc - yFunc);
|
||||||
|
let myB = 200 * (yFunc - zFunc);
|
||||||
|
|
||||||
|
return {l: myL, a: myA, b: myB};
|
||||||
|
|
||||||
|
}
|
||||||
|
function CIELABconvF(value) {
|
||||||
|
if (value > Math.pow(6/29, 3)) {
|
||||||
|
return Math.cbrt(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
return 1/3 * Math.pow(6/29, 2) * value + 4/29;
|
||||||
|
}
|
|
@ -1,4 +1,9 @@
|
||||||
function changeZoom (layer, direction, cursorLocation) {
|
/** Changes the zoom level of the canvas
|
||||||
|
* @param {*} direction 'in' or 'out'
|
||||||
|
* @param {*} cursorLocation The position of the cursor when the user zoomed
|
||||||
|
*/
|
||||||
|
function changeZoom (direction, cursorLocation) {
|
||||||
|
// Computing current width and height
|
||||||
var oldWidth = canvasSize[0] * zoom;
|
var oldWidth = canvasSize[0] * zoom;
|
||||||
var oldHeight = canvasSize[1] * zoom;
|
var oldHeight = canvasSize[1] * zoom;
|
||||||
var newWidth, newHeight;
|
var newWidth, newHeight;
|
||||||
|
@ -11,7 +16,9 @@ function changeZoom (layer, direction, cursorLocation) {
|
||||||
newHeight = canvasSize[1] * zoom;
|
newHeight = canvasSize[1] * zoom;
|
||||||
|
|
||||||
//adjust canvas position
|
//adjust canvas position
|
||||||
layer.setCanvasOffset(layer.canvas.offsetLeft + (oldWidth - newWidth) *cursorLocation[0]/oldWidth, layer.canvas.offsetTop + (oldHeight - newHeight) *cursorLocation[1]/oldWidth);
|
layers[0].setCanvasOffset(
|
||||||
|
layers[0].canvas.offsetLeft + (oldWidth - newWidth) * cursorLocation[0]/oldWidth,
|
||||||
|
layers[0].canvas.offsetTop + (oldHeight - newHeight) * cursorLocation[1]/oldWidth);
|
||||||
}
|
}
|
||||||
//if you want to zoom in
|
//if you want to zoom in
|
||||||
else if (direction == 'in' && zoom + Math.ceil(zoom/10) < window.innerHeight/4){
|
else if (direction == 'in' && zoom + Math.ceil(zoom/10) < window.innerHeight/4){
|
||||||
|
@ -20,11 +27,13 @@ function changeZoom (layer, direction, cursorLocation) {
|
||||||
newHeight = canvasSize[1] * zoom;
|
newHeight = canvasSize[1] * zoom;
|
||||||
|
|
||||||
//adjust canvas position
|
//adjust canvas position
|
||||||
layer.setCanvasOffset(layer.canvas.offsetLeft - Math.round((newWidth - oldWidth)*cursorLocation[0]/oldWidth), layer.canvas.offsetTop - Math.round((newHeight - oldHeight)*cursorLocation[1]/oldHeight));
|
layers[0].setCanvasOffset(
|
||||||
|
layers[0].canvas.offsetLeft - Math.round((newWidth - oldWidth)*cursorLocation[0]/oldWidth),
|
||||||
|
layers[0].canvas.offsetTop - Math.round((newHeight - oldHeight)*cursorLocation[1]/oldHeight));
|
||||||
}
|
}
|
||||||
|
|
||||||
//resize canvas
|
//resize canvas
|
||||||
layer.resize();
|
layers[0].resize();
|
||||||
|
|
||||||
// adjust brush size
|
// adjust brush size
|
||||||
currentTool.updateCursor();
|
currentTool.updateCursor();
|
||||||
|
|
|
@ -13,7 +13,9 @@ var currentColor = firstCheckerBoardColor;
|
||||||
// Saving number of squares filled until now
|
// Saving number of squares filled until now
|
||||||
var nSquaresFilled = 0;
|
var nSquaresFilled = 0;
|
||||||
|
|
||||||
|
/** Fills the checkerboard canvas with squares with alternating colours
|
||||||
|
*
|
||||||
|
*/
|
||||||
function fillCheckerboard() {
|
function fillCheckerboard() {
|
||||||
// Getting checkerboard context
|
// Getting checkerboard context
|
||||||
var context = checkerBoard.context;
|
var context = checkerBoard.context;
|
||||||
|
|
|
@ -17,7 +17,7 @@ function clickedColor (e){
|
||||||
e.target.parentElement.classList.add('selected');
|
e.target.parentElement.classList.add('selected');
|
||||||
|
|
||||||
} else if (e.which == 3) { //right clicked color
|
} else if (e.which == 3) { //right clicked color
|
||||||
console.log('right clicked color button');
|
//console.log('right clicked color button');
|
||||||
|
|
||||||
//hide edit color button (to prevent it from showing)
|
//hide edit color button (to prevent it from showing)
|
||||||
e.target.parentElement.lastChild.classList.add('hidden');
|
e.target.parentElement.lastChild.classList.add('hidden');
|
||||||
|
|
|
@ -23,7 +23,7 @@ on('input', 'jscolor-hex-input', function (e) {
|
||||||
|
|
||||||
//changes all of one color to another after being changed from color picker
|
//changes all of one color to another after being changed from color picker
|
||||||
function colorChanged(e) {
|
function colorChanged(e) {
|
||||||
console.log('colorChanged() to ' + e.target.value);
|
//console.log('colorChanged() to ' + e.target.value);
|
||||||
//get colors
|
//get colors
|
||||||
var newColor = hexToRgb(e.target.value);
|
var newColor = hexToRgb(e.target.value);
|
||||||
var oldColor = e.target.oldColor;
|
var oldColor = e.target.oldColor;
|
||||||
|
@ -41,7 +41,7 @@ function colorChanged(e) {
|
||||||
|
|
||||||
//check if selected color already matches another color
|
//check if selected color already matches another color
|
||||||
colors = document.getElementsByClassName('color-button');
|
colors = document.getElementsByClassName('color-button');
|
||||||
console.log(colors);
|
//console.log(colors);
|
||||||
var colorCheckingStyle = 'background: #bc60c4; color: white';
|
var colorCheckingStyle = 'background: #bc60c4; color: white';
|
||||||
var newColorHex = e.target.value.toLowerCase();
|
var newColorHex = e.target.value.toLowerCase();
|
||||||
|
|
||||||
|
@ -54,11 +54,11 @@ function colorChanged(e) {
|
||||||
|
|
||||||
//if generated color matches this color
|
//if generated color matches this color
|
||||||
if (newColorHex == colors[i].jscolor.toString()) {
|
if (newColorHex == colors[i].jscolor.toString()) {
|
||||||
console.log('%ccolor already exists'+(colors[i].parentElement.classList.contains('jscolor-active')?' (but is the current color)':''), colorCheckingStyle);
|
//console.log('%ccolor already exists'+(colors[i].parentElement.classList.contains('jscolor-active')?' (but is the current color)':''), colorCheckingStyle);
|
||||||
|
|
||||||
//if the color isnt the one that has the picker currently open
|
//if the color isnt the one that has the picker currently open
|
||||||
if (!colors[i].parentElement.classList.contains('jscolor-active')) {
|
if (!colors[i].parentElement.classList.contains('jscolor-active')) {
|
||||||
console.log('%cColor is duplicate', colorCheckingStyle);
|
//console.log('%cColor is duplicate', colorCheckingStyle);
|
||||||
|
|
||||||
//show the duplicate color warning
|
//show the duplicate color warning
|
||||||
duplicateColorWarning.style.visibility = 'visible';
|
duplicateColorWarning.style.visibility = 'visible';
|
||||||
|
|
|
@ -0,0 +1,806 @@
|
||||||
|
let sliders = document.getElementsByClassName("cp-slider-entry");
|
||||||
|
let colourPreview = document.getElementById("cp-colour-preview");
|
||||||
|
let colourValue = document.getElementById("cp-hex");
|
||||||
|
let currentPickerMode = "rgb";
|
||||||
|
let currentPickingMode = "mono";
|
||||||
|
let styleElement = document.createElement("style");
|
||||||
|
let miniPickerCanvas = document.getElementById("cp-spectrum");
|
||||||
|
let miniPickerSlider = document.getElementById("cp-minipicker-slider");
|
||||||
|
let activePickerIcon = document.getElementById("cp-active-icon");
|
||||||
|
let pickerIcons = [activePickerIcon];
|
||||||
|
let hexContainers = [document.getElementById("cp-colours-previews").children[0],null,null,null];
|
||||||
|
let startPickerIconPos = [[0,0],[0,0],[0,0],[0,0]];
|
||||||
|
let currPickerIconPos = [[0,0], [0,0],[0,0],[0,0]];
|
||||||
|
let styles = ["",""];
|
||||||
|
let draggingCursor = false;
|
||||||
|
|
||||||
|
cpInit();
|
||||||
|
|
||||||
|
function cpInit() {
|
||||||
|
// Appending the palette styles
|
||||||
|
document.getElementsByTagName("head")[0].appendChild(styleElement);
|
||||||
|
|
||||||
|
// Saving first icon position
|
||||||
|
startPickerIconPos[0] = [miniPickerCanvas.getBoundingClientRect().left, miniPickerCanvas.getBoundingClientRect().top];
|
||||||
|
// Set the correct size of the canvas
|
||||||
|
miniPickerCanvas.height = miniPickerCanvas.getBoundingClientRect().height;
|
||||||
|
miniPickerCanvas.width = miniPickerCanvas.getBoundingClientRect().width;
|
||||||
|
|
||||||
|
// Update picker position
|
||||||
|
updatePickerByHex(colourValue.value);
|
||||||
|
// Startup updating
|
||||||
|
updateAllSliders();
|
||||||
|
// Fill minislider
|
||||||
|
updateMiniSlider(colourValue.value);
|
||||||
|
// Fill minipicker
|
||||||
|
updatePickerByHex(colourValue.value);
|
||||||
|
|
||||||
|
updateMiniPickerSpectrum();
|
||||||
|
}
|
||||||
|
|
||||||
|
function hexUpdated() {
|
||||||
|
updatePickerByHex(colourValue.value);
|
||||||
|
updateSlidersByHex(colourValue.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Applies the styles saved in the style array to the style element in the head of the document
|
||||||
|
function updateStyles() {
|
||||||
|
styleElement.innerHTML = styles[0] + styles[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Updates the background gradients of the sliders given their value
|
||||||
|
* Updates the hex colour and its preview
|
||||||
|
* Updates the minipicker according to the computed hex colour
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
function updateSliderValue (sliderIndex, updateMini = true) {
|
||||||
|
let toUpdate;
|
||||||
|
let slider;
|
||||||
|
let input;
|
||||||
|
let hexColour;
|
||||||
|
let sliderValues;
|
||||||
|
|
||||||
|
toUpdate = sliders[sliderIndex - 1];
|
||||||
|
|
||||||
|
slider = toUpdate.getElementsByTagName("input")[0];
|
||||||
|
input = toUpdate.getElementsByTagName("input")[1];
|
||||||
|
|
||||||
|
// Update label value
|
||||||
|
input.value = slider.value;
|
||||||
|
|
||||||
|
// Update preview colour
|
||||||
|
// get slider values
|
||||||
|
sliderValues = getSlidersValues();
|
||||||
|
|
||||||
|
// Generate preview colour
|
||||||
|
switch (currentPickerMode) {
|
||||||
|
case 'rgb':
|
||||||
|
hexColour = rgbToHex(sliderValues[0], sliderValues[1], sliderValues[2]);
|
||||||
|
break;
|
||||||
|
case 'hsv':
|
||||||
|
let tmpRgb = hsvToRgb(sliderValues[0], sliderValues[1], sliderValues[2]);
|
||||||
|
hexColour = rgbToHex(parseInt(tmpRgb[0]), parseInt(tmpRgb[1]), parseInt(tmpRgb[2]));
|
||||||
|
break;
|
||||||
|
case 'hsl':
|
||||||
|
hexColour = hslToHex(sliderValues[0], sliderValues[1], sliderValues[2]);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
console.log("wtf select a decent picker mode");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Update preview colour div
|
||||||
|
colourPreview.style.backgroundColor = '#' + hexColour;
|
||||||
|
colourValue.value = '#' + hexColour;
|
||||||
|
|
||||||
|
// Update sliders background
|
||||||
|
// there's no other way than creating a custom css file, appending it to the head and
|
||||||
|
// specify the sliders' backgrounds here
|
||||||
|
|
||||||
|
styles[0] = '';
|
||||||
|
for (let i=0; i<sliders.length; i++) {
|
||||||
|
styles[0] += getSliderCSS(i + 1, sliderValues);
|
||||||
|
}
|
||||||
|
|
||||||
|
updateStyles();
|
||||||
|
|
||||||
|
if (updateMini) {
|
||||||
|
updatePickerByHex(colourValue.value);
|
||||||
|
updateMiniPickerSpectrum();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculates the css gradient for a slider
|
||||||
|
function getSliderCSS(index, sliderValues) {
|
||||||
|
let ret = 'input[type=range]#';
|
||||||
|
let sliderId;
|
||||||
|
let gradientMin;
|
||||||
|
let gradientMax;
|
||||||
|
let hueGradient;
|
||||||
|
let rgbColour;
|
||||||
|
|
||||||
|
switch (index) {
|
||||||
|
case 1:
|
||||||
|
sliderId = 'first-slider';
|
||||||
|
switch (currentPickerMode) {
|
||||||
|
case 'rgb':
|
||||||
|
gradientMin = 'rgba(0,' + sliderValues[1] + ',' + sliderValues[2] + ',1)';
|
||||||
|
gradientMax = 'rgba(255,' + sliderValues[1] + ',' + sliderValues[2] + ',1)';
|
||||||
|
break;
|
||||||
|
case 'hsv':
|
||||||
|
hueGradient = getHueGradientHSV(sliderValues);
|
||||||
|
break;
|
||||||
|
case 'hsl':
|
||||||
|
// Hue gradient
|
||||||
|
hueGradient = getHueGradientHSL(sliderValues);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
sliderId = 'second-slider';
|
||||||
|
switch (currentPickerMode) {
|
||||||
|
case 'rgb':
|
||||||
|
gradientMin = 'rgba(' + sliderValues[0] + ',0,' + sliderValues[2] + ',1)';
|
||||||
|
gradientMax = 'rgba(' + sliderValues[0] + ',255,' + sliderValues[2] + ',1)';
|
||||||
|
break;
|
||||||
|
case 'hsv':
|
||||||
|
rgbColour = hsvToRgb(sliderValues[0], 0, sliderValues[2]);
|
||||||
|
gradientMin = 'rgba(' + rgbColour[0] + ',' + rgbColour[1] + ',' + rgbColour[2] + ',1)';
|
||||||
|
|
||||||
|
rgbColour = hsvToRgb(sliderValues[0], 100, sliderValues[2]);
|
||||||
|
gradientMax = 'rgba(' + rgbColour[0] + ',' + rgbColour[1] + ',' + rgbColour[2] + ',1)';
|
||||||
|
break;
|
||||||
|
case 'hsl':
|
||||||
|
rgbColour = cpHslToRgb(sliderValues[0], 0, sliderValues[2]);
|
||||||
|
gradientMin = 'rgba(' + rgbColour[0] + ',' + rgbColour[1] + ',' + rgbColour[2] + ',1)';
|
||||||
|
|
||||||
|
rgbColour = cpHslToRgb(sliderValues[0], 100, sliderValues[2]);
|
||||||
|
gradientMax = 'rgba(' + rgbColour[0] + ',' + rgbColour[1] + ',' + rgbColour[2] + ',1)';
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 3:
|
||||||
|
sliderId = 'third-slider';
|
||||||
|
switch (currentPickerMode) {
|
||||||
|
case 'rgb':
|
||||||
|
gradientMin = 'rgba(' + sliderValues[0] + ',' + sliderValues[1] + ',0,1)';
|
||||||
|
gradientMax = 'rgba(' + sliderValues[0] + ',' + sliderValues[1] + ',255,1)';
|
||||||
|
break;
|
||||||
|
case 'hsv':
|
||||||
|
rgbColour = hsvToRgb(sliderValues[0], sliderValues[1], 0);
|
||||||
|
gradientMin = 'rgba(' + rgbColour[0] + ',' + rgbColour[1] + ',' + rgbColour[2] + ',1)';
|
||||||
|
|
||||||
|
rgbColour = hsvToRgb(sliderValues[0], sliderValues[1], 100);
|
||||||
|
gradientMax = 'rgba(' + rgbColour[0] + ',' + rgbColour[1] + ',' + rgbColour[2] + ',1)';
|
||||||
|
break;
|
||||||
|
case 'hsl':
|
||||||
|
gradientMin = 'rgba(0,0,0,1)';
|
||||||
|
gradientMax = 'rgba(255,255,255,1)';
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
ret += sliderId;
|
||||||
|
ret += '::-webkit-slider-runnable-track {';
|
||||||
|
|
||||||
|
switch (currentPickerMode) {
|
||||||
|
case 'rgb':
|
||||||
|
ret += 'background: linear-gradient(90deg, rgba(2,0,36,1) 0%, ' +
|
||||||
|
gradientMin + ' 0%, ' + gradientMax + '100%)';
|
||||||
|
break;
|
||||||
|
case 'hsv':
|
||||||
|
case 'hsl':
|
||||||
|
ret += 'background: ';
|
||||||
|
if (index == 1) {
|
||||||
|
ret += hueGradient;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
ret += 'linear-gradient(90deg, rgba(2,0,36,1) 0%, ' + gradientMin + ' 0%, ';
|
||||||
|
// For hsl I also have to add a middle point
|
||||||
|
if (currentPickerMode == 'hsl' && index == 3) {
|
||||||
|
let rgb = cpHslToRgb(sliderValues[0], sliderValues[1], 50);
|
||||||
|
ret += 'rgba(' + rgb[0] + ',' + rgb[1] + ',' + rgb[2] + ',1) 50%,';
|
||||||
|
}
|
||||||
|
|
||||||
|
ret += gradientMax + '100%);';
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
ret += '}'
|
||||||
|
|
||||||
|
ret += ret.replace('::-webkit-slider-runnable-track', '::-moz-range-track');
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Computes the hue gradient used for hsl
|
||||||
|
function getHueGradientHSL(sliderValues) {
|
||||||
|
return 'linear-gradient(90deg, rgba(2,0,36,1) 0%, \
|
||||||
|
hsl(0,' + sliderValues[1] + '%,' + sliderValues[2]+ '%) 0%, \
|
||||||
|
hsl(60,' + sliderValues[1] + '%,' + sliderValues[2]+ '%) 16.6666%, \
|
||||||
|
hsl(120,' + sliderValues[1] + '%,' + sliderValues[2]+ '%) 33.3333333333%, \
|
||||||
|
hsl(180,'+ sliderValues[1] + '%,' + sliderValues[2]+ '%) 50%, \
|
||||||
|
hsl(240,' + sliderValues[1] + '%,' + sliderValues[2]+ '%) 66.66666%, \
|
||||||
|
hsl(300,'+ sliderValues[1] + '%,' + sliderValues[2]+ '%) 83.333333%, \
|
||||||
|
hsl(360,'+ sliderValues[1] + '%,' + sliderValues[2]+ '%) 100%);';
|
||||||
|
}
|
||||||
|
// Computes the hue gradient used for hsv
|
||||||
|
function getHueGradientHSV(sliderValues) {
|
||||||
|
let col = hsvToRgb(0, sliderValues[1], sliderValues[2]);
|
||||||
|
let ret = 'linear-gradient(90deg, rgba(2,0,36,1) 0%, ';
|
||||||
|
|
||||||
|
ret += 'rgba(' + col[0] + ',' + col[1] + ',' + col[2] + ',1) 0%,'
|
||||||
|
|
||||||
|
col = hsvToRgb(60, sliderValues[1], sliderValues[2]);
|
||||||
|
ret += 'rgba(' + col[0] + ',' + col[1] + ',' + col[2] + ',1) 16.6666%,';
|
||||||
|
|
||||||
|
col = hsvToRgb(120, sliderValues[1], sliderValues[2]);
|
||||||
|
ret += 'rgba(' + col[0] + ',' + col[1] + ',' + col[2] + ',1) 33.3333333333%,';
|
||||||
|
|
||||||
|
col = hsvToRgb(180, sliderValues[1], sliderValues[2]);
|
||||||
|
ret += 'rgba(' + col[0] + ',' + col[1] + ',' + col[2] + ',1) 50%,';
|
||||||
|
|
||||||
|
col = hsvToRgb(240, sliderValues[1], sliderValues[2]);
|
||||||
|
ret += 'rgba(' + col[0] + ',' + col[1] + ',' + col[2] + ',1) 66.66666%,';
|
||||||
|
|
||||||
|
col = hsvToRgb(300, sliderValues[1], sliderValues[2]);
|
||||||
|
ret += 'rgba(' + col[0] + ',' + col[1] + ',' + col[2] + ',1) 83.333333%,';
|
||||||
|
|
||||||
|
col = hsvToRgb(360, sliderValues[1], sliderValues[2]);
|
||||||
|
ret += 'rgba(' + col[0] + ',' + col[1] + ',' + col[2] + ',1) 100%);';
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fired when the values in the labels are changed
|
||||||
|
function inputChanged(target, index) {
|
||||||
|
let sliderIndex = index - 1;
|
||||||
|
|
||||||
|
sliders[sliderIndex].getElementsByTagName("input")[0].value = target.value;
|
||||||
|
updateSliderValue(index);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Updates the colour model used to pick colours
|
||||||
|
function changePickerMode(target, newMode) {
|
||||||
|
let maxRange;
|
||||||
|
let colArray;
|
||||||
|
let rgbTmp;
|
||||||
|
let hexColour = colourValue.value.replace('#', '');
|
||||||
|
|
||||||
|
currentPickerMode = newMode;
|
||||||
|
document.getElementsByClassName("cp-selected-mode")[0].classList.remove("cp-selected-mode");
|
||||||
|
target.classList.add("cp-selected-mode");
|
||||||
|
|
||||||
|
switch (newMode)
|
||||||
|
{
|
||||||
|
case 'rgb':
|
||||||
|
maxRange = [255,255,255];
|
||||||
|
sliders[0].getElementsByTagName("label")[0].innerHTML = 'R';
|
||||||
|
sliders[1].getElementsByTagName("label")[0].innerHTML = 'G';
|
||||||
|
sliders[2].getElementsByTagName("label")[0].innerHTML = 'B';
|
||||||
|
break;
|
||||||
|
case 'hsv':
|
||||||
|
maxRange = [360, 100, 100];
|
||||||
|
sliders[0].getElementsByTagName("label")[0].innerHTML = 'H';
|
||||||
|
sliders[1].getElementsByTagName("label")[0].innerHTML = 'S';
|
||||||
|
sliders[2].getElementsByTagName("label")[0].innerHTML = 'V';
|
||||||
|
break;
|
||||||
|
case 'hsl':
|
||||||
|
maxRange = [360, 100, 100];
|
||||||
|
sliders[0].getElementsByTagName("label")[0].innerHTML = 'H';
|
||||||
|
sliders[1].getElementsByTagName("label")[0].innerHTML = 'S';
|
||||||
|
sliders[2].getElementsByTagName("label")[0].innerHTML = 'L';
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
console.log("wtf select a decent picker mode");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let i=0; i<sliders.length; i++) {
|
||||||
|
let slider = sliders[i].getElementsByTagName("input")[0];
|
||||||
|
|
||||||
|
slider.setAttribute("max", maxRange[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Putting the current colour in the new slider
|
||||||
|
switch(currentPickerMode) {
|
||||||
|
case 'rgb':
|
||||||
|
colArray = hexToRgb(hexColour);
|
||||||
|
colArray = [colArray.r, colArray.g, colArray.b];
|
||||||
|
break;
|
||||||
|
case 'hsv':
|
||||||
|
rgbTmp = hexToRgb(hexColour);
|
||||||
|
colArray = rgbToHsv(rgbTmp);
|
||||||
|
|
||||||
|
colArray.h *= 360;
|
||||||
|
colArray.s *= 100;
|
||||||
|
colArray.v *= 100;
|
||||||
|
|
||||||
|
colArray = [colArray.h, colArray.s, colArray.v];
|
||||||
|
|
||||||
|
break;
|
||||||
|
case 'hsl':
|
||||||
|
rgbTmp = hexToRgb(hexColour);
|
||||||
|
colArray = rgbToHsl(rgbTmp);
|
||||||
|
|
||||||
|
colArray.h *= 360;
|
||||||
|
colArray.s *= 100;
|
||||||
|
colArray.l *= 100;
|
||||||
|
|
||||||
|
colArray = [colArray.h, colArray.s, colArray.l];
|
||||||
|
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let i=0; i<3; i++) {
|
||||||
|
sliders[i].getElementsByTagName("input")[0].value = colArray[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
updateAllSliders();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns an array containing the values of the sliders
|
||||||
|
function getSlidersValues() {
|
||||||
|
return [parseInt(sliders[0].getElementsByTagName("input")[0].value),
|
||||||
|
parseInt(sliders[1].getElementsByTagName("input")[0].value),
|
||||||
|
parseInt(sliders[2].getElementsByTagName("input")[0].value)];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Updates every slider
|
||||||
|
function updateAllSliders(updateMini=true) {
|
||||||
|
for (let i=1; i<=3; i++) {
|
||||||
|
updateSliderValue(i, updateMini);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/******************SECTION: MINIPICKER******************/
|
||||||
|
|
||||||
|
// Moves the picker icon according to the mouse position on the canvas
|
||||||
|
function movePickerIcon(event) {
|
||||||
|
event.preventDefault();
|
||||||
|
|
||||||
|
if (event.which == 1 || draggingCursor) {
|
||||||
|
let cursorPos = getCursorPosMinipicker(event);
|
||||||
|
let canvasRect = miniPickerCanvas.getBoundingClientRect();
|
||||||
|
|
||||||
|
let left = (cursorPos[0] - startPickerIconPos[0][0] - 8);
|
||||||
|
let top = (cursorPos[1] - startPickerIconPos[0][1] - 8);
|
||||||
|
|
||||||
|
if (left > -8 && top > -8 && left < canvasRect.width-8 && top < canvasRect.height-8){
|
||||||
|
activePickerIcon.style["left"] = "" + left + "px";
|
||||||
|
activePickerIcon.style["top"]= "" + top + "px";
|
||||||
|
|
||||||
|
currPickerIconPos[0] = [left, top];
|
||||||
|
}
|
||||||
|
|
||||||
|
updateMiniPickerColour();
|
||||||
|
updateOtherIcons();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Updates the main sliders given a hex value computed with the minipicker
|
||||||
|
function updateSlidersByHex(hex, updateMini = true) {
|
||||||
|
let colour;
|
||||||
|
let mySliders = [sliders[0].getElementsByTagName("input")[0],
|
||||||
|
sliders[1].getElementsByTagName("input")[0],
|
||||||
|
sliders[2].getElementsByTagName("input")[0]];
|
||||||
|
|
||||||
|
switch (currentPickerMode) {
|
||||||
|
case 'rgb':
|
||||||
|
colour = hexToRgb(hex);
|
||||||
|
|
||||||
|
mySliders[0].value = colour.r;
|
||||||
|
mySliders[1].value = colour.g;
|
||||||
|
mySliders[2].value = colour.b;
|
||||||
|
|
||||||
|
break;
|
||||||
|
case 'hsv':
|
||||||
|
colour = rgbToHsv(hexToRgb(hex));
|
||||||
|
|
||||||
|
mySliders[0].value = colour.h * 360;
|
||||||
|
mySliders[1].value = colour.s * 100;
|
||||||
|
mySliders[2].value = colour.v * 100;
|
||||||
|
|
||||||
|
break;
|
||||||
|
case 'hsl':
|
||||||
|
colour = rgbToHsl(hexToRgb(hex));
|
||||||
|
|
||||||
|
mySliders[0].value = colour.h * 360;
|
||||||
|
mySliders[1].value = colour.s * 100;
|
||||||
|
mySliders[2].value = colour.l * 100;
|
||||||
|
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
updateAllSliders(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Gets the position of the picker cursor relative to the canvas
|
||||||
|
function getCursorPosMinipicker(e) {
|
||||||
|
var x;
|
||||||
|
var y;
|
||||||
|
|
||||||
|
if (e.pageX != undefined && e.pageY != undefined) {
|
||||||
|
x = e.pageX;
|
||||||
|
y = e.pageY;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
x = e.clientX + document.body.scrollLeft + document.documentElement.scrollLeft;
|
||||||
|
y = e.clientY + document.body.scrollTop + document.documentElement.scrollTop;
|
||||||
|
}
|
||||||
|
|
||||||
|
x -= miniPickerCanvas.offsetLeft;
|
||||||
|
y -= miniPickerCanvas.offsetTop;
|
||||||
|
|
||||||
|
return [Math.round(x), Math.round(y)];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Updates the minipicker given a hex computed by the main sliders
|
||||||
|
// Moves the cursor
|
||||||
|
function updatePickerByHex(hex) {
|
||||||
|
let hsv = rgbToHsv(hexToRgb(hex));
|
||||||
|
let xPos = miniPickerCanvas.width * hsv.h - 8;
|
||||||
|
let yPos = miniPickerCanvas.height * hsv.s + 8;
|
||||||
|
|
||||||
|
miniPickerSlider.value = hsv.v * 100;
|
||||||
|
|
||||||
|
currPickerIconPos[0][0] = xPos;
|
||||||
|
currPickerIconPos[0][1] = miniPickerCanvas.height - yPos;
|
||||||
|
|
||||||
|
if (currPickerIconPos[0][1] >= 92)
|
||||||
|
{
|
||||||
|
currPickerIconPos[0][1] = 91.999;
|
||||||
|
}
|
||||||
|
|
||||||
|
activePickerIcon.style.left = '' + xPos + 'px';
|
||||||
|
activePickerIcon.style.top = '' + (miniPickerCanvas.height - yPos) + 'px';
|
||||||
|
activePickerIcon.style.backgroundColor = '#' + getMiniPickerColour();
|
||||||
|
|
||||||
|
colourPreview.style.backgroundColor = hex;
|
||||||
|
|
||||||
|
updateOtherIcons();
|
||||||
|
updateMiniSlider(hex);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fired when the value of the minislider changes: updates the spectrum gradient and the hex colour
|
||||||
|
function miniSliderInput(event) {
|
||||||
|
let newHex;
|
||||||
|
let newHsv = rgbToHsv(hexToRgb(getMiniPickerColour()));
|
||||||
|
let rgb;
|
||||||
|
|
||||||
|
// Adding slider value to value
|
||||||
|
newHsv.v = parseInt(event.target.value);
|
||||||
|
// Updating hex
|
||||||
|
rgb = hsvToRgb(newHsv.h * 360, newHsv.s * 100, newHsv.v);
|
||||||
|
newHex = rgbToHex(Math.round(rgb[0]), Math.round(rgb[1]), Math.round(rgb[2]));
|
||||||
|
|
||||||
|
colourValue.value = newHex;
|
||||||
|
|
||||||
|
updateMiniPickerSpectrum();
|
||||||
|
updateMiniPickerColour();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Updates the hex colour after having changed the minislider (MERGE)
|
||||||
|
function updateMiniPickerColour() {
|
||||||
|
let hex = getMiniPickerColour();
|
||||||
|
|
||||||
|
activePickerIcon.style.backgroundColor = '#' + hex;
|
||||||
|
|
||||||
|
// Update hex and sliders based on hex
|
||||||
|
colourValue.value = '#' + hex;
|
||||||
|
colourPreview.style.backgroundColor = '#' + hex;
|
||||||
|
|
||||||
|
updateSlidersByHex(hex);
|
||||||
|
updateMiniSlider(hex);
|
||||||
|
updateOtherIcons();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns the current colour of the minipicker
|
||||||
|
function getMiniPickerColour() {
|
||||||
|
let hex;
|
||||||
|
let pickedColour;
|
||||||
|
|
||||||
|
pickedColour = miniPickerCanvas.getContext('2d').getImageData(currPickerIconPos[0][0] + 8,
|
||||||
|
currPickerIconPos[0][1] + 8, 1, 1).data;
|
||||||
|
|
||||||
|
hex = rgbToHex(pickedColour[0], pickedColour[1], pickedColour[2]);
|
||||||
|
|
||||||
|
return hex;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update the background gradient of the slider in the minipicker
|
||||||
|
function updateMiniSlider(hex) {
|
||||||
|
let rgb = hexToRgb(hex);
|
||||||
|
|
||||||
|
styles[1] = "input[type=range]#cp-minipicker-slider::-webkit-slider-runnable-track { background: rgb(2,0,36);";
|
||||||
|
styles[1] += "background: linear-gradient(90deg, rgba(2,0,36,1) 0%, rgba(0,0,0,1) 0%, " +
|
||||||
|
"rgba(" + rgb.r + "," + rgb.g + "," + rgb.b + ",1) 100%);}";
|
||||||
|
|
||||||
|
updateMiniPickerSpectrum();
|
||||||
|
updateStyles();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Updates the gradient of the spectrum canvas in the minipicker
|
||||||
|
function updateMiniPickerSpectrum() {
|
||||||
|
let ctx = miniPickerCanvas.getContext('2d');
|
||||||
|
let hsv = rgbToHsv(hexToRgb(colourValue.value));
|
||||||
|
let tmp;
|
||||||
|
let white = {h:hsv.h * 360, s:0, v: parseInt(miniPickerSlider.value)};
|
||||||
|
|
||||||
|
white = hsvToRgb(white.h, white.s, white.v);
|
||||||
|
|
||||||
|
ctx.clearRect(0, 0, miniPickerCanvas.width, miniPickerCanvas.height);
|
||||||
|
|
||||||
|
// Drawing hues
|
||||||
|
var hGrad = ctx.createLinearGradient(0, 0, miniPickerCanvas.width, 0);
|
||||||
|
|
||||||
|
for (let i=0; i<7; i++) {
|
||||||
|
tmp = hsvToRgb(60 * i, 100, hsv.v * 100);
|
||||||
|
hGrad.addColorStop(i / 6, '#' + rgbToHex(Math.round(tmp[0]), Math.round(tmp[1]), Math.round(tmp[2])));
|
||||||
|
}
|
||||||
|
ctx.fillStyle = hGrad;
|
||||||
|
ctx.fillRect(0, 0, miniPickerCanvas.width, miniPickerCanvas.height);
|
||||||
|
|
||||||
|
// Drawing sat / lum
|
||||||
|
var vGrad = ctx.createLinearGradient(0, 0, 0, miniPickerCanvas.height);
|
||||||
|
vGrad.addColorStop(0, 'rgba(' + white[0] +',' + white[1] + ',' + white[2] + ',0)');
|
||||||
|
/*
|
||||||
|
vGrad.addColorStop(0.1, 'rgba(255,255,255,0)');
|
||||||
|
vGrad.addColorStop(0.9, 'rgba(255,255,255,1)');
|
||||||
|
*/
|
||||||
|
vGrad.addColorStop(1, 'rgba(' + white[0] +',' + white[1] + ',' + white[2] + ',1)');
|
||||||
|
|
||||||
|
ctx.fillStyle = vGrad;
|
||||||
|
ctx.fillRect(0, 0, miniPickerCanvas.width, miniPickerCanvas.height);
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggleDraggingCursor() {
|
||||||
|
draggingCursor = !draggingCursor;
|
||||||
|
}
|
||||||
|
|
||||||
|
function changePickingMode(event, newMode) {
|
||||||
|
let nIcons = pickerIcons.length;
|
||||||
|
let canvasContainer = document.getElementById("cp-canvas-container");
|
||||||
|
// Number of hex containers to add
|
||||||
|
let nHexContainers;
|
||||||
|
|
||||||
|
// Remove selected class from previous mode
|
||||||
|
document.getElementById("cp-colour-picking-modes").getElementsByClassName("cp-selected-mode")[0].classList.remove("cp-selected-mode");
|
||||||
|
// Updating mode
|
||||||
|
currentPickingMode = newMode;
|
||||||
|
// Adding selected class to new mode
|
||||||
|
event.target.classList.add("cp-selected-mode");
|
||||||
|
|
||||||
|
for (let i=1; i<nIcons; i++) {
|
||||||
|
// Deleting extra icons
|
||||||
|
pickerIcons.pop();
|
||||||
|
canvasContainer.removeChild(canvasContainer.children[2]);
|
||||||
|
|
||||||
|
// Deleting extra hex containers
|
||||||
|
hexContainers[0].parentElement.removeChild(hexContainers[0].parentElement.children[1]);
|
||||||
|
hexContainers[i] = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Resetting first hex container size
|
||||||
|
hexContainers[0].style.width = '100%';
|
||||||
|
|
||||||
|
switch (currentPickingMode)
|
||||||
|
{
|
||||||
|
case 'analog':
|
||||||
|
createIcon();
|
||||||
|
createIcon();
|
||||||
|
|
||||||
|
nHexContainers = 2;
|
||||||
|
break;
|
||||||
|
case 'cmpt':
|
||||||
|
// Easiest one, add 180 to the H value and move the icon
|
||||||
|
createIcon();
|
||||||
|
nHexContainers = 1;
|
||||||
|
break;
|
||||||
|
case 'tri':
|
||||||
|
createIcon();
|
||||||
|
createIcon();
|
||||||
|
nHexContainers = 2;
|
||||||
|
break
|
||||||
|
case 'scmpt':
|
||||||
|
createIcon();
|
||||||
|
createIcon();
|
||||||
|
nHexContainers = 2;
|
||||||
|
break;
|
||||||
|
case 'tetra':
|
||||||
|
for (let i=0; i<3; i++) {
|
||||||
|
createIcon();
|
||||||
|
}
|
||||||
|
nHexContainers = 3;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
console.log("How did you select the " + currentPickingMode + ", hackerman?");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Editing the size of the first container
|
||||||
|
hexContainers[0].style.width = '' + 100 / (nHexContainers + 1) + '%';
|
||||||
|
// Adding hex preview containers
|
||||||
|
for (let i=0; i<nHexContainers; i++) {
|
||||||
|
let newContainer = document.createElement("div");
|
||||||
|
newContainer.classList.add("cp-colour-preview");
|
||||||
|
newContainer.style.width = "" + (100 / (nHexContainers + 1)) + "%";
|
||||||
|
|
||||||
|
hexContainers[0].parentElement.appendChild(newContainer);
|
||||||
|
hexContainers[i + 1] = newContainer;
|
||||||
|
}
|
||||||
|
|
||||||
|
function createIcon() {
|
||||||
|
let newIcon = document.createElement("div");
|
||||||
|
newIcon.classList.add("cp-picker-icon");
|
||||||
|
pickerIcons.push(newIcon);
|
||||||
|
|
||||||
|
canvasContainer.appendChild(newIcon);
|
||||||
|
}
|
||||||
|
|
||||||
|
updateOtherIcons();
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateOtherIcons() {
|
||||||
|
let currentColorHex = colourValue.value;
|
||||||
|
let currentColourHsv = rgbToHsv(hexToRgb(currentColorHex));
|
||||||
|
let newColourHsv = {h:currentColourHsv.h, s:currentColourHsv.s, v:currentColourHsv.v};
|
||||||
|
let newColourHexes = ['', '', ''];
|
||||||
|
let tmpRgb;
|
||||||
|
|
||||||
|
// Salvo tutti i
|
||||||
|
|
||||||
|
switch (currentPickingMode)
|
||||||
|
{
|
||||||
|
case 'mono':
|
||||||
|
break;
|
||||||
|
case 'analog':
|
||||||
|
// First colour
|
||||||
|
newColourHsv.h = (((currentColourHsv.h*360 + 40) % 360) / 360);
|
||||||
|
|
||||||
|
currPickerIconPos[1][0] = miniPickerCanvas.width * newColourHsv.h - 8;
|
||||||
|
currPickerIconPos[1][1] = miniPickerCanvas.height - (miniPickerCanvas.height * newColourHsv.s + 8);
|
||||||
|
|
||||||
|
tmpRgb = hsvToRgb(newColourHsv.h*360, newColourHsv.s*100, newColourHsv.v*100);
|
||||||
|
newColourHexes[0] = rgbToHex(Math.round(tmpRgb[0]), Math.round(tmpRgb[1]), Math.round(tmpRgb[2]));
|
||||||
|
|
||||||
|
// Second colour
|
||||||
|
newColourHsv.h = (((currentColourHsv.h*360 - 40) % 360) / 360);
|
||||||
|
if (newColourHsv.h < 0) {
|
||||||
|
newColourHsv.h += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
currPickerIconPos[2][0] = miniPickerCanvas.width * newColourHsv.h - 8;
|
||||||
|
currPickerIconPos[2][1] = miniPickerCanvas.height - (miniPickerCanvas.height * newColourHsv.s + 8);
|
||||||
|
|
||||||
|
tmpRgb = hsvToRgb(newColourHsv.h*360, newColourHsv.s*100, newColourHsv.v*100);
|
||||||
|
newColourHexes[1] = rgbToHex(Math.round(tmpRgb[0]), Math.round(tmpRgb[1]), Math.round(tmpRgb[2]));
|
||||||
|
break;
|
||||||
|
case 'cmpt':
|
||||||
|
newColourHsv.h = (((currentColourHsv.h*360 + 180) % 360) / 360);
|
||||||
|
|
||||||
|
currPickerIconPos[1][0] = miniPickerCanvas.width * newColourHsv.h - 8;
|
||||||
|
currPickerIconPos[1][1] = miniPickerCanvas.height - (miniPickerCanvas.height * newColourHsv.s + 8);
|
||||||
|
|
||||||
|
tmpRgb = hsvToRgb(newColourHsv.h*360, newColourHsv.s*100, newColourHsv.v*100);
|
||||||
|
newColourHexes[0] = rgbToHex(Math.round(tmpRgb[0]), Math.round(tmpRgb[1]), Math.round(tmpRgb[2]));
|
||||||
|
break;
|
||||||
|
case 'tri':
|
||||||
|
for (let i=1; i< 3; i++) {
|
||||||
|
newColourHsv.h = (((currentColourHsv.h*360 + 120*i) % 360) / 360);
|
||||||
|
|
||||||
|
currPickerIconPos[i][0] = miniPickerCanvas.width * newColourHsv.h - 8;
|
||||||
|
currPickerIconPos[i][1] = miniPickerCanvas.height - (miniPickerCanvas.height * newColourHsv.s + 8);
|
||||||
|
|
||||||
|
tmpRgb = hsvToRgb(newColourHsv.h*360, newColourHsv.s*100, newColourHsv.v*100);
|
||||||
|
newColourHexes[i - 1] = rgbToHex(Math.round(tmpRgb[0]), Math.round(tmpRgb[1]), Math.round(tmpRgb[2]));
|
||||||
|
}
|
||||||
|
|
||||||
|
break
|
||||||
|
case 'scmpt':
|
||||||
|
// First colour
|
||||||
|
newColourHsv.h = (((currentColourHsv.h*360 + 210) % 360) / 360);
|
||||||
|
|
||||||
|
currPickerIconPos[1][0] = miniPickerCanvas.width * newColourHsv.h - 8;
|
||||||
|
currPickerIconPos[1][1] = miniPickerCanvas.height - (miniPickerCanvas.height * newColourHsv.s + 8);
|
||||||
|
|
||||||
|
tmpRgb = hsvToRgb(newColourHsv.h*360, newColourHsv.s*100, newColourHsv.v*100);
|
||||||
|
newColourHexes[0] = rgbToHex(Math.round(tmpRgb[0]), Math.round(tmpRgb[1]), Math.round(tmpRgb[2]));
|
||||||
|
|
||||||
|
// Second colour
|
||||||
|
newColourHsv.h = (((currentColourHsv.h*360 + 150) % 360) / 360);
|
||||||
|
|
||||||
|
currPickerIconPos[2][0] = miniPickerCanvas.width * newColourHsv.h - 8;
|
||||||
|
currPickerIconPos[2][1] = miniPickerCanvas.height - (miniPickerCanvas.height * newColourHsv.s + 8);
|
||||||
|
|
||||||
|
tmpRgb = hsvToRgb(newColourHsv.h*360, newColourHsv.s*100, newColourHsv.v*100);
|
||||||
|
newColourHexes[1] = rgbToHex(Math.round(tmpRgb[0]), Math.round(tmpRgb[1]), Math.round(tmpRgb[2]));
|
||||||
|
break;
|
||||||
|
case 'tetra':
|
||||||
|
for (let i=1; i< 4; i++) {
|
||||||
|
newColourHsv.h = (((currentColourHsv.h*360 + 90*i) % 360) / 360);
|
||||||
|
|
||||||
|
currPickerIconPos[i][0] = miniPickerCanvas.width * newColourHsv.h - 8;
|
||||||
|
currPickerIconPos[i][1] = miniPickerCanvas.height - (miniPickerCanvas.height * newColourHsv.s + 8);
|
||||||
|
|
||||||
|
tmpRgb = hsvToRgb(newColourHsv.h*360, newColourHsv.s*100, newColourHsv.v*100);
|
||||||
|
newColourHexes[i - 1] = rgbToHex(Math.round(tmpRgb[0]), Math.round(tmpRgb[1]), Math.round(tmpRgb[2]));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
console.log("How did you select the " + currentPickingMode + ", hackerman?");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
hexContainers[0].style.color = getHexPreviewColour(colourValue.value);
|
||||||
|
|
||||||
|
for (let i=1; i<pickerIcons.length; i++) {
|
||||||
|
pickerIcons[i].style.left = '' + currPickerIconPos[i][0] + 'px';
|
||||||
|
pickerIcons[i].style.top = '' + currPickerIconPos[i][1] + 'px';
|
||||||
|
|
||||||
|
pickerIcons[i].style.backgroundColor = '#' + newColourHexes[i - 1];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (currentPickingMode != "analog") {
|
||||||
|
hexContainers[0].style.backgroundColor = colourValue.value;
|
||||||
|
hexContainers[0].innerHTML = colourValue.value;
|
||||||
|
|
||||||
|
for (let i=0; i<pickerIcons.length - 1; i++) {
|
||||||
|
hexContainers[i + 1].style.backgroundColor = '#' + newColourHexes[i];
|
||||||
|
hexContainers[i + 1].innerHTML = '#' + newColourHexes[i];
|
||||||
|
hexContainers[i + 1].style.color = getHexPreviewColour(newColourHexes[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// If I'm using analogous mode, I place the current colour in the middle
|
||||||
|
else {
|
||||||
|
hexContainers[1].style.backgroundColor = colourValue.value;
|
||||||
|
hexContainers[1].innerHTML = colourValue.value;
|
||||||
|
|
||||||
|
hexContainers[2].style.backgroundColor = '#' + newColourHexes[0];
|
||||||
|
hexContainers[2].innerHTML = '#' + newColourHexes[0];
|
||||||
|
|
||||||
|
hexContainers[0].style.backgroundColor = '#' + newColourHexes[1];
|
||||||
|
hexContainers[0].innerHTML = '#' + newColourHexes[1];
|
||||||
|
|
||||||
|
for (let i=1; i<3; i++) {
|
||||||
|
hexContainers[i].style.color = getHexPreviewColour(newColourHexes[i - 1]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getSelectedColours() {
|
||||||
|
let ret = [];
|
||||||
|
|
||||||
|
for (let i=0; i<hexContainers.length; i++) {
|
||||||
|
if (hexContainers[i] != null) {
|
||||||
|
ret.push(hexContainers[i].innerHTML);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getHexPreviewColour(hex) {
|
||||||
|
//if brightness is over threshold, make the text dark
|
||||||
|
if (colorBrightness(hex) > 110) {
|
||||||
|
return '#332f35'
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return '#c2bbc7';
|
||||||
|
}
|
||||||
|
|
||||||
|
//take in a color and return its brightness
|
||||||
|
function colorBrightness (color) {
|
||||||
|
var r = parseInt(color.slice(1, 3), 16);
|
||||||
|
var g = parseInt(color.slice(3, 5), 16);
|
||||||
|
var b = parseInt(color.slice(5, 7), 16);
|
||||||
|
return Math.round(((parseInt(r) * 299) + (parseInt(g) * 587) + (parseInt(b) * 114)) / 1000);
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,11 +1,17 @@
|
||||||
|
// Data saved when copying or cutting
|
||||||
let clipboardData;
|
let clipboardData;
|
||||||
|
// Tells if the user is pasting something or not
|
||||||
let isPasting = false;
|
let isPasting = false;
|
||||||
|
|
||||||
|
// Coordinates of the copied (or cut) selection
|
||||||
let copiedStartX;
|
let copiedStartX;
|
||||||
let copiedStartY;
|
let copiedStartY;
|
||||||
let copiedEndX;
|
let copiedEndX;
|
||||||
let copiedEndY;
|
let copiedEndY;
|
||||||
|
|
||||||
|
/** Copies the current selection to the clipboard
|
||||||
|
*
|
||||||
|
*/
|
||||||
function copySelection() {
|
function copySelection() {
|
||||||
copiedEndX = endX;
|
copiedEndX = endX;
|
||||||
copiedEndY = endY;
|
copiedEndY = endY;
|
||||||
|
@ -16,13 +22,19 @@ function copySelection() {
|
||||||
clipboardData = currentLayer.context.getImageData(startX, startY, endX - startX + 1, endY - startY + 1);
|
clipboardData = currentLayer.context.getImageData(startX, startY, endX - startX + 1, endY - startY + 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Pastes the clipboard data onto the current layer
|
||||||
|
*
|
||||||
|
*/
|
||||||
function pasteSelection() {
|
function pasteSelection() {
|
||||||
// Can't paste if the layer is locked
|
// Can't paste if the layer is locked
|
||||||
if (currentLayer.isLocked) {
|
if (currentLayer.isLocked) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Cancel the current selection
|
||||||
endSelection();
|
endSelection();
|
||||||
|
|
||||||
|
// I'm pasting
|
||||||
isPasting = true;
|
isPasting = true;
|
||||||
// Putting the image data on the tmp layer
|
// Putting the image data on the tmp layer
|
||||||
TMPLayer.context.putImageData(clipboardData, copiedStartX, copiedStartY);
|
TMPLayer.context.putImageData(clipboardData, copiedStartX, copiedStartY);
|
||||||
|
@ -43,9 +55,11 @@ function pasteSelection() {
|
||||||
//drawRect(copiedStartX, copiedEndX, copiedStartY, copiedEndY);
|
//drawRect(copiedStartX, copiedEndX, copiedStartY, copiedEndY);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Cuts the current selection and copies it to the clipboard
|
||||||
|
*
|
||||||
|
*/
|
||||||
function cutSelectionTool() {
|
function cutSelectionTool() {
|
||||||
console.log("Taglio");
|
// Saving the coordinates
|
||||||
|
|
||||||
copiedEndX = endX;
|
copiedEndX = endX;
|
||||||
copiedEndY = endY;
|
copiedEndY = endY;
|
||||||
|
|
||||||
|
@ -53,13 +67,19 @@ function cutSelectionTool() {
|
||||||
copiedStartY = startY;
|
copiedStartY = startY;
|
||||||
|
|
||||||
// Getting the selected pixels
|
// Getting the selected pixels
|
||||||
|
// If I'm already moving a selection
|
||||||
if (imageDataToMove !== undefined) {
|
if (imageDataToMove !== undefined) {
|
||||||
|
// I just save that selection in the clipboard
|
||||||
clipboardData = imageDataToMove;
|
clipboardData = imageDataToMove;
|
||||||
|
// And clear the underlying space
|
||||||
TMPLayer.context.clearRect(0, 0, TMPLayer.canvas.width, TMPLayer.canvas.height);
|
TMPLayer.context.clearRect(0, 0, TMPLayer.canvas.width, TMPLayer.canvas.height);
|
||||||
|
// The image has been cleared, so I don't have anything to move anymore
|
||||||
imageDataToMove = undefined;
|
imageDataToMove = undefined;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
clipboardData = currentLayer.context.getImageData(startX, startY, endX - startX + 1, endY - startY + 1);
|
// Otherwise, I copy the current selection into the clipboard
|
||||||
|
copySelection();
|
||||||
|
// And clear the selection
|
||||||
currentLayer.context.clearRect(startX - 0.5, startY - 0.5, endX - startX + 1, endY - startY + 1);
|
currentLayer.context.clearRect(startX - 0.5, startY - 0.5, endX - startX + 1, endY - startY + 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,7 +1,51 @@
|
||||||
|
function create(isSplash) {
|
||||||
|
var splashPostfix = '';
|
||||||
|
// If I'm creating from the splash menu, I append '-splash' so I get the corresponding values
|
||||||
|
if (isSplash) {
|
||||||
|
splashPostfix = '-splash';
|
||||||
|
}
|
||||||
|
|
||||||
|
var width = getValue('size-width' + splashPostfix);
|
||||||
|
var height = getValue('size-height' + splashPostfix);
|
||||||
|
|
||||||
|
// If I'm creating from the splash screen, I use the splashMode variable
|
||||||
|
var mode = isSplash ? splashMode : getValue('editor-mode');
|
||||||
|
|
||||||
|
newPixel(width, height, mode);
|
||||||
|
|
||||||
|
// If I'm not creating from the splash page, then this is not the first project I've created
|
||||||
|
if (!isSplash)
|
||||||
|
document.getElementById('new-pixel-warning').style.display = 'block';
|
||||||
|
|
||||||
|
//get selected palette name
|
||||||
|
var selectedPalette = getText('palette-button' + splashPostfix);
|
||||||
|
if (selectedPalette == 'Choose a palette...')
|
||||||
|
selectedPalette = 'none';
|
||||||
|
|
||||||
|
//track google event
|
||||||
|
ga('send', 'event', 'Pixel Editor New', selectedPalette, width+'/'+height); /*global ga*/
|
||||||
|
|
||||||
|
|
||||||
|
//reset new form
|
||||||
|
setValue('size-width', 64);
|
||||||
|
setValue('size-height', 64);
|
||||||
|
setValue("editor-mode", 'Advanced')
|
||||||
|
|
||||||
|
setText('editor-mode-button', 'Choose a mode...');
|
||||||
|
setText('palette-button', 'Choose a palette...');
|
||||||
|
setText('preset-button', 'Choose a preset...');
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Triggered when the "Create" button in the new pixel dialogue is pressed
|
||||||
|
*
|
||||||
|
*/
|
||||||
on('click', 'create-button', function (){
|
on('click', 'create-button', function (){
|
||||||
|
// Getting the values of the form
|
||||||
var width = getValue('size-width');
|
var width = getValue('size-width');
|
||||||
var height = getValue('size-height');
|
var height = getValue('size-height');
|
||||||
var mode = getValue("editor-mode");
|
var mode = getValue("editor-mode");
|
||||||
|
|
||||||
|
// Creating a new pixel with those properties
|
||||||
newPixel(width, height, mode);
|
newPixel(width, height, mode);
|
||||||
document.getElementById('new-pixel-warning').style.display = 'block';
|
document.getElementById('new-pixel-warning').style.display = 'block';
|
||||||
|
|
||||||
|
@ -23,3 +67,37 @@ on('click', 'create-button', function (){
|
||||||
setText('palette-button', 'Choose a palette...');
|
setText('palette-button', 'Choose a palette...');
|
||||||
setText('preset-button', 'Choose a preset...');
|
setText('preset-button', 'Choose a preset...');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/** Triggered when the "Create" button in the new pixel dialogue is pressed
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
on('click', 'create-button-splash', function (){
|
||||||
|
// Getting the values of the form
|
||||||
|
var width = getValue('size-width-splash');
|
||||||
|
var height = getValue('size-height-splash');
|
||||||
|
var mode = pixelEditorMode;
|
||||||
|
|
||||||
|
if (mode == 'Advanced')
|
||||||
|
mode = "Basic";
|
||||||
|
else
|
||||||
|
mode = "Advanced";
|
||||||
|
|
||||||
|
// Creating a new pixel with those properties
|
||||||
|
newPixel(width, height, mode);
|
||||||
|
|
||||||
|
//track google event
|
||||||
|
ga('send', 'event', 'Pixel Editor New', selectedPalette, width+'/'+height); /*global ga*/
|
||||||
|
document.getElementById('new-pixel-warning').style.display = 'block';
|
||||||
|
|
||||||
|
// Resetting the new pixel values
|
||||||
|
selectedPalette = 'none';
|
||||||
|
|
||||||
|
//reset new pixel form
|
||||||
|
setValue('size-width-splash', 64);
|
||||||
|
setValue('size-height-splash', 64);
|
||||||
|
setValue("editor-mode", 'Advanced')
|
||||||
|
|
||||||
|
setText('editor-mode-button', 'Choose a mode...');
|
||||||
|
setText('palette-button', 'Choose a palette...');
|
||||||
|
setText('preset-button', 'Choose a preset...');
|
||||||
|
});
|
||||||
|
|
|
@ -1,5 +1,11 @@
|
||||||
|
|
||||||
function createColorPalette(paletteColors, fillBackground, deletePreviousPalette = true) {
|
/** Creates the colour palette
|
||||||
|
*
|
||||||
|
* @param {*} paletteColors The colours of the palette
|
||||||
|
* @param {*} deletePreviousPalette Tells if the app should delete the previous palette or not
|
||||||
|
* (used when opening a file, for example)
|
||||||
|
*/
|
||||||
|
function createColorPalette(paletteColors, deletePreviousPalette = true) {
|
||||||
//remove current palette
|
//remove current palette
|
||||||
if (deletePreviousPalette) {
|
if (deletePreviousPalette) {
|
||||||
colors = document.getElementsByClassName('color-button');
|
colors = document.getElementsByClassName('color-button');
|
||||||
|
@ -11,6 +17,7 @@ function createColorPalette(paletteColors, fillBackground, deletePreviousPalette
|
||||||
var lightestColor = '#000000';
|
var lightestColor = '#000000';
|
||||||
var darkestColor = '#ffffff';
|
var darkestColor = '#ffffff';
|
||||||
|
|
||||||
|
// Adding all the colours in the array
|
||||||
for (var i = 0; i < paletteColors.length; i++) {
|
for (var i = 0; i < paletteColors.length; i++) {
|
||||||
var newColor = paletteColors[i];
|
var newColor = paletteColors[i];
|
||||||
var newColorElement = addColor(newColor);
|
var newColorElement = addColor(newColor);
|
||||||
|
@ -42,6 +49,9 @@ function createColorPalette(paletteColors, fillBackground, deletePreviousPalette
|
||||||
currentLayer.context.fillStyle = darkestColor;
|
currentLayer.context.fillStyle = darkestColor;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Creates the palette with the colours used in all the layers
|
||||||
|
*
|
||||||
|
*/
|
||||||
function createPaletteFromLayers() {
|
function createPaletteFromLayers() {
|
||||||
let colors = {};
|
let colors = {};
|
||||||
|
|
||||||
|
@ -68,8 +78,6 @@ function createPaletteFromLayers() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log(colors);
|
|
||||||
|
|
||||||
//create array out of colors object
|
//create array out of colors object
|
||||||
let colorPaletteArray = [];
|
let colorPaletteArray = [];
|
||||||
for (let color in colors) {
|
for (let color in colors) {
|
||||||
|
@ -77,8 +85,7 @@ function createPaletteFromLayers() {
|
||||||
colorPaletteArray.push('#'+rgbToHex(colors[color]));
|
colorPaletteArray.push('#'+rgbToHex(colors[color]));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
console.log('COLOR PALETTE ARRAY', colorPaletteArray);
|
|
||||||
|
|
||||||
//create palette form colors array
|
//create palette from colors array
|
||||||
createColorPalette(colorPaletteArray, false);
|
createColorPalette(colorPaletteArray, true);
|
||||||
}
|
}
|
|
@ -9,19 +9,19 @@ function deleteColor (color) {
|
||||||
|
|
||||||
//if color is a string, then find the corresponding button
|
//if color is a string, then find the corresponding button
|
||||||
if (typeof color === 'string') {
|
if (typeof color === 'string') {
|
||||||
console.log('trying to find ',color);
|
//console.log('trying to find ',color);
|
||||||
//get all colors in palette
|
//get all colors in palette
|
||||||
colors = document.getElementsByClassName('color-button');
|
colors = document.getElementsByClassName('color-button');
|
||||||
|
|
||||||
//loop through colors
|
//loop through colors
|
||||||
for (var i = 0; i < colors.length; i++) {
|
for (var i = 0; i < colors.length; i++) {
|
||||||
console.log(color,'=',colors[i].jscolor.toString());
|
//console.log(color,'=',colors[i].jscolor.toString());
|
||||||
|
|
||||||
if (color == colors[i].jscolor.toString()) {
|
if (color == colors[i].jscolor.toString()) {
|
||||||
console.log('match');
|
//console.log('match');
|
||||||
//set color to the color button
|
//set color to the color button
|
||||||
color = colors[i];
|
color = colors[i];
|
||||||
console.log('found color', color);
|
//console.log('found color', color);
|
||||||
|
|
||||||
//exit loop
|
//exit loop
|
||||||
break;
|
break;
|
||||||
|
@ -30,7 +30,7 @@ function deleteColor (color) {
|
||||||
|
|
||||||
//if the color wasn't found
|
//if the color wasn't found
|
||||||
if (typeof color === 'string') {
|
if (typeof color === 'string') {
|
||||||
console.log('color not found');
|
//console.log('color not found');
|
||||||
//exit function
|
//exit function
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,27 +1,55 @@
|
||||||
|
let currentOpenDialogue = "";
|
||||||
|
|
||||||
|
/** Shows the dialogue window called dialogueName, which is a child of pop-up-container in pixel-editor.hbs
|
||||||
|
*
|
||||||
|
* @param {*} dialogueName The name of the window to show
|
||||||
|
* @param {*} trackEvent Should I track the GA event?
|
||||||
|
*/
|
||||||
function showDialogue (dialogueName, trackEvent) {
|
function showDialogue (dialogueName, trackEvent) {
|
||||||
if (typeof trackEvent === 'undefined') trackEvent = true;
|
if (typeof trackEvent === 'undefined') trackEvent = true;
|
||||||
|
|
||||||
|
// Updating currently open dialogue
|
||||||
|
currentOpenDialogue = dialogueName;
|
||||||
|
// The pop up window is open
|
||||||
dialogueOpen = true;
|
dialogueOpen = true;
|
||||||
|
// Showing the pop up container
|
||||||
popUpContainer.style.display = 'block';
|
popUpContainer.style.display = 'block';
|
||||||
|
|
||||||
|
// Showing the window
|
||||||
document.getElementById(dialogueName).style.display = 'block';
|
document.getElementById(dialogueName).style.display = 'block';
|
||||||
|
|
||||||
|
// If I'm opening the palette window, I initialize the colour picker
|
||||||
|
if (dialogueName == 'palette-block' && documentCreated) {
|
||||||
|
cpInit();
|
||||||
|
pbInit();
|
||||||
|
}
|
||||||
|
|
||||||
//track google event
|
//track google event
|
||||||
if (trackEvent)
|
if (trackEvent)
|
||||||
ga('send', 'event', 'Palette Editor Dialogue', dialogueName); /*global ga*/
|
ga('send', 'event', 'Palette Editor Dialogue', dialogueName); /*global ga*/
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Closes the current dialogue by hiding the window and the pop-up-container
|
||||||
|
*
|
||||||
|
*/
|
||||||
function closeDialogue () {
|
function closeDialogue () {
|
||||||
popUpContainer.style.display = 'none';
|
popUpContainer.style.display = 'none';
|
||||||
|
|
||||||
var popups = popUpContainer.children;
|
var popups = popUpContainer.children;
|
||||||
|
|
||||||
for (var i = 0; i < popups.length; i++) {
|
for (var i = 0; i < popups.length; i++) {
|
||||||
popups[i].style.display = 'none';
|
popups[i].style.display = 'none';
|
||||||
}
|
}
|
||||||
|
|
||||||
dialogueOpen = false;
|
dialogueOpen = false;
|
||||||
|
|
||||||
|
if (currentOpenDialogue == "palette-block") {
|
||||||
|
pbAddToSimplePalette();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Closes a dialogue window if the user clicks everywhere but in the current window
|
||||||
|
*
|
||||||
|
*/
|
||||||
popUpContainer.addEventListener('click', function (e) {
|
popUpContainer.addEventListener('click', function (e) {
|
||||||
if (e.target == popUpContainer)
|
if (e.target == popUpContainer)
|
||||||
closeDialogue();
|
closeDialogue();
|
||||||
|
@ -31,6 +59,6 @@ popUpContainer.addEventListener('click', function (e) {
|
||||||
var cancelButtons = popUpContainer.getElementsByClassName('close-button');
|
var cancelButtons = popUpContainer.getElementsByClassName('close-button');
|
||||||
for (var i = 0; i < cancelButtons.length; i++) {
|
for (var i = 0; i < cancelButtons.length; i++) {
|
||||||
cancelButtons[i].addEventListener('click', function () {
|
cancelButtons[i].addEventListener('click', function () {
|
||||||
closeDialogue();
|
closeDialogue();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
//draw a line between two points on canvas
|
//draws a line between two points on canvas
|
||||||
function line(x0,y0,x1,y1, brushSize) {
|
function line(x0,y0,x1,y1, brushSize) {
|
||||||
var dx = Math.abs(x1-x0);
|
var dx = Math.abs(x1-x0);
|
||||||
var dy = Math.abs(y1-y0);
|
var dy = Math.abs(y1-y0);
|
||||||
|
@ -9,7 +9,7 @@ function line(x0,y0,x1,y1, brushSize) {
|
||||||
while (true) {
|
while (true) {
|
||||||
//set pixel
|
//set pixel
|
||||||
// If the current tool is the brush
|
// If the current tool is the brush
|
||||||
if (currentTool.name == 'pencil' || currentTool.name == 'rectangle') {
|
if (currentTool.name == 'pencil' || currentTool.name == 'rectangle' || currentTool.name == 'ellipse') {
|
||||||
// I fill the rect
|
// I fill the rect
|
||||||
currentLayer.context.fillRect(x0-Math.floor(brushSize/2), y0-Math.floor(brushSize/2), brushSize, brushSize);
|
currentLayer.context.fillRect(x0-Math.floor(brushSize/2), y0-Math.floor(brushSize/2), brushSize, brushSize);
|
||||||
} else if (currentTool.name == 'eraser') {
|
} else if (currentTool.name == 'eraser') {
|
||||||
|
|
|
@ -3,27 +3,46 @@ let modes = {
|
||||||
description: 'Basic mode is perfect if you want to create simple sprites or try out palettes.'
|
description: 'Basic mode is perfect if you want to create simple sprites or try out palettes.'
|
||||||
},
|
},
|
||||||
'Advanced' : {
|
'Advanced' : {
|
||||||
description: 'Choose advanced mode to gain access to features such as layers.'
|
description: 'Choose advanced mode to gain access to more advanced features such as layers.'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let infoBox = document.getElementById('editor-mode-info');
|
let infoBox = document.getElementById('editor-mode-info');
|
||||||
|
let currentSplashButton = document.getElementById("sp-mode-palette").children[0].children[1];
|
||||||
|
|
||||||
function switchMode(currentMode, mustConfirm = true) {
|
function splashMode(mouseEvent, mode) {
|
||||||
|
if (currentSplashButton == undefined) {
|
||||||
|
currentSplashButton = mouseEvent.target.parentElement;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mode !== pixelEditorMode) {
|
||||||
|
// Remove selected class to old button
|
||||||
|
currentSplashButton.classList.remove("sp-interface-selected");
|
||||||
|
// Add selected class to new button
|
||||||
|
mouseEvent.target.parentElement.classList.add("sp-interface-selected");
|
||||||
|
|
||||||
|
// Setting the new mode
|
||||||
|
pixelEditorMode = mode;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Setting the new selected button
|
||||||
|
currentSplashButton = mouseEvent.target.parentElement;
|
||||||
|
}
|
||||||
|
|
||||||
|
function switchMode(mustConfirm = true) {
|
||||||
|
|
||||||
//switch to advanced mode
|
//switch to advanced mode
|
||||||
if (currentMode == 'Basic') {
|
if (pixelEditorMode == 'Basic') {
|
||||||
// Switch to advanced ez pez lemon squez
|
// Switch to advanced ez pez lemon squez
|
||||||
document.getElementById('switch-mode-button').innerHTML = 'Switch to basic mode';
|
document.getElementById('switch-mode-button').innerHTML = 'Switch to basic mode';
|
||||||
// Show the layer menus
|
// Show the layer menus
|
||||||
layerList.style.display = "inline-block";
|
layerList.style.display = "inline-block";
|
||||||
document.getElementById('layer-button').style.display = 'inline-block';
|
document.getElementById('layer-button').style.display = 'inline-block';
|
||||||
// Move the palette menu
|
// Hide the palette menu
|
||||||
document.getElementById('colors-menu').style.right = '200px';
|
document.getElementById('colors-menu').style.right = '200px'
|
||||||
|
|
||||||
pixelEditorMode = 'Advanced';
|
pixelEditorMode = 'Advanced';
|
||||||
}
|
}
|
||||||
|
|
||||||
//switch to basic mode
|
//switch to basic mode
|
||||||
else {
|
else {
|
||||||
//if there is a current layer (a document is active)
|
//if there is a current layer (a document is active)
|
||||||
|
@ -47,15 +66,17 @@ function switchMode(currentMode, mustConfirm = true) {
|
||||||
// Hide the layer menus
|
// Hide the layer menus
|
||||||
layerList.style.display = 'none';
|
layerList.style.display = 'none';
|
||||||
document.getElementById('layer-button').style.display = 'none';
|
document.getElementById('layer-button').style.display = 'none';
|
||||||
// Move the palette menu
|
// Show the palette menu
|
||||||
document.getElementById('colors-menu').style.right = '0px';
|
document.getElementById('colors-menu').style.display = 'flex';
|
||||||
|
// Move the palette menu
|
||||||
|
document.getElementById('colors-menu').style.right = '0px';
|
||||||
|
|
||||||
pixelEditorMode = 'Basic';
|
pixelEditorMode = 'Basic';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
on('click', 'switch-mode-button', function (e) {
|
on('click', 'switch-mode-button', function (e) {
|
||||||
switchMode(pixelEditorMode);
|
switchMode();
|
||||||
});
|
});
|
||||||
|
|
||||||
// Makes the menu open
|
// Makes the menu open
|
||||||
|
|
|
@ -0,0 +1,146 @@
|
||||||
|
// Saving the empty rect svg
|
||||||
|
var emptyEllipseSVG = document.getElementById("ellipse-empty-button-svg");
|
||||||
|
// and the full rect svg so that I can change them when the user changes rect modes
|
||||||
|
var fullEllipseSVG = document.getElementById("ellipse-full-button-svg");
|
||||||
|
|
||||||
|
// The start mode is empty ellipse
|
||||||
|
var ellipseDrawMode = 'empty';
|
||||||
|
// I'm not drawing a ellipse at the beginning
|
||||||
|
var isDrawingEllipse = false;
|
||||||
|
|
||||||
|
// Ellipse coordinates
|
||||||
|
let startEllipseX;
|
||||||
|
let startEllipseY;
|
||||||
|
let endEllipseX;
|
||||||
|
let endEllipseY;
|
||||||
|
|
||||||
|
// TODO: [ELLIPSE] Make it draw ellipse instead of copy-pasted rectangle
|
||||||
|
/** Starts drawing the ellipse, saves the start coordinates
|
||||||
|
*
|
||||||
|
* @param {*} mouseEvent
|
||||||
|
*/
|
||||||
|
function startEllipseDrawing(mouseEvent) {
|
||||||
|
// Putting the vfx layer on top of everything
|
||||||
|
VFXCanvas.style.zIndex = parseInt(currentLayer.canvas.style.zIndex, 10) + 1;;
|
||||||
|
// Updating flag
|
||||||
|
isDrawingEllipse = true;
|
||||||
|
|
||||||
|
// Saving the start coords of the ellipse
|
||||||
|
let cursorPos = getCursorPosition(mouseEvent);
|
||||||
|
startEllipseX = Math.floor(cursorPos[0] / zoom) + 0.5;
|
||||||
|
startEllipseY = Math.floor(cursorPos[1] / zoom) + 0.5;
|
||||||
|
|
||||||
|
drawEllipse(startEllipseX, startEllipseY);
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: [ELLIPSE] Make it draw ellipse instead of copy-pasted rectangle
|
||||||
|
/** Updates the ellipse preview depending on the position of the mouse
|
||||||
|
*
|
||||||
|
* @param {*} mouseEvent The mouseEvent from which we'll get the mouse position
|
||||||
|
*/
|
||||||
|
function updateEllipseDrawing(mouseEvent) {
|
||||||
|
let pos = getCursorPosition(mouseEvent);
|
||||||
|
|
||||||
|
// Drawing the ellipse at the right position
|
||||||
|
drawEllipse(Math.round(pos[0] / zoom) + 0.5, Math.round(pos[1] / zoom) + 0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: [ELLIPSE] Make it draw ellipse instead of copy-pasted rectangle
|
||||||
|
/** Finishes drawing the ellipse, decides the end coordinates and moves the preview ellipse to the
|
||||||
|
* current layer
|
||||||
|
*
|
||||||
|
* @param {*} mouseEvent event from which we'll get the mouse position
|
||||||
|
*/
|
||||||
|
function endEllipseDrawing(mouseEvent) {
|
||||||
|
// Getting the end position
|
||||||
|
let currentPos = getCursorPosition(mouseEvent);
|
||||||
|
let vfxContext = VFXCanvas.getContext("2d");
|
||||||
|
|
||||||
|
endEllipseX = Math.round(currentPos[0] / zoom) + 0.5;
|
||||||
|
endEllipseY = Math.round(currentPos[1] / zoom) + 0.5;
|
||||||
|
|
||||||
|
// Inverting end and start (start must always be the top left corner)
|
||||||
|
if (endEllipseX < startEllipseX) {
|
||||||
|
let tmp = endEllipseX;
|
||||||
|
endEllipseX = startEllipseX;
|
||||||
|
startEllipseX = tmp;
|
||||||
|
}
|
||||||
|
// Same for the y
|
||||||
|
if (endEllipseY < startEllipseY) {
|
||||||
|
let tmp = endEllipseY;
|
||||||
|
endEllipseY = startEllipseY;
|
||||||
|
startEllipseY = tmp;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Resetting this
|
||||||
|
isDrawingEllipse = false;
|
||||||
|
// Drawing the ellipse
|
||||||
|
startEllipseY -= 0.5;
|
||||||
|
endEllipseY -= 0.5;
|
||||||
|
endEllipseX -= 0.5;
|
||||||
|
startEllipseX -= 0.5;
|
||||||
|
|
||||||
|
// Setting the correct linewidth and colour
|
||||||
|
currentLayer.context.lineWidth = tool.ellipse.brushSize;
|
||||||
|
currentLayer.context.fillStyle = currentGlobalColor;
|
||||||
|
|
||||||
|
// Drawing the ellipse using 4 lines
|
||||||
|
line(startEllipseX, startEllipseY, endEllipseX, startEllipseY, tool.ellipse.brushSize);
|
||||||
|
line(endEllipseX, startEllipseY, endEllipseX, endEllipseY, tool.ellipse.brushSize);
|
||||||
|
line(endEllipseX, endEllipseY, startEllipseX, endEllipseY, tool.ellipse.brushSize);
|
||||||
|
line(startEllipseX, endEllipseY, startEllipseX, startEllipseY, tool.ellipse.brushSize);
|
||||||
|
|
||||||
|
// If I have to fill it, I do so
|
||||||
|
if (ellipseDrawMode == 'fill') {
|
||||||
|
currentLayer.context.fillRect(startEllipseX, startEllipseY, endEllipseX - startEllipseX, endEllipseY - startEllipseY);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clearing the vfx canvas
|
||||||
|
vfxContext.clearRect(0, 0, VFXCanvas.width, VFXCanvas.height);
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: [ELLIPSE] Make it draw ellipse instead of copy-pasted rectangle
|
||||||
|
/** Draws a ellipse with end coordinates given by x and y on the VFX layer (draws
|
||||||
|
* the preview for the ellipse tool)
|
||||||
|
*
|
||||||
|
* @param {*} x The current end x of the ellipse
|
||||||
|
* @param {*} y The current end y of the ellipse
|
||||||
|
*/
|
||||||
|
function drawEllipse(x, y) {
|
||||||
|
// Getting the vfx context
|
||||||
|
let vfxContext = VFXCanvas.getContext("2d");
|
||||||
|
|
||||||
|
// Clearing the vfx canvas
|
||||||
|
vfxContext.clearRect(0, 0, VFXCanvas.width, VFXCanvas.height);
|
||||||
|
|
||||||
|
// Drawing the ellipse
|
||||||
|
vfxContext.lineWidth = tool.ellipse.brushSize;
|
||||||
|
vfxContext.strokeStyle = currentGlobalColor;
|
||||||
|
|
||||||
|
// Drawing the ellipse
|
||||||
|
vfxContext.beginPath();
|
||||||
|
if ((tool.ellipse.brushSize % 2 ) == 0) {
|
||||||
|
vfxContext.rect(startEllipseX - 0.5, startEllipseY - 0.5, x - startEllipseX, y - startEllipseY);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
vfxContext.rect(startEllipseX, startEllipseY, x - startEllipseX, y - startEllipseY);
|
||||||
|
}
|
||||||
|
|
||||||
|
vfxContext.setLineDash([]);
|
||||||
|
vfxContext.stroke();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Sets the correct tool icon depending on its mode
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
function setEllipseToolSvg() {
|
||||||
|
console.log("set eilipse svg");
|
||||||
|
if (ellipseDrawMode == 'empty') {
|
||||||
|
emptyEllipseSVG.setAttribute('display', 'visible');
|
||||||
|
fullEllipseSVG.setAttribute('display', 'none');
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
emptyEllipseSVG.setAttribute('display', 'none');
|
||||||
|
fullEllipseSVG.setAttribute('display', 'visible');
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,33 @@
|
||||||
|
const featureToggles = (function featureTogglesModule() {
|
||||||
|
|
||||||
|
const ellipseToolLocalStorageKey = 'feature_ellipseTool';
|
||||||
|
|
||||||
|
return {
|
||||||
|
onLoad: () => {
|
||||||
|
updateEllipseToolVisibility()
|
||||||
|
},
|
||||||
|
enableEllipseTool,
|
||||||
|
disableEllipseTool
|
||||||
|
}
|
||||||
|
|
||||||
|
////////
|
||||||
|
|
||||||
|
function updateEllipseToolVisibility() {
|
||||||
|
// TODO: [ELLIPSE] Once ellipse is ready for release make it enabled by default
|
||||||
|
const isEllipseToolEnabled = (window.localStorage.getItem(ellipseToolLocalStorageKey) === "yes") || false;
|
||||||
|
const ellipseToolElement = document.getElementById("tools-menu--ellipse");
|
||||||
|
ellipseToolElement.style.display = isEllipseToolEnabled ? 'block' : 'none';
|
||||||
|
}
|
||||||
|
|
||||||
|
function enableEllipseTool() {
|
||||||
|
window.localStorage.setItem(ellipseToolLocalStorageKey, "yes");
|
||||||
|
updateEllipseToolVisibility();
|
||||||
|
}
|
||||||
|
|
||||||
|
function disableEllipseTool() {
|
||||||
|
window.localStorage.setItem(ellipseToolLocalStorageKey, "no");
|
||||||
|
updateEllipseToolVisibility();
|
||||||
|
}
|
||||||
|
|
||||||
|
})();
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
showDialogue("splash", false);
|
|
@ -7,19 +7,14 @@ for (var i = 1; i < mainMenuItems.length; i++) {
|
||||||
var menuItem = mainMenuItems[i];
|
var menuItem = mainMenuItems[i];
|
||||||
var menuButton = menuItem.children[0];
|
var menuButton = menuItem.children[0];
|
||||||
|
|
||||||
console.log(mainMenuItems);
|
|
||||||
|
|
||||||
//when you click a main menu items button
|
//when you click a main menu items button
|
||||||
on('click', menuButton, function (e, button) {
|
on('click', menuButton, function (e, button) {
|
||||||
console.log('parent ', button.parentElement);
|
|
||||||
select(button.parentElement);
|
select(button.parentElement);
|
||||||
});
|
});
|
||||||
|
|
||||||
var subMenu = menuItem.children[1];
|
var subMenu = menuItem.children[1];
|
||||||
var subMenuItems = subMenu.children;
|
var subMenuItems = subMenu.children;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
//when you click an item within a menu button
|
//when you click an item within a menu button
|
||||||
for (var j = 0; j < subMenuItems.length; j++) {
|
for (var j = 0; j < subMenuItems.length; j++) {
|
||||||
|
|
||||||
|
|
19
js/_fill.js
|
@ -7,11 +7,6 @@ function fill(cursorLocation) {
|
||||||
tempImage.data[pixelPos + 1] = fillColor.g;
|
tempImage.data[pixelPos + 1] = fillColor.g;
|
||||||
tempImage.data[pixelPos + 2] = fillColor.b;
|
tempImage.data[pixelPos + 2] = fillColor.b;
|
||||||
tempImage.data[pixelPos + 3] = 255;
|
tempImage.data[pixelPos + 3] = 255;
|
||||||
/*
|
|
||||||
tempImage.data[pixelPos] = fillColor.r;
|
|
||||||
tempImage.data[pixelPos + 1] = fillColor.g;
|
|
||||||
tempImage.data[pixelPos + 2] = fillColor.b;
|
|
||||||
*/
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//change x y to color value passed from the function and use that as the original color
|
//change x y to color value passed from the function and use that as the original color
|
||||||
|
@ -21,8 +16,9 @@ function fill(cursorLocation) {
|
||||||
var r = tempImage.data[pixelPos];
|
var r = tempImage.data[pixelPos];
|
||||||
var g = tempImage.data[pixelPos + 1];
|
var g = tempImage.data[pixelPos + 1];
|
||||||
var b = tempImage.data[pixelPos + 2];
|
var b = tempImage.data[pixelPos + 2];
|
||||||
|
var a = tempImage.data[pixelPos + 3];
|
||||||
//console.log(r == color[0] && g == color[1] && b == color[2]);
|
//console.log(r == color[0] && g == color[1] && b == color[2]);
|
||||||
return (r == color[0] && g == color[1] && b == color[2]);
|
return (r == color[0] && g == color[1] && b == color[2] && a == color[3]);
|
||||||
}
|
}
|
||||||
|
|
||||||
//save history state
|
//save history state
|
||||||
|
@ -41,16 +37,21 @@ function fill(cursorLocation) {
|
||||||
var startingPosition = (topmostPixelsArray[0][1] * canvasSize[0] + topmostPixelsArray[0][0]) * 4;
|
var startingPosition = (topmostPixelsArray[0][1] * canvasSize[0] + topmostPixelsArray[0][0]) * 4;
|
||||||
|
|
||||||
//the color of the cluster that is being filled
|
//the color of the cluster that is being filled
|
||||||
var clusterColor = [tempImage.data[startingPosition],tempImage.data[startingPosition+1],tempImage.data[startingPosition+2]];
|
var clusterColor = [tempImage.data[startingPosition],tempImage.data[startingPosition+1],tempImage.data[startingPosition+2], tempImage.data[startingPosition+3]];
|
||||||
|
|
||||||
//the new color to fill with
|
//the new color to fill with
|
||||||
var fillColor = hexToRgb(currentLayer.context.fillStyle);
|
var fillColor = hexToRgb(currentLayer.context.fillStyle);
|
||||||
|
console.log("here");
|
||||||
|
|
||||||
//if you try to fill with the same color that's already there, exit the function
|
//if you try to fill with the same color that's already there, exit the function
|
||||||
if (clusterColor[0] == fillColor.r &&
|
if (clusterColor[0] == fillColor.r &&
|
||||||
clusterColor[1] == fillColor.g &&
|
clusterColor[1] == fillColor.g &&
|
||||||
clusterColor[2] == fillColor.b )
|
clusterColor[2] == fillColor.b &&
|
||||||
return;
|
clusterColor[3] != 0) {
|
||||||
|
console.log("Returned");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
//loop until there are no more values left in this array
|
//loop until there are no more values left in this array
|
||||||
while (topmostPixelsArray.length) {
|
while (topmostPixelsArray.length) {
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
//get cursor position relative to canvas
|
//gets cursor position relative to canvas
|
||||||
function getCursorPosition(e) {
|
function getCursorPosition(e) {
|
||||||
var x;
|
var x;
|
||||||
var y;
|
var y;
|
||||||
|
|
|
@ -1,3 +1,17 @@
|
||||||
|
/** How the history works
|
||||||
|
* - undoStates stores the states that can be undone
|
||||||
|
* - redoStates stores the states that can be redone
|
||||||
|
* - undo() undoes an action and adds it to the redoStates
|
||||||
|
* - redo() redoes an action and adds it to the undoStates
|
||||||
|
* - Each HistoryState must implement an undo() and redo() function
|
||||||
|
* Those functions actually implement the undo and redo mechanism for that action,
|
||||||
|
* so you'll need to save the data you need as attributes in the constructor. For example,
|
||||||
|
* for the HistoryStateAddColour, the added colour is saved so that it can be removed in
|
||||||
|
* undo() or added back in redo().
|
||||||
|
* - Each HistoryState must call saveHistoryState(this) so that it gets added to the stack
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
var undoStates = [];
|
var undoStates = [];
|
||||||
var redoStates = [];
|
var redoStates = [];
|
||||||
|
|
||||||
|
@ -28,6 +42,8 @@ function HistoryStateResizeSprite(xRatio, yRatio, algo, oldData) {
|
||||||
};
|
};
|
||||||
|
|
||||||
this.redo = function() {
|
this.redo = function() {
|
||||||
|
console.log("REDOOOO");
|
||||||
|
console.log("Ratio: " + this.xRatio + "," + this.yRatio);
|
||||||
currentAlgo = algo;
|
currentAlgo = algo;
|
||||||
resizeSprite(null, [this.xRatio, this.yRatio]);
|
resizeSprite(null, [this.xRatio, this.yRatio]);
|
||||||
undoStates.push(this);
|
undoStates.push(this);
|
||||||
|
@ -44,7 +60,6 @@ function HistoryStateResizeCanvas(newSize, oldSize, imageDatas, trim) {
|
||||||
|
|
||||||
this.undo = function() {
|
this.undo = function() {
|
||||||
let dataIndex = 0;
|
let dataIndex = 0;
|
||||||
console.log("breakpoint");
|
|
||||||
// Resizing the canvas
|
// Resizing the canvas
|
||||||
resizeCanvas(null, oldSize, null, false);
|
resizeCanvas(null, oldSize, null, false);
|
||||||
// Putting the image datas
|
// Putting the image datas
|
||||||
|
@ -59,7 +74,6 @@ function HistoryStateResizeCanvas(newSize, oldSize, imageDatas, trim) {
|
||||||
};
|
};
|
||||||
|
|
||||||
this.redo = function() {
|
this.redo = function() {
|
||||||
console.log("trim: " + this.trim);
|
|
||||||
if (!this.trim) {
|
if (!this.trim) {
|
||||||
resizeCanvas(null, newSize, null, false);
|
resizeCanvas(null, newSize, null, false);
|
||||||
}
|
}
|
||||||
|
@ -101,7 +115,6 @@ function HistoryStateFlattenTwoVisibles(belowImageData, afterAbove, layerIndex,
|
||||||
this.belowImageData = belowImageData;
|
this.belowImageData = belowImageData;
|
||||||
|
|
||||||
this.undo = function() {
|
this.undo = function() {
|
||||||
console.log(afterAbove.menuEntry);
|
|
||||||
canvasView.append(aboveLayer.canvas);
|
canvasView.append(aboveLayer.canvas);
|
||||||
layerList.insertBefore(aboveLayer.menuEntry, afterAbove);
|
layerList.insertBefore(aboveLayer.menuEntry, afterAbove);
|
||||||
|
|
||||||
|
@ -300,7 +313,6 @@ function HistoryStateAddLayer(layerData, index) {
|
||||||
this.index = index;
|
this.index = index;
|
||||||
|
|
||||||
this.undo = function() {
|
this.undo = function() {
|
||||||
console.log("uo");
|
|
||||||
|
|
||||||
redoStates.push(this);
|
redoStates.push(this);
|
||||||
if (layers.length - nAppLayers > this.index + 1) {
|
if (layers.length - nAppLayers > this.index + 1) {
|
||||||
|
@ -473,8 +485,6 @@ function saveHistoryState (state) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function undo () {
|
function undo () {
|
||||||
//console.log('%cundo', undoLogStyle);
|
|
||||||
|
|
||||||
//if there are any states saved to undo
|
//if there are any states saved to undo
|
||||||
if (undoStates.length > 0) {
|
if (undoStates.length > 0) {
|
||||||
document.getElementById('redo-button').classList.remove('disabled');
|
document.getElementById('redo-button').classList.remove('disabled');
|
||||||
|
@ -496,8 +506,6 @@ function undo () {
|
||||||
}
|
}
|
||||||
|
|
||||||
function redo () {
|
function redo () {
|
||||||
//console.log('%credo', undoLogStyle);
|
|
||||||
|
|
||||||
if (redoStates.length > 0) {
|
if (redoStates.length > 0) {
|
||||||
|
|
||||||
//enable undo button
|
//enable undo button
|
||||||
|
|
|
@ -1,5 +1,9 @@
|
||||||
var spacePressed = false;
|
var spacePressed = false;
|
||||||
|
|
||||||
|
/** Just listens to hotkeys and calls the linked functions
|
||||||
|
*
|
||||||
|
* @param {*} e
|
||||||
|
*/
|
||||||
function KeyPress(e) {
|
function KeyPress(e) {
|
||||||
var keyboardEvent = window.event? event : e;
|
var keyboardEvent = window.event? event : e;
|
||||||
|
|
||||||
|
@ -46,6 +50,9 @@ function KeyPress(e) {
|
||||||
case 52: case 80:
|
case 52: case 80:
|
||||||
tool.pan.switchTo();
|
tool.pan.switchTo();
|
||||||
break;
|
break;
|
||||||
|
case 76:
|
||||||
|
tool.line.switchTo();
|
||||||
|
break;
|
||||||
//zoom - 5
|
//zoom - 5
|
||||||
case 53:
|
case 53:
|
||||||
tool.zoom.switchTo();
|
tool.zoom.switchTo();
|
||||||
|
@ -58,6 +65,15 @@ function KeyPress(e) {
|
||||||
case 77: case 109:
|
case 77: case 109:
|
||||||
tool.rectselect.switchTo()
|
tool.rectselect.switchTo()
|
||||||
break;
|
break;
|
||||||
|
// TODO: [ELLIPSE] Decide on a shortcut to use. "s" was chosen without any in-team consultation.
|
||||||
|
// ellipse tool, s
|
||||||
|
case 83:
|
||||||
|
tool.ellipse.switchTo()
|
||||||
|
break;
|
||||||
|
// rectangle tool, u
|
||||||
|
case 85:
|
||||||
|
tool.rectangle.switchTo()
|
||||||
|
break;
|
||||||
// Paste tool
|
// Paste tool
|
||||||
case 86: case 118:
|
case 86: case 118:
|
||||||
if (keyboardEvent.ctrlKey && !dragging) {
|
if (keyboardEvent.ctrlKey && !dragging) {
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
|
// NEXTPULL: to remove when the new palette system is added
|
||||||
|
|
||||||
//format a color button
|
|
||||||
|
//formats a color button
|
||||||
function initColor (colorElement) {
|
function initColor (colorElement) {
|
||||||
//console.log('initColor()');
|
//console.log('initColor()');
|
||||||
//console.log(document.getElementById('jscolor-hex-input'))
|
//console.log(document.getElementById('jscolor-hex-input'))
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
// NEXTPULL: to remove when the new palette system is added
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* jscolor - JavaScript Color Picker
|
* jscolor - JavaScript Color Picker
|
||||||
*
|
*
|
||||||
|
|
226
js/_layer.js
|
@ -1,31 +1,29 @@
|
||||||
/** TODO LIST FOR LAYERS
|
// HTML element that contains the layer entries
|
||||||
|
|
||||||
GENERAL REQUIREMENTS:
|
|
||||||
- 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
|
|
||||||
file for it to be opened right in the pixel editor
|
|
||||||
|
|
||||||
OPTIONAL:
|
|
||||||
|
|
||||||
1 - Fix issues
|
|
||||||
*/
|
|
||||||
|
|
||||||
let layerList;
|
let layerList;
|
||||||
|
// A single layer entry (used as a prototype to create the new ones)
|
||||||
let layerListEntry;
|
let layerListEntry;
|
||||||
|
// NEXTPULL: remove the drag n drop system and use Sortable.js instead
|
||||||
let layerDragSource = null;
|
let layerDragSource = null;
|
||||||
|
|
||||||
|
// Number of layers at the beginning
|
||||||
let layerCount = 1;
|
let layerCount = 1;
|
||||||
|
// Current max z index (so that I know which z-index to assign to new layers)
|
||||||
let maxZIndex = 3;
|
let maxZIndex = 3;
|
||||||
|
|
||||||
|
// When a layer is deleted, its id is added to this array and can be reused
|
||||||
let unusedIDs = [];
|
let unusedIDs = [];
|
||||||
|
// Id for the next added layer
|
||||||
let currentID = layerCount;
|
let currentID = layerCount;
|
||||||
let idToDelete;
|
// Layer menu
|
||||||
let layerOptions = document.getElementById("layer-properties-menu");
|
let layerOptions = document.getElementById("layer-properties-menu");
|
||||||
|
// Is the user currently renaming a layer?
|
||||||
let isRenamingLayer = false;
|
let isRenamingLayer = false;
|
||||||
|
// I need to save this, trust me
|
||||||
let oldLayerName = null;
|
let oldLayerName = null;
|
||||||
|
|
||||||
|
let dragStartLayer;
|
||||||
|
|
||||||
|
// Binding the add layer button to the function
|
||||||
on('click',"add-layer-button", addLayer, false);
|
on('click',"add-layer-button", addLayer, false);
|
||||||
|
|
||||||
/** Handler class for a single canvas (a single layer)
|
/** Handler class for a single canvas (a single layer)
|
||||||
|
@ -54,6 +52,7 @@ class Layer {
|
||||||
|
|
||||||
this.id = "layer" + id;
|
this.id = "layer" + id;
|
||||||
|
|
||||||
|
// Binding the events
|
||||||
if (menuEntry != null) {
|
if (menuEntry != null) {
|
||||||
this.name = menuEntry.getElementsByTagName("p")[0].innerHTML;
|
this.name = menuEntry.getElementsByTagName("p")[0].innerHTML;
|
||||||
menuEntry.id = "layer" + id;
|
menuEntry.id = "layer" + id;
|
||||||
|
@ -78,20 +77,13 @@ class Layer {
|
||||||
|
|
||||||
// Initializes the canvas
|
// Initializes the canvas
|
||||||
initialize() {
|
initialize() {
|
||||||
/*
|
|
||||||
var maxHorizontalZoom = Math.floor(window.innerWidth/this.canvasSize[0]*0.75);
|
|
||||||
var maxVerticalZoom = Math.floor(window.innerHeight/this.canvasSize[1]*0.75);
|
|
||||||
|
|
||||||
zoom = Math.min(maxHorizontalZoom,maxVerticalZoom);
|
|
||||||
if (zoom < 1) zoom = 1;
|
|
||||||
*/
|
|
||||||
//resize canvas
|
//resize canvas
|
||||||
this.canvas.width = this.canvasSize[0];
|
this.canvas.width = this.canvasSize[0];
|
||||||
this.canvas.height = this.canvasSize[1];
|
this.canvas.height = this.canvasSize[1];
|
||||||
this.canvas.style.width = (this.canvas.width*zoom)+'px';
|
this.canvas.style.width = (this.canvas.width*zoom)+'px';
|
||||||
this.canvas.style.height = (this.canvas.height*zoom)+'px';
|
this.canvas.style.height = (this.canvas.height*zoom)+'px';
|
||||||
|
|
||||||
//unhide canvas
|
//show canvas
|
||||||
this.canvas.style.display = 'block';
|
this.canvas.style.display = 'block';
|
||||||
|
|
||||||
//center canvas in window
|
//center canvas in window
|
||||||
|
@ -103,7 +95,7 @@ class Layer {
|
||||||
}
|
}
|
||||||
|
|
||||||
hover() {
|
hover() {
|
||||||
// Hide all the layers but the current one
|
// Hides all the layers but the current one
|
||||||
for (let i=1; i<layers.length - nAppLayers; i++) {
|
for (let i=1; i<layers.length - nAppLayers; i++) {
|
||||||
if (layers[i] !== this) {
|
if (layers[i] !== this) {
|
||||||
layers[i].canvas.style.opacity = 0.3;
|
layers[i].canvas.style.opacity = 0.3;
|
||||||
|
@ -112,7 +104,7 @@ class Layer {
|
||||||
}
|
}
|
||||||
|
|
||||||
unhover() {
|
unhover() {
|
||||||
// Show all the layers again
|
// Shows all the layers again
|
||||||
for (let i=1; i<layers.length - nAppLayers; i++) {
|
for (let i=1; i<layers.length - nAppLayers; i++) {
|
||||||
if (layers[i] !== this) {
|
if (layers[i] !== this) {
|
||||||
layers[i].canvas.style.opacity = 1;
|
layers[i].canvas.style.opacity = 1;
|
||||||
|
@ -127,52 +119,6 @@ class Layer {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
layerDragStart(element) {
|
|
||||||
layerDragSource = this;
|
|
||||||
element.dataTransfer.effectAllowed = 'move';
|
|
||||||
element.dataTransfer.setData('text/html', this.id);
|
|
||||||
|
|
||||||
this.classList.add('dragElem');
|
|
||||||
}
|
|
||||||
|
|
||||||
layerDragOver(element) {
|
|
||||||
if (element.preventDefault) {
|
|
||||||
element.preventDefault(); // Necessary. Allows us to drop.
|
|
||||||
}
|
|
||||||
this.classList.add('layerdragover');
|
|
||||||
|
|
||||||
element.dataTransfer.dropEffect = 'move';
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
layerDragLeave(element) {
|
|
||||||
this.classList.remove('layerdragover');
|
|
||||||
}
|
|
||||||
|
|
||||||
layerDragDrop(element) {
|
|
||||||
if (element.stopPropagation) {
|
|
||||||
element.stopPropagation(); // Stops some browsers from redirecting.
|
|
||||||
}
|
|
||||||
|
|
||||||
// Don't do anything if dropping the same column we're dragging.
|
|
||||||
if (layerDragSource != this) {
|
|
||||||
let toDropID = element.dataTransfer.getData('text/html');
|
|
||||||
let thisID = this.id;
|
|
||||||
|
|
||||||
moveLayers(toDropID, thisID);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.classList.remove('layerdragover');
|
|
||||||
dragging = false;
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
layerDragEnd(element) {
|
|
||||||
this.classList.remove('layerdragover');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Resizes canvas
|
// Resizes canvas
|
||||||
resize() {
|
resize() {
|
||||||
let newWidth = (this.canvas.width * zoom) + 'px';
|
let newWidth = (this.canvas.width * zoom) + 'px';
|
||||||
|
@ -217,20 +163,20 @@ class Layer {
|
||||||
|
|
||||||
openOptionsMenu(event) {
|
openOptionsMenu(event) {
|
||||||
if (event.which == 3) {
|
if (event.which == 3) {
|
||||||
|
let selectedId;
|
||||||
let target = event.target;
|
let target = event.target;
|
||||||
let offsets = getElementAbsolutePosition(this);
|
|
||||||
|
|
||||||
while (target != null && target.classList != null && !target.classList.contains("layers-menu-entry")) {
|
while (target != null && target.classList != null && !target.classList.contains("layers-menu-entry")) {
|
||||||
target = target.parentElement;
|
target = target.parentElement;
|
||||||
}
|
}
|
||||||
|
|
||||||
idToDelete = target.id;
|
selectedId = target.id;
|
||||||
|
|
||||||
layerOptions.style.visibility = "visible";
|
layerOptions.style.visibility = "visible";
|
||||||
layerOptions.style.top = "0";
|
layerOptions.style.top = "0";
|
||||||
layerOptions.style.marginTop = "" + (event.clientY - 25) + "px";
|
layerOptions.style.marginTop = "" + (event.clientY - 25) + "px";
|
||||||
|
|
||||||
getLayerByID(idToDelete).selectLayer();
|
getLayerByID(selectedId).selectLayer();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -265,10 +211,6 @@ class Layer {
|
||||||
layer.menuEntry.classList.add("selected-layer");
|
layer.menuEntry.classList.add("selected-layer");
|
||||||
currentLayer = layer;
|
currentLayer = layer;
|
||||||
}
|
}
|
||||||
/*
|
|
||||||
canvas = currentLayer.canvas;
|
|
||||||
context = currentLayer.context;
|
|
||||||
*/
|
|
||||||
}
|
}
|
||||||
|
|
||||||
toggleLock() {
|
toggleLock() {
|
||||||
|
@ -442,7 +384,6 @@ function merge(saveHistory = true) {
|
||||||
// Updating the layer preview
|
// Updating the layer preview
|
||||||
currentLayer.updateLayerPreview();
|
currentLayer.updateLayerPreview();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function deleteLayer(saveHistory = true) {
|
function deleteLayer(saveHistory = true) {
|
||||||
|
@ -456,7 +397,7 @@ function deleteLayer(saveHistory = true) {
|
||||||
unusedIDs.push(toDelete.id);
|
unusedIDs.push(toDelete.id);
|
||||||
|
|
||||||
// Selecting the next layer
|
// Selecting the next layer
|
||||||
if (layerIndex != (layers.length - 3)) {
|
if (layerIndex != (layers.length - 4)) {
|
||||||
layers[layerIndex + 1].selectLayer();
|
layers[layerIndex + 1].selectLayer();
|
||||||
}
|
}
|
||||||
// or the previous one if the next one doesn't exist
|
// or the previous one if the next one doesn't exist
|
||||||
|
@ -489,13 +430,13 @@ function duplicateLayer(event, saveHistory = true) {
|
||||||
for (let i=getMenuEntryIndex(menuEntries, toDuplicate.menuEntry) - 1; i>=0; i--) {
|
for (let i=getMenuEntryIndex(menuEntries, toDuplicate.menuEntry) - 1; i>=0; i--) {
|
||||||
getLayerByID(menuEntries[i].id).canvas.style.zIndex++;
|
getLayerByID(menuEntries[i].id).canvas.style.zIndex++;
|
||||||
}
|
}
|
||||||
maxZIndex++;
|
maxZIndex+=2;
|
||||||
|
|
||||||
// Creating a new canvas
|
// Creating a new canvas
|
||||||
let newCanvas = document.createElement("canvas");
|
let newCanvas = document.createElement("canvas");
|
||||||
// Setting up the new canvas
|
// Setting up the new canvas
|
||||||
canvasView.append(newCanvas);
|
canvasView.append(newCanvas);
|
||||||
newCanvas.style.zIndex = parseInt(currentLayer.canvas.style.zIndex) + 1;
|
newCanvas.style.zIndex = parseInt(currentLayer.canvas.style.zIndex) + 2;
|
||||||
newCanvas.classList.add("drawingCanvas");
|
newCanvas.classList.add("drawingCanvas");
|
||||||
|
|
||||||
if (!layerListEntry) return console.warn('skipping adding layer because no document');
|
if (!layerListEntry) return console.warn('skipping adding layer because no document');
|
||||||
|
@ -545,78 +486,6 @@ function renameLayer(event) {
|
||||||
isRenamingLayer = true;
|
isRenamingLayer = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Swaps two layer entries in the layer menu
|
|
||||||
function moveLayers(toDropLayer, staticLayer, saveHistory = true) {
|
|
||||||
let toDrop = getLayerByID(toDropLayer);
|
|
||||||
let staticc = getLayerByID(staticLayer);
|
|
||||||
let layerCopy = layers.slice();
|
|
||||||
|
|
||||||
let beforeToDrop = toDrop.menuEntry.nextElementSibling;
|
|
||||||
let nMoved = 0;
|
|
||||||
|
|
||||||
layerCopy.sort((a, b) => (a.canvas.style.zIndex > b.canvas.style.zIndex) ? 1 : -1);
|
|
||||||
|
|
||||||
let toDropIndex = layerCopy.indexOf(toDrop);
|
|
||||||
let staticIndex = layerCopy.indexOf(staticc);
|
|
||||||
|
|
||||||
layerList.insertBefore(toDrop.menuEntry, staticc.menuEntry);
|
|
||||||
|
|
||||||
if (toDropIndex < staticIndex) {
|
|
||||||
let tmp = toDrop.canvas.style.zIndex;
|
|
||||||
let tmp2;
|
|
||||||
|
|
||||||
for (let i=toDropIndex+1; i<=staticIndex; i++) {
|
|
||||||
tmp2 = layerCopy[i].canvas.style.zIndex;
|
|
||||||
|
|
||||||
if (saveHistory) {
|
|
||||||
new HistoryStateMoveTwoLayers(layerCopy[i], tmp2, tmp);
|
|
||||||
}
|
|
||||||
|
|
||||||
layerCopy[i].canvas.style.zIndex = tmp;
|
|
||||||
tmp = tmp2;
|
|
||||||
nMoved++;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (saveHistory) {
|
|
||||||
new HistoryStateMoveTwoLayers(toDrop, toDrop.canvas.style.zIndex, tmp);
|
|
||||||
}
|
|
||||||
|
|
||||||
toDrop.canvas.style.zIndex = tmp;
|
|
||||||
|
|
||||||
if (saveHistory) {
|
|
||||||
new HistoryStateMoveLayer(beforeToDrop, toDrop, staticc, nMoved);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
// BUG QUI
|
|
||||||
let tmp = toDrop.canvas.style.zIndex;
|
|
||||||
let tmp2;
|
|
||||||
|
|
||||||
for (let i=toDropIndex-1; i>staticIndex; i--) {
|
|
||||||
tmp2 = layerCopy[i].canvas.style.zIndex;
|
|
||||||
|
|
||||||
if (saveHistory) {
|
|
||||||
new HistoryStateMoveTwoLayers(layerCopy[i], tmp2, tmp);
|
|
||||||
}
|
|
||||||
|
|
||||||
layerCopy[i].canvas.style.zIndex = tmp;
|
|
||||||
tmp = tmp2;
|
|
||||||
nMoved++;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (saveHistory) {
|
|
||||||
new HistoryStateMoveTwoLayers(toDrop, toDrop.canvas.style.zIndex, tmp);
|
|
||||||
}
|
|
||||||
|
|
||||||
toDrop.canvas.style.zIndex = tmp;
|
|
||||||
|
|
||||||
if (saveHistory) {
|
|
||||||
new HistoryStateMoveLayer(beforeToDrop, toDrop, staticc, nMoved);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function getMenuEntryIndex(list, entry) {
|
function getMenuEntryIndex(list, entry) {
|
||||||
for (let i=0; i<list.length; i++) {
|
for (let i=0; i<list.length; i++) {
|
||||||
if (list[i] === entry) {
|
if (list[i] === entry) {
|
||||||
|
@ -660,7 +529,7 @@ function addLayer(id, saveHistory = true) {
|
||||||
let newCanvas = document.createElement("canvas");
|
let newCanvas = document.createElement("canvas");
|
||||||
// Setting up the new canvas
|
// Setting up the new canvas
|
||||||
canvasView.append(newCanvas);
|
canvasView.append(newCanvas);
|
||||||
maxZIndex++;
|
maxZIndex+=2;
|
||||||
newCanvas.style.zIndex = maxZIndex;
|
newCanvas.style.zIndex = maxZIndex;
|
||||||
newCanvas.classList.add("drawingCanvas");
|
newCanvas.classList.add("drawingCanvas");
|
||||||
|
|
||||||
|
@ -696,4 +565,49 @@ function addLayer(id, saveHistory = true) {
|
||||||
return newLayer;
|
return newLayer;
|
||||||
}
|
}
|
||||||
|
|
||||||
layerList = document.getElementById("layers-menu");
|
/** Saves the layer that is being moved when the dragging starts
|
||||||
|
*
|
||||||
|
* @param {*} event
|
||||||
|
*/
|
||||||
|
function layerDragStart(event) {
|
||||||
|
dragStartLayer = getLayerByID(layerList.children[event.oldIndex].id);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Sets the z indexes of the layers when the user drops the layer in the menu
|
||||||
|
*
|
||||||
|
* @param {*} event
|
||||||
|
*/
|
||||||
|
function layerDragDrop(event) {
|
||||||
|
let oldIndex = event.oldDraggableIndex;
|
||||||
|
let newIndex = event.newDraggableIndex;
|
||||||
|
|
||||||
|
let movedZIndex = dragStartLayer.canvas.style.zIndex;
|
||||||
|
|
||||||
|
if (oldIndex > newIndex)
|
||||||
|
{
|
||||||
|
for (let i=newIndex; i<oldIndex; i++) {
|
||||||
|
getLayerByID(layerList.children[i].id).canvas.style.zIndex = getLayerByID(layerList.children[i + 1].id).canvas.style.zIndex;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
for (let i=newIndex; i>oldIndex; i--) {
|
||||||
|
getLayerByID(layerList.children[i].id).canvas.style.zIndex = getLayerByID(layerList.children[i - 1].id).canvas.style.zIndex;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getLayerByID(layerList.children[oldIndex].id).canvas.style.zIndex = movedZIndex;
|
||||||
|
|
||||||
|
dragging = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
layerList = document.getElementById("layers-menu");
|
||||||
|
|
||||||
|
// Making the layers list sortable
|
||||||
|
new Sortable(document.getElementById("layers-menu"), {
|
||||||
|
animation: 100,
|
||||||
|
filter: ".layer-button",
|
||||||
|
draggable: ".layers-menu-entry",
|
||||||
|
onStart: layerDragStart,
|
||||||
|
onEnd: layerDragDrop
|
||||||
|
});
|
|
@ -0,0 +1,45 @@
|
||||||
|
function diagLine(lastMouseClickPos, zoom, cursorLocation) {
|
||||||
|
|
||||||
|
let x0 = Math.floor(lastMouseClickPos[0]/zoom);
|
||||||
|
let y0 = Math.floor(lastMouseClickPos[1]/zoom);
|
||||||
|
let x1 = Math.floor(cursorLocation[0]/zoom);
|
||||||
|
let y1 = Math.floor(cursorLocation[1]/zoom);
|
||||||
|
|
||||||
|
let dx = Math.abs(x1-x0);
|
||||||
|
let dy = Math.abs(y1-y0);
|
||||||
|
let sx = (x0 < x1 ? 1 : -1);
|
||||||
|
let sy = (y0 < y1 ? 1 : -1);
|
||||||
|
let err = dx-dy;
|
||||||
|
|
||||||
|
const brushSize = tool.line.brushSize;
|
||||||
|
|
||||||
|
const canvas = document.getElementById('tmp-canvas');
|
||||||
|
const context = canvas.getContext('2d');
|
||||||
|
|
||||||
|
context.fillStyle=currentGlobalColor;
|
||||||
|
context.clearRect(0, 0, canvas.width, canvas.height);
|
||||||
|
|
||||||
|
canvas.style.zIndex = parseInt(currentLayer.canvas.style.zIndex, 10) + 1;
|
||||||
|
|
||||||
|
//console.log(canvas.style.zIndex, currentLayer.canvas.style.zIndex);
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
if (currentTool.name !== 'line') return;
|
||||||
|
|
||||||
|
context.fillRect(x0-Math.floor(brushSize/2), y0-Math.floor(brushSize/2), brushSize, brushSize);
|
||||||
|
|
||||||
|
//if we've reached the end goal, exit the loop
|
||||||
|
if ((x0==x1) && (y0==y1)) break;
|
||||||
|
var e2 = 2*err;
|
||||||
|
|
||||||
|
if (e2 >-dy) {
|
||||||
|
err -=dy;
|
||||||
|
x0+=sx;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (e2 < dx) {
|
||||||
|
err +=dx;
|
||||||
|
y0+=sy;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,18 +1,27 @@
|
||||||
|
/** Loads a file (.png or .lpe)
|
||||||
|
*
|
||||||
|
*/
|
||||||
document.getElementById('open-image-browse-holder').addEventListener('change', function () {
|
document.getElementById('open-image-browse-holder').addEventListener('change', function () {
|
||||||
let fileName = document.getElementById("open-image-browse-holder").value;
|
let fileName = document.getElementById("open-image-browse-holder").value;
|
||||||
|
// Getting the extension
|
||||||
let extension = fileName.substring(fileName.lastIndexOf('.')+1, fileName.length) || fileName;
|
let extension = fileName.substring(fileName.lastIndexOf('.')+1, fileName.length) || fileName;
|
||||||
|
|
||||||
|
// I didn't write this check and I have no idea what it does
|
||||||
if (this.files && this.files[0]) {
|
if (this.files && this.files[0]) {
|
||||||
|
// Btw, checking if the extension is supported
|
||||||
if (extension == 'png' || extension == 'gif' || extension == 'lpe') {
|
if (extension == 'png' || extension == 'gif' || extension == 'lpe') {
|
||||||
|
// If it's a Lospec Pixel Editor tm file, I load the project
|
||||||
if (extension == 'lpe') {
|
if (extension == 'lpe') {
|
||||||
let file = this.files[0];
|
let file = this.files[0];
|
||||||
let reader = new FileReader();
|
let reader = new FileReader();
|
||||||
|
|
||||||
|
// Getting all the data
|
||||||
reader.readAsText(file, "UTF-8");
|
reader.readAsText(file, "UTF-8");
|
||||||
|
// Converting the data to a json object and creating a new pixel (see _newPixel.js for more)
|
||||||
reader.onload = function (e) {
|
reader.onload = function (e) {
|
||||||
let dictionary = JSON.parse(e.target.result);
|
let dictionary = JSON.parse(e.target.result);
|
||||||
|
let mode = dictionary['editorMode'] == 'Advanced' ? 'Basic' : 'Advanced';
|
||||||
newPixel(dictionary['canvasWidth'], dictionary['canvasHeight'], dictionary['editorMode'], dictionary);
|
newPixel(dictionary['canvasWidth'], dictionary['canvasHeight'], mode, dictionary);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
//this is called when a user picks a file after selecting "load palette" from the new pixel dialogue
|
//this is called when a user picks a file after selecting "load palette" from the new pixel dialogue
|
||||||
|
|
||||||
|
// TODO: load palette from .lpe file
|
||||||
document.getElementById('load-palette-browse-holder').addEventListener('change', function () {
|
document.getElementById('load-palette-browse-holder').addEventListener('change', function () {
|
||||||
if (this.files && this.files[0]) {
|
if (this.files && this.files[0]) {
|
||||||
|
|
||||||
//make sure file is allowed filetype
|
//make sure file is allowed filetype
|
||||||
var fileContentType = this.files[0].type;
|
var fileContentType = this.files[0].type;
|
||||||
if (fileContentType == 'image/png' || fileContentType == 'image/gif') {
|
if (fileContentType == 'image/png' || fileContentType == 'image/gif') {
|
||||||
|
@ -26,8 +26,6 @@ document.getElementById('load-palette-browse-holder').addEventListener('change',
|
||||||
var colorPalette = [];
|
var colorPalette = [];
|
||||||
var imagePixelData = loadPaletteContext.getImageData(0,0,this.width, this.height).data;
|
var imagePixelData = loadPaletteContext.getImageData(0,0,this.width, this.height).data;
|
||||||
|
|
||||||
console.log(imagePixelData);
|
|
||||||
|
|
||||||
//loop through pixels looking for colors to add to palette
|
//loop through pixels looking for colors to add to palette
|
||||||
for (var i = 0; i < imagePixelData.length; i += 4) {
|
for (var i = 0; i < imagePixelData.length; i += 4) {
|
||||||
var color = '#'+rgbToHex(imagePixelData[i],imagePixelData[i + 1],imagePixelData[i + 2]);
|
var color = '#'+rgbToHex(imagePixelData[i],imagePixelData[i + 1],imagePixelData[i + 2]);
|
||||||
|
@ -40,6 +38,8 @@ document.getElementById('load-palette-browse-holder').addEventListener('change',
|
||||||
palettes['Loaded palette'] = {};
|
palettes['Loaded palette'] = {};
|
||||||
palettes['Loaded palette'].colors = colorPalette;
|
palettes['Loaded palette'].colors = colorPalette;
|
||||||
setText('palette-button', 'Loaded palette');
|
setText('palette-button', 'Loaded palette');
|
||||||
|
setText('palette-button-splash', 'Loaded palette');
|
||||||
|
toggle('palette-menu-splash');
|
||||||
};
|
};
|
||||||
img.src = e.target.result;
|
img.src = e.target.result;
|
||||||
};
|
};
|
||||||
|
|
|
@ -0,0 +1,15 @@
|
||||||
|
var rawFile = new XMLHttpRequest();
|
||||||
|
rawFile.open("GET", '/pixel-editor/latestLog.html', false);
|
||||||
|
rawFile.onreadystatechange = function ()
|
||||||
|
{
|
||||||
|
if(rawFile.readyState === 4)
|
||||||
|
{
|
||||||
|
if(rawFile.status === 200 || rawFile.status == 0)
|
||||||
|
{
|
||||||
|
var allText = rawFile.responseText;
|
||||||
|
|
||||||
|
document.getElementById("latest-update").innerHTML = allText;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
rawFile.send(null);
|
|
@ -22,7 +22,7 @@ window.addEventListener("mousedown", function (mouseEvent) {
|
||||||
else if (mouseEvent.altKey)
|
else if (mouseEvent.altKey)
|
||||||
currentTool = tool.eyedropper;
|
currentTool = tool.eyedropper;
|
||||||
else if (mouseEvent.target.className == 'drawingCanvas' &&
|
else if (mouseEvent.target.className == 'drawingCanvas' &&
|
||||||
(currentTool.name == 'pencil' || currentTool.name == 'eraser' || currentTool.name == 'rectangle'))
|
(currentTool.name == 'pencil' || currentTool.name == 'eraser' || currentTool.name == 'rectangle' || currentTool.name == 'ellipse' || currentTool.name === 'line'))
|
||||||
new HistoryStateEditCanvas();
|
new HistoryStateEditCanvas();
|
||||||
else if (currentTool.name == 'moveselection') {
|
else if (currentTool.name == 'moveselection') {
|
||||||
if (!cursorInSelectedArea() &&
|
if (!cursorInSelectedArea() &&
|
||||||
|
@ -30,14 +30,15 @@ window.addEventListener("mousedown", function (mouseEvent) {
|
||||||
tool.pencil.switchTo();
|
tool.pencil.switchTo();
|
||||||
canDraw = false;
|
canDraw = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
currentTool.updateCursor();
|
|
||||||
|
|
||||||
if (!currentLayer.isLocked && !currentLayer.isVisible && canDraw) {
|
if (!currentLayer.isLocked && !currentLayer.isVisible && canDraw) {
|
||||||
draw(mouseEvent);
|
draw(mouseEvent);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else if (mouseEvent.which == 2) {
|
||||||
|
currentTool = tool.pan;
|
||||||
|
}
|
||||||
else if (currentTool.name == 'pencil' && mouseEvent.which == 3) {
|
else if (currentTool.name == 'pencil' && mouseEvent.which == 3) {
|
||||||
currentTool = tool.resizebrush;
|
currentTool = tool.resizebrush;
|
||||||
tool.pencil.previousBrushSize = tool.pencil.brushSize;
|
tool.pencil.previousBrushSize = tool.pencil.brushSize;
|
||||||
|
@ -46,10 +47,15 @@ window.addEventListener("mousedown", function (mouseEvent) {
|
||||||
currentTool = tool.resizeeraser;
|
currentTool = tool.resizeeraser;
|
||||||
tool.eraser.previousBrushSize = tool.eraser.brushSize;
|
tool.eraser.previousBrushSize = tool.eraser.brushSize;
|
||||||
}
|
}
|
||||||
|
// TODO: [ELLIPSE] Do we need similar logic related to ellipse?
|
||||||
else if (currentTool.name == 'rectangle' && mouseEvent.which == 3) {
|
else if (currentTool.name == 'rectangle' && mouseEvent.which == 3) {
|
||||||
currentTool = tool.resizerectangle;
|
currentTool = tool.resizerectangle;
|
||||||
tool.rectangle.previousBrushSize = tool.rectangle.brushSize;
|
tool.rectangle.previousBrushSize = tool.rectangle.brushSize;
|
||||||
}
|
}
|
||||||
|
else if (currentTool.name == 'line' && mouseEvent.which == 3) {
|
||||||
|
currentTool = tool.resizeline;
|
||||||
|
tool.line.previousBrushSize = tool.line.brushSize;
|
||||||
|
}
|
||||||
|
|
||||||
if (currentTool.name == 'eyedropper' && mouseEvent.target.className == 'drawingCanvas')
|
if (currentTool.name == 'eyedropper' && mouseEvent.target.className == 'drawingCanvas')
|
||||||
eyedropperPreview.style.display = 'block';
|
eyedropperPreview.style.display = 'block';
|
||||||
|
@ -70,6 +76,15 @@ window.addEventListener("mouseup", function (mouseEvent) {
|
||||||
currentLayer.closeOptionsMenu();
|
currentLayer.closeOptionsMenu();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If the user finished placing down a line, clear the tmp canvas and copy the data to the current layer
|
||||||
|
if (currentTool.name === "line") {
|
||||||
|
const tmpCanvas = document.getElementById('tmp-canvas');
|
||||||
|
currentLayer.context.drawImage(tmpCanvas, 0, 0);
|
||||||
|
|
||||||
|
const tmpContext = tmpCanvas.getContext('2d');
|
||||||
|
tmpContext.clearRect(0, 0, tmpCanvas.width, tmpCanvas.height);
|
||||||
|
}
|
||||||
|
|
||||||
if (!documentCreated || dialogueOpen || !currentLayer.isVisible || currentLayer.isLocked) return;
|
if (!documentCreated || dialogueOpen || !currentLayer.isVisible || currentLayer.isLocked) return;
|
||||||
|
|
||||||
if (currentTool.name == 'eyedropper' && mouseEvent.target.className == 'drawingCanvas') {
|
if (currentTool.name == 'eyedropper' && mouseEvent.target.className == 'drawingCanvas') {
|
||||||
|
@ -134,7 +149,7 @@ window.addEventListener("mouseup", function (mouseEvent) {
|
||||||
mode = 'out';
|
mode = 'out';
|
||||||
}
|
}
|
||||||
|
|
||||||
changeZoom(layers[0], mode, getCursorPosition(mouseEvent));
|
changeZoom(mode, getCursorPosition(mouseEvent));
|
||||||
|
|
||||||
for (let i=1; i<layers.length; i++) {
|
for (let i=1; i<layers.length; i++) {
|
||||||
layers[i].copyData(layers[0]);
|
layers[i].copyData(layers[0]);
|
||||||
|
@ -147,6 +162,10 @@ window.addEventListener("mouseup", function (mouseEvent) {
|
||||||
endRectDrawing(mouseEvent);
|
endRectDrawing(mouseEvent);
|
||||||
currentLayer.updateLayerPreview();
|
currentLayer.updateLayerPreview();
|
||||||
}
|
}
|
||||||
|
else if (currentTool.name == 'ellipse' && isDrawingEllipse) {
|
||||||
|
endEllipseDrawing(mouseEvent);
|
||||||
|
currentLayer.updateLayerPreview();
|
||||||
|
}
|
||||||
|
|
||||||
dragging = false;
|
dragging = false;
|
||||||
currentTool = currentToolTemp;
|
currentTool = currentToolTemp;
|
||||||
|
@ -156,7 +175,6 @@ window.addEventListener("mouseup", function (mouseEvent) {
|
||||||
|
|
||||||
}, false);
|
}, false);
|
||||||
|
|
||||||
// TODO: Make it snap to the pixel grid
|
|
||||||
function setPreviewPosition(preview, size){
|
function setPreviewPosition(preview, size){
|
||||||
let toAdd = 0;
|
let toAdd = 0;
|
||||||
|
|
||||||
|
@ -182,6 +200,8 @@ function setPreviewPosition(preview, size){
|
||||||
|
|
||||||
//mouse is moving on canvas
|
//mouse is moving on canvas
|
||||||
window.addEventListener("mousemove", draw, false);
|
window.addEventListener("mousemove", draw, false);
|
||||||
|
window.addEventListener("mousedown", draw, false);
|
||||||
|
|
||||||
function draw (mouseEvent) {
|
function draw (mouseEvent) {
|
||||||
if (!dialogueOpen)
|
if (!dialogueOpen)
|
||||||
{
|
{
|
||||||
|
@ -209,7 +229,12 @@ function draw (mouseEvent) {
|
||||||
//draw line to current pixel
|
//draw line to current pixel
|
||||||
if (dragging) {
|
if (dragging) {
|
||||||
if (mouseEvent.target.className == 'drawingCanvas' || mouseEvent.target.className == 'drawingCanvas') {
|
if (mouseEvent.target.className == 'drawingCanvas' || mouseEvent.target.className == 'drawingCanvas') {
|
||||||
line(Math.floor(lastMouseClickPos[0]/zoom),Math.floor(lastMouseClickPos[1]/zoom),Math.floor(cursorLocation[0]/zoom),Math.floor(cursorLocation[1]/zoom), tool.pencil.brushSize);
|
line(Math.floor(lastMouseClickPos[0]/zoom),
|
||||||
|
Math.floor(lastMouseClickPos[1]/zoom),
|
||||||
|
Math.floor(cursorLocation[0]/zoom),
|
||||||
|
Math.floor(cursorLocation[1]/zoom),
|
||||||
|
tool.pencil.brushSize
|
||||||
|
);
|
||||||
lastMouseClickPos = cursorLocation;
|
lastMouseClickPos = cursorLocation;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -257,6 +282,21 @@ function draw (mouseEvent) {
|
||||||
updateRectDrawing(mouseEvent);
|
updateRectDrawing(mouseEvent);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else if (currentTool.name == 'ellipse')
|
||||||
|
{
|
||||||
|
//hide brush preview outside of canvas / canvas view
|
||||||
|
if (mouseEvent.target.className == 'drawingCanvas'|| mouseEvent.target.className == 'drawingCanvas')
|
||||||
|
brushPreview.style.visibility = 'visible';
|
||||||
|
else
|
||||||
|
brushPreview.style.visibility = 'hidden';
|
||||||
|
|
||||||
|
if (!isDrawingEllipse && dragging) {
|
||||||
|
startEllipseDrawing(mouseEvent);
|
||||||
|
}
|
||||||
|
else if (dragging){
|
||||||
|
updateEllipseDrawing(mouseEvent);
|
||||||
|
}
|
||||||
|
}
|
||||||
else if (currentTool.name == 'pan' && dragging) {
|
else if (currentTool.name == 'pan' && dragging) {
|
||||||
// Setting first layer position
|
// Setting first layer position
|
||||||
layers[0].setCanvasOffset(layers[0].canvas.offsetLeft + (cursorLocation[0] - lastMouseClickPos[0]), layers[0].canvas.offsetTop + (cursorLocation[1] - lastMouseClickPos[1]));
|
layers[0].setCanvasOffset(layers[0].canvas.offsetLeft + (cursorLocation[0] - lastMouseClickPos[0]), layers[0].canvas.offsetTop + (cursorLocation[1] - lastMouseClickPos[1]));
|
||||||
|
@ -316,15 +356,33 @@ function draw (mouseEvent) {
|
||||||
//var roundingAmount = 20 - Math.round(distanceFromClick/10);
|
//var roundingAmount = 20 - Math.round(distanceFromClick/10);
|
||||||
//this doesnt work in reverse... because... it's not basing it off of the brush size which it should be
|
//this doesnt work in reverse... because... it's not basing it off of the brush size which it should be
|
||||||
var rectangleSizeChange = Math.round(distanceFromClick/10);
|
var rectangleSizeChange = Math.round(distanceFromClick/10);
|
||||||
|
// TODO: [ELLIPSE] Do we need similar logic related to ellipse?
|
||||||
var newRectangleSize = tool.rectangle.previousBrushSize + rectangleSizeChange;
|
var newRectangleSize = tool.rectangle.previousBrushSize + rectangleSizeChange;
|
||||||
|
|
||||||
//set the brush to the new size as long as its bigger than 1
|
//set the brush to the new size as long as its bigger than 1
|
||||||
|
// TODO: [ELLIPSE] Do we need similar logic related to ellipse?
|
||||||
tool.rectangle.brushSize = Math.max(1,newRectangleSize);
|
tool.rectangle.brushSize = Math.max(1,newRectangleSize);
|
||||||
|
|
||||||
//fix offset so the cursor stays centered
|
//fix offset so the cursor stays centered
|
||||||
|
// TODO: [ELLIPSE] Do we need similar logic related to ellipse?
|
||||||
tool.rectangle.moveBrushPreview(lastMouseClickPos);
|
tool.rectangle.moveBrushPreview(lastMouseClickPos);
|
||||||
currentTool.updateCursor();
|
currentTool.updateCursor();
|
||||||
}
|
}
|
||||||
|
else if (currentTool.name == 'resizeline' && dragging) {
|
||||||
|
//get new brush size based on x distance from original clicking location
|
||||||
|
var distanceFromClick = cursorLocation[0] - lastMouseClickPos[0];
|
||||||
|
//var roundingAmount = 20 - Math.round(distanceFromClick/10);
|
||||||
|
//this doesnt work in reverse... because... it's not basing it off of the brush size which it should be
|
||||||
|
var lineSizeChange = Math.round(distanceFromClick/10);
|
||||||
|
var newLineSize = tool.line.previousBrushSize + lineSizeChange;
|
||||||
|
|
||||||
|
//set the brush to the new size as long as its bigger than 1
|
||||||
|
tool.line.brushSize = Math.max(1, newLineSize);
|
||||||
|
|
||||||
|
//fix offset so the cursor stays centered
|
||||||
|
tool.line.moveBrushPreview(lastMouseClickPos);
|
||||||
|
currentTool.updateCursor();
|
||||||
|
}
|
||||||
else if (currentTool.name == 'rectselect') {
|
else if (currentTool.name == 'rectselect') {
|
||||||
if (dragging && !isRectSelecting && mouseEvent.target.className == 'drawingCanvas') {
|
if (dragging && !isRectSelecting && mouseEvent.target.className == 'drawingCanvas') {
|
||||||
isRectSelecting = true;
|
isRectSelecting = true;
|
||||||
|
@ -346,7 +404,27 @@ function draw (mouseEvent) {
|
||||||
updateMovePreview(getCursorPosition(mouseEvent));
|
updateMovePreview(getCursorPosition(mouseEvent));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else if (currentTool.name === "line") {
|
||||||
|
if (mouseEvent.target.className == 'drawingCanvas'|| mouseEvent.target.className == 'drawingCanvas') {
|
||||||
|
brushPreview.style.visibility = 'visible';
|
||||||
|
} else {
|
||||||
|
brushPreview.style.visibility = 'hidden';
|
||||||
|
}
|
||||||
|
if (dragging) {
|
||||||
|
if (mouseEvent.target.className == 'drawingCanvas' || mouseEvent.target.className == 'drawingCanvas') {
|
||||||
|
diagLine(lastMouseClickPos, zoom, cursorLocation);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
currentLayer.updateLayerPreview();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (mouseEvent.target.className == 'drawingCanvas')
|
||||||
|
currentTool.updateCursor();
|
||||||
|
else
|
||||||
|
canvasView.style.cursor = 'default';
|
||||||
|
|
||||||
|
console.log("Cursor: " + canvasView.style.cursor);
|
||||||
}
|
}
|
||||||
|
|
||||||
//mousewheel scroll
|
//mousewheel scroll
|
||||||
|
@ -360,7 +438,7 @@ canvasView.addEventListener("wheel", function(mouseEvent){
|
||||||
}
|
}
|
||||||
|
|
||||||
// Changing zoom and position of the first layer
|
// Changing zoom and position of the first layer
|
||||||
changeZoom(layers[0], mode, getCursorPosition(mouseEvent));
|
changeZoom(mode, getCursorPosition(mouseEvent));
|
||||||
|
|
||||||
for (let i=1; i<layers.length; i++) {
|
for (let i=1; i<layers.length; i++) {
|
||||||
// Copying first layer's data into the other layers
|
// Copying first layer's data into the other layers
|
||||||
|
|
24
js/_move.js
|
@ -6,9 +6,15 @@ var selectionCanceled = true;
|
||||||
var firstTimeMove = true;
|
var firstTimeMove = true;
|
||||||
|
|
||||||
// TODO: move with arrows
|
// TODO: move with arrows
|
||||||
|
/** Updates the move preview so that is placed in the right position
|
||||||
|
*
|
||||||
|
* @param {*} mousePosition The position of the cursor
|
||||||
|
*/
|
||||||
function updateMovePreview(mousePosition) {
|
function updateMovePreview(mousePosition) {
|
||||||
|
// I haven't canceled the selection
|
||||||
selectionCanceled = false;
|
selectionCanceled = false;
|
||||||
|
|
||||||
|
// If it's the first time that I move the selection, I cut it from its original position
|
||||||
if (firstTimeMove) {
|
if (firstTimeMove) {
|
||||||
cutSelection(mousePosition);
|
cutSelection(mousePosition);
|
||||||
}
|
}
|
||||||
|
@ -18,26 +24,34 @@ function updateMovePreview(mousePosition) {
|
||||||
lastMousePos = mousePosition;
|
lastMousePos = mousePosition;
|
||||||
// clear the entire tmp layer
|
// clear the entire tmp layer
|
||||||
TMPLayer.context.clearRect(0, 0, TMPLayer.canvas.width, TMPLayer.canvas.height);
|
TMPLayer.context.clearRect(0, 0, TMPLayer.canvas.width, TMPLayer.canvas.height);
|
||||||
// put the image data with offset
|
// put the image data on the tmp layer with offset
|
||||||
TMPLayer.context.putImageData(
|
TMPLayer.context.putImageData(
|
||||||
imageDataToMove,
|
imageDataToMove,
|
||||||
Math.round(lastMousePos[0] / zoom) - imageDataToMove.width / 2,
|
Math.round(lastMousePos[0] / zoom) - imageDataToMove.width / 2,
|
||||||
Math.round(lastMousePos[1] / zoom) - imageDataToMove.height / 2);
|
Math.round(lastMousePos[1] / zoom) - imageDataToMove.height / 2);
|
||||||
|
|
||||||
lastMovePos = lastMousePos;
|
lastMovePos = lastMousePos;
|
||||||
|
// Moving the the rectangular ants
|
||||||
moveSelection(lastMousePos[0] / zoom, lastMousePos[1] / zoom, imageDataToMove.width, imageDataToMove.height);
|
moveSelection(lastMousePos[0] / zoom, lastMousePos[1] / zoom, imageDataToMove.width, imageDataToMove.height);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Ends a selection, meaning that it makes the changes definitive and creates the history states
|
||||||
|
*
|
||||||
|
*/
|
||||||
function endSelection() {
|
function endSelection() {
|
||||||
|
// Clearing the tmp (move preview) and vfx (ants) layers
|
||||||
TMPLayer.context.clearRect(0, 0, TMPLayer.canvas.width, TMPLayer.canvas.height);
|
TMPLayer.context.clearRect(0, 0, TMPLayer.canvas.width, TMPLayer.canvas.height);
|
||||||
VFXLayer.context.clearRect(0, 0, VFXLayer.canvas.width, VFXLayer.canvas.height);
|
VFXLayer.context.clearRect(0, 0, VFXLayer.canvas.width, VFXLayer.canvas.height);
|
||||||
|
// Preparing an empty imageData with the size of the canvas
|
||||||
let cleanImageData = new ImageData(endX - startX, endY - startY);
|
let cleanImageData = new ImageData(endX - startX, endY - startY);
|
||||||
|
|
||||||
|
// If I was moving something
|
||||||
if (imageDataToMove !== undefined) {
|
if (imageDataToMove !== undefined) {
|
||||||
console.log("definito");
|
|
||||||
// Saving the current clipboard before editing it in order to merge it with the current layer
|
// Saving the current clipboard before editing it in order to merge it with the current layer
|
||||||
cleanImageData.data.set(imageDataToMove.data);
|
cleanImageData.data.set(imageDataToMove.data);
|
||||||
|
|
||||||
|
// I have to save the underlying data, so that the transparent pixels in the clipboard
|
||||||
|
// don't override the coloured pixels in the canvas
|
||||||
let underlyingImageData = currentLayer.context.getImageData(startX, startY, endX - startX, endY - startY);
|
let underlyingImageData = currentLayer.context.getImageData(startX, startY, endX - startX, endY - startY);
|
||||||
|
|
||||||
for (let i=0; i<underlyingImageData.data.length; i+=4) {
|
for (let i=0; i<underlyingImageData.data.length; i+=4) {
|
||||||
|
@ -51,6 +65,7 @@ function endSelection() {
|
||||||
underlyingImageData.data[i+2], underlyingImageData.data[i+3]
|
underlyingImageData.data[i+2], underlyingImageData.data[i+3]
|
||||||
];
|
];
|
||||||
|
|
||||||
|
// If the pixel of the clipboard is empty, but the one below it isn't, I use the pixel below
|
||||||
if (isPixelEmpty(currentMovePixel)) {
|
if (isPixelEmpty(currentMovePixel)) {
|
||||||
if (!isPixelEmpty(underlyingImageData)) {
|
if (!isPixelEmpty(underlyingImageData)) {
|
||||||
imageDataToMove.data[i] = currentUnderlyingPixel[0];
|
imageDataToMove.data[i] = currentUnderlyingPixel[0];
|
||||||
|
@ -61,13 +76,16 @@ function endSelection() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If I moved the selection before confirming it
|
||||||
if (lastMovePos !== undefined) {
|
if (lastMovePos !== undefined) {
|
||||||
|
// I put it in the new position
|
||||||
currentLayer.context.putImageData(
|
currentLayer.context.putImageData(
|
||||||
imageDataToMove,
|
imageDataToMove,
|
||||||
Math.round(lastMovePos[0] / zoom) - imageDataToMove.width / 2,
|
Math.round(lastMovePos[0] / zoom) - imageDataToMove.width / 2,
|
||||||
Math.round(lastMovePos[1] / zoom) - imageDataToMove.height / 2);
|
Math.round(lastMovePos[1] / zoom) - imageDataToMove.height / 2);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
// I put it in the same position
|
||||||
currentLayer.context.putImageData(
|
currentLayer.context.putImageData(
|
||||||
imageDataToMove,
|
imageDataToMove,
|
||||||
copiedStartX,
|
copiedStartX,
|
||||||
|
@ -77,6 +95,7 @@ function endSelection() {
|
||||||
imageDataToMove.data.set(cleanImageData.data);
|
imageDataToMove.data.set(cleanImageData.data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Resetting all the flags
|
||||||
selectionCanceled = true;
|
selectionCanceled = true;
|
||||||
isRectSelecting = false;
|
isRectSelecting = false;
|
||||||
firstTimeMove = true;
|
firstTimeMove = true;
|
||||||
|
@ -86,5 +105,6 @@ function endSelection() {
|
||||||
lastMovePos = undefined;
|
lastMovePos = undefined;
|
||||||
currentLayer.updateLayerPreview();
|
currentLayer.updateLayerPreview();
|
||||||
|
|
||||||
|
// Saving the history
|
||||||
new HistoryStateEditCanvas();
|
new HistoryStateEditCanvas();
|
||||||
}
|
}
|
|
@ -1,16 +1,32 @@
|
||||||
let firstPixel = true;
|
let firstPixel = true;
|
||||||
|
|
||||||
|
/** Creates a new, empty file
|
||||||
|
*
|
||||||
|
* @param {*} width Start width of the canvas
|
||||||
|
* @param {*} height Start height of the canvas
|
||||||
|
* @param {*} editorMode The editor mode chosen by the user (advanced or basic)
|
||||||
|
* @param {*} fileContent If fileContent != null, then the newPixel is being called from the open menu
|
||||||
|
*/
|
||||||
function newPixel (width, height, editorMode, fileContent = null) {
|
function newPixel (width, height, editorMode, fileContent = null) {
|
||||||
|
// Saving the editor mode
|
||||||
pixelEditorMode = editorMode;
|
pixelEditorMode = editorMode;
|
||||||
|
|
||||||
|
// The palette is empty, at the beginning
|
||||||
currentPalette = [];
|
currentPalette = [];
|
||||||
|
|
||||||
|
// If this is the first pixel I'm creating since the app has started
|
||||||
if (firstPixel) {
|
if (firstPixel) {
|
||||||
|
// I configure the layers elements
|
||||||
layerListEntry = layerList.firstElementChild;
|
layerListEntry = layerList.firstElementChild;
|
||||||
|
|
||||||
|
// Creating the first layer
|
||||||
currentLayer = new Layer(width, height, canvas, layerListEntry);
|
currentLayer = new Layer(width, height, canvas, layerListEntry);
|
||||||
currentLayer.canvas.style.zIndex = 2;
|
currentLayer.canvas.style.zIndex = 2;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
// If it's not the first Pixel, I have to reset the app
|
||||||
|
|
||||||
|
// Deleting all the extra layers and canvases, leaving only one
|
||||||
let nLayers = layers.length;
|
let nLayers = layers.length;
|
||||||
for (let i=2; i < layers.length - nAppLayers; i++) {
|
for (let i=2; i < layers.length - nAppLayers; i++) {
|
||||||
let currentEntry = layers[i].menuEntry;
|
let currentEntry = layers[i].menuEntry;
|
||||||
|
@ -38,10 +54,9 @@ function newPixel (width, height, editorMode, fileContent = null) {
|
||||||
// Setting up the current layer
|
// Setting up the current layer
|
||||||
layers[1] = new Layer(width, height, layers[1].canvas, layers[1].menuEntry);
|
layers[1] = new Layer(width, height, layers[1].canvas, layers[1].menuEntry);
|
||||||
currentLayer = layers[1];
|
currentLayer = layers[1];
|
||||||
|
|
||||||
currentLayer.canvas.style.zIndex = 2;
|
currentLayer.canvas.style.zIndex = 2;
|
||||||
|
|
||||||
// Updating canvas size
|
// Updating canvas size to the new size
|
||||||
for (let i=0; i<nLayers; i++) {
|
for (let i=0; i<nLayers; i++) {
|
||||||
layers[i].canvasSize = [width, height];
|
layers[i].canvasSize = [width, height];
|
||||||
}
|
}
|
||||||
|
@ -58,7 +73,7 @@ function newPixel (width, height, editorMode, fileContent = null) {
|
||||||
|
|
||||||
// Pixel grid
|
// Pixel grid
|
||||||
pixelGrid = new Layer(width, height, pixelGridCanvas);
|
pixelGrid = new Layer(width, height, pixelGridCanvas);
|
||||||
|
// Setting the general canvasSize
|
||||||
canvasSize = currentLayer.canvasSize;
|
canvasSize = currentLayer.canvasSize;
|
||||||
|
|
||||||
if (firstPixel) {
|
if (firstPixel) {
|
||||||
|
@ -80,16 +95,23 @@ function newPixel (width, height, editorMode, fileContent = null) {
|
||||||
}
|
}
|
||||||
|
|
||||||
//add colors from selected palette
|
//add colors from selected palette
|
||||||
var selectedPalette = getText('palette-button');
|
var selectedPalette;
|
||||||
|
if (!firstPixel)
|
||||||
|
var selectedPalette = getText('palette-button');
|
||||||
|
else
|
||||||
|
var selectedPalette = getText('palette-button-splash');
|
||||||
|
|
||||||
|
// If the user selected a palette and isn't opening a file, I load the selected palette
|
||||||
if (selectedPalette != 'Choose a palette...' && fileContent == null) {
|
if (selectedPalette != 'Choose a palette...' && fileContent == null) {
|
||||||
|
|
||||||
//if this palette isnt the one specified in the url, then reset the url
|
//if this palette isnt the one specified in the url, then reset the url
|
||||||
if (!palettes[selectedPalette].specified)
|
if (!palettes[selectedPalette].specified)
|
||||||
history.pushState(null, null, '/pixel-editor/app');
|
history.pushState(null, null, '/pixel-editor/app');
|
||||||
|
|
||||||
//fill the palette with specified palette
|
//fill the palette with specified colours
|
||||||
createColorPalette(palettes[selectedPalette].colors,true);
|
createColorPalette(palettes[selectedPalette].colors,true);
|
||||||
}
|
}
|
||||||
|
// Otherwise, I just generate 2 semirandom colours
|
||||||
else if (fileContent == null) {
|
else if (fileContent == null) {
|
||||||
//this wasn't a specified palette, so reset the url
|
//this wasn't a specified palette, so reset the url
|
||||||
history.pushState(null, null, '/pixel-editor/app');
|
history.pushState(null, null, '/pixel-editor/app');
|
||||||
|
@ -120,15 +142,21 @@ function newPixel (width, height, editorMode, fileContent = null) {
|
||||||
undoStates = [];
|
undoStates = [];
|
||||||
redoStates = [];
|
redoStates = [];
|
||||||
|
|
||||||
|
// Closing the "New Pixel dialogue"
|
||||||
closeDialogue();
|
closeDialogue();
|
||||||
|
// Updating the cursor of the current tool
|
||||||
currentTool.updateCursor();
|
currentTool.updateCursor();
|
||||||
|
|
||||||
|
// The user is now able to export the Pixel
|
||||||
document.getElementById('export-button').classList.remove('disabled');
|
document.getElementById('export-button').classList.remove('disabled');
|
||||||
documentCreated = true;
|
documentCreated = true;
|
||||||
|
|
||||||
|
// This is not the first Pixel anymore
|
||||||
firstPixel = false;
|
firstPixel = false;
|
||||||
|
|
||||||
|
// Now, if I opened an LPE file
|
||||||
if (fileContent != null) {
|
if (fileContent != null) {
|
||||||
|
// I add every layer the file had in it
|
||||||
for (let i=0; i<fileContent['nLayers']; i++) {
|
for (let i=0; i<fileContent['nLayers']; i++) {
|
||||||
let layerData = fileContent['layer' + i];
|
let layerData = fileContent['layer' + i];
|
||||||
let layerImage = fileContent['layer' + i + 'ImageData'];
|
let layerImage = fileContent['layer' + i + 'ImageData'];
|
||||||
|
@ -166,10 +194,27 @@ function newPixel (width, height, editorMode, fileContent = null) {
|
||||||
deleteLayer(false);
|
deleteLayer(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Applying the correct editor mode
|
||||||
if (pixelEditorMode == 'Basic') {
|
if (pixelEditorMode == 'Basic') {
|
||||||
switchMode('Advanced', false);
|
switchMode(false);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
switchMode('Basic', false);
|
switchMode(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Resetting history
|
||||||
|
undoStates = [];
|
||||||
|
redoStates = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
function newFromTemplate(preset, x, y) {
|
||||||
|
if (preset != '') {
|
||||||
|
const presetProperties = PresetModule.propertiesOf(preset);
|
||||||
|
Util.setText('palette-button-splash', presetProperties.palette);
|
||||||
|
Util.setText('palette-button', presetProperties.palette);
|
||||||
|
|
||||||
|
x = presetProperties.width;
|
||||||
|
y = presetProperties.height;
|
||||||
|
}
|
||||||
|
newPixel(x, y, pixelEditorMode == 'Advanced' ? 'Basic' : 'Advanced');
|
||||||
}
|
}
|
|
@ -1,12 +1,17 @@
|
||||||
//when the page is donw loading, you can get ready to start
|
//when the page is done loading, you can get ready to start
|
||||||
window.onload = function(){
|
window.onload = function () {
|
||||||
currentTool.updateCursor();
|
|
||||||
|
|
||||||
//if the user specified dimentions
|
featureToggles.onLoad();
|
||||||
if (specifiedDimentions)
|
|
||||||
//create a new pixel
|
currentTool.updateCursor();
|
||||||
newPixel(getValue('size-width'),getValue('size-height'), getValue('editor-mode'));
|
|
||||||
else
|
//if the user specified dimensions
|
||||||
//otherwise show the new pixel dialog
|
if (specifiedDimentions) {
|
||||||
showDialogue('new-pixel', false);
|
//create a new pixel
|
||||||
};
|
newPixel(getValue('size-width'), getValue('size-height'), getValue('editor-mode'));
|
||||||
|
} else {
|
||||||
|
//otherwise show the new pixel dialog
|
||||||
|
showDialogue('splash', false);
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
|
@ -0,0 +1,333 @@
|
||||||
|
/** INIT is called when it shouldn't **/
|
||||||
|
|
||||||
|
let coloursList = document.getElementById("palette-list");
|
||||||
|
|
||||||
|
let rampMenu = document.getElementById("pb-ramp-options");
|
||||||
|
let pbRampDialogue = document.getElementById("pb-ramp-dialogue");
|
||||||
|
|
||||||
|
let currentSquareSize = coloursList.children[0].clientWidth;
|
||||||
|
let blockData = {blockWidth: 300, blockHeight: 320, squareSize: 40};
|
||||||
|
let isRampSelecting = false;
|
||||||
|
let ramps = [];
|
||||||
|
let currentSelection = {startIndex:0, endIndex:0, startCoords:[], endCoords: [], name: "", colour: "", label: null};
|
||||||
|
|
||||||
|
// Making the palette list sortable
|
||||||
|
new Sortable(document.getElementById("palette-list"), {
|
||||||
|
animation: 100,
|
||||||
|
onEnd: updateRampSelection
|
||||||
|
});
|
||||||
|
|
||||||
|
// Listening for the palette block resize
|
||||||
|
new ResizeObserver(updateSizeData).observe(coloursList.parentElement);
|
||||||
|
|
||||||
|
// Initializes the palette block
|
||||||
|
function pbInit() {
|
||||||
|
let simplePalette = document.getElementById("colors-menu");
|
||||||
|
let childCount = coloursList.childElementCount;
|
||||||
|
|
||||||
|
currentSquareSize = coloursList.children[0].clientWidth;
|
||||||
|
coloursList = document.getElementById("palette-list");
|
||||||
|
|
||||||
|
// Remove all the colours
|
||||||
|
for (let i=0; i<childCount; i++) {
|
||||||
|
coloursList.children[0].remove();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add all the colours from the simplepalette
|
||||||
|
for (let i=0; i<simplePalette.childElementCount-1; i++) {
|
||||||
|
addSingleColour(cssToHex(simplePalette.children[i].children[0].style.backgroundColor));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Listens for the mouse wheel, used to change the size of the squares in the palette list
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
coloursList.parentElement.addEventListener("wheel", function (mouseEvent) {
|
||||||
|
// Only resize when pressing alt, used to distinguish between scrolling through the palette and
|
||||||
|
// resizing it
|
||||||
|
if (mouseEvent.altKey) {
|
||||||
|
resizeSquares(mouseEvent);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
/** Tells whether a colour is in the palette or not
|
||||||
|
*
|
||||||
|
* @param {*} colour The colour to add
|
||||||
|
*/
|
||||||
|
function hasColour(colour) {
|
||||||
|
for (let i=0; i<coloursList.childElementCount; i++) {
|
||||||
|
let currentCol = coloursList.children[i].style.backgroundColor;
|
||||||
|
let currentHex = cssToHex(currentCol);
|
||||||
|
|
||||||
|
if (currentHex == colour) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Adds a single colour to the palette
|
||||||
|
*
|
||||||
|
* @param {*} colour The colour to add
|
||||||
|
*/
|
||||||
|
function addSingleColour(colour) {
|
||||||
|
if (!hasColour(colour)) {
|
||||||
|
let li = document.createElement("li");
|
||||||
|
|
||||||
|
li.style.width = currentSquareSize + "px";
|
||||||
|
li.style.height = currentSquareSize + "px";
|
||||||
|
li.style.backgroundColor = colour;
|
||||||
|
li.addEventListener("mousedown", startRampSelection);
|
||||||
|
li.addEventListener("mouseup", endRampSelection);
|
||||||
|
li.addEventListener("mousemove", updateRampSelection);
|
||||||
|
li.addEventListener("onclick", endRampSelection);
|
||||||
|
|
||||||
|
coloursList.appendChild(li);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/** Adds all the colours currently selected in the colour picker
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
function pbAddColours() {
|
||||||
|
let colours = getSelectedColours();
|
||||||
|
|
||||||
|
for (let i=0; i<colours.length; i++) {
|
||||||
|
addSingleColour(colours[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Removes all the currently selected colours from the palette
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
function pbRemoveColours() {
|
||||||
|
let startIndex = currentSelection.startIndex;
|
||||||
|
let endIndex = currentSelection.endIndex;
|
||||||
|
|
||||||
|
if (startIndex > endIndex) {
|
||||||
|
let tmp = startIndex;
|
||||||
|
startIndex = endIndex;
|
||||||
|
endIndex = tmp;
|
||||||
|
}
|
||||||
|
|
||||||
|
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.
|
||||||
|
*
|
||||||
|
* @param {*} mouseEvent
|
||||||
|
*/
|
||||||
|
function startRampSelection(mouseEvent) {
|
||||||
|
if (mouseEvent.which == 3) {
|
||||||
|
let index = getElementIndex(mouseEvent.target);
|
||||||
|
|
||||||
|
isRampSelecting = true;
|
||||||
|
|
||||||
|
currentSelection.startIndex = index;
|
||||||
|
currentSelection.endIndex = index;
|
||||||
|
|
||||||
|
currentSelection.startCoords = getColourCoordinates(index);
|
||||||
|
currentSelection.endCoords = getColourCoordinates(index);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Updates the outline for the current selection.
|
||||||
|
*
|
||||||
|
* @param {*} mouseEvent
|
||||||
|
*/
|
||||||
|
function updateRampSelection(mouseEvent) {
|
||||||
|
if (mouseEvent != null && mouseEvent.which == 3) {
|
||||||
|
currentSelection.endIndex = getElementIndex(mouseEvent.target);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mouseEvent == null || mouseEvent.which == 3) {
|
||||||
|
let startCoords = getColourCoordinates(currentSelection.startIndex);
|
||||||
|
let endCoords = getColourCoordinates(currentSelection.endIndex);
|
||||||
|
|
||||||
|
let startIndex = currentSelection.startIndex;
|
||||||
|
let endIndex = currentSelection.endIndex;
|
||||||
|
|
||||||
|
if (currentSelection.startIndex > endIndex) {
|
||||||
|
let tmp = startIndex;
|
||||||
|
startIndex = endIndex;
|
||||||
|
endIndex = tmp;
|
||||||
|
|
||||||
|
tmp = startCoords;
|
||||||
|
startCoords = endCoords;
|
||||||
|
endCoords = tmp;
|
||||||
|
}
|
||||||
|
|
||||||
|
clearBorders();
|
||||||
|
|
||||||
|
for (let i=startIndex; i<=endIndex; i++) {
|
||||||
|
let currentSquare = coloursList.children[i];
|
||||||
|
let currentCoords = getColourCoordinates(i);
|
||||||
|
let borderStyle = "3px solid white";
|
||||||
|
let bordersToSet = [];
|
||||||
|
|
||||||
|
// Deciding which borders to use to make the outline
|
||||||
|
if (i == 0 || i == startIndex) {
|
||||||
|
bordersToSet.push("border-left");
|
||||||
|
}
|
||||||
|
if (currentCoords[1] == startCoords[1] || ((currentCoords[1] == startCoords[1] + 1)) && currentCoords[0] < startCoords[0]) {
|
||||||
|
bordersToSet.push("border-top");
|
||||||
|
}
|
||||||
|
if (currentCoords[1] == endCoords[1] || ((currentCoords[1] == endCoords[1] - 1)) && currentCoords[0] > endCoords[0]) {
|
||||||
|
bordersToSet.push("border-bottom");
|
||||||
|
}
|
||||||
|
if ((i == coloursList.childElementCount - 1) || (currentCoords[0] == Math.floor(blockData.blockWidth / blockData.squareSize) - 1)
|
||||||
|
|| i == endIndex) {
|
||||||
|
bordersToSet.push("border-right");
|
||||||
|
}
|
||||||
|
if (bordersToSet != []) {
|
||||||
|
currentSquare.style["box-sizing"] = "border-box";
|
||||||
|
|
||||||
|
for (let i=0; i<bordersToSet.length; i++) {
|
||||||
|
currentSquare.style[bordersToSet[i]] = borderStyle;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Removes all the borders from all the squares. The borders are cleared only for the
|
||||||
|
* current selection, so every border that is not white is kept.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
function clearBorders() {
|
||||||
|
for (let i=0; i<coloursList.childElementCount; i++) {
|
||||||
|
coloursList.children[i].style["border-top"] = "none";
|
||||||
|
coloursList.children[i].style["border-left"] = "none";
|
||||||
|
coloursList.children[i].style["border-right"] = "none";
|
||||||
|
coloursList.children[i].style["border-bottom"] = "none";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Ends the current selection, opens the ramp menu
|
||||||
|
*
|
||||||
|
* @param {*} mouseEvent
|
||||||
|
*/
|
||||||
|
function endRampSelection(mouseEvent) {
|
||||||
|
let col;
|
||||||
|
|
||||||
|
if (currentSelection.startCoords.length == 0) {
|
||||||
|
currentSelection.endIndex = getElementIndex(mouseEvent.target);
|
||||||
|
currentSelection.startIndex = currentSelection.endIndex;
|
||||||
|
currentSelection.startCoords = getColourCoordinates(currentSelection.startIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
// I'm not selecting a ramp anymore
|
||||||
|
isRampSelecting = false;
|
||||||
|
// Setting the end coordinates
|
||||||
|
currentSelection.endCoords = getColourCoordinates(getElementIndex(mouseEvent.target));
|
||||||
|
|
||||||
|
// Setting the colour in the colour picker
|
||||||
|
col = cssToHex(coloursList.children[currentSelection.startIndex].style.backgroundColor);
|
||||||
|
updatePickerByHex(col);
|
||||||
|
updateSlidersByHex(col);
|
||||||
|
updateMiniPickerColour();
|
||||||
|
|
||||||
|
updateRampSelection();
|
||||||
|
|
||||||
|
currentSelection.startCoords = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
function closeAllSubmenus() {
|
||||||
|
let menus = document.getElementsByClassName("pb-submenu");
|
||||||
|
|
||||||
|
for (let i=0; i<menus.length; i++) {
|
||||||
|
menus[i].style.display = "none";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Updates the current data about the size of the palette list (height, width and square size).
|
||||||
|
* It also updates the outline after doing so.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
function updateSizeData() {
|
||||||
|
blockData.blockHeight = coloursList.parentElement.clientHeight;
|
||||||
|
blockData.blockWidth = coloursList.parentElement.clientWidth;
|
||||||
|
blockData.squareSize = coloursList.children[0].clientWidth;
|
||||||
|
|
||||||
|
updateRampSelection();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Gets the colour coordinates relative to the colour list seen as a matrix. Coordinates
|
||||||
|
* start from the top left angle.
|
||||||
|
*
|
||||||
|
* @param {*} index The index of the colour in the list seen as a linear array
|
||||||
|
*/
|
||||||
|
function getColourCoordinates(index) {
|
||||||
|
let yIndex = Math.floor(index / Math.floor(blockData.blockWidth / blockData.squareSize));
|
||||||
|
let xIndex = Math.floor(index % Math.floor(blockData.blockWidth / blockData.squareSize));
|
||||||
|
|
||||||
|
return [xIndex, yIndex];
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Returns the index of the element in the colour list
|
||||||
|
*
|
||||||
|
* @param {*} element The element of which we need to get the index
|
||||||
|
*/
|
||||||
|
function getElementIndex(element) {
|
||||||
|
for (let i=0; i<coloursList.childElementCount; i++) {
|
||||||
|
if (element == coloursList.children[i]) {
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Resizes the squares depending on the scroll amount (only resizes if the user is
|
||||||
|
* also holding alt)
|
||||||
|
*
|
||||||
|
* @param {*} mouseEvent
|
||||||
|
*/
|
||||||
|
function resizeSquares(mouseEvent) {
|
||||||
|
let amount = mouseEvent.deltaY > 0 ? -5 : 5;
|
||||||
|
currentSquareSize += amount;
|
||||||
|
|
||||||
|
for (let i=0; i<coloursList.childElementCount; i++) {
|
||||||
|
let currLi = coloursList.children[i];
|
||||||
|
|
||||||
|
currLi.style["box-sizing"] = "content-box";
|
||||||
|
currLi.style.width = currLi.clientWidth + amount + "px";
|
||||||
|
currLi.style.height = currLi.clientHeight + amount + "px";
|
||||||
|
}
|
||||||
|
|
||||||
|
updateSizeData();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Converts a CSS colour eg rgb(x,y,z) to a hex string
|
||||||
|
*
|
||||||
|
* @param {*} rgb
|
||||||
|
*/
|
||||||
|
function cssToHex(rgb) {
|
||||||
|
rgb = rgb.match(/^rgb\((\d+),\s*(\d+),\s*(\d+)\)$/);
|
||||||
|
function hex(x) {
|
||||||
|
return ("0" + parseInt(x).toString(16)).slice(-2);
|
||||||
|
}
|
||||||
|
return "#" + hex(rgb[1]) + hex(rgb[2]) + hex(rgb[3]);
|
||||||
|
}
|
||||||
|
|
||||||
|
function pbAddToSimplePalette() {
|
||||||
|
let simplePalette = document.getElementById("colors-menu");
|
||||||
|
let childCount = simplePalette.childElementCount;
|
||||||
|
|
||||||
|
// Removing all the colours
|
||||||
|
for (let i=0; i<childCount-1; i++) {
|
||||||
|
simplePalette.removeChild(simplePalette.children[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Adding the new ones
|
||||||
|
for (let i=0; i<coloursList.childElementCount; i++) {
|
||||||
|
let col = coloursList.children[i].style.backgroundColor;
|
||||||
|
|
||||||
|
if (col.includes("rgb")) {
|
||||||
|
addColor(cssToHex(col));
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
addColor(col);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
123
js/_palettes.js
|
@ -1,62 +1,91 @@
|
||||||
//populate palettes list in new pixel menu
|
//populate palettes list in new pixel menu
|
||||||
Object.keys(palettes).forEach(function(paletteName,index) {
|
(() => {
|
||||||
|
const palettesMenu = document.getElementById('palette-menu');
|
||||||
|
const splashPalettes = document.getElementById('palette-menu-splash');
|
||||||
|
const noPaletteButton = document.getElementById('no-palette-button');
|
||||||
|
const newPixelElement = document.getElementById('new-pixel');
|
||||||
|
const paletteButton = document.getElementById('palette-button');
|
||||||
|
const paletteButtonSplash = document.getElementById('palette-button-splash');
|
||||||
|
const loadPaletteButton = document.getElementById('load-palette-button');
|
||||||
|
const loadPaletteButtonSplash = document.getElementById('load-palette-button-splash');
|
||||||
|
|
||||||
var palettesMenu = document.getElementById('palette-menu');
|
Object.keys(palettes).forEach((paletteName,) => {
|
||||||
|
|
||||||
//create button
|
const button = document.createElement('button');
|
||||||
var button = document.createElement('button');
|
button.appendChild(document.createTextNode(paletteName));
|
||||||
button.appendChild(document.createTextNode(paletteName));
|
|
||||||
|
|
||||||
//insert new element
|
//if the palette was specified by the user, change the dropdown to it
|
||||||
palettesMenu.appendChild(button);
|
if (palettes[paletteName].specified) {
|
||||||
|
Util.setText('palette-button', paletteName);
|
||||||
|
Util.setText('palette-button-splash', paletteName)
|
||||||
|
//Show empty palette option
|
||||||
|
noPaletteButton.style.display = 'block';
|
||||||
|
}
|
||||||
|
|
||||||
//if the palette was specified by the user, change the dropdown to it
|
const buttonEvent = () => {
|
||||||
if (palettes[paletteName].specified == true) {
|
//hide the dropdown menu
|
||||||
setText('palette-button', paletteName);
|
Util.deselect('palette-menu');
|
||||||
//Show empty palette option
|
Util.deselect('palette-button');
|
||||||
document.getElementById('no-palette-button').style.display = 'block';
|
Util.deselect('palette-menu-splash');
|
||||||
}
|
Util.deselect('palette-button-splash');
|
||||||
|
|
||||||
on('click', button, function() {
|
//show empty palette option
|
||||||
|
noPaletteButton.style.display = 'block';
|
||||||
|
|
||||||
//hide the dropdown menu
|
//set the text of the dropdown to the newly selected preset
|
||||||
deselect('palette-menu');
|
Util.setText('palette-button', paletteName);
|
||||||
deselect('palette-button');
|
Util.setText('palette-button-splash', paletteName);
|
||||||
|
}
|
||||||
|
|
||||||
//show empty palette option
|
// Making a copy for the splash page too
|
||||||
document.getElementById('no-palette-button').style.display = 'block';
|
const copyButton = button.cloneNode(true);
|
||||||
|
copyButton.addEventListener('click', buttonEvent);
|
||||||
|
button.addEventListener('click', buttonEvent);
|
||||||
|
|
||||||
//set the text of the dropdown to the newly selected preset
|
// Appending it to the splash palette menu
|
||||||
setText('palette-button', paletteName);
|
splashPalettes.appendChild(copyButton);
|
||||||
|
palettesMenu.appendChild(button);
|
||||||
});
|
});
|
||||||
});
|
|
||||||
|
|
||||||
//select no palette
|
|
||||||
on('click', 'no-palette-button', function () {
|
|
||||||
document.getElementById('no-palette-button').style.display = 'none';
|
|
||||||
setText('palette-button', 'Choose a palette...');
|
|
||||||
});
|
|
||||||
|
|
||||||
//select load palette
|
|
||||||
on('click', 'load-palette-button', function () {
|
|
||||||
document.getElementById('load-palette-browse-holder').click();
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
|
const loadPaletteButtonEvent = () => {
|
||||||
|
document.getElementById('load-palette-browse-holder').click();
|
||||||
|
}
|
||||||
|
const clickPaletteButtonEvent = (e) => {
|
||||||
|
Util.toggle('palette-button');
|
||||||
|
Util.toggle('palette-menu');
|
||||||
|
|
||||||
on('click', 'palette-button', function (e){
|
Util.deselect('preset-button');
|
||||||
toggle('palette-button');
|
Util.deselect('preset-menu');
|
||||||
toggle('palette-menu');
|
|
||||||
|
|
||||||
deselect('preset-button');
|
// Splash version
|
||||||
deselect('preset-menu');
|
Util.toggle('palette-button-splash');
|
||||||
e.stopPropagation();
|
Util.toggle('palette-menu-splash');
|
||||||
});
|
|
||||||
|
|
||||||
on('click', 'new-pixel', function (){
|
e.stopPropagation();
|
||||||
deselect('editor-mode-menu');
|
}
|
||||||
deselect('preset-button');
|
// Load Palettes
|
||||||
deselect('preset-menu');
|
loadPaletteButton.addEventListener('click', loadPaletteButtonEvent);
|
||||||
deselect('palette-button');
|
loadPaletteButtonSplash.addEventListener('click', loadPaletteButtonEvent);
|
||||||
deselect('palette-menu');
|
|
||||||
});
|
// Palette menu click
|
||||||
|
paletteButtonSplash.addEventListener('click', clickPaletteButtonEvent);
|
||||||
|
paletteButton.addEventListener('click', clickPaletteButtonEvent);
|
||||||
|
|
||||||
|
noPaletteButton.addEventListener('click', () => {
|
||||||
|
noPaletteButton.style.display = 'none';
|
||||||
|
Util.setText('palette-button', 'Choose a palette...');
|
||||||
|
})
|
||||||
|
|
||||||
|
newPixelElement.addEventListener('click', () => {
|
||||||
|
Util.deselect('editor-mode-menu');
|
||||||
|
Util.deselect('preset-button');
|
||||||
|
Util.deselect('preset-menu');
|
||||||
|
Util.deselect('palette-button');
|
||||||
|
Util.deselect('palette-menu');
|
||||||
|
|
||||||
|
// Splash version
|
||||||
|
Util.deselect('palette-button-splash');
|
||||||
|
Util.deselect('palette-menu-splash');
|
||||||
|
})
|
||||||
|
})();
|
|
@ -1,3 +1,10 @@
|
||||||
|
/***********MISCELLANEOUS UTILITY FUNCTIONS**************/
|
||||||
|
|
||||||
|
/** Merges topLayer onto belowLayer
|
||||||
|
*
|
||||||
|
* @param {*} belowLayer The layer on the bottom of the layer stack
|
||||||
|
* @param {*} topLayer The layer on the top of the layer stack
|
||||||
|
*/
|
||||||
function mergeLayers(belowLayer, topLayer) {
|
function mergeLayers(belowLayer, topLayer) {
|
||||||
// Copying the above content on the layerBelow
|
// Copying the above content on the layerBelow
|
||||||
let belowImageData = belowLayer.getImageData(0, 0, canvas.width, canvas.height);
|
let belowImageData = belowLayer.getImageData(0, 0, canvas.width, canvas.height);
|
||||||
|
@ -24,10 +31,19 @@ function mergeLayers(belowLayer, topLayer) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Putting the top data into the belowdata
|
||||||
belowLayer.putImageData(toMergeImageData, 0, 0);
|
belowLayer.putImageData(toMergeImageData, 0, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Used to programmatically create an input event
|
||||||
|
*
|
||||||
|
* @param {*} keyCode KeyCode of the key to press
|
||||||
|
* @param {*} ctrl Is ctrl pressed?
|
||||||
|
* @param {*} alt Is alt pressed?
|
||||||
|
* @param {*} shift Is shift pressed?
|
||||||
|
*/
|
||||||
function simulateInput(keyCode, ctrl, alt, shift) {
|
function simulateInput(keyCode, ctrl, alt, shift) {
|
||||||
|
// I just copy pasted this from stack overflow lol please have mercy
|
||||||
let keyboardEvent = document.createEvent("KeyboardEvent");
|
let keyboardEvent = document.createEvent("KeyboardEvent");
|
||||||
let initMethod = typeof keyboardEvent.initKeyboardEvent !== 'undefined' ? "initKeyboardEvent" : "initKeyEvent";
|
let initMethod = typeof keyboardEvent.initKeyboardEvent !== 'undefined' ? "initKeyboardEvent" : "initKeyEvent";
|
||||||
|
|
||||||
|
@ -46,38 +62,62 @@ function simulateInput(keyCode, ctrl, alt, shift) {
|
||||||
document.dispatchEvent(keyboardEvent);
|
document.dispatchEvent(keyboardEvent);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Tells if a pixel is empty (has alpha = 0)
|
||||||
|
*
|
||||||
|
* @param {*} pixel
|
||||||
|
*/
|
||||||
function isPixelEmpty(pixel) {
|
function isPixelEmpty(pixel) {
|
||||||
if (pixel == null || pixel === undefined) {
|
if (pixel == null || pixel === undefined) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((pixel[0] == 0 && pixel[1] == 0 && pixel[2] == 0) || pixel[3] == 0) {
|
// If the alpha channel is 0, the current pixel is empty
|
||||||
|
if (pixel[3] == 0) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Tells if element is a child of an element with class className
|
||||||
|
*
|
||||||
|
* @param {*} element
|
||||||
|
* @param {*} className
|
||||||
|
*/
|
||||||
function isChildOfByClass(element, className) {
|
function isChildOfByClass(element, className) {
|
||||||
|
// Getting the element with class className
|
||||||
while (element != null && element.classList != null && !element.classList.contains(className)) {
|
while (element != null && element.classList != null && !element.classList.contains(className)) {
|
||||||
element = element.parentElement;
|
element = element.parentElement;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If that element exists and its class is the correct one
|
||||||
if (element != null && element.classList != null && element.classList.contains(className)) {
|
if (element != null && element.classList != null && element.classList.contains(className)) {
|
||||||
|
// Then element is a chld of an element with class className
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Gets the eyedropped colour (the colour of the pixel pointed by the cursor when the user is using the eyedropper).
|
||||||
|
* It takes the colour of the canvas with the biggest z-index, basically the one the user can see, since it doesn't
|
||||||
|
* make much sense to sample a colour which is hidden behind a different layer
|
||||||
|
*
|
||||||
|
* @param {*} cursorLocation The position of the cursor
|
||||||
|
*/
|
||||||
function getEyedropperColor(cursorLocation) {
|
function getEyedropperColor(cursorLocation) {
|
||||||
|
// Making sure max will take some kind of value
|
||||||
let max = -1;
|
let max = -1;
|
||||||
|
// Using tmpColour to sample the sprite
|
||||||
let tmpColour;
|
let tmpColour;
|
||||||
|
// Returned colour
|
||||||
let selectedColor;
|
let selectedColor;
|
||||||
|
|
||||||
for (let i=1; i<layers.length; i++) {
|
for (let i=1; i<layers.length; i++) {
|
||||||
|
// Getting the colour of the pixel in the cursorLocation
|
||||||
tmpColour = layers[i].context.getImageData(Math.floor(cursorLocation[0]/zoom),Math.floor(cursorLocation[1]/zoom),1,1).data;
|
tmpColour = layers[i].context.getImageData(Math.floor(cursorLocation[0]/zoom),Math.floor(cursorLocation[1]/zoom),1,1).data;
|
||||||
|
|
||||||
|
// If it's not empty, I check if it's on the top of the previous colour
|
||||||
if (layers[i].canvas.style.zIndex > max || isPixelEmpty(selectedColor) || selectedColor === undefined) {
|
if (layers[i].canvas.style.zIndex > max || isPixelEmpty(selectedColor) || selectedColor === undefined) {
|
||||||
max = layers[i].canvas.style.zIndex;
|
max = layers[i].canvas.style.zIndex;
|
||||||
|
|
||||||
|
@ -87,6 +127,7 @@ function getEyedropperColor(cursorLocation) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If the final colour was empty, I return black
|
||||||
if (isPixelEmpty(tmpColour) && selectedColor === undefined) {
|
if (isPixelEmpty(tmpColour) && selectedColor === undefined) {
|
||||||
selectedColor = [0, 0, 0];
|
selectedColor = [0, 0, 0];
|
||||||
}
|
}
|
||||||
|
@ -94,7 +135,12 @@ function getEyedropperColor(cursorLocation) {
|
||||||
return selectedColor;
|
return selectedColor;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Gets the absolute position of the element (position on the screen)
|
||||||
|
*
|
||||||
|
* @param {*} element The element of which we have to get the position
|
||||||
|
*/
|
||||||
function getElementAbsolutePosition(element) {
|
function getElementAbsolutePosition(element) {
|
||||||
|
// Probably copy pasted this from stack overflow too, if not I don't recall how it works
|
||||||
let curleft = curtop = 0;
|
let curleft = curtop = 0;
|
||||||
|
|
||||||
if (element.offsetParent) {
|
if (element.offsetParent) {
|
||||||
|
@ -107,9 +153,15 @@ function getElementAbsolutePosition(element) {
|
||||||
return [curleft,curtop];
|
return [curleft,curtop];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Nearest neighbor algorithm to scale a sprite
|
||||||
|
*
|
||||||
|
* @param {*} src The source imageData
|
||||||
|
* @param {*} dst The destination imageData
|
||||||
|
*/
|
||||||
function nearestNeighbor (src, dst) {
|
function nearestNeighbor (src, dst) {
|
||||||
let pos = 0
|
let pos = 0
|
||||||
|
|
||||||
|
// Just applying the nearest neighbor algorithm
|
||||||
for (let y = 0; y < dst.height; y++) {
|
for (let y = 0; y < dst.height; y++) {
|
||||||
for (let x = 0; x < dst.width; x++) {
|
for (let x = 0; x < dst.width; x++) {
|
||||||
const srcX = Math.floor(x * src.width / dst.width)
|
const srcX = Math.floor(x * src.width / dst.width)
|
||||||
|
@ -125,7 +177,14 @@ function nearestNeighbor (src, dst) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Bilinear interpolation used to scale a sprite
|
||||||
|
*
|
||||||
|
* @param {*} src The source imageData
|
||||||
|
* @param {*} dst The destination imageData
|
||||||
|
*/
|
||||||
function bilinearInterpolation (src, dst) {
|
function bilinearInterpolation (src, dst) {
|
||||||
|
// Applying the bilinear interpolation algorithm
|
||||||
|
|
||||||
function interpolate (k, kMin, kMax, vMin, vMax) {
|
function interpolate (k, kMin, kMax, vMin, vMax) {
|
||||||
return Math.round((k - kMin) * vMax + (kMax - k) * vMin)
|
return Math.round((k - kMin) * vMax + (kMax - k) * vMin)
|
||||||
}
|
}
|
||||||
|
@ -167,14 +226,21 @@ function bilinearInterpolation (src, dst) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Resizes an imageData depending on the algorithm and on the new width and height
|
||||||
|
*
|
||||||
|
* @param {*} image The imageData to scale
|
||||||
|
* @param {*} width The new width of the imageData
|
||||||
|
* @param {*} height The new height of the imageData
|
||||||
|
* @param {*} algorithm Scaling algorithm chosen by the user in the dialogue
|
||||||
|
*/
|
||||||
function resizeImageData (image, width, height, algorithm) {
|
function resizeImageData (image, width, height, algorithm) {
|
||||||
algorithm = algorithm || 'bilinear-interpolation'
|
algorithm = algorithm || 'bilinear-interpolation'
|
||||||
|
|
||||||
let resize
|
let resize;
|
||||||
switch (algorithm) {
|
switch (algorithm) {
|
||||||
case 'nearest-neighbor': resize = nearestNeighbor; break
|
case 'nearest-neighbor': resize = nearestNeighbor; break
|
||||||
case 'bilinear-interpolation': resize = bilinearInterpolation; break
|
case 'bilinear-interpolation': resize = bilinearInterpolation; break
|
||||||
default: throw new Error(`Unknown algorithm: ${algorithm}`)
|
default: return image;
|
||||||
}
|
}
|
||||||
|
|
||||||
const result = new ImageData(width, height)
|
const result = new ImageData(width, height)
|
||||||
|
@ -184,10 +250,21 @@ function resizeImageData (image, width, height, algorithm) {
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Gets the position in (x, y) format of the pixel with index "index"
|
||||||
|
*
|
||||||
|
* @param {*} index The index of the pixel of which we need the (x, y) position
|
||||||
|
*/
|
||||||
function getPixelPosition(index) {
|
function getPixelPosition(index) {
|
||||||
let linearIndex = index / 4;
|
let linearIndex = index / 4;
|
||||||
let x = linearIndex % layers[0].canvasSize[0];
|
let x = linearIndex % layers[0].canvasSize[0];
|
||||||
let y = Math.floor(linearIndex / layers[0].canvasSize[0]);
|
let y = Math.floor(linearIndex / layers[0].canvasSize[0]);
|
||||||
|
|
||||||
return [Math.ceil(x), Math.ceil(y)];
|
return [Math.ceil(x), Math.ceil(y)];
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Sets isDragging to false, used when the user interacts with sortable lists
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
function makeIsDraggingFalse(event) {
|
||||||
|
dragging = false;
|
||||||
}
|
}
|
|
@ -1,27 +1,44 @@
|
||||||
|
// Start colour of the pixel grid (can be changed in the preferences)
|
||||||
let pixelGridColor = "#0000FF";
|
let pixelGridColor = "#0000FF";
|
||||||
|
// Distance between one line and another in HTML pixels
|
||||||
let lineDistance = 12;
|
let lineDistance = 12;
|
||||||
|
// The grid is not visible at the beginning
|
||||||
let pixelGridVisible = false;
|
let pixelGridVisible = false;
|
||||||
|
// Saving the canvas containing the pixel grid
|
||||||
pixelGridCanvas = document.getElementById("pixel-grid");
|
pixelGridCanvas = document.getElementById("pixel-grid");
|
||||||
|
|
||||||
|
/** Shows or hides the pixel grid depening on its current visibility
|
||||||
|
* (triggered by the show pixel grid button in the top menu)
|
||||||
|
*
|
||||||
|
*/
|
||||||
function togglePixelGrid(event) {
|
function togglePixelGrid(event) {
|
||||||
|
// Getting the button because I have to change its text
|
||||||
let button = document.getElementById("toggle-pixelgrid-button");
|
let button = document.getElementById("toggle-pixelgrid-button");
|
||||||
|
|
||||||
|
// Toggling the state
|
||||||
pixelGridVisible = !pixelGridVisible;
|
pixelGridVisible = !pixelGridVisible;
|
||||||
|
|
||||||
|
// If it was visible, I hide it
|
||||||
if (pixelGridVisible) {
|
if (pixelGridVisible) {
|
||||||
button.innerHTML = "Hide pixel grid";
|
button.innerHTML = "Hide pixel grid";
|
||||||
pixelGridCanvas.style.display = "inline-block";
|
pixelGridCanvas.style.display = "inline-block";
|
||||||
}
|
}
|
||||||
|
// Otherwise, I show it
|
||||||
else {
|
else {
|
||||||
button.innerHTML = "Show pixel grid";
|
button.innerHTML = "Show pixel grid";
|
||||||
pixelGridCanvas.style.display = "none";
|
pixelGridCanvas.style.display = "none";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Fills the pixelGridCanvas with the pixelgrid
|
||||||
|
*
|
||||||
|
*/
|
||||||
function fillPixelGrid() {
|
function fillPixelGrid() {
|
||||||
let context = pixelGridCanvas.getContext("2d");
|
let context = pixelGridCanvas.getContext("2d");
|
||||||
let originalSize = layers[0].canvasSize;
|
let originalSize = layers[0].canvasSize;
|
||||||
|
|
||||||
|
// The pixelGridCanvas is lineDistance times bigger so that the lines don't take 1 canvas pixel
|
||||||
|
// (which would cover the whole canvas with the pixelGridColour), but they take 1/lineDistance canvas pixels
|
||||||
pixelGridCanvas.width = originalSize[0] * lineDistance;
|
pixelGridCanvas.width = originalSize[0] * lineDistance;
|
||||||
pixelGridCanvas.height = originalSize[1] * lineDistance;
|
pixelGridCanvas.height = originalSize[1] * lineDistance;
|
||||||
|
|
||||||
|
|
117
js/_presets.js
|
@ -1,63 +1,60 @@
|
||||||
//prests
|
const PresetModule = (() => {
|
||||||
var presets = {
|
const presets = {
|
||||||
'Gameboy Color': {
|
'Gameboy Color': {width: 240, height: 203, palette: 'Gameboy Color'},
|
||||||
width: 240,
|
'PICO-8': {width: 128, height: 128, palette: 'PICO-8'},
|
||||||
height: 203,
|
'Commodore 64': {width: 40, height: 80, palette: 'Commodore 64'}
|
||||||
palette: 'Gameboy Color'
|
};
|
||||||
},
|
|
||||||
'PICO-8': {
|
function instrumentPresetMenu() {
|
||||||
width: 128,
|
console.info("Initializing presets..");
|
||||||
height: 128,
|
// Add a button for all the presets available
|
||||||
palette: 'PICO-8'
|
const presetsMenu = document.getElementById('preset-menu');
|
||||||
},
|
Object.keys(presets).forEach((presetName,) => {
|
||||||
'Commodore 64': {
|
|
||||||
width: 40,
|
const button = document.createElement('button');
|
||||||
height: 80,
|
button.appendChild(document.createTextNode(presetName));
|
||||||
palette: 'Commodore 64'
|
|
||||||
|
presetsMenu.appendChild(button);
|
||||||
|
|
||||||
|
button.addEventListener('click', () => {
|
||||||
|
//change dimentions on new pixel form
|
||||||
|
Util.setValue('size-width', presets[presetName].width);
|
||||||
|
Util.setValue('size-height', presets[presetName].height);
|
||||||
|
|
||||||
|
//set the text of the dropdown to the newly selected preset
|
||||||
|
Util.setText('palette-button', presets[presetName].palette);
|
||||||
|
|
||||||
|
//hide the dropdown menu
|
||||||
|
Util.deselect('preset-menu');
|
||||||
|
Util.deselect('preset-button');
|
||||||
|
|
||||||
|
//set the text of the dropdown to the newly selected preset
|
||||||
|
Util.setText('preset-button', presetName);
|
||||||
|
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
const presetButton = document.getElementById('preset-button');
|
||||||
|
presetButton.addEventListener('click', (e) => {
|
||||||
|
//open or close the preset menu
|
||||||
|
Util.toggle('preset-button');
|
||||||
|
Util.toggle('preset-menu');
|
||||||
|
|
||||||
|
//close the palette menu
|
||||||
|
Util.deselect('palette-button');
|
||||||
|
Util.deselect('palette-menu');
|
||||||
|
|
||||||
|
e.stopPropagation();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
};
|
|
||||||
|
|
||||||
//populate preset list in new pixel menu
|
function propertiesOf(presetId) {
|
||||||
Object.keys(presets).forEach(function(presetName,index) {
|
return presets[presetId];
|
||||||
|
}
|
||||||
|
|
||||||
var presetsMenu = document.getElementById('preset-menu');
|
return {
|
||||||
|
propertiesOf,
|
||||||
//create button
|
instrumentPresetMenu
|
||||||
var button = document.createElement('button');
|
};
|
||||||
button.appendChild(document.createTextNode(presetName));
|
|
||||||
|
})();
|
||||||
//insert new element
|
|
||||||
presetsMenu.appendChild(button);
|
|
||||||
|
|
||||||
//add click event listener
|
|
||||||
on('click', button, function() {
|
|
||||||
|
|
||||||
//change dimentions on new pixel form
|
|
||||||
setValue('size-width', presets[presetName].width);
|
|
||||||
setValue('size-height', presets[presetName].height);
|
|
||||||
|
|
||||||
//set the text of the dropdown to the newly selected preset
|
|
||||||
setText('palette-button', presets[presetName].palette);
|
|
||||||
|
|
||||||
//hide the dropdown menu
|
|
||||||
deselect('preset-menu');
|
|
||||||
deselect('preset-button');
|
|
||||||
|
|
||||||
//set the text of the dropdown to the newly selected preset
|
|
||||||
setText('preset-button', presetName);
|
|
||||||
});
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
on('click', 'preset-button', function (e){
|
|
||||||
//open or close the preset menu
|
|
||||||
toggle('preset-button');
|
|
||||||
toggle('preset-menu');
|
|
||||||
|
|
||||||
//close the palette menu
|
|
||||||
deselect('palette-button');
|
|
||||||
deselect('palette-menu');
|
|
||||||
|
|
||||||
//stop the click from propogating to the parent element
|
|
||||||
e.stopPropagation();
|
|
||||||
});
|
|
|
@ -4,6 +4,10 @@ let startY;
|
||||||
let endX;
|
let endX;
|
||||||
let endY;
|
let endY;
|
||||||
|
|
||||||
|
/** Starts the selection: saves the canvas, sets the start coordinates
|
||||||
|
*
|
||||||
|
* @param {*} mouseEvent
|
||||||
|
*/
|
||||||
function startRectSelection(mouseEvent) {
|
function startRectSelection(mouseEvent) {
|
||||||
// Saving the canvas
|
// Saving the canvas
|
||||||
new HistoryStateEditCanvas();
|
new HistoryStateEditCanvas();
|
||||||
|
@ -35,6 +39,10 @@ function startRectSelection(mouseEvent) {
|
||||||
selectionCanceled = false;
|
selectionCanceled = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Updates the selection
|
||||||
|
*
|
||||||
|
* @param {*} mouseEvent
|
||||||
|
*/
|
||||||
function updateRectSelection(mouseEvent) {
|
function updateRectSelection(mouseEvent) {
|
||||||
let pos = getCursorPosition(mouseEvent);
|
let pos = getCursorPosition(mouseEvent);
|
||||||
|
|
||||||
|
@ -42,6 +50,10 @@ function updateRectSelection(mouseEvent) {
|
||||||
drawRect(Math.round(pos[0] / zoom) + 0.5, Math.round(pos[1] / zoom) + 0.5);
|
drawRect(Math.round(pos[0] / zoom) + 0.5, Math.round(pos[1] / zoom) + 0.5);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Ends the selection: sets the end coordiantes
|
||||||
|
*
|
||||||
|
* @param {*} mouseEvent
|
||||||
|
*/
|
||||||
function endRectSelection(mouseEvent) {
|
function endRectSelection(mouseEvent) {
|
||||||
// Getting the end position
|
// Getting the end position
|
||||||
let currentPos = getCursorPosition(mouseEvent);
|
let currentPos = getCursorPosition(mouseEvent);
|
||||||
|
@ -72,6 +84,10 @@ function endRectSelection(mouseEvent) {
|
||||||
currentTool.updateCursor();
|
currentTool.updateCursor();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Cuts the selection from its canvas and puts it in the tmp layer so it can be moved
|
||||||
|
*
|
||||||
|
* @param {*} mousePosition
|
||||||
|
*/
|
||||||
function cutSelection(mousePosition) {
|
function cutSelection(mousePosition) {
|
||||||
// Getting the selected pixels
|
// Getting the selected pixels
|
||||||
imageDataToMove = currentLayer.context.getImageData(startX, startY, endX - startX + 1, endY - startY + 1);
|
imageDataToMove = currentLayer.context.getImageData(startX, startY, endX - startX + 1, endY - startY + 1);
|
||||||
|
@ -79,10 +95,13 @@ function cutSelection(mousePosition) {
|
||||||
currentLayer.context.clearRect(startX - 0.5, startY - 0.5, endX - startX + 1, endY - startY + 1);
|
currentLayer.context.clearRect(startX - 0.5, startY - 0.5, endX - startX + 1, endY - startY + 1);
|
||||||
// Moving those pixels from the current layer to the tmp layer
|
// Moving those pixels from the current layer to the tmp layer
|
||||||
TMPLayer.context.putImageData(imageDataToMove, startX + 1, startY);
|
TMPLayer.context.putImageData(imageDataToMove, startX + 1, startY);
|
||||||
|
|
||||||
//originalDataPosition = [currentPos[0], currentPos[1]];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Draws a dashed rectangle representing the selection
|
||||||
|
*
|
||||||
|
* @param {*} x Current end x coordinate of the selection
|
||||||
|
* @param {*} y Current end y coordinate of the selection
|
||||||
|
*/
|
||||||
function drawRect(x, y) {
|
function drawRect(x, y) {
|
||||||
// Getting the vfx context
|
// Getting the vfx context
|
||||||
let vfxContext = VFXCanvas.getContext('2d');
|
let vfxContext = VFXCanvas.getContext('2d');
|
||||||
|
@ -106,10 +125,13 @@ function applyChanges() {
|
||||||
|
|
||||||
// Checks whether the pointer is inside the selected area or not
|
// Checks whether the pointer is inside the selected area or not
|
||||||
function cursorInSelectedArea() {
|
function cursorInSelectedArea() {
|
||||||
|
// Getting the cursor position
|
||||||
let cursorPos = getCursorPosition(currentMouseEvent);
|
let cursorPos = getCursorPosition(currentMouseEvent);
|
||||||
|
// Getting the coordinates relatively to the canvas
|
||||||
let x = cursorPos[0] / zoom;
|
let x = cursorPos[0] / zoom;
|
||||||
let y = cursorPos[1] / zoom;
|
let y = cursorPos[1] / zoom;
|
||||||
|
|
||||||
|
// This is to avoid rightX or topY being less than leftX or bottomY
|
||||||
let leftX = Math.min(startX, endX);
|
let leftX = Math.min(startX, endX);
|
||||||
let rightX = Math.max(startX, endX);
|
let rightX = Math.max(startX, endX);
|
||||||
let topY = Math.max(startY, endY);
|
let topY = Math.max(startY, endY);
|
||||||
|
@ -126,6 +148,13 @@ function cursorInSelectedArea() {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Moves the rect ants to the specified position
|
||||||
|
*
|
||||||
|
* @param {*} x X coordinate of the rect ants
|
||||||
|
* @param {*} y Y coordinat of the rect ants
|
||||||
|
* @param {*} width Width of the selection
|
||||||
|
* @param {*} height Height of the selectin
|
||||||
|
*/
|
||||||
function moveSelection(x, y, width, height) {
|
function moveSelection(x, y, width, height) {
|
||||||
// Getting the vfx context
|
// Getting the vfx context
|
||||||
let vfxContext = VFXCanvas.getContext('2d');
|
let vfxContext = VFXCanvas.getContext('2d');
|
||||||
|
@ -135,6 +164,7 @@ function moveSelection(x, y, width, height) {
|
||||||
vfxContext.lineWidth = 1;
|
vfxContext.lineWidth = 1;
|
||||||
vfxContext.setLineDash([4]);
|
vfxContext.setLineDash([4]);
|
||||||
|
|
||||||
|
// Fixing the coordinates
|
||||||
startX = Math.round(Math.round(x) - 0.5 - Math.round(width / 2)) + 0.5;
|
startX = Math.round(Math.round(x) - 0.5 - Math.round(width / 2)) + 0.5;
|
||||||
startY = Math.round(Math.round(y) - 0.5 - Math.round(height / 2)) + 0.5;
|
startY = Math.round(Math.round(y) - 0.5 - Math.round(height / 2)) + 0.5;
|
||||||
endX = startX + Math.round(width);
|
endX = startX + Math.round(width);
|
||||||
|
|
|
@ -1,19 +1,26 @@
|
||||||
|
// Saving the empty rect svg
|
||||||
|
var emptyRectangleSVG = document.getElementById("rectangle-empty-button-svg");
|
||||||
|
// and the full rect svg so that I can change them when the user changes rect modes
|
||||||
|
var fullRectangleSVG = document.getElementById("rectangle-full-button-svg");
|
||||||
|
|
||||||
var emptySVG = document.getElementById("empty-button-svg");
|
// The start mode is empty rectangle
|
||||||
var fullSVG = document.getElementById("full-button-svg");
|
var rectangleDrawMode = 'empty';
|
||||||
|
// I'm not drawing a rectangle at the beginning
|
||||||
var drawMode = 'empty';
|
|
||||||
var isDrawingRect = false;
|
var isDrawingRect = false;
|
||||||
|
|
||||||
|
// Rect coordinates
|
||||||
let startRectX;
|
let startRectX;
|
||||||
let startRectY;
|
let startRectY;
|
||||||
let endRectX;
|
let endRectX;
|
||||||
let endRectY;
|
let endRectY;
|
||||||
|
|
||||||
|
/** Starts drawing the rect, saves the start coordinates
|
||||||
|
*
|
||||||
|
* @param {*} mouseEvent
|
||||||
|
*/
|
||||||
function startRectDrawing(mouseEvent) {
|
function startRectDrawing(mouseEvent) {
|
||||||
// Putting the vfx layer on top of everything
|
// Putting the vfx layer on top of everything
|
||||||
VFXCanvas.style.zIndex = MAX_Z_INDEX;
|
VFXCanvas.style.zIndex = parseInt(currentLayer.canvas.style.zIndex, 10) + 1;;
|
||||||
// Updating flag
|
// Updating flag
|
||||||
isDrawingRect = true;
|
isDrawingRect = true;
|
||||||
|
|
||||||
|
@ -25,20 +32,29 @@ function startRectDrawing(mouseEvent) {
|
||||||
drawRectangle(startRectX, startRectY);
|
drawRectangle(startRectX, startRectY);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Updates the rectangle preview depending on the position of the mouse
|
||||||
|
*
|
||||||
|
* @param {*} mouseEvent The mouseEvent from which we'll get the mouse position
|
||||||
|
*/
|
||||||
function updateRectDrawing(mouseEvent) {
|
function updateRectDrawing(mouseEvent) {
|
||||||
let pos = getCursorPosition(mouseEvent);
|
let pos = getCursorPosition(mouseEvent);
|
||||||
|
|
||||||
// Drawing the rect
|
// Drawing the rect at the right position
|
||||||
drawRectangle(Math.round(pos[0] / zoom) + 0.5, Math.round(pos[1] / zoom) + 0.5);
|
drawRectangle(Math.floor(pos[0] / zoom) + 0.5, Math.floor(pos[1] / zoom) + 0.5);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Finishes drawing the rect, decides the end coordinates and moves the preview rectangle to the
|
||||||
|
* current layer
|
||||||
|
*
|
||||||
|
* @param {*} mouseEvent event from which we'll get the mouse position
|
||||||
|
*/
|
||||||
function endRectDrawing(mouseEvent) {
|
function endRectDrawing(mouseEvent) {
|
||||||
// Getting the end position
|
// Getting the end position
|
||||||
let currentPos = getCursorPosition(mouseEvent);
|
let currentPos = getCursorPosition(mouseEvent);
|
||||||
let vfxContext = VFXCanvas.getContext("2d");
|
let vfxContext = VFXCanvas.getContext("2d");
|
||||||
|
|
||||||
endRectX = Math.round(currentPos[0] / zoom) + 0.5;
|
endRectX = Math.floor(currentPos[0] / zoom) + 0.5;
|
||||||
endRectY = Math.round(currentPos[1] / zoom) + 0.5;
|
endRectY = Math.floor(currentPos[1] / zoom) + 0.5;
|
||||||
|
|
||||||
// Inverting end and start (start must always be the top left corner)
|
// Inverting end and start (start must always be the top left corner)
|
||||||
if (endRectX < startRectX) {
|
if (endRectX < startRectX) {
|
||||||
|
@ -61,15 +77,18 @@ function endRectDrawing(mouseEvent) {
|
||||||
endRectX -= 0.5;
|
endRectX -= 0.5;
|
||||||
startRectX -= 0.5;
|
startRectX -= 0.5;
|
||||||
|
|
||||||
|
// Setting the correct linewidth and colour
|
||||||
currentLayer.context.lineWidth = tool.rectangle.brushSize;
|
currentLayer.context.lineWidth = tool.rectangle.brushSize;
|
||||||
currentLayer.context.fillStyle = currentGlobalColor;
|
currentLayer.context.fillStyle = currentGlobalColor;
|
||||||
|
|
||||||
|
// Drawing the rect using 4 lines
|
||||||
line(startRectX, startRectY, endRectX, startRectY, tool.rectangle.brushSize);
|
line(startRectX, startRectY, endRectX, startRectY, tool.rectangle.brushSize);
|
||||||
line(endRectX, startRectY, endRectX, endRectY, tool.rectangle.brushSize);
|
line(endRectX, startRectY, endRectX, endRectY, tool.rectangle.brushSize);
|
||||||
line(endRectX, endRectY, startRectX, endRectY, tool.rectangle.brushSize);
|
line(endRectX, endRectY, startRectX, endRectY, tool.rectangle.brushSize);
|
||||||
line(startRectX, endRectY, startRectX, startRectY, tool.rectangle.brushSize);
|
line(startRectX, endRectY, startRectX, startRectY, tool.rectangle.brushSize);
|
||||||
|
|
||||||
if (drawMode == 'fill') {
|
// If I have to fill it, I do so
|
||||||
|
if (rectangleDrawMode == 'fill') {
|
||||||
currentLayer.context.fillRect(startRectX, startRectY, endRectX - startRectX, endRectY - startRectY);
|
currentLayer.context.fillRect(startRectX, startRectY, endRectX - startRectX, endRectY - startRectY);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -77,6 +96,12 @@ function endRectDrawing(mouseEvent) {
|
||||||
vfxContext.clearRect(0, 0, VFXCanvas.width, VFXCanvas.height);
|
vfxContext.clearRect(0, 0, VFXCanvas.width, VFXCanvas.height);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Draws a rectangle with end coordinates given by x and y on the VFX layer (draws
|
||||||
|
* the preview for the rectangle tool)
|
||||||
|
*
|
||||||
|
* @param {*} x The current end x of the rectangle
|
||||||
|
* @param {*} y The current end y of the rectangle
|
||||||
|
*/
|
||||||
function drawRectangle(x, y) {
|
function drawRectangle(x, y) {
|
||||||
// Getting the vfx context
|
// Getting the vfx context
|
||||||
let vfxContext = VFXCanvas.getContext("2d");
|
let vfxContext = VFXCanvas.getContext("2d");
|
||||||
|
@ -98,21 +123,23 @@ function drawRectangle(x, y) {
|
||||||
}
|
}
|
||||||
|
|
||||||
vfxContext.setLineDash([]);
|
vfxContext.setLineDash([]);
|
||||||
|
|
||||||
vfxContext.stroke();
|
vfxContext.stroke();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Sets the correct tool icon depending on its mode
|
||||||
|
*
|
||||||
|
*/
|
||||||
function setRectToolSvg() {
|
function setRectToolSvg() {
|
||||||
if (drawMode == 'empty') {
|
if (rectangleDrawMode == 'empty') {
|
||||||
emptySVG.setAttribute('display', 'visible');
|
emptyRectangleSVG.setAttribute('display', 'visible');
|
||||||
fullSVG.setAttribute('display', 'none');
|
fullRectangleSVG.setAttribute('display', 'none');
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
emptySVG.setAttribute('display', 'none');
|
emptyRectangleSVG.setAttribute('display', 'none');
|
||||||
fullSVG.setAttribute('display', 'visible');
|
fullRectangleSVG.setAttribute('display', 'visible');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function applyChanges() {
|
function applyChanges() {
|
||||||
VFXCanvas.style.zIndex = MIN_Z_INDEX;
|
//VFXCanvas.style.zIndex = MIN_Z_INDEX;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,16 +1,31 @@
|
||||||
let resizeCanvasContainer = document.getElementById("resize-canvas");
|
/* This scripts contains all the code used to handle the canvas resizing */
|
||||||
let rcPivot = "middle";
|
|
||||||
let currentPivotObject;
|
|
||||||
let borders = {left: 0, right: 0, top: 0, bottom: 0};
|
|
||||||
|
|
||||||
|
// Resize canvas pop up window
|
||||||
|
let resizeCanvasContainer = document.getElementById("resize-canvas");
|
||||||
|
// Start pivot
|
||||||
|
let rcPivot = "middle";
|
||||||
|
// Selected pivot button
|
||||||
|
let currentPivotObject;
|
||||||
|
// Border offsets
|
||||||
|
let rcBorders = {left: 0, right: 0, top: 0, bottom: 0};
|
||||||
|
|
||||||
|
/** Opens the canvas resize window
|
||||||
|
*
|
||||||
|
*/
|
||||||
function openResizeCanvasWindow() {
|
function openResizeCanvasWindow() {
|
||||||
|
// Initializes the inputs
|
||||||
initResizeCanvasInputs();
|
initResizeCanvasInputs();
|
||||||
showDialogue('resize-canvas');
|
showDialogue('resize-canvas');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Initializes the canvas resizing input
|
||||||
|
*
|
||||||
|
*/
|
||||||
function initResizeCanvasInputs() {
|
function initResizeCanvasInputs() {
|
||||||
|
// Getting the pivot buttons
|
||||||
let buttons = document.getElementsByClassName("pivot-button");
|
let buttons = document.getElementsByClassName("pivot-button");
|
||||||
|
|
||||||
|
// Adding the event handlers for them
|
||||||
for (let i=0; i<buttons.length; i++) {
|
for (let i=0; i<buttons.length; i++) {
|
||||||
buttons[i].addEventListener("click", changePivot);
|
buttons[i].addEventListener("click", changePivot);
|
||||||
if (buttons[i].getAttribute("value").includes("middle")) {
|
if (buttons[i].getAttribute("value").includes("middle")) {
|
||||||
|
@ -33,13 +48,21 @@ function initResizeCanvasInputs() {
|
||||||
console.log("Pivot selezionato: " + currentPivotObject);
|
console.log("Pivot selezionato: " + currentPivotObject);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Fired when a border offset is changed: it updates the width and height
|
||||||
|
*
|
||||||
|
* @param {*} event
|
||||||
|
*/
|
||||||
function rcChangedBorder(event) {
|
function rcChangedBorder(event) {
|
||||||
rcUpdateBorders();
|
rcUpdateBorders();
|
||||||
|
|
||||||
document.getElementById("rc-width").value = parseInt(layers[0].canvasSize[0]) + borders.left + borders.right;
|
document.getElementById("rc-width").value = parseInt(layers[0].canvasSize[0]) + rcBorders.left + rcBorders.right;
|
||||||
document.getElementById("rc-height").value = parseInt(layers[0].canvasSize[1]) + borders.top + borders.bottom;
|
document.getElementById("rc-height").value = parseInt(layers[0].canvasSize[1]) + rcBorders.top + rcBorders.bottom;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Fired when width or height are changed: updates the border offsets
|
||||||
|
*
|
||||||
|
* @param {*} event
|
||||||
|
*/
|
||||||
function rcChangedSize(event) {
|
function rcChangedSize(event) {
|
||||||
let widthOffset = Math.abs(document.getElementById("rc-width").value) - layers[0].canvasSize[0];
|
let widthOffset = Math.abs(document.getElementById("rc-width").value) - layers[0].canvasSize[0];
|
||||||
let heightOffset = Math.abs(document.getElementById("rc-height").value) - layers[0].canvasSize[1];
|
let heightOffset = Math.abs(document.getElementById("rc-height").value) - layers[0].canvasSize[1];
|
||||||
|
@ -54,12 +77,19 @@ function rcChangedSize(event) {
|
||||||
document.getElementById("rc-border-top").value = top;
|
document.getElementById("rc-border-top").value = top;
|
||||||
document.getElementById("rc-border-bottom").value = bottom;
|
document.getElementById("rc-border-bottom").value = bottom;
|
||||||
|
|
||||||
borders.left = left;
|
rcBorders.left = left;
|
||||||
borders.right = right;
|
rcBorders.right = right;
|
||||||
borders.top = top;
|
rcBorders.top = top;
|
||||||
borders.bottom = bottom;
|
rcBorders.bottom = bottom;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Resizes the canvas
|
||||||
|
*
|
||||||
|
* @param {*} event The event that triggered the canvas resizing
|
||||||
|
* @param {*} size The new size of the picture
|
||||||
|
* @param {*} customData Used when ctrl+z ing
|
||||||
|
* @param {*} saveHistory Should I save the history? You shouldn't if you're undoing
|
||||||
|
*/
|
||||||
function resizeCanvas(event, size, customData, saveHistory = true) {
|
function resizeCanvas(event, size, customData, saveHistory = true) {
|
||||||
let imageDatas = [];
|
let imageDatas = [];
|
||||||
let leftOffset = 0;
|
let leftOffset = 0;
|
||||||
|
@ -84,11 +114,11 @@ function resizeCanvas(event, size, customData, saveHistory = true) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Saving the history only if I'm not already undoing or redoing
|
// Saving the history only if I'm not already undoing or redoing
|
||||||
if (saveHistory) {
|
if (saveHistory && event != null) {
|
||||||
// Saving history
|
// Saving history
|
||||||
new HistoryStateResizeCanvas(
|
new HistoryStateResizeCanvas(
|
||||||
{x: parseInt(layers[0].canvasSize[0]) + borders.left + borders.right,
|
{x: parseInt(layers[0].canvasSize[0]) + rcBorders.left + rcBorders.right,
|
||||||
y: parseInt(layers[0].canvasSize[1]) + borders.top + borders.bottom},
|
y: parseInt(layers[0].canvasSize[1]) + rcBorders.top + rcBorders.bottom},
|
||||||
|
|
||||||
{x: layers[0].canvasSize[0],
|
{x: layers[0].canvasSize[0],
|
||||||
y: layers[0].canvasSize[1]},
|
y: layers[0].canvasSize[1]},
|
||||||
|
@ -100,8 +130,8 @@ function resizeCanvas(event, size, customData, saveHistory = true) {
|
||||||
|
|
||||||
// Resize the canvases
|
// Resize the canvases
|
||||||
for (let i=0; i<layers.length; i++) {
|
for (let i=0; i<layers.length; i++) {
|
||||||
layers[i].canvasSize[0] = parseInt(layers[i].canvasSize[0]) + borders.left + borders.right;
|
layers[i].canvasSize[0] = parseInt(layers[i].canvasSize[0]) + rcBorders.left + rcBorders.right;
|
||||||
layers[i].canvasSize[1] = parseInt(layers[i].canvasSize[1]) + borders.top + borders.bottom;
|
layers[i].canvasSize[1] = parseInt(layers[i].canvasSize[1]) + rcBorders.top + rcBorders.bottom;
|
||||||
|
|
||||||
layers[i].canvas.width = layers[i].canvasSize[0];
|
layers[i].canvas.width = layers[i].canvasSize[0];
|
||||||
layers[i].canvas.height = layers[i].canvasSize[1];
|
layers[i].canvas.height = layers[i].canvasSize[1];
|
||||||
|
@ -121,51 +151,49 @@ function resizeCanvas(event, size, customData, saveHistory = true) {
|
||||||
topOffset = 0;
|
topOffset = 0;
|
||||||
break;
|
break;
|
||||||
case 'top':
|
case 'top':
|
||||||
leftOffset = (borders.left + borders.right) / 2;
|
leftOffset = (rcBorders.left + rcBorders.right) / 2;
|
||||||
topOffset = 0;
|
topOffset = 0;
|
||||||
break;
|
break;
|
||||||
case 'topright':
|
case 'topright':
|
||||||
leftOffset = borders.left + borders.right;
|
leftOffset = rcBorders.left + rcBorders.right;
|
||||||
topOffset = 0;
|
topOffset = 0;
|
||||||
break;
|
break;
|
||||||
case 'left':
|
case 'left':
|
||||||
leftOffset = 0;
|
leftOffset = 0;
|
||||||
topOffset = (borders.top + borders.bottom) / 2;
|
topOffset = (rcBorders.top + rcBorders.bottom) / 2;
|
||||||
break;
|
break;
|
||||||
case 'middle':
|
case 'middle':
|
||||||
leftOffset = (borders.left + borders.right) / 2;
|
leftOffset = (rcBorders.left + rcBorders.right) / 2;
|
||||||
topOffset = (borders.top + borders.bottom) / 2;
|
topOffset = (rcBorders.top + rcBorders.bottom) / 2;
|
||||||
break;
|
break;
|
||||||
case 'right':
|
case 'right':
|
||||||
leftOffset = borders.left + borders.right;
|
leftOffset = rcBorders.left + rcBorders.right;
|
||||||
topOffset = (borders.top + borders.bottom) / 2;
|
topOffset = (rcBorders.top + rcBorders.bottom) / 2;
|
||||||
break;
|
break;
|
||||||
case 'bottomleft':
|
case 'bottomleft':
|
||||||
leftOffset = 0;
|
leftOffset = 0;
|
||||||
topOffset = borders.top + borders.bottom;
|
topOffset = rcBorders.top + rcBorders.bottom;
|
||||||
break;
|
break;
|
||||||
case 'bottom':
|
case 'bottom':
|
||||||
leftOffset = (borders.left + borders.right) / 2;
|
leftOffset = (rcBorders.left + rcBorders.right) / 2;
|
||||||
topOffset = borders.top + borders.bottom;
|
topOffset = rcBorders.top + rcBorders.bottom;
|
||||||
break;
|
break;
|
||||||
case 'bottomright':
|
case 'bottomright':
|
||||||
leftOffset = borders.left + borders.right;
|
leftOffset = rcBorders.left + rcBorders.right;
|
||||||
topOffset = borders.top + borders.bottom;
|
topOffset = rcBorders.top + rcBorders.bottom;
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
console.log('Pivot does not exist, please report an issue at https://github.com/lospec/pixel-editor');
|
console.log('Pivot does not exist, please report an issue at https://github.com/lospec/pixel-editor');
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Putting all the data for each layer with the right offsets (decided by the pivot)
|
||||||
for (let i=0; i<layers.length; i++) {
|
for (let i=0; i<layers.length; i++) {
|
||||||
if (layers[i].menuEntry != null) {
|
if (layers[i].menuEntry != null) {
|
||||||
if (customData == undefined) {
|
if (customData == undefined) {
|
||||||
layers[i].context.putImageData(imageDatas[copiedDataIndex], leftOffset, topOffset);
|
layers[i].context.putImageData(imageDatas[copiedDataIndex], leftOffset, topOffset);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
console.log("sgancio " + layers[i].canvasSize + ", [" +
|
|
||||||
customData[copiedDataIndex].width + "," + customData[copiedDataIndex].height
|
|
||||||
+ "]");
|
|
||||||
layers[i].context.putImageData(customData[copiedDataIndex], 0, 0);
|
layers[i].context.putImageData(customData[copiedDataIndex], 0, 0);
|
||||||
}
|
}
|
||||||
layers[i].updateLayerPreview();
|
layers[i].updateLayerPreview();
|
||||||
|
@ -176,6 +204,11 @@ function resizeCanvas(event, size, customData, saveHistory = true) {
|
||||||
closeDialogue();
|
closeDialogue();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Trims the canvas so tat the sprite is perfectly contained in it
|
||||||
|
*
|
||||||
|
* @param {*} event
|
||||||
|
* @param {*} saveHistory Should I save the history? You shouldn't if you're undoing
|
||||||
|
*/
|
||||||
function trimCanvas(event, saveHistory) {
|
function trimCanvas(event, saveHistory) {
|
||||||
let minY = Infinity;
|
let minY = Infinity;
|
||||||
let minX = Infinity;
|
let minX = Infinity;
|
||||||
|
@ -189,6 +222,7 @@ function trimCanvas(event, saveHistory) {
|
||||||
rcPivot = "topleft";
|
rcPivot = "topleft";
|
||||||
console.log("debug");
|
console.log("debug");
|
||||||
|
|
||||||
|
// Computing the min and max coordinates in which there's a non empty pixel
|
||||||
for (let i=1; i<layers.length - nAppLayers; i++) {
|
for (let i=1; i<layers.length - nAppLayers; i++) {
|
||||||
let imageData = layers[i].context.getImageData(0, 0, layers[0].canvasSize[0], layers[0].canvasSize[1]);
|
let imageData = layers[i].context.getImageData(0, 0, layers[0].canvasSize[0], layers[0].canvasSize[1]);
|
||||||
let pixelPosition;
|
let pixelPosition;
|
||||||
|
@ -226,10 +260,11 @@ function trimCanvas(event, saveHistory) {
|
||||||
minY = layers[0].canvasSize[1] - minY;
|
minY = layers[0].canvasSize[1] - minY;
|
||||||
maxY = layers[0].canvasSize[1] - maxY;
|
maxY = layers[0].canvasSize[1] - maxY;
|
||||||
|
|
||||||
borders.right = (maxX - layers[0].canvasSize[0]) + 1;
|
// Setting the borders coherently with the values I've just computed
|
||||||
borders.left = -minX;
|
rcBorders.right = (maxX - layers[0].canvasSize[0]) + 1;
|
||||||
borders.top = maxY - layers[0].canvasSize[1] + 1;
|
rcBorders.left = -minX;
|
||||||
borders.bottom = -minY;
|
rcBorders.top = maxY - layers[0].canvasSize[1] + 1;
|
||||||
|
rcBorders.bottom = -minY;
|
||||||
|
|
||||||
// Saving the data
|
// Saving the data
|
||||||
for (let i=0; i<layers.length; i++) {
|
for (let i=0; i<layers.length; i++) {
|
||||||
|
@ -241,11 +276,12 @@ function trimCanvas(event, saveHistory) {
|
||||||
console.log(imageDatas);
|
console.log(imageDatas);
|
||||||
//console.log("sx: " + borders.left + "dx: " + borders.right + "top: " + borders.top + "btm: " + borders.bottom);
|
//console.log("sx: " + borders.left + "dx: " + borders.right + "top: " + borders.top + "btm: " + borders.bottom);
|
||||||
|
|
||||||
document.getElementById("rc-border-left").value = borders.left;
|
document.getElementById("rc-border-left").value = rcBorders.left;
|
||||||
document.getElementById("rc-border-right").value = borders.right;
|
document.getElementById("rc-border-right").value = rcBorders.right;
|
||||||
document.getElementById("rc-border-top").value = borders.top;
|
document.getElementById("rc-border-top").value = rcBorders.top;
|
||||||
document.getElementById("rc-border-bottom").value = borders.bottom;
|
document.getElementById("rc-border-bottom").value = rcBorders.bottom;
|
||||||
|
|
||||||
|
// Resizing the canvas with the decided border offsets
|
||||||
resizeCanvas(null, null, imageDatas.slice(), historySave);
|
resizeCanvas(null, null, imageDatas.slice(), historySave);
|
||||||
// Resetting the previous pivot
|
// Resetting the previous pivot
|
||||||
rcPivot = prevPivot;
|
rcPivot = prevPivot;
|
||||||
|
@ -253,16 +289,16 @@ function trimCanvas(event, saveHistory) {
|
||||||
|
|
||||||
function rcUpdateBorders() {
|
function rcUpdateBorders() {
|
||||||
// Getting input
|
// Getting input
|
||||||
borders.left = document.getElementById("rc-border-left").value;
|
rcBorders.left = document.getElementById("rc-border-left").value;
|
||||||
borders.right = document.getElementById("rc-border-right").value;
|
rcBorders.right = document.getElementById("rc-border-right").value;
|
||||||
borders.top = document.getElementById("rc-border-top").value;
|
rcBorders.top = document.getElementById("rc-border-top").value;
|
||||||
borders.bottom = document.getElementById("rc-border-bottom").value;
|
rcBorders.bottom = document.getElementById("rc-border-bottom").value;
|
||||||
|
|
||||||
// Validating input
|
// Validating input
|
||||||
borders.left == "" ? borders.left = 0 : borders.left = Math.round(parseInt(borders.left));
|
rcBorders.left == "" ? rcBorders.left = 0 : rcBorders.left = Math.round(parseInt(rcBorders.left));
|
||||||
borders.right == "" ? borders.right = 0 : borders.right = Math.round(parseInt(borders.right));
|
rcBorders.right == "" ? rcBorders.right = 0 : rcBorders.right = Math.round(parseInt(rcBorders.right));
|
||||||
borders.top == "" ? borders.top = 0 : borders.top = Math.round(parseInt(borders.top));
|
rcBorders.top == "" ? rcBorders.top = 0 : rcBorders.top = Math.round(parseInt(rcBorders.top));
|
||||||
borders.bottom == "" ? borders.bottom = 0 : borders.bottom = Math.round(parseInt(borders.bottom));
|
rcBorders.bottom == "" ? rcBorders.bottom = 0 : rcBorders.bottom = Math.round(parseInt(rcBorders.bottom));
|
||||||
}
|
}
|
||||||
|
|
||||||
function changePivot(event) {
|
function changePivot(event) {
|
||||||
|
|
|
@ -1,15 +1,28 @@
|
||||||
|
/* This scripts contains all the code used to handle the sprite scaling */
|
||||||
|
// Should I keep the sprite ratio?
|
||||||
let keepRatio = true;
|
let keepRatio = true;
|
||||||
|
// Used to store the current ratio
|
||||||
let currentRatio;
|
let currentRatio;
|
||||||
|
// The currenty selected resizing algorithm (nearest-neighbor or bilinear-interpolation)
|
||||||
let currentAlgo = 'nearest-neighbor';
|
let currentAlgo = 'nearest-neighbor';
|
||||||
|
// Current resize data
|
||||||
let data = {width: 0, height: 0, widthPercentage: 100, heightPercentage: 100};
|
let data = {width: 0, height: 0, widthPercentage: 100, heightPercentage: 100};
|
||||||
|
// Start resize data
|
||||||
let startData = {width: 0, height:0, widthPercentage: 100, heightPercentage: 100};
|
let startData = {width: 0, height:0, widthPercentage: 100, heightPercentage: 100};
|
||||||
|
|
||||||
|
/** Opens the sprite resizing window
|
||||||
|
*
|
||||||
|
*/
|
||||||
function openResizeSpriteWindow() {
|
function openResizeSpriteWindow() {
|
||||||
|
// Inits the sprie resize inputs
|
||||||
initResizeSpriteInputs();
|
initResizeSpriteInputs();
|
||||||
|
|
||||||
|
// Computing the current ratio
|
||||||
currentRatio = layers[0].canvasSize[0] / layers[0].canvasSize[1];
|
currentRatio = layers[0].canvasSize[0] / layers[0].canvasSize[1];
|
||||||
|
|
||||||
|
console.log("Current ratio: " + currentRatio);
|
||||||
|
|
||||||
|
// Initializing the input fields
|
||||||
data.width = layers[0].canvasSize[0];
|
data.width = layers[0].canvasSize[0];
|
||||||
data.height = layers[1].canvasSize[1];
|
data.height = layers[1].canvasSize[1];
|
||||||
|
|
||||||
|
@ -18,9 +31,13 @@ function openResizeSpriteWindow() {
|
||||||
startData.heightPercentage = 100;
|
startData.heightPercentage = 100;
|
||||||
startData.widthPercentage = 100;
|
startData.widthPercentage = 100;
|
||||||
|
|
||||||
|
// Opening the pop up now that it's ready
|
||||||
showDialogue('resize-sprite');
|
showDialogue('resize-sprite');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Initalizes the input values and binds the elements to their events
|
||||||
|
*
|
||||||
|
*/
|
||||||
function initResizeSpriteInputs() {
|
function initResizeSpriteInputs() {
|
||||||
document.getElementById("rs-width").value = layers[0].canvasSize[0];
|
document.getElementById("rs-width").value = layers[0].canvasSize[0];
|
||||||
document.getElementById("rs-height").value = layers[0].canvasSize[1];
|
document.getElementById("rs-height").value = layers[0].canvasSize[1];
|
||||||
|
@ -40,11 +57,21 @@ function initResizeSpriteInputs() {
|
||||||
document.getElementById("resize-algorithm-combobox").addEventListener("change", changedAlgorithm);
|
document.getElementById("resize-algorithm-combobox").addEventListener("change", changedAlgorithm);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Resizes (scales) the sprite
|
||||||
|
*
|
||||||
|
* @param {*} event
|
||||||
|
* @param {*} ratio Keeps infos about the x ratio and y ratio
|
||||||
|
*/
|
||||||
function resizeSprite(event, ratio) {
|
function resizeSprite(event, ratio) {
|
||||||
|
// Old data
|
||||||
let oldWidth, oldHeight;
|
let oldWidth, oldHeight;
|
||||||
|
// New data
|
||||||
let newWidth, newHeight;
|
let newWidth, newHeight;
|
||||||
|
// Current imageDatas
|
||||||
let rsImageDatas = [];
|
let rsImageDatas = [];
|
||||||
|
// Index that will be used a few lines below
|
||||||
let layerIndex = 0;
|
let layerIndex = 0;
|
||||||
|
// Copy of the imageDatas that will be stored in the history
|
||||||
let imageDatasCopy = [];
|
let imageDatasCopy = [];
|
||||||
|
|
||||||
oldWidth = layers[0].canvasSize[0];
|
oldWidth = layers[0].canvasSize[0];
|
||||||
|
@ -70,6 +97,7 @@ function resizeSprite(event, ratio) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Computing newWidth and newHeight
|
||||||
if (ratio == null) {
|
if (ratio == null) {
|
||||||
newWidth = data.width;
|
newWidth = data.width;
|
||||||
newHeight = data.height;
|
newHeight = data.height;
|
||||||
|
@ -88,8 +116,11 @@ function resizeSprite(event, ratio) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ratio == null) {
|
// event is null when the user is undoing
|
||||||
|
if (event != null) {
|
||||||
|
// Copying the image data
|
||||||
imageDatasCopy = rsImageDatas.slice();
|
imageDatasCopy = rsImageDatas.slice();
|
||||||
|
// Saving the history
|
||||||
new HistoryStateResizeSprite(newWidth / oldWidth, newHeight / oldHeight, currentAlgo, imageDatasCopy);
|
new HistoryStateResizeSprite(newWidth / oldWidth, newHeight / oldHeight, currentAlgo, imageDatasCopy);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -124,6 +155,12 @@ function resizeSprite(event, ratio) {
|
||||||
closeDialogue();
|
closeDialogue();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Trust me, the math for the functions below works. If you want to optimize them feel free to have a look, though */
|
||||||
|
|
||||||
|
/** Fired when the input field for width is changed. Updates th othe input fields consequently
|
||||||
|
*
|
||||||
|
* @param {*} event
|
||||||
|
*/
|
||||||
function changedWidth(event) {
|
function changedWidth(event) {
|
||||||
let oldValue = data.width;
|
let oldValue = data.width;
|
||||||
let ratio;
|
let ratio;
|
||||||
|
@ -150,6 +187,10 @@ function changedWidth(event) {
|
||||||
document.getElementById("rs-width-percentage").value = newWidthPerc;
|
document.getElementById("rs-width-percentage").value = newWidthPerc;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**Fired when the input field for width is changed. Updates the other input fields consequently
|
||||||
|
*
|
||||||
|
* @param {*} event
|
||||||
|
*/
|
||||||
function changedHeight(event) {
|
function changedHeight(event) {
|
||||||
let oldValue = 100;
|
let oldValue = 100;
|
||||||
let ratio;
|
let ratio;
|
||||||
|
@ -176,6 +217,10 @@ function changedHeight(event) {
|
||||||
data.heightPercentage = newHeightPerc;
|
data.heightPercentage = newHeightPerc;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**Fired when the input field for width percentage is changed. Updates the other input fields consequently
|
||||||
|
*
|
||||||
|
* @param {*} event
|
||||||
|
*/
|
||||||
function changedWidthPercentage(event) {
|
function changedWidthPercentage(event) {
|
||||||
let oldValue = 100;
|
let oldValue = 100;
|
||||||
let ratio;
|
let ratio;
|
||||||
|
@ -189,7 +234,7 @@ function changedWidthPercentage(event) {
|
||||||
console.log("old value: " + oldValue + ", ratio: " + ratio);
|
console.log("old value: " + oldValue + ", ratio: " + ratio);
|
||||||
|
|
||||||
newHeight = startData.height * ratio;
|
newHeight = startData.height * ratio;
|
||||||
newHeightPerc = data.widthPercentage / currentRatio;
|
newHeightPerc = data.widthPercentage;
|
||||||
newWidth = startData.width * ratio;
|
newWidth = startData.width * ratio;
|
||||||
|
|
||||||
if (keepRatio) {
|
if (keepRatio) {
|
||||||
|
@ -204,6 +249,10 @@ function changedWidthPercentage(event) {
|
||||||
data.width = newWidth;
|
data.width = newWidth;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**Fired when the input field for height percentage is changed. Updates the other input fields consequently
|
||||||
|
*
|
||||||
|
* @param {*} event
|
||||||
|
*/
|
||||||
function changedHeightPercentage(event) {
|
function changedHeightPercentage(event) {
|
||||||
let oldValue = data.heightPercentage;
|
let oldValue = data.heightPercentage;
|
||||||
let ratio;
|
let ratio;
|
||||||
|
@ -215,7 +264,7 @@ function changedHeightPercentage(event) {
|
||||||
ratio = data.heightPercentage / oldValue;
|
ratio = data.heightPercentage / oldValue;
|
||||||
|
|
||||||
newWidth = startData.width * ratio;
|
newWidth = startData.width * ratio;
|
||||||
newWidthPerc = data.heightPercentage * currentRatio;
|
newWidthPerc = data.heightPercentage;
|
||||||
newHeight = startData.height * ratio;
|
newHeight = startData.height * ratio;
|
||||||
|
|
||||||
if (keepRatio) {
|
if (keepRatio) {
|
||||||
|
@ -230,10 +279,18 @@ function changedHeightPercentage(event) {
|
||||||
data.height = newHeight;
|
data.height = newHeight;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Toggles the keepRatio value (fired by the checkbox in the pop up window)
|
||||||
|
*
|
||||||
|
* @param {*} event
|
||||||
|
*/
|
||||||
function toggleRatio(event) {
|
function toggleRatio(event) {
|
||||||
keepRatio = !keepRatio;
|
keepRatio = !keepRatio;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Changes the scaling algorithm (fired by the combobox in the pop up window)
|
||||||
|
*
|
||||||
|
* @param {*} event
|
||||||
|
*/
|
||||||
function changedAlgorithm(event) {
|
function changedAlgorithm(event) {
|
||||||
currentAlgo = event.target.value;
|
currentAlgo = event.target.value;
|
||||||
}
|
}
|
|
@ -26,8 +26,9 @@ else{
|
||||||
console.log(settings);
|
console.log(settings);
|
||||||
|
|
||||||
//on clicking the save button in the settings dialog
|
//on clicking the save button in the settings dialog
|
||||||
on('click', 'save-settings', function (){
|
on('click', 'save-settings', saveSettings);
|
||||||
|
|
||||||
|
function saveSettings() {
|
||||||
//check if values are valid
|
//check if values are valid
|
||||||
if (isNaN(getValue('setting-numberOfHistoryStates'))) {
|
if (isNaN(getValue('setting-numberOfHistoryStates'))) {
|
||||||
alert('Invalid value for numberOfHistoryStates');
|
alert('Invalid value for numberOfHistoryStates');
|
||||||
|
@ -46,4 +47,4 @@ on('click', 'save-settings', function (){
|
||||||
|
|
||||||
//close window
|
//close window
|
||||||
closeDialogue();
|
closeDialogue();
|
||||||
});
|
}
|
|
@ -0,0 +1,23 @@
|
||||||
|
function SplashCoverImage(path, author, link) {
|
||||||
|
this.path = path;
|
||||||
|
this.author = author;
|
||||||
|
this.link = link;
|
||||||
|
}
|
||||||
|
|
||||||
|
let images = [
|
||||||
|
new SplashCoverImage('Rayquaza', 'Unsettled', 'https://lospec.com/unsettled'),
|
||||||
|
new SplashCoverImage('Mountains', 'Skeddles', 'https://lospec.com/skeddles'),
|
||||||
|
new SplashCoverImage('Sweetie', 'GrafxKid', 'https://twitter.com/GrafxKid'),
|
||||||
|
new SplashCoverImage('Glacier', 'WindfallApples', 'https://lospec.com/windfallapples'),
|
||||||
|
new SplashCoverImage('Polyphorge1', 'Polyphorge', 'https://lospec.com/poly-phorge'),
|
||||||
|
new SplashCoverImage('Fusionnist', 'Fusionnist', 'https://lospec.com/fusionnist')
|
||||||
|
];
|
||||||
|
|
||||||
|
|
||||||
|
let coverImage = document.getElementById('editor-logo');
|
||||||
|
let authorLink = coverImage.getElementsByTagName('a')[0];
|
||||||
|
let chosenImage = images[Math.round(Math.random() * (images.length - 1))];
|
||||||
|
|
||||||
|
coverImage.style.backgroundImage = 'url("/pixel-editor/' + chosenImage.path + '.png")';
|
||||||
|
authorLink.setAttribute('href', chosenImage.link);
|
||||||
|
authorLink.innerHTML = 'Art by ' + chosenImage.author;
|
|
@ -35,12 +35,12 @@ on('click',"eraser-smaller-button", function(e){
|
||||||
on('click','rectangle-button', function(e){
|
on('click','rectangle-button', function(e){
|
||||||
// If the user clicks twice on the button, they change the draw mode
|
// If the user clicks twice on the button, they change the draw mode
|
||||||
if (currentTool.name == 'rectangle') {
|
if (currentTool.name == 'rectangle') {
|
||||||
if (drawMode == 'empty') {
|
if (rectangleDrawMode == 'empty') {
|
||||||
drawMode = 'fill';
|
rectangleDrawMode = 'fill';
|
||||||
setRectToolSvg();
|
setRectToolSvg();
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
drawMode = 'empty';
|
rectangleDrawMode = 'empty';
|
||||||
setRectToolSvg();
|
setRectToolSvg();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -49,6 +49,24 @@ on('click','rectangle-button', function(e){
|
||||||
}
|
}
|
||||||
}, false);
|
}, false);
|
||||||
|
|
||||||
|
// ellipse
|
||||||
|
on('click','ellipse-button', function(e){
|
||||||
|
// If the user clicks twice on the button, they change the draw mode
|
||||||
|
if (currentTool.name == 'ellipse') {
|
||||||
|
if (ellipseDrawMode == 'empty') {
|
||||||
|
ellipseDrawMode = 'fill';
|
||||||
|
setEllipseToolSvg();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
ellipseDrawMode = 'empty';
|
||||||
|
setEllipseToolSvg();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
tool.ellipse.switchTo();
|
||||||
|
}
|
||||||
|
}, false);
|
||||||
|
|
||||||
// rectangle bigger
|
// rectangle bigger
|
||||||
on('click',"rectangle-bigger-button", function(){
|
on('click',"rectangle-bigger-button", function(){
|
||||||
tool.rectangle.brushSize++;
|
tool.rectangle.brushSize++;
|
||||||
|
@ -60,6 +78,17 @@ on('click',"rectangle-smaller-button", function(e){
|
||||||
tool.rectangle.brushSize--;
|
tool.rectangle.brushSize--;
|
||||||
}, false);
|
}, false);
|
||||||
|
|
||||||
|
// ellipse bigger
|
||||||
|
on('click',"ellipse-bigger-button", function(){
|
||||||
|
tool.ellipse.brushSize++;
|
||||||
|
}, false);
|
||||||
|
|
||||||
|
// ellipse smaller
|
||||||
|
on('click',"ellipse-smaller-button", function(e){
|
||||||
|
if(tool.ellipse.brushSize > 1)
|
||||||
|
tool.ellipse.brushSize--;
|
||||||
|
}, false);
|
||||||
|
|
||||||
//fill
|
//fill
|
||||||
on('click',"fill-button", function(){
|
on('click',"fill-button", function(){
|
||||||
tool.fill.switchTo();
|
tool.fill.switchTo();
|
||||||
|
@ -75,33 +104,24 @@ on('click',"eyedropper-button", function(){
|
||||||
tool.eyedropper.switchTo();
|
tool.eyedropper.switchTo();
|
||||||
}, false);
|
}, false);
|
||||||
|
|
||||||
//zoom tool button
|
|
||||||
on('click',"zoom-button", function(){
|
|
||||||
tool.zoom.switchTo();
|
|
||||||
}, false);
|
|
||||||
|
|
||||||
//zoom in button
|
|
||||||
on('click','zoom-in-button', function(){
|
|
||||||
//changeZoom('in',[window.innerWidth/2-canvas.offsetLeft,window.innerHeight/2-canvas.offsetTop]);
|
|
||||||
changeZoom(layers[0],'in', [canvasSize[0] * zoom / 2, canvasSize[1] * zoom / 2]);
|
|
||||||
|
|
||||||
for (let i=1; i<layers.length; i++) {
|
|
||||||
layers[i].copyData(layers[0]);
|
|
||||||
}
|
|
||||||
}, false);
|
|
||||||
|
|
||||||
//zoom out button
|
|
||||||
on('click','zoom-out-button', function(){
|
|
||||||
changeZoom(layers[0],'out',[canvasSize[0]*zoom/2,canvasSize[1]*zoom/2]);
|
|
||||||
|
|
||||||
for (let i=1; i<layers.length; i++) {
|
|
||||||
layers[i].copyData(layers[0]);
|
|
||||||
}
|
|
||||||
}, false);
|
|
||||||
|
|
||||||
//rectangular selection button
|
//rectangular selection button
|
||||||
on('click', "rectselect-button", function(){
|
on('click', "rectselect-button", function(){
|
||||||
tool.rectselect.switchTo();
|
tool.rectselect.switchTo();
|
||||||
}, false);
|
}, false);
|
||||||
|
|
||||||
|
//line
|
||||||
|
on('click',"line-button", function(){
|
||||||
|
tool.line.switchTo();
|
||||||
|
}, false);
|
||||||
|
|
||||||
|
on('click',"line-bigger-button", function(){
|
||||||
|
tool.line.brushSize++;
|
||||||
|
}, false);
|
||||||
|
|
||||||
|
on('click',"line-smaller-button", function(){
|
||||||
|
if(tool.line.brushSize > 1)
|
||||||
|
tool.line.brushSize--;
|
||||||
|
}, false);
|
||||||
|
|
||||||
|
|
||||||
/*global on */
|
/*global on */
|
||||||
|
|
17
js/_tools.js
|
@ -95,15 +95,14 @@ class Tool {
|
||||||
}
|
}
|
||||||
|
|
||||||
moveBrushPreview(cursorLocation) {
|
moveBrushPreview(cursorLocation) {
|
||||||
let toSub = 0;
|
let toSub = 1;
|
||||||
// Prevents the brush to be put in the middle of pixels
|
// Prevents the brush to be put in the middle of pixels
|
||||||
if (this.currentBrushSize % 2 == 0) {
|
if (this.currentBrushSize % 2 == 0) {
|
||||||
toSub = 0.5;
|
toSub = 0.5;
|
||||||
}
|
}
|
||||||
|
brushPreview.style.left = (Math.floor(cursorLocation[0] / zoom) * zoom + currentLayer.canvas.offsetLeft - this.currentBrushSize * zoom / 2 - zoom / 2 + toSub * zoom) + 'px';
|
||||||
brushPreview.style.left = (Math.ceil(cursorLocation[0] / zoom) * zoom + currentLayer.canvas.offsetLeft - this.currentBrushSize * zoom / 2 - zoom / 2 - toSub * zoom) + 'px';
|
brushPreview.style.top = (Math.floor(cursorLocation[1] / zoom) * zoom + currentLayer.canvas.offsetTop - this.currentBrushSize * zoom / 2 - zoom / 2 + toSub * zoom) + 'px';
|
||||||
brushPreview.style.top = (Math.ceil(cursorLocation[1] / zoom) * zoom + currentLayer.canvas.offsetTop - this.currentBrushSize * zoom / 2 - zoom / 2 - toSub * zoom) + 'px';
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -5,7 +5,7 @@ var dragging = false;
|
||||||
var lastMouseClickPos = [0,0];
|
var lastMouseClickPos = [0,0];
|
||||||
var dialogueOpen = false;
|
var dialogueOpen = false;
|
||||||
var documentCreated = false;
|
var documentCreated = false;
|
||||||
var pixelEditorMode;
|
var pixelEditorMode = "Advanced";
|
||||||
|
|
||||||
//common elements
|
//common elements
|
||||||
var brushPreview = document.getElementById("brush-preview");
|
var brushPreview = document.getElementById("brush-preview");
|
||||||
|
|
|
@ -1,8 +1,6 @@
|
||||||
|
|
||||||
|
|
||||||
ajax('https://api.github.com/repos/lospec/pixel-editor/contributors', response => {
|
ajax('https://api.github.com/repos/lospec/pixel-editor/contributors', response => {
|
||||||
console.log(response)
|
|
||||||
|
|
||||||
if (Array.isArray(response)) {
|
if (Array.isArray(response)) {
|
||||||
|
|
||||||
var html = '';
|
var html = '';
|
||||||
|
|
|
@ -12,6 +12,9 @@
|
||||||
//=include utilities/hslToRgb.js
|
//=include utilities/hslToRgb.js
|
||||||
//=include libraries/cookies.js
|
//=include libraries/cookies.js
|
||||||
//=include _pixelEditorUtility.js
|
//=include _pixelEditorUtility.js
|
||||||
|
//=include sortable.js
|
||||||
|
//=include _algorithms.js
|
||||||
|
//=include Util.js
|
||||||
|
|
||||||
/**init**/
|
/**init**/
|
||||||
//=include _consts.js
|
//=include _consts.js
|
||||||
|
@ -28,16 +31,16 @@
|
||||||
//=include tools/*.js
|
//=include tools/*.js
|
||||||
//=include _newPixel.js
|
//=include _newPixel.js
|
||||||
//=include _createColorPalette.js
|
//=include _createColorPalette.js
|
||||||
//=include _setCanvasOffset.js
|
|
||||||
//=include _changeZoom.js
|
//=include _changeZoom.js
|
||||||
//=include _addColor.js
|
//=include _addColor.js
|
||||||
//=include _colorChanged.js
|
//=include _colorChanged.js
|
||||||
//=include _initColor.js
|
//=include _initColor.js
|
||||||
//=include _dialogue.js
|
//=include _dialogue.js
|
||||||
//=include _updateCursor.js
|
//=include _featuresLog.js
|
||||||
//=include _drawLine.js
|
//=include _drawLine.js
|
||||||
//=include _getCursorPosition.js
|
//=include _getCursorPosition.js
|
||||||
//=include _fill.js
|
//=include _fill.js
|
||||||
|
//=include _line.js
|
||||||
//=include _history.js
|
//=include _history.js
|
||||||
//=include _deleteColor.js
|
//=include _deleteColor.js
|
||||||
//=include _replaceAllOfColor.js
|
//=include _replaceAllOfColor.js
|
||||||
|
@ -47,6 +50,10 @@
|
||||||
//=include _copyPaste.js
|
//=include _copyPaste.js
|
||||||
//=include _resizeCanvas.js
|
//=include _resizeCanvas.js
|
||||||
//=include _resizeSprite.js
|
//=include _resizeSprite.js
|
||||||
|
//=include _colorPicker.js
|
||||||
|
//=include _paletteBlock.js
|
||||||
|
//=include _splashPage.js
|
||||||
|
//=include _logs.js
|
||||||
|
|
||||||
/**load file**/
|
/**load file**/
|
||||||
//=include _loadImage.js
|
//=include _loadImage.js
|
||||||
|
@ -65,6 +72,7 @@
|
||||||
//=include _rectSelect.js
|
//=include _rectSelect.js
|
||||||
//=include _move.js
|
//=include _move.js
|
||||||
//=include _rectangle.js
|
//=include _rectangle.js
|
||||||
|
//=include _ellipse.js
|
||||||
|
|
||||||
/**onload**/
|
/**onload**/
|
||||||
//=include _onLoad.js
|
//=include _onLoad.js
|
||||||
|
@ -72,3 +80,9 @@
|
||||||
|
|
||||||
/**libraries**/
|
/**libraries**/
|
||||||
//=include _jscolor.js
|
//=include _jscolor.js
|
||||||
|
|
||||||
|
/**feature toggles**/
|
||||||
|
//=include _featureToggles.js
|
||||||
|
|
||||||
|
// Controls execution of this preset module
|
||||||
|
PresetModule.instrumentPresetMenu();
|
|
@ -0,0 +1,68 @@
|
||||||
|
new Tool('eraser', {
|
||||||
|
cursor: 'none',
|
||||||
|
brushPreview: true,
|
||||||
|
});
|
||||||
|
new Tool('resizeeraser', {
|
||||||
|
cursor: 'default',
|
||||||
|
});
|
||||||
|
|
||||||
|
new Tool('eyedropper', {
|
||||||
|
imageCursor: 'eyedropper',
|
||||||
|
});
|
||||||
|
|
||||||
|
new Tool('fill', {
|
||||||
|
imageCursor: 'fill',
|
||||||
|
});
|
||||||
|
|
||||||
|
new Tool('line', {
|
||||||
|
cursor: 'none',
|
||||||
|
brushPreview: true,
|
||||||
|
});
|
||||||
|
new Tool('resizeline', {
|
||||||
|
cursor: 'default',
|
||||||
|
});
|
||||||
|
|
||||||
|
new Tool('pan', {
|
||||||
|
cursor: function () {
|
||||||
|
if (dragging) return 'url(\'/pixel-editor/pan-held.png\'), auto';
|
||||||
|
else return 'url(\'/pixel-editor/pan.png\'), auto';
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
new Tool('pencil', {
|
||||||
|
cursor: 'none',
|
||||||
|
brushPreview: true,
|
||||||
|
});
|
||||||
|
new Tool('resizebrush', {
|
||||||
|
cursor: 'default',
|
||||||
|
});
|
||||||
|
|
||||||
|
new Tool('rectangle', {
|
||||||
|
cursor: 'none',
|
||||||
|
brushPreview: true,
|
||||||
|
});
|
||||||
|
new Tool('ellipse', {
|
||||||
|
cursor: 'none',
|
||||||
|
brushPreview: true,
|
||||||
|
});
|
||||||
|
new Tool('resizerectangle', {
|
||||||
|
cursor: 'default',
|
||||||
|
});
|
||||||
|
|
||||||
|
new Tool('rectselect', {
|
||||||
|
cursor: 'crosshair',
|
||||||
|
brushPreview: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
new Tool('moveselection', {
|
||||||
|
cursor: 'crosshair',
|
||||||
|
});
|
||||||
|
|
||||||
|
new Tool('zoom', {
|
||||||
|
imageCursor: 'zoom-in',
|
||||||
|
});
|
||||||
|
|
||||||
|
//set a default tool
|
||||||
|
var currentTool = tool.pencil;
|
||||||
|
var currentToolTemp = tool.pencil;
|
|
@ -1,13 +0,0 @@
|
||||||
|
|
||||||
new Tool('eraser', {
|
|
||||||
cursor: 'crosshair',
|
|
||||||
brushPreview: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
new Tool('resizeeraser', {
|
|
||||||
cursor: 'default',
|
|
||||||
});
|
|
||||||
|
|
||||||
/*global Tool, tool*/
|
|
|
@ -1,7 +0,0 @@
|
||||||
|
|
||||||
new Tool('eyedropper', {
|
|
||||||
imageCursor: 'eyedropper',
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
/*global Tool, tool*/
|
|
|
@ -1,7 +0,0 @@
|
||||||
|
|
||||||
new Tool('fill', {
|
|
||||||
imageCursor: 'fill',
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
/*global Tool, tool*/
|
|
|
@ -1,10 +0,0 @@
|
||||||
|
|
||||||
new Tool('pan', {
|
|
||||||
cursor: function () {
|
|
||||||
if (dragging) return 'url(\'/pixel-editor/pan-held.png\'), auto';
|
|
||||||
else return 'url(\'/pixel-editor/pan.png\'), auto';
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
/*global Tool, tool*/
|
|
|
@ -1,17 +0,0 @@
|
||||||
|
|
||||||
new Tool('pencil', {
|
|
||||||
cursor: 'crosshair',
|
|
||||||
brushPreview: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
new Tool('resizebrush', {
|
|
||||||
cursor: 'default',
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
//set as default tool
|
|
||||||
var currentTool = tool.pencil;
|
|
||||||
var currentToolTemp = tool.pencil;
|
|
||||||
|
|
||||||
/*global Tool, tool*/
|
|
|
@ -1,14 +0,0 @@
|
||||||
|
|
||||||
new Tool('rectangle', {
|
|
||||||
cursor: 'crosshair',
|
|
||||||
brushPreview: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
new Tool('resizerectangle', {
|
|
||||||
cursor: 'default',
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
/*global Tool, tool*/
|
|
|
@ -1,14 +0,0 @@
|
||||||
|
|
||||||
|
|
||||||
new Tool('rectselect', {
|
|
||||||
cursor: 'crosshair',
|
|
||||||
brushPreview: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
new Tool('moveselection', {
|
|
||||||
cursor: 'crosshair',
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
/*global Tool, tool*/
|
|
|
@ -1,6 +0,0 @@
|
||||||
|
|
||||||
new Tool('zoom', {
|
|
||||||
imageCursor: 'zoom-in',
|
|
||||||
});
|
|
||||||
|
|
||||||
/*global Tool, tool*/
|
|
|
@ -0,0 +1,66 @@
|
||||||
|
<h1>Latest update</h1>
|
||||||
|
Hello there, welcome to the latest version of the Lospec Pixel Editor. As you can see, we changed
|
||||||
|
quite a lot of things. Let's go through all them, starting from this page.
|
||||||
|
|
||||||
|
<h2>Splash page</h2>
|
||||||
|
|
||||||
|
The editor now has a splash page! Besides a fancy cover image with beautiful art, on the bottom
|
||||||
|
left of the page you'll be able to create a new custom pixel. You can also use the quickstart
|
||||||
|
menu to quickly select a preset or load an existing file. It was designed by <a href="https://twitter.com/skeddles">Skeddles</a> himself!
|
||||||
|
|
||||||
|
<img src="/pixel-editor/splash.gif"/>
|
||||||
|
|
||||||
|
<strong>Pro tip: </strong> once you've created a new project, you can go back to the splash page
|
||||||
|
by clicking on <strong>Editor -> Splash page</strong>
|
||||||
|
|
||||||
|
<h2>Canvas resizing</h2>
|
||||||
|
|
||||||
|
We implemented canvas resizing! If you wanted to draw a t-rex but created a 4x4 canvas, we've got you covered!
|
||||||
|
You can now click on <strong>Edit -> Resize canvas</strong> to decrease the size of the project. Same goes if you
|
||||||
|
drew an ant on a 1024x1024 canvas, just go to <strong>Edit -> Resize canvas</strong> and decrease
|
||||||
|
the dimensions.
|
||||||
|
|
||||||
|
<img src="/pixel-editor/resize-canvas.gif"/>
|
||||||
|
|
||||||
|
<h2>Sprite scaling</h2>
|
||||||
|
|
||||||
|
In addition to the <a href="https://www.lospec.com/pixel-art-scaler">Lospec Pixel Art Scaler</a>,
|
||||||
|
you can now take advantage of the editor's built-in scaling function. Just click on <strong>Edit -> Scale sprite</strong>
|
||||||
|
to scale up or down your work. With the nearest-neighbour algorithm you'll be able to scale sprites
|
||||||
|
in a pixel-perfect manner, while with bilinear interpolation it's possible to add (or remove, if you're scaling
|
||||||
|
down a sprite) antialiasing.
|
||||||
|
|
||||||
|
<img src="/pixel-editor/scale-sprite.gif"/>
|
||||||
|
|
||||||
|
<h2>Line tool</h2>
|
||||||
|
|
||||||
|
Our contributor <a href="https://github.com/liamortiz">Liam</a> added a new line tool! Quality of
|
||||||
|
life improvement are planned for it, the rectangle and the rectangular selection tools.
|
||||||
|
|
||||||
|
<img src="/pixel-editor/line-tool.gif"/>
|
||||||
|
|
||||||
|
<h2>Advanced mode: colour picker and palette block</h2>
|
||||||
|
|
||||||
|
If you're a proud user of the advanced mode, you'll be able to try out the new colour picker: it
|
||||||
|
supports 3 colour models, 6 colour harmonies and multiple ways to input data. Next to it,
|
||||||
|
you'll find the new palette block, which lets you arrange your colours however you want, add and
|
||||||
|
remove multiple colours at once. Changes made in the palette block will update the palette list
|
||||||
|
you've always been familiar with.
|
||||||
|
|
||||||
|
<img src="/pixel-editor/palette-block.gif"/>
|
||||||
|
|
||||||
|
<h2>Other changes:</h2>
|
||||||
|
<ul>
|
||||||
|
<li>You can now move colours in the palette menu</li>
|
||||||
|
<li>Use <strong>View -> Pixel grid</strong> to show the pixel grid</li>
|
||||||
|
<li>Fixed a bunch of bugs, made the brush preview pixel perfect</li>
|
||||||
|
<li>Added <strong>Layer -> Duplicate</strong> to duplicate a layer</li>
|
||||||
|
<li>Quality of life development improvements by <a href="https://github.com/nkoder">Nkoder</a> and <a href="https://github.com/JulianWebb">Pongles</a></a></li>
|
||||||
|
<li>Canvas trimming to get rid of all the extra space you have in your sprite</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<h2>That's all folks!</h2>
|
||||||
|
That's all for this update! Hope you have fun with this new release :) </br>
|
||||||
|
- <a href="https://github.com/unsettledgames">Unsettled</a>
|
||||||
|
</br></br>
|
||||||
|
P.S.: we're always looking for contributors! Join the <a href="https://discord.com/invite/QjsgTQM">Lospec discord</a> to get in touch!
|
18
package.json
|
@ -6,11 +6,18 @@
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "node ./build.js ./build",
|
"build": "node ./build.js ./build",
|
||||||
"serve": "node ./server.js ./build 3000",
|
"serve": "node ./server.js ./build 3000",
|
||||||
"test": "npm run build && npm run serve"
|
"test": "npm run build && npm run serve",
|
||||||
|
"hot": "concurrently \"nodemon --exec npm test\" \"await tcp localhost:3000 && open-cli http://localhost:3000\"",
|
||||||
|
"hot:reload": "cross-env RELOAD=yes npm run hot"
|
||||||
},
|
},
|
||||||
"author": "Lospec",
|
"author": "Lospec",
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
|
"nodemonConfig": {
|
||||||
|
"ext": "js,hbs,scss",
|
||||||
|
"ignore": "build/"
|
||||||
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"concurrently": "^6.0.2",
|
||||||
"express": "^4.16.4",
|
"express": "^4.16.4",
|
||||||
"fs-extra": "^7.0.1",
|
"fs-extra": "^7.0.1",
|
||||||
"gulp": "^4.0.2",
|
"gulp": "^4.0.2",
|
||||||
|
@ -19,7 +26,14 @@
|
||||||
"gulp-rename": "^2.0.0",
|
"gulp-rename": "^2.0.0",
|
||||||
"gulp-sass": "^4.0.2",
|
"gulp-sass": "^4.0.2",
|
||||||
"handlebars-helper-svg": "git+https://bitbucket.org/skeddles/npm-handlebars-helper-svg-lospec-open-source.git",
|
"handlebars-helper-svg": "git+https://bitbucket.org/skeddles/npm-handlebars-helper-svg-lospec-open-source.git",
|
||||||
"open": "^6.0.0",
|
"nodemon": "^2.0.7",
|
||||||
|
"open": "^8.0.6",
|
||||||
|
"open-cli": "^6.0.1",
|
||||||
"sass": "^1.17.3"
|
"sass": "^1.17.3"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"cross-env": "7.0.3",
|
||||||
|
"reload": "^3.1.1",
|
||||||
|
"wait-cli": "^1.0.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
47
server.js
|
@ -1,37 +1,48 @@
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
const express = require('express');
|
const express = require('express');
|
||||||
|
const reload = require('reload');
|
||||||
const app = express();
|
const app = express();
|
||||||
|
|
||||||
const BUILDDIR = process.argv[2] || './build';
|
const BUILDDIR = process.argv[2] || './build';
|
||||||
const PORT = process.argv[3] || 3000;
|
const PORT = process.argv[3] || 3000;
|
||||||
|
|
||||||
//ROUTE - index.htm
|
//ROUTE - index.htm
|
||||||
app.get('/', (req, res) => {
|
app.get('/', (req, res) => {
|
||||||
res.sendFile(path.join(__dirname, BUILDDIR, 'index.htm'), {}, function (err) {
|
res.sendFile(path.join(__dirname, BUILDDIR, 'index.htm'), {}, function (err) {
|
||||||
if(err){
|
if (err) {
|
||||||
console.log('error sending file',err);
|
console.log('error sending file', err);
|
||||||
} else {
|
} else {
|
||||||
setTimeout(()=>{
|
console.log("Server: Successfully served index.html");
|
||||||
|
|
||||||
|
/*setTimeout(()=>{
|
||||||
console.log('closing server');
|
console.log('closing server');
|
||||||
server.close();
|
res.app.server.close();
|
||||||
process.exit();
|
process.exit();
|
||||||
},1000*10);
|
},1000*10); */
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Better to show landing page rather than 404 on editor page reload
|
||||||
|
app.get('/pixel-editor/app', (req, res) => {
|
||||||
|
res.redirect('/');
|
||||||
|
})
|
||||||
|
|
||||||
//ROUTE - other files
|
//ROUTE - other files
|
||||||
app.use(express.static(path.join(__dirname, BUILDDIR)));
|
app.use(express.static(path.join(__dirname, BUILDDIR)));
|
||||||
|
|
||||||
//start server
|
// "reload" module allows us to trigger webpage reload automatically on file changes, but inside pixel editor it also
|
||||||
var server = app.listen(PORT, () => {
|
// makes browser steal focus from any other window in order to ask user about unsaved changes. It might be quite
|
||||||
console.log(`\nTemp server started at http://localhost:${PORT}!`);
|
// intrusive so we decided to give option to choose preferred workflow.
|
||||||
//console.log('press ctrl+c to stop ');
|
if (process.env.RELOAD === "yes") {
|
||||||
|
reload(app).then(() => {
|
||||||
var opn = require('open');
|
//start server
|
||||||
|
app.server = app.listen(PORT, () => {
|
||||||
// opens the url in the default browser
|
console.log(`Web server listening on port ${PORT} (with reload module)`);
|
||||||
opn(`http://localhost:${PORT}`);
|
})
|
||||||
});
|
});
|
||||||
|
} else {
|
||||||
|
app.listen(PORT, () => {
|
||||||
|
console.log(`Web server listening on port ${PORT}`);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
|
@ -13,7 +13,6 @@
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body oncontextmenu="return false;">
|
<body oncontextmenu="return false;">
|
||||||
|
|
||||||
<div id="compatibility-warning">
|
<div id="compatibility-warning">
|
||||||
<div><div>
|
<div><div>
|
||||||
<p><strong>Warning: a modern, desktop, web browser is required to use this tool.</strong></p>
|
<p><strong>Warning: a modern, desktop, web browser is required to use this tool.</strong></p>
|
||||||
|
@ -34,6 +33,7 @@
|
||||||
<img src="/pixel-editor/zoom-in.png" />
|
<img src="/pixel-editor/zoom-in.png" />
|
||||||
<img src = "/pixel-editor/eraser.png"/>
|
<img src = "/pixel-editor/eraser.png"/>
|
||||||
<img src = "/pixel-editor/rectselect.png"/>
|
<img src = "/pixel-editor/rectselect.png"/>
|
||||||
|
<!-- TODO: [ELLIPSE] Where is this icon used? Do we need similar one for ellipsis? -->
|
||||||
<img src= "/pixel-editor/rectangle.png">
|
<img src= "/pixel-editor/rectangle.png">
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -91,6 +91,7 @@
|
||||||
<button>Editor</button>
|
<button>Editor</button>
|
||||||
<ul>
|
<ul>
|
||||||
<li><button id="switch-mode-button">Switch to basic mode</button></li>
|
<li><button id="switch-mode-button">Switch to basic mode</button></li>
|
||||||
|
<li><button onclick="showDialogue('splash', false)">Splash page</button></li>
|
||||||
<li><button>Settings</button></li>
|
<li><button>Settings</button></li>
|
||||||
</ul>
|
</ul>
|
||||||
</li>
|
</li>
|
||||||
|
@ -119,40 +120,46 @@
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
<li class="expanded">
|
<li class="expanded">
|
||||||
<button title="Rectangle Tool (U)" id="rectangle-button">{{svg "rectangle.svg" width="32" height="32" id = "empty-button-svg"}}
|
<button title="Rectangle Tool (U)" id="rectangle-button">{{svg "rectangle.svg" width="32" height="32" id = "rectangle-empty-button-svg"}}
|
||||||
{{svg "fullrect.svg" width="32" height="32" id = "full-button-svg" display = "none"}}</button>
|
{{svg "fullrect.svg" width="32" height="32" id = "rectangle-full-button-svg" display = "none"}}</button>
|
||||||
<button title="Increase Rectangle Size" id="rectangle-bigger-button" class="tools-menu-sub-button">{{svg "plus.svg" width="12" height="12"}}</button>
|
<button title="Increase Rectangle Size" id="rectangle-bigger-button" class="tools-menu-sub-button">{{svg "plus.svg" width="12" height="12"}}</button>
|
||||||
<button title="Decrease Rectangle Size" id="rectangle-smaller-button" class="tools-menu-sub-button">{{svg "minus.svg" width="12" height="12"}}</button>
|
<button title="Decrease Rectangle Size" id="rectangle-smaller-button" class="tools-menu-sub-button">{{svg "minus.svg" width="12" height="12"}}</button>
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
|
<!-- TODO: [ELLIPSE] Once ellipse is ready for release make it visible by default -->
|
||||||
|
<li class="expanded" id="tools-menu--ellipse" style="display: none">
|
||||||
|
<!-- TODO: [ELLIPSE] Decide on a shortcut to use. "S" was chosen without any in-team consultation. -->
|
||||||
|
<!-- TODO: [ELLIPSE] Decide on icons to use. Current ones are quickly prepared drafts and display with incorrect color. -->
|
||||||
|
<button title="Ellipse Tool (S)" id="ellipse-button">
|
||||||
|
{{svg "ellipse.svg" width="32" height="32" id = "ellipse-empty-button-svg"}}
|
||||||
|
{{svg "filledellipse.svg" width="32" height="32" id = "ellipse-full-button-svg" display = "none"}}
|
||||||
|
</button>
|
||||||
|
<button title="Increase Ellipse Size" id="ellipse-bigger-button" class="tools-menu-sub-button">{{svg "plus.svg" width="12" height="12"}}</button>
|
||||||
|
<button title="Decrease Ellipse Size" id="ellipse-smaller-button" class="tools-menu-sub-button">{{svg "minus.svg" width="12" height="12"}}</button>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li class="expanded">
|
||||||
|
<button title="Line Tool (L)" id="line-button">{{svg "line.svg" width="32" height="32"}}</button>
|
||||||
|
<button title="Increase Line Size" id="line-bigger-button" class="tools-menu-sub-button">{{svg "plus.svg" width="12" height="12"}}</button>
|
||||||
|
<button title="Decrease Line Size" id="line-smaller-button" class="tools-menu-sub-button">{{svg "minus.svg" width="12" height="12"}}</button>
|
||||||
|
</li>
|
||||||
|
|
||||||
<li><button title="Fill Tool (F)" id="fill-button">{{svg "fill.svg" width="32" height="32"}}</button></li>
|
<li><button title="Fill Tool (F)" id="fill-button">{{svg "fill.svg" width="32" height="32"}}</button></li>
|
||||||
|
|
||||||
<li><button title="Eyedropper Tool (E)" id="eyedropper-button">{{svg "eyedropper.svg" width="32" height="32"}}</button></li>
|
<li><button title="Eyedropper Tool (E)" id="eyedropper-button">{{svg "eyedropper.svg" width="32" height="32"}}</button></li>
|
||||||
|
|
||||||
<li><button title="Pan Tool (P)" id="pan-button">{{svg "pan.svg" width="32" height="32"}}</button></li>
|
<li><button title="Pan Tool (P)" id="pan-button">{{svg "pan.svg" width="32" height="32"}}</button></li>
|
||||||
<li class="expanded">
|
|
||||||
<button title="Zoom Tool (Z)" id="zoom-button">{{svg "zoom.svg" width="32" height="32"}}</button>
|
|
||||||
<button title="Zoom In" id="zoom-in-button" class="tools-menu-sub-button">{{svg "plus.svg" width="12" height="12"}}</button>
|
|
||||||
<button title="Zoom Out" id="zoom-out-button" class="tools-menu-sub-button">{{svg "minus.svg" width="12" height="12"}}</button>
|
|
||||||
</li>
|
|
||||||
|
|
||||||
<li><button title = "Rectangular Selection Tool (M)" id = "rectselect-button">{{svg "rectselect.svg" width = "32" height = "32"}}</button><li>
|
<li><button title = "Rectangular Selection Tool (M)" id = "rectselect-button">{{svg "rectselect.svg" width = "32" height = "32"}}</button><li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<!-- PALETTE -->
|
|
||||||
<ul id="colors-menu">
|
<ul id="colors-menu">
|
||||||
|
|
||||||
{{!
|
|
||||||
<li class="noshrink"><button id="current-color" class="jscolor {valueElement: 'current-color-value', styleElement: 'current-color-preview', onFineChange:'setColor(this)', width:151, position: 'left', padding:0,
|
|
||||||
borderWidth:14, borderColor: '#332f35',backgroundColor: '#332f35', insetColor:'transparent'}"><div id="current-color-preview"></div></button><input id="current-color-value" class="color-value" value="#000000" autocomplete="off" /></li>
|
|
||||||
}}
|
|
||||||
|
|
||||||
<li class="noshrink"><button title="Add Current Color To Palette" id="add-color-button">{{svg "./plus.svg" width="30" height="30"}}</button></li>
|
<li class="noshrink"><button title="Add Current Color To Palette" id="add-color-button">{{svg "./plus.svg" width="30" height="30"}}</button></li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<!-- LAYER MENU -->
|
<!-- LAYER MENU -->
|
||||||
<ul id = "layers-menu">
|
<ul id = "layers-menu">
|
||||||
<li class = "layers-menu-entry selected-layer" draggable = "true">
|
<li class = "layers-menu-entry selected-layer">
|
||||||
<canvas class = "preview-canvas"></canvas>
|
<canvas class = "preview-canvas"></canvas>
|
||||||
<ul class="layer-buttons">
|
<ul class="layer-buttons">
|
||||||
<li class = "layer-button">
|
<li class = "layer-button">
|
||||||
|
@ -232,9 +239,9 @@
|
||||||
{{svg "adjust.svg" width="20" height="20" }}
|
{{svg "adjust.svg" width="20" height="20" }}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="pop-up-container" id = "new-pixel-container">
|
<div id="pop-up-container">
|
||||||
<!-- NEW PIXEL -->
|
<!-- NEW PIXEL -->
|
||||||
<div id="new-pixel">
|
<div id="new-pixel" class="update">
|
||||||
<button class="close-button">{{svg "x.svg" width="20" height="20"}}</button>
|
<button class="close-button">{{svg "x.svg" width="20" height="20"}}</button>
|
||||||
<h1>New Pixel</h1>
|
<h1>New Pixel</h1>
|
||||||
|
|
||||||
|
@ -262,8 +269,76 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Splash page -->
|
||||||
|
<div id = "splash">
|
||||||
|
<div id="splash-news">
|
||||||
|
<div id="latest-update">
|
||||||
|
<h1>Latest updates</h1>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="splash-input">
|
||||||
|
<div id="editor-logo">
|
||||||
|
<div id="black">
|
||||||
|
<div id="sp-coverdata">
|
||||||
|
<img src="https://lospec.com/brand/lospec_logo_3x.png"/> pixel editor
|
||||||
|
<p>Version 1.4.0</p>
|
||||||
|
<a href="https://cdn.discordapp.com/attachments/506277390050131978/795660870221955082/final.png">Art by Unsettled</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="splash-menu">
|
||||||
|
<div id="sp-newpixel">
|
||||||
|
<h1>New Custom Pixel</h1>
|
||||||
|
<!-- Editor mode-->
|
||||||
|
<h2>Editor mode</h2>
|
||||||
|
<div class="sp-np-entry" id="sp-mode-palette">
|
||||||
|
<div class="button-menu">
|
||||||
|
<div class="bm-left" onclick="splashMode(event,'Basic')"><p>Basic</p></div>
|
||||||
|
<div class="sp-interface-selected bm-right" onclick="splashMode(event,'Advanced')"><p>Advanced</p></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h2>Size</h2>
|
||||||
|
<div class="sp-np-entry">
|
||||||
|
<input id="size-width-splash" value="{{#if width}}{{width}}{{else}}64{{/if}}" autocomplete="off" />{{svg "x.svg" width="16" height="16" class="dimentions-x"}}<input id="size-height-splash" value="{{#if height}}{{height}}{{else}}64{{/if}}" autocomplete="off" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h2>Palette</h2>
|
||||||
|
<button id="palette-button-splash" class="dropdown-button">Choose a palette...</button>
|
||||||
|
<div id="palette-menu-splash" class="dropdown-menu"><button id="load-palette-button-splash">Load palette...</button></div>
|
||||||
|
|
||||||
|
<div id="new-pixel-warning">Creating a new pixel will discard your current one.</div>
|
||||||
|
<div class="sp-np-entry">
|
||||||
|
<button id="create-button-splash" class="default">Create</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id = "sp-quickstart-container">
|
||||||
|
<div id="sp-quickstart-title">
|
||||||
|
Quickstart
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="sp-quickstart">
|
||||||
|
<div class="sp-template" onclick="document.getElementById('open-image-browse-holder').click()"><p>Load</p></div>
|
||||||
|
<div class="sp-template" onclick="newFromTemplate('Gameboy Color')"><p><span>New</span> Gameboy</p></div>
|
||||||
|
<div class="sp-template" onclick="newFromTemplate('Commodore 64')"><p><span>New</span> C64</p></div>
|
||||||
|
<div class="sp-template" onclick="newFromTemplate('PICO-8')"><p><span>New</span> Pico8</p></div>
|
||||||
|
<div class="sp-template" onclick="newFromTemplate('',16,16)"><p><span>New</span> 16x16</p></div>
|
||||||
|
<div class="sp-template" onclick="newFromTemplate('',32,32)"><p><span>New</span> 32x32</p></div>
|
||||||
|
<div class="sp-template" onclick="newFromTemplate('',64,64)"><p><span>New</span> 64x64</p></div>
|
||||||
|
<div class="sp-template" onclick="newFromTemplate('',128,128)"><p><span>New</span> 128x128</p></div>
|
||||||
|
<div class="sp-template" onclick="newFromTemplate('',256,256)"><p><span>New</span> 256x256</p></div>
|
||||||
|
<div class="sp-template" onclick="newFromTemplate('',512,512)"><p><span>New</span> 512x512</p></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!--SPRITE RESIZE-->
|
<!--SPRITE RESIZE-->
|
||||||
<div id = "resize-sprite">
|
<div class="update" id = "resize-sprite">
|
||||||
<button class="close-button">{{svg "x.svg" width="20" height="20"}}</button>
|
<button class="close-button">{{svg "x.svg" width="20" height="20"}}</button>
|
||||||
<h1>Scale sprite</h1>
|
<h1>Scale sprite</h1>
|
||||||
<!-- SIZE-->
|
<!-- SIZE-->
|
||||||
|
@ -313,7 +388,7 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!--CANVAS RESIZE-->
|
<!--CANVAS RESIZE-->
|
||||||
<div id = "resize-canvas">
|
<div class="update" id = "resize-canvas">
|
||||||
<button class="close-button">{{svg "x.svg" width="20" height="20"}}</button>
|
<button class="close-button">{{svg "x.svg" width="20" height="20"}}</button>
|
||||||
<h1>Resize canvas</h1>
|
<h1>Resize canvas</h1>
|
||||||
|
|
||||||
|
@ -371,6 +446,82 @@
|
||||||
<button id = "resize-canvas-confirm">Resize canvas</button>
|
<button id = "resize-canvas-confirm">Resize canvas</button>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- PALETTE -->
|
||||||
|
<div id = "palette-block">
|
||||||
|
<button class="close-button">{{svg "x.svg" width="20" height="20"}}</button>
|
||||||
|
|
||||||
|
<h1>Edit palette</h1>
|
||||||
|
|
||||||
|
<div id = "colour-picker">
|
||||||
|
<div id = "cp-modes">
|
||||||
|
<button id="cp-rgb" class="cp-selected-mode" onclick="changePickerMode(this, 'rgb')">RGB</button>
|
||||||
|
<button id="cp-hsv" onclick="changePickerMode(this, 'hsv')">HSV</button>
|
||||||
|
<button id="cp-hsl" onclick="changePickerMode(this, 'hsl')">HSL</button>
|
||||||
|
|
||||||
|
<div id="cp-colour-preview" class="cp-colour-preview"></div>
|
||||||
|
<input id="cp-hex" type="text" value="#123456" onchange="hexUpdated()"/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id = "sliders-container">
|
||||||
|
<div class = "cp-slider-entry">
|
||||||
|
<label for = "first-slider">R</label>
|
||||||
|
<input type="range" min="0" max="255" class="colour-picker-slider" id="first-slider" onmousemove="updateSliderValue(1)" onclick="updateSliderValue(1)"/>
|
||||||
|
<input type = "text" value = "128" onchange="inputChanged(this,1)"/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class = "cp-slider-entry">
|
||||||
|
<label for = "second-slider">G</label>
|
||||||
|
<input type="range" min="0" max ="255" class="colour-picker-slider" id="second-slider" onmousemove="updateSliderValue(2)" onclick="updateSliderValue(2)"/>
|
||||||
|
<input type = "text" value = "128" onchange="inputChanged(this,2)"/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class = "cp-slider-entry">
|
||||||
|
<label for = "third-slider">B</label>
|
||||||
|
<input type="range" min = "0" max = "255" class = "colour-picker-slider" id = "third-slider" onmousemove="updateSliderValue(3)" onclick="updateSliderValue(3)"/>
|
||||||
|
<input type = "text" value = "128" onchange="inputChanged(this,3)"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id = "cp-minipicker">
|
||||||
|
<input type = "range" min = "0" max = "100" id = "cp-minipicker-slider" onmousemove="miniSliderInput(event)" onclick="miniSliderInput(event)"/>
|
||||||
|
<div id="cp-canvas-container" onmousemove="movePickerIcon(event)">
|
||||||
|
<canvas id = "cp-spectrum"></canvas>
|
||||||
|
<div id="cp-active-icon" class="cp-picker-icon"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id = "cp-colours-previews">
|
||||||
|
<div class = "cp-colour-preview">
|
||||||
|
#123456
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id = "cp-colour-picking-modes">
|
||||||
|
<button class="cp-selected-mode" onclick="changePickingMode(event,'mono')">Mono</button>
|
||||||
|
<button onclick="changePickingMode(event,'analog')">Nlgs</button>
|
||||||
|
<button onclick="changePickingMode(event,'cmpt')">Cmpt</button>
|
||||||
|
<button onclick="changePickingMode(event,'tri')">Tri</button>
|
||||||
|
<button onclick="changePickingMode(event,'scmpt')">Scm</button>
|
||||||
|
<button onclick="changePickingMode(event,'tetra')">Tetra</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id = "palette-container">
|
||||||
|
<ul id = "palette-list">
|
||||||
|
<li style = "background-color:rgb(255,0,0);width:40px;height:40px;" onmousedown="startRampSelection(event)"
|
||||||
|
onmousemove="updateRampSelection(event)" onmouseup="endRampSelection(event)"></li>
|
||||||
|
<li style = "background-color:rgb(0,255,0);width:40px;height:40px;"onmousedown="startRampSelection(event)"
|
||||||
|
onmousemove="updateRampSelection(event)" onmouseup="endRampSelection(event)"></li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="pb-options">
|
||||||
|
<button title="Add colours to palette" onclick="pbAddColours()">Add colours</button>
|
||||||
|
<button title="Remove colours from palette" onclick="pbRemoveColours()">Remove colours</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div id="help">
|
<div id="help">
|
||||||
<button class="close-button">{{svg "x.svg" width="20" height="20"}}</button>
|
<button class="close-button">{{svg "x.svg" width="20" height="20"}}</button>
|
||||||
<h1>Help</h1>
|
<h1>Help</h1>
|
||||||
|
@ -381,19 +532,38 @@
|
||||||
</ul>
|
</ul>
|
||||||
<h2>Hotkeys</h2>
|
<h2>Hotkeys</h2>
|
||||||
<ul>
|
<ul>
|
||||||
<li>Pencil: <span class="keyboard-key">B</span> or <span class="keyboard-key">1</span></li>
|
<li><strong>Pencil:</strong> <span class="keyboard-key">B</span> or <span class="keyboard-key">1</span></li>
|
||||||
<li>Fill: <span class="keyboard-key">F</span> or <span class="keyboard-key">2</span></li>
|
<li><strong>Eraser:</strong> <span class="keyboard-key">R</span></li>
|
||||||
<li>Eyedropper: <span class="keyboard-key">E</span> or <span class="keyboard-key">3</span></li>
|
<li><strong>Rectangle:</strong> <span class="keyboard-key">U</span></li>
|
||||||
<li>Pan: <span class="keyboard-key">P</span> or <span class="keyboard-key">M</span> or <span class="keyboard-key">4</span></li>
|
<li><strong>Line:</strong> <span class="keyboard-key">L</span></li>
|
||||||
<li>Zoom: <span class="keyboard-key">Z</span> or <span class="keyboard-key">5</span></li>
|
<li><strong>Fill:</strong> <span class="keyboard-key">F</span> or <span class="keyboard-key">2</span></li>
|
||||||
<li>Undo: Ctrl + <span class="keyboard-key">Z</span></li>
|
<li><strong>Eyedropper:</strong> <span class="keyboard-key">E</span> or <span class="keyboard-key">3</span></li>
|
||||||
<li>Redo: Ctrl + <span class="keyboard-key">Y</span> or Ctrl + Alt + <span class="keyboard-key">Z</span></li>
|
<li><strong>Pan:</strong> <span class="keyboard-key">P</span> or <span class="keyboard-key">M</span> or <span class="keyboard-key">4</span></li>
|
||||||
|
<li><strong>Zoom:</strong> <span class="keyboard-key">Z</span> or <span class="keyboard-key">5</span></li>
|
||||||
|
<li><strong>Undo:</strong> Ctrl + <span class="keyboard-key">Z</span></li>
|
||||||
|
<li><strong>Redo:</strong> Ctrl + <span class="keyboard-key">Y</span> or Ctrl + Alt + <span class="keyboard-key">Z</span></li>
|
||||||
|
<li><strong>Rectangular selection:</strong> <span class="keyboard-key">M</span></li>
|
||||||
</ul>
|
</ul>
|
||||||
<h2>Mouse Shortcuts</h2>
|
<h2>Mouse Shortcuts</h2>
|
||||||
<ul>
|
<ul>
|
||||||
<li>Alt + Click - Eyedropper</li>
|
<li><strong>Eyedropper: </strong>Alt + Click</li>
|
||||||
<li>Space + Click - Pan</li>
|
<li><strong>Pan: </strong>Space + Click</li>
|
||||||
<li>Alt + Scroll Wheel - Zoom</li>
|
<li><strong>Zoom: </strong>Alt + Scroll Wheel</li>
|
||||||
|
</ul>
|
||||||
|
<h2>Layers</h2>
|
||||||
|
<ul>
|
||||||
|
<li>{{svg "visible.svg" width="15" height="15" class = "default-icon"}}: show / hide layer</li>
|
||||||
|
<li>{{svg "lockedpadlock.svg" width="15" height="15" class = "default-icon"}}: lock / unlock layer, when a layer is locked it's not possible to draw on it</li>
|
||||||
|
<li>Right click on a layer to open the <strong>menu</strong>:
|
||||||
|
<ul>
|
||||||
|
<li><strong>Rename:</strong> change the name of the layer</li>
|
||||||
|
<li><strong>Duplicate:</strong> duplicate the layer</li>
|
||||||
|
<li><strong>Delete:</strong> delete the layer (doesn't work if there's only one layer)</li>
|
||||||
|
<li><strong>Merge below:</strong> merges the selected the layer with the one below it</li>
|
||||||
|
<li><strong>Flatten visible:</strong> merges all the visible layers</li>
|
||||||
|
<li></strong>Flatten all:</strong> merges all the layers</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
<div id="about">
|
<div id="about">
|
||||||
|
@ -481,5 +651,6 @@
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
<script src="/pixel-editor/pixel-editor.js"></script>
|
<script src="/pixel-editor/pixel-editor.js"></script>
|
||||||
|
<script src="/reload/reload.js"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|