Add table heading and footnote support

Closes #116
This commit is contained in:
Patrik Svensson 2020-10-19 00:41:40 +02:00 committed by Patrik Svensson
parent 5c119ee0c3
commit cb2924a609
15 changed files with 299 additions and 259 deletions

View File

@ -1,6 +1,4 @@
using System.Collections.Generic;
using Spectre.Console; using Spectre.Console;
using Spectre.Console.Rendering;
namespace Calendars namespace Calendars
{ {
@ -9,60 +7,13 @@ namespace Calendars
public static void Main(string[] args) public static void Main(string[] args)
{ {
AnsiConsole.WriteLine(); AnsiConsole.WriteLine();
AnsiConsole.Render( AnsiConsole.Render(new Calendar(2020, 10)
new Columns(GetCalendars()) .RoundedBorder()
.Collapse());
}
private static IEnumerable<IRenderable> GetCalendars()
{
yield return EmbedInPanel(
"Invariant calendar",
new Calendar(2020, 10)
.SimpleHeavyBorder()
.SetHighlightStyle(Style.Parse("red")) .SetHighlightStyle(Style.Parse("red"))
.SetHeaderStyle(Style.Parse("yellow"))
.AddCalendarEvent("An event", 2020, 9, 22) .AddCalendarEvent("An event", 2020, 9, 22)
.AddCalendarEvent("Another event", 2020, 10, 2) .AddCalendarEvent("Another event", 2020, 10, 2)
.AddCalendarEvent("A third event", 2020, 10, 13)); .AddCalendarEvent("A third event", 2020, 10, 13));
yield return EmbedInPanel(
"Swedish calendar (sv-SE)",
new Calendar(2020, 10)
.RoundedBorder()
.SetHighlightStyle(Style.Parse("blue"))
.SetCulture("sv-SE")
.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"));
} }
} }
} }

View File

@ -12,12 +12,11 @@ namespace GridExample
var grid = new Grid(); var grid = new Grid();
grid.AddColumn(new GridColumn { NoWrap = true }); grid.AddColumn(new GridColumn { NoWrap = true });
grid.AddColumn(new GridColumn { NoWrap = true, Width = 2 }); grid.AddColumn(new GridColumn().PadLeft(2));
grid.AddColumn(); grid.AddRow("Options:");
grid.AddRow("Options:", "", ""); grid.AddRow(" [blue]-h[/], [blue]--help[/]", "Show command line help.");
grid.AddRow(" [blue]-h[/], [blue]--help[/]", "", "Show command line help."); grid.AddRow(" [blue]-c[/], [blue]--configuration[/] <CONFIGURATION>", "The configuration to run for.");
grid.AddRow(" [blue]-c[/], [blue]--configuration[/] <CONFIGURATION>", "", "The configuration to run for."); grid.AddRow(" [blue]-v[/], [blue]--verbosity[/] <LEVEL>", "Set the [grey]MSBuild[/] verbosity level.");
grid.AddRow(" [blue]-v[/], [blue]--verbosity[/] <LEVEL>", "", "Set the [grey]MSBuild[/] verbosity level.");
AnsiConsole.Render(grid); AnsiConsole.Render(grid);
} }

View File

