diff --git a/Terminal.Gui/TextEffects/Animation.cs b/Terminal.Gui/TextEffects/Animation.cs
new file mode 100644
index 0000000000..591d282c3d
--- /dev/null
+++ b/Terminal.Gui/TextEffects/Animation.cs
@@ -0,0 +1,399 @@
+using System;
+using System.Collections.Generic;
+using static System.Runtime.InteropServices.JavaScript.JSType;
+using Terminal.Gui;
+
+public enum SyncMetric
+{
+ Distance,
+ Step
+}
+
+public class CharacterVisual
+{
+ public string Symbol { get; set; }
+ public bool Bold { get; set; }
+ public bool Dim { get; set; }
+ public bool Italic { get; set; }
+ public bool Underline { get; set; }
+ public bool Blink { get; set; }
+ public bool Reverse { get; set; }
+ public bool Hidden { get; set; }
+ public bool Strike { get; set; }
+ public Color Color { get; set; }
+ public string FormattedSymbol { get; private set; }
+ private string _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)
+ {
+ Symbol = symbol;
+ Bold = bold;
+ Dim = dim;
+ Italic = italic;
+ Underline = underline;
+ Blink = blink;
+ Reverse = reverse;
+ Hidden = hidden;
+ Strike = strike;
+ Color = color;
+ _colorCode = colorCode;
+ FormattedSymbol = FormatSymbol ();
+ }
+
+ private string FormatSymbol ()
+ {
+ string formattingString = "";
+ if (Bold) formattingString += Ansitools.ApplyBold ();
+ if (Italic) formattingString += Ansitools.ApplyItalic ();
+ if (Underline) formattingString += Ansitools.ApplyUnderline ();
+ if (Blink) formattingString += Ansitools.ApplyBlink ();
+ if (Reverse) formattingString += Ansitools.ApplyReverse ();
+ if (Hidden) formattingString += Ansitools.ApplyHidden ();
+ if (Strike) formattingString += Ansitools.ApplyStrikethrough ();
+ if (_colorCode != null) formattingString += Colorterm.Fg (_colorCode);
+
+ return $"{formattingString}{Symbol}{(formattingString != "" ? Ansitools.ResetAll () : "")}";
+ }
+
+ public void DisableModes ()
+ {
+ Bold = false;
+ Dim = false;
+ Italic = false;
+ Underline = false;
+ Blink = false;
+ Reverse = false;
+ Hidden = false;
+ Strike = false;
+ }
+}
+
+public class Frame
+{
+ public CharacterVisual CharacterVisual { get; }
+ public int Duration { get; }
+ public int TicksElapsed { get; set; }
+
+ public Frame (CharacterVisual characterVisual, int duration)
+ {
+ CharacterVisual = characterVisual;
+ Duration = duration;
+ TicksElapsed = 0;
+ }
+
+ public void IncrementTicks ()
+ {
+ TicksElapsed++;
+ }
+}
+
+public class Scene
+{
+ public string SceneId { get; }
+ public bool IsLooping { get; }
+ public SyncMetric? Sync { get; }
+ public EasingFunction Ease { get; }
+ public bool NoColor { get; set; }
+ public bool UseXtermColors { get; set; }
+ public List Frames { get; } = new List ();
+ public List PlayedFrames { get; } = new List ();
+ public Dictionary FrameIndexMap { get; } = new Dictionary ();
+ public int EasingTotalSteps { get; private set; }
+ public int EasingCurrentStep { get; private set; }
+ public static Dictionary XtermColorMap { get; } = new Dictionary ();
+
+ public Scene (string sceneId, bool isLooping = false, SyncMetric? sync = null, EasingFunction ease = null, bool noColor = false, bool useXtermColors = false)
+ {
+ SceneId = sceneId;
+ IsLooping = isLooping;
+ Sync = sync;
+ Ease = ease;
+ NoColor = noColor;
+ UseXtermColors = useXtermColors;
+ }
+
+ public void AddFrame (string symbol, int duration, Color color = null, bool bold = false, bool dim = false, bool italic = false, bool underline = false, bool blink = false, bool reverse = false, bool hidden = false, bool strike = false)
+ {
+ string charVisColor = null;
+ if (color != null)
+ {
+ if (NoColor)
+ {
+ charVisColor = null;
+ }
+ else if (UseXtermColors)
+ {
+ if (color.XtermColor != null)
+ {
+ charVisColor = color.XtermColor;
+ }
+ else if (XtermColorMap.ContainsKey (color.RgbColor))
+ {
+ // Build error says Error CS0029 Cannot implicitly convert type 'int' to 'string' Terminal.Gui (net8.0) D:\Repos\TerminalGuiDesigner\gui.cs\Terminal.Gui\TextEffects\Animation.cs 120 Active
+ charVisColor = XtermColorMap [color.RgbColor].ToString ();
+ }
+ else
+ {
+ var xtermColor = Hexterm.HexToXterm (color.RgbColor);
+ XtermColorMap [color.RgbColor] = int.Parse (xtermColor);
+ charVisColor = xtermColor;
+ }
+ }
+ else
+ {
+ charVisColor = color.RgbColor;
+ }
+ }
+
+ if (duration < 1)
+ {
+ throw new ArgumentException ("duration must be greater than 0");
+ }
+
+ var charVis = new CharacterVisual (symbol, bold, dim, italic, underline, blink, reverse, hidden, strike, color, charVisColor);
+ var frame = new Frame (charVis, duration);
+ Frames.Add (frame);
+ for (int i = 0; i < frame.Duration; i++)
+ {
+ FrameIndexMap [EasingTotalSteps] = frame;
+ EasingTotalSteps++;
+ }
+ }
+
+ public CharacterVisual Activate ()
+ {
+ if (Frames.Count > 0)
+ {
+ return Frames [0].CharacterVisual;
+ }
+ else
+ {
+ throw new InvalidOperationException ("Scene has no frames.");
+ }
+ }
+
+ public CharacterVisual GetNextVisual ()
+ {
+ var currentFrame = Frames [0];
+ var nextVisual = currentFrame.CharacterVisual;
+ currentFrame.IncrementTicks ();
+ if (currentFrame.TicksElapsed == currentFrame.Duration)
+ {
+ currentFrame.TicksElapsed = 0;
+ PlayedFrames.Add (Frames [0]);
+ Frames.RemoveAt (0);
+ if (IsLooping && Frames.Count == 0)
+ {
+ Frames.AddRange (PlayedFrames);
+ PlayedFrames.Clear ();
+ }
+ }
+ return nextVisual;
+ }
+
+ public void ApplyGradientToSymbols (Gradient gradient, IList symbols, int duration)
+ {
+ int lastIndex = 0;
+ for (int symbolIndex = 0; symbolIndex < symbols.Count; symbolIndex++)
+ {
+ var symbol = symbols [symbolIndex];
+ double symbolProgress = (symbolIndex + 1) / (double)symbols.Count;
+ int gradientIndex = (int)(symbolProgress * gradient.Spectrum.Count);
+ foreach (var color in gradient.Spectrum.GetRange (lastIndex, Math.Max (gradientIndex - lastIndex, 1)))
+ {
+ AddFrame (symbol, duration, color);
+ }
+ lastIndex = gradientIndex;
+ }
+ }
+
+ public void ResetScene ()
+ {
+ foreach (var sequence in Frames)
+ {
+ sequence.TicksElapsed = 0;
+ PlayedFrames.Add (sequence);
+ }
+ Frames.Clear ();
+ Frames.AddRange (PlayedFrames);
+ PlayedFrames.Clear ();
+ }
+
+ public override bool Equals (object obj)
+ {
+ if (obj is Scene other)
+ {
+ return SceneId == other.SceneId;
+ }
+ return false;
+ }
+
+ public override int GetHashCode ()
+ {
+ return SceneId.GetHashCode ();
+ }
+}
+
+public class Animation
+{
+ public Dictionary Scenes { get; } = new Dictionary ();
+ public EffectCharacter Character { get; }
+ public Scene ActiveScene { get; private set; }
+ public bool UseXtermColors { get; set; } = false;
+ public bool NoColor { get; set; } = false;
+ public Dictionary XtermColorMap { get; } = new Dictionary ();
+ public int ActiveSceneCurrentStep { get; private set; } = 0;
+ public CharacterVisual CurrentCharacterVisual { get; private set; }
+
+ public Animation (EffectCharacter character)
+ {
+ Character = character;
+ CurrentCharacterVisual = new CharacterVisual (character.InputSymbol);
+ }
+
+ public Scene NewScene (bool isLooping = false, SyncMetric? sync = null, EasingFunction ease = null, string id = "")
+ {
+ if (string.IsNullOrEmpty (id))
+ {
+ bool foundUnique = false;
+ int currentId = Scenes.Count;
+ while (!foundUnique)
+ {
+ id = $"{Scenes.Count}";
+ if (!Scenes.ContainsKey (id))
+ {
+ foundUnique = true;
+ }
+ else
+ {
+ currentId++;
+ }
+ }
+ }
+
+ var newScene = new Scene (id, isLooping, sync, ease);
+ Scenes [id] = newScene;
+ newScene.NoColor = NoColor;
+ newScene.UseXtermColors = UseXtermColors;
+ return newScene;
+ }
+
+ public Scene QueryScene (string sceneId)
+ {
+ if (!Scenes.TryGetValue (sceneId, out var scene))
+ {
+ throw new ArgumentException ($"Scene {sceneId} does not exist.");
+ }
+ return scene;
+ }
+
+ public bool ActiveSceneIsComplete ()
+ {
+ if (ActiveScene == null)
+ {
+ return true;
+ }
+ return ActiveScene.Frames.Count == 0 && !ActiveScene.IsLooping;
+ }
+
+ public bool SceneExists (string sceneId)
+ {
+ return Scenes.ContainsKey (sceneId);
+ }
+
+ public void ActivateScene (string sceneId)
+ {
+ ActiveScene = QueryScene (sceneId);
+ CurrentCharacterVisual = ActiveScene.Activate ();
+ ActiveSceneCurrentStep = 0;
+ }
+
+ public void IncrementScene ()
+ {
+ if (ActiveScene == null || ActiveSceneIsComplete ())
+ {
+ return;
+ }
+ CurrentCharacterVisual = ActiveScene.GetNextVisual ();
+ }
+}
+
+public class EffectCharacter
+{
+ public string InputSymbol { get; }
+ public CharacterVisual CharacterVisual { get; set; }
+ public Animation Animation { get; set; }
+
+ public EffectCharacter (string inputSymbol)
+ {
+ InputSymbol = inputSymbol;
+ CharacterVisual = new CharacterVisual (inputSymbol);
+ Animation = new Animation (this);
+ }
+
+ public void Animate (string sceneId)
+ {
+ Animation.ActivateScene (sceneId);
+ Animation.IncrementScene ();
+ CharacterVisual = Animation.CurrentCharacterVisual;
+ }
+
+ public void ResetEffects ()
+ {
+ CharacterVisual.DisableModes ();
+ }
+}
+
+public class Color
+{
+ public string RgbColor { get; }
+ public string XtermColor { get; }
+
+ public Color (string rgbColor, string xtermColor)
+ {
+ RgbColor = rgbColor;
+ XtermColor = xtermColor;
+ }
+}
+
+public class Gradient
+{
+ public List Spectrum { get; }
+
+ public Gradient (List spectrum)
+ {
+ Spectrum = spectrum;
+ }
+}
+
+public class EasingFunction
+{
+ // Easing functions implementation
+}
+
+// Dummy classes for Ansitools, Colorterm, and Hexterm as placeholders
+public static class Ansitools
+{
+ public static string ApplyBold () => "\x1b[1m";
+ public static string ApplyItalic () => "\x1b[3m";
+ public static string ApplyUnderline () => "\x1b[4m";
+ public static string ApplyBlink () => "\x1b[5m";
+ public static string ApplyReverse () => "\x1b[7m";
+ public static string ApplyHidden () => "\x1b[8m";
+ public static string ApplyStrikethrough () => "\x1b[9m";
+ public static string ResetAll () => "\x1b[0m";
+}
+
+public static class Colorterm
+{
+ public static string Fg (string colorCode) => $"\x1b[38;5;{colorCode}m";
+}
+
+public static class Hexterm
+{
+ public static string HexToXterm (string hex)
+ {
+ // Convert hex color to xterm color code (0-255)
+ return "15"; // Example output
+ }
+}