Make styles composable

Also adds some new extension methods and make some APIs a bit more consistent.

Closes #64
This commit is contained in:
Patrik Svensson 2020-09-03 21:18:52 +02:00 committed by Patrik Svensson
parent bcaaa6b2d3
commit e946289bd9
12 changed files with 296 additions and 97 deletions

View File

@ -1,4 +1,5 @@
using Spectre.Console;
using Spectre.Console.Rendering;
namespace PanelExample
{
@ -13,35 +14,28 @@ namespace PanelExample
AnsiConsole.Render(
new Panel(
new Panel(content)
{
Border = BorderKind.Rounded,
}));
.SetBorderKind(BorderKind.Rounded)));
// Left adjusted panel with text
AnsiConsole.Render(new Panel(
new Text("Left adjusted\nLeft").LeftAligned())
{
Expand = true,
Header = new Header("Left", new Style(foreground: Color.Red)).LeftAligned(),
});
AnsiConsole.Render(
new Panel(new Text("Left adjusted\nLeft").LeftAligned())
.Expand()
.SquareBorder()
.SetHeader("Left", Style.WithForeground(Color.Red)));
// Centered ASCII panel with text
AnsiConsole.Render(new Panel(
new Text("Centered\nCenter").Centered())
{
Expand = true,
Border = BorderKind.Ascii,
Header = new Header("Center", new Style(foreground: Color.Green)).Centered(),
});
AnsiConsole.Render(
new Panel(new Text("Centered\nCenter").Centered())
.Expand()
.AsciiBorder()
.SetHeader("Center", Style.WithForeground(Color.Green), Justify.Center));
// Right adjusted, rounded panel with text
AnsiConsole.Render(new Panel(
new Text("Right adjusted\nRight").RightAligned())
{
Expand = true,
Border = BorderKind.Rounded,
Header = new Header("Right", new Style(foreground: Color.Blue)).RightAligned(),
});
AnsiConsole.Render(
new Panel(new Text("Right adjusted\nRight").RightAligned())
.Expand()
.RoundedBorder()
.SetHeader("Right", Style.WithForeground(Color.Blue), Justify.Right));
}
}
}

View File