@ -1,4 +1,3 @@
using System.Diagnostics;
using Spectre.Console; using Spectre.Console;
namespace TableExample namespace TableExample
@ -7,84 +6,45 @@ namespace TableExample
{ {
public static void Main() public static void Main()
{ {
// A simple table // Create the table.
RenderSimpleTable(); var table = CreateTable();
// 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![/]", "");
// Render the table.
AnsiConsole.Render(table); AnsiConsole.Render(table);
} }
private static void RenderBigTable() private static Table CreateTable()
{ {
// Create the table var simple = new Table()
var table = new Table().SetBorder(TableBorder.Rounded); .SetBorder(TableBorder.Square)
table.AddColumn("[red underline]Foo[/]"); .SetBorderColor(Color.Red)
table.AddColumn(new TableColumn("[blue]Bar[/]") { Alignment = Justify.Right, NoWrap = true }); .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 var second = new Table()
table.AddRow("[blue][underline]Hell[/]o[/]", "World"); .SetBorder(TableBorder.Rounded)
table.AddRow("[yellow]Patrik [green]\"Hello World\"[/] Svensson[/]", "Was [underline]here[/]!"); .SetBorderColor(Color.Green)
table.AddEmptyRow(); .AddColumn(new TableColumn("[u]Foo[/]"))
table.AddRow( .AddColumn(new TableColumn("[u]Bar[/]"))
"Lorem ipsum dolor sit amet, consectetur adipiscing elit,sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. " + .AddColumn(new TableColumn("[u]Baz[/]"))
"Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure " + .AddRow("Hello", "[red]World![/]", "")
"dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat " + .AddRow(simple, new Text("Whaaat"), new Text("Lolz"))
"non proident, sunt in culpa qui officia deserunt mollit anim id est laborum", "<- Strange language"); .AddRow("[blue]Hej[/]", "[yellow]Världen![/]", "");
table.AddEmptyRow();
table.AddRow("Hej", "[green]Världen[/]");
AnsiConsole.Render(table); return new Table()
} .SetBorder(TableBorder.DoubleEdge)
.SetHeading("TABLE [yellow]HEADING[/]")
private static void RenderNestedTable() .SetFootnote("TABLE [yellow]FOOTNOTE[/]")
{ .AddColumn(new TableColumn(new Panel("[u]ABC[/]").SetBorderColor(Color.Red)))
// Create simple table .AddColumn(new TableColumn(new Panel("[u]DEF[/]").SetBorderColor(Color.Green)))
var simple = new Table().SetBorder(TableBorder.Rounded).SetBorderColor(Color.Red); .AddColumn(new TableColumn(new Panel("[u]GHI[/]").SetBorderColor(Color.Blue)))
simple.AddColumn(new TableColumn("[u]CDE[/]").Centered()); .AddRow(new Text("Hello").Centered(), new Markup("[red]World![/]"), Text.Empty)
simple.AddColumn(new TableColumn("[u]FED[/]")); .AddRow(second, new Text("Whaaat"), new Text("Lol"))
simple.AddColumn(new TableColumn("[u]IHG[/]")); .AddRow(new Markup("[blue]Hej[/]").Centered(), new Markup("[yellow]Världen![/]"), Text.Empty);
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);
} }
} }
} }

View File

