Issue 24 : Layers

!! NOT STABLE !!
Initial implementation. No UI update yet.
Check js/model/Piskel.js and js/model/Layer.js for an overview of the new
API.

Piskels can be saved on the existing service.

Previous piskels cannot be loaded. This should be fixed soon.
This commit is contained in:
jdescottes
2013-09-22 21:02:43 +02:00
parent 4f6863eb8a
commit 6528c7724b
24 changed files with 572 additions and 230 deletions

View File

@ -32,5 +32,5 @@ ul, li {
}
::-webkit-scrollbar-track {
background-color: rgba(50, 50, 50, 0.4);;
background-color: rgba(50, 50, 50, 0.4);
}

View File

@ -19,7 +19,6 @@
.tool-icon.selected {
cursor: default;
background-color: #444;
cursor: normal;
border: 1px gold solid;
margin: 0;
}
@ -151,7 +150,7 @@
}
.palette .palette-color.transparent-color {
.palette-color[data-color=TRANSPARENT] {
position: relative;
top: 10px;
left: 10px;

View File

@ -1,10 +1,13 @@
// TODO(grosbouddha): put under pskl namespace.
var Constants = {
DEFAULT_SIZE : {
height : 32,
width : 32
DEFAULT : {
HEIGHT : 32,
WIDTH : 32,
FPS : 12
},
MODEL_VERSION : 1,
MAX_HEIGHT : 128,
MAX_WIDTH : 128,

View File

@ -4,43 +4,39 @@
*/
(function () {
var ns = $.namespace("pskl");
/**
* FrameSheetModel instance.
*/
var frameSheet;
/**
* Main application controller
*/
ns.app = {
init : function () {
var frameSize = this.readSizeFromURL_();
frameSheet = new pskl.model.FrameSheet(frameSize.height, frameSize.width);
frameSheet.addEmptyFrame();
frameSheet.setCurrentFrameIndex(0);
var size = this.readSizeFromURL_();
var piskel = new pskl.model.Piskel(size.width, size.height, Constants.DEFAULT.FPS);
/**
* 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_;
var layer = new pskl.model.Layer("Default layer");
var frame = new pskl.model.Frame(size.width, size.height);
layer.addFrame(frame);
this.drawingController = new pskl.controller.DrawingController(frameSheet, $('#drawing-canvas-container'));
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(frameSheet, $('#preview-canvas-container'));
this.animationController = new pskl.controller.AnimatedPreviewController(this.piskelController, $('#preview-canvas-container'));
this.animationController.init();
this.previewsController = new pskl.controller.PreviewFilmController(frameSheet, $('#preview-list'));
this.previewsController = new pskl.controller.PreviewFilmController(this.piskelController, $('#preview-list'));
this.previewsController.init();
this.settingsController = new pskl.controller.SettingsController(frameSheet);
this.settingsController = new pskl.controller.SettingsController(this.piskelController);
this.settingsController.init();
this.selectionManager = new pskl.selection.SelectionManager(frameSheet);
this.selectionManager = new pskl.selection.SelectionManager(this.piskelController);
this.selectionManager.init();
this.historyService = new pskl.service.HistoryService(frameSheet);
this.historyService = new pskl.service.HistoryService(this.piskelController);
this.historyService.init();
this.keyboardEventService = new pskl.service.KeyboardEventService();
@ -49,7 +45,7 @@
this.notificationController = new pskl.controller.NotificationController();
this.notificationController.init();
this.localStorageService = new pskl.service.LocalStorageService(frameSheet);
this.localStorageService = new pskl.service.LocalStorageService(this.piskelController);
this.localStorageService.init();
this.imageUploadService = new pskl.service.ImageUploadService();
@ -59,16 +55,13 @@
this.toolController.init();
this.paletteController = new pskl.controller.PaletteController();
this.paletteController.init(frameSheet);
this.paletteController.init();
var drawingLoop = new pskl.rendering.DrawingLoop();
drawingLoop.addCallback(this.render, this);
drawingLoop.start();
// Init (event-delegated) bootstrap tooltips:
$('body').tooltip({
selector: '[rel=tooltip]'
});
this.initBootstrapTooltips_();
/**
@ -89,12 +82,18 @@
}
} else {
if (pskl.framesheetData_ && pskl.framesheetData_.content) {
frameSheet.load(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);
@ -115,7 +114,10 @@
width : Math.min(width, Constants.MAX_WIDTH)
};
} else {
size = Constants.DEFAULT_SIZE;
size = {
height : Constants.DEFAULT.HEIGHT,
width : Constants.DEFAULT.WIDTH
};
}
return size;
},
@ -141,10 +143,10 @@
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);
frameSheet.load(res.framesheet);
piskelController.deserialize(JSON.stringify(res.framesheet));
pskl.app.animationController.setFPS(res.fps);
$.publish(Events.HIDE_NOTIFICATION);
};
@ -156,21 +158,15 @@
xhr.send();
},
loadFramesheet : function (framesheet) {
frameSheet.load(framesheet);
},
getFirstFrameAsPNGData_ : function () {
var tmpSheet = new pskl.model.FrameSheet(frameSheet.getHeight(), frameSheet.getWidth());
tmpSheet.addFrame(frameSheet.getFrameByIndex(0));
return (new pskl.rendering.SpritesheetRenderer(tmpSheet)).renderAsImageDataSpritesheetPNG();
throw 'getFirstFrameAsPNGData_ not implemented';
},
// TODO(julz): Create package ?
storeSheet : function (event) {
var xhr = new XMLHttpRequest();
var formData = new FormData();
formData.append('framesheet_content', frameSheet.serialize());
formData.append('framesheet_content', this.piskelController.serialize());
formData.append('fps_speed', $('#preview-fps').val());
if (this.isStaticVersion) {
@ -179,12 +175,12 @@
} else {
// additional values only used with latest app-engine backend
formData.append('name', $('#piskel-name').val());
formData.append('frames', frameSheet.getFrameCount());
// Get image/png data for first frame
formData.append('frames', this.piskelController.getFrameCount());
// Get image/png data for first frame
formData.append('preview', this.getFirstFrameAsPNGData_());
var imageData = (new pskl.rendering.SpritesheetRenderer(frameSheet)).renderAsImageDataSpritesheetPNG();
var imageData = (new pskl.rendering.SpritesheetRenderer(this.piskelController)).renderAsImageDataSpritesheetPNG();
formData.append('framesheet', imageData);
xhr.open('POST', "save", true);
@ -214,25 +210,16 @@
return false;
},
uploadAsAnimatedGIF : function () {
var fps = pskl.app.animationController.fps;
var renderer = new pskl.rendering.SpritesheetRenderer(frameSheet);
renderer.renderAsImageDataAnimatedGIF(fps, function (imageData) {
this.imageUploadService.upload(imageData, this.openWindow);
}.bind(this));
},
uploadAsSpritesheetPNG : function () {
var imageData = (new pskl.rendering.SpritesheetRenderer(frameSheet)).renderAsImageDataSpritesheetPNG();
this.imageUploadService.upload(imageData, this.openWindow);
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=" + frameSheet.getWidth() * frameSheet.getFrameCount(),
"height=" + frameSheet.getHeight()
"width=" + this.piskelController.getWidth() * this.piskelController.getFrameCount(),
"height=" + this.piskelController.getHeight()
].join(",");
window.open(url, "piskel-export", options);

View File

@ -1,7 +1,7 @@
(function () {
var ns = $.namespace("pskl.controller");
ns.AnimatedPreviewController = function (framesheet, container, dpi) {
this.framesheet = framesheet;
ns.AnimatedPreviewController = function (piskelController, container, dpi) {
this.piskelController = piskelController;
this.container = container;
this.elapsedTime = 0;
@ -39,11 +39,11 @@
var index = Math.floor(this.elapsedTime / (1000/this.fps));
if (index != this.currentIndex) {
this.currentIndex = index;
if (!this.framesheet.hasFrameAtIndex(this.currentIndex)) {
if (!this.piskelController.hasFrameAt(this.currentIndex)) {
this.currentIndex = 0;
this.elapsedTime = 0;
}
this.renderer.render(this.framesheet.getFrameByIndex(this.currentIndex));
this.renderer.render(this.piskelController.getFrameAt(this.currentIndex));
}
};
@ -52,8 +52,8 @@
*/
ns.AnimatedPreviewController.prototype.calculateDPI_ = function () {
var previewSize = 200,
framePixelHeight = this.framesheet.getCurrentFrame().getHeight(),
framePixelWidth = this.framesheet.getCurrentFrame().getWidth();
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);

View File

@ -1,15 +1,15 @@
(function () {
var ns = $.namespace("pskl.controller");
ns.DrawingController = function (framesheet, container) {
ns.DrawingController = function (piskelController, container) {
/**
* @public
*/
this.framesheet = framesheet;
this.piskelController = piskelController;
/**
* @public
*/
this.overlayFrame = pskl.model.Frame.createEmptyFromFrame(framesheet.getCurrentFrame());
this.overlayFrame = pskl.model.Frame.createEmptyFromFrame(piskelController.getCurrentFrame());
/**
* @private
@ -36,7 +36,7 @@
};
ns.DrawingController.prototype.init = function () {
this.renderer.render(this.framesheet.getCurrentFrame());
this.renderer.render(this.piskelController.getCurrentFrame());
this.overlayRenderer.render(this.overlayFrame);
this.initMouseBehavior();
@ -114,7 +114,7 @@
this.currentToolBehavior.applyToolAt(
coords.col, coords.row,
this.getCurrentColor_(),
this.framesheet.getCurrentFrame(),
this.piskelController.getCurrentFrame(),
this.overlayFrame,
this.wrapEvtInfo_(event)
);
@ -135,7 +135,7 @@
this.currentToolBehavior.moveToolAt(
coords.col, coords.row,
this.getCurrentColor_(),
this.framesheet.getCurrentFrame(),
this.piskelController.getCurrentFrame(),
this.overlayFrame,
this.wrapEvtInfo_(event)
);
@ -149,7 +149,7 @@
this.currentToolBehavior.moveUnactiveToolAt(
coords.col, coords.row,
this.getCurrentColor_(),
this.framesheet.getCurrentFrame(),
this.piskelController.getCurrentFrame(),
this.overlayFrame,
this.wrapEvtInfo_(event)
);
@ -176,7 +176,7 @@
this.currentToolBehavior.releaseToolAt(
coords.col, coords.row,
this.getCurrentColor_(),
this.framesheet.getCurrentFrame(),
this.piskelController.getCurrentFrame(),
this.overlayFrame,
this.wrapEvtInfo_(event)
);
@ -247,7 +247,7 @@
};
ns.DrawingController.prototype.renderFrame = function () {
var frame = this.framesheet.getCurrentFrame();
var frame = this.piskelController.getCurrentFrame();
var serializedFrame = frame.serialize();
if (this.serializedFrame != serializedFrame) {
if (!frame.isSameSize(this.overlayFrame)) {
@ -278,8 +278,8 @@
leftSectionWidth = $('.left-column').outerWidth(true),
rightSectionWidth = $('.right-column').outerWidth(true),
availableViewportWidth = $('#main-wrapper').width() - leftSectionWidth - rightSectionWidth,
framePixelHeight = this.framesheet.getCurrentFrame().getHeight(),
framePixelWidth = this.framesheet.getCurrentFrame().getWidth();
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);
@ -300,7 +300,7 @@
this.renderer.updateDPI(dpi);
this.overlayRenderer.updateDPI(dpi);
var currentFrameHeight = this.framesheet.getCurrentFrame().getHeight();
var currentFrameHeight = this.piskelController.getCurrentFrame().getHeight();
var canvasHeight = currentFrameHeight * dpi;
if (pskl.UserSettings.get(pskl.UserSettings.SHOW_GRID)) {
canvasHeight += Constants.GRID_STROKE_WIDTH * currentFrameHeight;

View File

@ -3,6 +3,14 @@
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
*/
@ -28,12 +36,4 @@
message.remove();
}
};
/**
* @public
*/
ns.NotificationController.prototype.init = function() {
$.subscribe(Events.SHOW_NOTIFICATION, $.proxy(this.displayMessage_, this));
$.subscribe(Events.HIDE_NOTIFICATION, $.proxy(this.removeMessage_, this));
};
})();

View File

@ -1,9 +1,35 @@
(function () {
var ns = $.namespace("pskl.controller");
ns.PaletteController = function () {
this.paletteRoot = null;
this.paletteColors = [];
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.addColorToPalette_(color);
}, this));
$.subscribe(Events.SECONDARY_COLOR_UPDATED, $.proxy(function(evt, color) {
this.updateColorPicker_(color, $('#secondary-color-picker'));
this.addColorToPalette_(color);
}, 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));
};
/**
@ -13,30 +39,11 @@
var inputPicker = $(evt.target);
if(evt.data.isPrimary) {
$.publish(Events.PRIMARY_COLOR_SELECTED, [inputPicker.val()]);
}
else {
} else {
$.publish(Events.SECONDARY_COLOR_SELECTED, [inputPicker.val()]);
}
};
/**
* @private
*/
ns.PaletteController.prototype.addColorToPalette_ = function (color) {
if (this.paletteColors.indexOf(color) == -1 && color != Constants.TRANSPARENT_COLOR) {
this.paletteColors.push(color);
}
};
/**
* @private
*/
ns.PaletteController.prototype.addColorsToPalette_ = function (colors) {
for(var color in colors) {
this.addColorToPalette_(color);
}
};
/**
* @private
*/
@ -71,45 +78,6 @@
colorPicker[0].color.fromString(color);
}
};
/**
* @public
*/
ns.PaletteController.prototype.init = function(framesheet) {
this.paletteRoot = $("#palette");
this.framesheet = framesheet;
// Initialize palette:
this.addColorsToPalette_(this.framesheet.getUsedColors());
$.subscribe(Events.FRAMESHEET_RESET, $.proxy(function(evt) {
this.addColorsToPalette_(this.framesheet.getUsedColors());
}, this));
this.paletteRoot.mouseup($.proxy(this.onPaletteColorClick_, this));
$.subscribe(Events.PRIMARY_COLOR_UPDATED, $.proxy(function(evt, color) {
this.updateColorPicker_(color, $('#color-picker'));
this.addColorToPalette_(color);
}, this));
$.subscribe(Events.SECONDARY_COLOR_UPDATED, $.proxy(function(evt, color) {
this.updateColorPicker_(color, $('#secondary-color-picker'));
this.addColorToPalette_(color);
}, 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));
};
})();

View File

@ -0,0 +1,125 @@
(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;
$.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.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.piskel.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.piskel.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.piskel.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.piskel.getLayers();
layers.forEach(function (l) {
l.duplicateFrameAt(index);
});
};
ns.PiskelController.prototype.moveFrame = function (fromIndex, toIndex) {
var layers = this.piskel.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.createLayer = function (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);
};
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

@ -1,8 +1,8 @@
(function () {
var ns = $.namespace("pskl.controller");
ns.PreviewFilmController = function (framesheet, container, dpi) {
ns.PreviewFilmController = function (piskelController, container, dpi) {
this.framesheet = framesheet;
this.piskelController = piskelController;
this.container = container;
this.dpi = this.calculateDPI_();
@ -19,8 +19,8 @@
};
ns.PreviewFilmController.prototype.addFrame = function () {
this.framesheet.addEmptyFrame();
this.framesheet.setCurrentFrameIndex(this.framesheet.getFrameCount() - 1);
this.piskelController.addEmptyFrame();
this.piskelController.setCurrentFrameIndex(this.piskelController.getFrameCount() - 1);
this.updateScrollerOverflows();
};
@ -68,7 +68,7 @@
// Manually remove tooltips since mouseout events were shortcut by the DOM refresh:
$(".tooltip").remove();
var frameCount = this.framesheet.getFrameCount();
var frameCount = this.piskelController.getFrameCount();
for (var i = 0, l = frameCount; i < l ; i++) {
this.container.append(this.createPreviewTile_(i));
@ -110,8 +110,8 @@
var originFrameId = parseInt(ui.item.data("tile-number"), 10);
var targetInsertionId = $('.preview-tile').index(ui.item);
this.framesheet.moveFrame(originFrameId, targetInsertionId);
this.framesheet.setCurrentFrameIndex(targetInsertionId);
this.piskelController.moveFrame(originFrameId, targetInsertionId);
this.piskelController.setCurrentFrameIndex(targetInsertionId);
// TODO(grosbouddha): move localstorage request to the model layer?
$.publish(Events.LOCALSTORAGE_REQUEST);
@ -123,13 +123,13 @@
* TODO(vincz): clean this giant rendering function & remove listeners.
*/
ns.PreviewFilmController.prototype.createPreviewTile_ = function(tileNumber) {
var currentFrame = this.framesheet.getFrameByIndex(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.framesheet.getCurrentFrame() == currentFrame) {
if (this.piskelController.getCurrentFrame() == currentFrame) {
classname += " selected";
}
previewTileRoot.className = classname;
@ -159,7 +159,7 @@
previewTileRoot.appendChild(canvasContainer);
if(tileNumber > 0 || this.framesheet.getFrameCount() > 1) {
if(tileNumber > 0 || this.piskelController.getFrameCount() > 1) {
// Add 'remove frame' button.
var deleteButton = document.createElement("button");
deleteButton.setAttribute('rel', 'tooltip');
@ -186,28 +186,28 @@
ns.PreviewFilmController.prototype.onPreviewClick_ = function (index, evt) {
// has not class tile-action:
if(!evt.target.classList.contains('tile-overlay')) {
this.framesheet.setCurrentFrameIndex(index);
this.piskelController.setCurrentFrameIndex(index);
}
};
ns.PreviewFilmController.prototype.onDeleteButtonClick_ = function (index, evt) {
this.framesheet.removeFrameByIndex(index);
this.piskelController.removeFrameAt(index);
$.publish(Events.LOCALSTORAGE_REQUEST); // Should come from model
this.updateScrollerOverflows();
};
ns.PreviewFilmController.prototype.onAddButtonClick_ = function (index, evt) {
this.framesheet.duplicateFrameByIndex(index);
this.piskelController.duplicateFrameAt(index);
$.publish(Events.LOCALSTORAGE_REQUEST); // Should come from model
this.framesheet.setCurrentFrameIndex(index + 1);
this.piskelController.setCurrentFrameIndex(index + 1);
this.updateScrollerOverflows();
};
/**
* Calculate the preview DPI depending on the framesheet size
* Calculate the preview DPI depending on the piskel size
*/
ns.PreviewFilmController.prototype.calculateDPI_ = function () {
var curFrame = this.framesheet.getCurrentFrame(),
var curFrame = this.piskelController.getCurrentFrame(),
frameHeight = curFrame.getHeight(),
frameWidth = curFrame.getWidth(),
maxFrameDim = Math.max(frameWidth, frameHeight);

View File

@ -15,8 +15,8 @@
var SEL_SETTING_CLS = 'has-expanded-drawer';
var EXP_DRAWER_CLS = 'expanded';
ns.SettingsController = function (framesheet) {
this.framesheet = framesheet;
ns.SettingsController = function (piskelController) {
this.piskelController = piskelController;
this.drawerContainer = document.getElementById("drawer-container");
this.settingsContainer = $('[data-pskl-controller=settings]');
this.expanded = false;
@ -48,7 +48,7 @@
ns.SettingsController.prototype.loadSetting = function (setting) {
this.drawerContainer.innerHTML = pskl.utils.Template.get(settings[setting].template);
(new settings[setting].controller(this.framesheet)).init();
(new settings[setting].controller(this.piskelController)).init();
this.settingsContainer.addClass(EXP_DRAWER_CLS);

View File

@ -1,7 +1,7 @@
(function () {
var ns = $.namespace("pskl.controller.settings");
ns.GifExportController = function (framesheet) {
this.framesheet = framesheet;
ns.GifExportController = function (piskelController) {
this.piskelController = piskelController;
};
ns.GifExportController.prototype.init = function () {
@ -61,7 +61,7 @@
};
ns.GifExportController.prototype.createRadioForDpi_ = function (dpi, template) {
var label = dpi[0]*this.framesheet.getWidth() + "x" + dpi[0]*this.framesheet.getHeight();
var label = dpi[0]*this.piskelController.getWidth() + "x" + dpi[0]*this.piskelController.getHeight();
var value = dpi[0];
var radioHTML = pskl.utils.Template.replace(template, {value : value, label : label});
var radio = pskl.utils.Template.createFromHTML(radioHTML);
@ -86,12 +86,12 @@
var gif = new window.GIF({
workers: 2,
quality: 10,
width: this.framesheet.getWidth()*dpi,
height: this.framesheet.getHeight()*dpi
width: this.piskelController.getWidth()*dpi,
height: this.piskelController.getHeight()*dpi
});
for (var i = 0; i < this.framesheet.frames.length; i++) {
var frame = this.framesheet.frames[i];
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

View File

@ -1,15 +1,28 @@
(function () {
var ns = $.namespace("pskl.model");
ns.Frame = function (pixels) {
this.pixels = pixels;
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.createEmpty = function (width, height) {
var pixels = ns.Frame.createEmptyPixelGrid_(width, height);
return new ns.Frame(pixels);
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) {
@ -25,11 +38,13 @@
};
ns.Frame.createEmptyFromFrame = function (frame) {
return ns.Frame.createEmpty(frame.getWidth(), frame.getHeight());
return new ns.Frame(frame.getWidth(), frame.getHeight());
};
ns.Frame.prototype.clone = function () {
return new ns.Frame(this.getPixels());
var clone = new ns.Frame(this.width, this.height);
clone.setPixels(this.getPixels());
return clone;
};
/**
@ -46,8 +61,6 @@
this.pixels = this.clonePixels_(pixels);
};
ns.Frame.prototype.clear = function () {
var pixels = ns.Frame.createEmptyPixelGrid_(this.getWidth(), this.getHeight());
this.setPixels(pixels);
@ -77,12 +90,20 @@
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.pixels.length;
return this.width;
};
ns.Frame.prototype.getHeight = function () {
return this.pixels[0].length;
return this.height;
};
ns.Frame.prototype.containsPixel = function (col, row) {

View File

@ -23,14 +23,14 @@
this.frames.push(frame);
};
ns.FrameSheet.prototype.getFrameCount = function () {
return this.frames.length;
};
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()]);
@ -101,7 +101,7 @@
ns.FrameSheet.prototype.hasFrameAtIndex = function(index) {
return (index >= 0 && index < this.getFrameCount());
return (index >= 0 && index < this.frames.length);
};
ns.FrameSheet.prototype.getFrameByIndex = function(index) {
@ -122,10 +122,9 @@
}
this.frames.splice(index, 1);
// Current frame index might not be valid anymore
if (!this.hasFrameAtIndex(this.currentFrameIndex)) {
// if not select last frame available
this.setCurrentFrameIndex(this.getFrameCount() - 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);

82
js/model/Layer.js Normal file
View File

@ -0,0 +1,82 @@
(function () {
var ns = $.namespace('pskl.model');
ns.Layer = function (name) {
if (!name) {
throw 'Invalid arguments in Layer constructor : \'name\' is mandatory';
} else {
this.name = name;
this.frames = [];
}
};
ns.Layer.prototype.getName = function () {
return this.name;
};
ns.Layer.prototype.getFrames = function () {
return this.frames;
};
ns.Layer.prototype.getFrameAt = function (index) {
return this.frames[index];
};
ns.Layer.prototype.addFrame = function (frame) {
this.frames.push(frame);
};
ns.Layer.prototype.addFrameAt = function (frame, index) {
this.frames.splice(index, 0, frame);
};
ns.Layer.prototype.removeFrame = function (frame) {
var index = this.frames.indexOf(frame);
this.removeFrameAt(index);
};
ns.Layer.prototype.removeFrameAt = function (index) {
if (this.frames[index]) {
this.frames.splice(index, 1);
} else {
throw 'Invalid index in removeFrameAt : ' + index + ' (size : ' + this.length() + ')';
}
};
ns.Layer.prototype.moveFrame = function (fromIndex, toIndex) {
var frame = this.frames.splice(fromIndex, 1)[0];
this.frames.splice(toIndex, 0, frame);
};
ns.Layer.prototype.swapFramesAt = function (fromIndex, toIndex) {
var fromFrame = this.frames[fromIndex];
var toFrame = this.frames[toIndex];
if (fromFrame && toFrame) {
this.frames[toIndex] = fromFrame;
this.frames[fromIndex] = toFrame;
} else {
console.log('frames', this.frames);
console.log('fromIndex', fromIndex, 'toIndex', toIndex);
throw 'Frame not found in moveFrameAt';
}
};
ns.Layer.prototype.duplicateFrame = function (frame) {
var index = this.frames.indexOf(frame);
this.duplicateFrameAt();
};
ns.Layer.prototype.duplicateFrameAt = function (index) {
var frame = this.frames[index];
if (frame) {
var clone = frame.clone();
this.addFrameAt(clone, index);
} else {
throw 'Frame not found in duplicateFrameAt';
}
};
ns.Layer.prototype.length = function () {
return this.frames.length;
};
})();

66
js/model/Piskel.js Normal file
View File

@ -0,0 +1,66 @@
(function () {
var ns = $.namespace('pskl.model');
/**
* @constructor
* @param {Number} width
* @param {Number} height
*/
ns.Piskel = function (width, height, fps) {
if (width && height && fps) {
/** @type {Array} */
this.layers = [];
/** @type {Number} */
this.fps = fps;
/** @type {Number} */
this.width = width;
/** @type {Number} */
this.height = height;
} else {
throw 'Missing arguments in Piskel constructor : ' + Array.prototype.join.call(arguments, ",");
}
};
ns.Piskel.prototype.getLayers = function () {
return this.layers;
};
ns.Piskel.prototype.getHeight = function () {
return this.height;
};
ns.Piskel.prototype.getWidth = function () {
return this.width;
};
ns.Piskel.prototype.getFps = function () {
return this.fps;
};
ns.Piskel.prototype.getLayers = function () {
return this.layers;
};
ns.Piskel.prototype.getLayerAt = function (index) {
return this.layers[index];
};
ns.Piskel.prototype.addLayer = function (layer) {
this.layers.push(layer);
};
ns.Piskel.prototype.removeLayer = function (layer) {
var index = this.layers.indexOf(layer);
if (index != -1) {
this.layers.splice(index, 1);
}
};
ns.Piskel.prototype.removeLayerAt = function (index) {
this.layers.splice(index, 1);
};
})();

View File

@ -2,15 +2,15 @@
var ns = $.namespace("pskl.rendering");
ns.SpritesheetRenderer = function (framesheet) {
this.framesheet = framesheet;
ns.SpritesheetRenderer = function (piskelController) {
this.piskelController = piskelController;
};
ns.SpritesheetRenderer.prototype.renderAsImageDataSpritesheetPNG = function () {
var canvas = this.createCanvas_();
for (var i = 0 ; i < this.framesheet.getFrameCount() ; i++) {
var frame = this.framesheet.getFrameByIndex(i);
this.drawFrameInCanvas_(frame, canvas, i * this.framesheet.getWidth(), 0);
for (var i = 0 ; i < this.piskelController.getFrameCount() ; i++) {
var frame = this.piskelController.getFrameAt(i);
this.drawFrameInCanvas_(frame, canvas, i * this.piskelController.getWidth(), 0);
}
return canvas.toDataURL("image/png");
};
@ -32,10 +32,10 @@
};
ns.SpritesheetRenderer.prototype.createCanvas_ = function () {
var frameCount = this.framesheet.getFrameCount();
var frameCount = this.piskelController.getFrameCount();
if (frameCount > 0){
var width = frameCount * this.framesheet.getWidth();
var height = this.framesheet.getHeight();
var width = frameCount * this.piskelController.getWidth();
var height = this.piskelController.getHeight();
return pskl.CanvasUtils.createCanvas(width, height);
} else {
throw "Cannot render empty Spritesheet";

View File

@ -1,9 +1,9 @@
(function () {
var ns = $.namespace("pskl.selection");
ns.SelectionManager = function (framesheet) {
ns.SelectionManager = function (piskelController) {
this.framesheet = framesheet;
this.piskelController = piskelController;
this.currentSelection = null;
};
@ -52,10 +52,10 @@
ns.SelectionManager.prototype.onCut_ = function(evt) {
if(this.currentSelection) {
// Put cut target into the selection:
this.currentSelection.fillSelectionFromFrame(this.framesheet.getCurrentFrame());
this.currentSelection.fillSelectionFromFrame(this.piskelController.getCurrentFrame());
var pixels = this.currentSelection.pixels;
var currentFrame = this.framesheet.getCurrentFrame();
var currentFrame = this.piskelController.getCurrentFrame();
for(var i=0, l=pixels.length; i<l; i++) {
try {
currentFrame.setPixel(pixels[i].col, pixels[i].row, Constants.TRANSPARENT_COLOR);
@ -73,7 +73,7 @@
ns.SelectionManager.prototype.onPaste_ = function(evt) {
if(this.currentSelection && this.currentSelection.hasPastedContent) {
var pixels = this.currentSelection.pixels;
var currentFrame = this.framesheet.getCurrentFrame();
var currentFrame = this.piskelController.getCurrentFrame();
for(var i=0, l=pixels.length; i<l; i++) {
try {
currentFrame.setPixel(
@ -91,8 +91,8 @@
* @private
*/
ns.SelectionManager.prototype.onCopy_ = function(evt) {
if(this.currentSelection && this.framesheet.getCurrentFrame()) {
this.currentSelection.fillSelectionFromFrame(this.framesheet.getCurrentFrame());
if(this.currentSelection && this.piskelController.getCurrentFrame()) {
this.currentSelection.fillSelectionFromFrame(this.piskelController.getCurrentFrame());
}
else {
throw "Bad state for CUT callback in SelectionManager";

View File

@ -1,7 +1,7 @@
(function () {
var ns = $.namespace("pskl.service");
ns.HistoryService = function (framesheet) {
this.framesheet = framesheet;
ns.HistoryService = function (piskelController) {
this.piskelController = piskelController;
};
ns.HistoryService.prototype.init = function () {
@ -12,16 +12,16 @@
};
ns.HistoryService.prototype.saveState = function () {
this.framesheet.getCurrentFrame().saveState();
this.piskelController.getCurrentFrame().saveState();
};
ns.HistoryService.prototype.undo = function () {
this.framesheet.getCurrentFrame().loadPreviousState();
this.piskelController.getCurrentFrame().loadPreviousState();
$.publish(Events.FRAMESHEET_RESET);
};
ns.HistoryService.prototype.redo = function () {
this.framesheet.getCurrentFrame().loadNextState();
this.piskelController.getCurrentFrame().loadNextState();
$.publish(Events.FRAMESHEET_RESET);
};

View File

@ -1,19 +1,19 @@
(function () {
var ns = $.namespace("pskl.service");
ns.LocalStorageService = function (framesheet_) {
ns.LocalStorageService = function (piskelController) {
if(framesheet_ === undefined) {
throw "Bad LocalStorageService initialization: <undefined frameSheet>";
if(piskelController === undefined) {
throw "Bad LocalStorageService initialization: <undefined piskelController>";
}
this.framesheet = framesheet_;
this.piskelController = piskelController;
this.localStorageThrottler_ = null;
};
/**
* @public
*/
ns.LocalStorageService.prototype.init = function(framesheet_) {
ns.LocalStorageService.prototype.init = function(piskelController) {
$.subscribe(Events.LOCALSTORAGE_REQUEST, $.proxy(this.persistToLocalStorageRequest_, this));
};
@ -38,7 +38,7 @@
ns.LocalStorageService.prototype.persistToLocalStorage_ = function() {
console.log('[LocalStorage service]: Snapshot stored');
window.localStorage.snapShot = this.framesheet.serialize();
window.localStorage.snapShot = this.piskelController.serialize();
};
/**
@ -46,8 +46,8 @@
*/
ns.LocalStorageService.prototype.restoreFromLocalStorage_ = function() {
this.framesheet.deserialize(window.localStorage.snapShot);
this.framesheet.setCurrentFrameIndex(0);
this.piskelController.deserialize(window.localStorage.snapShot);
this.piskelController.setCurrentFrameIndex(0);
};
/**

22
js/utils/FrameUtils.js Normal file
View File

@ -0,0 +1,22 @@
(function () {
var ns = $.namespace('pskl.utils');
ns.FrameUtils = {
merge : function (frames) {
var merged = frames[0].clone();
var w = merged.getWidth(), h = merged.getHeight();
for (var i = 1 ; i < frames.length ; i++) {
pskl.utils.FrameUtils.mergeFrames_(merged, frames[i]);
}
return merged;
},
mergeFrames_ : function (frameA, frameB) {
frameB.forEachPixel(function (p, col, row) {
if (p != Constants.TRANSPARENT_COLOR) {
frameA.setPixel(col, row, p);
}
});
}
};
})();

64
js/utils/Serializer.js Normal file
View File

@ -0,0 +1,64 @@
(function () {
var ns = $.namespace('pskl.utils');
ns.Serializer = {
serializePiskel : function (piskel) {
var serializedLayers = piskel.getLayers().map(function (l) {
return pskl.utils.Serializer.serializeLayer(l);
});
return JSON.stringify({
modelVersion : Constants.MODEL_VERSION,
piskel : {
height : piskel.getHeight(),
width : piskel.getWidth(),
fps : piskel.getFps(),
layers : serializedLayers
}
});
},
serializeLayer : function (layer) {
var serializedFrames = layer.getFrames().map(function (f) {
return f.serialize();
});
return JSON.stringify({
name : layer.getName(),
frames : serializedFrames
});
},
deserializePiskel : function (json) {
var data = JSON.parse(json);
if (data.modelVersion == Constants.MODEL_VERSION) {
var pData = data.piskel;
var layers = pData.layers.map(function (serializedLayer) {
return pskl.utils.Serializer.deserializeLayer(serializedLayer);
});
var piskel = new pskl.model.Piskel(pData.width, pData.height, pData.fps);
layers.forEach(function (layer) {
piskel.addLayer(layer);
});
return piskel;
} else {
// pre-layer implementation adapter
}
},
deserializeLayer : function (json) {
var lData = JSON.parse(json);
var frames = lData.frames.map(function (serializedFrame) {
return pskl.utils.Serializer.deserializeFrame(serializedFrame);
});
var layer = new pskl.model.Layer(lData.name);
frames.forEach(function (frame) {
layer.addFrame(frame);
});
return layer;
},
deserializeFrame : function (json) {
var framePixelGrid = JSON.parse(json);
return pskl.model.Frame.fromPixelGrid(framePixelGrid);
}
};
})();

View File

@ -13,9 +13,11 @@ exports.scripts = [
// Libraries
"js/utils/core.js",
"js/utils/Template.js",
"js/utils/PixelUtils.js",
"js/utils/CanvasUtils.js",
"js/utils/FrameUtils.js",
"js/utils/PixelUtils.js",
"js/utils/Serializer.js",
"js/utils/Template.js",
"js/utils/UserSettings.js",
"js/lib/jsColor_1_4_0/jscolor.js",
@ -24,7 +26,10 @@ exports.scripts = [
// Models
"js/model/Frame.js",
"js/model/FrameSheet.js",
"js/model/Layer.js",
"js/model/Piskel.js",
// Selection
"js/selection/SelectionManager.js",
"js/selection/BaseSelection.js",
"js/selection/RectangularSelection.js",
@ -36,6 +41,7 @@ exports.scripts = [
"js/rendering/SpritesheetRenderer.js",
// Controllers
"js/controller/PiskelController.js",
"js/controller/DrawingController.js",
"js/controller/PreviewFilmController.js",
"js/controller/AnimatedPreviewController.js",
@ -68,5 +74,5 @@ exports.scripts = [
"js/drawingtools/ColorPicker.js",
// Application controller and initialization
"js/piskel.js"
"js/app.js"
];

View File

@ -8,8 +8,8 @@
<input id="secondary-color-picker" class="secondary-color-picker color {hash:true}" type="text" value="" />
</div>
<div class="tool-icon tool-palette">
<div id="palette" class="palette">
<span class="tool-icon palette-color transparent-color" data-color="TRANSPARENT" title="Transparent"></span>
<div>
<span class="tool-icon palette-color" data-color="TRANSPARENT" title="Transparent"></span>
</div>
</div>
</div>