Skip to content

Commit 2aaf697

Browse files
committed
refactor: compact branch names in graph (#2369)
Signed-off-by: leo <longshuang@msn.cn>
1 parent b973572 commit 2aaf697

1 file changed

Lines changed: 141 additions & 77 deletions

File tree

src/Views/CommitRefsPresenter.cs

Lines changed: 141 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ public class RenderItem
1818
public bool IsHead { get; set; } = false;
1919
public double Width { get; set; } = 0.0;
2020
public Models.Decorator Decorator { get; set; } = null;
21+
public List<FormattedText> Remotes { get; set; } = [];
2122
}
2223

2324
public static readonly StyledProperty<FontFamily> FontFamilyProperty =
@@ -130,7 +131,6 @@ public override void Render(DrawingContext context)
130131
}
131132

132133
var entireRect = new RoundedRect(new Rect(x, y, item.Width, 16), new CornerRadius(4));
133-
134134
if (item.IsHead)
135135
{
136136
if (useGraphColor)
@@ -141,24 +141,32 @@ public override void Render(DrawingContext context)
141141
using (context.PushOpacity(.6))
142142
context.DrawRectangle(item.Brush, null, entireRect);
143143
}
144-
145-
context.DrawText(item.Label, new Point(x + 16, y + 8.0 - item.Label.Height * 0.5));
146144
}
147145
else
148146
{
149147
if (bg != null)
150148
context.DrawRectangle(bg, null, entireRect);
151149

152-
var labelRect = new RoundedRect(new Rect(x + 16, y, item.Label.Width + 8, 16), new CornerRadius(0, 4, 4, 0));
150+
var labelRect = new RoundedRect(new Rect(x + 16, y, item.Width - 16, 16), new CornerRadius(4, 0, 0, 4));
153151
using (context.PushOpacity(.2))
154152
context.DrawRectangle(item.Brush, null, labelRect);
153+
}
154+
155+
context.DrawLine(new Pen(item.Brush), new Point(x + 16, y), new Point(x + 16, y + 16));
156+
context.DrawText(item.Label, new Point(x + 20, y + 8.0 - item.Label.Height * 0.5));
155157

156-
context.DrawLine(new Pen(item.Brush), new Point(x + 16, y), new Point(x + 16, y + 16));
157-
context.DrawText(item.Label, new Point(x + 20, y + 8.0 - item.Label.Height * 0.5));
158+
if (item.Remotes.Count > 0)
159+
{
160+
var rx = x + 20 + item.Label.WidthIncludingTrailingWhitespace + 4;
161+
foreach (var remote in item.Remotes)
162+
{
163+
context.DrawLine(new Pen(item.Brush), new Point(rx, y), new Point(rx, y + 16));
164+
context.DrawText(remote, new Point(rx + 4, y + 8.0 - remote.Height * 0.5));
165+
rx += remote.WidthIncludingTrailingWhitespace + 9;
166+
}
158167
}
159168

160169
context.DrawRectangle(null, new Pen(item.Brush), entireRect);
161-
162170
using (context.PushTransform(Matrix.CreateTranslation(x + 3, y + 3)))
163171
context.DrawGeometry(fg, null, item.Icon);
164172

@@ -180,94 +188,150 @@ protected override Size MeasureOverride(Size availableSize)
180188
return new Size(0, 0);
181189

