private void Plotter_CommitsChanged(object sender, CommitsChangedEventArgs e) { const int moveAnimationDurationInMilliseconds = 2500; const int fadeInDurationInMilliseconds = 3000; Console.WriteLine("Added " + e.AddedCommits.Length + ", removed " + e.RemovedCommits.Length + ", moved " + e.MovedCommits.Length); foreach (var commit in e.AddedCommits) { spritesByCommitHash.Add(commit.Hash, new RevisionSprite(commit)); Point destinationLocation = CellNumberToAbsoluteLocation(Plotter.GetCellNumber(commit.Hash)); // When we first start up, we don't want to fade in everything. But when commits get added, we want to animate that. if (e.WasTriggeredByInitialLoad) { spritesByCommitHash[commit.Hash].X.PopTo(destinationLocation.X); spritesByCommitHash[commit.Hash].Y.PopTo(destinationLocation.Y); spritesByCommitHash[commit.Hash].Opacity.PopTo(1); } else { // If this commit is being copied from another commit (i.e. if the hash is identical to another commit), animate moving it from the old commit to the new one. List <string> duplicateDiffCommitHashes = this.GetCommitsWithIdenticalDiff(commit.Hash); if (duplicateDiffCommitHashes.Count > 0) { // Arbitrarily pick one of the commits to say it's a duplicate of if there's more than one. var duplicatedRevision = duplicateDiffCommitHashes[duplicateDiffCommitHashes.Count - 1]; Point sourceLocation = CellNumberToAbsoluteLocation(Plotter.GetCellNumber(duplicatedRevision)); // The source cell might be moving (done down below), so we need the start location, not the end location. foreach (var movedCommit in e.MovedCommits) { if (movedCommit.Revision.Hash == duplicatedRevision) { sourceLocation = CellNumberToAbsoluteLocation(movedCommit.StartCellCoordinates); break; } } spritesByCommitHash[commit.Hash].X.Animate(sourceLocation.X, destinationLocation.X, DateTime.Now, DateTime.Now.AddMilliseconds(moveAnimationDurationInMilliseconds)); spritesByCommitHash[commit.Hash].Y.Animate(sourceLocation.Y, destinationLocation.Y, DateTime.Now, DateTime.Now.AddMilliseconds(moveAnimationDurationInMilliseconds)); spritesByCommitHash[commit.Hash].Opacity.PopTo(1); } else { spritesByCommitHash[commit.Hash].X.PopTo(destinationLocation.X); spritesByCommitHash[commit.Hash].Y.PopTo(destinationLocation.Y); spritesByCommitHash[commit.Hash].Opacity.Animate(0, 1, DateTime.Now, DateTime.Now.AddMilliseconds(fadeInDurationInMilliseconds)); } } } foreach (var commit in e.MovedCommits) { Point sourceLocation = CellNumberToAbsoluteLocation(commit.StartCellCoordinates); Point destinationLocation = CellNumberToAbsoluteLocation(commit.EndCellCoordinates); spritesByCommitHash[commit.Revision.Hash].X.Animate(sourceLocation.X, destinationLocation.X, DateTime.Now, DateTime.Now.AddMilliseconds(moveAnimationDurationInMilliseconds)); spritesByCommitHash[commit.Revision.Hash].Y.Animate(sourceLocation.Y, destinationLocation.Y, DateTime.Now, DateTime.Now.AddMilliseconds(moveAnimationDurationInMilliseconds)); } foreach (var commit in e.RemovedCommits) { spritesByCommitHash.Remove(commit.Hash); } StartAnimationTimer(3); Invalidate(); }