From bd990278523a1eea8516727167566772a2c3bbfc Mon Sep 17 00:00:00 2001 From: jdescottes Date: Fri, 1 Nov 2013 23:11:11 +0100 Subject: [PATCH] feature : zoom - Added MinimapController that displays a frame on the animated preview when zoomed in - Added bounds for the offset to make sure it doesn't go crazy - Added new utility Math.js with a minmax function - TODO : the minimap controller has a lot of dependencies, see if could be cleaned up - TODO : DrawingController knows the size of the picture it has to render only indirectly, which makes it hard in some cases (such as boundary checking performed during setOffset) --- css/minimap.css | 8 +++ css/style.css | 5 ++ index.html | 1 + js/app.js | 3 + js/controller/DrawingController.js | 13 ++++- js/controller/MinimapController.js | 85 +++++++++++++++++++++++++++++ js/rendering/AbstractRenderer.js | 1 + js/rendering/CompositeRenderer.js | 10 +++- js/rendering/frame/FrameRenderer.js | 40 +++++++++----- js/utils/Math.js | 9 +++ piskel-script-list.js | 2 + 11 files changed, 158 insertions(+), 19 deletions(-) create mode 100644 css/minimap.css create mode 100644 js/controller/MinimapController.js create mode 100644 js/utils/Math.js diff --git a/css/minimap.css b/css/minimap.css new file mode 100644 index 00000000..0383e127 --- /dev/null +++ b/css/minimap.css @@ -0,0 +1,8 @@ +.minimap-crop-frame { + position:absolute; + border:1px solid red; + z-index:9999; + box-sizing : border-box; + -moz-box-sizing : border-box; + cursor : pointer; +} \ No newline at end of file diff --git a/css/style.css b/css/style.css index 1b810419..6fc8e490 100644 --- a/css/style.css +++ b/css/style.css @@ -174,6 +174,10 @@ body { font-size: 0; } +.preview-container .canvas-container { + overflow : hidden; +} + .preview-container canvas { border : 0px Solid transparent; } @@ -189,6 +193,7 @@ body { .range-fps { overflow: hidden; + width: 120px; } /** diff --git a/index.html b/index.html index 50c1ddd6..cb8f278c 100644 --- a/index.html +++ b/index.html @@ -14,6 +14,7 @@ + diff --git a/js/app.js b/js/app.js index 93444c84..b783d785 100644 --- a/js/app.js +++ b/js/app.js @@ -27,6 +27,9 @@ this.animationController = new pskl.controller.AnimatedPreviewController(this.piskelController, $('#preview-canvas-container')); this.animationController.init(); + this.minimapController = new pskl.controller.MinimapController(this.piskelController, this.animationController, this.drawingController, $('#preview-canvas-container')); + this.minimapController.init(); + this.previewsController = new pskl.controller.PreviewFilmController(this.piskelController, $('#preview-list')); this.previewsController.init(); diff --git a/js/controller/DrawingController.js b/js/controller/DrawingController.js index ab1d65ea..98efb7f4 100644 --- a/js/controller/DrawingController.js +++ b/js/controller/DrawingController.js @@ -82,6 +82,7 @@ this.container.mousedown($.proxy(this.onMousedown_, this)); this.container.mousemove($.proxy(this.onMousemove_, this)); this.container.on('mousewheel', $.proxy(this.onMousewheel_, this)); + this.container.on('wheel', $.proxy(this.onMousewheel_, this)); body.mouseup($.proxy(this.onMouseup_, this)); @@ -175,13 +176,14 @@ ns.DrawingController.prototype.onMousewheel_ = function (jQueryEvent) { var event = jQueryEvent.originalEvent; - var delta = event.wheelDeltaY; + var delta = event.wheelDeltaY || (-2 * event.deltaY); var currentZoom = this.renderer.getZoom(); if (delta > 0) { this.compositeRenderer.setZoom(currentZoom + 1); } else if (delta < 0) { this.compositeRenderer.setZoom(currentZoom - 1); } + pskl.app.minimapController.onDrawingControllerMove_(); }; /** @@ -308,7 +310,12 @@ }); }; - ns.DrawingController.prototype.moveOffset = function (xOffset, yOffset) { - this.compositeRenderer.moveOffset(xOffset, yOffset); + ns.DrawingController.prototype.getRenderer = function () { + return this.compositeRenderer; + }; + + ns.DrawingController.prototype.setOffset = function (x, y) { + this.compositeRenderer.setOffset(x, y); + pskl.app.minimapController.onDrawingControllerMove_(); }; })(); \ No newline at end of file diff --git a/js/controller/MinimapController.js b/js/controller/MinimapController.js new file mode 100644 index 00000000..8128790b --- /dev/null +++ b/js/controller/MinimapController.js @@ -0,0 +1,85 @@ +(function () { + var ns = $.namespace('pskl.controller'); + + ns.MinimapController = function (piskelController, animationController, drawingController, container) { + this.piskelController = piskelController; + this.animationController = animationController; + this.drawingController = drawingController; + this.container = container; + + this.isClicked = false; + }; + + ns.MinimapController.prototype.init = function () { + this.cropFrame = document.createElement('DIV'); + this.cropFrame.className = 'minimap-crop-frame'; + this.cropFrame.style.display = 'none'; + $(this.container).mousedown(this.onMinimapMousedown_.bind(this)); + $(this.container).mousemove(this.onMinimapMousemove_.bind(this)); + $(this.container).mouseup(this.onMinimapMouseup_.bind(this)); + $(this.container).append(this.cropFrame); + }; + + ns.MinimapController.prototype.onDrawingControllerMove_ = function () { + var zoomRatio = this.getDrawingAreaZoomRatio_(); + if (zoomRatio > 1) { + this.displayCropFrame_(zoomRatio, this.drawingController.getRenderer().getOffset()); + } else { + this.hideCropFrame_(); + } + }; + + ns.MinimapController.prototype.displayCropFrame_ = function (ratio, offset) { + this.cropFrame.style.display = 'block'; + this.cropFrame.style.top = (offset.y * this.animationController.renderer.getZoom()) + 'px'; + this.cropFrame.style.left = (offset.x * this.animationController.renderer.getZoom()) + 'px'; + var zoomRatio = this.getDrawingAreaZoomRatio_(); + this.cropFrame.style.width = (this.container.width() / zoomRatio) + 'px'; + this.cropFrame.style.height = (this.container.height() / zoomRatio) + 'px'; + + }; + + ns.MinimapController.prototype.hideCropFrame_ = function () { + this.cropFrame.style.display = 'none'; + }; + + ns.MinimapController.prototype.onMinimapMousemove_ = function (evt) { + if (this.isClicked) { + if (this.getDrawingAreaZoomRatio_() > 1) { + var coords = this.getCoordinatesCenteredAround_(evt.clientX, evt.clientY); + this.drawingController.setOffset(coords.x, coords.y); + } + } + }; + + ns.MinimapController.prototype.onMinimapMousedown_ = function (evt) { + this.isClicked = true; + }; + + ns.MinimapController.prototype.onMinimapMouseup_ = function (evt) { + this.isClicked = false; + }; + + ns.MinimapController.prototype.getCoordinatesCenteredAround_ = function (x, y) { + var frameCoords = this.animationController.renderer.getCoordinates(x, y); + var zoomRatio = this.getDrawingAreaZoomRatio_(); + var frameWidth = this.piskelController.getCurrentFrame().getWidth(); + var frameHeight = this.piskelController.getCurrentFrame().getHeight(); + + var width = frameWidth / zoomRatio; + var height = frameHeight / zoomRatio; + + return { + x : frameCoords.x - (width/2), + y : frameCoords.y - (height/2) + }; + }; + + ns.MinimapController.prototype.getDrawingAreaZoomRatio_ = function () { + var drawingAreaZoom = this.drawingController.getRenderer().getZoom(); + var drawingAreaFullHeight = this.piskelController.getCurrentFrame().getHeight() * drawingAreaZoom; + var zoomRatio = drawingAreaFullHeight / this.drawingController.getRenderer().getDisplaySize().height; + + return zoomRatio; + }; +})(); \ No newline at end of file diff --git a/js/rendering/AbstractRenderer.js b/js/rendering/AbstractRenderer.js index ffb4dcb5..a3539402 100644 --- a/js/rendering/AbstractRenderer.js +++ b/js/rendering/AbstractRenderer.js @@ -14,6 +14,7 @@ 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.setOffset = 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';}; diff --git a/js/rendering/CompositeRenderer.js b/js/rendering/CompositeRenderer.js index 834d2c73..d5a770f4 100644 --- a/js/rendering/CompositeRenderer.js +++ b/js/rendering/CompositeRenderer.js @@ -38,9 +38,15 @@ return this.getSampleRenderer_().getDisplaySize(); }; - ns.CompositeRenderer.prototype.moveOffset = function (offsetX, offsetY) { + ns.CompositeRenderer.prototype.moveOffset = function (x, y) { this.renderers.forEach(function (renderer) { - renderer.moveOffset(offsetX, offsetY); + renderer.moveOffset(x, y); + }); + }; + + ns.CompositeRenderer.prototype.setOffset = function (x, y) { + this.renderers.forEach(function (renderer) { + renderer.setOffset(x, y); }); }; diff --git a/js/rendering/frame/FrameRenderer.js b/js/rendering/frame/FrameRenderer.js index 038ff097..9d3bb57d 100644 --- a/js/rendering/frame/FrameRenderer.js +++ b/js/rendering/frame/FrameRenderer.js @@ -76,7 +76,17 @@ }; ns.FrameRenderer.prototype.setZoom = function (zoom) { - this.zoom = zoom; + // back up center coordinates + var centerX = this.offset.x + (this.displayWidth/(2*this.zoom)); + var centerY = this.offset.y + (this.displayHeight/(2*this.zoom)); + + this.zoom = Math.max(1, zoom); + + // recenter + this.setOffset( + centerX - (this.displayWidth/(2*this.zoom)), + centerY - (this.displayHeight/(2*this.zoom)) + ); }; ns.FrameRenderer.prototype.getZoom = function () { @@ -108,7 +118,21 @@ }, ns.FrameRenderer.prototype.moveOffset = function (x, y) { - this.setOffset_(this.offset.x + x, this.offset.y + y); + this.setOffset(this.offset.x + x, this.offset.y + y); + }; + + ns.FrameRenderer.prototype.setOffset = function (x, y) { + // TODO : provide frame size information to the FrameRenderer constructor + // here I first need to verify I have a 'canvas' which I can use to infer the frame information + // and then perform my boundaries checking. This sucks + if (this.canvas) { + var maxX = this.canvas.width - (this.displayWidth/this.zoom); + x = pskl.utils.Math.minmax(x, 0, maxX); + var maxY = this.canvas.height - (this.displayHeight/this.zoom); + y = pskl.utils.Math.minmax(y, 0, maxY); + } + this.offset.x = x; + this.offset.y = y; }; ns.FrameRenderer.prototype.setGridEnabled = function (flag) { @@ -135,11 +159,6 @@ 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); @@ -189,13 +208,6 @@ }; }; - /** - * @private - */ - ns.FrameRenderer.prototype.getFramePos_ = function(index) { - return index * this.dpi + ((index - 1) * this.gridStrokeWidth); - }; - /** * @private */ diff --git a/js/utils/Math.js b/js/utils/Math.js new file mode 100644 index 00000000..b333a333 --- /dev/null +++ b/js/utils/Math.js @@ -0,0 +1,9 @@ +(function () { + var ns = $.namespace('pskl.utils'); + + ns.Math = { + minmax : function (val, min, max) { + return Math.max(Math.min(val, max), min); + } + }; +})(); \ No newline at end of file diff --git a/piskel-script-list.js b/piskel-script-list.js index e4c84c2f..9f3288d8 100644 --- a/piskel-script-list.js +++ b/piskel-script-list.js @@ -14,6 +14,7 @@ exports.scripts = [ // Libraries "js/utils/core.js", "js/utils/CanvasUtils.js", + "js/utils/Math.js", "js/utils/FrameUtils.js", "js/utils/PixelUtils.js", "js/utils/Serializer.js", @@ -50,6 +51,7 @@ exports.scripts = [ "js/controller/PreviewFilmController.js", "js/controller/LayersListController.js", "js/controller/AnimatedPreviewController.js", + "js/controller/MinimapController.js", "js/controller/ToolController.js", "js/controller/PaletteController.js", "js/controller/NotificationController.js",