Browse Source

Radial gradient

tznind 1 year ago
parent
commit
ab07f53bd2

+ 273 - 0
Terminal.Gui/TextEffects/ArgValidators.cs

@@ -0,0 +1,273 @@
+using System;
+using System.Collections.Generic;
+using System.Globalization;
+using System.Linq;
+using System.Text;
+using Terminal.Gui.TextEffects;
+
+using Color = Terminal.Gui.TextEffects.Color;
+
+public static class PositiveInt
+{
+    public static int Parse (string arg)
+    {
+        if (int.TryParse (arg, out int value) && value > 0)
+        {
+            return value;
+        }
+        else
+        {
+            throw new ArgumentException ($"invalid value: '{arg}' is not > 0.");
+        }
+    }
+}
+
+public static class NonNegativeInt
+{
+    public static int Parse (string arg)
+    {
+        if (int.TryParse (arg, out int value) && value >= 0)
+        {
+            return value;
+        }
+        else
+        {
+            throw new ArgumentException ($"invalid value: '{arg}' Argument must be int >= 0.");
+        }
+    }
+}
+
+public static class IntRange
+{
+    public static (int, int) Parse (string arg)
+    {
+        var parts = arg.Split ('-');
+        if (parts.Length == 2 && int.TryParse (parts [0], out int start) && int.TryParse (parts [1], out int end) && start > 0 && start <= end)
+        {
+            return (start, end);
+        }
+        else
+        {
+            throw new ArgumentException ($"invalid range: '{arg}' is not a valid range. Must be start-end. Ex: 1-10");
+        }
+    }
+}
+
+public static class PositiveFloat
+{
+    public static float Parse (string arg)
+    {
+        if (float.TryParse (arg, out float value) && value > 0)
+        {
+            return value;
+        }
+        else
+        {
+            throw new ArgumentException ($"invalid value: '{arg}' is not a valid value. Argument must be a float > 0.");
+        }
+    }
+}
+
+public static class NonNegativeFloat
+{
+    public static float Parse (string arg)
+    {
+        if (float.TryParse (arg, out float value) && value >= 0)
+        {
+            return value;
+        }
+        else
+        {
+            throw new ArgumentException ($"invalid argument value: '{arg}' is out of range. Must be float >= 0.");
+        }
+    }
+}
+
+public static class PositiveFloatRange
+{
+    public static (float, float) Parse (string arg)
+    {
+        var parts = arg.Split ('-');
+        if (parts.Length == 2 && float.TryParse (parts [0], out float start) && float.TryParse (parts [1], out float end) && start > 0 && start <= end)
+        {
+            return (start, end);
+        }
+        else
+        {
+            throw new ArgumentException ($"invalid range: '{arg}' is not a valid range. Must be start-end. Ex: 0.1-1.0");
+        }
+    }
+}
+
+public static class Ratio
+{
+    public static float Parse (string arg)
+    {
+        if (float.TryParse (arg, out float value) && value >= 0 && value <= 1)
+        {
+            return value;
+        }
+        else
+        {
+            throw new ArgumentException ($"invalid value: '{arg}' is not a float >= 0 and <= 1. Example: 0.5");
+        }
+    }
+}
+
+public enum GradientDirection
+{
+    Horizontal,
+    Vertical,
+    Diagonal,
+    Radial
+}
+
+public static class GradientDirectionParser
+{
+    public static GradientDirection Parse (string arg)
+    {
+        return arg.ToLower () switch
+        {
+            "horizontal" => GradientDirection.Horizontal,
+            "vertical" => GradientDirection.Vertical,
+            "diagonal" => GradientDirection.Diagonal,
+            "radial" => GradientDirection.Radial,
+            _ => throw new ArgumentException ($"invalid gradient direction: '{arg}' is not a valid gradient direction. Choices are diagonal, horizontal, vertical, or radial."),
+        };
+    }
+}
+
+public static class ColorArg
+{
+    public static Color Parse (string arg)
+    {
+        if (int.TryParse (arg, out int xtermValue) && xtermValue >= 0 && xtermValue <= 255)
+        {
+            return new Color (xtermValue);
+        }
+        else if (arg.Length == 6 && int.TryParse (arg, NumberStyles.HexNumber, null, out int _))
+        {
+            return new Color (arg);
+        }
+        else
+        {
+            throw new ArgumentException ($"invalid color value: '{arg}' is not a valid XTerm or RGB color. Must be in range 0-255 or 000000-FFFFFF.");
+        }
+    }
+}
+
+public static class Symbol
+{
+    public static string Parse (string arg)
+    {
+        if (arg.Length == 1 && IsAsciiOrUtf8 (arg))
+        {
+            return arg;
+        }
+        else
+        {
+            throw new ArgumentException ($"invalid symbol: '{arg}' is not a valid symbol. Must be a single ASCII/UTF-8 character.");
+        }
+    }
+
+    private static bool IsAsciiOrUtf8 (string s)
+    {
+        try
+        {
+            Encoding.ASCII.GetBytes (s);
+        }
+        catch (EncoderFallbackException)
+        {
+            try
+            {
+                Encoding.UTF8.GetBytes (s);
+            }
+            catch (EncoderFallbackException)
+            {
+                return false;
+            }
+        }
+        return true;
+    }
+}
+
+public static class CanvasDimension
+{
+    public static int Parse (string arg)
+    {
+        if (int.TryParse (arg, out int value) && value >= -1)
+        {
+            return value;
+        }
+        else
+        {
+            throw new ArgumentException ($"invalid value: '{arg}' is not >= -1.");
+        }
+    }
+}
+
+public static class TerminalDimensions
+{
+    public static (int, int) Parse (string arg)
+    {
+        var parts = arg.Split (' ');
+        if (parts.Length == 2 && int.TryParse (parts [0], out int width) && int.TryParse (parts [1], out int height) && width >= 0 && height >= 0)
+        {
+            return (width, height);
+        }
+        else
+        {
+            throw new ArgumentException ($"invalid terminal dimensions: '{arg}' is not a valid terminal dimension. Must be >= 0.");
+        }
+    }
+}
+
+public static class Ease
+{
+    private static readonly Dictionary<string, EasingFunction> easingFuncMap = new ()
+    {
+        {"linear", Easing.Linear},
+        {"in_sine", Easing.InSine},
+        {"out_sine", Easing.OutSine},
+        {"in_out_sine", Easing.InOutSine},
+        {"in_quad", Easing.InQuad},
+        {"out_quad", Easing.OutQuad},
+        {"in_out_quad", Easing.InOutQuad},
+        {"in_cubic", Easing.InCubic},
+        {"out_cubic", Easing.OutCubic},
+        {"in_out_cubic", Easing.InOutCubic},
+        {"in_quart", Easing.InQuart},
+        {"out_quart", Easing.OutQuart},
+        {"in_out_quart", Easing.InOutQuart},
+        {"in_quint", Easing.InQuint},
+        {"out_quint", Easing.OutQuint},
+        {"in_out_quint", Easing.InOutQuint},
+        {"in_expo", Easing.InExpo},
+        {"out_expo", Easing.OutExpo},
+        {"in_out_expo", Easing.InOutExpo},
+        {"in_circ", Easing.InCirc},
+        {"out_circ", Easing.OutCirc},
+        {"in_out_circ", Easing.InOutCirc},
+        {"in_back", Easing.InBack},
+        {"out_back", Easing.OutBack},
+        {"in_out_back", Easing.InOutBack},
+        {"in_elastic", Easing.InElastic},
+        {"out_elastic", Easing.OutElastic},
+        {"in_out_elastic", Easing.InOutElastic},
+        {"in_bounce", Easing.InBounce},
+        {"out_bounce", Easing.OutBounce},
+        {"in_out_bounce", Easing.InOutBounce},
+    };
+
+    public static EasingFunction Parse (string arg)
+    {
+        if (easingFuncMap.TryGetValue (arg.ToLower (), out var easingFunc))
+        {
+            return easingFunc;
+        }
+        else
+        {
+            throw new ArgumentException ($"invalid ease value: '{arg}' is not a valid ease.");
+        }
+    }
+}

