mirror of
https://github.com/sourcegit-scm/sourcegit.git
synced 2024-12-23 20:47:25 -08:00
feature: new way to expand/collapse folder node in TreeDataGrid
This commit is contained in:
parent
f4d379e3b8
commit
55c9fae110
10 changed files with 58 additions and 73 deletions
|
@ -1,24 +1,17 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
|
||||
namespace SourceGit.ViewModels
|
||||
namespace SourceGit.Models
|
||||
{
|
||||
public class FileTreeNode : ObservableObject
|
||||
public class FileTreeNode
|
||||
{
|
||||
public string FullPath { get; set; } = string.Empty;
|
||||
public bool IsFolder { get; set; } = false;
|
||||
public bool IsExpanded { get; set; } = false;
|
||||
public object Backend { get; set; } = null;
|
||||
public List<FileTreeNode> Children { get; set; } = new List<FileTreeNode>();
|
||||
|
||||
public bool IsExpanded
|
||||
{
|
||||
get => _isExpanded;
|
||||
set => SetProperty(ref _isExpanded, value);
|
||||
}
|
||||
|
||||
public static List<FileTreeNode> Build(List<Models.Change> changes, bool expanded)
|
||||
public static List<FileTreeNode> Build(List<Change> changes, bool expanded)
|
||||
{
|
||||
var nodes = new List<FileTreeNode>();
|
||||
var folders = new Dictionary<string, FileTreeNode>();
|
||||
|
@ -93,7 +86,7 @@ namespace SourceGit.ViewModels
|
|||
return nodes;
|
||||
}
|
||||
|
||||
public static List<FileTreeNode> Build(List<Models.Object> files, bool expanded)
|
||||
public static List<FileTreeNode> Build(List<Object> files, bool expanded)
|
||||
{
|
||||
var nodes = new List<FileTreeNode>();
|
||||
var folders = new Dictionary<string, FileTreeNode>();
|
||||
|
@ -168,27 +161,6 @@ namespace SourceGit.ViewModels
|
|||
return nodes;
|
||||
}
|
||||
|
||||
public static FileTreeNode SelectByPath(List<FileTreeNode> nodes, string path)
|
||||
{
|
||||
foreach (var node in nodes)
|
||||
{
|
||||
if (node.FullPath == path)
|
||||
return node;
|
||||
|
||||
if (node.IsFolder && path.StartsWith(node.FullPath + "/", StringComparison.Ordinal))
|
||||
{
|
||||
var foundInChildren = SelectByPath(node.Children, path);
|
||||
if (foundInChildren != null)
|
||||
{
|
||||
node.IsExpanded = true;
|
||||
}
|
||||
return foundInChildren;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static void Sort(List<FileTreeNode> nodes)
|
||||
{
|
||||
nodes.Sort((l, r) =>
|
||||
|
@ -209,7 +181,5 @@ namespace SourceGit.ViewModels
|
|||
Sort(node.Children);
|
||||
}
|
||||
}
|
||||
|
||||
private bool _isExpanded = true;
|
||||
}
|
||||
}
|
|
@ -188,7 +188,12 @@ namespace SourceGit.Models
|
|||
}
|
||||
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)
|
||||
|
@ -420,7 +425,22 @@ namespace SourceGit.Models
|
|||
|
||||
protected override IEnumerable<TModel> GetChildren(TModel node)
|
||||
{
|
||||
if (node == null)
|
||||
return null;
|
||||
|
||||
return _childrenGetter?.Invoke(node);
|
||||
}
|
||||
|
||||
private bool HasChildren(IRow row)
|
||||
{
|
||||
var children = GetChildren(row.Model as TModel);
|
||||
if (children != null)
|
||||
{
|
||||
foreach (var c in children)
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1123,6 +1123,10 @@
|
|||
</Style>
|
||||
</Style>
|
||||
|
||||
<Style Selector="TreeDataGridRow">
|
||||
<Setter Property="Background" Value="Transparent"/>
|
||||
</Style>
|
||||
|
||||
<Style Selector="TreeDataGridColumnHeader">
|
||||
<Setter Property="HorizontalContentAlignment" Value="Center"/>
|
||||
</Style>
|
||||
|
|
|
@ -5,7 +5,6 @@ using System.Threading.Tasks;
|
|||
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Controls.Models.TreeDataGrid;
|
||||
using Avalonia.Interactivity;
|
||||
using Avalonia.Media.Imaging;
|
||||
using Avalonia.Platform.Storage;
|
||||
using Avalonia.Threading;
|
||||
|
@ -77,7 +76,7 @@ namespace SourceGit.ViewModels
|
|||
}
|
||||
}
|
||||
|
||||
public HierarchicalTreeDataGridSource<FileTreeNode> RevisionFiles
|
||||
public HierarchicalTreeDataGridSource<Models.FileTreeNode> RevisionFiles
|
||||
{
|
||||
get => _revisionFiles;
|
||||
private set => SetProperty(ref _revisionFiles, value);
|
||||
|
@ -336,7 +335,6 @@ namespace SourceGit.ViewModels
|
|||
}
|
||||
}
|
||||
|
||||
var tree = FileTreeNode.Build(visible, true);
|
||||
Dispatcher.UIThread.Invoke(() =>
|
||||
{
|
||||
Changes = changes;
|
||||
|
@ -362,7 +360,7 @@ namespace SourceGit.ViewModels
|
|||
}
|
||||
}
|
||||
|
||||
var tree = FileTreeNode.Build(visible, isSearching || visible.Count <= 100);
|
||||
var tree = Models.FileTreeNode.Build(visible, isSearching || visible.Count <= 100);
|
||||
Dispatcher.UIThread.Invoke(() => BuildRevisionFilesSource(tree));
|
||||
});
|
||||
}
|
||||
|
@ -406,7 +404,7 @@ namespace SourceGit.ViewModels
|
|||
}
|
||||
}
|
||||
|
||||
BuildRevisionFilesSource(FileTreeNode.Build(visible, isSearching || visible.Count < 100));
|
||||
BuildRevisionFilesSource(Models.FileTreeNode.Build(visible, isSearching || visible.Count < 100));
|
||||
}
|
||||
|
||||
private void RefreshViewRevisionFile(Models.Object file)
|
||||
|
@ -493,29 +491,29 @@ namespace SourceGit.ViewModels
|
|||
}
|
||||
}
|
||||
|
||||
private void BuildRevisionFilesSource(List<FileTreeNode> tree)
|
||||
private void BuildRevisionFilesSource(List<Models.FileTreeNode> tree)
|
||||
{
|
||||
var source = new HierarchicalTreeDataGridSource<FileTreeNode>(tree)
|
||||
var source = new HierarchicalTreeDataGridSource<Models.FileTreeNode>(tree)
|
||||
{
|
||||
Columns =
|
||||
{
|
||||
new HierarchicalExpanderColumn<FileTreeNode>(
|
||||
new TemplateColumn<FileTreeNode>("Icon", "FileTreeNodeExpanderTemplate", null, GridLength.Auto),
|
||||
new HierarchicalExpanderColumn<Models.FileTreeNode>(
|
||||
new TemplateColumn<Models.FileTreeNode>("Icon", "FileTreeNodeExpanderTemplate", null, GridLength.Auto),
|
||||
x => x.Children,
|
||||
x => x.Children.Count > 0,
|
||||
x => x.IsExpanded),
|
||||
new TextColumn<FileTreeNode, string>(
|
||||
new TextColumn<Models.FileTreeNode, string>(
|
||||
null,
|
||||
x => string.Empty,
|
||||
GridLength.Star)
|
||||
}
|
||||
};
|
||||
|
||||
var selection = new Models.TreeDataGridSelectionModel<FileTreeNode>(source, x => x.Children);
|
||||
var selection = new Models.TreeDataGridSelectionModel<Models.FileTreeNode>(source, x => x.Children);
|
||||
selection.SingleSelect = true;
|
||||
selection.SelectionChanged += (s, _) =>
|
||||
{
|
||||
if (s is Models.TreeDataGridSelectionModel<FileTreeNode> selection)
|
||||
if (s is Models.TreeDataGridSelectionModel<Models.FileTreeNode> selection)
|
||||
RefreshViewRevisionFile(selection.SelectedItem?.Backend as Models.Object);
|
||||
};
|
||||
|
||||
|
@ -537,7 +535,7 @@ namespace SourceGit.ViewModels
|
|||
private string _searchChangeFilter = string.Empty;
|
||||
private DiffContext _diffContext = null;
|
||||
private List<Models.Object> _revisionFilesBackup = null;
|
||||
private HierarchicalTreeDataGridSource<FileTreeNode> _revisionFiles = null;
|
||||
private HierarchicalTreeDataGridSource<Models.FileTreeNode> _revisionFiles = null;
|
||||
private string _searchFileFilter = string.Empty;
|
||||
private object _viewRevisionFileContent = null;
|
||||
private Commands.Command.CancelToken _cancelToken = null;
|
||||
|
|
|
@ -389,6 +389,7 @@ namespace SourceGit.ViewModels
|
|||
App.RaiseException(_fullpath, "Can NOT found current branch!!!");
|
||||
return;
|
||||
}
|
||||
|
||||
PopupHost.ShowPopup(new Push(this, null));
|
||||
}
|
||||
|
||||
|
|
|
@ -99,11 +99,7 @@ namespace SourceGit.ViewModels
|
|||
}
|
||||
}
|
||||
|
||||
var tree = FileTreeNode.Build(visible, true);
|
||||
Dispatcher.UIThread.Invoke(() =>
|
||||
{
|
||||
VisibleChanges = visible;
|
||||
});
|
||||
Dispatcher.UIThread.Invoke(() => VisibleChanges = visible);
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
CanUserSortColumns="False"
|
||||
ScrollViewer.BringIntoViewOnFocusChange="True">
|
||||
<TreeDataGrid.Resources>
|
||||
<DataTemplate x:Key="TreeModeTemplate" DataType="vm:FileTreeNode">
|
||||
<DataTemplate x:Key="TreeModeTemplate" DataType="m:FileTreeNode">
|
||||
<Grid HorizontalAlignment="Stretch" Height="24" ColumnDefinitions="Auto,*">
|
||||
<Path Grid.Column="0" Classes="folder_icon" Width="14" Height="14" Margin="0,2,0,0" IsVisible="{Binding IsFolder}" Fill="Goldenrod" VerticalAlignment="Center"/>
|
||||
<v:ChangeStatusIcon Grid.Column="0" Width="14" Height="14" IsWorkingCopyChange="{Binding #me.IsWorkingCopyChange}" Change="{Binding Backend}" IsVisible="{Binding !IsFolder}"/>
|
||||
|
|
|
@ -91,29 +91,25 @@ namespace SourceGit.Views
|
|||
var viewMode = ViewMode;
|
||||
if (viewMode == Models.ChangeViewMode.Tree)
|
||||
{
|
||||
var filetree = ViewModels.FileTreeNode.Build(changes, true);
|
||||
var source = new HierarchicalTreeDataGridSource<ViewModels.FileTreeNode>(filetree)
|
||||
var filetree = Models.FileTreeNode.Build(changes, true);
|
||||
var source = new HierarchicalTreeDataGridSource<Models.FileTreeNode>(filetree)
|
||||
{
|
||||
Columns =
|
||||
{
|
||||
new HierarchicalExpanderColumn<ViewModels.FileTreeNode>(
|
||||
new TemplateColumn<ViewModels.FileTreeNode>(null, "TreeModeTemplate", null, GridLength.Auto),
|
||||
new HierarchicalExpanderColumn<Models.FileTreeNode>(
|
||||
new TemplateColumn<Models.FileTreeNode>(null, "TreeModeTemplate", null, GridLength.Auto),
|
||||
x => x.Children,
|
||||
x => x.Children.Count > 0,
|
||||
x => x.IsExpanded),
|
||||
new TextColumn<ViewModels.FileTreeNode, string>(
|
||||
null,
|
||||
x => string.Empty,
|
||||
GridLength.Star)
|
||||
x => x.IsExpanded)
|
||||
}
|
||||
};
|
||||
|
||||
var selection = new Models.TreeDataGridSelectionModel<ViewModels.FileTreeNode>(source, x => x.Children);
|
||||
var selection = new Models.TreeDataGridSelectionModel<Models.FileTreeNode>(source, x => x.Children);
|
||||
selection.SingleSelect = SingleSelect;
|
||||
selection.RowDoubleTapped += (_, e) => RaiseEvent(new RoutedEventArgs(ChangeDoubleTappedEvent));
|
||||
selection.SelectionChanged += (s, _) =>
|
||||
{
|
||||
if (!_isSelecting && s is Models.TreeDataGridSelectionModel<ViewModels.FileTreeNode> model)
|
||||
if (!_isSelecting && s is Models.TreeDataGridSelectionModel<Models.FileTreeNode> model)
|
||||
{
|
||||
var selection = new List<Models.Change>();
|
||||
foreach (var c in model.SelectedItems)
|
||||
|
@ -202,7 +198,7 @@ namespace SourceGit.Views
|
|||
else
|
||||
changeSelection.Select(selected);
|
||||
}
|
||||
else if (tree.Source.Selection is Models.TreeDataGridSelectionModel<ViewModels.FileTreeNode> treeSelection)
|
||||
else if (tree.Source.Selection is Models.TreeDataGridSelectionModel<Models.FileTreeNode> treeSelection)
|
||||
{
|
||||
if (selected == null || selected.Count == 0)
|
||||
{
|
||||
|
@ -215,9 +211,9 @@ namespace SourceGit.Views
|
|||
foreach (var c in selected)
|
||||
set.Add(c);
|
||||
|
||||
var nodes = new List<ViewModels.FileTreeNode>();
|
||||
var nodes = new List<Models.FileTreeNode>();
|
||||
foreach (var node in tree.Source.Items)
|
||||
CollectSelectedNodeByChange(nodes, node as ViewModels.FileTreeNode, set);
|
||||
CollectSelectedNodeByChange(nodes, node as Models.FileTreeNode, set);
|
||||
|
||||
if (nodes.Count == 0)
|
||||
{
|
||||
|
@ -231,7 +227,7 @@ namespace SourceGit.Views
|
|||
_isSelecting = false;
|
||||
}
|
||||
|
||||
private void CollectChangesInNode(List<Models.Change> outs, ViewModels.FileTreeNode node)
|
||||
private void CollectChangesInNode(List<Models.Change> outs, Models.FileTreeNode node)
|
||||
{
|
||||
if (node.IsFolder)
|
||||
{
|
||||
|
@ -246,7 +242,7 @@ namespace SourceGit.Views
|
|||
}
|
||||
}
|
||||
|
||||
private void CollectSelectedNodeByChange(List<ViewModels.FileTreeNode> outs, ViewModels.FileTreeNode node, HashSet<object> selected)
|
||||
private void CollectSelectedNodeByChange(List<Models.FileTreeNode> outs, Models.FileTreeNode node, HashSet<object> selected)
|
||||
{
|
||||
if (node == null)
|
||||
return;
|
||||
|
|
|
@ -48,7 +48,7 @@
|
|||
Source="{Binding RevisionFiles}"
|
||||
ContextRequested="OnFileContextRequested">
|
||||
<TreeDataGrid.Resources>
|
||||
<DataTemplate x:Key="FileTreeNodeExpanderTemplate" DataType="vm:FileTreeNode">
|
||||
<DataTemplate x:Key="FileTreeNodeExpanderTemplate" DataType="m:FileTreeNode">
|
||||
<Grid HorizontalAlignment="Stretch" Height="24" ColumnDefinitions="Auto,*">
|
||||
<Path Grid.Column="0" Classes="folder_icon" Width="14" Height="14" Margin="0,2,0,0" IsVisible="{Binding IsFolder}" Fill="Goldenrod" VerticalAlignment="Center"/>
|
||||
<Path Grid.Column="0" Width="14" Height="14" IsVisible="{Binding !IsFolder}" Data="{StaticResource Icons.File}" VerticalAlignment="Center"/>
|
||||
|
|
|
@ -217,7 +217,7 @@ namespace SourceGit.Views
|
|||
{
|
||||
if (DataContext is ViewModels.CommitDetail vm && sender is TreeDataGrid tree)
|
||||
{
|
||||
var selected = tree.RowSelection.SelectedItem as ViewModels.FileTreeNode;
|
||||
var selected = tree.RowSelection.SelectedItem as Models.FileTreeNode;
|
||||
if (selected != null && !selected.IsFolder && selected.Backend is Models.Object obj)
|
||||
{
|
||||
var menu = vm.CreateRevisionFileContextMenu(obj);
|
||||
|
|
Loading…
Reference in a new issue