Clean up public API

* Make things a bit more consistent
* Add extension methods to configure things like tables, panels and grids.
This commit is contained in:
Patrik Svensson 2020-08-26 15:03:49 +02:00 committed by Patrik Svensson
parent c111c7d463
commit 31f117aed0
24 changed files with 569 additions and 266 deletions

View File

@ -6,42 +6,38 @@ namespace PanelExample
{ {
static void Main(string[] args) static void Main(string[] args)
{ {
var content = Text.Markup( var content = new Markup(
"[underline]I[/] heard [underline on blue]you[/] like 📦\n\n\n\n" + "[underline]I[/] heard [underline on blue]you[/] like 📦\n\n\n\n" +
"So I put a 📦 in a 📦\n\n" + "So I put a 📦 in a 📦\n\n" +
"😅"); "😅").Centered();
AnsiConsole.Render( AnsiConsole.Render(
new Panel( new Panel(
new Panel(content) new Panel(content)
{ {
Alignment = Justify.Center,
Border = BorderKind.Rounded Border = BorderKind.Rounded
})); }));
// Left adjusted panel with text // Left adjusted panel with text
AnsiConsole.Render(new Panel( AnsiConsole.Render(new Panel(
new Text("Left adjusted\nLeft")) new Text("Left adjusted\nLeft").LeftAligned())
{ {
Expand = true, Expand = true,
Alignment = Justify.Left,
}); });
// Centered ASCII panel with text // Centered ASCII panel with text
AnsiConsole.Render(new Panel( AnsiConsole.Render(new Panel(
new Text("Centered\nCenter")) new Text("Centered\nCenter").Centered())
{ {
Expand = true, Expand = true,
Alignment = Justify.Center,
Border = BorderKind.Ascii, Border = BorderKind.Ascii,
}); });
// Right adjusted, rounded panel with text // Right adjusted, rounded panel with text
AnsiConsole.Render(new Panel( AnsiConsole.Render(new Panel(
new Text("Right adjusted\nRight")) new Text("Right adjusted\nRight").RightAligned())
{ {
Expand = true, Expand = true,
Alignment = Justify.Right,
Border = BorderKind.Rounded, Border = BorderKind.Rounded,
}); });
} }

View File

@ -81,7 +81,7 @@ namespace Spectre.Console.Tests.Unit
// Given // Given
var console = new PlainConsole(width: 80); var console = new PlainConsole(width: 80);
var text = new Panel( var text = new Panel(
Text.Markup("I heard [underline on blue]you[/] like 📦\n\n\n\nSo I put a 📦 in a 📦")); new Markup("I heard [underline on blue]you[/] like 📦\n\n\n\nSo I put a 📦 in a 📦"));
// When // When
console.Render(text); console.Render(text);

View File

@ -10,20 +10,26 @@ namespace Spectre.Console.Tests.Unit
[Fact] [Fact]
public void Should_Consider_The_Longest_Word_As_Minimum_Width() public void Should_Consider_The_Longest_Word_As_Minimum_Width()
{ {
// Given
var text = new Text("Foo Bar Baz\nQux\nLol mobile"); var text = new Text("Foo Bar Baz\nQux\nLol mobile");
// When
var result = ((IRenderable)text).Measure(new RenderContext(Encoding.Unicode, false), 80); var result = ((IRenderable)text).Measure(new RenderContext(Encoding.Unicode, false), 80);
// Then
result.Min.ShouldBe(6); result.Min.ShouldBe(6);
} }
[Fact] [Fact]
public void Should_Consider_The_Longest_Line_As_Maximum_Width() public void Should_Consider_The_Longest_Line_As_Maximum_Width()
{ {
// Given
var text = new Text("Foo Bar Baz\nQux\nLol mobile"); var text = new Text("Foo Bar Baz\nQux\nLol mobile");
// When
var result = ((IRenderable)text).Measure(new RenderContext(Encoding.Unicode, false), 80); var result = ((IRenderable)text).Measure(new RenderContext(Encoding.Unicode, false), 80);
// Then
result.Max.ShouldBe(11); result.Max.ShouldBe(11);
} }

View File

@ -6,11 +6,11 @@ namespace Spectre.Console.Internal
{ {
internal static class MarkupParser internal static class MarkupParser
{ {
public static Text Parse(string text, Style? style = null) public static Paragraph Parse(string text, Style? style = null)
{ {
style ??= Style.Plain; style ??= Style.Plain;
var result = new Text(); var result = new Paragraph();
using var tokenizer = new MarkupTokenizer(text); using var tokenizer = new MarkupTokenizer(text);
var stack = new Stack<Style>(); var stack = new Stack<Style>();

View File

@ -64,13 +64,13 @@ namespace Spectre.Console
} }
// Only pad the most right cell if we've explicitly set a padding. // Only pad the most right cell if we've explicitly set a padding.
_table.PadRightCell = column.Padding != null; _table.PadRightCell = column.HasExplicitPadding;
_table.AddColumn(new TableColumn(string.Empty) _table.AddColumn(new TableColumn(string.Empty)
{ {
Width = column.Width, Width = column.Width,
NoWrap = column.NoWrap, NoWrap = column.NoWrap,
Padding = column.Padding ?? new Padding(0, 2), Padding = column.Padding,
Alignment = column.Alignment, Alignment = column.Alignment,
}); });
} }

View File

@ -3,8 +3,10 @@ namespace Spectre.Console
/// <summary> /// <summary>
/// Represents a grid column. /// Represents a grid column.
/// </summary> /// </summary>
public sealed class GridColumn : IAlignable public sealed class GridColumn : IColumn
{ {
private Padding _padding;
/// <summary> /// <summary>
/// Gets or sets the width of the column. /// Gets or sets the width of the column.
/// If <c>null</c>, the column will adapt to it's contents. /// If <c>null</c>, the column will adapt to it's contents.
@ -20,11 +22,33 @@ namespace Spectre.Console
/// <summary> /// <summary>
/// Gets or sets the padding of the column. /// Gets or sets the padding of the column.
/// </summary> /// </summary>
public Padding? Padding { get; set; } public Padding Padding
{
get => _padding;
set
{
HasExplicitPadding = true;
_padding = value;
}
}
/// <summary> /// <summary>
/// Gets or sets the alignment of the column. /// Gets or sets the alignment of the column.
/// </summary> /// </summary>
public Justify? Alignment { get; set; } public Justify? Alignment { get; set; }
/// <summary>
/// Gets a value indicating whether the user
/// has set an explicit padding for this column.
/// </summary>
internal bool HasExplicitPadding { get; private set; }
/// <summary>
/// Initializes a new instance of the <see cref="GridColumn"/> class.
/// </summary>
public GridColumn()
{
_padding = new Padding(0, 2);
}
} }
} }

View File

@ -9,13 +9,13 @@ namespace Spectre.Console
/// </summary> /// </summary>
public sealed class Markup : Renderable, IAlignable public sealed class Markup : Renderable, IAlignable
{ {
private readonly Text _text; private readonly Paragraph _paragraph;
/// <inheritdoc/> /// <inheritdoc/>
public Justify? Alignment public Justify? Alignment
{ {
get => _text.Alignment; get => _paragraph.Alignment;
set => _text.Alignment = value; set => _paragraph.Alignment = value;
} }
/// <summary> /// <summary>
@ -25,19 +25,19 @@ namespace Spectre.Console
/// <param name="style">The style of the text.</param> /// <param name="style">The style of the text.</param>
public Markup(string text, Style? style = null) public Markup(string text, Style? style = null)
{ {
_text = MarkupParser.Parse(text, style); _paragraph = MarkupParser.Parse(text, style);
} }
/// <inheritdoc/> /// <inheritdoc/>
protected override Measurement Measure(RenderContext context, int maxWidth) protected override Measurement Measure(RenderContext context, int maxWidth)
{ {
return ((IRenderable)_text).Measure(context, maxWidth); return ((IRenderable)_paragraph).Measure(context, maxWidth);
} }
/// <inheritdoc/> /// <inheritdoc/>
protected override IEnumerable<Segment> Render(RenderContext context, int maxWidth) protected override IEnumerable<Segment> Render(RenderContext context, int maxWidth)
{ {
return ((IRenderable)_text).Render(context, maxWidth); return ((IRenderable)_paragraph).Render(context, maxWidth);
} }
} }
} }

