Don't limit tables and grids to markup text

Closes #13
This commit is contained in:
Patrik Svensson 2020-08-24 23:24:23 +02:00 committed by Patrik Svensson
parent effdecb1d4
commit 8a01b93aca
41 changed files with 472 additions and 193 deletions

View File

@ -1,4 +1,3 @@
using System;
using Spectre.Console; using Spectre.Console;
namespace GridExample namespace GridExample

View File

@ -1,4 +1,3 @@
using System;
using Spectre.Console; using Spectre.Console;
namespace PanelExample namespace PanelExample

View File

@ -1,4 +1,3 @@
using System;
using Spectre.Console; using Spectre.Console;
namespace TableExample namespace TableExample
@ -12,6 +11,42 @@ namespace TableExample
// A big table // A big table
RenderBigTable(); RenderBigTable();
// A complex table
RenderComplexTable();
}
private static void RenderComplexTable()
{
// Create simple table.
var simple = new Table { Border = BorderKind.Rounded };
simple.AddColumn(new TableColumn("[u]Foo[/]").Centered());
simple.AddColumn(new TableColumn("[u]Bar[/]"));
simple.AddColumn(new TableColumn("[u]Baz[/]"));
simple.AddRow("Hello", "[red]World![/]", "");
simple.AddRow("[blue]Bounjour[/]", "[white]le[/]", "[red]monde![/]");
simple.AddRow("[blue]Hej[/]", "[yellow]Världen![/]", "");
// Create other table.
var second = new Table { Border = BorderKind.Square };
second.AddColumn(new TableColumn("[u]Foo[/]"));
second.AddColumn(new TableColumn("[u]Bar[/]"));
second.AddColumn(new TableColumn("[u]Baz[/]"));
second.AddRow("Hello", "[red]World![/]", "");
second.AddRow(simple, new Text("Whaaat"), new Text("Lolz"));
second.AddRow("[blue]Hej[/]", "[yellow]Världen![/]", "");
var table = new Table { Border = BorderKind.Rounded };
table.AddColumn(new TableColumn(new Panel("[u]Foo[/]")));
table.AddColumn(new TableColumn(new Panel("[u]Bar[/]")));
table.AddColumn(new TableColumn(new Panel("[u]Baz[/]")));
// Add some rows
table.AddRow(new Text("Hello").Centered(), new Markup("[red]World![/] 🌍"), Text.Empty);
table.AddRow(second, new Text("Whaaat"), new Text("Lol"));
table.AddRow(new Markup("[blue]Hej[/]").Centered(), new Markup("[yellow]Världen![/]"), Text.Empty);
AnsiConsole.Render(table);
} }
private static void RenderSimpleTable() private static void RenderSimpleTable()

View File

@ -81,3 +81,6 @@ dotnet_diagnostic.RCS1079.severity = warning
# RCS1057: Add empty line between declarations. # RCS1057: Add empty line between declarations.
dotnet_diagnostic.RCS1057.severity = none dotnet_diagnostic.RCS1057.severity = none
# IDE0004: Remove Unnecessary Cast
dotnet_diagnostic.IDE0004.severity = warning

View File

@ -1,6 +1,6 @@
using System; using System;
using Shouldly; using Shouldly;
using Spectre.Console.Composition; using Spectre.Console.Rendering;
using Xunit; using Xunit;
namespace Spectre.Console.Tests.Unit namespace Spectre.Console.Tests.Unit

View File

@ -125,8 +125,7 @@ namespace Spectre.Console.Tests.Unit
// When // When
console.Render( console.Render(
new Panel( new Panel(new Text("Hello World").RightAligned())
new Text("Hello World").WithAlignment(Justify.Right))
{ {
Expand = true, Expand = true,
}); });
@ -146,8 +145,7 @@ namespace Spectre.Console.Tests.Unit
// When // When
console.Render( console.Render(
new Panel( new Panel(new Text("Hello World").Centered())
new Text("Hello World").WithAlignment(Justify.Center))
{ {
Expand = true, Expand = true,
}); });

View File

@ -1,5 +1,5 @@
using Shouldly; using Shouldly;
using Spectre.Console.Composition; using Spectre.Console.Rendering;
using Xunit; using Xunit;
namespace Spectre.Console.Tests.Unit namespace Spectre.Console.Tests.Unit

View File

