250 Commits

Author SHA1 Message Date
5e46d8b7e2 docs(readme): add Greenkeeper badge 2017-05-06 14:44:55 +00:00
e15d7b2422 chore(package): update dependencies 2017-05-06 14:43:20 +00:00
a8c85b8a29 Issue #654 : stop using jquery to create the FramesList markup 2017-04-01 12:48:11 +02:00
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
98768b2e5b select color using alt+click (fixes #623) 2017-03-02 01:39:28 +01:00
62b1b8baf0 Merge pull request #642 from juliandescottes/sanitize-strings
sanitize strings coming from user inputs
2017-02-23 21:01:05 +01:00
11a063de12 sanitize strings coming from user inputs 2017-02-23 19:37:29 +01:00
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
974450837e add confirmation message when closing desktop app with unsaved changes 2017-02-19 13:19:09 +01:00
c6c64af2fd Use Q deferred instead of native promise (IE11) 2017-02-11 16:57:56 +01:00
c68b82339c remove classList toggle with 2nd argument for IE11 2017-02-11 16:51:27 +01:00
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
f30e16386d Use standard HTML entities for the arrows displayed on the keyboard shortcut list 2017-02-05 11:47:45 +01:00
08b97cb6f0 select previous layer after deleting layer 2017-02-04 14:32:23 +01:00
b6fa769ba1 temporary fix for layerlist scroll 2017-02-04 14:09:27 +01:00
47b09b10c5 Merge branch 'master' of https://github.com/juliandescottes/piskel 2017-02-04 13:49:20 +01:00
f039a89572 disable layerlist smoothscrolling 2017-02-04 13:43:49 +01:00
e74329f04e Merge pull request #629 from GMartigny/master
Add CodeClimate
2017-02-02 19:30:40 +01:00
8959b201e9 Excludes lib from linting 2017-02-01 16:17:38 +01:00
f5491dc557 Irregular whitespace 2017-02-01 16:15:02 +01:00
9ce3d44cc6 Add config file for CodeClimate 2017-02-01 15:51:13 +01:00
8928f3e626 Release version 0.10.0 2017-01-29 14:25:44 +01:00
e04fde7084 log errors when Deserialize fails 2017-01-29 14:18:59 +01:00
a25648f8e2 bump version to 0.10.0-RC2 2017-01-29 14:05:11 +01:00
1918227c81 add integration tests for PNG export & scaled export 2017-01-29 13:56:06 +01:00
8b1b21368c add gif export integration test 2017-01-29 02:42:42 +01:00
d781df3290 add drawing test for center tool 2017-01-29 00:11:58 +01:00
35bfcb5f00 Refines Circle tool for PenSize = 1 2017-01-28 02:27:50 +01:00
cce4cde98b Corrects Circle tool for even dimensions. 2017-01-28 02:27:50 +01:00
4706587abb Rewrites Circle drawing tool to behave better with large pen sizes. 2017-01-28 02:27:50 +01:00
c0182952c9 Merge pull request #622 from juliandescottes/fix-current-colors
Fix current colors
2017-01-25 15:08:30 +01:00
ba779a97a6 fix #621 : allow color worker to detect up to 256 colors 2017-01-25 15:07:55 +01:00
dc149f88d6 remove extra caching from CurrentColorsService 2017-01-25 14:59:09 +01:00
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
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
11db5ca45c remove getBoundRectanglePixels from PixelUtils 2017-01-21 18:24:48 +01:00
37445202f3 Merge pull request #618 from smiegrin/rectanlgeRetool
Improves Rectangle tool performance.
2017-01-21 18:19:56 +01:00
f8b77403cd Improves Rectangle tool performance. 2017-01-16 11:52:22 -07:00
74d8aa8c9c migrate copy script to node 2017-01-15 14:13:05 +01:00
acad4df49e fix typos in perf warning popup 2017-01-15 13:33:57 +01:00
af92ee6bd7 increase height of performance info popup 2017-01-15 13:26:48 +01:00
a740699775 increase appengine storage limit to avoid false positive 2017-01-15 13:08:41 +01:00
6ce2c00acf force color black on brush size label 2017-01-15 13:00:15 +01:00
93db679dd7 release: bump version to 0.10.0-RC1 2017-01-15 12:51:10 +01:00
5bde3c471d remove unused paletteController member from DrawingController 2017-01-15 12:35:38 +01:00
94b1a1df4a History service gets core piskelController from public piskelController 2017-01-14 21:58:37 +01:00
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
8cad3bb607 display error message on file load error 2017-01-14 21:37:52 +01:00
552d4fa710 nits on test/js/utils/PixelUtilsTest_visit_connected.js 2017-01-14 15:44:47 +01:00
b11f7a430d add unit test for getSimilarConnectedPixelsFromFrame 2017-01-14 00:56:24 +01:00
a4952d0db2 fix #603 shape select fails when clicking outside drawing area 2017-01-13 15:07:05 +01:00
42f4812b46 move drop image logic to FrameUtils, add unit test 2017-01-13 13:58:57 +01:00
bf91c4ef62 Fix image dropper when image has to be moved 2017-01-13 13:22:21 +01:00
03f37d46ac add unit tests for colorToInt 2017-01-13 03:31:02 +01:00
0abd779348 fix #602 : shape selection fails with some colors 2017-01-13 02:59:59 +01:00
33259e5522 nits on previewSize widget 2017-01-11 23:43:57 +01:00
e8207aba57 update labels for preview size widget 2017-01-11 21:59:37 +01:00
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
2f47bc433c lint fix 2017-01-11 12:32:29 +01:00
9cd3fa03a0 Merge branch 'master' into 362-MultiSizePreview 2017-01-11 12:28:20 +01:00
d4b4192d45 Final : Specific tooltip while preview size selection disabled 2017-01-11 12:23:49 +01:00
d5199d38aa remove css variables from built css (edge :( ) 2017-01-11 01:13:56 +01:00
ab082b9f4a set nwjs downloadURL to https 2017-01-09 09:27:49 +01:00
2ec6c4b848 upgrade nwjs to 0.19.4 2017-01-09 09:19:12 +01:00
2cdc999875 Fix bug when opening save panel for sprite with performance problem 2017-01-09 09:10:20 +01:00
4c6d2c1e48 Merge pull request #607 from juliandescottes/controller-tests
Add first integration tests
2017-01-09 00:43:46 +01:00
95256071e1 add missing EOF new lines 2017-01-08 20:43:15 +01:00
b86ec72a4e use evalLine in integration/settings/test-resize 2017-01-08 20:42:00 +01:00
70085bc056 Add integration tests for resize content and resize from other origin 2017-01-08 20:38:12 +01:00
a328e4d20e add resize test checking frame content
Moved shared methods to a head.js file
2017-01-08 19:56:53 +01:00
569b508fd5 mutualize casperjsOptions in gruntfile 2017-01-08 16:39:55 +01:00
d30f6a05d1 add integration tests 2017-01-08 16:09:03 +01:00
76ae797a9e nits: typos and case 2017-01-08 11:55:09 +01:00
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
5bd113b38f Removed reset from SelectedColorsService and implemented the reset in PaletteController 2017-01-04 18:28:58 +00:00
aa5d4d4090 Merge branch 'master' of https://github.com/juliandescottes/piskel 2017-01-02 23:35:07 +01:00
398d93557a remove unused argument in FileDropperService constructor 2017-01-02 23:34:54 +01:00
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
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
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
809737908c Add a setting for changing the color format shown in the color picker 2016-12-29 08:08:13 +01:00
e43cee3c94 Merge pull request #598 from juliandescottes/disable-gallery
Disable gallery
2016-12-27 01:04:25 +01:00
b869d63211 remove useless comment 2016-12-27 00:56:18 +01:00
c0f7e7be52 jshint cleanup, move HTML to a template, add css classes 2016-12-27 00:56:18 +01:00
98527c6ded update notification style to use piskel theme colors 2016-12-27 00:56:17 +01:00
9518d570e6 display save to gallery warning if performance issue detected 2016-12-27 00:52:32 +01:00
01b9898181 improve serialization error detection for firefox 2016-12-27 00:52:32 +01:00
184b2e48aa disable gallery save when if spritesheet size too big (WIP) 2016-12-27 00:52:32 +01:00
e97a641e95 add constant for the max datastore accepted size 2016-12-27 00:52:32 +01:00
7eea5d672a fix hover image for minimap popup preview 2016-12-25 10:20:57 +01:00
537234b1d1 update minimap crop style 2016-12-24 11:09:12 +01:00
57936d90b1 fix jshint issue 2016-12-24 10:26:40 +01:00
ef8060c07d remove unused resizeNearestNeighbor util 2016-12-24 10:24:23 +01:00
8551a8546a fix #369 improve perf of grid rendering 2016-12-23 23:41:41 +01:00
eb84e87a13 add gold border on textfield:focus 2016-12-23 21:09:05 +01:00
ca3b789747 add user setting for seamless mode opacity 2016-12-22 23:27:00 +01:00
22dd474799 add border radius to settings export tabs 2016-12-22 22:33:04 +01:00
16151e8e95 remove text-shadow from settings panels 2016-12-22 22:21:32 +01:00
2f62be4927 simplify buttons styling 2016-12-22 14:21:21 +01:00
fbaccb03f2 introduce css variables 2016-12-22 13:06:01 +01:00
dd9b1e0189 add test to check deserialized piskel contains valid frames 2016-12-21 17:20:54 +01:00
dda566a218 fix broken serializer 2016-12-21 17:20:54 +01:00
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
6cc41ee07b add a warning popup when detecting an unsupported browser 2016-12-21 15:44:04 +01:00
eca9191f29 increase drawing test timeout to 30 seconds 2016-12-21 15:38:31 +01:00
83c7e950f0 Merge pull request #593 from juliandescottes/save-split
Save split
2016-12-21 12:25:54 +01:00
32070efcc1 save split: add comments and cleanup 2016-12-21 12:22:50 +01:00
c743334a31 Switch back to line layout 2016-12-21 11:45:34 +01:00
84419a1550 split saved piskel in chunks when serialization fails 2016-12-21 02:28:11 +01:00
66c941dd25 support chunked layerData in regular deserializer 2016-12-18 11:36:11 +01:00
dba62d2b0d add pskl.utils.Array.chunk 2016-12-18 09:16:52 +01:00
156161f7c8 Merge pull request #574 from juliandescottes/warning-notifications
Warning notifications
2016-12-18 07:15:03 +01:00
58bfe16b27 performance warning: text cleanup 2016-12-18 06:52:44 +01:00
57b37971f6 add svg warning icon 2016-12-17 10:19:17 +01:00
8dab74ceea add text to warning notifications popup 2016-12-16 10:04:36 +01:00
d0cca52f47 issue #555: add performance warning icon, show dialog 2016-12-16 07:49:15 +01:00
58cf9f8390 issue #555 detect performance issues based on sprite specs 2016-12-16 07:49:15 +01:00
49109e51c9 fix js error when resizing piskel (missing fps arg) 2016-12-16 07:02:31 +01:00
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
b21fc0490d remove unused previewController member in ImportService 2016-12-13 11:04:03 +01:00
25ede9ffff cleanup unused arguments in PiskelFileUtils 2016-12-13 11:00:58 +01:00
37d2861352 move fps info from preview controller to piskel model 2016-12-13 09:17:34 +01:00
2fe0b842a5 update pixijs export metadata app url 2016-12-13 07:28:55 +01:00
dd7ab574b9 Merge pull request #581 from PSeitz/savejson
add pixi export
2016-12-12 20:23:45 -10:00
c2fdb65e37 simplify error template 2016-12-12 10:08:37 +01:00
aef88c8713 simplify error template 2016-12-12 09:58:56 +01:00
b3c13f1630 Merge pull request #587 from juliandescottes/create-issue-template
add issue template
2016-12-11 22:42:46 -10:00
6b0eda2998 add issue template 2016-12-12 09:35:02 +01:00
16cfffa898 Merge pull request #579 from PSeitz/piskelcopy
Copy between piskels
2016-12-09 07:18:45 -10:00
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
21c7425cbc add pixi export 2016-12-05 19:11:01 +01:00
9ac9583455 Address comment and nit 2016-12-05 09:43:15 +01:00
9608995d9c Fix spaces 2016-12-04 11:59:07 +01:00
5faa985dea Copy between piskels
#272
2016-12-04 11:40:18 +01:00
5544f734cf 🥷 2016-12-02 17:30:38 +01:00
9d0d38e081 Proposal : tooltip only when layer name overflow 2016-12-02 17:21:10 +01:00
ad5c8182e4 Add ellipsis to overflowing layer item name #559 2016-12-01 11:43:23 +01:00
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
f2424ed16e Resolve options collision
Disable preview widget with tooltip
Fix animation with shortcuts
2016-11-30 11:25:47 +01:00
da0e8dec84 Stylistic changes to pixel and pen size code. 2016-11-28 08:21:30 -07:00
a5fc491e92 Merge pull request #573 from juliandescottes/update-deps
Update deps
2016-11-27 23:43:23 +01:00
e53ee0604c chore: update dateformat to 2.0.0 2016-11-27 12:08:51 +01:00
e1671202af chore: update grunt-contrib-jshint to 1.1.0 2016-11-27 12:08:18 +01:00
5e3bb9d79c chore: update nwjs to 0.18.8 2016-11-27 12:07:25 +01:00
c5c336e12c chore: update grunt-nw-builder to 3.1.0 2016-11-27 11:57:13 +01:00
85b141df52 chore: udpate karma to 1.3.0 2016-11-27 11:54:27 +01:00
a65eeee2e0 Merge branch 'master' of https://github.com/juliandescottes/piskel 2016-11-26 13:32:23 -07:00
39dba2b70f Displays pen size in selector for sizes above 4 pixels wide. 2016-11-26 13:09:07 -07:00
0cb4a7831b fix drawing tests when running in browser 2016-11-24 14:18:52 +01:00
696d18748a Move tooltips
Change "best" option on resize
Update available options on resize and configuration change
2016-11-23 18:32:17 +01:00
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
5996216ae7 Merge pull request #566 from juliandescottes/memory-improvements
Memory improvements
2016-11-22 00:27:33 +01:00
8cb7a4aaf6 Permits brush size of only 1-32 pixels. 2016-11-21 12:53:14 -07:00
136506da40 issue #374 throttle calls to currentColorsService update function 2016-11-20 12:08:31 +01:00
d08c101b11 issue #374 cap cached frames for cachedframeprocessor to 100 2016-11-20 01:10:05 +01:00
4b50dfdb5b issue #374 limit undo/redo to the last 500 states 2016-11-20 00:24:48 +01:00
e3b363d757 Fix regression when loading piskel files 2016-11-12 18:33:00 +01:00
440c28d0fd Fix regression when loading piskel files 2016-11-12 18:32:21 +01:00
a560872df7 Stylistic changes to src/js/utils/PixelUtils.js 2016-10-28 18:39:00 -06:00
aca6c28b4b Allows square brush size of up to 1,000,000 pixels using keyboard shortcuts. 2016-10-28 17:56:22 -06:00
4a01f5625c release: set version to v0.9.0-RC2 2016-10-16 21:32:00 +02:00
8f493a8147 add comment in arraybuffer serialization helpers 2016-10-16 18:33:10 +02:00
73986c4e61 move arraybuffer serializer to dedicated subfolder 2016-10-16 18:28:12 +02:00
2a7957bce2 rollback to modelv2 for all serializations except history service 2016-10-16 00:22:06 +02:00
0586756b92 release: set version to 0.9.0-RC1 2016-10-15 19:30:53 +02:00
2b78456314 fix #554: zooming in/out breaks drawing area on Safari 2016-10-10 18:14:06 +02:00
4b378d48a2 trigger piskel_saved event after loading page sprite 2016-10-10 03:03:59 +02:00
5faf4af7c9 fix page title update when loading existing piskel 2016-10-10 02:44:51 +02:00
d947fe1c05 add blob type 2016-10-10 02:17:19 +02:00
85336f9b9a fix loading of v2 models 2016-10-10 01:47:26 +02:00
91f637d2fd fix color picker regression with intToColor missing 2016-10-09 23:59:16 +02:00
eb69c91d43 add other color picker test 2016-10-09 23:59:16 +02:00
7ae40d8794 fix pick color with middle mouse no longer workin 2016-10-09 23:59:16 +02:00
5a39d6850e fix backward compatibility issue on piskelapp.com 2016-10-09 23:59:16 +02:00
f342b72e1b Merge pull request #553 from juliandescottes/fix-transparency-glitch
Fix transparency issues after deserialization
2016-10-09 13:25:49 +02:00
1624792f61 Fix transparency issues after deserialization 2016-10-09 13:09:28 +02:00
dd58af30b9 close dialogs when clicking outside of the dialog container 2016-10-02 00:56:13 +02:00
4e515f4820 use monospace instead of Courier 2016-10-02 00:49:49 +02:00
71ec809aea fix #502: add smoothscroll polyfill 2016-10-02 00:23:26 +02:00
9538e03928 Fix BackupService change JSON to new serialization 2016-10-01 14:34:01 +02:00
e647826095 Move string to buffer converting code to utils 2016-10-01 14:34:01 +02:00
5fe9081df1 Remove unneccessary frame version incrementing 2016-10-01 14:34:01 +02:00
d4f038f97b Move buffer to string converting to utils 2016-10-01 14:34:01 +02:00
23a0768f8d Fix wrong calculation of requried bytes 2016-10-01 14:34:01 +02:00
4cc434edd6 Remove atob and btoa, it's already in base64 2016-10-01 14:34:01 +02:00
26b3cf3a23 Remove unnecessary caching 2016-10-01 14:34:01 +02:00
7e9c632efd Remove unused sub variable 2016-10-01 14:34:01 +02:00
6c50816830 Refactor downloadAsFile to accept blob 2016-10-01 14:34:01 +02:00
38cbfe4fce Fix FileDropperService to work with the new cb 2016-10-01 14:34:01 +02:00
d775609183 Remove unneccessary frame version incrementation 2016-10-01 14:34:01 +02:00
74f36daa7c Break oneliner to multiple lines 2016-10-01 14:34:01 +02:00
ffca6aa44c Move intToHex to utils and change for to map 2016-10-01 14:34:01 +02:00
6ea9c248d8 Remove unneccessary else if 2016-10-01 14:34:01 +02:00
9c96c7eaf2 Fix missing variable and remove unnecessesary if 2016-10-01 14:34:01 +02:00
c97ce34e6e Fix consistency 2016-10-01 14:34:01 +02:00
43f989919c Remove unneccessary type check 2016-10-01 14:34:01 +02:00
0ec8036670 Move Uint32Array.fill polyfill to utils 2016-10-01 14:34:01 +02:00
3a2d837f5a Decrease animated preview throttle to 300ms 2016-10-01 14:34:01 +02:00
0f404d2b25 Refactor dom initialize flag 2016-10-01 14:34:01 +02:00
5577c3ab5a Fix GalleryStorageService
GalleryStorageService is sending JSON so it couldn't serialize the
ArrayBuffer which came from serializing the Piskel. In this commit it
sends the ArrayBuffer as an Array to the server so it can store it easily.
2016-10-01 14:34:01 +02:00
baf8049ea6 Fix jshint errors 2016-10-01 14:34:01 +02:00
74b7f93ddb Fix code style errors 2016-10-01 14:34:01 +02:00
fb97533cc9 Rework frameslist for better perfomance 2016-10-01 14:34:01 +02:00
ebab5ecb3f Change frameslist throttle from 1sec to 500ms 2016-10-01 14:34:01 +02:00
37443ecdc2 Fix code style errors 2016-10-01 14:34:01 +02:00
91f0d98d63 Fix wrong colors being in current colors 2016-10-01 14:34:01 +02:00
fa716d7a6c Fix ColorSwap and Bucket tool 2016-10-01 14:34:01 +02:00
ac3e43bf61 Add caching to CurrentColorsService 2016-10-01 14:34:01 +02:00
502eb1aac4 Fix missing name and description from v2 2016-10-01 14:34:01 +02:00
218c0b511b Fix callbacks to work with the new serializer 2016-10-01 14:34:01 +02:00
a3fd2f0d89 Remove unnecessary argument 2016-10-01 14:34:01 +02:00
c522d48c8f Fix test to work with new serializer format 2016-10-01 14:34:01 +02:00
c2b283be67 Fix services to use new serializer format 2016-10-01 14:34:01 +02:00
dbbd57ae28 Fix old deserializers to match new callback func 2016-10-01 14:34:01 +02:00
fefc635e25 Add function for reading file as array buffer 2016-10-01 14:34:01 +02:00
8910bf0dbd Add model version 2 backward compatiblity 2016-10-01 14:34:01 +02:00
f607c65789 Add model version 3 deserializer 2016-10-01 14:34:01 +02:00
2962a838d3 Add model version 3 serializer 2016-10-01 14:34:01 +02:00
7eab386122 Make sure opacity is a float and rounded 2016-10-01 14:34:01 +02:00
0c0aa5f2c9 Add caching to CurrentColorsService 2016-10-01 14:34:01 +02:00
bb7d5c862f Remove unnecessary rendering 2016-10-01 14:34:01 +02:00
f0f79754f1 Remove loop and use the pixels directly 2016-10-01 14:34:01 +02:00
a097d0b897 Fix frame processor caching 2016-10-01 14:34:01 +02:00
a1ab693fb5 Remove nested loop 2016-10-01 14:34:01 +02:00
7707aca03e Change nested loop and directly change pixels 2016-10-01 14:34:01 +02:00
f829d589d5 Improve frame colors worker perfomance by caching 2016-10-01 14:34:01 +02:00
712262b82a Format some code and remove unnecessary logging 2016-10-01 14:34:01 +02:00
0bbb4ff2d5 Remove 2nd level cache 2016-10-01 14:34:01 +02:00
6ee23e22df Throttle frameslist and preview rendering 2016-10-01 14:34:01 +02:00
f5c98cf0b3 Rework pixel storage, manipulation, rendering 2016-10-01 14:34:01 +02:00
06abdca62e Change model version to 3
The new serializing will use typed arrays / blob.
2016-10-01 14:34:01 +02:00
2ca97b48e7 Add another performance tests for layers and undo 2016-10-01 13:56:53 +02:00
132487704b Add simple performance test 2016-10-01 13:48:58 +02:00
f6be33d5bf Add performance figures to drawing tests 2016-10-01 13:09:19 +02:00
8a031d771a Merge pull request #550 from david-szabo97/feature_export-selected-frame
Add misc export: download selected frame as png
2016-09-28 14:42:46 +02:00
3be2b3d3bc Add misc export: download selected frame as png 2016-09-23 16:35:22 +02:00
7b512f6412 Merge pull request #539 from juliandescottes/fix-drawing-tests
Fix drawing tests
2016-09-01 15:51:05 +02:00
b1058c452e Issue #533: Add grunt task aliases for backward compat 2016-09-01 15:37:28 +02:00
7bc5d20b2e Issue #533: Merge test suites. use delay as timeout 2016-09-01 01:42:06 +02:00
e9ef6eb5b3 Issue #533: drawing tests, fix keyboard event replay on osx 2016-09-01 00:14:13 +02:00
a2d5e6ff04 Issue #533: Fix drawing tests cross-browser/OS issues 2016-09-01 00:12:07 +02:00
4d12908363 Merge pull request #536 from david-szabo97/update_phantomjs_casperjs
Update PhantomJS and CasperJS
2016-09-01 00:11:01 +02:00
682ef9cf91 Merge pull request #538 from david-szabo97/hotfix-retina_icons_gitignore
Remove icons@2x.png from the repo
2016-08-31 23:08:37 +02:00
cbacd68414 Remove icons@2x.png from the repo 2016-08-31 22:40:22 +02:00
3b65b90de1 Fix package.json 2016-08-31 17:25:20 +02:00
e7ed2f9b24 Update phantomjs to 2.1.7
PhantomJS 1.9.19 doesn't support typed arrays so the tests would fail.
2016-08-31 16:27:37 +02:00
0238dffe71 Update CasperJS and refactor tests 2016-08-31 16:20:01 +02:00
52eaa8b170 Merge pull request #529 from david-szabo97/hotfix-multiple_piskel_ready_callback
Change onPiskelReady to support multiple callback
2016-08-30 00:18:53 +02:00
53d728cf3a Make code more consistent 2016-08-26 01:11:03 +02:00
d01f4ef83e Change onPiskelReady to support multiple callback 2016-08-24 22:43:16 +02:00
187 changed files with 4976 additions and 1109 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.

1
.gitignore vendored
View File

@ -28,6 +28,7 @@ build/closure/closure_compiled_binary.js
# spriting artifacts
src/img/icons.png
src/img/icons@2x.png
src/css/icons.css
# plato report directory

View File

@ -6,7 +6,7 @@ before_install:
- npm install -g grunt-cli
- git clone git://github.com/n1k0/casperjs.git ~/casperjs
- cd ~/casperjs
- git checkout tags/1.0.2
- git checkout tags/1.1.3
- export PATH=$PATH:`pwd`/bin
- cd -
before_script:

View File

@ -9,8 +9,6 @@ module.exports = function(grunt) {
TEST : 9991
};
var DEV_MODE = '?debug';
// create a version based on the build timestamp
var dateFormat = require('dateformat');
var version = '-' + dateFormat(new Date(), "yyyy-mm-dd-hh-MM");
@ -34,26 +32,19 @@ module.exports = function(grunt) {
var stylePaths = require('./src/piskel-style-list.js').styles;
var piskelStyles = prefixPaths(stylePaths, "src/");
var getCasperConfig = function (suiteName, delay, host) {
var testPaths = require('./test/casperjs/' + suiteName).tests;
var tests = prefixPaths(testPaths, "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',
];
return {
filesSrc : tests,
options : {
args : {
baseUrl : 'http://' + host + ':' + PORT.TEST,
mode : DEV_MODE,
delay : delay
},
async : false,
direct : false,
logLevel : 'info',
printCommand : false,
printFilePaths : 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') {
@ -110,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'
]
},
@ -135,7 +128,7 @@ module.exports = function(grunt) {
path : 'http://' + hostname + ':' + PORT.PROD + '/'
},
dev : {
path : 'http://' + hostname + ':' + PORT.DEV + '/' + DEV_MODE
path : 'http://' + hostname + ':' + PORT.DEV + '/?debug'
}
},
@ -180,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'
}
},
@ -231,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: {
@ -282,9 +288,23 @@ module.exports = function(grunt) {
}
},
ghost : {
'travis' : getCasperConfig('TravisTestSuite.js', 10000, hostname),
'local' : getCasperConfig('LocalTestSuite.js', 50, hostname)
casperjs : {
drawing : {
files : {
src: ['test/casperjs/DrawingTest.js']
},
options : {
casperjsOptions: casperjsOptions
}
},
integration : {
files : {
src: integrationTests
},
options : {
casperjsOptions: casperjsOptions
}
}
},
/**
@ -294,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,
@ -304,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/"]
@ -313,38 +335,42 @@ module.exports = function(grunt) {
}
});
// Validate
// TEST TASKS
// Run linting
grunt.registerTask('lint', ['jscs:js', 'leadingIndent:css', 'jshint']);
// karma/unit-tests task
// Run unit-tests
grunt.registerTask('unit-test', ['karma']);
// 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']);
// Validate & Test
grunt.registerTask('test-travis', ['lint', 'unit-test', 'build-dev', 'connect:test', 'ghost:travis']);
// Validate & Test (faster version) will NOT work on travis !!
grunt.registerTask('test-local', ['lint', 'unit-test', 'build-dev', 'connect:test', 'ghost:local']);
grunt.registerTask('test-local-nolint', ['unit-test', 'build-dev', 'connect:test', 'ghost:local']);
// Run the tests, even if the linting fails
grunt.registerTask('test-nolint', ['unit-test', 'build-dev', 'connect:test', 'casperjs:drawing', 'casperjs:integration']);
grunt.registerTask('test', ['test-travis']);
grunt.registerTask('precommit', ['test-local']);
// 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']);
// Validate & Build
grunt.registerTask('default', ['lint', 'build']);
// Build stand alone app with nodewebkit
grunt.registerTask('desktop', ['clean:desktop', 'default', 'replace:desktop', 'nwjs:windows']);
grunt.registerTask('desktop-mac', ['clean:desktop', 'default', 'replace:desktop', 'nwjs:macos']);
// SERVER TASKS
// Start webserver and watch for changes
grunt.registerTask('serve', ['build', 'connect:prod', 'open:prod', 'watch:prod']);
// Start webserver on src folder, in debug mode
grunt.registerTask('serve-dev', ['build-dev', 'connect:dev', 'open:dev', 'watch:dev']);
grunt.registerTask('play', ['build-dev', 'connect:dev', 'open:dev', 'watch:dev']);
grunt.registerTask('serve-debug', ['serve-dev']);
grunt.registerTask('play', ['serve-dev']);
// ALIASES, kept for backward compatibility
grunt.registerTask('serve-debug', ['play']);
grunt.registerTask('serve-dev', ['play']);
grunt.registerTask('test-travis', ['test']);
grunt.registerTask('test-local', ['test']);
// Default task
grunt.registerTask('default', ['lint', 'build']);
};

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

@ -8,7 +8,10 @@ module.exports = function(config) {
var piskelScripts = require('./src/piskel-script-list.js').scripts.map(mapToSrcFolder);
piskelScripts.push('test/js/testutils/**/*.js');
piskelScripts.push('test/js/**/*.js');
// 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)
@ -21,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-SNAPSHOT",
"version": "0.10.0",
"description": "Pixel art editor",
"author": "Julian Descottes <julian.descottes@gmail.com>",
"contributors": [
@ -23,35 +23,39 @@
"scripts": {
"test": "grunt test",
"start": "nodewebkit",
"preversion": "grunt test-local build",
"preversion": "grunt test build",
"postversion": "git push && git push --tags && npm publish"
},
"devDependencies": {
"dateformat": "1.0.11",
"grunt": "^0.4.5",
"grunt-contrib-clean": "1.0.0",
"dateformat": "2.0.0",
"fs-extra": "3.0.1",
"grunt": "^1.0.1",
"grunt-casperjs": "^2.2.1",
"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-ghost": "1.1.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",
"phantomjs": "1.9.19"
"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",
"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;
@ -97,7 +97,7 @@
border-radius: 2px;
text-align: center;
font-family:Courier;
font-family: monospace;
font-weight: bold;
font-size : 18px;
color: white;
@ -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

@ -83,7 +83,7 @@
color:#888;
font-size:12px;
font-weight:bold;
font-family:Courier;
font-family:monospace;
}
/**

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 {
@ -110,7 +110,7 @@
background: #111;
border-radius: 2px;
color: #D3D3D3;
font-family: Courier!important;
font-family: monospace!important;
}
.sp-input.sp-validation-error {

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

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,20 +154,26 @@
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();
var drawingLoop = new pskl.rendering.DrawingLoop();
drawingLoop.addCallback(this.render, this);
drawingLoop.start();
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();
this.initTooltips_();
var piskelData = this.getPiskelInitData_();
if (piskelData && piskelData.piskel) {
this.loadPiskel_(piskelData.piskel, piskelData.descriptor, piskelData.fps);
this.loadPiskel_(piskelData);
}
if (pskl.devtools) {
@ -173,13 +186,23 @@
mb.createMacBuiltin('Piskel');
gui.Window.get().menu = mb;
}
if (pskl.utils.UserAgent.isUnsupported()) {
$.publish(Events.DIALOG_DISPLAY, {
dialogId : 'unsupported-browser'
});
}
},
loadPiskel_ : function (serializedPiskel, descriptor, fps) {
loadPiskel_ : function (piskelData) {
var serializedPiskel = piskelData.piskel;
pskl.utils.serialization.Deserializer.deserialize(serializedPiskel, function (piskel) {
piskel.setDescriptor(descriptor);
pskl.app.piskelController.setPiskel(piskel);
pskl.app.previewController.setFPS(fps);
$.publish(Events.PISKEL_SAVED);
if (piskelData.descriptor) {
// Backward compatibility for v2 or older
piskel.setDescriptor(piskelData.descriptor);
}
});
},

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(
@ -238,13 +241,15 @@
var evt = jQueryEvent.originalEvent;
// Ratio between wheelDeltaY (mousewheel event) and deltaY (wheel event) is -40
var delta;
if (pskl.utils.UserAgent.isChrome) {
delta = evt.wheelDeltaY;
} else if (pskl.utils.UserAgent.isIE11) {
if (pskl.utils.UserAgent.isIE11) {
delta = evt.wheelDelta;
} else if (pskl.utils.UserAgent.isFirefox) {
delta = -40 * evt.deltaY;
} else {
delta = evt.wheelDeltaY;
}
delta = delta || 0;
var modifier = (delta / 120);
if (pskl.utils.UserAgent.isMac ? evt.metaKey : evt.ctrlKey) {
@ -294,38 +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)) {
$.publish(Events.SELECT_PRIMARY_COLOR, [frame.getPixel(coords.x, coords.y)]);
}
} 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,29 +11,39 @@
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));
this.cachedFrameProcessor.setOutputCloner(this.clonePreviewCanvas_.bind(this));
this.initDragndropBehavior_();
};
ns.FramesListController.prototype.init = function() {
$.subscribe(Events.TOOL_RELEASED, this.flagForRedraw_.bind(this));
$.subscribe(Events.PISKEL_RESET, this.flagForRedraw_.bind(this));
$.subscribe(Events.PISKEL_RESET, this.flagForRedraw_.bind(this, true));
$.subscribe(Events.USER_SETTINGS_CHANGED, this.flagForRedraw_.bind(this));
$.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();
};
ns.FramesListController.prototype.flagForRedraw_ = function () {
ns.FramesListController.prototype.flagForRedraw_ = function (regenerateDom) {
this.redrawFlag = true;
if (regenerateDom) {
this.regenerateDomFlag = true;
}
};
ns.FramesListController.prototype.refreshZoom_ = function () {
@ -42,17 +52,25 @@
ns.FramesListController.prototype.render = function () {
if (this.redrawFlag) {
this.createPreviews_();
if (this.regenerateDomFlag) {
this.tiles = [];
this.addFrameTile = null;
this.createPreviews_();
this.regenerateDomFlag = false;
}
this.updatePreviews_();
this.redrawFlag = false;
}
};
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;
@ -65,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) {
@ -80,27 +97,79 @@
if (action === ACTION.CLONE) {
this.piskelController.duplicateFrameAt(index);
var clonedTile = this.createPreviewTile_(index + 1);
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.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.previewList.insertBefore(newtile, this.addFrameTile);
this.updateScrollerOverflows();
}
this.flagForRedraw_();
};
ns.FramesListController.prototype.updatePreviews_ = function () {
var i;
var length;
for (i = 0, length = this.tiles.length; i < length; i++) {
// Remove selected class
this.tiles[i].classList.remove('selected');
// Update tile numbers
this.tiles[i].setAttribute('data-tile-number', i);
this.tiles[i].querySelector('.tile-count').innerHTML = (i + 1);
// Check if any tile is updated
var hash = this.piskelController.getCurrentLayer().getFrameAt(i).getHash();
if (this.tiles[i].getAttribute('data-tile-hash') !== hash) {
if (this.tiles[i].querySelector('canvas')) {
this.tiles[i].querySelector('.canvas-container').replaceChild(
this.getCanvasForFrame(this.piskelController.getCurrentLayer().getFrameAt(i)),
this.tiles[i].querySelector('canvas')
);
} else {
this.tiles[i].querySelector('.canvas-container').appendChild(
this.getCanvasForFrame(this.piskelController.getCurrentLayer().getFrameAt(i))
);
}
}
}
// Hide/Show buttons if needed
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;
}
// Add selected class
this.tiles[this.piskelController.getCurrentFrameIndex()].classList.add('selected');
};
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();
var frameCount = this.piskelController.getFrameCount();
for (var i = 0 ; i < frameCount ; i++) {
this.container.append(this.createPreviewTile_(i));
var tile = this.createPreviewTile_(i);
this.previewList.appendChild(tile);
this.tiles[i] = tile;
}
// Append 'new empty frame' button
var newFrameButton = document.createElement('div');
@ -109,12 +178,9 @@
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;
var needDragndropBehavior = (frameCount > 1);
if (needDragndropBehavior) {
this.initDragndropBehavior_();
}
this.updateScrollerOverflows();
};
@ -122,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();
};
/**
@ -142,6 +208,21 @@
this.piskelController.moveFrame(originFrameId, targetInsertionId);
this.piskelController.setCurrentFrameIndex(targetInsertionId);
var tile = this.tiles.splice(originFrameId, 1)[0];
this.tiles.splice(targetInsertionId, 0, tile);
this.flagForRedraw_();
};
/**
* @private
*/
ns.FramesListController.prototype.onSortableStop_ = function (event, ui) {
this.justDropped = true;
this.resizeTimer = window.setTimeout($.proxy(function() {
this.justDropped = false;
}, this), 200);
};
/**
@ -153,6 +234,7 @@
var previewTileRoot = document.createElement('li');
previewTileRoot.setAttribute('data-tile-number', tileNumber);
previewTileRoot.setAttribute('data-tile-hash', currentFrame.getHash());
previewTileRoot.setAttribute('data-tile-action', ACTION.SELECT);
previewTileRoot.classList.add('preview-tile');
if (this.piskelController.getCurrentFrame() == currentFrame) {
@ -171,10 +253,14 @@
canvasContainer.style.marginLeft = verticalMargin + 'px';
canvasContainer.style.marginRight = verticalMargin + 'px';
// Add canvas background and canvas
var canvasBackground = document.createElement('div');
canvasBackground.className = 'canvas-background';
canvasContainer.appendChild(canvasBackground);
canvasContainer.appendChild(this.getCanvasForFrame(currentFrame));
previewTileRoot.appendChild(canvasContainer);
// Add clone button
var cloneFrameButton = document.createElement('button');
cloneFrameButton.setAttribute('rel', 'tooltip');
cloneFrameButton.setAttribute('data-placement', 'right');
@ -184,25 +270,22 @@
cloneFrameButton.className = 'tile-overlay duplicate-frame-action icon-frame-duplicate-white';
previewTileRoot.appendChild(cloneFrameButton);
canvasContainer.appendChild(this.getCanvasForFrame(currentFrame));
previewTileRoot.appendChild(canvasContainer);
// Add delete button
var deleteButton = document.createElement('button');
deleteButton.setAttribute('rel', 'tooltip');
deleteButton.setAttribute('data-placement', 'right');
deleteButton.setAttribute('title', 'Delete this frame');
deleteButton.setAttribute('data-tile-number', tileNumber);
deleteButton.setAttribute('data-tile-action', ACTION.DELETE);
deleteButton.className = 'tile-overlay delete-frame-action icon-frame-recyclebin-white';
previewTileRoot.appendChild(deleteButton);
if (tileNumber > 0 || this.piskelController.getFrameCount() > 1) {
// Add 'remove frame' button.
var deleteButton = document.createElement('button');
deleteButton.setAttribute('rel', 'tooltip');
deleteButton.setAttribute('data-placement', 'right');
deleteButton.setAttribute('title', 'Delete this frame');
deleteButton.setAttribute('data-tile-number', tileNumber);
deleteButton.setAttribute('data-tile-action', ACTION.DELETE);
deleteButton.className = 'tile-overlay delete-frame-action icon-frame-recyclebin-white';
previewTileRoot.appendChild(deleteButton);
// Add 'dragndrop handle'.
var dndHandle = document.createElement('div');
dndHandle.className = 'tile-overlay dnd-action icon-frame-dragndrop-white' ;
previewTileRoot.appendChild(dndHandle);
// Add 'dragndrop handle'.
var dndHandle = document.createElement('div');
dndHandle.className = 'tile-overlay dnd-action icon-frame-dragndrop-white' ;
previewTileRoot.appendChild(dndHandle);
}
// Add tile count
var tileCount = document.createElement('div');
tileCount.className = 'tile-overlay tile-count';
tileCount.innerHTML = tileNumber + 1;

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();
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
}
};
@ -42,6 +50,8 @@
// adding the .animated class here instead of in the markup to avoid an animation during app startup
this.dialogWrapper_.classList.add('animated');
pskl.utils.Event.addEventListener(this.dialogWrapper_, 'click', this.onWrapperClicked_, this);
};
ns.DialogsController.prototype.onCreatePaletteShortcut_ = function () {
@ -69,6 +79,12 @@
this.showDialog(args.dialogId, args.initArgs);
};
ns.DialogsController.prototype.onWrapperClicked_ = function (evt) {
if (evt.target === this.dialogWrapper_) {
this.hideDialog();
}
};
ns.DialogsController.prototype.showDialog = function (dialogId, initArgs) {
if (this.isDisplayingDialog_()) {
return;

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,16 +270,20 @@
};
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));
}
};
ns.PiskelController.prototype.serialize = function (expanded) {
return pskl.utils.Serializer.serializePiskel(this.piskel, expanded);
ns.PiskelController.prototype.serialize = function () {
return pskl.utils.serialization.Serializer.serialize(this.piskel);
};
})();

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

@ -3,9 +3,7 @@
// Preview is a square of PREVIEW_SIZE x PREVIEW_SIZE
var PREVIEW_SIZE = 200;
var ONION_SKIN_SHORTCUT = 'alt+O';
var ORIGINAL_SIZE_SHORTCUT = 'alt+1';
var RENDER_MINIMUM_DELAY = 300;
ns.PreviewController = function (piskelController, container) {
this.piskelController = piskelController;
@ -15,27 +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);
};
@ -46,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) {
@ -89,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_();
}
};
@ -99,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);
};
@ -143,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;
@ -161,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);
@ -173,6 +269,7 @@
var frame = pskl.utils.LayerUtils.mergeFrameAt(this.piskelController.getLayers(), index);
this.renderer.render(frame);
this.renderFlag = false;
this.lastRenderTime = Date.now();
this.popupPreviewController.render(frame);
}
@ -205,6 +302,7 @@
ns.PreviewController.prototype.onFrameSizeChange_ = function () {
this.updateZoom_();
this.updateContainerDimensions_();
this.updatePreviewSizeButtons_();
};
ns.PreviewController.prototype.updateContainerDimensions_ = function () {
@ -241,7 +339,8 @@
};
ns.PreviewController.prototype.shouldRender_ = function () {
return this.renderFlag || this.popupPreviewController.renderFlag;
return (this.renderFlag || this.popupPreviewController.renderFlag) &&
(Date.now() - this.lastRenderTime > RENDER_MINIMUM_DELAY);
};
ns.PreviewController.prototype.toggleOnionSkin_ = 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,11 +79,15 @@
ns.ImportController.prototype.openPiskelFile_ = function (file) {
if (this.isPiskel_(file)) {
pskl.utils.PiskelFileUtils.loadFromFile(file, function (piskel, descriptor, fps) {
piskel.setDescriptor(descriptor);
pskl.app.piskelController.setPiskel(piskel);
pskl.app.previewController.setFPS(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

@ -12,6 +12,9 @@
ns.MiscExportController.prototype.init = function () {
var cDownloadButton = document.querySelector('.c-download-button');
this.addEventListener(cDownloadButton, 'click', this.onDownloadCFileClick_);
var selectedFrameDownloadButton = document.querySelector('.selected-frame-download-button');
this.addEventListener(selectedFrameDownloadButton, 'click', this.onDownloadSelectedFrameClick_);
};
ns.MiscExportController.prototype.onDownloadCFileClick_ = function (evt) {
@ -73,4 +76,14 @@
hexStr += ('00' + r.toString(16)).substr(-2);
return hexStr;
};
ns.MiscExportController.prototype.onDownloadSelectedFrameClick_ = function (evt) {
var frameIndex = this.piskelController.getCurrentFrameIndex();
var fileName = this.getPiskelName_() + '-' + (frameIndex + 1) + '.png';
var canvas = this.piskelController.renderFrameAt(frameIndex, true);
pskl.utils.BlobUtils.canvasToBlob(canvas, function(blob) {
pskl.utils.FileUtils.downloadAsFile(blob, fileName);
});
};
})();

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

@ -8,6 +8,8 @@
this.step = step || this.initialState.step || ns.DrawingTestPlayer.DEFAULT_STEP;
this.callbacks = [];
this.shim = null;
this.performance = 0;
};
ns.DrawingTestPlayer.DEFAULT_STEP = 50;
@ -15,7 +17,16 @@
ns.DrawingTestPlayer.prototype.start = function () {
this.setupInitialState_();
this.createMouseShim_();
this.regenerateReferencePng().then(function () {
// Override the main drawing loop to record the time spent rendering.
this.loopBackup = pskl.app.drawingLoop.loop;
pskl.app.drawingLoop.loop = function () {
var before = window.performance.now();
this.loopBackup.call(pskl.app.drawingLoop);
this.performance += window.performance.now() - before;
}.bind(this);
this.regenerateReferencePng(function () {
this.playEvent_(0);
}.bind(this));
};
@ -35,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);
@ -45,21 +56,13 @@
return piskel;
};
ns.DrawingTestPlayer.prototype.regenerateReferencePng = function () {
ns.DrawingTestPlayer.prototype.regenerateReferencePng = function (callback) {
var image = new Image();
var then = function () {};
image.onload = function () {
this.referencePng = pskl.utils.CanvasUtils.createFromImage(image).toDataURL();
then();
this.referenceCanvas = pskl.utils.CanvasUtils.createFromImage(image);
callback();
}.bind(this);
image.src = this.referencePng;
return {
then : function (cb) {
then = cb;
}
};
};
/**
@ -84,11 +87,13 @@
this.timer = window.setTimeout(function () {
var recordEvent = this.events[index];
// All events have already been replayed, finish the test.
if (!recordEvent) {
this.onTestEnd_();
return;
}
var before = window.performance.now();
if (recordEvent.type === 'mouse-event') {
this.playMouseEvent_(recordEvent);
} else if (recordEvent.type === 'keyboard-event') {
@ -105,6 +110,9 @@
this.playInstrumentedEvent_(recordEvent);
}
// Record the time spent replaying the event
this.performance += window.performance.now() - before;
this.playEvent_(index + 1);
}.bind(this), this.step);
};
@ -129,8 +137,8 @@
ns.DrawingTestPlayer.prototype.playKeyboardEvent_ = function (recordEvent) {
var event = recordEvent.event;
if (pskl.utils.UserAgent.isMac && event.ctrlKey) {
event.metaKey = true;
if (pskl.utils.UserAgent.isMac) {
event.metaKey = event.ctrlKey;
}
event.preventDefault = function () {};
@ -163,16 +171,33 @@
ns.DrawingTestPlayer.prototype.onTestEnd_ = function () {
this.removeMouseShim_();
// Restore the original drawing loop.
pskl.app.drawingLoop.loop = this.loopBackup;
// Retrieve the imageData corresponding to the spritesheet created by the test.
var renderer = new pskl.rendering.PiskelRenderer(pskl.app.piskelController);
var png = renderer.renderAsCanvas().toDataURL();
var canvas = renderer.renderAsCanvas();
var testData = canvas.getContext('2d').getImageData(0, 0, canvas.width, canvas.height);
var success = png === this.referencePng;
// Retrieve the reference imageData corresponding to the reference data-url png stored for this test.
var refCanvas = this.referenceCanvas;
this.referenceData = refCanvas.getContext('2d').getImageData(0, 0, refCanvas.width, refCanvas.height);
$.publish(Events.TEST_RECORD_END, [success, png, this.referencePng]);
// Compare the two imageData arrays.
var success = true;
for (var i = 0 ; i < this.referenceData.data.length ; i++) {
if (this.referenceData.data[i] != testData.data[i]) {
success = false;
}
}
$.publish(Events.TEST_RECORD_END, [success]);
this.callbacks.forEach(function (callback) {
callback(success, png, this.referencePng);
});
callback({
success: success,
performance: this.performance
});
}.bind(this));
};
ns.DrawingTestPlayer.prototype.addEndTestCallback = function (callback) {

View File

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

View File

@ -14,11 +14,11 @@
}.bind(this));
};
ns.DrawingTestRunner.prototype.onTestRecordEnd_ = function (evt, success, png) {
ns.DrawingTestRunner.prototype.onTestRecordEnd_ = function (evt, success) {
var testResult = document.createElement('div');
testResult.id = 'drawing-test-result';
testResult.setAttribute('data-test-name', this.testName);
testResult.innerHTML = success ? 'OK' : ('KO:' + png);
testResult.innerHTML = success ? 'OK' : 'KO';
document.body.appendChild(testResult);
};
})();

View File

@ -47,25 +47,33 @@
this.testSuiteRunner.start();
};
ns.DrawingTestSuiteController.prototype.onTestCaseEnd_ = function (evt, testPath, status) {
ns.DrawingTestSuiteController.prototype.onTestCaseEnd_ = function (evt, testPath, success, performance) {
var testCaseStatus = document.createElement('li');
testCaseStatus.innerHTML = pskl.utils.Template.replace(
'[{{path}}] finished : <b style="color:{{color}}">{{status}}</b>',
{path : this.shortenPath_(testPath), status : status ? 'OK' : 'KO', color : status ? 'green' : 'red'}
'[{{path}}] finished : <b style="color:{{color}}">{{status}} ({{performance}})</b>',
{
path : this.shortenPath_(testPath),
status : success ? 'OK' : 'KO',
color : success ? 'green' : 'red',
performance: performance.toFixed(2)
}
);
this.testListElt.appendChild(testCaseStatus);
};
ns.DrawingTestSuiteController.prototype.onTestSuiteEnd_ = function (evt, status) {
console.log('on test suite end');
ns.DrawingTestSuiteController.prototype.onTestSuiteEnd_ = function (evt, status, performance) {
var elapsed = Date.now() - this.startTime_;
elapsed = (elapsed / 1000).toFixed(4);
var testSuiteStatus = document.createElement('li');
testSuiteStatus.innerHTML = pskl.utils.Template.replace(
'<b>Test finished : {{status}}</b> ({{elapsed}} seconds)',
{status : status, elapsed : elapsed}
'<b>Test finished : {{status}}</b> ({{elapsed}}s, performance: {{performance}})',
{
status : status,
elapsed : elapsed,
performance: performance.toFixed(2)
}
);
this.testListElt.appendChild(testSuiteStatus);
};

View File

@ -44,20 +44,25 @@
testPlayer.start();
};
ns.DrawingTestSuiteRunner.prototype.onTestEnd_ = function (success, png, referencePng) {
ns.DrawingTestSuiteRunner.prototype.onTestEnd_ = function (data /* {success, performance} */) {
var path = this.testPaths[this.currentIndex];
this.testStatus[path] = success;
$.publish(Events.TEST_CASE_END, [path, success]);
this.testStatus[path] = data;
$.publish(Events.TEST_CASE_END, [path, data.success, data.performance]);
this.runTest(this.currentIndex + 1);
};
ns.DrawingTestSuiteRunner.prototype.onTestSuiteEnd_ = function () {
var success = this.testPaths.every(function (path) {
return this.testStatus[path];
return this.testStatus[path].success;
}.bind(this));
var performance = this.testPaths.reduce(function (p, path) {
return this.testStatus[path].performance + p;
}.bind(this), 0);
this.status = success ? ns.DrawingTestSuiteRunner.STATUS.SUCCESS : ns.DrawingTestSuiteRunner.STATUS.ERROR;
$.publish(Events.TEST_SUITE_END, [this.status]);
$.publish(Events.TEST_SUITE_END, [this.status, performance]);
};
})();

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

