namespace Terminal.Gui;
/// Specifies the style that a uses to indicate the progress of an operation.
public enum ProgressBarStyle
{
/// Indicates progress by increasing the number of segmented blocks in a .
Blocks,
/// Indicates progress by increasing the size of a smooth, continuous bar in a .
Continuous,
/// Indicates progress by continuously scrolling a block across a in a marquee fashion.
MarqueeBlocks,
/// Indicates progress by continuously scrolling a block across a in a marquee fashion.
MarqueeContinuous
}
/// Specifies the format that a uses to indicate the visual presentation.
public enum ProgressBarFormat
{
/// A simple visual presentation showing only the progress bar.
Simple,
/// A simple visual presentation showing the progress bar overlaid with the percentage.
SimplePlusPercentage
}
/// A Progress Bar view that can indicate progress of an activity visually.
///
///
/// can operate in two modes, percentage mode, or activity mode. The progress bar
/// starts in percentage mode and setting the Fraction property will reflect on the UI the progress made so far.
/// Activity mode is used when the application has no way of knowing how much time is left, and is started when the
/// method is called. Call repeatedly as progress is made.
///
///
public class ProgressBar : View, IDesignable
{
private int [] _activityPos;
private bool _bidirectionalMarquee = true;
private int _delta;
private float _fraction;
private bool _isActivity;
private ProgressBarStyle _progressBarStyle = ProgressBarStyle.Blocks;
private ProgressBarFormat _progressBarFormat = ProgressBarFormat.Simple;
private Rune _segmentCharacter = Glyphs.BlocksMeterSegment;
///
/// Initializes a new instance of the class, starts in percentage mode and uses relative
/// layout.
///
public ProgressBar () { SetInitialProperties (); }
///
/// Specifies if the or the
/// styles is unidirectional or bidirectional.
///
public bool BidirectionalMarquee
{
get => _bidirectionalMarquee;
set => _bidirectionalMarquee = value;
}
/// Gets or sets the fraction to display, must be a value between 0 and 1.
/// The fraction representing the progress.
public float Fraction
{
get => _fraction;
set
{
_fraction = Math.Min (value, 1);
_isActivity = false;
SetNeedsDraw ();
}
}
/// Specifies the format that a uses to indicate the visual presentation.
public ProgressBarFormat ProgressBarFormat
{
get => _progressBarFormat;
set => _progressBarFormat = value;
}
/// Gets/Sets the progress bar style based on the
public ProgressBarStyle ProgressBarStyle
{
get => _progressBarStyle;
set
{
_progressBarStyle = value;
switch (value)
{
case ProgressBarStyle.Blocks:
SegmentCharacter = Glyphs.BlocksMeterSegment;
break;
case ProgressBarStyle.Continuous:
SegmentCharacter = Glyphs.ContinuousMeterSegment;
break;
case ProgressBarStyle.MarqueeBlocks:
SegmentCharacter = Glyphs.BlocksMeterSegment;
break;
case ProgressBarStyle.MarqueeContinuous:
SegmentCharacter = Glyphs.ContinuousMeterSegment;
break;
}
SetNeedsDraw ();
}
}
/// Segment indicator for meter views.
public Rune SegmentCharacter
{
get => _segmentCharacter;
set => _segmentCharacter = value;
}
///
/// Gets or sets the text displayed on the progress bar. If set to an empty string and
/// is the percentage will be
/// displayed. If is a marquee style, the text will be displayed.
///
public override string Text
{
get => string.IsNullOrEmpty (base.Text) ? $"{_fraction * 100:F0}%" : base.Text;
set
{
if (ProgressBarStyle == ProgressBarStyle.MarqueeBlocks
|| ProgressBarStyle == ProgressBarStyle.MarqueeContinuous)
{
base.Text = value;
}
}
}
///
protected override bool OnDrawingContent (Rectangle viewport)
{
SetAttribute (GetHotNormalColor ());
Move (0, 0);
if (_isActivity)
{
for (var i = 0; i < Viewport.Width; i++)
{
if (Array.IndexOf (_activityPos, i) != -1)
{
Driver?.AddRune (SegmentCharacter);
}
else
{
Driver?.AddRune ((Rune)' ');
}
}
}
else
{
var mid = (int)(_fraction * Viewport.Width);
int i;
for (i = 0; (i < mid) & (i < Viewport.Width); i++)
{
Driver?.AddRune (SegmentCharacter);
}
for (; i < Viewport.Width; i++)
{
Driver?.AddRune ((Rune)' ');
}
}
if (ProgressBarFormat != ProgressBarFormat.Simple && !_isActivity)
{
var tf = new TextFormatter { Alignment = Alignment.Center, Text = Text };
var attr = new Attribute (ColorScheme.HotNormal.Foreground, ColorScheme.HotNormal.Background);
if (_fraction > .5)
{
attr = new Attribute (ColorScheme.HotNormal.Background, ColorScheme.HotNormal.Foreground);
}
tf.Draw (
ViewportToScreen (Viewport),
attr,
ColorScheme.Normal,
SuperView?.ViewportToScreen (SuperView.Viewport) ?? default (Rectangle)
);
}
return true;
}
/// Notifies the that some progress has taken place.
///
/// If the is percentage mode, it switches to activity mode. If is in activity mode, the
/// marker is moved.
///
public void Pulse ()
{
if (_activityPos is null || _activityPos.Length == 0)
{
PopulateActivityPos ();
}
if (_activityPos!.Length == 0)
{
return;
}
if (!_isActivity)
{
_isActivity = true;
_delta = 1;
}
else
{
for (var i = 0; i < _activityPos.Length; i++)
{
_activityPos [i] += _delta;
}
if (_activityPos [^1] < 0)
{
for (var i = 0; i < _activityPos.Length; i++)
{
_activityPos [i] = i - _activityPos.Length + 2;
}
_delta = 1;
}
else if (_activityPos [0] >= Viewport.Width)
{
if (_bidirectionalMarquee)
{
for (var i = 0; i < _activityPos.Length; i++)
{
_activityPos [i] = Viewport.Width + i - 2;
}
_delta = -1;
}
else
{
PopulateActivityPos ();
}
}
}
SetNeedsDraw ();
}
private void PopulateActivityPos ()
{
_activityPos = new int [Math.Min (Frame.Width / 3, 5)];
for (var i = 0; i < _activityPos.Length; i++)
{
_activityPos [i] = i - _activityPos.Length + 1;
}
}
private void ProgressBar_Initialized (object sender, EventArgs e)
{
//ColorScheme = new ColorScheme (ColorScheme ?? SuperView?.ColorScheme ?? Colors.ColorSchemes ["Base"])
//{
// HotNormal = new Attribute (Color.BrightGreen, Color.Gray)
//};
}
private void SetInitialProperties ()
{
Width = Dim.Auto (DimAutoStyle.Content);
Height = Dim.Auto (DimAutoStyle.Content, minimumContentDim: 1);
CanFocus = false;
_fraction = 0;
Initialized += ProgressBar_Initialized;
}
///
public bool EnableForDesign ()
{
Width = Dim.Fill ();
Height = Dim.Auto (DimAutoStyle.Text, minimumContentDim: 1);
Fraction = 0.75f;
return true;
}
}