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;
using Spectre.Console.Rendering;
namespace TableExample namespace TableExample
{ {
class Program public static class Program
{ {
static void Main(string[] args) public static void Main(string[] args)
{ {
// A simple table // A simple table
RenderSimpleTable(); RenderSimpleTable();
@ -35,7 +36,7 @@ namespace TableExample
private static void RenderBigTable() private static void RenderBigTable()
{ {
// Create the table. // Create the table.
var table = new Table { Border = BorderKind.Rounded }; var table = new Table().SetBorder(BorderKind.Rounded);
table.AddColumn("[red underline]Foo[/]"); table.AddColumn("[red underline]Foo[/]");
table.AddColumn(new TableColumn("[blue]Bar[/]") { Alignment = Justify.Right, NoWrap = true }); table.AddColumn(new TableColumn("[blue]Bar[/]") { Alignment = Justify.Right, NoWrap = true });
@ -57,7 +58,7 @@ namespace TableExample
private static void RenderNestedTable() private static void RenderNestedTable()
{ {
// Create simple table. // 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]Foo[/]").Centered());
simple.AddColumn(new TableColumn("[u]Bar[/]")); simple.AddColumn(new TableColumn("[u]Bar[/]"));
simple.AddColumn(new TableColumn("[u]Baz[/]")); simple.AddColumn(new TableColumn("[u]Baz[/]"));
@ -66,7 +67,7 @@ namespace TableExample
simple.AddRow("[blue]Hej[/]", "[yellow]Världen![/]", ""); simple.AddRow("[blue]Hej[/]", "[yellow]Världen![/]", "");
// Create other table. // 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]Foo[/]"));
second.AddColumn(new TableColumn("[u]Bar[/]")); second.AddColumn(new TableColumn("[u]Bar[/]"));
second.AddColumn(new TableColumn("[u]Baz[/]")); second.AddColumn(new TableColumn("[u]Baz[/]"));
@ -74,10 +75,10 @@ namespace TableExample
second.AddRow(simple, new Text("Whaaat"), new Text("Lolz")); second.AddRow(simple, new Text("Whaaat"), new Text("Lolz"));
second.AddRow("[blue]Hej[/]", "[yellow]Världen![/]", ""); second.AddRow("[blue]Hej[/]", "[yellow]Världen![/]", "");
var table = new Table { Border = BorderKind.Rounded }; var table = new Table().SetBorder(BorderKind.Rounded);
table.AddColumn(new TableColumn(new Panel("[u]Foo[/]"))); table.AddColumn(new TableColumn(new Panel("[u]Foo[/]").SetBorderColor(Color.Red)));
table.AddColumn(new TableColumn(new Panel("[u]Bar[/]"))); table.AddColumn(new TableColumn(new Panel("[u]Bar[/]").SetBorderColor(Color.Green)));
table.AddColumn(new TableColumn(new Panel("[u]Baz[/]"))); table.AddColumn(new TableColumn(new Panel("[u]Baz[/]").SetBorderColor(Color.Blue)));
// Add some rows // Add some rows
table.AddRow(new Text("Hello").Centered(), new Markup("[red]World![/] 🌍"), Text.Empty); 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> /// <summary>
/// Contains extension methods for <see cref="IAlignable"/>. /// Contains extension methods for <see cref="IAlignable"/>.
@ -8,54 +8,74 @@
/// <summary> /// <summary>
/// Sets the alignment for an <see cref="IAlignable"/> object. /// Sets the alignment for an <see cref="IAlignable"/> object.
/// </summary> /// </summary>
/// <typeparam name="T">The alignable type.</typeparam> /// <typeparam name="T">The alignable object type.</typeparam>
/// <param name="alignable">The alignable object.</param> /// <param name="obj">The alignable object.</param>
/// <param name="alignment">The alignment.</param> /// <param name="alignment">The alignment.</param>
/// <returns>The same alignable object.</returns> /// <returns>The same <see cref="IAlignable"/> instance.</returns>
public static T WithAlignment<T>(this T alignable, Justify alignment) public static T SetAlignment<T>(this T obj, Justify alignment)
where T : IAlignable where T : class, IAlignable
{ {
alignable.Alignment = alignment; if (obj is null)
return alignable; {
throw new System.ArgumentNullException(nameof(obj));
}
obj.Alignment = alignment;
return obj;
} }
/// <summary> /// <summary>
/// Sets the <see cref="IAlignable"/> object to be left aligned. /// Sets the <see cref="IAlignable"/> object to be left aligned.
/// </summary> /// </summary>
/// <typeparam name="T">The alignable type.</typeparam> /// <typeparam name="T">The alignable type.</typeparam>
/// <param name="alignable">The alignable object.</param> /// <param name="obj">The alignable object.</param>
/// <returns>The same alignable object.</returns> /// <returns>The same <see cref="IAlignable"/> instance.</returns>
public static T LeftAligned<T>(this T alignable) public static T LeftAligned<T>(this T obj)
where T : IAlignable where T : class, IAlignable
{ {
alignable.Alignment = Justify.Left; if (obj is null)
return alignable; {
throw new System.ArgumentNullException(nameof(obj));
}
obj.Alignment = Justify.Left;
return obj;
} }
/// <summary> /// <summary>
/// Sets the <see cref="IAlignable"/> object to be centered. /// Sets the <see cref="IAlignable"/> object to be centered.
/// </summary> /// </summary>
/// <typeparam name="T">The alignable type.</typeparam> /// <typeparam name="T">The alignable type.</typeparam>
/// <param name="alignable">The alignable object.</param> /// <param name="obj">The alignable object.</param>
/// <returns>The same alignable object.</returns> /// <returns>The same <see cref="IAlignable"/> instance.</returns>
public static T Centered<T>(this T alignable) public static T Centered<T>(this T obj)
where T : IAlignable where T : class, IAlignable
{ {
alignable.Alignment = Justify.Center; if (obj is null)
return alignable; {
throw new System.ArgumentNullException(nameof(obj));
}
obj.Alignment = Justify.Center;
return obj;
} }
/// <summary> /// <summary>
/// Sets the <see cref="IAlignable"/> object to be right aligned. /// Sets the <see cref="IAlignable"/> object to be right aligned.
/// </summary> /// </summary>
/// <typeparam name="T">The alignable type.</typeparam> /// <typeparam name="T">The alignable type.</typeparam>
/// <param name="alignable">The alignable object.</param> /// <param name="obj">The alignable object.</param>
/// <returns>The same alignable object.</returns> /// <returns>The same <see cref="IAlignable"/> instance.</returns>
public static T RightAligned<T>(this T alignable) public static T RightAligned<T>(this T obj)
where T : IAlignable where T : class, IAlignable
{ {
alignable.Alignment = Justify.Right; if (obj is null)
return alignable; {
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> /// <summary>
/// A renderable panel. /// A renderable panel.
/// </summary> /// </summary>
public sealed class Panel : Renderable public sealed class Panel : Renderable, IHasBorder
{ {
private const int EdgeWidth = 2; private const int EdgeWidth = 2;
private readonly IRenderable _child; private readonly IRenderable _child;
/// <summary> /// <inheritdoc/>
/// Gets or sets a value indicating whether or not to use public BorderKind Border { get; set; } = BorderKind.Square;
/// a "safe" border on legacy consoles that might not be able
/// to render non-ASCII characters. Defaults to <c>true</c>. /// <inheritdoc/>
/// </summary>
public bool SafeBorder { get; set; } = true; public bool SafeBorder { get; set; } = true;
/// <summary> /// <inheritdoc/>
/// Gets or sets the kind of border to use. public Color? BorderColor { get; set; }
/// </summary>
public BorderKind Border { get; set; } = BorderKind.Square;
/// <summary> /// <summary>
/// Gets or sets the alignment of the panel contents. /// 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) protected override IEnumerable<Segment> Render(RenderContext context, int maxWidth)
{ {
var border = SpectreBorder.GetBorder(Border, (context.LegacyConsole || !context.Unicode) && SafeBorder); var border = SpectreBorder.GetBorder(Border, (context.LegacyConsole || !context.Unicode) && SafeBorder);
var borderStyle = new Style(BorderColor, null, null);
var paddingWidth = Padding.GetHorizontalPadding(); var paddingWidth = Padding.GetHorizontalPadding();
var childWidth = maxWidth - EdgeWidth - paddingWidth; var childWidth = maxWidth - EdgeWidth - paddingWidth;
@ -89,9 +87,9 @@ namespace Spectre.Console
// Panel top // Panel top
var result = new List<Segment> var result = new List<Segment>
{ {
new Segment(border.GetPart(BorderPart.HeaderTopLeft)), new Segment(border.GetPart(BorderPart.HeaderTopLeft), borderStyle),
new Segment(border.GetPart(BorderPart.HeaderTop, panelWidth)), new Segment(border.GetPart(BorderPart.HeaderTop, panelWidth), borderStyle),
new Segment(border.GetPart(BorderPart.HeaderTopRight)), new Segment(border.GetPart(BorderPart.HeaderTopRight), borderStyle),
Segment.LineBreak, Segment.LineBreak,
}; };
@ -102,7 +100,7 @@ namespace Spectre.Console
// Split the child segments into lines. // Split the child segments into lines.
foreach (var line in Segment.SplitLines(childSegments, panelWidth)) 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 // Left padding
if (Padding.Left > 0) if (Padding.Left > 0)
@ -129,14 +127,14 @@ namespace Spectre.Console
result.Add(new Segment(new string(' ', Padding.Right))); 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); result.Add(Segment.LineBreak);
} }
// Panel bottom // Panel bottom
result.Add(new Segment(border.GetPart(BorderPart.FooterBottomLeft))); result.Add(new Segment(border.GetPart(BorderPart.FooterBottomLeft), borderStyle));
result.Add(new Segment(border.GetPart(BorderPart.FooterBottom, panelWidth))); result.Add(new Segment(border.GetPart(BorderPart.FooterBottom, panelWidth), borderStyle));
result.Add(new Segment(border.GetPart(BorderPart.FooterBottomRight))); result.Add(new Segment(border.GetPart(BorderPart.FooterBottomRight), borderStyle));
result.Add(Segment.LineBreak); result.Add(Segment.LineBreak);
return result; return result;

View File

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

View File

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