diff --git a/src/Views/ChangeCollectionView.axaml.cs b/src/Views/ChangeCollectionView.axaml.cs index d23bf5bcd..be2e7ebec 100644 --- a/src/Views/ChangeCollectionView.axaml.cs +++ b/src/Views/ChangeCollectionView.axaml.cs @@ -8,6 +8,7 @@ using Avalonia.Input; using Avalonia.Interactivity; using Avalonia.Media; +using Avalonia.Threading; using Avalonia.VisualTree; namespace SourceGit.Views @@ -35,19 +36,77 @@ public class ChangeCollectionContainer : ListBox protected override void OnKeyDown(KeyEventArgs e) { - if (SelectedItems is [ViewModels.ChangeTreeNode node]) + if (e.KeyModifiers == KeyModifiers.None && + (e.Key == Key.Left || e.Key == Key.Right) && + SelectedItems is [ViewModels.ChangeTreeNode node] && + this.FindAncestorOfType() is { Content: ViewModels.ChangeCollectionAsTree tree } view) { - if (((e.Key == Key.Left && node.IsExpanded) || (e.Key == Key.Right && !node.IsExpanded)) && - e.KeyModifiers == KeyModifiers.None) + if (e.Key == Key.Left) { - this.FindAncestorOfType()?.ToggleNodeIsExpanded(node); - e.Handled = true; + if (node.IsFolder && node.IsExpanded) + { + view.ToggleNodeIsExpanded(node); + } + else + { + var parent = FindParentRow(tree.Rows, node); + if (parent != null) + MoveSelectionTo(parent); + } } + else + { + if (node.IsFolder && !node.IsExpanded) + { + view.ToggleNodeIsExpanded(node); + } + else if (node.IsFolder) + { + var idx = tree.Rows.IndexOf(node); + if (idx >= 0 && idx + 1 < tree.Rows.Count) + { + var child = tree.Rows[idx + 1]; + if (child.Depth == node.Depth + 1) + MoveSelectionTo(child); + } + } + } + + e.Handled = true; } if (!e.Handled && e.Key != Key.Space && e.Key != Key.Enter) base.OnKeyDown(e); } + + private void MoveSelectionTo(ViewModels.ChangeTreeNode target) + { + SelectedItem = target; + ScrollIntoView(target); + if (ContainerFromItem(target) is Control container) + container.Focus(); + else + Dispatcher.UIThread.Post(() => (ContainerFromItem(target) as Control)?.Focus(), DispatcherPriority.Loaded); + } + + private static ViewModels.ChangeTreeNode FindParentRow(IList rows, ViewModels.ChangeTreeNode node) + { + if (node.Depth == 0) + return null; + + var idx = rows.IndexOf(node); + if (idx <= 0) + return null; + + var parentDepth = node.Depth - 1; + for (int i = idx - 1; i >= 0; i--) + { + if (rows[i].Depth == parentDepth) + return rows[i]; + } + + return null; + } } public partial class ChangeCollectionView : UserControl diff --git a/src/Views/RevisionFileTreeView.axaml.cs b/src/Views/RevisionFileTreeView.axaml.cs index e9a0e22fe..114b809af 100644 --- a/src/Views/RevisionFileTreeView.axaml.cs +++ b/src/Views/RevisionFileTreeView.axaml.cs @@ -12,6 +12,7 @@ using Avalonia.Layout; using Avalonia.Media; using Avalonia.Platform.Storage; +using Avalonia.Threading; using Avalonia.VisualTree; namespace SourceGit.Views @@ -123,13 +124,41 @@ protected override async void OnKeyDown(KeyEventArgs e) if (SelectedItem is ViewModels.RevisionFileTreeNode node) { - if (node.IsFolder && - e.KeyModifiers == KeyModifiers.None && - (node.IsExpanded && e.Key == Key.Left) || (!node.IsExpanded && e.Key == Key.Right)) + if (e.KeyModifiers == KeyModifiers.None && + (e.Key == Key.Left || e.Key == Key.Right) && + this.FindAncestorOfType() is { } view) { - var tree = this.FindAncestorOfType(); - if (tree != null) - await tree.ToggleNodeIsExpandedAsync(node); + if (e.Key == Key.Left) + { + if (node.IsFolder && node.IsExpanded) + { + await view.ToggleNodeIsExpandedAsync(node); + } + else + { + var parent = FindParentRow(view.Rows, node); + if (parent != null) + MoveSelectionTo(parent); + } + } + else + { + if (node.IsFolder && !node.IsExpanded) + { + await view.ToggleNodeIsExpandedAsync(node); + } + else if (node.IsFolder) + { + var idx = view.Rows.IndexOf(node); + if (idx >= 0 && idx + 1 < view.Rows.Count) + { + var child = view.Rows[idx + 1]; + if (child.Depth == node.Depth + 1) + MoveSelectionTo(child); + } + } + } + e.Handled = true; } else if (e.Key == Key.C && @@ -182,6 +211,35 @@ protected override async void OnKeyDown(KeyEventArgs e) if (!e.Handled) base.OnKeyDown(e); } + + private void MoveSelectionTo(ViewModels.RevisionFileTreeNode target) + { + SelectedItem = target; + ScrollIntoView(target); + if (ContainerFromItem(target) is Control container) + container.Focus(); + else + Dispatcher.UIThread.Post(() => (ContainerFromItem(target) as Control)?.Focus(), DispatcherPriority.Loaded); + } + + private static ViewModels.RevisionFileTreeNode FindParentRow(IList rows, ViewModels.RevisionFileTreeNode node) + { + if (node.Depth == 0) + return null; + + var idx = rows.IndexOf(node); + if (idx <= 0) + return null; + + var parentDepth = node.Depth - 1; + for (int i = idx - 1; i >= 0; i--) + { + if (rows[i].Depth == parentDepth) + return rows[i]; + } + + return null; + } } public partial class RevisionFileTreeView : UserControl