diff --git a/src/App.axaml b/src/App.axaml index d73ea45d..6e485f3a 100644 --- a/src/App.axaml +++ b/src/App.axaml @@ -20,7 +20,6 @@ - diff --git a/src/Models/TreeDataGridSelectionModel.cs b/src/Models/TreeDataGridSelectionModel.cs deleted file mode 100644 index be9d7d7c..00000000 --- a/src/Models/TreeDataGridSelectionModel.cs +++ /dev/null @@ -1,462 +0,0 @@ -using System; -using System.Collections; -using System.Collections.Generic; - -using Avalonia; -using Avalonia.Controls; -using Avalonia.Controls.Models.TreeDataGrid; -using Avalonia.Controls.Primitives; -using Avalonia.Controls.Selection; -using Avalonia.Input; - -namespace SourceGit.Models -{ - public class TreeDataGridSelectionModel : TreeSelectionModelBase, - ITreeDataGridRowSelectionModel, - ITreeDataGridSelectionInteraction - where TModel : class - { - private static readonly Point s_InvalidPoint = new(double.NegativeInfinity, double.NegativeInfinity); - - private readonly ITreeDataGridSource _source; - private EventHandler _viewSelectionChanged; - private EventHandler _rowDoubleTapped; - private Point _pressedPoint = s_InvalidPoint; - private bool _raiseViewSelectionChanged; - private Func> _childrenGetter; - - public TreeDataGridSelectionModel(ITreeDataGridSource source, Func> childrenGetter) - : base(source.Items) - { - _source = source; - _childrenGetter = childrenGetter; - - SelectionChanged += (s, e) => - { - if (!IsSourceCollectionChanging) - _viewSelectionChanged?.Invoke(this, e); - else - _raiseViewSelectionChanged = true; - }; - } - - public void Select(IEnumerable items) - { - using (BatchUpdate()) - { - Clear(); - - foreach (var selected in items) - { - var idx = GetModelIndex(_source.Items, selected, IndexPath.Unselected); - if (!idx.Equals(IndexPath.Unselected)) - Select(idx); - } - } - } - - event EventHandler ITreeDataGridSelectionInteraction.SelectionChanged - { - add => _viewSelectionChanged += value; - remove => _viewSelectionChanged -= value; - } - - public event EventHandler RowDoubleTapped - { - add => _rowDoubleTapped += value; - remove => _rowDoubleTapped -= value; - } - - IEnumerable ITreeDataGridSelection.Source - { - get => Source; - set => Source = value; - } - - bool ITreeDataGridSelectionInteraction.IsRowSelected(IRow rowModel) - { - if (rowModel is IModelIndexableRow indexable) - return IsSelected(indexable.ModelIndexPath); - return false; - } - - bool ITreeDataGridSelectionInteraction.IsRowSelected(int rowIndex) - { - if (rowIndex >= 0 && rowIndex < _source.Rows.Count) - { - if (_source.Rows[rowIndex] is IModelIndexableRow indexable) - return IsSelected(indexable.ModelIndexPath); - } - - return false; - } - - void ITreeDataGridSelectionInteraction.OnKeyDown(TreeDataGrid sender, KeyEventArgs e) - { - if (sender.RowsPresenter is null) - return; - - if (!e.Handled) - { - var ctrl = e.KeyModifiers.HasFlag(KeyModifiers.Control); - if (e.Key == Key.A && ctrl && !SingleSelect) - { - using (BatchUpdate()) - { - Clear(); - - int num = _source.Rows.Count; - for (int i = 0; i < num; ++i) - { - var m = _source.Rows.RowIndexToModelIndex(i); - Select(m); - } - } - e.Handled = true; - } - - var direction = e.Key.ToNavigationDirection(); - var shift = e.KeyModifiers.HasFlag(KeyModifiers.Shift); - if (direction.HasValue) - { - var anchorRowIndex = _source.Rows.ModelIndexToRowIndex(AnchorIndex); - sender.RowsPresenter.BringIntoView(anchorRowIndex); - - var anchor = sender.TryGetRow(anchorRowIndex); - if (anchor is not null && !ctrl) - { - e.Handled = TryKeyExpandCollapse(sender, direction.Value, anchor); - } - - if (!e.Handled && (!ctrl || shift)) - { - e.Handled = MoveSelection(sender, direction.Value, shift, anchor); - } - - if (!e.Handled && direction == NavigationDirection.Left - && anchor?.Rows is HierarchicalRows hierarchicalRows && anchorRowIndex > 0) - { - var newIndex = hierarchicalRows.GetParentRowIndex(AnchorIndex); - UpdateSelection(sender, newIndex, true); - FocusRow(sender, sender.RowsPresenter.BringIntoView(newIndex)); - } - - if (!e.Handled && direction == NavigationDirection.Right - && anchor?.Rows is HierarchicalRows hierarchicalRows2 && hierarchicalRows2[anchorRowIndex].IsExpanded) - { - var newIndex = anchorRowIndex + 1; - UpdateSelection(sender, newIndex, true); - sender.RowsPresenter.BringIntoView(newIndex); - } - } - } - } - - void ITreeDataGridSelectionInteraction.OnPointerPressed(TreeDataGrid sender, PointerPressedEventArgs e) - { - if (!e.Handled && - e.Pointer.Type == PointerType.Mouse && - e.Source is Control source && - sender.TryGetRow(source, out var row) && - _source.Rows.RowIndexToModelIndex(row.RowIndex) is { } modelIndex) - { - if (!IsSelected(modelIndex)) - { - PointerSelect(sender, row, e); - _pressedPoint = s_InvalidPoint; - } - else - { - var point = e.GetCurrentPoint(sender); - if (point.Properties.IsRightButtonPressed) - { - _pressedPoint = s_InvalidPoint; - return; - } - - if (e.KeyModifiers == KeyModifiers.Control) - { - Deselect(modelIndex); - } - else if (e.ClickCount % 2 == 0) - { - var focus = _source.Rows[row.RowIndex]; - if (focus is IExpander expander && HasChildren(focus)) - expander.IsExpanded = !expander.IsExpanded; - else - _rowDoubleTapped?.Invoke(this, e); - - e.Handled = true; - } - else if (sender.RowSelection.Count > 1) - { - using (BatchUpdate()) - { - Clear(); - Select(modelIndex); - } - } - - _pressedPoint = s_InvalidPoint; - } - } - else - { - if (!sender.TryGetRow(e.Source as Control, out var test)) - Clear(); - - _pressedPoint = e.GetPosition(sender); - } - } - - void ITreeDataGridSelectionInteraction.OnPointerReleased(TreeDataGrid sender, PointerReleasedEventArgs e) - { - if (!e.Handled && - _pressedPoint != s_InvalidPoint && - e.Source is Control source && - sender.TryGetRow(source, out var row) && - _source.Rows.RowIndexToModelIndex(row.RowIndex) is { } modelIndex) - { - if (!IsSelected(modelIndex)) - { - var p = e.GetPosition(sender); - if (Math.Abs(p.X - _pressedPoint.X) <= 3 || Math.Abs(p.Y - _pressedPoint.Y) <= 3) - PointerSelect(sender, row, e); - } - } - } - - protected override void OnSourceCollectionChangeFinished() - { - if (_raiseViewSelectionChanged) - { - _viewSelectionChanged?.Invoke(this, EventArgs.Empty); - _raiseViewSelectionChanged = false; - } - } - - private void PointerSelect(TreeDataGrid sender, TreeDataGridRow row, PointerEventArgs e) - { - var point = e.GetCurrentPoint(sender); - - var commandModifiers = TopLevel.GetTopLevel(sender)?.PlatformSettings?.HotkeyConfiguration.CommandModifiers; - var toggleModifier = commandModifiers is not null && e.KeyModifiers.HasFlag(commandModifiers); - var isRightButton = point.Properties.PointerUpdateKind is PointerUpdateKind.RightButtonPressed or - PointerUpdateKind.RightButtonReleased; - - UpdateSelection( - sender, - row.RowIndex, - select: true, - rangeModifier: e.KeyModifiers.HasFlag(KeyModifiers.Shift), - toggleModifier: toggleModifier, - rightButton: isRightButton); - e.Handled = true; - } - - private void UpdateSelection(TreeDataGrid treeDataGrid, int rowIndex, bool select = true, bool rangeModifier = false, bool toggleModifier = false, bool rightButton = false) - { - var modelIndex = _source.Rows.RowIndexToModelIndex(rowIndex); - if (modelIndex == default) - return; - - var mode = SingleSelect ? SelectionMode.Single : SelectionMode.Multiple; - var multi = (mode & SelectionMode.Multiple) != 0; - var toggle = (toggleModifier || (mode & SelectionMode.Toggle) != 0); - var range = multi && rangeModifier; - - if (!select) - { - if (IsSelected(modelIndex) && !treeDataGrid.QueryCancelSelection()) - Deselect(modelIndex); - } - else if (rightButton) - { - if (IsSelected(modelIndex) == false && !treeDataGrid.QueryCancelSelection()) - SelectedIndex = modelIndex; - } - else if (range) - { - if (!treeDataGrid.QueryCancelSelection()) - { - var anchor = RangeAnchorIndex; - var i = Math.Max(_source.Rows.ModelIndexToRowIndex(anchor), 0); - var step = i < rowIndex ? 1 : -1; - - using (BatchUpdate()) - { - Clear(); - - while (true) - { - var m = _source.Rows.RowIndexToModelIndex(i); - Select(m); - anchor = m; - if (i == rowIndex) - break; - i += step; - } - } - } - } - else if (multi && toggle) - { - if (!treeDataGrid.QueryCancelSelection()) - { - if (IsSelected(modelIndex) == true) - Deselect(modelIndex); - else - Select(modelIndex); - } - } - else if (toggle) - { - if (!treeDataGrid.QueryCancelSelection()) - SelectedIndex = (SelectedIndex == modelIndex) ? -1 : modelIndex; - } - else if (SelectedIndex != modelIndex || Count > 1) - { - if (!treeDataGrid.QueryCancelSelection()) - SelectedIndex = modelIndex; - } - } - - private bool TryKeyExpandCollapse(TreeDataGrid treeDataGrid, NavigationDirection direction, TreeDataGridRow focused) - { - if (treeDataGrid.RowsPresenter is null || focused.RowIndex < 0) - return false; - - var row = _source.Rows[focused.RowIndex]; - - if (row is IExpander expander) - { - if (direction == NavigationDirection.Right && !expander.IsExpanded) - { - expander.IsExpanded = true; - return true; - } - else if (direction == NavigationDirection.Left && expander.IsExpanded) - { - expander.IsExpanded = false; - return true; - } - } - - return false; - } - - private bool MoveSelection(TreeDataGrid treeDataGrid, NavigationDirection direction, bool rangeModifier, TreeDataGridRow focused) - { - if (treeDataGrid.RowsPresenter is null || _source.Columns.Count == 0 || _source.Rows.Count == 0) - return false; - - var currentRowIndex = focused?.RowIndex ?? _source.Rows.ModelIndexToRowIndex(SelectedIndex); - int newRowIndex; - - if (direction == NavigationDirection.First || direction == NavigationDirection.Last) - { - newRowIndex = direction == NavigationDirection.First ? 0 : _source.Rows.Count - 1; - } - else - { - (var x, var y) = direction switch - { - NavigationDirection.Up => (0, -1), - NavigationDirection.Down => (0, 1), - NavigationDirection.Left => (-1, 0), - NavigationDirection.Right => (1, 0), - _ => (0, 0) - }; - - newRowIndex = Math.Max(0, Math.Min(currentRowIndex + y, _source.Rows.Count - 1)); - } - - if (newRowIndex != currentRowIndex) - UpdateSelection(treeDataGrid, newRowIndex, true, rangeModifier); - - if (newRowIndex != currentRowIndex) - { - treeDataGrid.RowsPresenter?.BringIntoView(newRowIndex); - FocusRow(treeDataGrid, treeDataGrid.TryGetRow(newRowIndex)); - return true; - } - else - { - return false; - } - } - - private static void FocusRow(TreeDataGrid owner, Control control) - { - if (!owner.TryGetRow(control, out var row) || row.CellsPresenter is null) - return; - - // Get the column index of the currently focused cell if possible: we'll try to focus the - // same column in the new row. - if (TopLevel.GetTopLevel(owner)?.FocusManager is { } focusManager && - focusManager.GetFocusedElement() is Control currentFocus && - owner.TryGetCell(currentFocus, out var currentCell) && - row.TryGetCell(currentCell.ColumnIndex) is { } newCell && - newCell.Focusable) - { - newCell.Focus(); - } - else - { - // Otherwise, just focus the first focusable cell in the row. - foreach (var cell in row.CellsPresenter.GetRealizedElements()) - { - if (cell.Focusable) - { - cell.Focus(); - break; - } - } - } - } - - protected override IEnumerable GetChildren(TModel node) - { - if (node == null) - return null; - - return _childrenGetter?.Invoke(node); - } - - private IndexPath GetModelIndex(IEnumerable collection, TModel model, IndexPath parent) - { - int i = 0; - - foreach (var item in collection) - { - var index = parent.Append(i); - if (item != null && item == model) - return index; - - var children = GetChildren(item); - if (children != null) - { - var findInChildren = GetModelIndex(children, model, index); - if (!findInChildren.Equals(IndexPath.Unselected)) - return findInChildren; - } - - i++; - } - - return IndexPath.Unselected; - } - - private bool HasChildren(IRow row) - { - var children = GetChildren(row.Model as TModel); - if (children != null) - { - foreach (var c in children) - return true; - } - - return false; - } - } -} diff --git a/src/Resources/Styles.axaml b/src/Resources/Styles.axaml index 78f3bbca..9d63184a 100644 --- a/src/Resources/Styles.axaml +++ b/src/Resources/Styles.axaml @@ -1093,6 +1093,33 @@ + + + + - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + - - - - - + + + + + + + + + + + + + + - - - - - - + + + + + + + + + + + + - + diff --git a/src/Views/ChangeCollectionView.axaml.cs b/src/Views/ChangeCollectionView.axaml.cs index c9ae72b2..beb5e60b 100644 --- a/src/Views/ChangeCollectionView.axaml.cs +++ b/src/Views/ChangeCollectionView.axaml.cs @@ -2,22 +2,51 @@ using System; using System.Collections.Generic; using Avalonia; +using Avalonia.Collections; using Avalonia.Controls; -using Avalonia.Controls.Models.TreeDataGrid; -using Avalonia.Controls.Templates; +using Avalonia.Controls.Primitives; +using Avalonia.Input; using Avalonia.Interactivity; +using Avalonia.VisualTree; + +using CommunityToolkit.Mvvm.ComponentModel; namespace SourceGit.Views { - public class ChangeTreeNode + public class ChangeTreeNode : ObservableObject { public string FullPath { get; set; } = string.Empty; - public bool IsFolder { get; set; } = false; - public bool IsExpanded { get; set; } = false; + public int Depth { get; private set; } = 0; public Models.Change Change { get; set; } = null; public List Children { get; set; } = new List(); - public static List Build(IList changes, bool expanded) + public bool IsFolder + { + get => Change == null; + } + + public bool IsExpanded + { + get => _isExpanded; + set => SetProperty(ref _isExpanded, value); + } + + public ChangeTreeNode(Models.Change c, int depth) + { + FullPath = c.Path; + Depth = depth; + Change = c; + IsExpanded = false; + } + + public ChangeTreeNode(string path, bool isExpanded, int depth) + { + FullPath = path; + Depth = depth; + IsExpanded = isExpanded; + } + + public static List Build(IList changes, HashSet folded) { var nodes = new List(); var folders = new Dictionary(); @@ -27,18 +56,13 @@ namespace SourceGit.Views var sepIdx = c.Path.IndexOf('/', StringComparison.Ordinal); if (sepIdx == -1) { - nodes.Add(new ChangeTreeNode() - { - FullPath = c.Path, - Change = c, - IsFolder = false, - IsExpanded = false - }); + nodes.Add(new ChangeTreeNode(c, 0)); } else { ChangeTreeNode lastFolder = null; var start = 0; + var depth = 0; while (sepIdx != -1) { @@ -49,42 +73,29 @@ namespace SourceGit.Views } else if (lastFolder == null) { - lastFolder = new ChangeTreeNode() - { - FullPath = folder, - IsFolder = true, - IsExpanded = expanded - }; + lastFolder = new ChangeTreeNode(folder, !folded.Contains(folder), depth); folders.Add(folder, lastFolder); InsertFolder(nodes, lastFolder); } else { - var cur = new ChangeTreeNode() - { - FullPath = folder, - IsFolder = true, - IsExpanded = expanded - }; + var cur = new ChangeTreeNode(folder, !folded.Contains(folder), depth); folders.Add(folder, cur); InsertFolder(lastFolder.Children, cur); lastFolder = cur; } start = sepIdx + 1; + depth++; sepIdx = c.Path.IndexOf('/', start); } - lastFolder.Children.Add(new ChangeTreeNode() - { - FullPath = c.Path, - Change = c, - IsFolder = false, - IsExpanded = false - }); + lastFolder.Children.Add(new ChangeTreeNode(c, depth)); } } + Sort(nodes); + folders.Clear(); return nodes; } @@ -102,6 +113,68 @@ namespace SourceGit.Views collection.Add(subFolder); } + + private static void Sort(List nodes) + { + foreach (var node in nodes) + { + if (node.IsFolder) + Sort(node.Children); + } + + nodes.Sort((l, r) => + { + if (l.IsFolder) + return r.IsFolder ? string.Compare(l.FullPath, r.FullPath, StringComparison.Ordinal) : -1; + return r.IsFolder ? 1 : string.Compare(l.FullPath, r.FullPath, StringComparison.Ordinal); + }); + } + + private bool _isExpanded = true; + } + + public class ChangeCollectionAsTree + { + public List Tree { get; set; } = new List(); + public AvaloniaList Rows { get; set; } = new AvaloniaList(); + } + + public class ChangeCollectionAsGrid + { + public AvaloniaList Changes { get; set; } = new AvaloniaList(); + } + + public class ChangeCollectionAsList + { + public AvaloniaList Changes { get; set; } = new AvaloniaList(); + } + + public class ChangeTreeNodeToggleButton : ToggleButton + { + protected override Type StyleKeyOverride => typeof(ToggleButton); + + protected override void OnPointerPressed(PointerPressedEventArgs e) + { + if (e.GetCurrentPoint(this).Properties.IsLeftButtonPressed && + DataContext is ChangeTreeNode { IsFolder: true } node) + { + var tree = this.FindAncestorOfType(); + tree.ToggleNodeIsExpanded(node); + } + + e.Handled = true; + } + } + + public class ChangeCollectionContainer : ListBox + { + protected override Type StyleKeyOverride => typeof(ListBox); + + protected override void OnKeyDown(KeyEventArgs e) + { + if (e.Key != Key.Space) + base.OnKeyDown(e); + } } public partial class ChangeCollectionView : UserControl @@ -115,13 +188,13 @@ namespace SourceGit.Views set => SetValue(IsWorkingCopyChangeProperty, value); } - public static readonly StyledProperty SingleSelectProperty = - AvaloniaProperty.Register(nameof(SingleSelect), true); + public static readonly StyledProperty SelectionModeProperty = + AvaloniaProperty.Register(nameof(SelectionMode), SelectionMode.Single); - public bool SingleSelect + public SelectionMode SelectionMode { - get => GetValue(SingleSelectProperty); - set => SetValue(SingleSelectProperty, value); + get => GetValue(SelectionModeProperty); + set => SetValue(SelectionModeProperty, value); } public static readonly StyledProperty ViewModeProperty = @@ -160,171 +233,193 @@ namespace SourceGit.Views remove { RemoveHandler(ChangeDoubleTappedEvent, value); } } - static ChangeCollectionView() - { - ViewModeProperty.Changed.AddClassHandler((c, e) => c.UpdateSource()); - ChangesProperty.Changed.AddClassHandler((c, e) => c.UpdateSource()); - SelectedChangesProperty.Changed.AddClassHandler((c, e) => c.UpdateSelected()); - } - public ChangeCollectionView() { InitializeComponent(); } - private void UpdateSource() + public void ToggleNodeIsExpanded(ChangeTreeNode node) { - if (Content is TreeDataGrid tree && tree.Source is IDisposable disposable) - disposable.Dispose(); - - Content = null; - - var changes = Changes; - if (changes == null || changes.Count == 0) - return; - - var viewMode = ViewMode; - if (viewMode == Models.ChangeViewMode.Tree) + if (_displayContext is ChangeCollectionAsTree tree) { - var filetree = ChangeTreeNode.Build(changes, true); - var template = this.FindResource("TreeModeTemplate") as IDataTemplate; - var source = new HierarchicalTreeDataGridSource(filetree) + _disableSelectionChangingEvent = true; + node.IsExpanded = !node.IsExpanded; + + var depth = node.Depth; + var idx = tree.Rows.IndexOf(node); + if (idx == -1) + return; + + if (node.IsExpanded) { - Columns = + var subrows = new List(); + MakeTreeRows(subrows, node.Children); + tree.Rows.InsertRange(idx + 1, subrows); + } + else + { + var removeCount = 0; + for (int i = idx + 1; i < tree.Rows.Count; i++) { - new HierarchicalExpanderColumn( - new TemplateColumn(null, template, null, GridLength.Auto), - x => x.Children, - x => x.Children.Count > 0, - x => x.IsExpanded) + var row = tree.Rows[i]; + if (row.Depth <= depth) + break; + + removeCount++; } - }; + tree.Rows.RemoveRange(idx + 1, removeCount); + } - var selection = new Models.TreeDataGridSelectionModel(source, x => x.Children); - selection.SingleSelect = SingleSelect; - selection.RowDoubleTapped += (_, e) => RaiseEvent(new RoutedEventArgs(ChangeDoubleTappedEvent)); - selection.SelectionChanged += (s, _) => - { - if (!_isSelecting && s is Models.TreeDataGridSelectionModel model) - { - var selected = new List(); - foreach (var c in model.SelectedItems) - CollectChangesInNode(selected, c); - - TrySetSelected(selected); - } - }; - - source.Selection = selection; - CreateTreeDataGrid(source); - } - else if (viewMode == Models.ChangeViewMode.List) - { - var template = this.FindResource("ListModeTemplate") as IDataTemplate; - var source = new FlatTreeDataGridSource(changes) - { - Columns = { new TemplateColumn(null, template, null, GridLength.Auto) } - }; - - var selection = new Models.TreeDataGridSelectionModel(source, null); - selection.SingleSelect = SingleSelect; - selection.RowDoubleTapped += (_, e) => RaiseEvent(new RoutedEventArgs(ChangeDoubleTappedEvent)); - selection.SelectionChanged += (s, _) => - { - if (!_isSelecting && s is Models.TreeDataGridSelectionModel model) - { - var selected = new List(); - foreach (var c in model.SelectedItems) - selected.Add(c); - - TrySetSelected(selected); - } - }; - - source.Selection = selection; - CreateTreeDataGrid(source); - } - else - { - var template = this.FindResource("GridModeTemplate") as IDataTemplate; - var source = new FlatTreeDataGridSource(changes) - { - Columns = { new TemplateColumn(null, template, null, GridLength.Auto) }, - }; - - var selection = new Models.TreeDataGridSelectionModel(source, null); - selection.SingleSelect = SingleSelect; - selection.RowDoubleTapped += (_, e) => RaiseEvent(new RoutedEventArgs(ChangeDoubleTappedEvent)); - selection.SelectionChanged += (s, _) => - { - if (!_isSelecting && s is Models.TreeDataGridSelectionModel model) - { - var selected = new List(); - foreach (var c in model.SelectedItems) - selected.Add(c); - - TrySetSelected(selected); - } - }; - - source.Selection = selection; - CreateTreeDataGrid(source); + _disableSelectionChangingEvent = false; } } - private void UpdateSelected() + protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change) { - if (_isSelecting || Content == null) - return; + base.OnPropertyChanged(change); - var tree = Content as TreeDataGrid; - if (tree == null) - return; - - _isSelecting = true; - var selected = SelectedChanges; - if (tree.Source.Selection is Models.TreeDataGridSelectionModel changeSelection) + if (change.Property == ViewModeProperty || change.Property == ChangesProperty) { - if (selected == null || selected.Count == 0) - changeSelection.Clear(); - else - changeSelection.Select(selected); - } - else if (tree.Source.Selection is Models.TreeDataGridSelectionModel treeSelection) - { - if (selected == null || selected.Count == 0) + _disableSelectionChangingEvent = change.Property == ChangesProperty; + var changes = Changes; + if (changes == null || changes.Count == 0) { - treeSelection.Clear(); - _isSelecting = false; + Content = null; + _displayContext = null; + _disableSelectionChangingEvent = false; return; } - var set = new HashSet(); - foreach (var c in selected) - set.Add(c); + if (ViewMode == Models.ChangeViewMode.Tree) + { + HashSet oldFolded = new HashSet(); + if (_displayContext is ChangeCollectionAsTree oldTree) + { + foreach (var row in oldTree.Rows) + { + if (row.IsFolder && !row.IsExpanded) + oldFolded.Add(row.FullPath); + } + } - var nodes = new List(); - foreach (var node in tree.Source.Items) - CollectSelectedNodeByChange(nodes, node as ChangeTreeNode, set); + var tree = new ChangeCollectionAsTree(); + tree.Tree = ChangeTreeNode.Build(changes, oldFolded); - if (nodes.Count == 0) - treeSelection.Clear(); + var rows = new List(); + MakeTreeRows(rows, tree.Tree); + tree.Rows.AddRange(rows); + _displayContext = tree; + } + else if (ViewMode == Models.ChangeViewMode.Grid) + { + var grid = new ChangeCollectionAsGrid(); + grid.Changes.AddRange(changes); + _displayContext = grid; + } else - treeSelection.Select(nodes); + { + var list = new ChangeCollectionAsList(); + list.Changes.AddRange(changes); + _displayContext = list; + } + + Content = _displayContext; + _disableSelectionChangingEvent = false; + } + else if (change.Property == SelectedChangesProperty) + { + if (_disableSelectionChangingEvent) + return; + + var list = this.FindDescendantOfType(); + if (list == null) + return; + + _disableSelectionChangingEvent = true; + + var selected = SelectedChanges; + if (selected == null || selected.Count == 0) + { + list.SelectedItem = null; + } + else if (_displayContext is ChangeCollectionAsTree tree) + { + var sets = new HashSet(); + foreach (var c in selected) + sets.Add(c); + + var nodes = new List(); + foreach (var row in tree.Rows) + { + if (row.Change != null && sets.Contains(row.Change)) + nodes.Add(row); + } + + list.SelectedItems = nodes; + } + else + { + list.SelectedItems = selected; + } + + _disableSelectionChangingEvent = false; } - _isSelecting = false; } - private void CreateTreeDataGrid(ITreeDataGridSource source) + private void OnRowDoubleTapped(object sender, TappedEventArgs e) { - Content = new TreeDataGrid() + var grid = sender as Grid; + if (grid.DataContext is ChangeTreeNode node) { - AutoDragDropRows = false, - ShowColumnHeaders = false, - CanUserResizeColumns = false, - CanUserSortColumns = false, - Source = source, - }; + if (node.IsFolder) + { + var posX = e.GetPosition(this).X; + if (posX < node.Depth * 16 + 16) + return; + + ToggleNodeIsExpanded(node); + } + else + { + RaiseEvent(new RoutedEventArgs(ChangeDoubleTappedEvent)); + } + } + else if (grid.DataContext is Models.Change) + { + RaiseEvent(new RoutedEventArgs(ChangeDoubleTappedEvent)); + } + } + + private void OnRowSelectionChanged(object sender, SelectionChangedEventArgs e) + { + if (_disableSelectionChangingEvent) + return; + + _disableSelectionChangingEvent = true; + var selected = new List(); + var list = sender as ListBox; + foreach (var item in list.SelectedItems) + { + if (item is Models.Change c) + selected.Add(c); + else if (item is ChangeTreeNode node) + CollectChangesInNode(selected, node); + } + TrySetSelected(selected); + _disableSelectionChangingEvent = false; + } + + private void MakeTreeRows(List rows, List nodes) + { + foreach (var node in nodes) + { + rows.Add(node); + + if (!node.IsExpanded || !node.IsFolder) + continue; + + MakeTreeRows(rows, node.Children); + } } private void CollectChangesInNode(List outs, ChangeTreeNode node) @@ -340,26 +435,9 @@ namespace SourceGit.Views } } - private void CollectSelectedNodeByChange(List outs, ChangeTreeNode node, HashSet selected) - { - if (node == null) - return; - - if (node.IsFolder) - { - foreach (var child in node.Children) - CollectSelectedNodeByChange(outs, child, selected); - } - else if (node.Change != null && selected.Contains(node.Change)) - { - outs.Add(node); - } - } - private void TrySetSelected(List changes) { var old = SelectedChanges; - if (old == null && changes.Count == 0) return; @@ -379,11 +457,12 @@ namespace SourceGit.Views return; } - _isSelecting = true; + _disableSelectionChangingEvent = true; SetCurrentValue(SelectedChangesProperty, changes); - _isSelecting = false; + _disableSelectionChangingEvent = false; } - private bool _isSelecting = false; + private bool _disableSelectionChangingEvent = false; + private object _displayContext = null; } } diff --git a/src/Views/CommitChanges.axaml b/src/Views/CommitChanges.axaml index fffb3bd3..a9ee868b 100644 --- a/src/Views/CommitChanges.axaml +++ b/src/Views/CommitChanges.axaml @@ -47,6 +47,7 @@ PreviewKeyDownEvent = - RoutedEvent.Register(nameof(KeyEventArgs), RoutingStrategies.Tunnel | RoutingStrategies.Bubble); + RoutedEvent.Register(nameof(KeyEventArgs), RoutingStrategies.Tunnel | RoutingStrategies.Bubble); public event EventHandler PreviewKeyDown { diff --git a/src/Views/RevisionFileTreeView.axaml.cs b/src/Views/RevisionFileTreeView.axaml.cs index 6a026276..a711ac46 100644 --- a/src/Views/RevisionFileTreeView.axaml.cs +++ b/src/Views/RevisionFileTreeView.axaml.cs @@ -170,7 +170,7 @@ namespace SourceGit.Views if (subtree != null && subtree.Count > 0) { var subrows = new List(); - MakeRows(subrows, node.Children, depth + 1); + MakeRows(subrows, subtree, depth + 1); _rows.InsertRange(idx + 1, subrows); } } diff --git a/src/Views/WorkingCopy.axaml b/src/Views/WorkingCopy.axaml index 8e77d9d8..a651c15b 100644 --- a/src/Views/WorkingCopy.axaml +++ b/src/Views/WorkingCopy.axaml @@ -65,7 +65,7 @@