@ -36,7 +36,7 @@ namespace TableExample
private static void RenderBigTable()
{
// Create the table.
var table = new Table().SetBorder(BorderKind.Rounded);
var table = new Table().SetBorderKind(BorderKind.Rounded);
table.AddColumn("[red underline]Foo[/]");
table.AddColumn(new TableColumn("[blue]Bar[/]") { Alignment = Justify.Right, NoWrap = true });
@ -58,7 +58,7 @@ namespace TableExample
private static void RenderNestedTable()
{
// Create simple table.
var simple = new Table().SetBorder(BorderKind.Rounded).SetBorderColor(Color.Red);
var simple = new Table().SetBorderKind(BorderKind.Rounded).SetBorderColor(Color.Red);
simple.AddColumn(new TableColumn("[u]Foo[/]").Centered());
simple.AddColumn(new TableColumn("[u]Bar[/]"));
simple.AddColumn(new TableColumn("[u]Baz[/]"));
@ -67,7 +67,7 @@ namespace TableExample
simple.AddRow("[blue]Hej[/]", "[yellow]Världen![/]", "");
// Create other table.
var second = new Table().SetBorder(BorderKind.Square).SetBorderColor(Color.Green);
var second = new Table().SetBorderKind(BorderKind.Square).SetBorderColor(Color.Green);
second.AddColumn(new TableColumn("[u]Foo[/]"));
second.AddColumn(new TableColumn("[u]Bar[/]"));
second.AddColumn(new TableColumn("[u]Baz[/]"));
@ -75,7 +75,7 @@ namespace TableExample
second.AddRow(simple, new Text("Whaaat"), new Text("Lolz"));
second.AddRow("[blue]Hej[/]", "[yellow]Världen![/]", "");
var table = new Table().SetBorder(BorderKind.Rounded);
var table = new Table().SetBorderKind(BorderKind.Rounded);
table.AddColumn(new TableColumn(new Panel("[u]Foo[/]").SetBorderColor(Color.Red)));
table.AddColumn(new TableColumn(new Panel("[u]Bar[/]").SetBorderColor(Color.Green)));
table.AddColumn(new TableColumn(new Panel("[u]Baz[/]").SetBorderColor(Color.Blue)));

View File

@ -174,7 +174,7 @@ namespace Spectre.Console.Tests.Unit
{
// A simple table
var console = new PlainConsole(width: 80);
var table = new Table() { Border = BorderKind.Rounded };
var table = new Table() { BorderKind = BorderKind.Rounded };
table.AddColumn("Foo");
table.AddColumn("Bar");
table.AddColumn(new TableColumn("Baz") { Alignment = Justify.Right });
@ -184,7 +184,7 @@ namespace Spectre.Console.Tests.Unit
// Render a table in some panels.
console.Render(new Panel(new Panel(table)
{
Border = BorderKind.Ascii,
BorderKind = BorderKind.Ascii,
}));
// Then
@ -256,7 +256,7 @@ namespace Spectre.Console.Tests.Unit
{
// Given
var console = new PlainConsole(width: 80);
var table = new Table { Border = BorderKind.Ascii };
var table = new Table { BorderKind = BorderKind.Ascii };
table.AddColumns("Foo", "Bar", "Baz");
table.AddRow("Qux", "Corgi", "Waldo");
table.AddRow("Grault", "Garply", "Fred");
@ -279,7 +279,7 @@ namespace Spectre.Console.Tests.Unit
{
// Given
var console = new PlainConsole(width: 80);
var table = new Table { Border = BorderKind.Rounded };
var table = new Table { BorderKind = BorderKind.Rounded };
table.AddColumns("Foo", "Bar", "Baz");
table.AddRow("Qux", "Corgi", "Waldo");
table.AddRow("Grault", "Garply", "Fred");
@ -302,7 +302,7 @@ namespace Spectre.Console.Tests.Unit
{
// Given
var console = new PlainConsole(width: 80);
var table = new Table { Border = BorderKind.None };
var table = new Table { BorderKind = BorderKind.None };
table.AddColumns("Foo", "Bar", "Baz");
table.AddRow("Qux", "Corgi", "Waldo");
table.AddRow("Grault", "Garply", "Fred");

View File

@ -1,7 +1,5 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Spectre.Console.Internal;
using Spectre.Console.Rendering;
namespace Spectre.Console
@ -13,6 +11,16 @@ namespace Spectre.Console
{
private readonly Table _table;
/// <summary>
/// Gets the number of columns in the table.
/// </summary>
public int ColumnCount => _table.ColumnCount;
/// <summary>
/// Gets the number of rows in the table.
/// </summary>
public int RowCount => _table.RowCount;
/// <summary>
/// Initializes a new instance of the <see cref="Grid"/> class.
/// </summary>
@ -20,7 +28,7 @@ namespace Spectre.Console
{
_table = new Table
{
Border = BorderKind.None,
BorderKind = BorderKind.None,
ShowHeaders = false,
IsGrid = true,
PadRightCell = false,
@ -75,45 +83,6 @@ namespace Spectre.Console
});
}
/// <summary>
/// Adds a column to the grid.
/// </summary>
/// <param name="count">The number of columns to add.</param>
public void AddColumns(int count)
{
for (var index = 0; index < count; index++)
{
AddColumn(new GridColumn());
}
}
/// <summary>
/// Adds a column to the grid.
/// </summary>
/// <param name="columns">The columns to add.</param>
public void AddColumns(params GridColumn[] columns)
{
if (columns is null)
{
throw new ArgumentNullException(nameof(columns));
}
foreach (var column in columns)
{
AddColumn(column);
}
}
/// <summary>
/// Adds an empty row to the grid.
/// </summary>
public void AddEmptyRow()
{
var columns = new IRenderable[_table.ColumnCount];
Enumerable.Range(0, _table.ColumnCount).ForEach(index => columns[index] = Text.Empty);
AddRow(columns);
}
/// <summary>
/// Adds a new row to the grid.
/// </summary>

View File

@ -1,5 +1,7 @@
using System;
using System.Linq;
using Spectre.Console.Internal;
using Spectre.Console.Rendering;
namespace Spectre.Console
{
@ -8,6 +10,63 @@ namespace Spectre.Console
/// </summary>
public static class GridExtensions
{
/// <summary>
/// Adds a column to the grid.
/// </summary>
/// <param name="grid">The grid to add the column to.</param>
/// <param name="count">The number of columns to add.</param>
public static void AddColumns(this Grid grid, int count)
{
if (grid is null)
{
throw new ArgumentNullException(nameof(grid));
}
for (var index = 0; index < count; index++)
{
grid.AddColumn(new GridColumn());
}
}
/// <summary>
/// Adds a column to the grid.
/// </summary>
/// <param name="grid">The grid to add the column to.</param>
/// <param name="columns">The columns to add.</param>
public static void AddColumns(this Grid grid, params GridColumn[] columns)
{
if (grid is null)
{
throw new ArgumentNullException(nameof(grid));
}
if (columns is null)
{
throw new ArgumentNullException(nameof(columns));
}
foreach (var column in columns)
{
grid.AddColumn(column);
}
}
/// <summary>
/// Adds an empty row to the grid.
/// </summary>
/// <param name="grid">The grid to add the row to.</param>
public static void AddEmptyRow(this Grid grid)
{
if (grid is null)
{
throw new ArgumentNullException(nameof(grid));
}
var columns = new IRenderable[grid.ColumnCount];
Enumerable.Range(0, grid.ColumnCount).ForEach(index => columns[index] = Text.Empty);
grid.AddRow(columns);
}
/// <summary>
/// Adds a new row to the grid.
/// </summary>

View File

@ -16,13 +16,13 @@ namespace Spectre.Console
private readonly IRenderable _child;
/// <inheritdoc/>
public BorderKind Border { get; set; } = BorderKind.Square;
public BorderKind BorderKind { get; set; } = BorderKind.Square;
/// <inheritdoc/>
public bool SafeBorder { get; set; } = true;
/// <inheritdoc/>
public Color? BorderColor { get; set; }
public Style? BorderStyle { get; set; }
/// <summary>
/// Gets or sets a value indicating whether or not the panel should
@ -71,8 +71,8 @@ namespace Spectre.Console
/// <inheritdoc/>
protected override IEnumerable<Segment> Render(RenderContext context, int maxWidth)
{
var border = SpectreBorder.GetBorder(Border, (context.LegacyConsole || !context.Unicode) && SafeBorder);
var borderStyle = new Style(BorderColor, null, null);
var border = SpectreBorder.GetBorder(BorderKind, (context.LegacyConsole || !context.Unicode) && SafeBorder);
var borderStyle = BorderStyle ?? Style.Plain;
var paddingWidth = Padding.GetHorizontalPadding();
var childWidth = maxWidth - EdgeWidth - paddingWidth;

View File

@ -26,10 +26,10 @@ namespace Spectre.Console
public int RowCount => _rows.Count;
/// <inheritdoc/>
public BorderKind Border { get; set; } = BorderKind.Square;
public BorderKind BorderKind { get; set; } = BorderKind.Square;
/// <inheritdoc/>
public Color? BorderColor { get; set; }
public Style? BorderStyle { get; set; }
/// <inheritdoc/>
public bool SafeBorder { get; set; } = true;
@ -168,13 +168,13 @@ namespace Spectre.Console
throw new ArgumentNullException(nameof(context));
}
var border = SpectreBorder.GetBorder(Border, (context.LegacyConsole || !context.Unicode) && SafeBorder);
var borderStyle = new Style(BorderColor, null, null);
var border = SpectreBorder.GetBorder(BorderKind, (context.LegacyConsole || !context.Unicode) && SafeBorder);
var borderStyle = BorderStyle ?? Style.Plain;
var tableWidth = maxWidth;
var showBorder = Border != BorderKind.None;
var hideBorder = Border == BorderKind.None;
var showBorder = BorderKind != BorderKind.None;
var hideBorder = BorderKind == BorderKind.None;
var hasRows = _rows.Count > 0;
if (Width != null)

View File

@ -8,13 +8,61 @@ namespace Spectre.Console.Rendering
public static class BorderExtensions
{
/// <summary>
/// Sets the border.
/// Do not display a border.
/// </summary>
/// <typeparam name="T">The object that has a border.</typeparam>
/// <typeparam name="T">An object type with a border.</typeparam>
/// <param name="obj">The object to set the border for.</param>
/// <param name="border">The border to use.</param>
/// <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 NoBorder<T>(this T obj)
where T : class, IHasBorder
{
return SetBorderKind(obj, BorderKind.None);
}
/// <summary>
/// Display a square border.
/// </summary>
/// <typeparam name="T">An object type with a border.</typeparam>
/// <param name="obj">The object to set the border for.</param>
/// <returns>The same instance so that multiple calls can be chained.</returns>
public static T SquareBorder<T>(this T obj)
where T : class, IHasBorder
{
return SetBorderKind(obj, BorderKind.Square);
}
/// <summary>
/// Display an ASCII border.
/// </summary>
/// <typeparam name="T">An object type with a border.</typeparam>
/// <param name="obj">The object to set the border for.</param>
/// <returns>The same instance so that multiple calls can be chained.</returns>
public static T AsciiBorder<T>(this T obj)
where T : class, IHasBorder
{
return SetBorderKind(obj, BorderKind.Ascii);
}
/// <summary>
/// Display a rounded border.
/// </summary>
/// <typeparam name="T">An object type with a border.</typeparam>
/// <param name="obj">The object to set the border for.</param>
/// <returns>The same instance so that multiple calls can be chained.</returns>
public static T RoundedBorder<T>(this T obj)
where T : class, IHasBorder
{
return SetBorderKind(obj, BorderKind.Rounded);
}
/// <summary>
/// Sets the border kind.
/// </summary>
/// <typeparam name="T">An object type with a border.</typeparam>
/// <param name="obj">The object to set the border for.</param>
/// <param name="border">The border kind to use.</param>
/// <returns>The same instance so that multiple calls can be chained.</returns>
public static T SetBorderKind<T>(this T obj, BorderKind border)
where T : class, IHasBorder
{
if (obj is null)
@ -22,14 +70,14 @@ namespace Spectre.Console.Rendering
throw new ArgumentNullException(nameof(obj));
}
obj.Border = border;
obj.BorderKind = border;
return obj;
}
/// <summary>
/// Disables the safe border.
/// </summary>
/// <typeparam name="T">The object that has a border.</typeparam>
/// <typeparam name="T">An object type with a border.</typeparam>
/// <param name="obj">The object to set the border for.</param>
/// <returns>The same instance so that multiple calls can be chained.</returns>
public static T NoSafeBorder<T>(this T obj)
@ -44,12 +92,31 @@ namespace Spectre.Console.Rendering
return obj;
}
/// <summary>
/// Sets the border style.
/// </summary>
/// <typeparam name="T">An object type with a border.</typeparam>
/// <param name="obj">The object to set the border color for.</param>
/// <param name="style">The border style to set.</param>
/// <returns>The same instance so that multiple calls can be chained.</returns>
public static T SetBorderStyle<T>(this T obj, Style style)
where T : class, IHasBorder
{
if (obj is null)
{
throw new ArgumentNullException(nameof(obj));
}
obj.BorderStyle = style;
return obj;
}
/// <summary>
/// Sets the border color.
/// </summary>
/// <typeparam name="T">The object that has a border.</typeparam>
/// <typeparam name="T">An object type with a border.</typeparam>
/// <param name="obj">The object to set the border color for.</param>
/// <param name="color">The color to set.</param>
/// <param name="color">The border color to set.</param>
/// <returns>The same instance so that multiple calls can be chained.</returns>
public static T SetBorderColor<T>(this T obj, Color color)
where T : class, IHasBorder
@ -59,7 +126,7 @@ namespace Spectre.Console.Rendering
throw new ArgumentNullException(nameof(obj));
}
obj.BorderColor = color;
obj.BorderStyle = (obj.BorderStyle ?? Style.Plain).WithForeground(color);
return obj;
}
}

View File

@ -15,11 +15,11 @@ namespace Spectre.Console
/// <summary>
/// Gets or sets the kind of border to use.
/// </summary>
public BorderKind Border { get; set; }
public BorderKind BorderKind { get; set; }
/// <summary>
/// Gets or sets the border color.
/// Gets or sets the border style.
/// </summary>
public Color? BorderColor { get; set; }
public Style? BorderStyle { get; set; }
}
}

View File

@ -0,0 +1,40 @@
using System;
namespace Spectre.Console
{
/// <summary>
/// Represents color and text decoration.
/// </summary>
public sealed partial class Style : IEquatable<Style>
{
/// <summary>
/// Creates a new style from the specified foreground color.
/// </summary>
/// <param name="color">The foreground color.</param>
/// <returns>A new <see cref="Style"/> with the specified foreground color.</returns>
public static Style WithForeground(Color color)
{
return new Style(foreground: color);
}
/// <summary>
/// Creates a new style from the specified background color.
/// </summary>
/// <param name="color">The background color.</param>
/// <returns>A new <see cref="Style"/> with the specified background color.</returns>
public static Style WithBackground(Color color)
{
return new Style(background: color);
}
/// <summary>
/// Creates a new style from the specified text decoration.
/// </summary>
/// <param name="decoration">The text decoration.</param>
/// <returns>A new <see cref="Style"/> with the specified text decoration.</returns>
public static Style WithDecoration(Decoration decoration)
{
return new Style(decoration: decoration);
}
}
}

View File

@ -6,7 +6,7 @@ namespace Spectre.Console
/// <summary>
/// Represents color and text decoration.
/// </summary>
public sealed class Style : IEquatable<Style>
public sealed partial class Style : IEquatable<Style>
{
/// <summary>
/// Gets the foreground color.

View File

@ -0,0 +1,70 @@
using System;
namespace Spectre.Console
{
/// <summary>
/// Contains extension methods for <see cref="Style"/>.
/// </summary>
public static class StyleExtensions
{
/// <summary>
/// Creates a new style from the specified one with
/// the specified foreground color.
/// </summary>
/// <param name="style">The style.</param>
/// <param name="color">The foreground color.</param>
/// <returns>The same instance so that multiple calls can be chained.</returns>
public static Style WithForeground(this Style style, Color color)
{
if (style is null)
{
throw new ArgumentNullException(nameof(style));
}
return new Style(
foreground: color,
background: style.Background,
decoration: style.Decoration);
}
/// <summary>
/// Creates a new style from the specified one with
/// the specified background color.
/// </summary>
/// <param name="style">The style.</param>
/// <param name="color">The background color.</param>
/// <returns>The same instance so that multiple calls can be chained.</returns>
public static Style WithBackground(this Style style, Color color)
{
if (style is null)
{
throw new ArgumentNullException(nameof(style));
}
return new Style(
foreground: style.Foreground,
background: color,
decoration: style.Decoration);
}
/// <summary>
/// Creates a new style from the specified one with
/// the specified text decoration.
/// </summary>
/// <param name="style">The style.</param>
/// <param name="decoration">The text decoration.</param>
/// <returns>The same instance so that multiple calls can be chained.</returns>
public static Style WithDecoration(this Style style, Decoration decoration)
{
if (style is null)
{
throw new ArgumentNullException(nameof(style));
}
return new Style(
foreground: style.Foreground,
background: style.Background,
decoration: decoration);
}
}
}