@ -20,17 +20,18 @@ namespace Spectre.Console.Tests.Unit
console.Render(calendar); console.Render(calendar);
// Then // Then
console.Lines.Count.ShouldBe(10); console.Lines.Count.ShouldBe(11);
console.Lines[0].ShouldBe("┌─────┬─────┬─────┬─────┬─────┬─────┬─────┐"); console.Lines[00].ShouldBe(" 2020 October ");
console.Lines[1].ShouldBe("│ Sun │ Mon │ Tue │ Wed │ Thu │ Fri │ Sat │"); console.Lines[01].ShouldBe("┌─────┬─────┬─────┬─────┬─────┬─────┬─────┐");
console.Lines[2].ShouldBe("├─────┼─────┼─────┼─────┼─────┼─────┼─────┤"); console.Lines[02].ShouldBe("│ Sun │ Mon │ Tue │ Wed │ Thu │ Fri │ Sat │");
console.Lines[3].ShouldBe("│ │ │ │ │ 1 │ 2 │ 3* │"); console.Lines[03].ShouldBe("├─────┼─────┼─────┼─────┼─────┼─────┼─────┤");
console.Lines[4].ShouldBe("│ 4 │ 5 │ 6 │ 7 │ 8 │ 9 │ 10 │"); console.Lines[04].ShouldBe("│ │ │ │ │ 1 │ 2 │ 3* │");
console.Lines[5].ShouldBe("│ 11 │ 12* │ 13 │ 14 │ 15 │ 16 │ 17 │"); console.Lines[05].ShouldBe("│ 4 │ 5 │ 6 │ 7 │ 8 │ 9 │ 10 │");
console.Lines[6].ShouldBe("│ 18 │ 19 │ 20 │ 21 │ 22 │ 23 │ 24 │"); console.Lines[06].ShouldBe("│ 11 │ 12* │ 13 │ 14 │ 15 │ 16 │ 17 │");
console.Lines[7].ShouldBe("│ 25 │ 26 │ 27 │ 28 │ 29 │ 30 │ 31 │"); console.Lines[07].ShouldBe("│ 18 │ 19 │ 20 │ 21 │ 22 │ 23 │ 24 │");
console.Lines[8].ShouldBe("│ │ │ │ │ │ │ │"); console.Lines[08].ShouldBe("│ 25 │ 26 │ 27 │ 28 │ 29 │ 30 │ 31 │");
console.Lines[9].ShouldBe("└─────┴─────┴─────┴─────┴─────┴─────┴─────┘"); console.Lines[09].ShouldBe("│ │ │ │ │ │ │ │");
console.Lines[10].ShouldBe("└─────┴─────┴─────┴─────┴─────┴─────┴─────┘");
} }
[Fact] [Fact]
@ -48,45 +49,18 @@ namespace Spectre.Console.Tests.Unit
console.Render(calendar); console.Render(calendar);
// Then // Then
console.Lines.Count.ShouldBe(10); console.Lines.Count.ShouldBe(11);
console.Lines[0].ShouldBe("┌─────┬────┬────┬────┬────┬────┬────┐"); console.Lines[00].ShouldBe(" Oktober 2020 ");
console.Lines[1].ShouldBe("│ Mo │ Di │ Mi │ Do │ Fr │ Sa │ So │"); console.Lines[01].ShouldBe("┌─────┬────┬────┬────┬────┬────┬────┐");
console.Lines[2].ShouldBe("├─────┼────┼────┼────┼────┼────┼────┤"); console.Lines[02].ShouldBe("│ Mo │ Di │ Mi │ Do │ Fr │ Sa │ So │");
console.Lines[3].ShouldBe("│ │ │ │ 1 │ 2 │ 3* │ 4 │"); console.Lines[03].ShouldBe("├─────┼────┼────┼────┼────┼────┼────┤");
console.Lines[4].ShouldBe("│ 5 │ 6 │ 7 │ 8 │ 9 │ 10 │ 11 │"); console.Lines[04].ShouldBe("│ │ │ │ 1 │ 2 │ 3* │ 4 │");
console.Lines[5].ShouldBe("│ 12* │ 13 │ 14 │ 15 │ 16 │ 17 │ 18 │"); console.Lines[05].ShouldBe("│ 5 │ 6 │ 7 │ 8 │ 9 │ 10 │ 11 │");
console.Lines[6].ShouldBe("│ 19 │ 20 │ 21 │ 22 │ 23 │ 24 │ 25 │"); console.Lines[06].ShouldBe("│ 12* │ 13 │ 14 │ 15 │ 16 │ 17 │ 18 │");
console.Lines[7].ShouldBe("│ 26 │ 27 │ 28 │ 29 │ 30 │ 31 │ │"); console.Lines[07].ShouldBe("│ 19 │ 20 │ 21 │ 22 │ 23 │ 24 │ 25 │");
console.Lines[8].ShouldBe("│ │ │ │ │ │ │ │"); console.Lines[08].ShouldBe("│ 26 │ 27 │ 28 │ 29 │ 30 │ 31 │ │");
console.Lines[9].ShouldBe("└─────┴────┴────┴────┴────┴────┴────┘"); console.Lines[09].ShouldBe("│ │ │ │ │ │ │ │");
} console.Lines[10].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("└─────┴────┴────┴────┴────┴────┴────┘");
} }
} }
} }

View File

@ -73,7 +73,7 @@ namespace Spectre.Console.Tests.Unit
// When // When
console.Render(new Panel("Hello World") console.Render(new Panel("Hello World")
{ {
Header = new PanelHeader("Greeting"), Header = new Title("Greeting"),
Expand = true, Expand = true,
Padding = new Padding(2, 0, 2, 0), Padding = new Padding(2, 0, 2, 0),
}); });
@ -94,7 +94,7 @@ namespace Spectre.Console.Tests.Unit
// When // When
console.Render(new Panel("Hello World") console.Render(new Panel("Hello World")
{ {
Header = new PanelHeader("Greeting").LeftAligned(), Header = new Title("Greeting").LeftAligned(),
Expand = true, Expand = true,
}); });
@ -114,7 +114,7 @@ namespace Spectre.Console.Tests.Unit
// When // When
console.Render(new Panel("Hello World") console.Render(new Panel("Hello World")
{ {
Header = new PanelHeader("Greeting").Centered(), Header = new Title("Greeting").Centered(),
Expand = true, Expand = true,
}); });
@ -134,7 +134,7 @@ namespace Spectre.Console.Tests.Unit
// When // When
console.Render(new Panel("Hello World") console.Render(new Panel("Hello World")
{ {
Header = new PanelHeader("Greeting").RightAligned(), Header = new Title("Greeting").RightAligned(),
Expand = true, Expand = true,
}); });
@ -154,7 +154,7 @@ namespace Spectre.Console.Tests.Unit
// When // When
console.Render(new Panel("Hello World") console.Render(new Panel("Hello World")
{ {
Header = new PanelHeader("Greeting"), Header = new Title("Greeting"),
Expand = true, Expand = true,
}); });

