Add row and column accessors for tables and grids

This commit is contained in:
Patrik Svensson 2020-10-26 11:51:35 +01:00 committed by Patrik Svensson
parent 3e5e22d6c2
commit e7f497050c
30 changed files with 511 additions and 162 deletions

View File

@ -140,6 +140,11 @@
@foreach (IDocument document in OutputPages.GetChildrenOf(root).OnlyVisible()) @foreach (IDocument document in OutputPages.GetChildrenOf(root).OnlyVisible())
{ {
if(string.IsNullOrWhiteSpace(document.GetTitle()))
{
continue;
}
DocumentList<IDocument> documentChildren = OutputPages.GetChildrenOf(document); DocumentList<IDocument> documentChildren = OutputPages.GetChildrenOf(document);
<div class="sidebar-nav-item @(Document.IdEquals(document) ? "active" : null) @(documentChildren.Any() ? "has-children" : null)"> <div class="sidebar-nav-item @(Document.IdEquals(document) ? "active" : null) @(documentChildren.Any() ? "has-children" : null)">
@if(document.ShowLink()) @if(document.ShowLink())

View File

@ -1,4 +1,4 @@
Title: Calendar Title: Calendar
Order: 4 Order: 4
RedirectFrom: calendar RedirectFrom: calendar
--- ---
@ -58,7 +58,7 @@ You can hide the calendar header.
```csharp ```csharp
var calendar = new Calendar(2020,10); var calendar = new Calendar(2020,10);
calendar.ShowHeader = false; calendar.ShowHeader();
AnsiConsole.Render(calendar); AnsiConsole.Render(calendar);
``` ```

View File

@ -91,33 +91,33 @@ table.Width(50);
## Alignment ## Alignment
```csharp ```csharp
column.Alignment(Justify.Right); table.Columns[0].Alignment(Justify.Right);
column.LeftAligned(); table.Columns[0].LeftAligned();
column.Centered(); table.Columns[0].Centered();
column.RightAligned(); table.Columns[0].RightAligned();
``` ```
## Padding ## Padding
```csharp ```csharp
// Set left and right padding // Set left and right padding
column.Padding(left: 3, right: 5); table.Columns[0].Padding(left: 3, right: 5);
// Set padding individually. // Set padding individually
column.PadLeft(3); table.Columns[0].PadLeft(3);
column.PadRight(5); table.Columns[0].PadRight(5);
``` ```
## Disable column wrapping ## Disable column wrapping
```csharp ```csharp
// Disable column wrapping // Disable column wrapping
column.NoWrap(); table.Columns[0].NoWrap();
``` ```
## Set column width ## Set column width
```csharp ```csharp
// Set the column width (no fluent extension method for this yet) // Set the column width
column.Width = 15; table.Columns[0].Width(15);
``` ```

View File

@ -1,4 +1,3 @@
using System.Diagnostics;
using Spectre.Console; using Spectre.Console;
using Spectre.Console.Rendering; using Spectre.Console.Rendering;

View File

@ -53,7 +53,7 @@ namespace Spectre.Console.Tests.Unit
grid.AddRow("Foo"); grid.AddRow("Foo");
// Then // Then
grid.RowCount.ShouldBe(1); grid.Rows.Count.ShouldBe(1);
} }
[Fact] [Fact]

View File

@ -98,7 +98,7 @@ namespace Spectre.Console.Tests.Unit
table.AddRow("Foo"); table.AddRow("Foo");
// Then // Then
table.RowCount.ShouldBe(1); table.Rows.Count.ShouldBe(1);
} }
[Fact] [Fact]

View File

@ -96,5 +96,37 @@ namespace Spectre.Console
calendar.HeaderStyle = style ?? Style.Plain; calendar.HeaderStyle = style ?? Style.Plain;
return calendar; return calendar;
} }
/// <summary>
/// Shows the calendar header.
/// </summary>
/// <param name="calendar">The calendar.</param>
/// <returns>The same instance so that multiple calls can be chained.</returns>
public static Calendar ShowHeader(this Calendar calendar)
{
if (calendar is null)
{
throw new ArgumentNullException(nameof(calendar));
}
calendar.ShowHeader = true;
return calendar;
}
/// <summary>
/// Hides the calendar header.
/// </summary>
/// <param name="calendar">The calendar.</param>
/// <returns>The same instance so that multiple calls can be chained.</returns>
public static Calendar HideHeader(this Calendar calendar)
{
if (calendar is null)
{
throw new ArgumentNullException(nameof(calendar));
}
calendar.ShowHeader = false;
return calendar;
}
} }
} }

View File

@ -24,5 +24,24 @@ namespace Spectre.Console
obj.NoWrap = true; obj.NoWrap = true;
return obj; return obj;
} }
/// <summary>
/// Sets the width of the column.
/// </summary>
/// <typeparam name="T">An object implementing <see cref="IColumn"/>.</typeparam>
/// <param name="obj">The column.</param>
/// <param name="width">The column width.</param>
/// <returns>The same instance so that multiple calls can be chained.</returns>
public static T Width<T>(this T obj, int? width)
where T : class, IColumn
{
if (obj is null)
{
throw new ArgumentNullException(nameof(obj));
}
obj.Width = width;
return obj;
}
} }
} }

View File

