diff --git a/src/animationFactory.js b/src/animationFactory.js index 0fc5fc10..e39ba6c3 100644 --- a/src/animationFactory.js +++ b/src/animationFactory.js @@ -1,13 +1,41 @@ /****************** * This class is responsible for a lot of the heavy lifting around creating an animation at a certain state in time. - * The tricky thing is that when a new commit has to be "born," say in the middle of a rebase or something, it must animate - * out from the parent position to it's birth position. + * The tricky thing is that when a new commit has to be "born," say in the middle of a rebase + * or something, it must animate out from the parent position to it's birth position. * These two positions though may not be where the commit finally ends up. So we actually need to take a snapshot of the tree, * store all those positions, take a snapshot of the tree after a layout refresh afterwards, and then animate between those two spots. * and then essentially animate the entire tree too. - - * not sure if this is necessary yet, so ill hold off for now. lets do some refs - */ +// essentially a static class +function AnimationFactory() { + +} + +AnimationFactory.prototype.genCommitBirthAnimation = function(animationQueue, commit) { + if (!animationQueue) { + throw new Error("Need animation queue to add closure to!"); + } + + var time = GRAPHICS.defaultAnimationTime * 1.0; + + // essentially refresh the entire tree, but do a special thing for the commit + var visNode = commit.get('visNode'); + + var animation = function() { + // this takes care of refs and all that jazz, and updates all the positions + gitVisuals.refreshTree(time); + + visNode.setBirthPosition(); + visNode.setOutgoingEdgesBirthPosition(); + visNode.parentInFront(); + + visNode.animateUpdatedPosition(time); + visNode.animateOutgoingEdges(time); + }; + + animationQueue.add(new Animation({ + closure: animation + })); +}; diff --git a/src/git.js b/src/git.js index 09eca65e..10859879 100644 --- a/src/git.js +++ b/src/git.js @@ -230,7 +230,9 @@ GitEngine.prototype.commitStarter = function() { if (this.commandOptions['-am']) { this.command.addWarning("Don't worry about adding files or commit messages in this demo"); } - this.commit(); + + var newCommit = this.commit(); + animationFactory.genCommitBirthAnimation(this.animationQueue, newCommit); }; GitEngine.prototype.commit = function() { @@ -248,6 +250,7 @@ GitEngine.prototype.commit = function() { var targetBranch = this.HEAD.get('target'); targetBranch.set('target', newCommit); } + return newCommit; }; GitEngine.prototype.resolveId = function(idOrTarget) { @@ -750,22 +753,23 @@ GitEngine.prototype.dispatch = function(command, callback) { } catch (err) { if (err instanceof GitError || err instanceof CommandResult) { - // short circuit animation by just setting error and returning command.set('error', err); callback(); return; - } else { throw err; } } - this.animationQueue.add(new Animation({ - closure: function() { - gitVisuals.refreshTree(); - } - })); + // only add the refresh if we didn't do manual animations + if (!this.animationQueue.get('animations').length) { + this.animationQueue.add(new Animation({ + closure: function() { + gitVisuals.refreshTree(); + } + })); + } // animation queue will call the callback when its done this.animationQueue.start(); diff --git a/src/main.js b/src/main.js index 007dfa90..243f3711 100644 --- a/src/main.js +++ b/src/main.js @@ -8,10 +8,13 @@ var gitVisuals = null; var commandCollection = null; var commandBuffer = null; +var animationFactory = null; var paper = null; $(document).ready(function(){ + animationFactory = new AnimationFactory(); + // the two major collections that affect everything var commitCollection = new CommitCollection(); commandCollection = new CommandCollection(); diff --git a/src/tree.js b/src/tree.js index cf1d0290..1e08da9f 100644 --- a/src/tree.js +++ b/src/tree.js @@ -320,6 +320,7 @@ var VisNode = Backbone.Model.extend({ defaults: { depth: undefined, maxWidth: null, + outgoingEdges: null, id: null, pos: null, radius: null, @@ -346,6 +347,8 @@ var VisNode = Backbone.Model.extend({ initialize: function() { this.validateAtInit(); + + this.set('outgoingEdges', []); }, setDepthBasedOn: function(depthIncrement) { @@ -394,7 +397,8 @@ var VisNode = Backbone.Model.extend({ this.get('circle').stop().animate({ cx: pos.x, cy: pos.y, - opacity: opacity + opacity: opacity, + r: this.getRadius() }, speed !== undefined ? speed : this.get('animationSpeed'), easing || this.get('animationEasing') @@ -410,6 +414,45 @@ var VisNode = Backbone.Model.extend({ return this.get('radius') || GRAPHICS.nodeRadius; }, + getParentScreenCoords: function() { + return this.get('commit').get('parents')[0].get('visNode').getScreenCoords(); + }, + + setBirthPosition: function() { + // utility method for animating it out from underneath a parent + var parentCoords = this.getParentScreenCoords(); + + this.get('circle').attr({ + cx: parentCoords.x, + cy: parentCoords.y, + opacity: 0, + r: 0, + }); + }, + + animateOutgoingEdges: function(speed, easing) { + _.each(this.get('outgoingEdges'), function(edge) { + edge.animateUpdatedPath(speed, easing); + }, this); + }, + + setOutgoingEdgesBirthPosition: function() { + var parentCoords = this.getParentScreenCoords(); + _.each(this.get('outgoingEdges'), function(edge) { + var headPos = edge.get('head').getScreenCoords(); + var path = edge.genSmoothBezierPathStringFromCoords(parentCoords, headPos); + edge.get('path').stop().attr({ + path: path, + opacity: 0 + }); + }, this); + }, + + parentInFront: function() { + // woof! + this.get('commit').get('parents')[0].get('visNode').get('circle').toFront(); + }, + genGraphics: function(paper) { var pos = this.getScreenCoords(); var circle = cuteSmallCircle(paper, pos.x, pos.y, { @@ -438,11 +481,17 @@ var VisEdge = Backbone.Model.extend({ initialize: function() { this.validateAtInit(); + + this.get('tail').get('outgoingEdges').push(this); }, genSmoothBezierPathString: function(tail, head) { var tailPos = tail.getScreenCoords(); var headPos = head.getScreenCoords(); + return this.genSmoothBezierPathStringFromCoords(tailPos, headPos); + }, + + genSmoothBezierPathStringFromCoords: function(tailPos, headPos) { // we need to generate the path and control points for the bezier. format // is M(move abs) C (curve to) (control point 1) (control point 2) (final point) // the control points have to be __below__ to get the curve starting off straight. @@ -465,8 +514,8 @@ var VisEdge = Backbone.Model.extend({ }; // first offset tail and head by radii - tailPos = offset(tailPos, -1, tail.getRadius()); - headPos = offset(headPos, 1, head.getRadius()); + tailPos = offset(tailPos, -1, this.get('tail').getRadius()); + headPos = offset(headPos, 1, this.get('head').getRadius()); var str = ''; // first move to bottom of tail @@ -514,6 +563,10 @@ var VisEdge = Backbone.Model.extend({ animateUpdatedPath: function(speed, easing) { var newPath = this.getBezierCurve(); + this.animateUpdatedPathFromPath(newPath, speed, easing); + }, + + animateUpdatedPathFromPath: function(newPath, speed, easing) { var opacity = this.getOpacity(); this.get('path').toBack(); diff --git a/src/visuals.js b/src/visuals.js index 7107de8a..5a7d0923 100644 --- a/src/visuals.js +++ b/src/visuals.js @@ -81,12 +81,12 @@ GitVisuals.prototype.toScreenCoords = function(pos) { **************************************/ -GitVisuals.prototype.refreshTree = function() { +GitVisuals.prototype.refreshTree = function(speed) { // this method can only be called after graphics are rendered this.calcTreeCoords(); this.calcGraphicsCoords(); - this.animateAll(); + this.animateAll(speed); }; GitVisuals.prototype.refreshTreeHarsh = function() { diff --git a/todo.txt b/todo.txt index e362614a..4866b859 100644 --- a/todo.txt +++ b/todo.txt @@ -6,13 +6,11 @@ integrate animation into all git commands animation factory? stuff like: --commitBirthAnimation(snapshotBefore, snapshotAfter, commit) -highlightCommit(50, 'targetColor') // during search -clearHighlightsAllNodes ALSO other big things: - - Text on commit nodes - Division in their rings based on how many / what branches they are part of - Color on branch edges??