Merge branch 'master' into 362-MultiSizePreview

This commit is contained in:
Guillaume Martigny 2017-01-11 12:28:20 +01:00 committed by GitHub
commit 9cd3fa03a0
119 changed files with 1950 additions and 516 deletions

19
.github/ISSUE_TEMPLATE vendored Normal file
View File

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

View File

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

View File

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

View File

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

After

Width:  |  Height:  |  Size: 4.1 KiB

View File

@ -27,32 +27,33 @@
"postversion": "git push && git push --tags && npm publish"
},
"devDependencies": {
"dateformat": "1.0.11",
"dateformat": "2.0.0",
"grunt": "^0.4.5",
"grunt-casperjs": "^2.2.1",
"grunt-contrib-clean": "1.0.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-jshint": "1.1.0",
"grunt-contrib-uglify": "1.0.1",
"grunt-contrib-watch": "1.0.0",
"grunt-include-replace": "4.0.1",
"grunt-jscs": "2.8.0",
"grunt-karma": "1.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": "1.3.0",
"karma-chrome-launcher": "1.0.1",
"karma-jasmine": "1.0.2",
"karma-phantomjs-launcher": "0.2.3",
"load-grunt-tasks": "3.5.0",
"phantomjs": "2.1.7",
"phantomjs-polyfill-object-assign": "0.0.2"
"phantomjs-polyfill-object-assign": "0.0.2",
"promise-polyfill": "6.0.2"
},
"window": {
"title": "Piskel",

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -16,17 +16,17 @@
}
.pen-size-option[data-size='1'] {
padding: 6px;
}
.pen-size-option[data-size='2'] {
padding: 5px;
}
.pen-size-option[data-size='3'] {
.pen-size-option[data-size='2'] {
padding: 4px;
}
.pen-size-option[data-size='4'] {
.pen-size-option[data-size='3'] {
padding: 3px;
}
.pen-size-option[data-size='4'] {
padding: 2px;
}
.pen-size-option:before {
content: '';
@ -34,6 +34,9 @@
height: 100%;
background-color: white;
display: block;
text-align: center;
line-height: 12px;
font-size: 90%;
}
.pen-size-option:hover {
@ -41,9 +44,13 @@
}
.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);
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -79,7 +79,7 @@
.preview-toggle-onion-skin-enabled,
.preview-toggle-onion-skin-enabled:hover {
color : gold;
color : var(--highlight-color);
}
.preview-contextual-actions {
@ -120,8 +120,8 @@
}
.preview-contextual-action:hover {
color: gold;
border-color: gold;
color: var(--highlight-color);
border-color: var(--highlight-color);
}
/**
@ -181,13 +181,20 @@
}
.open-popup-preview-button:hover {
border-color: gold;
border-color: var(--highlight-color);
}
/**
* The regular image is provided bby the sprite icons.png+icons.css
* The regular image is provided by the sprite icons.png+icons.css
*/
.icon-minimap-popup-preview-arrow-white:hover {
background-image: url(../img/icons/minimap/minimap-popup-preview-arrow-gold.png);
background-position: 0 0;
background-size: 18px 18px;
}
@media (-webkit-min-device-pixel-ratio: 2),
(min-resolution: 192dpi) {
background-image: url(../img/icons/minimap/minimap-popup-preview-arrow-gold@2x.png);
}

View File

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

View File

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

View File

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

View File

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

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

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 446 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 851 B

View File

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

View File

@ -19,7 +19,7 @@ var Constants = {
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 +49,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 * 1000 * 1000,
// 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,8 @@ var Events = {
CURRENT_COLORS_UPDATED : 'CURRENT_COLORS_UPDATED',
PERFORMANCE_REPORT_CHANGED : 'PERFORMANCE_REPORT_CHANGED',
// 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);
@ -124,7 +125,7 @@
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.imageUploadService = new pskl.service.ImageUploadService();
this.imageUploadService.init();
@ -147,11 +148,16 @@
this.penSizeController = new pskl.controller.PenSizeController();
this.penSizeController.init();
this.fileDropperService = new pskl.service.FileDropperService(
this.piskelController,
document.querySelector('#drawing-canvas-container'));
this.fileDropperService = new pskl.service.FileDropperService(this.piskelController);
this.fileDropperService.init();
this.userWarningController = new pskl.controller.UserWarningController(this.piskelController);
this.userWarningController.init();
this.performanceReportService = new pskl.service.performance.PerformanceReportService(
this.piskelController, this.currentColorsService);
this.performanceReportService.init();
this.drawingLoop = new pskl.rendering.DrawingLoop();
this.drawingLoop.addCallback(this.render, this);
this.drawingLoop.start();
@ -173,20 +179,23 @@
mb.createMacBuiltin('Piskel');
gui.Window.get().menu = mb;
}
if (pskl.utils.UserAgent.isUnsupported()) {
$.publish(Events.DIALOG_DISPLAY, {
dialogId : 'unsupported-browser'
});
}
},
loadPiskel_ : function (piskelData) {
var serializedPiskel = piskelData.piskel;
pskl.utils.serialization.Deserializer.deserialize(serializedPiskel, function (piskel, extra) {
pskl.utils.serialization.Deserializer.deserialize(serializedPiskel, function (piskel) {
pskl.app.piskelController.setPiskel(piskel);
$.publish(Events.PISKEL_SAVED);
var fps = extra.fps;
if (piskelData.descriptor) {
// Backward compatibility for v2 or older
piskel.setDescriptor(piskelData.descriptor);
fps = piskelData.fps;
}
pskl.app.previewController.setFPS(fps);
});
},

View File

@ -15,6 +15,7 @@
this.redrawFlag = true;
this.regenerateDomFlag = true;
this.justDropped = false;
this.cachedFrameProcessor = new pskl.model.frame.CachedFrameProcessor();
this.cachedFrameProcessor.setFrameProcessor(this.frameToPreviewCanvas_.bind(this));
@ -104,7 +105,7 @@
this.container.get(0).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();
@ -189,6 +190,7 @@
$('#preview-list').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'
@ -211,6 +213,17 @@
this.flagForRedraw_();
};
/**
* @private
*/
ns.FramesListController.prototype.onSortableStop_ = function (event, ui) {
this.justDropped = true;
this.resizeTimer = window.setTimeout($.proxy(function() {
this.justDropped = false;
}, this), 200);
};
/**
* @private
* TODO(vincz): clean this giant rendering function & remove listeners.

View File

@ -99,6 +99,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 +112,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

@ -92,7 +92,8 @@
};
ns.PaletteController.prototype.resetColors = function () {
pskl.app.selectedColorsService.reset();
this.setPrimaryColor_(Constants.DEFAULT_PEN_COLOR);
this.setSecondaryColor_(Constants.TRANSPARENT_COLOR);
};
/**

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,55 @@
(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.
this.performanceLinkEl.classList.toggle('visible', shouldDisplayWarning);
// 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

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

View File

@ -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 () {

View File

@ -17,10 +17,6 @@
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');
@ -54,13 +50,6 @@
this.selectedPreviewSize = pskl.UserSettings.get(pskl.UserSettings.PREVIEW_SIZE);
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);
};
@ -97,7 +86,10 @@
$.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.updatePreviewSizeButtons_();
this.popupPreviewController.init();
@ -105,6 +97,7 @@
this.updateZoom_();
this.updateOnionSkinPreview_();
this.selectPreviewSizeButton_();
this.updateFPS_();
this.updateMaxFPS_();
this.updateContainerDimensions_();
};
@ -200,7 +193,7 @@
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 () {
@ -231,15 +224,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;
@ -249,10 +244,6 @@
}
};
ns.PreviewController.prototype.getFPS = function () {
return this.fps;
};
ns.PreviewController.prototype.render = function (delta) {
this.elapsedTime += delta;
var index = this.getNextIndex_(delta);

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);
};
@ -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,9 +79,8 @@
ns.ImportController.prototype.openPiskelFile_ = function (file) {
if (this.isPiskel_(file)) {
pskl.utils.PiskelFileUtils.loadFromFile(file, function (piskel, extra) {
pskl.utils.PiskelFileUtils.loadFromFile(file, function (piskel) {
pskl.app.piskelController.setPiskel(piskel);
pskl.app.previewController.setFPS(extra.fps);
});
this.closeDrawer_();
}

View File

@ -46,10 +46,20 @@
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);

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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));
};

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

View File

@ -24,6 +24,9 @@
// Interval/buffer (in milliseconds) between two state load (ctrl+z/y spamming)
ns.HistoryService.LOAD_STATE_INTERVAL = 50;
// Maximum number of states that can be recorded.
ns.HistoryService.MAX_SAVED_STATES = 500;
ns.HistoryService.prototype.init = function () {
$.subscribe(Events.PISKEL_SAVE_STATE, this.onSaveStateEvent.bind(this));
@ -48,6 +51,7 @@
action : action,
frameIndex : action.state ? action.state.frameIndex : this.piskelController.currentFrameIndex,
layerIndex : action.state ? action.state.layerIndex : this.piskelController.currentLayerIndex,
fps : this.piskelController.getFPS(),
uuid: pskl.utils.Uuid.generate()
};
@ -58,6 +62,13 @@
state.piskel = this.serializer.serialize(piskel);
}
// If the new state pushes over MAX_SAVED_STATES, erase all states between the first and
// second snapshot states.
if (this.stateQueue.length > ns.HistoryService.MAX_SAVED_STATES) {
var firstSnapshotIndex = this.getNextSnapshotIndex_(1);
this.stateQueue.splice(0, firstSnapshotIndex);
this.currentIndex = this.currentIndex - firstSnapshotIndex;
}
this.stateQueue.push(state);
$.publish(Events.HISTORY_STATE_SAVED);
};
@ -92,6 +103,13 @@
return index;
};
ns.HistoryService.prototype.getNextSnapshotIndex_ = function (index) {
while (this.stateQueue[index] && !this.stateQueue[index].piskel) {
index = index + 1;
}
return index;
};
ns.HistoryService.prototype.loadState = function (index) {
try {
if (this.isLoadStateAllowed_(index)) {
@ -165,6 +183,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,11 @@
/**
* Image an animation import service supporting the import dialog.
* @param {!PiskelController} piskelController
* @param {!PreviewController} previewController
* @constructor
*/
ns.ImportService =
function (piskelController, previewController) {
function (piskelController) {
this.piskelController_ = piskelController;
this.previewController_ = previewController;
};
/**
@ -101,10 +99,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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -177,74 +177,61 @@
},
/**
* Alpha compositing using porter duff algorithm :
* http://en.wikipedia.org/wiki/Alpha_compositing
* http://keithp.com/~keithp/porterduff/p253-porter.pdf
* @param {String} strColor1 color over
* @param {String} strColor2 color under
* @return {String} the composite color
* Create a Frame array from an Image object.
* Transparent pixels will either be converted to completely opaque or completely transparent pixels.
*
* @param {Image} image source image
* @param {Number} frameCount number of frames in the spritesheet
* @return {Array<Frame>}
*/
mergePixels__ : function (strColor1, strColor2, globalOpacity1) {
var col1 = pskl.utils.FrameUtils.toRgba__(strColor1);
var col2 = pskl.utils.FrameUtils.toRgba__(strColor2);
if (typeof globalOpacity1 == 'number') {
col1 = JSON.parse(JSON.stringify(col1));
col1.a = globalOpacity1 * col1.a;
createFramesFromSpritesheet : function (image, frameCount) {
var layout = [];
for (var i = 0 ; i < frameCount ; i++) {
layout.push([i]);
}
var a = col1.a + col2.a * (1 - col1.a);
var r = ((col1.r * col1.a + col2.r * col2.a * (1 - col1.a)) / a) | 0;
var g = ((col1.g * col1.a + col2.g * col2.a * (1 - col1.a)) / a) | 0;
var b = ((col1.b * col1.a + col2.b * col2.a * (1 - col1.a)) / a) | 0;
return 'rgba(' + r + ',' + g + ',' + b + ',' + a + ')';
var chunkFrames = pskl.utils.FrameUtils.createFramesFromChunk(image, layout);
return chunkFrames.map(function (chunkFrame) {
return chunkFrame.frame;
});
},
/**
* Convert a color defined as a string (hex, rgba, rgb, 'TRANSPARENT') to an Object with r,g,b,a properties.
* r, g and b are integers between 0 and 255, a is a float between 0 and 1
* @param {String} c color as a string
* @return {Object} {r:Number,g:Number,b:Number,a:Number}
* Create a Frame array from an Image object.
* Transparent pixels will either be converted to completely opaque or completely transparent pixels.
*
* @param {Image} image source image
* @param {Array <Array>} layout description of the frame indexes expected to be found in the chunk
* @return {Array<Object>} array of objects containing: {index: frame index, frame: frame instance}
*/
toRgba__ : function (c) {
if (colorCache[c]) {
return colorCache[c];
createFramesFromChunk : function (image, layout) {
var width = image.width;
var height = image.height;
// Recalculate the expected frame dimensions from the layout information
var frameWidth = width / layout.length;
var frameHeight = height / layout[0].length;
// Create a canvas adapted to the image size
var canvas = pskl.utils.CanvasUtils.createCanvas(frameWidth, frameHeight);
var context = canvas.getContext('2d');
// Draw the zoomed-up pixels to a different canvas context
var chunkFrames = [];
for (var i = 0 ; i < layout.length ; i++) {
var row = layout[i];
for (var j = 0 ; j < row.length ; j++) {
context.clearRect(0, 0 , frameWidth, frameHeight);
context.drawImage(image, frameWidth * i, frameHeight * j,
frameWidth, frameHeight, 0, 0, frameWidth, frameHeight);
var frame = pskl.utils.FrameUtils.createFromCanvas(canvas, 0, 0, frameWidth, frameHeight);
chunkFrames.push({
index : layout[i][j],
frame : frame
});
}
}
var color, matches;
if (c === 'TRANSPARENT') {
color = {
r : 0,
g : 0,
b : 0,
a : 0
};
} else if (c.indexOf('rgba(') != -1) {
matches = /rgba\((\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*,\s*(1|0\.\d+)\s*\)/.exec(c);
color = {
r : parseInt(matches[1], 10),
g : parseInt(matches[2], 10),
b : parseInt(matches[3], 10),
a : parseFloat(matches[4])
};
} else if (c.indexOf('rgb(') != -1) {
matches = /rgb\((\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\)/.exec(c);
color = {
r : parseInt(matches[1], 10),
g : parseInt(matches[2], 10),
b : parseInt(matches[3], 10),
a : 1
};
} else {
matches = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(c);
color = {
r : parseInt(matches[1], 16),
g : parseInt(matches[2], 16),
b : parseInt(matches[3], 16),
a : 1
};
}
colorCache[c] = color;
return color;
return chunkFrames;
}
};
})();

View File

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

View File

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

View File

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

View File

@ -5,7 +5,7 @@
/**
* Load a piskel from a piskel file.
* After deserialization is successful, the provided success callback will be called.
* Success callback is expected to handle 3 arguments : (piskel:Piskel, descriptor:PiskelDescriptor, fps:Number)
* Success callback is expected to receive a single Piskel object argument
* @param {File} file the .piskel file to load
* @param {Function} onSuccess Called if the deserialization of the piskel is successful
* @param {Function} onError NOT USED YET
@ -15,13 +15,13 @@
var rawPiskel = pskl.utils.Base64.toText(content);
ns.PiskelFileUtils.decodePiskelFile(
rawPiskel,
function (piskel, descriptor, fps) {
function (piskel) {
// if using Node-Webkit, store the savePath on load
// Note: the 'path' property is unique to Node-Webkit, and holds the full path
if (pskl.utils.Environment.detectNodeWebkit()) {
piskel.savePath = file.path;
}
onSuccess(piskel, descriptor, fps);
onSuccess(piskel);
},
onError
);
@ -30,11 +30,9 @@
decodePiskelFile : function (rawPiskel, onSuccess, onError) {
var serializedPiskel = JSON.parse(rawPiskel);
var fps = serializedPiskel.piskel.fps;
var piskel = serializedPiskel.piskel;
var descriptor = new pskl.model.piskel.Descriptor(piskel.name, piskel.description, true);
pskl.utils.serialization.Deserializer.deserialize(serializedPiskel, function (piskel) {
onSuccess(piskel, descriptor, fps);
onSuccess(piskel);
});
}
};

View File

@ -76,33 +76,19 @@
*
* @param {Number} row x-coordinate of the original pixel
* @param {Number} col y-coordinate of the original pixel
* @param {Number} size >= 1 && <= 4
* @param {Number} size >= 1 && <= 32
* @return {Array} array of arrays of 2 Numbers (eg. [[0,0], [0,1], [1,0], [1,1]])
*/
resizePixel : function (col, row, size) {
if (size == 1) {
return [[col, row]];
} else if (size == 2) {
return [
[col, row], [col + 1, row],
[col, row + 1], [col + 1, row + 1]
];
} else if (size == 3) {
return [
[col - 1, row - 1], [col, row - 1], [col + 1, row - 1],
[col - 1, row + 0], [col, row + 0], [col + 1, row + 0],
[col - 1, row + 1], [col, row + 1], [col + 1, row + 1],
];
} else if (size == 4) {
return [
[col - 1, row - 1], [col, row - 1], [col + 1, row - 1], [col + 2, row - 1],
[col - 1, row + 0], [col, row + 0], [col + 1, row + 0], [col + 2, row + 0],
[col - 1, row + 1], [col, row + 1], [col + 1, row + 1], [col + 2, row + 1],
[col - 1, row + 2], [col, row + 2], [col + 1, row + 2], [col + 2, row + 2],
];
} else {
console.error('Unsupported size : ' + size);
var pixels = [];
for (var j = 0; j < size; j++) {
for (var i = 0; i < size; i++) {
pixels.push([col - Math.floor(size / 2) + i, row - Math.floor(size / 2) + j]);
}
}
return pixels;
},
/**

View File

@ -2,14 +2,30 @@
var ns = $.namespace('pskl.utils');
var ua = navigator.userAgent;
var hasChrome =
ns.UserAgent = {
isIE : /MSIE/i.test(ua),
isIE11 : /trident/i.test(ua),
isChrome : /Chrome/i.test(ua),
isEdge : /edge\//i.test(ua),
isFirefox : /Firefox/i.test(ua),
isMac : /Mac/.test(ua)
isMac : /Mac/.test(ua),
isOpera : /OPR\//.test(ua),
// Shared user agent strings, sadly found in many useragent strings
hasChrome : /Chrome/i.test(ua),
hasSafari : /Safari\//.test(ua),
};
ns.UserAgent.isChrome = ns.UserAgent.hasChrome && !ns.UserAgent.isOpera && !ns.UserAgent.isEdge;
ns.UserAgent.isSafari = ns.UserAgent.hasSafari && !ns.UserAgent.isOpera && !ns.UserAgent.isEdge;
ns.UserAgent.supportedUserAgents = [
'isIE11',
'isEdge',
'isChrome',
'isFirefox'
];
ns.UserAgent.version = (function () {
if (pskl.utils.UserAgent.isIE) {
return parseInt(/MSIE\s?(\d+)/i.exec(ua)[1], 10);
@ -19,4 +35,27 @@
return parseInt(/Firefox\/(\d+)/i.exec(ua)[1], 10);
}
})();
ns.UserAgent.isUnsupported = function () {
// Check that none of the supported UAs are set to true.
return ns.UserAgent.supportedUserAgents.every(function (uaTest) {
return !ns.UserAgent[uaTest];
});
};
ns.UserAgent.getDisplayName = function () {
if (ns.UserAgent.isIE) {
return 'Internet Explorer';
} else if (ns.UserAgent.isChrome) {
return 'Chrome';
} else if (ns.UserAgent.isFirefox) {
return 'Firefox';
} else if (ns.UserAgent.isSafari) {
return 'Safari';
} else if (ns.UserAgent.isOpera) {
return 'Opera';
} else {
return ua;
}
};
})();

View File

@ -7,6 +7,7 @@
DEFAULT_SIZE : 'DEFAULT_SIZE',
CANVAS_BACKGROUND : 'CANVAS_BACKGROUND',
SELECTED_PALETTE : 'SELECTED_PALETTE',
SEAMLESS_OPACITY : 'SEAMLESS_OPACITY',
SEAMLESS_MODE : 'SEAMLESS_MODE',
PREVIEW_SIZE : 'PREVIEW_SIZE',
ONION_SKIN : 'ONION_SKIN',
@ -16,6 +17,7 @@
EXPORT_TAB: 'EXPORT_TAB',
PEN_SIZE : 'PEN_SIZE',
RESIZE_SETTINGS: 'RESIZE_SETTINGS',
COLOR_FORMAT: 'COLOR_FORMAT',
KEY_TO_DEFAULT_VALUE_MAP_ : {
'GRID_WIDTH' : 0,
'MAX_FPS' : 24,
@ -25,6 +27,7 @@
},
'CANVAS_BACKGROUND' : 'lowcont-dark-canvas-background',
'SELECTED_PALETTE' : Constants.CURRENT_COLORS_PALETTE_ID,
'SEAMLESS_OPACITY' : 0.30,
'SEAMLESS_MODE' : false,
'PREVIEW_SIZE' : 'original',
'ONION_SKIN' : false,
@ -37,7 +40,8 @@
maintainRatio : true,
resizeContent : false,
origin : 'TOPLEFT'
}
},
COLOR_FORMAT: 'hex',
},
/**

View File

@ -28,14 +28,10 @@
var description = piskelData.description || '';
var descriptor = new pskl.model.piskel.Descriptor(name, description);
this.piskel_ = new pskl.model.Piskel(piskelData.width, piskelData.height, descriptor);
this.piskel_ = new pskl.model.Piskel(piskelData.width, piskelData.height, piskelData.fps, descriptor);
this.layersToLoad_ = piskelData.layers.length;
if (piskelData.expanded) {
piskelData.layers.forEach(this.loadExpandedLayer.bind(this));
} else {
piskelData.layers.forEach(this.deserializeLayer.bind(this));
}
piskelData.layers.forEach(this.deserializeLayer.bind(this));
};
ns.Deserializer.prototype.deserializeLayer = function (layerString, index) {
@ -43,49 +39,66 @@
var layer = new pskl.model.Layer(layerData.name);
layer.setOpacity(layerData.opacity);
// 1 - create an image to load the base64PNG representing the layer
var base64PNG = layerData.base64PNG;
var image = new Image();
// Backward compatibility: if the layerData is not chunked but contains a single base64PNG,
// create a fake chunk, expected to represent all frames side-by-side.
if (typeof layerData.chunks === 'undefined' && layerData.base64PNG) {
this.normalizeLayerData_(layerData);
}
// 2 - attach the onload callback that will be triggered asynchronously
image.onload = function () {
// 5 - extract the frames from the loaded image
var frames = pskl.utils.LayerUtils.createFramesFromSpritesheet(image, layerData.frameCount);
// 6 - add each image to the layer
this.addFramesToLayer(frames, layer, index);
}.bind(this);
var chunks = layerData.chunks;
// 3 - set the source of the image
image.src = base64PNG;
return layer;
};
ns.Deserializer.prototype.loadExpandedLayer = function (layerData, index) {
var width = this.piskel_.getWidth();
var height = this.piskel_.getHeight();
var layer = new pskl.model.Layer(layerData.name);
layer.setOpacity(layerData.opacity);
var frames = layerData.grids.map(function (grid) {
return pskl.model.Frame.fromPixelGrid(grid, width, height);
// Prepare a frames array to store frame objects extracted from the chunks.
var frames = [];
Promise.all(chunks.map(function (chunk) {
// Create a promise for each chunk.
return new Promise(function (resolve, reject) {
var image = new Image();
// Load the chunk image in an Image object.
image.onload = function () {
// extract the chunkFrames from the chunk image
var chunkFrames = pskl.utils.FrameUtils.createFramesFromChunk(image, chunk.layout);
// add each image to the frames array, at the extracted index
chunkFrames.forEach(function (chunkFrame) {
frames[chunkFrame.index] = chunkFrame.frame;
});
resolve();
};
image.src = chunk.base64PNG;
});
})).then(function () {
frames.forEach(layer.addFrame.bind(layer));
this.layers_[index] = layer;
this.onLayerLoaded_();
}.bind(this)).catch(function (error) {
console.error('Failed to deserialize layer');
console.error(error);
});
this.addFramesToLayer(frames, layer, index);
return layer;
};
ns.Deserializer.prototype.addFramesToLayer = function (frames, layer, index) {
frames.forEach(layer.addFrame.bind(layer));
this.layers_[index] = layer;
this.onLayerLoaded_();
};
ns.Deserializer.prototype.onLayerLoaded_ = function () {
this.layersToLoad_ = this.layersToLoad_ - 1;
if (this.layersToLoad_ === 0) {
this.layers_.forEach(function (layer) {
this.piskel_.addLayer(layer);
}.bind(this));
this.callback_(this.piskel_, {fps: this.data_.piskel.fps});
this.callback_(this.piskel_);
}
};
/**
* Backward comptibility only. Create a chunk for layerData objects that only contain
* an single base64PNG without chunk/layout information.
*/
ns.Deserializer.prototype.normalizeLayerData_ = function (layerData) {
var layout = [];
for (var i = 0 ; i < layerData.frameCount ; i++) {
layout.push([i]);
}
layerData.chunks = [{
base64PNG : layerData.base64PNG,
layout : layout
}];
};
})();

View File

@ -1,6 +1,21 @@
(function () {
var ns = $.namespace('pskl.utils.serialization');
var areChunksValid = function (chunks) {
return chunks.length && chunks.every(function (chunk) {
return chunk.base64PNG && chunk.base64PNG !== 'data:,';
});
};
var createLineLayout = function (size, offset) {
var layout = [];
for (var i = 0 ; i < size ; i++) {
layout.push([i + offset]);
}
return layout;
};
ns.Serializer = {
serialize : function (piskel) {
var serializedLayers = piskel.getLayers().map(function (l) {
@ -26,9 +41,51 @@
opacity : layer.getOpacity(),
frameCount : frames.length
};
var renderer = new pskl.rendering.FramesheetRenderer(frames);
layerToSerialize.base64PNG = renderer.renderAsCanvas().toDataURL();
// A layer spritesheet data can be chunked in case the spritesheet PNG is to big to be
// converted to a dataURL.
// Frames are divided equally amongst chunks and each chunk is converted to a spritesheet
// PNG. If any chunk contains an invalid base64 PNG, we increase the number of chunks and
// retry.
var chunks = [];
while (!areChunksValid(chunks)) {
if (chunks.length >= frames.length) {
// Something went horribly wrong.
chunks = [];
break;
}
// Chunks are invalid, increase the number of chunks by one, and chunk the frames array.
var frameChunks = pskl.utils.Array.chunk(frames, chunks.length + 1);
// Reset chunks array.
chunks = [];
// After each chunk update the offset by te number of frames that have been processed.
var offset = 0;
for (var i = 0 ; i < frameChunks.length ; i++) {
var chunkFrames = frameChunks[i];
chunks.push({
// create a layout array, containing the indices of the frames extracted in this chunk
layout : createLineLayout(chunkFrames.length, offset),
base64PNG : ns.Serializer.serializeFramesToBase64(chunkFrames),
});
offset += chunkFrames.length;
}
}
layerToSerialize.chunks = chunks;
return JSON.stringify(layerToSerialize);
},
serializeFramesToBase64 : function (frames) {
try {
var renderer = new pskl.rendering.FramesheetRenderer(frames);
return renderer.renderAsCanvas().toDataURL();
} catch (e) {
return '';
}
}
};
})();

View File

@ -90,20 +90,20 @@
}
var descriptor = new pskl.model.piskel.Descriptor(descriptorName, descriptorDescription);
var piskel = new pskl.model.Piskel(width, height, descriptor);
var piskel = new pskl.model.Piskel(width, height, fps, descriptor);
var loadedLayers = 0;
var loadLayerImage = function(layer, cb) {
var image = new Image();
image.onload = function() {
var frames = pskl.utils.LayerUtils.createFramesFromSpritesheet(this, layer.frameCount);
var frames = pskl.utils.FrameUtils.createFramesFromSpritesheet(this, layer.frameCount);
frames.forEach(function (frame) {
layer.model.addFrame(frame);
});
loadedLayers++;
if (loadedLayers == layerCount) {
cb(piskel, {fps: fps});
cb(piskel);
}
};
image.src = layer.dataUri;

View File

@ -85,7 +85,7 @@
return bytes;
},
serialize : function (piskel, expanded) {
serialize : function (piskel) {
var i;
var j;
var layers;

View File

@ -14,6 +14,6 @@
var descriptor = new pskl.model.piskel.Descriptor('Deserialized piskel', '');
var layer = pskl.model.Layer.fromFrames('Layer 1', frames);
this.callback_(pskl.model.Piskel.fromLayers([layer], descriptor), {fps: Constants.DEFAULTS.FPS});
this.callback_(pskl.model.Piskel.fromLayers([layer], Constants.DEFAULTS.FPS, descriptor));
};
})();

View File

@ -9,14 +9,14 @@
ns.Deserializer_v1.prototype.deserialize = function () {
var piskelData = this.data_.piskel;
var descriptor = new pskl.model.piskel.Descriptor('Deserialized piskel', '');
var piskel = new pskl.model.Piskel(piskelData.width, piskelData.height, descriptor);
var piskel = new pskl.model.Piskel(piskelData.width, piskelData.height, Constants.DEFAULTS.FPS, descriptor);
piskelData.layers.forEach(function (serializedLayer) {
var layer = this.deserializeLayer(serializedLayer);
piskel.addLayer(layer);
}.bind(this));
this.callback_(piskel, {fps: Constants.DEFAULTS.FPS});
this.callback_(piskel);
};
ns.Deserializer_v1.prototype.deserializeLayer = function (layerString) {

View File

@ -115,6 +115,7 @@
"js/controller/NotificationController.js",
"js/controller/TransformationsController.js",
"js/controller/CanvasBackgroundController.js",
"js/controller/UserWarningController.js",
// Settings sub-controllers
"js/controller/settings/AbstractSettingController.js",
@ -139,6 +140,8 @@
"js/controller/dialogs/ImportImageController.js",
"js/controller/dialogs/BrowseLocalController.js",
"js/controller/dialogs/CheatsheetController.js",
"js/controller/dialogs/PerformanceInfoController.js",
"js/controller/dialogs/UnsupportedBrowserController.js",
// Dialogs controller
"js/controller/dialogs/DialogsController.js",
@ -181,6 +184,8 @@
"js/service/FileDropperService.js",
"js/service/SelectedColorsService.js",
"js/service/MouseStateService.js",
"js/service/performance/PerformanceReport.js",
"js/service/performance/PerformanceReportService.js",
"js/service/storage/LocalStorageService.js",
"js/service/storage/GalleryStorageService.js",
"js/service/storage/DesktopStorageService.js",

View File

@ -1,6 +1,7 @@
// This list is used both by the grunt build and index.html (in debug mode)
(typeof exports != "undefined" ? exports : pskl_exports).styles = [
"css/variables.css",
"css/reset.css",
"css/style.css",
"css/animations.css",
@ -22,6 +23,8 @@
"css/dialogs-cheatsheet.css",
"css/dialogs-create-palette.css",
"css/dialogs-import-image.css",
"css/dialogs-performance-info.css",
"css/dialogs-unsupported-browser.css",
"css/notifications.css",
"css/toolbox.css",
"css/toolbox-layers-list.css",

View File

@ -0,0 +1,32 @@
<script type="text/template" id="templates/dialogs/performance-info.html">
<div class="dialog-wrapper">
<h3 class="dialog-head">
Performance issues
<span class="dialog-close">X</span>
</h3>
<div class="dialog-performance-info-body">
<p>The current sprite may exceed the recommendations for Piskel.</p>
<p>You can ignore this warning and continue working with your sprite but you might experience the following issues:</p>
<ul>
<li>saving failures</li>
<li>slowdowns</li>
<li>crashes</li>
</ul>
<p>If you ignore this warning, please save often!</p>
<p>To fix the issue, try adjusting your sprite settings:</p>
<ul>
<li>sprite resolution <sup title="recommended: lower than 256x256, max: 512x512" rel="tooltip" data-placement="top">?</sup></li>
<li>number of layers <sup title="recommended: lower than 5, max: 20" rel="tooltip" data-placement="top">?</sup></li>
<li>number of frames <sup title="recommended: lower than 25, max: 100" rel="tooltip" data-placement="top">?</sup></li>
<li>number of colors <sup title="max: 100" rel="tooltip" data-placement="top">?</sup></li>
</ul>
<p>We strive to improve Piskel, its performances and stability, but this is a personal project with limited time and resources.
We prefer to warn you early rather than having you lose your work.</p>
<p>Feedback welcome at <a href="https://github.com/juliandescottes/piskel" target="_blank">https://github.com/juliandescottes/piskel</a></p>
<p>
<div class="warning-icon icon-common-warning-red">&nbsp;</div>
<div class="warning-icon-info">This icon will remain on the top-bottom of the application until the performance issue is fixed. You can click on it at anytime to display this information again.</div>
</p>
</div>
</div>
</script>

View File

@ -0,0 +1,18 @@
<script type="text/template" id="templates/dialogs/unsupported-browser.html">
<div class="dialog-wrapper">
<h3 class="dialog-head">
Browser not supported
<span class="dialog-close">X</span>
</h3>
<div class="dialog-content">
<p>Your current browser (<span id="current-user-agent"></span>) is not supported.</p>
<p>Piskel is currently tested for:</p>
<ul class="supported-browser-list">
<li>Google Chrome <a href="https://www.google.com/chrome/browser/desktop/" target="_blank">https://www.google.com/chrome/browser/desktop/</a></li>
<li>Mozilla Firefox <a href="https://www.mozilla.org/en-US/firefox/" target="_blank">https://www.mozilla.org/en-US/firefox/</a></li>
<li>Microsoft Edge <a href="https://www.microsoft.com/en-us/windows/microsoft-edge" target="_blank">https://www.microsoft.com/en-us/windows/microsoft-edge</a></li>
</ul>
<p>This browser has not been tested with Piskel, you might experience unknown bugs and crashes if you decide to continue.</p>
</div>
</div>
</script>

View File

@ -43,4 +43,4 @@
<script type="text/template" id="drawingTool-item-template">
<li rel="tooltip" data-placement="{{tooltipposition}}" class="{{cssclass}}" data-tool-id="{{toolid}}" title="{{title}}"></li>
</script>
</div>
</div>

View File

@ -36,7 +36,7 @@
<script type="text/template" id="layer-item-template">
<li class="layer-item {{isselected:current-layer-item}}"
data-layer-index="{{layerindex}}">
{{layername}}
<span class="layer-name" data-placement="top">{{layername}}</span>
<span class="layer-item-opacity"
title="Layer opacity" rel="tooltip" data-placement="top">
{{opacity}}&#945;

View File

@ -2,35 +2,35 @@
<div
data-setting="user"
class="tool-icon icon-settings-gear-white"
title="<span style='color:gold'>PREFERENCES</span></br>"
title="<span class='highlight'>PREFERENCES</span></br>"
rel="tooltip" data-placement="left">
</div>
<div
data-setting="resize"
class="tool-icon icon-settings-resize-white"
title="<span style='color:gold'>RESIZE</span></br>Resize the drawing area"
title="<span class='highlight'>RESIZE</span></br>Resize the drawing area"
rel="tooltip" data-placement="left">
</div>
<div
data-setting="save"
class="tool-icon icon-settings-save-white"
title="<span style='color:gold'>SAVE</span></br>Save to your gallery, save locally<br/>or export as a file"
title="<span class='highlight'>SAVE</span></br>Save to your gallery, save locally<br/>or export as a file"
rel="tooltip" data-placement="left" >
</div>
<div
data-setting="export"
class="tool-icon icon-settings-export-white"
title="<span style='color:gold'>EXPORT</span></br>Export as Image, as Spritesheet<br/>or as Animated GIF"
title="<span class='highlight'>EXPORT</span></br>Export as Image, as Spritesheet<br/>or as Animated GIF"
rel="tooltip" data-placement="left">
</div>
<div
data-setting="import"
class="tool-icon icon-settings-open-folder-white"
title="<span style='color:gold'>IMPORT</span></br>Import an existing image,<br/>an animated GIF or a .piskel file"
title="<span class='highlight'>IMPORT</span></br>Import an existing image,<br/>an animated GIF or a .piskel file"
rel="tooltip" data-placement="left">
</div>

View File

@ -24,7 +24,7 @@
</div>
<div class="settings-item">
<label for="grid-width">Pixel Grid</label>
<label for="grid-width">Pixel grid</label>
<select id="grid-width" class="grid-width-select">
<option value="0">Disabled</option>
<option value="1">1px</option>
@ -35,9 +35,9 @@
</div>
<div class="settings-item">
<label>Layer Opacity</label>
<input type="range" class="layer-opacity-input" name="layer-opacity" min="0" max="1" step="0.05"/>
<span class="layer-opacity-text"></span>
<label>Layer opacity</label>
<input type="range" class="settings-opacity-input layer-opacity-input" name="layer-opacity" min="0" max="1" step="0.05"/>
<span class="settings-opacity-text layer-opacity-text"></span>
</div>
<div class="settings-item">
@ -47,11 +47,25 @@
</label>
</div>
<div class="settings-item">
<label>Seamless opacity</label>
<input type="range" class="settings-opacity-input seamless-opacity-input" name="seamless-opacity" min="0" max="0.5" step="0.01"/>
<span class="settings-opacity-text seamless-opacity-text"></span>
</div>
<div class="settings-item">
<label>Maximum FPS</label>
<input type="text" class="textfield textfield-small max-fps-input" autocomplete="off" name="max-fps"/>
</div>
<div class="settings-item">
<label for="color-format">Color format</label>
<select id="color-format" class="color-format-select">
<option value="hex">Hex</option>
<option value="rgb">RGB</option>
</select>
</div>
<input type="submit" class="button button-primary" value="Apply settings" />
</div>
</form>

View File

@ -5,7 +5,7 @@
</div>
<div class="export-panel-section">
<div style="padding-bottom: 5px">
<span style="color: gold;">Export as C File: </span>
<span class="highlight">Export as C File: </span>
<span class="export-info">
C file with frame rendered as array.
</span>
@ -14,7 +14,7 @@
</div>
<div class="export-panel-section">
<div style="padding-bottom: 5px">
<span style="color: gold;">Export selected frame as PNG File: </span>
<span class="highlight">Export selected frame as PNG File: </span>
<span class="export-info">
PNG export of the currently selected frame.
</span>

View File

@ -2,7 +2,7 @@
<div class="export-panel-png">
<div class="export-panel-header export-info">Export your animation as a PNG spritesheet containing all frames.</div>
<div class="export-panel-section png-export-layout-section">
<div style="color: gold; padding-bottom: 5px;">Spritesheet layout options:</div>
<div class="highlight" style="padding-bottom: 5px;">Spritesheet layout options:</div>
<div style="display: flex; line-height: 20px;">
<div style="flex: 1;">
<span>Columns</span>
@ -22,5 +22,17 @@
<button type="button" style="white-space: nowrap;" class="button button-primary datauri-open-button">To data-uri</button>
<span class="png-export-datauri-info export-info">Open the PNG export in your browser as a data-uri</span>
</div>
<div class="export-panel-section">
<div style="padding-bottom: 5px">
<span class="highlight export-panel-row">Export for PixiJS Movie: </span>
</div>
<div class="export-panel-row">
<button type="button" class="button button-primary png-pixi-download-button"/>Download</button>
<span class="png-export-dimension-info export-info">Spritesheet with JSON metadata</span>
</div>
</div>
</div>
</script>
</script>

View File

@ -45,7 +45,7 @@
<script type="text/template" id="previous-session-info-template">
<div>
Restore a backup of <span style="color:gold">{{name}}</span>, saved at <span style="color:white">{{date}}</span> ?
Restore a backup of <span class="highlight">{{name}}</span>, saved at <span style="color:white">{{date}}</span> ?
<div style="margin-top:10px;">
<button type="button" class="button button-primary restore-session-button">Restore</button>
</div>

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