160 Commits

Author SHA1 Message Date
greenkeeper[bot]
5e46d8b7e2 docs(readme): add Greenkeeper badge 2017-05-06 14:44:55 +00:00
greenkeeper[bot]
e15d7b2422 chore(package): update dependencies 2017-05-06 14:43:20 +00:00
juliandescottes
a8c85b8a29 Issue #654 : stop using jquery to create the FramesList markup 2017-04-01 12:48:11 +02:00
Julian Descottes
89199f2d6a Merge pull request #644 from juliandescottes/alt-to-pick-color
select color using alt+click (fixes #623)
2017-03-02 01:52:04 +01:00
juliandescottes
98768b2e5b select color using alt+click (fixes #623) 2017-03-02 01:39:28 +01:00
Julian Descottes
62b1b8baf0 Merge pull request #642 from juliandescottes/sanitize-strings
sanitize strings coming from user inputs
2017-02-23 21:01:05 +01:00
Julian Descottes
11a063de12 sanitize strings coming from user inputs 2017-02-23 19:37:29 +01:00
Julian Descottes
6f4413f353 Merge pull request #638 from juliandescottes/nw-warning
add confirmation message when closing desktop app with unsaved changes
2017-02-19 15:14:33 +01:00
Julian Descottes
974450837e add confirmation message when closing desktop app with unsaved changes 2017-02-19 13:19:09 +01:00
juliandescottes
c6c64af2fd Use Q deferred instead of native promise (IE11) 2017-02-11 16:57:56 +01:00
juliandescottes
c68b82339c remove classList toggle with 2nd argument for IE11 2017-02-11 16:51:27 +01:00
Julian Descottes
099ff80155 Merge pull request #634 from zoeesilcock/628-fix-arrows-in-cheatsheet
Use standard HTML entities for the arrows displayed on the keyboard shortcut list
2017-02-05 12:41:30 +01:00
Zoee Silcock
f30e16386d Use standard HTML entities for the arrows displayed on the keyboard shortcut list 2017-02-05 11:47:45 +01:00
juliandescottes
08b97cb6f0 select previous layer after deleting layer 2017-02-04 14:32:23 +01:00
juliandescottes
b6fa769ba1 temporary fix for layerlist scroll 2017-02-04 14:09:27 +01:00
juliandescottes
47b09b10c5 Merge branch 'master' of https://github.com/juliandescottes/piskel 2017-02-04 13:49:20 +01:00
juliandescottes
f039a89572 disable layerlist smoothscrolling 2017-02-04 13:43:49 +01:00
Julian Descottes
e74329f04e Merge pull request #629 from GMartigny/master
Add CodeClimate
2017-02-02 19:30:40 +01:00
Guillaume Martigny
8959b201e9 Excludes lib from linting 2017-02-01 16:17:38 +01:00
Guillaume Martigny
f5491dc557 Irregular whitespace 2017-02-01 16:15:02 +01:00
Guillaume Martigny
9ce3d44cc6 Add config file for CodeClimate 2017-02-01 15:51:13 +01:00
juliandescottes
8928f3e626 Release version 0.10.0 2017-01-29 14:25:44 +01:00
juliandescottes
e04fde7084 log errors when Deserialize fails 2017-01-29 14:18:59 +01:00
juliandescottes
a25648f8e2 bump version to 0.10.0-RC2 2017-01-29 14:05:11 +01:00
juliandescottes
1918227c81 add integration tests for PNG export & scaled export 2017-01-29 13:56:06 +01:00
juliandescottes
8b1b21368c add gif export integration test 2017-01-29 02:42:42 +01:00
juliandescottes
d781df3290 add drawing test for center tool 2017-01-29 00:11:58 +01:00
Smie
35bfcb5f00 Refines Circle tool for PenSize = 1 2017-01-28 02:27:50 +01:00
Smie
cce4cde98b Corrects Circle tool for even dimensions. 2017-01-28 02:27:50 +01:00
Smie
4706587abb Rewrites Circle drawing tool to behave better with large pen sizes. 2017-01-28 02:27:50 +01:00
Julian Descottes
c0182952c9 Merge pull request #622 from juliandescottes/fix-current-colors
Fix current colors
2017-01-25 15:08:30 +01:00
juliandescottes
ba779a97a6 fix #621 : allow color worker to detect up to 256 colors 2017-01-25 15:07:55 +01:00
juliandescottes
dc149f88d6 remove extra caching from CurrentColorsService 2017-01-25 14:59:09 +01:00
Julian Descottes
ac08ff8b82 Merge pull request #620 from zoeesilcock/285-import-multiple-frames
Allow adding multiple frames with drag and drop
2017-01-24 00:11:39 +01:00
Zoee Silcock
9be332fad3 Allow adding multiple frames with drag and drop
This is a partial solution issue number #285. When dropping multiple images it creates a new frame for each one. It works great when all images are the same size and within the size of the piskel document. When the images aren't the size of the document they are positioned in the same way as single image import.
2017-01-22 11:16:18 +01:00
juliandescottes
11db5ca45c remove getBoundRectanglePixels from PixelUtils 2017-01-21 18:24:48 +01:00
Julian Descottes
37445202f3 Merge pull request #618 from smiegrin/rectanlgeRetool
Improves Rectangle tool performance.
2017-01-21 18:19:56 +01:00
Smie
f8b77403cd Improves Rectangle tool performance. 2017-01-16 11:52:22 -07:00
juliandescottes
74d8aa8c9c migrate copy script to node 2017-01-15 14:13:05 +01:00
juliandescottes
acad4df49e fix typos in perf warning popup 2017-01-15 13:33:57 +01:00
juliandescottes
af92ee6bd7 increase height of performance info popup 2017-01-15 13:26:48 +01:00
juliandescottes
a740699775 increase appengine storage limit to avoid false positive 2017-01-15 13:08:41 +01:00
juliandescottes
6ce2c00acf force color black on brush size label 2017-01-15 13:00:15 +01:00
juliandescottes
93db679dd7 release: bump version to 0.10.0-RC1 2017-01-15 12:51:10 +01:00
juliandescottes
5bde3c471d remove unused paletteController member from DrawingController 2017-01-15 12:35:38 +01:00
juliandescottes
94b1a1df4a History service gets core piskelController from public piskelController 2017-01-14 21:58:37 +01:00
Julian Descottes
8806c41892 Merge pull request #615 from juliandescottes/load-error-msg
display error message on file load error
2017-01-14 21:53:52 +01:00
juliandescottes
8cad3bb607 display error message on file load error 2017-01-14 21:37:52 +01:00
juliandescottes
552d4fa710 nits on test/js/utils/PixelUtilsTest_visit_connected.js 2017-01-14 15:44:47 +01:00
juliandescottes
b11f7a430d add unit test for getSimilarConnectedPixelsFromFrame 2017-01-14 00:56:24 +01:00
juliandescottes
a4952d0db2 fix #603 shape select fails when clicking outside drawing area 2017-01-13 15:07:05 +01:00
juliandescottes
42f4812b46 move drop image logic to FrameUtils, add unit test 2017-01-13 13:58:57 +01:00
juliandescottes
bf91c4ef62 Fix image dropper when image has to be moved 2017-01-13 13:22:21 +01:00
juliandescottes
03f37d46ac add unit tests for colorToInt 2017-01-13 03:31:02 +01:00
juliandescottes
0abd779348 fix #602 : shape selection fails with some colors 2017-01-13 02:59:59 +01:00
juliandescottes
33259e5522 nits on previewSize widget 2017-01-11 23:43:57 +01:00
juliandescottes
e8207aba57 update labels for preview size widget 2017-01-11 21:59:37 +01:00
Julian Descottes
b03b4a7c60 Merge pull request #567 from GMartigny/362-MultiSizePreview
Add a dropdown for preview size options [1x, best, full]
2017-01-11 21:56:23 +01:00
Guillaume Martigny
2f47bc433c lint fix 2017-01-11 12:32:29 +01:00
Guillaume Martigny
9cd3fa03a0 Merge branch 'master' into 362-MultiSizePreview 2017-01-11 12:28:20 +01:00
Guillaume Martigny
d4b4192d45 Final : Specific tooltip while preview size selection disabled 2017-01-11 12:23:49 +01:00
juliandescottes
d5199d38aa remove css variables from built css (edge :( ) 2017-01-11 01:13:56 +01:00
juliandescottes
ab082b9f4a set nwjs downloadURL to https 2017-01-09 09:27:49 +01:00
juliandescottes
2ec6c4b848 upgrade nwjs to 0.19.4 2017-01-09 09:19:12 +01:00
juliandescottes
2cdc999875 Fix bug when opening save panel for sprite with performance problem 2017-01-09 09:10:20 +01:00
Julian Descottes
4c6d2c1e48 Merge pull request #607 from juliandescottes/controller-tests
Add first integration tests
2017-01-09 00:43:46 +01:00
juliandescottes
95256071e1 add missing EOF new lines 2017-01-08 20:43:15 +01:00
juliandescottes
b86ec72a4e use evalLine in integration/settings/test-resize 2017-01-08 20:42:00 +01:00
juliandescottes
70085bc056 Add integration tests for resize content and resize from other origin 2017-01-08 20:38:12 +01:00
Julian Descottes
a328e4d20e add resize test checking frame content
Moved shared methods to a head.js file
2017-01-08 19:56:53 +01:00
Julian Descottes
569b508fd5 mutualize casperjsOptions in gruntfile 2017-01-08 16:39:55 +01:00
Julian Descottes
d30f6a05d1 add integration tests 2017-01-08 16:09:03 +01:00
Julian Descottes
76ae797a9e nits: typos and case 2017-01-08 11:55:09 +01:00
Julian Descottes
61c76f980a Merge pull request #605 from Sal0hcine/fix-reset-default-colours
Pressing "d" for default colors doesn't fully work. #601
2017-01-05 11:44:49 +01:00
Nick Garland
5bd113b38f Removed reset from SelectedColorsService and implemented the reset in PaletteController 2017-01-04 18:28:58 +00:00
Julian Descottes
aa5d4d4090 Merge branch 'master' of https://github.com/juliandescottes/piskel 2017-01-02 23:35:07 +01:00
Julian Descottes
398d93557a remove unused argument in FileDropperService constructor 2017-01-02 23:34:54 +01:00
Julian Descottes
0733a5a142 Merge pull request #600 from zoeesilcock/363-firefox-dnd-bug
Workaround for incorrect frame selection after re-ordering on Firefox
2017-01-02 23:27:33 +01:00
Julian Descottes
835cee37aa Merge pull request #599 from zoeesilcock/563-color-format-preference
Add a setting for changing the color format shown in the color picker
2017-01-02 23:09:19 +01:00
Zoee Silcock
2dea7faea0 Workaround for incorrect frame selection after re-ordering on Firefox
This commit is related to the following issue: #363

When a frame is dropped both `mouseup` and `click` events are triggered on Firefox. This is somwhat expected behaviour but other browsers do not trigger the `click` event.

One would expect jquery-ui to work the same across all browsers but when they got a bug report they decided to not change anything:
https://bugzilla.mozilla.org/show_bug.cgi?id=787944

The most comon workaround appears to be to use the `clone` helper in jquery-ui sortable. Unfortunately this doesn't work because the cloned frame doesn't keep the contents of the canvas. This seemed like the cleanest workaround, here are a few others:
http://stackoverflow.com/questions/947195/jquery-ui-sortable-how-can-i-cancel-the-click-event-on-an-item-thats-dragged
2016-12-29 10:09:05 +01:00
Zoee Silcock
809737908c Add a setting for changing the color format shown in the color picker 2016-12-29 08:08:13 +01:00
Julian Descottes
e43cee3c94 Merge pull request #598 from juliandescottes/disable-gallery
Disable gallery
2016-12-27 01:04:25 +01:00
Julian Descottes
b869d63211 remove useless comment 2016-12-27 00:56:18 +01:00
Julian Descottes
c0f7e7be52 jshint cleanup, move HTML to a template, add css classes 2016-12-27 00:56:18 +01:00
Julian Descottes
98527c6ded update notification style to use piskel theme colors 2016-12-27 00:56:17 +01:00
Julian Descottes
9518d570e6 display save to gallery warning if performance issue detected 2016-12-27 00:52:32 +01:00
Julian Descottes
01b9898181 improve serialization error detection for firefox 2016-12-27 00:52:32 +01:00
juliandescottes
184b2e48aa disable gallery save when if spritesheet size too big (WIP) 2016-12-27 00:52:32 +01:00
juliandescottes
e97a641e95 add constant for the max datastore accepted size 2016-12-27 00:52:32 +01:00
Julian Descottes
7eea5d672a fix hover image for minimap popup preview 2016-12-25 10:20:57 +01:00
Julian Descottes
537234b1d1 update minimap crop style 2016-12-24 11:09:12 +01:00
Julian Descottes
57936d90b1 fix jshint issue 2016-12-24 10:26:40 +01:00
Julian Descottes
ef8060c07d remove unused resizeNearestNeighbor util 2016-12-24 10:24:23 +01:00
Julian Descottes
8551a8546a fix #369 improve perf of grid rendering 2016-12-23 23:41:41 +01:00
Julian Descottes
eb84e87a13 add gold border on textfield:focus 2016-12-23 21:09:05 +01:00
Julian Descottes
ca3b789747 add user setting for seamless mode opacity 2016-12-22 23:27:00 +01:00
Julian Descottes
22dd474799 add border radius to settings export tabs 2016-12-22 22:33:04 +01:00
Julian Descottes
16151e8e95 remove text-shadow from settings panels 2016-12-22 22:21:32 +01:00
Julian Descottes
2f62be4927 simplify buttons styling 2016-12-22 14:21:21 +01:00
Julian Descottes
fbaccb03f2 introduce css variables 2016-12-22 13:06:01 +01:00
juliandescottes
dd9b1e0189 add test to check deserialized piskel contains valid frames 2016-12-21 17:20:54 +01:00
juliandescottes
dda566a218 fix broken serializer 2016-12-21 17:20:54 +01:00
Julian Descottes
2bcd354342 Merge pull request #595 from juliandescottes/unsupported-browser-popup
add a warning popup when detecting an unsupported browser
2016-12-21 15:52:45 +01:00
Julian Descottes
6cc41ee07b add a warning popup when detecting an unsupported browser 2016-12-21 15:44:04 +01:00
juliandescottes
eca9191f29 increase drawing test timeout to 30 seconds 2016-12-21 15:38:31 +01:00
Julian Descottes
83c7e950f0 Merge pull request #593 from juliandescottes/save-split
Save split
2016-12-21 12:25:54 +01:00
juliandescottes
32070efcc1 save split: add comments and cleanup 2016-12-21 12:22:50 +01:00
juliandescottes
c743334a31 Switch back to line layout 2016-12-21 11:45:34 +01:00
juliandescottes
84419a1550 split saved piskel in chunks when serialization fails 2016-12-21 02:28:11 +01:00
juliandescottes
66c941dd25 support chunked layerData in regular deserializer 2016-12-18 11:36:11 +01:00
juliandescottes
dba62d2b0d add pskl.utils.Array.chunk 2016-12-18 09:16:52 +01:00
Julian Descottes
156161f7c8 Merge pull request #574 from juliandescottes/warning-notifications
Warning notifications
2016-12-18 07:15:03 +01:00
juliandescottes
58bfe16b27 performance warning: text cleanup 2016-12-18 06:52:44 +01:00
juliandescottes
57b37971f6 add svg warning icon 2016-12-17 10:19:17 +01:00
juliandescottes
8dab74ceea add text to warning notifications popup 2016-12-16 10:04:36 +01:00
juliandescottes
d0cca52f47 issue #555: add performance warning icon, show dialog 2016-12-16 07:49:15 +01:00
juliandescottes
58cf9f8390 issue #555 detect performance issues based on sprite specs 2016-12-16 07:49:15 +01:00
juliandescottes
49109e51c9 fix js error when resizing piskel (missing fps arg) 2016-12-16 07:02:31 +01:00
Julian Descottes
27b26fe4f3 Merge pull request #589 from juliandescottes/move-fps-to-model
move fps info from preview controller to piskel model
2016-12-13 00:11:47 -10:00
Julian Descottes
b21fc0490d remove unused previewController member in ImportService 2016-12-13 11:04:03 +01:00
Julian Descottes
25ede9ffff cleanup unused arguments in PiskelFileUtils 2016-12-13 11:00:58 +01:00
Julian Descottes
37d2861352 move fps info from preview controller to piskel model 2016-12-13 09:17:34 +01:00
Julian Descottes
2fe0b842a5 update pixijs export metadata app url 2016-12-13 07:28:55 +01:00
Julian Descottes
dd7ab574b9 Merge pull request #581 from PSeitz/savejson
add pixi export
2016-12-12 20:23:45 -10:00
Julian Descottes
c2fdb65e37 simplify error template 2016-12-12 10:08:37 +01:00
Julian Descottes
aef88c8713 simplify error template 2016-12-12 09:58:56 +01:00
Julian Descottes
b3c13f1630 Merge pull request #587 from juliandescottes/create-issue-template
add issue template
2016-12-11 22:42:46 -10:00
Julian Descottes
6b0eda2998 add issue template 2016-12-12 09:35:02 +01:00
Julian Descottes
16cfffa898 Merge pull request #579 from PSeitz/piskelcopy
Copy between piskels
2016-12-09 07:18:45 -10:00
Julian Descottes
732c3c2d76 Merge pull request #577 from GMartigny/559-TooLongLayerName
Add ellipsis to overflowing layer item name #559
2016-12-06 03:19:48 +01:00
Pascal Seitz
21c7425cbc add pixi export 2016-12-05 19:11:01 +01:00
Guillaume Martigny
9ac9583455 Address comment and nit 2016-12-05 09:43:15 +01:00
Pascal Seitz
9608995d9c Fix spaces 2016-12-04 11:59:07 +01:00
Pascal Seitz
5faa985dea Copy between piskels
#272
2016-12-04 11:40:18 +01:00
Guillaume Martigny
5544f734cf 🥷 2016-12-02 17:30:38 +01:00
Guillaume Martigny
9d0d38e081 Proposal : tooltip only when layer name overflow 2016-12-02 17:21:10 +01:00
Guillaume Martigny
ad5c8182e4 Add ellipsis to overflowing layer item name #559 2016-12-01 11:43:23 +01:00
Julian Descottes
9dd403a54e Merge pull request #560 from smiegrin/master
Allows square brush size of up to 32 pixels using keyboard...
2016-11-30 23:05:03 +01:00
Guillaume Martigny
f2424ed16e Resolve options collision
Disable preview widget with tooltip
Fix animation with shortcuts
2016-11-30 11:25:47 +01:00
Smie
da0e8dec84 Stylistic changes to pixel and pen size code. 2016-11-28 08:21:30 -07:00
Julian Descottes
a5fc491e92 Merge pull request #573 from juliandescottes/update-deps
Update deps
2016-11-27 23:43:23 +01:00
Julian Descottes
e53ee0604c chore: update dateformat to 2.0.0 2016-11-27 12:08:51 +01:00
Julian Descottes
e1671202af chore: update grunt-contrib-jshint to 1.1.0 2016-11-27 12:08:18 +01:00
Julian Descottes
5e3bb9d79c chore: update nwjs to 0.18.8 2016-11-27 12:07:25 +01:00
Julian Descottes
c5c336e12c chore: update grunt-nw-builder to 3.1.0 2016-11-27 11:57:13 +01:00
Julian Descottes
85b141df52 chore: udpate karma to 1.3.0 2016-11-27 11:54:27 +01:00
Smie
a65eeee2e0 Merge branch 'master' of https://github.com/juliandescottes/piskel 2016-11-26 13:32:23 -07:00
Smie
39dba2b70f Displays pen size in selector for sizes above 4 pixels wide. 2016-11-26 13:09:07 -07:00
juliandescottes
0cb4a7831b fix drawing tests when running in browser 2016-11-24 14:18:52 +01:00
Guillaume Martigny
696d18748a Move tooltips
Change "best" option on resize
Update available options on resize and configuration change
2016-11-23 18:32:17 +01:00
Guillaume Martigny
e328b4c7b8 Add a dropdown for preview size options [1x, best, full]
https://github.com/juliandescottes/piskel/issues/362
2016-11-22 17:12:15 +01:00
Julian Descottes
5996216ae7 Merge pull request #566 from juliandescottes/memory-improvements
Memory improvements
2016-11-22 00:27:33 +01:00
Smie
8cb7a4aaf6 Permits brush size of only 1-32 pixels. 2016-11-21 12:53:14 -07:00
juliandescottes
136506da40 issue #374 throttle calls to currentColorsService update function 2016-11-20 12:08:31 +01:00
juliandescottes
d08c101b11 issue #374 cap cached frames for cachedframeprocessor to 100 2016-11-20 01:10:05 +01:00
juliandescottes
4b50dfdb5b issue #374 limit undo/redo to the last 500 states 2016-11-20 00:24:48 +01:00
Julian Descottes
e3b363d757 Fix regression when loading piskel files 2016-11-12 18:33:00 +01:00
Julian Descottes
440c28d0fd Fix regression when loading piskel files 2016-11-12 18:32:21 +01:00
Smie
a560872df7 Stylistic changes to src/js/utils/PixelUtils.js 2016-10-28 18:39:00 -06:00
Smie
aca6c28b4b Allows square brush size of up to 1,000,000 pixels using keyboard shortcuts. 2016-10-28 17:56:22 -06:00
157 changed files with 3754 additions and 870 deletions

25
.codeclimate.yml Normal file
View File

@@ -0,0 +1,25 @@
engines:
csslint:
enabled: true
duplication:
enabled: true
config:
languages:
- javascript
eslint:
enabled: true
checks:
wrap-iife:
enabled: false
fixme:
enabled: true
ratings:
paths:
- "**.css"
- "**.js"
exclude_paths:
- .github/
- bin/
- misc/
- src/js/lib/
- test/

19
.github/ISSUE_TEMPLATE vendored Normal file
View File

@@ -0,0 +1,19 @@
Bug description
(this template is intended for bug reports, erase it when requesting features/enhancements)
### Steps to reproduce the bug
1. go to piskelapp.com
2. select a tool
3. ...
### Environment details
* operating system:
* browser (or offline application version):
### Sprite details
* number of frames:
* sprite resolution:
* session duration:
Feel free to include a screenshot of the bug, a .piskel file of the sprite or anything else that can help us investigate.

View File

@@ -32,8 +32,19 @@ module.exports = function(grunt) {
var stylePaths = require('./src/piskel-style-list.js').styles;
var piskelStyles = prefixPaths(stylePaths, "src/");
var casperTestPaths = require('./test/casperjs/TestSuite.js').tests;
var casperTests = prefixPaths(casperTestPaths, "test/casperjs/");
// Casper JS tests
var casperjsOptions = [
'--baseUrl=http://' + hostname + ':' + PORT.TEST,
'--mode=?debug',
'--verbose=false',
'--includes=test/casperjs/integration/include.js',
'--log-level=info',
'--print-command=false',
'--print-file-paths=true',
];
var integrationTestPaths = require('./test/casperjs/integration/IntegrationSuite.js').tests;
var integrationTests = prefixPaths(integrationTestPaths, "test/casperjs/integration/");
var getConnectConfig = function (base, port, host) {
if (typeof base === 'string') {
@@ -90,13 +101,15 @@ module.exports = function(grunt) {
browser : true,
trailing : true,
curly : true,
globals : {'$':true, 'jQuery' : true, 'pskl':true, 'Events':true, 'Constants':true, 'console' : true, 'module':true, 'require':true, 'Q':true}
globals : {'$':true, 'jQuery' : true, 'pskl':true, 'Events':true, 'Constants':true, 'console' : true, 'module':true, 'require':true, 'Q':true, 'Promise': true}
},
files: [
// Includes
'Gruntfile.js',
'package.json',
'src/js/**/*.js',
'!src/js/**/lib/**/*.js' // Exclude lib folder (note the leading !)
// Excludes
'!src/js/**/lib/**/*.js'
]
},
@@ -160,7 +173,7 @@ module.exports = function(grunt) {
},
css : {
src : piskelStyles,
dest : 'dest/prod/css/piskel-style-packaged' + version + '.css'
dest : 'dest/tmp/css/piskel-style-packaged' + version + '.css'
}
},
@@ -211,6 +224,19 @@ module.exports = function(grunt) {
{src: ['dest/tmp/index.html'], dest: 'dest/prod/piskelapp-partials/main-partial.html'}
]
},
css: {
options: {
patterns: [{
match: /var\(--highlight-color\)/g,
replacement: "gold",
}]
},
files: [{
src: ['dest/tmp/css/piskel-style-packaged' + version + '.css'],
dest: 'dest/prod/css/piskel-style-packaged' + version + '.css'
}]
},
// remove the fake header from the desktop build
desktop: {
options: {
@@ -263,19 +289,22 @@ module.exports = function(grunt) {
},
casperjs : {
files : {
src: casperTests
},
options : {
casperjsOptions: [
'--baseUrl=http://' + hostname + ':' + PORT.TEST,
'--mode=?debug',
'--verbose=false',
'--log-level=info',
'--print-command=false',
'--print-file-paths=true',
]
drawing : {
files : {
src: ['test/casperjs/DrawingTest.js']
},
options : {
casperjsOptions: casperjsOptions
}
},
integration : {
files : {
src: integrationTests
},
options : {
casperjsOptions: casperjsOptions
}
}
},
/**
@@ -285,7 +314,8 @@ module.exports = function(grunt) {
nwjs: {
windows : {
options: {
version : "0.15.4",
downloadUrl: 'https://dl.nwjs.io/',
version : "0.19.4",
build_dir: './dest/desktop/', // destination folder of releases.
win: true,
linux32: true,
@@ -295,8 +325,9 @@ module.exports = function(grunt) {
},
macos : {
options: {
downloadUrl: 'https://dl.nwjs.io/',
osx64: true,
version : "0.15.4",
version : "0.19.4",
build_dir: './dest/desktop/'
},
src: ['./dest/prod/**/*', "./package.json", "!./dest/desktop/"]
@@ -309,18 +340,21 @@ module.exports = function(grunt) {
grunt.registerTask('lint', ['jscs:js', 'leadingIndent:css', 'jshint']);
// Run unit-tests
grunt.registerTask('unit-test', ['karma']);
// Run linting, unit tests and drawing tests
grunt.registerTask('test', ['lint', 'unit-test', 'build-dev', 'connect:test', 'casperjs']);
// Run integration tests
grunt.registerTask('integration-test', ['build-dev', 'connect:test', 'casperjs:integration']);
// Run linting, unit tests, drawing tests and integration tests
grunt.registerTask('test', ['lint', 'unit-test', 'build-dev', 'connect:test', 'casperjs:drawing', 'casperjs:integration']);
// Run the tests, even if the linting fails
grunt.registerTask('test-nolint', ['unit-test', 'build-dev', 'connect:test', 'casperjs']);
grunt.registerTask('test-nolint', ['unit-test', 'build-dev', 'connect:test', 'casperjs:drawing', 'casperjs:integration']);
// Used by optional precommit hook
grunt.registerTask('precommit', ['test']);
// BUILD TASKS
grunt.registerTask('build-index.html', ['includereplace']);
grunt.registerTask('merge-statics', ['concat:js', 'concat:css', 'uglify']);
grunt.registerTask('build', ['clean:prod', 'sprite', 'merge-statics', 'build-index.html', 'replace:mainPartial', 'copy:prod']);
grunt.registerTask('build', ['clean:prod', 'sprite', 'merge-statics', 'build-index.html', 'replace:mainPartial', 'replace:css', 'copy:prod']);
grunt.registerTask('build-dev', ['clean:dev', 'sprite', 'build-index.html', 'copy:dev']);
grunt.registerTask('desktop', ['clean:desktop', 'default', 'replace:desktop', 'nwjs:windows']);
grunt.registerTask('desktop-mac', ['clean:desktop', 'default', 'replace:desktop', 'nwjs:macos']);

View File

@@ -1,6 +1,8 @@
Piskel
======
[![Greenkeeper badge](https://badges.greenkeeper.io/juliandescottes/piskel.svg)](https://greenkeeper.io/)
[![Travis Status](https://api.travis-ci.org/juliandescottes/piskel.png?branch=master)](https://travis-ci.org/juliandescottes/piskel) [![Built with Grunt](https://cdn.gruntjs.com/builtwith.png)](http://gruntjs.com/)
A simple web-based tool for Spriting and Pixel art.

View File

@@ -0,0 +1,62 @@
const rmdir = require('rmdir');
const path = require('path');
const fs = require('fs');
const fse = require('fs-extra');
const PISKEL_PATH = path.resolve(__dirname, '..');
const PISKELAPP_PATH = path.resolve(__dirname, '../../piskel-website');
// Callbacks sorted by call sequence.
function onCopy(err) {
if (err) {
console.error('Failed to copy static files...');
return console.error(err);
}
console.log('Copied static files to piskel-website...');
let previousPartialPath = path.resolve(PISKELAPP_PATH, 'templates/editor/main-partial.html');
fs.unlink(previousPartialPath, onDeletePreviousPartial);
}
function onDeletePreviousPartial(err) {
if (err) {
console.error('Failed to delete previous main partial...');
return console.error(err);
}
console.log('Previous main partial deleted...');
fse.copy(
path.resolve(PISKELAPP_PATH, "static/editor/piskelapp-partials/main-partial.html"),
path.resolve(PISKELAPP_PATH, "templates/editor/main-partial.html"),
onCopyNewPartial
);
}
function onCopyNewPartial(err) {
if (err) {
console.error('Failed to delete previous main partial...');
return console.error(err);
}
console.log('Main partial copied...');
rmdir(
path.resolve(PISKELAPP_PATH, "static/editor/piskelapp-partials/"),
onDeleteTempPartial
);
}
function onDeleteTempPartial(err) {
if (err) {
console.error('Failed to delete temporary main partial...');
return console.error(err);
}
console.log('Temporary main partial deleted...');
console.log('Finished!');
}
fse.copy(
path.resolve(PISKEL_PATH, "dest/prod"),
path.resolve(PISKELAPP_PATH, "static/editor"),
onCopy
);

View File

@@ -11,7 +11,7 @@ module.exports = function(config) {
// Polyfill for Object.assign (missing in PhantomJS)
piskelScripts.push('./node_modules/phantomjs-polyfill-object-assign/object-assign-polyfill.js');
config.set({
// base path that will be used to resolve all patterns (eg. files, exclude)
@@ -24,7 +24,9 @@ module.exports = function(config) {
// list of files / patterns to load in the browser
files: piskelScripts,
files: piskelScripts.concat([
'./node_modules/promise-polyfill/promise.js'
]),
// list of files to exclude

View File

@@ -0,0 +1,64 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Generator: Adobe Illustrator 15.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
version="1.1"
id="Layer_1"
x="0px"
y="0px"
width="42"
height="42"
viewBox="0 0 41.999999 41.999999"
enable-background="new 0 0 99.997 69.373"
xml:space="preserve"
inkscape:version="0.48.4 r9939"
sodipodi:docname="common-warning-red.svg"
inkscape:export-filename="C:\Development\git\piskel\misc\icons\source\tool-move.png"
inkscape:export-xdpi="45"
inkscape:export-ydpi="45"><metadata
id="metadata9"><rdf:RDF><cc:Work
rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title></dc:title></cc:Work></rdf:RDF></metadata><defs
id="defs7" /><sodipodi:namedview
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="1920"
inkscape:window-height="1148"
id="namedview5"
showgrid="false"
inkscape:zoom="4"
inkscape:cx="85.612634"
inkscape:cy="3.0742543"
inkscape:window-x="-8"
inkscape:window-y="-8"
inkscape:window-maximized="1"
inkscape:current-layer="Layer_1" />
<path
style="font-size:medium;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-indent:0;text-align:start;text-decoration:none;line-height:normal;letter-spacing:normal;word-spacing:normal;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;text-anchor:start;baseline-shift:baseline;color:#000000;fill:#ff0000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:4;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate;font-family:Sans;-inkscape-font-specification:Sans"
d="m 21,4.2499995 c -0.868171,0 -1.517453,0.2705716 -2,1.03125 L 3.9999999,34.999999 c -0.6703157,1.211185 0.6156981,2.999959 2,3 l 29.9999991,0 c 1.377219,0.0098 2.645421,-1.78334 2,-3 l -15,-29.6874995 C 22.632848,4.6114627 21.868171,4.2499995 21,4.2499995 z m 0,6.2187495 11.999999,23.53125 -23.9999992,0 z"
id="rect3765"
inkscape:connector-curvature="0"
sodipodi:nodetypes="zcccccczcccc" /><path
style="font-size:medium;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-indent:0;text-align:start;text-decoration:none;line-height:normal;letter-spacing:normal;word-spacing:normal;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;text-anchor:start;baseline-shift:baseline;color:#000000;fill:#ff0000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:4;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate;font-family:Sans;-inkscape-font-specification:Sans"
d="m 20.572432,16.003047 c -0.858253,0.109012 -1.577503,1.040604 -1.572402,2.036618 L 20,25.963381 c 9.2e-5,1.066342 -0.158856,2.036512 0.765534,2.036618 l 0.468961,0 c 0.92439,-1.06e-4 0.765412,-0.970276 0.765504,-2.036618 l 1,-7.923716 c -9.2e-5,-1.066342 -0.841114,-2.036512 -1.765504,-2.036618 l -0.468961,0 c -0.0643,-0.0041 -0.128799,-0.0041 -0.193102,0 z"
id="rect3772"
inkscape:connector-curvature="0"
sodipodi:nodetypes="cccccccccc" /><path
style="fill:#ff0000;fill-opacity:1;stroke:#00ffff;stroke-width:0;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dashoffset:1.60000000000000010"
d="m 20.999298,29.000086 c 0.620953,-0.006 0.971639,0.298697 0.999273,1.499911 0.02763,1.201212 -0.347092,1.493936 -0.999273,1.499909 -0.65218,0.006 -1.002864,-0.275261 -0.999271,-1.499909 0.0036,-1.22465 0.378318,-1.493938 0.999271,-1.499911 z"
inkscape:connector-curvature="0"
sodipodi:nodetypes="zzzzz" /></svg>

After

Width:  |  Height:  |  Size: 4.1 KiB

View File

@@ -1,21 +0,0 @@
@ECHO off
SETLOCAL
SET PISKEL_PATH="C:\Development\git\piskel"
SET PISKELAPP_PATH="C:\Development\git\piskel-website"
ECHO "Copying files to piskelapp"
XCOPY "%PISKEL_PATH%\dest\prod" "%PISKELAPP_PATH%\static\editor" /e /i /h /y
ECHO "Delete previous partial"
DEL "%PISKELAPP_PATH%\templates\editor\main-partial.html"
ECHO "Copy new partial"
MOVE "%PISKELAPP_PATH%\static\editor\piskelapp-partials\main-partial.html" "%PISKELAPP_PATH%\templates\editor"
ECHO "Delete temp partial"
RMDIR "%PISKELAPP_PATH%\static\editor\piskelapp-partials\" /S /Q
PAUSE
explorer "%PISKELAPP_PATH%\"
ENDLOCAL

View File

@@ -1,6 +1,6 @@
{
"name": "piskel",
"version": "0.9.0-RC2",
"version": "0.10.0",
"description": "Pixel art editor",
"author": "Julian Descottes <julian.descottes@gmail.com>",
"contributors": [
@@ -27,32 +27,35 @@
"postversion": "git push && git push --tags && npm publish"
},
"devDependencies": {
"dateformat": "1.0.11",
"grunt": "^0.4.5",
"dateformat": "2.0.0",
"fs-extra": "3.0.1",
"grunt": "^1.0.1",
"grunt-casperjs": "^2.2.1",
"grunt-contrib-clean": "1.0.0",
"grunt-contrib-clean": "1.1.0",
"grunt-contrib-concat": "1.0.1",
"grunt-contrib-connect": "1.0.2",
"grunt-contrib-copy": "1.0.0",
"grunt-contrib-jshint": "1.0.0",
"grunt-contrib-uglify": "1.0.1",
"grunt-contrib-jshint": "1.1.0",
"grunt-contrib-uglify": "2.3.0",
"grunt-contrib-watch": "1.0.0",
"grunt-include-replace": "4.0.1",
"grunt-jscs": "2.8.0",
"grunt-karma": "1.0.0",
"grunt-include-replace": "5.0.0",
"grunt-jscs": "3.0.1",
"grunt-karma": "2.0.0",
"grunt-leading-indent": "0.2.0",
"grunt-nw-builder": "2.0.3",
"grunt-nw-builder": "3.1.0",
"grunt-open": "0.2.3",
"grunt-replace": "1.0.1",
"grunt-spritesmith": "6.3.0",
"jasmine-core": "2.1.0",
"karma": "0.13.21",
"karma-chrome-launcher": "1.0.1",
"karma-jasmine": "1.0.2",
"karma-phantomjs-launcher": "0.2.3",
"load-grunt-tasks": "3.5.0",
"grunt-spritesmith": "6.4.0",
"jasmine-core": "2.6.1",
"karma": "1.7.0",
"karma-chrome-launcher": "2.1.1",
"karma-jasmine": "1.1.0",
"karma-phantomjs-launcher": "1.0.4",
"load-grunt-tasks": "3.5.2",
"phantomjs": "2.1.7",
"phantomjs-polyfill-object-assign": "0.0.2"
"phantomjs-polyfill-object-assign": "0.0.2",
"promise-polyfill": "6.0.2",
"rmdir": "1.2.0"
},
"window": {
"title": "Piskel",

View File

@@ -1,3 +1,9 @@
@keyframes fade {
50% { opacity: 0.5; }
}
@keyframes glow {
0% { opacity: 0.66; }
50% { opacity: 1; }
100% { opacity: 0.66; }
}

View File

@@ -29,7 +29,7 @@
.local-piskel-list-head {
font-weight: bold;
color: gold;
color: var(--highlight-color);
}
.local-piskel-load-button,

View File

@@ -3,7 +3,7 @@
bottom: 10px;
left: 10px;
color : gold;
color : var(--highlight-color);
font-weight: bold;
font-size : 1.25em;
line-height: 20px;
@@ -113,8 +113,8 @@
}
.cheatsheet-shortcut-editable .cheatsheet-key {
border-color: gold;
color: gold;
border-color: var(--highlight-color);
color: var(--highlight-color);
}
.cheatsheet-shortcut-editing .cheatsheet-key {
@@ -140,7 +140,7 @@
padding : 10px;
overflow: hidden;
background-color : gold;
background-color : var(--highlight-color);
}
.cheatsheet-helptext {

View File

@@ -103,7 +103,7 @@
transition : border-color 0.2s;
}
.create-palette-color:hover {
border:1px solid gold;
border:1px solid var(--highlight-color);
}
.colors-list-drop-proxy {
@@ -111,17 +111,17 @@
}
.create-palette-new-color {
border:2px dotted gold;
border:2px dotted var(--highlight-color);
border-radius: 2px;
line-height: 40px;
text-align: center;
font-size: 20px;
color: gold;
color: var(--highlight-color);
}
.create-palette-color.selected {
border:2px solid gold;
border:2px solid var(--highlight-color);
}
.create-palette-remove-color {

View File

@@ -81,7 +81,7 @@
font-style: italic;
font-weight: normal;
text-shadow: none;
color: gold;
color: var(--highlight-color);
}
[name=smooth-resize-checkbox] {

View File

@@ -0,0 +1,68 @@
.performance-link {
display: none;
position: fixed;
bottom: 10px;
right: 10px;
z-index: 11000;
cursor: pointer;
opacity: 0;
transition : opacity 0.3s;
}
.performance-link.visible {
display: block;
opacity: 0.66;
animation: glow 2s infinite;
}
.performance-link.visible:hover {
opacity: 1;
animation: none;
}
#dialog-container.performance-info {
width: 500px;
height: 525px;
top : 50%;
left : 50%;
position : absolute;
margin-left: -250px;
margin-top: -260px;
}
.dialog-performance-info-body {
font-size: 13px;
letter-spacing: 1px;
padding: 10px 20px;
}
.dialog-performance-info-body ul {
border: 1px solid #666;
padding: 5px;
border-radius: 3px;
}
.dialog-performance-info-body li {
list-style-type: initial;
margin: 0 20px;
}
.dialog-performance-info-body sup {
color: var(--highlight-color);
cursor: pointer;
}
.show #dialog-container.performance-info {
margin-top: -300px;
}
.dialog-performance-info-body .warning-icon {
float: left;
margin-top: 10px;
}
.dialog-performance-info-body .warning-icon-info {
overflow: hidden;
margin-left: 30px;
}

View File

@@ -0,0 +1,32 @@
/************************************************************************************************/
/* Unsupported browser dialog */
/************************************************************************************************/
#dialog-container.unsupported-browser {
width: 600px;
height: 260px;
top : 50%;
left : 50%;
position : absolute;
margin-top: -130px;
margin-left: -300px;
}
.unsupported-browser .dialog-content {
font-size:1.2em;
letter-spacing: 1px;
padding:10px 20px;
overflow: auto;
}
.unsupported-browser .supported-browser-list {
padding: 5px 20px;
}
.unsupported-browser .supported-browser-list li {
list-style-type: square;
}
#current-user-agent {
color: var(--highlight-color);
}

View File

@@ -39,7 +39,7 @@
-moz-box-sizing : border-box;
border-radius: 3px;
border : 3px solid gold;
border : 3px solid var(--highlight-color);
background: #444;
overflow: auto;
}
@@ -76,7 +76,7 @@
.dialog-head {
width: 100%;
background: gold;
background: var(--highlight-color);
margin: 0;
padding: 10px;
color: black;

View File

@@ -22,6 +22,11 @@
background : #3a3a3a;
}
.textfield:focus {
border-color: var(--highlight-color);
outline: none;
}
.textfield-small {
width : 50px;
}
@@ -29,17 +34,13 @@
.button {
box-sizing: border-box;
height: 24px;
background-color: #3f3f3f;
border: 1px solid #333;
border-top-color: #666;
border-bottom-color: #222;
background-color: #666;
border-style: none;
border-radius: 2px;
cursor: pointer;
text-decoration: none;
color: white;
text-shadow: 0 -1px 0 black;
font-weight: bold;
font-size: 1rem;
text-align: center;
@@ -48,25 +49,17 @@
}
.button:hover {
text-decoration: none;
background-color: #484848;
color: gold;
color: var(--highlight-color);
}
.button-primary {
background-color: rgb(255,215,0); /* gold */
border-color: rgb(179, 164, 0);
border-top-color: white;
border-bottom-color: rgb(151, 133, 0);
background-color: var(--highlight-color);
color: black;
text-shadow: 0 1px 0 #fff;
}
.button-primary:hover {
background-color: rgb(255,235,20);
color: #333;
background-color: white;
color: black;
}
.button[disabled],
@@ -74,10 +67,6 @@
cursor:default;
background-color: #aaa;
color: #777;
text-shadow: 0 1px 0 #bbb;
border-color: #666;
border-top-color: #999;
border-bottom-color: #555;
}
.import-size-field,

View File

@@ -79,7 +79,7 @@
}
.add-frame-action:hover {
border-color: gold;
border-color: var(--highlight-color);
}
.preview-tile {
@@ -153,7 +153,7 @@
}
.preview-tile.selected {
border-color: gold;
border-color: var(--highlight-color);
}
.preview-tile.selected:after {
@@ -162,7 +162,7 @@
top: 38px;
right: -9px;
border: transparent 4px solid;
border-left-color: gold;
border-left-color: var(--highlight-color);
border-width: 6px 0 6px 6px;
border-style: solid;
}
@@ -173,6 +173,6 @@
*/
.preview-tile-drop-proxy {
border: 3px dashed gold;
border: 3px dashed var(--highlight-color);
background-color: rgba(255, 215,0, 0.2);
}

View File

@@ -1,8 +1,8 @@
.minimap-crop-frame {
position:absolute;
border:1px solid rgba(255,255,255,0.5);
z-index:100;
box-sizing : border-box;
-moz-box-sizing : border-box;
cursor : pointer;
position: absolute;
border: 2px solid gold;
z-index: 100;
box-sizing: border-box;
-moz-box-sizing: border-box;
cursor: pointer;
}

View File

@@ -6,12 +6,12 @@
max-width: 300px;
border-top-left-radius: 7px;
border: #F0C36D 1px solid;
border: #e1a325 2px solid;
border-right: 0;
border-bottom: 0;
color: #222;
background-color: #F9EDBE;
background-color: var(--highlight-color);
font-weight: bold;
font-size: 13px;
@@ -24,8 +24,6 @@
top: 6px;
right: 17px;
color: gray;
font-size: 18px;
font-weight: bold;
@@ -43,7 +41,7 @@
padding: 10px;
width: 360px;
border-top-right-radius: 2px;
border: gold 2px solid;
border: var(--highlight-color) 2px solid;
border-left: 0;
border-bottom: 0;
background-color: #444;
@@ -69,6 +67,6 @@
margin-top: 8px;
height : 4px;
width : 300px;
background : linear-gradient(to left, gold, gold) no-repeat -300px 0;
background : linear-gradient(to left, var(--highlight-color), var(--highlight-color)) no-repeat -300px 0;
background-color : black;
}

View File

@@ -16,17 +16,17 @@
}
.pen-size-option[data-size='1'] {
padding: 6px;
}
.pen-size-option[data-size='2'] {
padding: 5px;
}
.pen-size-option[data-size='3'] {
.pen-size-option[data-size='2'] {
padding: 4px;
}
.pen-size-option[data-size='4'] {
.pen-size-option[data-size='3'] {
padding: 3px;
}
.pen-size-option[data-size='4'] {
padding: 2px;
}
.pen-size-option:before {
content: '';
@@ -34,6 +34,9 @@
height: 100%;
background-color: white;
display: block;
text-align: center;
line-height: 12px;
font-size: 90%;
}
.pen-size-option:hover {
@@ -41,9 +44,14 @@
}
.pen-size-option.selected:before {
background-color: gold;
background-color: var(--highlight-color);
}
.pen-size-option.selected {
border-color: gold;
}
border-color: var(--highlight-color);
}
.pen-size-option.labeled:before {
content: attr(real-pen-size);
color: black;
}

View File

@@ -47,5 +47,5 @@ input[type="range"] {
}
a, a:visited {
color:gold;
color: var(--highlight-color);
}

View File

@@ -27,16 +27,23 @@
}
.background-picker.selected {
border-color: gold;
border-color: var(--highlight-color);
}
.settings-opacity-input {
margin: 5px;
vertical-align: middle;
}
.layer-opacity-input {
margin: 5px;
vertical-align: middle;
width: 100px;
}
.layer-opacity-text {
.seamless-opacity-input {
width: 75px;
}
.settings-opacity-text {
height: 31px;
display: inline-block;
line-height: 30px;
@@ -47,7 +54,8 @@
text-align: center;
}
.grid-width-select {
.grid-width-select,
.color-format-select {
margin: 5px 5px 0 5px;
}
@@ -55,3 +63,7 @@
/* Override the default 10px margin bottom for this panel */
margin-bottom: 15px;
}
.settings-section-application .button-primary {
margin-top: 10px;
}

View File

@@ -113,7 +113,7 @@
width: 100%;
height: 1px;
z-index: 0;
background-color: gold;
background-color: var(--highlight-color);
}
.export-tab {
@@ -121,6 +121,7 @@
cursor: pointer;
padding: 5px;
border: 1px solid transparent;
border-radius: 2px 2px 0 0;
/* Make sure the tab and its border are positioned above the :after element; */
position: relative;
z-index: 1;
@@ -128,11 +129,12 @@
.export-tab.selected,
.export-tab:hover {
color: gold;
color: var(--highlight-color);
}
.export-tab.selected {
border-color: gold gold #444 gold;
border-color: var(--highlight-color);
border-bottom-color: #444;
border-style: solid;
border-width: 1px;
}

View File

@@ -49,7 +49,7 @@
}
.resize-origin-option.selected {
border : 3px solid gold;
border : 3px solid var(--highlight-color);
}
.resize-origin-option:before {
@@ -65,7 +65,7 @@
content: '';
width: 4px;
height: 4px;
background: gold;
background: var(--highlight-color);
}
.disabled .resize-origin-option.selected:before {
@@ -85,23 +85,23 @@
}
.resize-origin-option[data-neighbor="bottom"]:before {
border-top-color: gold;
border-top-color: var(--highlight-color);
margin-left: -4px;
}
.resize-origin-option[data-neighbor="left"]:before {
border-right-color: gold;
border-right-color: var(--highlight-color);
margin-top: -4px;
margin-left: -6px;
}
.resize-origin-option[data-neighbor="top"]:before {
border-bottom-color: gold;
border-bottom-color: var(--highlight-color);
margin-top: -6px;
margin-left: -4px;
}
.resize-origin-option[data-neighbor="right"]:before {
border-left-color: gold;
border-left-color: var(--highlight-color);
margin-top: -4px;
}

View File

@@ -24,3 +24,13 @@
color: white;
font-style: normal;
}
.save-status-warning-icon {
float: left;
margin-top: 5px;
}
.save-status-warning-icon {
overflow: hidden;
padding-left: 10px;
}

View File

@@ -52,7 +52,7 @@
background-color: #444;
margin-right: 0;
padding-right: 2px;
border-left : 3px solid gold;
border-left : 3px solid var(--highlight-color);
}
/************************************************************************************************/
@@ -64,7 +64,6 @@
font-size: 12px;
font-weight: bold;
color: #ccc;
text-shadow: 1px 1px #000;
}
.settings-section .button {
@@ -77,7 +76,7 @@
text-transform: uppercase;
border-bottom: 1px #aaa solid;
padding-bottom: 5px;
color: gold;
color: var(--highlight-color);
}
.settings-description {

View File

@@ -100,8 +100,8 @@
}
.sp-palette .sp-thumb-el.sp-thumb-active {
border-color: gold;
box-shadow: 0 0 0px 1px gold;
border-color: var(--highlight-color);
box-shadow: 0 0 0px 1px var(--highlight-color);
}
.sp-input {

View File

@@ -17,11 +17,11 @@ body {
}
.no-overflow {
overflow : hidden;
overflow: hidden;
}
.image-link {
color : gold;
.highlight {
color: var(--highlight-color);
}
.pull-top,
@@ -59,7 +59,7 @@ body {
* TOOLTIPS
*/
.tooltip-shortcut {
color:gold;
color: var(--highlight-color);
}
.tooltip-container {

View File

@@ -79,7 +79,7 @@
.preview-toggle-onion-skin-enabled,
.preview-toggle-onion-skin-enabled:hover {
color : gold;
color : var(--highlight-color);
}
.preview-contextual-actions {
@@ -98,7 +98,9 @@
opacity: 1;
}
.original-size-button {
.preview-contextual-action {
float: left;
width : 18px;
height: 18px;
line-height: 18px;
@@ -113,28 +115,86 @@
font-family: Tahoma;
}
.original-size-button-enabled {
color: gold;
border-color: gold;
.preview-contextual-action-hidden {
display: none;
}
.preview-contextual-action {
float: left;
.preview-contextual-action:hover {
color: var(--highlight-color);
border-color: var(--highlight-color);
}
/**
* Drop-down in preview size selection
*/
.preview-drop-down {
float: left;
position: relative;
width : 22px;
min-height: 22px;
margin: 0 5px;
}
.preview-drop-down.preview-drop-down-disabled {
opacity: 0.5;
}
.preview-disable-overlay{
position: absolute;
width: 100%;
height: 100%;
display: none;
}
.preview-drop-down.preview-drop-down-disabled .preview-disable-overlay {
display: block;
z-index: 10;
}
.preview-drop-down .preview-contextual-action {
position: relative;
margin: 0 0 -100% 0;
opacity: 0;
transition: opacity linear .2s,
margin linear .2s;
transition-delay: 0s, .2s;
z-index: 1;
}
.preview-drop-down:hover .preview-contextual-action {
margin: 0 0 5px 0;
opacity: 1;
transition-delay: 0s, 0s;
}
.preview-drop-down .size-button-selected {
opacity: 1;
color: gold;
border-color: gold;
z-index: 5;
}
.open-popup-preview-button {
border : 2px solid white;
background-color : rgba(0,0,0,0.3);
}
.open-popup-preview-button:hover {
border-color: gold;
border-color: var(--highlight-color);
}
/**
* The regular image is provided bby the sprite icons.png+icons.css
* The regular image is provided by the sprite icons.png+icons.css
*/
.icon-minimap-popup-preview-arrow-white:hover {
background-image: url(../img/icons/minimap/minimap-popup-preview-arrow-gold.png);
background-position: 0 0;
background-size: 18px 18px;
}
@media (-webkit-min-device-pixel-ratio: 2),
(min-resolution: 192dpi) {
background-image: url(../img/icons/minimap/minimap-popup-preview-arrow-gold@2x.png);
}

View File

@@ -35,7 +35,7 @@
.layers-toggle-preview-enabled,
.layers-toggle-preview-enabled:hover {
color : gold;
color : var(--highlight-color);
}
/**
@@ -59,24 +59,34 @@
.layer-item {
position: relative;
display: flex;
height:24px;
line-height: 24px;
padding: 0 0 0 10px;
border-top: 1px solid #444;
cursor: pointer;
}
.layer-item .layer-name {
padding: 0 0 0 10px;
flex: 1 auto;
white-space: nowrap;
}
.layer-item .layer-name.overflowing-name {
overflow: hidden;
text-overflow: ellipsis;
}
.layer-item:hover {
background : #222;
}
.layer-item-opacity {
position: absolute;
right: 8px;
padding-right: 8px;
}
.current-layer-item,
.current-layer-item:hover {
background : #333;
color: gold;
color: var(--highlight-color);
}

View File

@@ -127,7 +127,7 @@
right: 0;
background-color: black;
color: gold;
color: var(--highlight-color);
font-family: Tahoma;
font-size: 0.5em;

View File

@@ -26,7 +26,17 @@
.toolbox-buttons .button {
/* Override border propery on .button elements from form.css */
border-style: solid;
border-color: #333;
border-width: 0 1px 0 0;
border-radius: 0;
background-color: #3f3f3f;
}
.toolbox-buttons .button[disabled],
.toolbox-buttons .button[disabled]:hover {
background-color: #aaa;
}
.toolbox-buttons button:last-child {

View File

@@ -17,7 +17,7 @@
position : absolute;
height : 100%;
width : 100%;
border: 3px solid gold;
border: 3px solid var(--highlight-color);
box-sizing: border-box;
}

3
src/css/variables.css Normal file
View File

@@ -0,0 +1,3 @@
html, body {
--highlight-color: gold;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 446 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 851 B

View File

@@ -71,13 +71,18 @@
@@include('templates/misc-templates.html', {})
@@include('templates/popup-preview.html', {})
<span class="cheatsheet-link icon-common-keyboard-gold" rel="tooltip" data-placement="right" title="Keyboard shortcuts">&nbsp;</span>
<span class="cheatsheet-link icon-common-keyboard-gold"
rel="tooltip" data-placement="right" title="Keyboard shortcuts">&nbsp;</span>
<div class="performance-link icon-common-warning-red"
rel="tooltip" data-placement="left" title="Performance problem detected, learn more.">&nbsp;</div>
<!-- dialogs partials -->
@@include('templates/dialogs/create-palette.html', {})
@@include('templates/dialogs/import-image.html', {})
@@include('templates/dialogs/browse-local.html', {})
@@include('templates/dialogs/cheatsheet.html', {})
@@include('templates/dialogs/performance-info.html', {})
@@include('templates/dialogs/unsupported-browser.html', {})
<!-- settings-panel partials -->
@@include('templates/settings/application.html', {})

View File

@@ -13,13 +13,16 @@ var Constants = {
MAX_WIDTH : 1024,
MAX_PALETTE_COLORS : 100,
// allow current colors service to get up to 256 colors.
// GIF generation is different if the color count goes over 256.
MAX_WORKER_COLORS : 256,
PREVIEW_FILM_SIZE : 96,
ANIMATED_PREVIEW_WIDTH : 200,
DEFAULT_PEN_COLOR : '#000000',
TRANSPARENT_COLOR : 'rgba(0, 0, 0, 0)',
SEAMLESS_MODE_OVERLAY_COLOR : 'rgba(255, 255, 255, 0.5)',
SEAMLESS_MODE_OVERLAY_COLOR : 'rgba(255, 255, 255, 0)',
CURRENT_COLORS_PALETTE_ID : '__current-colors',
@@ -49,6 +52,12 @@ var Constants = {
// TESTS
DRAWING_TEST_FOLDER : 'drawing',
// Maximum size of a sprite that can be saved on piskelapp datastore.
// This size will be compared to the length of the stringified serialization of the sprite.
// This is an approximation at best but gives correct results in most cases.
// The datastore limit is 1 MiB, which we roughly approximate to 1 million characters.
APPENGINE_SAVE_LIMIT : 1 * 1024 * 1024,
// SERVICE URLS
APPENGINE_SAVE_URL : 'save',
IMAGE_SERVICE_UPLOAD_URL : 'http://piskel-imgstore-b.appspot.com/__/upload',

View File

@@ -59,6 +59,7 @@ var Events = {
AFTER_SAVING_PISKEL: 'AFTER_SAVING_PISKEL',
FRAME_SIZE_CHANGED : 'FRAME_SIZE_CHANGED',
FPS_CHANGED : 'FPS_CHANGED',
SELECTION_CREATED: 'SELECTION_CREATED',
SELECTION_MOVE_REQUEST: 'SELECTION_MOVE_REQUEST',
@@ -79,6 +80,10 @@ var Events = {
CURRENT_COLORS_UPDATED : 'CURRENT_COLORS_UPDATED',
PERFORMANCE_REPORT_CHANGED : 'PERFORMANCE_REPORT_CHANGED',
PISKEL_FILE_IMPORT_FAILED : 'PISKEL_FILE_IMPORT_FAILED',
// Tests
MOUSE_EVENT : 'MOUSE_EVENT',
KEYBOARD_EVENT : 'KEYBOARD_EVENT',

View File

@@ -19,8 +19,9 @@
this.shortcutService.init();
var size = pskl.UserSettings.get(pskl.UserSettings.DEFAULT_SIZE);
var fps = Constants.DEFAULT.FPS;
var descriptor = new pskl.model.piskel.Descriptor('New Piskel', '');
var piskel = new pskl.model.Piskel(size.width, size.height, descriptor);
var piskel = new pskl.model.Piskel(size.width, size.height, fps, descriptor);
var layer = new pskl.model.Layer('Layer 1');
var frame = new pskl.model.Frame(size.width, size.height);
@@ -35,6 +36,8 @@
this.piskelController.init();
this.paletteImportService = new pskl.service.palette.PaletteImportService();
this.paletteImportService.init();
this.paletteService = new pskl.service.palette.PaletteService();
this.paletteService.addDynamicPalette(new pskl.service.palette.CurrentColorsPalette());
@@ -58,7 +61,6 @@
this.drawingController = new pskl.controller.DrawingController(
this.piskelController,
this.paletteController,
$('#drawing-canvas-container'));
this.drawingController.init();
@@ -76,7 +78,7 @@
this.framesListController = new pskl.controller.FramesListController(
this.piskelController,
$('#preview-list'));
$('#preview-list-wrapper').get(0));
this.framesListController.init();
this.layersListController = new pskl.controller.LayersListController(this.piskelController);
@@ -94,7 +96,7 @@
this.selectionManager = new pskl.selection.SelectionManager(this.piskelController);
this.selectionManager.init();
this.historyService = new pskl.service.HistoryService(this.corePiskelController);
this.historyService = new pskl.service.HistoryService(this.piskelController);
this.historyService.init();
this.notificationController = new pskl.controller.NotificationController();
@@ -124,12 +126,15 @@
this.storageService = new pskl.service.storage.StorageService(this.piskelController);
this.storageService.init();
this.importService = new pskl.service.ImportService(this.piskelController, this.previewController);
this.importService = new pskl.service.ImportService(this.piskelController);
this.importService.init();
this.imageUploadService = new pskl.service.ImageUploadService();
this.imageUploadService.init();
this.savedStatusService = new pskl.service.SavedStatusService(this.piskelController, this.historyService);
this.savedStatusService = new pskl.service.SavedStatusService(
this.piskelController,
this.historyService);
this.savedStatusService.init();
this.backupService = new pskl.service.BackupService(this.piskelController);
@@ -138,7 +143,9 @@
this.beforeUnloadService = new pskl.service.BeforeUnloadService(this.piskelController);
this.beforeUnloadService.init();
this.headerController = new pskl.controller.HeaderController(this.piskelController, this.savedStatusService);
this.headerController = new pskl.controller.HeaderController(
this.piskelController,
this.savedStatusService);
this.headerController.init();
this.penSizeService = new pskl.service.pensize.PenSizeService();
@@ -147,11 +154,17 @@
this.penSizeController = new pskl.controller.PenSizeController();
this.penSizeController.init();
this.fileDropperService = new pskl.service.FileDropperService(
this.piskelController,
document.querySelector('#drawing-canvas-container'));
this.fileDropperService = new pskl.service.FileDropperService(this.piskelController);
this.fileDropperService.init();
this.userWarningController = new pskl.controller.UserWarningController(this.piskelController);
this.userWarningController.init();
this.performanceReportService = new pskl.service.performance.PerformanceReportService(
this.piskelController,
this.currentColorsService);
this.performanceReportService.init();
this.drawingLoop = new pskl.rendering.DrawingLoop();
this.drawingLoop.addCallback(this.render, this);
this.drawingLoop.start();
@@ -173,20 +186,23 @@
mb.createMacBuiltin('Piskel');
gui.Window.get().menu = mb;
}
if (pskl.utils.UserAgent.isUnsupported()) {
$.publish(Events.DIALOG_DISPLAY, {
dialogId : 'unsupported-browser'
});
}
},
loadPiskel_ : function (piskelData) {
var serializedPiskel = piskelData.piskel;
pskl.utils.serialization.Deserializer.deserialize(serializedPiskel, function (piskel, extra) {
pskl.utils.serialization.Deserializer.deserialize(serializedPiskel, function (piskel) {
pskl.app.piskelController.setPiskel(piskel);
$.publish(Events.PISKEL_SAVED);
var fps = extra.fps;
if (piskelData.descriptor) {
// Backward compatibility for v2 or older
piskel.setDescriptor(piskelData.descriptor);
fps = piskelData.fps;
}
pskl.app.previewController.setFPS(fps);
});
},

View File

@@ -2,14 +2,12 @@
var ns = $.namespace('pskl.controller');
ns.DrawingController = function (piskelController, paletteController, container) {
ns.DrawingController = function (piskelController, container) {
/**
* @public
*/
this.piskelController = piskelController;
this.paletteController = paletteController;
this.dragHandler = new ns.drawing.DragHandler(this);
/**
@@ -165,6 +163,9 @@
if (event.button === Constants.MIDDLE_BUTTON) {
this.dragHandler.startDrag(event.clientX, event.clientY);
} else if (event.altKey && !this.currentToolBehavior.supportsAlt()) {
this.currentToolBehavior.hideHighlightedPixel(this.overlayFrame);
this.isPickingColor = true;
} else {
this.currentToolBehavior.hideHighlightedPixel(this.overlayFrame);
$.publish(Events.TOOL_PRESSED);
@@ -212,6 +213,8 @@
if (this.isClicked) {
if (pskl.app.mouseStateService.isMiddleButtonPressed()) {
this.dragHandler.updateDrag(x, y);
} else if (this.isPickingColor) {
// Nothing to do on mousemove when picking a color with ALT+click.
} else {
$.publish(Events.MOUSE_EVENT, [event, this]);
this.currentToolBehavior.moveToolAt(
@@ -296,39 +299,63 @@
* @private
*/
ns.DrawingController.prototype.onMouseup_ = function (event) {
var frame = this.piskelController.getCurrentFrame();
if (!this.isClicked) {
return;
}
var coords = this.getSpriteCoordinates(event.clientX, event.clientY);
if (event.changedTouches && event.changedTouches[0]) {
coords = this.getSpriteCoordinates(event.changedTouches[0].clientX, event.changedTouches[0].clientY);
}
if (this.isClicked) {
// A mouse button was clicked on the drawing canvas before this mouseup event,
// the user was probably drawing on the canvas.
// Note: The mousemove movement (and the mouseup) may end up outside
// of the drawing canvas.
this.isClicked = false;
// A mouse button was clicked on the drawing canvas before this mouseup event,
// the user was probably drawing on the canvas.
// Note: The mousemove movement (and the mouseup) may end up outside
// of the drawing canvas.
if (pskl.app.mouseStateService.isMiddleButtonPressed()) {
if (this.dragHandler.isDragging()) {
this.dragHandler.stopDrag();
} else if (frame.containsPixel(coords.x, coords.y)) {
var color = pskl.utils.intToColor(frame.getPixel(coords.x, coords.y));
$.publish(Events.SELECT_PRIMARY_COLOR, [color]);
}
} else {
this.currentToolBehavior.releaseToolAt(
coords.x,
coords.y,
this.piskelController.getCurrentFrame(),
this.overlayFrame,
event
);
this.isClicked = false;
$.publish(Events.TOOL_RELEASED);
}
$.publish(Events.MOUSE_EVENT, [event, this]);
var isMiddleButton = pskl.app.mouseStateService.isMiddleButtonPressed();
var isMiddleClick = isMiddleButton && !this.dragHandler.isDragging();
var isMiddleDrag = isMiddleButton && this.dragHandler.isDragging();
if (this.isPickingColor || isMiddleClick) {
// Picking color after ALT+click or middle mouse button click.
this.pickColorAt_(coords);
this.isPickingColor = false;
} else if (isMiddleDrag) {
// Stop the drag handler after a middle button drag action.
this.dragHandler.stopDrag();
} else {
// Regular tool click, release the current tool.
this.currentToolBehavior.releaseToolAt(
coords.x,
coords.y,
this.piskelController.getCurrentFrame(),
this.overlayFrame,
event
);
$.publish(Events.TOOL_RELEASED);
}
$.publish(Events.MOUSE_EVENT, [event, this]);
};
/**
* Send a COLOR selection event for the color contained at the provided coordinates.
* No-op if the coordinate is outside of the drawing canvas.
* @param {Object} coords {x: Number, y: Number}
*/
ns.DrawingController.prototype.pickColorAt_ = function (coords) {
var frame = this.piskelController.getCurrentFrame();
if (!frame.containsPixel(coords.x, coords.y)) {
return;
}
var color = pskl.utils.intToColor(frame.getPixel(coords.x, coords.y));
var isRightButton = pskl.app.mouseStateService.isRightButtonPressed();
var evt = isRightButton ? Events.SELECT_SECONDARY_COLOR : Events.SELECT_PRIMARY_COLOR;
$.publish(evt, [color]);
};
/**

View File

@@ -11,10 +11,12 @@
ns.FramesListController = function (piskelController, container) {
this.piskelController = piskelController;
this.container = container;
this.previewList = container.querySelector('#preview-list');
this.refreshZoom_();
this.redrawFlag = true;
this.regenerateDomFlag = true;
this.justDropped = false;
this.cachedFrameProcessor = new pskl.model.frame.CachedFrameProcessor();
this.cachedFrameProcessor.setFrameProcessor(this.frameToPreviewCanvas_.bind(this));
@@ -30,8 +32,9 @@
$.subscribe(Events.PISKEL_RESET, this.refreshZoom_.bind(this));
$('#preview-list-scroller').scroll(this.updateScrollerOverflows.bind(this));
this.container.get(0).addEventListener('click', this.onContainerClick_.bind(this));
this.previewListScroller = document.querySelector('#preview-list-scroller');
this.previewListScroller.addEventListener('scroll', this.updateScrollerOverflows.bind(this));
this.container.addEventListener('click', this.onContainerClick_.bind(this));
this.updateScrollerOverflows();
};
@@ -63,11 +66,11 @@
};
ns.FramesListController.prototype.updateScrollerOverflows = function () {
var scroller = $('#preview-list-scroller');
var scrollerHeight = scroller.height();
var scrollTop = scroller.scrollTop();
var scrollerContentHeight = $('#preview-list').height();
var treshold = $('.top-overflow').height();
var scroller = this.previewListScroller;
var scrollerHeight = scroller.offsetHeight;
var scrollTop = scroller.scrollTop;
var scrollerContentHeight = this.previewList.offsetHeight;
var treshold = this.container.querySelector('.top-overflow').offsetHeight;
var overflowTop = false;
var overflowBottom = false;
@@ -80,9 +83,8 @@
overflowBottom = true;
}
}
var wrapper = $('#preview-list-wrapper');
wrapper.toggleClass('top-overflow-visible', overflowTop);
wrapper.toggleClass('bottom-overflow-visible', overflowBottom);
this.container.classList.toggle('top-overflow-visible', overflowTop);
this.container.classList.toggle('bottom-overflow-visible', overflowBottom);
};
ns.FramesListController.prototype.onContainerClick_ = function (event) {
@@ -96,21 +98,21 @@
if (action === ACTION.CLONE) {
this.piskelController.duplicateFrameAt(index);
var clonedTile = this.createPreviewTile_(index + 1);
this.container.get(0).insertBefore(clonedTile, this.tiles[index].nextSibling);
this.previewList.insertBefore(clonedTile, this.tiles[index].nextSibling);
this.tiles.splice(index, 0, clonedTile);
this.updateScrollerOverflows();
} else if (action === ACTION.DELETE) {
this.piskelController.removeFrameAt(index);
this.container.get(0).removeChild(this.tiles[index]);
this.previewList.removeChild(this.tiles[index]);
this.tiles.splice(index, 1);
this.updateScrollerOverflows();
} else if (action === ACTION.SELECT) {
} else if (action === ACTION.SELECT && !this.justDropped) {
this.piskelController.setCurrentFrameIndex(index);
} else if (action === ACTION.NEW_FRAME) {
this.piskelController.addFrame();
var newtile = this.createPreviewTile_(this.tiles.length);
this.tiles.push(newtile);
this.container.get(0).insertBefore(newtile, this.addFrameTile);
this.previewList.insertBefore(newtile, this.addFrameTile);
this.updateScrollerOverflows();
}
@@ -146,7 +148,7 @@
}
// Hide/Show buttons if needed
var buttons = this.container.get(0).querySelectorAll('.delete-frame-action, .dnd-action');
var buttons = this.container.querySelectorAll('.delete-frame-action, .dnd-action');
var display = (this.piskelController.getFrameCount() > 1) ? 'block' : 'none';
for (i = 0, length = buttons.length; i < length; i++) {
buttons[i].style.display = display;
@@ -157,7 +159,8 @@
};
ns.FramesListController.prototype.createPreviews_ = function () {
this.container.html('');
this.previewList.innerHTML = '';
// Manually remove tooltips since mouseout events were shortcut by the DOM refresh:
$('.tooltip').remove();
@@ -165,7 +168,7 @@
for (var i = 0 ; i < frameCount ; i++) {
var tile = this.createPreviewTile_(i);
this.container.append(tile);
this.previewList.appendChild(tile);
this.tiles[i] = tile;
}
// Append 'new empty frame' button
@@ -175,7 +178,7 @@
newFrameButton.setAttribute('data-tile-action', ACTION.NEW_FRAME);
newFrameButton.innerHTML = '<div class="add-frame-action-icon icon-frame-plus-white">' +
'</div><div class="label">Add new frame</div>';
this.container.append(newFrameButton);
this.previewList.appendChild(newFrameButton);
this.addFrameTile = newFrameButton;
this.updateScrollerOverflows();
@@ -185,15 +188,15 @@
* @private
*/
ns.FramesListController.prototype.initDragndropBehavior_ = function () {
$('#preview-list').sortable({
$(this.previewList).sortable({
placeholder: 'preview-tile preview-tile-drop-proxy',
update: $.proxy(this.onUpdate_, this),
stop: $.proxy(this.onSortableStop_, this),
items: '.preview-tile',
axis: 'y',
tolerance: 'pointer'
});
$('#preview-list').disableSelection();
$(this.previewList).disableSelection();
};
/**
@@ -211,6 +214,17 @@
this.flagForRedraw_();
};
/**
* @private
*/
ns.FramesListController.prototype.onSortableStop_ = function (event, ui) {
this.justDropped = true;
this.resizeTimer = window.setTimeout($.proxy(function() {
this.justDropped = false;
}, this), 200);
};
/**
* @private
* TODO(vincz): clean this giant rendering function & remove listeners.

View File

@@ -31,7 +31,7 @@
}
if (this.piskelName_) {
this.piskelName_.innerHTML = name;
this.piskelName_.textContent = name;
}
} catch (e) {
console.warn('Could not update header : ' + e.message);

View File

@@ -27,15 +27,21 @@
};
ns.LayersListController.prototype.renderLayerList_ = function () {
// Backup scroll before refresh.
var scrollTop = this.layersListEl.scrollTop;
this.layersListEl.innerHTML = '';
var layers = this.piskelController.getLayers();
layers.forEach(this.addLayerItem.bind(this));
this.updateButtonStatus_();
// Restore scroll
this.layersListEl.scrollTop = scrollTop;
// Ensure the currently the selected layer is visible.
var currentLayerEl = this.layersListEl.querySelector('.current-layer-item');
if (currentLayerEl) {
currentLayerEl.scrollIntoView({behavior: 'smooth'});
currentLayerEl.scrollIntoViewIfNeeded(false);
}
};
@@ -99,6 +105,12 @@
});
var layerItem = pskl.utils.Template.createFromHTML(layerItemHtml);
this.layersListEl.insertBefore(layerItem, this.layersListEl.firstChild);
if (layerItem.offsetWidth < layerItem.scrollWidth) {
$(layerItem).find('.layer-name')
.addClass('overflowing-name')
.attr('title', layer.getName())
.tooltip();
}
};
ns.LayersListController.prototype.onClick_ = function (evt) {
@@ -106,8 +118,8 @@
var index;
if (el.classList.contains('button')) {
this.onButtonClick_(el);
} else if (el.classList.contains('layer-item')) {
index = el.dataset.layerIndex;
} else if (el.classList.contains('layer-name')) {
index = pskl.utils.Dom.getData(el, 'layerIndex');
this.piskelController.setCurrentLayerIndex(parseInt(index, 10));
} else if (el.classList.contains('layer-item-opacity')) {
index = pskl.utils.Dom.getData(el, 'layerIndex');

View File

@@ -1,6 +1,10 @@
(function () {
var ns = $.namespace('pskl.controller');
/**
* The PaletteController is responsible for handling the two color picker
* widgets found in the left column, below the tools.
*/
ns.PaletteController = function () {};
/**
@@ -92,7 +96,8 @@
};
ns.PaletteController.prototype.resetColors = function () {
pskl.app.selectedColorsService.reset();
this.setPrimaryColor_(Constants.DEFAULT_PEN_COLOR);
this.setSecondaryColor_(Constants.TRANSPARENT_COLOR);
};
/**

View File

@@ -100,7 +100,7 @@
ns.PalettesListController.prototype.getCurrentColorIndex_ = function () {
var currentIndex = 0;
var selectedColor = document.querySelector('.' + PRIMARY_COLOR_CLASSNAME);
if (selectedColor) {
if (selectedColor) {
currentIndex = parseInt(selectedColor.dataset.colorIndex, 10);
}
return currentIndex;

View File

@@ -25,9 +25,17 @@
};
ns.PenSizeController.prototype.updateSelectedOption_ = function () {
pskl.utils.Dom.removeClass('labeled', this.container);
pskl.utils.Dom.removeClass('selected', this.container);
var size = pskl.app.penSizeService.getPenSize();
var selectedOption = this.container.querySelector('[data-size="' + size + '"]');
var selectedOption;
if (size <= 4) {
selectedOption = this.container.querySelector('[data-size="' + size + '"]');
} else {
selectedOption = this.container.querySelector('[data-size="4"]');
selectedOption.classList.add('labeled');
selectedOption.setAttribute('real-pen-size', size);
}
if (selectedOption) {
selectedOption.classList.add('selected');
}

View File

@@ -0,0 +1,59 @@
(function () {
var ns = $.namespace('pskl.controller');
ns.UserWarningController = function (piskelController, currentColorsService) {
this.piskelController = piskelController;
this.currentColorsService = currentColorsService;
};
// This method is not attached to the prototype because we want to trigger it
// from markup generated for a notification message.
ns.UserWarningController.showPerformanceInfoDialog = function () {
$.publish(Events.DIALOG_DISPLAY, {
dialogId: 'performance-info'
});
};
ns.UserWarningController.prototype.init = function () {
$.subscribe(Events.PERFORMANCE_REPORT_CHANGED, this.onPerformanceReportChanged_.bind(this));
this.performanceLinkEl = document.querySelector('.performance-link');
pskl.utils.Event.addEventListener(
this.performanceLinkEl,
'click',
ns.UserWarningController.showPerformanceInfoDialog,
this
);
};
ns.UserWarningController.prototype.destroy = function () {
pskl.utils.Event.removeAllEventListeners(this);
this.performanceLinkEl = null;
};
ns.UserWarningController.prototype.onPerformanceReportChanged_ = function (event, report) {
var shouldDisplayWarning = report.hasProblem();
// Check if a performance warning is already displayed.
var isWarningDisplayed = this.performanceLinkEl.classList.contains('visible');
// Show/hide the performance warning link depending on the received report.
if (shouldDisplayWarning) {
this.performanceLinkEl.classList.add('visible');
} else {
this.performanceLinkEl.classList.remove('visible');
}
// Show a notification message if the new report indicates a performance issue
// and we were not displaying a warning before.
if (shouldDisplayWarning && !isWarningDisplayed) {
$.publish(Events.SHOW_NOTIFICATION, [{
'content': 'Performance problem detected, ' +
'<a href="#" style="color:red;"' +
'onclick="pskl.controller.UserWarningController.showPerformanceInfoDialog()">' +
'learn more?</a>',
'hideDelay' : 5000
}]);
}
};
})();

View File

@@ -48,7 +48,10 @@
keys.forEach((function (key) {
var date = pskl.utils.DateUtils.format(key.date, '{{Y}}/{{M}}/{{D}} {{H}}:{{m}}');
html += pskl.utils.Template.replace(this.localStorageItemTemplate_, {name : key.name, date : date});
html += pskl.utils.Template.replace(this.localStorageItemTemplate_, {
name : key.name,
date : date
});
}).bind(this));
var tableBody_ = this.piskelList.get(0).tBodies[0];

View File

@@ -152,10 +152,10 @@
key = key.replace('ctrl', 'cmd');
key = key.replace('alt', 'option');
}
key = key.replace(/left/i, '&#65513;');
key = key.replace(/up/i, '&#65514;');
key = key.replace(/right/i, '&#65515;');
key = key.replace(/down/i, '&#65516;');
key = key.replace(/left/i, '&larr;');
key = key.replace(/up/i, '&uarr;');
key = key.replace(/right/i, '&rarr;');
key = key.replace(/down/i, '&darr;');
key = key.replace(/>/g, '&gt;');
key = key.replace(/</g, '&lt;');
// add spaces around '+' delimiters

View File

@@ -17,6 +17,14 @@
'import-image' : {
template : 'templates/dialogs/import-image.html',
controller : ns.ImportImageController
},
'performance-info' : {
template : 'templates/dialogs/performance-info.html',
controller : ns.PerformanceInfoController
},
'unsupported-browser' : {
template : 'templates/dialogs/unsupported-browser.html',
controller : ns.UnsupportedBrowserController
}
};

View File

@@ -135,7 +135,7 @@
this.importedImage_.onload = function () {};
var fileName = this.extractFileNameFromPath_(this.file_.name);
this.fileNameContainer.html(fileName);
this.fileNameContainer.text(fileName);
this.fileNameContainer.attr('title', fileName);
this.resizeWidth.val(w);

View File

@@ -0,0 +1,11 @@
(function () {
var ns = $.namespace('pskl.controller.dialogs');
ns.PerformanceInfoController = function () {};
pskl.utils.inherit(ns.PerformanceInfoController, ns.AbstractDialogController);
ns.PerformanceInfoController.prototype.init = function () {
this.superclass.init.call(this);
};
})();

View File

@@ -0,0 +1,13 @@
(function () {
var ns = $.namespace('pskl.controller.dialogs');
ns.UnsupportedBrowserController = function () {};
pskl.utils.inherit(ns.UnsupportedBrowserController, ns.AbstractDialogController);
ns.UnsupportedBrowserController.prototype.init = function () {
this.superclass.init.call(this);
var currentUserAgentElement = document.querySelector('#current-user-agent');
currentUserAgentElement.innerText = pskl.utils.UserAgent.getDisplayName();
};
})();

View File

@@ -35,15 +35,16 @@
return this.piskel.getWidth();
};
/**
* TODO : this should be removed
* FPS should be stored in the Piskel model and not in the
* previewController
* Then piskelController should be able to return this information
* @return {Number} Frames per second for the current animation
*/
ns.PiskelController.prototype.getFPS = function () {
return pskl.app.previewController.getFPS();
return this.piskel.fps;
};
ns.PiskelController.prototype.setFPS = function (fps) {
if (typeof fps !== 'number') {
return;
}
this.piskel.fps = fps;
$.publish(Events.FPS_CHANGED);
};
ns.PiskelController.prototype.getLayers = function () {
@@ -269,12 +270,16 @@
};
ns.PiskelController.prototype.removeLayerAt = function (index) {
if (this.getLayers().length > 1) {
var layer = this.getLayerAt(index);
if (layer) {
this.piskel.removeLayer(layer);
this.setCurrentLayerIndex(0);
}
if (!this.hasLayerAt(index)) {
return;
}
var layer = this.getLayerAt(index);
this.piskel.removeLayer(layer);
// Update the selected layer if needed.
if (this.getCurrentLayerIndex() === index) {
this.setCurrentLayerIndex(Math.max(0, index - 1));
}
};

View File

@@ -1,6 +1,12 @@
(function () {
var ns = $.namespace('pskl.controller.piskel');
/**
* The PublicPiskelController is a decorator on PiskelController, provides the same API
* but will fire RESET/SAVE events when appropriate so that other objects get notified
* when important changes are made on the current Piskel.
* @param {PiskelController} piskelController the wrapped PiskelController
*/
ns.PublicPiskelController = function (piskelController) {
this.piskelController = piskelController;
pskl.utils.wrap(this, this.piskelController);
@@ -38,6 +44,10 @@
pskl.app.shortcutService.registerShortcut(shortcuts.MISC.DUPLICATE_FRAME, this.duplicateCurrentFrame.bind(this));
};
ns.PublicPiskelController.prototype.getWrappedPiskelController = function () {
return this.piskelController;
};
ns.PublicPiskelController.prototype.setPiskel = function (piskel, preserveState) {
this.piskelController.setPiskel(piskel, preserveState);

View File

@@ -5,9 +5,6 @@
var PREVIEW_SIZE = 200;
var RENDER_MINIMUM_DELAY = 300;
var ONION_SKIN_SHORTCUT = 'alt+O';
var ORIGINAL_SIZE_SHORTCUT = 'alt+1';
ns.PreviewController = function (piskelController, container) {
this.piskelController = piskelController;
this.container = container;
@@ -16,28 +13,33 @@
this.currentIndex = 0;
this.onionSkinShortcut = pskl.service.keyboard.Shortcuts.MISC.ONION_SKIN;
this.originalSizeShortcut = pskl.service.keyboard.Shortcuts.MISC.X1_PREVIEW;
this.lastRenderTime = 0;
this.renderFlag = true;
/**
* !! WARNING !! ALL THE INITIALISATION BELOW SHOULD BE MOVED TO INIT()
* IT WILL STAY HERE UNTIL WE CAN REMOVE SETFPS (see comment below)
*/
this.fpsRangeInput = document.querySelector('#preview-fps');
this.fpsCounterDisplay = document.querySelector('#display-fps');
this.openPopupPreview = document.querySelector('.open-popup-preview-button');
this.originalSizeButton = document.querySelector('.original-size-button');
this.previewSizeDropdown = document.querySelector('.preview-drop-down');
this.previewSizes = {
original: {
button: document.querySelector('.original-size-button'),
shortcut: pskl.service.keyboard.Shortcuts.MISC.X1_PREVIEW,
tooltip: 'Original size preview'
},
best: {
button: document.querySelector('.best-size-button'),
shortcut: pskl.service.keyboard.Shortcuts.MISC.BEST_PREVIEW,
tooltip: 'Best size preview'
},
full: {
button: document.querySelector('.full-size-button'),
shortcut: pskl.service.keyboard.Shortcuts.MISC.FULL_PREVIEW,
tooltip: 'Full size preview'
}
};
this.toggleOnionSkinButton = document.querySelector('.preview-toggle-onion-skin');
/**
* !! WARNING !! THIS SHOULD REMAIN HERE UNTIL, BECAUSE THE PREVIEW CONTROLLER
* IS THE SOURCE OF TRUTH AT THE MOMENT WHEN IT COMES TO FPSs
* IT WILL BE QUERIED BY OTHER OBJECTS SO DEFINE IT AS SOON AS POSSIBLE
*/
this.setFPS(Constants.DEFAULT.FPS);
this.renderer = new pskl.rendering.frame.BackgroundImageFrameRenderer(this.container);
this.popupPreviewController = new ns.PopupPreviewController(piskelController);
};
@@ -48,42 +50,117 @@
document.querySelector('.right-column').style.width = Constants.ANIMATED_PREVIEW_WIDTH + 'px';
pskl.utils.Event.addEventListener(this.toggleOnionSkinButton, 'click', this.toggleOnionSkin_, this);
pskl.utils.Event.addEventListener(this.openPopupPreview, 'click', this.onOpenPopupPreviewClick_, this);
pskl.utils.Event.addEventListener(this.originalSizeButton, 'click', this.onOriginalSizeButtonClick_, this);
var addEvent = pskl.utils.Event.addEventListener;
addEvent(this.toggleOnionSkinButton, 'click', this.toggleOnionSkin_, this);
addEvent(this.openPopupPreview, 'click', this.onOpenPopupPreviewClick_, this);
pskl.app.shortcutService.registerShortcut(this.onionSkinShortcut, this.toggleOnionSkin_.bind(this));
pskl.app.shortcutService.registerShortcut(this.originalSizeShortcut, this.onOriginalSizeButtonClick_.bind(this));
var registerShortcut = pskl.app.shortcutService.registerShortcut.bind(pskl.app.shortcutService);
registerShortcut(this.onionSkinShortcut, this.toggleOnionSkin_.bind(this));
var onionSkinTooltip = pskl.utils.TooltipFormatter.format('Toggle onion skin', this.onionSkinShortcut);
this.toggleOnionSkinButton.setAttribute('title', onionSkinTooltip);
for (var size in this.previewSizes) {
if (this.previewSizes.hasOwnProperty(size)) {
var previewSize = this.previewSizes[size];
addEvent(previewSize.button, 'click', this.onChangePreviewSize_, this, size);
registerShortcut(previewSize.shortcut, this.onChangePreviewSize_.bind(this, size));
var tooltip = pskl.utils.TooltipFormatter.format(previewSize.tooltip, previewSize.shortcut);
previewSize.button.setAttribute('title', tooltip);
}
}
$.subscribe(Events.FRAME_SIZE_CHANGED, this.onFrameSizeChange_.bind(this));
$.subscribe(Events.USER_SETTINGS_CHANGED, $.proxy(this.onUserSettingsChange_, this));
$.subscribe(Events.PISKEL_SAVE_STATE, this.setRenderFlag_.bind(this, true));
$.subscribe(Events.FPS_CHANGED, this.updateFPS_.bind(this));
// On PISKEL_RESET, set the render flag and update the FPS input
$.subscribe(Events.PISKEL_RESET, this.setRenderFlag_.bind(this, true));
$.subscribe(Events.PISKEL_RESET, this.updateFPS_.bind(this));
this.initTooltips_();
this.updatePreviewSizeButtons_();
this.popupPreviewController.init();
this.updateZoom_();
this.updateOnionSkinPreview_();
this.updateOriginalSizeButton_();
this.selectPreviewSizeButton_();
this.updateFPS_();
this.updateMaxFPS_();
this.updateContainerDimensions_();
};
ns.PreviewController.prototype.initTooltips_ = function () {
var onionSkinTooltip = pskl.utils.TooltipFormatter.format('Toggle onion skin', this.onionSkinShortcut);
this.toggleOnionSkinButton.setAttribute('title', onionSkinTooltip);
var originalSizeTooltip = pskl.utils.TooltipFormatter.format('Original size preview', this.originalSizeShortcut);
this.originalSizeButton.setAttribute('title', originalSizeTooltip);
ns.PreviewController.prototype.updatePreviewSizeButtons_ = function () {
var fullZoom = this.calculateZoom_();
var bestZoom = Math.floor(fullZoom);
var seamlessModeEnabled = pskl.UserSettings.get(pskl.UserSettings.SEAMLESS_MODE);
var validSizes;
if (fullZoom < 1) {
this.disablePreviewSizeWidget_('No other option available');
validSizes = ['full'];
} else if (fullZoom === 1) {
this.disablePreviewSizeWidget_('No other option available');
validSizes = ['original'];
} else if (seamlessModeEnabled) {
this.disablePreviewSizeWidget_('Disabled in seamless mode');
validSizes = ['original'];
} else {
this.enablePreviewSizeWidget_();
if (fullZoom === bestZoom) {
// If the full zoom is the same as the best zoom, display the best option only as
// it gives the exact factor information.
validSizes = ['original', 'best'];
} else if (bestZoom === 1) {
// If best zoom is 1x, remove it as it is redundant with the original option.
validSizes = ['full', 'original'];
} else {
validSizes = ['full', 'original', 'best'];
}
}
// Update buttons content and status.
this.previewSizes.best.button.textContent = Math.floor(fullZoom) + 'x';
for (var size in this.previewSizes) {
if (this.previewSizes.hasOwnProperty(size)) {
var previewSize = this.previewSizes[size];
var isSizeEnabled = validSizes.indexOf(size) != -1;
// classList.toggle is not available on IE11.
if (isSizeEnabled) {
previewSize.button.classList.remove('preview-contextual-action-hidden');
} else {
previewSize.button.classList.add('preview-contextual-action-hidden');
}
}
}
// Update the selected preview size if the currently selected size is not valid.
var selectedSize = pskl.UserSettings.get(pskl.UserSettings.PREVIEW_SIZE);
if (validSizes.indexOf(selectedSize) === -1) {
this.onChangePreviewSize_(validSizes[0]);
}
};
ns.PreviewController.prototype.enablePreviewSizeWidget_ = function () {
this.previewSizeDropdown.classList.remove('preview-drop-down-disabled');
};
ns.PreviewController.prototype.disablePreviewSizeWidget_ = function (reason) {
// The .preview-disable-overlay is displayed on top of the preview size widget
document.querySelector('.preview-disable-overlay').setAttribute('data-original-title', reason);
this.previewSizeDropdown.classList.add('preview-drop-down-disabled');
};
ns.PreviewController.prototype.onOpenPopupPreviewClick_ = function () {
this.popupPreviewController.open();
};
ns.PreviewController.prototype.onOriginalSizeButtonClick_ = function () {
var isEnabled = pskl.UserSettings.get(pskl.UserSettings.ORIGINAL_SIZE_PREVIEW);
pskl.UserSettings.set(pskl.UserSettings.ORIGINAL_SIZE_PREVIEW, !isEnabled);
ns.PreviewController.prototype.onChangePreviewSize_ = function (size) {
var previewSize = this.previewSizes[size];
var isEnabled = !previewSize.button.classList.contains('preview-contextual-action-hidden');
if (isEnabled) {
pskl.UserSettings.set(pskl.UserSettings.PREVIEW_SIZE, size);
}
};
ns.PreviewController.prototype.onUserSettingsChange_ = function (evt, name, value) {
@@ -91,9 +168,11 @@
this.updateOnionSkinPreview_();
} else if (name == pskl.UserSettings.MAX_FPS) {
this.updateMaxFPS_();
} else if (name === pskl.UserSettings.SEAMLESS_MODE) {
this.onFrameSizeChange_();
} else {
this.updateZoom_();
this.updateOriginalSizeButton_();
this.selectPreviewSizeButton_();
this.updateContainerDimensions_();
}
};
@@ -101,27 +180,44 @@
ns.PreviewController.prototype.updateOnionSkinPreview_ = function () {
var enabledClassname = 'preview-toggle-onion-skin-enabled';
var isEnabled = pskl.UserSettings.get(pskl.UserSettings.ONION_SKIN);
this.toggleOnionSkinButton.classList.toggle(enabledClassname, isEnabled);
// classList.toggle is not available on IE11.
if (isEnabled) {
this.toggleOnionSkinButton.classList.add(enabledClassname);
} else {
this.toggleOnionSkinButton.classList.remove(enabledClassname);
}
};
ns.PreviewController.prototype.updateOriginalSizeButton_ = function () {
var enabledClassname = 'original-size-button-enabled';
var isEnabled = pskl.UserSettings.get(pskl.UserSettings.ORIGINAL_SIZE_PREVIEW);
this.originalSizeButton.classList.toggle(enabledClassname, isEnabled);
ns.PreviewController.prototype.selectPreviewSizeButton_ = function () {
var currentlySelected = document.querySelector('.size-button-selected');
if (currentlySelected) {
currentlySelected.classList.remove('size-button-selected');
}
var selectedSize = pskl.UserSettings.get(pskl.UserSettings.PREVIEW_SIZE);
var previewSize = this.previewSizes[selectedSize];
previewSize.button.classList.add('size-button-selected');
};
ns.PreviewController.prototype.updateMaxFPS_ = function () {
var maxFps = pskl.UserSettings.get(pskl.UserSettings.MAX_FPS);
this.fpsRangeInput.setAttribute('max', maxFps);
this.setFPS(Math.min(this.fps, maxFps));
this.piskelController.setFPS(Math.min(maxFps, this.piskelController.getFPS()));
};
ns.PreviewController.prototype.updateZoom_ = function () {
var originalSizeEnabled = pskl.UserSettings.get(pskl.UserSettings.ORIGINAL_SIZE_PREVIEW);
var seamlessModeEnabled = pskl.UserSettings.get(pskl.UserSettings.SEAMLESS_MODE);
var useOriginalSize = originalSizeEnabled || seamlessModeEnabled;
var previewSize = pskl.UserSettings.get(pskl.UserSettings.PREVIEW_SIZE);
var zoom;
if (previewSize === 'original') {
zoom = 1;
} else if (previewSize === 'best') {
zoom = Math.floor(this.calculateZoom_());
} else if (previewSize === 'full') {
zoom = this.calculateZoom_();
}
var zoom = useOriginalSize ? 1 : this.calculateZoom_();
this.renderer.setZoom(zoom);
this.setRenderFlag_(true);
};
@@ -145,15 +241,17 @@
* Event handler triggered on 'input' or 'change' events.
*/
ns.PreviewController.prototype.onFpsRangeInputUpdate_ = function (evt) {
this.setFPS(parseInt(this.fpsRangeInput.value, 10));
var fps = parseInt(this.fpsRangeInput.value, 10);
this.piskelController.setFPS(fps);
// blur only on 'change' events, as blurring on 'input' breaks on Firefox
if (evt.type === 'change') {
this.fpsRangeInput.blur();
}
};
ns.PreviewController.prototype.setFPS = function (fps) {
if (typeof fps === 'number') {
ns.PreviewController.prototype.updateFPS_ = function () {
var fps = this.piskelController.getFPS();
if (fps !== this.fps) {
this.fps = fps;
// reset
this.fpsRangeInput.value = 0;
@@ -163,10 +261,6 @@
}
};
ns.PreviewController.prototype.getFPS = function () {
return this.fps;
};
ns.PreviewController.prototype.render = function (delta) {
this.elapsedTime += delta;
var index = this.getNextIndex_(delta);
@@ -208,6 +302,7 @@
ns.PreviewController.prototype.onFrameSizeChange_ = function () {
this.updateZoom_();
this.updateContainerDimensions_();
this.updatePreviewSizeButtons_();
};
ns.PreviewController.prototype.updateContainerDimensions_ = function () {

View File

@@ -39,12 +39,30 @@
maxFpsInput.value = pskl.UserSettings.get(pskl.UserSettings.MAX_FPS);
this.addEventListener(maxFpsInput, 'change', this.onMaxFpsChange_);
// Color format
var colorFormat = pskl.UserSettings.get(pskl.UserSettings.COLOR_FORMAT);
var colorFormatSelect = document.querySelector('.color-format-select');
var selectedColorFormatOption = colorFormatSelect.querySelector('option[value="' + colorFormat + '"]');
if (selectedColorFormatOption) {
selectedColorFormatOption.setAttribute('selected', 'selected');
}
this.addEventListener(colorFormatSelect, 'change', this.onColorFormatChange_);
// Layer preview opacity
var layerOpacityInput = document.querySelector('.layer-opacity-input');
layerOpacityInput.value = pskl.UserSettings.get(pskl.UserSettings.LAYER_OPACITY);
this.addEventListener(layerOpacityInput, 'change', this.onLayerOpacityChange_);
this.addEventListener(layerOpacityInput, 'input', this.onLayerOpacityChange_);
this.updateLayerOpacityText_(layerOpacityInput.value);
// Seamless mask opacity
var seamlessOpacityInput = document.querySelector('.seamless-opacity-input');
seamlessOpacityInput.value = pskl.UserSettings.get(pskl.UserSettings.SEAMLESS_OPACITY);
this.addEventListener(seamlessOpacityInput, 'change', this.onSeamlessOpacityChange_);
this.addEventListener(seamlessOpacityInput, 'input', this.onSeamlessOpacityChange_);
this.updateSeamlessOpacityText_(seamlessOpacityInput.value);
// Form
this.applicationSettingsForm = document.querySelector('[name="application-settings-form"]');
this.addEventListener(this.applicationSettingsForm, 'submit', this.onFormSubmit_);
@@ -55,6 +73,10 @@
pskl.UserSettings.set(pskl.UserSettings.GRID_WIDTH, width);
};
ns.ApplicationSettingsController.prototype.onColorFormatChange_ = function (evt) {
pskl.UserSettings.set(pskl.UserSettings.COLOR_FORMAT, evt.target.value);
};
ns.ApplicationSettingsController.prototype.onSeamlessModeChange_ = function (evt) {
pskl.UserSettings.set(pskl.UserSettings.SEAMLESS_MODE, evt.currentTarget.checked);
};
@@ -65,7 +87,7 @@
if (background) {
pskl.UserSettings.set(pskl.UserSettings.CANVAS_BACKGROUND, background);
var selected = this.backgroundContainer.querySelector('.selected');
if (selected) {
if (selected) {
selected.classList.remove('selected');
}
target.classList.add('selected');
@@ -94,11 +116,27 @@
}
};
ns.ApplicationSettingsController.prototype.onSeamlessOpacityChange_ = function (evt) {
var target = evt.target;
var opacity = parseFloat(target.value);
if (!isNaN(opacity)) {
pskl.UserSettings.set(pskl.UserSettings.SEAMLESS_OPACITY, opacity);
this.updateSeamlessOpacityText_(opacity);
} else {
target.value = pskl.UserSettings.get(pskl.UserSettings.SEAMLESS_OPACITY);
}
};
ns.ApplicationSettingsController.prototype.updateLayerOpacityText_ = function (opacity) {
var layerOpacityText = document.querySelector('.layer-opacity-text');
layerOpacityText.innerHTML = opacity;
};
ns.ApplicationSettingsController.prototype.updateSeamlessOpacityText_ = function (opacity) {
var seamlessOpacityText = document.querySelector('.seamless-opacity-text');
seamlessOpacityText.innerHTML = opacity;
};
ns.ApplicationSettingsController.prototype.onFormSubmit_ = function (evt) {
evt.preventDefault();
$.publish(Events.CLOSE_SETTINGS_DRAWER);

View File

@@ -79,10 +79,15 @@
ns.ImportController.prototype.openPiskelFile_ = function (file) {
if (this.isPiskel_(file)) {
pskl.utils.PiskelFileUtils.loadFromFile(file, function (piskel, extra) {
pskl.app.piskelController.setPiskel(piskel);
pskl.app.previewController.setFPS(extra.fps);
});
pskl.utils.PiskelFileUtils.loadFromFile(file,
// onSuccess
function (piskel) {
pskl.app.piskelController.setPiskel(piskel);
},
// onError
function (reason) {
$.publish(Events.PISKEL_FILE_IMPORT_FAILED, [reason]);
});
this.closeDrawer_();
}
};

View File

@@ -46,13 +46,23 @@
this.disableSaveButtons_();
}
this.updateSaveToGalleryMessage_();
$.subscribe(Events.BEFORE_SAVING_PISKEL, this.disableSaveButtons_.bind(this));
$.subscribe(Events.AFTER_SAVING_PISKEL, this.enableSaveButtons_.bind(this));
};
ns.SaveController.prototype.updateSaveToGalleryMessage_ = function (spritesheetSize) {
var saveToGalleryStatus = document.querySelector('.save-online-status');
if (saveToGalleryStatus && pskl.app.performanceReportService.hasProblem()) {
var warningPartial = pskl.utils.Template.get('save-gallery-warning-partial');
saveToGalleryStatus.innerHTML = warningPartial;
}
};
ns.SaveController.prototype.insertSavePartials_ = function () {
this.getPartials_().forEach(function (partial) {
pskl.utils.Template.insert(this.saveForm, 'beforeend', partial);
this.saveForm.insertAdjacentHTML('beforeend', pskl.utils.Template.get(partial));
}.bind(this));
};

View File

@@ -150,7 +150,7 @@
ns.GifExportController.prototype.updateStatus_ = function (imageUrl, error) {
if (imageUrl) {
var linkTpl = '<a class="image-link" href="{{link}}" target="_blank">{{shortLink}}</a>';
var linkTpl = '<a class="highlight" href="{{link}}" target="_blank">{{shortLink}}</a>';
var linkHtml = pskl.utils.Template.replace(linkTpl, {
link : imageUrl,
shortLink : this.shorten_(imageUrl, URL_MAX_LENGTH, '...')

View File

@@ -3,11 +3,9 @@
var dimensionInfoPattern = '{{width}} x {{height}} px, {{frames}}<br/>{{columns}}, {{rows}}.';
// Shortcut to pskl.utils.Template.replace
var replace = pskl.utils.Template.replace;
// Helper to return "X items" or "1 item" if X is 1. Can be cnsidered as an overkill,
// but the one-liner equivalent is hard to read.
// Helper to return "X items" or "1 item" if X is 1.
var pluralize = function (word, count) {
if (count === 1) {
return '1 ' + word;
@@ -31,6 +29,7 @@
this.columnsInput = document.querySelector('#png-export-columns');
var downloadButton = document.querySelector('.png-download-button');
var downloadPixiButton = document.querySelector('.png-pixi-download-button');
var dataUriButton = document.querySelector('.datauri-open-button');
this.initLayoutSection_();
@@ -38,6 +37,7 @@
this.addEventListener(this.columnsInput, 'input', this.onColumnsInput_);
this.addEventListener(downloadButton, 'click', this.onDownloadClick_);
this.addEventListener(downloadPixiButton, 'click', this.onPixiDownloadClick_);
this.addEventListener(dataUriButton, 'click', this.onDataUriClick_);
$.subscribe(Events.EXPORT_SCALE_CHANGED, this.onScaleChanged_);
};
@@ -118,7 +118,7 @@
value = 1;
}
// Force the value to be in bounds, in the user tried to update it by directly typing
// Force the value to be in bounds, if the user tried to update it by directly typing
// a value.
value = pskl.utils.Math.minmax(value, 1, this.piskelController.getFrameCount());
this.columnsInput.value = value;
@@ -145,7 +145,11 @@
ns.PngExportController.prototype.onDownloadClick_ = function (evt) {
// Create PNG export.
var canvas = this.createPngSpritesheet_();
this.downloadCanvas_(canvas);
};
// Used and overridden in casper integration tests.
ns.PngExportController.prototype.downloadCanvas_ = function (canvas) {
// Generate file name
var name = this.piskelController.getPiskel().getDescriptor().name;
var fileName = name + '.png';
@@ -156,6 +160,52 @@
});
};
ns.PngExportController.prototype.onPixiDownloadClick_ = function () {
var zip = new window.JSZip();
// Create PNG export.
var canvas = this.createPngSpritesheet_();
var name = this.piskelController.getPiskel().getDescriptor().name;
zip.file(name + '.png', pskl.utils.CanvasUtils.getBase64FromCanvas(canvas) + '\n', {base64: true});
var width = canvas.width / this.getColumns_();
var height = canvas.height / this.getRows_();
var numFrames = this.piskelController.getFrameCount();
var frames = {};
for (var i = 0; i < numFrames; i++) {
var column = i % this.getColumns_();
var row = (i - column) / this.getColumns_();
var frame = {
'frame': {'x': width * column,'y': height * row,'w': width,'h': height},
'rotated': false,
'trimmed': false,
'spriteSourceSize': {'x': 0,'y': 0,'w': width,'h': height},
'sourceSize': {'w': width,'h': height}
};
frames[name + i + '.png'] = frame;
}
var json = {
'frames': frames,
'meta': {
'app': 'https://github.com/juliandescottes/piskel/',
'version': '1.0',
'image': name + '.png',
'format': 'RGBA8888',
'size': {'w': canvas.width,'h': canvas.height}
}
};
zip.file(name + '.json', JSON.stringify(json));
var blob = zip.generate({
type : 'blob'
});
pskl.utils.FileUtils.downloadAsFile(blob, name + '.zip');
};
ns.PngExportController.prototype.onDataUriClick_ = function (evt) {
window.open(this.createPngSpritesheet_().toDataURL('image/png'));
};

View File

@@ -64,7 +64,8 @@
var resizedLayers = this.piskelController.getLayers().map(this.resizeLayer_.bind(this));
var currentPiskel = this.piskelController.getPiskel();
var piskel = pskl.model.Piskel.fromLayers(resizedLayers, currentPiskel.getDescriptor());
var fps = this.piskelController.getFPS();
var piskel = pskl.model.Piskel.fromLayers(resizedLayers, fps, currentPiskel.getDescriptor());
// propagate savepath to new Piskel
piskel.savePath = currentPiskel.savePath;

View File

@@ -46,7 +46,7 @@
ns.DrawingTestPlayer.prototype.createPiskel_ = function (width, height) {
var descriptor = new pskl.model.piskel.Descriptor('TestPiskel', '');
var piskel = new pskl.model.Piskel(width, height, descriptor);
var piskel = new pskl.model.Piskel(width, height, 12, descriptor);
var layer = new pskl.model.Layer('Layer 1');
var frame = new pskl.model.Frame(width, height);

View File

@@ -64,7 +64,7 @@
events : this.events,
initialState : this.initialState,
png : png
});
}, null, ' ');
this.reset();

View File

@@ -0,0 +1,27 @@
if (!Element.prototype.scrollIntoViewIfNeeded) {
Element.prototype.scrollIntoViewIfNeeded = function (centerIfNeeded) {
centerIfNeeded = arguments.length === 0 ? true : !!centerIfNeeded;
var parent = this.parentNode,
parentComputedStyle = window.getComputedStyle(parent, null),
parentBorderTopWidth = parseInt(parentComputedStyle.getPropertyValue('border-top-width')),
parentBorderLeftWidth = parseInt(parentComputedStyle.getPropertyValue('border-left-width')),
overTop = this.offsetTop - parent.offsetTop < parent.scrollTop,
overBottom = (this.offsetTop - parent.offsetTop + this.clientHeight - parentBorderTopWidth) > (parent.scrollTop + parent.clientHeight),
overLeft = this.offsetLeft - parent.offsetLeft < parent.scrollLeft,
overRight = (this.offsetLeft - parent.offsetLeft + this.clientWidth - parentBorderLeftWidth) > (parent.scrollLeft + parent.clientWidth),
alignWithTop = overTop && !overBottom;
if ((overTop || overBottom) && centerIfNeeded) {
parent.scrollTop = this.offsetTop - parent.offsetTop - parent.clientHeight / 2 - parentBorderTopWidth + this.clientHeight / 2;
}
if ((overLeft || overRight) && centerIfNeeded) {
parent.scrollLeft = this.offsetLeft - parent.offsetLeft - parent.clientWidth / 2 - parentBorderLeftWidth + this.clientWidth / 2;
}
if ((overTop || overBottom || overLeft || overRight) && !centerIfNeeded) {
this.scrollIntoView(alignWithTop);
}
};
}

View File

@@ -671,7 +671,8 @@
// Update the text entry input as it changes happen
if (opts.showInput) {
textInput.val(realColor.toString(format));
var displayFormat = pskl.UserSettings.get(pskl.UserSettings.COLOR_FORMAT);
textInput.val(realColor.toString(displayFormat));
}
if (opts.showPalette) {

View File

@@ -8,21 +8,14 @@
* @param {String} name
* @param {String} description
*/
ns.Piskel = function (width, height, descriptor) {
ns.Piskel = function (width, height, fps, descriptor) {
if (width && height && descriptor) {
/** @type {Array} */
this.layers = [];
/** @type {Number} */
this.width = width;
/** @type {Number} */
this.height = height;
this.descriptor = descriptor;
/** @type {String} */
this.savePath = null;
this.fps = fps;
} else {
throw 'Missing arguments in Piskel constructor : ' + Array.prototype.join.call(arguments, ',');
@@ -35,11 +28,11 @@
* @param {Array<pskl.model.Layer>} layers
* @return {pskl.model.Piskel}
*/
ns.Piskel.fromLayers = function (layers, descriptor) {
ns.Piskel.fromLayers = function (layers, fps, descriptor) {
var piskel = null;
if (layers.length > 0 && layers[0].size() > 0) {
var sampleFrame = layers[0].getFrameAt(0);
piskel = new pskl.model.Piskel(sampleFrame.getWidth(), sampleFrame.getHeight(), descriptor);
piskel = new pskl.model.Piskel(sampleFrame.getWidth(), sampleFrame.getHeight(), fps, descriptor);
layers.forEach(piskel.addLayer.bind(piskel));
} else {
throw 'Piskel.fromLayers expects array of non empty pskl.model.Layer as first argument';
@@ -59,6 +52,10 @@
return this.width;
};
ns.Piskel.prototype.getFPS = function () {
return this.fps;
};
ns.Piskel.prototype.getLayers = function () {
return this.layers;
};

View File

@@ -1,8 +1,8 @@
(function () {
var ns = $.namespace('pskl.model.frame');
// 10 * 60 * 1000 = 10 minutes
var DEFAULT_CLEAR_INTERVAL = 10 * 60 * 1000;
// Maximum number of cache entries
var MAX_CACHE_ENTRIES = 100;
var DEFAULT_FRAME_PROCESSOR = function (frame) {
return pskl.utils.FrameUtils.toImage(frame);
@@ -12,14 +12,16 @@
var DEFAULT_NAMESPACE = '__cache_default__';
ns.CachedFrameProcessor = function (cacheResetInterval) {
ns.CachedFrameProcessor = function () {
// Cache object.
this.cache_ = {};
this.cacheResetInterval = cacheResetInterval || DEFAULT_CLEAR_INTERVAL;
// Array of [namespace, key] for each cached frame.
this.cacheQueue_ = [];
this.frameProcessor = DEFAULT_FRAME_PROCESSOR;
this.outputCloner = DEFAULT_OUTPUT_CLONER;
this.defaultNamespace = DEFAULT_NAMESPACE;
window.setInterval(this.clear.bind(this), this.cacheResetInterval);
};
ns.CachedFrameProcessor.prototype.clear = function () {
@@ -69,6 +71,11 @@
} else {
processedFrame = this.frameProcessor(frame);
cache[cacheKey] = processedFrame;
this.cacheQueue_.unshift([namespace, cacheKey]);
if (this.cacheQueue_.length > MAX_CACHE_ENTRIES) {
var oldestItem = this.cacheQueue_.pop();
this.cache_[oldestItem[0]][oldestItem[1]] = null;
}
}
return processedFrame;

View File

@@ -35,6 +35,7 @@
this.getZoom(),
this.getGridWidth(),
pskl.UserSettings.get('SEAMLESS_MODE'),
pskl.UserSettings.get('SEAMLESS_OPACITY'),
offset.x, offset.y,
size.width, size.height,
frame.getHash()

View File

@@ -261,27 +261,33 @@
this.margin.y - this.offset.y * z
);
// Scale up to draw the canvas content
displayContext.scale(z, z);
if (pskl.UserSettings.get('SEAMLESS_MODE')) {
displayContext.clearRect(-1 * w * z, -1 * h * z, 3 * w * z, 3 * h * z);
displayContext.clearRect(-1 * w, -1 * h, 3 * w, 3 * h);
} else {
displayContext.clearRect(0, 0, w * z, h * z);
displayContext.clearRect(0, 0, w, h);
}
if (pskl.UserSettings.get('SEAMLESS_MODE')) {
this.drawTiledFrames_(displayContext, this.canvas, w, h, 1);
}
displayContext.drawImage(this.canvas, 0, 0);
// Draw grid.
var gridWidth = this.computeGridWidthForDisplay_();
if (gridWidth > 0) {
var scaled = pskl.utils.ImageResizer.resizeNearestNeighbour(this.canvas, z, gridWidth);
if (pskl.UserSettings.get('SEAMLESS_MODE')) {
this.drawTiledFrames_(displayContext, scaled, w, h, z);
// Scale out before drawing the grid.
displayContext.scale(1 / z, 1 / z);
// Clear vertical lines.
for (var i = 1 ; i < frame.getWidth() ; i++) {
displayContext.clearRect((i * z) - (gridWidth / 2), 0, gridWidth, h * z);
}
displayContext.drawImage(scaled, 0, 0);
} else {
displayContext.scale(z, z);
if (pskl.UserSettings.get('SEAMLESS_MODE')) {
this.drawTiledFrames_(displayContext, this.canvas, w, h, 1);
// Clear horizontal lines.
for (var j = 1 ; j < frame.getHeight() ; j++) {
displayContext.clearRect(0, (j * z) - (gridWidth / 2), w * z, gridWidth);
}
displayContext.drawImage(this.canvas, 0, 0);
}
displayContext.restore();
@@ -293,7 +299,9 @@
* differentiate those additional frames from the main frame.
*/
ns.FrameRenderer.prototype.drawTiledFrames_ = function (context, image, w, h, z) {
context.fillStyle = Constants.SEAMLESS_MODE_OVERLAY_COLOR;
var opacity = pskl.UserSettings.get('SEAMLESS_OPACITY');
opacity = pskl.utils.Math.minmax(opacity, 0, 1);
context.fillStyle = 'rgba(255, 255, 255, ' + opacity + ')';
[[0, -1], [0, 1], [-1, -1], [-1, 0], [-1, 1], [1, -1], [1, 0], [1, 1]].forEach(function (d) {
context.drawImage(image, d[0] * w * z, d[1] * h * z);
context.fillRect(d[0] * w * z, d[1] * h * z, w * z, h * z);

View File

@@ -93,7 +93,11 @@
ns.SelectionManager.prototype.paste = function() {
if (!this.currentSelection || !this.currentSelection.hasPastedContent) {
return;
if (window.localStorage.getItem('piskel.clipboard')) {
this.currentSelection = JSON.parse(window.localStorage.getItem('piskel.clipboard'));
} else {
return;
}
}
var pixels = this.currentSelection.pixels;
@@ -146,6 +150,7 @@
ns.SelectionManager.prototype.copy = function() {
if (this.currentSelection && this.piskelController.getCurrentFrame()) {
this.currentSelection.fillSelectionFromFrame(this.piskelController.getCurrentFrame());
window.localStorage.setItem('piskel.clipboard', JSON.stringify(this.currentSelection));
}
};

View File

@@ -26,7 +26,6 @@
var info = {
name : descriptor.name,
description : descriptor.info,
fps : this.piskelController.getFPS(),
date : Date.now(),
hash : hash
};
@@ -47,16 +46,11 @@
};
ns.BackupService.prototype.load = function() {
var previousPiskel = window.localStorage.getItem('bkp.prev.piskel');
var previousInfo = window.localStorage.getItem('bkp.prev.info');
previousPiskel = JSON.parse(previousPiskel);
previousInfo = JSON.parse(previousInfo);
pskl.utils.serialization.Deserializer.deserialize(previousPiskel, function (piskel) {
pskl.app.piskelController.setPiskel(piskel);
pskl.app.previewController.setFPS(previousInfo.fps);
});
};

View File

@@ -6,15 +6,38 @@
};
ns.BeforeUnloadService.prototype.init = function () {
if (pskl.utils.Environment.detectNodeWebkit()) {
// Add a dedicated listener to window 'close' event in nwjs environment.
var win = require('nw.gui').Window.get();
win.on('close', this.onNwWindowClose.bind(this, win));
}
window.addEventListener('beforeunload', this.onBeforeUnload.bind(this));
};
/**
* In nw.js environment "onbeforeunload" is not triggered when closing the window.
* Polyfill the behavior here.
*/
ns.BeforeUnloadService.prototype.onNwWindowClose = function (win) {
var msg = this.onBeforeUnload();
if (msg) {
if (!window.confirm(msg)) {
return false;
}
}
win.close(true);
};
ns.BeforeUnloadService.prototype.onBeforeUnload = function (evt) {
pskl.app.backupService.backup();
if (pskl.app.savedStatusService.isDirty()) {
var confirmationMessage = 'Your Piskel seems to have unsaved changes';
(evt || window.event).returnValue = confirmationMessage;
evt = evt || window.event;
if (evt) {
evt.returnValue = confirmationMessage;
}
return confirmationMessage;
}
};

View File

@@ -10,11 +10,16 @@
this.cachedFrameProcessor = new pskl.model.frame.AsyncCachedFrameProcessor();
this.cachedFrameProcessor.setFrameProcessor(this.getFrameColors_.bind(this));
this.throttledUpdateCurrentColors_ = pskl.utils.FunctionUtils.throttle(
this.updateCurrentColors_.bind(this),
1000
);
this.paletteService = pskl.app.paletteService;
};
ns.CurrentColorsService.prototype.init = function () {
$.subscribe(Events.HISTORY_STATE_SAVED, this.updateCurrentColors_.bind(this));
$.subscribe(Events.HISTORY_STATE_SAVED, this.throttledUpdateCurrentColors_);
$.subscribe(Events.HISTORY_STATE_LOADED, this.loadColorsFromCache_.bind(this));
};
@@ -65,52 +70,26 @@
return result;
};
var frameCache = {};
ns.CurrentColorsService.prototype.updateCurrentColors_ = function () {
var layers = this.piskelController.getLayers();
var frames = layers.map(function (l) {return l.getFrames();}).reduce(function (p, n) {return p.concat(n);});
var job = function (frame) {
// Concatenate all frames in a single array.
var frames = layers.map(function (l) {
return l.getFrames();
}).reduce(function (p, n) {
return p.concat(n);
});
batchAll(frames, function (frame) {
return this.cachedFrameProcessor.get(frame);
}.bind(this);
var colors = {};
var framesToBatch = [];
var removeColorsIfNotInCurrent = function(hash, color) {
if (!frameCache[hash][color]) {
delete colors[color];
}
};
for (var i = 0, length = frames.length; i < length; ++i) {
var frame = frames[i];
var hash = frame.getHash();
if (frameCache[hash]) {
colors = Object.assign(colors, frameCache[hash]);
var hashParts = hash.split('-');
var hashVersion = parseInt(hashParts.pop());
if (hashVersion > 0) {
var lastColors = frameCache[hashParts.join('-') + '-' + (hashVersion - 1)];
if (lastColors) {
Object.keys(lastColors).forEach(removeColorsIfNotInCurrent.bind(this, hash));
}
}
} else {
framesToBatch.push(frame);
}
}
var batchAllThen = function (colors, results) {
}.bind(this))
.then(function (results) {
var colors = {};
results.forEach(function (result) {
Object.keys(result).forEach(function (color) {
colors[color] = true;
});
});
// Remove transparent color from used colors
delete colors[pskl.utils.colorToInt(Constants.TRANSPARENT_COLOR)];
@@ -118,13 +97,7 @@
return pskl.utils.intToHex(color);
});
this.setCurrentColors(hexColors);
}.bind(this, colors);
if (framesToBatch.length === 0) {
batchAllThen([colors]);
} else {
batchAll(framesToBatch, job).then(batchAllThen);
}
}.bind(this));
};
ns.CurrentColorsService.prototype.isCurrentColorsPaletteSelected_ = function () {
@@ -145,7 +118,6 @@
ns.CurrentColorsService.prototype.getFrameColors_ = function (frame, processorCallback) {
var frameColorsWorker = new pskl.worker.framecolors.FrameColors(frame,
function (event) {
frameCache[frame.getHash()] = event.data.colors;
processorCallback(event.data.colors);
},
function () {},

View File

@@ -1,9 +1,8 @@
(function () {
var ns = $.namespace('pskl.service');
ns.FileDropperService = function (piskelController, drawingAreaContainer) {
ns.FileDropperService = function (piskelController) {
this.piskelController = piskelController;
this.drawingAreaContainer = drawingAreaContainer;
this.dropPosition_ = null;
};
@@ -28,6 +27,8 @@
};
var files = event.dataTransfer.files;
this.isMultipleFiles_ = (files.length > 1);
for (var i = 0; i < files.length ; i++) {
var file = files[i];
var isImage = file.type.indexOf('image') === 0;
@@ -36,7 +37,7 @@
if (isImage) {
this.readImageFile_(file);
} else if (isPiskel) {
pskl.utils.PiskelFileUtils.loadFromFile(file, this.onPiskelFileLoaded_);
pskl.utils.PiskelFileUtils.loadFromFile(file, this.onPiskelFileLoaded_, this.onPiskelFileError_);
} else if (isPalette) {
pskl.app.paletteImportService.read(file, this.onPaletteLoaded_.bind(this));
}
@@ -52,31 +53,39 @@
pskl.UserSettings.set(pskl.UserSettings.SELECTED_PALETTE, palette.id);
};
ns.FileDropperService.prototype.onPiskelFileLoaded_ = function (piskel, extra) {
ns.FileDropperService.prototype.onPiskelFileLoaded_ = function (piskel) {
if (window.confirm('This will replace your current animation')) {
pskl.app.piskelController.setPiskel(piskel);
pskl.app.previewController.setFPS(extra.fps);
}
};
ns.FileDropperService.prototype.processImageSource_ = function (imageSource) {
this.importedImage_ = new Image();
this.importedImage_.onload = this.onImageLoaded_.bind(this);
this.importedImage_.src = imageSource;
ns.FileDropperService.prototype.onPiskelFileError_ = function (reason) {
$.publish(Events.PISKEL_FILE_IMPORT_FAILED, [reason]);
};
ns.FileDropperService.prototype.onImageLoaded_ = function () {
var droppedFrame = pskl.utils.FrameUtils.createFromImage(this.importedImage_);
ns.FileDropperService.prototype.processImageSource_ = function (imageSource) {
var importedImage = new Image();
importedImage.onload = this.onImageLoaded_.bind(this, importedImage);
importedImage.src = imageSource;
};
ns.FileDropperService.prototype.onImageLoaded_ = function (importedImage) {
if (this.isMultipleFiles_) {
this.piskelController.addFrameAtCurrentIndex();
this.piskelController.selectNextFrame();
}
var currentFrame = this.piskelController.getCurrentFrame();
// Convert client coordinates to sprite coordinates
var spriteDropPosition = pskl.app.drawingController.getSpriteCoordinates(
this.dropPosition_.x,
this.dropPosition_.y
);
var dropCoordinates = this.adjustDropPosition_(this.dropPosition_, droppedFrame);
var x = spriteDropPosition.x;
var y = spriteDropPosition.y;
currentFrame.forEachPixel(function (color, x, y) {
var fColor = droppedFrame.getPixel(x - dropCoordinates.x, y - dropCoordinates.y);
if (fColor && fColor != Constants.TRANSPARENT_COLOR) {
currentFrame.setPixel(x, y, fColor);
}
});
pskl.utils.FrameUtils.addImageToFrame(currentFrame, importedImage, x, y);
$.publish(Events.PISKEL_RESET);
$.publish(Events.PISKEL_SAVE_STATE, {
@@ -84,28 +93,4 @@
});
};
ns.FileDropperService.prototype.adjustDropPosition_ = function (position, droppedFrame) {
var framePosition = pskl.app.drawingController.getSpriteCoordinates(position.x, position.y);
var xCoord = framePosition.x - Math.floor(droppedFrame.width / 2);
var yCoord = framePosition.y - Math.floor(droppedFrame.height / 2);
xCoord = Math.max(0, xCoord);
yCoord = Math.max(0, yCoord);
var currentFrame = this.piskelController.getCurrentFrame();
if (droppedFrame.width <= currentFrame.width) {
xCoord = Math.min(xCoord, currentFrame.width - droppedFrame.width);
}
if (droppedFrame.height <= currentFrame.height) {
yCoord = Math.min(yCoord, currentFrame.height - droppedFrame.height);
}
return {
x : xCoord,
y : yCoord
};
};
})();

View File

@@ -2,7 +2,9 @@
var ns = $.namespace('pskl.service');
ns.HistoryService = function (piskelController, shortcutService, deserializer, serializer) {
this.piskelController = piskelController || pskl.app.piskelController;
// Use the real piskel controller that will not fire events when calling setters
this.piskelController = piskelController.getWrappedPiskelController();
this.shortcutService = shortcutService || pskl.app.shortcutService;
this.deserializer = deserializer || pskl.utils.serialization.arraybuffer.ArrayBufferDeserializer;
this.serializer = serializer || pskl.utils.serialization.arraybuffer.ArrayBufferSerializer;
@@ -24,6 +26,9 @@
// Interval/buffer (in milliseconds) between two state load (ctrl+z/y spamming)
ns.HistoryService.LOAD_STATE_INTERVAL = 50;
// Maximum number of states that can be recorded.
ns.HistoryService.MAX_SAVED_STATES = 500;
ns.HistoryService.prototype.init = function () {
$.subscribe(Events.PISKEL_SAVE_STATE, this.onSaveStateEvent.bind(this));
@@ -48,6 +53,7 @@
action : action,
frameIndex : action.state ? action.state.frameIndex : this.piskelController.currentFrameIndex,
layerIndex : action.state ? action.state.layerIndex : this.piskelController.currentLayerIndex,
fps : this.piskelController.getFPS(),
uuid: pskl.utils.Uuid.generate()
};
@@ -58,6 +64,13 @@
state.piskel = this.serializer.serialize(piskel);
}
// If the new state pushes over MAX_SAVED_STATES, erase all states between the first and
// second snapshot states.
if (this.stateQueue.length > ns.HistoryService.MAX_SAVED_STATES) {
var firstSnapshotIndex = this.getNextSnapshotIndex_(1);
this.stateQueue.splice(0, firstSnapshotIndex);
this.currentIndex = this.currentIndex - firstSnapshotIndex;
}
this.stateQueue.push(state);
$.publish(Events.HISTORY_STATE_SAVED);
};
@@ -92,6 +105,13 @@
return index;
};
ns.HistoryService.prototype.getNextSnapshotIndex_ = function (index) {
while (this.stateQueue[index] && !this.stateQueue[index].piskel) {
index = index + 1;
}
return index;
};
ns.HistoryService.prototype.loadState = function (index) {
try {
if (this.isLoadStateAllowed_(index)) {
@@ -165,6 +185,7 @@
ns.HistoryService.prototype.setupState = function (state) {
this.piskelController.setCurrentFrameIndex(state.frameIndex);
this.piskelController.setCurrentLayerIndex(state.layerIndex);
this.piskelController.setFPS(state.fps);
};
ns.HistoryService.prototype.replayState = function (state) {

View File

@@ -4,13 +4,25 @@
/**
* Image an animation import service supporting the import dialog.
* @param {!PiskelController} piskelController
* @param {!PreviewController} previewController
* @constructor
*/
ns.ImportService =
function (piskelController, previewController) {
ns.ImportService = function (piskelController) {
this.piskelController_ = piskelController;
this.previewController_ = previewController;
};
ns.ImportService.prototype.init = function () {
$.subscribe(Events.PISKEL_FILE_IMPORT_FAILED, this.onPiskelFileImportFailed_);
};
/**
* Called when a piskel load failed event is published. Display an appropriate error message.
* TODO: for some failure reasons, we might want to display a dialog with more details.
*/
ns.ImportService.prototype.onPiskelFileImportFailed_ = function (evt, reason) {
$.publish(Events.SHOW_NOTIFICATION, [{
'content': 'Piskel file import failed (' + reason + ')',
'hideDelay' : 10000
}]);
};
/**
@@ -101,10 +113,9 @@
var frames = this.createFramesFromImages_(images, frameSizeX, frameSizeY, smoothing);
var layer = pskl.model.Layer.fromFrames('Layer 1', frames);
var descriptor = new pskl.model.piskel.Descriptor('Imported piskel', '');
var piskel = pskl.model.Piskel.fromLayers([layer], descriptor);
var piskel = pskl.model.Piskel.fromLayers([layer], Constants.DEFAULT.FPS, descriptor);
this.piskelController_.setPiskel(piskel);
this.previewController_.setFPS(Constants.DEFAULT.FPS);
};
/**

View File

@@ -2,7 +2,8 @@
var ns = $.namespace('pskl.service');
ns.SelectedColorsService = function () {
this.reset();
this.primaryColor_ = Constants.DEFAULT_PEN_COLOR;
this.secondaryColor_ = Constants.TRANSPARENT_COLOR;
};
ns.SelectedColorsService.prototype.init = function () {
@@ -18,11 +19,6 @@
return this.secondaryColor_;
};
ns.SelectedColorsService.prototype.reset = function () {
this.primaryColor_ = Constants.DEFAULT_PEN_COLOR;
this.secondaryColor_ = Constants.TRANSPARENT_COLOR;
};
ns.SelectedColorsService.prototype.onPrimaryColorUpdate_ = function (evt, color) {
this.primaryColor_ = color;
};

View File

@@ -55,7 +55,9 @@
NEW_FRAME : createShortcut('new-frame', 'Create new empty frame', 'N'),
DUPLICATE_FRAME : createShortcut('duplicate-frame', 'Duplicate selected frame', 'shift+N'),
CHEATSHEET : createShortcut('cheatsheet', 'Open the keyboard shortcut cheatsheet', ['?', 'shift+?']),
X1_PREVIEW : createShortcut('x1-preview', 'Toggle original size preview', 'alt+1'),
X1_PREVIEW : createShortcut('x1-preview', 'Select original size preview', 'alt+1'),
BEST_PREVIEW : createShortcut('best-preview', 'Select best size preview', 'alt+2'),
FULL_PREVIEW : createShortcut('full-preview', 'Select full size preview', 'alt+3'),
ONION_SKIN : createShortcut('onion-skin', 'Toggle onion skin', 'alt+O'),
LAYER_PREVIEW : createShortcut('layer-preview', 'Toggle layer preview', 'alt+L'),
CLOSE_POPUP : createShortcut('close-popup', 'Close an opened popup', 'ESC')

View File

@@ -9,6 +9,7 @@
};
ns.PaletteImportService = function () {};
ns.PaletteImportService.prototype.init = function () {};
ns.PaletteImportService.prototype.read = function (file, onSuccess, onError) {
var reader = this.getReader_(file, onSuccess, onError);

View File

@@ -2,7 +2,7 @@
var ns = $.namespace('pskl.service.pensize');
var MIN_PENSIZE = 1;
var MAX_PENSIZE = 4;
var MAX_PENSIZE = 32;
/**
* Service to retrieve and modify the current pen size.

View File

@@ -0,0 +1,45 @@
(function () {
var ns = $.namespace('pskl.service.performance');
/**
* We consider that piskel should behave correctly for a sprite with the following specs:
* - 256*256
* - 30 frames
* - 5 layers
* - 30 colors
* Based on these assumptions, as well as a few arbitrary hard limits we try to check
* if the provided sprite might present a performance issue.
*
* @param {Piskel} piskel the sprite to analyze
* @param {Number} colorsCount number of colors for the current sprite
* (not part of the piskel model so has to be provided separately).
*/
ns.PerformanceReport = function (piskel, colorsCount) {
var pixels = piskel.getWidth() * piskel.getHeight();
this.resolution = pixels > (500 * 500);
var layersCount = piskel.getLayers().length;
this.layers = layersCount > 25;
var framesCount = piskel.getLayerAt(0).size();
this.frames = framesCount > 100;
this.colors = colorsCount > 100;
var overallScore = (pixels / 2500) + (layersCount * 4) + framesCount + colorsCount;
this.overall = overallScore > 100;
};
ns.PerformanceReport.prototype.equals = function (report) {
return (report instanceof ns.PerformanceReport &&
this.resolution == report.resolution &&
this.layers == report.layers &&
this.frames == report.frames &&
this.colors == report.colors &&
this.overall == report.overall);
};
ns.PerformanceReport.prototype.hasProblem = function () {
return this.resolution || this.layers || this.frames || this.colors || this.overall;
};
})();

View File

@@ -0,0 +1,29 @@
(function () {
var ns = $.namespace('pskl.service.performance');
ns.PerformanceReportService = function (piskelController, currentColorsService) {
this.piskelController = piskelController;
this.currentColorsService = currentColorsService;
this.currentReport = null;
};
ns.PerformanceReportService.prototype.init = function () {
$.subscribe(Events.HISTORY_STATE_SAVED, this.createReport_.bind(this));
};
ns.PerformanceReportService.prototype.createReport_ = function () {
var report = new ns.PerformanceReport(
this.piskelController.getPiskel(),
this.currentColorsService.getCurrentColors().length);
if (!report.equals(this.currentReport)) {
$.publish(Events.PERFORMANCE_REPORT_CHANGED, [report]);
this.currentReport = report;
}
};
ns.PerformanceReportService.prototype.hasProblem = function () {
return this.currentReport && this.currentReport.hasProblem();
};
})();

View File

@@ -37,11 +37,10 @@
ns.DesktopStorageService.prototype.load = function (savePath) {
pskl.utils.FileUtilsDesktop.readFile(savePath).then(function (content) {
pskl.utils.PiskelFileUtils.decodePiskelFile(content, function (piskel, extra) {
pskl.utils.PiskelFileUtils.decodePiskelFile(content, function (piskel) {
// store save path so we can re-save without opening the save dialog
piskel.savePath = savePath;
pskl.app.piskelController.setPiskel(piskel);
pskl.app.previewController.setFPS(extra.fps);
});
});
};

View File

@@ -23,6 +23,10 @@
framesheet_as_png : pskl.app.getFramesheetAsPng()
};
if (serialized.length > Constants.APPENGINE_SAVE_LIMIT) {
deferred.reject('This sprite is too big to be saved on the gallery. Try saving it as a .piskel file.');
}
if (descriptor.isPublic) {
data.public = true;
}

View File

@@ -36,9 +36,8 @@
var piskelString = this.getPiskel(name);
var key = this.getKey_(name);
pskl.utils.serialization.Deserializer.deserialize(JSON.parse(piskelString), function (piskel, extra) {
pskl.utils.serialization.Deserializer.deserialize(JSON.parse(piskelString), function (piskel) {
pskl.app.piskelController.setPiskel(piskel);
pskl.app.previewController.setFPS(extra.fps);
});
};

View File

@@ -80,7 +80,10 @@
};
ns.StorageService.prototype.onSaveSuccess_ = function () {
$.publish(Events.SHOW_NOTIFICATION, [{'content': 'Successfully saved !'}]);
$.publish(Events.SHOW_NOTIFICATION, [{
content : 'Successfully saved !',
hideDelay : 3000
}]);
$.publish(Events.PISKEL_SAVED);
this.afterSaving_();
};
@@ -90,14 +93,16 @@
if (errorMessage) {
errorText += ' : ' + errorMessage;
}
$.publish(Events.SHOW_NOTIFICATION, [{'content': errorText}]);
$.publish(Events.SHOW_NOTIFICATION, [{
content : errorText,
hideDelay : 10000
}]);
this.afterSaving_();
return Q.reject(errorMessage);
};
ns.StorageService.prototype.afterSaving_ = function () {
$.publish(Events.AFTER_SAVING_PISKEL);
window.setTimeout($.publish.bind($, Events.HIDE_NOTIFICATION), 5000);
};
ns.StorageService.prototype.setSavingFlag_ = function (savingFlag) {

View File

@@ -82,4 +82,12 @@
ns.BaseTool.prototype.releaseToolAt = function (col, row, frame, overlay, event) {};
/**
* Does the tool support the ALT modifier. To be overridden by subclasses.
*
* @return {Boolean} true if the tool supports ALT.
*/
ns.BaseTool.prototype.supportsAlt = function () {
return false;
};
})();

View File

@@ -20,35 +20,67 @@
* @override
*/
ns.Circle.prototype.draw = function (col, row, color, targetFrame, penSize) {
var circlePixels = this.getCirclePixels_(this.startCol, this.startRow, col, row);
pskl.PixelUtils.resizePixels(circlePixels, penSize).forEach(function (point) {
this.getCirclePixels_(this.startCol, this.startRow, col, row, penSize).forEach(function (point) {
targetFrame.setPixel(point[0], point[1], color);
});
};
ns.Circle.prototype.getCirclePixels_ = function (x0, y0, x1, y1) {
var coords = pskl.PixelUtils.getOrderedRectangleCoordinates(x0, y0, x1, y1);
var xC = (coords.x0 + coords.x1) / 2;
var yC = (coords.y0 + coords.y1) / 2;
ns.Circle.prototype.getCirclePixels_ = function (x0, y0, x1, y1, penSize) {
var coords = pskl.PixelUtils.getOrderedRectangleCoordinates(x0, y0, x1, y1);
var pixels = [];
var xC = Math.round((coords.x0 + coords.x1) / 2);
var yC = Math.round((coords.y0 + coords.y1) / 2);
var evenX = (coords.x0 + coords.x1) % 2;
var evenY = (coords.y0 + coords.y1) % 2;
var rX = coords.x1 - xC;
var rY = coords.y1 - yC;
var x, y, angle, r;
var pixels = [];
var x, y, angle;
for (x = coords.x0 ; x < coords.x1 ; x++) {
angle = Math.acos((x - xC) / rX);
y = Math.round(rY * Math.sin(angle) + yC);
pixels.push({'col': x, 'row': y});
pixels.push({'col': 2 * xC - x, 'row': 2 * yC - y});
if (penSize == 1) {
for (x = coords.x0 ; x <= xC ; x++) {
angle = Math.acos((x - xC) / rX);
y = Math.round(rY * Math.sin(angle) + yC);
pixels.push([x - evenX, y]);
pixels.push([x - evenX, 2 * yC - y - evenY]);
pixels.push([2 * xC - x, y]);
pixels.push([2 * xC - x, 2 * yC - y - evenY]);
}
for (y = coords.y0 ; y <= yC ; y++) {
angle = Math.asin((y - yC) / rY);
x = Math.round(rX * Math.cos(angle) + xC);
pixels.push([x, y - evenY]);
pixels.push([2 * xC - x - evenX, y - evenY]);
pixels.push([x, 2 * yC - y]);
pixels.push([2 * xC - x - evenX, 2 * yC - y]);
}
return pixels;
}
for (y = coords.y0 ; y < coords.y1 ; y++) {
angle = Math.asin((y - yC) / rY);
x = Math.round(rX * Math.cos(angle) + xC);
pixels.push({'col': x, 'row': y});
pixels.push({'col': 2 * xC - x, 'row': 2 * yC - y});
var iX = rX - penSize;
var iY = rY - penSize;
if (iX < 0) {
iX = 0;
}
if (iY < 0) {
iY = 0;
}
for (x = 0 ; x <= rX ; x++) {
for (y = 0 ; y <= rY ; y++) {
angle = Math.atan(y / x);
r = Math.sqrt(x * x + y * y);
if ((rX <= penSize || rY <= penSize ||
r > iX * iY / Math.sqrt(iY * iY * Math.pow(Math.cos(angle), 2) + iX * iX * Math.pow(Math.sin(angle), 2)) +
0.5) &&
r < rX * rY / Math.sqrt(rY * rY * Math.pow(Math.cos(angle), 2) + rX * rX * Math.pow(Math.sin(angle), 2)) +
0.5) {
pixels.push([xC + x, yC + y]);
pixels.push([xC - x - evenX, yC + y]);
pixels.push([xC + x, yC - y - evenY]);
pixels.push([xC - x - evenX, yC - y - evenY]);
}
}
}
return pixels;

View File

@@ -102,4 +102,8 @@
this.shiftFrame(replayData.colDiff, replayData.rowDiff, frame, frame.clone(), event);
}.bind(this));
};
ns.Move.prototype.supportsAlt = function() {
return true;
};
})();

View File

@@ -20,10 +20,19 @@
* @override
*/
ns.Rectangle.prototype.draw = function (col, row, color, targetFrame, penSize) {
var rectanglePixels = pskl.PixelUtils.getBoundRectanglePixels(this.startCol, this.startRow, col, row);
var rectangle = pskl.PixelUtils.getOrderedRectangleCoordinates(this.startCol, this.startRow, col, row);
pskl.PixelUtils.resizePixels(rectanglePixels, penSize).forEach(function (point) {
targetFrame.setPixel(point[0], point[1], color);
});
for (var x = rectangle.x0; x <= rectangle.x1; x++) {
for (var y = rectangle.y0; y <= rectangle.y1; y++) {
if (
x > rectangle.x1 - penSize ||
x < rectangle.x0 + penSize ||
y > rectangle.y1 - penSize ||
y < rectangle.y0 + penSize
) {
targetFrame.setPixel(x, y, color);
}
}
}
};
})();

View File

@@ -10,6 +10,31 @@
match = filtered[0];
}
return match;
},
/**
* Split a provided array in a given amount of chunks.
* For instance [1,2,3,4] chunked in 2 parts will be [1,2] & [3,4].
* @param {Array} array the array to chunk
* @param {Number} chunksCount the number of chunks to create
* @return {Array<Array>} array of arrays containing the items of the original array
*/
chunk : function (array, chunksCount) {
var chunks = [];
// We cannot have more chunks than array items.
chunksCount = Math.min(chunksCount, array.length);
// chunksCount should be at least 1
chunksCount = Math.max(1, chunksCount);
var step = Math.round(array.length / chunksCount);
for (var i = 0 ; i < chunksCount ; i++) {
var isLast = i == chunksCount - 1;
var end = isLast ? array.length : (i + 1) * step;
chunks.push(array.slice(i * step, end));
}
return chunks;
}
};

View File

@@ -123,6 +123,33 @@
}
},
/**
* Insert the provided image in the provided frame, at the x, y sprite coordinates.
* Coordinates will be adjusted so that the image fits in the frame, if possible.
*/
addImageToFrame: function (frame, image, x, y) {
var imageFrame = pskl.utils.FrameUtils.createFromImage(image);
x = x - Math.floor(imageFrame.width / 2);
y = y - Math.floor(imageFrame.height / 2);
x = Math.max(0, x);
y = Math.max(0, y);
if (imageFrame.width <= frame.width) {
x = Math.min(x, frame.width - imageFrame.width);
}
if (imageFrame.height <= frame.height) {
y = Math.min(y, frame.height - imageFrame.height);
}
imageFrame.forEachPixel(function (color, frameX, frameY) {
if (color != pskl.utils.colorToInt(Constants.TRANSPARENT_COLOR)) {
frame.setPixel(x + frameX, y + frameY, color);
}
});
},
resize : function (frame, targetWidth, targetHeight, smoothing) {
var image = pskl.utils.FrameUtils.toImage(frame);
var resizedImage = pskl.utils.ImageResizer.resize(image, targetWidth, targetHeight, smoothing);
@@ -145,6 +172,16 @@
return pskl.utils.FrameUtils.createFromImageData_(imgData, w, h, preserveOpacity);
},
createFromImageSrc : function (src, preserveOpacity, cb) {
var image = new Image();
image.addEventListener('load', function onImageLoaded() {
image.removeEventListener('load', onImageLoaded);
var frame = ns.FrameUtils.createFromImage(image, preserveOpacity);
cb(frame);
});
image.src = src;
},
/*
* Create a pskl.model.Frame from an Image object. By default transparent
* pixels will be converted to completely opaque or completely transparent
@@ -177,74 +214,74 @@
},
/**
* Alpha compositing using porter duff algorithm :
* http://en.wikipedia.org/wiki/Alpha_compositing
* http://keithp.com/~keithp/porterduff/p253-porter.pdf
* @param {String} strColor1 color over
* @param {String} strColor2 color under
* @return {String} the composite color
* Create a Frame array from an Image object.
* Transparent pixels will either be converted to completely opaque or completely transparent pixels.
*
* @param {Image} image source image
* @param {Number} frameCount number of frames in the spritesheet
* @return {Array<Frame>}
*/
mergePixels__ : function (strColor1, strColor2, globalOpacity1) {
var col1 = pskl.utils.FrameUtils.toRgba__(strColor1);
var col2 = pskl.utils.FrameUtils.toRgba__(strColor2);
if (typeof globalOpacity1 == 'number') {
col1 = JSON.parse(JSON.stringify(col1));
col1.a = globalOpacity1 * col1.a;
createFramesFromSpritesheet : function (image, frameCount) {
var layout = [];
for (var i = 0 ; i < frameCount ; i++) {
layout.push([i]);
}
var a = col1.a + col2.a * (1 - col1.a);
var r = ((col1.r * col1.a + col2.r * col2.a * (1 - col1.a)) / a) | 0;
var g = ((col1.g * col1.a + col2.g * col2.a * (1 - col1.a)) / a) | 0;
var b = ((col1.b * col1.a + col2.b * col2.a * (1 - col1.a)) / a) | 0;
return 'rgba(' + r + ',' + g + ',' + b + ',' + a + ')';
var chunkFrames = pskl.utils.FrameUtils.createFramesFromChunk(image, layout);
return chunkFrames.map(function (chunkFrame) {
return chunkFrame.frame;
});
},
/**
* Convert a color defined as a string (hex, rgba, rgb, 'TRANSPARENT') to an Object with r,g,b,a properties.
* r, g and b are integers between 0 and 255, a is a float between 0 and 1
* @param {String} c color as a string
* @return {Object} {r:Number,g:Number,b:Number,a:Number}
* Create a Frame array from an Image object.
* Transparent pixels will either be converted to completely opaque or completely transparent pixels.
*
* @param {Image} image source image
* @param {Array <Array>} layout description of the frame indexes expected to be found in the chunk
* @return {Array<Object>} array of objects containing: {index: frame index, frame: frame instance}
*/
toRgba__ : function (c) {
if (colorCache[c]) {
return colorCache[c];
createFramesFromChunk : function (image, layout) {
var width = image.width;
var height = image.height;
// Recalculate the expected frame dimensions from the layout information
var frameWidth = width / layout.length;
var frameHeight = height / layout[0].length;
// Create a canvas adapted to the image size
var canvas = pskl.utils.CanvasUtils.createCanvas(frameWidth, frameHeight);
var context = canvas.getContext('2d');
// Draw the zoomed-up pixels to a different canvas context
var chunkFrames = [];
for (var i = 0 ; i < layout.length ; i++) {
var row = layout[i];
for (var j = 0 ; j < row.length ; j++) {
context.clearRect(0, 0 , frameWidth, frameHeight);
context.drawImage(image, frameWidth * i, frameHeight * j,
frameWidth, frameHeight, 0, 0, frameWidth, frameHeight);
var frame = pskl.utils.FrameUtils.createFromCanvas(canvas, 0, 0, frameWidth, frameHeight);
chunkFrames.push({
index : layout[i][j],
frame : frame
});
}
}
var color, matches;
if (c === 'TRANSPARENT') {
color = {
r : 0,
g : 0,
b : 0,
a : 0
};
} else if (c.indexOf('rgba(') != -1) {
matches = /rgba\((\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*,\s*(1|0\.\d+)\s*\)/.exec(c);
color = {
r : parseInt(matches[1], 10),
g : parseInt(matches[2], 10),
b : parseInt(matches[3], 10),
a : parseFloat(matches[4])
};
} else if (c.indexOf('rgb(') != -1) {
matches = /rgb\((\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\)/.exec(c);
color = {
r : parseInt(matches[1], 10),
g : parseInt(matches[2], 10),
b : parseInt(matches[3], 10),
a : 1
};
} else {
matches = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(c);
color = {
r : parseInt(matches[1], 16),
g : parseInt(matches[2], 16),
b : parseInt(matches[3], 16),
a : 1
};
return chunkFrames;
},
toFrameGrid : function (normalGrid) {
var frameGrid = [];
var w = normalGrid[0].length;
var h = normalGrid.length;
for (var x = 0 ; x < w ; x++) {
frameGrid[x] = [];
for (var y = 0 ; y < h ; y++) {
frameGrid[x][y] = normalGrid[y][x];
}
}
colorCache[c] = color;
return color;
return frameGrid;
}
};
})();

View File

@@ -2,6 +2,9 @@
var ns = $.namespace('pskl.utils');
ns.FunctionUtils = {
/**
* Returns a memoized version of the provided function.
*/
memo : function (fn, cache, scope) {
var memoized = function () {
var key = Array.prototype.join.call(arguments, '-');
@@ -11,6 +14,27 @@
return cache[key];
};
return memoized;
},
/**
* Returns a throttled version of the provided method, that will be called at most
* every X milliseconds, where X is the provided interval.
*/
throttle : function (fn, interval) {
var last, timer;
return function () {
var now = Date.now();
if (last && now < last + interval) {
clearTimeout(timer);
timer = setTimeout(function () {
last = now;
fn();
}, interval);
} else {
last = now;
fn();
}
};
}
};
})();

View File

@@ -20,66 +20,6 @@
context.drawImage(image, -image.width / 2, -image.height / 2);
context.restore();
return canvas;
},
/**
* Manual implementation of resize using a nearest neighbour algorithm
* It is slower than relying on the native 'disabledImageSmoothing' available on CanvasRenderingContext2d.
* But it can be useful if :
* - IE < 11 (doesn't support msDisableImageSmoothing)
* - need to display a gap between pixel
*
* @param {Canvas2d} source original image to be resized, as a 2d canvas
* @param {Number} zoom ratio between desired dim / source dim
* @param {Number} margin gap to be displayed between pixels
* @param {String} color or the margin (will be transparent if not provided)
* @return {Canvas2d} the resized canvas
*/
resizeNearestNeighbour : function (source, zoom, margin, marginColor) {
margin = margin || 0;
var canvas = pskl.utils.CanvasUtils.createCanvas(zoom * source.width, zoom * source.height);
var context = canvas.getContext('2d');
var imgData = pskl.utils.CanvasUtils.getImageDataFromCanvas(source);
var yRanges = {};
var xOffset = 0;
var yOffset = 0;
// Draw the zoomed-up pixels to a different canvas context
for (var x = 0; x < source.width; x++) {
// Calculate X Range
var xRange = Math.floor((x + 1) * zoom) - xOffset;
for (var y = 0; y < source.height; y++) {
// Calculate Y Range
if (!yRanges[y + '']) {
// Cache Y Range
yRanges[y + ''] = Math.floor((y + 1) * zoom) - yOffset;
}
var yRange = yRanges[y + ''];
var i = (y * source.width + x) * 4;
var r = imgData[i];
var g = imgData[i + 1];
var b = imgData[i + 2];
var a = imgData[i + 3];
context.fillStyle = 'rgba(' + r + ',' + g + ',' + b + ',' + (a / 255) + ')';
context.fillRect(xOffset, yOffset, xRange - margin, yRange - margin);
if (margin && marginColor) {
context.fillStyle = marginColor;
context.fillRect(xOffset + xRange - margin, yOffset, margin, yRange);
context.fillRect(xOffset, yOffset + yRange - margin, xRange, margin);
}
yOffset += yRange;
}
yOffset = 0;
xOffset += xRange;
}
return canvas;
}
};

View File

@@ -2,34 +2,6 @@
var ns = $.namespace('pskl.utils');
ns.LayerUtils = {
/**
* Create a Frame array from an Image object.
* Transparent pixels will either be converted to completely opaque or completely transparent pixels.
* TODO : move to FrameUtils
*
* @param {Image} image source image
* @param {Number} frameCount number of frames in the spritesheet
* @return {Array<Frame>}
*/
createFramesFromSpritesheet : function (image, frameCount) {
var width = image.width;
var height = image.height;
var frameWidth = width / frameCount;
var canvas = pskl.utils.CanvasUtils.createCanvas(frameWidth, height);
var context = canvas.getContext('2d');
// Draw the zoomed-up pixels to a different canvas context
var frames = [];
for (var i = 0 ; i < frameCount ; i++) {
context.clearRect(0, 0 , frameWidth, height);
context.drawImage(image, frameWidth * i, 0, frameWidth, height, 0, 0, frameWidth, height);
var frame = pskl.utils.FrameUtils.createFromCanvas(canvas, 0, 0, frameWidth, height);
frames.push(frame);
}
return frames;
},
mergeLayers : function (layerA, layerB) {
var framesA = layerA.getFrames();
var framesB = layerB.getFrames();

View File

@@ -2,34 +2,55 @@
var ns = $.namespace('pskl.utils');
ns.PiskelFileUtils = {
FAILURE : {
EMPTY : 'No data found in piskel file',
INVALID : 'Invalid piskel file, contact us on twitter @piskelapp',
DESERIALIZATION : 'Piskel data deserialization failed'
},
/**
* Load a piskel from a piskel file.
* After deserialization is successful, the provided success callback will be called.
* Success callback is expected to handle 3 arguments : (piskel:Piskel, descriptor:PiskelDescriptor, fps:Number)
* Success callback is expected to receive a single Piskel object argument
* @param {File} file the .piskel file to load
* @param {Function} onSuccess Called if the deserialization of the piskel is successful
* @param {Function} onError NOT USED YET
*/
loadFromFile : function (file, onSuccess, onError) {
pskl.utils.FileUtils.readFileAsArrayBuffer(file, function (content) {
pskl.utils.FileUtils.readFile(file, function (content) {
var rawPiskel = pskl.utils.Base64.toText(content);
ns.PiskelFileUtils.decodePiskelFile(
content,
function (piskel, extra) {
rawPiskel,
function (piskel) {
// if using Node-Webkit, store the savePath on load
// Note: the 'path' property is unique to Node-Webkit, and holds the full path
if (pskl.utils.Environment.detectNodeWebkit()) {
piskel.savePath = file.path;
}
onSuccess(piskel, extra);
onSuccess(piskel);
},
onError
);
});
},
decodePiskelFile : function (serializedPiskel, onSuccess, onError) {
pskl.utils.serialization.Deserializer.deserialize(serializedPiskel, function (piskel, extra) {
onSuccess(piskel, extra);
decodePiskelFile : function (rawPiskel, onSuccess, onError) {
var serializedPiskel;
if (rawPiskel.length === 0) {
onError(ns.PiskelFileUtils.FAILURE.EMPTY);
return;
}
try {
serializedPiskel = JSON.parse(rawPiskel);
} catch (e) {
onError(ns.PiskelFileUtils.FAILURE.INVALID);
return;
}
var piskel = serializedPiskel.piskel;
pskl.utils.serialization.Deserializer.deserialize(serializedPiskel, onSuccess, function () {
onError(ns.PiskelFileUtils.FAILURE.DESERIALIZATION);
});
}
};

Some files were not shown because too many files have changed in this diff Show More