View File

@ -250,52 +250,6 @@ namespace Spectre.Console.Tests.Unit
console.Lines[5].ShouldBe("└───────────────────────────┴───────────────────────────┴──────────────────────┘"); 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] [Fact]
public void Should_Render_Table_With_No_Border_Correctly() 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[10].ShouldBe("│ │ en │ │");
console.Lines[11].ShouldBe("╰───────┴───────┴───────╯"); 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 ");
}
} }
} }

View File

@ -79,5 +79,22 @@ namespace Spectre.Console
calendar.HightlightStyle = style ?? Style.Plain; calendar.HightlightStyle = style ?? Style.Plain;
return calendar; return calendar;
} }
/// <summary>
/// Sets the calendar's header <see cref="Style"/>.
/// </summary>
/// <param name="calendar">The calendar.</param>
/// <param name="style">The header style.</param>
/// <returns>The same instance so that multiple calls can be chained.</returns>
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;
}
} }
} }

View File

@ -27,7 +27,7 @@ namespace Spectre.Console
throw new ArgumentNullException(nameof(text)); throw new ArgumentNullException(nameof(text));
} }
return SetHeader(panel, new PanelHeader(text, style, alignment)); return SetHeader(panel, new Title(text, style, alignment));
} }
/// <summary> /// <summary>
@ -36,7 +36,7 @@ namespace Spectre.Console
/// <param name="panel">The panel.</param> /// <param name="panel">The panel.</param>
/// <param name="header">The header to use.</param> /// <param name="header">The header to use.</param>
/// <returns>The same instance so that multiple calls can be chained.</returns> /// <returns>The same instance so that multiple calls can be chained.</returns>
public static Panel SetHeader(this Panel panel, PanelHeader header) public static Panel SetHeader(this Panel panel, Title header)
{ {
if (panel is null) if (panel is null)
{ {

View File

@ -176,5 +176,85 @@ namespace Spectre.Console
table.ShowHeaders = false; table.ShowHeaders = false;
return table; return table;
} }
/// <summary>
/// Sets the table heading.
/// </summary>
/// <param name="table">The table.</param>
/// <param name="text">The heading.</param>
/// <param name="style">The style.</param>
/// <param name="alignment">The alignment.</param>
/// <returns>The same instance so that multiple calls can be chained.</returns>
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));
}
/// <summary>
/// Sets the table heading.
/// </summary>
/// <param name="table">The table.</param>
/// <param name="heading">The heading.</param>
/// <returns>The same instance so that multiple calls can be chained.</returns>
public static Table SetHeading(this Table table, Title heading)
{
if (table is null)
{
throw new ArgumentNullException(nameof(table));
}
table.Heading = heading;
return table;
}
/// <summary>
/// Sets the table footnote.
/// </summary>
/// <param name="table">The table.</param>
/// <param name="text">The footnote.</param>
/// <param name="style">The style.</param>
/// <param name="alignment">The alignment.</param>
/// <returns>The same instance so that multiple calls can be chained.</returns>
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));
}
/// <summary>
/// Sets the table footnote.
/// </summary>
/// <param name="table">The table.</param>
/// <param name="footnote">The footnote.</param>
/// <returns>The same instance so that multiple calls can be chained.</returns>
public static Table SetFootnote(this Table table, Title footnote)
{
if (table is null)
{
throw new ArgumentNullException(nameof(table));
}
table.Footnote = footnote;
return table;
}
} }
} }

View File