+ 6 - 0
Terminal.Gui/TextEffects/BaseEffect.cs

@@ -6,10 +6,16 @@ public abstract class BaseEffectIterator<T>  where T : EffectConfig, new()
     protected Terminal Terminal { get; set; }
     protected Terminal Terminal { get; set; }
     protected List<EffectCharacter> ActiveCharacters { get; set; } = new List<EffectCharacter> ();
     protected List<EffectCharacter> ActiveCharacters { get; set; } = new List<EffectCharacter> ();
 
 
+    protected BaseEffect<T> Effect { get; }
+
+
+
     public BaseEffectIterator (BaseEffect<T> effect)
     public BaseEffectIterator (BaseEffect<T> effect)
     {
     {
+        Effect = effect;
         Config = effect.EffectConfig;
         Config = effect.EffectConfig;
         Terminal = new Terminal (effect.InputData, effect.TerminalConfig);
         Terminal = new Terminal (effect.InputData, effect.TerminalConfig);
+
     }
     }
 
 
     public void Update ()
     public void Update ()

+ 241 - 0
Terminal.Gui/TextEffects/Effects/Beams.cs

@@ -0,0 +1,241 @@
+/*namespace Terminal.Gui.TextEffects.Effects;
+
+public class BeamsConfig : EffectConfig
+{
+    public string [] BeamRowSymbols { get; set; } = { "▂", "▁", "_" };
+    public string [] BeamColumnSymbols { get; set; } = { "▌", "▍", "▎", "▏" };
+    public int BeamDelay { get; set; } = 10;
+    public (int, int) BeamRowSpeedRange { get; set; } = (10, 40);
+    public (int, int) BeamColumnSpeedRange { get; set; } = (6, 10);
+    public Color [] BeamGradientStops { get; set; } = { new Color ("ffffff"), new Color ("00D1FF"), new Color ("8A008A") };
+    public int [] BeamGradientSteps { get; set; } = { 2, 8 };
+    public int BeamGradientFrames { get; set; } = 2;
+    public Color [] FinalGradientStops { get; set; } = { new Color ("8A008A"), new Color ("00D1FF"), new Color ("ffffff") };
+    public int [] FinalGradientSteps { get; set; } = { 12 };
+    public int FinalGradientFrames { get; set; } = 5;
+    public GradientDirection FinalGradientDirection { get; set; } = GradientDirection.Vertical;
+    public int FinalWipeSpeed { get; set; } = 1;
+}
+
+public class Beams : BaseEffect<BeamsConfig>
+{
+    public Beams (string inputData) : base (inputData)
+    {
+    }
+
+    protected override BaseEffectIterator<BeamsConfig> CreateIterator ()
+    {
+        return new BeamsIterator (this);
+    }
+}
+
+
+public class BeamsIterator : BaseEffectIterator<BeamsConfig>
+{
+    private class Group
+    {
+        public List<EffectCharacter> Characters { get; private set; }
+        public string Direction { get; private set; }
+        private Terminal Terminal;
+        private BeamsConfig Config;
+        private double Speed;
+        private float NextCharacterCounter;
+        private List<EffectCharacter> SortedCharacters;
+
+        public Group (List<EffectCharacter> characters, string direction, Terminal terminal, BeamsConfig config)
+        {
+            Characters = characters;
+            Direction = direction;
+            Terminal = terminal;
+            Config = config;
+            Speed = new Random ().Next (config.BeamRowSpeedRange.Item1, config.BeamRowSpeedRange.Item2) * 0.1;
+            NextCharacterCounter = 0;
+            SortedCharacters = direction == "row"
+                ? characters.OrderBy (c => c.InputCoord.Column).ToList ()
+                : characters.OrderBy (c => c.InputCoord.Row).ToList ();
+
+            if (new Random ().Next (0, 2) == 0)
+            {
+                SortedCharacters.Reverse ();
+            }
+        }
+
+        public void IncrementNextCharacterCounter ()
+        {
+            NextCharacterCounter += (float)Speed;
+        }
+
+        public EffectCharacter GetNextCharacter ()
+        {
+            NextCharacterCounter -= 1;
+            var nextCharacter = SortedCharacters.First ();
+            SortedCharacters.RemoveAt (0);
+            if (nextCharacter.Animation.ActiveScene != null)
+            {
+                nextCharacter.Animation.ActiveScene.ResetScene ();
+                return null;
+            }
+
+            Terminal.SetCharacterVisibility (nextCharacter, true);
+            nextCharacter.Animation.ActivateScene (nextCharacter.Animation.QueryScene ("beam_" + Direction));
+            return nextCharacter;
+        }
+
+        public bool Complete ()
+        {
+            return !SortedCharacters.Any ();
+        }
+    }
+
+    private List<Group> PendingGroups = new List<Group> ();
+    private Dictionary<EffectCharacter, Color> CharacterFinalColorMap = new Dictionary<EffectCharacter, Color> ();
+    private List<Group> ActiveGroups = new List<Group> ();
+    private int Delay = 0;
+    private string Phase = "beams";
+    private List<List<EffectCharacter>> FinalWipeGroups;
+
+    public BeamsIterator (Beams effect) : base (effect)
+    {
+        Build ();
+    }
+
+    private void Build ()
+    {
+        var finalGradient = new Gradient (Effect.Config.FinalGradientStops, Effect.Config.FinalGradientSteps);
+        var finalGradientMapping = finalGradient.BuildCoordinateColorMapping (
+            Effect.Terminal.Canvas.Top,
+            Effect.Terminal.Canvas.Right,
+            Effect.Config.FinalGradientDirection
+        );
+
+        foreach (var character in Effect.Terminal.GetCharacters (fillChars: true))
+        {
+            CharacterFinalColorMap [character] = finalGradientMapping [character.InputCoord];
+        }
+
+        var beamGradient = new Gradient (Effect.Config.BeamGradientStops, Effect.Config.BeamGradientSteps);
+        var groups = new List<Group> ();
+
+        foreach (var row in Effect.Terminal.GetCharactersGrouped (Terminal.CharacterGroup.RowTopToBottom, fillChars: true))
+        {
+            groups.Add (new Group (row, "row", Effect.Terminal, Effect.Config));
+        }
+
+        foreach (var column in Effect.Terminal.GetCharactersGrouped (Terminal.CharacterGroup.ColumnLeftToRight, fillChars: true))
+        {
+            groups.Add (new Group (column, "column", Effect.Terminal, Effect.Config));
+        }
+
+        foreach (var group in groups)
+        {
+            foreach (var character in group.Characters)
+            {
+                var beamRowScene = character.Animation.NewScene (id: "beam_row");
+                var beamColumnScene = character.Animation.NewScene (id: "beam_column");
+                beamRowScene.ApplyGradientToSymbols (
+                    beamGradient, Effect.Config.BeamRowSymbols, Effect.Config.BeamGradientFrames);
+                beamColumnScene.ApplyGradientToSymbols (
+                    beamGradient, Effect.Config.BeamColumnSymbols, Effect.Config.BeamGradientFrames);
+
+                var fadedColor = character.Animation.AdjustColorBrightness (CharacterFinalColorMap [character], 0.3f);
+                var fadeGradient = new Gradient (CharacterFinalColorMap [character], fadedColor, steps: 10);
+                beamRowScene.ApplyGradientToSymbols (fadeGradient, character.InputSymbol, 5);
+                beamColumnScene.ApplyGradientToSymbols (fadeGradient, character.InputSymbol, 5);
+
+                var brightenGradient = new Gradient (fadedColor, CharacterFinalColorMap [character], steps: 10);
+                var brightenScene = character.Animation.NewScene (id: "brighten");
+                brightenScene.ApplyGradientToSymbols (
+                    brightenGradient, character.InputSymbol, Effect.Config.FinalGradientFrames);
+            }
+        }
+
+        PendingGroups = groups;
+        new Random ().Shuffle (PendingGroups);
+    }
+
+    public override bool MoveNext ()
+    {
+        if (Phase != "complete" || ActiveCharacters.Any ())
+        {
+            if (Phase == "beams")
+            {
+                if (Delay == 0)
+                {
+                    if (PendingGroups.Any ())
+                    {
+                        for (int i = 0; i < new Random ().Next (1, 6); i++)
+                        {
+                            if (PendingGroups.Any ())
+                            {
+                                ActiveGroups.Add (PendingGroups.First ());
+                                PendingGroups.RemoveAt (0);
+                            }
+                        }
+                    }
+                    Delay = Effect.Config.BeamDelay;
+                }
+                else
+                {
+                    Delay--;
+                }
+
+                foreach (var group in ActiveGroups)
+                {
+                    group.IncrementNextCharacterCounter ();
+                    if ((int)group.NextCharacterCounter > 1)
+                    {
+                        for (int i = 0; i < (int)group.NextCharacterCounter; i++)
+                        {
+                            if (!group.Complete ())
+                            {
+                                var nextChar = group.GetNextCharacter ();
+                                if (nextChar != null)
+                                {
+                                    ActiveCharacters.Add (nextChar);
+                                }
+                            }
+                        }
+                    }
+                }
+
+                ActiveGroups = ActiveGroups.Where (g => !g.Complete ()).ToList ();
+                if (!PendingGroups.Any () && !ActiveGroups.Any () && !ActiveCharacters.Any ())
+                {
+                    Phase = "final_wipe";
+                }
+            }
+            else if (Phase == "final_wipe")
+            {
+                if (FinalWipeGroups.Any ())
+                {
+                    for (int i = 0; i < Effect.Config.FinalWipeSpeed; i++)
+                    {
+                        if (!FinalWipeGroups.Any ()) break;
+
+                        var nextGroup = FinalWipeGroups.First ();
+                        FinalWipeGroups.RemoveAt (0);
+
+                        foreach (var character in nextGroup)
+                        {
+                            character.Animation.ActivateScene (character.Animation.QueryScene ("brighten"));
+                            Effect.Terminal.SetCharacterVisibility (character, true);
+                            ActiveCharacters.Add (character);
+                        }
+                    }
+                }
+                else
+                {
+                    Phase = "complete";
+                }
+            }
+
+            Update ();
+            return true;
+        }
+        else
+        {
+            return false;
+        }
+    }
+}
+*/

