Add support for showing no border

This commit is contained in:
Patrik Svensson 2020-08-04 23:20:52 +02:00 committed by Patrik Svensson
parent a068fc68c3
commit f9bd936254
7 changed files with 128 additions and 62 deletions

View File

@ -111,7 +111,7 @@ namespace Spectre.Console.Tests.Unit.Composition
} }
[Fact] [Fact]
public void Should_Render_Table_With_Specified_Border() public void Should_Render_Table_With_Specified_Border_Correctly()
{ {
// Given // Given
var console = new PlainConsole(width: 80); var console = new PlainConsole(width: 80);
@ -132,6 +132,26 @@ namespace Spectre.Console.Tests.Unit.Composition
console.Lines[5].ShouldBe("+-------------------------+"); console.Lines[5].ShouldBe("+-------------------------+");
} }
[Fact]
public void Should_Render_Table_With_No_Border_Correctly()
{
// Given
var console = new PlainConsole(width: 80);
var table = new Table(BorderKind.None);
table.AddColumns("Foo", "Bar", "Baz");
table.AddRow("Qux", "Corgi", "Waldo");
table.AddRow("Grault", "Garply", "Fred");
// When
console.Render(table);
// Then
console.Lines.Count.ShouldBe(3);
console.Lines[0].ShouldBe("Foo Bar Baz ");
console.Lines[1].ShouldBe("Qux Corgi Waldo");
console.Lines[2].ShouldBe("Grault Garply Fred ");
}
[Fact] [Fact]
public void Should_Render_Table_With_Multiple_Rows_In_Cell_Correctly() public void Should_Render_Table_With_Multiple_Rows_In_Cell_Correctly()
{ {

View File

@ -1,6 +1,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Globalization; using System.Globalization;
using System.Linq;
namespace Spectre.Console.Composition namespace Spectre.Console.Composition
{ {
@ -9,10 +10,11 @@ namespace Spectre.Console.Composition
/// </summary> /// </summary>
public abstract class Border public abstract class Border
{ {
private readonly Dictionary<BorderPart, char> _lookup; private readonly Dictionary<BorderPart, string> _lookup;
private static readonly Dictionary<BorderKind, Border> _borders = new Dictionary<BorderKind, Border> private static readonly Dictionary<BorderKind, Border> _borders = new Dictionary<BorderKind, Border>
{ {
{ BorderKind.None, new NoBorder() },
{ BorderKind.Ascii, new AsciiBorder() }, { BorderKind.Ascii, new AsciiBorder() },
{ BorderKind.Square, new SquareBorder() }, { BorderKind.Square, new SquareBorder() },
}; };
@ -40,11 +42,17 @@ namespace Spectre.Console.Composition
return border; return border;
} }
private Dictionary<BorderPart, char> Initialize() private Dictionary<BorderPart, string> Initialize()
{ {
var lookup = new Dictionary<BorderPart, char>(); var lookup = new Dictionary<BorderPart, string>();
foreach (BorderPart part in Enum.GetValues(typeof(BorderPart))) foreach (BorderPart part in Enum.GetValues(typeof(BorderPart)))
{ {
var text = GetBoxPart(part);
if (text.Length > 1)
{
throw new InvalidOperationException("A box part cannot contain more than one character.");
}
lookup.Add(part, GetBoxPart(part)); lookup.Add(part, GetBoxPart(part));
} }
@ -59,7 +67,8 @@ namespace Spectre.Console.Composition
/// <returns>A string representation of the specified border part.</returns> /// <returns>A string representation of the specified border part.</returns>
public string GetPart(BorderPart part, int count) public string GetPart(BorderPart part, int count)
{ {
return new string(GetBoxPart(part), count); // TODO: This need some optimization...
return string.Join(string.Empty, Enumerable.Repeat(GetBoxPart(part)[0], count));
} }
/// <summary> /// <summary>
@ -77,6 +86,6 @@ namespace Spectre.Console.Composition
/// </summary> /// </summary>
/// <param name="part">The part to get the character representation for.</param> /// <param name="part">The part to get the character representation for.</param>
/// <returns>A character representation of the specified border part.</returns> /// <returns>A character representation of the specified border part.</returns>
protected abstract char GetBoxPart(BorderPart part); protected abstract string GetBoxPart(BorderPart part);
} }
} }

View File

@ -5,14 +5,19 @@ namespace Spectre.Console
/// </summary> /// </summary>
public enum BorderKind public enum BorderKind
{ {
/// <summary>
/// No border.
/// </summary>
None = 0,
/// <summary> /// <summary>
/// A square border. /// A square border.
/// </summary> /// </summary>
Square = 0, Square = 1,
/// <summary> /// <summary>
/// An old school ASCII border. /// An old school ASCII border.
/// </summary> /// </summary>
Ascii = 1, Ascii = 2,
} }
} }

View File