182190
var refs = commit.Decorators;
183-
if (refs is { Count: > 0 })
191+
var count = refs.Count;
192+
if (count == 0)
184193
{
185-
var typeface = new Typeface(FontFamily);
186-
var typefaceBold = new Typeface(FontFamily, FontStyle.Normal, FontWeight.Bold);
187-
var fg = Foreground;
188-
var normalBG = UseGraphColor ? Models.CommitGraph.Pens[commit.Color].Brush : Brushes.Gray;
189-
var labelSize = FontSize;
190-
var requiredHeight = 16.0;
191-
var x = 0.0;
192-
var allowWrap = AllowWrap;
193-
var showTags = ShowTags;
194-
195-
foreach (var decorator in refs)
196-
{
197-
if (!showTags && decorator.Type == Models.DecoratorType.Tag)
198-
continue;
194+
InvalidateVisual();
195+
return new Size(0, 0);
196+
}
197+
198+
var typeface = new Typeface(FontFamily);
199+
var typefaceHead = new Typeface(FontFamily, FontStyle.Normal, FontWeight.Bold);
200+
var typefaceRemote = new Typeface(FontFamily, FontStyle.Italic, FontWeight.Bold);
201+
var fg = Foreground;
202+
var normalBG = UseGraphColor ? Models.CommitGraph.Pens[commit.Color].Brush : Brushes.Gray;
203+
var labelSize = FontSize;
204+
var requiredHeight = 16.0;
205+
var x = 0.0;
206+
var allowWrap = AllowWrap;
207+
var showTags = ShowTags;
208+
var skippedIdx = new HashSet<int>();
199209

200-
var isHead = decorator.Type is Models.DecoratorType.CurrentBranchHead or Models.DecoratorType.CurrentCommitHead;
210+
for (var i = 0; i < count; i++)
211+
{
212+
if (skippedIdx.Contains(i))
213+
continue;
201214

202-
var label = new FormattedText(
215+
var decorator = refs[i];
216+
if (!showTags && decorator.Type == Models.DecoratorType.Tag)
217+
continue;
218+
219+
var item = new RenderItem() { Brush = normalBG, Decorator = decorator };
220+
var findRemotes = true;
221+
_items.Add(item);
222+
223+
switch (decorator.Type)
224+
{
225+
case Models.DecoratorType.CurrentBranchHead:
226+
case Models.DecoratorType.CurrentCommitHead:
227+
item.Icon = LoadIcon("Icons.Head");
228+
item.IsHead = true;
229+
break;
230+
case Models.DecoratorType.RemoteBranchHead:
231+
findRemotes = false;
232+
item.Icon = LoadIcon("Icons.Remote");
233+
break;
234+
case Models.DecoratorType.Tag:
235+
item.Brush = Brushes.Gray;
236+
findRemotes = false;
237+
item.Icon = LoadIcon("Icons.Tag");
238+
break;
239+
default:
240+
item.Icon = LoadIcon("Icons.Branch");
241+
break;
242+
}
243+
244+
if (item.IsHead)
245+
{
246+
item.Label = new FormattedText(
203247
decorator.Name,
204248
CultureInfo.CurrentCulture,
205249
FlowDirection.LeftToRight,
206-
isHead ? typefaceBold : typeface,
207-
isHead ? labelSize + 1 : labelSize,
250+
typefaceHead,
251+
labelSize + 1,
208252
fg);
253+
}
254+
else
255+
{
256+
item.Label = new FormattedText(
257+
decorator.Name,
258+
CultureInfo.CurrentCulture,
259+
FlowDirection.LeftToRight,
260+
typeface,
261+
labelSize,
262+
fg);
263+
}
209264

210-
var item = new RenderItem()
211-
{
212-
Label = label,
213-
Brush = normalBG,
214-
IsHead = isHead,
215-
Decorator = decorator,
216-
};
217-
218-
StreamGeometry geo;
219-
switch (decorator.Type)
220-
{
221-
case Models.DecoratorType.CurrentBranchHead:
222-
case Models.DecoratorType.CurrentCommitHead:
223-
geo = this.FindResource("Icons.Head") as StreamGeometry;
224-
break;
225-
case Models.DecoratorType.RemoteBranchHead:
226-
geo = this.FindResource("Icons.Remote") as StreamGeometry;
227-
break;
228-
case Models.DecoratorType.Tag:
229-
item.Brush = Brushes.Gray;
230-
geo = this.FindResource("Icons.Tag") as StreamGeometry;
231-
break;
232-
default:
233-
geo = this.FindResource("Icons.Branch") as StreamGeometry;
234-
break;
235-
}
265+
item.Width = item.Label.Width + 24;
236266

237-
var drawGeo = geo!.Clone();
238-
var iconBounds = drawGeo.Bounds;
239-
var translation = Matrix.CreateTranslation(-(Vector)iconBounds.Position);
240-
var scale = Math.Min(10.0 / iconBounds.Width, 10.0 / iconBounds.Height);
241-
var transform = translation * Matrix.CreateScale(scale, scale);
242-
if (drawGeo.Transform == null || drawGeo.Transform.Value == Matrix.Identity)
243-
drawGeo.Transform = new MatrixTransform(transform);
244-
else
245-
drawGeo.Transform = new MatrixTransform(drawGeo.Transform.Value * transform);
246-
247-
item.Icon = drawGeo;
248-
item.Width = 16 + (isHead ? 0 : 4) + label.Width + 4;
249-
_items.Add(item);
250-
251-
x += item.Width + 4;
252-
if (allowWrap)
267+
if (findRemotes)
268+
{
269+
for (var j = i + 1; j < count; j++)
253270
{
254-
if (x > availableSize.Width)
271+
var test = refs[j];
272+
if (test.Type != Models.DecoratorType.RemoteBranchHead)
273+
continue;
274+
275+
var idxOfSlash = test.Name.IndexOf('/');
276+
if (idxOfSlash < 1 || idxOfSlash == test.Name.Length - 1)
277+
continue;
278+
279+
var name = test.Name.Substring(idxOfSlash + 1);
280+
if (decorator.Name.Equals(name, StringComparison.Ordinal))
255281
{
256-
requiredHeight += 20.0;
257-
x = item.Width;
282+
var remote = new FormattedText(
283+
test.Name.Substring(0, idxOfSlash),
284+
CultureInfo.CurrentCulture,
285+
FlowDirection.LeftToRight,
286+
typefaceRemote,
287+
labelSize,
288+
fg);
289+
290+
item.Remotes.Add(remote);
291+
item.Width += remote.Width + 9;
292+
skippedIdx.Add(j);
258293
}
259294
}
260295
}
261296

262-
var requiredWidth = allowWrap && requiredHeight > 16.0
263-
? (double.IsInfinity(availableSize.Width) ? x + 2 : availableSize.Width)
264-
: x + 2;
265-
InvalidateVisual();
266-
return new Size(requiredWidth, requiredHeight);
297+
x += item.Width + 4;
298+
if (allowWrap)
299+
{
300+
if (x > availableSize.Width)
301+
{
302+
requiredHeight += 20.0;
303+
x = item.Width;
304+
}
305+
}
306+
}
307+
308+
double requiredWidth = 0;
309+
if (_items.Count > 0)
310+
{
311+
if (allowWrap && requiredHeight > 16.0)
312+
requiredWidth = double.IsInfinity(availableSize.Width) ? x + 2 : availableSize.Width;
313+
else
314+
requiredWidth = x + 2;
267315
}
268316

269317
InvalidateVisual();
270-
return new Size(0, 0);
318+
return new Size(requiredWidth, requiredHeight);
319+
}
320+
321+
private Geometry LoadIcon(string resourceKey)
322+
{
323+
var geo = this.FindResource(resourceKey) as StreamGeometry;
324+
var drawGeo = geo!.Clone();
325+
var iconBounds = drawGeo.Bounds;
326+
var translation = Matrix.CreateTranslation(-(Vector)iconBounds.Position);
327+
var scale = Math.Min(10.0 / iconBounds.Width, 10.0 / iconBounds.Height);
328+
var transform = translation * Matrix.CreateScale(scale, scale);
329+
if (drawGeo.Transform == null || drawGeo.Transform.Value == Matrix.Identity)
330+
drawGeo.Transform = new MatrixTransform(transform);
331+
else
332+
drawGeo.Transform = new MatrixTransform(drawGeo.Transform.Value * transform);
333+
334+
return drawGeo;
271335
}
272336

273337
private List<RenderItem> _items = new List<RenderItem>();

0 commit comments

Comments
 (0)