@ -59,7 +59,21 @@ namespace Spectre.Console.Tests.Unit
public sealed class TheAddRowMethod public sealed class TheAddRowMethod
{ {
[Fact] [Fact]
public void Should_Throw_If_Rows_Are_Null() public void Should_Throw_If_String_Rows_Are_Null()
{
// Given
var table = new Table();
// When
var result = Record.Exception(() => table.AddRow((string[])null));
// Then
result.ShouldBeOfType<ArgumentNullException>()
.ParamName.ShouldBe("columns");
}
[Fact]
public void Should_Throw_If_Renderable_Rows_Are_Null()
{ {
// Given // Given
var table = new Table(); var table = new Table();

View File

@ -1,37 +1,32 @@
using System.Text; using System.Text;
using Shouldly; using Shouldly;
using Spectre.Console.Composition; using Spectre.Console.Rendering;
using Xunit; using Xunit;
namespace Spectre.Console.Tests.Unit namespace Spectre.Console.Tests.Unit
{ {
public sealed class TextTests public sealed class TextTests
{
public sealed class Measuring
{ {
[Fact] [Fact]
public void Should_Return_The_Longest_Word_As_Minimum_Width() public void Should_Consider_The_Longest_Word_As_Minimum_Width()
{ {
var text = new Text("Foo Bar Baz\nQux\nLol mobile"); var text = new Text("Foo Bar Baz\nQux\nLol mobile");
var result = text.Measure(new RenderContext(Encoding.Unicode, false), 80); var result = ((IRenderable)text).Measure(new RenderContext(Encoding.Unicode, false), 80);
result.Min.ShouldBe(6); result.Min.ShouldBe(6);
} }
[Fact] [Fact]
public void Should_Return_The_Longest_Line_As_Maximum_Width() public void Should_Consider_The_Longest_Line_As_Maximum_Width()
{ {
var text = new Text("Foo Bar Baz\nQux\nLol mobile"); var text = new Text("Foo Bar Baz\nQux\nLol mobile");
var result = text.Measure(new RenderContext(Encoding.Unicode, false), 80); var result = ((IRenderable)text).Measure(new RenderContext(Encoding.Unicode, false), 80);
result.Max.ShouldBe(11); result.Max.ShouldBe(11);
} }
}
public sealed class Rendering
{
[Fact] [Fact]
public void Should_Render_Unstyled_Text_As_Expected() public void Should_Render_Unstyled_Text_As_Expected()
{ {
@ -83,5 +78,4 @@ namespace Spectre.Console.Tests.Unit
.ShouldBe(expected); .ShouldBe(expected);
} }
} }
}
} }

View File

@ -1,4 +1,4 @@
using Spectre.Console.Composition; using Spectre.Console.Rendering;
namespace Spectre.Console namespace Spectre.Console
{ {

View File

@ -1,27 +0,0 @@
using System;
namespace Spectre.Console
{
/// <summary>
/// Contains extension methods for <see cref="Text"/>.
/// </summary>
public static class TextExtensions
{
/// <summary>
/// Sets the text alignment.
/// </summary>
/// <param name="text">The <see cref="Text"/> instance.</param>
/// <param name="alignment">The text alignment.</param>
/// <returns>The same <see cref="Text"/> instance.</returns>
public static Text WithAlignment(this Text text, Justify alignment)
{
if (text is null)
{
throw new ArgumentNullException(nameof(text));
}
text.Alignment = alignment;
return text;
}
}
}

View File

@ -1,6 +1,6 @@
using System; using System;
using Spectre.Console.Composition;
using Spectre.Console.Internal; using Spectre.Console.Internal;
using Spectre.Console.Rendering;
namespace Spectre.Console namespace Spectre.Console
{ {

View File

@ -0,0 +1,61 @@
namespace Spectre.Console
{
/// <summary>
/// Contains extension methods for <see cref="IAlignable"/>.
/// </summary>
public static class AlignableExtensions
{
/// <summary>
/// Sets the alignment for an <see cref="IAlignable"/> object.
/// </summary>
/// <typeparam name="T">The alignable type.</typeparam>
/// <param name="alignable">The alignable object.</param>
/// <param name="alignment">The alignment.</param>
/// <returns>The same alignable object.</returns>
public static T WithAlignment<T>(this T alignable, Justify alignment)
where T : IAlignable
{
alignable.Alignment = alignment;
return alignable;
}
/// <summary>
/// Sets the <see cref="IAlignable"/> object to be left aligned.
/// </summary>
/// <typeparam name="T">The alignable type.</typeparam>
/// <param name="alignable">The alignable object.</param>
/// <returns>The same alignable object.</returns>
public static T LeftAligned<T>(this T alignable)
where T : IAlignable
{
alignable.Alignment = Justify.Left;
return alignable;
}
/// <summary>
/// Sets the <see cref="IAlignable"/> object to be centered.
/// </summary>
/// <typeparam name="T">The alignable type.</typeparam>
/// <param name="alignable">The alignable object.</param>
/// <returns>The same alignable object.</returns>
public static T Centered<T>(this T alignable)
where T : IAlignable
{
alignable.Alignment = Justify.Center;
return alignable;
}
/// <summary>
/// Sets the <see cref="IAlignable"/> object to be right aligned.
/// </summary>
/// <typeparam name="T">The alignable type.</typeparam>
/// <param name="alignable">The alignable object.</param>
/// <returns>The same alignable object.</returns>
public static T RightAligned<T>(this T alignable)
where T : IAlignable
{
alignable.Alignment = Justify.Right;
return alignable;
}
}
}

View File

@ -3,7 +3,7 @@ using System.Collections.Generic;
using System.Globalization; using System.Globalization;
using System.Linq; using System.Linq;
namespace Spectre.Console.Composition namespace Spectre.Console.Rendering
{ {
/// <summary> /// <summary>
/// Represents a border used by tables. /// Represents a border used by tables.

View File

@ -1,4 +1,4 @@
namespace Spectre.Console.Composition namespace Spectre.Console.Rendering
{ {
/// <summary> /// <summary>
/// Represents the different border parts. /// Represents the different border parts.

View File

@ -1,6 +1,6 @@
using System; using System;
namespace Spectre.Console.Composition namespace Spectre.Console.Rendering
{ {
/// <summary> /// <summary>
/// Represents an old school ASCII border. /// Represents an old school ASCII border.

View File

@ -1,4 +1,4 @@
namespace Spectre.Console.Composition namespace Spectre.Console.Rendering
{ {
/// <summary> /// <summary>
/// Represents an invisible border. /// Represents an invisible border.

View File

@ -1,6 +1,6 @@
using System; using System;
namespace Spectre.Console.Composition namespace Spectre.Console.Rendering
{ {
/// <summary> /// <summary>
/// Represents a rounded border. /// Represents a rounded border.

View File

@ -1,6 +1,6 @@
using System; using System;
namespace Spectre.Console.Composition namespace Spectre.Console.Rendering
{ {
/// <summary> /// <summary>
/// Represents a square border. /// Represents a square border.

View File

@ -1,15 +1,15 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using Spectre.Console.Composition;
using Spectre.Console.Internal; using Spectre.Console.Internal;
using Spectre.Console.Rendering;
namespace Spectre.Console namespace Spectre.Console
{ {
/// <summary> /// <summary>
/// Represents a grid. /// A renderable grid.
/// </summary> /// </summary>
public sealed class Grid : IRenderable public sealed class Grid : Renderable
{ {
private readonly Table _table; private readonly Table _table;
@ -28,13 +28,13 @@ namespace Spectre.Console
} }
/// <inheritdoc/> /// <inheritdoc/>
public Measurement Measure(RenderContext context, int maxWidth) protected override Measurement Measure(RenderContext context, int maxWidth)
{ {
return ((IRenderable)_table).Measure(context, maxWidth); return ((IRenderable)_table).Measure(context, maxWidth);
} }
/// <inheritdoc/> /// <inheritdoc/>
public IEnumerable<Segment> Render(RenderContext context, int width) protected override IEnumerable<Segment> Render(RenderContext context, int width)
{ {
return ((IRenderable)_table).Render(context, width); return ((IRenderable)_table).Render(context, width);
} }
@ -109,8 +109,8 @@ namespace Spectre.Console
/// </summary> /// </summary>
public void AddEmptyRow() public void AddEmptyRow()
{ {
var columns = new string[_table.ColumnCount]; var columns = new IRenderable[_table.ColumnCount];
Enumerable.Range(0, _table.ColumnCount).ForEach(index => columns[index] = string.Empty); Enumerable.Range(0, _table.ColumnCount).ForEach(index => columns[index] = Text.Empty);
AddRow(columns); AddRow(columns);
} }
@ -118,7 +118,7 @@ namespace Spectre.Console
/// Adds a new row to the grid. /// Adds a new row to the grid.
/// </summary> /// </summary>
/// <param name="columns">The columns to add.</param> /// <param name="columns">The columns to add.</param>
public void AddRow(params string[] columns) public void AddRow(params IRenderable[] columns)
{ {
if (columns is null) if (columns is null)
{ {

View File

@ -3,7 +3,7 @@ namespace Spectre.Console
/// <summary> /// <summary>
/// Represents a grid column. /// Represents a grid column.
/// </summary> /// </summary>
public sealed class GridColumn public sealed class GridColumn : IAlignable
{ {
/// <summary> /// <summary>
/// Gets or sets the width of the column. /// Gets or sets the width of the column.

View File

@ -0,0 +1,31 @@
using System;
using System.Linq;
namespace Spectre.Console
{
/// <summary>
/// Contains extension methods for <see cref="Grid"/>.
/// </summary>
public static class GridExtensions
{
/// <summary>
/// Adds a new row to the grid.
/// </summary>
/// <param name="grid">The grid to add the row to.</param>
/// <param name="columns">The columns to add.</param>
public static void AddRow(this Grid grid, params string[] columns)
{
if (grid is null)
{
throw new ArgumentNullException(nameof(grid));
}
if (columns is null)
{
throw new ArgumentNullException(nameof(columns));
}
grid.AddRow(columns.Select(column => new Markup(column)).ToArray());
}
}
}

View File

@ -0,0 +1,13 @@
namespace Spectre.Console
{
/// <summary>
/// Represents something that is alignable.
/// </summary>
public interface IAlignable
{
/// <summary>
/// Gets or sets the alignment.
/// </summary>
Justify? Alignment { get; set; }
}
}

View File

@ -1,6 +1,6 @@
using System.Collections.Generic; using System.Collections.Generic;
namespace Spectre.Console.Composition namespace Spectre.Console.Rendering
{ {
/// <summary> /// <summary>
/// Represents something that can be rendered to the console. /// Represents something that can be rendered to the console.

View File

@ -0,0 +1,43 @@
using System.Collections.Generic;
using Spectre.Console.Internal;
using Spectre.Console.Rendering;
namespace Spectre.Console
{
/// <summary>
/// A renderable piece of markup text.
/// </summary>
public sealed class Markup : Renderable, IAlignable
{
private readonly Text _text;
/// <inheritdoc/>
public Justify? Alignment
{
get => _text.Alignment;
set => _text.Alignment = value;
}
/// <summary>
/// Initializes a new instance of the <see cref="Markup"/> class.
/// </summary>
/// <param name="text">The markup text.</param>
/// <param name="style">The style of the text.</param>
public Markup(string text, Style? style = null)
{
_text = MarkupParser.Parse(text, style);
}
/// <inheritdoc/>
protected override Measurement Measure(RenderContext context, int maxWidth)
{
return ((IRenderable)_text).Measure(context, maxWidth);
}
/// <inheritdoc/>
protected override IEnumerable<Segment> Render(RenderContext context, int maxWidth)
{
return ((IRenderable)_text).Render(context, maxWidth);
}
}
}

View File

@ -1,6 +1,6 @@
using System; using System;
namespace Spectre.Console.Composition namespace Spectre.Console.Rendering
{ {
/// <summary> /// <summary>
/// Represents a measurement. /// Represents a measurement.

View File

@ -1,13 +1,14 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using Spectre.Console.Composition; using Spectre.Console.Rendering;
using SpectreBorder = Spectre.Console.Rendering.Border;
namespace Spectre.Console namespace Spectre.Console
{ {
/// <summary> /// <summary>
/// Represents a panel which contains another renderable item. /// A renderable panel.
/// </summary> /// </summary>
public sealed class Panel : IRenderable public sealed class Panel : Renderable
{ {
private const int EdgeWidth = 2; private const int EdgeWidth = 2;
@ -42,6 +43,15 @@ namespace Spectre.Console
/// </summary> /// </summary>
public Padding Padding { get; set; } = new Padding(1, 1); public Padding Padding { get; set; } = new Padding(1, 1);
/// <summary>
/// Initializes a new instance of the <see cref="Panel"/> class.
/// </summary>
/// <param name="text">The panel content.</param>
public Panel(string text)
: this(new Markup(text))
{
}
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="Panel"/> class. /// Initializes a new instance of the <see cref="Panel"/> class.
/// </summary> /// </summary>
@ -52,23 +62,25 @@ namespace Spectre.Console
} }
/// <inheritdoc/> /// <inheritdoc/>
Measurement IRenderable.Measure(RenderContext context, int maxWidth) protected override Measurement Measure(RenderContext context, int maxWidth)
{ {
var childWidth = _child.Measure(context, maxWidth); var childWidth = _child.Measure(context, maxWidth);
return new Measurement(childWidth.Min + 2 + Padding.GetHorizontalPadding(), childWidth.Max + 2 + Padding.GetHorizontalPadding()); return new Measurement(
childWidth.Min + 2 + Padding.GetHorizontalPadding(),
childWidth.Max + 2 + Padding.GetHorizontalPadding());
} }
/// <inheritdoc/> /// <inheritdoc/>
IEnumerable<Segment> IRenderable.Render(RenderContext context, int width) protected override IEnumerable<Segment> Render(RenderContext context, int maxWidth)
{ {
var border = Composition.Border.GetBorder(Border, (context.LegacyConsole || !context.Unicode) && SafeBorder); var border = SpectreBorder.GetBorder(Border, (context.LegacyConsole || !context.Unicode) && SafeBorder);
var paddingWidth = Padding.GetHorizontalPadding(); var paddingWidth = Padding.GetHorizontalPadding();
var childWidth = width - EdgeWidth - paddingWidth; var childWidth = maxWidth - EdgeWidth - paddingWidth;
if (!Expand) if (!Expand)
{ {
var measurement = _child.Measure(context, width - EdgeWidth - paddingWidth); var measurement = _child.Measure(context, maxWidth - EdgeWidth - paddingWidth);
childWidth = measurement.Max; childWidth = measurement.Max;
} }

View File

@ -1,6 +1,6 @@
using System.Text; using System.Text;
namespace Spectre.Console.Composition namespace Spectre.Console.Rendering
{ {
/// <summary> /// <summary>
/// Represents a render context. /// Represents a render context.

View File

@ -0,0 +1,41 @@
using System.Collections.Generic;
namespace Spectre.Console.Rendering
{
/// <summary>
/// Base class for a renderable object implementing <see cref="IRenderable"/>.
/// </summary>
public abstract class Renderable : IRenderable
{
/// <inheritdoc/>
Measurement IRenderable.Measure(RenderContext context, int maxWidth)
{
return Measure(context, maxWidth);
}
/// <inheritdoc/>
IEnumerable<Segment> IRenderable.Render(RenderContext context, int maxWidth)
{
return Render(context, maxWidth);
}
/// <summary>
/// Measures the renderable object.
/// </summary>
/// <param name="context">The render context.</param>
/// <param name="maxWidth">The maximum allowed width.</param>
/// <returns>The minimum and maximum width of the object.</returns>
protected virtual Measurement Measure(RenderContext context, int maxWidth)
{
return new Measurement(maxWidth, maxWidth);
}
/// <summary>
/// Renders the object.
/// </summary>
/// <param name="context">The render context.</param>
/// <param name="maxWidth">The maximum allowed width.</param>
/// <returns>A collection of segments.</returns>
protected abstract IEnumerable<Segment> Render(RenderContext context, int maxWidth);
}
}

View File

@ -5,7 +5,7 @@ using System.Linq;
using System.Text; using System.Text;
using Spectre.Console.Internal; using Spectre.Console.Internal;
namespace Spectre.Console.Composition namespace Spectre.Console.Rendering
{ {
/// <summary> /// <summary>
/// Represents a renderable segment. /// Represents a renderable segment.

View File

@ -3,7 +3,7 @@ using System.Diagnostics.CodeAnalysis;
using System.Linq; using System.Linq;
using System.Text; using System.Text;
namespace Spectre.Console.Composition namespace Spectre.Console.Rendering
{ {
/// <summary> /// <summary>
/// Represents a collection of segments. /// Represents a collection of segments.

View File

@ -1,7 +1,7 @@
using System.Collections; using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
namespace Spectre.Console.Composition namespace Spectre.Console.Rendering
{ {
internal sealed class SegmentLineEnumerator : IEnumerable<Segment> internal sealed class SegmentLineEnumerator : IEnumerable<Segment>
{ {

View File

@ -1,7 +1,7 @@
using System.Collections; using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
namespace Spectre.Console.Composition namespace Spectre.Console.Rendering
{ {
internal sealed class SegmentLineIterator : IEnumerator<Segment> internal sealed class SegmentLineIterator : IEnumerator<Segment>
{ {

View File

@ -1,8 +1,8 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using Spectre.Console.Composition;
using Spectre.Console.Internal; using Spectre.Console.Internal;
using Spectre.Console.Rendering;
namespace Spectre.Console namespace Spectre.Console
{ {
@ -100,13 +100,13 @@ namespace Spectre.Console
var maxWidths = new List<int>(); var maxWidths = new List<int>();
// Include columns in measurement // Include columns in measurement
var measure = ((IRenderable)column.Text).Measure(options, maxWidth); var measure = column.Text.Measure(options, maxWidth);
minWidths.Add(measure.Min); minWidths.Add(measure.Min);
maxWidths.Add(measure.Max); maxWidths.Add(measure.Max);
foreach (var row in rows) foreach (var row in rows)
{ {
measure = ((IRenderable)row).Measure(options, maxWidth); measure = row.Measure(options, maxWidth);
minWidths.Add(measure.Min); minWidths.Add(measure.Min);
maxWidths.Add(measure.Max); maxWidths.Add(measure.Max);
} }

View File

@ -1,18 +1,19 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using Spectre.Console.Composition;
using Spectre.Console.Internal; using Spectre.Console.Internal;
using Spectre.Console.Rendering;
using SpectreBorder = Spectre.Console.Rendering.Border;
namespace Spectre.Console namespace Spectre.Console
{ {
/// <summary> /// <summary>
/// Represents a table. /// A renderable table.
/// </summary> /// </summary>
public sealed partial class Table : IRenderable public sealed partial class Table : Renderable
{ {
private readonly List<TableColumn> _columns; private readonly List<TableColumn> _columns;
private readonly List<List<Text>> _rows; private readonly List<List<IRenderable>> _rows;
/// <summary> /// <summary>
/// Gets the number of columns in the table. /// Gets the number of columns in the table.
@ -67,21 +68,7 @@ namespace Spectre.Console
public Table() public Table()
{ {
_columns = new List<TableColumn>(); _columns = new List<TableColumn>();
_rows = new List<List<Text>>(); _rows = new List<List<IRenderable>>();
}
/// <summary>
/// Adds a column to the table.
/// </summary>
/// <param name="column">The column to add.</param>
public void AddColumn(string column)
{
if (column is null)
{
throw new ArgumentNullException(nameof(column));
}
AddColumn(new TableColumn(column));
} }
/// <summary> /// <summary>
@ -103,23 +90,6 @@ namespace Spectre.Console
_columns.Add(column); _columns.Add(column);
} }
/// <summary>
/// Adds multiple columns to the table.
/// </summary>
/// <param name="columns">The columns to add.</param>
public void AddColumns(params string[] columns)
{
if (columns is null)
{
throw new ArgumentNullException(nameof(columns));
}
foreach (var column in columns)
{
AddColumn(column);
}
}
/// <summary> /// <summary>
/// Adds multiple columns to the table. /// Adds multiple columns to the table.
/// </summary> /// </summary>
@ -142,8 +112,8 @@ namespace Spectre.Console
/// </summary> /// </summary>
public void AddEmptyRow() public void AddEmptyRow()
{ {
var columns = new string[ColumnCount]; var columns = new IRenderable[ColumnCount];
Enumerable.Range(0, ColumnCount).ForEach(index => columns[index] = string.Empty); Enumerable.Range(0, ColumnCount).ForEach(index => columns[index] = Text.Empty);
AddRow(columns); AddRow(columns);
} }
@ -151,7 +121,7 @@ namespace Spectre.Console
/// Adds a row to the table. /// Adds a row to the table.
/// </summary> /// </summary>
/// <param name="columns">The row columns to add.</param> /// <param name="columns">The row columns to add.</param>
public void AddRow(params string[] columns) public void AddRow(params IRenderable[] columns)
{ {
if (columns is null) if (columns is null)
{ {
@ -168,11 +138,11 @@ namespace Spectre.Console
throw new InvalidOperationException("The number of row columns are greater than the number of table columns."); throw new InvalidOperationException("The number of row columns are greater than the number of table columns.");
} }
_rows.Add(columns.Select(column => Text.Markup(column)).ToList()); _rows.Add(columns.ToList());
} }
/// <inheritdoc/> /// <inheritdoc/>
Measurement IRenderable.Measure(RenderContext context, int maxWidth) protected override Measurement Measure(RenderContext context, int maxWidth)
{ {
if (context is null) if (context is null)
{ {
@ -194,20 +164,20 @@ namespace Spectre.Console
} }
/// <inheritdoc/> /// <inheritdoc/>
IEnumerable<Segment> IRenderable.Render(RenderContext context, int width) protected override IEnumerable<Segment> Render(RenderContext context, int maxWidth)
{ {
if (context is null) if (context is null)
{ {
throw new ArgumentNullException(nameof(context)); throw new ArgumentNullException(nameof(context));
} }
var border = Composition.Border.GetBorder(Border, (context.LegacyConsole || !context.Unicode) && SafeBorder); var border = SpectreBorder.GetBorder(Border, (context.LegacyConsole || !context.Unicode) && SafeBorder);
var tableWidth = maxWidth;
var showBorder = Border != BorderKind.None; var showBorder = Border != BorderKind.None;
var hideBorder = Border == BorderKind.None; var hideBorder = Border == BorderKind.None;
var hasRows = _rows.Count > 0; var hasRows = _rows.Count > 0;
var maxWidth = width;
if (Width != null) if (Width != null)
{ {
maxWidth = Math.Min(Width.Value, maxWidth); maxWidth = Math.Min(Width.Value, maxWidth);
@ -219,13 +189,13 @@ namespace Spectre.Console
var columnWidths = CalculateColumnWidths(context, maxWidth); var columnWidths = CalculateColumnWidths(context, maxWidth);
// Update the table width. // Update the table width.
width = columnWidths.Sum() + GetExtraWidth(includePadding: true); tableWidth = columnWidths.Sum() + GetExtraWidth(includePadding: true);
var rows = new List<List<Text>>(); var rows = new List<List<IRenderable>>();
if (ShowHeaders) if (ShowHeaders)
{ {
// Add columns to top of rows // Add columns to top of rows
rows.Add(new List<Text>(_columns.Select(c => c.Text))); rows.Add(new List<IRenderable>(_columns.Select(c => c.Text)));
} }
// Add rows. // Add rows.
@ -244,7 +214,7 @@ namespace Spectre.Console
var justification = _columns[columnIndex].Alignment; var justification = _columns[columnIndex].Alignment;
var childContext = context.WithJustification(justification); var childContext = context.WithJustification(justification);
var lines = Segment.SplitLines(((IRenderable)cell).Render(childContext, rowWidth)); var lines = Segment.SplitLines(cell.Render(childContext, rowWidth));
cellHeight = Math.Max(cellHeight, lines.Count); cellHeight = Math.Max(cellHeight, lines.Count);
cells.Add(lines); cells.Add(lines);
} }

View File

@ -1,16 +1,17 @@
using System; using System;
using Spectre.Console.Rendering;
namespace Spectre.Console namespace Spectre.Console
{ {
/// <summary> /// <summary>
/// Represents a table column. /// Represents a table column.
/// </summary> /// </summary>
public sealed class TableColumn public sealed class TableColumn : IAlignable
{ {
/// <summary> /// <summary>
/// Gets the text associated with the column. /// Gets the text associated with the column.
/// </summary> /// </summary>
public Text Text { get; } public IRenderable Text { get; }
/// <summary> /// <summary>
/// Gets or sets the width of the column. /// Gets or sets the width of the column.
@ -39,8 +40,17 @@ namespace Spectre.Console
/// </summary> /// </summary>
/// <param name="text">The table column text.</param> /// <param name="text">The table column text.</param>
public TableColumn(string text) public TableColumn(string text)
: this(new Markup(text))
{ {
Text = Text.Markup(text ?? throw new ArgumentNullException(nameof(text))); }
/// <summary>
/// Initializes a new instance of the <see cref="TableColumn"/> class.
/// </summary>
/// <param name="renderable">The <see cref="IRenderable"/> instance to use as the table column.</param>
public TableColumn(IRenderable renderable)
{
Text = renderable ?? throw new ArgumentNullException(nameof(renderable));
Width = null; Width = null;
Padding = new Padding(1, 1); Padding = new Padding(1, 1);
NoWrap = false; NoWrap = false;

View File

@ -0,0 +1,78 @@
using System;
using System.Linq;
namespace Spectre.Console
{
/// <summary>
/// Contains extension methods for <see cref="Table"/>.
/// </summary>
public static class TableExtensions
{
/// <summary>
/// Adds a column to the table.
/// </summary>
/// <param name="table">The table to add the column to.</param>
/// <param name="column">The column to add.</param>
/// <returns>The added <see cref="TableColumn"/> instance.</returns>
public static TableColumn AddColumn(this Table table, string column)
{
if (table is null)
{
throw new ArgumentNullException(nameof(table));
}
if (column is null)
{
throw new ArgumentNullException(nameof(column));
}
var tableColumn = new TableColumn(column);
table.AddColumn(tableColumn);
return tableColumn;
}
/// <summary>
/// Adds multiple columns to the table.
/// </summary>
/// <param name="table">The table to add the columns to.</param>
/// <param name="columns">The columns to add.</param>
public static void AddColumns(this Table table, params string[] columns)
{
if (table is null)
{
throw new ArgumentNullException(nameof(table));
}
if (columns is null)
{
throw new ArgumentNullException(nameof(columns));
}
foreach (var column in columns)
{
AddColumn(table, column);
}
}
/// <summary>
/// Adds a row to the table.
/// </summary>
/// <param name="table">The table to add the row to.</param>
/// <param name="columns">The row columns to add.</param>
public static void AddRow(this Table table, params string[] columns)
{
if (table is null)
{
throw new ArgumentNullException(nameof(table));
}
if (columns is null)
{
throw new ArgumentNullException(nameof(columns));
}
table.AddRow(columns.Select(column => new Markup(column)).ToArray());
}
}
}

View File

@ -3,24 +3,29 @@ using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
using System.Linq; using System.Linq;
using Spectre.Console.Composition;
using Spectre.Console.Internal; using Spectre.Console.Internal;
using Spectre.Console.Rendering;
namespace Spectre.Console namespace Spectre.Console
{ {
/// <summary> /// <summary>
/// Represents a piece of text. /// A renderable piece of text.
/// </summary> /// </summary>
[DebuggerDisplay("{_text,nq}")] [DebuggerDisplay("{_text,nq}")]
[SuppressMessage("Naming", "CA1724:Type names should not match namespaces")] [SuppressMessage("Naming", "CA1724:Type names should not match namespaces")]
public sealed class Text : IRenderable public sealed class Text : Renderable, IAlignable
{ {
private readonly List<SegmentLine> _lines; private readonly List<SegmentLine> _lines;
/// <summary>
/// Gets an empty <see cref="Text"/> instance.
/// </summary>
public static Text Empty { get; } = new Text(string.Empty);
/// <summary> /// <summary>
/// Gets or sets the text alignment. /// Gets or sets the text alignment.
/// </summary> /// </summary>
public Justify Alignment { get; set; } = Justify.Left; public Justify? Alignment { get; set; }
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="Text"/> class. /// Initializes a new instance of the <see cref="Text"/> class.
@ -60,7 +65,7 @@ namespace Spectre.Console
} }
/// <inheritdoc/> /// <inheritdoc/>
public Measurement Measure(RenderContext context, int maxWidth) protected override Measurement Measure(RenderContext context, int maxWidth)
{ {
if (_lines.Count == 0) if (_lines.Count == 0)
{ {
@ -74,7 +79,7 @@ namespace Spectre.Console
} }
/// <inheritdoc/> /// <inheritdoc/>
public IEnumerable<Segment> Render(RenderContext context, int maxWidth) protected override IEnumerable<Segment> Render(RenderContext context, int maxWidth)
{ {
if (context is null) if (context is null)
{ {
@ -89,7 +94,7 @@ namespace Spectre.Console
var lines = SplitLines(context, maxWidth); var lines = SplitLines(context, maxWidth);
// Justify lines // Justify lines
var justification = context.Justification ?? Alignment; var justification = context.Justification ?? Alignment ?? Justify.Left;
foreach (var (_, _, last, line) in lines.Enumerate()) foreach (var (_, _, last, line) in lines.Enumerate())
{ {
var length = line.Sum(l => l.StripLineEndings().CellLength(context.Encoding)); var length = line.Sum(l => l.StripLineEndings().CellLength(context.Encoding));