mirror of
				https://github.com/nsnail/spectre.console.git
				synced 2025-11-01 01:25:27 +08:00 
			
		
		
		
	Fix tree rendering
Fixes some tree rendering problems where lines were not properly drawn at some levels during some circumstances. * Change the API back to only allow one root. * Now uses a stack based approach to rendering instead of recursion. * Removes the need for measuring the whole tree in advance. Leave this up to each child to render.
This commit is contained in:
		 Patrik Svensson
					Patrik Svensson
				
			
				
					committed by
					
						 Patrik Svensson
						Patrik Svensson
					
				
			
			
				
	
			
			
			 Patrik Svensson
						Patrik Svensson
					
				
			
						parent
						
							0e0f4b4220
						
					
				
				
					commit
					8261b25e5c
				
			| @@ -1,43 +0,0 @@ | ||||
| ├── Root node | ||||
| │   ├── child1 | ||||
| │   │   ├── multiple  | ||||
| │   │   │    line 0 | ||||
| │   │   ├── multiple  | ||||
| │   │   │    line 1 | ||||
| │   │   ├── multiple  | ||||
| │   │   │    line 2 | ||||
| │   │   ├── multiple  | ||||
| │   │   │    line 3 | ||||
| │   │   ├── multiple  | ||||
| │   │   │    line 4 | ||||
| │   │   ├── multiple  | ||||
| │   │   │    line 5 | ||||
| │   │   ├── multiple  | ||||
| │   │   │    line 6 | ||||
| │   │   ├── multiple  | ||||
| │   │   │    line 7 | ||||
| │   │   ├── multiple  | ||||
| │   │   │    line 8 | ||||
| │   │   └── multiple  | ||||
| │   │        line 9 | ||||
| │   ├── child2 | ||||
| │   │   └── child2Child | ||||
| │   │       └── Child 2 child | ||||
| │   │            child | ||||
| │   └── child3 | ||||
| │       └── single leaf | ||||
| │            multiline | ||||
| │           └──                2020 January                 | ||||
| │               ┌─────┬─────┬─────┬─────┬─────┬─────┬─────┐ | ||||
| │               │ Sun │ Mon │ Tue │ Wed │ Thu │ Fri │ Sat │ | ||||
| │               ├─────┼─────┼─────┼─────┼─────┼─────┼─────┤ | ||||
| │               │     │     │     │ 1   │ 2   │ 3   │ 4   │ | ||||
| │               │ 5   │ 6   │ 7   │ 8   │ 9   │ 10  │ 11  │ | ||||
| │               │ 12  │ 13  │ 14  │ 15  │ 16  │ 17  │ 18  │ | ||||
| │               │ 19  │ 20  │ 21  │ 22  │ 23  │ 24  │ 25  │ | ||||
| │               │ 26  │ 27  │ 28  │ 29  │ 30  │ 31  │     │ | ||||
| │               │     │     │     │     │     │     │     │ | ||||
| │               └─────┴─────┴─────┴─────┴─────┴─────┴─────┘ | ||||
| └── child2Child | ||||
|     └── Child 2 child | ||||
|          child | ||||
| @@ -0,0 +1,41 @@ | ||||
| Root node | ||||
| ╠══ child1 | ||||
| ║   ╠══ multiple | ||||
| ║   ║   line 0 | ||||
| ║   ╠══ multiple | ||||
| ║   ║   line 1 | ||||
| ║   ╠══ multiple | ||||
| ║   ║   line 2 | ||||
| ║   ╠══ multiple | ||||
| ║   ║   line 3 | ||||
| ║   ╠══ multiple | ||||
| ║   ║   line 4 | ||||
| ║   ╠══ multiple | ||||
| ║   ║   line 5 | ||||
| ║   ╠══ multiple | ||||
| ║   ║   line 6 | ||||
| ║   ╠══ multiple | ||||
| ║   ║   line 7 | ||||
| ║   ╠══ multiple | ||||
| ║   ║   line 8 | ||||
| ║   ╚══ multiple | ||||
| ║       line 9 | ||||
| ╠══ child2 | ||||
| ║   ╚══ child2-1 | ||||
| ║       ╚══ Child2-1-1 | ||||
| ║           child | ||||
| ╠══ child3 | ||||
| ║   ╚══ single leaf | ||||
| ║       multiline | ||||
| ║       ╚══                2021 January                 | ||||
| ║           ┌─────┬─────┬─────┬─────┬─────┬─────┬─────┐ | ||||
| ║           │ Sun │ Mon │ Tue │ Wed │ Thu │ Fri │ Sat │ | ||||
| ║           ├─────┼─────┼─────┼─────┼─────┼─────┼─────┤ | ||||
| ║           │     │     │     │     │     │ 1   │ 2   │ | ||||
| ║           │ 3   │ 4   │ 5   │ 6   │ 7   │ 8   │ 9   │ | ||||
| ║           │ 10  │ 11  │ 12  │ 13  │ 14  │ 15  │ 16  │ | ||||
| ║           │ 17  │ 18  │ 19  │ 20  │ 21  │ 22  │ 23  │ | ||||
| ║           │ 24  │ 25  │ 26  │ 27  │ 28  │ 29  │ 30  │ | ||||
| ║           │ 31  │     │     │     │     │     │     │ | ||||
| ║           └─────┴─────┴─────┴─────┴─────┴─────┴─────┘ | ||||
| ╚══ child4 | ||||
| @@ -1,40 +0,0 @@ | ||||
| Root node | ||||
| ├── child1 | ||||
| │   ├── multiple  | ||||
| │   │    line 0 | ||||
| │   ├── multiple  | ||||
| │   │    line 1 | ||||
| │   ├── multiple  | ||||
| │   │    line 2 | ||||
| │   ├── multiple  | ||||
| │   │    line 3 | ||||
| │   ├── multiple  | ||||
| │   │    line 4 | ||||
| │   ├── multiple  | ||||
| │   │    line 5 | ||||
| │   ├── multiple  | ||||
| │   │    line 6 | ||||
| │   ├── multiple  | ||||
| │   │    line 7 | ||||
| │   ├── multiple  | ||||
| │   │    line 8 | ||||
| │   └── multiple  | ||||
| │        line 9 | ||||
| ├── child2 | ||||
| │   └── child2Child | ||||
| │       └── Child 2 child | ||||
| │            child | ||||
| └── child3 | ||||
|     └── single leaf | ||||
|          multiline | ||||
|         └──                2020 January                 | ||||
|             ┌─────┬─────┬─────┬─────┬─────┬─────┬─────┐ | ||||
|             │ Sun │ Mon │ Tue │ Wed │ Thu │ Fri │ Sat │ | ||||
|             ├─────┼─────┼─────┼─────┼─────┼─────┼─────┤ | ||||
|             │     │     │     │ 1   │ 2   │ 3   │ 4   │ | ||||
|             │ 5   │ 6   │ 7   │ 8   │ 9   │ 10  │ 11  │ | ||||
|             │ 12  │ 13  │ 14  │ 15  │ 16  │ 17  │ 18  │ | ||||
|             │ 19  │ 20  │ 21  │ 22  │ 23  │ 24  │ 25  │ | ||||
|             │ 26  │ 27  │ 28  │ 29  │ 30  │ 31  │     │ | ||||
|             │     │     │     │     │     │     │     │ | ||||
|             └─────┴─────┴─────┴─────┴─────┴─────┴─────┘ | ||||
| @@ -33,4 +33,8 @@ | ||||
|     <ProjectReference Include="..\Spectre.Console\Spectre.Console.csproj" /> | ||||
|   </ItemGroup> | ||||
|  | ||||
|   <ItemGroup> | ||||
|     <Folder Include="Expectations\Widgets\Tree\" /> | ||||
|   </ItemGroup> | ||||
|  | ||||
| </Project> | ||||
|   | ||||
| @@ -1,4 +1,3 @@ | ||||
| using System.Collections.Generic; | ||||
| using System.Linq; | ||||
| using System.Threading.Tasks; | ||||
| using Spectre.Console.Testing; | ||||
| @@ -13,25 +12,28 @@ namespace Spectre.Console.Tests.Unit | ||||
|     public class TreeTests | ||||
|     { | ||||
|         [Fact] | ||||
|         [Expectation("SingleRoot")] | ||||
|         public Task Should_Render_Tree_With_Single_Root_Correctly() | ||||
|         [Expectation("Render")] | ||||
|         public Task Should_Render_Tree_Correctly() | ||||
|         { | ||||
|             // Given | ||||
|             var console = new FakeConsole(width: 80); | ||||
|             var nestedChildren = | ||||
|                 Enumerable.Range(0, 10) | ||||
|                     .Select(x => new TreeNode(new Text($"multiple \n line {x}"))); | ||||
|  | ||||
|             var tree = new Tree(new Text("Root node")).Guide(TreeGuide.DoubleLine); | ||||
|  | ||||
|             var nestedChildren = Enumerable.Range(0, 10).Select(x => new Text($"multiple\nline {x}")); | ||||
|             var child2 = new TreeNode(new Text("child2")); | ||||
|             var child2Child = new TreeNode(new Text("child2Child")); | ||||
|             var child2Child = new TreeNode(new Text("child2-1")); | ||||
|             child2.AddNode(child2Child); | ||||
|             child2Child.AddNode(new TreeNode(new Text("Child 2 child\n child"))); | ||||
|             child2Child.AddNode(new TreeNode(new Text("Child2-1-1\nchild"))); | ||||
|             var child3 = new TreeNode(new Text("child3")); | ||||
|             var child3Child = new TreeNode(new Text("single leaf\n multiline")); | ||||
|             child3Child.AddNode(new TreeNode(new Calendar(2020, 01))); | ||||
|             var child3Child = new TreeNode(new Text("single leaf\nmultiline")); | ||||
|             child3Child.AddNode(new TreeNode(new Calendar(2021, 01))); | ||||
|             child3.AddNode(child3Child); | ||||
|             var children = new List<TreeNode> { new(new Text("child1"), nestedChildren), child2, child3 }; | ||||
|             var root = new TreeNode(new Text("Root node"), children); | ||||
|             var tree = new Tree().AddNode(root); | ||||
|  | ||||
|             tree.AddNode("child1").AddNodes(nestedChildren); | ||||
|             tree.AddNode(child2); | ||||
|             tree.AddNode(child3); | ||||
|             tree.AddNode("child4"); | ||||
|  | ||||
|             // When | ||||
|             console.Render(tree); | ||||
| @@ -41,41 +43,12 @@ namespace Spectre.Console.Tests.Unit | ||||
|         } | ||||
|  | ||||
|         [Fact] | ||||
|         [Expectation("MultipleRoots")] | ||||
|         public Task Should_Render_Tree_With_Multiple_Roots_Correctly() | ||||
|         [Expectation("Render_NoChildren")] | ||||
|         public Task Should_Render_Tree_With_No_Child_Nodes_Correctly() | ||||
|         { | ||||
|             // Given | ||||
|             var console = new FakeConsole(width: 80); | ||||
|             var nestedChildren = | ||||
|                 Enumerable.Range(0, 10) | ||||
|                     .Select(x => new TreeNode(new Text($"multiple \n line {x}"))); | ||||
|             var child2 = new TreeNode(new Text("child2")); | ||||
|             var child2Child = new TreeNode(new Text("child2Child")); | ||||
|             child2.AddNode(child2Child); | ||||
|             child2Child.AddNode(new TreeNode(new Text("Child 2 child\n child"))); | ||||
|             var child3 = new TreeNode(new Text("child3")); | ||||
|             var child3Child = new TreeNode(new Text("single leaf\n multiline")); | ||||
|             child3Child.AddNode(new TreeNode(new Calendar(2020, 01))); | ||||
|             child3.AddNode(child3Child); | ||||
|             var children = new List<TreeNode> { new(new Text("child1"), nestedChildren), child2, child3 }; | ||||
|             var root = new TreeNode(new Text("Root node"), children); | ||||
|             var tree = new Tree().AddNode(root).AddNode(child2Child); | ||||
|  | ||||
|             // When | ||||
|             console.Render(tree); | ||||
|  | ||||
|             // Then | ||||
|             return Verifier.Verify(console.Output); | ||||
|         } | ||||
|  | ||||
|         [Fact] | ||||
|         [Expectation("OnlyRoot")] | ||||
|         public Task Should_Render_Tree_With_Only_Root_Node_Correctly() | ||||
|         { | ||||
|             // Given | ||||
|             var console = new FakeConsole(width: 80); | ||||
|             var root = new TreeNode(new Text("Root node"), Enumerable.Empty<TreeNode>()); | ||||
|             var tree = new Tree().AddNode(root); | ||||
|             var tree = new Tree(new Text("Root node")); | ||||
|  | ||||
|             // When | ||||
|             console.Render(tree); | ||||
|   | ||||
| @@ -1,23 +1,24 @@ | ||||
| using System; | ||||
| using System.Collections.Generic; | ||||
| using System.Linq; | ||||
| using Spectre.Console.Rendering; | ||||
|  | ||||
| namespace Spectre.Console | ||||
| { | ||||
|     /// <summary> | ||||
|     /// Contains extension methods for <see cref="IHasCulture"/>. | ||||
|     /// Contains extension methods for <see cref="IHasTreeNodes"/>. | ||||
|     /// </summary> | ||||
|     public static class HasTreeNodeExtensions | ||||
|     { | ||||
|         /// <summary> | ||||
|         /// Adds a tree node. | ||||
|         /// </summary> | ||||
|         /// <typeparam name="T">An object type with tree nodes.</typeparam> | ||||
|         /// <param name="obj">The object that has tree nodes.</param> | ||||
|         /// <typeparam name="T">An object with tree nodes.</typeparam> | ||||
|         /// <param name="obj">The object to add the tree node to.</param> | ||||
|         /// <param name="markup">The node's markup text.</param> | ||||
|         /// <returns>The same instance so that multiple calls can be chained.</returns> | ||||
|         public static T AddNode<T>(this T obj, string markup) | ||||
|             where T : class, IHasTreeNodes | ||||
|         /// <returns>The added tree node.</returns> | ||||
|         public static TreeNode AddNode<T>(this T obj, string markup) | ||||
|             where T : IHasTreeNodes | ||||
|         { | ||||
|             if (obj is null) | ||||
|             { | ||||
| @@ -35,31 +36,12 @@ namespace Spectre.Console | ||||
|         /// <summary> | ||||
|         /// Adds a tree node. | ||||
|         /// </summary> | ||||
|         /// <typeparam name="T">An object type with tree nodes.</typeparam> | ||||
|         /// <param name="obj">The object that has tree nodes.</param> | ||||
|         /// <param name="markup">The node's markup text.</param> | ||||
|         /// <param name="action">An action that can be used to configure the created node further.</param> | ||||
|         /// <returns>The same instance so that multiple calls can be chained.</returns> | ||||
|         public static T AddNode<T>(this T obj, string markup, Action<TreeNode> action) | ||||
|             where T : class, IHasTreeNodes | ||||
|         { | ||||
|             if (markup is null) | ||||
|             { | ||||
|                 throw new ArgumentNullException(nameof(markup)); | ||||
|             } | ||||
|  | ||||
|             return AddNode(obj, new Markup(markup), action); | ||||
|         } | ||||
|  | ||||
|         /// <summary> | ||||
|         /// Adds a tree node. | ||||
|         /// </summary> | ||||
|         /// <typeparam name="T">An object type with tree nodes.</typeparam> | ||||
|         /// <param name="obj">The object that has tree nodes.</param> | ||||
|         /// <typeparam name="T">An object with tree nodes.</typeparam> | ||||
|         /// <param name="obj">The object to add the tree node to.</param> | ||||
|         /// <param name="renderable">The renderable to add.</param> | ||||
|         /// <returns>The same instance so that multiple calls can be chained.</returns> | ||||
|         public static T AddNode<T>(this T obj, IRenderable renderable) | ||||
|             where T : class, IHasTreeNodes | ||||
|         /// <returns>The added tree node.</returns> | ||||
|         public static TreeNode AddNode<T>(this T obj, IRenderable renderable) | ||||
|             where T : IHasTreeNodes | ||||
|         { | ||||
|             if (obj is null) | ||||
|             { | ||||
| @@ -71,52 +53,20 @@ namespace Spectre.Console | ||||
|                 throw new ArgumentNullException(nameof(renderable)); | ||||
|             } | ||||
|  | ||||
|             obj.Children.Add(new TreeNode(renderable)); | ||||
|             return obj; | ||||
|         } | ||||
|  | ||||
|         /// <summary> | ||||
|         /// Adds a tree node. | ||||
|         /// </summary> | ||||
|         /// <typeparam name="T">An object type with tree nodes.</typeparam> | ||||
|         /// <param name="obj">The object that has tree nodes.</param> | ||||
|         /// <param name="renderable">The renderable to add.</param> | ||||
|         /// <param name="action">An action that can be used to configure the created node further.</param> | ||||
|         /// <returns>The same instance so that multiple calls can be chained.</returns> | ||||
|         public static T AddNode<T>(this T obj, IRenderable renderable, Action<TreeNode> action) | ||||
|             where T : class, IHasTreeNodes | ||||
|         { | ||||
|             if (obj is null) | ||||
|             { | ||||
|                 throw new ArgumentNullException(nameof(obj)); | ||||
|             } | ||||
|  | ||||
|             if (renderable is null) | ||||
|             { | ||||
|                 throw new ArgumentNullException(nameof(renderable)); | ||||
|             } | ||||
|  | ||||
|             if (action is null) | ||||
|             { | ||||
|                 throw new ArgumentNullException(nameof(action)); | ||||
|             } | ||||
|  | ||||
|             var node = new TreeNode(renderable); | ||||
|             action(node); | ||||
|  | ||||
|             obj.Children.Add(node); | ||||
|             return obj; | ||||
|             obj.Nodes.Add(node); | ||||
|             return node; | ||||
|         } | ||||
|  | ||||
|         /// <summary> | ||||
|         /// Adds a tree node. | ||||
|         /// </summary> | ||||
|         /// <typeparam name="T">An object type with tree nodes.</typeparam> | ||||
|         /// <param name="obj">The object that has tree nodes.</param> | ||||
|         /// <typeparam name="T">An object with tree nodes.</typeparam> | ||||
|         /// <param name="obj">The object to add the tree node to.</param> | ||||
|         /// <param name="node">The tree node to add.</param> | ||||
|         /// <returns>The same instance so that multiple calls can be chained.</returns> | ||||
|         public static T AddNode<T>(this T obj, TreeNode node) | ||||
|             where T : class, IHasTreeNodes | ||||
|         /// <returns>The added tree node.</returns> | ||||
|         public static TreeNode AddNode<T>(this T obj, TreeNode node) | ||||
|             where T : IHasTreeNodes | ||||
|         { | ||||
|             if (obj is null) | ||||
|             { | ||||
| @@ -128,19 +78,18 @@ namespace Spectre.Console | ||||
|                 throw new ArgumentNullException(nameof(node)); | ||||
|             } | ||||
|  | ||||
|             obj.Children.Add(node); | ||||
|             return obj; | ||||
|             obj.Nodes.Add(node); | ||||
|             return node; | ||||
|         } | ||||
|  | ||||
|         /// <summary> | ||||
|         /// Add multiple tree nodes. | ||||
|         /// </summary> | ||||
|         /// <typeparam name="T">An object type with tree nodes.</typeparam> | ||||
|         /// <param name="obj">The object that has tree nodes.</param> | ||||
|         /// <typeparam name="T">An object with tree nodes.</typeparam> | ||||
|         /// <param name="obj">The object to add the tree nodes to.</param> | ||||
|         /// <param name="nodes">The tree nodes to add.</param> | ||||
|         /// <returns>The same instance so that multiple calls can be chained.</returns> | ||||
|         public static T AddNodes<T>(this T obj, params string[] nodes) | ||||
|             where T : class, IHasTreeNodes | ||||
|         public static void AddNodes<T>(this T obj, params string[] nodes) | ||||
|             where T : IHasTreeNodes | ||||
|         { | ||||
|             if (obj is null) | ||||
|             { | ||||
| @@ -152,19 +101,17 @@ namespace Spectre.Console | ||||
|                 throw new ArgumentNullException(nameof(nodes)); | ||||
|             } | ||||
|  | ||||
|             obj.Children.AddRange(nodes.Select(node => new TreeNode(new Markup(node)))); | ||||
|             return obj; | ||||
|             obj.Nodes.AddRange(nodes.Select(node => new TreeNode(new Markup(node)))); | ||||
|         } | ||||
|  | ||||
|         /// <summary> | ||||
|         /// Add multiple tree nodes. | ||||
|         /// </summary> | ||||
|         /// <typeparam name="T">An object type with tree nodes.</typeparam> | ||||
|         /// <param name="obj">The object that has tree nodes.</param> | ||||
|         /// <typeparam name="T">An object with tree nodes.</typeparam> | ||||
|         /// <param name="obj">The object to add the tree nodes to.</param> | ||||
|         /// <param name="nodes">The tree nodes to add.</param> | ||||
|         /// <returns>The same instance so that multiple calls can be chained.</returns> | ||||
|         public static T AddNodes<T>(this T obj, params TreeNode[] nodes) | ||||
|             where T : class, IHasTreeNodes | ||||
|         public static void AddNodes<T>(this T obj, IEnumerable<string> nodes) | ||||
|             where T : IHasTreeNodes | ||||
|         { | ||||
|             if (obj is null) | ||||
|             { | ||||
| @@ -176,8 +123,95 @@ namespace Spectre.Console | ||||
|                 throw new ArgumentNullException(nameof(nodes)); | ||||
|             } | ||||
|  | ||||
|             obj.Children.AddRange(nodes); | ||||
|             return obj; | ||||
|             obj.Nodes.AddRange(nodes.Select(node => new TreeNode(new Markup(node)))); | ||||
|         } | ||||
|  | ||||
|         /// <summary> | ||||
|         /// Add multiple tree nodes. | ||||
|         /// </summary> | ||||
|         /// <typeparam name="T">An object with tree nodes.</typeparam> | ||||
|         /// <param name="obj">The object to add the tree nodes to.</param> | ||||
|         /// <param name="nodes">The tree nodes to add.</param> | ||||
|         public static void AddNodes<T>(this T obj, params IRenderable[] nodes) | ||||
|             where T : IHasTreeNodes | ||||
|         { | ||||
|             if (obj is null) | ||||
|             { | ||||
|                 throw new ArgumentNullException(nameof(obj)); | ||||
|             } | ||||
|  | ||||
|             if (nodes is null) | ||||
|             { | ||||
|                 throw new ArgumentNullException(nameof(nodes)); | ||||
|             } | ||||
|  | ||||
|             obj.Nodes.AddRange(nodes.Select(node => new TreeNode(node))); | ||||
|         } | ||||
|  | ||||
|         /// <summary> | ||||
|         /// Add multiple tree nodes. | ||||
|         /// </summary> | ||||
|         /// <typeparam name="T">An object with tree nodes.</typeparam> | ||||
|         /// <param name="obj">The object to add the tree nodes to.</param> | ||||
|         /// <param name="nodes">The tree nodes to add.</param> | ||||
|         public static void AddNodes<T>(this T obj, IEnumerable<IRenderable> nodes) | ||||
|             where T : IHasTreeNodes | ||||
|         { | ||||
|             if (obj is null) | ||||
|             { | ||||
|                 throw new ArgumentNullException(nameof(obj)); | ||||
|             } | ||||
|  | ||||
|             if (nodes is null) | ||||
|             { | ||||
|                 throw new ArgumentNullException(nameof(nodes)); | ||||
|             } | ||||
|  | ||||
|             obj.Nodes.AddRange(nodes.Select(node => new TreeNode(node))); | ||||
|         } | ||||
|  | ||||
|         /// <summary> | ||||
|         /// Add multiple tree nodes. | ||||
|         /// </summary> | ||||
|         /// <typeparam name="T">An object with tree nodes.</typeparam> | ||||
|         /// <param name="obj">The object to add the tree nodes to.</param> | ||||
|         /// <param name="nodes">The tree nodes to add.</param> | ||||
|         public static void AddNodes<T>(this T obj, params TreeNode[] nodes) | ||||
|             where T : IHasTreeNodes | ||||
|         { | ||||
|             if (obj is null) | ||||
|             { | ||||
|                 throw new ArgumentNullException(nameof(obj)); | ||||
|             } | ||||
|  | ||||
|             if (nodes is null) | ||||
|             { | ||||
|                 throw new ArgumentNullException(nameof(nodes)); | ||||
|             } | ||||
|  | ||||
|             obj.Nodes.AddRange(nodes); | ||||
|         } | ||||
|  | ||||
|         /// <summary> | ||||
|         /// Add multiple tree nodes. | ||||
|         /// </summary> | ||||
|         /// <typeparam name="T">An object with tree nodes.</typeparam> | ||||
|         /// <param name="obj">The object to add the tree nodes to.</param> | ||||
|         /// <param name="nodes">The tree nodes to add.</param> | ||||
|         public static void AddNodes<T>(this T obj, IEnumerable<TreeNode> nodes) | ||||
|             where T : IHasTreeNodes | ||||
|         { | ||||
|             if (obj is null) | ||||
|             { | ||||
|                 throw new ArgumentNullException(nameof(obj)); | ||||
|             } | ||||
|  | ||||
|             if (nodes is null) | ||||
|             { | ||||
|                 throw new ArgumentNullException(nameof(nodes)); | ||||
|             } | ||||
|  | ||||
|             obj.Nodes.AddRange(nodes); | ||||
|         } | ||||
|     } | ||||
| } | ||||
|   | ||||
							
								
								
									
										38
									
								
								src/Spectre.Console/Extensions/ListExtensions.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										38
									
								
								src/Spectre.Console/Extensions/ListExtensions.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,38 @@ | ||||
| using System; | ||||
| using System.Collections.Generic; | ||||
|  | ||||
| namespace Spectre.Console | ||||
| { | ||||
|     internal static class ListExtensions | ||||
|     { | ||||
|         public static void RemoveLast<T>(this List<T> list) | ||||
|         { | ||||
|             if (list is null) | ||||
|             { | ||||
|                 throw new ArgumentNullException(nameof(list)); | ||||
|             } | ||||
|  | ||||
|             if (list.Count > 0) | ||||
|             { | ||||
|                 list.RemoveAt(list.Count - 1); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         public static void AddOrReplaceLast<T>(this List<T> list, T item) | ||||
|         { | ||||
|             if (list is null) | ||||
|             { | ||||
|                 throw new ArgumentNullException(nameof(list)); | ||||
|             } | ||||
|  | ||||
|             if (list.Count == 0) | ||||
|             { | ||||
|                 list.Add(item); | ||||
|             } | ||||
|             else | ||||
|             { | ||||
|                 list[list.Count - 1] = item; | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
							
								
								
									
										44
									
								
								src/Spectre.Console/Extensions/TreeExtensions.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										44
									
								
								src/Spectre.Console/Extensions/TreeExtensions.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,44 @@ | ||||
| using System; | ||||
|  | ||||
| namespace Spectre.Console | ||||
| { | ||||
|     /// <summary> | ||||
|     /// Contains extension methods for <see cref="Tree"/>. | ||||
|     /// </summary> | ||||
|     public static class TreeExtensions | ||||
|     { | ||||
|         /// <summary> | ||||
|         /// Sets the tree style. | ||||
|         /// </summary> | ||||
|         /// <param name="tree">The tree.</param> | ||||
|         /// <param name="style">The tree style.</param> | ||||
|         /// <returns>The same instance so that multiple calls can be chained.</returns> | ||||
|         public static Tree Style(this Tree tree, Style? style) | ||||
|         { | ||||
|             if (tree is null) | ||||
|             { | ||||
|                 throw new ArgumentNullException(nameof(tree)); | ||||
|             } | ||||
|  | ||||
|             tree.Style = style; | ||||
|             return tree; | ||||
|         } | ||||
|  | ||||
|         /// <summary> | ||||
|         /// Sets the tree guide line appearance. | ||||
|         /// </summary> | ||||
|         /// <param name="tree">The tree.</param> | ||||
|         /// <param name="guide">The tree guide lines to use.</param> | ||||
|         /// <returns>The same instance so that multiple calls can be chained.</returns> | ||||
|         public static Tree Guide(this Tree tree, TreeGuide guide) | ||||
|         { | ||||
|             if (tree is null) | ||||
|             { | ||||
|                 throw new ArgumentNullException(nameof(tree)); | ||||
|             } | ||||
|  | ||||
|             tree.Guide = guide; | ||||
|             return tree; | ||||
|         } | ||||
|     } | ||||
| } | ||||
							
								
								
									
										31
									
								
								src/Spectre.Console/Extensions/TreeGuideExtensions.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										31
									
								
								src/Spectre.Console/Extensions/TreeGuideExtensions.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,31 @@ | ||||
| using System; | ||||
|  | ||||
| namespace Spectre.Console.Rendering | ||||
| { | ||||
|     /// <summary> | ||||
|     /// Contains extension methods for <see cref="TreeGuide"/>. | ||||
|     /// </summary> | ||||
|     public static class TreeGuideExtensions | ||||
|     { | ||||
|         /// <summary> | ||||
|         /// Gets the safe border for a border. | ||||
|         /// </summary> | ||||
|         /// <param name="guide">The tree guide to get the safe version for.</param> | ||||
|         /// <param name="safe">Whether or not to return the safe border.</param> | ||||
|         /// <returns>The safe border if one exist, otherwise the original border.</returns> | ||||
|         public static TreeGuide GetSafeTreeGuide(this TreeGuide guide, bool safe) | ||||
|         { | ||||
|             if (guide is null) | ||||
|             { | ||||
|                 throw new ArgumentNullException(nameof(guide)); | ||||
|             } | ||||
|  | ||||
|             if (safe && guide.SafeTreeGuide != null) | ||||
|             { | ||||
|                 return guide.SafeTreeGuide; | ||||
|             } | ||||
|  | ||||
|             return guide; | ||||
|         } | ||||
|     } | ||||
| } | ||||
							
								
								
									
										47
									
								
								src/Spectre.Console/Extensions/TreeNodeExtensions.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										47
									
								
								src/Spectre.Console/Extensions/TreeNodeExtensions.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,47 @@ | ||||
| using System; | ||||
|  | ||||
| namespace Spectre.Console | ||||
| { | ||||
|     /// <summary> | ||||
|     /// Contains extension methods for <see cref="TreeNode"/>. | ||||
|     /// </summary> | ||||
|     public static class TreeNodeExtensions | ||||
|     { | ||||
|         /// <summary> | ||||
|         /// Expands the tree. | ||||
|         /// </summary> | ||||
|         /// <param name="node">The tree node.</param> | ||||
|         /// <returns>The same instance so that multiple calls can be chained.</returns> | ||||
|         public static TreeNode Expand(this TreeNode node) | ||||
|         { | ||||
|             return Expand(node, true); | ||||
|         } | ||||
|  | ||||
|         /// <summary> | ||||
|         /// Collapses the tree. | ||||
|         /// </summary> | ||||
|         /// <param name="node">The tree node.</param> | ||||
|         /// <returns>The same instance so that multiple calls can be chained.</returns> | ||||
|         public static TreeNode Collapse(this TreeNode node) | ||||
|         { | ||||
|             return Expand(node, false); | ||||
|         } | ||||
|  | ||||
|         /// <summary> | ||||
|         /// Sets whether or not the tree node should be expanded. | ||||
|         /// </summary> | ||||
|         /// <param name="node">The tree node.</param> | ||||
|         /// <param name="expand">Whether or not the tree node should be expanded.</param> | ||||
|         /// <returns>The same instance so that multiple calls can be chained.</returns> | ||||
|         public static TreeNode Expand(this TreeNode node, bool expand) | ||||
|         { | ||||
|             if (node is null) | ||||
|             { | ||||
|                 throw new ArgumentNullException(nameof(node)); | ||||
|             } | ||||
|  | ||||
|             node.Expanded = expand; | ||||
|             return node; | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -1,4 +1,4 @@ | ||||
| using System.Collections.Generic; | ||||
| using System.Collections.Generic; | ||||
|  | ||||
| namespace Spectre.Console | ||||
| { | ||||
| @@ -8,8 +8,8 @@ namespace Spectre.Console | ||||
|     public interface IHasTreeNodes | ||||
|     { | ||||
|         /// <summary> | ||||
|         /// Gets the children of this node. | ||||
|         /// Gets the tree's child nodes. | ||||
|         /// </summary> | ||||
|         public List<TreeNode> Children { get; } | ||||
|         List<TreeNode> Nodes { get; } | ||||
|     } | ||||
| } | ||||
| } | ||||
| @@ -1,25 +0,0 @@ | ||||
| using System; | ||||
|  | ||||
| namespace Spectre.Console.Rendering | ||||
| { | ||||
|     /// <summary> | ||||
|     /// An ASCII rendering of a tree. | ||||
|     /// </summary> | ||||
|     public sealed class AsciiTreeAppearance : TreeAppearance | ||||
|     { | ||||
|         /// <inheritdoc/> | ||||
|         public override int PartSize => 4; | ||||
|  | ||||
|         /// <inheritdoc/> | ||||
|         public override string GetPart(TreePart part) | ||||
|         { | ||||
|             return part switch | ||||
|             { | ||||
|                 TreePart.SiblingConnector => "│   ", | ||||
|                 TreePart.ChildBranch => "├── ", | ||||
|                 TreePart.BottomChildBranch => "└── ", | ||||
|                 _ => throw new ArgumentOutOfRangeException(nameof(part), part, "Unknown tree part."), | ||||
|             }; | ||||
|         } | ||||
|     } | ||||
| } | ||||
							
								
								
									
										23
									
								
								src/Spectre.Console/Rendering/Tree/AsciiTreeGuide.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								src/Spectre.Console/Rendering/Tree/AsciiTreeGuide.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,23 @@ | ||||
| using System; | ||||
|  | ||||
| namespace Spectre.Console.Rendering | ||||
| { | ||||
|     /// <summary> | ||||
|     /// An ASCII tree guide. | ||||
|     /// </summary> | ||||
|     public sealed class AsciiTreeGuide : TreeGuide | ||||
|     { | ||||
|         /// <inheritdoc/> | ||||
|         public override string GetPart(TreeGuidePart part) | ||||
|         { | ||||
|             return part switch | ||||
|             { | ||||
|                 TreeGuidePart.Space => "    ", | ||||
|                 TreeGuidePart.Continue => "|   ", | ||||
|                 TreeGuidePart.Fork => "|-- ", | ||||
|                 TreeGuidePart.End => "`-- ", | ||||
|                 _ => throw new ArgumentOutOfRangeException(nameof(part), part, "Unknown tree part."), | ||||
|             }; | ||||
|         } | ||||
|     } | ||||
| } | ||||
							
								
								
									
										26
									
								
								src/Spectre.Console/Rendering/Tree/BoldLineTreeGuide.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								src/Spectre.Console/Rendering/Tree/BoldLineTreeGuide.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,26 @@ | ||||
| using System; | ||||
|  | ||||
| namespace Spectre.Console.Rendering | ||||
| { | ||||
|     /// <summary> | ||||
|     /// A tree guide made up of bold lines. | ||||
|     /// </summary> | ||||
|     public sealed class BoldLineTreeGuide : TreeGuide | ||||
|     { | ||||
|         /// <inheritdoc/> | ||||
|         public override TreeGuide? SafeTreeGuide => Ascii; | ||||
|  | ||||
|         /// <inheritdoc/> | ||||
|         public override string GetPart(TreeGuidePart part) | ||||
|         { | ||||
|             return part switch | ||||
|             { | ||||
|                 TreeGuidePart.Space => "    ", | ||||
|                 TreeGuidePart.Continue => "┃   ", | ||||
|                 TreeGuidePart.Fork => "┣━━ ", | ||||
|                 TreeGuidePart.End => "┗━━ ", | ||||
|                 _ => throw new ArgumentOutOfRangeException(nameof(part), part, "Unknown tree part."), | ||||
|             }; | ||||
|         } | ||||
|     } | ||||
| } | ||||
							
								
								
									
										26
									
								
								src/Spectre.Console/Rendering/Tree/DoubleLineTreeGuide.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								src/Spectre.Console/Rendering/Tree/DoubleLineTreeGuide.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,26 @@ | ||||
| using System; | ||||
|  | ||||
| namespace Spectre.Console.Rendering | ||||
| { | ||||
|     /// <summary> | ||||
|     /// A tree guide made up of double lines. | ||||
|     /// </summary> | ||||
|     public sealed class DoubleLineTreeGuide : TreeGuide | ||||
|     { | ||||
|         /// <inheritdoc/> | ||||
|         public override TreeGuide? SafeTreeGuide => Ascii; | ||||
|  | ||||
|         /// <inheritdoc/> | ||||
|         public override string GetPart(TreeGuidePart part) | ||||
|         { | ||||
|             return part switch | ||||
|             { | ||||
|                 TreeGuidePart.Space => "    ", | ||||
|                 TreeGuidePart.Continue => "║   ", | ||||
|                 TreeGuidePart.Fork => "╠══ ", | ||||
|                 TreeGuidePart.End => "╚══ ", | ||||
|                 _ => throw new ArgumentOutOfRangeException(nameof(part), part, "Unknown tree part."), | ||||
|             }; | ||||
|         } | ||||
|     } | ||||
| } | ||||
							
								
								
									
										26
									
								
								src/Spectre.Console/Rendering/Tree/LineTreeGuide.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								src/Spectre.Console/Rendering/Tree/LineTreeGuide.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,26 @@ | ||||
| using System; | ||||
|  | ||||
| namespace Spectre.Console.Rendering | ||||
| { | ||||
|     /// <summary> | ||||
|     /// A tree guide made up of lines. | ||||
|     /// </summary> | ||||
|     public sealed class LineTreeGuide : TreeGuide | ||||
|     { | ||||
|         /// <inheritdoc/> | ||||
|         public override TreeGuide? SafeTreeGuide => Ascii; | ||||
|  | ||||
|         /// <inheritdoc/> | ||||
|         public override string GetPart(TreeGuidePart part) | ||||
|         { | ||||
|             return part switch | ||||
|             { | ||||
|                 TreeGuidePart.Space => "    ", | ||||
|                 TreeGuidePart.Continue => "│   ", | ||||
|                 TreeGuidePart.Fork => "├── ", | ||||
|                 TreeGuidePart.End => "└── ", | ||||
|                 _ => throw new ArgumentOutOfRangeException(nameof(part), part, "Unknown tree part."), | ||||
|             }; | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -3,21 +3,26 @@ namespace Spectre.Console.Rendering | ||||
|     /// <summary> | ||||
|     /// Defines the different rendering parts of a <see cref="Tree"/>. | ||||
|     /// </summary> | ||||
|     public enum TreePart | ||||
|     public enum TreeGuidePart | ||||
|     { | ||||
|         /// <summary> | ||||
|         /// Represents a space. | ||||
|         /// </summary> | ||||
|         Space, | ||||
| 
 | ||||
|         /// <summary> | ||||
|         /// Connection between siblings. | ||||
|         /// </summary> | ||||
|         SiblingConnector, | ||||
|         Continue, | ||||
| 
 | ||||
|         /// <summary> | ||||
|         /// Branch from parent to child. | ||||
|         /// </summary> | ||||
|         ChildBranch, | ||||
|         Fork, | ||||
| 
 | ||||
|         /// <summary> | ||||
|         /// Branch from parent to child for the last child in a set. | ||||
|         /// </summary> | ||||
|         BottomChildBranch, | ||||
|         End, | ||||
|     } | ||||
| } | ||||
| @@ -1,15 +0,0 @@ | ||||
| using Spectre.Console.Rendering; | ||||
|  | ||||
| namespace Spectre.Console | ||||
| { | ||||
|     /// <summary> | ||||
|     /// Represents a tree appearance. | ||||
|     /// </summary> | ||||
|     public abstract partial class TreeAppearance | ||||
|     { | ||||
|         /// <summary> | ||||
|         /// Gets ASCII rendering of a tree. | ||||
|         /// </summary> | ||||
|         public static TreeAppearance Ascii { get; } = new AsciiTreeAppearance(); | ||||
|     } | ||||
| } | ||||
							
								
								
									
										30
									
								
								src/Spectre.Console/TreeGuide.Known.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								src/Spectre.Console/TreeGuide.Known.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,30 @@ | ||||
| using Spectre.Console.Rendering; | ||||
|  | ||||
| namespace Spectre.Console | ||||
| { | ||||
|     /// <summary> | ||||
|     /// Represents tree guide lines. | ||||
|     /// </summary> | ||||
|     public abstract partial class TreeGuide | ||||
|     { | ||||
|         /// <summary> | ||||
|         /// Gets an <see cref="AsciiTreeGuide"/> instance. | ||||
|         /// </summary> | ||||
|         public static TreeGuide Ascii { get; } = new AsciiTreeGuide(); | ||||
|  | ||||
|         /// <summary> | ||||
|         /// Gets a <see cref="LineTreeGuide"/> instance. | ||||
|         /// </summary> | ||||
|         public static TreeGuide Line { get; } = new LineTreeGuide(); | ||||
|  | ||||
|         /// <summary> | ||||
|         /// Gets a <see cref="DoubleLineTreeGuide"/> instance. | ||||
|         /// </summary> | ||||
|         public static TreeGuide DoubleLine { get; } = new DoubleLineTreeGuide(); | ||||
|  | ||||
|         /// <summary> | ||||
|         /// Gets a <see cref="BoldLineTreeGuide"/> instance. | ||||
|         /// </summary> | ||||
|         public static TreeGuide BoldLine { get; } = new BoldLineTreeGuide(); | ||||
|     } | ||||
| } | ||||
| @@ -3,20 +3,20 @@ using Spectre.Console.Rendering; | ||||
| namespace Spectre.Console | ||||
| { | ||||
|     /// <summary> | ||||
|     /// Represents a tree appearance. | ||||
|     /// Represents tree guide lines. | ||||
|     /// </summary> | ||||
|     public abstract partial class TreeAppearance | ||||
|     public abstract partial class TreeGuide | ||||
|     { | ||||
|         /// <summary> | ||||
|         /// Gets the length of all tree part strings. | ||||
|         /// Gets the safe guide lines or <c>null</c> if none exist. | ||||
|         /// </summary> | ||||
|         public abstract int PartSize { get; } | ||||
|         public virtual TreeGuide? SafeTreeGuide { get; } | ||||
| 
 | ||||
|         /// <summary> | ||||
|         /// Get the set of characters used to render the corresponding <see cref="TreePart"/>. | ||||
|         /// Get the set of characters used to render the corresponding <see cref="TreeGuidePart"/>. | ||||
|         /// </summary> | ||||
|         /// <param name="part">The part of the tree to get rendering string for.</param> | ||||
|         /// <returns>Rendering string for the tree part.</returns> | ||||
|         public abstract string GetPart(TreePart part); | ||||
|         public abstract string GetPart(TreeGuidePart part); | ||||
|     } | ||||
| } | ||||
| @@ -48,20 +48,20 @@ namespace Spectre.Console | ||||
|         { | ||||
|             var maxValue = Data.Max(item => item.Value); | ||||
|  | ||||
|             var table = new Grid(); | ||||
|             table.Collapse(); | ||||
|             table.AddColumn(new GridColumn().PadRight(2).RightAligned()); | ||||
|             table.AddColumn(new GridColumn().PadLeft(0)); | ||||
|             table.Width = Width; | ||||
|             var grid = new Grid(); | ||||
|             grid.Collapse(); | ||||
|             grid.AddColumn(new GridColumn().PadRight(2).RightAligned()); | ||||
|             grid.AddColumn(new GridColumn().PadLeft(0)); | ||||
|             grid.Width = Width; | ||||
|  | ||||
|             if (!string.IsNullOrWhiteSpace(Label)) | ||||
|             { | ||||
|                 table.AddRow(Text.Empty, new Markup(Label).Alignment(LabelAlignment)); | ||||
|                 grid.AddRow(Text.Empty, new Markup(Label).Alignment(LabelAlignment)); | ||||
|             } | ||||
|  | ||||
|             foreach (var item in Data) | ||||
|             { | ||||
|                 table.AddRow( | ||||
|                 grid.AddRow( | ||||
|                     new Markup(item.Label), | ||||
|                     new ProgressBar() | ||||
|                     { | ||||
| @@ -76,7 +76,7 @@ namespace Spectre.Console | ||||
|                     }); | ||||
|             } | ||||
|  | ||||
|             return ((IRenderable)table).Render(context, maxWidth); | ||||
|             return ((IRenderable)grid).Render(context, maxWidth); | ||||
|         } | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -1,4 +1,3 @@ | ||||
| using System; | ||||
| using System.Collections.Generic; | ||||
| using System.Linq; | ||||
| using Spectre.Console.Internal; | ||||
| @@ -7,155 +6,124 @@ using Spectre.Console.Rendering; | ||||
| namespace Spectre.Console | ||||
| { | ||||
|     /// <summary> | ||||
|     ///     Representation of tree data. | ||||
|     /// A renderable tree. | ||||
|     /// </summary> | ||||
|     public sealed class Tree : Renderable, IHasTreeNodes | ||||
|     { | ||||
|         private readonly TreeNode _root; | ||||
|  | ||||
|         /// <summary> | ||||
|         /// Gets or sets the tree style. | ||||
|         /// </summary> | ||||
|         public Style Style { get; set; } = Style.Plain; | ||||
|         public Style? Style { get; set; } | ||||
|  | ||||
|         /// <summary> | ||||
|         ///  Gets or sets the appearance of the tree. | ||||
|         ///  Gets or sets the tree guide lines. | ||||
|         /// </summary> | ||||
|         public TreeAppearance Appearance { get; set; } = TreeAppearance.Ascii; | ||||
|         public TreeGuide Guide { get; set; } = TreeGuide.Line; | ||||
|  | ||||
|         /// <summary> | ||||
|         /// Gets the tree nodes. | ||||
|         /// Gets the tree's child nodes. | ||||
|         /// </summary> | ||||
|         public List<TreeNode> Nodes { get; } | ||||
|         public List<Tree> Nodes { get; } = new List<Tree>(); | ||||
|  | ||||
|         /// <summary> | ||||
|         /// Gets or sets a value indicating whether or not the tree is expanded or not. | ||||
|         /// </summary> | ||||
|         public bool Expanded { get; set; } = true; | ||||
|  | ||||
|         /// <inheritdoc/> | ||||
|         List<TreeNode> IHasTreeNodes.Children => Nodes; | ||||
|         List<TreeNode> IHasTreeNodes.Nodes => _root.Nodes; | ||||
|  | ||||
|         /// <summary> | ||||
|         /// Initializes a new instance of the <see cref="Tree"/> class. | ||||
|         /// </summary> | ||||
|         public Tree() | ||||
|         /// <param name="renderable">The tree label.</param> | ||||
|         public Tree(IRenderable renderable) | ||||
|         { | ||||
|             Nodes = new List<TreeNode>(); | ||||
|             _root = new TreeNode(renderable); | ||||
|         } | ||||
|  | ||||
|         /// <inheritdoc /> | ||||
|         protected override Measurement Measure(RenderContext context, int maxWidth) | ||||
|         /// <summary> | ||||
|         /// Initializes a new instance of the <see cref="Tree"/> class. | ||||
|         /// </summary> | ||||
|         /// <param name="label">The tree label.</param> | ||||
|         public Tree(string label) | ||||
|         { | ||||
|             Measurement MeasureAtDepth(RenderContext context, int maxWidth, TreeNode node, int depth) | ||||
|             { | ||||
|                 var rootMeasurement = node.Measure(context, maxWidth); | ||||
|                 var treeIndentation = depth * Appearance.PartSize; | ||||
|                 var currentMax = rootMeasurement.Max + treeIndentation; | ||||
|                 var currentMin = rootMeasurement.Min + treeIndentation; | ||||
|  | ||||
|                 foreach (var child in node.Children) | ||||
|                 { | ||||
|                     var childMeasurement = MeasureAtDepth(context, maxWidth, child, depth + 1); | ||||
|                     if (childMeasurement.Min > currentMin) | ||||
|                     { | ||||
|                         currentMin = childMeasurement.Min; | ||||
|                     } | ||||
|  | ||||
|                     if (childMeasurement.Max > currentMax) | ||||
|                     { | ||||
|                         currentMax = childMeasurement.Max; | ||||
|                     } | ||||
|                 } | ||||
|  | ||||
|                 return new Measurement(currentMin, Math.Min(currentMax, maxWidth)); | ||||
|             } | ||||
|  | ||||
|             if (Nodes.Count == 1) | ||||
|             { | ||||
|                 return MeasureAtDepth(context, maxWidth, Nodes[0], depth: 0); | ||||
|             } | ||||
|             else | ||||
|             { | ||||
|                 var root = new TreeNode(Text.Empty); | ||||
|                 foreach (var node in Nodes) | ||||
|                 { | ||||
|                     root.AddNode(node); | ||||
|                 } | ||||
|  | ||||
|                 return MeasureAtDepth(context, maxWidth, root, depth: 0); | ||||
|             } | ||||
|             _root = new TreeNode(new Markup(label)); | ||||
|         } | ||||
|  | ||||
|         /// <inheritdoc /> | ||||
|         protected override IEnumerable<Segment> Render(RenderContext context, int maxWidth) | ||||
|         { | ||||
|             if (Nodes.Count == 1) | ||||
|             var result = new List<Segment>(); | ||||
|  | ||||
|             var stack = new Stack<Queue<TreeNode>>(); | ||||
|             stack.Push(new Queue<TreeNode>(new[] { _root })); | ||||
|  | ||||
|             var levels = new List<Segment>(); | ||||
|             levels.Add(GetGuide(context, TreeGuidePart.Continue)); | ||||
|  | ||||
|             while (stack.Count > 0) | ||||
|             { | ||||
|                 // Single root | ||||
|                 return Nodes[0] | ||||
|                     .Render(context, maxWidth) | ||||
|                     .Concat(new List<Segment> { Segment.LineBreak }) | ||||
|                     .Concat(RenderChildren(context, maxWidth - Appearance.PartSize, Nodes[0], depth: 0)); | ||||
|             } | ||||
|             else | ||||
|             { | ||||
|                 // Multiple roots | ||||
|                 var root = new TreeNode(Text.Empty); | ||||
|                 foreach (var node in Nodes) | ||||
|                 var stackNode = stack.Pop(); | ||||
|                 if (stackNode.Count == 0) | ||||
|                 { | ||||
|                     root.AddNode(node); | ||||
|                     levels.RemoveLast(); | ||||
|                     if (levels.Count > 0) | ||||
|                     { | ||||
|                         levels.AddOrReplaceLast(GetGuide(context, TreeGuidePart.Fork)); | ||||
|                     } | ||||
|  | ||||
|                     continue; | ||||
|                 } | ||||
|  | ||||
|                 return Enumerable.Empty<Segment>() | ||||
|                     .Concat(RenderChildren( | ||||
|                         context, maxWidth - Appearance.PartSize, root, | ||||
|                         depth: 0)); | ||||
|             } | ||||
|         } | ||||
|                 var isLastChild = stackNode.Count == 1; | ||||
|                 var current = stackNode.Dequeue(); | ||||
|  | ||||
|         private IEnumerable<Segment> RenderChildren( | ||||
|             RenderContext context, int maxWidth, TreeNode node, | ||||
|             int depth, int? trailingStarted = null) | ||||
|         { | ||||
|             var result = new List<Segment>(); | ||||
|             foreach (var (_, _, lastChild, childNode) in node.Children.Enumerate()) | ||||
|             { | ||||
|                 var lines = Segment.SplitLines(context, childNode.Render(context, maxWidth)); | ||||
|                 foreach (var (_, isFirstLine, _, line) in lines.Enumerate()) | ||||
|                 stack.Push(stackNode); | ||||
|  | ||||
|                 if (isLastChild) | ||||
|                 { | ||||
|                     var siblingConnectorSegment = | ||||
|                         new Segment(Appearance.GetPart(TreePart.SiblingConnector), Style); | ||||
|                     if (trailingStarted != null) | ||||
|                     { | ||||
|                         result.AddRange(Enumerable.Repeat(siblingConnectorSegment, trailingStarted.Value)); | ||||
|                         result.AddRange(Enumerable.Repeat( | ||||
|                             Segment.Padding(Appearance.PartSize), | ||||
|                             depth - trailingStarted.Value)); | ||||
|                     } | ||||
|                     else | ||||
|                     { | ||||
|                         result.AddRange(Enumerable.Repeat(siblingConnectorSegment, depth)); | ||||
|                     } | ||||
|                     levels.AddOrReplaceLast(GetGuide(context, TreeGuidePart.End)); | ||||
|                 } | ||||
|  | ||||
|                     if (isFirstLine) | ||||
|                 var prefix = levels.Skip(1).ToList(); | ||||
|                 var renderableLines = Segment.SplitLines(context, current.Renderable.Render(context, maxWidth - Segment.CellCount(context, prefix))); | ||||
|  | ||||
|                 foreach (var (_, isFirstLine, _, line) in renderableLines.Enumerate()) | ||||
|                 { | ||||
|                     if (prefix.Count > 0) | ||||
|                     { | ||||
|                         result.Add(lastChild | ||||
|                             ? new Segment(Appearance.GetPart(TreePart.BottomChildBranch), Style) | ||||
|                             : new Segment(Appearance.GetPart(TreePart.ChildBranch), Style)); | ||||
|                     } | ||||
|                     else | ||||
|                     { | ||||
|                         result.Add(lastChild ? Segment.Padding(Appearance.PartSize) : siblingConnectorSegment); | ||||
|                         result.AddRange(prefix.ToList()); | ||||
|                     } | ||||
|  | ||||
|                     result.AddRange(line); | ||||
|                     result.Add(Segment.LineBreak); | ||||
|  | ||||
|                     if (isFirstLine && prefix.Count > 0) | ||||
|                     { | ||||
|                         var part = isLastChild ? TreeGuidePart.Space : TreeGuidePart.Continue; | ||||
|                         prefix.AddOrReplaceLast(GetGuide(context, part)); | ||||
|                     } | ||||
|                 } | ||||
|  | ||||
|                 var childTrailingStarted = trailingStarted ?? (lastChild ? depth : null); | ||||
|                 if (current.Expanded && current.Nodes.Count > 0) | ||||
|                 { | ||||
|                     levels.AddOrReplaceLast(GetGuide(context, isLastChild ? TreeGuidePart.Space : TreeGuidePart.Continue)); | ||||
|                     levels.Add(GetGuide(context, current.Nodes.Count == 1 ? TreeGuidePart.End : TreeGuidePart.Fork)); | ||||
|  | ||||
|                 result.AddRange( | ||||
|                     RenderChildren( | ||||
|                         context, maxWidth - Appearance.PartSize, | ||||
|                         childNode, depth + 1, | ||||
|                         childTrailingStarted)); | ||||
|                     stack.Push(new Queue<TreeNode>(current.Nodes)); | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             return result; | ||||
|         } | ||||
|  | ||||
|         private Segment GetGuide(RenderContext context, TreeGuidePart part) | ||||
|         { | ||||
|             var guide = Guide.GetSafeTreeGuide(context.LegacyConsole || !context.Unicode); | ||||
|             return new Segment(guide.GetPart(part), Style ?? Style.Plain); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -1,41 +1,32 @@ | ||||
| using System; | ||||
| using System.Collections.Generic; | ||||
| using System.Linq; | ||||
| using System.Collections.Generic; | ||||
| using Spectre.Console.Rendering; | ||||
|  | ||||
| namespace Spectre.Console | ||||
| { | ||||
|     /// <summary> | ||||
|     /// Node of a tree. | ||||
|     /// Represents a tree node. | ||||
|     /// </summary> | ||||
|     public sealed class TreeNode : IHasTreeNodes, IRenderable | ||||
|     public sealed class TreeNode : IHasTreeNodes | ||||
|     { | ||||
|         private readonly IRenderable _renderable; | ||||
|         internal IRenderable Renderable { get; } | ||||
|  | ||||
|         /// <inheritdoc/> | ||||
|         public List<TreeNode> Children { get; } | ||||
|         /// <summary> | ||||
|         /// Gets the tree node's child nodes. | ||||
|         /// </summary> | ||||
|         public List<TreeNode> Nodes { get; } = new List<TreeNode>(); | ||||
|  | ||||
|         /// <summary> | ||||
|         /// Gets or sets a value indicating whether or not the tree node is expanded or not. | ||||
|         /// </summary> | ||||
|         public bool Expanded { get; set; } = true; | ||||
|  | ||||
|         /// <summary> | ||||
|         /// Initializes a new instance of the <see cref="TreeNode"/> class. | ||||
|         /// </summary> | ||||
|         /// <param name="renderable">The <see cref="IRenderable"/> which this node wraps.</param> | ||||
|         /// <param name="children">Any children that the node is declared with.</param> | ||||
|         public TreeNode(IRenderable renderable, IEnumerable<TreeNode>? children = null) | ||||
|         /// <param name="renderable">The tree node label.</param> | ||||
|         public TreeNode(IRenderable renderable) | ||||
|         { | ||||
|             _renderable = renderable ?? throw new ArgumentNullException(nameof(renderable)); | ||||
|             Children = new List<TreeNode>(children ?? Enumerable.Empty<TreeNode>()); | ||||
|         } | ||||
|  | ||||
|         /// <inheritdoc/> | ||||
|         public Measurement Measure(RenderContext context, int maxWidth) | ||||
|         { | ||||
|             return _renderable.Measure(context, maxWidth); | ||||
|         } | ||||
|  | ||||
|         /// <inheritdoc/> | ||||
|         public IEnumerable<Segment> Render(RenderContext context, int maxWidth) | ||||
|         { | ||||
|             return _renderable.Render(context, maxWidth); | ||||
|             Renderable = renderable; | ||||
|         } | ||||
|     } | ||||
| } | ||||
		Reference in New Issue
	
	Block a user