+ 115 - 26
Terminal.Gui/TextEffects/Graphics.cs

@@ -54,32 +54,66 @@ public class Color
     }
     }
 }
 }
 
 
-
 public class Gradient
 public class Gradient
 {
 {
     public List<Color> Spectrum { get; private set; }
     public List<Color> Spectrum { get; private set; }
+    private readonly bool _loop;
+    private readonly List<Color> _stops;
+    private readonly List<int> _steps;
+
+    public enum Direction
+    {
+        Vertical,
+        Horizontal,
+        Radial,
+        Diagonal
+    }
 
 
-    // Constructor now accepts IEnumerable<int> for steps.
     public Gradient (IEnumerable<Color> stops, IEnumerable<int> steps, bool loop = false)
     public Gradient (IEnumerable<Color> stops, IEnumerable<int> steps, bool loop = false)
     {
     {
-        if (stops == null || !stops.Any () || stops.Count () < 2)
-            throw new ArgumentException ("At least two color stops are required to create a gradient.");
-        if (steps == null || !steps.Any ())
-            throw new ArgumentException ("Steps are required to define the transitions between colors.");
+        _stops = stops.ToList ();
+        if (_stops.Count < 1)
+            throw new ArgumentException ("At least one color stop must be provided.");
 
 
-        Spectrum = GenerateGradient (stops.ToList (), steps.ToList (), loop);
+        _steps = steps.ToList ();
+        if (_steps.Any (step => step < 1))
+            throw new ArgumentException ("Steps must be greater than 0.");
+
+        _loop = loop;
+        Spectrum = GenerateGradient (_steps);
+    }
+
+    public Color GetColorAtFraction (double fraction)
+    {
+        if (fraction < 0 || fraction > 1)
+            throw new ArgumentOutOfRangeException (nameof (fraction), "Fraction must be between 0 and 1.");
+        int index = (int)(fraction * (Spectrum.Count - 1));
+        return Spectrum [index];
     }
     }
 
 
-    private List<Color> GenerateGradient (List<Color> stops, List<int> steps, bool loop)
+    private List<Color> GenerateGradient (IEnumerable<int> steps)
     {
     {
         List<Color> gradient = new List<Color> ();
         List<Color> gradient = new List<Color> ();
-        if (loop)
-            stops.Add (stops [0]); // Loop the gradient back to the first color.
+        if (_stops.Count == 1)
+        {
+            for (int i = 0; i < steps.Sum (); i++)
+            {
+                gradient.Add (_stops [0]);
+            }
+            return gradient;
+        }
 
 
-        for (int i = 0; i < stops.Count - 1; i++)
+        if (_loop)
         {
         {
-            int currentSteps = i < steps.Count ? steps [i] : steps.Last ();
-            gradient.AddRange (InterpolateColors (stops [i], stops [i + 1], currentSteps));
+            _stops.Add (_stops [0]);
+        }
+
+        var colorPairs = _stops.Zip (_stops.Skip (1), (start, end) => new { start, end });
+        var stepsList = _steps.ToList ();
+
+        foreach (var (colorPair, thesteps) in colorPairs.Zip (stepsList, (pair, step) => (pair, step)))
+        {
+            gradient.AddRange (InterpolateColors (colorPair.start, colorPair.end, thesteps));
         }
         }
 
 
         return gradient;
         return gradient;
@@ -89,25 +123,80 @@ public class Gradient
     {
     {
         for (int step = 0; step <= steps; step++)
         for (int step = 0; step <= steps; step++)
         {
         {
-            int r = Interpolate (start.R, end.R, steps, step);
-            int g = Interpolate (start.G, end.G, steps, step);
-            int b = Interpolate (start.B, end.B, steps, step);
+            double fraction = (double)step / steps;
+            int r = (int)(start.R + fraction * (end.R - start.R));
+            int g = (int)(start.G + fraction * (end.G - start.G));
+            int b = (int)(start.B + fraction * (end.B - start.B));
             yield return Color.FromRgb (r, g, b);
             yield return Color.FromRgb (r, g, b);
         }
         }
     }
     }
 
 
-    private int Interpolate (int start, int end, int steps, int currentStep)
+    public Dictionary<Coord, Color> BuildCoordinateColorMapping (int maxRow, int maxColumn, Direction direction)
     {
     {
-        return start + (int)((end - start) * (double)currentStep / steps);
+        var gradientMapping = new Dictionary<Coord, Color> ();
+
+        switch (direction)
+        {
+            case Direction.Vertical:
+                for (int row = 0; row <= maxRow; row++)
+                {
+                    double fraction = maxRow == 0 ? 1.0 : (double)row / maxRow;
+                    Color color = GetColorAtFraction (fraction);
+                    for (int col = 0; col <= maxColumn; col++)
+                    {
+                        gradientMapping [new Coord (col, row)] = color;
+                    }
+                }
+                break;
+
+            case Direction.Horizontal:
+                for (int col = 0; col <= maxColumn; col++)
+                {
+                    double fraction = maxColumn == 0 ? 1.0 : (double)col / maxColumn;
+                    Color color = GetColorAtFraction (fraction);
+                    for (int row = 0; row <= maxRow; row++)
+                    {
+                        gradientMapping [new Coord (col, row)] = color;
+                    }
+                }
+                break;
+
+            case Direction.Radial:
+                for (int row = 0; row <= maxRow; row++)
+                {
+                    for (int col = 0; col <= maxColumn; col++)
+                    {
+                        double distanceFromCenter = FindNormalizedDistanceFromCenter (maxRow, maxColumn, new Coord (col, row));
+                        Color color = GetColorAtFraction (distanceFromCenter);
+                        gradientMapping [new Coord (col, row)] = color;
+                    }
+                }
+                break;
+
+            case Direction.Diagonal:
+                for (int row = 0; row <= maxRow; row++)
+                {
+                    for (int col = 0; col <= maxColumn; col++)
+                    {
+                        double fraction = ((double)row * 2 + col) / ((maxRow * 2) + maxColumn);
+                        Color color = GetColorAtFraction (fraction);
+                        gradientMapping [new Coord (col, row)] = color;
+                    }
+                }
+                break;
+        }
+
+        return gradientMapping;
     }
     }
 
 
-    public Color GetColorAtFraction (double fraction)
+    private double FindNormalizedDistanceFromCenter (int maxRow, int maxColumn, Coord coord)
     {
     {
-        if (fraction < 0 || fraction > 1)
-            throw new ArgumentOutOfRangeException (nameof (fraction), "Fraction must be between 0 and 1.");
-        int index = (int)(fraction * (Spectrum.Count - 1));
-        return Spectrum [index];
+        double centerX = maxColumn / 2.0;
+        double centerY = maxRow / 2.0;
+        double dx = coord.Column - centerX;
+        double dy = coord.Row - centerY;
+        double distance = Math.Sqrt (dx * dx + dy * dy);
+        double maxDistance = Math.Sqrt (centerX * centerX + centerY * centerY);
+        return distance / maxDistance;
     }
     }
-}
-
-
+}

+ 28 - 1
Terminal.Gui/TextEffects/Motion.cs

@@ -1,5 +1,4 @@
 namespace Terminal.Gui.TextEffects;
 namespace Terminal.Gui.TextEffects;
-
 public class Coord
 public class Coord
 {
 {
     public int Column { get; set; }
     public int Column { get; set; }
@@ -12,6 +11,34 @@ public class Coord
     }
     }
 
 
     public override string ToString () => $"({Column}, {Row})";
     public override string ToString () => $"({Column}, {Row})";
+
+    public override bool Equals (object obj)
+    {
+        if (obj is Coord other)
+        {
+            return Column == other.Column && Row == other.Row;
+        }
+        return false;
+    }
+
+    public override int GetHashCode ()
+    {
+        return HashCode.Combine (Column, Row);
+    }
+
+    public static bool operator == (Coord left, Coord right)
+    {
+        if (left is null)
+        {
+            return right is null;
+        }
+        return left.Equals (right);
+    }
+
+    public static bool operator != (Coord left, Coord right)
+    {
+        return !(left == right);
+    }
 }
 }
 
 
 public class Waypoint
 public class Waypoint