View File

@ -8,7 +8,7 @@ namespace Spectre.Console
/// <summary> /// <summary>
/// A renderable panel. /// A renderable panel.
/// </summary> /// </summary>
public sealed class Panel : Renderable, IHasBorder public sealed class Panel : Renderable, IHasBorder, IExpandable, IPaddable
{ {
private const int EdgeWidth = 2; private const int EdgeWidth = 2;
@ -23,11 +23,6 @@ namespace Spectre.Console
/// <inheritdoc/> /// <inheritdoc/>
public Color? BorderColor { get; set; } public Color? BorderColor { get; set; }
/// <summary>
/// Gets or sets the alignment of the panel contents.
/// </summary>
public Justify? Alignment { get; set; }
/// <summary> /// <summary>
/// Gets or sets a value indicating whether or not the panel should /// Gets or sets a value indicating whether or not the panel should
/// fit the available space. If <c>false</c>, the panel width will be /// fit the available space. If <c>false</c>, the panel width will be
@ -94,8 +89,7 @@ namespace Spectre.Console
}; };
// Render the child. // Render the child.
var childContext = context.WithJustification(Alignment); var childSegments = _child.Render(context, childWidth);
var childSegments = _child.Render(childContext, childWidth);
// Split the child segments into lines. // Split the child segments into lines.
foreach (var line in Segment.SplitLines(childSegments, panelWidth)) foreach (var line in Segment.SplitLines(childSegments, panelWidth))

