2012-08-23 03:00:30 +04:00
|
|
|
(function ($) {
|
2012-08-28 02:53:27 +04:00
|
|
|
var frameSheet,
|
|
|
|
|
|
|
|
// Constants:
|
|
|
|
TRANSPARENT_COLOR = 'tc',
|
2012-08-29 12:57:56 +04:00
|
|
|
//TRANSPARENT_COLOR = 'pink'
|
2012-08-29 12:46:25 +04:00
|
|
|
DEFAULT_PEN_COLOR = '#000000',
|
2012-08-28 02:53:27 +04:00
|
|
|
|
|
|
|
// Configuration:
|
|
|
|
// Canvas size in pixel size (not dpi related)
|
|
|
|
framePixelWidth = 32,
|
|
|
|
framePixelHeight = 32,
|
|
|
|
|
|
|
|
// Scaling factors for a given frameSheet rendering:
|
|
|
|
// Main drawing area:
|
2012-08-29 01:30:55 +04:00
|
|
|
drawingCanvasDpi = 20,
|
2012-08-29 12:57:56 +04:00
|
|
|
|
2012-08-28 02:53:27 +04:00
|
|
|
// Canvas previous in the slideshow:
|
|
|
|
previewTileCanvasDpi = 4,
|
|
|
|
// Ainmated canvas preview:
|
|
|
|
previewAnimationCanvasDpi = 8,
|
|
|
|
|
|
|
|
// DOM references:
|
|
|
|
drawingAreaContainer,
|
|
|
|
drawingAreaCanvas,
|
|
|
|
previewCanvas,
|
|
|
|
|
|
|
|
// States:
|
|
|
|
isClicked = false,
|
|
|
|
isRightClicked = false,
|
|
|
|
activeFrameIndex = -1,
|
|
|
|
animIndex = 0,
|
2012-08-29 10:39:03 +04:00
|
|
|
penColor = DEFAULT_PEN_COLOR,
|
|
|
|
paletteColors = [];
|
2012-08-23 03:00:30 +04:00
|
|
|
|
|
|
|
|
|
|
|
var piskel = {
|
|
|
|
init : function () {
|
2012-08-28 02:53:27 +04:00
|
|
|
frameSheet = FrameSheetModel.getInstance(framePixelWidth, framePixelHeight);
|
|
|
|
frameSheet.addEmptyFrame();
|
|
|
|
this.setActiveFrame(0);
|
2012-08-29 12:46:25 +04:00
|
|
|
this.initPalette();
|
2012-08-28 02:53:27 +04:00
|
|
|
this.initDrawingArea();
|
|
|
|
this.initPreviewSlideshow();
|
|
|
|
this.initAnimationPreview();
|
2012-08-29 00:24:58 +04:00
|
|
|
this.initColorPicker();
|
2012-08-23 03:00:30 +04:00
|
|
|
},
|
|
|
|
|
2012-08-28 02:53:27 +04:00
|
|
|
setActiveFrame: function(index) {
|
|
|
|
activeFrameIndex = index;
|
2012-08-23 03:00:30 +04:00
|
|
|
},
|
|
|
|
|
2012-08-28 02:53:27 +04:00
|
|
|
setActiveFrameAndRedraw: function(index) {
|
|
|
|
this.setActiveFrame(index);
|
|
|
|
|
|
|
|
// Update drawing canvas:
|
|
|
|
this.drawFrameToCanvas(frameSheet.getFrameByIndex(this.getActiveFrameIndex()), drawingAreaCanvas, drawingCanvasDpi);
|
|
|
|
|
|
|
|
// Update slideshow:
|
|
|
|
this.createPreviews();
|
|
|
|
|
|
|
|
// Update animation preview:
|
|
|
|
animIndex = 0;
|
|
|
|
},
|
|
|
|
|
|
|
|
getActiveFrameIndex: function() {
|
|
|
|
if(-1 == activeFrameIndex) {
|
|
|
|
throw "Bad active frane initialization."
|
2012-08-23 03:00:30 +04:00
|
|
|
}
|
2012-08-28 02:53:27 +04:00
|
|
|
return activeFrameIndex;
|
|
|
|
},
|
|
|
|
|
2012-08-29 00:24:58 +04:00
|
|
|
initColorPicker: function() {
|
2012-08-29 10:39:03 +04:00
|
|
|
this.colorPicker = $('color-picker');
|
|
|
|
this.colorPicker.value = DEFAULT_PEN_COLOR;
|
|
|
|
this.colorPicker.addEventListener('change', this.onPickerChange.bind(this));
|
|
|
|
},
|
|
|
|
|
|
|
|
onPickerChange : function(evt) {
|
2012-08-29 12:46:25 +04:00
|
|
|
penColor = this.colorPicker.value;
|
2012-08-29 10:39:03 +04:00
|
|
|
},
|
|
|
|
|
|
|
|
initPalette : function (color) {
|
2012-08-29 12:46:25 +04:00
|
|
|
var pixels = frameSheet.getAllPixels();
|
|
|
|
this.paletteEl = $('palette');
|
|
|
|
for (var i = 0 ; i < pixels.length ; i++) {
|
|
|
|
this.addColorToPalette(pixels[i]);
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
addColorToPalette : function (color) {
|
|
|
|
if (color && color != TRANSPARENT_COLOR && paletteColors.indexOf(color) == -1) {
|
2012-08-29 10:39:03 +04:00
|
|
|
var colorEl = document.createElement("li");
|
|
|
|
colorEl.setAttribute("data-color", color);
|
2012-08-29 12:46:25 +04:00
|
|
|
colorEl.setAttribute("title", color);
|
|
|
|
colorEl.style.background = color;
|
|
|
|
this.paletteEl.appendChild(colorEl);
|
|
|
|
paletteColors.push(color);
|
2012-08-29 10:39:03 +04:00
|
|
|
}
|
2012-08-29 00:24:58 +04:00
|
|
|
},
|
|
|
|
|
2012-08-28 02:53:27 +04:00
|
|
|
initDrawingArea : function() {
|
|
|
|
drawingAreaContainer = $('drawing-canvas-container');
|
|
|
|
|
|
|
|
drawingAreaCanvas = document.createElement("canvas");
|
|
|
|
drawingAreaCanvas.className = 'canvas';
|
|
|
|
drawingAreaCanvas.setAttribute('width', '' + framePixelWidth * drawingCanvasDpi);
|
|
|
|
drawingAreaCanvas.setAttribute('height', '' + framePixelHeight * drawingCanvasDpi);
|
|
|
|
|
|
|
|
drawingAreaContainer.setAttribute('style',
|
|
|
|
'width:' + framePixelWidth * drawingCanvasDpi + 'px; height:' + framePixelHeight * drawingCanvasDpi + 'px;');
|
|
|
|
|
2012-08-29 10:39:03 +04:00
|
|
|
drawingAreaCanvas.setAttribute('oncontextmenu', 'piskel.onCanvasContextMenu(event)');
|
2012-08-28 02:53:27 +04:00
|
|
|
drawingAreaContainer.appendChild(drawingAreaCanvas);
|
|
|
|
|
|
|
|
var body = document.getElementsByTagName('body')[0];
|
|
|
|
body.setAttribute('onmouseup', 'piskel.onCanvasMouseup(arguments[0])');
|
2012-08-29 10:39:03 +04:00
|
|
|
drawingAreaContainer.setAttribute('onmousedown', 'piskel.onCanvasMousedown(event)');
|
|
|
|
drawingAreaContainer.setAttribute('onmousemove', 'piskel.onCanvasMousemove(event)');
|
2012-08-28 02:53:27 +04:00
|
|
|
this.drawFrameToCanvas(frameSheet.getFrameByIndex(this.getActiveFrameIndex()), drawingAreaCanvas, drawingCanvasDpi);
|
|
|
|
},
|
|
|
|
|
|
|
|
initPreviewSlideshow: function() {
|
|
|
|
var addFrameButton = $('add-frame-button');
|
|
|
|
addFrameButton.addEventListener('mousedown', function() {
|
|
|
|
frameSheet.addEmptyFrame();
|
|
|
|
piskel.setActiveFrameAndRedraw(frameSheet.getFrameCount() - 1);
|
|
|
|
});
|
|
|
|
this.createPreviews();
|
|
|
|
},
|
|
|
|
|
|
|
|
initAnimationPreview : function() {
|
|
|
|
var scope = this;
|
|
|
|
|
|
|
|
var previewAnimationContainer = $('preview-canvas-container');
|
|
|
|
previewCanvas = document.createElement('canvas');
|
|
|
|
previewCanvas.className = 'canvas';
|
|
|
|
previewAnimationContainer.setAttribute('style',
|
|
|
|
'width:' + framePixelWidth * previewAnimationCanvasDpi + 'px; height:' + framePixelHeight * previewAnimationCanvasDpi + 'px;');
|
|
|
|
previewAnimationContainer.appendChild(previewCanvas);
|
|
|
|
previewCanvas.setAttribute('width', framePixelWidth * previewAnimationCanvasDpi);
|
|
|
|
previewCanvas.setAttribute('height', framePixelHeight * previewAnimationCanvasDpi);
|
|
|
|
|
|
|
|
var animFPSTuner = document.getElementById("preview-fps");
|
|
|
|
var animPreviewFPS = parseInt(animFPSTuner.value, 10);
|
|
|
|
var startPreviewRefresh = function() {
|
|
|
|
return setInterval(scope.refreshAnimatedPreview, 1000/animPreviewFPS);
|
|
|
|
};
|
|
|
|
var refreshUpdater = startPreviewRefresh();
|
|
|
|
|
2012-08-29 15:43:36 +04:00
|
|
|
animFPSTuner.addEventListener('change', function(evt) {
|
2012-08-28 02:53:27 +04:00
|
|
|
window.clearInterval(refreshUpdater);
|
|
|
|
animPreviewFPS = parseInt(animFPSTuner.value, 10);
|
|
|
|
if(isNaN(animPreviewFPS)) {
|
|
|
|
animPreviewFPS = 1;
|
|
|
|
}
|
|
|
|
if(evt.keyCode == 38) {
|
|
|
|
animPreviewFPS++;
|
|
|
|
}
|
|
|
|
else if (evt.keyCode == 40) {
|
|
|
|
animPreviewFPS--;
|
|
|
|
}
|
|
|
|
if(animPreviewFPS < 1) {
|
|
|
|
animPreviewFPS = 1;
|
|
|
|
}
|
|
|
|
if(animPreviewFPS > 100) {
|
|
|
|
animPreviewFPS = 100;
|
|
|
|
}
|
|
|
|
animFPSTuner.value = animPreviewFPS;
|
2012-08-29 15:43:36 +04:00
|
|
|
$("fps-value").innerHTML = animPreviewFPS + " fps";
|
2012-08-28 02:53:27 +04:00
|
|
|
refreshUpdater = startPreviewRefresh();
|
|
|
|
});
|
2012-08-23 03:00:30 +04:00
|
|
|
},
|
|
|
|
|
|
|
|
createPreviews : function () {
|
2012-08-28 02:53:27 +04:00
|
|
|
var container = $('preview-list'), previewTile;
|
2012-08-23 03:00:30 +04:00
|
|
|
container.innerHTML = "";
|
2012-08-28 02:53:27 +04:00
|
|
|
for (var i = 0, l = frameSheet.getFrameCount(); i < l ; i++) {
|
|
|
|
previewTile = this.createPreviewTile(i);
|
|
|
|
container.appendChild(previewTile);
|
|
|
|
}
|
|
|
|
},
|
2012-08-23 03:00:30 +04:00
|
|
|
|
2012-08-28 02:53:27 +04:00
|
|
|
createPreviewTile: function(tileNumber) {
|
|
|
|
var previewTileRoot = document.createElement("li");
|
|
|
|
var classname = "preview-tile";
|
2012-08-23 03:00:30 +04:00
|
|
|
|
2012-08-28 02:53:27 +04:00
|
|
|
if (this.getActiveFrameIndex() == tileNumber) {
|
|
|
|
classname += " selected";
|
|
|
|
}
|
|
|
|
previewTileRoot.className = classname;
|
|
|
|
|
|
|
|
var canvasContainer = document.createElement("div");
|
|
|
|
canvasContainer.className = "canvas-container";
|
|
|
|
canvasContainer.setAttribute('style',
|
|
|
|
'width:' + framePixelWidth * previewTileCanvasDpi + 'px; height:' + framePixelHeight * previewTileCanvasDpi + 'px;');
|
|
|
|
|
|
|
|
var canvasBackground = document.createElement("div");
|
|
|
|
canvasBackground.className = "canvas-background";
|
|
|
|
canvasContainer.appendChild(canvasBackground);
|
|
|
|
|
|
|
|
var canvasPreview = document.createElement("canvas");
|
|
|
|
canvasPreview.className = "canvas tile-view"
|
|
|
|
|
|
|
|
canvasPreview.setAttribute('width', framePixelWidth * previewTileCanvasDpi);
|
|
|
|
canvasPreview.setAttribute('height', framePixelHeight * previewTileCanvasDpi);
|
|
|
|
|
|
|
|
previewTileRoot.addEventListener('click', function(evt) {
|
|
|
|
// has not class tile-action:
|
|
|
|
// TODO: let me know when you want to start using a framework :)
|
|
|
|
if(!evt.target.className.match(new RegExp('(\\s|^)'+ 'tile-action' +'(\\s|$)'))) {
|
|
|
|
piskel.setActiveFrameAndRedraw(tileNumber);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
var canvasPreviewDuplicateAction = document.createElement("button");
|
|
|
|
canvasPreviewDuplicateAction.className = "tile-action"
|
|
|
|
canvasPreviewDuplicateAction.innerHTML = "dup"
|
2012-08-23 03:00:30 +04:00
|
|
|
|
2012-08-28 02:53:27 +04:00
|
|
|
canvasPreviewDuplicateAction.addEventListener('click', function(evt) {
|
|
|
|
piskel.duplicateFrame(tileNumber);
|
|
|
|
});
|
2012-08-23 03:00:30 +04:00
|
|
|
|
2012-08-28 02:53:27 +04:00
|
|
|
this.drawFrameToCanvas(frameSheet.getFrameByIndex(tileNumber), canvasPreview, previewTileCanvasDpi);
|
|
|
|
canvasContainer.appendChild(canvasPreview);
|
|
|
|
previewTileRoot.appendChild(canvasContainer);
|
|
|
|
previewTileRoot.appendChild(canvasPreviewDuplicateAction);
|
|
|
|
|
|
|
|
if(tileNumber > 0 || frameSheet.getFrameCount() > 1) {
|
|
|
|
var canvasPreviewDeleteAction = document.createElement("button");
|
|
|
|
canvasPreviewDeleteAction.className = "tile-action"
|
|
|
|
canvasPreviewDeleteAction.innerHTML = "del"
|
|
|
|
canvasPreviewDeleteAction.addEventListener('click', function(evt) {
|
2012-08-29 01:30:55 +04:00
|
|
|
piskel.removeFrame(tileNumber);
|
2012-08-28 02:53:27 +04:00
|
|
|
});
|
|
|
|
previewTileRoot.appendChild(canvasPreviewDeleteAction);
|
2012-08-23 03:00:30 +04:00
|
|
|
}
|
2012-08-28 02:53:27 +04:00
|
|
|
|
|
|
|
return previewTileRoot;
|
2012-08-23 03:00:30 +04:00
|
|
|
},
|
|
|
|
|
|
|
|
refreshAnimatedPreview : function () {
|
2012-08-28 02:53:27 +04:00
|
|
|
piskel.drawFrameToCanvas(frameSheet.getFrameByIndex(animIndex), previewCanvas, previewAnimationCanvasDpi);
|
|
|
|
animIndex++;
|
|
|
|
if (animIndex == frameSheet.getFrameCount()) {
|
2012-08-23 03:00:30 +04:00
|
|
|
animIndex = 0;
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
2012-08-28 02:53:27 +04:00
|
|
|
removeFrame: function(frameIndex) {
|
|
|
|
frameSheet.removeFrameByIndex(frameIndex);
|
|
|
|
|
|
|
|
this.setActiveFrameAndRedraw(frameIndex - 1);
|
|
|
|
},
|
|
|
|
|
|
|
|
duplicateFrame: function(frameIndex) {
|
|
|
|
frameSheet.duplicateFrameByIndex(frameIndex);
|
|
|
|
|
|
|
|
this.setActiveFrameAndRedraw(frameIndex + 1);
|
2012-08-23 03:00:30 +04:00
|
|
|
},
|
|
|
|
|
|
|
|
updateCursorInfo : function (event) {
|
|
|
|
var cursor = $('cursorInfo');
|
|
|
|
cursor.style.top = event.clientY + 10 + "px";
|
|
|
|
cursor.style.left = event.clientX + 10 + "px";
|
|
|
|
|
|
|
|
var coordinates = this.getRelativeCoordinates(event.clientX, event.clientY)
|
|
|
|
cursor.innerHTML = [
|
|
|
|
"X : " + coordinates.x,
|
|
|
|
"Y : " + coordinates.y
|
|
|
|
].join(", ");
|
|
|
|
},
|
|
|
|
|
|
|
|
onCanvasMousedown : function (event) {
|
|
|
|
isClicked = true;
|
|
|
|
var coords = this.getRelativeCoordinates(event.clientX, event.clientY);
|
2012-08-28 02:53:27 +04:00
|
|
|
if(event.button == 0) {
|
|
|
|
this.drawAt(coords.x, coords.y, penColor);
|
|
|
|
} else {
|
|
|
|
// Right click used to delete.
|
|
|
|
isRightClicked = true;
|
|
|
|
this.drawAt(coords.x, coords.y, TRANSPARENT_COLOR);
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
onCanvasMousemove : function (event) {
|
|
|
|
//this.updateCursorInfo(event);
|
|
|
|
if (isClicked) {
|
|
|
|
var coords = this.getRelativeCoordinates(event.clientX, event.clientY);
|
|
|
|
if(isRightClicked) {
|
|
|
|
this.drawAt(coords.x, coords.y, TRANSPARENT_COLOR);
|
|
|
|
} else {
|
|
|
|
this.drawAt(coords.x, coords.y, penColor);
|
|
|
|
}
|
|
|
|
}
|
2012-08-23 03:00:30 +04:00
|
|
|
},
|
|
|
|
|
|
|
|
onCanvasMouseup : function (event) {
|
2012-08-28 02:53:27 +04:00
|
|
|
if(isClicked || 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.createPreviews();
|
|
|
|
}
|
2012-08-23 03:00:30 +04:00
|
|
|
isClicked = false;
|
2012-08-28 02:53:27 +04:00
|
|
|
isRightClicked = false;
|
2012-08-23 03:00:30 +04:00
|
|
|
},
|
|
|
|
|
2012-08-28 02:53:27 +04:00
|
|
|
drawAt : function (x, y, color) {
|
2012-08-29 01:30:55 +04:00
|
|
|
var pixelWidthIndex = (x - x%drawingCanvasDpi) / drawingCanvasDpi;
|
|
|
|
var pixelHeightIndex = (y - y%drawingCanvasDpi) / drawingCanvasDpi;
|
2012-08-28 02:53:27 +04:00
|
|
|
|
|
|
|
// Update model:
|
|
|
|
var currentFrame = frameSheet.getFrameByIndex(this.getActiveFrameIndex());
|
|
|
|
|
|
|
|
// TODO: make a better accessor for pixel state update:
|
|
|
|
// TODO: Make pen color dynamic:
|
|
|
|
currentFrame[pixelWidthIndex][pixelHeightIndex] = color;
|
|
|
|
|
|
|
|
// Update view:
|
|
|
|
// TODO: Create a per pixel update function for perf ?
|
|
|
|
this.drawFrameToCanvas(currentFrame, drawingAreaCanvas, drawingCanvasDpi);
|
|
|
|
},
|
2012-08-23 03:00:30 +04:00
|
|
|
|
2012-08-28 02:53:27 +04:00
|
|
|
// TODO: move that to a FrameRenderer (/w cache) ?
|
|
|
|
drawFrameToCanvas: function(frame, canvasElement, dpi) {
|
|
|
|
var pixelColor, context = canvasElement.getContext('2d');
|
|
|
|
for(var col = 0, num_col = frame.length; col < num_col; col++) {
|
|
|
|
for(var row = 0, num_row = frame[col].length; row < num_row; row++) {
|
|
|
|
pixelColor = frame[col][row];
|
|
|
|
if(pixelColor == undefined || pixelColor == TRANSPARENT_COLOR) {
|
|
|
|
context.clearRect(col * dpi, row * dpi, dpi, dpi);
|
|
|
|
} else {
|
2012-08-29 12:46:25 +04:00
|
|
|
if (pixelColor.indexOf("#") != 0)
|
|
|
|
pixelColor = "#" + pixelColor;
|
|
|
|
this.addColorToPalette(pixelColor);
|
2012-08-28 02:53:27 +04:00
|
|
|
context.fillStyle = pixelColor;
|
|
|
|
context.fillRect(col * dpi, row * dpi, dpi, dpi);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2012-08-23 03:00:30 +04:00
|
|
|
},
|
|
|
|
|
|
|
|
onCanvasContextMenu : function (event) {
|
|
|
|
event.preventDefault();
|
|
|
|
event.stopPropagation();
|
|
|
|
event.cancelBubble = true;
|
|
|
|
return false;
|
|
|
|
},
|
2012-08-29 10:39:03 +04:00
|
|
|
|
|
|
|
onPaletteClick : function (event) {
|
|
|
|
var color = event.target.getAttribute("data-color");
|
2012-08-29 12:46:25 +04:00
|
|
|
if (null !== color) {
|
|
|
|
var colorPicker = $('color-picker');
|
|
|
|
colorPicker.color.fromString(color);
|
|
|
|
this.onPickerChange();
|
|
|
|
}
|
2012-08-29 10:39:03 +04:00
|
|
|
},
|
2012-08-28 02:53:27 +04:00
|
|
|
|
2012-08-23 03:00:30 +04:00
|
|
|
getRelativeCoordinates : function (x, y) {
|
2012-08-28 02:53:27 +04:00
|
|
|
var canvasRect = drawingAreaCanvas.getBoundingClientRect();
|
2012-08-23 03:00:30 +04:00
|
|
|
return {
|
|
|
|
x : x - canvasRect.left,
|
|
|
|
y : y - canvasRect.top
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
window.piskel = piskel;
|
|
|
|
piskel.init();
|
|
|
|
|
2012-08-29 13:51:40 +04:00
|
|
|
})(function(id){return document.getElementById(id)});
|
|
|
|
//small change for checking my git setup :(
|