Progress.cs 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267
  1. using NStack;
  2. using System;
  3. using System.Threading;
  4. using Terminal.Gui;
  5. using System.Linq;
  6. using System.Runtime.CompilerServices;
  7. namespace UICatalog.Scenarios {
  8. //
  9. // This would be a great scenario to show of threading (Issue #471)
  10. //
  11. [ScenarioMetadata (Name: "Progress", Description: "Shows off ProgressBar and Threading.")]
  12. [ScenarioCategory ("Controls")]
  13. [ScenarioCategory ("Threading"), ScenarioCategory ("ProgressBar")]
  14. public class Progress : Scenario {
  15. class ProgressDemo : FrameView {
  16. const int _verticalSpace = 1;
  17. internal FrameView LeftFrame { get; private set; }
  18. internal TextField Speed { get; private set; }
  19. internal ProgressBar ActivityProgressBar { get; private set; }
  20. internal ProgressBar PulseProgressBar { get; private set; }
  21. internal SpinnerView Spinner { get; private set; }
  22. internal Action StartBtnClick;
  23. internal Action StopBtnClick;
  24. internal Action PulseBtnClick = null;
  25. private Label _startedLabel;
  26. internal bool Started {
  27. get {
  28. return _startedLabel.Text == "Started";
  29. }
  30. private set {
  31. _startedLabel.Text = value ? "Started" : "Stopped";
  32. }
  33. }
  34. internal ProgressDemo (string title) : base (title)
  35. {
  36. ColorScheme = Colors.Dialog;
  37. LeftFrame = new FrameView ("Settings") {
  38. X = 0,
  39. Y = 0,
  40. Height = Dim.Percent (100),
  41. Width = Dim.Percent (25)
  42. };
  43. var lbl = new Label (1, 1, "Tick every (ms):");
  44. LeftFrame.Add (lbl);
  45. Speed = new TextField ("") {
  46. X = Pos.X (lbl),
  47. Y = Pos.Bottom (lbl),
  48. Width = 7,
  49. };
  50. LeftFrame.Add (Speed);
  51. Add (LeftFrame);
  52. var startButton = new Button ("Start Timer") {
  53. X = Pos.Right (LeftFrame) + 1,
  54. Y = 0,
  55. };
  56. startButton.Clicked += (s,e) => Start ();
  57. var pulseButton = new Button ("Pulse") {
  58. X = Pos.Right (startButton) + 2,
  59. Y = Pos.Y (startButton),
  60. };
  61. pulseButton.Clicked += (s,e) => Pulse ();
  62. var stopbutton = new Button ("Stop Timer") {
  63. X = Pos.Right (pulseButton) + 2,
  64. Y = Pos.Top (pulseButton),
  65. };
  66. stopbutton.Clicked += (s,e) => Stop ();
  67. Add (startButton);
  68. Add (pulseButton);
  69. Add (stopbutton);
  70. ActivityProgressBar = new ProgressBar () {
  71. X = Pos.Right (LeftFrame) + 1,
  72. Y = Pos.Bottom (startButton) + 1,
  73. Width = Dim.Fill (),
  74. Height = 1,
  75. Fraction = 0.25F,
  76. ColorScheme = Colors.Error
  77. };
  78. Add (ActivityProgressBar);
  79. Spinner = new SpinnerView {
  80. X = Pos.Right (ActivityProgressBar),
  81. Y = ActivityProgressBar.Y,
  82. Visible = false,
  83. };
  84. Add (Spinner);
  85. PulseProgressBar = new ProgressBar () {
  86. X = Pos.Right (LeftFrame) + 1,
  87. Y = Pos.Bottom (ActivityProgressBar) + 1,
  88. Width = Dim.Fill (),
  89. Height = 1,
  90. ColorScheme = Colors.Error
  91. };
  92. Add (PulseProgressBar);
  93. _startedLabel = new Label ("Stopped") {
  94. X = Pos.Right (LeftFrame) + 1,
  95. Y = Pos.Bottom (PulseProgressBar),
  96. };
  97. Add (_startedLabel);
  98. // Explictly cause layout so the setting of Height below works
  99. LayoutSubviews ();
  100. // Set height to height of controls + spacing + frame
  101. Height = 2 + _verticalSpace + startButton.Frame.Height + _verticalSpace + ActivityProgressBar.Frame.Height + _verticalSpace + PulseProgressBar.Frame.Height + _verticalSpace;
  102. }
  103. internal void Start ()
  104. {
  105. Started = true;
  106. StartBtnClick?.Invoke ();
  107. Application.MainLoop.Invoke(()=>{
  108. Spinner.Visible = true;
  109. ActivityProgressBar.Width = Dim.Fill(1);
  110. this.LayoutSubviews();
  111. });
  112. }
  113. internal void Stop ()
  114. {
  115. Started = false;
  116. StopBtnClick?.Invoke ();
  117. Application.MainLoop.Invoke(()=>{
  118. Spinner.Visible = false;
  119. ActivityProgressBar.Width = Dim.Fill();
  120. this.LayoutSubviews();
  121. });
  122. }
  123. internal void Pulse ()
  124. {
  125. if (PulseBtnClick != null) {
  126. PulseBtnClick?.Invoke ();
  127. } else {
  128. if (ActivityProgressBar.Fraction + 0.01F >= 1) {
  129. ActivityProgressBar.Fraction = 0F;
  130. } else {
  131. ActivityProgressBar.Fraction += 0.01F;
  132. }
  133. PulseProgressBar.Pulse ();
  134. Spinner.SetNeedsDisplay ();
  135. }
  136. }
  137. }
  138. private Timer _systemTimer = null;
  139. private uint _systemTimerTick = 100; // ms
  140. private object _mainLoopTimeout = null;
  141. private uint _mainLooopTimeoutTick = 100; // ms
  142. public override void Setup ()
  143. {
  144. // Demo #1 - Use System.Timer (and threading)
  145. var systemTimerDemo = new ProgressDemo ("System.Timer (threads)") {
  146. X = 0,
  147. Y = 0,
  148. Width = Dim.Percent (100),
  149. };
  150. systemTimerDemo.StartBtnClick = () => {
  151. _systemTimer?.Dispose ();
  152. _systemTimer = null;
  153. systemTimerDemo.ActivityProgressBar.Fraction = 0F;
  154. systemTimerDemo.PulseProgressBar.Fraction = 0F;
  155. _systemTimer = new Timer ((o) => {
  156. // Note the check for Mainloop being valid. System.Timers can run after they are Disposed.
  157. // This code must be defensive for that.
  158. Application.MainLoop?.Invoke (() => systemTimerDemo.Pulse ());
  159. }, null, 0, _systemTimerTick);
  160. };
  161. systemTimerDemo.StopBtnClick = () => {
  162. _systemTimer?.Dispose ();
  163. _systemTimer = null;
  164. systemTimerDemo.ActivityProgressBar.Fraction = 1F;
  165. systemTimerDemo.PulseProgressBar.Fraction = 1F;
  166. };
  167. systemTimerDemo.Speed.Text = $"{_systemTimerTick}";
  168. systemTimerDemo.Speed.TextChanged += (s, a) => {
  169. uint result;
  170. if (uint.TryParse (systemTimerDemo.Speed.Text.ToString(), out result)) {
  171. _systemTimerTick = result;
  172. System.Diagnostics.Debug.WriteLine ($"{_systemTimerTick}");
  173. if (systemTimerDemo.Started) {
  174. systemTimerDemo.Start ();
  175. }
  176. } else {
  177. System.Diagnostics.Debug.WriteLine ("bad entry");
  178. }
  179. };
  180. Win.Add (systemTimerDemo);
  181. // Demo #2 - Use Application.MainLoop.AddTimeout (no threads)
  182. var mainLoopTimeoutDemo = new ProgressDemo ("Application.AddTimer (no threads)") {
  183. X = 0,
  184. Y = Pos.Bottom (systemTimerDemo),
  185. Width = Dim.Percent (100),
  186. };
  187. mainLoopTimeoutDemo.StartBtnClick = () => {
  188. mainLoopTimeoutDemo.StopBtnClick ();
  189. mainLoopTimeoutDemo.ActivityProgressBar.Fraction = 0F;
  190. mainLoopTimeoutDemo.PulseProgressBar.Fraction = 0F;
  191. _mainLoopTimeout = Application.MainLoop.AddTimeout (TimeSpan.FromMilliseconds (_mainLooopTimeoutTick), (loop) => {
  192. mainLoopTimeoutDemo.Pulse ();
  193. return true;
  194. });
  195. };
  196. mainLoopTimeoutDemo.StopBtnClick = () => {
  197. if (_mainLoopTimeout != null) {
  198. Application.MainLoop.RemoveTimeout (_mainLoopTimeout);
  199. _mainLoopTimeout = null;
  200. }
  201. mainLoopTimeoutDemo.ActivityProgressBar.Fraction = 1F;
  202. mainLoopTimeoutDemo.PulseProgressBar.Fraction = 1F;
  203. };
  204. mainLoopTimeoutDemo.Speed.Text = $"{_mainLooopTimeoutTick}";
  205. mainLoopTimeoutDemo.Speed.TextChanged += (s, a) => {
  206. uint result;
  207. if (uint.TryParse (mainLoopTimeoutDemo.Speed.Text.ToString (), out result)) {
  208. _mainLooopTimeoutTick = result;
  209. if (mainLoopTimeoutDemo.Started) {
  210. mainLoopTimeoutDemo.Start ();
  211. }
  212. }
  213. };
  214. Win.Add (mainLoopTimeoutDemo);
  215. var startBoth = new Button ("Start Both") {
  216. X = Pos.Center (),
  217. Y = Pos.Bottom(mainLoopTimeoutDemo) + 1,
  218. };
  219. startBoth.Clicked += (s,e) => {
  220. systemTimerDemo.Start ();
  221. mainLoopTimeoutDemo.Start ();
  222. };
  223. Win.Add (startBoth);
  224. }
  225. protected override void Dispose (bool disposing)
  226. {
  227. foreach (var v in Win.Subviews.OfType<ProgressDemo>()) {
  228. v?.StopBtnClick ();
  229. }
  230. base.Dispose (disposing);
  231. }
  232. }
  233. }