Browse Source

More random code ported

tznind 1 year ago
parent
commit
7d62ad272a

+ 32 - 93
Terminal.Gui/TextEffects/Animation.cs

@@ -109,6 +109,8 @@ public class Scene
         Ease = ease;
         NoColor = noColor;
         UseXtermColors = useXtermColors;
+        EasingTotalSteps = 0;
+        EasingCurrentStep = 0;
     }
 
     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)
@@ -120,23 +122,13 @@ public class Scene
             {
                 charVisColor = null;
             }
-            else if (UseXtermColors)
+            else if (UseXtermColors && color.XtermColor.HasValue)
             {
-                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;
-                }
+                charVisColor = color.XtermColor.Value.ToString ();
+            }
+            else if (color.RgbColor != null && XtermColorMap.ContainsKey (color.RgbColor))
+            {
+                charVisColor = XtermColorMap [color.RgbColor].ToString ();
             }
             else
             {
@@ -145,12 +137,10 @@ public class Scene
         }
 
         if (duration < 1)
-        {
-            throw new ArgumentException ("duration must be greater than 0");
-        }
+            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);
+        var characterVisual = new CharacterVisual (symbol, bold, dim, italic, underline, blink, reverse, hidden, strike, color, charVisColor);
+        var frame = new Frame (characterVisual, duration);
         Frames.Add (frame);
         for (int i = 0; i < frame.Duration; i++)
         {
@@ -161,33 +151,32 @@ public class Scene
 
     public CharacterVisual Activate ()
     {
-        if (Frames.Count > 0)
-        {
-            return Frames [0].CharacterVisual;
-        }
-        else
-        {
+        if (Frames.Count == 0)
             throw new InvalidOperationException ("Scene has no frames.");
-        }
+        EasingCurrentStep = 0;
+        return Frames [0].CharacterVisual;
     }
 
     public CharacterVisual GetNextVisual ()
     {
-        var currentFrame = Frames [0];
-        var nextVisual = currentFrame.CharacterVisual;
-        currentFrame.IncrementTicks ();
-        if (currentFrame.TicksElapsed == currentFrame.Duration)
+        if (Frames.Count == 0)
+            return null;
+
+        var frame = Frames [0];
+        if (++EasingCurrentStep >= frame.Duration)
         {
-            currentFrame.TicksElapsed = 0;
-            PlayedFrames.Add (Frames [0]);
+            EasingCurrentStep = 0;
+            PlayedFrames.Add (frame);
             Frames.RemoveAt (0);
             if (IsLooping && Frames.Count == 0)
             {
                 Frames.AddRange (PlayedFrames);
                 PlayedFrames.Clear ();
             }
+            if (Frames.Count > 0)
+                return Frames [0].CharacterVisual;
         }
-        return nextVisual;
+        return frame.CharacterVisual;
     }
 
     public void ApplyGradientToSymbols (Gradient gradient, IList<string> symbols, int duration)
@@ -208,11 +197,7 @@ public class Scene
 
     public void ResetScene ()
     {
-        foreach (var sequence in Frames)
-        {
-            sequence.TicksElapsed = 0;
-            PlayedFrames.Add (sequence);
-        }
+        EasingCurrentStep = 0;
         Frames.Clear ();
         Frames.AddRange (PlayedFrames);
         PlayedFrames.Clear ();
@@ -220,11 +205,7 @@ public class Scene
 
     public override bool Equals (object obj)
     {
-        if (obj is Scene other)
-        {
-            return SceneId == other.SceneId;
-        }
-        return false;
+        return obj is Scene other && SceneId == other.SceneId;
     }
 
     public override int GetHashCode ()
@@ -306,14 +287,14 @@ public class Animation
             }
             else if (UseXtermColors)
             {
-                charVisColor = color.XtermColor;
+                charVisColor = color.XtermColor.ToString();
             }
             else
             {
                 charVisColor = color.RgbColor;
             }
         }
-        CurrentCharacterVisual = new CharacterVisual (symbol, color: color, _colorCode: charVisColor);
+        CurrentCharacterVisual = new CharacterVisual (symbol, color: color, colorCode: charVisColor);
     }
 
     public static Color RandomColor ()
@@ -489,55 +470,13 @@ public class Animation
     }
 }
 