View File

@ -0,0 +1,256 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using Spectre.Console.Internal;
using Spectre.Console.Rendering;
namespace Spectre.Console
{
/// <summary>
/// A paragraph of text where different parts
/// of the paragraph can have individual styling.
/// </summary>
[DebuggerDisplay("{_text,nq}")]
public sealed class Paragraph : Renderable, IAlignable
{
private readonly List<SegmentLine> _lines;
/// <summary>
/// Gets or sets the alignment of the whole paragraph.
/// </summary>
public Justify? Alignment { get; set; }
/// <summary>
/// Initializes a new instance of the <see cref="Paragraph"/> class.
/// </summary>
public Paragraph()
{
_lines = new List<SegmentLine>();
}
/// <summary>
/// Initializes a new instance of the <see cref="Paragraph"/> class.
/// </summary>
/// <param name="text">The text.</param>
/// <param name="style">The style of the text.</param>
public Paragraph(string text, Style? style = null)
: this()
{
if (text is null)
{
throw new ArgumentNullException(nameof(text));
}
Append(text, style);
}
/// <summary>
/// Appends some text to this paragraph.
/// </summary>
/// <param name="text">The text to append.</param>
/// <param name="style">The style of the appended text.</param>
/// <returns>The same instance so that multiple calls can be chained.</returns>
public Paragraph Append(string text, Style? style = null)
{
if (text is null)
{
throw new ArgumentNullException(nameof(text));
}
foreach (var (_, first, last, part) in text.SplitLines().Enumerate())
{
var current = part;
if (first)
{
var line = _lines.LastOrDefault();
if (line == null)
{
_lines.Add(new SegmentLine());
line = _lines.Last();
}
if (string.IsNullOrEmpty(current))
{
line.Add(Segment.Empty);
}
else
{
foreach (var span in current.SplitWords())
{
line.Add(new Segment(span, style ?? Style.Plain));
}
}
}
else
{
var line = new SegmentLine();
if (string.IsNullOrEmpty(current))
{
line.Add(Segment.Empty);
}
else
{
foreach (var span in current.SplitWords())
{
line.Add(new Segment(span, style ?? Style.Plain));
}
}
_lines.Add(line);
}
}
return this;
}
/// <inheritdoc/>
protected override Measurement Measure(RenderContext context, int maxWidth)
{
if (_lines.Count == 0)
{
return new Measurement(0, 0);
}
var min = _lines.Max(line => line.Max(segment => segment.CellLength(context.Encoding)));
var max = _lines.Max(x => x.CellWidth(context.Encoding));
return new Measurement(min, max);
}
/// <inheritdoc/>
protected override IEnumerable<Segment> Render(RenderContext context, int maxWidth)
{
if (context is null)
{
throw new ArgumentNullException(nameof(context));
}
if (_lines.Count == 0)
{
return Array.Empty<Segment>();
}
var lines = SplitLines(context, maxWidth);
// Justify lines
var justification = context.Justification ?? Alignment ?? Justify.Left;
foreach (var (_, _, last, line) in lines.Enumerate())
{
var length = line.Sum(l => l.StripLineEndings().CellLength(context.Encoding));
if (length < maxWidth)
{
// 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)));
}
}
}
}
return new SegmentLineEnumerator(lines);
}
private List<SegmentLine> Clone()
{
var result = new List<SegmentLine>();
foreach (var line in _lines)
{
var newLine = new SegmentLine();
foreach (var segment in line)
{
newLine.Add(segment);
}
result.Add(newLine);
}
return result;
}
private List<SegmentLine> SplitLines(RenderContext context, int maxWidth)
{
if (_lines.Max(x => x.CellWidth(context.Encoding)) <= maxWidth)
{
return Clone();
}
var lines = new List<SegmentLine>();
var line = new SegmentLine();
var newLine = true;
using (var iterator = new SegmentLineIterator(_lines))
{
while (iterator.MoveNext())
{
var current = iterator.Current;
if (current == null)
{
throw new InvalidOperationException("Iterator returned empty segment.");
}
if (newLine && current.IsWhiteSpace && !current.IsLineBreak)
{
newLine = false;
continue;
}
newLine = false;
if (current.IsLineBreak)
{
line.Add(current);
lines.Add(line);
line = new SegmentLine();
newLine = true;
continue;
}
var length = current.CellLength(context.Encoding);
if (line.CellWidth(context.Encoding) + length > maxWidth)
{
line.Add(Segment.Empty);
lines.Add(line);
line = new SegmentLine();
newLine = true;
}
if (newLine && current.IsWhiteSpace)
{
continue;
}
newLine = false;
line.Add(current);
}
}
// Flush remaining.
if (line.Count > 0)
{
lines.Add(line);
}
return lines;
}
}
}

