From 8a01b93acad83118d090f4da8a8d9e66ed0f476e Mon Sep 17 00:00:00 2001 From: Patrik Svensson Date: Mon, 24 Aug 2020 23:24:23 +0200 Subject: [PATCH] Don't limit tables and grids to markup text Closes #13 --- examples/Grid/Program.cs | 1 - examples/Panel/Program.cs | 1 - examples/Table/Program.cs | 37 +++++- src/.editorconfig | 3 + src/Spectre.Console.Tests/Unit/BorderTests.cs | 2 +- src/Spectre.Console.Tests/Unit/PanelTests.cs | 6 +- .../Unit/SegmentTests.cs | 2 +- src/Spectre.Console.Tests/Unit/TableTests.cs | 16 ++- src/Spectre.Console.Tests/Unit/TextTests.cs | 118 +++++++++--------- src/Spectre.Console/AnsiConsole.Rendering.cs | 2 +- .../Composition/Widgets/TextExtensions.cs | 27 ---- .../ConsoleExtensions.Rendering.cs | 2 +- .../Rendering/AlignableExtensions.cs | 61 +++++++++ .../{Composition => Rendering}/Border.cs | 2 +- .../{Composition => Rendering}/BorderKind.cs | 0 .../{Composition => Rendering}/BorderPart.cs | 2 +- .../Borders/AsciiBorder.cs | 2 +- .../Borders/NoBorder.cs | 2 +- .../Borders/RoundedBorder.cs | 2 +- .../Borders/SquareBorder.cs | 2 +- .../Widgets => Rendering}/Grid.cs | 16 +-- .../Widgets => Rendering}/GridColumn.cs | 2 +- .../Rendering/GridExtensions.cs | 31 +++++ src/Spectre.Console/Rendering/IAlignable.cs | 13 ++ .../{Composition => Rendering}/IRenderable.cs | 2 +- .../{Composition => Rendering}/Justify.cs | 0 src/Spectre.Console/Rendering/Markup.cs | 43 +++++++ .../{Composition => Rendering}/Measurement.cs | 2 +- .../{Composition => Rendering}/Padding.cs | 0 .../Widgets => Rendering}/Panel.cs | 30 +++-- .../RenderContext.cs | 2 +- src/Spectre.Console/Rendering/Renderable.cs | 41 ++++++ .../{Composition => Rendering}/Segment.cs | 2 +- .../{Composition => Rendering}/SegmentLine.cs | 2 +- .../SegmentLineEnumerator.cs | 2 +- .../SegmentLineIterator.cs | 2 +- .../Table.Calculations.cs | 6 +- .../Widgets => Rendering}/Table.cs | 66 +++------- .../Widgets => Rendering}/TableColumn.cs | 16 ++- .../Rendering/TableExtensions.cs | 78 ++++++++++++ .../Widgets => Rendering}/Text.cs | 19 +-- 41 files changed, 472 insertions(+), 193 deletions(-) delete mode 100644 src/Spectre.Console/Composition/Widgets/TextExtensions.cs create mode 100644 src/Spectre.Console/Rendering/AlignableExtensions.cs rename src/Spectre.Console/{Composition => Rendering}/Border.cs (99%) rename src/Spectre.Console/{Composition => Rendering}/BorderKind.cs (100%) rename src/Spectre.Console/{Composition => Rendering}/BorderPart.cs (98%) rename src/Spectre.Console/{Composition => Rendering}/Borders/AsciiBorder.cs (97%) rename src/Spectre.Console/{Composition => Rendering}/Borders/NoBorder.cs (87%) rename src/Spectre.Console/{Composition => Rendering}/Borders/RoundedBorder.cs (97%) rename src/Spectre.Console/{Composition => Rendering}/Borders/SquareBorder.cs (97%) rename src/Spectre.Console/{Composition/Widgets => Rendering}/Grid.cs (89%) rename src/Spectre.Console/{Composition/Widgets => Rendering}/GridColumn.cs (94%) create mode 100644 src/Spectre.Console/Rendering/GridExtensions.cs create mode 100644 src/Spectre.Console/Rendering/IAlignable.cs rename src/Spectre.Console/{Composition => Rendering}/IRenderable.cs (95%) rename src/Spectre.Console/{Composition => Rendering}/Justify.cs (100%) create mode 100644 src/Spectre.Console/Rendering/Markup.cs rename src/Spectre.Console/{Composition => Rendering}/Measurement.cs (98%) rename src/Spectre.Console/{Composition => Rendering}/Padding.cs (100%) rename src/Spectre.Console/{Composition/Widgets => Rendering}/Panel.cs (79%) rename src/Spectre.Console/{Composition => Rendering}/RenderContext.cs (98%) create mode 100644 src/Spectre.Console/Rendering/Renderable.cs rename src/Spectre.Console/{Composition => Rendering}/Segment.cs (99%) rename src/Spectre.Console/{Composition => Rendering}/SegmentLine.cs (96%) rename src/Spectre.Console/{Composition => Rendering}/SegmentLineEnumerator.cs (93%) rename src/Spectre.Console/{Composition => Rendering}/SegmentLineIterator.cs (98%) rename src/Spectre.Console/{Composition/Widgets => Rendering}/Table.Calculations.cs (96%) rename src/Spectre.Console/{Composition/Widgets => Rendering}/Table.cs (86%) rename src/Spectre.Console/{Composition/Widgets => Rendering}/TableColumn.cs (70%) create mode 100644 src/Spectre.Console/Rendering/TableExtensions.cs rename src/Spectre.Console/{Composition/Widgets => Rendering}/Text.cs (93%) diff --git a/examples/Grid/Program.cs b/examples/Grid/Program.cs index 5cfabec..99a3052 100644 --- a/examples/Grid/Program.cs +++ b/examples/Grid/Program.cs @@ -1,4 +1,3 @@ -using System; using Spectre.Console; namespace GridExample diff --git a/examples/Panel/Program.cs b/examples/Panel/Program.cs index f0ace0d..f219eb3 100644 --- a/examples/Panel/Program.cs +++ b/examples/Panel/Program.cs @@ -1,4 +1,3 @@ -using System; using Spectre.Console; namespace PanelExample diff --git a/examples/Table/Program.cs b/examples/Table/Program.cs index 4595713..3825ea9 100644 --- a/examples/Table/Program.cs +++ b/examples/Table/Program.cs @@ -1,4 +1,3 @@ -using System; using Spectre.Console; namespace TableExample @@ -12,6 +11,42 @@ namespace TableExample // A big table RenderBigTable(); + + // A complex table + RenderComplexTable(); + } + + private static void RenderComplexTable() + { + // Create simple table. + var simple = new Table { Border = BorderKind.Rounded }; + simple.AddColumn(new TableColumn("[u]Foo[/]").Centered()); + simple.AddColumn(new TableColumn("[u]Bar[/]")); + simple.AddColumn(new TableColumn("[u]Baz[/]")); + simple.AddRow("Hello", "[red]World![/]", ""); + simple.AddRow("[blue]Bounjour[/]", "[white]le[/]", "[red]monde![/]"); + simple.AddRow("[blue]Hej[/]", "[yellow]Världen![/]", ""); + + // Create other table. + var second = new Table { Border = BorderKind.Square }; + second.AddColumn(new TableColumn("[u]Foo[/]")); + second.AddColumn(new TableColumn("[u]Bar[/]")); + second.AddColumn(new TableColumn("[u]Baz[/]")); + second.AddRow("Hello", "[red]World![/]", ""); + second.AddRow(simple, new Text("Whaaat"), new Text("Lolz")); + second.AddRow("[blue]Hej[/]", "[yellow]Världen![/]", ""); + + var table = new Table { Border = BorderKind.Rounded }; + table.AddColumn(new TableColumn(new Panel("[u]Foo[/]"))); + table.AddColumn(new TableColumn(new Panel("[u]Bar[/]"))); + table.AddColumn(new TableColumn(new Panel("[u]Baz[/]"))); + + // Add some rows + table.AddRow(new Text("Hello").Centered(), new Markup("[red]World![/] 🌍"), Text.Empty); + table.AddRow(second, new Text("Whaaat"), new Text("Lol")); + table.AddRow(new Markup("[blue]Hej[/]").Centered(), new Markup("[yellow]Världen![/]"), Text.Empty); + + AnsiConsole.Render(table); } private static void RenderSimpleTable() diff --git a/src/.editorconfig b/src/.editorconfig index a156b68..c7304ba 100644 --- a/src/.editorconfig +++ b/src/.editorconfig @@ -81,3 +81,6 @@ dotnet_diagnostic.RCS1079.severity = warning # RCS1057: Add empty line between declarations. dotnet_diagnostic.RCS1057.severity = none + +# IDE0004: Remove Unnecessary Cast +dotnet_diagnostic.IDE0004.severity = warning \ No newline at end of file diff --git a/src/Spectre.Console.Tests/Unit/BorderTests.cs b/src/Spectre.Console.Tests/Unit/BorderTests.cs index de0749d..e70b664 100644 --- a/src/Spectre.Console.Tests/Unit/BorderTests.cs +++ b/src/Spectre.Console.Tests/Unit/BorderTests.cs @@ -1,6 +1,6 @@ using System; using Shouldly; -using Spectre.Console.Composition; +using Spectre.Console.Rendering; using Xunit; namespace Spectre.Console.Tests.Unit diff --git a/src/Spectre.Console.Tests/Unit/PanelTests.cs b/src/Spectre.Console.Tests/Unit/PanelTests.cs index 79b984a..72ce417 100644 --- a/src/Spectre.Console.Tests/Unit/PanelTests.cs +++ b/src/Spectre.Console.Tests/Unit/PanelTests.cs @@ -125,8 +125,7 @@ namespace Spectre.Console.Tests.Unit // When console.Render( - new Panel( - new Text("Hello World").WithAlignment(Justify.Right)) + new Panel(new Text("Hello World").RightAligned()) { Expand = true, }); @@ -146,8 +145,7 @@ namespace Spectre.Console.Tests.Unit // When console.Render( - new Panel( - new Text("Hello World").WithAlignment(Justify.Center)) + new Panel(new Text("Hello World").Centered()) { Expand = true, }); diff --git a/src/Spectre.Console.Tests/Unit/SegmentTests.cs b/src/Spectre.Console.Tests/Unit/SegmentTests.cs index dd49299..ca8141c 100644 --- a/src/Spectre.Console.Tests/Unit/SegmentTests.cs +++ b/src/Spectre.Console.Tests/Unit/SegmentTests.cs @@ -1,5 +1,5 @@ using Shouldly; -using Spectre.Console.Composition; +using Spectre.Console.Rendering; using Xunit; namespace Spectre.Console.Tests.Unit diff --git a/src/Spectre.Console.Tests/Unit/TableTests.cs b/src/Spectre.Console.Tests/Unit/TableTests.cs index 879ad77..54e7d4f 100644 --- a/src/Spectre.Console.Tests/Unit/TableTests.cs +++ b/src/Spectre.Console.Tests/Unit/TableTests.cs @@ -59,7 +59,21 @@ namespace Spectre.Console.Tests.Unit public sealed class TheAddRowMethod { [Fact] - public void Should_Throw_If_Rows_Are_Null() + public void Should_Throw_If_String_Rows_Are_Null() + { + // Given + var table = new Table(); + + // When + var result = Record.Exception(() => table.AddRow((string[])null)); + + // Then + result.ShouldBeOfType() + .ParamName.ShouldBe("columns"); + } + + [Fact] + public void Should_Throw_If_Renderable_Rows_Are_Null() { // Given var table = new Table(); diff --git a/src/Spectre.Console.Tests/Unit/TextTests.cs b/src/Spectre.Console.Tests/Unit/TextTests.cs index da30d54..f9c2e59 100644 --- a/src/Spectre.Console.Tests/Unit/TextTests.cs +++ b/src/Spectre.Console.Tests/Unit/TextTests.cs @@ -1,87 +1,81 @@ using System.Text; using Shouldly; -using Spectre.Console.Composition; +using Spectre.Console.Rendering; using Xunit; namespace Spectre.Console.Tests.Unit { public sealed class TextTests { - public sealed class Measuring + [Fact] + public void Should_Consider_The_Longest_Word_As_Minimum_Width() { - [Fact] - public void Should_Return_The_Longest_Word_As_Minimum_Width() - { - var text = new Text("Foo Bar Baz\nQux\nLol mobile"); + var text = new Text("Foo Bar Baz\nQux\nLol mobile"); - var result = text.Measure(new RenderContext(Encoding.Unicode, false), 80); + var result = ((IRenderable)text).Measure(new RenderContext(Encoding.Unicode, false), 80); - result.Min.ShouldBe(6); - } - - [Fact] - public void Should_Return_The_Longest_Line_As_Maximum_Width() - { - var text = new Text("Foo Bar Baz\nQux\nLol mobile"); - - var result = text.Measure(new RenderContext(Encoding.Unicode, false), 80); - - result.Max.ShouldBe(11); - } + result.Min.ShouldBe(6); } - public sealed class Rendering + [Fact] + public void Should_Consider_The_Longest_Line_As_Maximum_Width() { - [Fact] - public void Should_Render_Unstyled_Text_As_Expected() - { - // Given - var fixture = new PlainConsole(width: 80); - var text = new Text("Hello World"); + var text = new Text("Foo Bar Baz\nQux\nLol mobile"); - // When - fixture.Render(text); + var result = ((IRenderable)text).Measure(new RenderContext(Encoding.Unicode, false), 80); - // Then - fixture.Output - .NormalizeLineEndings() - .ShouldBe("Hello World"); - } + result.Max.ShouldBe(11); + } - [Theory] - [InlineData("Hello\n\nWorld\n\n")] - [InlineData("Hello\r\n\r\nWorld\r\n\r\n")] - public void Should_Write_Line_Breaks(string input) - { - // Given - var fixture = new PlainConsole(width: 5); - var text = new Text(input); + [Fact] + public void Should_Render_Unstyled_Text_As_Expected() + { + // Given + var fixture = new PlainConsole(width: 80); + var text = new Text("Hello World"); - // When - fixture.Render(text); + // When + fixture.Render(text); - // Then - fixture.RawOutput.ShouldBe("Hello\n\nWorld\n\n"); - } + // Then + fixture.Output + .NormalizeLineEndings() + .ShouldBe("Hello World"); + } - [Theory] - [InlineData(5, "Hello World", "Hello\nWorld")] - [InlineData(10, "Hello Sweet Nice World", "Hello \nSweet Nice\nWorld")] - public void Should_Split_Unstyled_Text_To_New_Lines_If_Width_Exceeds_Console_Width( - int width, string input, string expected) - { - // Given - var fixture = new PlainConsole(width); - var text = new Text(input); + [Theory] + [InlineData("Hello\n\nWorld\n\n")] + [InlineData("Hello\r\n\r\nWorld\r\n\r\n")] + public void Should_Write_Line_Breaks(string input) + { + // Given + var fixture = new PlainConsole(width: 5); + var text = new Text(input); - // When - fixture.Render(text); + // When + fixture.Render(text); - // Then - fixture.Output - .NormalizeLineEndings() - .ShouldBe(expected); - } + // Then + fixture.RawOutput.ShouldBe("Hello\n\nWorld\n\n"); + } + + [Theory] + [InlineData(5, "Hello World", "Hello\nWorld")] + [InlineData(10, "Hello Sweet Nice World", "Hello \nSweet Nice\nWorld")] + public void Should_Split_Unstyled_Text_To_New_Lines_If_Width_Exceeds_Console_Width( + int width, string input, string expected) + { + // Given + var fixture = new PlainConsole(width); + var text = new Text(input); + + // When + fixture.Render(text); + + // Then + fixture.Output + .NormalizeLineEndings() + .ShouldBe(expected); } } } diff --git a/src/Spectre.Console/AnsiConsole.Rendering.cs b/src/Spectre.Console/AnsiConsole.Rendering.cs index c49e139..e0e9103 100644 --- a/src/Spectre.Console/AnsiConsole.Rendering.cs +++ b/src/Spectre.Console/AnsiConsole.Rendering.cs @@ -1,4 +1,4 @@ -using Spectre.Console.Composition; +using Spectre.Console.Rendering; namespace Spectre.Console { diff --git a/src/Spectre.Console/Composition/Widgets/TextExtensions.cs b/src/Spectre.Console/Composition/Widgets/TextExtensions.cs deleted file mode 100644 index 405efdb..0000000 --- a/src/Spectre.Console/Composition/Widgets/TextExtensions.cs +++ /dev/null @@ -1,27 +0,0 @@ -using System; - -namespace Spectre.Console -{ - /// - /// Contains extension methods for . - /// - public static class TextExtensions - { - /// - /// Sets the text alignment. - /// - /// The instance. - /// The text alignment. - /// The same instance. - public static Text WithAlignment(this Text text, Justify alignment) - { - if (text is null) - { - throw new ArgumentNullException(nameof(text)); - } - - text.Alignment = alignment; - return text; - } - } -} diff --git a/src/Spectre.Console/ConsoleExtensions.Rendering.cs b/src/Spectre.Console/ConsoleExtensions.Rendering.cs index 1eeafae..c7f364e 100644 --- a/src/Spectre.Console/ConsoleExtensions.Rendering.cs +++ b/src/Spectre.Console/ConsoleExtensions.Rendering.cs @@ -1,6 +1,6 @@ using System; -using Spectre.Console.Composition; using Spectre.Console.Internal; +using Spectre.Console.Rendering; namespace Spectre.Console { diff --git a/src/Spectre.Console/Rendering/AlignableExtensions.cs b/src/Spectre.Console/Rendering/AlignableExtensions.cs new file mode 100644 index 0000000..5193a50 --- /dev/null +++ b/src/Spectre.Console/Rendering/AlignableExtensions.cs @@ -0,0 +1,61 @@ +namespace Spectre.Console +{ + /// + /// Contains extension methods for . + /// + public static class AlignableExtensions + { + /// + /// Sets the alignment for an object. + /// + /// The alignable type. + /// The alignable object. + /// The alignment. + /// The same alignable object. + public static T WithAlignment(this T alignable, Justify alignment) + where T : IAlignable + { + alignable.Alignment = alignment; + return alignable; + } + + /// + /// Sets the object to be left aligned. + /// + /// The alignable type. + /// The alignable object. + /// The same alignable object. + public static T LeftAligned(this T alignable) + where T : IAlignable + { + alignable.Alignment = Justify.Left; + return alignable; + } + + /// + /// Sets the object to be centered. + /// + /// The alignable type. + /// The alignable object. + /// The same alignable object. + public static T Centered(this T alignable) + where T : IAlignable + { + alignable.Alignment = Justify.Center; + return alignable; + } + + /// + /// Sets the object to be right aligned. + /// + /// The alignable type. + /// The alignable object. + /// The same alignable object. + public static T RightAligned(this T alignable) + where T : IAlignable + { + alignable.Alignment = Justify.Right; + return alignable; + } + } +} diff --git a/src/Spectre.Console/Composition/Border.cs b/src/Spectre.Console/Rendering/Border.cs similarity index 99% rename from src/Spectre.Console/Composition/Border.cs rename to src/Spectre.Console/Rendering/Border.cs index 1ae3198..22ead5d 100644 --- a/src/Spectre.Console/Composition/Border.cs +++ b/src/Spectre.Console/Rendering/Border.cs @@ -3,7 +3,7 @@ using System.Collections.Generic; using System.Globalization; using System.Linq; -namespace Spectre.Console.Composition +namespace Spectre.Console.Rendering { /// /// Represents a border used by tables. diff --git a/src/Spectre.Console/Composition/BorderKind.cs b/src/Spectre.Console/Rendering/BorderKind.cs similarity index 100% rename from src/Spectre.Console/Composition/BorderKind.cs rename to src/Spectre.Console/Rendering/BorderKind.cs diff --git a/src/Spectre.Console/Composition/BorderPart.cs b/src/Spectre.Console/Rendering/BorderPart.cs similarity index 98% rename from src/Spectre.Console/Composition/BorderPart.cs rename to src/Spectre.Console/Rendering/BorderPart.cs index decfc1c..0f23ca3 100644 --- a/src/Spectre.Console/Composition/BorderPart.cs +++ b/src/Spectre.Console/Rendering/BorderPart.cs @@ -1,4 +1,4 @@ -namespace Spectre.Console.Composition +namespace Spectre.Console.Rendering { /// /// Represents the different border parts. diff --git a/src/Spectre.Console/Composition/Borders/AsciiBorder.cs b/src/Spectre.Console/Rendering/Borders/AsciiBorder.cs similarity index 97% rename from src/Spectre.Console/Composition/Borders/AsciiBorder.cs rename to src/Spectre.Console/Rendering/Borders/AsciiBorder.cs index d9521fe..0c8575f 100644 --- a/src/Spectre.Console/Composition/Borders/AsciiBorder.cs +++ b/src/Spectre.Console/Rendering/Borders/AsciiBorder.cs @@ -1,6 +1,6 @@ using System; -namespace Spectre.Console.Composition +namespace Spectre.Console.Rendering { /// /// Represents an old school ASCII border. diff --git a/src/Spectre.Console/Composition/Borders/NoBorder.cs b/src/Spectre.Console/Rendering/Borders/NoBorder.cs similarity index 87% rename from src/Spectre.Console/Composition/Borders/NoBorder.cs rename to src/Spectre.Console/Rendering/Borders/NoBorder.cs index 8a2cd16..c1eba8a 100644 --- a/src/Spectre.Console/Composition/Borders/NoBorder.cs +++ b/src/Spectre.Console/Rendering/Borders/NoBorder.cs @@ -1,4 +1,4 @@ -namespace Spectre.Console.Composition +namespace Spectre.Console.Rendering { /// /// Represents an invisible border. diff --git a/src/Spectre.Console/Composition/Borders/RoundedBorder.cs b/src/Spectre.Console/Rendering/Borders/RoundedBorder.cs similarity index 97% rename from src/Spectre.Console/Composition/Borders/RoundedBorder.cs rename to src/Spectre.Console/Rendering/Borders/RoundedBorder.cs index 27b0321..9121ee2 100644 --- a/src/Spectre.Console/Composition/Borders/RoundedBorder.cs +++ b/src/Spectre.Console/Rendering/Borders/RoundedBorder.cs @@ -1,6 +1,6 @@ using System; -namespace Spectre.Console.Composition +namespace Spectre.Console.Rendering { /// /// Represents a rounded border. diff --git a/src/Spectre.Console/Composition/Borders/SquareBorder.cs b/src/Spectre.Console/Rendering/Borders/SquareBorder.cs similarity index 97% rename from src/Spectre.Console/Composition/Borders/SquareBorder.cs rename to src/Spectre.Console/Rendering/Borders/SquareBorder.cs index 4c49c7b..1ba058e 100644 --- a/src/Spectre.Console/Composition/Borders/SquareBorder.cs +++ b/src/Spectre.Console/Rendering/Borders/SquareBorder.cs @@ -1,6 +1,6 @@ using System; -namespace Spectre.Console.Composition +namespace Spectre.Console.Rendering { /// /// Represents a square border. diff --git a/src/Spectre.Console/Composition/Widgets/Grid.cs b/src/Spectre.Console/Rendering/Grid.cs similarity index 89% rename from src/Spectre.Console/Composition/Widgets/Grid.cs rename to src/Spectre.Console/Rendering/Grid.cs index ff1bfaf..37451a3 100644 --- a/src/Spectre.Console/Composition/Widgets/Grid.cs +++ b/src/Spectre.Console/Rendering/Grid.cs @@ -1,15 +1,15 @@ using System; using System.Collections.Generic; using System.Linq; -using Spectre.Console.Composition; using Spectre.Console.Internal; +using Spectre.Console.Rendering; namespace Spectre.Console { /// - /// Represents a grid. + /// A renderable grid. /// - public sealed class Grid : IRenderable + public sealed class Grid : Renderable { private readonly Table _table; @@ -28,13 +28,13 @@ namespace Spectre.Console } /// - public Measurement Measure(RenderContext context, int maxWidth) + protected override Measurement Measure(RenderContext context, int maxWidth) { return ((IRenderable)_table).Measure(context, maxWidth); } /// - public IEnumerable Render(RenderContext context, int width) + protected override IEnumerable Render(RenderContext context, int width) { return ((IRenderable)_table).Render(context, width); } @@ -109,8 +109,8 @@ namespace Spectre.Console /// public void AddEmptyRow() { - var columns = new string[_table.ColumnCount]; - Enumerable.Range(0, _table.ColumnCount).ForEach(index => columns[index] = string.Empty); + var columns = new IRenderable[_table.ColumnCount]; + Enumerable.Range(0, _table.ColumnCount).ForEach(index => columns[index] = Text.Empty); AddRow(columns); } @@ -118,7 +118,7 @@ namespace Spectre.Console /// Adds a new row to the grid. /// /// The columns to add. - public void AddRow(params string[] columns) + public void AddRow(params IRenderable[] columns) { if (columns is null) { diff --git a/src/Spectre.Console/Composition/Widgets/GridColumn.cs b/src/Spectre.Console/Rendering/GridColumn.cs similarity index 94% rename from src/Spectre.Console/Composition/Widgets/GridColumn.cs rename to src/Spectre.Console/Rendering/GridColumn.cs index 706b26e..288c8fc 100644 --- a/src/Spectre.Console/Composition/Widgets/GridColumn.cs +++ b/src/Spectre.Console/Rendering/GridColumn.cs @@ -3,7 +3,7 @@ namespace Spectre.Console /// /// Represents a grid column. /// - public sealed class GridColumn + public sealed class GridColumn : IAlignable { /// /// Gets or sets the width of the column. diff --git a/src/Spectre.Console/Rendering/GridExtensions.cs b/src/Spectre.Console/Rendering/GridExtensions.cs new file mode 100644 index 0000000..7fb8902 --- /dev/null +++ b/src/Spectre.Console/Rendering/GridExtensions.cs @@ -0,0 +1,31 @@ +using System; +using System.Linq; + +namespace Spectre.Console +{ + /// + /// Contains extension methods for . + /// + public static class GridExtensions + { + /// + /// Adds a new row to the grid. + /// + /// The grid to add the row to. + /// The columns to add. + public static void AddRow(this Grid grid, params string[] columns) + { + if (grid is null) + { + throw new ArgumentNullException(nameof(grid)); + } + + if (columns is null) + { + throw new ArgumentNullException(nameof(columns)); + } + + grid.AddRow(columns.Select(column => new Markup(column)).ToArray()); + } + } +} diff --git a/src/Spectre.Console/Rendering/IAlignable.cs b/src/Spectre.Console/Rendering/IAlignable.cs new file mode 100644 index 0000000..91b5ef5 --- /dev/null +++ b/src/Spectre.Console/Rendering/IAlignable.cs @@ -0,0 +1,13 @@ +namespace Spectre.Console +{ + /// + /// Represents something that is alignable. + /// + public interface IAlignable + { + /// + /// Gets or sets the alignment. + /// + Justify? Alignment { get; set; } + } +} diff --git a/src/Spectre.Console/Composition/IRenderable.cs b/src/Spectre.Console/Rendering/IRenderable.cs similarity index 95% rename from src/Spectre.Console/Composition/IRenderable.cs rename to src/Spectre.Console/Rendering/IRenderable.cs index d12b409..7d6ab92 100644 --- a/src/Spectre.Console/Composition/IRenderable.cs +++ b/src/Spectre.Console/Rendering/IRenderable.cs @@ -1,6 +1,6 @@ using System.Collections.Generic; -namespace Spectre.Console.Composition +namespace Spectre.Console.Rendering { /// /// Represents something that can be rendered to the console. diff --git a/src/Spectre.Console/Composition/Justify.cs b/src/Spectre.Console/Rendering/Justify.cs similarity index 100% rename from src/Spectre.Console/Composition/Justify.cs rename to src/Spectre.Console/Rendering/Justify.cs diff --git a/src/Spectre.Console/Rendering/Markup.cs b/src/Spectre.Console/Rendering/Markup.cs new file mode 100644 index 0000000..0837677 --- /dev/null +++ b/src/Spectre.Console/Rendering/Markup.cs @@ -0,0 +1,43 @@ +using System.Collections.Generic; +using Spectre.Console.Internal; +using Spectre.Console.Rendering; + +namespace Spectre.Console +{ + /// + /// A renderable piece of markup text. + /// + public sealed class Markup : Renderable, IAlignable + { + private readonly Text _text; + + /// + public Justify? Alignment + { + get => _text.Alignment; + set => _text.Alignment = value; + } + + /// + /// Initializes a new instance of the class. + /// + /// The markup text. + /// The style of the text. + public Markup(string text, Style? style = null) + { + _text = MarkupParser.Parse(text, style); + } + + /// + protected override Measurement Measure(RenderContext context, int maxWidth) + { + return ((IRenderable)_text).Measure(context, maxWidth); + } + + /// + protected override IEnumerable Render(RenderContext context, int maxWidth) + { + return ((IRenderable)_text).Render(context, maxWidth); + } + } +} diff --git a/src/Spectre.Console/Composition/Measurement.cs b/src/Spectre.Console/Rendering/Measurement.cs similarity index 98% rename from src/Spectre.Console/Composition/Measurement.cs rename to src/Spectre.Console/Rendering/Measurement.cs index 83ec738..365c2ab 100644 --- a/src/Spectre.Console/Composition/Measurement.cs +++ b/src/Spectre.Console/Rendering/Measurement.cs @@ -1,6 +1,6 @@ using System; -namespace Spectre.Console.Composition +namespace Spectre.Console.Rendering { /// /// Represents a measurement. diff --git a/src/Spectre.Console/Composition/Padding.cs b/src/Spectre.Console/Rendering/Padding.cs similarity index 100% rename from src/Spectre.Console/Composition/Padding.cs rename to src/Spectre.Console/Rendering/Padding.cs diff --git a/src/Spectre.Console/Composition/Widgets/Panel.cs b/src/Spectre.Console/Rendering/Panel.cs similarity index 79% rename from src/Spectre.Console/Composition/Widgets/Panel.cs rename to src/Spectre.Console/Rendering/Panel.cs index 175bd2f..f1b8b21 100644 --- a/src/Spectre.Console/Composition/Widgets/Panel.cs +++ b/src/Spectre.Console/Rendering/Panel.cs @@ -1,13 +1,14 @@ using System.Collections.Generic; using System.Linq; -using Spectre.Console.Composition; +using Spectre.Console.Rendering; +using SpectreBorder = Spectre.Console.Rendering.Border; namespace Spectre.Console { /// - /// Represents a panel which contains another renderable item. + /// A renderable panel. /// - public sealed class Panel : IRenderable + public sealed class Panel : Renderable { private const int EdgeWidth = 2; @@ -42,6 +43,15 @@ namespace Spectre.Console /// public Padding Padding { get; set; } = new Padding(1, 1); + /// + /// Initializes a new instance of the class. + /// + /// The panel content. + public Panel(string text) + : this(new Markup(text)) + { + } + /// /// Initializes a new instance of the class. /// @@ -52,23 +62,25 @@ namespace Spectre.Console } /// - Measurement IRenderable.Measure(RenderContext context, int maxWidth) + protected override Measurement Measure(RenderContext context, int maxWidth) { var childWidth = _child.Measure(context, maxWidth); - return new Measurement(childWidth.Min + 2 + Padding.GetHorizontalPadding(), childWidth.Max + 2 + Padding.GetHorizontalPadding()); + return new Measurement( + childWidth.Min + 2 + Padding.GetHorizontalPadding(), + childWidth.Max + 2 + Padding.GetHorizontalPadding()); } /// - IEnumerable IRenderable.Render(RenderContext context, int width) + protected override IEnumerable Render(RenderContext context, int maxWidth) { - var border = Composition.Border.GetBorder(Border, (context.LegacyConsole || !context.Unicode) && SafeBorder); + var border = SpectreBorder.GetBorder(Border, (context.LegacyConsole || !context.Unicode) && SafeBorder); var paddingWidth = Padding.GetHorizontalPadding(); - var childWidth = width - EdgeWidth - paddingWidth; + var childWidth = maxWidth - EdgeWidth - paddingWidth; if (!Expand) { - var measurement = _child.Measure(context, width - EdgeWidth - paddingWidth); + var measurement = _child.Measure(context, maxWidth - EdgeWidth - paddingWidth); childWidth = measurement.Max; } diff --git a/src/Spectre.Console/Composition/RenderContext.cs b/src/Spectre.Console/Rendering/RenderContext.cs similarity index 98% rename from src/Spectre.Console/Composition/RenderContext.cs rename to src/Spectre.Console/Rendering/RenderContext.cs index 85fec19..33b119b 100644 --- a/src/Spectre.Console/Composition/RenderContext.cs +++ b/src/Spectre.Console/Rendering/RenderContext.cs @@ -1,6 +1,6 @@ using System.Text; -namespace Spectre.Console.Composition +namespace Spectre.Console.Rendering { /// /// Represents a render context. diff --git a/src/Spectre.Console/Rendering/Renderable.cs b/src/Spectre.Console/Rendering/Renderable.cs new file mode 100644 index 0000000..296af6f --- /dev/null +++ b/src/Spectre.Console/Rendering/Renderable.cs @@ -0,0 +1,41 @@ +using System.Collections.Generic; + +namespace Spectre.Console.Rendering +{ + /// + /// Base class for a renderable object implementing . + /// + public abstract class Renderable : IRenderable + { + /// + Measurement IRenderable.Measure(RenderContext context, int maxWidth) + { + return Measure(context, maxWidth); + } + + /// + IEnumerable IRenderable.Render(RenderContext context, int maxWidth) + { + return Render(context, maxWidth); + } + + /// + /// Measures the renderable object. + /// + /// The render context. + /// The maximum allowed width. + /// The minimum and maximum width of the object. + protected virtual Measurement Measure(RenderContext context, int maxWidth) + { + return new Measurement(maxWidth, maxWidth); + } + + /// + /// Renders the object. + /// + /// The render context. + /// The maximum allowed width. + /// A collection of segments. + protected abstract IEnumerable Render(RenderContext context, int maxWidth); + } +} diff --git a/src/Spectre.Console/Composition/Segment.cs b/src/Spectre.Console/Rendering/Segment.cs similarity index 99% rename from src/Spectre.Console/Composition/Segment.cs rename to src/Spectre.Console/Rendering/Segment.cs index c9a02e0..889f8f5 100644 --- a/src/Spectre.Console/Composition/Segment.cs +++ b/src/Spectre.Console/Rendering/Segment.cs @@ -5,7 +5,7 @@ using System.Linq; using System.Text; using Spectre.Console.Internal; -namespace Spectre.Console.Composition +namespace Spectre.Console.Rendering { /// /// Represents a renderable segment. diff --git a/src/Spectre.Console/Composition/SegmentLine.cs b/src/Spectre.Console/Rendering/SegmentLine.cs similarity index 96% rename from src/Spectre.Console/Composition/SegmentLine.cs rename to src/Spectre.Console/Rendering/SegmentLine.cs index 99cf6d7..234ceb7 100644 --- a/src/Spectre.Console/Composition/SegmentLine.cs +++ b/src/Spectre.Console/Rendering/SegmentLine.cs @@ -3,7 +3,7 @@ using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Text; -namespace Spectre.Console.Composition +namespace Spectre.Console.Rendering { /// /// Represents a collection of segments. diff --git a/src/Spectre.Console/Composition/SegmentLineEnumerator.cs b/src/Spectre.Console/Rendering/SegmentLineEnumerator.cs similarity index 93% rename from src/Spectre.Console/Composition/SegmentLineEnumerator.cs rename to src/Spectre.Console/Rendering/SegmentLineEnumerator.cs index 350f943..9a64c32 100644 --- a/src/Spectre.Console/Composition/SegmentLineEnumerator.cs +++ b/src/Spectre.Console/Rendering/SegmentLineEnumerator.cs @@ -1,7 +1,7 @@ using System.Collections; using System.Collections.Generic; -namespace Spectre.Console.Composition +namespace Spectre.Console.Rendering { internal sealed class SegmentLineEnumerator : IEnumerable { diff --git a/src/Spectre.Console/Composition/SegmentLineIterator.cs b/src/Spectre.Console/Rendering/SegmentLineIterator.cs similarity index 98% rename from src/Spectre.Console/Composition/SegmentLineIterator.cs rename to src/Spectre.Console/Rendering/SegmentLineIterator.cs index f26c755..eacc3f9 100644 --- a/src/Spectre.Console/Composition/SegmentLineIterator.cs +++ b/src/Spectre.Console/Rendering/SegmentLineIterator.cs @@ -1,7 +1,7 @@ using System.Collections; using System.Collections.Generic; -namespace Spectre.Console.Composition +namespace Spectre.Console.Rendering { internal sealed class SegmentLineIterator : IEnumerator { diff --git a/src/Spectre.Console/Composition/Widgets/Table.Calculations.cs b/src/Spectre.Console/Rendering/Table.Calculations.cs similarity index 96% rename from src/Spectre.Console/Composition/Widgets/Table.Calculations.cs rename to src/Spectre.Console/Rendering/Table.Calculations.cs index ee1bd65..87c1da4 100644 --- a/src/Spectre.Console/Composition/Widgets/Table.Calculations.cs +++ b/src/Spectre.Console/Rendering/Table.Calculations.cs @@ -1,8 +1,8 @@ using System; using System.Collections.Generic; using System.Linq; -using Spectre.Console.Composition; using Spectre.Console.Internal; +using Spectre.Console.Rendering; namespace Spectre.Console { @@ -100,13 +100,13 @@ namespace Spectre.Console var maxWidths = new List(); // Include columns in measurement - var measure = ((IRenderable)column.Text).Measure(options, maxWidth); + var measure = column.Text.Measure(options, maxWidth); minWidths.Add(measure.Min); maxWidths.Add(measure.Max); foreach (var row in rows) { - measure = ((IRenderable)row).Measure(options, maxWidth); + measure = row.Measure(options, maxWidth); minWidths.Add(measure.Min); maxWidths.Add(measure.Max); } diff --git a/src/Spectre.Console/Composition/Widgets/Table.cs b/src/Spectre.Console/Rendering/Table.cs similarity index 86% rename from src/Spectre.Console/Composition/Widgets/Table.cs rename to src/Spectre.Console/Rendering/Table.cs index a869cfd..145143a 100644 --- a/src/Spectre.Console/Composition/Widgets/Table.cs +++ b/src/Spectre.Console/Rendering/Table.cs @@ -1,18 +1,19 @@ using System; using System.Collections.Generic; using System.Linq; -using Spectre.Console.Composition; using Spectre.Console.Internal; +using Spectre.Console.Rendering; +using SpectreBorder = Spectre.Console.Rendering.Border; namespace Spectre.Console { /// - /// Represents a table. + /// A renderable table. /// - public sealed partial class Table : IRenderable + public sealed partial class Table : Renderable { private readonly List _columns; - private readonly List> _rows; + private readonly List> _rows; /// /// Gets the number of columns in the table. @@ -67,21 +68,7 @@ namespace Spectre.Console public Table() { _columns = new List(); - _rows = new List>(); - } - - /// - /// Adds a column to the table. - /// - /// The column to add. - public void AddColumn(string column) - { - if (column is null) - { - throw new ArgumentNullException(nameof(column)); - } - - AddColumn(new TableColumn(column)); + _rows = new List>(); } /// @@ -103,23 +90,6 @@ namespace Spectre.Console _columns.Add(column); } - /// - /// Adds multiple columns to the table. - /// - /// The columns to add. - public void AddColumns(params string[] columns) - { - if (columns is null) - { - throw new ArgumentNullException(nameof(columns)); - } - - foreach (var column in columns) - { - AddColumn(column); - } - } - /// /// Adds multiple columns to the table. /// @@ -142,8 +112,8 @@ namespace Spectre.Console /// public void AddEmptyRow() { - var columns = new string[ColumnCount]; - Enumerable.Range(0, ColumnCount).ForEach(index => columns[index] = string.Empty); + var columns = new IRenderable[ColumnCount]; + Enumerable.Range(0, ColumnCount).ForEach(index => columns[index] = Text.Empty); AddRow(columns); } @@ -151,7 +121,7 @@ namespace Spectre.Console /// Adds a row to the table. /// /// The row columns to add. - public void AddRow(params string[] columns) + public void AddRow(params IRenderable[] columns) { if (columns is null) { @@ -168,11 +138,11 @@ namespace Spectre.Console throw new InvalidOperationException("The number of row columns are greater than the number of table columns."); } - _rows.Add(columns.Select(column => Text.Markup(column)).ToList()); + _rows.Add(columns.ToList()); } /// - Measurement IRenderable.Measure(RenderContext context, int maxWidth) + protected override Measurement Measure(RenderContext context, int maxWidth) { if (context is null) { @@ -194,20 +164,20 @@ namespace Spectre.Console } /// - IEnumerable IRenderable.Render(RenderContext context, int width) + protected override IEnumerable Render(RenderContext context, int maxWidth) { if (context is null) { throw new ArgumentNullException(nameof(context)); } - var border = Composition.Border.GetBorder(Border, (context.LegacyConsole || !context.Unicode) && SafeBorder); + var border = SpectreBorder.GetBorder(Border, (context.LegacyConsole || !context.Unicode) && SafeBorder); + var tableWidth = maxWidth; var showBorder = Border != BorderKind.None; var hideBorder = Border == BorderKind.None; var hasRows = _rows.Count > 0; - var maxWidth = width; if (Width != null) { maxWidth = Math.Min(Width.Value, maxWidth); @@ -219,13 +189,13 @@ namespace Spectre.Console var columnWidths = CalculateColumnWidths(context, maxWidth); // Update the table width. - width = columnWidths.Sum() + GetExtraWidth(includePadding: true); + tableWidth = columnWidths.Sum() + GetExtraWidth(includePadding: true); - var rows = new List>(); + var rows = new List>(); if (ShowHeaders) { // Add columns to top of rows - rows.Add(new List(_columns.Select(c => c.Text))); + rows.Add(new List(_columns.Select(c => c.Text))); } // Add rows. @@ -244,7 +214,7 @@ namespace Spectre.Console var justification = _columns[columnIndex].Alignment; var childContext = context.WithJustification(justification); - var lines = Segment.SplitLines(((IRenderable)cell).Render(childContext, rowWidth)); + var lines = Segment.SplitLines(cell.Render(childContext, rowWidth)); cellHeight = Math.Max(cellHeight, lines.Count); cells.Add(lines); } diff --git a/src/Spectre.Console/Composition/Widgets/TableColumn.cs b/src/Spectre.Console/Rendering/TableColumn.cs similarity index 70% rename from src/Spectre.Console/Composition/Widgets/TableColumn.cs rename to src/Spectre.Console/Rendering/TableColumn.cs index 5b1fde4..dad5bb5 100644 --- a/src/Spectre.Console/Composition/Widgets/TableColumn.cs +++ b/src/Spectre.Console/Rendering/TableColumn.cs @@ -1,16 +1,17 @@ using System; +using Spectre.Console.Rendering; namespace Spectre.Console { /// /// Represents a table column. /// - public sealed class TableColumn + public sealed class TableColumn : IAlignable { /// /// Gets the text associated with the column. /// - public Text Text { get; } + public IRenderable Text { get; } /// /// Gets or sets the width of the column. @@ -39,8 +40,17 @@ namespace Spectre.Console /// /// The table column text. public TableColumn(string text) + : this(new Markup(text)) { - Text = Text.Markup(text ?? throw new ArgumentNullException(nameof(text))); + } + + /// + /// Initializes a new instance of the class. + /// + /// The instance to use as the table column. + public TableColumn(IRenderable renderable) + { + Text = renderable ?? throw new ArgumentNullException(nameof(renderable)); Width = null; Padding = new Padding(1, 1); NoWrap = false; diff --git a/src/Spectre.Console/Rendering/TableExtensions.cs b/src/Spectre.Console/Rendering/TableExtensions.cs new file mode 100644 index 0000000..fa92f59 --- /dev/null +++ b/src/Spectre.Console/Rendering/TableExtensions.cs @@ -0,0 +1,78 @@ +using System; +using System.Linq; + +namespace Spectre.Console +{ + /// + /// Contains extension methods for . + /// + public static class TableExtensions + { + /// + /// Adds a column to the table. + /// + /// The table to add the column to. + /// The column to add. + /// The added instance. + public static TableColumn AddColumn(this Table table, string column) + { + if (table is null) + { + throw new ArgumentNullException(nameof(table)); + } + + if (column is null) + { + throw new ArgumentNullException(nameof(column)); + } + + var tableColumn = new TableColumn(column); + table.AddColumn(tableColumn); + + return tableColumn; + } + + /// + /// Adds multiple columns to the table. + /// + /// The table to add the columns to. + /// The columns to add. + public static void AddColumns(this Table table, params string[] columns) + { + if (table is null) + { + throw new ArgumentNullException(nameof(table)); + } + + if (columns is null) + { + throw new ArgumentNullException(nameof(columns)); + } + + foreach (var column in columns) + { + AddColumn(table, column); + } + } + + /// + /// Adds a row to the table. + /// + /// The table to add the row to. + /// The row columns to add. + public static void AddRow(this Table table, params string[] columns) + { + if (table is null) + { + throw new ArgumentNullException(nameof(table)); + } + + if (columns is null) + { + throw new ArgumentNullException(nameof(columns)); + } + + table.AddRow(columns.Select(column => new Markup(column)).ToArray()); + } + } +} diff --git a/src/Spectre.Console/Composition/Widgets/Text.cs b/src/Spectre.Console/Rendering/Text.cs similarity index 93% rename from src/Spectre.Console/Composition/Widgets/Text.cs rename to src/Spectre.Console/Rendering/Text.cs index afc5b2c..2a13234 100644 --- a/src/Spectre.Console/Composition/Widgets/Text.cs +++ b/src/Spectre.Console/Rendering/Text.cs @@ -3,24 +3,29 @@ using System.Collections.Generic; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Linq; -using Spectre.Console.Composition; using Spectre.Console.Internal; +using Spectre.Console.Rendering; namespace Spectre.Console { /// - /// Represents a piece of text. + /// A renderable piece of text. /// [DebuggerDisplay("{_text,nq}")] [SuppressMessage("Naming", "CA1724:Type names should not match namespaces")] - public sealed class Text : IRenderable + public sealed class Text : Renderable, IAlignable { private readonly List _lines; + /// + /// Gets an empty instance. + /// + public static Text Empty { get; } = new Text(string.Empty); + /// /// Gets or sets the text alignment. /// - public Justify Alignment { get; set; } = Justify.Left; + public Justify? Alignment { get; set; } /// /// Initializes a new instance of the class. @@ -60,7 +65,7 @@ namespace Spectre.Console } /// - public Measurement Measure(RenderContext context, int maxWidth) + protected override Measurement Measure(RenderContext context, int maxWidth) { if (_lines.Count == 0) { @@ -74,7 +79,7 @@ namespace Spectre.Console } /// - public IEnumerable Render(RenderContext context, int maxWidth) + protected override IEnumerable Render(RenderContext context, int maxWidth) { if (context is null) { @@ -89,7 +94,7 @@ namespace Spectre.Console var lines = SplitLines(context, maxWidth); // Justify lines - var justification = context.Justification ?? Alignment; + var justification = context.Justification ?? Alignment ?? Justify.Left; foreach (var (_, _, last, line) in lines.Enumerate()) { var length = line.Sum(l => l.StripLineEndings().CellLength(context.Encoding));