diff --git a/Terminal.Gui/Views/SpinnerView.cs b/Terminal.Gui/Views/SpinnerView.cs
new file mode 100644
index 000000000..06984462c
--- /dev/null
+++ b/Terminal.Gui/Views/SpinnerView.cs
@@ -0,0 +1,74 @@
+using System;
+
+namespace Terminal.Gui {
+
+ ///
+ /// A 1x1 based on which displays a spinning
+ /// line character.
+ ///
+ ///
+ /// By default animation only occurs when you call .
+ /// Use to make the automate calls to .
+ ///
+ public class SpinnerView : Label {
+ private Rune [] runes = new Rune [] { '|', '/', '\u2500', '\\' };
+ private int currentIdx = 0;
+ private DateTime lastRender = DateTime.MinValue;
+ private object _timeout;
+
+ ///
+ /// Gets or sets the number of milliseconds to wait between characters
+ /// in the spin. Defaults to 250.
+ ///
+ /// This is the maximum speed the spinner will rotate at. You still need to
+ /// call or to
+ /// advance/start animation.
+ public int SpinDelayInMilliseconds { get; set; } = 250;
+
+ ///
+ /// Creates a new instance of the class.
+ ///
+ public SpinnerView ()
+ {
+ Width = 1; Height = 1;
+ }
+
+ ///
+ 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);
+ }
+
+ ///
+ /// Automates spinning
+ ///
+ public void AutoSpin()
+ {
+ if(_timeout != null) {
+ return;
+ }
+
+ _timeout = Application.MainLoop.AddTimeout (
+ TimeSpan.FromMilliseconds (SpinDelayInMilliseconds), (m) => {
+ Application.MainLoop.Invoke (this.SetNeedsDisplay);
+ return true;
+ });
+ }
+
+ ///
+ protected override void Dispose (bool disposing)
+ {
+ if (_timeout != null) {
+ Application.MainLoop.RemoveTimeout (_timeout);
+ }
+
+ base.Dispose (disposing);
+ }
+ }
+}
\ No newline at end of file
diff --git a/UICatalog/Scenarios/Progress.cs b/UICatalog/Scenarios/Progress.cs
index 40cbb5aa9..94f278e71 100644
--- a/UICatalog/Scenarios/Progress.cs
+++ b/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,
@@ -129,6 +137,7 @@ namespace UICatalog.Scenarios {
ActivityProgressBar.Fraction += 0.01F;
}
PulseProgressBar.Pulse ();
+ Spinner.SetNeedsDisplay ();
}
}
}
@@ -196,6 +205,7 @@ namespace UICatalog.Scenarios {
_mainLoopTimeout = Application.MainLoop.AddTimeout (TimeSpan.FromMilliseconds (_mainLooopTimeoutTick), (loop) => {
mainLoopTimeoutDemo.Pulse ();
+
return true;
});
};
diff --git a/UnitTests/Views/SpinnerViewTests.cs b/UnitTests/Views/SpinnerViewTests.cs
new file mode 100644
index 000000000..3a6d6c482
--- /dev/null
+++ b/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;
+ }
+ }
+}
\ No newline at end of file