tznind 1 year ago
parent
commit
8fbf4d5da9

+ 10 - 13
Terminal.Gui/TextEffects/Animation.cs

@@ -1,4 +1,5 @@
-using static Unix.Terminal.Curses;
+
+using static Terminal.Gui.TextEffects.EventHandler;
 
 
 namespace Terminal.Gui.TextEffects;
 namespace Terminal.Gui.TextEffects;
 
 
@@ -7,7 +8,6 @@ public enum SyncMetric
     Distance,
     Distance,
     Step
     Step
 }
 }
-
 public class CharacterVisual
 public class CharacterVisual
 {
 {
     public string Symbol { get; set; }
     public string Symbol { get; set; }
@@ -21,7 +21,9 @@ public class CharacterVisual
     public bool Strike { get; set; }
     public bool Strike { get; set; }
     public Color Color { get; set; }
     public Color Color { get; set; }
     public string FormattedSymbol { get; private set; }
     public string FormattedSymbol { get; private set; }
-    private string _colorCode;
+    private string _colorCode;  // Holds the ANSI color code or similar string directly
+
+    public string ColorCode => _colorCode;
 
 
     public CharacterVisual (string symbol, bool bold = false, bool dim = false, bool italic = false, bool underline = false, bool blink = false, bool reverse = false, bool hidden = false, bool strike = false, Color color = null, string colorCode = null)
     public CharacterVisual (string symbol, bool bold = false, bool dim = false, bool italic = false, bool underline = false, bool blink = false, bool reverse = false, bool hidden = false, bool strike = false, Color color = null, string colorCode = null)
     {
     {
@@ -35,7 +37,7 @@ public class CharacterVisual
         Hidden = hidden;
         Hidden = hidden;
         Strike = strike;
         Strike = strike;
         Color = color;
         Color = color;
-        _colorCode = colorCode;
+        _colorCode = colorCode;  // Initialize _colorCode from the constructor argument
         FormattedSymbol = FormatSymbol ();
         FormattedSymbol = FormatSymbol ();
     }
     }
 
 
@@ -49,7 +51,7 @@ public class CharacterVisual
         if (Reverse) formattingString += Ansitools.ApplyReverse ();
         if (Reverse) formattingString += Ansitools.ApplyReverse ();
         if (Hidden) formattingString += Ansitools.ApplyHidden ();
         if (Hidden) formattingString += Ansitools.ApplyHidden ();
         if (Strike) formattingString += Ansitools.ApplyStrikethrough ();
         if (Strike) formattingString += Ansitools.ApplyStrikethrough ();
-        if (_colorCode != null) formattingString += Colorterm.Fg (_colorCode);
+        if (_colorCode != null) formattingString += Colorterm.Fg (_colorCode);  // Use the direct color code
 
 
         return $"{formattingString}{Symbol}{(formattingString != "" ? Ansitools.ResetAll () : "")}";
         return $"{formattingString}{Symbol}{(formattingString != "" ? Ansitools.ResetAll () : "")}";
     }
     }
@@ -67,6 +69,7 @@ public class CharacterVisual
     }
     }
 }
 }
 
 
+
 public class Frame
 public class Frame
 {
 {
     public CharacterVisual CharacterVisual { get; }
     public CharacterVisual CharacterVisual { get; }
@@ -97,8 +100,8 @@ public class Scene
     public List<Frame> Frames { get; } = new List<Frame> ();
     public List<Frame> Frames { get; } = new List<Frame> ();
     public List<Frame> PlayedFrames { get; } = new List<Frame> ();
     public List<Frame> PlayedFrames { get; } = new List<Frame> ();
     public Dictionary<int, Frame> FrameIndexMap { get; } = new Dictionary<int, Frame> ();
     public Dictionary<int, Frame> FrameIndexMap { get; } = new Dictionary<int, Frame> ();
-    public int EasingTotalSteps { get; private set; }
-    public int EasingCurrentStep { get; private set; }
+    public int EasingTotalSteps { get; set; }
+    public int EasingCurrentStep { get; set; }
     public static Dictionary<string, int> XtermColorMap { get; } = new Dictionary<string, int> ();
     public static Dictionary<string, int> XtermColorMap { get; } = new Dictionary<string, int> ();
 
 
     public Scene (string sceneId, bool isLooping = false, SyncMetric? sync = null, EasingFunction ease = null, bool noColor = false, bool useXtermColors = false)
     public Scene (string sceneId, bool isLooping = false, SyncMetric? sync = null, EasingFunction ease = null, bool noColor = false, bool useXtermColors = false)
@@ -470,12 +473,6 @@ public class Animation
     }
     }
 }
 }
 
 