-public class EffectCharacter
+// Dummy Enum for Event handling
+public enum Event
 {
-    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;
-    }
+    SceneComplete,
+    SceneActivated
 }
 
-public class Gradient
-{
-    public List<Color> Spectrum { get; }
-
-    public Gradient (List<Color> spectrum)
-    {
-        Spectrum = spectrum;
-    }
-}
-
-
 // Dummy classes for Ansitools, Colorterm, and Hexterm as placeholders
 public static class Ansitools
 {

+ 107 - 0
Terminal.Gui/TextEffects/BaseCharacter.cs

@@ -0,0 +1,107 @@
+namespace Terminal.Gui.TextEffects;
+
+public class EffectCharacter
+{
+    public int CharacterId { get; }
+    public string InputSymbol { get; }
+    public Coord InputCoord { get; }
+    public bool IsVisible { get; set; }
+    public Animation Animation { get; }
+    public Motion Motion { get; }
+    public EventHandler EventHandler { get; }
+    public int Layer { get; set; }
+    public bool IsFillCharacter { get; set; }
+
+    public EffectCharacter (int characterId, string symbol, int inputColumn, int inputRow)
+    {
+        CharacterId = characterId;
+        InputSymbol = symbol;
+        InputCoord = new Coord (inputColumn, inputRow);
+        IsVisible = false;
+        Animation = new Animation (this);
+        Motion = new Motion (this);
+        EventHandler = new EventHandler (this);
+        Layer = 0;
+        IsFillCharacter = false;
+    }
+
+    public bool IsActive => !Animation.ActiveSceneIsComplete() || !Motion.MovementIsComplete ();
+
+    public void Tick ()
+    {
+        Motion.Move ();
+        Animation.StepAnimation ();
+    }
+}
+
+public class EventHandler
+{
+    public EffectCharacter Character { get; }
+    public Dictionary<(Event, object), List<(Action, object)>> RegisteredEvents { get; }
+
+    public EventHandler (EffectCharacter character)
+    {
+        Character = character;
+        RegisteredEvents = new Dictionary<(Event, object), List<(Action, object)>> ();
+    }
+
+    public void RegisterEvent (Event @event, object caller, Action action, object target)
+    {
+        var key = (@event, caller);
+        if (!RegisteredEvents.ContainsKey (key))
+            RegisteredEvents [key] = new List<(Action, object)> ();
+
+        RegisteredEvents [key].Add ((action, target));
+    }
+
+    public void HandleEvent (Event @event, object caller)
+    {
+        var key = (@event, caller);
+        if (!RegisteredEvents.ContainsKey (key))
+            return;
+
+        foreach (var (action, target) in RegisteredEvents [key])
+        {
+            switch (action)
+            {
+                case Action.ActivatePath:
+                    Character.Motion.ActivatePath (target as Path);
+                    break;
+                case Action.DeactivatePath:
+                    Character.Motion.DeactivatePath (target as Path);
+                    break;
+                case Action.SetLayer:
+                    Character.Layer = (int)target;
+                    break;
+                case Action.SetCoordinate:
+                    Character.Motion.CurrentCoord = (Coord)target;
+                    break;
+                case Action.Callback:
+                    (target as Action)?.Invoke ();
+                    break;
+                default:
+                    throw new ArgumentOutOfRangeException (nameof (action), "Unhandled action.");
+            }
+        }
+    }
+
+    public enum Event
+    {
+        SegmentEntered,
+        SegmentExited,
+        PathActivated,
+        PathComplete,
+        PathHolding,
+        SceneActivated,
+        SceneComplete
+    }
+
+    public enum Action
+    {
+        ActivatePath,
+        DeactivatePath,
+        SetLayer,
+        SetCoordinate,
+        Callback
+    }
+}

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

@@ -0,0 +1,74 @@
+namespace Terminal.Gui.TextEffects;
+
+public abstract class BaseEffectIterator<T> : IEnumerable<string> where T : EffectConfig
+{
+    protected T Config { get; set; }
+    protected Terminal Terminal { get; set; }
+    protected List<EffectCharacter> ActiveCharacters { get; set; } = new List<EffectCharacter> ();
+
+    public BaseEffectIterator (BaseEffect<T> effect)
+    {
+        Config = effect.EffectConfig;
+        Terminal = new Terminal (effect.InputData, effect.TerminalConfig);
+    }
+
+    public string Frame => Terminal.GetFormattedOutputString ();
+
+    public void Update ()
+    {
+        foreach (var character in ActiveCharacters)
+        {
+            character.Tick ();
+        }
+        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 string InputData { get; set; }
+    public T EffectConfig { get; set; }
+    public TerminalConfig TerminalConfig { get; set; }
+
+    protected BaseEffect (string inputData)
+    {
+        InputData = inputData;
+        EffectConfig = new T ();
+        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")
+    {
+        var terminal = new Terminal (InputData, TerminalConfig);
+        terminal.PrepCanvas ();
+        try
+        {
+            return terminal;
+        }
+        finally
+        {
+            terminal.RestoreCursor (endSymbol);
+        }
+    }
+}
+

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

@@ -0,0 +1,58 @@
+namespace Terminal.Gui.TextEffects;
+
+public class EffectConfig
+{
+    public Color ColorSingle { get; set; }
+    public List<Color> ColorList { get; set; }
+    public Color FinalColor { get; set; }
+    public List<Color> FinalGradientStops { get; set; }
+    public List<int> FinalGradientSteps { get; set; }
+    public int FinalGradientFrames { get; set; }
+    public float MovementSpeed { 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);
+}

+ 137 - 0
Terminal.Gui/TextEffects/Geometry.cs

@@ -0,0 +1,137 @@
+namespace Terminal.Gui.TextEffects;
+
+
+public static class GeometryUtils
+{
+
+    public static List<Coord> FindCoordsOnCircle (Coord origin, int radius, int coordsLimit = 0, bool unique = true)
+    {
+        var points = new List<Coord> ();
+        var seenPoints = new HashSet<Coord> ();
+        if (coordsLimit == 0)
+            coordsLimit = (int)Math.Ceiling (2 * Math.PI * radius);
+        double angleStep = 2 * Math.PI / coordsLimit;
+
+        for (int i = 0; i < coordsLimit; i++)
+        {
+            double angle = i * angleStep;
+            int x = (int)(origin.Column + radius * Math.Cos (angle));
+            int y = (int)(origin.Row + radius * Math.Sin (angle));
+            var coord = new Coord (x, y);
+
+            if (unique && !seenPoints.Contains (coord))
+            {
+                points.Add (coord);
+                seenPoints.Add (coord);
+            }
+            else if (!unique)
+            {
+                points.Add (coord);
+            }
+        }
+
+        return points;
+    }
+
+    public static List<Coord> FindCoordsInCircle (Coord center, int diameter)
+    {
+        var coordsInEllipse = new List<Coord> ();
+        int radius = diameter / 2;
+        for (int x = center.Column - radius; x <= center.Column + radius; x++)
+        {
+            for (int y = center.Row - radius; y <= center.Row + radius; y++)
+            {
+                if (Math.Pow (x - center.Column, 2) + Math.Pow (y - center.Row, 2) <= Math.Pow (radius, 2))
+                    coordsInEllipse.Add (new Coord (x, y));
+            }
+        }
+        return coordsInEllipse;
+    }
+
+    public static List<Coord> FindCoordsInRect (Coord origin, int distance)
+    {
+        var coords = new List<Coord> ();
+        for (int column = origin.Column - distance; column <= origin.Column + distance; column++)
+        {
+            for (int row = origin.Row - distance; row <= origin.Row + distance; row++)
+            {
+                coords.Add (new Coord (column, row));
+            }
+        }
+        return coords;
+    }
+
+    public static Coord FindCoordAtDistance (Coord origin, Coord target, double distance)
+    {
+        double totalDistance = FindLengthOfLine (origin, target) + distance;
+        double t = distance / totalDistance;
+        int nextColumn = (int)((1 - t) * origin.Column + t * target.Column);
+        int nextRow = (int)((1 - t) * origin.Row + t * target.Row);
+        return new Coord (nextColumn, nextRow);
+    }
+
+    public static Coord FindCoordOnBezierCurve (Coord start, List<Coord> controlPoints, Coord end, double t)
+    {
+        // Implementing De Casteljau's algorithm for Bezier curve
+        if (controlPoints.Count == 1) // Quadratic
+        {
+            double x = Math.Pow (1 - t, 2) * start.Column +
+                       2 * (1 - t) * t * controlPoints [0].Column +
+                       Math.Pow (t, 2) * end.Column;
+            double y = Math.Pow (1 - t, 2) * start.Row +
+                       2 * (1 - t) * t * controlPoints [0].Row +
+                       Math.Pow (t, 2) * end.Row;
+            return new Coord ((int)x, (int)y);
+        }
+        else if (controlPoints.Count == 2) // Cubic
+        {
+            double x = Math.Pow (1 - t, 3) * start.Column +
+                       3 * Math.Pow (1 - t, 2) * t * controlPoints [0].Column +
+                       3 * (1 - t) * Math.Pow (t, 2) * controlPoints [1].Column +
+                       Math.Pow (t, 3) * end.Column;
+            double y = Math.Pow (1 - t, 3) * start.Row +
+                       3 * Math.Pow (1 - t, 2) * t * controlPoints [0].Row +
+                       3 * (1 - t) * Math.Pow (t, 2) * controlPoints [1].Row +
+                       Math.Pow (t, 3) * end.Row;
+            return new Coord ((int)x, (int)y);
+        }
+        throw new ArgumentException ("Invalid number of control points for bezier curve");
+    }
+
+    public static Coord FindCoordOnLine (Coord start, Coord end, double t)
+    {
+        int x = (int)((1 - t) * start.Column + t * end.Column);
+        int y = (int)((1 - t) * start.Row + t * end.Row);
+        return new Coord (x, y);
+    }
+
+    public static double FindLengthOfBezierCurve (Coord start, List<Coord> controlPoints, Coord end)
+    {
+        double length = 0.0;
+        Coord prevCoord = start;
+        for (int i = 1; i <= 10; i++)
+        {
+            double t = i / 10.0;
+            Coord coord = FindCoordOnBezierCurve (start, controlPoints, end, t);
+            length += FindLengthOfLine (prevCoord, coord);
+            prevCoord = coord;
+        }
+        return length;
+    }
+
+    public static double FindLengthOfLine (Coord coord1, Coord coord2)
+    {
+        return Math.Sqrt (Math.Pow (coord2.Column - coord1.Column, 2) +
+                         Math.Pow (coord2.Row - coord1.Row, 2));
+    }
+
+    public static double FindNormalizedDistanceFromCenter (int maxRow, int maxColumn, Coord otherCoord)
+    {
+        double center_x = maxColumn / 2.0;
+        double center_y = maxRow / 2.0;
+        double maxDistance = Math.Sqrt (Math.Pow (maxColumn, 2) + Math.Pow (maxRow, 2));
+        double distance = Math.Sqrt (Math.Pow (otherCoord.Column - center_x, 2) +
+                                    Math.Pow (otherCoord.Row - center_y, 2));
+        return distance / (maxDistance / 2);
+    }
+}

+ 126 - 0
Terminal.Gui/TextEffects/Graphics.cs

@@ -0,0 +1,126 @@
+namespace Terminal.Gui.TextEffects;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+
+public class Color
+{
+    public string RgbColor { get; }
+    public int? XtermColor { get; }
+
+    public Color (string rgbColor)
+    {
+        if (!IsValidHexColor (rgbColor))
+            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
+    }
+
+    public Color (int xtermColor)
+    {
+        XtermColor = xtermColor;
+        RgbColor = ConvertXtermToHex (xtermColor); // Implement conversion logic
+    }
+
+    private bool IsValidHexColor (string color)
+    {
+        if (color.StartsWith ("#"))
+        {
+            color = color.Substring (1);
+        }
+        return color.Length == 6 && int.TryParse (color, System.Globalization.NumberStyles.HexNumber, null, out _);
+    }
+
+    private string ConvertXtermToHex (int xtermColor)
+    {
+        // Dummy conversion for the sake of example
+        return "000000"; // Actual conversion logic needed
+    }
+
+    public (int, int, int) GetRgbInts ()
+    {
+        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);
+    }
+
+    public override string ToString ()
+    {
+        return $"#{RgbColor.ToUpper ()}";
+    }
+
+    public override bool Equals (object obj)
+    {
+        return obj is Color other && RgbColor == other.RgbColor;
+    }
+
+    public override int GetHashCode ()
+    {
+        return HashCode.Combine (RgbColor);
+    }
+}
+public class Gradient
+{
+    public List<Color> Spectrum { get; private set; }
+
+    public Gradient (IEnumerable<Color> stops, IEnumerable<int> steps, bool loop = false)
+    {
+        if (stops == null || 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.");
+
+        Spectrum = GenerateGradient (stops.ToList (), steps.ToList (), loop);
+    }
+
+    private List<Color> GenerateGradient (List<Color> stops, List<int> steps, bool loop)
+    {
+        List<Color> gradient = new List<Color> ();
+        if (loop)
+            stops.Add (stops [0]); // Loop the gradient back to the first color.
+
+        for (int i = 0; i < stops.Count - 1; i++)
+        {
+            gradient.AddRange (InterpolateColors (stops [i], stops [i + 1], i < steps.Count ? steps [i] : steps.Last ()));
+        }
+
+        return gradient;
+    }
+
+    private IEnumerable<Color> InterpolateColors (Color start, Color end, int steps)
+    {
+        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}");
+        }
+    }
+
+    private int Interpolate (int start, int end, int steps, int currentStep)
+    {
+        return start + (end - start) * currentStep / 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];
+    }
+
+    public IEnumerable<Color> GetRange (int startIndex, int count)
+    {
+        return Spectrum.Skip (startIndex).Take (count);
+    }
+
+    public override string ToString ()
+    {
+        return $"Gradient with {Spectrum.Count} colors.";
+    }
+}
+

