Skip to content
This repository was archived by the owner on Dec 5, 2024. It is now read-only.

Commit f528378

Browse files
Merge pull request #513 from github-for-unity/fixes/refactor-tree-view
Refactor TreeView
2 parents 11686f5 + 74c8358 commit f528378

File tree

9 files changed

+500
-396
lines changed

9 files changed

+500
-396
lines changed

src/GitHub.Api/Git/RepositoryManager.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -446,6 +446,7 @@ private void WatcherOnLocalBranchesChanged()
446446
{
447447
Logger.Trace("WatcherOnLocalBranchesChanged");
448448
UpdateLocalBranches();
449+
UpdateGitLog();
449450
}
450451

451452
private void WatcherOnRepositoryCommitted()

src/GitHub.Api/GitHub.Api.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -238,8 +238,8 @@
238238
<Compile Include="Git\GitCredentialManager.cs" />
239239
<Compile Include="UI\FileTreeNode.cs" />
240240
<Compile Include="UI\GitCommitTarget.cs" />
241+
<Compile Include="UI\TreeBase.cs" />
241242
<Compile Include="UI\TreeBuilder.cs" />
242-
<Compile Include="UI\TreeLoader.cs" />
243243
</ItemGroup>
244244
<Choose>
245245
<When Condition="$(Buildtype) == 'Internal'">

src/GitHub.Api/UI/TreeBase.cs