-// Dummy Enum for Event handling
-public enum Event
-{
-    SceneComplete,
-    SceneActivated
-}
 
 
 // Dummy classes for Ansitools, Colorterm, and Hexterm as placeholders
 // Dummy classes for Ansitools, Colorterm, and Hexterm as placeholders
 public static class Ansitools
 public static class Ansitools

+ 3 - 1
Terminal.Gui/TextEffects/BaseCharacter.cs

@@ -77,7 +77,9 @@ public class EventHandler
                     Character.Motion.CurrentCoord = (Coord)target;
                     Character.Motion.CurrentCoord = (Coord)target;
                     break;
                     break;
                 case Action.Callback:
                 case Action.Callback:
-                    (target as Action)?.Invoke ();
+
+                    // TODO:
+                    throw new NotImplementedException ("TODO, port (target as Action)?.Invoke ()");
                     break;
                     break;
                 default:
                 default:
                     throw new ArgumentOutOfRangeException (nameof (action), "Unhandled action.");
                     throw new ArgumentOutOfRangeException (nameof (action), "Unhandled action.");

+ 3 - 23
Terminal.Gui/TextEffects/BaseEffect.cs

@@ -1,6 +1,6 @@
 namespace Terminal.Gui.TextEffects;
 namespace Terminal.Gui.TextEffects;
 
 
-public abstract class BaseEffectIterator<T> : IEnumerable<string> where T : EffectConfig
+public abstract class BaseEffectIterator<T>  where T : EffectConfig, new()
 {
 {
     protected T Config { get; set; }
     protected T Config { get; set; }
     protected Terminal Terminal { get; set; }
     protected Terminal Terminal { get; set; }
@@ -12,8 +12,6 @@ public abstract class BaseEffectIterator<T> : IEnumerable<string> where T : Effe
         Terminal = new Terminal (effect.InputData, effect.TerminalConfig);
         Terminal = new Terminal (effect.InputData, effect.TerminalConfig);
     }
     }
 
 
-    public string Frame => Terminal.GetFormattedOutputString ();
-
     public void Update ()
     public void Update ()
     {
     {
         foreach (var character in ActiveCharacters)
         foreach (var character in ActiveCharacters)
@@ -23,17 +21,6 @@ public abstract class BaseEffectIterator<T> : IEnumerable<string> where T : Effe
         ActiveCharacters.RemoveAll (character => !character.IsActive);
         ActiveCharacters.RemoveAll (character => !character.IsActive);
     }
     }
 
 
-    public IEnumerator<string> GetEnumerator ()
-    {
-        return this;
-    }
-
-    IEnumerator IEnumerable.GetEnumerator ()
-    {
-        return GetEnumerator ();
-    }
-
-    public abstract string Next ();
 }
 }
 
 
 public abstract class BaseEffect<T> where T : EffectConfig, new()
 public abstract class BaseEffect<T> where T : EffectConfig, new()
@@ -49,14 +36,7 @@ public abstract class BaseEffect<T> where T : EffectConfig, new()
         TerminalConfig = new TerminalConfig ();
         TerminalConfig = new TerminalConfig ();
     }
     }
 
 
-    public abstract Type IteratorClass { get; }
-
-    public IEnumerator<string> GetEnumerator ()
-    {
-        var iterator = (BaseEffectIterator<T>)Activator.CreateInstance (IteratorClass, this);
-        return iterator;
-    }
-
+    /*
     public IDisposable TerminalOutput (string endSymbol = "\n")
     public IDisposable TerminalOutput (string endSymbol = "\n")
     {
     {
         var terminal = new Terminal (InputData, TerminalConfig);
         var terminal = new Terminal (InputData, TerminalConfig);
@@ -69,6 +49,6 @@ public abstract class BaseEffect<T> where T : EffectConfig, new()
         {
         {
             terminal.RestoreCursor (endSymbol);
             terminal.RestoreCursor (endSymbol);
         }
         }
-    }
+    }*/
 }
 }
 
 

+ 0 - 45
Terminal.Gui/TextEffects/EffectTemplate.cs

@@ -11,48 +11,3 @@ public class EffectConfig
     public float MovementSpeed { get; set; }
     public float MovementSpeed { get; set; }
     public EasingFunction Easing { get; set; }
     public EasingFunction Easing { get; set; }
 }
 }
