Compare commits

...

8 commits

Author SHA1 Message Date
leo
268dd9849d
enhance: better margin of commit subject (#528)
Some checks are pending
Continuous Integration / Build (push) Waiting to run
Continuous Integration / Prepare version string (push) Waiting to run
Continuous Integration / Package (push) Blocked by required conditions
2024-09-29 19:56:16 +08:00
leo
fbfca7b4a5
enhance: better graph for ended orphan branch (#528) 2024-09-29 18:41:50 +08:00
leo
4b5d65cdb7
refactor: rewrite Models.CommitGraph.PathHelper (#528) 2024-09-29 18:16:28 +08:00
leo
fe015f9bfd
fix: endpoint of a graph path has been added twice (#528) 2024-09-29 16:45:50 +08:00
GadflyFang
c7332aff03
fix: avoid NRE in ContextMenuExtension (#526) 2024-09-29 16:05:02 +08:00
GadflyFang
18e0479288
enhance: reduce Minimum of History Commits setting (#527) 2024-09-29 15:23:38 +08:00
leo
3af30f54b6
enhance: avoid flicker at the first time clicking on text diff view 2024-09-29 14:16:42 +08:00
leo
d5671ea8df
enhance: only re-calculate highlight chunk when it is needed 2024-09-29 13:48:48 +08:00
4 changed files with 164 additions and 102 deletions

View file

@ -20,7 +20,6 @@ namespace SourceGit.Models
public bool IsMerged; public bool IsMerged;
public double LastX; public double LastX;
public double LastY; public double LastY;
public double EndY;
public Path Path; public Path Path;
public PathHelper(string next, bool isMerged, int color, Point start) public PathHelper(string next, bool isMerged, int color, Point start)
@ -29,7 +28,6 @@ namespace SourceGit.Models
IsMerged = isMerged; IsMerged = isMerged;
LastX = start.X; LastX = start.X;
LastY = start.Y; LastY = start.Y;
EndY = LastY;
Path = new Path(); Path = new Path();
Path.Color = color; Path.Color = color;
@ -42,7 +40,6 @@ namespace SourceGit.Models
IsMerged = isMerged; IsMerged = isMerged;
LastX = to.X; LastX = to.X;
LastY = to.Y; LastY = to.Y;
EndY = LastY;
Path = new Path(); Path = new Path();
Path.Color = color; Path.Color = color;
@ -50,43 +47,91 @@ namespace SourceGit.Models
Path.Points.Add(to); Path.Points.Add(to);
} }
public void Add(double x, double y, double halfHeight, bool isEnd = false) /// <summary>
/// A path that just passed this row.
/// </summary>
/// <param name="x"></param>
/// <param name="y"></param>
/// <param name="halfHeight"></param>
public void Pass(double x, double y, double halfHeight)
{ {
if (x > LastX) if (x > LastX)
{ {
Add(new Point(LastX, LastY)); Add(LastX, LastY);
Add(new Point(x, y - halfHeight)); Add(x, y - halfHeight);
if (isEnd)
Add(new Point(x, y));
} }
else if (x < LastX) else if (x < LastX)
{ {
var testY = LastY + halfHeight; Add(LastX, y - halfHeight);
if (y > testY) y += halfHeight;
Add(new Point(LastX, testY)); Add(x, y);
if (!isEnd)
y += halfHeight;
Add(new Point(x, y));
}
else if (isEnd)
{
Add(new Point(x, y));
} }
LastX = x; LastX = x;
LastY = y; LastY = y;
} }
private void Add(Point p) /// <summary>
/// A path that has commit in this row but not ended
/// </summary>
/// <param name="x"></param>
/// <param name="y"></param>
/// <param name="halfHeight"></param>
public void Goto(double x, double y, double halfHeight)
{ {
if (EndY < p.Y) if (x > LastX)
{ {
Path.Points.Add(p); Add(LastX, LastY);
EndY = p.Y; Add(x, y - halfHeight);
}
else if (x < LastX)
{
var minY = y - halfHeight;
if (minY > LastY)
minY -= halfHeight;
Add(LastX, minY);
Add(x, y);
}
LastX = x;
LastY = y;
}
/// <summary>
/// A path that has commit in this row and end.
/// </summary>
/// <param name="x"></param>
/// <param name="y"></param>
/// <param name="halfHeight"></param>
public void End(double x, double y, double halfHeight)
{
if (x > LastX)
{
Add(LastX, LastY);
Add(x, y - halfHeight);
}
else if (x < LastX)
{
Add(LastX, y - halfHeight);
}
Add(x, y);
LastX = x;
LastY = y;
}
private void Add(double x, double y)
{
if (_endY < y)
{
Path.Points.Add(new Point(x, y));
_endY = y;
} }
} }
private double _endY = 0;
} }
public class Link public class Link
@ -146,7 +191,6 @@ namespace SourceGit.Models
var temp = new CommitGraph(); var temp = new CommitGraph();
var unsolved = new List<PathHelper>(); var unsolved = new List<PathHelper>();
var mapUnsolved = new Dictionary<string, PathHelper>();
var ended = new List<PathHelper>(); var ended = new List<PathHelper>();
var offsetY = -HALF_HEIGHT; var offsetY = -HALF_HEIGHT;
var colorIdx = 0; var colorIdx = 0;
@ -155,13 +199,13 @@ namespace SourceGit.Models
{ {
var major = null as PathHelper; var major = null as PathHelper;
var isMerged = commit.IsMerged; var isMerged = commit.IsMerged;
var oldCount = unsolved.Count;
// Update current y offset // Update current y offset
offsetY += UNIT_HEIGHT; offsetY += UNIT_HEIGHT;
// Find first curves that links to this commit and marks others that links to this commit ended. // Find first curves that links to this commit and marks others that links to this commit ended.
double offsetX = H_MARGIN - HALF_WIDTH; var offsetX = 4 - HALF_WIDTH;
var maxOffsetOld = unsolved.Count > 0 ? unsolved[^1].LastX : offsetX + UNIT_WIDTH;
foreach (var l in unsolved) foreach (var l in unsolved)
{ {
if (l.Next == commit.SHA) if (l.Next == commit.SHA)
@ -174,19 +218,17 @@ namespace SourceGit.Models
if (commit.Parents.Count > 0) if (commit.Parents.Count > 0)
{ {
major.Next = commit.Parents[0]; major.Next = commit.Parents[0];
if (!mapUnsolved.ContainsKey(major.Next)) major.Goto(offsetX, offsetY, HALF_HEIGHT);
mapUnsolved.Add(major.Next, major);
} }
else else
{ {
major.Next = "ENDED"; major.End(offsetX, offsetY, HALF_HEIGHT);
ended.Add(l); ended.Add(l);
} }
major.Add(offsetX, offsetY, HALF_HEIGHT);
} }
else else
{ {
l.End(major.LastX, offsetY, HALF_HEIGHT);
ended.Add(l); ended.Add(l);
} }
@ -195,13 +237,16 @@ namespace SourceGit.Models
} }
else else
{ {
if (!mapUnsolved.ContainsKey(l.Next))
mapUnsolved.Add(l.Next, l);
offsetX += UNIT_WIDTH; offsetX += UNIT_WIDTH;
l.Add(offsetX, offsetY, HALF_HEIGHT); l.Pass(offsetX, offsetY, HALF_HEIGHT);
} }
} }
// Remove ended curves from unsolved
foreach (var l in ended)
unsolved.Remove(l);
ended.Clear();
// Create new curve for branch head // Create new curve for branch head
if (major == null) if (major == null)
{ {
@ -218,13 +263,8 @@ namespace SourceGit.Models
} }
// Calculate link position of this commit. // Calculate link position of this commit.
Point position = new Point(offsetX, offsetY); Point position = new Point(major?.LastX ?? offsetX, offsetY);
int dotColor = 0; int dotColor = major?.Path.Color ?? 0;
if (major != null)
{
position = new Point(major.LastX, offsetY);
dotColor = major.Path.Color;
}
Dot anchor = new Dot() { Center = position, Color = dotColor }; Dot anchor = new Dot() { Center = position, Color = dotColor };
if (commit.IsCurrentHead) if (commit.IsCurrentHead)
anchor.Type = DotType.Head; anchor.Type = DotType.Head;
@ -239,11 +279,12 @@ namespace SourceGit.Models
{ {
for (int j = 1; j < commit.Parents.Count; j++) for (int j = 1; j < commit.Parents.Count; j++)
{ {
var parent = commit.Parents[j]; var parentSHA = commit.Parents[j];
if (mapUnsolved.TryGetValue(parent, out var value)) var parent = unsolved.Find(x => x.Next.Equals(parentSHA, StringComparison.Ordinal));
if (parent != null)
{ {
// Try to change the merge state of linked graph // Try to change the merge state of linked graph
var l = value; var l = parent;
if (isMerged) if (isMerged)
l.IsMerged = true; l.IsMerged = true;
@ -260,7 +301,7 @@ namespace SourceGit.Models
offsetX += UNIT_WIDTH; offsetX += UNIT_WIDTH;
// Create new curve for parent commit that not includes before // Create new curve for parent commit that not includes before
var l = new PathHelper(parent, isMerged, colorIdx, position, new Point(offsetX, position.Y + HALF_HEIGHT)); var l = new PathHelper(parentSHA, isMerged, colorIdx, position, new Point(offsetX, position.Y + HALF_HEIGHT));
unsolved.Add(l); unsolved.Add(l);
temp.Paths.Add(l.Path); temp.Paths.Add(l.Path);
colorIdx = (colorIdx + 1) % _penCount; colorIdx = (colorIdx + 1) % _penCount;
@ -268,20 +309,9 @@ namespace SourceGit.Models
} }
} }
// 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 Views.Histories). // Margins & merge state (used by Views.Histories).
commit.IsMerged = isMerged; commit.IsMerged = isMerged;
commit.Margin = new Thickness(Math.Max(offsetX + HALF_WIDTH, oldCount * UNIT_WIDTH + H_MARGIN) + H_MARGIN, 0, 0, 0); commit.Margin = new Thickness(Math.Max(offsetX, maxOffsetOld) + HALF_WIDTH + H_MARGIN, 0, 0, 0);
// Clean up
ended.Clear();
mapUnsolved.Clear();
} }
// Deal with curves haven't ended yet. // Deal with curves haven't ended yet.
@ -293,7 +323,7 @@ namespace SourceGit.Models
if (path.Path.Points.Count == 1 && Math.Abs(path.Path.Points[0].Y - endY) < 0.0001) if (path.Path.Points.Count == 1 && Math.Abs(path.Path.Points[0].Y - endY) < 0.0001)
continue; continue;
path.Add((i + 0.5) * UNIT_WIDTH + H_MARGIN, endY + HALF_HEIGHT, HALF_HEIGHT, true); path.End((i + 0.5) * UNIT_WIDTH + 4, endY + HALF_HEIGHT, HALF_HEIGHT);
} }
unsolved.Clear(); unsolved.Clear();