@ -69,8 +69,8 @@ namespace Spectre.Console
throw new ArgumentNullException(nameof(grid)); throw new ArgumentNullException(nameof(grid));
} }
var columns = new IRenderable[grid.ColumnCount]; var columns = new IRenderable[grid.Columns.Count];
Enumerable.Range(0, grid.ColumnCount).ForEach(index => columns[index] = Text.Empty); Enumerable.Range(0, grid.Columns.Count).ForEach(index => columns[index] = Text.Empty);
grid.AddRow(columns); grid.AddRow(columns);
return grid; return grid;

View File

@ -22,7 +22,7 @@ namespace Spectre.Console
throw new ArgumentNullException(nameof(obj)); throw new ArgumentNullException(nameof(obj));
} }
return Padding(obj, new Padding(left, obj.Padding.Top, obj.Padding.Right, obj.Padding.Bottom)); return Padding(obj, new Padding(left, obj.Padding.GetTopSafe(), obj.Padding.GetRightSafe(), obj.Padding.GetBottomSafe()));
} }
/// <summary> /// <summary>
@ -40,7 +40,7 @@ namespace Spectre.Console
throw new ArgumentNullException(nameof(obj)); throw new ArgumentNullException(nameof(obj));
} }
return Padding(obj, new Padding(obj.Padding.Left, top, obj.Padding.Right, obj.Padding.Bottom)); return Padding(obj, new Padding(obj.Padding.GetLeftSafe(), top, obj.Padding.GetRightSafe(), obj.Padding.GetBottomSafe()));
} }
/// <summary> /// <summary>
@ -58,7 +58,7 @@ namespace Spectre.Console
throw new ArgumentNullException(nameof(obj)); throw new ArgumentNullException(nameof(obj));
} }
return Padding(obj, new Padding(obj.Padding.Left, obj.Padding.Top, right, obj.Padding.Bottom)); return Padding(obj, new Padding(obj.Padding.GetLeftSafe(), obj.Padding.GetTopSafe(), right, obj.Padding.GetBottomSafe()));
} }
/// <summary> /// <summary>
@ -76,7 +76,7 @@ namespace Spectre.Console
throw new ArgumentNullException(nameof(obj)); throw new ArgumentNullException(nameof(obj));
} }
return Padding(obj, new Padding(obj.Padding.Left, obj.Padding.Top, obj.Padding.Right, bottom)); return Padding(obj, new Padding(obj.Padding.GetLeftSafe(), obj.Padding.GetTopSafe(), obj.Padding.GetRightSafe(), bottom));
} }
/// <summary> /// <summary>

View File

@ -0,0 +1,48 @@
namespace Spectre.Console
{
/// <summary>
/// Contains extension methods for <see cref="Padding"/>.
/// </summary>
public static class PaddingExtensions
{
/// <summary>
/// Gets the left padding.
/// </summary>
/// <param name="padding">The padding.</param>
/// <returns>The left padding or zero if <c>padding</c> is null.</returns>
public static int GetLeftSafe(this Padding? padding)
{
return padding?.Left ?? 0;
}
/// <summary>
/// Gets the right padding.
/// </summary>
/// <param name="padding">The padding.</param>
/// <returns>The right padding or zero if <c>padding</c> is null.</returns>
public static int GetRightSafe(this Padding? padding)
{
return padding?.Right ?? 0;
}
/// <summary>
/// Gets the top padding.
/// </summary>
/// <param name="padding">The padding.</param>
/// <returns>The top padding or zero if <c>padding</c> is null.</returns>
public static int GetTopSafe(this Padding? padding)
{
return padding?.Top ?? 0;
}
/// <summary>
/// Gets the bottom padding.
/// </summary>
/// <param name="padding">The padding.</param>
/// <returns>The bottom padding or zero if <c>padding</c> is null.</returns>
public static int GetBottomSafe(this Padding? padding)
{
return padding?.Bottom ?? 0;
}
}
}

View File

@ -36,6 +36,22 @@ namespace Spectre.Console
return table; return table;
} }
/// <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>
/// <returns>The same instance so that multiple calls can be chained.</returns>
public static Table AddRow(this Table table, params IRenderable[] columns)
{
if (table is null)
{
throw new ArgumentNullException(nameof(table));
}
return table.AddRow(columns);
}
/// <summary> /// <summary>
/// Adds an empty row to the table. /// Adds an empty row to the table.
/// </summary> /// </summary>
@ -48,8 +64,8 @@ namespace Spectre.Console
throw new ArgumentNullException(nameof(table)); throw new ArgumentNullException(nameof(table));
} }
var columns = new IRenderable[table.ColumnCount]; var columns = new IRenderable[table.Columns.Count];
Enumerable.Range(0, table.ColumnCount).ForEach(index => columns[index] = Text.Empty); Enumerable.Range(0, table.Columns.Count).ForEach(index => columns[index] = Text.Empty);
table.AddRow(columns); table.AddRow(columns);
return table; return table;
} }

View File

@ -10,5 +10,10 @@ namespace Spectre.Console
/// or not wrapping should be prevented. /// or not wrapping should be prevented.
/// </summary> /// </summary>
bool NoWrap { get; set; } bool NoWrap { get; set; }
/// <summary>
/// Gets or sets the width of the column.
/// </summary>
int? Width { get; set; }
} }
} }

