mirror of
				https://github.com/nsnail/spectre.console.git
				synced 2025-10-31 17:15:28 +08:00 
			
		
		
		
	Add tree widget
This commit is contained in:
		| @@ -0,0 +1,40 @@ | ||||
| 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  │     │ | ||||
|             │     │     │     │     │     │     │     │ | ||||
|             └─────┴─────┴─────┴─────┴─────┴─────┴─────┘ | ||||
| @@ -0,0 +1 @@ | ||||
| Root node | ||||
							
								
								
									
										90
									
								
								src/Spectre.Console.Tests/Unit/TreeMeasureTests.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										90
									
								
								src/Spectre.Console.Tests/Unit/TreeMeasureTests.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,90 @@ | ||||
| using System.Collections.Generic; | ||||
| using System.Linq; | ||||
| using System.Text; | ||||
| using Spectre.Console.Rendering; | ||||
| using Xunit; | ||||
|  | ||||
| namespace Spectre.Console.Tests.Unit | ||||
| { | ||||
|     public class TreeMeasureTests | ||||
|     { | ||||
|         [Fact] | ||||
|         public void Measure_Tree_Dominated_Width() | ||||
|         { | ||||
|             // Given | ||||
|             var nestedChildren = | ||||
|                 Enumerable.Range(0, 10) | ||||
|                     .Select(x => new TreeNode(new Text($"multiple \n line {x}"))); | ||||
|             var child3 = new TreeNode(new Text("child3")); | ||||
|             child3.AddChild(new TreeNode(new Text("single leaf\n multiline"))); | ||||
|             var children = new List<TreeNode> | ||||
|             { | ||||
|                 new(new Text("child1"), nestedChildren), new(new Text("child2")), child3, | ||||
|             }; | ||||
|             var root = new TreeNode(new Text("Root node"), children); | ||||
|             var tree = new Tree(root); | ||||
|  | ||||
|             // When | ||||
|             var measurement = ((IRenderable)tree).Measure(new RenderContext(Encoding.Unicode, false), 80); | ||||
|  | ||||
|             // Then | ||||
|             // Corresponds to "│   └── multiple" | ||||
|             Assert.Equal(17, measurement.Min); | ||||
|  | ||||
|             // Corresponds to "    └── single leaf" when untrimmed | ||||
|             Assert.Equal(19, measurement.Max); | ||||
|         } | ||||
|  | ||||
|         [Fact] | ||||
|         public void Measure_Max_Width_Bound() | ||||
|         { | ||||
|             // Given | ||||
|             var root = new TreeNode(new Text("Root node")); | ||||
|             var currentNode = root; | ||||
|             foreach (var i in Enumerable.Range(0, 100)) | ||||
|             { | ||||
|                 var newNode = new TreeNode(new Text(string.Empty)); | ||||
|                 currentNode.AddChild(newNode); | ||||
|                 currentNode = newNode; | ||||
|             } | ||||
|  | ||||
|             var tree = new Tree(root); | ||||
|  | ||||
|             // When | ||||
|             var measurement = ((IRenderable)tree).Measure(new RenderContext(Encoding.Unicode, false), 80); | ||||
|  | ||||
|             // Then | ||||
|             // Each node depth contributes 4 characters, so 100 node depth -> 400 character min width | ||||
|             Assert.Equal(400, measurement.Min); | ||||
|  | ||||
|             // Successfully capped at 80 terminal width | ||||
|             Assert.Equal(80, measurement.Max); | ||||
|         } | ||||
|  | ||||
|         [Fact] | ||||
|         public void Measure_Leaf_Dominated_Width() | ||||
|         { | ||||
|             // Given | ||||
|             var root = new TreeNode(new Text("Root node")); | ||||
|             var currentNode = root; | ||||
|             foreach (var i in Enumerable.Range(0, 10)) | ||||
|             { | ||||
|                 var newNode = new TreeNode(new Text(string.Empty)); | ||||
|                 currentNode.AddChild(newNode); | ||||
|                 currentNode = newNode; | ||||
|             } | ||||
|  | ||||
|             var tree = new Tree(root); | ||||
|  | ||||
|             // When | ||||
|             var measurement = ((IRenderable)tree).Measure(new RenderContext(Encoding.Unicode, false), 80); | ||||
|  | ||||
|             // Then | ||||
|             // Corresponds to "│   │   │   │   │   │   │   │   │   └── " | ||||
|             Assert.Equal(40, measurement.Min); | ||||
|  | ||||
|             // Corresponds to "│   │   │   │   │   │   │   │   │   └── " | ||||
|             Assert.Equal(40, measurement.Max); | ||||
|         } | ||||
|     } | ||||
| } | ||||
							
								
								
									
										55
									
								
								src/Spectre.Console.Tests/Unit/TreeRenderingTests.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										55
									
								
								src/Spectre.Console.Tests/Unit/TreeRenderingTests.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,55 @@ | ||||