@ -8,28 +8,28 @@ namespace Spectre.Console.Composition
public sealed class AsciiBorder : Border public sealed class AsciiBorder : Border
{ {
/// <inheritdoc/> /// <inheritdoc/>
protected override char GetBoxPart(BorderPart part) protected override string GetBoxPart(BorderPart part)
{ {
return part switch return part switch
{ {
BorderPart.HeaderTopLeft => '+', BorderPart.HeaderTopLeft => "+",
BorderPart.HeaderTop => '-', BorderPart.HeaderTop => "-",
BorderPart.HeaderTopSeparator => '-', BorderPart.HeaderTopSeparator => "-",
BorderPart.HeaderTopRight => '+', BorderPart.HeaderTopRight => "+",
BorderPart.HeaderLeft => '|', BorderPart.HeaderLeft => "|",
BorderPart.HeaderSeparator => '|', BorderPart.HeaderSeparator => "|",
BorderPart.HeaderRight => '|', BorderPart.HeaderRight => "|",
BorderPart.HeaderBottomLeft => '|', BorderPart.HeaderBottomLeft => "|",
BorderPart.HeaderBottom => '-', BorderPart.HeaderBottom => "-",
BorderPart.HeaderBottomSeparator => '+', BorderPart.HeaderBottomSeparator => "+",
BorderPart.HeaderBottomRight => '|', BorderPart.HeaderBottomRight => "|",
BorderPart.CellLeft => '|', BorderPart.CellLeft => "|",
BorderPart.CellSeparator => '|', BorderPart.CellSeparator => "|",
BorderPart.ColumnRight => '|', BorderPart.ColumnRight => "|",
BorderPart.FooterBottomLeft => '+', BorderPart.FooterBottomLeft => "+",
BorderPart.FooterBottom => '-', BorderPart.FooterBottom => "-",
BorderPart.FooterBottomSeparator => '-', BorderPart.FooterBottomSeparator => "-",
BorderPart.FooterBottomRight => '+', BorderPart.FooterBottomRight => "+",
_ => throw new InvalidOperationException("Unknown box part."), _ => throw new InvalidOperationException("Unknown box part."),
}; };
} }

View File

@ -0,0 +1,10 @@
namespace Spectre.Console.Composition
{
internal sealed class NoBorder : Border
{
protected override string GetBoxPart(BorderPart part)
{
return " ";
}
}
}

View File

@ -8,28 +8,28 @@ namespace Spectre.Console.Composition
public sealed class SquareBorder : Border public sealed class SquareBorder : Border
{ {
/// <inheritdoc/> /// <inheritdoc/>
protected override char GetBoxPart(BorderPart part) protected override string GetBoxPart(BorderPart part)
{ {
return part switch return part switch
{ {
BorderPart.HeaderTopLeft => '┌', BorderPart.HeaderTopLeft => "┌",
BorderPart.HeaderTop => '─', BorderPart.HeaderTop => "─",
BorderPart.HeaderTopSeparator => '┬', BorderPart.HeaderTopSeparator => "┬",
BorderPart.HeaderTopRight => '┐', BorderPart.HeaderTopRight => "┐",
BorderPart.HeaderLeft => '│', BorderPart.HeaderLeft => "│",
BorderPart.HeaderSeparator => '│', BorderPart.HeaderSeparator => "│",
BorderPart.HeaderRight => '│', BorderPart.HeaderRight => "│",
BorderPart.HeaderBottomLeft => '├', BorderPart.HeaderBottomLeft => "├",
BorderPart.HeaderBottom => '─', BorderPart.HeaderBottom => "─",
BorderPart.HeaderBottomSeparator => '┼', BorderPart.HeaderBottomSeparator => "┼",
BorderPart.HeaderBottomRight => '┤', BorderPart.HeaderBottomRight => "┤",
BorderPart.CellLeft => '│', BorderPart.CellLeft => "│",
BorderPart.CellSeparator => '│', BorderPart.CellSeparator => "│",
BorderPart.ColumnRight => '│', BorderPart.ColumnRight => "│",
BorderPart.FooterBottomLeft => '└', BorderPart.FooterBottomLeft => "└",
BorderPart.FooterBottom => '─', BorderPart.FooterBottom => "─",
BorderPart.FooterBottomSeparator => '┴', BorderPart.FooterBottomSeparator => "┴",
BorderPart.FooterBottomRight => '┘', BorderPart.FooterBottomRight => "┘",
_ => throw new InvalidOperationException("Unknown box part."), _ => throw new InvalidOperationException("Unknown box part."),
}; };
} }

View File

@ -15,6 +15,7 @@ namespace Spectre.Console
private readonly List<Text> _columns; private readonly List<Text> _columns;
private readonly List<List<Text>> _rows; private readonly List<List<Text>> _rows;
private readonly Border _border; private readonly Border _border;
private readonly BorderKind _borderKind;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="Table"/> class. /// Initializes a new instance of the <see cref="Table"/> class.
@ -25,6 +26,7 @@ namespace Spectre.Console
_columns = new List<Text>(); _columns = new List<Text>();
_rows = new List<List<Text>>(); _rows = new List<List<Text>>();
_border = Border.GetBorder(border); _border = Border.GetBorder(border);
_borderKind = border;
} }
/// <summary> /// <summary>
@ -104,8 +106,15 @@ namespace Spectre.Console
/// <inheritdoc/> /// <inheritdoc/>
public IEnumerable<Segment> Render(Encoding encoding, int width) public IEnumerable<Segment> Render(Encoding encoding, int width)
{ {
var showBorder = _borderKind != BorderKind.None;
var hideBorder = _borderKind == BorderKind.None;
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;
// Calculate the max width for each column. // Calculate the max width for each column.
var maxColumnWidth = (width - (2 + (_columns.Count * 2) + (_columns.Count - 1))) / _columns.Count; var maxColumnWidth = (width - (leftRightBorderWidth + columnPadding + separatorCount)) / _columns.Count;
var columnWidths = _columns.Select(c => c.Measure(encoding, maxColumnWidth)).ToArray(); var columnWidths = _columns.Select(c => c.Measure(encoding, maxColumnWidth)).ToArray();
for (var rowIndex = 0; rowIndex < _rows.Count; rowIndex++) for (var rowIndex = 0; rowIndex < _rows.Count; rowIndex++)
{ {
@ -120,7 +129,7 @@ namespace Spectre.Console
} }
// We now know the max width of each column, so let's recalculate the width. // We now know the max width of each column, so let's recalculate the width.
width = columnWidths.Sum() + 2 + (_columns.Count * 2) + (_columns.Count - 1); width = columnWidths.Sum() + leftRightBorderWidth + columnPadding + separatorCount;
// Create the rows. // Create the rows.
var rows = new List<List<Text>>(); var rows = new List<List<Text>>();
@ -133,9 +142,8 @@ namespace Spectre.Console
{ {
var cellHeight = 1; var cellHeight = 1;
// Get the list of cells for the row. // Get the list of cells for the row and calculate the cell height.
var cells = new List<List<SegmentLine>>(); var cells = new List<List<SegmentLine>>();
foreach (var (rowWidth, cell) in columnWidths.Zip(row, (f, s) => (f, s))) foreach (var (rowWidth, cell) in columnWidths.Zip(row, (f, s) => (f, s)))
{ {
var lines = Segment.SplitLines(cell.Render(encoding, rowWidth)); var lines = Segment.SplitLines(cell.Render(encoding, rowWidth));
@ -143,7 +151,8 @@ namespace Spectre.Console
cells.Add(lines); cells.Add(lines);
} }
if (firstRow) // 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()) foreach (var (columnIndex, _, lastColumn, columnWidth) in columnWidths.Enumerate())
@ -171,31 +180,42 @@ namespace Spectre.Console
var w00t = cells.Enumerate().ToArray(); var w00t = cells.Enumerate().ToArray();
foreach (var (cellIndex, firstCell, lastCell, cell) in w00t) foreach (var (cellIndex, firstCell, lastCell, cell) in w00t)
{ {
if (firstCell) 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(" ")); result.Add(new Segment(" "));
}
// Add content
result.AddRange(cell[cellRowIndex]); result.AddRange(cell[cellRowIndex]);
// Pad cell right // Pad cell content right
var length = cell[cellRowIndex].Sum(segment => segment.CellLength(encoding)); var length = cell[cellRowIndex].Sum(segment => segment.CellLength(encoding));
if (length < columnWidths[cellIndex]) if (length < columnWidths[cellIndex])
{ {
result.Add(new Segment(new string(' ', columnWidths[cellIndex] - length))); result.Add(new Segment(new string(' ', columnWidths[cellIndex] - length)));
} }
result.Add(new Segment(" ")); // Pad column on the right side
if (showBorder || (hideBorder && !lastCell))
if (lastCell)
{ {
// Separator result.Add(new Segment(" "));
}
if (lastCell && showBorder)
{
// Add right column edge
result.Add(new Segment(_border.GetPart(BorderPart.ColumnRight))); result.Add(new Segment(_border.GetPart(BorderPart.ColumnRight)));
} }
else else if (showBorder || (hideBorder && !lastCell))
{ {
// Separator // Add column separator
result.Add(new Segment(_border.GetPart(BorderPart.CellSeparator))); result.Add(new Segment(_border.GetPart(BorderPart.CellSeparator)));
} }
} }
@ -203,7 +223,8 @@ namespace Spectre.Console
result.Add(Segment.LineBreak()); result.Add(Segment.LineBreak());
} }
if (firstRow) // Show bottom of header?
if (firstRow && showBorder)
{ {
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()) foreach (var (columnIndex, first, lastColumn, columnWidth) in columnWidths.Enumerate())
@ -222,7 +243,8 @@ namespace Spectre.Console
result.Add(Segment.LineBreak()); result.Add(Segment.LineBreak());
} }
if (lastRow) // 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()) foreach (var (columnIndex, first, lastColumn, columnWidth) in columnWidths.Enumerate())