diff --git a/src/Sample/Program.cs b/src/Sample/Program.cs index f129d13..e9de2cd 100644 --- a/src/Sample/Program.cs +++ b/src/Sample/Program.cs @@ -67,31 +67,38 @@ namespace Sample Text.New( "[underline]I[/] heard [underline on blue]you[/] like ๐Ÿ“ฆ\n\n\n\n" + "So I put a ๐Ÿ“ฆ in a ๐Ÿ“ฆ\nin a ๐Ÿ“ฆ in a ๐Ÿ“ฆ\n\n" + - "๐Ÿ˜…", foreground: Color.White), - content: Justify.Center, - border: BorderKind.Rounded))), - border: BorderKind.Ascii)); + "๐Ÿ˜…", foreground: Color.White)) + { Alignment = Justify.Center, Border = BorderKind.Rounded }))) + { + Border = BorderKind.Ascii + }); // Reset colors AnsiConsole.ResetColors(); // Left adjusted panel with text AnsiConsole.Render(new Panel( - Text.New("Left adjusted\nLeft", - foreground: Color.White), - fit: true)); + Text.New("Left adjusted\nLeft")) + { + Expand = true, + Alignment = Justify.Left, + }); // Centered panel with text AnsiConsole.Render(new Panel( - Text.New("Centered\nCenter", - foreground: Color.White), - fit: true, content: Justify.Center)); + Text.New("Centered\nCenter")) + { + Expand = true, + Alignment = Justify.Center, + }); // Right adjusted panel with text AnsiConsole.Render(new Panel( - Text.New("Right adjusted\nRight", - foreground: Color.White), - fit: true, content: Justify.Right)); + Text.New("Right adjusted\nRight")) + { + Expand = true, + Alignment = Justify.Right, + }); // A normal, square table var table = new Table(); @@ -145,7 +152,7 @@ namespace Sample AnsiConsole.Render(table); // Render a table in some panels. - AnsiConsole.Render(new Panel(new Panel(table, border: BorderKind.Ascii))); + AnsiConsole.Render(new Panel(new Panel(table) { Border = BorderKind.Ascii }) { Padding = new Padding(0, 0) }); // Draw another table table = new Table { Expand = false }; diff --git a/src/Spectre.Console.Tests/Unit/Composition/GridTests.cs b/src/Spectre.Console.Tests/Unit/Composition/GridTests.cs index de80d31..cc40a82 100644 --- a/src/Spectre.Console.Tests/Unit/Composition/GridTests.cs +++ b/src/Spectre.Console.Tests/Unit/Composition/GridTests.cs @@ -55,7 +55,7 @@ namespace Spectre.Console.Tests.Unit.Composition } [Fact] - public void Should_Render_Grid_With_No_Border_Correctly() + public void Should_Render_Grid_Correctly() { // Given var console = new PlainConsole(width: 80); @@ -75,13 +75,58 @@ namespace Spectre.Console.Tests.Unit.Composition console.Lines[1].ShouldBe("Grault Garply Fred "); } + [Fact] + public void Should_Render_Grid_Column_Alignment_Correctly() + { + // Given + var console = new PlainConsole(width: 80); + var grid = new Grid(); + grid.AddColumn(new GridColumn { Alignment = Justify.Right }); + grid.AddColumn(new GridColumn { Alignment = Justify.Center }); + grid.AddColumn(new GridColumn { Alignment = Justify.Left }); + grid.AddRow("Foo", "Bar", "Baz"); + grid.AddRow("Qux", "Corgi", "Waldo"); + grid.AddRow("Grault", "Garply", "Fred"); + + // When + console.Render(grid); + + // Then + console.Lines.Count.ShouldBe(3); + console.Lines[0].ShouldBe(" Foo Bar Baz "); + console.Lines[1].ShouldBe(" Qux Corgi Waldo"); + console.Lines[2].ShouldBe("Grault Garply Fred "); + } + + [Fact] + public void Should_Render_Grid_Column_Padding_Correctly() + { + // Given + var console = new PlainConsole(width: 80); + var grid = new Grid(); + grid.AddColumn(new GridColumn { Padding = new Padding(3, 0) }); + grid.AddColumns(2); + grid.AddRow("Foo", "Bar", "Baz"); + grid.AddRow("Qux", "Corgi", "Waldo"); + grid.AddRow("Grault", "Garply", "Fred"); + + // When + console.Render(grid); + + // Then + console.Lines.Count.ShouldBe(3); + console.Lines[0].ShouldBe(" Foo Bar Baz "); + console.Lines[1].ShouldBe(" Qux Corgi Waldo"); + console.Lines[2].ShouldBe(" Grault Garply Fred "); + } + [Fact] public void Should_Render_Grid() { var console = new PlainConsole(width: 80); var grid = new Grid(); grid.AddColumn(new GridColumn { NoWrap = true }); - grid.AddColumn(); + grid.AddColumn(new GridColumn { Padding = new Padding(2, 0) }); 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[/]."); @@ -91,10 +136,10 @@ namespace Spectre.Console.Tests.Unit.Composition // 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."); } } } diff --git a/src/Spectre.Console.Tests/Unit/Composition/PanelTests.cs b/src/Spectre.Console.Tests/Unit/Composition/PanelTests.cs index a84a9b1..6f32910 100644 --- a/src/Spectre.Console.Tests/Unit/Composition/PanelTests.cs +++ b/src/Spectre.Console.Tests/Unit/Composition/PanelTests.cs @@ -21,6 +21,25 @@ namespace Spectre.Console.Tests.Unit console.Lines[2].ShouldBe("โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜"); } + [Fact] + public void Should_Render_Panel_With_Padding() + { + // Given + var console = new PlainConsole(width: 80); + + // When + console.Render(new Panel(Text.New("Hello World")) + { + Padding = new Padding(3, 5), + }); + + // Then + console.Lines.Count.ShouldBe(3); + console.Lines[0].ShouldBe("โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”"); + console.Lines[1].ShouldBe("โ”‚ Hello World โ”‚"); + console.Lines[2].ShouldBe("โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜"); + } + [Fact] public void Should_Render_Panel_With_Unicode_Correctly() { @@ -62,8 +81,7 @@ namespace Spectre.Console.Tests.Unit // Given var console = new PlainConsole(width: 80); var text = new Panel( - Text.New("I heard [underline on blue]you[/] like ๐Ÿ“ฆ\n\n\n\nSo I put a ๐Ÿ“ฆ in a ๐Ÿ“ฆ"), - content: Justify.Center); + Text.New("I heard [underline on blue]you[/] like ๐Ÿ“ฆ\n\n\n\nSo I put a ๐Ÿ“ฆ in a ๐Ÿ“ฆ")); // When console.Render(text); @@ -71,7 +89,7 @@ namespace Spectre.Console.Tests.Unit // Then console.Lines.Count.ShouldBe(7); console.Lines[0].ShouldBe("โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”"); - console.Lines[1].ShouldBe("โ”‚ I heard you like ๐Ÿ“ฆ โ”‚"); + console.Lines[1].ShouldBe("โ”‚ I heard you like ๐Ÿ“ฆ โ”‚"); console.Lines[2].ShouldBe("โ”‚ โ”‚"); console.Lines[3].ShouldBe("โ”‚ โ”‚"); console.Lines[4].ShouldBe("โ”‚ โ”‚"); @@ -80,19 +98,23 @@ namespace Spectre.Console.Tests.Unit } [Fact] - public void Should_Fit_Panel_To_Parent_If_Enabled() + public void Should_Expand_Panel_If_Enabled() { // Given - var console = new PlainConsole(width: 25); + var console = new PlainConsole(width: 80); // When - console.Render(new Panel(Text.New("Hello World"), fit: true)); + console.Render(new Panel(Text.New("Hello World")) + { + Expand = true, + }); // Then console.Lines.Count.ShouldBe(3); - console.Lines[0].ShouldBe("โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”"); - console.Lines[1].ShouldBe("โ”‚ Hello World โ”‚"); - console.Lines[2].ShouldBe("โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜"); + console.Lines[0].Length.ShouldBe(80); + console.Lines[0].ShouldBe("โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”"); + console.Lines[1].ShouldBe("โ”‚ Hello World โ”‚"); + console.Lines[2].ShouldBe("โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜"); } [Fact] @@ -102,7 +124,12 @@ namespace Spectre.Console.Tests.Unit var console = new PlainConsole(width: 25); // When - console.Render(new Panel(Text.New("Hello World"), fit: true, content: Justify.Right)); + console.Render( + new Panel( + Text.New("Hello World").WithAlignment(Justify.Right)) + { + Expand = true, + }); // Then console.Lines.Count.ShouldBe(3); @@ -118,7 +145,12 @@ namespace Spectre.Console.Tests.Unit var console = new PlainConsole(width: 25); // When - console.Render(new Panel(Text.New("Hello World"), fit: true, content: Justify.Center)); + console.Render( + new Panel( + Text.New("Hello World").WithAlignment(Justify.Center)) + { + Expand = true, + }); // Then console.Lines.Count.ShouldBe(3); diff --git a/src/Spectre.Console.Tests/Unit/Composition/TableTests.cs b/src/Spectre.Console.Tests/Unit/Composition/TableTests.cs index 4755e41..ee35dcf 100644 --- a/src/Spectre.Console.Tests/Unit/Composition/TableTests.cs +++ b/src/Spectre.Console.Tests/Unit/Composition/TableTests.cs @@ -32,7 +32,7 @@ namespace Spectre.Console.Tests.Unit.Composition var table = new Table(); // When - var result = Record.Exception(() => table.AddColumns(null)); + var result = Record.Exception(() => table.AddColumns((string[])null)); // Then result.ShouldBeOfType() @@ -88,31 +88,6 @@ 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() { @@ -136,6 +111,64 @@ namespace Spectre.Console.Tests.Unit.Composition console.Lines[5].ShouldBe("โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜"); } + [Fact] + public void Should_Render_Table_Nested_In_Panels_Correctly() + { + // A simple table + var console = new PlainConsole(width: 80); + var table = new Table() { Border = BorderKind.Rounded }; + table.AddColumn("Foo"); + table.AddColumn("Bar"); + table.AddColumn(new TableColumn("Baz") { Alignment = Justify.Right }); + table.AddRow("Qux\nQuuuuuux", "[blue]Corgi[/]", "Waldo"); + table.AddRow("Grault", "Garply", "Fred"); + + // Render a table in some panels. + console.Render(new Panel(new Panel(table) + { + Border = BorderKind.Ascii, + })); + + // Then + console.Lines.Count.ShouldBe(11); + console.Lines[00].ShouldBe("โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”"); + console.Lines[01].ShouldBe("โ”‚ +-------------------------------+ โ”‚"); + console.Lines[02].ShouldBe("โ”‚ | โ•ญโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฎ | โ”‚"); + console.Lines[03].ShouldBe("โ”‚ | โ”‚ Foo โ”‚ Bar โ”‚ Baz โ”‚ | โ”‚"); + console.Lines[04].ShouldBe("โ”‚ | โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค | โ”‚"); + console.Lines[05].ShouldBe("โ”‚ | โ”‚ Qux โ”‚ Corgi โ”‚ Waldo โ”‚ | โ”‚"); + console.Lines[06].ShouldBe("โ”‚ | โ”‚ Quuuuuux โ”‚ โ”‚ โ”‚ | โ”‚"); + console.Lines[07].ShouldBe("โ”‚ | โ”‚ Grault โ”‚ Garply โ”‚ Fred โ”‚ | โ”‚"); + console.Lines[08].ShouldBe("โ”‚ | โ•ฐโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฏ | โ”‚"); + console.Lines[09].ShouldBe("โ”‚ +-------------------------------+ โ”‚"); + console.Lines[10].ShouldBe("โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜"); + } + + [Fact] + public void Should_Render_Table_With_Column_Justification_Correctly() + { + // Given + var console = new PlainConsole(width: 80); + var table = new Table(); + table.AddColumn(new TableColumn("Foo") { Alignment = Justify.Left }); + table.AddColumn(new TableColumn("Bar") { Alignment = Justify.Right }); + table.AddColumn(new TableColumn("Baz") { Alignment = Justify.Center }); + table.AddRow("Qux", "Corgi", "Waldo"); + table.AddRow("Grault", "Garply", "Lorem ipsum dolor sit amet"); + + // When + console.Render(table); + + // Then + console.Lines.Count.ShouldBe(6); + console.Lines[0].ShouldBe("โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”"); + console.Lines[1].ShouldBe("โ”‚ Foo โ”‚ Bar โ”‚ Baz โ”‚"); + console.Lines[2].ShouldBe("โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค"); + console.Lines[3].ShouldBe("โ”‚ Qux โ”‚ Corgi โ”‚ Waldo โ”‚"); + console.Lines[4].ShouldBe("โ”‚ Grault โ”‚ Garply โ”‚ Lorem ipsum dolor sit amet โ”‚"); + console.Lines[5].ShouldBe("โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜"); + } + [Fact] public void Should_Expand_Table_To_Available_Space_If_Specified() { @@ -249,5 +282,30 @@ namespace Spectre.Console.Tests.Unit.Composition console.Lines[5].ShouldBe("โ”‚ Grault โ”‚ Garply โ”‚ Fred โ”‚"); console.Lines[6].ShouldBe("โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜"); } + + [Fact] + public void Should_Render_Table_With_Cell_Padding_Correctly() + { + // Given + var console = new PlainConsole(width: 80); + var table = new Table(); + table.AddColumns("Foo", "Bar"); + table.AddColumn(new TableColumn("Baz") { Padding = new Padding(3, 2) }); + table.AddRow("Qux\nQuuux", "Corgi", "Waldo"); + table.AddRow("Grault", "Garply", "Fred"); + + // When + console.Render(table); + + // Then + console.Lines.Count.ShouldBe(7); + 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("โ”‚ Quuux โ”‚ โ”‚ โ”‚"); + console.Lines[5].ShouldBe("โ”‚ Grault โ”‚ Garply โ”‚ Fred โ”‚"); + console.Lines[6].ShouldBe("โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜"); + } } } diff --git a/src/Spectre.Console/Composition/Grid.cs b/src/Spectre.Console/Composition/Grid.cs index 99c8f0a..5a2c95c 100644 --- a/src/Spectre.Console/Composition/Grid.cs +++ b/src/Spectre.Console/Composition/Grid.cs @@ -20,6 +20,7 @@ namespace Spectre.Console { Border = BorderKind.None, ShowHeaders = false, + IsGrid = true, }; } @@ -40,7 +41,7 @@ namespace Spectre.Console /// public void AddColumn() { - _table.AddColumn(string.Empty); + AddColumn(new GridColumn()); } /// @@ -58,11 +59,40 @@ namespace Spectre.Console { Width = column.Width, NoWrap = column.NoWrap, - LeftPadding = 0, - RightPadding = 1, + Padding = column.Padding, + Alignment = column.Alignment, }); } + /// + /// Adds a column to the grid. + /// + /// The number of columns to add. + public void AddColumns(int count) + { + for (var index = 0; index < count; index++) + { + AddColumn(new GridColumn()); + } + } + + /// + /// Adds a column to the grid. + /// + /// The columns to add. + public void AddColumns(params GridColumn[] columns) + { + if (columns is null) + { + throw new ArgumentNullException(nameof(columns)); + } + + foreach (var column in columns) + { + AddColumn(column); + } + } + /// /// Adds a new row to the grid. /// diff --git a/src/Spectre.Console/Composition/GridColumn.cs b/src/Spectre.Console/Composition/GridColumn.cs index 0783c24..a67f31f 100644 --- a/src/Spectre.Console/Composition/GridColumn.cs +++ b/src/Spectre.Console/Composition/GridColumn.cs @@ -1,4 +1,4 @@ -๏ปฟnamespace Spectre.Console +namespace Spectre.Console { /// /// Represents a grid column. @@ -9,12 +9,22 @@ /// Gets or sets the width of the column. /// If null, the column will adapt to it's contents. /// - public int? Width { get; set; } + public int? Width { get; set; } = null; /// /// Gets or sets a value indicating whether wrapping of /// text within the column should be prevented. /// - public bool NoWrap { get; set; } + public bool NoWrap { get; set; } = false; + + /// + /// Gets or sets the padding of the column. + /// + public Padding Padding { get; set; } = new Padding(0, 1); + + /// + /// Gets or sets the alignment of the column. + /// + public Justify? Alignment { get; set; } = null; } } diff --git a/src/Spectre.Console/Justify.cs b/src/Spectre.Console/Composition/Justify.cs similarity index 100% rename from src/Spectre.Console/Justify.cs rename to src/Spectre.Console/Composition/Justify.cs diff --git a/src/Spectre.Console/Composition/Padding.cs b/src/Spectre.Console/Composition/Padding.cs new file mode 100644 index 0000000..21d710c --- /dev/null +++ b/src/Spectre.Console/Composition/Padding.cs @@ -0,0 +1,86 @@ +using System; + +namespace Spectre.Console +{ + /// + /// Represents a measurement. + /// + public struct Padding : IEquatable + { + /// + /// Gets the left padding. + /// + public int Left { get; } + + /// + /// Gets the right padding. + /// + public int Right { get; } + + /// + /// Initializes a new instance of the struct. + /// + /// The left padding. + /// The right padding. + public Padding(int left, int right) + { + Left = left; + Right = right; + } + + /// + public override bool Equals(object obj) + { + return obj is Padding padding && Equals(padding); + } + + /// + public override int GetHashCode() + { + unchecked + { + var hash = (int)2166136261; + hash = (hash * 16777619) ^ Left.GetHashCode(); + hash = (hash * 16777619) ^ Right.GetHashCode(); + return hash; + } + } + + /// + public bool Equals(Padding other) + { + return Left == other.Left && Right == other.Right; + } + + /// + /// Checks if two instances are equal. + /// + /// The first instance to compare. + /// The second instance to compare. + /// true if the two instances are equal, otherwise false. + public static bool operator ==(Padding left, Padding right) + { + return left.Equals(right); + } + + /// + /// Checks if two instances are not equal. + /// + /// The first instance to compare. + /// The second instance to compare. + /// true if the two instances are not equal, otherwise false. + public static bool operator !=(Padding left, Padding right) + { + return !(left == right); + } + + /// + /// Gets the horizontal padding. + /// + /// The horizontal padding. + public int GetHorizontalPadding() + { + return Left + Right; + } + } +} diff --git a/src/Spectre.Console/Composition/Panel.cs b/src/Spectre.Console/Composition/Panel.cs index 2d44885..94168f5 100644 --- a/src/Spectre.Console/Composition/Panel.cs +++ b/src/Spectre.Console/Composition/Panel.cs @@ -10,9 +10,6 @@ namespace Spectre.Console public sealed class Panel : IRenderable { private readonly IRenderable _child; - private readonly bool _fit; - private readonly Justify _content; - private readonly BorderKind _border; /// /// Gets or sets a value indicating whether or not to use @@ -21,113 +18,109 @@ namespace Spectre.Console /// public bool SafeBorder { get; set; } = true; + /// + /// Gets or sets the kind of border to use. + /// + public BorderKind Border { get; set; } = BorderKind.Square; + + /// + /// Gets or sets the alignment of the panel contents. + /// + public Justify? Alignment { get; set; } = null; + + /// + /// Gets or sets a value indicating whether or not the panel should + /// fit the available space. If false, the panel width will be + /// auto calculated. Defaults to false. + /// + public bool Expand { get; set; } = false; + + /// + /// Gets or sets the padding. + /// + public Padding Padding { get; set; } = new Padding(1, 1); + /// /// Initializes a new instance of the class. /// - /// The child. - /// Whether or not to fit the panel to it's parent. - /// The justification of the panel content. - /// The border to use. - public Panel( - IRenderable child, - bool fit = false, - Justify content = Justify.Left, - BorderKind border = BorderKind.Square) + /// The panel content. + public Panel(IRenderable content) { - _child = child ?? throw new System.ArgumentNullException(nameof(child)); - _fit = fit; - _content = content; - _border = border; + _child = content ?? throw new System.ArgumentNullException(nameof(content)); } /// Measurement IRenderable.Measure(RenderContext context, int maxWidth) { var childWidth = _child.Measure(context, maxWidth); - return new Measurement(childWidth.Min + 4, childWidth.Max + 4); + return new Measurement(childWidth.Min + 2 + Padding.GetHorizontalPadding(), childWidth.Max + 2 + Padding.GetHorizontalPadding()); } /// IEnumerable IRenderable.Render(RenderContext context, int width) { - var border = Border.GetBorder(_border, (context.LegacyConsole || !context.Unicode) && SafeBorder); + var border = Composition.Border.GetBorder(Border, (context.LegacyConsole || !context.Unicode) && SafeBorder); - var childWidth = width - 4; - if (!_fit) + var edgeWidth = 2; + var paddingWidth = Padding.GetHorizontalPadding(); + var childWidth = width - edgeWidth - paddingWidth; + + if (!Expand) { - var measurement = _child.Measure(context, width - 2); + var measurement = _child.Measure(context, width - edgeWidth - paddingWidth); childWidth = measurement.Max; } - var result = new List(); - var panelWidth = childWidth + 2; + var panelWidth = childWidth + paddingWidth; - result.Add(new Segment(border.GetPart(BorderPart.HeaderTopLeft))); - result.Add(new Segment(border.GetPart(BorderPart.HeaderTop, panelWidth))); - result.Add(new Segment(border.GetPart(BorderPart.HeaderTopRight))); - result.Add(new Segment("\n")); + // Panel top + var result = new List + { + new Segment(border.GetPart(BorderPart.HeaderTopLeft)), + new Segment(border.GetPart(BorderPart.HeaderTop, panelWidth)), + new Segment(border.GetPart(BorderPart.HeaderTopRight)), + new Segment("\n"), + }; // Render the child. - var childSegments = _child.Render(context, childWidth); + var childContext = context.WithJustification(Alignment); + var childSegments = _child.Render(childContext, childWidth); // Split the child segments into lines. - var lines = Segment.SplitLines(childSegments, childWidth); - foreach (var line in lines) + foreach (var line in Segment.SplitLines(childSegments, panelWidth)) { result.Add(new Segment(border.GetPart(BorderPart.CellLeft))); - result.Add(new Segment(" ")); // Left padding + + // Left padding + if (Padding.Left > 0) + { + result.Add(new Segment(new string(' ', Padding.Left))); + } var content = new List(); + content.AddRange(line); + // Do we need to pad the panel? var length = line.Sum(segment => segment.CellLength(context.Encoding)); if (length < childWidth) { - // Justify right side - if (_content == Justify.Right) - { - var diff = childWidth - length; - content.Add(new Segment(new string(' ', diff))); - } - else if (_content == Justify.Center) - { - var diff = (childWidth - length) / 2; - content.Add(new Segment(new string(' ', diff))); - } - } - - foreach (var segment in line) - { - content.Add(segment.StripLineEndings()); - } - - // Justify left side - if (length < childWidth) - { - if (_content == Justify.Left) - { - var diff = childWidth - length; - content.Add(new Segment(new string(' ', diff))); - } - else if (_content == Justify.Center) - { - var diff = (childWidth - length) / 2; - content.Add(new Segment(new string(' ', diff))); - - var remainder = (childWidth - length) % 2; - if (remainder != 0) - { - content.Add(new Segment(new string(' ', remainder))); - } - } + var diff = childWidth - length; + content.Add(new Segment(new string(' ', diff))); } result.AddRange(content); - result.Add(new Segment(" ")); + // Right padding + if (Padding.Right > 0) + { + result.Add(new Segment(new string(' ', Padding.Right))); + } + result.Add(new Segment(border.GetPart(BorderPart.CellRight))); result.Add(new Segment("\n")); } + // Panel bottom result.Add(new Segment(border.GetPart(BorderPart.FooterBottomLeft))); result.Add(new Segment(border.GetPart(BorderPart.FooterBottom, panelWidth))); result.Add(new Segment(border.GetPart(BorderPart.FooterBottomRight))); diff --git a/src/Spectre.Console/Composition/RenderContext.cs b/src/Spectre.Console/Composition/RenderContext.cs index 97194a1..85fec19 100644 --- a/src/Spectre.Console/Composition/RenderContext.cs +++ b/src/Spectre.Console/Composition/RenderContext.cs @@ -22,16 +22,33 @@ namespace Spectre.Console.Composition /// public bool Unicode { get; } + /// + /// Gets the current justification. + /// + public Justify? Justification { get; } + /// /// Initializes a new instance of the class. /// /// The console's output encoding. /// A value indicating whether or not this a legacy console (i.e. cmd.exe). - public RenderContext(Encoding encoding, bool legacyConsole) + /// The justification to use when rendering. + public RenderContext(Encoding encoding, bool legacyConsole, Justify? justification = null) { Encoding = encoding ?? throw new System.ArgumentNullException(nameof(encoding)); LegacyConsole = legacyConsole; + Justification = justification; Unicode = Encoding == Encoding.UTF8 || Encoding == Encoding.Unicode; } + + /// + /// Creates a new context with the specified justification. + /// + /// The justification. + /// A new instance with the specified justification. + public RenderContext WithJustification(Justify? justification) + { + return new RenderContext(Encoding, LegacyConsole, justification); + } } } diff --git a/src/Spectre.Console/Composition/Table.Calculations.cs b/src/Spectre.Console/Composition/Table.Calculations.cs index c161af2..8316cb3 100644 --- a/src/Spectre.Console/Composition/Table.Calculations.cs +++ b/src/Spectre.Console/Composition/Table.Calculations.cs @@ -21,47 +21,6 @@ namespace Spectre.Console 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(); - foreach (var (range, column) in width_ranges.Zip(_columns, (a, b) => (a, b))) - { - fixedWidths.Add(column.IsFlexible() ? 0 : range.Max); - } - - var flexMinimum = new List(); - 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(); @@ -72,7 +31,7 @@ namespace Spectre.Console if (tableWidth > maxWidth) { var excessWidth = tableWidth - maxWidth; - widths = Ratio.Reduce(excessWidth, widths.Select(w => 1).ToList(), widths, widths); + widths = Ratio.Reduce(excessWidth, widths.Select(_ => 1).ToList(), widths, widths); tableWidth = widths.Sum(); } } @@ -96,26 +55,17 @@ namespace Spectre.Console if (wrappable.AnyTrue()) { - while (totalWidth > 0 && excessWidth > 0) + while (totalWidth != 0 && excessWidth > 0) { - var maxColumn = widths.Zip(wrappable, (first, second) => (width: first, isWrappable: second)) - .Where(x => x.isWrappable) + var maxColumn = widths.Zip(wrappable, (first, second) => (width: first, allowWrap: second)) + .Where(x => x.allowWrap) .Max(x => x.width); - var secondMaxColumn = widths.Zip(wrappable, (width, isWrappable) => isWrappable && width != maxColumn ? maxColumn : 0).Max(); + var secondMaxColumn = widths.Zip(wrappable, (width, allowWrap) => allowWrap && width != maxColumn ? width : 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) + var ratios = widths.Zip(wrappable, (width, allowWrap) => width == maxColumn && allowWrap ? 1 : 0).ToList(); + if (!ratios.Any(x => x != 0) || columnDifference == 0) { break; } @@ -133,10 +83,11 @@ namespace Spectre.Console private (int Min, int Max) MeasureColumn(TableColumn column, RenderContext options, int maxWidth) { + var padding = column.Padding.GetHorizontalPadding(); + // Predetermined width? if (column.Width != null) { - var padding = column.GetPadding(); return (column.Width.Value + padding, column.Width.Value + padding); } @@ -152,7 +103,7 @@ namespace Spectre.Console maxWidths.Add(measure.Max); } - return (minWidths.Count > 0 ? minWidths.Max() : 1, + return (minWidths.Count > 0 ? minWidths.Max() : padding, maxWidths.Count > 0 ? maxWidths.Max() : maxWidth); } @@ -160,7 +111,7 @@ namespace Spectre.Console { var edges = 2; var separators = _columns.Count - 1; - var padding = includePadding ? _columns.Select(x => x.GetPadding()).Sum() : 0; + var padding = includePadding ? _columns.Select(x => x.Padding.GetHorizontalPadding()).Sum() : 0; return separators + edges + padding; } } diff --git a/src/Spectre.Console/Composition/Table.cs b/src/Spectre.Console/Composition/Table.cs index 8e40cd4..d0e3c42 100644 --- a/src/Spectre.Console/Composition/Table.cs +++ b/src/Spectre.Console/Composition/Table.cs @@ -53,6 +53,8 @@ namespace Spectre.Console /// public bool SafeBorder { get; set; } = true; + internal bool IsGrid { get; set; } = false; + /// /// Initializes a new instance of the class. /// @@ -104,6 +106,20 @@ namespace Spectre.Console _columns.AddRange(columns.Select(column => new TableColumn(column))); } + /// + /// Adds multiple columns to the table. + /// + /// The columns to add. + public void AddColumns(params TableColumn[] columns) + { + if (columns is null) + { + throw new ArgumentNullException(nameof(columns)); + } + + _columns.AddRange(columns.Select(column => column)); + } + /// /// Adds a row to the table. /// @@ -175,7 +191,7 @@ namespace Spectre.Console var columnWidths = CalculateColumnWidths(context, maxWidth); // Update the table width. - width = columnWidths.Sum() + GetExtraWidth(includePadding: false); + width = columnWidths.Sum() + GetExtraWidth(includePadding: true); var rows = new List>(); if (ShowHeaders) @@ -195,9 +211,12 @@ namespace Spectre.Console // Get the list of cells for the row and calculate the cell height var cells = new List>(); - foreach (var (rowWidth, cell) in columnWidths.Zip(row, (f, s) => (f, s))) + foreach (var (columnIndex, _, _, (rowWidth, cell)) in columnWidths.Zip(row).Enumerate()) { - var lines = Segment.SplitLines(((IRenderable)cell).Render(context, rowWidth)); + var justification = _columns[columnIndex].Alignment; + var childContext = context.WithJustification(justification); + + var lines = Segment.SplitLines(((IRenderable)cell).Render(childContext, rowWidth)); cellHeight = Math.Max(cellHeight, lines.Count); cells.Add(lines); } @@ -208,9 +227,11 @@ namespace Spectre.Console 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 + var padding = _columns[columnIndex].Padding; + + result.Add(new Segment(border.GetPart(BorderPart.HeaderTop, padding.Left))); // 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, padding.Right))); // Right padding if (!lastColumn) { @@ -237,9 +258,9 @@ namespace Spectre.Console } // Pad column on left side. - if (showBorder) + if (showBorder || IsGrid) { - var leftPadding = _columns[cellIndex].LeftPadding; + var leftPadding = _columns[cellIndex].Padding.Left; if (leftPadding > 0) { result.Add(new Segment(new string(' ', leftPadding))); @@ -257,9 +278,9 @@ namespace Spectre.Console } // Pad column on the right side - if (showBorder || (hideBorder && !lastCell)) + if (showBorder || (hideBorder && !lastCell) || (IsGrid && !lastCell)) { - var rightPadding = _columns[cellIndex].RightPadding; + var rightPadding = _columns[cellIndex].Padding.Right; if (rightPadding > 0) { result.Add(new Segment(new string(' ', rightPadding))); @@ -281,15 +302,17 @@ namespace Spectre.Console result.Add(Segment.LineBreak()); } - // Show bottom of header? + // Show header separator? if (firstRow && showBorder && ShowHeaders) { 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 + var padding = _columns[columnIndex].Padding; + + result.Add(new Segment(border.GetPart(BorderPart.HeaderBottom, padding.Left))); // 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, padding.Right))); // Right padding if (!lastColumn) { @@ -307,9 +330,11 @@ namespace Spectre.Console 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))); + var padding = _columns[columnIndex].Padding; + + result.Add(new Segment(border.GetPart(BorderPart.FooterBottom, padding.Left))); // Left padding 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, padding.Right))); // Right padding if (!lastColumn) { diff --git a/src/Spectre.Console/Composition/TableColumn.cs b/src/Spectre.Console/Composition/TableColumn.cs index 3b19de1..10145b9 100644 --- a/src/Spectre.Console/Composition/TableColumn.cs +++ b/src/Spectre.Console/Composition/TableColumn.cs @@ -19,20 +19,9 @@ namespace Spectre.Console public int? Width { get; set; } /// - /// Gets or sets the left padding. + /// Gets or sets the padding of the column. /// - public int LeftPadding { get; set; } - - /// - /// Gets or sets the right padding. - /// - public int RightPadding { get; set; } - - /// - /// Gets or sets the ratio to use when calculating column width. - /// If null, the column will adapt to it's contents. - /// - public int? Ratio { get; set; } + public Padding Padding { get; set; } /// /// Gets or sets a value indicating whether wrapping of @@ -40,6 +29,11 @@ namespace Spectre.Console /// public bool NoWrap { get; set; } + /// + /// Gets or sets the alignment of the column. + /// + public Justify? Alignment { get; set; } + /// /// Initializes a new instance of the class. /// @@ -48,20 +42,9 @@ namespace Spectre.Console { Text = Text.New(text ?? throw new ArgumentNullException(nameof(text))); Width = null; - LeftPadding = 1; - RightPadding = 1; - Ratio = null; + Padding = new Padding(1, 1); NoWrap = false; - } - - internal int GetPadding() - { - return LeftPadding + RightPadding; - } - - internal bool IsFlexible() - { - return Width == null; + Alignment = null; } } } diff --git a/src/Spectre.Console/Composition/Text.cs b/src/Spectre.Console/Composition/Text.cs index b05e0f6..4978206 100644 --- a/src/Spectre.Console/Composition/Text.cs +++ b/src/Spectre.Console/Composition/Text.cs @@ -18,6 +18,11 @@ namespace Spectre.Console private readonly List _spans; private string _text; + /// + /// Gets or sets the text alignment. + /// + public Justify Alignment { get; set; } = Justify.Left; + private sealed class Span { public int Start { get; } @@ -38,7 +43,7 @@ namespace Spectre.Console /// The text. internal Text(string text) { - _text = text ?? throw new ArgumentNullException(nameof(text)); + _text = text?.NormalizeLineEndings() ?? throw new ArgumentNullException(nameof(text)); _spans = new List(); } @@ -51,12 +56,26 @@ namespace Spectre.Console /// The text decoration. /// A instance. public static Text New( - string text, Color? foreground = null, Color? background = null, Decoration? decoration = null) + string text, + Color? foreground = null, + Color? background = null, + Decoration? decoration = null) { var result = MarkupParser.Parse(text, new Style(foreground, background, decoration)); return result; } + /// + /// Sets the text alignment. + /// + /// The text alignment. + /// The same instance. + public Text WithAlignment(Justify alignment) + { + Alignment = alignment; + return this; + } + /// /// Appends some text with the specified color and decorations. /// @@ -99,14 +118,17 @@ namespace Spectre.Console /// Measurement IRenderable.Measure(RenderContext context, int maxWidth) { - var lines = Segment.SplitLines(((IRenderable)this).Render(context, maxWidth)); - if (lines.Count == 0) + if (string.IsNullOrEmpty(_text)) { - return new Measurement(0, maxWidth); + return new Measurement(1, 1); } - var max = lines.Max(line => line.Length); - var min = lines.SelectMany(line => line.Select(segment => segment.Text.Length)).Max(); + // TODO: Write some kind of tokenizer for this + var min = Segment.SplitLines(((IRenderable)this).Render(context, maxWidth)) + .SelectMany(line => line.Select(segment => segment.Text.Length)) + .Max(); + + var max = _text.SplitLines().Max(x => Cell.GetCellLength(context.Encoding, x)); return new Measurement(min, max); } @@ -122,13 +144,49 @@ namespace Spectre.Console var result = new List(); var segments = SplitLineBreaks(CreateSegments()); + var justification = context.Justification ?? Alignment; + foreach (var (_, _, last, line) in Segment.SplitLines(segments, width).Enumerate()) { + var length = line.Sum(l => l.StripLineEndings().CellLength(context.Encoding)); + + if (length < width) + { + // Justify right side + if (justification == Justify.Right) + { + var diff = width - length; + result.Add(new Segment(new string(' ', diff))); + } + else if (justification == Justify.Center) + { + var diff = (width - length) / 2; + result.Add(new Segment(new string(' ', diff))); + } + } + + // Render the line. foreach (var segment in line) { result.Add(segment.StripLineEndings()); } + // Justify left side + if (length < width) + { + if (justification == Justify.Center) + { + var diff = (width - length) / 2; + result.Add(new Segment(new string(' ', diff))); + + var remainder = (width - length) % 2; + if (remainder != 0) + { + result.Add(new Segment(new string(' ', remainder))); + } + } + } + if (!last) { result.Add(Segment.LineBreak()); diff --git a/src/Spectre.Console/Internal/Extensions/StringExtensions.cs b/src/Spectre.Console/Internal/Extensions/StringExtensions.cs index 58ce136..f02feb3 100644 --- a/src/Spectre.Console/Internal/Extensions/StringExtensions.cs +++ b/src/Spectre.Console/Internal/Extensions/StringExtensions.cs @@ -17,6 +17,11 @@ namespace Spectre.Console.Internal public static string NormalizeLineEndings(this string text, bool native = false) { + if (text == null) + { + return null; + } + var normalized = text?.Replace("\r\n", "\n") ?.Replace("\r", string.Empty); diff --git a/src/Spectre.Console/Internal/Utilities/Ratio.cs b/src/Spectre.Console/Internal/Utilities/Ratio.cs index b4acdbd..dbbf4eb 100644 --- a/src/Spectre.Console/Internal/Utilities/Ratio.cs +++ b/src/Spectre.Console/Internal/Utilities/Ratio.cs @@ -14,7 +14,7 @@ namespace Spectre.Console.Internal { 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) + if (totalRatio <= 0) { return values; } @@ -24,9 +24,9 @@ namespace Spectre.Console.Internal foreach (var (ratio, maximum, value) in ratios.Zip(maximums, values)) { - if (ratio > 0 && totalRatio > 0) + if (ratio != 0 && totalRatio > 0) { - var distributed = (int)Math.Min(maximum, Math.Round(ratio * totalRemaining / (double)totalRatio)); + var distributed = (int)Math.Min(maximum, Math.Round((double)(ratio * totalRemaining / totalRatio))); result.Add(value - distributed); totalRemaining -= distributed; totalRatio -= ratio;