Browse Source

Gradient tests

tznind 1 year ago
parent
commit
116cba8c8c

+ 38 - 4
Terminal.Gui/Drawing/Gradient.cs

@@ -44,15 +44,42 @@ public class Gradient
     private readonly List<int> _steps;
 
 
+    /// <summary>
+    /// Creates a new instance of the <see cref="Gradient"/> class which hosts a <see cref="Spectrum"/>
+    /// of colors including all <paramref name="stops"/> and <paramref name="steps"/> interpolated colors
+    /// between each corresponding pair.
+    /// </summary>
+    /// <param name="stops">The colors to use in the spectrum (N)</param>
+    /// <param name="steps">The number of colors to generate between each pair (must be N-1 numbers).
+    /// If only one step is passed then it is assumed to be the same distance for all pairs.</param>
+    /// <param name="loop">True to duplicate the first stop and step so that the gradient repeats itself</param>
+    /// <exception cref="ArgumentException"></exception>
     public Gradient (IEnumerable<Color> stops, IEnumerable<int> steps, bool loop = false)
     {
         _stops = stops.ToList ();
+
         if (_stops.Count < 1)
+        {
             throw new ArgumentException ("At least one color stop must be provided.");
+        }
 
         _steps = steps.ToList ();
+
+        // If multiple colors and only 1 step assume same distance applies to all steps
+        if (_stops.Count > 2 && _steps.Count == 1)
+        {
+            _steps = Enumerable.Repeat (_steps.Single (),_stops.Count() - 1).ToList();
+        }
+
         if (_steps.Any (step => step < 1))
+        {
             throw new ArgumentException ("Steps must be greater than 0.");
+        }   
+
+        if (_steps.Count != _stops.Count - 1)
+        {
+            throw new ArgumentException ("Number of steps must be N-1");
+        }
 
         _loop = loop;
         Spectrum = GenerateGradient (_steps);
@@ -85,6 +112,7 @@ public class Gradient
     private List<Color> GenerateGradient (IEnumerable<int> steps)
     {
         List<Color> gradient = new List<Color> ();
+
         if (_stops.Count == 1)
         {
             for (int i = 0; i < steps.Sum (); i++)
@@ -94,13 +122,17 @@ public class Gradient
             return gradient;
         }
 
+        var stopsToUse = _stops.ToList ();
+        var stepsToUse = _steps.ToList ();
+
         if (_loop)
         {
-            _stops.Add (_stops [0]);
+            stopsToUse.Add (_stops [0]);
+            stepsToUse.Add (_steps.First ());
         }
 
-        var colorPairs = _stops.Zip (_stops.Skip (1), (start, end) => new { start, end });
-        var stepsList = _steps.ToList ();
+        var colorPairs = stopsToUse.Zip (stopsToUse.Skip (1), (start, end) => new { start, end });
+        var stepsList = stepsToUse;
 
         foreach (var (colorPair, thesteps) in colorPairs.Zip (stepsList, (pair, step) => (pair, step)))
         {
@@ -112,7 +144,7 @@ public class Gradient
 
     private IEnumerable<Color> InterpolateColors (Color start, Color end, int steps)
     {
-        for (int step = 0; step <= steps; step++)
+        for (int step = 0; step < steps; step++)
         {
             double fraction = (double)step / steps;
             int r = (int)(start.R + fraction * (end.R - start.R));
@@ -120,8 +152,10 @@ public class Gradient
             int b = (int)(start.B + fraction * (end.B - start.B));
             yield return new Color (r, g, b);
         }
+        yield return end; // Ensure the last color is included
     }
 
+
     /// <summary>
     /// <para>
     /// Creates a mapping starting at 0,0 and going to <paramref name="maxRow"/> and <paramref name="maxColumn"/>

+ 28 - 14
UICatalog/Scenarios/TextEffectsScenario.cs

@@ -14,6 +14,8 @@ public class TextEffectsScenario : Scenario
 {
     private TabView tabView;
 
+    public static bool LoopingGradient = false;
+
     public override void Main ()
     {
         Application.Init ();
@@ -27,8 +29,6 @@ public class TextEffectsScenario : Scenario
         w.Loaded += (s, e) =>
         {
             SetupGradientLineCanvas (w, w.Frame.Size);
-            // TODO: Does not work
-            //  SetupGradientLineCanvas (tabView, tabView.Frame.Size);
         };
         w.SizeChanging += (s, e) =>
         {
@@ -36,9 +36,6 @@ public class TextEffectsScenario : Scenario
             {
                 SetupGradientLineCanvas (w, e.Size.Value);
             }
-
-            // TODO: Does not work
-            //SetupGradientLineCanvas (tabView, tabView.Frame.Size);
         };
 
         w.ColorScheme = new ColorScheme
@@ -57,16 +54,32 @@ public class TextEffectsScenario : Scenario
             Height = Dim.Fill (),
         };
 
+        var gradientsView = new GradientsView ()
+        {
+            Width = Dim.Fill (),
+            Height = Dim.Fill (),
+        };
         var t1 = new Tab ()
         {
-            View = new GradientsView ()
-            {
-                Width = Dim.Fill (),
-                Height = Dim.Fill (),
-            },
+            View = gradientsView,
             DisplayText = "Gradients"
         };
 
+
+        var cbLooping = new CheckBox ()
+        {
+            Text = "Looping",
+            Y = Pos.AnchorEnd (1)
+        };
+        cbLooping.Toggle += (s, e) =>
+        {
+            LoopingGradient = e.NewValue == CheckState.Checked;
+            SetupGradientLineCanvas (w, w.Frame.Size);
+            tabView.SetNeedsDisplay ();
+        };
+
+        gradientsView.Add (cbLooping);
+
         tabView.AddTab (t1, false);
 
         w.Add (tabView);
@@ -83,7 +96,7 @@ public class TextEffectsScenario : Scenario
     {
         GetAppealingGradientColors (out var stops, out var steps);
 
-        var g = new Gradient (stops, steps);
+        var g = new Gradient (stops, steps, LoopingGradient);
 
         var fore = new GradientFill (
             new Rectangle (0, 0, size.Width, size.Height), g, GradientDirection.Diagonal);
@@ -107,7 +120,8 @@ public class TextEffectsScenario : Scenario
         };
 
         // Define the number of steps between each color for smoother transitions
-        steps = new List<int> { 15, 15, 15, 15 };
+        // If we pass only a single value then it will assume equal steps between all pairs
+        steps = new List<int> { 15 };
     }
 }
 
@@ -157,7 +171,7 @@ internal class GradientsView : View
         var steps = new List<int> { 10, 10 }; // 10 steps between Red -> Green, and Green -> Blue
 
         // Create the gradient
-        var radialGradient = new Gradient (stops, steps, loop: false);
+        var radialGradient = new Gradient (stops, steps, loop: TextEffectsScenario.LoopingGradient);
 
         // Define the size of the rectangle
         int maxRow = 15; // Adjusted to keep aspect ratio
@@ -207,7 +221,7 @@ internal class GradientsView : View
         };
 
         // Create the gradient
-        var rainbowGradient = new Gradient (stops, steps, loop: true);
+        var rainbowGradient = new Gradient (stops, steps, TextEffectsScenario.LoopingGradient);
 
         for (int x = 0; x < viewport.Width; x++)
         {

+ 123 - 1
UnitTests/Drawing/GradientTests.cs

@@ -50,4 +50,126 @@ public class GradientTests
         Assert.Equal (c.Key, new Point(0,0));
         Assert.Equal (c.Value, new Color (0, 0, 255));
     }
-}
+
+    [Fact]
+    public void SingleColorStop ()
+    {
+        var stops = new List<Color> { new Color (255, 0, 0) }; // Red
+        var steps = new List<int> { };
+
+        var g = new Gradient (stops, steps, loop: false);
+        Assert.All (g.Spectrum, color => Assert.Equal (new Color (255, 0, 0), color));
+    }
+
+    [Fact]
+    public void LoopingGradient_CorrectColors ()
+    {
+        var stops = new List<Color>
+            {
+                new Color(255, 0, 0),    // Red
+                new Color(0, 0, 255)     // Blue
+            };
+
+        var steps = new List<int> { 10 };
+
+        var g = new Gradient (stops, steps, loop: true);
+        Assert.Equal (new Color (255, 0, 0), g.Spectrum.First ());
+        Assert.Equal (new Color (255, 0, 0), g.Spectrum.Last ());
+    }
+
+    [Fact]
+    public void DifferentStepSizes ()
+    {
+        var stops = new List<Color>
+            {
+                new Color(255, 0, 0),    // Red
+                new Color(0, 255, 0),    // Green
+                new Color(0, 0, 255)     // Blue
+            };
+
+        var steps = new List<int> { 5, 15 }; // Different steps
+
+        var g = new Gradient (stops, steps, loop: false);
+        Assert.Equal (22, g.Spectrum.Count);
+    }
+
+    [Fact]
+    public void FractionOutOfRange_ThrowsException ()
+    {
+        var stops = new List<Color>
+            {
+                new Color(255, 0, 0),    // Red
+                new Color(0, 0, 255)     // Blue
+            };
+
+        var steps = new List<int> { 10 };
+
+        var g = new Gradient (stops, steps, loop: false);
+
+        Assert.Throws<ArgumentOutOfRangeException> (() => g.GetColorAtFraction (-0.1));
+        Assert.Throws<ArgumentOutOfRangeException> (() => g.GetColorAtFraction (1.1));
+    }
+
+    [Fact]
+    public void NaNFraction_ReturnsLastColor ()
+    {
+        var stops = new List<Color>
+            {
+                new Color(255, 0, 0),    // Red
+                new Color(0, 0, 255)     // Blue
+            };
+
+        var steps = new List<int> { 10 };
+
+        var g = new Gradient (stops, steps, loop: false);
+        Assert.Equal (new Color (0, 0, 255), g.GetColorAtFraction (double.NaN));
+    }
+
+    [Fact]
+    public void Constructor_SingleStepProvided_ReplicatesForAllPairs ()
+    {
+        var stops = new List<Color>
+    {
+        new Color(255, 0, 0),    // Red
+        new Color(0, 255, 0),    // Green
+        new Color(0, 0, 255)     // Blue
+    };
+
+        var singleStep = new List<int> { 5 }; // Single step provided
+        var gradient = new Gradient (stops, singleStep, loop: false);
+
+        Assert.NotNull (gradient.Spectrum);
+        Assert.Equal (12, gradient.Spectrum.Count); // 5 steps Red -> Green + 5 steps Green -> Blue + 2 end colors
+    }
+
+    [Fact]
+    public void Constructor_InvalidStepsLength_ThrowsArgumentException ()
+    {
+        var stops = new List<Color>
+    {
+        new Color(255, 0, 0),    // Red
+        new Color(0, 0, 255)     // Blue
+    };
+
+        var invalidSteps = new List<int> { 5, 5 }; // Invalid length (N-1 expected)
+        Assert.Throws<ArgumentException> (() => new Gradient (stops, invalidSteps, loop: false));
+    }
+
+    [Fact]
+    public void Constructor_ValidStepsLength_DoesNotThrow ()
+    {
+        var stops = new List<Color>
+    {
+        new Color(255, 0, 0),    // Red
+        new Color(0, 255, 0),    // Green
+        new Color(0, 0, 255)     // Blue
+    };
+
+        var validSteps = new List<int> { 5, 5 }; // Valid length (N-1)
+        var gradient = new Gradient (stops, validSteps, loop: false);
+
+        Assert.NotNull (gradient.Spectrum);
+        Assert.Equal (12, gradient.Spectrum.Count); // 5 steps Red -> Green + 5 steps Green -> Blue + 2 end colors
+    }
+
+}