piskel/js/piskel.js
Vince 651563f793 FrameSheet model object migration
Migration to a Domain object (currently a FrameSheetModel, feel free to
change its name). The model is being used by the slideshow (drawing
each tiles), animation preview (drawing animation) and drawing (update
model and redraw current tile).
Now the rendering information are not stored in a canvas element that
you paste from canvas to canvas but centralize in this model. The frame
is described as an array of array: that will allow different rendering
using the dpi constants and more flexibility (e.g. drawing a grid,
serializing the data).

Some minor modifications:
  - cleaning markup
  - adding background image to highlight transparent area
2012-08-27 02:05:13 +02:00

334 lines
11 KiB
JavaScript

(function ($) {
var frameSheet,
// Constants:
TRANSPARENT_COLOR = 'tc',
//TRANSPARENT_COLOR = 'pink',
DEFAULT_PEN_COLOR = '#000',
// Configuration:
// Canvas size in pixel size (not dpi related)
framePixelWidth = 32,
framePixelHeight = 32,
// Scaling factors for a given frameSheet rendering:
// Main drawing area:
drawingCanvasDpi = 10,
// 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,
penColor = DEFAULT_PEN_COLOR;
var piskel = {
init : function () {
frameSheet = FrameSheetModel.getInstance(framePixelWidth, framePixelHeight);
frameSheet.addEmptyFrame();
this.setActiveFrame(0);
this.initDrawingArea();
this.initPreviewSlideshow();
this.initAnimationPreview();
},
setActiveFrame: function(index) {
activeFrameIndex = index;
},
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."
}
return activeFrameIndex;
},
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;');
drawingAreaCanvas.setAttribute('oncontextmenu', 'piskel.onCanvasContextMenu(arguments[0])');
drawingAreaContainer.appendChild(drawingAreaCanvas);
var body = document.getElementsByTagName('body')[0];
body.setAttribute('onmouseup', 'piskel.onCanvasMouseup(arguments[0])');
drawingAreaContainer.setAttribute('onmousedown', 'piskel.onCanvasMousedown(arguments[0])');
drawingAreaContainer.setAttribute('onmousemove', 'piskel.onCanvasMousemove(arguments[0])');
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();
animFPSTuner.addEventListener('keyup', function(evt) {
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;
refreshUpdater = startPreviewRefresh();
});
},
createPreviews : function () {
var container = $('preview-list'), previewTile;
container.innerHTML = "";
for (var i = 0, l = frameSheet.getFrameCount(); i < l ; i++) {
previewTile = this.createPreviewTile(i);
container.appendChild(previewTile);
}
},
createPreviewTile: function(tileNumber) {
var previewTileRoot = document.createElement("li");
var classname = "preview-tile";
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"
canvasPreviewDuplicateAction.addEventListener('click', function(evt) {
piskel.duplicateFrame(tileNumber);
});
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) {
frameSheet.removeFrameByIndex(tileNumber);
animIndex = 0;
piskel.createPreviews();
});
previewTileRoot.appendChild(canvasPreviewDeleteAction);
}
return previewTileRoot;
},
refreshAnimatedPreview : function () {
piskel.drawFrameToCanvas(frameSheet.getFrameByIndex(animIndex), previewCanvas, previewAnimationCanvasDpi);
animIndex++;
if (animIndex == frameSheet.getFrameCount()) {
animIndex = 0;
}
},
removeFrame: function(frameIndex) {
frameSheet.removeFrameByIndex(frameIndex);
this.setActiveFrameAndRedraw(frameIndex - 1);
},
duplicateFrame: function(frameIndex) {
frameSheet.duplicateFrameByIndex(frameIndex);
this.setActiveFrameAndRedraw(frameIndex + 1);
},
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);
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);
}
}
},
onCanvasMouseup : function (event) {
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();
}
isClicked = false;
isRightClicked = false;
},
drawAt : function (x, y, color) {
var pixelWidthIndex = (x - x%drawingCanvasDpi) / 10;
var pixelHeightIndex = (y - y%drawingCanvasDpi) / 10;
// 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);
},
// 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 {
context.fillStyle = pixelColor;
context.fillRect(col * dpi, row * dpi, dpi, dpi);
}
}
}
},
onCanvasContextMenu : function (event) {
event.preventDefault();
event.stopPropagation();
event.cancelBubble = true;
return false;
},
getRelativeCoordinates : function (x, y) {
var canvasRect = drawingAreaCanvas.getBoundingClientRect();
return {
x : x - canvasRect.left,
y : y - canvasRect.top
}
}
};
window.piskel = piskel;
piskel.init();
})(function(id){return document.getElementById(id)});