فهرست منبع

Add SpinnerView

tznind 2 سال پیش
والد
کامیت
82e41a928a
3فایلهای تغییر یافته به همراه156 افزوده شده و 1 حذف شده
  1. 74 0
      Terminal.Gui/Views/SpinnerView.cs
  2. 11 1
      UICatalog/Scenarios/Progress.cs
  3. 71 0
      UnitTests/Views/SpinnerViewTests.cs

+ 74 - 0
Terminal.Gui/Views/SpinnerView.cs

@@ -0,0 +1,74 @@
+using System;
+
+namespace Terminal.Gui {
+
+	/// <summary>
+	/// A 1x1 <see cref="View"/> based on <see cref="Label"/> which displays a spinning
+	/// line character.
+	/// </summary>
+	/// <remarks>
+	/// By default animation only occurs when you call <see cref="View.SetNeedsDisplay()"/>.
+	/// Use <see cref="AutoSpin"/> to make the automate calls to <see cref="View.SetNeedsDisplay()"/>.
+	/// </remarks>
+	public class SpinnerView : Label {
+		private Rune [] runes = new Rune [] { '|', '/', '\u2500', '\\' };
+		private int currentIdx = 0;
+		private DateTime lastRender = DateTime.MinValue;
+		private object _timeout;
+
+		/// <summary>
+		/// Gets or sets the number of milliseconds to wait between characters
+		/// in the spin.  Defaults to 250.
+		/// </summary>
+		/// <remarks>This is the maximum speed the spinner will rotate at.  You still need to
+		/// call <see cref="View.SetNeedsDisplay()"/> or <see cref="SpinnerView.AutoSpin"/> to
+		/// advance/start animation.</remarks>
+		public int SpinDelayInMilliseconds { get; set; } = 250;
+
+		/// <summary>
+		/// Creates a new instance of the <see cref="SpinnerView"/> class.
+		/// </summary>
+		public SpinnerView ()
+		{
+			Width = 1; Height = 1;
+		}
+
+		/// <inheritdoc/>
+		public override void Redraw (Rect bounds)
+		{
+			if (DateTime.Now - lastRender > TimeSpan.FromMilliseconds (SpinDelayInMilliseconds)) {
+				currentIdx = (currentIdx + 1) % runes.Length;
+				Text = "" + runes [currentIdx];
+				lastRender = DateTime.Now;
+			}
+
+			base.Redraw (bounds);
+		}
+
+		/// <summary>
+		/// Automates spinning
+		/// </summary>
+		public void AutoSpin()
+		{
+			if(_timeout != null) {
+				return;
+			}
+
+			_timeout = Application.MainLoop.AddTimeout (
+				TimeSpan.FromMilliseconds (SpinDelayInMilliseconds), (m) => {
+					Application.MainLoop.Invoke (this.SetNeedsDisplay);
+					return true;
+				});
+		}
+
+		/// <inheritdoc/>
+		protected override void Dispose (bool disposing)
+		{
+			if (_timeout != null) {
+				Application.MainLoop.RemoveTimeout (_timeout);
+			}
+
+			base.Dispose (disposing);
+		}
+	}
+}

+ 11 - 1
UICatalog/Scenarios/Progress.cs

@@ -3,6 +3,7 @@ using System;
 using System.Threading;
 using Terminal.Gui;
 using System.Linq;
+using System.Runtime.CompilerServices;
 
 namespace UICatalog.Scenarios {
 	// 
@@ -20,6 +21,7 @@ namespace UICatalog.Scenarios {
 			internal TextField Speed { get; private set; }
 			internal ProgressBar ActivityProgressBar { get; private set; }
 			internal ProgressBar PulseProgressBar { get; private set; }
+			internal SpinnerView Spinner { get; private set; }
 			internal Action StartBtnClick;
 			internal Action StopBtnClick;
 			internal Action PulseBtnClick = null;
@@ -77,13 +79,19 @@ namespace UICatalog.Scenarios {
 				ActivityProgressBar = new ProgressBar () {
 					X = Pos.Right (LeftFrame) + 1,
 					Y = Pos.Bottom (startButton) + 1,
-					Width = Dim.Fill (),
+					Width = Dim.Fill (1),
 					Height = 1,
 					Fraction = 0.25F,
 					ColorScheme = Colors.Error
 				};
 				Add (ActivityProgressBar);
 
+				Spinner = new SpinnerView {
+					X = Pos.Right (ActivityProgressBar),
+					Y = ActivityProgressBar.Y
+				};
+				Add (Spinner);
+
 				PulseProgressBar = new ProgressBar () {
 					X = Pos.Right (LeftFrame) + 1,
 					Y = Pos.Bottom (ActivityProgressBar) + 1,
@@ -130,6 +138,7 @@ namespace UICatalog.Scenarios {
 						ActivityProgressBar.Fraction += 0.01F;
 					}
 					PulseProgressBar.Pulse ();
+					Spinner.SetNeedsDisplay ();
 				}
 			}
 		}
@@ -197,6 +206,7 @@ namespace UICatalog.Scenarios {
 
 				_mainLoopTimeout = Application.MainLoop.AddTimeout (TimeSpan.FromMilliseconds (_mainLooopTimeoutTick), (loop) => {
 					mainLoopTimeoutDemo.Pulse ();
+					
 					return true;
 				});
 			};

+ 71 - 0
UnitTests/Views/SpinnerViewTests.cs

@@ -0,0 +1,71 @@
+using Terminal.Gui;
+using Xunit;
+using Xunit.Abstractions;
+
+namespace UnitTests.Views {
+	public class SpinnerViewTests {
+
+		readonly ITestOutputHelper output;
+
+		public SpinnerViewTests (ITestOutputHelper output)
+		{
+			this.output = output;
+		}
+
+
+		[Fact,AutoInitShutdown]
+		public void TestSpinnerView_ThrottlesAnimation()
+		{
+			var view = GetSpinnerView ();
+
+			view.Redraw (view.Bounds);
+			
+			var expected = "/";
+			TestHelpers.AssertDriverContentsWithFrameAre (expected, output);
+
+			view.SetNeedsDisplay ();
+			view.Redraw (view.Bounds);
+
+			expected = "/";
+			TestHelpers.AssertDriverContentsWithFrameAre (expected, output);
+
+			view.SetNeedsDisplay ();
+			view.Redraw (view.Bounds);
+
+			expected = "/";
+			TestHelpers.AssertDriverContentsWithFrameAre (expected, output);
+		}
+		[Fact, AutoInitShutdown]
+		public void TestSpinnerView_NoThrottle()
+		{
+			var view = GetSpinnerView ();
+			view.SpinDelayInMilliseconds = 0;
+
+			view.Redraw (view.Bounds);
+
+
+			var expected = @"─";
+			TestHelpers.AssertDriverContentsWithFrameAre (expected, output);
+
+			view.SetNeedsDisplay ();
+			view.Redraw (view.Bounds);
+
+
+			expected = @"\";
+			TestHelpers.AssertDriverContentsWithFrameAre (expected, output);
+		}
+
+		private SpinnerView GetSpinnerView ()
+		{
+			var view = new SpinnerView (); 
+			
+			Application.Top.Add (view);
+			Application.Begin (Application.Top);
+
+			Assert.Equal (1, view.Width);
+			Assert.Equal (1, view.Height);
+
+			return view;
+		}
+	}
+}