diff options
Diffstat (limited to 'js/dojo/dojox/mobile/app/ImageView.js')
| -rw-r--r-- | js/dojo/dojox/mobile/app/ImageView.js | 721 |
1 files changed, 721 insertions, 0 deletions
diff --git a/js/dojo/dojox/mobile/app/ImageView.js b/js/dojo/dojox/mobile/app/ImageView.js new file mode 100644 index 0000000..d27bdf7 --- /dev/null +++ b/js/dojo/dojox/mobile/app/ImageView.js @@ -0,0 +1,721 @@ +//>>built +// wrapped by build app +define("dojox/mobile/app/ImageView", ["dijit","dojo","dojox","dojo/require!dojox/mobile/app/_Widget,dojo/fx/easing"], function(dijit,dojo,dojox){ +dojo.provide("dojox.mobile.app.ImageView"); +dojo.experimental("dojox.mobile.app.ImageView"); +dojo.require("dojox.mobile.app._Widget"); + +dojo.require("dojo.fx.easing"); + +dojo.declare("dojox.mobile.app.ImageView", dojox.mobile.app._Widget, { + + // zoom: Number + // The current level of zoom. This should not be set manually. + zoom: 1, + + // zoomCenterX: Number + // The X coordinate in the image where the zoom is focused + zoomCenterX: 0, + + // zoomCenterY: Number + // The Y coordinate in the image where the zoom is focused + zoomCenterY: 0, + + // maxZoom: Number + // The highest degree to which an image can be zoomed. For example, + // a maxZoom of 5 means that the image will be 5 times larger than normal + maxZoom: 5, + + // autoZoomLevel: Number + // The degree to which the image is zoomed when auto zoom is invoked. + // The higher the number, the more the image is zoomed in. + autoZoomLevel: 3, + + // disableAutoZoom: Boolean + // Disables auto zoom + disableAutoZoom: false, + + // disableSwipe: Boolean + // Disables the users ability to swipe from one image to the next. + disableSwipe: false, + + // autoZoomEvent: String + // Overrides the default event listened to which invokes auto zoom + autoZoomEvent: null, + + // _leftImg: Node + // The full sized image to the left + _leftImg: null, + + // _centerImg: Node + // The full sized image in the center + _centerImg: null, + + // _rightImg: Node + // The full sized image to the right + _rightImg: null, + + // _leftImg: Node + // The small sized image to the left + _leftSmallImg: null, + + // _centerImg: Node + // The small sized image in the center + _centerSmallImg: null, + + // _rightImg: Node + // The small sized image to the right + _rightSmallImg: null, + + constructor: function(){ + + this.panX = 0; + this.panY = 0; + + this.handleLoad = dojo.hitch(this, this.handleLoad); + this._updateAnimatedZoom = dojo.hitch(this, this._updateAnimatedZoom); + this._updateAnimatedPan = dojo.hitch(this, this._updateAnimatedPan); + this._onAnimPanEnd = dojo.hitch(this, this._onAnimPanEnd); + }, + + buildRendering: function(){ + this.inherited(arguments); + + this.canvas = dojo.create("canvas", {}, this.domNode); + + dojo.addClass(this.domNode, "mblImageView"); + }, + + postCreate: function(){ + this.inherited(arguments); + + this.size = dojo.marginBox(this.domNode); + + dojo.style(this.canvas, { + width: this.size.w + "px", + height: this.size.h + "px" + }); + this.canvas.height = this.size.h; + this.canvas.width = this.size.w; + + var _this = this; + + // Listen to the mousedown/touchstart event. Record the position + // so we can use it to pan the image. + this.connect(this.domNode, "onmousedown", function(event){ + if(_this.isAnimating()){ + return; + } + if(_this.panX){ + _this.handleDragEnd(); + } + + _this.downX = event.targetTouches ? event.targetTouches[0].clientX : event.clientX; + _this.downY = event.targetTouches ? event.targetTouches[0].clientY : event.clientY; + }); + + // record the movement of the mouse. + this.connect(this.domNode, "onmousemove", function(event){ + if(_this.isAnimating()){ + return; + } + if((!_this.downX && _this.downX !== 0) || (!_this.downY && _this.downY !== 0)){ + // If the touch didn't begin on this widget, ignore the movement + return; + } + + if((!_this.disableSwipe && _this.zoom == 1) + || (!_this.disableAutoZoom && _this.zoom != 1)){ + var x = event.targetTouches ? + event.targetTouches[0].clientX : event.pageX; + var y = event.targetTouches ? + event.targetTouches[0].clientY : event.pageY; + + _this.panX = x - _this.downX; + _this.panY = y - _this.downY; + + if(_this.zoom == 1){ + // If not zoomed in, then try to move to the next or prev image + // but only if the mouse has moved more than 10 pixels + // in the X direction + if(Math.abs(_this.panX) > 10){ + _this.render(); + } + }else{ + // If zoomed in, pan the image if the mouse has moved more + // than 10 pixels in either direction. + if(Math.abs(_this.panX) > 10 || Math.abs(_this.panY) > 10){ + _this.render(); + } + } + } + }); + + this.connect(this.domNode, "onmouseout", function(event){ + if(!_this.isAnimating() && _this.panX){ + _this.handleDragEnd(); + } + }); + + this.connect(this.domNode, "onmouseover", function(event){ + _this.downX = _this.downY = null; + }); + + // Set up AutoZoom, which zooms in a fixed amount when the user taps + // a part of the canvas + this.connect(this.domNode, "onclick", function(event){ + if(_this.isAnimating()){ + return; + } + if(_this.downX == null || _this.downY == null){ + return; + } + + var x = (event.targetTouches ? + event.targetTouches[0].clientX : event.pageX); + var y = (event.targetTouches ? + event.targetTouches[0].clientY : event.pageY); + + // If the mouse/finger has moved more than 14 pixels from where it + // started, do not treat it as a click. It is a drag. + if(Math.abs(_this.panX) > 14 || Math.abs(_this.panY) > 14){ + _this.downX = _this.downY = null; + _this.handleDragEnd(); + return; + } + _this.downX = _this.downY = null; + + if(!_this.disableAutoZoom){ + + if(!_this._centerImg || !_this._centerImg._loaded){ + // Do nothing until the image is loaded + return; + } + if(_this.zoom != 1){ + _this.set("animatedZoom", 1); + return; + } + + var pos = dojo._abs(_this.domNode); + + // Translate the clicked point to a point on the source image + var xRatio = _this.size.w / _this._centerImg.width; + var yRatio = _this.size.h / _this._centerImg.height; + + // Do an animated zoom to the point which was clicked. + _this.zoomTo( + ((x - pos.x) / xRatio) - _this.panX, + ((y - pos.y) / yRatio) - _this.panY, + _this.autoZoomLevel); + } + }); + + // Listen for Flick events + dojo.connect(this.domNode, "flick", this, "handleFlick"); + }, + + isAnimating: function(){ + // summary: + // Returns true if an animation is in progress, false otherwise. + return this._anim && this._anim.status() == "playing"; + }, + + handleDragEnd: function(){ + // summary: + // Handles the end of a dragging event. If not zoomed in, it + // determines if the next or previous image should be transitioned + // to. + this.downX = this.downY = null; + console.log("handleDragEnd"); + + if(this.zoom == 1){ + if(!this.panX){ + return; + } + + var leftLoaded = (this._leftImg && this._leftImg._loaded) + || (this._leftSmallImg && this._leftSmallImg._loaded); + var rightLoaded = (this._rightImg && this._rightImg._loaded) + || (this._rightSmallImg && this._rightSmallImg._loaded); + + // Check if the drag has moved the image more than half its length. + // If so, move to either the previous or next image. + var doMove = + !(Math.abs(this.panX) < this._centerImg._baseWidth / 2) && + ( + (this.panX > 0 && leftLoaded ? 1 : 0) || + (this.panX < 0 && rightLoaded ? 1 : 0) + ); + + + if(!doMove){ + // If not moving to another image, animate the sliding of the + // image back into place. + this._animPanTo(0, dojo.fx.easing.expoOut, 700); + }else{ + // Move to another image. + this.moveTo(this.panX); + } + }else{ + if(!this.panX && !this.panY){ + return; + } + // Recenter the zoomed image based on where it was panned to + // previously + this.zoomCenterX -= (this.panX / this.zoom); + this.zoomCenterY -= (this.panY / this.zoom); + + this.panX = this.panY = 0; + } + + }, + + handleFlick: function(event){ + // summary: + // Handle a flick event. + if(this.zoom == 1 && event.duration < 500){ + // Only handle quick flicks here, less than 0.5 seconds + + // If not zoomed in, then check if we should move to the next photo + // or not + if(event.direction == "ltr"){ + this.moveTo(1); + }else if(event.direction == "rtl"){ + this.moveTo(-1); + } + // If an up or down flick occurs, it means nothing so ignore it + this.downX = this.downY = null; + } + }, + + moveTo: function(direction){ + direction = direction > 0 ? 1 : -1; + var toImg; + + if(direction < 1){ + if(this._rightImg && this._rightImg._loaded){ + toImg = this._rightImg; + }else if(this._rightSmallImg && this._rightSmallImg._loaded){ + toImg = this._rightSmallImg; + } + }else{ + if(this._leftImg && this._leftImg._loaded){ + toImg = this._leftImg; + }else if(this._leftSmallImg && this._leftSmallImg._loaded){ + toImg = this._leftSmallImg; + } + } + + this._moveDir = direction; + var _this = this; + + if(toImg && toImg._loaded){ + // If the image is loaded, make a linear animation to show it + this._animPanTo(this.size.w * direction, null, 500, function(){ + _this.panX = 0; + _this.panY = 0; + + if(direction < 0){ + // Moving to show the right image + _this._switchImage("left", "right"); + }else{ + // Moving to show the left image + _this._switchImage("right", "left"); + } + + _this.render(); + _this.onChange(direction * -1); + }); + + }else{ + // If the next image is not loaded, make an animation to + // move the center image to half the width of the widget and back + // again + + console.log("moveTo image not loaded!", toImg); + + this._animPanTo(0, dojo.fx.easing.expoOut, 700); + } + }, + + _switchImage: function(toImg, fromImg){ + var toSmallImgName = "_" + toImg + "SmallImg"; + var toImgName = "_" + toImg + "Img"; + + var fromSmallImgName = "_" + fromImg + "SmallImg"; + var fromImgName = "_" + fromImg + "Img"; + + this[toImgName] = this._centerImg; + this[toSmallImgName] = this._centerSmallImg; + + this[toImgName]._type = toImg; + + if(this[toSmallImgName]){ + this[toSmallImgName]._type = toImg; + } + + this._centerImg = this[fromImgName]; + this._centerSmallImg = this[fromSmallImgName]; + this._centerImg._type = "center"; + + if(this._centerSmallImg){ + this._centerSmallImg._type = "center"; + } + this[fromImgName] = this[fromSmallImgName] = null; + }, + + _animPanTo: function(to, easing, duration, callback){ + this._animCallback = callback; + this._anim = new dojo.Animation({ + curve: [this.panX, to], + onAnimate: this._updateAnimatedPan, + duration: duration || 500, + easing: easing, + onEnd: this._onAnimPanEnd + }); + + this._anim.play(); + return this._anim; + }, + + onChange: function(direction){ + // summary: + // Stub function that can be listened to in order to provide + // new images when the displayed image changes + }, + + _updateAnimatedPan: function(amount){ + this.panX = amount; + this.render(); + }, + + _onAnimPanEnd: function(){ + this.panX = this.panY = 0; + + if(this._animCallback){ + this._animCallback(); + } + }, + + zoomTo: function(centerX, centerY, zoom){ + this.set("zoomCenterX", centerX); + this.set("zoomCenterY", centerY); + + this.set("animatedZoom", zoom); + }, + + render: function(){ + var cxt = this.canvas.getContext('2d'); + + cxt.clearRect(0, 0, this.canvas.width, this.canvas.height); + + // Render the center image + this._renderImg( + this._centerSmallImg, + this._centerImg, + this.zoom == 1 ? (this.panX < 0 ? 1 : this.panX > 0 ? -1 : 0) : 0); + + if(this.zoom == 1 && this.panX != 0){ + if(this.panX > 0){ + // Render the left image, showing the right side of it + this._renderImg(this._leftSmallImg, this._leftImg, 1); + }else{ + // Render the right image, showing the left side of it + this._renderImg(this._rightSmallImg, this._rightImg, -1); + } + } + }, + + _renderImg: function(smallImg, largeImg, panDir){ + // summary: + // Renders a single image + + + // If zoomed, we just display the center img + var img = (largeImg && largeImg._loaded) ? largeImg : smallImg; + + if(!img || !img._loaded){ + // If neither the large or small image is loaded, display nothing + return; + } + var cxt = this.canvas.getContext('2d'); + + var baseWidth = img._baseWidth; + var baseHeight = img._baseHeight; + + // Calculate the size the image would be if there were no bounds + var desiredWidth = baseWidth * this.zoom; + var desiredHeight = baseHeight * this.zoom; + + // Calculate the actual size of the viewable image + var destWidth = Math.min(this.size.w, desiredWidth); + var destHeight = Math.min(this.size.h, desiredHeight); + + + // Calculate the size of the window on the original image to use + var sourceWidth = this.dispWidth = img.width * (destWidth / desiredWidth); + var sourceHeight = this.dispHeight = img.height * (destHeight / desiredHeight); + + var zoomCenterX = this.zoomCenterX - (this.panX / this.zoom); + var zoomCenterY = this.zoomCenterY - (this.panY / this.zoom); + + // Calculate where the center of the view should be + var centerX = Math.floor(Math.max(sourceWidth / 2, + Math.min(img.width - sourceWidth / 2, zoomCenterX))); + var centerY = Math.floor(Math.max(sourceHeight / 2, + Math.min(img.height - sourceHeight / 2, zoomCenterY))); + + + var sourceX = Math.max(0, + Math.round((img.width - sourceWidth)/2 + (centerX - img._centerX)) ); + var sourceY = Math.max(0, + Math.round((img.height - sourceHeight) / 2 + (centerY - img._centerY)) + ); + + var destX = Math.round(Math.max(0, this.canvas.width - destWidth)/2); + var destY = Math.round(Math.max(0, this.canvas.height - destHeight)/2); + + var oldDestWidth = destWidth; + var oldSourceWidth = sourceWidth; + + if(this.zoom == 1 && panDir && this.panX){ + + if(this.panX < 0){ + if(panDir > 0){ + // If the touch is moving left, and the right side of the + // image should be shown, then reduce the destination width + // by the absolute value of panX + destWidth -= Math.abs(this.panX); + destX = 0; + }else if(panDir < 0){ + // If the touch is moving left, and the left side of the + // image should be shown, then set the displayed width + // to the absolute value of panX, less some pixels for + // a padding between images + destWidth = Math.max(1, Math.abs(this.panX) - 5); + destX = this.size.w - destWidth; + } + }else{ + if(panDir > 0){ + // If the touch is moving right, and the right side of the + // image should be shown, then set the destination width + // to the absolute value of the pan, less some pixels for + // padding + destWidth = Math.max(1, Math.abs(this.panX) - 5); + destX = 0; + }else if(panDir < 0){ + // If the touch is moving right, and the left side of the + // image should be shown, then reduce the destination width + // by the widget width minus the absolute value of panX + destWidth -= Math.abs(this.panX); + destX = this.size.w - destWidth; + } + } + + sourceWidth = Math.max(1, + Math.floor(sourceWidth * (destWidth / oldDestWidth))); + + if(panDir > 0){ + // If the right side of the image should be displayed, move + // the sourceX to be the width of the image minus the difference + // between the original sourceWidth and the new sourceWidth + sourceX = (sourceX + oldSourceWidth) - (sourceWidth); + } + sourceX = Math.floor(sourceX); + } + + try{ + + // See https://developer.mozilla.org/en/Canvas_tutorial/Using_images + cxt.drawImage( + img, + Math.max(0, sourceX), + sourceY, + Math.min(oldSourceWidth, sourceWidth), + sourceHeight, + destX, // Xpos + destY, // Ypos + Math.min(oldDestWidth, destWidth), + destHeight + ); + }catch(e){ + console.log("Caught Error",e, + + "type=", img._type, + "oldDestWidth = ", oldDestWidth, + "destWidth", destWidth, + "destX", destX + , "oldSourceWidth=",oldSourceWidth, + "sourceWidth=", sourceWidth, + "sourceX = " + sourceX + ); + } + }, + + _setZoomAttr: function(amount){ + this.zoom = Math.min(this.maxZoom, Math.max(1, amount)); + + if(this.zoom == 1 + && this._centerImg + && this._centerImg._loaded){ + + if(!this.isAnimating()){ + this.zoomCenterX = this._centerImg.width / 2; + this.zoomCenterY = this._centerImg.height / 2; + } + this.panX = this.panY = 0; + } + + this.render(); + }, + + _setZoomCenterXAttr: function(value){ + if(value != this.zoomCenterX){ + if(this._centerImg && this._centerImg._loaded){ + value = Math.min(this._centerImg.width, value); + } + this.zoomCenterX = Math.max(0, Math.round(value)); + } + }, + + _setZoomCenterYAttr: function(value){ + if(value != this.zoomCenterY){ + if(this._centerImg && this._centerImg._loaded){ + value = Math.min(this._centerImg.height, value); + } + this.zoomCenterY = Math.max(0, Math.round(value)); + } + }, + + _setZoomCenterAttr: function(value){ + if(value.x != this.zoomCenterX || value.y != this.zoomCenterY){ + this.set("zoomCenterX", value.x); + this.set("zoomCenterY", value.y); + this.render(); + } + }, + + _setAnimatedZoomAttr: function(amount){ + if(this._anim && this._anim.status() == "playing"){ + return; + } + + this._anim = new dojo.Animation({ + curve: [this.zoom, amount], + onAnimate: this._updateAnimatedZoom, + onEnd: this._onAnimEnd + }); + + this._anim.play(); + }, + + _updateAnimatedZoom: function(amount){ + this._setZoomAttr(amount); + }, + + _setCenterUrlAttr: function(urlOrObj){ + this._setImage("center", urlOrObj); + }, + _setLeftUrlAttr: function(urlOrObj){ + this._setImage("left", urlOrObj); + }, + _setRightUrlAttr: function(urlOrObj){ + this._setImage("right", urlOrObj); + }, + + _setImage: function(name, urlOrObj){ + var smallUrl = null; + + var largeUrl = null; + + if(dojo.isString(urlOrObj)){ + // If the argument is a string, then just load the large url + largeUrl = urlOrObj; + }else{ + largeUrl = urlOrObj.large; + smallUrl = urlOrObj.small; + } + + if(this["_" + name + "Img"] && this["_" + name + "Img"]._src == largeUrl){ + // Identical URL, ignore it + return; + } + + // Just do the large image for now + var largeImg = this["_" + name + "Img"] = new Image(); + largeImg._type = name; + largeImg._loaded = false; + largeImg._src = largeUrl; + largeImg._conn = dojo.connect(largeImg, "onload", this.handleLoad); + + if(smallUrl){ + // If a url to a small version of the image has been provided, + // load that image first. + var smallImg = this["_" + name + "SmallImg"] = new Image(); + smallImg._type = name; + smallImg._loaded = false; + smallImg._conn = dojo.connect(smallImg, "onload", this.handleLoad); + smallImg._isSmall = true; + smallImg._src = smallUrl; + smallImg.src = smallUrl; + } + + // It's important that the large url's src is set after the small image + // to ensure it's loaded second. + largeImg.src = largeUrl; + }, + + handleLoad: function(evt){ + // summary: + // Handles the loading of an image, both the large and small + // versions. A render is triggered as a result of each image load. + + var img = evt.target; + img._loaded = true; + + dojo.disconnect(img._conn); + + var type = img._type; + + switch(type){ + case "center": + this.zoomCenterX = img.width / 2; + this.zoomCenterY = img.height / 2; + break; + } + + var height = img.height; + var width = img.width; + + if(width / this.size.w < height / this.size.h){ + // Fit the height to the height of the canvas + img._baseHeight = this.canvas.height; + img._baseWidth = width / (height / this.size.h); + }else{ + // Fix the width to the width of the canvas + img._baseWidth = this.canvas.width; + img._baseHeight = height / (width / this.size.w); + } + img._centerX = width / 2; + img._centerY = height / 2; + + this.render(); + + this.onLoad(img._type, img._src, img._isSmall); + }, + + onLoad: function(type, url, isSmall){ + // summary: + // Dummy function that is called whenever an image loads. + // type: String + // The position of the image that has loaded, either + // "center", "left" or "right" + // url: String + // The src of the image + // isSmall: Boolean + // True if it is a small version of the image that has loaded, + // false otherwise. + } +}); + +}); |