+ 58 - 7
UICatalog/Scenarios/TextEffectsScenario.cs

@@ -84,6 +84,53 @@ internal class TextEffectsExampleView : View
             resized = false;
             resized = false;
         }
         }
 
 
+        DrawTopLineGradient (viewport);
+        DrawRadialGradient (viewport);
+
+        _ball?.Draw ();
+    }
+
+    private void DrawRadialGradient (Rectangle viewport)
+    {
+        // Define the colors of the gradient stops
+        var stops = new List<Color>
+        {
+            Color.FromRgb(255, 0, 0),    // Red
+            Color.FromRgb(0, 255, 0),    // Green
+            Color.FromRgb(238, 130, 238)  // Violet
+        };
+
+        // Define the number of steps between each color
+        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);
+
+        // Define the size of the rectangle
+        int maxRow = 20;
+        int maxColumn = 40;
+
+        // Build the coordinate-color mapping for a radial gradient
+        var gradientMapping = radialGradient.BuildCoordinateColorMapping (maxRow, maxColumn, Gradient.Direction.Radial);
+
+        // Print the gradient
+        for (int row = 0; row <= maxRow; row++)
+        {
+            for (int col = 0; col <= maxColumn; col++)
+            {
+                var coord = new Coord (col, row);
+                var color = gradientMapping [coord];
+                
+                SetColor (color);
+
+                AddRune (col+2, row+3, new Rune ('█'));
+            }
+        }
+    }
+
+    private void DrawTopLineGradient (Rectangle viewport)
+    {
+
         // Define the colors of the rainbow
         // Define the colors of the rainbow
         var stops = new List<Color>
         var stops = new List<Color>
         {
         {
@@ -115,17 +162,21 @@ internal class TextEffectsExampleView : View
             double fraction = (double)x / (viewport.Width - 1);
             double fraction = (double)x / (viewport.Width - 1);
             Color color = rainbowGradient.GetColorAtFraction (fraction);
             Color color = rainbowGradient.GetColorAtFraction (fraction);
 
 
-            // Assuming AddRune is a method you have for drawing at specific positions
-            Application.Driver.SetAttribute (
-                new Attribute (
-                    new Terminal.Gui.Color (color.R, color.G, color.B),
-                    new Terminal.Gui.Color (color.R, color.G, color.B)
-                )); // Setting color based on RGB
+            SetColor (color);
 
 
             AddRune (x, 0, new Rune ('█'));
             AddRune (x, 0, new Rune ('█'));
         }
         }
+    }
+
+    private void SetColor (Color color)
+    {
+        // Assuming AddRune is a method you have for drawing at specific positions
+        Application.Driver.SetAttribute (
+            new Attribute (
+                new Terminal.Gui.Color (color.R, color.G, color.B),
+                new Terminal.Gui.Color (color.R, color.G, color.B)
+            )); // Setting color based on RGB
 
 
-        _ball?.Draw ();
     }
     }
 
 
     public class Ball
     public class Ball