View File

@ -10,7 +10,7 @@ namespace Spectre.Console
/// <summary> /// <summary>
/// A renderable table. /// A renderable table.
/// </summary> /// </summary>
public sealed partial class Table : Renderable, IHasBorder public sealed partial class Table : Renderable, IHasBorder, IExpandable
{ {
private readonly List<TableColumn> _columns; private readonly List<TableColumn> _columns;
private readonly List<List<IRenderable>> _rows; private readonly List<List<IRenderable>> _rows;

View File

@ -6,7 +6,7 @@ namespace Spectre.Console
/// <summary> /// <summary>
/// Represents a table column. /// Represents a table column.
/// </summary> /// </summary>
public sealed class TableColumn : IAlignable public sealed class TableColumn : IColumn
{ {
/// <summary> /// <summary>
/// Gets the text associated with the column. /// Gets the text associated with the column.

View File

@ -74,5 +74,54 @@ namespace Spectre.Console
table.AddRow(columns.Select(column => new Markup(column)).ToArray()); table.AddRow(columns.Select(column => new Markup(column)).ToArray());
} }
/// <summary>
/// Sets the table width.
/// </summary>
/// <param name="table">The table.</param>
/// <param name="width">The width.</param>
/// <returns>The same instance so that multiple calls can be chained.</returns>
public static Table SetWidth(this Table table, int width)
{
if (table is null)
{
throw new ArgumentNullException(nameof(table));
}
table.Width = width;
return table;
}
/// <summary>
/// Shows table headers.
/// </summary>
/// <param name="table">The table.</param>
/// <returns>The same instance so that multiple calls can be chained.</returns>
public static Table ShowHeaders(this Table table)
{
if (table is null)
{
throw new ArgumentNullException(nameof(table));
}
table.ShowHeaders = true;
return table;
}
/// <summary>
/// Hides table headers.
/// </summary>
/// <param name="table">The table.</param>
/// <returns>The same instance so that multiple calls can be chained.</returns>
public static Table HideHeaders(this Table table)
{
if (table is null)
{
throw new ArgumentNullException(nameof(table));
}
table.ShowHeaders = false;
return table;
}
} }
} }