Lines changed: 302 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,302 @@
1+
using System;
2+
using System.Collections.Generic;
3+
4+
namespace GitHub.Unity
5+
{
6+
public enum CheckState
7+
{
8+
Empty,
9+
Checked,
10+
Mixed
11+
}
12+
13+
public interface ITreeNode
14+
{
15+
string Path { get; set; }
16+
string Label { get; set; }
17+
int Level { get; set; }
18+
bool IsFolder { get; set; }
19+
bool IsCollapsed { get; set; }
20+
bool IsHidden { get; set; }
21+
bool IsActive { get; set; }
22+
bool TreeIsCheckable { get; set; }
23+
CheckState CheckState { get; set; }
24+
}
25+
26+
public abstract class TreeBase<TNode, TData> where TNode : class, ITreeNode where TData : struct, ITreeData
27+
{
28+
public abstract IEnumerable<string> GetCheckedFiles();
29+
30+
public void Load(IEnumerable<TData> treeDatas)
31+
{
32+
var collapsedFolders = new HashSet<string>(GetCollapsedFolders());
33+
var selectedNodePath = SelectedNodePath;
34+
35+
Clear();
36+
37+
var displayRootLevel = DisplayRootNode ? 1 : 0;
38+
39+
var isSelected = selectedNodePath != null && Title == selectedNodePath;
40+
AddNode(Title, Title, -1 + displayRootLevel, true, false, false, false, isSelected, null);
41+
42+
var hideChildren = false;
43+
var hideChildrenBelowLevel = 0;
44+
45+
var folders = new HashSet<string>();
46+
47+
foreach (var treeData in treeDatas)
48+
{
49+
var parts = treeData.Path.Split(new[] { PathSeparator }, StringSplitOptions.None);
50+
for (var i = 0; i < parts.Length; i++)
51+
{
52+
var label = parts[i];
53+
var level = i + 1;
54+
var nodePath = String.Join(PathSeparator, parts, 0, level);
55+
var isFolder = i < parts.Length - 1;
56+
var alreadyExists = folders.Contains(nodePath);
57+
if (!alreadyExists)
58+
{
59+
var nodeIsHidden = false;
60+
if (hideChildren)
61+
{
62+
if (level <= hideChildrenBelowLevel)
63+
{
64+
hideChildren = false;
65+
}
66+
else
67+
{
68+
nodeIsHidden = true;
69+
}
70+
}
71+
72+
var isActive = false;
73+
var nodeIsCollapsed = false;
74+
TData? treeNodeTreeData = null;
75+
76+
if (isFolder)
77+
{
78+
folders.Add(nodePath);
79+
80+
if (collapsedFolders.Contains(nodePath))
81+
{
82+
nodeIsCollapsed = true;
83+
84+
if (!hideChildren)
85+
{
86+
hideChildren = true;
87+
hideChildrenBelowLevel = level;
88+
}
89+
}
90+
}
91+
else
92+
{
93+
isActive = treeData.IsActive;
94+
treeNodeTreeData = treeData;
95+
}
96+
97+
isSelected = selectedNodePath != null && nodePath == selectedNodePath;
98+
AddNode(nodePath, label, i + displayRootLevel, isFolder, isActive, nodeIsHidden,
99+
nodeIsCollapsed, isSelected, treeNodeTreeData);
100+
}
101+
}
102+
}
103+
}
104+
105+
protected abstract IEnumerable<string> GetCollapsedFolders();
106+
107+
protected void AddNode(string path, string label, int level, bool isFolder, bool isActive, bool isHidden,
108+
bool isCollapsed, bool isSelected, TData? treeData)
109+
{
110+
var node = CreateTreeNode(path, label, level, isFolder, isActive, isHidden, isCollapsed, treeData);
111+
112+
SetNodeIcon(node);
113+
Nodes.Add(node);
114+
115+
if (isSelected)
116+
{
117+
SelectedNode = node;
118+
}
119+
}
120+
121+
protected void Clear()
122+
{
123+
OnClear();
124+
Nodes.Clear();
125+
SelectedNode = null;
126+
}
127+
128+
protected abstract void RemoveCheckedNode(TNode node);
129+
130+
protected abstract void AddCheckedNode(TNode node);
131+
132+
protected abstract TNode CreateTreeNode(string path, string label, int level, bool isFolder, bool isActive,
133+
bool isHidden, bool isCollapsed, TData? treeData);
134+
135+
protected abstract void OnClear();
136+
137+
protected abstract void SetNodeIcon(TNode node);
138+
139+
protected void ToggleNodeVisibility(int idx, TNode node)
140+
{
141+
var nodeLevel = node.Level;
142+
node.IsCollapsed = !node.IsCollapsed;
143+
idx++;
144+
for (; idx < Nodes.Count && Nodes[idx].Level > nodeLevel; idx++)
145+
{
146+
Nodes[idx].IsHidden = node.IsCollapsed;
147+
if (Nodes[idx].IsFolder && !node.IsCollapsed && Nodes[idx].IsCollapsed)
148+
{
149+
var level = Nodes[idx].Level;
150+
for (idx++; idx < Nodes.Count && Nodes[idx].Level > level; idx++)
151+
{ }
152+
153+
idx--;
154+
}
155+
}
156+
157+
if (SelectedNode != null && SelectedNode.IsHidden)
158+
{
159+
SelectedNode = node;
160+
}
161+
}
162+
163+
protected void ToggleNodeChecked(int idx, TNode node)
164+
{
165+
var isChecked = false;
166+
167+
switch (node.CheckState)
168+
{
169+
case CheckState.Mixed:
170+
case CheckState.Empty:
171+
node.CheckState = CheckState.Checked;
172+
isChecked = true;
173+
break;
174+
175+
case CheckState.Checked:
176+
node.CheckState = CheckState.Empty;
177+
break;
178+
}
179+
180+
if (node.IsFolder)
181+
{
182+
ToggleChildrenChecked(idx, node, isChecked);
183+
}
184+
else
185+
{
186+
if (isChecked)
187+
{
188+
AddCheckedNode(node);
189+
}
190+
else
191+
{
192+
RemoveCheckedNode(node);
193+
}
194+
}
195+
196+
ToggleParentFoldersChecked(idx, node, isChecked);
197+
}
198+
199+
private void ToggleChildrenChecked(int idx, TNode node, bool isChecked)
200+
{
201+
for (var i = idx + 1; i < Nodes.Count && node.Level < Nodes[i].Level; i++)
202+
{
203+
var childNode = Nodes[i];
204+
var wasChecked = childNode.CheckState == CheckState.Checked;
205+
childNode.CheckState = isChecked ? CheckState.Checked : CheckState.Empty;
206+
207+
if (childNode.IsFolder)
208+
{
209+
ToggleChildrenChecked(i, childNode, isChecked);
210+
}
211+
else
212+
{
213+
if (isChecked && !wasChecked)
214+
{
215+
AddCheckedNode(childNode);
216+
}
217+
else if (!isChecked && wasChecked)
218+
{
219+
RemoveCheckedNode(childNode);
220+
}
221+
}
222+
}
223+
}
224+
225+
private void ToggleParentFoldersChecked(int idx, TNode node, bool isChecked)
226+
{
227+
while (true)
228+
{
229+
if (node.Level > 0)
230+
{
231+
var siblingsInSameState = true;
232+
var firstSiblingIndex = idx;
233+
234+
for (var i = idx - 1; i > 0 && node.Level <= Nodes[i].Level; i--)
235+
{
236+
var previousNode = Nodes[i];
237+
if (node.Level < previousNode.Level)
238+
{
239+
continue;
240+
}
241+
242+
firstSiblingIndex = i;
243+
244+
if (siblingsInSameState)
245+
{
246+
var previousNodeIsChecked = previousNode.CheckState == CheckState.Checked;
247+
248+
if (isChecked != previousNodeIsChecked)
249+
{
250+
siblingsInSameState = false;
251+
}
252+
}
253+
}
254+
255+
if (siblingsInSameState)
256+
{
257+
for (var i = idx + 1; i < Nodes.Count && node.Level <= Nodes[i].Level; i++)
258+
{
259+
var followingNode = Nodes[i];
260+
if (node.Level < followingNode.Level)
261+
{
262+
continue;
263+
}
264+
265+
var followingNodeIsChecked = followingNode.CheckState == CheckState.Checked;
266+
if (isChecked != followingNodeIsChecked)
267+
{
268+
siblingsInSameState = false;
269+
break;
270+
}
271+
}
272+
}
273+
274+
var parentIndex = firstSiblingIndex - 1;
275+
var parentNode = Nodes[parentIndex];
276+
if (siblingsInSameState)
277+
{
278+
parentNode.CheckState = isChecked ? CheckState.Checked : CheckState.Empty;
279+
}
280+
else
281+
{
282+
parentNode.CheckState = CheckState.Mixed;
283+
}
284+
285+
idx = parentIndex;
286+
node = parentNode;
287+
continue;
288+
}
289+
290+
break;
291+
}
292+
}
293+
294+
public string SelectedNodePath => SelectedNode?.Path;
295+
public abstract TNode SelectedNode { get; set; }
296+
protected abstract List<TNode> Nodes { get; }
297+
public abstract string Title { get; set; }
298+
public abstract bool DisplayRootNode { get; set; }
299+
public abstract bool IsCheckable { get; set; }
300+
public abstract string PathSeparator { get; set; }
301+
}
302+
}

0 commit comments

Comments
 (0)