-
-public class NamedEffectIterator : BaseEffectIterator<EffectConfig>
-{
-    public NamedEffectIterator (NamedEffect effect) : base (effect)
-    {
-        Build ();
-    }
-
-    private void Build ()
-    {
-        var finalGradient = new Gradient (Config.FinalGradientStops, Config.FinalGradientSteps);
-        foreach (var character in Terminal.GetCharacters ())
-        {
-            CharacterFinalColorMap [character] = finalGradient.GetColorAtFraction (
-                character.InputCoord.Row / (float)Terminal.Canvas.Top
-            );
-        }
-    }
-
-    public override string Next ()
-    {
-        if (PendingChars.Any () || ActiveCharacters.Any ())
-        {
-            Update ();
-            return Frame;
-        }
-        else
-        {
-            throw new InvalidOperationException ("No more elements in effect iterator.");
-        }
-    }
-
-    private List<EffectCharacter> PendingChars = new List<EffectCharacter> ();
-    private Dictionary<EffectCharacter, Color> CharacterFinalColorMap = new Dictionary<EffectCharacter, Color> ();
-}
-
-public class NamedEffect : BaseEffect<EffectConfig>
-{
-    public NamedEffect (string inputData) : base (inputData)
-    {
-    }
-
-    protected override Type ConfigCls => typeof (EffectConfig);
-    protected override Type IteratorCls => typeof (NamedEffectIterator);
-}

+ 39 - 52
Terminal.Gui/TextEffects/Graphics.cs

@@ -5,69 +5,64 @@ using System.Linq;
 
 
 public class Color
 public class Color
 {
 {
-    public string RgbColor { get; }
-    public int? XtermColor { get; }
+    public string RgbColor { get; private set; }
+    public int? XtermColor { get; private set; }
 
 
     public Color (string rgbColor)
     public Color (string rgbColor)
     {
     {
-        if (!IsValidHexColor (rgbColor))
+        if (!ColorUtils.IsValidHexColor (rgbColor))
             throw new ArgumentException ("Invalid RGB hex color format.");
             throw new ArgumentException ("Invalid RGB hex color format.");
 
 
-        RgbColor = rgbColor.StartsWith ("#") ? rgbColor.Substring (1) : rgbColor;
-        XtermColor = null; // Convert RGB to XTerm-256 here if necessary
+        RgbColor = rgbColor.StartsWith ("#") ? rgbColor.Substring (1).ToUpper () : rgbColor.ToUpper ();
+        XtermColor = ColorUtils.HexToXterm (RgbColor);  // Convert RGB to XTerm-256
     }
     }
 
 
     public Color (int xtermColor)
     public Color (int xtermColor)
     {
     {
+        if (!ColorUtils.IsValidXtermColor (xtermColor))
+            throw new ArgumentException ("Invalid XTerm-256 color code.");
+
         XtermColor = xtermColor;
         XtermColor = xtermColor;
-        RgbColor = ConvertXtermToHex (xtermColor); // Implement conversion logic
+        RgbColor = ColorUtils.XtermToHex (xtermColor); // Perform the actual conversion
     }
     }
+    public int R => Convert.ToInt32 (RgbColor.Substring (0, 2), 16);
+    public int G => Convert.ToInt32 (RgbColor.Substring (2, 2), 16);
+    public int B => Convert.ToInt32 (RgbColor.Substring (4, 2), 16);
 
 
-    private bool IsValidHexColor (string color)
+    public (int R, int G, int B) GetRgbInts ()
     {
     {
-        if (color.StartsWith ("#"))
-        {
-            color = color.Substring (1);
-        }
-        return color.Length == 6 && int.TryParse (color, System.Globalization.NumberStyles.HexNumber, null, out _);
+        return (
+            Convert.ToInt32 (RgbColor.Substring (0, 2), 16),
+            Convert.ToInt32 (RgbColor.Substring (2, 2), 16),
+            Convert.ToInt32 (RgbColor.Substring (4, 2), 16)
+        );
     }
     }
 
 
-    private string ConvertXtermToHex (int xtermColor)
-    {
-        // Dummy conversion for the sake of example
-        return "000000"; // Actual conversion logic needed
-    }
+    public override string ToString () => $"#{RgbColor}";
 
 
-    public (int, int, int) GetRgbInts ()
+    public static Color FromRgb (int r, int g, int b)
     {
     {
-        int r = Convert.ToInt32 (RgbColor.Substring (0, 2), 16);
-        int g = Convert.ToInt32 (RgbColor.Substring (2, 2), 16);
-        int b = Convert.ToInt32 (RgbColor.Substring (4, 2), 16);
-        return (r, g, b);
-    }
+        // Validate the RGB values to ensure they are within the 0-255 range
+        if (r < 0 || r > 255 || g < 0 || g > 255 || b < 0 || b > 255)
+            throw new ArgumentOutOfRangeException ("RGB values must be between 0 and 255.");
 
 
-    public override string ToString ()
-    {
-        return $"#{RgbColor.ToUpper ()}";
-    }
+        // Convert RGB values to a hexadecimal string
+        string rgbColor = $"#{r:X2}{g:X2}{b:X2}";
 
 
-    public override bool Equals (object obj)
-    {
-        return obj is Color other && RgbColor == other.RgbColor;
-    }
-
-    public override int GetHashCode ()
-    {
-        return HashCode.Combine (RgbColor);
+        // Create and return a new Color instance using the hexadecimal string
+        return new Color (rgbColor);
     }
     }
 }
 }