View File

@ -1,9 +1,6 @@
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
using System.Linq;
using Spectre.Console.Internal;
using Spectre.Console.Rendering; using Spectre.Console.Rendering;
namespace Spectre.Console namespace Spectre.Console
@ -15,258 +12,42 @@ namespace Spectre.Console
[SuppressMessage("Naming", "CA1724:Type names should not match namespaces")] [SuppressMessage("Naming", "CA1724:Type names should not match namespaces")]
public sealed class Text : Renderable, IAlignable public sealed class Text : Renderable, IAlignable
{ {
private readonly List<SegmentLine> _lines; private readonly Paragraph _paragraph;
/// <summary> /// <summary>
/// Gets an empty <see cref="Text"/> instance. /// Gets an empty <see cref="Text"/> instance.
/// </summary> /// </summary>
public static Text Empty { get; } = new Text(string.Empty); public static Text Empty { get; } = new Text(string.Empty);
/// <summary>
/// Gets or sets the text alignment.
/// </summary>
public Justify? Alignment { get; set; }
/// <summary>
/// Initializes a new instance of the <see cref="Text"/> class.
/// </summary>
public Text()
{
_lines = new List<SegmentLine>();
}
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="Text"/> class. /// Initializes a new instance of the <see cref="Text"/> class.
/// </summary> /// </summary>
/// <param name="text">The text.</param> /// <param name="text">The text.</param>
/// <param name="style">The style of the text.</param> /// <param name="style">The style of the text.</param>
public Text(string text, Style? style = null) public Text(string text, Style? style = null)
: this()
{ {
if (text is null) _paragraph = new Paragraph(text, style);
{
throw new ArgumentNullException(nameof(text));
}
Append(text, style);
} }
/// <summary> /// <summary>
/// Creates a <see cref="Text"/> instance representing /// Gets or sets the text alignment.
/// the specified markup text.
/// </summary> /// </summary>
/// <param name="text">The markup text.</param> public Justify? Alignment
/// <param name="style">The text style.</param>
/// <returns>a <see cref="Text"/> instance representing the specified markup text.</returns>
public static Text Markup(string text, Style? style = null)
{ {
var result = MarkupParser.Parse(text, style ?? Style.Plain); get => _paragraph.Alignment;
return result; set => _paragraph.Alignment = value;
} }
/// <inheritdoc/> /// <inheritdoc/>
protected override Measurement Measure(RenderContext context, int maxWidth) protected override Measurement Measure(RenderContext context, int maxWidth)
{ {
if (_lines.Count == 0) return ((IRenderable)_paragraph).Measure(context, maxWidth);
{
return new Measurement(0, 0);
}
var min = _lines.Max(line => line.Max(segment => segment.CellLength(context.Encoding)));
var max = _lines.Max(x => x.CellWidth(context.Encoding));
return new Measurement(min, max);
} }
/// <inheritdoc/> /// <inheritdoc/>
protected override IEnumerable<Segment> Render(RenderContext context, int maxWidth) protected override IEnumerable<Segment> Render(RenderContext context, int maxWidth)
{ {
if (context is null) return ((IRenderable)_paragraph).Render(context, maxWidth);
{
throw new ArgumentNullException(nameof(context));
}
if (_lines.Count == 0)
{
return Array.Empty<Segment>();
}
var lines = SplitLines(context, maxWidth);
// Justify lines
var justification = context.Justification ?? Alignment ?? Justify.Left;
foreach (var (_, _, last, line) in lines.Enumerate())
{
var length = line.Sum(l => l.StripLineEndings().CellLength(context.Encoding));
if (length < maxWidth)
{
// 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)));
}
}
}
}
return new SegmentLineEnumerator(lines);
}
/// <summary>
/// Appends a piece of text.
/// </summary>
/// <param name="text">The text to append.</param>
/// <param name="style">The style of the appended text.</param>
public void Append(string text, Style? style = null)
{
if (text is null)
{
throw new ArgumentNullException(nameof(text));
}
foreach (var (_, first, last, part) in text.SplitLines().Enumerate())
{
var current = part;
if (first)
{
var line = _lines.LastOrDefault();
if (line == null)
{
_lines.Add(new SegmentLine());
line = _lines.Last();
}
if (string.IsNullOrEmpty(current))
{
line.Add(Segment.Empty);
}
else
{
foreach (var span in current.SplitWords())
{
line.Add(new Segment(span, style ?? Style.Plain));
}
}
}
else
{
var line = new SegmentLine();
if (string.IsNullOrEmpty(current))
{
line.Add(Segment.Empty);
}
else
{
foreach (var span in current.SplitWords())
{
line.Add(new Segment(span, style ?? Style.Plain));
}
}
_lines.Add(line);
}
}
}
private List<SegmentLine> Clone()
{
var result = new List<SegmentLine>();
foreach (var line in _lines)
{
var newLine = new SegmentLine();
foreach (var segment in line)
{
newLine.Add(segment);
}
result.Add(newLine);
}
return result;
}
private List<SegmentLine> SplitLines(RenderContext context, int maxWidth)
{
if (_lines.Max(x => x.CellWidth(context.Encoding)) <= maxWidth)
{
return Clone();
}
var lines = new List<SegmentLine>();
var line = new SegmentLine();
var newLine = true;
using (var iterator = new SegmentLineIterator(_lines))
{
while (iterator.MoveNext())
{
var current = iterator.Current;
if (current == null)
{
throw new InvalidOperationException("Iterator returned empty segment.");
}
if (newLine && current.IsWhiteSpace && !current.IsLineBreak)
{
newLine = false;
continue;
}
newLine = false;
if (current.IsLineBreak)
{
line.Add(current);
lines.Add(line);
line = new SegmentLine();
newLine = true;
continue;
}
var length = current.CellLength(context.Encoding);
if (line.CellWidth(context.Encoding) + length > maxWidth)
{
line.Add(Segment.Empty);
lines.Add(line);
line = new SegmentLine();
newLine = true;
}
if (newLine && current.IsWhiteSpace)
{
continue;
}
newLine = false;
line.Add(current);
}
}
// Flush remaining.
if (line.Count > 0)
{
lines.Add(line);
}
return lines;
} }
} }
} }

