using System; using System.Collections.Generic; using Avalonia; namespace SourceGit.Models { public class CommitGraph { public class Path { public List Points = new List(); public int Color = 0; } public class PathHelper { public string Next; public bool IsMerged; public double LastX; public double LastY; public double EndY; public Path Path; public PathHelper(string next, bool isMerged, int color, Point start) { Next = next; IsMerged = isMerged; LastX = start.X; LastY = start.Y; EndY = LastY; Path = new Path(); Path.Color = color; Path.Points.Add(start); } public PathHelper(string next, bool isMerged, int color, Point start, Point to) { Next = next; IsMerged = isMerged; LastX = to.X; LastY = to.Y; EndY = LastY; Path = new Path(); Path.Color = color; Path.Points.Add(start); Path.Points.Add(to); } public void Add(double x, double y, double halfHeight, bool isEnd = false) { if (x > LastX) { Add(new Point(LastX, LastY)); Add(new Point(x, y - halfHeight)); if (isEnd) Add(new Point(x, y)); } else if (x < LastX) { if (y > LastY + halfHeight) Add(new Point(LastX, LastY + halfHeight)); Add(new Point(x, y)); } else if (isEnd) { Add(new Point(x, y)); } LastX = x; LastY = y; } private void Add(Point p) { if (EndY < p.Y) { Path.Points.Add(p); EndY = p.Y; } } } public class Link { public Point Start; public Point Control; public Point End; public int Color; } public class Dot { public Point Center; public int Color; } public List Paths { get; set; } = new List(); public List Links { get; set; } = new List(); public List Dots { get; set; } = new List(); public static CommitGraph Parse(List commits, double rowHeight, int colorCount) { double UNIT_WIDTH = 12; double HALF_WIDTH = 6; double UNIT_HEIGHT = rowHeight; double HALF_HEIGHT = rowHeight / 2; var temp = new CommitGraph(); var unsolved = new List(); var mapUnsolved = new Dictionary(); var ended = new List(); var offsetY = -HALF_HEIGHT; var colorIdx = 0; foreach (var commit in commits) { var major = null as PathHelper; var isMerged = commit.IsMerged; var oldCount = unsolved.Count; // Update current y offset offsetY += UNIT_HEIGHT; // Find first curves that links to this commit and marks others that links to this commit ended. double offsetX = -HALF_WIDTH; foreach (var l in unsolved) { if (l.Next == commit.SHA) { if (major == null) { offsetX += UNIT_WIDTH; major = l; if (commit.Parents.Count > 0) { major.Next = commit.Parents[0]; if (!mapUnsolved.ContainsKey(major.Next)) mapUnsolved.Add(major.Next, major); } else { major.Next = "ENDED"; ended.Add(l); } major.Add(offsetX, offsetY, HALF_HEIGHT); } else { ended.Add(l); } isMerged = isMerged || l.IsMerged; } else { if (!mapUnsolved.ContainsKey(l.Next)) mapUnsolved.Add(l.Next, l); offsetX += UNIT_WIDTH; l.Add(offsetX, offsetY, HALF_HEIGHT); } } // Create new curve for branch head if (major == null && commit.Parents.Count > 0) { offsetX += UNIT_WIDTH; major = new PathHelper(commit.Parents[0], isMerged, colorIdx, new Point(offsetX, offsetY)); unsolved.Add(major); temp.Paths.Add(major.Path); colorIdx = (colorIdx + 1) % colorCount; } // Calculate link position of this commit. Point position = new Point(offsetX, offsetY); if (major != null) { major.IsMerged = isMerged; position = new Point(major.LastX, offsetY); temp.Dots.Add(new Dot() { Center = position, Color = major.Path.Color }); } else { temp.Dots.Add(new Dot() { Center = position, Color = 0 }); } // Deal with parents for (int j = 1; j < commit.Parents.Count; j++) { var parent = commit.Parents[j]; if (mapUnsolved.TryGetValue(parent, out var value)) { var l = value; var link = new Link(); link.Start = position; link.End = new Point(l.LastX, offsetY + HALF_HEIGHT); link.Control = new Point(link.End.X, link.Start.Y); link.Color = l.Path.Color; temp.Links.Add(link); } else { offsetX += UNIT_WIDTH; // Create new curve for parent commit that not includes before var l = new PathHelper(commit.Parents[j], isMerged, colorIdx, position, new Point(offsetX, position.Y + HALF_HEIGHT)); unsolved.Add(l); temp.Paths.Add(l.Path); colorIdx = (colorIdx + 1) % colorCount; } } // Remove ended curves from unsolved foreach (var l in ended) { l.Add(position.X, position.Y, HALF_HEIGHT, true); unsolved.Remove(l); } // Margins & merge state (used by datagrid). commit.IsMerged = isMerged; commit.Margin = new Thickness(Math.Max(offsetX + HALF_WIDTH, oldCount * UNIT_WIDTH), 0, 0, 0); // Clean up ended.Clear(); mapUnsolved.Clear(); } // Deal with curves haven't ended yet. for (int i = 0; i < unsolved.Count; i++) { var path = unsolved[i]; var endY = (commits.Count - 0.5) * UNIT_HEIGHT; if (path.Path.Points.Count == 1 && path.Path.Points[0].Y == endY) continue; path.Add((i + 0.5) * UNIT_WIDTH, endY + HALF_HEIGHT, HALF_HEIGHT, true); } unsolved.Clear(); return temp; } } }