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/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/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/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"> + + + + + + diff --git a/src/ViewModels/BranchTreeNode.cs b/src/ViewModels/BranchTreeNode.cs index f4044476..a29e6a2c 100644 --- a/src/ViewModels/BranchTreeNode.cs +++ b/src/ViewModels/BranchTreeNode.cs @@ -1,122 +1,63 @@ -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 bool IsSelected { 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 +74,6 @@ namespace SourceGit.ViewModels var node = new BranchTreeNode() { Name = remote.Name, - Type = BranchTreeNodeType.Remote, Backend = remote, IsExpanded = bForceExpanded || _expanded.Contains(path), }; @@ -176,9 +116,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 +135,6 @@ namespace SourceGit.ViewModels roots.Add(new BranchTreeNode() { Name = branch.Name, - Type = BranchTreeNodeType.Branch, Backend = branch, IsExpanded = false, IsFiltered = isFiltered, @@ -215,7 +158,6 @@ namespace SourceGit.ViewModels lastFolder = new BranchTreeNode() { Name = name, - Type = BranchTreeNodeType.Folder, IsExpanded = bForceExpanded || branch.IsCurrent || _expanded.Contains(folder), }; roots.Add(lastFolder); @@ -226,7 +168,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 +179,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 +192,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..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; @@ -1905,7 +1911,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/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/BranchTree.axaml b/src/Views/BranchTree.axaml new file mode 100644 index 00000000..66eca538 --- /dev/null +++ b/src/Views/BranchTree.axaml @@ -0,0 +1,108 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Views/BranchTree.axaml.cs b/src/Views/BranchTree.axaml.cs new file mode 100644 index 00000000..95e70f48 --- /dev/null +++ b/src/Views/BranchTree.axaml.cs @@ -0,0 +1,347 @@ +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)); + + public ViewModels.BranchTreeNode Node + { + get => GetValue(NodeProperty); + set => SetValue(NodeProperty, value); + } + + public static readonly StyledProperty IsExpandedProperty = + AvaloniaProperty.Register(nameof(IsExpanded)); + + public bool IsExpanded + { + get => GetValue(IsExpandedProperty); + set => SetValue(IsExpandedProperty, value); + } + + static BranchTreeNodeIcon() + { + NodeProperty.Changed.AddClassHandler((icon, _) => icon.UpdateContent()); + IsExpandedProperty.Changed.AddClassHandler((icon, _) => 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)); + + 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 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(); + } + + public void UnselectAll() + { + 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); + + if (change.Property == NodesProperty) + { + Rows.Clear(); + + if (Nodes is { Count: > 0 }) + { + var rows = new List(); + MakeRows(rows, Nodes, 0); + Rows.AddRange(rows); + } + + RaiseEvent(new RoutedEventArgs(RowsChangedEvent)); + } + else if (change.Property == IsVisibleProperty) + { + 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) + node.IsSelected = true; + } + + foreach (var item in e.RemovedItems) + { + 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 }) + repo.NavigateToCommit(branch.Head); + + var prev = null as ViewModels.BranchTreeNode; + foreach (var row in Rows) + { + if (row.IsSelected) + { + if (prev is { IsSelected: true }) + { + var prevTop = prev.CornerRadius.TopLeft; + prev.CornerRadius = new CornerRadius(prevTop, 0); + row.CornerRadius = new CornerRadius(0, 4); + } + else + { + row.CornerRadius = new CornerRadius(4); + } + } + + prev = row; + } + + RaiseEvent(new RoutedEventArgs(SelectionChangedEvent)); + } + + private void OnTreeContextRequested(object _1, ContextRequestedEventArgs _2) + { + 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 _) + { + 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); + } + + RaiseEvent(new RoutedEventArgs(RowsChangedEvent)); + } + } + } + + 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..569709a6 100644 --- a/src/Views/Repository.axaml +++ b/src/Views/Repository.axaml @@ -236,77 +236,14 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + @@ -317,64 +254,14 @@ - - - - - - - - - - - - - - - - - - - - - - - - + @@ -388,6 +275,7 @@ @@ -491,7 +379,7 @@ (); - 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) + + private void OnRemoteBranchTreeSelectionChanged(object _1, RoutedEventArgs _2) { 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) + private void OnTagDataGridSelectionChanged(object sender, SelectionChangedEventArgs _) { - 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) + if (sender is DataGrid { SelectedItem: Models.Tag tag }) { localBranchTree.UnselectAll(); remoteBranchTree.UnselectAll(); - var tag = datagrid.SelectedItem as Models.Tag; if (DataContext is ViewModels.Repository repo) repo.NavigateToCommit(tag.SHA); } @@ -300,25 +132,11 @@ namespace SourceGit.Views e.Handled = true; } - private void OnToggleFilter(object sender, RoutedEventArgs e) + 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 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); - } + repo.UpdateFilter(tag.Name, toggle.IsChecked == true); } e.Handled = true; @@ -338,10 +156,10 @@ namespace SourceGit.Views private void OnDoubleTappedSubmodule(object sender, TappedEventArgs e) { - if (sender is DataGrid datagrid && datagrid.SelectedItem != null && DataContext is ViewModels.Repository repo) + if (sender is DataGrid { SelectedItem: not null } grid && DataContext is ViewModels.Repository repo) { - var submodule = datagrid.SelectedItem as string; - (DataContext as ViewModels.Repository).OpenSubmodule(submodule); + var submodule = grid.SelectedItem as string; + repo.OpenSubmodule(submodule); } e.Handled = true; @@ -349,11 +167,11 @@ namespace SourceGit.Views private void OnWorktreeContextRequested(object sender, ContextRequestedEventArgs e) { - if (sender is DataGrid datagrid && datagrid.SelectedItem != null && DataContext is ViewModels.Repository repo) + if (sender is DataGrid { SelectedItem: not null } grid && DataContext is ViewModels.Repository repo) { - var worktree = datagrid.SelectedItem as Models.Worktree; + var worktree = grid.SelectedItem as Models.Worktree; var menu = repo.CreateContextMenuForWorktree(worktree); - datagrid.OpenContextMenu(menu); + grid.OpenContextMenu(menu); } e.Handled = true; @@ -361,42 +179,16 @@ namespace SourceGit.Views private void OnDoubleTappedWorktree(object sender, TappedEventArgs e) { - if (sender is DataGrid datagrid && datagrid.SelectedItem != null && DataContext is ViewModels.Repository repo) + if (sender is DataGrid { SelectedItem: not null } grid && DataContext is ViewModels.Repository repo) { - var worktree = datagrid.SelectedItem as Models.Worktree; - (DataContext as ViewModels.Repository).OpenWorktree(worktree); + var worktree = grid.SelectedItem as Models.Worktree; + repo.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) + private void OnLeftSidebarDataGridPropertyChanged(object _, AvaloniaPropertyChangedEventArgs e) { if (e.Property == DataGrid.ItemsSourceProperty || e.Property == DataGrid.IsVisibleProperty) { @@ -414,8 +206,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; @@ -434,7 +226,7 @@ namespace SourceGit.Views else height = Math.Max(200, test); } - + leftHeight -= height; tagsList.Height = height; hasOverflow = (desiredBranches + desiredSubmodule + desiredWorktree) > leftHeight; @@ -522,19 +314,8 @@ namespace SourceGit.Views remoteBranchTree.Height = height; } } - } - private int GetTreeRowsCount(List nodes) - { - int count = nodes.Count; - - foreach (var node in nodes) - { - if (!node.IsBranch && node.IsExpanded) - count += GetTreeRowsCount(node.Children); - } - - return count; + leftSidebarGroups.InvalidateMeasure(); } } } 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 @@ - +