mirror of
https://github.com/nsnail/spectre.console.git
synced 2025-06-19 21:38:16 +08:00

committed by
Patrik Svensson

parent
0b4321115a
commit
9637066927
@ -17,23 +17,27 @@ namespace Spectre.Console
|
||||
/// </summary>
|
||||
public Grid()
|
||||
{
|
||||
_table = new Table(BorderKind.None, showHeaders: false);
|
||||
_table = new Table
|
||||
{
|
||||
Border = BorderKind.None,
|
||||
ShowHeaders = false,
|
||||
};
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public int Measure(Encoding encoding, int maxWidth)
|
||||
public Measurement Measure(Encoding encoding, int maxWidth)
|
||||
{
|
||||
return _table.Measure(encoding, maxWidth);
|
||||
return ((IRenderable)_table).Measure(encoding, maxWidth);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IEnumerable<Segment> Render(Encoding encoding, int width)
|
||||
{
|
||||
return _table.Render(encoding, width);
|
||||
return ((IRenderable)_table).Render(encoding, width);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a single column to the grid.
|
||||
/// Adds a column to the grid.
|
||||
/// </summary>
|
||||
public void AddColumn()
|
||||
{
|
||||
@ -41,15 +45,23 @@ namespace Spectre.Console
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds the specified number of columns to the grid.
|
||||
/// Adds a column to the grid.
|
||||
/// </summary>
|
||||
/// <param name="count">The number of columns.</param>
|
||||
public void AddColumns(int count)
|
||||
/// <param name="column">The column to add.</param>
|
||||
public void AddColumn(GridColumn column)
|
||||
{
|
||||
for (var i = 0; i < count; i++)
|
||||
if (column is null)
|
||||
{
|
||||
_table.AddColumn(string.Empty);
|
||||
throw new ArgumentNullException(nameof(column));
|
||||
}
|
||||
|
||||
_table.AddColumn(new TableColumn(string.Empty)
|
||||
{
|
||||
Width = column.Width,
|
||||
NoWrap = column.NoWrap,
|
||||
LeftPadding = 0,
|
||||
RightPadding = 1,
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
20
src/Spectre.Console/Composition/GridColumn.cs
Normal file
20
src/Spectre.Console/Composition/GridColumn.cs
Normal file
@ -0,0 +1,20 @@
|
||||
namespace Spectre.Console
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a grid column.
|
||||
/// </summary>
|
||||
public sealed class GridColumn
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the width of the column.
|
||||
/// If <c>null</c>, the column will adapt to it's contents.
|
||||
/// </summary>
|
||||
public int? Width { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether wrapping of
|
||||
/// text within the column should be prevented.
|
||||
/// </summary>
|
||||
public bool NoWrap { get; set; }
|
||||
}
|
||||
}
|
@ -13,8 +13,8 @@ namespace Spectre.Console.Composition
|
||||
/// </summary>
|
||||
/// <param name="encoding">The encoding to use.</param>
|
||||
/// <param name="maxWidth">The maximum allowed width.</param>
|
||||
/// <returns>The width of the object.</returns>
|
||||
int Measure(Encoding encoding, int maxWidth);
|
||||
/// <returns>The minimum and maximum width of the object.</returns>
|
||||
Measurement Measure(Encoding encoding, int maxWidth);
|
||||
|
||||
/// <summary>
|
||||
/// Renders the object.
|
||||
|
77
src/Spectre.Console/Composition/Measurement.cs
Normal file
77
src/Spectre.Console/Composition/Measurement.cs
Normal file
@ -0,0 +1,77 @@
|
||||
using System;
|
||||
|
||||
namespace Spectre.Console.Composition
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a measurement.
|
||||
/// </summary>
|
||||
public struct Measurement : IEquatable<Measurement>
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the minimum width.
|
||||
/// </summary>
|
||||
public int Min { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the maximum width.
|
||||
/// </summary>
|
||||
public int Max { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="Measurement"/> struct.
|
||||
/// </summary>
|
||||
/// <param name="min">The minimum width.</param>
|
||||
/// <param name="max">The maximum width.</param>
|
||||
public Measurement(int min, int max)
|
||||
{
|
||||
Min = min;
|
||||
Max = max;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
return obj is Measurement measurement && Equals(measurement);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override int GetHashCode()
|
||||
{
|
||||
unchecked
|
||||
{
|
||||
var hash = (int)2166136261;
|
||||
hash = (hash * 16777619) ^ Min.GetHashCode();
|
||||
hash = (hash * 16777619) ^ Max.GetHashCode();
|
||||
return hash;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool Equals(Measurement other)
|
||||
{
|
||||
return Min == other.Min && Max == other.Max;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if two <see cref="Measurement"/> instances are equal.
|
||||
/// </summary>
|
||||
/// <param name="left">The first measurement instance to compare.</param>
|
||||
/// <param name="right">The second measurement instance to compare.</param>
|
||||
/// <returns><c>true</c> if the two measurements are equal, otherwise <c>false</c>.</returns>
|
||||
public static bool operator ==(Measurement left, Measurement right)
|
||||
{
|
||||
return left.Equals(right);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if two <see cref="Measurement"/> instances are not equal.
|
||||
/// </summary>
|
||||
/// <param name="left">The first measurement instance to compare.</param>
|
||||
/// <param name="right">The second measurement instance to compare.</param>
|
||||
/// <returns><c>true</c> if the two measurements are not equal, otherwise <c>false</c>.</returns>
|
||||
public static bool operator !=(Measurement left, Measurement right)
|
||||
{
|
||||
return !(left == right);
|
||||
}
|
||||
}
|
||||
}
|
@ -35,19 +35,20 @@ namespace Spectre.Console
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public int Measure(Encoding encoding, int maxWidth)
|
||||
Measurement IRenderable.Measure(Encoding encoding, int maxWidth)
|
||||
{
|
||||
var childWidth = _child.Measure(encoding, maxWidth);
|
||||
return childWidth + 4;
|
||||
return new Measurement(childWidth.Min + 4, childWidth.Max + 4);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IEnumerable<Segment> Render(Encoding encoding, int width)
|
||||
IEnumerable<Segment> IRenderable.Render(Encoding encoding, int width)
|
||||
{
|
||||
var childWidth = width - 4;
|
||||
if (!_fit)
|
||||
{
|
||||
childWidth = _child.Measure(encoding, width - 2);
|
||||
var measurement = _child.Measure(encoding, width - 2);
|
||||
childWidth = measurement.Max;
|
||||
}
|
||||
|
||||
var result = new List<Segment>();
|
||||
|
@ -211,5 +211,21 @@ namespace Spectre.Console.Composition
|
||||
|
||||
return lines;
|
||||
}
|
||||
|
||||
internal static List<List<SegmentLine>> MakeSameHeight(int cellHeight, List<List<SegmentLine>> cells)
|
||||
{
|
||||
foreach (var cell in cells)
|
||||
{
|
||||
if (cell.Count < cellHeight)
|
||||
{
|
||||
while (cell.Count != cellHeight)
|
||||
{
|
||||
cell.Add(new SegmentLine());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return cells;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
168
src/Spectre.Console/Composition/Table.Calculations.cs
Normal file
168
src/Spectre.Console/Composition/Table.Calculations.cs
Normal file
@ -0,0 +1,168 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using Spectre.Console.Composition;
|
||||
using Spectre.Console.Internal;
|
||||
|
||||
namespace Spectre.Console
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a table.
|
||||
/// </summary>
|
||||
public sealed partial class Table
|
||||
{
|
||||
// Calculate the widths of each column, including padding, not including borders.
|
||||
// Ported from Rich by Will McGugan, licensed under MIT.
|
||||
// https://github.com/willmcgugan/rich/blob/527475837ebbfc427530b3ee0d4d0741d2d0fc6d/rich/table.py#L394
|
||||
private List<int> CalculateColumnWidths(Encoding encoding, int maxWidth)
|
||||
{
|
||||
var width_ranges = _columns.Select(column => MeasureColumn(column, encoding, maxWidth));
|
||||
var widths = width_ranges.Select(range => range.Max).ToList();
|
||||
|
||||
var tableWidth = widths.Sum();
|
||||
|
||||
if (ShouldExpand())
|
||||
{
|
||||
var ratios = _columns.Select(c => c.Ratio ?? 0).ToList();
|
||||
if (ratios.Any(r => r != 0))
|
||||
{
|
||||
var fixedWidths = new List<int>();
|
||||
foreach (var (range, column) in width_ranges.Zip(_columns, (a, b) => (a, b)))
|
||||
{
|
||||
fixedWidths.Add(column.IsFlexible() ? 0 : range.Max);
|
||||
}
|
||||
|
||||
var flexMinimum = new List<int>();
|
||||
foreach (var column in _columns)
|
||||
{
|
||||
if (column.IsFlexible())
|
||||
{
|
||||
flexMinimum.Add(column.Width ?? 1 + column.GetPadding());
|
||||
}
|
||||
else
|
||||
{
|
||||
flexMinimum.Add(0);
|
||||
}
|
||||
}
|
||||
|
||||
var flexibleWidth = maxWidth - fixedWidths.Sum();
|
||||
var flexWidths = Ratio.Distribute(flexibleWidth, ratios, flexMinimum);
|
||||
|
||||
var flexWidthsIterator = flexWidths.GetEnumerator();
|
||||
foreach (var (index, _, _, column) in _columns.Enumerate())
|
||||
{
|
||||
if (column.IsFlexible())
|
||||
{
|
||||
flexWidthsIterator.MoveNext();
|
||||
widths[index] = fixedWidths[index] + flexWidthsIterator.Current;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tableWidth = widths.Sum();
|
||||
|
||||
if (tableWidth > maxWidth)
|
||||
{
|
||||
var wrappable = _columns.Select(c => !c.NoWrap).ToList();
|
||||
widths = CollapseWidths(widths, wrappable, maxWidth);
|
||||
tableWidth = widths.Sum();
|
||||
|
||||
// last resort, reduce columns evenly
|
||||
if (tableWidth > maxWidth)
|
||||
{
|
||||
var excessWidth = tableWidth - maxWidth;
|
||||
widths = Ratio.Reduce(excessWidth, widths.Select(w => 1).ToList(), widths, widths);
|
||||
tableWidth = widths.Sum();
|
||||
}
|
||||
}
|
||||
|
||||
if (tableWidth < maxWidth && ShouldExpand())
|
||||
{
|
||||
var padWidths = Ratio.Distribute(maxWidth - tableWidth, widths);
|
||||
widths = widths.Zip(padWidths, (a, b) => (a, b)).Select(f => f.a + f.b).ToList();
|
||||
}
|
||||
|
||||
return widths;
|
||||
}
|
||||
|
||||
// Reduce widths so that the total is less or equal to the max width.
|
||||
// Ported from Rich by Will McGugan, licensed under MIT.
|
||||
// https://github.com/willmcgugan/rich/blob/527475837ebbfc427530b3ee0d4d0741d2d0fc6d/rich/table.py#L442
|
||||
private static List<int> CollapseWidths(List<int> widths, List<bool> wrappable, int maxWidth)
|
||||
{
|
||||
var totalWidth = widths.Sum();
|
||||
var excessWidth = totalWidth - maxWidth;
|
||||
|
||||
if (wrappable.AnyTrue())
|
||||
{
|
||||
while (totalWidth > 0 && excessWidth > 0)
|
||||
{
|
||||
var maxColumn = widths.Zip(wrappable, (first, second) => (width: first, isWrappable: second))
|
||||
.Where(x => x.isWrappable)
|
||||
.Max(x => x.width);
|
||||
|
||||
var secondMaxColumn = widths.Zip(wrappable, (width, isWrappable) => isWrappable && width != maxColumn ? maxColumn : 0).Max();
|
||||
var columnDifference = maxColumn - secondMaxColumn;
|
||||
|
||||
var ratios = widths.Zip(wrappable, (width, allowWrap) =>
|
||||
{
|
||||
if (width == maxColumn && allowWrap)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}).ToList();
|
||||
|
||||
if (!ratios.Any(x => x > 0) || columnDifference == 0)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
var maxReduce = widths.Select(_ => Math.Min(excessWidth, columnDifference)).ToList();
|
||||
widths = Ratio.Reduce(excessWidth, ratios, maxReduce, widths);
|
||||
|
||||
totalWidth = widths.Sum();
|
||||
excessWidth = totalWidth - maxWidth;
|
||||
}
|
||||
}
|
||||
|
||||
return widths;
|
||||
}
|
||||
|
||||
private (int Min, int Max) MeasureColumn(TableColumn column, Encoding encoding, int maxWidth)
|
||||
{
|
||||
// Predetermined width?
|
||||
if (column.Width != null)
|
||||
{
|
||||
var padding = column.GetPadding();
|
||||
return (column.Width.Value + padding, column.Width.Value + padding);
|
||||
}
|
||||
|
||||
var columnIndex = _columns.IndexOf(column);
|
||||
var rows = _rows.Select(row => row[columnIndex]);
|
||||
|
||||
var minWidths = new List<int>();
|
||||
var maxWidths = new List<int>();
|
||||
foreach (var row in rows)
|
||||
{
|
||||
var measure = ((IRenderable)row).Measure(encoding, maxWidth);
|
||||
minWidths.Add(measure.Min);
|
||||
maxWidths.Add(measure.Max);
|
||||
}
|
||||
|
||||
return (minWidths.Count > 0 ? minWidths.Max() : 1,
|
||||
maxWidths.Count > 0 ? maxWidths.Max() : maxWidth);
|
||||
}
|
||||
|
||||
private int GetExtraWidth(bool includePadding)
|
||||
{
|
||||
var edges = 2;
|
||||
var separators = _columns.Count - 1;
|
||||
var padding = includePadding ? _columns.Select(x => x.GetPadding()).Sum() : 0;
|
||||
return separators + edges + padding;
|
||||
}
|
||||
}
|
||||
}
|
@ -10,31 +10,50 @@ namespace Spectre.Console
|
||||
/// <summary>
|
||||
/// Represents a table.
|
||||
/// </summary>
|
||||
public sealed class Table : IRenderable
|
||||
public sealed partial class Table : IRenderable
|
||||
{
|
||||
private readonly List<Text> _columns;
|
||||
private readonly List<TableColumn> _columns;
|
||||
private readonly List<List<Text>> _rows;
|
||||
private readonly Border _border;
|
||||
private readonly BorderKind _borderKind;
|
||||
private readonly bool _showHeaders;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the number of columns in the table.
|
||||
/// </summary>
|
||||
public int ColumnCount => _columns.Count;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the number of rows in the table.
|
||||
/// </summary>
|
||||
public int RowCount => _rows.Count;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the kind of border to use.
|
||||
/// </summary>
|
||||
public BorderKind Border { get; set; } = BorderKind.Square;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether or not table headers should be shown.
|
||||
/// </summary>
|
||||
public bool ShowHeaders { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether or not the table should
|
||||
/// fit the available space. If <c>false</c>, the table width will be
|
||||
/// auto calculated. Defaults to <c>false</c>.
|
||||
/// </summary>
|
||||
public bool Expand { get; set; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the width of the table.
|
||||
/// </summary>
|
||||
public int? Width { get; set; } = null;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="Table"/> class.
|
||||
/// </summary>
|
||||
/// <param name="border">The border to use.</param>
|
||||
/// <param name="showHeaders">Whether or not to show table headers.</param>
|
||||
public Table(BorderKind border = BorderKind.Square, bool showHeaders = true)
|
||||
public Table()
|
||||
{
|
||||
_columns = new List<Text>();
|
||||
_columns = new List<TableColumn>();
|
||||
_rows = new List<List<Text>>();
|
||||
_border = Border.GetBorder(border);
|
||||
_borderKind = border;
|
||||
_showHeaders = showHeaders;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -48,7 +67,21 @@ namespace Spectre.Console
|
||||
throw new ArgumentNullException(nameof(column));
|
||||
}
|
||||
|
||||
_columns.Add(Text.New(column));
|
||||
_columns.Add(new TableColumn(column));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a column to the table.
|
||||
/// </summary>
|
||||
/// <param name="column">The column to add.</param>
|
||||
public void AddColumn(TableColumn column)
|
||||
{
|
||||
if (column is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(column));
|
||||
}
|
||||
|
||||
_columns.Add(column);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -62,7 +95,7 @@ namespace Spectre.Console
|
||||
throw new ArgumentNullException(nameof(columns));
|
||||
}
|
||||
|
||||
_columns.AddRange(columns.Select(column => Text.New(column)));
|
||||
_columns.AddRange(columns.Select(column => new TableColumn(column)));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -90,66 +123,55 @@ namespace Spectre.Console
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public int Measure(Encoding encoding, int maxWidth)
|
||||
Measurement IRenderable.Measure(Encoding encoding, int maxWidth)
|
||||
{
|
||||
// Calculate the max width for each column
|
||||
var maxColumnWidth = (maxWidth - (2 + (_columns.Count * 2) + (_columns.Count - 1))) / _columns.Count;
|
||||
var columnWidths = _columns.Select(c => c.Measure(encoding, maxColumnWidth)).ToArray();
|
||||
for (var rowIndex = 0; rowIndex < _rows.Count; rowIndex++)
|
||||
if (Width != null)
|
||||
{
|
||||
for (var columnIndex = 0; columnIndex < _rows[rowIndex].Count; columnIndex++)
|
||||
{
|
||||
var columnWidth = _rows[rowIndex][columnIndex].Measure(encoding, maxColumnWidth);
|
||||
if (columnWidth > columnWidths[columnIndex])
|
||||
{
|
||||
columnWidths[columnIndex] = columnWidth;
|
||||
}
|
||||
}
|
||||
maxWidth = Math.Min(Width.Value, maxWidth);
|
||||
}
|
||||
|
||||
// We now know the max width of each column, so let's recalculate the width
|
||||
return columnWidths.Sum() + 2 + (_columns.Count * 2) + (_columns.Count - 1);
|
||||
maxWidth -= GetExtraWidth(includePadding: true);
|
||||
|
||||
var measurements = _columns.Select(column => MeasureColumn(column, encoding, maxWidth)).ToList();
|
||||
var min = measurements.Sum(x => x.Min) + GetExtraWidth(includePadding: true);
|
||||
var max = Width ?? measurements.Sum(x => x.Max) + GetExtraWidth(includePadding: true);
|
||||
|
||||
return new Measurement(min, max);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IEnumerable<Segment> Render(Encoding encoding, int width)
|
||||
IEnumerable<Segment> IRenderable.Render(Encoding encoding, int width)
|
||||
{
|
||||
var showBorder = _borderKind != BorderKind.None;
|
||||
var hideBorder = _borderKind == BorderKind.None;
|
||||
var border = Composition.Border.GetBorder(Border);
|
||||
|
||||
var leftRightBorderWidth = _borderKind == BorderKind.None ? 0 : 2;
|
||||
var columnPadding = _borderKind == BorderKind.None ? _columns.Count : _columns.Count * 2;
|
||||
var separatorCount = _borderKind == BorderKind.None ? 0 : _columns.Count - 1;
|
||||
var showBorder = Border != BorderKind.None;
|
||||
var hideBorder = Border == BorderKind.None;
|
||||
|
||||
// Calculate the max width for each column.
|
||||
var maxColumnWidth = (width - (leftRightBorderWidth + columnPadding + separatorCount)) / _columns.Count;
|
||||
var columnWidths = _columns.Select(c => c.Measure(encoding, maxColumnWidth)).ToArray();
|
||||
for (var rowIndex = 0; rowIndex < _rows.Count; rowIndex++)
|
||||
var maxWidth = width;
|
||||
if (Width != null)
|
||||
{
|
||||
for (var columnIndex = 0; columnIndex < _rows[rowIndex].Count; columnIndex++)
|
||||
{
|
||||
var columnWidth = _rows[rowIndex][columnIndex].Measure(encoding, maxColumnWidth);
|
||||
if (columnWidth > columnWidths[columnIndex])
|
||||
{
|
||||
columnWidths[columnIndex] = columnWidth;
|
||||
}
|
||||
}
|
||||
maxWidth = Math.Min(Width.Value, maxWidth);
|
||||
}
|
||||
|
||||
// We now know the max width of each column, so let's recalculate the width
|
||||
width = columnWidths.Sum() + leftRightBorderWidth + columnPadding + separatorCount;
|
||||
maxWidth -= GetExtraWidth(includePadding: true);
|
||||
|
||||
// Calculate the column and table widths
|
||||
var columnWidths = CalculateColumnWidths(encoding, maxWidth);
|
||||
|
||||
// Update the table width.
|
||||
width = columnWidths.Sum() + GetExtraWidth(includePadding: false);
|
||||
|
||||
var rows = new List<List<Text>>();
|
||||
if (_showHeaders)
|
||||
if (ShowHeaders)
|
||||
{
|
||||
// Add columns to top of rows
|
||||
rows.Add(new List<Text>(_columns));
|
||||
rows.Add(new List<Text>(_columns.Select(c => c.Text)));
|
||||
}
|
||||
|
||||
// Add tows.
|
||||
// Add rows.
|
||||
rows.AddRange(_rows);
|
||||
|
||||
// Iterate all rows.
|
||||
// Iterate all rows
|
||||
var result = new List<Segment>();
|
||||
foreach (var (index, firstRow, lastRow, row) in rows.Enumerate())
|
||||
{
|
||||
@ -159,7 +181,7 @@ namespace Spectre.Console
|
||||
var cells = new List<List<SegmentLine>>();
|
||||
foreach (var (rowWidth, cell) in columnWidths.Zip(row, (f, s) => (f, s)))
|
||||
{
|
||||
var lines = Segment.SplitLines(cell.Render(encoding, rowWidth));
|
||||
var lines = Segment.SplitLines(((IRenderable)cell).Render(encoding, rowWidth));
|
||||
cellHeight = Math.Max(cellHeight, lines.Count);
|
||||
cells.Add(lines);
|
||||
}
|
||||
@ -167,20 +189,20 @@ namespace Spectre.Console
|
||||
// Show top of header?
|
||||
if (firstRow && showBorder)
|
||||
{
|
||||
result.Add(new Segment(_border.GetPart(BorderPart.HeaderTopLeft)));
|
||||
result.Add(new Segment(border.GetPart(BorderPart.HeaderTopLeft)));
|
||||
foreach (var (columnIndex, _, lastColumn, columnWidth) in columnWidths.Enumerate())
|
||||
{
|
||||
result.Add(new Segment(_border.GetPart(BorderPart.HeaderTop))); // Left padding
|
||||
result.Add(new Segment(_border.GetPart(BorderPart.HeaderTop, columnWidth)));
|
||||
result.Add(new Segment(_border.GetPart(BorderPart.HeaderTop))); // Right padding
|
||||
result.Add(new Segment(border.GetPart(BorderPart.HeaderTop))); // Left padding
|
||||
result.Add(new Segment(border.GetPart(BorderPart.HeaderTop, columnWidth)));
|
||||
result.Add(new Segment(border.GetPart(BorderPart.HeaderTop))); // Right padding
|
||||
|
||||
if (!lastColumn)
|
||||
{
|
||||
result.Add(new Segment(_border.GetPart(BorderPart.HeaderTopSeparator)));
|
||||
result.Add(new Segment(border.GetPart(BorderPart.HeaderTopSeparator)));
|
||||
}
|
||||
}
|
||||
|
||||
result.Add(new Segment(_border.GetPart(BorderPart.HeaderTopRight)));
|
||||
result.Add(new Segment(border.GetPart(BorderPart.HeaderTopRight)));
|
||||
result.Add(Segment.LineBreak());
|
||||
}
|
||||
|
||||
@ -188,21 +210,24 @@ namespace Spectre.Console
|
||||
foreach (var cellRowIndex in Enumerable.Range(0, cellHeight))
|
||||
{
|
||||
// Make cells the same shape
|
||||
MakeSameHeight(cellHeight, cells);
|
||||
cells = Segment.MakeSameHeight(cellHeight, cells);
|
||||
|
||||
var w00t = cells.Enumerate().ToArray();
|
||||
foreach (var (cellIndex, firstCell, lastCell, cell) in w00t)
|
||||
foreach (var (cellIndex, firstCell, lastCell, cell) in cells.Enumerate())
|
||||
{
|
||||
if (firstCell && showBorder)
|
||||
{
|
||||
// Show left column edge
|
||||
result.Add(new Segment(_border.GetPart(BorderPart.CellLeft)));
|
||||
result.Add(new Segment(border.GetPart(BorderPart.CellLeft)));
|
||||
}
|
||||
|
||||
// Pad column on left side.
|
||||
if (showBorder)
|
||||
{
|
||||
result.Add(new Segment(" "));
|
||||
var leftPadding = _columns[cellIndex].LeftPadding;
|
||||
if (leftPadding > 0)
|
||||
{
|
||||
result.Add(new Segment(new string(' ', leftPadding)));
|
||||
}
|
||||
}
|
||||
|
||||
// Add content
|
||||
@ -218,18 +243,22 @@ namespace Spectre.Console
|
||||
// Pad column on the right side
|
||||
if (showBorder || (hideBorder && !lastCell))
|
||||
{
|
||||
result.Add(new Segment(" "));
|
||||
var rightPadding = _columns[cellIndex].RightPadding;
|
||||
if (rightPadding > 0)
|
||||
{
|
||||
result.Add(new Segment(new string(' ', rightPadding)));
|
||||
}
|
||||
}
|
||||
|
||||
if (lastCell && showBorder)
|
||||
{
|
||||
// Add right column edge
|
||||
result.Add(new Segment(_border.GetPart(BorderPart.CellRight)));
|
||||
result.Add(new Segment(border.GetPart(BorderPart.CellRight)));
|
||||
}
|
||||
else if (showBorder || (hideBorder && !lastCell))
|
||||
{
|
||||
// Add column separator
|
||||
result.Add(new Segment(_border.GetPart(BorderPart.CellSeparator)));
|
||||
result.Add(new Segment(border.GetPart(BorderPart.CellSeparator)));
|
||||
}
|
||||
}
|
||||
|
||||
@ -237,42 +266,42 @@ namespace Spectre.Console
|
||||
}
|
||||
|
||||
// Show bottom of header?
|
||||
if (firstRow && showBorder)
|
||||
if (firstRow && showBorder && ShowHeaders)
|
||||
{
|
||||
result.Add(new Segment(_border.GetPart(BorderPart.HeaderBottomLeft)));
|
||||
result.Add(new Segment(border.GetPart(BorderPart.HeaderBottomLeft)));
|
||||
foreach (var (columnIndex, first, lastColumn, columnWidth) in columnWidths.Enumerate())
|
||||
{
|
||||
result.Add(new Segment(_border.GetPart(BorderPart.HeaderBottom))); // Left padding
|
||||
result.Add(new Segment(_border.GetPart(BorderPart.HeaderBottom, columnWidth)));
|
||||
result.Add(new Segment(_border.GetPart(BorderPart.HeaderBottom))); // Right padding
|
||||
result.Add(new Segment(border.GetPart(BorderPart.HeaderBottom))); // Left padding
|
||||
result.Add(new Segment(border.GetPart(BorderPart.HeaderBottom, columnWidth)));
|
||||
result.Add(new Segment(border.GetPart(BorderPart.HeaderBottom))); // Right padding
|
||||
|
||||
if (!lastColumn)
|
||||
{
|
||||
result.Add(new Segment(_border.GetPart(BorderPart.HeaderBottomSeparator)));
|
||||
result.Add(new Segment(border.GetPart(BorderPart.HeaderBottomSeparator)));
|
||||
}
|
||||
}
|
||||
|
||||
result.Add(new Segment(_border.GetPart(BorderPart.HeaderBottomRight)));
|
||||
result.Add(new Segment(border.GetPart(BorderPart.HeaderBottomRight)));
|
||||
result.Add(Segment.LineBreak());
|
||||
}
|
||||
|
||||
// Show bottom of footer?
|
||||
if (lastRow && showBorder)
|
||||
{
|
||||
result.Add(new Segment(_border.GetPart(BorderPart.FooterBottomLeft)));
|
||||
result.Add(new Segment(border.GetPart(BorderPart.FooterBottomLeft)));
|
||||
foreach (var (columnIndex, first, lastColumn, columnWidth) in columnWidths.Enumerate())
|
||||
{
|
||||
result.Add(new Segment(_border.GetPart(BorderPart.FooterBottom)));
|
||||
result.Add(new Segment(_border.GetPart(BorderPart.FooterBottom, columnWidth)));
|
||||
result.Add(new Segment(_border.GetPart(BorderPart.FooterBottom)));
|
||||
result.Add(new Segment(border.GetPart(BorderPart.FooterBottom)));
|
||||
result.Add(new Segment(border.GetPart(BorderPart.FooterBottom, columnWidth)));
|
||||
result.Add(new Segment(border.GetPart(BorderPart.FooterBottom)));
|
||||
|
||||
if (!lastColumn)
|
||||
{
|
||||
result.Add(new Segment(_border.GetPart(BorderPart.FooterBottomSeparator)));
|
||||
result.Add(new Segment(border.GetPart(BorderPart.FooterBottomSeparator)));
|
||||
}
|
||||
}
|
||||
|
||||
result.Add(new Segment(_border.GetPart(BorderPart.FooterBottomRight)));
|
||||
result.Add(new Segment(border.GetPart(BorderPart.FooterBottomRight)));
|
||||
result.Add(Segment.LineBreak());
|
||||
}
|
||||
}
|
||||
@ -280,18 +309,9 @@ namespace Spectre.Console
|
||||
return result;
|
||||
}
|
||||
|
||||
private static void MakeSameHeight(int cellHeight, List<List<SegmentLine>> cells)
|
||||
private bool ShouldExpand()
|
||||
{
|
||||
foreach (var cell in cells)
|
||||
{
|
||||
if (cell.Count < cellHeight)
|
||||
{
|
||||
while (cell.Count != cellHeight)
|
||||
{
|
||||
cell.Add(new SegmentLine());
|
||||
}
|
||||
}
|
||||
}
|
||||
return Expand || Width != null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
67
src/Spectre.Console/Composition/TableColumn.cs
Normal file
67
src/Spectre.Console/Composition/TableColumn.cs
Normal file
@ -0,0 +1,67 @@
|
||||
using System;
|
||||
|
||||
namespace Spectre.Console
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a table column.
|
||||
/// </summary>
|
||||
public sealed class TableColumn
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the text associated with the column.
|
||||
/// </summary>
|
||||
public Text Text { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the width of the column.
|
||||
/// If <c>null</c>, the column will adapt to it's contents.
|
||||
/// </summary>
|
||||
public int? Width { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the left padding.
|
||||
/// </summary>
|
||||
public int LeftPadding { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the right padding.
|
||||
/// </summary>
|
||||
public int RightPadding { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the ratio to use when calculating column width.
|
||||
/// If <c>null</c>, the column will adapt to it's contents.
|
||||
/// </summary>
|
||||
public int? Ratio { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether wrapping of
|
||||
/// text within the column should be prevented.
|
||||
/// </summary>
|
||||
public bool NoWrap { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="TableColumn"/> class.
|
||||
/// </summary>
|
||||
/// <param name="text">The table column text.</param>
|
||||
public TableColumn(string text)
|
||||
{
|
||||
Text = Text.New(text ?? throw new ArgumentNullException(nameof(text)));
|
||||
Width = null;
|
||||
LeftPadding = 1;
|
||||
RightPadding = 1;
|
||||
Ratio = null;
|
||||
NoWrap = false;
|
||||
}
|
||||
|
||||
internal int GetPadding()
|
||||
{
|
||||
return LeftPadding + RightPadding;
|
||||
}
|
||||
|
||||
internal bool IsFlexible()
|
||||
{
|
||||
return Width == null;
|
||||
}
|
||||
}
|
||||
}
|
@ -98,19 +98,22 @@ namespace Spectre.Console
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public int Measure(Encoding encoding, int maxWidth)
|
||||
Measurement IRenderable.Measure(Encoding encoding, int maxWidth)
|
||||
{
|
||||
var lines = Segment.SplitLines(Render(encoding, maxWidth));
|
||||
var lines = Segment.SplitLines(((IRenderable)this).Render(encoding, maxWidth));
|
||||
if (lines.Count == 0)
|
||||
{
|
||||
return 0;
|
||||
return new Measurement(0, maxWidth);
|
||||
}
|
||||
|
||||
return lines.Max(x => x.Length);
|
||||
var max = lines.Max(line => line.Length);
|
||||
var min = lines.SelectMany(line => line.Select(segment => segment.Text.Length)).Max();
|
||||
|
||||
return new Measurement(min, max);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IEnumerable<Segment> Render(Encoding encoding, int width)
|
||||
IEnumerable<Segment> IRenderable.Render(Encoding encoding, int width)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(_text))
|
||||
{
|
||||
|
@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using Spectre.Console.Internal;
|
||||
|
||||
namespace Spectre.Console
|
||||
{
|
||||
@ -19,7 +20,12 @@ namespace Spectre.Console
|
||||
throw new ArgumentNullException(nameof(console));
|
||||
}
|
||||
|
||||
console.Write(Environment.NewLine);
|
||||
using (console.PushColor(Color.Default, true))
|
||||
using (console.PushColor(Color.Default, false))
|
||||
using (console.PushDecoration(Decoration.None))
|
||||
{
|
||||
console.Write(Environment.NewLine);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -6,6 +6,19 @@ namespace Spectre.Console.Internal
|
||||
{
|
||||
internal static class EnumerableExtensions
|
||||
{
|
||||
public static void ForEach<T>(this IEnumerable<T> source, Action<T> action)
|
||||
{
|
||||
foreach (var item in source)
|
||||
{
|
||||
action(item);
|
||||
}
|
||||
}
|
||||
|
||||
public static bool AnyTrue(this IEnumerable<bool> source)
|
||||
{
|
||||
return source.Any(b => b);
|
||||
}
|
||||
|
||||
public static IEnumerable<(int Index, bool First, bool Last, T Item)> Enumerate<T>(this IEnumerable<T> source)
|
||||
{
|
||||
if (source is null)
|
||||
@ -40,5 +53,18 @@ namespace Spectre.Console.Internal
|
||||
{
|
||||
return source.Select((value, index) => func(value, index));
|
||||
}
|
||||
|
||||
public static IEnumerable<(TFirst First, TSecond Second)> Zip<TFirst, TSecond>(
|
||||
this IEnumerable<TFirst> source, IEnumerable<TSecond> first)
|
||||
{
|
||||
return source.Zip(first, (first, second) => (first, second));
|
||||
}
|
||||
|
||||
public static IEnumerable<(TFirst First, TSecond Second, TThird Third)> Zip<TFirst, TSecond, TThird>(
|
||||
this IEnumerable<TFirst> first, IEnumerable<TSecond> second, IEnumerable<TThird> third)
|
||||
{
|
||||
return first.Zip(second, (a, b) => (a, b))
|
||||
.Zip(third, (a, b) => (a.a, a.b, b));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
75
src/Spectre.Console/Internal/Utilities/Ratio.cs
Normal file
75
src/Spectre.Console/Internal/Utilities/Ratio.cs
Normal file
@ -0,0 +1,75 @@
|
||||
// Ported from Rich by Will McGugan, licensed under MIT.
|
||||
// https://github.com/willmcgugan/rich/blob/527475837ebbfc427530b3ee0d4d0741d2d0fc6d/rich/_ratio.py
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
|
||||
namespace Spectre.Console.Internal
|
||||
{
|
||||
internal static class Ratio
|
||||
{
|
||||
public static List<int> Reduce(int total, List<int> ratios, List<int> maximums, List<int> values)
|
||||
{
|
||||
ratios = ratios.Zip(maximums, (a, b) => (ratio: a, max: b)).Select(a => a.max > 0 ? a.ratio : 0).ToList();
|
||||
var totalRatio = ratios.Sum();
|
||||
if (totalRatio == 0)
|
||||
{
|
||||
return values;
|
||||
}
|
||||
|
||||
var totalRemaining = total;
|
||||
var result = new List<int>();
|
||||
|
||||
foreach (var (ratio, maximum, value) in ratios.Zip(maximums, values))
|
||||
{
|
||||
if (ratio > 0 && totalRatio > 0)
|
||||
{
|
||||
var distributed = (int)Math.Min(maximum, Math.Round(ratio * totalRemaining / (double)totalRatio));
|
||||
result.Add(value - distributed);
|
||||
totalRemaining -= distributed;
|
||||
totalRatio -= ratio;
|
||||
}
|
||||
else
|
||||
{
|
||||
result.Add(value);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public static List<int> Distribute(int total, List<int> ratios, List<int> minimums = null)
|
||||
{
|
||||
if (minimums != null)
|
||||
{
|
||||
ratios = ratios.Zip(minimums, (a, b) => (ratio: a, min: b)).Select(a => a.min > 0 ? a.ratio : 0).ToList();
|
||||
}
|
||||
|
||||
var totalRatio = ratios.Sum();
|
||||
Debug.Assert(totalRatio > 0, "Sum or ratios must be > 0");
|
||||
|
||||
var totalRemaining = total;
|
||||
var distributedTotal = new List<int>();
|
||||
|
||||
if (minimums == null)
|
||||
{
|
||||
minimums = ratios.Select(_ => 0).ToList();
|
||||
}
|
||||
|
||||
foreach (var (ratio, minimum) in ratios.Zip(minimums, (a, b) => (a, b)))
|
||||
{
|
||||
var distributed = (totalRatio > 0)
|
||||
? Math.Max(minimum, (int)Math.Ceiling(ratio * totalRemaining / (double)totalRatio))
|
||||
: totalRemaining;
|
||||
|
||||
distributedTotal.Add(distributed);
|
||||
totalRatio -= ratio;
|
||||
totalRemaining -= distributed;
|
||||
}
|
||||
|
||||
return distributedTotal;
|
||||
}
|
||||
}
|
||||
}
|
@ -22,6 +22,9 @@
|
||||
<Compile Update="ConsoleExtensions.*.cs">
|
||||
<DependentUpon>ConsoleExtensions.cs</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Update="**/Table.*.cs">
|
||||
<DependentUpon>**/Table.cs</DependentUpon>
|
||||
</Compile>
|
||||
|
||||
</ItemGroup>
|
||||
|
||||
|
Reference in New Issue
Block a user