using System.Text; using System; 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 { bool _isActivity; int [] _activityPos; int _delta; /// /// Initializes a new instance of the class, starts in percentage mode and uses relative layout. /// public ProgressBar () { SetInitialProperties (); } void SetInitialProperties () { Height = 1; // This will be updated when Bounds is updated in ProgressBar_LayoutStarted CanFocus = false; _fraction = 0; LayoutStarted += ProgressBar_LayoutStarted; Initialized += ProgressBar_Initialized; } void ProgressBar_Initialized (object sender, EventArgs e) { ColorScheme = new ColorScheme (ColorScheme ?? SuperView?.ColorScheme ?? Colors.ColorSchemes ["Base"]) { HotNormal = new Attribute (Color.BrightGreen, Color.Gray) }; } void ProgressBar_LayoutStarted (object sender, EventArgs e) { // TODO: use Dim.Auto Height = 1 + GetAdornmentsThickness ().Vertical; } float _fraction; /// /// 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; SetNeedsDisplay (); } } ProgressBarStyle _progressBarStyle; /// /// Gets/Sets the progress bar style based on the /// public ProgressBarStyle ProgressBarStyle { get => _progressBarStyle; set { _progressBarStyle = value; switch (value) { case ProgressBarStyle.Blocks: SegmentCharacter = CM.Glyphs.BlocksMeterSegment; break; case ProgressBarStyle.Continuous: SegmentCharacter = CM.Glyphs.ContinuousMeterSegment; break; case ProgressBarStyle.MarqueeBlocks: SegmentCharacter = CM.Glyphs.BlocksMeterSegment; break; case ProgressBarStyle.MarqueeContinuous: SegmentCharacter = CM.Glyphs.ContinuousMeterSegment; break; } SetNeedsDisplay (); } } /// /// Specifies the format that a uses to indicate the visual presentation. /// public ProgressBarFormat ProgressBarFormat { get; set; } private Rune _segmentCharacter = CM.Glyphs.BlocksMeterSegment; /// /// Segment indicator for meter views. /// public Rune SegmentCharacter { get => _segmentCharacter; set { _segmentCharacter = value; SetNeedsDisplay (); } } /// /// 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; } } } bool _bidirectionalMarquee = true; /// /// Specifies if the or the /// styles is unidirectional /// or bidirectional. /// public bool BidirectionalMarquee { get => _bidirectionalMarquee; set { _bidirectionalMarquee = value; SetNeedsDisplay (); } } /// /// 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 == 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] >= Bounds.Width) { if (_bidirectionalMarquee) { for (var i = 0; i < _activityPos.Length; i++) { _activityPos [i] = Bounds.Width + i - 2; } _delta = -1; } else { PopulateActivityPos (); } } } SetNeedsDisplay (); } /// public override void OnDrawContent (Rect contentArea) { Driver.SetAttribute (GetHotNormalColor ()); Move (0, 0); if (_isActivity) { for (int i = 0; i < Bounds.Width; i++) if (Array.IndexOf (_activityPos, i) != -1) { Driver.AddRune (SegmentCharacter); } else { Driver.AddRune ((Rune)' '); } } else { int mid = (int)(_fraction * Bounds.Width); int i; for (i = 0; i < mid & i < Bounds.Width; i++) { Driver.AddRune (SegmentCharacter); } for (; i < Bounds.Width; i++) { Driver.AddRune ((Rune)' '); } } if (ProgressBarFormat != ProgressBarFormat.Simple && !_isActivity) { var tf = new TextFormatter () { Alignment = TextAlignment.Centered, Text = Text }; Attribute attr = new Attribute (ColorScheme.HotNormal.Foreground, ColorScheme.HotNormal.Background); if (_fraction > .5) { attr = new Attribute (ColorScheme.HotNormal.Background, ColorScheme.HotNormal.Foreground); } tf?.Draw (BoundsToScreen (Bounds), attr, ColorScheme.Normal, SuperView?.BoundsToScreen (SuperView.Bounds) ?? default, fillRemaining: false); } } 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; } } /// public override bool OnEnter (View view) { Application.Driver.SetCursorVisibility (CursorVisibility.Invisible); return base.OnEnter (view); } }