ProgressBar.cs 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381
  1. using NStack;
  2. using System;
  3. namespace Terminal.Gui {
  4. /// <summary>
  5. /// Specifies the style that a <see cref="ProgressBar"/> uses to indicate the progress of an operation.
  6. /// </summary>
  7. public enum ProgressBarStyle {
  8. /// <summary>
  9. /// Indicates progress by increasing the number of segmented blocks in a <see cref="ProgressBar"/>.
  10. /// </summary>
  11. Blocks,
  12. /// <summary>
  13. /// Indicates progress by increasing the size of a smooth, continuous bar in a <see cref="ProgressBar"/>.
  14. /// </summary>
  15. Continuous,
  16. /// <summary>
  17. /// Indicates progress by continuously scrolling a block across a <see cref="ProgressBar"/> in a marquee fashion.
  18. /// </summary>
  19. MarqueeBlocks,
  20. /// <summary>
  21. /// Indicates progress by continuously scrolling a block across a <see cref="ProgressBar"/> in a marquee fashion.
  22. /// </summary>
  23. MarqueeContinuous
  24. }
  25. /// <summary>
  26. ///Specifies the format that a <see cref="ProgressBar"/> uses to indicate the visual presentation.
  27. /// </summary>
  28. public enum ProgressBarFormat {
  29. /// <summary>
  30. /// A simple visual presentation showing only the progress bar.
  31. /// </summary>
  32. Simple,
  33. /// <summary>
  34. /// A simple visual presentation showing the progress bar and the percentage.
  35. /// </summary>
  36. SimplePlusPercentage,
  37. /// <summary>
  38. /// A framed visual presentation showing only the progress bar.
  39. /// </summary>
  40. Framed,
  41. /// <summary>
  42. /// A framed visual presentation showing the progress bar and the percentage.
  43. /// </summary>
  44. FramedPlusPercentage,
  45. /// <summary>
  46. /// A framed visual presentation showing all with the progress bar padded.
  47. /// </summary>
  48. FramedProgressPadded
  49. }
  50. /// <summary>
  51. /// A Progress Bar view that can indicate progress of an activity visually.
  52. /// </summary>
  53. /// <remarks>
  54. /// <para>
  55. /// <see cref="ProgressBar"/> can operate in two modes, percentage mode, or
  56. /// activity mode. The progress bar starts in percentage mode and
  57. /// setting the Fraction property will reflect on the UI the progress
  58. /// made so far. Activity mode is used when the application has no
  59. /// way of knowing how much time is left, and is started when the <see cref="Pulse"/> method is called.
  60. /// Call <see cref="Pulse"/> repeatedly as progress is made.
  61. /// </para>
  62. /// </remarks>
  63. public class ProgressBar : View {
  64. bool isActivity;
  65. int [] activityPos;
  66. int delta, padding;
  67. /// <summary>
  68. /// Initializes a new instance of the <see cref="ProgressBar"/> class, starts in percentage mode with an absolute position and size.
  69. /// </summary>
  70. /// <param name="rect">Rect.</param>
  71. public ProgressBar (Rect rect) : base (rect)
  72. {
  73. Initialize (rect);
  74. }
  75. /// <summary>
  76. /// Initializes a new instance of the <see cref="ProgressBar"/> class, starts in percentage mode and uses relative layout.
  77. /// </summary>
  78. public ProgressBar () : base ()
  79. {
  80. Initialize (Rect.Empty);
  81. }
  82. void Initialize (Rect rect)
  83. {
  84. CanFocus = false;
  85. fraction = 0;
  86. ColorScheme = new ColorScheme () {
  87. Normal = Application.Driver.MakeAttribute (Color.BrightGreen, Color.Gray),
  88. HotNormal = Colors.Base.Normal
  89. };
  90. if (rect.IsEmpty) {
  91. Height = 1;
  92. }
  93. }
  94. float fraction;
  95. /// <summary>
  96. /// Gets or sets the <see cref="ProgressBar"/> fraction to display, must be a value between 0 and 1.
  97. /// </summary>
  98. /// <value>The fraction representing the progress.</value>
  99. public float Fraction {
  100. get => fraction;
  101. set {
  102. fraction = Math.Min (value, 1);
  103. isActivity = false;
  104. SetNeedsDisplay ();
  105. }
  106. }
  107. ProgressBarStyle progressBarStyle;
  108. /// <summary>
  109. /// Gets/Sets the progress bar style based on the <see cref="Terminal.Gui.ProgressBarStyle"/>
  110. /// </summary>
  111. public ProgressBarStyle ProgressBarStyle {
  112. get => progressBarStyle;
  113. set {
  114. progressBarStyle = value;
  115. switch (value) {
  116. case ProgressBarStyle.Blocks:
  117. SegmentCharacter = Driver.BlocksMeterSegment;
  118. break;
  119. case ProgressBarStyle.Continuous:
  120. SegmentCharacter = Driver.ContinuousMeterSegment;
  121. break;
  122. case ProgressBarStyle.MarqueeBlocks:
  123. SegmentCharacter = Driver.BlocksMeterSegment;
  124. break;
  125. case ProgressBarStyle.MarqueeContinuous:
  126. SegmentCharacter = Driver.ContinuousMeterSegment;
  127. break;
  128. }
  129. SetNeedsDisplay ();
  130. }
  131. }
  132. private ProgressBarFormat progressBarFormat;
  133. /// <summary>
  134. /// Specifies the format that a <see cref="ProgressBar"/> uses to indicate the visual presentation.
  135. /// </summary>
  136. public ProgressBarFormat ProgressBarFormat {
  137. get => progressBarFormat;
  138. set {
  139. progressBarFormat = value;
  140. switch (progressBarFormat) {
  141. case ProgressBarFormat.Simple:
  142. Height = 1;
  143. break;
  144. case ProgressBarFormat.SimplePlusPercentage:
  145. Height = 2;
  146. break;
  147. case ProgressBarFormat.Framed:
  148. Height = 3;
  149. break;
  150. case ProgressBarFormat.FramedPlusPercentage:
  151. Height = 4;
  152. break;
  153. case ProgressBarFormat.FramedProgressPadded:
  154. Height = 6;
  155. break;
  156. }
  157. SetNeedsDisplay ();
  158. }
  159. }
  160. private Rune segmentCharacter = Driver.BlocksMeterSegment;
  161. /// <summary>
  162. /// Segment indicator for meter views.
  163. /// </summary>
  164. public Rune SegmentCharacter {
  165. get => segmentCharacter;
  166. set {
  167. segmentCharacter = value;
  168. SetNeedsDisplay ();
  169. }
  170. }
  171. ///<inheritdoc/>
  172. public override ustring Text {
  173. get => GetPercentageText ();
  174. set {
  175. base.Text = SetPercentageText (value);
  176. }
  177. }
  178. private bool bidirectionalMarquee = true;
  179. /// <summary>
  180. /// Specifies if the <see cref="ProgressBarStyle.MarqueeBlocks"/> or the
  181. /// <see cref="ProgressBarStyle.MarqueeContinuous"/> styles is unidirectional
  182. /// or bidirectional.
  183. /// </summary>
  184. public bool BidirectionalMarquee {
  185. get => bidirectionalMarquee;
  186. set {
  187. bidirectionalMarquee = value;
  188. SetNeedsDisplay ();
  189. }
  190. }
  191. ustring GetPercentageText ()
  192. {
  193. switch (progressBarStyle) {
  194. case ProgressBarStyle.Blocks:
  195. case ProgressBarStyle.Continuous:
  196. return $"{fraction * 100:F0}%";
  197. case ProgressBarStyle.MarqueeBlocks:
  198. case ProgressBarStyle.MarqueeContinuous:
  199. break;
  200. }
  201. return base.Text;
  202. }
  203. ustring SetPercentageText (ustring value)
  204. {
  205. switch (progressBarStyle) {
  206. case ProgressBarStyle.Blocks:
  207. case ProgressBarStyle.Continuous:
  208. return $"{fraction * 100:F0}%";
  209. case ProgressBarStyle.MarqueeBlocks:
  210. case ProgressBarStyle.MarqueeContinuous:
  211. break;
  212. }
  213. return value;
  214. }
  215. /// <summary>
  216. /// Notifies the <see cref="ProgressBar"/> that some progress has taken place.
  217. /// </summary>
  218. /// <remarks>
  219. /// If the <see cref="ProgressBar"/> is percentage mode, it switches to activity
  220. /// mode. If is in activity mode, the marker is moved.
  221. /// </remarks>
  222. public void Pulse ()
  223. {
  224. if (activityPos == null) {
  225. PopulateActivityPos ();
  226. }
  227. if (!isActivity) {
  228. isActivity = true;
  229. delta = 1;
  230. } else {
  231. for (int i = 0; i < activityPos.Length; i++) {
  232. activityPos [i] += delta;
  233. }
  234. int fWidth = GetFrameWidth ();
  235. if (activityPos [activityPos.Length - 1] < 0) {
  236. for (int i = 0; i < activityPos.Length; i++) {
  237. activityPos [i] = i - activityPos.Length + 2;
  238. }
  239. delta = 1;
  240. } else if (activityPos [0] >= fWidth) {
  241. if (bidirectionalMarquee) {
  242. for (int i = 0; i < activityPos.Length; i++) {
  243. activityPos [i] = fWidth + i - 2;
  244. }
  245. delta = -1;
  246. } else {
  247. PopulateActivityPos ();
  248. }
  249. }
  250. }
  251. SetNeedsDisplay ();
  252. }
  253. ///<inheritdoc/>
  254. public override void Redraw (Rect region)
  255. {
  256. DrawFrame ();
  257. Driver.SetAttribute (GetNormalColor ());
  258. int fWidth = GetFrameWidth ();
  259. if (isActivity) {
  260. Move (padding, padding);
  261. for (int i = 0; i < fWidth; i++)
  262. if (Array.IndexOf (activityPos, i) != -1)
  263. Driver.AddRune (SegmentCharacter);
  264. else
  265. Driver.AddRune (' ');
  266. } else {
  267. Move (padding, padding);
  268. int mid = (int)(fraction * fWidth);
  269. int i;
  270. for (i = 0; i < mid & i < fWidth; i++)
  271. Driver.AddRune (SegmentCharacter);
  272. for (; i < fWidth; i++)
  273. Driver.AddRune (' ');
  274. }
  275. DrawText (fWidth);
  276. }
  277. int GetFrameWidth ()
  278. {
  279. switch (progressBarFormat) {
  280. case ProgressBarFormat.Simple:
  281. case ProgressBarFormat.SimplePlusPercentage:
  282. break;
  283. case ProgressBarFormat.Framed:
  284. case ProgressBarFormat.FramedPlusPercentage:
  285. return Frame.Width - 2;
  286. case ProgressBarFormat.FramedProgressPadded:
  287. return Frame.Width - 2 - padding;
  288. }
  289. return Frame.Width;
  290. }
  291. void DrawText (int fWidth)
  292. {
  293. switch (progressBarFormat) {
  294. case ProgressBarFormat.Simple:
  295. case ProgressBarFormat.Framed:
  296. break;
  297. case ProgressBarFormat.SimplePlusPercentage:
  298. case ProgressBarFormat.FramedPlusPercentage:
  299. case ProgressBarFormat.FramedProgressPadded:
  300. var tf = new TextFormatter () {
  301. Alignment = TextAlignment.Centered,
  302. Text = Text
  303. };
  304. var row = padding + (progressBarFormat == ProgressBarFormat.FramedProgressPadded
  305. ? 2 : 1);
  306. Move (padding, row);
  307. var rect = new Rect (padding, row, fWidth, Frame.Height);
  308. tf?.Draw (ViewToScreen (rect), ColorScheme.HotNormal, ColorScheme.HotNormal,
  309. SuperView == null ? default : SuperView.ViewToScreen (SuperView.Bounds));
  310. break;
  311. }
  312. }
  313. // TODO: Use v2 Frames
  314. void DrawFrame ()
  315. {
  316. switch (progressBarFormat) {
  317. case ProgressBarFormat.Simple:
  318. case ProgressBarFormat.SimplePlusPercentage:
  319. padding = 0;
  320. break;
  321. case ProgressBarFormat.Framed:
  322. case ProgressBarFormat.FramedPlusPercentage:
  323. padding = 1;
  324. Border.DrawFrame (Bounds, false);
  325. break;
  326. case ProgressBarFormat.FramedProgressPadded:
  327. padding = 2;
  328. Border.DrawFrame (Bounds, false);
  329. Border.DrawFrame (new Rect (Bounds.X + padding/2, Bounds.Y + padding/2, Bounds.Width - (padding), Bounds.Height - padding - 1), false);
  330. break;
  331. }
  332. }
  333. void PopulateActivityPos ()
  334. {
  335. activityPos = new int [Math.Min (Frame.Width / 3, 5)];
  336. for (int i = 0; i < activityPos.Length; i++) {
  337. activityPos [i] = i - activityPos.Length + 1;
  338. }
  339. }
  340. ///<inheritdoc/>
  341. public override bool OnEnter (View view)
  342. {
  343. Application.Driver.SetCursorVisibility (CursorVisibility.Invisible);
  344. return base.OnEnter (view);
  345. }
  346. }
  347. }