1 // ==========================================================================
  2 // NodeGraph.animator
  3 //
  4 // License:  PaperCube is open source software released under 
  5 //           the MIT License (see license.js)
  6 // ==========================================================================
  7 
  8 require('core');
  9 
 10 /** @class NodeGraph.animator
 11 
 12   This is the NodeGraph animation engine. This runs every 40 milliseconds.
 13 
 14   @extends SC.Object
 15   @author Peter Bergstrom
 16   @version 1.0
 17   @copyright 2008-2009 Peter Bergström.
 18   @static
 19 */
 20 NodeGraph.animator = SC.Object.create(
 21 /** @scope NodeGraph.animator.prototype */ {
 22   
 23   /**
 24     The queue of animation subjects.
 25     
 26     @property {Array}
 27   */
 28   _queue: [],
 29   
 30   /**
 31     A reference to a view's main SVG element. Used to "snap" the element to prevent rendering/display bugs.
 32     
 33     @property {DOM Element}
 34   */
 35   _svgMain: null,
 36 
 37   /**
 38     The height of the svg element passed in.
 39     
 40     @property {Integer}
 41   */
 42   _svgMainHeight: 0,
 43   
 44   /**
 45     Add an array of subjects to be animated. 
 46     
 47     Removes all the previous elements and starts the timer if is has been stopped. 
 48   
 49     @param arr {Array} The array of animation subjects.
 50     @param {DOM Element} svgMain  A reference to a view's main SVG element. Used to "snap" the element to prevent rendering/display bugs.
 51     @param svgMainHeight {Integer} The height of the svg element passed in.
 52   */
 53   addSubjects: function(arr, svgMain, svgMainHeight)
 54   {
 55     // Save the svg element and height if needed.
 56     this._svgMain = svgMain;
 57     this._svgMainHeight = svgMainHeight;
 58     
 59     // Add the subjects.
 60     var arrLen = arr.length;
 61     for(var i=0; i<arrLen; i++)
 62     {
 63       var subject = arr[i];
 64       this.addSubject(subject.elm, subject.props, subject.duration, subject.isSVG);
 65     }
 66   },
 67   
 68   /**
 69     Add a subject to the queue.
 70   
 71     @param {DOM Element} elm The subject to be animated.
 72     @param props {Object}  The properties that are to be animated.
 73     @param duration {Integer} The duration in milliseconds of the element.
 74     @param isSVG {boolean} Determines if it is an SVG element or not.
 75   */
 76   addSubject: function(elm, props, duration, isSVG)
 77   {
 78     
 79     // Fix the properties so that they are whole numbers.
 80     var fixed = {};
 81     for(var key in props)
 82     {
 83       if(key.indexOf('opacity') == -1)
 84       {
 85          fixed[key] = {start: Math.floor(props[key].start), end: Math.floor(props[key].end)};
 86       } 
 87       else
 88       {
 89         fixed[key] = {start: props[key].start, end: props[key].end};
 90       }
 91     }
 92     
 93     // Start time.
 94     var start = Date.now();
 95     
 96     
 97     var subject = {isCompleted: NO};
 98     
 99     // Animation function. Called once during a heart beat.
100     subject.animate = function()
101     {
102 
103       // Calculate the percentage completed.
104       var pct = Math.min(100,parseInt((100*(Date.now()-start)/duration),0));
105       var done = NO;
106       if(pct >= 100)
107       {
108         done = YES;
109         this.isCompleted = YES;
110       }
111       
112       // Loop through the properties and then based on the percentage, set the props.
113       for(var propertyName in props)
114       {
115         var s = fixed[propertyName].start;
116         var e = fixed[propertyName].end;
117         var attrVal = (done) ? e : Math.floor(s + (e - s)*pct/100);
118         
119         if(isSVG)
120         {
121           elm.setAttributeNS(null, propertyName, attrVal);
122         }
123         else
124         {
125           if(propertyName == 'opacity')
126           {
127             elm.style.opacity = attrVal;
128           }
129           else
130           {
131             elm.style[propertyName] = attrVal + "px";
132           }
133         }
134       }
135     };
136 
137     // Push the subject on the queue.
138     this._queue.push(subject);
139     
140   },
141   
142   /**
143     Runs the animation one frame, this is called by the SC Timer object.
144     
145     @returns {Boolean} Returns NO if the queue is null.
146   */
147   runAnimation: function()
148   {
149     var q = this._queue;
150     if(!q) return NO;
151     var qLen = q.length;
152     if(qLen == 0) return NO;
153     
154     var newQ = [];
155     var delQ = [];
156     
157     // Iterate through each element and if it is completed, add it to be deleted.
158     for(var i=0; i<qLen; i++)
159     {
160       q[i].animate();
161       
162       if(!q[i].isCompleted)
163       {
164         newQ.push(q[i]);
165       }
166       else
167       {
168         delQ.push(q[i]);
169       }
170     }
171     
172     // Delete what needs to be deleted.
173     qLen = delQ.length; 
174     for(var i=0; i<qLen; i++)
175     {
176       delete delQ[i];
177     }
178     
179     // Snap the SVG element.
180     if(this._svgMain)
181     {
182       this._svgMain.setAttributeNS(null, 'height', this._svgMainHeight+1);
183       this._svgMain.setAttributeNS(null, 'height', this._svgMainHeight);
184     }
185     
186     // Set the new queue.
187     this._queue =  newQ;
188   },
189   
190   init: function() {
191     sc_super();
192     
193     setInterval('NodeGraph.animator.runAnimation()', 50);
194     
195   }
196 });
197 
198 
199