@ -8,14 +8,9 @@ namespace Spectre.Console.Internal
public static string GetAbbreviatedDayName(this DayOfWeek day, CultureInfo culture) public static string GetAbbreviatedDayName(this DayOfWeek day, CultureInfo culture)
{ {
culture ??= CultureInfo.InvariantCulture; culture ??= CultureInfo.InvariantCulture;
var name = culture.DateTimeFormat.GetAbbreviatedDayName(day); return culture.DateTimeFormat
.GetAbbreviatedDayName(day)
if (name.Length > 0 && char.IsLower(name[0])) .Capitalize(culture);
{
name = string.Format(culture, "{0}{1}", char.ToUpper(name[0], culture), name.Substring(1));
}
return name;
} }
public static DayOfWeek GetNextWeekDay(this DayOfWeek day) public static DayOfWeek GetNextWeekDay(this DayOfWeek day)

View File

@ -1,5 +1,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Globalization;
using System.Linq; using System.Linq;
using System.Text; using System.Text;
using Spectre.Console.Rendering; using Spectre.Console.Rendering;
@ -18,6 +19,23 @@ namespace Spectre.Console.Internal
return Cell.GetCellLength(context, text); 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) public static string NormalizeLineEndings(this string text, bool native = false)
{ {
text ??= string.Empty; text ??= string.Empty;

View File

@ -28,6 +28,8 @@ namespace Spectre.Console
private bool _dirty; private bool _dirty;
private CultureInfo _culture; private CultureInfo _culture;
private Style _highlightStyle; private Style _highlightStyle;
private bool _showHeader;
private Style? _headerStyle;
/// <summary> /// <summary>
/// Gets or sets the calendar year. /// Gets or sets the calendar year.
@ -95,6 +97,24 @@ namespace Spectre.Console
set => MarkAsDirty(() => _highlightStyle = value); set => MarkAsDirty(() => _highlightStyle = value);
} }
/// <summary>
/// Gets or sets a value indicating whether or not the calendar header should be shown.
/// </summary>
public bool ShowHeader
{
get => _showHeader;
set => MarkAsDirty(() => _showHeader = value);
}
/// <summary>
/// Gets or sets the header style.
/// </summary>
public Style? HeaderStyle
{
get => _headerStyle;
set => MarkAsDirty(() => _headerStyle = value);
}
/// <summary> /// <summary>
/// Gets a list containing all calendar events. /// Gets a list containing all calendar events.
/// </summary> /// </summary>
@ -137,6 +157,7 @@ namespace Spectre.Console
_dirty = true; _dirty = true;
_culture = CultureInfo.InvariantCulture; _culture = CultureInfo.InvariantCulture;
_highlightStyle = new Style(foreground: Color.Blue); _highlightStyle = new Style(foreground: Color.Blue);
_showHeader = true;
_calendarEvents = new ListWithCallback<CalendarEvent>(() => _dirty = true); _calendarEvents = new ListWithCallback<CalendarEvent>(() => _dirty = true);
} }
@ -176,6 +197,12 @@ namespace Spectre.Console
BorderStyle = _borderStyle, BorderStyle = _borderStyle,
}; };
if (ShowHeader)
{
var heading = new DateTime(Year, Month, Day).ToString("Y", culture).SafeMarkup();
table.Heading = new Title(heading, HeaderStyle);
}
// Add columns // Add columns
foreach (var order in GetWeekdays()) foreach (var order in GetWeekdays())
{ {

View File

@ -39,7 +39,7 @@ namespace Spectre.Console
/// <summary> /// <summary>
/// Gets or sets the header. /// Gets or sets the header.
/// </summary> /// </summary>
public PanelHeader? Header { get; set; } public Title? Header { get; set; }
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="Panel"/> class. /// Initializes a new instance of the <see cref="Panel"/> class.

View File

@ -16,6 +16,9 @@ namespace Spectre.Console
private readonly List<TableColumn> _columns; private readonly List<TableColumn> _columns;
private readonly List<List<IRenderable>> _rows; private readonly List<List<IRenderable>> _rows;
private static Style _defaultHeadingStyle = new Style(Color.Silver);
private static Style _defaultFootnoteStyle = new Style(Color.Grey);
/// <summary> /// <summary>
/// Gets the number of columns in the table. /// Gets the number of columns in the table.
/// </summary> /// </summary>
@ -52,6 +55,16 @@ namespace Spectre.Console
/// </summary> /// </summary>
public int? Width { get; set; } public int? Width { get; set; }
/// <summary>
/// Gets or sets the table title.
/// </summary>
public Title? Heading { get; set; }
/// <summary>
/// Gets or sets the table footnote.
/// </summary>
public Title? Footnote { get; set; }
// Whether this is a grid or not. // Whether this is a grid or not.
internal bool IsGrid { get; set; } internal bool IsGrid { get; set; }
@ -186,8 +199,10 @@ namespace Spectre.Console
// Add rows. // Add rows.
rows.AddRange(_rows); rows.AddRange(_rows);
// Iterate all rows
var result = new List<Segment>(); var result = new List<Segment>();
result.AddRange(RenderAnnotation(context, Heading, tableWidth, _defaultHeadingStyle));
// Iterate all rows
foreach (var (index, firstRow, lastRow, row) in rows.Enumerate()) foreach (var (index, firstRow, lastRow, row) in rows.Enumerate())
{ {
var cellHeight = 1; var cellHeight = 1;
@ -303,6 +318,8 @@ namespace Spectre.Console
} }
} }
result.AddRange(RenderAnnotation(context, Footnote, tableWidth, _defaultFootnoteStyle));
return result; return result;
} }
@ -375,6 +392,27 @@ namespace Spectre.Console
return widths; return widths;
} }
private static IEnumerable<Segment> 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) private (int Min, int Max) MeasureColumn(TableColumn column, RenderContext options, int maxWidth)
{ {
var padding = column.Padding.GetWidth(); var padding = column.Padding.GetWidth();

View File

@ -3,32 +3,32 @@ using System;
namespace Spectre.Console namespace Spectre.Console
{ {
/// <summary> /// <summary>
/// Represents a header. /// Represents a title.
/// </summary> /// </summary>
public sealed class PanelHeader : IAlignable public sealed class Title : IAlignable
{ {
/// <summary> /// <summary>
/// Gets the header text. /// Gets the title text.
/// </summary> /// </summary>
public string Text { get; } public string Text { get; }
/// <summary> /// <summary>
/// Gets or sets the header style. /// Gets or sets the title style.
/// </summary> /// </summary>
public Style? Style { get; set; } public Style? Style { get; set; }
/// <summary> /// <summary>
/// Gets or sets the header alignment. /// Gets or sets the title alignment.
/// </summary> /// </summary>
public Justify? Alignment { get; set; } public Justify? Alignment { get; set; }
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="PanelHeader"/> class. /// Initializes a new instance of the <see cref="Title"/> class.
/// </summary> /// </summary>
/// <param name="text">The header text.</param> /// <param name="text">The title text.</param>
/// <param name="style">The header style.</param> /// <param name="style">The title style.</param>
/// <param name="alignment">The header alignment.</param> /// <param name="alignment">The title alignment.</param>
public PanelHeader(string text, Style? style = null, Justify? alignment = null) public Title(string text, Style? style = null, Justify? alignment = null)
{ {
Text = text ?? throw new ArgumentNullException(nameof(text)); Text = text ?? throw new ArgumentNullException(nameof(text));
Style = style; Style = style;
@ -36,22 +36,22 @@ namespace Spectre.Console
} }
/// <summary> /// <summary>
/// Sets the header style. /// Sets the title style.
/// </summary> /// </summary>
/// <param name="style">The header style.</param> /// <param name="style">The title style.</param>
/// <returns>The same instance so that multiple calls can be chained.</returns> /// <returns>The same instance so that multiple calls can be chained.</returns>
public PanelHeader SetStyle(Style? style) public Title SetStyle(Style? style)
{ {
Style = style ?? Style.Plain; Style = style ?? Style.Plain;
return this; return this;
} }
/// <summary> /// <summary>
/// Sets the header alignment. /// Sets the title alignment.
/// </summary> /// </summary>
/// <param name="alignment">The header alignment.</param> /// <param name="alignment">The title alignment.</param>
/// <returns>The same instance so that multiple calls can be chained.</returns> /// <returns>The same instance so that multiple calls can be chained.</returns>
public PanelHeader SetAlignment(Justify alignment) public Title SetAlignment(Justify alignment)
{ {
Alignment = alignment; Alignment = alignment;
return this; return this;