X-Git-Url: https://scm.cri.ensmp.fr/git/Portfolio.git/blobdiff_plain/2da869b2b46179d64c05eaac4081226fdbbbc9ff..e73353500c67453ef9663d67eaeee7dd46c358ee:/Products/Portfolio/skins/mosaique.js diff --git a/Products/Portfolio/skins/mosaique.js b/Products/Portfolio/skins/mosaique.js new file mode 100755 index 0000000..47b8d1e --- /dev/null +++ b/Products/Portfolio/skins/mosaique.js @@ -0,0 +1,735 @@ +/* +* © 2007-2008 Benoît Pin – Centre de recherche en informatique – École des mines de Paris +* http://plinn.org +* Licence Creative Commons http://creativecommons.org/licenses/by-nc/2.0/ +* +* +*/ + +var Mosaique; + +(function(){ + +var hiddenTilesNumber = 1; +var batchSize = 5; +var reNb = /\-?\d+/ ; + +Mosaique = function(screenArea, imgUrlBase, margins) { + this.screenArea = screenArea; + if (!margins) + margins = {'top':0, 'right':0, 'bottom':0, 'left':0}; + this.margins = margins; + this.prepareScreen(); + this.setContainerPosition(new Point(0,0)); + + this.imgUrlBase = imgUrlBase; + this.xmlPath = imgUrlBase + "/tiling_infos.xml"; + + this.tiles = null; + this.xTileRange = [0,0]; + this.yTileRange = [0,0]; + + this._loadingQueue = new Array(); + this._currentSequence = null; + this._loadingIterator = 0; + this.loadingState = 0; + + this.dragInProgress = false; + this.initialClickPoint = null; + this.initialPosition = null; + var thisMos = this; + this._ddHandlers = {'down' : function(evt){thisMos._mouseDownHandler(evt);}, + 'move' : function(evt){thisMos._mouseMoveHandler(evt);}, + 'up' : function(evt){thisMos._mouseUpHandler(evt);}}; + this.ddHandlers = null; + + this.rcsItoC = new Point(0, 0); // vector to translate coordinate systems + + this.remainD = new Point(0, 0); + this.getXmlInfo(); +} + +Mosaique.prototype.getXmlInfo = function() { + var req = new XMLHttpRequest(); + var thisMosaique = this; + req.onreadystatechange = function() { + if(req.readyState == 4) + thisMosaique._loadXmlInfo(req); + }; + req.open("GET",thisMosaique.xmlPath,true); + req.setRequestHeader("Content-Type", "text/xml; charset=utf-8"); + req.send(null); +}; + +Mosaique.prototype._loadXmlInfo = function(req) { + var doc = req.responseXML.documentElement; + this.thumbnailWidth = parseInt(doc.getElementsByTagName('thumbnailwidth')[0].firstChild.data); + this.thumbnailHeight = parseInt(doc.getElementsByTagName('thumbnailheight')[0].firstChild.data); + this.tileSize = parseInt(doc.getElementsByTagName('tilesize')[0].firstChild.data); + + var zoomList = doc.getElementsByTagName("zoom"); + this.zoomTable = new Array(zoomList.length); + var zoom; + for (var i=0 ; i= this.imageHeight) + ulc.y = this.imageHeight/2 - this.screenHeight/2; + this.loadScreen(ulc); + this.addEventListeners(); +}; + +Mosaique.prototype.addEventListeners = function() { + var thisMos = this; + addListener(document, 'mousedown', function(evt){thisMos.mouseDownHandler(evt);}, 'mosaique.dd'); + addListener(document, 'mouseup', function(evt){thisMos.mouseUpHandler(evt);}, 'mosaique.dd'); +}; + +Mosaique.prototype.getBestFitZoom = function() { + var i; + for (i = 1 ; i < this.zoomTable.length ; i++) + if (this.screenWidth - this.zoomTable[i]['width'] < 0 || + this.screenHeight - this.zoomTable[i]['height'] < 0) + break; + return i-1; +}; + +Mosaique.prototype.setCurrentDimensionValues = function(zoomIndex) { + var zoomInfo = this.zoomTable[zoomIndex]; + this.zoomIndex = zoomIndex; + this.zoomLevel = zoomInfo['level']; + this.imageWidth = zoomInfo['width']; + this.imageHeight = zoomInfo['height']; + this.xtiles = zoomInfo['tilesX']; + this.ytiles = zoomInfo['tilesY']; + this.halfTileSize = this.tileSize / 2; + this.gridWidth = this.xtiles*this.tileSize; + this.gridHeight = this.ytiles*this.tileSize; + + var WnbTiles = Math.ceil(this.screenWidth/this.tileSize) + 2 * hiddenTilesNumber; + var HnbTiles = Math.ceil(this.screenHeight/this.tileSize) + 2 * hiddenTilesNumber; + this.WnbTiles = (WnbTiles > this.xtiles) ? this.xtiles : WnbTiles; + this.HnbTiles = (HnbTiles > this.ytiles) ? this.ytiles : HnbTiles; + +}; + +Mosaique.prototype.prepareScreen = function() { + this.screenWidth = getObjectWidth(this.screenArea); + this.screenHeight = getObjectHeight(this.screenArea); + var mask = this.rootElement = document.createElement('div'); + with (mask.style) { + position = 'absolute'; + width = this.screenWidth - this.margins['right'] + 'px'; + height = this.screenHeight - this.margins['bottom'] + 'px'; + background = base_properties["contentBackgroundColor"]; + overflow = 'hidden'; + } + this.screenArea.insertBefore(mask, this.screenArea.firstChild); + + var container = document.createElement('div'); + with (container.style) { + position = 'absolute'; + top = '0px'; + left = '0px'; + cursor = 'move'; + } + + mask.appendChild(container); + this.container = container; +}; + +Mosaique.prototype.prepareContainer = function() { + var __getImg; + if (browser.isGecko) { + __getImg = function(evt, o) { + return o; + } + } + else { + __getImg = function(evt) { + return getTargetedObject(evt); + } + } + var thisMos = this; + var loadNext; + if (browser.isIE) { + loadNext = function(evt) { + var tile = __getImg(evt, this); + tile.style.visibility = 'visible'; + setTimeout(function(){thisMos._loadNextTile();}, 1); + } + } + else { + loadNext = function(evt) { + var tile = __getImg(evt, this); + tile.style.visibility = 'visible'; + thisMos._loadNextTile(); + } + } + + this.setContainerPosition(new Point(0,0)); + this.xTileRange = [0,0]; + this.yTileRange = [0,0]; + + var size = parseInt(this.tileSize); + this.tiles = new Array(this.WnbTiles); + for (var x = 0 ; x < this.WnbTiles ; x++) { + this.tiles[x] = new Array(this.HnbTiles); + for (var y = 0 ; y < this.HnbTiles ; y++) { + var img = document.createElement("img"); + with(img) { + width = size; + height = size; + } + addListener(img, 'load', loadNext, 'mosaique.tiles'); + img.style.position='absolute'; + img.style.left = x * size + 'px'; + img.style.top = y * size + 'px'; + this.container.appendChild(img); + this.tiles[x][y] = img; + } + } + with(this.container.style) { + width = size * this.WnbTiles + 'px'; + height = size * this.HnbTiles + 'px'; + } +}; + + +Mosaique.prototype.setContainerPosition = function(point) { + with(this.container.style) { + left = point.x + 'px'; + top = point.y + 'px'; + } +}; + +Mosaique.prototype.getContainerPosition = function() { + var x = parseInt(this.container.style.left); + var y = parseInt(this.container.style.top); + var p = new Point(x, y); + return p; +}; + +Mosaique.prototype.setImagePosition = function(point) { + this.setContainerPosition(this.rcsItoC.diff(point)); +}; + +Mosaique.prototype.getImagePosition = function() { + var cp = this.getContainerPosition(); + return this.rcsItoC.diff(cp); +}; + +Mosaique.prototype.getImageCenterPosition = function() { + var ip = this.getImagePosition(); + return ip.add(new Point(this.screenWidth/2, this.screenHeight/2)); +} + + + +Mosaique.prototype.loadScreen = function(position) { + + var tSize = this.tileSize; + var ulTileCoord = new Point(Math.floor(position.x/tSize), Math.floor(position.y/tSize)); + var modulo = new Point(position.x % tSize, position.y % tSize); + + var adjX, adjY; + if (modulo.x > this.halfTileSize) { + adjX = function(n){return n - hiddenTilesNumber + 1;}; + this.remainD.x = - (modulo.x - tSize); + } + else { + adjX = function(n){return n - hiddenTilesNumber;}; + this.remainD.x = - modulo.x; + } + if (modulo.y > this.halfTileSize) { + this.remainD.y = - (modulo.y - tSize); + adjY = function(n){return n - hiddenTilesNumber + 1;}; + } + else { + adjY = function(n){return n - hiddenTilesNumber;}; + this.remainD.y = - modulo.y; + } + + var xTileRange, yTileRange; + xTileRange = [ulTileCoord.x, ulTileCoord.x + this.WnbTiles].map(adjX); + yTileRange = [ulTileCoord.y, ulTileCoord.y + this.HnbTiles].map(adjY); + + //console.assert(xTileRange[1] - xTileRange[0] == this.WnbTiles, xTileRange, this.WnbTiles); + //console.assert(yTileRange[1] - yTileRange[0] == this.HnbTiles, yTileRange, this.HnbTiles); + + if (xTileRange[0] < 0) { + this.remainD.x = Math.abs(xTileRange[0] + ((position.x < 0) ? 1 : 0)) * tSize - modulo.x; + xTileRange = [0, this.WnbTiles]; + } + else if (xTileRange[1] > this.xtiles) { + this.remainD.x = - (xTileRange[1] - this.xtiles - ((modulo.x > this.halfTileSize) ? 1 : 0)) * tSize - modulo.x; + xTileRange = [this.xtiles - this.WnbTiles, this.xtiles]; + } + + if (yTileRange[0] < 0) { + this.remainD.y = Math.abs(yTileRange[0] + ((position.y < 0) ? 1 : 0)) * tSize - modulo.y; + yTileRange = [0, this.HnbTiles]; + } + else if (yTileRange[1] > this.ytiles) { + this.remainD.y = - (yTileRange[1] - this.ytiles - ((modulo.y > this.halfTileSize) ? 1 : 0)) * tSize - modulo.y; + yTileRange = [this.ytiles - this.HnbTiles, this.ytiles]; + } + + //console.assert(xTileRange[0] >= 0 && xTileRange[1] <= this.xtiles); + //console.assert(yTileRange[0] >= 0 && yTileRange[1] <= this.ytiles); + + var dTx = this.xTileRange[0] - xTileRange[0]; + var dTy = this.yTileRange[0] - yTileRange[0]; + this.rcsItoC = this.rcsItoC.diff(new Point(dTx * tSize, dTy * tSize)); + + this.setImagePosition(position); + + this.xTileRange = xTileRange; + this.yTileRange = yTileRange; + + var xOffset = this.xTileRange[0]; + var yOffset = this.yTileRange[0]; + + var baseUrl = this.imgUrlBase + '/getTile?zoom=' + this.zoomLevel / 100.0; + + var tilesSrc = new Array(this.WnbTiles); + for (var x = 0 ; x < this.WnbTiles ; x++) { + tilesSrc[x] = new Array(this.HnbTiles); + for (var y = 0 ; y < this.HnbTiles ; y++) { + tilesSrc[x][y] = baseUrl + '&x=' + (x + xOffset) + '&y=' + (y + yOffset); + } + } + this.queueLoadingSequence({'type':'full', + 'src' : tilesSrc, + 'order' : this.loadingOrder, + 'length' : this.loadingOrder.length}); +}; + +Mosaique.prototype.loadColumns = function(n){ + /* + n > 0 <=> x displacement > 0 => shift columns from right to left + Returns the number of columns that have been loaded. + */ + + + var newRange; + + while (n != 0) { + newRange = [this.xTileRange[0] - n, this.xTileRange[1] - n] + if (newRange[0]<0 || newRange[1] > this.xtiles) { + (n>0) ? n-- : n++; + continue; + } + else { + this.xTileRange = newRange; + break; + } + return 0; + } + + + var shift, from, to, increment; + if (n>0) { + shift = - (this.WnbTiles) * this.tileSize; + from = 0; + to = n; + increment = 1; + } + else { + shift = (this.WnbTiles) * this.tileSize; + from = this.WnbTiles - 1; + to = this.WnbTiles + n -1; + increment = -1; + } + + var thisMos = this; + var beforeSequence = function(){thisMos._shiftColumns(n, shift, from, to, increment)}; + + var order = new Array(); + var tilesSrc = new Array(); + var baseUrl = this.imgUrlBase + '/getTile?zoom=' + this.zoomLevel / 100.0; + var xOffset = this.xTileRange[0]; + var yOffset = this.yTileRange[0]; + + for (var x = from ; x != to ; x += increment) { + tilesSrc[x] = new Array(); + for (var y = 0 ; y < this.HnbTiles ; y++) { + order.push([x, y]); + tilesSrc[x][y] = baseUrl + '&x=' + (x + xOffset) + '&y=' + (y + yOffset); + } + } + order = order.reverse(); + this.queueLoadingSequence({'type':'column', + 'src' : tilesSrc, + 'order' : order, + 'length' : order.length, + 'beforeSequence' : beforeSequence}); + return n; +}; + +Mosaique.prototype._shiftColumns = function(n, shift, from, to, increment){ + /* rows rotations */ + this.tiles = rotateArray(this.tiles, -n); + var tile, left; + /* positional shifting */ + for (var x = from ; x != to ; x += increment) { + left = parseInt(this.tiles[x][0].style.left); + for (var y = 0 ; y < this.HnbTiles ; y++) { + var tile = this.tiles[x][y]; + tile.style.left = left + shift + 'px'; + tile.style.visibility = 'hidden'; + } + } + +}; + + + +Mosaique.prototype.loadRows = function(n) { + /* + n > 0 <=> y displacement > 0 => shift rows from bottom to top + Returns the number of rows that's have been loaded. + */ + + var newRange; + + while (n != 0) { + newRange = [this.yTileRange[0] - n, this.yTileRange[1] - n] + if (newRange[0]<0 || newRange[1] > this.ytiles) { + (n>0) ? n-- : n++; + continue; + } + else { + this.yTileRange = newRange; + break; + } + return 0; + } + + var shift, from, to, increment; + if (n>0) { + shift = - (this.HnbTiles) * this.tileSize; + from = 0; + to = n; + increment = 1; + } + else { + shift = (this.HnbTiles) * this.tileSize; + from = this.HnbTiles - 1; + to = this.HnbTiles + n -1; + increment = -1; + } + + var thisMos = this; + var beforeSequence = function(){thisMos._shiftRows(n, shift, from, to, increment)}; + + var order = new Array(); + var tilesSrc = new Array(); + var baseUrl = this.imgUrlBase + '/getTile?zoom=' + this.zoomLevel / 100.0; + var xOffset = this.xTileRange[0]; + var yOffset = this.yTileRange[0]; + + for (var y = from ; y != to ; y += increment) { + for (var x = 0 ; x < this.WnbTiles ; x++) { + order.push([x, y]); + if (!tilesSrc[x]) + tilesSrc[x] = new Array(); + tilesSrc[x][y] = baseUrl + '&x=' + (x + xOffset) + '&y=' + (y + yOffset); + } + } + order = order.reverse(); + this.queueLoadingSequence({'type':'row', + 'src' : tilesSrc, + 'order' : order, + 'length' : order.length, + 'beforeSequence' : beforeSequence}); + return n; +}; + +Mosaique.prototype._shiftRows = function(n, shift, from, to, increment) { + /* columns rotations */ + for (var x = 0 ; x < this.WnbTiles ; x++) + this.tiles[x] = rotateArray(this.tiles[x], -n); + + var tile, top; + + /* positional shifting */ + for (var y = from ; y != to ; y += increment) { + top = parseInt(this.tiles[0][y].style.top); + for (var x = 0 ; x < this.WnbTiles ; x++) { + var tile = this.tiles[x][y]; + tile.style.top = top + shift + 'px'; + tile.style.visibility = 'hidden'; + } + } +}; + + +Mosaique.prototype.queueLoadingSequence = function(sequenceInfo) { + if(!sequenceInfo.length) return; + this._loadingQueue.push(sequenceInfo); + if (!this.loadingState && this._loadingQueue.length) + this._loadNextSequence(); +}; + +Mosaique.prototype._loadNextSequence = function() { + var seq = this._loadingQueue.shift(); + if (seq == null) { + this._loadingQueue = new Array(); + this.loadingState = 0; + return; + } + switch(seq['type']) { + case 'full' : + this.loadingState = 1; + break; + case 'row' : + case 'column' : + this.loadingState = 2; + seq['beforeSequence'](); + break; + } + this._loadingIterator = 0; + //this._loadNextTile(); + this._startSequence(seq); +}; + +Mosaique.prototype._startSequence = function(seq) { + this._currentSequence = seq; + + var size = Math.min(batchSize, this._currentSequence.length); + this._loadingIterator += size; + + var coord, src, tile; + for (var i=0 ; i= this._currentSequence['length']) { + this._loadNextSequence(); + return; + } + + var coord = this._currentSequence.order[this._loadingIterator]; + this._loadingIterator++; + + var src = this._currentSequence.src[coord[0]][coord[1]]; + var tile = this.tiles[coord[0]][coord[1]]; + tile.src = src; +}; + + +/* drag and drop generic handlers */ +Mosaique.prototype.mouseDownHandler = function(evt) { + var target = getTargetedObject(evt); + if (target.tagName == 'INPUT' || target.tagName == 'TEXTAREA') + return; + disableDefault(evt); + evt = getEventObject(evt); + var navDisp = this.navigateur.display; + + if (target.parentNode.parentNode == navDisp) { + if (target == this.navigateur.frame.firstChild) + this.ddHandlers = this.navigateur._ddHandlers; + else { + this.ddHandlers = null; + return; + } + } + else + this.ddHandlers = this._ddHandlers; + + addListener(document, 'mousemove', this.ddHandlers['move'], 'mosaique.dd'); + + this.ddHandlers['down'](evt); +}; + +Mosaique.prototype.mouseUpHandler = function(evt) { + if (this.ddHandlers != null) { + removeListener(document, 'mousemove', this.ddHandlers['move']); + this.ddHandlers['up'](evt); + this.ddHandlers = null; + } +} + + +/* Mosaique drag and drop handlers */ +Mosaique.prototype._mouseDownHandler = function(evt) { + this.initialClickPoint = new Point(evt.clientX, evt.clientY); + this.initialPosition = this.getContainerPosition(); + this.rShift = 0; + this.cShift = 0; + this.dragInProgress = true; +} + +Mosaique.prototype._mouseMoveHandler = function(evt) { + disableDefault(evt); + if(!this.dragInProgress) + return; + + evt = getEventObject(evt); + var currentPoint = new Point(evt.clientX, evt.clientY); + var displacement = currentPoint.diff(this.initialClickPoint); + this.setContainerPosition(this.initialPosition.add(displacement)); + + var r = (displacement.y + this.halfTileSize + this.remainD.y) / this.tileSize; + r = Math.floor(r); + + if (this.rShift - r != 0) + this.rShift += this.loadRows(r - this.rShift); + + var c = (displacement.x + this.halfTileSize + this.remainD.x) / this.tileSize; + c = Math.floor(c); + + if (this.cShift - c != 0) + this.cShift += this.loadColumns(c - this.cShift); +}; + +Mosaique.prototype._mouseUpHandler = function(evt) { + this.dragInProgress = false; + evt = getEventObject(evt); + this._mouseMoveHandler(evt); + var finalPoint = new Point(evt.clientX, evt.clientY); + var displacement = finalPoint.diff(this.initialClickPoint); + this.remainD = this.remainD.add(new Point(displacement.x - this.cShift * this.tileSize, displacement.y - this.rShift * this.tileSize)); + this.navigateur.alignFrame(); +}; + + + +Mosaique.prototype.setLoadingOrder = function() { + var startX = 0; + var stopX = this.WnbTiles; + var startY = 0; + var stopY = this.HnbTiles; + var x = 0, y = 0; + var order = new Array(); + + var direction=0; + + + while((startX != stopX) && (startY != stopY)) { + switch(direction) { + case 0 : // left -> right + startY++; + + for (x = startX ; x < stopX ; x++) + order.push([x, y]); + x--; + break; + + case 1 : // up -> bottom + stopX--; + for (y = startY ; y < stopY ; y++) + order.push([x, y]); + y--; + break; + + case 2 : // right -> left + stopY--; + + for (x = stopX-1 ; x >= startX ; x--) + order.push([x, y]) + x++; + break; + + case 3 : // bottom -> up + startX++; + + for (y = stopY-1 ; y >= startY ; y--) + order.push([x,y]); + y++; + break; + } + + direction++; + if (direction % 4 == 0) + direction = 0; + } + + + this.loadingOrder = order.reverse(); +} + +Mosaique.prototype.cleanContainer = function() { + removeGroupListeners('mosaique.tiles'); + while (this.container.childNodes[0]) + this.container.removeChild(this.container.childNodes[0]); +} + +Mosaique.prototype.loadZoomLevel = function(zoomIndex) { + this.loadingState = 0; + var oldWnbTiles = this.WnbTiles; + var oldHnbTiles = this.HnbTiles; + var oldCenter = this.getImageCenterPosition(); + var zoomInfo = this.zoomTable[zoomIndex]; + var newLevel = zoomInfo['level']; + + // center coordinates translated into target zoom level + var center = oldCenter.mul(newLevel / this.zoomLevel); + var ulc = center.diff(new Point(this.screenWidth/2, this.screenHeight/2)); // upper left corner + + this.setCurrentDimensionValues(zoomIndex); + + if (oldWnbTiles != this.WnbTiles || oldHnbTiles != this.HnbTiles) { + this.cleanContainer(); + this.prepareContainer(); + this.rcsItoC = new Point(0,0); + this.setLoadingOrder(); + } + this.loadScreen(ulc); +}; + +Mosaique.prototype.unload = function() { + this.navigateur.unload(); + removeGroupListeners('mosaique.dd'); + removeGroupListeners('mosaique.tiles'); + this.screenArea.removeChild(this.rootElement); +}; + +})(); + +/* UTILS */ +function Point(x, y) { + this.x = Math.round(x); + this.y = Math.round(y); +} +Point.prototype.diff = function(point) { return new Point(this.x - point.x, this.y - point.y); }; +Point.prototype.add = function(point) { return new Point(this.x + point.x, this.y + point.y); }; +Point.prototype.mul = function(k) { return new Point(this.x * k, this.y *k)}; +Point.prototype.toString = function() { return "(" + String(this.x) + ", " + String(this.y) + ")"; }; + +function rotateArray(t, n) { + return t.slice(n,t.length).concat(t.slice(0,n)); +} + +if (!Array.prototype.map) { + Array.prototype.map = function(f) { + var r = new Array(this.length); + for (var i = 0 ; i < this.length ; i++ ){ + r[i] = f(this[i]); + } + return r; + }; +}