From e946289bd93bd33271b7949b36d2f04ef914b43f Mon Sep 17 00:00:00 2001 From: Patrik Svensson Date: Thu, 3 Sep 2020 21:18:52 +0200 Subject: [PATCH] Make styles composable Also adds some new extension methods and make some APIs a bit more consistent. Closes #64 --- examples/Panel/Program.cs | 40 ++++----- examples/Table/Program.cs | 8 +- src/Spectre.Console.Tests/Unit/TableTests.cs | 10 +-- src/Spectre.Console/Rendering/Grid.cs | 53 +++--------- .../Rendering/GridExtensions.cs | 59 +++++++++++++ src/Spectre.Console/Rendering/Panel.cs | 8 +- src/Spectre.Console/Rendering/Table.cs | 12 +-- .../Traits/Extensions/BorderExtensions.cs | 85 +++++++++++++++++-- .../Rendering/Traits/IHasBorder.cs | 6 +- src/Spectre.Console/Style.Factory.cs | 40 +++++++++ src/Spectre.Console/Style.cs | 2 +- src/Spectre.Console/StyleExtensions.cs | 70 +++++++++++++++ 12 files changed, 296 insertions(+), 97 deletions(-) create mode 100644 src/Spectre.Console/Style.Factory.cs create mode 100644 src/Spectre.Console/StyleExtensions.cs diff --git a/examples/Panel/Program.cs b/examples/Panel/Program.cs index df3b033..4f76421 100644 --- a/examples/Panel/Program.cs +++ b/examples/Panel/Program.cs @@ -1,4 +1,5 @@ using Spectre.Console; +using Spectre.Console.Rendering; namespace PanelExample { @@ -13,35 +14,28 @@ namespace PanelExample AnsiConsole.Render( new Panel( new Panel(content) - { - Border = BorderKind.Rounded, - })); + .SetBorderKind(BorderKind.Rounded))); // Left adjusted panel with text - AnsiConsole.Render(new Panel( - new Text("Left adjusted\nLeft").LeftAligned()) - { - Expand = true, - Header = new Header("Left", new Style(foreground: Color.Red)).LeftAligned(), - }); + AnsiConsole.Render( + new Panel(new Text("Left adjusted\nLeft").LeftAligned()) + .Expand() + .SquareBorder() + .SetHeader("Left", Style.WithForeground(Color.Red))); // Centered ASCII panel with text - AnsiConsole.Render(new Panel( - new Text("Centered\nCenter").Centered()) - { - Expand = true, - Border = BorderKind.Ascii, - Header = new Header("Center", new Style(foreground: Color.Green)).Centered(), - }); + AnsiConsole.Render( + new Panel(new Text("Centered\nCenter").Centered()) + .Expand() + .AsciiBorder() + .SetHeader("Center", Style.WithForeground(Color.Green), Justify.Center)); // Right adjusted, rounded panel with text - AnsiConsole.Render(new Panel( - new Text("Right adjusted\nRight").RightAligned()) - { - Expand = true, - Border = BorderKind.Rounded, - Header = new Header("Right", new Style(foreground: Color.Blue)).RightAligned(), - }); + AnsiConsole.Render( + new Panel(new Text("Right adjusted\nRight").RightAligned()) + .Expand() + .RoundedBorder() + .SetHeader("Right", Style.WithForeground(Color.Blue), Justify.Right)); } } } diff --git a/examples/Table/Program.cs b/examples/Table/Program.cs index 1b00046..36704b9 100644 --- a/examples/Table/Program.cs +++ b/examples/Table/Program.cs @@ -36,7 +36,7 @@ namespace TableExample private static void RenderBigTable() { // Create the table. - var table = new Table().SetBorder(BorderKind.Rounded); + var table = new Table().SetBorderKind(BorderKind.Rounded); table.AddColumn("[red underline]Foo[/]"); table.AddColumn(new TableColumn("[blue]Bar[/]") { Alignment = Justify.Right, NoWrap = true }); @@ -58,7 +58,7 @@ namespace TableExample private static void RenderNestedTable() { // Create simple table. - var simple = new Table().SetBorder(BorderKind.Rounded).SetBorderColor(Color.Red); + var simple = new Table().SetBorderKind(BorderKind.Rounded).SetBorderColor(Color.Red); simple.AddColumn(new TableColumn("[u]Foo[/]").Centered()); simple.AddColumn(new TableColumn("[u]Bar[/]")); simple.AddColumn(new TableColumn("[u]Baz[/]")); @@ -67,7 +67,7 @@ namespace TableExample simple.AddRow("[blue]Hej[/]", "[yellow]Världen![/]", ""); // Create other table. - var second = new Table().SetBorder(BorderKind.Square).SetBorderColor(Color.Green); + var second = new Table().SetBorderKind(BorderKind.Square).SetBorderColor(Color.Green); second.AddColumn(new TableColumn("[u]Foo[/]")); second.AddColumn(new TableColumn("[u]Bar[/]")); second.AddColumn(new TableColumn("[u]Baz[/]")); @@ -75,7 +75,7 @@ namespace TableExample second.AddRow(simple, new Text("Whaaat"), new Text("Lolz")); second.AddRow("[blue]Hej[/]", "[yellow]Världen![/]", ""); - var table = new Table().SetBorder(BorderKind.Rounded); + var table = new Table().SetBorderKind(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))); diff --git a/src/Spectre.Console.Tests/Unit/TableTests.cs b/src/Spectre.Console.Tests/Unit/TableTests.cs index 54e7d4f..c58061a 100644 --- a/src/Spectre.Console.Tests/Unit/TableTests.cs +++ b/src/Spectre.Console.Tests/Unit/TableTests.cs @@ -174,7 +174,7 @@ namespace Spectre.Console.Tests.Unit { // A simple table var console = new PlainConsole(width: 80); - var table = new Table() { Border = BorderKind.Rounded }; + var table = new Table() { BorderKind = BorderKind.Rounded }; table.AddColumn("Foo"); table.AddColumn("Bar"); table.AddColumn(new TableColumn("Baz") { Alignment = Justify.Right }); @@ -184,7 +184,7 @@ namespace Spectre.Console.Tests.Unit // Render a table in some panels. console.Render(new Panel(new Panel(table) { - Border = BorderKind.Ascii, + BorderKind = BorderKind.Ascii, })); // Then @@ -256,7 +256,7 @@ namespace Spectre.Console.Tests.Unit { // Given var console = new PlainConsole(width: 80); - var table = new Table { Border = BorderKind.Ascii }; + var table = new Table { BorderKind = BorderKind.Ascii }; table.AddColumns("Foo", "Bar", "Baz"); table.AddRow("Qux", "Corgi", "Waldo"); table.AddRow("Grault", "Garply", "Fred"); @@ -279,7 +279,7 @@ namespace Spectre.Console.Tests.Unit { // Given var console = new PlainConsole(width: 80); - var table = new Table { Border = BorderKind.Rounded }; + var table = new Table { BorderKind = BorderKind.Rounded }; table.AddColumns("Foo", "Bar", "Baz"); table.AddRow("Qux", "Corgi", "Waldo"); table.AddRow("Grault", "Garply", "Fred"); @@ -302,7 +302,7 @@ namespace Spectre.Console.Tests.Unit { // Given var console = new PlainConsole(width: 80); - var table = new Table { Border = BorderKind.None }; + var table = new Table { BorderKind = BorderKind.None }; table.AddColumns("Foo", "Bar", "Baz"); table.AddRow("Qux", "Corgi", "Waldo"); table.AddRow("Grault", "Garply", "Fred"); diff --git a/src/Spectre.Console/Rendering/Grid.cs b/src/Spectre.Console/Rendering/Grid.cs index da733d4..7a3a433 100644 --- a/src/Spectre.Console/Rendering/Grid.cs +++ b/src/Spectre.Console/Rendering/Grid.cs @@ -1,7 +1,5 @@ using System; using System.Collections.Generic; -using System.Linq; -using Spectre.Console.Internal; using Spectre.Console.Rendering; namespace Spectre.Console @@ -13,6 +11,16 @@ namespace Spectre.Console { private readonly Table _table; + /// + /// Gets the number of columns in the table. + /// + public int ColumnCount => _table.ColumnCount; + + /// + /// Gets the number of rows in the table. + /// + public int RowCount => _table.RowCount; + /// /// Initializes a new instance of the class. /// @@ -20,7 +28,7 @@ namespace Spectre.Console { _table = new Table { - Border = BorderKind.None, + BorderKind = BorderKind.None, ShowHeaders = false, IsGrid = true, PadRightCell = false, @@ -75,45 +83,6 @@ namespace Spectre.Console }); } - /// - /// Adds a column to the grid. - /// - /// The number of columns to add. - public void AddColumns(int count) - { - for (var index = 0; index < count; index++) - { - AddColumn(new GridColumn()); - } - } - - /// - /// Adds a column to the grid. - /// - /// The columns to add. - public void AddColumns(params GridColumn[] columns) - { - if (columns is null) - { - throw new ArgumentNullException(nameof(columns)); - } - - foreach (var column in columns) - { - AddColumn(column); - } - } - - /// - /// Adds an empty row to the grid. - /// - public void AddEmptyRow() - { - var columns = new IRenderable[_table.ColumnCount]; - Enumerable.Range(0, _table.ColumnCount).ForEach(index => columns[index] = Text.Empty); - AddRow(columns); - } - /// /// Adds a new row to the grid. /// diff --git a/src/Spectre.Console/Rendering/GridExtensions.cs b/src/Spectre.Console/Rendering/GridExtensions.cs index 7fb8902..9508eb3 100644 --- a/src/Spectre.Console/Rendering/GridExtensions.cs +++ b/src/Spectre.Console/Rendering/GridExtensions.cs @@ -1,5 +1,7 @@ using System; using System.Linq; +using Spectre.Console.Internal; +using Spectre.Console.Rendering; namespace Spectre.Console { @@ -8,6 +10,63 @@ namespace Spectre.Console /// public static class GridExtensions { + /// + /// Adds a column to the grid. + /// + /// The grid to add the column to. + /// The number of columns to add. + public static void AddColumns(this Grid grid, int count) + { + if (grid is null) + { + throw new ArgumentNullException(nameof(grid)); + } + + for (var index = 0; index < count; index++) + { + grid.AddColumn(new GridColumn()); + } + } + + /// + /// Adds a column to the grid. + /// + /// The grid to add the column to. + /// The columns to add. + public static void AddColumns(this Grid grid, params GridColumn[] columns) + { + if (grid is null) + { + throw new ArgumentNullException(nameof(grid)); + } + + if (columns is null) + { + throw new ArgumentNullException(nameof(columns)); + } + + foreach (var column in columns) + { + grid.AddColumn(column); + } + } + + /// + /// Adds an empty row to the grid. + /// + /// The grid to add the row to. + public static void AddEmptyRow(this Grid grid) + { + if (grid is null) + { + throw new ArgumentNullException(nameof(grid)); + } + + var columns = new IRenderable[grid.ColumnCount]; + Enumerable.Range(0, grid.ColumnCount).ForEach(index => columns[index] = Text.Empty); + grid.AddRow(columns); + } + /// /// Adds a new row to the grid. /// diff --git a/src/Spectre.Console/Rendering/Panel.cs b/src/Spectre.Console/Rendering/Panel.cs index 200af4b..d83b1f9 100644 --- a/src/Spectre.Console/Rendering/Panel.cs +++ b/src/Spectre.Console/Rendering/Panel.cs @@ -16,13 +16,13 @@ namespace Spectre.Console private readonly IRenderable _child; /// - public BorderKind Border { get; set; } = BorderKind.Square; + public BorderKind BorderKind { get; set; } = BorderKind.Square; /// public bool SafeBorder { get; set; } = true; /// - public Color? BorderColor { get; set; } + public Style? BorderStyle { get; set; } /// /// Gets or sets a value indicating whether or not the panel should @@ -71,8 +71,8 @@ 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 border = SpectreBorder.GetBorder(BorderKind, (context.LegacyConsole || !context.Unicode) && SafeBorder); + var borderStyle = BorderStyle ?? Style.Plain; var paddingWidth = Padding.GetHorizontalPadding(); var childWidth = maxWidth - EdgeWidth - paddingWidth; diff --git a/src/Spectre.Console/Rendering/Table.cs b/src/Spectre.Console/Rendering/Table.cs index 2db2511..a1f47c7 100644 --- a/src/Spectre.Console/Rendering/Table.cs +++ b/src/Spectre.Console/Rendering/Table.cs @@ -26,10 +26,10 @@ namespace Spectre.Console public int RowCount => _rows.Count; /// - public BorderKind Border { get; set; } = BorderKind.Square; + public BorderKind BorderKind { get; set; } = BorderKind.Square; /// - public Color? BorderColor { get; set; } + public Style? BorderStyle { get; set; } /// public bool SafeBorder { get; set; } = true; @@ -168,13 +168,13 @@ namespace Spectre.Console throw new ArgumentNullException(nameof(context)); } - var border = SpectreBorder.GetBorder(Border, (context.LegacyConsole || !context.Unicode) && SafeBorder); - var borderStyle = new Style(BorderColor, null, null); + var border = SpectreBorder.GetBorder(BorderKind, (context.LegacyConsole || !context.Unicode) && SafeBorder); + var borderStyle = BorderStyle ?? Style.Plain; var tableWidth = maxWidth; - var showBorder = Border != BorderKind.None; - var hideBorder = Border == BorderKind.None; + var showBorder = BorderKind != BorderKind.None; + var hideBorder = BorderKind == BorderKind.None; var hasRows = _rows.Count > 0; if (Width != null) diff --git a/src/Spectre.Console/Rendering/Traits/Extensions/BorderExtensions.cs b/src/Spectre.Console/Rendering/Traits/Extensions/BorderExtensions.cs index 559056e..55a8085 100644 --- a/src/Spectre.Console/Rendering/Traits/Extensions/BorderExtensions.cs +++ b/src/Spectre.Console/Rendering/Traits/Extensions/BorderExtensions.cs @@ -8,13 +8,61 @@ namespace Spectre.Console.Rendering public static class BorderExtensions { /// - /// Sets the border. + /// Do not display a border. /// - /// The object that has a border. + /// An object type with a border. /// The object to set the border for. - /// The border to use. /// The same instance so that multiple calls can be chained. - public static T SetBorder(this T obj, BorderKind border) + public static T NoBorder(this T obj) + where T : class, IHasBorder + { + return SetBorderKind(obj, BorderKind.None); + } + + /// + /// Display a square border. + /// + /// An object type with a border. + /// The object to set the border for. + /// The same instance so that multiple calls can be chained. + public static T SquareBorder(this T obj) + where T : class, IHasBorder + { + return SetBorderKind(obj, BorderKind.Square); + } + + /// + /// Display an ASCII border. + /// + /// An object type with a border. + /// The object to set the border for. + /// The same instance so that multiple calls can be chained. + public static T AsciiBorder(this T obj) + where T : class, IHasBorder + { + return SetBorderKind(obj, BorderKind.Ascii); + } + + /// + /// Display a rounded border. + /// + /// An object type with a border. + /// The object to set the border for. + /// The same instance so that multiple calls can be chained. + public static T RoundedBorder(this T obj) + where T : class, IHasBorder + { + return SetBorderKind(obj, BorderKind.Rounded); + } + + /// + /// Sets the border kind. + /// + /// An object type with a border. + /// The object to set the border for. + /// The border kind to use. + /// The same instance so that multiple calls can be chained. + public static T SetBorderKind(this T obj, BorderKind border) where T : class, IHasBorder { if (obj is null) @@ -22,14 +70,14 @@ namespace Spectre.Console.Rendering throw new ArgumentNullException(nameof(obj)); } - obj.Border = border; + obj.BorderKind = border; return obj; } /// /// Disables the safe border. /// - /// The object that has a border. + /// An object type with a border. /// The object to set the border for. /// The same instance so that multiple calls can be chained. public static T NoSafeBorder(this T obj) @@ -44,12 +92,31 @@ namespace Spectre.Console.Rendering return obj; } + /// + /// Sets the border style. + /// + /// An object type with a border. + /// The object to set the border color for. + /// The border style to set. + /// The same instance so that multiple calls can be chained. + public static T SetBorderStyle(this T obj, Style style) + where T : class, IHasBorder + { + if (obj is null) + { + throw new ArgumentNullException(nameof(obj)); + } + + obj.BorderStyle = style; + return obj; + } + /// /// Sets the border color. /// - /// The object that has a border. + /// An object type with a border. /// The object to set the border color for. - /// The color to set. + /// The border color to set. /// The same instance so that multiple calls can be chained. public static T SetBorderColor(this T obj, Color color) where T : class, IHasBorder @@ -59,7 +126,7 @@ namespace Spectre.Console.Rendering throw new ArgumentNullException(nameof(obj)); } - obj.BorderColor = color; + obj.BorderStyle = (obj.BorderStyle ?? Style.Plain).WithForeground(color); return obj; } } diff --git a/src/Spectre.Console/Rendering/Traits/IHasBorder.cs b/src/Spectre.Console/Rendering/Traits/IHasBorder.cs index b69a79d..48831f0 100644 --- a/src/Spectre.Console/Rendering/Traits/IHasBorder.cs +++ b/src/Spectre.Console/Rendering/Traits/IHasBorder.cs @@ -15,11 +15,11 @@ namespace Spectre.Console /// /// Gets or sets the kind of border to use. /// - public BorderKind Border { get; set; } + public BorderKind BorderKind { get; set; } /// - /// Gets or sets the border color. + /// Gets or sets the border style. /// - public Color? BorderColor { get; set; } + public Style? BorderStyle { get; set; } } } diff --git a/src/Spectre.Console/Style.Factory.cs b/src/Spectre.Console/Style.Factory.cs new file mode 100644 index 0000000..63293d7 --- /dev/null +++ b/src/Spectre.Console/Style.Factory.cs @@ -0,0 +1,40 @@ +using System; + +namespace Spectre.Console +{ + /// + /// Represents color and text decoration. + /// + public sealed partial class Style : IEquatable