mirror of
				https://github.com/nsnail/spectre.console.git
				synced 2025-11-01 01:25:27 +08:00 
			
		
		
		
	Add Layout widget (#1041)
* Add width to panels * Add height to panels * Replace RenderContext with RenderOptions * Remove exclusivity from alternative buffer * Add Layout widget * Add Align widget
This commit is contained in:
		| @@ -84,7 +84,7 @@ public static class Program | |||||||
|     private static void HorizontalRule(string title) |     private static void HorizontalRule(string title) | ||||||
|     { |     { | ||||||
|         AnsiConsole.WriteLine(); |         AnsiConsole.WriteLine(); | ||||||
|         AnsiConsole.Write(new Rule($"[white bold]{title}[/]").RuleStyle("grey").LeftAligned()); |         AnsiConsole.Write(new Rule($"[white bold]{title}[/]").RuleStyle("grey").LeftJustified()); | ||||||
|         AnsiConsole.WriteLine(); |         AnsiConsole.WriteLine(); | ||||||
|     } |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -40,7 +40,7 @@ public static class Program | |||||||
|     private static void Render(IRenderable canvas, string title) |     private static void Render(IRenderable canvas, string title) | ||||||
|     { |     { | ||||||
|         AnsiConsole.WriteLine(); |         AnsiConsole.WriteLine(); | ||||||
|         AnsiConsole.Write(new Rule($"[yellow]{title}[/]").LeftAligned().RuleStyle("grey")); |         AnsiConsole.Write(new Rule($"[yellow]{title}[/]").LeftJustified().RuleStyle("grey")); | ||||||
|         AnsiConsole.WriteLine(); |         AnsiConsole.WriteLine(); | ||||||
|         AnsiConsole.Write(canvas); |         AnsiConsole.Write(canvas); | ||||||
|     } |     } | ||||||
|   | |||||||
| @@ -23,7 +23,7 @@ public static class Program | |||||||
|         { |         { | ||||||
|             AnsiConsole.ResetColors(); |             AnsiConsole.ResetColors(); | ||||||
|             AnsiConsole.WriteLine(); |             AnsiConsole.WriteLine(); | ||||||
|             AnsiConsole.Write(new Rule("[yellow bold underline]3-bit Colors[/]").RuleStyle("grey").LeftAligned()); |             AnsiConsole.Write(new Rule("[yellow bold underline]3-bit Colors[/]").RuleStyle("grey").LeftJustified()); | ||||||
|             AnsiConsole.WriteLine(); |             AnsiConsole.WriteLine(); | ||||||
|  |  | ||||||
|             for (var i = 0; i < 8; i++) |             for (var i = 0; i < 8; i++) | ||||||
| @@ -46,7 +46,7 @@ public static class Program | |||||||
|         { |         { | ||||||
|             AnsiConsole.ResetColors(); |             AnsiConsole.ResetColors(); | ||||||
|             AnsiConsole.WriteLine(); |             AnsiConsole.WriteLine(); | ||||||
|             AnsiConsole.Write(new Rule("[yellow bold underline]4-bit Colors[/]").RuleStyle("grey").LeftAligned()); |             AnsiConsole.Write(new Rule("[yellow bold underline]4-bit Colors[/]").RuleStyle("grey").LeftJustified()); | ||||||
|             AnsiConsole.WriteLine(); |             AnsiConsole.WriteLine(); | ||||||
|  |  | ||||||
|             for (var i = 0; i < 16; i++) |             for (var i = 0; i < 16; i++) | ||||||
| @@ -69,7 +69,7 @@ public static class Program | |||||||
|         { |         { | ||||||
|             AnsiConsole.ResetColors(); |             AnsiConsole.ResetColors(); | ||||||
|             AnsiConsole.WriteLine(); |             AnsiConsole.WriteLine(); | ||||||
|             AnsiConsole.Write(new Rule("[yellow bold underline]8-bit Colors[/]").RuleStyle("grey").LeftAligned()); |             AnsiConsole.Write(new Rule("[yellow bold underline]8-bit Colors[/]").RuleStyle("grey").LeftJustified()); | ||||||
|             AnsiConsole.WriteLine(); |             AnsiConsole.WriteLine(); | ||||||
|  |  | ||||||
|             for (var i = 0; i < 16; i++) |             for (var i = 0; i < 16; i++) | ||||||
| @@ -96,7 +96,7 @@ public static class Program | |||||||
|         { |         { | ||||||
|             AnsiConsole.ResetColors(); |             AnsiConsole.ResetColors(); | ||||||
|             AnsiConsole.WriteLine(); |             AnsiConsole.WriteLine(); | ||||||
|             AnsiConsole.Write(new Rule("[yellow bold underline]24-bit Colors[/]").RuleStyle("grey").LeftAligned()); |             AnsiConsole.Write(new Rule("[yellow bold underline]24-bit Colors[/]").RuleStyle("grey").LeftJustified()); | ||||||
|             AnsiConsole.WriteLine(); |             AnsiConsole.WriteLine(); | ||||||
|  |  | ||||||
|             AnsiConsole.Write(new ColorBox(width: 80, height: 15)); |             AnsiConsole.Write(new ColorBox(width: 80, height: 15)); | ||||||
|   | |||||||
| @@ -19,17 +19,17 @@ public static class Program | |||||||
|         catch (Exception ex) |         catch (Exception ex) | ||||||
|         { |         { | ||||||
|             AnsiConsole.WriteLine(); |             AnsiConsole.WriteLine(); | ||||||
|             AnsiConsole.Write(new Rule("Default").LeftAligned()); |             AnsiConsole.Write(new Rule("Default").LeftJustified()); | ||||||
|             AnsiConsole.WriteLine(); |             AnsiConsole.WriteLine(); | ||||||
|             AnsiConsole.WriteException(ex); |             AnsiConsole.WriteException(ex); | ||||||
|  |  | ||||||
|             AnsiConsole.WriteLine(); |             AnsiConsole.WriteLine(); | ||||||
|             AnsiConsole.Write(new Rule("Compact").LeftAligned()); |             AnsiConsole.Write(new Rule("Compact").LeftJustified()); | ||||||
|             AnsiConsole.WriteLine(); |             AnsiConsole.WriteLine(); | ||||||
|             AnsiConsole.WriteException(ex, ExceptionFormats.ShortenEverything | ExceptionFormats.ShowLinks); |             AnsiConsole.WriteException(ex, ExceptionFormats.ShortenEverything | ExceptionFormats.ShowLinks); | ||||||
|  |  | ||||||
|             AnsiConsole.WriteLine(); |             AnsiConsole.WriteLine(); | ||||||
|             AnsiConsole.Write(new Rule("Compact + Custom colors").LeftAligned()); |             AnsiConsole.Write(new Rule("Compact + Custom colors").LeftJustified()); | ||||||
|             AnsiConsole.WriteLine(); |             AnsiConsole.WriteLine(); | ||||||
|             AnsiConsole.WriteException(ex, new ExceptionSettings |             AnsiConsole.WriteException(ex, new ExceptionSettings | ||||||
|             { |             { | ||||||
| @@ -56,7 +56,7 @@ public static class Program | |||||||
|         catch (Exception ex) |         catch (Exception ex) | ||||||
|         { |         { | ||||||
|             AnsiConsole.WriteLine(); |             AnsiConsole.WriteLine(); | ||||||
|             AnsiConsole.Write(new Rule("Async").LeftAligned()); |             AnsiConsole.Write(new Rule("Async").LeftJustified()); | ||||||
|             AnsiConsole.WriteLine(); |             AnsiConsole.WriteLine(); | ||||||
|             AnsiConsole.WriteException(ex, ExceptionFormats.ShortenPaths); |             AnsiConsole.WriteException(ex, ExceptionFormats.ShortenPaths); | ||||||
|         } |         } | ||||||
|   | |||||||
| @@ -6,8 +6,8 @@ public static class Program | |||||||
| { | { | ||||||
|     public static void Main(string[] args) |     public static void Main(string[] args) | ||||||
|     { |     { | ||||||
|         AnsiConsole.Write(new FigletText("Left aligned").LeftAligned().Color(Color.Red)); |         AnsiConsole.Write(new FigletText("Left aligned").LeftJustified().Color(Color.Red)); | ||||||
|         AnsiConsole.Write(new FigletText("Centered").Centered().Color(Color.Green)); |         AnsiConsole.Write(new FigletText("Centered").Centered().Color(Color.Green)); | ||||||
|         AnsiConsole.Write(new FigletText("Right aligned").RightAligned().Color(Color.Blue)); |         AnsiConsole.Write(new FigletText("Right aligned").RightJustified().Color(Color.Blue)); | ||||||
|     } |     } | ||||||
| } | } | ||||||
|   | |||||||
							
								
								
									
										15
									
								
								examples/Console/Layout/Layout.csproj
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								examples/Console/Layout/Layout.csproj
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,15 @@ | |||||||
|  | <Project Sdk="Microsoft.NET.Sdk"> | ||||||
|  |  | ||||||
|  |   <PropertyGroup> | ||||||
|  |     <OutputType>Exe</OutputType> | ||||||
|  |     <TargetFramework>net7.0</TargetFramework> | ||||||
|  |     <ExampleTitle>Layout</ExampleTitle> | ||||||
|  |     <ExampleDescription>Demonstrates how to use layouts.</ExampleDescription> | ||||||
|  |     <ExampleGroup>Widgets</ExampleGroup> | ||||||
|  |   </PropertyGroup> | ||||||
|  |  | ||||||
|  |   <ItemGroup> | ||||||
|  |     <ProjectReference Include="..\..\Shared\Shared.csproj" /> | ||||||
|  |   </ItemGroup> | ||||||
|  |  | ||||||
|  | </Project> | ||||||
							
								
								
									
										60
									
								
								examples/Console/Layout/Program.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										60
									
								
								examples/Console/Layout/Program.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,60 @@ | |||||||
|  | using System; | ||||||
|  | using Spectre.Console; | ||||||
|  |  | ||||||
|  | namespace Layouts; | ||||||
|  |  | ||||||
|  | public static class Program | ||||||
|  | { | ||||||
|  |     public static void Main() | ||||||
|  |     { | ||||||
|  |         var layout = CreateLayout(); | ||||||
|  |         AnsiConsole.Write(layout); | ||||||
|  |  | ||||||
|  |         Console.ReadKey(true); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     private static Layout CreateLayout() | ||||||
|  |     { | ||||||
|  |         var layout = new Layout(); | ||||||
|  |  | ||||||
|  |         layout.SplitRows( | ||||||
|  |             new Layout("Top") | ||||||
|  |                 .SplitColumns( | ||||||
|  |                     new Layout("Left") | ||||||
|  |                         .SplitRows( | ||||||
|  |                             new Layout("LeftTop"), | ||||||
|  |                             new Layout("LeftBottom")), | ||||||
|  |                     new Layout("Right").Ratio(2), | ||||||
|  |                     new Layout("RightRight").Size(3)), | ||||||
|  |             new Layout("Bottom")); | ||||||
|  |  | ||||||
|  |         layout["LeftBottom"].Update( | ||||||
|  |             new Panel("[blink]PRESS ANY KEY TO QUIT[/]") | ||||||
|  |                 .Expand() | ||||||
|  |                 .BorderColor(Color.Yellow) | ||||||
|  |                 .Padding(0, 0)); | ||||||
|  |  | ||||||
|  |         layout["Right"].Update( | ||||||
|  |             new Panel( | ||||||
|  |                 new Table() | ||||||
|  |                     .AddColumns("[blue]Qux[/]", "[green]Corgi[/]") | ||||||
|  |                     .AddRow("9", "8") | ||||||
|  |                     .AddRow("7", "6") | ||||||
|  |                     .Expand()) | ||||||
|  |             .Header("A [yellow]Table[/] in a [blue]Panel[/] (Ratio=2)") | ||||||
|  |             .Expand()); | ||||||
|  |  | ||||||
|  |         layout["RightRight"].Update( | ||||||
|  |             new Panel("Explicit-size-is-[yellow]3[/]") | ||||||
|  |                 .BorderColor(Color.Yellow) | ||||||
|  |                 .Padding(0, 0)); | ||||||
|  |  | ||||||
|  |         layout["Bottom"].Update( | ||||||
|  |         new Panel( | ||||||
|  |                 new FigletText("Hello World")) | ||||||
|  |             .Header("Some [green]Figlet[/] text") | ||||||
|  |             .Expand()); | ||||||
|  |  | ||||||
|  |         return layout; | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -17,7 +17,7 @@ public static class Program | |||||||
|  |  | ||||||
|         // Left adjusted panel with text |         // Left adjusted panel with text | ||||||
|         AnsiConsole.Write( |         AnsiConsole.Write( | ||||||
|             new Panel(new Text("Left adjusted\nLeft").LeftAligned()) |             new Panel(new Text("Left adjusted\nLeft").LeftJustified()) | ||||||
|                 .Expand() |                 .Expand() | ||||||
|                 .SquareBorder() |                 .SquareBorder() | ||||||
|                 .Header("[red]Left[/]")); |                 .Header("[red]Left[/]")); | ||||||
| @@ -32,7 +32,7 @@ public static class Program | |||||||
|  |  | ||||||
|         // Right adjusted, rounded panel with text |         // Right adjusted, rounded panel with text | ||||||
|         AnsiConsole.Write( |         AnsiConsole.Write( | ||||||
|             new Panel(new Text("Right adjusted\nRight").RightAligned()) |             new Panel(new Text("Right adjusted\nRight").RightJustified()) | ||||||
|                 .Expand() |                 .Expand() | ||||||
|                 .RoundedBorder() |                 .RoundedBorder() | ||||||
|                 .Header("[blue]Right[/]") |                 .Header("[blue]Right[/]") | ||||||
|   | |||||||
| @@ -58,9 +58,9 @@ public static class Program | |||||||
|         var table = new Table().BorderColor(Color.Grey).Title("Aligned").RoundedBorder(); |         var table = new Table().BorderColor(Color.Grey).Title("Aligned").RoundedBorder(); | ||||||
|         table.AddColumns("[grey]Alignment[/]", "[grey]Path[/]"); |         table.AddColumns("[grey]Alignment[/]", "[grey]Path[/]"); | ||||||
|  |  | ||||||
|         table.AddRow(new Text("Left"), new TextPath(path).LeftAligned()); |         table.AddRow(new Text("Left"), new TextPath(path).LeftJustified()); | ||||||
|         table.AddRow(new Text("Center"), new TextPath(path).Centered()); |         table.AddRow(new Text("Center"), new TextPath(path).Centered()); | ||||||
|         table.AddRow(new Text("Right"), new TextPath(path).RightAligned()); |         table.AddRow(new Text("Right"), new TextPath(path).RightJustified()); | ||||||
|  |  | ||||||
|         AnsiConsole.Write(table); |         AnsiConsole.Write(table); | ||||||
|     } |     } | ||||||
|   | |||||||
| @@ -46,7 +46,7 @@ namespace Prompt | |||||||
|  |  | ||||||
|             // Summary |             // Summary | ||||||
|             AnsiConsole.WriteLine(); |             AnsiConsole.WriteLine(); | ||||||
|             AnsiConsole.Write(new Rule("[yellow]Results[/]").RuleStyle("grey").LeftAligned()); |             AnsiConsole.Write(new Rule("[yellow]Results[/]").RuleStyle("grey").LeftJustified()); | ||||||
|             AnsiConsole.Write(new Table().AddColumns("[grey]Question[/]", "[grey]Answer[/]") |             AnsiConsole.Write(new Table().AddColumns("[grey]Question[/]", "[grey]Answer[/]") | ||||||
|                 .RoundedBorder() |                 .RoundedBorder() | ||||||
|                 .BorderColor(Color.Grey) |                 .BorderColor(Color.Grey) | ||||||
| @@ -63,7 +63,7 @@ namespace Prompt | |||||||
|         private static void WriteDivider(string text) |         private static void WriteDivider(string text) | ||||||
|         { |         { | ||||||
|             AnsiConsole.WriteLine(); |             AnsiConsole.WriteLine(); | ||||||
|             AnsiConsole.Write(new Rule($"[yellow]{text}[/]").RuleStyle("grey").LeftAligned()); |             AnsiConsole.Write(new Rule($"[yellow]{text}[/]").RuleStyle("grey").LeftJustified()); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         public static bool AskConfirmation() |         public static bool AskConfirmation() | ||||||
|   | |||||||
| @@ -11,14 +11,14 @@ public static class Program | |||||||
|             new Rule() |             new Rule() | ||||||
|                 .RuleStyle(Style.Parse("yellow")) |                 .RuleStyle(Style.Parse("yellow")) | ||||||
|                 .AsciiBorder() |                 .AsciiBorder() | ||||||
|                 .LeftAligned()); |                 .LeftJustified()); | ||||||
|  |  | ||||||
|         // Left aligned title |         // Left aligned title | ||||||
|         Render( |         Render( | ||||||
|             new Rule("[blue]Left aligned[/]") |             new Rule("[blue]Left aligned[/]") | ||||||
|                 .RuleStyle(Style.Parse("red")) |                 .RuleStyle(Style.Parse("red")) | ||||||
|                 .DoubleBorder() |                 .DoubleBorder() | ||||||
|                 .LeftAligned()); |                 .LeftJustified()); | ||||||
|  |  | ||||||
|         // Centered title |         // Centered title | ||||||
|         Render( |         Render( | ||||||
| @@ -31,7 +31,7 @@ public static class Program | |||||||
|         Render( |         Render( | ||||||
|             new Rule("[red]Right aligned[/]") |             new Rule("[red]Right aligned[/]") | ||||||
|                 .RuleStyle(Style.Parse("blue")) |                 .RuleStyle(Style.Parse("blue")) | ||||||
|                 .RightAligned()); |                 .RightJustified()); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     private static void Render(Rule rule) |     private static void Render(Rule rule) | ||||||
|   | |||||||
| @@ -77,6 +77,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Paths", "Console\Paths\Path | |||||||
| EndProject | EndProject | ||||||
| Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Spectre.Console.Cli", "..\src\Spectre.Console.Cli\Spectre.Console.Cli.csproj", "{EFAADF6A-C77D-41EC-83F5-BBB4FFC5A6D7}" | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Spectre.Console.Cli", "..\src\Spectre.Console.Cli\Spectre.Console.Cli.csproj", "{EFAADF6A-C77D-41EC-83F5-BBB4FFC5A6D7}" | ||||||
| EndProject | EndProject | ||||||
|  | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Layout", "Console\Layout\Layout.csproj", "{A9FDE73A-8452-4CA3-B366-3F900597E132}" | ||||||
|  | EndProject | ||||||
| Global | Global | ||||||
| 	GlobalSection(SolutionConfigurationPlatforms) = preSolution | 	GlobalSection(SolutionConfigurationPlatforms) = preSolution | ||||||
| 		Debug|Any CPU = Debug|Any CPU | 		Debug|Any CPU = Debug|Any CPU | ||||||
| @@ -507,6 +509,18 @@ Global | |||||||
| 		{EFAADF6A-C77D-41EC-83F5-BBB4FFC5A6D7}.Release|x64.Build.0 = Release|Any CPU | 		{EFAADF6A-C77D-41EC-83F5-BBB4FFC5A6D7}.Release|x64.Build.0 = Release|Any CPU | ||||||
| 		{EFAADF6A-C77D-41EC-83F5-BBB4FFC5A6D7}.Release|x86.ActiveCfg = Release|Any CPU | 		{EFAADF6A-C77D-41EC-83F5-BBB4FFC5A6D7}.Release|x86.ActiveCfg = Release|Any CPU | ||||||
| 		{EFAADF6A-C77D-41EC-83F5-BBB4FFC5A6D7}.Release|x86.Build.0 = Release|Any CPU | 		{EFAADF6A-C77D-41EC-83F5-BBB4FFC5A6D7}.Release|x86.Build.0 = Release|Any CPU | ||||||
|  | 		{A9FDE73A-8452-4CA3-B366-3F900597E132}.Debug|Any CPU.ActiveCfg = Debug|Any CPU | ||||||
|  | 		{A9FDE73A-8452-4CA3-B366-3F900597E132}.Debug|Any CPU.Build.0 = Debug|Any CPU | ||||||
|  | 		{A9FDE73A-8452-4CA3-B366-3F900597E132}.Debug|x64.ActiveCfg = Debug|Any CPU | ||||||
|  | 		{A9FDE73A-8452-4CA3-B366-3F900597E132}.Debug|x64.Build.0 = Debug|Any CPU | ||||||
|  | 		{A9FDE73A-8452-4CA3-B366-3F900597E132}.Debug|x86.ActiveCfg = Debug|Any CPU | ||||||
|  | 		{A9FDE73A-8452-4CA3-B366-3F900597E132}.Debug|x86.Build.0 = Debug|Any CPU | ||||||
|  | 		{A9FDE73A-8452-4CA3-B366-3F900597E132}.Release|Any CPU.ActiveCfg = Release|Any CPU | ||||||
|  | 		{A9FDE73A-8452-4CA3-B366-3F900597E132}.Release|Any CPU.Build.0 = Release|Any CPU | ||||||
|  | 		{A9FDE73A-8452-4CA3-B366-3F900597E132}.Release|x64.ActiveCfg = Release|Any CPU | ||||||
|  | 		{A9FDE73A-8452-4CA3-B366-3F900597E132}.Release|x64.Build.0 = Release|Any CPU | ||||||
|  | 		{A9FDE73A-8452-4CA3-B366-3F900597E132}.Release|x86.ActiveCfg = Release|Any CPU | ||||||
|  | 		{A9FDE73A-8452-4CA3-B366-3F900597E132}.Release|x86.Build.0 = Release|Any CPU | ||||||
| 	EndGlobalSection | 	EndGlobalSection | ||||||
| 	GlobalSection(SolutionProperties) = preSolution | 	GlobalSection(SolutionProperties) = preSolution | ||||||
| 		HideSolutionNode = FALSE | 		HideSolutionNode = FALSE | ||||||
|   | |||||||
| @@ -20,12 +20,12 @@ public sealed class ColorBox : Renderable | |||||||
|         _width = width; |         _width = width; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     protected override Measurement Measure(RenderContext context, int maxWidth) |     protected override Measurement Measure(RenderOptions options, int maxWidth) | ||||||
|     { |     { | ||||||
|         return new Measurement(1, GetWidth(maxWidth)); |         return new Measurement(1, GetWidth(maxWidth)); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     protected override IEnumerable<Segment> Render(RenderContext context, int maxWidth) |     protected override IEnumerable<Segment> Render(RenderOptions options, int maxWidth) | ||||||
|     { |     { | ||||||
|         maxWidth = GetWidth(maxWidth); |         maxWidth = GetWidth(maxWidth); | ||||||
|  |  | ||||||
|   | |||||||
| @@ -83,14 +83,14 @@ internal sealed class Composer : IRenderable | |||||||
|         return this; |         return this; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     public Measurement Measure(RenderContext context, int maxWidth) |     public Measurement Measure(RenderOptions options, int maxWidth) | ||||||
|     { |     { | ||||||
|         return ((IRenderable)new Markup(_content.ToString())).Measure(context, maxWidth); |         return ((IRenderable)new Markup(_content.ToString())).Measure(options, maxWidth); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     public IEnumerable<Segment> Render(RenderContext context, int maxWidth) |     public IEnumerable<Segment> Render(RenderOptions options, int maxWidth) | ||||||
|     { |     { | ||||||
|         return ((IRenderable)new Markup(_content.ToString())).Render(context, maxWidth); |         return ((IRenderable)new Markup(_content.ToString())).Render(options, maxWidth); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     public override string ToString() |     public override string ToString() | ||||||
|   | |||||||
| @@ -71,7 +71,7 @@ public sealed class CanvasImage : Renderable | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     /// <inheritdoc/> |     /// <inheritdoc/> | ||||||
|     protected override Measurement Measure(RenderContext context, int maxWidth) |     protected override Measurement Measure(RenderOptions options, int maxWidth) | ||||||
|     { |     { | ||||||
|         if (PixelWidth < 0) |         if (PixelWidth < 0) | ||||||
|         { |         { | ||||||
| @@ -88,7 +88,7 @@ public sealed class CanvasImage : Renderable | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     /// <inheritdoc/> |     /// <inheritdoc/> | ||||||
|     protected override IEnumerable<Segment> Render(RenderContext context, int maxWidth) |     protected override IEnumerable<Segment> Render(RenderOptions options, int maxWidth) | ||||||
|     { |     { | ||||||
|         var image = Image; |         var image = Image; | ||||||
|  |  | ||||||
| @@ -138,6 +138,6 @@ public sealed class CanvasImage : Renderable | |||||||
|             } |             } | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         return ((IRenderable)canvas).Render(context, maxWidth); |         return ((IRenderable)canvas).Render(options, maxWidth); | ||||||
|     } |     } | ||||||
| } | } | ||||||
| @@ -27,11 +27,17 @@ public sealed class TestCapabilities : IReadOnlyCapabilities | |||||||
|     public bool Unicode { get; set; } |     public bool Unicode { get; set; } | ||||||
|  |  | ||||||
|     /// <summary> |     /// <summary> | ||||||
|     /// Creates a <see cref="RenderContext"/> with the same capabilities as this instace. |     /// Creates a <see cref="RenderOptions"/> with the same capabilities as this instace. | ||||||
|     /// </summary> |     /// </summary> | ||||||
|     /// <returns>A <see cref="RenderContext"/> with the same capabilities as this instace.</returns> |     /// <param name="console">The console.</param> | ||||||
|     public RenderContext CreateRenderContext() |     /// <returns>A <see cref="RenderOptions"/> with the same capabilities as this instace.</returns> | ||||||
|  |     public RenderOptions CreateRenderContext(IAnsiConsole console) | ||||||
|     { |     { | ||||||
|         return new RenderContext(this); |         if (console is null) | ||||||
|  |         { | ||||||
|  |             throw new ArgumentNullException(nameof(console)); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         return RenderOptions.Create(console, this); | ||||||
|     } |     } | ||||||
| } | } | ||||||
| @@ -52,6 +52,31 @@ public static class TestConsoleExtensions | |||||||
|         return console; |         return console; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// Sets the console height. | ||||||
|  |     /// </summary> | ||||||
|  |     /// <param name="console">The console.</param> | ||||||
|  |     /// <param name="width">The console height.</param> | ||||||
|  |     /// <returns>The same instance so that multiple calls can be chained.</returns> | ||||||
|  |     public static TestConsole Height(this TestConsole console, int width) | ||||||
|  |     { | ||||||
|  |         console.Profile.Height = width; | ||||||
|  |         return console; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// Sets the console size. | ||||||
|  |     /// </summary> | ||||||
|  |     /// <param name="console">The console.</param> | ||||||
|  |     /// <param name="size">The console size.</param> | ||||||
|  |     /// <returns>The same instance so that multiple calls can be chained.</returns> | ||||||
|  |     public static TestConsole Size(this TestConsole console, Size size) | ||||||
|  |     { | ||||||
|  |         console.Profile.Width = size.Width; | ||||||
|  |         console.Profile.Height = size.Height; | ||||||
|  |         return console; | ||||||
|  |     } | ||||||
|  |  | ||||||
|     /// <summary> |     /// <summary> | ||||||
|     /// Turns on emitting of VT/ANSI sequences. |     /// Turns on emitting of VT/ANSI sequences. | ||||||
|     /// </summary> |     /// </summary> | ||||||
|   | |||||||
							
								
								
									
										6
									
								
								src/Spectre.Console.v3.ncrunchsolution
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								src/Spectre.Console.v3.ncrunchsolution
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,6 @@ | |||||||
|  | <SolutionConfiguration> | ||||||
|  |   <Settings> | ||||||
|  |     <AllowParallelTestExecution>True</AllowParallelTestExecution> | ||||||
|  |     <SolutionConfigured>True</SolutionConfigured> | ||||||
|  |   </Settings> | ||||||
|  | </SolutionConfiguration> | ||||||
							
								
								
									
										106
									
								
								src/Spectre.Console/Extensions/AlignExtensions.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										106
									
								
								src/Spectre.Console/Extensions/AlignExtensions.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,106 @@ | |||||||
|  | namespace Spectre.Console.Extensions; | ||||||
|  |  | ||||||
|  | /// <summary> | ||||||
|  | /// Contains extension methods for <see cref="Align"/>. | ||||||
|  | /// </summary> | ||||||
|  | public static class AlignExtensions | ||||||
|  | { | ||||||
|  |     /// <summary> | ||||||
|  |     /// Sets the width. | ||||||
|  |     /// </summary> | ||||||
|  |     /// <param name="align">The <see cref="Align"/> object.</param> | ||||||
|  |     /// <param name="width">The width, or <c>null</c> for no explicit width.</param> | ||||||
|  |     /// <returns>The same instance so that multiple calls can be chained.</returns> | ||||||
|  |     public static Align Width(this Align align, int? width) | ||||||
|  |     { | ||||||
|  |         if (align is null) | ||||||
|  |         { | ||||||
|  |             throw new ArgumentNullException(nameof(align)); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         align.Width = width; | ||||||
|  |         return align; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// Sets the height. | ||||||
|  |     /// </summary> | ||||||
|  |     /// <param name="align">The <see cref="Align"/> object.</param> | ||||||
|  |     /// <param name="height">The height, or <c>null</c> for no explicit height.</param> | ||||||
|  |     /// <returns>The same instance so that multiple calls can be chained.</returns> | ||||||
|  |     public static Align Height(this Align align, int? height) | ||||||
|  |     { | ||||||
|  |         if (align is null) | ||||||
|  |         { | ||||||
|  |             throw new ArgumentNullException(nameof(align)); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         align.Height = height; | ||||||
|  |         return align; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// Sets the vertical alignment. | ||||||
|  |     /// </summary> | ||||||
|  |     /// <param name="align">The <see cref="Align"/> object.</param> | ||||||
|  |     /// <param name="vertical">The vertical alignment, or <c>null</c> for no vertical alignment.</param> | ||||||
|  |     /// <returns>The same instance so that multiple calls can be chained.</returns> | ||||||
|  |     public static Align VerticalAlignment(this Align align, VerticalAlignment? vertical) | ||||||
|  |     { | ||||||
|  |         if (align is null) | ||||||
|  |         { | ||||||
|  |             throw new ArgumentNullException(nameof(align)); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         align.Vertical = vertical; | ||||||
|  |         return align; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// Sets the <see cref="Align"/> object to be top aligned. | ||||||
|  |     /// </summary> | ||||||
|  |     /// <param name="align">The <see cref="Align"/> object.</param> | ||||||
|  |     /// <returns>The same instance so that multiple calls can be chained.</returns> | ||||||
|  |     public static Align TopAligned(this Align align) | ||||||
|  |     { | ||||||
|  |         if (align is null) | ||||||
|  |         { | ||||||
|  |             throw new ArgumentNullException(nameof(align)); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         align.Vertical = Console.VerticalAlignment.Top; | ||||||
|  |         return align; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// Sets the <see cref="Align"/> object to be middle aligned. | ||||||
|  |     /// </summary> | ||||||
|  |     /// <param name="align">The <see cref="Align"/> object.</param> | ||||||
|  |     /// <returns>The same instance so that multiple calls can be chained.</returns> | ||||||
|  |     public static Align MiddleAligned(this Align align) | ||||||
|  |     { | ||||||
|  |         if (align is null) | ||||||
|  |         { | ||||||
|  |             throw new ArgumentNullException(nameof(align)); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         align.Vertical = Console.VerticalAlignment.Middle; | ||||||
|  |         return align; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// Sets the <see cref="Align"/> object to be bottom aligned. | ||||||
|  |     /// </summary> | ||||||
|  |     /// <param name="align">The <see cref="Align"/> object.</param> | ||||||
|  |     /// <returns>The same instance so that multiple calls can be chained.</returns> | ||||||
|  |     public static Align BottomAligned(this Align align) | ||||||
|  |     { | ||||||
|  |         if (align is null) | ||||||
|  |         { | ||||||
|  |             throw new ArgumentNullException(nameof(align)); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         align.Vertical = Console.VerticalAlignment.Bottom; | ||||||
|  |         return align; | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -77,4 +77,4 @@ public static class AlignableExtensions | |||||||
|         obj.Alignment = Justify.Right; |         obj.Alignment = Justify.Right; | ||||||
|         return obj; |         return obj; | ||||||
|     } |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -27,19 +27,18 @@ public static partial class AnsiConsoleExtensions | |||||||
|             throw new NotSupportedException("Alternate buffers are not supported by your terminal."); |             throw new NotSupportedException("Alternate buffers are not supported by your terminal."); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         console.ExclusivityMode.Run<object?>(() => |         // Switch to alternate screen | ||||||
|         { |         console.Write(new ControlCode("\u001b[?1049h\u001b[H")); | ||||||
|             // Switch to alternate screen |  | ||||||
|             console.Write(new ControlCode("\u001b[?1049h\u001b[H")); |  | ||||||
|  |  | ||||||
|  |         try | ||||||
|  |         { | ||||||
|             // Execute custom action |             // Execute custom action | ||||||
|             action(); |             action(); | ||||||
|  |         } | ||||||
|  |         finally | ||||||
|  |         { | ||||||
|             // Switch back to primary screen |             // Switch back to primary screen | ||||||
|             console.Write(new ControlCode("\u001b[?1049l")); |             console.Write(new ControlCode("\u001b[?1049l")); | ||||||
|  |         } | ||||||
|             // Dummy result |  | ||||||
|             return null; |  | ||||||
|         }); |  | ||||||
|     } |     } | ||||||
| } | } | ||||||
							
								
								
									
										80
									
								
								src/Spectre.Console/Extensions/HasJustificationExtensions.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										80
									
								
								src/Spectre.Console/Extensions/HasJustificationExtensions.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,80 @@ | |||||||
|  | namespace Spectre.Console; | ||||||
|  |  | ||||||
|  | /// <summary> | ||||||
|  | /// Contains extension methods for <see cref="IHasJustification"/>. | ||||||
|  | /// </summary> | ||||||
|  | public static class HasJustificationExtensions | ||||||
|  | { | ||||||
|  |     /// <summary> | ||||||
|  |     /// Sets the justification for an <see cref="IHasJustification"/> object. | ||||||
|  |     /// </summary> | ||||||
|  |     /// <typeparam name="T">The type that can be justified.</typeparam> | ||||||
|  |     /// <param name="obj">The alignable object.</param> | ||||||
|  |     /// <param name="alignment">The alignment.</param> | ||||||
|  |     /// <returns>The same instance so that multiple calls can be chained.</returns> | ||||||
|  |     public static T Justify<T>(this T obj, Justify? alignment) | ||||||
|  |         where T : class, IHasJustification | ||||||
|  |     { | ||||||
|  |         if (obj is null) | ||||||
|  |         { | ||||||
|  |             throw new System.ArgumentNullException(nameof(obj)); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         obj.Justification = alignment; | ||||||
|  |         return obj; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// Sets the <see cref="IHasJustification"/> object to be left justified. | ||||||
|  |     /// </summary> | ||||||
|  |     /// <typeparam name="T">The type that can be justified.</typeparam> | ||||||
|  |     /// <param name="obj">The alignable object.</param> | ||||||
|  |     /// <returns>The same instance so that multiple calls can be chained.</returns> | ||||||
|  |     public static T LeftJustified<T>(this T obj) | ||||||
|  |         where T : class, IHasJustification | ||||||
|  |     { | ||||||
|  |         if (obj is null) | ||||||
|  |         { | ||||||
|  |             throw new System.ArgumentNullException(nameof(obj)); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         obj.Justification = Console.Justify.Left; | ||||||
|  |         return obj; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// Sets the <see cref="IHasJustification"/> object to be centered. | ||||||
|  |     /// </summary> | ||||||
|  |     /// <typeparam name="T">The type that can be justified.</typeparam> | ||||||
|  |     /// <param name="obj">The alignable object.</param> | ||||||
|  |     /// <returns>The same instance so that multiple calls can be chained.</returns> | ||||||
|  |     public static T Centered<T>(this T obj) | ||||||
|  |         where T : class, IHasJustification | ||||||
|  |     { | ||||||
|  |         if (obj is null) | ||||||
|  |         { | ||||||
|  |             throw new System.ArgumentNullException(nameof(obj)); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         obj.Justification = Console.Justify.Center; | ||||||
|  |         return obj; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// Sets the <see cref="IHasJustification"/> object to be right justified. | ||||||
|  |     /// </summary> | ||||||
|  |     /// <typeparam name="T">The type that can be justified.</typeparam> | ||||||
|  |     /// <param name="obj">The alignable object.</param> | ||||||
|  |     /// <returns>The same instance so that multiple calls can be chained.</returns> | ||||||
|  |     public static T RightJustified<T>(this T obj) | ||||||
|  |         where T : class, IHasJustification | ||||||
|  |     { | ||||||
|  |         if (obj is null) | ||||||
|  |         { | ||||||
|  |             throw new System.ArgumentNullException(nameof(obj)); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         obj.Justification = Console.Justify.Right; | ||||||
|  |         return obj; | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										58
									
								
								src/Spectre.Console/Extensions/LayoutExtensions.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										58
									
								
								src/Spectre.Console/Extensions/LayoutExtensions.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,58 @@ | |||||||
|  | namespace Spectre.Console; | ||||||
|  |  | ||||||
|  | /// <summary> | ||||||
|  | /// Contains extension methods for <see cref="Layout"/>. | ||||||
|  | /// </summary> | ||||||
|  | public static class LayoutExtensions | ||||||
|  | { | ||||||
|  |     /// <summary> | ||||||
|  |     /// Sets the ratio of the layout. | ||||||
|  |     /// </summary> | ||||||
|  |     /// <param name="layout">The layout.</param> | ||||||
|  |     /// <param name="ratio">The ratio.</param> | ||||||
|  |     /// <returns>The same instance so that multiple calls can be chained.</returns> | ||||||
|  |     public static Layout Ratio(this Layout layout, int ratio) | ||||||
|  |     { | ||||||
|  |         if (layout is null) | ||||||
|  |         { | ||||||
|  |             throw new ArgumentNullException(nameof(layout)); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         layout.Ratio = ratio; | ||||||
|  |         return layout; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// Sets the size of the layout. | ||||||
|  |     /// </summary> | ||||||
|  |     /// <param name="layout">The layout.</param> | ||||||
|  |     /// <param name="size">The size.</param> | ||||||
|  |     /// <returns>The same instance so that multiple calls can be chained.</returns> | ||||||
|  |     public static Layout Size(this Layout layout, int size) | ||||||
|  |     { | ||||||
|  |         if (layout is null) | ||||||
|  |         { | ||||||
|  |             throw new ArgumentNullException(nameof(layout)); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         layout.Size = size; | ||||||
|  |         return layout; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// Sets the minimum width of the layout. | ||||||
|  |     /// </summary> | ||||||
|  |     /// <param name="layout">The layout.</param> | ||||||
|  |     /// <param name="size">The size.</param> | ||||||
|  |     /// <returns>The same instance so that multiple calls can be chained.</returns> | ||||||
|  |     public static Layout MinimumSize(this Layout layout, int size) | ||||||
|  |     { | ||||||
|  |         if (layout is null) | ||||||
|  |         { | ||||||
|  |             throw new ArgumentNullException(nameof(layout)); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         layout.MinimumSize = size; | ||||||
|  |         return layout; | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -24,7 +24,7 @@ public static class PanelExtensions | |||||||
|             throw new ArgumentNullException(nameof(text)); |             throw new ArgumentNullException(nameof(text)); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         alignment ??= panel.Header?.Alignment; |         alignment ??= panel.Header?.Justification; | ||||||
|         return Header(panel, new PanelHeader(text, alignment)); |         return Header(panel, new PanelHeader(text, alignment)); | ||||||
|     } |     } | ||||||
|  |  | ||||||
| @@ -44,7 +44,7 @@ public static class PanelExtensions | |||||||
|         if (panel.Header != null) |         if (panel.Header != null) | ||||||
|         { |         { | ||||||
|             // Update existing style |             // Update existing style | ||||||
|             panel.Header.Alignment = alignment; |             panel.Header.Justification = alignment; | ||||||
|         } |         } | ||||||
|         else |         else | ||||||
|         { |         { | ||||||
|   | |||||||
							
								
								
									
										10
									
								
								src/Spectre.Console/Extensions/RenderOptionsExtensions.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								src/Spectre.Console/Extensions/RenderOptionsExtensions.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,10 @@ | |||||||
|  | namespace Spectre.Console; | ||||||
|  |  | ||||||
|  | internal static class RenderOptionsExtensions | ||||||
|  | { | ||||||
|  |     public static BoxBorder GetSafeBorder<T>(this RenderOptions options, T border) | ||||||
|  |         where T : IHasBoxBorder, IHasBorder | ||||||
|  |     { | ||||||
|  |         return BoxExtensions.GetSafeBorder(border.Border, !options.Unicode && border.UseSafeBorder); | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -23,13 +23,13 @@ public static class RenderableExtensions | |||||||
|             throw new ArgumentNullException(nameof(renderable)); |             throw new ArgumentNullException(nameof(renderable)); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         var context = new RenderContext(console.Profile.Capabilities); |         var context = RenderOptions.Create(console, console.Profile.Capabilities); | ||||||
|         var renderables = console.Pipeline.Process(context, new[] { renderable }); |         var renderables = console.Pipeline.Process(context, new[] { renderable }); | ||||||
|  |  | ||||||
|         return GetSegments(console, context, renderables); |         return GetSegments(console, context, renderables); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     private static IEnumerable<Segment> GetSegments(IAnsiConsole console, RenderContext options, IEnumerable<IRenderable> renderables) |     private static IEnumerable<Segment> GetSegments(IAnsiConsole console, RenderOptions options, IEnumerable<IRenderable> renderables) | ||||||
|     { |     { | ||||||
|         var result = new List<Segment>(); |         var result = new List<Segment>(); | ||||||
|         foreach (var renderable in renderables) |         foreach (var renderable in renderables) | ||||||
|   | |||||||
							
								
								
									
										43
									
								
								src/Spectre.Console/Extensions/VisibilityExtensions.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										43
									
								
								src/Spectre.Console/Extensions/VisibilityExtensions.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,43 @@ | |||||||
|  | namespace Spectre.Console; | ||||||
|  |  | ||||||
|  | /// <summary> | ||||||
|  | /// Contains extension methods for <see cref="IHasVisibility"/>. | ||||||
|  | /// </summary> | ||||||
|  | public static class VisibilityExtensions | ||||||
|  | { | ||||||
|  |     /// <summary> | ||||||
|  |     /// Marks the specified object as being invisible. | ||||||
|  |     /// </summary> | ||||||
|  |     /// <typeparam name="T">An object implementing <see cref="IHasVisibility"/>.</typeparam> | ||||||
|  |     /// <param name="obj">The object to hide.</param> | ||||||
|  |     /// <returns>The same instance so that multiple calls can be chained.</returns> | ||||||
|  |     public static T Invisible<T>(this T obj) | ||||||
|  |         where T : class, IHasVisibility | ||||||
|  |     { | ||||||
|  |         if (obj is null) | ||||||
|  |         { | ||||||
|  |             throw new ArgumentNullException(nameof(obj)); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         obj.IsVisible = false; | ||||||
|  |         return obj; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// Marks the specified object as being visible. | ||||||
|  |     /// </summary> | ||||||
|  |     /// <typeparam name="T">An object implementing <see cref="IHasVisibility"/>.</typeparam> | ||||||
|  |     /// <param name="obj">The object to show.</param> | ||||||
|  |     /// <returns>The same instance so that multiple calls can be chained.</returns> | ||||||
|  |     public static T Visible<T>(this T obj) | ||||||
|  |         where T : class, IHasVisibility | ||||||
|  |     { | ||||||
|  |         if (obj is null) | ||||||
|  |         { | ||||||
|  |             throw new ArgumentNullException(nameof(obj)); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         obj.IsVisible = true; | ||||||
|  |         return obj; | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										22
									
								
								src/Spectre.Console/HorizontalAlignment.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								src/Spectre.Console/HorizontalAlignment.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,22 @@ | |||||||
|  | namespace Spectre.Console; | ||||||
|  |  | ||||||
|  | /// <summary> | ||||||
|  | /// Represents horizontal alignment. | ||||||
|  | /// </summary> | ||||||
|  | public enum HorizontalAlignment | ||||||
|  | { | ||||||
|  |     /// <summary> | ||||||
|  |     /// Left aligned. | ||||||
|  |     /// </summary> | ||||||
|  |     Left, | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// Centered. | ||||||
|  |     /// </summary> | ||||||
|  |     Center, | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// Right aligned. | ||||||
|  |     /// </summary> | ||||||
|  |     Right, | ||||||
|  | } | ||||||
| @@ -9,4 +9,4 @@ public interface IAlignable | |||||||
|     /// Gets or sets the alignment. |     /// Gets or sets the alignment. | ||||||
|     /// </summary> |     /// </summary> | ||||||
|     Justify? Alignment { get; set; } |     Justify? Alignment { get; set; } | ||||||
| } | } | ||||||
|   | |||||||
							
								
								
									
										12
									
								
								src/Spectre.Console/IHasJustification.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								src/Spectre.Console/IHasJustification.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,12 @@ | |||||||
|  | namespace Spectre.Console; | ||||||
|  |  | ||||||
|  | /// <summary> | ||||||
|  | /// Represents something that has justification. | ||||||
|  | /// </summary> | ||||||
|  | public interface IHasJustification | ||||||
|  | { | ||||||
|  |     /// <summary> | ||||||
|  |     /// Gets or sets the justification. | ||||||
|  |     /// </summary> | ||||||
|  |     Justify? Justification { get; set; } | ||||||
|  | } | ||||||
							
								
								
									
										13
									
								
								src/Spectre.Console/IHasVisibility.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								src/Spectre.Console/IHasVisibility.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,13 @@ | |||||||
|  | namespace Spectre.Console; | ||||||
|  |  | ||||||
|  | /// <summary> | ||||||
|  | /// Represents something that can be hidden. | ||||||
|  | /// </summary> | ||||||
|  | public interface IHasVisibility | ||||||
|  | { | ||||||
|  |     /// <summary> | ||||||
|  |     /// Gets or sets a value indicating whether or not the object should | ||||||
|  |     /// be visible or not. | ||||||
|  |     /// </summary> | ||||||
|  |     bool IsVisible { get; set; } | ||||||
|  | } | ||||||
| @@ -8,16 +8,16 @@ public interface IRenderable | |||||||
|     /// <summary> |     /// <summary> | ||||||
|     /// Measures the renderable object. |     /// Measures the renderable object. | ||||||
|     /// </summary> |     /// </summary> | ||||||
|     /// <param name="context">The render context.</param> |     /// <param name="options">The render options.</param> | ||||||
|     /// <param name="maxWidth">The maximum allowed width.</param> |     /// <param name="maxWidth">The maximum allowed width.</param> | ||||||
|     /// <returns>The minimum and maximum width of the object.</returns> |     /// <returns>The minimum and maximum width of the object.</returns> | ||||||
|     Measurement Measure(RenderContext context, int maxWidth); |     Measurement Measure(RenderOptions options, int maxWidth); | ||||||
|  |  | ||||||
|     /// <summary> |     /// <summary> | ||||||
|     /// Renders the object. |     /// Renders the object. | ||||||
|     /// </summary> |     /// </summary> | ||||||
|     /// <param name="context">The render context.</param> |     /// <param name="options">The render options.</param> | ||||||
|     /// <param name="maxWidth">The maximum allowed width.</param> |     /// <param name="maxWidth">The maximum allowed width.</param> | ||||||
|     /// <returns>A collection of segments.</returns> |     /// <returns>A collection of segments.</returns> | ||||||
|     IEnumerable<Segment> Render(RenderContext context, int maxWidth); |     IEnumerable<Segment> Render(RenderOptions options, int maxWidth); | ||||||
| } | } | ||||||
| @@ -89,4 +89,51 @@ internal static class Aligner | |||||||
|                 throw new NotSupportedException("Unknown alignment"); |                 throw new NotSupportedException("Unknown alignment"); | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     public static void AlignHorizontally<T>(T segments, HorizontalAlignment alignment, int maxWidth) | ||||||
|  |         where T : List<Segment> | ||||||
|  |     { | ||||||
|  |         var width = Segment.CellCount(segments); | ||||||
|  |         if (width >= maxWidth) | ||||||
|  |         { | ||||||
|  |             return; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         switch (alignment) | ||||||
|  |         { | ||||||
|  |             case HorizontalAlignment.Left: | ||||||
|  |                 { | ||||||
|  |                     var diff = maxWidth - width; | ||||||
|  |                     segments.Add(Segment.Padding(diff)); | ||||||
|  |                     break; | ||||||
|  |                 } | ||||||
|  |  | ||||||
|  |             case HorizontalAlignment.Right: | ||||||
|  |                 { | ||||||
|  |                     var diff = maxWidth - width; | ||||||
|  |                     segments.Insert(0, Segment.Padding(diff)); | ||||||
|  |                     break; | ||||||
|  |                 } | ||||||
|  |  | ||||||
|  |             case HorizontalAlignment.Center: | ||||||
|  |                 { | ||||||
|  |                     // Left side. | ||||||
|  |                     var diff = (maxWidth - width) / 2; | ||||||
|  |                     segments.Insert(0, Segment.Padding(diff)); | ||||||
|  |  | ||||||
|  |                     // Right side | ||||||
|  |                     segments.Add(Segment.Padding(diff)); | ||||||
|  |                     var remainder = (maxWidth - width) % 2; | ||||||
|  |                     if (remainder != 0) | ||||||
|  |                     { | ||||||
|  |                         segments.Add(Segment.Padding(remainder)); | ||||||
|  |                     } | ||||||
|  |  | ||||||
|  |                     break; | ||||||
|  |                 } | ||||||
|  |  | ||||||
|  |             default: | ||||||
|  |                 throw new NotSupportedException("Unknown alignment"); | ||||||
|  |         } | ||||||
|  |     } | ||||||
| } | } | ||||||
							
								
								
									
										22
									
								
								src/Spectre.Console/Internal/IRatioResolvable.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								src/Spectre.Console/Internal/IRatioResolvable.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,22 @@ | |||||||
|  | namespace Spectre.Console; | ||||||
|  |  | ||||||
|  | /// <summary> | ||||||
|  | /// Represents something that can be used to resolve ratios. | ||||||
|  | /// </summary> | ||||||
|  | internal interface IRatioResolvable | ||||||
|  | { | ||||||
|  |     /// <summary> | ||||||
|  |     /// Gets the ratio. | ||||||
|  |     /// </summary> | ||||||
|  |     int Ratio { get; } | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// Gets the size. | ||||||
|  |     /// </summary> | ||||||
|  |     int? Size { get; } | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// Gets the minimum size. | ||||||
|  |     /// </summary> | ||||||
|  |     int MinimumSize { get; } | ||||||
|  | } | ||||||
							
								
								
									
										15
									
								
								src/Spectre.Console/Internal/Polyfill/IsExternalInit.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								src/Spectre.Console/Internal/Polyfill/IsExternalInit.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,15 @@ | |||||||
|  | #if NETSTANDARD2_0 | ||||||
|  | using System.ComponentModel; | ||||||
|  |  | ||||||
|  | namespace System.Runtime.CompilerServices | ||||||
|  | { | ||||||
|  |     /// <summary> | ||||||
|  |     /// Reserved to be used by the compiler for tracking metadata. | ||||||
|  |     /// This class should not be used by developers in source code. | ||||||
|  |     /// </summary> | ||||||
|  |     [EditorBrowsable(EditorBrowsableState.Never)] | ||||||
|  |     internal static class IsExternalInit | ||||||
|  |     { | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | #endif | ||||||
| @@ -5,6 +5,78 @@ namespace Spectre.Console; | |||||||
|  |  | ||||||
| internal static class Ratio | internal static class Ratio | ||||||
| { | { | ||||||
|  |     public static List<int> Resolve(int total, IEnumerable<IRatioResolvable> edges) | ||||||
|  |     { | ||||||
|  |         static (int Div, float Mod) DivMod(float x, float y) | ||||||
|  |         { | ||||||
|  |             return ((int)(x / y), x % y); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         static int? GetEdgeWidth(IRatioResolvable edge) | ||||||
|  |         { | ||||||
|  |             if (edge.Size != null && edge.Size < edge.MinimumSize) | ||||||
|  |             { | ||||||
|  |                 return edge.MinimumSize; | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             return edge.Size; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         var sizes = edges.Select(x => GetEdgeWidth(x)).ToArray(); | ||||||
|  |  | ||||||
|  |         while (sizes.Any(s => s == null)) | ||||||
|  |         { | ||||||
|  |             // Get all edges and map them back to their index. | ||||||
|  |             // Ignore edges which have a explicit size. | ||||||
|  |             var flexibleEdges = sizes.Zip(edges, (a, b) => (Size: a, Edge: b)) | ||||||
|  |                 .Enumerate() | ||||||
|  |                 .Select(x => (x.Index, x.Item.Size, x.Item.Edge)) | ||||||
|  |                 .Where(x => x.Size == null) | ||||||
|  |                 .ToList(); | ||||||
|  |  | ||||||
|  |             // Get the remaining space | ||||||
|  |             var remaining = total - sizes.Select(size => size ?? 0).Sum(); | ||||||
|  |             if (remaining <= 0) | ||||||
|  |             { | ||||||
|  |                 // No more room for flexible edges. | ||||||
|  |                 return sizes | ||||||
|  |                     .Zip(edges, (size, edge) => (Size: size, Edge: edge)) | ||||||
|  |                     .Select(zip => zip.Size ?? zip.Edge.MinimumSize) | ||||||
|  |                     .Select(size => size > 0 ? size : 1) | ||||||
|  |                     .ToList(); | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             var portion = (float)remaining / flexibleEdges.Sum(x => Math.Max(1, x.Edge.Ratio)); | ||||||
|  |  | ||||||
|  |             var invalidate = false; | ||||||
|  |             foreach (var (index, size, edge) in flexibleEdges) | ||||||
|  |             { | ||||||
|  |                 if (portion * edge.Ratio <= edge.MinimumSize) | ||||||
|  |                 { | ||||||
|  |                     sizes[index] = edge.MinimumSize; | ||||||
|  |  | ||||||
|  |                     // New fixed size will invalidate calculations, | ||||||
|  |                     // so we need to repeat the process | ||||||
|  |                     invalidate = true; | ||||||
|  |                     break; | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             if (!invalidate) | ||||||
|  |             { | ||||||
|  |                 var remainder = 0f; | ||||||
|  |                 foreach (var flexibleEdge in flexibleEdges) | ||||||
|  |                 { | ||||||
|  |                     var (div, mod) = DivMod((portion * flexibleEdge.Edge.Ratio) + remainder, 1); | ||||||
|  |                     remainder = mod; | ||||||
|  |                     sizes[flexibleEdge.Index] = div; | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         return sizes.Select(x => x ?? 1).ToList(); | ||||||
|  |     } | ||||||
|  |  | ||||||
|     public static List<int> Reduce(int total, List<int> ratios, List<int> maximums, List<int> values) |     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(); |         ratios = ratios.Zip(maximums, (a, b) => (ratio: a, max: b)).Select(a => a.max > 0 ? a.ratio : 0).ToList(); | ||||||
|   | |||||||
| @@ -4,7 +4,7 @@ internal sealed class HtmlEncoder : IAnsiConsoleEncoder | |||||||
| { | { | ||||||
|     public string Encode(IAnsiConsole console, IEnumerable<IRenderable> renderables) |     public string Encode(IAnsiConsole console, IEnumerable<IRenderable> renderables) | ||||||
|     { |     { | ||||||
|         var context = new RenderContext(new EncoderCapabilities(ColorSystem.TrueColor)); |         var context = RenderOptions.Create(console, new EncoderCapabilities(ColorSystem.TrueColor)); | ||||||
|         var builder = new StringBuilder(); |         var builder = new StringBuilder(); | ||||||
|  |  | ||||||
|         builder.Append("<pre style=\"font-size:90%;font-family:consolas,'Courier New',monospace\">\n"); |         builder.Append("<pre style=\"font-size:90%;font-family:consolas,'Courier New',monospace\">\n"); | ||||||
|   | |||||||
| @@ -4,7 +4,7 @@ internal sealed class TextEncoder : IAnsiConsoleEncoder | |||||||
| { | { | ||||||
|     public string Encode(IAnsiConsole console, IEnumerable<IRenderable> renderables) |     public string Encode(IAnsiConsole console, IEnumerable<IRenderable> renderables) | ||||||
|     { |     { | ||||||
|         var context = new RenderContext(new EncoderCapabilities(ColorSystem.TrueColor)); |         var context = RenderOptions.Create(console, new EncoderCapabilities(ColorSystem.TrueColor)); | ||||||
|         var builder = new StringBuilder(); |         var builder = new StringBuilder(); | ||||||
|  |  | ||||||
|         foreach (var renderable in renderables) |         foreach (var renderable in renderables) | ||||||
|   | |||||||
| @@ -6,12 +6,12 @@ namespace Spectre.Console; | |||||||
| public enum Justify | public enum Justify | ||||||
| { | { | ||||||
|     /// <summary> |     /// <summary> | ||||||
|     /// Left aligned. |     /// Left justified. | ||||||
|     /// </summary> |     /// </summary> | ||||||
|     Left = 0, |     Left = 0, | ||||||
|  |  | ||||||
|     /// <summary> |     /// <summary> | ||||||
|     /// Right aligned. |     /// Right justified. | ||||||
|     /// </summary> |     /// </summary> | ||||||
|     Right = 1, |     Right = 1, | ||||||
|  |  | ||||||
|   | |||||||
| @@ -41,7 +41,7 @@ internal sealed class LiveDisplayRenderer : IRenderHook | |||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     public IEnumerable<IRenderable> Process(RenderContext context, IEnumerable<IRenderable> renderables) |     public IEnumerable<IRenderable> Process(RenderOptions options, IEnumerable<IRenderable> renderables) | ||||||
|     { |     { | ||||||
|         lock (_context.Lock) |         lock (_context.Lock) | ||||||
|         { |         { | ||||||
|   | |||||||
| @@ -67,7 +67,7 @@ internal sealed class LiveRenderable : Renderable | |||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     protected override IEnumerable<Segment> Render(RenderContext context, int maxWidth) |     protected override IEnumerable<Segment> Render(RenderOptions options, int maxWidth) | ||||||
|     { |     { | ||||||
|         lock (_lock) |         lock (_lock) | ||||||
|         { |         { | ||||||
| @@ -75,10 +75,10 @@ internal sealed class LiveRenderable : Renderable | |||||||
|  |  | ||||||
|             if (_renderable != null) |             if (_renderable != null) | ||||||
|             { |             { | ||||||
|                 var segments = _renderable.Render(context, maxWidth); |                 var segments = _renderable.Render(options, maxWidth); | ||||||
|                 var lines = Segment.SplitLines(segments); |                 var lines = Segment.SplitLines(segments); | ||||||
|  |  | ||||||
|                 var shape = SegmentShape.Calculate(context, lines); |                 var shape = SegmentShape.Calculate(options, lines); | ||||||
|                 if (shape.Height > _console.Profile.Height) |                 if (shape.Height > _console.Profile.Height) | ||||||
|                 { |                 { | ||||||
|                     if (Overflow == VerticalOverflow.Crop) |                     if (Overflow == VerticalOverflow.Crop) | ||||||
| @@ -97,12 +97,12 @@ internal sealed class LiveRenderable : Renderable | |||||||
|                             lines.RemoveRange(0, start); |                             lines.RemoveRange(0, start); | ||||||
|                         } |                         } | ||||||
|  |  | ||||||
|                         shape = SegmentShape.Calculate(context, lines); |                         shape = SegmentShape.Calculate(options, lines); | ||||||
|                     } |                     } | ||||||
|                     else if (Overflow == VerticalOverflow.Ellipsis) |                     else if (Overflow == VerticalOverflow.Ellipsis) | ||||||
|                     { |                     { | ||||||
|                         var ellipsisText = _console.Profile.Capabilities.Unicode ? "…" : "..."; |                         var ellipsisText = _console.Profile.Capabilities.Unicode ? "…" : "..."; | ||||||
|                         var ellipsis = new SegmentLine(((IRenderable)new Markup($"[yellow]{ellipsisText}[/]")).Render(context, maxWidth)); |                         var ellipsis = new SegmentLine(((IRenderable)new Markup($"[yellow]{ellipsisText}[/]")).Render(options, maxWidth)); | ||||||
|  |  | ||||||
|                         if (OverflowCropping == VerticalOverflowCropping.Bottom) |                         if (OverflowCropping == VerticalOverflowCropping.Bottom) | ||||||
|                         { |                         { | ||||||
| @@ -120,14 +120,14 @@ internal sealed class LiveRenderable : Renderable | |||||||
|                             lines.Insert(0, ellipsis); |                             lines.Insert(0, ellipsis); | ||||||
|                         } |                         } | ||||||
|  |  | ||||||
|                         shape = SegmentShape.Calculate(context, lines); |                         shape = SegmentShape.Calculate(options, lines); | ||||||
|                     } |                     } | ||||||
|  |  | ||||||
|                     DidOverflow = true; |                     DidOverflow = true; | ||||||
|                 } |                 } | ||||||
|  |  | ||||||
|                 _shape = _shape == null ? shape : _shape.Value.Inflate(shape); |                 _shape = _shape == null ? shape : _shape.Value.Inflate(shape); | ||||||
|                 _shape.Value.Apply(context, ref lines); |                 _shape.Value.Apply(options, ref lines); | ||||||
|  |  | ||||||
|                 foreach (var (_, _, last, line) in lines.Enumerate()) |                 foreach (var (_, _, last, line) in lines.Enumerate()) | ||||||
|                 { |                 { | ||||||
|   | |||||||
| @@ -11,7 +11,7 @@ public sealed class DownloadedColumn : ProgressColumn | |||||||
|     public CultureInfo? Culture { get; set; } |     public CultureInfo? Culture { get; set; } | ||||||
|  |  | ||||||
|     /// <inheritdoc/> |     /// <inheritdoc/> | ||||||
|     public override IRenderable Render(RenderContext context, ProgressTask task, TimeSpan deltaTime) |     public override IRenderable Render(RenderOptions options, ProgressTask task, TimeSpan deltaTime) | ||||||
|     { |     { | ||||||
|         var total = new FileSize(task.MaxValue); |         var total = new FileSize(task.MaxValue); | ||||||
|  |  | ||||||
|   | |||||||
| @@ -14,7 +14,7 @@ public sealed class ElapsedTimeColumn : ProgressColumn | |||||||
|     public Style Style { get; set; } = new Style(foreground: Color.Blue); |     public Style Style { get; set; } = new Style(foreground: Color.Blue); | ||||||
|  |  | ||||||
|     /// <inheritdoc/> |     /// <inheritdoc/> | ||||||
|     public override IRenderable Render(RenderContext context, ProgressTask task, TimeSpan deltaTime) |     public override IRenderable Render(RenderOptions options, ProgressTask task, TimeSpan deltaTime) | ||||||
|     { |     { | ||||||
|         var elapsed = task.ElapsedTime; |         var elapsed = task.ElapsedTime; | ||||||
|         if (elapsed == null) |         if (elapsed == null) | ||||||
| @@ -31,7 +31,7 @@ public sealed class ElapsedTimeColumn : ProgressColumn | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     /// <inheritdoc/> |     /// <inheritdoc/> | ||||||
|     public override int? GetColumnWidth(RenderContext context) |     public override int? GetColumnWidth(RenderOptions options) | ||||||
|     { |     { | ||||||
|         return 8; |         return 8; | ||||||
|     } |     } | ||||||
|   | |||||||
| @@ -16,15 +16,15 @@ public sealed class PercentageColumn : ProgressColumn | |||||||
|     public Style CompletedStyle { get; set; } = new Style(foreground: Color.Green); |     public Style CompletedStyle { get; set; } = new Style(foreground: Color.Green); | ||||||
|  |  | ||||||
|     /// <inheritdoc/> |     /// <inheritdoc/> | ||||||
|     public override IRenderable Render(RenderContext context, ProgressTask task, TimeSpan deltaTime) |     public override IRenderable Render(RenderOptions options, ProgressTask task, TimeSpan deltaTime) | ||||||
|     { |     { | ||||||
|         var percentage = (int)task.Percentage; |         var percentage = (int)task.Percentage; | ||||||
|         var style = percentage == 100 ? CompletedStyle : Style ?? Style.Plain; |         var style = percentage == 100 ? CompletedStyle : Style ?? Style.Plain; | ||||||
|         return new Text($"{percentage}%", style).RightAligned(); |         return new Text($"{percentage}%", style).RightJustified(); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     /// <inheritdoc/> |     /// <inheritdoc/> | ||||||
|     public override int? GetColumnWidth(RenderContext context) |     public override int? GetColumnWidth(RenderOptions options) | ||||||
|     { |     { | ||||||
|         return 4; |         return 4; | ||||||
|     } |     } | ||||||
|   | |||||||
| @@ -31,7 +31,7 @@ public sealed class ProgressBarColumn : ProgressColumn | |||||||
|     public Style IndeterminateStyle { get; set; } = ProgressBar.DefaultPulseStyle; |     public Style IndeterminateStyle { get; set; } = ProgressBar.DefaultPulseStyle; | ||||||
|  |  | ||||||
|     /// <inheritdoc/> |     /// <inheritdoc/> | ||||||
|     public override IRenderable Render(RenderContext context, ProgressTask task, TimeSpan deltaTime) |     public override IRenderable Render(RenderOptions options, ProgressTask task, TimeSpan deltaTime) | ||||||
|     { |     { | ||||||
|         return new ProgressBar |         return new ProgressBar | ||||||
|         { |         { | ||||||
|   | |||||||
| @@ -14,7 +14,7 @@ public sealed class RemainingTimeColumn : ProgressColumn | |||||||
|     public Style Style { get; set; } = new Style(foreground: Color.Blue); |     public Style Style { get; set; } = new Style(foreground: Color.Blue); | ||||||
|  |  | ||||||
|     /// <inheritdoc/> |     /// <inheritdoc/> | ||||||
|     public override IRenderable Render(RenderContext context, ProgressTask task, TimeSpan deltaTime) |     public override IRenderable Render(RenderOptions options, ProgressTask task, TimeSpan deltaTime) | ||||||
|     { |     { | ||||||
|         var remaining = task.RemainingTime; |         var remaining = task.RemainingTime; | ||||||
|         if (remaining == null) |         if (remaining == null) | ||||||
| @@ -31,7 +31,7 @@ public sealed class RemainingTimeColumn : ProgressColumn | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     /// <inheritdoc/> |     /// <inheritdoc/> | ||||||
|     public override int? GetColumnWidth(RenderContext context) |     public override int? GetColumnWidth(RenderOptions options) | ||||||
|     { |     { | ||||||
|         return 8; |         return 8; | ||||||
|     } |     } | ||||||
|   | |||||||
| @@ -95,9 +95,9 @@ public sealed class SpinnerColumn : ProgressColumn | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     /// <inheritdoc/> |     /// <inheritdoc/> | ||||||
|     public override IRenderable Render(RenderContext context, ProgressTask task, TimeSpan deltaTime) |     public override IRenderable Render(RenderOptions options, ProgressTask task, TimeSpan deltaTime) | ||||||
|     { |     { | ||||||
|         var useAscii = !context.Unicode && _spinner.IsUnicode; |         var useAscii = !options.Unicode && _spinner.IsUnicode; | ||||||
|         var spinner = useAscii ? Spinner.Known.Ascii : _spinner ?? Spinner.Known.Default; |         var spinner = useAscii ? Spinner.Known.Ascii : _spinner ?? Spinner.Known.Default; | ||||||
|  |  | ||||||
|         if (!task.IsStarted) |         if (!task.IsStarted) | ||||||
| @@ -123,24 +123,24 @@ public sealed class SpinnerColumn : ProgressColumn | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     /// <inheritdoc/> |     /// <inheritdoc/> | ||||||
|     public override int? GetColumnWidth(RenderContext context) |     public override int? GetColumnWidth(RenderOptions options) | ||||||
|     { |     { | ||||||
|         return GetMaxWidth(context); |         return GetMaxWidth(options); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     private int GetMaxWidth(RenderContext context) |     private int GetMaxWidth(RenderOptions options) | ||||||
|     { |     { | ||||||
|         lock (_lock) |         lock (_lock) | ||||||
|         { |         { | ||||||
|             if (_maxWidth == null) |             if (_maxWidth == null) | ||||||
|             { |             { | ||||||
|                 var useAscii = !context.Unicode && _spinner.IsUnicode; |                 var useAscii = !options.Unicode && _spinner.IsUnicode; | ||||||
|                 var spinner = useAscii ? Spinner.Known.Ascii : _spinner ?? Spinner.Known.Default; |                 var spinner = useAscii ? Spinner.Known.Ascii : _spinner ?? Spinner.Known.Default; | ||||||
|  |  | ||||||
|                 _maxWidth = Math.Max( |                 _maxWidth = Math.Max( | ||||||
|                     Math.Max( |                     Math.Max( | ||||||
|                     ((IRenderable)new Markup(PendingText ?? " ")).Measure(context, int.MaxValue).Max, |                     ((IRenderable)new Markup(PendingText ?? " ")).Measure(options, int.MaxValue).Max, | ||||||
|                     ((IRenderable)new Markup(CompletedText ?? " ")).Measure(context, int.MaxValue).Max), |                     ((IRenderable)new Markup(CompletedText ?? " ")).Measure(options, int.MaxValue).Max), | ||||||
|                     spinner.Frames.Max(frame => Cell.GetCellLength(frame))); |                     spinner.Frames.Max(frame => Cell.GetCellLength(frame))); | ||||||
|             } |             } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -14,9 +14,9 @@ public sealed class TaskDescriptionColumn : ProgressColumn | |||||||
|     public Justify Alignment { get; set; } = Justify.Right; |     public Justify Alignment { get; set; } = Justify.Right; | ||||||
|  |  | ||||||
|     /// <inheritdoc/> |     /// <inheritdoc/> | ||||||
|     public override IRenderable Render(RenderContext context, ProgressTask task, TimeSpan deltaTime) |     public override IRenderable Render(RenderOptions options, ProgressTask task, TimeSpan deltaTime) | ||||||
|     { |     { | ||||||
|         var text = task.Description?.RemoveNewLines()?.Trim(); |         var text = task.Description?.RemoveNewLines()?.Trim(); | ||||||
|         return new Markup(text ?? string.Empty).Overflow(Overflow.Ellipsis).Alignment(Alignment); |         return new Markup(text ?? string.Empty).Overflow(Overflow.Ellipsis).Justify(Alignment); | ||||||
|     } |     } | ||||||
| } | } | ||||||
| @@ -11,7 +11,7 @@ public sealed class TransferSpeedColumn : ProgressColumn | |||||||
|     public CultureInfo? Culture { get; set; } |     public CultureInfo? Culture { get; set; } | ||||||
|  |  | ||||||
|     /// <inheritdoc/> |     /// <inheritdoc/> | ||||||
|     public override IRenderable Render(RenderContext context, ProgressTask task, TimeSpan deltaTime) |     public override IRenderable Render(RenderOptions options, ProgressTask task, TimeSpan deltaTime) | ||||||
|     { |     { | ||||||
|         if (task.Speed == null) |         if (task.Speed == null) | ||||||
|         { |         { | ||||||
|   | |||||||
| @@ -13,18 +13,18 @@ public abstract class ProgressColumn | |||||||
|     /// <summary> |     /// <summary> | ||||||
|     /// Gets a renderable representing the column. |     /// Gets a renderable representing the column. | ||||||
|     /// </summary> |     /// </summary> | ||||||
|     /// <param name="context">The render context.</param> |     /// <param name="options">The render options.</param> | ||||||
|     /// <param name="task">The task.</param> |     /// <param name="task">The task.</param> | ||||||
|     /// <param name="deltaTime">The elapsed time since last call.</param> |     /// <param name="deltaTime">The elapsed time since last call.</param> | ||||||
|     /// <returns>A renderable representing the column.</returns> |     /// <returns>A renderable representing the column.</returns> | ||||||
|     public abstract IRenderable Render(RenderContext context, ProgressTask task, TimeSpan deltaTime); |     public abstract IRenderable Render(RenderOptions options, ProgressTask task, TimeSpan deltaTime); | ||||||
|  |  | ||||||
|     /// <summary> |     /// <summary> | ||||||
|     /// Gets the width of the column. |     /// Gets the width of the column. | ||||||
|     /// </summary> |     /// </summary> | ||||||
|     /// <param name="context">The context.</param> |     /// <param name="options">The render options.</param> | ||||||
|     /// <returns>The width of the column, or <c>null</c> to calculate.</returns> |     /// <returns>The width of the column, or <c>null</c> to calculate.</returns> | ||||||
|     public virtual int? GetColumnWidth(RenderContext context) |     public virtual int? GetColumnWidth(RenderOptions options) | ||||||
|     { |     { | ||||||
|         return null; |         return null; | ||||||
|     } |     } | ||||||
|   | |||||||
| @@ -13,5 +13,5 @@ internal abstract class ProgressRenderer : IRenderHook | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     public abstract void Update(ProgressContext context); |     public abstract void Update(ProgressContext context); | ||||||
|     public abstract IEnumerable<IRenderable> Process(RenderContext context, IEnumerable<IRenderable> renderables); |     public abstract IEnumerable<IRenderable> Process(RenderOptions options, IEnumerable<IRenderable> renderables); | ||||||
| } | } | ||||||
| @@ -64,7 +64,7 @@ internal sealed class DefaultProgressRenderer : ProgressRenderer | |||||||
|                 _stopwatch.Start(); |                 _stopwatch.Start(); | ||||||
|             } |             } | ||||||
|  |  | ||||||
|             var renderContext = new RenderContext(_console.Profile.Capabilities); |             var renderContext = RenderOptions.Create(_console, _console.Profile.Capabilities); | ||||||
|  |  | ||||||
|             var delta = _stopwatch.Elapsed - _lastUpdate; |             var delta = _stopwatch.Elapsed - _lastUpdate; | ||||||
|             _lastUpdate = _stopwatch.Elapsed; |             _lastUpdate = _stopwatch.Elapsed; | ||||||
| @@ -105,7 +105,7 @@ internal sealed class DefaultProgressRenderer : ProgressRenderer | |||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     public override IEnumerable<IRenderable> Process(RenderContext context, IEnumerable<IRenderable> renderables) |     public override IEnumerable<IRenderable> Process(RenderOptions options, IEnumerable<IRenderable> renderables) | ||||||
|     { |     { | ||||||
|         lock (_lock) |         lock (_lock) | ||||||
|         { |         { | ||||||
|   | |||||||
| @@ -58,7 +58,7 @@ internal sealed class FallbackProgressRenderer : ProgressRenderer | |||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     public override IEnumerable<IRenderable> Process(RenderContext context, IEnumerable<IRenderable> renderables) |     public override IEnumerable<IRenderable> Process(RenderOptions options, IEnumerable<IRenderable> renderables) | ||||||
|     { |     { | ||||||
|         lock (_lock) |         lock (_lock) | ||||||
|         { |         { | ||||||
|   | |||||||
| @@ -34,7 +34,7 @@ internal sealed class FallbackStatusRenderer : ProgressRenderer | |||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     public override IEnumerable<IRenderable> Process(RenderContext context, IEnumerable<IRenderable> renderables) |     public override IEnumerable<IRenderable> Process(RenderOptions options, IEnumerable<IRenderable> renderables) | ||||||
|     { |     { | ||||||
|         lock (_lock) |         lock (_lock) | ||||||
|         { |         { | ||||||
|   | |||||||
| @@ -32,7 +32,7 @@ internal sealed class ListPromptRenderHook<T> : IRenderHook | |||||||
|         _console.Write(new ControlCode(string.Empty)); |         _console.Write(new ControlCode(string.Empty)); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     public IEnumerable<IRenderable> Process(RenderContext context, IEnumerable<IRenderable> renderables) |     public IEnumerable<IRenderable> Process(RenderOptions options, IEnumerable<IRenderable> renderables) | ||||||
|     { |     { | ||||||
|         lock (_lock) |         lock (_lock) | ||||||
|         { |         { | ||||||
|   | |||||||
							
								
								
									
										43
									
								
								src/Spectre.Console/Region.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										43
									
								
								src/Spectre.Console/Region.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,43 @@ | |||||||
|  | namespace Spectre.Console; | ||||||
|  |  | ||||||
|  | /// <summary> | ||||||
|  | /// Represents a region. | ||||||
|  | /// </summary> | ||||||
|  | [DebuggerDisplay("[X={X,nq}, Y={Y,nq}, W={Width,nq}, H={Height,nq}]")] | ||||||
|  | public struct Region | ||||||
|  | { | ||||||
|  |     /// <summary> | ||||||
|  |     /// Gets the x-coordinate. | ||||||
|  |     /// </summary> | ||||||
|  |     public int X { get; } | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// Gets the y-coordinate. | ||||||
|  |     /// </summary> | ||||||
|  |     public int Y { get; } | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// Gets the width. | ||||||
|  |     /// </summary> | ||||||
|  |     public int Width { get; } | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// Gets the height. | ||||||
|  |     /// </summary> | ||||||
|  |     public int Height { get; } | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// Initializes a new instance of the <see cref="Region"/> struct. | ||||||
|  |     /// </summary> | ||||||
|  |     /// <param name="x">The x-coordinate.</param> | ||||||
|  |     /// <param name="y">The y-coordinate.</param> | ||||||
|  |     /// <param name="width">The width.</param> | ||||||
|  |     /// <param name="height">The height.</param> | ||||||
|  |     public Region(int x, int y, int width, int height) | ||||||
|  |     { | ||||||
|  |         X = x; | ||||||
|  |         Y = y; | ||||||
|  |         Width = width; | ||||||
|  |         Height = height; | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -8,8 +8,8 @@ public interface IRenderHook | |||||||
|     /// <summary> |     /// <summary> | ||||||
|     /// Processes the specified renderables. |     /// Processes the specified renderables. | ||||||
|     /// </summary> |     /// </summary> | ||||||
|     /// <param name="context">The render context.</param> |     /// <param name="options">The render options.</param> | ||||||
|     /// <param name="renderables">The renderables to process.</param> |     /// <param name="renderables">The renderables to process.</param> | ||||||
|     /// <returns>The processed renderables.</returns> |     /// <returns>The processed renderables.</returns> | ||||||
|     IEnumerable<IRenderable> Process(RenderContext context, IEnumerable<IRenderable> renderables); |     IEnumerable<IRenderable> Process(RenderOptions options, IEnumerable<IRenderable> renderables); | ||||||
| } | } | ||||||
| @@ -10,13 +10,13 @@ public abstract class JustInTimeRenderable : Renderable | |||||||
|     private IRenderable? _rendered; |     private IRenderable? _rendered; | ||||||
|  |  | ||||||
|     /// <inheritdoc/> |     /// <inheritdoc/> | ||||||
|     protected sealed override Measurement Measure(RenderContext context, int maxWidth) |     protected sealed override Measurement Measure(RenderOptions context, int maxWidth) | ||||||
|     { |     { | ||||||
|         return GetInner().Measure(context, maxWidth); |         return GetInner().Measure(context, maxWidth); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     /// <inheritdoc/> |     /// <inheritdoc/> | ||||||
|     protected sealed override IEnumerable<Segment> Render(RenderContext context, int width) |     protected sealed override IEnumerable<Segment> Render(RenderOptions context, int width) | ||||||
|     { |     { | ||||||
|         return GetInner().Render(context, width); |         return GetInner().Render(context, width); | ||||||
|     } |     } | ||||||
|   | |||||||
| @@ -1,78 +0,0 @@ | |||||||
| namespace Spectre.Console.Rendering; |  | ||||||
|  |  | ||||||
| /// <summary> |  | ||||||
| /// Represents a render context. |  | ||||||
| /// </summary> |  | ||||||
| public sealed class RenderContext |  | ||||||
| { |  | ||||||
|     private readonly IReadOnlyCapabilities _capabilities; |  | ||||||
|  |  | ||||||
|     /// <summary> |  | ||||||
|     /// Gets the current color system. |  | ||||||
|     /// </summary> |  | ||||||
|     public ColorSystem ColorSystem => _capabilities.ColorSystem; |  | ||||||
|  |  | ||||||
|     /// <summary> |  | ||||||
|     /// Gets a value indicating whether or not VT/Ansi codes are supported. |  | ||||||
|     /// </summary> |  | ||||||
|     public bool Ansi => _capabilities.Ansi; |  | ||||||
|  |  | ||||||
|     /// <summary> |  | ||||||
|     /// Gets a value indicating whether or not unicode is supported. |  | ||||||
|     /// </summary> |  | ||||||
|     public bool Unicode => _capabilities.Unicode; |  | ||||||
|  |  | ||||||
|     /// <summary> |  | ||||||
|     /// Gets the current justification. |  | ||||||
|     /// </summary> |  | ||||||
|     public Justify? Justification { get; } |  | ||||||
|  |  | ||||||
|     /// <summary> |  | ||||||
|     /// Gets a value indicating whether the context want items to render without |  | ||||||
|     /// line breaks and return a single line where applicable. |  | ||||||
|     /// </summary> |  | ||||||
|     internal bool SingleLine { get; } |  | ||||||
|  |  | ||||||
|     /// <summary> |  | ||||||
|     /// Initializes a new instance of the <see cref="RenderContext"/> class. |  | ||||||
|     /// </summary> |  | ||||||
|     /// <param name="capabilities">The capabilities.</param> |  | ||||||
|     /// <param name="justification">The justification.</param> |  | ||||||
|     public RenderContext(IReadOnlyCapabilities capabilities, Justify? justification = null) |  | ||||||
|         : this(capabilities, justification, false) |  | ||||||
|     { |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     private RenderContext(IReadOnlyCapabilities capabilities, Justify? justification = null, bool singleLine = false) |  | ||||||
|     { |  | ||||||
|         _capabilities = capabilities ?? throw new ArgumentNullException(nameof(capabilities)); |  | ||||||
|  |  | ||||||
|         Justification = justification; |  | ||||||
|         SingleLine = singleLine; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     /// <summary> |  | ||||||
|     /// Creates a new context with the specified justification. |  | ||||||
|     /// </summary> |  | ||||||
|     /// <param name="justification">The justification.</param> |  | ||||||
|     /// <returns>A new <see cref="RenderContext"/> instance.</returns> |  | ||||||
|     public RenderContext WithJustification(Justify? justification) |  | ||||||
|     { |  | ||||||
|         return new RenderContext(_capabilities, justification, SingleLine); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     /// <summary> |  | ||||||
|     /// Creates a new context that tell <see cref="IRenderable"/> instances |  | ||||||
|     /// to not care about splitting things in new lines. Whether or not to |  | ||||||
|     /// comply to the request is up to the item being rendered. |  | ||||||
|     /// </summary> |  | ||||||
|     /// <remarks> |  | ||||||
|     /// Use with care since this has the potential to mess things up. |  | ||||||
|     /// Only use this kind of context with items that you know about. |  | ||||||
|     /// </remarks> |  | ||||||
|     /// <returns>A new <see cref="RenderContext"/> instance.</returns> |  | ||||||
|     internal RenderContext WithSingleLine() |  | ||||||
|     { |  | ||||||
|         return new RenderContext(_capabilities, Justification, true); |  | ||||||
|     } |  | ||||||
| } |  | ||||||
							
								
								
									
										63
									
								
								src/Spectre.Console/Rendering/RenderOptions.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										63
									
								
								src/Spectre.Console/Rendering/RenderOptions.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,63 @@ | |||||||
|  | namespace Spectre.Console.Rendering; | ||||||
|  |  | ||||||
|  | /// <summary> | ||||||
|  | /// Represents render options. | ||||||
|  | /// </summary> | ||||||
|  | /// <param name="Capabilities">The capabilities.</param> | ||||||
|  | /// <param name="ConsoleSize">The console size.</param> | ||||||
|  | public record class RenderOptions(IReadOnlyCapabilities Capabilities, Size ConsoleSize) | ||||||
|  | { | ||||||
|  |     /// <summary> | ||||||
|  |     /// Gets the current color system. | ||||||
|  |     /// </summary> | ||||||
|  |     public ColorSystem ColorSystem => Capabilities.ColorSystem; | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// Gets a value indicating whether or not VT/Ansi codes are supported. | ||||||
|  |     /// </summary> | ||||||
|  |     public bool Ansi => Capabilities.Ansi; | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// Gets a value indicating whether or not unicode is supported. | ||||||
|  |     /// </summary> | ||||||
|  |     public bool Unicode => Capabilities.Unicode; | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// Gets the current justification. | ||||||
|  |     /// </summary> | ||||||
|  |     public Justify? Justification { get; init; } | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// Gets the requested height. | ||||||
|  |     /// </summary> | ||||||
|  |     public int? Height { get; init; } | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// Gets a value indicating whether the context want items to render without | ||||||
|  |     /// line breaks and return a single line where applicable. | ||||||
|  |     /// </summary> | ||||||
|  |     internal bool SingleLine { get; init; } | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// Creates a <see cref="RenderOptions"/> instance from a <see cref="IAnsiConsole"/>. | ||||||
|  |     /// </summary> | ||||||
|  |     /// <param name="console">The console.</param> | ||||||
|  |     /// <param name="capabilities">The capabilities, or <c>null</c> to use the provided console's capabilities.</param> | ||||||
|  |     /// <returns>A <see cref="RenderOptions"/> representing the provided <see cref="IAnsiConsole"/>.</returns> | ||||||
|  |     public static RenderOptions Create(IAnsiConsole console, IReadOnlyCapabilities? capabilities = null) | ||||||
|  |     { | ||||||
|  |         if (console is null) | ||||||
|  |         { | ||||||
|  |             throw new ArgumentNullException(nameof(console)); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         return new RenderOptions( | ||||||
|  |             capabilities ?? console.Profile.Capabilities, | ||||||
|  |             new Size(console.Profile.Width, console.Profile.Height)) | ||||||
|  |         { | ||||||
|  |             Justification = null, | ||||||
|  |             Height = null, | ||||||
|  |             SingleLine = false, | ||||||
|  |         }; | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -44,17 +44,17 @@ public sealed class RenderPipeline | |||||||
|     /// <summary> |     /// <summary> | ||||||
|     /// Processes the specified renderables. |     /// Processes the specified renderables. | ||||||
|     /// </summary> |     /// </summary> | ||||||
|     /// <param name="context">The render context.</param> |     /// <param name="options">The render options.</param> | ||||||
|     /// <param name="renderables">The renderables to process.</param> |     /// <param name="renderables">The renderables to process.</param> | ||||||
|     /// <returns>The processed renderables.</returns> |     /// <returns>The processed renderables.</returns> | ||||||
|     public IEnumerable<IRenderable> Process(RenderContext context, IEnumerable<IRenderable> renderables) |     public IEnumerable<IRenderable> Process(RenderOptions options, IEnumerable<IRenderable> renderables) | ||||||
|     { |     { | ||||||
|         lock (_lock) |         lock (_lock) | ||||||
|         { |         { | ||||||
|             var current = renderables; |             var current = renderables; | ||||||
|             for (var index = _hooks.Count - 1; index >= 0; index--) |             for (var index = _hooks.Count - 1; index >= 0; index--) | ||||||
|             { |             { | ||||||
|                 current = _hooks[index].Process(context, current); |                 current = _hooks[index].Process(options, current); | ||||||
|             } |             } | ||||||
|  |  | ||||||
|             return current; |             return current; | ||||||
|   | |||||||
| @@ -7,25 +7,25 @@ public abstract class Renderable : IRenderable | |||||||
| { | { | ||||||
|     /// <inheritdoc/> |     /// <inheritdoc/> | ||||||
|     [DebuggerStepThrough] |     [DebuggerStepThrough] | ||||||
|     Measurement IRenderable.Measure(RenderContext context, int maxWidth) |     Measurement IRenderable.Measure(RenderOptions options, int maxWidth) | ||||||
|     { |     { | ||||||
|         return Measure(context, maxWidth); |         return Measure(options, maxWidth); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     /// <inheritdoc/> |     /// <inheritdoc/> | ||||||
|     [DebuggerStepThrough] |     [DebuggerStepThrough] | ||||||
|     IEnumerable<Segment> IRenderable.Render(RenderContext context, int maxWidth) |     IEnumerable<Segment> IRenderable.Render(RenderOptions options, int maxWidth) | ||||||
|     { |     { | ||||||
|         return Render(context, maxWidth); |         return Render(options, maxWidth); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     /// <summary> |     /// <summary> | ||||||
|     /// Measures the renderable object. |     /// Measures the renderable object. | ||||||
|     /// </summary> |     /// </summary> | ||||||
|     /// <param name="context">The render context.</param> |     /// <param name="options">The render options.</param> | ||||||
|     /// <param name="maxWidth">The maximum allowed width.</param> |     /// <param name="maxWidth">The maximum allowed width.</param> | ||||||
|     /// <returns>The minimum and maximum width of the object.</returns> |     /// <returns>The minimum and maximum width of the object.</returns> | ||||||
|     protected virtual Measurement Measure(RenderContext context, int maxWidth) |     protected virtual Measurement Measure(RenderOptions options, int maxWidth) | ||||||
|     { |     { | ||||||
|         return new Measurement(maxWidth, maxWidth); |         return new Measurement(maxWidth, maxWidth); | ||||||
|     } |     } | ||||||
| @@ -33,8 +33,8 @@ public abstract class Renderable : IRenderable | |||||||
|     /// <summary> |     /// <summary> | ||||||
|     /// Renders the object. |     /// Renders the object. | ||||||
|     /// </summary> |     /// </summary> | ||||||
|     /// <param name="context">The render context.</param> |     /// <param name="options">The render options.</param> | ||||||
|     /// <param name="maxWidth">The maximum allowed width.</param> |     /// <param name="maxWidth">The maximum allowed width.</param> | ||||||
|     /// <returns>A collection of segments.</returns> |     /// <returns>A collection of segments.</returns> | ||||||
|     protected abstract IEnumerable<Segment> Render(RenderContext context, int maxWidth); |     protected abstract IEnumerable<Segment> Render(RenderOptions options, int maxWidth); | ||||||
| } | } | ||||||
| @@ -201,8 +201,9 @@ public class Segment | |||||||
|     /// </summary> |     /// </summary> | ||||||
|     /// <param name="segments">The segments to split into lines.</param> |     /// <param name="segments">The segments to split into lines.</param> | ||||||
|     /// <param name="maxWidth">The maximum width.</param> |     /// <param name="maxWidth">The maximum width.</param> | ||||||
|  |     /// <param name="height">The height (if any).</param> | ||||||
|     /// <returns>A list of lines.</returns> |     /// <returns>A list of lines.</returns> | ||||||
|     public static List<SegmentLine> SplitLines(IEnumerable<Segment> segments, int maxWidth) |     public static List<SegmentLine> SplitLines(IEnumerable<Segment> segments, int maxWidth, int? height = null) | ||||||
|     { |     { | ||||||
|         if (segments is null) |         if (segments is null) | ||||||
|         { |         { | ||||||
| @@ -294,6 +295,25 @@ public class Segment | |||||||
|             lines.Add(line); |             lines.Add(line); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|  |         // Got a height specified? | ||||||
|  |         if (height != null) | ||||||
|  |         { | ||||||
|  |             if (lines.Count >= height) | ||||||
|  |             { | ||||||
|  |                 // Remove lines | ||||||
|  |                 lines.RemoveRange(height.Value, lines.Count - height.Value); | ||||||
|  |             } | ||||||
|  |             else | ||||||
|  |             { | ||||||
|  |                 // Add lines | ||||||
|  |                 var missing = height - lines.Count; | ||||||
|  |                 for (var i = 0; i < missing; i++) | ||||||
|  |                 { | ||||||
|  |                     lines.Add(new SegmentLine()); | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |  | ||||||
|         return lines; |         return lines; | ||||||
|     } |     } | ||||||
|  |  | ||||||
| @@ -549,6 +569,21 @@ public class Segment | |||||||
|         return cells; |         return cells; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     internal static List<SegmentLine> MakeWidth(int expectedWidth, List<SegmentLine> lines) | ||||||
|  |     { | ||||||
|  |         foreach (var line in lines) | ||||||
|  |         { | ||||||
|  |             var width = line.CellCount(); | ||||||
|  |             if (width < expectedWidth) | ||||||
|  |             { | ||||||
|  |                 var diff = expectedWidth - width; | ||||||
|  |                 line.Add(new Segment(new string(' ', diff))); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         return lines; | ||||||
|  |     } | ||||||
|  |  | ||||||
|     internal static List<string> SplitSegment(string text, int maxCellLength) |     internal static List<string> SplitSegment(string text, int maxCellLength) | ||||||
|     { |     { | ||||||
|         var list = new List<string>(); |         var list = new List<string>(); | ||||||
|   | |||||||
| @@ -11,13 +11,8 @@ internal readonly struct SegmentShape | |||||||
|         Height = height; |         Height = height; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     public static SegmentShape Calculate(RenderContext context, List<SegmentLine> lines) |     public static SegmentShape Calculate(RenderOptions options, List<SegmentLine> lines) | ||||||
|     { |     { | ||||||
|         if (context is null) |  | ||||||
|         { |  | ||||||
|             throw new ArgumentNullException(nameof(context)); |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         if (lines is null) |         if (lines is null) | ||||||
|         { |         { | ||||||
|             throw new ArgumentNullException(nameof(lines)); |             throw new ArgumentNullException(nameof(lines)); | ||||||
| @@ -36,7 +31,7 @@ internal readonly struct SegmentShape | |||||||
|             Math.Max(Height, other.Height)); |             Math.Max(Height, other.Height)); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     public void Apply(RenderContext context, ref List<SegmentLine> lines) |     public void Apply(RenderOptions options, ref List<SegmentLine> lines) | ||||||
|     { |     { | ||||||
|         foreach (var line in lines) |         foreach (var line in lines) | ||||||
|         { |         { | ||||||
|   | |||||||
							
								
								
									
										29
									
								
								src/Spectre.Console/Size.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								src/Spectre.Console/Size.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,29 @@ | |||||||
|  | namespace Spectre.Console; | ||||||
|  |  | ||||||
|  | /// <summary> | ||||||
|  | /// Represents a size. | ||||||
|  | /// </summary> | ||||||
|  | [DebuggerDisplay("{Width,nq}x{Height,nq}")] | ||||||
|  | public struct Size | ||||||
|  | { | ||||||
|  |     /// <summary> | ||||||
|  |     /// Gets the width. | ||||||
|  |     /// </summary> | ||||||
|  |     public int Width { get; } | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// Gets the height. | ||||||
|  |     /// </summary> | ||||||
|  |     public int Height { get; } | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// Initializes a new instance of the <see cref="Size"/> struct. | ||||||
|  |     /// </summary> | ||||||
|  |     /// <param name="width">The width.</param> | ||||||
|  |     /// <param name="height">The height.</param> | ||||||
|  |     public Size(int width, int height) | ||||||
|  |     { | ||||||
|  |         Width = width; | ||||||
|  |         Height = height; | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										22
									
								
								src/Spectre.Console/VerticalAlignment.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								src/Spectre.Console/VerticalAlignment.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,22 @@ | |||||||
|  | namespace Spectre.Console; | ||||||
|  |  | ||||||
|  | /// <summary> | ||||||
|  | /// Represents vertical alignment. | ||||||
|  | /// </summary> | ||||||
|  | public enum VerticalAlignment | ||||||
|  | { | ||||||
|  |     /// <summary> | ||||||
|  |     /// Top aligned. | ||||||
|  |     /// </summary> | ||||||
|  |     Top, | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// Middle aligned. | ||||||
|  |     /// </summary> | ||||||
|  |     Middle, | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// Bottom aligned. | ||||||
|  |     /// </summary> | ||||||
|  |     Bottom, | ||||||
|  | } | ||||||
							
								
								
									
										146
									
								
								src/Spectre.Console/Widgets/Align.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										146
									
								
								src/Spectre.Console/Widgets/Align.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,146 @@ | |||||||
|  | namespace Spectre.Console; | ||||||
|  |  | ||||||
|  | /// <summary> | ||||||
|  | /// Represents a renderable used to align content. | ||||||
|  | /// </summary> | ||||||
|  | public sealed class Align : Renderable | ||||||
|  | { | ||||||
|  |     private readonly IRenderable _renderable; | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// Gets or sets the horizontal alignment. | ||||||
|  |     /// </summary> | ||||||
|  |     public HorizontalAlignment Horizontal { get; set; } = HorizontalAlignment.Left; | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// Gets or sets the vertical alignment. | ||||||
|  |     /// </summary> | ||||||
|  |     public VerticalAlignment? Vertical { get; set; } | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// Gets or sets the width. | ||||||
|  |     /// </summary> | ||||||
|  |     public int? Width { get; set; } | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// Gets or sets the height. | ||||||
|  |     /// </summary> | ||||||
|  |     public int? Height { get; set; } | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// Initializes a new instance of the <see cref="Align"/> class. | ||||||
|  |     /// </summary> | ||||||
|  |     /// <param name="renderable">The renderable to align.</param> | ||||||
|  |     /// <param name="horizontal">The horizontal alignment.</param> | ||||||
|  |     /// <param name="vertical">The vertical alignment, or <c>null</c> if none.</param> | ||||||
|  |     public Align(IRenderable renderable, HorizontalAlignment horizontal, VerticalAlignment? vertical = null) | ||||||
|  |     { | ||||||
|  |         _renderable = renderable ?? throw new ArgumentNullException(nameof(renderable)); | ||||||
|  |  | ||||||
|  |         Horizontal = horizontal; | ||||||
|  |         Vertical = vertical; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// Initializes a new instance of the <see cref="Align"/> class that is left aligned. | ||||||
|  |     /// </summary> | ||||||
|  |     /// <param name="renderable">The <see cref="IRenderable"/> to align.</param> | ||||||
|  |     /// <param name="vertical">The vertical alignment, or <c>null</c> if none.</param> | ||||||
|  |     /// <returns>A new <see cref="Align"/> object.</returns> | ||||||
|  |     public static Align Left(IRenderable renderable, VerticalAlignment? vertical = null) | ||||||
|  |     { | ||||||
|  |         return new Align(renderable, HorizontalAlignment.Left, vertical); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// Initializes a new instance of the <see cref="Align"/> class that is center aligned. | ||||||
|  |     /// </summary> | ||||||
|  |     /// <param name="renderable">The <see cref="IRenderable"/> to align.</param> | ||||||
|  |     /// <param name="vertical">The vertical alignment, or <c>null</c> if none.</param> | ||||||
|  |     /// <returns>A new <see cref="Align"/> object.</returns> | ||||||
|  |     public static Align Center(IRenderable renderable, VerticalAlignment? vertical = null) | ||||||
|  |     { | ||||||
|  |         return new Align(renderable, HorizontalAlignment.Center, vertical); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// Initializes a new instance of the <see cref="Align"/> class that is right aligned. | ||||||
|  |     /// </summary> | ||||||
|  |     /// <param name="renderable">The <see cref="IRenderable"/> to align.</param> | ||||||
|  |     /// <param name="vertical">The vertical alignment, or <c>null</c> if none.</param> | ||||||
|  |     /// <returns>A new <see cref="Align"/> object.</returns> | ||||||
|  |     public static Align Right(IRenderable renderable, VerticalAlignment? vertical = null) | ||||||
|  |     { | ||||||
|  |         return new Align(renderable, HorizontalAlignment.Right, vertical); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /// <inheritdoc/> | ||||||
|  |     protected override IEnumerable<Segment> Render(RenderOptions options, int maxWidth) | ||||||
|  |     { | ||||||
|  |         var rendered = _renderable.Render(options with { Height = null }, maxWidth); | ||||||
|  |         var lines = Segment.SplitLines(rendered); | ||||||
|  |  | ||||||
|  |         var width = Math.Min(Width ?? maxWidth, maxWidth); | ||||||
|  |         var height = Height ?? options.Height; | ||||||
|  |  | ||||||
|  |         var blank = new SegmentLine(new[] { new Segment(new string(' ', width)) }); | ||||||
|  |  | ||||||
|  |         // Align vertically | ||||||
|  |         if (Vertical != null && height != null) | ||||||
|  |         { | ||||||
|  |             switch (Vertical) | ||||||
|  |             { | ||||||
|  |                 case VerticalAlignment.Top: | ||||||
|  |                     { | ||||||
|  |                         var diff = Height - lines.Count; | ||||||
|  |                         for (var i = 0; i < diff; i++) | ||||||
|  |                         { | ||||||
|  |                             lines.Add(blank); | ||||||
|  |                         } | ||||||
|  |  | ||||||
|  |                         break; | ||||||
|  |                     } | ||||||
|  |  | ||||||
|  |                 case VerticalAlignment.Middle: | ||||||
|  |                     { | ||||||
|  |                         var top = (height - lines.Count) / 2; | ||||||
|  |                         var bottom = height - top - lines.Count; | ||||||
|  |  | ||||||
|  |                         for (var i = 0; i < top; i++) | ||||||
|  |                         { | ||||||
|  |                             lines.Insert(0, blank); | ||||||
|  |                         } | ||||||
|  |  | ||||||
|  |                         for (var i = 0; i < bottom; i++) | ||||||
|  |                         { | ||||||
|  |                             lines.Add(blank); | ||||||
|  |                         } | ||||||
|  |  | ||||||
|  |                         break; | ||||||
|  |                     } | ||||||
|  |  | ||||||
|  |                 case VerticalAlignment.Bottom: | ||||||
|  |                     { | ||||||
|  |                         var diff = Height - lines.Count; | ||||||
|  |                         for (var i = 0; i < diff; i++) | ||||||
|  |                         { | ||||||
|  |                             lines.Insert(0, blank); | ||||||
|  |                         } | ||||||
|  |  | ||||||
|  |                         break; | ||||||
|  |                     } | ||||||
|  |  | ||||||
|  |                 default: | ||||||
|  |                     throw new NotSupportedException("Unknown vertical alignment"); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         // Align horizontally | ||||||
|  |         foreach (var line in lines) | ||||||
|  |         { | ||||||
|  |             Aligner.AlignHorizontally(line, Horizontal, width); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         return new SegmentLineEnumerator(lines); | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -107,6 +107,7 @@ public sealed class Calendar : JustInTimeRenderable, IHasCulture, IHasTableBorde | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     /// <inheritdoc/> |     /// <inheritdoc/> | ||||||
|  |     [Obsolete("Use the Align widget instead. This property will be removed in a later release.")] | ||||||
|     public Justify? Alignment |     public Justify? Alignment | ||||||
|     { |     { | ||||||
|         get => _alignment; |         get => _alignment; | ||||||
| @@ -162,6 +163,7 @@ public sealed class Calendar : JustInTimeRenderable, IHasCulture, IHasTableBorde | |||||||
|     { |     { | ||||||
|         var culture = Culture ?? CultureInfo.InvariantCulture; |         var culture = Culture ?? CultureInfo.InvariantCulture; | ||||||
|  |  | ||||||
|  | #pragma warning disable CS0618 // Type or member is obsolete | ||||||
|         var table = new Table |         var table = new Table | ||||||
|         { |         { | ||||||
|             Border = _border, |             Border = _border, | ||||||
| @@ -169,6 +171,7 @@ public sealed class Calendar : JustInTimeRenderable, IHasCulture, IHasTableBorde | |||||||
|             BorderStyle = _borderStyle, |             BorderStyle = _borderStyle, | ||||||
|             Alignment = _alignment, |             Alignment = _alignment, | ||||||
|         }; |         }; | ||||||
|  | #pragma warning restore CS0618 // Type or member is obsolete | ||||||
|  |  | ||||||
|         if (ShowHeader) |         if (ShowHeader) | ||||||
|         { |         { | ||||||
|   | |||||||
| @@ -70,7 +70,7 @@ public sealed class Canvas : Renderable | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     /// <inheritdoc/> |     /// <inheritdoc/> | ||||||
|     protected override Measurement Measure(RenderContext context, int maxWidth) |     protected override Measurement Measure(RenderOptions options, int maxWidth) | ||||||
|     { |     { | ||||||
|         if (PixelWidth < 0) |         if (PixelWidth < 0) | ||||||
|         { |         { | ||||||
| @@ -88,7 +88,7 @@ public sealed class Canvas : Renderable | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     /// <inheritdoc/> |     /// <inheritdoc/> | ||||||
|     protected override IEnumerable<Segment> Render(RenderContext context, int maxWidth) |     protected override IEnumerable<Segment> Render(RenderOptions options, int maxWidth) | ||||||
|     { |     { | ||||||
|         if (PixelWidth < 0) |         if (PixelWidth < 0) | ||||||
|         { |         { | ||||||
|   | |||||||
| @@ -52,14 +52,14 @@ public sealed class BarChart : Renderable, IHasCulture | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     /// <inheritdoc/> |     /// <inheritdoc/> | ||||||
|     protected override Measurement Measure(RenderContext context, int maxWidth) |     protected override Measurement Measure(RenderOptions options, int maxWidth) | ||||||
|     { |     { | ||||||
|         var width = Math.Min(Width ?? maxWidth, maxWidth); |         var width = Math.Min(Width ?? maxWidth, maxWidth); | ||||||
|         return new Measurement(width, width); |         return new Measurement(width, width); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     /// <inheritdoc/> |     /// <inheritdoc/> | ||||||
|     protected override IEnumerable<Segment> Render(RenderContext context, int maxWidth) |     protected override IEnumerable<Segment> Render(RenderOptions options, int maxWidth) | ||||||
|     { |     { | ||||||
|         var width = Math.Min(Width ?? maxWidth, maxWidth); |         var width = Math.Min(Width ?? maxWidth, maxWidth); | ||||||
|         var maxValue = Math.Max(MaxValue ?? 0d, Data.Max(item => item.Value)); |         var maxValue = Math.Max(MaxValue ?? 0d, Data.Max(item => item.Value)); | ||||||
| @@ -72,7 +72,7 @@ public sealed class BarChart : Renderable, IHasCulture | |||||||
|  |  | ||||||
|         if (!string.IsNullOrWhiteSpace(Label)) |         if (!string.IsNullOrWhiteSpace(Label)) | ||||||
|         { |         { | ||||||
|             grid.AddRow(Text.Empty, new Markup(Label).Alignment(LabelAlignment)); |             grid.AddRow(Text.Empty, new Markup(Label).Justify(LabelAlignment)); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         foreach (var item in Data) |         foreach (var item in Data) | ||||||
| @@ -93,6 +93,6 @@ public sealed class BarChart : Renderable, IHasCulture | |||||||
|                 }); |                 }); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         return ((IRenderable)grid).Render(context, width); |         return ((IRenderable)grid).Render(options, width); | ||||||
|     } |     } | ||||||
| } | } | ||||||
| @@ -11,13 +11,13 @@ internal sealed class BreakdownBar : Renderable | |||||||
|         _data = data ?? throw new ArgumentNullException(nameof(data)); |         _data = data ?? throw new ArgumentNullException(nameof(data)); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     protected override Measurement Measure(RenderContext context, int maxWidth) |     protected override Measurement Measure(RenderOptions options, int maxWidth) | ||||||
|     { |     { | ||||||
|         var width = Math.Min(Width ?? maxWidth, maxWidth); |         var width = Math.Min(Width ?? maxWidth, maxWidth); | ||||||
|         return new Measurement(width, width); |         return new Measurement(width, width); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     protected override IEnumerable<Segment> Render(RenderContext context, int maxWidth) |     protected override IEnumerable<Segment> Render(RenderOptions options, int maxWidth) | ||||||
|     { |     { | ||||||
|         var width = Math.Min(Width ?? maxWidth, maxWidth); |         var width = Math.Min(Width ?? maxWidth, maxWidth); | ||||||
|  |  | ||||||
|   | |||||||
| @@ -3,7 +3,7 @@ namespace Spectre.Console; | |||||||
| /// <summary> | /// <summary> | ||||||
| /// A renderable breakdown chart. | /// A renderable breakdown chart. | ||||||
| /// </summary> | /// </summary> | ||||||
| public sealed class BreakdownChart : Renderable, IHasCulture | public sealed class BreakdownChart : Renderable, IHasCulture, IExpandable | ||||||
| { | { | ||||||
|     /// <summary> |     /// <summary> | ||||||
|     /// Gets the breakdown chart data. |     /// Gets the breakdown chart data. | ||||||
| @@ -43,6 +43,13 @@ public sealed class BreakdownChart : Renderable, IHasCulture | |||||||
|     /// <remarks>Defaults to invariant culture.</remarks> |     /// <remarks>Defaults to invariant culture.</remarks> | ||||||
|     public CultureInfo? Culture { get; set; } |     public CultureInfo? Culture { get; set; } | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// Gets or sets a value indicating whether or not the object should | ||||||
|  |     /// expand to the available space. If <c>false</c>, the object's | ||||||
|  |     /// width will be auto calculated. | ||||||
|  |     /// </summary> | ||||||
|  |     public bool Expand { get; set; } = true; | ||||||
|  |  | ||||||
|     /// <summary> |     /// <summary> | ||||||
|     /// Initializes a new instance of the <see cref="BreakdownChart"/> class. |     /// Initializes a new instance of the <see cref="BreakdownChart"/> class. | ||||||
|     /// </summary> |     /// </summary> | ||||||
| @@ -53,14 +60,14 @@ public sealed class BreakdownChart : Renderable, IHasCulture | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     /// <inheritdoc/> |     /// <inheritdoc/> | ||||||
|     protected override Measurement Measure(RenderContext context, int maxWidth) |     protected override Measurement Measure(RenderOptions options, int maxWidth) | ||||||
|     { |     { | ||||||
|         var width = Math.Min(Width ?? maxWidth, maxWidth); |         var width = Math.Min(Width ?? maxWidth, maxWidth); | ||||||
|         return new Measurement(width, width); |         return new Measurement(width, width); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     /// <inheritdoc/> |     /// <inheritdoc/> | ||||||
|     protected override IEnumerable<Segment> Render(RenderContext context, int maxWidth) |     protected override IEnumerable<Segment> Render(RenderOptions options, int maxWidth) | ||||||
|     { |     { | ||||||
|         var width = Math.Min(Width ?? maxWidth, maxWidth); |         var width = Math.Min(Width ?? maxWidth, maxWidth); | ||||||
|  |  | ||||||
| @@ -90,6 +97,11 @@ public sealed class BreakdownChart : Renderable, IHasCulture | |||||||
|             }); |             }); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         return ((IRenderable)grid).Render(context, width); |         if (!Expand) | ||||||
|  |         { | ||||||
|  |             grid.Collapse(); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         return ((IRenderable)grid).Render(options, width); | ||||||
|     } |     } | ||||||
| } | } | ||||||
| @@ -14,13 +14,13 @@ internal sealed class BreakdownTags : Renderable | |||||||
|         _data = data ?? throw new ArgumentNullException(nameof(data)); |         _data = data ?? throw new ArgumentNullException(nameof(data)); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     protected override Measurement Measure(RenderContext context, int maxWidth) |     protected override Measurement Measure(RenderOptions options, int maxWidth) | ||||||
|     { |     { | ||||||
|         var width = Math.Min(Width ?? maxWidth, maxWidth); |         var width = Math.Min(Width ?? maxWidth, maxWidth); | ||||||
|         return new Measurement(width, width); |         return new Measurement(width, width); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     protected override IEnumerable<Segment> Render(RenderContext context, int maxWidth) |     protected override IEnumerable<Segment> Render(RenderOptions options, int maxWidth) | ||||||
|     { |     { | ||||||
|         var culture = Culture ?? CultureInfo.InvariantCulture; |         var culture = Culture ?? CultureInfo.InvariantCulture; | ||||||
|  |  | ||||||
| @@ -29,13 +29,13 @@ internal sealed class BreakdownTags : Renderable | |||||||
|         { |         { | ||||||
|             var panel = new Panel(GetTag(item, culture)); |             var panel = new Panel(GetTag(item, culture)); | ||||||
|             panel.Inline = true; |             panel.Inline = true; | ||||||
|             panel.Padding = new Padding(0, 0); |             panel.Padding = new Padding(0, 0, 2, 0); | ||||||
|             panel.NoBorder(); |             panel.NoBorder(); | ||||||
|  |  | ||||||
|             panels.Add(panel); |             panels.Add(panel); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         foreach (var segment in ((IRenderable)new Columns(panels).Padding(0, 0)).Render(context, maxWidth)) |         foreach (var segment in ((IRenderable)new Columns(panels).Padding(0, 0)).Render(options, maxWidth)) | ||||||
|         { |         { | ||||||
|             yield return segment; |             yield return segment; | ||||||
|         } |         } | ||||||
|   | |||||||
| @@ -55,11 +55,11 @@ public sealed class Columns : Renderable, IPaddable, IExpandable | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     /// <inheritdoc/> |     /// <inheritdoc/> | ||||||
|     protected override Measurement Measure(RenderContext context, int maxWidth) |     protected override Measurement Measure(RenderOptions options, int maxWidth) | ||||||
|     { |     { | ||||||
|         var maxPadding = Math.Max(Padding.GetLeftSafe(), Padding.GetRightSafe()); |         var maxPadding = Math.Max(Padding.GetLeftSafe(), Padding.GetRightSafe()); | ||||||
|  |  | ||||||
|         var itemWidths = _items.Select(item => item.Measure(context, maxWidth).Max).ToArray(); |         var itemWidths = _items.Select(item => item.Measure(options, maxWidth).Max).ToArray(); | ||||||
|         var columnCount = CalculateColumnCount(maxWidth, itemWidths, _items.Count, maxPadding); |         var columnCount = CalculateColumnCount(maxWidth, itemWidths, _items.Count, maxPadding); | ||||||
|         if (columnCount == 0) |         if (columnCount == 0) | ||||||
|         { |         { | ||||||
| @@ -83,11 +83,11 @@ public sealed class Columns : Renderable, IPaddable, IExpandable | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     /// <inheritdoc/> |     /// <inheritdoc/> | ||||||
|     protected override IEnumerable<Segment> Render(RenderContext context, int maxWidth) |     protected override IEnumerable<Segment> Render(RenderOptions options, int maxWidth) | ||||||
|     { |     { | ||||||
|         var maxPadding = Math.Max(Padding.GetLeftSafe(), Padding.GetRightSafe()); |         var maxPadding = Math.Max(Padding.GetLeftSafe(), Padding.GetRightSafe()); | ||||||
|  |  | ||||||
|         var itemWidths = _items.Select(item => item.Measure(context, maxWidth).Max).ToArray(); |         var itemWidths = _items.Select(item => item.Measure(options, maxWidth).Max).ToArray(); | ||||||
|         var columnCount = CalculateColumnCount(maxWidth, itemWidths, _items.Count, maxPadding); |         var columnCount = CalculateColumnCount(maxWidth, itemWidths, _items.Count, maxPadding); | ||||||
|         if (columnCount == 0) |         if (columnCount == 0) | ||||||
|         { |         { | ||||||
| @@ -121,7 +121,7 @@ public sealed class Columns : Renderable, IPaddable, IExpandable | |||||||
|             table.AddRow(_items.Skip(start).Take(columnCount).ToArray()); |             table.AddRow(_items.Skip(start).Take(columnCount).ToArray()); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         return ((IRenderable)table).Render(context, maxWidth); |         return ((IRenderable)table).Render(options, maxWidth); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     // Algorithm borrowed from https://github.com/willmcgugan/rich/blob/master/rich/columns.py |     // Algorithm borrowed from https://github.com/willmcgugan/rich/blob/master/rich/columns.py | ||||||
|   | |||||||
| @@ -9,14 +9,14 @@ internal sealed class ControlCode : Renderable | |||||||
|         _segment = Segment.Control(control); |         _segment = Segment.Control(control); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     protected override Measurement Measure(RenderContext context, int maxWidth) |     protected override Measurement Measure(RenderOptions options, int maxWidth) | ||||||
|     { |     { | ||||||
|         return new Measurement(0, 0); |         return new Measurement(0, 0); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     protected override IEnumerable<Segment> Render(RenderContext context, int maxWidth) |     protected override IEnumerable<Segment> Render(RenderOptions options, int maxWidth) | ||||||
|     { |     { | ||||||
|         if (context.Ansi) |         if (options.Ansi) | ||||||
|         { |         { | ||||||
|             yield return _segment; |             yield return _segment; | ||||||
|         } |         } | ||||||
|   | |||||||
| @@ -3,7 +3,7 @@ namespace Spectre.Console; | |||||||
| /// <summary> | /// <summary> | ||||||
| /// Represents text rendered with a FIGlet font. | /// Represents text rendered with a FIGlet font. | ||||||
| /// </summary> | /// </summary> | ||||||
| public sealed class FigletText : Renderable, IAlignable | public sealed class FigletText : Renderable, IHasJustification | ||||||
| { | { | ||||||
|     private readonly FigletFont _font; |     private readonly FigletFont _font; | ||||||
|     private readonly string _text; |     private readonly string _text; | ||||||
| @@ -14,7 +14,7 @@ public sealed class FigletText : Renderable, IAlignable | |||||||
|     public Color? Color { get; set; } |     public Color? Color { get; set; } | ||||||
|  |  | ||||||
|     /// <inheritdoc/> |     /// <inheritdoc/> | ||||||
|     public Justify? Alignment { get; set; } |     public Justify? Justification { get; set; } | ||||||
|  |  | ||||||
|     /// <summary> |     /// <summary> | ||||||
|     /// Initializes a new instance of the <see cref="FigletText"/> class. |     /// Initializes a new instance of the <see cref="FigletText"/> class. | ||||||
| @@ -37,10 +37,10 @@ public sealed class FigletText : Renderable, IAlignable | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     /// <inheritdoc/> |     /// <inheritdoc/> | ||||||
|     protected override IEnumerable<Segment> Render(RenderContext context, int maxWidth) |     protected override IEnumerable<Segment> Render(RenderOptions options, int maxWidth) | ||||||
|     { |     { | ||||||
|         var style = new Style(Color ?? Console.Color.Default); |         var style = new Style(Color ?? Console.Color.Default); | ||||||
|         var alignment = Alignment ?? Justify.Left; |         var alignment = Justification ?? Console.Justify.Left; | ||||||
|  |  | ||||||
|         foreach (var row in GetRows(maxWidth)) |         foreach (var row in GetRows(maxWidth)) | ||||||
|         { |         { | ||||||
| @@ -49,7 +49,7 @@ public sealed class FigletText : Renderable, IAlignable | |||||||
|                 var line = new Segment(string.Concat(row.Select(x => x.Lines[index])), style); |                 var line = new Segment(string.Concat(row.Select(x => x.Lines[index])), style); | ||||||
|  |  | ||||||
|                 var lineWidth = line.CellCount(); |                 var lineWidth = line.CellCount(); | ||||||
|                 if (alignment == Justify.Left) |                 if (alignment == Console.Justify.Left) | ||||||
|                 { |                 { | ||||||
|                     yield return line; |                     yield return line; | ||||||
|  |  | ||||||
| @@ -58,7 +58,7 @@ public sealed class FigletText : Renderable, IAlignable | |||||||
|                         yield return Segment.Padding(maxWidth - lineWidth); |                         yield return Segment.Padding(maxWidth - lineWidth); | ||||||
|                     } |                     } | ||||||
|                 } |                 } | ||||||
|                 else if (alignment == Justify.Center) |                 else if (alignment == Console.Justify.Center) | ||||||
|                 { |                 { | ||||||
|                     var left = (maxWidth - lineWidth) / 2; |                     var left = (maxWidth - lineWidth) / 2; | ||||||
|                     var right = left + ((maxWidth - lineWidth) % 2); |                     var right = left + ((maxWidth - lineWidth) % 2); | ||||||
| @@ -67,7 +67,7 @@ public sealed class FigletText : Renderable, IAlignable | |||||||
|                     yield return line; |                     yield return line; | ||||||
|                     yield return Segment.Padding(right); |                     yield return Segment.Padding(right); | ||||||
|                 } |                 } | ||||||
|                 else if (alignment == Justify.Right) |                 else if (alignment == Console.Justify.Right) | ||||||
|                 { |                 { | ||||||
|                     if (lineWidth < maxWidth) |                     if (lineWidth < maxWidth) | ||||||
|                     { |                     { | ||||||
|   | |||||||
| @@ -30,6 +30,7 @@ public sealed class Grid : JustInTimeRenderable, IExpandable, IAlignable | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     /// <inheritdoc/> |     /// <inheritdoc/> | ||||||
|  |     [Obsolete("Use the Align widget instead. This property will be removed in a later release.")] | ||||||
|     public Justify? Alignment |     public Justify? Alignment | ||||||
|     { |     { | ||||||
|         get => _alignment; |         get => _alignment; | ||||||
|   | |||||||
							
								
								
									
										311
									
								
								src/Spectre.Console/Widgets/Layout/Layout.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										311
									
								
								src/Spectre.Console/Widgets/Layout/Layout.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,311 @@ | |||||||
|  | namespace Spectre.Console; | ||||||
|  |  | ||||||
|  | /// <summary> | ||||||
|  | /// Represents a renderable to divide a fixed height into rows or columns. | ||||||
|  | /// </summary> | ||||||
|  | public sealed class Layout : Renderable, IRatioResolvable, IHasVisibility | ||||||
|  | { | ||||||
|  |     private LayoutSplitter _splitter; | ||||||
|  |     private Layout[] _children; | ||||||
|  |     private IRenderable _renderable; | ||||||
|  |     private int _ratio; | ||||||
|  |     private int _minimumSize; | ||||||
|  |     private int? _size; | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// Gets or sets the name. | ||||||
|  |     /// </summary> | ||||||
|  |     public string? Name { get; set; } | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// Gets or sets the ratio. | ||||||
|  |     /// </summary> | ||||||
|  |     /// <remarks> | ||||||
|  |     /// Defaults to <c>1</c>. | ||||||
|  |     /// Must be greater than <c>0</c>. | ||||||
|  |     /// </remarks> | ||||||
|  |     public int Ratio | ||||||
|  |     { | ||||||
|  |         get => _ratio; | ||||||
|  |         set | ||||||
|  |         { | ||||||
|  |             if (value < 1) | ||||||
|  |             { | ||||||
|  |                 throw new InvalidOperationException("Ratio must be equal to or greater than 1"); | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             _ratio = value; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// Gets or sets the minimum width. | ||||||
|  |     /// </summary> | ||||||
|  |     /// <remarks> | ||||||
|  |     /// Defaults to <c>1</c>. | ||||||
|  |     /// Must be greater than <c>0</c>. | ||||||
|  |     /// </remarks> | ||||||
|  |     public int MinimumSize | ||||||
|  |     { | ||||||
|  |         get => _minimumSize; | ||||||
|  |         set | ||||||
|  |         { | ||||||
|  |             if (value < 1) | ||||||
|  |             { | ||||||
|  |                 throw new InvalidOperationException("Minimum size must be equal to or greater than 1"); | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             _minimumSize = value; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// Gets or sets the width. | ||||||
|  |     /// </summary> | ||||||
|  |     /// <remarks> | ||||||
|  |     /// Defaults to <c>null</c>. | ||||||
|  |     /// Must be greater than <c>0</c>. | ||||||
|  |     /// </remarks> | ||||||
|  |     public int? Size | ||||||
|  |     { | ||||||
|  |         get => _size; | ||||||
|  |         set | ||||||
|  |         { | ||||||
|  |             if (value < 1) | ||||||
|  |             { | ||||||
|  |                 throw new InvalidOperationException("Size must be equal to or greater than 1"); | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             _size = value; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// Gets or sets a value indicating whether or not the layout should | ||||||
|  |     /// be visible or not. | ||||||
|  |     /// </summary> | ||||||
|  |     /// <remarks>Defaults to <c>true</c>.</remarks> | ||||||
|  |     public bool IsVisible { get; set; } = true; | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// Gets the splitter used for this layout. | ||||||
|  |     /// </summary> | ||||||
|  |     internal LayoutSplitter Splitter => _splitter; | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// Gets the <see cref="IRenderable"/> associated with this layout. | ||||||
|  |     /// </summary> | ||||||
|  |     internal IRenderable Renderable => _renderable; | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// Gets a child layout by it's name. | ||||||
|  |     /// </summary> | ||||||
|  |     /// <param name="name">The layout name.</param> | ||||||
|  |     /// <returns>The specified child <see cref="Layout"/>.</returns> | ||||||
|  |     public Layout this[string name] | ||||||
|  |     { | ||||||
|  |         get => GetLayout(name); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// Initializes a new instance of the <see cref="Layout"/> class. | ||||||
|  |     /// </summary> | ||||||
|  |     /// <param name="name">The layout name.</param> | ||||||
|  |     public Layout(string name) | ||||||
|  |         : this(name, null) | ||||||
|  |     { | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// Initializes a new instance of the <see cref="Layout"/> class. | ||||||
|  |     /// </summary> | ||||||
|  |     /// <param name="renderable">The renderable.</param> | ||||||
|  |     public Layout(IRenderable renderable) | ||||||
|  |         : this(null, renderable) | ||||||
|  |     { | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// Initializes a new instance of the <see cref="Layout"/> class. | ||||||
|  |     /// </summary> | ||||||
|  |     /// <param name="name">The layout name.</param> | ||||||
|  |     /// <param name="renderable">The renderable.</param> | ||||||
|  |     public Layout(string? name = null, IRenderable? renderable = null) | ||||||
|  |     { | ||||||
|  |         _splitter = LayoutSplitter.Null; | ||||||
|  |         _children = Array.Empty<Layout>(); | ||||||
|  |         _renderable = renderable ?? new LayoutPlaceholder(this); | ||||||
|  |         _ratio = 1; | ||||||
|  |         _size = null; | ||||||
|  |  | ||||||
|  |         Name = name; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// Gets a child layout by it's name. | ||||||
|  |     /// </summary> | ||||||
|  |     /// <param name="name">The layout name.</param> | ||||||
|  |     /// <returns>The specified child <see cref="Layout"/>.</returns> | ||||||
|  |     public Layout GetLayout(string name) | ||||||
|  |     { | ||||||
|  |         if (string.IsNullOrEmpty(name)) | ||||||
|  |         { | ||||||
|  |             throw new ArgumentException($"'{nameof(name)}' cannot be null or empty.", nameof(name)); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         var stack = new Stack<Layout>(); | ||||||
|  |         stack.Push(this); | ||||||
|  |  | ||||||
|  |         while (stack.Count > 0) | ||||||
|  |         { | ||||||
|  |             var current = stack.Pop(); | ||||||
|  |             if (name.Equals(current.Name, StringComparison.OrdinalIgnoreCase)) | ||||||
|  |             { | ||||||
|  |                 return current; | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             foreach (var layout in current.GetChildren()) | ||||||
|  |             { | ||||||
|  |                 stack.Push(layout); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         throw new InvalidOperationException($"Could not find layout '{name}'"); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// Splits the layout into rows. | ||||||
|  |     /// </summary> | ||||||
|  |     /// <param name="children">The layout to split into rows.</param> | ||||||
|  |     /// <returns>The same instance so that multiple calls can be chained.</returns> | ||||||
|  |     public Layout SplitRows(params Layout[] children) | ||||||
|  |     { | ||||||
|  |         Split(LayoutSplitter.Row, children); | ||||||
|  |         return this; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// Splits the layout into columns. | ||||||
|  |     /// </summary> | ||||||
|  |     /// <param name="children">The layout to split into columns.</param> | ||||||
|  |     /// <returns>The same instance so that multiple calls can be chained.</returns> | ||||||
|  |     public Layout SplitColumns(params Layout[] children) | ||||||
|  |     { | ||||||
|  |         Split(LayoutSplitter.Column, children); | ||||||
|  |         return this; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// Updates the containing <see cref="IRenderable"/>. | ||||||
|  |     /// </summary> | ||||||
|  |     /// <param name="renderable">The renderable to use for this layout.</param> | ||||||
|  |     /// /// <returns>The same instance so that multiple calls can be chained.</returns> | ||||||
|  |     public Layout Update(IRenderable renderable) | ||||||
|  |     { | ||||||
|  |         _renderable = renderable ?? new LayoutPlaceholder(this); | ||||||
|  |         return this; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /// <inheritdoc/> | ||||||
|  |     protected override IEnumerable<Segment> Render(RenderOptions options, int maxWidth) | ||||||
|  |     { | ||||||
|  |         var height = options.Height ?? options.ConsoleSize.Height; | ||||||
|  |         var map = MakeRenderMap(options, maxWidth); | ||||||
|  |  | ||||||
|  |         var layoutLines = new List<SegmentLine>(); | ||||||
|  |         layoutLines.AddRange(Enumerable.Range(0, height).Select(x => new SegmentLine())); | ||||||
|  |  | ||||||
|  |         foreach (var (region, lines) in map.Values.Select(x => (x.Region, x.Render))) | ||||||
|  |         { | ||||||
|  |             foreach (var line in layoutLines | ||||||
|  |                 .Skip(region.Y) | ||||||
|  |                 .Take(region.Y + region.Height) | ||||||
|  |                 .Enumerate().Select(x => (Index: x.Index + region.Y, Line: x.Item)) | ||||||
|  |                 .Zip(lines, (first, second) => (first.Index, Line: second))) | ||||||
|  |             { | ||||||
|  |                 layoutLines[line.Index].AddRange(line.Line); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         // Return all the segments in all the lines | ||||||
|  |         foreach (var (_, _, last, line) in layoutLines.Enumerate()) | ||||||
|  |         { | ||||||
|  |             foreach (var segment in line) | ||||||
|  |             { | ||||||
|  |                 yield return segment; | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             if (!last) | ||||||
|  |             { | ||||||
|  |                 yield return Segment.LineBreak; | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     private IEnumerable<Layout> GetChildren(bool visibleOnly = false) | ||||||
|  |     { | ||||||
|  |         return visibleOnly ? _children.Where(c => c.IsVisible) : _children; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     private bool HasChildren(bool visibleOnly = false) | ||||||
|  |     { | ||||||
|  |         return visibleOnly ? _children.Any(c => c.IsVisible) : _children.Any(); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     private void Split(LayoutSplitter splitter, Layout[] layouts) | ||||||
|  |     { | ||||||
|  |         if (_children.Length > 0) | ||||||
|  |         { | ||||||
|  |             throw new InvalidOperationException("Cannot split the same layout twice"); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         _splitter = splitter ?? throw new ArgumentNullException(nameof(splitter)); | ||||||
|  |         _children = layouts ?? throw new ArgumentNullException(nameof(layouts)); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     private Dictionary<Layout, LayoutRender> MakeRenderMap(RenderOptions options, int maxWidth) | ||||||
|  |     { | ||||||
|  |         var result = new Dictionary<Layout, LayoutRender>(); | ||||||
|  |  | ||||||
|  |         var renderWidth = maxWidth; | ||||||
|  |         var renderHeight = options.Height ?? options.ConsoleSize.Height; | ||||||
|  |         var regionMap = MakeRegionMap(maxWidth, renderHeight); | ||||||
|  |  | ||||||
|  |         foreach (var (layout, region) in regionMap.Where(x => !x.Layout.HasChildren(visibleOnly: true))) | ||||||
|  |         { | ||||||
|  |             var segments = layout.Renderable.Render(options with { Height = region.Height }, region.Width); | ||||||
|  |  | ||||||
|  |             var lines = Segment.SplitLines(segments, region.Width, region.Height); | ||||||
|  |             lines = Segment.MakeWidth(region.Width, lines); | ||||||
|  |  | ||||||
|  |             result[layout] = new LayoutRender(region, lines); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         return result; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     private IEnumerable<(Layout Layout, Region Region)> MakeRegionMap(int width, int height) | ||||||
|  |     { | ||||||
|  |         var stack = new Stack<(Layout Layout, Region Region)>(); | ||||||
|  |         stack.Push((this, new Region(0, 0, width, height))); | ||||||
|  |  | ||||||
|  |         var result = new List<(Layout Layout, Region Region)>(); | ||||||
|  |  | ||||||
|  |         while (stack.Count > 0) | ||||||
|  |         { | ||||||
|  |             var current = stack.Pop(); | ||||||
|  |             result.Add(current); | ||||||
|  |  | ||||||
|  |             if (current.Layout.HasChildren(visibleOnly: true)) | ||||||
|  |             { | ||||||
|  |                 foreach (var childAndRegion in current.Layout.Splitter | ||||||
|  |                     .Divide(current.Region, current.Layout.GetChildren(visibleOnly: true))) | ||||||
|  |                 { | ||||||
|  |                     stack.Push(childAndRegion); | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         return result.ReverseEnumerable(); | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										31
									
								
								src/Spectre.Console/Widgets/Layout/LayoutPlaceholder.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										31
									
								
								src/Spectre.Console/Widgets/Layout/LayoutPlaceholder.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,31 @@ | |||||||
|  | namespace Spectre.Console; | ||||||
|  |  | ||||||
|  | internal sealed class LayoutPlaceholder : Renderable | ||||||
|  | { | ||||||
|  |     public Layout Layout { get; } | ||||||
|  |  | ||||||
|  |     public LayoutPlaceholder(Layout layout) | ||||||
|  |     { | ||||||
|  |         Layout = layout ?? throw new ArgumentNullException(nameof(layout)); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     protected override IEnumerable<Segment> Render(RenderOptions options, int maxWidth) | ||||||
|  |     { | ||||||
|  |         var width = maxWidth; | ||||||
|  |         var height = options.Height ?? options.ConsoleSize.Height; | ||||||
|  |         var title = Layout.Name != null | ||||||
|  |             ? $"{Layout.Name} ({width} x {height})" | ||||||
|  |             : $"{width} x {height}"; | ||||||
|  |  | ||||||
|  |         var panel = new Panel( | ||||||
|  |             Align.Center(new Text("Placeholder"), VerticalAlignment.Middle)) | ||||||
|  |         { | ||||||
|  |             Width = maxWidth, | ||||||
|  |             Height = options.Height ?? options.ConsoleSize.Height, | ||||||
|  |             Header = new PanelHeader(title), | ||||||
|  |             Border = BoxBorder.Rounded, | ||||||
|  |         }; | ||||||
|  |  | ||||||
|  |         return ((IRenderable)panel).Render(options, maxWidth); | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										14
									
								
								src/Spectre.Console/Widgets/Layout/LayoutRender.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								src/Spectre.Console/Widgets/Layout/LayoutRender.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,14 @@ | |||||||
|  | namespace Spectre.Console; | ||||||
|  |  | ||||||
|  | [DebuggerDisplay("{Region,nq}")] | ||||||
|  | internal sealed class LayoutRender | ||||||
|  | { | ||||||
|  |     public Region Region { get; } | ||||||
|  |     public List<SegmentLine> Render { get; } | ||||||
|  |  | ||||||
|  |     public LayoutRender(Region region, List<SegmentLine> render) | ||||||
|  |     { | ||||||
|  |         Region = region; | ||||||
|  |         Render = render ?? throw new ArgumentNullException(nameof(render)); | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										48
									
								
								src/Spectre.Console/Widgets/Layout/LayoutSplitter.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										48
									
								
								src/Spectre.Console/Widgets/Layout/LayoutSplitter.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,48 @@ | |||||||
|  | namespace Spectre.Console; | ||||||
|  |  | ||||||
|  | internal abstract class LayoutSplitter | ||||||
|  | { | ||||||
|  |     public static LayoutSplitter Column { get; } = new ColumnSplitter(); | ||||||
|  |     public static LayoutSplitter Row { get; } = new RowSplitter(); | ||||||
|  |     public static LayoutSplitter Null { get; } = new NullSplitter(); | ||||||
|  |  | ||||||
|  |     public abstract IEnumerable<(Layout Child, Region Region)> Divide(Region region, IEnumerable<Layout> layouts); | ||||||
|  |  | ||||||
|  |     private sealed class NullSplitter : LayoutSplitter | ||||||
|  |     { | ||||||
|  |         public override IEnumerable<(Layout Child, Region Region)> Divide(Region region, IEnumerable<Layout> layouts) | ||||||
|  |         { | ||||||
|  |             yield break; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     private sealed class ColumnSplitter : LayoutSplitter | ||||||
|  |     { | ||||||
|  |         public override IEnumerable<(Layout Child, Region Region)> Divide(Region region, IEnumerable<Layout> children) | ||||||
|  |         { | ||||||
|  |             var widths = Ratio.Resolve(region.Width, children); | ||||||
|  |             var offset = 0; | ||||||
|  |  | ||||||
|  |             foreach (var (child, childWidth) in children.Zip(widths, (child, width) => (child, width))) | ||||||
|  |             { | ||||||
|  |                 yield return (child, new Region(region.X + offset, region.Y, childWidth, region.Height)); | ||||||
|  |                 offset += childWidth; | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     private sealed class RowSplitter : LayoutSplitter | ||||||
|  |     { | ||||||
|  |         public override IEnumerable<(Layout Child, Region Region)> Divide(Region region, IEnumerable<Layout> children) | ||||||
|  |         { | ||||||
|  |             var heights = Ratio.Resolve(region.Height, children); | ||||||
|  |             var offset = 0; | ||||||
|  |  | ||||||
|  |             foreach (var (child, childHeight) in children.Zip(heights, (child, height) => (child, height))) | ||||||
|  |             { | ||||||
|  |                 yield return (child, new Region(region.X, region.Y + offset, region.Width, childHeight)); | ||||||
|  |                 offset += childHeight; | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -4,15 +4,15 @@ namespace Spectre.Console; | |||||||
| /// A renderable piece of markup text. | /// A renderable piece of markup text. | ||||||
| /// </summary> | /// </summary> | ||||||
| [SuppressMessage("Naming", "CA1724:Type names should not match namespaces")] | [SuppressMessage("Naming", "CA1724:Type names should not match namespaces")] | ||||||
| public sealed class Markup : Renderable, IAlignable, IOverflowable | public sealed class Markup : Renderable, IHasJustification, IOverflowable | ||||||
| { | { | ||||||
|     private readonly Paragraph _paragraph; |     private readonly Paragraph _paragraph; | ||||||
|  |  | ||||||
|     /// <inheritdoc/> |     /// <inheritdoc/> | ||||||
|     public Justify? Alignment |     public Justify? Justification | ||||||
|     { |     { | ||||||
|         get => _paragraph.Alignment; |         get => _paragraph.Justification; | ||||||
|         set => _paragraph.Alignment = value; |         set => _paragraph.Justification = value; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     /// <inheritdoc/> |     /// <inheritdoc/> | ||||||
| @@ -43,15 +43,15 @@ public sealed class Markup : Renderable, IAlignable, IOverflowable | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     /// <inheritdoc/> |     /// <inheritdoc/> | ||||||
|     protected override Measurement Measure(RenderContext context, int maxWidth) |     protected override Measurement Measure(RenderOptions options, int maxWidth) | ||||||
|     { |     { | ||||||
|         return ((IRenderable)_paragraph).Measure(context, maxWidth); |         return ((IRenderable)_paragraph).Measure(options, maxWidth); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     /// <inheritdoc/> |     /// <inheritdoc/> | ||||||
|     protected override IEnumerable<Segment> Render(RenderContext context, int maxWidth) |     protected override IEnumerable<Segment> Render(RenderOptions options, int maxWidth) | ||||||
|     { |     { | ||||||
|         return ((IRenderable)_paragraph).Render(context, maxWidth); |         return ((IRenderable)_paragraph).Render(options, maxWidth); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     /// <summary> |     /// <summary> | ||||||
|   | |||||||
| @@ -29,10 +29,10 @@ public sealed class Padder : Renderable, IPaddable, IExpandable | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     /// <inheritdoc/> |     /// <inheritdoc/> | ||||||
|     protected override Measurement Measure(RenderContext context, int maxWidth) |     protected override Measurement Measure(RenderOptions options, int maxWidth) | ||||||
|     { |     { | ||||||
|         var paddingWidth = Padding?.GetWidth() ?? 0; |         var paddingWidth = Padding?.GetWidth() ?? 0; | ||||||
|         var measurement = _child.Measure(context, maxWidth - paddingWidth); |         var measurement = _child.Measure(options, maxWidth - paddingWidth); | ||||||
|  |  | ||||||
|         return new Measurement( |         return new Measurement( | ||||||
|             measurement.Min + paddingWidth, |             measurement.Min + paddingWidth, | ||||||
| @@ -40,14 +40,14 @@ public sealed class Padder : Renderable, IPaddable, IExpandable | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     /// <inheritdoc/> |     /// <inheritdoc/> | ||||||
|     protected override IEnumerable<Segment> Render(RenderContext context, int maxWidth) |     protected override IEnumerable<Segment> Render(RenderOptions options, int maxWidth) | ||||||
|     { |     { | ||||||
|         var paddingWidth = Padding?.GetWidth() ?? 0; |         var paddingWidth = Padding?.GetWidth() ?? 0; | ||||||
|         var childWidth = maxWidth - paddingWidth; |         var childWidth = maxWidth - paddingWidth; | ||||||
|  |  | ||||||
|         if (!Expand) |         if (!Expand) | ||||||
|         { |         { | ||||||
|             var measurement = _child.Measure(context, maxWidth - paddingWidth); |             var measurement = _child.Measure(options, maxWidth - paddingWidth); | ||||||
|             childWidth = measurement.Max; |             childWidth = measurement.Max; | ||||||
|         } |         } | ||||||
|  |  | ||||||
| @@ -66,7 +66,7 @@ public sealed class Padder : Renderable, IPaddable, IExpandable | |||||||
|             result.Add(Segment.LineBreak); |             result.Add(Segment.LineBreak); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         var child = _child.Render(context, maxWidth - paddingWidth); |         var child = _child.Render(options, maxWidth - paddingWidth); | ||||||
|         foreach (var line in Segment.SplitLines(child)) |         foreach (var line in Segment.SplitLines(child)) | ||||||
|         { |         { | ||||||
|             // Left padding |             // Left padding | ||||||
|   | |||||||
| @@ -35,6 +35,16 @@ public sealed class Panel : Renderable, IHasBoxBorder, IHasBorder, IExpandable, | |||||||
|     /// </summary> |     /// </summary> | ||||||
|     public PanelHeader? Header { get; set; } |     public PanelHeader? Header { get; set; } | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// Gets or sets the width of the panel. | ||||||
|  |     /// </summary> | ||||||
|  |     public int? Width { get; set; } | ||||||
|  |  | ||||||
|  |     /// <summary> | ||||||
|  |     /// Gets or sets the height of the panel. | ||||||
|  |     /// </summary> | ||||||
|  |     public int? Height { get; set; } | ||||||
|  |  | ||||||
|     /// <summary> |     /// <summary> | ||||||
|     /// Gets or sets a value indicating whether or not the panel is inlined. |     /// Gets or sets a value indicating whether or not the panel is inlined. | ||||||
|     /// </summary> |     /// </summary> | ||||||
| @@ -59,54 +69,73 @@ public sealed class Panel : Renderable, IHasBoxBorder, IHasBorder, IExpandable, | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     /// <inheritdoc/> |     /// <inheritdoc/> | ||||||
|     protected override Measurement Measure(RenderContext context, int maxWidth) |     protected override Measurement Measure(RenderOptions options, int maxWidth) | ||||||
|     { |     { | ||||||
|         var child = new Padder(_child, Padding); |         var child = new Padder(_child, Padding); | ||||||
|         var childWidth = ((IRenderable)child).Measure(context, maxWidth); |         return Measure(options, maxWidth, child); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     private Measurement Measure(RenderOptions options, int maxWidth, IRenderable child) | ||||||
|  |     { | ||||||
|  |         var edgeWidth = (options.GetSafeBorder(this) is not NoBoxBorder) ? EdgeWidth : 0; | ||||||
|  |         var childWidth = child.Measure(options, maxWidth - edgeWidth); | ||||||
|  |  | ||||||
|  |         if (Width != null) | ||||||
|  |         { | ||||||
|  |             var width = Width.Value - edgeWidth; | ||||||
|  |             if (width > childWidth.Max) | ||||||
|  |             { | ||||||
|  |                 childWidth = new Measurement( | ||||||
|  |                     childWidth.Min, | ||||||
|  |                     width); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |  | ||||||
|         return new Measurement( |         return new Measurement( | ||||||
|             childWidth.Min + EdgeWidth, |             childWidth.Min + edgeWidth, | ||||||
|             childWidth.Max + EdgeWidth); |             childWidth.Max + edgeWidth); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     /// <inheritdoc/> |     /// <inheritdoc/> | ||||||
|     protected override IEnumerable<Segment> Render(RenderContext context, int maxWidth) |     protected override IEnumerable<Segment> Render(RenderOptions options, int maxWidth) | ||||||
|     { |     { | ||||||
|         var edgeWidth = EdgeWidth; |         var border = options.GetSafeBorder(this); | ||||||
|  |  | ||||||
|         var border = BoxExtensions.GetSafeBorder(Border, !context.Unicode && UseSafeBorder); |  | ||||||
|         var borderStyle = BorderStyle ?? Style.Plain; |         var borderStyle = BorderStyle ?? Style.Plain; | ||||||
|  |  | ||||||
|         var showBorder = true; |         var showBorder = border is not NoBoxBorder; | ||||||
|         if (border is NoBoxBorder) |         var edgeWidth = showBorder ? EdgeWidth : 0; | ||||||
|         { |  | ||||||
|             showBorder = false; |  | ||||||
|             edgeWidth = 0; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         var child = new Padder(_child, Padding); |         var child = new Padder(_child, Padding); | ||||||
|         var childWidth = maxWidth - edgeWidth; |         var width = Measure(options, maxWidth, child); | ||||||
|  |  | ||||||
|  |         var panelWidth = Math.Min(!Expand ? width.Max : maxWidth, maxWidth); | ||||||
|  |         var innerWidth = panelWidth - edgeWidth; | ||||||
|  |  | ||||||
|  |         var height = Height != null | ||||||
|  |             ? Height - 2 | ||||||
|  |             : options.Height != null | ||||||
|  |                 ? options.Height - 2 | ||||||
|  |                 : null; | ||||||
|  |  | ||||||
|         if (!Expand) |         if (!Expand) | ||||||
|         { |         { | ||||||
|             var measurement = ((IRenderable)child).Measure(context, maxWidth - edgeWidth); |             // Set the height to the explicit height (or null) | ||||||
|             childWidth = measurement.Max; |             // if the panel isn't expandable. | ||||||
|  |             height = Height != null ? Height - 2 : null; | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         var panelWidth = childWidth + edgeWidth; |         // Start building the panel | ||||||
|         panelWidth = Math.Min(panelWidth, maxWidth); |  | ||||||
|         childWidth = panelWidth - edgeWidth; |  | ||||||
|  |  | ||||||
|         var result = new List<Segment>(); |         var result = new List<Segment>(); | ||||||
|  |  | ||||||
|  |         // Panel top | ||||||
|         if (showBorder) |         if (showBorder) | ||||||
|         { |         { | ||||||
|             // Panel top |             AddTopBorder(result, options, border, borderStyle, panelWidth); | ||||||
|             AddTopBorder(result, context, border, borderStyle, panelWidth); |  | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         // Split the child segments into lines. |         // Split the child segments into lines. | ||||||
|         var childSegments = ((IRenderable)child).Render(context, childWidth); |         var childSegments = ((IRenderable)child).Render(options with { Height = height }, innerWidth); | ||||||
|         foreach (var (_, _, last, line) in Segment.SplitLines(childSegments, childWidth).Enumerate()) |         foreach (var (_, _, last, line) in Segment.SplitLines(childSegments, innerWidth, height).Enumerate()) | ||||||
|         { |         { | ||||||
|             if (line.Count == 1 && line[0].IsWhiteSpace) |             if (line.Count == 1 && line[0].IsWhiteSpace) | ||||||
|             { |             { | ||||||
| @@ -125,9 +154,9 @@ public sealed class Panel : Renderable, IHasBoxBorder, IHasBorder, IExpandable, | |||||||
|  |  | ||||||
|             // Do we need to pad the panel? |             // Do we need to pad the panel? | ||||||
|             var length = line.Sum(segment => segment.CellCount()); |             var length = line.Sum(segment => segment.CellCount()); | ||||||
|             if (length < childWidth) |             if (length < innerWidth) | ||||||
|             { |             { | ||||||
|                 var diff = childWidth - length; |                 var diff = innerWidth - length; | ||||||
|                 content.Add(Segment.Padding(diff)); |                 content.Add(Segment.Padding(diff)); | ||||||
|             } |             } | ||||||
|  |  | ||||||
| @@ -170,7 +199,7 @@ public sealed class Panel : Renderable, IHasBoxBorder, IHasBorder, IExpandable, | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     private void AddTopBorder( |     private void AddTopBorder( | ||||||
|         List<Segment> result, RenderContext context, BoxBorder border, |         List<Segment> result, RenderOptions options, BoxBorder border, | ||||||
|         Style borderStyle, int panelWidth) |         Style borderStyle, int panelWidth) | ||||||
|     { |     { | ||||||
|         var rule = new Rule |         var rule = new Rule | ||||||
| @@ -180,14 +209,14 @@ public sealed class Panel : Renderable, IHasBoxBorder, IHasBorder, IExpandable, | |||||||
|             TitlePadding = 1, |             TitlePadding = 1, | ||||||
|             TitleSpacing = 0, |             TitleSpacing = 0, | ||||||
|             Title = Header?.Text, |             Title = Header?.Text, | ||||||
|             Alignment = Header?.Alignment ?? Justify.Left, |             Justification = Header?.Justification ?? Justify.Left, | ||||||
|         }; |         }; | ||||||
|  |  | ||||||
|         // Top left border |         // Top left border | ||||||
|         result.Add(new Segment(border.GetPart(BoxBorderPart.TopLeft), borderStyle)); |         result.Add(new Segment(border.GetPart(BoxBorderPart.TopLeft), borderStyle)); | ||||||
|  |  | ||||||
|         // Top border (and header text if specified) |         // Top border (and header text if specified) | ||||||
|         result.AddRange(((IRenderable)rule).Render(context, panelWidth - 2).Where(x => !x.IsLineBreak)); |         result.AddRange(((IRenderable)rule).Render(options, panelWidth - 2).Where(x => !x.IsLineBreak)); | ||||||
|  |  | ||||||
|         // Top right border |         // Top right border | ||||||
|         result.Add(new Segment(border.GetPart(BoxBorderPart.TopRight), borderStyle)); |         result.Add(new Segment(border.GetPart(BoxBorderPart.TopRight), borderStyle)); | ||||||
|   | |||||||
| @@ -3,7 +3,7 @@ namespace Spectre.Console; | |||||||
| /// <summary> | /// <summary> | ||||||
| /// Represents a panel header. | /// Represents a panel header. | ||||||
| /// </summary> | /// </summary> | ||||||
| public sealed class PanelHeader : IAlignable | public sealed class PanelHeader : IHasJustification | ||||||
| { | { | ||||||
|     /// <summary> |     /// <summary> | ||||||
|     /// Gets the panel header text. |     /// Gets the panel header text. | ||||||
| @@ -13,7 +13,7 @@ public sealed class PanelHeader : IAlignable | |||||||
|     /// <summary> |     /// <summary> | ||||||
|     /// Gets or sets the panel header alignment. |     /// Gets or sets the panel header alignment. | ||||||
|     /// </summary> |     /// </summary> | ||||||
|     public Justify? Alignment { get; set; } |     public Justify? Justification { get; set; } | ||||||
|  |  | ||||||
|     /// <summary> |     /// <summary> | ||||||
|     /// Initializes a new instance of the <see cref="PanelHeader"/> class. |     /// Initializes a new instance of the <see cref="PanelHeader"/> class. | ||||||
| @@ -23,7 +23,7 @@ public sealed class PanelHeader : IAlignable | |||||||
|     public PanelHeader(string text, Justify? alignment = null) |     public PanelHeader(string text, Justify? alignment = null) | ||||||
|     { |     { | ||||||
|         Text = text ?? throw new ArgumentNullException(nameof(text)); |         Text = text ?? throw new ArgumentNullException(nameof(text)); | ||||||
|         Alignment = alignment; |         Justification = alignment; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     /// <summary> |     /// <summary> | ||||||
| @@ -57,7 +57,7 @@ public sealed class PanelHeader : IAlignable | |||||||
|     /// <returns>The same instance so that multiple calls can be chained.</returns> |     /// <returns>The same instance so that multiple calls can be chained.</returns> | ||||||
|     public PanelHeader SetAlignment(Justify alignment) |     public PanelHeader SetAlignment(Justify alignment) | ||||||
|     { |     { | ||||||
|         Alignment = alignment; |         Justification = alignment; | ||||||
|         return this; |         return this; | ||||||
|     } |     } | ||||||
| } | } | ||||||
| @@ -5,14 +5,14 @@ namespace Spectre.Console; | |||||||
| /// of the paragraph can have individual styling. | /// of the paragraph can have individual styling. | ||||||
| /// </summary> | /// </summary> | ||||||
| [DebuggerDisplay("{_text,nq}")] | [DebuggerDisplay("{_text,nq}")] | ||||||
| public sealed class Paragraph : Renderable, IAlignable, IOverflowable | public sealed class Paragraph : Renderable, IHasJustification, IOverflowable | ||||||
| { | { | ||||||
|     private readonly List<SegmentLine> _lines; |     private readonly List<SegmentLine> _lines; | ||||||
|  |  | ||||||
|     /// <summary> |     /// <summary> | ||||||
|     /// Gets or sets the alignment of the whole paragraph. |     /// Gets or sets the alignment of the whole paragraph. | ||||||
|     /// </summary> |     /// </summary> | ||||||
|     public Justify? Alignment { get; set; } |     public Justify? Justification { get; set; } | ||||||
|  |  | ||||||
|     /// <summary> |     /// <summary> | ||||||
|     /// Gets or sets the text overflow strategy. |     /// Gets or sets the text overflow strategy. | ||||||
| @@ -115,7 +115,7 @@ public sealed class Paragraph : Renderable, IAlignable, IOverflowable | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     /// <inheritdoc/> |     /// <inheritdoc/> | ||||||
|     protected override Measurement Measure(RenderContext context, int maxWidth) |     protected override Measurement Measure(RenderOptions options, int maxWidth) | ||||||
|     { |     { | ||||||
|         if (_lines.Count == 0) |         if (_lines.Count == 0) | ||||||
|         { |         { | ||||||
| @@ -129,11 +129,11 @@ public sealed class Paragraph : Renderable, IAlignable, IOverflowable | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     /// <inheritdoc/> |     /// <inheritdoc/> | ||||||
|     protected override IEnumerable<Segment> Render(RenderContext context, int maxWidth) |     protected override IEnumerable<Segment> Render(RenderOptions options, int maxWidth) | ||||||
|     { |     { | ||||||
|         if (context is null) |         if (options is null) | ||||||
|         { |         { | ||||||
|             throw new ArgumentNullException(nameof(context)); |             throw new ArgumentNullException(nameof(options)); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         if (_lines.Count == 0) |         if (_lines.Count == 0) | ||||||
| @@ -141,13 +141,13 @@ public sealed class Paragraph : Renderable, IAlignable, IOverflowable | |||||||
|             return Array.Empty<Segment>(); |             return Array.Empty<Segment>(); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         var lines = context.SingleLine |         var lines = options.SingleLine | ||||||
|             ? new List<SegmentLine>(_lines) |             ? new List<SegmentLine>(_lines) | ||||||
|             : SplitLines(maxWidth); |             : SplitLines(maxWidth); | ||||||
|  |  | ||||||
|         // Justify lines |         // Justify lines | ||||||
|         var justification = context.Justification ?? Alignment ?? Justify.Left; |         var justification = options.Justification ?? Justification ?? Console.Justify.Left; | ||||||
|         if (justification != Justify.Left) |         if (justification != Console.Justify.Left) | ||||||
|         { |         { | ||||||
|             foreach (var line in lines) |             foreach (var line in lines) | ||||||
|             { |             { | ||||||
| @@ -155,7 +155,7 @@ public sealed class Paragraph : Renderable, IAlignable, IOverflowable | |||||||
|             } |             } | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         if (context.SingleLine) |         if (options.SingleLine) | ||||||
|         { |         { | ||||||
|             // Return the first line |             // Return the first line | ||||||
|             return lines[0].Where(segment => !segment.IsLineBreak); |             return lines[0].Where(segment => !segment.IsLineBreak); | ||||||
|   | |||||||
| @@ -23,13 +23,13 @@ internal sealed class ProgressBar : Renderable, IHasCulture | |||||||
|  |  | ||||||
|     internal static Style DefaultPulseStyle { get; } = new Style(foreground: Color.DodgerBlue1, background: Color.Grey23); |     internal static Style DefaultPulseStyle { get; } = new Style(foreground: Color.DodgerBlue1, background: Color.Grey23); | ||||||
|  |  | ||||||
|     protected override Measurement Measure(RenderContext context, int maxWidth) |     protected override Measurement Measure(RenderOptions options, int maxWidth) | ||||||
|     { |     { | ||||||
|         var width = Math.Min(Width ?? maxWidth, maxWidth); |         var width = Math.Min(Width ?? maxWidth, maxWidth); | ||||||
|         return new Measurement(4, width); |         return new Measurement(4, width); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     protected override IEnumerable<Segment> Render(RenderContext context, int maxWidth) |     protected override IEnumerable<Segment> Render(RenderOptions options, int maxWidth) | ||||||
|     { |     { | ||||||
|         var width = Math.Min(Width ?? maxWidth, maxWidth); |         var width = Math.Min(Width ?? maxWidth, maxWidth); | ||||||
|         var completedBarCount = Math.Min(MaxValue, Math.Max(0, Value)); |         var completedBarCount = Math.Min(MaxValue, Math.Max(0, Value)); | ||||||
| @@ -37,7 +37,7 @@ internal sealed class ProgressBar : Renderable, IHasCulture | |||||||
|  |  | ||||||
|         if (IsIndeterminate && !isCompleted) |         if (IsIndeterminate && !isCompleted) | ||||||
|         { |         { | ||||||
|             foreach (var segment in RenderIndeterminate(context, width)) |             foreach (var segment in RenderIndeterminate(options, width)) | ||||||
|             { |             { | ||||||
|                 yield return segment; |                 yield return segment; | ||||||
|             } |             } | ||||||
| @@ -45,7 +45,7 @@ internal sealed class ProgressBar : Renderable, IHasCulture | |||||||
|             yield break; |             yield break; | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         var bar = !context.Unicode ? AsciiBar : UnicodeBar; |         var bar = !options.Unicode ? AsciiBar : UnicodeBar; | ||||||
|         var style = isCompleted ? FinishedStyle : CompletedStyle; |         var style = isCompleted ? FinishedStyle : CompletedStyle; | ||||||
|         var barCount = Math.Max(0, (int)(width * (completedBarCount / MaxValue))); |         var barCount = Math.Max(0, (int)(width * (completedBarCount / MaxValue))); | ||||||
|  |  | ||||||
| @@ -84,29 +84,29 @@ internal sealed class ProgressBar : Renderable, IHasCulture | |||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
|  |  | ||||||
|             var legacy = context.ColorSystem == ColorSystem.NoColors || context.ColorSystem == ColorSystem.Legacy; |             var legacy = options.ColorSystem == ColorSystem.NoColors || options.ColorSystem == ColorSystem.Legacy; | ||||||
|             var remainingToken = ShowRemaining && !legacy ? bar : ' '; |             var remainingToken = ShowRemaining && !legacy ? bar : ' '; | ||||||
|             yield return new Segment(new string(remainingToken, diff), RemainingStyle); |             yield return new Segment(new string(remainingToken, diff), RemainingStyle); | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     private IEnumerable<Segment> RenderIndeterminate(RenderContext context, int width) |     private IEnumerable<Segment> RenderIndeterminate(RenderOptions options, int width) | ||||||
|     { |     { | ||||||
|         var bar = context.Unicode ? UnicodeBar.ToString() : AsciiBar.ToString(); |         var bar = options.Unicode ? UnicodeBar.ToString() : AsciiBar.ToString(); | ||||||
|         var style = IndeterminateStyle ?? DefaultPulseStyle; |         var style = IndeterminateStyle ?? DefaultPulseStyle; | ||||||
|  |  | ||||||
|         IEnumerable<Segment> GetPulseSegments() |         IEnumerable<Segment> GetPulseSegments() | ||||||
|         { |         { | ||||||
|             // For 1-bit and 3-bit colors, fall back to |             // For 1-bit and 3-bit colors, fall back to | ||||||
|             // a simpler versions with only two colors. |             // a simpler versions with only two colors. | ||||||
|             if (context.ColorSystem == ColorSystem.NoColors || |             if (options.ColorSystem == ColorSystem.NoColors || | ||||||
|                 context.ColorSystem == ColorSystem.Legacy) |                 options.ColorSystem == ColorSystem.Legacy) | ||||||
|             { |             { | ||||||
|                 // First half of the pulse |                 // First half of the pulse | ||||||
|                 var segments = Enumerable.Repeat(new Segment(bar, new Style(style.Foreground)), PULSESIZE / 2); |                 var segments = Enumerable.Repeat(new Segment(bar, new Style(style.Foreground)), PULSESIZE / 2); | ||||||
|  |  | ||||||
|                 // Second half of the pulse |                 // Second half of the pulse | ||||||
|                 var legacy = context.ColorSystem == ColorSystem.NoColors || context.ColorSystem == ColorSystem.Legacy; |                 var legacy = options.ColorSystem == ColorSystem.NoColors || options.ColorSystem == ColorSystem.Legacy; | ||||||
|                 var bar2 = legacy ? " " : bar; |                 var bar2 = legacy ? " " : bar; | ||||||
|                 segments = segments.Concat(Enumerable.Repeat(new Segment(bar2, new Style(style.Background)), PULSESIZE - (PULSESIZE / 2))); |                 segments = segments.Concat(Enumerable.Repeat(new Segment(bar2, new Style(style.Background)), PULSESIZE - (PULSESIZE / 2))); | ||||||
|  |  | ||||||
|   | |||||||
| @@ -29,7 +29,7 @@ public sealed class Rows : Renderable, IExpandable | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     /// <inheritdoc/> |     /// <inheritdoc/> | ||||||
|     protected override Measurement Measure(RenderContext context, int maxWidth) |     protected override Measurement Measure(RenderOptions options, int maxWidth) | ||||||
|     { |     { | ||||||
|         if (Expand) |         if (Expand) | ||||||
|         { |         { | ||||||
| @@ -37,7 +37,7 @@ public sealed class Rows : Renderable, IExpandable | |||||||
|         } |         } | ||||||
|         else |         else | ||||||
|         { |         { | ||||||
|             var measurements = _children.Select(c => c.Measure(context, maxWidth)); |             var measurements = _children.Select(c => c.Measure(options, maxWidth)); | ||||||
|             return new Measurement( |             return new Measurement( | ||||||
|                 measurements.Min(c => c.Min), |                 measurements.Min(c => c.Min), | ||||||
|                 measurements.Min(c => c.Max)); |                 measurements.Min(c => c.Max)); | ||||||
| @@ -45,13 +45,13 @@ public sealed class Rows : Renderable, IExpandable | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     /// <inheritdoc/> |     /// <inheritdoc/> | ||||||
|     protected override IEnumerable<Segment> Render(RenderContext context, int maxWidth) |     protected override IEnumerable<Segment> Render(RenderOptions options, int maxWidth) | ||||||
|     { |     { | ||||||
|         var result = new List<Segment>(); |         var result = new List<Segment>(); | ||||||
|  |  | ||||||
|         foreach (var child in _children) |         foreach (var child in _children) | ||||||
|         { |         { | ||||||
|             var segments = child.Render(context, maxWidth); |             var segments = child.Render(options, maxWidth); | ||||||
|             foreach (var (_, _, last, segment) in segments.Enumerate()) |             foreach (var (_, _, last, segment) in segments.Enumerate()) | ||||||
|             { |             { | ||||||
|                 result.Add(segment); |                 result.Add(segment); | ||||||
|   | |||||||
| @@ -3,7 +3,7 @@ namespace Spectre.Console; | |||||||
| /// <summary> | /// <summary> | ||||||
| /// A renderable horizontal rule. | /// A renderable horizontal rule. | ||||||
| /// </summary> | /// </summary> | ||||||
| public sealed class Rule : Renderable, IAlignable, IHasBoxBorder | public sealed class Rule : Renderable, IHasJustification, IHasBoxBorder | ||||||
| { | { | ||||||
|     /// <summary> |     /// <summary> | ||||||
|     /// Gets or sets the rule title markup text. |     /// Gets or sets the rule title markup text. | ||||||
| @@ -16,9 +16,9 @@ public sealed class Rule : Renderable, IAlignable, IHasBoxBorder | |||||||
|     public Style? Style { get; set; } |     public Style? Style { get; set; } | ||||||
|  |  | ||||||
|     /// <summary> |     /// <summary> | ||||||
|     /// Gets or sets the rule's title alignment. |     /// Gets or sets the rule's title justification. | ||||||
|     /// </summary> |     /// </summary> | ||||||
|     public Justify? Alignment { get; set; } |     public Justify? Justification { get; set; } | ||||||
|  |  | ||||||
|     /// <inheritdoc/> |     /// <inheritdoc/> | ||||||
|     public BoxBorder Border { get; set; } = BoxBorder.Square; |     public BoxBorder Border { get; set; } = BoxBorder.Square; | ||||||
| @@ -43,17 +43,17 @@ public sealed class Rule : Renderable, IAlignable, IHasBoxBorder | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     /// <inheritdoc/> |     /// <inheritdoc/> | ||||||
|     protected override IEnumerable<Segment> Render(RenderContext context, int maxWidth) |     protected override IEnumerable<Segment> Render(RenderOptions options, int maxWidth) | ||||||
|     { |     { | ||||||
|         var extraLength = (2 * TitlePadding) + (2 * TitleSpacing); |         var extraLength = (2 * TitlePadding) + (2 * TitleSpacing); | ||||||
|  |  | ||||||
|         if (Title == null || maxWidth <= extraLength) |         if (Title == null || maxWidth <= extraLength) | ||||||
|         { |         { | ||||||
|             return GetLineWithoutTitle(context, maxWidth); |             return GetLineWithoutTitle(options, maxWidth); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         // Get the title and make sure it fits. |         // Get the title and make sure it fits. | ||||||
|         var title = GetTitleSegments(context, Title, maxWidth - extraLength); |         var title = GetTitleSegments(options, Title, maxWidth - extraLength); | ||||||
|         if (Segment.CellCount(title) > maxWidth - extraLength) |         if (Segment.CellCount(title) > maxWidth - extraLength) | ||||||
|         { |         { | ||||||
|             // Truncate the title |             // Truncate the title | ||||||
| @@ -61,11 +61,11 @@ public sealed class Rule : Renderable, IAlignable, IHasBoxBorder | |||||||
|             if (!title.Any()) |             if (!title.Any()) | ||||||
|             { |             { | ||||||
|                 // We couldn't fit the title at all. |                 // We couldn't fit the title at all. | ||||||
|                 return GetLineWithoutTitle(context, maxWidth); |                 return GetLineWithoutTitle(options, maxWidth); | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         var (left, right) = GetLineSegments(context, maxWidth, title); |         var (left, right) = GetLineSegments(options, maxWidth, title); | ||||||
|  |  | ||||||
|         var segments = new List<Segment>(); |         var segments = new List<Segment>(); | ||||||
|         segments.Add(left); |         segments.Add(left); | ||||||
| @@ -76,9 +76,9 @@ public sealed class Rule : Renderable, IAlignable, IHasBoxBorder | |||||||
|         return segments; |         return segments; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     private IEnumerable<Segment> GetLineWithoutTitle(RenderContext context, int maxWidth) |     private IEnumerable<Segment> GetLineWithoutTitle(RenderOptions options, int maxWidth) | ||||||
|     { |     { | ||||||
|         var border = Border.GetSafeBorder(safe: !context.Unicode); |         var border = Border.GetSafeBorder(safe: !options.Unicode); | ||||||
|         var text = border.GetPart(BoxBorderPart.Top).Repeat(maxWidth); |         var text = border.GetPart(BoxBorderPart.Top).Repeat(maxWidth); | ||||||
|  |  | ||||||
|         return new[] |         return new[] | ||||||
| @@ -88,21 +88,21 @@ public sealed class Rule : Renderable, IAlignable, IHasBoxBorder | |||||||
|         }; |         }; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     private IEnumerable<Segment> GetTitleSegments(RenderContext context, string title, int width) |     private IEnumerable<Segment> GetTitleSegments(RenderOptions options, string title, int width) | ||||||
|     { |     { | ||||||
|         title = title.NormalizeNewLines().ReplaceExact("\n", " ").Trim(); |         title = title.NormalizeNewLines().ReplaceExact("\n", " ").Trim(); | ||||||
|         var markup = new Markup(title, Style); |         var markup = new Markup(title, Style); | ||||||
|         return ((IRenderable)markup).Render(context.WithSingleLine(), width); |         return ((IRenderable)markup).Render(options with { SingleLine = true }, width); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     private (Segment Left, Segment Right) GetLineSegments(RenderContext context, int width, IEnumerable<Segment> title) |     private (Segment Left, Segment Right) GetLineSegments(RenderOptions options, int width, IEnumerable<Segment> title) | ||||||
|     { |     { | ||||||
|         var titleLength = Segment.CellCount(title); |         var titleLength = Segment.CellCount(title); | ||||||
|  |  | ||||||
|         var border = Border.GetSafeBorder(safe: !context.Unicode); |         var border = Border.GetSafeBorder(safe: !options.Unicode); | ||||||
|         var borderPart = border.GetPart(BoxBorderPart.Top); |         var borderPart = border.GetPart(BoxBorderPart.Top); | ||||||
|  |  | ||||||
|         var alignment = Alignment ?? Justify.Center; |         var alignment = Justification ?? Justify.Center; | ||||||
|         if (alignment == Justify.Left) |         if (alignment == Justify.Left) | ||||||
|         { |         { | ||||||
|             var left = new Segment(borderPart.Repeat(TitlePadding) + new string(' ', TitleSpacing), Style ?? Style.Plain); |             var left = new Segment(borderPart.Repeat(TitlePadding) + new string(' ', TitleSpacing), Style ?? Style.Plain); | ||||||
|   | |||||||
| @@ -59,6 +59,7 @@ public sealed class Table : Renderable, IHasTableBorder, IExpandable, IAlignable | |||||||
|     public TableTitle? Caption { get; set; } |     public TableTitle? Caption { get; set; } | ||||||
|  |  | ||||||
|     /// <inheritdoc/> |     /// <inheritdoc/> | ||||||
|  |     [Obsolete("Use the Align widget instead. This property will be removed in a later release.")] | ||||||
|     public Justify? Alignment { get; set; } |     public Justify? Alignment { get; set; } | ||||||
|  |  | ||||||
|     // Whether this is a grid or not. |     // Whether this is a grid or not. | ||||||
| @@ -100,14 +101,14 @@ public sealed class Table : Renderable, IHasTableBorder, IExpandable, IAlignable | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     /// <inheritdoc/> |     /// <inheritdoc/> | ||||||
|     protected override Measurement Measure(RenderContext context, int maxWidth) |     protected override Measurement Measure(RenderOptions options, int maxWidth) | ||||||
|     { |     { | ||||||
|         if (context is null) |         if (options is null) | ||||||
|         { |         { | ||||||
|             throw new ArgumentNullException(nameof(context)); |             throw new ArgumentNullException(nameof(options)); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         var measurer = new TableMeasurer(this, context); |         var measurer = new TableMeasurer(this, options); | ||||||
|  |  | ||||||
|         // Calculate the total cell width |         // Calculate the total cell width | ||||||
|         var totalCellWidth = measurer.CalculateTotalCellWidth(maxWidth); |         var totalCellWidth = measurer.CalculateTotalCellWidth(maxWidth); | ||||||
| @@ -120,14 +121,14 @@ public sealed class Table : Renderable, IHasTableBorder, IExpandable, IAlignable | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     /// <inheritdoc/> |     /// <inheritdoc/> | ||||||
|     protected override IEnumerable<Segment> Render(RenderContext context, int maxWidth) |     protected override IEnumerable<Segment> Render(RenderOptions options, int maxWidth) | ||||||
|     { |     { | ||||||
|         if (context is null) |         if (options is null) | ||||||
|         { |         { | ||||||
|             throw new ArgumentNullException(nameof(context)); |             throw new ArgumentNullException(nameof(options)); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         var measurer = new TableMeasurer(this, context); |         var measurer = new TableMeasurer(this, options); | ||||||
|  |  | ||||||
|         // Calculate the column and table width |         // Calculate the column and table width | ||||||
|         var totalCellWidth = measurer.CalculateTotalCellWidth(maxWidth); |         var totalCellWidth = measurer.CalculateTotalCellWidth(maxWidth); | ||||||
| @@ -139,7 +140,7 @@ public sealed class Table : Renderable, IHasTableBorder, IExpandable, IAlignable | |||||||
|  |  | ||||||
|         // Render the table |         // Render the table | ||||||
|         return TableRenderer.Render( |         return TableRenderer.Render( | ||||||
|             new TableRendererContext(this, context, rows, tableWidth, maxWidth), |             new TableRendererContext(this, options, rows, tableWidth, maxWidth), | ||||||
|             columnWidths); |             columnWidths); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -4,12 +4,12 @@ internal abstract class TableAccessor | |||||||
| { | { | ||||||
|     private readonly Table _table; |     private readonly Table _table; | ||||||
|  |  | ||||||
|     public RenderContext Options { get; } |     public RenderOptions Options { get; } | ||||||
|     public IReadOnlyList<TableColumn> Columns => _table.Columns; |     public IReadOnlyList<TableColumn> Columns => _table.Columns; | ||||||
|     public virtual IReadOnlyList<TableRow> Rows => _table.Rows; |     public virtual IReadOnlyList<TableRow> Rows => _table.Rows; | ||||||
|     public bool Expand => _table.Expand || _table.Width != null; |     public bool Expand => _table.Expand || _table.Width != null; | ||||||
|  |  | ||||||
|     protected TableAccessor(Table table, RenderContext options) |     protected TableAccessor(Table table, RenderOptions options) | ||||||
|     { |     { | ||||||
|         _table = table ?? throw new ArgumentNullException(nameof(table)); |         _table = table ?? throw new ArgumentNullException(nameof(table)); | ||||||
|         Options = options ?? throw new ArgumentNullException(nameof(options)); |         Options = options ?? throw new ArgumentNullException(nameof(options)); | ||||||
|   | |||||||
| @@ -8,7 +8,7 @@ internal sealed class TableMeasurer : TableAccessor | |||||||
|     private readonly TableBorder _border; |     private readonly TableBorder _border; | ||||||
|     private readonly bool _padRightCell; |     private readonly bool _padRightCell; | ||||||
|  |  | ||||||
|     public TableMeasurer(Table table, RenderContext options) |     public TableMeasurer(Table table, RenderOptions options) | ||||||
|         : base(table, options) |         : base(table, options) | ||||||
|     { |     { | ||||||
|         _explicitWidth = table.Width; |         _explicitWidth = table.Width; | ||||||
|   | |||||||
| @@ -26,7 +26,7 @@ internal static class TableRenderer | |||||||
|             foreach (var (columnIndex, _, _, (rowWidth, cell)) in columnWidths.Zip(row).Enumerate()) |             foreach (var (columnIndex, _, _, (rowWidth, cell)) in columnWidths.Zip(row).Enumerate()) | ||||||
|             { |             { | ||||||
|                 var justification = context.Columns[columnIndex].Alignment; |                 var justification = context.Columns[columnIndex].Alignment; | ||||||
|                 var childContext = context.Options.WithJustification(justification); |                 var childContext = context.Options with { Justification = justification }; | ||||||
|  |  | ||||||
|                 var lines = Segment.SplitLines(cell.Render(childContext, rowWidth)); |                 var lines = Segment.SplitLines(cell.Render(childContext, rowWidth)); | ||||||
|                 cellHeight = Math.Max(cellHeight, lines.Count); |                 cellHeight = Math.Max(cellHeight, lines.Count); | ||||||
| @@ -159,7 +159,7 @@ internal static class TableRenderer | |||||||
|         } |         } | ||||||
|  |  | ||||||
|         var paragraph = new Markup(header.Text, header.Style ?? defaultStyle) |         var paragraph = new Markup(header.Text, header.Style ?? defaultStyle) | ||||||
|             .Alignment(Justify.Center) |             .Justify(Justify.Center) | ||||||
|             .Overflow(Overflow.Ellipsis); |             .Overflow(Overflow.Ellipsis); | ||||||
|  |  | ||||||
|         // Render the paragraphs |         // Render the paragraphs | ||||||
|   | |||||||
| @@ -31,9 +31,12 @@ internal sealed class TableRendererContext : TableAccessor | |||||||
|     public bool PadRightCell => _table.PadRightCell; |     public bool PadRightCell => _table.PadRightCell; | ||||||
|     public TableTitle? Title => _table.Title; |     public TableTitle? Title => _table.Title; | ||||||
|     public TableTitle? Caption => _table.Caption; |     public TableTitle? Caption => _table.Caption; | ||||||
|     public Justify? Alignment => _table.Alignment; |  | ||||||
|  |  | ||||||
|     public TableRendererContext(Table table, RenderContext options, IEnumerable<TableRow> rows, int tableWidth, int maxWidth) | #pragma warning disable CS0618 // Type or member is obsolete | ||||||
|  |     public Justify? Alignment => _table.Alignment; | ||||||
|  | #pragma warning restore CS0618 // Type or member is obsolete | ||||||
|  |  | ||||||
|  |     public TableRendererContext(Table table, RenderOptions options, IEnumerable<TableRow> rows, int tableWidth, int maxWidth) | ||||||
|         : base(table, options) |         : base(table, options) | ||||||
|     { |     { | ||||||
|         _table = table ?? throw new ArgumentNullException(nameof(table)); |         _table = table ?? throw new ArgumentNullException(nameof(table)); | ||||||
|   | |||||||
| @@ -5,7 +5,7 @@ namespace Spectre.Console; | |||||||
| /// </summary> | /// </summary> | ||||||
| [DebuggerDisplay("{_text,nq}")] | [DebuggerDisplay("{_text,nq}")] | ||||||
| [SuppressMessage("Naming", "CA1724:Type names should not match namespaces")] | [SuppressMessage("Naming", "CA1724:Type names should not match namespaces")] | ||||||
| public sealed class Text : Renderable, IAlignable, IOverflowable | public sealed class Text : Renderable, IHasJustification, IOverflowable | ||||||
| { | { | ||||||
|     private readonly Paragraph _paragraph; |     private readonly Paragraph _paragraph; | ||||||
|  |  | ||||||
| @@ -32,10 +32,10 @@ public sealed class Text : Renderable, IAlignable, IOverflowable | |||||||
|     /// <summary> |     /// <summary> | ||||||
|     /// Gets or sets the text alignment. |     /// Gets or sets the text alignment. | ||||||
|     /// </summary> |     /// </summary> | ||||||
|     public Justify? Alignment |     public Justify? Justification | ||||||
|     { |     { | ||||||
|         get => _paragraph.Alignment; |         get => _paragraph.Justification; | ||||||
|         set => _paragraph.Alignment = value; |         set => _paragraph.Justification = value; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     /// <summary> |     /// <summary> | ||||||
| @@ -58,14 +58,14 @@ public sealed class Text : Renderable, IAlignable, IOverflowable | |||||||
|     public int Lines => _paragraph.Lines; |     public int Lines => _paragraph.Lines; | ||||||
|  |  | ||||||
|     /// <inheritdoc/> |     /// <inheritdoc/> | ||||||
|     protected override Measurement Measure(RenderContext context, int maxWidth) |     protected override Measurement Measure(RenderOptions options, int maxWidth) | ||||||
|     { |     { | ||||||
|         return ((IRenderable)_paragraph).Measure(context, maxWidth); |         return ((IRenderable)_paragraph).Measure(options, maxWidth); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     /// <inheritdoc/> |     /// <inheritdoc/> | ||||||
|     protected override IEnumerable<Segment> Render(RenderContext context, int maxWidth) |     protected override IEnumerable<Segment> Render(RenderOptions options, int maxWidth) | ||||||
|     { |     { | ||||||
|         return ((IRenderable)_paragraph).Render(context, maxWidth); |         return ((IRenderable)_paragraph).Render(options, maxWidth); | ||||||
|     } |     } | ||||||
| } | } | ||||||
| @@ -3,7 +3,7 @@ namespace Spectre.Console; | |||||||
| /// <summary> | /// <summary> | ||||||
| /// Representation of a file system path. | /// Representation of a file system path. | ||||||
| /// </summary> | /// </summary> | ||||||
| public sealed class TextPath : IRenderable, IAlignable | public sealed class TextPath : IRenderable, IHasJustification | ||||||
| { | { | ||||||
|     private const string Ellipsis = "..."; |     private const string Ellipsis = "..."; | ||||||
|     private const string UnicodeEllipsis = "…"; |     private const string UnicodeEllipsis = "…"; | ||||||
| @@ -35,7 +35,7 @@ public sealed class TextPath : IRenderable, IAlignable | |||||||
|     /// <summary> |     /// <summary> | ||||||
|     /// Gets or sets the alignment. |     /// Gets or sets the alignment. | ||||||
|     /// </summary> |     /// </summary> | ||||||
|     public Justify? Alignment { get; set; } |     public Justify? Justification { get; set; } | ||||||
|  |  | ||||||
|     /// <summary> |     /// <summary> | ||||||
|     /// Initializes a new instance of the <see cref="TextPath"/> class. |     /// Initializes a new instance of the <see cref="TextPath"/> class. | ||||||
| @@ -66,9 +66,9 @@ public sealed class TextPath : IRenderable, IAlignable | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     /// <inheritdoc/> |     /// <inheritdoc/> | ||||||
|     public Measurement Measure(RenderContext context, int maxWidth) |     public Measurement Measure(RenderOptions options, int maxWidth) | ||||||
|     { |     { | ||||||
|         var fitted = Fit(context, maxWidth); |         var fitted = Fit(options, maxWidth); | ||||||
|         var separatorCount = fitted.Length - 1; |         var separatorCount = fitted.Length - 1; | ||||||
|         var length = fitted.Sum(f => f.Length) + separatorCount; |         var length = fitted.Sum(f => f.Length) + separatorCount; | ||||||
|  |  | ||||||
| @@ -78,16 +78,14 @@ public sealed class TextPath : IRenderable, IAlignable | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     /// <inheritdoc/> |     /// <inheritdoc/> | ||||||
|     public IEnumerable<Segment> Render(RenderContext context, int maxWidth) |     public IEnumerable<Segment> Render(RenderOptions options, int maxWidth) | ||||||
|     { |     { | ||||||
|         var alignment = Alignment ?? Justify.Left; |  | ||||||
|  |  | ||||||
|         var rootStyle = RootStyle ?? Style.Plain; |         var rootStyle = RootStyle ?? Style.Plain; | ||||||
|         var separatorStyle = SeparatorStyle ?? Style.Plain; |         var separatorStyle = SeparatorStyle ?? Style.Plain; | ||||||
|         var stemStyle = StemStyle ?? Style.Plain; |         var stemStyle = StemStyle ?? Style.Plain; | ||||||
|         var leafStyle = LeafStyle ?? Style.Plain; |         var leafStyle = LeafStyle ?? Style.Plain; | ||||||
|  |  | ||||||
|         var fitted = Fit(context, maxWidth); |         var fitted = Fit(options, maxWidth); | ||||||
|         var parts = new List<Segment>(); |         var parts = new List<Segment>(); | ||||||
|         foreach (var (_, first, last, item) in fitted.Enumerate()) |         foreach (var (_, first, last, item) in fitted.Enumerate()) | ||||||
|         { |         { | ||||||
| @@ -119,7 +117,7 @@ public sealed class TextPath : IRenderable, IAlignable | |||||||
|         } |         } | ||||||
|  |  | ||||||
|         // Align the result |         // Align the result | ||||||
|         Aligner.Align(parts, Alignment, maxWidth); |         Aligner.Align(parts, Justification, maxWidth); | ||||||
|  |  | ||||||
|         // Insert a line break |         // Insert a line break | ||||||
|         parts.Add(Segment.LineBreak); |         parts.Add(Segment.LineBreak); | ||||||
| @@ -127,7 +125,7 @@ public sealed class TextPath : IRenderable, IAlignable | |||||||
|         return parts; |         return parts; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     private string[] Fit(RenderContext context, int maxWidth) |     private string[] Fit(RenderOptions options, int maxWidth) | ||||||
|     { |     { | ||||||
|         // No parts? |         // No parts? | ||||||
|         if (_parts.Length == 0) |         if (_parts.Length == 0) | ||||||
| @@ -141,7 +139,7 @@ public sealed class TextPath : IRenderable, IAlignable | |||||||
|             return _parts; |             return _parts; | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         var ellipsis = context.Unicode ? UnicodeEllipsis : Ellipsis; |         var ellipsis = options.Unicode ? UnicodeEllipsis : Ellipsis; | ||||||
|         var ellipsisLength = Cell.GetCellLength(ellipsis); |         var ellipsisLength = Cell.GetCellLength(ellipsis); | ||||||
|  |  | ||||||
|         if (_parts.Length >= 2) |         if (_parts.Length >= 2) | ||||||
|   | |||||||
| @@ -47,7 +47,7 @@ public sealed class Tree : Renderable, IHasTreeNodes | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     /// <inheritdoc /> |     /// <inheritdoc /> | ||||||
|     protected override IEnumerable<Segment> Render(RenderContext context, int maxWidth) |     protected override IEnumerable<Segment> Render(RenderOptions options, int maxWidth) | ||||||
|     { |     { | ||||||
|         var result = new List<Segment>(); |         var result = new List<Segment>(); | ||||||
|         var visitedNodes = new HashSet<TreeNode>(); |         var visitedNodes = new HashSet<TreeNode>(); | ||||||
| @@ -56,7 +56,7 @@ public sealed class Tree : Renderable, IHasTreeNodes | |||||||
|         stack.Push(new Queue<TreeNode>(new[] { _root })); |         stack.Push(new Queue<TreeNode>(new[] { _root })); | ||||||
|  |  | ||||||
|         var levels = new List<Segment>(); |         var levels = new List<Segment>(); | ||||||
|         levels.Add(GetGuide(context, TreeGuidePart.Continue)); |         levels.Add(GetGuide(options, TreeGuidePart.Continue)); | ||||||
|  |  | ||||||
|         while (stack.Count > 0) |         while (stack.Count > 0) | ||||||
|         { |         { | ||||||
| @@ -66,7 +66,7 @@ public sealed class Tree : Renderable, IHasTreeNodes | |||||||
|                 levels.RemoveLast(); |                 levels.RemoveLast(); | ||||||
|                 if (levels.Count > 0) |                 if (levels.Count > 0) | ||||||
|                 { |                 { | ||||||
|                     levels.AddOrReplaceLast(GetGuide(context, TreeGuidePart.Fork)); |                     levels.AddOrReplaceLast(GetGuide(options, TreeGuidePart.Fork)); | ||||||
|                 } |                 } | ||||||
|  |  | ||||||
|                 continue; |                 continue; | ||||||
| @@ -83,11 +83,11 @@ public sealed class Tree : Renderable, IHasTreeNodes | |||||||
|  |  | ||||||
|             if (isLastChild) |             if (isLastChild) | ||||||
|             { |             { | ||||||
|                 levels.AddOrReplaceLast(GetGuide(context, TreeGuidePart.End)); |                 levels.AddOrReplaceLast(GetGuide(options, TreeGuidePart.End)); | ||||||
|             } |             } | ||||||
|  |  | ||||||
|             var prefix = levels.Skip(1).ToList(); |             var prefix = levels.Skip(1).ToList(); | ||||||
|             var renderableLines = Segment.SplitLines(current.Renderable.Render(context, maxWidth - Segment.CellCount(prefix))); |             var renderableLines = Segment.SplitLines(current.Renderable.Render(options, maxWidth - Segment.CellCount(prefix))); | ||||||
|  |  | ||||||
|             foreach (var (_, isFirstLine, _, line) in renderableLines.Enumerate()) |             foreach (var (_, isFirstLine, _, line) in renderableLines.Enumerate()) | ||||||
|             { |             { | ||||||
| @@ -102,14 +102,14 @@ public sealed class Tree : Renderable, IHasTreeNodes | |||||||
|                 if (isFirstLine && prefix.Count > 0) |                 if (isFirstLine && prefix.Count > 0) | ||||||
|                 { |                 { | ||||||
|                     var part = isLastChild ? TreeGuidePart.Space : TreeGuidePart.Continue; |                     var part = isLastChild ? TreeGuidePart.Space : TreeGuidePart.Continue; | ||||||
|                     prefix.AddOrReplaceLast(GetGuide(context, part)); |                     prefix.AddOrReplaceLast(GetGuide(options, part)); | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
|  |  | ||||||
|             if (current.Expanded && current.Nodes.Count > 0) |             if (current.Expanded && current.Nodes.Count > 0) | ||||||
|             { |             { | ||||||
|                 levels.AddOrReplaceLast(GetGuide(context, isLastChild ? TreeGuidePart.Space : TreeGuidePart.Continue)); |                 levels.AddOrReplaceLast(GetGuide(options, isLastChild ? TreeGuidePart.Space : TreeGuidePart.Continue)); | ||||||
|                 levels.Add(GetGuide(context, current.Nodes.Count == 1 ? TreeGuidePart.End : TreeGuidePart.Fork)); |                 levels.Add(GetGuide(options, current.Nodes.Count == 1 ? TreeGuidePart.End : TreeGuidePart.Fork)); | ||||||
|  |  | ||||||
|                 stack.Push(new Queue<TreeNode>(current.Nodes)); |                 stack.Push(new Queue<TreeNode>(current.Nodes)); | ||||||
|             } |             } | ||||||
| @@ -118,9 +118,9 @@ public sealed class Tree : Renderable, IHasTreeNodes | |||||||
|         return result; |         return result; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     private Segment GetGuide(RenderContext context, TreeGuidePart part) |     private Segment GetGuide(RenderOptions options, TreeGuidePart part) | ||||||
|     { |     { | ||||||
|         var guide = Guide.GetSafeTreeGuide(safe: !context.Unicode); |         var guide = Guide.GetSafeTreeGuide(safe: !options.Unicode); | ||||||
|         return new Segment(guide.GetPart(part), Style ?? Style.Plain); |         return new Segment(guide.GetPart(part), Style ?? Style.Plain); | ||||||
|     } |     } | ||||||
| } | } | ||||||
| @@ -1,8 +1,7 @@ | |||||||
| <Project Sdk="Microsoft.NET.Sdk"> | <Project Sdk="Microsoft.NET.Sdk"> | ||||||
|  |  | ||||||
|   <PropertyGroup> |   <PropertyGroup> | ||||||
|     <TargetFrameworks Condition="$([MSBuild]::IsOSPlatform('Windows'))">net7.0;net48</TargetFrameworks> |     <TargetFrameworks>net7.0</TargetFrameworks> | ||||||
|     <TargetFrameworks Condition="!$([MSBuild]::IsOSPlatform('Windows'))">net7.0</TargetFrameworks> |  | ||||||
|   </PropertyGroup> |   </PropertyGroup> | ||||||
|  |  | ||||||
|   <ItemGroup> |   <ItemGroup> | ||||||
| @@ -30,15 +29,4 @@ | |||||||
|     <ProjectReference Include="..\..\src\Spectre.Console\Spectre.Console.csproj" /> |     <ProjectReference Include="..\..\src\Spectre.Console\Spectre.Console.csproj" /> | ||||||
|   </ItemGroup> |   </ItemGroup> | ||||||
|  |  | ||||||
|   <ItemGroup> |  | ||||||
|     <None Update="Expectations\Help\Description_No_Trailing_Period.Output.received.txt"> |  | ||||||
|       <ParentFile>$([System.String]::Copy('%(FileName)').Split('.')[0])</ParentFile> |  | ||||||
|       <DependentUpon>%(ParentFile).cs</DependentUpon> |  | ||||||
|     </None> |  | ||||||
|     <None Update="Expectations\Help\Description_No_Trailing_Period.Output.verified.txt"> |  | ||||||
|       <ParentFile>$([System.String]::Copy('%(FileName)').Split('.')[0])</ParentFile> |  | ||||||
|       <DependentUpon>%(ParentFile).cs</DependentUpon> |  | ||||||
|     </None> |  | ||||||
|   </ItemGroup> |  | ||||||
|  |  | ||||||
| </Project> | </Project> | ||||||
|   | |||||||
| @@ -0,0 +1,15 @@ | |||||||
|  |                                          | ||||||
|  |                                          | ||||||
|  |                                          | ||||||
|  |                                          | ||||||
|  |                                          | ||||||
|  |                                          | ||||||
|  |                                          | ||||||
|  |                                          | ||||||
|  |                                          | ||||||
|  |                                          | ||||||
|  |                                          | ||||||
|  |                                          | ||||||
|  |             ┌──────────────┐             | ||||||
|  |             │ Hello World! │             | ||||||
|  |             └──────────────┘             | ||||||
| @@ -0,0 +1,15 @@ | |||||||
|  |                                          | ||||||
|  |                                          | ||||||
|  |                                          | ||||||
|  |                                          | ||||||
|  |                                          | ||||||
|  |                                          | ||||||
|  |             ┌──────────────┐             | ||||||
|  |             │ Hello World! │             | ||||||
|  |             └──────────────┘             | ||||||
|  |                                          | ||||||
|  |                                          | ||||||
|  |                                          | ||||||
|  |                                          | ||||||
|  |                                          | ||||||
|  |                                          | ||||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user
	 Patrik Svensson
					Patrik Svensson