feature : zoom

- Created AbstractRenderer in rendering package
- Created CachedRenderer and CachedFrameRenderer to extract basic frame
  caching logic from DrawingController
- Created RendererManager to synchronize updates made to several Renderer
  settings
- Moved FrameRenderer from pskl.rendering to pskl.rendering.frame
- Fixed the resize of the drawing area when the window is resized
This commit is contained in:
jdescottes
2013-11-01 15:39:42 +01:00
parent 3ce9aaa843
commit 51f86afe6e
11 changed files with 345 additions and 218 deletions

View File

@ -0,0 +1,22 @@
(function () {
var ns = $.namespace('pskl.rendering');
ns.AbstractRenderer = function () {};
ns.AbstractRenderer.prototype.render = function (frame) {throw 'abstract method should be implemented';};
ns.AbstractRenderer.prototype.clear = function () {throw 'abstract method should be implemented';};
ns.AbstractRenderer.prototype.getCoordinates = function (x, y) {throw 'abstract method should be implemented';};
ns.AbstractRenderer.prototype.setGridEnabled = function (b) {throw 'abstract method should be implemented';};
ns.AbstractRenderer.prototype.isGridEnabled = function () {throw 'abstract method should be implemented';};
ns.AbstractRenderer.prototype.setZoom = function (zoom) {throw 'abstract method should be implemented';};
ns.AbstractRenderer.prototype.getZoom = function () {throw 'abstract method should be implemented';};
ns.AbstractRenderer.prototype.moveOffset = function (x, y) {throw 'abstract method should be implemented';};
ns.AbstractRenderer.prototype.getOffset = function () {throw 'abstract method should be implemented';};
ns.AbstractRenderer.prototype.setDisplaySize = function (w, h) {throw 'abstract method should be implemented';};
ns.AbstractRenderer.prototype.getDisplaySize = function () {throw 'abstract method should be implemented';};
})();

View File

@ -0,0 +1,64 @@
(function () {
var ns = $.namespace('pskl.rendering');
/**
* Decorator on a renderer that will only render the frame if something has changed in the frame itself or in the renderer's configuration
* @param {pskl.rendering.AbstractRenderer} renderer
*/
ns.CachedRenderer = function (renderer) {
this.decoratedRenderer = renderer;
this.serializedFrame = '';
};
pskl.utils.inherit(ns.CachedRenderer, ns.AbstractRenderer);
ns.CachedRenderer.prototype.render = function (frame) {
var offset = this.getOffset();
var size = this.getDisplaySize();
var serializedFrame = [this.getZoom(), offset.x, offset.y, size.width, size.height, frame.serialize()].join('-');
if (this.serializedFrame != serializedFrame) {
this.serializedFrame = serializedFrame;
this.decoratedRenderer.render(frame);
}
};
ns.CachedRenderer.prototype.clear = function () {
this.decoratedRenderer.clear();
};
ns.CachedRenderer.prototype.getCoordinates = function (x, y) {
return this.decoratedRenderer.getCoordinates(x, y);
};
ns.CachedRenderer.prototype.setGridEnabled = function (b) {
this.decoratedRenderer.setGridEnabled(b);
};
ns.CachedRenderer.prototype.isGridEnabled = function () {
return this.decoratedRenderer.isGridEnabled();
};
ns.CachedRenderer.prototype.getZoom = function () {
return this.decoratedRenderer.getZoom();
};
ns.CachedRenderer.prototype.setZoom = function (zoom) {
return this.decoratedRenderer.setZoom(zoom);
};
ns.CachedRenderer.prototype.moveOffset = function (x, y) {
this.decoratedRenderer.moveOffset(x, y);
};
ns.CachedRenderer.prototype.getOffset = function () {
return this.decoratedRenderer.getOffset();
};
ns.CachedRenderer.prototype.setDisplaySize = function (w, h) {
this.decoratedRenderer.setDisplaySize(w, h);
};
ns.CachedRenderer.prototype.getDisplaySize = function () {
return this.decoratedRenderer.getDisplaySize();
};
})();

View File

@ -0,0 +1,30 @@
(function () {
var ns = $.namespace('pskl.rendering');
ns.RendererManager = function () {
this.renderers = [];
};
ns.RendererManager.prototype.add = function (renderer) {
this.renderers.push(renderer);
return this;
};
ns.RendererManager.prototype.setZoom = function (zoom) {
this.renderers.forEach(function (renderer) {
renderer.setZoom(zoom);
});
};
ns.RendererManager.prototype.setDisplaySize = function (w, h) {
this.renderers.forEach(function (renderer) {
renderer.setDisplaySize(w, h);
});
};
ns.RendererManager.prototype.moveOffset = function (offsetX, offsetY) {
this.renderers.forEach(function (renderer) {
renderer.moveOffset(offsetX, offsetY);
});
};
})();

View File

