Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion src/Models/Commit.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,11 @@ public class Commit : ObservableObject
public List<Decorator> Decorators { get; set; } = new();

public bool IsMerged { get; set; } = false;
public int Color { get; set; } = 0;
public int Color
{
get => _color;
set => SetProperty(ref _color, value);
}
public double LeftMargin { get; set; } = 0;

public bool IsHighlightedInGraph
Expand Down Expand Up @@ -125,6 +129,7 @@ public void ParseDecorators(string data)
});
}

private int _color = 0;
private bool _isHighlightedInGraph = false;
}

Expand Down
91 changes: 78 additions & 13 deletions src/Models/CommitGraph.cs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ public static void SetPens(List<Color> colors, double thickness)
Pens.Add(new Pen(c.ToUInt32(), thickness));

s_penCount = colors.Count;
for (var i = 1; i < Bookmarks.Brushes.Length; i++)
Pens.Add(new Pen(Bookmarks.Brushes[i], thickness));
}

public class Path(int color, bool isHighlighted)
Expand Down Expand Up @@ -71,7 +73,7 @@ public class Dot
public List<Link> Links { get; } = [];
public List<Dot> Dots { get; } = [];

public static CommitGraph Generate(List<Commit> commits, bool firstParentOnlyEnabled, CommitGraphHighlighting highlighting, HashSet<string> highlightExtraCommits)
public static CommitGraph Generate(List<Commit> commits, bool firstParentOnlyEnabled, CommitGraphHighlighting highlighting, HashSet<string> highlightExtraCommits, int bookmark = 0)
{
const double unitWidth = 12;
const double halfWidth = 6;
Expand All @@ -82,12 +84,14 @@ public static CommitGraph Generate(List<Commit> commits, bool firstParentOnlyEna
var unsolved = new List<PathHelper>();
var ended = new List<PathHelper>();
var offsetY = -halfHeight;
var colorPicker = new ColorPicker();
var headColor = commits.Exists(x => x.IsCurrentHead) ? GetBookmarkColor(bookmark) : -1;
var colorPicker = new ColorPicker(headColor);
var defHighlighting = highlighting == CommitGraphHighlighting.All;

foreach (var commit in commits)
{
PathHelper major = null;
var isBookmarkedHead = commit.IsCurrentHead && headColor >= 0;

// Update current y offset
offsetY += unitHeight;
Expand Down Expand Up @@ -184,7 +188,8 @@ public static CommitGraph Generate(List<Commit> commits, bool firstParentOnlyEna

if (commit.Parents.Count > 0)
{
major = new PathHelper(commit.Parents[0], isHighlighted, colorPicker.Next(), new Point(offsetX, offsetY));
var color = isBookmarkedHead ? headColor : colorPicker.Next();
major = new PathHelper(commit.Parents[0], isHighlighted, color, new Point(offsetX, offsetY));
unsolved.Add(major);
temp.Paths.Add(major.Path);
}
Expand All @@ -195,9 +200,37 @@ public static CommitGraph Generate(List<Commit> commits, bool firstParentOnlyEna
temp.Paths.Add(major.Path);
}

var hasBranch = commit.Decorators.Exists(x => x.Type is DecoratorType.CurrentBranchHead or DecoratorType.LocalBranchHead or DecoratorType.RemoteBranchHead);
if (major != null && hasBranch)
{
if (major.HasBranch && !isBookmarkedHead)
{
// A named branch can start on an existing line without a fork.
// Split its color at the branch tip, preserving the lane and highlighting.
var oldColor = major.Path.Color;
var color = colorPicker.Next(oldColor);
if (color != oldColor)
{
major.ChangeColor(color);
if (commit.Parents.Count > 0)
temp.Paths.Add(major.Path);
}
colorPicker.Recycle(commit.Parents.Count > 0 ? oldColor : color);
}
major.HasBranch = true;
}

if (isBookmarkedHead && major != null && major.Path.Color != headColor)
{
colorPicker.Recycle(major.Path.Color);
major.ChangeColor(headColor);
if (commit.Parents.Count > 0)
temp.Paths.Add(major.Path);
}

// Calculate link position of this commit.
var position = new Point(major?.LastX ?? offsetX, offsetY);
var dotColor = major?.Path.Color ?? 0;
var dotColor = major?.Path.Color ?? (isBookmarkedHead ? headColor : 0);
var anchor = new Dot() { Center = position, Color = dotColor, IsHighlighted = isHighlighted };
if (commit.IsCurrentHead)
anchor.Type = DotType.Head;
Expand Down Expand Up @@ -268,22 +301,44 @@ public static CommitGraph Generate(List<Commit> commits, bool firstParentOnlyEna
return temp;
}

private class ColorPicker
private static int GetBookmarkColor(int bookmark)
{
public int Next()
if (Bookmarks.Get(bookmark) is not ISolidColorBrush brush)
return -1;

for (var i = 0; i < s_penCount; i++)
{
if (_colorsQueue.Count == 0)
if (Pens[i].Brush is ISolidColorBrush graphBrush && graphBrush.Color == brush.Color)
return i;
}

return s_penCount + bookmark - 1;
}

private class ColorPicker(int reservedColor)
{
public int Next(int exclude = -1)
{
var reservedCount = s_penCount > 1 && reservedColor >= 0 && reservedColor < s_penCount ? 1 : 0;
int color;
do
{
for (var i = 0; i < s_penCount; i++)
_colorsQueue.Enqueue(i);
if (_colorsQueue.Count == 0)
{
for (var i = 0; i < s_penCount; i++)
_colorsQueue.Enqueue(i);
}

color = _colorsQueue.Dequeue();
}
while ((reservedCount > 0 && color == reservedColor) || (s_penCount - reservedCount > 1 && color == exclude));

return _colorsQueue.Dequeue();
return color;
}

public void Recycle(int idx)
{
if (!_colorsQueue.Contains(idx))
if (idx < s_penCount && idx != reservedColor && !_colorsQueue.Contains(idx))
_colorsQueue.Enqueue(idx);
}

Expand All @@ -292,6 +347,7 @@ public void Recycle(int idx)

private class PathHelper
{
public bool HasBranch { get; set; }
public Path Path { get; private set; }
public string Next { get; set; }
public double LastX { get; private set; }
Expand Down Expand Up @@ -398,10 +454,19 @@ public void End(double x, double y, double halfHeight)
/// </summary>
public void Highlight()
{
var color = Path.Color;
Restart(Path.Color, true);
}

public void ChangeColor(int color)
{
Restart(color, Path.IsHighlighted);
}

private void Restart(int color, bool isHighlighted)
{
Add(LastX, _lastY);

Path = new Path(color, true);
Path = new Path(color, isHighlighted);
Path.Points.Add(new Point(LastX, _lastY));
_endY = 0;
}
Expand Down
7 changes: 6 additions & 1 deletion src/ViewModels/Histories.cs
Original file line number Diff line number Diff line change
Expand Up @@ -567,6 +567,11 @@ private void PostSelectedCommitsChanged()
GenerateGraph(_commits);
}

public void RefreshGraph()
{
GenerateGraph(_commits);
}

private void GenerateGraph(List<Models.Commit> commits)
{
var firstParentOnly = _repo.UIStates.HistoryShowFlags.HasFlag(Models.HistoryShowFlags.FirstParentOnly);
Expand All @@ -579,7 +584,7 @@ private void GenerateGraph(List<Models.Commit> commits)
extraHeads.Add(c.SHA);
}

Graph = Models.CommitGraph.Generate(commits, firstParentOnly, highlighting, extraHeads);
Graph = Models.CommitGraph.Generate(commits, firstParentOnly, highlighting, extraHeads, _repo.Bookmark);
}

private Repository _repo = null;
Expand Down
2 changes: 2 additions & 0 deletions src/ViewModels/Repository.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ public string GitDir
get;
}

public int Bookmark => GetOwnerPage()?.Node.Bookmark ?? Preferences.Instance.FindNode(FullPath)?.Bookmark ?? 0;

public Models.RepositorySettings Settings
{
get => _settings;
Expand Down
12 changes: 11 additions & 1 deletion src/ViewModels/RepositoryNode.cs
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,17 @@ public string Name
public int Bookmark
{
get => _bookmark;
set => SetProperty(ref _bookmark, value);
set
{
if (!SetProperty(ref _bookmark, value) || App.GetLauncher() is not { } launcher)
return;

foreach (var page in launcher.Pages)
{
if (ReferenceEquals(page.Node, this) && page.Data is Repository repo)
repo.Histories?.RefreshGraph();
}
}
}

public bool IsRepository
Expand Down
12 changes: 11 additions & 1 deletion src/Views/CommitRefsPresenter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,15 @@ public bool UseGraphColor
set => SetValue(UseGraphColorProperty, value);
}

public static readonly StyledProperty<int> GraphColorProperty =
AvaloniaProperty.Register<CommitRefsPresenter, int>(nameof(GraphColor), -1);

public int GraphColor
{
get => GetValue(GraphColorProperty);
set => SetValue(GraphColorProperty, value);
}

public static readonly StyledProperty<bool> AllowWrapProperty =
AvaloniaProperty.Register<CommitRefsPresenter, bool>(nameof(AllowWrap));

Expand Down Expand Up @@ -260,6 +269,7 @@ protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs chang
change.Property == FontSizeProperty ||
change.Property == ForegroundProperty ||
change.Property == UseGraphColorProperty ||
change.Property == GraphColorProperty ||
change.Property == UseCompactBranchNamesProperty ||
change.Property == HasSingleRemoteProperty ||
change.Property == BackgroundProperty ||
Expand Down Expand Up @@ -294,7 +304,7 @@ protected override Size MeasureOverride(Size availableSize)
var typefaceHead = new Typeface(FontFamily, FontStyle.Normal, FontWeight.Bold);
var typefaceRemote = new Typeface(FontFamily, FontStyle.Italic, FontWeight.Bold);
var fg = Foreground;
var normalBG = UseGraphColor ? Models.CommitGraph.Pens[commit.Color].Brush : Brushes.Gray;
var normalBG = UseGraphColor ? Models.CommitGraph.Pens[GraphColor >= 0 ? GraphColor : commit.Color].Brush : Brushes.Gray;
var labelSize = FontSize;
var requiredHeight = 16.0;
var x = 0.0;
Expand Down
1 change: 1 addition & 0 deletions src/Views/Histories.axaml
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,7 @@
FontSize="12"
VerticalAlignment="Center"
UseGraphColor="{Binding IsHighlightedInGraph, Mode=OneWay}"
GraphColor="{Binding Color, Mode=OneWay}"
UseCompactBranchNames="{Binding Source={x:Static vm:Preferences.Instance}, Path=UseCompactBranchNamesInGraph}"
HasSingleRemote="{Binding $parent[v:Histories].HasSingleRemote}"/>

Expand Down
10 changes: 10 additions & 0 deletions tests/BookmarkBranchColorChecks/BookmarkBranchColorChecks.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="../../src/SourceGit.csproj" />
</ItemGroup>
</Project>
Loading