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 + } +}