From 3197b4bfe8b91d0397dbd62cb2bf37d1bf4b0e90 Mon Sep 17 00:00:00 2001 From: leo Date: Sat, 6 Jul 2024 17:17:41 +0800 Subject: [PATCH 1/7] refactor: use custom BranchTree instead of TreeView to improve performance --- src/Converters/IntConverters.cs | 6 +- src/ViewModels/BranchTreeNode.cs | 152 +++----- src/ViewModels/Repository.cs | 2 +- src/Views/BranchTree.axaml | 110 ++++++ src/Views/BranchTree.axaml.cs | 324 +++++++++++++++++ src/Views/DeleteBranch.axaml | 5 +- src/Views/Repository.axaml | 145 +------- src/Views/Repository.axaml.cs | 602 +++++++++++-------------------- 8 files changed, 703 insertions(+), 643 deletions(-) create mode 100644 src/Views/BranchTree.axaml create mode 100644 src/Views/BranchTree.axaml.cs diff --git a/src/Converters/IntConverters.cs b/src/Converters/IntConverters.cs index 64f9b357..137f6c9b 100644 --- a/src/Converters/IntConverters.cs +++ b/src/Converters/IntConverters.cs @@ -1,4 +1,5 @@ -using Avalonia.Data.Converters; +using Avalonia; +using Avalonia.Data.Converters; namespace SourceGit.Converters { @@ -24,5 +25,8 @@ namespace SourceGit.Converters public static readonly FuncValueConverter IsSubjectLengthGood = new FuncValueConverter(v => v <= ViewModels.Preference.Instance.SubjectGuideLength); + + public static readonly FuncValueConverter ToTreeMargin = + new FuncValueConverter(v => new Thickness(v * 16, 0, 0, 0)); } } diff --git a/src/ViewModels/BranchTreeNode.cs b/src/ViewModels/BranchTreeNode.cs index f4044476..63848b9f 100644 --- a/src/ViewModels/BranchTreeNode.cs +++ b/src/ViewModels/BranchTreeNode.cs @@ -1,122 +1,62 @@ -using System; +using System; using System.Collections.Generic; using System.IO; using Avalonia; using Avalonia.Collections; +using Avalonia.Media; using CommunityToolkit.Mvvm.ComponentModel; namespace SourceGit.ViewModels { - public enum BranchTreeNodeType - { - DetachedHead, - Remote, - Folder, - Branch, - } - public class BranchTreeNode : ObservableObject { - public const double DEFAULT_CORNER = 4.0; - - public string Name { get; set; } - public BranchTreeNodeType Type { get; set; } - public object Backend { get; set; } - public bool IsFiltered { get; set; } - public List Children { get; set; } = new List(); - - public bool IsUpstreamTrackStatusVisible - { - get => IsBranch && !string.IsNullOrEmpty((Backend as Models.Branch).UpstreamTrackStatus); - } - - public string UpstreamTrackStatus - { - get => Type == BranchTreeNodeType.Branch ? (Backend as Models.Branch).UpstreamTrackStatus : ""; - } - - public bool IsRemote - { - get => Type == BranchTreeNodeType.Remote; - } - - public bool IsFolder - { - get => Type == BranchTreeNodeType.Folder; - } - - public bool IsBranch - { - get => Type == BranchTreeNodeType.Branch; - } - - public bool IsDetachedHead - { - get => Type == BranchTreeNodeType.DetachedHead; - } - - public bool IsCurrent - { - get => IsBranch && (Backend as Models.Branch).IsCurrent; - } - - public bool IsSelected - { - get => _isSelected; - set => SetProperty(ref _isSelected, value); - } - + public string Name { get; private set; } = string.Empty; + public object Backend { get; private set; } = null; + public int Depth { get; set; } = 0; + public bool IsFiltered { get; set; } = false; + public List Children { get; private set; } = new List(); + public bool IsExpanded { get => _isExpanded; set => SetProperty(ref _isExpanded, value); } - - public string Tooltip - { - get - { - if (Backend is Models.Branch b) - return b.FriendlyName; - - return null; - } - } - + public CornerRadius CornerRadius { get => _cornerRadius; set => SetProperty(ref _cornerRadius, value); } - - public void UpdateCornerRadius(ref BranchTreeNode prev) + + public bool IsBranch { - if (_isSelected && prev != null && prev.IsSelected) - { - var prevTop = prev.CornerRadius.TopLeft; - prev.CornerRadius = new CornerRadius(prevTop, 0); - CornerRadius = new CornerRadius(0, DEFAULT_CORNER); - } - else if (CornerRadius.TopLeft != DEFAULT_CORNER || - CornerRadius.BottomLeft != DEFAULT_CORNER) - { - CornerRadius = new CornerRadius(DEFAULT_CORNER); - } - - prev = this; - - if (!IsBranch && IsExpanded) - { - foreach (var child in Children) - child.UpdateCornerRadius(ref prev); - } + get => Backend is Models.Branch; } - private bool _isSelected = false; + public bool IsUpstreamTrackStatusVisible + { + get => Backend is Models.Branch { IsLocal: true } branch && !string.IsNullOrEmpty(branch.UpstreamTrackStatus); + } + + public string UpstreamTrackStatus + { + get => Backend is Models.Branch branch ? branch.UpstreamTrackStatus : ""; + } + + public FontWeight NameFontWeight + { + get => Backend is Models.Branch { IsCurrent: true } ? FontWeight.Bold : FontWeight.Regular; + } + + public string Tooltip + { + get => Backend is Models.Branch b ? b.FriendlyName : null; + } + private bool _isExpanded = false; - private CornerRadius _cornerRadius = new CornerRadius(DEFAULT_CORNER); + private CornerRadius _cornerRadius = new CornerRadius(4); public class Builder { @@ -133,7 +73,6 @@ namespace SourceGit.ViewModels var node = new BranchTreeNode() { Name = remote.Name, - Type = BranchTreeNodeType.Remote, Backend = remote, IsExpanded = bForceExpanded || _expanded.Contains(path), }; @@ -176,9 +115,13 @@ namespace SourceGit.ViewModels { foreach (var node in nodes) { + if (node.Backend is Models.Branch) + continue; + var path = prefix + "/" + node.Name; - if (node.Type != BranchTreeNodeType.Branch && node.IsExpanded) + if (node.IsExpanded) _expanded.Add(path); + CollectExpandedNodes(node.Children, path); } } @@ -191,7 +134,6 @@ namespace SourceGit.ViewModels roots.Add(new BranchTreeNode() { Name = branch.Name, - Type = BranchTreeNodeType.Branch, Backend = branch, IsExpanded = false, IsFiltered = isFiltered, @@ -215,7 +157,6 @@ namespace SourceGit.ViewModels lastFolder = new BranchTreeNode() { Name = name, - Type = BranchTreeNodeType.Folder, IsExpanded = bForceExpanded || branch.IsCurrent || _expanded.Contains(folder), }; roots.Add(lastFolder); @@ -226,7 +167,6 @@ namespace SourceGit.ViewModels var cur = new BranchTreeNode() { Name = name, - Type = BranchTreeNodeType.Folder, IsExpanded = bForceExpanded || branch.IsCurrent || _expanded.Contains(folder), }; lastFolder.Children.Add(cur); @@ -238,10 +178,9 @@ namespace SourceGit.ViewModels sepIdx = branch.Name.IndexOf('/', start); } - lastFolder.Children.Add(new BranchTreeNode() + lastFolder?.Children.Add(new BranchTreeNode() { Name = Path.GetFileName(branch.Name), - Type = branch.IsHead ? BranchTreeNodeType.DetachedHead : BranchTreeNodeType.Branch, Backend = branch, IsExpanded = false, IsFiltered = isFiltered, @@ -252,16 +191,13 @@ namespace SourceGit.ViewModels { nodes.Sort((l, r) => { - if (l.Type == BranchTreeNodeType.DetachedHead) - { + if (l.Backend is Models.Branch { IsHead: true }) return -1; - } - if (l.Type == r.Type) - { - return l.Name.CompareTo(r.Name); - } - return (int)l.Type - (int)r.Type; + if (l.Backend is Models.Branch) + return r.Backend is Models.Branch ? string.Compare(l.Name, r.Name, StringComparison.Ordinal) : 1; + + return r.Backend is Models.Branch ? -1 : string.Compare(l.Name, r.Name, StringComparison.Ordinal); }); foreach (var node in nodes) diff --git a/src/ViewModels/Repository.cs b/src/ViewModels/Repository.cs index 55962b73..67250c4b 100644 --- a/src/ViewModels/Repository.cs +++ b/src/ViewModels/Repository.cs @@ -1905,7 +1905,7 @@ namespace SourceGit.ViewModels visibles.Add(b); } - builder.Run(visibles, remotes, visibles.Count <= 20); + builder.Run(visibles, remotes, true); } return builder; diff --git a/src/Views/BranchTree.axaml b/src/Views/BranchTree.axaml new file mode 100644 index 00000000..3ccadbc9 --- /dev/null +++ b/src/Views/BranchTree.axaml @@ -0,0 +1,110 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Views/BranchTree.axaml.cs b/src/Views/BranchTree.axaml.cs new file mode 100644 index 00000000..deab507c --- /dev/null +++ b/src/Views/BranchTree.axaml.cs @@ -0,0 +1,324 @@ +using System; +using System.Collections.Generic; + +using Avalonia; +using Avalonia.Collections; +using Avalonia.Controls; +using Avalonia.Controls.Primitives; +using Avalonia.Controls.Shapes; +using Avalonia.Input; +using Avalonia.Interactivity; +using Avalonia.Layout; +using Avalonia.Media; +using Avalonia.VisualTree; + +namespace SourceGit.Views +{ + public class BranchTreeNodeIcon : UserControl + { + public static readonly StyledProperty NodeProperty = + AvaloniaProperty.Register(nameof(Node), null); + + public ViewModels.BranchTreeNode Node + { + get => GetValue(NodeProperty); + set => SetValue(NodeProperty, value); + } + + public static readonly StyledProperty IsExpandedProperty = + AvaloniaProperty.Register(nameof(IsExpanded), false); + + public bool IsExpanded + { + get => GetValue(IsExpandedProperty); + set => SetValue(IsExpandedProperty, value); + } + + static BranchTreeNodeIcon() + { + NodeProperty.Changed.AddClassHandler((icon, e) => icon.UpdateContent()); + IsExpandedProperty.Changed.AddClassHandler((icon, e) => icon.UpdateContent()); + } + + private void UpdateContent() + { + var node = Node; + if (node == null) + { + Content = null; + return; + } + + if (node.Backend is Models.Remote) + { + CreateContent(12, new Thickness(0,2,0,0), "Icons.Remote"); + } + else if (node.Backend is Models.Branch branch) + { + if (branch.IsCurrent) + CreateContent(12, new Thickness(0,2,0,0), "Icons.Check"); + else + CreateContent(12, new Thickness(2,0,0,0), "Icons.Branch"); + } + else + { + if (node.IsExpanded) + CreateContent(10, new Thickness(0,2,0,0), "Icons.Folder.Open"); + else + CreateContent(10, new Thickness(0,2,0,0), "Icons.Folder.Fill"); + } + } + + private void CreateContent(double size, Thickness margin, string iconKey) + { + var geo = this.FindResource(iconKey) as StreamGeometry; + if (geo == null) + return; + + Content = new Path() + { + Width = size, + Height = size, + HorizontalAlignment = HorizontalAlignment.Left, + VerticalAlignment = VerticalAlignment.Center, + Margin = margin, + Data = geo, + }; + } + } + + public partial class BranchTree : UserControl + { + public static readonly StyledProperty> NodesProperty = + AvaloniaProperty.Register>(nameof(Nodes), null); + + public List Nodes + { + get => GetValue(NodesProperty); + set => SetValue(NodesProperty, value); + } + + public AvaloniaList Rows + { + get; + private set; + } = new AvaloniaList(); + + public static readonly RoutedEvent SelectionChangedEvent = + RoutedEvent.Register(nameof(SelectionChanged), RoutingStrategies.Tunnel | RoutingStrategies.Bubble); + + public event EventHandler SelectionChanged + { + add { AddHandler(SelectionChangedEvent, value); } + remove { RemoveHandler(SelectionChangedEvent, value); } + } + + public BranchTree() + { + InitializeComponent(); + } + + public void UnselectAll() + { + BranchesPresenter.SelectedItem = null; + } + + protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change) + { + base.OnPropertyChanged(change); + + if (change.Property == NodesProperty) + { + Rows.Clear(); + + if (Nodes is { Count: > 0 }) + { + var rows = new List(); + MakeRows(rows, Nodes, 0); + Rows.AddRange(rows); + } + + var repo = this.FindAncestorOfType(); + repo?.UpdateLeftSidebarLayout(); + } + else if (change.Property == IsVisibleProperty) + { + var repo = this.FindAncestorOfType(); + repo?.UpdateLeftSidebarLayout(); + } + } + + private void OnNodesSelectionChanged(object sender, SelectionChangedEventArgs e) + { + var selected = BranchesPresenter.SelectedItems; + if (selected == null || selected.Count == 0) + return; + + var set = new HashSet(); + foreach (var item in selected) + { + if (item is ViewModels.BranchTreeNode node) + set.Add(node); + } + + var prev = null as ViewModels.BranchTreeNode; + var isPrevSelected = false; + foreach (var row in Rows) + { + var isSelected = set.Contains(row); + if (isSelected) + { + if (isPrevSelected) + { + var prevTop = prev.CornerRadius.TopLeft; + prev.CornerRadius = new CornerRadius(prevTop, 0); + row.CornerRadius = new CornerRadius(0, 4); + } + else + { + row.CornerRadius = new CornerRadius(4); + } + } + + isPrevSelected = isSelected; + prev = row; + } + + RaiseEvent(new RoutedEventArgs(SelectionChangedEvent)); + } + + private void OnTreeContextRequested(object sender, ContextRequestedEventArgs e) + { + var repo = DataContext as ViewModels.Repository; + if (repo?.Settings == null) + return; + + var selected = BranchesPresenter.SelectedItems; + if (selected == null || selected.Count == 0) + return; + + if (selected.Count == 1 && selected[0] is ViewModels.BranchTreeNode { Backend: Models.Remote remote }) + { + var menu = repo.CreateContextMenuForRemote(remote); + this.OpenContextMenu(menu); + return; + } + + var branches = new List(); + foreach (var item in selected) + { + if (item is ViewModels.BranchTreeNode node) + CollectBranchesInNode(branches, node); + } + + if (branches.Count == 1) + { + var branch = branches[0]; + var menu = branch.IsLocal ? + repo.CreateContextMenuForLocalBranch(branch) : + repo.CreateContextMenuForRemoteBranch(branch); + this.OpenContextMenu(menu); + } + else if (branches.Find(x => x.IsCurrent) == null) + { + var menu = new ContextMenu(); + var deleteMulti = new MenuItem(); + deleteMulti.Header = App.Text("BranchCM.DeleteMultiBranches", branches.Count); + deleteMulti.Icon = App.CreateMenuIcon("Icons.Clear"); + deleteMulti.Click += (_, ev) => + { + repo.DeleteMultipleBranches(branches, branches[0].IsLocal); + ev.Handled = true; + }; + menu.Items.Add(deleteMulti); + this.OpenContextMenu(menu); + } + } + + private void OnDoubleTappedBranchNode(object sender, TappedEventArgs e) + { + if (sender is Grid { DataContext: ViewModels.BranchTreeNode node }) + { + if (node.Backend is Models.Branch branch) + { + if (branch.IsCurrent) + return; + + if (DataContext is ViewModels.Repository { Settings: not null } repo) + repo.CheckoutBranch(branch); + } + else + { + node.IsExpanded = !node.IsExpanded; + + var rows = Rows; + var depth = node.Depth; + var idx = rows.IndexOf(node); + if (idx == -1) + return; + + if (node.IsExpanded) + { + var subtree = new List(); + MakeRows(subtree, node.Children, depth + 1); + rows.InsertRange(idx + 1, subtree); + } + else + { + var removeCount = 0; + for (int i = idx + 1; i < rows.Count; i++) + { + var row = rows[i]; + if (row.Depth <= depth) + break; + + removeCount++; + } + rows.RemoveRange(idx + 1, removeCount); + } + + var repo = this.FindAncestorOfType(); + repo?.UpdateLeftSidebarLayout(); + } + } + } + + private void OnToggleFilter(object sender, RoutedEventArgs e) + { + if (sender is ToggleButton toggle && DataContext is ViewModels.Repository repo) + { + if (toggle.DataContext is ViewModels.BranchTreeNode { Backend: Models.Branch branch }) + repo.UpdateFilter(branch.FullName, toggle.IsChecked == true); + } + + e.Handled = true; + } + + private void MakeRows(List rows, List nodes, int depth) + { + foreach (var node in nodes) + { + node.Depth = depth; + rows.Add(node); + + if (!node.IsExpanded || node.Backend is Models.Branch) + continue; + + MakeRows(rows, node.Children, depth + 1); + } + } + + private void CollectBranchesInNode(List outs, ViewModels.BranchTreeNode node) + { + if (node.Backend is Models.Branch branch && !outs.Contains(branch)) + { + outs.Add(branch); + return; + } + + foreach (var sub in node.Children) + CollectBranchesInNode(outs, sub); + } + } +} + diff --git a/src/Views/DeleteBranch.axaml b/src/Views/DeleteBranch.axaml index 07230de9..b2693bf0 100644 --- a/src/Views/DeleteBranch.axaml +++ b/src/Views/DeleteBranch.axaml @@ -2,10 +2,7 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" - xmlns:m="using:SourceGit.Models" xmlns:vm="using:SourceGit.ViewModels" - xmlns:v="using:SourceGit.Views" - xmlns:c="using:SourceGit.Converters" mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" x:Class="SourceGit.Views.DeleteBranch" x:DataType="vm:DeleteBranch"> @@ -18,7 +15,7 @@ - + diff --git a/src/Views/Repository.axaml b/src/Views/Repository.axaml index 2db2f166..2ffcc485 100644 --- a/src/Views/Repository.axaml +++ b/src/Views/Repository.axaml @@ -236,77 +236,12 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + @@ -317,64 +252,12 @@ - - - - - - - - - - - - - - - - - - - - - - - - + @@ -455,8 +338,8 @@ diff --git a/src/Views/Repository.axaml.cs b/src/Views/Repository.axaml.cs index 9046a375..f5612d70 100644 --- a/src/Views/Repository.axaml.cs +++ b/src/Views/Repository.axaml.cs @@ -1,13 +1,10 @@ using System; -using System.Collections.Generic; using Avalonia; using Avalonia.Controls; using Avalonia.Controls.Primitives; using Avalonia.Input; using Avalonia.Interactivity; -using Avalonia.VisualTree; -using AvaloniaEdit.Utils; namespace SourceGit.Views { @@ -18,393 +15,7 @@ namespace SourceGit.Views InitializeComponent(); } - protected override void OnLoaded(RoutedEventArgs e) - { - base.OnLoaded(e); - - if (DataContext is ViewModels.Repository repo && !repo.IsSearching) - { - UpdateLeftSidebarLayout(); - } - } - - private void OpenWithExternalTools(object sender, RoutedEventArgs e) - { - if (sender is Button button && DataContext is ViewModels.Repository repo) - { - var menu = repo.CreateContextMenuForExternalTools(); - button.OpenContextMenu(menu); - e.Handled = true; - } - } - - private void OpenGitFlowMenu(object sender, RoutedEventArgs e) - { - if (DataContext is ViewModels.Repository repo) - { - var menu = repo.CreateContextMenuForGitFlow(); - (sender as Control)?.OpenContextMenu(menu); - } - - e.Handled = true; - } - - private void OpenGitLFSMenu(object sender, RoutedEventArgs e) - { - if (DataContext is ViewModels.Repository repo) - { - var menu = repo.CreateContextMenuForGitLFS(); - (sender as Control)?.OpenContextMenu(menu); - } - - e.Handled = true; - } - - private async void OpenStatistics(object sender, RoutedEventArgs e) - { - if (DataContext is ViewModels.Repository repo) - { - var dialog = new Statistics() { DataContext = new ViewModels.Statistics(repo.FullPath) }; - await dialog.ShowDialog(TopLevel.GetTopLevel(this) as Window); - e.Handled = true; - } - } - - private void OnSearchCommitPanelPropertyChanged(object sender, AvaloniaPropertyChangedEventArgs e) - { - var grid = sender as Grid; - if (e.Property == IsVisibleProperty && grid.IsVisible) - txtSearchCommitsBox.Focus(); - } - - private void OnSearchKeyDown(object sender, KeyEventArgs e) - { - if (e.Key == Key.Enter) - { - if (DataContext is ViewModels.Repository repo) - repo.StartSearchCommits(); - - e.Handled = true; - } - } - - private void OnSearchResultDataGridSelectionChanged(object sender, SelectionChangedEventArgs e) - { - if (sender is DataGrid datagrid && datagrid.SelectedItem != null) - { - if (DataContext is ViewModels.Repository repo) - { - var commit = datagrid.SelectedItem as Models.Commit; - repo.NavigateToCommit(commit.SHA); - } - } - e.Handled = true; - } - - private void OnLocalBranchTreeSelectionChanged(object sender, SelectionChangedEventArgs e) - { - if (sender is TreeView tree && tree.SelectedItem != null && DataContext is ViewModels.Repository repo) - { - remoteBranchTree.UnselectAll(); - tagsList.SelectedItem = null; - - ViewModels.BranchTreeNode prev = null; - foreach (var node in repo.LocalBranchTrees) - node.UpdateCornerRadius(ref prev); - - if (tree.SelectedItems.Count == 1) - { - var node = tree.SelectedItem as ViewModels.BranchTreeNode; - if (node.IsBranch) - repo.NavigateToCommit((node.Backend as Models.Branch).Head); - } - } - } - - private void OnRemoteBranchTreeSelectionChanged(object sender, SelectionChangedEventArgs e) - { - if (sender is TreeView tree && tree.SelectedItem != null && DataContext is ViewModels.Repository repo) - { - localBranchTree.UnselectAll(); - tagsList.SelectedItem = null; - - ViewModels.BranchTreeNode prev = null; - foreach (var node in repo.RemoteBranchTrees) - node.UpdateCornerRadius(ref prev); - - if (tree.SelectedItems.Count == 1) - { - var node = tree.SelectedItem as ViewModels.BranchTreeNode; - if (node.IsBranch) - repo.NavigateToCommit((node.Backend as Models.Branch).Head); - } - } - } - - private void OnLocalBranchContextMenuRequested(object sender, ContextRequestedEventArgs e) - { - remoteBranchTree.UnselectAll(); - tagsList.SelectedItem = null; - - var repo = DataContext as ViewModels.Repository; - var tree = sender as TreeView; - if (tree.SelectedItems.Count == 0) - { - e.Handled = true; - return; - } - - var branches = new List(); - foreach (var item in tree.SelectedItems) - CollectBranchesFromNode(branches, item as ViewModels.BranchTreeNode); - - if (branches.Count == 1) - { - var item = (e.Source as Control)?.FindAncestorOfType(true); - if (item != null) - { - var menu = repo.CreateContextMenuForLocalBranch(branches[0]); - item.OpenContextMenu(menu); - } - } - else if (branches.Count > 1 && branches.Find(x => x.IsCurrent) == null) - { - var menu = new ContextMenu(); - var deleteMulti = new MenuItem(); - deleteMulti.Header = App.Text("BranchCM.DeleteMultiBranches", branches.Count); - deleteMulti.Icon = App.CreateMenuIcon("Icons.Clear"); - deleteMulti.Click += (_, ev) => - { - repo.DeleteMultipleBranches(branches, true); - ev.Handled = true; - }; - menu.Items.Add(deleteMulti); - tree.OpenContextMenu(menu); - } - - e.Handled = true; - } - - private void OnRemoteBranchContextMenuRequested(object sender, ContextRequestedEventArgs e) - { - localBranchTree.UnselectAll(); - tagsList.SelectedItem = null; - - var repo = DataContext as ViewModels.Repository; - var tree = sender as TreeView; - if (tree.SelectedItems.Count == 0) - { - e.Handled = true; - return; - } - - if (tree.SelectedItems.Count == 1) - { - var node = tree.SelectedItem as ViewModels.BranchTreeNode; - if (node != null && node.IsRemote) - { - var item = (e.Source as Control)?.FindAncestorOfType(true); - if (item != null && item.DataContext == node) - { - var menu = repo.CreateContextMenuForRemote(node.Backend as Models.Remote); - item.OpenContextMenu(menu); - } - - e.Handled = true; - return; - } - } - - var branches = new List(); - foreach (var item in tree.SelectedItems) - CollectBranchesFromNode(branches, item as ViewModels.BranchTreeNode); - - if (branches.Count == 1) - { - var item = (e.Source as Control)?.FindAncestorOfType(true); - if (item != null) - { - var menu = repo.CreateContextMenuForRemoteBranch(branches[0]); - item.OpenContextMenu(menu); - } - } - else if (branches.Count > 1) - { - var menu = new ContextMenu(); - var deleteMulti = new MenuItem(); - deleteMulti.Header = App.Text("BranchCM.DeleteMultiBranches", branches.Count); - deleteMulti.Icon = App.CreateMenuIcon("Icons.Clear"); - deleteMulti.Click += (_, ev) => - { - repo.DeleteMultipleBranches(branches, false); - ev.Handled = true; - }; - menu.Items.Add(deleteMulti); - tree.OpenContextMenu(menu); - } - - e.Handled = true; - } - - private void OnDoubleTappedBranchNode(object sender, TappedEventArgs e) - { - if (!ViewModels.PopupHost.CanCreatePopup()) - return; - - if (sender is Grid grid && DataContext is ViewModels.Repository repo) - { - var node = grid.DataContext as ViewModels.BranchTreeNode; - if (node == null) - return; - - if (node.IsBranch) - { - var branch = node.Backend as Models.Branch; - if (branch.IsCurrent) - return; - - repo.CheckoutBranch(branch); - } - else - { - node.IsExpanded = !node.IsExpanded; - UpdateLeftSidebarLayout(); - } - - e.Handled = true; - } - } - - private void OnTagDataGridSelectionChanged(object sender, SelectionChangedEventArgs e) - { - if (sender is DataGrid datagrid && datagrid.SelectedItem != null) - { - localBranchTree.UnselectAll(); - remoteBranchTree.UnselectAll(); - - var tag = datagrid.SelectedItem as Models.Tag; - if (DataContext is ViewModels.Repository repo) - repo.NavigateToCommit(tag.SHA); - } - } - - private void OnTagContextRequested(object sender, ContextRequestedEventArgs e) - { - if (sender is DataGrid datagrid && datagrid.SelectedItem != null && DataContext is ViewModels.Repository repo) - { - var tag = datagrid.SelectedItem as Models.Tag; - var menu = repo.CreateContextMenuForTag(tag); - datagrid.OpenContextMenu(menu); - } - - e.Handled = true; - } - - private void OnToggleFilter(object sender, RoutedEventArgs e) - { - if (sender is ToggleButton toggle) - { - var filter = string.Empty; - if (toggle.DataContext is ViewModels.BranchTreeNode node) - { - if (node.IsBranch) - filter = (node.Backend as Models.Branch).FullName; - } - else if (toggle.DataContext is Models.Tag tag) - { - filter = tag.Name; - } - - if (!string.IsNullOrEmpty(filter) && DataContext is ViewModels.Repository repo) - { - repo.UpdateFilter(filter, toggle.IsChecked == true); - } - } - - e.Handled = true; - } - - private void OnSubmoduleContextRequested(object sender, ContextRequestedEventArgs e) - { - if (sender is DataGrid datagrid && datagrid.SelectedItem != null && DataContext is ViewModels.Repository repo) - { - var submodule = datagrid.SelectedItem as string; - var menu = repo.CreateContextMenuForSubmodule(submodule); - datagrid.OpenContextMenu(menu); - } - - e.Handled = true; - } - - private void OnDoubleTappedSubmodule(object sender, TappedEventArgs e) - { - if (sender is DataGrid datagrid && datagrid.SelectedItem != null && DataContext is ViewModels.Repository repo) - { - var submodule = datagrid.SelectedItem as string; - (DataContext as ViewModels.Repository).OpenSubmodule(submodule); - } - - e.Handled = true; - } - - private void OnWorktreeContextRequested(object sender, ContextRequestedEventArgs e) - { - if (sender is DataGrid datagrid && datagrid.SelectedItem != null && DataContext is ViewModels.Repository repo) - { - var worktree = datagrid.SelectedItem as Models.Worktree; - var menu = repo.CreateContextMenuForWorktree(worktree); - datagrid.OpenContextMenu(menu); - } - - e.Handled = true; - } - - private void OnDoubleTappedWorktree(object sender, TappedEventArgs e) - { - if (sender is DataGrid datagrid && datagrid.SelectedItem != null && DataContext is ViewModels.Repository repo) - { - var worktree = datagrid.SelectedItem as Models.Worktree; - (DataContext as ViewModels.Repository).OpenWorktree(worktree); - } - - e.Handled = true; - } - - private void CollectBranchesFromNode(List outs, ViewModels.BranchTreeNode node) - { - if (node == null || node.IsRemote) - return; - - if (node.IsFolder) - { - foreach (var child in node.Children) - CollectBranchesFromNode(outs, child); - } - else - { - var b = node.Backend as Models.Branch; - if (b != null && !outs.Contains(b)) - outs.Add(b); - } - } - - private void OnLeftSidebarTreeViewPropertyChanged(object sender, AvaloniaPropertyChangedEventArgs e) - { - if (e.Property == TreeView.ItemsSourceProperty || e.Property == TreeView.IsVisibleProperty) - { - UpdateLeftSidebarLayout(); - } - } - - private void OnLeftSidebarDataGridPropertyChanged(object sender, AvaloniaPropertyChangedEventArgs e) - { - if (e.Property == DataGrid.ItemsSourceProperty || e.Property == DataGrid.IsVisibleProperty) - { - UpdateLeftSidebarLayout(); - } - } - - private void UpdateLeftSidebarLayout() + public void UpdateLeftSidebarLayout() { var vm = DataContext as ViewModels.Repository; if (vm == null || vm.Settings == null) @@ -414,8 +25,8 @@ namespace SourceGit.Views return; var leftHeight = leftSidebarGroups.Bounds.Height - 28.0 * 5; - var localBranchRows = vm.IsLocalBranchGroupExpanded ? GetTreeRowsCount(vm.LocalBranchTrees) : 0; - var remoteBranchRows = vm.IsRemoteGroupExpanded ? GetTreeRowsCount(vm.RemoteBranchTrees) : 0; + var localBranchRows = vm.IsLocalBranchGroupExpanded ? localBranchTree.Rows.Count : 0; + var remoteBranchRows = vm.IsRemoteGroupExpanded ? remoteBranchTree.Rows.Count : 0; var desiredBranches = (localBranchRows + remoteBranchRows) * 24.0; var desiredTag = vm.IsTagGroupExpanded ? tagsList.RowHeight * vm.VisibleTags.Count : 0; var desiredSubmodule = vm.IsSubmoduleGroupExpanded ? submoduleList.RowHeight * vm.Submodules.Count : 0; @@ -524,17 +135,212 @@ namespace SourceGit.Views } } - private int GetTreeRowsCount(List nodes) + protected override void OnLoaded(RoutedEventArgs e) { - int count = nodes.Count; + base.OnLoaded(e); - foreach (var node in nodes) + if (DataContext is ViewModels.Repository { IsSearching: false }) + UpdateLeftSidebarLayout(); + } + + private void OpenWithExternalTools(object sender, RoutedEventArgs e) + { + if (sender is Button button && DataContext is ViewModels.Repository repo) { - if (!node.IsBranch && node.IsExpanded) - count += GetTreeRowsCount(node.Children); + var menu = repo.CreateContextMenuForExternalTools(); + button.OpenContextMenu(menu); + e.Handled = true; } + } + + private void OpenGitFlowMenu(object sender, RoutedEventArgs e) + { + if (DataContext is ViewModels.Repository repo) + { + var menu = repo.CreateContextMenuForGitFlow(); + (sender as Control)?.OpenContextMenu(menu); + } + + e.Handled = true; + } + + private void OpenGitLFSMenu(object sender, RoutedEventArgs e) + { + if (DataContext is ViewModels.Repository repo) + { + var menu = repo.CreateContextMenuForGitLFS(); + (sender as Control)?.OpenContextMenu(menu); + } + + e.Handled = true; + } + + private async void OpenStatistics(object sender, RoutedEventArgs e) + { + if (DataContext is ViewModels.Repository repo && TopLevel.GetTopLevel(this) is Window owner) + { + var dialog = new Statistics() { DataContext = new ViewModels.Statistics(repo.FullPath) }; + await dialog.ShowDialog(owner); + e.Handled = true; + } + } + + private void OnSearchCommitPanelPropertyChanged(object sender, AvaloniaPropertyChangedEventArgs e) + { + if (e.Property == IsVisibleProperty && sender is Grid { IsVisible: true} grid) + txtSearchCommitsBox.Focus(); + } + + private void OnSearchKeyDown(object sender, KeyEventArgs e) + { + if (e.Key == Key.Enter) + { + if (DataContext is ViewModels.Repository repo) + repo.StartSearchCommits(); + + e.Handled = true; + } + } + + private void OnSearchResultDataGridSelectionChanged(object sender, SelectionChangedEventArgs e) + { + if (sender is DataGrid { SelectedItem: not null } grid && DataContext is ViewModels.Repository repo) + { + var commit = grid.SelectedItem as Models.Commit; + repo.NavigateToCommit(commit.SHA); + } + + e.Handled = true; + } + + private void OnLocalBranchTreeSelectionChanged(object sender, RoutedEventArgs e) + { + if (sender is BranchTree tree && DataContext is ViewModels.Repository repo) + { + var selected = tree.BranchesPresenter.SelectedItems; + if (selected == null || selected.Count == 0) + return; - return count; + remoteBranchTree.UnselectAll(); + tagsList.SelectedItem = null; + + if (selected.Count == 1 && selected[0] is ViewModels.BranchTreeNode { Backend: Models.Branch branch }) + repo.NavigateToCommit(branch.Head); + } + } + + private void OnRemoteBranchTreeSelectionChanged(object sender, RoutedEventArgs e) + { + if (sender is BranchTree tree && DataContext is ViewModels.Repository repo) + { + var selected = tree.BranchesPresenter.SelectedItems; + if (selected == null || selected.Count == 0) + return; + + localBranchTree.UnselectAll(); + tagsList.SelectedItem = null; + + if (selected.Count == 1 && selected[0] is ViewModels.BranchTreeNode { Backend: Models.Branch branch }) + repo.NavigateToCommit(branch.Head); + } + } + + private void OnTagDataGridSelectionChanged(object sender, SelectionChangedEventArgs e) + { + if (sender is DataGrid { SelectedItem: not null } grid) + { + localBranchTree.UnselectAll(); + remoteBranchTree.UnselectAll(); + + var tag = grid.SelectedItem as Models.Tag; + if (DataContext is ViewModels.Repository repo) + repo.NavigateToCommit(tag.SHA); + } + } + + private void OnTagContextRequested(object sender, ContextRequestedEventArgs e) + { + if (sender is DataGrid datagrid && datagrid.SelectedItem != null && DataContext is ViewModels.Repository repo) + { + var tag = datagrid.SelectedItem as Models.Tag; + var menu = repo.CreateContextMenuForTag(tag); + datagrid.OpenContextMenu(menu); + } + + e.Handled = true; + } + + private void OnToggleTagFilter(object sender, RoutedEventArgs e) + { + if (sender is ToggleButton toggle) + { + var filter = string.Empty; + if (toggle.DataContext is Models.Tag tag) + { + filter = tag.Name; + } + + if (!string.IsNullOrEmpty(filter) && DataContext is ViewModels.Repository repo) + { + repo.UpdateFilter(filter, toggle.IsChecked == true); + } + } + + e.Handled = true; + } + + private void OnSubmoduleContextRequested(object sender, ContextRequestedEventArgs e) + { + if (sender is DataGrid datagrid && datagrid.SelectedItem != null && DataContext is ViewModels.Repository repo) + { + var submodule = datagrid.SelectedItem as string; + var menu = repo.CreateContextMenuForSubmodule(submodule); + datagrid.OpenContextMenu(menu); + } + + e.Handled = true; + } + + private void OnDoubleTappedSubmodule(object sender, TappedEventArgs e) + { + if (sender is DataGrid { SelectedItem: not null } grid && DataContext is ViewModels.Repository repo) + { + var submodule = grid.SelectedItem as string; + repo.OpenSubmodule(submodule); + } + + e.Handled = true; + } + + private void OnWorktreeContextRequested(object sender, ContextRequestedEventArgs e) + { + if (sender is DataGrid { SelectedItem: not null } grid && DataContext is ViewModels.Repository repo) + { + var worktree = grid.SelectedItem as Models.Worktree; + var menu = repo.CreateContextMenuForWorktree(worktree); + grid.OpenContextMenu(menu); + } + + e.Handled = true; + } + + private void OnDoubleTappedWorktree(object sender, TappedEventArgs e) + { + if (sender is DataGrid { SelectedItem: not null } grid && DataContext is ViewModels.Repository repo) + { + var worktree = grid.SelectedItem as Models.Worktree; + repo.OpenWorktree(worktree); + } + + e.Handled = true; + } + + private void OnLeftSidebarDataGridPropertyChanged(object sender, AvaloniaPropertyChangedEventArgs e) + { + if (e.Property == DataGrid.ItemsSourceProperty || e.Property == DataGrid.IsVisibleProperty) + { + UpdateLeftSidebarLayout(); + } } } } From 36b8472d023b3e6c3bc9f2739dbb091690ada4d9 Mon Sep 17 00:00:00 2001 From: leo Date: Sat, 6 Jul 2024 17:29:24 +0800 Subject: [PATCH 2/7] enhance: improve selection changed event handler performance --- src/ViewModels/BranchTreeNode.cs | 1 + src/Views/BranchTree.axaml.cs | 18 ++++++++++-------- 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/src/ViewModels/BranchTreeNode.cs b/src/ViewModels/BranchTreeNode.cs index 63848b9f..a29e6a2c 100644 --- a/src/ViewModels/BranchTreeNode.cs +++ b/src/ViewModels/BranchTreeNode.cs @@ -16,6 +16,7 @@ namespace SourceGit.ViewModels public object Backend { get; private set; } = null; public int Depth { get; set; } = 0; public bool IsFiltered { get; set; } = false; + public bool IsSelected { get; set; } = false; public List Children { get; private set; } = new List(); public bool IsExpanded diff --git a/src/Views/BranchTree.axaml.cs b/src/Views/BranchTree.axaml.cs index deab507c..4fe4fce5 100644 --- a/src/Views/BranchTree.axaml.cs +++ b/src/Views/BranchTree.axaml.cs @@ -154,21 +154,24 @@ namespace SourceGit.Views if (selected == null || selected.Count == 0) return; - var set = new HashSet(); - foreach (var item in selected) + foreach (var item in e.AddedItems) { if (item is ViewModels.BranchTreeNode node) - set.Add(node); + node.IsSelected = true; + } + + foreach (var item in e.RemovedItems) + { + if (item is ViewModels.BranchTreeNode node) + node.IsSelected = false; } var prev = null as ViewModels.BranchTreeNode; - var isPrevSelected = false; foreach (var row in Rows) { - var isSelected = set.Contains(row); - if (isSelected) + if (row.IsSelected) { - if (isPrevSelected) + if (prev is { IsSelected: true }) { var prevTop = prev.CornerRadius.TopLeft; prev.CornerRadius = new CornerRadius(prevTop, 0); @@ -180,7 +183,6 @@ namespace SourceGit.Views } } - isPrevSelected = isSelected; prev = row; } From b03ee19e5481f6c7f94dcf2a58c7f248cde9c5d3 Mon Sep 17 00:00:00 2001 From: leo Date: Sat, 6 Jul 2024 20:46:26 +0800 Subject: [PATCH 3/7] refactor: trigger nagivation in BranchTree --- src/Views/BranchTree.axaml.cs | 30 ++++++++++------- src/Views/Repository.axaml.cs | 61 +++++++++-------------------------- 2 files changed, 33 insertions(+), 58 deletions(-) diff --git a/src/Views/BranchTree.axaml.cs b/src/Views/BranchTree.axaml.cs index 4fe4fce5..cb0d1c58 100644 --- a/src/Views/BranchTree.axaml.cs +++ b/src/Views/BranchTree.axaml.cs @@ -17,7 +17,7 @@ namespace SourceGit.Views public class BranchTreeNodeIcon : UserControl { public static readonly StyledProperty NodeProperty = - AvaloniaProperty.Register(nameof(Node), null); + AvaloniaProperty.Register(nameof(Node)); public ViewModels.BranchTreeNode Node { @@ -26,7 +26,7 @@ namespace SourceGit.Views } public static readonly StyledProperty IsExpandedProperty = - AvaloniaProperty.Register(nameof(IsExpanded), false); + AvaloniaProperty.Register(nameof(IsExpanded)); public bool IsExpanded { @@ -36,8 +36,8 @@ namespace SourceGit.Views static BranchTreeNodeIcon() { - NodeProperty.Changed.AddClassHandler((icon, e) => icon.UpdateContent()); - IsExpandedProperty.Changed.AddClassHandler((icon, e) => icon.UpdateContent()); + NodeProperty.Changed.AddClassHandler((icon, _) => icon.UpdateContent()); + IsExpandedProperty.Changed.AddClassHandler((icon, _) => icon.UpdateContent()); } private void UpdateContent() @@ -90,7 +90,7 @@ namespace SourceGit.Views public partial class BranchTree : UserControl { public static readonly StyledProperty> NodesProperty = - AvaloniaProperty.Register>(nameof(Nodes), null); + AvaloniaProperty.Register>(nameof(Nodes)); public List Nodes { @@ -148,12 +148,8 @@ namespace SourceGit.Views } } - private void OnNodesSelectionChanged(object sender, SelectionChangedEventArgs e) + private void OnNodesSelectionChanged(object _, SelectionChangedEventArgs e) { - var selected = BranchesPresenter.SelectedItems; - if (selected == null || selected.Count == 0) - return; - foreach (var item in e.AddedItems) { if (item is ViewModels.BranchTreeNode node) @@ -165,6 +161,16 @@ namespace SourceGit.Views if (item is ViewModels.BranchTreeNode node) node.IsSelected = false; } + + var selected = BranchesPresenter.SelectedItems; + if (selected == null || selected.Count == 0) + return; + + if (selected.Count == 1 && selected[0] is ViewModels.BranchTreeNode { Backend: Models.Branch branch }) + { + var repo = DataContext as ViewModels.Repository; + repo?.NavigateToCommit(branch.Head); + } var prev = null as ViewModels.BranchTreeNode; foreach (var row in Rows) @@ -189,7 +195,7 @@ namespace SourceGit.Views RaiseEvent(new RoutedEventArgs(SelectionChangedEvent)); } - private void OnTreeContextRequested(object sender, ContextRequestedEventArgs e) + private void OnTreeContextRequested(object _1, ContextRequestedEventArgs _2) { var repo = DataContext as ViewModels.Repository; if (repo?.Settings == null) @@ -237,7 +243,7 @@ namespace SourceGit.Views } } - private void OnDoubleTappedBranchNode(object sender, TappedEventArgs e) + private void OnDoubleTappedBranchNode(object sender, TappedEventArgs _) { if (sender is Grid { DataContext: ViewModels.BranchTreeNode node }) { diff --git a/src/Views/Repository.axaml.cs b/src/Views/Repository.axaml.cs index f5612d70..e2ffc0d6 100644 --- a/src/Views/Repository.axaml.cs +++ b/src/Views/Repository.axaml.cs @@ -175,7 +175,7 @@ namespace SourceGit.Views e.Handled = true; } - private async void OpenStatistics(object sender, RoutedEventArgs e) + private async void OpenStatistics(object _, RoutedEventArgs e) { if (DataContext is ViewModels.Repository repo && TopLevel.GetTopLevel(this) is Window owner) { @@ -187,11 +187,11 @@ namespace SourceGit.Views private void OnSearchCommitPanelPropertyChanged(object sender, AvaloniaPropertyChangedEventArgs e) { - if (e.Property == IsVisibleProperty && sender is Grid { IsVisible: true} grid) + if (e.Property == IsVisibleProperty && sender is Grid { IsVisible: true}) txtSearchCommitsBox.Focus(); } - private void OnSearchKeyDown(object sender, KeyEventArgs e) + private void OnSearchKeyDown(object _, KeyEventArgs e) { if (e.Key == Key.Enter) { @@ -204,55 +204,33 @@ namespace SourceGit.Views private void OnSearchResultDataGridSelectionChanged(object sender, SelectionChangedEventArgs e) { - if (sender is DataGrid { SelectedItem: not null } grid && DataContext is ViewModels.Repository repo) + if (sender is DataGrid { SelectedItem: Models.Commit commit } && DataContext is ViewModels.Repository repo) { - var commit = grid.SelectedItem as Models.Commit; repo.NavigateToCommit(commit.SHA); } e.Handled = true; } - private void OnLocalBranchTreeSelectionChanged(object sender, RoutedEventArgs e) + private void OnLocalBranchTreeSelectionChanged(object _1, RoutedEventArgs _2) { - if (sender is BranchTree tree && DataContext is ViewModels.Repository repo) - { - var selected = tree.BranchesPresenter.SelectedItems; - if (selected == null || selected.Count == 0) - return; - - remoteBranchTree.UnselectAll(); - tagsList.SelectedItem = null; - - if (selected.Count == 1 && selected[0] is ViewModels.BranchTreeNode { Backend: Models.Branch branch }) - repo.NavigateToCommit(branch.Head); - } + remoteBranchTree.UnselectAll(); + tagsList.SelectedItem = null; } - private void OnRemoteBranchTreeSelectionChanged(object sender, RoutedEventArgs e) + private void OnRemoteBranchTreeSelectionChanged(object _1, RoutedEventArgs _2) { - if (sender is BranchTree tree && DataContext is ViewModels.Repository repo) - { - var selected = tree.BranchesPresenter.SelectedItems; - if (selected == null || selected.Count == 0) - return; - - localBranchTree.UnselectAll(); - tagsList.SelectedItem = null; - - if (selected.Count == 1 && selected[0] is ViewModels.BranchTreeNode { Backend: Models.Branch branch }) - repo.NavigateToCommit(branch.Head); - } + localBranchTree.UnselectAll(); + tagsList.SelectedItem = null; } - private void OnTagDataGridSelectionChanged(object sender, SelectionChangedEventArgs e) + private void OnTagDataGridSelectionChanged(object sender, SelectionChangedEventArgs _) { - if (sender is DataGrid { SelectedItem: not null } grid) + if (sender is DataGrid { SelectedItem: Models.Tag tag }) { localBranchTree.UnselectAll(); remoteBranchTree.UnselectAll(); - var tag = grid.SelectedItem as Models.Tag; if (DataContext is ViewModels.Repository repo) repo.NavigateToCommit(tag.SHA); } @@ -272,18 +250,9 @@ namespace SourceGit.Views private void OnToggleTagFilter(object sender, RoutedEventArgs e) { - if (sender is ToggleButton toggle) + if (sender is ToggleButton { DataContext: Models.Tag tag } toggle && DataContext is ViewModels.Repository repo) { - var filter = string.Empty; - if (toggle.DataContext is Models.Tag tag) - { - filter = tag.Name; - } - - if (!string.IsNullOrEmpty(filter) && DataContext is ViewModels.Repository repo) - { - repo.UpdateFilter(filter, toggle.IsChecked == true); - } + repo.UpdateFilter(tag.Name, toggle.IsChecked == true); } e.Handled = true; @@ -335,7 +304,7 @@ namespace SourceGit.Views e.Handled = true; } - private void OnLeftSidebarDataGridPropertyChanged(object sender, AvaloniaPropertyChangedEventArgs e) + private void OnLeftSidebarDataGridPropertyChanged(object _, AvaloniaPropertyChangedEventArgs e) { if (e.Property == DataGrid.ItemsSourceProperty || e.Property == DataGrid.IsVisibleProperty) { From 294f856150d69fbc11b5d010f518b1e4e68489e4 Mon Sep 17 00:00:00 2001 From: leo Date: Sat, 6 Jul 2024 23:50:54 +0800 Subject: [PATCH 4/7] fix: bounds not updated after left side bar layout updated --- src/Views/BranchTree.axaml | 32 ++--- src/Views/BranchTree.axaml.cs | 35 +++-- src/Views/Repository.axaml | 6 +- src/Views/Repository.axaml.cs | 254 +++++++++++++++++----------------- 4 files changed, 174 insertions(+), 153 deletions(-) diff --git a/src/Views/BranchTree.axaml b/src/Views/BranchTree.axaml index 3ccadbc9..66eca538 100644 --- a/src/Views/BranchTree.axaml +++ b/src/Views/BranchTree.axaml @@ -10,13 +10,11 @@ x:Name="ThisControl"> - + - + - - + - + - diff --git a/src/Views/BranchTree.axaml.cs b/src/Views/BranchTree.axaml.cs index cb0d1c58..95e70f48 100644 --- a/src/Views/BranchTree.axaml.cs +++ b/src/Views/BranchTree.axaml.cs @@ -113,6 +113,15 @@ namespace SourceGit.Views remove { RemoveHandler(SelectionChangedEvent, value); } } + public static readonly RoutedEvent RowsChangedEvent = + RoutedEvent.Register(nameof(RowsChanged), RoutingStrategies.Tunnel | RoutingStrategies.Bubble); + + public event EventHandler RowsChanged + { + add { AddHandler(RowsChangedEvent, value); } + remove { RemoveHandler(RowsChangedEvent, value); } + } + public BranchTree() { InitializeComponent(); @@ -123,6 +132,14 @@ namespace SourceGit.Views BranchesPresenter.SelectedItem = null; } + protected override void OnSizeChanged(SizeChangedEventArgs e) + { + base.OnSizeChanged(e); + + if (Bounds.Height >= 23.0) + BranchesPresenter.Height = Bounds.Height; + } + protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change) { base.OnPropertyChanged(change); @@ -138,18 +155,20 @@ namespace SourceGit.Views Rows.AddRange(rows); } - var repo = this.FindAncestorOfType(); - repo?.UpdateLeftSidebarLayout(); + RaiseEvent(new RoutedEventArgs(RowsChangedEvent)); } else if (change.Property == IsVisibleProperty) { - var repo = this.FindAncestorOfType(); - repo?.UpdateLeftSidebarLayout(); + RaiseEvent(new RoutedEventArgs(RowsChangedEvent)); } } private void OnNodesSelectionChanged(object _, SelectionChangedEventArgs e) { + var repo = DataContext as ViewModels.Repository; + if (repo?.Settings == null) + return; + foreach (var item in e.AddedItems) { if (item is ViewModels.BranchTreeNode node) @@ -167,10 +186,7 @@ namespace SourceGit.Views return; if (selected.Count == 1 && selected[0] is ViewModels.BranchTreeNode { Backend: Models.Branch branch }) - { - var repo = DataContext as ViewModels.Repository; - repo?.NavigateToCommit(branch.Head); - } + repo.NavigateToCommit(branch.Head); var prev = null as ViewModels.BranchTreeNode; foreach (var row in Rows) @@ -285,8 +301,7 @@ namespace SourceGit.Views rows.RemoveRange(idx + 1, removeCount); } - var repo = this.FindAncestorOfType(); - repo?.UpdateLeftSidebarLayout(); + RaiseEvent(new RoutedEventArgs(RowsChangedEvent)); } } } diff --git a/src/Views/Repository.axaml b/src/Views/Repository.axaml index 2ffcc485..2c3ecbe2 100644 --- a/src/Views/Repository.axaml +++ b/src/Views/Repository.axaml @@ -241,7 +241,8 @@ Margin="8,0,4,0" Nodes="{Binding LocalBranchTrees}" IsVisible="{Binding IsLocalBranchGroupExpanded}" - SelectionChanged="OnLocalBranchTreeSelectionChanged"/> + SelectionChanged="OnLocalBranchTreeSelectionChanged" + RowsChanged="OnBranchTreeRowsChanged"/> @@ -257,7 +258,8 @@ Margin="8,0,4,0" Nodes="{Binding RemoteBranchTrees}" IsVisible="{Binding IsRemoteGroupExpanded}" - SelectionChanged="OnRemoteBranchTreeSelectionChanged"/> + SelectionChanged="OnRemoteBranchTreeSelectionChanged" + RowsChanged="OnBranchTreeRowsChanged"/> diff --git a/src/Views/Repository.axaml.cs b/src/Views/Repository.axaml.cs index e2ffc0d6..0e3d7a19 100644 --- a/src/Views/Repository.axaml.cs +++ b/src/Views/Repository.axaml.cs @@ -15,132 +15,10 @@ namespace SourceGit.Views InitializeComponent(); } - public void UpdateLeftSidebarLayout() - { - var vm = DataContext as ViewModels.Repository; - if (vm == null || vm.Settings == null) - return; - - if (!IsLoaded) - return; - - var leftHeight = leftSidebarGroups.Bounds.Height - 28.0 * 5; - var localBranchRows = vm.IsLocalBranchGroupExpanded ? localBranchTree.Rows.Count : 0; - var remoteBranchRows = vm.IsRemoteGroupExpanded ? remoteBranchTree.Rows.Count : 0; - var desiredBranches = (localBranchRows + remoteBranchRows) * 24.0; - var desiredTag = vm.IsTagGroupExpanded ? tagsList.RowHeight * vm.VisibleTags.Count : 0; - var desiredSubmodule = vm.IsSubmoduleGroupExpanded ? submoduleList.RowHeight * vm.Submodules.Count : 0; - var desiredWorktree = vm.IsWorktreeGroupExpanded ? worktreeList.RowHeight * vm.Worktrees.Count : 0; - var desiredOthers = desiredTag + desiredSubmodule + desiredWorktree; - var hasOverflow = (desiredBranches + desiredOthers > leftHeight); - - if (vm.IsTagGroupExpanded) - { - var height = desiredTag; - if (hasOverflow) - { - var test = leftHeight - desiredBranches - desiredSubmodule - desiredWorktree; - if (test < 0) - height = Math.Min(200, height); - else - height = Math.Max(200, test); - } - - leftHeight -= height; - tagsList.Height = height; - hasOverflow = (desiredBranches + desiredSubmodule + desiredWorktree) > leftHeight; - } - - if (vm.IsSubmoduleGroupExpanded) - { - var height = desiredSubmodule; - if (hasOverflow) - { - var test = leftHeight - desiredBranches - desiredWorktree; - if (test < 0) - height = Math.Min(200, height); - else - height = Math.Max(200, test); - } - - leftHeight -= height; - submoduleList.Height = height; - hasOverflow = (desiredBranches + desiredWorktree) > leftHeight; - } - - if (vm.IsWorktreeGroupExpanded) - { - var height = desiredWorktree; - if (hasOverflow) - { - var test = leftHeight - desiredBranches; - if (test < 0) - height = Math.Min(200, height); - else - height = Math.Max(200, test); - } - - leftHeight -= height; - worktreeList.Height = height; - } - - if (desiredBranches > leftHeight) - { - var local = localBranchRows * 24.0; - var remote = remoteBranchRows * 24.0; - var half = leftHeight / 2; - if (vm.IsLocalBranchGroupExpanded) - { - if (vm.IsRemoteGroupExpanded) - { - if (local < half) - { - localBranchTree.Height = local; - remoteBranchTree.Height = leftHeight - local; - } - else if (remote < half) - { - remoteBranchTree.Height = remote; - localBranchTree.Height = leftHeight - remote; - } - else - { - localBranchTree.Height = half; - remoteBranchTree.Height = half; - } - } - else - { - localBranchTree.Height = leftHeight; - } - } - else if (vm.IsRemoteGroupExpanded) - { - remoteBranchTree.Height = leftHeight; - } - } - else - { - if (vm.IsLocalBranchGroupExpanded) - { - var height = localBranchRows * 24; - localBranchTree.Height = height; - } - - if (vm.IsRemoteGroupExpanded) - { - var height = remoteBranchRows * 24; - remoteBranchTree.Height = height; - } - } - } - protected override void OnLoaded(RoutedEventArgs e) { base.OnLoaded(e); - - if (DataContext is ViewModels.Repository { IsSearching: false }) - UpdateLeftSidebarLayout(); + UpdateLeftSidebarLayout(); } private void OpenWithExternalTools(object sender, RoutedEventArgs e) @@ -211,7 +89,13 @@ namespace SourceGit.Views e.Handled = true; } - + + private void OnBranchTreeRowsChanged(object _, RoutedEventArgs e) + { + UpdateLeftSidebarLayout(); + e.Handled = true; + } + private void OnLocalBranchTreeSelectionChanged(object _1, RoutedEventArgs _2) { remoteBranchTree.UnselectAll(); @@ -311,5 +195,127 @@ namespace SourceGit.Views UpdateLeftSidebarLayout(); } } + + private void UpdateLeftSidebarLayout() + { + var vm = DataContext as ViewModels.Repository; + if (vm == null || vm.Settings == null) + return; + + if (!IsLoaded) + return; + + var leftHeight = leftSidebarGroups.Bounds.Height - 28.0 * 5; + var localBranchRows = vm.IsLocalBranchGroupExpanded ? localBranchTree.Rows.Count : 0; + var remoteBranchRows = vm.IsRemoteGroupExpanded ? remoteBranchTree.Rows.Count : 0; + var desiredBranches = (localBranchRows + remoteBranchRows) * 24.0; + var desiredTag = vm.IsTagGroupExpanded ? tagsList.RowHeight * vm.VisibleTags.Count : 0; + var desiredSubmodule = vm.IsSubmoduleGroupExpanded ? submoduleList.RowHeight * vm.Submodules.Count : 0; + var desiredWorktree = vm.IsWorktreeGroupExpanded ? worktreeList.RowHeight * vm.Worktrees.Count : 0; + var desiredOthers = desiredTag + desiredSubmodule + desiredWorktree; + var hasOverflow = (desiredBranches + desiredOthers > leftHeight); + + if (vm.IsTagGroupExpanded) + { + var height = desiredTag; + if (hasOverflow) + { + var test = leftHeight - desiredBranches - desiredSubmodule - desiredWorktree; + if (test < 0) + height = Math.Min(200, height); + else + height = Math.Max(200, test); + } + + leftHeight -= height; + tagsList.Height = height; + hasOverflow = (desiredBranches + desiredSubmodule + desiredWorktree) > leftHeight; + } + + if (vm.IsSubmoduleGroupExpanded) + { + var height = desiredSubmodule; + if (hasOverflow) + { + var test = leftHeight - desiredBranches - desiredWorktree; + if (test < 0) + height = Math.Min(200, height); + else + height = Math.Max(200, test); + } + + leftHeight -= height; + submoduleList.Height = height; + hasOverflow = (desiredBranches + desiredWorktree) > leftHeight; + } + + if (vm.IsWorktreeGroupExpanded) + { + var height = desiredWorktree; + if (hasOverflow) + { + var test = leftHeight - desiredBranches; + if (test < 0) + height = Math.Min(200, height); + else + height = Math.Max(200, test); + } + + leftHeight -= height; + worktreeList.Height = height; + } + + if (desiredBranches > leftHeight) + { + var local = localBranchRows * 24.0; + var remote = remoteBranchRows * 24.0; + var half = leftHeight / 2; + if (vm.IsLocalBranchGroupExpanded) + { + if (vm.IsRemoteGroupExpanded) + { + if (local < half) + { + localBranchTree.Height = local; + remoteBranchTree.Height = leftHeight - local; + } + else if (remote < half) + { + remoteBranchTree.Height = remote; + localBranchTree.Height = leftHeight - remote; + } + else + { + localBranchTree.Height = half; + remoteBranchTree.Height = half; + } + } + else + { + localBranchTree.Height = leftHeight; + } + } + else if (vm.IsRemoteGroupExpanded) + { + remoteBranchTree.Height = leftHeight; + } + } + else + { + if (vm.IsLocalBranchGroupExpanded) + { + var height = localBranchRows * 24; + localBranchTree.Height = height; + } + + if (vm.IsRemoteGroupExpanded) + { + var height = remoteBranchRows * 24; + remoteBranchTree.Height = height; + } + } + + leftSidebarGroups.InvalidateMeasure(); + } } } From 1d7b77e45fdf603a888abd03a116a6ac013d71de Mon Sep 17 00:00:00 2001 From: leo Date: Sun, 7 Jul 2024 00:04:39 +0800 Subject: [PATCH 5/7] fix: virtualization not working --- src/Views/Repository.axaml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/Views/Repository.axaml b/src/Views/Repository.axaml index 2c3ecbe2..569709a6 100644 --- a/src/Views/Repository.axaml +++ b/src/Views/Repository.axaml @@ -238,6 +238,7 @@ Date: Sun, 7 Jul 2024 09:56:22 +0800 Subject: [PATCH 6/7] enhance: supports virtualization in sub context menu --- src/Resources/Styles.axaml | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/Resources/Styles.axaml b/src/Resources/Styles.axaml index b19709e7..139e5bd5 100644 --- a/src/Resources/Styles.axaml +++ b/src/Resources/Styles.axaml @@ -772,6 +772,7 @@ + Grid.IsSharedSizeScope="True"> + + + + + + From 09b418374a2f55fc07f820a01d4a4afd21829a15 Mon Sep 17 00:00:00 2001 From: leo Date: Sun, 7 Jul 2024 10:28:14 +0800 Subject: [PATCH 7/7] feature: add an option to enable `-a,--all` in commit command (#244) --- src/Commands/Commit.cs | 4 +++- src/Resources/Locales/en_US.axaml | 2 ++ src/Resources/Locales/zh_CN.axaml | 2 ++ src/Resources/Locales/zh_TW.axaml | 2 ++ src/ViewModels/Repository.cs | 6 ++++++ src/ViewModels/Reword.cs | 2 +- src/ViewModels/Squash.cs | 2 +- src/ViewModels/WorkingCopy.cs | 17 +++++++++++++++-- src/Views/WorkingCopy.axaml | 16 ++++++++++++---- 9 files changed, 44 insertions(+), 9 deletions(-) diff --git a/src/Commands/Commit.cs b/src/Commands/Commit.cs index 8ac6501f..492d00c7 100644 --- a/src/Commands/Commit.cs +++ b/src/Commands/Commit.cs @@ -4,7 +4,7 @@ namespace SourceGit.Commands { public class Commit : Command { - public Commit(string repo, string message, bool amend, bool allowEmpty = false) + public Commit(string repo, string message, bool autoStage, bool amend, bool allowEmpty = false) { var file = Path.GetTempFileName(); File.WriteAllText(file, message); @@ -12,6 +12,8 @@ namespace SourceGit.Commands WorkingDirectory = repo; Context = repo; Args = $"commit --file=\"{file}\""; + if (autoStage) + Args += " --all"; if (amend) Args += " --amend --no-edit"; if (allowEmpty) diff --git a/src/Resources/Locales/en_US.axaml b/src/Resources/Locales/en_US.axaml index 546a300b..55b05a01 100644 --- a/src/Resources/Locales/en_US.axaml +++ b/src/Resources/Locales/en_US.axaml @@ -543,6 +543,8 @@ Ignore files in the same folder Ignore this file only Amend + Auto-Stage + Tell the command to automatically stage files that have been modified and deleted, but new files you have not told Git about are not affected. You can stage this file now. COMMIT COMMIT & PUSH diff --git a/src/Resources/Locales/zh_CN.axaml b/src/Resources/Locales/zh_CN.axaml index fcc2f40f..58a72d47 100644 --- a/src/Resources/Locales/zh_CN.axaml +++ b/src/Resources/Locales/zh_CN.axaml @@ -545,6 +545,8 @@ 忽略同目录下所有文件 忽略本文件 修补(--amend) + 自动暂存(--all) + 提交前自动将修改过和删除的文件加入暂存区,但新增文件需要手动添加。 现在您已可将其加入暂存区中 提交 提交并推送 diff --git a/src/Resources/Locales/zh_TW.axaml b/src/Resources/Locales/zh_TW.axaml index 159009b9..73176126 100644 --- a/src/Resources/Locales/zh_TW.axaml +++ b/src/Resources/Locales/zh_TW.axaml @@ -545,6 +545,8 @@ 忽略同路徑下所有檔案 忽略本檔案 修補(--amend) + 自動暫存(--all) + 提交前自動將修改過和刪除的檔案加入暫存區,但新增檔案需要手動添加。 現在您已可將其加入暫存區中 提交 提交併推送 diff --git a/src/ViewModels/Repository.cs b/src/ViewModels/Repository.cs index 67250c4b..1c087506 100644 --- a/src/ViewModels/Repository.cs +++ b/src/ViewModels/Repository.cs @@ -64,6 +64,12 @@ namespace SourceGit.ViewModels set; } = true; + public bool AutoStageBeforeCommit + { + get; + set; + } = false; + public AvaloniaList Filters { get; diff --git a/src/ViewModels/Reword.cs b/src/ViewModels/Reword.cs index 47b1bd06..b3f0e9f8 100644 --- a/src/ViewModels/Reword.cs +++ b/src/ViewModels/Reword.cs @@ -39,7 +39,7 @@ namespace SourceGit.ViewModels return Task.Run(() => { - var succ = new Commands.Commit(_repo.FullPath, _message, true, true).Exec(); + var succ = new Commands.Commit(_repo.FullPath, _message, false, true, true).Exec(); CallUIThread(() => _repo.SetWatcherEnabled(true)); return succ; }); diff --git a/src/ViewModels/Squash.cs b/src/ViewModels/Squash.cs index 4b35266b..8e78658d 100644 --- a/src/ViewModels/Squash.cs +++ b/src/ViewModels/Squash.cs @@ -43,7 +43,7 @@ namespace SourceGit.ViewModels { var succ = new Commands.Reset(_repo.FullPath, Parent.SHA, "--soft").Exec(); if (succ) - succ = new Commands.Commit(_repo.FullPath, _message, true).Exec(); + succ = new Commands.Commit(_repo.FullPath, _message, false, true).Exec(); CallUIThread(() => _repo.SetWatcherEnabled(true)); return succ; }); diff --git a/src/ViewModels/WorkingCopy.cs b/src/ViewModels/WorkingCopy.cs index 8b32240c..a4d8b84e 100644 --- a/src/ViewModels/WorkingCopy.cs +++ b/src/ViewModels/WorkingCopy.cs @@ -77,6 +77,12 @@ namespace SourceGit.ViewModels private set => SetProperty(ref _isCommitting, value); } + public bool AutoStageBeforeCommit + { + get => _repo.Settings.AutoStageBeforeCommit; + set => _repo.Settings.AutoStageBeforeCommit = value; + } + public bool UseAmend { get => _useAmend; @@ -1216,7 +1222,13 @@ namespace SourceGit.ViewModels return; } - if (_staged.Count == 0) + if (_count == 0) + { + App.RaiseException(_repo.FullPath, "No files added to commit!"); + return; + } + + if (!AutoStageBeforeCommit && _staged.Count == 0) { App.RaiseException(_repo.FullPath, "No files added to commit!"); return; @@ -1234,9 +1246,10 @@ namespace SourceGit.ViewModels IsCommitting = true; _repo.SetWatcherEnabled(false); + var autoStage = AutoStageBeforeCommit; Task.Run(() => { - var succ = new Commands.Commit(_repo.FullPath, _commitMessage, _useAmend).Exec(); + var succ = new Commands.Commit(_repo.FullPath, _commitMessage, autoStage, _useAmend).Exec(); Dispatcher.UIThread.Post(() => { if (succ) diff --git a/src/Views/WorkingCopy.axaml b/src/Views/WorkingCopy.axaml index 49d693ae..8e77d9d8 100644 --- a/src/Views/WorkingCopy.axaml +++ b/src/Views/WorkingCopy.axaml @@ -174,7 +174,7 @@ - +