+ 216 - 0
Terminal.Gui/TextEffects/Motion.cs

@@ -0,0 +1,216 @@
+namespace Terminal.Gui.TextEffects;
+
+public class Coord
+{
+    public int Column { get; set; }
+    public int Row { get; set; }
+
+    public Coord (int column, int row)
+    {
+        Column = column;
+        Row = row;
+    }
+
+    public override string ToString () => $"({Column}, {Row})";
+}
+
+public class Waypoint
+{
+    public string WaypointId { get; set; }
+    public Coord Coord { get; set; }
+    public List<Coord> BezierControl { get; set; }
+
+    public Waypoint (string waypointId, Coord coord, List<Coord> bezierControl = null)
+    {
+        WaypointId = waypointId;
+        Coord = coord;
+        BezierControl = bezierControl ?? new List<Coord> ();
+    }
+}
+
+public class Segment
+{
+    public Waypoint Start { get; private set; }
+    public Waypoint End { get; private set; }
+    public double Distance { get; private set; }
+    public bool EnterEventTriggered { get; set; }
+    public bool ExitEventTriggered { get; set; }
+
+    public Segment (Waypoint start, Waypoint end)
+    {
+        Start = start;
+        End = end;
+        Distance = CalculateDistance (start, end);
+    }
+
+    private double CalculateDistance (Waypoint start, Waypoint end)
+    {
+        // Add bezier control point distance calculation if needed
+        return Math.Sqrt (Math.Pow (end.Coord.Column - start.Coord.Column, 2) + Math.Pow (end.Coord.Row - start.Coord.Row, 2));
+    }
+
+    public Coord GetCoordOnSegment (double distanceFactor)
+    {
+        int column = (int)(Start.Coord.Column + (End.Coord.Column - Start.Coord.Column) * distanceFactor);
+        int row = (int)(Start.Coord.Row + (End.Coord.Row - Start.Coord.Row) * distanceFactor);
+        return new Coord (column, row);
+    }
+}
+
+public class Path
+{
+    public string PathId { get; private set; }
+    public double Speed { get; set; }
+    public Func<double, double> EaseFunction { get; set; }
+    public int Layer { get; set; }
+    public int HoldTime { get; set; }
+    public bool Loop { get; set; }
+    public List<Segment> Segments { get; private set; } = new List<Segment> ();
+    public int CurrentStep { get; set; }
+    public double TotalDistance { get; set; }
+    public double LastDistanceReached { get; set; }
+
+    public Path (string pathId, double speed, Func<double, double> easeFunction = null, int layer = 0, int holdTime = 0, bool loop = false)
+    {
+        PathId = pathId;
+        Speed = speed;
+        EaseFunction = easeFunction;
+        Layer = layer;
+        HoldTime = holdTime;
+        Loop = loop;
+    }
+
+    public void AddWaypoint (Waypoint waypoint)
+    {
+        if (Segments.Count > 0)
+        {
+            var lastSegment = Segments.Last ();
+            var newSegment = new Segment (lastSegment.End, waypoint);
+            Segments.Add (newSegment);
+            TotalDistance += newSegment.Distance;
+        }
+        else
+        {
+            var originWaypoint = new Waypoint ("origin", new Coord (0, 0));  // Assuming the path starts at origin
+            var initialSegment = new Segment (originWaypoint, waypoint);
+            Segments.Add (initialSegment);
+            TotalDistance = initialSegment.Distance;
+        }
+    }
+
+    public Coord Step ()
+    {
+        if (EaseFunction != null && CurrentStep <= TotalDistance)
+        {
+            double progress = EaseFunction ((double)CurrentStep / TotalDistance);
+            double distanceTravelled = TotalDistance * progress;
+            LastDistanceReached = distanceTravelled;
+
+            foreach (var segment in Segments)
+            {
+                if (distanceTravelled <= segment.Distance)
+                {
+                    double segmentProgress = distanceTravelled / segment.Distance;
+                    return segment.GetCoordOnSegment (segmentProgress);
+                }
+
+                distanceTravelled -= segment.Distance;
+            }
+        }
+
+        return Segments.Last ().End.Coord; // Return the end of the last segment if out of bounds
+    }
+}
+
+public class Motion
+{
+    public Dictionary<string, Path> Paths { get; private set; } = new Dictionary<string, Path> ();
+    public Path ActivePath { get; private set; }
+    public Coord CurrentCoord { get; set; }
+    public Coord PreviousCoord { get; set; }
+    public EffectCharacter Character { get; private set; }  // Assuming EffectCharacter is similar to base_character.EffectCharacter
+
+    public Motion (EffectCharacter character)
+    {
+        Character = character;
+        CurrentCoord = new Coord (character.InputCoord.Column, character.InputCoord.Row);  // Assuming similar properties
+        PreviousCoord = new Coord (-1, -1);
+    }
+
+    public void SetCoordinate (Coord coord)
+    {
+        CurrentCoord = coord;
+    }
+
+    public Path CreatePath (string pathId, double speed, Func<double, double> easeFunction = null, int layer = 0, int holdTime = 0, bool loop = false)
+    {
+        if (Paths.ContainsKey (pathId))
+            throw new ArgumentException ($"A path with ID {pathId} already exists.");
+
+        var path = new Path (pathId, speed, easeFunction, layer, holdTime, loop);
+        Paths [pathId] = path;
+        return path;
+    }
+
+    public Path QueryPath (string pathId)
+    {
+        if (!Paths.TryGetValue (pathId, out var path))
+            throw new KeyNotFoundException ($"No path found with ID {pathId}.");
+
+        return path;
+    }
+
+    public bool MovementIsComplete ()
+    {
+        return ActivePath == null || ActivePath.CurrentStep >= ActivePath.TotalDistance;
+    }
+
+    public void ActivatePath (Path path)
+    {
+        if (path == null)
+            throw new ArgumentNullException (nameof (path), "Path cannot be null when activating.");
+
+        ActivePath = path;
+        ActivePath.CurrentStep = 0;  // Reset the path's progress
+    }
+
+    public void DeactivatePath ()
+    {
+        ActivePath = null;
+    }
+
+    public void Move ()
+    {
+        if (ActivePath != null)
+        {
+            PreviousCoord = CurrentCoord;
+            CurrentCoord = ActivePath.Step ();
+            ActivePath.CurrentStep++;
+
+            if (ActivePath.CurrentStep >= ActivePath.TotalDistance)
+            {
+                if (ActivePath.Loop)
+                    ActivePath.CurrentStep = 0;  // Reset the path for looping
+                else
+                    DeactivatePath ();  // Deactivate the path if it is not set to loop
+            }
+        }
+    }
+
+    public void ChainPaths (IEnumerable<Path> paths, bool loop = false)
+    {
+        var pathList = paths.ToList ();
+        for (int i = 0; i < pathList.Count; i++)
+        {
+            var currentPath = pathList [i];
+            var nextPath = i + 1 < pathList.Count ? pathList [i + 1] : pathList.FirstOrDefault ();
+
+            // Here we could define an event system to trigger path activation when another completes
+            // For example, you could listen for a "path complete" event and then activate the next path
+            if (loop && nextPath != null)
+            {
+                // Implementation depends on your event system
+            }
+        }
+    }
+}

