Add support for setting border color

Closes #51
This commit is contained in:
Patrik Svensson 2020-08-25 10:09:25 +02:00 committed by Patrik Svensson
parent 7182d01a23
commit 6df90442b7
7 changed files with 197 additions and 88 deletions

View File

@ -1,10 +1,11 @@
using Spectre.Console;
using Spectre.Console.Rendering;
namespace TableExample
{
class Program
public static class Program
{
static void Main(string[] args)
public static void Main(string[] args)
{
// A simple table
RenderSimpleTable();
@ -35,7 +36,7 @@ namespace TableExample
private static void RenderBigTable()
{
// Create the table.
var table = new Table { Border = BorderKind.Rounded };
var table = new Table().SetBorder(BorderKind.Rounded);
table.AddColumn("[red underline]Foo[/]");
table.AddColumn(new TableColumn("[blue]Bar[/]") { Alignment = Justify.Right, NoWrap = true });
@ -57,7 +58,7 @@ namespace TableExample
private static void RenderNestedTable()
{
// Create simple table.
var simple = new Table { Border = BorderKind.Rounded };
var simple = new Table().SetBorder(BorderKind.Rounded).SetBorderColor(Color.Red);
simple.AddColumn(new TableColumn("[u]Foo[/]").Centered());
simple.AddColumn(new TableColumn("[u]Bar[/]"));
simple.AddColumn(new TableColumn("[u]Baz[/]"));
@ -66,7 +67,7 @@ namespace TableExample
simple.AddRow("[blue]Hej[/]", "[yellow]Världen![/]", "");
// Create other table.
var second = new Table { Border = BorderKind.Square };
var second = new Table().SetBorder(BorderKind.Square).SetBorderColor(Color.Green);
second.AddColumn(new TableColumn("[u]Foo[/]"));
second.AddColumn(new TableColumn("[u]Bar[/]"));
second.AddColumn(new TableColumn("[u]Baz[/]"));
@ -74,10 +75,10 @@ namespace TableExample
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[/]")));
var table = new Table().SetBorder(BorderKind.Rounded);
table.AddColumn(new TableColumn(new Panel("[u]Foo[/]").SetBorderColor(Color.Red)));
table.AddColumn(new TableColumn(new Panel("[u]Bar[/]").SetBorderColor(Color.Green)));
table.AddColumn(new TableColumn(new Panel("[u]Baz[/]").SetBorderColor(Color.Blue)));
// Add some rows
table.AddRow(new Text("Hello").Centered(), new Markup("[red]World![/] 🌍"), Text.Empty);

View File

@ -1,4 +1,4 @@
namespace Spectre.Console
namespace Spectre.Console
{
/// <summary>
/// Contains extension methods for <see cref="IAlignable"/>.
@ -8,54 +8,74 @@
/// <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>
/// <typeparam name="T">The alignable object type.</typeparam>
/// <param name="obj">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
/// <returns>The same <see cref="IAlignable"/> instance.</returns>
public static T SetAlignment<T>(this T obj, Justify alignment)
where T : class, IAlignable
{
alignable.Alignment = alignment;
return alignable;
if (obj is null)
{
throw new System.ArgumentNullException(nameof(obj));
}
obj.Alignment = alignment;
return obj;
}
/// <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
/// <param name="obj">The alignable object.</param>
/// <returns>The same <see cref="IAlignable"/> instance.</returns>
public static T LeftAligned<T>(this T obj)
where T : class, IAlignable
{
alignable.Alignment = Justify.Left;
return alignable;
if (obj is null)
{
throw new System.ArgumentNullException(nameof(obj));
}
obj.Alignment = Justify.Left;
return obj;
}
/// <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
/// <param name="obj">The alignable object.</param>
/// <returns>The same <see cref="IAlignable"/> instance.</returns>
public static T Centered<T>(this T obj)
where T : class, IAlignable
{
alignable.Alignment = Justify.Center;
return alignable;
if (obj is null)
{
throw new System.ArgumentNullException(nameof(obj));
}
obj.Alignment = Justify.Center;
return obj;
}
/// <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
/// <param name="obj">The alignable object.</param>
/// <returns>The same <see cref="IAlignable"/> instance.</returns>
public static T RightAligned<T>(this T obj)
where T : class, IAlignable
{
alignable.Alignment = Justify.Right;
return alignable;
if (obj is null)
{
throw new System.ArgumentNullException(nameof(obj));
}
obj.Alignment = Justify.Right;
return obj;
}
}
}

View File

@ -0,0 +1,66 @@
using System;
namespace Spectre.Console.Rendering
{
/// <summary>
/// Contains extension methods for <see cref="IHasBorder"/>.
/// </summary>
public static class BorderExtensions
{
/// <summary>
/// Sets the border.
/// </summary>
/// <typeparam name="T">The object that has a border.</typeparam>
/// <param name="obj">The object to set the border for.</param>
/// <param name="border">The border to use.</param>
/// <returns>The same <see cref="IHasBorder"/> instance.</returns>
public static T SetBorder<T>(this T obj, BorderKind border)
where T : class, IHasBorder
{
if (obj is null)
{
throw new ArgumentNullException(nameof(obj));
}
obj.Border = border;
return obj;
}
/// <summary>
/// Disables the safe border.
/// </summary>
/// <typeparam name="T">The object that has a border.</typeparam>
/// <param name="obj">The object to set the border for.</param>
/// <returns>The same <see cref="IHasBorder"/> instance.</returns>
public static T NoSafeBorder<T>(this T obj)
where T : class, IHasBorder
{
if (obj is null)
{
throw new ArgumentNullException(nameof(obj));
}
obj.SafeBorder = false;
return obj;
}
/// <summary>
/// Sets the border color.
/// </summary>
/// <typeparam name="T">The object that has a border.</typeparam>
/// <param name="obj">The object to set the border color for.</param>
/// <param name="color">The color to set.</param>
/// <returns>The same <see cref="IHasBorder"/> instance.</returns>
public static T SetBorderColor<T>(this T obj, Color color)
where T : class, IHasBorder
{
if (obj is null)
{
throw new ArgumentNullException(nameof(obj));
}
obj.BorderColor = color;
return obj;
}
}
}

View File

@ -0,0 +1,25 @@
namespace Spectre.Console
{
/// <summary>
/// Represents something that has a border.
/// </summary>
public interface IHasBorder
{
/// <summary>
/// Gets or sets a value indicating whether or not to use
/// a "safe" border on legacy consoles that might not be able
/// to render non-ASCII characters.
/// </summary>
bool SafeBorder { get; set; }
/// <summary>
/// Gets or sets the kind of border to use.
/// </summary>
public BorderKind Border { get; set; }
/// <summary>
/// Gets or sets the border color.
/// </summary>
public Color? BorderColor { get; set; }
}
}

View File

@ -8,23 +8,20 @@ namespace Spectre.Console
/// <summary>
/// A renderable panel.
/// </summary>
public sealed class Panel : Renderable
public sealed class Panel : Renderable, IHasBorder
{
private const int EdgeWidth = 2;
private readonly IRenderable _child;
/// <summary>
/// Gets or sets a value indicating whether or not to use
/// a "safe" border on legacy consoles that might not be able
/// to render non-ASCII characters. Defaults to <c>true</c>.
/// </summary>
/// <inheritdoc/>
public BorderKind Border { get; set; } = BorderKind.Square;
/// <inheritdoc/>
public bool SafeBorder { get; set; } = true;
/// <summary>
/// Gets or sets the kind of border to use.
/// </summary>
public BorderKind Border { get; set; } = BorderKind.Square;
/// <inheritdoc/>
public Color? BorderColor { get; set; }
/// <summary>
/// Gets or sets the alignment of the panel contents.
@ -74,6 +71,7 @@ namespace Spectre.Console
protected override IEnumerable<Segment> Render(RenderContext context, int maxWidth)
{
var border = SpectreBorder.GetBorder(Border, (context.LegacyConsole || !context.Unicode) && SafeBorder);
var borderStyle = new Style(BorderColor, null, null);
var paddingWidth = Padding.GetHorizontalPadding();
var childWidth = maxWidth - EdgeWidth - paddingWidth;
@ -89,9 +87,9 @@ namespace Spectre.Console
// Panel top
var result = new List<Segment>
{
new Segment(border.GetPart(BorderPart.HeaderTopLeft)),
new Segment(border.GetPart(BorderPart.HeaderTop, panelWidth)),
new Segment(border.GetPart(BorderPart.HeaderTopRight)),
new Segment(border.GetPart(BorderPart.HeaderTopLeft), borderStyle),
new Segment(border.GetPart(BorderPart.HeaderTop, panelWidth), borderStyle),
new Segment(border.GetPart(BorderPart.HeaderTopRight), borderStyle),
Segment.LineBreak,
};
@ -102,7 +100,7 @@ namespace Spectre.Console
// Split the child segments into lines.
foreach (var line in Segment.SplitLines(childSegments, panelWidth))
{
result.Add(new Segment(border.GetPart(BorderPart.CellLeft)));
result.Add(new Segment(border.GetPart(BorderPart.CellLeft), borderStyle));
// Left padding
if (Padding.Left > 0)
@ -129,14 +127,14 @@ namespace Spectre.Console
result.Add(new Segment(new string(' ', Padding.Right)));
}
result.Add(new Segment(border.GetPart(BorderPart.CellRight)));
result.Add(new Segment(border.GetPart(BorderPart.CellRight), borderStyle));
result.Add(Segment.LineBreak);
}
// Panel bottom
result.Add(new Segment(border.GetPart(BorderPart.FooterBottomLeft)));
result.Add(new Segment(border.GetPart(BorderPart.FooterBottom, panelWidth)));
result.Add(new Segment(border.GetPart(BorderPart.FooterBottomRight)));
result.Add(new Segment(border.GetPart(BorderPart.FooterBottomLeft), borderStyle));
result.Add(new Segment(border.GetPart(BorderPart.FooterBottom, panelWidth), borderStyle));
result.Add(new Segment(border.GetPart(BorderPart.FooterBottomRight), borderStyle));
result.Add(Segment.LineBreak);
return result;

View File

@ -33,7 +33,7 @@ namespace Spectre.Console.Rendering
return false;
}
_currentIndex += 1;
_currentIndex++;
// Did we go past the end of the line?
if (_currentIndex > _lines[_currentLine].Count - 1)
@ -57,7 +57,7 @@ namespace Spectre.Console.Rendering
}
// Increase the line and reset the index.
_currentLine += 1;
_currentLine++;
_currentIndex = 0;
_lineBreakEmitted = false;
@ -71,7 +71,7 @@ namespace Spectre.Console.Rendering
// Nothing on the line?
while (_currentIndex > _lines[_currentLine].Count - 1)
{
_currentLine += 1;
_currentLine++;
_currentIndex = 0;
if (_currentLine > _lines.Count - 1)

View File

@ -10,7 +10,7 @@ namespace Spectre.Console
/// <summary>
/// A renderable table.
/// </summary>
public sealed partial class Table : Renderable
public sealed partial class Table : Renderable, IHasBorder
{
private readonly List<TableColumn> _columns;
private readonly List<List<IRenderable>> _rows;
@ -25,11 +25,15 @@ namespace Spectre.Console
/// </summary>
public int RowCount => _rows.Count;
/// <summary>
/// Gets or sets the kind of border to use.
/// </summary>
/// <inheritdoc/>
public BorderKind Border { get; set; } = BorderKind.Square;
/// <inheritdoc/>
public Color? BorderColor { get; set; }
/// <inheritdoc/>
public bool SafeBorder { get; set; } = true;
/// <summary>
/// Gets or sets a value indicating whether or not table headers should be shown.
/// </summary>
@ -47,13 +51,6 @@ namespace Spectre.Console
/// </summary>
public int? Width { get; set; }
/// <summary>
/// Gets or sets a value indicating whether or not to use
/// a "safe" border on legacy consoles that might not be able
/// to render non-ASCII characters. Defaults to <c>true</c>.
/// </summary>
public bool SafeBorder { get; set; } = true;
// Whether this is a grid or not.
internal bool IsGrid { get; set; }
@ -172,6 +169,8 @@ namespace Spectre.Console
}
var border = SpectreBorder.GetBorder(Border, (context.LegacyConsole || !context.Unicode) && SafeBorder);
var borderStyle = new Style(BorderColor, null, null);
var tableWidth = maxWidth;
var showBorder = Border != BorderKind.None;
@ -222,22 +221,22 @@ 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), borderStyle));
foreach (var (columnIndex, _, lastColumn, columnWidth) in columnWidths.Enumerate())
{
var padding = _columns[columnIndex].Padding;
result.Add(new Segment(border.GetPart(BorderPart.HeaderTop, padding.Left))); // Left padding
result.Add(new Segment(border.GetPart(BorderPart.HeaderTop, columnWidth)));
result.Add(new Segment(border.GetPart(BorderPart.HeaderTop, padding.Right))); // Right padding
result.Add(new Segment(border.GetPart(BorderPart.HeaderTop, padding.Left), borderStyle)); // Left padding
result.Add(new Segment(border.GetPart(BorderPart.HeaderTop, columnWidth), borderStyle));
result.Add(new Segment(border.GetPart(BorderPart.HeaderTop, padding.Right), borderStyle)); // Right padding
if (!lastColumn)
{
result.Add(new Segment(border.GetPart(BorderPart.HeaderTopSeparator)));
result.Add(new Segment(border.GetPart(BorderPart.HeaderTopSeparator), borderStyle));
}
}
result.Add(new Segment(border.GetPart(BorderPart.HeaderTopRight)));
result.Add(new Segment(border.GetPart(BorderPart.HeaderTopRight), borderStyle));
result.Add(Segment.LineBreak);
}
@ -252,7 +251,7 @@ namespace Spectre.Console
if (firstCell && showBorder)
{
// Show left column edge
result.Add(new Segment(border.GetPart(BorderPart.CellLeft)));
result.Add(new Segment(border.GetPart(BorderPart.CellLeft), borderStyle));
}
// Pad column on left side.
@ -288,12 +287,12 @@ namespace Spectre.Console
if (lastCell && showBorder)
{
// Add right column edge
result.Add(new Segment(border.GetPart(BorderPart.CellRight)));
result.Add(new Segment(border.GetPart(BorderPart.CellRight), borderStyle));
}
else if (showBorder)
{
// Add column separator
result.Add(new Segment(border.GetPart(BorderPart.CellSeparator)));
result.Add(new Segment(border.GetPart(BorderPart.CellSeparator), borderStyle));
}
}
@ -303,44 +302,44 @@ namespace Spectre.Console
// Show header separator?
if (firstRow && showBorder && ShowHeaders && hasRows)
{
result.Add(new Segment(border.GetPart(BorderPart.HeaderBottomLeft)));
result.Add(new Segment(border.GetPart(BorderPart.HeaderBottomLeft), borderStyle));
foreach (var (columnIndex, first, lastColumn, columnWidth) in columnWidths.Enumerate())
{
var padding = _columns[columnIndex].Padding;
result.Add(new Segment(border.GetPart(BorderPart.HeaderBottom, padding.Left))); // Left padding
result.Add(new Segment(border.GetPart(BorderPart.HeaderBottom, columnWidth)));
result.Add(new Segment(border.GetPart(BorderPart.HeaderBottom, padding.Right))); // Right padding
result.Add(new Segment(border.GetPart(BorderPart.HeaderBottom, padding.Left), borderStyle)); // Left padding
result.Add(new Segment(border.GetPart(BorderPart.HeaderBottom, columnWidth), borderStyle));
result.Add(new Segment(border.GetPart(BorderPart.HeaderBottom, padding.Right), borderStyle)); // Right padding
if (!lastColumn)
{
result.Add(new Segment(border.GetPart(BorderPart.HeaderBottomSeparator)));
result.Add(new Segment(border.GetPart(BorderPart.HeaderBottomSeparator), borderStyle));
}
}
result.Add(new Segment(border.GetPart(BorderPart.HeaderBottomRight)));
result.Add(new Segment(border.GetPart(BorderPart.HeaderBottomRight), borderStyle));
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), borderStyle));
foreach (var (columnIndex, first, lastColumn, columnWidth) in columnWidths.Enumerate())
{
var padding = _columns[columnIndex].Padding;
result.Add(new Segment(border.GetPart(BorderPart.FooterBottom, padding.Left))); // Left padding
result.Add(new Segment(border.GetPart(BorderPart.FooterBottom, columnWidth)));
result.Add(new Segment(border.GetPart(BorderPart.FooterBottom, padding.Right))); // Right padding
result.Add(new Segment(border.GetPart(BorderPart.FooterBottom, padding.Left), borderStyle)); // Left padding
result.Add(new Segment(border.GetPart(BorderPart.FooterBottom, columnWidth), borderStyle));
result.Add(new Segment(border.GetPart(BorderPart.FooterBottom, padding.Right), borderStyle)); // Right padding
if (!lastColumn)
{
result.Add(new Segment(border.GetPart(BorderPart.FooterBottomSeparator)));
result.Add(new Segment(border.GetPart(BorderPart.FooterBottomSeparator), borderStyle));
}
}
result.Add(new Segment(border.GetPart(BorderPart.FooterBottomRight)));
result.Add(new Segment(border.GetPart(BorderPart.FooterBottomRight), borderStyle));
result.Add(Segment.LineBreak);
}
}