mirror of
https://github.com/gui-cs/Terminal.Gui.git
synced 2025-12-29 17:28:01 +01:00
Radial gradient
This commit is contained in:
273
Terminal.Gui/TextEffects/ArgValidators.cs
Normal file
273
Terminal.Gui/TextEffects/ArgValidators.cs
Normal file
@@ -0,0 +1,273 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using Terminal.Gui.TextEffects;
|
||||
|
||||
using Color = Terminal.Gui.TextEffects.Color;
|
||||
|
||||
public static class PositiveInt
|
||||
{
|
||||
public static int Parse (string arg)
|
||||
{
|
||||
if (int.TryParse (arg, out int value) && value > 0)
|
||||
{
|
||||
return value;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new ArgumentException ($"invalid value: '{arg}' is not > 0.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static class NonNegativeInt
|
||||
{
|
||||
public static int Parse (string arg)
|
||||
{
|
||||
if (int.TryParse (arg, out int value) && value >= 0)
|
||||
{
|
||||
return value;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new ArgumentException ($"invalid value: '{arg}' Argument must be int >= 0.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static class IntRange
|
||||
{
|
||||
public static (int, int) Parse (string arg)
|
||||
{
|
||||
var parts = arg.Split ('-');
|
||||
if (parts.Length == 2 && int.TryParse (parts [0], out int start) && int.TryParse (parts [1], out int end) && start > 0 && start <= end)
|
||||
{
|
||||
return (start, end);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new ArgumentException ($"invalid range: '{arg}' is not a valid range. Must be start-end. Ex: 1-10");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static class PositiveFloat
|
||||
{
|
||||
public static float Parse (string arg)
|
||||
{
|
||||
if (float.TryParse (arg, out float value) && value > 0)
|
||||
{
|
||||
return value;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new ArgumentException ($"invalid value: '{arg}' is not a valid value. Argument must be a float > 0.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static class NonNegativeFloat
|
||||
{
|
||||
public static float Parse (string arg)
|
||||
{
|
||||
if (float.TryParse (arg, out float value) && value >= 0)
|
||||
{
|
||||
return value;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new ArgumentException ($"invalid argument value: '{arg}' is out of range. Must be float >= 0.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static class PositiveFloatRange
|
||||
{
|
||||
public static (float, float) Parse (string arg)
|
||||
{
|
||||
var parts = arg.Split ('-');
|
||||
if (parts.Length == 2 && float.TryParse (parts [0], out float start) && float.TryParse (parts [1], out float end) && start > 0 && start <= end)
|
||||
{
|
||||
return (start, end);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new ArgumentException ($"invalid range: '{arg}' is not a valid range. Must be start-end. Ex: 0.1-1.0");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static class Ratio
|
||||
{
|
||||
public static float Parse (string arg)
|
||||
{
|
||||
if (float.TryParse (arg, out float value) && value >= 0 && value <= 1)
|
||||
{
|
||||
return value;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new ArgumentException ($"invalid value: '{arg}' is not a float >= 0 and <= 1. Example: 0.5");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public enum GradientDirection
|
||||
{
|
||||
Horizontal,
|
||||
Vertical,
|
||||
Diagonal,
|
||||
Radial
|
||||
}
|
||||
|
||||
public static class GradientDirectionParser
|
||||
{
|
||||
public static GradientDirection Parse (string arg)
|
||||
{
|
||||
return arg.ToLower () switch
|
||||
{
|
||||
"horizontal" => GradientDirection.Horizontal,
|
||||
"vertical" => GradientDirection.Vertical,
|
||||
"diagonal" => GradientDirection.Diagonal,
|
||||
"radial" => GradientDirection.Radial,
|
||||
_ => throw new ArgumentException ($"invalid gradient direction: '{arg}' is not a valid gradient direction. Choices are diagonal, horizontal, vertical, or radial."),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public static class ColorArg
|
||||
{
|
||||
public static Color Parse (string arg)
|
||||
{
|
||||
if (int.TryParse (arg, out int xtermValue) && xtermValue >= 0 && xtermValue <= 255)
|
||||
{
|
||||
return new Color (xtermValue);
|
||||
}
|
||||
else if (arg.Length == 6 && int.TryParse (arg, NumberStyles.HexNumber, null, out int _))
|
||||
{
|
||||
return new Color (arg);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new ArgumentException ($"invalid color value: '{arg}' is not a valid XTerm or RGB color. Must be in range 0-255 or 000000-FFFFFF.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static class Symbol
|
||||
{
|
||||
public static string Parse (string arg)
|
||||
{
|
||||
if (arg.Length == 1 && IsAsciiOrUtf8 (arg))
|
||||
{
|
||||
return arg;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new ArgumentException ($"invalid symbol: '{arg}' is not a valid symbol. Must be a single ASCII/UTF-8 character.");
|
||||
}
|
||||
}
|
||||
|
||||
private static bool IsAsciiOrUtf8 (string s)
|
||||
{
|
||||
try
|
||||
{
|
||||
Encoding.ASCII.GetBytes (s);
|
||||
}
|
||||
catch (EncoderFallbackException)
|
||||
{
|
||||
try
|
||||
{
|
||||
Encoding.UTF8.GetBytes (s);
|
||||
}
|
||||
catch (EncoderFallbackException)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public static class CanvasDimension
|
||||
{
|
||||
public static int Parse (string arg)
|
||||
{
|
||||
if (int.TryParse (arg, out int value) && value >= -1)
|
||||
{
|
||||
return value;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new ArgumentException ($"invalid value: '{arg}' is not >= -1.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static class TerminalDimensions
|
||||
{
|
||||
public static (int, int) Parse (string arg)
|
||||
{
|
||||
var parts = arg.Split (' ');
|
||||
if (parts.Length == 2 && int.TryParse (parts [0], out int width) && int.TryParse (parts [1], out int height) && width >= 0 && height >= 0)
|
||||
{
|
||||
return (width, height);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new ArgumentException ($"invalid terminal dimensions: '{arg}' is not a valid terminal dimension. Must be >= 0.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static class Ease
|
||||
{
|
||||
private static readonly Dictionary<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.");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6,10 +6,16 @@ public abstract class BaseEffectIterator<T> where T : EffectConfig, new()
|
||||
protected Terminal 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 Terminal (effect.InputData, effect.TerminalConfig);
|
||||
|
||||
}
|
||||
|
||||
public void Update ()
|
||||
|
||||
241
Terminal.Gui/TextEffects/Effects/Beams.cs
Normal file
241
Terminal.Gui/TextEffects/Effects/Beams.cs
Normal file
@@ -0,0 +1,241 @@
|
||||
/*namespace Terminal.Gui.TextEffects.Effects;
|
||||
|
||||
public class BeamsConfig : EffectConfig
|
||||
{
|
||||
public string [] BeamRowSymbols { get; set; } = { "▂", "▁", "_" };
|
||||
public string [] BeamColumnSymbols { get; set; } = { "▌", "▍", "▎", "▏" };
|
||||
public int BeamDelay { get; set; } = 10;
|
||||
public (int, int) BeamRowSpeedRange { get; set; } = (10, 40);
|
||||
public (int, int) BeamColumnSpeedRange { get; set; } = (6, 10);
|
||||
public Color [] BeamGradientStops { get; set; } = { new Color ("ffffff"), new Color ("00D1FF"), new Color ("8A008A") };
|
||||
public int [] BeamGradientSteps { get; set; } = { 2, 8 };
|
||||
public int BeamGradientFrames { get; set; } = 2;
|
||||
public Color [] FinalGradientStops { get; set; } = { new Color ("8A008A"), new Color ("00D1FF"), new Color ("ffffff") };
|
||||
public int [] FinalGradientSteps { get; set; } = { 12 };
|
||||
public int FinalGradientFrames { get; set; } = 5;
|
||||
public GradientDirection FinalGradientDirection { get; set; } = GradientDirection.Vertical;
|
||||
public int FinalWipeSpeed { get; set; } = 1;
|
||||
}
|
||||
|
||||
public class Beams : BaseEffect<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;
|
||||
}
|
||||
}
|
||||
}
|
||||
*/
|
||||
@@ -54,51 +54,33 @@ public class Color
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public class Gradient
|
||||
{
|
||||
public List<Color> Spectrum { get; private set; }
|
||||
private readonly bool _loop;
|
||||
private readonly List<Color> _stops;
|
||||
private readonly List<int> _steps;
|
||||
|
||||
public enum Direction
|
||||
{
|
||||
Vertical,
|
||||
Horizontal,
|
||||
Radial,
|
||||
Diagonal
|
||||
}
|
||||
|
||||
// Constructor now accepts IEnumerable<int> for steps.
|
||||
public Gradient (IEnumerable<Color> stops, IEnumerable<int> steps, bool loop = false)
|
||||
{
|
||||
if (stops == null || !stops.Any () || stops.Count () < 2)
|
||||
throw new ArgumentException ("At least two color stops are required to create a gradient.");
|
||||
if (steps == null || !steps.Any ())
|
||||
throw new ArgumentException ("Steps are required to define the transitions between colors.");
|
||||
_stops = stops.ToList ();
|
||||
if (_stops.Count < 1)
|
||||
throw new ArgumentException ("At least one color stop must be provided.");
|
||||
|
||||
Spectrum = GenerateGradient (stops.ToList (), steps.ToList (), loop);
|
||||
}
|
||||
_steps = steps.ToList ();
|
||||
if (_steps.Any (step => step < 1))
|
||||
throw new ArgumentException ("Steps must be greater than 0.");
|
||||
|
||||
private List<Color> GenerateGradient (List<Color> stops, List<int> steps, bool loop)
|
||||
{
|
||||
List<Color> gradient = new List<Color> ();
|
||||
if (loop)
|
||||
stops.Add (stops [0]); // Loop the gradient back to the first color.
|
||||
|
||||
for (int i = 0; i < stops.Count - 1; i++)
|
||||
{
|
||||
int currentSteps = i < steps.Count ? steps [i] : steps.Last ();
|
||||
gradient.AddRange (InterpolateColors (stops [i], stops [i + 1], currentSteps));
|
||||
}
|
||||
|
||||
return gradient;
|
||||
}
|
||||
|
||||
private IEnumerable<Color> InterpolateColors (Color start, Color end, int steps)
|
||||
{
|
||||
for (int step = 0; step <= steps; step++)
|
||||
{
|
||||
int r = Interpolate (start.R, end.R, steps, step);
|
||||
int g = Interpolate (start.G, end.G, steps, step);
|
||||
int b = Interpolate (start.B, end.B, steps, step);
|
||||
yield return Color.FromRgb (r, g, b);
|
||||
}
|
||||
}
|
||||
|
||||
private int Interpolate (int start, int end, int steps, int currentStep)
|
||||
{
|
||||
return start + (int)((end - start) * (double)currentStep / steps);
|
||||
_loop = loop;
|
||||
Spectrum = GenerateGradient (_steps);
|
||||
}
|
||||
|
||||
public Color GetColorAtFraction (double fraction)
|
||||
@@ -108,6 +90,113 @@ public class Gradient
|
||||
int index = (int)(fraction * (Spectrum.Count - 1));
|
||||
return Spectrum [index];
|
||||
}
|
||||
}
|
||||
|
||||
private List<Color> GenerateGradient (IEnumerable<int> steps)
|
||||
{
|
||||
List<Color> gradient = new List<Color> ();
|
||||
if (_stops.Count == 1)
|
||||
{
|
||||
for (int i = 0; i < steps.Sum (); i++)
|
||||
{
|
||||
gradient.Add (_stops [0]);
|
||||
}
|
||||
return gradient;
|
||||
}
|
||||
|
||||
if (_loop)
|
||||
{
|
||||
_stops.Add (_stops [0]);
|
||||
}
|
||||
|
||||
var colorPairs = _stops.Zip (_stops.Skip (1), (start, end) => new { start, end });
|
||||
var stepsList = _steps.ToList ();
|
||||
|
||||
foreach (var (colorPair, thesteps) in colorPairs.Zip (stepsList, (pair, step) => (pair, step)))
|
||||
{
|
||||
gradient.AddRange (InterpolateColors (colorPair.start, colorPair.end, thesteps));
|
||||
}
|
||||
|
||||
return gradient;
|
||||
}
|
||||
|
||||
private IEnumerable<Color> InterpolateColors (Color start, Color end, int steps)
|
||||
{
|
||||
for (int step = 0; step <= steps; step++)
|
||||
{
|
||||
double fraction = (double)step / steps;
|
||||
int r = (int)(start.R + fraction * (end.R - start.R));
|
||||
int g = (int)(start.G + fraction * (end.G - start.G));
|
||||
int b = (int)(start.B + fraction * (end.B - start.B));
|
||||
yield return Color.FromRgb (r, g, b);
|
||||
}
|
||||
}
|
||||
|
||||
public Dictionary<Coord, Color> BuildCoordinateColorMapping (int maxRow, int maxColumn, Direction direction)
|
||||
{
|
||||
var gradientMapping = new Dictionary<Coord, Color> ();
|
||||
|
||||
switch (direction)
|
||||
{
|
||||
case Direction.Vertical:
|
||||
for (int row = 0; row <= maxRow; row++)
|
||||
{
|
||||
double fraction = maxRow == 0 ? 1.0 : (double)row / maxRow;
|
||||
Color color = GetColorAtFraction (fraction);
|
||||
for (int col = 0; col <= maxColumn; col++)
|
||||
{
|
||||
gradientMapping [new Coord (col, row)] = color;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case Direction.Horizontal:
|
||||
for (int col = 0; col <= maxColumn; col++)
|
||||
{
|
||||
double fraction = maxColumn == 0 ? 1.0 : (double)col / maxColumn;
|
||||
Color color = GetColorAtFraction (fraction);
|
||||
for (int row = 0; row <= maxRow; row++)
|
||||
{
|
||||
gradientMapping [new Coord (col, row)] = color;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case Direction.Radial:
|
||||
for (int row = 0; row <= maxRow; row++)
|
||||
{
|
||||
for (int col = 0; col <= maxColumn; col++)
|
||||
{
|
||||
double distanceFromCenter = FindNormalizedDistanceFromCenter (maxRow, maxColumn, new Coord (col, row));
|
||||
Color color = GetColorAtFraction (distanceFromCenter);
|
||||
gradientMapping [new Coord (col, row)] = color;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case Direction.Diagonal:
|
||||
for (int row = 0; row <= maxRow; row++)
|
||||
{
|
||||
for (int col = 0; col <= maxColumn; col++)
|
||||
{
|
||||
double fraction = ((double)row * 2 + col) / ((maxRow * 2) + maxColumn);
|
||||
Color color = GetColorAtFraction (fraction);
|
||||
gradientMapping [new Coord (col, row)] = color;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return gradientMapping;
|
||||
}
|
||||
|
||||
private double FindNormalizedDistanceFromCenter (int maxRow, int maxColumn, Coord coord)
|
||||
{
|
||||
double centerX = maxColumn / 2.0;
|
||||
double centerY = maxRow / 2.0;
|
||||
double dx = coord.Column - centerX;
|
||||
double dy = coord.Row - centerY;
|
||||
double distance = Math.Sqrt (dx * dx + dy * dy);
|
||||
double maxDistance = Math.Sqrt (centerX * centerX + centerY * centerY);
|
||||
return distance / maxDistance;
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,4 @@
|
||||
namespace Terminal.Gui.TextEffects;
|
||||
|
||||
public class Coord
|
||||
{
|
||||
public int Column { get; set; }
|
||||
@@ -12,6 +11,34 @@ public class Coord
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
@@ -84,6 +84,53 @@ internal class TextEffectsExampleView : View
|
||||
resized = false;
|
||||
}
|
||||
|
||||
DrawTopLineGradient (viewport);
|
||||
DrawRadialGradient (viewport);
|
||||
|
||||
_ball?.Draw ();
|
||||
}
|
||||
|
||||
private void DrawRadialGradient (Rectangle viewport)
|
||||
{
|
||||
// Define the colors of the gradient stops
|
||||
var stops = new List<Color>
|
||||
{
|
||||
Color.FromRgb(255, 0, 0), // Red
|
||||
Color.FromRgb(0, 255, 0), // Green
|
||||
Color.FromRgb(238, 130, 238) // Violet
|
||||
};
|
||||
|
||||
// Define the number of steps between each color
|
||||
var steps = new List<int> { 10, 10 }; // 10 steps between Red -> Green, and Green -> Blue
|
||||
|
||||
// Create the gradient
|
||||
var radialGradient = new Gradient (stops, steps, loop: false);
|
||||
|
||||
// Define the size of the rectangle
|
||||
int maxRow = 20;
|
||||
int maxColumn = 40;
|
||||
|
||||
// Build the coordinate-color mapping for a radial gradient
|
||||
var gradientMapping = radialGradient.BuildCoordinateColorMapping (maxRow, maxColumn, Gradient.Direction.Radial);
|
||||
|
||||
// Print the gradient
|
||||
for (int row = 0; row <= maxRow; row++)
|
||||
{
|
||||
for (int col = 0; col <= maxColumn; col++)
|
||||
{
|
||||
var coord = new Coord (col, row);
|
||||
var color = gradientMapping [coord];
|
||||
|
||||
SetColor (color);
|
||||
|
||||
AddRune (col+2, row+3, new Rune ('█'));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawTopLineGradient (Rectangle viewport)
|
||||
{
|
||||
|
||||
// Define the colors of the rainbow
|
||||
var stops = new List<Color>
|
||||
{
|
||||
@@ -115,17 +162,21 @@ internal class TextEffectsExampleView : View
|
||||
double fraction = (double)x / (viewport.Width - 1);
|
||||
Color color = rainbowGradient.GetColorAtFraction (fraction);
|
||||
|
||||
// Assuming AddRune is a method you have for drawing at specific positions
|
||||
Application.Driver.SetAttribute (
|
||||
new Attribute (
|
||||
new Terminal.Gui.Color (color.R, color.G, color.B),
|
||||
new Terminal.Gui.Color (color.R, color.G, color.B)
|
||||
)); // Setting color based on RGB
|
||||
SetColor (color);
|
||||
|
||||
AddRune (x, 0, new Rune ('█'));
|
||||
}
|
||||
}
|
||||
|
||||
private void SetColor (Color color)
|
||||
{
|
||||
// Assuming AddRune is a method you have for drawing at specific positions
|
||||
Application.Driver.SetAttribute (
|
||||
new Attribute (
|
||||
new Terminal.Gui.Color (color.R, color.G, color.B),
|
||||
new Terminal.Gui.Color (color.R, color.G, color.B)
|
||||
)); // Setting color based on RGB
|
||||
|
||||
_ball?.Draw ();
|
||||
}
|
||||
|
||||
public class Ball
|
||||
|
||||
Reference in New Issue
Block a user