+
+
 public class Gradient
 public class Gradient
 {
 {
     public List<Color> Spectrum { get; private set; }
     public List<Color> Spectrum { get; private set; }
 
 
+    // 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.Count () < 2)
+        if (stops == null || !stops.Any () || stops.Count () < 2)
             throw new ArgumentException ("At least two color stops are required to create a gradient.");
             throw new ArgumentException ("At least two color stops are required to create a gradient.");
         if (steps == null || !steps.Any ())
         if (steps == null || !steps.Any ())
             throw new ArgumentException ("Steps are required to define the transitions between colors.");
             throw new ArgumentException ("Steps are required to define the transitions between colors.");
@@ -83,7 +78,8 @@ public class Gradient
 
 
         for (int i = 0; i < stops.Count - 1; i++)
         for (int i = 0; i < stops.Count - 1; i++)
         {
         {
-            gradient.AddRange (InterpolateColors (stops [i], stops [i + 1], i < steps.Count ? steps [i] : steps.Last ()));
+            int currentSteps = i < steps.Count ? steps [i] : steps.Last ();
+            gradient.AddRange (InterpolateColors (stops [i], stops [i + 1], currentSteps));
         }
         }
 
 
         return gradient;
         return gradient;
@@ -93,16 +89,16 @@ public class Gradient
     {
     {
         for (int step = 0; step <= steps; step++)
         for (int step = 0; step <= steps; step++)
         {
         {
-            int r = Interpolate (start.GetRgbInts ().Item1, end.GetRgbInts ().Item1, steps, step);
-            int g = Interpolate (start.GetRgbInts ().Item2, end.GetRgbInts ().Item2, steps, step);
-            int b = Interpolate (start.GetRgbInts ().Item3, end.GetRgbInts ().Item3, steps, step);
-            yield return new Color ($"#{r:X2}{g:X2}{b:X2}");
+            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);
+            yield return Color.FromRgb (r, g, b);
         }
         }
     }
     }
 
 
     private int Interpolate (int start, int end, int steps, int currentStep)
     private int Interpolate (int start, int end, int steps, int currentStep)
     {
     {
-        return start + (end - start) * currentStep / steps;
+        return start + (int)((end - start) * (double)currentStep / steps);
     }
     }
 
 
     public Color GetColorAtFraction (double fraction)
     public Color GetColorAtFraction (double fraction)
@@ -112,15 +108,6 @@ public class Gradient
         int index = (int)(fraction * (Spectrum.Count - 1));
         int index = (int)(fraction * (Spectrum.Count - 1));
         return Spectrum [index];
         return Spectrum [index];
     }
     }
-
-    public IEnumerable<Color> GetRange (int startIndex, int count)
-    {
-        return Spectrum.Skip (startIndex).Take (count);
-    }
-
-    public override string ToString ()
-    {
-        return $"Gradient with {Spectrum.Count} colors.";
-    }
 }
 }
 
 
+

+ 94 - 0
Terminal.Gui/TextEffects/HexTerm.cs

