Add tree widget

This commit is contained in:
Matt Constable
2021-01-02 09:01:16 +00:00
committed by GitHub
parent 179e243214
commit b136d0299b
16 changed files with 467 additions and 16 deletions

View File

@ -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;

View File

@ -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);
}

View File

@ -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);

View 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;
}
}
}

View 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);
}
}
}