From e9fa9a42cadf4a35bb50313fe2fdbf8f4fcd0784 Mon Sep 17 00:00:00 2001 From: leo Date: Thu, 25 Jul 2024 15:31:16 +0800 Subject: [PATCH] enhance: add `Views.CommitRefsPresenter` to draw commit's decorators (refs) directly --- src/ViewModels/Repository.cs | 2 +- src/Views/CommitBaseInfo.axaml | 36 ++---- src/Views/CommitRefsPresenter.cs | 202 +++++++++++++++++++++++++++++++ src/Views/Histories.axaml | 40 ++---- src/Views/Histories.axaml.cs | 3 + 5 files changed, 227 insertions(+), 56 deletions(-) create mode 100644 src/Views/CommitRefsPresenter.cs diff --git a/src/ViewModels/Repository.cs b/src/ViewModels/Repository.cs index 05858d14..a7faeb35 100644 --- a/src/ViewModels/Repository.cs +++ b/src/ViewModels/Repository.cs @@ -281,7 +281,7 @@ namespace SourceGit.ViewModels public void Close() { - SelectedView = 0.0; // Do NOT modify. Used to remove exists widgets for GC.Collect + SelectedView = null; // Do NOT modify. Used to remove exists widgets for GC.Collect var settingsSerialized = JsonSerializer.Serialize(_settings, JsonCodeGen.Default.RepositorySettings); File.WriteAllText(Path.Combine(_gitDir, "sourcegit.settings"), settingsSerialized); diff --git a/src/Views/CommitBaseInfo.axaml b/src/Views/CommitBaseInfo.axaml index 19b75a50..8bc19ea2 100644 --- a/src/Views/CommitBaseInfo.axaml +++ b/src/Views/CommitBaseInfo.axaml @@ -78,30 +78,18 @@ - - - - - - - - - - - - - - - - - - - - - - - - + + + + + diff --git a/src/Views/CommitRefsPresenter.cs b/src/Views/CommitRefsPresenter.cs new file mode 100644 index 00000000..b49a8c02 --- /dev/null +++ b/src/Views/CommitRefsPresenter.cs @@ -0,0 +1,202 @@ +using System; +using System.Collections.Generic; +using System.Globalization; + +using Avalonia; +using Avalonia.Controls; +using Avalonia.Media; + +namespace SourceGit.Views +{ + public class CommitRefsPresenter : Control + { + public class RenderItem + { + public Geometry Icon { get; set; } = null; + public FormattedText Label { get; set; } = null; + public bool IsTag { get; set; } = false; + } + + public static readonly StyledProperty FontFamilyProperty = + TextBlock.FontFamilyProperty.AddOwner(); + + public FontFamily FontFamily + { + get => GetValue(FontFamilyProperty); + set => SetValue(FontFamilyProperty, value); + } + + public static readonly StyledProperty FontSizeProperty = + TextBlock.FontSizeProperty.AddOwner(); + + public double FontSize + { + get => GetValue(FontSizeProperty); + set => SetValue(FontSizeProperty, value); + } + + public static readonly StyledProperty IconBackgroundProperty = + AvaloniaProperty.Register(nameof(IconBackground), Brushes.White); + + public IBrush IconBackground + { + get => GetValue(IconBackgroundProperty); + set => SetValue(IconBackgroundProperty, value); + } + + public static readonly StyledProperty IconForegroundProperty = + AvaloniaProperty.Register(nameof(IconForeground), Brushes.White); + + public IBrush IconForeground + { + get => GetValue(IconForegroundProperty); + set => SetValue(IconForegroundProperty, value); + } + + public static readonly StyledProperty LabelForegroundProperty = + AvaloniaProperty.Register(nameof(LabelForeground), Brushes.White); + + public IBrush LabelForeground + { + get => GetValue(LabelForegroundProperty); + set => SetValue(LabelForegroundProperty, value); + } + + public static readonly StyledProperty BranchNameBackgroundProperty = + AvaloniaProperty.Register(nameof(BranchNameBackground), Brushes.White); + + public IBrush BranchNameBackground + { + get => GetValue(BranchNameBackgroundProperty); + set => SetValue(BranchNameBackgroundProperty, value); + } + + public static readonly StyledProperty TagNameBackgroundProperty = + AvaloniaProperty.Register(nameof(TagNameBackground), Brushes.White); + + public IBrush TagNameBackground + { + get => GetValue(TagNameBackgroundProperty); + set => SetValue(TagNameBackgroundProperty, value); + } + + static CommitRefsPresenter() + { + AffectsMeasure( + FontFamilyProperty, + FontSizeProperty, + LabelForegroundProperty); + + AffectsRender( + IconBackgroundProperty, + IconForegroundProperty, + BranchNameBackgroundProperty, + TagNameBackgroundProperty); + } + + public override void Render(DrawingContext context) + { + if (_items.Count == 0) + return; + + var iconFG = IconForeground; + var iconBG = IconBackground; + var branchBG = BranchNameBackground; + var tagBG = TagNameBackground; + var x = 0.0; + + foreach (var item in _items) + { + var iconRect = new RoundedRect(new Rect(x, 0, 16, 16), new CornerRadius(2, 0, 0, 2)); + var labelRect = new RoundedRect(new Rect(x + 16, 0, item.Label.Width + 8, 16), new CornerRadius(0, 2, 2, 0)); + + context.DrawRectangle(iconBG, null, iconRect); + context.DrawRectangle(item.IsTag ? tagBG : branchBG, null, labelRect); + context.DrawText(item.Label, new Point(x + 20, 8.0 - item.Label.Height * 0.5)); + + using (context.PushTransform(Matrix.CreateTranslation(x + 4, 4))) + context.DrawGeometry(iconFG, null, item.Icon); + + x += item.Label.Width + 16 + 8 + 4; + } + } + + protected override void OnDataContextChanged(EventArgs e) + { + base.OnDataContextChanged(e); + InvalidateMeasure(); + } + + protected override Size MeasureOverride(Size availableSize) + { + _items.Clear(); + + if (DataContext is Models.Commit commit && commit.HasDecorators) + { + var typeface = new Typeface(FontFamily); + var typefaceBold = new Typeface(FontFamily, FontStyle.Normal, FontWeight.Bold); + var labelFG = LabelForeground; + var labelSize = FontSize; + var requiredWidth = 0.0; + + foreach (var decorator in commit.Decorators) + { + var isHead = decorator.Type == Models.DecoratorType.CurrentBranchHead || + decorator.Type == Models.DecoratorType.CurrentCommitHead; + + var label = new FormattedText( + decorator.Name, + CultureInfo.CurrentCulture, + FlowDirection.LeftToRight, + isHead ? typefaceBold : typeface, + labelSize, + labelFG); + + var item = new RenderItem() + { + Label = label, + IsTag = decorator.Type == Models.DecoratorType.Tag, + }; + + var geo = null as StreamGeometry; + switch (decorator.Type) + { + case Models.DecoratorType.CurrentBranchHead: + case Models.DecoratorType.CurrentCommitHead: + geo = this.FindResource("Icons.Check") as StreamGeometry; + break; + case Models.DecoratorType.RemoteBranchHead: + geo = this.FindResource("Icons.Remote") as StreamGeometry; + break; + case Models.DecoratorType.Tag: + geo = this.FindResource("Icons.Tag") as StreamGeometry; + break; + default: + geo = this.FindResource("Icons.Branch") as StreamGeometry; + break; + } + + var drawGeo = geo.Clone(); + var iconBounds = drawGeo.Bounds; + var translation = Matrix.CreateTranslation(-(Vector)iconBounds.Position); + var scale = Math.Min(8.0 / iconBounds.Width, 8.0 / iconBounds.Height); + var transform = translation * Matrix.CreateScale(scale, scale); + if (drawGeo.Transform == null || drawGeo.Transform.Value == Matrix.Identity) + drawGeo.Transform = new MatrixTransform(transform); + else + drawGeo.Transform = new MatrixTransform(drawGeo.Transform.Value * transform); + + item.Icon = drawGeo; + _items.Add(item); + requiredWidth += label.Width + 16 /* icon */ + 8 /* label margin */ + 4 /* item right margin */; + } + + return new Size(requiredWidth, 16); + } + + return new Size(0, 0); + } + + private List _items = new List(); + } +} diff --git a/src/Views/Histories.axaml b/src/Views/Histories.axaml index 74818238..bf6b75f4 100644 --- a/src/Views/Histories.axaml +++ b/src/Views/Histories.axaml @@ -69,38 +69,16 @@ Margin="0,0,4,0" Fill="{DynamicResource Brush.FG1}" IsVisible="{Binding CanPullFromUpstream}"/> - - - - - - - - - - - - - - - - - - - - - - + ((h, _) => { + if (h.DataContext == null) + return; + // Force scroll selected item (current head) into view. see issue #58 var datagrid = h.CommitDataGrid; if (datagrid != null && datagrid.SelectedItems.Count == 1)