From 2e04af953463643791f6362bd8ef4c6ba190abfa Mon Sep 17 00:00:00 2001 From: Valerio Virgillito Date: Wed, 18 Apr 2012 13:48:51 -0700 Subject: Squashed commit of the following: commit 2054551bfb01a0f4ca2e138b9d724835462d45cd Merge: 765c2da 616a853 Author: Valerio Virgillito Date: Wed Apr 18 13:48:21 2012 -0700 Merge branch 'refs/heads/master' into integration commit 765c2da8e1aa03550caf42b2bd5f367555ad2843 Author: Valerio Virgillito Date: Tue Apr 17 15:29:41 2012 -0700 updating the picasa carousel Signed-off-by: Valerio Virgillito commit 9484f1c82b81e27edf2dc0a1bcc1fa3b12077406 Merge: d27f2df cacb4a2 Author: Valerio Virgillito Date: Tue Apr 17 15:03:50 2012 -0700 Merge branch 'refs/heads/master' into integration commit d27f2df4d846064444263d7832d213535962abe7 Author: Valerio Virgillito Date: Wed Apr 11 10:39:36 2012 -0700 integrating new picasa carousel component Signed-off-by: Valerio Virgillito commit 6f98384c9ecbc8abe55ccfe1fc25a0c7ce22c493 Author: Valerio Virgillito Date: Tue Apr 10 14:33:00 2012 -0700 fixed the text area case issue Text area was renamed from TextArea to Textarea Signed-off-by: Valerio Virgillito commit 1e83e26652266136802bc7af930379c1ecd631a6 Author: Valerio Virgillito Date: Mon Apr 9 22:10:45 2012 -0700 integrating montage v0.8 into ninja. Signed-off-by: Valerio Virgillito Signed-off-by: Valerio Virgillito --- node_modules/montage/ui/flow.reel/flow.js | 1002 +++++++++++++++++++++-------- 1 file changed, 740 insertions(+), 262 deletions(-) (limited to 'node_modules/montage/ui/flow.reel/flow.js') diff --git a/node_modules/montage/ui/flow.reel/flow.js b/node_modules/montage/ui/flow.reel/flow.js index 089353a8..929e61b8 100644 --- a/node_modules/montage/ui/flow.reel/flow.js +++ b/node_modules/montage/ui/flow.reel/flow.js @@ -5,145 +5,319 @@ */ var Montage = require("montage").Montage, - Component = require("ui/component").Component; + Component = require("ui/component").Component, + FlowBezierSpline = require("ui/flow-bezier-spline").FlowBezierSpline; var Flow = exports.Flow = Montage.create(Component, { - // TODO: Review _externalUpdate + _splinePaths: { + enumerable: false, + value: null + }, - _externalUpdate: { + splinePaths: { enumerable: false, - value: true + get: function () { + if (!this._splinePaths) { + this._splinePaths = []; + } + return this._splinePaths; + }, + set: function (value) { + this._splinePaths = value; + } }, - _isCameraUpdated: { + appendPath: { + value: function (path) { + var splinePath = Object.create(FlowBezierSpline), + pathKnots = path.knots, + length = path.knots.length, + knots = [], + nextHandlers = [], + previousHandlers = [], + densities = [], + i; + + for (i = 0; i < length; i++) { + knots[i] = pathKnots[i].knotPosition; + previousHandlers[i] = pathKnots[i].previousHandlerPosition; + nextHandlers[i] = pathKnots[i].nextHandlerPosition; + densities[i] = pathKnots[i].previousDensity; // TODO: implement previous/next density + // TODO: add rotations, arbitrary CSS and units + } + splinePath.knots = knots; + splinePath.previousHandlers = previousHandlers; + splinePath.nextHandlers = nextHandlers; + splinePath.densities = densities; + splinePath._computeDensitySummation(); + this.splinePaths.push(splinePath); + this._paths.push(path); + } + }, + + _paths: { enumerable: false, - value: false + value: null }, - // Camera rotation based in CSS3 rotate3D axis/angle system + paths: { // TODO: listen for changes? + get: function () { + return this._paths; + }, + set: function (value) { + var length = value.length, + i; - _cameraRotationAxisX: { + if (length) { + this._paths = []; + for (i = 0; i < length; i++) { + this.appendPath(value[i]); + } + } + } + }, + + _cameraPosition: { enumerable: false, - value: 0 + value: [0, 0, 800] }, - _cameraRotationAxisY: { + _cameraTargetPoint: { enumerable: false, - value: 0 + value: [0, 0, 0] }, - _cameraRotationAxisZ: { + _cameraFov: { enumerable: false, - value: 1 + value: 50 }, - _cameraRotationAngle: { + // TODO: Implement camera roll + + _cameraRoll: { enumerable: false, value: 0 }, - cameraRotationAxisX: { + cameraPosition: { get: function () { - return this._cameraRotationAxisX; + return this._cameraPosition; }, set: function (value) { - this._cameraRotationAxisX = value; + this._cameraPosition = value; this._isCameraUpdated = true; this.needsDraw = true; } }, - cameraRotationAxisY: { + cameraTargetPoint: { get: function () { - return this._cameraRotationAxisY; + return this._cameraTargetPoint; }, set: function (value) { - this._cameraRotationAxisY = value; + this._cameraTargetPoint = value; this._isCameraUpdated = true; this.needsDraw = true; } }, - cameraRotationAxisZ: { + cameraFov: { get: function () { - return this._cameraRotationAxisZ; + return this._cameraFov; }, set: function (value) { - this._cameraRotationAxisZ = value; + this._cameraFov = value; this._isCameraUpdated = true; this.needsDraw = true; } }, - cameraRotationAngle: { + cameraRoll: { get: function () { - return this._cameraRotationAngle; + return this._cameraRoll; }, set: function (value) { - this._cameraRotationAngle = value; + this._cameraRoll = value; this._isCameraUpdated = true; this.needsDraw = true; } }, - _path: { + _scrollingTransitionDurationMiliseconds: { enumerable: false, - value: { - value: function (slide) { - return { - translateX: slide.time, - translateY: 0, - translateZ: 0, - scale: 1, - rotateX: 0, - rotateY: 0, - rotateZ: 0, - transformOriginX: 0, - transformOriginY: 0, - transformOriginZ: 0, - style: {} - }; - } - } + value: 500 + }, + + _scrollingTransitionDuration: { + enumerable: false, + value: "500ms" }, - path: { + scrollingTransitionDuration: { // TODO: think about using the Date Converter get: function () { - return this._path; + return this._scrollingTransitionDuration; }, - set: function (value) { - this._path = value; - this.needsDraw = true; + set: function (duration) { + var durationString = duration + "", + length = durationString.length, + value; + + if ((length >= 2) && (durationString[length - 1] === "s")) { + if ((length >= 3) && (durationString[length - 2] === "m")) { + value = durationString.substr(0, length - 2) - 0; + } else { + value = durationString.substr(0, length - 1) * 1000; + } + } else { + value = durationString - 0; + durationString += "ms"; + } + if (!isNaN(value) && (this._scrollingTransitionDurationMiliseconds !== value)) { + this._scrollingTransitionDurationMiliseconds = value; + this._scrollingTransitionDuration = durationString; + } + } + }, + + _scrollingTransitionTimingFunctionBezier: { + enumerable: false, + value: [.25, .1, .25, 1] + }, + + _scrollingTransitionTimingFunction: { + enumerable: false, + value: "ease" + }, + + hasSelectedIndexScrolling: { + enumerable: false, + value: false + }, + + selectedIndexScrollingOffset: { + enumerable: false, + value: 0 + }, + + _handleSelectedIndexesChange: { + enumerable: false, + value: function (event) { + if (this.hasSelectedIndexScrolling && event._plus) { + this.startScrollingIndexToOffset(event._plus[0], this.selectedIndexScrollingOffset); + } } }, - _rotationOrder: { + _timingFunctions: { enumerable: false, - value: "xyz" + value: { + "ease": [.25, .1, .25, 1], + "linear": [0, 0, 1, 1], + "ease-in": [.42, 0, 1, 1], + "ease-out": [0, 0, .58, 1], + "ease-in-out": [.42, 0, .58, 1] + } }, - rotationOrder: { + scrollingTransitionTimingFunction: { get: function () { - return this._rotationOrder; + return this._scrollingTransitionTimingFunction; }, - set: function (value) { - switch (value) { - case "xzy": - case "yxz": - case "yzx": - case "zxy": - case "zyx": - this._rotationOrder=value; - break; - default: - this._rotationOrder="xyz"; - break; + set: function (timingFunction) { + var string = timingFunction + ""; + + if (this._timingFunctions.hasOwnProperty(string)) { + this._scrollingTransitionTimingFunction = string; + this._scrollingTransitionTimingFunctionBezier = this._timingFunctions[string]; + } else { + if ((string.substr(0, 13) === "cubic-bezier(") && (string.substr(string.length - 1, 1) === ")")) { + var bezier = string.substr(13, string.length - 14).split(","), + i; + + if (bezier.length === 4) { + for (i = 0; i < 4; i++) { + bezier[i] -= 0; + if (isNaN(bezier[i])) { + return; + } + } + if (bezier[0] < 0) { + bezier[0] = 0; + } else { + if (bezier[0] > 1) { + bezier[0] = 1; + } + } + if (bezier[2] < 0) { + bezier[2] = 0; + } else { + if (bezier[2] > 1) { + bezier[2] = 1; + } + } + // TODO: check it is not the same bezier + this._scrollingTransitionTimingFunction = "cubic-bezier(" + bezier + ")"; + this._scrollingTransitionTimingFunctionBezier = bezier; + } + } } + } + }, + + _computeCssCubicBezierValue: { + enumerable: false, + value: function (x, bezier) { + var t = .5, + step = .25, + t2, + k, + i; + + for (i = 0; i < 20; i++) { // TODO: optimize with Newton's method or similar + t2 = t * t; + k = 1 - t; + if ((3 * (k * k * t * bezier[0] + k * t2 * bezier[2]) + t2 * t) > x) { + t -= step; + } else { + t += step; + } + step *= .5; + } + t2 = t * t; + k = 1 - t; + return 3 * (k * k * t * bezier[1] + k * t2 * bezier[3]) + t2 * t; + } + }, + + _isTransitioningScroll: { + enumerable: false, + value: false + }, + + stopScrolling: { + value: function () { + this._isTransitioningScroll = false; + // TODO: Fire scrollingTransitionCancel event + } + }, + + startScrollingIndexToOffset: { // TODO: Fire scrollingTransitionStart event + value: function (index, offset) { + this._scrollingOrigin = this.origin; + this._scrollingDestination = index - offset; + this._isScrolling = true; + this._scrollingStartTime = Date.now(); + this._isTransitioningScroll = true; this.needsDraw = true; } }, + _isCameraUpdated: { + enumerable: false, + value: true + }, + _width: { enumerable: false, value: null @@ -159,10 +333,312 @@ var Flow = exports.Flow = Montage.create(Component, { value: null }, + _elementsBoundingSphereRadius: { + enumerable: false, + value: 150 + }, + + elementsBoundingSphereRadius: { + get: function () { + return this._elementsBoundingSphereRadius; + }, + set: function (value) { + if (this._elementsBoundingSphereRadius !== value) { + this._elementsBoundingSphereRadius = value; + this.needsDraw = true; + } + } + }, + + _computeFrustumNormals: { + value: function() { + var angle = ((this.cameraFov * .5) * Math.PI * 2) / 360, + y = Math.sin(angle), + z = Math.cos(angle), + x = (y * this._width) / this._height, + vX = this.cameraTargetPoint[0] - this.cameraPosition[0], + vY = this.cameraTargetPoint[1] - this.cameraPosition[1], + vZ = this.cameraTargetPoint[2] - this.cameraPosition[2], + yAngle = Math.PI / 2 - Math.atan2(vZ, vX), + tmpZ = vX * Math.sin(yAngle) + vZ * Math.cos(yAngle), + rX, rY, rZ, + rX2, rY2, rZ2, + xAngle = Math.PI / 2 - Math.atan2(tmpZ, vY), + invLength, + vectors = [[z, 0, x], [-z, 0, x], [0, z, y], [0, -z, y]], + iVector, + out = [], + i; + + for (i = 0; i < 4; i++) { + iVector = vectors[i]; + rX = iVector[0]; + rY = iVector[1] * Math.cos(-xAngle) - iVector[2] * Math.sin(-xAngle); + rZ = iVector[1] * Math.sin(-xAngle) + iVector[2] * Math.cos(-xAngle); + rX2 = rX * Math.cos(-yAngle) - rZ * Math.sin(-yAngle); + rY2 = rY; + rZ2 = rX * Math.sin(-yAngle) + rZ * Math.cos(-yAngle); + invLength = 1 / Math.sqrt(rX2 * rX2 + rY2 * rY2 + rZ2 * rZ2); + out.push([rX2 * invLength, rY2 * invLength, rZ2 * invLength]); + } + return out; + } + }, + + _segmentsIntersection: { + enumerable: false, + value: function (segment1, segment2) { + var n = 0, + m = 0, + start, + end, + result = []; + + while ((n < segment1.length) && (m < segment2.length)) { + if (segment1[n][0] >= segment2[m][1]) { + m++; + } else { + if (segment1[n][1] <= segment2[m][0]) { + n++; + } else { + if (segment1[n][0] >= segment2[m][0]) { + start = segment1[n][0]; + } else { + start = segment2[m][0]; + } + if (segment1[n][1] <= segment2[m][1]) { + end = segment1[n][1]; + } else { + end = segment2[m][1]; + } + result.push([start, end]); + if (segment1[n][1] < segment2[m][1]) { + n++; + } else { + if (segment1[n][1] > segment2[m][1]) { + m++; + } else { + n++; + m++; + } + } + } + } + } + return result; + } + }, + + _computeVisibleRange: { // TODO: make it a loop, optimize + enumerable: false, + value: function (spline) { + var splineLength = spline.knotsLength - 1, + planeOrigin = this._cameraPosition, + normals = this._computeFrustumNormals(), + mod, + r, r2, r3 = [], out = [], tmp, + i, j; + + for (i = 0; i < splineLength; i++) { + mod = normals[0]; + r = spline.directedPlaneBezierIntersection( + [ + planeOrigin[0] - mod[0] * this._elementsBoundingSphereRadius, + planeOrigin[1] - mod[1] * this._elementsBoundingSphereRadius, + planeOrigin[2] - mod[2] * this._elementsBoundingSphereRadius + ], + normals[0], + spline._knots[i], + spline._nextHandlers[i], + spline._previousHandlers[i + 1], + spline._knots[i + 1] + ); + if (r.length) { + mod = normals[1]; + r2 = spline.directedPlaneBezierIntersection( + [ + planeOrigin[0] - mod[0] * this._elementsBoundingSphereRadius, + planeOrigin[1] - mod[1] * this._elementsBoundingSphereRadius, + planeOrigin[2] - mod[2] * this._elementsBoundingSphereRadius + ], + normals[1], + spline._knots[i], + spline._nextHandlers[i], + spline._previousHandlers[i + 1], + spline._knots[i + 1] + ); + if (r2.length) { + tmp = this._segmentsIntersection(r, r2); + if (tmp.length) { + mod = normals[2]; + r = spline.directedPlaneBezierIntersection( + [ + planeOrigin[0] - mod[0] * this._elementsBoundingSphereRadius, + planeOrigin[1] - mod[1] * this._elementsBoundingSphereRadius, + planeOrigin[2] - mod[2] * this._elementsBoundingSphereRadius + ], + normals[2], + spline._knots[i], + spline._nextHandlers[i], + spline._previousHandlers[i + 1], + spline._knots[i + 1] + ); + tmp = this._segmentsIntersection(r, tmp); + if (tmp.length) { + mod = normals[3]; + r = spline.directedPlaneBezierIntersection( + [ + planeOrigin[0] - mod[0] * this._elementsBoundingSphereRadius, + planeOrigin[1] - mod[1] * this._elementsBoundingSphereRadius, + planeOrigin[2] - mod[2] * this._elementsBoundingSphereRadius + ], + normals[3], + spline._knots[i], + spline._nextHandlers[i], + spline._previousHandlers[i + 1], + spline._knots[i + 1] + ); + tmp = this._segmentsIntersection(r, tmp); + for (j = 0; j < tmp.length; j++) { + r3.push([i, tmp[j][0], tmp[j][1]]); + } + } + } + } + } + } + var densities = spline._densities, d1, d2, dS, p1, p2, t1, t2; + for (i = 0; i < r3.length; i++) { + d1 = densities[r3[i][0]]; + d2 = densities[r3[i][0] + 1]; + dS = r3[i][0] ? spline._densitySummation[r3[i][0]-1] : 0; + p1 = r3[i][1]; + p2 = r3[i][2]; + t1 = (d2 - d1) * p1 * p1 * .5 + p1 * d1 + dS; + t2 = (d2 - d1) * p2 * p2 * .5 + p2 * d1 + dS; + out.push([t1, t2]); + } + return out; + } + }, + prepareForDraw: { enumerable: false, value: function () { + var self = this; + this._repetitionComponents = this._repetition._childComponents; + window.addEventListener("resize", function () { + self._isCameraUpdated = true; + self.needsDraw = true; + }, false); + } + }, + + _updateIndexMap: { + enumerable: false, + value: function (currentIndexMap, newIndexes) { + var indexMap = currentIndexMap.slice(0, newIndexes.length), + newIndexesHash = {}, + emptySpaces = [], + j, + i; + + for (i = 0; i < newIndexes.length; i++) { + newIndexesHash[newIndexes[i]] = i; + } + for (i = 0; i < indexMap.length; i++) { + if (newIndexesHash.hasOwnProperty(indexMap[i])) { + newIndexes[newIndexesHash[indexMap[i]]] = null; + } else { + emptySpaces.push(i); + } + } + for (i = j = 0; j < emptySpaces.length; i++) { + if (newIndexes[i] !== null) { + indexMap[emptySpaces[j]] = newIndexes[i]; + j++; + } + } + for (j = indexMap.length; i < newIndexes.length; i++) { + if (newIndexes[i] !== null) { + indexMap[j] = newIndexes[i]; + j++; + } + } + return indexMap; + } + }, + + _updateIndexMap2: { + enumerable: false, + value: function (currentIndexMap, newIndexes) { + var indexMap = currentIndexMap.slice(0, currentIndexMap.length), + newIndexesHash = {}, + emptySpaces = [], + j, + i; + + for (i = 0; i < newIndexes.length; i++) { + newIndexesHash[newIndexes[i]] = i; + } + for (i = 0; i < indexMap.length; i++) { + if (newIndexesHash.hasOwnProperty(indexMap[i])) { + newIndexes[newIndexesHash[indexMap[i]]] = null; + } else { + emptySpaces.push(i); + } + } + for (i = j = 0; (j < emptySpaces.length) && (i < newIndexes.length); i++) { + if (newIndexes[i] !== null) { + indexMap[emptySpaces[j]] = newIndexes[i]; + j++; + } + } + for (j = indexMap.length; i < newIndexes.length; i++) { + if (newIndexes[i] !== null) { + indexMap[j] = newIndexes[i]; + j++; + } + } + return indexMap; + } + }, + + willDraw: { + enumerable: false, + value: function () { + var newIndexMap = [], + intersections, + i, + j, + tmp; + + if (this._isTransitioningScroll) { + var time = (Date.now() - this._scrollingStartTime) / this._scrollingTransitionDurationMiliseconds, // TODO: division by zero + interpolant = this._computeCssCubicBezierValue(time, this._scrollingTransitionTimingFunctionBezier); + + if (time < 1) { + this.origin = this._scrollingOrigin + (this._scrollingDestination - this._scrollingOrigin) * interpolant; + } else { + this.origin = this._scrollingDestination; + this._isTransitioningScroll = false; + } + } + this._width = this._element.offsetWidth; + this._height = this._element.offsetHeight; + if (this.splinePaths.length) { + intersections = this._computeVisibleRange(this.splinePaths[0]); + for (i = 0; i < intersections.length; i++) { + for (j = Math.ceil(intersections[i][0] + this._origin); j < intersections[i][1] + this._origin; j++) { + newIndexMap.push(j); + } + } + tmp = this._updateIndexMap2(this._repetition.indexMap, newIndexMap); + if (this._repetition.indexMap.join("-") !== tmp.join("-")) { + this._repetition.indexMap = tmp; + } + } } }, @@ -170,121 +646,133 @@ var Flow = exports.Flow = Montage.create(Component, { enumerable: false, value: function () { var i, - length = this.numberOfNodes, - slide={ - index: null, - time: null, - speed: null - }, + length = this._repetitionComponents.length, + slide = {}, transform, origin, - iPath, j, - jPath, iOffset, - iStyle; + iStyle, + pos; - if (this.isAnimating) { + if (this._isTransitioningScroll) { + this.needsDraw = true; + } + if (this.isAnimating) { // move it to willDraw this._animationInterval(); } if (this._isCameraUpdated) { - this._repetition._element.style.webkitTransform = "rotate3d("+this._cameraRotationAxisX+","+this._cameraRotationAxisY+","+this._cameraRotationAxisZ+","+this._cameraRotationAngle+"rad)"; + var perspective = Math.tan(((90 - this.cameraFov * .5) * Math.PI * 2) / 360) * this._height * .5, + vX = this.cameraTargetPoint[0] - this.cameraPosition[0], + vY = this.cameraTargetPoint[1] - this.cameraPosition[1], + vZ = this.cameraTargetPoint[2] - this.cameraPosition[2], + yAngle = Math.atan2(-vX, -vZ), // TODO: Review this + tmpZ, + xAngle; + + tmpZ = vX * -Math.sin(-yAngle) + vZ * Math.cos(-yAngle); + xAngle = Math.atan2(-vY, -tmpZ); +// this._element.style.webkitPerspective = perspective + "px"; + this._repetition._element.style.webkitTransform = + "translate3d(" + 0 + "px, " + 0 + "px, " + perspective + "px) rotateX(" + xAngle + "rad) rotateY(" + (-yAngle) + "rad) " + + "translate3d(" + (-this.cameraPosition[0]) + "px, " + (-this.cameraPosition[1]) + "px, " + (-this.cameraPosition[2]) + "px)"; this._isCameraUpdated = false; } - //if (this._externalUpdate) { - for (i=0; ii*this._scale) { + if (x > i) { this.startAnimating(i, x); } } } } this.stopAnimating(this._selectedSlideIndex); - if (!this.isAnimating) { - this._animationInterval=function () { - var animatingLength=self.animating.length, - n, j, i, _iterations=8, - time=Date.now(), - interval1=self.lastDrawTime?(time-self.lastDrawTime)*0.015*this._elasticScrollingSpeed:0, - interval=interval1/_iterations, - mW=self._scale, x, - epsilon=.5; - - for (n=0; n<_iterations; n++) { - for (j=0; ji*self._scale-epsilon) { + j = 0; + while (j < animatingLength) { + i = self.animating[j]; + if (i < self._selectedSlideIndex) { + if (self.slide[i].x > i - epsilon) { self.stopAnimating(i); animatingLength--; } else { j++; } } else { - if (self.slide[i].x --- node_modules/montage/ui/flow.reel/flow.js | 1 + 1 file changed, 1 insertion(+) (limited to 'node_modules/montage/ui/flow.reel/flow.js') diff --git a/node_modules/montage/ui/flow.reel/flow.js b/node_modules/montage/ui/flow.reel/flow.js index 929e61b8..2137f782 100644 --- a/node_modules/montage/ui/flow.reel/flow.js +++ b/node_modules/montage/ui/flow.reel/flow.js @@ -786,6 +786,7 @@ var Flow = exports.Flow = Montage.create(Component, { } else { this._objectsForRepetition = value; } + this.needsDraw = true; } }, -- cgit v1.2.3