@ -0,0 +1,20 @@
The MIT License (MIT)
Copyright (c) 2013 Dustan Kasten
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View File

@ -0,0 +1,277 @@
/*
* smoothscroll polyfill - v0.3.3
* https://iamdustan.github.io/smoothscroll
* 2016 (c) Dustan Kasten, Jeremias Menichelli - MIT License
*/
(function(w, d, undefined) {
'use strict';
/*
* aliases
* w: window global object
* d: document
* undefined: undefined
*/
// polyfill
function polyfill() {
// return when scrollBehavior interface is supported
if ('scrollBehavior' in d.documentElement.style) {
return;
}
/*
* globals
*/
var Element = w.HTMLElement || w.Element;
var SCROLL_TIME = 468;
/*
* object gathering original scroll methods
*/
var original = {
scroll: w.scroll || w.scrollTo,
scrollBy: w.scrollBy,
scrollIntoView: Element.prototype.scrollIntoView
};
/*
* define timing method
*/
var now = w.performance && w.performance.now
? w.performance.now.bind(w.performance) : Date.now;
/**
* changes scroll position inside an element
* @method scrollElement
* @param {Number} x
* @param {Number} y
*/
function scrollElement(x, y) {
this.scrollLeft = x;
this.scrollTop = y;
}
/**
* returns result of applying ease math function to a number
* @method ease
* @param {Number} k
* @returns {Number}
*/
function ease(k) {
return 0.5 * (1 - Math.cos(Math.PI * k));
}
/**
* indicates if a smooth behavior should be applied
* @method shouldBailOut
* @param {Number|Object} x
* @returns {Boolean}
*/
function shouldBailOut(x) {
if (typeof x !== 'object'
|| x.behavior === undefined
|| x.behavior === 'auto'
|| x.behavior === 'instant') {
// first arg not an object, or behavior is auto, instant or undefined
return true;
}
if (typeof x === 'object'
&& x.behavior === 'smooth') {
// first argument is an object and behavior is smooth
return false;
}
// throw error when behavior is not supported
throw new TypeError('behavior not valid');
}
/**
* finds scrollable parent of an element
* @method findScrollableParent
* @param {Node} el
* @returns {Node} el
*/
function findScrollableParent(el) {
do {
el = el.parentNode;
} while (el !== d.body
&& !(el.clientHeight < el.scrollHeight
|| el.clientWidth < el.scrollWidth));
return el;
}
/**
* self invoked function that, given a context, steps through scrolling
* @method step
* @param {Object} context
*/
function step(context) {
// call method again on next available frame
context.frame = w.requestAnimationFrame(step.bind(w, context));
var time = now();
var value;
var currentX;
var currentY;
var elapsed = (time - context.startTime) / SCROLL_TIME;
// avoid elapsed times higher than one
elapsed = elapsed > 1 ? 1 : elapsed;
// apply easing to elapsed time
value = ease(elapsed);
currentX = context.startX + (context.x - context.startX) * value;
currentY = context.startY + (context.y - context.startY) * value;
context.method.call(context.scrollable, currentX, currentY);
// return when end points have been reached
if (currentX === context.x && currentY === context.y) {
w.cancelAnimationFrame(context.frame);
return;
}
}
/**
* scrolls window with a smooth behavior
* @method smoothScroll
* @param {Object|Node} el
* @param {Number} x
* @param {Number} y
*/
function smoothScroll(el, x, y) {
var scrollable;
var startX;
var startY;
var method;
var startTime = now();
var frame;
// define scroll context
if (el === d.body) {
scrollable = w;
startX = w.scrollX || w.pageXOffset;
startY = w.scrollY || w.pageYOffset;
method = original.scroll;
} else {
scrollable = el;
startX = el.scrollLeft;
startY = el.scrollTop;
method = scrollElement;
}
// cancel frame when a scroll event's happening
if (frame) {
w.cancelAnimationFrame(frame);
}
// scroll looping over a frame
step({
scrollable: scrollable,
method: method,
startTime: startTime,
startX: startX,
startY: startY,
x: x,
y: y,
frame: frame
});
}
/*
* ORIGINAL METHODS OVERRIDES
*/
// w.scroll and w.scrollTo
w.scroll = w.scrollTo = function() {
// avoid smooth behavior if not required
if (shouldBailOut(arguments[0])) {
original.scroll.call(
w,
arguments[0].left || arguments[0],
arguments[0].top || arguments[1]
);
return;
}
// LET THE SMOOTHNESS BEGIN!
smoothScroll.call(
w,
d.body,
~~arguments[0].left,
~~arguments[0].top
);
};
// w.scrollBy
w.scrollBy = function() {
// avoid smooth behavior if not required
if (shouldBailOut(arguments[0])) {
original.scrollBy.call(
w,
arguments[0].left || arguments[0],
arguments[0].top || arguments[1]
);
return;
}
// LET THE SMOOTHNESS BEGIN!
smoothScroll.call(
w,
d.body,
~~arguments[0].left + (w.scrollX || w.pageXOffset),
~~arguments[0].top + (w.scrollY || w.pageYOffset)
);
};
// Element.prototype.scrollIntoView
Element.prototype.scrollIntoView = function() {
// avoid smooth behavior if not required
if (shouldBailOut(arguments[0])) {
original.scrollIntoView.call(this, arguments[0] || true);
return;
}
// LET THE SMOOTHNESS BEGIN!
var scrollableParent = findScrollableParent(this);
var parentRects = scrollableParent.getBoundingClientRect();
var clientRects = this.getBoundingClientRect();
if (scrollableParent !== d.body) {
// reveal element inside parent
smoothScroll.call(
this,
scrollableParent,
scrollableParent.scrollLeft + clientRects.left - parentRects.left,
scrollableParent.scrollTop + clientRects.top - parentRects.top
);
// reveal parent in viewport
w.scrollBy({
left: parentRects.left,
top: parentRects.top,
behavior: 'smooth'
});
} else {
// reveal element in viewport
w.scrollBy({
left: clientRects.left,
top: clientRects.top,
behavior: 'smooth'
});
}
};
}
if (typeof exports === 'object') {
// commonjs
module.exports = { polyfill: polyfill };
} else {
// global
polyfill();
}
})(window, document);

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

