feature: new way to expand/collapse folder node in TreeDataGrid

This commit is contained in:
leo 2024-05-30 09:53:07 +08:00
parent f4d379e3b8
commit 55c9fae110
10 changed files with 58 additions and 73 deletions

View file

@ -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;

View file

@ -1,215 +0,0 @@
using System;
using System.Collections.Generic;
using CommunityToolkit.Mvvm.ComponentModel;
namespace SourceGit.ViewModels
{
public class FileTreeNode : ObservableObject
{
public string FullPath { get; set; } = string.Empty;
public bool IsFolder { 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)
{
var nodes = new List<FileTreeNode>();
var folders = new Dictionary<string, FileTreeNode>();
foreach (var c in changes)
{
var sepIdx = c.Path.IndexOf('/', StringComparison.Ordinal);
if (sepIdx == -1)
{
nodes.Add(new FileTreeNode()
{
FullPath = c.Path,
Backend = c,
IsFolder = false,
IsExpanded = false
});
}
else
{
FileTreeNode lastFolder = null;
var start = 0;
while (sepIdx != -1)
{
var folder = c.Path.Substring(0, sepIdx);
if (folders.TryGetValue(folder, out var value))
{
lastFolder = value;
}
else if (lastFolder == null)
{
lastFolder = new FileTreeNode()
{
FullPath = folder,
Backend = null,
IsFolder = true,
IsExpanded = expanded
};
nodes.Add(lastFolder);
folders.Add(folder, lastFolder);
}
else
{
var cur = new FileTreeNode()
{
FullPath = folder,
Backend = null,
IsFolder = true,
IsExpanded = expanded
};
folders.Add(folder, cur);
lastFolder.Children.Add(cur);
lastFolder = cur;
}
start = sepIdx + 1;
sepIdx = c.Path.IndexOf('/', start);
}
lastFolder.Children.Add(new FileTreeNode()
{
FullPath = c.Path,
Backend = c,
IsFolder = false,
IsExpanded = false
});
}
}
folders.Clear();
Sort(nodes);
return nodes;
}
public static List<FileTreeNode> Build(List<Models.Object> files, bool expanded)
{
var nodes = new List<FileTreeNode>();
var folders = new Dictionary<string, FileTreeNode>();
foreach (var f in files)
{
var sepIdx = f.Path.IndexOf('/', StringComparison.Ordinal);
if (sepIdx == -1)
{
nodes.Add(new FileTreeNode()
{
FullPath = f.Path,
Backend = f,
IsFolder = false,
IsExpanded = false
});
}
else
{
FileTreeNode lastFolder = null;
var start = 0;
while (sepIdx != -1)
{
var folder = f.Path.Substring(0, sepIdx);
if (folders.TryGetValue(folder, out var value))
{
lastFolder = value;
}
else if (lastFolder == null)
{
lastFolder = new FileTreeNode()
{
FullPath = folder,
Backend = null,
IsFolder = true,
IsExpanded = expanded
};
nodes.Add(lastFolder);
folders.Add(folder, lastFolder);
}
else
{
var cur = new FileTreeNode()
{
FullPath = folder,
Backend = null,
IsFolder = true,
IsExpanded = expanded
};
folders.Add(folder, cur);
lastFolder.Children.Add(cur);
lastFolder = cur;
}
start = sepIdx + 1;
sepIdx = f.Path.IndexOf('/', start);
}
lastFolder.Children.Add(new FileTreeNode()
{
FullPath = f.Path,
Backend = f,
IsFolder = false,
IsExpanded = false
});
}
}
folders.Clear();
Sort(nodes);
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) =>
{
if (l.IsFolder == r.IsFolder)
{
return l.FullPath.CompareTo(r.FullPath);
}
else
{
return l.IsFolder ? -1 : 1;
}
});
foreach (var node in nodes)
{
if (node.Children.Count > 1)
Sort(node.Children);
}
}
private bool _isExpanded = true;
}
}

View file

@ -389,6 +389,7 @@ namespace SourceGit.ViewModels
App.RaiseException(_fullpath, "Can NOT found current branch!!!");
return;
}
PopupHost.ShowPopup(new Push(this, null));
}

View file

@ -99,11 +99,7 @@ namespace SourceGit.ViewModels
}
}
var tree = FileTreeNode.Build(visible, true);
Dispatcher.UIThread.Invoke(() =>
{
VisibleChanges = visible;
});
Dispatcher.UIThread.Invoke(() => VisibleChanges = visible);
});
}