mirror of
https://github.com/piskelapp/piskel.git
synced 2023-08-10 21:12:52 +03:00
preview zoom
This commit is contained in:
73
zoom/js/controller/AnimatedPreviewController.js
Normal file
73
zoom/js/controller/AnimatedPreviewController.js
Normal file
@@ -0,0 +1,73 @@
|
||||
(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.setFPS(Constants.DEFAULT.FPS);
|
||||
|
||||
var renderingOptions = {
|
||||
"zoom": this.calculateZoom_(),
|
||||
"height" : "auto",
|
||||
"width" : "auto"
|
||||
};
|
||||
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.getFPS = function () {
|
||||
return this.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.calculateZoom_ = 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.setDPI(this.dpi);
|
||||
};
|
||||
})();
|
||||
387
zoom/js/controller/DrawingController.js
Normal file
387
zoom/js/controller/DrawingController.js
Normal file
@@ -0,0 +1,387 @@
|
||||
(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;
|
||||
|
||||
this.zoom = this.calculateZoom_();
|
||||
this.xOffset = 0;
|
||||
this.yOffset = 0;
|
||||
|
||||
// TODO(vincz): Store user prefs in a localstorage string ?
|
||||
var renderingOptions = {
|
||||
"zoom": this.zoom,
|
||||
"supportGridRendering" : true,
|
||||
"height" : this.getContainerHeight_(),
|
||||
"width" : this.getContainerWidth_(),
|
||||
"xOffset" : this.xOffset,
|
||||
"yOffset" : this.yOffset
|
||||
};
|
||||
|
||||
this.overlayRenderer = new pskl.rendering.FrameRenderer(this.container, renderingOptions, ["canvas-overlay"]);
|
||||
this.renderer = new pskl.rendering.FrameRenderer(this.container, renderingOptions, ["drawing-canvas"]);
|
||||
this.layersBelowRenderer = new pskl.rendering.FrameRenderer(this.container, renderingOptions, ["layers-canvas", "layers-below-canvas"]);
|
||||
this.layersAboveRenderer = new pskl.rendering.FrameRenderer(this.container, renderingOptions, ["layers-canvas", "layers-above-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.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.updateZoom_, this));
|
||||
|
||||
this.updateZoom_();
|
||||
};
|
||||
|
||||
ns.DrawingController.prototype.initMouseBehavior = function() {
|
||||
var body = $('body');
|
||||
this.container.mousedown($.proxy(this.onMousedown_, this));
|
||||
this.container.mousemove($.proxy(this.onMousemove_, this));
|
||||
this.container.on('mousewheel', $.proxy(this.onMousewheel_, this));
|
||||
|
||||
body.mouseup($.proxy(this.onMouseup_, this));
|
||||
|
||||
// Deactivate right click:
|
||||
body.contextmenu(this.onCanvasContextMenu_);
|
||||
};
|
||||
|
||||
ns.DrawingController.prototype.startDPIUpdateTimer_ = function () {
|
||||
if (this.zoomUpdateTimer) {
|
||||
window.clearInterval(this.zoomUpdateTimer);
|
||||
}
|
||||
this.zoomUpdateTimer = window.setTimeout($.proxy(this.updateZoom_, this), 200);
|
||||
},
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
ns.DrawingController.prototype.onUserSettingsChange_ = function (evt, settingsName, settingsValue) {
|
||||
if(settingsName == pskl.UserSettings.SHOW_GRID) {
|
||||
this.updateZoom_();
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* @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;
|
||||
}
|
||||
};
|
||||
|
||||
ns.DrawingController.prototype.onMousewheel_ = function (jQueryEvent) {
|
||||
var event = jQueryEvent.originalEvent;
|
||||
var delta = event.wheelDeltaY;
|
||||
if (delta > 0) {
|
||||
this.setZoom(this.zoom + 1);
|
||||
} else if (delta < 0) {
|
||||
this.setZoom(this.zoom - 1);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @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 = [this.zoom, this.xOffset, this.yOffset, frame.serialize()].join('-');
|
||||
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.zoom, this.xOffset, this.yOffset, this.overlayFrame.serialize()].join('-');
|
||||
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 serializedLayerFrame = [
|
||||
this.zoom,
|
||||
currentFrameIndex,
|
||||
currentLayerIndex,
|
||||
layers.length
|
||||
].join("-");
|
||||
|
||||
if (this.serializedLayerFrame != serializedLayerFrame) {
|
||||
this.layersAboveRenderer.clear();
|
||||
this.layersBelowRenderer.clear();
|
||||
|
||||
var downLayers = layers.slice(0, currentLayerIndex);
|
||||
var downFrame = this.getFrameForLayersAt_(currentFrameIndex, downLayers);
|
||||
this.layersBelowRenderer.render(downFrame);
|
||||
|
||||
if (currentLayerIndex + 1 < layers.length) {
|
||||
var upLayers = layers.slice(currentLayerIndex + 1, layers.length);
|
||||
var upFrame = this.getFrameForLayersAt_(currentFrameIndex, upLayers);
|
||||
this.layersAboveRenderer.render(upFrame);
|
||||
}
|
||||
|
||||
this.serializedLayerFrame = serializedLayerFrame;
|
||||
}
|
||||
};
|
||||
|
||||
ns.DrawingController.prototype.getFrameForLayersAt_ = function (frameIndex, layers) {
|
||||
var frames = layers.map(function (l) {
|
||||
return l.getFrameAt(frameIndex);
|
||||
});
|
||||
return pskl.utils.FrameUtils.merge(frames);
|
||||
};
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
ns.DrawingController.prototype.calculateZoom_ = function() {
|
||||
var frameHeight = this.piskelController.getCurrentFrame().getHeight(),
|
||||
frameWidth = this.piskelController.getCurrentFrame().getWidth();
|
||||
|
||||
return Math.min(this.getAvailableWidth_()/frameWidth, this.getAvailableHeight_()/frameHeight);
|
||||
};
|
||||
|
||||
ns.DrawingController.prototype.getAvailableHeight_ = function () {
|
||||
return $('#main-wrapper').height();
|
||||
};
|
||||
|
||||
ns.DrawingController.prototype.getAvailableWidth_ = function () {
|
||||
var leftSectionWidth = $('.left-column').outerWidth(true),
|
||||
rightSectionWidth = $('.right-column').outerWidth(true),
|
||||
availableWidth = $('#main-wrapper').width() - leftSectionWidth - rightSectionWidth;
|
||||
return availableWidth;
|
||||
};
|
||||
|
||||
ns.DrawingController.prototype.getContainerHeight_ = function () {
|
||||
return this.calculateZoom_() * this.piskelController.getCurrentFrame().getHeight();
|
||||
};
|
||||
|
||||
ns.DrawingController.prototype.getContainerWidth_ = function () {
|
||||
return this.calculateZoom_() * this.piskelController.getCurrentFrame().getWidth();
|
||||
};
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
ns.DrawingController.prototype.updateZoom_ = function() {
|
||||
this.setZoom(this.calculateZoom_());
|
||||
|
||||
var currentFrameHeight = this.piskelController.getCurrentFrame().getHeight();
|
||||
var canvasHeight = currentFrameHeight * this.zoom;
|
||||
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'
|
||||
});
|
||||
};
|
||||
|
||||
ns.DrawingController.prototype.setZoom = function (zoom) {
|
||||
this.zoom = zoom;
|
||||
this.overlayRenderer.setZoom(this.zoom);
|
||||
this.renderer.setZoom(this.zoom);
|
||||
this.layersAboveRenderer.setZoom(this.zoom);
|
||||
this.layersBelowRenderer.setZoom(this.zoom);
|
||||
};
|
||||
|
||||
ns.DrawingController.prototype.moveOffset = function (xOffset, yOffset) {
|
||||
this.xOffset = xOffset;
|
||||
this.yOffset = yOffset;
|
||||
this.overlayRenderer.setDisplayOffset(xOffset, yOffset);
|
||||
this.renderer.setDisplayOffset(xOffset, yOffset);
|
||||
this.layersAboveRenderer.setDisplayOffset(xOffset, yOffset);
|
||||
this.layersBelowRenderer.setDisplayOffset(xOffset, yOffset);
|
||||
};
|
||||
})();
|
||||
59
zoom/js/controller/LayersListController.js
Normal file
59
zoom/js/controller/LayersListController.js
Normal file
@@ -0,0 +1,59 @@
|
||||
(function () {
|
||||
var ns = $.namespace('pskl.controller');
|
||||
|
||||
ns.LayersListController = function (piskelController) {
|
||||
this.piskelController = piskelController;
|
||||
};
|
||||
|
||||
ns.LayersListController.prototype.init = function () {
|
||||
this.layerItemTemplate_ = pskl.utils.Template.get('layer-item-template');
|
||||
this.rootEl = document.querySelectorAll('.layers-list-container')[0];
|
||||
this.layersListEl = document.querySelectorAll('.layers-list')[0];
|
||||
|
||||
this.rootEl.addEventListener('click', this.onClick_.bind(this));
|
||||
|
||||
$.subscribe(Events.PISKEL_RESET, this.renderLayerList_.bind(this));
|
||||
|
||||
this.renderLayerList_();
|
||||
};
|
||||
|
||||
ns.LayersListController.prototype.renderLayerList_ = function () {
|
||||
this.layersListEl.innerHTML = '';
|
||||
var layers = this.piskelController.getLayers();
|
||||
layers.forEach(this.addLayerItem.bind(this));
|
||||
};
|
||||
|
||||
ns.LayersListController.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.LayersListController.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.LayersListController.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();
|
||||
}
|
||||
};
|
||||
})();
|
||||
39
zoom/js/controller/NotificationController.js
Normal file
39
zoom/js/controller/NotificationController.js
Normal 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();
|
||||
}
|
||||
};
|
||||
})();
|
||||
83
zoom/js/controller/PaletteController.js
Normal file
83
zoom/js/controller/PaletteController.js
Normal 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);
|
||||
}
|
||||
};
|
||||
})();
|
||||
|
||||
|
||||
|
||||
194
zoom/js/controller/PiskelController.js
Normal file
194
zoom/js/controller/PiskelController.js
Normal file
@@ -0,0 +1,194 @@
|
||||
(function () {
|
||||
var ns = $.namespace('pskl.controller');
|
||||
|
||||
ns.PiskelController = function (piskel) {
|
||||
if (piskel) {
|
||||
this.setPiskel(piskel);
|
||||
} else {
|
||||
throw 'A piskel instance is mandatory for instanciating PiskelController';
|
||||
}
|
||||
};
|
||||
|
||||
ns.PiskelController.prototype.setPiskel = function (piskel) {
|
||||
this.piskel = piskel;
|
||||
this.currentLayerIndex = 0;
|
||||
this.currentFrameIndex = 0;
|
||||
|
||||
this.layerIdCounter = 1;
|
||||
|
||||
$.publish(Events.PISKEL_RESET);
|
||||
$.publish(Events.FRAME_SIZE_CHANGED);
|
||||
};
|
||||
|
||||
ns.PiskelController.prototype.getHeight = function () {
|
||||
return this.piskel.getHeight();
|
||||
};
|
||||
|
||||
ns.PiskelController.prototype.getWidth = function () {
|
||||
return this.piskel.getWidth();
|
||||
};
|
||||
|
||||
/**
|
||||
* TODO : this should be removed
|
||||
* FPS should be stored in the Piskel model and not in the
|
||||
* animationController
|
||||
* Then piskelController should be able to return this information
|
||||
* @return {Number} Frames per second for the current animation
|
||||
*/
|
||||
ns.PiskelController.prototype.getFPS = function () {
|
||||
return pskl.app.animationController.getFPS();
|
||||
};
|
||||
|
||||
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.PISKEL_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.PISKEL_RESET);
|
||||
};
|
||||
|
||||
ns.PiskelController.prototype.setCurrentLayerIndex = function (index) {
|
||||
this.currentLayerIndex = index;
|
||||
$.publish(Events.PISKEL_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.load = function (data) {
|
||||
this.deserialize(JSON.stringify(data));
|
||||
};
|
||||
})();
|
||||
224
zoom/js/controller/PreviewFilmController.js
Normal file
224
zoom/js/controller/PreviewFilmController.js
Normal file
@@ -0,0 +1,224 @@
|
||||
(function () {
|
||||
var ns = $.namespace("pskl.controller");
|
||||
ns.PreviewFilmController = function (piskelController, container) {
|
||||
|
||||
this.piskelController = piskelController;
|
||||
this.container = container;
|
||||
this.refreshZoom_();
|
||||
|
||||
this.redrawFlag = true;
|
||||
};
|
||||
|
||||
ns.PreviewFilmController.prototype.init = function() {
|
||||
$.subscribe(Events.TOOL_RELEASED, this.flagForRedraw_.bind(this));
|
||||
$.subscribe(Events.PISKEL_RESET, this.flagForRedraw_.bind(this));
|
||||
$.subscribe(Events.PISKEL_RESET, this.refreshZoom_.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.refreshZoom_ = function () {
|
||||
this.zoom = this.calculateZoom_();
|
||||
};
|
||||
|
||||
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 = {
|
||||
"zoom" : this.zoom,
|
||||
"height" : "auto",
|
||||
"width" : "auto"
|
||||
};
|
||||
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.calculateZoom_ = 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;
|
||||
};
|
||||
})();
|
||||
70
zoom/js/controller/SettingsController.js
Normal file
70
zoom/js/controller/SettingsController.js
Normal 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;
|
||||
};
|
||||
|
||||
})();
|
||||
107
zoom/js/controller/ToolController.js
Normal file
107
zoom/js/controller/ToolController.js
Normal 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);
|
||||
};
|
||||
})();
|
||||
38
zoom/js/controller/settings/ApplicationSettingsController.js
Normal file
38
zoom/js/controller/settings/ApplicationSettingsController.js
Normal 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');
|
||||
}
|
||||
});
|
||||
};
|
||||
})();
|
||||
129
zoom/js/controller/settings/GifExportController.js
Normal file
129
zoom/js/controller/settings/GifExportController.js
Normal file
@@ -0,0 +1,129 @@
|
||||
(function () {
|
||||
var ns = $.namespace("pskl.controller.settings");
|
||||
|
||||
ns.GifExportController = function (piskelController) {
|
||||
this.piskelController = piskelController;
|
||||
};
|
||||
|
||||
/**
|
||||
* List of Resolutions applicable for Gif export
|
||||
* @static
|
||||
* @type {Array} array of Objects {dpi:{Number}, default:{Boolean}}
|
||||
*/
|
||||
ns.GifExportController.RESOLUTIONS = [
|
||||
{
|
||||
'dpi' : 1
|
||||
},{
|
||||
'dpi' : 5
|
||||
},{
|
||||
'dpi' : 10,
|
||||
'default' : true
|
||||
},{
|
||||
'dpi' : 20
|
||||
}
|
||||
];
|
||||
|
||||
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.uploadForm = $("[name=gif-export-upload-form]");
|
||||
this.uploadForm.submit(this.onUploadFormSubmit_.bind(this));
|
||||
|
||||
this.createRadioElements_();
|
||||
};
|
||||
|
||||
ns.GifExportController.prototype.onUploadFormSubmit_ = function (evt) {
|
||||
evt.originalEvent.preventDefault();
|
||||
var selectedDpi = this.getSelectedDpi_(),
|
||||
fps = this.piskelController.getFPS(),
|
||||
dpi = selectedDpi;
|
||||
|
||||
this.renderAsImageDataAnimatedGIF(dpi, fps, this.onGifRenderingCompleted_.bind(this));
|
||||
};
|
||||
|
||||
ns.GifExportController.prototype.onGifRenderingCompleted_ = function (imageData) {
|
||||
this.updatePreview_(imageData);
|
||||
this.previewContainerEl.classList.add("preview-upload-ongoing");
|
||||
pskl.app.imageUploadService.upload(imageData, this.onImageUploadCompleted_.bind(this));
|
||||
};
|
||||
|
||||
ns.GifExportController.prototype.onImageUploadCompleted_ = function (imageUrl) {
|
||||
this.updatePreview_(imageUrl);
|
||||
this.previewContainerEl.classList.remove("preview-upload-ongoing");
|
||||
};
|
||||
|
||||
ns.GifExportController.prototype.updatePreview_ = function (src) {
|
||||
this.previewContainerEl.innerHTML = "<img style='max-width:240px;' src='"+src+"'/>";
|
||||
};
|
||||
|
||||
ns.GifExportController.prototype.getSelectedDpi_ = function () {
|
||||
var radiosColl = this.uploadForm.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.createRadioElements_ = function () {
|
||||
var resolutions = ns.GifExportController.RESOLUTIONS;
|
||||
for (var i = 0 ; i < resolutions.length ; i++) {
|
||||
var radio = this.createRadioForResolution_(resolutions[i]);
|
||||
this.radioGroupEl.appendChild(radio);
|
||||
}
|
||||
};
|
||||
|
||||
ns.GifExportController.prototype.createRadioForResolution_ = function (resolution) {
|
||||
var dpi = resolution.dpi;
|
||||
var label = dpi*this.piskelController.getWidth() + "x" + dpi*this.piskelController.getHeight();
|
||||
var value = dpi;
|
||||
|
||||
var radioHTML = pskl.utils.Template.replace(this.radioTemplate_, {value : value, label : label});
|
||||
var radioEl = pskl.utils.Template.createFromHTML(radioHTML);
|
||||
|
||||
if (resolution['default']) {
|
||||
var input = radioEl.getElementsByTagName("input")[0];
|
||||
input.setAttribute("checked", "checked");
|
||||
}
|
||||
|
||||
return radioEl;
|
||||
};
|
||||
|
||||
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();
|
||||
};
|
||||
})();
|
||||
Reference in New Issue
Block a user