@@ -0,0 +1,94 @@
+using System.Text.RegularExpressions;
+
+namespace Terminal.Gui.TextEffects;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+
+public static class ColorUtils
+{
+    private static readonly Dictionary<int, string> xtermToHexMap = new Dictionary<int, string>
+    {
+        {0, "#000000"}, {1, "#800000"}, {2, "#008000"}, {3, "#808000"}, {4, "#000080"}, {5, "#800080"}, {6, "#008080"}, {7, "#c0c0c0"},
+        {8, "#808080"}, {9, "#ff0000"}, {10, "#00ff00"}, {11, "#ffff00"}, {12, "#0000ff"}, {13, "#ff00ff"}, {14, "#00ffff"}, {15, "#ffffff"},
+        {16, "#000000"}, {17, "#00005f"}, {18, "#000087"}, {19, "#0000af"}, {20, "#0000d7"}, {21, "#0000ff"}, {22, "#005f00"}, {23, "#005f5f"},
+        {24, "#005f87"}, {25, "#005faf"}, {26, "#005fd7"}, {27, "#005fff"}, {28, "#008700"}, {29, "#00875f"}, {30, "#008787"}, {31, "#0087af"},
+        {32, "#0087d7"}, {33, "#0087ff"}, {34, "#00af00"}, {35, "#00af5f"}, {36, "#00af87"}, {37, "#00afaf"}, {38, "#00afd7"}, {39, "#00afff"},
+        {40, "#00d700"}, {41, "#00d75f"}, {42, "#00d787"}, {43, "#00d7af"}, {44, "#00d7d7"}, {45, "#00d7ff"}, {46, "#00ff00"}, {47, "#00ff5f"},
+        {48, "#00ff87"}, {49, "#00ffaf"}, {50, "#00ffd7"}, {51, "#00ffff"}, {52, "#5f0000"}, {53, "#5f005f"}, {54, "#5f0087"}, {55, "#5f00af"},
+        {56, "#5f00d7"}, {57, "#5f00ff"}, {58, "#5f5f00"}, {59, "#5f5f5f"}, {60, "#5f5f87"}, {61, "#5f5faf"}, {62, "#5f5fd7"}, {63, "#5f5fff"},
+        {64, "#5f8700"}, {65, "#5f875f"}, {66, "#5f8787"}, {67, "#5f87af"}, {68, "#5f87d7"}, {69, "#5f87ff"}, {70, "#5faf00"}, {71, "#5faf5f"},
+        {72, "#5faf87"}, {73, "#5fafaf"}, {74, "#5fafd7"}, {75, "#5fafff"}, {76, "#5fd700"}, {77, "#5fd75f"}, {78, "#5fd787"}, {79, "#5fd7af"},
+        {80, "#5fd7d7"}, {81, "#5fd7ff"}, {82, "#5fff00"}, {83, "#5fff5f"}, {84, "#5fff87"}, {85, "#5fffaf"}, {86, "#5fffd7"}, {87, "#5fffff"},
+        {88, "#870000"}, {89, "#87005f"}, {90, "#870087"}, {91, "#8700af"}, {92, "#8700d7"}, {93, "#8700ff"}, {94, "#875f00"}, {95, "#875f5f"},
+        {96, "#875f87"}, {97, "#875faf"}, {98, "#875fd7"}, {99, "#875fff"}, {100, "#878700"}, {101, "#87875f"}, {102, "#878787"}, {103, "#8787af"},
+        {104, "#8787d7"}, {105, "#8787ff"}, {106, "#87af00"}, {107, "#87af5f"}, {108, "#87af87"}, {109, "#87afaf"}, {110, "#87afd7"}, {111, "#87afff"},
+        {112, "#87d700"}, {113, "#87d75f"}, {114, "#87d787"}, {115, "#87d7af"}, {116, "#87d7d7"}, {117, "#87d7ff"}, {118, "#87ff00"}, {119, "#87ff5f"},
+        {120, "#87ff87"}, {121, "#87ffaf"}, {122, "#87ffd7"}, {123, "#87ffff"}, {124, "#af0000"}, {125, "#af005f"}, {126, "#af0087"}, {127, "#af00af"},
+        {128, "#af00d7"}, {129, "#af00ff"}, {130, "#af5f00"}, {131, "#af5f5f"}, {132, "#af5f87"}, {133, "#af5faf"}, {134, "#af5fd7"}, {135, "#af5fff"},
+        {136, "#af8700"}, {137, "#af875f"}, {138, "#af8787"}, {139, "#af87af"}, {140, "#af87d7"}, {141, "#af87ff"}, {142, "#afaf00"}, {143, "#afaf5f"},
+        {144, "#afaf87"}, {145, "#afafaf"}, {146, "#afafd7"}, {147, "#afafff"}, {148, "#afd700"}, {149, "#afd75f"}, {150, "#afd787"}, {151, "#afd7af"},
+        {152, "#afd7d7"}, {153, "#afd7ff"}, {154, "#afff00"}, {155, "#afff5f"}, {156, "#afff87"}, {157, "#afffaf"}, {158, "#afffd7"}, {159, "#afffff"},
+        {160, "#d70000"}, {161, "#d7005f"}, {162, "#d70087"}, {163, "#d700af"}, {164, "#d700d7"}, {165, "#d700ff"}, {166, "#d75f00"}, {167, "#d75f5f"},
+        {168, "#d75f87"}, {169, "#d75faf"}, {170, "#d75fd7"}, {171, "#d75fff"}, {172, "#d78700"}, {173, "#d7875f"}, {174, "#d78787"}, {175, "#d787af"},
+        {176, "#d787d7"}, {177, "#d787ff"}, {178, "#d7af00"}, {179, "#d7af5f"}, {180, "#d7af87"}, {181, "#d7afaf"}, {182, "#d7afd7"}, {183, "#d7afff"},
+        {184, "#d7d700"}, {185, "#d7d75f"}, {186, "#d7d787"}, {187, "#d7d7af"}, {188, "#d7d7d7"}, {189, "#d7d7ff"}, {190, "#d7ff00"}, {191, "#d7ff5f"},
+        {192, "#d7ff87"}, {193, "#d7ffaf"}, {194, "#d7ffd7"}, {195, "#d7ffff"}, {196, "#ff0000"}, {197, "#ff005f"}, {198, "#ff0087"}, {199, "#ff00af"},
+        {200, "#ff00d7"}, {201, "#ff00ff"}, {202, "#ff5f00"}, {203, "#ff5f5f"}, {204, "#ff5f87"}, {205, "#ff5faf"}, {206, "#ff5fd7"}, {207, "#ff5fff"},
+        {208, "#ff8700"}, {209, "#ff875f"}, {210, "#ff8787"}, {211, "#ff87af"}, {212, "#ff87d7"}, {213, "#ff87ff"}, {214, "#ffaf00"}, {215, "#ffaf5f"},
+        {216, "#ffaf87"}, {217, "#ffafaf"}, {218, "#ffafd7"}, {219, "#ffafff"}, {220, "#ffd700"}, {221, "#ffd75f"}, {222, "#ffd787"}, {223, "#ffd7af"},
+        {224, "#ffd7d7"}, {225, "#ffd7ff"}, {226, "#ffff00"}, {227, "#ffff5f"}, {228, "#ffff87"}, {229, "#ffffaf"}, {230, "#ffffd7"}, {231, "#ffffff"},
+        {232, "#080808"}, {233, "#121212"}, {234, "#1c1c1c"}, {235, "#262626"}, {236, "#303030"}, {237, "#3a3a3a"}, {238, "#444444"}, {239, "#4e4e4e"},
+        {240, "#585858"}, {241, "#626262"}, {242, "#6c6c6c"}, {243, "#767676"}, {244, "#808080"}, {245, "#8a8a8a"}, {246, "#949494"}, {247, "#9e9e9e"},
+        {248, "#a8a8a8"}, {249, "#b2b2b2"}, {250, "#bcbcbc"}, {251, "#c6c6c6"}, {252, "#d0d0d0"}, {253, "#dadada"}, {254, "#e4e4e4"}, {255, "#eeeeee"}
+    };
+
+    private static readonly Dictionary<int, (int R, int G, int B)> xtermToRgbMap = xtermToHexMap.ToDictionary (
+        item => item.Key,
+        item => (
+            R: Convert.ToInt32 (item.Value.Substring (1, 2), 16),
+            G: Convert.ToInt32 (item.Value.Substring (3, 2), 16),
+            B: Convert.ToInt32 (item.Value.Substring (5, 2), 16)
+        ));
+    private static readonly Regex hexColorRegex = new Regex ("^#?[0-9A-Fa-f]{6}$");
+
+    public static bool IsValidHexColor (string hexColor)
+    {
+        return hexColorRegex.IsMatch (hexColor);
+    }
+
+    public static bool IsValidXtermColor (int xtermColor)
+    {
+        return xtermColor >= 0 && xtermColor <= 255;
+    }
+
+    public static string XtermToHex (int xtermColor)
+    {
+        if (xtermToHexMap.TryGetValue (xtermColor, out string hex))
+        {
+            return hex;
+        }
+        throw new ArgumentException ($"Invalid XTerm-256 color code: {xtermColor}");
+    }
+
+    public static int HexToXterm (string hexColor)
+    {
+        if (!IsValidHexColor (hexColor))
+            throw new ArgumentException ("Invalid RGB hex color format.");
+
+        hexColor = hexColor.StartsWith ("#") ? hexColor.Substring (1) : hexColor;
+        var rgb = (
+            R: Convert.ToInt32 (hexColor.Substring (0, 2), 16),
+            G: Convert.ToInt32 (hexColor.Substring (2, 2), 16),
+            B: Convert.ToInt32 (hexColor.Substring (4, 2), 16)
+        );
+
+        return xtermToRgbMap.Aggregate ((current, next) =>
+            ColorDifference (current.Value, rgb) < ColorDifference (next.Value, rgb) ? current : next).Key;
+    }
+
+    private static double ColorDifference ((int R, int G, int B) c1, (int R, int G, int B) c2)
+    {
+        return Math.Sqrt (Math.Pow (c1.R - c2.R, 2) + Math.Pow (c1.G - c2.G, 2) + Math.Pow (c1.B - c2.B, 2));
+    }
+}

