mirror of
https://github.com/nsnail/spectre.console.git
synced 2025-04-27 05:32:50 +08:00
147 lines
5.1 KiB
C#
147 lines
5.1 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Globalization;
|
|
using System.Linq;
|
|
using Spectre.Console.Rendering;
|
|
|
|
namespace Spectre.Console;
|
|
|
|
internal sealed class ProgressBar : Renderable, IHasCulture
|
|
{
|
|
private const int PULSESIZE = 20;
|
|
private const int PULSESPEED = 15;
|
|
|
|
public double Value { get; set; }
|
|
public double MaxValue { get; set; } = 100;
|
|
|
|
public int? Width { get; set; }
|
|
public bool ShowRemaining { get; set; } = true;
|
|
public char UnicodeBar { get; set; } = '━';
|
|
public char AsciiBar { get; set; } = '-';
|
|
public bool ShowValue { get; set; }
|
|
public bool IsIndeterminate { get; set; }
|
|
public CultureInfo? Culture { get; set; }
|
|
|
|
public Style CompletedStyle { get; set; } = new Style(foreground: Color.Yellow);
|
|
public Style FinishedStyle { get; set; } = new Style(foreground: Color.Green);
|
|
public Style RemainingStyle { get; set; } = new Style(foreground: Color.Grey);
|
|
public Style IndeterminateStyle { get; set; } = DefaultPulseStyle;
|
|
|
|
internal static Style DefaultPulseStyle { get; } = new Style(foreground: Color.DodgerBlue1, background: Color.Grey23);
|
|
|
|
protected override Measurement Measure(RenderContext context, int maxWidth)
|
|
{
|
|
var width = Math.Min(Width ?? maxWidth, maxWidth);
|
|
return new Measurement(4, width);
|
|
}
|
|
|
|
protected override IEnumerable<Segment> Render(RenderContext context, int maxWidth)
|
|
{
|
|
var width = Math.Min(Width ?? maxWidth, maxWidth);
|
|
var completedBarCount = Math.Min(MaxValue, Math.Max(0, Value));
|
|
var isCompleted = completedBarCount >= MaxValue;
|
|
|
|
if (IsIndeterminate && !isCompleted)
|
|
{
|
|
foreach (var segment in RenderIndeterminate(context, width))
|
|
{
|
|
yield return segment;
|
|
}
|
|
|
|
yield break;
|
|
}
|
|
|
|
var bar = !context.Unicode ? AsciiBar : UnicodeBar;
|
|
var style = isCompleted ? FinishedStyle : CompletedStyle;
|
|
var barCount = Math.Max(0, (int)(width * (completedBarCount / MaxValue)));
|
|
|
|
// Show value?
|
|
var value = completedBarCount.ToString(Culture ?? CultureInfo.InvariantCulture);
|
|
if (ShowValue)
|
|
{
|
|
barCount = barCount - value.Length - 1;
|
|
barCount = Math.Max(0, barCount);
|
|
}
|
|
|
|
if (barCount < 0)
|
|
{
|
|
yield break;
|
|
}
|
|
|
|
yield return new Segment(new string(bar, barCount), style);
|
|
|
|
if (ShowValue)
|
|
{
|
|
yield return barCount == 0
|
|
? new Segment(value, style)
|
|
: new Segment(" " + value, style);
|
|
}
|
|
|
|
// More space available?
|
|
if (barCount < width)
|
|
{
|
|
var diff = width - barCount;
|
|
if (ShowValue)
|
|
{
|
|
diff = diff - value.Length - 1;
|
|
if (diff <= 0)
|
|
{
|
|
yield break;
|
|
}
|
|
}
|
|
|
|
var legacy = context.ColorSystem == ColorSystem.NoColors || context.ColorSystem == ColorSystem.Legacy;
|
|
var remainingToken = ShowRemaining && !legacy ? bar : ' ';
|
|
yield return new Segment(new string(remainingToken, diff), RemainingStyle);
|
|
}
|
|
}
|
|
|
|
private IEnumerable<Segment> RenderIndeterminate(RenderContext context, int width)
|
|
{
|
|
var bar = context.Unicode ? UnicodeBar.ToString() : AsciiBar.ToString();
|
|
var style = IndeterminateStyle ?? DefaultPulseStyle;
|
|
|
|
IEnumerable<Segment> GetPulseSegments()
|
|
{
|
|
// For 1-bit and 3-bit colors, fall back to
|
|
// a simpler versions with only two colors.
|
|
if (context.ColorSystem == ColorSystem.NoColors ||
|
|
context.ColorSystem == ColorSystem.Legacy)
|
|
{
|
|
// First half of the pulse
|
|
var segments = Enumerable.Repeat(new Segment(bar, new Style(style.Foreground)), PULSESIZE / 2);
|
|
|
|
// Second half of the pulse
|
|
var legacy = context.ColorSystem == ColorSystem.NoColors || context.ColorSystem == ColorSystem.Legacy;
|
|
var bar2 = legacy ? " " : bar;
|
|
segments = segments.Concat(Enumerable.Repeat(new Segment(bar2, new Style(style.Background)), PULSESIZE - (PULSESIZE / 2)));
|
|
|
|
foreach (var segment in segments)
|
|
{
|
|
yield return segment;
|
|
}
|
|
|
|
yield break;
|
|
}
|
|
|
|
for (var index = 0; index < PULSESIZE; index++)
|
|
{
|
|
var position = index / (float)PULSESIZE;
|
|
var fade = 0.5f + ((float)Math.Cos(position * Math.PI * 2) / 2.0f);
|
|
var color = style.Foreground.Blend(style.Background, fade);
|
|
|
|
yield return new Segment(bar, new Style(foreground: color));
|
|
}
|
|
}
|
|
|
|
// Get the pulse segments
|
|
var pulseSegments = GetPulseSegments();
|
|
pulseSegments = pulseSegments.Repeat((width / PULSESIZE) + 2);
|
|
|
|
// Repeat the pulse segments
|
|
var currentTime = (DateTime.Now - DateTime.Today).TotalSeconds;
|
|
var offset = (int)(currentTime * PULSESPEED) % PULSESIZE;
|
|
|
|
return pulseSegments.Skip(offset).Take(width);
|
|
}
|
|
} |