From f3120226c430271f067c2a55910f615f2a6bb389 Mon Sep 17 00:00:00 2001 From: tznind Date: Fri, 5 Jul 2024 19:26:29 +0100 Subject: [PATCH 01/30] Start pulling Animation a bit at a time through chat gpt from https://github.com/ChrisBuilds/terminaltexteffects --- Terminal.Gui/TextEffects/Animation.cs | 566 ++++++++++++++++++++++++ Terminal.Gui/TextEffects/Easing.cs | 303 +++++++++++++ UnitTests/TextEffects/AnimationTests.cs | 191 ++++++++ 3 files changed, 1060 insertions(+) create mode 100644 Terminal.Gui/TextEffects/Animation.cs create mode 100644 Terminal.Gui/TextEffects/Easing.cs create mode 100644 UnitTests/TextEffects/AnimationTests.cs diff --git a/Terminal.Gui/TextEffects/Animation.cs b/Terminal.Gui/TextEffects/Animation.cs new file mode 100644 index 0000000000..c7e17fff07 --- /dev/null +++ b/Terminal.Gui/TextEffects/Animation.cs @@ -0,0 +1,566 @@ +using static Unix.Terminal.Curses; + +namespace Terminal.Gui.TextEffects; + +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 void SetAppearance (string symbol, Color? color = null) + { + string charVisColor = null; + if (color != null) + { + if (NoColor) + { + charVisColor = null; + } + else if (UseXtermColors) + { + charVisColor = color.XtermColor; + } + else + { + charVisColor = color.RgbColor; + } + } + CurrentCharacterVisual = new CharacterVisual (symbol, color: color, _colorCode: charVisColor); + } + + public static Color RandomColor () + { + var random = new Random (); + var colorHex = random.Next (0, 0xFFFFFF).ToString ("X6"); + return new Color (colorHex); + } + + public static Color AdjustColorBrightness (Color color, float brightness) + { + float HueToRgb (float p, float q, float t) + { + if (t < 0) t += 1; + if (t > 1) t -= 1; + if (t < 1 / 6f) return p + (q - p) * 6 * t; + if (t < 1 / 2f) return q; + if (t < 2 / 3f) return p + (q - p) * (2 / 3f - t) * 6; + return p; + } + + float r = int.Parse (color.RgbColor.Substring (0, 2), System.Globalization.NumberStyles.HexNumber) / 255f; + float g = int.Parse (color.RgbColor.Substring (2, 2), System.Globalization.NumberStyles.HexNumber) / 255f; + float b = int.Parse (color.RgbColor.Substring (4, 2), System.Globalization.NumberStyles.HexNumber) / 255f; + + float max = Math.Max (r, Math.Max (g, b)); + float min = Math.Min (r, Math.Min (g, b)); + float h, s, l = (max + min) / 2f; + + if (max == min) + { + h = s = 0; // achromatic + } + else + { + float d = max - min; + s = l > 0.5f ? d / (2f - max - min) : d / (max + min); + if (max == r) + { + h = (g - b) / d + (g < b ? 6 : 0); + } + else if (max == g) + { + h = (b - r) / d + 2; + } + else + { + h = (r - g) / d + 4; + } + h /= 6; + } + + l = Math.Max (Math.Min (l * brightness, 1), 0); + + if (s == 0) + { + r = g = b = l; // achromatic + } + else + { + float q = l < 0.5f ? l * (1 + s) : l + s - l * s; + float p = 2 * l - q; + r = HueToRgb (p, q, h + 1 / 3f); + g = HueToRgb (p, q, h); + b = HueToRgb (p, q, h - 1 / 3f); + } + + var adjustedColor = $"{(int)(r * 255):X2}{(int)(g * 255):X2}{(int)(b * 255):X2}"; + return new Color (adjustedColor); + } + + private float EaseAnimation (EasingFunction easingFunc) + { + if (ActiveScene == null) + { + return 0; + } + float elapsedStepRatio = ActiveScene.EasingCurrentStep / (float)ActiveScene.EasingTotalSteps; + return easingFunc (elapsedStepRatio); + } + + public void StepAnimation () + { + if (ActiveScene != null && ActiveScene.Frames.Count > 0) + { + if (ActiveScene.Sync != null) + { + if (Character.Motion.ActivePath != null) + { + int sequenceIndex = 0; + if (ActiveScene.Sync == SyncMetric.Step) + { + sequenceIndex = (int)Math.Round ((ActiveScene.Frames.Count - 1) * + (Math.Max (Character.Motion.ActivePath.CurrentStep, 1) / + (float)Math.Max (Character.Motion.ActivePath.MaxSteps, 1))); + } + else if (ActiveScene.Sync == SyncMetric.Distance) + { + sequenceIndex = (int)Math.Round ((ActiveScene.Frames.Count - 1) * + (Math.Max (Math.Max (Character.Motion.ActivePath.TotalDistance, 1) - + Math.Max (Character.Motion.ActivePath.TotalDistance - + Character.Motion.ActivePath.LastDistanceReached, 1), 1) / + (float)Math.Max (Character.Motion.ActivePath.TotalDistance, 1))); + } + try + { + CurrentCharacterVisual = ActiveScene.Frames [sequenceIndex].CharacterVisual; + } + catch (IndexOutOfRangeException) + { + CurrentCharacterVisual = ActiveScene.Frames [^1].CharacterVisual; + } + } + else + { + CurrentCharacterVisual = ActiveScene.Frames [^1].CharacterVisual; + ActiveScene.PlayedFrames.AddRange (ActiveScene.Frames); + ActiveScene.Frames.Clear (); + } + } + else if (ActiveScene.Ease != null) + { + float easingFactor = EaseAnimation (ActiveScene.Ease); + int frameIndex = (int)Math.Round (easingFactor * Math.Max (ActiveScene.EasingTotalSteps - 1, 0)); + frameIndex = Math.Max (Math.Min (frameIndex, ActiveScene.EasingTotalSteps - 1), 0); + Frame frame = ActiveScene.FrameIndexMap [frameIndex]; + CurrentCharacterVisual = frame.CharacterVisual; + ActiveScene.EasingCurrentStep++; + if (ActiveScene.EasingCurrentStep == ActiveScene.EasingTotalSteps) + { + if (ActiveScene.IsLooping) + { + ActiveScene.EasingCurrentStep = 0; + } + else + { + ActiveScene.PlayedFrames.AddRange (ActiveScene.Frames); + ActiveScene.Frames.Clear (); + } + } + } + else + { + CurrentCharacterVisual = ActiveScene.GetNextVisual (); + } + if (ActiveSceneIsComplete ()) + { + var completedScene = ActiveScene; + if (!ActiveScene.IsLooping) + { + ActiveScene.ResetScene (); + ActiveScene = null; + } + Character.EventHandler.HandleEvent (Event.SceneComplete, completedScene); + } + } + } + + public void ActivateScene (Scene scene) + { + ActiveScene = scene; + ActiveSceneCurrentStep = 0; + CurrentCharacterVisual = ActiveScene.Activate (); + Character.EventHandler.HandleEvent (Event.SceneActivated, scene); + } + + public void DeactivateScene (Scene scene) + { + if (ActiveScene == scene) + { + ActiveScene = null; + } + } +} + +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; + } +} + + +// 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 + } +} diff --git a/Terminal.Gui/TextEffects/Easing.cs b/Terminal.Gui/TextEffects/Easing.cs new file mode 100644 index 0000000000..3b63ca032c --- /dev/null +++ b/Terminal.Gui/TextEffects/Easing.cs @@ -0,0 +1,303 @@ +namespace Terminal.Gui.TextEffects; +using System; + +public delegate float EasingFunction (float progressRatio); + +public static class Easing +{ + public static float Linear (float progressRatio) + { + return progressRatio; + } + + public static float InSine (float progressRatio) + { + return 1 - (float)Math.Cos ((progressRatio * Math.PI) / 2); + } + + public static float OutSine (float progressRatio) + { + return (float)Math.Sin ((progressRatio * Math.PI) / 2); + } + + public static float InOutSine (float progressRatio) + { + return -(float)(Math.Cos (Math.PI * progressRatio) - 1) / 2; + } + + public static float InQuad (float progressRatio) + { + return progressRatio * progressRatio; + } + + public static float OutQuad (float progressRatio) + { + return 1 - (1 - progressRatio) * (1 - progressRatio); + } + + public static float InOutQuad (float progressRatio) + { + if (progressRatio < 0.5) + { + return 2 * progressRatio * progressRatio; + } + else + { + return 1 - (float)Math.Pow (-2 * progressRatio + 2, 2) / 2; + } + } + + public static float InCubic (float progressRatio) + { + return progressRatio * progressRatio * progressRatio; + } + + public static float OutCubic (float progressRatio) + { + return 1 - (float)Math.Pow (1 - progressRatio, 3); + } + + public static float InOutCubic (float progressRatio) + { + if (progressRatio < 0.5) + { + return 4 * progressRatio * progressRatio * progressRatio; + } + else + { + return 1 - (float)Math.Pow (-2 * progressRatio + 2, 3) / 2; + } + } + + public static float InQuart (float progressRatio) + { + return progressRatio * progressRatio * progressRatio * progressRatio; + } + + public static float OutQuart (float progressRatio) + { + return 1 - (float)Math.Pow (1 - progressRatio, 4); + } + + public static float InOutQuart (float progressRatio) + { + if (progressRatio < 0.5) + { + return 8 * progressRatio * progressRatio * progressRatio * progressRatio; + } + else + { + return 1 - (float)Math.Pow (-2 * progressRatio + 2, 4) / 2; + } + } + + public static float InQuint (float progressRatio) + { + return progressRatio * progressRatio * progressRatio * progressRatio * progressRatio; + } + + public static float OutQuint (float progressRatio) + { + return 1 - (float)Math.Pow (1 - progressRatio, 5); + } + + public static float InOutQuint (float progressRatio) + { + if (progressRatio < 0.5) + { + return 16 * progressRatio * progressRatio * progressRatio * progressRatio * progressRatio; + } + else + { + return 1 - (float)Math.Pow (-2 * progressRatio + 2, 5) / 2; + } + } + + public static float InExpo (float progressRatio) + { + if (progressRatio == 0) + { + return 0; + } + else + { + return (float)Math.Pow (2, 10 * progressRatio - 10); + } + } + + public static float OutExpo (float progressRatio) + { + if (progressRatio == 1) + { + return 1; + } + else + { + return 1 - (float)Math.Pow (2, -10 * progressRatio); + } + } + + public static float InOutExpo (float progressRatio) + { + if (progressRatio == 0) + { + return 0; + } + else if (progressRatio == 1) + { + return 1; + } + else if (progressRatio < 0.5) + { + return (float)Math.Pow (2, 20 * progressRatio - 10) / 2; + } + else + { + return (2 - (float)Math.Pow (2, -20 * progressRatio + 10)) / 2; + } + } + + public static float InCirc (float progressRatio) + { + return 1 - (float)Math.Sqrt (1 - progressRatio * progressRatio); + } + + public static float OutCirc (float progressRatio) + { + return (float)Math.Sqrt (1 - (progressRatio - 1) * (progressRatio - 1)); + } + + public static float InOutCirc (float progressRatio) + { + if (progressRatio < 0.5) + { + return (1 - (float)Math.Sqrt (1 - (2 * progressRatio) * (2 * progressRatio))) / 2; + } + else + { + return ((float)Math.Sqrt (1 - (-2 * progressRatio + 2) * (-2 * progressRatio + 2)) + 1) / 2; + } + } + + public static float InBack (float progressRatio) + { + const float c1 = 1.70158f; + const float c3 = c1 + 1; + return c3 * progressRatio * progressRatio * progressRatio - c1 * progressRatio * progressRatio; + } + + public static float OutBack (float progressRatio) + { + const float c1 = 1.70158f; + const float c3 = c1 + 1; + return 1 + c3 * (progressRatio - 1) * (progressRatio - 1) * (progressRatio - 1) + c1 * (progressRatio - 1) * (progressRatio - 1); + } + + public static float InOutBack (float progressRatio) + { + const float c1 = 1.70158f; + const float c2 = c1 * 1.525f; + if (progressRatio < 0.5) + { + return ((2 * progressRatio) * (2 * progressRatio) * ((c2 + 1) * 2 * progressRatio - c2)) / 2; + } + else + { + return ((2 * progressRatio - 2) * (2 * progressRatio - 2) * ((c2 + 1) * (progressRatio * 2 - 2) + c2) + 2) / 2; + } + } + + public static float InElastic (float progressRatio) + { + const float c4 = (2 * (float)Math.PI) / 3; + if (progressRatio == 0) + { + return 0; + } + else if (progressRatio == 1) + { + return 1; + } + else + { + return -(float)Math.Pow (2, 10 * progressRatio - 10) * (float)Math.Sin ((progressRatio * 10 - 10.75) * c4); + } + } + + public static float OutElastic (float progressRatio) + { + const float c4 = (2 * (float)Math.PI) / 3; + if (progressRatio == 0) + { + return 0; + } + else if (progressRatio == 1) + { + return 1; + } + else + { + return (float)Math.Pow (2, -10 * progressRatio) * (float)Math.Sin ((progressRatio * 10 - 0.75) * c4) + 1; + } + } + + public static float InOutElastic (float progressRatio) + { + const float c5 = (2 * (float)Math.PI) / 4.5f; + if (progressRatio == 0) + { + return 0; + } + else if (progressRatio == 1) + { + return 1; + } + else if (progressRatio < 0.5) + { + return -(float)Math.Pow (2, 20 * progressRatio - 10) * (float)Math.Sin ((20 * progressRatio - 11.125) * c5) / 2; + } + else + { + return ((float)Math.Pow (2, -20 * progressRatio + 10) * (float)Math.Sin ((20 * progressRatio - 11.125) * c5)) / 2 + 1; + } + } + + public static float InBounce (float progressRatio) + { + return 1 - OutBounce (1 - progressRatio); + } + + public static float OutBounce (float progressRatio) + { + const float n1 = 7.5625f; + const float d1 = 2.75f; + if (progressRatio < 1 / d1) + { + return n1 * progressRatio * progressRatio; + } + else if (progressRatio < 2 / d1) + { + return n1 * (progressRatio - 1.5f / d1) * (progressRatio - 1.5f / d1) + 0.75f; + } + else if (progressRatio < 2.5 / d1) + { + return n1 * (progressRatio - 2.25f / d1) * (progressRatio - 2.25f / d1) + 0.9375f; + } + else + { + return n1 * (progressRatio - 2.625f / d1) * (progressRatio - 2.625f / d1) + 0.984375f; + } + } + + public static float InOutBounce (float progressRatio) + { + if (progressRatio < 0.5) + { + return (1 - OutBounce (1 - 2 * progressRatio)) / 2; + } + else + { + return (1 + OutBounce (2 * progressRatio - 1)) / 2; + } + } +} diff --git a/UnitTests/TextEffects/AnimationTests.cs b/UnitTests/TextEffects/AnimationTests.cs new file mode 100644 index 0000000000..0a00b2e7c1 --- /dev/null +++ b/UnitTests/TextEffects/AnimationTests.cs @@ -0,0 +1,191 @@ +using System; +using System.Collections.Generic; +using System.Security.Cryptography; +using Terminal.Gui.TextEffects; +using Xunit; + +namespace Terminal.Gui.TextEffectsTests; + +public class AnimationTests +{ + private EffectCharacter character; + + public AnimationTests () + { + character = new EffectCharacter (0, "a", 0, 0); + } + + [Fact] + public void TestCharacterVisualInit () + { + var visual = new CharacterVisual ( + symbol: "a", + bold: true, + dim: false, + italic: true, + underline: false, + blink: true, + reverse: false, + hidden: true, + strike: false, + color: new Color ("ffffff"), + colorCode: "ffffff" + ); + Assert.Equal ("\x1b[1m\x1b[3m\x1b[5m\x1b[8m\x1b[38;2;255;255;255ma\x1b[0m", visual.FormattedSymbol); + Assert.True (visual.Bold); + Assert.False (visual.Dim); + Assert.True (visual.Italic); + Assert.False (visual.Underline); + Assert.True (visual.Blink); + Assert.False (visual.Reverse); + Assert.True (visual.Hidden); + Assert.False (visual.Strike); + Assert.Equal (new Color ("ffffff"), visual.Color); + Assert.Equal ("ffffff", visual.ColorCode); + } + + [Fact] + public void TestFrameInit () + { + var visual = new CharacterVisual ( + symbol: "a", + bold: true, + dim: false, + italic: true, + underline: false, + blink: true, + reverse: false, + hidden: true, + strike: false, + color: new Color ("ffffff") + ); + var frame = new Frame (characterVisual: visual, duration: 5); + Assert.Equal (visual, frame.CharacterVisual); + Assert.Equal (5, frame.Duration); + Assert.Equal (0, frame.TicksElapsed); + } + + [Fact] + public void TestSceneInit () + { + var scene = new Scene (sceneId: "test_scene", isLooping: true, sync: SyncMetric.Step, ease: Easing.InSine); + Assert.Equal ("test_scene", scene.SceneId); + Assert.True (scene.IsLooping); + Assert.Equal (SyncMetric.Step, scene.Sync); + Assert.Equal (Easing.InSine, scene.Ease); + } + + [Fact] + public void TestSceneAddFrame () + { + var scene = new Scene (sceneId: "test_scene"); + scene.AddFrame (symbol: "a", duration: 5, color: new Color ("ffffff"), bold: true, italic: true, blink: true, hidden: true); + Assert.Single (scene.Frames); + var frame = scene.Frames [0]; + Assert.Equal ("\x1b[1m\x1b[3m\x1b[5m\x1b[8m\x1b[38;2;255;255;255ma\x1b[0m", frame.CharacterVisual.FormattedSymbol); + Assert.Equal (5, frame.Duration); + Assert.Equal (new Color ("ffffff"), frame.CharacterVisual.Color); + Assert.True (frame.CharacterVisual.Bold); + } + + [Fact] + public void TestSceneAddFrameInvalidDuration () + { + var scene = new Scene (sceneId: "test_scene"); + var exception = Assert.Throws (() => scene.AddFrame (symbol: "a", duration: 0, color: new Color ("ffffff"))); + Assert.Equal ("duration must be greater than 0", exception.Message); + } + + [Fact] + public void TestSceneApplyGradientToSymbolsEqualColorsAndSymbols () + { + var scene = new Scene (sceneId: "test_scene"); + var gradient = new Gradient (new Color ("000000"), new Color ("ffffff"), steps: 2); + var symbols = new List { "a", "b", "c" }; + scene.ApplyGradientToSymbols (gradient, symbols, duration: 1); + Assert.Equal (3, scene.Frames.Count); + for (int i = 0; i < scene.Frames.Count; i++) + { + Assert.Equal (1, scene.Frames [i].Duration); + Assert.Equal (gradient.Spectrum [i].RgbColor, scene.Frames [i].CharacterVisual.ColorCode); + } + } + + [Fact] + public void TestSceneApplyGradientToSymbolsUnequalColorsAndSymbols () + { + var scene = new Scene (sceneId: "test_scene"); + var gradient = new Gradient (new Color ("000000"), new Color ("ffffff"), steps: 4); + var symbols = new List { "q", "z" }; + scene.ApplyGradientToSymbols (gradient, symbols, duration: 1); + Assert.Equal (5, scene.Frames.Count); + Assert.Equal (gradient.Spectrum [0].RgbColor, scene.Frames [0].CharacterVisual.ColorCode); + Assert.Contains ("q", scene.Frames [0].CharacterVisual.Symbol); + Assert.Equal (gradient.Spectrum [^1].RgbColor, scene.Frames [^1].CharacterVisual.ColorCode); + Assert.Contains ("z", scene.Frames [^1].CharacterVisual.Symbol); + } + + [Fact] + public void TestAnimationInit () + { + var animation = character.Animation; + Assert.Equal (character, animation.Character); + Assert.Empty (animation.Scenes); + Assert.Null (animation.ActiveScene); + Assert.False (animation.UseXtermColors); + Assert.False (animation.NoColor); + Assert.Empty (animation.XtermColorMap); + Assert.Equal (0, animation.ActiveSceneCurrentStep); + } + + [Fact] + public void TestAnimationNewScene () + { + var animation = character.Animation; + var scene = animation.NewScene ("test_scene", isLooping: true); + Assert.IsType (scene); + Assert.Equal ("test_scene", scene.SceneId); + Assert.True (scene.IsLooping); + Assert.True (animation.Scenes.ContainsKey ("test_scene")); + } + + [Fact] + public void TestAnimationNewSceneWithoutId () + { + var animation = character.Animation; + var scene = animation.NewScene (); + Assert.IsType (scene); + Assert.Equal ("0", scene.SceneId); + Assert.True (animation.Scenes.ContainsKey ("0")); + } + + [Fact] + public void TestAnimationQueryScene () + { + var animation = character.Animation; + var scene = animation.NewScene ("test_scene", isLooping: true); + Assert.Equal (scene, animation.QueryScene ("test_scene")); + } + + [Fact] + public void TestAnimationLoopingActiveSceneIsComplete () + { + var animation = character.Animation; + var scene = animation.NewScene ("test_scene", isLooping: true); + scene.AddFrame (symbol: "a", duration: 2); + animation.ActivateScene (scene); + Assert.True (animation.ActiveSceneIsComplete ()); + } + + [Fact] + public void TestAnimationNonLoopingActiveSceneIsComplete () + { + var animation = character.Animation; + var scene = animation.NewScene ("test_scene"); + scene.AddFrame (symbol: "a", duration: 1); + animation.ActivateScene (scene); + Assert.False (animation.ActiveSceneIsComplete ()); + animation.StepAnimation (); + Assert.True (animation.ActiveSceneIsComplete ()); + } +} \ No newline at end of file From 7d62ad272a2dc3f1c0cb0f14ad1c0ce34775bb55 Mon Sep 17 00:00:00 2001 From: tznind Date: Fri, 5 Jul 2024 20:25:13 +0100 Subject: [PATCH 02/30] More random code ported --- Terminal.Gui/TextEffects/Animation.cs | 125 +++--------- Terminal.Gui/TextEffects/BaseCharacter.cs | 107 ++++++++++ Terminal.Gui/TextEffects/BaseEffect.cs | 74 +++++++ Terminal.Gui/TextEffects/EffectTemplate.cs | 58 ++++++ Terminal.Gui/TextEffects/Geometry.cs | 137 +++++++++++++ Terminal.Gui/TextEffects/Graphics.cs | 126 ++++++++++++ Terminal.Gui/TextEffects/Motion.cs | 216 +++++++++++++++++++++ Terminal.Gui/TextEffects/Terminal.cs | 106 ++++++++++ 8 files changed, 856 insertions(+), 93 deletions(-) create mode 100644 Terminal.Gui/TextEffects/BaseCharacter.cs create mode 100644 Terminal.Gui/TextEffects/BaseEffect.cs create mode 100644 Terminal.Gui/TextEffects/EffectTemplate.cs create mode 100644 Terminal.Gui/TextEffects/Geometry.cs create mode 100644 Terminal.Gui/TextEffects/Graphics.cs create mode 100644 Terminal.Gui/TextEffects/Motion.cs create mode 100644 Terminal.Gui/TextEffects/Terminal.cs diff --git a/Terminal.Gui/TextEffects/Animation.cs b/Terminal.Gui/TextEffects/Animation.cs index c7e17fff07..210329ce25 100644 --- a/Terminal.Gui/TextEffects/Animation.cs +++ b/Terminal.Gui/TextEffects/Animation.cs @@ -109,6 +109,8 @@ public Scene (string sceneId, bool isLooping = false, SyncMetric? sync = null, E 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 void AddFrame (string symbol, int duration, Color color = null, bool bold { 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 void AddFrame (string symbol, int duration, Color color = null, bool bold } 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 void AddFrame (string symbol, int duration, Color color = null, bool bold 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 symbols, int duration) @@ -208,11 +197,7 @@ public void ApplyGradientToSymbols (Gradient gradient, IList symbols, in 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 void ResetScene () 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 void SetAppearance (string symbol, Color? color = null) } 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 void DeactivateScene (Scene scene) } } -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 Spectrum { get; } - - public Gradient (List spectrum) - { - Spectrum = spectrum; - } -} - - // Dummy classes for Ansitools, Colorterm, and Hexterm as placeholders public static class Ansitools { diff --git a/Terminal.Gui/TextEffects/BaseCharacter.cs b/Terminal.Gui/TextEffects/BaseCharacter.cs new file mode 100644 index 0000000000..3297754780 --- /dev/null +++ b/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 + } +} diff --git a/Terminal.Gui/TextEffects/BaseEffect.cs b/Terminal.Gui/TextEffects/BaseEffect.cs new file mode 100644 index 0000000000..5855f89793 --- /dev/null +++ b/Terminal.Gui/TextEffects/BaseEffect.cs @@ -0,0 +1,74 @@ +namespace Terminal.Gui.TextEffects; + +public abstract class BaseEffectIterator : IEnumerable where T : EffectConfig +{ + protected T Config { get; set; } + protected Terminal Terminal { get; set; } + protected List ActiveCharacters { get; set; } = new List (); + + public BaseEffectIterator (BaseEffect 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 GetEnumerator () + { + return this; + } + + IEnumerator IEnumerable.GetEnumerator () + { + return GetEnumerator (); + } + + public abstract string Next (); +} + +public abstract class BaseEffect 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 GetEnumerator () + { + var iterator = (BaseEffectIterator)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); + } + } +} + diff --git a/Terminal.Gui/TextEffects/EffectTemplate.cs b/Terminal.Gui/TextEffects/EffectTemplate.cs new file mode 100644 index 0000000000..c16dbccb97 --- /dev/null +++ b/Terminal.Gui/TextEffects/EffectTemplate.cs @@ -0,0 +1,58 @@ +namespace Terminal.Gui.TextEffects; + +public class EffectConfig +{ + public Color ColorSingle { get; set; } + public List ColorList { get; set; } + public Color FinalColor { get; set; } + public List FinalGradientStops { get; set; } + public List FinalGradientSteps { get; set; } + public int FinalGradientFrames { get; set; } + public float MovementSpeed { get; set; } + public EasingFunction Easing { get; set; } +} + +public class NamedEffectIterator : BaseEffectIterator +{ + 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 PendingChars = new List (); + private Dictionary CharacterFinalColorMap = new Dictionary (); +} + +public class NamedEffect : BaseEffect +{ + public NamedEffect (string inputData) : base (inputData) + { + } + + protected override Type ConfigCls => typeof (EffectConfig); + protected override Type IteratorCls => typeof (NamedEffectIterator); +} diff --git a/Terminal.Gui/TextEffects/Geometry.cs b/Terminal.Gui/TextEffects/Geometry.cs new file mode 100644 index 0000000000..5de2f70730 --- /dev/null +++ b/Terminal.Gui/TextEffects/Geometry.cs @@ -0,0 +1,137 @@ +namespace Terminal.Gui.TextEffects; + + +public static class GeometryUtils +{ + + public static List FindCoordsOnCircle (Coord origin, int radius, int coordsLimit = 0, bool unique = true) + { + var points = new List (); + var seenPoints = new HashSet (); + 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 FindCoordsInCircle (Coord center, int diameter) + { + var coordsInEllipse = new List (); + 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 FindCoordsInRect (Coord origin, int distance) + { + var coords = new List (); + 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 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 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); + } +} \ No newline at end of file diff --git a/Terminal.Gui/TextEffects/Graphics.cs b/Terminal.Gui/TextEffects/Graphics.cs new file mode 100644 index 0000000000..e03063a72f --- /dev/null +++ b/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 Spectrum { get; private set; } + + public Gradient (IEnumerable stops, IEnumerable 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 GenerateGradient (List stops, List steps, bool loop) + { + List gradient = new List (); + 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 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 GetRange (int startIndex, int count) + { + return Spectrum.Skip (startIndex).Take (count); + } + + public override string ToString () + { + return $"Gradient with {Spectrum.Count} colors."; + } +} + diff --git a/Terminal.Gui/TextEffects/Motion.cs b/Terminal.Gui/TextEffects/Motion.cs new file mode 100644 index 0000000000..ae8c061889 --- /dev/null +++ b/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 BezierControl { get; set; } + + public Waypoint (string waypointId, Coord coord, List bezierControl = null) + { + WaypointId = waypointId; + Coord = coord; + BezierControl = bezierControl ?? new List (); + } +} + +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 EaseFunction { get; set; } + public int Layer { get; set; } + public int HoldTime { get; set; } + public bool Loop { get; set; } + public List Segments { get; private set; } = new List (); + public int CurrentStep { get; set; } + public double TotalDistance { get; set; } + public double LastDistanceReached { get; set; } + + public Path (string pathId, double speed, Func 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 Paths { get; private set; } = new Dictionary (); + 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 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 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 + } + } + } +} diff --git a/Terminal.Gui/TextEffects/Terminal.cs b/Terminal.Gui/TextEffects/Terminal.cs new file mode 100644 index 0000000000..9302f0d47c --- /dev/null +++ b/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 CharacterByInputCoord = new Dictionary (); + + 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); + } +} From 8fbf4d5da99a70de307ab10efe7e740b1d75ad2a Mon Sep 17 00:00:00 2001 From: tznind Date: Sat, 6 Jul 2024 02:57:15 +0100 Subject: [PATCH 03/30] Building --- Terminal.Gui/TextEffects/Animation.cs | 23 +++--- Terminal.Gui/TextEffects/BaseCharacter.cs | 4 +- Terminal.Gui/TextEffects/BaseEffect.cs | 26 +----- Terminal.Gui/TextEffects/EffectTemplate.cs | 45 ----------- Terminal.Gui/TextEffects/Graphics.cs | 91 +++++++++------------ Terminal.Gui/TextEffects/HexTerm.cs | 94 ++++++++++++++++++++++ Terminal.Gui/TextEffects/Motion.cs | 18 ++++- UnitTests/TextEffects/AnimationTests.cs | 22 ++--- 8 files changed, 174 insertions(+), 149 deletions(-) create mode 100644 Terminal.Gui/TextEffects/HexTerm.cs diff --git a/Terminal.Gui/TextEffects/Animation.cs b/Terminal.Gui/TextEffects/Animation.cs index 210329ce25..a96f08a9ee 100644 --- a/Terminal.Gui/TextEffects/Animation.cs +++ b/Terminal.Gui/TextEffects/Animation.cs @@ -1,4 +1,5 @@ -using static Unix.Terminal.Curses; + +using static Terminal.Gui.TextEffects.EventHandler; namespace Terminal.Gui.TextEffects; @@ -7,7 +8,6 @@ public enum SyncMetric Distance, Step } - public class CharacterVisual { public string Symbol { get; set; } @@ -21,7 +21,9 @@ public class CharacterVisual public bool Strike { get; set; } public Color Color { get; 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) { @@ -35,7 +37,7 @@ public CharacterVisual (string symbol, bool bold = false, bool dim = false, bool Hidden = hidden; Strike = strike; Color = color; - _colorCode = colorCode; + _colorCode = colorCode; // Initialize _colorCode from the constructor argument FormattedSymbol = FormatSymbol (); } @@ -49,7 +51,7 @@ private string FormatSymbol () if (Reverse) formattingString += Ansitools.ApplyReverse (); if (Hidden) formattingString += Ansitools.ApplyHidden (); 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 () : "")}"; } @@ -67,6 +69,7 @@ public void DisableModes () } } + public class Frame { public CharacterVisual CharacterVisual { get; } @@ -97,8 +100,8 @@ public class Scene 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 int EasingTotalSteps { get; set; } + public int EasingCurrentStep { get; 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) @@ -470,12 +473,6 @@ public void DeactivateScene (Scene scene) } } -// Dummy Enum for Event handling -public enum Event -{ - SceneComplete, - SceneActivated -} // Dummy classes for Ansitools, Colorterm, and Hexterm as placeholders public static class Ansitools diff --git a/Terminal.Gui/TextEffects/BaseCharacter.cs b/Terminal.Gui/TextEffects/BaseCharacter.cs index 3297754780..886cd6b599 100644 --- a/Terminal.Gui/TextEffects/BaseCharacter.cs +++ b/Terminal.Gui/TextEffects/BaseCharacter.cs @@ -77,7 +77,9 @@ public void HandleEvent (Event @event, object caller) Character.Motion.CurrentCoord = (Coord)target; break; case Action.Callback: - (target as Action)?.Invoke (); + + // TODO: + throw new NotImplementedException ("TODO, port (target as Action)?.Invoke ()"); break; default: throw new ArgumentOutOfRangeException (nameof (action), "Unhandled action."); diff --git a/Terminal.Gui/TextEffects/BaseEffect.cs b/Terminal.Gui/TextEffects/BaseEffect.cs index 5855f89793..09a9059ba8 100644 --- a/Terminal.Gui/TextEffects/BaseEffect.cs +++ b/Terminal.Gui/TextEffects/BaseEffect.cs @@ -1,6 +1,6 @@ namespace Terminal.Gui.TextEffects; -public abstract class BaseEffectIterator : IEnumerable where T : EffectConfig +public abstract class BaseEffectIterator where T : EffectConfig, new() { protected T Config { get; set; } protected Terminal Terminal { get; set; } @@ -12,8 +12,6 @@ public BaseEffectIterator (BaseEffect effect) Terminal = new Terminal (effect.InputData, effect.TerminalConfig); } - public string Frame => Terminal.GetFormattedOutputString (); - public void Update () { foreach (var character in ActiveCharacters) @@ -23,17 +21,6 @@ public void Update () ActiveCharacters.RemoveAll (character => !character.IsActive); } - public IEnumerator GetEnumerator () - { - return this; - } - - IEnumerator IEnumerable.GetEnumerator () - { - return GetEnumerator (); - } - - public abstract string Next (); } public abstract class BaseEffect where T : EffectConfig, new() @@ -49,14 +36,7 @@ protected BaseEffect (string inputData) TerminalConfig = new TerminalConfig (); } - public abstract Type IteratorClass { get; } - - public IEnumerator GetEnumerator () - { - var iterator = (BaseEffectIterator)Activator.CreateInstance (IteratorClass, this); - return iterator; - } - + /* public IDisposable TerminalOutput (string endSymbol = "\n") { var terminal = new Terminal (InputData, TerminalConfig); @@ -69,6 +49,6 @@ public IDisposable TerminalOutput (string endSymbol = "\n") { terminal.RestoreCursor (endSymbol); } - } + }*/ } diff --git a/Terminal.Gui/TextEffects/EffectTemplate.cs b/Terminal.Gui/TextEffects/EffectTemplate.cs index c16dbccb97..4b0fddcb7e 100644 --- a/Terminal.Gui/TextEffects/EffectTemplate.cs +++ b/Terminal.Gui/TextEffects/EffectTemplate.cs @@ -11,48 +11,3 @@ public class EffectConfig public float MovementSpeed { get; set; } public EasingFunction Easing { get; set; } } - -public class NamedEffectIterator : BaseEffectIterator -{ - 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 PendingChars = new List (); - private Dictionary CharacterFinalColorMap = new Dictionary (); -} - -public class NamedEffect : BaseEffect -{ - public NamedEffect (string inputData) : base (inputData) - { - } - - protected override Type ConfigCls => typeof (EffectConfig); - protected override Type IteratorCls => typeof (NamedEffectIterator); -} diff --git a/Terminal.Gui/TextEffects/Graphics.cs b/Terminal.Gui/TextEffects/Graphics.cs index e03063a72f..73cd022635 100644 --- a/Terminal.Gui/TextEffects/Graphics.cs +++ b/Terminal.Gui/TextEffects/Graphics.cs @@ -5,69 +5,64 @@ 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) { - if (!IsValidHexColor (rgbColor)) + if (!ColorUtils.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 + RgbColor = rgbColor.StartsWith ("#") ? rgbColor.Substring (1).ToUpper () : rgbColor.ToUpper (); + XtermColor = ColorUtils.HexToXterm (RgbColor); // Convert RGB to XTerm-256 } public Color (int xtermColor) { + if (!ColorUtils.IsValidXtermColor (xtermColor)) + throw new ArgumentException ("Invalid XTerm-256 color code."); + 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 List Spectrum { get; private set; } + // Constructor now accepts IEnumerable for steps. public Gradient (IEnumerable stops, IEnumerable 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."); if (steps == null || !steps.Any ()) throw new ArgumentException ("Steps are required to define the transitions between colors."); @@ -83,7 +78,8 @@ private List GenerateGradient (List stops, List steps, bool l 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; @@ -93,16 +89,16 @@ private IEnumerable 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}"); + 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) { - return start + (end - start) * currentStep / steps; + return start + (int)((end - start) * (double)currentStep / steps); } public Color GetColorAtFraction (double fraction) @@ -112,15 +108,6 @@ public Color GetColorAtFraction (double fraction) int index = (int)(fraction * (Spectrum.Count - 1)); return Spectrum [index]; } - - public IEnumerable GetRange (int startIndex, int count) - { - return Spectrum.Skip (startIndex).Take (count); - } - - public override string ToString () - { - return $"Gradient with {Spectrum.Count} colors."; - } } + diff --git a/Terminal.Gui/TextEffects/HexTerm.cs b/Terminal.Gui/TextEffects/HexTerm.cs new file mode 100644 index 0000000000..b3b96c4d37 --- /dev/null +++ b/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 xtermToHexMap = new Dictionary + { + {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 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)); + } +} diff --git a/Terminal.Gui/TextEffects/Motion.cs b/Terminal.Gui/TextEffects/Motion.cs index ae8c061889..e2431cd493 100644 --- a/Terminal.Gui/TextEffects/Motion.cs +++ b/Terminal.Gui/TextEffects/Motion.cs @@ -56,7 +56,6 @@ public Coord GetCoordOnSegment (double distanceFactor) return new Coord (column, row); } } - public class Path { public string PathId { get; private set; } @@ -69,6 +68,7 @@ public class Path public int CurrentStep { get; set; } public double TotalDistance { 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 easeFunction = null, int layer = 0, int holdTime = 0, bool loop = false) { @@ -100,9 +100,9 @@ public void AddWaypoint (Waypoint waypoint) 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; LastDistanceReached = distanceTravelled; @@ -174,9 +174,19 @@ public void ActivatePath (Path path) ActivePath.CurrentStep = 0; // Reset the path's progress } + /// + /// Set the active path to None if the active path is the given path. + /// + public void DeactivatePath (Path p) + { + if (p == ActivePath) + { + ActivePath = null; + } + } public void DeactivatePath () { - ActivePath = null; + ActivePath = null; } public void Move () diff --git a/UnitTests/TextEffects/AnimationTests.cs b/UnitTests/TextEffects/AnimationTests.cs index 0a00b2e7c1..84de80f707 100644 --- a/UnitTests/TextEffects/AnimationTests.cs +++ b/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; +using Color = Terminal.Gui.TextEffects.Color; public class AnimationTests { @@ -100,7 +97,8 @@ public void TestSceneAddFrameInvalidDuration () public void TestSceneApplyGradientToSymbolsEqualColorsAndSymbols () { 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 { "a", "b", "c" }; scene.ApplyGradientToSymbols (gradient, symbols, duration: 1); Assert.Equal (3, scene.Frames.Count); @@ -115,7 +113,9 @@ public void TestSceneApplyGradientToSymbolsEqualColorsAndSymbols () public void TestSceneApplyGradientToSymbolsUnequalColorsAndSymbols () { 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 { "q", "z" }; scene.ApplyGradientToSymbols (gradient, symbols, duration: 1); Assert.Equal (5, scene.Frames.Count); @@ -142,7 +142,7 @@ public void TestAnimationInit () public void TestAnimationNewScene () { var animation = character.Animation; - var scene = animation.NewScene ("test_scene", isLooping: true); + var scene = animation.NewScene (id:"test_scene", isLooping: true); Assert.IsType (scene); Assert.Equal ("test_scene", scene.SceneId); Assert.True (scene.IsLooping); @@ -163,7 +163,7 @@ public void TestAnimationNewSceneWithoutId () public void TestAnimationQueryScene () { 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")); } @@ -171,7 +171,7 @@ public void TestAnimationQueryScene () public void TestAnimationLoopingActiveSceneIsComplete () { 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); animation.ActivateScene (scene); Assert.True (animation.ActiveSceneIsComplete ()); @@ -181,7 +181,7 @@ public void TestAnimationLoopingActiveSceneIsComplete () public void TestAnimationNonLoopingActiveSceneIsComplete () { var animation = character.Animation; - var scene = animation.NewScene ("test_scene"); + var scene = animation.NewScene (id: "test_scene"); scene.AddFrame (symbol: "a", duration: 1); animation.ActivateScene (scene); Assert.False (animation.ActiveSceneIsComplete ()); From b82eda4ae5436e8b7bd6d2f2be7f1ecbc932e282 Mon Sep 17 00:00:00 2001 From: tznind Date: Sat, 6 Jul 2024 03:24:27 +0100 Subject: [PATCH 04/30] Rainbow gradient! --- UICatalog/Scenarios/TextEffectsScenario.cs | 113 +++++++++++++++++++++ 1 file changed, 113 insertions(+) create mode 100644 UICatalog/Scenarios/TextEffectsScenario.cs diff --git a/UICatalog/Scenarios/TextEffectsScenario.cs b/UICatalog/Scenarios/TextEffectsScenario.cs new file mode 100644 index 0000000000..00cb4cb5fe --- /dev/null +++ b/UICatalog/Scenarios/TextEffectsScenario.cs @@ -0,0 +1,113 @@ +using System; +using System.Collections.Generic; +using System.Text; +using Terminal.Gui; +using Terminal.Gui.TextEffects; +using static UICatalog.Scenario; + + +using Color = Terminal.Gui.TextEffects.Color; + +namespace UICatalog.Scenarios; + + + +[ScenarioMetadata ("Text Effects", "Text Effects.")] +[ScenarioCategory ("Colors")] +public class TextEffectsScenario : Scenario +{ + public override void Main () + { + Application.Init (); + var top = Application.Top; + + // Creates a window that occupies the entire terminal with a title. + var window = new Window () + { + X = 0, + Y = 1, // Leaves one row for the toplevel menu + + // By using Dim.Fill(), it will automatically resize without manual intervention + Width = Dim.Fill (), + Height = Dim.Fill (), + Title = "Text Effects Scenario" + }; + + // Create a large empty view. + var emptyView = new TextEffectsExampleView () + { + X = 0, + Y = 0, + Width = Dim.Fill (), + Height = Dim.Fill () , + }; + + window.Add (emptyView); + + // Create a label in the center of the window. + var label = new Label () + { + X = Pos.Center (), + Y = Pos.Center (), + Width = 10, + Height = 1, + Title = "Hello" + }; + window.Add (label); + + Application.Run (window); + Application.Shutdown (); + } +} + +internal class TextEffectsExampleView : View +{ + public override void OnDrawContent (Rectangle viewport) + { + base.OnDrawContent (viewport); + + // Define the colors of the rainbow + var stops = new List + { + Color.FromRgb(255, 0, 0), // Red + Color.FromRgb(255, 165, 0), // Orange + Color.FromRgb(255, 255, 0), // Yellow + Color.FromRgb(0, 128, 0), // Green + Color.FromRgb(0, 0, 255), // Blue + Color.FromRgb(75, 0, 130), // Indigo + Color.FromRgb(238, 130, 238) // Violet + }; + + // Define the number of steps between each color + var steps = new List + { + 20, // between Red and Orange + 20, // between Orange and Yellow + 20, // between Yellow and Green + 20, // between Green and Blue + 20, // between Blue and Indigo + 20 // between Indigo and Violet + }; + + // Create the gradient + var rainbowGradient = new Gradient (stops, steps, loop: true); + + + for (int x = 0 ; x < viewport.Width; x++) + { + double fraction = (double)x / (viewport.Width - 1); + 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 + + + AddRune (x, 0, new Rune ('█')); + } + } +} \ No newline at end of file From 5cac6597dd878617a3c485c95093cf5c96aadcdc Mon Sep 17 00:00:00 2001 From: tznind Date: Sat, 6 Jul 2024 09:23:06 +0100 Subject: [PATCH 05/30] Investigate adding a bouncing ball animation --- UICatalog/Scenarios/TextEffectsScenario.cs | 68 +++++++++++++++++++++- 1 file changed, 67 insertions(+), 1 deletion(-) diff --git a/UICatalog/Scenarios/TextEffectsScenario.cs b/UICatalog/Scenarios/TextEffectsScenario.cs index 00cb4cb5fe..8a28901b0a 100644 --- a/UICatalog/Scenarios/TextEffectsScenario.cs +++ b/UICatalog/Scenarios/TextEffectsScenario.cs @@ -1,12 +1,14 @@ using System; using System.Collections.Generic; using System.Text; +using System.Threading; using Terminal.Gui; using Terminal.Gui.TextEffects; using static UICatalog.Scenario; using Color = Terminal.Gui.TextEffects.Color; +using Animation = Terminal.Gui.TextEffects.Animation; namespace UICatalog.Scenarios; @@ -110,4 +112,68 @@ public override void OnDrawContent (Rectangle viewport) AddRune (x, 0, new Rune ('█')); } } -} \ No newline at end of file + public class Ball + { + public Animation Animation { get; private set; } + public Scene BouncingScene { get; private set; } + public View Viewport { get; private set; } + public EffectCharacter Character { get; private set; } + + public Ball (View viewport) + { + Viewport = viewport; + Character = new EffectCharacter (1, "O", 0, 0); + Animation = Character.Animation; + CreateBouncingScene (); + } + + private void CreateBouncingScene () + { + BouncingScene = Animation.NewScene (isLooping: true); + int width = Viewport.Frame.Width; + int height = Viewport.Frame.Height; + double frequency = 2 * Math.PI / width; + + for (int x = 0; x < width; x++) + { + int y = (int)((height - 1) / 2 * (1 + Math.Sin (frequency * x))); + BouncingScene.AddFrame ("O", 1); + BouncingScene.Frames [BouncingScene.Frames.Count - 1].CharacterVisual.Position = new Coord (x, y); + } + + for (int x = width - 1; x >= 0; x--) + { + int y = (int)((height - 1) / 2 * (1 + Math.Sin (frequency * x))); + BouncingScene.AddFrame ("O", 1); + BouncingScene.Frames [BouncingScene.Frames.Count - 1].CharacterVisual.Position = new Coord (x, y); + } + } + + public void Start () + { + Animation.ActivateScene (BouncingScene); + new Thread (() => + { + while (true) + { + Draw (); + Thread.Sleep (100); // Adjust the speed of animation + Animation.StepAnimation (); + } + }) + { IsBackground = true }.Start (); + } + + private void Draw () + { + var characterVisual = Animation.CurrentCharacterVisual; + var coord = characterVisual.Position; + Application.MainLoop.Invoke (() => + { + Viewport.Clear (); + Viewport.AddRune (coord.X, coord.Y, new Rune ('O')); + Application.Refresh (); + }); + } + } +} From d7a4e0e7c17b2d5eeb99d60d3a699a5ff8d64fb8 Mon Sep 17 00:00:00 2001 From: tznind Date: Sat, 6 Jul 2024 20:01:49 +0100 Subject: [PATCH 06/30] Ball bouncing properly --- .../{Animation.cs => AnimationScenario.cs} | 2 +- UICatalog/Scenarios/TextEffectsScenario.cs | 89 +++++++++++++------ 2 files changed, 62 insertions(+), 29 deletions(-) rename UICatalog/Scenarios/{Animation.cs => AnimationScenario.cs} (99%) diff --git a/UICatalog/Scenarios/Animation.cs b/UICatalog/Scenarios/AnimationScenario.cs similarity index 99% rename from UICatalog/Scenarios/Animation.cs rename to UICatalog/Scenarios/AnimationScenario.cs index 42ad540c1f..f924f8f08d 100644 --- a/UICatalog/Scenarios/Animation.cs +++ b/UICatalog/Scenarios/AnimationScenario.cs @@ -13,7 +13,7 @@ namespace UICatalog.Scenarios; [ScenarioMetadata ("Animation", "Demonstration of how to render animated images with threading.")] [ScenarioCategory ("Threading")] [ScenarioCategory ("Drawing")] -public class Animation : Scenario +public class AnimationScenario : Scenario { private bool _isDisposed; diff --git a/UICatalog/Scenarios/TextEffectsScenario.cs b/UICatalog/Scenarios/TextEffectsScenario.cs index 8a28901b0a..2652c52499 100644 --- a/UICatalog/Scenarios/TextEffectsScenario.cs +++ b/UICatalog/Scenarios/TextEffectsScenario.cs @@ -6,14 +6,11 @@ using Terminal.Gui.TextEffects; using static UICatalog.Scenario; - using Color = Terminal.Gui.TextEffects.Color; using Animation = Terminal.Gui.TextEffects.Animation; namespace UICatalog.Scenarios; - - [ScenarioMetadata ("Text Effects", "Text Effects.")] [ScenarioCategory ("Colors")] public class TextEffectsScenario : Scenario @@ -41,7 +38,7 @@ public override void Main () X = 0, Y = 0, Width = Dim.Fill (), - Height = Dim.Fill () , + Height = Dim.Fill (), }; window.Add (emptyView); @@ -53,7 +50,7 @@ public override void Main () Y = Pos.Center (), Width = 10, Height = 1, - Title = "Hello" + Text = "Hello" }; window.Add (label); @@ -64,10 +61,29 @@ public override void Main () internal class TextEffectsExampleView : View { + Ball? _ball; + private bool resized; + + protected override void OnViewportChanged (DrawEventArgs e) + { + base.OnViewportChanged (e); + resized = true; + } + public override void OnDrawContent (Rectangle viewport) { base.OnDrawContent (viewport); + if ( + // First time + (_ball == null && viewport.Width > 0 && viewport.Height > 0) + || resized) + { + _ball = new Ball (this); + _ball.Start (); + resized = false; + } + // Define the colors of the rainbow var stops = new List { @@ -94,24 +110,24 @@ public override void OnDrawContent (Rectangle viewport) // Create the gradient var rainbowGradient = new Gradient (stops, steps, loop: true); - - for (int x = 0 ; x < viewport.Width; x++) + for (int x = 0; x < viewport.Width; x++) { double fraction = (double)x / (viewport.Width - 1); 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), new Terminal.Gui.Color (color.R, color.G, color.B) - )); // Setting color based on RGB - + )); // Setting color based on RGB AddRune (x, 0, new Rune ('█')); } + + _ball?.Draw (); } + public class Ball { public Animation Animation { get; private set; } @@ -125,6 +141,7 @@ public Ball (View viewport) Character = new EffectCharacter (1, "O", 0, 0); Animation = Character.Animation; CreateBouncingScene (); + CreateMotionPath (); } private void CreateBouncingScene () @@ -132,23 +149,44 @@ private void CreateBouncingScene () BouncingScene = Animation.NewScene (isLooping: true); int width = Viewport.Frame.Width; int height = Viewport.Frame.Height; - double frequency = 2 * Math.PI / width; + double frequency = 4 * Math.PI / width; // Double the frequency for (int x = 0; x < width; x++) { - int y = (int)((height - 1) / 2 * (1 + Math.Sin (frequency * x))); + int y = (int)((height) / 2 * (1 + Math.Sin (frequency * x))); // Decrease amplitude BouncingScene.AddFrame ("O", 1); - BouncingScene.Frames [BouncingScene.Frames.Count - 1].CharacterVisual.Position = new Coord (x, y); } for (int x = width - 1; x >= 0; x--) { - int y = (int)((height - 1) / 2 * (1 + Math.Sin (frequency * x))); + int y = (int)((height) / 2 * (1 + Math.Sin (frequency * x))); // Decrease amplitude BouncingScene.AddFrame ("O", 1); - BouncingScene.Frames [BouncingScene.Frames.Count - 1].CharacterVisual.Position = new Coord (x, y); } } + private void CreateMotionPath () + { + int width = Viewport.Frame.Width; + int height = Viewport.Frame.Height; + double frequency = 4 * Math.PI / width; // Double the frequency + + var path = Character.Motion.CreatePath ("sineWavePath", speed: 1, loop: true); + + for (int x = 0; x < width; x++) + { + int y = (int)((height) / 2 * (1 + Math.Sin (frequency * x))); // Decrease amplitude + path.AddWaypoint (new Waypoint ($"waypoint_{x}", new Coord (x, y))); + } + + for (int x = width - 1; x >= 0; x--) + { + int y = (int)((height) / 2 * (1 + Math.Sin (frequency * x))); // Decrease amplitude + path.AddWaypoint (new Waypoint ($"waypoint_{x}", new Coord (x, y))); + } + + Character.Motion.ActivatePath (path); + } + public void Start () { Animation.ActivateScene (BouncingScene); @@ -156,24 +194,19 @@ public void Start () { while (true) { - Draw (); - Thread.Sleep (100); // Adjust the speed of animation - Animation.StepAnimation (); + Thread.Sleep (10); // Adjust the speed of animation + Character.Tick (); + + Application.Invoke (() => Viewport.SetNeedsDisplay ()); } }) { IsBackground = true }.Start (); } - private void Draw () + public void Draw () { - var characterVisual = Animation.CurrentCharacterVisual; - var coord = characterVisual.Position; - Application.MainLoop.Invoke (() => - { - Viewport.Clear (); - Viewport.AddRune (coord.X, coord.Y, new Rune ('O')); - Application.Refresh (); - }); + Driver.SetAttribute (Viewport.ColorScheme.Normal); + Viewport.AddRune (Character.Motion.CurrentCoord.Column, Character.Motion.CurrentCoord.Row, new Rune ('O')); } } } From ab07f53bd29dbfc21612b720748c7c86eceddf4a Mon Sep 17 00:00:00 2001 From: tznind Date: Sat, 6 Jul 2024 20:39:01 +0100 Subject: [PATCH 07/30] Radial gradient --- Terminal.Gui/TextEffects/ArgValidators.cs | 273 +++++++++++++++++++++ Terminal.Gui/TextEffects/BaseEffect.cs | 6 + Terminal.Gui/TextEffects/Effects/Beams.cs | 241 ++++++++++++++++++ Terminal.Gui/TextEffects/Graphics.cs | 141 +++++++++-- Terminal.Gui/TextEffects/Motion.cs | 29 ++- UICatalog/Scenarios/TextEffectsScenario.cs | 65 ++++- 6 files changed, 721 insertions(+), 34 deletions(-) create mode 100644 Terminal.Gui/TextEffects/ArgValidators.cs create mode 100644 Terminal.Gui/TextEffects/Effects/Beams.cs diff --git a/Terminal.Gui/TextEffects/ArgValidators.cs b/Terminal.Gui/TextEffects/ArgValidators.cs new file mode 100644 index 0000000000..4e31e7a511 --- /dev/null +++ b/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 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."); + } + } +} diff --git a/Terminal.Gui/TextEffects/BaseEffect.cs b/Terminal.Gui/TextEffects/BaseEffect.cs index 09a9059ba8..e10699a9a3 100644 --- a/Terminal.Gui/TextEffects/BaseEffect.cs +++ b/Terminal.Gui/TextEffects/BaseEffect.cs @@ -6,10 +6,16 @@ protected Terminal Terminal { get; set; } protected List ActiveCharacters { get; set; } = new List (); + protected BaseEffect Effect { get; } + + + public BaseEffectIterator (BaseEffect effect) { + Effect = effect; Config = effect.EffectConfig; Terminal = new Terminal (effect.InputData, effect.TerminalConfig); + } public void Update () diff --git a/Terminal.Gui/TextEffects/Effects/Beams.cs b/Terminal.Gui/TextEffects/Effects/Beams.cs new file mode 100644 index 0000000000..b39ef9a911 --- /dev/null +++ b/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 +{ + public Beams (string inputData) : base (inputData) + { + } + + protected override BaseEffectIterator CreateIterator () + { + return new BeamsIterator (this); + } +} + + +public class BeamsIterator : BaseEffectIterator +{ + private class Group + { + public List Characters { get; private set; } + public string Direction { get; private set; } + private Terminal Terminal; + private BeamsConfig Config; + private double Speed; + private float NextCharacterCounter; + private List SortedCharacters; + + public Group (List 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 PendingGroups = new List (); + private Dictionary CharacterFinalColorMap = new Dictionary (); + private List ActiveGroups = new List (); + private int Delay = 0; + private string Phase = "beams"; + private List> 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 (); + + 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; + } + } +} +*/ \ No newline at end of file diff --git a/Terminal.Gui/TextEffects/Graphics.cs b/Terminal.Gui/TextEffects/Graphics.cs index 73cd022635..03fb2059ab 100644 --- a/Terminal.Gui/TextEffects/Graphics.cs +++ b/Terminal.Gui/TextEffects/Graphics.cs @@ -54,32 +54,66 @@ public static Color FromRgb (int r, int g, int b) } } - public class Gradient { public List Spectrum { get; private set; } + private readonly bool _loop; + private readonly List _stops; + private readonly List _steps; + + public enum Direction + { + Vertical, + Horizontal, + Radial, + Diagonal + } - // Constructor now accepts IEnumerable for steps. public Gradient (IEnumerable stops, IEnumerable 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 GenerateGradient (List stops, List steps, bool loop) + private List GenerateGradient (IEnumerable steps) { List gradient = new List (); - 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; @@ -89,25 +123,80 @@ private IEnumerable InterpolateColors (Color start, Color end, int steps) { 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); } } - private int Interpolate (int start, int end, int steps, int currentStep) + public Dictionary BuildCoordinateColorMapping (int maxRow, int maxColumn, Direction direction) { - return start + (int)((end - start) * (double)currentStep / steps); + var gradientMapping = new Dictionary (); + + 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; } -} - - +} \ No newline at end of file diff --git a/Terminal.Gui/TextEffects/Motion.cs b/Terminal.Gui/TextEffects/Motion.cs index e2431cd493..9ed544f5e6 100644 --- a/Terminal.Gui/TextEffects/Motion.cs +++ b/Terminal.Gui/TextEffects/Motion.cs @@ -1,5 +1,4 @@ namespace Terminal.Gui.TextEffects; - public class Coord { public int Column { get; set; } @@ -12,6 +11,34 @@ public Coord (int column, int 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 diff --git a/UICatalog/Scenarios/TextEffectsScenario.cs b/UICatalog/Scenarios/TextEffectsScenario.cs index 2652c52499..3cdd90eee2 100644 --- a/UICatalog/Scenarios/TextEffectsScenario.cs +++ b/UICatalog/Scenarios/TextEffectsScenario.cs @@ -84,6 +84,53 @@ public override void OnDrawContent (Rectangle viewport) 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.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 { 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 var stops = new List { @@ -115,17 +162,21 @@ public override void OnDrawContent (Rectangle viewport) double fraction = (double)x / (viewport.Width - 1); 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 ('█')); } + } + + 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 From 9672de726261eb83385c001e75f3461acb0ce51e Mon Sep 17 00:00:00 2001 From: tznind Date: Sat, 6 Jul 2024 20:43:23 +0100 Subject: [PATCH 08/30] Add other gradients --- UICatalog/Scenarios/TextEffectsScenario.cs | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/UICatalog/Scenarios/TextEffectsScenario.cs b/UICatalog/Scenarios/TextEffectsScenario.cs index 3cdd90eee2..21347766a9 100644 --- a/UICatalog/Scenarios/TextEffectsScenario.cs +++ b/UICatalog/Scenarios/TextEffectsScenario.cs @@ -85,12 +85,17 @@ public override void OnDrawContent (Rectangle viewport) } DrawTopLineGradient (viewport); - DrawRadialGradient (viewport); + + int x = 2; + DrawGradientArea (Gradient.Direction.Horizontal,x); + DrawGradientArea (Gradient.Direction.Vertical, x += 41); + DrawGradientArea (Gradient.Direction.Radial, x += 41); + DrawGradientArea (Gradient.Direction.Diagonal, x += 41); _ball?.Draw (); } - private void DrawRadialGradient (Rectangle viewport) + private void DrawGradientArea (Gradient.Direction direction, int xOffset) { // Define the colors of the gradient stops var stops = new List @@ -111,7 +116,7 @@ private void DrawRadialGradient (Rectangle viewport) int maxColumn = 40; // Build the coordinate-color mapping for a radial gradient - var gradientMapping = radialGradient.BuildCoordinateColorMapping (maxRow, maxColumn, Gradient.Direction.Radial); + var gradientMapping = radialGradient.BuildCoordinateColorMapping (maxRow, maxColumn, direction); // Print the gradient for (int row = 0; row <= maxRow; row++) @@ -123,7 +128,7 @@ private void DrawRadialGradient (Rectangle viewport) SetColor (color); - AddRune (col+2, row+3, new Rune ('█')); + AddRune (col+ xOffset, row+3, new Rune ('█')); } } } From 1f13ec5ab783183411cc6a62bd7b6bde3c00dfb2 Mon Sep 17 00:00:00 2001 From: tznind Date: Sat, 6 Jul 2024 20:57:15 +0100 Subject: [PATCH 09/30] Streamline gradients example and add tabs --- UICatalog/Scenarios/TextEffectsScenario.cs | 227 +++++++++++++++------ 1 file changed, 167 insertions(+), 60 deletions(-) diff --git a/UICatalog/Scenarios/TextEffectsScenario.cs b/UICatalog/Scenarios/TextEffectsScenario.cs index 21347766a9..d5e914c776 100644 --- a/UICatalog/Scenarios/TextEffectsScenario.cs +++ b/UICatalog/Scenarios/TextEffectsScenario.cs @@ -18,84 +18,83 @@ public class TextEffectsScenario : Scenario public override void Main () { Application.Init (); - var top = Application.Top; - - // Creates a window that occupies the entire terminal with a title. - var window = new Window () + var w = new Window { - X = 0, - Y = 1, // Leaves one row for the toplevel menu - - // By using Dim.Fill(), it will automatically resize without manual intervention - Width = Dim.Fill (), + Width = Dim.Fill(), Height = Dim.Fill (), - Title = "Text Effects Scenario" }; - // Create a large empty view. - var emptyView = new TextEffectsExampleView () + // Creates a window that occupies the entire terminal with a title. + var tabView = new TabView () { - X = 0, - Y = 0, Width = Dim.Fill (), Height = Dim.Fill (), }; - window.Add (emptyView); - - // Create a label in the center of the window. - var label = new Label () + var t1 = new Tab () { - X = Pos.Center (), - Y = Pos.Center (), - Width = 10, - Height = 1, - Text = "Hello" + View = new GradientsView () + { + Width = Dim.Fill (), + Height = Dim.Fill (), + }, + DisplayText = "Gradients" + }; + var t2 = new Tab () + { + View = new BallsView () + { + Width = Dim.Fill (), + Height = Dim.Fill (), + }, + DisplayText = "Ball" }; - window.Add (label); - Application.Run (window); + tabView.AddTab (t1,false); + tabView.AddTab (t2,false); + + w.Add (tabView); + + Application.Run (w); + w.Dispose (); + Application.Shutdown (); + this.Dispose (); } } -internal class TextEffectsExampleView : View -{ - Ball? _ball; - private bool resized; - - protected override void OnViewportChanged (DrawEventArgs e) - { - base.OnViewportChanged (e); - resized = true; - } +internal class GradientsView : View +{ public override void OnDrawContent (Rectangle viewport) { base.OnDrawContent (viewport); - if ( - // First time - (_ball == null && viewport.Width > 0 && viewport.Height > 0) - || resized) - { - _ball = new Ball (this); - _ball.Start (); - resized = false; - } - DrawTopLineGradient (viewport); int x = 2; - DrawGradientArea (Gradient.Direction.Horizontal,x); - DrawGradientArea (Gradient.Direction.Vertical, x += 41); - DrawGradientArea (Gradient.Direction.Radial, x += 41); - DrawGradientArea (Gradient.Direction.Diagonal, x += 41); + int y = 3; - _ball?.Draw (); + if (viewport.Height < 25) // Not enough space, render in a single line + { + DrawGradientArea (Gradient.Direction.Horizontal, x, y); + DrawGradientArea (Gradient.Direction.Vertical, x + 32, y); + DrawGradientArea (Gradient.Direction.Radial, x + 64, y); + DrawGradientArea (Gradient.Direction.Diagonal, x + 96, y); + } + else // Enough space, render in two lines + { + DrawGradientArea (Gradient.Direction.Horizontal, x, y); + DrawGradientArea (Gradient.Direction.Vertical, x + 32, y); + DrawGradientArea (Gradient.Direction.Radial, x, y + 17); + DrawGradientArea (Gradient.Direction.Diagonal, x + 32, y + 17); + } } - private void DrawGradientArea (Gradient.Direction direction, int xOffset) + + + + private void DrawGradientArea (Gradient.Direction direction, int xOffset, int yOffset) { // Define the colors of the gradient stops var stops = new List @@ -112,8 +111,8 @@ private void DrawGradientArea (Gradient.Direction direction, int xOffset) var radialGradient = new Gradient (stops, steps, loop: false); // Define the size of the rectangle - int maxRow = 20; - int maxColumn = 40; + int maxRow = 15; // Adjusted to keep aspect ratio + int maxColumn = 30; // Build the coordinate-color mapping for a radial gradient var gradientMapping = radialGradient.BuildCoordinateColorMapping (maxRow, maxColumn, direction); @@ -125,17 +124,16 @@ private void DrawGradientArea (Gradient.Direction direction, int xOffset) { var coord = new Coord (col, row); var color = gradientMapping [coord]; - + SetColor (color); - AddRune (col+ xOffset, row+3, new Rune ('█')); + AddRune (col + xOffset, row + yOffset, new Rune ('█')); } } } private void DrawTopLineGradient (Rectangle viewport) { - // Define the colors of the rainbow var stops = new List { @@ -181,7 +179,32 @@ private void SetColor (Color color) 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 + } +} + +internal class BallsView : View +{ + private Ball? _ball; + private bool _resized; + + protected override void OnViewportChanged (DrawEventArgs e) + { + base.OnViewportChanged (e); + _resized = true; + } + + public override void OnDrawContent (Rectangle viewport) + { + base.OnDrawContent (viewport); + + if ((_ball == null && viewport.Width > 0 && viewport.Height > 0) || _resized) + { + _ball = new Ball (this); + _ball.Start (); + _resized = false; + } + _ball?.Draw (); } public class Ball @@ -209,13 +232,13 @@ private void CreateBouncingScene () for (int x = 0; x < width; x++) { - int y = (int)((height) / 2 * (1 + Math.Sin (frequency * x))); // Decrease amplitude + int y = (int)((height - 1) / 2 * (1 + Math.Sin (frequency * x) * 0.8)); // Decrease amplitude BouncingScene.AddFrame ("O", 1); } for (int x = width - 1; x >= 0; x--) { - int y = (int)((height) / 2 * (1 + Math.Sin (frequency * x))); // Decrease amplitude + int y = (int)((height - 1) / 2 * (1 + Math.Sin (frequency * x) * 0.8)); // Decrease amplitude BouncingScene.AddFrame ("O", 1); } } @@ -230,13 +253,13 @@ private void CreateMotionPath () for (int x = 0; x < width; x++) { - int y = (int)((height) / 2 * (1 + Math.Sin (frequency * x))); // Decrease amplitude + int y = (int)((height - 1) / 2 * (1 + Math.Sin (frequency * x) * 0.8)); // Decrease amplitude path.AddWaypoint (new Waypoint ($"waypoint_{x}", new Coord (x, y))); } for (int x = width - 1; x >= 0; x--) { - int y = (int)((height) / 2 * (1 + Math.Sin (frequency * x))); // Decrease amplitude + int y = (int)((height - 1) / 2 * (1 + Math.Sin (frequency * x) * 0.8)); // Decrease amplitude path.AddWaypoint (new Waypoint ($"waypoint_{x}", new Coord (x, y))); } @@ -266,3 +289,87 @@ public void Draw () } } } + + +public class Ball +{ + public Animation Animation { get; private set; } + public Scene BouncingScene { get; private set; } + public View Viewport { get; private set; } + public EffectCharacter Character { get; private set; } + + public Ball (View viewport) + { + Viewport = viewport; + Character = new EffectCharacter (1, "O", 0, 0); + Animation = Character.Animation; + CreateBouncingScene (); + CreateMotionPath (); + } + + private void CreateBouncingScene () + { + BouncingScene = Animation.NewScene (isLooping: true); + int width = Viewport.Frame.Width; + int height = Viewport.Frame.Height; + double frequency = 4 * Math.PI / width; // Double the frequency + + for (int x = 0; x < width; x++) + { + int y = (int)((height) / 2 * (1 + Math.Sin (frequency * x))); // Decrease amplitude + BouncingScene.AddFrame ("O", 1); + } + + for (int x = width - 1; x >= 0; x--) + { + int y = (int)((height) / 2 * (1 + Math.Sin (frequency * x))); // Decrease amplitude + BouncingScene.AddFrame ("O", 1); + } + } + + private void CreateMotionPath () + { + int width = Viewport.Frame.Width; + int height = Viewport.Frame.Height; + double frequency = 4 * Math.PI / width; // Double the frequency + + var path = Character.Motion.CreatePath ("sineWavePath", speed: 1, loop: true); + + for (int x = 0; x < width; x++) + { + int y = (int)((height) / 2 * (1 + Math.Sin (frequency * x))); // Decrease amplitude + path.AddWaypoint (new Waypoint ($"waypoint_{x}", new Coord (x, y))); + } + + for (int x = width - 1; x >= 0; x--) + { + int y = (int)((height) / 2 * (1 + Math.Sin (frequency * x))); // Decrease amplitude + path.AddWaypoint (new Waypoint ($"waypoint_{x}", new Coord (x, y))); + } + + Character.Motion.ActivatePath (path); + } + + public void Start () + { + Animation.ActivateScene (BouncingScene); + new Thread (() => + { + while (true) + { + Thread.Sleep (10); // Adjust the speed of animation + Character.Tick (); + + Application.Invoke (() => Viewport.SetNeedsDisplay ()); + } + }) + { IsBackground = true }.Start (); + } + + public void Draw () + { + Application.Driver.SetAttribute (Viewport.ColorScheme.Normal); + Viewport.AddRune (Character.Motion.CurrentCoord.Column, Character.Motion.CurrentCoord.Row, new Rune ('O')); + } +} + From 3171df8e0212c0a44a9676b490aa5d0351f227c7 Mon Sep 17 00:00:00 2001 From: tznind Date: Sun, 7 Jul 2024 04:41:34 +0100 Subject: [PATCH 10/30] LineCanvas support for gradient fill --- Terminal.Gui/Drawing/FillPair.cs | 29 ++++++++++ Terminal.Gui/Drawing/IFill.cs | 15 +++++ Terminal.Gui/Drawing/LineCanvas.cs | 5 +- Terminal.Gui/TextEffects/ArgValidators.cs | 17 ++---- Terminal.Gui/TextEffects/BaseEffect.cs | 4 +- Terminal.Gui/TextEffects/New/GradientFill.cs | 31 +++++++++++ Terminal.Gui/TextEffects/New/SolidFill.cs | 19 +++++++ Terminal.Gui/TextEffects/Terminal.cs | 4 +- UICatalog/Scenarios/TextEffectsScenario.cs | 58 +++++++++++++++++++- 9 files changed, 164 insertions(+), 18 deletions(-) create mode 100644 Terminal.Gui/Drawing/FillPair.cs create mode 100644 Terminal.Gui/Drawing/IFill.cs create mode 100644 Terminal.Gui/TextEffects/New/GradientFill.cs create mode 100644 Terminal.Gui/TextEffects/New/SolidFill.cs diff --git a/Terminal.Gui/Drawing/FillPair.cs b/Terminal.Gui/Drawing/FillPair.cs new file mode 100644 index 0000000000..41eb2b426d --- /dev/null +++ b/Terminal.Gui/Drawing/FillPair.cs @@ -0,0 +1,29 @@ + +using Terminal.Gui.TextEffects; + +namespace Terminal.Gui; + + +/// +/// Describes a pair of which cooperate in creating +/// . One gives foreground color while other gives background. +/// +public class FillPair +{ + public FillPair (GradientFill fore, SolidFill back) + { + Foreground = fore; + Background = back; + } + + IFill Foreground { get; set; } + IFill Background { get; set; } + + internal Attribute? GetAttribute (Point point) + { + return new Attribute ( + Foreground.GetColor (point), + Background.GetColor (point) + ); + } +} diff --git a/Terminal.Gui/Drawing/IFill.cs b/Terminal.Gui/Drawing/IFill.cs new file mode 100644 index 0000000000..8f81d305a7 --- /dev/null +++ b/Terminal.Gui/Drawing/IFill.cs @@ -0,0 +1,15 @@ + +namespace Terminal.Gui; + +/// +/// Describes an area fill (e.g. solid color or gradient). +/// +public interface IFill +{ + /// + /// Returns the color that should be used at the given point + /// + /// + /// + Color GetColor (Point point); +} \ No newline at end of file diff --git a/Terminal.Gui/Drawing/LineCanvas.cs b/Terminal.Gui/Drawing/LineCanvas.cs index b1e0ced13b..2fa0d8a9e7 100644 --- a/Terminal.Gui/Drawing/LineCanvas.cs +++ b/Terminal.Gui/Drawing/LineCanvas.cs @@ -4,6 +4,7 @@ namespace Terminal.Gui; /// Facilitates box drawing and line intersection detection and rendering. Does not support diagonal lines. public class LineCanvas : IDisposable { + public FillPair? Fill { get; set; } private readonly List _lines = []; private readonly Dictionary _runeResolvers = new () @@ -324,7 +325,9 @@ private void ConfigurationManager_Applied (object? sender, ConfigurationManagerE /// private bool Exactly (HashSet intersects, params IntersectionType [] types) { return intersects.SetEquals (types); } - private Attribute? GetAttributeForIntersects (IntersectionDefinition? [] intersects) { return intersects [0]!.Line.Attribute; } + private Attribute? GetAttributeForIntersects (IntersectionDefinition? [] intersects) { + return Fill != null ? Fill.GetAttribute(intersects [0]!.Point): + intersects [0]!.Line.Attribute; } private Cell? GetCellForIntersects (ConsoleDriver driver, IntersectionDefinition? [] intersects) { diff --git a/Terminal.Gui/TextEffects/ArgValidators.cs b/Terminal.Gui/TextEffects/ArgValidators.cs index 4e31e7a511..4070a56ae3 100644 --- a/Terminal.Gui/TextEffects/ArgValidators.cs +++ b/Terminal.Gui/TextEffects/ArgValidators.cs @@ -114,24 +114,17 @@ public static float Parse (string arg) } } -public enum GradientDirection -{ - Horizontal, - Vertical, - Diagonal, - Radial -} public static class GradientDirectionParser { - public static GradientDirection Parse (string arg) + public static Gradient.Direction Parse (string arg) { return arg.ToLower () switch { - "horizontal" => GradientDirection.Horizontal, - "vertical" => GradientDirection.Vertical, - "diagonal" => GradientDirection.Diagonal, - "radial" => GradientDirection.Radial, + "horizontal" => Gradient.Direction.Horizontal, + "vertical" => Gradient.Direction.Vertical, + "diagonal" => Gradient.Direction.Diagonal, + "radial" => Gradient.Direction.Radial, _ => throw new ArgumentException ($"invalid gradient direction: '{arg}' is not a valid gradient direction. Choices are diagonal, horizontal, vertical, or radial."), }; } diff --git a/Terminal.Gui/TextEffects/BaseEffect.cs b/Terminal.Gui/TextEffects/BaseEffect.cs index e10699a9a3..f8a710302d 100644 --- a/Terminal.Gui/TextEffects/BaseEffect.cs +++ b/Terminal.Gui/TextEffects/BaseEffect.cs @@ -3,7 +3,7 @@ public abstract class BaseEffectIterator where T : EffectConfig, new() { protected T Config { get; set; } - protected Terminal Terminal { get; set; } + protected TerminalA Terminal { get; set; } protected List ActiveCharacters { get; set; } = new List (); protected BaseEffect Effect { get; } @@ -14,7 +14,7 @@ public BaseEffectIterator (BaseEffect effect) { Effect = effect; Config = effect.EffectConfig; - Terminal = new Terminal (effect.InputData, effect.TerminalConfig); + Terminal = new TerminalA (effect.InputData, effect.TerminalConfig); } diff --git a/Terminal.Gui/TextEffects/New/GradientFill.cs b/Terminal.Gui/TextEffects/New/GradientFill.cs new file mode 100644 index 0000000000..e173514693 --- /dev/null +++ b/Terminal.Gui/TextEffects/New/GradientFill.cs @@ -0,0 +1,31 @@ +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Terminal.Gui.TextEffects; + +/// +/// Implementation of that uses a color gradient (including +/// radial, diagonal etc). +/// +public class GradientFill : IFill +{ + private Dictionary _map; + + public GradientFill (Rectangle area, Gradient gradient, Gradient.Direction direction) + { + _map = + gradient.BuildCoordinateColorMapping (area.Height, area.Width, direction) + .ToDictionary( + (k)=> new Point(k.Key.Column,k.Key.Row), + (v)=> new Terminal.Gui.Color (v.Value.R, v.Value.G, v.Value.B)); + } + + public Terminal.Gui.Color GetColor (Point point) + { + return _map [point]; + } +} diff --git a/Terminal.Gui/TextEffects/New/SolidFill.cs b/Terminal.Gui/TextEffects/New/SolidFill.cs new file mode 100644 index 0000000000..4bcc174f58 --- /dev/null +++ b/Terminal.Gui/TextEffects/New/SolidFill.cs @@ -0,0 +1,19 @@ +namespace Terminal.Gui.TextEffects; + + +/// +/// implementation that uses a solid color for all points +/// +public class SolidFill : IFill +{ + readonly Terminal.Gui.Color _color; + + public SolidFill (Terminal.Gui.Color color) + { + _color = color; + } + public Gui.Color GetColor (Point point) + { + return _color; + } +} diff --git a/Terminal.Gui/TextEffects/Terminal.cs b/Terminal.Gui/TextEffects/Terminal.cs index 9302f0d47c..08522069d7 100644 --- a/Terminal.Gui/TextEffects/Terminal.cs +++ b/Terminal.Gui/TextEffects/Terminal.cs @@ -57,13 +57,13 @@ public Coord GetRandomCoord (bool outsideScope = false) } } -public class Terminal +public class TerminalA { public TerminalConfig Config { get; } public Canvas Canvas { get; private set; } private Dictionary CharacterByInputCoord = new Dictionary (); - public Terminal (string input, TerminalConfig config = null) + public TerminalA (string input, TerminalConfig config = null) { Config = config ?? new TerminalConfig (); var dimensions = GetTerminalDimensions (); diff --git a/UICatalog/Scenarios/TextEffectsScenario.cs b/UICatalog/Scenarios/TextEffectsScenario.cs index d5e914c776..5cfd265f25 100644 --- a/UICatalog/Scenarios/TextEffectsScenario.cs +++ b/UICatalog/Scenarios/TextEffectsScenario.cs @@ -15,6 +15,8 @@ namespace UICatalog.Scenarios; [ScenarioCategory ("Colors")] public class TextEffectsScenario : Scenario { + private TabView tabView; + public override void Main () { Application.Init (); @@ -24,8 +26,30 @@ public override void Main () Height = Dim.Fill (), }; + w.Loaded += (s, e) => + { + SetupGradientLineCanvas (w, w.Frame.Size); + // TODO: Does not work + // SetupGradientLineCanvas (tabView, tabView.Frame.Size); + }; + w.SizeChanging += (s,e)=> + { + SetupGradientLineCanvas (w, e.Size); + // TODO: Does not work + //SetupGradientLineCanvas (tabView, tabView.Frame.Size); + }; + + w.ColorScheme = new ColorScheme + { + Normal = new Terminal.Gui.Attribute (ColorName.White, ColorName.Black), + Focus = new Terminal.Gui.Attribute (ColorName.Black,ColorName.White), + HotNormal = new Terminal.Gui.Attribute (ColorName.White, ColorName.Black), + HotFocus = new Terminal.Gui.Attribute (ColorName.White, ColorName.Black), + Disabled = new Terminal.Gui.Attribute (ColorName.Gray, ColorName.Black) + }; + // Creates a window that occupies the entire terminal with a title. - var tabView = new TabView () + tabView = new TabView () { Width = Dim.Fill (), Height = Dim.Fill (), @@ -61,6 +85,38 @@ public override void Main () Application.Shutdown (); this.Dispose (); } + + + private void SetupGradientLineCanvas (View w, Size? size) + { + GetAppealingGradientColors (out var stops, out var steps); + + var g = new Gradient (stops, steps); + + var fore = new GradientFill ( + new Rectangle (0, 0, size.Value.Width, size.Value.Height), g, Gradient.Direction.Diagonal); + var back = new SolidFill (new Terminal.Gui.Color (ColorName.Black)); + + w.LineCanvas.Fill = new FillPair ( + fore, + back); + } + + private void GetAppealingGradientColors (out List stops, out List steps) + { + // Define the colors of the gradient stops with more appealing colors + stops = new List + { + Color.FromRgb(0, 128, 255), // Bright Blue + Color.FromRgb(0, 255, 128), // Bright Green + Color.FromRgb(255, 255, 0), // Bright Yellow + Color.FromRgb(255, 128, 0), // Bright Orange + Color.FromRgb(255, 0, 128) // Bright Pink + }; + + // Define the number of steps between each color for smoother transitions + steps = new List { 15, 15, 15, 15 }; // 15 steps between each color + } } From c1e82e63e4469824b5f1121c97ae9ef01ea1060b Mon Sep 17 00:00:00 2001 From: tznind Date: Sun, 7 Jul 2024 05:34:50 +0100 Subject: [PATCH 11/30] Add tests, corners not working properly for some reason --- Terminal.Gui/TextEffects/New/GradientFill.cs | 16 ++- UICatalog/Scenarios/TextEffectsScenario.cs | 22 ++-- .../TextEffects/New/GradientFillTests.cs | 111 ++++++++++++++++++ 3 files changed, 136 insertions(+), 13 deletions(-) create mode 100644 UnitTests/TextEffects/New/GradientFillTests.cs diff --git a/Terminal.Gui/TextEffects/New/GradientFill.cs b/Terminal.Gui/TextEffects/New/GradientFill.cs index e173514693..a6d69f4e30 100644 --- a/Terminal.Gui/TextEffects/New/GradientFill.cs +++ b/Terminal.Gui/TextEffects/New/GradientFill.cs @@ -17,15 +17,19 @@ public class GradientFill : IFill public GradientFill (Rectangle area, Gradient gradient, Gradient.Direction direction) { - _map = + _map = gradient.BuildCoordinateColorMapping (area.Height, area.Width, direction) - .ToDictionary( - (k)=> new Point(k.Key.Column,k.Key.Row), - (v)=> new Terminal.Gui.Color (v.Value.R, v.Value.G, v.Value.B)); + .ToDictionary ( + (k) => new Point (k.Key.Column, k.Key.Row), + (v) => new Terminal.Gui.Color (v.Value.R, v.Value.G, v.Value.B)); } public Terminal.Gui.Color GetColor (Point point) { - return _map [point]; + if (_map.TryGetValue (point, out var color)) + { + return color; + } + return new Terminal.Gui.Color (0, 0, 0); // Default to black if point not found } -} +} \ No newline at end of file diff --git a/UICatalog/Scenarios/TextEffectsScenario.cs b/UICatalog/Scenarios/TextEffectsScenario.cs index 5cfd265f25..b8e65b8c2f 100644 --- a/UICatalog/Scenarios/TextEffectsScenario.cs +++ b/UICatalog/Scenarios/TextEffectsScenario.cs @@ -24,6 +24,7 @@ public override void Main () { Width = Dim.Fill(), Height = Dim.Fill (), + Title = "Text Effects Scenario" }; w.Loaded += (s, e) => @@ -34,7 +35,11 @@ public override void Main () }; w.SizeChanging += (s,e)=> { - SetupGradientLineCanvas (w, e.Size); + if(e.Size.HasValue) + { + SetupGradientLineCanvas (w, e.Size.Value); + } + // TODO: Does not work //SetupGradientLineCanvas (tabView, tabView.Frame.Size); }; @@ -87,14 +92,14 @@ public override void Main () } - private void SetupGradientLineCanvas (View w, Size? size) + private void SetupGradientLineCanvas (View w, Size size) { - GetAppealingGradientColors (out var stops, out var steps); + GetAppealingGradientColors (size, out var stops, out var steps); var g = new Gradient (stops, steps); var fore = new GradientFill ( - new Rectangle (0, 0, size.Value.Width, size.Value.Height), g, Gradient.Direction.Diagonal); + new Rectangle (0, 0, size.Width, size.Height), g, Gradient.Direction.Diagonal); var back = new SolidFill (new Terminal.Gui.Color (ColorName.Black)); w.LineCanvas.Fill = new FillPair ( @@ -102,7 +107,7 @@ private void SetupGradientLineCanvas (View w, Size? size) back); } - private void GetAppealingGradientColors (out List stops, out List steps) + private void GetAppealingGradientColors (Size size, out List stops, out List steps) { // Define the colors of the gradient stops with more appealing colors stops = new List @@ -114,8 +119,11 @@ private void GetAppealingGradientColors (out List stops, out List st Color.FromRgb(255, 0, 128) // Bright Pink }; - // Define the number of steps between each color for smoother transitions - steps = new List { 15, 15, 15, 15 }; // 15 steps between each color + // Calculate the number of steps based on the size + int maxSteps = Math.Max (size.Width, size.Height); + + // Define the number of steps between each color for smoother transitions + steps = new List { maxSteps / 4, maxSteps / 4, maxSteps / 4, maxSteps / 4 }; } } diff --git a/UnitTests/TextEffects/New/GradientFillTests.cs b/UnitTests/TextEffects/New/GradientFillTests.cs new file mode 100644 index 0000000000..400425dc50 --- /dev/null +++ b/UnitTests/TextEffects/New/GradientFillTests.cs @@ -0,0 +1,111 @@ +namespace Terminal.Gui.TextEffects.Tests; + +public class GradientFillTests +{ + private Gradient _gradient; + + public GradientFillTests () + { + // Define the colors of the gradient stops + var stops = new List + { + Color.FromRgb(255, 0, 0), // Red + Color.FromRgb(0, 0, 255) // Blue + }; + + // Define the number of steps between each color + var steps = new List { 10 }; // 10 steps between Red -> Blue + + _gradient = new Gradient (stops, steps, loop: false); + } + + [Fact] + public void TestGradientFillCorners () + { + var area = new Rectangle (0, 0, 10, 10); + var gradientFill = new GradientFill (area, _gradient, Gradient.Direction.Diagonal); + + // Test the corners + var topLeft = new Point (0, 0); + var topRight = new Point (area.Width - 1, 0); + var bottomLeft = new Point (0, area.Height - 1); + var bottomRight = new Point (area.Width - 1, area.Height - 1); + + var topLeftColor = gradientFill.GetColor (topLeft); + var topRightColor = gradientFill.GetColor (topRight); + var bottomLeftColor = gradientFill.GetColor (bottomLeft); + var bottomRightColor = gradientFill.GetColor (bottomRight); + + // Validate the colors at the corners + Assert.NotNull (topLeftColor); + Assert.NotNull (topRightColor); + Assert.NotNull (bottomLeftColor); + Assert.NotNull (bottomRightColor); + + // Expected colors + var expectedTopLeftColor = new Terminal.Gui.Color (255, 0, 0); // Red + var expectedBottomRightColor = new Terminal.Gui.Color (0, 0, 255); // Blue + + Assert.Equal (expectedTopLeftColor, topLeftColor); + Assert.Equal (expectedBottomRightColor, bottomRightColor); + + // Additional checks can be added to verify the exact expected colors if known + Console.WriteLine ($"Top-left: {topLeftColor}"); + Console.WriteLine ($"Top-right: {topRightColor}"); + Console.WriteLine ($"Bottom-left: {bottomLeftColor}"); + Console.WriteLine ($"Bottom-right: {bottomRightColor}"); + } + + [Fact] + public void TestGradientFillColorTransition () + { + var area = new Rectangle (0, 0, 10, 10); + var gradientFill = new GradientFill (area, _gradient, Gradient.Direction.Diagonal); + + for (int row = 0; row < area.Height; row++) + { + int previousRed = 255; + int previousBlue = 0; + + for (int col = 0; col < area.Width; col++) + { + var point = new Point (col, row); + var color = gradientFill.GetColor (point); + + // Ensure color is not null + Assert.NotNull (color); + + // Check if the current color is 'more blue' and 'less red' as it goes right and down + Assert.True (color.R <= previousRed, $"Failed at ({col}, {row}): {color.R} > {previousRed}"); + Assert.True (color.B >= previousBlue, $"Failed at ({col}, {row}): {color.B} < {previousBlue}"); + + // Update the previous color values for the next iteration + previousRed = color.R; + previousBlue = color.B; + } + } + + for (int col = 0; col < area.Width; col++) + { + int previousRed = 255; + int previousBlue = 0; + + for (int row = 0; row < area.Height; row++) + { + var point = new Point (col, row); + var color = gradientFill.GetColor (point); + + // Ensure color is not null + Assert.NotNull (color); + + // Check if the current color is 'more blue' and 'less red' as it goes right and down + Assert.True (color.R <= previousRed, $"Failed at ({col}, {row}): {color.R} > {previousRed}"); + Assert.True (color.B >= previousBlue, $"Failed at ({col}, {row}): {color.B} < {previousBlue}"); + + // Update the previous color values for the next iteration + previousRed = color.R; + previousBlue = color.B; + } + } + } +} From a5c1d73c55dca19452d5d8ecc75173bcf88452c0 Mon Sep 17 00:00:00 2001 From: tznind Date: Sun, 7 Jul 2024 09:32:34 +0100 Subject: [PATCH 12/30] Fix bug in StraightLine as it calculates intersections --- Terminal.Gui/Drawing/StraightLine.cs | 8 +-- UICatalog/Scenarios/TextEffectsScenario.cs | 65 ++++++++++++++++++---- 2 files changed, 59 insertions(+), 14 deletions(-) diff --git a/Terminal.Gui/Drawing/StraightLine.cs b/Terminal.Gui/Drawing/StraightLine.cs index 9a2785f0f4..f8dfb2d887 100644 --- a/Terminal.Gui/Drawing/StraightLine.cs +++ b/Terminal.Gui/Drawing/StraightLine.cs @@ -114,7 +114,7 @@ IntersectionType typeWhenPositive if (StartsAt (x, y)) { return new IntersectionDefinition ( - Start, + new Point (x, y), GetTypeByLength ( IntersectionType.StartLeft, IntersectionType.PassOverHorizontal, @@ -127,7 +127,7 @@ IntersectionType typeWhenPositive if (EndsAt (x, y)) { return new IntersectionDefinition ( - Start, + new Point (x, y), Length < 0 ? IntersectionType.StartRight : IntersectionType.StartLeft, this ); @@ -158,7 +158,7 @@ IntersectionType typeWhenPositive if (StartsAt (x, y)) { return new IntersectionDefinition ( - Start, + new Point (x, y), GetTypeByLength ( IntersectionType.StartUp, IntersectionType.PassOverVertical, @@ -171,7 +171,7 @@ IntersectionType typeWhenPositive if (EndsAt (x, y)) { return new IntersectionDefinition ( - Start, + new Point (x, y), Length < 0 ? IntersectionType.StartDown : IntersectionType.StartUp, this ); diff --git a/UICatalog/Scenarios/TextEffectsScenario.cs b/UICatalog/Scenarios/TextEffectsScenario.cs index b8e65b8c2f..920f288799 100644 --- a/UICatalog/Scenarios/TextEffectsScenario.cs +++ b/UICatalog/Scenarios/TextEffectsScenario.cs @@ -4,7 +4,6 @@ using System.Threading; using Terminal.Gui; using Terminal.Gui.TextEffects; -using static UICatalog.Scenario; using Color = Terminal.Gui.TextEffects.Color; using Animation = Terminal.Gui.TextEffects.Animation; @@ -94,7 +93,7 @@ public override void Main () private void SetupGradientLineCanvas (View w, Size size) { - GetAppealingGradientColors (size, out var stops, out var steps); + GetAppealingGradientColors (out var stops, out var steps); var g = new Gradient (stops, steps); @@ -107,7 +106,7 @@ private void SetupGradientLineCanvas (View w, Size size) back); } - private void GetAppealingGradientColors (Size size, out List stops, out List steps) + public static void GetAppealingGradientColors (out List stops, out List steps) { // Define the colors of the gradient stops with more appealing colors stops = new List @@ -119,11 +118,8 @@ private void GetAppealingGradientColors (Size size, out List stops, out L Color.FromRgb(255, 0, 128) // Bright Pink }; - // Calculate the number of steps based on the size - int maxSteps = Math.Max (size.Width, size.Height); - - // Define the number of steps between each color for smoother transitions - steps = new List { maxSteps / 4, maxSteps / 4, maxSteps / 4, maxSteps / 4 }; + // Define the number of steps between each color for smoother transitions + steps = new List { 15,15, 15, 15 }; } } @@ -144,14 +140,14 @@ public override void OnDrawContent (Rectangle viewport) DrawGradientArea (Gradient.Direction.Horizontal, x, y); DrawGradientArea (Gradient.Direction.Vertical, x + 32, y); DrawGradientArea (Gradient.Direction.Radial, x + 64, y); - DrawGradientArea (Gradient.Direction.Diagonal, x + 96, y); + //DrawGradientArea (Gradient.Direction.Diagonal, x + 96, y); } else // Enough space, render in two lines { DrawGradientArea (Gradient.Direction.Horizontal, x, y); DrawGradientArea (Gradient.Direction.Vertical, x + 32, y); DrawGradientArea (Gradient.Direction.Radial, x, y + 17); - DrawGradientArea (Gradient.Direction.Diagonal, x + 32, y + 17); + //DrawGradientArea (Gradient.Direction.Diagonal, x + 32, y + 17); } } @@ -250,11 +246,26 @@ internal class BallsView : View { private Ball? _ball; private bool _resized; + private LineCanvas lc; + private Gradient gradient; protected override void OnViewportChanged (DrawEventArgs e) { base.OnViewportChanged (e); _resized = true; + + lc = new LineCanvas (new []{ + new StraightLine(new System.Drawing.Point(0,0),10,Orientation.Horizontal,LineStyle.Single), + + }); + TextEffectsScenario.GetAppealingGradientColors (out var stops, out var steps); + gradient = new Gradient (stops, steps); + var fill = new FillPair ( + new GradientFill (new System.Drawing.Rectangle (0, 0, 10, 0), gradient , Gradient.Direction.Horizontal), + new SolidFill(Terminal.Gui.Color.Black) + ); + lc.Fill = fill; + } public override void OnDrawContent (Rectangle viewport) @@ -269,8 +280,42 @@ public override void OnDrawContent (Rectangle viewport) } _ball?.Draw (); + + foreach(var map in lc.GetCellMap()) + { + Driver.SetAttribute (map.Value.Value.Attribute.Value); + AddRune (map.Key.X, map.Key.Y, map.Value.Value.Rune); + } + + for (int x = 0; x < 10; x++) + { + double fraction = (double)x / 10; + Color color = gradient.GetColorAtFraction (fraction); + + SetColor (color); + + AddRune (x, 2, new Rune ('█')); + } + + var map2 = gradient.BuildCoordinateColorMapping (0,10,Gradient.Direction.Horizontal); + + for (int x = 0; x < map2.Count; x++) + { + SetColor (map2[new Coord(x,0)]); + + AddRune (x, 3, 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 + } public class Ball { public Animation Animation { get; private set; } From e80f61b17137c46a193a57f5baae3cc46cbcd822 Mon Sep 17 00:00:00 2001 From: tznind Date: Sun, 7 Jul 2024 09:34:08 +0100 Subject: [PATCH 13/30] Restore diagonal demo --- UICatalog/Scenarios/TextEffectsScenario.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/UICatalog/Scenarios/TextEffectsScenario.cs b/UICatalog/Scenarios/TextEffectsScenario.cs index 920f288799..2df1131e9f 100644 --- a/UICatalog/Scenarios/TextEffectsScenario.cs +++ b/UICatalog/Scenarios/TextEffectsScenario.cs @@ -140,14 +140,14 @@ public override void OnDrawContent (Rectangle viewport) DrawGradientArea (Gradient.Direction.Horizontal, x, y); DrawGradientArea (Gradient.Direction.Vertical, x + 32, y); DrawGradientArea (Gradient.Direction.Radial, x + 64, y); - //DrawGradientArea (Gradient.Direction.Diagonal, x + 96, y); + DrawGradientArea (Gradient.Direction.Diagonal, x + 96, y); } else // Enough space, render in two lines { DrawGradientArea (Gradient.Direction.Horizontal, x, y); DrawGradientArea (Gradient.Direction.Vertical, x + 32, y); DrawGradientArea (Gradient.Direction.Radial, x, y + 17); - //DrawGradientArea (Gradient.Direction.Diagonal, x + 32, y + 17); + DrawGradientArea (Gradient.Direction.Diagonal, x + 32, y + 17); } } From cbcf4b5186e2be1cebfb2b96589c7a1cf59c40f3 Mon Sep 17 00:00:00 2001 From: tznind Date: Sun, 7 Jul 2024 10:18:28 +0100 Subject: [PATCH 14/30] Remove everything except gradient --- Terminal.Gui/Drawing/FillPair.cs | 2 +- .../Graphics.cs => Drawing/Gradient.cs} | 77 +-- Terminal.Gui/Drawing/GradientFill.cs | 24 + .../{TextEffects/New => Drawing}/SolidFill.cs | 8 +- Terminal.Gui/TextEffects/Animation.cs | 502 ------------------ Terminal.Gui/TextEffects/ArgValidators.cs | 266 ---------- Terminal.Gui/TextEffects/BaseCharacter.cs | 109 ---- Terminal.Gui/TextEffects/BaseEffect.cs | 60 --- Terminal.Gui/TextEffects/Easing.cs | 303 ----------- Terminal.Gui/TextEffects/EffectTemplate.cs | 13 - Terminal.Gui/TextEffects/Effects/Beams.cs | 241 --------- Terminal.Gui/TextEffects/Geometry.cs | 137 ----- Terminal.Gui/TextEffects/HexTerm.cs | 94 ---- Terminal.Gui/TextEffects/Motion.cs | 253 --------- Terminal.Gui/TextEffects/New/GradientFill.cs | 35 -- Terminal.Gui/TextEffects/Terminal.cs | 106 ---- UICatalog/Scenarios/TextEffectsScenario.cs | 289 +--------- UnitTests/TextEffects/AnimationTests.cs | 191 ------- .../TextEffects/New/GradientFillTests.cs | 8 +- 19 files changed, 65 insertions(+), 2653 deletions(-) rename Terminal.Gui/{TextEffects/Graphics.cs => Drawing/Gradient.cs} (63%) create mode 100644 Terminal.Gui/Drawing/GradientFill.cs rename Terminal.Gui/{TextEffects/New => Drawing}/SolidFill.cs (55%) delete mode 100644 Terminal.Gui/TextEffects/Animation.cs delete mode 100644 Terminal.Gui/TextEffects/ArgValidators.cs delete mode 100644 Terminal.Gui/TextEffects/BaseCharacter.cs delete mode 100644 Terminal.Gui/TextEffects/BaseEffect.cs delete mode 100644 Terminal.Gui/TextEffects/Easing.cs delete mode 100644 Terminal.Gui/TextEffects/EffectTemplate.cs delete mode 100644 Terminal.Gui/TextEffects/Effects/Beams.cs delete mode 100644 Terminal.Gui/TextEffects/Geometry.cs delete mode 100644 Terminal.Gui/TextEffects/HexTerm.cs delete mode 100644 Terminal.Gui/TextEffects/Motion.cs delete mode 100644 Terminal.Gui/TextEffects/New/GradientFill.cs delete mode 100644 Terminal.Gui/TextEffects/Terminal.cs delete mode 100644 UnitTests/TextEffects/AnimationTests.cs diff --git a/Terminal.Gui/Drawing/FillPair.cs b/Terminal.Gui/Drawing/FillPair.cs index 41eb2b426d..f51ceec658 100644 --- a/Terminal.Gui/Drawing/FillPair.cs +++ b/Terminal.Gui/Drawing/FillPair.cs @@ -1,5 +1,5 @@  -using Terminal.Gui.TextEffects; +using Terminal.Gui.Drawing; namespace Terminal.Gui; diff --git a/Terminal.Gui/TextEffects/Graphics.cs b/Terminal.Gui/Drawing/Gradient.cs similarity index 63% rename from Terminal.Gui/TextEffects/Graphics.cs rename to Terminal.Gui/Drawing/Gradient.cs index 03fb2059ab..b5d58e34a4 100644 --- a/Terminal.Gui/TextEffects/Graphics.cs +++ b/Terminal.Gui/Drawing/Gradient.cs @@ -1,59 +1,8 @@ -namespace Terminal.Gui.TextEffects; +namespace Terminal.Gui; using System; using System.Collections.Generic; using System.Linq; -public class Color -{ - public string RgbColor { get; private set; } - public int? XtermColor { get; private set; } - - public Color (string rgbColor) - { - if (!ColorUtils.IsValidHexColor (rgbColor)) - throw new ArgumentException ("Invalid RGB hex color format."); - - RgbColor = rgbColor.StartsWith ("#") ? rgbColor.Substring (1).ToUpper () : rgbColor.ToUpper (); - XtermColor = ColorUtils.HexToXterm (RgbColor); // Convert RGB to XTerm-256 - } - - public Color (int xtermColor) - { - if (!ColorUtils.IsValidXtermColor (xtermColor)) - throw new ArgumentException ("Invalid XTerm-256 color code."); - - XtermColor = xtermColor; - 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); - - public (int R, int G, int B) GetRgbInts () - { - return ( - Convert.ToInt32 (RgbColor.Substring (0, 2), 16), - Convert.ToInt32 (RgbColor.Substring (2, 2), 16), - Convert.ToInt32 (RgbColor.Substring (4, 2), 16) - ); - } - - public override string ToString () => $"#{RgbColor}"; - - public static Color FromRgb (int r, int g, int 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."); - - // Convert RGB values to a hexadecimal string - string rgbColor = $"#{r:X2}{g:X2}{b:X2}"; - - // Create and return a new Color instance using the hexadecimal string - return new Color (rgbColor); - } -} - public class Gradient { public List Spectrum { get; private set; } @@ -127,13 +76,13 @@ private IEnumerable InterpolateColors (Color start, Color end, int 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 new Color (r, g, b); } } - public Dictionary BuildCoordinateColorMapping (int maxRow, int maxColumn, Direction direction) + public Dictionary BuildCoordinateColorMapping (int maxRow, int maxColumn, Direction direction) { - var gradientMapping = new Dictionary (); + var gradientMapping = new Dictionary (); switch (direction) { @@ -144,7 +93,7 @@ public Dictionary BuildCoordinateColorMapping (int maxRow, int max Color color = GetColorAtFraction (fraction); for (int col = 0; col <= maxColumn; col++) { - gradientMapping [new Coord (col, row)] = color; + gradientMapping [new Point (col, row)] = color; } } break; @@ -156,7 +105,7 @@ public Dictionary BuildCoordinateColorMapping (int maxRow, int max Color color = GetColorAtFraction (fraction); for (int row = 0; row <= maxRow; row++) { - gradientMapping [new Coord (col, row)] = color; + gradientMapping [new Point (col, row)] = color; } } break; @@ -166,9 +115,9 @@ public Dictionary BuildCoordinateColorMapping (int maxRow, int max { for (int col = 0; col <= maxColumn; col++) { - double distanceFromCenter = FindNormalizedDistanceFromCenter (maxRow, maxColumn, new Coord (col, row)); + double distanceFromCenter = FindNormalizedDistanceFromCenter (maxRow, maxColumn, new Point (col, row)); Color color = GetColorAtFraction (distanceFromCenter); - gradientMapping [new Coord (col, row)] = color; + gradientMapping [new Point (col, row)] = color; } } break; @@ -178,9 +127,9 @@ public Dictionary BuildCoordinateColorMapping (int maxRow, int max { for (int col = 0; col <= maxColumn; col++) { - double fraction = ((double)row * 2 + col) / ((maxRow * 2) + maxColumn); + double fraction = ((double)row * 2 + col) / (maxRow * 2 + maxColumn); Color color = GetColorAtFraction (fraction); - gradientMapping [new Coord (col, row)] = color; + gradientMapping [new Point (col, row)] = color; } } break; @@ -189,12 +138,12 @@ public Dictionary BuildCoordinateColorMapping (int maxRow, int max return gradientMapping; } - private double FindNormalizedDistanceFromCenter (int maxRow, int maxColumn, Coord coord) + private double FindNormalizedDistanceFromCenter (int maxRow, int maxColumn, Point coord) { double centerX = maxColumn / 2.0; double centerY = maxRow / 2.0; - double dx = coord.Column - centerX; - double dy = coord.Row - centerY; + double dx = coord.X - centerX; + double dy = coord.Y - centerY; double distance = Math.Sqrt (dx * dx + dy * dy); double maxDistance = Math.Sqrt (centerX * centerX + centerY * centerY); return distance / maxDistance; diff --git a/Terminal.Gui/Drawing/GradientFill.cs b/Terminal.Gui/Drawing/GradientFill.cs new file mode 100644 index 0000000000..d0bf163daf --- /dev/null +++ b/Terminal.Gui/Drawing/GradientFill.cs @@ -0,0 +1,24 @@ +namespace Terminal.Gui; + +/// +/// Implementation of that uses a color gradient (including +/// radial, diagonal etc). +/// +public class GradientFill : IFill +{ + private Dictionary _map; + + public GradientFill (Rectangle area, Gradient gradient, Gradient.Direction direction) + { + _map = gradient.BuildCoordinateColorMapping (area.Height, area.Width, direction); + } + + public Color GetColor (Point point) + { + if (_map.TryGetValue (point, out var color)) + { + return color; + } + return new Color (0, 0, 0); // Default to black if point not found + } +} \ No newline at end of file diff --git a/Terminal.Gui/TextEffects/New/SolidFill.cs b/Terminal.Gui/Drawing/SolidFill.cs similarity index 55% rename from Terminal.Gui/TextEffects/New/SolidFill.cs rename to Terminal.Gui/Drawing/SolidFill.cs index 4bcc174f58..202cec57c2 100644 --- a/Terminal.Gui/TextEffects/New/SolidFill.cs +++ b/Terminal.Gui/Drawing/SolidFill.cs @@ -1,4 +1,4 @@ -namespace Terminal.Gui.TextEffects; +namespace Terminal.Gui.Drawing; /// @@ -6,13 +6,13 @@ /// public class SolidFill : IFill { - readonly Terminal.Gui.Color _color; + readonly Color _color; - public SolidFill (Terminal.Gui.Color color) + public SolidFill (Color color) { _color = color; } - public Gui.Color GetColor (Point point) + public Color GetColor (Point point) { return _color; } diff --git a/Terminal.Gui/TextEffects/Animation.cs b/Terminal.Gui/TextEffects/Animation.cs deleted file mode 100644 index a96f08a9ee..0000000000 --- a/Terminal.Gui/TextEffects/Animation.cs +++ /dev/null @@ -1,502 +0,0 @@ - -using static Terminal.Gui.TextEffects.EventHandler; - -namespace Terminal.Gui.TextEffects; - -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; // 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) - { - Symbol = symbol; - Bold = bold; - Dim = dim; - Italic = italic; - Underline = underline; - Blink = blink; - Reverse = reverse; - Hidden = hidden; - Strike = strike; - Color = color; - _colorCode = colorCode; // Initialize _colorCode from the constructor argument - 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); // Use the direct color code - - 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; set; } - public int EasingCurrentStep { get; 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; - 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) - { - string charVisColor = null; - if (color != null) - { - if (NoColor) - { - charVisColor = null; - } - else if (UseXtermColors && color.XtermColor.HasValue) - { - charVisColor = color.XtermColor.Value.ToString (); - } - else if (color.RgbColor != null && XtermColorMap.ContainsKey (color.RgbColor)) - { - charVisColor = XtermColorMap [color.RgbColor].ToString (); - } - else - { - charVisColor = color.RgbColor; - } - } - - if (duration < 1) - throw new ArgumentException ("Duration must be greater than 0."); - - 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++) - { - FrameIndexMap [EasingTotalSteps] = frame; - EasingTotalSteps++; - } - } - - public CharacterVisual Activate () - { - if (Frames.Count == 0) - throw new InvalidOperationException ("Scene has no frames."); - EasingCurrentStep = 0; - return Frames [0].CharacterVisual; - } - - public CharacterVisual GetNextVisual () - { - if (Frames.Count == 0) - return null; - - var frame = Frames [0]; - if (++EasingCurrentStep >= frame.Duration) - { - 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 frame.CharacterVisual; - } - - 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 () - { - EasingCurrentStep = 0; - Frames.Clear (); - Frames.AddRange (PlayedFrames); - PlayedFrames.Clear (); - } - - public override bool Equals (object obj) - { - return obj is Scene other && SceneId == other.SceneId; - } - - 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 void SetAppearance (string symbol, Color? color = null) - { - string charVisColor = null; - if (color != null) - { - if (NoColor) - { - charVisColor = null; - } - else if (UseXtermColors) - { - charVisColor = color.XtermColor.ToString(); - } - else - { - charVisColor = color.RgbColor; - } - } - CurrentCharacterVisual = new CharacterVisual (symbol, color: color, colorCode: charVisColor); - } - - public static Color RandomColor () - { - var random = new Random (); - var colorHex = random.Next (0, 0xFFFFFF).ToString ("X6"); - return new Color (colorHex); - } - - public static Color AdjustColorBrightness (Color color, float brightness) - { - float HueToRgb (float p, float q, float t) - { - if (t < 0) t += 1; - if (t > 1) t -= 1; - if (t < 1 / 6f) return p + (q - p) * 6 * t; - if (t < 1 / 2f) return q; - if (t < 2 / 3f) return p + (q - p) * (2 / 3f - t) * 6; - return p; - } - - float r = int.Parse (color.RgbColor.Substring (0, 2), System.Globalization.NumberStyles.HexNumber) / 255f; - float g = int.Parse (color.RgbColor.Substring (2, 2), System.Globalization.NumberStyles.HexNumber) / 255f; - float b = int.Parse (color.RgbColor.Substring (4, 2), System.Globalization.NumberStyles.HexNumber) / 255f; - - float max = Math.Max (r, Math.Max (g, b)); - float min = Math.Min (r, Math.Min (g, b)); - float h, s, l = (max + min) / 2f; - - if (max == min) - { - h = s = 0; // achromatic - } - else - { - float d = max - min; - s = l > 0.5f ? d / (2f - max - min) : d / (max + min); - if (max == r) - { - h = (g - b) / d + (g < b ? 6 : 0); - } - else if (max == g) - { - h = (b - r) / d + 2; - } - else - { - h = (r - g) / d + 4; - } - h /= 6; - } - - l = Math.Max (Math.Min (l * brightness, 1), 0); - - if (s == 0) - { - r = g = b = l; // achromatic - } - else - { - float q = l < 0.5f ? l * (1 + s) : l + s - l * s; - float p = 2 * l - q; - r = HueToRgb (p, q, h + 1 / 3f); - g = HueToRgb (p, q, h); - b = HueToRgb (p, q, h - 1 / 3f); - } - - var adjustedColor = $"{(int)(r * 255):X2}{(int)(g * 255):X2}{(int)(b * 255):X2}"; - return new Color (adjustedColor); - } - - private float EaseAnimation (EasingFunction easingFunc) - { - if (ActiveScene == null) - { - return 0; - } - float elapsedStepRatio = ActiveScene.EasingCurrentStep / (float)ActiveScene.EasingTotalSteps; - return easingFunc (elapsedStepRatio); - } - - public void StepAnimation () - { - if (ActiveScene != null && ActiveScene.Frames.Count > 0) - { - if (ActiveScene.Sync != null) - { - if (Character.Motion.ActivePath != null) - { - int sequenceIndex = 0; - if (ActiveScene.Sync == SyncMetric.Step) - { - sequenceIndex = (int)Math.Round ((ActiveScene.Frames.Count - 1) * - (Math.Max (Character.Motion.ActivePath.CurrentStep, 1) / - (float)Math.Max (Character.Motion.ActivePath.MaxSteps, 1))); - } - else if (ActiveScene.Sync == SyncMetric.Distance) - { - sequenceIndex = (int)Math.Round ((ActiveScene.Frames.Count - 1) * - (Math.Max (Math.Max (Character.Motion.ActivePath.TotalDistance, 1) - - Math.Max (Character.Motion.ActivePath.TotalDistance - - Character.Motion.ActivePath.LastDistanceReached, 1), 1) / - (float)Math.Max (Character.Motion.ActivePath.TotalDistance, 1))); - } - try - { - CurrentCharacterVisual = ActiveScene.Frames [sequenceIndex].CharacterVisual; - } - catch (IndexOutOfRangeException) - { - CurrentCharacterVisual = ActiveScene.Frames [^1].CharacterVisual; - } - } - else - { - CurrentCharacterVisual = ActiveScene.Frames [^1].CharacterVisual; - ActiveScene.PlayedFrames.AddRange (ActiveScene.Frames); - ActiveScene.Frames.Clear (); - } - } - else if (ActiveScene.Ease != null) - { - float easingFactor = EaseAnimation (ActiveScene.Ease); - int frameIndex = (int)Math.Round (easingFactor * Math.Max (ActiveScene.EasingTotalSteps - 1, 0)); - frameIndex = Math.Max (Math.Min (frameIndex, ActiveScene.EasingTotalSteps - 1), 0); - Frame frame = ActiveScene.FrameIndexMap [frameIndex]; - CurrentCharacterVisual = frame.CharacterVisual; - ActiveScene.EasingCurrentStep++; - if (ActiveScene.EasingCurrentStep == ActiveScene.EasingTotalSteps) - { - if (ActiveScene.IsLooping) - { - ActiveScene.EasingCurrentStep = 0; - } - else - { - ActiveScene.PlayedFrames.AddRange (ActiveScene.Frames); - ActiveScene.Frames.Clear (); - } - } - } - else - { - CurrentCharacterVisual = ActiveScene.GetNextVisual (); - } - if (ActiveSceneIsComplete ()) - { - var completedScene = ActiveScene; - if (!ActiveScene.IsLooping) - { - ActiveScene.ResetScene (); - ActiveScene = null; - } - Character.EventHandler.HandleEvent (Event.SceneComplete, completedScene); - } - } - } - - public void ActivateScene (Scene scene) - { - ActiveScene = scene; - ActiveSceneCurrentStep = 0; - CurrentCharacterVisual = ActiveScene.Activate (); - Character.EventHandler.HandleEvent (Event.SceneActivated, scene); - } - - public void DeactivateScene (Scene scene) - { - if (ActiveScene == scene) - { - ActiveScene = null; - } - } -} - - -// 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 - } -} diff --git a/Terminal.Gui/TextEffects/ArgValidators.cs b/Terminal.Gui/TextEffects/ArgValidators.cs deleted file mode 100644 index 4070a56ae3..0000000000 --- a/Terminal.Gui/TextEffects/ArgValidators.cs +++ /dev/null @@ -1,266 +0,0 @@ -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 static class GradientDirectionParser -{ - public static Gradient.Direction Parse (string arg) - { - return arg.ToLower () switch - { - "horizontal" => Gradient.Direction.Horizontal, - "vertical" => Gradient.Direction.Vertical, - "diagonal" => Gradient.Direction.Diagonal, - "radial" => Gradient.Direction.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 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."); - } - } -} diff --git a/Terminal.Gui/TextEffects/BaseCharacter.cs b/Terminal.Gui/TextEffects/BaseCharacter.cs deleted file mode 100644 index 886cd6b599..0000000000 --- a/Terminal.Gui/TextEffects/BaseCharacter.cs +++ /dev/null @@ -1,109 +0,0 @@ -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: - - // TODO: - throw new NotImplementedException ("TODO, port (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 - } -} diff --git a/Terminal.Gui/TextEffects/BaseEffect.cs b/Terminal.Gui/TextEffects/BaseEffect.cs deleted file mode 100644 index f8a710302d..0000000000 --- a/Terminal.Gui/TextEffects/BaseEffect.cs +++ /dev/null @@ -1,60 +0,0 @@ -namespace Terminal.Gui.TextEffects; - -public abstract class BaseEffectIterator where T : EffectConfig, new() -{ - protected T Config { get; set; } - protected TerminalA Terminal { get; set; } - protected List ActiveCharacters { get; set; } = new List (); - - protected BaseEffect Effect { get; } - - - - public BaseEffectIterator (BaseEffect effect) - { - Effect = effect; - Config = effect.EffectConfig; - Terminal = new TerminalA (effect.InputData, effect.TerminalConfig); - - } - - public void Update () - { - foreach (var character in ActiveCharacters) - { - character.Tick (); - } - ActiveCharacters.RemoveAll (character => !character.IsActive); - } - -} - -public abstract class BaseEffect 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 IDisposable TerminalOutput (string endSymbol = "\n") - { - var terminal = new Terminal (InputData, TerminalConfig); - terminal.PrepCanvas (); - try - { - return terminal; - } - finally - { - terminal.RestoreCursor (endSymbol); - } - }*/ -} - diff --git a/Terminal.Gui/TextEffects/Easing.cs b/Terminal.Gui/TextEffects/Easing.cs deleted file mode 100644 index 3b63ca032c..0000000000 --- a/Terminal.Gui/TextEffects/Easing.cs +++ /dev/null @@ -1,303 +0,0 @@ -namespace Terminal.Gui.TextEffects; -using System; - -public delegate float EasingFunction (float progressRatio); - -public static class Easing -{ - public static float Linear (float progressRatio) - { - return progressRatio; - } - - public static float InSine (float progressRatio) - { - return 1 - (float)Math.Cos ((progressRatio * Math.PI) / 2); - } - - public static float OutSine (float progressRatio) - { - return (float)Math.Sin ((progressRatio * Math.PI) / 2); - } - - public static float InOutSine (float progressRatio) - { - return -(float)(Math.Cos (Math.PI * progressRatio) - 1) / 2; - } - - public static float InQuad (float progressRatio) - { - return progressRatio * progressRatio; - } - - public static float OutQuad (float progressRatio) - { - return 1 - (1 - progressRatio) * (1 - progressRatio); - } - - public static float InOutQuad (float progressRatio) - { - if (progressRatio < 0.5) - { - return 2 * progressRatio * progressRatio; - } - else - { - return 1 - (float)Math.Pow (-2 * progressRatio + 2, 2) / 2; - } - } - - public static float InCubic (float progressRatio) - { - return progressRatio * progressRatio * progressRatio; - } - - public static float OutCubic (float progressRatio) - { - return 1 - (float)Math.Pow (1 - progressRatio, 3); - } - - public static float InOutCubic (float progressRatio) - { - if (progressRatio < 0.5) - { - return 4 * progressRatio * progressRatio * progressRatio; - } - else - { - return 1 - (float)Math.Pow (-2 * progressRatio + 2, 3) / 2; - } - } - - public static float InQuart (float progressRatio) - { - return progressRatio * progressRatio * progressRatio * progressRatio; - } - - public static float OutQuart (float progressRatio) - { - return 1 - (float)Math.Pow (1 - progressRatio, 4); - } - - public static float InOutQuart (float progressRatio) - { - if (progressRatio < 0.5) - { - return 8 * progressRatio * progressRatio * progressRatio * progressRatio; - } - else - { - return 1 - (float)Math.Pow (-2 * progressRatio + 2, 4) / 2; - } - } - - public static float InQuint (float progressRatio) - { - return progressRatio * progressRatio * progressRatio * progressRatio * progressRatio; - } - - public static float OutQuint (float progressRatio) - { - return 1 - (float)Math.Pow (1 - progressRatio, 5); - } - - public static float InOutQuint (float progressRatio) - { - if (progressRatio < 0.5) - { - return 16 * progressRatio * progressRatio * progressRatio * progressRatio * progressRatio; - } - else - { - return 1 - (float)Math.Pow (-2 * progressRatio + 2, 5) / 2; - } - } - - public static float InExpo (float progressRatio) - { - if (progressRatio == 0) - { - return 0; - } - else - { - return (float)Math.Pow (2, 10 * progressRatio - 10); - } - } - - public static float OutExpo (float progressRatio) - { - if (progressRatio == 1) - { - return 1; - } - else - { - return 1 - (float)Math.Pow (2, -10 * progressRatio); - } - } - - public static float InOutExpo (float progressRatio) - { - if (progressRatio == 0) - { - return 0; - } - else if (progressRatio == 1) - { - return 1; - } - else if (progressRatio < 0.5) - { - return (float)Math.Pow (2, 20 * progressRatio - 10) / 2; - } - else - { - return (2 - (float)Math.Pow (2, -20 * progressRatio + 10)) / 2; - } - } - - public static float InCirc (float progressRatio) - { - return 1 - (float)Math.Sqrt (1 - progressRatio * progressRatio); - } - - public static float OutCirc (float progressRatio) - { - return (float)Math.Sqrt (1 - (progressRatio - 1) * (progressRatio - 1)); - } - - public static float InOutCirc (float progressRatio) - { - if (progressRatio < 0.5) - { - return (1 - (float)Math.Sqrt (1 - (2 * progressRatio) * (2 * progressRatio))) / 2; - } - else - { - return ((float)Math.Sqrt (1 - (-2 * progressRatio + 2) * (-2 * progressRatio + 2)) + 1) / 2; - } - } - - public static float InBack (float progressRatio) - { - const float c1 = 1.70158f; - const float c3 = c1 + 1; - return c3 * progressRatio * progressRatio * progressRatio - c1 * progressRatio * progressRatio; - } - - public static float OutBack (float progressRatio) - { - const float c1 = 1.70158f; - const float c3 = c1 + 1; - return 1 + c3 * (progressRatio - 1) * (progressRatio - 1) * (progressRatio - 1) + c1 * (progressRatio - 1) * (progressRatio - 1); - } - - public static float InOutBack (float progressRatio) - { - const float c1 = 1.70158f; - const float c2 = c1 * 1.525f; - if (progressRatio < 0.5) - { - return ((2 * progressRatio) * (2 * progressRatio) * ((c2 + 1) * 2 * progressRatio - c2)) / 2; - } - else - { - return ((2 * progressRatio - 2) * (2 * progressRatio - 2) * ((c2 + 1) * (progressRatio * 2 - 2) + c2) + 2) / 2; - } - } - - public static float InElastic (float progressRatio) - { - const float c4 = (2 * (float)Math.PI) / 3; - if (progressRatio == 0) - { - return 0; - } - else if (progressRatio == 1) - { - return 1; - } - else - { - return -(float)Math.Pow (2, 10 * progressRatio - 10) * (float)Math.Sin ((progressRatio * 10 - 10.75) * c4); - } - } - - public static float OutElastic (float progressRatio) - { - const float c4 = (2 * (float)Math.PI) / 3; - if (progressRatio == 0) - { - return 0; - } - else if (progressRatio == 1) - { - return 1; - } - else - { - return (float)Math.Pow (2, -10 * progressRatio) * (float)Math.Sin ((progressRatio * 10 - 0.75) * c4) + 1; - } - } - - public static float InOutElastic (float progressRatio) - { - const float c5 = (2 * (float)Math.PI) / 4.5f; - if (progressRatio == 0) - { - return 0; - } - else if (progressRatio == 1) - { - return 1; - } - else if (progressRatio < 0.5) - { - return -(float)Math.Pow (2, 20 * progressRatio - 10) * (float)Math.Sin ((20 * progressRatio - 11.125) * c5) / 2; - } - else - { - return ((float)Math.Pow (2, -20 * progressRatio + 10) * (float)Math.Sin ((20 * progressRatio - 11.125) * c5)) / 2 + 1; - } - } - - public static float InBounce (float progressRatio) - { - return 1 - OutBounce (1 - progressRatio); - } - - public static float OutBounce (float progressRatio) - { - const float n1 = 7.5625f; - const float d1 = 2.75f; - if (progressRatio < 1 / d1) - { - return n1 * progressRatio * progressRatio; - } - else if (progressRatio < 2 / d1) - { - return n1 * (progressRatio - 1.5f / d1) * (progressRatio - 1.5f / d1) + 0.75f; - } - else if (progressRatio < 2.5 / d1) - { - return n1 * (progressRatio - 2.25f / d1) * (progressRatio - 2.25f / d1) + 0.9375f; - } - else - { - return n1 * (progressRatio - 2.625f / d1) * (progressRatio - 2.625f / d1) + 0.984375f; - } - } - - public static float InOutBounce (float progressRatio) - { - if (progressRatio < 0.5) - { - return (1 - OutBounce (1 - 2 * progressRatio)) / 2; - } - else - { - return (1 + OutBounce (2 * progressRatio - 1)) / 2; - } - } -} diff --git a/Terminal.Gui/TextEffects/EffectTemplate.cs b/Terminal.Gui/TextEffects/EffectTemplate.cs deleted file mode 100644 index 4b0fddcb7e..0000000000 --- a/Terminal.Gui/TextEffects/EffectTemplate.cs +++ /dev/null @@ -1,13 +0,0 @@ -namespace Terminal.Gui.TextEffects; - -public class EffectConfig -{ - public Color ColorSingle { get; set; } - public List ColorList { get; set; } - public Color FinalColor { get; set; } - public List FinalGradientStops { get; set; } - public List FinalGradientSteps { get; set; } - public int FinalGradientFrames { get; set; } - public float MovementSpeed { get; set; } - public EasingFunction Easing { get; set; } -} diff --git a/Terminal.Gui/TextEffects/Effects/Beams.cs b/Terminal.Gui/TextEffects/Effects/Beams.cs deleted file mode 100644 index b39ef9a911..0000000000 --- a/Terminal.Gui/TextEffects/Effects/Beams.cs +++ /dev/null @@ -1,241 +0,0 @@ -/*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 -{ - public Beams (string inputData) : base (inputData) - { - } - - protected override BaseEffectIterator CreateIterator () - { - return new BeamsIterator (this); - } -} - - -public class BeamsIterator : BaseEffectIterator -{ - private class Group - { - public List Characters { get; private set; } - public string Direction { get; private set; } - private Terminal Terminal; - private BeamsConfig Config; - private double Speed; - private float NextCharacterCounter; - private List SortedCharacters; - - public Group (List 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 PendingGroups = new List (); - private Dictionary CharacterFinalColorMap = new Dictionary (); - private List ActiveGroups = new List (); - private int Delay = 0; - private string Phase = "beams"; - private List> 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 (); - - 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; - } - } -} -*/ \ No newline at end of file diff --git a/Terminal.Gui/TextEffects/Geometry.cs b/Terminal.Gui/TextEffects/Geometry.cs deleted file mode 100644 index 5de2f70730..0000000000 --- a/Terminal.Gui/TextEffects/Geometry.cs +++ /dev/null @@ -1,137 +0,0 @@ -namespace Terminal.Gui.TextEffects; - - -public static class GeometryUtils -{ - - public static List FindCoordsOnCircle (Coord origin, int radius, int coordsLimit = 0, bool unique = true) - { - var points = new List (); - var seenPoints = new HashSet (); - 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 FindCoordsInCircle (Coord center, int diameter) - { - var coordsInEllipse = new List (); - 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 FindCoordsInRect (Coord origin, int distance) - { - var coords = new List (); - 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 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 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); - } -} \ No newline at end of file diff --git a/Terminal.Gui/TextEffects/HexTerm.cs b/Terminal.Gui/TextEffects/HexTerm.cs deleted file mode 100644 index b3b96c4d37..0000000000 --- a/Terminal.Gui/TextEffects/HexTerm.cs +++ /dev/null @@ -1,94 +0,0 @@ -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 xtermToHexMap = new Dictionary - { - {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 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)); - } -} diff --git a/Terminal.Gui/TextEffects/Motion.cs b/Terminal.Gui/TextEffects/Motion.cs deleted file mode 100644 index 9ed544f5e6..0000000000 --- a/Terminal.Gui/TextEffects/Motion.cs +++ /dev/null @@ -1,253 +0,0 @@ -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 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 string WaypointId { get; set; } - public Coord Coord { get; set; } - public List BezierControl { get; set; } - - public Waypoint (string waypointId, Coord coord, List bezierControl = null) - { - WaypointId = waypointId; - Coord = coord; - BezierControl = bezierControl ?? new List (); - } -} - -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 EaseFunction { get; set; } - public int Layer { get; set; } - public int HoldTime { get; set; } - public bool Loop { get; set; } - public List Segments { get; private set; } = new List (); - public int CurrentStep { get; set; } - public double TotalDistance { 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 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 (CurrentStep <= MaxSteps) - { - double progress = EaseFunction?.Invoke ((double)CurrentStep / TotalDistance) ?? (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 Paths { get; private set; } = new Dictionary (); - 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 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 - } - - /// - /// Set the active path to None if the active path is the given path. - /// - public void DeactivatePath (Path p) - { - if (p == ActivePath) - { - ActivePath = null; - } - } - 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 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 - } - } - } -} diff --git a/Terminal.Gui/TextEffects/New/GradientFill.cs b/Terminal.Gui/TextEffects/New/GradientFill.cs deleted file mode 100644 index a6d69f4e30..0000000000 --- a/Terminal.Gui/TextEffects/New/GradientFill.cs +++ /dev/null @@ -1,35 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Drawing; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace Terminal.Gui.TextEffects; - -/// -/// Implementation of that uses a color gradient (including -/// radial, diagonal etc). -/// -public class GradientFill : IFill -{ - private Dictionary _map; - - public GradientFill (Rectangle area, Gradient gradient, Gradient.Direction direction) - { - _map = - gradient.BuildCoordinateColorMapping (area.Height, area.Width, direction) - .ToDictionary ( - (k) => new Point (k.Key.Column, k.Key.Row), - (v) => new Terminal.Gui.Color (v.Value.R, v.Value.G, v.Value.B)); - } - - public Terminal.Gui.Color GetColor (Point point) - { - if (_map.TryGetValue (point, out var color)) - { - return color; - } - return new Terminal.Gui.Color (0, 0, 0); // Default to black if point not found - } -} \ No newline at end of file diff --git a/Terminal.Gui/TextEffects/Terminal.cs b/Terminal.Gui/TextEffects/Terminal.cs deleted file mode 100644 index 08522069d7..0000000000 --- a/Terminal.Gui/TextEffects/Terminal.cs +++ /dev/null @@ -1,106 +0,0 @@ -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 TerminalA -{ - public TerminalConfig Config { get; } - public Canvas Canvas { get; private set; } - private Dictionary CharacterByInputCoord = new Dictionary (); - - public TerminalA (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); - } -} diff --git a/UICatalog/Scenarios/TextEffectsScenario.cs b/UICatalog/Scenarios/TextEffectsScenario.cs index 2df1131e9f..d8f20bac11 100644 --- a/UICatalog/Scenarios/TextEffectsScenario.cs +++ b/UICatalog/Scenarios/TextEffectsScenario.cs @@ -3,10 +3,8 @@ using System.Text; using System.Threading; using Terminal.Gui; -using Terminal.Gui.TextEffects; -using Color = Terminal.Gui.TextEffects.Color; -using Animation = Terminal.Gui.TextEffects.Animation; +using Terminal.Gui.Drawing; namespace UICatalog.Scenarios; @@ -68,18 +66,8 @@ public override void Main () }, DisplayText = "Gradients" }; - var t2 = new Tab () - { - View = new BallsView () - { - Width = Dim.Fill (), - Height = Dim.Fill (), - }, - DisplayText = "Ball" - }; tabView.AddTab (t1,false); - tabView.AddTab (t2,false); w.Add (tabView); @@ -111,11 +99,11 @@ public static void GetAppealingGradientColors (out List stops, out List { - Color.FromRgb(0, 128, 255), // Bright Blue - Color.FromRgb(0, 255, 128), // Bright Green - Color.FromRgb(255, 255, 0), // Bright Yellow - Color.FromRgb(255, 128, 0), // Bright Orange - Color.FromRgb(255, 0, 128) // Bright Pink + new Color(0, 128, 255), // Bright Blue + new Color(0, 255, 128), // Bright Green + new Color(255, 255, 0), // Bright Yellow + new Color(255, 128, 0), // Bright Orange + new Color(255, 0, 128) // Bright Pink }; // Define the number of steps between each color for smoother transitions @@ -159,9 +147,9 @@ private void DrawGradientArea (Gradient.Direction direction, int xOffset, int yO // Define the colors of the gradient stops var stops = new List { - Color.FromRgb(255, 0, 0), // Red - Color.FromRgb(0, 255, 0), // Green - Color.FromRgb(238, 130, 238) // Violet + new Color(255, 0, 0), // Red + new Color(0, 255, 0), // Green + new Color(238, 130, 238) // Violet }; // Define the number of steps between each color @@ -182,7 +170,7 @@ private void DrawGradientArea (Gradient.Direction direction, int xOffset, int yO { for (int col = 0; col <= maxColumn; col++) { - var coord = new Coord (col, row); + var coord = new Point (col, row); var color = gradientMapping [coord]; SetColor (color); @@ -197,13 +185,13 @@ private void DrawTopLineGradient (Rectangle viewport) // Define the colors of the rainbow var stops = new List { - Color.FromRgb(255, 0, 0), // Red - Color.FromRgb(255, 165, 0), // Orange - Color.FromRgb(255, 255, 0), // Yellow - Color.FromRgb(0, 128, 0), // Green - Color.FromRgb(0, 0, 255), // Blue - Color.FromRgb(75, 0, 130), // Indigo - Color.FromRgb(238, 130, 238) // Violet + new Color(255, 0, 0), // Red + new Color(255, 165, 0), // Orange + new Color(255, 255, 0), // Yellow + new Color(0, 128, 0), // Green + new Color(0, 0, 255), // Blue + new Color(75, 0, 130), // Indigo + new Color(238, 130, 238) // Violet }; // Define the number of steps between each color @@ -240,245 +228,4 @@ private void SetColor (Color color) new Terminal.Gui.Color (color.R, color.G, color.B) )); // Setting color based on RGB } -} - -internal class BallsView : View -{ - private Ball? _ball; - private bool _resized; - private LineCanvas lc; - private Gradient gradient; - - protected override void OnViewportChanged (DrawEventArgs e) - { - base.OnViewportChanged (e); - _resized = true; - - lc = new LineCanvas (new []{ - new StraightLine(new System.Drawing.Point(0,0),10,Orientation.Horizontal,LineStyle.Single), - - }); - TextEffectsScenario.GetAppealingGradientColors (out var stops, out var steps); - gradient = new Gradient (stops, steps); - var fill = new FillPair ( - new GradientFill (new System.Drawing.Rectangle (0, 0, 10, 0), gradient , Gradient.Direction.Horizontal), - new SolidFill(Terminal.Gui.Color.Black) - ); - lc.Fill = fill; - - } - - public override void OnDrawContent (Rectangle viewport) - { - base.OnDrawContent (viewport); - - if ((_ball == null && viewport.Width > 0 && viewport.Height > 0) || _resized) - { - _ball = new Ball (this); - _ball.Start (); - _resized = false; - } - - _ball?.Draw (); - - foreach(var map in lc.GetCellMap()) - { - Driver.SetAttribute (map.Value.Value.Attribute.Value); - AddRune (map.Key.X, map.Key.Y, map.Value.Value.Rune); - } - - for (int x = 0; x < 10; x++) - { - double fraction = (double)x / 10; - Color color = gradient.GetColorAtFraction (fraction); - - SetColor (color); - - AddRune (x, 2, new Rune ('█')); - } - - var map2 = gradient.BuildCoordinateColorMapping (0,10,Gradient.Direction.Horizontal); - - for (int x = 0; x < map2.Count; x++) - { - SetColor (map2[new Coord(x,0)]); - - AddRune (x, 3, 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 - } - public class Ball - { - public Animation Animation { get; private set; } - public Scene BouncingScene { get; private set; } - public View Viewport { get; private set; } - public EffectCharacter Character { get; private set; } - - public Ball (View viewport) - { - Viewport = viewport; - Character = new EffectCharacter (1, "O", 0, 0); - Animation = Character.Animation; - CreateBouncingScene (); - CreateMotionPath (); - } - - private void CreateBouncingScene () - { - BouncingScene = Animation.NewScene (isLooping: true); - int width = Viewport.Frame.Width; - int height = Viewport.Frame.Height; - double frequency = 4 * Math.PI / width; // Double the frequency - - for (int x = 0; x < width; x++) - { - int y = (int)((height - 1) / 2 * (1 + Math.Sin (frequency * x) * 0.8)); // Decrease amplitude - BouncingScene.AddFrame ("O", 1); - } - - for (int x = width - 1; x >= 0; x--) - { - int y = (int)((height - 1) / 2 * (1 + Math.Sin (frequency * x) * 0.8)); // Decrease amplitude - BouncingScene.AddFrame ("O", 1); - } - } - - private void CreateMotionPath () - { - int width = Viewport.Frame.Width; - int height = Viewport.Frame.Height; - double frequency = 4 * Math.PI / width; // Double the frequency - - var path = Character.Motion.CreatePath ("sineWavePath", speed: 1, loop: true); - - for (int x = 0; x < width; x++) - { - int y = (int)((height - 1) / 2 * (1 + Math.Sin (frequency * x) * 0.8)); // Decrease amplitude - path.AddWaypoint (new Waypoint ($"waypoint_{x}", new Coord (x, y))); - } - - for (int x = width - 1; x >= 0; x--) - { - int y = (int)((height - 1) / 2 * (1 + Math.Sin (frequency * x) * 0.8)); // Decrease amplitude - path.AddWaypoint (new Waypoint ($"waypoint_{x}", new Coord (x, y))); - } - - Character.Motion.ActivatePath (path); - } - - public void Start () - { - Animation.ActivateScene (BouncingScene); - new Thread (() => - { - while (true) - { - Thread.Sleep (10); // Adjust the speed of animation - Character.Tick (); - - Application.Invoke (() => Viewport.SetNeedsDisplay ()); - } - }) - { IsBackground = true }.Start (); - } - - public void Draw () - { - Driver.SetAttribute (Viewport.ColorScheme.Normal); - Viewport.AddRune (Character.Motion.CurrentCoord.Column, Character.Motion.CurrentCoord.Row, new Rune ('O')); - } - } -} - - -public class Ball -{ - public Animation Animation { get; private set; } - public Scene BouncingScene { get; private set; } - public View Viewport { get; private set; } - public EffectCharacter Character { get; private set; } - - public Ball (View viewport) - { - Viewport = viewport; - Character = new EffectCharacter (1, "O", 0, 0); - Animation = Character.Animation; - CreateBouncingScene (); - CreateMotionPath (); - } - - private void CreateBouncingScene () - { - BouncingScene = Animation.NewScene (isLooping: true); - int width = Viewport.Frame.Width; - int height = Viewport.Frame.Height; - double frequency = 4 * Math.PI / width; // Double the frequency - - for (int x = 0; x < width; x++) - { - int y = (int)((height) / 2 * (1 + Math.Sin (frequency * x))); // Decrease amplitude - BouncingScene.AddFrame ("O", 1); - } - - for (int x = width - 1; x >= 0; x--) - { - int y = (int)((height) / 2 * (1 + Math.Sin (frequency * x))); // Decrease amplitude - BouncingScene.AddFrame ("O", 1); - } - } - - private void CreateMotionPath () - { - int width = Viewport.Frame.Width; - int height = Viewport.Frame.Height; - double frequency = 4 * Math.PI / width; // Double the frequency - - var path = Character.Motion.CreatePath ("sineWavePath", speed: 1, loop: true); - - for (int x = 0; x < width; x++) - { - int y = (int)((height) / 2 * (1 + Math.Sin (frequency * x))); // Decrease amplitude - path.AddWaypoint (new Waypoint ($"waypoint_{x}", new Coord (x, y))); - } - - for (int x = width - 1; x >= 0; x--) - { - int y = (int)((height) / 2 * (1 + Math.Sin (frequency * x))); // Decrease amplitude - path.AddWaypoint (new Waypoint ($"waypoint_{x}", new Coord (x, y))); - } - - Character.Motion.ActivatePath (path); - } - - public void Start () - { - Animation.ActivateScene (BouncingScene); - new Thread (() => - { - while (true) - { - Thread.Sleep (10); // Adjust the speed of animation - Character.Tick (); - - Application.Invoke (() => Viewport.SetNeedsDisplay ()); - } - }) - { IsBackground = true }.Start (); - } - - public void Draw () - { - Application.Driver.SetAttribute (Viewport.ColorScheme.Normal); - Viewport.AddRune (Character.Motion.CurrentCoord.Column, Character.Motion.CurrentCoord.Row, new Rune ('O')); - } -} - +} \ No newline at end of file diff --git a/UnitTests/TextEffects/AnimationTests.cs b/UnitTests/TextEffects/AnimationTests.cs deleted file mode 100644 index 84de80f707..0000000000 --- a/UnitTests/TextEffects/AnimationTests.cs +++ /dev/null @@ -1,191 +0,0 @@ -using Terminal.Gui.TextEffects; - -namespace Terminal.Gui.TextEffectsTests; -using Color = Terminal.Gui.TextEffects.Color; - -public class AnimationTests -{ - private EffectCharacter character; - - public AnimationTests () - { - character = new EffectCharacter (0, "a", 0, 0); - } - - [Fact] - public void TestCharacterVisualInit () - { - var visual = new CharacterVisual ( - symbol: "a", - bold: true, - dim: false, - italic: true, - underline: false, - blink: true, - reverse: false, - hidden: true, - strike: false, - color: new Color ("ffffff"), - colorCode: "ffffff" - ); - Assert.Equal ("\x1b[1m\x1b[3m\x1b[5m\x1b[8m\x1b[38;2;255;255;255ma\x1b[0m", visual.FormattedSymbol); - Assert.True (visual.Bold); - Assert.False (visual.Dim); - Assert.True (visual.Italic); - Assert.False (visual.Underline); - Assert.True (visual.Blink); - Assert.False (visual.Reverse); - Assert.True (visual.Hidden); - Assert.False (visual.Strike); - Assert.Equal (new Color ("ffffff"), visual.Color); - Assert.Equal ("ffffff", visual.ColorCode); - } - - [Fact] - public void TestFrameInit () - { - var visual = new CharacterVisual ( - symbol: "a", - bold: true, - dim: false, - italic: true, - underline: false, - blink: true, - reverse: false, - hidden: true, - strike: false, - color: new Color ("ffffff") - ); - var frame = new Frame (characterVisual: visual, duration: 5); - Assert.Equal (visual, frame.CharacterVisual); - Assert.Equal (5, frame.Duration); - Assert.Equal (0, frame.TicksElapsed); - } - - [Fact] - public void TestSceneInit () - { - var scene = new Scene (sceneId: "test_scene", isLooping: true, sync: SyncMetric.Step, ease: Easing.InSine); - Assert.Equal ("test_scene", scene.SceneId); - Assert.True (scene.IsLooping); - Assert.Equal (SyncMetric.Step, scene.Sync); - Assert.Equal (Easing.InSine, scene.Ease); - } - - [Fact] - public void TestSceneAddFrame () - { - var scene = new Scene (sceneId: "test_scene"); - scene.AddFrame (symbol: "a", duration: 5, color: new Color ("ffffff"), bold: true, italic: true, blink: true, hidden: true); - Assert.Single (scene.Frames); - var frame = scene.Frames [0]; - Assert.Equal ("\x1b[1m\x1b[3m\x1b[5m\x1b[8m\x1b[38;2;255;255;255ma\x1b[0m", frame.CharacterVisual.FormattedSymbol); - Assert.Equal (5, frame.Duration); - Assert.Equal (new Color ("ffffff"), frame.CharacterVisual.Color); - Assert.True (frame.CharacterVisual.Bold); - } - - [Fact] - public void TestSceneAddFrameInvalidDuration () - { - var scene = new Scene (sceneId: "test_scene"); - var exception = Assert.Throws (() => scene.AddFrame (symbol: "a", duration: 0, color: new Color ("ffffff"))); - Assert.Equal ("duration must be greater than 0", exception.Message); - } - - [Fact] - public void TestSceneApplyGradientToSymbolsEqualColorsAndSymbols () - { - var scene = new Scene (sceneId: "test_scene"); - var gradient = new Gradient (new [] { new Color ("000000"), new Color ("ffffff") }, - steps: new [] { 2 }); - var symbols = new List { "a", "b", "c" }; - scene.ApplyGradientToSymbols (gradient, symbols, duration: 1); - Assert.Equal (3, scene.Frames.Count); - for (int i = 0; i < scene.Frames.Count; i++) - { - Assert.Equal (1, scene.Frames [i].Duration); - Assert.Equal (gradient.Spectrum [i].RgbColor, scene.Frames [i].CharacterVisual.ColorCode); - } - } - - [Fact] - public void TestSceneApplyGradientToSymbolsUnequalColorsAndSymbols () - { - var scene = new Scene (sceneId: "test_scene"); - var gradient = new Gradient ( - new [] { new Color ("000000"), new Color ("ffffff") }, - steps: new [] { 4 }); - var symbols = new List { "q", "z" }; - scene.ApplyGradientToSymbols (gradient, symbols, duration: 1); - Assert.Equal (5, scene.Frames.Count); - Assert.Equal (gradient.Spectrum [0].RgbColor, scene.Frames [0].CharacterVisual.ColorCode); - Assert.Contains ("q", scene.Frames [0].CharacterVisual.Symbol); - Assert.Equal (gradient.Spectrum [^1].RgbColor, scene.Frames [^1].CharacterVisual.ColorCode); - Assert.Contains ("z", scene.Frames [^1].CharacterVisual.Symbol); - } - - [Fact] - public void TestAnimationInit () - { - var animation = character.Animation; - Assert.Equal (character, animation.Character); - Assert.Empty (animation.Scenes); - Assert.Null (animation.ActiveScene); - Assert.False (animation.UseXtermColors); - Assert.False (animation.NoColor); - Assert.Empty (animation.XtermColorMap); - Assert.Equal (0, animation.ActiveSceneCurrentStep); - } - - [Fact] - public void TestAnimationNewScene () - { - var animation = character.Animation; - var scene = animation.NewScene (id:"test_scene", isLooping: true); - Assert.IsType (scene); - Assert.Equal ("test_scene", scene.SceneId); - Assert.True (scene.IsLooping); - Assert.True (animation.Scenes.ContainsKey ("test_scene")); - } - - [Fact] - public void TestAnimationNewSceneWithoutId () - { - var animation = character.Animation; - var scene = animation.NewScene (); - Assert.IsType (scene); - Assert.Equal ("0", scene.SceneId); - Assert.True (animation.Scenes.ContainsKey ("0")); - } - - [Fact] - public void TestAnimationQueryScene () - { - var animation = character.Animation; - var scene = animation.NewScene (id:"test_scene", isLooping: true); - Assert.Equal (scene, animation.QueryScene ("test_scene")); - } - - [Fact] - public void TestAnimationLoopingActiveSceneIsComplete () - { - var animation = character.Animation; - var scene = animation.NewScene (id: "test_scene", isLooping: true); - scene.AddFrame (symbol: "a", duration: 2); - animation.ActivateScene (scene); - Assert.True (animation.ActiveSceneIsComplete ()); - } - - [Fact] - public void TestAnimationNonLoopingActiveSceneIsComplete () - { - var animation = character.Animation; - var scene = animation.NewScene (id: "test_scene"); - scene.AddFrame (symbol: "a", duration: 1); - animation.ActivateScene (scene); - Assert.False (animation.ActiveSceneIsComplete ()); - animation.StepAnimation (); - Assert.True (animation.ActiveSceneIsComplete ()); - } -} \ No newline at end of file diff --git a/UnitTests/TextEffects/New/GradientFillTests.cs b/UnitTests/TextEffects/New/GradientFillTests.cs index 400425dc50..a4f38d9fe8 100644 --- a/UnitTests/TextEffects/New/GradientFillTests.cs +++ b/UnitTests/TextEffects/New/GradientFillTests.cs @@ -1,4 +1,6 @@ -namespace Terminal.Gui.TextEffects.Tests; +using Terminal.Gui.Drawing; + +namespace Terminal.Gui.TextEffects.Tests; public class GradientFillTests { @@ -9,8 +11,8 @@ public GradientFillTests () // Define the colors of the gradient stops var stops = new List { - Color.FromRgb(255, 0, 0), // Red - Color.FromRgb(0, 0, 255) // Blue + new Color(255, 0, 0), // Red + new Color(0, 0, 255) // Blue }; // Define the number of steps between each color From f7d584be59d0bf0bc5dee316b7b7ffddaa30b945 Mon Sep 17 00:00:00 2001 From: tznind Date: Sun, 7 Jul 2024 11:12:15 +0100 Subject: [PATCH 15/30] Add attribution --- Terminal.Gui/Drawing/Gradient.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Terminal.Gui/Drawing/Gradient.cs b/Terminal.Gui/Drawing/Gradient.cs index b5d58e34a4..27aab4afea 100644 --- a/Terminal.Gui/Drawing/Gradient.cs +++ b/Terminal.Gui/Drawing/Gradient.cs @@ -1,4 +1,7 @@ -namespace Terminal.Gui; +// This code is a C# port from python library Terminal Text Effects https://github.com/ChrisBuilds/terminaltexteffects/ + +namespace Terminal.Gui; + using System; using System.Collections.Generic; using System.Linq; From be764b7903d57415f3bf2384642c706a9088074f Mon Sep 17 00:00:00 2001 From: tznind Date: Sun, 7 Jul 2024 19:54:02 +0100 Subject: [PATCH 16/30] xml doc --- Terminal.Gui/Drawing/FillPair.cs | 25 ++++++++-- Terminal.Gui/Drawing/Gradient.cs | 47 ++++++++++++++----- Terminal.Gui/Drawing/GradientFill.cs | 16 ++++++- Terminal.Gui/Drawing/LineCanvas.cs | 16 +++++-- Terminal.Gui/Drawing/SolidFill.cs | 12 +++++ Terminal.Gui/Drawing/StraightLine.cs | 22 ++++----- UICatalog/Scenarios/TextEffectsScenario.cs | 35 +++++++------- .../TextEffects/New/GradientFillTests.cs | 10 ++-- 8 files changed, 129 insertions(+), 54 deletions(-) diff --git a/Terminal.Gui/Drawing/FillPair.cs b/Terminal.Gui/Drawing/FillPair.cs index f51ceec658..2a6c7d5ae8 100644 --- a/Terminal.Gui/Drawing/FillPair.cs +++ b/Terminal.Gui/Drawing/FillPair.cs @@ -10,16 +10,35 @@ namespace Terminal.Gui; /// public class FillPair { + /// + /// Creates a new instance using the provided fills for foreground and background + /// color when assembling . + /// + /// + /// public FillPair (GradientFill fore, SolidFill back) { Foreground = fore; Background = back; } - IFill Foreground { get; set; } - IFill Background { get; set; } + /// + /// The fill which provides point based foreground color. + /// + public IFill Foreground { get; init; } - internal Attribute? GetAttribute (Point point) + /// + /// The fill which provides point based background color. + /// + public IFill Background { get; init; } + + /// + /// Returns the color pair (foreground+background) to use when rendering + /// a rune at the given . + /// + /// + /// + public Attribute GetAttribute (Point point) { return new Attribute ( Foreground.GetColor (point), diff --git a/Terminal.Gui/Drawing/Gradient.cs b/Terminal.Gui/Drawing/Gradient.cs index 27aab4afea..b0cdf072f9 100644 --- a/Terminal.Gui/Drawing/Gradient.cs +++ b/Terminal.Gui/Drawing/Gradient.cs @@ -6,6 +6,36 @@ namespace Terminal.Gui; using System.Collections.Generic; using System.Linq; +/// +/// Describes the pattern that a results in e.g. , etc +/// +public enum GradientDirection +{ + /// + /// Color varies along Y axis but is constant on X axis. + /// + Vertical, + + /// + /// Color varies along X axis but is constant on Y axis. + /// + Horizontal, + + + /// + /// Color varies by distance from center (i.e. in circular ripples) + /// + Radial, + + /// + /// Color varies by X and Y axis (i.e. a slanted gradient) + /// + Diagonal +} + +/// +/// Describes +/// public class Gradient { public List Spectrum { get; private set; } @@ -13,13 +43,6 @@ public class Gradient private readonly List _stops; private readonly List _steps; - public enum Direction - { - Vertical, - Horizontal, - Radial, - Diagonal - } public Gradient (IEnumerable stops, IEnumerable steps, bool loop = false) { @@ -83,13 +106,13 @@ private IEnumerable InterpolateColors (Color start, Color end, int steps) } } - public Dictionary BuildCoordinateColorMapping (int maxRow, int maxColumn, Direction direction) + public Dictionary BuildCoordinateColorMapping (int maxRow, int maxColumn, GradientDirection direction) { var gradientMapping = new Dictionary (); switch (direction) { - case Direction.Vertical: + case GradientDirection.Vertical: for (int row = 0; row <= maxRow; row++) { double fraction = maxRow == 0 ? 1.0 : (double)row / maxRow; @@ -101,7 +124,7 @@ public Dictionary BuildCoordinateColorMapping (int maxRow, int max } break; - case Direction.Horizontal: + case GradientDirection.Horizontal: for (int col = 0; col <= maxColumn; col++) { double fraction = maxColumn == 0 ? 1.0 : (double)col / maxColumn; @@ -113,7 +136,7 @@ public Dictionary BuildCoordinateColorMapping (int maxRow, int max } break; - case Direction.Radial: + case GradientDirection.Radial: for (int row = 0; row <= maxRow; row++) { for (int col = 0; col <= maxColumn; col++) @@ -125,7 +148,7 @@ public Dictionary BuildCoordinateColorMapping (int maxRow, int max } break; - case Direction.Diagonal: + case GradientDirection.Diagonal: for (int row = 0; row <= maxRow; row++) { for (int col = 0; col <= maxColumn; col++) diff --git a/Terminal.Gui/Drawing/GradientFill.cs b/Terminal.Gui/Drawing/GradientFill.cs index d0bf163daf..393a183f46 100644 --- a/Terminal.Gui/Drawing/GradientFill.cs +++ b/Terminal.Gui/Drawing/GradientFill.cs @@ -8,11 +8,25 @@ public class GradientFill : IFill { private Dictionary _map; - public GradientFill (Rectangle area, Gradient gradient, Gradient.Direction direction) + /// + /// Creates a new instance of the class that can return + /// color for any point in the given using the provided + /// and . + /// + /// + /// + /// + public GradientFill (Rectangle area, Gradient gradient, GradientDirection direction) { _map = gradient.BuildCoordinateColorMapping (area.Height, area.Width, direction); } + /// + /// Returns the color to use for the given or Black if it + /// lies outside of the prepared gradient area (see constructor). + /// + /// + /// public Color GetColor (Point point) { if (_map.TryGetValue (point, out var color)) diff --git a/Terminal.Gui/Drawing/LineCanvas.cs b/Terminal.Gui/Drawing/LineCanvas.cs index 2fa0d8a9e7..2c7367fcfb 100644 --- a/Terminal.Gui/Drawing/LineCanvas.cs +++ b/Terminal.Gui/Drawing/LineCanvas.cs @@ -4,7 +4,13 @@ namespace Terminal.Gui; /// Facilitates box drawing and line intersection detection and rendering. Does not support diagonal lines. public class LineCanvas : IDisposable { + /// + /// Optional which when present overrides the + /// (colors) of lines in the canvas. This can be used e.g. to apply a global + /// across all lines. + /// public FillPair? Fill { get; set; } + private readonly List _lines = []; private readonly Dictionary _runeResolvers = new () @@ -86,7 +92,7 @@ public Rectangle Viewport viewport = Rectangle.Union (viewport, _lines [i].Viewport); } - if (viewport is {Width: 0} or {Height: 0}) + if (viewport is { Width: 0 } or { Height: 0 }) { viewport = viewport with { @@ -325,9 +331,11 @@ private void ConfigurationManager_Applied (object? sender, ConfigurationManagerE /// private bool Exactly (HashSet intersects, params IntersectionType [] types) { return intersects.SetEquals (types); } - private Attribute? GetAttributeForIntersects (IntersectionDefinition? [] intersects) { - return Fill != null ? Fill.GetAttribute(intersects [0]!.Point): - intersects [0]!.Line.Attribute; } + private Attribute? GetAttributeForIntersects (IntersectionDefinition? [] intersects) + { + return Fill != null ? Fill.GetAttribute (intersects [0]!.Point) : + intersects [0]!.Line.Attribute; + } private Cell? GetCellForIntersects (ConsoleDriver driver, IntersectionDefinition? [] intersects) { diff --git a/Terminal.Gui/Drawing/SolidFill.cs b/Terminal.Gui/Drawing/SolidFill.cs index 202cec57c2..67e1bdf1f9 100644 --- a/Terminal.Gui/Drawing/SolidFill.cs +++ b/Terminal.Gui/Drawing/SolidFill.cs @@ -8,10 +8,22 @@ public class SolidFill : IFill { readonly Color _color; + /// + /// Creates a new instance of the class which will return + /// the provided regardless of which point is requested. + /// + /// public SolidFill (Color color) { _color = color; } + + /// + /// Returns the color this instance was constructed with regardless of + /// which is being colored. + /// + /// + /// public Color GetColor (Point point) { return _color; diff --git a/Terminal.Gui/Drawing/StraightLine.cs b/Terminal.Gui/Drawing/StraightLine.cs index f8dfb2d887..8bbd83494c 100644 --- a/Terminal.Gui/Drawing/StraightLine.cs +++ b/Terminal.Gui/Drawing/StraightLine.cs @@ -111,10 +111,11 @@ IntersectionType typeWhenPositive return null; } + var p = new Point (x, y); + if (StartsAt (x, y)) { - return new IntersectionDefinition ( - new Point (x, y), + return new IntersectionDefinition (p, GetTypeByLength ( IntersectionType.StartLeft, IntersectionType.PassOverHorizontal, @@ -126,8 +127,7 @@ IntersectionType typeWhenPositive if (EndsAt (x, y)) { - return new IntersectionDefinition ( - new Point (x, y), + return new IntersectionDefinition (p, Length < 0 ? IntersectionType.StartRight : IntersectionType.StartLeft, this ); @@ -138,8 +138,7 @@ IntersectionType typeWhenPositive if (xmin < x && xmax > x) { - return new IntersectionDefinition ( - new Point (x, y), + return new IntersectionDefinition (p, IntersectionType.PassOverHorizontal, this ); @@ -155,10 +154,11 @@ IntersectionType typeWhenPositive return null; } + var p = new Point (x, y); + if (StartsAt (x, y)) { - return new IntersectionDefinition ( - new Point (x, y), + return new IntersectionDefinition (p, GetTypeByLength ( IntersectionType.StartUp, IntersectionType.PassOverVertical, @@ -170,8 +170,7 @@ IntersectionType typeWhenPositive if (EndsAt (x, y)) { - return new IntersectionDefinition ( - new Point (x, y), + return new IntersectionDefinition (p, Length < 0 ? IntersectionType.StartDown : IntersectionType.StartUp, this ); @@ -182,8 +181,7 @@ IntersectionType typeWhenPositive if (ymin < y && ymax > y) { - return new IntersectionDefinition ( - new Point (x, y), + return new IntersectionDefinition (p, IntersectionType.PassOverVertical, this ); diff --git a/UICatalog/Scenarios/TextEffectsScenario.cs b/UICatalog/Scenarios/TextEffectsScenario.cs index d8f20bac11..63bdc8b005 100644 --- a/UICatalog/Scenarios/TextEffectsScenario.cs +++ b/UICatalog/Scenarios/TextEffectsScenario.cs @@ -19,7 +19,7 @@ public override void Main () Application.Init (); var w = new Window { - Width = Dim.Fill(), + Width = Dim.Fill (), Height = Dim.Fill (), Title = "Text Effects Scenario" }; @@ -30,13 +30,13 @@ public override void Main () // TODO: Does not work // SetupGradientLineCanvas (tabView, tabView.Frame.Size); }; - w.SizeChanging += (s,e)=> + w.SizeChanging += (s, e) => { - if(e.Size.HasValue) + if (e.Size.HasValue) { SetupGradientLineCanvas (w, e.Size.Value); } - + // TODO: Does not work //SetupGradientLineCanvas (tabView, tabView.Frame.Size); }; @@ -44,7 +44,7 @@ public override void Main () w.ColorScheme = new ColorScheme { Normal = new Terminal.Gui.Attribute (ColorName.White, ColorName.Black), - Focus = new Terminal.Gui.Attribute (ColorName.Black,ColorName.White), + Focus = new Terminal.Gui.Attribute (ColorName.Black, ColorName.White), HotNormal = new Terminal.Gui.Attribute (ColorName.White, ColorName.Black), HotFocus = new Terminal.Gui.Attribute (ColorName.White, ColorName.Black), Disabled = new Terminal.Gui.Attribute (ColorName.Gray, ColorName.Black) @@ -67,7 +67,7 @@ public override void Main () DisplayText = "Gradients" }; - tabView.AddTab (t1,false); + tabView.AddTab (t1, false); w.Add (tabView); @@ -86,7 +86,7 @@ private void SetupGradientLineCanvas (View w, Size size) var g = new Gradient (stops, steps); var fore = new GradientFill ( - new Rectangle (0, 0, size.Width, size.Height), g, Gradient.Direction.Diagonal); + new Rectangle (0, 0, size.Width, size.Height), g, GradientDirection.Diagonal); var back = new SolidFill (new Terminal.Gui.Color (ColorName.Black)); w.LineCanvas.Fill = new FillPair ( @@ -107,7 +107,7 @@ public static void GetAppealingGradientColors (out List stops, out List { 15,15, 15, 15 }; + steps = new List { 15, 15, 15, 15 }; } } @@ -125,24 +125,25 @@ public override void OnDrawContent (Rectangle viewport) if (viewport.Height < 25) // Not enough space, render in a single line { - DrawGradientArea (Gradient.Direction.Horizontal, x, y); - DrawGradientArea (Gradient.Direction.Vertical, x + 32, y); - DrawGradientArea (Gradient.Direction.Radial, x + 64, y); - DrawGradientArea (Gradient.Direction.Diagonal, x + 96, y); + DrawGradientArea (GradientDirection.Horizontal, x, y); + DrawGradientArea (GradientDirection.Horizontal, x, y); + DrawGradientArea (GradientDirection.Vertical, x + 32, y); + DrawGradientArea (GradientDirection.Radial, x + 64, y); + DrawGradientArea (GradientDirection.Diagonal, x + 96, y); } else // Enough space, render in two lines { - DrawGradientArea (Gradient.Direction.Horizontal, x, y); - DrawGradientArea (Gradient.Direction.Vertical, x + 32, y); - DrawGradientArea (Gradient.Direction.Radial, x, y + 17); - DrawGradientArea (Gradient.Direction.Diagonal, x + 32, y + 17); + DrawGradientArea (GradientDirection.Horizontal, x, y); + DrawGradientArea (GradientDirection.Vertical, x + 32, y); + DrawGradientArea (GradientDirection.Radial, x, y + 17); + DrawGradientArea (GradientDirection.Diagonal, x + 32, y + 17); } } - private void DrawGradientArea (Gradient.Direction direction, int xOffset, int yOffset) + private void DrawGradientArea (GradientDirection direction, int xOffset, int yOffset) { // Define the colors of the gradient stops var stops = new List diff --git a/UnitTests/TextEffects/New/GradientFillTests.cs b/UnitTests/TextEffects/New/GradientFillTests.cs index a4f38d9fe8..8349edd6d7 100644 --- a/UnitTests/TextEffects/New/GradientFillTests.cs +++ b/UnitTests/TextEffects/New/GradientFillTests.cs @@ -25,13 +25,13 @@ public GradientFillTests () public void TestGradientFillCorners () { var area = new Rectangle (0, 0, 10, 10); - var gradientFill = new GradientFill (area, _gradient, Gradient.Direction.Diagonal); + var gradientFill = new GradientFill (area, _gradient, GradientDirection.Diagonal); // Test the corners var topLeft = new Point (0, 0); - var topRight = new Point (area.Width - 1, 0); - var bottomLeft = new Point (0, area.Height - 1); - var bottomRight = new Point (area.Width - 1, area.Height - 1); + var topRight = new Point (area.Width, 0); + var bottomLeft = new Point (0, area.Height ); + var bottomRight = new Point (area.Width, area.Height); var topLeftColor = gradientFill.GetColor (topLeft); var topRightColor = gradientFill.GetColor (topRight); @@ -62,7 +62,7 @@ public void TestGradientFillCorners () public void TestGradientFillColorTransition () { var area = new Rectangle (0, 0, 10, 10); - var gradientFill = new GradientFill (area, _gradient, Gradient.Direction.Diagonal); + var gradientFill = new GradientFill (area, _gradient, GradientDirection.Diagonal); for (int row = 0; row < area.Height; row++) { From a167366b147a46be441684b767a90c14a85fa36b Mon Sep 17 00:00:00 2001 From: tznind Date: Sun, 7 Jul 2024 20:13:56 +0100 Subject: [PATCH 17/30] Tests, xmldoc and guards --- Terminal.Gui/Drawing/Gradient.cs | 29 ++++++++++ Terminal.Gui/Drawing/GradientFill.cs | 2 +- .../New => Drawing}/GradientFillTests.cs | 11 ++-- UnitTests/Drawing/GradientTests.cs | 53 +++++++++++++++++++ 4 files changed, 88 insertions(+), 7 deletions(-) rename UnitTests/{TextEffects/New => Drawing}/GradientFillTests.cs (94%) create mode 100644 UnitTests/Drawing/GradientTests.cs diff --git a/Terminal.Gui/Drawing/Gradient.cs b/Terminal.Gui/Drawing/Gradient.cs index b0cdf072f9..7aea37b905 100644 --- a/Terminal.Gui/Drawing/Gradient.cs +++ b/Terminal.Gui/Drawing/Gradient.cs @@ -58,10 +58,26 @@ public Gradient (IEnumerable stops, IEnumerable steps, bool loop = f Spectrum = GenerateGradient (_steps); } + /// + /// Returns the color to use at the given part of the spectrum + /// + /// Proportion of the way through the spectrum, must be between + /// 0 and 1 (inclusive). Returns the last color if is + /// . + /// + /// public Color GetColorAtFraction (double fraction) { + if (double.IsNaN (fraction)) + { + return Spectrum.Last (); + } + 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]; } @@ -106,6 +122,19 @@ private IEnumerable InterpolateColors (Color start, Color end, int steps) } } + /// + /// + /// Creates a mapping starting at 0,0 and going to and + /// (inclusively) using the supplied . + /// + /// + /// Note that this method is inclusive i.e. passing 1/1 results in 4 mapped coordinates. + /// + /// + /// + /// + /// + /// public Dictionary BuildCoordinateColorMapping (int maxRow, int maxColumn, GradientDirection direction) { var gradientMapping = new Dictionary (); diff --git a/Terminal.Gui/Drawing/GradientFill.cs b/Terminal.Gui/Drawing/GradientFill.cs index 393a183f46..31339ebe0c 100644 --- a/Terminal.Gui/Drawing/GradientFill.cs +++ b/Terminal.Gui/Drawing/GradientFill.cs @@ -18,7 +18,7 @@ public class GradientFill : IFill /// public GradientFill (Rectangle area, Gradient gradient, GradientDirection direction) { - _map = gradient.BuildCoordinateColorMapping (area.Height, area.Width, direction); + _map = gradient.BuildCoordinateColorMapping (area.Height-1, area.Width-1, direction); } /// diff --git a/UnitTests/TextEffects/New/GradientFillTests.cs b/UnitTests/Drawing/GradientFillTests.cs similarity index 94% rename from UnitTests/TextEffects/New/GradientFillTests.cs rename to UnitTests/Drawing/GradientFillTests.cs index 8349edd6d7..fd68225396 100644 --- a/UnitTests/TextEffects/New/GradientFillTests.cs +++ b/UnitTests/Drawing/GradientFillTests.cs @@ -1,6 +1,5 @@ -using Terminal.Gui.Drawing; - -namespace Terminal.Gui.TextEffects.Tests; + +namespace Terminal.Gui.DrawingTests; public class GradientFillTests { @@ -29,9 +28,9 @@ public void TestGradientFillCorners () // Test the corners var topLeft = new Point (0, 0); - var topRight = new Point (area.Width, 0); - var bottomLeft = new Point (0, area.Height ); - var bottomRight = new Point (area.Width, area.Height); + var topRight = new Point (area.Width - 1, 0); + var bottomLeft = new Point (0, area.Height - 1); + var bottomRight = new Point (area.Width - 1, area.Height - 1); var topLeftColor = gradientFill.GetColor (topLeft); var topRightColor = gradientFill.GetColor (topRight); diff --git a/UnitTests/Drawing/GradientTests.cs b/UnitTests/Drawing/GradientTests.cs new file mode 100644 index 0000000000..e9e0194ea2 --- /dev/null +++ b/UnitTests/Drawing/GradientTests.cs @@ -0,0 +1,53 @@ + +namespace Terminal.Gui.DrawingTests; + +public class GradientTests +{ + // Static method to provide all enum values + public static IEnumerable GradientDirectionValues () + { + return typeof (GradientDirection).GetEnumValues () + .Cast () + .Select (direction => new object [] { direction }); + } + + [Theory] + [MemberData (nameof (GradientDirectionValues))] + public void GradientIsInclusive_2_by_2 (GradientDirection direction) + { + // Define the colors of the gradient stops + var stops = new List + { + new Color(255, 0, 0), // Red + new Color(0, 0, 255) // Blue + }; + + // Define the number of steps between each color + var steps = new List { 10 }; // 10 steps between Red -> Blue + + var g = new Gradient (stops, steps, loop: false); + Assert.Equal (4, g.BuildCoordinateColorMapping (1, 1, direction).Count); + } + + [Theory] + [MemberData (nameof (GradientDirectionValues))] + public void GradientIsInclusive_1_by_1 (GradientDirection direction) + { + // Define the colors of the gradient stops + var stops = new List + { + new Color(255, 0, 0), // Red + new Color(0, 0, 255) // Blue + }; + + // Define the number of steps between each color + var steps = new List { 10 }; // 10 steps between Red -> Blue + + var g = new Gradient (stops, steps, loop: false); + + // Note that + var c = Assert.Single (g.BuildCoordinateColorMapping (0, 0, direction)); + Assert.Equal (c.Key, new Point(0,0)); + Assert.Equal (c.Value, new Color (0, 0, 255)); + } +} From 116cba8c8c2b845414e22867701a7ed8841ebf06 Mon Sep 17 00:00:00 2001 From: tznind Date: Sun, 7 Jul 2024 21:06:21 +0100 Subject: [PATCH 18/30] Gradient tests --- Terminal.Gui/Drawing/Gradient.cs | 42 ++++++- UICatalog/Scenarios/TextEffectsScenario.cs | 42 ++++--- UnitTests/Drawing/GradientTests.cs | 124 ++++++++++++++++++++- 3 files changed, 189 insertions(+), 19 deletions(-) diff --git a/Terminal.Gui/Drawing/Gradient.cs b/Terminal.Gui/Drawing/Gradient.cs index 7aea37b905..01d7d2490c 100644 --- a/Terminal.Gui/Drawing/Gradient.cs +++ b/Terminal.Gui/Drawing/Gradient.cs @@ -44,15 +44,42 @@ public class Gradient private readonly List _steps; + /// + /// Creates a new instance of the class which hosts a + /// of colors including all and interpolated colors + /// between each corresponding pair. + /// + /// The colors to use in the spectrum (N) + /// The number of colors to generate between each pair (must be N-1 numbers). + /// If only one step is passed then it is assumed to be the same distance for all pairs. + /// True to duplicate the first stop and step so that the gradient repeats itself + /// public Gradient (IEnumerable stops, IEnumerable steps, bool loop = false) { _stops = stops.ToList (); + if (_stops.Count < 1) + { throw new ArgumentException ("At least one color stop must be provided."); + } _steps = steps.ToList (); + + // If multiple colors and only 1 step assume same distance applies to all steps + if (_stops.Count > 2 && _steps.Count == 1) + { + _steps = Enumerable.Repeat (_steps.Single (),_stops.Count() - 1).ToList(); + } + if (_steps.Any (step => step < 1)) + { throw new ArgumentException ("Steps must be greater than 0."); + } + + if (_steps.Count != _stops.Count - 1) + { + throw new ArgumentException ("Number of steps must be N-1"); + } _loop = loop; Spectrum = GenerateGradient (_steps); @@ -85,6 +112,7 @@ public Color GetColorAtFraction (double fraction) private List GenerateGradient (IEnumerable steps) { List gradient = new List (); + if (_stops.Count == 1) { for (int i = 0; i < steps.Sum (); i++) @@ -94,13 +122,17 @@ private List GenerateGradient (IEnumerable steps) return gradient; } + var stopsToUse = _stops.ToList (); + var stepsToUse = _steps.ToList (); + if (_loop) { - _stops.Add (_stops [0]); + stopsToUse.Add (_stops [0]); + stepsToUse.Add (_steps.First ()); } - var colorPairs = _stops.Zip (_stops.Skip (1), (start, end) => new { start, end }); - var stepsList = _steps.ToList (); + var colorPairs = stopsToUse.Zip (stopsToUse.Skip (1), (start, end) => new { start, end }); + var stepsList = stepsToUse; foreach (var (colorPair, thesteps) in colorPairs.Zip (stepsList, (pair, step) => (pair, step))) { @@ -112,7 +144,7 @@ private List GenerateGradient (IEnumerable steps) private IEnumerable InterpolateColors (Color start, Color end, int steps) { - for (int step = 0; step <= steps; step++) + for (int step = 0; step < steps; step++) { double fraction = (double)step / steps; int r = (int)(start.R + fraction * (end.R - start.R)); @@ -120,8 +152,10 @@ private IEnumerable InterpolateColors (Color start, Color end, int steps) int b = (int)(start.B + fraction * (end.B - start.B)); yield return new Color (r, g, b); } + yield return end; // Ensure the last color is included } + /// /// /// Creates a mapping starting at 0,0 and going to and diff --git a/UICatalog/Scenarios/TextEffectsScenario.cs b/UICatalog/Scenarios/TextEffectsScenario.cs index 63bdc8b005..14ce7c3292 100644 --- a/UICatalog/Scenarios/TextEffectsScenario.cs +++ b/UICatalog/Scenarios/TextEffectsScenario.cs @@ -14,6 +14,8 @@ public class TextEffectsScenario : Scenario { private TabView tabView; + public static bool LoopingGradient = false; + public override void Main () { Application.Init (); @@ -27,8 +29,6 @@ public override void Main () w.Loaded += (s, e) => { SetupGradientLineCanvas (w, w.Frame.Size); - // TODO: Does not work - // SetupGradientLineCanvas (tabView, tabView.Frame.Size); }; w.SizeChanging += (s, e) => { @@ -36,9 +36,6 @@ public override void Main () { SetupGradientLineCanvas (w, e.Size.Value); } - - // TODO: Does not work - //SetupGradientLineCanvas (tabView, tabView.Frame.Size); }; w.ColorScheme = new ColorScheme @@ -57,16 +54,32 @@ public override void Main () Height = Dim.Fill (), }; + var gradientsView = new GradientsView () + { + Width = Dim.Fill (), + Height = Dim.Fill (), + }; var t1 = new Tab () { - View = new GradientsView () - { - Width = Dim.Fill (), - Height = Dim.Fill (), - }, + View = gradientsView, DisplayText = "Gradients" }; + + var cbLooping = new CheckBox () + { + Text = "Looping", + Y = Pos.AnchorEnd (1) + }; + cbLooping.Toggle += (s, e) => + { + LoopingGradient = e.NewValue == CheckState.Checked; + SetupGradientLineCanvas (w, w.Frame.Size); + tabView.SetNeedsDisplay (); + }; + + gradientsView.Add (cbLooping); + tabView.AddTab (t1, false); w.Add (tabView); @@ -83,7 +96,7 @@ private void SetupGradientLineCanvas (View w, Size size) { GetAppealingGradientColors (out var stops, out var steps); - var g = new Gradient (stops, steps); + var g = new Gradient (stops, steps, LoopingGradient); var fore = new GradientFill ( new Rectangle (0, 0, size.Width, size.Height), g, GradientDirection.Diagonal); @@ -107,7 +120,8 @@ public static void GetAppealingGradientColors (out List stops, out List { 15, 15, 15, 15 }; + // If we pass only a single value then it will assume equal steps between all pairs + steps = new List { 15 }; } } @@ -157,7 +171,7 @@ private void DrawGradientArea (GradientDirection direction, int xOffset, int yOf var steps = new List { 10, 10 }; // 10 steps between Red -> Green, and Green -> Blue // Create the gradient - var radialGradient = new Gradient (stops, steps, loop: false); + var radialGradient = new Gradient (stops, steps, loop: TextEffectsScenario.LoopingGradient); // Define the size of the rectangle int maxRow = 15; // Adjusted to keep aspect ratio @@ -207,7 +221,7 @@ private void DrawTopLineGradient (Rectangle viewport) }; // Create the gradient - var rainbowGradient = new Gradient (stops, steps, loop: true); + var rainbowGradient = new Gradient (stops, steps, TextEffectsScenario.LoopingGradient); for (int x = 0; x < viewport.Width; x++) { diff --git a/UnitTests/Drawing/GradientTests.cs b/UnitTests/Drawing/GradientTests.cs index e9e0194ea2..0ff4c4a68d 100644 --- a/UnitTests/Drawing/GradientTests.cs +++ b/UnitTests/Drawing/GradientTests.cs @@ -50,4 +50,126 @@ public void GradientIsInclusive_1_by_1 (GradientDirection direction) Assert.Equal (c.Key, new Point(0,0)); Assert.Equal (c.Value, new Color (0, 0, 255)); } -} + + [Fact] + public void SingleColorStop () + { + var stops = new List { new Color (255, 0, 0) }; // Red + var steps = new List { }; + + var g = new Gradient (stops, steps, loop: false); + Assert.All (g.Spectrum, color => Assert.Equal (new Color (255, 0, 0), color)); + } + + [Fact] + public void LoopingGradient_CorrectColors () + { + var stops = new List + { + new Color(255, 0, 0), // Red + new Color(0, 0, 255) // Blue + }; + + var steps = new List { 10 }; + + var g = new Gradient (stops, steps, loop: true); + Assert.Equal (new Color (255, 0, 0), g.Spectrum.First ()); + Assert.Equal (new Color (255, 0, 0), g.Spectrum.Last ()); + } + + [Fact] + public void DifferentStepSizes () + { + var stops = new List + { + new Color(255, 0, 0), // Red + new Color(0, 255, 0), // Green + new Color(0, 0, 255) // Blue + }; + + var steps = new List { 5, 15 }; // Different steps + + var g = new Gradient (stops, steps, loop: false); + Assert.Equal (22, g.Spectrum.Count); + } + + [Fact] + public void FractionOutOfRange_ThrowsException () + { + var stops = new List + { + new Color(255, 0, 0), // Red + new Color(0, 0, 255) // Blue + }; + + var steps = new List { 10 }; + + var g = new Gradient (stops, steps, loop: false); + + Assert.Throws (() => g.GetColorAtFraction (-0.1)); + Assert.Throws (() => g.GetColorAtFraction (1.1)); + } + + [Fact] + public void NaNFraction_ReturnsLastColor () + { + var stops = new List + { + new Color(255, 0, 0), // Red + new Color(0, 0, 255) // Blue + }; + + var steps = new List { 10 }; + + var g = new Gradient (stops, steps, loop: false); + Assert.Equal (new Color (0, 0, 255), g.GetColorAtFraction (double.NaN)); + } + + [Fact] + public void Constructor_SingleStepProvided_ReplicatesForAllPairs () + { + var stops = new List + { + new Color(255, 0, 0), // Red + new Color(0, 255, 0), // Green + new Color(0, 0, 255) // Blue + }; + + var singleStep = new List { 5 }; // Single step provided + var gradient = new Gradient (stops, singleStep, loop: false); + + Assert.NotNull (gradient.Spectrum); + Assert.Equal (12, gradient.Spectrum.Count); // 5 steps Red -> Green + 5 steps Green -> Blue + 2 end colors + } + + [Fact] + public void Constructor_InvalidStepsLength_ThrowsArgumentException () + { + var stops = new List + { + new Color(255, 0, 0), // Red + new Color(0, 0, 255) // Blue + }; + + var invalidSteps = new List { 5, 5 }; // Invalid length (N-1 expected) + Assert.Throws (() => new Gradient (stops, invalidSteps, loop: false)); + } + + [Fact] + public void Constructor_ValidStepsLength_DoesNotThrow () + { + var stops = new List + { + new Color(255, 0, 0), // Red + new Color(0, 255, 0), // Green + new Color(0, 0, 255) // Blue + }; + + var validSteps = new List { 5, 5 }; // Valid length (N-1) + var gradient = new Gradient (stops, validSteps, loop: false); + + Assert.NotNull (gradient.Spectrum); + Assert.Equal (12, gradient.Spectrum.Count); // 5 steps Red -> Green + 5 steps Green -> Blue + 2 end colors + } + +} \ No newline at end of file From 0631579bd99d46b9157b75aacbfce0a954ac424e Mon Sep 17 00:00:00 2001 From: tznind Date: Sun, 7 Jul 2024 21:19:55 +0100 Subject: [PATCH 19/30] Fix naming and tests compiler warnings --- UICatalog/Scenarios/TextEffectsScenario.cs | 10 +++++----- UnitTests/Drawing/GradientFillTests.cs | 12 ------------ 2 files changed, 5 insertions(+), 17 deletions(-) diff --git a/UICatalog/Scenarios/TextEffectsScenario.cs b/UICatalog/Scenarios/TextEffectsScenario.cs index 14ce7c3292..c660ddb5c5 100644 --- a/UICatalog/Scenarios/TextEffectsScenario.cs +++ b/UICatalog/Scenarios/TextEffectsScenario.cs @@ -12,7 +12,7 @@ namespace UICatalog.Scenarios; [ScenarioCategory ("Colors")] public class TextEffectsScenario : Scenario { - private TabView tabView; + private TabView _tabView; public static bool LoopingGradient = false; @@ -48,7 +48,7 @@ public override void Main () }; // Creates a window that occupies the entire terminal with a title. - tabView = new TabView () + _tabView = new TabView () { Width = Dim.Fill (), Height = Dim.Fill (), @@ -75,14 +75,14 @@ public override void Main () { LoopingGradient = e.NewValue == CheckState.Checked; SetupGradientLineCanvas (w, w.Frame.Size); - tabView.SetNeedsDisplay (); + _tabView.SetNeedsDisplay (); }; gradientsView.Add (cbLooping); - tabView.AddTab (t1, false); + _tabView.AddTab (t1, false); - w.Add (tabView); + w.Add (_tabView); Application.Run (w); w.Dispose (); diff --git a/UnitTests/Drawing/GradientFillTests.cs b/UnitTests/Drawing/GradientFillTests.cs index fd68225396..bf0d4d0e22 100644 --- a/UnitTests/Drawing/GradientFillTests.cs +++ b/UnitTests/Drawing/GradientFillTests.cs @@ -37,12 +37,6 @@ public void TestGradientFillCorners () var bottomLeftColor = gradientFill.GetColor (bottomLeft); var bottomRightColor = gradientFill.GetColor (bottomRight); - // Validate the colors at the corners - Assert.NotNull (topLeftColor); - Assert.NotNull (topRightColor); - Assert.NotNull (bottomLeftColor); - Assert.NotNull (bottomRightColor); - // Expected colors var expectedTopLeftColor = new Terminal.Gui.Color (255, 0, 0); // Red var expectedBottomRightColor = new Terminal.Gui.Color (0, 0, 255); // Blue @@ -73,9 +67,6 @@ public void TestGradientFillColorTransition () var point = new Point (col, row); var color = gradientFill.GetColor (point); - // Ensure color is not null - Assert.NotNull (color); - // Check if the current color is 'more blue' and 'less red' as it goes right and down Assert.True (color.R <= previousRed, $"Failed at ({col}, {row}): {color.R} > {previousRed}"); Assert.True (color.B >= previousBlue, $"Failed at ({col}, {row}): {color.B} < {previousBlue}"); @@ -96,9 +87,6 @@ public void TestGradientFillColorTransition () var point = new Point (col, row); var color = gradientFill.GetColor (point); - // Ensure color is not null - Assert.NotNull (color); - // Check if the current color is 'more blue' and 'less red' as it goes right and down Assert.True (color.R <= previousRed, $"Failed at ({col}, {row}): {color.R} > {previousRed}"); Assert.True (color.B >= previousBlue, $"Failed at ({col}, {row}): {color.B} < {previousBlue}"); From 8a56586fec75737d68cbee023fd74354c0b39d3a Mon Sep 17 00:00:00 2001 From: tznind Date: Sun, 7 Jul 2024 21:30:31 +0100 Subject: [PATCH 20/30] Fix note comment and add tests for SolidFill class --- UnitTests/Drawing/GradientTests.cs | 2 +- UnitTests/Drawing/SolidFillTests.cs | 40 +++++++++++++++++++++++++++++ 2 files changed, 41 insertions(+), 1 deletion(-) create mode 100644 UnitTests/Drawing/SolidFillTests.cs diff --git a/UnitTests/Drawing/GradientTests.cs b/UnitTests/Drawing/GradientTests.cs index 0ff4c4a68d..8fcfc13543 100644 --- a/UnitTests/Drawing/GradientTests.cs +++ b/UnitTests/Drawing/GradientTests.cs @@ -45,7 +45,7 @@ public void GradientIsInclusive_1_by_1 (GradientDirection direction) var g = new Gradient (stops, steps, loop: false); - // Note that + // Note that maxRow and maxCol are inclusive so this results in 1x1 area i.e. a single cell. var c = Assert.Single (g.BuildCoordinateColorMapping (0, 0, direction)); Assert.Equal (c.Key, new Point(0,0)); Assert.Equal (c.Value, new Color (0, 0, 255)); diff --git a/UnitTests/Drawing/SolidFillTests.cs b/UnitTests/Drawing/SolidFillTests.cs new file mode 100644 index 0000000000..50eaa9f9f4 --- /dev/null +++ b/UnitTests/Drawing/SolidFillTests.cs @@ -0,0 +1,40 @@ + +using Terminal.Gui.Drawing; + +namespace Terminal.Gui.DrawingTests; + +public class SolidFillTests +{ + [Fact] + public void GetColor_ReturnsCorrectColor () + { + // Arrange + var expectedColor = new Color (100, 150, 200); + var solidFill = new SolidFill (expectedColor); + + // Act + var resultColor = solidFill.GetColor (new Point (0, 0)); + + // Assert + Assert.Equal (expectedColor, resultColor); + } + + [Theory] + [InlineData (0, 0)] + [InlineData (1, 1)] + [InlineData (-1, -1)] + [InlineData (100, 100)] + [InlineData (-100, -100)] + public void GetColor_ReturnsSameColorForDifferentPoints (int x, int y) + { + // Arrange + var expectedColor = new Color (50, 100, 150); + var solidFill = new SolidFill (expectedColor); + + // Act + var resultColor = solidFill.GetColor (new Point (x, y)); + + // Assert + Assert.Equal (expectedColor, resultColor); + } +} \ No newline at end of file From afc0bf02c504c5c0a827e3e9541ae11b4e0ed49b Mon Sep 17 00:00:00 2001 From: tznind Date: Sun, 7 Jul 2024 21:35:13 +0100 Subject: [PATCH 21/30] Fix dodgy constructor on FillPair and add tests --- Terminal.Gui/Drawing/FillPair.cs | 2 +- UnitTests/Drawing/FillPairTests.cs | 32 ++++++++++++++++++++++++++++++ 2 files changed, 33 insertions(+), 1 deletion(-) create mode 100644 UnitTests/Drawing/FillPairTests.cs diff --git a/Terminal.Gui/Drawing/FillPair.cs b/Terminal.Gui/Drawing/FillPair.cs index 2a6c7d5ae8..648dbb40f2 100644 --- a/Terminal.Gui/Drawing/FillPair.cs +++ b/Terminal.Gui/Drawing/FillPair.cs @@ -16,7 +16,7 @@ public class FillPair /// /// /// - public FillPair (GradientFill fore, SolidFill back) + public FillPair (IFill fore, IFill back) { Foreground = fore; Background = back; diff --git a/UnitTests/Drawing/FillPairTests.cs b/UnitTests/Drawing/FillPairTests.cs new file mode 100644 index 0000000000..bbe8b6563e --- /dev/null +++ b/UnitTests/Drawing/FillPairTests.cs @@ -0,0 +1,32 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Terminal.Gui.Drawing; + +namespace Terminal.Gui.DrawingTests; + +public class FillPairTests +{ + + [Fact] + public void GetAttribute_ReturnsCorrectColors () + { + // Arrange + var foregroundColor = new Color (100, 150, 200); + var backgroundColor = new Color (50, 75, 100); + var foregroundFill = new SolidFill (foregroundColor); + var backgroundFill = new SolidFill (backgroundColor); + + var fillPair = new FillPair (foregroundFill, backgroundFill); + + // Act + var resultAttribute = fillPair.GetAttribute (new Point (0, 0)); + + // Assert + Assert.Equal (foregroundColor, resultAttribute.Foreground); + Assert.Equal (backgroundColor, resultAttribute.Background); + } +} + From d15f3af388a5bc9b8ea177a14e0a241cbda187af Mon Sep 17 00:00:00 2001 From: tznind Date: Sun, 7 Jul 2024 21:41:19 +0100 Subject: [PATCH 22/30] Add tests that confirm LineCanvas behavior with Fill --- UnitTests/Drawing/LineCanvasTests.cs | 87 ++++++++++++++++++++++++++++ 1 file changed, 87 insertions(+) diff --git a/UnitTests/Drawing/LineCanvasTests.cs b/UnitTests/Drawing/LineCanvasTests.cs index 8426d39525..d160b5420b 100644 --- a/UnitTests/Drawing/LineCanvasTests.cs +++ b/UnitTests/Drawing/LineCanvasTests.cs @@ -1,4 +1,5 @@ using System.Text; +using Terminal.Gui.Drawing; using Xunit.Abstractions; namespace Terminal.Gui.DrawingTests; @@ -1303,6 +1304,92 @@ public void Zero_Length_Intersections () TestHelpers.AssertEqual (output, looksLike, $"{Environment.NewLine}{lc}"); } + [Fact] + public void LineCanvas_UsesFillCorrectly () + { + // Arrange + var foregroundColor = new Color (255, 0, 0); // Red + var backgroundColor = new Color (0, 0, 0); // Black + var foregroundFill = new SolidFill (foregroundColor); + var backgroundFill = new SolidFill (backgroundColor); + var fillPair = new FillPair (foregroundFill, backgroundFill); + + var lineCanvas = new LineCanvas + { + Fill = fillPair + }; + + // Act + lineCanvas.AddLine (new Point (0, 0), 5, Orientation.Horizontal, LineStyle.Single); + var cellMap = lineCanvas.GetCellMap (); + + // Assert + foreach (var cell in cellMap.Values) + { + Assert.NotNull (cell); + Assert.Equal (foregroundColor, cell.Value.Attribute.Value.Foreground); + Assert.Equal (backgroundColor, cell.Value.Attribute.Value.Background); + } + } + + [Fact] + public void LineCanvas_LineColorIgnoredBecauseOfFill () + { + // Arrange + var foregroundColor = new Color (255, 0, 0); // Red + var backgroundColor = new Color (0, 0, 0); // Black + var lineColor = new Attribute (new Color (0, 255, 0), new Color (255, 255, 255)); // Green on White + var foregroundFill = new SolidFill (foregroundColor); + var backgroundFill = new SolidFill (backgroundColor); + var fillPair = new FillPair (foregroundFill, backgroundFill); + + var lineCanvas = new LineCanvas + { + Fill = fillPair + }; + + // Act + lineCanvas.AddLine (new Point (0, 0), 5, Orientation.Horizontal, LineStyle.Single, lineColor); + var cellMap = lineCanvas.GetCellMap (); + + // Assert + foreach (var cell in cellMap.Values) + { + Assert.NotNull (cell); + Assert.Equal (foregroundColor, cell.Value.Attribute.Value.Foreground); + Assert.Equal (backgroundColor, cell.Value.Attribute.Value.Background); + } + } + + [Fact] + public void LineCanvas_IntersectingLinesUseFillCorrectly () + { + // Arrange + var foregroundColor = new Color (255, 0, 0); // Red + var backgroundColor = new Color (0, 0, 0); // Black + var foregroundFill = new SolidFill (foregroundColor); + var backgroundFill = new SolidFill (backgroundColor); + var fillPair = new FillPair (foregroundFill, backgroundFill); + + var lineCanvas = new LineCanvas + { + Fill = fillPair + }; + + // Act + lineCanvas.AddLine (new Point (0, 0), 5, Orientation.Horizontal, LineStyle.Single); + lineCanvas.AddLine (new Point (2, -2), 5, Orientation.Vertical, LineStyle.Single); + var cellMap = lineCanvas.GetCellMap (); + + // Assert + foreach (var cell in cellMap.Values) + { + Assert.NotNull (cell); + Assert.Equal (foregroundColor, cell.Value.Attribute.Value.Foreground); + Assert.Equal (backgroundColor, cell.Value.Attribute.Value.Background); + } + } + // TODO: Remove this and make all LineCanvas tests independent of View /// /// Creates a new into which a is rendered at From c62cd84b99d0df9a20148dfb7e4e782e555830a5 Mon Sep 17 00:00:00 2001 From: tznind Date: Sun, 7 Jul 2024 21:44:49 +0100 Subject: [PATCH 23/30] Add xml comment --- Terminal.Gui/Drawing/Gradient.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Terminal.Gui/Drawing/Gradient.cs b/Terminal.Gui/Drawing/Gradient.cs index 01d7d2490c..5091cc314b 100644 --- a/Terminal.Gui/Drawing/Gradient.cs +++ b/Terminal.Gui/Drawing/Gradient.cs @@ -38,6 +38,9 @@ public enum GradientDirection /// public class Gradient { + /// + /// The discrete colors that will make up the . + /// public List Spectrum { get; private set; } private readonly bool _loop; private readonly List _stops; From b9a8c7d2630c9e18cbb9a955f32e159da3d5a704 Mon Sep 17 00:00:00 2001 From: tznind Date: Sun, 7 Jul 2024 21:56:23 +0100 Subject: [PATCH 24/30] Fix GradientFill when not at origin --- Terminal.Gui/Drawing/GradientFill.cs | 7 +++-- UnitTests/Drawing/GradientFillTests.cs | 38 +++++++++++++++++++------- 2 files changed, 33 insertions(+), 12 deletions(-) diff --git a/Terminal.Gui/Drawing/GradientFill.cs b/Terminal.Gui/Drawing/GradientFill.cs index 31339ebe0c..6763de8857 100644 --- a/Terminal.Gui/Drawing/GradientFill.cs +++ b/Terminal.Gui/Drawing/GradientFill.cs @@ -18,7 +18,10 @@ public class GradientFill : IFill /// public GradientFill (Rectangle area, Gradient gradient, GradientDirection direction) { - _map = gradient.BuildCoordinateColorMapping (area.Height-1, area.Width-1, direction); + _map = gradient.BuildCoordinateColorMapping (area.Height - 1, area.Width - 1, direction) + .ToDictionary ( + kvp => new Point (kvp.Key.X + area.X, kvp.Key.Y + area.Y), + kvp => kvp.Value); } /// @@ -35,4 +38,4 @@ public Color GetColor (Point point) } return new Color (0, 0, 0); // Default to black if point not found } -} \ No newline at end of file +} diff --git a/UnitTests/Drawing/GradientFillTests.cs b/UnitTests/Drawing/GradientFillTests.cs index bf0d4d0e22..e1f035d8db 100644 --- a/UnitTests/Drawing/GradientFillTests.cs +++ b/UnitTests/Drawing/GradientFillTests.cs @@ -1,5 +1,4 @@ - -namespace Terminal.Gui.DrawingTests; +namespace Terminal.Gui.DrawingTests; public class GradientFillTests { @@ -21,7 +20,7 @@ public GradientFillTests () } [Fact] - public void TestGradientFillCorners () + public void TestGradientFillCorners_AtOrigin () { var area = new Rectangle (0, 0, 10, 10); var gradientFill = new GradientFill (area, _gradient, GradientDirection.Diagonal); @@ -38,17 +37,36 @@ public void TestGradientFillCorners () var bottomRightColor = gradientFill.GetColor (bottomRight); // Expected colors - var expectedTopLeftColor = new Terminal.Gui.Color (255, 0, 0); // Red - var expectedBottomRightColor = new Terminal.Gui.Color (0, 0, 255); // Blue + var expectedTopLeftColor = new Color (255, 0, 0); // Red + var expectedBottomRightColor = new Color (0, 0, 255); // Blue Assert.Equal (expectedTopLeftColor, topLeftColor); Assert.Equal (expectedBottomRightColor, bottomRightColor); + } + + [Fact] + public void TestGradientFillCorners_NotAtOrigin () + { + var area = new Rectangle (5, 5, 10, 10); + var gradientFill = new GradientFill (area, _gradient, GradientDirection.Diagonal); + + // Test the corners + var topLeft = new Point (5, 5); + var topRight = new Point (area.Right - 1, 5); + var bottomLeft = new Point (5, area.Bottom - 1); + var bottomRight = new Point (area.Right - 1, area.Bottom - 1); + + var topLeftColor = gradientFill.GetColor (topLeft); + var topRightColor = gradientFill.GetColor (topRight); + var bottomLeftColor = gradientFill.GetColor (bottomLeft); + var bottomRightColor = gradientFill.GetColor (bottomRight); - // Additional checks can be added to verify the exact expected colors if known - Console.WriteLine ($"Top-left: {topLeftColor}"); - Console.WriteLine ($"Top-right: {topRightColor}"); - Console.WriteLine ($"Bottom-left: {bottomLeftColor}"); - Console.WriteLine ($"Bottom-right: {bottomRightColor}"); + // Expected colors + var expectedTopLeftColor = new Color (255, 0, 0); // Red + var expectedBottomRightColor = new Color (0, 0, 255); // Blue + + Assert.Equal (expectedTopLeftColor, topLeftColor); + Assert.Equal (expectedBottomRightColor, bottomRightColor); } [Fact] From 18e1956b7c48769c5663bad019da2505b05610d8 Mon Sep 17 00:00:00 2001 From: tznind Date: Sun, 7 Jul 2024 22:12:49 +0100 Subject: [PATCH 25/30] Make gradients flow like a flow layout --- UICatalog/Scenarios/TextEffectsScenario.cs | 64 +++++++++++++++------- 1 file changed, 43 insertions(+), 21 deletions(-) diff --git a/UICatalog/Scenarios/TextEffectsScenario.cs b/UICatalog/Scenarios/TextEffectsScenario.cs index c660ddb5c5..b34e77321b 100644 --- a/UICatalog/Scenarios/TextEffectsScenario.cs +++ b/UICatalog/Scenarios/TextEffectsScenario.cs @@ -128,6 +128,11 @@ public static void GetAppealingGradientColors (out List stops, out List { - DrawGradientArea (GradientDirection.Horizontal, x, y); - DrawGradientArea (GradientDirection.Horizontal, x, y); - DrawGradientArea (GradientDirection.Vertical, x + 32, y); - DrawGradientArea (GradientDirection.Radial, x + 64, y); - DrawGradientArea (GradientDirection.Diagonal, x + 96, y); - } - else // Enough space, render in two lines + ("Horizontal", GradientDirection.Horizontal), + ("Vertical", GradientDirection.Vertical), + ("Radial", GradientDirection.Radial), + ("Diagonal", GradientDirection.Diagonal) + }; + + foreach (var (label, direction) in gradients) { - DrawGradientArea (GradientDirection.Horizontal, x, y); - DrawGradientArea (GradientDirection.Vertical, x + 32, y); - DrawGradientArea (GradientDirection.Radial, x, y + 17); - DrawGradientArea (GradientDirection.Diagonal, x + 32, y + 17); + if (x + GradientWidth > viewport.Width) + { + x = 2; // Reset to left margin + y += GradientWithLabelHeight; // Move down to next row + } + + DrawLabeledGradientArea (label, direction, x, y); + x += GradientWidth + 2; // Move right for next gradient, +2 for spacing } } + private void DrawLabeledGradientArea (string label, GradientDirection direction, int xOffset, int yOffset) + { + DrawGradientArea (direction, xOffset, yOffset); + CenterText (label, xOffset, yOffset + GradientHeight); // Adjusted for text below the gradient + } + private void CenterText (string text, int xOffset, int yOffset) + { + if(yOffset+1 >= Viewport.Height) + { + // Not enough space for label + return; + } + var width = text.Length; + var x = xOffset + (GradientWidth - width) / 2; // Center the text within the gradient area width + Driver.SetAttribute (GetNormalColor ()); + Move (x, yOffset+1); + Driver.AddStr (text); + } private void DrawGradientArea (GradientDirection direction, int xOffset, int yOffset) { @@ -174,8 +201,8 @@ private void DrawGradientArea (GradientDirection direction, int xOffset, int yOf var radialGradient = new Gradient (stops, steps, loop: TextEffectsScenario.LoopingGradient); // Define the size of the rectangle - int maxRow = 15; // Adjusted to keep aspect ratio - int maxColumn = 30; + int maxRow = GradientHeight; // Adjusted to keep aspect ratio + int maxColumn = GradientWidth; // Build the coordinate-color mapping for a radial gradient var gradientMapping = radialGradient.BuildCoordinateColorMapping (maxRow, maxColumn, direction); @@ -236,11 +263,6 @@ private void DrawTopLineGradient (Rectangle viewport) 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 + Application.Driver.SetAttribute (new Attribute (color, color)); } -} \ No newline at end of file +} From c981eefed8f9bcf9e44990af67fcf5c5b982d021 Mon Sep 17 00:00:00 2001 From: Thomas Date: Tue, 9 Jul 2024 16:54:00 +0100 Subject: [PATCH 26/30] Fix hanging xml comment --- Terminal.Gui/Drawing/Gradient.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Terminal.Gui/Drawing/Gradient.cs b/Terminal.Gui/Drawing/Gradient.cs index 5091cc314b..622f93239a 100644 --- a/Terminal.Gui/Drawing/Gradient.cs +++ b/Terminal.Gui/Drawing/Gradient.cs @@ -34,7 +34,9 @@ public enum GradientDirection } /// -/// Describes +/// Describes a of colors that can be combined +/// to make a color gradient. Use +/// to create into gradient fill area maps. /// public class Gradient { From 1ba84ded9a800206a1724a92980d634522c19bc7 Mon Sep 17 00:00:00 2001 From: Thomas Date: Tue, 9 Jul 2024 16:59:21 +0100 Subject: [PATCH 27/30] Fix bad namespace --- Terminal.Gui/Drawing/FillPair.cs | 2 -- Terminal.Gui/Drawing/SolidFill.cs | 2 +- UICatalog/Scenarios/TextEffectsScenario.cs | 2 -- UnitTests/Drawing/FillPairTests.cs | 1 - UnitTests/Drawing/LineCanvasTests.cs | 1 - UnitTests/Drawing/SolidFillTests.cs | 2 -- 6 files changed, 1 insertion(+), 9 deletions(-) diff --git a/Terminal.Gui/Drawing/FillPair.cs b/Terminal.Gui/Drawing/FillPair.cs index 648dbb40f2..5a7caf6d8c 100644 --- a/Terminal.Gui/Drawing/FillPair.cs +++ b/Terminal.Gui/Drawing/FillPair.cs @@ -1,6 +1,4 @@  -using Terminal.Gui.Drawing; - namespace Terminal.Gui; diff --git a/Terminal.Gui/Drawing/SolidFill.cs b/Terminal.Gui/Drawing/SolidFill.cs index 67e1bdf1f9..d456df8a9d 100644 --- a/Terminal.Gui/Drawing/SolidFill.cs +++ b/Terminal.Gui/Drawing/SolidFill.cs @@ -1,4 +1,4 @@ -namespace Terminal.Gui.Drawing; +namespace Terminal.Gui; /// diff --git a/UICatalog/Scenarios/TextEffectsScenario.cs b/UICatalog/Scenarios/TextEffectsScenario.cs index b34e77321b..71c75cfca8 100644 --- a/UICatalog/Scenarios/TextEffectsScenario.cs +++ b/UICatalog/Scenarios/TextEffectsScenario.cs @@ -4,8 +4,6 @@ using System.Threading; using Terminal.Gui; -using Terminal.Gui.Drawing; - namespace UICatalog.Scenarios; [ScenarioMetadata ("Text Effects", "Text Effects.")] diff --git a/UnitTests/Drawing/FillPairTests.cs b/UnitTests/Drawing/FillPairTests.cs index bbe8b6563e..34953a4a49 100644 --- a/UnitTests/Drawing/FillPairTests.cs +++ b/UnitTests/Drawing/FillPairTests.cs @@ -3,7 +3,6 @@ using System.Linq; using System.Text; using System.Threading.Tasks; -using Terminal.Gui.Drawing; namespace Terminal.Gui.DrawingTests; diff --git a/UnitTests/Drawing/LineCanvasTests.cs b/UnitTests/Drawing/LineCanvasTests.cs index d160b5420b..05df06bcf1 100644 --- a/UnitTests/Drawing/LineCanvasTests.cs +++ b/UnitTests/Drawing/LineCanvasTests.cs @@ -1,5 +1,4 @@ using System.Text; -using Terminal.Gui.Drawing; using Xunit.Abstractions; namespace Terminal.Gui.DrawingTests; diff --git a/UnitTests/Drawing/SolidFillTests.cs b/UnitTests/Drawing/SolidFillTests.cs index 50eaa9f9f4..9e70103d52 100644 --- a/UnitTests/Drawing/SolidFillTests.cs +++ b/UnitTests/Drawing/SolidFillTests.cs @@ -1,6 +1,4 @@  -using Terminal.Gui.Drawing; - namespace Terminal.Gui.DrawingTests; public class SolidFillTests From 65efddbc9ff1a7f080579c6bfec3c02407f434ff Mon Sep 17 00:00:00 2001 From: Tig Date: Tue, 9 Jul 2024 13:26:19 -0600 Subject: [PATCH 28/30] Added Border Settings. Enabled BorderSettings.Gradient --- Terminal.Gui/View/Adornment/Border.cs | 146 ++++++++++++------ Terminal.Gui/View/Adornment/BorderSettings.cs | 26 ++++ Terminal.Gui/View/Adornment/Margin.cs | 2 +- Terminal.Gui/Views/Shortcut.cs | 2 +- UICatalog/Scenarios/BorderEditor.cs | 58 +++++-- 5 files changed, 173 insertions(+), 61 deletions(-) create mode 100644 Terminal.Gui/View/Adornment/BorderSettings.cs diff --git a/Terminal.Gui/View/Adornment/Border.cs b/Terminal.Gui/View/Adornment/Border.cs index 39d98f635f..1ed64abca0 100644 --- a/Terminal.Gui/View/Adornment/Border.cs +++ b/Terminal.Gui/View/Adornment/Border.cs @@ -1,3 +1,5 @@ +using Terminal.Gui.Drawing; + namespace Terminal.Gui; /// The Border for a . @@ -78,7 +80,7 @@ public override void BeginInit () if ((Parent?.Arrangement & ViewArrangement.Movable) != 0) { HighlightStyle |= HighlightStyle.Hover; - } + } #endif base.BeginInit (); @@ -149,31 +151,32 @@ public override ColorScheme ColorScheme } } - Rectangle GetBorderRectangle (Rectangle screenRect) + private Rectangle GetBorderRectangle (Rectangle screenRect) { return new ( - screenRect.X + Math.Max (0, Thickness.Left - 1), - screenRect.Y + Math.Max (0, Thickness.Top - 1), - Math.Max ( - 0, - screenRect.Width - - Math.Max ( - 0, - Math.Max (0, Thickness.Left - 1) - + Math.Max (0, Thickness.Right - 1) - ) - ), - Math.Max ( - 0, - screenRect.Height - - Math.Max ( - 0, - Math.Max (0, Thickness.Top - 1) - + Math.Max (0, Thickness.Bottom - 1) - ) - ) - ); + screenRect.X + Math.Max (0, Thickness.Left - 1), + screenRect.Y + Math.Max (0, Thickness.Top - 1), + Math.Max ( + 0, + screenRect.Width + - Math.Max ( + 0, + Math.Max (0, Thickness.Left - 1) + + Math.Max (0, Thickness.Right - 1) + ) + ), + Math.Max ( + 0, + screenRect.Height + - Math.Max ( + 0, + Math.Max (0, Thickness.Top - 1) + + Math.Max (0, Thickness.Bottom - 1) + ) + ) + ); } + /// /// Sets the style of the border by changing the . This is a helper API for setting the /// to (1,1,1,1) and setting the line style of the views that comprise the border. If @@ -196,21 +199,22 @@ public LineStyle LineStyle set => _lineStyle = value; } - private bool _showTitle = true; + private BorderSettings _settings = BorderSettings.Title; /// - /// Gets or sets whether the title should be shown. The default is . + /// Gets or sets the settings for the border. /// - public bool ShowTitle + public BorderSettings Settings { - get => _showTitle; + get => _settings; set { - if (value == _showTitle) + if (value == _settings) { return; } - _showTitle = value; + + _settings = value; Parent?.SetNeedsDisplay (); } @@ -225,6 +229,7 @@ private void Border_Highlight (object sender, CancelEventArgs e) if (!Parent.Arrangement.HasFlag (ViewArrangement.Movable)) { e.Cancel = true; + return; } @@ -235,9 +240,9 @@ private void Border_Highlight (object sender, CancelEventArgs e) _savedForeColor = ColorScheme.Normal.Foreground; } - ColorScheme cs = new ColorScheme (ColorScheme) + var cs = new ColorScheme (ColorScheme) { - Normal = new Attribute (ColorScheme.Normal.Foreground.GetHighlightColor (), ColorScheme.Normal.Background) + Normal = new (ColorScheme.Normal.Foreground.GetHighlightColor (), ColorScheme.Normal.Background) }; ColorScheme = cs; } @@ -254,12 +259,13 @@ private void Border_Highlight (object sender, CancelEventArgs e) if (e.NewValue == HighlightStyle.None && _savedForeColor.HasValue) { - ColorScheme cs = new ColorScheme (ColorScheme) + var cs = new ColorScheme (ColorScheme) { - Normal = new Attribute (_savedForeColor.Value, ColorScheme.Normal.Background) + Normal = new (_savedForeColor.Value, ColorScheme.Normal.Background) }; ColorScheme = cs; } + Parent?.SetNeedsDisplay (); e.Cancel = true; } @@ -267,7 +273,7 @@ private void Border_Highlight (object sender, CancelEventArgs e) private Point? _dragPosition; private Point _startGrabPoint; - /// + /// protected internal override bool OnMouseEvent (MouseEvent mouseEvent) { if (base.OnMouseEvent (mouseEvent)) @@ -322,16 +328,17 @@ protected internal override bool OnMouseEvent (MouseEvent mouseEvent) _dragPosition = mouseEvent.Position; - Point parentLoc = Parent.SuperView?.ScreenToViewport (new (mouseEvent.ScreenPosition.X, mouseEvent.ScreenPosition.Y)) ?? mouseEvent.ScreenPosition; + Point parentLoc = Parent.SuperView?.ScreenToViewport (new (mouseEvent.ScreenPosition.X, mouseEvent.ScreenPosition.Y)) + ?? mouseEvent.ScreenPosition; GetLocationEnsuringFullVisibility ( - Parent, - parentLoc.X - _startGrabPoint.X, - parentLoc.Y - _startGrabPoint.Y, - out int nx, - out int ny, - out _ - ); + Parent, + parentLoc.X - _startGrabPoint.X, + parentLoc.Y - _startGrabPoint.Y, + out int nx, + out int ny, + out _ + ); Parent.X = nx; Parent.Y = ny; @@ -352,7 +359,6 @@ out _ return false; } - /// protected override void Dispose (bool disposing) { @@ -403,7 +409,7 @@ public override void OnDrawContent (Rectangle viewport) // ...thickness extends outward (border/title is always as far in as possible) // PERF: How about a call to Rectangle.Offset? - var borderBounds = GetBorderRectangle (screenBounds); + Rectangle borderBounds = GetBorderRectangle (screenBounds); int topTitleLineY = borderBounds.Y; int titleY = borderBounds.Y; var titleBarsLength = 0; // the little vertical thingies @@ -421,7 +427,7 @@ public override void OnDrawContent (Rectangle viewport) int sideLineLength = borderBounds.Height; bool canDrawBorder = borderBounds is { Width: > 0, Height: > 0 }; - if (ShowTitle) + if (Settings.FastHasFlags (BorderSettings.Title)) { if (Thickness.Top == 2) { @@ -453,9 +459,10 @@ public override void OnDrawContent (Rectangle viewport) } } - if (canDrawBorder && Thickness.Top > 0 && maxTitleWidth > 0 && ShowTitle && !string.IsNullOrEmpty (Parent?.Title)) + if (canDrawBorder && Thickness.Top > 0 && maxTitleWidth > 0 && Settings.FastHasFlags (BorderSettings.Title) && !string.IsNullOrEmpty (Parent?.Title)) { - var focus = Parent.GetNormalColor (); + Attribute focus = Parent.GetNormalColor (); + if (Parent.SuperView is { } && Parent.SuperView?.Subviews!.Count (s => s.CanFocus) > 1) { // Only use focus color if there are multiple focusable views @@ -492,7 +499,7 @@ public override void OnDrawContent (Rectangle viewport) { // ╔╡Title╞═════╗ // ╔╡╞═════╗ - if (borderBounds.Width < 4 || !ShowTitle || string.IsNullOrEmpty (Parent?.Title)) + if (borderBounds.Width < 4 || !Settings.FastHasFlags (BorderSettings.Title) || string.IsNullOrEmpty (Parent?.Title)) { // ╔╡╞╗ should be ╔══╗ lc.AddLine ( @@ -631,7 +638,7 @@ public override void OnDrawContent (Rectangle viewport) Driver.SetAttribute (prevAttr); // TODO: This should be moved to LineCanvas as a new BorderStyle.Ruler - if (View.Diagnostics.HasFlag (ViewDiagnosticFlags.Ruler)) + if (Diagnostics.HasFlag (ViewDiagnosticFlags.Ruler)) { // Top var hruler = new Ruler { Length = screenBounds.Width, Orientation = Orientation.Horizontal }; @@ -642,7 +649,7 @@ public override void OnDrawContent (Rectangle viewport) } // Redraw title - if (drawTop && maxTitleWidth > 0 && ShowTitle) + if (drawTop && maxTitleWidth > 0 && Settings.FastHasFlags (BorderSettings.Title)) { Parent.TitleTextFormatter.Draw ( new (borderBounds.X + 2, titleY, maxTitleWidth, 1), @@ -670,6 +677,45 @@ public override void OnDrawContent (Rectangle viewport) vruler.Draw (new (screenBounds.X + screenBounds.Width - 1, screenBounds.Y + 1), 1); } } + + // TODO: This should not be done on each draw? + if (Settings.FastHasFlags (BorderSettings.Gradient)) + { + SetupGradientLineCanvas (lc, screenBounds); + } + else + { + lc.Fill = null; + } } } + + private void SetupGradientLineCanvas (LineCanvas lc, Rectangle rect) + { + GetAppealingGradientColors (out List stops, out List steps); + + var g = new Gradient (stops, steps); + + var fore = new GradientFill (rect, g, GradientDirection.Diagonal); + var back = new SolidFill (GetNormalColor ().Background); + + lc.Fill = new (fore, back); + } + + private static void GetAppealingGradientColors (out List stops, out List steps) + { + // Define the colors of the gradient stops with more appealing colors + stops = new() + { + new (0, 128, 255), // Bright Blue + new (0, 255, 128), // Bright Green + new (255, 255), // Bright Yellow + new (255, 128), // Bright Orange + new (255, 0, 128) // Bright Pink + }; + + // Define the number of steps between each color for smoother transitions + // If we pass only a single value then it will assume equal steps between all pairs + steps = new() { 15 }; + } } diff --git a/Terminal.Gui/View/Adornment/BorderSettings.cs b/Terminal.Gui/View/Adornment/BorderSettings.cs new file mode 100644 index 0000000000..5829d1ed67 --- /dev/null +++ b/Terminal.Gui/View/Adornment/BorderSettings.cs @@ -0,0 +1,26 @@ +using Terminal.Gui.Analyzers.Internal.Attributes; + +namespace Terminal.Gui; + +/// +/// Determines the settings for . +/// +[Flags] +[GenerateEnumExtensionMethods (FastHasFlags = true)] +public enum BorderSettings +{ + /// + /// No settings. + /// + None = 0, + + /// + /// Show the title. + /// + Title = 1, + + /// + /// Use to draw the border. + /// + Gradient = 2, +} diff --git a/Terminal.Gui/View/Adornment/Margin.cs b/Terminal.Gui/View/Adornment/Margin.cs index 2e1ea57602..046965e321 100644 --- a/Terminal.Gui/View/Adornment/Margin.cs +++ b/Terminal.Gui/View/Adornment/Margin.cs @@ -223,7 +223,7 @@ private void Margin_LayoutStarted (object? sender, LayoutEventArgs e) if (ShadowStyle != ShadowStyle.None && _rightShadow is { } && _bottomShadow is { }) { _rightShadow.Y = Parent.Border.Thickness.Top > 0 - ? Parent.Border.Thickness.Top - (Parent.Border.Thickness.Top > 2 && Parent.Border.ShowTitle ? 1 : 0) + ? Parent.Border.Thickness.Top - (Parent.Border.Thickness.Top > 2 && Parent.Border.Settings.FastHasFlags (BorderSettings.Title) ? 1 : 0) : 1; _bottomShadow.X = Parent.Border.Thickness.Left > 0 ? Parent.Border.Thickness.Left : 1; } diff --git a/Terminal.Gui/Views/Shortcut.cs b/Terminal.Gui/Views/Shortcut.cs index 802631d521..7ddbe7c5a0 100644 --- a/Terminal.Gui/Views/Shortcut.cs +++ b/Terminal.Gui/Views/Shortcut.cs @@ -104,7 +104,7 @@ public Shortcut (Key key, string commandText, Action action, string helpText = n void OnInitialized (object sender, EventArgs e) { SuperViewRendersLineCanvas = true; - Border.ShowTitle = false; + Border.Settings &= ~BorderSettings.Title; ShowHide (); diff --git a/UICatalog/Scenarios/BorderEditor.cs b/UICatalog/Scenarios/BorderEditor.cs index a5ccae2129..5e0a6e77a6 100644 --- a/UICatalog/Scenarios/BorderEditor.cs +++ b/UICatalog/Scenarios/BorderEditor.cs @@ -9,29 +9,30 @@ public class BorderEditor : AdornmentEditor { private CheckBox _ckbTitle; private RadioGroup _rbBorderStyle; + private CheckBox _ckbGradient; public BorderEditor () { Title = "_Border"; Initialized += BorderEditor_Initialized; AdornmentChanged += BorderEditor_AdornmentChanged; - } private void BorderEditor_AdornmentChanged (object sender, EventArgs e) { - _ckbTitle.State = ((Border)AdornmentToEdit).ShowTitle ? CheckState.Checked : CheckState.UnChecked; + _ckbTitle.State = ((Border)AdornmentToEdit).Settings.FastHasFlags (BorderSettings.Title) ? CheckState.Checked : CheckState.UnChecked; _rbBorderStyle.SelectedItem = (int)((Border)AdornmentToEdit).LineStyle; + _ckbGradient.State = ((Border)AdornmentToEdit).Settings.FastHasFlags (BorderSettings.Gradient) ? CheckState.Checked : CheckState.UnChecked; } private void BorderEditor_Initialized (object sender, EventArgs e) { - List borderStyleEnum = Enum.GetValues (typeof (LineStyle)).Cast ().ToList (); - _rbBorderStyle = new RadioGroup + _rbBorderStyle = new() { X = 0, + // BUGBUG: Hack until dimauto is working properly Y = Pos.Bottom (Subviews [^1]), Width = Dim.Width (Subviews [^2]) + Dim.Width (Subviews [^1]) - 1, @@ -46,21 +47,34 @@ private void BorderEditor_Initialized (object sender, EventArgs e) _rbBorderStyle.SelectedItemChanged += OnRbBorderStyleOnSelectedItemChanged; - _ckbTitle = new CheckBox + _ckbTitle = new() { X = 0, Y = Pos.Bottom (_rbBorderStyle), State = CheckState.Checked, SuperViewRendersLineCanvas = true, - Text = "Show Title", + Text = "Title", Enabled = AdornmentToEdit is { } }; - _ckbTitle.Toggle += OnCkbTitleOnToggle; Add (_ckbTitle); + _ckbGradient = new () + { + X = 0, + Y = Pos.Bottom (_ckbTitle), + + State = CheckState.Checked, + SuperViewRendersLineCanvas = true, + Text = "Gradient", + Enabled = AdornmentToEdit is { } + }; + + _ckbGradient.Toggle += OnCkbGradientOnToggle; + Add (_ckbGradient); + return; void OnRbBorderStyleOnSelectedItemChanged (object s, SelectedItemChangedArgs e) @@ -81,6 +95,32 @@ void OnRbBorderStyleOnSelectedItemChanged (object s, SelectedItemChangedArgs e) LayoutSubviews (); } - void OnCkbTitleOnToggle (object sender, CancelEventArgs args) { ((Border)AdornmentToEdit).ShowTitle = args.NewValue == CheckState.Checked; } + void OnCkbTitleOnToggle (object sender, CancelEventArgs args) + { + if (args.NewValue == CheckState.Checked) + + { + ((Border)AdornmentToEdit).Settings |= BorderSettings.Title; + } + else + + { + ((Border)AdornmentToEdit).Settings &= ~BorderSettings.Title; + } + } + + void OnCkbGradientOnToggle (object sender, CancelEventArgs args) + { + if (args.NewValue == CheckState.Checked) + + { + ((Border)AdornmentToEdit).Settings |= BorderSettings.Gradient; + } + else + + { + ((Border)AdornmentToEdit).Settings &= ~BorderSettings.Gradient; + } + } } -} \ No newline at end of file +} From d0f1280f293ae5d63bd0cff442c51655a8e481e4 Mon Sep 17 00:00:00 2001 From: Tig Date: Tue, 9 Jul 2024 13:37:08 -0600 Subject: [PATCH 29/30] Code cleanup --- Terminal.Gui/Drawing/FillPair.cs | 27 +-- Terminal.Gui/Drawing/Gradient.cs | 138 ++++++------ Terminal.Gui/Drawing/GradientFill.cs | 27 +-- Terminal.Gui/Drawing/IFill.cs | 9 +- Terminal.Gui/Drawing/LineCanvas.cs | 29 ++- Terminal.Gui/Drawing/SolidFill.cs | 23 +- Terminal.Gui/Drawing/StraightLine.cs | 71 +++--- UICatalog/Scenarios/TextEffectsScenario.cs | 209 +++++++++--------- UnitTests/Drawing/FillPairTests.cs | 11 +- UnitTests/Drawing/GradientFillTests.cs | 52 ++--- UnitTests/Drawing/GradientTests.cs | 164 +++++++------- UnitTests/Drawing/LineCanvasTests.cs | 96 ++++---- UnitTests/Drawing/SolidFillTests.cs | 9 +- .../Drawing/StraightLineExtensionsTests.cs | 25 +-- UnitTests/Drawing/StraightLineTests.cs | 9 +- 15 files changed, 442 insertions(+), 457 deletions(-) diff --git a/Terminal.Gui/Drawing/FillPair.cs b/Terminal.Gui/Drawing/FillPair.cs index 648dbb40f2..2150f778d5 100644 --- a/Terminal.Gui/Drawing/FillPair.cs +++ b/Terminal.Gui/Drawing/FillPair.cs @@ -1,18 +1,14 @@ - -using Terminal.Gui.Drawing; - -namespace Terminal.Gui; - +namespace Terminal.Gui; /// -/// Describes a pair of which cooperate in creating -/// . One gives foreground color while other gives background. +/// Describes a pair of which cooperate in creating +/// . One gives foreground color while other gives background. /// public class FillPair { /// - /// Creates a new instance using the provided fills for foreground and background - /// color when assembling . + /// Creates a new instance using the provided fills for foreground and background + /// color when assembling . /// /// /// @@ -23,26 +19,23 @@ public FillPair (IFill fore, IFill back) } /// - /// The fill which provides point based foreground color. + /// The fill which provides point based foreground color. /// public IFill Foreground { get; init; } /// - /// The fill which provides point based background color. + /// The fill which provides point based background color. /// public IFill Background { get; init; } /// - /// Returns the color pair (foreground+background) to use when rendering - /// a rune at the given . + /// Returns the color pair (foreground+background) to use when rendering + /// a rune at the given . /// /// /// public Attribute GetAttribute (Point point) { - return new Attribute ( - Foreground.GetColor (point), - Background.GetColor (point) - ); + return new (Foreground.GetColor (point), Background.GetColor (point)); } } diff --git a/Terminal.Gui/Drawing/Gradient.cs b/Terminal.Gui/Drawing/Gradient.cs index 5091cc314b..fa1cb04823 100644 --- a/Terminal.Gui/Drawing/Gradient.cs +++ b/Terminal.Gui/Drawing/Gradient.cs @@ -2,59 +2,57 @@ namespace Terminal.Gui; -using System; -using System.Collections.Generic; -using System.Linq; - /// -/// Describes the pattern that a results in e.g. , etc +/// Describes the pattern that a results in e.g. , +/// etc /// public enum GradientDirection { /// - /// Color varies along Y axis but is constant on X axis. + /// Color varies along Y axis but is constant on X axis. /// Vertical, /// - /// Color varies along X axis but is constant on Y axis. + /// Color varies along X axis but is constant on Y axis. /// Horizontal, - /// - /// Color varies by distance from center (i.e. in circular ripples) + /// Color varies by distance from center (i.e. in circular ripples) /// Radial, /// - /// Color varies by X and Y axis (i.e. a slanted gradient) + /// Color varies by X and Y axis (i.e. a slanted gradient) /// Diagonal } /// -/// Describes +/// Describes /// public class Gradient { /// - /// The discrete colors that will make up the . + /// The discrete colors that will make up the . /// - public List Spectrum { get; private set; } + public List Spectrum { get; } + private readonly bool _loop; private readonly List _stops; private readonly List _steps; - /// - /// Creates a new instance of the class which hosts a - /// of colors including all and interpolated colors - /// between each corresponding pair. + /// Creates a new instance of the class which hosts a + /// of colors including all and interpolated colors + /// between each corresponding pair. /// /// The colors to use in the spectrum (N) - /// The number of colors to generate between each pair (must be N-1 numbers). - /// If only one step is passed then it is assumed to be the same distance for all pairs. + /// + /// The number of colors to generate between each pair (must be N-1 numbers). + /// If only one step is passed then it is assumed to be the same distance for all pairs. + /// /// True to duplicate the first stop and step so that the gradient repeats itself /// public Gradient (IEnumerable stops, IEnumerable steps, bool loop = false) @@ -71,13 +69,13 @@ public Gradient (IEnumerable stops, IEnumerable steps, bool loop = f // If multiple colors and only 1 step assume same distance applies to all steps if (_stops.Count > 2 && _steps.Count == 1) { - _steps = Enumerable.Repeat (_steps.Single (),_stops.Count() - 1).ToList(); + _steps = Enumerable.Repeat (_steps.Single (), _stops.Count () - 1).ToList (); } if (_steps.Any (step => step < 1)) { throw new ArgumentException ("Steps must be greater than 0."); - } + } if (_steps.Count != _stops.Count - 1) { @@ -89,11 +87,13 @@ public Gradient (IEnumerable stops, IEnumerable steps, bool loop = f } /// - /// Returns the color to use at the given part of the spectrum + /// Returns the color to use at the given part of the spectrum /// - /// Proportion of the way through the spectrum, must be between - /// 0 and 1 (inclusive). Returns the last color if is - /// . + /// + /// Proportion of the way through the spectrum, must be between + /// 0 and 1 (inclusive). Returns the last color if is + /// . + /// /// /// public Color GetColorAtFraction (double fraction) @@ -103,30 +103,32 @@ public Color GetColorAtFraction (double fraction) return Spectrum.Last (); } - if (fraction < 0 || fraction > 1) + if (fraction is < 0 or > 1) { - throw new ArgumentOutOfRangeException (nameof (fraction), "Fraction must be between 0 and 1."); + throw new ArgumentOutOfRangeException (nameof (fraction), @"Fraction must be between 0 and 1."); } - int index = (int)(fraction * (Spectrum.Count - 1)); + var index = (int)(fraction * (Spectrum.Count - 1)); + return Spectrum [index]; } private List GenerateGradient (IEnumerable steps) { - List gradient = new List (); + List gradient = new (); if (_stops.Count == 1) { - for (int i = 0; i < steps.Sum (); i++) + for (var i = 0; i < steps.Sum (); i++) { gradient.Add (_stops [0]); } + return gradient; } - var stopsToUse = _stops.ToList (); - var stepsToUse = _steps.ToList (); + List stopsToUse = _stops.ToList (); + List stepsToUse = _steps.ToList (); if (_loop) { @@ -135,9 +137,9 @@ private List GenerateGradient (IEnumerable steps) } var colorPairs = stopsToUse.Zip (stopsToUse.Skip (1), (start, end) => new { start, end }); - var stepsList = stepsToUse; + List stepsList = stepsToUse; - foreach (var (colorPair, thesteps) in colorPairs.Zip (stepsList, (pair, step) => (pair, step))) + foreach ((var colorPair, int thesteps) in colorPairs.Zip (stepsList, (pair, step) => (pair, step))) { gradient.AddRange (InterpolateColors (colorPair.start, colorPair.end, thesteps)); } @@ -145,28 +147,29 @@ private List GenerateGradient (IEnumerable steps) return gradient; } - private IEnumerable InterpolateColors (Color start, Color end, int steps) + private static IEnumerable InterpolateColors (Color start, Color end, int steps) { - for (int step = 0; step < steps; step++) + for (var step = 0; step < 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 new Color (r, g, b); + var r = (int)(start.R + fraction * (end.R - start.R)); + var g = (int)(start.G + fraction * (end.G - start.G)); + var b = (int)(start.B + fraction * (end.B - start.B)); + + yield return new (r, g, b); } + yield return end; // Ensure the last color is included } - /// - /// - /// Creates a mapping starting at 0,0 and going to and - /// (inclusively) using the supplied . - /// - /// - /// Note that this method is inclusive i.e. passing 1/1 results in 4 mapped coordinates. - /// + /// + /// Creates a mapping starting at 0,0 and going to and + /// (inclusively) using the supplied . + /// + /// + /// Note that this method is inclusive i.e. passing 1/1 results in 4 mapped coordinates. + /// /// /// /// @@ -174,63 +177,69 @@ private IEnumerable InterpolateColors (Color start, Color end, int steps) /// public Dictionary BuildCoordinateColorMapping (int maxRow, int maxColumn, GradientDirection direction) { - var gradientMapping = new Dictionary (); + Dictionary gradientMapping = new (); switch (direction) { case GradientDirection.Vertical: - for (int row = 0; row <= maxRow; row++) + for (var 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++) + + for (var col = 0; col <= maxColumn; col++) { - gradientMapping [new Point (col, row)] = color; + gradientMapping [new (col, row)] = color; } } + break; case GradientDirection.Horizontal: - for (int col = 0; col <= maxColumn; col++) + for (var 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++) + + for (var row = 0; row <= maxRow; row++) { - gradientMapping [new Point (col, row)] = color; + gradientMapping [new (col, row)] = color; } } + break; case GradientDirection.Radial: - for (int row = 0; row <= maxRow; row++) + for (var row = 0; row <= maxRow; row++) { - for (int col = 0; col <= maxColumn; col++) + for (var col = 0; col <= maxColumn; col++) { - double distanceFromCenter = FindNormalizedDistanceFromCenter (maxRow, maxColumn, new Point (col, row)); + double distanceFromCenter = FindNormalizedDistanceFromCenter (maxRow, maxColumn, new (col, row)); Color color = GetColorAtFraction (distanceFromCenter); - gradientMapping [new Point (col, row)] = color; + gradientMapping [new (col, row)] = color; } } + break; case GradientDirection.Diagonal: - for (int row = 0; row <= maxRow; row++) + for (var row = 0; row <= maxRow; row++) { - for (int col = 0; col <= maxColumn; col++) + for (var col = 0; col <= maxColumn; col++) { double fraction = ((double)row * 2 + col) / (maxRow * 2 + maxColumn); Color color = GetColorAtFraction (fraction); - gradientMapping [new Point (col, row)] = color; + gradientMapping [new (col, row)] = color; } } + break; } return gradientMapping; } - private double FindNormalizedDistanceFromCenter (int maxRow, int maxColumn, Point coord) + private static double FindNormalizedDistanceFromCenter (int maxRow, int maxColumn, Point coord) { double centerX = maxColumn / 2.0; double centerY = maxRow / 2.0; @@ -238,6 +247,7 @@ private double FindNormalizedDistanceFromCenter (int maxRow, int maxColumn, Poin double dy = coord.Y - centerY; double distance = Math.Sqrt (dx * dx + dy * dy); double maxDistance = Math.Sqrt (centerX * centerX + centerY * centerY); + return distance / maxDistance; } -} \ No newline at end of file +} diff --git a/Terminal.Gui/Drawing/GradientFill.cs b/Terminal.Gui/Drawing/GradientFill.cs index 6763de8857..6518d2dabb 100644 --- a/Terminal.Gui/Drawing/GradientFill.cs +++ b/Terminal.Gui/Drawing/GradientFill.cs @@ -1,17 +1,17 @@ namespace Terminal.Gui; /// -/// Implementation of that uses a color gradient (including -/// radial, diagonal etc). +/// Implementation of that uses a color gradient (including +/// radial, diagonal etc.). /// public class GradientFill : IFill { - private Dictionary _map; + private readonly Dictionary _map; /// - /// Creates a new instance of the class that can return - /// color for any point in the given using the provided - /// and . + /// Creates a new instance of the class that can return + /// color for any point in the given using the provided + /// and . /// /// /// @@ -19,23 +19,24 @@ public class GradientFill : IFill public GradientFill (Rectangle area, Gradient gradient, GradientDirection direction) { _map = gradient.BuildCoordinateColorMapping (area.Height - 1, area.Width - 1, direction) - .ToDictionary ( - kvp => new Point (kvp.Key.X + area.X, kvp.Key.Y + area.Y), - kvp => kvp.Value); + .ToDictionary ( + kvp => new Point (kvp.Key.X + area.X, kvp.Key.Y + area.Y), + kvp => kvp.Value); } /// - /// Returns the color to use for the given or Black if it - /// lies outside of the prepared gradient area (see constructor). + /// Returns the color to use for the given or Black if it + /// lies outside the prepared gradient area (see constructor). /// /// /// public Color GetColor (Point point) { - if (_map.TryGetValue (point, out var color)) + if (_map.TryGetValue (point, out Color color)) { return color; } - return new Color (0, 0, 0); // Default to black if point not found + + return new (0, 0); // Default to black if point not found } } diff --git a/Terminal.Gui/Drawing/IFill.cs b/Terminal.Gui/Drawing/IFill.cs index 8f81d305a7..7d1d19a68f 100644 --- a/Terminal.Gui/Drawing/IFill.cs +++ b/Terminal.Gui/Drawing/IFill.cs @@ -1,15 +1,14 @@ - -namespace Terminal.Gui; +namespace Terminal.Gui; /// -/// Describes an area fill (e.g. solid color or gradient). +/// Describes an area fill (e.g. solid color or gradient). /// public interface IFill { /// - /// Returns the color that should be used at the given point + /// Returns the color that should be used at the given point /// /// /// Color GetColor (Point point); -} \ No newline at end of file +} diff --git a/Terminal.Gui/Drawing/LineCanvas.cs b/Terminal.Gui/Drawing/LineCanvas.cs index 2c7367fcfb..2bda9e5dd4 100644 --- a/Terminal.Gui/Drawing/LineCanvas.cs +++ b/Terminal.Gui/Drawing/LineCanvas.cs @@ -5,9 +5,9 @@ namespace Terminal.Gui; public class LineCanvas : IDisposable { /// - /// Optional which when present overrides the - /// (colors) of lines in the canvas. This can be used e.g. to apply a global - /// across all lines. + /// Optional which when present overrides the + /// (colors) of lines in the canvas. This can be used e.g. to apply a global + /// across all lines. /// public FillPair? Fill { get; set; } @@ -142,7 +142,7 @@ public void AddLine ( ) { _cachedViewport = Rectangle.Empty; - _lines.Add (new StraightLine (start, length, orientation, style, attribute)); + _lines.Add (new (start, length, orientation, style, attribute)); } /// Adds a new line to the canvas @@ -190,7 +190,7 @@ public void Clear () if (cell is { }) { - map.Add (new Point (x, y), cell); + map.Add (new (x, y), cell); } } } @@ -225,7 +225,7 @@ public Dictionary GetMap (Rectangle inArea) if (rune is { }) { - map.Add (new Point (x, y), rune.Value); + map.Add (new (x, y), rune.Value); } } } @@ -333,8 +333,7 @@ private void ConfigurationManager_Applied (object? sender, ConfigurationManagerE private Attribute? GetAttributeForIntersects (IntersectionDefinition? [] intersects) { - return Fill != null ? Fill.GetAttribute (intersects [0]!.Point) : - intersects [0]!.Line.Attribute; + return Fill != null ? Fill.GetAttribute (intersects [0]!.Point) : intersects [0]!.Line.Attribute; } private Cell? GetCellForIntersects (ConsoleDriver driver, IntersectionDefinition? [] intersects) @@ -439,12 +438,12 @@ private void ConfigurationManager_Applied (object? sender, ConfigurationManagerE useThickDotted ? Glyphs.VLineHvDa4 : Glyphs.VLine; default: - throw new Exception ( - "Could not find resolver or switch case for " - + nameof (runeType) - + ":" - + runeType - ); + throw new ( + "Could not find resolver or switch case for " + + nameof (runeType) + + ":" + + runeType + ); } } @@ -854,4 +853,4 @@ public override void SetGlyphs () _normal = Glyphs.URCorner; } } -} \ No newline at end of file +} diff --git a/Terminal.Gui/Drawing/SolidFill.cs b/Terminal.Gui/Drawing/SolidFill.cs index 67e1bdf1f9..70c549608d 100644 --- a/Terminal.Gui/Drawing/SolidFill.cs +++ b/Terminal.Gui/Drawing/SolidFill.cs @@ -1,31 +1,24 @@ namespace Terminal.Gui.Drawing; - /// -/// implementation that uses a solid color for all points +/// implementation that uses a solid color for all points /// public class SolidFill : IFill { - readonly Color _color; + private readonly Color _color; /// - /// Creates a new instance of the class which will return - /// the provided regardless of which point is requested. + /// Creates a new instance of the class which will return + /// the provided regardless of which point is requested. /// /// - public SolidFill (Color color) - { - _color = color; - } + public SolidFill (Color color) { _color = color; } /// - /// Returns the color this instance was constructed with regardless of - /// which is being colored. + /// Returns the color this instance was constructed with regardless of + /// which is being colored. /// /// /// - public Color GetColor (Point point) - { - return _color; - } + public Color GetColor (Point point) { return _color; } } diff --git a/Terminal.Gui/Drawing/StraightLine.cs b/Terminal.Gui/Drawing/StraightLine.cs index 8bbd83494c..2f36995df6 100644 --- a/Terminal.Gui/Drawing/StraightLine.cs +++ b/Terminal.Gui/Drawing/StraightLine.cs @@ -45,6 +45,7 @@ public StraightLine ( /// Gets the rectangle that describes the bounds of the canvas. Location is the coordinates of the line that is /// furthest left/top and Size is defined by the line that extends the furthest right/bottom. /// + // PERF: Probably better to store the rectangle rather than make a new one on every single access to Viewport. internal Rectangle Viewport { @@ -115,22 +116,24 @@ IntersectionType typeWhenPositive if (StartsAt (x, y)) { - return new IntersectionDefinition (p, - GetTypeByLength ( - IntersectionType.StartLeft, - IntersectionType.PassOverHorizontal, - IntersectionType.StartRight - ), - this - ); + return new ( + p, + GetTypeByLength ( + IntersectionType.StartLeft, + IntersectionType.PassOverHorizontal, + IntersectionType.StartRight + ), + this + ); } if (EndsAt (x, y)) { - return new IntersectionDefinition (p, - Length < 0 ? IntersectionType.StartRight : IntersectionType.StartLeft, - this - ); + return new ( + p, + Length < 0 ? IntersectionType.StartRight : IntersectionType.StartLeft, + this + ); } int xmin = Math.Min (Start.X, Start.X + Length); @@ -138,10 +141,11 @@ IntersectionType typeWhenPositive if (xmin < x && xmax > x) { - return new IntersectionDefinition (p, - IntersectionType.PassOverHorizontal, - this - ); + return new ( + p, + IntersectionType.PassOverHorizontal, + this + ); } return null; @@ -158,22 +162,24 @@ IntersectionType typeWhenPositive if (StartsAt (x, y)) { - return new IntersectionDefinition (p, - GetTypeByLength ( - IntersectionType.StartUp, - IntersectionType.PassOverVertical, - IntersectionType.StartDown - ), - this - ); + return new ( + p, + GetTypeByLength ( + IntersectionType.StartUp, + IntersectionType.PassOverVertical, + IntersectionType.StartDown + ), + this + ); } if (EndsAt (x, y)) { - return new IntersectionDefinition (p, - Length < 0 ? IntersectionType.StartDown : IntersectionType.StartUp, - this - ); + return new ( + p, + Length < 0 ? IntersectionType.StartDown : IntersectionType.StartUp, + this + ); } int ymin = Math.Min (Start.Y, Start.Y + Length); @@ -181,10 +187,11 @@ IntersectionType typeWhenPositive if (ymin < y && ymax > y) { - return new IntersectionDefinition (p, - IntersectionType.PassOverVertical, - this - ); + return new ( + p, + IntersectionType.PassOverVertical, + this + ); } return null; diff --git a/UICatalog/Scenarios/TextEffectsScenario.cs b/UICatalog/Scenarios/TextEffectsScenario.cs index b34e77321b..4c4f5d7cbb 100644 --- a/UICatalog/Scenarios/TextEffectsScenario.cs +++ b/UICatalog/Scenarios/TextEffectsScenario.cs @@ -1,24 +1,25 @@ -using System; -using System.Collections.Generic; -using System.Text; -using System.Threading; +using System.Collections.Generic; using Terminal.Gui; - using Terminal.Gui.Drawing; namespace UICatalog.Scenarios; [ScenarioMetadata ("Text Effects", "Text Effects.")] [ScenarioCategory ("Colors")] +[ScenarioCategory ("Text and Formatting")] public class TextEffectsScenario : Scenario { private TabView _tabView; - public static bool LoopingGradient = false; + /// + /// Enable or disable looping of the gradient colors. + /// + public static bool LoopingGradient; public override void Main () { Application.Init (); + var w = new Window { Width = Dim.Fill (), @@ -26,57 +27,56 @@ public override void Main () Title = "Text Effects Scenario" }; - w.Loaded += (s, e) => - { - SetupGradientLineCanvas (w, w.Frame.Size); - }; - w.SizeChanging += (s, e) => - { - if (e.Size.HasValue) - { - SetupGradientLineCanvas (w, e.Size.Value); - } - }; + w.Loaded += (s, e) => { SetupGradientLineCanvas (w, w.Frame.Size); }; - w.ColorScheme = new ColorScheme + w.SizeChanging += (s, e) => + { + if (e.Size.HasValue) + { + SetupGradientLineCanvas (w, e.Size.Value); + } + }; + + w.ColorScheme = new () { - Normal = new Terminal.Gui.Attribute (ColorName.White, ColorName.Black), - Focus = new Terminal.Gui.Attribute (ColorName.Black, ColorName.White), - HotNormal = new Terminal.Gui.Attribute (ColorName.White, ColorName.Black), - HotFocus = new Terminal.Gui.Attribute (ColorName.White, ColorName.Black), - Disabled = new Terminal.Gui.Attribute (ColorName.Gray, ColorName.Black) + Normal = new (ColorName.White, ColorName.Black), + Focus = new (ColorName.Black, ColorName.White), + HotNormal = new (ColorName.White, ColorName.Black), + HotFocus = new (ColorName.White, ColorName.Black), + Disabled = new (ColorName.Gray, ColorName.Black) }; // Creates a window that occupies the entire terminal with a title. - _tabView = new TabView () + _tabView = new () { Width = Dim.Fill (), - Height = Dim.Fill (), + Height = Dim.Fill () }; - var gradientsView = new GradientsView () + var gradientsView = new GradientsView { Width = Dim.Fill (), - Height = Dim.Fill (), + Height = Dim.Fill () }; - var t1 = new Tab () + + var t1 = new Tab { View = gradientsView, DisplayText = "Gradients" }; - - var cbLooping = new CheckBox () + var cbLooping = new CheckBox { Text = "Looping", Y = Pos.AnchorEnd (1) }; + cbLooping.Toggle += (s, e) => - { - LoopingGradient = e.NewValue == CheckState.Checked; - SetupGradientLineCanvas (w, w.Frame.Size); - _tabView.SetNeedsDisplay (); - }; + { + LoopingGradient = e.NewValue == CheckState.Checked; + SetupGradientLineCanvas (w, w.Frame.Size); + _tabView.SetNeedsDisplay (); + }; gradientsView.Add (cbLooping); @@ -88,50 +88,50 @@ public override void Main () w.Dispose (); Application.Shutdown (); - this.Dispose (); + Dispose (); } - - private void SetupGradientLineCanvas (View w, Size size) + private static void SetupGradientLineCanvas (View w, Size size) { - GetAppealingGradientColors (out var stops, out var steps); + GetAppealingGradientColors (out List stops, out List steps); var g = new Gradient (stops, steps, LoopingGradient); var fore = new GradientFill ( - new Rectangle (0, 0, size.Width, size.Height), g, GradientDirection.Diagonal); - var back = new SolidFill (new Terminal.Gui.Color (ColorName.Black)); - - w.LineCanvas.Fill = new FillPair ( - fore, - back); + new (0, 0, size.Width, size.Height), + g, + GradientDirection.Diagonal); + var back = new SolidFill (new (ColorName.Black)); + + w.LineCanvas.Fill = new ( + fore, + back); } public static void GetAppealingGradientColors (out List stops, out List steps) { // Define the colors of the gradient stops with more appealing colors - stops = new List - { - new Color(0, 128, 255), // Bright Blue - new Color(0, 255, 128), // Bright Green - new Color(255, 255, 0), // Bright Yellow - new Color(255, 128, 0), // Bright Orange - new Color(255, 0, 128) // Bright Pink - }; + stops = + [ + new (0, 128, 255), // Bright Blue + new (0, 255, 128), // Bright Green + new (255, 255), // Bright Yellow + new (255, 128), // Bright Orange + new (255, 0, 128) + ]; // Define the number of steps between each color for smoother transitions // If we pass only a single value then it will assume equal steps between all pairs - steps = new List { 15 }; + steps = [15]; } } - internal class GradientsView : View { - private const int GradientWidth = 30; - private const int GradientHeight = 15; - private const int LabelHeight = 1; - private const int GradientWithLabelHeight = GradientHeight + LabelHeight + 1; // +1 for spacing + private const int GRADIENT_WIDTH = 30; + private const int GRADIENT_HEIGHT = 15; + private const int LABEL_HEIGHT = 1; + private const int GRADIENT_WITH_LABEL_HEIGHT = GRADIENT_HEIGHT + LABEL_HEIGHT + 1; // +1 for spacing public override void OnDrawContent (Rectangle viewport) { @@ -139,10 +139,10 @@ public override void OnDrawContent (Rectangle viewport) DrawTopLineGradient (viewport); - int x = 2; - int y = 3; + var x = 2; + var y = 3; - var gradients = new List<(string Label, GradientDirection Direction)> + List<(string Label, GradientDirection Direction)> gradients = new () { ("Horizontal", GradientDirection.Horizontal), ("Vertical", GradientDirection.Vertical), @@ -150,74 +150,74 @@ public override void OnDrawContent (Rectangle viewport) ("Diagonal", GradientDirection.Diagonal) }; - foreach (var (label, direction) in gradients) + foreach ((string label, GradientDirection direction) in gradients) { - if (x + GradientWidth > viewport.Width) + if (x + GRADIENT_WIDTH > viewport.Width) { x = 2; // Reset to left margin - y += GradientWithLabelHeight; // Move down to next row + y += GRADIENT_WITH_LABEL_HEIGHT; // Move down to next row } DrawLabeledGradientArea (label, direction, x, y); - x += GradientWidth + 2; // Move right for next gradient, +2 for spacing + x += GRADIENT_WIDTH + 2; // Move right for next gradient, +2 for spacing } } private void DrawLabeledGradientArea (string label, GradientDirection direction, int xOffset, int yOffset) { DrawGradientArea (direction, xOffset, yOffset); - CenterText (label, xOffset, yOffset + GradientHeight); // Adjusted for text below the gradient + CenterText (label, xOffset, yOffset + GRADIENT_HEIGHT); // Adjusted for text below the gradient } private void CenterText (string text, int xOffset, int yOffset) { - if(yOffset+1 >= Viewport.Height) + if (yOffset + 1 >= Viewport.Height) { // Not enough space for label return; } - var width = text.Length; - var x = xOffset + (GradientWidth - width) / 2; // Center the text within the gradient area width + int width = text.Length; + int x = xOffset + (GRADIENT_WIDTH - width) / 2; // Center the text within the gradient area width Driver.SetAttribute (GetNormalColor ()); - Move (x, yOffset+1); + Move (x, yOffset + 1); Driver.AddStr (text); } private void DrawGradientArea (GradientDirection direction, int xOffset, int yOffset) { // Define the colors of the gradient stops - var stops = new List - { - new Color(255, 0, 0), // Red - new Color(0, 255, 0), // Green - new Color(238, 130, 238) // Violet - }; + List stops = + [ + new (255, 0), // Red + new (0, 255), // Green + new (238, 130, 238) + ]; // Define the number of steps between each color - var steps = new List { 10, 10 }; // 10 steps between Red -> Green, and Green -> Blue + List steps = [10, 10]; // 10 steps between Red -> Green, and Green -> Blue // Create the gradient - var radialGradient = new Gradient (stops, steps, loop: TextEffectsScenario.LoopingGradient); + var radialGradient = new Gradient (stops, steps, TextEffectsScenario.LoopingGradient); // Define the size of the rectangle - int maxRow = GradientHeight; // Adjusted to keep aspect ratio - int maxColumn = GradientWidth; + int maxRow = GRADIENT_HEIGHT; // Adjusted to keep aspect ratio + int maxColumn = GRADIENT_WIDTH; // Build the coordinate-color mapping for a radial gradient - var gradientMapping = radialGradient.BuildCoordinateColorMapping (maxRow, maxColumn, direction); + Dictionary gradientMapping = radialGradient.BuildCoordinateColorMapping (maxRow, maxColumn, direction); // Print the gradient - for (int row = 0; row <= maxRow; row++) + for (var row = 0; row <= maxRow; row++) { - for (int col = 0; col <= maxColumn; col++) + for (var col = 0; col <= maxColumn; col++) { var coord = new Point (col, row); - var color = gradientMapping [coord]; + Color color = gradientMapping [coord]; SetColor (color); - AddRune (col + xOffset, row + yOffset, new Rune ('█')); + AddRune (col + xOffset, row + yOffset, new ('█')); } } } @@ -225,44 +225,41 @@ private void DrawGradientArea (GradientDirection direction, int xOffset, int yOf private void DrawTopLineGradient (Rectangle viewport) { // Define the colors of the rainbow - var stops = new List - { - new Color(255, 0, 0), // Red - new Color(255, 165, 0), // Orange - new Color(255, 255, 0), // Yellow - new Color(0, 128, 0), // Green - new Color(0, 0, 255), // Blue - new Color(75, 0, 130), // Indigo - new Color(238, 130, 238) // Violet - }; + List stops = + [ + new (255, 0), // Red + new (255, 165), // Orange + new (255, 255), // Yellow + new (0, 128), // Green + new (0, 0, 255), // Blue + new (75, 0, 130), // Indigo + new (238, 130, 238) + ]; // Define the number of steps between each color - var steps = new List - { + List steps = + [ 20, // between Red and Orange 20, // between Orange and Yellow 20, // between Yellow and Green 20, // between Green and Blue 20, // between Blue and Indigo - 20 // between Indigo and Violet - }; + 20 + ]; // Create the gradient var rainbowGradient = new Gradient (stops, steps, TextEffectsScenario.LoopingGradient); - for (int x = 0; x < viewport.Width; x++) + for (var x = 0; x < viewport.Width; x++) { double fraction = (double)x / (viewport.Width - 1); Color color = rainbowGradient.GetColorAtFraction (fraction); SetColor (color); - AddRune (x, 0, new Rune ('█')); + AddRune (x, 0, new ('█')); } } - private void SetColor (Color color) - { - Application.Driver.SetAttribute (new Attribute (color, color)); - } + private static void SetColor (Color color) { Application.Driver.SetAttribute (new (color, color)); } } diff --git a/UnitTests/Drawing/FillPairTests.cs b/UnitTests/Drawing/FillPairTests.cs index bbe8b6563e..067053e6e1 100644 --- a/UnitTests/Drawing/FillPairTests.cs +++ b/UnitTests/Drawing/FillPairTests.cs @@ -1,15 +1,9 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using Terminal.Gui.Drawing; +using Terminal.Gui.Drawing; namespace Terminal.Gui.DrawingTests; public class FillPairTests { - [Fact] public void GetAttribute_ReturnsCorrectColors () { @@ -22,11 +16,10 @@ public void GetAttribute_ReturnsCorrectColors () var fillPair = new FillPair (foregroundFill, backgroundFill); // Act - var resultAttribute = fillPair.GetAttribute (new Point (0, 0)); + Attribute resultAttribute = fillPair.GetAttribute (new (0, 0)); // Assert Assert.Equal (foregroundColor, resultAttribute.Foreground); Assert.Equal (backgroundColor, resultAttribute.Background); } } - diff --git a/UnitTests/Drawing/GradientFillTests.cs b/UnitTests/Drawing/GradientFillTests.cs index e1f035d8db..75bc65bbc8 100644 --- a/UnitTests/Drawing/GradientFillTests.cs +++ b/UnitTests/Drawing/GradientFillTests.cs @@ -2,21 +2,21 @@ public class GradientFillTests { - private Gradient _gradient; + private readonly Gradient _gradient; public GradientFillTests () { // Define the colors of the gradient stops - var stops = new List + List stops = new List { - new Color(255, 0, 0), // Red - new Color(0, 0, 255) // Blue + new (255, 0), // Red + new (0, 0, 255) // Blue }; // Define the number of steps between each color - var steps = new List { 10 }; // 10 steps between Red -> Blue + List steps = new() { 10 }; // 10 steps between Red -> Blue - _gradient = new Gradient (stops, steps, loop: false); + _gradient = new (stops, steps); } [Fact] @@ -31,13 +31,13 @@ public void TestGradientFillCorners_AtOrigin () var bottomLeft = new Point (0, area.Height - 1); var bottomRight = new Point (area.Width - 1, area.Height - 1); - var topLeftColor = gradientFill.GetColor (topLeft); - var topRightColor = gradientFill.GetColor (topRight); - var bottomLeftColor = gradientFill.GetColor (bottomLeft); - var bottomRightColor = gradientFill.GetColor (bottomRight); + Color topLeftColor = gradientFill.GetColor (topLeft); + Color topRightColor = gradientFill.GetColor (topRight); + Color bottomLeftColor = gradientFill.GetColor (bottomLeft); + Color bottomRightColor = gradientFill.GetColor (bottomRight); // Expected colors - var expectedTopLeftColor = new Color (255, 0, 0); // Red + var expectedTopLeftColor = new Color (255, 0); // Red var expectedBottomRightColor = new Color (0, 0, 255); // Blue Assert.Equal (expectedTopLeftColor, topLeftColor); @@ -56,13 +56,13 @@ public void TestGradientFillCorners_NotAtOrigin () var bottomLeft = new Point (5, area.Bottom - 1); var bottomRight = new Point (area.Right - 1, area.Bottom - 1); - var topLeftColor = gradientFill.GetColor (topLeft); - var topRightColor = gradientFill.GetColor (topRight); - var bottomLeftColor = gradientFill.GetColor (bottomLeft); - var bottomRightColor = gradientFill.GetColor (bottomRight); + Color topLeftColor = gradientFill.GetColor (topLeft); + Color topRightColor = gradientFill.GetColor (topRight); + Color bottomLeftColor = gradientFill.GetColor (bottomLeft); + Color bottomRightColor = gradientFill.GetColor (bottomRight); // Expected colors - var expectedTopLeftColor = new Color (255, 0, 0); // Red + var expectedTopLeftColor = new Color (255, 0); // Red var expectedBottomRightColor = new Color (0, 0, 255); // Blue Assert.Equal (expectedTopLeftColor, topLeftColor); @@ -75,15 +75,15 @@ public void TestGradientFillColorTransition () var area = new Rectangle (0, 0, 10, 10); var gradientFill = new GradientFill (area, _gradient, GradientDirection.Diagonal); - for (int row = 0; row < area.Height; row++) + for (var row = 0; row < area.Height; row++) { - int previousRed = 255; - int previousBlue = 0; + var previousRed = 255; + var previousBlue = 0; - for (int col = 0; col < area.Width; col++) + for (var col = 0; col < area.Width; col++) { var point = new Point (col, row); - var color = gradientFill.GetColor (point); + Color color = gradientFill.GetColor (point); // Check if the current color is 'more blue' and 'less red' as it goes right and down Assert.True (color.R <= previousRed, $"Failed at ({col}, {row}): {color.R} > {previousRed}"); @@ -95,15 +95,15 @@ public void TestGradientFillColorTransition () } } - for (int col = 0; col < area.Width; col++) + for (var col = 0; col < area.Width; col++) { - int previousRed = 255; - int previousBlue = 0; + var previousRed = 255; + var previousBlue = 0; - for (int row = 0; row < area.Height; row++) + for (var row = 0; row < area.Height; row++) { var point = new Point (col, row); - var color = gradientFill.GetColor (point); + Color color = gradientFill.GetColor (point); // Check if the current color is 'more blue' and 'less red' as it goes right and down Assert.True (color.R <= previousRed, $"Failed at ({col}, {row}): {color.R} > {previousRed}"); diff --git a/UnitTests/Drawing/GradientTests.cs b/UnitTests/Drawing/GradientTests.cs index 8fcfc13543..0174a7ff9a 100644 --- a/UnitTests/Drawing/GradientTests.cs +++ b/UnitTests/Drawing/GradientTests.cs @@ -1,5 +1,4 @@ - -namespace Terminal.Gui.DrawingTests; +namespace Terminal.Gui.DrawingTests; public class GradientTests { @@ -7,8 +6,8 @@ public class GradientTests public static IEnumerable GradientDirectionValues () { return typeof (GradientDirection).GetEnumValues () - .Cast () - .Select (direction => new object [] { direction }); + .Cast () + .Select (direction => new object [] { direction }); } [Theory] @@ -16,16 +15,16 @@ public static IEnumerable GradientDirectionValues () public void GradientIsInclusive_2_by_2 (GradientDirection direction) { // Define the colors of the gradient stops - var stops = new List - { - new Color(255, 0, 0), // Red - new Color(0, 0, 255) // Blue - }; + List stops = new() + { + new (255, 0), // Red + new (0, 0, 255) // Blue + }; // Define the number of steps between each color - var steps = new List { 10 }; // 10 steps between Red -> Blue + List steps = new() { 10 }; // 10 steps between Red -> Blue - var g = new Gradient (stops, steps, loop: false); + var g = new Gradient (stops, steps); Assert.Equal (4, g.BuildCoordinateColorMapping (1, 1, direction).Count); } @@ -34,77 +33,77 @@ public void GradientIsInclusive_2_by_2 (GradientDirection direction) public void GradientIsInclusive_1_by_1 (GradientDirection direction) { // Define the colors of the gradient stops - var stops = new List - { - new Color(255, 0, 0), // Red - new Color(0, 0, 255) // Blue - }; + List stops = new() + { + new (255, 0), // Red + new (0, 0, 255) // Blue + }; // Define the number of steps between each color - var steps = new List { 10 }; // 10 steps between Red -> Blue + List steps = new() { 10 }; // 10 steps between Red -> Blue - var g = new Gradient (stops, steps, loop: false); + var g = new Gradient (stops, steps); // Note that maxRow and maxCol are inclusive so this results in 1x1 area i.e. a single cell. - var c = Assert.Single (g.BuildCoordinateColorMapping (0, 0, direction)); - Assert.Equal (c.Key, new Point(0,0)); - Assert.Equal (c.Value, new Color (0, 0, 255)); + KeyValuePair c = Assert.Single (g.BuildCoordinateColorMapping (0, 0, direction)); + Assert.Equal (c.Key, new (0, 0)); + Assert.Equal (c.Value, new (0, 0, 255)); } [Fact] public void SingleColorStop () { - var stops = new List { new Color (255, 0, 0) }; // Red - var steps = new List { }; + List stops = new() { new (255, 0) }; // Red + List steps = new (); - var g = new Gradient (stops, steps, loop: false); - Assert.All (g.Spectrum, color => Assert.Equal (new Color (255, 0, 0), color)); + var g = new Gradient (stops, steps); + Assert.All (g.Spectrum, color => Assert.Equal (new (255, 0), color)); } [Fact] public void LoopingGradient_CorrectColors () { - var stops = new List - { - new Color(255, 0, 0), // Red - new Color(0, 0, 255) // Blue - }; + List stops = new() + { + new (255, 0), // Red + new (0, 0, 255) // Blue + }; - var steps = new List { 10 }; + List steps = new() { 10 }; - var g = new Gradient (stops, steps, loop: true); - Assert.Equal (new Color (255, 0, 0), g.Spectrum.First ()); - Assert.Equal (new Color (255, 0, 0), g.Spectrum.Last ()); + var g = new Gradient (stops, steps, true); + Assert.Equal (new (255, 0), g.Spectrum.First ()); + Assert.Equal (new (255, 0), g.Spectrum.Last ()); } [Fact] public void DifferentStepSizes () { - var stops = new List - { - new Color(255, 0, 0), // Red - new Color(0, 255, 0), // Green - new Color(0, 0, 255) // Blue - }; + List stops = new List + { + new (255, 0), // Red + new (0, 255), // Green + new (0, 0, 255) // Blue + }; - var steps = new List { 5, 15 }; // Different steps + List steps = new() { 5, 15 }; // Different steps - var g = new Gradient (stops, steps, loop: false); + var g = new Gradient (stops, steps); Assert.Equal (22, g.Spectrum.Count); } [Fact] public void FractionOutOfRange_ThrowsException () { - var stops = new List - { - new Color(255, 0, 0), // Red - new Color(0, 0, 255) // Blue - }; + List stops = new() + { + new (255, 0), // Red + new (0, 0, 255) // Blue + }; - var steps = new List { 10 }; + List steps = new() { 10 }; - var g = new Gradient (stops, steps, loop: false); + var g = new Gradient (stops, steps); Assert.Throws (() => g.GetColorAtFraction (-0.1)); Assert.Throws (() => g.GetColorAtFraction (1.1)); @@ -113,30 +112,30 @@ public void FractionOutOfRange_ThrowsException () [Fact] public void NaNFraction_ReturnsLastColor () { - var stops = new List - { - new Color(255, 0, 0), // Red - new Color(0, 0, 255) // Blue - }; + List stops = new() + { + new (255, 0), // Red + new (0, 0, 255) // Blue + }; - var steps = new List { 10 }; + List steps = new() { 10 }; - var g = new Gradient (stops, steps, loop: false); - Assert.Equal (new Color (0, 0, 255), g.GetColorAtFraction (double.NaN)); + var g = new Gradient (stops, steps); + Assert.Equal (new (0, 0, 255), g.GetColorAtFraction (double.NaN)); } [Fact] public void Constructor_SingleStepProvided_ReplicatesForAllPairs () { - var stops = new List - { - new Color(255, 0, 0), // Red - new Color(0, 255, 0), // Green - new Color(0, 0, 255) // Blue - }; + List stops = new List + { + new (255, 0), // Red + new (0, 255), // Green + new (0, 0, 255) // Blue + }; - var singleStep = new List { 5 }; // Single step provided - var gradient = new Gradient (stops, singleStep, loop: false); + List singleStep = new() { 5 }; // Single step provided + var gradient = new Gradient (stops, singleStep); Assert.NotNull (gradient.Spectrum); Assert.Equal (12, gradient.Spectrum.Count); // 5 steps Red -> Green + 5 steps Green -> Blue + 2 end colors @@ -145,31 +144,30 @@ public void Constructor_SingleStepProvided_ReplicatesForAllPairs () [Fact] public void Constructor_InvalidStepsLength_ThrowsArgumentException () { - var stops = new List - { - new Color(255, 0, 0), // Red - new Color(0, 0, 255) // Blue - }; - - var invalidSteps = new List { 5, 5 }; // Invalid length (N-1 expected) - Assert.Throws (() => new Gradient (stops, invalidSteps, loop: false)); + List stops = new() + { + new (255, 0), // Red + new (0, 0, 255) // Blue + }; + + List invalidSteps = new() { 5, 5 }; // Invalid length (N-1 expected) + Assert.Throws (() => new Gradient (stops, invalidSteps)); } [Fact] public void Constructor_ValidStepsLength_DoesNotThrow () { - var stops = new List - { - new Color(255, 0, 0), // Red - new Color(0, 255, 0), // Green - new Color(0, 0, 255) // Blue - }; + List stops = new List + { + new (255, 0), // Red + new (0, 255), // Green + new (0, 0, 255) // Blue + }; - var validSteps = new List { 5, 5 }; // Valid length (N-1) - var gradient = new Gradient (stops, validSteps, loop: false); + List validSteps = new() { 5, 5 }; // Valid length (N-1) + var gradient = new Gradient (stops, validSteps); Assert.NotNull (gradient.Spectrum); Assert.Equal (12, gradient.Spectrum.Count); // 5 steps Red -> Green + 5 steps Green -> Blue + 2 end colors } - -} \ No newline at end of file +} diff --git a/UnitTests/Drawing/LineCanvasTests.cs b/UnitTests/Drawing/LineCanvasTests.cs index d160b5420b..7d002770d9 100644 --- a/UnitTests/Drawing/LineCanvasTests.cs +++ b/UnitTests/Drawing/LineCanvasTests.cs @@ -4,7 +4,7 @@ namespace Terminal.Gui.DrawingTests; -public class LineCanvasTests (ITestOutputHelper output) +public class LineCanvasTests (ITestOutputHelper _output) { [Theory] @@ -295,7 +295,7 @@ string expected lc.AddLine (new (x1, y1), len1, o1, s1); lc.AddLine (new (x2, y2), len2, o2, s2); - TestHelpers.AssertEqual (output, expected, lc.ToString ()); + TestHelpers.AssertEqual (_output, expected, lc.ToString ()); v.Dispose (); } @@ -504,7 +504,7 @@ public void Viewport_Specific () Assert.Equal (new (x, y, 4, 2), lc.Viewport); TestHelpers.AssertEqual ( - output, + _output, @" ╔╡╞╗ ║ ║", @@ -554,7 +554,7 @@ public void Viewport_Specific_With_Ustring () Assert.Equal (new (x, y, 4, 2), lc.Viewport); TestHelpers.AssertEqual ( - output, + _output, @" ╔╡╞╗ ║ ║", @@ -597,7 +597,7 @@ public void Length_0_Is_1_Long (int x, int y, Orientation orientation, string ex // Add a line at 5, 5 that's has length of 1 canvas.AddLine (new (x, y), 1, orientation, LineStyle.Single); - TestHelpers.AssertEqual (output, $"{expected}", $"{canvas}"); + TestHelpers.AssertEqual (_output, $"{expected}", $"{canvas}"); } // X is offset by 2 @@ -654,7 +654,7 @@ public void Length_n_Is_n_Long (int x, int y, int length, Orientation orientatio canvas.AddLine (new (x, y), length, orientation, LineStyle.Single); var result = canvas.ToString (); - TestHelpers.AssertEqual (output, expected, result); + TestHelpers.AssertEqual (_output, expected, result); } [Fact] @@ -681,7 +681,7 @@ public void Length_Zero_Alone_Is_Line (Orientation orientation, string expected) // Add a line at 0, 0 that's has length of 0 lc.AddLine (Point.Empty, 0, orientation, LineStyle.Single); - TestHelpers.AssertEqual (output, expected, $"{lc}"); + TestHelpers.AssertEqual (_output, expected, $"{lc}"); } [InlineData (Orientation.Horizontal, "┼")] @@ -702,7 +702,7 @@ public void Length_Zero_Cross_Is_Cross (Orientation orientation, string expected // Add a line at 0, 0 that's has length of 0 lc.AddLine (Point.Empty, 0, orientation, LineStyle.Single); - TestHelpers.AssertEqual (output, expected, $"{lc}"); + TestHelpers.AssertEqual (_output, expected, $"{lc}"); } [InlineData (Orientation.Horizontal, "╥")] @@ -725,7 +725,7 @@ public void Length_Zero_NextTo_Opposite_Is_T (Orientation orientation, string ex // Add a line at 0, 0 that's has length of 0 lc.AddLine (Point.Empty, 0, orientation, LineStyle.Single); - TestHelpers.AssertEqual (output, expected, $"{lc}"); + TestHelpers.AssertEqual (_output, expected, $"{lc}"); } [Fact] @@ -741,7 +741,7 @@ public void TestLineCanvas_LeaveMargin_Top1_Left1 () @" ┌─ │ "; - TestHelpers.AssertEqual (output, looksLike, $"{Environment.NewLine}{canvas}"); + TestHelpers.AssertEqual (_output, looksLike, $"{Environment.NewLine}{canvas}"); } [Fact] @@ -768,7 +768,7 @@ public void TestLineCanvas_Window_Heavy () ┣━━━━╋━━━┫ ┃ ┃ ┃ ┗━━━━┻━━━┛"; - TestHelpers.AssertDriverContentsAre (looksLike, output); + TestHelpers.AssertDriverContentsAre (looksLike, _output); v.Dispose (); } @@ -799,7 +799,7 @@ public void TestLineCanvas_Window_HeavyTop_ThinSides (LineStyle thinStyle) │ │ │ ┕━━━━┷━━━┙ "; - TestHelpers.AssertDriverContentsAre (looksLike, output); + TestHelpers.AssertDriverContentsAre (looksLike, _output); v.Dispose (); } @@ -831,7 +831,7 @@ public void TestLineCanvas_Window_ThinTop_HeavySides (LineStyle thinStyle) ┖────┸───┚ "; - TestHelpers.AssertDriverContentsAre (looksLike, output); + TestHelpers.AssertDriverContentsAre (looksLike, _output); v.Dispose (); } @@ -849,7 +849,7 @@ public void Top_Left_From_TopRight_LeftUp () @" ┌─ │ "; - TestHelpers.AssertEqual (output, looksLike, $"{Environment.NewLine}{canvas}"); + TestHelpers.AssertEqual (_output, looksLike, $"{Environment.NewLine}{canvas}"); } [Fact] @@ -879,7 +879,7 @@ public void Top_With_1Down () Assert.Equal (2, map.Count); TestHelpers.AssertEqual ( - output, + _output, @" ─ ─", @@ -892,7 +892,7 @@ public void Top_With_1Down () public void ToString_Empty () { var lc = new LineCanvas (); - TestHelpers.AssertEqual (output, string.Empty, lc.ToString ()); + TestHelpers.AssertEqual (_output, string.Empty, lc.ToString ()); } // 012 @@ -911,7 +911,7 @@ public void ToString_Positive_Horizontal_1Line_Offset (int x, int y, string expe { var lc = new LineCanvas (); lc.AddLine (new (x, y), 3, Orientation.Horizontal, LineStyle.Double); - TestHelpers.AssertEqual (output, expected, $"{lc}"); + TestHelpers.AssertEqual (_output, expected, $"{lc}"); } [InlineData (0, 0, 0, 0, "═══")] @@ -936,7 +936,7 @@ public void ToString_Positive_Horizontal_2Line_Offset (int x1, int y1, int x2, i lc.AddLine (new (x1, y1), 3, Orientation.Horizontal, LineStyle.Double); lc.AddLine (new (x2, y2), 3, Orientation.Horizontal, LineStyle.Double); - TestHelpers.AssertEqual (output, expected, $"{lc}"); + TestHelpers.AssertEqual (_output, expected, $"{lc}"); } // [Fact, SetupFakeDriver] @@ -996,7 +996,7 @@ string expected v.Draw (); - TestHelpers.AssertDriverContentsAre (expected, output); + TestHelpers.AssertDriverContentsAre (expected, _output); v.Dispose (); } @@ -1015,7 +1015,7 @@ public void View_Draws_Corner_Correct () @" ┌─ │"; - TestHelpers.AssertDriverContentsAre (looksLike, output); + TestHelpers.AssertDriverContentsAre (looksLike, _output); v.Dispose (); } @@ -1038,7 +1038,7 @@ public void View_Draws_Corner_NoOverlap () ── │ │"; - TestHelpers.AssertDriverContentsAre (looksLike, output); + TestHelpers.AssertDriverContentsAre (looksLike, _output); v.Dispose (); } @@ -1056,7 +1056,7 @@ public void View_Draws_Horizontal (LineStyle style) var looksLike = @" ──"; - TestHelpers.AssertDriverContentsAre (looksLike, output); + TestHelpers.AssertDriverContentsAre (looksLike, _output); v.Dispose (); } @@ -1072,7 +1072,7 @@ public void View_Draws_Horizontal_Double () var looksLike = @" ══"; - TestHelpers.AssertDriverContentsAre (looksLike, output); + TestHelpers.AssertDriverContentsAre (looksLike, _output); v.Dispose (); } @@ -1091,7 +1091,7 @@ public void View_Draws_Vertical (LineStyle style) @" │ │"; - TestHelpers.AssertDriverContentsAre (looksLike, output); + TestHelpers.AssertDriverContentsAre (looksLike, _output); v.Dispose (); } @@ -1108,7 +1108,7 @@ public void View_Draws_Vertical_Double () @" ║ ║"; - TestHelpers.AssertDriverContentsAre (looksLike, output); + TestHelpers.AssertDriverContentsAre (looksLike, _output); v.Dispose (); } @@ -1136,7 +1136,7 @@ public void View_Draws_Window_Double () ╠════╬═══╣ ║ ║ ║ ╚════╩═══╝"; - TestHelpers.AssertDriverContentsAre (looksLike, output); + TestHelpers.AssertDriverContentsAre (looksLike, _output); v.Dispose (); } @@ -1167,7 +1167,7 @@ public void View_Draws_Window_DoubleTop_SingleSides (LineStyle thinStyle) │ │ │ ╘════╧═══╛ "; - TestHelpers.AssertDriverContentsAre (looksLike, output); + TestHelpers.AssertDriverContentsAre (looksLike, _output); v.Dispose (); } @@ -1204,7 +1204,7 @@ public void View_Draws_Window_Rounded () ├────┼───┤ │ │ │ ╰────┴───╯"; - TestHelpers.AssertDriverContentsAre (looksLike, output); + TestHelpers.AssertDriverContentsAre (looksLike, _output); v.Dispose (); } @@ -1236,7 +1236,7 @@ public void View_Draws_Window_SingleTop_DoubleSides (LineStyle thinStyle) ╙────╨───╜ "; - TestHelpers.AssertDriverContentsAre (looksLike, output); + TestHelpers.AssertDriverContentsAre (looksLike, _output); v.Dispose (); } @@ -1263,7 +1263,7 @@ public void Window () ├────┼───┤ │ │ │ └────┴───┘"; - TestHelpers.AssertEqual (output, looksLike, $"{Environment.NewLine}{canvas}"); + TestHelpers.AssertEqual (_output, looksLike, $"{Environment.NewLine}{canvas}"); } [Fact] @@ -1301,15 +1301,15 @@ public void Zero_Length_Intersections () var looksLike = @" ╔╡╞══╗ ║ ║"; - TestHelpers.AssertEqual (output, looksLike, $"{Environment.NewLine}{lc}"); + TestHelpers.AssertEqual (_output, looksLike, $"{Environment.NewLine}{lc}"); } [Fact] public void LineCanvas_UsesFillCorrectly () { // Arrange - var foregroundColor = new Color (255, 0, 0); // Red - var backgroundColor = new Color (0, 0, 0); // Black + var foregroundColor = new Color (255, 0); // Red + var backgroundColor = new Color (0, 0); // Black var foregroundFill = new SolidFill (foregroundColor); var backgroundFill = new SolidFill (backgroundColor); var fillPair = new FillPair (foregroundFill, backgroundFill); @@ -1320,11 +1320,11 @@ public void LineCanvas_UsesFillCorrectly () }; // Act - lineCanvas.AddLine (new Point (0, 0), 5, Orientation.Horizontal, LineStyle.Single); - var cellMap = lineCanvas.GetCellMap (); + lineCanvas.AddLine (new (0, 0), 5, Orientation.Horizontal, LineStyle.Single); + Dictionary cellMap = lineCanvas.GetCellMap (); // Assert - foreach (var cell in cellMap.Values) + foreach (Cell? cell in cellMap.Values) { Assert.NotNull (cell); Assert.Equal (foregroundColor, cell.Value.Attribute.Value.Foreground); @@ -1336,9 +1336,9 @@ public void LineCanvas_UsesFillCorrectly () public void LineCanvas_LineColorIgnoredBecauseOfFill () { // Arrange - var foregroundColor = new Color (255, 0, 0); // Red - var backgroundColor = new Color (0, 0, 0); // Black - var lineColor = new Attribute (new Color (0, 255, 0), new Color (255, 255, 255)); // Green on White + var foregroundColor = new Color (255, 0); // Red + var backgroundColor = new Color (0, 0); // Black + var lineColor = new Attribute (new Color (0, 255), new Color (255, 255, 255)); // Green on White var foregroundFill = new SolidFill (foregroundColor); var backgroundFill = new SolidFill (backgroundColor); var fillPair = new FillPair (foregroundFill, backgroundFill); @@ -1349,11 +1349,11 @@ public void LineCanvas_LineColorIgnoredBecauseOfFill () }; // Act - lineCanvas.AddLine (new Point (0, 0), 5, Orientation.Horizontal, LineStyle.Single, lineColor); - var cellMap = lineCanvas.GetCellMap (); + lineCanvas.AddLine (new (0, 0), 5, Orientation.Horizontal, LineStyle.Single, lineColor); + Dictionary cellMap = lineCanvas.GetCellMap (); // Assert - foreach (var cell in cellMap.Values) + foreach (Cell? cell in cellMap.Values) { Assert.NotNull (cell); Assert.Equal (foregroundColor, cell.Value.Attribute.Value.Foreground); @@ -1365,8 +1365,8 @@ public void LineCanvas_LineColorIgnoredBecauseOfFill () public void LineCanvas_IntersectingLinesUseFillCorrectly () { // Arrange - var foregroundColor = new Color (255, 0, 0); // Red - var backgroundColor = new Color (0, 0, 0); // Black + var foregroundColor = new Color (255, 0); // Red + var backgroundColor = new Color (0, 0); // Black var foregroundFill = new SolidFill (foregroundColor); var backgroundFill = new SolidFill (backgroundColor); var fillPair = new FillPair (foregroundFill, backgroundFill); @@ -1377,12 +1377,12 @@ public void LineCanvas_IntersectingLinesUseFillCorrectly () }; // Act - lineCanvas.AddLine (new Point (0, 0), 5, Orientation.Horizontal, LineStyle.Single); - lineCanvas.AddLine (new Point (2, -2), 5, Orientation.Vertical, LineStyle.Single); - var cellMap = lineCanvas.GetCellMap (); + lineCanvas.AddLine (new (0, 0), 5, Orientation.Horizontal, LineStyle.Single); + lineCanvas.AddLine (new (2, -2), 5, Orientation.Vertical, LineStyle.Single); + Dictionary cellMap = lineCanvas.GetCellMap (); // Assert - foreach (var cell in cellMap.Values) + foreach (Cell? cell in cellMap.Values) { Assert.NotNull (cell); Assert.Equal (foregroundColor, cell.Value.Attribute.Value.Foreground); diff --git a/UnitTests/Drawing/SolidFillTests.cs b/UnitTests/Drawing/SolidFillTests.cs index 50eaa9f9f4..3cf0168679 100644 --- a/UnitTests/Drawing/SolidFillTests.cs +++ b/UnitTests/Drawing/SolidFillTests.cs @@ -1,5 +1,4 @@ - -using Terminal.Gui.Drawing; +using Terminal.Gui.Drawing; namespace Terminal.Gui.DrawingTests; @@ -13,7 +12,7 @@ public void GetColor_ReturnsCorrectColor () var solidFill = new SolidFill (expectedColor); // Act - var resultColor = solidFill.GetColor (new Point (0, 0)); + Color resultColor = solidFill.GetColor (new (0, 0)); // Assert Assert.Equal (expectedColor, resultColor); @@ -32,9 +31,9 @@ public void GetColor_ReturnsSameColorForDifferentPoints (int x, int y) var solidFill = new SolidFill (expectedColor); // Act - var resultColor = solidFill.GetColor (new Point (x, y)); + Color resultColor = solidFill.GetColor (new (x, y)); // Assert Assert.Equal (expectedColor, resultColor); } -} \ No newline at end of file +} diff --git a/UnitTests/Drawing/StraightLineExtensionsTests.cs b/UnitTests/Drawing/StraightLineExtensionsTests.cs index b7a5f36b10..865ae805ac 100644 --- a/UnitTests/Drawing/StraightLineExtensionsTests.cs +++ b/UnitTests/Drawing/StraightLineExtensionsTests.cs @@ -2,11 +2,8 @@ namespace Terminal.Gui.DrawingTests; -public class StraightLineExtensionsTests +public class StraightLineExtensionsTests (ITestOutputHelper output) { - private readonly ITestOutputHelper _output; - public StraightLineExtensionsTests (ITestOutputHelper output) { _output = output; } - [Fact] [AutoInitShutdown] public void LineCanvasIntegrationTest () @@ -18,7 +15,7 @@ public void LineCanvasIntegrationTest () lc.AddLine (new Point (0, 4), -5, Orientation.Vertical, LineStyle.Single); TestHelpers.AssertEqual ( - _output, + output, @" ┌────────┐ │ │ @@ -32,7 +29,7 @@ public void LineCanvasIntegrationTest () lc = new LineCanvas (origLines.Exclude (Point.Empty, 10, Orientation.Horizontal)); TestHelpers.AssertEqual ( - _output, + output, @" │ │ │ │ @@ -44,7 +41,7 @@ public void LineCanvasIntegrationTest () lc = new LineCanvas (origLines.Exclude (new Point (0, 1), 10, Orientation.Horizontal)); TestHelpers.AssertEqual ( - _output, + output, @" ┌────────┐ @@ -57,7 +54,7 @@ public void LineCanvasIntegrationTest () lc = new LineCanvas (origLines.Exclude (new Point (0, 2), 10, Orientation.Horizontal)); TestHelpers.AssertEqual ( - _output, + output, @" ┌────────┐ │ │ @@ -70,7 +67,7 @@ public void LineCanvasIntegrationTest () lc = new LineCanvas (origLines.Exclude (new Point (0, 3), 10, Orientation.Horizontal)); TestHelpers.AssertEqual ( - _output, + output, @" ┌────────┐ │ │ @@ -83,7 +80,7 @@ public void LineCanvasIntegrationTest () lc = new LineCanvas (origLines.Exclude (new Point (0, 4), 10, Orientation.Horizontal)); TestHelpers.AssertEqual ( - _output, + output, @" ┌────────┐ │ │ @@ -95,7 +92,7 @@ public void LineCanvasIntegrationTest () lc = new LineCanvas (origLines.Exclude (Point.Empty, 10, Orientation.Vertical)); TestHelpers.AssertEqual ( - _output, + output, @" ────────┐ │ @@ -108,7 +105,7 @@ public void LineCanvasIntegrationTest () lc = new LineCanvas (origLines.Exclude (new Point (1, 0), 10, Orientation.Vertical)); TestHelpers.AssertEqual ( - _output, + output, @" ┌ ───────┐ │ │ @@ -121,7 +118,7 @@ public void LineCanvasIntegrationTest () lc = new LineCanvas (origLines.Exclude (new Point (8, 0), 10, Orientation.Vertical)); TestHelpers.AssertEqual ( - _output, + output, @" ┌─────── ┐ │ │ @@ -134,7 +131,7 @@ public void LineCanvasIntegrationTest () lc = new LineCanvas (origLines.Exclude (new Point (9, 0), 10, Orientation.Vertical)); TestHelpers.AssertEqual ( - _output, + output, @" ┌──────── │ diff --git a/UnitTests/Drawing/StraightLineTests.cs b/UnitTests/Drawing/StraightLineTests.cs index bb68708214..4395ea0c17 100644 --- a/UnitTests/Drawing/StraightLineTests.cs +++ b/UnitTests/Drawing/StraightLineTests.cs @@ -2,10 +2,9 @@ namespace Terminal.Gui.DrawingTests; -public class StraightLineTests +public class StraightLineTests (ITestOutputHelper output) { - private readonly ITestOutputHelper output; - public StraightLineTests (ITestOutputHelper output) { this.output = output; } + private readonly ITestOutputHelper _output = output; [InlineData ( Orientation.Horizontal, @@ -320,8 +319,8 @@ public void Viewport ( int expectedHeight ) { - var sl = new StraightLine (new Point (x, y), length, orientation, LineStyle.Single); + var sl = new StraightLine (new (x, y), length, orientation, LineStyle.Single); - Assert.Equal (new Rectangle (expectedX, expectedY, expectedWidth, expectedHeight), sl.Viewport); + Assert.Equal (new (expectedX, expectedY, expectedWidth, expectedHeight), sl.Viewport); } } From f770bb983a023c3c55a531c1446a15ae4af0ffd8 Mon Sep 17 00:00:00 2001 From: Tig Date: Tue, 9 Jul 2024 13:39:52 -0600 Subject: [PATCH 30/30] Code cleanup2 --- Terminal.Gui/View/Adornment/Border.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/Terminal.Gui/View/Adornment/Border.cs b/Terminal.Gui/View/Adornment/Border.cs index 1ed64abca0..2931cd9ad2 100644 --- a/Terminal.Gui/View/Adornment/Border.cs +++ b/Terminal.Gui/View/Adornment/Border.cs @@ -1,5 +1,3 @@ -using Terminal.Gui.Drawing; - namespace Terminal.Gui; /// The Border for a .