Layer : demo under/layers

This commit is contained in:
Julian Descottes 2013-09-27 23:37:00 +02:00
parent 21cc6c1fdb
commit d98ddef0b9
156 changed files with 66772 additions and 0 deletions

8
layers/build/piskel-packaged-min.js vendored Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

16
layers/cheap-partials.js Normal file
View File

@ -0,0 +1,16 @@
// SUPER CHEAP TEMPLATES !
window._ctl = function (event) {
var iframe=event.target || event.srcElement, div=document.createElement("div");
div.innerHTML = iframe.contentWindow.document.body.innerHTML;
if (div.children.length == 1) div = div.children[0];
iframe.parentNode.replaceChild(div, iframe);
};
// cheap template prepare
window._ctp = function (event) {
var iframe=event.target || event.srcElement, script=document.createElement("script");
script.setAttribute("type", "text/html");
script.setAttribute("id", iframe.getAttribute("src"));
script.innerHTML = iframe.contentWindow.document.body.innerHTML;
iframe.parentNode.removeChild(iframe);
document.body.appendChild(script);
};

View File

@ -0,0 +1,8 @@
.tooltip.in {
opacity: 0.95;
filter: alpha(opacity=95);
}
.tooltip {
line-height: 20px;
}

112
layers/css/bootstrap/bootstrap.css vendored Executable file
View File

@ -0,0 +1,112 @@
/*!
* Bootstrap v2.1.1
*
* Copyright 2012 Twitter, Inc
* Licensed under the Apache License v2.0
* http://www.apache.org/licenses/LICENSE-2.0
*
* Designed and built with all the love in the world @twitter by @mdo and @fat.
*/
.clearfix {
*zoom: 1;
}
.clearfix:before,
.clearfix:after {
display: table;
content: "";
line-height: 0;
}
.clearfix:after {
clear: both;
}
.hide-text {
font: 0/0 a;
color: transparent;
text-shadow: none;
background-color: transparent;
border: 0;
}
.input-block-level {
display: block;
width: 100%;
min-height: 30px;
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
}
.tooltip {
position: absolute;
z-index: 1030;
display: block;
visibility: visible;
padding: 5px;
font-size: 11px;
opacity: 0;
filter: alpha(opacity=0);
}
.tooltip.in {
opacity: 0.8;
filter: alpha(opacity=80);
}
.tooltip.top {
margin-top: -3px;
}
.tooltip.right {
margin-left: 3px;
}
.tooltip.bottom {
margin-top: 3px;
}
.tooltip.left {
margin-left: -3px;
}
.tooltip-inner {
max-width: 200px;
padding: 3px 8px;
color: #ffffff;
text-align: center;
text-decoration: none;
background-color: #000000;
-webkit-border-radius: 4px;
-moz-border-radius: 4px;
border-radius: 4px;
}
.tooltip-arrow {
position: absolute;
width: 0;
height: 0;
border-color: transparent;
border-style: solid;
}
.tooltip.top .tooltip-arrow {
bottom: 0;
left: 50%;
margin-left: -5px;
border-width: 5px 5px 0;
border-top-color: #000000;
}
.tooltip.right .tooltip-arrow {
top: 50%;
left: 0;
margin-top: -5px;
border-width: 5px 5px 5px 0;
border-right-color: #000000;
}
.tooltip.left .tooltip-arrow {
top: 50%;
right: 0;
margin-top: -5px;
border-width: 5px 0 5px 5px;
border-left-color: #000000;
}
.tooltip.bottom .tooltip-arrow {
top: 0;
left: 50%;
margin-left: -5px;
border-width: 0 5px 5px;
border-bottom-color: #000000;
}
.tooltip {
line-height: 20px;
}

View File

@ -0,0 +1 @@
Bootstrap custom build containing only the tooltip component

View File

@ -0,0 +1,185 @@
.preview-list-wrapper {
position: relative;
height: 100%;
overflow: hidden;
}
.preview-list-scroller {
overflow-y: scroll;
overflow-x: hidden;
height: 100%;
}
.top-overflow,
.bottom-overflow {
height: 20px;
position: absolute;
left: 0;
right: 12px;
-webkit-transition: all 500ms ease-out;
-moz-transition: all 500ms ease-out;
-ms-transition: all 500ms ease-out;
-o-transition: all 500ms ease-out;
transition: all 500ms ease-out;
background-image: linear-gradient(45deg, rgba(0,0,0, 0.8) 25%, transparent 25%, transparent 75%, rgba(0,0,0, 0.8) 75%, rgba(0,0,0, 0.8)),
linear-gradient(-45deg, rgba(0,0,0, 0.8) 25%, transparent 25%, transparent 75%, rgba(0,0,0, 0.8) 75%, rgba(0,0,0, 0.8));
background-size: 29px 45px;
background-repeat: repeat-x;
background-position-x: 3px;
z-index: 10;
}
.top-overflow {
top: -20px;
}
.bottom-overflow {
bottom: -20px;
background-position-x: 0;
background-position-y: -23px;
}
.top-overflow-visible .top-overflow {
top: 0;
}
.bottom-overflow-visible .bottom-overflow {
bottom: 0;
}
.preview-list {
list-style-type: none;
padding-right: 7px;
}
.add-frame-action {
border: #888 solid 4px;
font-size: 13px;
color: #888;
cursor: pointer;
padding: 6px 0;
border-radius: 4px;
margin-top: 8px;
background-image: url(../img/plus.png);
background-repeat: no-repeat;
background-position: 3px 7px;
background-size: 26px 26px;
text-indent: 18px;
background-color: #222;
}
.add-frame-action .label {
width: 80px;
margin: 0 auto;
}
.add-frame-action:hover {
border-color: gold;
}
.preview-tile {
position: relative;
border: #444 3px solid;
border-radius: 3px;
margin: 5px 0;
}
.preview-tile:first-child {
margin-top: 0;
}
.preview-tile:hover {
border: #999 3px solid;
}
.preview-tile .tile-overlay {
z-index: 10;
position: absolute;
background-color: rgba(100, 100, 100, 0.6);
opacity: 0;
height: 30px;
width: 30px;
border: none;
}
.preview-tile:hover .tile-overlay {
opacity: 1.0;
}
.preview-tile .tile-overlay.tile-count {
display: block;
opacity: 1.0;
border-bottom-right-radius: 3px;
top: 0;
left: 0;
font-size: 14px;
line-height: 30px;
color: white;
font-weight: bold;
text-align: center;
cursor: default;
}
.preview-tile .tile-overlay.delete-frame-action {
background-image: url(../img/garbage.png);
background-repeat: no-repeat;
top: 0;
right: 0;
border-bottom-left-radius: 3px;
height: 30px;
width: 30px;
background-position: 5px 5px;
background-size: 20px;
cursor: pointer;
}
.preview-tile .tile-overlay.duplicate-frame-action {
bottom: 0;
right: 0;
border-bottom-left-radius: 3px;
background-image: url(../img/duplicate.png);
background-repeat: no-repeat;
background-position: 5px 5px;
background-size: 20px;
cursor: pointer;
}
.preview-tile .tile-overlay.dnd-action {
bottom: 0;
left: 0;
border-top-right-radius: 3px;
background-image: url(../img/dragndrop.png);
background-repeat: no-repeat;
background-position: 5px 5px;
background-size: 20px;
cursor: move;
}
.preview-tile.selected {
border-color: gold;
}
.preview-tile.selected:after {
content: " ";
position: absolute;
top: 38px;
right: -15px;
border: transparent 4px solid;
border-left-color: gold;
border-width: 6px;
border-style: solid;
}
/**
* Drag n drop styles.
*/
.preview-tile-drop-proxy {
border: 3px dashed gold;
height: 90px;
border-radius: 9px;
background-color: rgba(255, 215,0, 0.2);
}

36
layers/css/reset.css Normal file
View File

