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; } }