| using System.Collections.Generic; | ||||
| using System.Linq; | ||||
| using System.Threading.Tasks; | ||||
| using Spectre.Console.Testing; | ||||
| using VerifyXunit; | ||||
| using Xunit; | ||||
|  | ||||
| namespace Spectre.Console.Tests.Unit | ||||
| { | ||||
|     [UsesVerify] | ||||
|     public sealed class TreeRenderingTests | ||||
|     { | ||||
|         [Fact] | ||||
|         public Task Representative_Tree() | ||||
|         { | ||||
|             // 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.AddChild(child2Child); | ||||
|             child2Child.AddChild(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.AddChild(new TreeNode(new Calendar(2020, 01))); | ||||
|             child3.AddChild(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(root); | ||||
|  | ||||
|             // When | ||||
|             console.Render(tree); | ||||
|  | ||||
|             // Then | ||||
|             return Verifier.Verify(console.Output); | ||||
|         } | ||||
|  | ||||
|         [Fact] | ||||
|         public Task Root_Node_Only() | ||||
|         { | ||||
|             // Given | ||||
|             var console = new FakeConsole(width: 80); | ||||
|             var root = new TreeNode(new Text("Root node"), Enumerable.Empty<TreeNode>()); | ||||
|             var tree = new Tree(root); | ||||
|  | ||||
|             // When | ||||
|             console.Render(tree); | ||||
|  | ||||
|             // Then | ||||
|             return Verifier.Verify(console.Output); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -68,7 +68,7 @@ namespace Spectre.Console.Internal | ||||
|                 case Justify.Right: | ||||
|                     { | ||||
|                         var diff = maxWidth - width; | ||||
|                         segments.Insert(0, new Segment(new string(' ', diff))); | ||||
|                         segments.Insert(0, Segment.Padding(diff)); | ||||
|                         break; | ||||
|                     } | ||||
|  | ||||
| @@ -76,14 +76,14 @@ namespace Spectre.Console.Internal | ||||
|                     { | ||||
|                         // Left side. | ||||
|                         var diff = (maxWidth - width) / 2; | ||||
|                         segments.Insert(0, new Segment(new string(' ', diff))); | ||||
|                         segments.Insert(0, Segment.Padding(diff)); | ||||
|  | ||||
|                         // Right side | ||||
|                         segments.Add(new Segment(new string(' ', diff))); | ||||
|                         segments.Add(Segment.Padding(diff)); | ||||
|                         var remainder = (maxWidth - width) % 2; | ||||
|                         if (remainder != 0) | ||||
|                         { | ||||
|                             segments.Add(new Segment(new string(' ', remainder))); | ||||
|                             segments.Add(Segment.Padding(remainder)); | ||||
|                         } | ||||
|  | ||||
|                         break; | ||||
|   | ||||
							
								
								
									
										25
									
								
								src/Spectre.Console/Rendering/AsciiTreeRendering.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								src/Spectre.Console/Rendering/AsciiTreeRendering.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,25 @@ | ||||
| using System; | ||||
|  | ||||
| namespace Spectre.Console.Rendering | ||||
| { | ||||
|     /// <summary> | ||||
|     /// An ASCII rendering of a tree. | ||||
|     /// </summary> | ||||
|     public sealed class AsciiTreeRendering : ITreeRendering | ||||
|     { | ||||
|         /// <inheritdoc/> | ||||
|         public string GetPart(TreePart part) | ||||
|         { | ||||
|             return part switch | ||||
|             { | ||||
|                 TreePart.SiblingConnector => "│   ", | ||||
|                 TreePart.ChildBranch => "├── ", | ||||
|                 TreePart.BottomChildBranch => "└── ", | ||||
|                 _ => throw new ArgumentOutOfRangeException(nameof(part), part, "Unknown tree part."), | ||||
|             }; | ||||
|         } | ||||
|  | ||||
|         /// <inheritdoc/> | ||||
|         public int PartSize => 4; | ||||
|     } | ||||
| } | ||||
							
								
								
									
										20
									
								
								src/Spectre.Console/Rendering/ITreeRendering.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								src/Spectre.Console/Rendering/ITreeRendering.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,20 @@ | ||||
| namespace Spectre.Console.Rendering | ||||
| { | ||||
|     /// <summary> | ||||
|     /// Represents the characters used to render a tree. | ||||
|     /// </summary> | ||||
|     public interface ITreeRendering | ||||
|     { | ||||
|         /// <summary> | ||||
|         /// Get the set of characters used to render the corresponding <see cref="TreePart"/>. | ||||
|         /// </summary> | ||||
|         /// <param name="part">The part of the tree to get rendering string for.</param> | ||||
|         /// <returns>Rendering string for the tree part.</returns> | ||||
|         string GetPart(TreePart part); | ||||
|  | ||||
|         /// <summary> | ||||
|         /// Gets the length of all tree part strings. | ||||
|         /// </summary> | ||||
|         int PartSize { get; } | ||||
|     } | ||||
| } | ||||
| @@ -52,6 +52,13 @@ namespace Spectre.Console.Rendering | ||||
|         /// </summary> | ||||
|         public static Segment Empty { get; } = new Segment(string.Empty, Style.Plain, false, false); | ||||
|  | ||||
|         /// <summary> | ||||
|         /// Creates padding segment. | ||||
|         /// </summary> | ||||
|         /// <param name="size">Number of whitespace characters.</param> | ||||
|         /// <returns>Segment for specified padding size.</returns> | ||||
|         public static Segment Padding(int size) => new(new string(' ', size)); | ||||
|  | ||||
|         /// <summary> | ||||
|         /// Initializes a new instance of the <see cref="Segment"/> class. | ||||
|         /// </summary> | ||||
|   | ||||
| @@ -48,7 +48,7 @@ namespace Spectre.Console.Rendering | ||||
|                 var missing = Width - length; | ||||
|                 if (missing > 0) | ||||
|                 { | ||||
|                     line.Add(new Segment(new string(' ', missing))); | ||||
|                     line.Add(Segment.Padding(missing)); | ||||
|                 } | ||||
|             } | ||||
|  | ||||
| @@ -59,7 +59,7 @@ namespace Spectre.Console.Rendering | ||||
|                 { | ||||
|                     lines.Add(new SegmentLine | ||||
|                     { | ||||
|                         new Segment(new string(' ', Width)), | ||||
|                         Segment.Padding(Width), | ||||
|                     }); | ||||
|                 } | ||||
|             } | ||||
|   | ||||
							
								
								
									
										23
									
								
								src/Spectre.Console/Rendering/TreePart.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								src/Spectre.Console/Rendering/TreePart.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,23 @@ | ||||
| namespace Spectre.Console.Rendering | ||||
| { | ||||
|     /// <summary> | ||||
|     /// Defines the different rendering parts of a <see cref="Tree"/>. | ||||
|     /// </summary> | ||||
|     public enum TreePart | ||||
|     { | ||||
|         /// <summary> | ||||
|         /// Connection between siblings. | ||||
|         /// </summary> | ||||
|         SiblingConnector, | ||||
|  | ||||
|         /// <summary> | ||||
|         /// Branch from parent to child. | ||||
|         /// </summary> | ||||
|         ChildBranch, | ||||
|  | ||||
|         /// <summary> | ||||
|         /// Branch from parent to child for the last child in a set. | ||||
|         /// </summary> | ||||
|         BottomChildBranch, | ||||
|     } | ||||
| } | ||||
							
								
								
									
										13
									
								
								src/Spectre.Console/Rendering/TreeRendering.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								src/Spectre.Console/Rendering/TreeRendering.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,13 @@ | ||||
| namespace Spectre.Console.Rendering | ||||
| { | ||||
|     /// <summary> | ||||
|     /// Selection of different renderings which can be used by <see cref="Tree"/>. | ||||
|     /// </summary> | ||||
|     public static class TreeRendering | ||||
|     { | ||||
|         /// <summary> | ||||
|         /// Gets ASCII rendering of a tree. | ||||
|         /// </summary> | ||||
|         public static ITreeRendering Ascii { get; } = new AsciiTreeRendering(); | ||||
|     } | ||||
| } | ||||
| @@ -60,7 +60,7 @@ namespace Spectre.Console | ||||
|  | ||||
|                         if (lineWidth < maxWidth) | ||||
|                         { | ||||
|                             yield return new Segment(new string(' ', maxWidth - lineWidth)); | ||||
|                             yield return Segment.Padding(maxWidth - lineWidth); | ||||
|                         } | ||||
|                     } | ||||
|                     else if (alignment == Justify.Center) | ||||
| @@ -68,15 +68,15 @@ namespace Spectre.Console | ||||
|                         var left = (maxWidth - lineWidth) / 2; | ||||
|                         var right = left + ((maxWidth - lineWidth) % 2); | ||||
|  | ||||
|                         yield return new Segment(new string(' ', left)); | ||||
|                         yield return Segment.Padding(left); | ||||
|                         yield return line; | ||||
|                         yield return new Segment(new string(' ', right)); | ||||
|                         yield return Segment.Padding(right); | ||||
|                     } | ||||
|                     else if (alignment == Justify.Right) | ||||
|                     { | ||||
|                         if (lineWidth < maxWidth) | ||||
|                         { | ||||
|                             yield return new Segment(new string(' ', maxWidth - lineWidth)); | ||||
|                             yield return Segment.Padding(maxWidth - lineWidth); | ||||
|                         } | ||||
|  | ||||
|                         yield return line; | ||||
|   | ||||
| @@ -66,7 +66,7 @@ namespace Spectre.Console | ||||
|             // Top padding | ||||
|             for (var i = 0; i < Padding.GetTopSafe(); i++) | ||||
|             { | ||||
|                 result.Add(new Segment(new string(' ', width))); | ||||
|                 result.Add(Segment.Padding(width)); | ||||
|                 result.Add(Segment.LineBreak); | ||||
|             } | ||||
|  | ||||
| @@ -76,7 +76,7 @@ namespace Spectre.Console | ||||
|                 // Left padding | ||||
|                 if (Padding.GetLeftSafe() != 0) | ||||
|                 { | ||||
|                     result.Add(new Segment(new string(' ', Padding.GetLeftSafe()))); | ||||
|                     result.Add(Segment.Padding(Padding.GetLeftSafe())); | ||||
|                 } | ||||
|  | ||||
|                 result.AddRange(line); | ||||
| @@ -84,7 +84,7 @@ namespace Spectre.Console | ||||
|                 // Right padding | ||||
|                 if (Padding.GetRightSafe() != 0) | ||||
|                 { | ||||
|                     result.Add(new Segment(new string(' ', Padding.GetRightSafe()))); | ||||
|                     result.Add(Segment.Padding(Padding.GetRightSafe())); | ||||
|                 } | ||||
|  | ||||
|                 // Missing space on right side? | ||||
| @@ -92,7 +92,7 @@ namespace Spectre.Console | ||||
|                 var diff = width - lineWidth - Padding.GetLeftSafe() - Padding.GetRightSafe(); | ||||
|                 if (diff > 0) | ||||
|                 { | ||||
|                     result.Add(new Segment(new string(' ', diff))); | ||||
|                     result.Add(Segment.Padding(diff)); | ||||
|                 } | ||||
|  | ||||
|                 result.Add(Segment.LineBreak); | ||||
| @@ -101,7 +101,7 @@ namespace Spectre.Console | ||||
|             // Bottom padding | ||||
|             for (var i = 0; i < Padding.GetBottomSafe(); i++) | ||||
|             { | ||||
|                 result.Add(new Segment(new string(' ', width))); | ||||
|                 result.Add(Segment.Padding(width)); | ||||
|                 result.Add(Segment.LineBreak); | ||||
|             } | ||||
|  | ||||
|   | ||||
| @@ -112,7 +112,7 @@ namespace Spectre.Console | ||||
|                 if (length < childWidth) | ||||
|                 { | ||||
|                     var diff = childWidth - length; | ||||
|                     content.Add(new Segment(new string(' ', diff))); | ||||
|                     content.Add(Segment.Padding(diff)); | ||||
|                 } | ||||
|  | ||||
|                 result.AddRange(content); | ||||
|   | ||||
							
								
								
									
										122
									
								
								src/Spectre.Console/Widgets/Tree.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										122
									
								
								src/Spectre.Console/Widgets/Tree.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,122 @@ | ||||
| using System; | ||||
| using System.Collections.Generic; | ||||
| using System.Linq; | ||||
| using Spectre.Console.Internal; | ||||
| using Spectre.Console.Rendering; | ||||
|  | ||||
| namespace Spectre.Console | ||||
| { | ||||
|     /// <summary> | ||||
|     ///     Representation of tree data. | ||||
|     /// </summary> | ||||
|     public sealed class Tree : Renderable | ||||
|     { | ||||
|         private readonly TreeNode _rootNode; | ||||
|  | ||||
|         /// <summary> | ||||
|         /// Gets or sets the tree style. | ||||
|         /// </summary> | ||||
|         public Style Style { get; set; } = Style.Plain; | ||||
|  | ||||
|         /// <summary> | ||||
|         ///  Gets or sets the rendering type used for the tree. | ||||
|         /// </summary> | ||||
|         public ITreeRendering Rendering { get; set; } = TreeRendering.Ascii; | ||||
|  | ||||
|         /// <summary> | ||||
|         /// Initializes a new instance of the <see cref="Tree"/> class. | ||||
|         /// </summary> | ||||
|         /// <param name="rootNode">Root node of the tree to be rendered.</param> | ||||
|         public Tree(TreeNode rootNode) | ||||
|         { | ||||
|             _rootNode = rootNode; | ||||
|         } | ||||
|  | ||||
|         /// <inheritdoc /> | ||||
|         protected override Measurement Measure(RenderContext context, int maxWidth) | ||||
|         { | ||||
|             return MeasureAtDepth(context, maxWidth, _rootNode, depth: 0); | ||||
|         } | ||||
|  | ||||
|         private Measurement MeasureAtDepth(RenderContext context, int maxWidth, TreeNode node, int depth) | ||||
|         { | ||||
|             var rootMeasurement = node.Measure(context, maxWidth); | ||||
|             var treeIndentation = depth * Rendering.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)); | ||||
|         } | ||||
|  | ||||
|         /// <inheritdoc /> | ||||
|         protected override IEnumerable<Segment> Render(RenderContext context, int maxWidth) | ||||
|         { | ||||
|             return _rootNode | ||||
|                 .Render(context, maxWidth) | ||||
|                 .Concat(new List<Segment> { Segment.LineBreak }) | ||||
|                 .Concat(RenderChildren(context, maxWidth - Rendering.PartSize, _rootNode, depth: 0)); | ||||
|         } | ||||
|  | ||||
|         private IEnumerable<Segment> RenderChildren(RenderContext context, int maxWidth, TreeNode node, int depth, | ||||
|             int? trailingStarted = null) | ||||
|         { | ||||
|             var result = new List<Segment>(); | ||||
|  | ||||
|             foreach (var (index, firstChild, lastChild, childNode) in node.Children.Enumerate()) | ||||
|             { | ||||
|                 var lines = Segment.SplitLines(context, childNode.Render(context, maxWidth)); | ||||
|  | ||||
|                 foreach (var (lineIndex, firstLine, lastLine, line) in lines.Enumerate()) | ||||
|                 { | ||||
|                     var siblingConnectorSegment = | ||||
|                         new Segment(Rendering.GetPart(TreePart.SiblingConnector), Style); | ||||
|                     if (trailingStarted != null) | ||||
|                     { | ||||
|                         result.AddRange(Enumerable.Repeat(siblingConnectorSegment, trailingStarted.Value)); | ||||
|                         result.AddRange(Enumerable.Repeat( | ||||
|                             Segment.Padding(Rendering.PartSize), | ||||
|                             depth - trailingStarted.Value)); | ||||
|                     } | ||||
|                     else | ||||
|                     { | ||||
|                         result.AddRange(Enumerable.Repeat(siblingConnectorSegment, depth)); | ||||
|                     } | ||||
|  | ||||
|                     if (firstLine) | ||||
|                     { | ||||
|                         result.Add(lastChild | ||||
|                             ? new Segment(Rendering.GetPart(TreePart.BottomChildBranch), Style) | ||||
|                             : new Segment(Rendering.GetPart(TreePart.ChildBranch), Style)); | ||||
|                     } | ||||
|                     else | ||||
|                     { | ||||
|                         result.Add(lastChild ? Segment.Padding(Rendering.PartSize) : siblingConnectorSegment); | ||||
|                     } | ||||
|  | ||||
|                     result.AddRange(line); | ||||
|                     result.Add(Segment.LineBreak); | ||||
|                 } | ||||
|  | ||||
|                 var childTrailingStarted = trailingStarted ?? (lastChild ? depth : null); | ||||
|                 result.AddRange(RenderChildren(context, maxWidth - Rendering.PartSize, childNode, depth + 1, | ||||
|                     childTrailingStarted)); | ||||
|             } | ||||
|  | ||||
|             return result; | ||||
|         } | ||||
|     } | ||||
| } | ||||
							
								
								
									
										55
									
								
								src/Spectre.Console/Widgets/TreeNode.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										55
									
								
								src/Spectre.Console/Widgets/TreeNode.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,55 @@ | ||||
| using System.Collections.Generic; | ||||
| using System.Linq; | ||||
| using Spectre.Console.Rendering; | ||||
|  | ||||
| namespace Spectre.Console | ||||
| { | ||||
|     /// <summary> | ||||
|     /// Node of a tree. | ||||
|     /// </summary> | ||||
|     public sealed class TreeNode : IRenderable | ||||
|     { | ||||
|         private readonly IRenderable _renderable; | ||||
|         private List<TreeNode> _children; | ||||
|  | ||||
|         /// <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) | ||||
|         { | ||||
|             _renderable = renderable; | ||||
|             _children = new List<TreeNode>(children ?? Enumerable.Empty<TreeNode>()); | ||||
|         } | ||||
|  | ||||
|         /// <summary> | ||||
|         /// Gets the children of this node. | ||||
|         /// </summary> | ||||
|         public List<TreeNode> Children | ||||
|         { | ||||
|             get => _children; | ||||
|         } | ||||
|  | ||||
|         /// <summary> | ||||
|         /// Adds a child to the end of the node's list of children. | ||||
|         /// </summary> | ||||
|         /// <param name="child">Child to be added.</param> | ||||
|         public void AddChild(TreeNode child) | ||||
|         { | ||||
|             _children.Add(child); | ||||
|         } | ||||
|  | ||||
|         /// <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); | ||||
|         } | ||||
|     } | ||||
| } | ||||
		Reference in New Issue
	
	Block a user
	 Matt Constable
					Matt Constable