@ -14,28 +14,54 @@
}
};
ns.Frame.fromPixelGrid = function (pixels) {
if (pixels.length && pixels[0].length) {
var w = pixels.length;
var h = pixels[0].length;
ns.Frame.fromPixelGrid = function (pixels, width, height) {
if (pixels.length) {
var w;
var h;
var buffer;
if (pixels[0].length) {
w = pixels.length;
h = pixels[0].length;
buffer = [];
for (var y = 0; y < h; y++) {
for (var x = 0; x < w; x++) {
if (typeof pixels[x][y] == 'string') {
buffer[y * w + x] = pskl.utils.colorToInt(pixels[x][y]);
} else {
buffer[y * w + x] = pixels[x][y];
}
}
}
} else if (width && height) {
w = width;
h = height;
buffer = pixels;
} else {
throw 'Bad arguments in pskl.model.frame.fromPixelGrid, missing width and height';
}
var frame = new pskl.model.Frame(w, h);
frame.setPixels(pixels);
frame.setPixels(buffer);
return frame;
} else {
throw 'Bad arguments in pskl.model.Frame.fromPixelGrid : ' + pixels;
throw 'Bad arguments in pskl.model.Frame.fromPixelGrid';
}
};
var _emptyPixelGridCache = {};
ns.Frame.createEmptyPixelGrid_ = function (width, height) {
var pixels = [];
for (var columnIndex = 0 ; columnIndex < width ; columnIndex++) {
var columnArray = [];
for (var heightIndex = 0 ; heightIndex < height ; heightIndex++) {
columnArray.push(Constants.TRANSPARENT_COLOR);
}
pixels[columnIndex] = columnArray;
var pixels;
var key = width + '-' + height;
if (_emptyPixelGridCache[key]) {
pixels = _emptyPixelGridCache[key];
} else {
pixels = _emptyPixelGridCache[key] = new Uint32Array(width * height);
var transparentColorInt = pskl.utils.colorToInt(Constants.TRANSPARENT_COLOR);
pixels.fill(transparentColorInt);
}
return pixels;
return new Uint32Array(pixels);
};
ns.Frame.createEmptyFromFrame = function (frame) {
@ -44,7 +70,7 @@
ns.Frame.prototype.clone = function () {
var clone = new ns.Frame(this.width, this.height);
clone.setPixels(this.getPixels());
clone.setPixels(this.pixels);
return clone;
};
@ -64,8 +90,8 @@
};
ns.Frame.prototype.clear = function () {
var pixels = ns.Frame.createEmptyPixelGrid_(this.getWidth(), this.getHeight());
this.setPixels(pixels);
this.pixels = ns.Frame.createEmptyPixelGrid_(this.getWidth(), this.getHeight());
this.version++;
};
/**
@ -73,11 +99,7 @@
* @private
*/
ns.Frame.prototype.clonePixels_ = function (pixels) {
var clonedPixels = [];
for (var col = 0 ; col < pixels.length ; col++) {
clonedPixels[col] = pixels[col].slice(0 , pixels[col].length);
}
return clonedPixels;
return new Uint32Array(pixels);
};
ns.Frame.prototype.getHash = function () {
@ -86,9 +108,12 @@
ns.Frame.prototype.setPixel = function (x, y, color) {
if (this.containsPixel(x, y)) {
var p = this.pixels[x][y];
var index = y * this.width + x;
var p = this.pixels[index];
color = pskl.utils.colorToInt(color);
if (p !== color) {
this.pixels[x][y] = color || Constants.TRANSPARENT_COLOR;
this.pixels[index] = color || pskl.utils.colorToInt(Constants.TRANSPARENT_COLOR);
this.version++;
}
}
@ -96,7 +121,7 @@
ns.Frame.prototype.getPixel = function (x, y) {
if (this.containsPixel(x, y)) {
return this.pixels[x][y];
return this.pixels[y * this.width + x];
} else {
return null;
}
@ -105,10 +130,9 @@
ns.Frame.prototype.forEachPixel = function (callback) {
var width = this.getWidth();
var height = this.getHeight();
for (var x = 0 ; x < width ; x++) {
for (var y = 0 ; y < height ; y++) {
callback(this.pixels[x][y], x, y, this);
}
var length = width * height;
for (var i = 0; i < length ; i++) {
callback(this.pixels[i], i % width, Math.floor(i / width), this);
}
};

View File

@ -37,10 +37,13 @@
};
ns.Layer.prototype.setOpacity = function (opacity) {
if (typeof opacity == 'string') {
opacity = parseFloat(opacity);
}
if (opacity === null || isNaN(opacity) || opacity < 0 || opacity > 1) {
return;
}
this.opacity = opacity;
this.opacity = +opacity.toFixed(3);
};
ns.Layer.prototype.isTransparent = function () {

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

@ -30,20 +30,9 @@
var key1 = frame.getHash();
if (cache[key1]) {
processedFrame = cache[key1];
} else if (frame instanceof pskl.model.frame.RenderedFrame) {
// Cannot use 2nd level cache with rendered frames
var callbackFirstLvlCacheOnly = this.onProcessorComplete_.bind(this, deferred, cache, key1, key1);
this.frameProcessor(frame, callbackFirstLvlCacheOnly);
} else {
var framePixels = JSON.stringify(frame.getPixels());
var key2 = pskl.utils.hashCode(framePixels);
if (cache[key2]) {
processedFrame = this.outputCloner(cache[key2], frame);
cache[key1] = processedFrame;
} else {
var callback = this.onProcessorComplete_.bind(this, deferred, cache, key1, key2);
this.frameProcessor(frame, callback);
}
var callback = this.onProcessorComplete_.bind(this, deferred, cache, key1);
this.frameProcessor(frame, callback);
}
if (processedFrame) {
@ -53,9 +42,8 @@
return deferred.promise;
};
ns.AsyncCachedFrameProcessor.prototype.onProcessorComplete_ = function (deferred, cache, key1, key2, result) {
ns.AsyncCachedFrameProcessor.prototype.onProcessorComplete_ = function (deferred, cache, key1, result) {
cache[key1] = result;
cache[key2] = result;
deferred.resolve(result);
};
})();

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 () {
@ -66,21 +68,16 @@
var cacheKey = frame.getHash();
if (cache[cacheKey]) {
processedFrame = cache[cacheKey];
} else if (frame instanceof pskl.model.frame.RenderedFrame) {
// Cannot use 2nd level cache with rendered frames
} else {
processedFrame = this.frameProcessor(frame);
cache[cacheKey] = processedFrame;
} else {
var framePixels = JSON.stringify(frame.getPixels());
var frameAsString = pskl.utils.hashCode(framePixels);
if (cache[frameAsString]) {
processedFrame = this.outputCloner(cache[frameAsString], frame);
} else {
processedFrame = this.frameProcessor(frame);
cache[frameAsString] = 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;
}
cache[cacheKey] = processedFrame;
}
return processedFrame;
};
})();

View File

@ -15,6 +15,7 @@
scope : scope,
args : args
};
this.callbacks.push(callbackObj);
return callbackObj;
};

View File

@ -30,12 +30,11 @@
ns.FramesheetRenderer.prototype.drawFrameInCanvas_ = function (frame, canvas, offsetWidth, offsetHeight) {
var context = canvas.getContext('2d');
frame.forEachPixel(function (color, x, y) {
if (color != Constants.TRANSPARENT_COLOR) {
context.fillStyle = color;
context.fillRect(x + offsetWidth, y + offsetHeight, 1, 1);
}
});
var imageData = context.createImageData(frame.getWidth(), frame.getHeight());
var pixels = frame.getPixels();
var data = new Uint8ClampedArray(pixels.buffer);
imageData.data.set(data);
context.putImageData(imageData, offsetWidth, offsetHeight);
};
ns.FramesheetRenderer.prototype.createCanvas_ = function (columns, rows) {

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
};
@ -34,7 +33,8 @@
// Do not save an unchanged piskel
if (hash !== this.lastHash) {
this.lastHash = hash;
this.savePiskel_('next', this.piskelController.serialize(), JSON.stringify(info));
var serializedPiskel = pskl.utils.serialization.Serializer.serialize(piskel);
this.savePiskel_('next', serializedPiskel, JSON.stringify(info));
}
};
@ -46,17 +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) {
piskel.setDescriptor(new pskl.model.piskel.Descriptor(previousInfo.name, previousInfo.description, true));
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));
};
@ -67,13 +72,18 @@
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);
batchAll(frames, job).then(function (results) {
}.bind(this))
.then(function (results) {
var colors = {};
results.forEach(function (result) {
Object.keys(result).forEach(function (color) {
@ -81,8 +91,12 @@
});
});
// Remove transparent color from used colors
delete colors[Constants.TRANSPARENT_COLOR];
this.setCurrentColors(Object.keys(colors));
delete colors[pskl.utils.colorToInt(Constants.TRANSPARENT_COLOR)];
var hexColors = Object.keys(colors).map(function (color) {
return pskl.utils.intToHex(color);
});
this.setCurrentColors(hexColors);
}.bind(this));
};
@ -103,7 +117,9 @@
ns.CurrentColorsService.prototype.getFrameColors_ = function (frame, processorCallback) {
var frameColorsWorker = new pskl.worker.framecolors.FrameColors(frame,
function (event) {processorCallback(event.data.colors);},
function (event) {
processorCallback(event.data.colors);
},
function () {},
function (event) {processorCallback({});}
);

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,32 +53,39 @@
pskl.UserSettings.set(pskl.UserSettings.SELECTED_PALETTE, palette.id);
};
ns.FileDropperService.prototype.onPiskelFileLoaded_ = function (piskel, descriptor, fps) {
ns.FileDropperService.prototype.onPiskelFileLoaded_ = function (piskel) {
if (window.confirm('This will replace your current animation')) {
piskel.setDescriptor(descriptor);
pskl.app.piskelController.setPiskel(piskel);
pskl.app.previewController.setFPS(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, {
@ -85,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

@ -1,10 +1,13 @@
(function () {
var ns = $.namespace('pskl.service');
ns.HistoryService = function (piskelController, shortcutService, deserializer) {
this.piskelController = piskelController || pskl.app.piskelController;
ns.HistoryService = function (piskelController, shortcutService, deserializer, serializer) {
// 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.Deserializer;
this.deserializer = deserializer || pskl.utils.serialization.arraybuffer.ArrayBufferDeserializer;
this.serializer = serializer || pskl.utils.serialization.arraybuffer.ArrayBufferSerializer;
this.stateQueue = [];
this.currentIndex = -1;
@ -23,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));
@ -47,15 +53,24 @@
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()
};
var isSnapshot = action.type === ns.HistoryService.SNAPSHOT;
var isAtAutoSnapshotInterval = this.currentIndex % ns.HistoryService.SNAPSHOT_PERIOD === 0;
if (isSnapshot || isAtAutoSnapshotInterval) {
state.piskel = this.piskelController.serialize(true);
var piskel = this.piskelController.getPiskel();
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);
};
@ -90,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)) {
@ -124,13 +146,7 @@
var state = this.stateQueue[stateIndex];
var piskelSnapshot = state.piskel;
// If the snapshot is stringified, parse it and backup the result for faster access next time
// FIXME : Memory consumption might go crazy if we keep unpacking big piskels indefinitely
// ==> should ensure I remove some of them :)
if (typeof piskelSnapshot === 'string') {
piskelSnapshot = JSON.parse(piskelSnapshot);
state.piskel = piskelSnapshot;
}
state.piskel = piskelSnapshot;
return piskelSnapshot;
};
@ -169,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;
};
})();

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