From 6df90442b7fe6c6a7e03e67c23354f0a41ea4262 Mon Sep 17 00:00:00 2001 From: Patrik Svensson Date: Tue, 25 Aug 2020 10:09:25 +0200 Subject: [PATCH] Add support for setting border color Closes #51 --- examples/Table/Program.cs | 19 ++--- .../Rendering/AlignableExtensions.cs | 72 ++++++++++++------- .../Rendering/BorderExtensions.cs | 66 +++++++++++++++++ src/Spectre.Console/Rendering/IHasBorder.cs | 25 +++++++ src/Spectre.Console/Rendering/Panel.cs | 34 +++++---- .../Rendering/SegmentLineIterator.cs | 6 +- src/Spectre.Console/Rendering/Table.cs | 63 ++++++++-------- 7 files changed, 197 insertions(+), 88 deletions(-) create mode 100644 src/Spectre.Console/Rendering/BorderExtensions.cs create mode 100644 src/Spectre.Console/Rendering/IHasBorder.cs diff --git a/examples/Table/Program.cs b/examples/Table/Program.cs index 6a84a7b..eaf8af9 100644 --- a/examples/Table/Program.cs +++ b/examples/Table/Program.cs @@ -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); diff --git a/src/Spectre.Console/Rendering/AlignableExtensions.cs b/src/Spectre.Console/Rendering/AlignableExtensions.cs index 5193a50..aa14f93 100644 --- a/src/Spectre.Console/Rendering/AlignableExtensions.cs +++ b/src/Spectre.Console/Rendering/AlignableExtensions.cs @@ -1,4 +1,4 @@ -namespace Spectre.Console +namespace Spectre.Console { /// /// Contains extension methods for . @@ -8,54 +8,74 @@ /// /// Sets the alignment for an object. /// - /// The alignable type. - /// The alignable object. + /// The alignable object type. + /// The alignable object. /// The alignment. - /// The same alignable object. - public static T WithAlignment(this T alignable, Justify alignment) - where T : IAlignable + /// The same instance. + public static T SetAlignment(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; } /// /// Sets the object to be left aligned. /// /// The alignable type. - /// The alignable object. - /// The same alignable object. - public static T LeftAligned(this T alignable) - where T : IAlignable + /// The alignable object. + /// The same instance. + public static T LeftAligned(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; } /// /// Sets the object to be centered. /// /// The alignable type. - /// The alignable object. - /// The same alignable object. - public static T Centered(this T alignable) - where T : IAlignable + /// The alignable object. + /// The same instance. + public static T Centered(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; } /// /// Sets the object to be right aligned. /// /// The alignable type. - /// The alignable object. - /// The same alignable object. - public static T RightAligned(this T alignable) - where T : IAlignable + /// The alignable object. + /// The same instance. + public static T RightAligned(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; } } } diff --git a/src/Spectre.Console/Rendering/BorderExtensions.cs b/src/Spectre.Console/Rendering/BorderExtensions.cs new file mode 100644 index 0000000..f963c16 --- /dev/null +++ b/src/Spectre.Console/Rendering/BorderExtensions.cs @@ -0,0 +1,66 @@ +using System; + +namespace Spectre.Console.Rendering +{ + /// + /// Contains extension methods for . + /// + public static class BorderExtensions + { + /// + /// Sets the border. + /// + /// The object that has a border. + /// The object to set the border for. + /// The border to use. + /// The same instance. + public static T SetBorder(this T obj, BorderKind border) + where T : class, IHasBorder + { + if (obj is null) + { + throw new ArgumentNullException(nameof(obj)); + } + + obj.Border = border; + return obj; + } + + /// + /// Disables the safe border. + /// + /// The object that has a border. + /// The object to set the border for. + /// The same instance. + public static T NoSafeBorder(this T obj) + where T : class, IHasBorder + { + if (obj is null) + { + throw new ArgumentNullException(nameof(obj)); + } + + obj.SafeBorder = false; + return obj; + } + + /// + /// Sets the border color. + /// + /// The object that has a border. + /// The object to set the border color for. + /// The color to set. + /// The same instance. + public static T SetBorderColor(this T obj, Color color) + where T : class, IHasBorder + { + if (obj is null) + { + throw new ArgumentNullException(nameof(obj)); + } + + obj.BorderColor = color; + return obj; + } + } +} diff --git a/src/Spectre.Console/Rendering/IHasBorder.cs b/src/Spectre.Console/Rendering/IHasBorder.cs new file mode 100644 index 0000000..b69a79d --- /dev/null +++ b/src/Spectre.Console/Rendering/IHasBorder.cs @@ -0,0 +1,25 @@ +namespace Spectre.Console +{ + /// + /// Represents something that has a border. + /// + public interface IHasBorder + { + /// + /// 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. + /// + bool SafeBorder { get; set; } + + /// + /// Gets or sets the kind of border to use. + /// + public BorderKind Border { get; set; } + + /// + /// Gets or sets the border color. + /// + public Color? BorderColor { get; set; } + } +} diff --git a/src/Spectre.Console/Rendering/Panel.cs b/src/Spectre.Console/Rendering/Panel.cs index f1b8b21..4fab57f 100644 --- a/src/Spectre.Console/Rendering/Panel.cs +++ b/src/Spectre.Console/Rendering/Panel.cs @@ -8,23 +8,20 @@ namespace Spectre.Console /// /// A renderable panel. /// - public sealed class Panel : Renderable + public sealed class Panel : Renderable, IHasBorder { private const int EdgeWidth = 2; private readonly IRenderable _child; - /// - /// 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 true. - /// + /// + public BorderKind Border { get; set; } = BorderKind.Square; + + /// public bool SafeBorder { get; set; } = true; - /// - /// Gets or sets the kind of border to use. - /// - public BorderKind Border { get; set; } = BorderKind.Square; + /// + public Color? BorderColor { get; set; } /// /// Gets or sets the alignment of the panel contents. @@ -74,6 +71,7 @@ namespace Spectre.Console protected override IEnumerable 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 { - 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; diff --git a/src/Spectre.Console/Rendering/SegmentLineIterator.cs b/src/Spectre.Console/Rendering/SegmentLineIterator.cs index eacc3f9..fef5e80 100644 --- a/src/Spectre.Console/Rendering/SegmentLineIterator.cs +++ b/src/Spectre.Console/Rendering/SegmentLineIterator.cs @@ -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) diff --git a/src/Spectre.Console/Rendering/Table.cs b/src/Spectre.Console/Rendering/Table.cs index 145143a..4f5e22c 100644 --- a/src/Spectre.Console/Rendering/Table.cs +++ b/src/Spectre.Console/Rendering/Table.cs @@ -10,7 +10,7 @@ namespace Spectre.Console /// /// A renderable table. /// - public sealed partial class Table : Renderable + public sealed partial class Table : Renderable, IHasBorder { private readonly List _columns; private readonly List> _rows; @@ -25,11 +25,15 @@ namespace Spectre.Console /// public int RowCount => _rows.Count; - /// - /// Gets or sets the kind of border to use. - /// + /// public BorderKind Border { get; set; } = BorderKind.Square; + /// + public Color? BorderColor { get; set; } + + /// + public bool SafeBorder { get; set; } = true; + /// /// Gets or sets a value indicating whether or not table headers should be shown. /// @@ -47,13 +51,6 @@ namespace Spectre.Console /// public int? Width { get; set; } - /// - /// 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 true. - /// - 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); } }