/* <copyright> This file contains proprietary software owned by Motorola Mobility, Inc.<br/> No rights, expressed or implied, whatsoever to this software are provided by Motorola Mobility, Inc. hereunder.<br/> (c) Copyright 2011 Motorola Mobility, Inc. All Rights Reserved. </copyright> */ var Montage = require("montage/core/core").Montage; var Component = require("montage/ui/component").Component; var PropertyTrack = exports.PropertyTrack = Montage.create(Component, { hasTemplate:{ value: true }, prepareForDraw:{ value:function(){ this.element.addEventListener("click", this, false); this.trackID = this.parentComponent.parentComponent.parentComponent.trackID; this.animatedElement = this.parentComponent.parentComponent.parentComponent.animatedElement; this.ninjaStylesContoller = this.application.ninja.stylesController; } }, draw:{ value:function(){ } }, didDraw:{ value:function () { if(this.currentKeyframeRule){ this.retrieveStoredStyleTweens(); } } }, trackEditorProperty:{ value:"" }, animatedElement:{ value:null }, isSubproperty:{ value:true }, _propTweenRepetition:{ value:null }, propTweenRepetition:{ get:function () { return this._propTweenRepetition; }, set:function (newVal) { this._propTweenRepetition = newVal; } }, _propTweens:{ value:[] }, propTweens:{ serializable:true, get:function () { return this._propTweens; }, set:function (newVal) { this._propTweens = newVal; } }, _propTrackData:{ value:false }, propTrackData:{ serializable:true, get:function () { return this._propTrackData; }, set:function (val) { this._propTrackData = val; if (this._propTrackData) { this.setData(); } } }, nextKeyframe:{ value:1 }, ninjaStylesContoller:{ value:null }, animationName:{ value:null }, currentKeyframeRule:{ value:null }, trackDuration:{ value:0 }, _trackID:{ value:null }, trackID:{ serializable:true, get:function () { return this._trackID; }, set:function (value) { if (value !== this._trackID) { this._trackID = value; } } }, _trackType:{ value:null }, trackType:{ serializable:true, get:function () { return this._trackType; }, set:function (value) { if (value !== this._trackType) { this._trackType = value; } } }, _styleIndex:{ value:null }, styleIndex:{ serializable:true, get:function () { return this._styleIndex; }, set:function (value) { if (value !== this._styleIndex) { this._styleIndex = value; } } }, setData:{ value:function () { if (typeof(this.propTrackData) === "undefined") { return; } this.styleIndex = this.propTrackData.styleIndex; this.propTweens = this.propTrackData.propTweens; this.trackType = this.propTrackData.trackType; this.trackEditorProperty = this.propTrackData.trackEditorProperty; this.currentKeyframeRule = this.propTrackData.existingRule; this.needsDraw = true; } }, handleClick:{ value:function (ev) { if (ev.shiftKey) { if (this.trackType == "position") { this.parentComponent.parentComponent.parentComponent.parentComponent.handleNewTween(ev); } if (this.propTweens.length < 1) { // check if there is an editor property assigned yet // get this property track's editor prop name from layer data arrays var selectIndex = this.application.ninja.timeline.getLayerIndexByID(this.trackID), currentSelectedStyleIndex = this.getCurrentSelectedStyleIndex(selectIndex); if (this.trackType == "style") { //console.log("PropertyTrack.handleClick; selectIndex = ", selectIndex, "; styleIndex = ", currentSelectedStyleIndex) if (this.application.ninja.timeline.arrLayers[selectIndex].layerData.arrLayerStyles[currentSelectedStyleIndex].editorProperty == null) { console.log("Please enter a style property for this track before adding keyframes."); return; } else { this.trackEditorProperty = this.application.ninja.timeline.arrLayers[selectIndex].layerData.arrLayerStyles[currentSelectedStyleIndex].editorProperty; //console.log("Property track editorProperty set to: " + this.trackEditorProperty); } this.insertPropTween(0); this.addPropAnimationRuleToElement(ev); this.updatePropKeyframeRule(); } else if (this.trackType == "position") { //console.log("Property track editorProperty set to: " + this.trackEditorProperty); } } else { this.handleNewPropTween(ev); if (this.trackType == "style") { this.updatePropKeyframeRule(); } } } } }, getCurrentSelectedStyleIndex: { value: function(layerIndex) { var returnVal = false, i = 0, arrLayerStylesLength = this.application.ninja.timeline.arrLayers[layerIndex].layerData.arrLayerStyles.length; for (i = 0; i < arrLayerStylesLength; i++) { var currItem = this.application.ninja.timeline.arrLayers[layerIndex].layerData.arrLayerStyles[i]; if (currItem.isSelected === true) { returnVal = i; } } return returnVal; } }, handleNewPropTween:{ value:function (ev) { if (ev.offsetX > this.propTweens[this.propTweens.length - 1].tweenData.keyFramePosition) { this.insertPropTween(ev.offsetX); } else { // We will be splitting a tween. Get the x-coordinate of the mouse click within the target element. // You'd think you could use the event.x info for that, right? NO. We must use page values, calculating offsets and scrolling. // Here's an easy function that adds up offsets and scrolls and returns the page x value of an element var findXOffset = function (obj) { var curleft = 0; if (obj.offsetParent) { do { curleft += (obj.offsetLeft - obj.scrollLeft); } while (obj = obj.offsetParent); } return curleft; } var targetElementOffset = findXOffset(ev.currentTarget), position = event.pageX - targetElementOffset; this.splitPropTweenAt(position - 18); } } }, insertPropTween:{ value:function(clickPos){ var selectedIndex = this.application.ninja.timeline.getLayerIndexByID(this.trackID); this.application.ninja.timeline.selectLayer(selectedIndex, true); var currentMillisecPerPixel = Math.floor(this.application.ninja.timeline.millisecondsOffset / 80); var currentMillisec = currentMillisecPerPixel * clickPos; this.trackDuration = currentMillisec; var newTween = {}; newTween.tweenData = {}; newTween.tweenData.tweenedProperties = []; // TODO - check for color values vs px values and set the correct default var propVal = this.ninjaStylesContoller.getElementStyle(this.animatedElement, this.trackEditorProperty); if(propVal == null){ propVal = "1px"; } newTween.tweenData.tweenedProperties[this.trackEditorProperty] = propVal; if (clickPos == 0) { newTween.tweenData.spanWidth = 0; newTween.tweenData.keyFramePosition = 0; newTween.tweenData.keyFrameMillisec = 0; newTween.tweenData.tweenID = 0; newTween.tweenData.spanPosition = 0; this.propTweens.push(newTween); } else { newTween.tweenData.spanWidth = clickPos - this.propTweens[this.propTweens.length - 1].tweenData.keyFramePosition; newTween.tweenData.keyFramePosition = clickPos; newTween.tweenData.keyFrameMillisec = currentMillisec; newTween.tweenData.tweenID = this.nextKeyframe; newTween.tweenData.spanPosition = clickPos - newTween.tweenData.spanWidth; this.propTweens.push(newTween); this.nextKeyframe += 1; } this.application.ninja.currentDocument.model.needsSave = true; } }, splitPropTweenAt:{ value:function (position) { var i, j, nextComponentIndex, tweensLength = this.propTweens.length - 1, prevTween, nextTween, splitTweenIndex; // Search through the tweens and find the pair whose keyframes bracket position. for (i = 0; i < tweensLength; i++) { prevTween = this.propTweens[i].tweenData.keyFramePosition; nextTween = this.propTweens[i + 1].tweenData.keyFramePosition; if (position > prevTween && position < nextTween) { // We will insert a new tween at this index splitTweenIndex = i + 1; // Update the next tween to have new span position and width. this.propTweens[i + 1].tweenData.spanPosition = position; this.propTweens[i + 1].spanPosition = position; this.propTweens[i + 1].tweenData.spanWidth = this.propTweens[i + 1].tweenData.keyFramePosition - position; this.propTweens[i + 1].spanWidth = this.propTweens[i + 1].keyFramePosition - position; // You'd think that would be enough to make the component associated with that part of the array redraw, wouldn't you? // Turns out we have to manually poke the desired childComponent in the repetition to register its new changes. // So we have to get the index of the actual componentin the repetition, which may not match our iteration index. for (j = 0; j < tweensLength + 1; j++) { if (this.propTweenRepetition.childComponents[j].keyFramePosition === nextTween) { nextComponentIndex = j; } } this.propTweenRepetition.childComponents[nextComponentIndex].setData(); // Create the new tween and splice it into the model var newTweenToInsert = {}; newTweenToInsert.tweenData = {}; newTweenToInsert.tweenData.spanWidth = position - prevTween; newTweenToInsert.tweenData.keyFramePosition = position; newTweenToInsert.tweenData.keyFrameMillisec = Math.floor(this.application.ninja.timeline.millisecondsOffset / 80) * position; newTweenToInsert.tweenData.tweenID = this.propTweens.length; newTweenToInsert.tweenData.spanPosition = position - newTweenToInsert.tweenData.spanWidth; newTweenToInsert.tweenData.tweenedProperties = []; newTweenToInsert.tweenData.tweenedProperties[this.trackEditorProperty] = this.ninjaStylesContoller.getElementStyle(this.animatedElement, this.trackEditorProperty); this.propTweens.splice(splitTweenIndex, 0, newTweenToInsert); // We are done, so end the loop. i = tweensLength; } } // We've made a change, so set the needsSave flag this.application.ninja.currentDocument.model.needsSave = true; // Our tween IDs are now all messed up. Fix them. for (i = 0; i <= tweensLength + 1; i++) { this.propTweens[i].tweenID = i; this.propTweens[i].tweenData.tweenID = i; } } }, retrieveStoredStyleTweens:{ value:function(){ var percentValue, fraction, splitValue; var currentMilliSec, currentMilliSecPerPixel, clickPosition, tempTiming, tempTimingFloat, trackTiming, i = 0; if (this.animatedElement !== undefined) { this.animationName = this.currentKeyframeRule.name; if (this.animationName) { trackTiming = this.application.ninja.stylesController.getElementStyle(this.animatedElement, "-webkit-animation-duration"); this.nextKeyframe = 0; for (i = 0; this.currentKeyframeRule[i]; i++) { var newTween = {}; newTween.tweenData = {}; var j, styleLength = this.currentKeyframeRule[i].style.length, keyframeStyles = []; for (j = 0; j < styleLength; j++) { // check for vendor prefixes and skip them for now var firstChar = this.currentKeyframeRule[i].style[j].charAt(0); if (firstChar === "-") { break; } else { var currProp = this.currentKeyframeRule[i].style[j]; var propVal = this.currentKeyframeRule[i].style[currProp]; keyframeStyles.push([currProp, propVal]); } } // recreate tween properties array for timeline tween newTween.tweenData.tweenedProperties = []; for (var k in keyframeStyles) { newTween.tweenData.tweenedProperties[keyframeStyles[k][0]] = keyframeStyles[k][1]; } if (this.currentKeyframeRule[i].keyText === "0%") { newTween.tweenData.spanWidth = 0; newTween.tweenData.keyFramePosition = 0; newTween.tweenData.keyFrameMillisec = 0; newTween.tweenData.tweenID = 0; newTween.tweenData.spanPosition = 0; this.propTweens.push(newTween); } else { tempTiming = trackTiming.split("s"); tempTimingFloat = parseFloat(tempTiming[0]); this.trackDuration = tempTimingFloat * 1000; percentValue = this.currentKeyframeRule[i].keyText; splitValue = percentValue.split("%"); fraction = splitValue[0] / 100; currentMilliSec = fraction * this.trackDuration; currentMilliSecPerPixel = Math.floor(this.application.ninja.timeline.millisecondsOffset / 80); clickPosition = currentMilliSec / currentMilliSecPerPixel; newTween.tweenData.spanWidth = clickPosition - this.propTweens[this.propTweens.length - 1].tweenData.keyFramePosition; newTween.tweenData.keyFramePosition = clickPosition; newTween.tweenData.keyFrameMillisec = currentMilliSec; newTween.tweenData.tweenID = this.nextKeyframe; newTween.tweenData.spanPosition = clickPosition - newTween.tweenData.spanWidth; this.propTweens.push(newTween); } this.nextKeyframe += 1; } } } } }, updatePropKeyframeRule:{ value:function(){ // delete the current rule this.ninjaStylesContoller.deleteRule(this.currentKeyframeRule); // build the new keyframe string var keyframeString = "@-webkit-keyframes " + this.animationName + " {"; for (var i = 0; i < this.propTweens.length; i++) { var keyMill = parseInt(this.propTweens[i].tweenData.keyFrameMillisec); // TODO - trackDur should be parseFloat rounded to significant digits var trackDur = parseInt(this.trackDuration); var keyframePercent = Math.round((keyMill / trackDur) * 100) + "%"; var keyframePropertyString = " " + keyframePercent + " {"; for(var prop in this.propTweens[i].tweenData.tweenedProperties){ keyframePropertyString += prop + ": " + this.propTweens[i].tweenData.tweenedProperties[prop]; } keyframePropertyString += "}"; keyframeString += keyframePropertyString; } keyframeString += " }"; // set the keyframe string as the new rule this.currentKeyframeRule = this.ninjaStylesContoller.addRule(keyframeString); this.application.ninja.currentDocument.model.needsSave = true; } }, addPropAnimationRuleToElement:{ value:function(tweenEvent){ var currentStyleValue = this.ninjaStylesContoller.getElementStyle(this.animatedElement, this.trackEditorProperty); if (currentStyleValue == null) { currentStyleValue = "1px"; } console.log(currentStyleValue); this.propTweens[0].tweenData.tweenedProperties[this.trackEditorProperty] = currentStyleValue; this.animationName = this.animatedElement.classList[0] + "_" + this.trackEditorProperty; var currentAnimationNameString = this.parentComponent.parentComponent.parentComponent.animationNamesString; var newAnimationNames = currentAnimationNameString + "," + this.animationName; var currentAnimationDuration = this.ninjaStylesContoller.getElementStyle(this.animatedElement, "-webkit-animation-duration"); var newAnimationDuration = currentAnimationDuration + "," + currentAnimationDuration; var currentIterationCount = this.ninjaStylesContoller.getElementStyle(this.animatedElement, "-webkit-animation-iteration-count"); var newIterationCount = currentIterationCount + ",1"; this.parentComponent.parentComponent.parentComponent.animationNamesString = newAnimationNames; this.ninjaStylesContoller.setElementStyle(this.animatedElement, "-webkit-animation-name", newAnimationNames); this.ninjaStylesContoller.setElementStyle(this.animatedElement, "-webkit-animation-duration", newAnimationDuration); this.ninjaStylesContoller.setElementStyle(this.animatedElement, "-webkit-animation-iteration-count", newIterationCount); var initRule = "@-webkit-keyframes " + this.animationName + " { 0% {" + this.trackEditorProperty + ": " + currentStyleValue + ";} 100% {" + this.trackEditorProperty + ": " + currentStyleValue + ";} }"; console.log(initRule); this.currentKeyframeRule = this.ninjaStylesContoller.addRule(initRule); this.insertPropTween(tweenEvent.offsetX); } } });