From 03334f693d4934e7d4f897cac9484059f702cddc Mon Sep 17 00:00:00 2001 From: Patrik Svensson Date: Sat, 24 Oct 2020 17:15:19 +0200 Subject: [PATCH] Add support for table footers --- examples/Borders/Program.cs | 4 +- examples/Tables/Program.cs | 16 +-- .../Unit/TableBorderTests.cs | 119 ++++++++++++------ src/Spectre.Console.Tests/Unit/TableTests.cs | 51 ++++++-- .../Extensions/AlignableExtensions.cs | 2 +- .../Obsolete/ObsoleteTableExtensions.cs | 103 +++++++++++++-- .../Extensions/TableColumnExtensions.cs | 55 ++++++++ .../Extensions/TableExtensions.cs | 102 +++++++++------ .../Rendering/Borders/TableBorderPart.cs | 20 +++ .../Borders/Tables/Ascii2TableBorder.cs | 4 + .../Tables/AsciiDoubleHeadTableBorder.cs | 4 + .../Borders/Tables/AsciiTableBorder.cs | 4 + .../Borders/Tables/DoubleEdgeTableBorder.cs | 4 + .../Borders/Tables/DoubleTableBorder.cs | 4 + .../Borders/Tables/HeavyEdgeTableBorder.cs | 4 + .../Borders/Tables/HeavyHeadTableBorder.cs | 4 + .../Borders/Tables/HeavyTableBorder.cs | 4 + .../Borders/Tables/HorizontalTableBorder.cs | 4 + .../Borders/Tables/MarkdownTableBorder.cs | 11 +- .../Tables/MinimalDoubleHeadTableBorder.cs | 4 + .../Tables/MinimalHeavyHeadTableBorder.cs | 4 + .../Borders/Tables/MinimalTableBorder.cs | 4 + .../Borders/Tables/RoundedTableBorder.cs | 4 + .../Borders/Tables/SimpleHeavyTableBorder.cs | 4 + .../Borders/Tables/SimpleTableBorder.cs | 4 + .../Borders/Tables/SquareTableBorder.cs | 4 + src/Spectre.Console/Rendering/TablePart.cs | 7 +- src/Spectre.Console/TableBorder.cs | 7 +- src/Spectre.Console/Widgets/Calendar.cs | 2 +- src/Spectre.Console/Widgets/Table.cs | 52 +++++--- src/Spectre.Console/Widgets/TableColumn.cs | 21 ++-- 31 files changed, 504 insertions(+), 132 deletions(-) create mode 100644 src/Spectre.Console/Extensions/TableColumnExtensions.cs diff --git a/examples/Borders/Program.cs b/examples/Borders/Program.cs index 868e352..e49b9d1 100644 --- a/examples/Borders/Program.cs +++ b/examples/Borders/Program.cs @@ -48,8 +48,8 @@ namespace BordersExample static IRenderable CreateTable(string name, TableBorder border) { var table = new Table().Border(border); - table.AddColumn("[yellow]Header 1[/]"); - table.AddColumn("[yellow]Header 2[/]", col => col.RightAligned()); + table.AddColumn("[yellow]Header 1[/]", c => c.Footer("[grey]Footer 1[/]")); + table.AddColumn("[yellow]Header 2[/]", col => col.Footer("[grey]Footer 2[/]").RightAligned()); table.AddRow("Cell", "Cell"); table.AddRow("Cell", "Cell"); diff --git a/examples/Tables/Program.cs b/examples/Tables/Program.cs index cec47f8..31fc3ae 100644 --- a/examples/Tables/Program.cs +++ b/examples/Tables/Program.cs @@ -18,9 +18,9 @@ namespace TableExample var simple = new Table() .Border(TableBorder.Square) .BorderColor(Color.Red) - .AddColumn(new TableColumn("[u]CDE[/]").Centered()) - .AddColumn(new TableColumn("[u]FED[/]")) - .AddColumn(new TableColumn("[u]IHG[/]")) + .AddColumn(new TableColumn("[u]CDE[/]").Footer("EDC").Centered()) + .AddColumn(new TableColumn("[u]FED[/]").Footer("DEF")) + .AddColumn(new TableColumn("[u]IHG[/]").Footer("GHI")) .AddRow("Hello", "[red]World![/]", "") .AddRow("[blue]Bonjour[/]", "[white]le[/]", "[red]monde![/]") .AddRow("[blue]Hej[/]", "[yellow]Världen![/]", ""); @@ -38,11 +38,11 @@ namespace TableExample return new Table() .Centered() .Border(TableBorder.DoubleEdge) - .Heading("TABLE [yellow]HEADING[/]") - .Footnote("TABLE [yellow]FOOTNOTE[/]") - .AddColumn(new TableColumn(new Panel("[u]ABC[/]").BorderColor(Color.Red))) - .AddColumn(new TableColumn(new Panel("[u]DEF[/]").BorderColor(Color.Green))) - .AddColumn(new TableColumn(new Panel("[u]GHI[/]").BorderColor(Color.Blue))) + .Title("TABLE [yellow]TITLE[/]") + .Caption("TABLE [yellow]CAPTION[/]") + .AddColumn(new TableColumn(new Panel("[u]ABC[/]").BorderColor(Color.Red)).Footer("[u]FOOTER 1[/]")) + .AddColumn(new TableColumn(new Panel("[u]DEF[/]").BorderColor(Color.Green)).Footer("[u]FOOTER 2[/]")) + .AddColumn(new TableColumn(new Panel("[u]GHI[/]").BorderColor(Color.Blue)).Footer("[u]FOOTER 3[/]")) .AddRow(new Text("Hello").Centered(), new Markup("[red]World![/]"), Text.Empty) .AddRow(second, new Text("Whaaat"), new Text("Lol")) .AddRow(new Markup("[blue]Hej[/]").Centered(), new Markup("[yellow]Världen![/]"), Text.Empty); diff --git a/src/Spectre.Console.Tests/Unit/TableBorderTests.cs b/src/Spectre.Console.Tests/Unit/TableBorderTests.cs index 72fe572..8b47969 100644 --- a/src/Spectre.Console.Tests/Unit/TableBorderTests.cs +++ b/src/Spectre.Console.Tests/Unit/TableBorderTests.cs @@ -42,10 +42,11 @@ namespace Spectre.Console.Tests.Unit console.Render(table); // Then - console.Lines.Count.ShouldBe(3); + console.Lines.Count.ShouldBe(4); console.Lines[0].ShouldBe("Header 1 Header 2"); console.Lines[1].ShouldBe("Cell Cell "); console.Lines[2].ShouldBe("Cell Cell "); + console.Lines[3].ShouldBe("Footer 1 Footer 2"); } } @@ -85,13 +86,15 @@ namespace Spectre.Console.Tests.Unit console.Render(table); // Then - console.Lines.Count.ShouldBe(6); + console.Lines.Count.ShouldBe(8); console.Lines[0].ShouldBe("+---------------------+"); console.Lines[1].ShouldBe("| Header 1 | Header 2 |"); console.Lines[2].ShouldBe("|----------+----------|"); console.Lines[3].ShouldBe("| Cell | Cell |"); console.Lines[4].ShouldBe("| Cell | Cell |"); - console.Lines[5].ShouldBe("+---------------------+"); + console.Lines[5].ShouldBe("|----------+----------|"); + console.Lines[6].ShouldBe("| Footer 1 | Footer 2 |"); + console.Lines[7].ShouldBe("+---------------------+"); } } @@ -131,13 +134,15 @@ namespace Spectre.Console.Tests.Unit console.Render(table); // Then - console.Lines.Count.ShouldBe(6); + console.Lines.Count.ShouldBe(8); console.Lines[0].ShouldBe("+----------+----------+"); console.Lines[1].ShouldBe("| Header 1 | Header 2 |"); console.Lines[2].ShouldBe("|----------+----------|"); console.Lines[3].ShouldBe("| Cell | Cell |"); console.Lines[4].ShouldBe("| Cell | Cell |"); - console.Lines[5].ShouldBe("+----------+----------+"); + console.Lines[5].ShouldBe("|----------+----------|"); + console.Lines[6].ShouldBe("| Footer 1 | Footer 2 |"); + console.Lines[7].ShouldBe("+----------+----------+"); } } @@ -177,13 +182,15 @@ namespace Spectre.Console.Tests.Unit console.Render(table); // Then - console.Lines.Count.ShouldBe(6); + console.Lines.Count.ShouldBe(8); console.Lines[0].ShouldBe("+----------+----------+"); console.Lines[1].ShouldBe("| Header 1 | Header 2 |"); console.Lines[2].ShouldBe("|==========+==========|"); console.Lines[3].ShouldBe("| Cell | Cell |"); console.Lines[4].ShouldBe("| Cell | Cell |"); console.Lines[5].ShouldBe("+----------+----------+"); + console.Lines[6].ShouldBe("| Footer 1 | Footer 2 |"); + console.Lines[7].ShouldBe("+----------+----------+"); } } @@ -223,13 +230,15 @@ namespace Spectre.Console.Tests.Unit console.Render(table); // Then - console.Lines.Count.ShouldBe(6); + console.Lines.Count.ShouldBe(8); console.Lines[0].ShouldBe("┌──────────┬──────────┐"); console.Lines[1].ShouldBe("│ Header 1 │ Header 2 │"); console.Lines[2].ShouldBe("├──────────┼──────────┤"); console.Lines[3].ShouldBe("│ Cell │ Cell │"); console.Lines[4].ShouldBe("│ Cell │ Cell │"); - console.Lines[5].ShouldBe("└──────────┴──────────┘"); + console.Lines[5].ShouldBe("├──────────┼──────────┤"); + console.Lines[6].ShouldBe("│ Footer 1 │ Footer 2 │"); + console.Lines[7].ShouldBe("└──────────┴──────────┘"); } } @@ -269,13 +278,15 @@ namespace Spectre.Console.Tests.Unit console.Render(table); // Then - console.Lines.Count.ShouldBe(6); + console.Lines.Count.ShouldBe(8); console.Lines[0].ShouldBe("╭──────────┬──────────╮"); console.Lines[1].ShouldBe("│ Header 1 │ Header 2 │"); console.Lines[2].ShouldBe("├──────────┼──────────┤"); console.Lines[3].ShouldBe("│ Cell │ Cell │"); console.Lines[4].ShouldBe("│ Cell │ Cell │"); - console.Lines[5].ShouldBe("╰──────────┴──────────╯"); + console.Lines[5].ShouldBe("├──────────┼──────────┤"); + console.Lines[6].ShouldBe("│ Footer 1 │ Footer 2 │"); + console.Lines[7].ShouldBe("╰──────────┴──────────╯"); } } @@ -315,13 +326,15 @@ namespace Spectre.Console.Tests.Unit console.Render(table); // Then - console.Lines.Count.ShouldBe(6); + console.Lines.Count.ShouldBe(8); console.Lines[0].ShouldBe(" "); console.Lines[1].ShouldBe(" Header 1 │ Header 2 "); console.Lines[2].ShouldBe(" ──────────┼────────── "); console.Lines[3].ShouldBe(" Cell │ Cell "); console.Lines[4].ShouldBe(" Cell │ Cell "); - console.Lines[5].ShouldBe(" "); + console.Lines[5].ShouldBe(" ──────────┼────────── "); + console.Lines[6].ShouldBe(" Footer 1 │ Footer 2 "); + console.Lines[7].ShouldBe(" "); } } @@ -361,13 +374,15 @@ namespace Spectre.Console.Tests.Unit console.Render(table); // Then - console.Lines.Count.ShouldBe(6); + console.Lines.Count.ShouldBe(8); console.Lines[0].ShouldBe(" "); console.Lines[1].ShouldBe(" Header 1 │ Header 2 "); console.Lines[2].ShouldBe(" ━━━━━━━━━━┿━━━━━━━━━━ "); console.Lines[3].ShouldBe(" Cell │ Cell "); console.Lines[4].ShouldBe(" Cell │ Cell "); - console.Lines[5].ShouldBe(" "); + console.Lines[5].ShouldBe(" ━━━━━━━━━━┿━━━━━━━━━━ "); + console.Lines[6].ShouldBe(" Footer 1 │ Footer 2 "); + console.Lines[7].ShouldBe(" "); } } @@ -407,13 +422,15 @@ namespace Spectre.Console.Tests.Unit console.Render(table); // Then - console.Lines.Count.ShouldBe(6); + console.Lines.Count.ShouldBe(8); console.Lines[0].ShouldBe(" "); console.Lines[1].ShouldBe(" Header 1 │ Header 2 "); console.Lines[2].ShouldBe(" ══════════╪══════════ "); console.Lines[3].ShouldBe(" Cell │ Cell "); console.Lines[4].ShouldBe(" Cell │ Cell "); - console.Lines[5].ShouldBe(" "); + console.Lines[5].ShouldBe(" ══════════╪══════════ "); + console.Lines[6].ShouldBe(" Footer 1 │ Footer 2 "); + console.Lines[7].ShouldBe(" "); } } @@ -453,13 +470,15 @@ namespace Spectre.Console.Tests.Unit console.Render(table); // Then - console.Lines.Count.ShouldBe(6); + console.Lines.Count.ShouldBe(8); console.Lines[0].ShouldBe(" "); console.Lines[1].ShouldBe(" Header 1 Header 2 "); console.Lines[2].ShouldBe("───────────────────────"); console.Lines[3].ShouldBe(" Cell Cell "); console.Lines[4].ShouldBe(" Cell Cell "); - console.Lines[5].ShouldBe(" "); + console.Lines[5].ShouldBe("───────────────────────"); + console.Lines[6].ShouldBe(" Footer 1 Footer 2 "); + console.Lines[7].ShouldBe(" "); } } @@ -499,13 +518,15 @@ namespace Spectre.Console.Tests.Unit console.Render(table); // Then - console.Lines.Count.ShouldBe(6); + console.Lines.Count.ShouldBe(8); console.Lines[0].ShouldBe("───────────────────────"); console.Lines[1].ShouldBe(" Header 1 Header 2 "); console.Lines[2].ShouldBe("───────────────────────"); console.Lines[3].ShouldBe(" Cell Cell "); console.Lines[4].ShouldBe(" Cell Cell "); console.Lines[5].ShouldBe("───────────────────────"); + console.Lines[6].ShouldBe(" Footer 1 Footer 2 "); + console.Lines[7].ShouldBe("───────────────────────"); } } @@ -545,13 +566,15 @@ namespace Spectre.Console.Tests.Unit console.Render(table); // Then - console.Lines.Count.ShouldBe(6); + console.Lines.Count.ShouldBe(8); console.Lines[0].ShouldBe(" "); console.Lines[1].ShouldBe(" Header 1 Header 2 "); console.Lines[2].ShouldBe("━━━━━━━━━━━━━━━━━━━━━━━"); console.Lines[3].ShouldBe(" Cell Cell "); console.Lines[4].ShouldBe(" Cell Cell "); - console.Lines[5].ShouldBe(" "); + console.Lines[5].ShouldBe("━━━━━━━━━━━━━━━━━━━━━━━"); + console.Lines[6].ShouldBe(" Footer 1 Footer 2 "); + console.Lines[7].ShouldBe(" "); } } @@ -591,13 +614,15 @@ namespace Spectre.Console.Tests.Unit console.Render(table); // Then - console.Lines.Count.ShouldBe(6); + console.Lines.Count.ShouldBe(8); console.Lines[0].ShouldBe("┏━━━━━━━━━━┳━━━━━━━━━━┓"); console.Lines[1].ShouldBe("┃ Header 1 ┃ Header 2 ┃"); console.Lines[2].ShouldBe("┣━━━━━━━━━━╋━━━━━━━━━━┫"); console.Lines[3].ShouldBe("┃ Cell ┃ Cell ┃"); console.Lines[4].ShouldBe("┃ Cell ┃ Cell ┃"); - console.Lines[5].ShouldBe("┗━━━━━━━━━━┻━━━━━━━━━━┛"); + console.Lines[5].ShouldBe("┣━━━━━━━━━━╋━━━━━━━━━━┫"); + console.Lines[6].ShouldBe("┃ Footer 1 ┃ Footer 2 ┃"); + console.Lines[7].ShouldBe("┗━━━━━━━━━━┻━━━━━━━━━━┛"); } } @@ -637,13 +662,15 @@ namespace Spectre.Console.Tests.Unit console.Render(table); // Then - console.Lines.Count.ShouldBe(6); + console.Lines.Count.ShouldBe(8); console.Lines[0].ShouldBe("┏━━━━━━━━━━┯━━━━━━━━━━┓"); console.Lines[1].ShouldBe("┃ Header 1 │ Header 2 ┃"); console.Lines[2].ShouldBe("┠──────────┼──────────┨"); console.Lines[3].ShouldBe("┃ Cell │ Cell ┃"); console.Lines[4].ShouldBe("┃ Cell │ Cell ┃"); - console.Lines[5].ShouldBe("┗━━━━━━━━━━┷━━━━━━━━━━┛"); + console.Lines[5].ShouldBe("┠──────────┼──────────┨"); + console.Lines[6].ShouldBe("┃ Footer 1 │ Footer 2 ┃"); + console.Lines[7].ShouldBe("┗━━━━━━━━━━┷━━━━━━━━━━┛"); } } @@ -683,13 +710,15 @@ namespace Spectre.Console.Tests.Unit console.Render(table); // Then - console.Lines.Count.ShouldBe(6); + console.Lines.Count.ShouldBe(8); console.Lines[0].ShouldBe("┏━━━━━━━━━━┳━━━━━━━━━━┓"); console.Lines[1].ShouldBe("┃ Header 1 ┃ Header 2 ┃"); console.Lines[2].ShouldBe("┡━━━━━━━━━━╇━━━━━━━━━━┩"); console.Lines[3].ShouldBe("│ Cell │ Cell │"); console.Lines[4].ShouldBe("│ Cell │ Cell │"); - console.Lines[5].ShouldBe("└──────────┴──────────┘"); + console.Lines[5].ShouldBe("├──────────┼──────────┤"); + console.Lines[6].ShouldBe("│ Footer 1 │ Footer 2 │"); + console.Lines[7].ShouldBe("└──────────┴──────────┘"); } } @@ -729,13 +758,15 @@ namespace Spectre.Console.Tests.Unit console.Render(table); // Then - console.Lines.Count.ShouldBe(6); + console.Lines.Count.ShouldBe(8); console.Lines[0].ShouldBe("╔══════════╦══════════╗"); console.Lines[1].ShouldBe("║ Header 1 ║ Header 2 ║"); console.Lines[2].ShouldBe("╠══════════╬══════════╣"); console.Lines[3].ShouldBe("║ Cell ║ Cell ║"); console.Lines[4].ShouldBe("║ Cell ║ Cell ║"); - console.Lines[5].ShouldBe("╚══════════╩══════════╝"); + console.Lines[5].ShouldBe("╠══════════╬══════════╣"); + console.Lines[6].ShouldBe("║ Footer 1 ║ Footer 2 ║"); + console.Lines[7].ShouldBe("╚══════════╩══════════╝"); } } @@ -775,13 +806,15 @@ namespace Spectre.Console.Tests.Unit console.Render(table); // Then - console.Lines.Count.ShouldBe(6); + console.Lines.Count.ShouldBe(8); console.Lines[0].ShouldBe("╔══════════╤══════════╗"); console.Lines[1].ShouldBe("║ Header 1 │ Header 2 ║"); console.Lines[2].ShouldBe("╟──────────┼──────────╢"); console.Lines[3].ShouldBe("║ Cell │ Cell ║"); console.Lines[4].ShouldBe("║ Cell │ Cell ║"); - console.Lines[5].ShouldBe("╚══════════╧══════════╝"); + console.Lines[5].ShouldBe("╟──────────┼──────────╢"); + console.Lines[6].ShouldBe("║ Footer 1 │ Footer 2 ║"); + console.Lines[7].ShouldBe("╚══════════╧══════════╝"); } } @@ -821,13 +854,14 @@ namespace Spectre.Console.Tests.Unit console.Render(table); // Then - console.Lines.Count.ShouldBe(6); + console.Lines.Count.ShouldBe(7); console.Lines[0].ShouldBe(" "); console.Lines[1].ShouldBe("| Header 1 | Header 2 |"); console.Lines[2].ShouldBe("| -------- | -------- |"); console.Lines[3].ShouldBe("| Cell | Cell |"); console.Lines[4].ShouldBe("| Cell | Cell |"); - console.Lines[5].ShouldBe(" "); + console.Lines[5].ShouldBe("| Footer 1 | Footer 2 |"); + console.Lines[6].ShouldBe(" "); } [Fact] @@ -841,13 +875,14 @@ namespace Spectre.Console.Tests.Unit console.Render(table); // Then - console.Lines.Count.ShouldBe(6); + console.Lines.Count.ShouldBe(7); console.Lines[0].ShouldBe(" "); console.Lines[1].ShouldBe("| Header 1 | Header 2 |"); console.Lines[2].ShouldBe("| -------- | :------- |"); console.Lines[3].ShouldBe("| Cell | Cell |"); console.Lines[4].ShouldBe("| Cell | Cell |"); - console.Lines[5].ShouldBe(" "); + console.Lines[5].ShouldBe("| Footer 1 | Footer 2 |"); + console.Lines[6].ShouldBe(" "); } [Fact] @@ -861,13 +896,14 @@ namespace Spectre.Console.Tests.Unit console.Render(table); // Then - console.Lines.Count.ShouldBe(6); + console.Lines.Count.ShouldBe(7); console.Lines[0].ShouldBe(" "); console.Lines[1].ShouldBe("| Header 1 | Header 2 |"); console.Lines[2].ShouldBe("| -------- | :------: |"); console.Lines[3].ShouldBe("| Cell | Cell |"); console.Lines[4].ShouldBe("| Cell | Cell |"); - console.Lines[5].ShouldBe(" "); + console.Lines[5].ShouldBe("| Footer 1 | Footer 2 |"); + console.Lines[6].ShouldBe(" "); } [Fact] @@ -881,13 +917,14 @@ namespace Spectre.Console.Tests.Unit console.Render(table); // Then - console.Lines.Count.ShouldBe(6); + console.Lines.Count.ShouldBe(7); console.Lines[0].ShouldBe(" "); console.Lines[1].ShouldBe("| Header 1 | Header 2 |"); console.Lines[2].ShouldBe("| -------- | -------: |"); console.Lines[3].ShouldBe("| Cell | Cell |"); console.Lines[4].ShouldBe("| Cell | Cell |"); - console.Lines[5].ShouldBe(" "); + console.Lines[5].ShouldBe("| Footer 1 | Footer 2 |"); + console.Lines[6].ShouldBe(" "); } } @@ -896,8 +933,8 @@ namespace Spectre.Console.Tests.Unit public static Table GetTable(Justify? header1 = null, Justify? header2 = null) { var table = new Table(); - table.AddColumn("Header 1", c => c.Alignment = header1); - table.AddColumn("Header 2", c => c.Alignment = header2); + table.AddColumn("Header 1", c => c.Alignment(header1).Footer("Footer 1")); + table.AddColumn("Header 2", c => c.Alignment(header2).Footer("Footer 2")); table.AddRow("Cell", "Cell"); table.AddRow("Cell", "Cell"); return table; diff --git a/src/Spectre.Console.Tests/Unit/TableTests.cs b/src/Spectre.Console.Tests/Unit/TableTests.cs index 2a64fdf..ae324c2 100644 --- a/src/Spectre.Console.Tests/Unit/TableTests.cs +++ b/src/Spectre.Console.Tests/Unit/TableTests.cs @@ -168,6 +168,33 @@ namespace Spectre.Console.Tests.Unit console.Lines[5].ShouldBe("└────────┴────────┴───────┘"); } + [Fact] + public void Should_Render_Table_With_Footers_Correctly() + { + // Given + var console = new PlainConsole(width: 80); + var table = new Table(); + table.AddColumn(new TableColumn("Foo").Footer("Oof").RightAligned()); + table.AddColumn("Bar"); + table.AddColumns(new TableColumn("Baz").Footer("Zab")); + table.AddRow("Qux", "Corgi", "Waldo"); + table.AddRow("Grault", "Garply", "Fred"); + + // When + console.Render(table); + + // Then + console.Lines.Count.ShouldBe(8); + console.Lines[0].ShouldBe("┌────────┬────────┬───────┐"); + console.Lines[1].ShouldBe("│ Foo │ Bar │ Baz │"); + console.Lines[2].ShouldBe("├────────┼────────┼───────┤"); + console.Lines[3].ShouldBe("│ Qux │ Corgi │ Waldo │"); + console.Lines[4].ShouldBe("│ Grault │ Garply │ Fred │"); + console.Lines[5].ShouldBe("├────────┼────────┼───────┤"); + console.Lines[6].ShouldBe("│ Oof │ │ Zab │"); + console.Lines[7].ShouldBe("└────────┴────────┴───────┘"); + } + [Fact] public void Should_Left_Align_Table_Correctly() { @@ -460,13 +487,13 @@ namespace Spectre.Console.Tests.Unit } [Fact] - public void Should_Render_Table_With_Title_And_Footnote_Correctly() + public void Should_Render_Table_With_Title_And_Caption_Correctly() { // Given var console = new PlainConsole(width: 80); var table = new Table { Border = TableBorder.Rounded }; - table.Heading = new TableTitle("Hello World"); - table.Footnote = new TableTitle("Goodbye World"); + table.Title = new TableTitle("Hello World"); + table.Caption = new TableTitle("Goodbye World"); table.AddColumns("Foo", "Bar", "Baz"); table.AddRow("Qux", "Corgi", "Waldo"); table.AddRow("Grault", "Garply", "Fred"); @@ -487,14 +514,14 @@ namespace Spectre.Console.Tests.Unit } [Fact] - public void Should_Left_Align_Table_With_Title_And_Footnote_Correctly() + public void Should_Left_Align_Table_With_Title_And_Caption_Correctly() { // Given var console = new PlainConsole(width: 80); var table = new Table { Border = TableBorder.Rounded }; table.LeftAligned(); - table.Heading = new TableTitle("Hello World"); - table.Footnote = new TableTitle("Goodbye World"); + table.Title = new TableTitle("Hello World"); + table.Caption = new TableTitle("Goodbye World"); table.AddColumns("Foo", "Bar", "Baz"); table.AddRow("Qux", "Corgi", "Waldo"); table.AddRow("Grault", "Garply", "Fred"); @@ -515,14 +542,14 @@ namespace Spectre.Console.Tests.Unit } [Fact] - public void Should_Center_Table_With_Title_And_Footnote_Correctly() + public void Should_Center_Table_With_Title_And_Caption_Correctly() { // Given var console = new PlainConsole(width: 80); var table = new Table { Border = TableBorder.Rounded }; table.Centered(); - table.Heading = new TableTitle("Hello World"); - table.Footnote = new TableTitle("Goodbye World"); + table.Title = new TableTitle("Hello World"); + table.Caption = new TableTitle("Goodbye World"); table.AddColumns("Foo", "Bar", "Baz"); table.AddRow("Qux", "Corgi", "Waldo"); table.AddRow("Grault", "Garply", "Fred"); @@ -543,14 +570,14 @@ namespace Spectre.Console.Tests.Unit } [Fact] - public void Should_Right_Align_Table_With_Title_And_Footnote_Correctly() + public void Should_Right_Align_Table_With_Title_And_Caption_Correctly() { // Given var console = new PlainConsole(width: 80); var table = new Table { Border = TableBorder.Rounded }; table.RightAligned(); - table.Heading = new TableTitle("Hello World"); - table.Footnote = new TableTitle("Goodbye World"); + table.Title = new TableTitle("Hello World"); + table.Caption = new TableTitle("Goodbye World"); table.AddColumns("Foo", "Bar", "Baz"); table.AddRow("Qux", "Corgi", "Waldo"); table.AddRow("Grault", "Garply", "Fred"); diff --git a/src/Spectre.Console/Extensions/AlignableExtensions.cs b/src/Spectre.Console/Extensions/AlignableExtensions.cs index f40a8d3..05693ae 100644 --- a/src/Spectre.Console/Extensions/AlignableExtensions.cs +++ b/src/Spectre.Console/Extensions/AlignableExtensions.cs @@ -12,7 +12,7 @@ namespace Spectre.Console /// The alignable object. /// The alignment. /// The same instance so that multiple calls can be chained. - public static T Alignment(this T obj, Justify alignment) + public static T Alignment(this T obj, Justify? alignment) where T : class, IAlignable { if (obj is null) diff --git a/src/Spectre.Console/Extensions/Obsolete/ObsoleteTableExtensions.cs b/src/Spectre.Console/Extensions/Obsolete/ObsoleteTableExtensions.cs index 0a588ed..2005165 100644 --- a/src/Spectre.Console/Extensions/Obsolete/ObsoleteTableExtensions.cs +++ b/src/Spectre.Console/Extensions/Obsolete/ObsoleteTableExtensions.cs @@ -1,7 +1,5 @@ using System; -using System.Linq; -using Spectre.Console.Internal; -using Spectre.Console.Rendering; +using System.ComponentModel; namespace Spectre.Console { @@ -17,6 +15,7 @@ namespace Spectre.Console /// The width. /// The same instance so that multiple calls can be chained. [Obsolete("Use Width(..) instead.")] + [EditorBrowsable(EditorBrowsableState.Never)] public static Table SetWidth(this Table table, int width) { if (table is null) @@ -36,6 +35,7 @@ namespace Spectre.Console /// The style. /// The same instance so that multiple calls can be chained. [Obsolete("Use Heading(..) instead.")] + [EditorBrowsable(EditorBrowsableState.Never)] public static Table SetHeading(this Table table, string text, Style? style = null) { if (table is null) @@ -58,6 +58,7 @@ namespace Spectre.Console /// The heading. /// The same instance so that multiple calls can be chained. [Obsolete("Use Heading(..) instead.")] + [EditorBrowsable(EditorBrowsableState.Never)] public static Table SetHeading(this Table table, TableTitle heading) { if (table is null) @@ -65,7 +66,7 @@ namespace Spectre.Console throw new ArgumentNullException(nameof(table)); } - table.Heading = heading; + table.Title = heading; return table; } @@ -76,7 +77,8 @@ namespace Spectre.Console /// The footnote. /// The style. /// The same instance so that multiple calls can be chained. - [Obsolete("Use Footnote(..) instead.")] + [Obsolete("Use Caption(..) instead.")] + [EditorBrowsable(EditorBrowsableState.Never)] public static Table SetFootnote(this Table table, string text, Style? style = null) { if (table is null) @@ -98,7 +100,8 @@ namespace Spectre.Console /// The table. /// The footnote. /// The same instance so that multiple calls can be chained. - [Obsolete("Use Footnote(..) instead.")] + [Obsolete("Use Caption(..) instead.")] + [EditorBrowsable(EditorBrowsableState.Never)] public static Table SetFootnote(this Table table, TableTitle footnote) { if (table is null) @@ -106,7 +109,93 @@ namespace Spectre.Console throw new ArgumentNullException(nameof(table)); } - table.Footnote = footnote; + table.Caption = footnote; + return table; + } + + /// + /// Sets the table footnote. + /// + /// The table. + /// The footnote. + /// The style. + /// The same instance so that multiple calls can be chained. + [Obsolete("Use Caption(..) instead.")] + [EditorBrowsable(EditorBrowsableState.Never)] + public static Table Footnote(this Table table, string text, Style? style = null) + { + if (table is null) + { + throw new ArgumentNullException(nameof(table)); + } + + if (text is null) + { + throw new ArgumentNullException(nameof(text)); + } + + return Footnote(table, new TableTitle(text, style)); + } + + /// + /// Sets the table footnote. + /// + /// The table. + /// The footnote. + /// The same instance so that multiple calls can be chained. + [Obsolete("Use Caption(..) instead.")] + [EditorBrowsable(EditorBrowsableState.Never)] + public static Table Footnote(this Table table, TableTitle footnote) + { + if (table is null) + { + throw new ArgumentNullException(nameof(table)); + } + + table.Caption = footnote; + return table; + } + + /// + /// Sets the table heading. + /// + /// The table. + /// The heading. + /// The style. + /// The same instance so that multiple calls can be chained. + [Obsolete("Use Title(..) instead.")] + [EditorBrowsable(EditorBrowsableState.Never)] + public static Table Heading(this Table table, string text, Style? style = null) + { + if (table is null) + { + throw new ArgumentNullException(nameof(table)); + } + + if (text is null) + { + throw new ArgumentNullException(nameof(text)); + } + + return Heading(table, new TableTitle(text, style)); + } + + /// + /// Sets the table heading. + /// + /// The table. + /// The heading. + /// The same instance so that multiple calls can be chained. + [Obsolete("Use Title(..) instead.")] + [EditorBrowsable(EditorBrowsableState.Never)] + public static Table Heading(this Table table, TableTitle heading) + { + if (table is null) + { + throw new ArgumentNullException(nameof(table)); + } + + table.Title = heading; return table; } } diff --git a/src/Spectre.Console/Extensions/TableColumnExtensions.cs b/src/Spectre.Console/Extensions/TableColumnExtensions.cs new file mode 100644 index 0000000..37c8c64 --- /dev/null +++ b/src/Spectre.Console/Extensions/TableColumnExtensions.cs @@ -0,0 +1,55 @@ +using System; +using Spectre.Console.Rendering; + +namespace Spectre.Console +{ + /// + /// Contains extension methods for . + /// + public static class TableColumnExtensions + { + /// + /// Sets the table column footer. + /// + /// The table column. + /// The table column markup text. + /// The same instance so that multiple calls can be chained. + public static TableColumn Footer(this TableColumn column, string footer) + { + if (column is null) + { + throw new ArgumentNullException(nameof(column)); + } + + if (footer is null) + { + throw new ArgumentNullException(nameof(footer)); + } + + column.Footer = new Markup(footer); + return column; + } + + /// + /// Sets the table column footer. + /// + /// The table column. + /// The table column footer. + /// The same instance so that multiple calls can be chained. + public static TableColumn Footer(this TableColumn column, IRenderable footer) + { + if (column is null) + { + throw new ArgumentNullException(nameof(column)); + } + + if (footer is null) + { + throw new ArgumentNullException(nameof(footer)); + } + + column.Footer = footer; + return column; + } + } +} diff --git a/src/Spectre.Console/Extensions/TableExtensions.cs b/src/Spectre.Console/Extensions/TableExtensions.cs index 5024a8c..3f93b06 100644 --- a/src/Spectre.Console/Extensions/TableExtensions.cs +++ b/src/Spectre.Console/Extensions/TableExtensions.cs @@ -178,52 +178,45 @@ namespace Spectre.Console } /// - /// Sets the table heading. + /// Shows table footers. /// /// The table. - /// The heading. - /// The style. /// The same instance so that multiple calls can be chained. - public static Table Heading(this Table table, string text, Style? style = null) + public static Table ShowFooters(this Table table) { if (table is null) { throw new ArgumentNullException(nameof(table)); } - if (text is null) - { - throw new ArgumentNullException(nameof(text)); - } - - return Heading(table, new TableTitle(text, style)); - } - - /// - /// Sets the table heading. - /// - /// The table. - /// The heading. - /// The same instance so that multiple calls can be chained. - public static Table Heading(this Table table, TableTitle heading) - { - if (table is null) - { - throw new ArgumentNullException(nameof(table)); - } - - table.Heading = heading; + table.ShowFooters = true; return table; } /// - /// Sets the table footnote. + /// Hides table footers. /// /// The table. - /// The footnote. - /// The style. /// The same instance so that multiple calls can be chained. - public static Table Footnote(this Table table, string text, Style? style = null) + public static Table HideFooters(this Table table) + { + if (table is null) + { + throw new ArgumentNullException(nameof(table)); + } + + table.ShowFooters = false; + return table; + } + + /// + /// Sets the table title. + /// + /// The table. + /// The table title markup text. + /// The table title style. + /// The same instance so that multiple calls can be chained. + public static Table Title(this Table table, string text, Style? style = null) { if (table is null) { @@ -235,23 +228,62 @@ namespace Spectre.Console throw new ArgumentNullException(nameof(text)); } - return Footnote(table, new TableTitle(text, style)); + return Title(table, new TableTitle(text, style)); } /// - /// Sets the table footnote. + /// Sets the table title. /// /// The table. - /// The footnote. + /// The table title. /// The same instance so that multiple calls can be chained. - public static Table Footnote(this Table table, TableTitle footnote) + public static Table Title(this Table table, TableTitle title) { if (table is null) { throw new ArgumentNullException(nameof(table)); } - table.Footnote = footnote; + table.Title = title; + return table; + } + + /// + /// Sets the table caption. + /// + /// The table. + /// The caption markup text. + /// The style. + /// The same instance so that multiple calls can be chained. + public static Table Caption(this Table table, string text, Style? style = null) + { + if (table is null) + { + throw new ArgumentNullException(nameof(table)); + } + + if (text is null) + { + throw new ArgumentNullException(nameof(text)); + } + + return Caption(table, new TableTitle(text, style)); + } + + /// + /// Sets the table caption. + /// + /// The table. + /// The caption. + /// The same instance so that multiple calls can be chained. + public static Table Caption(this Table table, TableTitle caption) + { + if (table is null) + { + throw new ArgumentNullException(nameof(table)); + } + + table.Caption = caption; return table; } } diff --git a/src/Spectre.Console/Rendering/Borders/TableBorderPart.cs b/src/Spectre.Console/Rendering/Borders/TableBorderPart.cs index 370ad20..34b4af0 100644 --- a/src/Spectre.Console/Rendering/Borders/TableBorderPart.cs +++ b/src/Spectre.Console/Rendering/Borders/TableBorderPart.cs @@ -60,6 +60,26 @@ namespace Spectre.Console.Rendering /// HeaderBottomRight, + /// + /// The top left part of a footer. + /// + FooterTopLeft, + + /// + /// The top part of a footer. + /// + FooterTop, + + /// + /// The top separator part of a footer. + /// + FooterTopSeparator, + + /// + /// The top right part of a footer. + /// + FooterTopRight, + /// /// The left part of a cell. /// diff --git a/src/Spectre.Console/Rendering/Borders/Tables/Ascii2TableBorder.cs b/src/Spectre.Console/Rendering/Borders/Tables/Ascii2TableBorder.cs index 39df6a6..b4c47fc 100644 --- a/src/Spectre.Console/Rendering/Borders/Tables/Ascii2TableBorder.cs +++ b/src/Spectre.Console/Rendering/Borders/Tables/Ascii2TableBorder.cs @@ -26,6 +26,10 @@ namespace Spectre.Console.Rendering TableBorderPart.CellLeft => "|", TableBorderPart.CellSeparator => "|", TableBorderPart.CellRight => "|", + TableBorderPart.FooterTopLeft => "|", + TableBorderPart.FooterTop => "-", + TableBorderPart.FooterTopSeparator => "+", + TableBorderPart.FooterTopRight => "|", TableBorderPart.FooterBottomLeft => "+", TableBorderPart.FooterBottom => "-", TableBorderPart.FooterBottomSeparator => "+", diff --git a/src/Spectre.Console/Rendering/Borders/Tables/AsciiDoubleHeadTableBorder.cs b/src/Spectre.Console/Rendering/Borders/Tables/AsciiDoubleHeadTableBorder.cs index b4ca73d..c565e5a 100644 --- a/src/Spectre.Console/Rendering/Borders/Tables/AsciiDoubleHeadTableBorder.cs +++ b/src/Spectre.Console/Rendering/Borders/Tables/AsciiDoubleHeadTableBorder.cs @@ -26,6 +26,10 @@ namespace Spectre.Console.Rendering TableBorderPart.CellLeft => "|", TableBorderPart.CellSeparator => "|", TableBorderPart.CellRight => "|", + TableBorderPart.FooterTopLeft => "+", + TableBorderPart.FooterTop => "-", + TableBorderPart.FooterTopSeparator => "+", + TableBorderPart.FooterTopRight => "+", TableBorderPart.FooterBottomLeft => "+", TableBorderPart.FooterBottom => "-", TableBorderPart.FooterBottomSeparator => "+", diff --git a/src/Spectre.Console/Rendering/Borders/Tables/AsciiTableBorder.cs b/src/Spectre.Console/Rendering/Borders/Tables/AsciiTableBorder.cs index 932d717..6eb4000 100644 --- a/src/Spectre.Console/Rendering/Borders/Tables/AsciiTableBorder.cs +++ b/src/Spectre.Console/Rendering/Borders/Tables/AsciiTableBorder.cs @@ -26,6 +26,10 @@ namespace Spectre.Console.Rendering TableBorderPart.CellLeft => "|", TableBorderPart.CellSeparator => "|", TableBorderPart.CellRight => "|", + TableBorderPart.FooterTopLeft => "|", + TableBorderPart.FooterTop => "-", + TableBorderPart.FooterTopSeparator => "+", + TableBorderPart.FooterTopRight => "|", TableBorderPart.FooterBottomLeft => "+", TableBorderPart.FooterBottom => "-", TableBorderPart.FooterBottomSeparator => "-", diff --git a/src/Spectre.Console/Rendering/Borders/Tables/DoubleEdgeTableBorder.cs b/src/Spectre.Console/Rendering/Borders/Tables/DoubleEdgeTableBorder.cs index 1bc7733..e21c9ee 100644 --- a/src/Spectre.Console/Rendering/Borders/Tables/DoubleEdgeTableBorder.cs +++ b/src/Spectre.Console/Rendering/Borders/Tables/DoubleEdgeTableBorder.cs @@ -26,6 +26,10 @@ namespace Spectre.Console.Rendering TableBorderPart.CellLeft => "║", TableBorderPart.CellSeparator => "│", TableBorderPart.CellRight => "║", + TableBorderPart.FooterTopLeft => "╟", + TableBorderPart.FooterTop => "─", + TableBorderPart.FooterTopSeparator => "┼", + TableBorderPart.FooterTopRight => "╢", TableBorderPart.FooterBottomLeft => "╚", TableBorderPart.FooterBottom => "═", TableBorderPart.FooterBottomSeparator => "╧", diff --git a/src/Spectre.Console/Rendering/Borders/Tables/DoubleTableBorder.cs b/src/Spectre.Console/Rendering/Borders/Tables/DoubleTableBorder.cs index e88f3f5..3375101 100644 --- a/src/Spectre.Console/Rendering/Borders/Tables/DoubleTableBorder.cs +++ b/src/Spectre.Console/Rendering/Borders/Tables/DoubleTableBorder.cs @@ -26,6 +26,10 @@ namespace Spectre.Console.Rendering TableBorderPart.CellLeft => "║", TableBorderPart.CellSeparator => "║", TableBorderPart.CellRight => "║", + TableBorderPart.FooterTopLeft => "╠", + TableBorderPart.FooterTop => "═", + TableBorderPart.FooterTopSeparator => "╬", + TableBorderPart.FooterTopRight => "╣", TableBorderPart.FooterBottomLeft => "╚", TableBorderPart.FooterBottom => "═", TableBorderPart.FooterBottomSeparator => "╩", diff --git a/src/Spectre.Console/Rendering/Borders/Tables/HeavyEdgeTableBorder.cs b/src/Spectre.Console/Rendering/Borders/Tables/HeavyEdgeTableBorder.cs index 5e8d021..813828d 100644 --- a/src/Spectre.Console/Rendering/Borders/Tables/HeavyEdgeTableBorder.cs +++ b/src/Spectre.Console/Rendering/Borders/Tables/HeavyEdgeTableBorder.cs @@ -29,6 +29,10 @@ namespace Spectre.Console.Rendering TableBorderPart.CellLeft => "┃", TableBorderPart.CellSeparator => "│", TableBorderPart.CellRight => "┃", + TableBorderPart.FooterTopLeft => "┠", + TableBorderPart.FooterTop => "─", + TableBorderPart.FooterTopSeparator => "┼", + TableBorderPart.FooterTopRight => "┨", TableBorderPart.FooterBottomLeft => "┗", TableBorderPart.FooterBottom => "━", TableBorderPart.FooterBottomSeparator => "┷", diff --git a/src/Spectre.Console/Rendering/Borders/Tables/HeavyHeadTableBorder.cs b/src/Spectre.Console/Rendering/Borders/Tables/HeavyHeadTableBorder.cs index 3555128..0cf3dbe 100644 --- a/src/Spectre.Console/Rendering/Borders/Tables/HeavyHeadTableBorder.cs +++ b/src/Spectre.Console/Rendering/Borders/Tables/HeavyHeadTableBorder.cs @@ -29,6 +29,10 @@ namespace Spectre.Console.Rendering TableBorderPart.CellLeft => "│", TableBorderPart.CellSeparator => "│", TableBorderPart.CellRight => "│", + TableBorderPart.FooterTopLeft => "├", + TableBorderPart.FooterTop => "─", + TableBorderPart.FooterTopSeparator => "┼", + TableBorderPart.FooterTopRight => "┤", TableBorderPart.FooterBottomLeft => "└", TableBorderPart.FooterBottom => "─", TableBorderPart.FooterBottomSeparator => "┴", diff --git a/src/Spectre.Console/Rendering/Borders/Tables/HeavyTableBorder.cs b/src/Spectre.Console/Rendering/Borders/Tables/HeavyTableBorder.cs index c650253..3cd1190 100644 --- a/src/Spectre.Console/Rendering/Borders/Tables/HeavyTableBorder.cs +++ b/src/Spectre.Console/Rendering/Borders/Tables/HeavyTableBorder.cs @@ -29,6 +29,10 @@ namespace Spectre.Console.Rendering TableBorderPart.CellLeft => "┃", TableBorderPart.CellSeparator => "┃", TableBorderPart.CellRight => "┃", + TableBorderPart.FooterTopLeft => "┣", + TableBorderPart.FooterTop => "━", + TableBorderPart.FooterTopSeparator => "╋", + TableBorderPart.FooterTopRight => "┫", TableBorderPart.FooterBottomLeft => "┗", TableBorderPart.FooterBottom => "━", TableBorderPart.FooterBottomSeparator => "┻", diff --git a/src/Spectre.Console/Rendering/Borders/Tables/HorizontalTableBorder.cs b/src/Spectre.Console/Rendering/Borders/Tables/HorizontalTableBorder.cs index b9c121b..1464f7b 100644 --- a/src/Spectre.Console/Rendering/Borders/Tables/HorizontalTableBorder.cs +++ b/src/Spectre.Console/Rendering/Borders/Tables/HorizontalTableBorder.cs @@ -26,6 +26,10 @@ namespace Spectre.Console.Rendering TableBorderPart.CellLeft => " ", TableBorderPart.CellSeparator => " ", TableBorderPart.CellRight => " ", + TableBorderPart.FooterTopLeft => "─", + TableBorderPart.FooterTop => "─", + TableBorderPart.FooterTopSeparator => "─", + TableBorderPart.FooterTopRight => "─", TableBorderPart.FooterBottomLeft => "─", TableBorderPart.FooterBottom => "─", TableBorderPart.FooterBottomSeparator => "─", diff --git a/src/Spectre.Console/Rendering/Borders/Tables/MarkdownTableBorder.cs b/src/Spectre.Console/Rendering/Borders/Tables/MarkdownTableBorder.cs index 840e73e..6654f68 100644 --- a/src/Spectre.Console/Rendering/Borders/Tables/MarkdownTableBorder.cs +++ b/src/Spectre.Console/Rendering/Borders/Tables/MarkdownTableBorder.cs @@ -29,6 +29,10 @@ namespace Spectre.Console.Rendering TableBorderPart.CellLeft => "|", TableBorderPart.CellSeparator => "|", TableBorderPart.CellRight => "|", + TableBorderPart.FooterTopLeft => " ", + TableBorderPart.FooterTop => " ", + TableBorderPart.FooterTopSeparator => " ", + TableBorderPart.FooterTopRight => " ", TableBorderPart.FooterBottomLeft => " ", TableBorderPart.FooterBottom => " ", TableBorderPart.FooterBottomSeparator => " ", @@ -40,7 +44,12 @@ namespace Spectre.Console.Rendering /// public override string GetColumnRow(TablePart part, IReadOnlyList widths, IReadOnlyList columns) { - if (part != TablePart.Separator) + if (part == TablePart.FooterSeparator) + { + return string.Empty; + } + + if (part != TablePart.HeaderSeparator) { return base.GetColumnRow(part, widths, columns); } diff --git a/src/Spectre.Console/Rendering/Borders/Tables/MinimalDoubleHeadTableBorder.cs b/src/Spectre.Console/Rendering/Borders/Tables/MinimalDoubleHeadTableBorder.cs index b644880..817d707 100644 --- a/src/Spectre.Console/Rendering/Borders/Tables/MinimalDoubleHeadTableBorder.cs +++ b/src/Spectre.Console/Rendering/Borders/Tables/MinimalDoubleHeadTableBorder.cs @@ -26,6 +26,10 @@ namespace Spectre.Console.Rendering TableBorderPart.CellLeft => " ", TableBorderPart.CellSeparator => "│", TableBorderPart.CellRight => " ", + TableBorderPart.FooterTopLeft => " ", + TableBorderPart.FooterTop => "═", + TableBorderPart.FooterTopSeparator => "╪", + TableBorderPart.FooterTopRight => " ", TableBorderPart.FooterBottomLeft => " ", TableBorderPart.FooterBottom => " ", TableBorderPart.FooterBottomSeparator => " ", diff --git a/src/Spectre.Console/Rendering/Borders/Tables/MinimalHeavyHeadTableBorder.cs b/src/Spectre.Console/Rendering/Borders/Tables/MinimalHeavyHeadTableBorder.cs index 9a32bf7..10f01cf 100644 --- a/src/Spectre.Console/Rendering/Borders/Tables/MinimalHeavyHeadTableBorder.cs +++ b/src/Spectre.Console/Rendering/Borders/Tables/MinimalHeavyHeadTableBorder.cs @@ -29,6 +29,10 @@ namespace Spectre.Console.Rendering TableBorderPart.CellLeft => " ", TableBorderPart.CellSeparator => "│", TableBorderPart.CellRight => " ", + TableBorderPart.FooterTopLeft => " ", + TableBorderPart.FooterTop => "━", + TableBorderPart.FooterTopSeparator => "┿", + TableBorderPart.FooterTopRight => " ", TableBorderPart.FooterBottomLeft => " ", TableBorderPart.FooterBottom => " ", TableBorderPart.FooterBottomSeparator => " ", diff --git a/src/Spectre.Console/Rendering/Borders/Tables/MinimalTableBorder.cs b/src/Spectre.Console/Rendering/Borders/Tables/MinimalTableBorder.cs index 8b14617..9893435 100644 --- a/src/Spectre.Console/Rendering/Borders/Tables/MinimalTableBorder.cs +++ b/src/Spectre.Console/Rendering/Borders/Tables/MinimalTableBorder.cs @@ -26,6 +26,10 @@ namespace Spectre.Console.Rendering TableBorderPart.CellLeft => " ", TableBorderPart.CellSeparator => "│", TableBorderPart.CellRight => " ", + TableBorderPart.FooterTopLeft => " ", + TableBorderPart.FooterTop => "─", + TableBorderPart.FooterTopSeparator => "┼", + TableBorderPart.FooterTopRight => " ", TableBorderPart.FooterBottomLeft => " ", TableBorderPart.FooterBottom => " ", TableBorderPart.FooterBottomSeparator => " ", diff --git a/src/Spectre.Console/Rendering/Borders/Tables/RoundedTableBorder.cs b/src/Spectre.Console/Rendering/Borders/Tables/RoundedTableBorder.cs index 6cce42e..38b90af 100644 --- a/src/Spectre.Console/Rendering/Borders/Tables/RoundedTableBorder.cs +++ b/src/Spectre.Console/Rendering/Borders/Tables/RoundedTableBorder.cs @@ -29,6 +29,10 @@ namespace Spectre.Console.Rendering TableBorderPart.CellLeft => "│", TableBorderPart.CellSeparator => "│", TableBorderPart.CellRight => "│", + TableBorderPart.FooterTopLeft => "├", + TableBorderPart.FooterTop => "─", + TableBorderPart.FooterTopSeparator => "┼", + TableBorderPart.FooterTopRight => "┤", TableBorderPart.FooterBottomLeft => "╰", TableBorderPart.FooterBottom => "─", TableBorderPart.FooterBottomSeparator => "┴", diff --git a/src/Spectre.Console/Rendering/Borders/Tables/SimpleHeavyTableBorder.cs b/src/Spectre.Console/Rendering/Borders/Tables/SimpleHeavyTableBorder.cs index dd43d93..1f56178 100644 --- a/src/Spectre.Console/Rendering/Borders/Tables/SimpleHeavyTableBorder.cs +++ b/src/Spectre.Console/Rendering/Borders/Tables/SimpleHeavyTableBorder.cs @@ -29,6 +29,10 @@ namespace Spectre.Console.Rendering TableBorderPart.CellLeft => " ", TableBorderPart.CellSeparator => " ", TableBorderPart.CellRight => " ", + TableBorderPart.FooterTopLeft => "━", + TableBorderPart.FooterTop => "━", + TableBorderPart.FooterTopSeparator => "━", + TableBorderPart.FooterTopRight => "━", TableBorderPart.FooterBottomLeft => " ", TableBorderPart.FooterBottom => " ", TableBorderPart.FooterBottomSeparator => " ", diff --git a/src/Spectre.Console/Rendering/Borders/Tables/SimpleTableBorder.cs b/src/Spectre.Console/Rendering/Borders/Tables/SimpleTableBorder.cs index beadd02..eba1155 100644 --- a/src/Spectre.Console/Rendering/Borders/Tables/SimpleTableBorder.cs +++ b/src/Spectre.Console/Rendering/Borders/Tables/SimpleTableBorder.cs @@ -26,6 +26,10 @@ namespace Spectre.Console.Rendering TableBorderPart.CellLeft => " ", TableBorderPart.CellSeparator => " ", TableBorderPart.CellRight => " ", + TableBorderPart.FooterTopLeft => "─", + TableBorderPart.FooterTop => "─", + TableBorderPart.FooterTopSeparator => "─", + TableBorderPart.FooterTopRight => "─", TableBorderPart.FooterBottomLeft => " ", TableBorderPart.FooterBottom => " ", TableBorderPart.FooterBottomSeparator => " ", diff --git a/src/Spectre.Console/Rendering/Borders/Tables/SquareTableBorder.cs b/src/Spectre.Console/Rendering/Borders/Tables/SquareTableBorder.cs index 92cf072..e1829c6 100644 --- a/src/Spectre.Console/Rendering/Borders/Tables/SquareTableBorder.cs +++ b/src/Spectre.Console/Rendering/Borders/Tables/SquareTableBorder.cs @@ -26,6 +26,10 @@ namespace Spectre.Console.Rendering TableBorderPart.CellLeft => "│", TableBorderPart.CellSeparator => "│", TableBorderPart.CellRight => "│", + TableBorderPart.FooterTopLeft => "├", + TableBorderPart.FooterTop => "─", + TableBorderPart.FooterTopSeparator => "┼", + TableBorderPart.FooterTopRight => "┤", TableBorderPart.FooterBottomLeft => "└", TableBorderPart.FooterBottom => "─", TableBorderPart.FooterBottomSeparator => "┴", diff --git a/src/Spectre.Console/Rendering/TablePart.cs b/src/Spectre.Console/Rendering/TablePart.cs index 6c86d01..80cb7be 100644 --- a/src/Spectre.Console/Rendering/TablePart.cs +++ b/src/Spectre.Console/Rendering/TablePart.cs @@ -13,7 +13,12 @@ namespace Spectre.Console.Rendering /// /// The separator between the header and the cells. /// - Separator, + HeaderSeparator, + + /// + /// The separator between the footer and the cells. + /// + FooterSeparator, /// /// The bottom of a table. diff --git a/src/Spectre.Console/TableBorder.cs b/src/Spectre.Console/TableBorder.cs index 7dd937f..544d7b9 100644 --- a/src/Spectre.Console/TableBorder.cs +++ b/src/Spectre.Console/TableBorder.cs @@ -83,10 +83,15 @@ namespace Spectre.Console GetPart(TableBorderPart.HeaderTopSeparator), GetPart(TableBorderPart.HeaderTopRight)), // Separator between header and cells - TablePart.Separator => + TablePart.HeaderSeparator => (GetPart(TableBorderPart.HeaderBottomLeft), GetPart(TableBorderPart.HeaderBottom), GetPart(TableBorderPart.HeaderBottomSeparator), GetPart(TableBorderPart.HeaderBottomRight)), + // Separator between footer and cells + TablePart.FooterSeparator => + (GetPart(TableBorderPart.FooterTopLeft), GetPart(TableBorderPart.FooterTop), + GetPart(TableBorderPart.FooterTopSeparator), GetPart(TableBorderPart.FooterTopRight)), + // Bottom part TablePart.Bottom => (GetPart(TableBorderPart.FooterBottomLeft), GetPart(TableBorderPart.FooterBottom), diff --git a/src/Spectre.Console/Widgets/Calendar.cs b/src/Spectre.Console/Widgets/Calendar.cs index a1e665a..57eede5 100644 --- a/src/Spectre.Console/Widgets/Calendar.cs +++ b/src/Spectre.Console/Widgets/Calendar.cs @@ -209,7 +209,7 @@ namespace Spectre.Console if (ShowHeader) { var heading = new DateTime(Year, Month, Day).ToString("Y", culture).EscapeMarkup(); - table.Heading = new TableTitle(heading, HeaderStyle); + table.Title = new TableTitle(heading, HeaderStyle); } // Add columns diff --git a/src/Spectre.Console/Widgets/Table.cs b/src/Spectre.Console/Widgets/Table.cs index 03417ee..0208dce 100644 --- a/src/Spectre.Console/Widgets/Table.cs +++ b/src/Spectre.Console/Widgets/Table.cs @@ -17,7 +17,7 @@ namespace Spectre.Console private readonly List> _rows; private static Style _defaultHeadingStyle = new Style(Color.Silver); - private static Style _defaultFootnoteStyle = new Style(Color.Grey); + private static Style _defaultCaptionStyle = new Style(Color.Grey); /// /// Gets the number of columns in the table. @@ -43,6 +43,11 @@ namespace Spectre.Console /// public bool ShowHeaders { get; set; } = true; + /// + /// Gets or sets a value indicating whether or not table footers should be shown. + /// + public bool ShowFooters { get; set; } = true; + /// /// Gets or sets a value indicating whether or not the table should /// fit the available space. If false, the table width will be @@ -58,12 +63,12 @@ namespace Spectre.Console /// /// Gets or sets the table title. /// - public TableTitle? Heading { get; set; } + public TableTitle? Title { get; set; } /// /// Gets or sets the table footnote. /// - public TableTitle? Footnote { get; set; } + public TableTitle? Caption { get; set; } /// public Justify? Alignment { get; set; } @@ -174,6 +179,7 @@ namespace Spectre.Console var showBorder = Border.Visible; var hideBorder = !Border.Visible; var hasRows = _rows.Count > 0; + var hasFooters = _columns.Any(c => c.Footer != null); if (Width != null) { @@ -196,14 +202,19 @@ namespace Spectre.Console if (ShowHeaders) { // Add columns to top of rows - rows.Add(new List(_columns.Select(c => c.Text))); + rows.Add(new List(_columns.Select(c => c.Header))); } // Add rows. rows.AddRange(_rows); + if (hasFooters) + { + rows.Add(new List(_columns.Select(c => c.Footer ?? Text.Empty))); + } + var result = new List(); - result.AddRange(RenderAnnotation(context, Heading, actualMaxWidth, tableWidth, _defaultHeadingStyle)); + result.AddRange(RenderAnnotation(context, Title, actualMaxWidth, tableWidth, _defaultHeadingStyle)); // Iterate all rows foreach (var (index, firstRow, lastRow, row) in rows.Enumerate()) @@ -230,6 +241,18 @@ namespace Spectre.Console result.Add(Segment.LineBreak); } + // Show footer separator? + if (ShowFooters && lastRow && showBorder && hasFooters) + { + var textBorder = border.GetColumnRow(TablePart.FooterSeparator, columnWidths, _columns); + if (!string.IsNullOrEmpty(textBorder)) + { + var separator = Aligner.Align(context, textBorder, Alignment, actualMaxWidth); + result.Add(new Segment(separator, borderStyle)); + result.Add(Segment.LineBreak); + } + } + // Make cells the same shape cells = Segment.MakeSameHeight(cellHeight, cells); @@ -310,7 +333,7 @@ namespace Spectre.Console // Show header separator? if (firstRow && showBorder && ShowHeaders && hasRows) { - var separator = Aligner.Align(context, border.GetColumnRow(TablePart.Separator, columnWidths, _columns), Alignment, actualMaxWidth); + var separator = Aligner.Align(context, border.GetColumnRow(TablePart.HeaderSeparator, columnWidths, _columns), Alignment, actualMaxWidth); result.Add(new Segment(separator, borderStyle)); result.Add(Segment.LineBreak); } @@ -324,7 +347,7 @@ namespace Spectre.Console } } - result.AddRange(RenderAnnotation(context, Footnote, actualMaxWidth, tableWidth, _defaultFootnoteStyle)); + result.AddRange(RenderAnnotation(context, Caption, actualMaxWidth, tableWidth, _defaultCaptionStyle)); return result; } @@ -437,16 +460,17 @@ namespace Spectre.Console var minWidths = new List(); var maxWidths = new List(); - // Include columns in measurement - var measure = column.Text.Measure(options, maxWidth); - minWidths.Add(measure.Min); - maxWidths.Add(measure.Max); + // Include columns (both header and footer) in measurement + var headerMeasure = column.Header.Measure(options, maxWidth); + var footerMeasure = column.Footer?.Measure(options, maxWidth) ?? headerMeasure; + minWidths.Add(Math.Min(headerMeasure.Min, footerMeasure.Min)); + maxWidths.Add(Math.Max(headerMeasure.Max, footerMeasure.Max)); foreach (var row in rows) { - measure = row.Measure(options, maxWidth); - minWidths.Add(measure.Min); - maxWidths.Add(measure.Max); + var rowMeasure = row.Measure(options, maxWidth); + minWidths.Add(rowMeasure.Min); + maxWidths.Add(rowMeasure.Max); } return (minWidths.Count > 0 ? minWidths.Max() : padding, diff --git a/src/Spectre.Console/Widgets/TableColumn.cs b/src/Spectre.Console/Widgets/TableColumn.cs index eca6eeb..91bba5a 100644 --- a/src/Spectre.Console/Widgets/TableColumn.cs +++ b/src/Spectre.Console/Widgets/TableColumn.cs @@ -9,9 +9,14 @@ namespace Spectre.Console public sealed class TableColumn : IColumn { /// - /// Gets the text associated with the column. + /// Gets the column header. /// - public IRenderable Text { get; } + public IRenderable Header { get; } + + /// + /// Gets or sets the column footer. + /// + public IRenderable? Footer { get; set; } /// /// Gets or sets the width of the column. @@ -39,19 +44,19 @@ namespace Spectre.Console /// /// Initializes a new instance of the class. /// - /// The table column text. - public TableColumn(string text) - : this(new Markup(text).Overflow(Overflow.Ellipsis)) + /// The table column header. + public TableColumn(string header) + : this(new Markup(header).Overflow(Overflow.Ellipsis)) { } /// /// Initializes a new instance of the class. /// - /// The instance to use as the table column. - public TableColumn(IRenderable renderable) + /// The instance to use as the table column header. + public TableColumn(IRenderable header) { - Text = renderable ?? throw new ArgumentNullException(nameof(renderable)); + Header = header ?? throw new ArgumentNullException(nameof(header)); Width = null; Padding = new Padding(1, 0, 1, 0); NoWrap = false;