View File

@ -11,7 +11,7 @@ namespace Spectre.Console
/// <typeparam name="T">The alignable object type.</typeparam> /// <typeparam name="T">The alignable object type.</typeparam>
/// <param name="obj">The alignable object.</param> /// <param name="obj">The alignable object.</param>
/// <param name="alignment">The alignment.</param> /// <param name="alignment">The alignment.</param>
/// <returns>The same <see cref="IAlignable"/> instance.</returns> /// <returns>The same instance so that multiple calls can be chained.</returns>
public static T SetAlignment<T>(this T obj, Justify alignment) public static T SetAlignment<T>(this T obj, Justify alignment)
where T : class, IAlignable where T : class, IAlignable
{ {
@ -29,7 +29,7 @@ namespace Spectre.Console
/// </summary> /// </summary>
/// <typeparam name="T">The alignable type.</typeparam> /// <typeparam name="T">The alignable type.</typeparam>
/// <param name="obj">The alignable object.</param> /// <param name="obj">The alignable object.</param>
/// <returns>The same <see cref="IAlignable"/> instance.</returns> /// <returns>The same instance so that multiple calls can be chained.</returns>
public static T LeftAligned<T>(this T obj) public static T LeftAligned<T>(this T obj)
where T : class, IAlignable where T : class, IAlignable
{ {
@ -47,7 +47,7 @@ namespace Spectre.Console
/// </summary> /// </summary>
/// <typeparam name="T">The alignable type.</typeparam> /// <typeparam name="T">The alignable type.</typeparam>
/// <param name="obj">The alignable object.</param> /// <param name="obj">The alignable object.</param>
/// <returns>The same <see cref="IAlignable"/> instance.</returns> /// <returns>The same instance so that multiple calls can be chained.</returns>
public static T Centered<T>(this T obj) public static T Centered<T>(this T obj)
where T : class, IAlignable where T : class, IAlignable
{ {
@ -65,7 +65,7 @@ namespace Spectre.Console
/// </summary> /// </summary>
/// <typeparam name="T">The alignable type.</typeparam> /// <typeparam name="T">The alignable type.</typeparam>
/// <param name="obj">The alignable object.</param> /// <param name="obj">The alignable object.</param>
/// <returns>The same <see cref="IAlignable"/> instance.</returns> /// <returns>The same instance so that multiple calls can be chained.</returns>
public static T RightAligned<T>(this T obj) public static T RightAligned<T>(this T obj)
where T : class, IAlignable where T : class, IAlignable
{ {

View File

@ -13,7 +13,7 @@ namespace Spectre.Console.Rendering
/// <typeparam name="T">The object that has a border.</typeparam> /// <typeparam name="T">The object that has a border.</typeparam>
/// <param name="obj">The object to set the border for.</param> /// <param name="obj">The object to set the border for.</param>
/// <param name="border">The border to use.</param> /// <param name="border">The border to use.</param>
/// <returns>The same <see cref="IHasBorder"/> instance.</returns> /// <returns>The same instance so that multiple calls can be chained.</returns>
public static T SetBorder<T>(this T obj, BorderKind border) public static T SetBorder<T>(this T obj, BorderKind border)
where T : class, IHasBorder where T : class, IHasBorder
{ {
@ -31,7 +31,7 @@ namespace Spectre.Console.Rendering
/// </summary> /// </summary>
/// <typeparam name="T">The object that has a border.</typeparam> /// <typeparam name="T">The object that has a border.</typeparam>
/// <param name="obj">The object to set the border for.</param> /// <param name="obj">The object to set the border for.</param>
/// <returns>The same <see cref="IHasBorder"/> instance.</returns> /// <returns>The same instance so that multiple calls can be chained.</returns>
public static T NoSafeBorder<T>(this T obj) public static T NoSafeBorder<T>(this T obj)
where T : class, IHasBorder where T : class, IHasBorder
{ {
@ -50,7 +50,7 @@ namespace Spectre.Console.Rendering
/// <typeparam name="T">The object that has a border.</typeparam> /// <typeparam name="T">The object that has a border.</typeparam>
/// <param name="obj">The object to set the border color for.</param> /// <param name="obj">The object to set the border color for.</param>
/// <param name="color">The color to set.</param> /// <param name="color">The color to set.</param>
/// <returns>The same <see cref="IHasBorder"/> instance.</returns> /// <returns>The same instance so that multiple calls can be chained.</returns>
public static T SetBorderColor<T>(this T obj, Color color) public static T SetBorderColor<T>(this T obj, Color color)
where T : class, IHasBorder where T : class, IHasBorder
{ {

View File

@ -0,0 +1,28 @@
using System;
namespace Spectre.Console
{
/// <summary>
/// Contains extension methods for <see cref="IColumn"/>.
/// </summary>
public static class ColumnExtensions
{
/// <summary>
/// Prevents a column from wrapping.
/// </summary>
/// <typeparam name="T">An object implementing <see cref="IColumn"/>.</typeparam>
/// <param name="obj">The column.</param>
/// <returns>The same instance so that multiple calls can be chained.</returns>
public static T NoWrap<T>(this T obj)
where T : class, IColumn
{
if (obj is null)
{
throw new ArgumentNullException(nameof(obj));
}
obj.NoWrap = true;
return obj;
}
}
}

View File

@ -0,0 +1,48 @@
using System;
namespace Spectre.Console.Rendering
{
/// <summary>
/// Contains extension methods for <see cref="IExpandable"/>.
/// </summary>
public static class ExpandableExtensions
{
/// <summary>
/// Tells the specified object to not expand to the available area
/// but take as little space as possible.
/// </summary>
/// <typeparam name="T">The expandable object.</typeparam>
/// <param name="obj">The object to collapse.</param>
/// <returns>The same instance so that multiple calls can be chained.</returns>
public static T Collapse<T>(this T obj)
where T : class, IExpandable
{
SetExpand<T>(obj, false);
return obj;
}
/// <summary>
/// Tells the specified object to expand to the available area.
/// </summary>
/// <typeparam name="T">The expandable object.</typeparam>
/// <param name="obj">The object to expand.</param>
/// <returns>The same instance so that multiple calls can be chained.</returns>
public static T Expand<T>(this T obj)
where T : class, IExpandable
{
SetExpand<T>(obj, true);
return obj;
}
private static void SetExpand<T>(T obj, bool value)
where T : class, IExpandable
{
if (obj is null)
{
throw new ArgumentNullException(nameof(obj));
}
obj.Expand = value;
}
}
}

View File

@ -0,0 +1,79 @@
using System;
namespace Spectre.Console
{
/// <summary>
/// Contains extension methods for <see cref="IPaddable"/>.
/// </summary>
public static class PaddableExtensions
{
/// <summary>
/// Sets the left padding.
/// </summary>
/// <typeparam name="T">An object implementing <see cref="IPaddable"/>.</typeparam>
/// <param name="obj">The paddable object instance.</param>
/// <param name="padding">The left padding to apply.</param>
/// <returns>The same instance so that multiple calls can be chained.</returns>
public static T PadLeft<T>(this T obj, int padding)
where T : class, IPaddable
{
if (obj is null)
{
throw new ArgumentNullException(nameof(obj));
}
return SetPadding(obj, new Padding(padding, obj.Padding.Right));
}
/// <summary>
/// Sets the right padding.
/// </summary>
/// <typeparam name="T">An object implementing <see cref="IPaddable"/>.</typeparam>
/// <param name="obj">The paddable object instance.</param>
/// <param name="padding">The right padding to apply.</param>
/// <returns>The same instance so that multiple calls can be chained.</returns>
public static T PadRight<T>(this T obj, int padding)
where T : class, IPaddable
{
if (obj is null)
{
throw new ArgumentNullException(nameof(obj));
}
return SetPadding(obj, new Padding(obj.Padding.Left, padding));
}
/// <summary>
/// Sets the left and right padding.
/// </summary>
/// <typeparam name="T">An object implementing <see cref="IPaddable"/>.</typeparam>
/// <param name="obj">The paddable object instance.</param>
/// <param name="left">The left padding to apply.</param>
/// <param name="right">The right padding to apply.</param>
/// <returns>The same instance so that multiple calls can be chained.</returns>
public static T SetPadding<T>(this T obj, int left, int right)
where T : class, IPaddable
{
return SetPadding(obj, new Padding(left, right));
}
/// <summary>
/// Sets the padding.
/// </summary>
/// <typeparam name="T">An object implementing <see cref="IPaddable"/>.</typeparam>
/// <param name="obj">The paddable object instance.</param>
/// <param name="padding">The padding to apply.</param>
/// <returns>The same instance so that multiple calls can be chained.</returns>
public static T SetPadding<T>(this T obj, Padding padding)
where T : class, IPaddable
{
if (obj is null)
{
throw new ArgumentNullException(nameof(obj));
}
obj.Padding = padding;
return obj;
}
}
}

View File

@ -0,0 +1,14 @@
namespace Spectre.Console
{
/// <summary>
/// Represents a column.
/// </summary>
public interface IColumn : IAlignable, IPaddable
{
/// <summary>
/// Gets or sets a value indicating whether
/// or not wrapping should be prevented.
/// </summary>
bool NoWrap { get; set; }
}
}

View File

@ -0,0 +1,15 @@
namespace Spectre.Console
{
/// <summary>
/// Represents something that is expandable.
/// </summary>
public interface IExpandable
{
/// <summary>
/// Gets or sets a value indicating whether or not the object should
/// expand to the available space. If <c>false</c>, the object's
/// width will be auto calculated.
/// </summary>
bool Expand { get; set; }
}
}

View File

@ -0,0 +1,13 @@
namespace Spectre.Console
{
/// <summary>
/// Represents something that is paddable.
/// </summary>
public interface IPaddable
{
/// <summary>
/// Gets or sets the padding.
/// </summary>
public Padding Padding { get; set; }
}
}