+ 14 - 4
Terminal.Gui/TextEffects/Motion.cs

@@ -56,7 +56,6 @@ public class Segment
         return new Coord (column, row);
         return new Coord (column, row);
     }
     }
 }
 }
-
 public class Path
 public class Path
 {
 {
     public string PathId { get; private set; }
     public string PathId { get; private set; }
@@ -69,6 +68,7 @@ public class Path
     public int CurrentStep { get; set; }
     public int CurrentStep { get; set; }
     public double TotalDistance { get; set; }
     public double TotalDistance { get; set; }
     public double LastDistanceReached { get; set; }
     public double LastDistanceReached { get; set; }
+    public int MaxSteps => (int)Math.Ceiling (TotalDistance / Speed); // Calculates max steps based on total distance and speed
 
 
     public Path (string pathId, double speed, Func<double, double> easeFunction = null, int layer = 0, int holdTime = 0, bool loop = false)
     public Path (string pathId, double speed, Func<double, double> easeFunction = null, int layer = 0, int holdTime = 0, bool loop = false)
     {
     {
@@ -100,9 +100,9 @@ public class Path
 
 
     public Coord Step ()
     public Coord Step ()
     {
     {
-        if (EaseFunction != null && CurrentStep <= TotalDistance)
+        if (CurrentStep <= MaxSteps)
         {
         {
-            double progress = EaseFunction ((double)CurrentStep / TotalDistance);
+            double progress = EaseFunction?.Invoke ((double)CurrentStep / TotalDistance) ?? (double)CurrentStep / TotalDistance;
             double distanceTravelled = TotalDistance * progress;
             double distanceTravelled = TotalDistance * progress;
             LastDistanceReached = distanceTravelled;
             LastDistanceReached = distanceTravelled;
 
 
@@ -174,9 +174,19 @@ public class Motion
         ActivePath.CurrentStep = 0;  // Reset the path's progress
         ActivePath.CurrentStep = 0;  // Reset the path's progress
     }
     }
 
 
+    /// <summary>
+    /// Set the active path to None if the active path is the given path.    
+    /// </summary>
+    public void DeactivatePath (Path p)
+    {
+        if (p == ActivePath)
+        {
+            ActivePath = null;
+        }
+    }
     public void DeactivatePath ()
     public void DeactivatePath ()
     {
     {
-        ActivePath = null;
+       ActivePath = null;        
     }
     }
 
 
     public void Move ()
     public void Move ()

+ 11 - 11
UnitTests/TextEffects/AnimationTests.cs

@@ -1,10 +1,7 @@
-using System;
-using System.Collections.Generic;
-using System.Security.Cryptography;
-using Terminal.Gui.TextEffects;
-using Xunit;
+using Terminal.Gui.TextEffects;
 
 
 namespace Terminal.Gui.TextEffectsTests;
 namespace Terminal.Gui.TextEffectsTests;
+using Color = Terminal.Gui.TextEffects.Color;
 
 
 public class AnimationTests
 public class AnimationTests
 {
 {
@@ -100,7 +97,8 @@ public class AnimationTests
     public void TestSceneApplyGradientToSymbolsEqualColorsAndSymbols ()
     public void TestSceneApplyGradientToSymbolsEqualColorsAndSymbols ()
     {
     {
         var scene = new Scene (sceneId: "test_scene");
         var scene = new Scene (sceneId: "test_scene");
-        var gradient = new Gradient (new Color ("000000"), new Color ("ffffff"), steps: 2);
+        var gradient = new Gradient (new [] { new Color ("000000"), new Color ("ffffff") }, 
+            steps: new [] { 2 });
         var symbols = new List<string> { "a", "b", "c" };
         var symbols = new List<string> { "a", "b", "c" };
         scene.ApplyGradientToSymbols (gradient, symbols, duration: 1);
         scene.ApplyGradientToSymbols (gradient, symbols, duration: 1);
         Assert.Equal (3, scene.Frames.Count);
         Assert.Equal (3, scene.Frames.Count);
@@ -115,7 +113,9 @@ public class AnimationTests
     public void TestSceneApplyGradientToSymbolsUnequalColorsAndSymbols ()
     public void TestSceneApplyGradientToSymbolsUnequalColorsAndSymbols ()
     {
     {
         var scene = new Scene (sceneId: "test_scene");
         var scene = new Scene (sceneId: "test_scene");
-        var gradient = new Gradient (new Color ("000000"), new Color ("ffffff"), steps: 4);
+        var gradient = new Gradient (
+            new [] { new Color ("000000"), new Color ("ffffff") },
+            steps: new [] { 4 });
         var symbols = new List<string> { "q", "z" };
         var symbols = new List<string> { "q", "z" };
         scene.ApplyGradientToSymbols (gradient, symbols, duration: 1);
         scene.ApplyGradientToSymbols (gradient, symbols, duration: 1);
         Assert.Equal (5, scene.Frames.Count);
         Assert.Equal (5, scene.Frames.Count);
@@ -142,7 +142,7 @@ public class AnimationTests
     public void TestAnimationNewScene ()
     public void TestAnimationNewScene ()
     {
     {
         var animation = character.Animation;
         var animation = character.Animation;
-        var scene = animation.NewScene ("test_scene", isLooping: true);
+        var scene = animation.NewScene (id:"test_scene", isLooping: true);
         Assert.IsType<Scene> (scene);
         Assert.IsType<Scene> (scene);
         Assert.Equal ("test_scene", scene.SceneId);
         Assert.Equal ("test_scene", scene.SceneId);
         Assert.True (scene.IsLooping);
         Assert.True (scene.IsLooping);
@@ -163,7 +163,7 @@ public class AnimationTests
     public void TestAnimationQueryScene ()
     public void TestAnimationQueryScene ()
     {
     {
         var animation = character.Animation;
         var animation = character.Animation;
-        var scene = animation.NewScene ("test_scene", isLooping: true);
+        var scene = animation.NewScene (id:"test_scene", isLooping: true);
         Assert.Equal (scene, animation.QueryScene ("test_scene"));
         Assert.Equal (scene, animation.QueryScene ("test_scene"));
     }
     }
 
 
@@ -171,7 +171,7 @@ public class AnimationTests
     public void TestAnimationLoopingActiveSceneIsComplete ()
     public void TestAnimationLoopingActiveSceneIsComplete ()
     {
     {
         var animation = character.Animation;
         var animation = character.Animation;
-        var scene = animation.NewScene ("test_scene", isLooping: true);
+        var scene = animation.NewScene (id: "test_scene", isLooping: true);
         scene.AddFrame (symbol: "a", duration: 2);
         scene.AddFrame (symbol: "a", duration: 2);
         animation.ActivateScene (scene);
         animation.ActivateScene (scene);
         Assert.True (animation.ActiveSceneIsComplete ());
         Assert.True (animation.ActiveSceneIsComplete ());
@@ -181,7 +181,7 @@ public class AnimationTests
     public void TestAnimationNonLoopingActiveSceneIsComplete ()
     public void TestAnimationNonLoopingActiveSceneIsComplete ()
     {
     {
         var animation = character.Animation;
         var animation = character.Animation;
-        var scene = animation.NewScene ("test_scene");
+        var scene = animation.NewScene (id: "test_scene");
         scene.AddFrame (symbol: "a", duration: 1);
         scene.AddFrame (symbol: "a", duration: 1);
         animation.ActivateScene (scene);
         animation.ActivateScene (scene);
         Assert.False (animation.ActiveSceneIsComplete ());
         Assert.False (animation.ActiveSceneIsComplete ());