namespace Spectre.Console; /// /// A column showing a spinner. /// 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; /// protected internal override bool NoWrap => true; /// /// Gets or sets the . /// public Spinner Spinner { get => _spinner; set { lock (_lock) { _spinner = value ?? Spinner.Known.Default; _maxWidth = null; } } } /// /// Gets or sets the text that should be shown instead /// of the spinner once a task completes. /// public string? CompletedText { get => _completed; set { _completed = value; _maxWidth = null; } } /// /// Gets or sets the text that should be shown instead /// of the spinner before a task begins. /// public string? PendingText { get => _pending; set { _pending = value; _maxWidth = null; } } /// /// Gets or sets the completed style. /// public Style? CompletedStyle { get; set; } /// /// Gets or sets the pending style. /// public Style? PendingStyle { get; set; } /// /// Gets or sets the style of the spinner. /// public Style? Style { get; set; } = Color.Yellow; /// /// Initializes a new instance of the class. /// public SpinnerColumn() : this(Spinner.Known.Default) { } /// /// Initializes a new instance of the class. /// /// The spinner to use. public SpinnerColumn(Spinner spinner) { _spinner = spinner ?? throw new ArgumentNullException(nameof(spinner)); _lock = new object(); } /// public override IRenderable Render(RenderOptions options, ProgressTask task, TimeSpan deltaTime) { var useAscii = !options.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(ACCUMULATED, acc => acc + deltaTime.TotalMilliseconds); if (accumulated >= spinner.Interval.TotalMilliseconds) { task.State.Update(ACCUMULATED, _ => 0); task.State.Update(INDEX, index => index + 1); } var index = task.State.Get(INDEX); var frame = spinner.Frames[index % spinner.Frames.Count]; return new Markup(frame.EscapeMarkup(), Style ?? Style.Plain); } /// public override int? GetColumnWidth(RenderOptions options) { return GetMaxWidth(options); } private int GetMaxWidth(RenderOptions options) { lock (_lock) { if (_maxWidth == null) { var useAscii = !options.Unicode && _spinner.IsUnicode; var spinner = useAscii ? Spinner.Known.Ascii : _spinner ?? Spinner.Known.Default; _maxWidth = Math.Max( Math.Max( ((IRenderable)new Markup(PendingText ?? " ")).Measure(options, int.MaxValue).Max, ((IRenderable)new Markup(CompletedText ?? " ")).Measure(options, int.MaxValue).Max), spinner.Frames.Max(frame => Cell.GetCellLength(frame))); } return _maxWidth.Value; } } }