View File

@ -8,6 +8,6 @@ namespace Spectre.Console
/// <summary> /// <summary>
/// Gets or sets the padding. /// Gets or sets the padding.
/// </summary> /// </summary>
public Padding Padding { get; set; } public Padding? Padding { get; set; }
} }
} }

View File

@ -4,7 +4,7 @@ using System.Collections.Generic;
namespace Spectre.Console.Internal.Collections namespace Spectre.Console.Internal.Collections
{ {
internal sealed class ListWithCallback<T> : IList<T> internal sealed class ListWithCallback<T> : IList<T>, IReadOnlyList<T>
{ {
private readonly List<T> _list; private readonly List<T> _list;
private readonly Action _callback; private readonly Action _callback;

View File

@ -6,6 +6,21 @@ namespace Spectre.Console.Internal
{ {
internal static class EnumerableExtensions internal static class EnumerableExtensions
{ {
public static int GetCount<T>(this IEnumerable<T> source)
{
if (source is IList<T> list)
{
return list.Count;
}
if (source is T[] array)
{
return array.Length;
}
return source.Count();
}
public static void ForEach<T>(this IEnumerable<T> source, Action<T> action) public static void ForEach<T>(this IEnumerable<T> source, Action<T> action)
{ {
foreach (var item in source) foreach (var item in source)

View File

@ -63,10 +63,10 @@ namespace Spectre.Console.Rendering
{ {
var padding = columns[columnIndex].Padding; var padding = columns[columnIndex].Padding;
if (padding.Left > 0) if (padding != null && padding.Value.Left > 0)
{ {
// Left padding // Left padding
builder.Append(" ".Repeat(padding.Left)); builder.Append(" ".Repeat(padding.Value.Left));
} }
var justification = columns[columnIndex].Alignment; var justification = columns[columnIndex].Alignment;
@ -96,9 +96,9 @@ namespace Spectre.Console.Rendering
} }
// Right padding // Right padding
if (padding.Right > 0) if (padding != null && padding.Value.Right > 0)
{ {
builder.Append(" ".Repeat(padding.Right)); builder.Append(" ".Repeat(padding.Value.Right));
} }
if (!lastColumn) if (!lastColumn)

View File

@ -0,0 +1,13 @@
namespace Spectre.Console.Rendering
{
/// <summary>
/// Represents something that can be dirty.
/// </summary>
public interface IHasDirtyState
{
/// <summary>
/// Gets a value indicating whether the object is dirty.
/// </summary>
bool IsDirty { get; }
}
}

View File

@ -0,0 +1,79 @@
using System;
using System.Collections.Generic;
namespace Spectre.Console.Rendering
{
/// <summary>
/// Represents something renderable that's reconstructed
/// when it's state change in any way.
/// </summary>
public abstract class JustInTimeRenderable : Renderable
{
private bool _dirty;
private IRenderable? _rendered;
/// <inheritdoc/>
protected sealed override Measurement Measure(RenderContext context, int maxWidth)
{
return GetInner().Measure(context, maxWidth);
}
/// <inheritdoc/>
protected sealed override IEnumerable<Segment> Render(RenderContext context, int width)
{
return GetInner().Render(context, width);
}
/// <summary>
/// Builds the inner renderable.
/// </summary>
/// <returns>A new inner renderable.</returns>
protected abstract IRenderable Build();
/// <summary>
/// Checks if there are any children that has changed.
/// If so, the underlying renderable needs rebuilding.
/// </summary>
/// <returns><c>true</c> if the object needs rebuilding, otherwise <c>false</c>.</returns>
protected virtual bool HasDirtyChildren()
{
return false;
}
/// <summary>
/// Marks this instance as dirty.
/// </summary>
protected void MarkAsDirty()
{
_dirty = true;
}
/// <summary>
/// Marks this instance as dirty.
/// </summary>
/// <param name="action">
/// The action to execute before marking the instance as dirty.
/// </param>
protected void MarkAsDirty(Action action)
{
if (action is null)
{
throw new ArgumentNullException(nameof(action));
}
action();
_dirty = true;
}
private IRenderable GetInner()
{
if (_dirty || HasDirtyChildren() || _rendered == null)
{
_rendered = Build();
_dirty = false;
}
return _rendered;
}
}
}

View File

@ -55,7 +55,7 @@ namespace Spectre.Console
foreach (var (columnIndex, _, lastColumn, columnWidth) in widths.Enumerate()) foreach (var (columnIndex, _, lastColumn, columnWidth) in widths.Enumerate())
{ {
var padding = columns[columnIndex].Padding; var padding = columns[columnIndex].Padding;
var centerWidth = padding.Left + columnWidth + padding.Right; var centerWidth = padding.GetLeftSafe() + columnWidth + padding.GetRightSafe();
builder.Append(center.Repeat(centerWidth)); builder.Append(center.Repeat(centerWidth));
if (!lastColumn) if (!lastColumn)

View File

@ -11,7 +11,7 @@ namespace Spectre.Console
/// <summary> /// <summary>
/// A renderable calendar. /// A renderable calendar.
/// </summary> /// </summary>
public sealed class Calendar : Renderable, IHasCulture, IHasTableBorder, IAlignable public sealed class Calendar : JustInTimeRenderable, IHasCulture, IHasTableBorder, IAlignable
{ {
private const int NumberOfWeekDays = 7; private const int NumberOfWeekDays = 7;
private const int ExpectedRowCount = 6; private const int ExpectedRowCount = 6;
@ -21,11 +21,9 @@ namespace Spectre.Console
private int _year; private int _year;
private int _month; private int _month;
private int _day; private int _day;
private IRenderable? _table;
private TableBorder _border; private TableBorder _border;
private bool _useSafeBorder; private bool _useSafeBorder;
private Style? _borderStyle; private Style? _borderStyle;
private bool _dirty;
private CultureInfo _culture; private CultureInfo _culture;
private Style _highlightStyle; private Style _highlightStyle;
private bool _showHeader; private bool _showHeader;
@ -158,43 +156,17 @@ namespace Spectre.Console
_year = year; _year = year;
_month = month; _month = month;
_day = day; _day = day;
_table = null;
_border = TableBorder.Square; _border = TableBorder.Square;
_useSafeBorder = true; _useSafeBorder = true;
_borderStyle = null; _borderStyle = null;
_dirty = true;
_culture = CultureInfo.InvariantCulture; _culture = CultureInfo.InvariantCulture;
_highlightStyle = new Style(foreground: Color.Blue); _highlightStyle = new Style(foreground: Color.Blue);
_showHeader = true; _showHeader = true;
_calendarEvents = new ListWithCallback<CalendarEvent>(() => _dirty = true); _calendarEvents = new ListWithCallback<CalendarEvent>(() => MarkAsDirty());
} }
/// <inheritdoc/> /// <inheritdoc/>
protected override Measurement Measure(RenderContext context, int maxWidth) protected override IRenderable Build()
{
var table = GetTable();
return table.Measure(context, maxWidth);
}
/// <inheritdoc/>
protected override IEnumerable<Segment> Render(RenderContext context, int maxWidth)
{
return GetTable().Render(context, maxWidth);
}
private IRenderable GetTable()
{
// Table needs to be built?
if (_dirty || _table == null)
{
_table = BuildTable();
_dirty = false;
}
return _table;
}
private IRenderable BuildTable()
{ {
var culture = Culture ?? CultureInfo.InvariantCulture; var culture = Culture ?? CultureInfo.InvariantCulture;
@ -264,9 +236,9 @@ namespace Spectre.Console
} }
// We want all calendars to have the same height. // We want all calendars to have the same height.
if (table.RowCount < ExpectedRowCount) if (table.Rows.Count < ExpectedRowCount)
{ {
var diff = Math.Max(0, ExpectedRowCount - table.RowCount); var diff = Math.Max(0, ExpectedRowCount - table.Rows.Count);
for (var i = 0; i < diff; i++) for (var i = 0; i < diff; i++)
{ {
table.AddEmptyRow(); table.AddEmptyRow();
@ -276,12 +248,6 @@ namespace Spectre.Console
return table; return table;
} }
private void MarkAsDirty(Action action)
{
action();
_dirty = true;
}
private DayOfWeek[] GetWeekdays() private DayOfWeek[] GetWeekdays()
{ {
var culture = Culture ?? CultureInfo.InvariantCulture; var culture = Culture ?? CultureInfo.InvariantCulture;

View File

@ -13,7 +13,7 @@ namespace Spectre.Console
private readonly List<IRenderable> _items; private readonly List<IRenderable> _items;
/// <inheritdoc/> /// <inheritdoc/>
public Padding Padding { get; set; } = new Padding(0, 0, 1, 0); public Padding? Padding { get; set; } = new Padding(0, 0, 1, 0);
/// <summary> /// <summary>
/// Gets or sets a value indicating whether or not the columns should /// Gets or sets a value indicating whether or not the columns should
@ -62,7 +62,7 @@ namespace Spectre.Console
/// <inheritdoc/> /// <inheritdoc/>
protected override Measurement Measure(RenderContext context, int maxWidth) protected override Measurement Measure(RenderContext context, int maxWidth)
{ {
var maxPadding = Math.Max(Padding.Left, Padding.Right); var maxPadding = Math.Max(Padding.GetLeftSafe(), Padding.GetRightSafe());
var itemWidths = _items.Select(item => item.Measure(context, maxWidth).Max).ToArray(); var itemWidths = _items.Select(item => item.Measure(context, maxWidth).Max).ToArray();
var columnCount = CalculateColumnCount(maxWidth, itemWidths, _items.Count, maxPadding); var columnCount = CalculateColumnCount(maxWidth, itemWidths, _items.Count, maxPadding);
@ -90,7 +90,7 @@ namespace Spectre.Console
/// <inheritdoc/> /// <inheritdoc/>
protected override IEnumerable<Segment> Render(RenderContext context, int maxWidth) protected override IEnumerable<Segment> Render(RenderContext context, int maxWidth)
{ {
var maxPadding = Math.Max(Padding.Left, Padding.Right); var maxPadding = Math.Max(Padding.GetLeftSafe(), Padding.GetRightSafe());
var itemWidths = _items.Select(item => item.Measure(context, maxWidth).Max).ToArray(); var itemWidths = _items.Select(item => item.Measure(context, maxWidth).Max).ToArray();
var columnCount = CalculateColumnCount(maxWidth, itemWidths, _items.Count, maxPadding); var columnCount = CalculateColumnCount(maxWidth, itemWidths, _items.Count, maxPadding);

View File

@ -1,5 +1,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using Spectre.Console.Internal.Collections;
using Spectre.Console.Rendering; using Spectre.Console.Rendering;
namespace Spectre.Console namespace Spectre.Console
@ -7,32 +9,37 @@ namespace Spectre.Console
/// <summary> /// <summary>
/// A renderable grid. /// A renderable grid.
/// </summary> /// </summary>
public sealed class Grid : Renderable, IExpandable, IAlignable public sealed class Grid : JustInTimeRenderable, IExpandable, IAlignable
{ {
private readonly Table _table; private readonly ListWithCallback<GridColumn> _columns;
private readonly ListWithCallback<GridRow> _rows;
private bool _expand;
private Justify? _alignment;
private bool _padRightCell;
/// <summary> /// <summary>
/// Gets the number of columns in the table. /// Gets the grid columns.
/// </summary> /// </summary>
public int ColumnCount => _table.ColumnCount; public IReadOnlyList<GridColumn> Columns => _columns;
/// <summary> /// <summary>
/// Gets the number of rows in the table. /// Gets the grid rows.
/// </summary> /// </summary>
public int RowCount => _table.RowCount; public IReadOnlyList<GridRow> Rows => _rows;
/// <inheritdoc/> /// <inheritdoc/>
public bool Expand public bool Expand
{ {
get => _table.Expand; get => _expand;
set => _table.Expand = value; set => MarkAsDirty(() => _expand = value);
} }
/// <inheritdoc/> /// <inheritdoc/>
public Justify? Alignment public Justify? Alignment
{ {
get => _table.Alignment; get => _alignment;
set => _table.Alignment = value; set => MarkAsDirty(() => _alignment = value);
} }
/// <summary> /// <summary>
@ -40,25 +47,10 @@ namespace Spectre.Console
/// </summary> /// </summary>
public Grid() public Grid()
{ {
_table = new Table _expand = false;
{ _alignment = null;
Border = TableBorder.None, _columns = new ListWithCallback<GridColumn>(() => MarkAsDirty());
ShowHeaders = false, _rows = new ListWithCallback<GridRow>(() => MarkAsDirty());
IsGrid = true,
PadRightCell = false,
};
}
/// <inheritdoc/>
protected override Measurement Measure(RenderContext context, int maxWidth)
{
return ((IRenderable)_table).Measure(context, maxWidth);
}
/// <inheritdoc/>
protected override IEnumerable<Segment> Render(RenderContext context, int width)
{
return ((IRenderable)_table).Render(context, width);
} }
/// <summary> /// <summary>
@ -83,21 +75,15 @@ namespace Spectre.Console
throw new ArgumentNullException(nameof(column)); throw new ArgumentNullException(nameof(column));
} }
if (_table.RowCount > 0) if (_rows.Count > 0)
{ {
throw new InvalidOperationException("Cannot add new columns to grid with existing rows."); throw new InvalidOperationException("Cannot add new columns to grid with existing rows.");
} }
// Only pad the most right cell if we've explicitly set a padding. // Only pad the most right cell if we've explicitly set a padding.
_table.PadRightCell = column.HasExplicitPadding; _padRightCell = column.HasExplicitPadding;
_table.AddColumn(new TableColumn(string.Empty) _columns.Add(column);
{
Width = column.Width,
NoWrap = column.NoWrap,
Padding = column.Padding,
Alignment = column.Alignment,
});
return this; return this;
} }
@ -114,13 +100,49 @@ namespace Spectre.Console
throw new ArgumentNullException(nameof(columns)); throw new ArgumentNullException(nameof(columns));
} }
if (columns.Length > _table.ColumnCount) if (columns.Length > _columns.Count)
{ {
throw new InvalidOperationException("The number of row columns are greater than the number of grid columns."); throw new InvalidOperationException("The number of row columns are greater than the number of grid columns.");
} }
_table.AddRow(columns); _rows.Add(new GridRow(columns));
return this; return this;
} }
/// <inheritdoc/>
protected override bool HasDirtyChildren()
{
return _columns.Any(c => ((IHasDirtyState)c).IsDirty);
}
/// <inheritdoc/>
protected override IRenderable Build()
{
var table = new Table
{
Border = TableBorder.None,
ShowHeaders = false,
IsGrid = true,
PadRightCell = _padRightCell,
};
foreach (var column in _columns)
{
table.AddColumn(new TableColumn(string.Empty)
{
Width = column.Width,
NoWrap = column.NoWrap,
Padding = column.Padding ?? new Padding(0, 0, 2, 0),
Alignment = column.Alignment,
});
}
foreach (var row in _rows)
{
table.AddRow(row);
}
return table;
}
} }
} }

View File

@ -1,55 +1,71 @@
using System;
using Spectre.Console.Rendering;
namespace Spectre.Console namespace Spectre.Console
{ {
/// <summary> /// <summary>
/// Represents a grid column. /// Represents a grid column.
/// </summary> /// </summary>
public sealed class GridColumn : IColumn public sealed class GridColumn : IColumn, IHasDirtyState
{ {
private Padding _padding; private bool _isDirty;
private int? _width;
private bool _noWrap;
private Padding? _padding;
private Justify? _alignment;
/// <inheritdoc/>
bool IHasDirtyState.IsDirty => _isDirty;
/// <summary> /// <summary>
/// Gets or sets the width of the column. /// Gets or sets the width of the column.
/// If <c>null</c>, the column will adapt to it's contents. /// If <c>null</c>, the column will adapt to it's contents.
/// </summary> /// </summary>
public int? Width { get; set; } public int? Width
{
get => _width;
set => MarkAsDirty(() => _width = value);
}
/// <summary> /// <summary>
/// Gets or sets a value indicating whether wrapping of /// Gets or sets a value indicating whether wrapping of
/// text within the column should be prevented. /// text within the column should be prevented.
/// </summary> /// </summary>
public bool NoWrap { get; set; } public bool NoWrap
{
get => _noWrap;
set => MarkAsDirty(() => _noWrap = value);
}
/// <summary> /// <summary>
/// Gets or sets the padding of the column. /// Gets or sets the padding of the column.
/// Vertical padding (top and bottom) is ignored. /// Vertical padding (top and bottom) is ignored.
/// </summary> /// </summary>
public Padding Padding public Padding? Padding
{ {
get => _padding; get => _padding;
set set => MarkAsDirty(() => _padding = value);
{
HasExplicitPadding = true;
_padding = value;
}
} }
/// <summary> /// <summary>
/// Gets or sets the alignment of the column. /// Gets or sets the alignment of the column.
/// </summary> /// </summary>
public Justify? Alignment { get; set; } public Justify? Alignment
{
get => _alignment;
set => MarkAsDirty(() => _alignment = value);
}
/// <summary> /// <summary>
/// Gets a value indicating whether the user /// Gets a value indicating whether the user
/// has set an explicit padding for this column. /// has set an explicit padding for this column.
/// </summary> /// </summary>
internal bool HasExplicitPadding { get; private set; } internal bool HasExplicitPadding => Padding != null;
/// <summary> private void MarkAsDirty(Action action)
/// Initializes a new instance of the <see cref="GridColumn"/> class.
/// </summary>
public GridColumn()
{ {
_padding = new Padding(0, 0, 2, 0); action();
_isDirty = true;
} }
} }
} }

View File

@ -0,0 +1,56 @@
using System;
using System.Collections;
using System.Collections.Generic;
using Spectre.Console.Rendering;
namespace Spectre.Console
{
/// <summary>
/// Represents a grid row.
/// </summary>
public sealed class GridRow : IEnumerable<IRenderable>
{
private readonly List<IRenderable> _items;
/// <summary>
/// Gets a row item at the specified grid column index.
/// </summary>
/// <param name="index">The grid column index.</param>
/// <returns>The row item at the specified grid column index.</returns>
public IRenderable this[int index]
{
get => _items[index];
}
/// <summary>
/// Initializes a new instance of the <see cref="GridRow"/> class.
/// </summary>
/// <param name="items">The row items.</param>
public GridRow(IEnumerable<IRenderable> items)
{
_items = new List<IRenderable>(items ?? Array.Empty<IRenderable>());
}
internal void Add(IRenderable item)
{
if (item is null)
{
throw new ArgumentNullException(nameof(item));
}
_items.Add(item);
}
/// <inheritdoc/>
public IEnumerator<IRenderable> GetEnumerator()
{
return _items.GetEnumerator();
}
/// <inheritdoc/>
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}
}

View File

@ -12,7 +12,7 @@ namespace Spectre.Console
private readonly IRenderable _child; private readonly IRenderable _child;
/// <inheritdoc/> /// <inheritdoc/>
public Padding Padding { get; set; } = new Padding(1, 1, 1, 1); public Padding? Padding { get; set; } = new Padding(1, 1, 1, 1);
/// <summary> /// <summary>
/// Gets or sets a value indicating whether or not the padding should /// Gets or sets a value indicating whether or not the padding should
@ -35,7 +35,7 @@ namespace Spectre.Console
/// <inheritdoc/> /// <inheritdoc/>
protected override Measurement Measure(RenderContext context, int maxWidth) protected override Measurement Measure(RenderContext context, int maxWidth)
{ {
var paddingWidth = Padding.GetWidth(); var paddingWidth = Padding?.GetWidth() ?? 0;
var measurement = _child.Measure(context, maxWidth - paddingWidth); var measurement = _child.Measure(context, maxWidth - paddingWidth);
return new Measurement( return new Measurement(
@ -46,7 +46,7 @@ namespace Spectre.Console
/// <inheritdoc/> /// <inheritdoc/>
protected override IEnumerable<Segment> Render(RenderContext context, int maxWidth) protected override IEnumerable<Segment> Render(RenderContext context, int maxWidth)
{ {
var paddingWidth = Padding.GetWidth(); var paddingWidth = Padding?.GetWidth() ?? 0;
var childWidth = maxWidth - paddingWidth; var childWidth = maxWidth - paddingWidth;
if (!Expand) if (!Expand)
@ -59,7 +59,7 @@ namespace Spectre.Console
var result = new List<Segment>(); var result = new List<Segment>();
// Top padding // Top padding
for (var i = 0; i < Padding.Top; i++) for (var i = 0; i < Padding.GetTopSafe(); i++)
{ {
result.Add(new Segment(new string(' ', width))); result.Add(new Segment(new string(' ', width)));
result.Add(Segment.LineBreak); result.Add(Segment.LineBreak);
@ -69,22 +69,22 @@ namespace Spectre.Console
foreach (var (_, _, _, line) in Segment.SplitLines(context, child).Enumerate()) foreach (var (_, _, _, line) in Segment.SplitLines(context, child).Enumerate())
{ {
// Left padding // Left padding
if (Padding.Left != 0) if (Padding.GetLeftSafe() != 0)
{ {
result.Add(new Segment(new string(' ', Padding.Left))); result.Add(new Segment(new string(' ', Padding.GetLeftSafe())));
} }
result.AddRange(line); result.AddRange(line);
// Right padding // Right padding
if (Padding.Right != 0) if (Padding.GetRightSafe() != 0)
{ {
result.Add(new Segment(new string(' ', Padding.Right))); result.Add(new Segment(new string(' ', Padding.GetRightSafe())));
} }
// Missing space on right side? // Missing space on right side?
var lineWidth = line.CellCount(context); var lineWidth = line.CellCount(context);
var diff = width - lineWidth - Padding.Left - Padding.Right; var diff = width - lineWidth - Padding.GetLeftSafe() - Padding.GetRightSafe();
if (diff > 0) if (diff > 0)
{ {
result.Add(new Segment(new string(' ', diff))); result.Add(new Segment(new string(' ', diff)));
@ -94,7 +94,7 @@ namespace Spectre.Console
} }
// Bottom padding // Bottom padding
for (var i = 0; i < Padding.Bottom; i++) for (var i = 0; i < Padding.GetBottomSafe(); i++)
{ {
result.Add(new Segment(new string(' ', width))); result.Add(new Segment(new string(' ', width)));
result.Add(Segment.LineBreak); result.Add(Segment.LineBreak);

View File

@ -34,7 +34,7 @@ namespace Spectre.Console
/// <summary> /// <summary>
/// Gets or sets the padding. /// Gets or sets the padding.
/// </summary> /// </summary>
public Padding Padding { get; set; } = new Padding(1, 0, 1, 0); public Padding? Padding { get; set; } = new Padding(1, 0, 1, 0);
/// <summary> /// <summary>
/// Gets or sets the header. /// Gets or sets the header.

View File

@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Linq; using System.Linq;
using Spectre.Console.Internal; using Spectre.Console.Internal;
using Spectre.Console.Rendering; using Spectre.Console.Rendering;
using Spectre.Console.Widgets;
namespace Spectre.Console namespace Spectre.Console
{ {
@ -14,20 +15,20 @@ namespace Spectre.Console
private const int EdgeCount = 2; private const int EdgeCount = 2;
private readonly List<TableColumn> _columns; private readonly List<TableColumn> _columns;
private readonly List<List<IRenderable>> _rows; private readonly List<TableRow> _rows;
private static Style _defaultHeadingStyle = new Style(Color.Silver); private static Style _defaultHeadingStyle = new Style(Color.Silver);
private static Style _defaultCaptionStyle = new Style(Color.Grey); private static Style _defaultCaptionStyle = new Style(Color.Grey);
/// <summary> /// <summary>
/// Gets the number of columns in the table. /// Gets the table columns.
/// </summary> /// </summary>
public int ColumnCount => _columns.Count; public IReadOnlyList<TableColumn> Columns => _columns;
/// <summary> /// <summary>
/// Gets the number of rows in the table. /// Gets the table rows.
/// </summary> /// </summary>
public int RowCount => _rows.Count; public IReadOnlyList<TableRow> Rows => _rows;
/// <inheritdoc/> /// <inheritdoc/>
public TableBorder Border { get; set; } = TableBorder.Square; public TableBorder Border { get; set; } = TableBorder.Square;
@ -87,7 +88,7 @@ namespace Spectre.Console
public Table() public Table()
{ {
_columns = new List<TableColumn>(); _columns = new List<TableColumn>();
_rows = new List<List<IRenderable>>(); _rows = new List<TableRow>();
} }
/// <summary> /// <summary>
@ -116,24 +117,25 @@ namespace Spectre.Console
/// </summary> /// </summary>
/// <param name="columns">The row columns to add.</param> /// <param name="columns">The row columns to add.</param>
/// <returns>The same instance so that multiple calls can be chained.</returns> /// <returns>The same instance so that multiple calls can be chained.</returns>
public Table AddRow(params IRenderable[] columns) public Table AddRow(IEnumerable<IRenderable> columns)
{ {
if (columns is null) if (columns is null)
{ {
throw new ArgumentNullException(nameof(columns)); throw new ArgumentNullException(nameof(columns));
} }
if (columns.Length > _columns.Count) var rowColumnCount = columns.GetCount();
if (rowColumnCount > _columns.Count)
{ {
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.ToList()); _rows.Add(new TableRow(columns));
// Need to add missing columns? // Need to add missing columns?
if (columns.Length < _columns.Count) if (rowColumnCount < _columns.Count)
{ {
var diff = _columns.Count - columns.Length; var diff = _columns.Count - rowColumnCount;
Enumerable.Range(0, diff).ForEach(_ => _rows.Last().Add(Text.Empty)); Enumerable.Range(0, diff).ForEach(_ => _rows.Last().Add(Text.Empty));
} }
@ -198,11 +200,11 @@ namespace Spectre.Console
return new List<Segment>(new[] { new Segment("…", BorderStyle ?? Style.Plain) }); return new List<Segment>(new[] { new Segment("…", BorderStyle ?? Style.Plain) });
} }
var rows = new List<List<IRenderable>>(); var rows = new List<TableRow>();
if (ShowHeaders) if (ShowHeaders)
{ {
// Add columns to top of rows // Add columns to top of rows
rows.Add(new List<IRenderable>(_columns.Select(c => c.Header))); rows.Add(new TableRow(new List<IRenderable>(_columns.Select(c => c.Header))));
} }
// Add rows. // Add rows.
@ -210,7 +212,7 @@ namespace Spectre.Console
if (hasFooters) if (hasFooters)
{ {
rows.Add(new List<IRenderable>(_columns.Select(c => c.Footer ?? Text.Empty))); rows.Add(new TableRow(new List<IRenderable>(_columns.Select(c => c.Footer ?? Text.Empty))));
} }
var result = new List<Segment>(); var result = new List<Segment>();
@ -273,7 +275,7 @@ namespace Spectre.Console
// Pad column on left side. // Pad column on left side.
if (showBorder || IsGrid) if (showBorder || IsGrid)
{ {
var leftPadding = _columns[cellIndex].Padding.Left; var leftPadding = _columns[cellIndex].Padding.GetLeftSafe();
if (leftPadding > 0) if (leftPadding > 0)
{ {
rowResult.Add(new Segment(new string(' ', leftPadding))); rowResult.Add(new Segment(new string(' ', leftPadding)));
@ -293,7 +295,7 @@ namespace Spectre.Console
// Pad column on the right side // Pad column on the right side
if (showBorder || (hideBorder && !lastCell) || (hideBorder && lastCell && IsGrid && PadRightCell)) if (showBorder || (hideBorder && !lastCell) || (hideBorder && lastCell && IsGrid && PadRightCell))
{ {
var rightPadding = _columns[cellIndex].Padding.Right; var rightPadding = _columns[cellIndex].Padding.GetRightSafe();
if (rightPadding > 0) if (rightPadding > 0)
{ {
rowResult.Add(new Segment(new string(' ', rightPadding))); rowResult.Add(new Segment(new string(' ', rightPadding)));
@ -446,7 +448,7 @@ namespace Spectre.Console
private (int Min, int Max) MeasureColumn(TableColumn column, RenderContext options, int maxWidth) private (int Min, int Max) MeasureColumn(TableColumn column, RenderContext options, int maxWidth)
{ {
var padding = column.Padding.GetWidth(); var padding = column.Padding?.GetWidth() ?? 0;
// Predetermined width? // Predetermined width?
if (column.Width != null) if (column.Width != null)
@ -482,11 +484,11 @@ namespace Spectre.Console
var hideBorder = !Border.Visible; var hideBorder = !Border.Visible;
var separators = hideBorder ? 0 : _columns.Count - 1; var separators = hideBorder ? 0 : _columns.Count - 1;
var edges = hideBorder ? 0 : EdgeCount; var edges = hideBorder ? 0 : EdgeCount;
var padding = includePadding ? _columns.Select(x => x.Padding.GetWidth()).Sum() : 0; var padding = includePadding ? _columns.Select(x => x.Padding?.GetWidth() ?? 0).Sum() : 0;
if (!PadRightCell) if (!PadRightCell)
{ {
padding -= _columns.Last().Padding.Right; padding -= _columns.Last().Padding.GetRightSafe();
} }
return separators + edges + padding; return separators + edges + padding;

View File

@ -28,7 +28,7 @@ namespace Spectre.Console
/// Gets or sets the padding of the column. /// Gets or sets the padding of the column.
/// Vertical padding (top and bottom) is ignored. /// Vertical padding (top and bottom) is ignored.
/// </summary> /// </summary>
public Padding Padding { get; set; } public Padding? Padding { get; set; }
/// <summary> /// <summary>
/// Gets or sets a value indicating whether wrapping of /// Gets or sets a value indicating whether wrapping of

View File

@ -0,0 +1,56 @@
using System;
using System.Collections;
using System.Collections.Generic;
using Spectre.Console.Rendering;
namespace Spectre.Console.Widgets
{
/// <summary>
/// Represents a table row.
/// </summary>
public sealed class TableRow : IEnumerable<IRenderable>
{
private readonly List<IRenderable> _items;
/// <summary>
/// Gets a row item at the specified table column index.
/// </summary>
/// <param name="index">The table column index.</param>
/// <returns>The row item at the specified table column index.</returns>
public IRenderable this[int index]
{
get => _items[index];
}
/// <summary>
/// Initializes a new instance of the <see cref="TableRow"/> class.
/// </summary>
/// <param name="items">The row items.</param>
public TableRow(IEnumerable<IRenderable> items)
{
_items = new List<IRenderable>(items ?? Array.Empty<IRenderable>());
}
internal void Add(IRenderable item)
{
if (item is null)
{
throw new ArgumentNullException(nameof(item));
}
_items.Add(item);
}
/// <inheritdoc/>
public IEnumerator<IRenderable> GetEnumerator()
{
return _items.GetEnumerator();
}
/// <inheritdoc/>
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}
}