Add support for aligning tables

This commit is contained in:
Patrik Svensson 2020-10-21 17:59:55 +02:00 committed by Patrik Svensson
parent b52056ee49
commit 9afc1ea721
17 changed files with 542 additions and 132 deletions

View File

@ -1,4 +1,3 @@
using System;
using Spectre.Console;
namespace ColorExample

View File

@ -1,5 +1,4 @@
using System.Collections.Generic;
using System.Diagnostics;
using System.Net.Http;
using System.Threading.Tasks;
using Newtonsoft.Json.Linq;

View File

@ -36,6 +36,7 @@ namespace TableExample
.AddRow("[blue]Hej[/]", "[yellow]Världen![/]", "");
return new Table()
.Centered()
.SetBorder(TableBorder.DoubleEdge)
.SetHeading("TABLE [yellow]HEADING[/]")
.SetFootnote("TABLE [yellow]FOOTNOTE[/]")

View File

@ -34,6 +34,93 @@ namespace Spectre.Console.Tests.Unit
console.Lines[10].ShouldBe("└─────┴─────┴─────┴─────┴─────┴─────┴─────┘");
}
[Fact]
public void Should_Center_Calendar_Correctly()
{
// Given
var console = new PlainConsole(width: 80);
var calendar = new Calendar(2020, 10)
.Centered()
.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(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]
public void Should_Left_Align_Calendar_Correctly()
{
// Given
var console = new PlainConsole(width: 80);
var calendar = new Calendar(2020, 10)
.LeftAligned()
.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(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]
public void Should_Right_Align_Calendar_Correctly()
{
// Given
var console = new PlainConsole(width: 80);
var calendar = new Calendar(2020, 10)
.RightAligned()
.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(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]
public void Should_Render_Calendar_Correctly_For_Specific_Culture()
{

View File

@ -73,7 +73,7 @@ namespace Spectre.Console.Tests.Unit
// When
console.Render(new Panel("Hello World")
{
Header = new Title("Greeting"),
Header = new PanelHeader("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 Title("Greeting").LeftAligned(),
Header = new PanelHeader("Greeting").LeftAligned(),
Expand = true,
});
@ -114,7 +114,7 @@ namespace Spectre.Console.Tests.Unit
// When
console.Render(new Panel("Hello World")
{
Header = new Title("Greeting").Centered(),
Header = new PanelHeader("Greeting").Centered(),
Expand = true,
});
@ -134,7 +134,7 @@ namespace Spectre.Console.Tests.Unit
// When
console.Render(new Panel("Hello World")
{
Header = new Title("Greeting").RightAligned(),
Header = new PanelHeader("Greeting").RightAligned(),
Expand = true,
});
@ -154,7 +154,7 @@ namespace Spectre.Console.Tests.Unit
// When
console.Render(new Panel("Hello World")
{
Header = new Title("Greeting"),
Header = new PanelHeader("Greeting"),
Expand = true,
});
@ -244,7 +244,7 @@ namespace Spectre.Console.Tests.Unit
}
[Fact]
public void Should_Justify_Child_To_Right()
public void Should_Justify_Child_To_Right_Correctly()
{
// Given
var console = new PlainConsole(width: 25);
@ -264,7 +264,7 @@ namespace Spectre.Console.Tests.Unit
}
[Fact]
public void Should_Justify_Child_To_Center()
public void Should_Center_Child_Correctly()
{
// Given
var console = new PlainConsole(width: 25);

View File

@ -168,6 +168,78 @@ namespace Spectre.Console.Tests.Unit
console.Lines[5].ShouldBe("└────────┴────────┴───────┘");
}
[Fact]
public void Should_Left_Align_Table_Correctly()
{
// Given
var console = new PlainConsole(width: 80);
var table = new Table();
table.Alignment = Justify.Left;
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_Center_Table_Correctly()
{
// Given
var console = new PlainConsole(width: 80);
var table = new Table();
table.Alignment = Justify.Center;
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_Right_Align_Table_Correctly()
{
// Given
var console = new PlainConsole(width: 80);
var table = new Table();
table.Alignment = Justify.Right;
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_Nested_In_Panels_Correctly()
{
@ -393,8 +465,8 @@ namespace Spectre.Console.Tests.Unit
// 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.Heading = new TableTitle("Hello World");
table.Footnote = new TableTitle("Goodbye World");
table.AddColumns("Foo", "Bar", "Baz");
table.AddRow("Qux", "Corgi", "Waldo");
table.AddRow("Grault", "Garply", "Fred");
@ -413,5 +485,89 @@ namespace Spectre.Console.Tests.Unit
console.Lines[6].ShouldBe("╰────────┴────────┴───────╯");
console.Lines[7].ShouldBe(" Goodbye World ");
}
[Fact]
public void Should_Left_Align_Table_With_Title_And_Footnote_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.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 ");
}
[Fact]
public void Should_Center_Table_With_Title_And_Footnote_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.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 ");
}
[Fact]
public void Should_Right_Align_Table_With_Title_And_Footnote_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.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

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

View File

@ -183,9 +183,8 @@ namespace Spectre.Console
/// <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)
public static Table SetHeading(this Table table, string text, Style? style = null)
{
if (table is null)
{
@ -197,7 +196,7 @@ namespace Spectre.Console
throw new ArgumentNullException(nameof(text));
}
return SetHeading(table, new Title(text, style, alignment));
return SetHeading(table, new TableTitle(text, style));
}
/// <summary>
@ -206,7 +205,7 @@ namespace Spectre.Console
/// <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)
public static Table SetHeading(this Table table, TableTitle heading)
{
if (table is null)
{
@ -223,9 +222,8 @@ namespace Spectre.Console
/// <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)
public static Table SetFootnote(this Table table, string text, Style? style = null)
{
if (table is null)
{
@ -237,7 +235,7 @@ namespace Spectre.Console
throw new ArgumentNullException(nameof(text));
}
return SetFootnote(table, new Title(text, style, alignment));
return SetFootnote(table, new TableTitle(text, style));
}
/// <summary>
@ -246,7 +244,7 @@ namespace Spectre.Console
/// <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)
public static Table SetFootnote(this Table table, TableTitle footnote)
{
if (table is null)
{

View File

@ -0,0 +1,97 @@
using System;
using System.Collections.Generic;
using Spectre.Console.Rendering;
namespace Spectre.Console.Internal
{
internal static class Aligner
{
public static string Align(RenderContext context, string text, Justify? alignment, int maxWidth)
{
if (alignment == null || alignment == Justify.Left)
{
return text;
}
var width = Cell.GetCellLength(context, text);
if (width >= maxWidth)
{
return text;
}
switch (alignment)
{
case Justify.Right:
{
var diff = maxWidth - width;
return new string(' ', diff) + text;
}
case Justify.Center:
{
var diff = (maxWidth - width) / 2;
var left = new string(' ', diff);
var right = new string(' ', diff);
// Right side
var remainder = (maxWidth - width) % 2;
if (remainder != 0)
{
right += new string(' ', remainder);
}
return left + text + right;
}
default:
throw new NotSupportedException("Unknown alignment");
}
}
public static void Align<T>(RenderContext context, T segments, Justify? alignment, int maxWidth)
where T : List<Segment>
{
if (alignment == null || alignment == Justify.Left)
{
return;
}
var width = Segment.CellLength(context, segments);
if (width >= maxWidth)
{
return;
}
switch (alignment)
{
case Justify.Right:
{
var diff = maxWidth - width;
segments.Insert(0, new Segment(new string(' ', diff)));
break;
}
case Justify.Center:
{
// Left side.
var diff = (maxWidth - width) / 2;
segments.Insert(0, new Segment(new string(' ', diff)));
// Right side
segments.Add(new Segment(new string(' ', diff)));
var remainder = (maxWidth - width) % 2;
if (remainder != 0)
{
segments.Add(new Segment(new string(' ', remainder)));
}
break;
}
default:
throw new NotSupportedException("Unknown alignment");
}
}
}
}

View File

@ -1,7 +1,6 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text;
using Spectre.Console.Internal;
@ -68,12 +67,7 @@ namespace Spectre.Console.Rendering
private Segment(string text, Style style, bool lineBreak)
{
if (text is null)
{
throw new ArgumentNullException(nameof(text));
}
Text = text.NormalizeLineEndings();
Text = text?.NormalizeLineEndings() ?? throw new ArgumentNullException(nameof(text));
Style = style ?? throw new ArgumentNullException(nameof(style));
IsLineBreak = lineBreak;
IsWhiteSpace = string.IsNullOrWhiteSpace(text);
@ -121,6 +115,15 @@ namespace Spectre.Console.Rendering
new Segment(Text.Substring(offset, Text.Length - offset), Style));
}
/// <summary>
/// Clones the segment.
/// </summary>
/// <returns>A new segment that's identical to this one.</returns>
public Segment Clone()
{
return new Segment(Text, Style);
}
/// <summary>
/// Gets the number of cells that the segments occupies in the console.
/// </summary>
@ -238,48 +241,6 @@ namespace Spectre.Console.Rendering
return lines;
}
internal static IEnumerable<Segment> Merge(IEnumerable<Segment> segments)
{
var result = new List<Segment>();
var previous = (Segment?)null;
foreach (var segment in segments)
{
if (previous == null)
{
previous = segment;
continue;
}
// Same style?
if (previous.Style.Equals(segment.Style) && !previous.IsLineBreak)
{
previous = new Segment(previous.Text + segment.Text, previous.Style);
}
else
{
result.Add(previous);
previous = segment;
}
}
if (previous != null)
{
result.Add(previous);
}
return result;
}
/// <summary>
/// Clones the segment.
/// </summary>
/// <returns>A new segment that's identical to this one.</returns>
public Segment Clone()
{
return new Segment(Text, Style);
}
/// <summary>
/// Splits an overflowing segment into several new segments.
/// </summary>
@ -436,6 +397,39 @@ namespace Spectre.Console.Rendering
return new Segment(builder.ToString(), segment.Style);
}
internal static IEnumerable<Segment> Merge(IEnumerable<Segment> segments)
{
var result = new List<Segment>();
var previous = (Segment?)null;
foreach (var segment in segments)
{
if (previous == null)
{
previous = segment;
continue;
}
// Same style?
if (previous.Style.Equals(segment.Style) && !previous.IsLineBreak)
{
previous = new Segment(previous.Text + segment.Text, previous.Style);
}
else
{
result.Add(previous);
previous = segment;
}
}
if (previous != null)
{
result.Add(previous);
}
return result;
}
internal static Segment TruncateWithEllipsis(string text, Style style, RenderContext context, int maxWidth)
{
return SplitOverflow(

View File

@ -11,7 +11,7 @@ namespace Spectre.Console
/// <summary>
/// A renderable calendar.
/// </summary>
public sealed class Calendar : Renderable, IHasCulture, IHasTableBorder
public sealed class Calendar : Renderable, IHasCulture, IHasTableBorder, IAlignable
{
private const int NumberOfWeekDays = 7;
private const int ExpectedRowCount = 6;
@ -30,6 +30,7 @@ namespace Spectre.Console
private Style _highlightStyle;
private bool _showHeader;
private Style? _headerStyle;
private Justify? _alignment;
/// <summary>
/// Gets or sets the calendar year.
@ -115,6 +116,13 @@ namespace Spectre.Console
set => MarkAsDirty(() => _headerStyle = value);
}
/// <inheritdoc/>
public Justify? Alignment
{
get => _alignment;
set => MarkAsDirty(() => _alignment = value);
}
/// <summary>
/// Gets a list containing all calendar events.
/// </summary>
@ -195,12 +203,13 @@ namespace Spectre.Console
Border = _border,
UseSafeBorder = _useSafeBorder,
BorderStyle = _borderStyle,
Alignment = _alignment,
};
if (ShowHeader)
{
var heading = new DateTime(Year, Month, Day).ToString("Y", culture).EscapeMarkup();
table.Heading = new Title(heading, HeaderStyle);
table.Heading = new TableTitle(heading, HeaderStyle);
}
// Add columns

View File

@ -7,7 +7,7 @@ namespace Spectre.Console
/// <summary>
/// A renderable grid.
/// </summary>
public sealed class Grid : Renderable, IExpandable
public sealed class Grid : Renderable, IExpandable, IAlignable
{
private readonly Table _table;
@ -28,6 +28,13 @@ namespace Spectre.Console
set => _table.Expand = value;
}
/// <inheritdoc/>
public Justify? Alignment
{
get => _table.Alignment;
set => _table.Alignment = value;
}
/// <summary>
/// Initializes a new instance of the <see cref="Grid"/> class.
/// </summary>

View File

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

View File

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

View File

@ -144,37 +144,18 @@ namespace Spectre.Console
// Justify lines
var justification = context.Justification ?? Alignment ?? Justify.Left;
foreach (var (_, _, last, line) in lines.Enumerate())
if (justification != Justify.Left)
{
var length = line.Sum(l => l.StripLineEndings().CellLength(context));
if (length < maxWidth)
foreach (var line in lines)
{
// Justify right side
if (justification == Justify.Right)
{
var diff = maxWidth - length;
line.Prepend(new Segment(new string(' ', diff)));
}
else if (justification == Justify.Center)
{
// Left side.
var diff = (maxWidth - length) / 2;
line.Prepend(new Segment(new string(' ', diff)));
// Right side
line.Add(new Segment(new string(' ', diff)));
var remainder = (maxWidth - length) % 2;
if (remainder != 0)
{
line.Add(new Segment(new string(' ', remainder)));
}
}
Aligner.Align(context, line, justification, maxWidth);
}
}
if (context.SingleLine)
{
return lines.First().Where(segment => !segment.IsLineBreak);
// Return the first line
return lines[0].Where(segment => !segment.IsLineBreak);
}
return new SegmentLineEnumerator(lines);

View File

@ -9,7 +9,7 @@ namespace Spectre.Console
/// <summary>
/// A renderable table.
/// </summary>
public sealed class Table : Renderable, IHasTableBorder, IExpandable
public sealed class Table : Renderable, IHasTableBorder, IExpandable, IAlignable
{
private const int EdgeCount = 2;
@ -58,12 +58,15 @@ namespace Spectre.Console
/// <summary>
/// Gets or sets the table title.
/// </summary>
public Title? Heading { get; set; }
public TableTitle? Heading { get; set; }
/// <summary>
/// Gets or sets the table footnote.
/// </summary>
public Title? Footnote { get; set; }
public TableTitle? Footnote { get; set; }
/// <inheritdoc/>
public Justify? Alignment { get; set; }
// Whether this is a grid or not.
internal bool IsGrid { get; set; }
@ -200,7 +203,7 @@ namespace Spectre.Console
rows.AddRange(_rows);
var result = new List<Segment>();
result.AddRange(RenderAnnotation(context, Heading, tableWidth, _defaultHeadingStyle));
result.AddRange(RenderAnnotation(context, Heading, actualMaxWidth, tableWidth, _defaultHeadingStyle));
// Iterate all rows
foreach (var (index, firstRow, lastRow, row) in rows.Enumerate())
@ -222,7 +225,7 @@ namespace Spectre.Console
// Show top of header?
if (firstRow && showBorder)
{
var separator = border.GetColumnRow(TablePart.Top, columnWidths, _columns);
var separator = Aligner.Align(context, border.GetColumnRow(TablePart.Top, columnWidths, _columns), Alignment, actualMaxWidth);
result.Add(new Segment(separator, borderStyle));
result.Add(Segment.LineBreak);
}
@ -288,6 +291,9 @@ namespace Spectre.Console
}
}
// Align the row result.
Aligner.Align(context, rowResult, Alignment, actualMaxWidth);
// Is the row larger than the allowed max width?
if (Segment.CellLength(context, rowResult) > actualMaxWidth)
{
@ -304,7 +310,7 @@ namespace Spectre.Console
// Show header separator?
if (firstRow && showBorder && ShowHeaders && hasRows)
{
var separator = border.GetColumnRow(TablePart.Separator, columnWidths, _columns);
var separator = Aligner.Align(context, border.GetColumnRow(TablePart.Separator, columnWidths, _columns), Alignment, actualMaxWidth);
result.Add(new Segment(separator, borderStyle));
result.Add(Segment.LineBreak);
}
@ -312,13 +318,13 @@ namespace Spectre.Console
// Show bottom of footer?
if (lastRow && showBorder)
{
var separator = border.GetColumnRow(TablePart.Bottom, columnWidths, _columns);
var separator = Aligner.Align(context, border.GetColumnRow(TablePart.Bottom, columnWidths, _columns), Alignment, actualMaxWidth);
result.Add(new Segment(separator, borderStyle));
result.Add(Segment.LineBreak);
}
}
result.AddRange(RenderAnnotation(context, Footnote, tableWidth, _defaultFootnoteStyle));
result.AddRange(RenderAnnotation(context, Footnote, actualMaxWidth, tableWidth, _defaultFootnoteStyle));
return result;
}
@ -392,25 +398,27 @@ namespace Spectre.Console
return widths;
}
private static IEnumerable<Segment> RenderAnnotation(
RenderContext context, Title? header,
int maxWidth, Style defaultStyle)
private IEnumerable<Segment> RenderAnnotation(
RenderContext context, TableTitle? header,
int maxWidth, int tableWidth, Style defaultStyle)
{
if (header == null)
{
yield break;
return Array.Empty<Segment>();
}
var paragraph = new Markup(header.Text.Capitalize(), header.Style ?? defaultStyle)
.SetAlignment(header.Alignment ?? Justify.Center)
.SetAlignment(Justify.Center)
.SetOverflow(Overflow.Ellipsis);
foreach (var segment in ((IRenderable)paragraph).Render(context, maxWidth))
{
yield return segment;
}
var items = new List<Segment>();
items.AddRange(((IRenderable)paragraph).Render(context, tableWidth));
yield return Segment.LineBreak;
// Align over the whole buffer area
Aligner.Align(context, items, Alignment, maxWidth);
items.Add(Segment.LineBreak);
return items;
}
private (int Min, int Max) MeasureColumn(TableColumn column, RenderContext options, int maxWidth)

View File

@ -3,9 +3,9 @@ using System;
namespace Spectre.Console
{
/// <summary>
/// Represents a title.
/// Represents a table title such as a heading or footnote.
/// </summary>
public sealed class Title : IAlignable
public sealed class TableTitle
{
/// <summary>
/// Gets the title text.
@ -18,21 +18,14 @@ namespace Spectre.Console
public Style? Style { get; set; }
/// <summary>
/// Gets or sets the title alignment.
/// </summary>
public Justify? Alignment { get; set; }
/// <summary>
/// Initializes a new instance of the <see cref="Title"/> class.
/// Initializes a new instance of the <see cref="TableTitle"/> class.
/// </summary>
/// <param name="text">The title text.</param>
/// <param name="style">The title style.</param>
/// <param name="alignment">The title alignment.</param>
public Title(string text, Style? style = null, Justify? alignment = null)
public TableTitle(string text, Style? style = null)
{
Text = text ?? throw new ArgumentNullException(nameof(text));
Style = style;
Alignment = alignment;
}
/// <summary>
@ -40,20 +33,25 @@ namespace Spectre.Console
/// </summary>
/// <param name="style">The title style.</param>
/// <returns>The same instance so that multiple calls can be chained.</returns>
public Title SetStyle(Style? style)
public TableTitle SetStyle(Style? style)
{
Style = style ?? Style.Plain;
return this;
}
/// <summary>
/// Sets the title alignment.
/// Sets the title style.
/// </summary>
/// <param name="alignment">The title alignment.</param>
/// <param name="style">The title style.</param>
/// <returns>The same instance so that multiple calls can be chained.</returns>
public Title SetAlignment(Justify alignment)
public TableTitle SetStyle(string style)
{
Alignment = alignment;
if (style is null)
{
throw new ArgumentNullException(nameof(style));
}
Style = Style.Parse(style);
return this;
}
}