mirror of
https://github.com/nsnail/spectre.console.git
synced 2025-04-14 16:02:50 +08:00
parent
0b4321115a
commit
9637066927
@ -17,23 +17,18 @@ namespace Sample
|
||||
AnsiConsole.MarkupLine("[white on red]Good[/] [red]bye[/]!");
|
||||
AnsiConsole.WriteLine();
|
||||
|
||||
// We can also use System.ConsoleColor with AnsiConsole.
|
||||
// To set the Foreground color
|
||||
// We can also use System.ConsoleColor with AnsiConsole
|
||||
// to set the foreground and background color.
|
||||
foreach (ConsoleColor value in Enum.GetValues(typeof(ConsoleColor)))
|
||||
{
|
||||
AnsiConsole.Foreground = value;
|
||||
AnsiConsole.WriteLine("Foreground: ConsoleColor.{0}", value);
|
||||
}
|
||||
var foreground = value;
|
||||
var background = (ConsoleColor)(15 - (int)value);
|
||||
|
||||
AnsiConsole.WriteLine();
|
||||
AnsiConsole.Foreground = Color.Chartreuse2;
|
||||
// As well as the background color
|
||||
foreach (ConsoleColor value in Enum.GetValues(typeof(ConsoleColor)))
|
||||
{
|
||||
AnsiConsole.Background = value;
|
||||
AnsiConsole.WriteLine("Background: ConsoleColor.{0}", value);
|
||||
AnsiConsole.Foreground = foreground;
|
||||
AnsiConsole.Background = background;
|
||||
AnsiConsole.WriteLine("{0} on {1}", foreground, background);
|
||||
AnsiConsole.ResetColors();
|
||||
}
|
||||
AnsiConsole.Reset();
|
||||
|
||||
// We can get the default console via the static API.
|
||||
var console = AnsiConsole.Console;
|
||||
@ -96,6 +91,7 @@ namespace Sample
|
||||
foreground: Color.White),
|
||||
fit: true, content: Justify.Right));
|
||||
|
||||
// A normal, square table
|
||||
var table = new Table();
|
||||
table.AddColumns("[red underline]Foo[/]", "Bar");
|
||||
table.AddRow("[blue][underline]Hell[/]o[/]", "World 🌍");
|
||||
@ -104,7 +100,8 @@ namespace Sample
|
||||
table.AddRow("Hej 👋", "[green]Världen[/]");
|
||||
AnsiConsole.Render(table);
|
||||
|
||||
table = new Table(BorderKind.Rounded);
|
||||
// A rounded table
|
||||
table = new Table { Border = BorderKind.Rounded };
|
||||
table.AddColumns("[red underline]Foo[/]", "Bar");
|
||||
table.AddRow("[blue][underline]Hell[/]o[/]", "World 🌍");
|
||||
table.AddRow("[yellow]Patrik [green]\"Lol[/]\" Svensson[/]", "Was [underline]here[/]!");
|
||||
@ -112,16 +109,52 @@ namespace Sample
|
||||
table.AddRow("Hej 👋", "[green]Världen[/]");
|
||||
AnsiConsole.Render(table);
|
||||
|
||||
// A rounded table without headers
|
||||
table = new Table { Border = BorderKind.Rounded, ShowHeaders = false };
|
||||
table.AddColumns("[red underline]Foo[/]", "Bar");
|
||||
table.AddRow("[blue][underline]Hell[/]o[/]", "World 🌍");
|
||||
table.AddRow("[yellow]Patrik [green]\"Lol[/]\" Svensson[/]", "Was [underline]here[/]!");
|
||||
table.AddRow("Lorem ipsum dolor sit amet, consectetur [blue]adipiscing[/] elit,sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum", "◀ Strange language");
|
||||
table.AddRow("Hej 👋", "[green]Världen[/]");
|
||||
AnsiConsole.Render(table);
|
||||
|
||||
// Emulate the usage information for "dotnet run"
|
||||
AnsiConsole.WriteLine();
|
||||
AnsiConsole.MarkupLine(" Usage: [grey]dotnet [blue]run[/] [[options] [[[[--] <additional arguments>...]][/]");
|
||||
AnsiConsole.MarkupLine("Usage: [grey]dotnet [blue]run[/] [[options] [[[[--] <additional arguments>...]][/]");
|
||||
AnsiConsole.WriteLine();
|
||||
var grid = new Grid();
|
||||
grid.AddColumns(3);
|
||||
grid.AddRow(" Options", "", "");
|
||||
grid.AddRow(" [blue]-h[/], [blue]--help[/]", " ", "Show command line help.");
|
||||
grid.AddRow(" [blue]-c[/], [blue]--configuration[/] <CONFIGURATION>", " ", "The configuration to run for.\nThe default for most projects is [green]Debug[/].");
|
||||
grid.AddRow(" [blue]-v[/], [blue]--verbosity[/] <LEVEL>", " ", "Set the MSBuild verbosity level.\nAllowed values are q[grey][[uiet][/], m[grey][[inimal][/], n[grey][[ormal][/], d[grey][[etailed][/], and diag[grey][[nostic][/].");
|
||||
grid.AddColumn(new GridColumn { NoWrap = true });
|
||||
grid.AddColumn(new GridColumn { NoWrap = true, Width = 2 });
|
||||
grid.AddColumn();
|
||||
grid.AddRow("Options:", "", "");
|
||||
grid.AddRow(" [blue]-h[/], [blue]--help[/]", "", "Show command line help.");
|
||||
grid.AddRow(" [blue]-c[/], [blue]--configuration[/] <CONFIGURATION>", "", "The configuration to run for.\nThe default for most projects is [green]Debug[/].");
|
||||
grid.AddRow(" [blue]-v[/], [blue]--verbosity[/] <LEVEL>", "", "Set the MSBuild verbosity level. Allowed values are \nq[grey][[uiet][/], m[grey][[inimal][/], n[grey][[ormal][/], d[grey][[etailed][/], and diag[grey][[nostic][/].");
|
||||
AnsiConsole.Render(grid);
|
||||
|
||||
// A simple table
|
||||
AnsiConsole.WriteLine();
|
||||
table = new Table { Border = BorderKind.Rounded };
|
||||
table.AddColumn("Foo");
|
||||
table.AddColumn("Bar");
|
||||
table.AddColumn("Baz");
|
||||
table.AddRow("Qux\nQuuuuuux", "[blue]Corgi[/]", "Waldo");
|
||||
table.AddRow("Grault", "Garply", "Fred");
|
||||
AnsiConsole.Render(table);
|
||||
|
||||
// Render a table in some panels.
|
||||
AnsiConsole.Render(new Panel(new Panel(table, border: BorderKind.Ascii)));
|
||||
|
||||
// Draw another table
|
||||
table = new Table { Expand = false };
|
||||
table.AddColumn(new TableColumn("Date"));
|
||||
table.AddColumn(new TableColumn("Title"));
|
||||
table.AddColumn(new TableColumn("Production\nBudget"));
|
||||
table.AddColumn(new TableColumn("Box Office"));
|
||||
table.AddRow("Dec 20, 2019", "Star Wars: The Rise of Skywalker", "$275,000,000", "[red]$375,126,118[/]");
|
||||
table.AddRow("May 25, 2018", "[yellow]Solo[/]: A Star Wars Story", "$275,000,000", "$393,151,347");
|
||||
table.AddRow("Dec 15, 2017", "Star Wars Ep. VIII: The Last Jedi", "$262,000,000", "[bold green]$1,332,539,889[/]");
|
||||
AnsiConsole.Render(table);
|
||||
}
|
||||
}
|
||||
}
|
@ -246,6 +246,23 @@ namespace Spectre.Console.Tests.Unit
|
||||
|
||||
public sealed class WriteLine
|
||||
{
|
||||
[Fact]
|
||||
public void Should_Reset_Colors_Correctly()
|
||||
{
|
||||
// Given
|
||||
var fixture = new AnsiConsoleFixture(ColorSystem.Standard, AnsiSupport.Yes);
|
||||
|
||||
// When
|
||||
fixture.Console.Background = ConsoleColor.Red;
|
||||
fixture.Console.WriteLine("Hello");
|
||||
fixture.Console.Background = ConsoleColor.Green;
|
||||
fixture.Console.WriteLine("World");
|
||||
|
||||
// Then
|
||||
fixture.Output.NormalizeLineEndings()
|
||||
.ShouldBe("[101mHello[0m\n[102mWorld[0m\n");
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(AnsiSupport.Yes)]
|
||||
[InlineData(AnsiSupport.No)]
|
||||
|
@ -27,7 +27,8 @@ namespace Spectre.Console.Tests.Unit.Composition
|
||||
{
|
||||
// Given
|
||||
var grid = new Grid();
|
||||
grid.AddColumns(2);
|
||||
grid.AddColumn();
|
||||
grid.AddColumn();
|
||||
|
||||
// When
|
||||
var result = Record.Exception(() => grid.AddRow("Foo"));
|
||||
@ -59,7 +60,9 @@ namespace Spectre.Console.Tests.Unit.Composition
|
||||
// Given
|
||||
var console = new PlainConsole(width: 80);
|
||||
var grid = new Grid();
|
||||
grid.AddColumns(3);
|
||||
grid.AddColumn();
|
||||
grid.AddColumn();
|
||||
grid.AddColumn();
|
||||
grid.AddRow("Qux", "Corgi", "Waldo");
|
||||
grid.AddRow("Grault", "Garply", "Fred");
|
||||
|
||||
@ -75,22 +78,23 @@ namespace Spectre.Console.Tests.Unit.Composition
|
||||
[Fact]
|
||||
public void Should_Render_Grid()
|
||||
{
|
||||
var console = new PlainConsole(width: 120);
|
||||
var console = new PlainConsole(width: 80);
|
||||
var grid = new Grid();
|
||||
grid.AddColumns(3);
|
||||
grid.AddRow("[bold]Options[/]", string.Empty, string.Empty);
|
||||
grid.AddRow(" [blue]-h[/], [blue]--help[/]", " ", "Show command line help.");
|
||||
grid.AddRow(" [blue]-c[/], [blue]--configuration[/]", " ", "The configuration to run for.\nThe default for most projects is [green]Debug[/].");
|
||||
grid.AddColumn(new GridColumn { NoWrap = true });
|
||||
grid.AddColumn();
|
||||
grid.AddRow("[bold]Options[/]", string.Empty);
|
||||
grid.AddRow(" [blue]-h[/], [blue]--help[/]", "Show command line help.");
|
||||
grid.AddRow(" [blue]-c[/], [blue]--configuration[/]", "The configuration to run for.\nThe default for most projects is [green]Debug[/].");
|
||||
|
||||
// When
|
||||
console.Render(grid);
|
||||
|
||||
// Then
|
||||
console.Lines.Count.ShouldBe(4);
|
||||
console.Lines[0].ShouldBe("Options ");
|
||||
console.Lines[1].ShouldBe(" -h, --help Show command line help. ");
|
||||
console.Lines[2].ShouldBe(" -c, --configuration The configuration to run for. ");
|
||||
console.Lines[3].ShouldBe(" The default for most projects is Debug.");
|
||||
console.Lines[0].ShouldBe("Options ");
|
||||
console.Lines[1].ShouldBe(" -h, --help Show command line help. ");
|
||||
console.Lines[2].ShouldBe(" -c, --configuration The configuration to run for. ");
|
||||
console.Lines[3].ShouldBe(" The default for most projects is Debug. ");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -15,7 +15,7 @@ namespace Spectre.Console.Tests.Unit.Composition
|
||||
var table = new Table();
|
||||
|
||||
// When
|
||||
var result = Record.Exception(() => table.AddColumn(null));
|
||||
var result = Record.Exception(() => table.AddColumn((string)null));
|
||||
|
||||
// Then
|
||||
result.ShouldBeOfType<ArgumentNullException>()
|
||||
@ -88,6 +88,31 @@ namespace Spectre.Console.Tests.Unit.Composition
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Should_Measure_Table_Correctly()
|
||||
{
|
||||
// Given
|
||||
var console = new PlainConsole(width: 80);
|
||||
var table = new Table();
|
||||
table.AddColumns("Foo", "Bar", "Baz");
|
||||
table.AddRow("Qux", "Corgi", "Waldo");
|
||||
table.AddRow("Grault", "Garply", "Fred");
|
||||
|
||||
// When
|
||||
console.Render(new Panel(table));
|
||||
|
||||
// Then
|
||||
console.Lines.Count.ShouldBe(8);
|
||||
console.Lines[0].ShouldBe("┌─────────────────────────────┐");
|
||||
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("└─────────────────────────────┘");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Should_Render_Table_Correctly()
|
||||
{
|
||||
@ -102,6 +127,7 @@ namespace Spectre.Console.Tests.Unit.Composition
|
||||
console.Render(table);
|
||||
|
||||
// Then
|
||||
console.Lines.Count.ShouldBe(6);
|
||||
console.Lines[0].ShouldBe("┌────────┬────────┬───────┐");
|
||||
console.Lines[1].ShouldBe("│ Foo │ Bar │ Baz │");
|
||||
console.Lines[2].ShouldBe("├────────┼────────┼───────┤");
|
||||
@ -111,11 +137,11 @@ namespace Spectre.Console.Tests.Unit.Composition
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Should_Render_Table_With_Ascii_Border_Correctly()
|
||||
public void Should_Expand_Table_To_Available_Space_If_Specified()
|
||||
{
|
||||
// Given
|
||||
var console = new PlainConsole(width: 80);
|
||||
var table = new Table(BorderKind.Ascii);
|
||||
var table = new Table() { Expand = true };
|
||||
table.AddColumns("Foo", "Bar", "Baz");
|
||||
table.AddRow("Qux", "Corgi", "Waldo");
|
||||
table.AddRow("Grault", "Garply", "Fred");
|
||||
@ -124,6 +150,31 @@ namespace Spectre.Console.Tests.Unit.Composition
|
||||
console.Render(table);
|
||||
|
||||
// Then
|
||||
console.Lines.Count.ShouldBe(6);
|
||||
console.Lines[0].Length.ShouldBe(80);
|
||||
console.Lines[0].ShouldBe("┌───────────────────────────┬───────────────────────────┬──────────────────────┐");
|
||||
console.Lines[1].ShouldBe("│ Foo │ Bar │ Baz │");
|
||||
console.Lines[2].ShouldBe("├───────────────────────────┼───────────────────────────┼──────────────────────┤");
|
||||
console.Lines[3].ShouldBe("│ Qux │ Corgi │ Waldo │");
|
||||
console.Lines[4].ShouldBe("│ Grault │ Garply │ Fred │");
|
||||
console.Lines[5].ShouldBe("└───────────────────────────┴───────────────────────────┴──────────────────────┘");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Should_Render_Table_With_Ascii_Border_Correctly()
|
||||
{
|
||||
// Given
|
||||
var console = new PlainConsole(width: 80);
|
||||
var table = new Table { Border = BorderKind.Ascii };
|
||||
table.AddColumns("Foo", "Bar", "Baz");
|
||||
table.AddRow("Qux", "Corgi", "Waldo");
|
||||
table.AddRow("Grault", "Garply", "Fred");
|
||||
|
||||
// When
|
||||
console.Render(table);
|
||||
|
||||
// Then
|
||||
console.Lines.Count.ShouldBe(6);
|
||||
console.Lines[0].ShouldBe("+-------------------------+");
|
||||
console.Lines[1].ShouldBe("| Foo | Bar | Baz |");
|
||||
console.Lines[2].ShouldBe("|--------+--------+-------|");
|
||||
@ -137,7 +188,7 @@ namespace Spectre.Console.Tests.Unit.Composition
|
||||
{
|
||||
// Given
|
||||
var console = new PlainConsole(width: 80);
|
||||
var table = new Table(BorderKind.Rounded);
|
||||
var table = new Table { Border = BorderKind.Rounded };
|
||||
table.AddColumns("Foo", "Bar", "Baz");
|
||||
table.AddRow("Qux", "Corgi", "Waldo");
|
||||
table.AddRow("Grault", "Garply", "Fred");
|
||||
@ -146,6 +197,7 @@ namespace Spectre.Console.Tests.Unit.Composition
|
||||
console.Render(table);
|
||||
|
||||
// Then
|
||||
console.Lines.Count.ShouldBe(6);
|
||||
console.Lines[0].ShouldBe("╭────────┬────────┬───────╮");
|
||||
console.Lines[1].ShouldBe("│ Foo │ Bar │ Baz │");
|
||||
console.Lines[2].ShouldBe("├────────┼────────┼───────┤");
|
||||
@ -159,7 +211,7 @@ namespace Spectre.Console.Tests.Unit.Composition
|
||||
{
|
||||
// Given
|
||||
var console = new PlainConsole(width: 80);
|
||||
var table = new Table(BorderKind.None);
|
||||
var table = new Table { Border = BorderKind.None };
|
||||
table.AddColumns("Foo", "Bar", "Baz");
|
||||
table.AddRow("Qux", "Corgi", "Waldo");
|
||||
table.AddRow("Grault", "Garply", "Fred");
|
||||
@ -188,6 +240,7 @@ namespace Spectre.Console.Tests.Unit.Composition
|
||||
console.Render(table);
|
||||
|
||||
// Then
|
||||
console.Lines.Count.ShouldBe(7);
|
||||
console.Lines[0].ShouldBe("┌────────┬────────┬───────┐");
|
||||
console.Lines[1].ShouldBe("│ Foo │ Bar │ Baz │");
|
||||
console.Lines[2].ShouldBe("├────────┼────────┼───────┤");
|
||||
|
@ -17,23 +17,27 @@ namespace Spectre.Console
|
||||
/// </summary>
|
||||
public Grid()
|
||||
{
|
||||
_table = new Table(BorderKind.None, showHeaders: false);
|
||||
_table = new Table
|
||||
{
|
||||
Border = BorderKind.None,
|
||||
ShowHeaders = false,
|
||||
};
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public int Measure(Encoding encoding, int maxWidth)
|
||||
public Measurement Measure(Encoding encoding, int maxWidth)
|
||||
{
|
||||
return _table.Measure(encoding, maxWidth);
|
||||
return ((IRenderable)_table).Measure(encoding, maxWidth);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IEnumerable<Segment> Render(Encoding encoding, int width)
|
||||
{
|
||||
return _table.Render(encoding, width);
|
||||
return ((IRenderable)_table).Render(encoding, width);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a single column to the grid.
|
||||
/// Adds a column to the grid.
|
||||
/// </summary>
|
||||
public void AddColumn()
|
||||
{
|
||||
@ -41,15 +45,23 @@ namespace Spectre.Console
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds the specified number of columns to the grid.
|
||||
/// Adds a column to the grid.
|
||||
/// </summary>
|
||||
/// <param name="count">The number of columns.</param>
|
||||
public void AddColumns(int count)
|
||||
/// <param name="column">The column to add.</param>
|
||||
public void AddColumn(GridColumn column)
|
||||
{
|
||||
for (var i = 0; i < count; i++)
|
||||
if (column is null)
|
||||
{
|
||||
_table.AddColumn(string.Empty);
|
||||
throw new ArgumentNullException(nameof(column));
|
||||
}
|
||||
|
||||
_table.AddColumn(new TableColumn(string.Empty)
|
||||
{
|
||||
Width = column.Width,
|
||||
NoWrap = column.NoWrap,
|
||||
LeftPadding = 0,
|
||||
RightPadding = 1,
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
20
src/Spectre.Console/Composition/GridColumn.cs
Normal file
20
src/Spectre.Console/Composition/GridColumn.cs
Normal file
@ -0,0 +1,20 @@
|
||||
namespace Spectre.Console
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a grid column.
|
||||
/// </summary>
|
||||
public sealed class GridColumn
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the width of the column.
|
||||
/// If <c>null</c>, the column will adapt to it's contents.
|
||||
/// </summary>
|
||||
public int? Width { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether wrapping of
|
||||
/// text within the column should be prevented.
|
||||
/// </summary>
|
||||
public bool NoWrap { get; set; }
|
||||
}
|
||||
}
|
@ -13,8 +13,8 @@ namespace Spectre.Console.Composition
|
||||
/// </summary>
|
||||
/// <param name="encoding">The encoding to use.</param>
|
||||
/// <param name="maxWidth">The maximum allowed width.</param>
|
||||
/// <returns>The width of the object.</returns>
|
||||
int Measure(Encoding encoding, int maxWidth);
|
||||
/// <returns>The minimum and maximum width of the object.</returns>
|
||||
Measurement Measure(Encoding encoding, int maxWidth);
|
||||
|
||||
/// <summary>
|
||||
/// Renders the object.
|
||||
|
77
src/Spectre.Console/Composition/Measurement.cs
Normal file
77
src/Spectre.Console/Composition/Measurement.cs
Normal file
@ -0,0 +1,77 @@
|
||||
using System;
|
||||
|
||||
namespace Spectre.Console.Composition
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a measurement.
|
||||
/// </summary>
|
||||
public struct Measurement : IEquatable<Measurement>
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the minimum width.
|
||||
/// </summary>
|
||||
public int Min { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the maximum width.
|
||||
/// </summary>
|
||||
public int Max { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="Measurement"/> struct.
|
||||
/// </summary>
|
||||
/// <param name="min">The minimum width.</param>
|
||||
/// <param name="max">The maximum width.</param>
|
||||
public Measurement(int min, int max)
|
||||
{
|
||||
Min = min;
|
||||
Max = max;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
return obj is Measurement measurement && Equals(measurement);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override int GetHashCode()
|
||||
{
|
||||
unchecked
|
||||
{
|
||||
var hash = (int)2166136261;
|
||||
hash = (hash * 16777619) ^ Min.GetHashCode();
|
||||
hash = (hash * 16777619) ^ Max.GetHashCode();
|
||||
return hash;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool Equals(Measurement other)
|
||||
{
|
||||
return Min == other.Min && Max == other.Max;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if two <see cref="Measurement"/> instances are equal.
|
||||
/// </summary>
|
||||
/// <param name="left">The first measurement instance to compare.</param>
|
||||
/// <param name="right">The second measurement instance to compare.</param>
|
||||
/// <returns><c>true</c> if the two measurements are equal, otherwise <c>false</c>.</returns>
|
||||
public static bool operator ==(Measurement left, Measurement right)
|
||||
{
|
||||
return left.Equals(right);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if two <see cref="Measurement"/> instances are not equal.
|
||||
/// </summary>
|
||||
/// <param name="left">The first measurement instance to compare.</param>
|
||||
/// <param name="right">The second measurement instance to compare.</param>
|
||||
/// <returns><c>true</c> if the two measurements are not equal, otherwise <c>false</c>.</returns>
|
||||
public static bool operator !=(Measurement left, Measurement right)
|
||||
{
|
||||
return !(left == right);
|
||||
}
|
||||
}
|
||||
}
|
@ -35,19 +35,20 @@ namespace Spectre.Console
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public int Measure(Encoding encoding, int maxWidth)
|
||||
Measurement IRenderable.Measure(Encoding encoding, int maxWidth)
|
||||
{
|
||||
var childWidth = _child.Measure(encoding, maxWidth);
|
||||
return childWidth + 4;
|
||||
return new Measurement(childWidth.Min + 4, childWidth.Max + 4);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IEnumerable<Segment> Render(Encoding encoding, int width)
|
||||
IEnumerable<Segment> IRenderable.Render(Encoding encoding, int width)
|
||||
{
|
||||
var childWidth = width - 4;
|
||||
if (!_fit)
|
||||
{
|
||||
childWidth = _child.Measure(encoding, width - 2);
|
||||
var measurement = _child.Measure(encoding, width - 2);
|
||||
childWidth = measurement.Max;
|
||||
}
|
||||
|
||||
var result = new List<Segment>();
|
||||
|
@ -211,5 +211,21 @@ namespace Spectre.Console.Composition
|
||||
|
||||
return lines;
|
||||
}
|
||||
|
||||
internal static List<List<SegmentLine>> MakeSameHeight(int cellHeight, List<List<SegmentLine>> cells)
|
||||
{
|
||||
foreach (var cell in cells)
|
||||
{
|
||||
if (cell.Count < cellHeight)
|
||||
{
|
||||
while (cell.Count != cellHeight)
|
||||
{
|
||||
cell.Add(new SegmentLine());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return cells;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
168
src/Spectre.Console/Composition/Table.Calculations.cs
Normal file
168
src/Spectre.Console/Composition/Table.Calculations.cs
Normal file
@ -0,0 +1,168 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using Spectre.Console.Composition;
|
||||
using Spectre.Console.Internal;
|
||||
|
||||
namespace Spectre.Console
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a table.
|
||||
/// </summary>
|
||||
public sealed partial class Table
|
||||
{
|
||||
// Calculate the widths of each column, including padding, not including borders.
|
||||
// Ported from Rich by Will McGugan, licensed under MIT.
|
||||
// https://github.com/willmcgugan/rich/blob/527475837ebbfc427530b3ee0d4d0741d2d0fc6d/rich/table.py#L394
|
||||
private List<int> CalculateColumnWidths(Encoding encoding, int maxWidth)
|
||||
{
|
||||
var width_ranges = _columns.Select(column => MeasureColumn(column, encoding, maxWidth));
|
||||
var widths = width_ranges.Select(range => range.Max).ToList();
|
||||
|
||||
var tableWidth = widths.Sum();
|
||||
|
||||
if (ShouldExpand())
|
||||
{
|
||||
var ratios = _columns.Select(c => c.Ratio ?? 0).ToList();
|
||||
if (ratios.Any(r => r != 0))
|
||||
{
|
||||
var fixedWidths = new List<int>();
|
||||
foreach (var (range, column) in width_ranges.Zip(_columns, (a, b) => (a, b)))
|
||||
{
|
||||
fixedWidths.Add(column.IsFlexible() ? 0 : range.Max);
|
||||
}
|
||||
|
||||
var flexMinimum = new List<int>();
|
||||
foreach (var column in _columns)
|
||||
{
|
||||
if (column.IsFlexible())
|
||||
{
|
||||
flexMinimum.Add(column.Width ?? 1 + column.GetPadding());
|
||||
}
|
||||
else
|
||||
{
|
||||
flexMinimum.Add(0);
|
||||
}
|
||||
}
|
||||
|
||||
var flexibleWidth = maxWidth - fixedWidths.Sum();
|
||||
var flexWidths = Ratio.Distribute(flexibleWidth, ratios, flexMinimum);
|
||||
|
||||
var flexWidthsIterator = flexWidths.GetEnumerator();
|
||||
foreach (var (index, _, _, column) in _columns.Enumerate())
|
||||
{
|
||||
if (column.IsFlexible())
|
||||
{
|
||||
flexWidthsIterator.MoveNext();
|
||||
widths[index] = fixedWidths[index] + flexWidthsIterator.Current;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tableWidth = widths.Sum();
|
||||
|
||||
if (tableWidth > maxWidth)
|
||||
{
|
||||
var wrappable = _columns.Select(c => !c.NoWrap).ToList();
|
||||
widths = CollapseWidths(widths, wrappable, maxWidth);
|
||||
tableWidth = widths.Sum();
|
||||
|
||||
// last resort, reduce columns evenly
|
||||
if (tableWidth > maxWidth)
|
||||
{
|
||||
var excessWidth = tableWidth - maxWidth;
|
||||
widths = Ratio.Reduce(excessWidth, widths.Select(w => 1).ToList(), widths, widths);
|
||||
tableWidth = widths.Sum();
|
||||
}
|
||||
}
|
||||
|
||||
if (tableWidth < maxWidth && ShouldExpand())
|
||||
{
|
||||
var padWidths = Ratio.Distribute(maxWidth - tableWidth, widths);
|
||||
widths = widths.Zip(padWidths, (a, b) => (a, b)).Select(f => f.a + f.b).ToList();
|
||||
}
|
||||
|
||||
return widths;
|
||||
}
|
||||
|
||||
// Reduce widths so that the total is less or equal to the max width.
|
||||
// Ported from Rich by Will McGugan, licensed under MIT.
|
||||
// https://github.com/willmcgugan/rich/blob/527475837ebbfc427530b3ee0d4d0741d2d0fc6d/rich/table.py#L442
|
||||
private static List<int> CollapseWidths(List<int> widths, List<bool> wrappable, int maxWidth)
|
||||
{
|
||||
var totalWidth = widths.Sum();
|
||||
var excessWidth = totalWidth - maxWidth;
|
||||
|
||||
if (wrappable.AnyTrue())
|
||||
{
|
||||
while (totalWidth > 0 && excessWidth > 0)
|
||||
{
|
||||
var maxColumn = widths.Zip(wrappable, (first, second) => (width: first, isWrappable: second))
|
||||
.Where(x => x.isWrappable)
|
||||
.Max(x => x.width);
|
||||
|
||||
var secondMaxColumn = widths.Zip(wrappable, (width, isWrappable) => isWrappable && width != maxColumn ? maxColumn : 0).Max();
|
||||
var columnDifference = maxColumn - secondMaxColumn;
|
||||
|
||||
var ratios = widths.Zip(wrappable, (width, allowWrap) =>
|
||||
{
|
||||
if (width == maxColumn && allowWrap)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}).ToList();
|
||||
|
||||
if (!ratios.Any(x => x > 0) || columnDifference == 0)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
var maxReduce = widths.Select(_ => Math.Min(excessWidth, columnDifference)).ToList();
|
||||
widths = Ratio.Reduce(excessWidth, ratios, maxReduce, widths);
|
||||
|
||||
totalWidth = widths.Sum();
|
||||
excessWidth = totalWidth - maxWidth;
|
||||
}
|
||||
}
|
||||
|
||||
return widths;
|
||||
}
|
||||
|
||||
private (int Min, int Max) MeasureColumn(TableColumn column, Encoding encoding, int maxWidth)
|
||||
{
|
||||
// Predetermined width?
|
||||
if (column.Width != null)
|
||||
{
|
||||
var padding = column.GetPadding();
|
||||
return (column.Width.Value + padding, column.Width.Value + padding);
|
||||
}
|
||||
|
||||
var columnIndex = _columns.IndexOf(column);
|
||||
var rows = _rows.Select(row => row[columnIndex]);
|
||||
|
||||
var minWidths = new List<int>();
|
||||
var maxWidths = new List<int>();
|
||||
foreach (var row in rows)
|
||||
{
|
||||
var measure = ((IRenderable)row).Measure(encoding, maxWidth);
|
||||
minWidths.Add(measure.Min);
|
||||
maxWidths.Add(measure.Max);
|
||||
}
|
||||
|
||||
return (minWidths.Count > 0 ? minWidths.Max() : 1,
|
||||
maxWidths.Count > 0 ? maxWidths.Max() : maxWidth);
|
||||
}
|
||||
|
||||
private int GetExtraWidth(bool includePadding)
|
||||
{
|
||||
var edges = 2;
|
||||
var separators = _columns.Count - 1;
|
||||
var padding = includePadding ? _columns.Select(x => x.GetPadding()).Sum() : 0;
|
||||
return separators + edges + padding;
|
||||
}
|
||||
}
|
||||
}
|
@ -10,31 +10,50 @@ namespace Spectre.Console
|
||||
/// <summary>
|
||||
/// Represents a table.
|
||||
/// </summary>
|
||||
public sealed class Table : IRenderable
|
||||
public sealed partial class Table : IRenderable
|
||||
{
|
||||
private readonly List<Text> _columns;
|
||||
private readonly List<TableColumn> _columns;
|
||||
private readonly List<List<Text>> _rows;
|
||||
private readonly Border _border;
|
||||
private readonly BorderKind _borderKind;
|
||||
private readonly bool _showHeaders;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the number of columns in the table.
|
||||
/// </summary>
|
||||
public int ColumnCount => _columns.Count;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the number of rows in the table.
|
||||
/// </summary>
|
||||
public int RowCount => _rows.Count;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the kind of border to use.
|
||||
/// </summary>
|
||||
public BorderKind Border { get; set; } = BorderKind.Square;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether or not table headers should be shown.
|
||||
/// </summary>
|
||||
public bool ShowHeaders { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether or not the table should
|
||||
/// fit the available space. If <c>false</c>, the table width will be
|
||||
/// auto calculated. Defaults to <c>false</c>.
|
||||
/// </summary>
|
||||
public bool Expand { get; set; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the width of the table.
|
||||
/// </summary>
|
||||
public int? Width { get; set; } = null;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="Table"/> class.
|
||||
/// </summary>
|
||||
/// <param name="border">The border to use.</param>
|
||||
/// <param name="showHeaders">Whether or not to show table headers.</param>
|
||||
public Table(BorderKind border = BorderKind.Square, bool showHeaders = true)
|
||||
public Table()
|
||||
{
|
||||
_columns = new List<Text>();
|
||||
_columns = new List<TableColumn>();
|
||||
_rows = new List<List<Text>>();
|
||||
_border = Border.GetBorder(border);
|
||||
_borderKind = border;
|
||||
_showHeaders = showHeaders;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -48,7 +67,21 @@ namespace Spectre.Console
|
||||
throw new ArgumentNullException(nameof(column));
|
||||
}
|
||||
|
||||
_columns.Add(Text.New(column));
|
||||
_columns.Add(new TableColumn(column));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a column to the table.
|
||||
/// </summary>
|
||||
/// <param name="column">The column to add.</param>
|
||||
public void AddColumn(TableColumn column)
|
||||
{
|
||||
if (column is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(column));
|
||||
}
|
||||
|
||||
_columns.Add(column);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -62,7 +95,7 @@ namespace Spectre.Console
|
||||
throw new ArgumentNullException(nameof(columns));
|
||||
}
|
||||
|
||||
_columns.AddRange(columns.Select(column => Text.New(column)));
|
||||
_columns.AddRange(columns.Select(column => new TableColumn(column)));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -90,66 +123,55 @@ namespace Spectre.Console
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public int Measure(Encoding encoding, int maxWidth)
|
||||
Measurement IRenderable.Measure(Encoding encoding, int maxWidth)
|
||||
{
|
||||
// Calculate the max width for each column
|
||||
var maxColumnWidth = (maxWidth - (2 + (_columns.Count * 2) + (_columns.Count - 1))) / _columns.Count;
|
||||
var columnWidths = _columns.Select(c => c.Measure(encoding, maxColumnWidth)).ToArray();
|
||||
for (var rowIndex = 0; rowIndex < _rows.Count; rowIndex++)
|
||||
if (Width != null)
|
||||
{
|
||||
for (var columnIndex = 0; columnIndex < _rows[rowIndex].Count; columnIndex++)
|
||||
{
|
||||
var columnWidth = _rows[rowIndex][columnIndex].Measure(encoding, maxColumnWidth);
|
||||
if (columnWidth > columnWidths[columnIndex])
|
||||
{
|
||||
columnWidths[columnIndex] = columnWidth;
|
||||
}
|
||||
}
|
||||
maxWidth = Math.Min(Width.Value, maxWidth);
|
||||
}
|
||||
|
||||
// We now know the max width of each column, so let's recalculate the width
|
||||
return columnWidths.Sum() + 2 + (_columns.Count * 2) + (_columns.Count - 1);
|
||||
maxWidth -= GetExtraWidth(includePadding: true);
|
||||
|
||||
var measurements = _columns.Select(column => MeasureColumn(column, encoding, maxWidth)).ToList();
|
||||
var min = measurements.Sum(x => x.Min) + GetExtraWidth(includePadding: true);
|
||||
var max = Width ?? measurements.Sum(x => x.Max) + GetExtraWidth(includePadding: true);
|
||||
|
||||
return new Measurement(min, max);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IEnumerable<Segment> Render(Encoding encoding, int width)
|
||||
IEnumerable<Segment> IRenderable.Render(Encoding encoding, int width)
|
||||
{
|
||||
var showBorder = _borderKind != BorderKind.None;
|
||||
var hideBorder = _borderKind == BorderKind.None;
|
||||
var border = Composition.Border.GetBorder(Border);
|
||||
|
||||
var leftRightBorderWidth = _borderKind == BorderKind.None ? 0 : 2;
|
||||
var columnPadding = _borderKind == BorderKind.None ? _columns.Count : _columns.Count * 2;
|
||||
var separatorCount = _borderKind == BorderKind.None ? 0 : _columns.Count - 1;
|
||||
var showBorder = Border != BorderKind.None;
|
||||
var hideBorder = Border == BorderKind.None;
|
||||
|
||||
// Calculate the max width for each column.
|
||||
var maxColumnWidth = (width - (leftRightBorderWidth + columnPadding + separatorCount)) / _columns.Count;
|
||||
var columnWidths = _columns.Select(c => c.Measure(encoding, maxColumnWidth)).ToArray();
|
||||
for (var rowIndex = 0; rowIndex < _rows.Count; rowIndex++)
|
||||
var maxWidth = width;
|
||||
if (Width != null)
|
||||
{
|
||||
for (var columnIndex = 0; columnIndex < _rows[rowIndex].Count; columnIndex++)
|
||||
{
|
||||
var columnWidth = _rows[rowIndex][columnIndex].Measure(encoding, maxColumnWidth);
|
||||
if (columnWidth > columnWidths[columnIndex])
|
||||
{
|
||||
columnWidths[columnIndex] = columnWidth;
|
||||
}
|
||||
}
|
||||
maxWidth = Math.Min(Width.Value, maxWidth);
|
||||
}
|
||||
|
||||
// We now know the max width of each column, so let's recalculate the width
|
||||
width = columnWidths.Sum() + leftRightBorderWidth + columnPadding + separatorCount;
|
||||
maxWidth -= GetExtraWidth(includePadding: true);
|
||||
|
||||
// Calculate the column and table widths
|
||||
var columnWidths = CalculateColumnWidths(encoding, maxWidth);
|
||||
|
||||
// Update the table width.
|
||||
width = columnWidths.Sum() + GetExtraWidth(includePadding: false);
|
||||
|
||||
var rows = new List<List<Text>>();
|
||||
if (_showHeaders)
|
||||
if (ShowHeaders)
|
||||
{
|
||||
// Add columns to top of rows
|
||||
rows.Add(new List<Text>(_columns));
|
||||
rows.Add(new List<Text>(_columns.Select(c => c.Text)));
|
||||
}
|
||||
|
||||
// Add tows.
|
||||
// Add rows.
|
||||
rows.AddRange(_rows);
|
||||
|
||||
// Iterate all rows.
|
||||
// Iterate all rows
|
||||
var result = new List<Segment>();
|
||||
foreach (var (index, firstRow, lastRow, row) in rows.Enumerate())
|
||||
{
|
||||
@ -159,7 +181,7 @@ namespace Spectre.Console
|
||||
var cells = new List<List<SegmentLine>>();
|
||||
foreach (var (rowWidth, cell) in columnWidths.Zip(row, (f, s) => (f, s)))
|
||||
{
|
||||
var lines = Segment.SplitLines(cell.Render(encoding, rowWidth));
|
||||
var lines = Segment.SplitLines(((IRenderable)cell).Render(encoding, rowWidth));
|
||||
cellHeight = Math.Max(cellHeight, lines.Count);
|
||||
cells.Add(lines);
|
||||
}
|
||||
@ -167,20 +189,20 @@ namespace Spectre.Console
|
||||
// Show top of header?
|
||||
if (firstRow && showBorder)
|
||||
{
|
||||
result.Add(new Segment(_border.GetPart(BorderPart.HeaderTopLeft)));
|
||||
result.Add(new Segment(border.GetPart(BorderPart.HeaderTopLeft)));
|
||||
foreach (var (columnIndex, _, lastColumn, columnWidth) in columnWidths.Enumerate())
|
||||
{
|
||||
result.Add(new Segment(_border.GetPart(BorderPart.HeaderTop))); // Left padding
|
||||
result.Add(new Segment(_border.GetPart(BorderPart.HeaderTop, columnWidth)));
|
||||
result.Add(new Segment(_border.GetPart(BorderPart.HeaderTop))); // Right padding
|
||||
result.Add(new Segment(border.GetPart(BorderPart.HeaderTop))); // Left padding
|
||||
result.Add(new Segment(border.GetPart(BorderPart.HeaderTop, columnWidth)));
|
||||
result.Add(new Segment(border.GetPart(BorderPart.HeaderTop))); // Right padding
|
||||
|
||||
if (!lastColumn)
|
||||
{
|
||||
result.Add(new Segment(_border.GetPart(BorderPart.HeaderTopSeparator)));
|
||||
result.Add(new Segment(border.GetPart(BorderPart.HeaderTopSeparator)));
|
||||
}
|
||||
}
|
||||
|
||||
result.Add(new Segment(_border.GetPart(BorderPart.HeaderTopRight)));
|
||||
result.Add(new Segment(border.GetPart(BorderPart.HeaderTopRight)));
|
||||
result.Add(Segment.LineBreak());
|
||||
}
|
||||
|
||||
@ -188,21 +210,24 @@ namespace Spectre.Console
|
||||
foreach (var cellRowIndex in Enumerable.Range(0, cellHeight))
|
||||
{
|
||||
// Make cells the same shape
|
||||
MakeSameHeight(cellHeight, cells);
|
||||
cells = Segment.MakeSameHeight(cellHeight, cells);
|
||||
|
||||
var w00t = cells.Enumerate().ToArray();
|
||||
foreach (var (cellIndex, firstCell, lastCell, cell) in w00t)
|
||||
foreach (var (cellIndex, firstCell, lastCell, cell) in cells.Enumerate())
|
||||
{
|
||||
if (firstCell && showBorder)
|
||||
{
|
||||
// Show left column edge
|
||||
result.Add(new Segment(_border.GetPart(BorderPart.CellLeft)));
|
||||
result.Add(new Segment(border.GetPart(BorderPart.CellLeft)));
|
||||
}
|
||||
|
||||
// Pad column on left side.
|
||||
if (showBorder)
|
||||
{
|
||||
result.Add(new Segment(" "));
|
||||
var leftPadding = _columns[cellIndex].LeftPadding;
|
||||
if (leftPadding > 0)
|
||||
{
|
||||
result.Add(new Segment(new string(' ', leftPadding)));
|
||||
}
|
||||
}
|
||||
|
||||
// Add content
|
||||
@ -218,18 +243,22 @@ namespace Spectre.Console
|
||||
// Pad column on the right side
|
||||
if (showBorder || (hideBorder && !lastCell))
|
||||
{
|
||||
result.Add(new Segment(" "));
|
||||
var rightPadding = _columns[cellIndex].RightPadding;
|
||||
if (rightPadding > 0)
|
||||
{
|
||||
result.Add(new Segment(new string(' ', rightPadding)));
|
||||
}
|
||||
}
|
||||
|
||||
if (lastCell && showBorder)
|
||||
{
|
||||
// Add right column edge
|
||||
result.Add(new Segment(_border.GetPart(BorderPart.CellRight)));
|
||||
result.Add(new Segment(border.GetPart(BorderPart.CellRight)));
|
||||
}
|
||||
else if (showBorder || (hideBorder && !lastCell))
|
||||
{
|
||||
// Add column separator
|
||||
result.Add(new Segment(_border.GetPart(BorderPart.CellSeparator)));
|
||||
result.Add(new Segment(border.GetPart(BorderPart.CellSeparator)));
|
||||
}
|
||||
}
|
||||
|
||||
@ -237,42 +266,42 @@ namespace Spectre.Console
|
||||
}
|
||||
|
||||
// Show bottom of header?
|
||||
if (firstRow && showBorder)
|
||||
if (firstRow && showBorder && ShowHeaders)
|
||||
{
|
||||
result.Add(new Segment(_border.GetPart(BorderPart.HeaderBottomLeft)));
|
||||
result.Add(new Segment(border.GetPart(BorderPart.HeaderBottomLeft)));
|
||||
foreach (var (columnIndex, first, lastColumn, columnWidth) in columnWidths.Enumerate())
|
||||
{
|
||||
result.Add(new Segment(_border.GetPart(BorderPart.HeaderBottom))); // Left padding
|
||||
result.Add(new Segment(_border.GetPart(BorderPart.HeaderBottom, columnWidth)));
|
||||
result.Add(new Segment(_border.GetPart(BorderPart.HeaderBottom))); // Right padding
|
||||
result.Add(new Segment(border.GetPart(BorderPart.HeaderBottom))); // Left padding
|
||||
result.Add(new Segment(border.GetPart(BorderPart.HeaderBottom, columnWidth)));
|
||||
result.Add(new Segment(border.GetPart(BorderPart.HeaderBottom))); // Right padding
|
||||
|
||||
if (!lastColumn)
|
||||
{
|
||||
result.Add(new Segment(_border.GetPart(BorderPart.HeaderBottomSeparator)));
|
||||
result.Add(new Segment(border.GetPart(BorderPart.HeaderBottomSeparator)));
|
||||
}
|
||||
}
|
||||
|
||||
result.Add(new Segment(_border.GetPart(BorderPart.HeaderBottomRight)));
|
||||
result.Add(new Segment(border.GetPart(BorderPart.HeaderBottomRight)));
|
||||
result.Add(Segment.LineBreak());
|
||||
}
|
||||
|
||||
// Show bottom of footer?
|
||||
if (lastRow && showBorder)
|
||||
{
|
||||
result.Add(new Segment(_border.GetPart(BorderPart.FooterBottomLeft)));
|
||||
result.Add(new Segment(border.GetPart(BorderPart.FooterBottomLeft)));
|
||||
foreach (var (columnIndex, first, lastColumn, columnWidth) in columnWidths.Enumerate())
|
||||
{
|
||||
result.Add(new Segment(_border.GetPart(BorderPart.FooterBottom)));
|
||||
result.Add(new Segment(_border.GetPart(BorderPart.FooterBottom, columnWidth)));
|
||||
result.Add(new Segment(_border.GetPart(BorderPart.FooterBottom)));
|
||||
result.Add(new Segment(border.GetPart(BorderPart.FooterBottom)));
|
||||
result.Add(new Segment(border.GetPart(BorderPart.FooterBottom, columnWidth)));
|
||||
result.Add(new Segment(border.GetPart(BorderPart.FooterBottom)));
|
||||
|
||||
if (!lastColumn)
|
||||
{
|
||||
result.Add(new Segment(_border.GetPart(BorderPart.FooterBottomSeparator)));
|
||||
result.Add(new Segment(border.GetPart(BorderPart.FooterBottomSeparator)));
|
||||
}
|
||||
}
|
||||
|
||||
result.Add(new Segment(_border.GetPart(BorderPart.FooterBottomRight)));
|
||||
result.Add(new Segment(border.GetPart(BorderPart.FooterBottomRight)));
|
||||
result.Add(Segment.LineBreak());
|
||||
}
|
||||
}
|
||||
@ -280,18 +309,9 @@ namespace Spectre.Console
|
||||
return result;
|
||||
}
|
||||
|
||||
private static void MakeSameHeight(int cellHeight, List<List<SegmentLine>> cells)
|
||||
private bool ShouldExpand()
|
||||
{
|
||||
foreach (var cell in cells)
|
||||
{
|
||||
if (cell.Count < cellHeight)
|
||||
{
|
||||
while (cell.Count != cellHeight)
|
||||
{
|
||||
cell.Add(new SegmentLine());
|
||||
}
|
||||
}
|
||||
}
|
||||
return Expand || Width != null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
67
src/Spectre.Console/Composition/TableColumn.cs
Normal file
67
src/Spectre.Console/Composition/TableColumn.cs
Normal file
@ -0,0 +1,67 @@
|
||||
using System;
|
||||
|
||||
namespace Spectre.Console
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a table column.
|
||||
/// </summary>
|
||||
public sealed class TableColumn
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the text associated with the column.
|
||||
/// </summary>
|
||||
public Text Text { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the width of the column.
|
||||
/// If <c>null</c>, the column will adapt to it's contents.
|
||||
/// </summary>
|
||||
public int? Width { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the left padding.
|
||||
/// </summary>
|
||||
public int LeftPadding { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the right padding.
|
||||
/// </summary>
|
||||
public int RightPadding { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the ratio to use when calculating column width.
|
||||
/// If <c>null</c>, the column will adapt to it's contents.
|
||||
/// </summary>
|
||||
public int? Ratio { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether wrapping of
|
||||
/// text within the column should be prevented.
|
||||
/// </summary>
|
||||
public bool NoWrap { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="TableColumn"/> class.
|
||||
/// </summary>
|
||||
/// <param name="text">The table column text.</param>
|
||||
public TableColumn(string text)
|
||||
{
|
||||
Text = Text.New(text ?? throw new ArgumentNullException(nameof(text)));
|
||||
Width = null;
|
||||
LeftPadding = 1;
|
||||
RightPadding = 1;
|
||||
Ratio = null;
|
||||
NoWrap = false;
|
||||
}
|
||||
|
||||
internal int GetPadding()
|
||||
{
|
||||
return LeftPadding + RightPadding;
|
||||
}
|
||||
|
||||
internal bool IsFlexible()
|
||||
{
|
||||
return Width == null;
|
||||
}
|
||||
}
|
||||
}
|
@ -98,19 +98,22 @@ namespace Spectre.Console
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public int Measure(Encoding encoding, int maxWidth)
|
||||
Measurement IRenderable.Measure(Encoding encoding, int maxWidth)
|
||||
{
|
||||
var lines = Segment.SplitLines(Render(encoding, maxWidth));
|
||||
var lines = Segment.SplitLines(((IRenderable)this).Render(encoding, maxWidth));
|
||||
if (lines.Count == 0)
|
||||
{
|
||||
return 0;
|
||||
return new Measurement(0, maxWidth);
|
||||
}
|
||||
|
||||
return lines.Max(x => x.Length);
|
||||
var max = lines.Max(line => line.Length);
|
||||
var min = lines.SelectMany(line => line.Select(segment => segment.Text.Length)).Max();
|
||||
|
||||
return new Measurement(min, max);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IEnumerable<Segment> Render(Encoding encoding, int width)
|
||||
IEnumerable<Segment> IRenderable.Render(Encoding encoding, int width)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(_text))
|
||||
{
|
||||
|
@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using Spectre.Console.Internal;
|
||||
|
||||
namespace Spectre.Console
|
||||
{
|
||||
@ -19,7 +20,12 @@ namespace Spectre.Console
|
||||
throw new ArgumentNullException(nameof(console));
|
||||
}
|
||||
|
||||
console.Write(Environment.NewLine);
|
||||
using (console.PushColor(Color.Default, true))
|
||||
using (console.PushColor(Color.Default, false))
|
||||
using (console.PushDecoration(Decoration.None))
|
||||
{
|
||||
console.Write(Environment.NewLine);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -6,6 +6,19 @@ namespace Spectre.Console.Internal
|
||||
{
|
||||
internal static class EnumerableExtensions
|
||||
{
|
||||
public static void ForEach<T>(this IEnumerable<T> source, Action<T> action)
|
||||
{
|
||||
foreach (var item in source)
|
||||
{
|
||||
action(item);
|
||||
}
|
||||
}
|
||||
|
||||
public static bool AnyTrue(this IEnumerable<bool> source)
|
||||
{
|
||||
return source.Any(b => b);
|
||||
}
|
||||
|
||||
public static IEnumerable<(int Index, bool First, bool Last, T Item)> Enumerate<T>(this IEnumerable<T> source)
|
||||
{
|
||||
if (source is null)
|
||||
@ -40,5 +53,18 @@ namespace Spectre.Console.Internal
|
||||
{
|
||||
return source.Select((value, index) => func(value, index));
|
||||
}
|
||||
|
||||
public static IEnumerable<(TFirst First, TSecond Second)> Zip<TFirst, TSecond>(
|
||||
this IEnumerable<TFirst> source, IEnumerable<TSecond> first)
|
||||
{
|
||||
return source.Zip(first, (first, second) => (first, second));
|
||||
}
|
||||
|
||||
public static IEnumerable<(TFirst First, TSecond Second, TThird Third)> Zip<TFirst, TSecond, TThird>(
|
||||
this IEnumerable<TFirst> first, IEnumerable<TSecond> second, IEnumerable<TThird> third)
|
||||
{
|
||||
return first.Zip(second, (a, b) => (a, b))
|
||||
.Zip(third, (a, b) => (a.a, a.b, b));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
75
src/Spectre.Console/Internal/Utilities/Ratio.cs
Normal file
75
src/Spectre.Console/Internal/Utilities/Ratio.cs
Normal file
@ -0,0 +1,75 @@
|
||||
// Ported from Rich by Will McGugan, licensed under MIT.
|
||||
// https://github.com/willmcgugan/rich/blob/527475837ebbfc427530b3ee0d4d0741d2d0fc6d/rich/_ratio.py
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
|
||||
namespace Spectre.Console.Internal
|
||||
{
|
||||
internal static class Ratio
|
||||
{
|
||||
public static List<int> Reduce(int total, List<int> ratios, List<int> maximums, List<int> values)
|
||||
{
|
||||
ratios = ratios.Zip(maximums, (a, b) => (ratio: a, max: b)).Select(a => a.max > 0 ? a.ratio : 0).ToList();
|
||||
var totalRatio = ratios.Sum();
|
||||
if (totalRatio == 0)
|
||||
{
|
||||
return values;
|
||||
}
|
||||
|
||||
var totalRemaining = total;
|
||||
var result = new List<int>();
|
||||
|
||||
foreach (var (ratio, maximum, value) in ratios.Zip(maximums, values))
|
||||
{
|
||||
if (ratio > 0 && totalRatio > 0)
|
||||
{
|
||||
var distributed = (int)Math.Min(maximum, Math.Round(ratio * totalRemaining / (double)totalRatio));
|
||||
result.Add(value - distributed);
|
||||
totalRemaining -= distributed;
|
||||
totalRatio -= ratio;
|
||||
}
|
||||
else
|
||||
{
|
||||
result.Add(value);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public static List<int> Distribute(int total, List<int> ratios, List<int> minimums = null)
|
||||
{
|
||||
if (minimums != null)
|
||||
{
|
||||
ratios = ratios.Zip(minimums, (a, b) => (ratio: a, min: b)).Select(a => a.min > 0 ? a.ratio : 0).ToList();
|
||||
}
|
||||
|
||||
var totalRatio = ratios.Sum();
|
||||
Debug.Assert(totalRatio > 0, "Sum or ratios must be > 0");
|
||||
|
||||
var totalRemaining = total;
|
||||
var distributedTotal = new List<int>();
|
||||
|
||||
if (minimums == null)
|
||||
{
|
||||
minimums = ratios.Select(_ => 0).ToList();
|
||||
}
|
||||
|
||||
foreach (var (ratio, minimum) in ratios.Zip(minimums, (a, b) => (a, b)))
|
||||
{
|
||||
var distributed = (totalRatio > 0)
|
||||
? Math.Max(minimum, (int)Math.Ceiling(ratio * totalRemaining / (double)totalRatio))
|
||||
: totalRemaining;
|
||||
|
||||
distributedTotal.Add(distributed);
|
||||
totalRatio -= ratio;
|
||||
totalRemaining -= distributed;
|
||||
}
|
||||
|
||||
return distributedTotal;
|
||||
}
|
||||
}
|
||||
}
|
@ -22,6 +22,9 @@
|
||||
<Compile Update="ConsoleExtensions.*.cs">
|
||||
<DependentUpon>ConsoleExtensions.cs</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Update="**/Table.*.cs">
|
||||
<DependentUpon>**/Table.cs</DependentUpon>
|
||||
</Compile>
|
||||
|
||||
</ItemGroup>
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user