mirror of
https://github.com/nsnail/spectre.console.git
synced 2025-04-19 02:12:49 +08:00

This makes it possible for encoders to output better representation of the actual objects instead of working with chopped up segments. * IAnsiConsole.Write now takes an IRenderable instead of segments * Calculating cell width does no longer require a render context * Removed RenderContext.LegacyConsole * Removed RenderContext.Encoding * Added Capabilities.Unicode
156 lines
4.8 KiB
C#
156 lines
4.8 KiB
C#
using System;
|
|
using System.Linq;
|
|
using Spectre.Console.Rendering;
|
|
|
|
namespace Spectre.Console
|
|
{
|
|
/// <summary>
|
|
/// A column showing a spinner.
|
|
/// </summary>
|
|
public sealed class SpinnerColumn : ProgressColumn
|
|
{
|
|
private const string ACCUMULATED = "SPINNER_ACCUMULATED";
|
|
private const string INDEX = "SPINNER_INDEX";
|
|
|
|
private readonly object _lock;
|
|
private Spinner _spinner;
|
|
private int? _maxWidth;
|
|
private string? _completed;
|
|
private string? _pending;
|
|
|
|
/// <inheritdoc/>
|
|
protected internal override bool NoWrap => true;
|
|
|
|
/// <summary>
|
|
/// Gets or sets the <see cref="Console.Spinner"/>.
|
|
/// </summary>
|
|
public Spinner Spinner
|
|
{
|
|
get => _spinner;
|
|
set
|
|
{
|
|
lock (_lock)
|
|
{
|
|
_spinner = value ?? Spinner.Known.Default;
|
|
_maxWidth = null;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets or sets the text that should be shown instead
|
|
/// of the spinner once a task completes.
|
|
/// </summary>
|
|
public string? CompletedText
|
|
{
|
|
get => _completed;
|
|
set
|
|
{
|
|
_completed = value;
|
|
_maxWidth = null;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets or sets the text that should be shown instead
|
|
/// of the spinner before a task begins.
|
|
/// </summary>
|
|
public string? PendingText
|
|
{
|
|
get => _pending;
|
|
set
|
|
{
|
|
_pending = value;
|
|
_maxWidth = null;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets or sets the completed style.
|
|
/// </summary>
|
|
public Style? CompletedStyle { get; set; }
|
|
|
|
/// <summary>
|
|
/// Gets or sets the pending style.
|
|
/// </summary>
|
|
public Style? PendingStyle { get; set; }
|
|
|
|
/// <summary>
|
|
/// Gets or sets the style of the spinner.
|
|
/// </summary>
|
|
public Style? Style { get; set; } = new Style(foreground: Color.Yellow);
|
|
|
|
/// <summary>
|
|
/// Initializes a new instance of the <see cref="SpinnerColumn"/> class.
|
|
/// </summary>
|
|
public SpinnerColumn()
|
|
: this(Spinner.Known.Default)
|
|
{
|
|
}
|
|
|
|
/// <summary>
|
|
/// Initializes a new instance of the <see cref="SpinnerColumn"/> class.
|
|
/// </summary>
|
|
/// <param name="spinner">The spinner to use.</param>
|
|
public SpinnerColumn(Spinner spinner)
|
|
{
|
|
_spinner = spinner ?? throw new ArgumentNullException(nameof(spinner));
|
|
_lock = new object();
|
|
}
|
|
|
|
/// <inheritdoc/>
|
|
public override IRenderable Render(RenderContext context, ProgressTask task, TimeSpan deltaTime)
|
|
{
|
|
var useAscii = !context.Unicode && _spinner.IsUnicode;
|
|
var spinner = useAscii ? Spinner.Known.Ascii : _spinner ?? Spinner.Known.Default;
|
|
|
|
if (!task.IsStarted)
|
|
{
|
|
return new Markup(PendingText ?? " ", PendingStyle ?? Style.Plain);
|
|
}
|
|
|
|
if (task.IsFinished)
|
|
{
|
|
return new Markup(CompletedText ?? " ", CompletedStyle ?? Style.Plain);
|
|
}
|
|
|
|
var accumulated = task.State.Update<double>(ACCUMULATED, acc => acc + deltaTime.TotalMilliseconds);
|
|
if (accumulated >= spinner.Interval.TotalMilliseconds)
|
|
{
|
|
task.State.Update<double>(ACCUMULATED, _ => 0);
|
|
task.State.Update<int>(INDEX, index => index + 1);
|
|
}
|
|
|
|
var index = task.State.Get<int>(INDEX);
|
|
var frame = spinner.Frames[index % spinner.Frames.Count];
|
|
return new Markup(frame.EscapeMarkup(), Style ?? Style.Plain);
|
|
}
|
|
|
|
/// <inheritdoc/>
|
|
public override int? GetColumnWidth(RenderContext context)
|
|
{
|
|
return GetMaxWidth(context);
|
|
}
|
|
|
|
private int GetMaxWidth(RenderContext context)
|
|
{
|
|
lock (_lock)
|
|
{
|
|
if (_maxWidth == null)
|
|
{
|
|
var useAscii = !context.Unicode && _spinner.IsUnicode;
|
|
var spinner = useAscii ? Spinner.Known.Ascii : _spinner ?? Spinner.Known.Default;
|
|
|
|
_maxWidth = Math.Max(
|
|
Math.Max(
|
|
((IRenderable)new Markup(PendingText ?? " ")).Measure(context, int.MaxValue).Max,
|
|
((IRenderable)new Markup(CompletedText ?? " ")).Measure(context, int.MaxValue).Max),
|
|
spinner.Frames.Max(frame => Cell.GetCellLength(frame)));
|
|
}
|
|
|
|
return _maxWidth.Value;
|
|
}
|
|
}
|
|
}
|
|
}
|