@ -0,0 +1,36 @@
html, body {
height : 100%; width: 100%;
margin : 0;
overflow: hidden;
cursor : default;
font-family: arial;
font-size: 11px;
-webkit-touch-callout: none;
-webkit-user-select: none;
-khtml-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
ul, li {
margin : 0;
padding : 0;
list-style-type: none;
}
/* Force apparition of scrollbars on leopard */
::-webkit-scrollbar {
-webkit-appearance: none;
width: 6px;
}
::-webkit-scrollbar-thumb {
border-radius: 2px;
background-color: #666;
}
::-webkit-scrollbar-track {
background-color: rgba(50, 50, 50, 0.4);
}

118
layers/css/settings.css Normal file
View File

@ -0,0 +1,118 @@
/** Righty sticky drawer expanded state. */
.right-sticky-section.sticky-section {
right: 0;
width: 47px;
-webkit-transition: all 200ms ease-out;
-moz-transition: all 200ms ease-out;
-ms-transition: all 200ms ease-out;
transition: all 200ms ease-out;
}
.right-sticky-section.expanded {
right: 280px;
}
.right-sticky-section .tool-icon {
float: right;
margin-right: 0;
}
.drawer-content {
overflow: hidden;
background-color: #444;
height: 550px;
max-height: 100%;
width: 280px;
border-top-left-radius: 4px;
border-bottom-left-radius: 4px;
}
.right-sticky-section.expanded .tool-icon {
margin-right: 1px;
}
.right-sticky-section .tool-icon.has-expanded-drawer {
position: relative;
background-color: #444;
margin-right: 0;
padding-right: 1px;
}
.settings-section {
margin: 10px 20px;
font-size: 12px;
font-weight: bold;
color: #ccc;
text-shadow: 1px 1px #000;
}
.settings-title {
margin-top: 20px;
margin-bottom: 10px;
text-transform: uppercase;
border-bottom: 1px #aaa solid;
padding-bottom: 5px;
}
.settings-item {}
.background-picker-wrapper {
overflow: hidden;
padding: 10px 5px 20px 5px;
}
.background-picker {
cursor: pointer;
float: left;
height: 35px;
width: 35px;
background-color: transparent;
margin-right: 15px;
padding: 1px;
position: relative;
}
.background-picker:after {
content: " ";
position: absolute;
top: -2px;
right: -2px;
bottom: -2px;
left: -2px;
}
.background-picker:hover:after {
border: #eee 1px solid;
}
.background-picker.selected:after {
border: gold 1px solid;
}
/* Gif Export Setting panel*/
.export-gif-upload-button {
margin-top : 10px;
}
.export-gif-preview {
margin-top:20px;
max-width:240px;
position:relative;
}
.preview-upload-ongoing:before{
content: "Upload ongoing ...";
position: absolute;
display: block;
height: 100%;
width: 100%;
text-align: center;
padding-top: 45%;
box-sizing:border-box;
-moz-box-sizing:border-box;
background: rgba(0,0,0,0.5);
color: white;
}

303
layers/css/style.css Normal file
View File

@ -0,0 +1,303 @@
body {
background: radial-gradient(circle, #000, #373737);
/* 16/06/2013 : -webkit still needed for
safari, safari mobile and android browser and chrome for android
cf http://caniuse.com/css-gradients */
background: -webkit-radial-gradient(circle, #000, #373737);
}
/**
* Application layout
*/
.main-wrapper {
position: absolute;
top: 5px;
right: 0;
bottom: 5px;
left: 0;
}
.column-wrapper {
text-align: center;
font-size: 0;
position: absolute;
left: 100px; /* Reserve room for tools on the left edge of the screen. */
top: 0;
right: 50px; /* Reserve room for actions on the right edge of the screen. */
bottom: 0;
}
.piskel-name-container {
overflow:hidden;
position:fixed;
top:10px;
left:10px;
color:white;
font-family:Tahoma;
z-index: 10000;
}
.piskel-name-container #piskel-name {
border :none;
color : lightgrey;
background: transparent;
font-size:16pt;
}
.column {
display: inline-block;
}
.left-column {
vertical-align: top;
height: 100%;
margin-right: 7px;
}
.main-column {
height: 100%;
position: relative;
}
.right-column {
vertical-align: top;
margin-left: 10px;
}
.drawing-canvas-container {
font-size: 0;
}
.layers-canvas {
opacity: 0.2;
}
.sticky-section {
position: fixed;
top: 0;
bottom: 0;
z-index: 1000;
}
.sticky-section .sticky-section-wrap {
display: table;
height: 100%;
}
.sticky-section .vertical-centerer {
display: table-cell;
vertical-align: middle;
}
.left-sticky-section.sticky-section {
left: 0;
max-width: 100px;
}
.left-sticky-section .tool-icon {
float: left;
}
/**
* Canvases layout
*/
.canvas {
position: relative;
z-index: 1;
}
.canvas-container {
position: relative;
display: block;
}
.canvas-container .canvas-background {
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
}
.light-picker-background,
.light-canvas-background .canvas-background {
background: url(../img/canvas_background/light_canvas_background.png) repeat;
}
.medium-picker-background,
.medium-canvas-background .canvas-background {
background: url(../img/canvas_background/medium_canvas_background.png) repeat;
}
.lowcont-medium-picker-background,
.lowcont-medium-canvas-background .canvas-background {
background: url(../img/canvas_background/lowcont_medium_canvas_background.png) repeat;
}
.lowcont-dark-picker-background,
.lowcont-dark-canvas-background .canvas-background {
background: url(../img/canvas_background/lowcont_dark_canvas_background.png) repeat;
}
.canvas.canvas-overlay, .canvas.layers-canvas {
position: absolute;
top: 0;
left: 0;
}
.canvas.layers-down-canvas {z-index: 7;}
.canvas.drawing-canvas {z-index: 8;}
.canvas.layers-up-canvas {z-index: 9;}
.canvas.canvas-overlay {z-index: 10;}
/**
* Animated preview styles.
*/
.preview-container {
border : 0px Solid black;
border-radius:5px 0px 0px 5px;
box-shadow : 0px 0px 2px rgba(0,0,0,0.2);
font-size: 0;
}
.preview-container canvas {
border : 0px Solid transparent;
}
.display-fps {
float: left;
color: #aaa;
font-size: 12px;
min-width: 55px;
vertical-align: bottom;
line-height: 24px;
}
.range-fps {
overflow: hidden;
}
/**
* Layers container
*/
.layers-container {
border: 4px solid #888;
font-size: medium;
color: white;
text-align: left;
border-radius: 4px;
margin-top: 10px;
}
.layers-title {
padding: 8px;
margin: 0;
font-size: 15px;
background: #222;
background-image: url('../img/layers.svg');
background-size: 22px;
background-repeat: no-repeat;
background-position: 97%;
}
.layers-list {
font-size : 12px;
}
.layer-item {
height:24px;
line-height: 24px;
padding : 0 10px;
border-top: 1px solid #444;
cursor : pointer;
}
.layer-item:hover {
background : #222;
}
.current-layer-item, .current-layer-item:hover {
background : #333;
color: gold;
}
.layers-button-container {
overflow : hidden;
}
.layers-button-arrow {
font-family : 'Lucida Grande', Calibri;
padding : 2px 6px 0 6px;
}
.layers-button {
height: 24px;
margin: 0;
width: 25%;
float : left;
border: none;
box-sizing: border-box;
border-top: 1px solid #666;
border-right: 1px solid #333;
border-bottom: 1px solid #333;
background-color: #3f3f3f;
cursor: pointer;
color: white;
font-size: 0.7em;
font-weight: bold;
text-align: center;
text-decoration: none;
text-shadow: 0px -1px 0 #000;
transition: background-color 0.2s linear;
}
.layers-button:last-child {
border-right-width: 0;
}
.layers-button:hover {
text-decoration: none;
background-color: #484848;
color: gold;
}
/**
* User messages
*/
.user-message {
position: absolute;
right: 0;
bottom: 0;
background-color: #F9EDBE;
padding: 10px 47px;
border-top-left-radius: 7px;
color: #222;
border: #F0C36D 1px solid;
border-right: 0;
border-bottom: 0;
font-weight: bold;
font-size: 13px;
z-index: 10000;
max-width: 300px;
}
.user-message .close {
position: absolute;
top: 6px;
right: 17px;
color: gray;
font-weight: bold;
cursor: pointer;
font-size: 18px;
}
.user-message .close:hover {
color: black;
}

222
layers/css/tools.css Normal file
View File

@ -0,0 +1,222 @@
.tools-wrapper,
.options-wrapper,
.palette-wrapper {
float: left;
}
.tool-icon {
cursor : pointer;
width: 46px;
height: 46px;
margin: 1px;
background-color: #3a3a3a;
background-repeat: no-repeat;
background-position: 12px 12px;
background-size: 24px 24px;
}
.tool-icon.selected {
cursor: default;
background-color: #444;
border: 1px gold solid;
margin: 0;
}
.tool-icon:hover {
background-color: #444;
}
/*
* Tool icons:
*/
.tool-icon.tool-pen {
background-image: url(../img/tools/pen.png);
}
.tool-icon.tool-vertical-mirror-pen {
background-image: url(../img/tools/mirror.png);
background-position: 0px 10px;
background-size: 38px 27px;
}
.tool-icon.tool-paint-bucket {
background-image: url(../img/tools/paintbucket.png);
}
.tool-icon.tool-eraser {
background-image: url(../img/tools/eraser.png);
}
.tool-icon.tool-stroke {
background-image: url(../img/tools/stroke.png);
}
.tool-icon.tool-rectangle {
background-image: url(../img/tools/rectangle.png);
background-position: 12px 14px;
background-size: 24px 20px;
}
.tool-icon.tool-circle {
background-image: url(../img/tools/circle.png);
}
.tool-icon.tool-move {
background-image: url(../img/tools/hand.png);
background-position: 12px 12px;
background-size: 24px 24px;
}
.tool-icon.tool-rectangle-select {
background-image: url(../img/tools/rectangle_selection.png);
background-position: 12px 14px;
background-size: 24px 20px;
}
.tool-icon.tool-shape-select {
background-image: url(../img/tools/magicwand.png);
}
.tool-icon.tool-colorpicker {
background-image: url(../img/tools/eyedropper.png);
background-position: 12px 12px;
background-size: 23px 23px;
}
/*
* Tool cursors:
*/
.tool-paint-bucket .drawing-canvas-container:hover {
cursor: url(../img/icons/paint-bucket.png) 12 12, pointer;
}
.tool-vertical-mirror-pen .drawing-canvas-container:hover {
cursor: url(../img/icons/vertical-mirror-pen.png) 5 15, pointer;
}
.tool-pen .drawing-canvas-container:hover {
cursor: url(../img/icons/pen.png) 0 15, pointer;
}
.tool-eraser .drawing-canvas-container:hover {
cursor: url(../img/icons/eraser.png) 0 15, pointer;
}
.tool-stroke .drawing-canvas-container:hover {
cursor: url(../img/icons/pen.png) 0 15, pointer;
}
.tool-rectangle .drawing-canvas-container:hover {
cursor: url(../img/icons/rectangle.png) 0 15, pointer;
}
.tool-circle .drawing-canvas-container:hover {
cursor: url(../img/icons/circle.png) 2 15, pointer;
}
.tool-move .drawing-canvas-container:hover {
cursor: url(../img/icons/hand.png) 15 15, pointer;
}
.tool-rectangle-select .drawing-canvas-container:hover {
cursor: url(../img/icons/select.png) 15 15, pointer;
}
.tool-shape-select .drawing-canvas-container:hover {
cursor: url(../img/icons/wand.png) 15 15, pointer;
}
.tool-colorpicker .drawing-canvas-container:hover {
cursor: url(../img/icons/dropper.png) 0 15, pointer;
}
.tool-color-picker input {
width: 16px;
height: 16px;
text-indent: -10000px;
border: 1px solid black;
background: white;
cursor: pointer;
position : relative;
top: 10px;
margin-left: 2px;
}
.tool-color-picker .secondary-color-picker {
top : 18px;
margin-left: 0;
}
.palette-color[data-color=TRANSPARENT] {
position: relative;
top: 10px;
left: 10px;
background-color: white;
height : 16px;
width : 16px;
border: 2px solid #000;
background-size: 16px 16px;
background-position: 0 0;
background-image: -webkit-gradient(
linear,
left top,
right bottom,
color-stop(0, #fff),
color-stop(0.45, #fff),
color-stop(0.5, #ff0000),
color-stop(0.55, #fff),
color-stop(1, #fff)
);
background-image: -moz-linear-gradient(
left top,
#fff 0%,
#fff 45%,
#f00 50%,
#fff 55%,
#fff 100%
);
}
/*
* Framesheet level actions:
*/
.tool-icon.gallery-icon {
background-image: url(../img/gallery.png);
background-position: 3px 3px;
background-size: 39px 39px;
}
.tool-icon.save-icon {
background-image: url(../img/save.png);
background-position: 6px 6px;
background-size: 36px 36px;
}
.tool-icon.gear-icon {
background-image: url(../img/gear.png);
background-position: 6px 7px;
background-size: 32px 32px;
}
.tool-icon.upload-cloud-icon {
background-image: url(../img/cloud_export.png);
background-position: 4px 0px;
background-size: 36px 36px;
position: relative;
}
.upload-cloud-icon .label {
position: absolute;
left: 0;
bottom: 4px;
right: 0;
font-size: 11px;
text-transform: uppercase;
color: #fff;
text-align: center;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 223 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 431 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 278 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 418 B

BIN
layers/img/cloud_export.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 594 B

BIN
layers/img/dragndrop.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 532 B

BIN
layers/img/duplicate.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 634 B

BIN
layers/img/gallery.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 305 B

BIN
layers/img/garbage.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

BIN
layers/img/gear.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 789 B

BIN
layers/img/icons/circle.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 761 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 209 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 543 B

BIN
layers/img/icons/eraser.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 656 B

BIN
layers/img/icons/hand.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 672 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 557 B

BIN
layers/img/icons/paint-bucket.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 707 B

BIN
layers/img/icons/pen.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 450 B

BIN
layers/img/icons/rectangle.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 660 B

BIN
layers/img/icons/select.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

BIN
layers/img/icons/stroke.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 450 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 603 B

BIN
layers/img/icons/wand.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 570 B

62
layers/img/layers.svg Normal file
View File

@ -0,0 +1,62 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<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"
version="1.1"
width="90"
height="87.886002"
id="svg3024">
<defs
id="defs3026" />
<metadata
id="metadata3029">
<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>
<g
transform="translate(-266.63206,-597.79217)"
id="layer1">
<g
transform="translate(262.14286,465.21932)"
id="Captions" />
<g
id="g3825" />
<g
transform="translate(-0.51079959,126.51585)"
id="layer1-9"
style="fill:#ffffff;fill-opacity:1">
<g
transform="translate(262.14286,465.21932)"
id="Captions-4"
style="fill:#ffffff;fill-opacity:1" />
<g
id="g3825-8"
style="fill:#ffffff;fill-opacity:1">
<path
d="m 357.14286,556.72132 c 0,1.346 -1.097,2.441 -2.442,2.441 h -60.239 c -1.347,0 -2.441,-1.096 -2.441,-2.441 v -4.555 h 50.241 c 4.346,0 7.884,-3.539 7.884,-7.885 v -48.128 h 4.555 c 1.346,0 2.442,1.096 2.442,2.441 v 58.127 z"
id="path2989-8"
style="fill:#ffffff;fill-opacity:1" />
<path
d="m 279.58086,544.28132 v -4.555 h 6.997 2.722 2.721 37.803 c 4.346,0 7.884,-3.537 7.884,-7.883 v -35.69 -2.721 -2.721 -6.998 h 4.555 c 1.346,0 2.442,1.096 2.442,2.443 v 4.555 2.721 2.721 48.129 c 0,1.346 -1.097,2.443 -2.442,2.443 h -50.242 -2.721 -2.722 -4.555 c -1.347,0 -2.442,-1.098 -2.442,-2.444 z"
id="path2991-2"
style="fill:#ffffff;fill-opacity:1" />
<path
d="m 329.82386,471.27632 h -60.239 c -1.347,0 -2.442,1.093 -2.442,2.441 v 58.127 c 0,1.344 1.095,2.441 2.442,2.441 h 4.555 2.721 2.721 6.997 2.722 2.721 37.803 c 1.345,0 2.441,-1.098 2.441,-2.441 v -35.691 -2.721 -2.721 -6.998 -2.721 -2.721 -4.554 c -10e-4,-1.348 -1.098,-2.441 -2.442,-2.441 z"
id="path2993-4"
style="fill:#ffffff;fill-opacity:1" />
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.4 KiB

BIN
layers/img/plus.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 271 B

BIN
layers/img/save.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 395 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

BIN
layers/img/tools/circle.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 909 B

BIN
layers/img/tools/eraser.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1010 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

BIN
layers/img/tools/hand.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

BIN
layers/img/tools/mirror.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

BIN
layers/img/tools/pen.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 253 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 245 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 376 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 372 B

BIN
layers/img/tools/stroke.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

59
layers/index.html Normal file
View File

@ -0,0 +1,59 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<title>Piskel</title>
<meta name="description" content="">
<meta name="author" content="Julian Descottes">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" type="text/css" href="css/reset.css">
<link rel="stylesheet" type="text/css" href="css/style.css">
<link rel="stylesheet" type="text/css" href="css/settings.css">
<link rel="stylesheet" type="text/css" href="css/tools.css">
<link rel="stylesheet" type="text/css" href="css/bootstrap/bootstrap.css">
<link rel="stylesheet" type="text/css" href="css/bootstrap/bootstrap-tooltip-custom.css">
<link rel="stylesheet" type="text/css" href="css/preview-film-section.css">
</head>
<body>
<script src="cheap-partials.js" type="text/javascript"></script>
<div class="piskel-name-container">
<input readonly id="piskel-name" type="text" value=""/>
</div>
<div id="main-wrapper" class="main-wrapper">
<iframe src="templates/drawing-tools.html" class="_ctl" onload="_ctl(event)"></iframe>
<div id="column-wrapper" class="column-wrapper">
<div class='column left-column'>
<iframe src="templates/frames-list.html" class="_ctl" onload="_ctl(event)"></iframe>
</div>
<div class='column main-column'>
<div id="drawing-canvas-container" class="drawing-canvas-container canvas-container">
<div class="canvas-background"></div>
</div>
</div>
<div class="column right-column">
<iframe src="templates/preview.html" class="_ctl" onload="_ctl(event)"></iframe>
<iframe src="templates/layers.html" class="_ctl" onload="_ctl(event)"></iframe>
</div>
</div>
<div id="application-action-section" data-pskl-controller="settings" class="sticky-section right-sticky-section">
<div class="sticky-section-wrap">
<iframe src="templates/settings.html" class="_ctl" onload="_ctl(event)"></iframe>
<div class="drawer vertical-centerer">
<div class="drawer-content" id="drawer-container">
<iframe src="templates/settings-application.html" onload="_ctp(event)"></iframe>
<iframe src="templates/settings-export-gif.html" onload="_ctp(event)"></iframe>
</div>
</div>
</div>
</div>
</div>
<script type="text/javascript" src="piskel-boot.js"></script>
</body>
</html>

41
layers/js/Constants.js Normal file
View File

@ -0,0 +1,41 @@
// TODO(grosbouddha): put under pskl namespace.
var Constants = {
DEFAULT : {
HEIGHT : 32,
WIDTH : 32,
FPS : 12
},
MODEL_VERSION : 1,
MAX_HEIGHT : 128,
MAX_WIDTH : 128,
PREVIEW_FILM_SIZE : 120,
DEFAULT_PEN_COLOR : '#000000',
TRANSPARENT_COLOR : 'TRANSPARENT',
/*
* Fake semi-transparent color used to highlight transparent
* strokes and rectangles:
*/
SELECTION_TRANSPARENT_COLOR: 'rgba(255, 255, 255, 0.6)',
/*
* When a tool is hovering the drawing canvas, we highlight the eventual
* pixel target with this color:
*/
TOOL_TARGET_HIGHLIGHT_COLOR: 'rgba(255, 255, 255, 0.2)',
/*
* Default entry point for piskel web service:
*/
PISKEL_SERVICE_URL: 'http://3.piskel-app.appspot.com',
GRID_STROKE_WIDTH: 1,
GRID_STROKE_COLOR: "lightgray",
LEFT_BUTTON : "left_button_1",
RIGHT_BUTTON : "right_button_2"
};

64
layers/js/Events.js Normal file
View File

@ -0,0 +1,64 @@
// TODO(grosbouddha): put under pskl namespace.
Events = {
TOOL_SELECTED : "TOOL_SELECTED",
TOOL_RELEASED : "TOOL_RELEASED",
PRIMARY_COLOR_SELECTED: "PRIMARY_COLOR_SELECTED",
PRIMARY_COLOR_UPDATED: "PRIMARY_COLOR_UPDATED",
SECONDARY_COLOR_SELECTED: "SECONDARY_COLOR_SELECTED",
SECONDARY_COLOR_UPDATED: "SECONDARY_COLOR_UPDATED",
/**
* When this event is emitted, a request is sent to the localstorage
* Service to save the current framesheet. The storage service
* may not immediately store data (internal throttling of requests).
*/
LOCALSTORAGE_REQUEST: "LOCALSTORAGE_REQUEST",
CANVAS_RIGHT_CLICKED: "CANVAS_RIGHT_CLICKED",
/**
* Event to request a refresh of the display.
* A bit overkill but, it's just workaround in our current drawing system.
* TODO: Remove or rework when redraw system is refactored.
*/
REFRESH: "REFRESH",
/**
* Temporary event to bind the redraw of right preview film to the canvas.
* This redraw should be driven by model updates.
* TODO(vincz): Remove.
*/
REDRAW_PREVIEWFILM: "REDRAW_PREVIEWFILM",
/**
* Fired each time a user setting change.
* The payload will be:
* 1st argument: Name of the settings
* 2nd argument: New value
*/
USER_SETTINGS_CHANGED: "USER_SETTINGS_CHANGED",
/**
* The framesheet was reseted and is now probably drastically different.
* Number of frames, content of frames, color used for the palette may have changed.
*/
FRAMESHEET_RESET: "FRAMESHEET_RESET",
FRAME_SIZE_CHANGED : "FRAME_SIZE_CHANGED",
CURRENT_FRAME_SET: "CURRENT_FRAME_SET",
SELECTION_CREATED: "SELECTION_CREATED",
SELECTION_MOVE_REQUEST: "SELECTION_MOVE_REQUEST",
SELECTION_DISMISSED: "SELECTION_DISMISSED",
SHOW_NOTIFICATION: "SHOW_NOTIFICATION",
HIDE_NOTIFICATION: "HIDE_NOTIFICATION",
UNDO: "UNDO",
REDO: "REDO",
CUT: "CUT",
COPY: "COPY",
PASTE: "PASTE"
};

231
layers/js/app.js Normal file
View File

@ -0,0 +1,231 @@
/**
* @require Constants
* @require Events
*/
(function () {
var ns = $.namespace("pskl");
/**
* Main application controller
*/
ns.app = {
init : function () {
var size = this.readSizeFromURL_();
var piskel = new pskl.model.Piskel(size.width, size.height, Constants.DEFAULT.FPS);
var layer = new pskl.model.Layer("Layer 1");
var frame = new pskl.model.Frame(size.width, size.height);
layer.addFrame(frame);
piskel.addLayer(layer);
this.piskelController = new pskl.controller.PiskelController(piskel);
this.drawingController = new pskl.controller.DrawingController(this.piskelController, $('#drawing-canvas-container'));
this.drawingController.init();
this.animationController = new pskl.controller.AnimatedPreviewController(this.piskelController, $('#preview-canvas-container'));
this.animationController.init();
this.previewsController = new pskl.controller.PreviewFilmController(this.piskelController, $('#preview-list'));
this.previewsController.init();
this.layersController = new pskl.controller.LayersController(this.piskelController);
this.layersController.init();
this.settingsController = new pskl.controller.SettingsController(this.piskelController);
this.settingsController.init();
this.selectionManager = new pskl.selection.SelectionManager(this.piskelController);
this.selectionManager.init();
this.historyService = new pskl.service.HistoryService(this.piskelController);
this.historyService.init();
this.keyboardEventService = new pskl.service.KeyboardEventService();
this.keyboardEventService.init();
this.notificationController = new pskl.controller.NotificationController();
this.notificationController.init();
this.localStorageService = new pskl.service.LocalStorageService(this.piskelController);
this.localStorageService.init();
this.imageUploadService = new pskl.service.ImageUploadService();
this.imageUploadService.init();
this.toolController = new pskl.controller.ToolController();
this.toolController.init();
this.paletteController = new pskl.controller.PaletteController();
this.paletteController.init();
var drawingLoop = new pskl.rendering.DrawingLoop();
drawingLoop.addCallback(this.render, this);
drawingLoop.start();
this.initBootstrapTooltips_();
/**
* True when piskel is running in static mode (no back end needed).
* When started from APP Engine, appEngineToken_ (Boolean) should be set on window.pskl
*/
this.isStaticVersion = !pskl.appEngineToken_;
if (this.isStaticVersion) {
var framesheetId = this.readFramesheetIdFromURL_();
if (framesheetId) {
$.publish(Events.SHOW_NOTIFICATION, [{
"content" : "Loading animation with id : [" + framesheetId + "]"
}]);
this.loadFramesheetFromService(framesheetId);
} else {
this.localStorageService.displayRestoreNotification();
}
} else {
if (pskl.framesheetData_ && pskl.framesheetData_.content) {
this.piskelController.load(pskl.framesheetData_.content);
pskl.app.animationController.setFPS(pskl.framesheetData_.fps);
}
}
},
initBootstrapTooltips_ : function () {
$('body').tooltip({
selector: '[rel=tooltip]'
});
},
render : function (delta) {
this.drawingController.render(delta);
this.animationController.render(delta);
this.previewsController.render(delta);
},
readSizeFromURL_ : function () {
var sizeParam = this.readUrlParameter_("size"),
size;
// parameter expected as size=64x48 => size=widthxheight
var parts = sizeParam.split("x");
if (parts && parts.length == 2 && !isNaN(parts[0]) && !isNaN(parts[1])) {
var width = parseInt(parts[0], 10),
height = parseInt(parts[1], 10);
size = {
height : Math.min(height, Constants.MAX_HEIGHT),
width : Math.min(width, Constants.MAX_WIDTH)
};
} else {
size = {
height : Constants.DEFAULT.HEIGHT,
width : Constants.DEFAULT.WIDTH
};
}
return size;
},
readFramesheetIdFromURL_ : function () {
return this.readUrlParameter_("frameId");
},
readUrlParameter_ : function (paramName) {
var searchString = window.location.search.substring(1),
i, val, params = searchString.split("&");
for (i = 0; i < params.length; i++) {
val = params[i].split("=");
if (val[0] == paramName) {
return window.unescape(val[1]);
}
}
return "";
},
loadFramesheetFromService : function (frameId) {
var xhr = new XMLHttpRequest();
xhr.open('GET', Constants.PISKEL_SERVICE_URL + '/get?l=' + frameId, true);
xhr.responseType = 'text';
var piskelController = this.piskelController;
xhr.onload = function (e) {
var res = JSON.parse(this.responseText);
piskelController.deserialize(JSON.stringify(res.framesheet));
pskl.app.animationController.setFPS(res.fps);
$.publish(Events.HIDE_NOTIFICATION);
};
xhr.onerror = function () {
$.publish(Events.HIDE_NOTIFICATION);
};
xhr.send();
},
getFirstFrameAsPNGData_ : function () {
throw 'getFirstFrameAsPNGData_ not implemented';
},
// TODO(julz): Create package ?
storeSheet : function (event) {
var xhr = new XMLHttpRequest();
var formData = new FormData();
formData.append('framesheet_content', this.piskelController.serialize());
formData.append('fps_speed', $('#preview-fps').val());
if (this.isStaticVersion) {
// anonymous save on old app-engine backend
xhr.open('POST', Constants.PISKEL_SERVICE_URL + "/store", true);
} else {
// additional values only used with latest app-engine backend
formData.append('name', $('#piskel-name').val());
formData.append('frames', this.piskelController.getFrameCount());
// Get image/png data for first frame
formData.append('preview', this.getFirstFrameAsPNGData_());
var imageData = (new pskl.rendering.SpritesheetRenderer(this.piskelController)).renderAsImageDataSpritesheetPNG();
formData.append('framesheet', imageData);
xhr.open('POST', "save", true);
}
xhr.onload = function(e) {
if (this.status == 200) {
if (pskl.app.isStaticVersion) {
var baseUrl = window.location.href.replace(window.location.search, "");
window.location.href = baseUrl + "?frameId=" + this.responseText;
} else {
$.publish(Events.SHOW_NOTIFICATION, [{"content": "Successfully saved !"}]);
}
} else {
this.onerror(e);
}
};
xhr.onerror = function(e) {
$.publish(Events.SHOW_NOTIFICATION, [{"content": "Saving failed ("+this.status+")"}]);
};
xhr.send(formData);
if(event) {
event.stopPropagation();
event.preventDefault();
}
return false;
},
uploadAsSpritesheetPNG : function () {
var imageData = (new pskl.rendering.SpritesheetRenderer(this.piskelController)).renderAsImageDataSpritesheetPNG();
this.imageUploadService.upload(imageData, this.openWindow.bind(this));
},
openWindow : function (url) {
var options = [
"dialog=yes", "scrollbars=no", "status=no",
"width=" + this.piskelController.getWidth() * this.piskelController.getFrameCount(),
"height=" + this.piskelController.getHeight()
].join(",");
window.open(url, "piskel-export", options);
}
};
})();

View File

@ -0,0 +1,67 @@
(function () {
var ns = $.namespace("pskl.controller");
ns.AnimatedPreviewController = function (piskelController, container, dpi) {
this.piskelController = piskelController;
this.container = container;
this.elapsedTime = 0;
this.currentIndex = 0;
this.fps = parseInt($("#preview-fps")[0].value, 10);
var renderingOptions = {
"dpi": this.calculateDPI_()
};
this.renderer = new pskl.rendering.FrameRenderer(this.container, renderingOptions);
$.subscribe(Events.FRAME_SIZE_CHANGED, this.updateDPI_.bind(this));
};
ns.AnimatedPreviewController.prototype.init = function () {
// the oninput event won't work on IE10 unfortunately, but at least will provide a
// consistent behavior across all other browsers that support the input type range
// see https://bugzilla.mozilla.org/show_bug.cgi?id=853670
$("#preview-fps")[0].addEventListener('change', this.onFPSSliderChange.bind(this));
};
ns.AnimatedPreviewController.prototype.onFPSSliderChange = function (evt) {
this.setFPS(parseInt($("#preview-fps")[0].value, 10));
};
ns.AnimatedPreviewController.prototype.setFPS = function (fps) {
this.fps = fps;
$("#preview-fps").val(this.fps);
$("#display-fps").html(this.fps + " FPS");
};
ns.AnimatedPreviewController.prototype.render = function (delta) {
this.elapsedTime += delta;
var index = Math.floor(this.elapsedTime / (1000/this.fps));
if (index != this.currentIndex) {
this.currentIndex = index;
if (!this.piskelController.hasFrameAt(this.currentIndex)) {
this.currentIndex = 0;
this.elapsedTime = 0;
}
this.renderer.render(this.piskelController.getFrameAt(this.currentIndex));
}
};
/**
* Calculate the preview DPI depending on the framesheet size
*/
ns.AnimatedPreviewController.prototype.calculateDPI_ = function () {
var previewSize = 200,
framePixelHeight = this.piskelController.getCurrentFrame().getHeight(),
framePixelWidth = this.piskelController.getCurrentFrame().getWidth();
// TODO (julz) : should have a utility to get a Size from framesheet easily (what about empty framesheets though ?)
//return pskl.PixelUtils.calculateDPIForContainer($(".preview-container"), framePixelHeight, framePixelWidth);
return pskl.PixelUtils.calculateDPI(previewSize, previewSize, framePixelHeight, framePixelWidth);
};
ns.AnimatedPreviewController.prototype.updateDPI_ = function () {
this.dpi = this.calculateDPI_();
this.renderer.updateDPI(this.dpi);
};
})();

View File

@ -0,0 +1,354 @@
(function () {
var ns = $.namespace("pskl.controller");
ns.DrawingController = function (piskelController, container) {
/**
* @public
*/
this.piskelController = piskelController;
/**
* @public
*/
this.overlayFrame = pskl.model.Frame.createEmptyFromFrame(piskelController.getCurrentFrame());
/**
* @private
*/
this.container = container;
// TODO(vincz): Store user prefs in a localstorage string ?
var renderingOptions = {
"dpi": this.calculateDPI_(),
"supportGridRendering" : true
};
this.overlayRenderer = new pskl.rendering.FrameRenderer(this.container, renderingOptions, "canvas-overlay");
this.renderer = new pskl.rendering.FrameRenderer(this.container, renderingOptions, "drawing-canvas");
this.layersDownRenderer = new pskl.rendering.FrameRenderer(this.container, renderingOptions, "layers-canvas layers-down-canvas");
this.layersUpRenderer = new pskl.rendering.FrameRenderer(this.container, renderingOptions, "layers-canvas layers-up-canvas");
// State of drawing controller:
this.isClicked = false;
this.isRightClicked = false;
this.previousMousemoveTime = 0;
this.currentToolBehavior = null;
this.primaryColor = Constants.DEFAULT_PEN_COLOR;
this.secondaryColor = Constants.TRANSPARENT_COLOR;
};
ns.DrawingController.prototype.init = function () {
// this.render();
this.initMouseBehavior();
$.subscribe(Events.TOOL_SELECTED, $.proxy(function(evt, toolBehavior) {
console.log("Tool selected: ", toolBehavior);
this.currentToolBehavior = toolBehavior;
this.overlayFrame.clear();
}, this));
/**
* TODO(grosbouddha): Primary/secondary color state are kept in this general controller.
* Find a better place to store that. Perhaps PaletteController?
*/
$.subscribe(Events.PRIMARY_COLOR_SELECTED, $.proxy(function(evt, color) {
console.log("Primary color selected: ", color);
this.primaryColor = color;
$.publish(Events.PRIMARY_COLOR_UPDATED, [color]);
}, this));
$.subscribe(Events.SECONDARY_COLOR_SELECTED, $.proxy(function(evt, color) {
console.log("Secondary color selected: ", color);
this.secondaryColor = color;
$.publish(Events.SECONDARY_COLOR_UPDATED, [color]);
}, this));
$(window).resize($.proxy(this.startDPIUpdateTimer_, this));
$.subscribe(Events.USER_SETTINGS_CHANGED, $.proxy(this.onUserSettingsChange_, this));
$.subscribe(Events.FRAME_SIZE_CHANGED, $.proxy(this.updateDPI_, this));
this.updateDPI_();
};
ns.DrawingController.prototype.initMouseBehavior = function() {
var body = $('body');
this.container.mousedown($.proxy(this.onMousedown_, this));
this.container.mousemove($.proxy(this.onMousemove_, this));
body.mouseup($.proxy(this.onMouseup_, this));
// Deactivate right click:
body.contextmenu(this.onCanvasContextMenu_);
};
ns.DrawingController.prototype.startDPIUpdateTimer_ = function () {
if (this.dpiUpdateTimer) {
window.clearInterval(this.dpiUpdateTimer);
}
this.dpiUpdateTimer = window.setTimeout($.proxy(this.updateDPI_, this), 200);
},
/**
* @private
*/
ns.DrawingController.prototype.onUserSettingsChange_ = function (evt, settingsName, settingsValue) {
if(settingsName == pskl.UserSettings.SHOW_GRID) {
this.updateDPI_();
}
},
/**
* @private
*/
ns.DrawingController.prototype.onMousedown_ = function (event) {
this.isClicked = true;
if(event.button == 2) { // right click
this.isRightClicked = true;
$.publish(Events.CANVAS_RIGHT_CLICKED);
}
var coords = this.getSpriteCoordinates(event);
this.currentToolBehavior.applyToolAt(
coords.col, coords.row,
this.getCurrentColor_(),
this.piskelController.getCurrentFrame(),
this.overlayFrame,
this.wrapEvtInfo_(event)
);
$.publish(Events.LOCALSTORAGE_REQUEST);
};
/**
* @private
*/
ns.DrawingController.prototype.onMousemove_ = function (event) {
var currentTime = new Date().getTime();
// Throttling of the mousemove event:
if ((currentTime - this.previousMousemoveTime) > 40 ) {
var coords = this.getSpriteCoordinates(event);
if (this.isClicked) {
this.currentToolBehavior.moveToolAt(
coords.col, coords.row,
this.getCurrentColor_(),
this.piskelController.getCurrentFrame(),
this.overlayFrame,
this.wrapEvtInfo_(event)
);
// TODO(vincz): Find a way to move that to the model instead of being at the interaction level.
// Eg when drawing, it may make sense to have it here. However for a non drawing tool,
// you don't need to draw anything when mousemoving and you request useless localStorage.
$.publish(Events.LOCALSTORAGE_REQUEST);
} else {
this.currentToolBehavior.moveUnactiveToolAt(
coords.col, coords.row,
this.getCurrentColor_(),
this.piskelController.getCurrentFrame(),
this.overlayFrame,
this.wrapEvtInfo_(event)
);
}
this.previousMousemoveTime = currentTime;
}
};
/**
* @private
*/
ns.DrawingController.prototype.onMouseup_ = function (event) {
if(this.isClicked || this.isRightClicked) {
// A mouse button was clicked on the drawing canvas before this mouseup event,
// the user was probably drawing on the canvas.
// Note: The mousemove movement (and the mouseup) may end up outside
// of the drawing canvas.
this.isClicked = false;
this.isRightClicked = false;
var coords = this.getSpriteCoordinates(event);
//console.log("mousemove: col: " + spriteCoordinate.col + " - row: " + spriteCoordinate.row);
this.currentToolBehavior.releaseToolAt(
coords.col, coords.row,
this.getCurrentColor_(),
this.piskelController.getCurrentFrame(),
this.overlayFrame,
this.wrapEvtInfo_(event)
);
$.publish(Events.TOOL_RELEASED);
}
};
/**
* @private
*/
ns.DrawingController.prototype.wrapEvtInfo_ = function (event) {
var evtInfo = {};
if (event.button === 0) {
evtInfo.button = Constants.LEFT_BUTTON;
} else if (event.button == 2) {
evtInfo.button = Constants.RIGHT_BUTTON;
}
return evtInfo;
};
/**
* @private
*/
ns.DrawingController.prototype.getRelativeCoordinates = function (clientX, clientY) {
var canvasPageOffset = this.container.offset();
return {
x : clientX - canvasPageOffset.left,
y : clientY - canvasPageOffset.top
};
};
/**
* @private
*/
ns.DrawingController.prototype.getSpriteCoordinates = function(event) {
var coords = this.getRelativeCoordinates(event.clientX, event.clientY);
return this.renderer.convertPixelCoordinatesIntoSpriteCoordinate(coords);
};
/**
* @private
*/
ns.DrawingController.prototype.getCurrentColor_ = function () {
if(this.isRightClicked) {
return this.secondaryColor;
} else {
return this.primaryColor;
}
};
/**
* @private
*/
ns.DrawingController.prototype.onCanvasContextMenu_ = function (event) {
if ($(event.target).closest('#drawing-canvas-container').length) {
// Deactivate right click on drawing canvas only.
event.preventDefault();
event.stopPropagation();
event.cancelBubble = true;
return false;
}
};
ns.DrawingController.prototype.render = function () {
this.renderLayers();
this.renderFrame();
this.renderOverlay();
};
ns.DrawingController.prototype.renderFrame = function () {
var frame = this.piskelController.getCurrentFrame();
var serializedFrame = frame.serialize();
if (this.serializedFrame != serializedFrame) {
if (!frame.isSameSize(this.overlayFrame)) {
this.overlayFrame = pskl.model.Frame.createEmptyFromFrame(frame);
}
this.serializedFrame = serializedFrame;
this.renderer.render(frame);
}
};
ns.DrawingController.prototype.renderOverlay = function () {
var serializedOverlay = this.overlayFrame.serialize();
if (this.serializedOverlay != serializedOverlay) {
this.serializedOverlay = serializedOverlay;
this.overlayRenderer.render(this.overlayFrame);
}
};
ns.DrawingController.prototype.renderLayers = function () {
var layers = this.piskelController.getLayers();
var currentFrameIndex = this.piskelController.currentFrameIndex;
var currentLayerIndex = this.piskelController.currentLayerIndex;
var serialized = [currentFrameIndex, this.piskelController.currentLayerIndex, layers.length].join("-");
if (this.serializedLayerFrame != serialized) {
this.layersUpRenderer.clear();
this.layersDownRenderer.clear();
var downLayers = layers.slice(0, currentLayerIndex);
var downFrame = this.getFrameForLayersAt_(currentFrameIndex, downLayers);
this.layersDownRenderer.render(downFrame);
if (currentLayerIndex + 1 < layers.length) {
var upLayers = layers.slice(currentLayerIndex + 1, layers.length);
var upFrame = this.getFrameForLayersAt_(currentFrameIndex, upLayers);
this.layersUpRenderer.render(upFrame);
}
this.serializedLayerFrame = serialized;
}
};
ns.DrawingController.prototype.getFrameForLayersAt_ = function (frameIndex, layers) {
var frames = layers.map(function (l) {
return l.getFrameAt(frameIndex);
});
return pskl.utils.FrameUtils.merge(frames);
};
ns.DrawingController.prototype.forceRendering_ = function () {
this.serializedFrame = this.serializedOverlay = null;
};
/**
* @private
*/
ns.DrawingController.prototype.calculateDPI_ = function() {
var availableViewportHeight = $('#main-wrapper').height(),
leftSectionWidth = $('.left-column').outerWidth(true),
rightSectionWidth = $('.right-column').outerWidth(true),
availableViewportWidth = $('#main-wrapper').width() - leftSectionWidth - rightSectionWidth,
framePixelHeight = this.piskelController.getCurrentFrame().getHeight(),
framePixelWidth = this.piskelController.getCurrentFrame().getWidth();
if (pskl.UserSettings.get(pskl.UserSettings.SHOW_GRID)) {
availableViewportWidth = availableViewportWidth - (framePixelWidth * Constants.GRID_STROKE_WIDTH);
availableViewportHeight = availableViewportHeight - (framePixelHeight * Constants.GRID_STROKE_WIDTH);
}
var dpi = pskl.PixelUtils.calculateDPI(
availableViewportHeight, availableViewportWidth, framePixelHeight, framePixelWidth);
return dpi;
};
/**
* @private
*/
ns.DrawingController.prototype.updateDPI_ = function() {
var dpi = this.calculateDPI_();
this.overlayRenderer.updateDPI(dpi);
this.renderer.updateDPI(dpi);
this.layersUpRenderer.updateDPI(dpi);
this.layersDownRenderer.updateDPI(dpi);
this.serializedLayerFrame ="";
var currentFrameHeight = this.piskelController.getCurrentFrame().getHeight();
var canvasHeight = currentFrameHeight * dpi;
if (pskl.UserSettings.get(pskl.UserSettings.SHOW_GRID)) {
canvasHeight += Constants.GRID_STROKE_WIDTH * currentFrameHeight;
}
var verticalGapInPixel = Math.floor(($('#main-wrapper').height() - canvasHeight) / 2);
$('#column-wrapper').css({
'top': verticalGapInPixel + 'px',
'height': canvasHeight + 'px'
});
this.forceRendering_();
};
})();

View File

@ -0,0 +1,59 @@
(function () {
var ns = $.namespace('pskl.controller');
ns.LayersController = function (piskelController) {
this.piskelController = piskelController;
};
ns.LayersController.prototype.init = function () {
this.layerItemTemplate_ = pskl.utils.Template.get('layer-item-template');
this.rootEl = document.querySelectorAll('.layers-container')[0];
this.layersListEl = document.querySelectorAll('.layers-list')[0];
this.rootEl.addEventListener('click', this.onClick_.bind(this));
$.subscribe(Events.FRAMESHEET_RESET, this.renderLayerList_.bind(this));
this.renderLayerList_();
};
ns.LayersController.prototype.renderLayerList_ = function () {
this.layersListEl.innerHTML = '';
var layers = this.piskelController.getLayers();
layers.forEach(this.addLayerItem.bind(this));
};
ns.LayersController.prototype.addLayerItem = function (layer) {
var layerItemHtml = pskl.utils.Template.replace(this.layerItemTemplate_, {
layername : layer.getName()
});
var layerItem = pskl.utils.Template.createFromHTML(layerItemHtml);
if (this.piskelController.getCurrentLayer() === layer) {
layerItem.classList.add('current-layer-item');
}
this.layersListEl.insertBefore(layerItem, this.layersListEl.firstChild);
};
ns.LayersController.prototype.onClick_ = function (evt) {
var el = evt.target || evt.srcElement;
if (el.nodeName == 'BUTTON') {
this.onButtonClick_(el);
} else if (el.nodeName == 'LI') {
var layerName = el.getAttribute('data-layer-name');
this.piskelController.selectLayerByName(layerName);
}
};
ns.LayersController.prototype.onButtonClick_ = function (button) {
var action = button.getAttribute('data-action');
if (action == 'up') {
this.piskelController.moveLayerUp();
} else if (action == 'down') {
this.piskelController.moveLayerDown();
} else if (action == 'add') {
this.piskelController.createLayer();
} else if (action == 'delete') {
this.piskelController.removeCurrentLayer();
}
};
})();

View File

@ -0,0 +1,39 @@
(function () {
var ns = $.namespace("pskl.controller");
ns.NotificationController = function () {};
/**
* @public
*/
ns.NotificationController.prototype.init = function() {
$.subscribe(Events.SHOW_NOTIFICATION, $.proxy(this.displayMessage_, this));
$.subscribe(Events.HIDE_NOTIFICATION, $.proxy(this.removeMessage_, this));
};
/**
* @private
*/
ns.NotificationController.prototype.displayMessage_ = function (evt, messageInfo) {
var message = document.createElement('div');
message.id = "user-message";
message.className = "user-message";
message.innerHTML = messageInfo.content;
message.innerHTML = message.innerHTML + "<div title='Close message' class='close'>x</div>";
document.body.appendChild(message);
$(message).find(".close").click($.proxy(this.removeMessage_, this));
if(messageInfo.behavior) {
messageInfo.behavior(message);
}
};
/**
* @private
*/
ns.NotificationController.prototype.removeMessage_ = function (evt) {
var message = $("#user-message");
if (message.length) {
message.remove();
}
};
})();

View File

@ -0,0 +1,83 @@
(function () {
var ns = $.namespace("pskl.controller");
ns.PaletteController = function () {};
/**
* @public
*/
ns.PaletteController.prototype.init = function() {
var transparentColorPalette = $(".palette-color[data-color=TRANSPARENT]");
transparentColorPalette.mouseup($.proxy(this.onPaletteColorClick_, this));
$.subscribe(Events.PRIMARY_COLOR_UPDATED, $.proxy(function(evt, color) {
this.updateColorPicker_(color, $('#color-picker'));
}, this));
$.subscribe(Events.SECONDARY_COLOR_UPDATED, $.proxy(function(evt, color) {
this.updateColorPicker_(color, $('#secondary-color-picker'));
}, this));
// Initialize colorpickers:
var colorPicker = $('#color-picker');
colorPicker.val(Constants.DEFAULT_PEN_COLOR);
colorPicker.change({isPrimary : true}, $.proxy(this.onPickerChange_, this));
var secondaryColorPicker = $('#secondary-color-picker');
secondaryColorPicker.val(Constants.TRANSPARENT_COLOR);
secondaryColorPicker.change({isPrimary : false}, $.proxy(this.onPickerChange_, this));
window.jscolor.install();
};
/**
* @private
*/
ns.PaletteController.prototype.onPickerChange_ = function(evt, isPrimary) {
var inputPicker = $(evt.target);
if(evt.data.isPrimary) {
$.publish(Events.PRIMARY_COLOR_SELECTED, [inputPicker.val()]);
} else {
$.publish(Events.SECONDARY_COLOR_SELECTED, [inputPicker.val()]);
}
};
/**
* @private
*/
ns.PaletteController.prototype.onPaletteColorClick_ = function (event) {
var selectedColor = $(event.target).data("color");
var isLeftClick = (event.which == 1);
var isRightClick = (event.which == 3);
if (isLeftClick) {
$.publish(Events.PRIMARY_COLOR_SELECTED, [selectedColor]);
} else if (isRightClick) {
$.publish(Events.SECONDARY_COLOR_SELECTED, [selectedColor]);
}
};
/**
* @private
*/
ns.PaletteController.prototype.updateColorPicker_ = function (color, colorPicker) {
if (color == Constants.TRANSPARENT_COLOR) {
// We can set the current palette color to transparent.
// You can then combine this transparent color with an advanced
// tool for customized deletions.
// Eg: bucket + transparent: Delete a colored area
// Stroke + transparent: hollow out the equivalent of a stroke
// The colorpicker can't be set to a transparent state.
// We set its background to white and insert the
// string "TRANSPARENT" to mimic this state:
colorPicker[0].color.fromString("#fff");
colorPicker.val(Constants.TRANSPARENT_COLOR);
} else {
colorPicker[0].color.fromString(color);
}
};
})();

View File

@ -0,0 +1,185 @@
(function () {
var ns = $.namespace('pskl.controller');
ns.PiskelController = function (piskel) {
this.setPiskel(piskel);
};
ns.PiskelController.prototype.setPiskel = function (piskel) {
this.piskel = piskel;
this.currentLayerIndex = 0;
this.currentFrameIndex = 0;
this.layerIdCounter = 1;
$.publish(Events.FRAMESHEET_RESET);
$.publish(Events.FRAME_SIZE_CHANGED);
};
ns.PiskelController.prototype.getHeight = function () {
return this.piskel.getHeight();
};
ns.PiskelController.prototype.getWidth = function () {
return this.piskel.getWidth();
};
ns.PiskelController.prototype.getLayers = function () {
return this.piskel.getLayers();
};
ns.PiskelController.prototype.getCurrentLayer = function () {
return this.piskel.getLayerAt(this.currentLayerIndex);
};
ns.PiskelController.prototype.getCurrentFrame = function () {
var layer = this.getCurrentLayer();
return layer.getFrameAt(this.currentFrameIndex);
};
ns.PiskelController.prototype.getFrameAt = function (index) {
var frames = this.getLayers().map(function (l) {
return l.getFrameAt(index);
});
return pskl.utils.FrameUtils.merge(frames);
};
ns.PiskelController.prototype.hasFrameAt = function (index) {
return !!this.getCurrentLayer().getFrameAt(index);
};
// backward from framesheet
ns.PiskelController.prototype.getFrameByIndex =
ns.PiskelController.prototype.getMergedFrameAt;
ns.PiskelController.prototype.addEmptyFrame = function () {
var layers = this.getLayers();
layers.forEach(function (l) {
l.addFrame(this.createEmptyFrame_());
}.bind(this));
};
ns.PiskelController.prototype.createEmptyFrame_ = function () {
var w = this.piskel.getWidth(), h = this.piskel.getHeight();
return new pskl.model.Frame(w, h);
};
ns.PiskelController.prototype.removeFrameAt = function (index) {
var layers = this.getLayers();
layers.forEach(function (l) {
l.removeFrameAt(index);
});
// Current frame index is impacted if the removed frame was before the current frame
if (this.currentFrameIndex >= index) {
this.setCurrentFrameIndex(this.currentFrameIndex - 1);
}
$.publish(Events.FRAMESHEET_RESET);
};
ns.PiskelController.prototype.duplicateFrameAt = function (index) {
var layers = this.getLayers();
layers.forEach(function (l) {
l.duplicateFrameAt(index);
});
};
ns.PiskelController.prototype.moveFrame = function (fromIndex, toIndex) {
var layers = this.getLayers();
layers.forEach(function (l) {
l.moveFrame(fromIndex, toIndex);
});
};
ns.PiskelController.prototype.getFrameCount = function () {
var layer = this.piskel.getLayerAt(0);
return layer.length();
};
ns.PiskelController.prototype.setCurrentFrameIndex = function (index) {
this.currentFrameIndex = index;
$.publish(Events.FRAMESHEET_RESET);
};
ns.PiskelController.prototype.setCurrentLayerIndex = function (index) {
this.currentLayerIndex = index;
$.publish(Events.FRAMESHEET_RESET);
};
ns.PiskelController.prototype.selectLayer = function (layer) {
var index = this.getLayers().indexOf(layer);
if (index != -1) {
this.setCurrentLayerIndex(index);
}
};
ns.PiskelController.prototype.selectLayerByName = function (name) {
if (this.hasLayerForName_(name)) {
var layer = this.piskel.getLayersByName(name)[0];
this.selectLayer(layer);
}
};
ns.PiskelController.prototype.generateLayerName_ = function () {
var name = "Layer " + this.layerIdCounter;
while (this.hasLayerForName_(name)) {
this.layerIdCounter++;
name = "Layer " + this.layerIdCounter;
}
return name;
};
ns.PiskelController.prototype.createLayer = function (name) {
if (!name) {
name = this.generateLayerName_();
}
if (!this.hasLayerForName_(name)) {
var layer = new pskl.model.Layer(name);
for (var i = 0 ; i < this.getFrameCount() ; i++) {
layer.addFrame(this.createEmptyFrame_());
}
this.piskel.addLayer(layer);
this.setCurrentLayerIndex(this.piskel.getLayers().length - 1);
} else {
throw 'Layer name should be unique';
}
};
ns.PiskelController.prototype.hasLayerForName_ = function (name) {
return this.piskel.getLayersByName(name).length > 0;
};
ns.PiskelController.prototype.moveLayerUp = function () {
var layer = this.getCurrentLayer();
this.piskel.moveLayerUp(layer);
this.selectLayer(layer);
};
ns.PiskelController.prototype.moveLayerDown = function () {
var layer = this.getCurrentLayer();
this.piskel.moveLayerDown(layer);
this.selectLayer(layer);
};
ns.PiskelController.prototype.removeCurrentLayer = function () {
if (this.getLayers().length > 1) {
var layer = this.getCurrentLayer();
this.piskel.removeLayer(layer);
this.setCurrentLayerIndex(0);
}
};
ns.PiskelController.prototype.serialize = function () {
return pskl.utils.Serializer.serializePiskel(this.piskel);
};
ns.PiskelController.prototype.deserialize = function (json) {
try {
var piskel = pskl.utils.Serializer.deserializePiskel(json);
this.setPiskel(piskel);
} catch (e) {
console.error('Failed to deserialize');
console.error(e.stack);
}
};
})();

View File

@ -0,0 +1,220 @@
(function () {
var ns = $.namespace("pskl.controller");
ns.PreviewFilmController = function (piskelController, container, dpi) {
this.piskelController = piskelController;
this.container = container;
this.dpi = this.calculateDPI_();
this.redrawFlag = true;
};
ns.PreviewFilmController.prototype.init = function() {
$.subscribe(Events.TOOL_RELEASED, this.flagForRedraw_.bind(this));
$.subscribe(Events.FRAMESHEET_RESET, this.flagForRedraw_.bind(this));
$.subscribe(Events.FRAMESHEET_RESET, this.refreshDPI_.bind(this));
$('#preview-list-scroller').scroll(this.updateScrollerOverflows.bind(this));
this.updateScrollerOverflows();
};
ns.PreviewFilmController.prototype.addFrame = function () {
this.piskelController.addEmptyFrame();
this.piskelController.setCurrentFrameIndex(this.piskelController.getFrameCount() - 1);
this.updateScrollerOverflows();
};
ns.PreviewFilmController.prototype.flagForRedraw_ = function () {
this.redrawFlag = true;
};
ns.PreviewFilmController.prototype.refreshDPI_ = function () {
this.dpi = this.calculateDPI_();
};
ns.PreviewFilmController.prototype.render = function () {
if (this.redrawFlag) {
// TODO(vincz): Full redraw on any drawing modification, optimize.
this.createPreviews_();
this.redrawFlag = false;
}
};
ns.PreviewFilmController.prototype.updateScrollerOverflows = function () {
var scroller = $('#preview-list-scroller');
var scrollerHeight = scroller.height();
var scrollTop = scroller.scrollTop();
var scrollerContentHeight = $('#preview-list').height();
var treshold = $('.top-overflow').height();
var overflowTop = false,
overflowBottom = false;
if (scrollerHeight < scrollerContentHeight) {
if (scrollTop > treshold) {
overflowTop = true;
}
var scrollBottom = (scrollerContentHeight - scrollTop) - scrollerHeight;
if (scrollBottom > treshold) {
overflowBottom = true;
}
}
var wrapper = $('#preview-list-wrapper');
wrapper.toggleClass('top-overflow-visible', overflowTop);
wrapper.toggleClass('bottom-overflow-visible', overflowBottom);
};
ns.PreviewFilmController.prototype.createPreviews_ = function () {
this.container.html("");
// Manually remove tooltips since mouseout events were shortcut by the DOM refresh:
$(".tooltip").remove();
var frameCount = this.piskelController.getFrameCount();
for (var i = 0, l = frameCount; i < l ; i++) {
this.container.append(this.createPreviewTile_(i));
}
// Append 'new empty frame' button
var newFrameButton = document.createElement("div");
newFrameButton.id = "add-frame-action";
newFrameButton.className = "add-frame-action";
newFrameButton.innerHTML = "<p class='label'>Add new frame</p>";
this.container.append(newFrameButton);
$(newFrameButton).click(this.addFrame.bind(this));
var needDragndropBehavior = (frameCount > 1);
if(needDragndropBehavior) {
this.initDragndropBehavior_();
}
this.updateScrollerOverflows();
};
/**
* @private
*/
ns.PreviewFilmController.prototype.initDragndropBehavior_ = function () {
$("#preview-list").sortable({
placeholder: "preview-tile-drop-proxy",
update: $.proxy(this.onUpdate_, this),
items: ".preview-tile"
});
$("#preview-list").disableSelection();
};
/**
* @private
*/
ns.PreviewFilmController.prototype.onUpdate_ = function( event, ui ) {
var originFrameId = parseInt(ui.item.data("tile-number"), 10);
var targetInsertionId = $('.preview-tile').index(ui.item);
this.piskelController.moveFrame(originFrameId, targetInsertionId);
this.piskelController.setCurrentFrameIndex(targetInsertionId);
// TODO(grosbouddha): move localstorage request to the model layer?
$.publish(Events.LOCALSTORAGE_REQUEST);
};
/**
* @private
* TODO(vincz): clean this giant rendering function & remove listeners.
*/
ns.PreviewFilmController.prototype.createPreviewTile_ = function(tileNumber) {
var currentFrame = this.piskelController.getCurrentLayer().getFrameAt(tileNumber);
var previewTileRoot = document.createElement("li");
var classname = "preview-tile";
previewTileRoot.setAttribute("data-tile-number", tileNumber);
if (this.piskelController.getCurrentFrame() == currentFrame) {
classname += " selected";
}
previewTileRoot.className = classname;
var canvasContainer = document.createElement("div");
canvasContainer.className = "canvas-container";
var canvasBackground = document.createElement("div");
canvasBackground.className = "canvas-background";
canvasContainer.appendChild(canvasBackground);
previewTileRoot.addEventListener('click', this.onPreviewClick_.bind(this, tileNumber));
var cloneFrameButton = document.createElement("button");
cloneFrameButton.setAttribute('rel', 'tooltip');
cloneFrameButton.setAttribute('data-placement', 'right');
cloneFrameButton.setAttribute('title', 'Duplicate this frame');
cloneFrameButton.className = "tile-overlay duplicate-frame-action";
previewTileRoot.appendChild(cloneFrameButton);
cloneFrameButton.addEventListener('click', this.onAddButtonClick_.bind(this, tileNumber));
// TODO(vincz): Eventually optimize this part by not recreating a FrameRenderer. Note that the real optim
// is to make this update function (#createPreviewTile) less aggressive.
var renderingOptions = {"dpi": this.dpi };
var currentFrameRenderer = new pskl.rendering.FrameRenderer($(canvasContainer), renderingOptions, "tile-view");
currentFrameRenderer.render(currentFrame);
previewTileRoot.appendChild(canvasContainer);
if(tileNumber > 0 || this.piskelController.getFrameCount() > 1) {
// Add 'remove frame' button.
var deleteButton = document.createElement("button");
deleteButton.setAttribute('rel', 'tooltip');
deleteButton.setAttribute('data-placement', 'right');
deleteButton.setAttribute('title', 'Delete this frame');
deleteButton.className = "tile-overlay delete-frame-action";
deleteButton.addEventListener('click', this.onDeleteButtonClick_.bind(this, tileNumber));
previewTileRoot.appendChild(deleteButton);
// Add 'dragndrop handle'.
var dndHandle = document.createElement("div");
dndHandle.className = "tile-overlay dnd-action";
previewTileRoot.appendChild(dndHandle);
}
var tileCount = document.createElement("div");
tileCount.className = "tile-overlay tile-count";
tileCount.innerHTML = tileNumber;
previewTileRoot.appendChild(tileCount);
return previewTileRoot;
};
ns.PreviewFilmController.prototype.onPreviewClick_ = function (index, evt) {
// has not class tile-action:
if(!evt.target.classList.contains('tile-overlay')) {
this.piskelController.setCurrentFrameIndex(index);
}
};
ns.PreviewFilmController.prototype.onDeleteButtonClick_ = function (index, evt) {
this.piskelController.removeFrameAt(index);
$.publish(Events.LOCALSTORAGE_REQUEST); // Should come from model
this.updateScrollerOverflows();
};
ns.PreviewFilmController.prototype.onAddButtonClick_ = function (index, evt) {
this.piskelController.duplicateFrameAt(index);
$.publish(Events.LOCALSTORAGE_REQUEST); // Should come from model
this.piskelController.setCurrentFrameIndex(index + 1);
this.updateScrollerOverflows();
};
/**
* Calculate the preview DPI depending on the piskel size
*/
ns.PreviewFilmController.prototype.calculateDPI_ = function () {
var curFrame = this.piskelController.getCurrentFrame(),
frameHeight = curFrame.getHeight(),
frameWidth = curFrame.getWidth(),
maxFrameDim = Math.max(frameWidth, frameHeight);
var previewHeight = Constants.PREVIEW_FILM_SIZE * frameHeight / maxFrameDim;
var previewWidth = Constants.PREVIEW_FILM_SIZE * frameWidth / maxFrameDim;
return pskl.PixelUtils.calculateDPI(previewHeight, previewWidth, frameHeight, frameWidth) || 1;
};
})();

View File

@ -0,0 +1,70 @@
(function () {
var ns = $.namespace("pskl.controller");
var settings = {
user : {
template : 'templates/settings-application.html',
controller : ns.settings.ApplicationSettingsController
},
gif : {
template : 'templates/settings-export-gif.html',
controller : ns.settings.GifExportController
}
};
var SEL_SETTING_CLS = 'has-expanded-drawer';
var EXP_DRAWER_CLS = 'expanded';
ns.SettingsController = function (piskelController) {
this.piskelController = piskelController;
this.drawerContainer = document.getElementById("drawer-container");
this.settingsContainer = $('[data-pskl-controller=settings]');
this.expanded = false;
this.currentSetting = null;
};
/**
* @public
*/
ns.SettingsController.prototype.init = function() {
// Expand drawer when clicking 'Settings' tab.
$('[data-setting]').click(function(evt) {
var el = evt.originalEvent.currentTarget;
var setting = el.getAttribute("data-setting");
if (this.currentSetting != setting) {
this.loadSetting(setting);
} else {
this.closeDrawer();
}
}.bind(this));
$('body').click(function (evt) {
var isInSettingsContainer = $.contains(this.settingsContainer.get(0), evt.target);
if (this.expanded && !isInSettingsContainer) {
this.closeDrawer();
}
}.bind(this));
};
ns.SettingsController.prototype.loadSetting = function (setting) {
this.drawerContainer.innerHTML = pskl.utils.Template.get(settings[setting].template);
(new settings[setting].controller(this.piskelController)).init();
this.settingsContainer.addClass(EXP_DRAWER_CLS);
$('.' + SEL_SETTING_CLS).removeClass(SEL_SETTING_CLS);
$('[data-setting='+setting+']').addClass(SEL_SETTING_CLS);
this.expanded = true;
this.currentSetting = setting;
};
ns.SettingsController.prototype.closeDrawer = function () {
this.settingsContainer.removeClass(EXP_DRAWER_CLS);
$('.' + SEL_SETTING_CLS).removeClass(SEL_SETTING_CLS);
this.expanded = false;
this.currentSetting = null;
};
})();

View File

@ -0,0 +1,107 @@
(function () {
var ns = $.namespace("pskl.controller");
ns.ToolController = function () {
this.toolInstances = {
"simplePen" : new pskl.drawingtools.SimplePen(),
"verticalMirrorPen" : new pskl.drawingtools.VerticalMirrorPen(),
"eraser" : new pskl.drawingtools.Eraser(),
"paintBucket" : new pskl.drawingtools.PaintBucket(),
"stroke" : new pskl.drawingtools.Stroke(),
"rectangle" : new pskl.drawingtools.Rectangle(),
"circle" : new pskl.drawingtools.Circle(),
"move" : new pskl.drawingtools.Move(),
"rectangleSelect" : new pskl.drawingtools.RectangleSelect(),
"shapeSelect" : new pskl.drawingtools.ShapeSelect(),
"colorPicker" : new pskl.drawingtools.ColorPicker()
};
this.currentSelectedTool = this.toolInstances.simplePen;
this.previousSelectedTool = this.toolInstances.simplePen;
};
/**
* @public
*/
ns.ToolController.prototype.init = function() {
this.createToolMarkup_();
// Initialize tool:
// Set SimplePen as default selected tool:
this.selectTool_(this.toolInstances.simplePen);
// Activate listener on tool panel:
$("#tool-section").click($.proxy(this.onToolIconClicked_, this));
};
/**
* @private
*/
ns.ToolController.prototype.activateToolOnStage_ = function(tool) {
var stage = $("body");
var previousSelectedToolClass = stage.data("selected-tool-class");
if(previousSelectedToolClass) {
stage.removeClass(previousSelectedToolClass);
}
stage.addClass(tool.toolId);
stage.data("selected-tool-class", tool.toolId);
};
/**
* @private
*/
ns.ToolController.prototype.selectTool_ = function(tool) {
console.log("Selecting Tool:" , this.currentSelectedTool);
this.currentSelectedTool = tool;
this.activateToolOnStage_(this.currentSelectedTool);
$.publish(Events.TOOL_SELECTED, [tool]);
};
/**
* @private
*/
ns.ToolController.prototype.onToolIconClicked_ = function(evt) {
var target = $(evt.target);
var clickedTool = target.closest(".tool-icon");
if(clickedTool.length) {
var toolId = clickedTool.data().toolId;
var tool = this.getToolById_(toolId);
if (tool) {
this.selectTool_(tool);
// Show tool as selected:
$('#tool-section .tool-icon.selected').removeClass('selected');
clickedTool.addClass('selected');
}
}
};
ns.ToolController.prototype.getToolById_ = function (toolId) {
for(var key in this.toolInstances) {
if (this.toolInstances[key].toolId == toolId) {
return this.toolInstances[key];
}
}
return null;
};
/**
* @private
*/
ns.ToolController.prototype.createToolMarkup_ = function() {
var currentTool, toolMarkup = '', extraClass;
// TODO(vincz): Tools rendering order is not enforced by the data stucture (this.toolInstances), fix that.
for (var toolKey in this.toolInstances) {
currentTool = this.toolInstances[toolKey];
extraClass = currentTool.toolId;
if (this.currentSelectedTool == currentTool) {
extraClass = extraClass + " selected";
}
toolMarkup += '<li rel="tooltip" data-placement="right" class="tool-icon ' + extraClass + '" data-tool-id="' + currentTool.toolId +
'" title="' + currentTool.helpText + '"></li>';
}
$('#tools-container').html(toolMarkup);
};
})();

View File

@ -0,0 +1,38 @@
(function () {
var ns = $.namespace("pskl.controller.settings");
ns.ApplicationSettingsController = function () {};
/**
* @public
*/
ns.ApplicationSettingsController.prototype.init = function() {
// Highlight selected background picker:
var backgroundClass = pskl.UserSettings.get(pskl.UserSettings.CANVAS_BACKGROUND);
$('#background-picker-wrapper')
.find('.background-picker[data-background-class=' + backgroundClass + ']')
.addClass('selected');
// Initial state for grid display:
var show_grid = pskl.UserSettings.get(pskl.UserSettings.SHOW_GRID);
$('#show-grid').prop('checked', show_grid);
// Handle grid display changes:
$('#show-grid').change($.proxy(function(evt) {
var checked = $('#show-grid').prop('checked');
pskl.UserSettings.set(pskl.UserSettings.SHOW_GRID, checked);
}, this));
// Handle canvas background changes:
$('#background-picker-wrapper').click(function(evt) {
var target = $(evt.target).closest('.background-picker');
if (target.length) {
var backgroundClass = target.data('background-class');
pskl.UserSettings.set(pskl.UserSettings.CANVAS_BACKGROUND, backgroundClass);
$('.background-picker').removeClass('selected');
target.addClass('selected');
}
});
};
})();

View File

@ -0,0 +1,110 @@
(function () {
var ns = $.namespace("pskl.controller.settings");
ns.GifExportController = function (piskelController) {
this.piskelController = piskelController;
};
ns.GifExportController.prototype.init = function () {
this.radioTemplate_ = pskl.utils.Template.get("export-gif-radio-template");
this.previewContainerEl = document.querySelectorAll(".export-gif-preview div")[0];
this.radioGroupEl = document.querySelectorAll(".gif-export-radio-group")[0];
this.uploadFormJQ = $("[name=gif-export-upload-form]");
this.uploadFormJQ.submit(this.upload.bind(this));
this.initRadioElements_();
};
ns.GifExportController.prototype.upload = function (evt) {
evt.originalEvent.preventDefault();
var selectedDpi = this.getSelectedDpi_(),
fps = pskl.app.animationController.fps,
dpi = selectedDpi;
this.renderAsImageDataAnimatedGIF(dpi, fps, function (imageData) {
this.updatePreview_(imageData);
this.previewContainerEl.classList.add("preview-upload-ongoing");
pskl.app.imageUploadService.upload(imageData, function (imageUrl) {
this.updatePreview_(imageUrl);
this.previewContainerEl.classList.remove("preview-upload-ongoing");
}.bind(this));
}.bind(this));
};
ns.GifExportController.prototype.updatePreview_ = function (src) {
this.previewContainerEl.innerHTML = "<img style='max-width:240px;' src='"+src+"'/>";
};
ns.GifExportController.prototype.getSelectedDpi_ = function () {
var radiosColl = this.uploadFormJQ.get(0).querySelectorAll("[name=gif-dpi]"),
radios = Array.prototype.slice.call(radiosColl,0);
var selectedRadios = radios.filter(function(radio) {return !!radio.checked;});
if (selectedRadios.length == 1) {
return selectedRadios[0].value;
} else {
throw "Unexpected error when retrieving selected dpi";
}
};
ns.GifExportController.prototype.initRadioElements_ = function () {
var dpis = [
[1],
[5],
[10,true], //default
[20],
];
for (var i = 0 ; i < dpis.length ; i++) {
var dpi = dpis[i];
var radio = this.createRadioForDpi_(dpi);
this.radioGroupEl.appendChild(radio);
}
};
ns.GifExportController.prototype.createRadioForDpi_ = function (dpi) {
var label = dpi[0]*this.piskelController.getWidth() + "x" + dpi[0]*this.piskelController.getHeight();
var value = dpi[0];
var radioHTML = pskl.utils.Template.replace(this.radioTemplate_, {value : value, label : label});
var radio = pskl.utils.Template.createFromHTML(radioHTML);
if (dpi[1]) {
radio.getElementsByTagName("input")[0].setAttribute("checked", "checked");
}
return radio;
};
ns.GifExportController.prototype.blobToBase64_ = function(blob, cb) {
var reader = new FileReader();
reader.onload = function() {
var dataUrl = reader.result;
cb(dataUrl);
};
reader.readAsDataURL(blob);
};
ns.GifExportController.prototype.renderAsImageDataAnimatedGIF = function(dpi, fps, cb) {
var gif = new window.GIF({
workers: 2,
quality: 10,
width: this.piskelController.getWidth()*dpi,
height: this.piskelController.getHeight()*dpi
});
for (var i = 0; i < this.piskelController.getFrameCount(); i++) {
var frame = this.piskelController.getFrameAt(i);
var renderer = new pskl.rendering.CanvasRenderer(frame, dpi);
gif.addFrame(renderer.render(), {
delay: 1000 / fps
});
}
gif.on('finished', function(blob) {
this.blobToBase64_(blob, cb);
}.bind(this));
gif.render();
};
})();

View File

@ -0,0 +1,72 @@
/*
* @provide pskl.drawingtools.BaseTool
*
* @require pskl.utils
*/
(function() {
var ns = $.namespace("pskl.drawingtools");
ns.BaseTool = function() {};
ns.BaseTool.prototype.applyToolAt = function(col, row, color, frame, overlay) {};
ns.BaseTool.prototype.moveToolAt = function(col, row, color, frame, overlay) {};
ns.BaseTool.prototype.moveUnactiveToolAt = function(col, row, color, frame, overlay) {
if (overlay.containsPixel(col, row)) {
if (!isNaN(this.highlightedPixelCol) &&
!isNaN(this.highlightedPixelRow) &&
(this.highlightedPixelRow != row ||
this.highlightedPixelCol != col)) {
// Clean the previously highlighted pixel:
overlay.clear();
}
// Show the current pixel targeted by the tool:
overlay.setPixel(col, row, Constants.TOOL_TARGET_HIGHLIGHT_COLOR);
this.highlightedPixelCol = col;
this.highlightedPixelRow = row;
}
};
ns.BaseTool.prototype.releaseToolAt = function(col, row, color, frame, overlay) {};
/**
* Bresenham line algorihtm: Get an array of pixels from
* start and end coordinates.
*
* http://en.wikipedia.org/wiki/Bresenham's_line_algorithm
* http://stackoverflow.com/questions/4672279/bresenham-algorithm-in-javascript
*
* @private
*/
ns.BaseTool.prototype.getLinePixels_ = function(x0, x1, y0, y1) {
var pixels = [];
var dx = Math.abs(x1-x0);
var dy = Math.abs(y1-y0);
var sx = (x0 < x1) ? 1 : -1;
var sy = (y0 < y1) ? 1 : -1;
var err = dx-dy;
while(true){
// Do what you need to for this
pixels.push({"col": x0, "row": y0});
if ((x0==x1) && (y0==y1)) break;
var e2 = 2*err;
if (e2>-dy){
err -= dy;
x0 += sx;
}
if (e2 < dx) {
err += dx;
y0 += sy;
}
}
return pixels;
};
})();

View File

@ -0,0 +1,85 @@
/*
* @provide pskl.drawingtools.Circle
*
* @require pskl.utils
*/
(function() {
var ns = $.namespace("pskl.drawingtools");
ns.Circle = function() {
this.toolId = "tool-circle";
this.helpText = "Circle tool";
// Circle's first point coordinates (set in applyToolAt)
this.startCol = null;
this.startRow = null;
};
pskl.utils.inherit(ns.Circle, ns.BaseTool);
/**
* @override
*/
ns.Circle.prototype.applyToolAt = function(col, row, color, frame, overlay) {
this.startCol = col;
this.startRow = row;
// Drawing the first point of the rectangle in the fake overlay canvas:
overlay.setPixel(col, row, color);
};
ns.Circle.prototype.moveToolAt = function(col, row, color, frame, overlay) {
overlay.clear();
if(color == Constants.TRANSPARENT_COLOR) {
color = Constants.SELECTION_TRANSPARENT_COLOR;
}
// draw in overlay
this.drawCircle_(col, row, color, overlay);
};
/**
* @override
*/
ns.Circle.prototype.releaseToolAt = function(col, row, color, frame, overlay) {
overlay.clear();
if(frame.containsPixel(col, row)) { // cancel if outside of canvas
// draw in frame to finalize
this.drawCircle_(col, row, color, frame);
}
};
ns.Circle.prototype.drawCircle_ = function (col, row, color, targetFrame) {
var circlePoints = this.getCirclePixels_(this.startCol, this.startRow, col, row);
for(var i = 0; i< circlePoints.length; i++) {
// Change model:
targetFrame.setPixel(circlePoints[i].col, circlePoints[i].row, color);
}
};
ns.Circle.prototype.getCirclePixels_ = function (x0, y0, x1, y1) {
var coords = pskl.PixelUtils.getOrderedRectangleCoordinates(x0, y0, x1, y1);
var xC = (coords.x0 + coords.x1)/2;
var yC = (coords.y0 + coords.y1)/2;
var rX = coords.x1 - xC;
var rY = coords.y1 - yC;
var pixels = [];
var x, y, angle;
for (x = coords.x0 ; x < coords.x1 ; x++) {
angle = Math.acos((x - xC)/rX);
y = Math.round(rY * Math.sin(angle) + yC);
pixels.push({"col": x, "row": y});
pixels.push({"col": 2*xC - x, "row": 2*yC - y});
}
for (y = coords.y0 ; y < coords.y1 ; y++) {
angle = Math.asin((y - yC)/rY);
x = Math.round(rX * Math.cos(angle) + xC);
pixels.push({"col": x, "row": y});
pixels.push({"col": 2*xC - x, "row": 2*yC - y});
}
return pixels;
};
})();

View File

@ -0,0 +1,29 @@
/*
* @provide pskl.drawingtools.ColorPicker
*
* @require pskl.utils
*/
(function() {
var ns = $.namespace("pskl.drawingtools");
ns.ColorPicker = function() {
this.toolId = "tool-colorpicker";
this.helpText = "Color picker";
};
pskl.utils.inherit(ns.ColorPicker, ns.BaseTool);
/**
* @override
*/
ns.ColorPicker.prototype.applyToolAt = function(col, row, color, frame, overlay, context) {
if (frame.containsPixel(col, row)) {
var sampledColor = frame.getPixel(col, row);
if (context.button == Constants.LEFT_BUTTON) {
$.publish(Events.PRIMARY_COLOR_SELECTED, [sampledColor]);
} else if (context.button == Constants.RIGHT_BUTTON) {
$.publish(Events.SECONDARY_COLOR_SELECTED, [sampledColor]);
}
}
};
})();

View File

@ -0,0 +1,23 @@
/*
* @provide pskl.drawingtools.Eraser
*
* @require Constants
* @require pskl.utils
*/
(function() {
var ns = $.namespace("pskl.drawingtools");
ns.Eraser = function() {
this.toolId = "tool-eraser";
this.helpText = "Eraser tool";
};
pskl.utils.inherit(ns.Eraser, ns.SimplePen);
/**
* @override
*/
ns.Eraser.prototype.applyToolAt = function(col, row, color, frame, overlay) {
this.superclass.applyToolAt.call(this, col, row, Constants.TRANSPARENT_COLOR, frame, overlay);
};
})();

View File

@ -0,0 +1,54 @@
/*
* @provide pskl.drawingtools.Move
*
* @require pskl.utils
*/
(function() {
var ns = $.namespace("pskl.drawingtools");
ns.Move = function() {
this.toolId = "tool-move";
this.helpText = "Move tool";
// Stroke's first point coordinates (set in applyToolAt)
this.startCol = null;
this.startRow = null;
};
pskl.utils.inherit(ns.Move, ns.BaseTool);
/**
* @override
*/
ns.Move.prototype.applyToolAt = function(col, row, color, frame, overlay) {
this.startCol = col;
this.startRow = row;
this.frameClone = frame.clone();
};
ns.Move.prototype.moveToolAt = function(col, row, color, frame, overlay) {
var colDiff = col - this.startCol, rowDiff = row - this.startRow;
this.shiftFrame(colDiff, rowDiff, frame, this.frameClone);
};
ns.Move.prototype.shiftFrame = function (colDiff, rowDiff, frame, reference) {
var color;
for (var col = 0 ; col < frame.getWidth() ; col++) {
for (var row = 0 ; row < frame.getHeight() ; row++) {
if (reference.containsPixel(col - colDiff, row - rowDiff)) {
color = reference.getPixel(col - colDiff, row - rowDiff);
} else {
color = Constants.TRANSPARENT_COLOR;
}
frame.setPixel(col, row, color);
}
}
};
/**
* @override
*/
ns.Move.prototype.releaseToolAt = function(col, row, color, frame, overlay) {
this.moveToolAt(col, row, color, frame, overlay);
};
})();

View File

@ -0,0 +1,36 @@
/*
* @provide pskl.drawingtools.PaintBucket
*
* @require pskl.utils
*/
(function() {
var ns = $.namespace("pskl.drawingtools");
ns.PaintBucket = function() {
this.toolId = "tool-paint-bucket";
this.helpText = "Paint bucket tool";
};
pskl.utils.inherit(ns.PaintBucket, ns.BaseTool);
/**
* @override
*/
ns.PaintBucket.prototype.applyToolAt = function(col, row, color, frame, overlay) {
pskl.PixelUtils.paintSimilarConnectedPixelsFromFrame(frame, col, row, color);
};
})();

View File

@ -0,0 +1,59 @@
/*
* @provide pskl.drawingtools.Rectangle
*
* @require pskl.utils
*/
(function() {
var ns = $.namespace("pskl.drawingtools");
ns.Rectangle = function() {
this.toolId = "tool-rectangle";
this.helpText = "Rectangle tool";
// Rectangle's first point coordinates (set in applyToolAt)
this.startCol = null;
this.startRow = null;
};
pskl.utils.inherit(ns.Rectangle, ns.BaseTool);
/**
* @override
*/
ns.Rectangle.prototype.applyToolAt = function(col, row, color, frame, overlay) {
this.startCol = col;
this.startRow = row;
// Drawing the first point of the rectangle in the fake overlay canvas:
overlay.setPixel(col, row, color);
};
ns.Rectangle.prototype.moveToolAt = function(col, row, color, frame, overlay) {
overlay.clear();
if(color == Constants.TRANSPARENT_COLOR) {
color = Constants.SELECTION_TRANSPARENT_COLOR;
}
// draw in overlay
this.drawRectangle_(col, row, color, overlay);
};
/**
* @override
*/
ns.Rectangle.prototype.releaseToolAt = function(col, row, color, frame, overlay) {
overlay.clear();
if(frame.containsPixel(col, row)) { // cancel if outside of canvas
// draw in frame to finalize
this.drawRectangle_(col, row, color, frame);
}
};
ns.Rectangle.prototype.drawRectangle_ = function (col, row, color, targetFrame) {
var strokePoints = pskl.PixelUtils.getBoundRectanglePixels(this.startCol, this.startRow, col, row);
for(var i = 0; i< strokePoints.length; i++) {
// Change model:
targetFrame.setPixel(strokePoints[i].col, strokePoints[i].row, color);
}
};
})();

View File

@ -0,0 +1,49 @@
/*
* @provide pskl.drawingtools.SimplePen
*
* @require pskl.utils
*/
(function() {
var ns = $.namespace("pskl.drawingtools");
ns.SimplePen = function() {
this.toolId = "tool-pen";
this.helpText = "Pen tool";
this.previousCol = null;
this.previousRow = null;
};
pskl.utils.inherit(ns.SimplePen, ns.BaseTool);
/**
* @override
*/
ns.SimplePen.prototype.applyToolAt = function(col, row, color, frame, overlay) {
if (frame.containsPixel(col, row)) {
frame.setPixel(col, row, color);
}
this.previousCol = col;
this.previousRow = row;
};
ns.SimplePen.prototype.moveToolAt = function(col, row, color, frame, overlay) {
if((Math.abs(col - this.previousCol) > 1) || (Math.abs(row - this.previousRow) > 1)) {
// The pen movement is too fast for the mousemove frequency, there is a gap between the
// current point and the previously drawn one.
// We fill the gap by calculating missing dots (simple linear interpolation) and draw them.
var interpolatedPixels = this.getLinePixels_(col, this.previousCol, row, this.previousRow);
for(var i=0, l=interpolatedPixels.length; i<l; i++) {
var coords = interpolatedPixels[i];
this.applyToolAt(coords.col, coords.row, color, frame, overlay);
}
}
else {
this.applyToolAt(col, row, color, frame, overlay);
}
this.previousCol = col;
this.previousRow = row;
};
})();

View File

@ -0,0 +1,80 @@
/*
* @provide pskl.drawingtools.Stroke
*
* @require pskl.utils
*/
(function() {
var ns = $.namespace("pskl.drawingtools");
ns.Stroke = function() {
this.toolId = "tool-stroke";
this.helpText = "Stroke tool";
// Stroke's first point coordinates (set in applyToolAt)
this.startCol = null;
this.startRow = null;
};
pskl.utils.inherit(ns.Stroke, ns.BaseTool);
/**
* @override
*/
ns.Stroke.prototype.applyToolAt = function(col, row, color, frame, overlay) {
this.startCol = col;
this.startRow = row;
// When drawing a stroke we don't change the model instantly, since the
// user can move his cursor to change the stroke direction and length
// dynamically. Instead we draw the (preview) stroke in a fake canvas that
// overlay the drawing canvas.
// We wait for the releaseToolAt callback to impact both the
// frame model and canvas rendering.
// The fake canvas where we will draw the preview of the stroke:
// Drawing the first point of the stroke in the fake overlay canvas:
overlay.setPixel(col, row, color);
};
ns.Stroke.prototype.moveToolAt = function(col, row, color, frame, overlay) {
overlay.clear();
// When the user moussemove (before releasing), we dynamically compute the
// pixel to draw the line and draw this line in the overlay canvas:
var strokePoints = this.getLinePixels_(this.startCol, col, this.startRow, row);
// Drawing current stroke:
for(var i = 0; i< strokePoints.length; i++) {
if(color == Constants.TRANSPARENT_COLOR) {
// When mousemoving the stroke tool, we draw in the canvas overlay above the drawing canvas.
// If the stroke color is transparent, we won't be
// able to see it during the movement.
// We set it to a semi-opaque white during the tool mousemove allowing to see colors below the stroke.
// When the stroke tool will be released, It will draw a transparent stroke,
// eg deleting the equivalent of a stroke.
color = Constants.SELECTION_TRANSPARENT_COLOR;
}
overlay.setPixel(strokePoints[i].col, strokePoints[i].row, color);
}
};
/**
* @override
*/
ns.Stroke.prototype.releaseToolAt = function(col, row, color, frame, overlay) {
// If the stroke tool is released outside of the canvas, we cancel the stroke:
// TODO: Mutualize this check in common method
if(frame.containsPixel(col, row)) {
// The user released the tool to draw a line. We will compute the pixel coordinate, impact
// the model and draw them in the drawing canvas (not the fake overlay anymore)
var strokePoints = this.getLinePixels_(this.startCol, col, this.startRow, row);
for(var i = 0; i< strokePoints.length; i++) {
// Change model:
frame.setPixel(strokePoints[i].col, strokePoints[i].row, color);
}
}
// For now, we are done with the stroke tool and don't need an overlay anymore:
overlay.clear();
};
})();

View File

@ -0,0 +1,46 @@
(function() {
var ns = $.namespace("pskl.drawingtools");
ns.VerticalMirrorPen = function() {
this.toolId = "tool-vertical-mirror-pen";
this.helpText = "vertical mirror pen tool";
this.swap = null;
this.mirroredPreviousCol = null;
this.mirroredPreviousRow = null;
};
pskl.utils.inherit(ns.VerticalMirrorPen, ns.SimplePen);
ns.VerticalMirrorPen.prototype.setMirrorContext = function() {
this.swap = this.previousCol;
this.previousCol = this.mirroredPreviousCol;
};
ns.VerticalMirrorPen.prototype.unsetMirrorContext = function() {
this.mirroredPreviousCol = this.previousCol;
this.previousCol = this.swap;
};
/**
* @override
*/
ns.VerticalMirrorPen.prototype.applyToolAt = function(col, row, color, frame, overlay) {
this.superclass.applyToolAt.call(this, col, row, color, frame, overlay);
var mirroredCol = this.getSymmetricCol_(col, frame);
this.mirroredPreviousCol = mirroredCol;
this.setMirrorContext();
this.superclass.applyToolAt.call(this, mirroredCol, row, color, frame, overlay);
this.unsetMirrorContext();
};
/**
* @private
*/
ns.VerticalMirrorPen.prototype.getSymmetricCol_ = function(col, frame) {
return frame.getWidth() - col - 1;
};
})();

View File

@ -0,0 +1,160 @@
/*
* @provide pskl.drawingtools.BaseSelect
*
* @require pskl.utils
*/
(function() {
var ns = $.namespace("pskl.drawingtools");
ns.BaseSelect = function() {
this.secondaryToolId = "tool-move";
this.BodyRoot = $('body');
// Select's first point coordinates (set in applyToolAt)
this.startCol = null;
this.startRow = null;
};
pskl.utils.inherit(ns.BaseSelect, ns.BaseTool);
/**
* @override
*/
ns.BaseSelect.prototype.applyToolAt = function(col, row, color, frame, overlay) {
this.startCol = col;
this.startRow = row;
this.lastCol = col;
this.lastRow = row;
// The select tool can be in two different state.
// If the inital click of the tool is not on a selection, we go in "select"
// mode to create a selection.
// If the initial click is on a previous selection, we go in "moveSelection"
// mode to allow to move the selection by drag'n dropping it.
if(overlay.getPixel(col, row) != Constants.SELECTION_TRANSPARENT_COLOR) {
this.mode = "select";
this.onSelectStart_(col, row, color, frame, overlay);
}
else {
this.mode = "moveSelection";
this.onSelectionDragStart_(col, row, color, frame, overlay);
}
};
/**
* @override
*/
ns.BaseSelect.prototype.moveToolAt = function(col, row, color, frame, overlay) {
if(this.mode == "select") {
this.onSelect_(col, row, color, frame, overlay);
}
else if(this.mode == "moveSelection") {
this.onSelectionDrag_(col, row, color, frame, overlay);
}
};
/**
* @override
*/
ns.BaseSelect.prototype.releaseToolAt = function(col, row, color, frame, overlay) {
if(this.mode == "select") {
this.onSelectEnd_(col, row, color, frame, overlay);
} else if(this.mode == "moveSelection") {
this.onSelectionDragEnd_(col, row, color, frame, overlay);
}
};
/**
* If we mouseover the selection draw inside the overlay frame, show the 'move' cursor
* instead of the 'select' one. It indicates that we can move the selection by dragndroping it.
* @override
*/
ns.BaseSelect.prototype.moveUnactiveToolAt = function(col, row, color, frame, overlay) {
if(overlay.getPixel(col, row) != Constants.SELECTION_TRANSPARENT_COLOR) {
// We're hovering the selection, show the move tool:
this.BodyRoot.addClass(this.toolId);
this.BodyRoot.removeClass(this.secondaryToolId);
} else {
// We're not hovering the selection, show create selection tool:
this.BodyRoot.addClass(this.secondaryToolId);
this.BodyRoot.removeClass(this.toolId);
}
};
/**
* For each pixel in the selection draw it in white transparent on the tool overlay
* @protected
*/
ns.BaseSelect.prototype.drawSelectionOnOverlay_ = function (selection, overlay) {
var pixels = selection.pixels;
for(var i=0, l=pixels.length; i<l; i++) {
overlay.setPixel(pixels[i].col, pixels[i].row, Constants.SELECTION_TRANSPARENT_COLOR);
}
};
/**
* Move the overlay frame filled with semi-transparent pixels that represent the selection.
* @private
*/
ns.BaseSelect.prototype.shiftOverlayFrame_ = function (colDiff, rowDiff, overlayFrame, reference) {
var color;
for (var col = 0 ; col < overlayFrame.getWidth() ; col++) {
for (var row = 0 ; row < overlayFrame.getHeight() ; row++) {
if (reference.containsPixel(col - colDiff, row - rowDiff)) {
color = reference.getPixel(col - colDiff, row - rowDiff);
} else {
color = Constants.TRANSPARENT_COLOR;
}
overlayFrame.setPixel(col, row, color);
}
}
};
// The list of callbacks to implement by specialized tools to implement the selection creation behavior.
/** @protected */
ns.BaseSelect.prototype.onSelectStart_ = function (col, row, color, frame, overlay) {};
/** @protected */
ns.BaseSelect.prototype.onSelect_ = function (col, row, color, frame, overlay) {};
/** @protected */
ns.BaseSelect.prototype.onSelectEnd_ = function (col, row, color, frame, overlay) {};
// The list of callbacks that define the drag'n drop behavior of the selection.
/** @private */
ns.BaseSelect.prototype.onSelectionDragStart_ = function (col, row, color, frame, overlay) {
// Since we will move the overlayFrame in which the current selection is rendered,
// we clone it to have a reference for the later shifting process.
this.overlayFrameReference = overlay.clone();
};
/** @private */
ns.BaseSelect.prototype.onSelectionDrag_ = function (col, row, color, frame, overlay) {
var deltaCol = col - this.lastCol;
var deltaRow = row - this.lastRow;
var colDiff = col - this.startCol, rowDiff = row - this.startRow;
// Shifting selection on overlay frame:
this.shiftOverlayFrame_(colDiff, rowDiff, overlay, this.overlayFrameReference);
// Update selection model:
$.publish(Events.SELECTION_MOVE_REQUEST, [deltaCol, deltaRow]);
this.lastCol = col;
this.lastRow = row;
};
/** @private */
ns.BaseSelect.prototype.onSelectionDragEnd_ = function (col, row, color, frame, overlay) {
this.onSelectionDrag_(col, row, color, frame, overlay);
};
})();

View File

@ -0,0 +1,52 @@
/*
* @provide pskl.drawingtools.RectangleSelect
*
* @require pskl.utils
*/
(function() {
var ns = $.namespace("pskl.drawingtools");
ns.RectangleSelect = function() {
this.toolId = "tool-rectangle-select";
this.helpText = "Rectangle selection tool";
ns.BaseSelect.call(this);
};
pskl.utils.inherit(ns.RectangleSelect, ns.BaseSelect);
/**
* @override
*/
ns.RectangleSelect.prototype.onSelectStart_ = function (col, row, color, frame, overlay) {
// Drawing the first point of the rectangle in the fake overlay canvas:
overlay.setPixel(col, row, color);
};
/**
* When creating the rectangle selection, we clear the current overlayFrame and
* redraw the current rectangle based on the orgin coordinate and
* the current mouse coordiinate in sprite.
* @override
*/
ns.RectangleSelect.prototype.onSelect_ = function (col, row, color, frame, overlay) {
overlay.clear();
if(this.startCol == col &&this.startRow == row) {
$.publish(Events.SELECTION_DISMISSED);
} else {
var selection = new pskl.selection.RectangularSelection(
this.startCol, this.startRow, col, row);
$.publish(Events.SELECTION_CREATED, [selection]);
this.drawSelectionOnOverlay_(selection, overlay);
}
};
/**
* @override
*/
ns.RectangleSelect.prototype.onSelectEnd_ = function (col, row, color, frame, overlay) {
this.onSelect_(col, row, color, frame, overlay);
};
})();

View File

@ -0,0 +1,36 @@
/*
* @provide pskl.drawingtools.ShapeSelect
*
* @require pskl.utils
*/
(function() {
var ns = $.namespace("pskl.drawingtools");
ns.ShapeSelect = function() {
this.toolId = "tool-shape-select";
this.helpText = "Shape selection tool";
ns.BaseSelect.call(this);
};
pskl.utils.inherit(ns.ShapeSelect, ns.BaseSelect);
/**
* For the shape select tool, you just need to click one time to create a selection.
* So we jsut need to implement onSelectStart_ (no need for onSelect_ & onSelectEnd_)
* @override
*/
ns.ShapeSelect.prototype.onSelectStart_ = function (col, row, color, frame, overlay) {
// Clean previous selection:
$.publish(Events.SELECTION_DISMISSED);
overlay.clear();
// From the pixel cliked, get shape using an algorithm similar to the paintbucket one:
var pixels = pskl.PixelUtils.getSimilarConnectedPixelsFromFrame(frame, col, row);
var selection = new pskl.selection.ShapeSelection(pixels);
$.publish(Events.SELECTION_CREATED, [selection]);
this.drawSelectionOnOverlay_(selection, overlay);
};
})();

275
layers/js/lib/bootstrap/bootstrap.js vendored Executable file
View File

@ -0,0 +1,275 @@
/* ===========================================================
* bootstrap-tooltip.js v2.1.1
* http://twitter.github.com/bootstrap/javascript.html#tooltips
* Inspired by the original jQuery.tipsy by Jason Frame
* ===========================================================
* Copyright 2012 Twitter, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* ========================================================== */
!function ($) {
"use strict"; // jshint ;_;
/* TOOLTIP PUBLIC CLASS DEFINITION
* =============================== */
var Tooltip = function (element, options) {
this.init('tooltip', element, options)
}
Tooltip.prototype = {
constructor: Tooltip
, init: function (type, element, options) {
var eventIn
, eventOut
this.type = type
this.$element = $(element)
this.options = this.getOptions(options)
this.enabled = true
if (this.options.trigger == 'click') {
this.$element.on('click.' + this.type, this.options.selector, $.proxy(this.toggle, this))
} else if (this.options.trigger != 'manual') {
eventIn = this.options.trigger == 'hover' ? 'mouseenter' : 'focus'
eventOut = this.options.trigger == 'hover' ? 'mouseleave' : 'blur'
this.$element.on(eventIn + '.' + this.type, this.options.selector, $.proxy(this.enter, this))
this.$element.on(eventOut + '.' + this.type, this.options.selector, $.proxy(this.leave, this))
}
this.options.selector ?
(this._options = $.extend({}, this.options, { trigger: 'manual', selector: '' })) :
this.fixTitle()
}
, getOptions: function (options) {
options = $.extend({}, $.fn[this.type].defaults, options, this.$element.data())
if (options.delay && typeof options.delay == 'number') {
options.delay = {
show: options.delay
, hide: options.delay
}
}
return options
}
, enter: function (e) {
var self = $(e.currentTarget)[this.type](this._options).data(this.type)
if (!self.options.delay || !self.options.delay.show) return self.show()
clearTimeout(this.timeout)
self.hoverState = 'in'
this.timeout = setTimeout(function() {
if (self.hoverState == 'in') self.show()
}, self.options.delay.show)
}
, leave: function (e) {
var self = $(e.currentTarget)[this.type](this._options).data(this.type)
if (this.timeout) clearTimeout(this.timeout)
if (!self.options.delay || !self.options.delay.hide) return self.hide()
self.hoverState = 'out'
this.timeout = setTimeout(function() {
if (self.hoverState == 'out') self.hide()
}, self.options.delay.hide)
}
, show: function () {
var $tip
, inside
, pos
, actualWidth
, actualHeight
, placement
, tp
if (this.hasContent() && this.enabled) {
$tip = this.tip()
this.setContent()
if (this.options.animation) {
$tip.addClass('fade')
}
placement = typeof this.options.placement == 'function' ?
this.options.placement.call(this, $tip[0], this.$element[0]) :
this.options.placement
inside = /in/.test(placement)
$tip
.remove()
.css({ top: 0, left: 0, display: 'block' })
.appendTo(inside ? this.$element : document.body)
pos = this.getPosition(inside)
actualWidth = $tip[0].offsetWidth
actualHeight = $tip[0].offsetHeight
switch (inside ? placement.split(' ')[1] : placement) {
case 'bottom':
tp = {top: pos.top + pos.height, left: pos.left + pos.width / 2 - actualWidth / 2}
break
case 'top':
tp = {top: pos.top - actualHeight, left: pos.left + pos.width / 2 - actualWidth / 2}
break
case 'left':
tp = {top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left - actualWidth}
break
case 'right':
tp = {top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left + pos.width}
break
}
$tip
.css(tp)
.addClass(placement)
.addClass('in')
}
}
, setContent: function () {
var $tip = this.tip()
, title = this.getTitle()
$tip.find('.tooltip-inner')[this.options.html ? 'html' : 'text'](title)
$tip.removeClass('fade in top bottom left right')
}
, hide: function () {
var that = this
, $tip = this.tip()
$tip.removeClass('in')
function removeWithAnimation() {
var timeout = setTimeout(function () {
$tip.off($.support.transition.end).remove()
}, 500)
$tip.one($.support.transition.end, function () {
clearTimeout(timeout)
$tip.remove()
})
}
$.support.transition && this.$tip.hasClass('fade') ?
removeWithAnimation() :
$tip.remove()
return this
}
, fixTitle: function () {
var $e = this.$element
if ($e.attr('title') || typeof($e.attr('data-original-title')) != 'string') {
$e.attr('data-original-title', $e.attr('title') || '').removeAttr('title')
}
}
, hasContent: function () {
return this.getTitle()
}
, getPosition: function (inside) {
return $.extend({}, (inside ? {top: 0, left: 0} : this.$element.offset()), {
width: this.$element[0].offsetWidth
, height: this.$element[0].offsetHeight
})
}
, getTitle: function () {
var title
, $e = this.$element
, o = this.options
title = $e.attr('data-original-title')
|| (typeof o.title == 'function' ? o.title.call($e[0]) : o.title)
return title
}
, tip: function () {
return this.$tip = this.$tip || $(this.options.template)
}
, validate: function () {
if (!this.$element[0].parentNode) {
this.hide()
this.$element = null
this.options = null
}
}
, enable: function () {
this.enabled = true
}
, disable: function () {
this.enabled = false
}
, toggleEnabled: function () {
this.enabled = !this.enabled
}
, toggle: function () {
this[this.tip().hasClass('in') ? 'hide' : 'show']()
}
, destroy: function () {
this.hide().$element.off('.' + this.type).removeData(this.type)
}
}
/* TOOLTIP PLUGIN DEFINITION
* ========================= */
$.fn.tooltip = function ( option ) {
return this.each(function () {
var $this = $(this)
, data = $this.data('tooltip')
, options = typeof option == 'object' && option
if (!data) $this.data('tooltip', (data = new Tooltip(this, options)))
if (typeof option == 'string') data[option]()
})
}
$.fn.tooltip.Constructor = Tooltip
$.fn.tooltip.defaults = {
animation: true
, placement: 'top'
, selector: false
, template: '<div class="tooltip"><div class="tooltip-arrow"></div><div class="tooltip-inner"></div></div>'
, trigger: 'hover'
, title: ''
, delay: 0
, html: true
}
}(window.jQuery);

View File

@ -0,0 +1 @@
Bootstrap custom build containing only the tooltip component

3
layers/js/lib/gif/gif.js Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

9227
layers/js/lib/jquery-1.8.0.js vendored Normal file

File diff suppressed because it is too large Load Diff

2252
layers/js/lib/jquery-ui-1.10.3.custom.js vendored Normal file

File diff suppressed because it is too large Load Diff

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 83 B

View File

@ -0,0 +1,12 @@
<html>
<head>
<title>jscolor demo</title>
</head>
<body>
<script type="text/javascript" src="jscolor.js"></script>
Click here: <input class="color" value="66ff00">
</body>
</html>

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

View File

@ -0,0 +1,927 @@
/**
* jscolor, JavaScript Color Picker
*
* @version 1.4.0
* @license GNU Lesser General Public License, http://www.gnu.org/copyleft/lesser.html
* @author Jan Odvarko, http://odvarko.cz
* @created 2008-06-15
* @updated 2012-07-06
* @link http://jscolor.com
*/
var jscolor = {
dir : '', // location of jscolor directory (leave empty to autodetect)
bindClass : 'color', // class name
binding : true, // automatic binding via <input class="...">
preloading : true, // use image preloading?
install : function() {
if (document.readyState === "complete") {
jscolor.init();
} else {
jscolor.addEvent(window, 'load', jscolor.init);
}
},
init : function() {
if(jscolor.binding) {
jscolor.bind();
}
if(jscolor.preloading) {
jscolor.preload();
}
},
getDir : function() {
return "/" + window.location.pathname.split("/")[1] + "/js/lib/jsColor_1_4_0/";
},
bind : function() {
var matchClass = new RegExp('(^|\\s)('+jscolor.bindClass+')\\s*(\\{[^}]*\\})?', 'i');
var e = document.getElementsByTagName('input');
for(var i=0; i<e.length; i+=1) {
var m;
if(!e[i].color && e[i].className && (m = e[i].className.match(matchClass))) {
var prop = {};
if(m[3]) {
try {
prop = (new Function ('return (' + m[3] + ')'))();
} catch(eInvalidProp) {}
}
e[i].color = new jscolor.color(e[i], prop);
}
}
},
preload : function() {
for(var fn in jscolor.imgRequire) {
if(jscolor.imgRequire.hasOwnProperty(fn)) {
jscolor.loadImage(fn);
}
}
},
images : {
pad : [ 181, 101 ],
sld : [ 16, 101 ],
cross : [ 15, 15 ],
arrow : [ 7, 11 ]
},
imgRequire : {},
imgLoaded : {},
requireImage : function(filename) {
jscolor.imgRequire[filename] = true;
},
loadImage : function(filename) {
if(!jscolor.imgLoaded[filename]) {
jscolor.imgLoaded[filename] = new Image();
jscolor.imgLoaded[filename].src = jscolor.getDir()+filename;
}
},
fetchElement : function(mixed) {
return typeof mixed === 'string' ? document.getElementById(mixed) : mixed;
},
addEvent : function(el, evnt, func) {
if(el.addEventListener) {
el.addEventListener(evnt, func, false);
} else if(el.attachEvent) {
el.attachEvent('on'+evnt, func);
}
},
fireEvent : function(el, evnt) {
if(!el) {
return;
}
if(document.createEvent) {
var ev = document.createEvent('HTMLEvents');
ev.initEvent(evnt, true, true);
el.dispatchEvent(ev);
} else if(document.createEventObject) {
var ev = document.createEventObject();
el.fireEvent('on'+evnt, ev);
} else if(el['on'+evnt]) { // alternatively use the traditional event model (IE5)
el['on'+evnt]();
}
},
getElementPos : function(e) {
var e1=e, e2=e;
var x=0, y=0;
if(e1.offsetParent) {
do {
x += e1.offsetLeft;
y += e1.offsetTop;
} while(e1 = e1.offsetParent);
}
while((e2 = e2.parentNode) && e2.nodeName.toUpperCase() !== 'BODY') {
x -= e2.scrollLeft;
y -= e2.scrollTop;
}
return [x, y];
},
getElementSize : function(e) {
return [e.offsetWidth, e.offsetHeight];
},
getRelMousePos : function(e) {
var x = 0, y = 0;
if (!e) { e = window.event; }
if (typeof e.offsetX === 'number') {
x = e.offsetX;
y = e.offsetY;
} else if (typeof e.layerX === 'number') {
x = e.layerX;
y = e.layerY;
}
return { x: x, y: y };
},
getViewPos : function() {
if(typeof window.pageYOffset === 'number') {
return [window.pageXOffset, window.pageYOffset];
} else if(document.body && (document.body.scrollLeft || document.body.scrollTop)) {
return [document.body.scrollLeft, document.body.scrollTop];
} else if(document.documentElement && (document.documentElement.scrollLeft || document.documentElement.scrollTop)) {
return [document.documentElement.scrollLeft, document.documentElement.scrollTop];
} else {
return [0, 0];
}
},
getViewSize : function() {
if(typeof window.innerWidth === 'number') {
return [window.innerWidth, window.innerHeight];
} else if(document.body && (document.body.clientWidth || document.body.clientHeight)) {
return [document.body.clientWidth, document.body.clientHeight];
} else if(document.documentElement && (document.documentElement.clientWidth || document.documentElement.clientHeight)) {
return [document.documentElement.clientWidth, document.documentElement.clientHeight];
} else {
return [0, 0];
}
},
URI : function(uri) { // See RFC3986
this.scheme = null;
this.authority = null;
this.path = '';
this.query = null;
this.fragment = null;
this.parse = function(uri) {
var m = uri.match(/^(([A-Za-z][0-9A-Za-z+.-]*)(:))?((\/\/)([^\/?#]*))?([^?#]*)((\?)([^#]*))?((#)(.*))?/);
this.scheme = m[3] ? m[2] : null;
this.authority = m[5] ? m[6] : null;
this.path = m[7];
this.query = m[9] ? m[10] : null;
this.fragment = m[12] ? m[13] : null;
return this;
};
this.toString = function() {
var result = '';
if(this.scheme !== null) { result = result + this.scheme + ':'; }
if(this.authority !== null) { result = result + '//' + this.authority; }
if(this.path !== null) { result = result + this.path; }
if(this.query !== null) { result = result + '?' + this.query; }
if(this.fragment !== null) { result = result + '#' + this.fragment; }
return result;
};
this.toAbsolute = function(base) {
var base = new jscolor.URI(base);
var r = this;
var t = new jscolor.URI;
if(base.scheme === null) { return false; }
if(r.scheme !== null && r.scheme.toLowerCase() === base.scheme.toLowerCase()) {
r.scheme = null;
}
if(r.scheme !== null) {
t.scheme = r.scheme;
t.authority = r.authority;
t.path = removeDotSegments(r.path);
t.query = r.query;
} else {
if(r.authority !== null) {
t.authority = r.authority;
t.path = removeDotSegments(r.path);
t.query = r.query;
} else {
if(r.path === '') {
t.path = base.path;
if(r.query !== null) {
t.query = r.query;
} else {
t.query = base.query;
}
} else {
if(r.path.substr(0,1) === '/') {
t.path = removeDotSegments(r.path);
} else {
if(base.authority !== null && base.path === '') {
t.path = '/'+r.path;
} else {
t.path = base.path.replace(/[^\/]+$/,'')+r.path;
}
t.path = removeDotSegments(t.path);
}
t.query = r.query;
}
t.authority = base.authority;
}
t.scheme = base.scheme;
}
t.fragment = r.fragment;
return t;
};
function removeDotSegments(path) {
var out = '';
while(path) {
if(path.substr(0,3)==='../' || path.substr(0,2)==='./') {
path = path.replace(/^\.+/,'').substr(1);
} else if(path.substr(0,3)==='/./' || path==='/.') {
path = '/'+path.substr(3);
} else if(path.substr(0,4)==='/../' || path==='/..') {
path = '/'+path.substr(4);
out = out.replace(/\/?[^\/]*$/, '');
} else if(path==='.' || path==='..') {
path = '';
} else {
var rm = path.match(/^\/?[^\/]*/)[0];
path = path.substr(rm.length);
out = out + rm;
}
}
return out;
}
if(uri) {
this.parse(uri);
}
},
/*
* Usage example:
* var myColor = new jscolor.color(myInputElement)
*/
color : function(target, prop) {
this.required = true; // refuse empty values?
this.adjust = true; // adjust value to uniform notation?
this.hash = false; // prefix color with # symbol?
this.caps = true; // uppercase?
this.slider = true; // show the value/saturation slider?
this.valueElement = target; // value holder
this.styleElement = target; // where to reflect current color
this.onImmediateChange = null; // onchange callback (can be either string or function)
this.hsv = [0, 0, 1]; // read-only 0-6, 0-1, 0-1
this.rgb = [1, 1, 1]; // read-only 0-1, 0-1, 0-1
this.minH = 0; // read-only 0-6
this.maxH = 6; // read-only 0-6
this.minS = 0; // read-only 0-1
this.maxS = 1; // read-only 0-1
this.minV = 0; // read-only 0-1
this.maxV = 1; // read-only 0-1
this.pickerOnfocus = true; // display picker on focus?
this.pickerMode = 'HSV'; // HSV | HVS
this.pickerPosition = 'bottom'; // left | right | top | bottom
this.pickerSmartPosition = true; // automatically adjust picker position when necessary
this.pickerButtonHeight = 20; // px
this.pickerClosable = false;
this.pickerCloseText = 'Close';
this.pickerButtonColor = 'ButtonText'; // px
this.pickerFace = 10; // px
this.pickerFaceColor = 'ThreeDFace'; // CSS color
this.pickerBorder = 1; // px
this.pickerBorderColor = 'ThreeDHighlight ThreeDShadow ThreeDShadow ThreeDHighlight'; // CSS color
this.pickerInset = 1; // px
this.pickerInsetColor = 'ThreeDShadow ThreeDHighlight ThreeDHighlight ThreeDShadow'; // CSS color
this.pickerZIndex = 10000;
for(var p in prop) {
if(prop.hasOwnProperty(p)) {
this[p] = prop[p];
}
}
this.hidePicker = function() {
if(isPickerOwner()) {
removePicker();
}
};
this.showPicker = function() {
if(!isPickerOwner()) {
var tp = jscolor.getElementPos(target); // target pos
var ts = jscolor.getElementSize(target); // target size
var vp = jscolor.getViewPos(); // view pos
var vs = jscolor.getViewSize(); // view size
var ps = getPickerDims(this); // picker size
var a, b, c;
switch(this.pickerPosition.toLowerCase()) {
case 'left': a=1; b=0; c=-1; break;
case 'right':a=1; b=0; c=1; break;
case 'top': a=0; b=1; c=-1; break;
default: a=0; b=1; c=1; break;
}
var l = (ts[b]+ps[b])/2;
// picker pos
if (!this.pickerSmartPosition) {
var pp = [
tp[a],
tp[b]+ts[b]-l+l*c
];
} else {
var pp = [
-vp[a]+tp[a]+ps[a] > vs[a] ?
(-vp[a]+tp[a]+ts[a]/2 > vs[a]/2 && tp[a]+ts[a]-ps[a] >= 0 ? tp[a]+ts[a]-ps[a] : tp[a]) :
tp[a],
-vp[b]+tp[b]+ts[b]+ps[b]-l+l*c > vs[b] ?
(-vp[b]+tp[b]+ts[b]/2 > vs[b]/2 && tp[b]+ts[b]-l-l*c >= 0 ? tp[b]+ts[b]-l-l*c : tp[b]+ts[b]-l+l*c) :
(tp[b]+ts[b]-l+l*c >= 0 ? tp[b]+ts[b]-l+l*c : tp[b]+ts[b]-l-l*c)
];
}
drawPicker(pp[a], pp[b]);
}
};
this.importColor = function() {
if(!valueElement) {
this.exportColor();
} else {
if(!this.adjust) {
if(!this.fromString(valueElement.value, leaveValue)) {
styleElement.style.backgroundImage = styleElement.jscStyle.backgroundImage;
styleElement.style.backgroundColor = styleElement.jscStyle.backgroundColor;
styleElement.style.color = styleElement.jscStyle.color;
this.exportColor(leaveValue | leaveStyle);
}
} else if(!this.required && /^\s*$/.test(valueElement.value)) {
valueElement.value = '';
styleElement.style.backgroundImage = styleElement.jscStyle.backgroundImage;
styleElement.style.backgroundColor = styleElement.jscStyle.backgroundColor;
styleElement.style.color = styleElement.jscStyle.color;
this.exportColor(leaveValue | leaveStyle);
} else if(this.fromString(valueElement.value)) {
// OK
} else {
this.exportColor();
}
}
};
this.exportColor = function(flags) {
if(!(flags & leaveValue) && valueElement) {
var value = this.toString();
if(this.caps) { value = value.toUpperCase(); }
if(this.hash) { value = '#'+value; }
valueElement.value = value;
}
if(!(flags & leaveStyle) && styleElement) {
styleElement.style.backgroundImage = "none";
styleElement.style.backgroundColor =
'#'+this.toString();
styleElement.style.color =
0.213 * this.rgb[0] +
0.715 * this.rgb[1] +
0.072 * this.rgb[2]
< 0.5 ? '#FFF' : '#000';
}
if(!(flags & leavePad) && isPickerOwner()) {
redrawPad();
}
if(!(flags & leaveSld) && isPickerOwner()) {
redrawSld();
}
};
this.fromHSV = function(h, s, v, flags) { // null = don't change
if(h !== null) { h = Math.max(0.0, this.minH, Math.min(6.0, this.maxH, h)); }
if(s !== null) { s = Math.max(0.0, this.minS, Math.min(1.0, this.maxS, s)); }
if(v !== null) { v = Math.max(0.0, this.minV, Math.min(1.0, this.maxV, v)); }
this.rgb = HSV_RGB(
h===null ? this.hsv[0] : (this.hsv[0]=h),
s===null ? this.hsv[1] : (this.hsv[1]=s),
v===null ? this.hsv[2] : (this.hsv[2]=v)
);
this.exportColor(flags);
};
this.fromRGB = function(r, g, b, flags) { // null = don't change
if(r !== null) { r = Math.max(0.0, Math.min(1.0, r)); }
if(g !== null) { g = Math.max(0.0, Math.min(1.0, g)); }
if(b !== null) { b = Math.max(0.0, Math.min(1.0, b)); }
var hsv = RGB_HSV(
r===null ? this.rgb[0] : r,
g===null ? this.rgb[1] : g,
b===null ? this.rgb[2] : b
);
if(hsv[0] !== null) {
this.hsv[0] = Math.max(0.0, this.minH, Math.min(6.0, this.maxH, hsv[0]));
}
if(hsv[2] !== 0) {
this.hsv[1] = hsv[1]===null ? null : Math.max(0.0, this.minS, Math.min(1.0, this.maxS, hsv[1]));
}
this.hsv[2] = hsv[2]===null ? null : Math.max(0.0, this.minV, Math.min(1.0, this.maxV, hsv[2]));
// update RGB according to final HSV, as some values might be trimmed
var rgb = HSV_RGB(this.hsv[0], this.hsv[1], this.hsv[2]);
this.rgb[0] = rgb[0];
this.rgb[1] = rgb[1];
this.rgb[2] = rgb[2];
this.exportColor(flags);
};
this.fromString = function(hex, flags) {
var m = hex.match(/^\W*([0-9A-F]{3}([0-9A-F]{3})?)\W*$/i);
if(!m) {
return false;
} else {
if(m[1].length === 6) { // 6-char notation
this.fromRGB(
parseInt(m[1].substr(0,2),16) / 255,
parseInt(m[1].substr(2,2),16) / 255,
parseInt(m[1].substr(4,2),16) / 255,
flags
);
} else { // 3-char notation
this.fromRGB(
parseInt(m[1].charAt(0)+m[1].charAt(0),16) / 255,
parseInt(m[1].charAt(1)+m[1].charAt(1),16) / 255,
parseInt(m[1].charAt(2)+m[1].charAt(2),16) / 255,
flags
);
}
return true;
}
};
this.toString = function() {
return (
(0x100 | Math.round(255*this.rgb[0])).toString(16).substr(1) +
(0x100 | Math.round(255*this.rgb[1])).toString(16).substr(1) +
(0x100 | Math.round(255*this.rgb[2])).toString(16).substr(1)
);
};
function RGB_HSV(r, g, b) {
var n = Math.min(Math.min(r,g),b);
var v = Math.max(Math.max(r,g),b);
var m = v - n;
if(m === 0) { return [ null, 0, v ]; }
var h = r===n ? 3+(b-g)/m : (g===n ? 5+(r-b)/m : 1+(g-r)/m);
return [ h===6?0:h, m/v, v ];
}
function HSV_RGB(h, s, v) {
if(h === null) { return [ v, v, v ]; }
var i = Math.floor(h);
var f = i%2 ? h-i : 1-(h-i);
var m = v * (1 - s);
var n = v * (1 - s*f);
switch(i) {
case 6:
case 0: return [v,n,m];
case 1: return [n,v,m];
case 2: return [m,v,n];
case 3: return [m,n,v];
case 4: return [n,m,v];
case 5: return [v,m,n];
}
}
function removePicker() {
delete jscolor.picker.owner;
document.getElementsByTagName('body')[0].removeChild(jscolor.picker.boxB);
}
function drawPicker(x, y) {
if(!jscolor.picker) {
jscolor.picker = {
box : document.createElement('div'),
boxB : document.createElement('div'),
pad : document.createElement('div'),
padB : document.createElement('div'),
padM : document.createElement('div'),
sld : document.createElement('div'),
sldB : document.createElement('div'),
sldM : document.createElement('div'),
btn : document.createElement('div'),
btnS : document.createElement('span'),
btnT : document.createTextNode(THIS.pickerCloseText)
};
for(var i=0,segSize=4; i<jscolor.images.sld[1]; i+=segSize) {
var seg = document.createElement('div');
seg.style.height = segSize+'px';
seg.style.fontSize = '1px';
seg.style.lineHeight = '0';
jscolor.picker.sld.appendChild(seg);
}
jscolor.picker.sldB.appendChild(jscolor.picker.sld);
jscolor.picker.box.appendChild(jscolor.picker.sldB);
jscolor.picker.box.appendChild(jscolor.picker.sldM);
jscolor.picker.padB.appendChild(jscolor.picker.pad);
jscolor.picker.box.appendChild(jscolor.picker.padB);
jscolor.picker.box.appendChild(jscolor.picker.padM);
jscolor.picker.btnS.appendChild(jscolor.picker.btnT);
jscolor.picker.btn.appendChild(jscolor.picker.btnS);
jscolor.picker.box.appendChild(jscolor.picker.btn);
jscolor.picker.boxB.appendChild(jscolor.picker.box);
}
var p = jscolor.picker;
// controls interaction
p.box.onmouseup =
p.box.onmouseout = function() { target.focus(); };
p.box.onmousedown = function() { abortBlur=true; };
p.box.onmousemove = function(e) {
if (holdPad || holdSld) {
holdPad && setPad(e);
holdSld && setSld(e);
if (document.selection) {
document.selection.empty();
} else if (window.getSelection) {
window.getSelection().removeAllRanges();
}
dispatchImmediateChange();
}
};
p.padM.onmouseup =
p.padM.onmouseout = function() { if(holdPad) { holdPad=false; jscolor.fireEvent(valueElement,'change'); } };
p.padM.onmousedown = function(e) {
// if the slider is at the bottom, move it up
switch(modeID) {
case 0: if (THIS.hsv[2] === 0) { THIS.fromHSV(null, null, 1.0); }; break;
case 1: if (THIS.hsv[1] === 0) { THIS.fromHSV(null, 1.0, null); }; break;
}
holdPad=true;
setPad(e);
dispatchImmediateChange();
};
p.sldM.onmouseup =
p.sldM.onmouseout = function() { if(holdSld) { holdSld=false; jscolor.fireEvent(valueElement,'change'); } };
p.sldM.onmousedown = function(e) {
holdSld=true;
setSld(e);
dispatchImmediateChange();
};
// picker
var dims = getPickerDims(THIS);
p.box.style.width = dims[0] + 'px';
p.box.style.height = dims[1] + 'px';
// picker border
p.boxB.style.position = 'absolute';
p.boxB.style.clear = 'both';
p.boxB.style.left = x+'px';
p.boxB.style.top = y+'px';
p.boxB.style.zIndex = THIS.pickerZIndex;
p.boxB.style.border = THIS.pickerBorder+'px solid';
p.boxB.style.borderColor = THIS.pickerBorderColor;
p.boxB.style.background = THIS.pickerFaceColor;
// pad image
p.pad.style.width = jscolor.images.pad[0]+'px';
p.pad.style.height = jscolor.images.pad[1]+'px';
// pad border
p.padB.style.position = 'absolute';
p.padB.style.left = THIS.pickerFace+'px';
p.padB.style.top = THIS.pickerFace+'px';
p.padB.style.border = THIS.pickerInset+'px solid';
p.padB.style.borderColor = THIS.pickerInsetColor;
// pad mouse area
p.padM.style.position = 'absolute';
p.padM.style.left = '0';
p.padM.style.top = '0';
p.padM.style.width = THIS.pickerFace + 2*THIS.pickerInset + jscolor.images.pad[0] + jscolor.images.arrow[0] + 'px';
p.padM.style.height = p.box.style.height;
p.padM.style.cursor = 'crosshair';
// slider image
p.sld.style.overflow = 'hidden';
p.sld.style.width = jscolor.images.sld[0]+'px';
p.sld.style.height = jscolor.images.sld[1]+'px';
// slider border
p.sldB.style.display = THIS.slider ? 'block' : 'none';
p.sldB.style.position = 'absolute';
p.sldB.style.right = THIS.pickerFace+'px';
p.sldB.style.top = THIS.pickerFace+'px';
p.sldB.style.border = THIS.pickerInset+'px solid';
p.sldB.style.borderColor = THIS.pickerInsetColor;
// slider mouse area
p.sldM.style.display = THIS.slider ? 'block' : 'none';
p.sldM.style.position = 'absolute';
p.sldM.style.right = '0';
p.sldM.style.top = '0';
p.sldM.style.width = jscolor.images.sld[0] + jscolor.images.arrow[0] + THIS.pickerFace + 2*THIS.pickerInset + 'px';
p.sldM.style.height = p.box.style.height;
try {
p.sldM.style.cursor = 'pointer';
} catch(eOldIE) {
p.sldM.style.cursor = 'hand';
}
// "close" button
function setBtnBorder() {
var insetColors = THIS.pickerInsetColor.split(/\s+/);
var pickerOutsetColor = insetColors.length < 2 ? insetColors[0] : insetColors[1] + ' ' + insetColors[0] + ' ' + insetColors[0] + ' ' + insetColors[1];
p.btn.style.borderColor = pickerOutsetColor;
}
p.btn.style.display = THIS.pickerClosable ? 'block' : 'none';
p.btn.style.position = 'absolute';
p.btn.style.left = THIS.pickerFace + 'px';
p.btn.style.bottom = THIS.pickerFace + 'px';
p.btn.style.padding = '0 15px';
p.btn.style.height = '18px';
p.btn.style.border = THIS.pickerInset + 'px solid';
setBtnBorder();
p.btn.style.color = THIS.pickerButtonColor;
p.btn.style.font = '12px sans-serif';
p.btn.style.textAlign = 'center';
try {
p.btn.style.cursor = 'pointer';
} catch(eOldIE) {
p.btn.style.cursor = 'hand';
}
p.btn.onmousedown = function () {
THIS.hidePicker();
};
p.btnS.style.lineHeight = p.btn.style.height;
// load images in optimal order
switch(modeID) {
case 0: var padImg = 'hs.png'; break;
case 1: var padImg = 'hv.png'; break;
}
p.padM.style.backgroundImage = "url('"+jscolor.getDir()+"cross.gif')";
p.padM.style.backgroundRepeat = "no-repeat";
p.sldM.style.backgroundImage = "url('"+jscolor.getDir()+"arrow.gif')";
p.sldM.style.backgroundRepeat = "no-repeat";
p.pad.style.backgroundImage = "url('"+jscolor.getDir()+padImg+"')";
p.pad.style.backgroundRepeat = "no-repeat";
p.pad.style.backgroundPosition = "0 0";
// place pointers
redrawPad();
redrawSld();
jscolor.picker.owner = THIS;
document.getElementsByTagName('body')[0].appendChild(p.boxB);
}
function getPickerDims(o) {
var dims = [
2*o.pickerInset + 2*o.pickerFace + jscolor.images.pad[0] +
(o.slider ? 2*o.pickerInset + 2*jscolor.images.arrow[0] + jscolor.images.sld[0] : 0),
o.pickerClosable ?
4*o.pickerInset + 3*o.pickerFace + jscolor.images.pad[1] + o.pickerButtonHeight :
2*o.pickerInset + 2*o.pickerFace + jscolor.images.pad[1]
];
return dims;
}
function redrawPad() {
// redraw the pad pointer
switch(modeID) {
case 0: var yComponent = 1; break;
case 1: var yComponent = 2; break;
}
var x = Math.round((THIS.hsv[0]/6) * (jscolor.images.pad[0]-1));
var y = Math.round((1-THIS.hsv[yComponent]) * (jscolor.images.pad[1]-1));
jscolor.picker.padM.style.backgroundPosition =
(THIS.pickerFace+THIS.pickerInset+x - Math.floor(jscolor.images.cross[0]/2)) + 'px ' +
(THIS.pickerFace+THIS.pickerInset+y - Math.floor(jscolor.images.cross[1]/2)) + 'px';
// redraw the slider image
var seg = jscolor.picker.sld.childNodes;
switch(modeID) {
case 0:
var rgb = HSV_RGB(THIS.hsv[0], THIS.hsv[1], 1);
for(var i=0; i<seg.length; i+=1) {
seg[i].style.backgroundColor = 'rgb('+
(rgb[0]*(1-i/seg.length)*100)+'%,'+
(rgb[1]*(1-i/seg.length)*100)+'%,'+
(rgb[2]*(1-i/seg.length)*100)+'%)';
}
break;
case 1:
var rgb, s, c = [ THIS.hsv[2], 0, 0 ];
var i = Math.floor(THIS.hsv[0]);
var f = i%2 ? THIS.hsv[0]-i : 1-(THIS.hsv[0]-i);
switch(i) {
case 6:
case 0: rgb=[0,1,2]; break;
case 1: rgb=[1,0,2]; break;
case 2: rgb=[2,0,1]; break;
case 3: rgb=[2,1,0]; break;
case 4: rgb=[1,2,0]; break;
case 5: rgb=[0,2,1]; break;
}
for(var i=0; i<seg.length; i+=1) {
s = 1 - 1/(seg.length-1)*i;
c[1] = c[0] * (1 - s*f);
c[2] = c[0] * (1 - s);
seg[i].style.backgroundColor = 'rgb('+
(c[rgb[0]]*100)+'%,'+
(c[rgb[1]]*100)+'%,'+
(c[rgb[2]]*100)+'%)';
}
break;
}
}
function redrawSld() {
// redraw the slider pointer
switch(modeID) {
case 0: var yComponent = 2; break;
case 1: var yComponent = 1; break;
}
var y = Math.round((1-THIS.hsv[yComponent]) * (jscolor.images.sld[1]-1));
jscolor.picker.sldM.style.backgroundPosition =
'0 ' + (THIS.pickerFace+THIS.pickerInset+y - Math.floor(jscolor.images.arrow[1]/2)) + 'px';
}
function isPickerOwner() {
return jscolor.picker && jscolor.picker.owner === THIS;
}
function blurTarget() {
if(valueElement === target) {
THIS.importColor();
}
if(THIS.pickerOnfocus) {
THIS.hidePicker();
}
}
function blurValue() {
if(valueElement !== target) {
THIS.importColor();
}
}
function setPad(e) {
var mpos = jscolor.getRelMousePos(e);
var x = mpos.x - THIS.pickerFace - THIS.pickerInset;
var y = mpos.y - THIS.pickerFace - THIS.pickerInset;
switch(modeID) {
case 0: THIS.fromHSV(x*(6/(jscolor.images.pad[0]-1)), 1 - y/(jscolor.images.pad[1]-1), null, leaveSld); break;
case 1: THIS.fromHSV(x*(6/(jscolor.images.pad[0]-1)), null, 1 - y/(jscolor.images.pad[1]-1), leaveSld); break;
}
}
function setSld(e) {
var mpos = jscolor.getRelMousePos(e);
var y = mpos.y - THIS.pickerFace - THIS.pickerInset;
switch(modeID) {
case 0: THIS.fromHSV(null, null, 1 - y/(jscolor.images.sld[1]-1), leavePad); break;
case 1: THIS.fromHSV(null, 1 - y/(jscolor.images.sld[1]-1), null, leavePad); break;
}
}
function dispatchImmediateChange() {
if (THIS.onImmediateChange) {
var callback;
if (typeof THIS.onImmediateChange === 'string') {
callback = new Function (THIS.onImmediateChange);
} else {
callback = THIS.onImmediateChange;
}
callback.call(THIS);
}
}
var THIS = this;
var modeID = this.pickerMode.toLowerCase()==='hvs' ? 1 : 0;
var abortBlur = false;
var
valueElement = jscolor.fetchElement(this.valueElement),
styleElement = jscolor.fetchElement(this.styleElement);
var
holdPad = false,
holdSld = false;
var
leaveValue = 1<<0,
leaveStyle = 1<<1,
leavePad = 1<<2,
leaveSld = 1<<3;
// target
jscolor.addEvent(target, 'focus', function() {
if(THIS.pickerOnfocus) { THIS.showPicker(); }
});
jscolor.addEvent(target, 'blur', function() {
if(!abortBlur) {
window.setTimeout(function(){ abortBlur || blurTarget(); abortBlur=false; }, 0);
} else {
abortBlur = false;
}
});
// valueElement
if(valueElement) {
var updateField = function() {
THIS.fromString(valueElement.value, leaveValue);
dispatchImmediateChange();
};
jscolor.addEvent(valueElement, 'keyup', updateField);
jscolor.addEvent(valueElement, 'input', updateField);
jscolor.addEvent(valueElement, 'blur', blurValue);
valueElement.setAttribute('autocomplete', 'off');
}
// styleElement
if(styleElement) {
styleElement.jscStyle = {
backgroundImage : styleElement.style.backgroundImage,
backgroundColor : styleElement.style.backgroundColor,
color : styleElement.style.color
};
}
// require images
switch(modeID) {
case 0: jscolor.requireImage('hs.png'); break;
case 1: jscolor.requireImage('hv.png'); break;
}
jscolor.requireImage('cross.gif');
jscolor.requireImage('arrow.gif');
this.importColor();
}
};

25
layers/js/lib/pubsub.js Normal file
View File

@ -0,0 +1,25 @@
/* jQuery Tiny Pub/Sub - v0.7 - 10/27/2011
* http://benalman.com/
* Copyright (c) 2011 "Cowboy" Ben Alman; Licensed MIT, GPL */
(function($) {
var o = $({});
$.subscribe = function() {
//console.log("SUBSCRIBE: " + arguments[0]);
o.on.apply(o, arguments);
};
$.unsubscribe = function() {
o.off.apply(o, arguments);
};
$.publish = function() {
//console.log("PUBLISH: " + arguments[0]);
o.trigger.apply(o, arguments);
};
}(jQuery));

139
layers/js/model/Frame.js Normal file
View File

@ -0,0 +1,139 @@
(function () {
var ns = $.namespace("pskl.model");
ns.Frame = function (width, height) {
if (width && height) {
this.width = width;
this.height = height;
this.pixels = ns.Frame.createEmptyPixelGrid_(width, height);
this.previousStates = [this.getPixels()];
this.stateIndex = 0;
} else {
throw 'Bad arguments in pskl.model.Frame constructor : ' + width + ', ' + height;
}
};
ns.Frame.fromPixelGrid = function (pixels) {
if (pixels.length && pixels[0].length) {
var w = pixels.length, h = pixels[0].length;
var frame = new pskl.model.Frame(w, h);
frame.setPixels(pixels);
return frame;
} else {
throw 'Bad arguments in pskl.model.Frame.fromPixelGrid : ' + pixels;
}
};
ns.Frame.createEmptyPixelGrid_ = function (width, height) {
var pixels = []; //new Array(width);
for (var columnIndex=0; columnIndex < width; columnIndex++) {
var columnArray = [];
for(var heightIndex = 0; heightIndex < height; heightIndex++) {
columnArray.push(Constants.TRANSPARENT_COLOR);
}
pixels[columnIndex] = columnArray;
}
return pixels;
};
ns.Frame.createEmptyFromFrame = function (frame) {
return new ns.Frame(frame.getWidth(), frame.getHeight());
};
ns.Frame.prototype.clone = function () {
var clone = new ns.Frame(this.width, this.height);
clone.setPixels(this.getPixels());
return clone;
};
/**
* Returns a copy of the pixels used by the frame
*/
ns.Frame.prototype.getPixels = function () {
return this.clonePixels_(this.pixels);
};
/**
* Copies the passed pixels into the frame.
*/
ns.Frame.prototype.setPixels = function (pixels) {
this.pixels = this.clonePixels_(pixels);
};
ns.Frame.prototype.clear = function () {
var pixels = ns.Frame.createEmptyPixelGrid_(this.getWidth(), this.getHeight());
this.setPixels(pixels);
};
/**
* Clone a set of pixels. Should be static utility method
* @private
*/
ns.Frame.prototype.clonePixels_ = function (pixels) {
var clonedPixels = [];
for (var col = 0 ; col < pixels.length ; col++) {
clonedPixels[col] = pixels[col].slice(0 , pixels[col].length);
}
return clonedPixels;
};
ns.Frame.prototype.serialize = function () {
return JSON.stringify(this.pixels);
};
ns.Frame.prototype.setPixel = function (col, row, color) {
this.pixels[col][row] = color;
};
ns.Frame.prototype.getPixel = function (col, row) {
return this.pixels[col][row];
};
ns.Frame.prototype.forEachPixel = function (callback) {
for (var col = 0 ; col < this.getWidth() ; col++) {
for (var row = 0 ; row < this.getHeight() ; row++) {
callback(this.getPixel(col, row), col, row);
}
}
};
ns.Frame.prototype.getWidth = function () {
return this.width;
};
ns.Frame.prototype.getHeight = function () {
return this.height;
};
ns.Frame.prototype.containsPixel = function (col, row) {
return col >= 0 && row >= 0 && col < this.pixels.length && row < this.pixels[0].length;
};
ns.Frame.prototype.saveState = function () {
// remove all states past current state
this.previousStates.length = this.stateIndex + 1;
// push new state
this.previousStates.push(this.getPixels());
// set the stateIndex to latest saved state
this.stateIndex = this.previousStates.length - 1;
};
ns.Frame.prototype.loadPreviousState = function () {
if (this.stateIndex > 0) {
this.stateIndex--;
this.setPixels(this.previousStates[this.stateIndex]);
}
};
ns.Frame.prototype.loadNextState = function () {
if (this.stateIndex < this.previousStates.length - 1) {
this.stateIndex++;
this.setPixels(this.previousStates[this.stateIndex]);
}
};
ns.Frame.prototype.isSameSize = function (otherFrame) {
return this.getHeight() == otherFrame.getHeight() && this.getWidth() == otherFrame.getWidth();
};
})();

View File

@ -0,0 +1,156 @@
(function () {
var ns = $.namespace("pskl.model");
ns.FrameSheet = function (height, width) {
this.width = width;
this.height = height;
this.frames = [];
this.currentFrameIndex = 0;
};
ns.FrameSheet.prototype.getHeight = function () {
return this.height;
};
ns.FrameSheet.prototype.getWidth = function () {
return this.width;
};
ns.FrameSheet.prototype.addEmptyFrame = function () {
this.addFrame(ns.Frame.createEmpty(this.width, this.height));
};
ns.FrameSheet.prototype.addFrame = function (frame) {
this.frames.push(frame);
};
ns.FrameSheet.prototype.getCurrentFrame = function () {
return this.frames[this.currentFrameIndex];
};
ns.FrameSheet.prototype.getFrameCount = function () {
return this.frames.length;
};
ns.FrameSheet.prototype.setCurrentFrameIndex = function (index) {
this.currentFrameIndex = index;
$.publish(Events.CURRENT_FRAME_SET, [this.getCurrentFrame()]);
$.publish(Events.FRAMESHEET_RESET); // Is it no to overkill to have this here ?
};
ns.FrameSheet.prototype.getUsedColors = function() {
var colors = {};
for (var frameIndex=0; frameIndex < this.frames.length; frameIndex++) {
var frame = this.frames[frameIndex];
for (var i = 0, width = frame.getWidth(); i < width ; i++) {
var line = frame[i];
for (var j = 0, height = frame.getHeight() ; j < height ; j++) {
var pixel = frame.getPixel(i, j);
colors[pixel] = pixel;
}
}
}
return colors;
};
// Could be used to pass around model using long GET param (good enough for simple models) and
// do some temporary locastorage
ns.FrameSheet.prototype.serialize = function() {
var serializedFrames = [];
for (var i = 0 ; i < this.frames.length ; i++) {
serializedFrames.push(this.frames[i].serialize());
}
return '[' + serializedFrames.join(",") + ']';
//return JSON.stringify(frames);
};
/**
* Load a framesheet from a model that might have been persisted in db / localstorage
* Overrides existing frames.
* @param {String} serialized
*/
ns.FrameSheet.prototype.deserialize = function (serialized) {
try {
this.load(JSON.parse(serialized));
} catch (e) {
throw "Could not load serialized framesheet : " + e.message;
}
};
/**
* Load a framesheet from a model that might have been persisted in db / localstorage
* Overrides existing frames.
* @param {String} serialized
*/
ns.FrameSheet.prototype.load = function (framesheet) {
this.frames = [];
for (var i = 0 ; i < framesheet.length ; i++) {
var frameCfg = framesheet[i];
this.addFrame(new ns.Frame(frameCfg));
}
if (this.hasFrameAtIndex(0)) {
this.height = this.getFrameByIndex(0).getHeight();
this.width = this.getFrameByIndex(0).getWidth();
this.setCurrentFrameIndex(0);
$.publish(Events.FRAME_SIZE_CHANGED);
}
$.publish(Events.FRAMESHEET_RESET);
};
ns.FrameSheet.prototype.hasFrameAtIndex = function(index) {
return (index >= 0 && index < this.frames.length);
};
ns.FrameSheet.prototype.getFrameByIndex = function(index) {
if (isNaN(index)) {
throw "Bad argument value for getFrameByIndex method: <" + index + ">";
}
if (!this.hasFrameAtIndex(index)) {
throw "Out of bound index for frameSheet object.";
}
return this.frames[index];
};
ns.FrameSheet.prototype.removeFrameByIndex = function(index) {
if(!this.hasFrameAtIndex(index)) {
throw "Out of bound index for frameSheet object.";
}
this.frames.splice(index, 1);
// Current frame index is impacted if the removed frame was before the current frame
if (this.currentFrameIndex >= index) {
this.setCurrentFrameIndex(this.currentFrameIndex - 1);
}
$.publish(Events.FRAMESHEET_RESET);
};
ns.FrameSheet.prototype.duplicateFrameByIndex = function(index) {
var frame = this.getFrameByIndex(index);
this.frames.splice(index + 1, 0, frame.clone());
};
ns.FrameSheet.prototype.moveFrame = function(originIndex, destinationIndex) {
this.frames.splice(destinationIndex, 0, this.frames.splice(originIndex, 1)[0]);
};
ns.FrameSheet.prototype.swapFrames = function(indexFrame1, indexFrame2) {
if(isNaN(indexFrame1) || isNaN(indexFrame1) ||
(!this.hasFrameAtIndex(indexFrame1) && !this.hasFrameAtIndex(indexFrame2))) {
throw "Bad indexes for swapFrames Framesheet function.";
}
if(indexFrame1 == indexFrame2) {
return;
}
else {
var swapFrame = this.frames[indexFrame1];
this.frames[indexFrame1] = this.frames[indexFrame2];
this.frames[indexFrame2] = swapFrame;
}
};
})();

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