piskel/js/piskel.js
2012-09-08 23:59:44 +02:00

379 lines
11 KiB
JavaScript

/**
* @require Constants
* @require Events
*/
$.namespace("pskl");
(function () {
/**
* FrameSheetModel instance.
*/
var frameSheet,
// Configuration:
// Canvas size in pixel size (not dpi related)
framePixelWidth = 32,
framePixelHeight = 32,
// Scaling factors for a given frameSheet rendering:
// Main drawing area:
drawingCanvasDpi = 20,
// Canvas preview film canvases:
previewTileCanvasDpi = 4,
// Animated canvas preview:
previewAnimationCanvasDpi = 8,
// DOM references:
drawingAreaContainer,
drawingAreaCanvas,
previewCanvas,
// States:
isClicked = false,
isRightClicked = false,
activeFrameIndex = -1,
animIndex = 0,
primaryColor = Constants.DEFAULT_PEN_COLOR,
secondaryColor = Constants.TRANSPARENT_COLOR,
currentFrame = null;
currentToolBehavior = null,
previousMousemoveTime = 0;
/**
* Main application controller
*/
var piskel = {
init : function () {
piskel.initDPIs_();
frameSheet = new pskl.model.FrameSheet(framePixelWidth, framePixelHeight);
frameSheet.addEmptyFrame();
this.drawingController = new pskl.controller.DrawingController(
frameSheet.getFrameByIndex(0),
$('#drawing-canvas-container')[0],
drawingCanvasDpi
);
this.setActiveFrame(0);
this.animationController = new pskl.controller.AnimatedPreviewController(
frameSheet,
$('#preview-canvas-container')[0],
previewAnimationCanvasDpi
);
this.previewsController = new pskl.controller.PreviewFilmController(
frameSheet,
$('#preview-list'),
previewTileCanvasDpi
);
this.animationController.init();
this.previewsController.init();
pskl.HistoryManager.init();
pskl.NotificationService.init();
pskl.LocalStorageService.init(frameSheet);
// TODO: Add comments
var framesheetId = this.getFramesheetIdFromUrl();
if (framesheetId) {
$.publish(Events.SHOW_NOTIFICATION, [{"content": "Loading animation with id : [" + framesheetId + "]"}]);
this.loadFramesheetFromService(framesheetId);
} else {
this.finishInit();
pskl.LocalStorageService.displayRestoreNotification();
}
var drawingLoop = new pskl.rendering.DrawingLoop();
drawingLoop.addCallback(this.render, this);
drawingLoop.start();
},
render : function (delta) {
this.drawingController.render(delta);
this.animationController.render(delta);
this.previewsController.render(delta);
},
/**
* Override default DPIs.
* @private
*/
initDPIs_ : function() {
drawingCanvasDpi = piskel.calculateDPIsForDrawingCanvas_();
// TODO(vincz): Add throttling on window.resize event.
$(window).resize($.proxy(function() {
drawingCanvasDpi = piskel.calculateDPIsForDrawingCanvas_();
this.drawingController.updateDPI(drawingCanvasDpi);
}, this));
// TODO(vincz): Check for user settings eventually from localstorage.
},
/**
* @private
*/
calculateDPIsForDrawingCanvas_ : function() {
var availableViewportHeight = $('.main-panel').height(),
availableViewportWidth = $('.main-panel').width(),
previewHeight = $(".preview-container").height(),
previewWidth = $(".preview-container").width();
var heightBoundDpi = Math.floor(availableViewportHeight / framePixelHeight),
widthBoundDpi = Math.floor(availableViewportWidth / framePixelWidth);
var dpi = Math.min(heightBoundDpi, widthBoundDpi);
var drawingCanvasHeight = dpi * framePixelHeight;
var drawingCanvasWidth = dpi * framePixelWidth;
// Check if preview and drawing canvas overlap
var heightGap = drawingCanvasHeight + previewHeight - availableViewportHeight,
widthGap = drawingCanvasWidth + previewWidth - availableViewportWidth;
if (heightGap > 0 && widthGap > 0) {
// Calculate the DPI change needed to bridge height and width gap
var heightGapDpi = Math.ceil(heightGap / framePixelHeight),
widthGapDpi = Math.ceil(widthGap / framePixelWidth);
// substract smallest dpi change to initial dpi
dpi -= Math.min(heightGapDpi, widthGapDpi);
}
return dpi;
},
finishInit : function () {
$.subscribe(Events.TOOL_SELECTED, function(evt, toolBehavior) {
console.log("Tool selected: ", toolBehavior);
currentToolBehavior = toolBehavior;
});
$.subscribe(Events.COLOR_SELECTED, function(evt, color, isPrimary) {
console.log("Color selected: ", color);
if (isPrimary) {
primaryColor = color;
} else {
secondaryColor = color;
}
});
$.subscribe(Events.REFRESH, function() {
piskel.setActiveFrame(0);
});
// TODO: Move this into their service or behavior files:
this.initDrawingArea();
pskl.ToolSelector.init();
pskl.Palette.init(frameSheet);
},
getFramesheetIdFromUrl : function() {
var href = window.location.href;
// TODO: Change frameId to framesheetId on the backend
if (href.indexOf('frameId=') != -1) {
return href.substring(href.indexOf('frameId=')+8);
}
},
loadFramesheetFromService : function (frameId) {
var xhr = new XMLHttpRequest();
// TODO: Change frameId to framesheetId on the backend
xhr.open('GET', Constants.PISKEL_SERVICE_URL + '/get?l=' + frameId, true);
xhr.responseType = 'text';
xhr.onload = function(e) {
frameSheet.deserialize(this.responseText);
piskel.setActiveFrame(0);
$.publish(Events.HIDE_NOTIFICATION);
piskel.finishInit();
};
xhr.onerror = function () {
$.publish(Events.HIDE_NOTIFICATION);
piskel.finishInit();
piskel.setActiveFrame(0);
};
xhr.send();
},
setActiveFrame: function(index) {
activeFrameIndex = index;
this.drawingController.frame = this.getCurrentFrame();
},
getActiveFrameIndex: function() {
if(-1 == activeFrameIndex) {
throw "Bad active frame initialization."
}
return activeFrameIndex;
},
getCurrentFrame : function () {
return frameSheet.getFrameByIndex(activeFrameIndex);
},
initDrawingArea : function() {
drawingAreaContainer = $('#drawing-canvas-container')[0];
document.body.addEventListener('mouseup', this.onMouseup.bind(this));
drawingAreaContainer.addEventListener('mousedown', this.onMousedown.bind(this));
drawingAreaContainer.addEventListener('mousemove', this.onMousemove.bind(this));
document.body.addEventListener('contextmenu', this.onCanvasContextMenu);
},
removeFrame: function(frameIndex) {
frameSheet.removeFrameByIndex(frameIndex);
var activeFrameIndex = frameIndex ? frameIndex - 1 : 0;
this.setActiveFrame(activeFrameIndex);
},
duplicateFrame: function(frameIndex) {
frameSheet.duplicateFrameByIndex(frameIndex);
this.setActiveFrame(frameIndex + 1);
},
getCurrentColor : function () {
if(isRightClicked) {
return secondaryColor;
} else {
return primaryColor;
}
},
onMousedown : function (event) {
isClicked = true;
if(event.button == 2) { // right click
isRightClicked = true;
$.publish(Events.CANVAS_RIGHT_CLICKED);
}
var spriteCoordinate = this.getSpriteCoordinate(event);
currentToolBehavior.applyToolAt(
spriteCoordinate.col,
spriteCoordinate.row,
this.getCurrentColor(),
this.drawingController
);
$.publish(Events.LOCALSTORAGE_REQUEST);
},
onMousemove : function (event) {
var currentTime = new Date().getTime();
// Throttling of the mousemove event:
if ((currentTime - previousMousemoveTime) > 40 ) {
var spriteCoordinate = this.getSpriteCoordinate(event);
if (isClicked) {
currentToolBehavior.moveToolAt(
spriteCoordinate.col,
spriteCoordinate.row,
this.getCurrentColor(),
this.drawingController
);
// 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 {
// debug mode to see the selected pixel
// this.drawingController.clearOverlay();
// this.drawingController.overlay.setPixel( spriteCoordinate.col,spriteCoordinate.row, "#ff0000");
// this.drawingController.renderOverlay();
}
previousMousemoveTime = currentTime;
}
},
onMouseup : 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.
if(isRightClicked) {
$.publish(Events.CANVAS_RIGHT_CLICK_RELEASED);
}
isClicked = false;
isRightClicked = false;
var spriteCoordinate = this.getSpriteCoordinate(event);
currentToolBehavior.releaseToolAt(
spriteCoordinate.col,
spriteCoordinate.row,
this.getCurrentColor(),
this.drawingController
);
$.publish(Events.TOOL_RELEASED);
}
},
onCanvasContextMenu : function (event) {
event.preventDefault();
event.stopPropagation();
event.cancelBubble = true;
return false;
},
getRelativeCoordinates : function (x, y) {
var canvasRect = $(".drawing-canvas")[0].getBoundingClientRect();
return {
x : x - canvasRect.left,
y : y - canvasRect.top
}
},
getSpriteCoordinate : function(event) {
var coord = this.getRelativeCoordinates(event.x, event.y);
var coords = this.getRelativeCoordinates(event.clientX, event.clientY);
return {
"col" : (coords.x - coords.x%drawingCanvasDpi) / drawingCanvasDpi,
"row" : (coords.y - coords.y%drawingCanvasDpi) / drawingCanvasDpi
}
},
// TODO(julz): Create package ?
storeSheet : function (event) {
// TODO Refactor using jquery ?
var xhr = new XMLHttpRequest();
var formData = new FormData();
formData.append('framesheet_content', frameSheet.serialize());
formData.append('fps_speed', $('#preview-fps').val());
xhr.open('POST', Constants.PISKEL_SERVICE_URL + "/store", true);
xhr.onload = function(e) {
if (this.status == 200) {
var baseUrl = window.location.href.replace(window.location.search, "");
window.location.href = baseUrl + "?frameId=" + this.responseText;
}
};
xhr.send(formData);
if(event) {
event.stopPropagation();
event.preventDefault();
}
return false;
}
};
window.piskel = piskel;
piskel.init();
})();