View file

@ -1,5 +1,4 @@
using System.ComponentModel; using System.ComponentModel;
using Avalonia.Controls; using Avalonia.Controls;
namespace SourceGit.Views namespace SourceGit.Views
@ -15,7 +14,7 @@ namespace SourceGit.Views
menu.Closing += OnContextMenuClosing; // Clear context menu because it is dynamic. menu.Closing += OnContextMenuClosing; // Clear context menu because it is dynamic.
control.ContextMenu = menu; control.ContextMenu = menu;
control.ContextMenu.Open(); control.ContextMenu?.Open();
} }
private static void OnContextMenuClosing(object sender, CancelEventArgs e) private static void OnContextMenuClosing(object sender, CancelEventArgs e)

View file

@ -99,7 +99,7 @@
Margin="0,0,16,0"/> Margin="0,0,16,0"/>
<Grid Grid.Row="3" Grid.Column="1" ColumnDefinitions="*,64"> <Grid Grid.Row="3" Grid.Column="1" ColumnDefinitions="*,64">
<Slider Grid.Column="0" <Slider Grid.Column="0"
Minimum="20000" Maximum="100000" Minimum="5000" Maximum="100000"
TickPlacement="BottomRight" TickFrequency="5000" TickPlacement="BottomRight" TickFrequency="5000"
IsSnapToTickEnabled="True" IsSnapToTickEnabled="True"
VerticalAlignment="Center" VerticalAlignment="Center"

