From cb2924a609d7a92e460173492ee3ae3d826fc36b Mon Sep 17 00:00:00 2001 From: Patrik Svensson Date: Mon, 19 Oct 2020 00:41:40 +0200 Subject: [PATCH] Add table heading and footnote support Closes #116 --- examples/Calendars/Program.cs | 55 +-------- examples/Grids/Program.cs | 11 +- examples/Tables/Program.cs | 104 ++++++------------ .../Unit/CalendarTests.cs | 74 ++++--------- src/Spectre.Console.Tests/Unit/PanelTests.cs | 10 +- src/Spectre.Console.Tests/Unit/TableTests.cs | 73 +++++------- .../Extensions/CalendarExtensions.cs | 17 +++ .../Extensions/PanelExtensions.cs | 4 +- .../Extensions/TableExtensions.cs | 80 ++++++++++++++ .../Extensions/DayOfWeekExtensions.cs | 11 +- .../Internal/Extensions/StringExtensions.cs | 18 +++ src/Spectre.Console/Widgets/Calendar.cs | 27 +++++ src/Spectre.Console/Widgets/Panel.cs | 2 +- src/Spectre.Console/Widgets/Table.cs | 40 ++++++- .../Widgets/{PanelHeader.cs => Title.cs} | 32 +++--- 15 files changed, 299 insertions(+), 259 deletions(-) rename src/Spectre.Console/Widgets/{PanelHeader.cs => Title.cs} (53%) diff --git a/examples/Calendars/Program.cs b/examples/Calendars/Program.cs index b606d39..cb790bc 100644 --- a/examples/Calendars/Program.cs +++ b/examples/Calendars/Program.cs @@ -1,6 +1,4 @@ -using System.Collections.Generic; using Spectre.Console; -using Spectre.Console.Rendering; namespace Calendars { @@ -9,60 +7,13 @@ namespace Calendars public static void Main(string[] args) { AnsiConsole.WriteLine(); - AnsiConsole.Render( - new Columns(GetCalendars()) - .Collapse()); - } - - private static IEnumerable GetCalendars() - { - yield return EmbedInPanel( - "Invariant calendar", - new Calendar(2020, 10) - .SimpleHeavyBorder() - .SetHighlightStyle(Style.Parse("red")) - .AddCalendarEvent("An event", 2020, 9, 22) - .AddCalendarEvent("Another event", 2020, 10, 2) - .AddCalendarEvent("A third event", 2020, 10, 13)); - - yield return EmbedInPanel( - "Swedish calendar (sv-SE)", - new Calendar(2020, 10) + AnsiConsole.Render(new Calendar(2020, 10) .RoundedBorder() - .SetHighlightStyle(Style.Parse("blue")) - .SetCulture("sv-SE") + .SetHighlightStyle(Style.Parse("red")) + .SetHeaderStyle(Style.Parse("yellow")) .AddCalendarEvent("An event", 2020, 9, 22) .AddCalendarEvent("Another event", 2020, 10, 2) .AddCalendarEvent("A third event", 2020, 10, 13)); - - yield return EmbedInPanel( - "German calendar (de-DE)", - new Calendar(2020, 10) - .MarkdownBorder() - .SetHighlightStyle(Style.Parse("yellow")) - .SetCulture("de-DE") - .AddCalendarEvent("An event", 2020, 9, 22) - .AddCalendarEvent("Another event", 2020, 10, 2) - .AddCalendarEvent("A third event", 2020, 10, 13)); - - yield return EmbedInPanel( - "Italian calendar (it-IT)", - new Calendar(2020, 10) - .DoubleBorder() - .SetHighlightStyle(Style.Parse("green")) - .SetCulture("it-IT") - .AddCalendarEvent("An event", 2020, 9, 22) - .AddCalendarEvent("Another event", 2020, 10, 2) - .AddCalendarEvent("A third event", 2020, 10, 13)); - } - - private static IRenderable EmbedInPanel(string title, Calendar calendar) - { - return new Panel(calendar) - .Expand() - .RoundedBorder() - .SetBorderStyle(Style.Parse("grey")) - .SetHeader($" {title} ", Style.Parse("yellow")); } } } diff --git a/examples/Grids/Program.cs b/examples/Grids/Program.cs index 67c2ab4..9153f74 100644 --- a/examples/Grids/Program.cs +++ b/examples/Grids/Program.cs @@ -12,12 +12,11 @@ namespace GridExample var grid = new Grid(); grid.AddColumn(new GridColumn { NoWrap = true }); - grid.AddColumn(new GridColumn { NoWrap = true, Width = 2 }); - grid.AddColumn(); - grid.AddRow("Options:", "", ""); - grid.AddRow(" [blue]-h[/], [blue]--help[/]", "", "Show command line help."); - grid.AddRow(" [blue]-c[/], [blue]--configuration[/] ", "", "The configuration to run for."); - grid.AddRow(" [blue]-v[/], [blue]--verbosity[/] ", "", "Set the [grey]MSBuild[/] verbosity level."); + grid.AddColumn(new GridColumn().PadLeft(2)); + grid.AddRow("Options:"); + grid.AddRow(" [blue]-h[/], [blue]--help[/]", "Show command line help."); + grid.AddRow(" [blue]-c[/], [blue]--configuration[/] ", "The configuration to run for."); + grid.AddRow(" [blue]-v[/], [blue]--verbosity[/] ", "Set the [grey]MSBuild[/] verbosity level."); AnsiConsole.Render(grid); } diff --git a/examples/Tables/Program.cs b/examples/Tables/Program.cs index 539d673..209bded 100644 --- a/examples/Tables/Program.cs +++ b/examples/Tables/Program.cs @@ -1,4 +1,3 @@ -using System.Diagnostics; using Spectre.Console; namespace TableExample @@ -7,84 +6,45 @@ namespace TableExample { public static void Main() { - // A simple table - RenderSimpleTable(); - - // A big table - RenderBigTable(); - - // A nested table - RenderNestedTable(); - } - - private static void RenderSimpleTable() - { - // Create the table - var table = new Table(); - table.AddColumn(new TableColumn("[u]Foo[/]")); - table.AddColumn(new TableColumn("[u]Bar[/]")); - table.AddColumn(new TableColumn("[u]Baz[/]")); - - // Add some rows - table.AddRow("Hello", "[red]World![/]", ""); - table.AddRow("[blue]Bonjour[/]", "[white]le[/]", "[red]monde![/]"); - table.AddRow("[blue]Hej[/]", "[yellow]Världen![/]", ""); + // Create the table. + var table = CreateTable(); + // Render the table. AnsiConsole.Render(table); } - private static void RenderBigTable() + private static Table CreateTable() { - // Create the table - var table = new Table().SetBorder(TableBorder.Rounded); - table.AddColumn("[red underline]Foo[/]"); - table.AddColumn(new TableColumn("[blue]Bar[/]") { Alignment = Justify.Right, NoWrap = true }); + var simple = new Table() + .SetBorder(TableBorder.Square) + .SetBorderColor(Color.Red) + .AddColumn(new TableColumn("[u]CDE[/]").Centered()) + .AddColumn(new TableColumn("[u]FED[/]")) + .AddColumn(new TableColumn("[u]IHG[/]")) + .AddRow("Hello", "[red]World![/]", "") + .AddRow("[blue]Bonjour[/]", "[white]le[/]", "[red]monde![/]") + .AddRow("[blue]Hej[/]", "[yellow]Världen![/]", ""); - // Add some rows - table.AddRow("[blue][underline]Hell[/]o[/]", "World"); - table.AddRow("[yellow]Patrik [green]\"Hello World\"[/] Svensson[/]", "Was [underline]here[/]!"); - table.AddEmptyRow(); - table.AddRow( - "Lorem ipsum dolor sit amet, consectetur adipiscing elit,sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. " + - "Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure " + - "dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat " + - "non proident, sunt in culpa qui officia deserunt mollit anim id est laborum", "<- Strange language"); - table.AddEmptyRow(); - table.AddRow("Hej", "[green]Världen[/]"); + var second = new Table() + .SetBorder(TableBorder.Rounded) + .SetBorderColor(Color.Green) + .AddColumn(new TableColumn("[u]Foo[/]")) + .AddColumn(new TableColumn("[u]Bar[/]")) + .AddColumn(new TableColumn("[u]Baz[/]")) + .AddRow("Hello", "[red]World![/]", "") + .AddRow(simple, new Text("Whaaat"), new Text("Lolz")) + .AddRow("[blue]Hej[/]", "[yellow]Världen![/]", ""); - AnsiConsole.Render(table); - } - - private static void RenderNestedTable() - { - // Create simple table - var simple = new Table().SetBorder(TableBorder.Rounded).SetBorderColor(Color.Red); - simple.AddColumn(new TableColumn("[u]CDE[/]").Centered()); - simple.AddColumn(new TableColumn("[u]FED[/]")); - simple.AddColumn(new TableColumn("[u]IHG[/]")); - simple.AddRow("Hello", "[red]World![/]", ""); - simple.AddRow("[blue]Bonjour[/]", "[white]le[/]", "[red]monde![/]"); - simple.AddRow("[blue]Hej[/]", "[yellow]Världen![/]", ""); - - // Create other table - var second = new Table().SetBorder(TableBorder.Square).SetBorderColor(Color.Green); - second.AddColumn(new TableColumn("[u]Foo[/]")); - second.AddColumn(new TableColumn("[u]Bar[/]")); - second.AddColumn(new TableColumn("[u]Baz[/]")); - second.AddRow("Hello", "[red]World![/]", ""); - second.AddRow(simple, new Text("Whaaat"), new Text("Lolz")); - second.AddRow("[blue]Hej[/]", "[yellow]Världen![/]", ""); - - // Create the outer most table - var table = new Table().SetBorder(TableBorder.Rounded); - table.AddColumn(new TableColumn(new Panel("[u]ABC[/]").SetBorderColor(Color.Red))); - table.AddColumn(new TableColumn(new Panel("[u]DEF[/]").SetBorderColor(Color.Green))); - table.AddColumn(new TableColumn(new Panel("[u]GHI[/]").SetBorderColor(Color.Blue))); - table.AddRow(new Text("Hello").Centered(), new Markup("[red]World![/]"), Text.Empty); - table.AddRow(second, new Text("Whaaat"), new Text("Lol")); - table.AddRow(new Markup("[blue]Hej[/]").Centered(), new Markup("[yellow]Världen![/]"), Text.Empty); - - AnsiConsole.Render(table); + return new Table() + .SetBorder(TableBorder.DoubleEdge) + .SetHeading("TABLE [yellow]HEADING[/]") + .SetFootnote("TABLE [yellow]FOOTNOTE[/]") + .AddColumn(new TableColumn(new Panel("[u]ABC[/]").SetBorderColor(Color.Red))) + .AddColumn(new TableColumn(new Panel("[u]DEF[/]").SetBorderColor(Color.Green))) + .AddColumn(new TableColumn(new Panel("[u]GHI[/]").SetBorderColor(Color.Blue))) + .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/CalendarTests.cs b/src/Spectre.Console.Tests/Unit/CalendarTests.cs index c653523..14a5369 100644 --- a/src/Spectre.Console.Tests/Unit/CalendarTests.cs +++ b/src/Spectre.Console.Tests/Unit/CalendarTests.cs @@ -20,17 +20,18 @@ namespace Spectre.Console.Tests.Unit console.Render(calendar); // Then - console.Lines.Count.ShouldBe(10); - console.Lines[0].ShouldBe("┌─────┬─────┬─────┬─────┬─────┬─────┬─────┐"); - console.Lines[1].ShouldBe("│ Sun │ Mon │ Tue │ Wed │ Thu │ Fri │ Sat │"); - console.Lines[2].ShouldBe("├─────┼─────┼─────┼─────┼─────┼─────┼─────┤"); - console.Lines[3].ShouldBe("│ │ │ │ │ 1 │ 2 │ 3* │"); - console.Lines[4].ShouldBe("│ 4 │ 5 │ 6 │ 7 │ 8 │ 9 │ 10 │"); - console.Lines[5].ShouldBe("│ 11 │ 12* │ 13 │ 14 │ 15 │ 16 │ 17 │"); - console.Lines[6].ShouldBe("│ 18 │ 19 │ 20 │ 21 │ 22 │ 23 │ 24 │"); - console.Lines[7].ShouldBe("│ 25 │ 26 │ 27 │ 28 │ 29 │ 30 │ 31 │"); - console.Lines[8].ShouldBe("│ │ │ │ │ │ │ │"); - console.Lines[9].ShouldBe("└─────┴─────┴─────┴─────┴─────┴─────┴─────┘"); + console.Lines.Count.ShouldBe(11); + console.Lines[00].ShouldBe(" 2020 October "); + console.Lines[01].ShouldBe("┌─────┬─────┬─────┬─────┬─────┬─────┬─────┐"); + console.Lines[02].ShouldBe("│ Sun │ Mon │ Tue │ Wed │ Thu │ Fri │ Sat │"); + console.Lines[03].ShouldBe("├─────┼─────┼─────┼─────┼─────┼─────┼─────┤"); + console.Lines[04].ShouldBe("│ │ │ │ │ 1 │ 2 │ 3* │"); + console.Lines[05].ShouldBe("│ 4 │ 5 │ 6 │ 7 │ 8 │ 9 │ 10 │"); + console.Lines[06].ShouldBe("│ 11 │ 12* │ 13 │ 14 │ 15 │ 16 │ 17 │"); + console.Lines[07].ShouldBe("│ 18 │ 19 │ 20 │ 21 │ 22 │ 23 │ 24 │"); + console.Lines[08].ShouldBe("│ 25 │ 26 │ 27 │ 28 │ 29 │ 30 │ 31 │"); + console.Lines[09].ShouldBe("│ │ │ │ │ │ │ │"); + console.Lines[10].ShouldBe("└─────┴─────┴─────┴─────┴─────┴─────┴─────┘"); } [Fact] @@ -48,45 +49,18 @@ namespace Spectre.Console.Tests.Unit console.Render(calendar); // Then - console.Lines.Count.ShouldBe(10); - console.Lines[0].ShouldBe("┌─────┬────┬────┬────┬────┬────┬────┐"); - console.Lines[1].ShouldBe("│ Mo │ Di │ Mi │ Do │ Fr │ Sa │ So │"); - console.Lines[2].ShouldBe("├─────┼────┼────┼────┼────┼────┼────┤"); - console.Lines[3].ShouldBe("│ │ │ │ 1 │ 2 │ 3* │ 4 │"); - console.Lines[4].ShouldBe("│ 5 │ 6 │ 7 │ 8 │ 9 │ 10 │ 11 │"); - console.Lines[5].ShouldBe("│ 12* │ 13 │ 14 │ 15 │ 16 │ 17 │ 18 │"); - console.Lines[6].ShouldBe("│ 19 │ 20 │ 21 │ 22 │ 23 │ 24 │ 25 │"); - console.Lines[7].ShouldBe("│ 26 │ 27 │ 28 │ 29 │ 30 │ 31 │ │"); - console.Lines[8].ShouldBe("│ │ │ │ │ │ │ │"); - console.Lines[9].ShouldBe("└─────┴────┴────┴────┴────┴────┴────┘"); - } - - [Fact] - public void Should_Render_List_Of_Events_If_Enabled() - { - // Given - var console = new PlainConsole(width: 80); - var calendar = new Calendar(2020, 10, 15) - .SetCulture("de-DE") - .AddCalendarEvent(new DateTime(2020, 9, 1)) - .AddCalendarEvent(new DateTime(2020, 10, 3)) - .AddCalendarEvent(new DateTime(2020, 10, 12)); - - // When - console.Render(calendar); - - // Then - console.Lines.Count.ShouldBe(10); - console.Lines[0].ShouldBe("┌─────┬────┬────┬────┬────┬────┬────┐"); - console.Lines[1].ShouldBe("│ Mo │ Di │ Mi │ Do │ Fr │ Sa │ So │"); - console.Lines[2].ShouldBe("├─────┼────┼────┼────┼────┼────┼────┤"); - console.Lines[3].ShouldBe("│ │ │ │ 1 │ 2 │ 3* │ 4 │"); - console.Lines[4].ShouldBe("│ 5 │ 6 │ 7 │ 8 │ 9 │ 10 │ 11 │"); - console.Lines[5].ShouldBe("│ 12* │ 13 │ 14 │ 15 │ 16 │ 17 │ 18 │"); - console.Lines[6].ShouldBe("│ 19 │ 20 │ 21 │ 22 │ 23 │ 24 │ 25 │"); - console.Lines[7].ShouldBe("│ 26 │ 27 │ 28 │ 29 │ 30 │ 31 │ │"); - console.Lines[8].ShouldBe("│ │ │ │ │ │ │ │"); - console.Lines[9].ShouldBe("└─────┴────┴────┴────┴────┴────┴────┘"); + console.Lines.Count.ShouldBe(11); + console.Lines[00].ShouldBe(" Oktober 2020 "); + console.Lines[01].ShouldBe("┌─────┬────┬────┬────┬────┬────┬────┐"); + console.Lines[02].ShouldBe("│ Mo │ Di │ Mi │ Do │ Fr │ Sa │ So │"); + console.Lines[03].ShouldBe("├─────┼────┼────┼────┼────┼────┼────┤"); + console.Lines[04].ShouldBe("│ │ │ │ 1 │ 2 │ 3* │ 4 │"); + console.Lines[05].ShouldBe("│ 5 │ 6 │ 7 │ 8 │ 9 │ 10 │ 11 │"); + console.Lines[06].ShouldBe("│ 12* │ 13 │ 14 │ 15 │ 16 │ 17 │ 18 │"); + console.Lines[07].ShouldBe("│ 19 │ 20 │ 21 │ 22 │ 23 │ 24 │ 25 │"); + console.Lines[08].ShouldBe("│ 26 │ 27 │ 28 │ 29 │ 30 │ 31 │ │"); + console.Lines[09].ShouldBe("│ │ │ │ │ │ │ │"); + console.Lines[10].ShouldBe("└─────┴────┴────┴────┴────┴────┴────┘"); } } } diff --git a/src/Spectre.Console.Tests/Unit/PanelTests.cs b/src/Spectre.Console.Tests/Unit/PanelTests.cs index 6a36c43..828f5a8 100644 --- a/src/Spectre.Console.Tests/Unit/PanelTests.cs +++ b/src/Spectre.Console.Tests/Unit/PanelTests.cs @@ -73,7 +73,7 @@ namespace Spectre.Console.Tests.Unit // When console.Render(new Panel("Hello World") { - Header = new PanelHeader("Greeting"), + Header = new Title("Greeting"), Expand = true, Padding = new Padding(2, 0, 2, 0), }); @@ -94,7 +94,7 @@ namespace Spectre.Console.Tests.Unit // When console.Render(new Panel("Hello World") { - Header = new PanelHeader("Greeting").LeftAligned(), + Header = new Title("Greeting").LeftAligned(), Expand = true, }); @@ -114,7 +114,7 @@ namespace Spectre.Console.Tests.Unit // When console.Render(new Panel("Hello World") { - Header = new PanelHeader("Greeting").Centered(), + Header = new Title("Greeting").Centered(), Expand = true, }); @@ -134,7 +134,7 @@ namespace Spectre.Console.Tests.Unit // When console.Render(new Panel("Hello World") { - Header = new PanelHeader("Greeting").RightAligned(), + Header = new Title("Greeting").RightAligned(), Expand = true, }); @@ -154,7 +154,7 @@ namespace Spectre.Console.Tests.Unit // When console.Render(new Panel("Hello World") { - Header = new PanelHeader("Greeting"), + Header = new Title("Greeting"), Expand = true, }); diff --git a/src/Spectre.Console.Tests/Unit/TableTests.cs b/src/Spectre.Console.Tests/Unit/TableTests.cs index 662c316..3842198 100644 --- a/src/Spectre.Console.Tests/Unit/TableTests.cs +++ b/src/Spectre.Console.Tests/Unit/TableTests.cs @@ -250,52 +250,6 @@ namespace Spectre.Console.Tests.Unit console.Lines[5].ShouldBe("└───────────────────────────┴───────────────────────────┴──────────────────────┘"); } - [Fact] - public void Should_Render_Table_With_Ascii_Border_Correctly() - { - // Given - var console = new PlainConsole(width: 80); - var table = new Table { Border = TableBorder.Ascii }; - table.AddColumns("Foo", "Bar", "Baz"); - table.AddRow("Qux", "Corgi", "Waldo"); - table.AddRow("Grault", "Garply", "Fred"); - - // When - console.Render(table); - - // Then - console.Lines.Count.ShouldBe(6); - 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("+-------------------------+"); - } - - [Fact] - public void Should_Render_Table_With_Rounded_Border_Correctly() - { - // Given - var console = new PlainConsole(width: 80); - var table = new Table { Border = TableBorder.Rounded }; - table.AddColumns("Foo", "Bar", "Baz"); - table.AddRow("Qux", "Corgi", "Waldo"); - table.AddRow("Grault", "Garply", "Fred"); - - // When - console.Render(table); - - // Then - console.Lines.Count.ShouldBe(6); - 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("╰────────┴────────┴───────╯"); - } - [Fact] public void Should_Render_Table_With_No_Border_Correctly() { @@ -432,5 +386,32 @@ namespace Spectre.Console.Tests.Unit console.Lines[10].ShouldBe("│ │ en │ │"); console.Lines[11].ShouldBe("╰───────┴───────┴───────╯"); } + + [Fact] + public void Should_Render_Table_With_Title_And_Footnote_Correctly() + { + // Given + var console = new PlainConsole(width: 80); + var table = new Table { Border = TableBorder.Rounded }; + table.Heading = new Title("Hello World"); + table.Footnote = new Title("Goodbye World"); + table.AddColumns("Foo", "Bar", "Baz"); + table.AddRow("Qux", "Corgi", "Waldo"); + table.AddRow("Grault", "Garply", "Fred"); + + // When + console.Render(table); + + // Then + console.Lines.Count.ShouldBe(8); + console.Lines[0].ShouldBe(" Hello World "); + console.Lines[1].ShouldBe("╭────────┬────────┬───────╮"); + console.Lines[2].ShouldBe("│ Foo │ Bar │ Baz │"); + console.Lines[3].ShouldBe("├────────┼────────┼───────┤"); + console.Lines[4].ShouldBe("│ Qux │ Corgi │ Waldo │"); + console.Lines[5].ShouldBe("│ Grault │ Garply │ Fred │"); + console.Lines[6].ShouldBe("╰────────┴────────┴───────╯"); + console.Lines[7].ShouldBe(" Goodbye World "); + } } } diff --git a/src/Spectre.Console/Extensions/CalendarExtensions.cs b/src/Spectre.Console/Extensions/CalendarExtensions.cs index 92a2988..596ceae 100644 --- a/src/Spectre.Console/Extensions/CalendarExtensions.cs +++ b/src/Spectre.Console/Extensions/CalendarExtensions.cs @@ -79,5 +79,22 @@ namespace Spectre.Console calendar.HightlightStyle = style ?? Style.Plain; return calendar; } + + /// + /// Sets the calendar's header . + /// + /// The calendar. + /// The header style. + /// The same instance so that multiple calls can be chained. + public static Calendar SetHeaderStyle(this Calendar calendar, Style? style) + { + if (calendar is null) + { + throw new ArgumentNullException(nameof(calendar)); + } + + calendar.HeaderStyle = style ?? Style.Plain; + return calendar; + } } } diff --git a/src/Spectre.Console/Extensions/PanelExtensions.cs b/src/Spectre.Console/Extensions/PanelExtensions.cs index 84a7c38..94492fd 100644 --- a/src/Spectre.Console/Extensions/PanelExtensions.cs +++ b/src/Spectre.Console/Extensions/PanelExtensions.cs @@ -27,7 +27,7 @@ namespace Spectre.Console throw new ArgumentNullException(nameof(text)); } - return SetHeader(panel, new PanelHeader(text, style, alignment)); + return SetHeader(panel, new Title(text, style, alignment)); } /// @@ -36,7 +36,7 @@ namespace Spectre.Console /// The panel. /// The header to use. /// The same instance so that multiple calls can be chained. - public static Panel SetHeader(this Panel panel, PanelHeader header) + public static Panel SetHeader(this Panel panel, Title header) { if (panel is null) { diff --git a/src/Spectre.Console/Extensions/TableExtensions.cs b/src/Spectre.Console/Extensions/TableExtensions.cs index 38b4fd1..b1d0c03 100644 --- a/src/Spectre.Console/Extensions/TableExtensions.cs +++ b/src/Spectre.Console/Extensions/TableExtensions.cs @@ -176,5 +176,85 @@ namespace Spectre.Console table.ShowHeaders = false; return table; } + + /// + /// Sets the table heading. + /// + /// The table. + /// The heading. + /// The style. + /// The alignment. + /// The same instance so that multiple calls can be chained. + public static Table SetHeading(this Table table, string text, Style? style = null, Justify? alignment = null) + { + if (table is null) + { + throw new ArgumentNullException(nameof(table)); + } + + if (text is null) + { + throw new ArgumentNullException(nameof(text)); + } + + return SetHeading(table, new Title(text, style, alignment)); + } + + /// + /// Sets the table heading. + /// + /// The table. + /// The heading. + /// The same instance so that multiple calls can be chained. + public static Table SetHeading(this Table table, Title heading) + { + if (table is null) + { + throw new ArgumentNullException(nameof(table)); + } + + table.Heading = heading; + return table; + } + + /// + /// Sets the table footnote. + /// + /// The table. + /// The footnote. + /// The style. + /// The alignment. + /// The same instance so that multiple calls can be chained. + public static Table SetFootnote(this Table table, string text, Style? style = null, Justify? alignment = null) + { + if (table is null) + { + throw new ArgumentNullException(nameof(table)); + } + + if (text is null) + { + throw new ArgumentNullException(nameof(text)); + } + + return SetFootnote(table, new Title(text, style, alignment)); + } + + /// + /// Sets the table footnote. + /// + /// The table. + /// The footnote. + /// The same instance so that multiple calls can be chained. + public static Table SetFootnote(this Table table, Title footnote) + { + if (table is null) + { + throw new ArgumentNullException(nameof(table)); + } + + table.Footnote = footnote; + return table; + } } } diff --git a/src/Spectre.Console/Internal/Extensions/DayOfWeekExtensions.cs b/src/Spectre.Console/Internal/Extensions/DayOfWeekExtensions.cs index df72516..f62ed10 100644 --- a/src/Spectre.Console/Internal/Extensions/DayOfWeekExtensions.cs +++ b/src/Spectre.Console/Internal/Extensions/DayOfWeekExtensions.cs @@ -8,14 +8,9 @@ namespace Spectre.Console.Internal public static string GetAbbreviatedDayName(this DayOfWeek day, CultureInfo culture) { culture ??= CultureInfo.InvariantCulture; - var name = culture.DateTimeFormat.GetAbbreviatedDayName(day); - - if (name.Length > 0 && char.IsLower(name[0])) - { - name = string.Format(culture, "{0}{1}", char.ToUpper(name[0], culture), name.Substring(1)); - } - - return name; + return culture.DateTimeFormat + .GetAbbreviatedDayName(day) + .Capitalize(culture); } public static DayOfWeek GetNextWeekDay(this DayOfWeek day) diff --git a/src/Spectre.Console/Internal/Extensions/StringExtensions.cs b/src/Spectre.Console/Internal/Extensions/StringExtensions.cs index 58c7059..7e22b9a 100644 --- a/src/Spectre.Console/Internal/Extensions/StringExtensions.cs +++ b/src/Spectre.Console/Internal/Extensions/StringExtensions.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Globalization; using System.Linq; using System.Text; using Spectre.Console.Rendering; @@ -18,6 +19,23 @@ namespace Spectre.Console.Internal return Cell.GetCellLength(context, text); } + public static string Capitalize(this string text, CultureInfo? culture = null) + { + if (text == null) + { + return string.Empty; + } + + culture ??= CultureInfo.InvariantCulture; + + if (text.Length > 0 && char.IsLower(text[0])) + { + text = string.Format(culture, "{0}{1}", char.ToUpper(text[0], culture), text.Substring(1)); + } + + return text; + } + public static string NormalizeLineEndings(this string text, bool native = false) { text ??= string.Empty; diff --git a/src/Spectre.Console/Widgets/Calendar.cs b/src/Spectre.Console/Widgets/Calendar.cs index a8412d9..9750aac 100644 --- a/src/Spectre.Console/Widgets/Calendar.cs +++ b/src/Spectre.Console/Widgets/Calendar.cs @@ -28,6 +28,8 @@ namespace Spectre.Console private bool _dirty; private CultureInfo _culture; private Style _highlightStyle; + private bool _showHeader; + private Style? _headerStyle; /// /// Gets or sets the calendar year. @@ -95,6 +97,24 @@ namespace Spectre.Console set => MarkAsDirty(() => _highlightStyle = value); } + /// + /// Gets or sets a value indicating whether or not the calendar header should be shown. + /// + public bool ShowHeader + { + get => _showHeader; + set => MarkAsDirty(() => _showHeader = value); + } + + /// + /// Gets or sets the header style. + /// + public Style? HeaderStyle + { + get => _headerStyle; + set => MarkAsDirty(() => _headerStyle = value); + } + /// /// Gets a list containing all calendar events. /// @@ -137,6 +157,7 @@ namespace Spectre.Console _dirty = true; _culture = CultureInfo.InvariantCulture; _highlightStyle = new Style(foreground: Color.Blue); + _showHeader = true; _calendarEvents = new ListWithCallback(() => _dirty = true); } @@ -176,6 +197,12 @@ namespace Spectre.Console BorderStyle = _borderStyle, }; + if (ShowHeader) + { + var heading = new DateTime(Year, Month, Day).ToString("Y", culture).SafeMarkup(); + table.Heading = new Title(heading, HeaderStyle); + } + // Add columns foreach (var order in GetWeekdays()) { diff --git a/src/Spectre.Console/Widgets/Panel.cs b/src/Spectre.Console/Widgets/Panel.cs index e1bc285..5c2f708 100644 --- a/src/Spectre.Console/Widgets/Panel.cs +++ b/src/Spectre.Console/Widgets/Panel.cs @@ -39,7 +39,7 @@ namespace Spectre.Console /// /// Gets or sets the header. /// - public PanelHeader? Header { get; set; } + public Title? Header { get; set; } /// /// Initializes a new instance of the class. diff --git a/src/Spectre.Console/Widgets/Table.cs b/src/Spectre.Console/Widgets/Table.cs index b7291c0..52bc054 100644 --- a/src/Spectre.Console/Widgets/Table.cs +++ b/src/Spectre.Console/Widgets/Table.cs @@ -16,6 +16,9 @@ namespace Spectre.Console private readonly List _columns; private readonly List> _rows; + private static Style _defaultHeadingStyle = new Style(Color.Silver); + private static Style _defaultFootnoteStyle = new Style(Color.Grey); + /// /// Gets the number of columns in the table. /// @@ -52,6 +55,16 @@ namespace Spectre.Console /// public int? Width { get; set; } + /// + /// Gets or sets the table title. + /// + public Title? Heading { get; set; } + + /// + /// Gets or sets the table footnote. + /// + public Title? Footnote { get; set; } + // Whether this is a grid or not. internal bool IsGrid { get; set; } @@ -186,8 +199,10 @@ namespace Spectre.Console // Add rows. rows.AddRange(_rows); - // Iterate all rows var result = new List(); + result.AddRange(RenderAnnotation(context, Heading, tableWidth, _defaultHeadingStyle)); + + // Iterate all rows foreach (var (index, firstRow, lastRow, row) in rows.Enumerate()) { var cellHeight = 1; @@ -303,6 +318,8 @@ namespace Spectre.Console } } + result.AddRange(RenderAnnotation(context, Footnote, tableWidth, _defaultFootnoteStyle)); + return result; } @@ -375,6 +392,27 @@ namespace Spectre.Console return widths; } + private static IEnumerable RenderAnnotation( + RenderContext context, Title? header, + int maxWidth, Style defaultStyle) + { + if (header == null) + { + yield break; + } + + var paragraph = new Markup(header.Text.Capitalize(), header.Style ?? defaultStyle) + .SetAlignment(header.Alignment ?? Justify.Center) + .SetOverflow(Overflow.Ellipsis); + + foreach (var segment in ((IRenderable)paragraph).Render(context, maxWidth)) + { + yield return segment; + } + + yield return Segment.LineBreak; + } + private (int Min, int Max) MeasureColumn(TableColumn column, RenderContext options, int maxWidth) { var padding = column.Padding.GetWidth(); diff --git a/src/Spectre.Console/Widgets/PanelHeader.cs b/src/Spectre.Console/Widgets/Title.cs similarity index 53% rename from src/Spectre.Console/Widgets/PanelHeader.cs rename to src/Spectre.Console/Widgets/Title.cs index 38d4e67..83cbab4 100644 --- a/src/Spectre.Console/Widgets/PanelHeader.cs +++ b/src/Spectre.Console/Widgets/Title.cs @@ -3,32 +3,32 @@ using System; namespace Spectre.Console { /// - /// Represents a header. + /// Represents a title. /// - public sealed class PanelHeader : IAlignable + public sealed class Title : IAlignable { /// - /// Gets the header text. + /// Gets the title text. /// public string Text { get; } /// - /// Gets or sets the header style. + /// Gets or sets the title style. /// public Style? Style { get; set; } /// - /// Gets or sets the header alignment. + /// Gets or sets the title alignment. /// public Justify? Alignment { get; set; } /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// - /// The header text. - /// The header style. - /// The header alignment. - public PanelHeader(string text, Style? style = null, Justify? alignment = null) + /// The title text. + /// The title style. + /// The title alignment. + public Title(string text, Style? style = null, Justify? alignment = null) { Text = text ?? throw new ArgumentNullException(nameof(text)); Style = style; @@ -36,22 +36,22 @@ namespace Spectre.Console } /// - /// Sets the header style. + /// Sets the title style. /// - /// The header style. + /// The title style. /// The same instance so that multiple calls can be chained. - public PanelHeader SetStyle(Style? style) + public Title SetStyle(Style? style) { Style = style ?? Style.Plain; return this; } /// - /// Sets the header alignment. + /// Sets the title alignment. /// - /// The header alignment. + /// The title alignment. /// The same instance so that multiple calls can be chained. - public PanelHeader SetAlignment(Justify alignment) + public Title SetAlignment(Justify alignment) { Alignment = alignment; return this;