using NStack; 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 and the percentage. /// SimplePlusPercentage, /// /// A framed visual presentation showing only the progress bar. /// Framed, /// /// A framed visual presentation showing the progress bar and the percentage. /// FramedPlusPercentage, /// /// A framed visual presentation showing all with the progress bar padded. /// FramedProgressPadded } /// /// 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, padding; /// /// Initializes a new instance of the class, starts in percentage mode with an absolute position and size. /// /// Rect. public ProgressBar (Rect rect) : base (rect) { Initialize (rect); } /// /// Initializes a new instance of the class, starts in percentage mode and uses relative layout. /// public ProgressBar () : base () { Initialize (Rect.Empty); } void Initialize (Rect rect) { CanFocus = false; fraction = 0; ColorScheme = new ColorScheme () { Normal = Application.Driver.MakeAttribute (Color.BrightGreen, Color.Gray), HotNormal = Colors.Base.Normal }; if (rect.IsEmpty) { Height = 1; } } 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 = Driver.BlocksMeterSegment; break; case ProgressBarStyle.Continuous: SegmentCharacter = Driver.ContinuousMeterSegment; break; case ProgressBarStyle.MarqueeBlocks: SegmentCharacter = Driver.BlocksMeterSegment; break; case ProgressBarStyle.MarqueeContinuous: SegmentCharacter = Driver.ContinuousMeterSegment; break; } SetNeedsDisplay (); } } private ProgressBarFormat progressBarFormat; /// /// Specifies the format that a uses to indicate the visual presentation. /// public ProgressBarFormat ProgressBarFormat { get => progressBarFormat; set { progressBarFormat = value; switch (progressBarFormat) { case ProgressBarFormat.Simple: Height = 1; break; case ProgressBarFormat.SimplePlusPercentage: Height = 2; break; case ProgressBarFormat.Framed: Height = 3; break; case ProgressBarFormat.FramedPlusPercentage: Height = 4; break; case ProgressBarFormat.FramedProgressPadded: Height = 6; break; } SetNeedsDisplay (); } } private Rune segmentCharacter = Driver.BlocksMeterSegment; /// /// Segment indicator for meter views. /// public Rune SegmentCharacter { get => segmentCharacter; set { segmentCharacter = value; SetNeedsDisplay (); } } /// public override ustring Text { get => GetPercentageText (); set { base.Text = SetPercentageText (value); } } private bool bidirectionalMarquee = true; /// /// Specifies if the or the /// styles is unidirectional /// or bidirectional. /// public bool BidirectionalMarquee { get => bidirectionalMarquee; set { bidirectionalMarquee = value; SetNeedsDisplay (); } } ustring GetPercentageText () { switch (progressBarStyle) { case ProgressBarStyle.Blocks: case ProgressBarStyle.Continuous: return $"{fraction * 100:F0}%"; case ProgressBarStyle.MarqueeBlocks: case ProgressBarStyle.MarqueeContinuous: break; } return base.Text; } ustring SetPercentageText (ustring value) { switch (progressBarStyle) { case ProgressBarStyle.Blocks: case ProgressBarStyle.Continuous: return $"{fraction * 100:F0}%"; case ProgressBarStyle.MarqueeBlocks: case ProgressBarStyle.MarqueeContinuous: break; } return value; } /// /// 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) { PopulateActivityPos (); } if (!isActivity) { isActivity = true; delta = 1; } else { for (int i = 0; i < activityPos.Length; i++) { activityPos [i] += delta; } int fWidth = GetFrameWidth (); if (activityPos [activityPos.Length - 1] < 0) { for (int i = 0; i < activityPos.Length; i++) { activityPos [i] = i - activityPos.Length + 2; } delta = 1; } else if (activityPos [0] >= fWidth) { if (bidirectionalMarquee) { for (int i = 0; i < activityPos.Length; i++) { activityPos [i] = fWidth + i - 2; } delta = -1; } else { PopulateActivityPos (); } } } SetNeedsDisplay (); } /// public override void Redraw (Rect region) { DrawFrame (); Driver.SetAttribute (GetNormalColor ()); int fWidth = GetFrameWidth (); if (isActivity) { Move (padding, padding); for (int i = 0; i < fWidth; i++) if (Array.IndexOf (activityPos, i) != -1) Driver.AddRune (SegmentCharacter); else Driver.AddRune (' '); } else { Move (padding, padding); int mid = (int)(fraction * fWidth); int i; for (i = 0; i < mid & i < fWidth; i++) Driver.AddRune (SegmentCharacter); for (; i < fWidth; i++) Driver.AddRune (' '); } DrawText (fWidth); } int GetFrameWidth () { switch (progressBarFormat) { case ProgressBarFormat.Simple: case ProgressBarFormat.SimplePlusPercentage: break; case ProgressBarFormat.Framed: case ProgressBarFormat.FramedPlusPercentage: return Frame.Width - 2; case ProgressBarFormat.FramedProgressPadded: return Frame.Width - 2 - padding; } return Frame.Width; } void DrawText (int fWidth) { switch (progressBarFormat) { case ProgressBarFormat.Simple: case ProgressBarFormat.Framed: break; case ProgressBarFormat.SimplePlusPercentage: case ProgressBarFormat.FramedPlusPercentage: case ProgressBarFormat.FramedProgressPadded: var tf = new TextFormatter () { Alignment = TextAlignment.Centered, Text = Text }; var row = padding + (progressBarFormat == ProgressBarFormat.FramedProgressPadded ? 2 : 1); Move (padding, row); var rect = new Rect (padding, row, fWidth, Frame.Height); tf?.Draw (ViewToScreen (rect), ColorScheme.HotNormal, ColorScheme.HotNormal, SuperView == null ? default : SuperView.ViewToScreen (SuperView.Bounds)); break; } } void DrawFrame () { switch (progressBarFormat) { case ProgressBarFormat.Simple: case ProgressBarFormat.SimplePlusPercentage: padding = 0; break; case ProgressBarFormat.Framed: case ProgressBarFormat.FramedPlusPercentage: padding = 1; Application.Driver.DrawWindowFrame (ViewToScreen (Bounds), padding, padding, padding, padding, true); break; case ProgressBarFormat.FramedProgressPadded: padding = 2; Application.Driver.DrawWindowFrame (ViewToScreen (Bounds), padding, padding, padding, padding + 1, true); Application.Driver.DrawWindowFrame (ViewToScreen (Bounds), padding - 1, padding - 1, padding - 1, padding - 1, true); break; } } void PopulateActivityPos () { activityPos = new int [Math.Min (Frame.Width / 3, 5)]; for (int 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); } } }