using System; using System.Collections.Generic; using Avalonia.Collections; namespace SourceGit.ViewModels { public enum BranchTreeNodeType { Remote, Folder, Branch, } public class BranchTreeNode { public string Name { get; set; } public BranchTreeNodeType Type { get; set; } public object Backend { get; set; } public bool IsExpanded { 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 IsCurrent { get => IsBranch && (Backend as Models.Branch).IsCurrent; } public class Builder { public List Locals => _locals; public List Remotes => _remotes; public void Run(List branches, List remotes, bool bForceExpanded) { foreach (var remote in remotes) { var path = $"remote/{remote.Name}"; var node = new BranchTreeNode() { Name = remote.Name, Type = BranchTreeNodeType.Remote, Backend = remote, IsExpanded = bForceExpanded || _expanded.Contains(path), }; _maps.Add(path, node); _remotes.Add(node); } foreach (var branch in branches) { var isFiltered = _filters.Contains(branch.FullName); if (branch.IsLocal) { MakeBranchNode(branch, _locals, "local", isFiltered, bForceExpanded); } else { var remote = _remotes.Find(x => x.Name == branch.Remote); if (remote != null) MakeBranchNode(branch, remote.Children, $"remote/{remote.Name}", isFiltered, bForceExpanded); } } SortNodes(_locals); SortNodes(_remotes); } public void SetFilters(AvaloniaList filters) { _filters.AddRange(filters); } public void CollectExpandedNodes(List nodes, bool isLocal) { CollectExpandedNodes(nodes, isLocal ? "local" : "remote"); } private void CollectExpandedNodes(List nodes, string prefix) { foreach (var node in nodes) { var path = prefix + "/" + node.Name; if (node.Type != BranchTreeNodeType.Branch && node.IsExpanded) _expanded.Add(path); CollectExpandedNodes(node.Children, path); } } private void MakeBranchNode(Models.Branch branch, List roots, string prefix, bool isFiltered, bool bForceExpanded) { var subs = branch.Name.Split(new char[] { '/' }, StringSplitOptions.RemoveEmptyEntries); if (subs.Length == 1) { var node = new BranchTreeNode() { Name = subs[0], Type = BranchTreeNodeType.Branch, Backend = branch, IsExpanded = false, IsFiltered = isFiltered, }; roots.Add(node); return; } BranchTreeNode lastFolder = null; var path = prefix; for (var i = 0; i < subs.Length - 1; i++) { path = string.Concat(path, "/", subs[i]); if (_maps.TryGetValue(path, out var value)) { lastFolder = value; } else if (lastFolder == null) { lastFolder = new BranchTreeNode() { Name = subs[i], Type = BranchTreeNodeType.Folder, IsExpanded = bForceExpanded || branch.IsCurrent || _expanded.Contains(path), }; roots.Add(lastFolder); _maps.Add(path, lastFolder); } else { var folder = new BranchTreeNode() { Name = subs[i], Type = BranchTreeNodeType.Folder, IsExpanded = bForceExpanded || branch.IsCurrent || _expanded.Contains(path), }; _maps.Add(path, folder); lastFolder.Children.Add(folder); lastFolder = folder; } } var last = new BranchTreeNode() { Name = subs[subs.Length - 1], Type = BranchTreeNodeType.Branch, Backend = branch, IsExpanded = false, IsFiltered = isFiltered, }; lastFolder.Children.Add(last); } private void SortNodes(List nodes) { nodes.Sort((l, r) => { if (l.Type == r.Type) { return l.Name.CompareTo(r.Name); } else { return (int)l.Type - (int)r.Type; } }); foreach (var node in nodes) SortNodes(node.Children); } private readonly List _locals = new List(); private readonly List _remotes = new List(); private readonly HashSet _expanded = new HashSet(); private readonly List _filters = new List(); private readonly Dictionary _maps = new Dictionary(); } } }