diff --git a/GitCommands/Settings/AppSettings.cs b/GitCommands/Settings/AppSettings.cs
index 46213481b51..c911784bd68 100644
--- a/GitCommands/Settings/AppSettings.cs
+++ b/GitCommands/Settings/AppSettings.cs
@@ -1690,6 +1690,12 @@ public static bool BlameShowOriginalFilePath
set => SetBool("Blame.ShowOriginalFilePath", value);
}
+ public static bool BlameShowAuthorAvatar
+ {
+ get => GetBool("Blame.ShowAuthorAvatar", true);
+ set => SetBool("Blame.ShowAuthorAvatar", value);
+ }
+
public static bool AutomaticContinuousScroll
{
get => GetBool("DiffViewer.AutomaticContinuousScroll", false);
diff --git a/GitUI/Avatars/AvatarService.cs b/GitUI/Avatars/AvatarService.cs
index 31c02fcd834..895208c22a6 100644
--- a/GitUI/Avatars/AvatarService.cs
+++ b/GitUI/Avatars/AvatarService.cs
@@ -4,7 +4,7 @@ namespace GitUI.Avatars
{
public static class AvatarService
{
- private static InitialsAvatarGenerator AvatarGenerator = new InitialsAvatarGenerator();
+ private static readonly InitialsAvatarGenerator AvatarGenerator = new InitialsAvatarGenerator();
public static IAvatarProvider Default { get; }
= new BackupAvatarProvider(new AvatarMemoryCache(new AvatarPersistentCache(new AvatarDownloader(AvatarGenerator), AvatarGenerator)),
Images.User80);
diff --git a/GitUI/CommandsDialogs/FormFileHistory.Designer.cs b/GitUI/CommandsDialogs/FormFileHistory.Designer.cs
index 73a7c031db7..a89d488d641 100644
--- a/GitUI/CommandsDialogs/FormFileHistory.Designer.cs
+++ b/GitUI/CommandsDialogs/FormFileHistory.Designer.cs
@@ -67,6 +67,7 @@ private void InitializeComponent()
this.showAuthorTimeToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
this.showLineNumbersToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
this.showOriginalFilePathToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
+ this.showAuthorAvatarToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
((System.ComponentModel.ISupportInitialize)(this.splitContainer1)).BeginInit();
this.splitContainer1.Panel1.SuspendLayout();
this.splitContainer1.Panel2.SuspendLayout();
@@ -481,6 +482,7 @@ private void InitializeComponent()
this.toolStripSeparator5,
this.displaySettingsToolStripMenuItem,
this.displayAuthorFirstToolStripMenuItem,
+ this.showAuthorAvatarToolStripMenuItem,
this.showAuthorToolStripMenuItem,
this.showAuthorDateToolStripMenuItem,
this.showAuthorTimeToolStripMenuItem,
@@ -575,6 +577,13 @@ private void InitializeComponent()
this.showOriginalFilePathToolStripMenuItem.Text = "Show original file path";
this.showOriginalFilePathToolStripMenuItem.Click += new System.EventHandler(this.showOriginalFilePathToolStripMenuItem_Click);
//
+ // showAuthorAvatarToolStripMenuItem
+ //
+ this.showAuthorAvatarToolStripMenuItem.Name = "showAuthorAvatarToolStripMenuItem";
+ this.showAuthorAvatarToolStripMenuItem.Size = new System.Drawing.Size(247, 22);
+ this.showAuthorAvatarToolStripMenuItem.Text = "Show author avatar";
+ this.showAuthorAvatarToolStripMenuItem.Click += new System.EventHandler(this.showAuthorAvatarToolStripMenuItem_Click);
+ //
// FormFileHistory
//
this.AutoScaleDimensions = new System.Drawing.SizeF(96F, 96F);
@@ -661,5 +670,6 @@ private void InitializeComponent()
private System.Windows.Forms.ToolStripMenuItem displaySettingsToolStripMenuItem;
private System.Windows.Forms.ToolStripMenuItem showAuthorToolStripMenuItem;
private System.Windows.Forms.ToolStripMenuItem showOriginalFilePathToolStripMenuItem;
+ private System.Windows.Forms.ToolStripMenuItem showAuthorAvatarToolStripMenuItem;
}
-}
\ No newline at end of file
+}
diff --git a/GitUI/CommandsDialogs/FormFileHistory.cs b/GitUI/CommandsDialogs/FormFileHistory.cs
index fd6460fe4f5..9a69eec78f5 100644
--- a/GitUI/CommandsDialogs/FormFileHistory.cs
+++ b/GitUI/CommandsDialogs/FormFileHistory.cs
@@ -135,6 +135,7 @@ public FormFileHistory(GitUICommands commands, string fileName, GitRevision revi
detectMoveAndCopyInAllFilesToolStripMenuItem.Checked = AppSettings.DetectCopyInAllOnBlame;
detectMoveAndCopyInThisFileToolStripMenuItem.Checked = AppSettings.DetectCopyInFileOnBlame;
displayAuthorFirstToolStripMenuItem.Checked = AppSettings.BlameDisplayAuthorFirst;
+ showAuthorAvatarToolStripMenuItem.Checked = AppSettings.BlameShowAuthorAvatar;
showAuthorToolStripMenuItem.Checked = AppSettings.BlameShowAuthor;
showAuthorDateToolStripMenuItem.Checked = AppSettings.BlameShowAuthorDate;
showAuthorTimeToolStripMenuItem.Checked = AppSettings.BlameShowAuthorTime;
@@ -704,5 +705,12 @@ private void showOriginalFilePathToolStripMenuItem_Click(object sender, EventArg
showOriginalFilePathToolStripMenuItem.Checked = AppSettings.BlameShowOriginalFilePath;
UpdateSelectedFileViewers(true);
}
+
+ private void showAuthorAvatarToolStripMenuItem_Click(object sender, EventArgs e)
+ {
+ AppSettings.BlameShowAuthorAvatar = !AppSettings.BlameShowAuthorAvatar;
+ showAuthorAvatarToolStripMenuItem.Checked = AppSettings.BlameShowAuthorAvatar;
+ UpdateSelectedFileViewers(true);
+ }
}
}
diff --git a/GitUI/Editor/BlameAuthorMargin.cs b/GitUI/Editor/BlameAuthorMargin.cs
new file mode 100644
index 00000000000..5baa14392ec
--- /dev/null
+++ b/GitUI/Editor/BlameAuthorMargin.cs
@@ -0,0 +1,110 @@
+using System;
+using System.Collections.Generic;
+using System.Drawing;
+using System.Linq;
+using System.Windows.Forms;
+using GitExtUtils.GitUI;
+using ICSharpCode.TextEditor;
+
+namespace GitUI.Editor
+{
+ ///
+ /// This class display avatars in the gutter in a blame control.
+ ///
+ public class BlameAuthorMargin : AbstractMargin
+ {
+ private static readonly int AgeBucketMarkerWidth = Convert.ToInt32(4 * DpiUtil.ScaleX);
+ private List _avatars;
+ private readonly int _lineHeight;
+ private readonly Color _backgroundColor;
+ private List _blameLines;
+ private readonly Dictionary _brushs = new Dictionary();
+ private bool _isVisible = true;
+
+ public BlameAuthorMargin(TextArea textArea) : base(textArea)
+ {
+ _lineHeight = GetFontHeight(textArea.Font);
+ _backgroundColor = SystemColors.Window;
+ Width = _lineHeight + AgeBucketMarkerWidth + DpiUtil.Scale(2);
+ }
+
+ public override int Width { get; }
+ public override bool IsVisible => _isVisible;
+
+ private static int GetFontHeight(Font font)
+ {
+ var max = Math.Max(
+ TextRenderer.MeasureText("_", font).Height,
+ (int)Math.Ceiling(font.GetHeight()));
+
+ return max + 1;
+ }
+
+ public void Initialize(IEnumerable blameLines)
+ {
+ _blameLines = blameLines.ToList();
+ _avatars = _blameLines.Select(a => a.Avatar).ToList();
+
+ // Update the resolution otherwise the image is not drawn at the good size :(
+ foreach (var avatar in _avatars)
+ {
+ if (avatar is Bitmap bitmapAvatar)
+ {
+ bitmapAvatar.SetResolution(DpiUtil.DpiX, DpiUtil.DpiY);
+ }
+ }
+
+ // Build brushes
+ foreach (var blameLine in _blameLines)
+ {
+ if (!_brushs.ContainsKey(blameLine.AgeBucketIndex))
+ {
+ _brushs.Add(blameLine.AgeBucketIndex, new SolidBrush(blameLine.AgeBucketColor));
+ }
+ }
+ }
+
+ public void SetVisiblity(bool isVisible)
+ {
+ _isVisible = isVisible;
+ }
+
+ public override void Paint(Graphics g, Rectangle rect)
+ {
+ if (rect.Width <= 0 || rect.Height <= 0 || _blameLines == null || _blameLines.Count == 0)
+ {
+ return;
+ }
+
+ g.Clear(_backgroundColor);
+
+ if (_avatars == null || _avatars.Count == 0)
+ {
+ return;
+ }
+
+ var verticalOffset = textArea.VirtualTop.Y;
+ var lineStart = verticalOffset / _lineHeight;
+ var negativeOffset = (lineStart * _lineHeight) - verticalOffset;
+ var lineCount = (int)Math.Ceiling((double)(rect.Height - negativeOffset) / _lineHeight);
+
+ for (int i = 0; i < lineCount; i++)
+ {
+ if (lineStart + i >= _avatars.Count)
+ {
+ break;
+ }
+
+ int y = negativeOffset + (i * _lineHeight);
+ g.FillRectangle(_brushs[_blameLines[lineStart + i].AgeBucketIndex], 0, y, AgeBucketMarkerWidth, _lineHeight);
+
+ if (_avatars[lineStart + i] != null)
+ {
+ g.DrawImage(_avatars[lineStart + i], new Point(AgeBucketMarkerWidth, y));
+ }
+ }
+
+ base.Paint(g, rect);
+ }
+ }
+}
diff --git a/GitUI/Editor/FileViewer.cs b/GitUI/Editor/FileViewer.cs
index 396786f4bd0..2e1a5bb912f 100644
--- a/GitUI/Editor/FileViewer.cs
+++ b/GitUI/Editor/FileViewer.cs
@@ -1,4 +1,5 @@
using System;
+using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.IO;
@@ -1667,5 +1668,15 @@ public bool ShowSyntaxHighlightingInDiff
public ToolStripButton IgnoreAllWhitespacesButton => _fileViewer.ignoreAllWhitespaces;
public ToolStripMenuItem IgnoreAllWhitespacesMenuItem => _fileViewer.ignoreAllWhitespaceChangesToolStripMenuItem;
}
+
+ public void SetGitBlameGutter(IEnumerable gitBlameEntries)
+ {
+ internalFileViewer.ShowGutterAvatars = AppSettings.BlameShowAuthorAvatar;
+
+ if (AppSettings.BlameShowAuthorAvatar)
+ {
+ internalFileViewer.SetGitBlameGutter(gitBlameEntries);
+ }
+ }
}
}
diff --git a/GitUI/Editor/FileViewerInternal.cs b/GitUI/Editor/FileViewerInternal.cs
index 3e5d8864682..f03573bd1a5 100644
--- a/GitUI/Editor/FileViewerInternal.cs
+++ b/GitUI/Editor/FileViewerInternal.cs
@@ -37,6 +37,8 @@ public partial class FileViewerInternal : GitModuleControl, IFileViewer
private bool _shouldScrollToBottom = false;
private readonly int _bottomBlankHeight = DpiUtil.Scale(300);
private ContinuousScrollEventManager _continuousScrollEventManager;
+ private BlameAuthorMargin _authorsAvatarMargin;
+ private bool _showGutterAvatars;
public FileViewerInternal()
{
@@ -536,6 +538,39 @@ private void OnVScrollPositionChanged(EventArgs e)
#endregion
+ public void SetGitBlameGutter(IEnumerable gitBlameEntries)
+ {
+ if (_showGutterAvatars)
+ {
+ _authorsAvatarMargin.Initialize(gitBlameEntries);
+ }
+ }
+
+ public bool ShowGutterAvatars
+ {
+ get => _showGutterAvatars;
+ set
+ {
+ _showGutterAvatars = value;
+ if (!_showGutterAvatars)
+ {
+ _authorsAvatarMargin?.SetVisiblity(false);
+
+ return;
+ }
+
+ if (_authorsAvatarMargin == null)
+ {
+ _authorsAvatarMargin = new BlameAuthorMargin(TextEditor.ActiveTextAreaControl.TextArea);
+ TextEditor.ActiveTextAreaControl.TextArea.InsertLeftMargin(0, _authorsAvatarMargin);
+ }
+ else
+ {
+ _authorsAvatarMargin.SetVisiblity(true);
+ }
+ }
+ }
+
internal sealed class CurrentViewPositionCache
{
private readonly FileViewerInternal _viewer;
diff --git a/GitUI/Editor/GitBlameEntry.cs b/GitUI/Editor/GitBlameEntry.cs
new file mode 100644
index 00000000000..8458364d47f
--- /dev/null
+++ b/GitUI/Editor/GitBlameEntry.cs
@@ -0,0 +1,11 @@
+using System.Drawing;
+
+namespace GitUI.Editor
+{
+ public class GitBlameEntry
+ {
+ public Image Avatar { get; set; }
+ public int AgeBucketIndex { get; set; }
+ public Color AgeBucketColor { get; set; }
+ }
+}
diff --git a/GitUI/Translation/English.xlf b/GitUI/Translation/English.xlf
index 29602713ad8..04d6913e3af 100644
--- a/GitUI/Translation/English.xlf
+++ b/GitUI/Translation/English.xlf
@@ -4268,6 +4268,10 @@ If you are not sure just close this window.
Save as
+