mirror of
https://github.com/nsnail/spectre.console.git
synced 2025-06-19 13:28:16 +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:

committed by
Phil Scott

parent
5d68020abb
commit
3dea412785
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 object _lock;
|
||||
private readonly Stopwatch _stopwatch;
|
||||
private readonly bool _hideCompleted;
|
||||
private TimeSpan _lastUpdate;
|
||||
private bool _hideCompleted;
|
||||
|
||||
public override TimeSpan RefreshRate { get; }
|
||||
|
||||
@ -22,7 +22,7 @@ namespace Spectre.Console
|
||||
{
|
||||
_console = console ?? throw new ArgumentNullException(nameof(console));
|
||||
_columns = columns ?? throw new ArgumentNullException(nameof(columns));
|
||||
_live = new LiveRenderable();
|
||||
_live = new LiveRenderable(console);
|
||||
_lock = new object();
|
||||
_stopwatch = new Stopwatch();
|
||||
_lastUpdate = TimeSpan.Zero;
|
||||
@ -46,6 +46,14 @@ namespace Spectre.Console
|
||||
}
|
||||
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();
|
||||
}
|
||||
|
||||
|
@ -7,20 +7,21 @@ namespace Spectre.Console
|
||||
internal sealed class ListPromptRenderHook<T> : IRenderHook
|
||||
where T : notnull
|
||||
{
|
||||
private readonly LiveRenderable _live;
|
||||
private readonly object _lock;
|
||||
private readonly IAnsiConsole _console;
|
||||
private readonly Func<IRenderable> _builder;
|
||||
private readonly LiveRenderable _live;
|
||||
private readonly object _lock;
|
||||
private bool _dirty;
|
||||
|
||||
public ListPromptRenderHook(
|
||||
IAnsiConsole console,
|
||||
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();
|
||||
_console = console;
|
||||
_builder = builder;
|
||||
_dirty = true;
|
||||
}
|
||||
|
||||
|
@ -9,9 +9,9 @@ namespace Spectre.Console
|
||||
public sealed class TableColumn : IColumn
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the column header.
|
||||
/// Gets or sets the column header.
|
||||
/// </summary>
|
||||
public IRenderable Header { get; }
|
||||
public IRenderable Header { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the column footer.
|
||||
|
Reference in New Issue
Block a user