View file

@ -437,8 +437,8 @@ namespace SourceGit.Views
base.OnLoaded(e); base.OnLoaded(e);
TextArea.TextView.ContextRequested += OnTextViewContextRequested; TextArea.TextView.ContextRequested += OnTextViewContextRequested;
TextArea.TextView.PointerEntered += OnTextViewPointerEntered; TextArea.TextView.PointerEntered += OnTextViewPointerChanged;
TextArea.TextView.PointerMoved += OnTextViewPointerMoved; TextArea.TextView.PointerMoved += OnTextViewPointerChanged;
TextArea.TextView.PointerWheelChanged += OnTextViewPointerWheelChanged; TextArea.TextView.PointerWheelChanged += OnTextViewPointerWheelChanged;
UpdateTextMate(); UpdateTextMate();
@ -449,8 +449,8 @@ namespace SourceGit.Views
base.OnUnloaded(e); base.OnUnloaded(e);
TextArea.TextView.ContextRequested -= OnTextViewContextRequested; TextArea.TextView.ContextRequested -= OnTextViewContextRequested;
TextArea.TextView.PointerEntered -= OnTextViewPointerEntered; TextArea.TextView.PointerEntered -= OnTextViewPointerChanged;
TextArea.TextView.PointerMoved -= OnTextViewPointerMoved; TextArea.TextView.PointerMoved -= OnTextViewPointerChanged;
TextArea.TextView.PointerWheelChanged -= OnTextViewPointerWheelChanged; TextArea.TextView.PointerWheelChanged -= OnTextViewPointerWheelChanged;
if (_textMate != null) if (_textMate != null)
@ -510,35 +510,43 @@ namespace SourceGit.Views
e.Handled = true; e.Handled = true;
} }
private void OnTextViewPointerEntered(object sender, PointerEventArgs e) private void OnTextViewPointerChanged(object sender, PointerEventArgs e)
{ {
if (EnableChunkSelection && sender is TextView view) if (EnableChunkSelection && sender is TextView view)
{ {
var chunk = SelectedChunk; var selection = TextArea.Selection;
if (chunk != null) if (selection == null || selection.IsEmpty)
{ {
var rect = new Rect(0, chunk.Y, Bounds.Width, chunk.Height); if (_lastSelectStart != _lastSelectEnd)
if (rect.Contains(e.GetPosition(this))) {
return; _lastSelectStart = TextLocation.Empty;
_lastSelectEnd = TextLocation.Empty;
}
var chunk = SelectedChunk;
if (chunk != null)
{
var rect = new Rect(0, chunk.Y, Bounds.Width, chunk.Height);
if (rect.Contains(e.GetPosition(this)))
return;
}
UpdateSelectedChunk(e.GetPosition(view).Y + view.VerticalOffset);
return;
} }
UpdateSelectedChunk(e.GetPosition(view).Y + view.VerticalOffset); var start = selection.StartPosition.Location;
} var end = selection.EndPosition.Location;
} if (_lastSelectStart != start || _lastSelectEnd != end)
private void OnTextViewPointerMoved(object sender, PointerEventArgs e)
{
if (EnableChunkSelection && sender is TextView view)
{
var chunk = SelectedChunk;
if (chunk != null)
{ {
var rect = new Rect(0, chunk.Y, Bounds.Width, chunk.Height); _lastSelectStart = start;
if (rect.Contains(e.GetPosition(this))) _lastSelectEnd = end;
return; UpdateSelectedChunk(e.GetPosition(view).Y + view.VerticalOffset);
return;
} }
UpdateSelectedChunk(e.GetPosition(view).Y + view.VerticalOffset); if (SelectedChunk == null)
UpdateSelectedChunk(e.GetPosition(view).Y + view.VerticalOffset);
} }
} }
@ -657,7 +665,9 @@ namespace SourceGit.Views
} }
private TextMate.Installation _textMate = null; private TextMate.Installation _textMate = null;
protected LineStyleTransformer _lineStyleTransformer = null; private TextLocation _lastSelectStart = TextLocation.Empty;
private TextLocation _lastSelectEnd = TextLocation.Empty;
private LineStyleTransformer _lineStyleTransformer = null;
} }
public class CombinedTextDiffPresenter : ThemedTextDiffPresenter public class CombinedTextDiffPresenter : ThemedTextDiffPresenter
@ -684,18 +694,6 @@ namespace SourceGit.Views
return 0; return 0;
} }
protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
{
base.OnApplyTemplate(e);
var scroller = (ScrollViewer)e.NameScope.Find("PART_ScrollViewer");
if (scroller != null)
{
scroller.Bind(ScrollViewer.OffsetProperty, new Binding("SyncScrollOffset", BindingMode.TwoWay));
scroller.GotFocus += (_, _) => TrySetChunk(null);
}
}
public override void UpdateSelectedChunk(double y) public override void UpdateSelectedChunk(double y)
{ {
var diff = DataContext as Models.TextDiff; var diff = DataContext as Models.TextDiff;
@ -812,6 +810,27 @@ namespace SourceGit.Views
} }
} }
protected override void OnLoaded(RoutedEventArgs e)
{
base.OnLoaded(e);
var scroller = this.FindDescendantOfType<ScrollViewer>();
if (scroller != null)
{
scroller.Bind(ScrollViewer.OffsetProperty, new Binding("SyncScrollOffset", BindingMode.TwoWay));
scroller.GotFocus += OnTextViewScrollGotFocus;
}
}
protected override void OnUnloaded(RoutedEventArgs e)
{
var scroller = this.FindDescendantOfType<ScrollViewer>();
if (scroller != null)
scroller.GotFocus -= OnTextViewScrollGotFocus;
base.OnUnloaded(e);
}
protected override void OnDataContextChanged(EventArgs e) protected override void OnDataContextChanged(EventArgs e)
{ {
base.OnDataContextChanged(e); base.OnDataContextChanged(e);
@ -843,6 +862,16 @@ namespace SourceGit.Views
GC.Collect(); GC.Collect();
} }
private void OnTextViewScrollGotFocus(object sender, GotFocusEventArgs e)
{
if (EnableChunkSelection && sender is ScrollViewer viewer)
{
var area = viewer.FindDescendantOfType<TextArea>();
if (!area.IsPointerOver)
TrySetChunk(null);
}
}
} }
public class SingleSideTextDiffPresenter : ThemedTextDiffPresenter public class SingleSideTextDiffPresenter : ThemedTextDiffPresenter
@ -1011,8 +1040,6 @@ namespace SourceGit.Views
protected override void OnUnloaded(RoutedEventArgs e) protected override void OnUnloaded(RoutedEventArgs e)
{ {
base.OnUnloaded(e);
if (_scrollViewer != null) if (_scrollViewer != null)
{ {
_scrollViewer.ScrollChanged -= OnTextViewScrollChanged; _scrollViewer.ScrollChanged -= OnTextViewScrollChanged;
@ -1022,6 +1049,7 @@ namespace SourceGit.Views
TextArea.PointerWheelChanged -= OnTextAreaPointerWheelChanged; TextArea.PointerWheelChanged -= OnTextAreaPointerWheelChanged;
base.OnUnloaded(e);
GC.Collect(); GC.Collect();
} }
@ -1057,7 +1085,12 @@ namespace SourceGit.Views
private void OnTextViewScrollGotFocus(object sender, GotFocusEventArgs e) private void OnTextViewScrollGotFocus(object sender, GotFocusEventArgs e)
{ {
TrySetChunk(null); if (EnableChunkSelection && sender is ScrollViewer viewer)
{
var area = viewer.FindDescendantOfType<TextArea>();
if (!area.IsPointerOver)
TrySetChunk(null);
}
} }
private void OnTextViewScrollChanged(object sender, ScrollChangedEventArgs e) private void OnTextViewScrollChanged(object sender, ScrollChangedEventArgs e)