mirror of
https://github.com/nsnail/spectre.console.git
synced 2025-06-20 05:48:14 +08:00
Add Layout widget (#1041)
* Add width to panels * Add height to panels * Replace RenderContext with RenderOptions * Remove exclusivity from alternative buffer * Add Layout widget * Add Align widget
This commit is contained in:
146
src/Spectre.Console/Widgets/Align.cs
Normal file
146
src/Spectre.Console/Widgets/Align.cs
Normal file
@ -0,0 +1,146 @@
|
||||
namespace Spectre.Console;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a renderable used to align content.
|
||||
/// </summary>
|
||||
public sealed class Align : Renderable
|
||||
{
|
||||
private readonly IRenderable _renderable;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the horizontal alignment.
|
||||
/// </summary>
|
||||
public HorizontalAlignment Horizontal { get; set; } = HorizontalAlignment.Left;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the vertical alignment.
|
||||
/// </summary>
|
||||
public VerticalAlignment? Vertical { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the width.
|
||||
/// </summary>
|
||||
public int? Width { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the height.
|
||||
/// </summary>
|
||||
public int? Height { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="Align"/> class.
|
||||
/// </summary>
|
||||
/// <param name="renderable">The renderable to align.</param>
|
||||
/// <param name="horizontal">The horizontal alignment.</param>
|
||||
/// <param name="vertical">The vertical alignment, or <c>null</c> if none.</param>
|
||||
public Align(IRenderable renderable, HorizontalAlignment horizontal, VerticalAlignment? vertical = null)
|
||||
{
|
||||
_renderable = renderable ?? throw new ArgumentNullException(nameof(renderable));
|
||||
|
||||
Horizontal = horizontal;
|
||||
Vertical = vertical;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="Align"/> class that is left aligned.
|
||||
/// </summary>
|
||||
/// <param name="renderable">The <see cref="IRenderable"/> to align.</param>
|
||||
/// <param name="vertical">The vertical alignment, or <c>null</c> if none.</param>
|
||||
/// <returns>A new <see cref="Align"/> object.</returns>
|
||||
public static Align Left(IRenderable renderable, VerticalAlignment? vertical = null)
|
||||
{
|
||||
return new Align(renderable, HorizontalAlignment.Left, vertical);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="Align"/> class that is center aligned.
|
||||
/// </summary>
|
||||
/// <param name="renderable">The <see cref="IRenderable"/> to align.</param>
|
||||
/// <param name="vertical">The vertical alignment, or <c>null</c> if none.</param>
|
||||
/// <returns>A new <see cref="Align"/> object.</returns>
|
||||
public static Align Center(IRenderable renderable, VerticalAlignment? vertical = null)
|
||||
{
|
||||
return new Align(renderable, HorizontalAlignment.Center, vertical);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="Align"/> class that is right aligned.
|
||||
/// </summary>
|
||||
/// <param name="renderable">The <see cref="IRenderable"/> to align.</param>
|
||||
/// <param name="vertical">The vertical alignment, or <c>null</c> if none.</param>
|
||||
/// <returns>A new <see cref="Align"/> object.</returns>
|
||||
public static Align Right(IRenderable renderable, VerticalAlignment? vertical = null)
|
||||
{
|
||||
return new Align(renderable, HorizontalAlignment.Right, vertical);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override IEnumerable<Segment> Render(RenderOptions options, int maxWidth)
|
||||
{
|
||||
var rendered = _renderable.Render(options with { Height = null }, maxWidth);
|
||||
var lines = Segment.SplitLines(rendered);
|
||||
|
||||
var width = Math.Min(Width ?? maxWidth, maxWidth);
|
||||
var height = Height ?? options.Height;
|
||||
|
||||
var blank = new SegmentLine(new[] { new Segment(new string(' ', width)) });
|
||||
|
||||
// Align vertically
|
||||
if (Vertical != null && height != null)
|
||||
{
|
||||
switch (Vertical)
|
||||
{
|
||||
case VerticalAlignment.Top:
|
||||
{
|
||||
var diff = Height - lines.Count;
|
||||
for (var i = 0; i < diff; i++)
|
||||
{
|
||||
lines.Add(blank);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case VerticalAlignment.Middle:
|
||||
{
|
||||
var top = (height - lines.Count) / 2;
|
||||
var bottom = height - top - lines.Count;
|
||||
|
||||
for (var i = 0; i < top; i++)
|
||||
{
|
||||
lines.Insert(0, blank);
|
||||
}
|
||||
|
||||
for (var i = 0; i < bottom; i++)
|
||||
{
|
||||
lines.Add(blank);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case VerticalAlignment.Bottom:
|
||||
{
|
||||
var diff = Height - lines.Count;
|
||||
for (var i = 0; i < diff; i++)
|
||||
{
|
||||
lines.Insert(0, blank);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
throw new NotSupportedException("Unknown vertical alignment");
|
||||
}
|
||||
}
|
||||
|
||||
// Align horizontally
|
||||
foreach (var line in lines)
|
||||
{
|
||||
Aligner.AlignHorizontally(line, Horizontal, width);
|
||||
}
|
||||
|
||||
return new SegmentLineEnumerator(lines);
|
||||
}
|
||||
}
|
@ -107,6 +107,7 @@ public sealed class Calendar : JustInTimeRenderable, IHasCulture, IHasTableBorde
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
[Obsolete("Use the Align widget instead. This property will be removed in a later release.")]
|
||||
public Justify? Alignment
|
||||
{
|
||||
get => _alignment;
|
||||
@ -162,6 +163,7 @@ public sealed class Calendar : JustInTimeRenderable, IHasCulture, IHasTableBorde
|
||||
{
|
||||
var culture = Culture ?? CultureInfo.InvariantCulture;
|
||||
|
||||
#pragma warning disable CS0618 // Type or member is obsolete
|
||||
var table = new Table
|
||||
{
|
||||
Border = _border,
|
||||
@ -169,6 +171,7 @@ public sealed class Calendar : JustInTimeRenderable, IHasCulture, IHasTableBorde
|
||||
BorderStyle = _borderStyle,
|
||||
Alignment = _alignment,
|
||||
};
|
||||
#pragma warning restore CS0618 // Type or member is obsolete
|
||||
|
||||
if (ShowHeader)
|
||||
{
|
||||
|
@ -70,7 +70,7 @@ public sealed class Canvas : Renderable
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override Measurement Measure(RenderContext context, int maxWidth)
|
||||
protected override Measurement Measure(RenderOptions options, int maxWidth)
|
||||
{
|
||||
if (PixelWidth < 0)
|
||||
{
|
||||
@ -88,7 +88,7 @@ public sealed class Canvas : Renderable
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override IEnumerable<Segment> Render(RenderContext context, int maxWidth)
|
||||
protected override IEnumerable<Segment> Render(RenderOptions options, int maxWidth)
|
||||
{
|
||||
if (PixelWidth < 0)
|
||||
{
|
||||
|
@ -52,14 +52,14 @@ public sealed class BarChart : Renderable, IHasCulture
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override Measurement Measure(RenderContext context, int maxWidth)
|
||||
protected override Measurement Measure(RenderOptions options, int maxWidth)
|
||||
{
|
||||
var width = Math.Min(Width ?? maxWidth, maxWidth);
|
||||
return new Measurement(width, width);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override IEnumerable<Segment> Render(RenderContext context, int maxWidth)
|
||||
protected override IEnumerable<Segment> Render(RenderOptions options, int maxWidth)
|
||||
{
|
||||
var width = Math.Min(Width ?? maxWidth, maxWidth);
|
||||
var maxValue = Math.Max(MaxValue ?? 0d, Data.Max(item => item.Value));
|
||||
@ -72,7 +72,7 @@ public sealed class BarChart : Renderable, IHasCulture
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(Label))
|
||||
{
|
||||
grid.AddRow(Text.Empty, new Markup(Label).Alignment(LabelAlignment));
|
||||
grid.AddRow(Text.Empty, new Markup(Label).Justify(LabelAlignment));
|
||||
}
|
||||
|
||||
foreach (var item in Data)
|
||||
@ -93,6 +93,6 @@ public sealed class BarChart : Renderable, IHasCulture
|
||||
});
|
||||
}
|
||||
|
||||
return ((IRenderable)grid).Render(context, width);
|
||||
return ((IRenderable)grid).Render(options, width);
|
||||
}
|
||||
}
|
@ -11,13 +11,13 @@ internal sealed class BreakdownBar : Renderable
|
||||
_data = data ?? throw new ArgumentNullException(nameof(data));
|
||||
}
|
||||
|
||||
protected override Measurement Measure(RenderContext context, int maxWidth)
|
||||
protected override Measurement Measure(RenderOptions options, int maxWidth)
|
||||
{
|
||||
var width = Math.Min(Width ?? maxWidth, maxWidth);
|
||||
return new Measurement(width, width);
|
||||
}
|
||||
|
||||
protected override IEnumerable<Segment> Render(RenderContext context, int maxWidth)
|
||||
protected override IEnumerable<Segment> Render(RenderOptions options, int maxWidth)
|
||||
{
|
||||
var width = Math.Min(Width ?? maxWidth, maxWidth);
|
||||
|
||||
|
@ -3,7 +3,7 @@ namespace Spectre.Console;
|
||||
/// <summary>
|
||||
/// A renderable breakdown chart.
|
||||
/// </summary>
|
||||
public sealed class BreakdownChart : Renderable, IHasCulture
|
||||
public sealed class BreakdownChart : Renderable, IHasCulture, IExpandable
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the breakdown chart data.
|
||||
@ -43,6 +43,13 @@ public sealed class BreakdownChart : Renderable, IHasCulture
|
||||
/// <remarks>Defaults to invariant culture.</remarks>
|
||||
public CultureInfo? Culture { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether or not the object should
|
||||
/// expand to the available space. If <c>false</c>, the object's
|
||||
/// width will be auto calculated.
|
||||
/// </summary>
|
||||
public bool Expand { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="BreakdownChart"/> class.
|
||||
/// </summary>
|
||||
@ -53,14 +60,14 @@ public sealed class BreakdownChart : Renderable, IHasCulture
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override Measurement Measure(RenderContext context, int maxWidth)
|
||||
protected override Measurement Measure(RenderOptions options, int maxWidth)
|
||||
{
|
||||
var width = Math.Min(Width ?? maxWidth, maxWidth);
|
||||
return new Measurement(width, width);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override IEnumerable<Segment> Render(RenderContext context, int maxWidth)
|
||||
protected override IEnumerable<Segment> Render(RenderOptions options, int maxWidth)
|
||||
{
|
||||
var width = Math.Min(Width ?? maxWidth, maxWidth);
|
||||
|
||||
@ -90,6 +97,11 @@ public sealed class BreakdownChart : Renderable, IHasCulture
|
||||
});
|
||||
}
|
||||
|
||||
return ((IRenderable)grid).Render(context, width);
|
||||
if (!Expand)
|
||||
{
|
||||
grid.Collapse();
|
||||
}
|
||||
|
||||
return ((IRenderable)grid).Render(options, width);
|
||||
}
|
||||
}
|
@ -14,13 +14,13 @@ internal sealed class BreakdownTags : Renderable
|
||||
_data = data ?? throw new ArgumentNullException(nameof(data));
|
||||
}
|
||||
|
||||
protected override Measurement Measure(RenderContext context, int maxWidth)
|
||||
protected override Measurement Measure(RenderOptions options, int maxWidth)
|
||||
{
|
||||
var width = Math.Min(Width ?? maxWidth, maxWidth);
|
||||
return new Measurement(width, width);
|
||||
}
|
||||
|
||||
protected override IEnumerable<Segment> Render(RenderContext context, int maxWidth)
|
||||
protected override IEnumerable<Segment> Render(RenderOptions options, int maxWidth)
|
||||
{
|
||||
var culture = Culture ?? CultureInfo.InvariantCulture;
|
||||
|
||||
@ -29,13 +29,13 @@ internal sealed class BreakdownTags : Renderable
|
||||
{
|
||||
var panel = new Panel(GetTag(item, culture));
|
||||
panel.Inline = true;
|
||||
panel.Padding = new Padding(0, 0);
|
||||
panel.Padding = new Padding(0, 0, 2, 0);
|
||||
panel.NoBorder();
|
||||
|
||||
panels.Add(panel);
|
||||
}
|
||||
|
||||
foreach (var segment in ((IRenderable)new Columns(panels).Padding(0, 0)).Render(context, maxWidth))
|
||||
foreach (var segment in ((IRenderable)new Columns(panels).Padding(0, 0)).Render(options, maxWidth))
|
||||
{
|
||||
yield return segment;
|
||||
}
|
||||
|
@ -55,11 +55,11 @@ public sealed class Columns : Renderable, IPaddable, IExpandable
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override Measurement Measure(RenderContext context, int maxWidth)
|
||||
protected override Measurement Measure(RenderOptions options, int maxWidth)
|
||||
{
|
||||
var maxPadding = Math.Max(Padding.GetLeftSafe(), Padding.GetRightSafe());
|
||||
|
||||
var itemWidths = _items.Select(item => item.Measure(context, maxWidth).Max).ToArray();
|
||||
var itemWidths = _items.Select(item => item.Measure(options, maxWidth).Max).ToArray();
|
||||
var columnCount = CalculateColumnCount(maxWidth, itemWidths, _items.Count, maxPadding);
|
||||
if (columnCount == 0)
|
||||
{
|
||||
@ -83,11 +83,11 @@ public sealed class Columns : Renderable, IPaddable, IExpandable
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override IEnumerable<Segment> Render(RenderContext context, int maxWidth)
|
||||
protected override IEnumerable<Segment> Render(RenderOptions options, int maxWidth)
|
||||
{
|
||||
var maxPadding = Math.Max(Padding.GetLeftSafe(), Padding.GetRightSafe());
|
||||
|
||||
var itemWidths = _items.Select(item => item.Measure(context, maxWidth).Max).ToArray();
|
||||
var itemWidths = _items.Select(item => item.Measure(options, maxWidth).Max).ToArray();
|
||||
var columnCount = CalculateColumnCount(maxWidth, itemWidths, _items.Count, maxPadding);
|
||||
if (columnCount == 0)
|
||||
{
|
||||
@ -121,7 +121,7 @@ public sealed class Columns : Renderable, IPaddable, IExpandable
|
||||
table.AddRow(_items.Skip(start).Take(columnCount).ToArray());
|
||||
}
|
||||
|
||||
return ((IRenderable)table).Render(context, maxWidth);
|
||||
return ((IRenderable)table).Render(options, maxWidth);
|
||||
}
|
||||
|
||||
// Algorithm borrowed from https://github.com/willmcgugan/rich/blob/master/rich/columns.py
|
||||
|
@ -9,14 +9,14 @@ internal sealed class ControlCode : Renderable
|
||||
_segment = Segment.Control(control);
|
||||
}
|
||||
|
||||
protected override Measurement Measure(RenderContext context, int maxWidth)
|
||||
protected override Measurement Measure(RenderOptions options, int maxWidth)
|
||||
{
|
||||
return new Measurement(0, 0);
|
||||
}
|
||||
|
||||
protected override IEnumerable<Segment> Render(RenderContext context, int maxWidth)
|
||||
protected override IEnumerable<Segment> Render(RenderOptions options, int maxWidth)
|
||||
{
|
||||
if (context.Ansi)
|
||||
if (options.Ansi)
|
||||
{
|
||||
yield return _segment;
|
||||
}
|
||||
|
@ -3,7 +3,7 @@ namespace Spectre.Console;
|
||||
/// <summary>
|
||||
/// Represents text rendered with a FIGlet font.
|
||||
/// </summary>
|
||||
public sealed class FigletText : Renderable, IAlignable
|
||||
public sealed class FigletText : Renderable, IHasJustification
|
||||
{
|
||||
private readonly FigletFont _font;
|
||||
private readonly string _text;
|
||||
@ -14,7 +14,7 @@ public sealed class FigletText : Renderable, IAlignable
|
||||
public Color? Color { get; set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public Justify? Alignment { get; set; }
|
||||
public Justify? Justification { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="FigletText"/> class.
|
||||
@ -37,10 +37,10 @@ public sealed class FigletText : Renderable, IAlignable
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override IEnumerable<Segment> Render(RenderContext context, int maxWidth)
|
||||
protected override IEnumerable<Segment> Render(RenderOptions options, int maxWidth)
|
||||
{
|
||||
var style = new Style(Color ?? Console.Color.Default);
|
||||
var alignment = Alignment ?? Justify.Left;
|
||||
var alignment = Justification ?? Console.Justify.Left;
|
||||
|
||||
foreach (var row in GetRows(maxWidth))
|
||||
{
|
||||
@ -49,7 +49,7 @@ public sealed class FigletText : Renderable, IAlignable
|
||||
var line = new Segment(string.Concat(row.Select(x => x.Lines[index])), style);
|
||||
|
||||
var lineWidth = line.CellCount();
|
||||
if (alignment == Justify.Left)
|
||||
if (alignment == Console.Justify.Left)
|
||||
{
|
||||
yield return line;
|
||||
|
||||
@ -58,7 +58,7 @@ public sealed class FigletText : Renderable, IAlignable
|
||||
yield return Segment.Padding(maxWidth - lineWidth);
|
||||
}
|
||||
}
|
||||
else if (alignment == Justify.Center)
|
||||
else if (alignment == Console.Justify.Center)
|
||||
{
|
||||
var left = (maxWidth - lineWidth) / 2;
|
||||
var right = left + ((maxWidth - lineWidth) % 2);
|
||||
@ -67,7 +67,7 @@ public sealed class FigletText : Renderable, IAlignable
|
||||
yield return line;
|
||||
yield return Segment.Padding(right);
|
||||
}
|
||||
else if (alignment == Justify.Right)
|
||||
else if (alignment == Console.Justify.Right)
|
||||
{
|
||||
if (lineWidth < maxWidth)
|
||||
{
|
||||
|
@ -30,6 +30,7 @@ public sealed class Grid : JustInTimeRenderable, IExpandable, IAlignable
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
[Obsolete("Use the Align widget instead. This property will be removed in a later release.")]
|
||||
public Justify? Alignment
|
||||
{
|
||||
get => _alignment;
|
||||
|
311
src/Spectre.Console/Widgets/Layout/Layout.cs
Normal file
311
src/Spectre.Console/Widgets/Layout/Layout.cs
Normal file
@ -0,0 +1,311 @@
|
||||
namespace Spectre.Console;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a renderable to divide a fixed height into rows or columns.
|
||||
/// </summary>
|
||||
public sealed class Layout : Renderable, IRatioResolvable, IHasVisibility
|
||||
{
|
||||
private LayoutSplitter _splitter;
|
||||
private Layout[] _children;
|
||||
private IRenderable _renderable;
|
||||
private int _ratio;
|
||||
private int _minimumSize;
|
||||
private int? _size;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the name.
|
||||
/// </summary>
|
||||
public string? Name { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the ratio.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Defaults to <c>1</c>.
|
||||
/// Must be greater than <c>0</c>.
|
||||
/// </remarks>
|
||||
public int Ratio
|
||||
{
|
||||
get => _ratio;
|
||||
set
|
||||
{
|
||||
if (value < 1)
|
||||
{
|
||||
throw new InvalidOperationException("Ratio must be equal to or greater than 1");
|
||||
}
|
||||
|
||||
_ratio = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the minimum width.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Defaults to <c>1</c>.
|
||||
/// Must be greater than <c>0</c>.
|
||||
/// </remarks>
|
||||
public int MinimumSize
|
||||
{
|
||||
get => _minimumSize;
|
||||
set
|
||||
{
|
||||
if (value < 1)
|
||||
{
|
||||
throw new InvalidOperationException("Minimum size must be equal to or greater than 1");
|
||||
}
|
||||
|
||||
_minimumSize = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the width.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Defaults to <c>null</c>.
|
||||
/// Must be greater than <c>0</c>.
|
||||
/// </remarks>
|
||||
public int? Size
|
||||
{
|
||||
get => _size;
|
||||
set
|
||||
{
|
||||
if (value < 1)
|
||||
{
|
||||
throw new InvalidOperationException("Size must be equal to or greater than 1");
|
||||
}
|
||||
|
||||
_size = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether or not the layout should
|
||||
/// be visible or not.
|
||||
/// </summary>
|
||||
/// <remarks>Defaults to <c>true</c>.</remarks>
|
||||
public bool IsVisible { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the splitter used for this layout.
|
||||
/// </summary>
|
||||
internal LayoutSplitter Splitter => _splitter;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the <see cref="IRenderable"/> associated with this layout.
|
||||
/// </summary>
|
||||
internal IRenderable Renderable => _renderable;
|
||||
|
||||
/// <summary>
|
||||
/// Gets a child layout by it's name.
|
||||
/// </summary>
|
||||
/// <param name="name">The layout name.</param>
|
||||
/// <returns>The specified child <see cref="Layout"/>.</returns>
|
||||
public Layout this[string name]
|
||||
{
|
||||
get => GetLayout(name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="Layout"/> class.
|
||||
/// </summary>
|
||||
/// <param name="name">The layout name.</param>
|
||||
public Layout(string name)
|
||||
: this(name, null)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="Layout"/> class.
|
||||
/// </summary>
|
||||
/// <param name="renderable">The renderable.</param>
|
||||
public Layout(IRenderable renderable)
|
||||
: this(null, renderable)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="Layout"/> class.
|
||||
/// </summary>
|
||||
/// <param name="name">The layout name.</param>
|
||||
/// <param name="renderable">The renderable.</param>
|
||||
public Layout(string? name = null, IRenderable? renderable = null)
|
||||
{
|
||||
_splitter = LayoutSplitter.Null;
|
||||
_children = Array.Empty<Layout>();
|
||||
_renderable = renderable ?? new LayoutPlaceholder(this);
|
||||
_ratio = 1;
|
||||
_size = null;
|
||||
|
||||
Name = name;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a child layout by it's name.
|
||||
/// </summary>
|
||||
/// <param name="name">The layout name.</param>
|
||||
/// <returns>The specified child <see cref="Layout"/>.</returns>
|
||||
public Layout GetLayout(string name)
|
||||
{
|
||||
if (string.IsNullOrEmpty(name))
|
||||
{
|
||||
throw new ArgumentException($"'{nameof(name)}' cannot be null or empty.", nameof(name));
|
||||
}
|
||||
|
||||
var stack = new Stack<Layout>();
|
||||
stack.Push(this);
|
||||
|
||||
while (stack.Count > 0)
|
||||
{
|
||||
var current = stack.Pop();
|
||||
if (name.Equals(current.Name, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return current;
|
||||
}
|
||||
|
||||
foreach (var layout in current.GetChildren())
|
||||
{
|
||||
stack.Push(layout);
|
||||
}
|
||||
}
|
||||
|
||||
throw new InvalidOperationException($"Could not find layout '{name}'");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Splits the layout into rows.
|
||||
/// </summary>
|
||||
/// <param name="children">The layout to split into rows.</param>
|
||||
/// <returns>The same instance so that multiple calls can be chained.</returns>
|
||||
public Layout SplitRows(params Layout[] children)
|
||||
{
|
||||
Split(LayoutSplitter.Row, children);
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Splits the layout into columns.
|
||||
/// </summary>
|
||||
/// <param name="children">The layout to split into columns.</param>
|
||||
/// <returns>The same instance so that multiple calls can be chained.</returns>
|
||||
public Layout SplitColumns(params Layout[] children)
|
||||
{
|
||||
Split(LayoutSplitter.Column, children);
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the containing <see cref="IRenderable"/>.
|
||||
/// </summary>
|
||||
/// <param name="renderable">The renderable to use for this layout.</param>
|
||||
/// /// <returns>The same instance so that multiple calls can be chained.</returns>
|
||||
public Layout Update(IRenderable renderable)
|
||||
{
|
||||
_renderable = renderable ?? new LayoutPlaceholder(this);
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override IEnumerable<Segment> Render(RenderOptions options, int maxWidth)
|
||||
{
|
||||
var height = options.Height ?? options.ConsoleSize.Height;
|
||||
var map = MakeRenderMap(options, maxWidth);
|
||||
|
||||
var layoutLines = new List<SegmentLine>();
|
||||
layoutLines.AddRange(Enumerable.Range(0, height).Select(x => new SegmentLine()));
|
||||
|
||||
foreach (var (region, lines) in map.Values.Select(x => (x.Region, x.Render)))
|
||||
{
|
||||
foreach (var line in layoutLines
|
||||
.Skip(region.Y)
|
||||
.Take(region.Y + region.Height)
|
||||
.Enumerate().Select(x => (Index: x.Index + region.Y, Line: x.Item))
|
||||
.Zip(lines, (first, second) => (first.Index, Line: second)))
|
||||
{
|
||||
layoutLines[line.Index].AddRange(line.Line);
|
||||
}
|
||||
}
|
||||
|
||||
// Return all the segments in all the lines
|
||||
foreach (var (_, _, last, line) in layoutLines.Enumerate())
|
||||
{
|
||||
foreach (var segment in line)
|
||||
{
|
||||
yield return segment;
|
||||
}
|
||||
|
||||
if (!last)
|
||||
{
|
||||
yield return Segment.LineBreak;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private IEnumerable<Layout> GetChildren(bool visibleOnly = false)
|
||||
{
|
||||
return visibleOnly ? _children.Where(c => c.IsVisible) : _children;
|
||||
}
|
||||
|
||||
private bool HasChildren(bool visibleOnly = false)
|
||||
{
|
||||
return visibleOnly ? _children.Any(c => c.IsVisible) : _children.Any();
|
||||
}
|
||||
|
||||
private void Split(LayoutSplitter splitter, Layout[] layouts)
|
||||
{
|
||||
if (_children.Length > 0)
|
||||
{
|
||||
throw new InvalidOperationException("Cannot split the same layout twice");
|
||||
}
|
||||
|
||||
_splitter = splitter ?? throw new ArgumentNullException(nameof(splitter));
|
||||
_children = layouts ?? throw new ArgumentNullException(nameof(layouts));
|
||||
}
|
||||
|
||||
private Dictionary<Layout, LayoutRender> MakeRenderMap(RenderOptions options, int maxWidth)
|
||||
{
|
||||
var result = new Dictionary<Layout, LayoutRender>();
|
||||
|
||||
var renderWidth = maxWidth;
|
||||
var renderHeight = options.Height ?? options.ConsoleSize.Height;
|
||||
var regionMap = MakeRegionMap(maxWidth, renderHeight);
|
||||
|
||||
foreach (var (layout, region) in regionMap.Where(x => !x.Layout.HasChildren(visibleOnly: true)))
|
||||
{
|
||||
var segments = layout.Renderable.Render(options with { Height = region.Height }, region.Width);
|
||||
|
||||
var lines = Segment.SplitLines(segments, region.Width, region.Height);
|
||||
lines = Segment.MakeWidth(region.Width, lines);
|
||||
|
||||
result[layout] = new LayoutRender(region, lines);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private IEnumerable<(Layout Layout, Region Region)> MakeRegionMap(int width, int height)
|
||||
{
|
||||
var stack = new Stack<(Layout Layout, Region Region)>();
|
||||
stack.Push((this, new Region(0, 0, width, height)));
|
||||
|
||||
var result = new List<(Layout Layout, Region Region)>();
|
||||
|
||||
while (stack.Count > 0)
|
||||
{
|
||||
var current = stack.Pop();
|
||||
result.Add(current);
|
||||
|
||||
if (current.Layout.HasChildren(visibleOnly: true))
|
||||
{
|
||||
foreach (var childAndRegion in current.Layout.Splitter
|
||||
.Divide(current.Region, current.Layout.GetChildren(visibleOnly: true)))
|
||||
{
|
||||
stack.Push(childAndRegion);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result.ReverseEnumerable();
|
||||
}
|
||||
}
|
31
src/Spectre.Console/Widgets/Layout/LayoutPlaceholder.cs
Normal file
31
src/Spectre.Console/Widgets/Layout/LayoutPlaceholder.cs
Normal file
@ -0,0 +1,31 @@
|
||||
namespace Spectre.Console;
|
||||
|
||||
internal sealed class LayoutPlaceholder : Renderable
|
||||
{
|
||||
public Layout Layout { get; }
|
||||
|
||||
public LayoutPlaceholder(Layout layout)
|
||||
{
|
||||
Layout = layout ?? throw new ArgumentNullException(nameof(layout));
|
||||
}
|
||||
|
||||
protected override IEnumerable<Segment> Render(RenderOptions options, int maxWidth)
|
||||
{
|
||||
var width = maxWidth;
|
||||
var height = options.Height ?? options.ConsoleSize.Height;
|
||||
var title = Layout.Name != null
|
||||
? $"{Layout.Name} ({width} x {height})"
|
||||
: $"{width} x {height}";
|
||||
|
||||
var panel = new Panel(
|
||||
Align.Center(new Text("Placeholder"), VerticalAlignment.Middle))
|
||||
{
|
||||
Width = maxWidth,
|
||||
Height = options.Height ?? options.ConsoleSize.Height,
|
||||
Header = new PanelHeader(title),
|
||||
Border = BoxBorder.Rounded,
|
||||
};
|
||||
|
||||
return ((IRenderable)panel).Render(options, maxWidth);
|
||||
}
|
||||
}
|
14
src/Spectre.Console/Widgets/Layout/LayoutRender.cs
Normal file
14
src/Spectre.Console/Widgets/Layout/LayoutRender.cs
Normal file
@ -0,0 +1,14 @@
|
||||
namespace Spectre.Console;
|
||||
|
||||
[DebuggerDisplay("{Region,nq}")]
|
||||
internal sealed class LayoutRender
|
||||
{
|
||||
public Region Region { get; }
|
||||
public List<SegmentLine> Render { get; }
|
||||
|
||||
public LayoutRender(Region region, List<SegmentLine> render)
|
||||
{
|
||||
Region = region;
|
||||
Render = render ?? throw new ArgumentNullException(nameof(render));
|
||||
}
|
||||
}
|
48
src/Spectre.Console/Widgets/Layout/LayoutSplitter.cs
Normal file
48
src/Spectre.Console/Widgets/Layout/LayoutSplitter.cs
Normal file
@ -0,0 +1,48 @@
|
||||
namespace Spectre.Console;
|
||||
|
||||
internal abstract class LayoutSplitter
|
||||
{
|
||||
public static LayoutSplitter Column { get; } = new ColumnSplitter();
|
||||
public static LayoutSplitter Row { get; } = new RowSplitter();
|
||||
public static LayoutSplitter Null { get; } = new NullSplitter();
|
||||
|
||||
public abstract IEnumerable<(Layout Child, Region Region)> Divide(Region region, IEnumerable<Layout> layouts);
|
||||
|
||||
private sealed class NullSplitter : LayoutSplitter
|
||||
{
|
||||
public override IEnumerable<(Layout Child, Region Region)> Divide(Region region, IEnumerable<Layout> layouts)
|
||||
{
|
||||
yield break;
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class ColumnSplitter : LayoutSplitter
|
||||
{
|
||||
public override IEnumerable<(Layout Child, Region Region)> Divide(Region region, IEnumerable<Layout> children)
|
||||
{
|
||||
var widths = Ratio.Resolve(region.Width, children);
|
||||
var offset = 0;
|
||||
|
||||
foreach (var (child, childWidth) in children.Zip(widths, (child, width) => (child, width)))
|
||||
{
|
||||
yield return (child, new Region(region.X + offset, region.Y, childWidth, region.Height));
|
||||
offset += childWidth;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class RowSplitter : LayoutSplitter
|
||||
{
|
||||
public override IEnumerable<(Layout Child, Region Region)> Divide(Region region, IEnumerable<Layout> children)
|
||||
{
|
||||
var heights = Ratio.Resolve(region.Height, children);
|
||||
var offset = 0;
|
||||
|
||||
foreach (var (child, childHeight) in children.Zip(heights, (child, height) => (child, height)))
|
||||
{
|
||||
yield return (child, new Region(region.X, region.Y + offset, region.Width, childHeight));
|
||||
offset += childHeight;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -4,15 +4,15 @@ namespace Spectre.Console;
|
||||
/// A renderable piece of markup text.
|
||||
/// </summary>
|
||||
[SuppressMessage("Naming", "CA1724:Type names should not match namespaces")]
|
||||
public sealed class Markup : Renderable, IAlignable, IOverflowable
|
||||
public sealed class Markup : Renderable, IHasJustification, IOverflowable
|
||||
{
|
||||
private readonly Paragraph _paragraph;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public Justify? Alignment
|
||||
public Justify? Justification
|
||||
{
|
||||
get => _paragraph.Alignment;
|
||||
set => _paragraph.Alignment = value;
|
||||
get => _paragraph.Justification;
|
||||
set => _paragraph.Justification = value;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
@ -43,15 +43,15 @@ public sealed class Markup : Renderable, IAlignable, IOverflowable
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override Measurement Measure(RenderContext context, int maxWidth)
|
||||
protected override Measurement Measure(RenderOptions options, int maxWidth)
|
||||
{
|
||||
return ((IRenderable)_paragraph).Measure(context, maxWidth);
|
||||
return ((IRenderable)_paragraph).Measure(options, maxWidth);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override IEnumerable<Segment> Render(RenderContext context, int maxWidth)
|
||||
protected override IEnumerable<Segment> Render(RenderOptions options, int maxWidth)
|
||||
{
|
||||
return ((IRenderable)_paragraph).Render(context, maxWidth);
|
||||
return ((IRenderable)_paragraph).Render(options, maxWidth);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -29,10 +29,10 @@ public sealed class Padder : Renderable, IPaddable, IExpandable
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override Measurement Measure(RenderContext context, int maxWidth)
|
||||
protected override Measurement Measure(RenderOptions options, int maxWidth)
|
||||
{
|
||||
var paddingWidth = Padding?.GetWidth() ?? 0;
|
||||
var measurement = _child.Measure(context, maxWidth - paddingWidth);
|
||||
var measurement = _child.Measure(options, maxWidth - paddingWidth);
|
||||
|
||||
return new Measurement(
|
||||
measurement.Min + paddingWidth,
|
||||
@ -40,14 +40,14 @@ public sealed class Padder : Renderable, IPaddable, IExpandable
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override IEnumerable<Segment> Render(RenderContext context, int maxWidth)
|
||||
protected override IEnumerable<Segment> Render(RenderOptions options, int maxWidth)
|
||||
{
|
||||
var paddingWidth = Padding?.GetWidth() ?? 0;
|
||||
var childWidth = maxWidth - paddingWidth;
|
||||
|
||||
if (!Expand)
|
||||
{
|
||||
var measurement = _child.Measure(context, maxWidth - paddingWidth);
|
||||
var measurement = _child.Measure(options, maxWidth - paddingWidth);
|
||||
childWidth = measurement.Max;
|
||||
}
|
||||
|
||||
@ -66,7 +66,7 @@ public sealed class Padder : Renderable, IPaddable, IExpandable
|
||||
result.Add(Segment.LineBreak);
|
||||
}
|
||||
|
||||
var child = _child.Render(context, maxWidth - paddingWidth);
|
||||
var child = _child.Render(options, maxWidth - paddingWidth);
|
||||
foreach (var line in Segment.SplitLines(child))
|
||||
{
|
||||
// Left padding
|
||||
|
@ -35,6 +35,16 @@ public sealed class Panel : Renderable, IHasBoxBorder, IHasBorder, IExpandable,
|
||||
/// </summary>
|
||||
public PanelHeader? Header { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the width of the panel.
|
||||
/// </summary>
|
||||
public int? Width { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the height of the panel.
|
||||
/// </summary>
|
||||
public int? Height { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether or not the panel is inlined.
|
||||
/// </summary>
|
||||
@ -59,54 +69,73 @@ public sealed class Panel : Renderable, IHasBoxBorder, IHasBorder, IExpandable,
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override Measurement Measure(RenderContext context, int maxWidth)
|
||||
protected override Measurement Measure(RenderOptions options, int maxWidth)
|
||||
{
|
||||
var child = new Padder(_child, Padding);
|
||||
var childWidth = ((IRenderable)child).Measure(context, maxWidth);
|
||||
return Measure(options, maxWidth, child);
|
||||
}
|
||||
|
||||
private Measurement Measure(RenderOptions options, int maxWidth, IRenderable child)
|
||||
{
|
||||
var edgeWidth = (options.GetSafeBorder(this) is not NoBoxBorder) ? EdgeWidth : 0;
|
||||
var childWidth = child.Measure(options, maxWidth - edgeWidth);
|
||||
|
||||
if (Width != null)
|
||||
{
|
||||
var width = Width.Value - edgeWidth;
|
||||
if (width > childWidth.Max)
|
||||
{
|
||||
childWidth = new Measurement(
|
||||
childWidth.Min,
|
||||
width);
|
||||
}
|
||||
}
|
||||
|
||||
return new Measurement(
|
||||
childWidth.Min + EdgeWidth,
|
||||
childWidth.Max + EdgeWidth);
|
||||
childWidth.Min + edgeWidth,
|
||||
childWidth.Max + edgeWidth);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override IEnumerable<Segment> Render(RenderContext context, int maxWidth)
|
||||
protected override IEnumerable<Segment> Render(RenderOptions options, int maxWidth)
|
||||
{
|
||||
var edgeWidth = EdgeWidth;
|
||||
|
||||
var border = BoxExtensions.GetSafeBorder(Border, !context.Unicode && UseSafeBorder);
|
||||
var border = options.GetSafeBorder(this);
|
||||
var borderStyle = BorderStyle ?? Style.Plain;
|
||||
|
||||
var showBorder = true;
|
||||
if (border is NoBoxBorder)
|
||||
{
|
||||
showBorder = false;
|
||||
edgeWidth = 0;
|
||||
}
|
||||
var showBorder = border is not NoBoxBorder;
|
||||
var edgeWidth = showBorder ? EdgeWidth : 0;
|
||||
|
||||
var child = new Padder(_child, Padding);
|
||||
var childWidth = maxWidth - edgeWidth;
|
||||
var width = Measure(options, maxWidth, child);
|
||||
|
||||
var panelWidth = Math.Min(!Expand ? width.Max : maxWidth, maxWidth);
|
||||
var innerWidth = panelWidth - edgeWidth;
|
||||
|
||||
var height = Height != null
|
||||
? Height - 2
|
||||
: options.Height != null
|
||||
? options.Height - 2
|
||||
: null;
|
||||
|
||||
if (!Expand)
|
||||
{
|
||||
var measurement = ((IRenderable)child).Measure(context, maxWidth - edgeWidth);
|
||||
childWidth = measurement.Max;
|
||||
// Set the height to the explicit height (or null)
|
||||
// if the panel isn't expandable.
|
||||
height = Height != null ? Height - 2 : null;
|
||||
}
|
||||
|
||||
var panelWidth = childWidth + edgeWidth;
|
||||
panelWidth = Math.Min(panelWidth, maxWidth);
|
||||
childWidth = panelWidth - edgeWidth;
|
||||
|
||||
// Start building the panel
|
||||
var result = new List<Segment>();
|
||||
|
||||
// Panel top
|
||||
if (showBorder)
|
||||
{
|
||||
// Panel top
|
||||
AddTopBorder(result, context, border, borderStyle, panelWidth);
|
||||
AddTopBorder(result, options, border, borderStyle, panelWidth);
|
||||
}
|
||||
|
||||
// Split the child segments into lines.
|
||||
var childSegments = ((IRenderable)child).Render(context, childWidth);
|
||||
foreach (var (_, _, last, line) in Segment.SplitLines(childSegments, childWidth).Enumerate())
|
||||
var childSegments = ((IRenderable)child).Render(options with { Height = height }, innerWidth);
|
||||
foreach (var (_, _, last, line) in Segment.SplitLines(childSegments, innerWidth, height).Enumerate())
|
||||
{
|
||||
if (line.Count == 1 && line[0].IsWhiteSpace)
|
||||
{
|
||||
@ -125,9 +154,9 @@ public sealed class Panel : Renderable, IHasBoxBorder, IHasBorder, IExpandable,
|
||||
|
||||
// Do we need to pad the panel?
|
||||
var length = line.Sum(segment => segment.CellCount());
|
||||
if (length < childWidth)
|
||||
if (length < innerWidth)
|
||||
{
|
||||
var diff = childWidth - length;
|
||||
var diff = innerWidth - length;
|
||||
content.Add(Segment.Padding(diff));
|
||||
}
|
||||
|
||||
@ -170,7 +199,7 @@ public sealed class Panel : Renderable, IHasBoxBorder, IHasBorder, IExpandable,
|
||||
}
|
||||
|
||||
private void AddTopBorder(
|
||||
List<Segment> result, RenderContext context, BoxBorder border,
|
||||
List<Segment> result, RenderOptions options, BoxBorder border,
|
||||
Style borderStyle, int panelWidth)
|
||||
{
|
||||
var rule = new Rule
|
||||
@ -180,14 +209,14 @@ public sealed class Panel : Renderable, IHasBoxBorder, IHasBorder, IExpandable,
|
||||
TitlePadding = 1,
|
||||
TitleSpacing = 0,
|
||||
Title = Header?.Text,
|
||||
Alignment = Header?.Alignment ?? Justify.Left,
|
||||
Justification = Header?.Justification ?? Justify.Left,
|
||||
};
|
||||
|
||||
// Top left border
|
||||
result.Add(new Segment(border.GetPart(BoxBorderPart.TopLeft), borderStyle));
|
||||
|
||||
// Top border (and header text if specified)
|
||||
result.AddRange(((IRenderable)rule).Render(context, panelWidth - 2).Where(x => !x.IsLineBreak));
|
||||
result.AddRange(((IRenderable)rule).Render(options, panelWidth - 2).Where(x => !x.IsLineBreak));
|
||||
|
||||
// Top right border
|
||||
result.Add(new Segment(border.GetPart(BoxBorderPart.TopRight), borderStyle));
|
||||
|
@ -3,7 +3,7 @@ namespace Spectre.Console;
|
||||
/// <summary>
|
||||
/// Represents a panel header.
|
||||
/// </summary>
|
||||
public sealed class PanelHeader : IAlignable
|
||||
public sealed class PanelHeader : IHasJustification
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the panel header text.
|
||||
@ -13,7 +13,7 @@ public sealed class PanelHeader : IAlignable
|
||||
/// <summary>
|
||||
/// Gets or sets the panel header alignment.
|
||||
/// </summary>
|
||||
public Justify? Alignment { get; set; }
|
||||
public Justify? Justification { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="PanelHeader"/> class.
|
||||
@ -23,7 +23,7 @@ public sealed class PanelHeader : IAlignable
|
||||
public PanelHeader(string text, Justify? alignment = null)
|
||||
{
|
||||
Text = text ?? throw new ArgumentNullException(nameof(text));
|
||||
Alignment = alignment;
|
||||
Justification = alignment;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -57,7 +57,7 @@ public sealed class PanelHeader : IAlignable
|
||||
/// <returns>The same instance so that multiple calls can be chained.</returns>
|
||||
public PanelHeader SetAlignment(Justify alignment)
|
||||
{
|
||||
Alignment = alignment;
|
||||
Justification = alignment;
|
||||
return this;
|
||||
}
|
||||
}
|
@ -5,14 +5,14 @@ namespace Spectre.Console;
|
||||
/// of the paragraph can have individual styling.
|
||||
/// </summary>
|
||||
[DebuggerDisplay("{_text,nq}")]
|
||||
public sealed class Paragraph : Renderable, IAlignable, IOverflowable
|
||||
public sealed class Paragraph : Renderable, IHasJustification, IOverflowable
|
||||
{
|
||||
private readonly List<SegmentLine> _lines;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the alignment of the whole paragraph.
|
||||
/// </summary>
|
||||
public Justify? Alignment { get; set; }
|
||||
public Justify? Justification { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the text overflow strategy.
|
||||
@ -115,7 +115,7 @@ public sealed class Paragraph : Renderable, IAlignable, IOverflowable
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override Measurement Measure(RenderContext context, int maxWidth)
|
||||
protected override Measurement Measure(RenderOptions options, int maxWidth)
|
||||
{
|
||||
if (_lines.Count == 0)
|
||||
{
|
||||
@ -129,11 +129,11 @@ public sealed class Paragraph : Renderable, IAlignable, IOverflowable
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override IEnumerable<Segment> Render(RenderContext context, int maxWidth)
|
||||
protected override IEnumerable<Segment> Render(RenderOptions options, int maxWidth)
|
||||
{
|
||||
if (context is null)
|
||||
if (options is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(context));
|
||||
throw new ArgumentNullException(nameof(options));
|
||||
}
|
||||
|
||||
if (_lines.Count == 0)
|
||||
@ -141,13 +141,13 @@ public sealed class Paragraph : Renderable, IAlignable, IOverflowable
|
||||
return Array.Empty<Segment>();
|
||||
}
|
||||
|
||||
var lines = context.SingleLine
|
||||
var lines = options.SingleLine
|
||||
? new List<SegmentLine>(_lines)
|
||||
: SplitLines(maxWidth);
|
||||
|
||||
// Justify lines
|
||||
var justification = context.Justification ?? Alignment ?? Justify.Left;
|
||||
if (justification != Justify.Left)
|
||||
var justification = options.Justification ?? Justification ?? Console.Justify.Left;
|
||||
if (justification != Console.Justify.Left)
|
||||
{
|
||||
foreach (var line in lines)
|
||||
{
|
||||
@ -155,7 +155,7 @@ public sealed class Paragraph : Renderable, IAlignable, IOverflowable
|
||||
}
|
||||
}
|
||||
|
||||
if (context.SingleLine)
|
||||
if (options.SingleLine)
|
||||
{
|
||||
// Return the first line
|
||||
return lines[0].Where(segment => !segment.IsLineBreak);
|
||||
|
@ -23,13 +23,13 @@ internal sealed class ProgressBar : Renderable, IHasCulture
|
||||
|
||||
internal static Style DefaultPulseStyle { get; } = new Style(foreground: Color.DodgerBlue1, background: Color.Grey23);
|
||||
|
||||
protected override Measurement Measure(RenderContext context, int maxWidth)
|
||||
protected override Measurement Measure(RenderOptions options, int maxWidth)
|
||||
{
|
||||
var width = Math.Min(Width ?? maxWidth, maxWidth);
|
||||
return new Measurement(4, width);
|
||||
}
|
||||
|
||||
protected override IEnumerable<Segment> Render(RenderContext context, int maxWidth)
|
||||
protected override IEnumerable<Segment> Render(RenderOptions options, int maxWidth)
|
||||
{
|
||||
var width = Math.Min(Width ?? maxWidth, maxWidth);
|
||||
var completedBarCount = Math.Min(MaxValue, Math.Max(0, Value));
|
||||
@ -37,7 +37,7 @@ internal sealed class ProgressBar : Renderable, IHasCulture
|
||||
|
||||
if (IsIndeterminate && !isCompleted)
|
||||
{
|
||||
foreach (var segment in RenderIndeterminate(context, width))
|
||||
foreach (var segment in RenderIndeterminate(options, width))
|
||||
{
|
||||
yield return segment;
|
||||
}
|
||||
@ -45,7 +45,7 @@ internal sealed class ProgressBar : Renderable, IHasCulture
|
||||
yield break;
|
||||
}
|
||||
|
||||
var bar = !context.Unicode ? AsciiBar : UnicodeBar;
|
||||
var bar = !options.Unicode ? AsciiBar : UnicodeBar;
|
||||
var style = isCompleted ? FinishedStyle : CompletedStyle;
|
||||
var barCount = Math.Max(0, (int)(width * (completedBarCount / MaxValue)));
|
||||
|
||||
@ -84,29 +84,29 @@ internal sealed class ProgressBar : Renderable, IHasCulture
|
||||
}
|
||||
}
|
||||
|
||||
var legacy = context.ColorSystem == ColorSystem.NoColors || context.ColorSystem == ColorSystem.Legacy;
|
||||
var legacy = options.ColorSystem == ColorSystem.NoColors || options.ColorSystem == ColorSystem.Legacy;
|
||||
var remainingToken = ShowRemaining && !legacy ? bar : ' ';
|
||||
yield return new Segment(new string(remainingToken, diff), RemainingStyle);
|
||||
}
|
||||
}
|
||||
|
||||
private IEnumerable<Segment> RenderIndeterminate(RenderContext context, int width)
|
||||
private IEnumerable<Segment> RenderIndeterminate(RenderOptions options, int width)
|
||||
{
|
||||
var bar = context.Unicode ? UnicodeBar.ToString() : AsciiBar.ToString();
|
||||
var bar = options.Unicode ? UnicodeBar.ToString() : AsciiBar.ToString();
|
||||
var style = IndeterminateStyle ?? DefaultPulseStyle;
|
||||
|
||||
IEnumerable<Segment> GetPulseSegments()
|
||||
{
|
||||
// For 1-bit and 3-bit colors, fall back to
|
||||
// a simpler versions with only two colors.
|
||||
if (context.ColorSystem == ColorSystem.NoColors ||
|
||||
context.ColorSystem == ColorSystem.Legacy)
|
||||
if (options.ColorSystem == ColorSystem.NoColors ||
|
||||
options.ColorSystem == ColorSystem.Legacy)
|
||||
{
|
||||
// First half of the pulse
|
||||
var segments = Enumerable.Repeat(new Segment(bar, new Style(style.Foreground)), PULSESIZE / 2);
|
||||
|
||||
// Second half of the pulse
|
||||
var legacy = context.ColorSystem == ColorSystem.NoColors || context.ColorSystem == ColorSystem.Legacy;
|
||||
var legacy = options.ColorSystem == ColorSystem.NoColors || options.ColorSystem == ColorSystem.Legacy;
|
||||
var bar2 = legacy ? " " : bar;
|
||||
segments = segments.Concat(Enumerable.Repeat(new Segment(bar2, new Style(style.Background)), PULSESIZE - (PULSESIZE / 2)));
|
||||
|
||||
|
@ -29,7 +29,7 @@ public sealed class Rows : Renderable, IExpandable
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override Measurement Measure(RenderContext context, int maxWidth)
|
||||
protected override Measurement Measure(RenderOptions options, int maxWidth)
|
||||
{
|
||||
if (Expand)
|
||||
{
|
||||
@ -37,7 +37,7 @@ public sealed class Rows : Renderable, IExpandable
|
||||
}
|
||||
else
|
||||
{
|
||||
var measurements = _children.Select(c => c.Measure(context, maxWidth));
|
||||
var measurements = _children.Select(c => c.Measure(options, maxWidth));
|
||||
return new Measurement(
|
||||
measurements.Min(c => c.Min),
|
||||
measurements.Min(c => c.Max));
|
||||
@ -45,13 +45,13 @@ public sealed class Rows : Renderable, IExpandable
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override IEnumerable<Segment> Render(RenderContext context, int maxWidth)
|
||||
protected override IEnumerable<Segment> Render(RenderOptions options, int maxWidth)
|
||||
{
|
||||
var result = new List<Segment>();
|
||||
|
||||
foreach (var child in _children)
|
||||
{
|
||||
var segments = child.Render(context, maxWidth);
|
||||
var segments = child.Render(options, maxWidth);
|
||||
foreach (var (_, _, last, segment) in segments.Enumerate())
|
||||
{
|
||||
result.Add(segment);
|
||||
|
@ -3,7 +3,7 @@ namespace Spectre.Console;
|
||||
/// <summary>
|
||||
/// A renderable horizontal rule.
|
||||
/// </summary>
|
||||
public sealed class Rule : Renderable, IAlignable, IHasBoxBorder
|
||||
public sealed class Rule : Renderable, IHasJustification, IHasBoxBorder
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the rule title markup text.
|
||||
@ -16,9 +16,9 @@ public sealed class Rule : Renderable, IAlignable, IHasBoxBorder
|
||||
public Style? Style { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the rule's title alignment.
|
||||
/// Gets or sets the rule's title justification.
|
||||
/// </summary>
|
||||
public Justify? Alignment { get; set; }
|
||||
public Justify? Justification { get; set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public BoxBorder Border { get; set; } = BoxBorder.Square;
|
||||
@ -43,17 +43,17 @@ public sealed class Rule : Renderable, IAlignable, IHasBoxBorder
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override IEnumerable<Segment> Render(RenderContext context, int maxWidth)
|
||||
protected override IEnumerable<Segment> Render(RenderOptions options, int maxWidth)
|
||||
{
|
||||
var extraLength = (2 * TitlePadding) + (2 * TitleSpacing);
|
||||
|
||||
if (Title == null || maxWidth <= extraLength)
|
||||
{
|
||||
return GetLineWithoutTitle(context, maxWidth);
|
||||
return GetLineWithoutTitle(options, maxWidth);
|
||||
}
|
||||
|
||||
// Get the title and make sure it fits.
|
||||
var title = GetTitleSegments(context, Title, maxWidth - extraLength);
|
||||
var title = GetTitleSegments(options, Title, maxWidth - extraLength);
|
||||
if (Segment.CellCount(title) > maxWidth - extraLength)
|
||||
{
|
||||
// Truncate the title
|
||||
@ -61,11 +61,11 @@ public sealed class Rule : Renderable, IAlignable, IHasBoxBorder
|
||||
if (!title.Any())
|
||||
{
|
||||
// We couldn't fit the title at all.
|
||||
return GetLineWithoutTitle(context, maxWidth);
|
||||
return GetLineWithoutTitle(options, maxWidth);
|
||||
}
|
||||
}
|
||||
|
||||
var (left, right) = GetLineSegments(context, maxWidth, title);
|
||||
var (left, right) = GetLineSegments(options, maxWidth, title);
|
||||
|
||||
var segments = new List<Segment>();
|
||||
segments.Add(left);
|
||||
@ -76,9 +76,9 @@ public sealed class Rule : Renderable, IAlignable, IHasBoxBorder
|
||||
return segments;
|
||||
}
|
||||
|
||||
private IEnumerable<Segment> GetLineWithoutTitle(RenderContext context, int maxWidth)
|
||||
private IEnumerable<Segment> GetLineWithoutTitle(RenderOptions options, int maxWidth)
|
||||
{
|
||||
var border = Border.GetSafeBorder(safe: !context.Unicode);
|
||||
var border = Border.GetSafeBorder(safe: !options.Unicode);
|
||||
var text = border.GetPart(BoxBorderPart.Top).Repeat(maxWidth);
|
||||
|
||||
return new[]
|
||||
@ -88,21 +88,21 @@ public sealed class Rule : Renderable, IAlignable, IHasBoxBorder
|
||||
};
|
||||
}
|
||||
|
||||
private IEnumerable<Segment> GetTitleSegments(RenderContext context, string title, int width)
|
||||
private IEnumerable<Segment> GetTitleSegments(RenderOptions options, string title, int width)
|
||||
{
|
||||
title = title.NormalizeNewLines().ReplaceExact("\n", " ").Trim();
|
||||
var markup = new Markup(title, Style);
|
||||
return ((IRenderable)markup).Render(context.WithSingleLine(), width);
|
||||
return ((IRenderable)markup).Render(options with { SingleLine = true }, width);
|
||||
}
|
||||
|
||||
private (Segment Left, Segment Right) GetLineSegments(RenderContext context, int width, IEnumerable<Segment> title)
|
||||
private (Segment Left, Segment Right) GetLineSegments(RenderOptions options, int width, IEnumerable<Segment> title)
|
||||
{
|
||||
var titleLength = Segment.CellCount(title);
|
||||
|
||||
var border = Border.GetSafeBorder(safe: !context.Unicode);
|
||||
var border = Border.GetSafeBorder(safe: !options.Unicode);
|
||||
var borderPart = border.GetPart(BoxBorderPart.Top);
|
||||
|
||||
var alignment = Alignment ?? Justify.Center;
|
||||
var alignment = Justification ?? Justify.Center;
|
||||
if (alignment == Justify.Left)
|
||||
{
|
||||
var left = new Segment(borderPart.Repeat(TitlePadding) + new string(' ', TitleSpacing), Style ?? Style.Plain);
|
||||
|
@ -59,6 +59,7 @@ public sealed class Table : Renderable, IHasTableBorder, IExpandable, IAlignable
|
||||
public TableTitle? Caption { get; set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
[Obsolete("Use the Align widget instead. This property will be removed in a later release.")]
|
||||
public Justify? Alignment { get; set; }
|
||||
|
||||
// Whether this is a grid or not.
|
||||
@ -100,14 +101,14 @@ public sealed class Table : Renderable, IHasTableBorder, IExpandable, IAlignable
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override Measurement Measure(RenderContext context, int maxWidth)
|
||||
protected override Measurement Measure(RenderOptions options, int maxWidth)
|
||||
{
|
||||
if (context is null)
|
||||
if (options is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(context));
|
||||
throw new ArgumentNullException(nameof(options));
|
||||
}
|
||||
|
||||
var measurer = new TableMeasurer(this, context);
|
||||
var measurer = new TableMeasurer(this, options);
|
||||
|
||||
// Calculate the total cell width
|
||||
var totalCellWidth = measurer.CalculateTotalCellWidth(maxWidth);
|
||||
@ -120,14 +121,14 @@ public sealed class Table : Renderable, IHasTableBorder, IExpandable, IAlignable
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override IEnumerable<Segment> Render(RenderContext context, int maxWidth)
|
||||
protected override IEnumerable<Segment> Render(RenderOptions options, int maxWidth)
|
||||
{
|
||||
if (context is null)
|
||||
if (options is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(context));
|
||||
throw new ArgumentNullException(nameof(options));
|
||||
}
|
||||
|
||||
var measurer = new TableMeasurer(this, context);
|
||||
var measurer = new TableMeasurer(this, options);
|
||||
|
||||
// Calculate the column and table width
|
||||
var totalCellWidth = measurer.CalculateTotalCellWidth(maxWidth);
|
||||
@ -139,7 +140,7 @@ public sealed class Table : Renderable, IHasTableBorder, IExpandable, IAlignable
|
||||
|
||||
// Render the table
|
||||
return TableRenderer.Render(
|
||||
new TableRendererContext(this, context, rows, tableWidth, maxWidth),
|
||||
new TableRendererContext(this, options, rows, tableWidth, maxWidth),
|
||||
columnWidths);
|
||||
}
|
||||
|
||||
|
@ -4,12 +4,12 @@ internal abstract class TableAccessor
|
||||
{
|
||||
private readonly Table _table;
|
||||
|
||||
public RenderContext Options { get; }
|
||||
public RenderOptions Options { get; }
|
||||
public IReadOnlyList<TableColumn> Columns => _table.Columns;
|
||||
public virtual IReadOnlyList<TableRow> Rows => _table.Rows;
|
||||
public bool Expand => _table.Expand || _table.Width != null;
|
||||
|
||||
protected TableAccessor(Table table, RenderContext options)
|
||||
protected TableAccessor(Table table, RenderOptions options)
|
||||
{
|
||||
_table = table ?? throw new ArgumentNullException(nameof(table));
|
||||
Options = options ?? throw new ArgumentNullException(nameof(options));
|
||||
|
@ -8,7 +8,7 @@ internal sealed class TableMeasurer : TableAccessor
|
||||
private readonly TableBorder _border;
|
||||
private readonly bool _padRightCell;
|
||||
|
||||
public TableMeasurer(Table table, RenderContext options)
|
||||
public TableMeasurer(Table table, RenderOptions options)
|
||||
: base(table, options)
|
||||
{
|
||||
_explicitWidth = table.Width;
|
||||
|
@ -26,7 +26,7 @@ internal static class TableRenderer
|
||||
foreach (var (columnIndex, _, _, (rowWidth, cell)) in columnWidths.Zip(row).Enumerate())
|
||||
{
|
||||
var justification = context.Columns[columnIndex].Alignment;
|
||||
var childContext = context.Options.WithJustification(justification);
|
||||
var childContext = context.Options with { Justification = justification };
|
||||
|
||||
var lines = Segment.SplitLines(cell.Render(childContext, rowWidth));
|
||||
cellHeight = Math.Max(cellHeight, lines.Count);
|
||||
@ -159,7 +159,7 @@ internal static class TableRenderer
|
||||
}
|
||||
|
||||
var paragraph = new Markup(header.Text, header.Style ?? defaultStyle)
|
||||
.Alignment(Justify.Center)
|
||||
.Justify(Justify.Center)
|
||||
.Overflow(Overflow.Ellipsis);
|
||||
|
||||
// Render the paragraphs
|
||||
|
@ -31,9 +31,12 @@ internal sealed class TableRendererContext : TableAccessor
|
||||
public bool PadRightCell => _table.PadRightCell;
|
||||
public TableTitle? Title => _table.Title;
|
||||
public TableTitle? Caption => _table.Caption;
|
||||
public Justify? Alignment => _table.Alignment;
|
||||
|
||||
public TableRendererContext(Table table, RenderContext options, IEnumerable<TableRow> rows, int tableWidth, int maxWidth)
|
||||
#pragma warning disable CS0618 // Type or member is obsolete
|
||||
public Justify? Alignment => _table.Alignment;
|
||||
#pragma warning restore CS0618 // Type or member is obsolete
|
||||
|
||||
public TableRendererContext(Table table, RenderOptions options, IEnumerable<TableRow> rows, int tableWidth, int maxWidth)
|
||||
: base(table, options)
|
||||
{
|
||||
_table = table ?? throw new ArgumentNullException(nameof(table));
|
||||
|
@ -5,7 +5,7 @@ namespace Spectre.Console;
|
||||
/// </summary>
|
||||
[DebuggerDisplay("{_text,nq}")]
|
||||
[SuppressMessage("Naming", "CA1724:Type names should not match namespaces")]
|
||||
public sealed class Text : Renderable, IAlignable, IOverflowable
|
||||
public sealed class Text : Renderable, IHasJustification, IOverflowable
|
||||
{
|
||||
private readonly Paragraph _paragraph;
|
||||
|
||||
@ -32,10 +32,10 @@ public sealed class Text : Renderable, IAlignable, IOverflowable
|
||||
/// <summary>
|
||||
/// Gets or sets the text alignment.
|
||||
/// </summary>
|
||||
public Justify? Alignment
|
||||
public Justify? Justification
|
||||
{
|
||||
get => _paragraph.Alignment;
|
||||
set => _paragraph.Alignment = value;
|
||||
get => _paragraph.Justification;
|
||||
set => _paragraph.Justification = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -58,14 +58,14 @@ public sealed class Text : Renderable, IAlignable, IOverflowable
|
||||
public int Lines => _paragraph.Lines;
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override Measurement Measure(RenderContext context, int maxWidth)
|
||||
protected override Measurement Measure(RenderOptions options, int maxWidth)
|
||||
{
|
||||
return ((IRenderable)_paragraph).Measure(context, maxWidth);
|
||||
return ((IRenderable)_paragraph).Measure(options, maxWidth);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override IEnumerable<Segment> Render(RenderContext context, int maxWidth)
|
||||
protected override IEnumerable<Segment> Render(RenderOptions options, int maxWidth)
|
||||
{
|
||||
return ((IRenderable)_paragraph).Render(context, maxWidth);
|
||||
return ((IRenderable)_paragraph).Render(options, maxWidth);
|
||||
}
|
||||
}
|
@ -3,7 +3,7 @@ namespace Spectre.Console;
|
||||
/// <summary>
|
||||
/// Representation of a file system path.
|
||||
/// </summary>
|
||||
public sealed class TextPath : IRenderable, IAlignable
|
||||
public sealed class TextPath : IRenderable, IHasJustification
|
||||
{
|
||||
private const string Ellipsis = "...";
|
||||
private const string UnicodeEllipsis = "…";
|
||||
@ -35,7 +35,7 @@ public sealed class TextPath : IRenderable, IAlignable
|
||||
/// <summary>
|
||||
/// Gets or sets the alignment.
|
||||
/// </summary>
|
||||
public Justify? Alignment { get; set; }
|
||||
public Justify? Justification { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="TextPath"/> class.
|
||||
@ -66,9 +66,9 @@ public sealed class TextPath : IRenderable, IAlignable
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public Measurement Measure(RenderContext context, int maxWidth)
|
||||
public Measurement Measure(RenderOptions options, int maxWidth)
|
||||
{
|
||||
var fitted = Fit(context, maxWidth);
|
||||
var fitted = Fit(options, maxWidth);
|
||||
var separatorCount = fitted.Length - 1;
|
||||
var length = fitted.Sum(f => f.Length) + separatorCount;
|
||||
|
||||
@ -78,16 +78,14 @@ public sealed class TextPath : IRenderable, IAlignable
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IEnumerable<Segment> Render(RenderContext context, int maxWidth)
|
||||
public IEnumerable<Segment> Render(RenderOptions options, int maxWidth)
|
||||
{
|
||||
var alignment = Alignment ?? Justify.Left;
|
||||
|
||||
var rootStyle = RootStyle ?? Style.Plain;
|
||||
var separatorStyle = SeparatorStyle ?? Style.Plain;
|
||||
var stemStyle = StemStyle ?? Style.Plain;
|
||||
var leafStyle = LeafStyle ?? Style.Plain;
|
||||
|
||||
var fitted = Fit(context, maxWidth);
|
||||
var fitted = Fit(options, maxWidth);
|
||||
var parts = new List<Segment>();
|
||||
foreach (var (_, first, last, item) in fitted.Enumerate())
|
||||
{
|
||||
@ -119,7 +117,7 @@ public sealed class TextPath : IRenderable, IAlignable
|
||||
}
|
||||
|
||||
// Align the result
|
||||
Aligner.Align(parts, Alignment, maxWidth);
|
||||
Aligner.Align(parts, Justification, maxWidth);
|
||||
|
||||
// Insert a line break
|
||||
parts.Add(Segment.LineBreak);
|
||||
@ -127,7 +125,7 @@ public sealed class TextPath : IRenderable, IAlignable
|
||||
return parts;
|
||||
}
|
||||
|
||||
private string[] Fit(RenderContext context, int maxWidth)
|
||||
private string[] Fit(RenderOptions options, int maxWidth)
|
||||
{
|
||||
// No parts?
|
||||
if (_parts.Length == 0)
|
||||
@ -141,7 +139,7 @@ public sealed class TextPath : IRenderable, IAlignable
|
||||
return _parts;
|
||||
}
|
||||
|
||||
var ellipsis = context.Unicode ? UnicodeEllipsis : Ellipsis;
|
||||
var ellipsis = options.Unicode ? UnicodeEllipsis : Ellipsis;
|
||||
var ellipsisLength = Cell.GetCellLength(ellipsis);
|
||||
|
||||
if (_parts.Length >= 2)
|
||||
|
@ -47,7 +47,7 @@ public sealed class Tree : Renderable, IHasTreeNodes
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override IEnumerable<Segment> Render(RenderContext context, int maxWidth)
|
||||
protected override IEnumerable<Segment> Render(RenderOptions options, int maxWidth)
|
||||
{
|
||||
var result = new List<Segment>();
|
||||
var visitedNodes = new HashSet<TreeNode>();
|
||||
@ -56,7 +56,7 @@ public sealed class Tree : Renderable, IHasTreeNodes
|
||||
stack.Push(new Queue<TreeNode>(new[] { _root }));
|
||||
|
||||
var levels = new List<Segment>();
|
||||
levels.Add(GetGuide(context, TreeGuidePart.Continue));
|
||||
levels.Add(GetGuide(options, TreeGuidePart.Continue));
|
||||
|
||||
while (stack.Count > 0)
|
||||
{
|
||||
@ -66,7 +66,7 @@ public sealed class Tree : Renderable, IHasTreeNodes
|
||||
levels.RemoveLast();
|
||||
if (levels.Count > 0)
|
||||
{
|
||||
levels.AddOrReplaceLast(GetGuide(context, TreeGuidePart.Fork));
|
||||
levels.AddOrReplaceLast(GetGuide(options, TreeGuidePart.Fork));
|
||||
}
|
||||
|
||||
continue;
|
||||
@ -83,11 +83,11 @@ public sealed class Tree : Renderable, IHasTreeNodes
|
||||
|
||||
if (isLastChild)
|
||||
{
|
||||
levels.AddOrReplaceLast(GetGuide(context, TreeGuidePart.End));
|
||||
levels.AddOrReplaceLast(GetGuide(options, TreeGuidePart.End));
|
||||
}
|
||||
|
||||
var prefix = levels.Skip(1).ToList();
|
||||
var renderableLines = Segment.SplitLines(current.Renderable.Render(context, maxWidth - Segment.CellCount(prefix)));
|
||||
var renderableLines = Segment.SplitLines(current.Renderable.Render(options, maxWidth - Segment.CellCount(prefix)));
|
||||
|
||||
foreach (var (_, isFirstLine, _, line) in renderableLines.Enumerate())
|
||||
{
|
||||
@ -102,14 +102,14 @@ public sealed class Tree : Renderable, IHasTreeNodes
|
||||
if (isFirstLine && prefix.Count > 0)
|
||||
{
|
||||
var part = isLastChild ? TreeGuidePart.Space : TreeGuidePart.Continue;
|
||||
prefix.AddOrReplaceLast(GetGuide(context, part));
|
||||
prefix.AddOrReplaceLast(GetGuide(options, 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));
|
||||
levels.AddOrReplaceLast(GetGuide(options, isLastChild ? TreeGuidePart.Space : TreeGuidePart.Continue));
|
||||
levels.Add(GetGuide(options, current.Nodes.Count == 1 ? TreeGuidePart.End : TreeGuidePart.Fork));
|
||||
|
||||
stack.Push(new Queue<TreeNode>(current.Nodes));
|
||||
}
|
||||
@ -118,9 +118,9 @@ public sealed class Tree : Renderable, IHasTreeNodes
|
||||
return result;
|
||||
}
|
||||
|
||||
private Segment GetGuide(RenderContext context, TreeGuidePart part)
|
||||
private Segment GetGuide(RenderOptions options, TreeGuidePart part)
|
||||
{
|
||||
var guide = Guide.GetSafeTreeGuide(safe: !context.Unicode);
|
||||
var guide = Guide.GetSafeTreeGuide(safe: !options.Unicode);
|
||||
return new Segment(guide.GetPart(part), Style ?? Style.Plain);
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user