mirror of
https://github.com/gui-cs/Terminal.Gui.git
synced 2025-12-26 15:57:56 +01:00
Remove everything except gradient
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
|
||||
using Terminal.Gui.TextEffects;
|
||||
using Terminal.Gui.Drawing;
|
||||
|
||||
namespace Terminal.Gui;
|
||||
|
||||
|
||||
@@ -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<Color> Spectrum { get; private set; }
|
||||
@@ -127,13 +76,13 @@ public class Gradient
|
||||
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<Coord, Color> BuildCoordinateColorMapping (int maxRow, int maxColumn, Direction direction)
|
||||
public Dictionary<Point, Color> BuildCoordinateColorMapping (int maxRow, int maxColumn, Direction direction)
|
||||
{
|
||||
var gradientMapping = new Dictionary<Coord, Color> ();
|
||||
var gradientMapping = new Dictionary<Point, Color> ();
|
||||
|
||||
switch (direction)
|
||||
{
|
||||
@@ -144,7 +93,7 @@ public class Gradient
|
||||
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 class Gradient
|
||||
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 class Gradient
|
||||
{
|
||||
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 class Gradient
|
||||
{
|
||||
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 class Gradient
|
||||
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;
|
||||
24
Terminal.Gui/Drawing/GradientFill.cs
Normal file
24
Terminal.Gui/Drawing/GradientFill.cs
Normal file
@@ -0,0 +1,24 @@
|
||||
namespace Terminal.Gui;
|
||||
|
||||
/// <summary>
|
||||
/// Implementation of <see cref="IFill"/> that uses a color gradient (including
|
||||
/// radial, diagonal etc).
|
||||
/// </summary>
|
||||
public class GradientFill : IFill
|
||||
{
|
||||
private Dictionary<Point, Color> _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
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
namespace Terminal.Gui.TextEffects;
|
||||
namespace Terminal.Gui.Drawing;
|
||||
|
||||
|
||||
/// <summary>
|
||||
@@ -6,13 +6,13 @@
|
||||
/// </summary>
|
||||
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;
|
||||
}
|
||||
@@ -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<Frame> Frames { get; } = new List<Frame> ();
|
||||
public List<Frame> PlayedFrames { get; } = new List<Frame> ();
|
||||
public Dictionary<int, Frame> FrameIndexMap { get; } = new Dictionary<int, Frame> ();
|
||||
public int EasingTotalSteps { get; set; }
|
||||
public int EasingCurrentStep { get; set; }
|
||||
public static Dictionary<string, int> XtermColorMap { get; } = new Dictionary<string, int> ();
|
||||
|
||||
public Scene (string sceneId, bool isLooping = false, SyncMetric? sync = null, EasingFunction ease = null, bool noColor = false, bool useXtermColors = false)
|
||||
{
|
||||
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<string> 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<string, Scene> Scenes { get; } = new Dictionary<string, Scene> ();
|
||||
public EffectCharacter Character { get; }
|
||||
public Scene ActiveScene { get; private set; }
|
||||
public bool UseXtermColors { get; set; } = false;
|
||||
public bool NoColor { get; set; } = false;
|
||||
public Dictionary<string, int> XtermColorMap { get; } = new Dictionary<string, int> ();
|
||||
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
|
||||
}
|
||||
}
|
||||
@@ -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<string, EasingFunction> 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.");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -1,60 +0,0 @@
|
||||
namespace Terminal.Gui.TextEffects;
|
||||
|
||||
public abstract class BaseEffectIterator<T> where T : EffectConfig, new()
|
||||
{
|
||||
protected T Config { get; set; }
|
||||
protected TerminalA Terminal { get; set; }
|
||||
protected List<EffectCharacter> ActiveCharacters { get; set; } = new List<EffectCharacter> ();
|
||||
|
||||
protected BaseEffect<T> Effect { get; }
|
||||
|
||||
|
||||
|
||||
public BaseEffectIterator (BaseEffect<T> 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<T> where T : EffectConfig, new()
|
||||
{
|
||||
public string InputData { get; set; }
|
||||
public T EffectConfig { get; set; }
|
||||
public TerminalConfig TerminalConfig { get; set; }
|
||||
|
||||
protected BaseEffect (string inputData)
|
||||
{
|
||||
InputData = inputData;
|
||||
EffectConfig = new T ();
|
||||
TerminalConfig = new TerminalConfig ();
|
||||
}
|
||||
|
||||
/*
|
||||
public IDisposable TerminalOutput (string endSymbol = "\n")
|
||||
{
|
||||
var terminal = new Terminal (InputData, TerminalConfig);
|
||||
terminal.PrepCanvas ();
|
||||
try
|
||||
{
|
||||
return terminal;
|
||||
}
|
||||
finally
|
||||
{
|
||||
terminal.RestoreCursor (endSymbol);
|
||||
}
|
||||
}*/
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
namespace Terminal.Gui.TextEffects;
|
||||
|
||||
public class EffectConfig
|
||||
{
|
||||
public Color ColorSingle { get; set; }
|
||||
public List<Color> ColorList { get; set; }
|
||||
public Color FinalColor { get; set; }
|
||||
public List<Color> FinalGradientStops { get; set; }
|
||||
public List<int> FinalGradientSteps { get; set; }
|
||||
public int FinalGradientFrames { get; set; }
|
||||
public float MovementSpeed { get; set; }
|
||||
public EasingFunction Easing { get; set; }
|
||||
}
|
||||
@@ -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<BeamsConfig>
|
||||
{
|
||||
public Beams (string inputData) : base (inputData)
|
||||
{
|
||||
}
|
||||
|
||||
protected override BaseEffectIterator<BeamsConfig> CreateIterator ()
|
||||
{
|
||||
return new BeamsIterator (this);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public class BeamsIterator : BaseEffectIterator<BeamsConfig>
|
||||
{
|
||||
private class Group
|
||||
{
|
||||
public List<EffectCharacter> Characters { get; private set; }
|
||||
public string Direction { get; private set; }
|
||||
private Terminal Terminal;
|
||||
private BeamsConfig Config;
|
||||
private double Speed;
|
||||
private float NextCharacterCounter;
|
||||
private List<EffectCharacter> SortedCharacters;
|
||||
|
||||
public Group (List<EffectCharacter> 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<Group> PendingGroups = new List<Group> ();
|
||||
private Dictionary<EffectCharacter, Color> CharacterFinalColorMap = new Dictionary<EffectCharacter, Color> ();
|
||||
private List<Group> ActiveGroups = new List<Group> ();
|
||||
private int Delay = 0;
|
||||
private string Phase = "beams";
|
||||
private List<List<EffectCharacter>> 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<Group> ();
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
*/
|
||||
@@ -1,137 +0,0 @@
|
||||
namespace Terminal.Gui.TextEffects;
|
||||
|
||||
|
||||
public static class GeometryUtils
|
||||
{
|
||||
|
||||
public static List<Coord> FindCoordsOnCircle (Coord origin, int radius, int coordsLimit = 0, bool unique = true)
|
||||
{
|
||||
var points = new List<Coord> ();
|
||||
var seenPoints = new HashSet<Coord> ();
|
||||
if (coordsLimit == 0)
|
||||
coordsLimit = (int)Math.Ceiling (2 * Math.PI * radius);
|
||||
double angleStep = 2 * Math.PI / coordsLimit;
|
||||
|
||||
for (int i = 0; i < coordsLimit; i++)
|
||||
{
|
||||
double angle = i * angleStep;
|
||||
int x = (int)(origin.Column + radius * Math.Cos (angle));
|
||||
int y = (int)(origin.Row + radius * Math.Sin (angle));
|
||||
var coord = new Coord (x, y);
|
||||
|
||||
if (unique && !seenPoints.Contains (coord))
|
||||
{
|
||||
points.Add (coord);
|
||||
seenPoints.Add (coord);
|
||||
}
|
||||
else if (!unique)
|
||||
{
|
||||
points.Add (coord);
|
||||
}
|
||||
}
|
||||
|
||||
return points;
|
||||
}
|
||||
|
||||
public static List<Coord> FindCoordsInCircle (Coord center, int diameter)
|
||||
{
|
||||
var coordsInEllipse = new List<Coord> ();
|
||||
int radius = diameter / 2;
|
||||
for (int x = center.Column - radius; x <= center.Column + radius; x++)
|
||||
{
|
||||
for (int y = center.Row - radius; y <= center.Row + radius; y++)
|
||||
{
|
||||
if (Math.Pow (x - center.Column, 2) + Math.Pow (y - center.Row, 2) <= Math.Pow (radius, 2))
|
||||
coordsInEllipse.Add (new Coord (x, y));
|
||||
}
|
||||
}
|
||||
return coordsInEllipse;
|
||||
}
|
||||
|
||||
public static List<Coord> FindCoordsInRect (Coord origin, int distance)
|
||||
{
|
||||
var coords = new List<Coord> ();
|
||||
for (int column = origin.Column - distance; column <= origin.Column + distance; column++)
|
||||
{
|
||||
for (int row = origin.Row - distance; row <= origin.Row + distance; row++)
|
||||
{
|
||||
coords.Add (new Coord (column, row));
|
||||
}
|
||||
}
|
||||
return coords;
|
||||
}
|
||||
|
||||
public static Coord FindCoordAtDistance (Coord origin, Coord target, double distance)
|
||||
{
|
||||
double totalDistance = FindLengthOfLine (origin, target) + distance;
|
||||
double t = distance / totalDistance;
|
||||
int nextColumn = (int)((1 - t) * origin.Column + t * target.Column);
|
||||
int nextRow = (int)((1 - t) * origin.Row + t * target.Row);
|
||||
return new Coord (nextColumn, nextRow);
|
||||
}
|
||||
|
||||
public static Coord FindCoordOnBezierCurve (Coord start, List<Coord> controlPoints, Coord end, double t)
|
||||
{
|
||||
// Implementing De Casteljau's algorithm for Bezier curve
|
||||
if (controlPoints.Count == 1) // Quadratic
|
||||
{
|
||||
double x = Math.Pow (1 - t, 2) * start.Column +
|
||||
2 * (1 - t) * t * controlPoints [0].Column +
|
||||
Math.Pow (t, 2) * end.Column;
|
||||
double y = Math.Pow (1 - t, 2) * start.Row +
|
||||
2 * (1 - t) * t * controlPoints [0].Row +
|
||||
Math.Pow (t, 2) * end.Row;
|
||||
return new Coord ((int)x, (int)y);
|
||||
}
|
||||
else if (controlPoints.Count == 2) // Cubic
|
||||
{
|
||||
double x = Math.Pow (1 - t, 3) * start.Column +
|
||||
3 * Math.Pow (1 - t, 2) * t * controlPoints [0].Column +
|
||||
3 * (1 - t) * Math.Pow (t, 2) * controlPoints [1].Column +
|
||||
Math.Pow (t, 3) * end.Column;
|
||||
double y = Math.Pow (1 - t, 3) * start.Row +
|
||||
3 * Math.Pow (1 - t, 2) * t * controlPoints [0].Row +
|
||||
3 * (1 - t) * Math.Pow (t, 2) * controlPoints [1].Row +
|
||||
Math.Pow (t, 3) * end.Row;
|
||||
return new Coord ((int)x, (int)y);
|
||||
}
|
||||
throw new ArgumentException ("Invalid number of control points for bezier curve");
|
||||
}
|
||||
|
||||
public static Coord FindCoordOnLine (Coord start, Coord end, double t)
|
||||
{
|
||||
int x = (int)((1 - t) * start.Column + t * end.Column);
|
||||
int y = (int)((1 - t) * start.Row + t * end.Row);
|
||||
return new Coord (x, y);
|
||||
}
|
||||
|
||||
public static double FindLengthOfBezierCurve (Coord start, List<Coord> controlPoints, Coord end)
|
||||
{
|
||||
double length = 0.0;
|
||||
Coord prevCoord = start;
|
||||
for (int i = 1; i <= 10; i++)
|
||||
{
|
||||
double t = i / 10.0;
|
||||
Coord coord = FindCoordOnBezierCurve (start, controlPoints, end, t);
|
||||
length += FindLengthOfLine (prevCoord, coord);
|
||||
prevCoord = coord;
|
||||
}
|
||||
return length;
|
||||
}
|
||||
|
||||
public static double FindLengthOfLine (Coord coord1, Coord coord2)
|
||||
{
|
||||
return Math.Sqrt (Math.Pow (coord2.Column - coord1.Column, 2) +
|
||||
Math.Pow (coord2.Row - coord1.Row, 2));
|
||||
}
|
||||
|
||||
public static double FindNormalizedDistanceFromCenter (int maxRow, int maxColumn, Coord otherCoord)
|
||||
{
|
||||
double center_x = maxColumn / 2.0;
|
||||
double center_y = maxRow / 2.0;
|
||||
double maxDistance = Math.Sqrt (Math.Pow (maxColumn, 2) + Math.Pow (maxRow, 2));
|
||||
double distance = Math.Sqrt (Math.Pow (otherCoord.Column - center_x, 2) +
|
||||
Math.Pow (otherCoord.Row - center_y, 2));
|
||||
return distance / (maxDistance / 2);
|
||||
}
|
||||
}
|
||||
@@ -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<int, string> xtermToHexMap = new Dictionary<int, string>
|
||||
{
|
||||
{0, "#000000"}, {1, "#800000"}, {2, "#008000"}, {3, "#808000"}, {4, "#000080"}, {5, "#800080"}, {6, "#008080"}, {7, "#c0c0c0"},
|
||||
{8, "#808080"}, {9, "#ff0000"}, {10, "#00ff00"}, {11, "#ffff00"}, {12, "#0000ff"}, {13, "#ff00ff"}, {14, "#00ffff"}, {15, "#ffffff"},
|
||||
{16, "#000000"}, {17, "#00005f"}, {18, "#000087"}, {19, "#0000af"}, {20, "#0000d7"}, {21, "#0000ff"}, {22, "#005f00"}, {23, "#005f5f"},
|
||||
{24, "#005f87"}, {25, "#005faf"}, {26, "#005fd7"}, {27, "#005fff"}, {28, "#008700"}, {29, "#00875f"}, {30, "#008787"}, {31, "#0087af"},
|
||||
{32, "#0087d7"}, {33, "#0087ff"}, {34, "#00af00"}, {35, "#00af5f"}, {36, "#00af87"}, {37, "#00afaf"}, {38, "#00afd7"}, {39, "#00afff"},
|
||||
{40, "#00d700"}, {41, "#00d75f"}, {42, "#00d787"}, {43, "#00d7af"}, {44, "#00d7d7"}, {45, "#00d7ff"}, {46, "#00ff00"}, {47, "#00ff5f"},
|
||||
{48, "#00ff87"}, {49, "#00ffaf"}, {50, "#00ffd7"}, {51, "#00ffff"}, {52, "#5f0000"}, {53, "#5f005f"}, {54, "#5f0087"}, {55, "#5f00af"},
|
||||
{56, "#5f00d7"}, {57, "#5f00ff"}, {58, "#5f5f00"}, {59, "#5f5f5f"}, {60, "#5f5f87"}, {61, "#5f5faf"}, {62, "#5f5fd7"}, {63, "#5f5fff"},
|
||||
{64, "#5f8700"}, {65, "#5f875f"}, {66, "#5f8787"}, {67, "#5f87af"}, {68, "#5f87d7"}, {69, "#5f87ff"}, {70, "#5faf00"}, {71, "#5faf5f"},
|
||||
{72, "#5faf87"}, {73, "#5fafaf"}, {74, "#5fafd7"}, {75, "#5fafff"}, {76, "#5fd700"}, {77, "#5fd75f"}, {78, "#5fd787"}, {79, "#5fd7af"},
|
||||
{80, "#5fd7d7"}, {81, "#5fd7ff"}, {82, "#5fff00"}, {83, "#5fff5f"}, {84, "#5fff87"}, {85, "#5fffaf"}, {86, "#5fffd7"}, {87, "#5fffff"},
|
||||
{88, "#870000"}, {89, "#87005f"}, {90, "#870087"}, {91, "#8700af"}, {92, "#8700d7"}, {93, "#8700ff"}, {94, "#875f00"}, {95, "#875f5f"},
|
||||
{96, "#875f87"}, {97, "#875faf"}, {98, "#875fd7"}, {99, "#875fff"}, {100, "#878700"}, {101, "#87875f"}, {102, "#878787"}, {103, "#8787af"},
|
||||
{104, "#8787d7"}, {105, "#8787ff"}, {106, "#87af00"}, {107, "#87af5f"}, {108, "#87af87"}, {109, "#87afaf"}, {110, "#87afd7"}, {111, "#87afff"},
|
||||
{112, "#87d700"}, {113, "#87d75f"}, {114, "#87d787"}, {115, "#87d7af"}, {116, "#87d7d7"}, {117, "#87d7ff"}, {118, "#87ff00"}, {119, "#87ff5f"},
|
||||
{120, "#87ff87"}, {121, "#87ffaf"}, {122, "#87ffd7"}, {123, "#87ffff"}, {124, "#af0000"}, {125, "#af005f"}, {126, "#af0087"}, {127, "#af00af"},
|
||||
{128, "#af00d7"}, {129, "#af00ff"}, {130, "#af5f00"}, {131, "#af5f5f"}, {132, "#af5f87"}, {133, "#af5faf"}, {134, "#af5fd7"}, {135, "#af5fff"},
|
||||
{136, "#af8700"}, {137, "#af875f"}, {138, "#af8787"}, {139, "#af87af"}, {140, "#af87d7"}, {141, "#af87ff"}, {142, "#afaf00"}, {143, "#afaf5f"},
|
||||
{144, "#afaf87"}, {145, "#afafaf"}, {146, "#afafd7"}, {147, "#afafff"}, {148, "#afd700"}, {149, "#afd75f"}, {150, "#afd787"}, {151, "#afd7af"},
|
||||
{152, "#afd7d7"}, {153, "#afd7ff"}, {154, "#afff00"}, {155, "#afff5f"}, {156, "#afff87"}, {157, "#afffaf"}, {158, "#afffd7"}, {159, "#afffff"},
|
||||
{160, "#d70000"}, {161, "#d7005f"}, {162, "#d70087"}, {163, "#d700af"}, {164, "#d700d7"}, {165, "#d700ff"}, {166, "#d75f00"}, {167, "#d75f5f"},
|
||||
{168, "#d75f87"}, {169, "#d75faf"}, {170, "#d75fd7"}, {171, "#d75fff"}, {172, "#d78700"}, {173, "#d7875f"}, {174, "#d78787"}, {175, "#d787af"},
|
||||
{176, "#d787d7"}, {177, "#d787ff"}, {178, "#d7af00"}, {179, "#d7af5f"}, {180, "#d7af87"}, {181, "#d7afaf"}, {182, "#d7afd7"}, {183, "#d7afff"},
|
||||
{184, "#d7d700"}, {185, "#d7d75f"}, {186, "#d7d787"}, {187, "#d7d7af"}, {188, "#d7d7d7"}, {189, "#d7d7ff"}, {190, "#d7ff00"}, {191, "#d7ff5f"},
|
||||
{192, "#d7ff87"}, {193, "#d7ffaf"}, {194, "#d7ffd7"}, {195, "#d7ffff"}, {196, "#ff0000"}, {197, "#ff005f"}, {198, "#ff0087"}, {199, "#ff00af"},
|
||||
{200, "#ff00d7"}, {201, "#ff00ff"}, {202, "#ff5f00"}, {203, "#ff5f5f"}, {204, "#ff5f87"}, {205, "#ff5faf"}, {206, "#ff5fd7"}, {207, "#ff5fff"},
|
||||
{208, "#ff8700"}, {209, "#ff875f"}, {210, "#ff8787"}, {211, "#ff87af"}, {212, "#ff87d7"}, {213, "#ff87ff"}, {214, "#ffaf00"}, {215, "#ffaf5f"},
|
||||
{216, "#ffaf87"}, {217, "#ffafaf"}, {218, "#ffafd7"}, {219, "#ffafff"}, {220, "#ffd700"}, {221, "#ffd75f"}, {222, "#ffd787"}, {223, "#ffd7af"},
|
||||
{224, "#ffd7d7"}, {225, "#ffd7ff"}, {226, "#ffff00"}, {227, "#ffff5f"}, {228, "#ffff87"}, {229, "#ffffaf"}, {230, "#ffffd7"}, {231, "#ffffff"},
|
||||
{232, "#080808"}, {233, "#121212"}, {234, "#1c1c1c"}, {235, "#262626"}, {236, "#303030"}, {237, "#3a3a3a"}, {238, "#444444"}, {239, "#4e4e4e"},
|
||||
{240, "#585858"}, {241, "#626262"}, {242, "#6c6c6c"}, {243, "#767676"}, {244, "#808080"}, {245, "#8a8a8a"}, {246, "#949494"}, {247, "#9e9e9e"},
|
||||
{248, "#a8a8a8"}, {249, "#b2b2b2"}, {250, "#bcbcbc"}, {251, "#c6c6c6"}, {252, "#d0d0d0"}, {253, "#dadada"}, {254, "#e4e4e4"}, {255, "#eeeeee"}
|
||||
};
|
||||
|
||||
private static readonly Dictionary<int, (int R, int G, int B)> xtermToRgbMap = xtermToHexMap.ToDictionary (
|
||||
item => item.Key,
|
||||
item => (
|
||||
R: Convert.ToInt32 (item.Value.Substring (1, 2), 16),
|
||||
G: Convert.ToInt32 (item.Value.Substring (3, 2), 16),
|
||||
B: Convert.ToInt32 (item.Value.Substring (5, 2), 16)
|
||||
));
|
||||
private static readonly Regex hexColorRegex = new Regex ("^#?[0-9A-Fa-f]{6}$");
|
||||
|
||||
public static bool IsValidHexColor (string hexColor)
|
||||
{
|
||||
return hexColorRegex.IsMatch (hexColor);
|
||||
}
|
||||
|
||||
public static bool IsValidXtermColor (int xtermColor)
|
||||
{
|
||||
return xtermColor >= 0 && xtermColor <= 255;
|
||||
}
|
||||
|
||||
public static string XtermToHex (int xtermColor)
|
||||
{
|
||||
if (xtermToHexMap.TryGetValue (xtermColor, out string hex))
|
||||
{
|
||||
return hex;
|
||||
}
|
||||
throw new ArgumentException ($"Invalid XTerm-256 color code: {xtermColor}");
|
||||
}
|
||||
|
||||
public static int HexToXterm (string hexColor)
|
||||
{
|
||||
if (!IsValidHexColor (hexColor))
|
||||
throw new ArgumentException ("Invalid RGB hex color format.");
|
||||
|
||||
hexColor = hexColor.StartsWith ("#") ? hexColor.Substring (1) : hexColor;
|
||||
var rgb = (
|
||||
R: Convert.ToInt32 (hexColor.Substring (0, 2), 16),
|
||||
G: Convert.ToInt32 (hexColor.Substring (2, 2), 16),
|
||||
B: Convert.ToInt32 (hexColor.Substring (4, 2), 16)
|
||||
);
|
||||
|
||||
return xtermToRgbMap.Aggregate ((current, next) =>
|
||||
ColorDifference (current.Value, rgb) < ColorDifference (next.Value, rgb) ? current : next).Key;
|
||||
}
|
||||
|
||||
private static double ColorDifference ((int R, int G, int B) c1, (int R, int G, int B) c2)
|
||||
{
|
||||
return Math.Sqrt (Math.Pow (c1.R - c2.R, 2) + Math.Pow (c1.G - c2.G, 2) + Math.Pow (c1.B - c2.B, 2));
|
||||
}
|
||||
}
|
||||
@@ -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<Coord> BezierControl { get; set; }
|
||||
|
||||
public Waypoint (string waypointId, Coord coord, List<Coord> bezierControl = null)
|
||||
{
|
||||
WaypointId = waypointId;
|
||||
Coord = coord;
|
||||
BezierControl = bezierControl ?? new List<Coord> ();
|
||||
}
|
||||
}
|
||||
|
||||
public class Segment
|
||||
{
|
||||
public Waypoint Start { get; private set; }
|
||||
public Waypoint End { get; private set; }
|
||||
public double Distance { get; private set; }
|
||||
public bool EnterEventTriggered { get; set; }
|
||||
public bool ExitEventTriggered { get; set; }
|
||||
|
||||
public Segment (Waypoint start, Waypoint end)
|
||||
{
|
||||
Start = start;
|
||||
End = end;
|
||||
Distance = CalculateDistance (start, end);
|
||||
}
|
||||
|
||||
private double CalculateDistance (Waypoint start, Waypoint end)
|
||||
{
|
||||
// Add bezier control point distance calculation if needed
|
||||
return Math.Sqrt (Math.Pow (end.Coord.Column - start.Coord.Column, 2) + Math.Pow (end.Coord.Row - start.Coord.Row, 2));
|
||||
}
|
||||
|
||||
public Coord GetCoordOnSegment (double distanceFactor)
|
||||
{
|
||||
int column = (int)(Start.Coord.Column + (End.Coord.Column - Start.Coord.Column) * distanceFactor);
|
||||
int row = (int)(Start.Coord.Row + (End.Coord.Row - Start.Coord.Row) * distanceFactor);
|
||||
return new Coord (column, row);
|
||||
}
|
||||
}
|
||||
public class Path
|
||||
{
|
||||
public string PathId { get; private set; }
|
||||
public double Speed { get; set; }
|
||||
public Func<double, double> EaseFunction { get; set; }
|
||||
public int Layer { get; set; }
|
||||
public int HoldTime { get; set; }
|
||||
public bool Loop { get; set; }
|
||||
public List<Segment> Segments { get; private set; } = new List<Segment> ();
|
||||
public int CurrentStep { get; set; }
|
||||
public double TotalDistance { get; set; }
|
||||
public double LastDistanceReached { get; set; }
|
||||
public int MaxSteps => (int)Math.Ceiling (TotalDistance / Speed); // Calculates max steps based on total distance and speed
|
||||
|
||||
public Path (string pathId, double speed, Func<double, double> easeFunction = null, int layer = 0, int holdTime = 0, bool loop = false)
|
||||
{
|
||||
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<string, Path> Paths { get; private set; } = new Dictionary<string, Path> ();
|
||||
public Path ActivePath { get; private set; }
|
||||
public Coord CurrentCoord { get; set; }
|
||||
public Coord PreviousCoord { get; set; }
|
||||
public EffectCharacter Character { get; private set; } // Assuming EffectCharacter is similar to base_character.EffectCharacter
|
||||
|
||||
public Motion (EffectCharacter character)
|
||||
{
|
||||
Character = character;
|
||||
CurrentCoord = new Coord (character.InputCoord.Column, character.InputCoord.Row); // Assuming similar properties
|
||||
PreviousCoord = new Coord (-1, -1);
|
||||
}
|
||||
|
||||
public void SetCoordinate (Coord coord)
|
||||
{
|
||||
CurrentCoord = coord;
|
||||
}
|
||||
|
||||
public Path CreatePath (string pathId, double speed, Func<double, double> easeFunction = null, int layer = 0, int holdTime = 0, bool loop = false)
|
||||
{
|
||||
if (Paths.ContainsKey (pathId))
|
||||
throw new ArgumentException ($"A path with ID {pathId} already exists.");
|
||||
|
||||
var path = new Path (pathId, speed, easeFunction, layer, holdTime, loop);
|
||||
Paths [pathId] = path;
|
||||
return path;
|
||||
}
|
||||
|
||||
public Path QueryPath (string pathId)
|
||||
{
|
||||
if (!Paths.TryGetValue (pathId, out var path))
|
||||
throw new KeyNotFoundException ($"No path found with ID {pathId}.");
|
||||
|
||||
return path;
|
||||
}
|
||||
|
||||
public bool MovementIsComplete ()
|
||||
{
|
||||
return ActivePath == null || ActivePath.CurrentStep >= ActivePath.TotalDistance;
|
||||
}
|
||||
|
||||
public void ActivatePath (Path path)
|
||||
{
|
||||
if (path == null)
|
||||
throw new ArgumentNullException (nameof (path), "Path cannot be null when activating.");
|
||||
|
||||
ActivePath = path;
|
||||
ActivePath.CurrentStep = 0; // Reset the path's progress
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set the active path to None if the active path is the given path.
|
||||
/// </summary>
|
||||
public void DeactivatePath (Path p)
|
||||
{
|
||||
if (p == ActivePath)
|
||||
{
|
||||
ActivePath = null;
|
||||
}
|
||||
}
|
||||
public void DeactivatePath ()
|
||||
{
|
||||
ActivePath = null;
|
||||
}
|
||||
|
||||
public void Move ()
|
||||
{
|
||||
if (ActivePath != null)
|
||||
{
|
||||
PreviousCoord = CurrentCoord;
|
||||
CurrentCoord = ActivePath.Step ();
|
||||
ActivePath.CurrentStep++;
|
||||
|
||||
if (ActivePath.CurrentStep >= ActivePath.TotalDistance)
|
||||
{
|
||||
if (ActivePath.Loop)
|
||||
ActivePath.CurrentStep = 0; // Reset the path for looping
|
||||
else
|
||||
DeactivatePath (); // Deactivate the path if it is not set to loop
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void ChainPaths (IEnumerable<Path> paths, bool loop = false)
|
||||
{
|
||||
var pathList = paths.ToList ();
|
||||
for (int i = 0; i < pathList.Count; i++)
|
||||
{
|
||||
var currentPath = pathList [i];
|
||||
var nextPath = i + 1 < pathList.Count ? pathList [i + 1] : pathList.FirstOrDefault ();
|
||||
|
||||
// Here we could define an event system to trigger path activation when another completes
|
||||
// For example, you could listen for a "path complete" event and then activate the next path
|
||||
if (loop && nextPath != null)
|
||||
{
|
||||
// Implementation depends on your event system
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
/// <summary>
|
||||
/// Implementation of <see cref="IFill"/> that uses a color gradient (including
|
||||
/// radial, diagonal etc).
|
||||
/// </summary>
|
||||
public class GradientFill : IFill
|
||||
{
|
||||
private Dictionary<Point, Terminal.Gui.Color> _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
|
||||
}
|
||||
}
|
||||
@@ -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<Coord, EffectCharacter> CharacterByInputCoord = new Dictionary<Coord, EffectCharacter> ();
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -3,10 +3,8 @@ using System.Collections.Generic;
|
||||
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 class TextEffectsScenario : Scenario
|
||||
},
|
||||
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 class TextEffectsScenario : Scenario
|
||||
// Define the colors of the gradient stops with more appealing colors
|
||||
stops = new List<Color>
|
||||
{
|
||||
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 @@ internal class GradientsView : View
|
||||
// Define the colors of the gradient stops
|
||||
var stops = new List<Color>
|
||||
{
|
||||
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 @@ internal class GradientsView : View
|
||||
{
|
||||
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 @@ internal class GradientsView : View
|
||||
// Define the colors of the rainbow
|
||||
var stops = new List<Color>
|
||||
{
|
||||
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 @@ internal class GradientsView : View
|
||||
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'));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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<ArgumentException> (() => 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<string> { "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<string> { "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> (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> (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 ());
|
||||
}
|
||||
}
|
||||
@@ -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 class GradientFillTests
|
||||
// Define the colors of the gradient stops
|
||||
var stops = new List<Color>
|
||||
{
|
||||
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
|
||||
|
||||
Reference in New Issue
Block a user