mirror of
https://github.com/nsnail/spectre.console.git
synced 2025-04-16 08:52:50 +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:
parent
9ce3b99cd6
commit
c3ec6a7363
@ -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;
|
||||||
|
}
|
||||||
|
}
|
@ -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
|
// Switch to alternate screen
|
||||||
console.Write(new ControlCode("\u001b[?1049h\u001b[H"));
|
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,
|
||||||
|
}
|
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! │
|
||||||
|
└──────────────┘
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -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
Loading…
x
Reference in New Issue
Block a user