+ 106 - 0
Terminal.Gui/TextEffects/Terminal.cs

@@ -0,0 +1,106 @@
+namespace Terminal.Gui.TextEffects;
+public class TerminalConfig
+{
+    public int TabWidth { get; set; } = 4;
+    public bool XtermColors { get; set; } = false;
+    public bool NoColor { get; set; } = false;
+    public bool WrapText { get; set; } = false;
+    public float FrameRate { get; set; } = 100.0f;
+    public int CanvasWidth { get; set; } = -1;
+    public int CanvasHeight { get; set; } = -1;
+    public string AnchorCanvas { get; set; } = "sw";
+    public string AnchorText { get; set; } = "sw";
+    public bool IgnoreTerminalDimensions { get; set; } = false;
+}
+
+public class Canvas
+{
+    public int Top { get; private set; }
+    public int Right { get; private set; }
+    public int Bottom { get; private set; } = 1;
+    public int Left { get; private set; } = 1;
+
+    public int CenterRow => (Top + Bottom) / 2;
+    public int CenterColumn => (Right + Left) / 2;
+    public Coord Center => new Coord (CenterColumn, CenterRow);
+    public int Width => Right - Left + 1;
+    public int Height => Top - Bottom + 1;
+
+    public Canvas (int top, int right)
+    {
+        Top = top;
+        Right = right;
+    }
+
+    public bool IsCoordInCanvas (Coord coord)
+    {
+        return coord.Column >= Left && coord.Column <= Right &&
+               coord.Row >= Bottom && coord.Row <= Top;
+    }
+
+    public Coord GetRandomCoord (bool outsideScope = false)
+    {
+        var random = new Random ();
+        if (outsideScope)
+        {
+            switch (random.Next (4))
+            {
+                case 0: return new Coord (random.Next (Left, Right + 1), Top + 1);
+                case 1: return new Coord (random.Next (Left, Right + 1), Bottom - 1);
+                case 2: return new Coord (Left - 1, random.Next (Bottom, Top + 1));
+                case 3: return new Coord (Right + 1, random.Next (Bottom, Top + 1));
+            }
+        }
+        return new Coord (
+            random.Next (Left, Right + 1),
+            random.Next (Bottom, Top + 1));
+    }
+}
+
+public class Terminal
+{
+    public TerminalConfig Config { get; }
+    public Canvas Canvas { get; private set; }
+    private Dictionary<Coord, EffectCharacter> CharacterByInputCoord = new Dictionary<Coord, EffectCharacter> ();
+
+    public Terminal (string input, TerminalConfig config = null)
+    {
+        Config = config ?? new TerminalConfig ();
+        var dimensions = GetTerminalDimensions ();
+        Canvas = new Canvas (dimensions.height, dimensions.width);
+        ProcessInput (input);
+    }
+
+    private void ProcessInput (string input)
+    {
+        // Handling input processing logic similar to Python's version
+    }
+
+    public string GetPipedInput ()
+    {
+        // C# way to get piped input or indicate there's none
+        return Console.IsInputRedirected ? Console.In.ReadToEnd () : string.Empty;
+    }
+
+    public void Print (string output, bool enforceFrameRate = true)
+    {
+        if (enforceFrameRate)
+            EnforceFrameRate ();
+
+        // Move cursor to top and clear the current console line
+        Console.SetCursorPosition (0, 0);
+        Console.Write (output);
+        Console.ResetColor ();
+    }
+
+    private void EnforceFrameRate ()
+    {
+        // Limit the printing speed based on the Config.FrameRate
+    }
+
+    private (int width, int height) GetTerminalDimensions ()
+    {
+        // Return terminal dimensions or defaults if not determinable
+        return (Console.WindowWidth, Console.WindowHeight);
+    }
+}