mirror of
https://github.com/nsnail/spectre.console.git
synced 2025-06-19 13:28:16 +08:00
Add column support
Adds support for rendering arbitrary data into columns. Closes #67
This commit is contained in:

committed by
Patrik Svensson

parent
e946289bd9
commit
ae6d2c63a3
@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using Spectre.Console.Internal;
|
||||
using Spectre.Console.Rendering;
|
||||
|
||||
@ -30,8 +31,8 @@ namespace Spectre.Console
|
||||
|
||||
using (console.PushStyle(Style.Plain))
|
||||
{
|
||||
var segments = renderable.Render(options, console.Width);
|
||||
segments = Segment.Merge(segments);
|
||||
var segments = renderable.Render(options, console.Width).Where(x => !(x.Text.Length == 0 && !x.IsLineBreak)).ToArray();
|
||||
segments = Segment.Merge(segments).ToArray();
|
||||
|
||||
var current = Style.Plain;
|
||||
foreach (var segment in segments)
|
||||
|
141
src/Spectre.Console/Rendering/Columns.cs
Normal file
141
src/Spectre.Console/Rendering/Columns.cs
Normal file
@ -0,0 +1,141 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Spectre.Console.Rendering;
|
||||
|
||||
namespace Spectre.Console
|
||||
{
|
||||
/// <summary>
|
||||
/// Renders things in columns.
|
||||
/// </summary>
|
||||
public sealed class Columns : Renderable, IPaddable, IExpandable
|
||||
{
|
||||
private readonly List<IRenderable> _items;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public Padding Padding { get; set; } = new Padding(0, 1);
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether or not the columns should
|
||||
/// expand to the available space. If <c>false</c>, the column
|
||||
/// width will be auto calculated. Defaults to <c>true</c>.
|
||||
/// </summary>
|
||||
public bool Expand { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="Columns"/> class.
|
||||
/// </summary>
|
||||
/// <param name="items">The items to render.</param>
|
||||
public Columns(IEnumerable<IRenderable> items)
|
||||
{
|
||||
if (items is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(items));
|
||||
}
|
||||
|
||||
_items = new List<IRenderable>(items);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="Columns"/> class.
|
||||
/// </summary>
|
||||
/// <param name="items">The items to render.</param>
|
||||
public Columns(IEnumerable<string> items)
|
||||
{
|
||||
if (items is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(items));
|
||||
}
|
||||
|
||||
_items = new List<IRenderable>(items.Select(item => new Markup(item)));
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override IEnumerable<Segment> Render(RenderContext context, int maxWidth)
|
||||
{
|
||||
var maxPadding = Math.Max(Padding.Left, Padding.Right);
|
||||
|
||||
var itemWidths = _items.Select(item => item.Measure(context, maxWidth).Max).ToArray();
|
||||
var columnCount = CalculateColumnCount(maxWidth, itemWidths, _items.Count, maxPadding);
|
||||
|
||||
var table = new Table();
|
||||
table.NoBorder();
|
||||
table.HideHeaders();
|
||||
table.PadRightCell = false;
|
||||
|
||||
if (Expand)
|
||||
{
|
||||
table.Expand();
|
||||
}
|
||||
|
||||
// Add columns
|
||||
for (var index = 0; index < columnCount; index++)
|
||||
{
|
||||
table.AddColumn(new TableColumn(string.Empty)
|
||||
{
|
||||
Padding = Padding,
|
||||
NoWrap = true,
|
||||
});
|
||||
}
|
||||
|
||||
// Add rows
|
||||
for (var start = 0; start < _items.Count; start += columnCount)
|
||||
{
|
||||
table.AddRow(_items.Skip(start).Take(columnCount).ToArray());
|
||||
}
|
||||
|
||||
return ((IRenderable)table).Render(context, maxWidth);
|
||||
}
|
||||
|
||||
// Algorithm borrowed from https://github.com/willmcgugan/rich/blob/master/rich/columns.py
|
||||
private int CalculateColumnCount(int maxWidth, int[] itemWidths, int columnCount, int padding)
|
||||
{
|
||||
var widths = new Dictionary<int, int>();
|
||||
while (columnCount > 1)
|
||||
{
|
||||
var columnIndex = 0;
|
||||
widths.Clear();
|
||||
|
||||
var exceededTotalWidth = false;
|
||||
foreach (var renderableWidth in IterateWidths(itemWidths, columnCount))
|
||||
{
|
||||
widths[columnIndex] = Math.Max(widths.ContainsKey(columnIndex) ? widths[columnIndex] : 0, renderableWidth);
|
||||
var totalWidth = widths.Values.Sum() + (padding * (widths.Count - 1));
|
||||
if (totalWidth > maxWidth)
|
||||
{
|
||||
columnCount = widths.Count - 1;
|
||||
exceededTotalWidth = true;
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
columnIndex = (columnIndex + 1) % columnCount;
|
||||
}
|
||||
}
|
||||
|
||||
if (!exceededTotalWidth)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return columnCount;
|
||||
}
|
||||
|
||||
private IEnumerable<int> IterateWidths(int[] itemWidths, int columnCount)
|
||||
{
|
||||
foreach (var width in itemWidths)
|
||||
{
|
||||
yield return width;
|
||||
}
|
||||
|
||||
if (_items.Count % columnCount != 0)
|
||||
{
|
||||
for (var i = 0; i < columnCount - (_items.Count % columnCount) - 1; i++)
|
||||
{
|
||||
yield return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -7,7 +7,7 @@ namespace Spectre.Console
|
||||
/// <summary>
|
||||
/// A renderable grid.
|
||||
/// </summary>
|
||||
public sealed class Grid : Renderable
|
||||
public sealed class Grid : Renderable, IExpandable
|
||||
{
|
||||
private readonly Table _table;
|
||||
|
||||
@ -21,6 +21,13 @@ namespace Spectre.Console
|
||||
/// </summary>
|
||||
public int RowCount => _table.RowCount;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool Expand
|
||||
{
|
||||
get => _table.Expand;
|
||||
set => _table.Expand = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="Grid"/> class.
|
||||
/// </summary>
|
||||
@ -94,11 +101,6 @@ namespace Spectre.Console
|
||||
throw new ArgumentNullException(nameof(columns));
|
||||
}
|
||||
|
||||
if (columns.Length < _table.ColumnCount)
|
||||
{
|
||||
throw new InvalidOperationException("The number of row columns are less than the number of grid columns.");
|
||||
}
|
||||
|
||||
if (columns.Length > _table.ColumnCount)
|
||||
{
|
||||
throw new InvalidOperationException("The number of row columns are greater than the number of grid columns.");
|
||||
|
@ -64,8 +64,8 @@ namespace Spectre.Console
|
||||
{
|
||||
var childWidth = _child.Measure(context, maxWidth);
|
||||
return new Measurement(
|
||||
childWidth.Min + 2 + Padding.GetHorizontalPadding(),
|
||||
childWidth.Max + 2 + Padding.GetHorizontalPadding());
|
||||
childWidth.Min + EdgeWidth + Padding.GetHorizontalPadding(),
|
||||
childWidth.Max + EdgeWidth + Padding.GetHorizontalPadding());
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
|
@ -13,10 +13,25 @@ namespace Spectre.Console.Rendering
|
||||
[DebuggerDisplay("{Text,nq}")]
|
||||
public class Segment
|
||||
{
|
||||
private readonly bool _mutable;
|
||||
private string _text;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the segment text.
|
||||
/// </summary>
|
||||
public string Text { get; internal set; }
|
||||
public string Text
|
||||
{
|
||||
get => _text;
|
||||
private set
|
||||
{
|
||||
if (!_mutable)
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
_text = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether or not this is an expicit line break
|
||||
@ -39,12 +54,12 @@ namespace Spectre.Console.Rendering
|
||||
/// <summary>
|
||||
/// Gets a segment representing a line break.
|
||||
/// </summary>
|
||||
public static Segment LineBreak { get; } = new Segment(Environment.NewLine, Style.Plain, true);
|
||||
public static Segment LineBreak { get; } = new Segment(Environment.NewLine, Style.Plain, true, false);
|
||||
|
||||
/// <summary>
|
||||
/// Gets an empty segment.
|
||||
/// </summary>
|
||||
public static Segment Empty { get; } = new Segment(string.Empty, Style.Plain);
|
||||
public static Segment Empty { get; } = new Segment(string.Empty, Style.Plain, false, false);
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="Segment"/> class.
|
||||
@ -65,14 +80,16 @@ namespace Spectre.Console.Rendering
|
||||
{
|
||||
}
|
||||
|
||||
private Segment(string text, Style style, bool lineBreak)
|
||||
private Segment(string text, Style style, bool lineBreak, bool mutable = true)
|
||||
{
|
||||
if (text is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(text));
|
||||
}
|
||||
|
||||
Text = text.NormalizeLineEndings();
|
||||
_mutable = mutable;
|
||||
_text = text.NormalizeLineEndings();
|
||||
|
||||
Style = style;
|
||||
IsLineBreak = lineBreak;
|
||||
IsWhiteSpace = string.IsNullOrWhiteSpace(text);
|
||||
|
@ -18,7 +18,7 @@ namespace Spectre.Console
|
||||
// https://github.com/willmcgugan/rich/blob/527475837ebbfc427530b3ee0d4d0741d2d0fc6d/rich/table.py#L394
|
||||
private List<int> CalculateColumnWidths(RenderContext options, int maxWidth)
|
||||
{
|
||||
var width_ranges = _columns.Select(column => MeasureColumn(column, options, maxWidth));
|
||||
var width_ranges = _columns.Select(column => MeasureColumn(column, options, maxWidth)).ToArray();
|
||||
var widths = width_ranges.Select(range => range.Max).ToList();
|
||||
|
||||
var tableWidth = widths.Sum();
|
||||
@ -117,9 +117,17 @@ namespace Spectre.Console
|
||||
|
||||
private int GetExtraWidth(bool includePadding)
|
||||
{
|
||||
var separators = _columns.Count - 1;
|
||||
var hideBorder = BorderKind == BorderKind.None;
|
||||
var separators = hideBorder ? 0 : _columns.Count - 1;
|
||||
var edges = hideBorder ? 0 : EdgeCount;
|
||||
var padding = includePadding ? _columns.Select(x => x.Padding.GetHorizontalPadding()).Sum() : 0;
|
||||
return separators + EdgeCount + padding;
|
||||
|
||||
if (!PadRightCell)
|
||||
{
|
||||
padding -= _columns.Last().Padding.Right;
|
||||
}
|
||||
|
||||
return separators + edges + padding;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -125,17 +125,19 @@ namespace Spectre.Console
|
||||
throw new ArgumentNullException(nameof(columns));
|
||||
}
|
||||
|
||||
if (columns.Length < _columns.Count)
|
||||
{
|
||||
throw new InvalidOperationException("The number of row columns are less than the number of table columns.");
|
||||
}
|
||||
|
||||
if (columns.Length > _columns.Count)
|
||||
{
|
||||
throw new InvalidOperationException("The number of row columns are greater than the number of table columns.");
|
||||
}
|
||||
|
||||
_rows.Add(columns.ToList());
|
||||
|
||||
// Need to add missing columns?
|
||||
if (columns.Length < _columns.Count)
|
||||
{
|
||||
var diff = _columns.Count - columns.Length;
|
||||
Enumerable.Range(0, diff).ForEach(_ => _rows.Last().Add(Text.Empty));
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
|
@ -1,6 +1,6 @@
|
||||
using System;
|
||||
|
||||
namespace Spectre.Console.Rendering
|
||||
namespace Spectre.Console
|
||||
{
|
||||
/// <summary>
|
||||
/// Contains extension methods for <see cref="IHasBorder"/>.
|
||||
|
@ -1,6 +1,6 @@
|
||||
using System;
|
||||
|
||||
namespace Spectre.Console.Rendering
|
||||
namespace Spectre.Console
|
||||
{
|
||||
/// <summary>
|
||||
/// Contains extension methods for <see cref="IExpandable"/>.
|
||||
|
Reference in New Issue
Block a user