mirror of
https://github.com/nsnail/spectre.console.git
synced 2025-04-23 11:42:50 +08:00
Add live display support
This commit also adds functionality to LiveRenderable that should fix some problems related to vertical overflow. Closes #316 Closes #415
This commit is contained in:
parent
5d68020abb
commit
3dea412785
@ -1,4 +1,4 @@
|
|||||||
Title: Live Displays
|
Title: Live
|
||||||
Order: 4
|
Order: 4
|
||||||
---
|
---
|
||||||
|
|
||||||
|
62
docs/input/live/live-display.md
Normal file
62
docs/input/live/live-display.md
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
Title: Live Display
|
||||||
|
Order: 0
|
||||||
|
---
|
||||||
|
|
||||||
|
Spectre.Console can update arbitrary widgets in-place.
|
||||||
|
|
||||||
|
<?# Alert ?>
|
||||||
|
The live display is not
|
||||||
|
thread safe, and using it together with other interactive components such as
|
||||||
|
prompts, status displays or other progress displays are not supported.
|
||||||
|
<?#/ Alert ?>
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
var table = new Table().Centered();
|
||||||
|
|
||||||
|
AnsiConsole.Live(table)
|
||||||
|
.Start(ctx =>
|
||||||
|
{
|
||||||
|
table.AddColumn("Foo");
|
||||||
|
ctx.Refresh();
|
||||||
|
Thread.Sleep(1000);
|
||||||
|
|
||||||
|
table.AddColumn("Bar");
|
||||||
|
ctx.Refresh();
|
||||||
|
Thread.Sleep(1000);
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
## Asynchronous progress
|
||||||
|
|
||||||
|
If you prefer to use async/await, you can use `StartAsync` instead of `Start`.
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
var table = new Table().Centered();
|
||||||
|
|
||||||
|
await AnsiConsole.Live(table)
|
||||||
|
.StartAsync(async ctx =>
|
||||||
|
{
|
||||||
|
table.AddColumn("Foo");
|
||||||
|
ctx.Refresh();
|
||||||
|
await Task.Delay(1000);
|
||||||
|
|
||||||
|
table.AddColumn("Bar");
|
||||||
|
ctx.Refresh();
|
||||||
|
await Task.Delay(1000);
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
## Configure
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
var table = new Table().Centered();
|
||||||
|
|
||||||
|
AnsiConsole.Live(table)
|
||||||
|
.AutoClear(false) // Do not remove when done
|
||||||
|
.Overflow(VerticalOverflow.Ellipsis) // Show ellipsis when overflowing
|
||||||
|
.Cropping(VerticalOverflowCropping.Top) // Crop overflow at top
|
||||||
|
.Start(ctx =>
|
||||||
|
{
|
||||||
|
// Omitted
|
||||||
|
});
|
||||||
|
```
|
@ -66,7 +66,6 @@ await AnsiConsole.Progress()
|
|||||||
## Configure
|
## Configure
|
||||||
|
|
||||||
```csharp
|
```csharp
|
||||||
// Asynchronous
|
|
||||||
AnsiConsole.Progress()
|
AnsiConsole.Progress()
|
||||||
.AutoRefresh(false) // Turn off auto refresh
|
.AutoRefresh(false) // Turn off auto refresh
|
||||||
.AutoClear(false) // Do not remove the task list when done
|
.AutoClear(false) // Do not remove the task list when done
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
Title: Status
|
Title: Status
|
||||||
Order: 6
|
Order: 10
|
||||||
RedirectFrom: status
|
RedirectFrom: status
|
||||||
---
|
---
|
||||||
|
|
||||||
|
15
examples/Console/Live/Live.csproj
Normal file
15
examples/Console/Live/Live.csproj
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<OutputType>Exe</OutputType>
|
||||||
|
<TargetFramework>net5.0</TargetFramework>
|
||||||
|
<ExampleTitle>Live</ExampleTitle>
|
||||||
|
<ExampleDescription>Demonstrates how to do live updates.</ExampleDescription>
|
||||||
|
<ExampleGroup>Misc</ExampleGroup>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\..\Shared\Shared.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
108
examples/Console/Live/Program.cs
Normal file
108
examples/Console/Live/Program.cs
Normal file
@ -0,0 +1,108 @@
|
|||||||
|
using System;
|
||||||
|
using System.Threading;
|
||||||
|
|
||||||
|
namespace Spectre.Console.Examples
|
||||||
|
{
|
||||||
|
public static class Program
|
||||||
|
{
|
||||||
|
public static void Main()
|
||||||
|
{
|
||||||
|
var table = new Table().Centered();
|
||||||
|
|
||||||
|
// Animate
|
||||||
|
AnsiConsole.Live(table)
|
||||||
|
.AutoClear(false)
|
||||||
|
.Overflow(VerticalOverflow.Ellipsis)
|
||||||
|
.Cropping(VerticalOverflowCropping.Top)
|
||||||
|
.Start(ctx =>
|
||||||
|
{
|
||||||
|
void Update(int delay, Action action)
|
||||||
|
{
|
||||||
|
action();
|
||||||
|
ctx.Refresh();
|
||||||
|
Thread.Sleep(delay);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Columns
|
||||||
|
Update(230, () => table.AddColumn("Release date"));
|
||||||
|
Update(230, () => table.AddColumn("Title"));
|
||||||
|
Update(230, () => table.AddColumn("Budget"));
|
||||||
|
Update(230, () => table.AddColumn("Opening Weekend"));
|
||||||
|
Update(230, () => table.AddColumn("Box office"));
|
||||||
|
|
||||||
|
// Rows
|
||||||
|
Update(70, () => table.AddRow("May 25, 1977", "[yellow]Star Wars[/] [grey]Ep.[/] [u]IV[/]", "$11,000,000", "$1,554,475", "$775,398,007"));
|
||||||
|
Update(70, () => table.AddRow("May 21, 1980", "[yellow]Star Wars[/] [grey]Ep.[/] [u]V[/]", "$18,000,000", "$4,910,483", "$547,969,004"));
|
||||||
|
Update(70, () => table.AddRow("May 25, 1983", "[yellow]Star Wars[/] [grey]Ep.[/] [u]VI[/]", "$32,500,000", "$23,019,618", "$475,106,177"));
|
||||||
|
Update(70, () => table.AddRow("May 19, 1999", "[yellow]Star Wars[/] [grey]Ep.[/] [u]I[/]", "$115,000,000", "$64,810,870", "$1,027,044,677"));
|
||||||
|
Update(70, () => table.AddRow("May 16, 2002", "[yellow]Star Wars[/] [grey]Ep.[/] [u]II[/]", "$115,000,000", "$80,027,814", "$649,436,358"));
|
||||||
|
Update(70, () => table.AddRow("May 19, 2005", "[yellow]Star Wars[/] [grey]Ep.[/] [u]III[/]", "$113,000,000", "$108,435,841", "$850,035,635"));
|
||||||
|
Update(70, () => table.AddRow("Dec 18, 2015", "[yellow]Star Wars[/] [grey]Ep.[/] [u]VII[/]", "$245,000,000", "$247,966,675", "$2,068,223,624"));
|
||||||
|
Update(70, () => table.AddRow("Dec 15, 2017", "[yellow]Star Wars[/] [grey]Ep.[/] [u]VIII[/]", "$317,000,000", "$220,009,584", "$1,333,539,889"));
|
||||||
|
Update(70, () => table.AddRow("Dec 20, 2019", "[yellow]Star Wars[/] [grey]Ep.[/] [u]IX[/]", "$245,000,000", "$177,383,864", "$1,074,114,248"));
|
||||||
|
Update(70, () => table.AddRow("May 25, 1977", "[yellow]Star Wars[/] [grey]Ep.[/] [u]IV[/]", "$11,000,000", "$1,554,475", "$775,398,007"));
|
||||||
|
Update(70, () => table.AddRow("May 21, 1980", "[yellow]Star Wars[/] [grey]Ep.[/] [u]V[/]", "18,000,000", "$4,910,483", "$547,969,004"));
|
||||||
|
Update(70, () => table.AddRow("May 25, 1983", "[yellow]Star Wars[/] [grey]Ep.[/] [u]VI[/]", "$32,500,000", "$23,019,618", "$475,106,177"));
|
||||||
|
Update(70, () => table.AddRow("May 19, 1999", "[yellow]Star Wars[/] [grey]Ep.[/] [u]I[/]", "$115,000,000", "$64,810,870", "$1,027,044,677"));
|
||||||
|
Update(70, () => table.AddRow("May 16, 2002", "[yellow]Star Wars[/] [grey]Ep.[/] [u]II[/]", "$115,000,000", "$80,027,814", "$649,436,358"));
|
||||||
|
Update(70, () => table.AddRow("May 19, 2005", "[yellow]Star Wars[/] [grey]Ep.[/] [u]III[/]", "$113,000,000", "$108,435,841", "$850,035,635"));
|
||||||
|
Update(70, () => table.AddRow("Dec 18, 2015", "[yellow]Star Wars[/] [grey]Ep.[/] [u]VII[/]", "$245,000,000", "$247,966,675", "$2,068,223,624"));
|
||||||
|
Update(70, () => table.AddRow("Dec 15, 2017", "[yellow]Star Wars[/] [grey]Ep.[/] [u]VIII[/]", "$317,000,000", "$220,009,584", "$1,333,539,889"));
|
||||||
|
Update(70, () => table.AddRow("Dec 20, 2019", "[yellow]Star Wars[/] [grey]Ep.[/] [u]IX[/]", "$245,000,000", "$177,383,864", "$1,074,114,248"));
|
||||||
|
Update(70, () => table.AddRow("May 25, 1977", "[yellow]Star Wars[/] [grey]Ep.[/] [u]IV[/]", "$11,000,000", "$1,554,475", "$775,398,007"));
|
||||||
|
Update(70, () => table.AddRow("May 21, 1980", "[yellow]Star Wars[/] [grey]Ep.[/] [u]V[/]", "18,000,000", "$4,910,483", "$547,969,004"));
|
||||||
|
Update(70, () => table.AddRow("May 25, 1983", "[yellow]Star Wars[/] [grey]Ep.[/] [u]VI[/]", "$32,500,000", "$23,019,618", "$475,106,177"));
|
||||||
|
Update(70, () => table.AddRow("May 19, 1999", "[yellow]Star Wars[/] [grey]Ep.[/] [u]I[/]", "$115,000,000", "$64,810,870", "$1,027,044,677"));
|
||||||
|
Update(70, () => table.AddRow("May 16, 2002", "[yellow]Star Wars[/] [grey]Ep.[/] [u]II[/]", "$115,000,000", "$80,027,814", "$649,436,358"));
|
||||||
|
Update(70, () => table.AddRow("May 19, 2005", "[yellow]Star Wars[/] [grey]Ep.[/] [u]III[/]", "$113,000,000", "$108,435,841", "$850,035,635"));
|
||||||
|
Update(70, () => table.AddRow("Dec 18, 2015", "[yellow]Star Wars[/] [grey]Ep.[/] [u]VII[/]", "$245,000,000", "$247,966,675", "$2,068,223,624"));
|
||||||
|
Update(70, () => table.AddRow("Dec 15, 2017", "[yellow]Star Wars[/] [grey]Ep.[/] [u]VIII[/]", "$317,000,000", "$220,009,584", "$1,333,539,889"));
|
||||||
|
Update(70, () => table.AddRow("Dec 20, 2019", "[yellow]Star Wars[/] [grey]Ep.[/] [u]IX[/]", "$245,000,000", "$177,383,864", "$1,074,114,248"));
|
||||||
|
Update(70, () => table.AddRow("May 25, 1977", "[yellow]Star Wars[/] [grey]Ep.[/] [u]IV[/]", "$11,000,000", "$1,554,475", "$775,398,007"));
|
||||||
|
Update(70, () => table.AddRow("May 21, 1980", "[yellow]Star Wars[/] [grey]Ep.[/] [u]V[/]", "18,000,000", "$4,910,483", "$547,969,004"));
|
||||||
|
Update(70, () => table.AddRow("May 25, 1983", "[yellow]Star Wars[/] [grey]Ep.[/] [u]VI[/]", "$32,500,000", "$23,019,618", "$475,106,177"));
|
||||||
|
Update(70, () => table.AddRow("May 19, 1999", "[yellow]Star Wars[/] [grey]Ep.[/] [u]I[/]", "$115,000,000", "$64,810,870", "$1,027,044,677"));
|
||||||
|
Update(70, () => table.AddRow("May 16, 2002", "[yellow]Star Wars[/] [grey]Ep.[/] [u]II[/]", "$115,000,000", "$80,027,814", "$649,436,358"));
|
||||||
|
Update(70, () => table.AddRow("May 19, 2005", "[yellow]Star Wars[/] [grey]Ep.[/] [u]III[/]", "$113,000,000", "$108,435,841", "$850,035,635"));
|
||||||
|
Update(70, () => table.AddRow("Dec 18, 2015", "[yellow]Star Wars[/] [grey]Ep.[/] [u]VII[/]", "$245,000,000", "$247,966,675", "$2,068,223,624"));
|
||||||
|
Update(70, () => table.AddRow("Dec 15, 2017", "[yellow]Star Wars[/] [grey]Ep.[/] [u]VIII[/]", "$317,000,000", "$220,009,584", "$1,333,539,889"));
|
||||||
|
Update(70, () => table.AddRow("Dec 20, 2019", "[yellow]Star Wars[/] [grey]Ep.[/] [u]IX[/]", "$245,000,000", "$177,383,864", "$1,074,114,248"));
|
||||||
|
|
||||||
|
// Column footer
|
||||||
|
Update(230, () => table.Columns[2].Footer("$1,633,000,000"));
|
||||||
|
Update(230, () => table.Columns[3].Footer("$928,119,224"));
|
||||||
|
Update(400, () => table.Columns[4].Footer("$10,318,030,576"));
|
||||||
|
|
||||||
|
// Column alignment
|
||||||
|
Update(230, () => table.Columns[2].RightAligned());
|
||||||
|
Update(230, () => table.Columns[3].RightAligned());
|
||||||
|
Update(400, () => table.Columns[4].RightAligned());
|
||||||
|
|
||||||
|
// Column titles
|
||||||
|
Update(70, () => table.Columns[0].Header("[bold]Release date[/]"));
|
||||||
|
Update(70, () => table.Columns[1].Header("[bold]Title[/]"));
|
||||||
|
Update(70, () => table.Columns[2].Header("[red bold]Budget[/]"));
|
||||||
|
Update(70, () => table.Columns[3].Header("[green bold]Opening Weekend[/]"));
|
||||||
|
Update(400, () => table.Columns[4].Header("[blue bold]Box office[/]"));
|
||||||
|
|
||||||
|
// Footers
|
||||||
|
Update(70, () => table.Columns[2].Footer("[red bold]$1,633,000,000[/]"));
|
||||||
|
Update(70, () => table.Columns[3].Footer("[green bold]$928,119,224[/]"));
|
||||||
|
Update(400, () => table.Columns[4].Footer("[blue bold]$10,318,030,576[/]"));
|
||||||
|
|
||||||
|
// Title
|
||||||
|
Update(500, () => table.Title("Star Wars Movies"));
|
||||||
|
Update(400, () => table.Title("[[ [yellow]Star Wars Movies[/] ]]"));
|
||||||
|
|
||||||
|
// Borders
|
||||||
|
Update(230, () => table.BorderColor(Color.Yellow));
|
||||||
|
Update(230, () => table.MinimalBorder());
|
||||||
|
Update(230, () => table.SimpleBorder());
|
||||||
|
Update(230, () => table.SimpleHeavyBorder());
|
||||||
|
|
||||||
|
// Caption
|
||||||
|
Update(400, () => table.Caption("[[ [blue]THE END[/] ]]"));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -4,11 +4,7 @@ namespace Spectre.Console.Examples
|
|||||||
{
|
{
|
||||||
public static void Main()
|
public static void Main()
|
||||||
{
|
{
|
||||||
// Create the table.
|
AnsiConsole.Render(CreateTable());
|
||||||
var table = CreateTable();
|
|
||||||
|
|
||||||
// Render the table.
|
|
||||||
AnsiConsole.Render(table);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Table CreateTable()
|
private static Table CreateTable()
|
||||||
|
@ -88,6 +88,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Shared", "..\examples\Share
|
|||||||
EndProject
|
EndProject
|
||||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Internal", "Internal", "{0FC844AD-FCBB-4B2F-9AEC-6CB5505E49E3}"
|
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Internal", "Internal", "{0FC844AD-FCBB-4B2F-9AEC-6CB5505E49E3}"
|
||||||
EndProject
|
EndProject
|
||||||
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Live", "..\examples\Console\Live\Live.csproj", "{E607AA2A-A4A6-48E4-8AAB-B0EB74EACAA1}"
|
||||||
|
EndProject
|
||||||
Global
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
Debug|Any CPU = Debug|Any CPU
|
Debug|Any CPU = Debug|Any CPU
|
||||||
@ -458,6 +460,18 @@ Global
|
|||||||
{8428A7DD-29FC-4417-9CA0-B90D34B26AB2}.Release|x64.Build.0 = Release|Any CPU
|
{8428A7DD-29FC-4417-9CA0-B90D34B26AB2}.Release|x64.Build.0 = Release|Any CPU
|
||||||
{8428A7DD-29FC-4417-9CA0-B90D34B26AB2}.Release|x86.ActiveCfg = Release|Any CPU
|
{8428A7DD-29FC-4417-9CA0-B90D34B26AB2}.Release|x86.ActiveCfg = Release|Any CPU
|
||||||
{8428A7DD-29FC-4417-9CA0-B90D34B26AB2}.Release|x86.Build.0 = Release|Any CPU
|
{8428A7DD-29FC-4417-9CA0-B90D34B26AB2}.Release|x86.Build.0 = Release|Any CPU
|
||||||
|
{E607AA2A-A4A6-48E4-8AAB-B0EB74EACAA1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{E607AA2A-A4A6-48E4-8AAB-B0EB74EACAA1}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{E607AA2A-A4A6-48E4-8AAB-B0EB74EACAA1}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||||
|
{E607AA2A-A4A6-48E4-8AAB-B0EB74EACAA1}.Debug|x64.Build.0 = Debug|Any CPU
|
||||||
|
{E607AA2A-A4A6-48E4-8AAB-B0EB74EACAA1}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||||
|
{E607AA2A-A4A6-48E4-8AAB-B0EB74EACAA1}.Debug|x86.Build.0 = Debug|Any CPU
|
||||||
|
{E607AA2A-A4A6-48E4-8AAB-B0EB74EACAA1}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{E607AA2A-A4A6-48E4-8AAB-B0EB74EACAA1}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{E607AA2A-A4A6-48E4-8AAB-B0EB74EACAA1}.Release|x64.ActiveCfg = Release|Any CPU
|
||||||
|
{E607AA2A-A4A6-48E4-8AAB-B0EB74EACAA1}.Release|x64.Build.0 = Release|Any CPU
|
||||||
|
{E607AA2A-A4A6-48E4-8AAB-B0EB74EACAA1}.Release|x86.ActiveCfg = Release|Any CPU
|
||||||
|
{E607AA2A-A4A6-48E4-8AAB-B0EB74EACAA1}.Release|x86.Build.0 = Release|Any CPU
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(SolutionProperties) = preSolution
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
HideSolutionNode = FALSE
|
HideSolutionNode = FALSE
|
||||||
@ -493,6 +507,7 @@ Global
|
|||||||
{4C30C028-E97D-4B4C-AD17-C90F338A4DFF} = {F0575243-121F-4DEE-9F6B-246E26DC0844}
|
{4C30C028-E97D-4B4C-AD17-C90F338A4DFF} = {F0575243-121F-4DEE-9F6B-246E26DC0844}
|
||||||
{A0C772BA-C5F4-451D-AA7A-4045F2FA0201} = {F0575243-121F-4DEE-9F6B-246E26DC0844}
|
{A0C772BA-C5F4-451D-AA7A-4045F2FA0201} = {F0575243-121F-4DEE-9F6B-246E26DC0844}
|
||||||
{8428A7DD-29FC-4417-9CA0-B90D34B26AB2} = {A0C772BA-C5F4-451D-AA7A-4045F2FA0201}
|
{8428A7DD-29FC-4417-9CA0-B90D34B26AB2} = {A0C772BA-C5F4-451D-AA7A-4045F2FA0201}
|
||||||
|
{E607AA2A-A4A6-48E4-8AAB-B0EB74EACAA1} = {F0575243-121F-4DEE-9F6B-246E26DC0844}
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||||
SolutionGuid = {5729B071-67A0-48FB-8B1B-275E6822086C}
|
SolutionGuid = {5729B071-67A0-48FB-8B1B-275E6822086C}
|
||||||
|
20
src/Spectre.Console/AnsiConsole.Live.cs
Normal file
20
src/Spectre.Console/AnsiConsole.Live.cs
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
using Spectre.Console.Rendering;
|
||||||
|
|
||||||
|
namespace Spectre.Console
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// A console capable of writing ANSI escape sequences.
|
||||||
|
/// </summary>
|
||||||
|
public static partial class AnsiConsole
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a new <see cref="LiveDisplay"/> instance.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="target">The target renderable to update.</param>
|
||||||
|
/// <returns>A <see cref="LiveDisplay"/> instance.</returns>
|
||||||
|
public static LiveDisplay Live(IRenderable target)
|
||||||
|
{
|
||||||
|
return Console.Live(target);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
32
src/Spectre.Console/Extensions/AnsiConsoleExtensions.Live.cs
Normal file
32
src/Spectre.Console/Extensions/AnsiConsoleExtensions.Live.cs
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
using System;
|
||||||
|
using Spectre.Console.Rendering;
|
||||||
|
|
||||||
|
namespace Spectre.Console
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Contains extension methods for <see cref="IAnsiConsole"/>.
|
||||||
|
/// </summary>
|
||||||
|
public static partial class AnsiConsoleExtensions
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a new <see cref="LiveDisplay"/> instance for the console.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="console">The console.</param>
|
||||||
|
/// <param name="target">The target renderable to update.</param>
|
||||||
|
/// <returns>A <see cref="LiveDisplay"/> instance.</returns>
|
||||||
|
public static LiveDisplay Live(this IAnsiConsole console, IRenderable target)
|
||||||
|
{
|
||||||
|
if (console is null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(console));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (target is null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(target));
|
||||||
|
}
|
||||||
|
|
||||||
|
return new LiveDisplay(console, target);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
65
src/Spectre.Console/Extensions/LiveDisplayExtensions.cs
Normal file
65
src/Spectre.Console/Extensions/LiveDisplayExtensions.cs
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Spectre.Console
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Contains extension methods for <see cref="LiveDisplay"/>.
|
||||||
|
/// </summary>
|
||||||
|
public static class LiveDisplayExtensions
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Sets whether or not auto clear is enabled.
|
||||||
|
/// If enabled, the live display will be cleared when done.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="live">The <see cref="LiveDisplay"/> instance.</param>
|
||||||
|
/// <param name="enabled">Whether or not auto clear is enabled.</param>
|
||||||
|
/// <returns>The same instance so that multiple calls can be chained.</returns>
|
||||||
|
public static LiveDisplay AutoClear(this LiveDisplay live, bool enabled)
|
||||||
|
{
|
||||||
|
if (live is null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(live));
|
||||||
|
}
|
||||||
|
|
||||||
|
live.AutoClear = enabled;
|
||||||
|
|
||||||
|
return live;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sets the vertical overflow strategy.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="live">The <see cref="LiveDisplay"/> instance.</param>
|
||||||
|
/// <param name="overflow">The overflow strategy to use.</param>
|
||||||
|
/// <returns>The same instance so that multiple calls can be chained.</returns>
|
||||||
|
public static LiveDisplay Overflow(this LiveDisplay live, VerticalOverflow overflow)
|
||||||
|
{
|
||||||
|
if (live is null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(live));
|
||||||
|
}
|
||||||
|
|
||||||
|
live.Overflow = overflow;
|
||||||
|
|
||||||
|
return live;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sets the vertical overflow cropping strategy.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="live">The <see cref="LiveDisplay"/> instance.</param>
|
||||||
|
/// <param name="cropping">The overflow cropping strategy to use.</param>
|
||||||
|
/// <returns>The same instance so that multiple calls can be chained.</returns>
|
||||||
|
public static LiveDisplay Cropping(this LiveDisplay live, VerticalOverflowCropping cropping)
|
||||||
|
{
|
||||||
|
if (live is null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(live));
|
||||||
|
}
|
||||||
|
|
||||||
|
live.Cropping = cropping;
|
||||||
|
|
||||||
|
return live;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -8,11 +8,55 @@ namespace Spectre.Console
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public static class TableColumnExtensions
|
public static class TableColumnExtensions
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Sets the table column header.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="column">The table column.</param>
|
||||||
|
/// <param name="header">The table column header markup text.</param>
|
||||||
|
/// <returns>The same instance so that multiple calls can be chained.</returns>
|
||||||
|
public static TableColumn Header(this TableColumn column, string header)
|
||||||
|
{
|
||||||
|
if (column is null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(column));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (header is null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(header));
|
||||||
|
}
|
||||||
|
|
||||||
|
column.Header = new Markup(header);
|
||||||
|
return column;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sets the table column header.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="column">The table column.</param>
|
||||||
|
/// <param name="header">The table column header.</param>
|
||||||
|
/// <returns>The same instance so that multiple calls can be chained.</returns>
|
||||||
|
public static TableColumn Header(this TableColumn column, IRenderable header)
|
||||||
|
{
|
||||||
|
if (column is null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(column));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (header is null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(header));
|
||||||
|
}
|
||||||
|
|
||||||
|
column.Footer = header;
|
||||||
|
return column;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Sets the table column footer.
|
/// Sets the table column footer.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="column">The table column.</param>
|
/// <param name="column">The table column.</param>
|
||||||
/// <param name="footer">The table column markup text.</param>
|
/// <param name="footer">The table column footer markup text.</param>
|
||||||
/// <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 static TableColumn Footer(this TableColumn column, string footer)
|
public static TableColumn Footer(this TableColumn column, string footer)
|
||||||
{
|
{
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
using static Spectre.Console.AnsiSequences;
|
using static Spectre.Console.AnsiSequences;
|
||||||
|
|
||||||
namespace Spectre.Console.Rendering
|
namespace Spectre.Console.Rendering
|
||||||
@ -6,12 +8,33 @@ namespace Spectre.Console.Rendering
|
|||||||
internal sealed class LiveRenderable : Renderable
|
internal sealed class LiveRenderable : Renderable
|
||||||
{
|
{
|
||||||
private readonly object _lock = new object();
|
private readonly object _lock = new object();
|
||||||
|
private readonly IAnsiConsole _console;
|
||||||
private IRenderable? _renderable;
|
private IRenderable? _renderable;
|
||||||
private SegmentShape? _shape;
|
private SegmentShape? _shape;
|
||||||
|
|
||||||
public bool HasRenderable => _renderable != null;
|
public IRenderable? Target => _renderable;
|
||||||
|
public bool DidOverflow { get; private set; }
|
||||||
|
|
||||||
public void SetRenderable(IRenderable renderable)
|
[MemberNotNullWhen(true, nameof(Target))]
|
||||||
|
public bool HasRenderable => _renderable != null;
|
||||||
|
public VerticalOverflow Overflow { get; set; }
|
||||||
|
public VerticalOverflowCropping OverflowCropping { get; set; }
|
||||||
|
|
||||||
|
public LiveRenderable(IAnsiConsole console)
|
||||||
|
{
|
||||||
|
_console = console ?? throw new ArgumentNullException(nameof(console));
|
||||||
|
|
||||||
|
Overflow = VerticalOverflow.Ellipsis;
|
||||||
|
OverflowCropping = VerticalOverflowCropping.Top;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LiveRenderable(IAnsiConsole console, IRenderable renderable)
|
||||||
|
: this(console)
|
||||||
|
{
|
||||||
|
_renderable = renderable ?? throw new ArgumentNullException(nameof(renderable));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetRenderable(IRenderable? renderable)
|
||||||
{
|
{
|
||||||
lock (_lock)
|
lock (_lock)
|
||||||
{
|
{
|
||||||
@ -51,12 +74,61 @@ namespace Spectre.Console.Rendering
|
|||||||
{
|
{
|
||||||
lock (_lock)
|
lock (_lock)
|
||||||
{
|
{
|
||||||
|
DidOverflow = false;
|
||||||
|
|
||||||
if (_renderable != null)
|
if (_renderable != null)
|
||||||
{
|
{
|
||||||
var segments = _renderable.Render(context, maxWidth);
|
var segments = _renderable.Render(context, maxWidth);
|
||||||
var lines = Segment.SplitLines(segments);
|
var lines = Segment.SplitLines(segments);
|
||||||
|
|
||||||
var shape = SegmentShape.Calculate(context, lines);
|
var shape = SegmentShape.Calculate(context, lines);
|
||||||
|
if (shape.Height > _console.Profile.Height)
|
||||||
|
{
|
||||||
|
if (Overflow == VerticalOverflow.Crop)
|
||||||
|
{
|
||||||
|
if (OverflowCropping == VerticalOverflowCropping.Bottom)
|
||||||
|
{
|
||||||
|
// Remove bottom lines
|
||||||
|
var index = Math.Min(_console.Profile.Height, lines.Count);
|
||||||
|
var count = lines.Count - index;
|
||||||
|
lines.RemoveRange(index, count);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Remove top lines
|
||||||
|
var start = lines.Count - _console.Profile.Height;
|
||||||
|
lines.RemoveRange(0, start);
|
||||||
|
}
|
||||||
|
|
||||||
|
shape = SegmentShape.Calculate(context, lines);
|
||||||
|
}
|
||||||
|
else if (Overflow == VerticalOverflow.Ellipsis)
|
||||||
|
{
|
||||||
|
var ellipsisText = _console.Profile.Capabilities.Unicode ? "…" : "...";
|
||||||
|
var ellipsis = new SegmentLine(((IRenderable)new Markup($"[yellow]{ellipsisText}[/]")).Render(context, maxWidth));
|
||||||
|
|
||||||
|
if (OverflowCropping == VerticalOverflowCropping.Bottom)
|
||||||
|
{
|
||||||
|
// Remove bottom lines
|
||||||
|
var index = Math.Min(_console.Profile.Height - 1, lines.Count);
|
||||||
|
var count = lines.Count - index;
|
||||||
|
lines.RemoveRange(index, count);
|
||||||
|
lines.Add(ellipsis);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Remove top lines
|
||||||
|
var start = lines.Count - _console.Profile.Height;
|
||||||
|
lines.RemoveRange(0, start + 1);
|
||||||
|
lines.Insert(0, ellipsis);
|
||||||
|
}
|
||||||
|
|
||||||
|
shape = SegmentShape.Calculate(context, lines);
|
||||||
|
}
|
||||||
|
|
||||||
|
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(context, ref lines);
|
||||||
|
|
||||||
|
@ -13,6 +13,22 @@ namespace Spectre.Console.Rendering
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public int Length => this.Sum(line => line.Text.Length);
|
public int Length => this.Sum(line => line.Text.Length);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="SegmentLine"/> class.
|
||||||
|
/// </summary>
|
||||||
|
public SegmentLine()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="SegmentLine"/> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="segments">The segments.</param>
|
||||||
|
public SegmentLine(IEnumerable<Segment> segments)
|
||||||
|
: base(segments)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the number of cells the segment line occupies.
|
/// Gets the number of cells the segment line occupies.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
23
src/Spectre.Console/VerticalOverflow.cs
Normal file
23
src/Spectre.Console/VerticalOverflow.cs
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
namespace Spectre.Console
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Represents vertical overflow.
|
||||||
|
/// </summary>
|
||||||
|
public enum VerticalOverflow
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Crop overflow.
|
||||||
|
/// </summary>
|
||||||
|
Crop = 0,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Add an ellipsis at the end.
|
||||||
|
/// </summary>
|
||||||
|
Ellipsis = 1,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Do not do anything about overflow.
|
||||||
|
/// </summary>
|
||||||
|
Visible = 2,
|
||||||
|
}
|
||||||
|
}
|
18
src/Spectre.Console/VerticalOverflowCropping.cs
Normal file
18
src/Spectre.Console/VerticalOverflowCropping.cs
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
namespace Spectre.Console
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Represent vertical overflow cropping.
|
||||||
|
/// </summary>
|
||||||
|
public enum VerticalOverflowCropping
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Crops the top.
|
||||||
|
/// </summary>
|
||||||
|
Top = 0,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Crops the bottom.
|
||||||
|
/// </summary>
|
||||||
|
Bottom = 1,
|
||||||
|
}
|
||||||
|
}
|
126
src/Spectre.Console/Widgets/Live/LiveDisplay.cs
Normal file
126
src/Spectre.Console/Widgets/Live/LiveDisplay.cs
Normal file
@ -0,0 +1,126 @@
|
|||||||
|
using System;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Spectre.Console.Rendering;
|
||||||
|
|
||||||
|
namespace Spectre.Console
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Represents a live display.
|
||||||
|
/// </summary>
|
||||||
|
public sealed class LiveDisplay
|
||||||
|
{
|
||||||
|
private readonly IAnsiConsole _console;
|
||||||
|
private readonly IRenderable _target;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets a value indicating whether or not the live display should
|
||||||
|
/// be cleared when it's done.
|
||||||
|
/// Defaults to <c>false</c>.
|
||||||
|
/// </summary>
|
||||||
|
public bool AutoClear { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the vertical overflow strategy.
|
||||||
|
/// </summary>
|
||||||
|
public VerticalOverflow Overflow { get; set; } = VerticalOverflow.Ellipsis;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the vertical overflow cropping strategy.
|
||||||
|
/// </summary>
|
||||||
|
public VerticalOverflowCropping Cropping { get; set; } = VerticalOverflowCropping.Top;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="LiveDisplay"/> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="console">The console.</param>
|
||||||
|
/// <param name="target">The target renderable to update.</param>
|
||||||
|
public LiveDisplay(IAnsiConsole console, IRenderable target)
|
||||||
|
{
|
||||||
|
_console = console ?? throw new ArgumentNullException(nameof(console));
|
||||||
|
_target = target ?? throw new ArgumentNullException(nameof(target));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Starts the live display.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="action">The action to execute.</param>
|
||||||
|
public void Start(Action<LiveDisplayContext> action)
|
||||||
|
{
|
||||||
|
var task = StartAsync(ctx =>
|
||||||
|
{
|
||||||
|
action(ctx);
|
||||||
|
return Task.CompletedTask;
|
||||||
|
});
|
||||||
|
|
||||||
|
task.GetAwaiter().GetResult();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Starts the live display.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="T">The result type.</typeparam>
|
||||||
|
/// <param name="func">The action to execute.</param>
|
||||||
|
/// <returns>The result.</returns>
|
||||||
|
public T Start<T>(Func<LiveDisplayContext, T> func)
|
||||||
|
{
|
||||||
|
var task = StartAsync(ctx => Task.FromResult(func(ctx)));
|
||||||
|
return task.GetAwaiter().GetResult();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Starts the live display.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="func">The action to execute.</param>
|
||||||
|
/// <returns>The result.</returns>
|
||||||
|
public async Task StartAsync(Func<LiveDisplayContext, Task> func)
|
||||||
|
{
|
||||||
|
if (func is null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(func));
|
||||||
|
}
|
||||||
|
|
||||||
|
_ = await StartAsync<object?>(async ctx =>
|
||||||
|
{
|
||||||
|
await func(ctx).ConfigureAwait(false);
|
||||||
|
return default;
|
||||||
|
}).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Starts the live display.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="T">The result type.</typeparam>
|
||||||
|
/// <param name="func">The action to execute.</param>
|
||||||
|
/// <returns>The result.</returns>
|
||||||
|
public async Task<T> StartAsync<T>(Func<LiveDisplayContext, Task<T>> func)
|
||||||
|
{
|
||||||
|
if (func is null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(func));
|
||||||
|
}
|
||||||
|
|
||||||
|
return await _console.RunExclusive(async () =>
|
||||||
|
{
|
||||||
|
var context = new LiveDisplayContext(_console, _target);
|
||||||
|
context.SetOverflow(Overflow, Cropping);
|
||||||
|
|
||||||
|
var renderer = new LiveDisplayRenderer(_console, context);
|
||||||
|
renderer.Started();
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using (new RenderHookScope(_console, renderer))
|
||||||
|
{
|
||||||
|
var result = await func(context).ConfigureAwait(false);
|
||||||
|
context.Refresh();
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
renderer.Completed(AutoClear);
|
||||||
|
}
|
||||||
|
}).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
54
src/Spectre.Console/Widgets/Live/LiveDisplayContext.cs
Normal file
54
src/Spectre.Console/Widgets/Live/LiveDisplayContext.cs
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
using System;
|
||||||
|
using Spectre.Console.Rendering;
|
||||||
|
|
||||||
|
namespace Spectre.Console
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Represents a context that can be used to interact with a <see cref="LiveDisplay"/>.
|
||||||
|
/// </summary>
|
||||||
|
public sealed class LiveDisplayContext
|
||||||
|
{
|
||||||
|
private readonly IAnsiConsole _console;
|
||||||
|
|
||||||
|
internal object Lock { get; }
|
||||||
|
internal LiveRenderable Live { get; }
|
||||||
|
|
||||||
|
internal LiveDisplayContext(IAnsiConsole console, IRenderable target)
|
||||||
|
{
|
||||||
|
_console = console ?? throw new ArgumentNullException(nameof(console));
|
||||||
|
|
||||||
|
Live = new LiveRenderable(_console, target);
|
||||||
|
Lock = new object();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Updates the live display target.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="target">The new live display target.</param>
|
||||||
|
public void UpdateTarget(IRenderable? target)
|
||||||
|
{
|
||||||
|
lock (Lock)
|
||||||
|
{
|
||||||
|
Live.SetRenderable(target);
|
||||||
|
Refresh();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Refreshes the live display.
|
||||||
|
/// </summary>
|
||||||
|
public void Refresh()
|
||||||
|
{
|
||||||
|
lock (Lock)
|
||||||
|
{
|
||||||
|
_console.Write(new ControlCode(string.Empty));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void SetOverflow(VerticalOverflow overflow, VerticalOverflowCropping cropping)
|
||||||
|
{
|
||||||
|
Live.Overflow = overflow;
|
||||||
|
Live.OverflowCropping = cropping;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
62
src/Spectre.Console/Widgets/Live/LiveDisplayRenderer.cs
Normal file
62
src/Spectre.Console/Widgets/Live/LiveDisplayRenderer.cs
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using Spectre.Console.Rendering;
|
||||||
|
|
||||||
|
namespace Spectre.Console
|
||||||
|
{
|
||||||
|
internal sealed class LiveDisplayRenderer : IRenderHook
|
||||||
|
{
|
||||||
|
private readonly IAnsiConsole _console;
|
||||||
|
private readonly LiveDisplayContext _context;
|
||||||
|
|
||||||
|
public LiveDisplayRenderer(IAnsiConsole console, LiveDisplayContext context)
|
||||||
|
{
|
||||||
|
_console = console;
|
||||||
|
_context = context;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Started()
|
||||||
|
{
|
||||||
|
_console.Cursor.Hide();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Completed(bool autoclear)
|
||||||
|
{
|
||||||
|
lock (_context.Lock)
|
||||||
|
{
|
||||||
|
if (autoclear)
|
||||||
|
{
|
||||||
|
_console.Write(_context.Live.RestoreCursor());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (_context.Live.HasRenderable && _context.Live.DidOverflow)
|
||||||
|
{
|
||||||
|
// Redraw the whole live renderable
|
||||||
|
_console.Write(_context.Live.RestoreCursor());
|
||||||
|
_context.Live.Overflow = VerticalOverflow.Visible;
|
||||||
|
_console.Write(_context.Live.Target);
|
||||||
|
}
|
||||||
|
|
||||||
|
_console.WriteLine();
|
||||||
|
}
|
||||||
|
|
||||||
|
_console.Cursor.Show();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public IEnumerable<IRenderable> Process(RenderContext context, IEnumerable<IRenderable> renderables)
|
||||||
|
{
|
||||||
|
lock (_context.Lock)
|
||||||
|
{
|
||||||
|
yield return _context.Live.PositionCursor();
|
||||||
|
|
||||||
|
foreach (var renderable in renderables)
|
||||||
|
{
|
||||||
|
yield return renderable;
|
||||||
|
}
|
||||||
|
|
||||||
|
yield return _context.Live;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -13,8 +13,8 @@ namespace Spectre.Console
|
|||||||
private readonly LiveRenderable _live;
|
private readonly LiveRenderable _live;
|
||||||
private readonly object _lock;
|
private readonly object _lock;
|
||||||
private readonly Stopwatch _stopwatch;
|
private readonly Stopwatch _stopwatch;
|
||||||
|
private readonly bool _hideCompleted;
|
||||||
private TimeSpan _lastUpdate;
|
private TimeSpan _lastUpdate;
|
||||||
private bool _hideCompleted;
|
|
||||||
|
|
||||||
public override TimeSpan RefreshRate { get; }
|
public override TimeSpan RefreshRate { get; }
|
||||||
|
|
||||||
@ -22,7 +22,7 @@ namespace Spectre.Console
|
|||||||
{
|
{
|
||||||
_console = console ?? throw new ArgumentNullException(nameof(console));
|
_console = console ?? throw new ArgumentNullException(nameof(console));
|
||||||
_columns = columns ?? throw new ArgumentNullException(nameof(columns));
|
_columns = columns ?? throw new ArgumentNullException(nameof(columns));
|
||||||
_live = new LiveRenderable();
|
_live = new LiveRenderable(console);
|
||||||
_lock = new object();
|
_lock = new object();
|
||||||
_stopwatch = new Stopwatch();
|
_stopwatch = new Stopwatch();
|
||||||
_lastUpdate = TimeSpan.Zero;
|
_lastUpdate = TimeSpan.Zero;
|
||||||
@ -46,6 +46,14 @@ namespace Spectre.Console
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
if (_live.HasRenderable && _live.DidOverflow)
|
||||||
|
{
|
||||||
|
// Redraw the whole live renderable
|
||||||
|
_console.Write(_live.RestoreCursor());
|
||||||
|
_live.Overflow = VerticalOverflow.Visible;
|
||||||
|
_console.Write(_live.Target);
|
||||||
|
}
|
||||||
|
|
||||||
_console.WriteLine();
|
_console.WriteLine();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -7,20 +7,21 @@ namespace Spectre.Console
|
|||||||
internal sealed class ListPromptRenderHook<T> : IRenderHook
|
internal sealed class ListPromptRenderHook<T> : IRenderHook
|
||||||
where T : notnull
|
where T : notnull
|
||||||
{
|
{
|
||||||
private readonly LiveRenderable _live;
|
|
||||||
private readonly object _lock;
|
|
||||||
private readonly IAnsiConsole _console;
|
private readonly IAnsiConsole _console;
|
||||||
private readonly Func<IRenderable> _builder;
|
private readonly Func<IRenderable> _builder;
|
||||||
|
private readonly LiveRenderable _live;
|
||||||
|
private readonly object _lock;
|
||||||
private bool _dirty;
|
private bool _dirty;
|
||||||
|
|
||||||
public ListPromptRenderHook(
|
public ListPromptRenderHook(
|
||||||
IAnsiConsole console,
|
IAnsiConsole console,
|
||||||
Func<IRenderable> builder)
|
Func<IRenderable> builder)
|
||||||
{
|
{
|
||||||
_live = new LiveRenderable();
|
_console = console ?? throw new ArgumentNullException(nameof(console));
|
||||||
|
_builder = builder ?? throw new ArgumentNullException(nameof(builder));
|
||||||
|
|
||||||
|
_live = new LiveRenderable(console);
|
||||||
_lock = new object();
|
_lock = new object();
|
||||||
_console = console;
|
|
||||||
_builder = builder;
|
|
||||||
_dirty = true;
|
_dirty = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -9,9 +9,9 @@ namespace Spectre.Console
|
|||||||
public sealed class TableColumn : IColumn
|
public sealed class TableColumn : IColumn
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the column header.
|
/// Gets or sets the column header.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public IRenderable Header { get; }
|
public IRenderable Header { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the column footer.
|
/// Gets or sets the column footer.
|
||||||
|
Loading…
x
Reference in New Issue
Block a user