@ -0,0 +1,18 @@
(function () {
var ns = $.namespace('pskl.rendering.frame');
/**
* Cached renderer that can uses the same constructor as pskl.rendering.FrameRenderer
* It will build a FrameRenderer on the fly to use as decorated renderer
* @param {HtmlElement} container HtmlElement to use as parentNode of the Frame
* @param {Object} renderingOptions
* @param {Array} classes array of strings to use for css classes
*/
ns.CachedFrameRenderer = function (container, renderingOptions, classes) {
var frameRenderer = new pskl.rendering.frame.FrameRenderer(container, renderingOptions, classes);
pskl.rendering.CachedRenderer.call(this, frameRenderer);
};
pskl.utils.inherit(pskl.rendering.frame.CachedFrameRenderer, pskl.rendering.CachedRenderer);
})();

View File

@ -1,5 +1,5 @@
(function () {
var ns = $.namespace("pskl.rendering");
var ns = $.namespace("pskl.rendering.frame");
/**
* FrameRenderer will display a given frame inside a canvas element.
@ -27,11 +27,15 @@
this.zoom = renderingOptions.zoom;
this.frameOffsetX = 0;
this.frameOffsetY = 0;
this.offset = {
x : 0,
y : 0
};
this.marginY = 0;
this.marginX = 0;
this.margin = {
x : 0,
y : 0
};
this.supportGridRendering = renderingOptions.supportGridRendering;
@ -51,93 +55,12 @@
this.displayCanvas = null;
this.setDisplaySize(renderingOptions.width, renderingOptions.height);
this.enableGrid(pskl.UserSettings.get(pskl.UserSettings.SHOW_GRID));
this.setGridEnabled(pskl.UserSettings.get(pskl.UserSettings.SHOW_GRID));
this.updateBackgroundClass_(pskl.UserSettings.get(pskl.UserSettings.CANVAS_BACKGROUND));
$.subscribe(Events.USER_SETTINGS_CHANGED, $.proxy(this.onUserSettingsChange_, this));
};
ns.FrameRenderer.prototype.setZoom = function (zoom) {
this.zoom = zoom;
};
ns.FrameRenderer.prototype.isAutoSized_ = function () {
return this.displayHeight === 'auto' && this.displayWidth === 'auto';
};
ns.FrameRenderer.prototype.setDisplaySize = function (width, height) {
this.displayHeight = height;
this.displayWidth = width;
if (this.displayCanvas) {
$(this.displayCanvas).remove();
this.displayCanvas = null;
}
};
ns.FrameRenderer.prototype.updateMargins_ = function () {
if (!this.isAutoSized_()) {
var deltaX = this.displayWidth - (this.zoom * this.canvas.width);
this.marginX = Math.max(0, deltaX) / 2;
var deltaY = this.displayHeight - (this.zoom * this.canvas.height);
this.marginY = Math.max(0, deltaY) / 2;
}
};
ns.FrameRenderer.prototype.createDisplayCanvas_ = function () {
var height = this.displayHeight;
var width = this.displayWidth;
if (this.isAutoSized_()) {
height = this.zoom * this.canvas.height;
width = this.zoom * this.canvas.width;
}
this.displayCanvas = pskl.CanvasUtils.createCanvas(width, height, this.classes);
if (true || this.zoom > 2) {
pskl.CanvasUtils.disableImageSmoothing(this.displayCanvas);
}
this.container.append(this.displayCanvas);
};
ns.FrameRenderer.prototype.setDisplayOffset = function (frameOffsetX, frameOffsetY) {
this.frameOffsetX = frameOffsetX;
this.frameOffsetY = frameOffsetY;
};
ns.FrameRenderer.prototype.moveOffset = function (frameOffsetX, frameOffsetY) {
this.setDisplayOffset(this.frameOffsetX + frameOffsetX, this.frameOffsetY + frameOffsetY);
};
/**
* @private
*/
ns.FrameRenderer.prototype.onUserSettingsChange_ = function (evt, settingName, settingValue) {
if(settingName == pskl.UserSettings.SHOW_GRID) {
this.enableGrid(settingValue);
}
else if (settingName == pskl.UserSettings.CANVAS_BACKGROUND) {
this.updateBackgroundClass_(settingValue);
}
};
/**
* @private
*/
ns.FrameRenderer.prototype.updateBackgroundClass_ = function (newClass) {
var currentClass = this.container.data('current-background-class');
if (currentClass) {
this.container.removeClass(currentClass);
}
this.container.addClass(newClass);
this.container.data('current-background-class', newClass);
};
ns.FrameRenderer.prototype.enableGrid = function (flag) {
this.gridStrokeWidth = (flag && this.supportGridRendering) ? Constants.GRID_STROKE_WIDTH : 0;
this.canvasConfigDirty = true;
};
ns.FrameRenderer.prototype.render = function (frame) {
if (frame) {
this.clear();
@ -150,10 +73,92 @@
pskl.CanvasUtils.clear(this.displayCanvas);
};
ns.FrameRenderer.prototype.renderPixel_ = function (color, col, row, context) {
ns.FrameRenderer.prototype.setZoom = function (zoom) {
this.zoom = zoom;
};
ns.FrameRenderer.prototype.getZoom = function () {
return this.zoom;
};
ns.FrameRenderer.prototype.setDisplaySize = function (width, height) {
this.displayWidth = width;
this.displayHeight = height;
if (this.displayCanvas) {
$(this.displayCanvas).remove();
this.displayCanvas = null;
}
this.createDisplayCanvas_();
};
ns.FrameRenderer.prototype.getDisplaySize = function () {
return {
height : this.displayHeight,
width : this.displayWidth
};
};
ns.FrameRenderer.prototype.getOffset = function () {
return {
x : this.offset.x,
y : this.offset.y
};
},
ns.FrameRenderer.prototype.moveOffset = function (x, y) {
this.setOffset_(this.offset.x + x, this.offset.y + y);
};
ns.FrameRenderer.prototype.setGridEnabled = function (flag) {
this.gridStrokeWidth = (flag && this.supportGridRendering) ? Constants.GRID_STROKE_WIDTH : 0;
this.canvasConfigDirty = true;
};
ns.FrameRenderer.prototype.updateMargins_ = function () {
var deltaX = this.displayWidth - (this.zoom * this.canvas.width);
this.margin.x = Math.max(0, deltaX) / 2;
var deltaY = this.displayHeight - (this.zoom * this.canvas.height);
this.margin.y = Math.max(0, deltaY) / 2;
};
ns.FrameRenderer.prototype.createDisplayCanvas_ = function () {
var height = this.displayHeight;
var width = this.displayWidth;
this.displayCanvas = pskl.CanvasUtils.createCanvas(width, height, this.classes);
if (true || this.zoom > 2) {
pskl.CanvasUtils.disableImageSmoothing(this.displayCanvas);
}
this.container.append(this.displayCanvas);
};
ns.FrameRenderer.prototype.setOffset_ = function (x, y) {
this.offset.x = x;
this.offset.y = y;
};
ns.FrameRenderer.prototype.onUserSettingsChange_ = function (evt, settingName, settingValue) {
if(settingName == pskl.UserSettings.SHOW_GRID) {
this.setGridEnabled(settingValue);
} else if (settingName == pskl.UserSettings.CANVAS_BACKGROUND) {
this.updateBackgroundClass_(settingValue);
}
};
ns.FrameRenderer.prototype.updateBackgroundClass_ = function (newClass) {
var currentClass = this.container.data('current-background-class');
if (currentClass) {
this.container.removeClass(currentClass);
}
this.container.addClass(newClass);
this.container.data('current-background-class', newClass);
};
ns.FrameRenderer.prototype.renderPixel_ = function (color, x, y, context) {
if(color != Constants.TRANSPARENT_COLOR) {
context.fillStyle = color;
context.fillRect(col, row, 1, 1);
context.fillRect(x, y, 1, 1);
}
};
@ -162,13 +167,23 @@
* frame) into a sprite coordinate in column and row.
* @public
*/
ns.FrameRenderer.prototype.convertPixelCoordinatesIntoSpriteCoordinate = function(coords) {
ns.FrameRenderer.prototype.getCoordinates = function(x, y) {
var containerOffset = this.container.offset();
x = x - containerOffset.left;
y = y - containerOffset.top;
// apply margins
x = x - this.margin.x;
y = y - this.margin.y;
var cellSize = this.zoom + this.gridStrokeWidth;
var xCoord = (coords.x - this.marginX) + (this.frameOffsetX * cellSize),
yCoord = (coords.y - this.marginY) + (this.frameOffsetY * cellSize);
// apply frame offset
x = x + this.offset.x * cellSize;
y = y + this.offset.y * cellSize;
return {
"col" : (xCoord - xCoord % cellSize) / cellSize,
"row" : (yCoord - yCoord % cellSize) / cellSize
x : (x / cellSize) | 0,
y : (y / cellSize) | 0
};
};
@ -188,17 +203,13 @@
}
var context = this.canvas.getContext('2d');
for(var col = 0, width = frame.getWidth(); col < width; col++) {
for(var row = 0, height = frame.getHeight(); row < height; row++) {
var color = frame.getPixel(col, row);
this.renderPixel_(color, col, row, context);
for(var x = 0, width = frame.getWidth(); x < width; x++) {
for(var y = 0, height = frame.getHeight(); y < height; y++) {
var color = frame.getPixel(x, y);
this.renderPixel_(color, x, y, context);
}
}
if (!this.displayCanvas) {
this.createDisplayCanvas_();
}
this.updateMargins_();
context = this.displayCanvas.getContext('2d');
@ -207,9 +218,9 @@
context.fillStyle = "#aaa";
// zoom < 1
context.fillRect(0,0,this.displayCanvas.width, this.displayCanvas.height);
context.translate(this.marginX, this.marginY);
context.translate(this.margin.x, this.margin.y);
context.scale(this.zoom, this.zoom);
context.translate(-this.frameOffsetX, -this.frameOffsetY);
context.translate(-this.offset.x, -this.offset.y);
// zoom < 1
context.clearRect(0, 0, this.canvas.width, this.canvas.height);
context.drawImage(this.canvas, 0, 0);