mirror of
https://github.com/nsnail/spectre.console.git
synced 2025-04-16 00:42:51 +08:00
Add tree widget
This commit is contained in:
parent
179e243214
commit
b136d0299b
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user