mirror of
https://github.com/nsnail/spectre.console.git
synced 2025-06-19 13:28:16 +08:00
Add support for aligning tables
This commit is contained in:

committed by
Patrik Svensson

parent
b52056ee49
commit
9afc1ea721
@ -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
|
||||
|
@ -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>
|
||||
|
@ -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.
|
||||
|
76
src/Spectre.Console/Widgets/PanelHeader.cs
Normal file
76
src/Spectre.Console/Widgets/PanelHeader.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
@ -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);
|
||||
|
@ -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)
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user