mirror of
https://github.com/spectreconsole/spectre.console.git
synced 2025-10-25 15:19:23 +00:00
126 lines
4.1 KiB
C#
126 lines
4.1 KiB
C#
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using Spectre.Console.Internal;
|
|
using Spectre.Console.Rendering;
|
|
|
|
namespace Spectre.Console
|
|
{
|
|
/// <summary>
|
|
/// 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; }
|
|
|
|
/// <summary>
|
|
/// Gets or sets the tree guide lines.
|
|
/// </summary>
|
|
public TreeGuide Guide { get; set; } = TreeGuide.Line;
|
|
|
|
/// <summary>
|
|
/// Gets the tree's child nodes.
|
|
/// </summary>
|
|
public List<TreeNode> Nodes => _root.Nodes;
|
|
|
|
/// <summary>
|
|
/// Gets or sets a value indicating whether or not the tree is expanded or not.
|
|
/// </summary>
|
|
public bool Expanded { get; set; } = true;
|
|
|
|
/// <summary>
|
|
/// Initializes a new instance of the <see cref="Tree"/> class.
|
|
/// </summary>
|
|
/// <param name="renderable">The tree label.</param>
|
|
public Tree(IRenderable renderable)
|
|
{
|
|
_root = new TreeNode(renderable);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Initializes a new instance of the <see cref="Tree"/> class.
|
|
/// </summary>
|
|
/// <param name="label">The tree label.</param>
|
|
public Tree(string label)
|
|
{
|
|
_root = new TreeNode(new Markup(label));
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
protected override IEnumerable<Segment> Render(RenderContext context, int maxWidth)
|
|
{
|
|
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)
|
|
{
|
|
var stackNode = stack.Pop();
|
|
if (stackNode.Count == 0)
|
|
{
|
|
levels.RemoveLast();
|
|
if (levels.Count > 0)
|
|
{
|
|
levels.AddOrReplaceLast(GetGuide(context, TreeGuidePart.Fork));
|
|
}
|
|
|
|
continue;
|
|
}
|
|
|
|
var isLastChild = stackNode.Count == 1;
|
|
var current = stackNode.Dequeue();
|
|
|
|
stack.Push(stackNode);
|
|
|
|
if (isLastChild)
|
|
{
|
|
levels.AddOrReplaceLast(GetGuide(context, TreeGuidePart.End));
|
|
}
|
|
|
|
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.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));
|
|
}
|
|
}
|
|
|
|
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));
|
|
|
|
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);
|
|
}
|
|
}
|
|
} |